From a255a6fb6967e77fa821633b06a0662a40755502 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 3 Apr 2018 17:37:17 -0700 Subject: [PATCH 0001/1635] use build_response method --- src/httpresponse.rs | 24 ++++++++++++------------ src/json.rs | 5 +++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 1f763159..5edb6de5 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -626,8 +626,8 @@ impl Responder for &'static str { type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok() + fn respond_to(self, req: HttpRequest) -> Result { + Ok(req.build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) } @@ -645,8 +645,8 @@ impl Responder for &'static [u8] { type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok() + fn respond_to(self, req: HttpRequest) -> Result { + Ok(req.build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) } @@ -664,8 +664,8 @@ impl Responder for String { type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok() + fn respond_to(self, req: HttpRequest) -> Result { + Ok(req.build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) } @@ -683,8 +683,8 @@ impl<'a> Responder for &'a String { type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok() + fn respond_to(self, req: HttpRequest) -> Result { + Ok(req.build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) } @@ -702,8 +702,8 @@ impl Responder for Bytes { type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok() + fn respond_to(self, req: HttpRequest) -> Result { + Ok(req.build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) } @@ -721,8 +721,8 @@ impl Responder for BytesMut { type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok() + fn respond_to(self, req: HttpRequest) -> Result { + Ok(req.build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) } diff --git a/src/json.rs b/src/json.rs index 3c8f81e8..bef34960 100644 --- a/src/json.rs +++ b/src/json.rs @@ -11,6 +11,7 @@ use serde::de::DeserializeOwned; use error::{Error, JsonPayloadError, PayloadError}; use handler::{Responder, FromRequest}; +use http::StatusCode; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -71,10 +72,10 @@ impl Responder for Json { type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { + fn respond_to(self, req: HttpRequest) -> Result { let body = serde_json::to_string(&self.0)?; - Ok(HttpResponse::Ok() + Ok(req.build_response(StatusCode::OK) .content_type("application/json") .body(body)) } From df21892b5b6f25066507430eb90682746c0d4e5f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 3 Apr 2018 22:06:18 -0700 Subject: [PATCH 0002/1635] added extractor configuration --- src/application.rs | 8 +-- src/extractor.rs | 87 ++++++++++++++++++++++++++------ src/handler.rs | 10 +++- src/httprequest.rs | 3 +- src/json.rs | 55 +++++++++++++++++++-- src/lib.rs | 3 +- src/resource.rs | 2 +- src/route.rs | 22 +++++++-- src/test.rs | 4 +- src/with.rs | 121 +++++++++++++++++++++++++++++++++++---------- 10 files changed, 253 insertions(+), 62 deletions(-) diff --git a/src/application.rs b/src/application.rs index 38886efc..96a932c9 100644 --- a/src/application.rs +++ b/src/application.rs @@ -255,8 +255,8 @@ impl App where S: 'static { /// }); /// } /// ``` - pub fn resource(mut self, path: &str, f: F) -> App - where F: FnOnce(&mut ResourceHandler) + 'static + pub fn resource(mut self, path: &str, f: F) -> App + where F: FnOnce(&mut ResourceHandler) -> R + 'static { { let parts = self.parts.as_mut().expect("Use after finish"); @@ -272,8 +272,8 @@ impl App where S: 'static { } /// Default resource is used if no matched route could be found. - pub fn default_resource(mut self, f: F) -> App - where F: FnOnce(&mut ResourceHandler) + 'static + pub fn default_resource(mut self, f: F) -> App + where F: FnOnce(&mut ResourceHandler) -> R + 'static { { let parts = self.parts.as_mut().expect("Use after finish"); diff --git a/src/extractor.rs b/src/extractor.rs index 2346365b..6f0e5b33 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -100,10 +100,11 @@ impl Path { impl FromRequest for Path where T: DeserializeOwned, S: 'static { + type Config = (); type Result = FutureResult; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); result(de::Deserialize::deserialize(PathDeserializer::new(&req)) .map_err(|e| e.into()) @@ -165,10 +166,11 @@ impl Query { impl FromRequest for Query where T: de::DeserializeOwned, S: 'static { + type Config = (); type Result = FutureResult; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); result(serde_urlencoded::from_str::(req.query_string()) .map_err(|e| e.into()) @@ -223,11 +225,60 @@ impl DerefMut for Form { impl FromRequest for Form where T: DeserializeOwned + 'static, S: 'static { + type Config = FormConfig; type Result = Box>; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - Box::new(UrlEncoded::new(req.clone()).from_err().map(Form)) + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + Box::new(UrlEncoded::new(req.clone()) + .limit(cfg.limit) + .from_err() + .map(Form)) + } +} + +/// Form extractor configuration +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Form, Result, http}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// /// extract form data using serde, max payload size is 4k +/// fn index(form: Form) -> Result { +/// Ok(format!("Welcome {}!", form.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", |r| { +/// r.method(http::Method::GET) +/// .with(index) +/// .limit(4096);} // <- change form extractor configuration +/// ); +/// } +/// ``` +pub struct FormConfig { + limit: usize, +} + +impl FormConfig { + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(&mut self, limit: usize) -> &mut Self { + self.limit = limit; + self + } +} + +impl Default for FormConfig { + fn default() -> Self { + FormConfig{limit: 262_144} } } @@ -250,10 +301,11 @@ impl FromRequest for Form /// ``` impl FromRequest for Bytes { + type Config = (); type Result = Box>; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { Box::new(MessageBody::new(req.clone()).from_err()) } } @@ -276,11 +328,12 @@ impl FromRequest for Bytes /// ``` impl FromRequest for String { + type Config = (); type Result = Either, Box>>; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let encoding = match req.encoding() { Err(_) => return Either::A( result(Err(ErrorBadRequest("Unknown request charset")))), @@ -325,7 +378,7 @@ mod tests { let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match Bytes::from_request(&req).poll().unwrap() { + match Bytes::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s, Bytes::from_static(b"hello=world")); }, @@ -338,7 +391,7 @@ mod tests { let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match String::from_request(&req).poll().unwrap() { + match String::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s, "hello=world"); }, @@ -354,7 +407,9 @@ mod tests { .finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match Form::::from_request(&req).poll().unwrap() { + let mut cfg = FormConfig::default(); + cfg.limit(4096); + match Form::::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.hello, "world"); }, @@ -390,7 +445,7 @@ mod tests { let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req).poll().unwrap() { + match Path::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); @@ -398,7 +453,7 @@ mod tests { _ => unreachable!(), } - match Path::<(String, String)>::from_request(&req).poll().unwrap() { + match Path::<(String, String)>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); @@ -406,7 +461,7 @@ mod tests { _ => unreachable!(), } - match Query::::from_request(&req).poll().unwrap() { + match Query::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.id, "test"); }, @@ -416,7 +471,7 @@ mod tests { let mut req = TestRequest::with_uri("/name/32/").finish(); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req).poll().unwrap() { + match Path::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.as_ref().key, "name"); assert_eq!(s.value, 32); @@ -424,7 +479,7 @@ mod tests { _ => unreachable!(), } - match Path::<(String, u8)>::from_request(&req).poll().unwrap() { + match Path::<(String, u8)>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, 32); @@ -432,7 +487,7 @@ mod tests { _ => unreachable!(), } - match Path::>::from_request(&req).poll().unwrap() { + match Path::>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]); }, @@ -451,7 +506,7 @@ mod tests { let mut req = TestRequest::with_uri("/32/").finish(); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req).poll().unwrap() { + match Path::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.into_inner(), 32); }, diff --git a/src/handler.rs b/src/handler.rs index 6041dc28..edfd1edb 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -37,9 +37,14 @@ pub trait Responder { /// Types that implement this trait can be used with `Route::with()` method. pub trait FromRequest: Sized where S: 'static { + /// Configuration for conversion process + type Config: Default; + + /// Future that resolves to a Self type Result: Future; - fn from_request(req: &HttpRequest) -> Self::Result; + /// Convert request to a Self + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; } /// Combines two different responder types into a single type @@ -433,10 +438,11 @@ impl Deref for State { impl FromRequest for State { + type Config = (); type Result = FutureResult; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { ok(State(req.clone())) } } diff --git a/src/httprequest.rs b/src/httprequest.rs index 00aacb81..8077ab23 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -494,10 +494,11 @@ impl Clone for HttpRequest { impl FromRequest for HttpRequest { + type Config = (); type Result = FutureResult; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { result(Ok(req.clone())) } } diff --git a/src/json.rs b/src/json.rs index bef34960..722b35ce 100644 --- a/src/json.rs +++ b/src/json.rs @@ -110,17 +110,64 @@ impl Responder for Json { impl FromRequest for Json where T: DeserializeOwned + 'static, S: 'static { + type Config = JsonConfig; type Result = Box>; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { Box::new( JsonBody::new(req.clone()) + .limit(cfg.limit) .from_err() .map(Json)) } } +/// Json extractor configuration +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Json, Result, http}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: Json) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", |r| { +/// r.method(http::Method::POST) +/// .with(index) +/// .limit(4096);} // <- change json extractor configuration +/// ); +/// } +/// ``` +pub struct JsonConfig { + limit: usize, +} + +impl JsonConfig { + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(&mut self, limit: usize) -> &mut Self { + self.limit = limit; + self + } +} + +impl Default for JsonConfig { + fn default() -> Self { + JsonConfig{limit: 262_144} + } +} + /// Request payload json parser that resolves to a deserialized `T` value. /// /// Returns error: @@ -231,7 +278,7 @@ mod tests { use http::header; use futures::Async; - use with::With; + use with::{With, ExtractorConfig}; use handler::Handler; impl PartialEq for JsonPayloadError { @@ -295,7 +342,9 @@ mod tests { #[test] fn test_with_json() { - let mut handler = With::new(|data: Json| data); + let mut cfg = ExtractorConfig::<_, Json>::default(); + cfg.limit(4096); + let mut handler = With::new(|data: Json| {data}, cfg); let req = HttpRequest::default(); let err = handler.handle(req).as_response().unwrap().error().is_some(); diff --git a/src/lib.rs b/src/lib.rs index 3e134c44..bf536e2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,9 +177,10 @@ pub mod dev { pub use body::BodyStream; pub use context::Drain; - pub use json::JsonBody; + pub use json::{JsonBody, JsonConfig}; pub use info::ConnectionInfo; pub use handler::{Handler, Reply}; + pub use extractor::{FormConfig}; pub use route::Route; pub use router::{Router, Resource, ResourceType}; pub use resource::ResourceHandler; diff --git a/src/resource.rs b/src/resource.rs index f28363e2..0ffc6412 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -144,7 +144,7 @@ impl ResourceHandler { T: FromRequest + 'static, { self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with(handler) + self.routes.last_mut().unwrap().with(handler); } /// Register a resource middleware diff --git a/src/route.rs b/src/route.rs index 1eebaa3e..a2c1947e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -11,7 +11,7 @@ use handler::{Reply, ReplyItem, Handler, FromRequest, use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use with::{With, With2, With3}; +use with::{With, With2, With3, ExtractorConfig}; /// Resource route definition /// @@ -127,12 +127,14 @@ impl Route { /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// } /// ``` - pub fn with(&mut self, handler: F) + pub fn with(&mut self, handler: F) -> ExtractorConfig where F: Fn(T) -> R + 'static, R: Responder + 'static, T: FromRequest + 'static, { - self.h(With::new(handler)) + let cfg = ExtractorConfig::default(); + self.h(With::new(handler, Clone::clone(&cfg))); + cfg } /// Set handler function, function has to accept two request extractors. @@ -166,23 +168,33 @@ impl Route { /// } /// ``` pub fn with2(&mut self, handler: F) + -> (ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2) -> R + 'static, R: Responder + 'static, T1: FromRequest + 'static, T2: FromRequest + 'static, { - self.h(With2::new(handler)) + let cfg1 = ExtractorConfig::default(); + let cfg2 = ExtractorConfig::default(); + self.h(With2::new(handler, Clone::clone(&cfg1), Clone::clone(&cfg2))); + (cfg1, cfg2) } /// Set handler function, function has to accept three request extractors. pub fn with3(&mut self, handler: F) + -> (ExtractorConfig, ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2, T3) -> R + 'static, R: Responder + 'static, T1: FromRequest + 'static, T2: FromRequest + 'static, T3: FromRequest + 'static, { - self.h(With3::new(handler)) + let cfg1 = ExtractorConfig::default(); + let cfg2 = ExtractorConfig::default(); + let cfg3 = ExtractorConfig::default(); + self.h(With3::new( + handler, Clone::clone(&cfg1), Clone::clone(&cfg2), Clone::clone(&cfg3))); + (cfg1, cfg2, cfg3) } } diff --git a/src/test.rs b/src/test.rs index b6fd22d2..4e5ed9bd 100644 --- a/src/test.rs +++ b/src/test.rs @@ -351,8 +351,8 @@ impl TestApp { /// Register resource. This method is similar /// to `App::resource()` method. - pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp - where F: FnOnce(&mut ResourceHandler) + 'static + pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp + where F: FnOnce(&mut ResourceHandler) -> R + 'static { self.app = Some(self.app.take().unwrap().resource(path, f)); self diff --git a/src/with.rs b/src/with.rs index 2a442039..5f70db25 100644 --- a/src/with.rs +++ b/src/with.rs @@ -1,6 +1,7 @@ use std::rc::Rc; use std::cell::UnsafeCell; use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; use futures::{Async, Future, Poll}; use error::Error; @@ -8,19 +9,55 @@ use handler::{Handler, FromRequest, Reply, ReplyItem, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; +pub struct ExtractorConfig> { + cfg: Rc> +} + +impl> Default for ExtractorConfig { + fn default() -> Self { + ExtractorConfig { cfg: Rc::new(UnsafeCell::new(T::Config::default())) } + } +} + +impl> Clone for ExtractorConfig { + fn clone(&self) -> Self { + ExtractorConfig { cfg: Rc::clone(&self.cfg) } + } +} + +impl> AsRef for ExtractorConfig { + fn as_ref(&self) -> &T::Config { + unsafe{&*self.cfg.get()} + } +} + +impl> Deref for ExtractorConfig { + type Target = T::Config; + + fn deref(&self) -> &T::Config { + unsafe{&*self.cfg.get()} + } +} + +impl> DerefMut for ExtractorConfig { + fn deref_mut(&mut self) -> &mut T::Config { + unsafe{&mut *self.cfg.get()} + } +} + pub struct With - where F: Fn(T) -> R + where F: Fn(T) -> R, T: FromRequest, S: 'static, { hnd: Rc>, - _t: PhantomData, + cfg: ExtractorConfig, _s: PhantomData, } impl With - where F: Fn(T) -> R, + where F: Fn(T) -> R, T: FromRequest, S: 'static, { - pub fn new(f: F) -> Self { - With{hnd: Rc::new(UnsafeCell::new(f)), _t: PhantomData, _s: PhantomData} + pub fn new(f: F, cfg: ExtractorConfig) -> Self { + With{cfg, hnd: Rc::new(UnsafeCell::new(f)), _s: PhantomData} } } @@ -37,6 +74,7 @@ impl Handler for With req, started: false, hnd: Rc::clone(&self.hnd), + cfg: self.cfg.clone(), fut1: None, fut2: None, }; @@ -57,6 +95,7 @@ struct WithHandlerFut { started: bool, hnd: Rc>, + cfg: ExtractorConfig, req: HttpRequest, fut1: Option>>, fut2: Option>>, @@ -78,7 +117,7 @@ impl Future for WithHandlerFut let item = if !self.started { self.started = true; - let mut fut = T::from_request(&self.req); + let mut fut = T::from_request(&self.req, self.cfg.as_ref()); match fut.poll() { Ok(Async::Ready(item)) => item, Ok(Async::NotReady) => { @@ -110,19 +149,23 @@ impl Future for WithHandlerFut } } -pub struct With2 where F: Fn(T1, T2) -> R +pub struct With2 + where F: Fn(T1, T2) -> R, + T1: FromRequest + 'static, T2: FromRequest + 'static, S: 'static { hnd: Rc>, - _t1: PhantomData, - _t2: PhantomData, + cfg1: ExtractorConfig, + cfg2: ExtractorConfig, _s: PhantomData, } -impl With2 where F: Fn(T1, T2) -> R +impl With2 + where F: Fn(T1, T2) -> R, + T1: FromRequest + 'static, T2: FromRequest + 'static, S: 'static { - pub fn new(f: F) -> Self { + pub fn new(f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig) -> Self { With2{hnd: Rc::new(UnsafeCell::new(f)), - _t1: PhantomData, _t2: PhantomData, _s: PhantomData} + cfg1, cfg2, _s: PhantomData} } } @@ -140,6 +183,8 @@ impl Handler for With2 req, started: false, hnd: Rc::clone(&self.hnd), + cfg1: self.cfg1.clone(), + cfg2: self.cfg2.clone(), item: None, fut1: None, fut2: None, @@ -162,6 +207,8 @@ struct WithHandlerFut2 { started: bool, hnd: Rc>, + cfg1: ExtractorConfig, + cfg2: ExtractorConfig, req: HttpRequest, item: Option, fut1: Option>>, @@ -186,10 +233,10 @@ impl Future for WithHandlerFut2 if !self.started { self.started = true; - let mut fut = T1::from_request(&self.req); + let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); match fut.poll() { Ok(Async::Ready(item1)) => { - let mut fut = T2::from_request(&self.req); + let mut fut = T2::from_request(&self.req,self.cfg2.as_ref()); match fut.poll() { Ok(Async::Ready(item2)) => { let hnd: &mut F = unsafe{&mut *self.hnd.get()}; @@ -228,7 +275,8 @@ impl Future for WithHandlerFut2 Async::Ready(item) => { self.item = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new(T2::from_request(&self.req))); + self.fut2 = Some(Box::new( + T2::from_request(&self.req, self.cfg2.as_ref()))); }, Async::NotReady => return Ok(Async::NotReady), } @@ -256,21 +304,32 @@ impl Future for WithHandlerFut2 } } -pub struct With3 where F: Fn(T1, T2, T3) -> R { +pub struct With3 + where F: Fn(T1, T2, T3) -> R, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, + S: 'static +{ hnd: Rc>, - _t1: PhantomData, - _t2: PhantomData, - _t3: PhantomData, + cfg1: ExtractorConfig, + cfg2: ExtractorConfig, + cfg3: ExtractorConfig, _s: PhantomData, } impl With3 where F: Fn(T1, T2, T3) -> R, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, + S: 'static { - pub fn new(f: F) -> Self { - With3{hnd: Rc::new(UnsafeCell::new(f)), - _s: PhantomData, _t1: PhantomData, _t2: PhantomData, _t3: PhantomData} + pub fn new(f: F, cfg1: ExtractorConfig, + cfg2: ExtractorConfig, cfg3: ExtractorConfig) -> Self + { + With3{hnd: Rc::new(UnsafeCell::new(f)), cfg1, cfg2, cfg3, _s: PhantomData} } } @@ -288,6 +347,9 @@ impl Handler for With3 let mut fut = WithHandlerFut3{ req, hnd: Rc::clone(&self.hnd), + cfg1: self.cfg1.clone(), + cfg2: self.cfg2.clone(), + cfg3: self.cfg3.clone(), started: false, item1: None, item2: None, @@ -314,6 +376,9 @@ struct WithHandlerFut3 { hnd: Rc>, req: HttpRequest, + cfg1: ExtractorConfig, + cfg2: ExtractorConfig, + cfg3: ExtractorConfig, started: bool, item1: Option, item2: Option, @@ -341,13 +406,13 @@ impl Future for WithHandlerFut3 if !self.started { self.started = true; - let mut fut = T1::from_request(&self.req); + let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); match fut.poll() { Ok(Async::Ready(item1)) => { - let mut fut = T2::from_request(&self.req); + let mut fut = T2::from_request(&self.req, self.cfg2.as_ref()); match fut.poll() { Ok(Async::Ready(item2)) => { - let mut fut = T3::from_request(&self.req); + let mut fut = T3::from_request(&self.req, self.cfg3.as_ref()); match fut.poll() { Ok(Async::Ready(item3)) => { let hnd: &mut F = unsafe{&mut *self.hnd.get()}; @@ -395,7 +460,8 @@ impl Future for WithHandlerFut3 Async::Ready(item) => { self.item1 = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new(T2::from_request(&self.req))); + self.fut2 = Some(Box::new( + T2::from_request(&self.req, self.cfg2.as_ref()))); }, Async::NotReady => return Ok(Async::NotReady), } @@ -406,7 +472,8 @@ impl Future for WithHandlerFut3 Async::Ready(item) => { self.item2 = Some(item); self.fut2.take(); - self.fut3 = Some(Box::new(T3::from_request(&self.req))); + self.fut3 = Some(Box::new( + T3::from_request(&self.req, self.cfg3.as_ref()))); }, Async::NotReady => return Ok(Async::NotReady), } From c273b7ac3fba82e5b740af255a0155f3e5511252 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Apr 2018 08:08:31 -0700 Subject: [PATCH 0003/1635] update json example --- examples/json/src/main.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 34730366..73863eef 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -21,7 +21,7 @@ struct MyObj { number: i32, } -/// This handler uses `HttpRequest::json()` for loading serde json object. +/// This handler uses `HttpRequest::json()` for loading json object. fn index(req: HttpRequest) -> Box> { req.json() .from_err() // convert all errors into `Error` @@ -32,7 +32,7 @@ fn index(req: HttpRequest) -> Box> { .responder() } -/// This handler uses `With` helper for loading serde json object. +/// This handler uses json extractor fn extract_item(item: Json) -> HttpResponse { println!("model: {:?}", &item); HttpResponse::Ok().json(item.0) // <- send response @@ -40,7 +40,7 @@ fn extract_item(item: Json) -> HttpResponse { const MAX_SIZE: usize = 262_144; // max payload size is 256k -/// This handler manually load request payload and parse serde json +/// This handler manually load request payload and parse json object fn index_manual(req: HttpRequest) -> Box> { // HttpRequest is stream of Bytes objects req @@ -86,15 +86,18 @@ fn index_mjsonrust(req: HttpRequest) -> Box Date: Wed, 4 Apr 2018 08:12:33 -0700 Subject: [PATCH 0004/1635] run test coverage on beta --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7aa8ebaa..8810ee72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,7 +87,7 @@ after_success: fi - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "1.21.0" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) USE_SKEPTIC=1 cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) From d8a9606162779cb7460f5cb149b8e66aeaf877bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Apr 2018 16:39:01 -0700 Subject: [PATCH 0005/1635] add connection limits to pool --- .travis.yml | 2 +- src/client/connector.rs | 697 +++++++++++++++++++++++++++++++--------- src/client/pipeline.rs | 8 + 3 files changed, 549 insertions(+), 158 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8810ee72..f27a445a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,7 +87,7 @@ after_success: fi - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) USE_SKEPTIC=1 cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) diff --git a/src/client/connector.rs b/src/client/connector.rs index 8f282893..4f14a9e2 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,4 +1,4 @@ -use std::{fmt, io, time}; +use std::{fmt, mem, io, time}; use std::cell::RefCell; use std::rc::Rc; use std::net::Shutdown; @@ -6,28 +6,26 @@ use std::time::{Duration, Instant}; use std::collections::{HashMap, VecDeque}; use actix::{fut, Actor, ActorFuture, Context, AsyncContext, - Handler, Message, ActorResponse, Supervised}; + Handler, Message, ActorResponse, Supervised, ContextFutureSpawner}; use actix::registry::ArbiterService; use actix::fut::WrapFuture; use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect}; use http::{Uri, HttpTryFrom, Error as HttpError}; -use futures::{Async, Poll}; +use futures::{Async, Future, Poll}; +use futures::task::{Task, current as current_task}; +use futures::unsync::oneshot; use tokio_io::{AsyncRead, AsyncWrite}; #[cfg(feature="alpn")] use openssl::ssl::{SslMethod, SslConnector, Error as OpensslError}; #[cfg(feature="alpn")] use tokio_openssl::SslConnectorExt; -#[cfg(feature="alpn")] -use futures::Future; #[cfg(all(feature="tls", not(feature="alpn")))] use native_tls::{TlsConnector, Error as TlsError}; #[cfg(all(feature="tls", not(feature="alpn")))] use tokio_tls::TlsConnectorExt; -#[cfg(all(feature="tls", not(feature="alpn")))] -use futures::Future; use {HAS_OPENSSL, HAS_TLS}; use server::IoStream; @@ -102,19 +100,37 @@ impl From for ClientConnectorError { } } +struct Waiter { + tx: oneshot::Sender>, + conn_timeout: Duration, +} + +/// `ClientConnector` type is responsible for transport layer of a client connection +/// of a client connection. pub struct ClientConnector { #[cfg(all(feature="alpn"))] connector: SslConnector, #[cfg(all(feature="tls", not(feature="alpn")))] connector: TlsConnector, + pool: Rc, + conn_lifetime: Duration, + conn_keep_alive: Duration, + limit: usize, + limit_per_host: usize, + acquired: usize, + acquired_per_host: HashMap, + available: HashMap>, + to_close: Vec, + waiters: HashMap>, } impl Actor for ClientConnector { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { - self.collect(ctx); + self.collect_periodic(ctx); + ctx.spawn(Maintenance); } } @@ -127,22 +143,38 @@ impl Default for ClientConnector { #[cfg(all(feature="alpn"))] { let builder = SslConnector::builder(SslMethod::tls()).unwrap(); - ClientConnector { - connector: builder.build(), - pool: Rc::new(Pool::new()), - } + ClientConnector::with_connector(builder.build()) } #[cfg(all(feature="tls", not(feature="alpn")))] { let builder = TlsConnector::builder().unwrap(); ClientConnector { - connector: builder.build().unwrap(), pool: Rc::new(Pool::new()), + connector: builder.build().unwrap(), + conn_lifetime: Duration::from_secs(15), + conn_keep_alive: Duration::from_secs(75), + limit: 100, + limit_per_host: 0, + acquired: 0, + acquired_per_host: HashMap::new(), + available: HashMap::new(), + to_close: Vec::new(), + waiters: HashMap::new(), } } #[cfg(not(any(feature="alpn", feature="tls")))] - ClientConnector {pool: Rc::new(Pool::new())} + ClientConnector {pool: Rc::new(Pool::new()), + conn_lifetime: Duration::from_secs(15), + conn_keep_alive: Duration::from_secs(75), + limit: 100, + limit_per_host: 0, + acquired: 0, + acquired_per_host: HashMap::new(), + available: HashMap::new(), + to_close: Vec::new(), + waiters: HashMap::new(), + } } } @@ -192,12 +224,200 @@ impl ClientConnector { /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { - ClientConnector { connector, pool: Rc::new(Pool::new()) } + ClientConnector { + connector, + pool: Rc::new(Pool::new()), + conn_lifetime: Duration::from_secs(15), + conn_keep_alive: Duration::from_secs(75), + limit: 100, + limit_per_host: 0, + acquired: 0, + acquired_per_host: HashMap::new(), + available: HashMap::new(), + to_close: Vec::new(), + waiters: HashMap::new(), + } } - fn collect(&mut self, ctx: &mut Context) { - self.pool.collect(); - ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect(ctx)); + /// Set total number of simultaneous connections. + /// + /// If limit is 0, the connector has no limit. + /// The default limit size is 100. + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set total number of simultaneous connections to the same endpoint. + /// + /// Endpoints are the same if they are have equal (host, port, ssl) triplet. + /// If limit is 0, the connector has no limit. The default limit size is 0. + pub fn limit_per_host(mut self, limit: usize) -> Self { + self.limit_per_host = limit; + self + } + + /// Set keep-alive period for opened connection. + /// + /// Keep-alive period is period between connection usage. + /// if deley between connection usage exceeds this period + /// connection closes. + pub fn conn_keep_alive(mut self, dur: Duration) -> Self { + self.conn_keep_alive = dur; + self + } + + /// Set max lifetime period for connection. + /// + /// Connection lifetime is max lifetime of any opened connection + /// until it get closed regardless of keep-alive period. + pub fn conn_lifetime(mut self, dur: Duration) -> Self { + self.conn_lifetime = dur; + self + } + + fn acquire(&mut self, key: &Key) -> Acquire { + // check limits + if self.limit > 0 { + if self.acquired >= self.limit { + return Acquire::NotAvailable + } + if self.limit_per_host > 0 { + if let Some(per_host) = self.acquired_per_host.get(key) { + if self.limit_per_host >= *per_host { + return Acquire::NotAvailable + } + } + } + } + else if self.limit_per_host > 0 { + if let Some(per_host) = self.acquired_per_host.get(key) { + if self.limit_per_host >= *per_host { + return Acquire::NotAvailable + } + } + } + + self.reserve(key); + + // check if open connection is available + // cleanup stale connections at the same time + if let Some(ref mut connections) = self.available.get_mut(key) { + let now = Instant::now(); + while let Some(conn) = connections.pop_back() { + // check if it still usable + if (now - conn.0) > self.conn_keep_alive + || (now - conn.1.ts) > self.conn_lifetime + { + self.to_close.push(conn.1); + } else { + let mut conn = conn.1; + let mut buf = [0; 2]; + match conn.stream().read(&mut buf) { + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), + Ok(n) if n > 0 => { + self.to_close.push(conn); + continue + }, + Ok(_) | Err(_) => continue, + } + return Acquire::Acquired(conn) + } + } + } + Acquire::Available + } + + fn reserve(&mut self, key: &Key) { + self.acquired += 1; + let per_host = + if let Some(per_host) = self.acquired_per_host.get(key) { + *per_host + } else { + 0 + }; + self.acquired_per_host.insert(key.clone(), per_host + 1); + } + + fn release_key(&mut self, key: &Key) { + self.acquired -= 1; + let per_host = + if let Some(per_host) = self.acquired_per_host.get(key) { + *per_host + } else { + return + }; + if per_host > 1 { + self.acquired_per_host.insert(key.clone(), per_host - 1); + } else { + self.acquired_per_host.remove(key); + } + } + + fn collect(&mut self, periodic: bool) { + let now = Instant::now(); + + // collect half acquire keys + if let Some(keys) = self.pool.collect_keys() { + for key in keys { + self.release_key(&key); + } + } + + // collect connections for close + if let Some(to_close) = self.pool.collect_close() { + for conn in to_close { + self.release_key(&conn.key); + self.to_close.push(conn); + } + } + + // connection connections + if let Some(to_release) = self.pool.collect_release() { + for conn in to_release { + self.release_key(&conn.key); + + // check connection lifetime and the return to available pool + if (now - conn.ts) < self.conn_lifetime { + self.available.entry(conn.key.clone()) + .or_insert_with(VecDeque::new) + .push_back(Conn(Instant::now(), conn)); + } + } + } + + // check keep-alive + for conns in self.available.values_mut() { + while !conns.is_empty() { + if (now > conns[0].0) && (now - conns[0].0) > self.conn_keep_alive + || (now - conns[0].1.ts) > self.conn_lifetime + { + let conn = conns.pop_front().unwrap().1; + self.to_close.push(conn); + } else { + break + } + } + } + + // check connections for shutdown + if periodic { + let mut idx = 0; + while idx < self.to_close.len() { + match AsyncWrite::shutdown(&mut self.to_close[idx]) { + Ok(Async::NotReady) => idx += 1, + _ => { + self.to_close.swap_remove(idx); + }, + } + } + } + } + + fn collect_periodic(&mut self, ctx: &mut Context) { + self.collect(true); + // re-schedule next collect period + ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); } } @@ -205,6 +425,8 @@ impl Handler for ClientConnector { type Result = ActorResponse; fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result { + self.collect(false); + let uri = &msg.uri; let conn_timeout = msg.conn_timeout; @@ -227,76 +449,244 @@ impl Handler for ClientConnector { return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)) } + // check if pool has task reference + if self.pool.task.borrow().is_none() { + *self.pool.task.borrow_mut() = Some(current_task()); + } + let host = uri.host().unwrap().to_owned(); let port = uri.port().unwrap_or_else(|| proto.port()); let key = Key {host, port, ssl: proto.is_secure()}; + // acquire connection let pool = if proto.is_http() { - if let Some(mut conn) = self.pool.query(&key) { - conn.pool = Some(self.pool.clone()); - return ActorResponse::async(fut::ok(conn)) - } else { - Some(Rc::clone(&self.pool)) + match self.acquire(&key) { + Acquire::Acquired(mut conn) => { + // use existing connection + conn.pool = Some(AcquiredConn(key, Some(Rc::clone(&self.pool)))); + return ActorResponse::async(fut::ok(conn)) + }, + Acquire::NotAvailable => { + // connection is not available, wait + let (tx, rx) = oneshot::channel(); + let waiter = Waiter{ tx, conn_timeout }; + self.waiters.entry(key.clone()).or_insert_with(VecDeque::new) + .push_back(waiter); + return ActorResponse::async( + rx.map_err(|_| ClientConnectorError::Disconnected) + .into_actor(self) + .and_then(|res, _, _| match res { + Ok(conn) => fut::ok(conn), + Err(err) => fut::err(err), + })); + } + Acquire::Available => { + Some(Rc::clone(&self.pool)) + }, } } else { None }; + let conn = AcquiredConn(key, pool); - ActorResponse::async( - Connector::from_registry() - .send(ResolveConnect::host_and_port(&key.host, port) - .timeout(conn_timeout)) - .into_actor(self) - .map_err(|_, _, _| ClientConnectorError::Disconnected) - .and_then(move |res, _act, _| { - #[cfg(feature="alpn")] - match res { - Err(err) => fut::Either::B(fut::err(err.into())), - Ok(stream) => { - if proto.is_secure() { - fut::Either::A( - _act.connector.connect_async(&key.host, stream) - .map_err(ClientConnectorError::SslError) - .map(|stream| Connection::new( - key, pool, Box::new(stream))) - .into_actor(_act)) - } else { - fut::Either::B(fut::ok( - Connection::new(key, pool, Box::new(stream)))) - } +{ + ActorResponse::async( + Connector::from_registry() + .send(ResolveConnect::host_and_port(&conn.0.host, port) + .timeout(conn_timeout)) + .into_actor(self) + .map_err(|_, _, _| ClientConnectorError::Disconnected) + .and_then(move |res, _act, _| { + #[cfg(feature="alpn")] + match res { + Err(err) => fut::Either::B(fut::err(err.into())), + Ok(stream) => { + if proto.is_secure() { + fut::Either::A( + _act.connector.connect_async(&conn.0.host, stream) + .map_err(ClientConnectorError::SslError) + .map(|stream| Connection::new( + conn.0.clone(), Some(conn), Box::new(stream))) + .into_actor(_act)) + } else { + fut::Either::B(fut::ok( + Connection::new( + conn.0.clone(), Some(conn), Box::new(stream)))) } } + } - #[cfg(all(feature="tls", not(feature="alpn")))] - match res { - Err(err) => fut::Either::B(fut::err(err.into())), - Ok(stream) => { - if proto.is_secure() { - fut::Either::A( - _act.connector.connect_async(&key.host, stream) - .map_err(ClientConnectorError::SslError) - .map(|stream| Connection::new( - key, pool, Box::new(stream))) - .into_actor(_act)) - } else { - fut::Either::B(fut::ok( - Connection::new(key, pool, Box::new(stream)))) - } + #[cfg(all(feature="tls", not(feature="alpn")))] + match res { + Err(err) => fut::Either::B(fut::err(err.into())), + Ok(stream) => { + if proto.is_secure() { + fut::Either::A( + _act.connector.connect_async(&conn.0.host, stream) + .map_err(ClientConnectorError::SslError) + .map(|stream| Connection::new( + conn.0.clone(), Some(conn), Box::new(stream))) + .into_actor(_act)) + } else { + fut::Either::B(fut::ok( + Connection::new( + conn.0.clone(), Some(conn), Box::new(stream)))) } } + } - #[cfg(not(any(feature="alpn", feature="tls")))] - match res { - Err(err) => fut::err(err.into()), - Ok(stream) => { - if proto.is_secure() { - fut::err(ClientConnectorError::SslIsNotSupported) - } else { - fut::ok(Connection::new(key, pool, Box::new(stream))) - } + #[cfg(not(any(feature="alpn", feature="tls")))] + match res { + Err(err) => fut::err(err.into()), + Ok(stream) => { + if proto.is_secure() { + fut::err(ClientConnectorError::SslIsNotSupported) + } else { + fut::ok(Connection::new( + conn.0.clone(), Some(conn), Box::new(stream))) } } - })) + } + })) +} + } +} + +struct Maintenance; + +impl fut::ActorFuture for Maintenance +{ + type Item = (); + type Error = (); + type Actor = ClientConnector; + + fn poll(&mut self, act: &mut ClientConnector, ctx: &mut Context) + -> Poll + { + // collecto connections + act.collect(false); + + // check waiters + let tmp: &mut ClientConnector = unsafe{mem::transmute(act as &mut _)}; + + for (key, waiters) in &mut tmp.waiters { + while let Some(waiter) = waiters.pop_front() { + if waiter.tx.is_canceled() { continue } + + match act.acquire(key) { + Acquire::Acquired(mut conn) => { + // use existing connection + conn.pool = Some( + AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)))); + let _ = waiter.tx.send(Ok(conn)); + }, + Acquire::NotAvailable => { + waiters.push_front(waiter); + break + } + Acquire::Available => + { + let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool))); + + fut::WrapFuture::::actfuture( + Connector::from_registry() + .send(ResolveConnect::host_and_port(&conn.0.host, conn.0.port) + .timeout(waiter.conn_timeout))) + .map_err(|_, _, _| ()) + .and_then(move |res, _act, _| { + #[cfg(feature="alpn")] + match res { + Err(err) => { + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + }, + Ok(stream) => { + if conn.0.ssl { + fut::Either::A( + _act.connector.connect_async(&key.host, stream) + .then(move |res| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e))); + }, + Ok(stream) => { + let _ = waiter.tx.send(Ok( + Connection::new( + conn.0.clone(), + Some(conn), Box::new(stream)))); + } + } + Ok(()) + }) + .actfuture()) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), Some(conn), Box::new(stream)))); + fut::Either::B(fut::ok(())) + } + } + } + + #[cfg(all(feature="tls", not(feature="alpn")))] + match res { + Err(err) => { + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + }, + Ok(stream) => { + if conn.0.ssl { + fut::Either::A( + _act.connector.connect_async(&conn.0.host, stream) + .then(|res| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e))); + }, + Ok(stream) => { + let _ = waiter.tx.send(Ok( + Connection::new( + conn.0.clone(), Some(conn), + Box::new(stream)))); + } + } + Ok(()) + }) + .into_actor(_act)) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), Some(conn), Box::new(stream)))); + fut::Either::B(fut::ok(())) + } + } + } + + #[cfg(not(any(feature="alpn", feature="tls")))] + match res { + Err(err) => { + let _ = waiter.tx.send(Err(err.into())); + fut::err(()) + }, + Ok(stream) => { + if conn.0.ssl { + let _ = waiter.tx.send( + Err(ClientConnectorError::SslIsNotSupported)); + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), Some(conn), Box::new(stream)))); + }; + fut::ok(()) + }, + } + }) + .spawn(ctx); + } + } + } + } + + Ok(Async::NotReady) } } @@ -357,104 +747,94 @@ impl Key { #[derive(Debug)] struct Conn(Instant, Connection); +enum Acquire { + Acquired(Connection), + Available, + NotAvailable, +} + +struct AcquiredConn(Key, Option>); + +impl AcquiredConn { + fn close(&mut self, conn: Connection) { + if let Some(pool) = self.1.take() { + pool.close(conn); + } + } + fn release(&mut self, conn: Connection) { + if let Some(pool) = self.1.take() { + pool.release(conn); + } + } +} + +impl Drop for AcquiredConn { + fn drop(&mut self) { + if let Some(pool) = self.1.take() { + pool.release_key(self.0.clone()); + } + } +} + pub struct Pool { - max_size: usize, - keep_alive: Duration, - max_lifetime: Duration, - pool: RefCell>>, + keys: RefCell>, to_close: RefCell>, + to_release: RefCell>, + task: RefCell>, } impl Pool { fn new() -> Pool { Pool { - max_size: 128, - keep_alive: Duration::from_secs(15), - max_lifetime: Duration::from_secs(75), - pool: RefCell::new(HashMap::new()), + keys: RefCell::new(Vec::new()), to_close: RefCell::new(Vec::new()), + to_release: RefCell::new(Vec::new()), + task: RefCell::new(None), } } - fn collect(&self) { - let mut pool = self.pool.borrow_mut(); - let mut to_close = self.to_close.borrow_mut(); - - // check keep-alive - let now = Instant::now(); - for conns in pool.values_mut() { - while !conns.is_empty() { - if (now - conns[0].0) > self.keep_alive - || (now - conns[0].1.ts) > self.max_lifetime - { - let conn = conns.pop_front().unwrap().1; - to_close.push(conn); - } else { - break - } - } - } - - // check connections for shutdown - let mut idx = 0; - while idx < to_close.len() { - match AsyncWrite::shutdown(&mut to_close[idx]) { - Ok(Async::NotReady) => idx += 1, - _ => { - to_close.swap_remove(idx); - }, - } + fn collect_keys(&self) -> Option> { + if self.keys.borrow().is_empty() { + None + } else { + Some(mem::replace(&mut *self.keys.borrow_mut(), Vec::new())) } } - fn query(&self, key: &Key) -> Option { - let mut pool = self.pool.borrow_mut(); - let mut to_close = self.to_close.borrow_mut(); - - if let Some(ref mut connections) = pool.get_mut(key) { - let now = Instant::now(); - while let Some(conn) = connections.pop_back() { - // check if it still usable - if (now - conn.0) > self.keep_alive - || (now - conn.1.ts) > self.max_lifetime - { - to_close.push(conn.1); - } else { - let mut conn = conn.1; - let mut buf = [0; 2]; - match conn.stream().read(&mut buf) { - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Ok(n) if n > 0 => { - to_close.push(conn); - continue - }, - Ok(_) | Err(_) => continue, - } - return Some(conn) - } - } + fn collect_close(&self) -> Option> { + if self.to_close.borrow().is_empty() { + None + } else { + Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new())) + } + } + + fn collect_release(&self) -> Option> { + if self.to_release.borrow().is_empty() { + None + } else { + Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new())) + } + } + + fn close(&self, conn: Connection) { + self.to_close.borrow_mut().push(conn); + if let Some(ref task) = *self.task.borrow() { + task.notify() } - None } fn release(&self, conn: Connection) { - if (Instant::now() - conn.ts) < self.max_lifetime { - let mut pool = self.pool.borrow_mut(); - if !pool.contains_key(&conn.key) { - let key = conn.key.clone(); - let mut vec = VecDeque::new(); - vec.push_back(Conn(Instant::now(), conn)); - pool.insert(key, vec); - } else { - let vec = pool.get_mut(&conn.key).unwrap(); - vec.push_back(Conn(Instant::now(), conn)); - if vec.len() > self.max_size { - let conn = vec.pop_front().unwrap(); - self.to_close.borrow_mut().push(conn.1); - } - } - } else { - self.to_close.borrow_mut().push(conn); + self.to_release.borrow_mut().push(conn); + if let Some(ref task) = *self.task.borrow() { + task.notify() + } + } + + fn release_key(&self, key: Key) { + self.keys.borrow_mut().push(key); + if let Some(ref task) = *self.task.borrow() { + task.notify() } } } @@ -463,7 +843,7 @@ impl Pool { pub struct Connection { key: Key, stream: Box, - pool: Option>, + pool: Option, ts: Instant, } @@ -474,11 +854,8 @@ impl fmt::Debug for Connection { } impl Connection { - fn new(key: Key, pool: Option>, stream: Box) -> Self { - Connection { - key, pool, stream, - ts: Instant::now(), - } + fn new(key: Key, pool: Option, stream: Box) -> Self { + Connection {key, stream, pool, ts: Instant::now()} } pub fn stream(&mut self) -> &mut IoStream { @@ -489,8 +866,14 @@ impl Connection { Connection::new(Key::empty(), None, Box::new(io)) } + pub fn close(mut self) { + if let Some(mut pool) = self.pool.take() { + pool.close(self) + } + } + pub fn release(mut self) { - if let Some(pool) = self.pool.take() { + if let Some(mut pool) = self.pool.take() { pool.release(self) } } diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 19ccf892..15e7ef47 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -458,3 +458,11 @@ impl Pipeline { } } } + +impl Drop for Pipeline { + fn drop(&mut self) { + if let Some(conn) = self.conn.take() { + conn.close() + } + } +} From c1af59c6184b90a1f0e7354c919e0ae74206c3df Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Apr 2018 17:57:02 -0700 Subject: [PATCH 0006/1635] update juniper example --- examples/juniper/src/main.rs | 40 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs index 97319afe..11676505 100644 --- a/examples/juniper/src/main.rs +++ b/examples/juniper/src/main.rs @@ -14,11 +14,10 @@ extern crate env_logger; use actix::prelude::*; use actix_web::{ - middleware, http::{self, header::CONTENT_TYPE}, server, - App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error}; + middleware, http, server, + App, AsyncResponder, HttpRequest, HttpResponse, FutureResponse, Error, State, Json}; use juniper::http::graphiql::graphiql_source; use juniper::http::GraphQLRequest; - use futures::future::Future; mod schema; @@ -26,7 +25,7 @@ mod schema; use schema::Schema; use schema::create_schema; -struct State { +struct AppState { executor: Addr, } @@ -63,33 +62,30 @@ impl Handler for GraphQLExecutor { } } -fn graphiql(_req: HttpRequest) -> Result { +fn graphiql(_req: HttpRequest) -> Result { let html = graphiql_source("http://127.0.0.1:8080/graphql"); Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) + .content_type("text/html; charset=utf-8") + .body(html)) } -fn graphql(req: HttpRequest) -> Box> { - let executor = req.state().executor.clone(); - req.json() +fn graphql(st: State, data: Json) -> FutureResponse { + st.executor.send(data.0) .from_err() - .and_then(move |val: GraphQLData| { - executor.send(val) - .from_err() - .and_then(|res| { - match res { - Ok(user) => Ok(HttpResponse::Ok().header(CONTENT_TYPE, "application/json").body(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()) - } - }) + .and_then(|res| { + match res { + Ok(user) => Ok(HttpResponse::Ok() + .content_type("application/json") + .body(user)), + Err(_) => Ok(HttpResponse::InternalServerError().into()) + } }) .responder() } fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); + env_logger::init(); let sys = actix::System::new("juniper-example"); let schema = std::sync::Arc::new(create_schema()); @@ -99,10 +95,10 @@ fn main() { // Start http server let _ = server::new(move || { - App::with_state(State{executor: addr.clone()}) + App::with_state(AppState{executor: addr.clone()}) // enable logger .middleware(middleware::Logger::default()) - .resource("/graphql", |r| r.method(http::Method::POST).h(graphql)) + .resource("/graphql", |r| r.method(http::Method::POST).with2(graphql)) .resource("/graphiql", |r| r.method(http::Method::GET).h(graphiql))}) .bind("127.0.0.1:8080").unwrap() .start(); From eeae0ddab4ca8bee6a48946011350e889765be0e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Apr 2018 20:15:47 -0700 Subject: [PATCH 0007/1635] start client timeout for response only --- src/client/pipeline.rs | 42 ++++++++++++++++++++++++------------------ src/server/channel.rs | 2 +- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 15e7ef47..aefffc89 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -115,19 +115,6 @@ impl SendRequest { self.conn_timeout = timeout; self } - - fn poll_timeout(&mut self) -> Poll<(), SendRequestError> { - if self.timeout.is_none() { - self.timeout = Some(Timeout::new( - Duration::from_secs(5), Arbiter::handle()).unwrap()); - } - - match self.timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(())) => Err(SendRequestError::Timeout), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => unreachable!() - } - } } impl Future for SendRequest { @@ -135,8 +122,6 @@ impl Future for SendRequest { type Error = SendRequestError; fn poll(&mut self) -> Poll { - self.poll_timeout()?; - loop { let state = mem::replace(&mut self.state, State::None); @@ -170,6 +155,10 @@ impl Future for SendRequest { _ => IoBody::Done, }; + let timeout = self.timeout.take().unwrap_or_else(|| + Timeout::new( + Duration::from_secs(5), Arbiter::handle()).unwrap()); + let pl = Box::new(Pipeline { body, writer, conn: Some(conn), @@ -180,6 +169,7 @@ impl Future for SendRequest { decompress: None, should_decompress: self.req.response_decompress(), write_state: RunningState::Running, + timeout: Some(timeout), }); self.state = State::Send(pl); }, @@ -218,6 +208,7 @@ pub(crate) struct Pipeline { decompress: Option, should_decompress: bool, write_state: RunningState, + timeout: Option, } enum IoBody { @@ -292,10 +283,14 @@ impl Pipeline { let mut need_run = false; // need write? - if let Async::NotReady = self.poll_write() + match self.poll_write() .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? { - need_run = true; + Async::NotReady => need_run = true, + Async::Ready(_) => { + let _ = self.poll_timeout() + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?; + } } // need read? @@ -343,6 +338,18 @@ impl Pipeline { } } + fn poll_timeout(&mut self) -> Poll<(), SendRequestError> { + if self.timeout.is_some() { + match self.timeout.as_mut().unwrap().poll() { + Ok(Async::Ready(())) => Err(SendRequestError::Timeout), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(_) => unreachable!() + } + } else { + Ok(Async::NotReady) + } + } + #[inline] fn poll_write(&mut self) -> Poll<(), Error> { if self.write_state == RunningState::Done || self.conn.is_none() { @@ -350,7 +357,6 @@ impl Pipeline { } let mut done = false; - if self.drain.is_none() && self.write_state != RunningState::Paused { 'outter: loop { let result = match mem::replace(&mut self.body, IoBody::Done) { diff --git a/src/server/channel.rs b/src/server/channel.rs index 390aaee8..49ea586e 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -69,7 +69,7 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta type Error = (); fn poll(&mut self) -> Poll { - if !self.node.is_none() { + if self.node.is_some() { let el = self as *mut _; self.node = Some(Node::new(el)); let _ = match self.proto { From 7be4b1f399281199817939f756fe69bc05646278 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Apr 2018 20:24:09 -0700 Subject: [PATCH 0008/1635] clippy warns --- src/body.rs | 26 +++++++++++++------------- src/middleware/logger.rs | 6 +++--- src/server/h1.rs | 2 +- src/ws/frame.rs | 30 +++++++++++++++--------------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/body.rs b/src/body.rs index 97b8850c..64123679 100644 --- a/src/body.rs +++ b/src/body.rs @@ -281,61 +281,61 @@ mod tests { #[test] fn test_static_str() { assert_eq!(Binary::from("test").len(), 4); - assert_eq!(Binary::from("test").as_ref(), "test".as_bytes()); + assert_eq!(Binary::from("test").as_ref(), b"test"); } #[test] fn test_static_bytes() { assert_eq!(Binary::from(b"test".as_ref()).len(), 4); - assert_eq!(Binary::from(b"test".as_ref()).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test"); assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4); - assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test"); } #[test] fn test_vec() { assert_eq!(Binary::from(Vec::from("test")).len(), 4); - assert_eq!(Binary::from(Vec::from("test")).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test"); } #[test] fn test_bytes() { assert_eq!(Binary::from(Bytes::from("test")).len(), 4); - assert_eq!(Binary::from(Bytes::from("test")).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test"); } #[test] fn test_ref_string() { let b = Rc::new("test".to_owned()); assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(&b).as_ref(), b"test"); } #[test] fn test_rc_string() { let b = Rc::new("test".to_owned()); assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(&b).as_ref(), b"test"); } #[test] fn test_arc_string() { let b = Arc::new("test".to_owned()); assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(&b).as_ref(), b"test"); } #[test] fn test_string() { let b = "test".to_owned(); assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(&b).as_ref(), b"test"); } #[test] @@ -351,7 +351,7 @@ mod tests { fn test_bytes_mut() { let b = BytesMut::from("test"); assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b).as_ref(), "test".as_bytes()); + assert_eq!(Binary::from(b).as_ref(), b"test"); } #[test] diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 48a8d3db..32d2ef4d 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -319,7 +319,7 @@ mod tests { } let entry_time = time::now(); let render = |fmt: &mut Formatter| { - for unit in logger.format.0.iter() { + for unit in &logger.format.0 { unit.render(fmt, &req, &resp, entry_time)?; } Ok(()) @@ -340,7 +340,7 @@ mod tests { let entry_time = time::now(); let render = |fmt: &mut Formatter| { - for unit in format.0.iter() { + for unit in &format.0 { unit.render(fmt, &req, &resp, entry_time)?; } Ok(()) @@ -357,7 +357,7 @@ mod tests { let entry_time = time::now(); let render = |fmt: &mut Formatter| { - for unit in format.0.iter() { + for unit in &format.0 { unit.render(fmt, &req, &resp, entry_time)?; } Ok(()) diff --git a/src/server/h1.rs b/src/server/h1.rs index cb2e0b04..66fdf8a7 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -1425,7 +1425,7 @@ mod tests { let mut reader = Reader::new(); let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - let _ = req.payload_mut().set_read_buffer_capacity(0); + req.payload_mut().set_read_buffer_capacity(0); assert!(req.chunked().unwrap()); assert!(!req.payload().eof()); diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 2afcd035..cb3e82fe 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -354,8 +354,8 @@ mod tests { use super::*; use futures::stream::once; - fn is_none(frm: Poll, ProtocolError>) -> bool { - match frm { + fn is_none(frm: &Poll, ProtocolError>) -> bool { + match *frm { Ok(Async::Ready(None)) => true, _ => false, } @@ -371,10 +371,10 @@ mod tests { #[test] fn test_parse() { let mut buf = PayloadHelper::new( - once(Ok(BytesMut::from(&[0b00000001u8, 0b00000001u8][..]).freeze()))); - assert!(is_none(Frame::parse(&mut buf, false, 1024))); + once(Ok(BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]).freeze()))); + assert!(is_none(&Frame::parse(&mut buf, false, 1024))); - let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]); + let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(b"1"); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); @@ -386,7 +386,7 @@ mod tests { #[test] fn test_parse_length0() { - let buf = BytesMut::from(&[0b00000001u8, 0b00000000u8][..]); + let buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); @@ -397,11 +397,11 @@ mod tests { #[test] fn test_parse_length2() { - let buf = BytesMut::from(&[0b00000001u8, 126u8][..]); + let buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - assert!(is_none(Frame::parse(&mut buf, false, 1024))); + assert!(is_none(&Frame::parse(&mut buf, false, 1024))); - let mut buf = BytesMut::from(&[0b00000001u8, 126u8][..]); + let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); buf.extend(&[0u8, 4u8][..]); buf.extend(b"1234"); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); @@ -414,11 +414,11 @@ mod tests { #[test] fn test_parse_length4() { - let buf = BytesMut::from(&[0b00000001u8, 127u8][..]); + let buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); - assert!(is_none(Frame::parse(&mut buf, false, 1024))); + assert!(is_none(&Frame::parse(&mut buf, false, 1024))); - let mut buf = BytesMut::from(&[0b00000001u8, 127u8][..]); + let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(b"1234"); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); @@ -431,7 +431,7 @@ mod tests { #[test] fn test_parse_frame_mask() { - let mut buf = BytesMut::from(&[0b00000001u8, 0b10000001u8][..]); + let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]); buf.extend(b"0001"); buf.extend(b"1"); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); @@ -446,7 +446,7 @@ mod tests { #[test] fn test_parse_frame_no_mask() { - let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]); + let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(&[1u8]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); @@ -460,7 +460,7 @@ mod tests { #[test] fn test_parse_frame_max_size() { - let mut buf = BytesMut::from(&[0b00000001u8, 0b00000010u8][..]); + let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); buf.extend(&[1u8, 1u8]); let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); From 800f711cc13c6155112f15641af97e529b23bd98 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Apr 2018 21:13:48 -0700 Subject: [PATCH 0009/1635] add PayloadConfig --- src/extractor.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 2 +- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 6f0e5b33..3ada8d5d 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,6 +1,7 @@ use std::str; use std::ops::{Deref, DerefMut}; +use mime::Mime; use bytes::Bytes; use serde_urlencoded; use serde::de::{self, DeserializeOwned}; @@ -301,12 +302,20 @@ impl Default for FormConfig { /// ``` impl FromRequest for Bytes { - type Config = (); - type Result = Box>; + type Config = PayloadConfig; + type Result = Either, + Box>>; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - Box::new(MessageBody::new(req.clone()).from_err()) + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + // check content-type + if let Err(e) = cfg.check_mimetype(req) { + return Either::A(result(Err(e))); + } + + Either::B(Box::new(MessageBody::new(req.clone()) + .limit(cfg.limit) + .from_err())) } } @@ -328,12 +337,18 @@ impl FromRequest for Bytes /// ``` impl FromRequest for String { - type Config = (); + type Config = PayloadConfig; type Result = Either, Box>>; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + // check content-type + if let Err(e) = cfg.check_mimetype(req) { + return Either::A(result(Err(e))); + } + + // check charset let encoding = match req.encoding() { Err(_) => return Either::A( result(Err(ErrorBadRequest("Unknown request charset")))), @@ -342,6 +357,7 @@ impl FromRequest for String Either::B(Box::new( MessageBody::new(req.clone()) + .limit(cfg.limit) .from_err() .and_then(move |body| { let enc: *const Encoding = encoding as *const Encoding; @@ -357,9 +373,57 @@ impl FromRequest for String } } +/// Payload configuration for request's payload. +pub struct PayloadConfig { + limit: usize, + mimetype: Option, +} + +impl PayloadConfig { + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(&mut self, limit: usize) -> &mut Self { + self.limit = limit; + self + } + + /// Set required mime-type of the request. By default mime type is not enforced. + pub fn mimetype(&mut self, mt: Mime) -> &mut Self { + self.mimetype = Some(mt); + self + } + + fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> { + // check content-type + if let Some(ref mt) = self.mimetype { + match req.mime_type() { + Ok(Some(ref req_mt)) => { + if mt != req_mt { + return Err(ErrorBadRequest("Unexpected Content-Type")); + } + }, + Ok(None) => { + return Err(ErrorBadRequest("Content-Type is expected")); + }, + Err(err) => { + return Err(err.into()); + }, + } + } + Ok(()) + } +} + +impl Default for PayloadConfig { + fn default() -> Self { + PayloadConfig{limit: 262_144, mimetype: None} + } +} + #[cfg(test)] mod tests { use super::*; + use mime; use bytes::Bytes; use futures::{Async, Future}; use http::header; @@ -375,10 +439,11 @@ mod tests { #[test] fn test_bytes() { + let cfg = PayloadConfig::default(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match Bytes::from_request(&req, &()).poll().unwrap() { + match Bytes::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { assert_eq!(s, Bytes::from_static(b"hello=world")); }, @@ -388,10 +453,11 @@ mod tests { #[test] fn test_string() { + let cfg = PayloadConfig::default(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match String::from_request(&req, &()).poll().unwrap() { + match String::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { assert_eq!(s, "hello=world"); }, @@ -417,6 +483,21 @@ mod tests { } } + #[test] + fn test_payload_config() { + let req = HttpRequest::default(); + let mut cfg = PayloadConfig::default(); + cfg.mimetype(mime::APPLICATION_JSON); + assert!(cfg.check_mimetype(&req).is_err()); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded").finish(); + assert!(cfg.check_mimetype(&req).is_err()); + + let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); + assert!(cfg.check_mimetype(&req).is_ok()); + } + #[derive(Deserialize)] struct MyStruct { key: String, diff --git a/src/lib.rs b/src/lib.rs index bf536e2d..3e21b397 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,7 +180,7 @@ pub mod dev { pub use json::{JsonBody, JsonConfig}; pub use info::ConnectionInfo; pub use handler::{Handler, Reply}; - pub use extractor::{FormConfig}; + pub use extractor::{FormConfig, PayloadConfig}; pub use route::Route; pub use router::{Router, Resource, ResourceType}; pub use resource::ResourceHandler; From a3f124685a642061b0bdef996bff00da2d37c71d Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 18:32:04 -0400 Subject: [PATCH 0010/1635] Remove redundant quickstart paragraph. --- guide/src/qs_1.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/guide/src/qs_1.md b/guide/src/qs_1.md index e73f6562..f3dda1cc 100644 --- a/guide/src/qs_1.md +++ b/guide/src/qs_1.md @@ -1,8 +1,5 @@ # Quick start -Before you can start writing a actix web applications, you’ll need a version of Rust installed. -We recommend you use rustup to install or configure such a version. - ## Install Rust Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/) installer: From 46e6641528de2a747507453b24278bbd55c33393 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 18:46:36 -0400 Subject: [PATCH 0011/1635] Add repository hyperlink and trim repeat. --- guide/src/qs_1.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guide/src/qs_1.md b/guide/src/qs_1.md index f3dda1cc..343f4419 100644 --- a/guide/src/qs_1.md +++ b/guide/src/qs_1.md @@ -2,7 +2,7 @@ ## Install Rust -Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/) installer: +Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/): ```bash curl https://sh.rustup.rs -sSf | sh @@ -18,9 +18,9 @@ Actix web framework requires rust version 1.21 and up. ## Running Examples -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 `basics` example: +The fastest way to start experimenting with actix web is to clone the [repository](https://github.com/actix/actix-web) and run the included examples. + +The following set of commands runs the `basics` example: ```bash git clone https://github.com/actix/actix-web From 9f45cfe49264a45ed964f21d52ad930887978e55 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 19:12:23 -0400 Subject: [PATCH 0012/1635] Expand note about actix. --- guide/src/qs_2.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index e405775d..8c061f57 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -3,7 +3,7 @@ Let’s create and run our first actix web application. We’ll create a new Cargo project that depends on actix web and then run the application. -In the previous section we already installed the required rust version. Now let's create new cargo projects. +In the previous section we already installed the required rust version. Now let's create a new cargo project. ## Hello, world! @@ -24,7 +24,7 @@ actix = "0.5" actix-web = "0.4" ``` -In order to implement a web server, first we need to create a request handler. +In order to implement a web server, we first need to create a request handler. A request handler is a function that accepts an `HttpRequest` instance as its only parameter and returns a type that can be converted into `HttpResponse`: @@ -64,7 +64,7 @@ connections. The server accepts a function that should return an `HttpHandler` i .run(); ``` -That's it. Now, compile and run the program with `cargo run`. +That's it! Now, compile and run the program with `cargo run`. Head over to ``http://localhost:8088/`` to see the results. Here is full source of main.rs file: @@ -80,7 +80,7 @@ fn index(req: HttpRequest) -> &'static str { fn main() { # // In the doctest suite we can't run blocking code - deliberately leak a thread -# // If copying this example in show-all mode make sure you skip the thread spawn +# // If copying this example in show-all mode, make sure you skip the thread spawn # // call. # thread::spawn(|| { HttpServer::new( @@ -92,7 +92,11 @@ fn main() { } ``` -Note on the `actix` crate. Actix web framework is built on top of actix actor library. +> **Note**: actix web is built upon [actix](https://github.com/actix/actix), +> an [actor model](https://en.wikipedia.org/wiki/Actor_model) framework in Rust. + `actix::System` initializes actor system, `HttpServer` is an actor and must run within a -properly configured actix system. For more information please check -[actix documentation](https://actix.github.io/actix/actix/) +properly configured actix system. + +> For more information, check out the [actix documentation](https://actix.github.io/actix/actix/) +> and [actix guide](https://actix.github.io/actix/guide/). From a0f1ff7eb3d5058aec186cbe684f7585627f0698 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 19:21:29 -0400 Subject: [PATCH 0013/1635] Add src directory to main.rs and list on first codeblock. --- guide/src/qs_2.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 8c061f57..543347cc 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -1,14 +1,10 @@ # Getting Started -Let’s create and run our first actix web application. We’ll create a new Cargo project -that depends on actix web and then run the application. - -In the previous section we already installed the required rust version. Now let's create a new cargo project. +Let’s write our first actix web application! ## Hello, world! -Let’s write our first actix web application! Start by creating a new binary-based -Cargo project and changing into the new directory: +Start by creating a new binary-based Cargo project and changing into the new directory: ```bash cargo new hello-world --bin @@ -29,6 +25,7 @@ In order to implement a web server, we first need to create a request handler. A request handler is a function that accepts an `HttpRequest` instance as its only parameter and returns a type that can be converted into `HttpResponse`: +Filename: src/main.rs ```rust # extern crate actix_web; # use actix_web::*; @@ -67,7 +64,7 @@ connections. The server accepts a function that should return an `HttpHandler` i That's it! Now, compile and run the program with `cargo run`. Head over to ``http://localhost:8088/`` to see the results. -Here is full source of main.rs file: +The full source of src/main.rs is listed below: ```rust # use std::thread; From 3c93e0c654501900f050bba7e24da6b1d46ea852 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 19:25:41 -0400 Subject: [PATCH 0014/1635] Add newline for reading source. --- guide/src/qs_1.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guide/src/qs_1.md b/guide/src/qs_1.md index 343f4419..aac24a7d 100644 --- a/guide/src/qs_1.md +++ b/guide/src/qs_1.md @@ -18,7 +18,8 @@ Actix web framework requires rust version 1.21 and up. ## Running Examples -The fastest way to start experimenting with actix web is to clone the [repository](https://github.com/actix/actix-web) and run the included examples. +The fastest way to start experimenting with actix web is to clone the +[repository](https://github.com/actix/actix-web) and run the included examples. The following set of commands runs the `basics` example: From c2ad65a61dc498d46a4b417891e885b17b51f9a4 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 19:43:17 -0400 Subject: [PATCH 0015/1635] Various tweaks to Application chapter. --- guide/src/qs_3.md | 55 ++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index bcfdee8a..32cd6bfb 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -1,20 +1,21 @@ # Application -Actix web provides some primitives to build web servers and applications with Rust. -It provides routing, middlewares, pre-processing of requests, and post-processing of responses, +Actix web provides various primitives to build web servers and applications with Rust. +It provides routing, middlewares, pre-processing of requests, post-processing of responses, websocket protocol handling, multipart streams, etc. All actix web servers are built around the `App` instance. -It is used for registering routes for resources, and middlewares. -It also stores application specific state that is shared across all handlers -within same application. +It is used for registering routes for resources and middlewares. +It also stores application state shared across all handlers within same application. -Application acts as a namespace for all routes, i.e all routes for a specific application +Applications act as a namespace for all routes, i.e all routes for a specific application have the same url path prefix. The application prefix always contains a leading "/" slash. -If supplied prefix does not contain leading slash, it gets inserted. -The prefix should consist of value path segments. i.e for an application with prefix `/app` -any request with the paths `/app`, `/app/` or `/app/test` would match, -but path `/application` would not match. +If a supplied prefix does not contain leading slash, it is automatically inserted. +The prefix should consist of value path segments. + +> For an application with prefix `/app`, +> any request with the paths `/app`, `/app/`, or `/app/test` would match; +> however, the path `/application` would not match. ```rust,ignore # extern crate actix_web; @@ -31,10 +32,11 @@ but path `/application` would not match. # } ``` -In this example application with `/app` prefix and `index.html` resource -gets 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. +In this example, an application with the `/app` prefix and a `index.html` resource +are created. This resource is available through the `/app/index.html` url. + +> For more information, check the +> [URL Dispatch](./qs_5.html#using-a-application-prefix-to-compose-applications) section. Multiple applications can be served with one server: @@ -59,18 +61,17 @@ fn main() { } ``` -All `/app1` requests route to the first application, `/app2` to the second and then all other to the third. -Applications get matched based on registration order, if an application with more general -prefix is registered before a less generic one, that would effectively block the less generic -application from getting matched. For example, if *application* with prefix "/" gets registered -as first application, it would match all incoming requests. +All `/app1` requests route to the first application, `/app2` to the second, and all other to the third. +**Applications get matched based on registration order**. If an application with a more generic +prefix is registered before a less generic one, it would effectively block the less generic +application matching. For example, if an `App` with the prefix `"/"` was registered +as the first application, it would match all incoming requests. ## State Application state is shared with all routes and resources within the same application. -State can be accessed with the `HttpRequest::state()` method as a read-only, -but an interior mutability pattern with `RefCell` can be used to achieve state mutability. -State can be accessed with `HttpContext::state()` when using an http actor. +When using an http actor,state can be accessed with the `HttpRequest::state()` as read-only, +but interior mutability with `RefCell` can be used to achieve state mutability. State is also available for route matching predicates and middlewares. Let's write a simple application that uses shared state. We are going to store request count @@ -102,8 +103,8 @@ fn main() { } ``` -Note on application state, http server accepts an application factory rather than an application -instance. Http server constructs an application instance for each thread, so application state -must be constructed multiple times. If you want to share state between different threads, a -shared object should be used, like `Arc`. Application state does not need to be `Send` and `Sync` -but the application factory must be `Send` + `Sync`. +> **Note**: http server accepts an application factory rather than an application +> instance. Http server constructs an application instance for each thread, thus application state +> must be constructed multiple times. If you want to share state between different threads, a +> shared object should be used, e.g. `Arc`. Application state does not need to be `Send` and `Sync`, +> but the application factory must be `Send` + `Sync`. From 7f0de705a39bf79d259eec82afe4d4902c7da4a4 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 20:55:19 -0400 Subject: [PATCH 0016/1635] Tweaks to Server chapter. --- guide/src/qs_3_5.md | 96 ++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 27452402..65d8ed71 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -1,13 +1,19 @@ # Server -The [*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for -serving http requests. *HttpServer* accepts application factory as a parameter, -Application factory must have `Send` + `Sync` boundaries. More about that in the -*multi-threading* section. To bind to a specific socket address, `bind()` must be used. -This method can be called multiple times. To start the http server, one of the *start* -methods can be used. `start()` method starts a simple server, `start_tls()` or `start_ssl()` -starts ssl server. *HttpServer* is an actix actor, it has to be initialized -within a properly configured actix system: +The [**HttpServer**](../actix_web/struct.HttpServer.html) type is responsible for +serving http requests. + +`HttpServer` accepts an application factory as a parameter, and the +application factory must have `Send` + `Sync` boundaries. More about that in the +*multi-threading* section. + +To bind to a specific socket address, `bind()` must be used, and it may be called multiple times. +To start the http server, one of the start methods. + +- use `start()` for a simple server +- use `start_tls()` or `start_ssl()` for a ssl server + +`HttpServer` is an actix actor. It must be initialized within a properly configured actix system: ```rust # extern crate actix; @@ -29,17 +35,17 @@ fn main() { } ``` -It is possible to start a server in a separate thread with the *spawn()* method. In that -case the server spawns a new thread and creates a new actix system in it. To stop -this server, send a `StopServer` message. +> It is possible to start a server in a separate thread with the `spawn()` method. In that +> case the server spawns a new thread and creates a new actix system in it. To stop +> this server, send a `StopServer` message. -Http server is implemented as an actix actor. It is possible to communicate with the server -via a messaging system. All start methods like `start()`, `start_ssl()`, etc. return the -address of the started http server. Actix http server accepts several messages: +`HttpServer` is implemented as an actix actor. It is possible to communicate with the server +via a messaging system. All start methods, e.g. `start()` and `start_ssl()`, return the +address of the started http server. It accepts several messages: -* `PauseServer` - Pause accepting incoming connections -* `ResumeServer` - Resume accepting incoming connections -* `StopServer` - Stop incoming connection processing, stop all workers and exit +- `PauseServer` - Pause accepting incoming connections +- `ResumeServer` - Resume accepting incoming connections +- `StopServer` - Stop incoming connection processing, stop all workers and exit ```rust # extern crate futures; @@ -74,7 +80,7 @@ fn main() { ## Multi-threading -Http server automatically starts an number of http workers, by default +`HttpServer` automatically starts an number of http workers, by default this number is equal to number of logical CPUs in the system. This number can be overridden with the `HttpServer::threads()` method. @@ -92,8 +98,10 @@ fn main() { ``` The server creates a 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`. +is not shared between threads. To share state, `Arc` could be used. + +> Application state does not need to be `Send` and `Sync`, +> but factories must be `Send` + `Sync`. ## SSL @@ -123,22 +131,21 @@ fn main() { } ``` -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) -for a full example. +> **Note**: the *HTTP/2.0* protocol requires +> [tls alpn](https://tools.ietf.org/html/rfc7301). +> At the moment, only `openssl` has `alpn` support. +> For a full example, check out +> [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls). ## Keep-Alive -Actix can wait for requests on a keep-alive connection. *Keep alive* -connection behavior is defined by server settings. +Actix can wait for requests on a keep-alive connection. - * `75` or `Some(75)` or `KeepAlive::Timeout(75)` - enable 75 sec *keep alive* timer according - request and response settings. - * `None` or `KeepAlive::Disabled` - disable *keep alive*. - * `KeepAlive::Tcp(75)` - Use `SO_KEEPALIVE` socket option. +> *keep alive* connection behavior is defined by server settings. + +- `75`, `Some(75)`, `KeepAlive::Timeout(75)` - enable 75 second *keep alive* timer. +- `None` or `KeepAlive::Disabled` - disable *keep alive*. +- `KeepAlive::Tcp(75)` - use `SO_KEEPALIVE` socket option. ```rust # extern crate actix_web; @@ -163,11 +170,12 @@ fn main() { } ``` -If first option is selected then *keep alive* state is +If the first option is selected, then *keep alive* state is calculated based on the 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*. +`HttpResponse::connection_type` is not defined. In that case *keep alive* is +defined by the 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* can be change with `HttpResponseBuilder::connection_type()` method. @@ -186,19 +194,19 @@ fn index(req: HttpRequest) -> HttpResponse { ## Graceful shutdown -Actix http server supports graceful shutdown. After receiving a stop signal, workers -have a specific amount of time to finish serving requests. Workers still alive after the +`HttpServer` supports graceful shutdown. After receiving a stop signal, workers +have a specific amount of time to finish serving requests. Any workers still alive after the timeout are force-dropped. By default the shutdown timeout is set to 30 seconds. You can change this parameter with the `HttpServer::shutdown_timeout()` method. You can send a stop message to the server with the server address and specify if you want -graceful shutdown or not. The `start()` methods return address of the server. +graceful shutdown or not. The `start()` methods returns address of the server. -Http server handles several OS signals. *CTRL-C* is available on all OSs, +`HttpServer` 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 +- *SIGINT* - Force shutdown workers +- *SIGTERM* - Graceful shutdown workers +- *SIGQUIT* - Force shutdown workers -It is possible to disable signal handling with `HttpServer::disable_signals()` method. +> It is possible to disable signal handling with `HttpServer::disable_signals()` method. From 961edfd21add398754a79833eb94a866064b860c Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 21:30:52 -0400 Subject: [PATCH 0017/1635] Tweaks to the Handler chapter. --- guide/src/qs_4.md | 75 ++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 5c31a78f..582f7256 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -1,17 +1,18 @@ # Handler A request handler can be any object that implements -[*Handler trait*](../actix_web/dev/trait.Handler.html). -Request handling happens in two stages. First the handler object is called. -Handler can return any object that implements -[*Responder trait*](../actix_web/trait.Responder.html#foreign-impls). -Then `respond_to()` is called on the returned object. And finally -result of the `respond_to()` call is converted to a `Reply` object. +[`Handler` trait](../actix_web/dev/trait.Handler.html). + +Request handling happens in two stages. First the handler object is called, +returning any object that implements the +[`Responder` trait](../actix_web/trait.Responder.html#foreign-impls). +Then, `respond_to()` is called on the returned object, converting itself to a `Reply` or `Error`. By default actix provides `Responder` implementations for some standard types, -like `&'static str`, `String`, etc. -For a complete list of implementations, check -[*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls). +such as `&'static str`, `String`, etc. + +> For a complete list of implementations, check +> [*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls). Examples of valid handlers: @@ -39,15 +40,16 @@ 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 the `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 mutably access its own state -as the `handle` method takes a 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 the same amount as number of threads -of application state objects and handler objects. +*Handler* trait is generic over *S*, which defines the application state's type. +Application state is accessible from the handler with the `HttpRequest::state()` method; +however, state is accessible as a read-only reference. If you need mutable access to state, +it must be implemented. + +> **Note**: Alternatively, the handler can mutably access its own state because the `handle` method takes +> mutable reference to *self*. **Beware**, actix creates multiple copies +> of the application state and the handlers, unique for each thread. If you run your +> application in several threads, actix will create the same amount as number of threads +> of application state objects and handler objects. Here is an example of a handler that stores the number of processed requests: @@ -69,8 +71,8 @@ impl Handler for MyHandler { # fn main() {} ``` -This handler will work, but `self.0` will be different depending on the number of threads and -number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize` +Although this handler will work, `self.0` will be different depending on the number of threads and +number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`. ```rust # extern crate actix; @@ -111,14 +113,15 @@ fn main() { } ``` -Be careful with synchronization primitives like *Mutex* or *RwLock*. Actix web framework -handles requests 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 the [actix](https://actix.github.io/actix/actix/) actor system. +> Be careful with synchronization primitives like `Mutex` or `RwLock`. Actix web framework +> handles requests 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 the [actix](https://actix.github.io/actix/actix/) actor system. ## Response with custom type -To return a custom type directly from a handler function, the type needs to implement the `Responder` trait. +To return a custom type directly from a handler function, the type needs to implement the `Responder` trait. + Let's create a response for a custom type that serializes to an `application/json` response: ```rust @@ -171,10 +174,10 @@ fn main() { ## Async handlers -There are two different types of async handlers. +There are two different types of async handlers. Response objects can be generated asynchronously +or more precisely, any type that implements the [*Responder*](../actix_web/trait.Responder.html) trait. -Response objects can be generated asynchronously or more precisely, any type -that implements the [*Responder*](../actix_web/trait.Responder.html) trait. In this case the handler must return a `Future` object that resolves to the *Responder* type, i.e: +In this case, the handler must return a `Future` object that resolves to the *Responder* type, i.e: ```rust # extern crate actix_web; @@ -205,8 +208,8 @@ fn main() { } ``` -Or the response body can be generated asynchronously. In this case body -must implement stream trait `Stream`, i.e: +Or the response body can be generated asynchronously. In this case, body +must implement the stream trait `Stream`, i.e: ```rust # extern crate actix_web; @@ -233,7 +236,7 @@ fn main() { Both methods can be combined. (i.e Async response with streaming body) It is possible to return a `Result` where the `Result::Item` type can be `Future`. -In this example the `index` handler can return an error immediately or return a +In this example, the `index` handler can return an error immediately or return a future that resolves to a `HttpResponse`. ```rust @@ -265,11 +268,11 @@ fn index(req: HttpRequest) -> Result> ## Different return types (Either) -Sometimes you need to return different types of responses. For example -you can do error check and return error and return async response otherwise. -Or any result that requires two different types. -For this case the [*Either*](../actix_web/enum.Either.html) type can be used. -*Either* allows combining two different responder types into a single type. +Sometimes, you need to return different types of responses. For example, +you can error check and return errors, return async responses, or any result that requires two different types. + +For this case, the [`Either`](../actix_web/enum.Either.html) type can be used. +`Either` allows combining two different responder types into a single type. ```rust # extern crate actix_web; From 6c555012520b98f2df12389a3fdf8b6465c8df41 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 5 Apr 2018 18:33:58 -0700 Subject: [PATCH 0018/1635] client connector wait timeout --- src/client/connector.rs | 158 +++++++++++++++++++++++++++++++--------- src/client/pipeline.rs | 15 +++- 2 files changed, 139 insertions(+), 34 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 4f14a9e2..effee7fa 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,11 +1,11 @@ use std::{fmt, mem, io, time}; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::rc::Rc; use std::net::Shutdown; use std::time::{Duration, Instant}; use std::collections::{HashMap, VecDeque}; -use actix::{fut, Actor, ActorFuture, Context, AsyncContext, +use actix::{fut, Actor, ActorFuture, Arbiter, Context, AsyncContext, Handler, Message, ActorResponse, Supervised, ContextFutureSpawner}; use actix::registry::ArbiterService; use actix::fut::WrapFuture; @@ -16,6 +16,7 @@ use futures::{Async, Future, Poll}; use futures::task::{Task, current as current_task}; use futures::unsync::oneshot; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_core::reactor::Timeout; #[cfg(feature="alpn")] use openssl::ssl::{SslMethod, SslConnector, Error as OpensslError}; @@ -35,8 +36,9 @@ use server::IoStream; /// `Connect` type represents message that can be send to `ClientConnector` /// with connection request. pub struct Connect { - pub uri: Uri, - pub conn_timeout: Duration, + pub(crate) uri: Uri, + pub(crate) wait_time: Duration, + pub(crate) conn_timeout: Duration, } impl Connect { @@ -44,9 +46,25 @@ impl Connect { pub fn new(uri: U) -> Result where Uri: HttpTryFrom { Ok(Connect { uri: Uri::try_from(uri).map_err(|e| e.into())?, - conn_timeout: Duration::from_secs(1) + wait_time: Duration::from_secs(5), + conn_timeout: Duration::from_secs(1), }) } + + /// Connection timeout, max time to connect to remote host. + /// By default connect timeout is 1 seccond. + pub fn conn_timeout(mut self, timeout: Duration) -> Self { + self.conn_timeout = timeout; + self + } + + /// If connection pool limits are enabled, wait time indicates + /// max time to wait for available connection. + /// By default connect timeout is 5 secconds. + pub fn wait_time(mut self, timeout: Duration) -> Self { + self.wait_time = timeout; + self + } } impl Message for Connect { @@ -102,6 +120,7 @@ impl From for ClientConnectorError { struct Waiter { tx: oneshot::Sender>, + wait: Instant, conn_timeout: Duration, } @@ -114,6 +133,8 @@ pub struct ClientConnector { connector: TlsConnector, pool: Rc, + pool_modified: Rc>, + conn_lifetime: Duration, conn_keep_alive: Duration, limit: usize, @@ -123,6 +144,7 @@ pub struct ClientConnector { available: HashMap>, to_close: Vec, waiters: HashMap>, + wait_timeout: Option<(Instant, Timeout)>, } impl Actor for ClientConnector { @@ -140,6 +162,8 @@ impl ArbiterService for ClientConnector {} impl Default for ClientConnector { fn default() -> ClientConnector { + let modified = Rc::new(Cell::new(false)); + #[cfg(all(feature="alpn"))] { let builder = SslConnector::builder(SslMethod::tls()).unwrap(); @@ -149,7 +173,8 @@ impl Default for ClientConnector { { let builder = TlsConnector::builder().unwrap(); ClientConnector { - pool: Rc::new(Pool::new()), + pool: Rc::new(Pool::new(Rc::clone(&modified))), + pool_modified: modified, connector: builder.build().unwrap(), conn_lifetime: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(75), @@ -160,11 +185,13 @@ impl Default for ClientConnector { available: HashMap::new(), to_close: Vec::new(), waiters: HashMap::new(), + wait_timeout: None, } } #[cfg(not(any(feature="alpn", feature="tls")))] - ClientConnector {pool: Rc::new(Pool::new()), + ClientConnector {pool: Rc::new(Pool::new(Rc::clone(&modified))), + pool_modified: modified, conn_lifetime: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(75), limit: 100, @@ -174,6 +201,7 @@ impl Default for ClientConnector { available: HashMap::new(), to_close: Vec::new(), waiters: HashMap::new(), + wait_timeout: None, } } } @@ -224,9 +252,11 @@ impl ClientConnector { /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { + let modified = Rc::new(Cell::new(false)); ClientConnector { connector, - pool: Rc::new(Pool::new()), + pool: Rc::new(Pool::new(Rc::clone(&modified))), + pool_modified: modified, conn_lifetime: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(75), limit: 100, @@ -236,6 +266,7 @@ impl ClientConnector { available: HashMap::new(), to_close: Vec::new(), waiters: HashMap::new(), + wait_timeout: None, } } @@ -357,31 +388,33 @@ impl ClientConnector { fn collect(&mut self, periodic: bool) { let now = Instant::now(); - // collect half acquire keys - if let Some(keys) = self.pool.collect_keys() { - for key in keys { - self.release_key(&key); + if self.pool_modified.get() { + // collect half acquire keys + if let Some(keys) = self.pool.collect_keys() { + for key in keys { + self.release_key(&key); + } } - } - // collect connections for close - if let Some(to_close) = self.pool.collect_close() { - for conn in to_close { - self.release_key(&conn.key); - self.to_close.push(conn); + // collect connections for close + if let Some(to_close) = self.pool.collect_close() { + for conn in to_close { + self.release_key(&conn.key); + self.to_close.push(conn); + } } - } - // connection connections - if let Some(to_release) = self.pool.collect_release() { - for conn in to_release { - self.release_key(&conn.key); + // connection connections + if let Some(to_release) = self.pool.collect_release() { + for conn in to_release { + self.release_key(&conn.key); - // check connection lifetime and the return to available pool - if (now - conn.ts) < self.conn_lifetime { - self.available.entry(conn.key.clone()) - .or_insert_with(VecDeque::new) - .push_back(Conn(Instant::now(), conn)); + // check connection lifetime and the return to available pool + if (now - conn.ts) < self.conn_lifetime { + self.available.entry(conn.key.clone()) + .or_insert_with(VecDeque::new) + .push_back(Conn(Instant::now(), conn)); + } } } } @@ -412,6 +445,8 @@ impl ClientConnector { } } } + + self.pool_modified.set(false); } fn collect_periodic(&mut self, ctx: &mut Context) { @@ -419,15 +454,58 @@ impl ClientConnector { // re-schedule next collect period ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); } + + fn collect_waiters(&mut self) { + let now = Instant::now(); + let mut next = None; + + for (_, waiters) in &mut self.waiters { + let mut idx = 0; + while idx < waiters.len() { + if waiters[idx].wait <= now { + let waiter = waiters.swap_remove_back(idx).unwrap(); + let _ = waiter.tx.send(Err(ClientConnectorError::Timeout)); + } else { + if let Some(n) = next { + if waiters[idx].wait < n { + next = Some(waiters[idx].wait); + } + } else { + next = Some(waiters[idx].wait); + } + idx += 1; + } + } + } + + if next.is_some() { + self.install_wait_timeout(next.unwrap()); + } + } + + fn install_wait_timeout(&mut self, time: Instant) { + if let Some(ref mut wait) = self.wait_timeout { + if wait.0 < time { + return + } + } + + let mut timeout = Timeout::new(time-Instant::now(), Arbiter::handle()).unwrap(); + let _ = timeout.poll(); + self.wait_timeout = Some((time, timeout)); + } } impl Handler for ClientConnector { type Result = ActorResponse; fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result { - self.collect(false); + if self.pool_modified.get() { + self.collect(false); + } let uri = &msg.uri; + let wait_time = msg.wait_time; let conn_timeout = msg.conn_timeout; // host name is required @@ -469,7 +547,11 @@ impl Handler for ClientConnector { Acquire::NotAvailable => { // connection is not available, wait let (tx, rx) = oneshot::channel(); - let waiter = Waiter{ tx, conn_timeout }; + + let wait = Instant::now() + wait_time; + self.install_wait_timeout(wait); + + let waiter = Waiter{ tx, wait, conn_timeout }; self.waiters.entry(key.clone()).or_insert_with(VecDeque::new) .push_back(waiter); return ActorResponse::async( @@ -563,8 +645,13 @@ impl fut::ActorFuture for Maintenance fn poll(&mut self, act: &mut ClientConnector, ctx: &mut Context) -> Poll { - // collecto connections - act.collect(false); + // collect connections + if act.pool_modified.get() { + act.collect(false); + } + + // collect wait timers + act.collect_waiters(); // check waiters let tmp: &mut ClientConnector = unsafe{mem::transmute(act as &mut _)}; @@ -781,11 +868,13 @@ pub struct Pool { to_close: RefCell>, to_release: RefCell>, task: RefCell>, + modified: Rc>, } impl Pool { - fn new() -> Pool { + fn new(modified: Rc>) -> Pool { Pool { + modified, keys: RefCell::new(Vec::new()), to_close: RefCell::new(Vec::new()), to_release: RefCell::new(Vec::new()), @@ -818,6 +907,7 @@ impl Pool { } fn close(&self, conn: Connection) { + self.modified.set(true); self.to_close.borrow_mut().push(conn); if let Some(ref task) = *self.task.borrow() { task.notify() @@ -825,6 +915,7 @@ impl Pool { } fn release(&self, conn: Connection) { + self.modified.set(true); self.to_release.borrow_mut().push(conn); if let Some(ref task) = *self.task.borrow() { task.notify() @@ -832,6 +923,7 @@ impl Pool { } fn release_key(&self, key: Key) { + self.modified.set(true); self.keys.borrow_mut().push(key); if let Some(ref task) = *self.task.borrow() { task.notify() diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index aefffc89..feb44366 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -69,6 +69,7 @@ pub struct SendRequest { state: State, conn: Addr, conn_timeout: Duration, + wait_time: Duration, timeout: Option, } @@ -83,7 +84,8 @@ impl SendRequest { SendRequest{req, conn, state: State::New, timeout: None, - conn_timeout: Duration::from_secs(1) + wait_time: Duration::from_secs(5), + conn_timeout: Duration::from_secs(1), } } @@ -93,6 +95,7 @@ impl SendRequest { state: State::Connection(conn), conn: ClientConnector::from_registry(), timeout: None, + wait_time: Duration::from_secs(5), conn_timeout: Duration::from_secs(1), } } @@ -115,6 +118,15 @@ impl SendRequest { self.conn_timeout = timeout; self } + + /// Set wait time + /// + /// If connections pool limits are enabled, wait time indicates max time + /// to wait for available connection. Default value is 5 seconds. + pub fn wait_time(mut self, timeout: Duration) -> Self { + self.wait_time = timeout; + self + } } impl Future for SendRequest { @@ -129,6 +141,7 @@ impl Future for SendRequest { State::New => self.state = State::Connect(self.conn.send(Connect { uri: self.req.uri().clone(), + wait_time: self.wait_time, conn_timeout: self.conn_timeout, })), State::Connect(mut conn) => match conn.poll() { From 0f86c596fac4f29e367cc2c0528e1d81a0bbcbde Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 21:54:39 -0400 Subject: [PATCH 0019/1635] Tweaks to Errors chapter. --- guide/src/qs_4_5.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md index cf8c6ef3..4bc82451 100644 --- a/guide/src/qs_4_5.md +++ b/guide/src/qs_4_5.md @@ -1,10 +1,11 @@ # Errors -Actix uses [`Error` type](../actix_web/error/struct.Error.html) +Actix uses the [`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 the `ResponseError` trait can be returned as an error value. -*Handler* can return an *Result* object; actix by default provides +`Handler` can return an `Result` object. By default, actix provides a `Responder` implementation for compatible result types. Here is the implementation definition: @@ -12,7 +13,8 @@ definition: impl> Responder for Result ``` -And any error that implements `ResponseError` can be converted into an `Error` object. +Any error that implements `ResponseError` can be converted into an `Error` object. + For example, if the *handler* function returns `io::Error`, it would be converted into an `HttpInternalServerError` response. Implementation for `io::Error` is provided by default. @@ -35,7 +37,7 @@ fn index(req: HttpRequest) -> io::Result { ## Custom error response -To add support for custom errors, all we need to do is just implement the `ResponseError` trait +To add support for custom errors, all we need to do is implement the `ResponseError` trait for the custom error type. The `ResponseError` trait has a default implementation for the `error_response()` method: it generates a *500* response. @@ -109,7 +111,7 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> { ## Error helpers Actix provides a set of error helper types. It is possible to use them for generating -specific error responses. We can use helper types for the first example with custom error. +specific error responses. We can use the helper types for the first example with a custom error. ```rust # extern crate actix_web; From 2a543001e0a65f5cce1e9cde3e3171f39e9ea42d Mon Sep 17 00:00:00 2001 From: memoryruins Date: Thu, 5 Apr 2018 22:12:20 -0400 Subject: [PATCH 0020/1635] Tweaks to the URL Dispatch chapter. --- guide/src/qs_5.md | 67 +++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index f97840a0..6f66af43 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -2,17 +2,19 @@ URL dispatch provides a simple way for mapping URLs to `Handler` code using a simple pattern matching language. 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 the -`Handler` trait, defined in your application, that receives the request and returns -a response object. More information is available in the [handler section](../qs_4.html). +a particular handler object is invoked. + +> A handler is a specific object that implements the +> `Handler` trait, defined in your application, that receives the request and returns +> a response object. More information is available in the [handler section](../qs_4.html). ## Resource configuration Resource configuration is the act of adding a new resources 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*, -it does not match against the *QUERY* portion (the portion following the scheme and +A resource also has a pattern, meant to match against the *PATH* portion of a *URL*. +It does not match against the *QUERY* portion (the portion following the scheme and port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*). The [App::resource](../actix_web/struct.App.html#method.resource) methods @@ -43,7 +45,7 @@ The *Configuration function* has the following type: ``` The *Configuration function* can set a name and register specific routes. -If a resource does not contain any route or does not have any matching routes it +If a resource does not contain any route or does not have any matching routes, it returns *NOT FOUND* http response. ## Configuring a Route @@ -55,8 +57,9 @@ all requests and the default handler is `HttpNotFound`. The application routes incoming requests based on route criteria which are defined during resource registration and route registration. Resource matches all routes it contains in -the order the routes were registered via `Resource::route()`. A *Route* can contain -any number of *predicates* but only one handler. +the order the routes were registered via `Resource::route()`. + +> A *Route* can contain any number of *predicates* but only one handler. ```rust # extern crate actix_web; @@ -74,10 +77,11 @@ fn main() { } ``` -In this example `HttpResponse::Ok()` is returned for *GET* requests, -if request contains `Content-Type` header and value of this header is *text/plain* -and path equals to `/path`. Resource calls handle of the first matching route. -If a resource can not match any route a "NOT FOUND" response is returned. +In this example, `HttpResponse::Ok()` is returned for *GET* requests. +If a request contains `Content-Type` header, the value of this header is *text/plain*, +and path equals to `/path`, Resource calls handle of the first matching route. + +If a resource can not match any route, a "NOT FOUND" response is returned. [*Resource::route()*](../actix_web/struct.Resource.html#method.route) returns a [*Route*](../actix_web/struct.Route.html) object. Route can be configured with a @@ -118,9 +122,7 @@ arguments provided to a route configuration returns `false` during a check, that 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 -the route is invoked. - -If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned. +the route is invoked. If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned. ## Resource pattern syntax @@ -208,8 +210,9 @@ 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. +> **Note**: 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: ``` @@ -292,11 +295,11 @@ 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. +* 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. @@ -350,8 +353,8 @@ fn main() { } ``` -It also possible to extract path information to a tuple, in this case you don't need -to define extra type, just use tuple for as a `Path` generic type. +It also possible to extract path information to a tuple. In this case, you don't need +to define an extra type; use a tuple as a `Path` generic type. Here is previous example re-written using tuple instead of specific type. @@ -433,21 +436,21 @@ fn main() { By normalizing it means: - - Add a trailing slash to the path. - - Double slashes are replaced by one. +* 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 +If *append* is *true*, append slash when needed. If a resource is defined with trailing slash and the request doesn't have one, it will be appended 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*. +This handler designed to be used as a handler for application's *default resource*. ```rust # extern crate actix_web; @@ -468,7 +471,7 @@ fn main() { In this example `/resource`, `//resource///` will be redirected to `/resource/`. -In this example path normalization handler is registered for all methods, +In this example, the path normalization handler is registered for all methods, 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. @@ -493,7 +496,7 @@ fn main() { ## Using an Application Prefix to Compose Applications -The `App::prefix()`" method allows to set a specific application prefix. +The `App::prefix()` method allows to set a 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 @@ -556,7 +559,7 @@ fn main() { } ``` -In this example *index* handler will be called only if request contains *CONTENT-TYPE* header. +In this example, *index* handler will be called only if request contains *CONTENT-TYPE* header. Predicates have access to the application's state via `HttpRequest::state()`. Also predicates can store extra information in @@ -565,7 +568,7 @@ Also predicates can store extra information in ### Modifying 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 +For example, if you want to return "METHOD NOT ALLOWED" response for all methods except "GET": ```rust From 2dafd9c6814904c2bd2e968fefb844343de76d1e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 08:40:11 -0700 Subject: [PATCH 0021/1635] do not re-export HttpServer from server module --- examples/basics/src/main.rs | 6 +++--- examples/http-proxy/src/main.rs | 9 +++++---- examples/json/src/main.rs | 2 +- examples/juniper/src/main.rs | 2 +- examples/multipart/src/main.rs | 2 +- examples/protobuf/src/main.rs | 2 +- examples/r2d2/src/main.rs | 2 +- examples/state/src/main.rs | 4 ++-- examples/template_tera/src/main.rs | 4 ++-- examples/tls/src/main.rs | 2 +- examples/unix-socket/src/main.rs | 4 ++-- examples/websocket-chat/src/main.rs | 4 ++-- examples/websocket/src/main.rs | 2 +- guide/src/qs_10.md | 4 ++-- guide/src/qs_2.md | 4 ++-- guide/src/qs_3.md | 4 ++-- guide/src/qs_3_5.md | 14 ++++++-------- guide/src/qs_4.md | 4 ++-- src/client/connector.rs | 12 ++++++------ src/lib.rs | 1 - src/server/mod.rs | 6 ++++-- 21 files changed, 47 insertions(+), 47 deletions(-) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index 750fc764..cfc9933d 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -8,8 +8,8 @@ extern crate futures; use futures::Stream; use std::{io, env}; -use actix_web::{error, fs, pred, - App, HttpRequest, HttpResponse, HttpServer, Result, Error}; +use actix_web::{error, fs, pred, server, + App, HttpRequest, HttpResponse, Result, Error}; use actix_web::http::{Method, StatusCode}; use actix_web::middleware::{self, RequestSession}; use futures::future::{FutureResult, result}; @@ -99,7 +99,7 @@ fn main() { env_logger::init(); let sys = actix::System::new("basic-example"); - let addr = HttpServer::new( + let addr = server::new( || App::new() // enable logger .middleware(middleware::Logger::default()) diff --git a/examples/http-proxy/src/main.rs b/examples/http-proxy/src/main.rs index a69fff88..0a392ed8 100644 --- a/examples/http-proxy/src/main.rs +++ b/examples/http-proxy/src/main.rs @@ -4,9 +4,10 @@ extern crate futures; extern crate env_logger; use futures::{Future, Stream}; -use actix_web::{client, server, middleware, - App, AsyncResponder, Body, - HttpRequest, HttpResponse, HttpMessage, Error}; +use actix_web::{ + client, server, middleware, + App, AsyncResponder, Body, HttpRequest, HttpResponse, HttpMessage, Error}; + /// Stream client request response and then send body to a server response fn index(_req: HttpRequest) -> Box> { @@ -44,7 +45,7 @@ fn main() { env_logger::init(); let sys = actix::System::new("http-proxy"); - let _addr = server::new( + server::new( || App::new() .middleware(middleware::Logger::default()) .resource("/streaming", |r| r.f(streaming)) diff --git a/examples/json/src/main.rs b/examples/json/src/main.rs index 73863eef..f864e008 100644 --- a/examples/json/src/main.rs +++ b/examples/json/src/main.rs @@ -89,7 +89,7 @@ fn main() { env_logger::init(); let sys = actix::System::new("json-example"); - let _ = server::new(|| { + server::new(|| { App::new() // enable logger .middleware(middleware::Logger::default()) diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs index 11676505..a92ce3fb 100644 --- a/examples/juniper/src/main.rs +++ b/examples/juniper/src/main.rs @@ -94,7 +94,7 @@ fn main() { }); // Start http server - let _ = server::new(move || { + server::new(move || { App::with_state(AppState{executor: addr.clone()}) // enable logger .middleware(middleware::Logger::default()) diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs index cac76d30..75f28963 100644 --- a/examples/multipart/src/main.rs +++ b/examples/multipart/src/main.rs @@ -49,7 +49,7 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("multipart-example"); - let _ = server::new( + server::new( || App::new() .middleware(middleware::Logger::default()) // <- logger .resource("/multipart", |r| r.method(http::Method::POST).a(index))) diff --git a/examples/protobuf/src/main.rs b/examples/protobuf/src/main.rs index ae61e0e4..c0a2abb3 100644 --- a/examples/protobuf/src/main.rs +++ b/examples/protobuf/src/main.rs @@ -44,7 +44,7 @@ fn main() { env_logger::init(); let sys = actix::System::new("protobuf-example"); - let _ = server::new(|| { + server::new(|| { App::new() .middleware(middleware::Logger::default()) .resource("/", |r| r.method(http::Method::POST).f(index))}) diff --git a/examples/r2d2/src/main.rs b/examples/r2d2/src/main.rs index a3cf637c..5e6d07f8 100644 --- a/examples/r2d2/src/main.rs +++ b/examples/r2d2/src/main.rs @@ -53,7 +53,7 @@ fn main() { let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone())); // Start http server - let _ = server::new(move || { + server::new(move || { App::with_state(State{db: addr.clone()}) // enable logger .middleware(middleware::Logger::default()) diff --git a/examples/state/src/main.rs b/examples/state/src/main.rs index e3b0890b..804b68c6 100644 --- a/examples/state/src/main.rs +++ b/examples/state/src/main.rs @@ -55,10 +55,10 @@ impl StreamHandler for MyWebSocket { fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); + env_logger::init(); let sys = actix::System::new("ws-example"); - let _ = server::new( + server::new( || App::with_state(AppState{counter: Cell::new(0)}) // enable logger .middleware(middleware::Logger::default()) diff --git a/examples/template_tera/src/main.rs b/examples/template_tera/src/main.rs index fb512d2c..e1a738d3 100644 --- a/examples/template_tera/src/main.rs +++ b/examples/template_tera/src/main.rs @@ -30,10 +30,10 @@ fn index(req: HttpRequest) -> Result { fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); + env_logger::init(); let sys = actix::System::new("tera-example"); - let _ = server::new(|| { + server::new(|| { let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")); App::with_state(State{template: tera}) diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs index 809af171..479ef8c0 100644 --- a/examples/tls/src/main.rs +++ b/examples/tls/src/main.rs @@ -29,7 +29,7 @@ fn main() { builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap(); - let _ = server::new( + server::new( || App::new() // enable logger .middleware(middleware::Logger::default()) diff --git a/examples/unix-socket/src/main.rs b/examples/unix-socket/src/main.rs index aeb749d1..c3071847 100644 --- a/examples/unix-socket/src/main.rs +++ b/examples/unix-socket/src/main.rs @@ -4,7 +4,7 @@ extern crate env_logger; extern crate tokio_uds; use actix::*; -use actix_web::*; +use actix_web::{middleware, server, App, HttpRequest}; use tokio_uds::UnixListener; @@ -19,7 +19,7 @@ fn main() { let listener = UnixListener::bind( "/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed"); - HttpServer::new( + server::new( || App::new() // enable logger .middleware(middleware::Logger::default()) diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index 1de3900c..d9b495c9 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -17,7 +17,7 @@ extern crate actix_web; use std::time::Instant; use actix::*; -use actix_web::{http, fs, ws, App, HttpRequest, HttpResponse, HttpServer, Error}; +use actix_web::{http, fs, ws, server::HttpServer, App, HttpRequest, HttpResponse, Error}; mod codec; mod server; @@ -183,7 +183,7 @@ fn main() { })); // Create Http server with websocket support - let addr = HttpServer::new( + HttpServer::new( move || { // Websocket sessions state let state = WsChatSessionState { addr: server.clone() }; diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index bcf2ee7b..07ad7ff4 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -45,7 +45,7 @@ impl StreamHandler for MyWebSocket { fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); + env_logger::init(); let sys = actix::System::new("ws-example"); server::new( diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index aaff39ae..ce1ed4a7 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -179,7 +179,7 @@ session data. ```rust # extern crate actix; # extern crate actix_web; -use actix_web::*; +use actix_web::{server, App, HttpRequest, Result}; use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend}; fn index(mut req: HttpRequest) -> Result<&'static str> { @@ -196,7 +196,7 @@ fn index(mut req: HttpRequest) -> Result<&'static str> { fn main() { # let sys = actix::System::new("basic-example"); - HttpServer::new( + server::new( || App::new() .middleware(SessionStorage::new( // <- create session middleware CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 543347cc..91fa8ec8 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -69,7 +69,7 @@ The full source of src/main.rs is listed below: ```rust # use std::thread; extern crate actix_web; -use actix_web::{App, HttpRequest, HttpResponse, HttpServer}; +use actix_web::{server, App, HttpRequest, HttpResponse}; fn index(req: HttpRequest) -> &'static str { "Hello world!" @@ -80,7 +80,7 @@ fn main() { # // If copying this example in show-all mode, make sure you skip the thread spawn # // call. # thread::spawn(|| { - HttpServer::new( + server::HttpServer::new( || App::new() .resource("/", |r| r.f(index))) .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 32cd6bfb..d5c0b325 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -45,10 +45,10 @@ Multiple applications can be served with one server: # extern crate tokio_core; # use tokio_core::net::TcpStream; # use std::net::SocketAddr; -use actix_web::{App, HttpResponse, HttpServer}; +use actix_web::{server, App, HttpResponse}; fn main() { - HttpServer::new(|| vec![ + server::new(|| vec![ App::new() .prefix("/app1") .resource("/", |r| r.f(|r| HttpResponse::Ok())), diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md index 65d8ed71..82e83ff1 100644 --- a/guide/src/qs_3_5.md +++ b/guide/src/qs_3_5.md @@ -1,6 +1,6 @@ # Server -The [**HttpServer**](../actix_web/struct.HttpServer.html) type is responsible for +The [**HttpServer**](../actix_web/server/struct.HttpServer.html) type is responsible for serving http requests. `HttpServer` accepts an application factory as a parameter, and the @@ -18,13 +18,12 @@ To start the http server, one of the start methods. ```rust # extern crate actix; # extern crate actix_web; -use actix::*; -use actix_web::{server, App, HttpResponse}; +use actix_web::{server::HttpServer, App, HttpResponse}; fn main() { let sys = actix::System::new("guide"); - server::new( + HttpServer::new( || App::new() .resource("/", |r| r.f(|_| HttpResponse::Ok()))) .bind("127.0.0.1:59080").unwrap() @@ -54,8 +53,7 @@ address of the started http server. It accepts several messages: # use futures::Future; use std::thread; use std::sync::mpsc; -use actix::*; -use actix_web::{server, App, HttpResponse, HttpServer}; +use actix_web::{server, App, HttpResponse}; fn main() { let (tx, rx) = mpsc::channel(); @@ -87,7 +85,7 @@ can be overridden with the `HttpServer::threads()` method. ```rust # extern crate actix_web; # extern crate tokio_core; -use actix_web::{App, HttpServer, HttpResponse}; +use actix_web::{App, HttpResponse, server::HttpServer}; fn main() { HttpServer::new( @@ -123,7 +121,7 @@ fn main() { builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap(); - HttpServer::new( + server::new( || App::new() .resource("/index.html", |r| r.f(index))) .bind("127.0.0.1:8080").unwrap() diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index 582f7256..1a1ff617 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -130,7 +130,7 @@ Let's create a response for a custom type that serializes to an `application/jso extern crate serde; extern crate serde_json; #[macro_use] extern crate serde_derive; -use actix_web::{App, HttpServer, HttpRequest, HttpResponse, Error, Responder, http}; +use actix_web::{server, App, HttpRequest, HttpResponse, Error, Responder, http}; #[derive(Serialize)] struct MyObj { @@ -160,7 +160,7 @@ fn index(req: HttpRequest) -> MyObj { fn main() { let sys = actix::System::new("example"); - HttpServer::new( + server::new( || App::new() .resource("/", |r| r.method(http::Method::GET).f(index))) .bind("127.0.0.1:8088").unwrap() diff --git a/src/client/connector.rs b/src/client/connector.rs index effee7fa..85ecd22b 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -162,7 +162,7 @@ impl ArbiterService for ClientConnector {} impl Default for ClientConnector { fn default() -> ClientConnector { - let modified = Rc::new(Cell::new(false)); + let _modified = Rc::new(Cell::new(false)); #[cfg(all(feature="alpn"))] { @@ -173,8 +173,8 @@ impl Default for ClientConnector { { let builder = TlsConnector::builder().unwrap(); ClientConnector { - pool: Rc::new(Pool::new(Rc::clone(&modified))), - pool_modified: modified, + pool: Rc::new(Pool::new(Rc::clone(&_modified))), + pool_modified: _modified, connector: builder.build().unwrap(), conn_lifetime: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(75), @@ -190,8 +190,8 @@ impl Default for ClientConnector { } #[cfg(not(any(feature="alpn", feature="tls")))] - ClientConnector {pool: Rc::new(Pool::new(Rc::clone(&modified))), - pool_modified: modified, + ClientConnector {pool: Rc::new(Pool::new(Rc::clone(&_modified))), + pool_modified: _modified, conn_lifetime: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(75), limit: 100, @@ -459,7 +459,7 @@ impl ClientConnector { let now = Instant::now(); let mut next = None; - for (_, waiters) in &mut self.waiters { + for waiters in self.waiters.values_mut() { let mut idx = 0; while idx < waiters.len() { if waiters[idx].wait <= now { diff --git a/src/lib.rs b/src/lib.rs index 3e21b397..f0aef5cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,6 @@ pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use handler::{Either, Responder, AsyncResponder, FromRequest, FutureResponse, State}; pub use context::HttpContext; -pub use server::HttpServer; #[doc(hidden)] pub mod httpcodes; diff --git a/src/server/mod.rs b/src/server/mod.rs index 96f53c5f..bbaa7f74 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -32,7 +32,9 @@ use httpresponse::HttpResponse; /// max buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; -/// Create new http server with application factory +/// Create new http server with application factory. +/// +/// This is shortcut for `server::HttpServer::new()` method. /// /// ```rust /// # extern crate actix; @@ -46,7 +48,7 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// server::new( /// || App::new() /// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59080").unwrap() +/// .bind("127.0.0.1:59090").unwrap() /// .start(); /// /// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); From 691457fbfe78a8b6ae11d12b7a93ea8b00de8045 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 09:45:10 -0700 Subject: [PATCH 0022/1635] update tests --- README.md | 4 ++-- src/application.rs | 4 ++-- src/lib.rs | 4 ++-- src/server/srv.rs | 4 ++-- tests/test_server.rs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 46f589d6..6f9a41bc 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,14 @@ Actix web is a simple, pragmatic, extremely fast, web framework for Rust. ```rust extern crate actix_web; -use actix_web::{App, HttpServer, Path}; +use actix_web::{server, App, Path}; fn index(info: Path<(String, u32)>) -> String { format!("Hello {}! id:{}", info.0, info.1) } fn main() { - HttpServer::new( + server::new( || App::new() .resource("/{name}/{id}/index.html", |r| r.with(index))) .bind("127.0.0.1:8080").unwrap() diff --git a/src/application.rs b/src/application.rs index 96a932c9..872f413e 100644 --- a/src/application.rs +++ b/src/application.rs @@ -439,7 +439,7 @@ impl App where S: 'static { /// ```rust /// # use std::thread; /// # extern crate actix_web; - /// use actix_web::*; + /// use actix_web::{server, App, HttpResponse}; /// /// struct State1; /// @@ -447,7 +447,7 @@ impl App where S: 'static { /// /// fn main() { /// # thread::spawn(|| { - /// HttpServer::new(|| { vec![ + /// server::new(|| { vec![ /// App::with_state(State1) /// .prefix("/app1") /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) diff --git a/src/lib.rs b/src/lib.rs index f0aef5cc..436c0c0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Actix web is a small, pragmatic, extremely fast, web framework for Rust. //! //! ```rust -//! use actix_web::{App, HttpServer, Path}; +//! use actix_web::{server, App, Path}; //! # use std::thread; //! //! fn index(info: Path<(String, u32)>) -> String { @@ -10,7 +10,7 @@ //! //! fn main() { //! # thread::spawn(|| { -//! HttpServer::new( +//! server::new( //! || App::new() //! .resource("/{name}/{id}/index.html", |r| r.with(index))) //! .bind("127.0.0.1:8080").unwrap() diff --git a/src/server/srv.rs b/src/server/srv.rs index 041021ac..f8915e0d 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -262,12 +262,12 @@ impl HttpServer /// ```rust /// extern crate actix; /// extern crate actix_web; - /// use actix_web::*; + /// use actix_web::{server, App, HttpResponse}; /// /// fn main() { /// let sys = actix::System::new("example"); // <- create Actix system /// - /// HttpServer::new( + /// server::new( /// || App::new() /// .resource("/", |r| r.h(|_| HttpResponse::Ok()))) /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") diff --git a/tests/test_server.rs b/tests/test_server.rs index a13fc2f8..8f3401db 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -63,7 +63,7 @@ fn test_start() { thread::spawn(move || { let sys = System::new("test"); - let srv = HttpServer::new( + let srv = server::new( || vec![App::new() .resource( "/", |r| r.method(http::Method::GET) @@ -108,7 +108,7 @@ fn test_shutdown() { thread::spawn(move || { let sys = System::new("test"); - let srv = HttpServer::new( + let srv = server::new( || vec![App::new() .resource( "/", |r| r.method(http::Method::GET).f(|_| HttpResponse::Ok()))]); From af0c8d893d7f13145deeacc9305f2e269eedba85 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 10:09:31 -0700 Subject: [PATCH 0023/1635] add shortcut method for client requests --- src/client/mod.rs | 63 +++++++++++++++++++++++++++++++++++++++++++ src/client/request.rs | 31 +++++++++++++++++++-- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 5abe4ff6..8becafc9 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -14,6 +14,7 @@ pub(crate) use self::writer::HttpClientWriter; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; use error::ResponseError; +use http::Method; use httpresponse::HttpResponse; @@ -28,3 +29,65 @@ impl ResponseError for SendRequestError { .into() } } + +/// Create request builder for `GET` request +/// +/// ```rust +/// # extern crate actix; +/// # extern crate actix_web; +/// # extern crate futures; +/// # use futures::Future; +/// use actix_web::client; +/// +/// fn main() { +/// let sys = actix::System::new("test"); +/// +/// actix::Arbiter::handle().spawn({ +/// client::get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .finish().unwrap() +/// .send() // <- Send http request +/// .map_err(|_| ()) +/// .and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +/// Ok(()) +/// }) +/// }); +/// +/// sys.run(); +/// } +/// ``` +pub fn get>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::GET).uri(uri); + builder +} + +/// Create request builder for `HEAD` request +pub fn head>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::HEAD).uri(uri); + builder +} + +/// Create request builder for `POST` request +pub fn post>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::POST).uri(uri); + builder +} + +/// Create request builder for `PUT` request +pub fn put>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::PUT).uri(uri); + builder +} + +/// Create request builder for `DELETE` request +pub fn delete>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::DELETE).uri(uri); + builder +} diff --git a/src/client/request.rs b/src/client/request.rs index 8f2967ab..d58a323a 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -6,8 +6,6 @@ use std::time::Duration; use actix::{Addr, Unsync}; use cookie::{Cookie, CookieJar}; use bytes::{Bytes, BytesMut, BufMut}; -use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError}; -use http::header::{self, HeaderName, HeaderValue}; use futures::Stream; use serde_json; use serde::Serialize; @@ -19,10 +17,39 @@ use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; use httpmessage::HttpMessage; use httprequest::HttpRequest; +use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError}; +use http::header::{self, HeaderName, HeaderValue}; use super::pipeline::SendRequest; use super::connector::{Connection, ClientConnector}; /// An HTTP Client Request +/// +/// ```rust +/// # extern crate actix; +/// # extern crate actix_web; +/// # extern crate futures; +/// # use futures::Future; +/// use actix_web::client::ClientRequest; +/// +/// fn main() { +/// let sys = actix::System::new("test"); +/// +/// actix::Arbiter::handle().spawn({ +/// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .finish().unwrap() +/// .send() // <- Send http request +/// .map_err(|_| ()) +/// .and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +/// Ok(()) +/// }) +/// }); +/// +/// sys.run(); +/// } +/// ``` pub struct ClientRequest { uri: Uri, method: Method, From 2c411a04a946a22968367e6784b1f064a2c5f66e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 10:15:06 -0700 Subject: [PATCH 0024/1635] no need for export in doc example --- examples/websocket-chat/src/main.rs | 3 ++- src/extractor.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs index d9b495c9..ee5c1c45 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -17,7 +17,8 @@ extern crate actix_web; use std::time::Instant; use actix::*; -use actix_web::{http, fs, ws, server::HttpServer, App, HttpRequest, HttpResponse, Error}; +use actix_web::server::HttpServer; +use actix_web::{http, fs, ws, App, HttpRequest, HttpResponse, Error}; mod codec; mod server; diff --git a/src/extractor.rs b/src/extractor.rs index 3ada8d5d..f1ce6bdf 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -23,7 +23,6 @@ use de::PathDeserializer; /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; -/// #[macro_use] extern crate serde_derive; /// use actix_web::{App, Path, Result, http}; /// /// /// extract path info from "/{username}/{count}/?index.html" url From 084104d0589058d0e372bb468a5dcf20ca65887c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 10:24:57 -0700 Subject: [PATCH 0025/1635] update doc strings for extractors --- src/extractor.rs | 14 ++++++++++++-- src/json.rs | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index f1ce6bdf..1fc6c078 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -183,6 +183,9 @@ impl FromRequest for Query /// To extract typed information from request's body, the type `T` must implement the /// `Deserialize` trait from *serde*. /// +/// [**FormConfig**](dev/struct.FormConfig.html) allows to configure extraction +/// process. +/// /// ## Example /// /// It is possible to extract path information to a specific type that implements @@ -199,7 +202,7 @@ impl FromRequest for Query /// } /// /// /// extract form data using serde -/// /// this handle get called only if content type is *x-www-form-urlencoded* +/// /// this handler get called only if content type is *x-www-form-urlencoded* /// /// and content of the request could be deserialized to a `FormData` struct /// fn index(form: Form) -> Result { /// Ok(format!("Welcome {}!", form.username)) @@ -249,7 +252,8 @@ impl FromRequest for Form /// username: String, /// } /// -/// /// extract form data using serde, max payload size is 4k +/// /// extract form data using serde. +/// /// custom configuration is used for this handler, max payload size is 4k /// fn index(form: Form) -> Result { /// Ok(format!("Welcome {}!", form.username)) /// } @@ -286,6 +290,9 @@ impl Default for FormConfig { /// /// Loads request's payload and construct Bytes instance. /// +/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure extraction +/// process. +/// /// ## Example /// /// ```rust @@ -322,6 +329,9 @@ impl FromRequest for Bytes /// /// Text extractor automatically decode body according to the request's charset. /// +/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure +/// extraction process. +/// /// ## Example /// /// ```rust diff --git a/src/json.rs b/src/json.rs index 722b35ce..977c8d18 100644 --- a/src/json.rs +++ b/src/json.rs @@ -84,6 +84,8 @@ impl Responder for Json { /// To extract typed information from request's body, the type `T` must implement the /// `Deserialize` trait from *serde*. /// +/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction process. +/// /// ## Example /// /// ```rust From 8d5fa6ee71c7e7aaba79c78af5d81a4107a3a229 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 11:08:41 -0700 Subject: [PATCH 0026/1635] added Pause/Resume for client connector --- src/client/connector.rs | 102 ++++++++++++++++++++++++++++++++++------ src/client/mod.rs | 4 +- src/client/pipeline.rs | 16 +++---- 3 files changed, 99 insertions(+), 23 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 85ecd22b..0ad066ae 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -37,7 +37,7 @@ use server::IoStream; /// with connection request. pub struct Connect { pub(crate) uri: Uri, - pub(crate) wait_time: Duration, + pub(crate) wait_timeout: Duration, pub(crate) conn_timeout: Duration, } @@ -46,7 +46,7 @@ impl Connect { pub fn new(uri: U) -> Result where Uri: HttpTryFrom { Ok(Connect { uri: Uri::try_from(uri).map_err(|e| e.into())?, - wait_time: Duration::from_secs(5), + wait_timeout: Duration::from_secs(5), conn_timeout: Duration::from_secs(1), }) } @@ -60,9 +60,9 @@ impl Connect { /// If connection pool limits are enabled, wait time indicates /// max time to wait for available connection. - /// By default connect timeout is 5 secconds. - pub fn wait_time(mut self, timeout: Duration) -> Self { - self.wait_time = timeout; + /// By default wait timeout is 5 secconds. + pub fn wait_timeout(mut self, timeout: Duration) -> Self { + self.wait_timeout = timeout; self } } @@ -71,6 +71,21 @@ impl Message for Connect { type Result = Result; } +/// Pause connection process for `ClientConnector` +/// +/// All connect requests enter wait state during connector pause. +pub struct Pause { + time: Option, +} + +impl Message for Pause { + type Result = (); +} + +/// Resume connection process for `ClientConnector` +#[derive(Message)] +pub struct Resume; + /// A set of errors that can occur during connecting to a http host #[derive(Fail, Debug)] pub enum ClientConnectorError { @@ -145,6 +160,7 @@ pub struct ClientConnector { to_close: Vec, waiters: HashMap>, wait_timeout: Option<(Instant, Timeout)>, + paused: Option>, } impl Actor for ClientConnector { @@ -186,6 +202,7 @@ impl Default for ClientConnector { to_close: Vec::new(), waiters: HashMap::new(), wait_timeout: None, + paused: None, } } @@ -202,6 +219,7 @@ impl Default for ClientConnector { to_close: Vec::new(), waiters: HashMap::new(), wait_timeout: None, + paused: None, } } } @@ -267,6 +285,7 @@ impl ClientConnector { to_close: Vec::new(), waiters: HashMap::new(), wait_timeout: None, + paused: None, } } @@ -494,6 +513,47 @@ impl ClientConnector { let _ = timeout.poll(); self.wait_timeout = Some((time, timeout)); } + + fn wait_for(&mut self, key: Key, + wait: Duration, conn_timeout: Duration) + -> oneshot::Receiver> + { + // connection is not available, wait + let (tx, rx) = oneshot::channel(); + + let wait = Instant::now() + wait; + self.install_wait_timeout(wait); + + let waiter = Waiter{ tx, wait, conn_timeout }; + self.waiters.entry(key.clone()).or_insert_with(VecDeque::new) + .push_back(waiter); + rx + } +} + +impl Handler for ClientConnector { + type Result = (); + + fn handle(&mut self, msg: Pause, _: &mut Self::Context) { + if let Some(time) = msg.time { + let when = Instant::now() + time; + let mut timeout = Timeout::new(time, Arbiter::handle()).unwrap(); + let _ = timeout.poll(); + self.paused = Some(Some((when, timeout))); + } else { + if self.paused.is_none() { + self.paused = Some(None); + } + } + } +} + +impl Handler for ClientConnector { + type Result = (); + + fn handle(&mut self, _: Resume, _: &mut Self::Context) { + self.paused.take(); + } } impl Handler for ClientConnector { @@ -505,7 +565,7 @@ impl Handler for ClientConnector { } let uri = &msg.uri; - let wait_time = msg.wait_time; + let wait_timeout = msg.wait_timeout; let conn_timeout = msg.conn_timeout; // host name is required @@ -536,6 +596,19 @@ impl Handler for ClientConnector { let port = uri.port().unwrap_or_else(|| proto.port()); let key = Key {host, port, ssl: proto.is_secure()}; + // check pause state + if self.paused.is_some() { + let rx = self.wait_for(key, wait_timeout, conn_timeout); + return ActorResponse::async( + rx.map_err(|_| ClientConnectorError::Disconnected) + .into_actor(self) + .and_then(|res, _, _| match res { + Ok(conn) => fut::ok(conn), + Err(err) => fut::err(err), + })); + + } + // acquire connection let pool = if proto.is_http() { match self.acquire(&key) { @@ -546,14 +619,7 @@ impl Handler for ClientConnector { }, Acquire::NotAvailable => { // connection is not available, wait - let (tx, rx) = oneshot::channel(); - - let wait = Instant::now() + wait_time; - self.install_wait_timeout(wait); - - let waiter = Waiter{ tx, wait, conn_timeout }; - self.waiters.entry(key.clone()).or_insert_with(VecDeque::new) - .push_back(waiter); + let rx = self.wait_for(key, wait_timeout, conn_timeout); return ActorResponse::async( rx.map_err(|_| ClientConnectorError::Disconnected) .into_actor(self) @@ -645,6 +711,14 @@ impl fut::ActorFuture for Maintenance fn poll(&mut self, act: &mut ClientConnector, ctx: &mut Context) -> Poll { + // check pause duration + let done = if let Some(Some(ref pause)) = act.paused { + if pause.0 <= Instant::now() {true} else {false} + } else { false }; + if done { + act.paused.take(); + } + // collect connections if act.pool_modified.get() { act.collect(false); diff --git a/src/client/mod.rs b/src/client/mod.rs index 8becafc9..8b5713a2 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -9,7 +9,9 @@ mod writer; pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; -pub use self::connector::{Connect, Connection, ClientConnector, ClientConnectorError}; +pub use self::connector::{ + Connect, Pause, Resume, + Connection, ClientConnector, ClientConnectorError}; pub(crate) use self::writer::HttpClientWriter; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index feb44366..7b91adb2 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -62,14 +62,14 @@ enum State { None, } -/// `SendRequest` is a `Future` which represents asynchronous request sending process. +/// `SendRequest` is a `Future` which represents asynchronous sending process. #[must_use = "SendRequest does nothing unless polled"] pub struct SendRequest { req: ClientRequest, state: State, conn: Addr, conn_timeout: Duration, - wait_time: Duration, + wait_timeout: Duration, timeout: Option, } @@ -84,7 +84,7 @@ impl SendRequest { SendRequest{req, conn, state: State::New, timeout: None, - wait_time: Duration::from_secs(5), + wait_timeout: Duration::from_secs(5), conn_timeout: Duration::from_secs(1), } } @@ -95,7 +95,7 @@ impl SendRequest { state: State::Connection(conn), conn: ClientConnector::from_registry(), timeout: None, - wait_time: Duration::from_secs(5), + wait_timeout: Duration::from_secs(5), conn_timeout: Duration::from_secs(1), } } @@ -119,12 +119,12 @@ impl SendRequest { self } - /// Set wait time + /// Set wait timeout /// /// If connections pool limits are enabled, wait time indicates max time /// to wait for available connection. Default value is 5 seconds. - pub fn wait_time(mut self, timeout: Duration) -> Self { - self.wait_time = timeout; + pub fn wait_timeout(mut self, timeout: Duration) -> Self { + self.wait_timeout = timeout; self } } @@ -141,7 +141,7 @@ impl Future for SendRequest { State::New => self.state = State::Connect(self.conn.send(Connect { uri: self.req.uri().clone(), - wait_time: self.wait_time, + wait_timeout: self.wait_timeout, conn_timeout: self.conn_timeout, })), State::Connect(mut conn) => match conn.poll() { From 5d8cbccfe961229c468ea8b8551c02098614510f Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 15:12:06 -0400 Subject: [PATCH 0027/1635] Remove article. --- guide/src/qs_1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/qs_1.md b/guide/src/qs_1.md index aac24a7d..5e31aec6 100644 --- a/guide/src/qs_1.md +++ b/guide/src/qs_1.md @@ -2,7 +2,7 @@ ## Install Rust -Before we begin, we need to install Rust using the [rustup](https://www.rustup.rs/): +Before we begin, we need to install Rust using [rustup](https://www.rustup.rs/): ```bash curl https://sh.rustup.rs -sSf | sh From 5bd5f67d79ff67f0c134f389b155adf448ccc92e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 12:31:31 -0700 Subject: [PATCH 0028/1635] add Pause message constructors --- src/client/connector.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/client/connector.rs b/src/client/connector.rs index 0ad066ae..dbd98bfb 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -78,6 +78,19 @@ pub struct Pause { time: Option, } +impl Pause { + /// Create message with pause duration parameter + fn new(time: Duration) -> Pause { + Pause{time: Some(time)} + } +} + +impl Default for Pause { + fn default() -> Pause { + Pause{time: None} + } +} + impl Message for Pause { type Result = (); } From 2d4ee0ee014744e22ea8bfa2b19c12d5187ccfa8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 12:34:24 -0700 Subject: [PATCH 0029/1635] make Pause::new public --- src/client/connector.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index dbd98bfb..5e364ae9 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -80,7 +80,7 @@ pub struct Pause { impl Pause { /// Create message with pause duration parameter - fn new(time: Duration) -> Pause { + pub fn new(time: Duration) -> Pause { Pause{time: Some(time)} } } From 191b53bd7c4ca763467508333e4f0ea00ff93739 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 13:22:27 -0700 Subject: [PATCH 0030/1635] pin futures 0.1 --- examples/basics/Cargo.toml | 2 +- examples/state/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/basics/Cargo.toml b/examples/basics/Cargo.toml index 76bfa52b..294075d4 100644 --- a/examples/basics/Cargo.toml +++ b/examples/basics/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Nikolay Kim "] workspace = "../.." [dependencies] -futures = "*" +futures = "0.1" env_logger = "0.5" actix = "0.5" actix-web = { path="../.." } diff --git a/examples/state/Cargo.toml b/examples/state/Cargo.toml index bd3ba243..a0ac2d28 100644 --- a/examples/state/Cargo.toml +++ b/examples/state/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Nikolay Kim "] workspace = "../.." [dependencies] -futures = "*" +futures = "0.1" env_logger = "0.5" actix = "0.5" actix-web = { path = "../../" } From fdb7419e24bd81c20e249d30c1e9c07214892f12 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 14:11:04 -0700 Subject: [PATCH 0031/1635] use actix-web from master --- README.md | 8 ++++++++ guide/src/qs_2.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f9a41bc..8e93552f 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,14 @@ Actix web is a simple, pragmatic, extremely fast, web framework for Rust. ## Example +At the moment all examples uses actix-web master. + +```toml +[dependencies] +actix = "0.5" +actix-web = { git="https://github.com/actix/actix-web.git" } +``` + ```rust extern crate actix_web; use actix_web::{server, App, Path}; diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 91fa8ec8..524c2c1d 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -17,7 +17,7 @@ contains the following: ```toml [dependencies] actix = "0.5" -actix-web = "0.4" +actix-web = { git="https://github.com/actix/actix-web.git" } ``` In order to implement a web server, we first need to create a request handler. From 0fbd05009df877a450ac6fe4563b3c7f03e318b3 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 17:31:18 -0400 Subject: [PATCH 0032/1635] Guide: tweaks to the request and response chapter. --- guide/src/qs_7.md | 61 +++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index fab21a34..9b664939 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -5,8 +5,11 @@ A builder-like pattern is used to construct an instance of `HttpResponse`. `HttpResponse` provides several methods that return a `HttpResponseBuilder` instance, which implements various convenience methods that helps building responses. -Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html) -for type descriptions. The methods `.body`, `.finish`, `.json` finalize response creation and + +> Check the [documentation](../actix_web/dev/struct.HttpResponseBuilder.html) +> for type descriptions. + +The methods `.body`, `.finish`, `.json` finalize response creation and return a constructed *HttpResponse* instance. If this methods is called for the same builder instance multiple times, the builder will panic. @@ -28,19 +31,19 @@ fn index(req: HttpRequest) -> HttpResponse { Actix automatically *compresses*/*decompresses* payloads. Following codecs are supported: - * Brotli - * Gzip - * Deflate - * Identity +* Brotli +* Gzip +* Deflate +* Identity - If request headers contain a `Content-Encoding` header, the request payload is decompressed - according to the header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. +If request headers contain a `Content-Encoding` header, the request payload is decompressed +according to the header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. Response payload is compressed based on the *content_encoding* parameter. -By default `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected +By default `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected, then compression depends on the request's `Accept-Encoding` header. `ContentEncoding::Identity` can be used to disable compression. -If another content encoding is selected the compression is enforced for this codec. For example, +If another content encoding is selected, the compression is enforced for this codec. For example, to enable `brotli` use `ContentEncoding::Br`: ```rust @@ -55,13 +58,12 @@ fn index(req: HttpRequest) -> HttpResponse { # fn main() {} ``` - ## JSON Request There are several options for json body deserialization. -The first option is to use *Json* extractor. You define handler function -that accepts `Json` as a parameter and use `.with()` method for registering +The first option is to use *Json* extractor. You define a handler function +that accepts `Json` as a parameter and use the `.with()` method for registering this handler. It is also possible to accept arbitrary valid json object by using `serde_json::Value` as a type `T` @@ -116,8 +118,9 @@ fn index(mut req: HttpRequest) -> Box> { # fn main() {} ``` -Or you can manually load the payload into memory and then deserialize it. -Here is a simple example. We will deserialize a *MyObj* struct. We need to load the request +Alternatively, you can manually load the payload into memory and then deserialize it. + +In the following example, we will deserialize a *MyObj* struct. We need to load the request body first and then deserialize the json into an object. ```rust @@ -149,9 +152,8 @@ fn index(req: HttpRequest) -> Box> { # fn main() {} ``` -A complete example for both options is available in -[examples directory](https://github.com/actix/actix-web/tree/master/examples/json/). - +> A complete example for both options is available in +> [examples directory](https://github.com/actix/actix-web/tree/master/examples/json/). ## JSON Response @@ -186,12 +188,12 @@ Actix automatically decodes *chunked* encoding. `HttpRequest::payload()` already the decoded byte stream. If the request payload is compressed with one of the supported compression codecs (br, gzip, deflate) the byte stream is decompressed. -Chunked encoding on response can be enabled with `HttpResponseBuilder::chunked()`. -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 +Chunked encoding on response can be enabled with `HttpResponseBuilder::chunked()`, +but this takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies. +Also, if the response payload compression is enabled and a streaming body is used, chunked encoding is enabled automatically. -Enabling chunked encoding for *HTTP/2.0* responses is forbidden. +> Enabling chunked encoding for *HTTP/2.0* responses is forbidden. ```rust # extern crate bytes; @@ -218,7 +220,7 @@ a stream of multipart items, each item can be a [*Field*](../actix_web/multipart/struct.Field.html) or a nested *Multipart* stream. `HttpResponse::multipart()` returns the *Multipart* stream for the current request. -In simple form multipart stream handling can be implemented similar to this example +Simple form multipart stream handling could be implemented like the following: ```rust,ignore # extern crate actix_web; @@ -248,17 +250,18 @@ fn index(req: HttpRequest) -> Box> { } ``` -A full example is available in the -[examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/). +> A full example is available in the +> [examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/). ## Urlencoded body Actix provides support for *application/x-www-form-urlencoded* encoded bodies. `HttpResponse::urlencoded()` returns a [*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, which resolves -to the deserialized instance, the type of the instance must implement the -`Deserialize` trait from *serde*. The *UrlEncoded* future can resolve into -a error in several cases: +to the deserialized instance. The type of the instance must implement the +`Deserialize` trait from *serde*. + +The *UrlEncoded* future can resolve into an error in several cases: * content type is not `application/x-www-form-urlencoded` * transfer encoding is `chunked`. @@ -294,7 +297,7 @@ fn index(mut req: HttpRequest) -> Box> { *HttpRequest* is a stream of `Bytes` objects. It can be used to read the request body payload. -In this example handle reads the request payload chunk by chunk and prints every chunk. +In the following example, we read and print the request payload chunk by chunk: ```rust # extern crate actix_web; From ab60ec6e1df60a8832f3f141701b30f0099a7bd7 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 18:03:30 -0400 Subject: [PATCH 0033/1635] Guide: updates to the Testing chapter. --- guide/src/qs_8.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md index 380f9e0e..9d6327cf 100644 --- a/guide/src/qs_8.md +++ b/guide/src/qs_8.md @@ -5,9 +5,9 @@ integration tests. ## Unit tests -For unit testing actix provides a request builder type and simple handler runner. +For unit testing, actix provides a request builder type and simple handler runner. [*TestRequest*](../actix_web/test/struct.TestRequest.html) implements a builder-like pattern. -You can generate a `HttpRequest` instance with `finish()` or you can +You can generate a `HttpRequest` instance with `finish()`, or you can run your handler with `run()` or `run_async()`. ```rust @@ -36,19 +36,20 @@ fn main() { } ``` - ## Integration tests -There are several methods how you can test your application. Actix provides -[*TestServer*](../actix_web/test/struct.TestServer.html) -server that can be used to run the whole application of just specific handlers -in real http server. *TestServer::get()*, *TestServer::post()* or *TestServer::client()* +There are several methods for testing your application. Actix provides +[*TestServer*](../actix_web/test/struct.TestServer.html), which can be used +to run the application with specific handlers in a real http server. + +`TestServer::get()`, `TestServer::post()`, or `TestServer::client()` methods can be used to send requests to the test server. -In simple form *TestServer* can be configured to use handler. *TestServer::new* method -accepts configuration function, only argument for this function is *test application* -instance. You can check the [api documentation](../actix_web/test/struct.TestApp.html) -for more information. +A simple form `TestServer` can be configured to use a handler. +`TestServer::new` method accepts a configuration function, and the only argument +for this function is a *test application* instance. + +> Check the [api documentation](../actix_web/test/struct.TestApp.html) for more information. ```rust # extern crate actix_web; @@ -70,8 +71,8 @@ fn main() { } ``` -The other option is to use an application factory. In this case you need to pass the factory -function same way as you would for real http server configuration. +The other option is to use an application factory. In this case, you need to pass the factory +function the same way as you would for real http server configuration. ```rust # extern crate actix_web; @@ -98,11 +99,10 @@ fn main() { } ``` -If you need more complex application configuration, for example you may need to -initialize application state or start `SyncActor`'s for diesel interation, you -can use `TestServer::build_with_state()` method. This method accepts closure -that has to construct application state. This closure runs when actix system is -configured already, so you can initialize any additional actors. +If you need more complex application configuration, use the `TestServer::build_with_state()` +method. For example, you may need to initialize application state or start `SyncActor`'s for diesel +interation. This method accepts a closure that constructs the application state, +and it runs when the actix system is configured. Thus, you can initialize any additional actors. ```rust,ignore #[test] @@ -127,10 +127,10 @@ fn test() { ## WebSocket server tests -It is possible to register a *handler* with `TestApp::handler()` that -initiates a web socket connection. *TestServer* provides `ws()` which connects to +It is possible to register a *handler* with `TestApp::handler()`, which +initiates a web socket connection. *TestServer* provides the method `ws()`, which connects to the websocket server and returns ws reader and writer objects. *TestServer* also -provides an `execute()` method which runs future objects to completion and returns +provides an `execute()` method, which runs future objects to completion and returns result of the future computation. Here is a simple example that shows how to test server websocket handler. From 1f08100f6f7bbeac92f6c689c174394c69d4ec70 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 18:04:42 -0400 Subject: [PATCH 0034/1635] Guide: updates to the WebSockets chapter. --- guide/src/qs_9.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index 158ba251..b7fdc840 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -3,10 +3,10 @@ Actix supports WebSockets out-of-the-box. It is possible to convert a 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 simpler to handle websocket communications +combinators to handle actual messages, but it is simpler to handle websocket communications with an http actor. -This is example of a simple websocket echo server: +The following is example of a simple websocket echo server: ```rust # extern crate actix; @@ -41,8 +41,8 @@ fn main() { } ``` -A simple websocket echo server example is available in the -[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket). +> A simple websocket echo server example is available in the +> [examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket). -An example chat server with the ability to chat over a websocket or tcp connection -is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) +> An example chat server with the ability to chat over a websocket or tcp connection +> is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) From a88e97edba030bde272e8739bf0b6d34d0aabc9b Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 18:29:18 -0400 Subject: [PATCH 0035/1635] Guide: updates to Middleware chapter. --- guide/src/qs_10.md | 78 ++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index ce1ed4a7..0594c04b 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -1,23 +1,25 @@ # Middleware -Actix' middleware system allows to add additional behavior to request/response processing. -Middleware can hook into incoming request process and modify request or halt request -processing and return response early. Also it can hook into response processing. +Actix's middleware system allows us to add additional behavior to request/response processing. +Middleware can hook into an incoming request process, enabling the ability to modify requests +as well as the ability to halt request processing and return response early. -Typically middlewares are involved in the following actions: +Middleware can also hook into response processing. + +Typically, middleware is involved in the 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 are executed in same order as -registration order. In general, a *middleware* is a type that implements the +Middleware is registered for each application and executed in same order as +registration. In general, a *middleware* is a type that implements the [*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method in this trait has a default implementation. Each method can return a result immediately or a *future* object. -Here is an example of a simple middleware that adds request and response headers: +The following is an example of a simple middleware that adds request and response headers: ```rust # extern crate http; @@ -57,16 +59,17 @@ fn main() { } ``` -Actix provides several useful middlewares, like *logging*, *user sessions*, etc. - +> Actix provides several useful middlewares, such as *logging*, *user sessions*, etc. ## Logging Logging is implemented as a middleware. It is common to register a logging middleware as the first middleware for the application. -Logging middleware has to be registered for each application. *Logger* middleware -uses the standard log crate to log information. You should enable logger for *actix_web* -package to see access log ([env_logger](https://docs.rs/env_logger/*/env_logger/) or similar). +Logging middleware must be registered for each application. + +The `Logger` middleware uses the standard log crate to log information. You should enable logger +for *actix_web* package to see access log +([env_logger](https://docs.rs/env_logger/*/env_logger/) or similar). ### Usage @@ -76,6 +79,7 @@ Default `Logger` can be created with `default` method, it uses the default forma ```ignore %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T ``` + ```rust # extern crate actix_web; extern crate env_logger; @@ -93,7 +97,7 @@ fn main() { } ``` -Here is an example of the default logging format: +The following is an example of the default logging format: ``` 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 @@ -126,12 +130,11 @@ INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] `%{FOO}e` os.environ['FOO'] - ## Default headers -To set default response headers the `DefaultHeaders` middleware can be used. The +To set default response headers, the `DefaultHeaders` middleware can be used. The *DefaultHeaders* middleware does not set the header if response headers already contain -the specified header. +a specified header. ```rust # extern crate actix_web; @@ -153,27 +156,28 @@ fn main() { ## User sessions Actix provides a general solution for session management. The -[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware can be +[**SessionStorage**](../actix_web/middleware/struct.SessionStorage.html) middleware can be used 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/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 is generated if session contains more than 4000 bytes. +> By default, only cookie session backend is implemented. Other backend implementations +> can be added. -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). +[**CookieSessionBackend**](../actix_web/middleware/struct.CookieSessionBackend.html) +uses signed cookies as session storage. `CookieSessionBackend` creates sessions which +are limited to storing fewer than 4000 bytes of data, as the payload must fit into a +single cookie. An internal server error is generated if a session contains more than 4000 bytes. -In general case, you create -[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware -and initializes it with specific backend implementation, like *CookieSessionBackend*. -To access session data +You need to pass a random value to the constructor of `CookieSessionBackend`. +This is a private key for cookie session. When this value is changed, all session data is lost. + +> **Note**: anything you write into the session is visible by the user, but it is not modifiable. + +In general, you create a +`SessionStorage` middleware and initialize it with specific backend implementation, +such as a `CookieSessionBackend`. To access session data, [*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session) - has to be used. This method returns a -[*Session*](../actix_web/middleware/struct.Session.html) object, which allows to get or set + must be used. This method returns a +[*Session*](../actix_web/middleware/struct.Session.html) object, which allows us to get or set session data. ```rust @@ -212,12 +216,12 @@ fn main() { ## Error handlers -`ErrorHandlers` middleware allows to provide custom handlers for responses. +`ErrorHandlers` middleware allows us to provide custom handlers for responses. -You can use `ErrorHandlers::handler()` method to register a custom error handler -for specific status code. You can modify existing response or create completly new -one. Error handler can return response immediately or return future that resolves -to a response. +You can use the `ErrorHandlers::handler()` method to register a custom error handler +for specific status code. You can modify an existing response or create completly new +one. The error handler can return a response immediately or return a future that resolves +into a response. ```rust # extern crate actix_web; From c3fbba26786029d067a2e4f39b1300c13815d609 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 18:40:57 -0400 Subject: [PATCH 0036/1635] Guide: updates to static file handling chapter. --- guide/src/qs_12.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 1da5f1ef..2feb5a1b 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -2,8 +2,8 @@ ## Individual file -It is possible to serve static files with custom path pattern and `NamedFile`. To -match path tail we can use `[.*]` regex. +It is possible to serve static files with a custom path pattern and `NamedFile`. To +match a path tail, we can use a `[.*]` regex. ```rust # extern crate actix_web; @@ -24,9 +24,9 @@ fn main() { ## Directory -To serve files from specific directory and sub-directories `StaticFiles` could be used. -`StaticFiles` must be registered with `App::handler()` method otherwise -it won't be able to serve sub-paths. +To serve files from specific directories and sub-directories, `StaticFiles` can be used. +`StaticFiles` must be registered with an `App::handler()` method, otherwise +it will be unable to serve sub-paths. ```rust # extern crate actix_web; @@ -39,11 +39,11 @@ fn main() { } ``` -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. +The first parameter is the base directory. If the second parameter, *show_index*, is set to **true**, +the directory listing will be returned, and if it is set to **false**, +*404 Not Found* will be returned. -Instead of showing files listing for directory, it is possible to redirect to specific -index file. Use +Instead of showing files listing for directory, it is possible to redirect to a specific +index file. Use the [*StaticFiles::index_file()*](../actix_web/s/struct.StaticFiles.html#method.index_file) method to configure this redirect. From e7f9f5b46d6e4e754d3f0a7eced79b330e1b5f14 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 18:46:56 -0400 Subject: [PATCH 0037/1635] Guide: updates to HTTP/2 chapter. --- guide/src/qs_13.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/guide/src/qs_13.md b/guide/src/qs_13.md index 753a9c16..bd25b7b6 100644 --- a/guide/src/qs_13.md +++ b/guide/src/qs_13.md @@ -1,13 +1,15 @@ # HTTP/2.0 -Actix web automatically upgrades connection to *HTTP/2.0* if possible. +Actix web automatically upgrades connections to *HTTP/2.0* if possible. ## Negotiation *HTTP/2.0* protocol over tls without prior knowledge requires -[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only -`rust-openssl` has support. Turn on the `alpn` feature to enable `alpn` negotiation. -With enabled `alpn` feature `HttpServer` provides the +[tls alpn](https://tools.ietf.org/html/rfc7301). + +> Currently, only `rust-openssl` has support. + +`alpn` negotiation requires enabling the feature. When enabled, `HttpServer` provides the [serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method. ```toml @@ -35,10 +37,10 @@ fn main() { } ``` -Upgrade to *HTTP/2.0* schema described in +Upgrades 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) -Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls) -for a concrete example. +> Check the [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls) +> for a concrete example. From 0f0fe5f14893302033729189eed8e4e6e71f17fd Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 19:02:11 -0400 Subject: [PATCH 0038/1635] Guide: updates to the Database integration chapter. --- guide/src/qs_14.md | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index a805e7a5..0fe16f17 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -2,13 +2,14 @@ ## Diesel -At the moment of 1.0 release Diesel does not support asynchronous operations. -But it possible to use the `actix` synchronous actor system as a db interface api. -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 mpsc mode). +At the moment, Diesel 1.0 does not support asynchronous operations, +but it possible to use the `actix` synchronous actor system as a database interface api. -Let's create a simple db api that can insert a new user row into an SQLite table. -We have to define sync actor and connection that this actor will use. The same approach +Technically, sync actors are worker style actors. Multiple sync actors +can be run in parallel and process messages from same queue. Sync actors work in mpsc mode. + +Let's create a simple database api that can insert a new user row into a SQLite table. +We must define a sync actor and a connection that this actor will use. The same approach can be used for other databases. ```rust,ignore @@ -21,7 +22,7 @@ impl Actor for DbExecutor { } ``` -This is the definition of our actor. Now we need to define the *create user* message and response. +This is the definition of our actor. Now, we must define the *create user* message and response. ```rust,ignore struct CreateUser { @@ -33,8 +34,8 @@ impl Message for CreateUser { } ``` -We can send a `CreateUser` message to the `DbExecutor` actor, and as a result we get a -`User` model instance. Now we need to define the actual handler implementation for this message. +We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we receive a +`User` model instance. Next, we must define the handler implementation for this message. ```rust,ignore impl Handler for DbExecutor { @@ -67,7 +68,7 @@ impl Handler for DbExecutor { } ``` -That's it. Now we can use the *DbExecutor* actor from any http handler or middleware. +That's it! Now, we can use the *DbExecutor* actor from any http handler or middleware. All we need is to start *DbExecutor* actors and store the address in a state where http handler can access it. @@ -97,9 +98,9 @@ fn main() { } ``` -And finally we can use the address in a request handler. We get a message response -asynchronously, so the handler needs to return a future object, also `Route::a()` needs to be -used for async handler registration. +Finally, we use the address in a request handler. We receive the message response +asynchronously, thus the handler returns a future object. +`Route::a()` must be used for async handler registration. ```rust,ignore @@ -120,8 +121,8 @@ fn index(req: HttpRequest) -> Box> } ``` -Full example is available in the -[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/). +> A full example is available in the +> [examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/). -More information on sync actors can be found in the -[actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html). +> More information on sync actors can be found in the +> [actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html). From 7cff5d9adecc6a5830dfe38586fb272b6bc37d7e Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 19:17:03 -0400 Subject: [PATCH 0039/1635] Guide: additional tweaks to request and response chapter. --- guide/src/qs_7.md | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 9b664939..3e869451 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -4,13 +4,13 @@ A builder-like pattern is used to construct an instance of `HttpResponse`. `HttpResponse` provides several methods that return a `HttpResponseBuilder` instance, -which implements various convenience methods that helps building responses. +which implements various convenience methods for building responses. > Check the [documentation](../actix_web/dev/struct.HttpResponseBuilder.html) > for type descriptions. -The methods `.body`, `.finish`, `.json` finalize response creation and -return a constructed *HttpResponse* instance. If this methods is called for the same +The methods `.body`, `.finish`, and `.json` finalize response creation and +return a constructed *HttpResponse* instance. If this methods is called on the same builder instance multiple times, the builder will panic. ```rust @@ -29,7 +29,7 @@ fn index(req: HttpRequest) -> HttpResponse { ## Content encoding -Actix automatically *compresses*/*decompresses* payloads. Following codecs are supported: +Actix automatically *compresses*/*decompresses* payloads. The following codecs are supported: * Brotli * Gzip @@ -40,11 +40,13 @@ If request headers contain a `Content-Encoding` header, the request payload is d according to the header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. Response payload is compressed based on the *content_encoding* parameter. -By default `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected, -then compression depends on the request's `Accept-Encoding` header. -`ContentEncoding::Identity` can be used to disable compression. -If another content encoding is selected, the compression is enforced for this codec. For example, -to enable `brotli` use `ContentEncoding::Br`: +By default, `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected, +then the compression depends on the request's `Accept-Encoding` header. + +> `ContentEncoding::Identity` can be used to disable compression. +> If another content encoding is selected, the compression is enforced for that codec. + +For example, to enable `brotli` use `ContentEncoding::Br`: ```rust # extern crate actix_web; @@ -62,10 +64,10 @@ fn index(req: HttpRequest) -> HttpResponse { There are several options for json body deserialization. -The first option is to use *Json* extractor. You define a handler function -that accepts `Json` as a parameter and use the `.with()` method for registering +The first option is to use *Json* extractor. First, you define a handler function +that accepts `Json` as a parameter, then, you use the `.with()` method for registering this handler. It is also possible to accept arbitrary valid json object by -using `serde_json::Value` as a type `T` +using `serde_json::Value` as a type `T`. ```rust # extern crate actix_web; @@ -89,7 +91,7 @@ fn main() { } ``` -The second option is to use *HttpResponse::json()*. This method returns a +Another option is to use *HttpResponse::json()*. This method returns a [*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into the deserialized value. @@ -118,7 +120,7 @@ fn index(mut req: HttpRequest) -> Box> { # fn main() {} ``` -Alternatively, you can manually load the payload into memory and then deserialize it. +You may also manually load the payload into memory and then deserialize it. In the following example, we will deserialize a *MyObj* struct. We need to load the request body first and then deserialize the json into an object. @@ -158,8 +160,8 @@ fn index(req: HttpRequest) -> Box> { ## JSON Response The `Json` type allows 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*. +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; @@ -186,11 +188,11 @@ fn main() { Actix automatically decodes *chunked* encoding. `HttpRequest::payload()` already contains the decoded byte stream. If the request payload is compressed with one of the supported -compression codecs (br, gzip, deflate) the byte stream is decompressed. +compression codecs (br, gzip, deflate), then the byte stream is decompressed. -Chunked encoding on response can be enabled with `HttpResponseBuilder::chunked()`, -but this takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies. -Also, if the response payload compression is enabled and a streaming body is used, chunked encoding +Chunked encoding on a response can be enabled with `HttpResponseBuilder::chunked()`. +This takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies. +If the response payload compression is enabled and a streaming body is used, chunked encoding is enabled automatically. > Enabling chunked encoding for *HTTP/2.0* responses is forbidden. @@ -216,11 +218,11 @@ fn index(req: HttpRequest) -> HttpResponse { Actix provides multipart stream support. [*Multipart*](../actix_web/multipart/struct.Multipart.html) is implemented as -a stream of multipart items, each item can be a +a stream of multipart items. Each item can be a [*Field*](../actix_web/multipart/struct.Field.html) or a nested *Multipart* stream. `HttpResponse::multipart()` returns the *Multipart* stream for the current request. -Simple form multipart stream handling could be implemented like the following: +The following demonstrates multipart stream handling for a simple form: ```rust,ignore # extern crate actix_web; From 1a45dbd768fc1b36404b9e54131811c9d708241f Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 19:26:07 -0400 Subject: [PATCH 0040/1635] Guide: additional tweak to testing chapter. --- guide/src/qs_8.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md index 9d6327cf..358d72ad 100644 --- a/guide/src/qs_8.md +++ b/guide/src/qs_8.md @@ -5,7 +5,7 @@ integration tests. ## Unit tests -For unit testing, actix provides a request builder type and simple handler runner. +For unit testing, actix provides a request builder type and a simple handler runner. [*TestRequest*](../actix_web/test/struct.TestRequest.html) implements a builder-like pattern. You can generate a `HttpRequest` instance with `finish()`, or you can run your handler with `run()` or `run_async()`. @@ -42,7 +42,7 @@ There are several methods for testing your application. Actix provides [*TestServer*](../actix_web/test/struct.TestServer.html), which can be used to run the application with specific handlers in a real http server. -`TestServer::get()`, `TestServer::post()`, or `TestServer::client()` +`TestServer::get()`, `TestServer::post()`, and `TestServer::client()` methods can be used to send requests to the test server. A simple form `TestServer` can be configured to use a handler. @@ -133,7 +133,7 @@ the websocket server and returns ws reader and writer objects. *TestServer* also provides an `execute()` method, which runs future objects to completion and returns result of the future computation. -Here is a simple example that shows how to test server websocket handler. +The following example shows how to test a server websocket handler: ```rust # extern crate actix; From e4a85a53f46e471c2dee27d056b699111c08b5ef Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 19:35:11 -0400 Subject: [PATCH 0041/1635] Guide: additional tweaks to Middleware. --- guide/src/qs_10.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 0594c04b..d4b4addf 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -1,8 +1,8 @@ # Middleware Actix's middleware system allows us to add additional behavior to request/response processing. -Middleware can hook into an incoming request process, enabling the ability to modify requests -as well as the ability to halt request processing and return response early. +Middleware can hook into an incoming request process, enabling us to modify requests +as well as halt request processing to return a response early. Middleware can also hook into response processing. @@ -19,7 +19,7 @@ registration. In general, a *middleware* is a type that implements the in this trait has a default implementation. Each method can return a result immediately or a *future* object. -The following is an example of a simple middleware that adds request and response headers: +The following demonstrates using middleware to add request and response headers: ```rust # extern crate http; @@ -68,8 +68,8 @@ It is common to register a logging middleware as the first middleware for the ap Logging middleware must be registered for each application. The `Logger` middleware uses the standard log crate to log information. You should enable logger -for *actix_web* package to see access log -([env_logger](https://docs.rs/env_logger/*/env_logger/) or similar). +for *actix_web* package to see access log ([env_logger](https://docs.rs/env_logger/*/env_logger/) +or similar). ### Usage @@ -219,7 +219,7 @@ fn main() { `ErrorHandlers` middleware allows us to provide custom handlers for responses. You can use the `ErrorHandlers::handler()` method to register a custom error handler -for specific status code. You can modify an existing response or create completly new +for a specific status code. You can modify an existing response or create a completly new one. The error handler can return a response immediately or return a future that resolves into a response. From 3a80cb7bf3423d2ba50af016c872ae3c54422254 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 19:37:14 -0400 Subject: [PATCH 0042/1635] Guide: tweak to http/2. --- guide/src/qs_13.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/qs_13.md b/guide/src/qs_13.md index bd25b7b6..963d5598 100644 --- a/guide/src/qs_13.md +++ b/guide/src/qs_13.md @@ -42,5 +42,5 @@ Upgrades to *HTTP/2.0* schema described in 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) -> Check the [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls) +> Check out [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls) > for a concrete example. From 94b41fd484b46605da30792d7f10031bf5e6f1c7 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 19:42:18 -0400 Subject: [PATCH 0043/1635] Guide: tweak to database integration. --- guide/src/qs_14.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md index 0fe16f17..0d1998e4 100644 --- a/guide/src/qs_14.md +++ b/guide/src/qs_14.md @@ -34,7 +34,7 @@ impl Message for CreateUser { } ``` -We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we receive a +We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we will receive a `User` model instance. Next, we must define the handler implementation for this message. ```rust,ignore @@ -98,8 +98,8 @@ fn main() { } ``` -Finally, we use the address in a request handler. We receive the message response -asynchronously, thus the handler returns a future object. +We will use the address in a request handler. The handle returns a future object; +thus, we receive the message response asynchronously. `Route::a()` must be used for async handler registration. From 18b706d4fb83490168cb9d6df422071c0739e189 Mon Sep 17 00:00:00 2001 From: memoryruins Date: Fri, 6 Apr 2018 19:44:52 -0400 Subject: [PATCH 0044/1635] Guide: tweak to websocket and testing. --- guide/src/qs_8.md | 2 +- guide/src/qs_9.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md index 358d72ad..f80fb8eb 100644 --- a/guide/src/qs_8.md +++ b/guide/src/qs_8.md @@ -133,7 +133,7 @@ the websocket server and returns ws reader and writer objects. *TestServer* also provides an `execute()` method, which runs future objects to completion and returns result of the future computation. -The following example shows how to test a server websocket handler: +The following example demonstrates how to test a websocket handler: ```rust # extern crate actix; diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md index b7fdc840..e0d71f12 100644 --- a/guide/src/qs_9.md +++ b/guide/src/qs_9.md @@ -6,7 +6,7 @@ a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream combinators to handle actual messages, but it is simpler to handle websocket communications with an http actor. -The following is example of a simple websocket echo server: +The following is an example of a simple websocket echo server: ```rust # extern crate actix; From 542315ce7f94371ca4755fca0ee3f144ac1c8cfc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 19:34:55 -0700 Subject: [PATCH 0045/1635] simplify StaticFiles --- examples/basics/src/main.rs | 2 +- examples/websocket-chat/src/main.rs | 2 +- examples/websocket/src/main.rs | 2 +- guide/src/qs_12.md | 17 +++++++++++------ src/application.rs | 2 +- src/client/connector.rs | 12 ++++-------- src/fs.rs | 27 ++++++++++++++++----------- 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index cfc9933d..510f02ce 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -129,7 +129,7 @@ fn main() { io::Error::new(io::ErrorKind::Other, "test"), StatusCode::OK) })) // static files - .handler("/static/", fs::StaticFiles::new("../static/", true)) + .handler("/static/", fs::StaticFiles::new("../static/")) // 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 ee5c1c45..5cd3e6e2 100644 --- a/examples/websocket-chat/src/main.rs +++ b/examples/websocket-chat/src/main.rs @@ -199,7 +199,7 @@ fn main() { // websocket .resource("/ws/", |r| r.route().f(chat_route)) // static resources - .handler("/static/", fs::StaticFiles::new("static/", true)) + .handler("/static/", fs::StaticFiles::new("static/")) }) .bind("127.0.0.1:8080").unwrap() .start(); diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 07ad7ff4..11292a9b 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -55,7 +55,7 @@ fn main() { // websocket route .resource("/ws/", |r| r.method(http::Method::GET).f(ws_index)) // static files - .handler("/", fs::StaticFiles::new("../static/", true) + .handler("/", fs::StaticFiles::new("../static/") .index_file("index.html"))) // start http server on 127.0.0.1:8080 .bind("127.0.0.1:8080").unwrap() diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 2feb5a1b..e399c2eb 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -34,16 +34,21 @@ use actix_web::*; fn main() { App::new() - .handler("/static", fs::StaticFiles::new(".", true)) + .handler( + "/static", + fs::StaticFiles::new(".") + .show_folder_listing()) .finish(); } ``` -The first parameter is the base directory. If the second parameter, *show_index*, is set to **true**, -the directory listing will be returned, and if it is set to **false**, -*404 Not Found* will be returned. +The parameter is the base directory. By default files listing for sub-directories +is disabled. Attempt to load directory listing will return *404 Not Found* response. +To enable files listing, use +[*StaticFiles::show_files_listing()*](../actix_web/s/struct.StaticFiles.html#method.show_files_listing) +method. -Instead of showing files listing for directory, it is possible to redirect to a specific -index file. Use the +Instead of showing files listing for directory, it is possible to redirect +to a specific index file. Use the [*StaticFiles::index_file()*](../actix_web/s/struct.StaticFiles.html#method.index_file) method to configure this redirect. diff --git a/src/application.rs b/src/application.rs index 872f413e..9bc51ab7 100644 --- a/src/application.rs +++ b/src/application.rs @@ -391,7 +391,7 @@ impl App where S: 'static { /// let app = App::new() /// .middleware(middleware::Logger::default()) /// .configure(config) // <- register resources - /// .handler("/static", fs::StaticFiles::new(".", true)); + /// .handler("/static", fs::StaticFiles::new(".")); /// } /// ``` pub fn configure(self, cfg: F) -> App diff --git a/src/client/connector.rs b/src/client/connector.rs index 5e364ae9..2b9a5e2f 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -538,8 +538,7 @@ impl ClientConnector { self.install_wait_timeout(wait); let waiter = Waiter{ tx, wait, conn_timeout }; - self.waiters.entry(key.clone()).or_insert_with(VecDeque::new) - .push_back(waiter); + self.waiters.entry(key).or_insert_with(VecDeque::new).push_back(waiter); rx } } @@ -553,10 +552,8 @@ impl Handler for ClientConnector { let mut timeout = Timeout::new(time, Arbiter::handle()).unwrap(); let _ = timeout.poll(); self.paused = Some(Some((when, timeout))); - } else { - if self.paused.is_none() { - self.paused = Some(None); - } + } else if self.paused.is_none() { + self.paused = Some(None); } } } @@ -726,8 +723,7 @@ impl fut::ActorFuture for Maintenance { // check pause duration let done = if let Some(Some(ref pause)) = act.paused { - if pause.0 <= Instant::now() {true} else {false} - } else { false }; + pause.0 <= Instant::now() } else { false }; if done { act.paused.take(); } diff --git a/src/fs.rs b/src/fs.rs index 2d6c0a35..4155aca9 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -372,7 +372,7 @@ impl Responder for Directory { /// /// fn main() { /// let app = App::new() -/// .handler("/static", fs::StaticFiles::new(".", true)) +/// .handler("/static", fs::StaticFiles::new(".")) /// .finish(); /// } /// ``` @@ -388,12 +388,9 @@ pub struct StaticFiles { } impl StaticFiles { - /// Create new `StaticFiles` instance - /// - /// `dir` - base directory - /// - /// `index` - show index for directory - pub fn new>(dir: T, index: bool) -> StaticFiles { + + /// Create new `StaticFiles` instance for specified base directory. + pub fn new>(dir: T) -> StaticFiles { let dir = dir.into(); let (dir, access) = match dir.canonicalize() { @@ -415,7 +412,7 @@ impl StaticFiles { directory: dir, accessible: access, index: None, - show_index: index, + show_index: false, cpu_pool: CpuPool::new(40), default: Box::new(WrapHandler::new( |_| HttpResponse::new(StatusCode::NOT_FOUND))), @@ -424,6 +421,14 @@ impl StaticFiles { } } + /// Show files listing for directories. + /// + /// By default show files listing is disabled. + pub fn show_files_listing(mut self) -> Self { + self.show_index = true; + self + } + /// Set index file /// /// Redirects to specific index file for directory "/" instead of @@ -523,7 +528,7 @@ mod tests { #[test] fn test_static_files() { - let mut st = StaticFiles::new(".", true); + let mut st = StaticFiles::new(".").show_files_listing(); st.accessible = false; let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap(); let resp = resp.as_response().expect("HTTP Response"); @@ -548,7 +553,7 @@ mod tests { #[test] fn test_redirect_to_index() { - let mut st = StaticFiles::new(".", false).index_file("index.html"); + let mut st = StaticFiles::new(".").index_file("index.html"); let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "guide"); @@ -568,7 +573,7 @@ mod tests { #[test] fn test_redirect_to_index_nested() { - let mut st = StaticFiles::new(".", false).index_file("Cargo.toml"); + let mut st = StaticFiles::new(".").index_file("Cargo.toml"); let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "examples/basics"); From a4b837a1c104f5cf5588685404e12232f043bdbc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 19:45:14 -0700 Subject: [PATCH 0046/1635] flaky test --- tests/test_server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 8f3401db..6234a7ea 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -78,7 +78,6 @@ fn test_start() { let (addr, srv_addr) = rx.recv().unwrap(); let mut sys = System::new("test-server"); - { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()).finish().unwrap(); let response = sys.run_until_complete(req.send()).unwrap(); @@ -87,11 +86,12 @@ fn test_start() { // pause let _ = srv_addr.send(server::PauseServer).wait(); - thread::sleep(time::Duration::from_millis(100)); + thread::sleep(time::Duration::from_millis(200)); assert!(net::TcpStream::connect(addr).is_err()); // resume let _ = srv_addr.send(server::ResumeServer).wait(); + thread::sleep(time::Duration::from_millis(200)); { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()).finish().unwrap(); From 7becb95a97099a913a9903c2fe231ca8233144d6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 20:24:49 -0700 Subject: [PATCH 0047/1635] fix guide example --- guide/src/qs_12.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index e399c2eb..1b2a98c4 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -37,7 +37,7 @@ fn main() { .handler( "/static", fs::StaticFiles::new(".") - .show_folder_listing()) + .show_files_listing()) .finish(); } ``` From fffaf2bb2de013c65d6fc282bdc9cb9895855836 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 21:18:42 -0700 Subject: [PATCH 0048/1635] App::route method --- guide/src/qs_5.md | 40 ++++++++++++++++++----- src/application.rs | 79 ++++++++++++++++++++++++++++++++++++++++++---- src/route.rs | 6 ++-- 3 files changed, 108 insertions(+), 17 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 6f66af43..734931e8 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -17,18 +17,42 @@ A resource also has a pattern, meant to match against the *PATH* portion of a *U It does not match against the *QUERY* portion (the portion following the scheme and port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*). -The [App::resource](../actix_web/struct.App.html#method.resource) methods -add a single resource to application routing table. This method accepts a *path pattern* +The [App::route](../actix_web/struct.App.html#method.route) method provides +simple way of registering routes. This method adds a single route to application +routing table. This method accepts a *path pattern*, +*http method* and a handler function. `route()` method could be called multiple times +for the same path, in that case, multiple routes register for the same resource path. + +```rust +# extern crate actix_web; +use actix_web::{App, HttpRequest, HttpResponse, http::Method}; + +fn index(req: HttpRequest) -> HttpResponse { + unimplemented!() +} + +fn main() { + App::new() + .route("/user/{name}", Method::GET, index) + .route("/user/{name}", Method::POST, index) + .finish(); +} +``` + +While *App::route()* provides simple way of registering routes, to access +complete resource configuration, different method has to be used. +The [App::resource](../actix_web/struct.App.html#method.resource) method +adds a single resource to application routing table. This method accepts a *path pattern* and a resource configuration function. ```rust # extern crate actix_web; -# use actix_web::{App, HttpRequest, HttpResponse, http::Method}; -# -# fn index(req: HttpRequest) -> HttpResponse { -# unimplemented!() -# } -# +use actix_web::{App, HttpRequest, HttpResponse, http::Method}; + +fn index(req: HttpRequest) -> HttpResponse { + unimplemented!() +} + fn main() { App::new() .resource("/prefix", |r| r.f(index)) diff --git a/src/application.rs b/src/application.rs index 9bc51ab7..ac38668b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -3,11 +3,12 @@ use std::rc::Rc; use std::cell::UnsafeCell; use std::collections::HashMap; +use http::Method; use handler::Reply; use router::{Router, Resource}; use resource::{ResourceHandler}; use header::ContentEncoding; -use handler::{Handler, RouteHandler, WrapHandler}; +use handler::{Handler, RouteHandler, WrapHandler, FromRequest, Responder}; use httprequest::HttpRequest; use pipeline::{Pipeline, PipelineHandler, HandlerType}; use middleware::Middleware; @@ -225,6 +226,52 @@ impl App where S: 'static { self } + /// Configure route for specific path. + /// + /// This is simplified version of `App::resource()` method. + /// Handler function needs to accept one request extractor argument. + /// This method could be called multiple times, in that case multiple routes + /// would be registered for same resource path. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpRequest, HttpResponse}; + /// + /// fn main() { + /// let app = App::new() + /// .route("/test", http::Method::GET, + /// |_: HttpRequest| HttpResponse::Ok()) + /// .route("/test", http::Method::POST, + /// |_: HttpRequest| HttpResponse::MethodNotAllowed()); + /// } + /// ``` + pub fn route(mut self, path: &str, method: Method, f: F) -> App + where F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, + { + { + let parts: &mut ApplicationParts = unsafe{ + mem::transmute(self.parts.as_mut().expect("Use after finish"))}; + + // get resource handler + for (pattern, handler) in &mut parts.resources { + if let Some(ref mut handler) = handler { + if pattern.pattern() == path { + handler.method(method).with(f); + return self + } + } + } + + let mut handler = ResourceHandler::default(); + handler.method(method).with(f); + let pattern = Resource::new(handler.get_name(), path); + parts.resources.push((pattern, Some(handler))); + } + self + } + /// Configure resource for specific path. /// /// Resource may have variable path also. For instance, a resource with @@ -261,12 +308,12 @@ impl App where S: 'static { { let parts = self.parts.as_mut().expect("Use after finish"); - // add resource - let mut resource = ResourceHandler::default(); - f(&mut resource); + // add resource handler + let mut handler = ResourceHandler::default(); + f(&mut handler); - let pattern = Resource::new(resource.get_name(), path); - parts.resources.push((pattern, Some(resource))); + let pattern = Resource::new(handler.get_name(), path); + parts.resources.push((pattern, Some(handler))); } self } @@ -603,6 +650,26 @@ mod tests { assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); } + #[test] + fn test_route() { + let mut app = App::new() + .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) + .route("/test", Method::POST, |_: HttpRequest| HttpResponse::Created()) + .finish(); + + let req = TestRequest::with_uri("/test").method(Method::GET).finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test").method(Method::POST).finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::CREATED); + + let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + } + #[test] fn test_handler_prefix() { let mut app = App::new() diff --git a/src/route.rs b/src/route.rs index a2c1947e..86614c0e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -102,7 +102,7 @@ impl Route { self.handler = InnerHandler::async(handler); } - /// Set handler function with http request extractor. + /// Set handler function, use request extractor for paramters. /// /// ```rust /// # extern crate bytes; @@ -137,7 +137,7 @@ impl Route { cfg } - /// Set handler function, function has to accept two request extractors. + /// Set handler function, use request extractor for both paramters. /// /// ```rust /// # extern crate bytes; @@ -180,7 +180,7 @@ impl Route { (cfg1, cfg2) } - /// Set handler function, function has to accept three request extractors. + /// Set handler function, use request extractor for all paramters. pub fn with3(&mut self, handler: F) -> (ExtractorConfig, ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2, T3) -> R + 'static, From 7243c58fce355876e475c939955874992106e0eb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Apr 2018 21:57:45 -0700 Subject: [PATCH 0049/1635] stable rust compatibility --- src/application.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/application.rs b/src/application.rs index ac38668b..12787d2b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -255,8 +255,8 @@ impl App where S: 'static { mem::transmute(self.parts.as_mut().expect("Use after finish"))}; // get resource handler - for (pattern, handler) in &mut parts.resources { - if let Some(ref mut handler) = handler { + for &mut (ref pattern, ref mut handler) in &mut parts.resources { + if let Some(ref mut handler) = *handler { if pattern.pattern() == path { handler.method(method).with(f); return self From 1045a6c6f01ee480b359f89d75aee3b84ae195e0 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 7 Apr 2018 17:00:39 +0200 Subject: [PATCH 0050/1635] docs(README): Minor formatting and spelling fixes --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e93552f..ae63f605 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Actix web is a simple, pragmatic, extremely fast, web framework for Rust. +Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols * Streaming and pipelining @@ -11,14 +11,14 @@ Actix web is a simple, pragmatic, extremely fast, web framework for Rust. * Graceful server shutdown * Multipart streams * Static assets -* SSL support with openssl or native-tls +* SSL support with OpenSSL or `native-tls` * Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), [Redis sessions](https://github.com/actix/actix-redis), [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers), [CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html), [CSRF](https://actix.github.io/actix-web/actix_web/middleware/csrf/index.html)) -* Built on top of [Actix actor framework](https://github.com/actix/actix). +* Built on top of [Actix actor framework](https://github.com/actix/actix) ## Documentation @@ -31,7 +31,7 @@ Actix web is a simple, pragmatic, extremely fast, web framework for Rust. ## Example -At the moment all examples uses actix-web master. +At the moment all examples are based on the actix-web `master` branch. ```toml [dependencies] From 38063b98736222a2dfeae194c9d187cf144ac807 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 7 Apr 2018 17:00:57 +0200 Subject: [PATCH 0051/1635] docs(client): Minor formatting and spelling fixes in module docs --- src/client/connector.rs | 46 ++++++++++++++++++++--------------------- src/client/mod.rs | 13 ++++++------ src/client/pipeline.rs | 9 ++++---- src/client/request.rs | 34 ++++++++++++++++-------------- 4 files changed, 52 insertions(+), 50 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 2b9a5e2f..30eccd2f 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -33,8 +33,8 @@ use server::IoStream; #[derive(Debug)] -/// `Connect` type represents message that can be send to `ClientConnector` -/// with connection request. +/// `Connect` type represents a message that can be sent to +/// `ClientConnector` with a connection request. pub struct Connect { pub(crate) uri: Uri, pub(crate) wait_timeout: Duration, @@ -51,16 +51,16 @@ impl Connect { }) } - /// Connection timeout, max time to connect to remote host. - /// By default connect timeout is 1 seccond. + /// Connection timeout, i.e. max time to connect to remote host. + /// Set to 1 second by default. pub fn conn_timeout(mut self, timeout: Duration) -> Self { self.conn_timeout = timeout; self } /// If connection pool limits are enabled, wait time indicates - /// max time to wait for available connection. - /// By default wait timeout is 5 secconds. + /// max time to wait for a connection to become available. + /// Set to 5 seconds by default. pub fn wait_timeout(mut self, timeout: Duration) -> Self { self.wait_timeout = timeout; self @@ -99,11 +99,11 @@ impl Message for Pause { #[derive(Message)] pub struct Resume; -/// A set of errors that can occur during connecting to a http host +/// A set of errors that can occur while connecting to an HTTP host #[derive(Fail, Debug)] pub enum ClientConnectorError { - /// Invalid url - #[fail(display="Invalid url")] + /// Invalid URL + #[fail(display="Invalid URL")] InvalidUrl, /// SSL feature is not enabled @@ -125,14 +125,14 @@ pub enum ClientConnectorError { Connector(#[cause] ConnectorError), /// Connection took too long - #[fail(display = "Timeout out while establishing connection")] + #[fail(display = "Timeout while establishing connection")] Timeout, /// Connector has been disconnected #[fail(display = "Internal error: connector has been disconnected")] Disconnected, - /// Connection io error + /// Connection IO error #[fail(display = "{}", _0)] IoError(#[cause] io::Error), } @@ -152,8 +152,8 @@ struct Waiter { conn_timeout: Duration, } -/// `ClientConnector` type is responsible for transport layer of a client connection -/// of a client connection. +/// `ClientConnector` type is responsible for transport layer of a +/// client connection. pub struct ClientConnector { #[cfg(all(feature="alpn"))] connector: SslConnector, @@ -242,9 +242,9 @@ impl ClientConnector { #[cfg(feature="alpn")] /// Create `ClientConnector` actor with custom `SslConnector` instance. /// - /// By default `ClientConnector` uses very simple ssl configuration. - /// With `with_connector` method it is possible to use custom `SslConnector` - /// object. + /// By default `ClientConnector` uses very a simple SSL configuration. + /// With `with_connector` method it is possible to use a custom + /// `SslConnector` object. /// /// ```rust /// # #![cfg(feature="alpn")] @@ -313,7 +313,7 @@ impl ClientConnector { /// Set total number of simultaneous connections to the same endpoint. /// - /// Endpoints are the same if they are have equal (host, port, ssl) triplet. + /// Endpoints are the same if they have equal (host, port, ssl) triplets. /// If limit is 0, the connector has no limit. The default limit size is 0. pub fn limit_per_host(mut self, limit: usize) -> Self { self.limit_per_host = limit; @@ -322,9 +322,9 @@ impl ClientConnector { /// Set keep-alive period for opened connection. /// - /// Keep-alive period is period between connection usage. - /// if deley between connection usage exceeds this period - /// connection closes. + /// Keep-alive period is the period between connection usage. If + /// the delay between repeated usages of the same connection + /// exceeds this period, the connection is closed. pub fn conn_keep_alive(mut self, dur: Duration) -> Self { self.conn_keep_alive = dur; self @@ -333,7 +333,7 @@ impl ClientConnector { /// Set max lifetime period for connection. /// /// Connection lifetime is max lifetime of any opened connection - /// until it get closed regardless of keep-alive period. + /// until it is closed regardless of keep-alive period. pub fn conn_lifetime(mut self, dur: Duration) -> Self { self.conn_lifetime = dur; self @@ -514,7 +514,7 @@ impl ClientConnector { self.install_wait_timeout(next.unwrap()); } } - + fn install_wait_timeout(&mut self, time: Instant) { if let Some(ref mut wait) = self.wait_timeout { if wait.0 < time { @@ -601,7 +601,7 @@ impl Handler for ClientConnector { if self.pool.task.borrow().is_none() { *self.pool.task.borrow_mut() = Some(current_task()); } - + let host = uri.host().unwrap().to_owned(); let port = uri.port().unwrap_or_else(|| proto.port()); let key = Key {host, port, ssl: proto.is_secure()}; diff --git a/src/client/mod.rs b/src/client/mod.rs index 8b5713a2..89b8bdea 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,4 +1,4 @@ -//! Http client +//! HTTP client mod connector; mod parser; mod request; @@ -22,7 +22,6 @@ use httpresponse::HttpResponse; /// Convert `SendRequestError` to a `HttpResponse` impl ResponseError for SendRequestError { - fn error_response(&self) -> HttpResponse { match *self { SendRequestError::Connector(_) => HttpResponse::BadGateway(), @@ -32,7 +31,7 @@ impl ResponseError for SendRequestError { } } -/// Create request builder for `GET` request +/// Create request builder for `GET` requests /// /// ```rust /// # extern crate actix; @@ -66,28 +65,28 @@ pub fn get>(uri: U) -> ClientRequestBuilder { builder } -/// Create request builder for `HEAD` request +/// Create request builder for `HEAD` requests pub fn head>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::HEAD).uri(uri); builder } -/// Create request builder for `POST` request +/// Create request builder for `POST` requests pub fn post>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::POST).uri(uri); builder } -/// Create request builder for `PUT` request +/// Create request builder for `PUT` requests pub fn put>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::PUT).uri(uri); builder } -/// Create request builder for `DELETE` request +/// Create request builder for `DELETE` requests pub fn delete>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::DELETE).uri(uri); diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 7b91adb2..5581e3b3 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -22,11 +22,11 @@ use super::{Connect, Connection, ClientConnector, ClientConnectorError}; use super::HttpClientWriter; use super::{HttpResponseParser, HttpResponseParserError}; -/// A set of errors that can occur during sending request and reading response +/// A set of errors that can occur during request sending and response reading #[derive(Fail, Debug)] pub enum SendRequestError { /// Response took too long - #[fail(display = "Timeout out while waiting for response")] + #[fail(display = "Timeout while waiting for response")] Timeout, /// Failed to connect to host #[fail(display="Failed to connect to host: {}", _0)] @@ -62,7 +62,8 @@ enum State { None, } -/// `SendRequest` is a `Future` which represents asynchronous sending process. +/// `SendRequest` is a `Future` which represents an asynchronous +/// request sending process. #[must_use = "SendRequest does nothing unless polled"] pub struct SendRequest { req: ClientRequest, @@ -102,7 +103,7 @@ impl SendRequest { /// Set request timeout /// - /// Request timeout is a total time before response should be received. + /// Request timeout is the total time before a response must be received. /// Default value is 5 seconds. pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(Timeout::new(timeout, Arbiter::handle()).unwrap()); diff --git a/src/client/request.rs b/src/client/request.rs index d58a323a..c3954c78 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -146,13 +146,13 @@ impl ClientRequest { source.into() } - /// Get the request uri + /// Get the request URI #[inline] pub fn uri(&self) -> &Uri { &self.uri } - /// Set client request uri + /// Set client request URI #[inline] pub fn set_uri(&mut self, uri: Uri) { self.uri = uri @@ -164,13 +164,13 @@ impl ClientRequest { &self.method } - /// Set http `Method` for the request + /// Set HTTP `Method` for the request #[inline] pub fn set_method(&mut self, method: Method) { self.method = method } - /// Get http version for the request + /// Get HTTP version for the request #[inline] pub fn version(&self) -> Version { self.version @@ -222,8 +222,8 @@ impl ClientRequest { pub fn write_buffer_capacity(&self) -> usize { self.buffer_capacity } - - /// Get body os this response + + /// Get body of this response #[inline] pub fn body(&self) -> &Body { &self.body @@ -234,14 +234,14 @@ impl ClientRequest { self.body = body.into(); } - /// Extract body, replace it with Empty + /// Extract body, replace it with `Empty` pub(crate) fn replace_body(&mut self, body: Body) -> Body { mem::replace(&mut self.body, body) } /// Send request /// - /// This method returns future that resolves to a ClientResponse + /// This method returns a future that resolves to a ClientResponse pub fn send(mut self) -> SendRequest { let timeout = self.timeout.take(); let send = match mem::replace(&mut self.conn, ConnectionType::Default) { @@ -281,7 +281,7 @@ pub struct ClientRequestBuilder { } impl ClientRequestBuilder { - /// Set HTTP uri of request. + /// Set HTTP URI of request. #[inline] pub fn uri>(&mut self, uri: U) -> &mut Self { match Url::parse(uri.as_ref()) { @@ -325,7 +325,7 @@ impl ClientRequestBuilder { /// Set HTTP version of this request. /// - /// By default requests's http version depends on network stream + /// By default requests's HTTP version depends on network stream #[inline] pub fn version(&mut self, version: Version) -> &mut Self { if let Some(parts) = parts(&mut self.request, &self.err) { @@ -364,7 +364,7 @@ impl ClientRequestBuilder { /// Append a header. /// - /// Header get appended to existing header. + /// Header gets appended to existing header. /// To override header use `set_header()` method. /// /// ```rust @@ -540,7 +540,7 @@ impl ClientRequestBuilder { self } - /// Send request using existing Connection + /// Send request using existing `Connection` pub fn with_connection(&mut self, conn: Connection) -> &mut Self { if let Some(parts) = parts(&mut self.request, &self.err) { parts.conn = ConnectionType::Connection(conn); @@ -548,7 +548,8 @@ impl ClientRequestBuilder { self } - /// This method 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: FnOnce(&mut ClientRequestBuilder) { @@ -558,7 +559,8 @@ impl ClientRequestBuilder { self } - /// This method calls provided closure with builder reference if value is Some. + /// This method calls provided closure with builder reference if + /// value is `Some`. pub fn if_some(&mut self, value: Option, f: F) -> &mut Self where F: FnOnce(T, &mut ClientRequestBuilder) { @@ -610,7 +612,7 @@ impl ClientRequestBuilder { Ok(request) } - /// Set a json body and generate `ClientRequest` + /// Set a JSON body and generate `ClientRequest` /// /// `ClientRequestBuilder` can not be used after this call. pub fn json(&mut self, value: T) -> Result { @@ -685,7 +687,7 @@ impl fmt::Debug for ClientRequestBuilder { /// Create `ClientRequestBuilder` from `HttpRequest` /// /// It is useful for proxy requests. This implementation -/// copies all request's headers and method. +/// copies all request headers and the method. impl<'a, S: 'static> From<&'a HttpRequest> for ClientRequestBuilder { fn from(req: &'a HttpRequest) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); From b2a43a3c8d845b3ffa47d24d8f11c90945e33521 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 7 Apr 2018 17:10:36 +0200 Subject: [PATCH 0052/1635] docs(application): Formatting & spelling fixes in module docs --- src/application.rs | 109 +++++++++++++++++++++++++-------------------- src/lib.rs | 9 ++-- 2 files changed, 66 insertions(+), 52 deletions(-) diff --git a/src/application.rs b/src/application.rs index 12787d2b..0a05d868 100644 --- a/src/application.rs +++ b/src/application.rs @@ -140,7 +140,7 @@ pub struct App { impl App<()> { /// Create application with empty state. Application can - /// be configured with builder-like pattern. + /// be configured with a builder-like pattern. pub fn new() -> App<()> { App { parts: Some(ApplicationParts { @@ -166,11 +166,11 @@ impl Default for App<()> { impl App where S: 'static { - /// Create application with specific state. Application can be - /// configured with builder-like pattern. + /// Create application with specified state. Application can be + /// configured with a builder-like pattern. /// - /// State is shared with all resources within same application and could be - /// accessed with `HttpRequest::state()` method. + /// State is shared with all resources within same application and + /// could be accessed with `HttpRequest::state()` method. pub fn with_state(state: S) -> App { App { parts: Some(ApplicationParts { @@ -187,18 +187,24 @@ impl App 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 leading "/" slash. If supplied prefix - /// does not contain leading slash, it get inserted. Prefix should - /// 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. + /// Only requests that match the application's prefix get + /// processed by this application. /// - /// 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* + /// The application prefix always contains a leading slash (`/`). + /// If the supplied prefix does not contain leading slash, it is + /// inserted. + /// + /// Prefix should consist of valid path segments. i.e for an + /// application with the prefix `/app` any request with the paths + /// `/app`, `/app/` or `/app/test` would match, but the path + /// `/application` would not. + /// + /// In the following example only requests with an `/app/` path + /// prefix get handled. Requests with path `/app/test/` would be + /// handled, while requests with the paths `/application` or + /// `/other/...` would return `NOT FOUND`. /// /// ```rust /// # extern crate actix_web; @@ -226,12 +232,14 @@ impl App where S: 'static { self } - /// Configure route for specific path. + /// Configure route for a specific path. /// - /// This is simplified version of `App::resource()` method. - /// Handler function needs to accept one request extractor argument. - /// This method could be called multiple times, in that case multiple routes - /// would be registered for same resource path. + /// This is a simplified version of the `App::resource()` method. + /// Handler functions need to accept one request extractor + /// argument. + /// + /// This method could be called multiple times, in that case + /// multiple routes would be registered for same resource path. /// /// ```rust /// # extern crate actix_web; @@ -272,23 +280,25 @@ impl App where S: 'static { self } - /// Configure resource for specific path. + /// Configure resource for a specific path. /// - /// 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*. + /// Resources may have variable path segments. For example, a + /// resource with the path `/a/{name}/c` would match all incoming + /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/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 `Params` object returned by `HttpRequest.match_info()` method. + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. This is done by + /// looking up the identifier in the `Params` object returned by + /// `HttpRequest.match_info()` method. /// - /// By default, each part matches the regular expression `[^{}/]+`. + /// By default, each segment matches the regular expression `[^{}/]+`. /// /// You can also specify a custom regex in the form `{identifier:regex}`: /// - /// For instance, to route Get requests on any route matching `/users/{userid}/{friend}` and - /// store userid and friend in the exposed Params object: + /// For instance, to route `GET`-requests on any route matching + /// `/users/{userid}/{friend}` and store `userid` and `friend` in + /// the exposed `Params` object: /// /// ```rust /// # extern crate actix_web; @@ -318,7 +328,8 @@ impl App where S: 'static { self } - /// Default resource is used if no matched route could be found. + /// Default resource to be used if no matching route could be + /// found. pub fn default_resource(mut self, f: F) -> App where F: FnOnce(&mut ResourceHandler) -> R + 'static { @@ -339,11 +350,11 @@ impl App where S: 'static { self } - /// Register external resource. + /// Register an 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. + /// External resources are useful for URL generation purposes only + /// and are never considered for matching at request time. Calls to + /// `HttpRequest::url_for()` will work as expected. /// /// ```rust /// # extern crate actix_web; @@ -380,9 +391,10 @@ impl App where S: 'static { /// 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. + /// A path prefix consists of valid path segments, i.e for the + /// prefix `/app` any request with the paths `/app`, `/app/` or + /// `/app/test` would match, but the path `/application` would + /// not. /// /// ```rust /// # extern crate actix_web; @@ -408,18 +420,19 @@ impl App where S: 'static { self } - /// Register a middleware + /// Register a middleware. pub fn middleware>(mut self, mw: M) -> App { self.parts.as_mut().expect("Use after finish") .middlewares.push(Box::new(mw)); self } - /// Run external configuration as part of application building process + /// Run external configuration as part of the application building + /// process /// - /// This function is useful for moving part of configuration to a different - /// module or event library. For example we can move some of the resources - /// configuration to different module. + /// This function is useful for moving parts of configuration to a + /// different module or event library. For example we can move + /// some of the resources' configuration to different module. /// /// ```rust /// # extern crate actix_web; @@ -447,7 +460,7 @@ impl App where S: 'static { cfg(self) } - /// Finish application configuration and create HttpHandler object + /// Finish application configuration and create `HttpHandler` object. pub fn finish(&mut self) -> HttpApplication { let parts = self.parts.take().expect("Use after finish"); let prefix = parts.prefix.trim().trim_right_matches('/'); @@ -478,10 +491,10 @@ impl App where S: 'static { } } - /// Convenience method for creating `Box` instance. + /// Convenience method for creating `Box` instances. /// - /// This method is useful if you need to register multiple application instances - /// with different state. + /// This method is useful if you need to register multiple + /// application instances with different state. /// /// ```rust /// # use std::thread; diff --git a/src/lib.rs b/src/lib.rs index 436c0c0c..b0d1d76d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ -//! Actix web is a small, pragmatic, extremely fast, web framework for Rust. +//! Actix web is a small, pragmatic, and extremely fast web framework +//! for Rust. //! //! ```rust //! use actix_web::{server, App, Path}; @@ -37,9 +38,9 @@ //! * Configurable request routing //! * Graceful server shutdown //! * Multipart streams -//! * SSL support with openssl or native-tls +//! * SSL support with OpenSSL or `native-tls` //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) -//! * Built on top of [Actix actor framework](https://github.com/actix/actix). +//! * Built on top of [Actix actor framework](https://github.com/actix/actix) #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error @@ -189,7 +190,7 @@ pub mod dev { } pub mod http { - //! Various http related types + //! Various HTTP related types // re-exports pub use modhttp::{Method, StatusCode, Version}; From 9fb0498437f605720c4c66e0855aa0b6e56dbd7c Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 7 Apr 2018 17:27:53 +0200 Subject: [PATCH 0053/1635] docs(lib): Add a note about getting started with the API docs Adds some initial pointers for newcomers to the documentation that direct them at some of the most commonly used API types. I based these links on what *I* usually end up looking at when I open the actix_web docs. --- src/lib.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b0d1d76d..14b6ae26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,13 +20,31 @@ //! } //! ``` //! -//! ## Documentation +//! ## Documentation & community resources +//! +//! Besides the API documentation (which you are currently looking +//! at!), several other resources are available: //! //! * [User Guide](http://actix.github.io/actix-web/guide/) //! * [Chat on gitter](https://gitter.im/actix/actix) //! * [GitHub repository](https://github.com/actix/actix-web) //! * [Cargo package](https://crates.io/crates/actix-web) -//! * Supported Rust version: 1.21 or later +//! +//! To get started navigating the API documentation you may want to +//! consider looking at the following pages: +//! +//! * [App](struct.App.html): This struct represents an actix-web +//! application and is used to configure routes and other common +//! settings. +//! +//! * [HttpServer](server/struct.HttpServer.html): This struct +//! represents an HTTP server instance and is used to instantiate and +//! configure servers. +//! +//! * [HttpRequest](struct.HttpRequest.html) and +//! [HttpResponse](struct.HttpResponse.html): These structs +//! represent HTTP requests and responses and expose various methods +//! for inspecting, creating and otherwise utilising them. //! //! ## Features //! @@ -41,6 +59,7 @@ //! * SSL support with OpenSSL or `native-tls` //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) //! * Built on top of [Actix actor framework](https://github.com/actix/actix) +//! * Supported Rust version: 1.21 or later #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error From 37db7d8168edbab537bfe1f7cb78753220357de9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 8 Apr 2018 10:53:58 -0700 Subject: [PATCH 0054/1635] allow to override status code for NamedFile --- src/fs.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 4155aca9..e526ffbc 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -36,6 +36,7 @@ pub struct NamedFile { modified: Option, cpu_pool: Option, only_get: bool, + status_code: StatusCode, } impl NamedFile { @@ -54,7 +55,9 @@ impl NamedFile { let path = path.as_ref().to_path_buf(); let modified = md.modified().ok(); let cpu_pool = None; - Ok(NamedFile{path, file, md, modified, cpu_pool, only_get: false}) + Ok(NamedFile{path, file, md, modified, cpu_pool, + only_get: false, + status_code: StatusCode::OK}) } /// Allow only GET and HEAD methods @@ -96,6 +99,12 @@ impl NamedFile { self } + /// Set response **Status Code** + pub fn set_status_code(mut self, status: StatusCode) -> Self { + self.status_code = status; + self + } + fn etag(&self) -> Option { // This etag format is similar to Apache's. self.modified.as_ref().map(|mtime| { @@ -207,7 +216,7 @@ impl Responder for NamedFile { false }; - let mut resp = HttpResponse::Ok(); + let mut resp = HttpResponse::build(self.status_code); resp .if_some(self.path().extension(), |ext, resp| { @@ -509,6 +518,20 @@ mod tests { assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml") } + #[test] + fn test_named_file_status_code() { + let mut file = NamedFile::open("Cargo.toml").unwrap() + .set_status_code(StatusCode::NOT_FOUND) + .set_cpu_pool(CpuPool::new(1)); + { file.file(); + let _f: &File = &file; } + { let _f: &mut File = &mut file; } + + let resp = file.respond_to(HttpRequest::default()).unwrap(); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml"); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + #[test] fn test_named_file_not_allowed() { let req = TestRequest::default().method(Method::POST).finish(); From ff14633b3d48aa9ff8b32c617c3880caee5a25df Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 8 Apr 2018 11:05:37 -0700 Subject: [PATCH 0055/1635] simplify CookieSessionBackend; expose max_age cookie setting --- src/middleware/mod.rs | 2 +- src/middleware/session.rs | 126 +++++++++++++++----------------------- 2 files changed, 52 insertions(+), 76 deletions(-) diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 8b050392..49d4d706 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -19,7 +19,7 @@ pub use self::defaultheaders::DefaultHeaders; #[cfg(feature = "session")] pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage, - CookieSessionError, CookieSessionBackend, CookieSessionBackendBuilder}; + CookieSessionError, CookieSessionBackend}; /// Middleware start result pub enum Started { diff --git a/src/middleware/session.rs b/src/middleware/session.rs index c0fe8015..c1828e68 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -7,6 +7,7 @@ use serde_json; use serde_json::error::Error as JsonError; use serde::{Serialize, Deserialize}; use http::header::{self, HeaderValue}; +use time::Duration; use cookie::{CookieJar, Cookie, Key}; use futures::Future; use futures::future::{FutureResult, ok as FutOk, err as FutErr}; @@ -263,6 +264,7 @@ struct CookieSessionInner { path: String, domain: Option, secure: bool, + max_age: Option, } impl CookieSessionInner { @@ -273,7 +275,8 @@ impl CookieSessionInner { name: "actix-session".to_owned(), path: "/".to_owned(), domain: None, - secure: true } + secure: true, + max_age: None } } fn set_cookie(&self, resp: &mut HttpResponse, state: &HashMap) -> Result<()> { @@ -292,6 +295,10 @@ impl CookieSessionInner { cookie.set_domain(domain.clone()); } + if let Some(max_age) = self.max_age { + cookie.set_max_age(max_age); + } + let mut jar = CookieJar::new(); jar.signed(&self.key).add(cookie); @@ -333,6 +340,21 @@ impl CookieSessionInner { /// Note that whatever you write into your session is visible by the user (but not modifiable). /// /// Constructor panics if key length is less than 32 bytes. +/// +/// # Example +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::middleware::CookieSessionBackend; +/// +/// # fn main() { +/// let backend: CookieSessionBackend = CookieSessionBackend::new(&[0; 32]) +/// .domain("www.rust-lang.org") +/// .name("actix_session") +/// .path("/") +/// .secure(true); +/// # } +/// ``` pub struct CookieSessionBackend(Rc); impl CookieSessionBackend { @@ -345,19 +367,34 @@ impl CookieSessionBackend { Rc::new(CookieSessionInner::new(key))) } - /// Creates a new `CookieSessionBackendBuilder` instance from the given key. - /// - /// Panics if key length is less than 32 bytes. - /// - /// # Example - /// - /// ``` - /// use actix_web::middleware::CookieSessionBackend; - /// - /// let backend = CookieSessionBackend::build(&[0; 32]).finish(); - /// ``` - pub fn build(key: &[u8]) -> CookieSessionBackendBuilder { - CookieSessionBackendBuilder::new(key) + /// Sets the `path` field in the session cookie being built. + pub fn path>(mut self, value: S) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().path = value.into(); + self + } + + /// Sets the `name` field in the session cookie being built. + pub fn name>(mut self, value: S) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().name = value.into(); + self + } + + /// Sets the `domain` field in the session cookie being built. + pub fn domain>(mut self, value: S) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); + self + } + + /// Sets the `secure` field in the session cookie being built. + pub fn secure(mut self, value: bool) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().secure = value; + self + } + + /// Sets the `max-age` field in the session cookie being built. + pub fn max_age(mut self, value: Duration) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); + self } } @@ -376,64 +413,3 @@ impl SessionBackend for CookieSessionBackend { }) } } - -/// Structure that follows the builder pattern for building `CookieSessionBackend` structs. -/// -/// To construct a backend: -/// -/// 1. Call [`CookieSessionBackend::build`](struct.CookieSessionBackend.html#method.build) to start building. -/// 2. Use any of the builder methods to set fields in the backend. -/// 3. Call [finish](#method.finish) to retrieve the constructed backend. -/// -/// # Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::CookieSessionBackend; -/// -/// # fn main() { -/// let backend: CookieSessionBackend = CookieSessionBackend::build(&[0; 32]) -/// .domain("www.rust-lang.org") -/// .name("actix_session") -/// .path("/") -/// .secure(true) -/// .finish(); -/// # } -/// ``` -pub struct CookieSessionBackendBuilder(CookieSessionInner); - -impl CookieSessionBackendBuilder { - pub fn new(key: &[u8]) -> CookieSessionBackendBuilder { - CookieSessionBackendBuilder( - CookieSessionInner::new(key)) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieSessionBackendBuilder { - self.0.path = value.into(); - 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()); - self - } - - /// Sets the `secure` field in the session cookie being built. - pub fn secure(mut self, value: bool) -> CookieSessionBackendBuilder { - self.0.secure = value; - self - } - - /// Finishes building and returns the built `CookieSessionBackend`. - pub fn finish(self) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(self.0)) - } -} From 48e70139978582e4100e11a8e18fb8b12565737d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 07:57:50 -0700 Subject: [PATCH 0056/1635] update guide examples --- examples/basics/src/main.rs | 4 +--- guide/src/qs_10.md | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index 510f02ce..5b64f112 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -105,9 +105,7 @@ fn main() { .middleware(middleware::Logger::default()) // cookie session middleware .middleware(middleware::SessionStorage::new( - middleware::CookieSessionBackend::build(&[0; 32]) - .secure(false) - .finish() + middleware::CookieSessionBackend::new(&[0; 32]).secure(false) )) // register favicon .resource("/favicon.ico", |r| r.f(favicon)) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index d4b4addf..1b2a7f2f 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -203,9 +203,8 @@ fn main() { server::new( || App::new() .middleware(SessionStorage::new( // <- create session middleware - CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend + CookieSessionBackend::new(&[0; 32]) // <- create cookie session backend .secure(false) - .finish() ))) .bind("127.0.0.1:59880").unwrap() .start(); From b505e682d423edb9f4e68acf354c2336f12c9fad Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 09:31:11 -0700 Subject: [PATCH 0057/1635] fix session doc test --- src/middleware/session.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index c1828e68..7051a034 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -121,9 +121,8 @@ unsafe impl Sync for SessionImplBox {} /// fn main() { /// let app = App::new().middleware( /// SessionStorage::new( // <- create session middleware -/// CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend -/// .secure(false) -/// .finish()) +/// CookieSessionBackend::new(&[0; 32]) // <- create cookie session backend +/// .secure(false)) /// ); /// } /// ``` From eb66685d1ab88b6258ea22970ab5f91dc8cfc5e7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 09:49:07 -0700 Subject: [PATCH 0058/1635] simplify csrf middleware --- src/middleware/csrf.rs | 172 +++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 94 deletions(-) diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index c2003ae3..a80b17cb 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -110,6 +110,28 @@ fn origin(headers: &HeaderMap) -> Option, CsrfError>> { } /// A middleware that filters cross-site requests. +/// +/// To construct a CSRF filter: +/// +/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to +/// start building. +/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed +/// origins. +/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve +/// the constructed filter. +/// +/// # Example +/// +/// ``` +/// use actix_web::App; +/// use actix_web::middleware::csrf; +/// +/// # fn main() { +/// let app = App::new().middleware( +/// csrf::CsrfFilter::new() +/// .allowed_origin("https://www.example.com")); +/// # } +/// ``` pub struct CsrfFilter { origins: HashSet, allow_xhr: bool, @@ -119,17 +141,55 @@ pub struct CsrfFilter { impl CsrfFilter { /// Start building a `CsrfFilter`. - pub fn build() -> CsrfFilterBuilder { - CsrfFilterBuilder { - csrf: CsrfFilter { - origins: HashSet::new(), - allow_xhr: false, - allow_missing_origin: false, - allow_upgrade: false, - } + pub fn new() -> CsrfFilter { + CsrfFilter { + origins: HashSet::new(), + allow_xhr: false, + allow_missing_origin: false, + allow_upgrade: false, } } + /// Add an origin that is allowed to make requests. Will be verified + /// against the `Origin` request header. + pub fn allowed_origin(mut self, origin: &str) -> CsrfFilter { + self.origins.insert(origin.to_owned()); + self + } + + /// Allow all requests with an `X-Requested-With` header. + /// + /// A cross-site attacker should not be able to send requests with custom + /// headers unless a CORS policy whitelists them. Therefore it should be + /// safe to allow requests with an `X-Requested-With` header (added + /// automatically by many JavaScript libraries). + /// + /// This is disabled by default, because in Safari it is possible to + /// circumvent this using redirects and Flash. + /// + /// Use this method to enable more lax filtering. + pub fn allow_xhr(mut self) -> CsrfFilter { + self.allow_xhr = true; + self + } + + /// Allow requests if the expected `Origin` header is missing (and + /// there is no `Referer` to fall back on). + /// + /// The filter is conservative by default, but it should be safe to allow + /// missing `Origin` headers because a cross-site attacker cannot prevent + /// the browser from sending `Origin` on unsafe requests. + pub fn allow_missing_origin(mut self) -> CsrfFilter { + self.allow_missing_origin = true; + self + } + + /// Allow cross-site upgrade requests (for example to open a WebSocket). + pub fn allow_upgrade(mut self) -> CsrfFilter { + self.allow_upgrade = true; + self + } + fn validate(&self, req: &mut HttpRequest) -> Result<(), CsrfError> { let is_upgrade = req.headers().contains_key(header::UPGRADE); let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade); @@ -157,77 +217,6 @@ impl Middleware for CsrfFilter { } } -/// Used to build a `CsrfFilter`. -/// -/// To construct a CSRF filter: -/// -/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to -/// start building. -/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed -/// origins. -/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve -/// the constructed filter. -/// -/// # Example -/// -/// ``` -/// use actix_web::middleware::csrf; -/// -/// let csrf = csrf::CsrfFilter::build() -/// .allowed_origin("https://www.example.com") -/// .finish(); -/// ``` -pub struct CsrfFilterBuilder { - csrf: CsrfFilter, -} - -impl CsrfFilterBuilder { - /// Add an origin that is allowed to make requests. Will be verified - /// against the `Origin` request header. - pub fn allowed_origin(mut self, origin: &str) -> CsrfFilterBuilder { - self.csrf.origins.insert(origin.to_owned()); - self - } - - /// Allow all requests with an `X-Requested-With` header. - /// - /// A cross-site attacker should not be able to send requests with custom - /// headers unless a CORS policy whitelists them. Therefore it should be - /// safe to allow requests with an `X-Requested-With` header (added - /// automatically by many JavaScript libraries). - /// - /// This is disabled by default, because in Safari it is possible to - /// circumvent this using redirects and Flash. - /// - /// Use this method to enable more lax filtering. - pub fn allow_xhr(mut self) -> CsrfFilterBuilder { - self.csrf.allow_xhr = true; - self - } - - /// Allow requests if the expected `Origin` header is missing (and - /// there is no `Referer` to fall back on). - /// - /// The filter is conservative by default, but it should be safe to allow - /// missing `Origin` headers because a cross-site attacker cannot prevent - /// the browser from sending `Origin` on unsafe requests. - pub fn allow_missing_origin(mut self) -> CsrfFilterBuilder { - self.csrf.allow_missing_origin = true; - self - } - - /// Allow cross-site upgrade requests (for example to open a WebSocket). - pub fn allow_upgrade(mut self) -> CsrfFilterBuilder { - self.csrf.allow_upgrade = true; - self - } - - /// Finishes building the `CsrfFilter` instance. - pub fn finish(self) -> CsrfFilter { - self.csrf - } -} - #[cfg(test)] mod tests { use super::*; @@ -236,9 +225,8 @@ mod tests { #[test] fn test_safe() { - let csrf = CsrfFilter::build() - .allowed_origin("https://www.example.com") - .finish(); + let csrf = CsrfFilter::new() + .allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::HEAD) @@ -249,9 +237,8 @@ mod tests { #[test] fn test_csrf() { - let csrf = CsrfFilter::build() - .allowed_origin("https://www.example.com") - .finish(); + let csrf = CsrfFilter::new() + .allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::POST) @@ -262,9 +249,8 @@ mod tests { #[test] fn test_referer() { - let csrf = CsrfFilter::build() - .allowed_origin("https://www.example.com") - .finish(); + let csrf = CsrfFilter::new() + .allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param") .method(Method::POST) @@ -275,14 +261,12 @@ mod tests { #[test] fn test_upgrade() { - let strict_csrf = CsrfFilter::build() - .allowed_origin("https://www.example.com") - .finish(); + let strict_csrf = CsrfFilter::new() + .allowed_origin("https://www.example.com"); - let lax_csrf = CsrfFilter::build() + let lax_csrf = CsrfFilter::new() .allowed_origin("https://www.example.com") - .allow_upgrade() - .finish(); + .allow_upgrade(); let mut req = TestRequest::with_header("Origin", "https://cswsh.com") .header("Connection", "Upgrade") From 9b152acc327d256c746eedf9498110ec7934878f Mon Sep 17 00:00:00 2001 From: Alex Whitney Date: Mon, 9 Apr 2018 16:22:25 +0100 Subject: [PATCH 0059/1635] add signed and private cookies --- CHANGES.md | 2 ++ examples/basics/src/main.rs | 2 +- guide/src/qs_10.md | 13 ++++--- src/middleware/session.rs | 71 +++++++++++++++++++++++++++++-------- 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 03f5b5e9..fb4943be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,8 @@ * Fix logger request duration calculation #152 +* Add `signed` and `private` `CookieSessionBackend`s + ## 0.4.10 (2018-03-20) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index 5b64f112..c79cb4ad 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -105,7 +105,7 @@ fn main() { .middleware(middleware::Logger::default()) // cookie session middleware .middleware(middleware::SessionStorage::new( - middleware::CookieSessionBackend::new(&[0; 32]).secure(false) + middleware::CookieSessionBackend::signed(&[0; 32]).secure(false) )) // register favicon .resource("/favicon.ico", |r| r.f(favicon)) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 1b2a7f2f..3cbd0938 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -163,14 +163,17 @@ used with different backend types to store session data in different backends. > can be added. [**CookieSessionBackend**](../actix_web/middleware/struct.CookieSessionBackend.html) -uses signed cookies as session storage. `CookieSessionBackend` creates sessions which +uses cookies as session storage. `CookieSessionBackend` creates sessions which are limited to storing fewer than 4000 bytes of data, as the payload must fit into a single cookie. An internal server error is generated if a session contains more than 4000 bytes. -You need to pass a random value to the constructor of `CookieSessionBackend`. -This is a private key for cookie session. When this value is changed, all session data is lost. +A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor. + +A *signed* cookie may be viewed but not modified by the client. A *private* cookie may neither be viewed nor modified by the client. + +The constructors take a key as an argument. This is the private key for cookie session - when this value is changed, all session data is lost. + -> **Note**: anything you write into the session is visible by the user, but it is not modifiable. In general, you create a `SessionStorage` middleware and initialize it with specific backend implementation, @@ -203,7 +206,7 @@ fn main() { server::new( || App::new() .middleware(SessionStorage::new( // <- create session middleware - CookieSessionBackend::new(&[0; 32]) // <- create cookie session backend + CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend .secure(false) ))) .bind("127.0.0.1:59880").unwrap() diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 7051a034..6ad61d62 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -121,7 +121,7 @@ unsafe impl Sync for SessionImplBox {} /// fn main() { /// let app = App::new().middleware( /// SessionStorage::new( // <- create session middleware -/// CookieSessionBackend::new(&[0; 32]) // <- create cookie session backend +/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend /// .secure(false)) /// ); /// } @@ -257,8 +257,14 @@ impl SessionImpl for CookieSession { } } +enum CookieSecurity { + Signed, + Private +} + struct CookieSessionInner { key: Key, + security: CookieSecurity, name: String, path: String, domain: Option, @@ -268,14 +274,16 @@ struct CookieSessionInner { impl CookieSessionInner { - fn new(key: &[u8]) -> CookieSessionInner { + fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { CookieSessionInner { key: Key::from_master(key), + security: security, name: "actix-session".to_owned(), path: "/".to_owned(), domain: None, secure: true, - max_age: None } + max_age: None, + } } fn set_cookie(&self, resp: &mut HttpResponse, state: &HashMap) -> Result<()> { @@ -299,7 +307,11 @@ impl CookieSessionInner { } let mut jar = CookieJar::new(); - jar.signed(&self.key).add(cookie); + + match self.security { + CookieSecurity::Signed => jar.signed(&self.key).add(cookie), + CookieSecurity::Private => jar.private(&self.key).add(cookie), + } for cookie in jar.delta() { let val = HeaderValue::from_str(&cookie.to_string())?; @@ -315,7 +327,12 @@ impl CookieSessionInner { if cookie.name() == self.name { let mut jar = CookieJar::new(); jar.add_original(cookie.clone()); - if let Some(cookie) = jar.signed(&self.key).get(&self.name) { + + let cookie_opt = match self.security { + CookieSecurity::Signed => jar.signed(&self.key).get(&self.name), + CookieSecurity::Private => jar.private(&self.key).get(&self.name), + }; + if let Some(cookie) = cookie_opt { if let Ok(val) = serde_json::from_str(cookie.value()) { return val; } @@ -327,18 +344,24 @@ impl CookieSessionInner { } } -/// Use signed cookies as session storage. +/// Use cookies for session storage. /// /// `CookieSessionBackend` 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. +/// An Internal Server Error is generated if the 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. +/// A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor. /// -/// Note that whatever you write into your session is visible by the user (but not modifiable). +/// A *signed* cookie is stored on the client as plaintext alongside +/// a signature such that the cookie may be viewed but not modified by the client. +/// +/// A *private* cookie is stored on the client as encrypted text +/// such that it may neither be viewed nor modified by the client. +/// +/// The constructors take a key as an argument. +/// This is the private key for cookie session - when this value is changed, all session data is lost. +/// The constructors will panic if the key is less than 32 bytes in length. /// -/// Constructor panics if key length is less than 32 bytes. /// /// # Example /// @@ -347,7 +370,7 @@ impl CookieSessionInner { /// use actix_web::middleware::CookieSessionBackend; /// /// # fn main() { -/// let backend: CookieSessionBackend = CookieSessionBackend::new(&[0; 32]) +/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32]) /// .domain("www.rust-lang.org") /// .name("actix_session") /// .path("/") @@ -358,12 +381,29 @@ pub struct CookieSessionBackend(Rc); impl CookieSessionBackend { - /// Construct new `CookieSessionBackend` instance. + /// Construct new signed `CookieSessionBackend` instance. /// /// Panics if key length is less than 32 bytes. + #[deprecated(since="0.5.0", note="use `signed` or `private` instead")] pub fn new(key: &[u8]) -> CookieSessionBackend { CookieSessionBackend( - Rc::new(CookieSessionInner::new(key))) + Rc::new(CookieSessionInner::new(key, CookieSecurity::Signed))) + } + + /// Construct new *signed* `CookieSessionBackend` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn signed(key: &[u8]) -> CookieSessionBackend { + CookieSessionBackend( + Rc::new(CookieSessionInner::new(key, CookieSecurity::Signed))) + } + + /// Construct new *private* `CookieSessionBackend` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn private(key: &[u8]) -> CookieSessionBackend { + CookieSessionBackend( + Rc::new(CookieSessionInner::new(key, CookieSecurity::Private))) } /// Sets the `path` field in the session cookie being built. @@ -385,6 +425,9 @@ impl CookieSessionBackend { } /// Sets the `secure` field in the session cookie being built. + /// + /// If the `secure` field is set, a cookie will only be transmitted when the + /// connection is secure - i.e. `https` pub fn secure(mut self, value: bool) -> CookieSessionBackend { Rc::get_mut(&mut self.0).unwrap().secure = value; self From 2b803f30c933d65b42c6afe2152a625bdf7c552f Mon Sep 17 00:00:00 2001 From: Alex Whitney Date: Mon, 9 Apr 2018 18:33:29 +0100 Subject: [PATCH 0060/1635] remove CookieSessionBackend::new --- src/middleware/session.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 6ad61d62..7964b855 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -381,15 +381,6 @@ pub struct CookieSessionBackend(Rc); impl CookieSessionBackend { - /// Construct new signed `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - #[deprecated(since="0.5.0", note="use `signed` or `private` instead")] - pub fn new(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend( - Rc::new(CookieSessionInner::new(key, CookieSecurity::Signed))) - } - /// Construct new *signed* `CookieSessionBackend` instance. /// /// Panics if key length is less than 32 bytes. From 561789678089b5ad05a66f4b8a38a0a640b97189 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 10:40:12 -0700 Subject: [PATCH 0061/1635] cleanup doc tests --- examples/diesel/src/db.rs | 2 +- src/middleware/csrf.rs | 7 +++---- src/pred.rs | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/diesel/src/db.rs b/examples/diesel/src/db.rs index 13b37682..78806c27 100644 --- a/examples/diesel/src/db.rs +++ b/examples/diesel/src/db.rs @@ -4,7 +4,7 @@ use diesel; use actix_web::*; use actix::prelude::*; use diesel::prelude::*; -use diesel::r2d2::{ Pool, ConnectionManager }; +use diesel::r2d2::{Pool, ConnectionManager}; use models; use schema; diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index a80b17cb..ba99b1f5 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -14,7 +14,7 @@ //! * There is no `Origin` header but the `Referer` header matches one of //! the allowed origins. //! -//! Use [`CsrfFilterBuilder::allow_xhr()`](struct.CsrfFilterBuilder.html#method.allow_xhr) +//! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr) //! if you want to allow requests with unsafe methods via //! [CORS](../cors/struct.Cors.html). //! @@ -32,9 +32,8 @@ //! fn main() { //! let app = App::new() //! .middleware( -//! csrf::CsrfFilter::build() -//! .allowed_origin("https://www.example.com") -//! .finish()) +//! csrf::CsrfFilter::new() +//! .allowed_origin("https://www.example.com")) //! .resource("/", |r| { //! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); //! r.method(http::Method::POST).f(handle_post); diff --git a/src/pred.rs b/src/pred.rs index 57398fc2..7bc8e187 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -61,10 +61,10 @@ impl Predicate for AnyPredicate { /// /// ```rust /// # extern crate actix_web; -/// use actix_web::{pred, Application, HttpResponse}; +/// use actix_web::{pred, App, HttpResponse}; /// /// fn main() { -/// Application::new() +/// App::new() /// .resource("/index.html", |r| r.route() /// .filter(pred::All(pred::Get()) /// .and(pred::Header("content-type", "plain/text"))) From 7df2d6b12a00e8e806145644186f2868e1bde9e3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 13:30:38 -0700 Subject: [PATCH 0062/1635] clippy warnings; extend url_for example in user guide --- guide/src/qs_5.md | 63 +++++++++++++++++++++------------------ src/middleware/csrf.rs | 1 + src/middleware/session.rs | 2 +- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index 734931e8..96f8b39b 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -350,10 +350,33 @@ List of `FromParam` implementations can be found in ## Path information extractor -Actix provides functionality for type safe request path information extraction. -It uses *serde* package as a deserialization library. -[Path](../actix_web/struct.Path.html) extracts information, the destination type -has to implement *serde's *`Deserialize` trait. +Actix provides functionality for type safe path information extraction. +[Path](../actix_web/struct.Path.html) extracts information, destination type +could be defined in several different forms. Simplest approach is to use +`tuple` type. Each element in tuple must correpond to one element from +path pattern. i.e. you can match path pattern `/{id}/{username}/` against +`Pyth<(u32, String)>` type, but `Path<(String, String, String)>` type will +always fail. + +```rust +# extern crate actix_web; +use actix_web::{App, Path, Result, http::Method}; + +// extract path info using serde +fn index(info: Path<(String, u32)>) -> Result { + Ok(format!("Welcome {}! id: {}", info.0, info.1)) +} + +fn main() { + let app = App::new() + .resource("/{username}/{id}/index.html", // <- define path parameters + |r| r.method(Method::GET).with(index)); +} +``` + + +It also possible to extract path pattern information to a struct. In this case, +this struct must implement *serde's *`Deserialize` trait. ```rust # extern crate actix_web; @@ -377,27 +400,6 @@ fn main() { } ``` -It also possible to extract path information to a tuple. In this case, you don't need -to define an extra type; use a tuple as a `Path` generic type. - -Here is previous example re-written using tuple instead of specific type. - -```rust -# extern crate actix_web; -use actix_web::{App, Path, Result, http::Method}; - -// extract path info using serde -fn index(info: Path<(String, u32)>) -> Result { - Ok(format!("Welcome {}! id: {}", info.0, info.1)) -} - -fn main() { - let app = App::new() - .resource("/{username}/{id}/index.html", // <- define path parameters - |r| r.method(Method::GET).with(index)); -} -``` - [Query](../actix_web/struct.Query.html) provides similar functionality for request query parameters. @@ -410,11 +412,13 @@ resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this: ```rust # extern crate actix_web; -# use actix_web::{App, HttpRequest, HttpResponse, http::Method}; +# use actix_web::{App, Result, HttpRequest, HttpResponse, http::Method, http::header}; # -fn index(req: HttpRequest) -> HttpResponse { - let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource - HttpResponse::Ok().into() +fn index(req: HttpRequest) -> Result { + let url = req.url_for("foo", &["1", "2", "3"])?; // <- generate url for "foo" resource + Ok(HttpResponse::Found() + .header(header::LOCATION, url.as_str()) + .finish()) } fn main() { @@ -423,6 +427,7 @@ fn main() { r.name("foo"); // <- set resource name, then it could be used in `url_for` r.method(Method::GET).f(|_| HttpResponse::Ok()); }) + .route("/test/", Method::GET, index) .finish(); } ``` diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index ba99b1f5..2e600f3d 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -131,6 +131,7 @@ fn origin(headers: &HeaderMap) -> Option, CsrfError>> { /// .allowed_origin("https://www.example.com")); /// # } /// ``` +#[derive(Default)] pub struct CsrfFilter { origins: HashSet, allow_xhr: bool, diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 7964b855..70b0aff6 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -276,8 +276,8 @@ impl CookieSessionInner { fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { CookieSessionInner { + security, key: Key::from_master(key), - security: security, name: "actix-session".to_owned(), path: "/".to_owned(), domain: None, From be358db422055a7245495e887540735f38414196 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 14:20:12 -0700 Subject: [PATCH 0063/1635] CorsBuilder::finish() panics on any configuration error --- src/middleware/cors.rs | 110 +++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 28c5c789..65f39d7b 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -34,7 +34,7 @@ //! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) //! .allowed_header(http::header::CONTENT_TYPE) //! .max_age(3600) -//! .finish().expect("Can not create CORS middleware") +//! .finish() //! .register(r); // <- Register CORS middleware //! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); //! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); @@ -47,6 +47,7 @@ //! Cors middleware automatically handle *OPTIONS* preflight request. use std::collections::HashSet; use std::iter::FromIterator; +use std::rc::Rc; use http::{self, Method, HttpTryFrom, Uri, StatusCode}; use http::header::{self, HeaderName, HeaderValue}; @@ -91,19 +92,6 @@ pub enum CorsError { HeadersNotAllowed, } -/// A set of errors that can occur during building CORS middleware -#[derive(Debug, Fail)] -pub enum CorsBuilderError { - #[fail(display="Parse error: {}", _0)] - ParseError(http::Error), - /// Credentials are allowed, but the Origin is set to "*". This is not allowed by W3C - /// - /// This is a misconfiguration. Check the documentation for `Cors`. - #[fail(display="Credentials are allowed, but the Origin is set to \"*\"")] - CredentialsWithWildcardOrigin, -} - - impl ResponseError for CorsError { fn error_response(&self) -> HttpResponse { @@ -155,7 +143,12 @@ impl AllOrSome { /// /// The Cors struct contains the settings for CORS requests to be validated and /// for responses to be generated. +#[derive(Clone)] pub struct Cors { + inner: Rc, +} + +struct Inner { methods: HashSet, origins: AllOrSome>, origins_str: Option, @@ -170,7 +163,7 @@ pub struct Cors { impl Default for Cors { fn default() -> Cors { - Cors { + let inner = Inner { origins: AllOrSome::default(), origins_str: None, methods: HashSet::from_iter( @@ -184,14 +177,15 @@ impl Default for Cors { send_wildcard: false, supports_credentials: false, vary_header: true, - } + }; + Cors{inner: Rc::new(inner)} } } impl Cors { pub fn build() -> CorsBuilder { CorsBuilder { - cors: Some(Cors { + cors: Some(Inner { origins: AllOrSome::All, origins_str: None, methods: HashSet::new(), @@ -223,7 +217,7 @@ impl Cors { fn validate_origin(&self, req: &mut HttpRequest) -> Result<(), CorsError> { if let Some(hdr) = req.headers().get(header::ORIGIN) { if let Ok(origin) = hdr.to_str() { - return match self.origins { + return match self.inner.origins { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_origins) => { allowed_origins @@ -235,7 +229,7 @@ impl Cors { } Err(CorsError::BadOrigin) } else { - return match self.origins { + return match self.inner.origins { AllOrSome::All => Ok(()), _ => Err(CorsError::MissingOrigin) } @@ -246,7 +240,7 @@ impl Cors { if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { - return self.methods.get(&method) + return self.inner.methods.get(&method) .and_then(|_| Some(())) .ok_or_else(|| CorsError::MethodNotAllowed); } @@ -258,7 +252,7 @@ impl Cors { } fn validate_allowed_headers(&self, req: &mut HttpRequest) -> Result<(), CorsError> { - match self.headers { + match self.inner.headers { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { @@ -288,13 +282,13 @@ impl Cors { impl Middleware for Cors { fn start(&self, req: &mut HttpRequest) -> Result { - if self.preflight && Method::OPTIONS == *req.method() { + if self.inner.preflight && Method::OPTIONS == *req.method() { self.validate_origin(req)?; self.validate_allowed_method(req)?; self.validate_allowed_headers(req)?; // allowed headers - let headers = if let Some(headers) = self.headers.as_ref() { + let headers = if let Some(headers) = self.inner.headers.as_ref() { Some(HeaderValue::try_from(&headers.iter().fold( String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]).unwrap()) } else if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { @@ -305,13 +299,13 @@ impl Middleware for Cors { Ok(Started::Response( HttpResponse::Ok() - .if_some(self.max_age.as_ref(), |max_age, resp| { + .if_some(self.inner.max_age.as_ref(), |max_age, resp| { let _ = resp.header( header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());}) .if_some(headers, |headers, resp| { let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); }) - .if_true(self.origins.is_all(), |resp| { - if self.send_wildcard { + .if_true(self.inner.origins.is_all(), |resp| { + if self.inner.send_wildcard { resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); } else { let origin = req.headers().get(header::ORIGIN).unwrap(); @@ -319,17 +313,17 @@ impl Middleware for Cors { header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); } }) - .if_true(self.origins.is_some(), |resp| { + .if_true(self.inner.origins.is_some(), |resp| { resp.header( header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.origins_str.as_ref().unwrap().clone()); + self.inner.origins_str.as_ref().unwrap().clone()); }) - .if_true(self.supports_credentials, |resp| { + .if_true(self.inner.supports_credentials, |resp| { resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); }) .header( header::ACCESS_CONTROL_ALLOW_METHODS, - &self.methods.iter().fold( + &self.inner.methods.iter().fold( String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]) .finish())) } else { @@ -340,9 +334,9 @@ impl Middleware for Cors { } fn response(&self, req: &mut HttpRequest, mut resp: HttpResponse) -> Result { - match self.origins { + match self.inner.origins { AllOrSome::All => { - if self.send_wildcard { + if self.inner.send_wildcard { resp.headers_mut().insert( header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); } else if let Some(origin) = req.headers().get(header::ORIGIN) { @@ -353,20 +347,20 @@ impl Middleware for Cors { AllOrSome::Some(_) => { resp.headers_mut().insert( header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.origins_str.as_ref().unwrap().clone()); + self.inner.origins_str.as_ref().unwrap().clone()); } } - if let Some(ref expose) = self.expose_hdrs { + if let Some(ref expose) = self.inner.expose_hdrs { resp.headers_mut().insert( header::ACCESS_CONTROL_EXPOSE_HEADERS, HeaderValue::try_from(expose.as_str()).unwrap()); } - if self.supports_credentials { + if self.inner.supports_credentials { resp.headers_mut().insert( header::ACCESS_CONTROL_ALLOW_CREDENTIALS, HeaderValue::from_static("true")); } - if self.vary_header { + if self.inner.vary_header { let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) { let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); val.extend(hdr.as_bytes()); @@ -404,17 +398,19 @@ impl Middleware for Cors { /// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) /// .allowed_header(header::CONTENT_TYPE) /// .max_age(3600) -/// .finish().unwrap(); +/// .finish(); /// # } /// ``` pub struct CorsBuilder { - cors: Option, + cors: Option, methods: bool, error: Option, expose_hdrs: HashSet, } -fn cors<'a>(parts: &'a mut Option, err: &Option) -> Option<&'a mut Cors> { +fn cors<'a>(parts: &'a mut Option, err: &Option) + -> Option<&'a mut Inner> +{ if err.is_some() { return None } @@ -437,6 +433,8 @@ impl CorsBuilder { /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// Defaults to `All`. + /// + /// Builder panics if supplied origin is not valid uri. pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { if let Some(cors) = cors(&mut self.cors, &self.error) { match Uri::try_from(origin) { @@ -602,6 +600,9 @@ impl CorsBuilder { /// and `send_wildcards` set to `true`. /// /// Defaults to `false`. + /// + /// Builder panics if credentials are allowed, but the Origin is set to "*". + /// This is not allowed by W3C pub fn supports_credentials(&mut self) -> &mut CorsBuilder { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.supports_credentials = true @@ -641,7 +642,9 @@ impl CorsBuilder { } /// Finishes building and returns the built `Cors` instance. - pub fn finish(&mut self) -> Result { + /// + /// This method panics in case of any configuration error. + pub fn finish(&mut self) -> Cors { if !self.methods { self.allowed_methods(vec![Method::GET, Method::HEAD, Method::POST, Method::OPTIONS, Method::PUT, @@ -649,13 +652,13 @@ impl CorsBuilder { } if let Some(e) = self.error.take() { - return Err(CorsBuilderError::ParseError(e)) + panic!("{}", e); } let mut cors = self.cors.take().expect("cannot reuse CorsBuilder"); if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() { - return Err(CorsBuilderError::CredentialsWithWildcardOrigin) + panic!("Credentials are allowed, but the Origin is set to \"*\""); } if let AllOrSome::Some(ref origins) = cors.origins { @@ -668,7 +671,7 @@ impl CorsBuilder { self.expose_hdrs.iter().fold( String::new(), |s, v| s + v.as_str())[1..].to_owned()); } - Ok(cors) + Cors{inner: Rc::new(cors)} } } @@ -702,13 +705,12 @@ mod tests { } #[test] - #[should_panic(expected = "CredentialsWithWildcardOrigin")] + #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] fn cors_validates_illegal_allow_credentials() { Cors::build() .supports_credentials() .send_wildcard() - .finish() - .unwrap(); + .finish(); } #[test] @@ -728,7 +730,7 @@ mod tests { .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_header(header::CONTENT_TYPE) - .finish().unwrap(); + .finish(); let mut req = TestRequest::with_header( "Origin", "https://www.example.com") @@ -764,7 +766,7 @@ mod tests { // &b"POST,GET,OPTIONS"[..], // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap().as_bytes()); - cors.preflight = false; + Rc::get_mut(&mut cors.inner).unwrap().preflight = false; assert!(cors.start(&mut req).unwrap().is_done()); } @@ -772,7 +774,7 @@ mod tests { #[should_panic(expected = "MissingOrigin")] fn test_validate_missing_origin() { let cors = Cors::build() - .allowed_origin("https://www.example.com").finish().unwrap(); + .allowed_origin("https://www.example.com").finish(); let mut req = HttpRequest::default(); cors.start(&mut req).unwrap(); @@ -782,7 +784,7 @@ mod tests { #[should_panic(expected = "OriginNotAllowed")] fn test_validate_not_allowed_origin() { let cors = Cors::build() - .allowed_origin("https://www.example.com").finish().unwrap(); + .allowed_origin("https://www.example.com").finish(); let mut req = TestRequest::with_header("Origin", "https://www.unknown.com") .method(Method::GET) @@ -793,7 +795,7 @@ mod tests { #[test] fn test_validate_origin() { let cors = Cors::build() - .allowed_origin("https://www.example.com").finish().unwrap(); + .allowed_origin("https://www.example.com").finish(); let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::GET) @@ -804,7 +806,7 @@ mod tests { #[test] fn test_no_origin_response() { - let cors = Cors::build().finish().unwrap(); + let cors = Cors::build().finish(); let mut req = TestRequest::default().method(Method::GET).finish(); let resp: HttpResponse = HttpResponse::Ok().into(); @@ -830,7 +832,7 @@ mod tests { .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_header(header::CONTENT_TYPE) - .finish().unwrap(); + .finish(); let mut req = TestRequest::with_header( "Origin", "https://www.example.com") @@ -857,7 +859,7 @@ mod tests { let cors = Cors::build() .disable_vary_header() .allowed_origin("https://www.example.com") - .finish().unwrap(); + .finish(); let resp: HttpResponse = HttpResponse::Ok().into(); let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( From e757dc5a717c2097cee208c5cde53723e3b8de8f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 14:25:30 -0700 Subject: [PATCH 0064/1635] clippy warnings --- src/client/request.rs | 14 +++++++------- src/client/response.rs | 8 ++++---- src/client/writer.rs | 8 ++++---- src/httprequest.rs | 10 +++++----- src/httpresponse.rs | 8 ++++---- src/multipart.rs | 8 ++++---- src/server/encoding.rs | 2 +- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index c3954c78..79bbd249 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -259,11 +259,11 @@ impl ClientRequest { impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = write!(f, "\nClientRequest {:?} {}:{}\n", + let res = writeln!(f, "\nClientRequest {:?} {}:{}", self.version, self.method, self.uri); - let _ = write!(f, " headers:\n"); + let _ = writeln!(f, " headers:"); for (key, val) in self.headers.iter() { - let _ = write!(f, " {:?}: {:?}\n", key, val); + let _ = writeln!(f, " {:?}: {:?}", key, val); } res } @@ -671,11 +671,11 @@ fn parts<'a>(parts: &'a mut Option, err: &Option) impl fmt::Debug for ClientRequestBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(ref parts) = self.request { - let res = write!(f, "\nClientRequestBuilder {:?} {}:{}\n", - parts.version, parts.method, parts.uri); - let _ = write!(f, " headers:\n"); + let res = writeln!(f, "\nClientRequestBuilder {:?} {}:{}", + parts.version, parts.method, parts.uri); + let _ = writeln!(f, " headers:"); for (key, val) in parts.headers.iter() { - let _ = write!(f, " {:?}: {:?}\n", key, val); + let _ = writeln!(f, " {:?}: {:?}", key, val); } res } else { diff --git a/src/client/response.rs b/src/client/response.rs index 1a82d64b..a0ecb8a6 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -106,11 +106,11 @@ impl ClientResponse { impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = write!( - f, "\nClientResponse {:?} {}\n", self.version(), self.status()); - let _ = write!(f, " headers:\n"); + let res = writeln!( + f, "\nClientResponse {:?} {}", self.version(), self.status()); + let _ = writeln!(f, " headers:"); for (key, val) in self.headers().iter() { - let _ = write!(f, " {:?}: {:?}\n", key, val); + let _ = writeln!(f, " {:?}: {:?}", key, val); } res } diff --git a/src/client/writer.rs b/src/client/writer.rs index cd50359c..d1c4bb22 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -105,10 +105,10 @@ impl HttpClientWriter { // render message { // status line - write!(self.buffer, "{} {} {:?}\r\n", - msg.method(), - msg.uri().path_and_query().map(|u| u.as_str()).unwrap_or("/"), - msg.version())?; + writeln!(self.buffer, "{} {} {:?}\r", + msg.method(), + msg.uri().path_and_query().map(|u| u.as_str()).unwrap_or("/"), + msg.version())?; // write headers let mut buffer = self.buffer.get_mut(); diff --git a/src/httprequest.rs b/src/httprequest.rs index 8077ab23..9d8c39b4 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -555,17 +555,17 @@ impl AsyncRead for HttpRequest {} impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = write!(f, "\nHttpRequest {:?} {}:{}\n", + let res = writeln!(f, "\nHttpRequest {:?} {}:{}", self.as_ref().version, self.as_ref().method, self.path_decoded()); if !self.query_string().is_empty() { - let _ = write!(f, " query: ?{:?}\n", self.query_string()); + let _ = writeln!(f, " query: ?{:?}", self.query_string()); } if !self.match_info().is_empty() { - let _ = write!(f, " params: {:?}\n", self.as_ref().params); + let _ = writeln!(f, " params: {:?}", self.as_ref().params); } - let _ = write!(f, " headers:\n"); + let _ = writeln!(f, " headers:"); for (key, val) in self.as_ref().headers.iter() { - let _ = write!(f, " {:?}: {:?}\n", key, val); + let _ = writeln!(f, " {:?}: {:?}", key, val); } res } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 5edb6de5..90243e4d 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -241,13 +241,13 @@ impl HttpResponse { impl fmt::Debug for HttpResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = write!(f, "\nHttpResponse {:?} {}{}\n", + let res = writeln!(f, "\nHttpResponse {:?} {}{}", 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"); + let _ = writeln!(f, " encoding: {:?}", self.get_ref().encoding); + let _ = writeln!(f, " headers:"); for (key, val) in self.get_ref().headers.iter() { - let _ = write!(f, " {:?}: {:?}\n", key, val); + let _ = writeln!(f, " {:?}: {:?}", key, val); } res } diff --git a/src/multipart.rs b/src/multipart.rs index 4ac7b2a1..a8d0c6e7 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -396,11 +396,11 @@ impl Stream for Field where S: Stream { impl fmt::Debug for Field { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = write!(f, "\nMultipartField: {}\n", self.ct); - let _ = write!(f, " boundary: {}\n", self.inner.borrow().boundary); - let _ = write!(f, " headers:\n"); + let res = writeln!(f, "\nMultipartField: {}", self.ct); + let _ = writeln!(f, " boundary: {}", self.inner.borrow().boundary); + let _ = writeln!(f, " headers:"); for (key, val) in self.headers.iter() { - let _ = write!(f, " {:?}: {:?}\n", key, val); + let _ = writeln!(f, " {:?}: {:?}", key, val); } res } diff --git a/src/server/encoding.rs b/src/server/encoding.rs index fc624d55..fd2ca432 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -718,7 +718,7 @@ impl TransferEncoding { self.buffer.extend_from_slice(b"0\r\n\r\n"); } else { let mut buf = BytesMut::new(); - write!(&mut buf, "{:X}\r\n", msg.len()) + writeln!(&mut buf, "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; self.buffer.reserve(buf.len() + msg.len() + 2); self.buffer.extend(buf.into()); From d04ff13955e88626c0e81afd5076c79435490046 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 14:27:13 -0700 Subject: [PATCH 0065/1635] update version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e5a17e9e..4a5255fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.5.0-dev" +version = "0.5.0" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust." readme = "README.md" From 1686682c190f0f63fb2190efa2e408e9a18b3ca9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 21:11:15 -0700 Subject: [PATCH 0066/1635] extend CorsBuilder api to make it more user friendly --- src/application.rs | 13 ++- src/middleware/cors.rs | 185 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 177 insertions(+), 21 deletions(-) diff --git a/src/application.rs b/src/application.rs index 0a05d868..14615092 100644 --- a/src/application.rs +++ b/src/application.rs @@ -139,7 +139,7 @@ pub struct App { impl App<()> { - /// Create application with empty state. Application can + /// Create application with empty state. Application can /// be configured with a builder-like pattern. pub fn new() -> App<()> { App { @@ -328,8 +328,15 @@ impl App where S: 'static { self } - /// Default resource to be used if no matching route could be - /// found. + /// Configure resource for a specific path. + #[doc(hidden)] + pub fn register_resource(&mut self, path: &str, resource: ResourceHandler) { + let pattern = Resource::new(resource.get_name(), path); + self.parts.as_mut().expect("Use after finish") + .resources.push((pattern, Some(resource))); + } + + /// Default resource to be used if no matching route could be found. pub fn default_resource(mut self, f: F) -> App where F: FnOnce(&mut ResourceHandler) -> R + 'static { diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 65f39d7b..9e34d832 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -52,6 +52,7 @@ use std::rc::Rc; use http::{self, Method, HttpTryFrom, Uri, StatusCode}; use http::header::{self, HeaderName, HeaderValue}; +use application::App; use error::{Result, ResponseError}; use resource::ResourceHandler; use httpmessage::HttpMessage; @@ -183,7 +184,7 @@ impl Default for Cors { } impl Cors { - pub fn build() -> CorsBuilder { + pub fn build() -> CorsBuilder<()> { CorsBuilder { cors: Some(Inner { origins: AllOrSome::All, @@ -200,6 +201,48 @@ impl Cors { methods: false, error: None, expose_hdrs: HashSet::new(), + resources: Vec::new(), + app: None, + } + } + + /// Create CorsBuilder for a specified application. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpResponse}; + /// use actix_web::middleware::cors::Cors; + /// + /// fn main() { + /// let app = App::new() + /// .configure(|app| Cors::for_app(app) // <- Construct CORS builder + /// .allowed_origin("https://www.rust-lang.org/") + /// .resource("/resource", |r| { // register resource + /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); + /// }) + /// .register() // construct CORS and return application instance + /// ); + /// } + /// ``` + pub fn for_app(app: App) -> CorsBuilder { + CorsBuilder { + cors: Some(Inner { + origins: AllOrSome::All, + origins_str: None, + methods: HashSet::new(), + headers: AllOrSome::All, + expose_hdrs: None, + max_age: None, + preflight: true, + send_wildcard: false, + supports_credentials: false, + vary_header: true, + }), + methods: false, + error: None, + expose_hdrs: HashSet::new(), + resources: Vec::new(), + app: Some(app), } } @@ -401,11 +444,13 @@ impl Middleware for Cors { /// .finish(); /// # } /// ``` -pub struct CorsBuilder { +pub struct CorsBuilder { cors: Option, methods: bool, error: Option, expose_hdrs: HashSet, + resources: Vec<(String, ResourceHandler)>, + app: Option>, } fn cors<'a>(parts: &'a mut Option, err: &Option) @@ -417,7 +462,7 @@ fn cors<'a>(parts: &'a mut Option, err: &Option) parts.as_mut() } -impl CorsBuilder { +impl CorsBuilder { /// Add an origin that are allowed to make requests. /// Will be verified against the `Origin` request header. @@ -435,7 +480,7 @@ impl CorsBuilder { /// Defaults to `All`. /// /// Builder panics if supplied origin is not valid uri. - pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { + pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { if let Some(cors) = cors(&mut self.cors, &self.error) { match Uri::try_from(origin) { Ok(_) => { @@ -461,7 +506,7 @@ impl CorsBuilder { /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]` - pub fn allowed_methods(&mut self, methods: U) -> &mut CorsBuilder + pub fn allowed_methods(&mut self, methods: U) -> &mut CorsBuilder where U: IntoIterator, Method: HttpTryFrom { self.methods = true; @@ -482,7 +527,7 @@ impl CorsBuilder { } /// Set an allowed header - pub fn allowed_header(&mut self, header: H) -> &mut CorsBuilder + pub fn allowed_header(&mut self, header: H) -> &mut CorsBuilder where HeaderName: HttpTryFrom { if let Some(cors) = cors(&mut self.cors, &self.error) { @@ -511,7 +556,7 @@ impl CorsBuilder { /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// Defaults to `All`. - pub fn allowed_headers(&mut self, headers: U) -> &mut CorsBuilder + pub fn allowed_headers(&mut self, headers: U) -> &mut CorsBuilder where U: IntoIterator, HeaderName: HttpTryFrom { if let Some(cors) = cors(&mut self.cors, &self.error) { @@ -542,7 +587,7 @@ impl CorsBuilder { /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// This defaults to an empty set. - pub fn expose_headers(&mut self, headers: U) -> &mut CorsBuilder + pub fn expose_headers(&mut self, headers: U) -> &mut CorsBuilder where U: IntoIterator, HeaderName: HttpTryFrom { for h in headers { @@ -563,7 +608,7 @@ impl CorsBuilder { /// This value is set as the `Access-Control-Max-Age` header. /// /// This defaults to `None` (unset). - pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder { + pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.max_age = Some(max_age) } @@ -584,7 +629,7 @@ impl CorsBuilder { /// in an `Error::CredentialsWithWildcardOrigin` error during actix launch or runtime. /// /// Defaults to `false`. - pub fn send_wildcard(&mut self) -> &mut CorsBuilder { + pub fn send_wildcard(&mut self) -> &mut CorsBuilder { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.send_wildcard = true } @@ -603,7 +648,7 @@ impl CorsBuilder { /// /// Builder panics if credentials are allowed, but the Origin is set to "*". /// This is not allowed by W3C - pub fn supports_credentials(&mut self) -> &mut CorsBuilder { + pub fn supports_credentials(&mut self) -> &mut CorsBuilder { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.supports_credentials = true } @@ -621,7 +666,7 @@ impl CorsBuilder { /// caches that the CORS headers are dynamic, and cannot be cached. /// /// By default `vary` header support is enabled. - pub fn disable_vary_header(&mut self) -> &mut CorsBuilder { + pub fn disable_vary_header(&mut self) -> &mut CorsBuilder { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.vary_header = false } @@ -634,21 +679,57 @@ impl CorsBuilder { /// This is useful application level middleware. /// /// By default *preflight* support is enabled. - pub fn disable_preflight(&mut self) -> &mut CorsBuilder { + pub fn disable_preflight(&mut self) -> &mut CorsBuilder { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.preflight = false } self } - /// Finishes building and returns the built `Cors` instance. + /// Configure resource for a specific path. /// - /// This method panics in case of any configuration error. - pub fn finish(&mut self) -> Cors { + /// This is similar to a `App::resource()` method. Except, cors middleware + /// get registered for the resource. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpResponse}; + /// use actix_web::middleware::cors::Cors; + /// + /// fn main() { + /// let app = App::new() + /// .configure(|app| Cors::for_app(app) // <- Construct CORS builder + /// .allowed_origin("https://www.rust-lang.org/") + /// .allowed_methods(vec!["GET", "POST"]) + /// .allowed_header(http::header::CONTENT_TYPE) + /// .max_age(3600) + /// .resource("/resource1", |r| { // register resource + /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); + /// }) + /// .resource("/resource2", |r| { // register another resource + /// r.method(http::Method::HEAD) + /// .f(|_| HttpResponse::MethodNotAllowed()); + /// }) + /// .register() // construct CORS and return application instance + /// ); + /// } + /// ``` + pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder + where F: FnOnce(&mut ResourceHandler) -> R + 'static + { + // add resource handler + let mut handler = ResourceHandler::default(); + f(&mut handler); + + self.resources.push((path.to_owned(), handler)); + self + } + + fn construct(&mut self) -> Cors { if !self.methods { self.allowed_methods(vec![Method::GET, Method::HEAD, - Method::POST, Method::OPTIONS, Method::PUT, - Method::PATCH, Method::DELETE]); + Method::POST, Method::OPTIONS, Method::PUT, + Method::PATCH, Method::DELETE]); } if let Some(e) = self.error.take() { @@ -673,6 +754,39 @@ impl CorsBuilder { } Cors{inner: Rc::new(cors)} } + + /// Finishes building and returns the built `Cors` instance. + /// + /// This method panics in case of any configuration error. + pub fn finish(&mut self) -> Cors { + if !self.resources.is_empty() { + panic!("CorsBuilder::resource() was used, + to construct CORS `.register(app)` method should be used"); + } + self.construct() + } + + /// Finishes building Cors middleware and register middleware for application + /// + /// This method panics in case of any configuration error or if non of + /// resources are registered. + pub fn register(&mut self) -> App { + if self.resources.is_empty() { + panic!("No resources are registered."); + } + + let cors = self.construct(); + let mut app = self.app.take().expect( + "CorsBuilder has to be constructed with Cors::for_app(app)"); + + // register resources + for (path, mut resource) in self.resources.drain(..) { + cors.clone().register(&mut resource); + app.register_resource(&path, resource); + } + + app + } } @@ -713,6 +827,23 @@ mod tests { .finish(); } + #[test] + #[should_panic(expected = "No resources are registered")] + fn no_resource() { + Cors::build() + .supports_credentials() + .send_wildcard() + .register(); + } + + #[test] + #[should_panic(expected = "Cors::for_app(app)")] + fn no_resource2() { + Cors::build() + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + .register(); + } + #[test] fn validate_origin_allows_all_origins() { let cors = Cors::default(); @@ -866,4 +997,22 @@ mod tests { &b"https://www.example.com"[..], resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()); } + + #[test] + fn cors_resource() { + let mut app = App::new() + .configure( + |app| Cors::for_app(app) + .allowed_origin("https://www.example.com") + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + .register()) + .finish(); + + let req = TestRequest::with_uri("/test").finish(); + let resp = app.run(req); + + // TODO: proper test + //assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert!(resp.as_response().is_none()); + } } From 2881859400f6b1efbfe866ab13c0809c236172b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 21:29:57 -0700 Subject: [PATCH 0067/1635] proper test for CorsBuilder::resource --- src/middleware/cors.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 9e34d832..0cd42aaa 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -793,7 +793,7 @@ impl CorsBuilder { #[cfg(test)] mod tests { use super::*; - use test::TestRequest; + use test::{self, TestRequest}; impl Started { fn is_done(&self) -> bool { @@ -1000,19 +1000,21 @@ mod tests { #[test] fn cors_resource() { - let mut app = App::new() - .configure( - |app| Cors::for_app(app) - .allowed_origin("https://www.example.com") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register()) - .finish(); + let mut srv = test::TestServer::with_factory( + || App::new() + .configure( + |app| Cors::for_app(app) + .allowed_origin("https://www.example.com") + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + .register())); - let req = TestRequest::with_uri("/test").finish(); - let resp = app.run(req); + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); - // TODO: proper test - //assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); - assert!(resp.as_response().is_none()); + let request = srv.get().uri(srv.url("/test")) + .header("ORIGIN", "https://www.example.com").finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::OK); } } From 23eea54776170f4f5255a04acfe60244f54baa29 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 21:39:32 -0700 Subject: [PATCH 0068/1635] update cors doc string --- src/middleware/cors.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 0cd42aaa..473f6f96 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -10,8 +10,8 @@ //! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend. //! //! Cors middleware could be used as parameter for `App::middleware()` or -//! `ResourceHandler::middleware()` methods. But you have to use `Cors::register()` method to -//! support *preflight* OPTIONS request. +//! `ResourceHandler::middleware()` methods. But you have to use +//! `Cors::for_app()` method to support *preflight* OPTIONS request. //! //! //! # Example @@ -19,7 +19,7 @@ //! ```rust //! # extern crate actix_web; //! use actix_web::{http, App, HttpRequest, HttpResponse}; -//! use actix_web::middleware::cors; +//! use actix_web::middleware::cors::Cors; //! //! fn index(mut req: HttpRequest) -> &'static str { //! "Hello world" @@ -27,19 +27,17 @@ //! //! fn main() { //! let app = App::new() -//! .resource("/index.html", |r| { -//! cors::Cors::build() // <- Construct CORS middleware -//! .allowed_origin("https://www.rust-lang.org/") -//! .allowed_methods(vec!["GET", "POST"]) -//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) -//! .allowed_header(http::header::CONTENT_TYPE) -//! .max_age(3600) -//! .finish() -//! .register(r); // <- Register CORS middleware -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); -//! }) -//! .finish(); +//! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder +//! .allowed_origin("https://www.rust-lang.org/") +//! .allowed_methods(vec!["GET", "POST"]) +//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) +//! .allowed_header(http::header::CONTENT_TYPE) +//! .max_age(3600) +//! .resource("/index.html", |r| { +//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); +//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); +//! }) +//! .register()); //! } //! ``` //! In this example custom *CORS* middleware get registered for "/index.html" endpoint. From bb11fb3d242477ad0d61458b4c1dca7b146b4f3c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 21:57:40 -0700 Subject: [PATCH 0069/1635] update client mod doc string --- src/client/mod.rs | 29 ++++++++++++++++++++++++++++- src/fs.rs | 4 +--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 89b8bdea..afe4e459 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,4 +1,31 @@ -//! HTTP client +//! Http client api +//! +//! ```rust +//! # extern crate actix; +//! # extern crate actix_web; +//! # extern crate futures; +//! # use futures::Future; +//! use actix_web::client; +//! +//! fn main() { +//! let sys = actix::System::new("test"); +//! +//! actix::Arbiter::handle().spawn({ +//! client::get("http://www.rust-lang.org") // <- Create request builder +//! .header("User-Agent", "Actix-web") +//! .finish().unwrap() +//! .send() // <- Send http request +//! .map_err(|_| ()) +//! .and_then(|response| { // <- server http response +//! println!("Response: {:?}", response); +//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +//! Ok(()) +//! }) +//! }); +//! +//! sys.run(); +//! } +//! ``` mod connector; mod parser; mod request; diff --git a/src/fs.rs b/src/fs.rs index e526ffbc..2f8f4b4e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,6 +1,4 @@ -//! Static files support. - -// //! TODO: needs to re-implement actual files handling, current impl blocks +//! Static files support use std::{io, cmp}; use std::io::{Read, Seek}; use std::fmt::Write; From 81ac905c7b0ff1528cf26121541fae3b60029155 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 10:13:52 -0700 Subject: [PATCH 0070/1635] fix prefix and static file serving #168 --- CHANGES.md | 8 +++-- src/application.rs | 79 ++++++++++++++++++++++++++++++++++++++++++---- src/fs.rs | 45 +++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fb4943be..cdac5fa0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,16 +13,18 @@ * Added `ErrorHandlers` middleware -* Router cannot parse Non-ASCII characters in URL #137 +* Fix router cannot parse Non-ASCII characters in URL #137 + +* Fix client connection pooling * Fix long client urls #129 * Fix panic on invalid URL characters #130 -* Fix client connection pooling - * Fix logger request duration calculation #152 +* Fix prefix and static file serving #168 + * Add `signed` and `private` `CookieSessionBackend`s diff --git a/src/application.rs b/src/application.rs index 14615092..f6c12610 100644 --- a/src/application.rs +++ b/src/application.rs @@ -21,6 +21,7 @@ pub type Application = App; pub struct HttpApplication { state: Rc, prefix: String, + prefix_len: usize, router: Router, inner: Rc>>, middlewares: Rc>>>, @@ -73,6 +74,7 @@ impl HttpApplication { path.len() == prefix.len() || path.split_at(prefix.len()).1.starts_with('/')) }; + if m { let path: &'static str = unsafe { mem::transmute(&req.path()[inner.prefix+prefix.len()..]) }; @@ -106,8 +108,8 @@ impl HttpHandler for HttpApplication { 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('/')) + path.len() == self.prefix_len || + path.split_at(self.prefix_len).1.starts_with('/')) }; if m { let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); @@ -420,8 +422,12 @@ impl App where S: 'static { pub fn handler>(mut self, path: &str, handler: H) -> App { { - let path = path.trim().trim_right_matches('/').to_owned(); + let mut path = path.trim().trim_right_matches('/').to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/') + } let parts = self.parts.as_mut().expect("Use after finish"); + parts.handlers.push((path, Box::new(WrapHandler::new(handler)))); } self @@ -471,17 +477,22 @@ impl App 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 (prefix, prefix_len) = if prefix.is_empty() { + ("/".to_owned(), 0) + } else { + (prefix.to_owned(), prefix.len()) + }; let mut resources = parts.resources; for (_, pattern) in parts.external { resources.push((pattern, None)); } - let (router, resources) = Router::new(prefix, parts.settings, resources); + let (router, resources) = Router::new(&prefix, parts.settings, resources); let inner = Rc::new(UnsafeCell::new( Inner { - prefix: prefix.len(), + prefix: prefix_len, default: parts.default, encoding: parts.encoding, handlers: parts.handlers, @@ -491,9 +502,10 @@ impl App where S: 'static { HttpApplication { state: Rc::new(parts.state), - prefix: prefix.to_owned(), router: router.clone(), middlewares: Rc::new(parts.middlewares), + prefix, + prefix_len, inner, } } @@ -670,6 +682,61 @@ mod tests { assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); } + #[test] + fn test_handler2() { + let mut app = App::new() + .handler("test", |_| HttpResponse::Ok()) + .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); + } + + #[test] + fn test_handler_with_prefix() { + let mut app = App::new() + .prefix("prefix") + .handler("/test", |_| HttpResponse::Ok()) + .finish(); + + let req = TestRequest::with_uri("/prefix/test").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/prefix/test/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/prefix/test/app").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/prefix/testapp").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/prefix/blah").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + } + #[test] fn test_route() { let mut app = App::new() diff --git a/src/fs.rs b/src/fs.rs index 2f8f4b4e..8c640262 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -476,6 +476,9 @@ impl Handler for StaticFiles { new_path.push_str(&el.to_string_lossy()); new_path.push('/'); } + if !new_path.ends_with('/') { + new_path.push('/'); + } new_path.push_str(redir_index); HttpResponse::Found() .header(header::LOCATION, new_path.as_str()) @@ -500,7 +503,8 @@ impl Handler for StaticFiles { #[cfg(test)] mod tests { use super::*; - use test::TestRequest; + use application::App; + use test::{self, TestRequest}; use http::{header, Method, StatusCode}; #[test] @@ -603,4 +607,43 @@ mod tests { assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/examples/basics/Cargo.toml"); } + + #[test] + fn integration_redirect_to_index_with_prefix() { + let mut srv = test::TestServer::with_factory( + || App::new() + .prefix("public") + .handler("/", StaticFiles::new(".").index_file("Cargo.toml"))); + + let request = srv.get().uri(srv.url("/public")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::FOUND); + let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + assert_eq!(loc, "/public/Cargo.toml"); + + let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::FOUND); + let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + assert_eq!(loc, "/public/Cargo.toml"); + } + + #[test] + fn integration_redirect_to_index() { + let mut srv = test::TestServer::with_factory( + || App::new() + .handler("test", StaticFiles::new(".").index_file("Cargo.toml"))); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::FOUND); + let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + assert_eq!(loc, "/test/Cargo.toml"); + + let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::FOUND); + let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + assert_eq!(loc, "/test/Cargo.toml"); + } } From fd87eb59f8928c240579c54885ab63ea28843c28 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 10:29:10 -0700 Subject: [PATCH 0071/1635] remove reference to master --- README.md | 14 +++----------- guide/src/qs_2.md | 2 +- src/server/shared.rs | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ae63f605..e8f93a92 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. [CSRF](https://actix.github.io/actix-web/actix_web/middleware/csrf/index.html)) * Built on top of [Actix actor framework](https://github.com/actix/actix) -## Documentation +## Documentation & community resources * [User Guide](http://actix.github.io/actix-web/guide/) * [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/) @@ -31,26 +31,18 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. ## Example -At the moment all examples are based on the actix-web `master` branch. - -```toml -[dependencies] -actix = "0.5" -actix-web = { git="https://github.com/actix/actix-web.git" } -``` - ```rust extern crate actix_web; use actix_web::{server, App, Path}; -fn index(info: Path<(String, u32)>) -> String { +fn index(info: Path<(u32, String)>) -> String { format!("Hello {}! id:{}", info.0, info.1) } fn main() { server::new( || App::new() - .resource("/{name}/{id}/index.html", |r| r.with(index))) + .resource("/{id}/{name}/index.html", |r| r.with(index))) .bind("127.0.0.1:8080").unwrap() .run(); } diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 524c2c1d..8f3ec392 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -17,7 +17,7 @@ contains the following: ```toml [dependencies] actix = "0.5" -actix-web = { git="https://github.com/actix/actix-web.git" } +actix-web = "0.5" ``` In order to implement a web server, we first need to create a request handler. diff --git a/src/server/shared.rs b/src/server/shared.rs index bb3269c0..6773abcd 100644 --- a/src/server/shared.rs +++ b/src/server/shared.rs @@ -39,7 +39,7 @@ pub(crate) struct SharedBytes( impl Drop for SharedBytes { fn drop(&mut self) { - if let Some(ref pool) = self.1 { + if let Some(pool) = self.1.take() { if let Some(bytes) = self.0.take() { if Rc::strong_count(&bytes) == 1 { pool.release_bytes(bytes); From 5e6a0aa3dfda610eb3e6aef647f3aa9a9ae59649 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 10:39:16 -0700 Subject: [PATCH 0072/1635] simplier example in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8f93a92..450240ca 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. ```rust extern crate actix_web; -use actix_web::{server, App, Path}; +use actix_web::{http, server, App, Path}; fn index(info: Path<(u32, String)>) -> String { format!("Hello {}! id:{}", info.0, info.1) @@ -42,7 +42,7 @@ fn index(info: Path<(u32, String)>) -> String { fn main() { server::new( || App::new() - .resource("/{id}/{name}/index.html", |r| r.with(index))) + .route("/{id}/{name}/index.html", http::Method::GET, index)) .bind("127.0.0.1:8080").unwrap() .run(); } From be288fa00af234c797aeaa7dd516dfdbf0e8f0f5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 10:57:53 -0700 Subject: [PATCH 0073/1635] for NamedFile process etag and last modified only if status code is 200 --- src/application.rs | 16 ++++++++-------- src/fs.rs | 15 +++++++++++++++ src/resource.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/application.rs b/src/application.rs index f6c12610..db0e9d81 100644 --- a/src/application.rs +++ b/src/application.rs @@ -216,8 +216,8 @@ impl App where S: 'static { /// let app = App::new() /// .prefix("/app") /// .resource("/test", |r| { - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); + /// r.get().f(|_| HttpResponse::Ok()); + /// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// }) /// .finish(); /// } @@ -309,8 +309,8 @@ impl App where S: 'static { /// fn main() { /// let app = App::new() /// .resource("/test", |r| { - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); + /// r.get().f(|_| HttpResponse::Ok()); + /// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// }); /// } /// ``` @@ -377,7 +377,7 @@ impl App where S: 'static { /// /// fn main() { /// let app = App::new() - /// .resource("/index.html", |r| r.f(index)) + /// .resource("/index.html", |r| r.get().f(index)) /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") /// .finish(); /// } @@ -449,14 +449,14 @@ impl App where S: 'static { /// /// ```rust /// # extern crate actix_web; - /// use actix_web::{App, HttpResponse, http, fs, middleware}; + /// use actix_web::{App, HttpResponse, fs, middleware}; /// /// // this function could be located in different module /// fn config(app: App) -> App { /// app /// .resource("/test", |r| { - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); + /// r.get().f(|_| HttpResponse::Ok()); + /// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// }) /// } /// diff --git a/src/fs.rs b/src/fs.rs index 8c640262..73ca6828 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -182,6 +182,21 @@ impl Responder for NamedFile { type Error = io::Error; fn respond_to(self, req: HttpRequest) -> Result { + if self.status_code != StatusCode::OK { + let mut resp = HttpResponse::build(self.status_code); + resp.if_some(self.path().extension(), |ext, resp| { + resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); + }); + let reader = ChunkedReadFile { + size: self.md.len(), + offset: 0, + cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), + file: Some(self.file), + fut: None, + }; + return Ok(resp.streaming(reader)) + } + if self.only_get && *req.method() != Method::GET && *req.method() != Method::HEAD { return Ok(HttpResponse::MethodNotAllowed() .header(header::CONTENT_TYPE, "text/plain") diff --git a/src/resource.rs b/src/resource.rs index 0ffc6412..19a1b057 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -92,6 +92,36 @@ impl ResourceHandler { self.routes.last_mut().unwrap() } + /// Register a new `GET` route. + pub fn get(&mut self) -> &mut Route { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().filter(pred::Get()) + } + + /// Register a new `POST` route. + pub fn post(&mut self) -> &mut Route { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().filter(pred::Post()) + } + + /// Register a new `PUT` route. + pub fn put(&mut self) -> &mut Route { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().filter(pred::Put()) + } + + /// Register a new `DELETE` route. + pub fn delete(&mut self) -> &mut Route { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().filter(pred::Delete()) + } + + /// Register a new `HEAD` route. + pub fn head(&mut self) -> &mut Route { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().filter(pred::Head()) + } + /// Register a new route and add method check to route. /// /// This is shortcut for: From 88f66d49d0a90f861972672a814c9891a92b43c4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 11:07:54 -0700 Subject: [PATCH 0074/1635] openssl features --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4a5255fa..c4c04e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ default = ["session", "brotli"] tls = ["native-tls", "tokio-tls"] # openssl -alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"] +alpn = ["openssl", "tokio-openssl"] # sessions session = ["cookie/secure"] From ca76dff5a70e78ab0bad2134084bae480d9249bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 13:21:54 -0700 Subject: [PATCH 0075/1635] update redis example --- examples/redis-session/Cargo.toml | 4 ++-- examples/redis-session/src/main.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/redis-session/Cargo.toml b/examples/redis-session/Cargo.toml index cfa102d1..55349b41 100644 --- a/examples/redis-session/Cargo.toml +++ b/examples/redis-session/Cargo.toml @@ -7,5 +7,5 @@ workspace = "../.." [dependencies] env_logger = "0.5" actix = "0.5" -actix-web = "0.4" -actix-redis = { version = "0.2", features = ["web"] } +actix-web = "0.5" +actix-redis = { version = "0.3", features = ["web"] } diff --git a/examples/redis-session/src/main.rs b/examples/redis-session/src/main.rs index 36df1655..f61496fc 100644 --- a/examples/redis-session/src/main.rs +++ b/examples/redis-session/src/main.rs @@ -5,8 +5,8 @@ extern crate actix_web; extern crate actix_redis; extern crate env_logger; -use actix_web::*; -use actix_web::middleware::RequestSession; +use actix_web::{server, App, HttpRequest, HttpResponse, Result}; +use actix_web::middleware::{Logger, SessionStorage, RequestSession}; use actix_redis::RedisSessionBackend; @@ -30,12 +30,12 @@ fn main() { env_logger::init(); let sys = actix::System::new("basic-example"); - HttpServer::new( - || Application::new() + server::new( + || App::new() // enable logger - .middleware(middleware::Logger::default()) + .middleware(Logger::default()) // cookie session middleware - .middleware(middleware::SessionStorage::new( + .middleware(SessionStorage::new( RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) )) // register simple route, handle all methods From 8dbbb0ee07a1c6f376cbccea05ebf8881cde8505 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 13:31:10 -0700 Subject: [PATCH 0076/1635] update guide --- guide/src/qs_2.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index 8f3ec392..a5f3d277 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -51,10 +51,11 @@ request handler with the application's `resource` on a particular *HTTP method* ``` After that, the application instance can be used with `HttpServer` to listen for incoming -connections. The server accepts a function that should return an `HttpHandler` instance: +connections. The server accepts a function that should return an `HttpHandler` instance. +For simplicity `server::new` could be used, this function is shortcut for `HttpServer::new`: ```rust,ignore - HttpServer::new( + server::new( || App::new() .resource("/", |r| r.f(index))) .bind("127.0.0.1:8088")? @@ -80,7 +81,7 @@ fn main() { # // If copying this example in show-all mode, make sure you skip the thread spawn # // call. # thread::spawn(|| { - server::HttpServer::new( + server::new( || App::new() .resource("/", |r| r.f(index))) .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") From 50c2a5ceb05411a083234a73c97e3e7e53527f98 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 14:45:03 -0700 Subject: [PATCH 0077/1635] update basic example --- examples/basics/src/main.rs | 36 ++++++++++------------------------ examples/static/actixLogo.png | Bin 8898 -> 13131 bytes 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs index c79cb4ad..633f4823 100644 --- a/examples/basics/src/main.rs +++ b/examples/basics/src/main.rs @@ -10,7 +10,7 @@ use futures::Stream; use std::{io, env}; use actix_web::{error, fs, pred, server, App, HttpRequest, HttpResponse, Result, Error}; -use actix_web::http::{Method, StatusCode}; +use actix_web::http::{header, Method, StatusCode}; use actix_web::middleware::{self, RequestSession}; use futures::future::{FutureResult, result}; @@ -40,36 +40,18 @@ fn index(mut req: HttpRequest) -> Result { req.session().set("counter", counter)?; } - // 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)) + .content_type("text/html; charset=utf-8") + .body(include_str!("../static/welcome.html"))) } /// 404 handler -fn p404(req: HttpRequest) -> Result { - - // html - let html = r#"actix - basics - - back to home -

404

- -"#; - - // response - Ok(HttpResponse::build(StatusCode::NOT_FOUND) - .content_type("text/html; charset=utf-8") - .body(html)) +fn p404(req: HttpRequest) -> Result { + Ok(fs::NamedFile::open("./static/404.html")? + .set_status_code(StatusCode::NOT_FOUND)) } @@ -131,14 +113,16 @@ fn main() { // redirect .resource("/", |r| r.method(Method::GET).f(|req| { println!("{:?}", req); - HttpResponse::Found() - .header("LOCATION", "/index.html") + .header(header::LOCATION, "/index.html") .finish() })) // default .default_resource(|r| { + // 404 for GET request r.method(Method::GET).f(p404); + + // all requests that are not `GET` r.route().filter(pred::Not(pred::Get())).f( |req| HttpResponse::MethodNotAllowed()); })) diff --git a/examples/static/actixLogo.png b/examples/static/actixLogo.png index 142e4e8d57704b6779d54f979dcf50764906d9fe..1e2509a75a75b950e331348b1241e077cbaa3222 100644 GIT binary patch literal 13131 zcmV-RGqlW!P)F6fe00009a7bBm000XU z000XU0RWnu7ytkO8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GPWjp`?GT2E( zK~#9!?0tQRWL0;QpVRySG2KH6f?HUy~qS!TTf*@J@4k#>sO$`Rag3>kk&kK@F z4Jb$;bWIT4Kh&vFHUVAfYQY#GVQTUrF@((RWR=~JWU5ir4GD9bklpts$lIMY$tKy& zc%H83?%b}ak8|(&KKJ*7L(Ozo-@0`^e)su)c3!=DB>*5*?Ck7xZ@VMOF4z1hBG2bbpG_~Pauhsf@^j``6#xVZm?pYRzVw)gXdDVSrd^;vo?YFS1fGu<|1;7&CmgCR?d7f^2 zgCVv09fkM&A1pfn0tHOquWkAL7v+e_UZ^60{uGuS0D%G~@O;bf|ClQ;_Rh{u-@s*h z3X<+G7`WG=98)<4?D+>kpg;@pAGiE|kL{!`V1kGabWI70_-A5a`gkhGdvcEEa*Wvt z2Y^6Z69-ZOCtw_ z)0hOwnIqf<>#Ka9=Nb@h~&gEzH8hi(u1C?0TQ+fSNbG|MVzRxWXC?rt8FaDcl$*>NHFeVKp*WDWLC$E34 z@5Jv{O$0d5nFO*yD}0}4AWsk|;2-}|j^E$%o+_PgN;$ z6{ZUBIX9TO^-_+1P%%9`=Yahojs*x5P$VnW!`(2Yu!+}umnG5JGQn|bBRO;q4SaVZ z(HE7JNm6G=?DTrtkUX!hTlaw@-%s{m6)%5bN_FRde#?qEp}8+Wr0xMPe2j~>v!l9b zXG`_aKQHPAf>DDG^@A zHmmP6b=Vc(cb8RXlPamIM0yYrd0wf+l)G-2JWcJNZXGiN$P)yb3UT(Bz@9Uq{IG}H zIAl9nZ_B+-h5rJ|zHzjPMEQ5e9_~vu^y{m_TskiR0`*3mT_z+l`d4j#f6AZ~S_edo zFG&qtCQ`#T%2Y_S;2HxNj|pdh9W}UKUw1DFwa~9G$nhyipN`KuY@o);lQ#Pg$nn8- z>bHT+mt5$1w8=(UK;m?QGr8VzWBQXVEfFDqsV(1Xh18F%v-G5Bf}O&O&?4cKRM>3k2#Kb4&pcFnN;mQl-8uIWDJ| zu{VLX#@9DgpErsr13KdK7C7U%D8%dfnALw@J!~`*3#zG-)&KW+(9crLywa5lRXYTH zUAXJD1p3d(ea6mBW<|~L{6WNx;n(^yiq;sA% zl?-ck2>4l(;ZjNQ2AoK1jp+YG&dtBjysk1o@4TLbwat0{h~1SWyh5dMW3= zYiPf(eK6`4zX5@!OPpPnURJ4he= zs*)%2>_MO#C5~-2pvEK`anX-6o|hakfY;WuRQU&iX%$L)l(X2TqFy4#9qa)R=(@x3 zvc?9j9Z=jbklxn;oK;8@(uUgl4wy^_!hd!cts!Ny&DPxKNYqaOe!_Jf01)WP1Mn;I z<#vrfQEm>#(q|#UO}jj2!kof6U}P^11CdP6#r4RBrcM2zx`RNKNbVguzNw~qJKB=7 zzR9i%(oA2o84C3tMxnw;Cs7b+`WrqIi-vD?L5`IvtGq>pI7tejjTeds_mb}ur0GX0 z4QVgb!A(e?AW+qfo?@ucGbYU$wm68i?%t9621UC;ptaPl6pDVFSES7XTRTW1okXD8 z*KR-3X0$#Rp2_A}#9!f*H$55s#~{yO_Zg8AJ**!Eeyb)6G*ip$AE&_mrj7L=fyN;n zkjnBjvfDmkX)^~sMYrF|nhU`N=ga-HWAHmtulo1#kH`U=rDj_NfxcewSSEsq6Ov{c zdu}WOE0k@JKz~WTe0=H0$&o0^*xn?`h{Wo~a$lv7ulN;7 z+&N39Akf4RA}N)`S=u)dT9O`#=3-$RJqv{VdYXSflrIl9yd^0T1%ZZ3proqzL7c51 zQCi0%JI8$n250p#Y;$s=oVD<_K_&ZU!Hirm2rb} z*yVWY2Q~hTNHqFhOVJW2k*9q5hjP4Mj$f7k|Aen6uOW~MN7Zl8PPW}dN-D5Vd;A_3 z!cjek1J>T^CZqhASY+;jKs}J>h{>}Ag-ZN1FJ_bL^@P8W60&nr9A_UPNdRLY{MtmR zk=%Gw>?X%}(tdp^>Lmw|hV@tFUjMEXOjFT?G08>yZu#<`=$~ueLMqUx&f<7X=X@d- zAxW@t0;?YCbKy60sVsF^@V%7nP@0zTJ7Ebsopp+dK-t#$aszo*7oO}ZlTGBrPgr{2 zq2sTzwW<>UM4I5S4oS8KiH1O#JpZ>?*gWgtS@{$#39HiohD-sji#(uI7*J>5)1Ezu z{A*C1hJK2gR!ju?ujR}4L_F6dbq-Nh4rrjt$6o9v+14P@IFaN6e6M`@_mR%?BX$yj zJb}9Vz7YOHn{;rJvZ*RYCsIuHJI`6_%s~Y}OQq0TDTn1n7skZF#!n)+RkFPzGzl;z zg`P!t>nEwlGc3zAn+GaG&8K)85D+n}#r*a|u zmjF9~y%PZ5fU)ziv$ONVa=gPy1t3tv@M)-im|AWx02o4Mh&-qArfO>j;CU?sGQC3B5Yk~o*^R2*{Edl0B0HQon-qIWg`Y{qU=qT^Z>g;-w$;J9h& zN)V`jfIIEKXbBQMf$BEF0(k-vVK1`#B@&#oM7ik< z512S9T~#$r`$KZf>JYRI-0)0(6d3#&NlKs9xXjSe?Sec3pJyeDnDn2^`=>bRTwM3g zXF{)3A6=Wyq&IcmTPBj6(0?vp{we?WT{+&C_cE(_4U#@xRPBDk>7xMP>%5S|@&RVz z4N&%9-&YK_7Xn>VS)M8vja8*QCQl+<+r5lV5UKg?b55X3s=EqEGA+gP zzHrxM%d2Vt0F^UCg2X|~r{R)?nV?ccof2q`rf?zP4hn++kUl}4o8UT(>GP40=M#`8 z5D3N~&oIf;2Z8=!1PgWsk2?^E^f`d)9(E%}rvxe|;-m8AGoddSjxG=Yc~XG*wB^%q zQa7~G`8s*>!Vh?J>;^U;i1!3SAy5;|6Df*>u9GkQoXD9RvUCpj1Dyg=-Fu-&!1xTL z@AJa$V!;65+S#Co_+uT(O)O%n3&nFtpLZD8G*Bz>aqw&9vnLW)YdH>NE|97Tgg~B| zXzq0E{950|59W&#s09crFZN%(28rTH&SV>4>JEl6L~-MfXWym!bvztAXIjlO)fk zx$Z+#wtH6phn}aTh-K}9s0YfnM_*Ft6pkYx0P<|AHlLVI`ryzZpB+GZrsvpJ-QbW4 zD*KWGr#QxdC-UsGx~gMyw57fI?Dv%!p5xz;<0nC&=5Zc!mIIJHdFrU`glD!q)hvbj z(hB!<3rKT&kRO--q@wK~kmE1d=%dv3o=C8L0zSxdD9pq_=az3Z_jxv(^gLp9eaw>m z-(LFlXHDNE2owO^k|!PRV__!V(zf8gIEnV52%!087Wg#q~abAy2mbfY zV?)QEcMkFd9Fb=x!pibdMK_T9pm{4(O^-CMbtQ$G$~x>A+$4WXQ~GpWwyVI=1{|sE zDt*qIplA8^`-v{gzcO{9j-gHRY5DRK^7Xgm_<@>#I}(2K;URqjHi@$@$J~I#kkrK z9r z6ImJ}O-c+f-U3z22c?40LLCMKsG%-pm#ldJf(6DEqtdu5n<eBvv?NeD(Z4TW-WTZv zqXsCTv-e5r8#`3jH|}LQ`X-1Xz)lfOpj{i)x&+ESTizG(B=4zYH~_%XzR%dZejEsS z3cinAcRzzX-Ky~96#9e6&jW0rKpo;Fa-o>c5F4@WGl}*ae!mpR(@0@yCj=^Q^06G> z#6hL78MVKKjv=!23Ar>*5 z`V{#3s@&jI#?9zJA!(iG%n?t^&xsl9bQ;-0lyP*B zMB`V{bNSzwVo@#;- zC>9AEuQ05SITK8;#u^ckj+tOx-6#28x;5@`z&!Pp$~B&<{=L<%ob2h0-B@q6o*>Z3 zra2UF0=dG8SQH|jYp&f;xJ^)a#-yJw{k)WtGqJv#=kLky`^`$<%c2vh$2O=GKp|Np zQ=R;PTKj0gn|%OVrgP#Po6hdxy!)dFzW?8p-~GS%B+y)>*S_P7i8ROgvPPKw3SpxC zOM-ix*LW8A192|r;I?xDWn(PaAyN)dKIf-1BvNyPS&m)IiZ?i`3&A`=Bps?LMoz`6 ztx-y*i;1|lbA+JU6NygYu~$AcRl@9pFoO{Y2Cnxo?*}?Rp+-*p8#yQ6+n7L^5RZ9+ zKQG6x%JFlRx0DB>59#4*j%Fa!H^{O&;P?p-r%av~9`{P9&gOUIy!;6r0{s>FavOWB zkz_h!6(#pkX3OybpoeEqypI3t2{?RWz!Pe^7RbXkRCWI5riaT~0*y2kGh{-g4N`_x z6tR&i+bX+Ekkx=`5M`XWACe89qju&Vwb)YVp>e_c{OZg$B~bE$KE*z>u0CDx#wlNg zOD}&#)fN>gQi#{>PGF}5dAiz*J~)Snlbqg@CT5IH1p1@$| zo4&voBHb)~1B*Atp~FaIj3TYJ)wiLQn!e9%aiC<6_$zY!JrPH0rZEG#{O%OafZCEk z%<$4H-JnBhYLIuVbl+7@<>WX(`a+>*lbp)$k{MjQ(wGfzH4r0=o(E3^N`47b72T{P z4Jfztp!7uUy?herd>ehz@{?+Wa|lF;b9Tf;4Gu3d)fQ-G;~qN=z7!GF{lMn>nWfC0 znTV@BGXT(j9VAo$d~E1CR_3EoQ#rOXJ_%@&tvp?L6XrORKtvd&RLq96^3Dxh%1Wci zj>Z=w_4X`~dCnufHUOB!Nuo7sCkC#O1x29c(=L#wRAKS*sPhTLfHRS<&EABDIkeHP zT#G;riq`-DXc9!KjSmeullBZrve#guFMJ_T(hM+%bMd;*coif)pdj;VJZwP`@2Sx5 zk#)#YsIiC5jTB~<;>9eg@jaYRYapqDv=}=4bCoq*Y72r+2#a{nNejLwsqx1Ku0zQ& z1qbO*YVNtK8o1SXQmvjy0To@gR-(&rHUzLJA$C=`)|3>$74ge{@ogeg3R&u>-tnNa z&NflzqG za?LiW>hw?>`|u7^gb3$?-xq0SlMUOpq1aIt!djLsaOx2#IcvB|(Q$ybrbF(jV#?k^ z8}vW`P3&^_irHM^GZ!KR;55W6Vt{5M)O=?4N{b<;M_s3oEb ziPC`#+2(yIEEK|s7$p7Ka%i8h`L9^oD=U2}r3!xwYXa0%p^{K1E9!j67^PZisUdO; z*QAtAa?lOyNcx?MstW+B(ojk*BTDJwVW78no@kL|9hlyGq^|dyL^qTK`eCdIpon3L zwm7b6bQ<8T!EvRT$8mwG%%& z^v7JDiR_@ClH>bCT;UynL+LYd$%YPPNY0uuw%l5n(1NsW%WG3P`DsiGh+3FHsp<^d z@I?(tH;_KVY#GieF(fc-q~2$Vfmcshbe+MveML&JydALlEgy zpX6mhfF2L}Kk9yN#;vv%v57R#IR$|(OPJ^5Fy~t(Nl+1>)o#S7r_9Bt! zO|d94(bhQFIgY8bIe_Hp9~7AXQgYl}jtRB*;c|!9_>X?Zquay=Iu`z#I#Jb>p^r*H ztj>*CI&F*H^tR!`4KYeNF7`Xcoc2(%E;>#4$Lh8`41 zEYuCQhVnhwTRmdL2cKYfdEg zIF&JxA%yVUi7~B>9S~ZRh)I?O?;Yegm30KOPe8(=Si$N z!ScR|K@fZz(8YC#SJ0 z7MisZdaz!ENTA8qpX6X^N)H?G0Sej4gep}FWEQS@Dfhw*QyUzf$T8W*KUGom3k0Z+~3}|OL5`liT^8c@T{z7>hSCOSr zBCCfW(B!{AGLOtaH#&l}zM<|-bmYN(TaFzb@26M!^E+Z;P<5S_*y<-UdFsU?ZZwp1 z&^^m>kfCE>;hJzBnSt)UoSLYB)md|pb2g~i@MiJZmucKG>67N$bha4O zceBuSA`Lmp!X1HD4z!IbL)cuKRr{rQ1{;koeuy;;f%l-^J-U4g^f0~cu3DUR0n8I1Y`_t= zV~Zbg55<#>ubJ;|oPukHs1Ya|S7*X}X|I(x1q4k9$&e_UBA#0Qn48G3GrDFZWWDfw z8}G_By=unUQMh4}YUZ}d6EOA=A}!&*qmQFKk;mwn=a8(a<&QZ9c^XFuTI!d|e{|MK zoNPcXSS8$bw5g%Onc+|dT4eMFy0TFu0lBZWzP;+Qx=pm9o{r_6yDYxZ-= zO<%75A~XVp3ZD*%?y|Z(V!Pwi6_GM=(r5b2m;-@^FJMsy-)vi)E;~qxn@E#+_uB8u zI8ev&gPVZ3Is0mmD-x}OObAn!NDE8fjSkpQ_)j$^`zOp%=GT|@jS0s#^&1kUq_mXV(yUKBRtQrXJ!q zmQ=h@>>P-nOG}+!28BI6=7Vv`4{y4cLcH!wFfTO`R>^yn{n6vsgFvSk1RADb^@Qu` z8pCoik<-ei{Hh#ZsBGHZh$adMpG2#Y>A@;84CH~{1PEU;58Vo2^q?M_zNuvbrKf+F z57nGATCS-}Or|4YFUbA?d&Mb<4p{XC%*`t#(C`GSnGN5{wPGSDulI`iXFkWe1s8f-CVUhqT9X87+8citd&ip;b=9a&xX)IC`VQC@q>L5- zH$tGcN|zcv zn1W=^J#iWp1E(4h-)V(Fxytxs7f*(e`s=h<3cX+wg=c;>VE;5HeRf4SQ8?djlt4Xn zHzbU(t%4xYIo4t=V5d)0>&!Rup{f&!p9&)$&f7ttMM6*#!c@Sivp(9#HysRRks26<+okf&JmP7+!K%6)6zmgD^a99kfZ zr!wrJZ_Wv9py{daTU!flRpA3GECDqsENOu@I|?zw#AIqe(Hy>KL5d_IB(p%Z&lX9# z)po_p9QQkNd}Lb;x{3-v#nTB`BX!Z0g(;6v0hskZG+?!ZX_@_CgE`Tx}BV=M=e z=q)+ESo!-ORY~AcA<&RIWF8`RWakc9ea76`tgDmZo&FC5YV z8DpOXC52unP2h&AgdxA=dF4xfS^n%#ZkQ1A zYSFM2Y(0{nF+r84;6Q2^Nu#?h``#c>|I()r9-Mh~QaMQqrI3Kzo5+(1cg7WbZVNm1 z(_KiRK}w;8fQ=fcFMS>ux?Ios{ntt5Y$EZoy^=(0)3Kx!#NFpZ3xstja)XB=2He6v z3}UaGaY#~l5@=I(ng7m)w73gtCumi^HCaj^&mg2v!aiqC2C2g9Y_QzYfkIp;RCyp? z*w`aZO?w@OpJmIyJHb8-l0@6=l?U8QpH-nMOOZfr;jYUG18S@C0N_gcEHQHxEAEm& zZQ3Rvg@&ujKT7;-Ldvc$eGY_OCnNbsau+1paWr9lVK+8#AN4|mB~g+z&`Sw$E%59! ziS|;EKwTJiCrI&xDh~uNeG(VADws?pI%kPDP6Fj8fQ$AI2x|&g3eAN=b#EYf^6Yvz zqr?kOxmB@oY1D-TI(0Z%4_wEA0kyOcRIqP1#=>8kFLn8RN&UP`5~)=JrF?baJ|ks{ zeHx?`S_nA%+AMvNVDlh=TRCJRHL@2w7AI`zX~Oo|&H{u7+O%*ewRi19!e25WiCQ60 zW_O>xa>mF|3Q`Ka5U^9XQTiN;@M6Y}L9IX)i91RX$?4;I6Oz6&&+&6tYt>cjrFDFI8loSg!J(oKCZu!%y+ zyKp6wTqIKRPVTQAtmw)$&g9E;3ul_hkLnZ8t5xFo^XP#_ai7@+;y&Se#X%7$eW}9$ z*YQ+-#|+!K6^9E18kXu#1%yln%~LG#P^Gjce?AfEc*Q*!tKVwU20CWZI5dzdKgO00 zj&w)@MIL#&m>4F{6YlYTp}4;;*xo)`aUEkCX~*D!pqqLe=wcorz&=PM@nA#5#~bru zx*yuPhRU6b>sfF{_nc8LeBgy6X2_L?6lw_|(Ce!6)b!ca*kdG#5~1IebYV zRZj{s{GP~hUo4u^?UY0Q|Bf6_91!oGKxk8_sveLIi0n*z-L;FcGm*|re_7)M%2LHi z2oaHWR#6TpyA;Vy_AB{JPHwU)%rHt)%olkIXtVu&2a}u zR~*~ynF0i-dDud$2$adpwbn2VEtWe}0?j-u3_SDxrO>H}x9vx3_79U-F5El)$W$@~ zfo_LuiO^XeX&_lCG;71+cqGpfcSG`BbOKzUO!zU%gG9qXtj?tUmh7FG@b5G`4248P zlt9Nm4*^KjyBbgNgt2BOC2dP*(h8-9E0O5j_gU=(wm_o*7HD6@?aW94In?+d$uqQi zzjRg$pHARlGLhJclZ$Y&QH)is25RpGIuK7Iy!A_}`vCIvg4@e=Hd;fV6~lCB0m&dy ze|zFpq_=+0M%5pX=SF-{&iS|b_~07>ED)uDeL;>72e>d~XAvM#FQB0MyAj?uNzX|T zsAcjz<9W&}KkJEw8H7fi)X;ZYslq!d90Ex+&cjRg@W+wf{>|76)mS7?it`yj>Y7|_Qt|8d7c{}yDwEt0bvL+0jUq`REL6XO5H80$HcKZ zf5G+~sN44T#Mqb0xMF|J8+`saf;k>KIxo4Q`<;Yz?m`NrU-roVksRM6@9zg|T*I~J z_L2`NGMBymVg{JO>G>(@I{?to=x8ix52!}DA?5s-)!K8AsI9{e?{RWHZKUnh` z6XCYcIFbER6lJ!CrYhQ{FkDlC)%iya{xrDgjY`(vq2td@&g>ObEq>52VOIewwx zWqefgIYxUBEBm3TE?4@Sz0)d(b*+pN#F6s?Mz}G)=W$Iz#9P|B#R19RCQbu2L1R9X^cZ~E;FPH~@ ze`O)yzNP(NCC(!eUaB+|2^pud*;151LsFmV$)7`|2bQqUT8c4gjP@8yx1kbe6%SJB zgqtFP4gx(aBLTQTFQR)TqAOTasr`_pI9WL+au8|0;0N|@B;P%WPPj>V;WdJtU4dd$ z2k_F@TGP54^iDL&3z5YSE)WIRCxP`tsen-3h_F7X$A+AHFbPoX^(Y5 zo>!d$DA|Q-Q)E17UT(^9U)0Y8+!b+|UD6{^xv_spzWnFNZkrBbk%}?h5}Mk`hL-N9 z?RIRcY&BS=a+AbQ<@?xta*K&p1xGQJ{Yy@TkGO+l6{oD$7vRimYfA^UY4Eqv=57(S zpRp!+5_#5~Ga^$W$Gt5oe6)7Z6bO`hqI@uc4N4nn(zJGEg5*hRosL70T6gqK*+OCa zY?dC&51#3Uxr;jeIRzE|=Ngg~6=Eiv&7I14!UxX>NV1_!m4ct#ZHKODgQOlK5-5A# z+f1H~(_>k+r?vcv)X1~^g0UCftyAo_5L=4u^7W>|fB&Uij$Vt4DHjIkvFov@a|&Z> z;EXQ!mrA0)So9JoKfN-sz3sviR_Ue+Pt2iEH-g&&#reJDAnRVs)b~u(>kGbZ9_MwX zdYW_Y$#eu|K)b%cNL;rar&F~oA&F(3B>FqNSaq-03rGUEU`eRk66tepX&$HB_JUY= z{-**`r!fhX=XE3!>};D_Abr`Ewan7^I@vax7H2lt719*098JPr!Qc?q&_!F8G6+1c z{H)JoPXNm>lHJ8?J56ctrdm*W!fJh{*+z97s0x=Pkyctwv^7*HWKDGkv+yQ5i7xg? zj();9=ey#1A&Tg2q?K?NkY||dgM^Q^5NL})WJBqzFF7?di}Y}IS@PV+k_L+*rT_$* zGAK^*ImeNXV#%`ydB(H9+7=B00@X*15*;}~$O-cvtM|dGUTpK!N!JA2VuC4H+QTV2 zkqjR23z3FRj+{;uoiGcbj2=s!V>upRmqmwrD8&c~ROOHlL^=+Q(pE}k^e$L39lBQY zm-_X9Jl%j&lPP7HYNYP4H~kj>?5x4{(B}&hsM2Adia6?v>VghY&SP$)A{0N2$^kFC z+qK;o%EyjJQqAj_Q7AVaeZD}T8&%BrS}d^?OsEBqxoSulI~r)v!R+%ZNYg#YY}2c; zZloF1iFv*gYZZAAsC@$c5&81n4L0nIN4aTwaIRFR;P3w26WP{z0xr#gp)GmSk z5Bc&J72i%arlCqTZICG75a)7F_B77GrY;XBT&)Zc@WL-|9I}0WUGeQykhxAPf)T6v z0AL%1=HHV9`o%SpC&&{7x*C<*qT4zPUOk2D7+1fo>-{ zF5UHL&0GGb##0yp$UqF3ENNQ(Sew3C*IHo%-Kf%+#dXUVIa0y@zK#b{UK0}z*oKx3 zl=ms^`x&y1K%gdwbSxG&%${zEJP;`0NRvYSP7r8NB-%5Pr2_&5wt(#KxmdKQ$wc!) zD+qLVfE$M=K-~`aiYs7|PdUOl#Db)-exJ2%TJa`lm`(8%eJ4qTAFe;Y4z#zJEf# z{Ctf+QOY-V)CL%^huckJEU5>UShR831~`+T!Y6@$XPP`&BAGy)k5_%y2v8^O<9y3| zPK171CjbKVE`3r+%EwHTX9eh06DJyH_pr@$Xwp0Y)CX*!ZZJ}O-=ik^#*{50Nt&Dy zsK@C*Y7`5*A`w$hEbQ98>b=r~`CU0ayy5qNP2mVOP-jx;wqmuvEZ5@OHLjn_mjleS zRgg+NTK1T8c-pwZ{GW2&1=H)j&iM#Hg^v@QQ`{5l!%+UM=11fje@pR&59PSSgZ_Pv zRl81}>nd=OlV(c+8XFjhH{(Ld7EVs10m#$c!Nzbrm`F()eZ}LsfoqN{*Haxt27@)% z>$0l)3Pa>I)?sMlx@$fwE3YFKf%rIIjt5kDziNGt?W1D@MM~)4)`sq1|oex+IAfLf=7kCawo{XBKR>HXJ;ST3FPu zN#91T&3FMXeEUEoqg+j&-p9fEO*5X(`SbEM5;zgwzJc%cY}NN-$$X;m0B9N_MxQ%Z z_}olrgfjtt0Vh&APz_lb*OhoUw5mxzCz3k*>oQ@uP)42n?oCqSV2y)k%F^grgX=a9 zbZ50%g?xwDx*$-%KR&7Op3}O$!YinoQ>|1agE4AIa95J&HWj#FW&=t_o2Z~%b< zkw7KWX{>Smx&*2mhsXpsYH&}Ns(;6yF{mj?=Q<0*pSel8{$-ZH0T3wQ3yO6{&oqll z)cM=fx*1h`Z*^7qNzF<|MMI))vL-#Y9e;Gg+dj^AU;qRPc*ZvM^nV$Urc|Z3X~U11 zT7G|N6LnF1g(OR=l67ar@6P#QaStI#kqa*fV@gUAr9(SpRdsKPICczNpQOZXp*dO| zA?0TjLM`;@tf~6T68m$7w^D6_vBr0yhfe8*f5uXLj^ujjv-%Trkf}MK!pb!lPO70B&AV^j{)hE$lxOiZ_PD7 zDmVN-9reGjxdud>Q;;VJ6aa`o|4cvxs;dTXBhddU(A5$EfdUTkuCBCICqqg@n(ng+ z;AAc lAx3S|L#toF1pdDO0{}JK1QOMu11tal002ovPDHLkV1f;i+j0N^ 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 From 26ab5cbd01ee596d50502abfb0346407ceabbfe1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 15:14:46 -0700 Subject: [PATCH 0078/1635] forgot to include --- examples/basics/static/404.html | 7 +++++++ examples/basics/static/welcome.html | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 examples/basics/static/404.html create mode 100644 examples/basics/static/welcome.html diff --git a/examples/basics/static/404.html b/examples/basics/static/404.html new file mode 100644 index 00000000..eda58c30 --- /dev/null +++ b/examples/basics/static/404.html @@ -0,0 +1,7 @@ +actix - basics + + + back to home +

404

+ + diff --git a/examples/basics/static/welcome.html b/examples/basics/static/welcome.html new file mode 100644 index 00000000..b85527fa --- /dev/null +++ b/examples/basics/static/welcome.html @@ -0,0 +1,6 @@ +actix - basics + + +

Welcome

+ + From bc28e54976450a6d552aa1bdd26bffc2ce4f8512 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 19:20:21 -0700 Subject: [PATCH 0079/1635] add homepage link --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c4c04e36..4669e3b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust." readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] -homepage = "https://github.com/actix/actix-web" +homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-web/" categories = ["network-programming", "asynchronous", From d041df6c4b294c035ea7c3f0247bbf65f0e217b4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 19:27:09 -0700 Subject: [PATCH 0080/1635] update links --- Cargo.toml | 2 +- README.md | 20 ++++++++++---------- src/lib.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4669e3b8..c4c04e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust." readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] -homepage = "https://actix.rs" +homepage = "https://github.com/actix/actix-web" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-web/" categories = ["network-programming", "asynchronous", diff --git a/README.md b/README.md index 450240ca..730169d4 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,28 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. -* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.github.io/actix-web/guide/qs_13.html) protocols +* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/actix-web/guide/qs_13.html) protocols * Streaming and pipelining * Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.github.io/actix-web/guide/qs_9.html) support +* Client/server [WebSockets](https://actix.rs/actix-web/guide/qs_9.html) support * Transparent content compression/decompression (br, gzip, deflate) -* Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html) +* Configurable [request routing](https://actix.rs/actix-web/guide/qs_5.html) * Graceful server shutdown * Multipart streams * Static assets * SSL support with OpenSSL or `native-tls` -* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging), - [Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions), +* Middlewares ([Logger](https://actix.rs/actix-web/guide/qs_10.html#logging), + [Session](https://actix.rs/actix-web/guide/qs_10.html#user-sessions), [Redis sessions](https://github.com/actix/actix-redis), - [DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers), - [CORS](https://actix.github.io/actix-web/actix_web/middleware/cors/index.html), - [CSRF](https://actix.github.io/actix-web/actix_web/middleware/csrf/index.html)) + [DefaultHeaders](https://actix.rs/actix-web/guide/qs_10.html#default-headers), + [CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html), + [CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html)) * Built on top of [Actix actor framework](https://github.com/actix/actix) ## Documentation & community resources -* [User Guide](http://actix.github.io/actix-web/guide/) -* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/) +* [User Guide](https://actix.rs/actix-web/guide/) +* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) diff --git a/src/lib.rs b/src/lib.rs index 14b6ae26..60b7c9f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ //! Besides the API documentation (which you are currently looking //! at!), several other resources are available: //! -//! * [User Guide](http://actix.github.io/actix-web/guide/) +//! * [User Guide](https://actix.rs/actix-web/guide/) //! * [Chat on gitter](https://gitter.im/actix/actix) //! * [GitHub repository](https://github.com/actix/actix-web) //! * [Cargo package](https://crates.io/crates/actix-web) From c5702293510f2ae7ccb078ecdcaf53b7aa34270c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Apr 2018 10:49:34 -0700 Subject: [PATCH 0081/1635] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 730169d4..764d7e03 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. ## Documentation & community resources -* [User Guide](https://actix.rs/actix-web/guide/) -* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) +* [User Guide](https://actix.github.io/actix-web/guide/) +* [API Documentation (Development)](https://actix.github.io/actix-web/actix_web/) * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) From 62a9b4c53cd857d9976dbf47b3a08c5256bab29c Mon Sep 17 00:00:00 2001 From: Douman Date: Wed, 11 Apr 2018 22:27:17 +0300 Subject: [PATCH 0082/1635] Rename HttpRequest::without_state into drop_state and make it public --- src/fs.rs | 10 +++++----- src/handler.rs | 4 ++-- src/httprequest.rs | 2 +- src/test.rs | 4 ++-- src/with.rs | 10 +++++----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 73ca6828..495a0510 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -498,18 +498,18 @@ impl Handler for StaticFiles { HttpResponse::Found() .header(header::LOCATION, new_path.as_str()) .finish() - .respond_to(req.without_state()) + .respond_to(req.drop_state()) } else if self.show_index { Directory::new(self.directory.clone(), path) - .respond_to(req.without_state())? - .respond_to(req.without_state()) + .respond_to(req.drop_state())? + .respond_to(req.drop_state()) } else { Ok(self.default.handle(req)) } } else { NamedFile::open(path)?.set_cpu_pool(self.cpu_pool.clone()) - .respond_to(req.without_state())? - .respond_to(req.without_state()) + .respond_to(req.drop_state())? + .respond_to(req.drop_state()) } } } diff --git a/src/handler.rs b/src/handler.rs index edfd1edb..1fc7febd 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -337,7 +337,7 @@ impl RouteHandler for WrapHandler S: 'static, { fn handle(&mut self, req: HttpRequest) -> Reply { - let req2 = req.without_state(); + let req2 = req.drop_state(); match self.h.handle(req).respond_to(req2) { Ok(reply) => reply.into(), Err(err) => Reply::response(err.into()), @@ -378,7 +378,7 @@ impl RouteHandler for AsyncHandler S: 'static, { fn handle(&mut self, req: HttpRequest) -> Reply { - let req2 = req.without_state(); + let req2 = req.drop_state(); let fut = (self.h)(req) .map_err(|e| e.into()) .then(move |r| { diff --git a/src/httprequest.rs b/src/httprequest.rs index 9d8c39b4..b4707f90 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -172,7 +172,7 @@ impl HttpRequest { #[inline] /// Construct new http request without state. - pub(crate) fn without_state(&self) -> HttpRequest { + pub fn drop_state(&self) -> HttpRequest { HttpRequest(self.0.clone(), None, self.2.clone()) } diff --git a/src/test.rs b/src/test.rs index 4e5ed9bd..2a12657c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -559,7 +559,7 @@ impl TestRequest { let req = self.finish(); let resp = h.handle(req.clone()); - match resp.respond_to(req.without_state()) { + match resp.respond_to(req.drop_state()) { Ok(resp) => { match resp.into().into() { ReplyItem::Message(resp) => Ok(resp), @@ -586,7 +586,7 @@ impl TestRequest { let mut core = Core::new().unwrap(); match core.run(fut) { Ok(r) => { - match r.respond_to(req.without_state()) { + match r.respond_to(req.drop_state()) { Ok(reply) => match reply.into().into() { ReplyItem::Message(resp) => Ok(resp), _ => panic!("Nested async replies are not supported"), diff --git a/src/with.rs b/src/with.rs index 5f70db25..5e117225 100644 --- a/src/with.rs +++ b/src/with.rs @@ -134,7 +134,7 @@ impl Future for WithHandlerFut }; let hnd: &mut F = unsafe{&mut *self.hnd.get()}; - let item = match (*hnd)(item).respond_to(self.req.without_state()) { + let item = match (*hnd)(item).respond_to(self.req.drop_state()) { Ok(item) => item.into(), Err(e) => return Err(e.into()), }; @@ -241,7 +241,7 @@ impl Future for WithHandlerFut2 Ok(Async::Ready(item2)) => { let hnd: &mut F = unsafe{&mut *self.hnd.get()}; match (*hnd)(item1, item2) - .respond_to(self.req.without_state()) + .respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { ReplyItem::Message(resp) => @@ -289,7 +289,7 @@ impl Future for WithHandlerFut2 let hnd: &mut F = unsafe{&mut *self.hnd.get()}; let item = match (*hnd)(self.item.take().unwrap(), item) - .respond_to(self.req.without_state()) + .respond_to(self.req.drop_state()) { Ok(item) => item.into(), Err(err) => return Err(err.into()), @@ -417,7 +417,7 @@ impl Future for WithHandlerFut3 Ok(Async::Ready(item3)) => { let hnd: &mut F = unsafe{&mut *self.hnd.get()}; match (*hnd)(item1, item2, item3) - .respond_to(self.req.without_state()) + .respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { ReplyItem::Message(resp) => @@ -488,7 +488,7 @@ impl Future for WithHandlerFut3 let item = match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item) - .respond_to(self.req.without_state()) + .respond_to(self.req.drop_state()) { Ok(item) => item.into(), Err(err) => return Err(err.into()), From d18f9c590531b201ead5c1f77c36a0fcb6c5f475 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Apr 2018 16:11:11 -0700 Subject: [PATCH 0083/1635] add clinet connector stats --- Cargo.toml | 2 +- src/client/connector.rs | 72 ++++++++++++++++++++++++++++++++++++----- src/client/mod.rs | 2 +- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c4c04e36..8c663f58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.5.0" +version = "0.5.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust." readme = "README.md" diff --git a/src/client/connector.rs b/src/client/connector.rs index 30eccd2f..af433be2 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -6,7 +6,8 @@ use std::time::{Duration, Instant}; use std::collections::{HashMap, VecDeque}; use actix::{fut, Actor, ActorFuture, Arbiter, Context, AsyncContext, - Handler, Message, ActorResponse, Supervised, ContextFutureSpawner}; + Recipient, Syn, Handler, Message, ActorResponse, + Supervised, ContextFutureSpawner}; use actix::registry::ArbiterService; use actix::fut::WrapFuture; use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect}; @@ -31,6 +32,15 @@ use tokio_tls::TlsConnectorExt; use {HAS_OPENSSL, HAS_TLS}; use server::IoStream; +/// Client connector usage stats +#[derive(Default, Message)] +pub struct ClientConnectorStats { + pub waits: usize, + pub reused: usize, + pub opened: usize, + pub closed: usize, + pub errors: usize, +} #[derive(Debug)] /// `Connect` type represents a message that can be sent to @@ -160,6 +170,9 @@ pub struct ClientConnector { #[cfg(all(feature="tls", not(feature="alpn")))] connector: TlsConnector, + stats: ClientConnectorStats, + subscriber: Option>, + pool: Rc, pool_modified: Rc>, @@ -202,6 +215,8 @@ impl Default for ClientConnector { { let builder = TlsConnector::builder().unwrap(); ClientConnector { + stats: ClientConnectorStats::default(), + subscriber: None, pool: Rc::new(Pool::new(Rc::clone(&_modified))), pool_modified: _modified, connector: builder.build().unwrap(), @@ -220,7 +235,9 @@ impl Default for ClientConnector { } #[cfg(not(any(feature="alpn", feature="tls")))] - ClientConnector {pool: Rc::new(Pool::new(Rc::clone(&_modified))), + ClientConnector {stats: ClientConnectorStats::default(), + subscriber: None, + pool: Rc::new(Pool::new(Rc::clone(&_modified))), pool_modified: _modified, conn_lifetime: Duration::from_secs(15), conn_keep_alive: Duration::from_secs(75), @@ -286,6 +303,8 @@ impl ClientConnector { let modified = Rc::new(Cell::new(false)); ClientConnector { connector, + stats: ClientConnectorStats::default(), + subscriber: None, pool: Rc::new(Pool::new(Rc::clone(&modified))), pool_modified: modified, conn_lifetime: Duration::from_secs(15), @@ -339,6 +358,12 @@ impl ClientConnector { self } + /// Subscribe for connector stats. Only one subscriber is supported. + pub fn stats(mut self, subs: Recipient) -> Self { + self.subscriber = Some(subs); + self + } + fn acquire(&mut self, key: &Key) -> Acquire { // check limits if self.limit > 0 { @@ -372,6 +397,7 @@ impl ClientConnector { if (now - conn.0) > self.conn_keep_alive || (now - conn.1.ts) > self.conn_lifetime { + self.stats.closed += 1; self.to_close.push(conn.1); } else { let mut conn = conn.1; @@ -379,6 +405,7 @@ impl ClientConnector { match conn.stream().read(&mut buf) { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), Ok(n) if n > 0 => { + self.stats.closed += 1; self.to_close.push(conn); continue }, @@ -433,6 +460,7 @@ impl ClientConnector { for conn in to_close { self.release_key(&conn.key); self.to_close.push(conn); + self.stats.closed += 1; } } @@ -459,6 +487,7 @@ impl ClientConnector { { let conn = conns.pop_front().unwrap().1; self.to_close.push(conn); + self.stats.closed += 1; } else { break } @@ -485,6 +514,12 @@ impl ClientConnector { self.collect(true); // re-schedule next collect period ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); + + // send stats + let stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); + if let Some(ref mut subscr) = self.subscriber { + let _ = subscr.do_send(stats); + } } fn collect_waiters(&mut self) { @@ -609,6 +644,7 @@ impl Handler for ClientConnector { // check pause state if self.paused.is_some() { let rx = self.wait_for(key, wait_timeout, conn_timeout); + self.stats.waits += 1; return ActorResponse::async( rx.map_err(|_| ClientConnectorError::Disconnected) .into_actor(self) @@ -616,7 +652,6 @@ impl Handler for ClientConnector { Ok(conn) => fut::ok(conn), Err(err) => fut::err(err), })); - } // acquire connection @@ -625,11 +660,13 @@ impl Handler for ClientConnector { Acquire::Acquired(mut conn) => { // use existing connection conn.pool = Some(AcquiredConn(key, Some(Rc::clone(&self.pool)))); + self.stats.reused += 1; return ActorResponse::async(fut::ok(conn)) }, Acquire::NotAvailable => { // connection is not available, wait let rx = self.wait_for(key, wait_timeout, conn_timeout); + self.stats.waits += 1; return ActorResponse::async( rx.map_err(|_| ClientConnectorError::Disconnected) .into_actor(self) @@ -654,11 +691,15 @@ impl Handler for ClientConnector { .timeout(conn_timeout)) .into_actor(self) .map_err(|_, _, _| ClientConnectorError::Disconnected) - .and_then(move |res, _act, _| { + .and_then(move |res, act, _| { #[cfg(feature="alpn")] match res { - Err(err) => fut::Either::B(fut::err(err.into())), + Err(err) => { + act.stats.opened += 1; + fut::Either::B(fut::err(err.into())) + }, Ok(stream) => { + act.stats.opened += 1; if proto.is_secure() { fut::Either::A( _act.connector.connect_async(&conn.0.host, stream) @@ -676,8 +717,12 @@ impl Handler for ClientConnector { #[cfg(all(feature="tls", not(feature="alpn")))] match res { - Err(err) => fut::Either::B(fut::err(err.into())), + Err(err) => { + act.stats.opened += 1; + fut::Either::B(fut::err(err.into())) + }, Ok(stream) => { + act.stats.opened += 1; if proto.is_secure() { fut::Either::A( _act.connector.connect_async(&conn.0.host, stream) @@ -695,8 +740,12 @@ impl Handler for ClientConnector { #[cfg(not(any(feature="alpn", feature="tls")))] match res { - Err(err) => fut::err(err.into()), + Err(err) => { + act.stats.opened += 1; + fut::err(err.into()) + }, Ok(stream) => { + act.stats.opened += 1; if proto.is_secure() { fut::err(ClientConnectorError::SslIsNotSupported) } else { @@ -746,6 +795,7 @@ impl fut::ActorFuture for Maintenance match act.acquire(key) { Acquire::Acquired(mut conn) => { // use existing connection + act.stats.reused += 1; conn.pool = Some( AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)))); let _ = waiter.tx.send(Ok(conn)); @@ -763,14 +813,16 @@ impl fut::ActorFuture for Maintenance .send(ResolveConnect::host_and_port(&conn.0.host, conn.0.port) .timeout(waiter.conn_timeout))) .map_err(|_, _, _| ()) - .and_then(move |res, _act, _| { + .and_then(move |res, act, _| { #[cfg(feature="alpn")] match res { Err(err) => { + act.stats.errors += 1; let _ = waiter.tx.send(Err(err.into())); fut::Either::B(fut::err(())) }, Ok(stream) => { + act.stats.opened += 1; if conn.0.ssl { fut::Either::A( _act.connector.connect_async(&key.host, stream) @@ -801,10 +853,12 @@ impl fut::ActorFuture for Maintenance #[cfg(all(feature="tls", not(feature="alpn")))] match res { Err(err) => { + act.stats.errors += 1; let _ = waiter.tx.send(Err(err.into())); fut::Either::B(fut::err(())) }, Ok(stream) => { + act.stats.opened += 1; if conn.0.ssl { fut::Either::A( _act.connector.connect_async(&conn.0.host, stream) @@ -835,10 +889,12 @@ impl fut::ActorFuture for Maintenance #[cfg(not(any(feature="alpn", feature="tls")))] match res { Err(err) => { + act.stats.errors += 1; let _ = waiter.tx.send(Err(err.into())); fut::err(()) }, Ok(stream) => { + act.stats.opened += 1; if conn.0.ssl { let _ = waiter.tx.send( Err(ClientConnectorError::SslIsNotSupported)); diff --git a/src/client/mod.rs b/src/client/mod.rs index afe4e459..4608e6a9 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -38,7 +38,7 @@ pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; pub use self::connector::{ Connect, Pause, Resume, - Connection, ClientConnector, ClientConnectorError}; + Connection, ClientConnector, ClientConnectorError, ClientConnectorStats}; pub(crate) use self::writer::HttpClientWriter; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; From b5179577614024685e8a95a707cb7728ce5b83f4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Apr 2018 16:34:01 -0700 Subject: [PATCH 0084/1635] fix stats for tls and alpn features --- src/client/connector.rs | 14 +++++++------- src/httprequest.rs | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index af433be2..4993f17f 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -702,11 +702,11 @@ impl Handler for ClientConnector { act.stats.opened += 1; if proto.is_secure() { fut::Either::A( - _act.connector.connect_async(&conn.0.host, stream) + act.connector.connect_async(&conn.0.host, stream) .map_err(ClientConnectorError::SslError) .map(|stream| Connection::new( conn.0.clone(), Some(conn), Box::new(stream))) - .into_actor(_act)) + .into_actor(act)) } else { fut::Either::B(fut::ok( Connection::new( @@ -725,11 +725,11 @@ impl Handler for ClientConnector { act.stats.opened += 1; if proto.is_secure() { fut::Either::A( - _act.connector.connect_async(&conn.0.host, stream) + act.connector.connect_async(&conn.0.host, stream) .map_err(ClientConnectorError::SslError) .map(|stream| Connection::new( conn.0.clone(), Some(conn), Box::new(stream))) - .into_actor(_act)) + .into_actor(act)) } else { fut::Either::B(fut::ok( Connection::new( @@ -825,7 +825,7 @@ impl fut::ActorFuture for Maintenance act.stats.opened += 1; if conn.0.ssl { fut::Either::A( - _act.connector.connect_async(&key.host, stream) + act.connector.connect_async(&key.host, stream) .then(move |res| { match res { Err(e) => { @@ -861,7 +861,7 @@ impl fut::ActorFuture for Maintenance act.stats.opened += 1; if conn.0.ssl { fut::Either::A( - _act.connector.connect_async(&conn.0.host, stream) + act.connector.connect_async(&conn.0.host, stream) .then(|res| { match res { Err(e) => { @@ -877,7 +877,7 @@ impl fut::ActorFuture for Maintenance } Ok(()) }) - .into_actor(_act)) + .into_actor(act)) } else { let _ = waiter.tx.send(Ok(Connection::new( conn.0.clone(), Some(conn), Box::new(stream)))); diff --git a/src/httprequest.rs b/src/httprequest.rs index b4707f90..90345d05 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -555,8 +555,9 @@ impl AsyncRead for HttpRequest {} impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!(f, "\nHttpRequest {:?} {}:{}", - self.as_ref().version, self.as_ref().method, self.path_decoded()); + let res = writeln!( + f, "\nHttpRequest {:?} {}:{}", + self.as_ref().version, self.as_ref().method, self.path_decoded()); if !self.query_string().is_empty() { let _ = writeln!(f, " query: ?{:?}", self.query_string()); } From 839d67ac6a6753e4268a13bd1b11dfd194a96167 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Apr 2018 16:46:21 -0700 Subject: [PATCH 0085/1635] migration to 0.5 --- CHANGES.md | 4 ++-- MIGRATION-0.4-0.5.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 MIGRATION-0.4-0.5.md diff --git a/CHANGES.md b/CHANGES.md index cdac5fa0..3c300894 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,8 @@ * Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()` +* Added `signed` and `private` `CookieSessionBackend`s + * Added `HttpRequest::resource()`, returns current matched resource * Added `ErrorHandlers` middleware @@ -25,8 +27,6 @@ * Fix prefix and static file serving #168 -* Add `signed` and `private` `CookieSessionBackend`s - ## 0.4.10 (2018-03-20) diff --git a/MIGRATION-0.4-0.5.md b/MIGRATION-0.4-0.5.md new file mode 100644 index 00000000..1ba32f71 --- /dev/null +++ b/MIGRATION-0.4-0.5.md @@ -0,0 +1,30 @@ +# Migration from 0.4 to 0.5 + +* `HttpResponseBuilder::body()`, `.finish()`, `.json()` + methods return `HttpResponse` instead of `Result` + +* `actix_web::Method`, `actix_web::StatusCode`, actix_web::Version` + moved to `actix_web::http` module + +* `actix_web::header` moved to `actix_web::http::header` + +* `NormalizePath` moved to `actix_web::http` module + +* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new` function + same as `actix_web::server::HttpServer::new` + +* `DefaultHeaders` middleware does not use seprate builder + +* `StaticFiles::new()`'s show_index removed, use `show_files_listing` method instead. + +* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type + +* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()` and other fn + should be used instead + +* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` + instead of `http::Error` + +* `Application` renamed to a `App` + +* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` From 0e3820afdffc63316ada8bb86e04e5662b6b721b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Apr 2018 16:49:45 -0700 Subject: [PATCH 0086/1635] Update MIGRATION-0.4-0.5.md --- MIGRATION-0.4-0.5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATION-0.4-0.5.md b/MIGRATION-0.4-0.5.md index 1ba32f71..457696bd 100644 --- a/MIGRATION-0.4-0.5.md +++ b/MIGRATION-0.4-0.5.md @@ -3,7 +3,7 @@ * `HttpResponseBuilder::body()`, `.finish()`, `.json()` methods return `HttpResponse` instead of `Result` -* `actix_web::Method`, `actix_web::StatusCode`, actix_web::Version` +* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` moved to `actix_web::http` module * `actix_web::header` moved to `actix_web::http::header` From 0624f9b9d9ef780a02cbaa2cdd033fe2e6106ad4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Apr 2018 16:53:27 -0700 Subject: [PATCH 0087/1635] Update MIGRATION-0.4-0.5.md --- MIGRATION-0.4-0.5.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MIGRATION-0.4-0.5.md b/MIGRATION-0.4-0.5.md index 457696bd..d618e054 100644 --- a/MIGRATION-0.4-0.5.md +++ b/MIGRATION-0.4-0.5.md @@ -10,20 +10,20 @@ * `NormalizePath` moved to `actix_web::http` module -* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new` function - same as `actix_web::server::HttpServer::new` +* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, + shortcut for `actix_web::server::HttpServer::new()` -* `DefaultHeaders` middleware does not use seprate builder +* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself -* `StaticFiles::new()`'s show_index removed, use `show_files_listing` method instead. +* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. * `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type -* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()` and other fn - should be used instead +* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` + functions should be used instead * `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` - instead of `http::Error` + instead of `Result<_, http::Error>` * `Application` renamed to a `App` From 35e68723df6be34a98b706d2c77cf0adf8da6123 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Apr 2018 19:05:14 -0700 Subject: [PATCH 0088/1635] use older mdbook --- .travis.yml | 2 +- CHANGES.md | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f27a445a..1c3fe7e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,7 +79,7 @@ after_success: if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then cargo doc --features "alpn, tls, session" --no-deps && echo "" > target/doc/index.html && - curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.5/mdbook-v0.1.5-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin && + curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.3/mdbook-v0.1.3-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin && cd guide && mdbook build -d ../target/doc/guide && cd .. && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/CHANGES.md b/CHANGES.md index 3c300894..72fcf9c7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ # Changes -## 0.5.0 + +## 0.5.1 (2018-xx-xx) + +* Client connector provides stats, `ClientConnector::stats()` + + +## 0.5.0 (2018-04-10) * Type-safe path/query/form parameter handling, using serde #70 From 72bc1546c484ea9419c3a913c9de217ed3b2aeaa Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Thu, 12 Apr 2018 09:47:32 +0200 Subject: [PATCH 0089/1635] fix end-of-stream handling in parse_payload parse_payload can be called with a pre-filled buf. In this case, it's totaly fine for read_from_io to return sync::Ready(0) while buf is not empty. This is not an PayloadError::Incomplete. So, move the check for PayloadError::Incomplete down to the decoding code: If the decoder is not ready, but the input stream is finished, PayloadError::Incomplete will be returned. --- src/client/parser.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index e0c49406..6feb0cc7 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -81,16 +81,11 @@ impl HttpResponseParser { if self.decoder.is_some() { loop { // read payload - let not_ready = match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - if buf.is_empty() { - return Err(PayloadError::Incomplete) - } - true - } + let (not_ready, stream_finished) = match utils::read_from_io(io, buf) { + Ok(Async::Ready(0)) => (false, true), Err(err) => return Err(err.into()), - Ok(Async::NotReady) => true, - _ => false, + Ok(Async::NotReady) => (true, false), + _ => (false, false), }; match self.decoder.as_mut().unwrap().decode(buf) { @@ -104,6 +99,9 @@ impl HttpResponseParser { if not_ready { return Ok(Async::NotReady) } + if stream_finished { + return Err(PayloadError::Incomplete) + } } Err(err) => return Err(err.into()), } From 83168731fc5e265f61dd25265a6ac11634c28175 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Apr 2018 09:54:35 -0700 Subject: [PATCH 0090/1635] update user guide content compression section --- .travis.yml | 2 +- CHANGES.md | 4 +++- Cargo.toml | 2 +- guide/src/qs_7.md | 37 ++++++++++++++++++++++++++++++++++++- src/application.rs | 2 +- 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1c3fe7e3..76352ddf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,7 +79,7 @@ after_success: if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then cargo doc --features "alpn, tls, session" --no-deps && echo "" > target/doc/index.html && - curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.3/mdbook-v0.1.3-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin && + curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.2/mdbook-v0.1.2-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin && cd guide && mdbook build -d ../target/doc/guide && cd .. && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/CHANGES.md b/CHANGES.md index 72fcf9c7..07a65504 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,12 @@ # Changes -## 0.5.1 (2018-xx-xx) +## 0.5.1 (2018-04-xx) * Client connector provides stats, `ClientConnector::stats()` +* Fix end-of-stream handling in parse_payload #173 + ## 0.5.0 (2018-04-10) diff --git a/Cargo.toml b/Cargo.toml index 8c663f58..9bafefee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "actix-web" version = "0.5.1" authors = ["Nikolay Kim "] -description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust." +description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://github.com/actix/actix-web" diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 3e869451..b07a25d6 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -37,7 +37,8 @@ Actix automatically *compresses*/*decompresses* payloads. The following codecs a * Identity If request headers contain a `Content-Encoding` header, the request payload is decompressed -according to the header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. +according to the header value. Multiple codecs are not supported, +i.e: `Content-Encoding: br, gzip`. Response payload is compressed based on the *content_encoding* parameter. By default, `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected, @@ -60,6 +61,40 @@ fn index(req: HttpRequest) -> HttpResponse { # fn main() {} ``` +In this case we explicitly disable content compression +by setting content encoding to a `Identity` value: + +```rust +# extern crate actix_web; +use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding}; + +fn index(req: HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_encoding(ContentEncoding::Identity) // <- disable compression + .body("data") +} +# fn main() {} +``` + +Also it is possible to set default content encoding on application level, by +default `ContentEncoding::Auto` is used, which implies automatic content compression +negotiation. + +```rust +# extern crate actix_web; +use actix_web::{App, HttpRequest, HttpResponse, http::ContentEncoding}; + +fn index(req: HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .body("data") +} +fn main() { + let app = App::new() + .default_encoding(ContentEncoding::Identity) // <- disable compression for all routes + .resource("/index.html", |r| r.with(index)); +} +``` + ## JSON Request There are several options for json body deserialization. diff --git a/src/application.rs b/src/application.rs index db0e9d81..d2f67343 100644 --- a/src/application.rs +++ b/src/application.rs @@ -350,7 +350,7 @@ impl App where S: 'static { } /// Set default content encoding. `ContentEncoding::Auto` is set by default. - pub fn default_encoding(mut self, encoding: ContentEncoding) -> App + pub fn default_encoding(mut self, encoding: ContentEncoding) -> App { { let parts = self.parts.as_mut().expect("Use after finish"); From 0b01884fca14a7b9a460c69a87cb550acb345dea Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Apr 2018 13:08:13 -0700 Subject: [PATCH 0091/1635] add timeouts stats to client connector --- src/client/connector.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 4993f17f..0b6e63bd 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -40,6 +40,7 @@ pub struct ClientConnectorStats { pub opened: usize, pub closed: usize, pub errors: usize, + pub timeouts: usize, } #[derive(Debug)] @@ -307,8 +308,8 @@ impl ClientConnector { subscriber: None, pool: Rc::new(Pool::new(Rc::clone(&modified))), pool_modified: modified, - conn_lifetime: Duration::from_secs(15), - conn_keep_alive: Duration::from_secs(75), + conn_lifetime: Duration::from_secs(75), + conn_keep_alive: Duration::from_secs(15), limit: 100, limit_per_host: 0, acquired: 0, @@ -530,6 +531,7 @@ impl ClientConnector { let mut idx = 0; while idx < waiters.len() { if waiters[idx].wait <= now { + self.stats.timeouts += 1; let waiter = waiters.swap_remove_back(idx).unwrap(); let _ = waiter.tx.send(Err(ClientConnectorError::Timeout)); } else { From 2ca0ea70c406736d9612c5075f2feac5fefad4bc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Apr 2018 15:50:20 -0700 Subject: [PATCH 0092/1635] use one default cpu pool for StaticFiles #174 --- CHANGES.md | 2 ++ src/fs.rs | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 07a65504..fd954fc9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,8 @@ * Fix end-of-stream handling in parse_payload #173 +* Fix StaticFiles generate a lot of threads #174 + ## 0.5.0 (2018-04-10) diff --git a/src/fs.rs b/src/fs.rs index 495a0510..0f6120b2 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -6,6 +6,7 @@ use std::fs::{File, DirEntry, Metadata}; use std::path::{Path, PathBuf}; use std::ops::{Deref, DerefMut}; use std::time::{SystemTime, UNIX_EPOCH}; +use std::sync::Mutex; #[cfg(unix)] use std::os::unix::fs::MetadataExt; @@ -409,6 +410,10 @@ pub struct StaticFiles { _follow_symlinks: bool, } +lazy_static!{ + static ref DEFAULT_CPUPOOL: Mutex = Mutex::new(CpuPool::new(20)); +} + impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory. @@ -430,12 +435,17 @@ impl StaticFiles { } }; + // use default CpuPool + let pool = { + DEFAULT_CPUPOOL.lock().unwrap().clone() + }; + StaticFiles { directory: dir, accessible: access, index: None, show_index: false, - cpu_pool: CpuPool::new(40), + cpu_pool: pool, default: Box::new(WrapHandler::new( |_| HttpResponse::new(StatusCode::NOT_FOUND))), _chunk_size: 0, From 94c5bb5cddf95c700b680f9f1bc5a15741029bb5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Apr 2018 15:55:15 -0700 Subject: [PATCH 0093/1635] add helper method for returning inner value --- src/extractor.rs | 7 +++++++ src/json.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/extractor.rs b/src/extractor.rs index 1fc6c078..e88998c7 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -211,6 +211,13 @@ impl FromRequest for Query /// ``` pub struct Form(pub T); +impl Form { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.0 + } +} + impl Deref for Form { type Target = T; diff --git a/src/json.rs b/src/json.rs index 977c8d18..646a0111 100644 --- a/src/json.rs +++ b/src/json.rs @@ -22,6 +22,13 @@ use httpresponse::HttpResponse; /// and second is for extracting typed information from request's payload. pub struct Json(pub T); +impl Json { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.0 + } +} + impl Deref for Json { type Target = T; From c5b18c6d3073698b3530dcc2ef364a5ad233364a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Apr 2018 16:03:22 -0700 Subject: [PATCH 0094/1635] prepare release --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fd954fc9..60b6aaf9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## 0.5.1 (2018-04-xx) +## 0.5.1 (2018-04-12) * Client connector provides stats, `ClientConnector::stats()` From e05aba65de7cd24771e7a160098f6f0031600d5c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Apr 2018 20:31:58 -0700 Subject: [PATCH 0095/1635] examples moved to separate repo --- .travis.yml | 18 -- Cargo.toml | 3 +- README.md | 22 +- examples/basics/Cargo.toml | 11 - examples/basics/README.md | 20 -- examples/basics/src/main.rs | 136 ------------ examples/basics/static/404.html | 7 - examples/basics/static/welcome.html | 6 - examples/diesel/.env | 1 - examples/diesel/Cargo.toml | 20 -- examples/diesel/README.md | 43 ---- .../20170124012402_create_users/down.sql | 1 - .../20170124012402_create_users/up.sql | 4 - examples/diesel/src/db.rs | 55 ----- examples/diesel/src/main.rs | 78 ------- examples/diesel/src/models.rs | 14 -- examples/diesel/src/schema.rs | 6 - examples/diesel/test.db | Bin 20480 -> 0 bytes examples/hello-world/Cargo.toml | 10 - examples/hello-world/src/main.rs | 28 --- examples/http-proxy/Cargo.toml | 11 - examples/http-proxy/src/main.rs | 58 ----- examples/json/Cargo.toml | 18 -- examples/json/README.md | 48 ---- examples/json/client.py | 18 -- examples/json/src/main.rs | 110 --------- examples/juniper/Cargo.toml | 17 -- examples/juniper/README.md | 15 -- examples/juniper/src/main.rs | 108 --------- examples/juniper/src/schema.rs | 58 ----- examples/multipart/Cargo.toml | 15 -- examples/multipart/README.md | 24 -- examples/multipart/client.py | 34 --- examples/multipart/src/main.rs | 61 ----- examples/protobuf/Cargo.toml | 16 -- examples/protobuf/client.py | 66 ------ examples/protobuf/src/main.rs | 57 ----- examples/protobuf/src/protobuf.rs | 168 -------------- examples/protobuf/test.proto | 6 - examples/protobuf/test_pb2.py | 76 ------- examples/r2d2/Cargo.toml | 20 -- examples/r2d2/src/db.rs | 41 ---- examples/r2d2/src/main.rs | 65 ------ examples/r2d2/test.db | Bin 20480 -> 0 bytes examples/redis-session/Cargo.toml | 11 - examples/redis-session/src/main.rs | 48 ---- examples/state/Cargo.toml | 11 - examples/state/README.md | 15 -- examples/state/src/main.rs | 77 ------- examples/static/actixLogo.png | Bin 13131 -> 0 bytes examples/static/favicon.ico | Bin 1150 -> 0 bytes examples/static/index.html | 90 -------- examples/template_tera/Cargo.toml | 11 - examples/template_tera/README.md | 17 -- examples/template_tera/src/main.rs | 48 ---- examples/template_tera/templates/index.html | 17 -- examples/template_tera/templates/user.html | 13 -- examples/tls/Cargo.toml | 15 -- examples/tls/README.md | 16 -- examples/tls/cert.pem | 31 --- examples/tls/key.pem | 51 ----- examples/tls/src/main.rs | 49 ---- examples/unix-socket/Cargo.toml | 10 - examples/unix-socket/README.md | 14 -- examples/unix-socket/src/main.rs | 32 --- examples/web-cors/README.md | 15 -- examples/web-cors/backend/.gitignore | 4 - examples/web-cors/backend/Cargo.toml | 17 -- examples/web-cors/backend/src/main.rs | 43 ---- examples/web-cors/backend/src/user.rs | 19 -- examples/web-cors/frontend/.babelrc | 3 - examples/web-cors/frontend/.gitignore | 14 -- examples/web-cors/frontend/index.html | 13 -- examples/web-cors/frontend/package.json | 22 -- examples/web-cors/frontend/src/app.vue | 145 ------------ examples/web-cors/frontend/src/main.js | 11 - examples/websocket-chat/Cargo.toml | 29 --- examples/websocket-chat/README.md | 32 --- examples/websocket-chat/client.py | 72 ------ examples/websocket-chat/src/client.rs | 153 ------------- examples/websocket-chat/src/codec.rs | 123 ----------- examples/websocket-chat/src/main.rs | 209 ------------------ examples/websocket-chat/src/server.rs | 197 ----------------- examples/websocket-chat/src/session.rs | 207 ----------------- examples/websocket-chat/static/websocket.html | 90 -------- examples/websocket/Cargo.toml | 20 -- examples/websocket/README.md | 27 --- examples/websocket/src/client.rs | 113 ---------- examples/websocket/src/main.rs | 66 ------ examples/websocket/websocket-client.py | 72 ------ guide/src/qs_1.md | 6 +- 91 files changed, 15 insertions(+), 3876 deletions(-) delete mode 100644 examples/basics/Cargo.toml delete mode 100644 examples/basics/README.md delete mode 100644 examples/basics/src/main.rs delete mode 100644 examples/basics/static/404.html delete mode 100644 examples/basics/static/welcome.html delete mode 100644 examples/diesel/.env delete mode 100644 examples/diesel/Cargo.toml delete mode 100644 examples/diesel/README.md delete mode 100644 examples/diesel/migrations/20170124012402_create_users/down.sql delete mode 100644 examples/diesel/migrations/20170124012402_create_users/up.sql delete mode 100644 examples/diesel/src/db.rs delete mode 100644 examples/diesel/src/main.rs delete mode 100644 examples/diesel/src/models.rs delete mode 100644 examples/diesel/src/schema.rs delete mode 100644 examples/diesel/test.db delete mode 100644 examples/hello-world/Cargo.toml delete mode 100644 examples/hello-world/src/main.rs delete mode 100644 examples/http-proxy/Cargo.toml delete mode 100644 examples/http-proxy/src/main.rs delete mode 100644 examples/json/Cargo.toml delete mode 100644 examples/json/README.md delete mode 100644 examples/json/client.py delete mode 100644 examples/json/src/main.rs delete mode 100644 examples/juniper/Cargo.toml delete mode 100644 examples/juniper/README.md delete mode 100644 examples/juniper/src/main.rs delete mode 100644 examples/juniper/src/schema.rs delete mode 100644 examples/multipart/Cargo.toml delete mode 100644 examples/multipart/README.md delete mode 100644 examples/multipart/client.py delete mode 100644 examples/multipart/src/main.rs delete mode 100644 examples/protobuf/Cargo.toml delete mode 100644 examples/protobuf/client.py delete mode 100644 examples/protobuf/src/main.rs delete mode 100644 examples/protobuf/src/protobuf.rs delete mode 100644 examples/protobuf/test.proto delete mode 100644 examples/protobuf/test_pb2.py delete mode 100644 examples/r2d2/Cargo.toml delete mode 100644 examples/r2d2/src/db.rs delete mode 100644 examples/r2d2/src/main.rs delete mode 100644 examples/r2d2/test.db delete mode 100644 examples/redis-session/Cargo.toml delete mode 100644 examples/redis-session/src/main.rs delete mode 100644 examples/state/Cargo.toml delete mode 100644 examples/state/README.md delete mode 100644 examples/state/src/main.rs delete mode 100644 examples/static/actixLogo.png delete mode 100644 examples/static/favicon.ico delete mode 100644 examples/static/index.html delete mode 100644 examples/template_tera/Cargo.toml delete mode 100644 examples/template_tera/README.md delete mode 100644 examples/template_tera/src/main.rs delete mode 100644 examples/template_tera/templates/index.html delete mode 100644 examples/template_tera/templates/user.html delete mode 100644 examples/tls/Cargo.toml delete mode 100644 examples/tls/README.md delete mode 100644 examples/tls/cert.pem delete mode 100644 examples/tls/key.pem delete mode 100644 examples/tls/src/main.rs delete mode 100644 examples/unix-socket/Cargo.toml delete mode 100644 examples/unix-socket/README.md delete mode 100644 examples/unix-socket/src/main.rs delete mode 100644 examples/web-cors/README.md delete mode 100644 examples/web-cors/backend/.gitignore delete mode 100644 examples/web-cors/backend/Cargo.toml delete mode 100644 examples/web-cors/backend/src/main.rs delete mode 100644 examples/web-cors/backend/src/user.rs delete mode 100644 examples/web-cors/frontend/.babelrc delete mode 100644 examples/web-cors/frontend/.gitignore delete mode 100644 examples/web-cors/frontend/index.html delete mode 100644 examples/web-cors/frontend/package.json delete mode 100644 examples/web-cors/frontend/src/app.vue delete mode 100644 examples/web-cors/frontend/src/main.js delete mode 100644 examples/websocket-chat/Cargo.toml delete mode 100644 examples/websocket-chat/README.md delete mode 100755 examples/websocket-chat/client.py delete mode 100644 examples/websocket-chat/src/client.rs delete mode 100644 examples/websocket-chat/src/codec.rs delete mode 100644 examples/websocket-chat/src/main.rs delete mode 100644 examples/websocket-chat/src/server.rs delete mode 100644 examples/websocket-chat/src/session.rs delete mode 100644 examples/websocket-chat/static/websocket.html delete mode 100644 examples/websocket/Cargo.toml delete mode 100644 examples/websocket/README.md delete mode 100644 examples/websocket/src/client.rs delete mode 100644 examples/websocket/src/main.rs delete mode 100755 examples/websocket/websocket-client.py diff --git a/.travis.yml b/.travis.yml index 76352ddf..90804412 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,24 +50,6 @@ script: # --features=alpn fi - - | - if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cd examples/basics && cargo check && cd ../.. - cd examples/hello-world && cargo check && cd ../.. - cd examples/http-proxy && cargo check && cd ../.. - cd examples/multipart && cargo check && cd ../.. - cd examples/json && cargo check && cd ../.. - cd examples/juniper && cargo check && cd ../.. - cd examples/protobuf && cargo check && cd ../.. - cd examples/state && cargo check && cd ../.. - cd examples/template_tera && cargo check && cd ../.. - cd examples/diesel && cargo check && cd ../.. - cd examples/r2d2 && cargo check && cd ../.. - cd examples/tls && cargo check && cd ../.. - cd examples/websocket-chat && cargo check && cd ../.. - cd examples/websocket && cargo check && cd ../.. - cd examples/unix-socket && cargo check && cd ../.. - fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then cargo clippy diff --git a/Cargo.toml b/Cargo.toml index 9bafefee..8654846d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,7 @@ categories = ["network-programming", "asynchronous", "web-programming::http-client", "web-programming::websocket"] license = "MIT/Apache-2.0" -exclude = [".gitignore", ".travis.yml", ".cargo/config", - "appveyor.yml", "/examples/**"] +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] build = "build.rs" [badges] diff --git a/README.md b/README.md index 764d7e03..ae070fac 100644 --- a/README.md +++ b/README.md @@ -50,19 +50,19 @@ fn main() { ### More examples -* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/) -* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/) -* [Protobuf support](https://github.com/actix/actix-web/tree/master/examples/protobuf/) -* [Multipart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/) -* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/) -* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/) -* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/) -* [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/) -* [Json](https://github.com/actix/actix-web/tree/master/examples/json/) +* [Basics](https://github.com/actix/examples/tree/master/basics/) +* [Stateful](https://github.com/actix/examples/tree/master/state/) +* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) +* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) +* [Simple websocket session](https://github.com/actix/examples/tree/master/websocket/) +* [Tera templates](https://github.com/actix/examples/tree/master/template_tera/) +* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) +* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) +* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) +* [Json](https://github.com/actix/examples/tree/master/json/) You may consider checking out -[this directory](https://github.com/actix/actix-web/tree/master/examples) for more examples. +[this directory](https://github.com/actix/examples/tree/master/) for more examples. ## Benchmarks diff --git a/examples/basics/Cargo.toml b/examples/basics/Cargo.toml deleted file mode 100644 index 294075d4..00000000 --- a/examples/basics/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "basics" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[dependencies] -futures = "0.1" -env_logger = "0.5" -actix = "0.5" -actix-web = { path="../.." } diff --git a/examples/basics/README.md b/examples/basics/README.md deleted file mode 100644 index 82e35e06..00000000 --- a/examples/basics/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# basics - -## Usage - -### server - -```bash -cd actix-web/examples/basics -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) -- [http://localhost:8080/static/notexit](http://localhost:8080/static/notexit) display 404 page diff --git a/examples/basics/src/main.rs b/examples/basics/src/main.rs deleted file mode 100644 index 633f4823..00000000 --- a/examples/basics/src/main.rs +++ /dev/null @@ -1,136 +0,0 @@ -#![allow(unused_variables)] -#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))] - -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; -use futures::Stream; - -use std::{io, env}; -use actix_web::{error, fs, pred, server, - App, HttpRequest, HttpResponse, Result, Error}; -use actix_web::http::{header, Method, StatusCode}; -use actix_web::middleware::{self, RequestSession}; -use futures::future::{FutureResult, result}; - -/// favicon handler -fn favicon(req: HttpRequest) -> Result { - Ok(fs::NamedFile::open("../static/favicon.ico")?) -} - -/// simple index handler -fn index(mut req: HttpRequest) -> Result { - println!("{:?}", req); - - // example of ... - if let Ok(ch) = req.poll() { - if let futures::Async::Ready(Some(d)) = ch { - println!("{}", String::from_utf8_lossy(d.as_ref())); - } - } - - // session - let mut counter = 1; - if let Some(count) = req.session().get::("counter")? { - println!("SESSION value: {}", count); - counter = count + 1; - req.session().set("counter", counter)?; - } else { - req.session().set("counter", counter)?; - } - - - // response - Ok(HttpResponse::build(StatusCode::OK) - .content_type("text/html; charset=utf-8") - .body(include_str!("../static/welcome.html"))) - -} - -/// 404 handler -fn p404(req: HttpRequest) -> Result { - Ok(fs::NamedFile::open("./static/404.html")? - .set_status_code(StatusCode::NOT_FOUND)) -} - - -/// async handler -fn index_async(req: HttpRequest) -> FutureResult -{ - println!("{:?}", req); - - result(Ok(HttpResponse::Ok() - .content_type("text/html") - .body(format!("Hello {}!", req.match_info().get("name").unwrap())))) -} - -/// handler with path parameters like `/user/{name}/` -fn with_param(req: HttpRequest) -> HttpResponse -{ - println!("{:?}", req); - - HttpResponse::Ok() - .content_type("test/plain") - .body(format!("Hello {}!", req.match_info().get("name").unwrap())) -} - -fn main() { - env::set_var("RUST_LOG", "actix_web=debug"); - env::set_var("RUST_BACKTRACE", "1"); - env_logger::init(); - let sys = actix::System::new("basic-example"); - - let addr = server::new( - || App::new() - // enable logger - .middleware(middleware::Logger::default()) - // cookie session middleware - .middleware(middleware::SessionStorage::new( - middleware::CookieSessionBackend::signed(&[0; 32]).secure(false) - )) - // 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 - .resource("/user/{name}/", |r| r.method(Method::GET).f(with_param)) - // async handler - .resource("/async/{name}", |r| r.method(Method::GET).a(index_async)) - .resource("/test", |r| r.f(|req| { - match *req.method() { - Method::GET => HttpResponse::Ok(), - Method::POST => HttpResponse::MethodNotAllowed(), - _ => HttpResponse::NotFound(), - } - })) - .resource("/error.html", |r| r.f(|req| { - error::InternalError::new( - io::Error::new(io::ErrorKind::Other, "test"), StatusCode::OK) - })) - // static files - .handler("/static/", fs::StaticFiles::new("../static/")) - // redirect - .resource("/", |r| r.method(Method::GET).f(|req| { - println!("{:?}", req); - HttpResponse::Found() - .header(header::LOCATION, "/index.html") - .finish() - })) - // default - .default_resource(|r| { - // 404 for GET request - r.method(Method::GET).f(p404); - - // all requests that are not `GET` - r.route().filter(pred::Not(pred::Get())).f( - |req| HttpResponse::MethodNotAllowed()); - })) - - .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"); - let _ = sys.run(); -} diff --git a/examples/basics/static/404.html b/examples/basics/static/404.html deleted file mode 100644 index eda58c30..00000000 --- a/examples/basics/static/404.html +++ /dev/null @@ -1,7 +0,0 @@ -actix - basics - - - back to home -

404

- - diff --git a/examples/basics/static/welcome.html b/examples/basics/static/welcome.html deleted file mode 100644 index b85527fa..00000000 --- a/examples/basics/static/welcome.html +++ /dev/null @@ -1,6 +0,0 @@ -actix - basics - - -

Welcome

- - diff --git a/examples/diesel/.env b/examples/diesel/.env deleted file mode 100644 index 1fbc5af7..00000000 --- a/examples/diesel/.env +++ /dev/null @@ -1 +0,0 @@ -DATABASE_URL=file:test.db diff --git a/examples/diesel/Cargo.toml b/examples/diesel/Cargo.toml deleted file mode 100644 index 2551b962..00000000 --- a/examples/diesel/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "diesel-example" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[dependencies] -env_logger = "0.5" -actix = "0.5" -actix-web = { path = "../../" } - -futures = "0.1" -uuid = { version = "0.5", features = ["serde", "v4"] } -serde = "1.0" -serde_json = "1.0" -serde_derive = "1.0" - -diesel = { version = "^1.1.0", features = ["sqlite", "r2d2"] } -r2d2 = "0.8" -dotenv = "0.10" diff --git a/examples/diesel/README.md b/examples/diesel/README.md deleted file mode 100644 index 922ba1e3..00000000 --- a/examples/diesel/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# diesel - -Diesel's `Getting Started` guide using SQLite for Actix web - -## Usage - -### init database sqlite - -```bash -cargo install diesel_cli --no-default-features --features sqlite -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/migrations/20170124012402_create_users/down.sql b/examples/diesel/migrations/20170124012402_create_users/down.sql deleted file mode 100644 index 9951735c..00000000 --- a/examples/diesel/migrations/20170124012402_create_users/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE users diff --git a/examples/diesel/migrations/20170124012402_create_users/up.sql b/examples/diesel/migrations/20170124012402_create_users/up.sql deleted file mode 100644 index d88d44fb..00000000 --- a/examples/diesel/migrations/20170124012402_create_users/up.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE users ( - id VARCHAR NOT NULL PRIMARY KEY, - name VARCHAR NOT NULL -) diff --git a/examples/diesel/src/db.rs b/examples/diesel/src/db.rs deleted file mode 100644 index 78806c27..00000000 --- a/examples/diesel/src/db.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Db executor actor -use uuid; -use diesel; -use actix_web::*; -use actix::prelude::*; -use diesel::prelude::*; -use diesel::r2d2::{Pool, ConnectionManager}; - -use models; -use schema; - -/// This is db executor actor. We are going to run 3 of them in parallel. -pub struct DbExecutor(pub Pool>); - -/// 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 Message for CreateUser { - type Result = Result; -} - -impl Actor for DbExecutor { - type Context = SyncContext; -} - -impl Handler for DbExecutor { - type Result = Result; - - fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result { - use self::schema::users::dsl::*; - - let uuid = format!("{}", uuid::Uuid::new_v4()); - let new_user = models::NewUser { - id: &uuid, - name: &msg.name, - }; - - let conn: &SqliteConnection = &self.0.get().unwrap(); - - diesel::insert_into(users) - .values(&new_user) - .execute(conn) - .expect("Error inserting person"); - - let mut items = users - .filter(id.eq(&uuid)) - .load::(conn) - .expect("Error loading person"); - - Ok(items.pop().unwrap()) - } -} diff --git a/examples/diesel/src/main.rs b/examples/diesel/src/main.rs deleted file mode 100644 index 2fd7087c..00000000 --- a/examples/diesel/src/main.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! 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 use diesel. -//! Technically sync actors are worker style actors, multiple of them -//! can run in parallel 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 r2d2; -extern crate uuid; -extern crate futures; -extern crate actix; -extern crate actix_web; -extern crate env_logger; - -use actix::prelude::*; -use actix_web::{http, server, middleware, - App, Path, State, HttpResponse, AsyncResponder, FutureResponse}; - -use diesel::prelude::*; -use diesel::r2d2::{ Pool, ConnectionManager }; -use futures::future::Future; - -mod db; -mod models; -mod schema; - -use db::{CreateUser, DbExecutor}; - - -/// State with DbExecutor address -struct AppState { - db: Addr, -} - -/// Async request handler -fn index(name: Path, state: State) -> FutureResponse { - // send async `CreateUser` message to a `DbExecutor` - state.db.send(CreateUser{name: name.into_inner()}) - .from_err() - .and_then(|res| { - match res { - Ok(user) => Ok(HttpResponse::Ok().json(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()) - } - }) - .responder() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("diesel-example"); - - // Start 3 db executor actors - let manager = ConnectionManager::::new("test.db"); - let pool = r2d2::Pool::builder().build(manager).expect("Failed to create pool."); - - let addr = SyncArbiter::start(3, move || { - DbExecutor(pool.clone()) - }); - - // Start http server - server::new(move || { - App::with_state(AppState{db: addr.clone()}) - // enable logger - .middleware(middleware::Logger::default()) - .resource("/{name}", |r| r.method(http::Method::GET).with2(index))}) - .bind("127.0.0.1:8080").unwrap() - .start(); - - 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 deleted file mode 100644 index 315d59f1..00000000 --- a/examples/diesel/src/models.rs +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 51aa40b8..00000000 --- a/examples/diesel/src/schema.rs +++ /dev/null @@ -1,6 +0,0 @@ -table! { - users (id) { - id -> Text, - name -> Text, - } -} diff --git a/examples/diesel/test.db b/examples/diesel/test.db deleted file mode 100644 index 65e590a6e5f8f16622eee5da4c64c5a18f7b3423..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI%!A{gb7zgm_b`c?AiwDyfFLN+)H&E=f-EB)^Vuh;21+layoSM>3VH0*YZVT~> z2VR7`K8zRjAv~LSakfDMAx$`NQ1hR3I@8XV>3qNR(&^6I{-ESEA5Vr!NlmgyB#Atu zln^p=UPV)thB!CR`_o3c)UWH#kd?(>3(8N@Y^{^lH|4Cg-uhG*jQbFP00bZa0SG_< z0uX=z1R(Ht3mnL^s;WvSPs(KPkRKI%QdFnrTHt%3Pebo{->20r+McI$kkNNuu=dIe z=+>K%Zbkh*-3~T3y$S4`|YeDm!PVv8v#RGwA0Je!isNj+3w{_E=>Z=m@o=y|Ny@=^RMd|&uB^X4j<%0Q%3`iR zD{go7&gG0Q(p;V#jbafOZfyEHp|`nxF+$h<7hcp4=~@&7{#F=YgmiWqchr5aF6sJZ z#jJiz7H`zu>07lRs-%1;;y{4_1Rwwb2tWV=5P$##AOHafK;WMfcqGXk)6ki%GsCK? zF}>25p)r^0`l@cM>TF)*B`H6MI8Yz}0SG_<0uX=z1Rwwb2tWV=5cn?y?#Z3Gt6Kuo z|NpXbOq4ImnP^ZT009U<00Izz00bZa0SG_<0uZ=0fhAdv?z^eu8cx^J^4xVaE8&*r zxQ?l%Y2MwlvR(vYKvSvSp`ZpJj1Cx&LF%+lP%N;K1AJ>E4^vzO}0Xj~rf z$&w@{=PcuyZR?utnzm+fwx>C)=V-ZYr)J7b*UGEOr~m(D<&-F=%4g;4ttE(wAOHaf qKmY;|fB*y_009U<00I!WCeW56=_cC&@-*-!TLF#7ax{07J%HZ}Gw{Cv diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml deleted file mode 100644 index 156a1ada..00000000 --- a/examples/hello-world/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "hello-world" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[dependencies] -env_logger = "0.5" -actix = "0.5" -actix-web = { path = "../../" } diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs deleted file mode 100644 index 2af47894..00000000 --- a/examples/hello-world/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; - -use actix_web::{App, HttpRequest, server, middleware}; - - -fn index(_req: HttpRequest) -> &'static str { - "Hello world!" -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("hello-world"); - - server::new( - || App::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(); -} diff --git a/examples/http-proxy/Cargo.toml b/examples/http-proxy/Cargo.toml deleted file mode 100644 index 7b9597bf..00000000 --- a/examples/http-proxy/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "http-proxy" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[dependencies] -env_logger = "0.5" -futures = "0.1" -actix = "0.5" -actix-web = { path = "../../", features=["alpn"] } diff --git a/examples/http-proxy/src/main.rs b/examples/http-proxy/src/main.rs deleted file mode 100644 index 0a392ed8..00000000 --- a/examples/http-proxy/src/main.rs +++ /dev/null @@ -1,58 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate futures; -extern crate env_logger; - -use futures::{Future, Stream}; -use actix_web::{ - client, server, middleware, - App, AsyncResponder, Body, HttpRequest, HttpResponse, HttpMessage, Error}; - - -/// Stream client request response and then send body to a server response -fn index(_req: HttpRequest) -> Box> { - client::ClientRequest::get("https://www.rust-lang.org/en-US/") - .finish().unwrap() - .send() - .map_err(Error::from) // <- convert SendRequestError to an Error - .and_then( - |resp| resp.body() // <- this is MessageBody type, resolves to complete body - .from_err() // <- convert PayloadError to a Error - .and_then(|body| { // <- we got complete body, now send as server response - Ok(HttpResponse::Ok().body(body)) - })) - .responder() -} - -/// streaming client request to a streaming server response -fn streaming(_req: HttpRequest) -> Box> { - // send client request - client::ClientRequest::get("https://www.rust-lang.org/en-US/") - .finish().unwrap() - .send() // <- connect to host and send request - .map_err(Error::from) // <- convert SendRequestError to an Error - .and_then(|resp| { // <- we received client response - Ok(HttpResponse::Ok() - // read one chunk from client response and send this chunk to a server response - // .from_err() converts PayloadError to a Error - .body(Body::Streaming(Box::new(resp.from_err())))) - }) - .responder() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("http-proxy"); - - server::new( - || App::new() - .middleware(middleware::Logger::default()) - .resource("/streaming", |r| r.f(streaming)) - .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(); -} diff --git a/examples/json/Cargo.toml b/examples/json/Cargo.toml deleted file mode 100644 index bf117c70..00000000 --- a/examples/json/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "json-example" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[dependencies] -bytes = "0.4" -futures = "0.1" -env_logger = "*" - -serde = "1.0" -serde_json = "1.0" -serde_derive = "1.0" -json = "*" - -actix = "0.5" -actix-web = { path="../../" } diff --git a/examples/json/README.md b/examples/json/README.md deleted file mode 100644 index 167c3909..00000000 --- a/examples/json/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# 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 -``` - -### web 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}``) - -### 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 deleted file mode 100644 index e89ffe09..00000000 --- a/examples/json/client.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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)) - print(await resp.text()) - 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 deleted file mode 100644 index f864e008..00000000 --- a/examples/json/src/main.rs +++ /dev/null @@ -1,110 +0,0 @@ -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; -#[macro_use] extern crate json; - -use actix_web::{ - middleware, http, error, server, - App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error, Json}; - -use bytes::BytesMut; -use futures::{Future, Stream}; -use json::JsonValue; - -#[derive(Debug, Serialize, Deserialize)] -struct MyObj { - name: String, - number: i32, -} - -/// This handler uses `HttpRequest::json()` for loading json object. -fn index(req: HttpRequest) -> Box> { - req.json() - .from_err() // convert all errors into `Error` - .and_then(|val: MyObj| { - println!("model: {:?}", val); - Ok(HttpResponse::Ok().json(val)) // <- send response - }) - .responder() -} - -/// This handler uses json extractor -fn extract_item(item: Json) -> HttpResponse { - println!("model: {:?}", &item); - HttpResponse::Ok().json(item.0) // <- send response -} - -const MAX_SIZE: usize = 262_144; // max payload size is 256k - -/// This handler manually load request payload and parse json object -fn index_manual(req: HttpRequest) -> Box> { - // HttpRequest is stream of Bytes objects - req - // `Future::from_err` acts like `?` in that it coerces the error type from - // the future into the final error type - .from_err() - - // `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 serde-json - let obj = serde_json::from_slice::(&body)?; - Ok(HttpResponse::Ok().json(obj)) // <- send response - }) - .responder() -} - -/// This handler manually load request payload and parse json-rust -fn index_mjsonrust(req: HttpRequest) -> Box> { - req.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::Ok() - .content_type("application/json") - .body(injson.dump())) - }) - .responder() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("json-example"); - - server::new(|| { - App::new() - // enable logger - .middleware(middleware::Logger::default()) - .resource("/extractor", |r| { - r.method(http::Method::POST) - .with(extract_item) - .limit(4096); // <- limit size of the payload - }) - .resource("/manual", |r| r.method(http::Method::POST).f(index_manual)) - .resource("/mjsonrust", |r| r.method(http::Method::POST).f(index_mjsonrust)) - .resource("/", |r| r.method(http::Method::POST).f(index))}) - .bind("127.0.0.1:8080").unwrap() - .shutdown_timeout(1) - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); -} diff --git a/examples/juniper/Cargo.toml b/examples/juniper/Cargo.toml deleted file mode 100644 index 9e52b0a8..00000000 --- a/examples/juniper/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "juniper-example" -version = "0.1.0" -authors = ["pyros2097 "] -workspace = "../.." - -[dependencies] -env_logger = "0.5" -actix = "0.5" -actix-web = { path = "../../" } - -futures = "0.1" -serde = "1.0" -serde_json = "1.0" -serde_derive = "1.0" - -juniper = "0.9.2" diff --git a/examples/juniper/README.md b/examples/juniper/README.md deleted file mode 100644 index 2ac0eac4..00000000 --- a/examples/juniper/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# juniper - -Juniper integration for Actix web - -### server - -```bash -cd actix-web/examples/juniper -cargo run (or ``cargo watch -x run``) -# Started http server: 127.0.0.1:8080 -``` - -### web client - -[http://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql) diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs deleted file mode 100644 index a92ce3fb..00000000 --- a/examples/juniper/src/main.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Actix web juniper example -//! -//! A simple example integrating juniper in actix-web -extern crate serde; -extern crate serde_json; -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate juniper; -extern crate futures; -extern crate actix; -extern crate actix_web; -extern crate env_logger; - -use actix::prelude::*; -use actix_web::{ - middleware, http, server, - App, AsyncResponder, HttpRequest, HttpResponse, FutureResponse, Error, State, Json}; -use juniper::http::graphiql::graphiql_source; -use juniper::http::GraphQLRequest; -use futures::future::Future; - -mod schema; - -use schema::Schema; -use schema::create_schema; - -struct AppState { - executor: Addr, -} - -#[derive(Serialize, Deserialize)] -pub struct GraphQLData(GraphQLRequest); - -impl Message for GraphQLData { - type Result = Result; -} - -pub struct GraphQLExecutor { - schema: std::sync::Arc -} - -impl GraphQLExecutor { - fn new(schema: std::sync::Arc) -> GraphQLExecutor { - GraphQLExecutor { - schema: schema, - } - } -} - -impl Actor for GraphQLExecutor { - type Context = SyncContext; -} - -impl Handler for GraphQLExecutor { - type Result = Result; - - fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result { - let res = msg.0.execute(&self.schema, &()); - let res_text = serde_json::to_string(&res)?; - Ok(res_text) - } -} - -fn graphiql(_req: HttpRequest) -> Result { - let html = graphiql_source("http://127.0.0.1:8080/graphql"); - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) -} - -fn graphql(st: State, data: Json) -> FutureResponse { - st.executor.send(data.0) - .from_err() - .and_then(|res| { - match res { - Ok(user) => Ok(HttpResponse::Ok() - .content_type("application/json") - .body(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()) - } - }) - .responder() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("juniper-example"); - - let schema = std::sync::Arc::new(create_schema()); - let addr = SyncArbiter::start(3, move || { - GraphQLExecutor::new(schema.clone()) - }); - - // Start http server - server::new(move || { - App::with_state(AppState{executor: addr.clone()}) - // enable logger - .middleware(middleware::Logger::default()) - .resource("/graphql", |r| r.method(http::Method::POST).with2(graphql)) - .resource("/graphiql", |r| r.method(http::Method::GET).h(graphiql))}) - .bind("127.0.0.1:8080").unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); -} diff --git a/examples/juniper/src/schema.rs b/examples/juniper/src/schema.rs deleted file mode 100644 index 2b4cf304..00000000 --- a/examples/juniper/src/schema.rs +++ /dev/null @@ -1,58 +0,0 @@ -use juniper::FieldResult; -use juniper::RootNode; - -#[derive(GraphQLEnum)] -enum Episode { - NewHope, - Empire, - Jedi, -} - -#[derive(GraphQLObject)] -#[graphql(description = "A humanoid creature in the Star Wars universe")] -struct Human { - id: String, - name: String, - appears_in: Vec, - home_planet: String, -} - -#[derive(GraphQLInputObject)] -#[graphql(description = "A humanoid creature in the Star Wars universe")] -struct NewHuman { - name: String, - appears_in: Vec, - home_planet: String, -} - -pub struct QueryRoot; - -graphql_object!(QueryRoot: () |&self| { - field human(&executor, id: String) -> FieldResult { - Ok(Human{ - id: "1234".to_owned(), - name: "Luke".to_owned(), - appears_in: vec![Episode::NewHope], - home_planet: "Mars".to_owned(), - }) - } -}); - -pub struct MutationRoot; - -graphql_object!(MutationRoot: () |&self| { - field createHuman(&executor, new_human: NewHuman) -> FieldResult { - Ok(Human{ - id: "1234".to_owned(), - name: new_human.name, - appears_in: new_human.appears_in, - home_planet: new_human.home_planet, - }) - } -}); - -pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; - -pub fn create_schema() -> Schema { - Schema::new(QueryRoot {}, MutationRoot {}) -} diff --git a/examples/multipart/Cargo.toml b/examples/multipart/Cargo.toml deleted file mode 100644 index b5235d7e..00000000 --- a/examples/multipart/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "multipart-example" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[[bin]] -name = "multipart" -path = "src/main.rs" - -[dependencies] -env_logger = "*" -futures = "0.1" -actix = "0.5" -actix-web = { path="../../" } diff --git a/examples/multipart/README.md b/examples/multipart/README.md deleted file mode 100644 index 348d2868..00000000 --- a/examples/multipart/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# 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/client.py b/examples/multipart/client.py deleted file mode 100644 index afc07f17..00000000 --- a/examples/multipart/client.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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') - writer.append_json({'passed': True}) - - resp = await aiohttp.ClientSession().request( - "post", 'http://localhost:8080/multipart', - data=writer, headers=writer.headers) - print(resp) - assert 200 == resp.status - - -async def req2(): - with aiohttp.MultipartWriter() as writer: - writer.append('test') - writer.append_json({'passed': True}) - writer.append(open('src/main.rs')) - - resp = await aiohttp.ClientSession().request( - "post", 'http://localhost:8080/multipart', - data=writer, headers=writer.headers) - print(resp) - assert 200 == resp.status - - -loop = asyncio.get_event_loop() -loop.run_until_complete(req1()) -loop.run_until_complete(req2()) diff --git a/examples/multipart/src/main.rs b/examples/multipart/src/main.rs deleted file mode 100644 index 75f28963..00000000 --- a/examples/multipart/src/main.rs +++ /dev/null @@ -1,61 +0,0 @@ -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; - -use actix::*; -use actix_web::{ - http, middleware, multipart, server, - App, AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error}; - -use futures::{Future, Stream}; -use futures::future::{result, Either}; - - -fn index(req: HttpRequest) -> Box> -{ - println!("{:?}", req); - - 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(()))) - } - } - }) - .finish() // <- Stream::finish() combinator from actix - .map(|_| HttpResponse::Ok().into()) - .responder() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); - let sys = actix::System::new("multipart-example"); - - server::new( - || App::new() - .middleware(middleware::Logger::default()) // <- logger - .resource("/multipart", |r| r.method(http::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/examples/protobuf/Cargo.toml b/examples/protobuf/Cargo.toml deleted file mode 100644 index 3bb56869..00000000 --- a/examples/protobuf/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "protobuf-example" -version = "0.1.0" -authors = ["kingxsp "] - -[dependencies] -bytes = "0.4" -futures = "0.1" -failure = "0.1" -env_logger = "*" - -prost = "0.2.0" -prost-derive = "0.2.0" - -actix = "0.5" -actix-web = { path="../../" } diff --git a/examples/protobuf/client.py b/examples/protobuf/client.py deleted file mode 100644 index ab91365d..00000000 --- a/examples/protobuf/client.py +++ /dev/null @@ -1,66 +0,0 @@ -# just start server and run client.py - -# wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-python-3.5.1.zip -# unzip protobuf-python-3.5.1.zip.1 -# cd protobuf-3.5.1/python/ -# python3.6 setup.py install - -# pip3.6 install --upgrade pip -# pip3.6 install aiohttp - -#!/usr/bin/env python -import test_pb2 -import traceback -import sys - -import asyncio -import aiohttp - -def op(): - try: - obj = test_pb2.MyObj() - obj.number = 9 - obj.name = 'USB' - - #Serialize - sendDataStr = obj.SerializeToString() - #print serialized string value - print('serialized string:', sendDataStr) - #------------------------# - # message transmission # - #------------------------# - receiveDataStr = sendDataStr - receiveData = test_pb2.MyObj() - - #Deserialize - receiveData.ParseFromString(receiveDataStr) - print('pares serialize string, return: devId = ', receiveData.number, ', name = ', receiveData.name) - except(Exception, e): - print(Exception, ':', e) - print(traceback.print_exc()) - errInfo = sys.exc_info() - print(errInfo[0], ':', errInfo[1]) - - -async def fetch(session): - obj = test_pb2.MyObj() - obj.number = 9 - obj.name = 'USB' - async with session.post('http://localhost:8080/', data=obj.SerializeToString(), - headers={"content-type": "application/protobuf"}) as resp: - print(resp.status) - data = await resp.read() - receiveObj = test_pb2.MyObj() - receiveObj.ParseFromString(data) - print(receiveObj) - -async def go(loop): - obj = test_pb2.MyObj() - obj.number = 9 - obj.name = 'USB' - async with aiohttp.ClientSession(loop=loop) as session: - await fetch(session) - -loop = asyncio.get_event_loop() -loop.run_until_complete(go(loop)) -loop.close() \ No newline at end of file diff --git a/examples/protobuf/src/main.rs b/examples/protobuf/src/main.rs deleted file mode 100644 index c0a2abb3..00000000 --- a/examples/protobuf/src/main.rs +++ /dev/null @@ -1,57 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -#[macro_use] -extern crate failure; -extern crate env_logger; -extern crate prost; -#[macro_use] -extern crate prost_derive; - -use futures::Future; -use actix_web::{ - http, middleware, server, - App, AsyncResponder, HttpRequest, HttpResponse, Error}; - -mod protobuf; -use protobuf::ProtoBufResponseBuilder; - - -#[derive(Clone, Debug, PartialEq, Message)] -pub struct MyObj { - #[prost(int32, tag="1")] - pub number: i32, - #[prost(string, tag="2")] - pub name: String, -} - - -/// This handler uses `ProtoBufMessage` for loading protobuf object. -fn index(req: HttpRequest) -> Box> { - protobuf::ProtoBufMessage::new(req) - .from_err() // convert all errors into `Error` - .and_then(|val: MyObj| { - println!("model: {:?}", val); - Ok(HttpResponse::Ok().protobuf(val)?) // <- send response - }) - .responder() -} - - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("protobuf-example"); - - server::new(|| { - App::new() - .middleware(middleware::Logger::default()) - .resource("/", |r| r.method(http::Method::POST).f(index))}) - .bind("127.0.0.1:8080").unwrap() - .shutdown_timeout(1) - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); -} diff --git a/examples/protobuf/src/protobuf.rs b/examples/protobuf/src/protobuf.rs deleted file mode 100644 index 2b117fe7..00000000 --- a/examples/protobuf/src/protobuf.rs +++ /dev/null @@ -1,168 +0,0 @@ -use bytes::{Bytes, BytesMut}; -use futures::{Poll, Future, Stream}; - -use bytes::IntoBuf; -use prost::Message; -use prost::DecodeError as ProtoBufDecodeError; -use prost::EncodeError as ProtoBufEncodeError; - -use actix_web::http::header::{CONTENT_TYPE, CONTENT_LENGTH}; -use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse}; -use actix_web::dev::HttpResponseBuilder; -use actix_web::error::{Error, PayloadError, ResponseError}; - - -#[derive(Fail, Debug)] -pub enum ProtoBufPayloadError { - /// Payload size is bigger than 256k - #[fail(display="Payload size is bigger than 256k")] - Overflow, - /// Content type error - #[fail(display="Content type error")] - ContentType, - /// Serialize error - #[fail(display="ProtoBud serialize error: {}", _0)] - Serialize(#[cause] ProtoBufEncodeError), - /// Deserialize error - #[fail(display="ProtoBud deserialize error: {}", _0)] - Deserialize(#[cause] ProtoBufDecodeError), - /// Payload error - #[fail(display="Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), -} - -impl ResponseError for ProtoBufPayloadError { - - fn error_response(&self) -> HttpResponse { - match *self { - ProtoBufPayloadError::Overflow => HttpResponse::PayloadTooLarge().into(), - _ => HttpResponse::BadRequest().into(), - } - } -} - -impl From for ProtoBufPayloadError { - fn from(err: PayloadError) -> ProtoBufPayloadError { - ProtoBufPayloadError::Payload(err) - } -} - -impl From for ProtoBufPayloadError { - fn from(err: ProtoBufDecodeError) -> ProtoBufPayloadError { - ProtoBufPayloadError::Deserialize(err) - } -} - -#[derive(Debug)] -pub struct ProtoBuf(pub T); - -impl Responder for ProtoBuf { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: HttpRequest) -> Result { - let mut buf = Vec::new(); - self.0.encode(&mut buf) - .map_err(|e| Error::from(ProtoBufPayloadError::Serialize(e))) - .and_then(|()| { - Ok(HttpResponse::Ok() - .content_type("application/protobuf") - .body(buf) - .into()) - }) - } -} - -pub struct ProtoBufMessage{ - limit: usize, - ct: &'static str, - req: Option, - fut: Option>>, -} - -impl ProtoBufMessage { - - /// Create `ProtoBufMessage` for request. - pub fn new(req: T) -> Self { - ProtoBufMessage{ - limit: 262_144, - req: Some(req), - fut: None, - ct: "application/protobuf", - } - } - - /// 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/protobuf* 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 ProtoBufMessage - where T: HttpMessage + Stream + 'static -{ - type Item = U; - type Error = ProtoBufPayloadError; - - fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if let Some(len) = req.headers().get(CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(ProtoBufPayloadError::Overflow); - } - } else { - return Err(ProtoBufPayloadError::Overflow); - } - } - } - // check content-type - if !self.ct.is_empty() && req.content_type() != self.ct { - return Err(ProtoBufPayloadError::ContentType) - } - - let limit = self.limit; - let fut = req.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(ProtoBufPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(|body| Ok(::decode(&mut body.into_buf())?)); - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("ProtoBufBody could not be used second time").poll() - } -} - - -pub trait ProtoBufResponseBuilder { - - fn protobuf(&mut self, value: T) -> Result; -} - -impl ProtoBufResponseBuilder for HttpResponseBuilder { - - fn protobuf(&mut self, value: T) -> Result { - self.header(CONTENT_TYPE, "application/protobuf"); - - let mut body = Vec::new(); - value.encode(&mut body).map_err(|e| ProtoBufPayloadError::Serialize(e))?; - Ok(self.body(body)) - } -} diff --git a/examples/protobuf/test.proto b/examples/protobuf/test.proto deleted file mode 100644 index 8ec278ca..00000000 --- a/examples/protobuf/test.proto +++ /dev/null @@ -1,6 +0,0 @@ -syntax = "proto3"; - -message MyObj { - int32 number = 1; - string name = 2; -} \ No newline at end of file diff --git a/examples/protobuf/test_pb2.py b/examples/protobuf/test_pb2.py deleted file mode 100644 index 05e71f3a..00000000 --- a/examples/protobuf/test_pb2.py +++ /dev/null @@ -1,76 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: test.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='test.proto', - package='', - syntax='proto3', - serialized_pb=_b('\n\ntest.proto\"%\n\x05MyObj\x12\x0e\n\x06number\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\tb\x06proto3') -) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - - - - -_MYOBJ = _descriptor.Descriptor( - name='MyObj', - full_name='MyObj', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='number', full_name='MyObj.number', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='name', full_name='MyObj.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=14, - serialized_end=51, -) - -DESCRIPTOR.message_types_by_name['MyObj'] = _MYOBJ - -MyObj = _reflection.GeneratedProtocolMessageType('MyObj', (_message.Message,), dict( - DESCRIPTOR = _MYOBJ, - __module__ = 'test_pb2' - # @@protoc_insertion_point(class_scope:MyObj) - )) -_sym_db.RegisterMessage(MyObj) - - -# @@protoc_insertion_point(module_scope) diff --git a/examples/r2d2/Cargo.toml b/examples/r2d2/Cargo.toml deleted file mode 100644 index ab9590a4..00000000 --- a/examples/r2d2/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "r2d2-example" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[dependencies] -env_logger = "0.5" -actix = "0.5" -actix-web = { path = "../../" } - -futures = "0.1" -uuid = { version = "0.5", features = ["serde", "v4"] } -serde = "1.0" -serde_json = "1.0" -serde_derive = "1.0" - -r2d2 = "*" -r2d2_sqlite = "*" -rusqlite = "*" diff --git a/examples/r2d2/src/db.rs b/examples/r2d2/src/db.rs deleted file mode 100644 index 6e2ddc09..00000000 --- a/examples/r2d2/src/db.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Db executor actor -use std::io; -use uuid; -use actix_web::*; -use actix::prelude::*; -use r2d2::Pool; -use r2d2_sqlite::SqliteConnectionManager; - - -/// This is db executor actor. We are going to run 3 of them in parallel. -pub struct DbExecutor(pub Pool); - -/// 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 Message for CreateUser { - type Result = Result; -} - -impl Actor for DbExecutor { - type Context = SyncContext; -} - -impl Handler for DbExecutor { - type Result = Result; - - fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result { - let conn = self.0.get().unwrap(); - - let uuid = format!("{}", uuid::Uuid::new_v4()); - conn.execute("INSERT INTO users (id, name) VALUES ($1, $2)", - &[&uuid, &msg.name]).unwrap(); - - Ok(conn.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| { - row.get(0) - }).map_err(|_| io::Error::new(io::ErrorKind::Other, "db error"))?) - } -} diff --git a/examples/r2d2/src/main.rs b/examples/r2d2/src/main.rs deleted file mode 100644 index 5e6d07f8..00000000 --- a/examples/r2d2/src/main.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Actix web r2d2 example -extern crate serde; -extern crate serde_json; -extern crate uuid; -extern crate futures; -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate r2d2; -extern crate r2d2_sqlite; -extern crate rusqlite; - -use actix::prelude::*; -use actix_web::{ - middleware, http, server, App, AsyncResponder, HttpRequest, HttpResponse, Error}; -use futures::future::Future; -use r2d2_sqlite::SqliteConnectionManager; - -mod db; -use db::{CreateUser, DbExecutor}; - - -/// State with DbExecutor address -struct State { - db: Addr, -} - -/// Async request handler -fn index(req: HttpRequest) -> Box> { - let name = &req.match_info()["name"]; - - req.state().db.send(CreateUser{name: name.to_owned()}) - .from_err() - .and_then(|res| { - match res { - Ok(user) => Ok(HttpResponse::Ok().json(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()) - } - }) - .responder() -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=debug"); - env_logger::init(); - let sys = actix::System::new("r2d2-example"); - - // r2d2 pool - let manager = SqliteConnectionManager::file("test.db"); - let pool = r2d2::Pool::new(manager).unwrap(); - - // Start db executor actors - let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone())); - - // Start http server - server::new(move || { - App::with_state(State{db: addr.clone()}) - // enable logger - .middleware(middleware::Logger::default()) - .resource("/{name}", |r| r.method(http::Method::GET).a(index))}) - .bind("127.0.0.1:8080").unwrap() - .start(); - - let _ = sys.run(); -} diff --git a/examples/r2d2/test.db b/examples/r2d2/test.db deleted file mode 100644 index 3ea0c83d772f543f8555d66d0e2ddeeddaf4a252..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI%PjAyO7zXg9=?ZM3fdi@#9DG1x)iA_~^A8D$uoal-26Qdb4k&UQhgI4-(RLHp zao~&W4j+aK@FC!e#04kPCJ-v>k)g+`n>cxz+Q0UaH(SrU!>J<0{&-JJiDz6gOw+he zh+!D#bzji^q}B9J{bZoG<}drRcF!BPFa5Y^e>dvQHKY02K5nix-_Hu;I0PU70SG_< z0uX=z1Rwwb2>jauJJz{Yt7RVDnTl*z9Zb}CQoYqzd!3};A^qe*w?nE!WOt4$=hNE1)nY`ZBx2~x; znC@5OwEBjWRhxyQN9MU!l9+F=Rua6Nc-eQ_zpm(XnYYFVqg3bm>l>Y|ezNiG^bA?+JWK}NK3N^~dY#RFdKzhO z)f%l=$*e`so>t0cpR@Eos=U89F6wUDUkv?1g8&2|009U<00Izz00bZa0SG|g%n95u zt+wYnEOeO5tL~N~%3R7~;y8C5_pZYh^}0;^tD#?L5P$##AOHafKmY;|fB*y_009X6 z7Xp{9_J!X|0_F4nM)R9tf3%P7Lwnu6t_NrkfB*y_009U<00Izz00bZafwL{J(y#_s zoFWvV&mt-V&!IdIGn#Q#&``LA!~9%k9-peobY-CoM_v%fJk&W^Q10t+>|}x#Tsfie zxZs7X!u2xjXJHh@ib@{oPXsEaan4=pdxFK8U&KMks<5TQv5aCj@;NPvIHFuAPNO*T zsFa1D@jNITsmk!}nl-qV`!bLGoY7bbmvYHCjRhCf<1A3VE4@HEVzfWqQR5X;UtF1Q>%jf?Md&#h0+T32U-`OARPiOll#_S;g0SG_< z0uX=z1Rwwb2tWV=e-&7^2If4afkqXaqb#sc1?MOU)XU&J"] -workspace = "../.." - -[dependencies] -env_logger = "0.5" -actix = "0.5" -actix-web = "0.5" -actix-redis = { version = "0.3", features = ["web"] } diff --git a/examples/redis-session/src/main.rs b/examples/redis-session/src/main.rs deleted file mode 100644 index f61496fc..00000000 --- a/examples/redis-session/src/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -#![allow(unused_variables)] - -extern crate actix; -extern crate actix_web; -extern crate actix_redis; -extern crate env_logger; - -use actix_web::{server, App, HttpRequest, HttpResponse, Result}; -use actix_web::middleware::{Logger, SessionStorage, RequestSession}; -use actix_redis::RedisSessionBackend; - - -/// simple handler -fn index(mut req: HttpRequest) -> Result { - println!("{:?}", req); - - // session - if let Some(count) = req.session().get::("counter")? { - println!("SESSION value: {}", count); - req.session().set("counter", count+1)?; - } else { - req.session().set("counter", 1)?; - } - - Ok("Welcome!".into()) -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); - env_logger::init(); - let sys = actix::System::new("basic-example"); - - server::new( - || App::new() - // enable logger - .middleware(Logger::default()) - // cookie session middleware - .middleware(SessionStorage::new( - RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) - )) - // register simple route, handle all methods - .resource("/", |r| r.f(index))) - .bind("0.0.0.0:8080").unwrap() - .threads(1) - .start(); - - let _ = sys.run(); -} diff --git a/examples/state/Cargo.toml b/examples/state/Cargo.toml deleted file mode 100644 index a0ac2d28..00000000 --- a/examples/state/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "state" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[dependencies] -futures = "0.1" -env_logger = "0.5" -actix = "0.5" -actix-web = { path = "../../" } diff --git a/examples/state/README.md b/examples/state/README.md deleted file mode 100644 index 127ed2a0..00000000 --- a/examples/state/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# 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/src/main.rs b/examples/state/src/main.rs deleted file mode 100644 index 804b68c6..00000000 --- a/examples/state/src/main.rs +++ /dev/null @@ -1,77 +0,0 @@ -#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))] -//! There are two level of statefulness in actix-web. Application has state -//! that is shared across all handlers within same Application. -//! And individual handler can have state. - -extern crate actix; -extern crate actix_web; -extern crate env_logger; - -use std::cell::Cell; - -use actix::prelude::*; -use actix_web::{ - http, server, ws, middleware, App, HttpRequest, HttpResponse}; - -/// Application state -struct AppState { - counter: Cell, -} - -/// simple handle -fn index(req: HttpRequest) -> HttpResponse { - println!("{:?}", req); - req.state().counter.set(req.state().counter.get() + 1); - - HttpResponse::Ok().body(format!("Num of requests: {}", req.state().counter.get())) -} - -/// `MyWebSocket` counts how many messages it receives from peer, -/// websocket-client.py could be used for tests -struct MyWebSocket { - counter: usize, -} - -impl Actor for MyWebSocket { - type Context = ws::WebsocketContext; -} - -impl StreamHandler for MyWebSocket { - - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - self.counter += 1; - println!("WS({}): {:?}", self.counter, msg); - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(_) => { - ctx.stop(); - } - _ => (), - } - } -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("ws-example"); - - server::new( - || App::with_state(AppState{counter: Cell::new(0)}) - // enable logger - .middleware(middleware::Logger::default()) - // websocket route - .resource( - "/ws/", |r| - r.method(http::Method::GET).f( - |req| ws::start(req, MyWebSocket{counter: 0}))) - // register simple handler, handle all methods - .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(); -} diff --git a/examples/static/actixLogo.png b/examples/static/actixLogo.png deleted file mode 100644 index 1e2509a75a75b950e331348b1241e077cbaa3222..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13131 zcmV-RGqlW!P)F6fe00009a7bBm000XU z000XU0RWnu7ytkO8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GPWjp`?GT2E( zK~#9!?0tQRWL0;QpVRySG2KH6f?HUy~qS!TTf*@J@4k#>sO$`Rag3>kk&kK@F z4Jb$;bWIT4Kh&vFHUVAfYQY#GVQTUrF@((RWR=~JWU5ir4GD9bklpts$lIMY$tKy& zc%H83?%b}ak8|(&KKJ*7L(Ozo-@0`^e)su)c3!=DB>*5*?Ck7xZ@VMOF4z1hBG2bbpG_~Pauhsf@^j``6#xVZm?pYRzVw)gXdDVSrd^;vo?YFS1fGu<|1;7&CmgCR?d7f^2 zgCVv09fkM&A1pfn0tHOquWkAL7v+e_UZ^60{uGuS0D%G~@O;bf|ClQ;_Rh{u-@s*h z3X<+G7`WG=98)<4?D+>kpg;@pAGiE|kL{!`V1kGabWI70_-A5a`gkhGdvcEEa*Wvt z2Y^6Z69-ZOCtw_ z)0hOwnIqf<>#Ka9=Nb@h~&gEzH8hi(u1C?0TQ+fSNbG|MVzRxWXC?rt8FaDcl$*>NHFeVKp*WDWLC$E34 z@5Jv{O$0d5nFO*yD}0}4AWsk|;2-}|j^E$%o+_PgN;$ z6{ZUBIX9TO^-_+1P%%9`=Yahojs*x5P$VnW!`(2Yu!+}umnG5JGQn|bBRO;q4SaVZ z(HE7JNm6G=?DTrtkUX!hTlaw@-%s{m6)%5bN_FRde#?qEp}8+Wr0xMPe2j~>v!l9b zXG`_aKQHPAf>DDG^@A zHmmP6b=Vc(cb8RXlPamIM0yYrd0wf+l)G-2JWcJNZXGiN$P)yb3UT(Bz@9Uq{IG}H zIAl9nZ_B+-h5rJ|zHzjPMEQ5e9_~vu^y{m_TskiR0`*3mT_z+l`d4j#f6AZ~S_edo zFG&qtCQ`#T%2Y_S;2HxNj|pdh9W}UKUw1DFwa~9G$nhyipN`KuY@o);lQ#Pg$nn8- z>bHT+mt5$1w8=(UK;m?QGr8VzWBQXVEfFDqsV(1Xh18F%v-G5Bf}O&O&?4cKRM>3k2#Kb4&pcFnN;mQl-8uIWDJ| zu{VLX#@9DgpErsr13KdK7C7U%D8%dfnALw@J!~`*3#zG-)&KW+(9crLywa5lRXYTH zUAXJD1p3d(ea6mBW<|~L{6WNx;n(^yiq;sA% zl?-ck2>4l(;ZjNQ2AoK1jp+YG&dtBjysk1o@4TLbwat0{h~1SWyh5dMW3= zYiPf(eK6`4zX5@!OPpPnURJ4he= zs*)%2>_MO#C5~-2pvEK`anX-6o|hakfY;WuRQU&iX%$L)l(X2TqFy4#9qa)R=(@x3 zvc?9j9Z=jbklxn;oK;8@(uUgl4wy^_!hd!cts!Ny&DPxKNYqaOe!_Jf01)WP1Mn;I z<#vrfQEm>#(q|#UO}jj2!kof6U}P^11CdP6#r4RBrcM2zx`RNKNbVguzNw~qJKB=7 zzR9i%(oA2o84C3tMxnw;Cs7b+`WrqIi-vD?L5`IvtGq>pI7tejjTeds_mb}ur0GX0 z4QVgb!A(e?AW+qfo?@ucGbYU$wm68i?%t9621UC;ptaPl6pDVFSES7XTRTW1okXD8 z*KR-3X0$#Rp2_A}#9!f*H$55s#~{yO_Zg8AJ**!Eeyb)6G*ip$AE&_mrj7L=fyN;n zkjnBjvfDmkX)^~sMYrF|nhU`N=ga-HWAHmtulo1#kH`U=rDj_NfxcewSSEsq6Ov{c zdu}WOE0k@JKz~WTe0=H0$&o0^*xn?`h{Wo~a$lv7ulN;7 z+&N39Akf4RA}N)`S=u)dT9O`#=3-$RJqv{VdYXSflrIl9yd^0T1%ZZ3proqzL7c51 zQCi0%JI8$n250p#Y;$s=oVD<_K_&ZU!Hirm2rb} z*yVWY2Q~hTNHqFhOVJW2k*9q5hjP4Mj$f7k|Aen6uOW~MN7Zl8PPW}dN-D5Vd;A_3 z!cjek1J>T^CZqhASY+;jKs}J>h{>}Ag-ZN1FJ_bL^@P8W60&nr9A_UPNdRLY{MtmR zk=%Gw>?X%}(tdp^>Lmw|hV@tFUjMEXOjFT?G08>yZu#<`=$~ueLMqUx&f<7X=X@d- zAxW@t0;?YCbKy60sVsF^@V%7nP@0zTJ7Ebsopp+dK-t#$aszo*7oO}ZlTGBrPgr{2 zq2sTzwW<>UM4I5S4oS8KiH1O#JpZ>?*gWgtS@{$#39HiohD-sji#(uI7*J>5)1Ezu z{A*C1hJK2gR!ju?ujR}4L_F6dbq-Nh4rrjt$6o9v+14P@IFaN6e6M`@_mR%?BX$yj zJb}9Vz7YOHn{;rJvZ*RYCsIuHJI`6_%s~Y}OQq0TDTn1n7skZF#!n)+RkFPzGzl;z zg`P!t>nEwlGc3zAn+GaG&8K)85D+n}#r*a|u zmjF9~y%PZ5fU)ziv$ONVa=gPy1t3tv@M)-im|AWx02o4Mh&-qArfO>j;CU?sGQC3B5Yk~o*^R2*{Edl0B0HQon-qIWg`Y{qU=qT^Z>g;-w$;J9h& zN)V`jfIIEKXbBQMf$BEF0(k-vVK1`#B@&#oM7ik< z512S9T~#$r`$KZf>JYRI-0)0(6d3#&NlKs9xXjSe?Sec3pJyeDnDn2^`=>bRTwM3g zXF{)3A6=Wyq&IcmTPBj6(0?vp{we?WT{+&C_cE(_4U#@xRPBDk>7xMP>%5S|@&RVz z4N&%9-&YK_7Xn>VS)M8vja8*QCQl+<+r5lV5UKg?b55X3s=EqEGA+gP zzHrxM%d2Vt0F^UCg2X|~r{R)?nV?ccof2q`rf?zP4hn++kUl}4o8UT(>GP40=M#`8 z5D3N~&oIf;2Z8=!1PgWsk2?^E^f`d)9(E%}rvxe|;-m8AGoddSjxG=Yc~XG*wB^%q zQa7~G`8s*>!Vh?J>;^U;i1!3SAy5;|6Df*>u9GkQoXD9RvUCpj1Dyg=-Fu-&!1xTL z@AJa$V!;65+S#Co_+uT(O)O%n3&nFtpLZD8G*Bz>aqw&9vnLW)YdH>NE|97Tgg~B| zXzq0E{950|59W&#s09crFZN%(28rTH&SV>4>JEl6L~-MfXWym!bvztAXIjlO)fk zx$Z+#wtH6phn}aTh-K}9s0YfnM_*Ft6pkYx0P<|AHlLVI`ryzZpB+GZrsvpJ-QbW4 zD*KWGr#QxdC-UsGx~gMyw57fI?Dv%!p5xz;<0nC&=5Zc!mIIJHdFrU`glD!q)hvbj z(hB!<3rKT&kRO--q@wK~kmE1d=%dv3o=C8L0zSxdD9pq_=az3Z_jxv(^gLp9eaw>m z-(LFlXHDNE2owO^k|!PRV__!V(zf8gIEnV52%!087Wg#q~abAy2mbfY zV?)QEcMkFd9Fb=x!pibdMK_T9pm{4(O^-CMbtQ$G$~x>A+$4WXQ~GpWwyVI=1{|sE zDt*qIplA8^`-v{gzcO{9j-gHRY5DRK^7Xgm_<@>#I}(2K;URqjHi@$@$J~I#kkrK z9r z6ImJ}O-c+f-U3z22c?40LLCMKsG%-pm#ldJf(6DEqtdu5n<eBvv?NeD(Z4TW-WTZv zqXsCTv-e5r8#`3jH|}LQ`X-1Xz)lfOpj{i)x&+ESTizG(B=4zYH~_%XzR%dZejEsS z3cinAcRzzX-Ky~96#9e6&jW0rKpo;Fa-o>c5F4@WGl}*ae!mpR(@0@yCj=^Q^06G> z#6hL78MVKKjv=!23Ar>*5 z`V{#3s@&jI#?9zJA!(iG%n?t^&xsl9bQ;-0lyP*B zMB`V{bNSzwVo@#;- zC>9AEuQ05SITK8;#u^ckj+tOx-6#28x;5@`z&!Pp$~B&<{=L<%ob2h0-B@q6o*>Z3 zra2UF0=dG8SQH|jYp&f;xJ^)a#-yJw{k)WtGqJv#=kLky`^`$<%c2vh$2O=GKp|Np zQ=R;PTKj0gn|%OVrgP#Po6hdxy!)dFzW?8p-~GS%B+y)>*S_P7i8ROgvPPKw3SpxC zOM-ix*LW8A192|r;I?xDWn(PaAyN)dKIf-1BvNyPS&m)IiZ?i`3&A`=Bps?LMoz`6 ztx-y*i;1|lbA+JU6NygYu~$AcRl@9pFoO{Y2Cnxo?*}?Rp+-*p8#yQ6+n7L^5RZ9+ zKQG6x%JFlRx0DB>59#4*j%Fa!H^{O&;P?p-r%av~9`{P9&gOUIy!;6r0{s>FavOWB zkz_h!6(#pkX3OybpoeEqypI3t2{?RWz!Pe^7RbXkRCWI5riaT~0*y2kGh{-g4N`_x z6tR&i+bX+Ekkx=`5M`XWACe89qju&Vwb)YVp>e_c{OZg$B~bE$KE*z>u0CDx#wlNg zOD}&#)fN>gQi#{>PGF}5dAiz*J~)Snlbqg@CT5IH1p1@$| zo4&voBHb)~1B*Atp~FaIj3TYJ)wiLQn!e9%aiC<6_$zY!JrPH0rZEG#{O%OafZCEk z%<$4H-JnBhYLIuVbl+7@<>WX(`a+>*lbp)$k{MjQ(wGfzH4r0=o(E3^N`47b72T{P z4Jfztp!7uUy?herd>ehz@{?+Wa|lF;b9Tf;4Gu3d)fQ-G;~qN=z7!GF{lMn>nWfC0 znTV@BGXT(j9VAo$d~E1CR_3EoQ#rOXJ_%@&tvp?L6XrORKtvd&RLq96^3Dxh%1Wci zj>Z=w_4X`~dCnufHUOB!Nuo7sCkC#O1x29c(=L#wRAKS*sPhTLfHRS<&EABDIkeHP zT#G;riq`-DXc9!KjSmeullBZrve#guFMJ_T(hM+%bMd;*coif)pdj;VJZwP`@2Sx5 zk#)#YsIiC5jTB~<;>9eg@jaYRYapqDv=}=4bCoq*Y72r+2#a{nNejLwsqx1Ku0zQ& z1qbO*YVNtK8o1SXQmvjy0To@gR-(&rHUzLJA$C=`)|3>$74ge{@ogeg3R&u>-tnNa z&NflzqG za?LiW>hw?>`|u7^gb3$?-xq0SlMUOpq1aIt!djLsaOx2#IcvB|(Q$ybrbF(jV#?k^ z8}vW`P3&^_irHM^GZ!KR;55W6Vt{5M)O=?4N{b<;M_s3oEb ziPC`#+2(yIEEK|s7$p7Ka%i8h`L9^oD=U2}r3!xwYXa0%p^{K1E9!j67^PZisUdO; z*QAtAa?lOyNcx?MstW+B(ojk*BTDJwVW78no@kL|9hlyGq^|dyL^qTK`eCdIpon3L zwm7b6bQ<8T!EvRT$8mwG%%& z^v7JDiR_@ClH>bCT;UynL+LYd$%YPPNY0uuw%l5n(1NsW%WG3P`DsiGh+3FHsp<^d z@I?(tH;_KVY#GieF(fc-q~2$Vfmcshbe+MveML&JydALlEgy zpX6mhfF2L}Kk9yN#;vv%v57R#IR$|(OPJ^5Fy~t(Nl+1>)o#S7r_9Bt! zO|d94(bhQFIgY8bIe_Hp9~7AXQgYl}jtRB*;c|!9_>X?Zquay=Iu`z#I#Jb>p^r*H ztj>*CI&F*H^tR!`4KYeNF7`Xcoc2(%E;>#4$Lh8`41 zEYuCQhVnhwTRmdL2cKYfdEg zIF&JxA%yVUi7~B>9S~ZRh)I?O?;Yegm30KOPe8(=Si$N z!ScR|K@fZz(8YC#SJ0 z7MisZdaz!ENTA8qpX6X^N)H?G0Sej4gep}FWEQS@Dfhw*QyUzf$T8W*KUGom3k0Z+~3}|OL5`liT^8c@T{z7>hSCOSr zBCCfW(B!{AGLOtaH#&l}zM<|-bmYN(TaFzb@26M!^E+Z;P<5S_*y<-UdFsU?ZZwp1 z&^^m>kfCE>;hJzBnSt)UoSLYB)md|pb2g~i@MiJZmucKG>67N$bha4O zceBuSA`Lmp!X1HD4z!IbL)cuKRr{rQ1{;koeuy;;f%l-^J-U4g^f0~cu3DUR0n8I1Y`_t= zV~Zbg55<#>ubJ;|oPukHs1Ya|S7*X}X|I(x1q4k9$&e_UBA#0Qn48G3GrDFZWWDfw z8}G_By=unUQMh4}YUZ}d6EOA=A}!&*qmQFKk;mwn=a8(a<&QZ9c^XFuTI!d|e{|MK zoNPcXSS8$bw5g%Onc+|dT4eMFy0TFu0lBZWzP;+Qx=pm9o{r_6yDYxZ-= zO<%75A~XVp3ZD*%?y|Z(V!Pwi6_GM=(r5b2m;-@^FJMsy-)vi)E;~qxn@E#+_uB8u zI8ev&gPVZ3Is0mmD-x}OObAn!NDE8fjSkpQ_)j$^`zOp%=GT|@jS0s#^&1kUq_mXV(yUKBRtQrXJ!q zmQ=h@>>P-nOG}+!28BI6=7Vv`4{y4cLcH!wFfTO`R>^yn{n6vsgFvSk1RADb^@Qu` z8pCoik<-ei{Hh#ZsBGHZh$adMpG2#Y>A@;84CH~{1PEU;58Vo2^q?M_zNuvbrKf+F z57nGATCS-}Or|4YFUbA?d&Mb<4p{XC%*`t#(C`GSnGN5{wPGSDulI`iXFkWe1s8f-CVUhqT9X87+8citd&ip;b=9a&xX)IC`VQC@q>L5- zH$tGcN|zcv zn1W=^J#iWp1E(4h-)V(Fxytxs7f*(e`s=h<3cX+wg=c;>VE;5HeRf4SQ8?djlt4Xn zHzbU(t%4xYIo4t=V5d)0>&!Rup{f&!p9&)$&f7ttMM6*#!c@Sivp(9#HysRRks26<+okf&JmP7+!K%6)6zmgD^a99kfZ zr!wrJZ_Wv9py{daTU!flRpA3GECDqsENOu@I|?zw#AIqe(Hy>KL5d_IB(p%Z&lX9# z)po_p9QQkNd}Lb;x{3-v#nTB`BX!Z0g(;6v0hskZG+?!ZX_@_CgE`Tx}BV=M=e z=q)+ESo!-ORY~AcA<&RIWF8`RWakc9ea76`tgDmZo&FC5YV z8DpOXC52unP2h&AgdxA=dF4xfS^n%#ZkQ1A zYSFM2Y(0{nF+r84;6Q2^Nu#?h``#c>|I()r9-Mh~QaMQqrI3Kzo5+(1cg7WbZVNm1 z(_KiRK}w;8fQ=fcFMS>ux?Ios{ntt5Y$EZoy^=(0)3Kx!#NFpZ3xstja)XB=2He6v z3}UaGaY#~l5@=I(ng7m)w73gtCumi^HCaj^&mg2v!aiqC2C2g9Y_QzYfkIp;RCyp? z*w`aZO?w@OpJmIyJHb8-l0@6=l?U8QpH-nMOOZfr;jYUG18S@C0N_gcEHQHxEAEm& zZQ3Rvg@&ujKT7;-Ldvc$eGY_OCnNbsau+1paWr9lVK+8#AN4|mB~g+z&`Sw$E%59! ziS|;EKwTJiCrI&xDh~uNeG(VADws?pI%kPDP6Fj8fQ$AI2x|&g3eAN=b#EYf^6Yvz zqr?kOxmB@oY1D-TI(0Z%4_wEA0kyOcRIqP1#=>8kFLn8RN&UP`5~)=JrF?baJ|ks{ zeHx?`S_nA%+AMvNVDlh=TRCJRHL@2w7AI`zX~Oo|&H{u7+O%*ewRi19!e25WiCQ60 zW_O>xa>mF|3Q`Ka5U^9XQTiN;@M6Y}L9IX)i91RX$?4;I6Oz6&&+&6tYt>cjrFDFI8loSg!J(oKCZu!%y+ zyKp6wTqIKRPVTQAtmw)$&g9E;3ul_hkLnZ8t5xFo^XP#_ai7@+;y&Se#X%7$eW}9$ z*YQ+-#|+!K6^9E18kXu#1%yln%~LG#P^Gjce?AfEc*Q*!tKVwU20CWZI5dzdKgO00 zj&w)@MIL#&m>4F{6YlYTp}4;;*xo)`aUEkCX~*D!pqqLe=wcorz&=PM@nA#5#~bru zx*yuPhRU6b>sfF{_nc8LeBgy6X2_L?6lw_|(Ce!6)b!ca*kdG#5~1IebYV zRZj{s{GP~hUo4u^?UY0Q|Bf6_91!oGKxk8_sveLIi0n*z-L;FcGm*|re_7)M%2LHi z2oaHWR#6TpyA;Vy_AB{JPHwU)%rHt)%olkIXtVu&2a}u zR~*~ynF0i-dDud$2$adpwbn2VEtWe}0?j-u3_SDxrO>H}x9vx3_79U-F5El)$W$@~ zfo_LuiO^XeX&_lCG;71+cqGpfcSG`BbOKzUO!zU%gG9qXtj?tUmh7FG@b5G`4248P zlt9Nm4*^KjyBbgNgt2BOC2dP*(h8-9E0O5j_gU=(wm_o*7HD6@?aW94In?+d$uqQi zzjRg$pHARlGLhJclZ$Y&QH)is25RpGIuK7Iy!A_}`vCIvg4@e=Hd;fV6~lCB0m&dy ze|zFpq_=+0M%5pX=SF-{&iS|b_~07>ED)uDeL;>72e>d~XAvM#FQB0MyAj?uNzX|T zsAcjz<9W&}KkJEw8H7fi)X;ZYslq!d90Ex+&cjRg@W+wf{>|76)mS7?it`yj>Y7|_Qt|8d7c{}yDwEt0bvL+0jUq`REL6XO5H80$HcKZ zf5G+~sN44T#Mqb0xMF|J8+`saf;k>KIxo4Q`<;Yz?m`NrU-roVksRM6@9zg|T*I~J z_L2`NGMBymVg{JO>G>(@I{?to=x8ix52!}DA?5s-)!K8AsI9{e?{RWHZKUnh` z6XCYcIFbER6lJ!CrYhQ{FkDlC)%iya{xrDgjY`(vq2td@&g>ObEq>52VOIewwx zWqefgIYxUBEBm3TE?4@Sz0)d(b*+pN#F6s?Mz}G)=W$Iz#9P|B#R19RCQbu2L1R9X^cZ~E;FPH~@ ze`O)yzNP(NCC(!eUaB+|2^pud*;151LsFmV$)7`|2bQqUT8c4gjP@8yx1kbe6%SJB zgqtFP4gx(aBLTQTFQR)TqAOTasr`_pI9WL+au8|0;0N|@B;P%WPPj>V;WdJtU4dd$ z2k_F@TGP54^iDL&3z5YSE)WIRCxP`tsen-3h_F7X$A+AHFbPoX^(Y5 zo>!d$DA|Q-Q)E17UT(^9U)0Y8+!b+|UD6{^xv_spzWnFNZkrBbk%}?h5}Mk`hL-N9 z?RIRcY&BS=a+AbQ<@?xta*K&p1xGQJ{Yy@TkGO+l6{oD$7vRimYfA^UY4Eqv=57(S zpRp!+5_#5~Ga^$W$Gt5oe6)7Z6bO`hqI@uc4N4nn(zJGEg5*hRosL70T6gqK*+OCa zY?dC&51#3Uxr;jeIRzE|=Ngg~6=Eiv&7I14!UxX>NV1_!m4ct#ZHKODgQOlK5-5A# z+f1H~(_>k+r?vcv)X1~^g0UCftyAo_5L=4u^7W>|fB&Uij$Vt4DHjIkvFov@a|&Z> z;EXQ!mrA0)So9JoKfN-sz3sviR_Ue+Pt2iEH-g&&#reJDAnRVs)b~u(>kGbZ9_MwX zdYW_Y$#eu|K)b%cNL;rar&F~oA&F(3B>FqNSaq-03rGUEU`eRk66tepX&$HB_JUY= z{-**`r!fhX=XE3!>};D_Abr`Ewan7^I@vax7H2lt719*098JPr!Qc?q&_!F8G6+1c z{H)JoPXNm>lHJ8?J56ctrdm*W!fJh{*+z97s0x=Pkyctwv^7*HWKDGkv+yQ5i7xg? zj();9=ey#1A&Tg2q?K?NkY||dgM^Q^5NL})WJBqzFF7?di}Y}IS@PV+k_L+*rT_$* zGAK^*ImeNXV#%`ydB(H9+7=B00@X*15*;}~$O-cvtM|dGUTpK!N!JA2VuC4H+QTV2 zkqjR23z3FRj+{;uoiGcbj2=s!V>upRmqmwrD8&c~ROOHlL^=+Q(pE}k^e$L39lBQY zm-_X9Jl%j&lPP7HYNYP4H~kj>?5x4{(B}&hsM2Adia6?v>VghY&SP$)A{0N2$^kFC z+qK;o%EyjJQqAj_Q7AVaeZD}T8&%BrS}d^?OsEBqxoSulI~r)v!R+%ZNYg#YY}2c; zZloF1iFv*gYZZAAsC@$c5&81n4L0nIN4aTwaIRFR;P3w26WP{z0xr#gp)GmSk z5Bc&J72i%arlCqTZICG75a)7F_B77GrY;XBT&)Zc@WL-|9I}0WUGeQykhxAPf)T6v z0AL%1=HHV9`o%SpC&&{7x*C<*qT4zPUOk2D7+1fo>-{ zF5UHL&0GGb##0yp$UqF3ENNQ(Sew3C*IHo%-Kf%+#dXUVIa0y@zK#b{UK0}z*oKx3 zl=ms^`x&y1K%gdwbSxG&%${zEJP;`0NRvYSP7r8NB-%5Pr2_&5wt(#KxmdKQ$wc!) zD+qLVfE$M=K-~`aiYs7|PdUOl#Db)-exJ2%TJa`lm`(8%eJ4qTAFe;Y4z#zJEf# z{Ctf+QOY-V)CL%^huckJEU5>UShR831~`+T!Y6@$XPP`&BAGy)k5_%y2v8^O<9y3| zPK171CjbKVE`3r+%EwHTX9eh06DJyH_pr@$Xwp0Y)CX*!ZZJ}O-=ik^#*{50Nt&Dy zsK@C*Y7`5*A`w$hEbQ98>b=r~`CU0ayy5qNP2mVOP-jx;wqmuvEZ5@OHLjn_mjleS zRgg+NTK1T8c-pwZ{GW2&1=H)j&iM#Hg^v@QQ`{5l!%+UM=11fje@pR&59PSSgZ_Pv zRl81}>nd=OlV(c+8XFjhH{(Ld7EVs10m#$c!Nzbrm`F()eZ}LsfoqN{*Haxt27@)% z>$0l)3Pa>I)?sMlx@$fwE3YFKf%rIIjt5kDziNGt?W1D@MM~)4)`sq1|oex+IAfLf=7kCawo{XBKR>HXJ;ST3FPu zN#91T&3FMXeEUEoqg+j&-p9fEO*5X(`SbEM5;zgwzJc%cY}NN-$$X;m0B9N_MxQ%Z z_}olrgfjtt0Vh&APz_lb*OhoUw5mxzCz3k*>oQ@uP)42n?oCqSV2y)k%F^grgX=a9 zbZ50%g?xwDx*$-%KR&7Op3}O$!YinoQ>|1agE4AIa95J&HWj#FW&=t_o2Z~%b< zkw7KWX{>Smx&*2mhsXpsYH&}Ns(;6yF{mj?=Q<0*pSel8{$-ZH0T3wQ3yO6{&oqll z)cM=fx*1h`Z*^7qNzF<|MMI))vL-#Y9e;Gg+dj^AU;qRPc*ZvM^nV$Urc|Z3X~U11 zT7G|N6LnF1g(OR=l67ar@6P#QaStI#kqa*fV@gUAr9(SpRdsKPICczNpQOZXp*dO| zA?0TjLM`;@tf~6T68m$7w^D6_vBr0yhfe8*f5uXLj^ujjv-%Trkf}MK!pb!lPO70B&AV^j{)hE$lxOiZ_PD7 zDmVN-9reGjxdud>Q;;VJ6aa`o|4cvxs;dTXBhddU(A5$EfdUTkuCBCICqqg@n(ng+ z;AAc lAx3S|L#toF1pdDO0{}JK1QOMu11tal002ovPDHLkV1f;i+j0N^ diff --git a/examples/static/favicon.ico b/examples/static/favicon.ico deleted file mode 100644 index 03018db5b5de10571c2038b583fd329f9533bb58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/examples/static/index.html b/examples/static/index.html deleted file mode 100644 index e59e13f1..00000000 --- a/examples/static/index.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - -

Chat!

-
-  | Status: - disconnected -
-
-
-
- - -
- - diff --git a/examples/template_tera/Cargo.toml b/examples/template_tera/Cargo.toml deleted file mode 100644 index 8591fa50..00000000 --- a/examples/template_tera/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "template-tera" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[dependencies] -env_logger = "0.5" -actix = "0.5" -actix-web = { path = "../../" } -tera = "*" diff --git a/examples/template_tera/README.md b/examples/template_tera/README.md deleted file mode 100644 index 35829599..00000000 --- a/examples/template_tera/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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 deleted file mode 100644 index e1a738d3..00000000 --- a/examples/template_tera/src/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; -#[macro_use] -extern crate tera; - -use actix_web::{ - http, error, middleware, server, App, HttpRequest, HttpResponse, Error}; - - -struct State { - template: tera::Tera, // <- store tera template in application 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.to_owned()); - ctx.add("text", &"Welcome!".to_owned()); - req.state().template.render("user.html", &ctx) - .map_err(|_| error::ErrorInternalServerError("Template error"))? - } else { - req.state().template.render("index.html", &tera::Context::new()) - .map_err(|_| error::ErrorInternalServerError("Template error"))? - }; - Ok(HttpResponse::Ok() - .content_type("text/html") - .body(s)) -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("tera-example"); - - server::new(|| { - let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")); - - App::with_state(State{template: tera}) - // enable logger - .middleware(middleware::Logger::default()) - .resource("/", |r| r.method(http::Method::GET).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/examples/template_tera/templates/index.html b/examples/template_tera/templates/index.html deleted file mode 100644 index d8a47bc0..00000000 --- a/examples/template_tera/templates/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Actix web - - -

Welcome!

-

-

What is your name?

-
-
-

-
-

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

Hi, {{ name }}!

-

- {{ text }} -

- - diff --git a/examples/tls/Cargo.toml b/examples/tls/Cargo.toml deleted file mode 100644 index a4706d41..00000000 --- a/examples/tls/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "tls-example" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[[bin]] -name = "server" -path = "src/main.rs" - -[dependencies] -env_logger = "0.5" -actix = "0.5" -actix-web = { path = "../../", features=["alpn"] } -openssl = { version="0.10" } diff --git a/examples/tls/README.md b/examples/tls/README.md deleted file mode 100644 index 1bc9ba3b..00000000 --- a/examples/tls/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# tls example - -## Usage - -### 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/cert.pem b/examples/tls/cert.pem deleted file mode 100644 index 159aacea..00000000 --- a/examples/tls/cert.pem +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww -CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx -NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD -QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY -MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A -MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1 -sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U -NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy -voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr -odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND -xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA -CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI -yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U -UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO -vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un -CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN -BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk -3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI -JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD -JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL -d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu -ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC -CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur -y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7 -YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh -g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt -tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y -1QU= ------END CERTIFICATE----- diff --git a/examples/tls/key.pem b/examples/tls/key.pem deleted file mode 100644 index aac387c6..00000000 --- a/examples/tls/key.pem +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP -n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M -IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5 -4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ -WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk -oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli -JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6 -/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD -YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP -wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA -69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA -AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/ -9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm -YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR -6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM -ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI -7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab -L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+ -vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ -b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz -0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL -OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI -6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC -71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g -9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu -bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb -IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga -/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc -KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2 -iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP -tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD -jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY -l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj -gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh -Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q -1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW -t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI -fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9 -5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt -+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc -3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf -cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T -qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU -DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K -5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc -fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc -Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ -4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6 -I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c= ------END RSA PRIVATE KEY----- diff --git a/examples/tls/src/main.rs b/examples/tls/src/main.rs deleted file mode 100644 index 479ef8c0..00000000 --- a/examples/tls/src/main.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate openssl; - -use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype}; -use actix_web::{ - http, middleware, server, App, HttpRequest, HttpResponse, Error}; - - -/// simple handle -fn index(req: HttpRequest) -> Result { - println!("{:?}", req); - Ok(HttpResponse::Ok() - .content_type("text/plain") - .body("Welcome!")) -} - -fn main() { - if ::std::env::var("RUST_LOG").is_err() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - } - env_logger::init(); - let sys = actix::System::new("ws-example"); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); - builder.set_certificate_chain_file("cert.pem").unwrap(); - - server::new( - || App::new() - // enable logger - .middleware(middleware::Logger::default()) - // register simple handler, handle all methods - .resource("/index.html", |r| r.f(index)) - // with path parameters - .resource("/", |r| r.method(http::Method::GET).f(|req| { - HttpResponse::Found() - .header("LOCATION", "/index.html") - .finish() - }))) - .bind("127.0.0.1:8443").unwrap() - .start_ssl(builder).unwrap(); - - println!("Started http server: 127.0.0.1:8443"); - let _ = sys.run(); -} diff --git a/examples/unix-socket/Cargo.toml b/examples/unix-socket/Cargo.toml deleted file mode 100644 index a7c31f21..00000000 --- a/examples/unix-socket/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "unix-socket" -version = "0.1.0" -authors = ["Messense Lv "] - -[dependencies] -env_logger = "0.5" -actix = "0.5" -actix-web = { path = "../../" } -tokio-uds = "0.1" diff --git a/examples/unix-socket/README.md b/examples/unix-socket/README.md deleted file mode 100644 index 03b0066a..00000000 --- a/examples/unix-socket/README.md +++ /dev/null @@ -1,14 +0,0 @@ -## Unix domain socket example - -```bash -$ curl --unix-socket /tmp/actix-uds.socket http://localhost/ -Hello world! -``` - -Although this will only one thread for handling incoming connections -according to the -[documentation](https://actix.github.io/actix-web/actix_web/struct.HttpServer.html#method.start_incoming). - -And it does not delete the socket file (`/tmp/actix-uds.socket`) when stopping -the server so it will fail to start next time you run it unless you delete -the socket file manually. diff --git a/examples/unix-socket/src/main.rs b/examples/unix-socket/src/main.rs deleted file mode 100644 index c3071847..00000000 --- a/examples/unix-socket/src/main.rs +++ /dev/null @@ -1,32 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate tokio_uds; - -use actix::*; -use actix_web::{middleware, server, App, HttpRequest}; -use tokio_uds::UnixListener; - - -fn index(_req: HttpRequest) -> &'static str { - "Hello world!" -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("unix-socket"); - - let listener = UnixListener::bind( - "/tmp/actix-uds.socket", Arbiter::handle()).expect("bind failed"); - server::new( - || App::new() - // enable logger - .middleware(middleware::Logger::default()) - .resource("/index.html", |r| r.f(|_| "Hello world!")) - .resource("/", |r| r.f(index))) - .start_incoming(listener.incoming(), false); - - println!("Started http server: /tmp/actix-uds.socket"); - let _ = sys.run(); -} diff --git a/examples/web-cors/README.md b/examples/web-cors/README.md deleted file mode 100644 index 6dd3d77f..00000000 --- a/examples/web-cors/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Actix Web CORS example - -## start -1 - backend server -```bash -$ cd web-cors/backend -$ cargo run -``` -2 - frontend server -```bash -$ cd web-cors/frontend -$ npm install -$ npm run dev -``` -then open browser 'http://localhost:1234/' diff --git a/examples/web-cors/backend/.gitignore b/examples/web-cors/backend/.gitignore deleted file mode 100644 index 250b626d..00000000 --- a/examples/web-cors/backend/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ - -/target/ -**/*.rs.bk -Cargo.lock \ No newline at end of file diff --git a/examples/web-cors/backend/Cargo.toml b/examples/web-cors/backend/Cargo.toml deleted file mode 100644 index cffc895f..00000000 --- a/examples/web-cors/backend/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "actix-web-cors" -version = "0.1.0" -authors = ["krircc "] -workspace = "../../../" - -[dependencies] -serde = "1.0" -serde_derive = "1.0" -serde_json = "1.0" -http = "0.1" - -actix = "0.5" -actix-web = { path = "../../../" } -dotenv = "0.10" -env_logger = "0.5" -futures = "0.1" diff --git a/examples/web-cors/backend/src/main.rs b/examples/web-cors/backend/src/main.rs deleted file mode 100644 index 599be2c9..00000000 --- a/examples/web-cors/backend/src/main.rs +++ /dev/null @@ -1,43 +0,0 @@ -#[macro_use] extern crate serde_derive; -extern crate serde; -extern crate serde_json; -extern crate futures; -extern crate actix; -extern crate actix_web; -extern crate env_logger; - -use std::env; -use actix_web::{http, middleware, server, App}; - -mod user; -use user::info; - - -fn main() { - env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - - let sys = actix::System::new("Actix-web-CORS"); - - server::new( - || App::new() - .middleware(middleware::Logger::default()) - .resource("/user/info", |r| { - middleware::cors::Cors::build() - .allowed_origin("http://localhost:1234") - .allowed_methods(vec!["GET", "POST"]) - .allowed_headers( - vec![http::header::AUTHORIZATION, - http::header::ACCEPT, - http::header::CONTENT_TYPE]) - .max_age(3600) - .finish().expect("Can not create CORS middleware") - .register(r); - r.method(http::Method::POST).a(info); - })) - .bind("127.0.0.1:8000").unwrap() - .shutdown_timeout(200) - .start(); - - let _ = sys.run(); -} diff --git a/examples/web-cors/backend/src/user.rs b/examples/web-cors/backend/src/user.rs deleted file mode 100644 index 364430fc..00000000 --- a/examples/web-cors/backend/src/user.rs +++ /dev/null @@ -1,19 +0,0 @@ -use actix_web::{AsyncResponder, Error, HttpMessage, HttpResponse, HttpRequest}; -use futures::Future; - - -#[derive(Deserialize,Serialize, Debug)] -struct Info { - username: String, - email: String, - password: String, - confirm_password: String, -} - -pub fn info(req: HttpRequest) -> Box> { - req.json() - .from_err() - .and_then(|res: Info| { - Ok(HttpResponse::Ok().json(res)) - }).responder() -} diff --git a/examples/web-cors/frontend/.babelrc b/examples/web-cors/frontend/.babelrc deleted file mode 100644 index 002b4aa0..00000000 --- a/examples/web-cors/frontend/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["env"] -} diff --git a/examples/web-cors/frontend/.gitignore b/examples/web-cors/frontend/.gitignore deleted file mode 100644 index 8875af86..00000000 --- a/examples/web-cors/frontend/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -.DS_Store -node_modules/ -/dist/ -.cache -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Editor directories and files -.idea -*.suo -*.ntvs* -*.njsproj -*.sln diff --git a/examples/web-cors/frontend/index.html b/examples/web-cors/frontend/index.html deleted file mode 100644 index d71de81c..00000000 --- a/examples/web-cors/frontend/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - webapp - - -
- - - - \ No newline at end of file diff --git a/examples/web-cors/frontend/package.json b/examples/web-cors/frontend/package.json deleted file mode 100644 index 7ce2f641..00000000 --- a/examples/web-cors/frontend/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "actix-web-cors", - "version": "0.1.0", - "description": "webapp", - "main": "main.js", - "scripts": { - "dev": "rm -rf dist/ && NODE_ENV=development parcel index.html", - "build": "NODE_ENV=production parcel build index.html", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "license": "ISC", - "dependencies": { - "vue": "^2.5.13", - "vue-router": "^3.0.1", - "axios": "^0.17.1" - }, - "devDependencies": { - "babel-preset-env": "^1.6.1", - "parcel-bundler": "^1.4.1", - "parcel-plugin-vue": "^1.5.0" - } -} diff --git a/examples/web-cors/frontend/src/app.vue b/examples/web-cors/frontend/src/app.vue deleted file mode 100644 index 0c054c20..00000000 --- a/examples/web-cors/frontend/src/app.vue +++ /dev/null @@ -1,145 +0,0 @@ - - - - - \ No newline at end of file diff --git a/examples/web-cors/frontend/src/main.js b/examples/web-cors/frontend/src/main.js deleted file mode 100644 index df1e4b7c..00000000 --- a/examples/web-cors/frontend/src/main.js +++ /dev/null @@ -1,11 +0,0 @@ -import Vue from 'vue' -import App from './app' - -new Vue({ - el: '#app', - render: h => h(App) -}) - -if (module.hot) { - module.hot.accept(); -} \ No newline at end of file diff --git a/examples/websocket-chat/Cargo.toml b/examples/websocket-chat/Cargo.toml deleted file mode 100644 index 389ccd34..00000000 --- a/examples/websocket-chat/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "websocket-example" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[[bin]] -name = "server" -path = "src/main.rs" - -[[bin]] -name = "client" -path = "src/client.rs" - -[dependencies] -rand = "*" -bytes = "0.4" -byteorder = "1.1" -futures = "0.1" -tokio-io = "0.1" -tokio-core = "0.1" -env_logger = "*" - -serde = "1.0" -serde_json = "1.0" -serde_derive = "1.0" - -actix = "0.5" -actix-web = { path="../../" } diff --git a/examples/websocket-chat/README.md b/examples/websocket-chat/README.md deleted file mode 100644 index a01dd68b..00000000 --- a/examples/websocket-chat/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Websocket chat example - -This is extension of the -[actix chat example](https://github.com/actix/actix/tree/master/examples/chat) - -Added features: - -* Browser WebSocket client -* 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 message 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 dropped - -To start server use command: `cargo run --bin server` - -## Client - -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/](http://localhost:8080/) diff --git a/examples/websocket-chat/client.py b/examples/websocket-chat/client.py deleted file mode 100755 index 8a1bd9ae..00000000 --- a/examples/websocket-chat/client.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -"""websocket cmd client for wssrv.py example.""" -import argparse -import asyncio -import signal -import sys - -import aiohttp - - -def start_client(loop, url): - name = input('Please enter your name: ') - - # send request - ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False) - - # input reader - def stdin_callback(): - line = sys.stdin.buffer.readline().decode('utf-8') - if not line: - loop.stop() - else: - ws.send_str(name + ': ' + line) - loop.add_reader(sys.stdin.fileno(), stdin_callback) - - @asyncio.coroutine - def dispatch(): - while True: - msg = yield from ws.receive() - - if msg.type == aiohttp.WSMsgType.TEXT: - print('Text: ', msg.data.strip()) - elif msg.type == aiohttp.WSMsgType.BINARY: - print('Binary: ', msg.data) - elif msg.type == aiohttp.WSMsgType.PING: - ws.pong() - elif msg.type == aiohttp.WSMsgType.PONG: - print('Pong received') - else: - if msg.type == aiohttp.WSMsgType.CLOSE: - yield from ws.close() - elif msg.type == aiohttp.WSMsgType.ERROR: - print('Error during receive %s' % ws.exception()) - elif msg.type == aiohttp.WSMsgType.CLOSED: - pass - - break - - yield from dispatch() - - -ARGS = argparse.ArgumentParser( - description="websocket console client for wssrv.py example.") -ARGS.add_argument( - '--host', action="store", dest='host', - default='127.0.0.1', help='Host name') -ARGS.add_argument( - '--port', action="store", dest='port', - default=8080, type=int, help='Port number') - -if __name__ == '__main__': - args = ARGS.parse_args() - if ':' in args.host: - args.host, port = args.host.split(':', 1) - args.port = int(port) - - url = 'http://{}:{}/ws/'.format(args.host, args.port) - - loop = asyncio.get_event_loop() - loop.add_signal_handler(signal.SIGINT, loop.stop) - asyncio.Task(start_client(loop, url)) - loop.run_forever() diff --git a/examples/websocket-chat/src/client.rs b/examples/websocket-chat/src/client.rs deleted file mode 100644 index e2e6a7c8..00000000 --- a/examples/websocket-chat/src/client.rs +++ /dev/null @@ -1,153 +0,0 @@ -#[macro_use] extern crate actix; -extern crate bytes; -extern crate byteorder; -extern crate futures; -extern crate tokio_io; -extern crate tokio_core; -extern crate serde; -extern crate serde_json; -#[macro_use] extern crate serde_derive; - -use std::{io, net, process, thread}; -use std::str::FromStr; -use std::time::Duration; -use futures::Future; -use tokio_io::AsyncRead; -use tokio_io::io::WriteHalf; -use tokio_io::codec::FramedRead; -use tokio_core::net::TcpStream; -use actix::prelude::*; - -mod codec; - - -fn main() { - let sys = actix::System::new("chat-client"); - - // Connect to server - let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap(); - Arbiter::handle().spawn( - TcpStream::connect(&addr, Arbiter::handle()) - .and_then(|stream| { - let addr: Addr = ChatClient::create(|ctx| { - let (r, w) = stream.split(); - ChatClient::add_stream(FramedRead::new(r, codec::ClientChatCodec), ctx); - ChatClient{ - framed: actix::io::FramedWrite::new( - w, codec::ClientChatCodec, ctx)}}); - - // start console loop - thread::spawn(move|| { - loop { - let mut cmd = String::new(); - if io::stdin().read_line(&mut cmd).is_err() { - println!("error"); - return - } - - addr.do_send(ClientCommand(cmd)); - } - }); - - futures::future::ok(()) - }) - .map_err(|e| { - println!("Can not connect to server: {}", e); - process::exit(1) - }) - ); - - println!("Running chat client"); - sys.run(); -} - - -struct ChatClient { - framed: actix::io::FramedWrite, codec::ClientChatCodec>, -} - -#[derive(Message)] -struct ClientCommand(String); - -impl Actor for ChatClient { - type Context = Context; - - fn started(&mut self, ctx: &mut Context) { - // start heartbeats otherwise server will disconnect after 10 seconds - self.hb(ctx) - } - - fn stopped(&mut self, _: &mut Context) { - println!("Disconnected"); - - // Stop application on disconnect - Arbiter::system().do_send(actix::msgs::SystemExit(0)); - } -} - -impl ChatClient { - fn hb(&self, ctx: &mut Context) { - ctx.run_later(Duration::new(1, 0), |act, ctx| { - act.framed.write(codec::ChatRequest::Ping); - act.hb(ctx); - }); - } -} - -impl actix::io::WriteHandler for ChatClient {} - -/// Handle stdin commands -impl Handler for ChatClient { - type Result = (); - - fn handle(&mut self, msg: ClientCommand, _: &mut Context) { - let m = msg.0.trim(); - if m.is_empty() { - return - } - - // we check for /sss type of messages - if m.starts_with('/') { - let v: Vec<&str> = m.splitn(2, ' ').collect(); - match v[0] { - "/list" => { - self.framed.write(codec::ChatRequest::List); - }, - "/join" => { - if v.len() == 2 { - self.framed.write(codec::ChatRequest::Join(v[1].to_owned())); - } else { - println!("!!! room name is required"); - } - }, - _ => println!("!!! unknown command"), - } - } else { - self.framed.write(codec::ChatRequest::Message(m.to_owned())); - } - } -} - -/// Server communication - -impl StreamHandler for ChatClient { - - fn handle(&mut self, msg: codec::ChatResponse, _: &mut Context) { - 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); - } - println!(""); - } - _ => (), - } - } -} diff --git a/examples/websocket-chat/src/codec.rs b/examples/websocket-chat/src/codec.rs deleted file mode 100644 index 03638241..00000000 --- a/examples/websocket-chat/src/codec.rs +++ /dev/null @@ -1,123 +0,0 @@ -#![allow(dead_code)] -use std::io; -use serde_json as json; -use byteorder::{BigEndian , ByteOrder}; -use bytes::{BytesMut, BufMut}; -use tokio_io::codec::{Encoder, Decoder}; - -/// Client request -#[derive(Serialize, Deserialize, Debug, Message)] -#[serde(tag="cmd", content="data")] -pub enum ChatRequest { - /// List rooms - List, - /// Join rooms - Join(String), - /// Send message - Message(String), - /// Ping - Ping -} - -/// Server response -#[derive(Serialize, Deserialize, Debug, Message)] -#[serde(tag="cmd", content="data")] -pub enum ChatResponse { - Ping, - - /// List of rooms - Rooms(Vec), - - /// Joined - Joined(String), - - /// Message - Message(String), -} - -/// Codec for Client -> Server transport -pub struct ChatCodec; - -impl Decoder for ChatCodec -{ - type Item = ChatRequest; - type Error = io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let size = { - if src.len() < 2 { - return Ok(None) - } - BigEndian::read_u16(src.as_ref()) as usize - }; - - if src.len() >= size + 2 { - src.split_to(2); - let buf = src.split_to(size); - Ok(Some(json::from_slice::(&buf)?)) - } else { - Ok(None) - } - } -} - -impl Encoder for ChatCodec -{ - type Item = ChatResponse; - type Error = io::Error; - - fn encode(&mut self, msg: ChatResponse, dst: &mut BytesMut) -> Result<(), Self::Error> { - let msg = json::to_string(&msg).unwrap(); - let msg_ref: &[u8] = msg.as_ref(); - - dst.reserve(msg_ref.len() + 2); - dst.put_u16::(msg_ref.len() as u16); - dst.put(msg_ref); - - Ok(()) - } -} - - -/// Codec for Server -> Client transport -pub struct ClientChatCodec; - -impl Decoder for ClientChatCodec -{ - type Item = ChatResponse; - type Error = io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let size = { - if src.len() < 2 { - return Ok(None) - } - BigEndian::read_u16(src.as_ref()) as usize - }; - - if src.len() >= size + 2 { - src.split_to(2); - let buf = src.split_to(size); - Ok(Some(json::from_slice::(&buf)?)) - } else { - Ok(None) - } - } -} - -impl Encoder for ClientChatCodec -{ - type Item = ChatRequest; - type Error = io::Error; - - fn encode(&mut self, msg: ChatRequest, dst: &mut BytesMut) -> Result<(), Self::Error> { - let msg = json::to_string(&msg).unwrap(); - let msg_ref: &[u8] = msg.as_ref(); - - dst.reserve(msg_ref.len() + 2); - dst.put_u16::(msg_ref.len() as u16); - dst.put(msg_ref); - - Ok(()) - } -} diff --git a/examples/websocket-chat/src/main.rs b/examples/websocket-chat/src/main.rs deleted file mode 100644 index 5cd3e6e2..00000000 --- a/examples/websocket-chat/src/main.rs +++ /dev/null @@ -1,209 +0,0 @@ -#![allow(unused_variables)] -extern crate rand; -extern crate bytes; -extern crate byteorder; -extern crate futures; -extern crate tokio_io; -extern crate tokio_core; -extern crate env_logger; -extern crate serde; -extern crate serde_json; -#[macro_use] extern crate serde_derive; - -#[macro_use] -extern crate actix; -extern crate actix_web; - -use std::time::Instant; - -use actix::*; -use actix_web::server::HttpServer; -use actix_web::{http, fs, ws, App, HttpRequest, HttpResponse, Error}; - -mod codec; -mod server; -mod session; - -/// This is our websocket route state, this state is shared with all route instances -/// via `HttpContext::state()` -struct WsChatSessionState { - addr: Addr, -} - -/// Entry point for our route -fn chat_route(req: HttpRequest) -> Result { - ws::start( - req, - WsChatSession { - id: 0, - hb: Instant::now(), - room: "Main".to_owned(), - name: None}) -} - -struct WsChatSession { - /// unique session id - id: usize, - /// Client must send ping at least once per 10 seconds, otherwise we drop connection. - hb: Instant, - /// joined room - room: String, - /// peer name - name: Option, -} - -impl Actor for WsChatSession { - type Context = ws::WebsocketContext; - - /// 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 addr: Addr = ctx.address(); - ctx.state().addr.send(server::Connect{addr: addr.recipient()}) - .into_actor(self) - .then(|res, act, ctx| { - match res { - 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) -> Running { - // notify chat server - ctx.state().addr.do_send(server::Disconnect{id: self.id}); - Running::Stop - } -} - -/// Handle messages from chat server, we simply send it to peer websocket -impl Handler for WsChatSession { - type Result = (); - - fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) { - ctx.text(msg.0); - } -} - -/// WebSocket message handler -impl StreamHandler for WsChatSession { - - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - println!("WEBSOCKET MESSAGE: {:?}", msg); - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Pong(msg) => self.hb = Instant::now(), - ws::Message::Text(text) => { - let m = text.trim(); - // we check for /sss type of messages - if m.starts_with('/') { - let v: Vec<&str> = m.splitn(2, ' ').collect(); - match v[0] { - "/list" => { - // Send ListRooms message to chat server and wait for response - println!("List rooms"); - ctx.state().addr.send(server::ListRooms) - .into_actor(self) - .then(|res, _, ctx| { - match res { - Ok(rooms) => { - for room in rooms { - ctx.text(room); - } - }, - _ => 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 - }, - "/join" => { - if v.len() == 2 { - self.room = v[1].to_owned(); - ctx.state().addr.do_send( - server::Join{id: self.id, name: self.room.clone()}); - - ctx.text("joined"); - } else { - ctx.text("!!! room name is required"); - } - }, - "/name" => { - if v.len() == 2 { - self.name = Some(v[1].to_owned()); - } else { - ctx.text("!!! name is required"); - } - }, - _ => ctx.text(format!("!!! unknown command: {:?}", m)), - } - } else { - let msg = if let Some(ref name) = self.name { - format!("{}: {}", name, m) - } else { - m.to_owned() - }; - // send message to chat server - ctx.state().addr.do_send( - server::Message{id: self.id, - msg: msg, - room: self.room.clone()}) - } - }, - ws::Message::Binary(bin) => - println!("Unexpected binary"), - ws::Message::Close(_) => { - ctx.stop(); - } - } - } -} - -fn main() { - let _ = env_logger::init(); - let sys = actix::System::new("websocket-example"); - - // Start chat server actor in separate thread - let server: Addr = Arbiter::start(|_| server::ChatServer::default()); - - // Start tcp server in separate thread - let srv = server.clone(); - Arbiter::new("tcp-server").do_send::( - msgs::Execute::new(move || { - session::TcpServer::new("127.0.0.1:12345", srv); - Ok(()) - })); - - // Create Http server with websocket support - HttpServer::new( - move || { - // Websocket sessions state - let state = WsChatSessionState { addr: server.clone() }; - - App::with_state(state) - // redirect to websocket.html - .resource("/", |r| r.method(http::Method::GET).f(|_| { - HttpResponse::Found() - .header("LOCATION", "/static/websocket.html") - .finish() - })) - // websocket - .resource("/ws/", |r| r.route().f(chat_route)) - // static resources - .handler("/static/", fs::StaticFiles::new("static/")) - }) - .bind("127.0.0.1:8080").unwrap() - .start(); - - 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 deleted file mode 100644 index 8b735b85..00000000 --- a/examples/websocket-chat/src/server.rs +++ /dev/null @@ -1,197 +0,0 @@ -//! `ChatServer` is an actor. It maintains list of connection client session. -//! And manages available rooms. Peers send messages to other peers in same -//! room through `ChatServer`. - -use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; -use rand::{self, Rng, ThreadRng}; -use actix::prelude::*; - -use session; - -/// Message for chat server communications - -/// New chat session is created -#[derive(Message)] -#[rtype(usize)] -pub struct Connect { - pub addr: Recipient, -} - -/// Session is disconnected -#[derive(Message)] -pub struct Disconnect { - pub id: usize, -} - -/// Send message to specific room -#[derive(Message)] -pub struct Message { - /// Id of the client session - pub id: usize, - /// Peer message - pub msg: String, - /// Room name - pub room: String, -} - -/// List of available rooms -pub struct ListRooms; - -impl actix::Message for ListRooms { - type Result = Vec; -} - -/// Join room, if room does not exists create new one. -#[derive(Message)] -pub struct Join { - /// Client id - pub id: usize, - /// Room name - pub name: String, -} - -/// `ChatServer` manages chat rooms and responsible for coordinating chat session. -/// implementation is super primitive -pub struct ChatServer { - sessions: HashMap>, - rooms: HashMap>, - rng: RefCell, -} - -impl Default for ChatServer { - fn default() -> ChatServer { - // default room - let mut rooms = HashMap::new(); - rooms.insert("Main".to_owned(), HashSet::new()); - - ChatServer { - sessions: HashMap::new(), - rooms: rooms, - rng: RefCell::new(rand::thread_rng()), - } - } -} - -impl ChatServer { - /// Send message to all users in the room - fn send_message(&self, room: &str, message: &str, skip_id: usize) { - if let Some(sessions) = self.rooms.get(room) { - for id in sessions { - if *id != skip_id { - if let Some(addr) = self.sessions.get(id) { - let _ = addr.do_send(session::Message(message.to_owned())); - } - } - } - } - } -} - -/// Make actor from `ChatServer` -impl Actor for ChatServer { - /// We are going to use simple Context, we just need ability to communicate - /// with other actors. - type Context = Context; -} - -/// Handler for Connect message. -/// -/// Register new session and assign unique id to this session -impl Handler for ChatServer { - type Result = usize; - - fn handle(&mut self, msg: Connect, _: &mut Context) -> Self::Result { - println!("Someone joined"); - - // notify all users in same room - self.send_message(&"Main".to_owned(), "Someone joined", 0); - - // register session with random id - let id = self.rng.borrow_mut().gen::(); - self.sessions.insert(id, msg.addr); - - // auto join session to Main room - self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id); - - // send id back - id - } -} - -/// Handler for Disconnect message. -impl Handler for ChatServer { - type Result = (); - - fn handle(&mut self, msg: Disconnect, _: &mut Context) { - println!("Someone disconnected"); - - let mut rooms: Vec = Vec::new(); - - // remove address - if self.sessions.remove(&msg.id).is_some() { - // remove session from all rooms - for (name, sessions) in &mut self.rooms { - if sessions.remove(&msg.id) { - rooms.push(name.to_owned()); - } - } - } - // send message to other users - for room in rooms { - self.send_message(&room, "Someone disconnected", 0); - } - } -} - -/// Handler for Message message. -impl Handler for ChatServer { - type Result = (); - - fn handle(&mut self, msg: Message, _: &mut Context) { - self.send_message(&msg.room, msg.msg.as_str(), msg.id); - } -} - -/// Handler for `ListRooms` message. -impl Handler for ChatServer { - type Result = MessageResult; - - 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()) - } - - MessageResult(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) { - let Join {id, name} = msg; - let mut rooms = Vec::new(); - - // remove session from all rooms - for (n, sessions) in &mut self.rooms { - if sessions.remove(&id) { - rooms.push(n.to_owned()); - } - } - // send message to other users - for room in rooms { - self.send_message(&room, "Someone disconnected", 0); - } - - if self.rooms.get_mut(&name).is_none() { - self.rooms.insert(name.clone(), HashSet::new()); - } - self.send_message(&name, "Someone connected", id); - self.rooms.get_mut(&name).unwrap().insert(id); - } -} diff --git a/examples/websocket-chat/src/session.rs b/examples/websocket-chat/src/session.rs deleted file mode 100644 index 7f28c6a4..00000000 --- a/examples/websocket-chat/src/session.rs +++ /dev/null @@ -1,207 +0,0 @@ -//! `ClientSession` is an actor, it manages peer tcp connection and -//! proxies commands from peer to `ChatServer`. -use std::{io, net}; -use std::str::FromStr; -use std::time::{Instant, Duration}; -use futures::Stream; -use tokio_io::AsyncRead; -use tokio_io::io::WriteHalf; -use tokio_io::codec::FramedRead; -use tokio_core::net::{TcpStream, TcpListener}; - -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); - -/// `ChatSession` actor is responsible for tcp peer communications. -pub struct ChatSession { - /// unique session id - id: usize, - /// this is address of chat server - addr: Addr, - /// Client must send ping at least once per 10 seconds, otherwise we drop connection. - hb: Instant, - /// joined room - room: String, - /// Framed wrapper - framed: actix::io::FramedWrite, ChatCodec>, -} - -impl Actor for ChatSession { - /// For tcp communication we are going to use `FramedContext`. - /// It is convenient wrapper around `Framed` object from `tokio_io` - type Context = Context; - - 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. - let addr: Addr = ctx.address(); - self.addr.send(server::Connect{addr: addr.recipient()}) - .into_actor(self) - .then(|res, act, ctx| { - match res { - Ok(res) => act.id = res, - // something is wrong with chat server - _ => ctx.stop(), - } - actix::fut::ok(()) - }).wait(ctx); - } - - fn stopping(&mut self, ctx: &mut Self::Context) -> Running { - // notify chat server - self.addr.do_send(server::Disconnect{id: self.id}); - Running::Stop - } -} - -impl actix::io::WriteHandler for ChatSession {} - -/// To use `Framed` we have to define Io type and Codec -impl StreamHandler for ChatSession { - - /// This is main event loop for client requests - fn handle(&mut self, msg: ChatRequest, ctx: &mut Context) { - match msg { - ChatRequest::List => { - // Send ListRooms message to chat server and wait for response - println!("List rooms"); - self.addr.send(server::ListRooms) - .into_actor(self) - .then(|res, act, ctx| { - match res { - Ok(rooms) => { - act.framed.write(ChatResponse::Rooms(rooms)); - }, - _ => println!("Something is wrong"), - } - 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.do_send(server::Join{id: self.id, name: name.clone()}); - self.framed.write(ChatResponse::Joined(name)); - }, - ChatRequest::Message(message) => { - // send message to chat server - println!("Peer message: {}", message); - self.addr.do_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(), - } - } -} - -/// 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 Context) { - // send message to peer - self.framed.write(ChatResponse::Message(msg.0)); - } -} - -/// Helper methods -impl ChatSession { - - pub fn new(addr: Addr, - framed: actix::io::FramedWrite, ChatCodec>) -> ChatSession { - ChatSession {id: 0, addr: addr, hb: Instant::now(), - room: "Main".to_owned(), framed: framed} - } - - /// helper method that sends ping to client every second. - /// - /// also this method check heartbeats from client - fn hb(&self, ctx: &mut Context) { - ctx.run_later(Duration::new(1, 0), |act, ctx| { - // check client heartbeats - if Instant::now().duration_since(act.hb) > Duration::new(10, 0) { - // heartbeat timed out - println!("Client heartbeat failed, disconnecting!"); - - // notify chat server - act.addr.do_send(server::Disconnect{id: act.id}); - - // stop actor - ctx.stop(); - } - - act.framed.write(ChatResponse::Ping); - // if we can not send message to sink, sink is closed (disconnected) - act.hb(ctx); - }); - } -} - - -/// Define tcp server that will accept incoming tcp connection and create -/// chat actors. -pub struct TcpServer { - chat: Addr, -} - -impl TcpServer { - pub fn new(s: &str, chat: Addr) { - // Create server listener - let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap(); - let listener = TcpListener::bind(&addr, Arbiter::handle()).unwrap(); - - // Our chat server `Server` is an actor, first we need to start it - // and then add stream on incoming tcp connections to it. - // TcpListener::incoming() returns stream of the (TcpStream, net::SocketAddr) items - // 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_message_stream(listener.incoming() - .map_err(|_| ()) - .map(|(t, a)| TcpConnect(t, a))); - TcpServer{chat: chat} - }); - } -} - -/// Make actor from `Server` -impl Actor for TcpServer { - /// Every actor has to provide execution `Context` in which it can run. - type Context = Context; -} - -#[derive(Message)] -struct TcpConnect(TcpStream, net::SocketAddr); - -/// Handle stream of TcpStream's -impl Handler for TcpServer { - type Result = (); - - 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::create(|ctx| { - let (r, w) = msg.0.split(); - ChatSession::add_stream(FramedRead::new(r, ChatCodec), ctx); - ChatSession::new(server, actix::io::FramedWrite::new(w, ChatCodec, ctx)) - }); - } -} diff --git a/examples/websocket-chat/static/websocket.html b/examples/websocket-chat/static/websocket.html deleted file mode 100644 index e59e13f1..00000000 --- a/examples/websocket-chat/static/websocket.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - -

Chat!

-
-  | Status: - disconnected -
-
-
-
- - -
- - diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml deleted file mode 100644 index 7b754f0d..00000000 --- a/examples/websocket/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "websocket" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[[bin]] -name = "server" -path = "src/main.rs" - -[[bin]] -name = "client" -path = "src/client.rs" - -[dependencies] -env_logger = "*" -futures = "0.1" -tokio-core = "0.1" -actix = "0.5" -actix-web = { path="../../" } diff --git a/examples/websocket/README.md b/examples/websocket/README.md deleted file mode 100644 index 8ffcba82..00000000 --- a/examples/websocket/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# websocket - -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/src/client.rs b/examples/websocket/src/client.rs deleted file mode 100644 index 34ff2437..00000000 --- a/examples/websocket/src/client.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! Simple websocket client. - -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate env_logger; -extern crate futures; -extern crate tokio_core; - -use std::{io, thread}; -use std::time::Duration; - -use actix::*; -use futures::Future; -use actix_web::ws::{Message, ProtocolError, Client, ClientWriter}; - - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); - let sys = actix::System::new("ws-example"); - - Arbiter::handle().spawn( - Client::new("http://127.0.0.1:8080/ws/") - .connect() - .map_err(|e| { - println!("Error: {}", e); - () - }) - .map(|(reader, writer)| { - let addr: Addr = ChatClient::create(|ctx| { - ChatClient::add_stream(reader, ctx); - ChatClient(writer) - }); - - // start console loop - thread::spawn(move|| { - loop { - let mut cmd = String::new(); - if io::stdin().read_line(&mut cmd).is_err() { - println!("error"); - return - } - addr.do_send(ClientCommand(cmd)); - } - }); - - () - }) - ); - - let _ = sys.run(); -} - - -struct ChatClient(ClientWriter); - -#[derive(Message)] -struct ClientCommand(String); - -impl Actor for ChatClient { - type Context = Context; - - fn started(&mut self, ctx: &mut Context) { - // start heartbeats otherwise server will disconnect after 10 seconds - self.hb(ctx) - } - - fn stopped(&mut self, _: &mut Context) { - println!("Disconnected"); - - // Stop application on disconnect - Arbiter::system().do_send(actix::msgs::SystemExit(0)); - } -} - -impl ChatClient { - fn hb(&self, ctx: &mut Context) { - ctx.run_later(Duration::new(1, 0), |act, ctx| { - act.0.ping(""); - act.hb(ctx); - }); - } -} - -/// Handle stdin commands -impl Handler for ChatClient { - type Result = (); - - fn handle(&mut self, msg: ClientCommand, ctx: &mut Context) { - self.0.text(msg.0) - } -} - -/// Handle server websocket messages -impl StreamHandler for ChatClient { - - fn handle(&mut self, msg: Message, ctx: &mut Context) { - match msg { - Message::Text(txt) => println!("Server: {:?}", txt), - _ => () - } - } - - fn started(&mut self, ctx: &mut Context) { - println!("Connected"); - } - - fn finished(&mut self, ctx: &mut Context) { - println!("Server disconnected"); - ctx.stop() - } -} diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs deleted file mode 100644 index 11292a9b..00000000 --- a/examples/websocket/src/main.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Simple echo websocket server. -//! Open `http://localhost:8080/ws/index.html` in browser -//! or [python console client](https://github.com/actix/actix-web/blob/master/examples/websocket-client.py) -//! could be used for testing. - -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate env_logger; - -use actix::prelude::*; -use actix_web::{ - http, middleware, server, fs, ws, App, HttpRequest, HttpResponse, Error}; - -/// do websocket handshake and start `MyWebSocket` actor -fn ws_index(r: HttpRequest) -> Result { - ws::start(r, MyWebSocket) -} - -/// websocket connection is long running connection, it easier -/// to handle with an actor -struct MyWebSocket; - -impl Actor for MyWebSocket { - type Context = ws::WebsocketContext; -} - -/// Handler for `ws::Message` -impl StreamHandler for MyWebSocket { - - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - // process websocket messages - println!("WS: {:?}", msg); - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(_) => { - ctx.stop(); - } - _ => (), - } - } -} - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("ws-example"); - - server::new( - || App::new() - // enable logger - .middleware(middleware::Logger::default()) - // websocket route - .resource("/ws/", |r| r.method(http::Method::GET).f(ws_index)) - // static files - .handler("/", fs::StaticFiles::new("../static/") - .index_file("index.html"))) - // start http server on 127.0.0.1:8080 - .bind("127.0.0.1:8080").unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); -} diff --git a/examples/websocket/websocket-client.py b/examples/websocket/websocket-client.py deleted file mode 100755 index 8a1bd9ae..00000000 --- a/examples/websocket/websocket-client.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -"""websocket cmd client for wssrv.py example.""" -import argparse -import asyncio -import signal -import sys - -import aiohttp - - -def start_client(loop, url): - name = input('Please enter your name: ') - - # send request - ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False) - - # input reader - def stdin_callback(): - line = sys.stdin.buffer.readline().decode('utf-8') - if not line: - loop.stop() - else: - ws.send_str(name + ': ' + line) - loop.add_reader(sys.stdin.fileno(), stdin_callback) - - @asyncio.coroutine - def dispatch(): - while True: - msg = yield from ws.receive() - - if msg.type == aiohttp.WSMsgType.TEXT: - print('Text: ', msg.data.strip()) - elif msg.type == aiohttp.WSMsgType.BINARY: - print('Binary: ', msg.data) - elif msg.type == aiohttp.WSMsgType.PING: - ws.pong() - elif msg.type == aiohttp.WSMsgType.PONG: - print('Pong received') - else: - if msg.type == aiohttp.WSMsgType.CLOSE: - yield from ws.close() - elif msg.type == aiohttp.WSMsgType.ERROR: - print('Error during receive %s' % ws.exception()) - elif msg.type == aiohttp.WSMsgType.CLOSED: - pass - - break - - yield from dispatch() - - -ARGS = argparse.ArgumentParser( - description="websocket console client for wssrv.py example.") -ARGS.add_argument( - '--host', action="store", dest='host', - default='127.0.0.1', help='Host name') -ARGS.add_argument( - '--port', action="store", dest='port', - default=8080, type=int, help='Port number') - -if __name__ == '__main__': - args = ARGS.parse_args() - if ':' in args.host: - args.host, port = args.host.split(':', 1) - args.port = int(port) - - url = 'http://{}:{}/ws/'.format(args.host, args.port) - - loop = asyncio.get_event_loop() - loop.add_signal_handler(signal.SIGINT, loop.stop) - asyncio.Task(start_client(loop, url)) - loop.run_forever() diff --git a/guide/src/qs_1.md b/guide/src/qs_1.md index 5e31aec6..b5c3ca0f 100644 --- a/guide/src/qs_1.md +++ b/guide/src/qs_1.md @@ -24,9 +24,9 @@ The fastest way to start experimenting with actix web is to clone the The following set of commands runs the `basics` example: ```bash -git clone https://github.com/actix/actix-web -cd actix-web/examples/basics +git clone https://github.com/actix/example +cd examples/basics cargo run ``` -Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples. +Check [examples/](https://github.com/actix/examples/tree/master/) directory for more examples. From 5e9ec4299c9f949ef59a01f043d26a5877782dc8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Apr 2018 20:52:30 -0700 Subject: [PATCH 0096/1635] fix workspace links --- Cargo.toml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8654846d..3544a67e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,22 +108,5 @@ codegen-units = 1 [workspace] members = [ "./", - "examples/basics", - "examples/juniper", - "examples/diesel", - "examples/r2d2", - "examples/json", - "examples/protobuf", - "examples/hello-world", - "examples/http-proxy", - "examples/multipart", - "examples/state", - "examples/redis-session", - "examples/template_tera", - "examples/tls", - "examples/websocket", - "examples/websocket-chat", - "examples/web-cors/backend", - "examples/unix-socket", "tools/wsload/", ] From c0976bfa1742d385e13272d543b334f0772248c9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Apr 2018 21:28:17 -0700 Subject: [PATCH 0097/1635] fix test --- src/fs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 0f6120b2..acb3121d 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -625,12 +625,12 @@ mod tests { fn test_redirect_to_index_nested() { let mut st = StaticFiles::new(".").index_file("Cargo.toml"); let mut req = HttpRequest::default(); - req.match_info_mut().add("tail", "examples/basics"); + req.match_info_mut().add("tail", "tools/wsload"); let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/examples/basics/Cargo.toml"); + assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/tools/wsload/Cargo.toml"); } #[test] From 22c776f46ea169a29d5a34572d71a966db25044b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 10:13:12 -0700 Subject: [PATCH 0098/1635] Fix StaticFiles does not support percent encoded paths #177 --- CHANGES.md | 5 +++++ src/fs.rs | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 60b6aaf9..bc1b68d8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # Changes +## 0.5.2 (2018-04-xx) + +* Fix StaticFiles does not support percent encoded paths #177 + + ## 0.5.1 (2018-04-12) * Client connector provides stats, `ClientConnector::stats()` diff --git a/src/fs.rs b/src/fs.rs index acb3121d..4dbff6f8 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -15,6 +15,7 @@ use bytes::{Bytes, BytesMut, BufMut}; use futures::{Async, Poll, Future, Stream}; use futures_cpupool::{CpuPool, CpuFuture}; use mime_guess::get_mime_type; +use percent_encoding::percent_decode; use header; use error::Error; @@ -484,7 +485,10 @@ impl Handler for StaticFiles { if !self.accessible { Ok(self.default.handle(req)) } else { - let relpath = match req.match_info().get("tail").map(PathBuf::from_param) { + let relpath = match req.match_info().get("tail").map( + |tail| percent_decode(tail.as_bytes()).decode_utf8().unwrap()) + .map(|tail| PathBuf::from_param(tail.as_ref())) + { Some(Ok(path)) => path, _ => return Ok(self.default.handle(req)) }; @@ -671,4 +675,15 @@ mod tests { let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); assert_eq!(loc, "/test/Cargo.toml"); } + + #[test] + fn integration_percent_encoded() { + let mut srv = test::TestServer::with_factory( + || App::new() + .handler("test", StaticFiles::new(".").index_file("Cargo.toml"))); + + let request = srv.get().uri(srv.url("/test/%43argo.toml")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::OK); + } } From 95f6277007332cf593e9f8ead3c6e0e57d243cfb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 14:36:07 -0700 Subject: [PATCH 0099/1635] fix typo --- README.md | 4 ++-- src/httpcodes.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae070fac..9eb11248 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. ## Documentation & community resources -* [User Guide](https://actix.github.io/actix-web/guide/) -* [API Documentation (Development)](https://actix.github.io/actix-web/actix_web/) +* [User Guide](https://actix.rs/actix-web/guide/) +* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 7ad66cb1..5d3eb7ff 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -203,6 +203,7 @@ impl HttpResponse { STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY); + STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); STATIC_RESP!(Found, StatusCode::FOUND); STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED); From 113f5ad1a884743350c38bf02b101e8f0afec551 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 16:02:01 -0700 Subject: [PATCH 0100/1635] add rustfmt config --- build.rs | 35 +- rustfmt.toml | 7 + src/application.rs | 245 +++++---- src/body.rs | 26 +- src/client/connector.rs | 646 ++++++++++++---------- src/client/mod.rs | 13 +- src/client/parser.rs | 113 ++-- src/client/pipeline.rs | 222 ++++---- src/client/request.rs | 139 +++-- src/client/response.rs | 31 +- src/client/writer.rs | 172 +++--- src/context.rs | 84 +-- src/de.rs | 228 +++++--- src/error.rs | 295 +++++----- src/extractor.rs | 213 +++++--- src/fs.rs | 318 +++++++---- src/handler.rs | 170 +++--- src/header/common/accept.rs | 4 +- src/header/common/accept_charset.rs | 2 +- src/header/common/accept_language.rs | 2 +- src/header/common/cache_control.rs | 140 +++-- src/header/common/content_language.rs | 12 +- src/header/common/content_range.rs | 61 ++- src/header/common/content_type.rs | 15 +- src/header/common/date.rs | 3 +- src/header/common/etag.rs | 2 +- src/header/common/expires.rs | 2 +- src/header/common/if_match.rs | 2 +- src/header/common/if_modified_since.rs | 2 +- src/header/common/if_none_match.rs | 9 +- src/header/common/if_range.rs | 28 +- src/header/common/if_unmodified_since.rs | 2 +- src/header/common/last_modified.rs | 2 +- src/header/common/mod.rs | 1 + src/header/common/range.rs | 183 ++++--- src/header/mod.rs | 55 +- src/header/shared/charset.rs | 22 +- src/header/shared/encoding.rs | 9 +- src/header/shared/entity.rs | 102 ++-- src/header/shared/httpdate.rs | 66 ++- src/header/shared/mod.rs | 4 +- src/header/shared/quality_item.rs | 81 ++- src/helpers.rs | 399 +++++++++++--- src/httpcodes.rs | 185 ++++--- src/httpmessage.rs | 239 ++++++--- src/httprequest.rs | 204 ++++--- src/httpresponse.rs | 398 +++++++++----- src/info.rs | 41 +- src/json.rs | 288 ++++++---- src/lib.rs | 131 ++--- src/middleware/cors.rs | 408 +++++++++----- src/middleware/csrf.rs | 57 +- src/middleware/defaultheaders.rs | 40 +- src/middleware/errhandlers.rs | 22 +- src/middleware/logger.rs | 156 +++--- src/middleware/mod.rs | 27 +- src/middleware/session.rs | 116 ++-- src/multipart.rs | 359 +++++++------ src/param.rs | 93 ++-- src/payload.rs | 309 ++++++----- src/pipeline.rs | 457 ++++++++-------- src/pred.rs | 135 +++-- src/resource.rs | 57 +- src/route.rs | 260 ++++----- src/router.rs | 179 ++++--- src/server/channel.rs | 181 ++++--- src/server/encoding.rs | 395 +++++++------- src/server/h1.rs | 558 +++++++++++-------- src/server/h1writer.rs | 110 ++-- src/server/h2.rs | 174 +++--- src/server/h2writer.rs | 76 +-- src/server/helpers.rs | 132 +++-- src/server/mod.rs | 39 +- src/server/settings.rs | 60 ++- src/server/shared.rs | 15 +- src/server/srv.rs | 652 +++++++++++++---------- src/server/utils.rs | 11 +- src/server/worker.rs | 113 ++-- src/test.rs | 233 ++++---- src/with.rs | 319 ++++++----- src/ws/client.rs | 203 +++---- src/ws/context.rs | 89 ++-- src/ws/frame.rs | 144 +++-- src/ws/mask.rs | 23 +- src/ws/mod.rs | 363 ++++++++----- src/ws/proto.rs | 116 ++-- tests/test_client.rs | 280 +++++----- tests/test_handlers.rs | 98 ++-- tests/test_server.rs | 571 +++++++++++--------- tests/test_ws.rs | 95 +++- tools/wsload/src/wsclient.rs | 188 ++++--- 91 files changed, 8057 insertions(+), 5509 deletions(-) create mode 100644 rustfmt.toml diff --git a/build.rs b/build.rs index bf2355e2..ee1fe22d 100644 --- a/build.rs +++ b/build.rs @@ -3,7 +3,6 @@ extern crate version_check; use std::{env, fs}; - #[cfg(unix)] fn main() { println!("cargo:rerun-if-env-changed=USE_SKEPTIC"); @@ -11,23 +10,23 @@ fn main() { if env::var("USE_SKEPTIC").is_ok() { let _ = fs::remove_file(f); // 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_3_5.md", - "guide/src/qs_4.md", - "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", - "guide/src/qs_13.md", - "guide/src/qs_14.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_3_5.md", + "guide/src/qs_4.md", + "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", + "guide/src/qs_13.md", + "guide/src/qs_14.md", + ]); } else { let _ = fs::File::create(f); } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..98d2ba7d --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +max_width = 89 +reorder_imports = true +reorder_imports_in_group = true +reorder_imported_names = true +wrap_comments = true +fn_args_density = "Compressed" +#use_small_heuristics = false diff --git a/src/application.rs b/src/application.rs index d2f67343..ff37b78f 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,24 +1,24 @@ -use std::mem; -use std::rc::Rc; use std::cell::UnsafeCell; use std::collections::HashMap; +use std::mem; +use std::rc::Rc; -use http::Method; use handler::Reply; -use router::{Router, Resource}; -use resource::{ResourceHandler}; +use handler::{FromRequest, Handler, Responder, RouteHandler, WrapHandler}; use header::ContentEncoding; -use handler::{Handler, RouteHandler, WrapHandler, FromRequest, Responder}; +use http::Method; use httprequest::HttpRequest; -use pipeline::{Pipeline, PipelineHandler, HandlerType}; use middleware::Middleware; -use server::{HttpHandler, IntoHttpHandler, HttpHandlerTask, ServerSettings}; +use pipeline::{HandlerType, Pipeline, PipelineHandler}; +use resource::ResourceHandler; +use router::{Resource, Router}; +use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings}; -#[deprecated(since="0.5.0", note="please use `actix_web::App` instead")] +#[deprecated(since = "0.5.0", note = "please use `actix_web::App` instead")] pub type Application = App; /// Application -pub struct HttpApplication { +pub struct HttpApplication { state: Rc, prefix: String, prefix_len: usize, @@ -36,28 +36,25 @@ pub(crate) struct Inner { } impl PipelineHandler for Inner { - fn encoding(&self) -> ContentEncoding { self.encoding } fn handle(&mut self, req: HttpRequest, htype: HandlerType) -> Reply { match htype { - HandlerType::Normal(idx) => - self.resources[idx].handle(req, Some(&mut self.default)), - HandlerType::Handler(idx) => - self.handlers[idx].1.handle(req), - HandlerType::Default => - self.default.handle(req, None) + HandlerType::Normal(idx) => { + self.resources[idx].handle(req, Some(&mut self.default)) + } + HandlerType::Handler(idx) => self.handlers[idx].1.handle(req), + HandlerType::Default => self.default.handle(req, None), } } } impl HttpApplication { - #[inline] fn as_ref(&self) -> &Inner { - unsafe{&*self.inner.get()} + unsafe { &*self.inner.get() } } #[inline] @@ -70,20 +67,21 @@ impl HttpApplication { let &(ref prefix, _) = &inner.handlers[idx]; let m = { let path = &req.path()[inner.prefix..]; - path.starts_with(prefix) && ( - path.len() == prefix.len() || - path.split_at(prefix.len()).1.starts_with('/')) + 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()[inner.prefix+prefix.len()..]) }; + mem::transmute(&req.path()[inner.prefix + prefix.len()..]) + }; if path.is_empty() { req.match_info_mut().add("tail", ""); } else { req.match_info_mut().add("tail", path.split_at(1).1); } - return HandlerType::Handler(idx) + return HandlerType::Handler(idx); } } HandlerType::Default @@ -93,7 +91,7 @@ impl HttpApplication { #[cfg(test)] pub(crate) fn run(&mut self, mut req: HttpRequest) -> Reply { let tp = self.get_handler(&mut req); - unsafe{&mut *self.inner.get()}.handle(req, tp) + unsafe { &mut *self.inner.get() }.handle(req, tp) } #[cfg(test)] @@ -103,19 +101,23 @@ impl HttpApplication { } impl HttpHandler for HttpApplication { - fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest> { 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('/')) + path.starts_with(&self.prefix) + && (path.len() == self.prefix_len + || path.split_at(self.prefix_len).1.starts_with('/')) }; if m { let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); let tp = self.get_handler(&mut req); let inner = Rc::clone(&self.inner); - Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner, tp))) + Ok(Box::new(Pipeline::new( + req, + Rc::clone(&self.middlewares), + inner, + tp, + ))) } else { Err(req) } @@ -134,14 +136,14 @@ struct ApplicationParts { middlewares: Vec>>, } -/// Structure that follows the builder pattern for building application instances. -pub struct App { +/// Structure that follows the builder pattern for building application +/// instances. +pub struct App { parts: Option>, } impl App<()> { - - /// Create application with empty state. Application can + /// Create application with empty state. Application can /// be configured with a builder-like pattern. pub fn new() -> App<()> { App { @@ -155,7 +157,7 @@ impl App<()> { external: HashMap::new(), encoding: ContentEncoding::Auto, middlewares: Vec::new(), - }) + }), } } } @@ -166,8 +168,10 @@ impl Default for App<()> { } } -impl App where S: 'static { - +impl App +where + S: 'static, +{ /// Create application with specified state. Application can be /// configured with a builder-like pattern. /// @@ -185,7 +189,7 @@ impl App where S: 'static { external: HashMap::new(), middlewares: Vec::new(), encoding: ContentEncoding::Auto, - }) + }), } } @@ -256,20 +260,22 @@ impl App where S: 'static { /// } /// ``` pub fn route(mut self, path: &str, method: Method, f: F) -> App - where F: Fn(T) -> R + 'static, - R: Responder + 'static, - T: FromRequest + 'static, + where + F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, { { - let parts: &mut ApplicationParts = unsafe{ - mem::transmute(self.parts.as_mut().expect("Use after finish"))}; + let parts: &mut ApplicationParts = unsafe { + mem::transmute(self.parts.as_mut().expect("Use after finish")) + }; // get resource handler for &mut (ref pattern, ref mut handler) in &mut parts.resources { if let Some(ref mut handler) = *handler { if pattern.pattern() == path { handler.method(method).with(f); - return self + return self; } } } @@ -315,7 +321,8 @@ impl App where S: 'static { /// } /// ``` pub fn resource(mut self, path: &str, f: F) -> App - where F: FnOnce(&mut ResourceHandler) -> R + 'static + where + F: FnOnce(&mut ResourceHandler) -> R + 'static, { { let parts = self.parts.as_mut().expect("Use after finish"); @@ -334,13 +341,17 @@ impl App where S: 'static { #[doc(hidden)] pub fn register_resource(&mut self, path: &str, resource: ResourceHandler) { let pattern = Resource::new(resource.get_name(), path); - self.parts.as_mut().expect("Use after finish") - .resources.push((pattern, Some(resource))); + self.parts + .as_mut() + .expect("Use after finish") + .resources + .push((pattern, Some(resource))); } /// Default resource to be used if no matching route could be found. pub fn default_resource(mut self, f: F) -> App - where F: FnOnce(&mut ResourceHandler) -> R + 'static + where + F: FnOnce(&mut ResourceHandler) -> R + 'static, { { let parts = self.parts.as_mut().expect("Use after finish"); @@ -350,8 +361,7 @@ impl App where S: 'static { } /// Set default content encoding. `ContentEncoding::Auto` is set by default. - pub fn default_encoding(mut self, encoding: ContentEncoding) -> App - { + pub fn default_encoding(mut self, encoding: ContentEncoding) -> App { { let parts = self.parts.as_mut().expect("Use after finish"); parts.encoding = encoding; @@ -383,7 +393,9 @@ impl App where S: 'static { /// } /// ``` pub fn external_resource(mut self, name: T, url: U) -> App - where T: AsRef, U: AsRef + where + T: AsRef, + U: AsRef, { { let parts = self.parts.as_mut().expect("Use after finish"); @@ -393,7 +405,8 @@ impl App where S: 'static { } parts.external.insert( String::from(name.as_ref()), - Resource::external(name.as_ref(), url.as_ref())); + Resource::external(name.as_ref(), url.as_ref()), + ); } self } @@ -419,8 +432,7 @@ impl App where S: 'static { /// }}); /// } /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> App - { + pub fn handler>(mut self, path: &str, handler: H) -> App { { let mut path = path.trim().trim_right_matches('/').to_owned(); if !path.is_empty() && !path.starts_with('/') { @@ -428,15 +440,20 @@ impl App where S: 'static { } let parts = self.parts.as_mut().expect("Use after finish"); - parts.handlers.push((path, Box::new(WrapHandler::new(handler)))); + parts + .handlers + .push((path, Box::new(WrapHandler::new(handler)))); } self } /// Register a middleware. pub fn middleware>(mut self, mw: M) -> App { - self.parts.as_mut().expect("Use after finish") - .middlewares.push(Box::new(mw)); + self.parts + .as_mut() + .expect("Use after finish") + .middlewares + .push(Box::new(mw)); self } @@ -468,7 +485,8 @@ impl App where S: 'static { /// } /// ``` pub fn configure(self, cfg: F) -> App - where F: Fn(App) -> App + where + F: Fn(App) -> App, { cfg(self) } @@ -490,15 +508,13 @@ impl App where S: 'static { let (router, resources) = Router::new(&prefix, parts.settings, resources); - let inner = Rc::new(UnsafeCell::new( - Inner { - prefix: prefix_len, - default: parts.default, - encoding: parts.encoding, - handlers: parts.handlers, - resources, - } - )); + let inner = Rc::new(UnsafeCell::new(Inner { + prefix: prefix_len, + default: parts.default, + encoding: parts.encoding, + handlers: parts.handlers, + resources, + })); HttpApplication { state: Rc::new(parts.state), @@ -582,14 +598,13 @@ impl Iterator for App { } } - #[cfg(test)] mod tests { - use http::StatusCode; use super::*; - use test::TestRequest; + use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; + use test::TestRequest; #[test] fn test_default_resource() { @@ -603,14 +618,20 @@ mod tests { let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); let mut app = App::new() .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::METHOD_NOT_ALLOWED); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::METHOD_NOT_ALLOWED + ); } #[test] @@ -627,7 +648,8 @@ mod tests { let mut app = App::with_state(10) .resource("/", |r| r.f(|_| HttpResponse::Ok())) .finish(); - let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); + let req = + HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); } @@ -675,11 +697,17 @@ mod tests { let req = TestRequest::with_uri("/testapp").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + 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); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } #[test] @@ -702,11 +730,17 @@ mod tests { let req = TestRequest::with_uri("/testapp").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + 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); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } #[test] @@ -730,31 +764,53 @@ mod tests { let req = TestRequest::with_uri("/prefix/testapp").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); let req = TestRequest::with_uri("/prefix/blah").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } #[test] fn test_route() { let mut app = App::new() - .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) - .route("/test", Method::POST, |_: HttpRequest| HttpResponse::Created()) + .route("/test", Method::GET, |_: HttpRequest| { + HttpResponse::Ok() + }) + .route("/test", Method::POST, |_: HttpRequest| { + HttpResponse::Created() + }) .finish(); - let req = TestRequest::with_uri("/test").method(Method::GET).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .finish(); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test").method(Method::POST).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::CREATED); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::CREATED + ); - let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::HEAD) + .finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } #[test] @@ -766,7 +822,10 @@ mod tests { let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); let req = TestRequest::with_uri("/app/test").finish(); let resp = app.run(req); @@ -782,12 +841,16 @@ mod tests { let req = TestRequest::with_uri("/app/testapp").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + 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); - + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } - } diff --git a/src/body.rs b/src/body.rs index 64123679..cf54361d 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,18 +1,17 @@ -use std::{fmt, mem}; -use std::rc::Rc; -use std::sync::Arc; use bytes::{Bytes, BytesMut}; use futures::Stream; +use std::rc::Rc; +use std::sync::Arc; +use std::{fmt, mem}; -use error::Error; use context::ActorHttpContext; +use error::Error; use handler::Responder; use httprequest::HttpRequest; use httpresponse::HttpResponse; - /// Type represent streaming body -pub type BodyStream = Box>; +pub type BodyStream = Box>; /// Represents various types of http message body. pub enum Body { @@ -50,7 +49,7 @@ impl Body { pub fn is_streaming(&self) -> bool { match *self { Body::Streaming(_) | Body::Actor(_) => true, - _ => false + _ => false, } } @@ -59,7 +58,7 @@ impl Body { pub fn is_binary(&self) -> bool { match *self { Body::Binary(_) => true, - _ => false + _ => false, } } @@ -96,7 +95,10 @@ impl fmt::Debug for Body { } } -impl From for Body where T: Into{ +impl From for Body +where + T: Into, +{ fn from(b: T) -> Body { Body::Binary(b.into()) } @@ -257,8 +259,8 @@ impl Responder for Binary { fn respond_to(self, _: HttpRequest) -> Result { Ok(HttpResponse::Ok() - .content_type("application/octet-stream") - .body(self)) + .content_type("application/octet-stream") + .body(self)) } } @@ -349,7 +351,7 @@ mod tests { #[test] fn test_bytes_mut() { - let b = BytesMut::from("test"); + let b = BytesMut::from("test"); assert_eq!(Binary::from(b.clone()).len(), 4); assert_eq!(Binary::from(b).as_ref(), b"test"); } diff --git a/src/client/connector.rs b/src/client/connector.rs index 0b6e63bd..e9eb0813 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,36 +1,35 @@ -use std::{fmt, mem, io, time}; use std::cell::{Cell, RefCell}; -use std::rc::Rc; -use std::net::Shutdown; -use std::time::{Duration, Instant}; use std::collections::{HashMap, VecDeque}; +use std::net::Shutdown; +use std::rc::Rc; +use std::time::{Duration, Instant}; +use std::{fmt, io, mem, time}; -use actix::{fut, Actor, ActorFuture, Arbiter, Context, AsyncContext, - Recipient, Syn, Handler, Message, ActorResponse, - Supervised, ContextFutureSpawner}; -use actix::registry::ArbiterService; +use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; use actix::fut::WrapFuture; -use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect}; +use actix::registry::ArbiterService; +use actix::{fut, Actor, ActorFuture, ActorResponse, Arbiter, AsyncContext, Context, + ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn}; -use http::{Uri, HttpTryFrom, Error as HttpError}; -use futures::{Async, Future, Poll}; -use futures::task::{Task, current as current_task}; +use futures::task::{current as current_task, Task}; use futures::unsync::oneshot; -use tokio_io::{AsyncRead, AsyncWrite}; +use futures::{Async, Future, Poll}; +use http::{Error as HttpError, HttpTryFrom, Uri}; use tokio_core::reactor::Timeout; +use tokio_io::{AsyncRead, AsyncWrite}; -#[cfg(feature="alpn")] -use openssl::ssl::{SslMethod, SslConnector, Error as OpensslError}; -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] +use openssl::ssl::{Error as OpensslError, SslConnector, SslMethod}; +#[cfg(feature = "alpn")] use tokio_openssl::SslConnectorExt; -#[cfg(all(feature="tls", not(feature="alpn")))] -use native_tls::{TlsConnector, Error as TlsError}; -#[cfg(all(feature="tls", not(feature="alpn")))] +#[cfg(all(feature = "tls", not(feature = "alpn")))] +use native_tls::{Error as TlsError, TlsConnector}; +#[cfg(all(feature = "tls", not(feature = "alpn")))] use tokio_tls::TlsConnectorExt; -use {HAS_OPENSSL, HAS_TLS}; use server::IoStream; +use {HAS_OPENSSL, HAS_TLS}; /// Client connector usage stats #[derive(Default, Message)] @@ -54,7 +53,10 @@ pub struct Connect { impl Connect { /// Create `Connect` message for specified `Uri` - pub fn new(uri: U) -> Result where Uri: HttpTryFrom { + pub fn new(uri: U) -> Result + where + Uri: HttpTryFrom, + { Ok(Connect { uri: Uri::try_from(uri).map_err(|e| e.into())?, wait_timeout: Duration::from_secs(5), @@ -92,13 +94,13 @@ pub struct Pause { impl Pause { /// Create message with pause duration parameter pub fn new(time: Duration) -> Pause { - Pause{time: Some(time)} + Pause { time: Some(time) } } } impl Default for Pause { fn default() -> Pause { - Pause{time: None} + Pause { time: None } } } @@ -114,21 +116,21 @@ pub struct Resume; #[derive(Fail, Debug)] pub enum ClientConnectorError { /// Invalid URL - #[fail(display="Invalid URL")] + #[fail(display = "Invalid URL")] InvalidUrl, /// SSL feature is not enabled - #[fail(display="SSL is not supported")] + #[fail(display = "SSL is not supported")] SslIsNotSupported, /// SSL error - #[cfg(feature="alpn")] - #[fail(display="{}", _0)] + #[cfg(feature = "alpn")] + #[fail(display = "{}", _0)] SslError(#[cause] OpensslError), /// SSL error - #[cfg(all(feature="tls", not(feature="alpn")))] - #[fail(display="{}", _0)] + #[cfg(all(feature = "tls", not(feature = "alpn")))] + #[fail(display = "{}", _0)] SslError(#[cause] TlsError), /// Connection error @@ -152,7 +154,7 @@ impl From for ClientConnectorError { fn from(err: ConnectorError) -> ClientConnectorError { match err { ConnectorError::Timeout => ClientConnectorError::Timeout, - _ => ClientConnectorError::Connector(err) + _ => ClientConnectorError::Connector(err), } } } @@ -166,9 +168,9 @@ struct Waiter { /// `ClientConnector` type is responsible for transport layer of a /// client connection. pub struct ClientConnector { - #[cfg(all(feature="alpn"))] + #[cfg(all(feature = "alpn"))] connector: SslConnector, - #[cfg(all(feature="tls", not(feature="alpn")))] + #[cfg(all(feature = "tls", not(feature = "alpn")))] connector: TlsConnector, stats: ClientConnectorStats, @@ -207,12 +209,12 @@ impl Default for ClientConnector { fn default() -> ClientConnector { let _modified = Rc::new(Cell::new(false)); - #[cfg(all(feature="alpn"))] + #[cfg(all(feature = "alpn"))] { let builder = SslConnector::builder(SslMethod::tls()).unwrap(); ClientConnector::with_connector(builder.build()) } - #[cfg(all(feature="tls", not(feature="alpn")))] + #[cfg(all(feature = "tls", not(feature = "alpn")))] { let builder = TlsConnector::builder().unwrap(); ClientConnector { @@ -235,29 +237,29 @@ impl Default for ClientConnector { } } - #[cfg(not(any(feature="alpn", feature="tls")))] - ClientConnector {stats: ClientConnectorStats::default(), - subscriber: None, - pool: Rc::new(Pool::new(Rc::clone(&_modified))), - pool_modified: _modified, - conn_lifetime: Duration::from_secs(15), - conn_keep_alive: Duration::from_secs(75), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: HashMap::new(), - wait_timeout: None, - paused: None, + #[cfg(not(any(feature = "alpn", feature = "tls")))] + ClientConnector { + stats: ClientConnectorStats::default(), + subscriber: None, + pool: Rc::new(Pool::new(Rc::clone(&_modified))), + pool_modified: _modified, + conn_lifetime: Duration::from_secs(15), + conn_keep_alive: Duration::from_secs(75), + limit: 100, + limit_per_host: 0, + acquired: 0, + acquired_per_host: HashMap::new(), + available: HashMap::new(), + to_close: Vec::new(), + waiters: HashMap::new(), + wait_timeout: None, + paused: None, } } } impl ClientConnector { - - #[cfg(feature="alpn")] + #[cfg(feature = "alpn")] /// Create `ClientConnector` actor with custom `SslConnector` instance. /// /// By default `ClientConnector` uses very a simple SSL configuration. @@ -369,20 +371,19 @@ impl ClientConnector { // check limits if self.limit > 0 { if self.acquired >= self.limit { - return Acquire::NotAvailable + return Acquire::NotAvailable; } if self.limit_per_host > 0 { if let Some(per_host) = self.acquired_per_host.get(key) { if self.limit_per_host >= *per_host { - return Acquire::NotAvailable + return Acquire::NotAvailable; } } } - } - else if self.limit_per_host > 0 { + } else if self.limit_per_host > 0 { if let Some(per_host) = self.acquired_per_host.get(key) { if self.limit_per_host >= *per_host { - return Acquire::NotAvailable + return Acquire::NotAvailable; } } } @@ -408,11 +409,11 @@ impl ClientConnector { Ok(n) if n > 0 => { self.stats.closed += 1; self.to_close.push(conn); - continue - }, + continue; + } Ok(_) | Err(_) => continue, } - return Acquire::Acquired(conn) + return Acquire::Acquired(conn); } } } @@ -421,25 +422,25 @@ impl ClientConnector { fn reserve(&mut self, key: &Key) { self.acquired += 1; - let per_host = - if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - 0 - }; - self.acquired_per_host.insert(key.clone(), per_host + 1); + let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { + *per_host + } else { + 0 + }; + self.acquired_per_host + .insert(key.clone(), per_host + 1); } fn release_key(&mut self, key: &Key) { self.acquired -= 1; - let per_host = - if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - return - }; + let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { + *per_host + } else { + return; + }; if per_host > 1 { - self.acquired_per_host.insert(key.clone(), per_host - 1); + self.acquired_per_host + .insert(key.clone(), per_host - 1); } else { self.acquired_per_host.remove(key); } @@ -472,7 +473,8 @@ impl ClientConnector { // check connection lifetime and the return to available pool if (now - conn.ts) < self.conn_lifetime { - self.available.entry(conn.key.clone()) + self.available + .entry(conn.key.clone()) .or_insert_with(VecDeque::new) .push_back(Conn(Instant::now(), conn)); } @@ -490,7 +492,7 @@ impl ClientConnector { self.to_close.push(conn); self.stats.closed += 1; } else { - break + break; } } } @@ -503,7 +505,7 @@ impl ClientConnector { Ok(Async::NotReady) => idx += 1, _ => { self.to_close.swap_remove(idx); - }, + } } } } @@ -514,7 +516,9 @@ impl ClientConnector { fn collect_periodic(&mut self, ctx: &mut Context) { self.collect(true); // re-schedule next collect period - ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); + ctx.run_later(Duration::from_secs(1), |act, ctx| { + act.collect_periodic(ctx) + }); // send stats let stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); @@ -555,27 +559,34 @@ impl ClientConnector { fn install_wait_timeout(&mut self, time: Instant) { if let Some(ref mut wait) = self.wait_timeout { if wait.0 < time { - return + return; } } - let mut timeout = Timeout::new(time-Instant::now(), Arbiter::handle()).unwrap(); + let mut timeout = + Timeout::new(time - Instant::now(), Arbiter::handle()).unwrap(); let _ = timeout.poll(); self.wait_timeout = Some((time, timeout)); } - fn wait_for(&mut self, key: Key, - wait: Duration, conn_timeout: Duration) - -> oneshot::Receiver> - { + fn wait_for( + &mut self, key: Key, wait: Duration, conn_timeout: Duration + ) -> oneshot::Receiver> { // connection is not available, wait let (tx, rx) = oneshot::channel(); let wait = Instant::now() + wait; self.install_wait_timeout(wait); - let waiter = Waiter{ tx, wait, conn_timeout }; - self.waiters.entry(key).or_insert_with(VecDeque::new).push_back(waiter); + let waiter = Waiter { + tx, + wait, + conn_timeout, + }; + self.waiters + .entry(key) + .or_insert_with(VecDeque::new) + .push_back(waiter); rx } } @@ -617,21 +628,23 @@ impl Handler for ClientConnector { // host name is required if uri.host().is_none() { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)) + return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)); } // supported protocols let proto = match uri.scheme_part() { Some(scheme) => match Protocol::from(scheme.as_str()) { Some(proto) => proto, - None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)), + None => { + return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)) + } }, None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)), }; // check ssl availability if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS { - return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)) + return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)); } // check if pool has task reference @@ -641,7 +654,11 @@ impl Handler for ClientConnector { let host = uri.host().unwrap().to_owned(); let port = uri.port().unwrap_or_else(|| proto.port()); - let key = Key {host, port, ssl: proto.is_secure()}; + let key = Key { + host, + port, + ssl: proto.is_secure(), + }; // check pause state if self.paused.is_some() { @@ -653,7 +670,8 @@ impl Handler for ClientConnector { .and_then(|res, _, _| match res { Ok(conn) => fut::ok(conn), Err(err) => fut::err(err), - })); + }), + ); } // acquire connection @@ -663,8 +681,8 @@ impl Handler for ClientConnector { // use existing connection conn.pool = Some(AcquiredConn(key, Some(Rc::clone(&self.pool)))); self.stats.reused += 1; - return ActorResponse::async(fut::ok(conn)) - }, + return ActorResponse::async(fut::ok(conn)); + } Acquire::NotAvailable => { // connection is not available, wait let rx = self.wait_for(key, wait_timeout, conn_timeout); @@ -675,106 +693,131 @@ impl Handler for ClientConnector { .and_then(|res, _, _| match res { Ok(conn) => fut::ok(conn), Err(err) => fut::err(err), - })); + }), + ); } - Acquire::Available => { - Some(Rc::clone(&self.pool)) - }, + Acquire::Available => Some(Rc::clone(&self.pool)), } } else { None }; let conn = AcquiredConn(key, pool); -{ - ActorResponse::async( - Connector::from_registry() - .send(ResolveConnect::host_and_port(&conn.0.host, port) - .timeout(conn_timeout)) - .into_actor(self) - .map_err(|_, _, _| ClientConnectorError::Disconnected) - .and_then(move |res, act, _| { - #[cfg(feature="alpn")] - match res { - Err(err) => { - act.stats.opened += 1; - fut::Either::B(fut::err(err.into())) - }, - Ok(stream) => { - act.stats.opened += 1; - if proto.is_secure() { - fut::Either::A( - act.connector.connect_async(&conn.0.host, stream) - .map_err(ClientConnectorError::SslError) - .map(|stream| Connection::new( - conn.0.clone(), Some(conn), Box::new(stream))) - .into_actor(act)) - } else { - fut::Either::B(fut::ok( - Connection::new( - conn.0.clone(), Some(conn), Box::new(stream)))) + { + ActorResponse::async( + Connector::from_registry() + .send( + ResolveConnect::host_and_port(&conn.0.host, port) + .timeout(conn_timeout), + ) + .into_actor(self) + .map_err(|_, _, _| ClientConnectorError::Disconnected) + .and_then(move |res, act, _| { + #[cfg(feature = "alpn")] + match res { + Err(err) => { + act.stats.opened += 1; + fut::Either::B(fut::err(err.into())) + } + Ok(stream) => { + act.stats.opened += 1; + if proto.is_secure() { + fut::Either::A( + act.connector + .connect_async(&conn.0.host, stream) + .map_err(ClientConnectorError::SslError) + .map(|stream| { + Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ) + }) + .into_actor(act), + ) + } else { + fut::Either::B(fut::ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))) + } + } } - } - } - #[cfg(all(feature="tls", not(feature="alpn")))] - match res { - Err(err) => { - act.stats.opened += 1; - fut::Either::B(fut::err(err.into())) - }, - Ok(stream) => { - act.stats.opened += 1; - if proto.is_secure() { - fut::Either::A( - act.connector.connect_async(&conn.0.host, stream) - .map_err(ClientConnectorError::SslError) - .map(|stream| Connection::new( - conn.0.clone(), Some(conn), Box::new(stream))) - .into_actor(act)) - } else { - fut::Either::B(fut::ok( - Connection::new( - conn.0.clone(), Some(conn), Box::new(stream)))) + #[cfg(all(feature = "tls", not(feature = "alpn")))] + match res { + Err(err) => { + act.stats.opened += 1; + fut::Either::B(fut::err(err.into())) + } + Ok(stream) => { + act.stats.opened += 1; + if proto.is_secure() { + fut::Either::A( + act.connector + .connect_async(&conn.0.host, stream) + .map_err(ClientConnectorError::SslError) + .map(|stream| { + Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ) + }) + .into_actor(act), + ) + } else { + fut::Either::B(fut::ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))) + } + } } - } - } - #[cfg(not(any(feature="alpn", feature="tls")))] - match res { - Err(err) => { - act.stats.opened += 1; - fut::err(err.into()) - }, - Ok(stream) => { - act.stats.opened += 1; - if proto.is_secure() { - fut::err(ClientConnectorError::SslIsNotSupported) - } else { - fut::ok(Connection::new( - conn.0.clone(), Some(conn), Box::new(stream))) + #[cfg(not(any(feature = "alpn", feature = "tls")))] + match res { + Err(err) => { + act.stats.opened += 1; + fut::err(err.into()) + } + Ok(stream) => { + act.stats.opened += 1; + if proto.is_secure() { + fut::err(ClientConnectorError::SslIsNotSupported) + } else { + fut::ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + )) + } + } } - } - } - })) -} + }), + ) + } } } struct Maintenance; -impl fut::ActorFuture for Maintenance -{ +impl fut::ActorFuture for Maintenance { type Item = (); type Error = (); type Actor = ClientConnector; - fn poll(&mut self, act: &mut ClientConnector, ctx: &mut Context) - -> Poll - { + fn poll( + &mut self, act: &mut ClientConnector, ctx: &mut Context + ) -> Poll { // check pause duration let done = if let Some(Some(ref pause)) = act.paused { - pause.0 <= Instant::now() } else { false }; + pause.0 <= Instant::now() + } else { + false + }; if done { act.paused.take(); } @@ -788,128 +831,151 @@ impl fut::ActorFuture for Maintenance act.collect_waiters(); // check waiters - let tmp: &mut ClientConnector = unsafe{mem::transmute(act as &mut _)}; + let tmp: &mut ClientConnector = unsafe { mem::transmute(act as &mut _) }; for (key, waiters) in &mut tmp.waiters { while let Some(waiter) = waiters.pop_front() { - if waiter.tx.is_canceled() { continue } + if waiter.tx.is_canceled() { + continue; + } match act.acquire(key) { Acquire::Acquired(mut conn) => { // use existing connection act.stats.reused += 1; - conn.pool = Some( - AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)))); + conn.pool = + Some(AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)))); let _ = waiter.tx.send(Ok(conn)); - }, + } Acquire::NotAvailable => { waiters.push_front(waiter); - break + break; } - Acquire::Available => - { - let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool))); + Acquire::Available => { + let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool))); - fut::WrapFuture::::actfuture( - Connector::from_registry() - .send(ResolveConnect::host_and_port(&conn.0.host, conn.0.port) - .timeout(waiter.conn_timeout))) - .map_err(|_, _, _| ()) - .and_then(move |res, act, _| { - #[cfg(feature="alpn")] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - }, - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector.connect_async(&key.host, stream) - .then(move |res| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e))); - }, - Ok(stream) => { - let _ = waiter.tx.send(Ok( - Connection::new( - conn.0.clone(), - Some(conn), Box::new(stream)))); - } - } - Ok(()) - }) - .actfuture()) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), Some(conn), Box::new(stream)))); - fut::Either::B(fut::ok(())) - } - } - } + fut::WrapFuture::::actfuture( + Connector::from_registry().send( + ResolveConnect::host_and_port(&conn.0.host, conn.0.port) + .timeout(waiter.conn_timeout), + ), + ).map_err(|_, _, _| ()) + .and_then(move |res, act, _| { + #[cfg_attr(rustfmt, rustfmt_skip)] + #[cfg(feature = "alpn")] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + fut::Either::A( + act.connector + .connect_async(&key.host, stream) + .then(move |res| { + match res { + Err(e) => { + let _ = waiter.tx.send( + Err(ClientConnectorError::SslError(e))); + } + Ok(stream) => { + let _ = waiter.tx.send( + Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + )), + ); + } + } + Ok(()) + }) + .actfuture(), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) + } + } + } - #[cfg(all(feature="tls", not(feature="alpn")))] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - }, - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector.connect_async(&conn.0.host, stream) - .then(|res| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e))); - }, - Ok(stream) => { - let _ = waiter.tx.send(Ok( - Connection::new( - conn.0.clone(), Some(conn), - Box::new(stream)))); - } - } - Ok(()) - }) - .into_actor(act)) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), Some(conn), Box::new(stream)))); - fut::Either::B(fut::ok(())) - } - } - } + #[cfg_attr(rustfmt, rustfmt_skip)] + #[cfg(all(feature = "tls", not(feature = "alpn")))] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + fut::Either::A( + act.connector + .connect_async(&conn.0.host, stream) + .then(|res| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e), + )); + } + Ok(stream) => { + let _ = waiter.tx.send( + Ok(Connection::new( + conn.0.clone(), Some(conn), + Box::new(stream), + )), + ); + } + } + Ok(()) + }) + .into_actor(act), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) + } + } + } - #[cfg(not(any(feature="alpn", feature="tls")))] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::err(()) - }, - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let _ = waiter.tx.send( - Err(ClientConnectorError::SslIsNotSupported)); - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), Some(conn), Box::new(stream)))); - }; - fut::ok(()) - }, - } - }) - .spawn(ctx); - } + #[cfg_attr(rustfmt, rustfmt_skip)] + #[cfg(not(any(feature = "alpn", feature = "tls")))] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::err(()) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + let _ = waiter.tx.send(Err(ClientConnectorError::SslIsNotSupported)); + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + }; + fut::ok(()) + } + } + }) + .spawn(ctx); + } } } } @@ -954,7 +1020,7 @@ impl Protocol { fn port(&self) -> u16 { match *self { Protocol::Http | Protocol::Ws => 80, - Protocol::Https | Protocol::Wss => 443 + Protocol::Https | Protocol::Wss => 443, } } } @@ -968,7 +1034,11 @@ struct Key { impl Key { fn empty() -> Key { - Key{host: String::new(), port: 0, ssl: false} + Key { + host: String::new(), + port: 0, + ssl: false, + } } } @@ -1035,7 +1105,10 @@ impl Pool { if self.to_close.borrow().is_empty() { None } else { - Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new())) + Some(mem::replace( + &mut *self.to_close.borrow_mut(), + Vec::new(), + )) } } @@ -1043,7 +1116,10 @@ impl Pool { if self.to_release.borrow().is_empty() { None } else { - Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new())) + Some(mem::replace( + &mut *self.to_release.borrow_mut(), + Vec::new(), + )) } } @@ -1072,7 +1148,6 @@ impl Pool { } } - pub struct Connection { key: Key, stream: Box, @@ -1088,7 +1163,12 @@ impl fmt::Debug for Connection { impl Connection { fn new(key: Key, pool: Option, stream: Box) -> Self { - Connection {key, stream, pool, ts: Instant::now()} + Connection { + key, + stream, + pool, + ts: Instant::now(), + } } pub fn stream(&mut self) -> &mut IoStream { diff --git a/src/client/mod.rs b/src/client/mod.rs index 4608e6a9..436fcf20 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -28,33 +28,30 @@ //! ``` mod connector; mod parser; +mod pipeline; mod request; mod response; -mod pipeline; mod writer; +pub use self::connector::{ClientConnector, ClientConnectorError, ClientConnectorStats, + Connect, Connection, Pause, Resume}; +pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; -pub use self::connector::{ - Connect, Pause, Resume, - Connection, ClientConnector, ClientConnectorError, ClientConnectorStats}; pub(crate) use self::writer::HttpClientWriter; -pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; use error::ResponseError; use http::Method; use httpresponse::HttpResponse; - /// Convert `SendRequestError` to a `HttpResponse` impl ResponseError for SendRequestError { fn error_response(&self) -> HttpResponse { match *self { SendRequestError::Connector(_) => HttpResponse::BadGateway(), _ => HttpResponse::InternalServerError(), - } - .into() + }.into() } } diff --git a/src/client/parser.rs b/src/client/parser.rs index 6feb0cc7..0d4da4c4 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -1,14 +1,14 @@ -use std::mem; -use httparse; -use http::{Version, HttpTryFrom, HeaderMap, StatusCode}; -use http::header::{self, HeaderName, HeaderValue}; use bytes::{Bytes, BytesMut}; -use futures::{Poll, Async}; +use futures::{Async, Poll}; +use http::header::{self, HeaderName, HeaderValue}; +use http::{HeaderMap, HttpTryFrom, StatusCode, Version}; +use httparse; +use std::mem; use error::{ParseError, PayloadError}; +use server::h1::{chunked, Decoder}; use server::{utils, IoStream}; -use server::h1::{Decoder, chunked}; use super::ClientResponse; use super::response::ClientMessage; @@ -24,28 +24,26 @@ pub struct HttpResponseParser { #[derive(Debug, Fail)] pub enum HttpResponseParserError { /// Server disconnected - #[fail(display="Server disconnected")] + #[fail(display = "Server disconnected")] Disconnect, - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] Error(#[cause] ParseError), } impl HttpResponseParser { - - pub fn parse(&mut self, io: &mut T, buf: &mut BytesMut) - -> Poll - where T: IoStream + pub fn parse( + &mut self, io: &mut T, buf: &mut BytesMut + ) -> Poll + where + T: IoStream, { // if buf is empty parse_message will always return NotReady, let's avoid that if buf.is_empty() { match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => - return Err(HttpResponseParserError::Disconnect), + Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect), Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => - return Ok(Async::NotReady), - Err(err) => - return Err(HttpResponseParserError::Error(err.into())) + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(err) => return Err(HttpResponseParserError::Error(err.into())), } } @@ -56,27 +54,31 @@ impl HttpResponseParser { Async::Ready((msg, decoder)) => { self.decoder = decoder; return Ok(Async::Ready(msg)); - }, + } Async::NotReady => { if buf.capacity() >= MAX_BUFFER_SIZE { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => - return Err(HttpResponseParserError::Disconnect), + Ok(Async::Ready(0)) => { + return Err(HttpResponseParserError::Disconnect) + } Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => - return Err(HttpResponseParserError::Error(err.into())), + Err(err) => { + return Err(HttpResponseParserError::Error(err.into())) + } } - }, + } } } } - pub fn parse_payload(&mut self, io: &mut T, buf: &mut BytesMut) - -> Poll, PayloadError> - where T: IoStream + pub fn parse_payload( + &mut self, io: &mut T, buf: &mut BytesMut + ) -> Poll, PayloadError> + where + T: IoStream, { if self.decoder.is_some() { loop { @@ -89,18 +91,17 @@ impl HttpResponseParser { }; match self.decoder.as_mut().unwrap().decode(buf) { - Ok(Async::Ready(Some(b))) => - return Ok(Async::Ready(Some(b))), + Ok(Async::Ready(Some(b))) => return Ok(Async::Ready(Some(b))), Ok(Async::Ready(None)) => { self.decoder.take(); - return Ok(Async::Ready(None)) + return Ok(Async::Ready(None)); } Ok(Async::NotReady) => { if not_ready { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } if stream_finished { - return Err(PayloadError::Incomplete) + return Err(PayloadError::Incomplete); } } Err(err) => return Err(err.into()), @@ -111,16 +112,19 @@ impl HttpResponseParser { } } - fn parse_message(buf: &mut BytesMut) - -> Poll<(ClientResponse, Option), ParseError> - { + fn parse_message( + buf: &mut BytesMut + ) -> Poll<(ClientResponse, Option), ParseError> { // Parse http message let bytes_ptr = buf.as_ref().as_ptr() as usize; let mut headers: [httparse::Header; MAX_HEADERS] = - unsafe{mem::uninitialized()}; + unsafe { mem::uninitialized() }; let (len, version, status, headers_len) = { - let b = unsafe{ let b: &[u8] = buf; mem::transmute(b) }; + let b = unsafe { + let b: &[u8] = buf; + mem::transmute(b) + }; let mut resp = httparse::Response::new(&mut headers); match resp.parse(b)? { httparse::Status::Complete(len) => { @@ -147,10 +151,11 @@ impl HttpResponseParser { 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)) }; + HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end)) + }; hdrs.append(name, value); } else { - return Err(ParseError::Header) + return Err(ParseError::Header); } } @@ -163,11 +168,11 @@ impl HttpResponseParser { Some(Decoder::length(len)) } else { debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header) + return Err(ParseError::Header); } } else { debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header) + return Err(ParseError::Header); } } else if chunked(&hdrs)? { // Chunked encoding @@ -177,15 +182,25 @@ impl HttpResponseParser { }; if let Some(decoder) = decoder { - Ok(Async::Ready( - (ClientResponse::new( - ClientMessage{status, version, - headers: hdrs, cookies: None}), Some(decoder)))) + Ok(Async::Ready(( + ClientResponse::new(ClientMessage { + status, + version, + headers: hdrs, + cookies: None, + }), + Some(decoder), + ))) } else { - Ok(Async::Ready( - (ClientResponse::new( - ClientMessage{status, version, - headers: hdrs, cookies: None}), None))) + Ok(Async::Ready(( + ClientResponse::new(ClientMessage { + status, + version, + headers: hdrs, + cookies: None, + }), + None, + ))) } } } diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 5581e3b3..05fcf812 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -1,26 +1,26 @@ -use std::{io, mem}; -use std::time::Duration; use bytes::{Bytes, BytesMut}; -use http::header::CONTENT_ENCODING; -use futures::{Async, Future, Poll}; use futures::unsync::oneshot; +use futures::{Async, Future, Poll}; +use http::header::CONTENT_ENCODING; +use std::time::Duration; +use std::{io, mem}; use tokio_core::reactor::Timeout; use actix::prelude::*; -use error::Error; +use super::HttpClientWriter; +use super::{ClientConnector, ClientConnectorError, Connect, Connection}; +use super::{ClientRequest, ClientResponse}; +use super::{HttpResponseParser, HttpResponseParserError}; use body::{Body, BodyStream}; -use context::{Frame, ActorHttpContext}; +use context::{ActorHttpContext, Frame}; +use error::Error; +use error::PayloadError; use header::ContentEncoding; use httpmessage::HttpMessage; -use error::PayloadError; use server::WriterState; -use server::shared::SharedBytes; use server::encoding::PayloadStream; -use super::{ClientRequest, ClientResponse}; -use super::{Connect, Connection, ClientConnector, ClientConnectorError}; -use super::HttpClientWriter; -use super::{HttpResponseParser, HttpResponseParserError}; +use server::shared::SharedBytes; /// A set of errors that can occur during request sending and response reading #[derive(Fail, Debug)] @@ -29,13 +29,13 @@ pub enum SendRequestError { #[fail(display = "Timeout while waiting for response")] Timeout, /// Failed to connect to host - #[fail(display="Failed to connect to host: {}", _0)] + #[fail(display = "Failed to connect to host: {}", _0)] Connector(#[cause] ClientConnectorError), /// Error parsing response - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] ParseError(#[cause] HttpResponseParserError), /// Error reading response payload - #[fail(display="Error reading response payload: {}", _0)] + #[fail(display = "Error reading response payload: {}", _0)] Io(#[cause] io::Error), } @@ -79,25 +79,27 @@ impl SendRequest { SendRequest::with_connector(req, ClientConnector::from_registry()) } - pub(crate) fn with_connector(req: ClientRequest, conn: Addr) - -> SendRequest - { - SendRequest{req, conn, - state: State::New, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), + pub(crate) fn with_connector( + req: ClientRequest, conn: Addr + ) -> SendRequest { + SendRequest { + req, + conn, + state: State::New, + timeout: None, + wait_timeout: Duration::from_secs(5), + conn_timeout: Duration::from_secs(1), } } - pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest - { - SendRequest{req, - state: State::Connection(conn), - conn: ClientConnector::from_registry(), - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), + pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest { + SendRequest { + req, + state: State::Connection(conn), + conn: ClientConnector::from_registry(), + timeout: None, + wait_timeout: Duration::from_secs(5), + conn_timeout: Duration::from_secs(1), } } @@ -139,25 +141,27 @@ impl Future for SendRequest { let state = mem::replace(&mut self.state, State::None); match state { - State::New => + State::New => { self.state = State::Connect(self.conn.send(Connect { uri: self.req.uri().clone(), wait_timeout: self.wait_timeout, conn_timeout: self.conn_timeout, - })), + })) + } State::Connect(mut conn) => match conn.poll() { Ok(Async::NotReady) => { self.state = State::Connect(conn); return Ok(Async::NotReady); - }, + } Ok(Async::Ready(result)) => match result { - Ok(stream) => { - self.state = State::Connection(stream) - }, + Ok(stream) => self.state = State::Connection(stream), Err(err) => return Err(err.into()), }, - Err(_) => return Err(SendRequestError::Connector( - ClientConnectorError::Disconnected)) + Err(_) => { + return Err(SendRequestError::Connector( + ClientConnectorError::Disconnected, + )) + } }, State::Connection(conn) => { let mut writer = HttpClientWriter::new(SharedBytes::default()); @@ -169,12 +173,13 @@ impl Future for SendRequest { _ => IoBody::Done, }; - let timeout = self.timeout.take().unwrap_or_else(|| - Timeout::new( - Duration::from_secs(5), Arbiter::handle()).unwrap()); + let timeout = self.timeout.take().unwrap_or_else(|| { + Timeout::new(Duration::from_secs(5), Arbiter::handle()).unwrap() + }); let pl = Box::new(Pipeline { - body, writer, + body, + writer, conn: Some(conn), parser: Some(HttpResponseParser::default()), parser_buf: BytesMut::new(), @@ -186,22 +191,22 @@ impl Future for SendRequest { timeout: Some(timeout), }); self.state = State::Send(pl); - }, + } State::Send(mut pl) => { - pl.poll_write() - .map_err(|e| io::Error::new( - io::ErrorKind::Other, format!("{}", e).as_str()))?; + pl.poll_write().map_err(|e| { + io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()) + })?; match pl.parse() { Ok(Async::Ready(mut resp)) => { resp.set_pipeline(pl); - return Ok(Async::Ready(resp)) - }, + return Ok(Async::Ready(resp)); + } Ok(Async::NotReady) => { self.state = State::Send(pl); - return Ok(Async::NotReady) - }, - Err(err) => return Err(SendRequestError::ParseError(err)) + return Ok(Async::NotReady); + } + Err(err) => return Err(SendRequestError::ParseError(err)), } } State::None => unreachable!(), @@ -210,7 +215,6 @@ impl Future for SendRequest { } } - pub(crate) struct Pipeline { body: IoBody, conn: Option, @@ -254,7 +258,6 @@ impl RunningState { } impl Pipeline { - fn release_conn(&mut self) { if let Some(conn) = self.conn.take() { conn.release() @@ -264,15 +267,22 @@ impl Pipeline { #[inline] fn parse(&mut self) -> Poll { if let Some(ref mut conn) = self.conn { - match self.parser.as_mut().unwrap().parse(conn, &mut self.parser_buf) { + match self.parser + .as_mut() + .unwrap() + .parse(conn, &mut self.parser_buf) + { Ok(Async::Ready(resp)) => { // check content-encoding if self.should_decompress { if let Some(enc) = resp.headers().get(CONTENT_ENCODING) { if let Ok(enc) = enc.to_str() { match ContentEncoding::from(enc) { - ContentEncoding::Auto | ContentEncoding::Identity => (), - enc => self.decompress = Some(PayloadStream::new(enc)), + ContentEncoding::Auto + | ContentEncoding::Identity => (), + enc => { + self.decompress = Some(PayloadStream::new(enc)) + } } } } @@ -290,9 +300,10 @@ impl Pipeline { #[inline] pub fn poll(&mut self) -> Poll, PayloadError> { if self.conn.is_none() { - return Ok(Async::Ready(None)) + return Ok(Async::Ready(None)); } - let conn: &mut Connection = unsafe{ mem::transmute(self.conn.as_mut().unwrap())}; + let conn: &mut Connection = + unsafe { mem::transmute(self.conn.as_mut().unwrap()) }; let mut need_run = false; @@ -302,15 +313,18 @@ impl Pipeline { { Async::NotReady => need_run = true, Async::Ready(_) => { - let _ = self.poll_timeout() - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?; + let _ = self.poll_timeout().map_err(|e| { + io::Error::new(io::ErrorKind::Other, format!("{}", e)) + })?; } } // need read? if self.parser.is_some() { loop { - match self.parser.as_mut().unwrap() + match self.parser + .as_mut() + .unwrap() .parse_payload(conn, &mut self.parser_buf)? { Async::Ready(Some(b)) => { @@ -318,17 +332,20 @@ impl Pipeline { match decompress.feed_data(b) { Ok(Some(b)) => return Ok(Async::Ready(Some(b))), Ok(None) => return Ok(Async::NotReady), - Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => - continue, + Err(ref err) + if err.kind() == io::ErrorKind::WouldBlock => + { + continue + } Err(err) => return Err(err.into()), } } else { - return Ok(Async::Ready(Some(b))) + return Ok(Async::Ready(Some(b))); } - }, + } Async::Ready(None) => { let _ = self.parser.take(); - break + break; } Async::NotReady => return Ok(Async::NotReady), } @@ -340,7 +357,7 @@ impl Pipeline { let res = decompress.feed_eof(); if let Some(b) = res? { self.release_conn(); - return Ok(Async::Ready(Some(b))) + return Ok(Async::Ready(Some(b))); } } @@ -357,7 +374,7 @@ impl Pipeline { match self.timeout.as_mut().unwrap().poll() { Ok(Async::Ready(())) => Err(SendRequestError::Timeout), Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => unreachable!() + Err(_) => unreachable!(), } } else { Ok(Async::NotReady) @@ -367,29 +384,27 @@ impl Pipeline { #[inline] fn poll_write(&mut self) -> Poll<(), Error> { if self.write_state == RunningState::Done || self.conn.is_none() { - return Ok(Async::Ready(())) + return Ok(Async::Ready(())); } let mut done = false; if self.drain.is_none() && self.write_state != RunningState::Paused { 'outter: loop { let result = match mem::replace(&mut self.body, IoBody::Done) { - IoBody::Payload(mut body) => { - match body.poll()? { - Async::Ready(None) => { - self.writer.write_eof()?; - self.disconnected = true; - break - }, - Async::Ready(Some(chunk)) => { - self.body = IoBody::Payload(body); - self.writer.write(chunk.into())? - } - Async::NotReady => { - done = true; - self.body = IoBody::Payload(body); - break - }, + IoBody::Payload(mut body) => match body.poll()? { + Async::Ready(None) => { + self.writer.write_eof()?; + self.disconnected = true; + break; + } + Async::Ready(Some(chunk)) => { + self.body = IoBody::Payload(body); + self.writer.write(chunk.into())? + } + Async::NotReady => { + done = true; + self.body = IoBody::Payload(body); + break; } }, IoBody::Actor(mut ctx) => { @@ -400,7 +415,7 @@ impl Pipeline { Async::Ready(Some(vec)) => { if vec.is_empty() { self.body = IoBody::Actor(ctx); - break + break; } let mut res = None; for frame in vec { @@ -409,52 +424,53 @@ impl Pipeline { // info.context = Some(ctx); self.disconnected = true; self.writer.write_eof()?; - break 'outter - }, - Frame::Chunk(Some(chunk)) => - res = Some(self.writer.write(chunk)?), + break 'outter; + } + Frame::Chunk(Some(chunk)) => { + res = Some(self.writer.write(chunk)?) + } Frame::Drain(fut) => self.drain = Some(fut), } } self.body = IoBody::Actor(ctx); if self.drain.is_some() { self.write_state.resume(); - break + break; } res.unwrap() - }, + } Async::Ready(None) => { done = true; - break + break; } Async::NotReady => { done = true; self.body = IoBody::Actor(ctx); - break + break; } } - }, + } IoBody::Done => { self.disconnected = true; done = true; - break + break; } }; match result { WriterState::Pause => { self.write_state.pause(); - break + break; } - WriterState::Done => { - self.write_state.resume() - }, + WriterState::Done => self.write_state.resume(), } } } // flush io but only if we need to - match self.writer.poll_completed(self.conn.as_mut().unwrap(), false) { + match self.writer + .poll_completed(self.conn.as_mut().unwrap(), false) + { Ok(Async::Ready(_)) => { if self.disconnected { self.write_state = RunningState::Done; @@ -472,7 +488,7 @@ impl Pipeline { } else { Ok(Async::NotReady) } - }, + } Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => Err(err.into()), } diff --git a/src/client/request.rs b/src/client/request.rs index 79bbd249..526a8d99 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -1,26 +1,26 @@ -use std::{fmt, mem}; use std::fmt::Write as FmtWrite; use std::io::Write; use std::time::Duration; +use std::{fmt, mem}; use actix::{Addr, Unsync}; +use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; -use bytes::{Bytes, BytesMut, BufMut}; use futures::Stream; -use serde_json; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use serde::Serialize; +use serde_json; use url::Url; -use percent_encoding::{USERINFO_ENCODE_SET, percent_encode}; +use super::connector::{ClientConnector, Connection}; +use super::pipeline::SendRequest; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; +use http::header::{self, HeaderName, HeaderValue}; +use http::{uri, Error as HttpError, HeaderMap, HttpTryFrom, Method, Uri, Version}; use httpmessage::HttpMessage; use httprequest::HttpRequest; -use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError}; -use http::header::{self, HeaderName, HeaderValue}; -use super::pipeline::SendRequest; -use super::connector::{Connection, ClientConnector}; /// An HTTP Client Request /// @@ -72,7 +72,6 @@ enum ConnectionType { } impl Default for ClientRequest { - fn default() -> ClientRequest { ClientRequest { uri: Uri::default(), @@ -92,7 +91,6 @@ impl Default for ClientRequest { } impl ClientRequest { - /// Create request builder for `GET` request pub fn get>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); @@ -130,14 +128,13 @@ impl ClientRequest { } impl ClientRequest { - /// Create client request builder pub fn build() -> ClientRequestBuilder { ClientRequestBuilder { request: Some(ClientRequest::default()), err: None, cookies: None, - default_headers: true + default_headers: true, } } @@ -259,8 +256,11 @@ impl ClientRequest { impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!(f, "\nClientRequest {:?} {}:{}", - self.version, self.method, self.uri); + let res = writeln!( + f, + "\nClientRequest {:?} {}:{}", + self.version, self.method, self.uri + ); let _ = writeln!(f, " headers:"); for (key, val) in self.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); @@ -277,7 +277,7 @@ pub struct ClientRequestBuilder { request: Option, err: Option, cookies: Option, - default_headers: bool + default_headers: bool, } impl ClientRequestBuilder { @@ -300,8 +300,8 @@ impl ClientRequestBuilder { if let Some(parts) = parts(&mut self.request, &self.err) { parts.uri = uri; } - }, - Err(e) => self.err = Some(e.into(),), + } + Err(e) => self.err = Some(e.into()), } self } @@ -318,8 +318,8 @@ impl ClientRequestBuilder { /// Set HTTP method of this request. #[inline] pub fn get_method(&mut self) -> &Method { - let parts = parts(&mut self.request, &self.err) - .expect("cannot reuse request builder"); + let parts = + parts(&mut self.request, &self.err).expect("cannot reuse request builder"); &parts.method } @@ -351,11 +351,12 @@ impl ClientRequestBuilder { /// } /// ``` #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self - { + pub fn set(&mut self, hdr: H) -> &mut Self { if let Some(parts) = parts(&mut self.request, &self.err) { match hdr.try_into() { - Ok(value) => { parts.headers.insert(H::name(), value); } + Ok(value) => { + parts.headers.insert(H::name(), value); + } Err(e) => self.err = Some(e.into()), } } @@ -382,15 +383,17 @@ impl ClientRequestBuilder { /// } /// ``` pub fn header(&mut self, key: K, value: V) -> &mut Self - where HeaderName: HttpTryFrom, V: IntoHeaderValue + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, { if let Some(parts) = parts(&mut self.request, &self.err) { match HeaderName::try_from(key) { - Ok(key) => { - match value.try_into() { - Ok(value) => { parts.headers.append(key, value); } - Err(e) => self.err = Some(e.into()), + Ok(key) => match value.try_into() { + Ok(value) => { + parts.headers.append(key, value); } + Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), }; @@ -400,15 +403,17 @@ impl ClientRequestBuilder { /// Set a header. pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where HeaderName: HttpTryFrom, V: IntoHeaderValue + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, { if let Some(parts) = parts(&mut self.request, &self.err) { match HeaderName::try_from(key) { - Ok(key) => { - match value.try_into() { - Ok(value) => { parts.headers.insert(key, value); } - Err(e) => self.err = Some(e.into()), + Ok(key) => match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); } + Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), }; @@ -448,11 +453,14 @@ impl ClientRequestBuilder { /// Set request's content type #[inline] pub fn content_type(&mut self, value: V) -> &mut Self - where HeaderValue: HttpTryFrom + where + HeaderValue: HttpTryFrom, { if let Some(parts) = parts(&mut self.request, &self.err) { match HeaderValue::try_from(value) { - Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); }, + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } Err(e) => self.err = Some(e.into()), }; } @@ -491,7 +499,10 @@ impl ClientRequestBuilder { jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); + self.cookies + .as_mut() + .unwrap() + .add(cookie.into_owned()); } self } @@ -551,7 +562,8 @@ impl ClientRequestBuilder { /// This method calls provided closure with builder reference if /// value is `true`. pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where F: FnOnce(&mut ClientRequestBuilder) + where + F: FnOnce(&mut ClientRequestBuilder), { if value { f(self); @@ -562,7 +574,8 @@ impl ClientRequestBuilder { /// This method calls provided closure with builder reference if /// value is `Some`. pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where F: FnOnce(T, &mut ClientRequestBuilder) + where + F: FnOnce(T, &mut ClientRequestBuilder), { if let Some(val) = value { f(val, self); @@ -575,18 +588,20 @@ impl ClientRequestBuilder { /// `ClientRequestBuilder` can not be used after this call. pub fn body>(&mut self, body: B) -> Result { if let Some(e) = self.err.take() { - return Err(e.into()) + return Err(e.into()); } if self.default_headers { // enable br only for https - let https = - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.uri.scheme_part() - .map(|s| s == &uri::Scheme::HTTPS).unwrap_or(true) - } else { - true - }; + let https = if let Some(parts) = parts(&mut self.request, &self.err) { + parts + .uri + .scheme_part() + .map(|s| s == &uri::Scheme::HTTPS) + .unwrap_or(true) + } else { + true + }; if https { self.header(header::ACCEPT_ENCODING, "br, gzip, deflate"); @@ -595,7 +610,9 @@ impl ClientRequestBuilder { } } - let mut request = self.request.take().expect("cannot reuse request builder"); + let mut request = self.request + .take() + .expect("cannot reuse request builder"); // set cookies if let Some(ref mut jar) = self.cookies { @@ -606,7 +623,9 @@ impl ClientRequestBuilder { let _ = write!(&mut cookie, "; {}={}", name, value); } request.headers.insert( - header::COOKIE, HeaderValue::from_str(&cookie.as_str()[2..]).unwrap()); + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); } request.body = body.into(); Ok(request) @@ -634,10 +653,13 @@ impl ClientRequestBuilder { /// /// `ClientRequestBuilder` can not be used after this call. pub fn streaming(&mut self, stream: S) -> Result - where S: Stream + 'static, - E: Into, + where + S: Stream + 'static, + E: Into, { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) + self.body(Body::Streaming(Box::new( + stream.map_err(|e| e.into()), + ))) } /// Set an empty body and generate `ClientRequest` @@ -653,17 +675,17 @@ impl ClientRequestBuilder { request: self.request.take(), err: self.err.take(), cookies: self.cookies.take(), - default_headers: self.default_headers + default_headers: self.default_headers, } } } #[inline] -fn parts<'a>(parts: &'a mut Option, err: &Option) - -> Option<&'a mut ClientRequest> -{ +fn parts<'a>( + parts: &'a mut Option, err: &Option +) -> Option<&'a mut ClientRequest> { if err.is_some() { - return None + return None; } parts.as_mut() } @@ -671,8 +693,11 @@ fn parts<'a>(parts: &'a mut Option, err: &Option) impl fmt::Debug for ClientRequestBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(ref parts) = self.request { - let res = writeln!(f, "\nClientRequestBuilder {:?} {}:{}", - parts.version, parts.method, parts.uri); + let res = writeln!( + f, + "\nClientRequestBuilder {:?} {}:{}", + parts.version, parts.method, parts.uri + ); let _ = writeln!(f, " headers:"); for (key, val) in parts.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); diff --git a/src/client/response.rs b/src/client/response.rs index a0ecb8a6..4d186d19 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,19 +1,18 @@ -use std::{fmt, str}; -use std::rc::Rc; use std::cell::UnsafeCell; +use std::rc::Rc; +use std::{fmt, str}; use bytes::Bytes; use cookie::Cookie; use futures::{Async, Poll, Stream}; -use http::{HeaderMap, StatusCode, Version}; use http::header::{self, HeaderValue}; +use http::{HeaderMap, StatusCode, Version}; -use httpmessage::HttpMessage; use error::{CookieParseError, PayloadError}; +use httpmessage::HttpMessage; use super::pipeline::Pipeline; - pub(crate) struct ClientMessage { pub status: StatusCode, pub version: Version, @@ -22,7 +21,6 @@ pub(crate) struct ClientMessage { } impl Default for ClientMessage { - fn default() -> ClientMessage { ClientMessage { status: StatusCode::OK, @@ -45,7 +43,6 @@ impl HttpMessage for ClientResponse { } impl ClientResponse { - pub(crate) fn new(msg: ClientMessage) -> ClientResponse { ClientResponse(Rc::new(UnsafeCell::new(msg)), None) } @@ -56,13 +53,13 @@ impl ClientResponse { #[inline] fn as_ref(&self) -> &ClientMessage { - unsafe{ &*self.0.get() } + unsafe { &*self.0.get() } } #[inline] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] fn as_mut(&self) -> &mut ClientMessage { - unsafe{ &mut *self.0.get() } + unsafe { &mut *self.0.get() } } /// Get the HTTP version of this response. @@ -96,7 +93,7 @@ impl ClientResponse { if let Ok(cookies) = self.cookies() { for cookie in cookies { if cookie.name() == name { - return Some(cookie) + return Some(cookie); } } } @@ -107,7 +104,11 @@ impl ClientResponse { impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = writeln!( - f, "\nClientResponse {:?} {}", self.version(), self.status()); + f, + "\nClientResponse {:?} {}", + self.version(), + self.status() + ); let _ = writeln!(f, " headers:"); for (key, val) in self.headers().iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); @@ -138,9 +139,13 @@ mod tests { fn test_debug() { let resp = ClientResponse::new(ClientMessage::default()); resp.as_mut().headers.insert( - header::COOKIE, HeaderValue::from_static("cookie1=value1")); + header::COOKIE, + HeaderValue::from_static("cookie1=value1"), + ); resp.as_mut().headers.insert( - header::COOKIE, HeaderValue::from_static("cookie2=value2")); + header::COOKIE, + HeaderValue::from_static("cookie2=value2"), + ); let dbg = format!("{:?}", resp); assert!(dbg.contains("ClientResponse")); diff --git a/src/client/writer.rs b/src/client/writer.rs index d1c4bb22..8d554b9b 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -1,30 +1,29 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] -use std::io::{self, Write}; use std::cell::RefCell; use std::fmt::Write as FmtWrite; +use std::io::{self, Write}; -use time::{self, Duration}; -use bytes::{BytesMut, BufMut}; -use futures::{Async, Poll}; -use tokio_io::AsyncWrite; -use http::{Version, HttpTryFrom}; -use http::header::{HeaderValue, DATE, - CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; -use flate2::Compression; -use flate2::write::{GzEncoder, DeflateEncoder}; -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] use brotli2::write::BrotliEncoder; +use bytes::{BufMut, BytesMut}; +use flate2::Compression; +use flate2::write::{DeflateEncoder, GzEncoder}; +use futures::{Async, Poll}; +use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, + TRANSFER_ENCODING}; +use http::{HttpTryFrom, Version}; +use time::{self, Duration}; +use tokio_io::AsyncWrite; -use body::{Body, Binary}; +use body::{Binary, Body}; use header::ContentEncoding; use server::WriterState; -use server::shared::SharedBytes; use server::encoding::{ContentEncoder, TransferEncoding}; +use server::shared::SharedBytes; use client::ClientRequest; - const AVERAGE_HEADER_SIZE: usize = 30; bitflags! { @@ -46,7 +45,6 @@ pub(crate) struct HttpClientWriter { } impl HttpClientWriter { - pub fn new(buffer: SharedBytes) -> HttpClientWriter { let encoder = ContentEncoder::Identity(TransferEncoding::eof(buffer.clone())); HttpClientWriter { @@ -64,24 +62,26 @@ impl HttpClientWriter { } // pub fn keepalive(&self) -> bool { - // self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) - // } + // self.flags.contains(Flags::KEEPALIVE) && + // !self.flags.contains(Flags::UPGRADE) } - fn write_to_stream(&mut self, stream: &mut T) -> io::Result { + fn write_to_stream( + &mut self, stream: &mut T + ) -> io::Result { while !self.buffer.is_empty() { match stream.write(self.buffer.as_ref()) { Ok(0) => { self.disconnected(); return Ok(WriterState::Done); - }, + } Ok(n) => { let _ = self.buffer.split_to(n); - }, + } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { if self.buffer.len() > self.buffer_capacity { - return Ok(WriterState::Pause) + return Ok(WriterState::Pause); } else { - return Ok(WriterState::Done) + return Ok(WriterState::Done); } } Err(err) => return Err(err), @@ -92,7 +92,6 @@ impl HttpClientWriter { } impl HttpClientWriter { - pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> { // prepare task self.flags.insert(Flags::STARTED); @@ -105,10 +104,16 @@ impl HttpClientWriter { // render message { // status line - writeln!(self.buffer, "{} {} {:?}\r", - msg.method(), - msg.uri().path_and_query().map(|u| u.as_str()).unwrap_or("/"), - msg.version())?; + writeln!( + self.buffer, + "{} {} {:?}\r", + msg.method(), + msg.uri() + .path_and_query() + .map(|u| u.as_str()) + .unwrap_or("/"), + msg.version() + )?; // write headers let mut buffer = self.buffer.get_mut(); @@ -173,15 +178,17 @@ impl HttpClientWriter { if self.encoder.is_eof() { Ok(()) } else { - Err(io::Error::new(io::ErrorKind::Other, - "Last payload item, but eof is not reached")) + Err(io::Error::new( + io::ErrorKind::Other, + "Last payload item, but eof is not reached", + )) } } #[inline] - pub fn poll_completed(&mut self, stream: &mut T, shutdown: bool) - -> Poll<(), io::Error> - { + pub fn poll_completed( + &mut self, stream: &mut T, shutdown: bool + ) -> Poll<(), io::Error> { match self.write_to_stream(stream) { Ok(WriterState::Done) => { if shutdown { @@ -189,14 +196,13 @@ impl HttpClientWriter { } else { Ok(Async::Ready(())) } - }, + } Ok(WriterState::Pause) => Ok(Async::NotReady), - Err(err) => Err(err) + Err(err) => Err(err), } } } - fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder { let version = req.version(); let mut body = req.replace_body(Body::Empty); @@ -206,21 +212,25 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder Body::Empty => { req.headers_mut().remove(CONTENT_LENGTH); TransferEncoding::length(0, buf) - }, + } Body::Binary(ref mut bytes) => { if encoding.is_compression() { let tmp = SharedBytes::default(); let transfer = TransferEncoding::eof(tmp.clone()); let mut enc = match encoding { ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::default())), - ContentEncoding::Gzip => ContentEncoder::Gzip( - GzEncoder::new(transfer, Compression::default())), - #[cfg(feature="brotli")] - ContentEncoding::Br => ContentEncoder::Br( - BrotliEncoder::new(transfer, 5)), + DeflateEncoder::new(transfer, Compression::default()), + ), + ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( + transfer, + Compression::default(), + )), + #[cfg(feature = "brotli")] + ContentEncoding::Br => { + ContentEncoder::Br(BrotliEncoder::new(transfer, 5)) + } ContentEncoding::Identity => ContentEncoder::Identity(transfer), - ContentEncoding::Auto => unreachable!() + ContentEncoding::Auto => unreachable!(), }; // TODO return error! let _ = enc.write(bytes.clone()); @@ -228,21 +238,26 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder *bytes = Binary::from(tmp.take()); req.headers_mut().insert( - CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); + CONTENT_ENCODING, + HeaderValue::from_static(encoding.as_str()), + ); encoding = ContentEncoding::Identity; } let mut b = BytesMut::new(); let _ = write!(b, "{}", bytes.len()); req.headers_mut().insert( - CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); + CONTENT_LENGTH, + HeaderValue::try_from(b.freeze()).unwrap(), + ); TransferEncoding::eof(buf) - }, + } Body::Streaming(_) | Body::Actor(_) => { if req.upgrade() { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); } else { - req.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade")); + req.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("upgrade")); } if encoding != ContentEncoding::Identity { encoding = ContentEncoding::Identity; @@ -257,24 +272,31 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder if encoding.is_compression() { req.headers_mut().insert( - CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); + CONTENT_ENCODING, + HeaderValue::from_static(encoding.as_str()), + ); } req.replace_body(body); match encoding { - ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::default())), - ContentEncoding::Gzip => ContentEncoder::Gzip( - GzEncoder::new(transfer, Compression::default())), - #[cfg(feature="brotli")] - ContentEncoding::Br => ContentEncoder::Br( - BrotliEncoder::new(transfer, 5)), - ContentEncoding::Identity | ContentEncoding::Auto => ContentEncoder::Identity(transfer), + ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( + transfer, + Compression::default(), + )), + ContentEncoding::Gzip => { + ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default())) + } + #[cfg(feature = "brotli")] + ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)), + ContentEncoding::Identity | ContentEncoding::Auto => { + ContentEncoder::Identity(transfer) + } } } -fn streaming_encoding(buf: SharedBytes, version: Version, req: &mut ClientRequest) - -> TransferEncoding { +fn streaming_encoding( + buf: SharedBytes, version: Version, req: &mut ClientRequest +) -> TransferEncoding { if req.chunked() { // Enable transfer encoding req.headers_mut().remove(CONTENT_LENGTH); @@ -282,29 +304,28 @@ fn streaming_encoding(buf: SharedBytes, version: Version, req: &mut ClientReques req.headers_mut().remove(TRANSFER_ENCODING); TransferEncoding::eof(buf) } else { - req.headers_mut().insert( - TRANSFER_ENCODING, HeaderValue::from_static("chunked")); + req.headers_mut() + .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); TransferEncoding::chunked(buf) } } else { // if Content-Length is specified, then use it as length hint - let (len, chunked) = - if let Some(len) = req.headers().get(CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - (Some(len), false) - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } + let (len, chunked) = if let Some(len) = req.headers().get(CONTENT_LENGTH) { + // Content-Length + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + (Some(len), false) } else { error!("illegal Content-Length: {:?}", len); (None, false) } } else { - (None, true) - }; + error!("illegal Content-Length: {:?}", len); + (None, false) + } + } else { + (None, true) + }; if !chunked { if let Some(len) = len { @@ -316,10 +337,10 @@ fn streaming_encoding(buf: SharedBytes, version: Version, req: &mut ClientReques // Enable transfer encoding match version { Version::HTTP_11 => { - req.headers_mut().insert( - TRANSFER_ENCODING, HeaderValue::from_static("chunked")); + req.headers_mut() + .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); TransferEncoding::chunked(buf) - }, + } _ => { req.headers_mut().remove(TRANSFER_ENCODING); TransferEncoding::eof(buf) @@ -329,7 +350,6 @@ fn streaming_encoding(buf: SharedBytes, version: Version, req: &mut ClientReques } } - // "Sun, 06 Nov 1994 08:49:37 GMT".len() pub const DATE_VALUE_LENGTH: usize = 29; diff --git a/src/context.rs b/src/context.rs index 5958f891..b095c29b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,20 +1,19 @@ -use std::mem; -use std::marker::PhantomData; -use futures::{Async, Future, Poll}; use futures::sync::oneshot::Sender; use futures::unsync::oneshot; +use futures::{Async, Future, Poll}; use smallvec::SmallVec; +use std::marker::PhantomData; +use std::mem; -use actix::{Actor, ActorState, ActorContext, AsyncContext, - Addr, Handler, Message, SpawnHandle, Syn, Unsync}; +use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::fut::ActorFuture; -use actix::dev::{ContextImpl, ToEnvelope, SyncEnvelope}; +use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, + SpawnHandle, Syn, Unsync}; -use body::{Body, Binary}; +use body::{Binary, Body}; use error::{Error, ErrorInternalServerError}; use httprequest::HttpRequest; - pub trait ActorHttpContext: 'static { fn disconnected(&mut self); fn poll(&mut self) -> Poll>, Error>; @@ -36,7 +35,9 @@ impl Frame { } /// Execution context for http actors -pub struct HttpContext where A: Actor>, +pub struct HttpContext +where + A: Actor>, { inner: ContextImpl, stream: Option>, @@ -44,7 +45,9 @@ pub struct HttpContext where A: Actor>, disconnected: bool, } -impl ActorContext for HttpContext where A: Actor +impl ActorContext for HttpContext +where + A: Actor, { fn stop(&mut self) { self.inner.stop(); @@ -57,25 +60,29 @@ impl ActorContext for HttpContext where A: Actor } } -impl AsyncContext for HttpContext where A: Actor +impl AsyncContext for HttpContext +where + A: Actor, { #[inline] fn spawn(&mut self, fut: F) -> SpawnHandle - where F: ActorFuture + 'static + where + F: ActorFuture + 'static, { self.inner.spawn(fut) } #[inline] fn wait(&mut self, fut: F) - where F: ActorFuture + 'static + where + F: ActorFuture + 'static, { self.inner.wait(fut) } #[doc(hidden)] #[inline] fn waiting(&self) -> bool { - self.inner.waiting() || self.inner.state() == ActorState::Stopping || - self.inner.state() == ActorState::Stopped + self.inner.waiting() || self.inner.state() == ActorState::Stopping + || self.inner.state() == ActorState::Stopped } #[inline] fn cancel_future(&mut self, handle: SpawnHandle) -> bool { @@ -93,8 +100,10 @@ impl AsyncContext for HttpContext where A: Actor } } -impl HttpContext where A: Actor { - +impl HttpContext +where + A: Actor, +{ #[inline] pub fn new(req: HttpRequest, actor: A) -> HttpContext { HttpContext::from_request(req).actor(actor) @@ -114,8 +123,10 @@ impl HttpContext where A: Actor { } } -impl HttpContext where A: Actor { - +impl HttpContext +where + A: Actor, +{ /// Shared application state #[inline] pub fn state(&self) -> &S { @@ -175,8 +186,11 @@ impl HttpContext where A: Actor { } } -impl ActorHttpContext for HttpContext where A: Actor, S: 'static { - +impl ActorHttpContext for HttpContext +where + A: Actor, + S: 'static, +{ #[inline] fn disconnected(&mut self) { self.disconnected = true; @@ -184,9 +198,8 @@ impl ActorHttpContext for HttpContext where A: Actor, } fn poll(&mut self) -> Poll>, Error> { - let ctx: &mut HttpContext = unsafe { - mem::transmute(self as &mut HttpContext) - }; + let ctx: &mut HttpContext = + unsafe { mem::transmute(self as &mut HttpContext) }; if self.inner.alive() { match self.inner.poll(ctx) { @@ -207,8 +220,10 @@ impl ActorHttpContext for HttpContext where A: Actor, } impl ToEnvelope for HttpContext - where A: Actor> + Handler, - M: Message + Send + 'static, M::Result: Send, +where + A: Actor> + Handler, + M: Message + Send + 'static, + M::Result: Send, { fn pack(msg: M, tx: Option>) -> SyncEnvelope { SyncEnvelope::new(msg, tx) @@ -216,8 +231,9 @@ impl ToEnvelope for HttpContext } impl From> for Body - where A: Actor>, - S: 'static +where + A: Actor>, + S: 'static, { fn from(ctx: HttpContext) -> Body { Body::Actor(Box::new(ctx)) @@ -231,7 +247,10 @@ pub struct Drain { impl Drain { pub fn new(fut: oneshot::Receiver<()>) -> Self { - Drain { fut, _a: PhantomData } + Drain { + fut, + _a: PhantomData, + } } } @@ -241,10 +260,9 @@ impl ActorFuture for Drain { type Actor = A; #[inline] - fn poll(&mut self, - _: &mut A, - _: &mut ::Context) -> Poll - { + fn poll( + &mut self, _: &mut A, _: &mut ::Context + ) -> Poll { self.fut.poll().map_err(|_| ()) } } diff --git a/src/de.rs b/src/de.rs index 659dc10a..47a3f4ff 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,11 +1,10 @@ -use std::slice::Iter; +use serde::de::{self, Deserializer, Error as DeError, Visitor}; use std::borrow::Cow; use std::convert::AsRef; -use serde::de::{self, Deserializer, Visitor, Error as DeError}; +use std::slice::Iter; use httprequest::HttpRequest; - macro_rules! unsupported_type { ($trait_fn:ident, $name:expr) => { fn $trait_fn(self, _: V) -> Result @@ -37,103 +36,136 @@ macro_rules! parse_single_value { } pub struct PathDeserializer<'de, S: 'de> { - req: &'de HttpRequest + req: &'de HttpRequest, } impl<'de, S: 'de> PathDeserializer<'de, S> { pub fn new(req: &'de HttpRequest) -> Self { - PathDeserializer{req} + PathDeserializer { req } } } -impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> -{ +impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { type Error = de::value::Error; fn deserialize_map(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { - visitor.visit_map(ParamsDeserializer{ + visitor.visit_map(ParamsDeserializer { params: self.req.match_info().iter(), current: None, }) } - fn deserialize_struct(self, _: &'static str, _: &'static [&'static str], visitor: V) - -> Result - where V: Visitor<'de>, + fn deserialize_struct( + self, _: &'static str, _: &'static [&'static str], visitor: V + ) -> Result + where + V: Visitor<'de>, { self.deserialize_map(visitor) } fn deserialize_unit(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { visitor.visit_unit() } - fn deserialize_unit_struct(self, _: &'static str, visitor: V) - -> Result - where V: Visitor<'de> + fn deserialize_unit_struct( + self, _: &'static str, visitor: V + ) -> Result + where + V: Visitor<'de>, { self.deserialize_unit(visitor) } - fn deserialize_newtype_struct(self, _: &'static str, visitor: V) - -> Result - where V: Visitor<'de>, + fn deserialize_newtype_struct( + self, _: &'static str, visitor: V + ) -> Result + where + V: Visitor<'de>, { visitor.visit_newtype_struct(self) } - fn deserialize_tuple(self, len: usize, visitor: V) -> Result - where V: Visitor<'de> + fn deserialize_tuple( + self, len: usize, visitor: V + ) -> Result + where + V: Visitor<'de>, { if self.req.match_info().len() < len { Err(de::value::Error::custom( - format!("wrong number of parameters: {} expected {}", - self.req.match_info().len(), len).as_str())) + format!( + "wrong number of parameters: {} expected {}", + self.req.match_info().len(), + len + ).as_str(), + )) } else { - visitor.visit_seq(ParamsSeq{params: self.req.match_info().iter()}) + visitor.visit_seq(ParamsSeq { + params: self.req.match_info().iter(), + }) } } - fn deserialize_tuple_struct(self, _: &'static str, len: usize, visitor: V) - -> Result - where V: Visitor<'de> + fn deserialize_tuple_struct( + self, _: &'static str, len: usize, visitor: V + ) -> Result + where + V: Visitor<'de>, { if self.req.match_info().len() < len { Err(de::value::Error::custom( - format!("wrong number of parameters: {} expected {}", - self.req.match_info().len(), len).as_str())) + format!( + "wrong number of parameters: {} expected {}", + self.req.match_info().len(), + len + ).as_str(), + )) } else { - visitor.visit_seq(ParamsSeq{params: self.req.match_info().iter()}) + visitor.visit_seq(ParamsSeq { + params: self.req.match_info().iter(), + }) } } - fn deserialize_enum(self, _: &'static str, _: &'static [&'static str], _: V) - -> Result - where V: Visitor<'de> + fn deserialize_enum( + self, _: &'static str, _: &'static [&'static str], _: V + ) -> Result + where + V: Visitor<'de>, { Err(de::value::Error::custom("unsupported type: enum")) } fn deserialize_str(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { if self.req.match_info().len() != 1 { Err(de::value::Error::custom( - format!("wrong number of parameters: {} expected 1", - self.req.match_info().len()).as_str())) + format!( + "wrong number of parameters: {} expected 1", + self.req.match_info().len() + ).as_str(), + )) } else { visitor.visit_str(&self.req.match_info()[0]) } } fn deserialize_seq(self, visitor: V) -> Result - where V: Visitor<'de> + where + V: Visitor<'de>, { - visitor.visit_seq(ParamsSeq{params: self.req.match_info().iter()}) + visitor.visit_seq(ParamsSeq { + params: self.req.match_info().iter(), + }) } unsupported_type!(deserialize_any, "'any'"); @@ -163,22 +195,25 @@ struct ParamsDeserializer<'de> { current: Option<(&'de str, &'de str)>, } -impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> -{ +impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { type Error = de::value::Error; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> - where K: de::DeserializeSeed<'de>, + where + K: de::DeserializeSeed<'de>, { - self.current = self.params.next().map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); + self.current = self.params + .next() + .map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); match self.current { - Some((key, _)) => Ok(Some(seed.deserialize(Key{key})?)), + Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), None => Ok(None), } } fn next_value_seed(&mut self, seed: V) -> Result - where V: de::DeserializeSeed<'de>, + where + V: de::DeserializeSeed<'de>, { if let Some((_, value)) = self.current.take() { seed.deserialize(Value { value }) @@ -196,13 +231,15 @@ impl<'de> Deserializer<'de> for Key<'de> { type Error = de::value::Error; fn deserialize_identifier(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { visitor.visit_str(self.key) } fn deserialize_any(self, _visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { Err(de::value::Error::custom("Unexpected")) } @@ -231,8 +268,7 @@ struct Value<'de> { value: &'de str, } -impl<'de> Deserializer<'de> for Value<'de> -{ +impl<'de> Deserializer<'de> for Value<'de> { type Error = de::value::Error; parse_value!(deserialize_bool, visit_bool, "bool"); @@ -251,74 +287,94 @@ impl<'de> Deserializer<'de> for Value<'de> parse_value!(deserialize_char, visit_char, "char"); fn deserialize_ignored_any(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { visitor.visit_unit() } fn deserialize_unit(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { visitor.visit_unit() } fn deserialize_unit_struct( - self, _: &'static str, visitor: V) -> Result - where V: Visitor<'de> + self, _: &'static str, visitor: V + ) -> Result + where + V: Visitor<'de>, { visitor.visit_unit() } fn deserialize_bytes(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { visitor.visit_borrowed_bytes(self.value.as_bytes()) } fn deserialize_str(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { visitor.visit_borrowed_str(self.value) } fn deserialize_option(self, visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { visitor.visit_some(self) } - fn deserialize_enum(self, _: &'static str, _: &'static [&'static str], visitor: V) - -> Result - where V: Visitor<'de>, + fn deserialize_enum( + self, _: &'static str, _: &'static [&'static str], visitor: V + ) -> Result + where + V: Visitor<'de>, { - visitor.visit_enum(ValueEnum {value: self.value}) + visitor.visit_enum(ValueEnum { + value: self.value, + }) } - fn deserialize_newtype_struct(self, _: &'static str, visitor: V) - -> Result - where V: Visitor<'de>, + fn deserialize_newtype_struct( + self, _: &'static str, visitor: V + ) -> Result + where + V: Visitor<'de>, { visitor.visit_newtype_struct(self) } fn deserialize_tuple(self, _: usize, _: V) -> Result - where V: Visitor<'de> + where + V: Visitor<'de>, { Err(de::value::Error::custom("unsupported type: tuple")) } - fn deserialize_struct(self, _: &'static str, _: &'static [&'static str], _: V) - -> Result - where V: Visitor<'de> + fn deserialize_struct( + self, _: &'static str, _: &'static [&'static str], _: V + ) -> Result + where + V: Visitor<'de>, { Err(de::value::Error::custom("unsupported type: struct")) } - fn deserialize_tuple_struct(self, _: &'static str, _: usize, _: V) - -> Result - where V: Visitor<'de> + fn deserialize_tuple_struct( + self, _: &'static str, _: usize, _: V + ) -> Result + where + V: Visitor<'de>, { - Err(de::value::Error::custom("unsupported type: tuple struct")) + Err(de::value::Error::custom( + "unsupported type: tuple struct", + )) } unsupported_type!(deserialize_any, "any"); @@ -331,15 +387,17 @@ struct ParamsSeq<'de> { params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>, } -impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> -{ +impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { type Error = de::value::Error; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> - where T: de::DeserializeSeed<'de>, + where + T: de::DeserializeSeed<'de>, { match self.params.next() { - Some(item) => Ok(Some(seed.deserialize(Value { value: item.1.as_ref() })?)), + Some(item) => Ok(Some(seed.deserialize(Value { + value: item.1.as_ref(), + })?)), None => Ok(None), } } @@ -354,9 +412,13 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { type Variant = UnitVariant; fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> - where V: de::DeserializeSeed<'de>, + where + V: de::DeserializeSeed<'de>, { - Ok((seed.deserialize(Key { key: self.value })?, UnitVariant)) + Ok(( + seed.deserialize(Key { key: self.value })?, + UnitVariant, + )) } } @@ -370,20 +432,24 @@ impl<'de> de::VariantAccess<'de> for UnitVariant { } fn newtype_variant_seed(self, _seed: T) -> Result - where T: de::DeserializeSeed<'de>, + where + T: de::DeserializeSeed<'de>, { Err(de::value::Error::custom("not supported")) } fn tuple_variant(self, _len: usize, _visitor: V) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { Err(de::value::Error::custom("not supported")) } - fn struct_variant(self, _: &'static [&'static str], _: V) - -> Result - where V: Visitor<'de>, + fn struct_variant( + self, _: &'static [&'static str], _: V + ) -> Result + where + V: Visitor<'de>, { Err(de::value::Error::custom("not supported")) } diff --git a/src/error.rs b/src/error.rs index dc4ae78e..7435b504 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,24 +1,24 @@ //! Error and Result module -use std::{io, fmt, result}; +use std::io::Error as IoError; use std::str::Utf8Error; use std::string::FromUtf8Error; -use std::io::Error as IoError; +use std::{fmt, io, result}; -use cookie; -use httparse; use actix::MailboxError; +use cookie; +use failure::{self, Backtrace, Fail}; use futures::Canceled; -use failure::{self, Fail, Backtrace}; -use http2::Error as Http2Error; -use http::{header, StatusCode, Error as HttpError}; use http::uri::InvalidUri; +use http::{header, Error as HttpError, StatusCode}; +use http2::Error as Http2Error; use http_range::HttpRangeParseError; +use httparse; use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; pub use url::ParseError as UrlParseError; // re-exports -pub use cookie::{ParseError as CookieParseError}; +pub use cookie::ParseError as CookieParseError; use handler::Responder; use httprequest::HttpRequest; @@ -27,9 +27,10 @@ use httpresponse::HttpResponse; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations /// -/// 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; +/// 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; /// General purpose actix web error pub struct Error { @@ -38,7 +39,6 @@ pub struct Error { } 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) -> &ResponseError { @@ -48,7 +48,6 @@ impl Error { /// Error that can be converted to `HttpResponse` pub trait ResponseError: Fail { - /// Create response for error /// /// Internal server error is generated by default. @@ -68,7 +67,12 @@ impl fmt::Debug for Error { if let Some(bt) = self.cause.backtrace() { write!(f, "{:?}\n\n{:?}", &self.cause, bt) } else { - write!(f, "{:?}\n\n{:?}", &self.cause, self.backtrace.as_ref().unwrap()) + write!( + f, + "{:?}\n\n{:?}", + &self.cause, + self.backtrace.as_ref().unwrap() + ) } } } @@ -88,13 +92,19 @@ impl From for Error { } else { None }; - Error { cause: Box::new(err), backtrace } + Error { + cause: Box::new(err), + backtrace, + } } } /// Compatibility for `failure::Error` impl ResponseError for failure::Compat - where T: fmt::Display + fmt::Debug + Sync + Send + 'static { } +where + T: fmt::Display + fmt::Debug + Sync + Send + 'static, +{ +} impl From for Error { fn from(err: failure::Error) -> Error { @@ -128,15 +138,11 @@ impl ResponseError for HttpError {} /// Return `InternalServerError` for `io::Error` impl ResponseError for io::Error { - fn error_response(&self) -> HttpResponse { match self.kind() { - io::ErrorKind::NotFound => - HttpResponse::new(StatusCode::NOT_FOUND), - io::ErrorKind::PermissionDenied => - HttpResponse::new(StatusCode::FORBIDDEN), - _ => - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) + io::ErrorKind::NotFound => HttpResponse::new(StatusCode::NOT_FOUND), + io::ErrorKind::PermissionDenied => HttpResponse::new(StatusCode::FORBIDDEN), + _ => HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR), } } } @@ -145,7 +151,7 @@ impl ResponseError for io::Error { impl ResponseError for header::InvalidHeaderValue { fn error_response(&self) -> HttpResponse { HttpResponse::new(StatusCode::BAD_REQUEST) - } + } } /// `BadRequest` for `InvalidHeaderValue` @@ -165,35 +171,36 @@ impl ResponseError for MailboxError {} #[derive(Fail, Debug)] pub enum ParseError { /// An invalid `Method`, such as `GE.T`. - #[fail(display="Invalid Method specified")] + #[fail(display = "Invalid Method specified")] Method, /// An invalid `Uri`, such as `exam ple.domain`. - #[fail(display="Uri error: {}", _0)] + #[fail(display = "Uri error: {}", _0)] Uri(InvalidUri), /// An invalid `HttpVersion`, such as `HTP/1.1` - #[fail(display="Invalid HTTP version specified")] + #[fail(display = "Invalid HTTP version specified")] Version, /// An invalid `Header`. - #[fail(display="Invalid Header provided")] + #[fail(display = "Invalid Header provided")] Header, /// A message head is too large to be reasonable. - #[fail(display="Message head is too large")] + #[fail(display = "Message head is too large")] TooLarge, /// A message reached EOF, but is not complete. - #[fail(display="Message is incomplete")] + #[fail(display = "Message is incomplete")] Incomplete, /// An invalid `Status`, such as `1337 ELITE`. - #[fail(display="Invalid Status provided")] + #[fail(display = "Invalid Status provided")] Status, /// A timeout occurred waiting for an IO event. #[allow(dead_code)] - #[fail(display="Timeout")] + #[fail(display = "Timeout")] Timeout, - /// An `io::Error` that occurred while trying to read or write to a network stream. - #[fail(display="IO error: {}", _0)] + /// An `io::Error` that occurred while trying to read or write to a network + /// stream. + #[fail(display = "IO error: {}", _0)] Io(#[cause] IoError), /// Parsing a field as string failed - #[fail(display="UTF8 error: {}", _0)] + #[fail(display = "UTF8 error: {}", _0)] Utf8(#[cause] Utf8Error), } @@ -231,8 +238,10 @@ 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, @@ -244,22 +253,22 @@ impl From for ParseError { /// A set of errors that can occur during payload parsing pub enum PayloadError { /// A payload reached EOF, but is not complete. - #[fail(display="A payload reached EOF, but is not complete.")] + #[fail(display = "A payload reached EOF, but is not complete.")] Incomplete, /// Content encoding stream corruption - #[fail(display="Can not decode content-encoding.")] + #[fail(display = "Can not decode content-encoding.")] EncodingCorrupted, /// A payload reached size limit. - #[fail(display="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.")] + #[fail(display = "A payload length is unknown.")] UnknownLength, /// Io error - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] Io(#[cause] IoError), /// Http2 error - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] Http2(#[cause] Http2Error), } @@ -283,12 +292,12 @@ impl ResponseError for cookie::ParseError { #[derive(Fail, PartialEq, Debug)] pub enum HttpRangeError { /// Returned if range is invalid. - #[fail(display="Range header is invalid")] + #[fail(display = "Range header is invalid")] 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` - #[fail(display="First-byte-pos of all of the byte-range-spec values is greater than the content size")] + #[fail(display = "First-byte-pos of all of the byte-range-spec values is greater than the content size")] NoOverlap, } @@ -296,7 +305,9 @@ pub enum HttpRangeError { impl ResponseError for HttpRangeError { fn error_response(&self) -> HttpResponse { HttpResponse::with_body( - StatusCode::BAD_REQUEST, "Invalid Range header provided") + StatusCode::BAD_REQUEST, + "Invalid Range header provided", + ) } } @@ -313,22 +324,22 @@ impl From for HttpRangeError { #[derive(Fail, Debug)] pub enum MultipartError { /// Content-Type header is not found - #[fail(display="No Content-type header found")] + #[fail(display = "No Content-type header found")] NoContentType, /// Can not parse Content-Type header - #[fail(display="Can not parse Content-Type header")] + #[fail(display = "Can not parse Content-Type header")] ParseContentType, /// Multipart boundary is not found - #[fail(display="Multipart boundary is not found")] + #[fail(display = "Multipart boundary is not found")] Boundary, /// Multipart stream is incomplete - #[fail(display="Multipart stream is incomplete")] + #[fail(display = "Multipart stream is incomplete")] Incomplete, /// Error during field parsing - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] Parse(#[cause] ParseError), /// Payload error - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] Payload(#[cause] PayloadError), } @@ -346,7 +357,6 @@ impl From for MultipartError { /// Return `BadRequest` for `MultipartError` impl ResponseError for MultipartError { - fn error_response(&self) -> HttpResponse { HttpResponse::new(StatusCode::BAD_REQUEST) } @@ -356,10 +366,10 @@ impl ResponseError for MultipartError { #[derive(Fail, PartialEq, Debug)] pub enum ExpectError { /// Expect header value can not be converted to utf8 - #[fail(display="Expect header value can not be converted to utf8")] + #[fail(display = "Expect header value can not be converted to utf8")] Encoding, /// Unknown expect value - #[fail(display="Unknown expect value")] + #[fail(display = "Unknown expect value")] UnknownExpect, } @@ -373,10 +383,10 @@ impl ResponseError for ExpectError { #[derive(Fail, PartialEq, Debug)] pub enum ContentTypeError { /// Can not parse content type - #[fail(display="Can not parse content type")] + #[fail(display = "Can not parse content type")] ParseError, /// Unknown content encoding - #[fail(display="Unknown content encoding")] + #[fail(display = "Unknown content encoding")] UnknownEncoding, } @@ -391,36 +401,36 @@ impl ResponseError for ContentTypeError { #[derive(Fail, Debug)] pub enum UrlencodedError { /// Can not decode chunked transfer encoding - #[fail(display="Can not decode chunked transfer encoding")] + #[fail(display = "Can not decode chunked transfer encoding")] Chunked, /// Payload size is bigger than 256k - #[fail(display="Payload size is bigger than 256k")] + #[fail(display = "Payload size is bigger than 256k")] Overflow, /// Payload size is now known - #[fail(display="Payload size is now known")] + #[fail(display = "Payload size is now known")] UnknownLength, /// Content type error - #[fail(display="Content type error")] + #[fail(display = "Content type error")] ContentType, /// Parse error - #[fail(display="Parse error")] + #[fail(display = "Parse error")] Parse, /// Payload error - #[fail(display="Error that occur during reading payload: {}", _0)] + #[fail(display = "Error that occur during reading payload: {}", _0)] Payload(#[cause] PayloadError), } /// Return `BadRequest` for `UrlencodedError` impl ResponseError for UrlencodedError { - fn error_response(&self) -> HttpResponse { match *self { - UrlencodedError::Overflow => - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), - UrlencodedError::UnknownLength => - HttpResponse::new(StatusCode::LENGTH_REQUIRED), - _ => - HttpResponse::new(StatusCode::BAD_REQUEST), + UrlencodedError::Overflow => { + HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) + } + UrlencodedError::UnknownLength => { + HttpResponse::new(StatusCode::LENGTH_REQUIRED) + } + _ => HttpResponse::new(StatusCode::BAD_REQUEST), } } } @@ -435,28 +445,27 @@ impl From for UrlencodedError { #[derive(Fail, Debug)] pub enum JsonPayloadError { /// Payload size is bigger than 256k - #[fail(display="Payload size is bigger than 256k")] + #[fail(display = "Payload size is bigger than 256k")] Overflow, /// Content type error - #[fail(display="Content type error")] + #[fail(display = "Content type error")] ContentType, /// Deserialize error - #[fail(display="Json deserialize error: {}", _0)] + #[fail(display = "Json deserialize error: {}", _0)] Deserialize(#[cause] JsonError), /// Payload error - #[fail(display="Error that occur during reading payload: {}", _0)] + #[fail(display = "Error that occur during reading payload: {}", _0)] Payload(#[cause] PayloadError), } /// Return `BadRequest` for `UrlencodedError` impl ResponseError for JsonPayloadError { - fn error_response(&self) -> HttpResponse { match *self { - JsonPayloadError::Overflow => - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => - HttpResponse::new(StatusCode::BAD_REQUEST), + JsonPayloadError::Overflow => { + HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) + } + _ => HttpResponse::new(StatusCode::BAD_REQUEST), } } } @@ -478,19 +487,18 @@ impl From for JsonPayloadError { #[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")] + #[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")] + #[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")] + #[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) } @@ -499,13 +507,13 @@ impl ResponseError for UriSegmentError { /// Errors which can occur when attempting to generate resource uri. #[derive(Fail, Debug, PartialEq)] pub enum UrlGenerationError { - #[fail(display="Resource not found")] + #[fail(display = "Resource not found")] ResourceNotFound, - #[fail(display="Not all path pattern covered")] + #[fail(display = "Not all path pattern covered")] NotEnoughElements, - #[fail(display="Router is not available")] + #[fail(display = "Router is not available")] RouterNotAvailable, - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] ParseError(#[cause] UrlParseError), } @@ -520,8 +528,9 @@ impl From for UrlGenerationError { /// Helper type that can wrap any error and generate custom response. /// -/// In following example any `io::Error` will be converted into "BAD REQUEST" response -/// as opposite to *INNTERNAL SERVER ERROR* which is defined by default. +/// In following example any `io::Error` will be converted into "BAD REQUEST" +/// response as opposite to *INNTERNAL SERVER ERROR* which is defined by +/// default. /// /// ```rust /// # extern crate actix_web; @@ -554,7 +563,8 @@ impl InternalError { } impl Fail for InternalError - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { fn backtrace(&self) -> Option<&Backtrace> { Some(&self.backtrace) @@ -562,7 +572,8 @@ impl Fail for InternalError } impl fmt::Debug for InternalError - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.cause, f) @@ -570,7 +581,8 @@ impl fmt::Debug for InternalError } impl fmt::Display for InternalError - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.cause, f) @@ -578,7 +590,8 @@ impl fmt::Display for InternalError } impl ResponseError for InternalError - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { fn error_response(&self) -> HttpResponse { HttpResponse::new(self.status) @@ -586,7 +599,8 @@ impl ResponseError for InternalError } impl Responder for InternalError - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { type Item = HttpResponse; type Error = Error; @@ -596,82 +610,102 @@ impl Responder for InternalError } } -/// Helper function that creates wrapper of any error and generate *BAD REQUEST* response. +/// Helper function that creates wrapper of any error and generate *BAD +/// REQUEST* response. #[allow(non_snake_case)] pub fn ErrorBadRequest(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::BAD_REQUEST).into() } -/// Helper function that creates wrapper of any error and generate *UNAUTHORIZED* response. +/// Helper function that creates wrapper of any error and generate +/// *UNAUTHORIZED* response. #[allow(non_snake_case)] pub fn ErrorUnauthorized(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::UNAUTHORIZED).into() } -/// Helper function that creates wrapper of any error and generate *FORBIDDEN* response. +/// Helper function that creates wrapper of any error and generate *FORBIDDEN* +/// response. #[allow(non_snake_case)] pub fn ErrorForbidden(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::FORBIDDEN).into() } -/// Helper function that creates wrapper of any error and generate *NOT FOUND* response. +/// Helper function that creates wrapper of any error and generate *NOT FOUND* +/// response. #[allow(non_snake_case)] pub fn ErrorNotFound(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::NOT_FOUND).into() } -/// Helper function that creates wrapper of any error and generate *METHOD NOT ALLOWED* response. +/// Helper function that creates wrapper of any error and generate *METHOD NOT +/// ALLOWED* response. #[allow(non_snake_case)] pub fn ErrorMethodNotAllowed(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() } -/// Helper function that creates wrapper of any error and generate *REQUEST TIMEOUT* response. +/// Helper function that creates wrapper of any error and generate *REQUEST +/// TIMEOUT* response. #[allow(non_snake_case)] pub fn ErrorRequestTimeout(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into() } -/// Helper function that creates wrapper of any error and generate *CONFLICT* response. +/// Helper function that creates wrapper of any error and generate *CONFLICT* +/// response. #[allow(non_snake_case)] pub fn ErrorConflict(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::CONFLICT).into() } -/// Helper function that creates wrapper of any error and generate *GONE* response. +/// Helper function that creates wrapper of any error and generate *GONE* +/// response. #[allow(non_snake_case)] pub fn ErrorGone(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::GONE).into() } -/// Helper function that creates wrapper of any error and generate *PRECONDITION FAILED* response. +/// Helper function that creates wrapper of any error and generate +/// *PRECONDITION FAILED* response. #[allow(non_snake_case)] pub fn ErrorPreconditionFailed(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() } -/// Helper function that creates wrapper of any error and generate *EXPECTATION FAILED* response. +/// Helper function that creates wrapper of any error and generate +/// *EXPECTATION FAILED* response. #[allow(non_snake_case)] pub fn ErrorExpectationFailed(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() } @@ -680,26 +714,28 @@ pub fn ErrorExpectationFailed(err: T) -> Error /// generate *INTERNAL SERVER ERROR* response. #[allow(non_snake_case)] pub fn ErrorInternalServerError(err: T) -> Error - where T: Send + Sync + fmt::Debug + 'static +where + T: Send + Sync + fmt::Debug + 'static, { InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() } #[cfg(test)] mod tests { + use super::*; + use cookie::ParseError as CookieParseError; + use failure; + use http::{Error as HttpError, StatusCode}; + use httparse; use std::env; use std::error::Error as StdError; use std::io; - use httparse; - use http::{StatusCode, Error as HttpError}; - use cookie::ParseError as CookieParseError; - use failure; - use super::*; #[test] #[cfg(actix_nightly)] fn test_nightly() { - let resp: HttpResponse = IoError::new(io::ErrorKind::Other, "test").error_response(); + let resp: HttpResponse = + IoError::new(io::ErrorKind::Other, "test").error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -775,10 +811,10 @@ mod tests { match ParseError::from($from) { e @ $error => { assert!(format!("{}", e).len() >= 5); - } , - e => unreachable!("{:?}", e) + } + e => unreachable!("{:?}", e), } - } + }; } macro_rules! from_and_cause { @@ -787,10 +823,10 @@ mod tests { e @ $error => { let desc = format!("{}", e.cause().unwrap()); assert_eq!(desc, $from.description().to_owned()); - }, - _ => unreachable!("{:?}", $from) + } + _ => unreachable!("{:?}", $from), } - } + }; } #[test] @@ -814,7 +850,10 @@ mod tests { env::set_var(NAME, "0"); let error = failure::err_msg("Hello!"); let resp: Error = error.into(); - assert_eq!(format!("{:?}", resp), "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n"); + assert_eq!( + format!("{:?}", resp), + "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n" + ); match old_tb { Ok(x) => env::set_var(NAME, x), _ => env::remove_var(NAME), diff --git a/src/extractor.rs b/src/extractor.rs index e88998c7..9415299b 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,19 +1,19 @@ -use std::str; use std::ops::{Deref, DerefMut}; +use std::str; -use mime::Mime; use bytes::Bytes; -use serde_urlencoded; -use serde::de::{self, DeserializeOwned}; -use futures::future::{Future, FutureResult, result}; use encoding::all::UTF_8; -use encoding::types::{Encoding, DecoderTrap}; +use encoding::types::{DecoderTrap, Encoding}; +use futures::future::{result, Future, FutureResult}; +use mime::Mime; +use serde::de::{self, DeserializeOwned}; +use serde_urlencoded; +use de::PathDeserializer; use error::{Error, ErrorBadRequest}; use handler::{Either, FromRequest}; -use httprequest::HttpRequest; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; -use de::PathDeserializer; +use httprequest::HttpRequest; /// Extract typed information from the request's path. /// @@ -39,8 +39,8 @@ use de::PathDeserializer; /// } /// ``` /// -/// It is possible to extract path information to a specific type that implements -/// `Deserialize` trait from *serde*. +/// It is possible to extract path information to a specific type that +/// implements `Deserialize` trait from *serde*. /// /// ```rust /// # extern crate bytes; @@ -65,12 +65,11 @@ use de::PathDeserializer; /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// } /// ``` -pub struct Path{ - inner: T +pub struct Path { + inner: T, } impl AsRef for Path { - fn as_ref(&self) -> &T { &self.inner } @@ -98,7 +97,9 @@ impl Path { } impl FromRequest for Path - where T: DeserializeOwned, S: 'static +where + T: DeserializeOwned, + S: 'static, { type Config = (); type Result = FutureResult; @@ -106,9 +107,11 @@ impl FromRequest for Path #[inline] fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); - result(de::Deserialize::deserialize(PathDeserializer::new(&req)) - .map_err(|e| e.into()) - .map(|inner| Path{inner})) + result( + de::Deserialize::deserialize(PathDeserializer::new(&req)) + .map_err(|e| e.into()) + .map(|inner| Path { inner }), + ) } } @@ -164,7 +167,9 @@ impl Query { } impl FromRequest for Query - where T: de::DeserializeOwned, S: 'static +where + T: de::DeserializeOwned, + S: 'static, { type Config = (); type Result = FutureResult; @@ -172,24 +177,26 @@ impl FromRequest for Query #[inline] fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); - result(serde_urlencoded::from_str::(req.query_string()) - .map_err(|e| e.into()) - .map(Query)) + result( + serde_urlencoded::from_str::(req.query_string()) + .map_err(|e| e.into()) + .map(Query), + ) } } /// Extract typed information from the request's body. /// -/// To extract typed information from request's body, the type `T` must implement the -/// `Deserialize` trait from *serde*. +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. /// /// [**FormConfig**](dev/struct.FormConfig.html) allows to configure extraction /// process. /// /// ## Example /// -/// It is possible to extract path information to a specific type that implements -/// `Deserialize` trait from *serde*. +/// It is possible to extract path information to a specific type that +/// implements `Deserialize` trait from *serde*. /// /// ```rust /// # extern crate actix_web; @@ -233,17 +240,21 @@ impl DerefMut for Form { } impl FromRequest for Form - where T: DeserializeOwned + 'static, S: 'static +where + T: DeserializeOwned + 'static, + S: 'static, { type Config = FormConfig; - type Result = Box>; + type Result = Box>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(UrlEncoded::new(req.clone()) - .limit(cfg.limit) - .from_err() - .map(Form)) + Box::new( + UrlEncoded::new(req.clone()) + .limit(cfg.limit) + .from_err() + .map(Form), + ) } } @@ -279,7 +290,6 @@ pub struct FormConfig { } impl FormConfig { - /// Change max size of payload. By default max size is 256Kb pub fn limit(&mut self, limit: usize) -> &mut Self { self.limit = limit; @@ -289,7 +299,7 @@ impl FormConfig { impl Default for FormConfig { fn default() -> Self { - FormConfig{limit: 262_144} + FormConfig { limit: 262_144 } } } @@ -297,8 +307,8 @@ impl Default for FormConfig { /// /// Loads request's payload and construct Bytes instance. /// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure extraction -/// process. +/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure +/// extraction process. /// /// ## Example /// @@ -313,11 +323,10 @@ impl Default for FormConfig { /// } /// # fn main() {} /// ``` -impl FromRequest for Bytes -{ +impl FromRequest for Bytes { type Config = PayloadConfig; - type Result = Either, - Box>>; + type Result = + Either, Box>>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { @@ -326,9 +335,11 @@ impl FromRequest for Bytes return Either::A(result(Err(e))); } - Either::B(Box::new(MessageBody::new(req.clone()) - .limit(cfg.limit) - .from_err())) + Either::B(Box::new( + MessageBody::new(req.clone()) + .limit(cfg.limit) + .from_err(), + )) } } @@ -351,11 +362,10 @@ impl FromRequest for Bytes /// } /// # fn main() {} /// ``` -impl FromRequest for String -{ +impl FromRequest for String { type Config = PayloadConfig; - type Result = Either, - Box>>; + type Result = + Either, Box>>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { @@ -366,8 +376,11 @@ impl FromRequest for String // check charset let encoding = match req.encoding() { - Err(_) => return Either::A( - result(Err(ErrorBadRequest("Unknown request charset")))), + Err(_) => { + return Either::A(result(Err(ErrorBadRequest( + "Unknown request charset", + )))) + } Ok(encoding) => encoding, }; @@ -379,13 +392,15 @@ impl FromRequest for String let enc: *const Encoding = encoding as *const Encoding; if enc == UTF_8 { Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned()) } else { - Ok(encoding.decode(&body, DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))?) + Ok(encoding + .decode(&body, DecoderTrap::Strict) + .map_err(|_| ErrorBadRequest("Can not decode body"))?) } - }))) + }), + )) } } @@ -396,14 +411,14 @@ pub struct PayloadConfig { } impl PayloadConfig { - /// Change max size of payload. By default max size is 256Kb pub fn limit(&mut self, limit: usize) -> &mut Self { self.limit = limit; self } - /// Set required mime-type of the request. By default mime type is not enforced. + /// Set required mime-type of the request. By default mime type is not + /// enforced. pub fn mimetype(&mut self, mt: Mime) -> &mut Self { self.mimetype = Some(mt); self @@ -417,13 +432,13 @@ impl PayloadConfig { if mt != req_mt { return Err(ErrorBadRequest("Unexpected Content-Type")); } - }, + } Ok(None) => { return Err(ErrorBadRequest("Content-Type is expected")); - }, + } Err(err) => { return Err(err.into()); - }, + } } } Ok(()) @@ -432,21 +447,24 @@ impl PayloadConfig { impl Default for PayloadConfig { fn default() -> Self { - PayloadConfig{limit: 262_144, mimetype: None} + PayloadConfig { + limit: 262_144, + mimetype: None, + } } } #[cfg(test)] mod tests { use super::*; - use mime; use bytes::Bytes; use futures::{Async, Future}; use http::header; - use router::{Router, Resource}; + use mime; use resource::ResourceHandler; - use test::TestRequest; + use router::{Resource, Router}; use server::ServerSettings; + use test::TestRequest; #[derive(Deserialize, Debug, PartialEq)] struct Info { @@ -457,12 +475,13 @@ mod tests { fn test_bytes() { let cfg = PayloadConfig::default(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); match Bytes::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { assert_eq!(s, Bytes::from_static(b"hello=world")); - }, + } _ => unreachable!(), } } @@ -471,12 +490,13 @@ mod tests { fn test_string() { let cfg = PayloadConfig::default(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); match String::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { assert_eq!(s, "hello=world"); - }, + } _ => unreachable!(), } } @@ -484,17 +504,19 @@ mod tests { #[test] fn test_form() { let mut req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(header::CONTENT_LENGTH, "11") + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); let mut cfg = FormConfig::default(); cfg.limit(4096); match Form::::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.hello, "world"); - }, + } _ => unreachable!(), } } @@ -507,10 +529,13 @@ mod tests { assert!(cfg.check_mimetype(&req).is_err()); let req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded").finish(); + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).finish(); assert!(cfg.check_mimetype(&req).is_err()); - let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); + let req = + TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); assert!(cfg.check_mimetype(&req).is_ok()); } @@ -538,30 +563,39 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); + routes.push(( + Resource::new("index", "/{key}/{value}/"), + Some(resource), + )); let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req, &()).poll().unwrap() { + match Path::::from_request(&req, &()) + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); - }, + } _ => unreachable!(), } - match Path::<(String, String)>::from_request(&req, &()).poll().unwrap() { + match Path::<(String, String)>::from_request(&req, &()) + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); - }, + } _ => unreachable!(), } match Query::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.id, "test"); - }, + } _ => unreachable!(), } @@ -572,22 +606,31 @@ mod tests { Async::Ready(s) => { assert_eq!(s.as_ref().key, "name"); assert_eq!(s.value, 32); - }, + } _ => unreachable!(), } - match Path::<(String, u8)>::from_request(&req, &()).poll().unwrap() { + match Path::<(String, u8)>::from_request(&req, &()) + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, 32); - }, + } _ => unreachable!(), } - match Path::>::from_request(&req, &()).poll().unwrap() { + match Path::>::from_request(&req, &()) + .poll() + .unwrap() + { Async::Ready(s) => { - assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]); - }, + assert_eq!( + s.into_inner(), + vec!["name".to_owned(), "32".to_owned()] + ); + } _ => unreachable!(), } } @@ -606,7 +649,7 @@ mod tests { match Path::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.into_inner(), 32); - }, + } _ => unreachable!(), } } diff --git a/src/fs.rs b/src/fs.rs index 4dbff6f8..4e7305b0 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,30 +1,30 @@ //! Static files support -use std::{io, cmp}; -use std::io::{Read, Seek}; use std::fmt::Write; -use std::fs::{File, DirEntry, Metadata}; -use std::path::{Path, PathBuf}; +use std::fs::{DirEntry, File, Metadata}; +use std::io::{Read, Seek}; use std::ops::{Deref, DerefMut}; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::path::{Path, PathBuf}; use std::sync::Mutex; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::{cmp, io}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -use bytes::{Bytes, BytesMut, BufMut}; -use futures::{Async, Poll, Future, Stream}; -use futures_cpupool::{CpuPool, CpuFuture}; +use bytes::{BufMut, Bytes, BytesMut}; +use futures::{Async, Future, Poll, Stream}; +use futures_cpupool::{CpuFuture, CpuPool}; use mime_guess::get_mime_type; use percent_encoding::percent_decode; -use header; use error::Error; -use param::FromParam; -use handler::{Handler, RouteHandler, WrapHandler, Responder, Reply}; +use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler}; +use header; use http::{Method, StatusCode}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; +use param::FromParam; /// A file with an associated name; responds with the Content-Type based on the /// file extension. @@ -55,9 +55,15 @@ impl NamedFile { let path = path.as_ref().to_path_buf(); let modified = md.modified().ok(); let cpu_pool = None; - Ok(NamedFile{path, file, md, modified, cpu_pool, - only_get: false, - status_code: StatusCode::OK}) + Ok(NamedFile { + path, + file, + md, + modified, + cpu_pool, + only_get: false, + status_code: StatusCode::OK, + }) } /// Allow only GET and HEAD methods @@ -110,17 +116,25 @@ impl NamedFile { self.modified.as_ref().map(|mtime| { let ino = { #[cfg(unix)] - { self.md.ino() } + { + self.md.ino() + } #[cfg(not(unix))] - { 0 } + { + 0 + } }; - let dur = mtime.duration_since(UNIX_EPOCH) + let dur = mtime + .duration_since(UNIX_EPOCH) .expect("modification time must be after epoch"); - header::EntityTag::strong( - format!("{:x}:{:x}:{:x}:{:x}", - ino, self.md.len(), dur.as_secs(), - dur.subsec_nanos())) + header::EntityTag::strong(format!( + "{:x}:{:x}:{:x}:{:x}", + ino, + self.md.len(), + dur.as_secs(), + dur.subsec_nanos() + )) }) } @@ -178,7 +192,6 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } } - impl Responder for NamedFile { type Item = HttpResponse; type Error = io::Error; @@ -187,23 +200,27 @@ impl Responder for NamedFile { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); + resp.set(header::ContentType(get_mime_type( + &ext.to_string_lossy(), + ))); }); let reader = ChunkedReadFile { size: self.md.len(), offset: 0, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), + cpu_pool: self.cpu_pool + .unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, }; - return Ok(resp.streaming(reader)) + return Ok(resp.streaming(reader)); } - if self.only_get && *req.method() != Method::GET && *req.method() != Method::HEAD { + if self.only_get && *req.method() != Method::GET && *req.method() != Method::HEAD + { return Ok(HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")) + .header(header::CONTENT_TYPE, "text/plain") + .header(header::ALLOW, "GET, HEAD") + .body("This resource only supports GET and HEAD.")); } let etag = self.etag(); @@ -233,17 +250,21 @@ impl Responder for NamedFile { let mut resp = HttpResponse::build(self.status_code); - resp - .if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); + resp.if_some(self.path().extension(), |ext, resp| { + resp.set(header::ContentType(get_mime_type( + &ext.to_string_lossy(), + ))); + }).if_some(last_modified, |lm, resp| { + resp.set(header::LastModified(lm)); }) - .if_some(last_modified, |lm, resp| {resp.set(header::LastModified(lm));}) - .if_some(etag, |etag, resp| {resp.set(header::ETag(etag));}); + .if_some(etag, |etag, resp| { + resp.set(header::ETag(etag)); + }); if precondition_failed { - return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()) + return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); } else if not_modified { - return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()) + return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); } if *req.method() == Method::HEAD { @@ -252,7 +273,8 @@ impl Responder for NamedFile { let reader = ChunkedReadFile { size: self.md.len(), offset: 0, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), + cpu_pool: self.cpu_pool + .unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, }; @@ -273,7 +295,7 @@ pub struct ChunkedReadFile { impl Stream for ChunkedReadFile { type Item = Bytes; - type Error= Error; + type Error = Error; fn poll(&mut self) -> Poll, Error> { if self.fut.is_some() { @@ -283,7 +305,7 @@ impl Stream for ChunkedReadFile { self.file = Some(file); self.offset += bytes.len() as u64; Ok(Async::Ready(Some(bytes))) - }, + } Async::NotReady => Ok(Async::NotReady), }; } @@ -299,11 +321,11 @@ impl Stream for ChunkedReadFile { let max_bytes = cmp::min(size.saturating_sub(offset), 65_536) as usize; let mut buf = BytesMut::with_capacity(max_bytes); file.seek(io::SeekFrom::Start(offset))?; - let nbytes = file.read(unsafe{buf.bytes_mut()})?; + let nbytes = file.read(unsafe { buf.bytes_mut() })?; if nbytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()) + return Err(io::ErrorKind::UnexpectedEof.into()); } - unsafe{buf.advance_mut(nbytes)}; + unsafe { buf.advance_mut(nbytes) }; Ok((file, buf.freeze())) })); self.poll() @@ -313,9 +335,9 @@ impl Stream for ChunkedReadFile { /// A directory; responds with the generated directory listing. #[derive(Debug)] -pub struct Directory{ +pub struct Directory { base: PathBuf, - path: PathBuf + path: PathBuf, } impl Directory { @@ -327,12 +349,12 @@ impl Directory { if let Ok(ref entry) = *entry { if let Some(name) = entry.file_name().to_str() { if name.starts_with('.') { - return false + return false; } } if let Ok(ref md) = entry.metadata() { let ft = md.file_type(); - return ft.is_dir() || ft.is_file() || ft.is_symlink() + return ft.is_dir() || ft.is_file() || ft.is_symlink(); } } false @@ -353,7 +375,7 @@ impl Responder for Directory { let entry = entry.unwrap(); let p = match entry.path().strip_prefix(&self.path) { Ok(p) => base.join(p), - Err(_) => continue + Err(_) => continue, }; // show file url as relative to static path let file_url = format!("{}", p.to_string_lossy()); @@ -361,27 +383,38 @@ impl Responder for Directory { // 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()); + let _ = write!( + body, + "
  • {}/
  • ", + file_url, + entry.file_name().to_string_lossy() + ); } else { - let _ = write!(body, "
  • {}
  • ", - file_url, entry.file_name().to_string_lossy()); + let _ = write!( + body, + "
  • {}
  • ", + file_url, + entry.file_name().to_string_lossy() + ); } } else { - continue + continue; } } } - let html = format!("\ - {}\ -

    {}

    \ -
      \ - {}\ -
    \n", index_of, index_of, body); + let html = format!( + "\ + {}\ +

    {}

    \ +
      \ + {}\ +
    \n", + index_of, index_of, body + ); Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) + .content_type("text/html; charset=utf-8") + .body(html)) } } @@ -411,12 +444,11 @@ pub struct StaticFiles { _follow_symlinks: bool, } -lazy_static!{ +lazy_static! { static ref DEFAULT_CPUPOOL: Mutex = Mutex::new(CpuPool::new(20)); } impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. pub fn new>(dir: T) -> StaticFiles { let dir = dir.into(); @@ -429,7 +461,7 @@ impl StaticFiles { warn!("Is not directory `{:?}`", dir); (dir, false) } - }, + } Err(err) => { warn!("Static files directory `{:?}` error: {}", dir, err); (dir, false) @@ -437,9 +469,7 @@ impl StaticFiles { }; // use default CpuPool - let pool = { - DEFAULT_CPUPOOL.lock().unwrap().clone() - }; + let pool = { DEFAULT_CPUPOOL.lock().unwrap().clone() }; StaticFiles { directory: dir, @@ -447,8 +477,9 @@ impl StaticFiles { index: None, show_index: false, cpu_pool: pool, - default: Box::new(WrapHandler::new( - |_| HttpResponse::new(StatusCode::NOT_FOUND))), + default: Box::new(WrapHandler::new(|_| { + HttpResponse::new(StatusCode::NOT_FOUND) + })), _chunk_size: 0, _follow_symlinks: false, } @@ -485,12 +516,13 @@ impl Handler for StaticFiles { if !self.accessible { Ok(self.default.handle(req)) } else { - let relpath = match req.match_info().get("tail").map( - |tail| percent_decode(tail.as_bytes()).decode_utf8().unwrap()) + let relpath = match req.match_info() + .get("tail") + .map(|tail| percent_decode(tail.as_bytes()).decode_utf8().unwrap()) .map(|tail| PathBuf::from_param(tail.as_ref())) { Some(Ok(path)) => path, - _ => return Ok(self.default.handle(req)) + _ => return Ok(self.default.handle(req)), }; // full filepath @@ -499,7 +531,8 @@ impl Handler for StaticFiles { if path.is_dir() { if let Some(ref redir_index) = self.index { // TODO: Don't redirect, just return the index content. - // TODO: It'd be nice if there were a good usable URL manipulation library + // TODO: It'd be nice if there were a good usable URL manipulation + // library let mut new_path: String = req.path().to_owned(); for el in relpath.iter() { new_path.push_str(&el.to_string_lossy()); @@ -516,14 +549,15 @@ impl Handler for StaticFiles { } else if self.show_index { Directory::new(self.directory.clone(), path) .respond_to(req.drop_state())? - .respond_to(req.drop_state()) + .respond_to(req.drop_state()) } else { Ok(self.default.handle(req)) } } else { - NamedFile::open(path)?.set_cpu_pool(self.cpu_pool.clone()) + NamedFile::open(path)? + .set_cpu_pool(self.cpu_pool.clone()) .respond_to(req.drop_state())? - .respond_to(req.drop_state()) + .respond_to(req.drop_state()) } } } @@ -533,33 +567,49 @@ impl Handler for StaticFiles { mod tests { use super::*; use application::App; - use test::{self, TestRequest}; use http::{header, Method, StatusCode}; + use test::{self, TestRequest}; #[test] fn test_named_file() { assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml").unwrap() + let mut file = NamedFile::open("Cargo.toml") + .unwrap() .set_cpu_pool(CpuPool::new(1)); - { file.file(); - let _f: &File = &file; } - { let _f: &mut File = &mut file; } + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } let resp = file.respond_to(HttpRequest::default()).unwrap(); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml") + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ) } #[test] fn test_named_file_status_code() { - let mut file = NamedFile::open("Cargo.toml").unwrap() + let mut file = NamedFile::open("Cargo.toml") + .unwrap() .set_status_code(StatusCode::NOT_FOUND) .set_cpu_pool(CpuPool::new(1)); - { file.file(); - let _f: &File = &file; } - { let _f: &mut File = &mut file; } + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } let resp = file.respond_to(HttpRequest::default()).unwrap(); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml"); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } @@ -584,13 +634,17 @@ mod tests { fn test_static_files() { let mut st = StaticFiles::new(".").show_files_listing(); st.accessible = false; - let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(HttpRequest::default()) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::NOT_FOUND); st.accessible = true; st.show_index = false; - let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(HttpRequest::default()) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::NOT_FOUND); @@ -598,9 +652,14 @@ mod tests { req.match_info_mut().add("tail", ""); st.show_index = true; - let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(req) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8"); + 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")); } @@ -611,18 +670,28 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "guide"); - let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(req) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html"); + assert_eq!( + resp.headers().get(header::LOCATION).unwrap(), + "/guide/index.html" + ); let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "guide/"); - let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(req) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html"); + assert_eq!( + resp.headers().get(header::LOCATION).unwrap(), + "/guide/index.html" + ); } #[test] @@ -631,58 +700,87 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tools/wsload"); - let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(req) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/tools/wsload/Cargo.toml"); + assert_eq!( + resp.headers().get(header::LOCATION).unwrap(), + "/tools/wsload/Cargo.toml" + ); } #[test] fn integration_redirect_to_index_with_prefix() { - let mut srv = test::TestServer::with_factory( - || App::new() + let mut srv = test::TestServer::with_factory(|| { + App::new() .prefix("public") - .handler("/", StaticFiles::new(".").index_file("Cargo.toml"))); + .handler("/", StaticFiles::new(".").index_file("Cargo.toml")) + }); let request = srv.get().uri(srv.url("/public")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + let loc = response + .headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(); assert_eq!(loc, "/public/Cargo.toml"); let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + let loc = response + .headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(); assert_eq!(loc, "/public/Cargo.toml"); } #[test] fn integration_redirect_to_index() { - let mut srv = test::TestServer::with_factory( - || App::new() - .handler("test", StaticFiles::new(".").index_file("Cargo.toml"))); + let mut srv = test::TestServer::with_factory(|| { + App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) + }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + let loc = response + .headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(); assert_eq!(loc, "/test/Cargo.toml"); let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + let loc = response + .headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(); assert_eq!(loc, "/test/Cargo.toml"); } #[test] fn integration_percent_encoded() { - let mut srv = test::TestServer::with_factory( - || App::new() - .handler("test", StaticFiles::new(".").index_file("Cargo.toml"))); + let mut srv = test::TestServer::with_factory(|| { + App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) + }); - let request = srv.get().uri(srv.url("/test/%43argo.toml")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test/%43argo.toml")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::OK); } diff --git a/src/handler.rs b/src/handler.rs index 1fc7febd..6da1f886 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,7 +1,7 @@ -use std::ops::Deref; -use std::marker::PhantomData; use futures::Poll; -use futures::future::{Future, FutureResult, ok, err}; +use futures::future::{err, ok, Future, FutureResult}; +use std::marker::PhantomData; +use std::ops::Deref; use error::Error; use httprequest::HttpRequest; @@ -10,7 +10,6 @@ use httpresponse::HttpResponse; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] pub trait Handler: 'static { - /// The type of value that handler will return. type Result: Responder; @@ -35,13 +34,15 @@ pub trait Responder { /// Trait implemented by types that can be extracted from request. /// /// Types that implement this trait can be used with `Route::with()` method. -pub trait FromRequest: Sized where S: 'static +pub trait FromRequest: Sized +where + S: 'static, { /// Configuration for conversion process type Config: Default; /// Future that resolves to a Self - type Result: Future; + type Result: Future; /// Convert request to a Self fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; @@ -83,7 +84,9 @@ pub enum Either { } impl Responder for Either - where A: Responder, B: Responder +where + A: Responder, + B: Responder, { type Item = Reply; type Error = Error; @@ -103,8 +106,9 @@ impl Responder for Either } impl Future for Either - where A: Future, - B: Future, +where + A: Future, + B: Future, { type Item = I; type Error = E; @@ -146,23 +150,25 @@ impl Future for Either /// # fn main() {} /// ``` pub trait AsyncResponder: Sized { - fn responder(self) -> Box>; + fn responder(self) -> Box>; } impl AsyncResponder for F - where F: Future + 'static, - I: Responder + 'static, - E: Into + 'static, +where + F: Future + 'static, + I: Responder + 'static, + E: Into + 'static, { - fn responder(self) -> Box> { + fn responder(self) -> Box> { Box::new(self) } } /// Handler for Fn() impl Handler for F - where F: Fn(HttpRequest) -> R + 'static, - R: Responder + 'static +where + F: Fn(HttpRequest) -> R + 'static, + R: Responder + 'static, { type Result = R; @@ -176,15 +182,15 @@ pub struct Reply(ReplyItem); pub(crate) enum ReplyItem { Message(HttpResponse), - Future(Box>), + Future(Box>), } impl Reply { - /// Create async response #[inline] pub fn async(fut: F) -> Reply - where F: Future + 'static + where + F: Future + 'static, { Reply(ReplyItem::Future(Box::new(fut))) } @@ -229,15 +235,13 @@ impl Responder for HttpResponse { } impl From for Reply { - #[inline] fn from(resp: HttpResponse) -> Reply { Reply(ReplyItem::Message(resp)) } } -impl> Responder for Result -{ +impl> Responder for Result { type Item = ::Item; type Error = Error; @@ -272,19 +276,20 @@ impl> From> for Reply { } } -impl From>> for Reply { +impl From>> for Reply { #[inline] - fn from(fut: Box>) -> Reply { + fn from(fut: Box>) -> Reply { Reply(ReplyItem::Future(fut)) } } /// Convenience type alias -pub type FutureResponse = Box>; +pub type FutureResponse = Box>; -impl Responder for Box> - where I: Responder + 'static, - E: Into + 'static +impl Responder for Box> +where + I: Responder + 'static, + E: Into + 'static, { type Item = Reply; type Error = Error; @@ -292,14 +297,12 @@ impl Responder for Box> #[inline] 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), - } + .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)) } @@ -311,30 +314,35 @@ pub(crate) trait RouteHandler: 'static { } /// Route handler wrapper for Handler -pub(crate) -struct WrapHandler - where H: Handler, - R: Responder, - S: 'static, +pub(crate) struct WrapHandler +where + H: Handler, + R: Responder, + S: 'static, { h: H, s: PhantomData, } impl WrapHandler - where H: Handler, - R: Responder, - S: 'static, +where + H: Handler, + R: Responder, + S: 'static, { pub fn new(h: H) -> Self { - WrapHandler{h, s: PhantomData} + WrapHandler { + h, + s: PhantomData, + } } } impl RouteHandler for WrapHandler - where H: Handler, - R: Responder + 'static, - S: 'static, +where + H: Handler, + R: Responder + 'static, + S: 'static, { fn handle(&mut self, req: HttpRequest) -> Reply { let req2 = req.drop_state(); @@ -346,50 +354,53 @@ impl RouteHandler for WrapHandler } /// Async route handler -pub(crate) -struct AsyncHandler - where H: Fn(HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, +pub(crate) struct AsyncHandler +where + H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static, + S: 'static, { h: Box, s: PhantomData, } impl AsyncHandler - where H: Fn(HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, +where + H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static, + S: 'static, { pub fn new(h: H) -> Self { - AsyncHandler{h: Box::new(h), s: PhantomData} + AsyncHandler { + h: Box::new(h), + s: PhantomData, + } } } impl RouteHandler for AsyncHandler - where H: Fn(HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, +where + H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static, + S: 'static, { fn handle(&mut self, req: HttpRequest) -> Reply { let req2 = req.drop_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), - } - }); + 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) } } @@ -426,7 +437,7 @@ impl RouteHandler for AsyncHandler /// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor /// } /// ``` -pub struct State (HttpRequest); +pub struct State(HttpRequest); impl Deref for State { type Target = S; @@ -436,8 +447,7 @@ impl Deref for State { } } -impl FromRequest for State -{ +impl FromRequest for State { type Config = (); type Result = FutureResult; diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs index be49b151..d736e53a 100644 --- a/src/header/common/accept.rs +++ b/src/header/common/accept.rs @@ -1,6 +1,6 @@ -use mime::{self, Mime}; -use header::{QualityItem, qitem}; +use header::{qitem, QualityItem}; use http::header as http; +use mime::{self, Mime}; header! { /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs index 3282198e..674415fb 100644 --- a/src/header/common/accept_charset.rs +++ b/src/header/common/accept_charset.rs @@ -1,4 +1,4 @@ -use header::{ACCEPT_CHARSET, Charset, QualityItem}; +use header::{Charset, QualityItem, ACCEPT_CHARSET}; header! { /// `Accept-Charset` header, defined in diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs index c9059bee..12593e1a 100644 --- a/src/header/common/accept_language.rs +++ b/src/header/common/accept_language.rs @@ -1,5 +1,5 @@ +use header::{QualityItem, ACCEPT_LANGUAGE}; use language_tags::LanguageTag; -use header::{ACCEPT_LANGUAGE, QualityItem}; header! { /// `Accept-Language` header, defined in diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs index 09a39b18..adc60e4a 100644 --- a/src/header/common/cache_control.rs +++ b/src/header/common/cache_control.rs @@ -1,8 +1,8 @@ +use header::{Header, IntoHeaderValue, Writer}; +use header::{fmt_comma_delimited, from_comma_delimited}; +use http::header; use std::fmt::{self, Write}; use std::str::FromStr; -use http::header; -use header::{Header, IntoHeaderValue, Writer}; -use header::{from_comma_delimited, fmt_comma_delimited}; /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) /// @@ -30,9 +30,7 @@ use header::{from_comma_delimited, fmt_comma_delimited}; /// use actix_web::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = HttpResponse::Ok(); -/// builder.set( -/// CacheControl(vec![CacheDirective::MaxAge(86400u32)]) -/// ); +/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// /// ```rust @@ -40,15 +38,12 @@ use header::{from_comma_delimited, fmt_comma_delimited}; /// use actix_web::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = HttpResponse::Ok(); -/// builder.set( -/// CacheControl(vec![ -/// CacheDirective::NoCache, -/// CacheDirective::Private, -/// CacheDirective::MaxAge(360u32), -/// CacheDirective::Extension("foo".to_owned(), -/// Some("bar".to_owned())), -/// ]) -/// ); +/// builder.set(CacheControl(vec![ +/// CacheDirective::NoCache, +/// CacheDirective::Private, +/// CacheDirective::MaxAge(360u32), +/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), +/// ])); /// ``` #[derive(PartialEq, Clone, Debug)] pub struct CacheControl(pub Vec); @@ -63,7 +58,8 @@ impl Header for CacheControl { #[inline] fn parse(msg: &T) -> Result - where T: ::HttpMessage + where + T: ::HttpMessage, { let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; if !directives.is_empty() { @@ -123,32 +119,36 @@ pub enum CacheDirective { SMaxAge(u32), /// Extension directives. Optionally include an argument. - Extension(String, Option) + Extension(String, Option), } impl fmt::Display for CacheDirective { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::CacheDirective::*; - fmt::Display::fmt(match *self { - NoCache => "no-cache", - NoStore => "no-store", - NoTransform => "no-transform", - OnlyIfCached => "only-if-cached", + fmt::Display::fmt( + match *self { + NoCache => "no-cache", + NoStore => "no-store", + NoTransform => "no-transform", + OnlyIfCached => "only-if-cached", - MaxAge(secs) => return write!(f, "max-age={}", secs), - MaxStale(secs) => return write!(f, "max-stale={}", secs), - MinFresh(secs) => return write!(f, "min-fresh={}", secs), + MaxAge(secs) => return write!(f, "max-age={}", secs), + MaxStale(secs) => return write!(f, "max-stale={}", secs), + MinFresh(secs) => return write!(f, "min-fresh={}", secs), - MustRevalidate => "must-revalidate", - Public => "public", - Private => "private", - ProxyRevalidate => "proxy-revalidate", - SMaxAge(secs) => return write!(f, "s-maxage={}", secs), + MustRevalidate => "must-revalidate", + Public => "public", + Private => "private", + ProxyRevalidate => "proxy-revalidate", + SMaxAge(secs) => return write!(f, "s-maxage={}", secs), - Extension(ref name, None) => &name[..], - Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg), - - }, f) + Extension(ref name, None) => &name[..], + Extension(ref name, Some(ref arg)) => { + return write!(f, "{}={}", name, arg) + } + }, + f, + ) } } @@ -167,16 +167,20 @@ impl FromStr for CacheDirective { "proxy-revalidate" => Ok(ProxyRevalidate), "" => Err(None), _ => match s.find('=') { - Some(idx) if idx+1 < s.len() => match (&s[..idx], (&s[idx+1..]).trim_matches('"')) { - ("max-age" , secs) => secs.parse().map(MaxAge).map_err(Some), - ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), - ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), - ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => Ok(Extension(left.to_owned(), Some(right.to_owned()))) - }, + Some(idx) if idx + 1 < s.len() => { + match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { + ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), + ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), + ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), + ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), + (left, right) => { + Ok(Extension(left.to_owned(), Some(right.to_owned()))) + } + } + } Some(_) => Err(None), - None => Ok(Extension(s.to_owned(), None)) - } + None => Ok(Extension(s.to_owned(), None)), + }, } } } @@ -189,38 +193,56 @@ mod tests { #[test] fn test_parse_multiple_headers() { - let req = TestRequest::with_header( - header::CACHE_CONTROL, "no-cache, private").finish(); + let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") + .finish(); let cache = Header::parse(&req); - assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::NoCache, - CacheDirective::Private]))) + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::NoCache, + CacheDirective::Private, + ])) + ) } #[test] fn test_parse_argument() { - let req = TestRequest::with_header( - header::CACHE_CONTROL, "max-age=100, private").finish(); + let req = + TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") + .finish(); let cache = Header::parse(&req); - assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(100), - CacheDirective::Private]))) + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::MaxAge(100), + CacheDirective::Private, + ])) + ) } #[test] fn test_parse_quote_form() { - let req = TestRequest::with_header( - header::CACHE_CONTROL, "max-age=\"200\"").finish(); + let req = + TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); let cache = Header::parse(&req); - assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(200)]))) + assert_eq!( + cache.ok(), + Some(CacheControl(vec![CacheDirective::MaxAge(200)])) + ) } #[test] fn test_parse_extension() { - let req = TestRequest::with_header( - header::CACHE_CONTROL, "foo, bar=baz").finish(); + let req = + TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); let cache = Header::parse(&req); - assert_eq!(cache.ok(), Some(CacheControl(vec![ - CacheDirective::Extension("foo".to_owned(), None), - CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned()))]))) + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::Extension("foo".to_owned(), None), + CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), + ])) + ) } #[test] diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs index a567ab69..e12d34d0 100644 --- a/src/header/common/content_language.rs +++ b/src/header/common/content_language.rs @@ -1,21 +1,21 @@ +use header::{QualityItem, CONTENT_LANGUAGE}; use language_tags::LanguageTag; -use header::{CONTENT_LANGUAGE, QualityItem}; header! { /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) - /// + /// /// The `Content-Language` header field describes the natural language(s) /// of the intended audience for the representation. Note that this /// might not be equivalent to all the languages used within the /// representation. - /// + /// /// # ABNF /// /// ```text /// Content-Language = 1#language-tag /// ``` - /// + /// /// # Example values /// /// * `da` @@ -28,7 +28,7 @@ header! { /// # #[macro_use] extern crate language_tags; /// use actix_web::HttpResponse; /// # use actix_web::http::header::{ContentLanguage, qitem}; - /// # + /// # /// # fn main() { /// let mut builder = HttpResponse::Ok(); /// builder.set( @@ -46,7 +46,7 @@ header! { /// # use actix_web::http::header::{ContentLanguage, qitem}; /// # /// # fn main() { - /// + /// /// let mut builder = HttpResponse::Ok(); /// builder.set( /// ContentLanguage(vec![ diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs index 8916cf54..ea0e3274 100644 --- a/src/header/common/content_range.rs +++ b/src/header/common/content_range.rs @@ -1,8 +1,8 @@ +use error::ParseError; +use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, + CONTENT_RANGE}; use std::fmt::{self, Display, Write}; use std::str::FromStr; -use error::ParseError; -use header::{IntoHeaderValue, Writer, - HeaderValue, InvalidHeaderValueBytes, CONTENT_RANGE}; header! { /// `Content-Range` header, defined in @@ -69,7 +69,6 @@ header! { } } - /// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) /// /// # ABNF @@ -99,7 +98,7 @@ pub enum ContentRangeSpec { range: Option<(u64, u64)>, /// Total length of the instance, can be omitted if unknown - instance_length: Option + instance_length: Option, }, /// Custom range, with unit not registered at IANA @@ -108,15 +107,15 @@ pub enum ContentRangeSpec { unit: String, /// other-range-resp - resp: String - } + resp: String, + }, } fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { let mut iter = s.splitn(2, separator); match (iter.next(), iter.next()) { (Some(a), Some(b)) => Some((a, b)), - _ => None + _ => None, } } @@ -126,40 +125,40 @@ impl FromStr for ContentRangeSpec { fn from_str(s: &str) -> Result { let res = match split_in_two(s, ' ') { Some(("bytes", resp)) => { - let (range, instance_length) = split_in_two( - resp, '/').ok_or(ParseError::Header)?; + let (range, instance_length) = + split_in_two(resp, '/').ok_or(ParseError::Header)?; let instance_length = if instance_length == "*" { None } else { - Some(instance_length.parse() - .map_err(|_| ParseError::Header)?) + Some(instance_length + .parse() + .map_err(|_| ParseError::Header)?) }; let range = if range == "*" { None } else { - let (first_byte, last_byte) = split_in_two( - range, '-').ok_or(ParseError::Header)?; - let first_byte = first_byte.parse() - .map_err(|_| ParseError::Header)?; - let last_byte = last_byte.parse() - .map_err(|_| ParseError::Header)?; + let (first_byte, last_byte) = + split_in_two(range, '-').ok_or(ParseError::Header)?; + let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?; + let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; if last_byte < first_byte { return Err(ParseError::Header); } Some((first_byte, last_byte)) }; - ContentRangeSpec::Bytes {range, instance_length} - } - Some((unit, resp)) => { - ContentRangeSpec::Unregistered { - unit: unit.to_owned(), - resp: resp.to_owned() + ContentRangeSpec::Bytes { + range, + instance_length, } } - _ => return Err(ParseError::Header) + Some((unit, resp)) => ContentRangeSpec::Unregistered { + unit: unit.to_owned(), + resp: resp.to_owned(), + }, + _ => return Err(ParseError::Header), }; Ok(res) } @@ -168,12 +167,15 @@ impl FromStr for ContentRangeSpec { impl Display for ContentRangeSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - ContentRangeSpec::Bytes { range, instance_length } => { + ContentRangeSpec::Bytes { + range, + instance_length, + } => { try!(f.write_str("bytes ")); match range { Some((first_byte, last_byte)) => { try!(write!(f, "{}-{}", first_byte, last_byte)); - }, + } None => { try!(f.write_str("*")); } @@ -185,7 +187,10 @@ impl Display for ContentRangeSpec { f.write_str("*") } } - ContentRangeSpec::Unregistered { ref unit, ref resp } => { + ContentRangeSpec::Unregistered { + ref unit, + ref resp, + } => { try!(f.write_str(unit)); try!(f.write_str(" ")); f.write_str(resp) diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs index 939054a0..08900e1c 100644 --- a/src/header/common/content_type.rs +++ b/src/header/common/content_type.rs @@ -1,6 +1,5 @@ -use mime::{self, Mime}; use header::CONTENT_TYPE; - +use mime::{self, Mime}; header! { /// `Content-Type` header, defined in @@ -68,13 +67,15 @@ header! { } impl ContentType { - /// A constructor to easily create a `Content-Type: application/json` header. + /// A constructor to easily create a `Content-Type: application/json` + /// header. #[inline] pub fn json() -> ContentType { ContentType(mime::APPLICATION_JSON) } - /// A constructor to easily create a `Content-Type: text/plain; charset=utf-8` header. + /// A constructor to easily create a `Content-Type: text/plain; + /// charset=utf-8` header. #[inline] pub fn plaintext() -> ContentType { ContentType(mime::TEXT_PLAIN_UTF_8) @@ -92,7 +93,8 @@ impl ContentType { ContentType(mime::TEXT_XML) } - /// A constructor to easily create a `Content-Type: application/www-form-url-encoded` header. + /// A constructor to easily create a `Content-Type: + /// application/www-form-url-encoded` header. #[inline] pub fn form_url_encoded() -> ContentType { ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) @@ -109,7 +111,8 @@ impl ContentType { ContentType(mime::IMAGE_PNG) } - /// A constructor to easily create a `Content-Type: application/octet-stream` header. + /// A constructor to easily create a `Content-Type: + /// application/octet-stream` header. #[inline] pub fn octet_stream() -> ContentType { ContentType(mime::APPLICATION_OCTET_STREAM) diff --git a/src/header/common/date.rs b/src/header/common/date.rs index 59d37d73..d130f064 100644 --- a/src/header/common/date.rs +++ b/src/header/common/date.rs @@ -1,6 +1,5 @@ +use header::{HttpDate, DATE}; use std::time::SystemTime; -use header::{DATE, HttpDate}; - header! { /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs index a52bd0a8..39dd908c 100644 --- a/src/header/common/etag.rs +++ b/src/header/common/etag.rs @@ -1,4 +1,4 @@ -use header::{ETAG, EntityTag}; +use header::{EntityTag, ETAG}; header! { /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs index aab751b0..4ec66b88 100644 --- a/src/header/common/expires.rs +++ b/src/header/common/expires.rs @@ -1,4 +1,4 @@ -use header::{EXPIRES, HttpDate}; +use header::{HttpDate, EXPIRES}; header! { /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs index a7ad7f70..20a2b1e6 100644 --- a/src/header/common/if_match.rs +++ b/src/header/common/if_match.rs @@ -1,4 +1,4 @@ -use header::{IF_MATCH, EntityTag}; +use header::{EntityTag, IF_MATCH}; header! { /// `If-Match` header, defined in diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs index 48d3c938..1914d34d 100644 --- a/src/header/common/if_modified_since.rs +++ b/src/header/common/if_modified_since.rs @@ -1,4 +1,4 @@ -use header::{IF_MODIFIED_SINCE, HttpDate}; +use header::{HttpDate, IF_MODIFIED_SINCE}; header! { /// `If-Modified-Since` header, defined in diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs index 8381988a..124f4b8e 100644 --- a/src/header/common/if_none_match.rs +++ b/src/header/common/if_none_match.rs @@ -1,4 +1,4 @@ -use header::{IF_NONE_MATCH, EntityTag}; +use header::{EntityTag, IF_NONE_MATCH}; header! { /// `If-None-Match` header, defined in @@ -66,8 +66,8 @@ header! { #[cfg(test)] mod tests { use super::IfNoneMatch; + use header::{EntityTag, Header, IF_NONE_MATCH}; use test::TestRequest; - use header::{IF_NONE_MATCH, Header, EntityTag}; #[test] fn test_if_none_match() { @@ -77,8 +77,9 @@ mod tests { if_none_match = Header::parse(&req); assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - let req = TestRequest::with_header( - IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]).finish(); + let req = + TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) + .finish(); if_none_match = Header::parse(&req); let mut entities: Vec = Vec::new(); diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs index 7848f12d..dd95b7ba 100644 --- a/src/header/common/if_range.rs +++ b/src/header/common/if_range.rs @@ -1,10 +1,10 @@ -use std::fmt::{self, Display, Write}; use error::ParseError; -use httpmessage::HttpMessage; -use http::header; use header::from_one_raw_str; -use header::{IntoHeaderValue, Header, HeaderName, HeaderValue, - EntityTag, HttpDate, Writer, InvalidHeaderValueBytes}; +use header::{EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, + InvalidHeaderValueBytes, Writer}; +use http::header; +use httpmessage::HttpMessage; +use std::fmt::{self, Display, Write}; /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) /// @@ -36,16 +36,19 @@ use header::{IntoHeaderValue, Header, HeaderName, HeaderValue, /// /// ```rust /// use actix_web::HttpResponse; -/// use actix_web::http::header::{IfRange, EntityTag}; +/// use actix_web::http::header::{EntityTag, IfRange}; /// /// let mut builder = HttpResponse::Ok(); -/// builder.set(IfRange::EntityTag(EntityTag::new(false, "xyzzy".to_owned()))); +/// builder.set(IfRange::EntityTag(EntityTag::new( +/// false, +/// "xyzzy".to_owned(), +/// ))); /// ``` /// /// ```rust /// use actix_web::HttpResponse; /// use actix_web::http::header::IfRange; -/// use std::time::{SystemTime, Duration}; +/// use std::time::{Duration, SystemTime}; /// /// let mut builder = HttpResponse::Ok(); /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); @@ -64,7 +67,9 @@ impl Header for IfRange { header::IF_RANGE } #[inline] - fn parse(msg: &T) -> Result where T: HttpMessage + fn parse(msg: &T) -> Result + where + T: HttpMessage, { let etag: Result = from_one_raw_str(msg.headers().get(header::IF_RANGE)); @@ -99,12 +104,11 @@ impl IntoHeaderValue for IfRange { } } - #[cfg(test)] mod test_if_range { - use std::str; - use header::*; use super::IfRange as HeaderField; + use header::*; + use std::str; test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); test_header!(test2, vec![b"\"xyzzy\""]); test_header!(test3, vec![b"this-is-invalid"], None::); diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs index 4750de0e..f87e760c 100644 --- a/src/header/common/if_unmodified_since.rs +++ b/src/header/common/if_unmodified_since.rs @@ -1,4 +1,4 @@ -use header::{IF_UNMODIFIED_SINCE, HttpDate}; +use header::{HttpDate, IF_UNMODIFIED_SINCE}; header! { /// `If-Unmodified-Since` header, defined in diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs index a882b0d1..aba82888 100644 --- a/src/header/common/last_modified.rs +++ b/src/header/common/last_modified.rs @@ -1,4 +1,4 @@ -use header::{LAST_MODIFIED, HttpDate}; +use header::{HttpDate, LAST_MODIFIED}; header! { /// `Last-Modified` header, defined in diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index 5f548f01..e86bf896 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -5,6 +5,7 @@ //! Several header fields use MIME values for their contents. Keeping with the //! strongly-typed theme, the [mime](https://docs.rs/mime) crate //! is used, such as `ContentType(pub Mime)`. +#![cfg_attr(rustfmt, rustfmt_skip)] pub use self::accept_charset::AcceptCharset; //pub use self::accept_encoding::AcceptEncoding; diff --git a/src/header/common/range.rs b/src/header/common/range.rs index d0fca0f3..71718fc7 100644 --- a/src/header/common/range.rs +++ b/src/header/common/range.rs @@ -1,8 +1,8 @@ use std::fmt::{self, Display}; use std::str::FromStr; +use header::parsing::from_one_raw_str; use header::{Header, Raw}; -use header::parsing::{from_one_raw_str}; /// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) /// @@ -65,7 +65,7 @@ pub enum Range { Bytes(Vec), /// Custom range, with unit not registered at IANA /// (`other-range-unit`: String , `other-range-set`: String) - Unregistered(String, String) + Unregistered(String, String), } /// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. @@ -77,25 +77,25 @@ pub enum ByteRangeSpec { /// Get all bytes starting from x ("x-") AllFrom(u64), /// Get last x bytes ("-x") - Last(u64) + Last(u64), } impl ByteRangeSpec { /// Given the full length of the entity, attempt to normalize the byte range /// into an satisfiable end-inclusive (from, to) range. /// - /// The resulting range is guaranteed to be a satisfiable range within the bounds - /// of `0 <= from <= to < full_length`. + /// The resulting range is guaranteed to be a satisfiable range within the + /// bounds of `0 <= from <= to < full_length`. /// /// If the byte range is deemed unsatisfiable, `None` is returned. /// An unsatisfiable range is generally cause for a server to either reject /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 OK` - /// status code. + /// simply ignore the range header and serve the full entity using a `200 + /// OK` status code. /// /// This function closely follows [RFC 7233][1] section 2.1. - /// As such, it considers ranges to be satisfiable if they meet the following - /// conditions: + /// As such, it considers ranges to be satisfiable if they meet the + /// following conditions: /// /// > If a valid byte-range-set includes at least one byte-range-spec with /// a first-byte-pos that is less than the current length of the @@ -125,14 +125,14 @@ impl ByteRangeSpec { } else { None } - }, + } &ByteRangeSpec::AllFrom(from) => { if from < full_length { Some((from, full_length - 1)) } else { None } - }, + } &ByteRangeSpec::Last(last) => { if last > 0 { // From the RFC: If the selected representation is shorter @@ -160,11 +160,15 @@ impl Range { /// Get byte range header with multiple subranges /// ("bytes=from1-to1,from2-to2,fromX-toX") pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { - Range::Bytes(ranges.iter().map(|r| ByteRangeSpec::FromTo(r.0, r.1)).collect()) + Range::Bytes( + ranges + .iter() + .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) + .collect(), + ) } } - impl fmt::Display for ByteRangeSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -175,7 +179,6 @@ impl fmt::Display for ByteRangeSpec { } } - impl fmt::Display for Range { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -189,10 +192,10 @@ impl fmt::Display for Range { try!(Display::fmt(range, f)); } Ok(()) - }, + } Range::Unregistered(ref unit, ref range_str) => { write!(f, "{}={}", unit, range_str) - }, + } } } } @@ -211,11 +214,10 @@ impl FromStr for Range { } Ok(Range::Bytes(ranges)) } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => { - Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) - - }, - _ => Err(::Error::Header) + (Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok( + Range::Unregistered(unit.to_owned(), range_str.to_owned()), + ), + _ => Err(::Error::Header), } } } @@ -227,19 +229,20 @@ impl FromStr for ByteRangeSpec { let mut parts = s.splitn(2, '-'); match (parts.next(), parts.next()) { - (Some(""), Some(end)) => { - end.parse().or(Err(::Error::Header)).map(ByteRangeSpec::Last) - }, - (Some(start), Some("")) => { - start.parse().or(Err(::Error::Header)).map(ByteRangeSpec::AllFrom) - }, - (Some(start), Some(end)) => { - match (start.parse(), end.parse()) { - (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), - _ => Err(::Error::Header) + (Some(""), Some(end)) => end.parse() + .or(Err(::Error::Header)) + .map(ByteRangeSpec::Last), + (Some(start), Some("")) => start + .parse() + .or(Err(::Error::Header)) + .map(ByteRangeSpec::AllFrom), + (Some(start), Some(end)) => match (start.parse(), end.parse()) { + (Ok(start), Ok(end)) if start <= end => { + Ok(ByteRangeSpec::FromTo(start, end)) } + _ => Err(::Error::Header), }, - _ => Err(::Error::Header) + _ => Err(::Error::Header), } } } @@ -248,14 +251,13 @@ fn from_comma_delimited(s: &str) -> Vec { s.split(',') .filter_map(|x| match x.trim() { "" => None, - y => Some(y) + y => Some(y), }) .filter_map(|x| x.parse().ok()) .collect() } impl Header for Range { - fn header_name() -> &'static str { static NAME: &'static str = "Range"; NAME @@ -268,51 +270,52 @@ impl Header for Range { fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { f.fmt_line(self) } - } #[test] fn test_parse_bytes_range_valid() { let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); - let r3 = Range::bytes(1, 100); + let r3 = Range::bytes(1, 100); assert_eq!(r, r2); assert_eq!(r2, r3); let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); - let r3 = Range::Bytes( - vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] - ); + let r2: Range = + Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); + let r3 = Range::Bytes(vec![ + ByteRangeSpec::FromTo(1, 100), + ByteRangeSpec::AllFrom(200), + ]); assert_eq!(r, r2); assert_eq!(r2, r3); let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); - let r3 = Range::Bytes( - vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::Last(100)] - ); + let r3 = Range::Bytes(vec![ + ByteRangeSpec::FromTo(1, 100), + ByteRangeSpec::Last(100), + ]); assert_eq!(r, r2); assert_eq!(r2, r3); let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); + let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); - } #[test] fn test_parse_unregistered_range_valid() { let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); + let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); + let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); assert_eq!(r, r2); let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); + let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); assert_eq!(r, r2); } @@ -346,10 +349,10 @@ fn test_fmt() { let mut headers = Headers::new(); - headers.set( - Range::Bytes( - vec![ByteRangeSpec::FromTo(0, 1000), ByteRangeSpec::AllFrom(2000)] - )); + headers.set(Range::Bytes(vec![ + ByteRangeSpec::FromTo(0, 1000), + ByteRangeSpec::AllFrom(2000), + ])); assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); headers.clear(); @@ -358,30 +361,74 @@ fn test_fmt() { assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); headers.clear(); - headers.set(Range::Unregistered("custom".to_owned(), "1-xxx".to_owned())); + headers.set(Range::Unregistered( + "custom".to_owned(), + "1-xxx".to_owned(), + )); assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); } #[test] fn test_byte_range_spec_to_satisfiable_range() { - assert_eq!(Some((0, 0)), ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3)); - assert_eq!(Some((1, 2)), ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3)); - assert_eq!(Some((1, 2)), ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)); + assert_eq!( + Some((0, 0)), + ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3) + ); + assert_eq!( + Some((1, 2)), + ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3) + ); + assert_eq!( + Some((1, 2)), + ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0) + ); - assert_eq!(Some((0, 2)), ByteRangeSpec::AllFrom(0).to_satisfiable_range(3)); - assert_eq!(Some((2, 2)), ByteRangeSpec::AllFrom(2).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)); + assert_eq!( + Some((0, 2)), + ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) + ); + assert_eq!( + Some((2, 2)), + ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(3).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(5).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(0).to_satisfiable_range(0) + ); - assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3)); - assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3)); - assert_eq!(Some((0, 2)), ByteRangeSpec::Last(5).to_satisfiable_range(3)); + assert_eq!( + Some((1, 2)), + ByteRangeSpec::Last(2).to_satisfiable_range(3) + ); + assert_eq!( + Some((2, 2)), + ByteRangeSpec::Last(1).to_satisfiable_range(3) + ); + assert_eq!( + Some((0, 2)), + ByteRangeSpec::Last(5).to_satisfiable_range(3) + ); assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); } - diff --git a/src/header/mod.rs b/src/header/mod.rs index 2e57eef8..3564da9e 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -5,9 +5,9 @@ use std::fmt; use std::str::FromStr; use bytes::{Bytes, BytesMut}; -use modhttp::{Error as HttpError}; -use modhttp::header::GetAll; use mime::Mime; +use modhttp::Error as HttpError; +use modhttp::header::GetAll; pub use modhttp::header::*; @@ -21,11 +21,12 @@ pub use self::common::*; #[doc(hidden)] pub use self::shared::*; - #[doc(hidden)] /// A trait for any object that will represent a header field and value. -pub trait Header where Self: IntoHeaderValue { - +pub trait Header +where + Self: IntoHeaderValue, +{ /// Returns the name of the header field fn name() -> HeaderName; @@ -112,7 +113,7 @@ pub enum ContentEncoding { /// Automatically select encoding based on encoding negotiation Auto, /// A format using the Brotli algorithm - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] Br, /// A format using the zlib structure with deflate algorithm Deflate, @@ -123,19 +124,18 @@ pub enum ContentEncoding { } impl ContentEncoding { - #[inline] pub fn is_compression(&self) -> bool { match *self { ContentEncoding::Identity | ContentEncoding::Auto => false, - _ => true + _ => true, } } #[inline] pub fn as_str(&self) -> &'static str { match *self { - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] ContentEncoding::Br => "br", ContentEncoding::Gzip => "gzip", ContentEncoding::Deflate => "deflate", @@ -147,7 +147,7 @@ impl ContentEncoding { /// default quality value pub fn quality(&self) -> f64 { match *self { - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] ContentEncoding::Br => 1.1, ContentEncoding::Gzip => 1.0, ContentEncoding::Deflate => 0.9, @@ -160,7 +160,7 @@ impl ContentEncoding { impl<'a> From<&'a str> for ContentEncoding { fn from(s: &'a str) -> ContentEncoding { match s.trim().to_lowercase().as_ref() { - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] "br" => ContentEncoding::Br, "gzip" => ContentEncoding::Gzip, "deflate" => ContentEncoding::Deflate, @@ -176,7 +176,9 @@ pub(crate) struct Writer { impl Writer { fn new() -> Writer { - Writer{buf: BytesMut::new()} + Writer { + buf: BytesMut::new(), + } } fn take(&mut self) -> Bytes { self.buf.take().freeze() @@ -199,18 +201,20 @@ impl fmt::Write for Writer { #[inline] #[doc(hidden)] /// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited(all: GetAll) - -> Result, ParseError> -{ +pub fn from_comma_delimited( + all: GetAll +) -> Result, ParseError> { let mut result = Vec::new(); for h in all { let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend(s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y) - }) - .filter_map(|x| x.trim().parse().ok())) + result.extend( + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.trim().parse().ok()), + ) } Ok(result) } @@ -218,13 +222,11 @@ pub fn from_comma_delimited(all: GetAll) #[inline] #[doc(hidden)] /// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) - -> Result -{ +pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { if let Some(line) = val { let line = line.to_str().map_err(|_| ParseError::Header)?; if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)) + return T::from_str(line).or(Err(ParseError::Header)); } } Err(ParseError::Header) @@ -234,7 +236,8 @@ pub fn from_one_raw_str(val: Option<&HeaderValue>) #[doc(hidden)] /// Format an array into a comma-delimited string. pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result - where T: fmt::Display +where + T: fmt::Display, { let mut iter = parts.iter(); if let Some(part) = iter.next() { diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs index 765b34af..bab9d65d 100644 --- a/src/header/shared/charset.rs +++ b/src/header/shared/charset.rs @@ -1,7 +1,7 @@ #![allow(unused, deprecated)] +use std::ascii::AsciiExt; use std::fmt::{self, Display}; use std::str::FromStr; -use std::ascii::AsciiExt; use self::Charset::*; @@ -12,9 +12,9 @@ use self::Charset::*; /// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. /// /// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml -#[derive(Clone,Debug,PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[allow(non_camel_case_types)] -pub enum Charset{ +pub enum Charset { /// US ASCII Us_Ascii, /// ISO-8859-1 @@ -64,7 +64,7 @@ pub enum Charset{ /// KOI8-R Koi8_R, /// An arbitrary charset specified as a string - Ext(String) + Ext(String), } impl Charset { @@ -94,7 +94,7 @@ impl Charset { Gb2312 => "GB2312", Big5 => "5", Koi8_R => "KOI8-R", - Ext(ref s) => s + Ext(ref s) => s, } } } @@ -133,18 +133,18 @@ impl FromStr for Charset { "GB2312" => Gb2312, "5" => Big5, "KOI8-R" => Koi8_R, - s => Ext(s.to_owned()) + s => Ext(s.to_owned()), }) } } #[test] fn test_parse() { - assert_eq!(Us_Ascii,"us-ascii".parse().unwrap()); - assert_eq!(Us_Ascii,"US-Ascii".parse().unwrap()); - assert_eq!(Us_Ascii,"US-ASCII".parse().unwrap()); - assert_eq!(Shift_Jis,"Shift-JIS".parse().unwrap()); - assert_eq!(Ext("ABCD".to_owned()),"abcd".parse().unwrap()); + assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); + assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); + assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); + assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); + assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); } #[test] diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs index 6381ac7e..e4abe470 100644 --- a/src/header/shared/encoding.rs +++ b/src/header/shared/encoding.rs @@ -1,7 +1,8 @@ use std::fmt; use std::str; -pub use self::Encoding::{Chunked, Brotli, Gzip, Deflate, Compress, Identity, EncodingExt, Trailers}; +pub use self::Encoding::{Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, + Identity, Trailers}; /// A value to represent an encoding used in `Transfer-Encoding` /// or `Accept-Encoding` header. @@ -22,7 +23,7 @@ pub enum Encoding { /// The `trailers` encoding. Trailers, /// Some other encoding that is less common, can be any String. - EncodingExt(String) + EncodingExt(String), } impl fmt::Display for Encoding { @@ -35,7 +36,7 @@ impl fmt::Display for Encoding { Compress => "compress", Identity => "identity", Trailers => "trailers", - EncodingExt(ref s) => s.as_ref() + EncodingExt(ref s) => s.as_ref(), }) } } @@ -51,7 +52,7 @@ impl str::FromStr for Encoding { "compress" => Ok(Compress), "identity" => Ok(Identity), "trailers" => Ok(Trailers), - _ => Ok(EncodingExt(s.to_owned())) + _ => Ok(EncodingExt(s.to_owned())), } } } diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs index 08a66b4f..347c4c02 100644 --- a/src/header/shared/entity.rs +++ b/src/header/shared/entity.rs @@ -1,21 +1,23 @@ -use std::str::FromStr; +use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; use std::fmt::{self, Display, Write}; -use header::{HeaderValue, Writer, IntoHeaderValue, InvalidHeaderValueBytes}; +use std::str::FromStr; /// check that each char in the slice is either: /// 1. `%x21`, or /// 2. in the range `%x23` to `%x7E`, or /// 3. above `%x80` fn check_slice_validity(slice: &str) -> bool { - slice.bytes().all(|c| - c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) + slice + .bytes() + .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) } /// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) /// /// An entity tag consists of a string enclosed by two literal double quotes. /// Preceding the first double quote is an optional weakness indicator, -/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and `W/"xyzzy"`. +/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and +/// `W/"xyzzy"`. /// /// # ABNF /// @@ -28,9 +30,9 @@ fn check_slice_validity(slice: &str) -> bool { /// ``` /// /// # Comparison -/// To check if two entity tags are equivalent in an application always use the `strong_eq` or -/// `weak_eq` methods based on the context of the Tag. Only use `==` to check if two tags are -/// identical. +/// To check if two entity tags are equivalent in an application always use the +/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use +/// `==` to check if two tags are identical. /// /// The example below shows the results for a set of entity-tag pairs and /// both the weak and strong comparison function results: @@ -46,7 +48,7 @@ pub struct EntityTag { /// Weakness indicator for the tag pub weak: bool, /// The opaque string in between the DQUOTEs - tag: String + tag: String, } impl EntityTag { @@ -85,8 +87,8 @@ impl EntityTag { self.tag = tag } - /// For strong comparison two entity-tags are equivalent if both are not weak and their - /// opaque-tags match character-by-character. + /// For strong comparison two entity-tags are equivalent if both are not + /// weak and their opaque-tags match character-by-character. pub fn strong_eq(&self, other: &EntityTag) -> bool { !self.weak && !other.weak && self.tag == other.tag } @@ -131,13 +133,21 @@ impl FromStr for EntityTag { } // The etag is weak if its first char is not a DQUOTE. if slice.len() >= 2 && slice.starts_with('"') - && check_slice_validity(&slice[1..length-1]) { + && check_slice_validity(&slice[1..length - 1]) + { // No need to check if the last char is a DQUOTE, // we already did that above. - return Ok(EntityTag { weak: false, tag: slice[1..length-1].to_owned() }); + return Ok(EntityTag { + weak: false, + tag: slice[1..length - 1].to_owned(), + }); } else if slice.len() >= 4 && slice.starts_with("W/\"") - && check_slice_validity(&slice[3..length-1]) { - return Ok(EntityTag { weak: true, tag: slice[3..length-1].to_owned() }); + && check_slice_validity(&slice[3..length - 1]) + { + return Ok(EntityTag { + weak: true, + tag: slice[3..length - 1].to_owned(), + }); } Err(::error::ParseError::Header) } @@ -149,7 +159,7 @@ impl IntoHeaderValue for EntityTag { fn try_into(self) -> Result { let mut wrt = Writer::new(); write!(wrt, "{}", self).unwrap(); - unsafe{Ok(HeaderValue::from_shared_unchecked(wrt.take()))} + unsafe { Ok(HeaderValue::from_shared_unchecked(wrt.take())) } } } @@ -160,22 +170,37 @@ mod tests { #[test] fn test_etag_parse_success() { // Expected success - assert_eq!("\"foobar\"".parse::().unwrap(), - EntityTag::strong("foobar".to_owned())); - assert_eq!("\"\"".parse::().unwrap(), - EntityTag::strong("".to_owned())); - assert_eq!("W/\"weaktag\"".parse::().unwrap(), - EntityTag::weak("weaktag".to_owned())); - assert_eq!("W/\"\x65\x62\"".parse::().unwrap(), - EntityTag::weak("\x65\x62".to_owned())); - assert_eq!("W/\"\"".parse::().unwrap(), EntityTag::weak("".to_owned())); + assert_eq!( + "\"foobar\"".parse::().unwrap(), + EntityTag::strong("foobar".to_owned()) + ); + assert_eq!( + "\"\"".parse::().unwrap(), + EntityTag::strong("".to_owned()) + ); + assert_eq!( + "W/\"weaktag\"".parse::().unwrap(), + EntityTag::weak("weaktag".to_owned()) + ); + assert_eq!( + "W/\"\x65\x62\"".parse::().unwrap(), + EntityTag::weak("\x65\x62".to_owned()) + ); + assert_eq!( + "W/\"\"".parse::().unwrap(), + EntityTag::weak("".to_owned()) + ); } #[test] fn test_etag_parse_failures() { // Expected failures assert!("no-dquotes".parse::().is_err()); - assert!("w/\"the-first-w-is-case-sensitive\"".parse::().is_err()); + assert!( + "w/\"the-first-w-is-case-sensitive\"" + .parse::() + .is_err() + ); assert!("".parse::().is_err()); assert!("\"unmatched-dquotes1".parse::().is_err()); assert!("unmatched-dquotes2\"".parse::().is_err()); @@ -184,11 +209,26 @@ mod tests { #[test] fn test_etag_fmt() { - assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\""); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); - assert_eq!(format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\""); - assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\""); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); + assert_eq!( + format!("{}", EntityTag::strong("foobar".to_owned())), + "\"foobar\"" + ); + assert_eq!( + format!("{}", EntityTag::strong("".to_owned())), + "\"\"" + ); + assert_eq!( + format!("{}", EntityTag::weak("weak-etag".to_owned())), + "W/\"weak-etag\"" + ); + assert_eq!( + format!("{}", EntityTag::weak("\u{0065}".to_owned())), + "W/\"\x65\"" + ); + assert_eq!( + format!("{}", EntityTag::weak("".to_owned())), + "W/\"\"" + ); } #[test] diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs index b2fcf527..5de1e3f9 100644 --- a/src/header/shared/httpdate.rs +++ b/src/header/shared/httpdate.rs @@ -3,14 +3,13 @@ use std::io::Write; use std::str::FromStr; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use time; -use bytes::{BytesMut, BufMut}; +use bytes::{BufMut, BytesMut}; use http::header::{HeaderValue, InvalidHeaderValueBytes}; +use time; use error::ParseError; use header::IntoHeaderValue; - /// A timestamp with HTTP formatting and parsing #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct HttpDate(time::Tm); @@ -19,11 +18,10 @@ impl FromStr for HttpDate { type Err = ParseError; fn from_str(s: &str) -> Result { - match time::strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { - time::strptime(s, "%A, %d-%b-%y %T %Z") - }).or_else(|_| { - time::strptime(s, "%c") - }) { + match time::strptime(s, "%a, %d %b %Y %T %Z") + .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) + .or_else(|_| time::strptime(s, "%c")) + { Ok(t) => Ok(HttpDate(t)), Err(_) => Err(ParseError::Header), } @@ -47,11 +45,14 @@ impl From for HttpDate { let tmspec = match sys.duration_since(UNIX_EPOCH) { Ok(dur) => { time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) - }, + } Err(err) => { let neg = err.duration(); - time::Timespec::new(-(neg.as_secs() as i64), -(neg.subsec_nanos() as i32)) - }, + time::Timespec::new( + -(neg.as_secs() as i64), + -(neg.subsec_nanos() as i32), + ) + } }; HttpDate(time::at_utc(tmspec)) } @@ -63,7 +64,11 @@ impl IntoHeaderValue for HttpDate { fn try_into(self) -> Result { let mut wrt = BytesMut::with_capacity(29).writer(); write!(wrt, "{}", self.0.rfc822()).unwrap(); - unsafe{Ok(HeaderValue::from_shared_unchecked(wrt.get_mut().take().freeze()))} + unsafe { + Ok(HeaderValue::from_shared_unchecked( + wrt.get_mut().take().freeze(), + )) + } } } @@ -80,18 +85,43 @@ impl From for SystemTime { #[cfg(test)] mod tests { - use time::Tm; use super::HttpDate; + use time::Tm; const NOV_07: HttpDate = HttpDate(Tm { - tm_nsec: 0, tm_sec: 37, tm_min: 48, tm_hour: 8, tm_mday: 7, tm_mon: 10, tm_year: 94, - tm_wday: 0, tm_isdst: 0, tm_yday: 0, tm_utcoff: 0}); + tm_nsec: 0, + tm_sec: 37, + tm_min: 48, + tm_hour: 8, + tm_mday: 7, + tm_mon: 10, + tm_year: 94, + tm_wday: 0, + tm_isdst: 0, + tm_yday: 0, + tm_utcoff: 0, + }); #[test] fn test_date() { - assert_eq!("Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), NOV_07); - assert_eq!("Sunday, 07-Nov-94 08:48:37 GMT".parse::().unwrap(), NOV_07); - assert_eq!("Sun Nov 7 08:48:37 1994".parse::().unwrap(), NOV_07); + assert_eq!( + "Sun, 07 Nov 1994 08:48:37 GMT" + .parse::() + .unwrap(), + NOV_07 + ); + assert_eq!( + "Sunday, 07-Nov-94 08:48:37 GMT" + .parse::() + .unwrap(), + NOV_07 + ); + assert_eq!( + "Sun Nov 7 08:48:37 1994" + .parse::() + .unwrap(), + NOV_07 + ); assert!("this-is-no-date".parse::().is_err()); } } diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs index 04ff7f41..f2bc9163 100644 --- a/src/header/shared/mod.rs +++ b/src/header/shared/mod.rs @@ -4,11 +4,11 @@ pub use self::charset::Charset; pub use self::encoding::Encoding; pub use self::entity::EntityTag; pub use self::httpdate::HttpDate; +pub use self::quality_item::{q, qitem, Quality, QualityItem}; pub use language_tags::LanguageTag; -pub use self::quality_item::{Quality, QualityItem, qitem, q}; mod charset; -mod entity; mod encoding; +mod entity; mod httpdate; mod quality_item; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs index aa56866a..6dca77fe 100644 --- a/src/header/shared/quality_item.rs +++ b/src/header/shared/quality_item.rs @@ -13,11 +13,13 @@ use self::internal::IntoQuality; /// /// # Implementation notes /// -/// The quality value is defined as a number between 0 and 1 with three decimal places. This means -/// there are 1001 possible values. Since floating point numbers are not exact and the smallest -/// floating point data type (`f32`) consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to a value between -/// 0 and 1000 e.g. `Quality(532)` matches the quality `q=0.532`. +/// The quality value is defined as a number between 0 and 1 with three decimal +/// places. This means there are 1001 possible values. Since floating point +/// numbers are not exact and the smallest floating point data type (`f32`) +/// consumes four bytes, hyper uses an `u16` value to store the +/// quality internally. For performance reasons you may set quality directly to +/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality +/// `q=0.532`. /// /// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) /// gives more information on quality values in HTTP header fields. @@ -61,7 +63,11 @@ impl fmt::Display for QualityItem { match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')) + x => write!( + f, + "; q=0.{}", + format!("{:03}", x).trim_right_matches('0') + ), } } } @@ -96,7 +102,7 @@ impl str::FromStr for QualityItem { } else { return Err(::error::ParseError::Header); } - }, + } Err(_) => return Err(::error::ParseError::Header), } } @@ -114,7 +120,10 @@ fn from_f32(f: f32) -> Quality { // this function is only used internally. A check that `f` is within range // should be done before calling this method. Just in case, this // debug_assert should catch if we were forgetful - debug_assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0"); + debug_assert!( + f >= 0f32 && f <= 1f32, + "q value must be between 0.0 and 1.0" + ); Quality((f * 1000f32) as u16) } @@ -125,7 +134,7 @@ pub fn qitem(item: T) -> QualityItem { } /// Convenience function to create a `Quality` from a float or integer. -/// +/// /// Implemented for `u16` and `f32`. Panics if value is out of range. pub fn q(val: T) -> Quality { val.into_quality() @@ -147,7 +156,10 @@ mod internal { impl IntoQuality for f32 { fn into_quality(self) -> Quality { - assert!(self >= 0f32 && self <= 1f32, "float must be between 0.0 and 1.0"); + assert!( + self >= 0f32 && self <= 1f32, + "float must be between 0.0 and 1.0" + ); super::from_f32(self) } } @@ -159,7 +171,6 @@ mod internal { } } - pub trait Sealed {} impl Sealed for u16 {} impl Sealed for f32 {} @@ -167,8 +178,8 @@ mod internal { #[cfg(test)] mod tests { - use super::*; use super::super::encoding::*; + use super::*; #[test] fn test_quality_item_fmt_q_1() { @@ -183,7 +194,7 @@ mod tests { #[test] fn test_quality_item_fmt_q_05() { // Custom value - let x = QualityItem{ + let x = QualityItem { item: EncodingExt("identity".to_owned()), quality: Quality(500), }; @@ -193,7 +204,7 @@ mod tests { #[test] fn test_quality_item_fmt_q_0() { // Custom value - let x = QualityItem{ + let x = QualityItem { item: EncodingExt("identity".to_owned()), quality: Quality(0), }; @@ -203,22 +214,46 @@ mod tests { #[test] fn test_quality_item_from_str1() { let x: Result, _> = "chunked".parse(); - assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: Quality(1000), }); + assert_eq!( + x.unwrap(), + QualityItem { + item: Chunked, + quality: Quality(1000), + } + ); } #[test] fn test_quality_item_from_str2() { let x: Result, _> = "chunked; q=1".parse(); - assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: Quality(1000), }); + assert_eq!( + x.unwrap(), + QualityItem { + item: Chunked, + quality: Quality(1000), + } + ); } #[test] fn test_quality_item_from_str3() { let x: Result, _> = "gzip; q=0.5".parse(); - assert_eq!(x.unwrap(), QualityItem{ item: Gzip, quality: Quality(500), }); + assert_eq!( + x.unwrap(), + QualityItem { + item: Gzip, + quality: Quality(500), + } + ); } #[test] fn test_quality_item_from_str4() { let x: Result, _> = "gzip; q=0.273".parse(); - assert_eq!(x.unwrap(), QualityItem{ item: Gzip, quality: Quality(273), }); + assert_eq!( + x.unwrap(), + QualityItem { + item: Gzip, + quality: Quality(273), + } + ); } #[test] fn test_quality_item_from_str5() { @@ -245,14 +280,14 @@ mod tests { #[test] #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch="x86", target_env="msvc"), ignore)] + #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] fn test_quality_invalid() { q(-1.0); } #[test] #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch="x86", target_env="msvc"), ignore)] + #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] fn test_quality_invalid2() { q(2.0); } @@ -260,6 +295,10 @@ mod tests { #[test] fn test_fuzzing_bugs() { assert!("99999;".parse::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) + assert!( + "\x0d;;;=\u{d6aa}==" + .parse::>() + .is_err() + ) } } diff --git a/src/helpers.rs b/src/helpers.rs index 446e717a..fda28f38 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,7 +1,7 @@ //! Various helpers -use regex::Regex; use http::{header, StatusCode}; +use regex::Regex; use handler::Handler; use httprequest::HttpRequest; @@ -24,9 +24,11 @@ use httpresponse::HttpResponse; /// 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. +/// 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*. +/// This handler designed to be use as a handler for application's *default +/// resource*. /// /// ```rust /// # extern crate actix_web; @@ -55,7 +57,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` + /// *merge* is set to *true* and *redirect* is set to + /// `StatusCode::MOVED_PERMANENTLY` fn default() -> NormalizePath { NormalizePath { append: true, @@ -91,7 +94,11 @@ impl Handler for NormalizePath { 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 }; + let p = if !query.is_empty() { + p + "?" + query + } else { + p + }; return HttpResponse::build(self.redirect) .header(header::LOCATION, p.as_ref()) .finish(); @@ -100,10 +107,14 @@ 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 }; + let p = if !query.is_empty() { + p + "?" + query + } else { + p + }; return HttpResponse::build(self.redirect) .header(header::LOCATION, p.as_str()) - .finish() + .finish(); } } @@ -113,11 +124,13 @@ impl Handler for NormalizePath { if router.has_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { - req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str()) + req.header( + header::LOCATION, + (p.to_owned() + "?" + query).as_str(), + ) } else { req.header(header::LOCATION, p) - } - .finish(); + }.finish(); } } } else if p.ends_with('/') { @@ -126,12 +139,13 @@ impl Handler for NormalizePath { if router.has_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { - req.header(header::LOCATION, - (p.to_owned() + "?" + query).as_str()) + req.header( + header::LOCATION, + (p.to_owned() + "?" + query).as_str(), + ) } else { req.header(header::LOCATION, p) - } - .finish(); + }.finish(); } } } @@ -139,7 +153,11 @@ 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 }; + let p = if !query.is_empty() { + p + "?" + query + } else { + p + }; return HttpResponse::build(self.redirect) .header(header::LOCATION, p.as_str()) .finish(); @@ -153,9 +171,9 @@ impl Handler for NormalizePath { #[cfg(test)] mod tests { use super::*; + use application::App; use http::{header, Method}; use test::TestRequest; - use application::App; fn index(_req: HttpRequest) -> HttpResponse { HttpResponse::new(StatusCode::OK) @@ -170,17 +188,32 @@ mod tests { .finish(); // trailing slashes - let params = - vec![("/resource1", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), - ("/resource2/", "", StatusCode::OK), - ("/resource1?p1=1&p2=2", "", StatusCode::OK), - ("/resource1/?p1=1&p2=2", "/resource1?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), - ("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY), - ("/resource2/?p1=1&p2=2", "", StatusCode::OK) - ]; + let params = vec![ + ("/resource1", "", StatusCode::OK), + ( + "/resource1/", + "/resource1", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/resource2", + "/resource2/", + StatusCode::MOVED_PERMANENTLY, + ), + ("/resource2/", "", StatusCode::OK), + ("/resource1?p1=1&p2=2", "", StatusCode::OK), + ( + "/resource1/?p1=1&p2=2", + "/resource1?p1=1&p2=2", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/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(TestRequest::with_uri(path).finish()); let resp = app.run(req); @@ -189,7 +222,12 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + r.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap() + ); } } } @@ -199,19 +237,25 @@ mod tests { let mut app = App::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))) + .default_resource(|r| { + r.h(NormalizePath::new( + false, + true, + StatusCode::MOVED_PERMANENTLY, + )) + }) .finish(); // trailing slashes - let params = vec![("/resource1", StatusCode::OK), - ("/resource1/", StatusCode::MOVED_PERMANENTLY), - ("/resource2", StatusCode::NOT_FOUND), - ("/resource2/", StatusCode::OK), - ("/resource1?p1=1&p2=2", StatusCode::OK), - ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), - ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), - ("/resource2/?p1=1&p2=2", StatusCode::OK) + let params = vec![ + ("/resource1", StatusCode::OK), + ("/resource1/", StatusCode::MOVED_PERMANENTLY), + ("/resource2", StatusCode::NOT_FOUND), + ("/resource2/", StatusCode::OK), + ("/resource1?p1=1&p2=2", StatusCode::OK), + ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), + ("/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(TestRequest::with_uri(path).finish()); @@ -232,21 +276,77 @@ mod tests { // trailing slashes let params = vec![ ("/resource1/a/b", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ( + "/resource1/", + "/resource1", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/resource1//", + "/resource1", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "//resource1//a//b", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "//resource1//a//b/", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "//resource1//a//b//", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "///resource1//a//b", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a///b", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a//b/", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), ("/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", "/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", "/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", "/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", + "/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", + "/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", + "/resource1/a/b?p=1", + StatusCode::MOVED_PERMANENTLY, + ), ]; for (path, target, code) in params { let req = app.prepare_request(TestRequest::with_uri(path).finish()); @@ -256,7 +356,12 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + r.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap() + ); } } } @@ -274,38 +379,158 @@ mod tests { // trailing slashes let params = vec![ ("/resource1/a/b", "", StatusCode::OK), - ("/resource1/a/b/", "/resource1/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", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ( + "/resource1/a/b/", + "/resource1/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", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "///resource1//a//b/", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a///b", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a///b/", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/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), + ( + "//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", "/resource1/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), - ("///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", "/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", "/resource1/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), - ("/////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, + ), + ( + "//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", + "/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", + "/resource1/a/b?p=1", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a///b//?p=1", + "/resource1/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, + ), + ( + "/////resource2/a///b/?p=1", + "/resource2/a/b/?p=1", + StatusCode::MOVED_PERMANENTLY, + ), ]; for (path, target, code) in params { let req = app.prepare_request(TestRequest::with_uri(path).finish()); @@ -314,7 +539,13 @@ mod tests { assert_eq!(r.status(), code); if !target.is_empty() { assert_eq!( - target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + target, + r.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap() + ); } } } diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 5d3eb7ff..058d1d2f 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -4,127 +4,155 @@ use http::StatusCode; use body::Body; use error::Error; -use handler::{Reply, Handler, RouteHandler, Responder}; +use handler::{Handler, Reply, Responder, RouteHandler}; use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseBuilder}; -#[deprecated(since="0.5.0", note="please use `HttpResponse::Ok()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Ok()` instead")] pub const HttpOk: StaticResponse = StaticResponse(StatusCode::OK); -#[deprecated(since="0.5.0", note="please use `HttpResponse::Created()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Created()` instead")] pub const HttpCreated: StaticResponse = StaticResponse(StatusCode::CREATED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::Accepted()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Accepted()` instead")] pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED); -#[deprecated(since="0.5.0", - note="please use `HttpResponse::pNonAuthoritativeInformation()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::pNonAuthoritativeInformation()` instead")] pub const HttpNonAuthoritativeInformation: StaticResponse = StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION); -#[deprecated(since="0.5.0", note="please use `HttpResponse::NoContent()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NoContent()` instead")] pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); -#[deprecated(since="0.5.0", note="please use `HttpResponse::ResetContent()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::ResetContent()` instead")] pub const HttpResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT); -#[deprecated(since="0.5.0", note="please use `HttpResponse::PartialContent()` instead")] -pub const HttpPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT); -#[deprecated(since="0.5.0", note="please use `HttpResponse::MultiStatus()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::PartialContent()` instead")] +pub const HttpPartialContent: StaticResponse = + StaticResponse(StatusCode::PARTIAL_CONTENT); +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::MultiStatus()` instead")] pub const HttpMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS); -#[deprecated(since="0.5.0", note="please use `HttpResponse::AlreadyReported()` instead")] -pub const HttpAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED); +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::AlreadyReported()` instead")] +pub const HttpAlreadyReported: StaticResponse = + StaticResponse(StatusCode::ALREADY_REPORTED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::MultipleChoices()` instead")] -pub const HttpMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES); -#[deprecated(since="0.5.0", note="please use `HttpResponse::MovedPermanently()` instead")] -pub const HttpMovedPermanently: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY); -#[deprecated(since="0.5.0", note="please use `HttpResponse::Found()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::MultipleChoices()` instead")] +pub const HttpMultipleChoices: StaticResponse = + StaticResponse(StatusCode::MULTIPLE_CHOICES); +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::MovedPermanently()` instead")] +pub const HttpMovedPermanently: StaticResponse = + StaticResponse(StatusCode::MOVED_PERMANENTLY); +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Found()` instead")] pub const HttpFound: StaticResponse = StaticResponse(StatusCode::FOUND); -#[deprecated(since="0.5.0", note="please use `HttpResponse::SeeOther()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::SeeOther()` instead")] pub const HttpSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER); -#[deprecated(since="0.5.0", note="please use `HttpResponse::NotModified()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotModified()` instead")] pub const HttpNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::UseProxy()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::UseProxy()` instead")] pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY); -#[deprecated(since="0.5.0", note="please use `HttpResponse::TemporaryRedirect()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::TemporaryRedirect()` instead")] pub const HttpTemporaryRedirect: StaticResponse = StaticResponse(StatusCode::TEMPORARY_REDIRECT); -#[deprecated(since="0.5.0", note="please use `HttpResponse::PermanentRedirect()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::PermanentRedirect()` instead")] pub const HttpPermanentRedirect: StaticResponse = StaticResponse(StatusCode::PERMANENT_REDIRECT); -#[deprecated(since="0.5.0", note="please use `HttpResponse::BadRequest()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadRequest()` instead")] pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); -#[deprecated(since="0.5.0", note="please use `HttpResponse::Unauthorized()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::Unauthorized()` instead")] pub const HttpUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::PaymentRequired()` instead")] -pub const HttpPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::Forbidden()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::PaymentRequired()` instead")] +pub const HttpPaymentRequired: StaticResponse = + StaticResponse(StatusCode::PAYMENT_REQUIRED); +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Forbidden()` instead")] pub const HttpForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN); -#[deprecated(since="0.5.0", note="please use `HttpResponse::NotFound()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotFound()` instead")] pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND); -#[deprecated(since="0.5.0", note="please use `HttpResponse::MethodNotAllowed()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::MethodNotAllowed()` instead")] pub const HttpMethodNotAllowed: StaticResponse = StaticResponse(StatusCode::METHOD_NOT_ALLOWED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::NotAcceptable()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::NotAcceptable()` instead")] pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); -#[deprecated(since="0.5.0", - note="please use `HttpResponse::ProxyAuthenticationRequired()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::ProxyAuthenticationRequired()` instead")] pub const HttpProxyAuthenticationRequired: StaticResponse = StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::RequestTimeout()` instead")] -pub const HttpRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT); -#[deprecated(since="0.5.0", note="please use `HttpResponse::Conflict()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::RequestTimeout()` instead")] +pub const HttpRequestTimeout: StaticResponse = + StaticResponse(StatusCode::REQUEST_TIMEOUT); +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Conflict()` instead")] pub const HttpConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT); -#[deprecated(since="0.5.0", note="please use `HttpResponse::Gone()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Gone()` instead")] pub const HttpGone: StaticResponse = StaticResponse(StatusCode::GONE); -#[deprecated(since="0.5.0", note="please use `HttpResponse::LengthRequired()` instead")] -pub const HttpLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::PreconditionFailed()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::LengthRequired()` instead")] +pub const HttpLengthRequired: StaticResponse = + StaticResponse(StatusCode::LENGTH_REQUIRED); +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::PreconditionFailed()` instead")] pub const HttpPreconditionFailed: StaticResponse = StaticResponse(StatusCode::PRECONDITION_FAILED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::PayloadTooLarge()` instead")] -pub const HttpPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); -#[deprecated(since="0.5.0", note="please use `HttpResponse::UriTooLong()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::PayloadTooLarge()` instead")] +pub const HttpPayloadTooLarge: StaticResponse = + StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::UriTooLong()` instead")] pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG); -#[deprecated(since="0.5.0", - note="please use `HttpResponse::UnsupportedMediaType()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::UnsupportedMediaType()` instead")] pub const HttpUnsupportedMediaType: StaticResponse = StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE); -#[deprecated(since="0.5.0", - note="please use `HttpResponse::RangeNotSatisfiable()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::RangeNotSatisfiable()` instead")] pub const HttpRangeNotSatisfiable: StaticResponse = StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE); -#[deprecated(since="0.5.0", note="please use `HttpResponse::ExpectationFailed()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::ExpectationFailed()` instead")] pub const HttpExpectationFailed: StaticResponse = StaticResponse(StatusCode::EXPECTATION_FAILED); -#[deprecated(since="0.5.0", - note="please use `HttpResponse::InternalServerError()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::InternalServerError()` instead")] pub const HttpInternalServerError: StaticResponse = StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); -#[deprecated(since="0.5.0", note="please use `HttpResponse::NotImplemented()` instead")] -pub const HttpNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED); -#[deprecated(since="0.5.0", note="please use `HttpResponse::BadGateway()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::NotImplemented()` instead")] +pub const HttpNotImplemented: StaticResponse = + StaticResponse(StatusCode::NOT_IMPLEMENTED); +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadGateway()` instead")] pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY); -#[deprecated(since="0.5.0", note="please use `HttpResponse::ServiceUnavailable()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::ServiceUnavailable()` instead")] pub const HttpServiceUnavailable: StaticResponse = StaticResponse(StatusCode::SERVICE_UNAVAILABLE); -#[deprecated(since="0.5.0", note="please use `HttpResponse::GatewayTimeout()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::GatewayTimeout()` instead")] pub const HttpGatewayTimeout: StaticResponse = StaticResponse(StatusCode::GATEWAY_TIMEOUT); -#[deprecated(since="0.5.0", - note="please use `HttpResponse::VersionNotSupported()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::VersionNotSupported()` instead")] pub const HttpVersionNotSupported: StaticResponse = StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED); -#[deprecated(since="0.5.0", - note="please use `HttpResponse::VariantAlsoNegotiates()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::VariantAlsoNegotiates()` instead")] pub const HttpVariantAlsoNegotiates: StaticResponse = StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES); -#[deprecated(since="0.5.0", - note="please use `HttpResponse::InsufficientStorage()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::InsufficientStorage()` instead")] pub const HttpInsufficientStorage: StaticResponse = StaticResponse(StatusCode::INSUFFICIENT_STORAGE); -#[deprecated(since="0.5.0", note="please use `HttpResponse::LoopDetected()` instead")] +#[deprecated(since = "0.5.0", + note = "please use `HttpResponse::LoopDetected()` instead")] pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED); - -#[deprecated(since="0.5.0", note="please use `HttpResponse` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse` instead")] #[derive(Copy, Clone, Debug)] pub struct StaticResponse(StatusCode); @@ -186,14 +214,17 @@ macro_rules! STATIC_RESP { pub fn $name() -> HttpResponseBuilder { HttpResponse::build($status) } - } + }; } impl HttpResponse { STATIC_RESP!(Ok, StatusCode::OK); STATIC_RESP!(Created, StatusCode::CREATED); STATIC_RESP!(Accepted, StatusCode::ACCEPTED); - STATIC_RESP!(NonAuthoritativeInformation, StatusCode::NON_AUTHORITATIVE_INFORMATION); + STATIC_RESP!( + NonAuthoritativeInformation, + StatusCode::NON_AUTHORITATIVE_INFORMATION + ); STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); @@ -218,7 +249,10 @@ impl HttpResponse { STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - STATIC_RESP!(ProxyAuthenticationRequired, StatusCode::PROXY_AUTHENTICATION_REQUIRED); + STATIC_RESP!( + ProxyAuthenticationRequired, + StatusCode::PROXY_AUTHENTICATION_REQUIRED + ); STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); STATIC_RESP!(Conflict, StatusCode::CONFLICT); STATIC_RESP!(Gone, StatusCode::GONE); @@ -226,7 +260,10 @@ impl HttpResponse { STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); - STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); + STATIC_RESP!( + UnsupportedMediaType, + StatusCode::UNSUPPORTED_MEDIA_TYPE + ); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); @@ -235,16 +272,22 @@ impl HttpResponse { STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); - STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); + STATIC_RESP!( + VersionNotSupported, + StatusCode::HTTP_VERSION_NOT_SUPPORTED + ); + STATIC_RESP!( + VariantAlsoNegotiates, + StatusCode::VARIANT_ALSO_NEGOTIATES + ); STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); } #[cfg(test)] mod tests { + use super::{Body, HttpBadRequest, HttpOk, HttpResponse}; use http::StatusCode; - use super::{HttpOk, HttpBadRequest, Body, HttpResponse}; #[test] fn test_build() { diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 11d1d087..0b40a812 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,45 +1,45 @@ -use std::str; use bytes::{Bytes, BytesMut}; -use futures::{Future, Stream, Poll}; -use http_range::HttpRange; -use serde::de::DeserializeOwned; -use mime::Mime; -use serde_urlencoded; -use encoding::all::UTF_8; use encoding::EncodingRef; -use encoding::types::{Encoding, DecoderTrap}; +use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; +use encoding::types::{DecoderTrap, Encoding}; +use futures::{Future, Poll, Stream}; use http::{header, HeaderMap}; +use http_range::HttpRange; +use mime::Mime; +use serde::de::DeserializeOwned; +use serde_urlencoded; +use std::str; -use json::JsonBody; +use error::{ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError}; use header::Header; +use json::JsonBody; use multipart::Multipart; -use error::{ParseError, ContentTypeError, - HttpRangeError, PayloadError, UrlencodedError}; - /// Trait that implements general purpose operations on http messages pub trait HttpMessage { - /// Read the message headers. fn headers(&self) -> &HeaderMap; #[doc(hidden)] /// Get a header - fn get_header(&self) -> Option where Self: Sized { + fn get_header(&self) -> Option + where + Self: Sized, + { if self.headers().contains_key(H::name()) { H::parse(self).ok() } else { None } } - + /// Read the request content type. If request does not contain /// *Content-Type* header, empty str get returned. fn content_type(&self) -> &str { if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { - return content_type.split(';').next().unwrap().trim() + return content_type.split(';').next().unwrap().trim(); } } "" @@ -73,7 +73,7 @@ pub trait HttpMessage { Err(_) => Err(ContentTypeError::ParseError), }; } else { - return Err(ContentTypeError::ParseError) + return Err(ContentTypeError::ParseError); } } Ok(None) @@ -96,8 +96,10 @@ pub trait HttpMessage { /// `size` is full size of response (file). fn range(&self, size: u64) -> Result, HttpRangeError> { if let Some(range) = self.headers().get(header::RANGE) { - HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size) - .map_err(|e| e.into()) + HttpRange::parse( + unsafe { str::from_utf8_unchecked(range.as_bytes()) }, + size, + ).map_err(|e| e.into()) } else { Ok(Vec::new()) } @@ -105,8 +107,9 @@ pub trait HttpMessage { /// Load http message body. /// - /// By default only 256Kb payload reads to a memory, then `PayloadError::Overflow` - /// get returned. Use `MessageBody::limit()` method to change upper limit. + /// By default only 256Kb payload reads to a memory, then + /// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` + /// method to change upper limit. /// /// ## Server example /// @@ -131,14 +134,15 @@ pub trait HttpMessage { /// # fn main() {} /// ``` fn body(self) -> MessageBody - where Self: Stream + Sized + where + Self: Stream + Sized, { MessageBody::new(self) } /// Parse `application/x-www-form-urlencoded` encoded request's body. - /// Return `UrlEncoded` future. Form can be deserialized to any type that implements - /// `Deserialize` trait from *serde*. + /// Return `UrlEncoded` future. Form can be deserialized to any type that + /// implements `Deserialize` trait from *serde*. /// /// Returns error: /// @@ -167,7 +171,8 @@ pub trait HttpMessage { /// # fn main() {} /// ``` fn urlencoded(self) -> UrlEncoded - where Self: Stream + Sized + where + Self: Stream + Sized, { UrlEncoded::new(self) } @@ -205,7 +210,8 @@ pub trait HttpMessage { /// # fn main() {} /// ``` fn json(self) -> JsonBody - where Self: Stream + Sized + where + Self: Stream + Sized, { JsonBody::new(self) } @@ -247,7 +253,8 @@ pub trait HttpMessage { /// # fn main() {} /// ``` fn multipart(self) -> Multipart - where Self: Stream + Sized + where + Self: Stream + Sized, { let boundary = Multipart::boundary(self.headers()); Multipart::new(boundary, self) @@ -258,11 +265,10 @@ pub trait HttpMessage { pub struct MessageBody { limit: usize, req: Option, - fut: Option>>, + fut: Option>>, } impl MessageBody { - /// Create `RequestBody` for request. pub fn new(req: T) -> MessageBody { MessageBody { @@ -280,7 +286,8 @@ impl MessageBody { } impl Future for MessageBody - where T: HttpMessage + Stream + 'static +where + T: HttpMessage + Stream + 'static, { type Item = Bytes; type Error = PayloadError; @@ -313,11 +320,14 @@ impl Future for MessageBody Ok(body) } }) - .map(|body| body.freeze()) + .map(|body| body.freeze()), )); } - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() + self.fut + .as_mut() + .expect("UrlEncoded could not be used second time") + .poll() } } @@ -325,7 +335,7 @@ impl Future for MessageBody pub struct UrlEncoded { req: Option, limit: usize, - fut: Option>>, + fut: Option>>, } impl UrlEncoded { @@ -345,8 +355,9 @@ impl UrlEncoded { } impl Future for UrlEncoded - where T: HttpMessage + Stream + 'static, - U: DeserializeOwned + 'static +where + T: HttpMessage + Stream + 'static, + U: DeserializeOwned + 'static, { type Item = U; type Error = UrlencodedError; @@ -354,7 +365,7 @@ impl Future for UrlEncoded fn poll(&mut self) -> Poll { if let Some(req) = self.req.take() { if req.chunked().unwrap_or(false) { - return Err(UrlencodedError::Chunked) + return Err(UrlencodedError::Chunked); } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { @@ -362,18 +373,19 @@ impl Future for UrlEncoded return Err(UrlencodedError::Overflow); } } else { - return Err(UrlencodedError::UnknownLength) + return Err(UrlencodedError::UnknownLength); } } else { - return Err(UrlencodedError::UnknownLength) + return Err(UrlencodedError::UnknownLength); } } // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { - return Err(UrlencodedError::ContentType) + return Err(UrlencodedError::ContentType); } - let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; + let encoding = req.encoding() + .map_err(|_| UrlencodedError::ContentType)?; // future let limit = self.limit; @@ -392,7 +404,8 @@ impl Future for UrlEncoded serde_urlencoded::from_bytes::(&body) .map_err(|_| UrlencodedError::Parse) } else { - let body = encoding.decode(&body, DecoderTrap::Strict) + let body = encoding + .decode(&body, DecoderTrap::Strict) .map_err(|_| UrlencodedError::Parse)?; serde_urlencoded::from_str::(&body) .map_err(|_| UrlencodedError::Parse) @@ -401,19 +414,22 @@ impl Future for UrlEncoded self.fut = Some(Box::new(fut)); } - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() + self.fut + .as_mut() + .expect("UrlEncoded could not be used second time") + .poll() } } #[cfg(test)] mod tests { use super::*; - use mime; use encoding::Encoding; use encoding::all::ISO_8859_2; use futures::Async; - use http::{Method, Version, Uri}; + use http::{Method, Uri, Version}; use httprequest::HttpRequest; + use mime; use std::str::FromStr; use test::TestRequest; @@ -421,8 +437,9 @@ mod tests { fn test_content_type() { let req = TestRequest::with_header("content-type", "text/plain").finish(); assert_eq!(req.content_type(), "text/plain"); - let req = TestRequest::with_header( - "content-type", "application/json; charset=utf=8").finish(); + let req = + TestRequest::with_header("content-type", "application/json; charset=utf=8") + .finish(); assert_eq!(req.content_type(), "application/json"); let req = HttpRequest::default(); assert_eq!(req.content_type(), ""); @@ -434,8 +451,9 @@ mod tests { assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); let req = HttpRequest::default(); assert_eq!(req.mime_type().unwrap(), None); - let req = TestRequest::with_header( - "content-type", "application/json; charset=utf-8").finish(); + let req = + TestRequest::with_header("content-type", "application/json; charset=utf-8") + .finish(); let mt = req.mime_type().unwrap().unwrap(); assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); assert_eq!(mt.type_(), mime::APPLICATION); @@ -445,7 +463,9 @@ mod tests { #[test] fn test_mime_type_error() { let req = TestRequest::with_header( - "content-type", "applicationadfadsfasdflknadsfklnadsfjson").finish(); + "content-type", + "applicationadfadsfasdflknadsfklnadsfjson", + ).finish(); assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); } @@ -454,24 +474,32 @@ mod tests { let req = HttpRequest::default(); assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - let req = TestRequest::with_header( - "content-type", "application/json").finish(); + let req = TestRequest::with_header("content-type", "application/json").finish(); assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); let req = TestRequest::with_header( - "content-type", "application/json; charset=ISO-8859-2").finish(); + "content-type", + "application/json; charset=ISO-8859-2", + ).finish(); assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); } #[test] fn test_encoding_error() { - let req = TestRequest::with_header( - "content-type", "applicatjson").finish(); - assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); + let req = TestRequest::with_header("content-type", "applicatjson").finish(); + assert_eq!( + Some(ContentTypeError::ParseError), + req.encoding().err() + ); let req = TestRequest::with_header( - "content-type", "application/json; charset=kkkttktk").finish(); - assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err()); + "content-type", + "application/json; charset=kkkttktk", + ).finish(); + assert_eq!( + Some(ContentTypeError::UnknownEncoding), + req.encoding().err() + ); } #[test] @@ -495,17 +523,26 @@ mod tests { let req = HttpRequest::default(); assert!(!req.chunked().unwrap()); - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); + let req = + TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); assert!(req.chunked().unwrap()); let mut headers = HeaderMap::new(); - let s = unsafe{str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref())}; + 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()); + 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); + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); assert!(req.chunked().is_err()); } @@ -540,51 +577,75 @@ mod tests { #[test] fn test_urlencoded_error() { - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); - assert_eq!(req.urlencoded::() - .poll().err().unwrap(), UrlencodedError::Chunked); + let req = + TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); + assert_eq!( + req.urlencoded::().poll().err().unwrap(), + UrlencodedError::Chunked + ); let req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(header::CONTENT_LENGTH, "xxxx") + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "xxxx") .finish(); - assert_eq!(req.urlencoded::() - .poll().err().unwrap(), UrlencodedError::UnknownLength); + assert_eq!( + req.urlencoded::().poll().err().unwrap(), + UrlencodedError::UnknownLength + ); let req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(header::CONTENT_LENGTH, "1000000") + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "1000000") .finish(); - assert_eq!(req.urlencoded::() - .poll().err().unwrap(), UrlencodedError::Overflow); + assert_eq!( + req.urlencoded::().poll().err().unwrap(), + UrlencodedError::Overflow + ); - let req = TestRequest::with_header( - header::CONTENT_TYPE, "text/plain") + 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); + assert_eq!( + req.urlencoded::().poll().err().unwrap(), + UrlencodedError::ContentType + ); } #[test] fn test_urlencoded() { let mut req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(header::CONTENT_LENGTH, "11") + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); let result = req.urlencoded::().poll().ok().unwrap(); - assert_eq!(result, Async::Ready(Info{hello: "world".to_owned()})); + assert_eq!( + result, + Async::Ready(Info { + hello: "world".to_owned() + }) + ); let mut req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8") - .header(header::CONTENT_LENGTH, "11") + header::CONTENT_TYPE, + "application/x-www-form-urlencoded; charset=utf-8", + ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); let result = req.urlencoded().poll().ok().unwrap(); - assert_eq!(result, Async::Ready(Info{hello: "world".to_owned()})); + assert_eq!( + result, + Async::Ready(Info { + hello: "world".to_owned() + }) + ); } #[test] @@ -602,14 +663,16 @@ mod tests { } let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"test")); + 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")), _ => unreachable!("error"), } let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); + req.payload_mut() + .unread_data(Bytes::from_static(b"11111111111111")); match req.body().limit(5).poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), diff --git a/src/httprequest.rs b/src/httprequest.rs index 90345d05..88ff2549 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,30 +1,29 @@ //! HTTP Request message related code. -use std::{io, cmp, str, fmt, mem}; -use std::rc::Rc; -use std::net::SocketAddr; -use std::borrow::Cow; use bytes::Bytes; use cookie::Cookie; -use futures::{Async, Stream, Poll}; -use futures::future::{FutureResult, result}; -use futures_cpupool::CpuPool; use failure; -use url::{Url, form_urlencoded}; -use http::{header, Uri, Method, Version, HeaderMap, Extensions, StatusCode}; -use tokio_io::AsyncRead; +use futures::future::{result, FutureResult}; +use futures::{Async, Poll, Stream}; +use futures_cpupool::CpuPool; +use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version}; use percent_encoding::percent_decode; +use std::borrow::Cow; +use std::net::SocketAddr; +use std::rc::Rc; +use std::{cmp, fmt, io, mem, str}; +use tokio_io::AsyncRead; +use url::{form_urlencoded, Url}; use body::Body; -use info::ConnectionInfo; -use param::Params; -use router::{Router, Resource}; -use payload::Payload; +use error::{CookieParseError, Error, PayloadError, UrlGenerationError}; use handler::FromRequest; use httpmessage::HttpMessage; use httpresponse::{HttpResponse, HttpResponseBuilder}; +use info::ConnectionInfo; +use param::Params; +use payload::Payload; +use router::{Resource, Router}; use server::helpers::SharedHttpInnerMessage; -use error::{Error, UrlGenerationError, CookieParseError, PayloadError}; - pub struct HttpInnerMessage { pub version: Version, @@ -42,14 +41,13 @@ pub struct HttpInnerMessage { resource: RouterResource, } -#[derive(Debug, Copy, Clone,PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] enum RouterResource { Notset, Normal(u16), } impl Default for HttpInnerMessage { - fn default() -> HttpInnerMessage { HttpInnerMessage { method: Method::GET, @@ -70,7 +68,6 @@ impl Default for HttpInnerMessage { } impl HttpInnerMessage { - /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { @@ -79,8 +76,8 @@ impl HttpInnerMessage { if self.version == Version::HTTP_10 && conn.contains("keep-alive") { true } else { - self.version == Version::HTTP_11 && - !(conn.contains("close") || conn.contains("upgrade")) + self.version == Version::HTTP_11 + && !(conn.contains("close") || conn.contains("upgrade")) } } else { false @@ -105,21 +102,20 @@ impl HttpInnerMessage { } } -lazy_static!{ +lazy_static! { static ref RESOURCE: Resource = Resource::unset(); } - /// An HTTP Request -pub struct HttpRequest(SharedHttpInnerMessage, Option>, Option); +pub struct HttpRequest(SharedHttpInnerMessage, Option>, Option); impl HttpRequest<()> { /// Construct a new Request. #[inline] - pub fn new(method: Method, uri: Uri, - version: Version, headers: HeaderMap, payload: Option) - -> HttpRequest - { + pub fn new( + method: Method, uri: Uri, version: Version, headers: HeaderMap, + payload: Option, + ) -> HttpRequest { HttpRequest( SharedHttpInnerMessage::from_message(HttpInnerMessage { method, @@ -142,7 +138,7 @@ impl HttpRequest<()> { } #[inline(always)] - #[cfg_attr(feature="cargo-clippy", allow(inline_always))] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest { HttpRequest(msg, None, None) } @@ -154,7 +150,6 @@ impl HttpRequest<()> { } } - impl HttpMessage for HttpRequest { #[inline] fn headers(&self) -> &HeaderMap { @@ -163,7 +158,6 @@ impl HttpMessage for HttpRequest { } impl HttpRequest { - #[inline] /// Construct new http request with state. pub fn change_state(&self, state: Rc) -> HttpRequest { @@ -211,8 +205,10 @@ impl HttpRequest { #[inline] #[doc(hidden)] pub fn cpu_pool(&self) -> &CpuPool { - self.router().expect("HttpRequest has to have Router instance") - .server_settings().cpu_pool() + self.router() + .expect("HttpRequest has to have Router instance") + .server_settings() + .cpu_pool() } /// Create http response @@ -235,12 +231,18 @@ 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. #[inline] - pub fn uri(&self) -> &Uri { &self.as_ref().uri } + pub fn uri(&self) -> &Uri { + &self.as_ref().uri + } /// Returns mutable the Request Uri. /// @@ -252,7 +254,9 @@ impl HttpRequest { /// Read the Request method. #[inline] - pub fn method(&self) -> &Method { &self.as_ref().method } + pub fn method(&self) -> &Method { + &self.as_ref().method + } /// Read the Request Version. #[inline] @@ -277,14 +281,16 @@ impl HttpRequest { /// Percent decoded path of this Request. #[inline] pub fn path_decoded(&self) -> Cow { - percent_decode(self.uri().path().as_bytes()).decode_utf8().unwrap() + percent_decode(self.uri().path().as_bytes()) + .decode_utf8() + .unwrap() } /// Get *ConnectionInfo* for correct request. pub fn connection_info(&self) -> &ConnectionInfo { if self.as_ref().info.is_none() { - let info: ConnectionInfo<'static> = unsafe{ - mem::transmute(ConnectionInfo::new(self))}; + let info: ConnectionInfo<'static> = + unsafe { mem::transmute(ConnectionInfo::new(self)) }; self.as_mut().info = Some(info); } self.as_ref().info.as_ref().unwrap() @@ -310,9 +316,12 @@ impl HttpRequest { /// .finish(); /// } /// ``` - pub fn url_for(&self, name: &str, elements: U) -> Result - where U: IntoIterator, - I: AsRef, + pub fn url_for( + &self, name: &str, elements: U + ) -> Result + where + U: IntoIterator, + I: AsRef, { if self.router().is_none() { Err(UrlGenerationError::RouterNotAvailable) @@ -320,7 +329,12 @@ impl HttpRequest { let path = self.router().unwrap().resource_path(name, elements)?; if path.starts_with('/') { let conn = self.connection_info(); - Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?) + Ok(Url::parse(&format!( + "{}://{}{}", + conn.scheme(), + conn.host(), + path + ))?) } else { Ok(Url::parse(&path)?) } @@ -338,7 +352,7 @@ impl HttpRequest { pub fn resource(&self) -> &Resource { if let Some(ref router) = self.2 { if let RouterResource::Normal(idx) = self.as_ref().resource { - return router.get_resource(idx as usize) + return router.get_resource(idx as usize); } } &*RESOURCE @@ -353,7 +367,8 @@ impl HttpRequest { /// Peer address is actual socket address, if proxy is used in front of /// actix http server, then peer address would be address of this proxy. /// - /// To get client connection information `connection_info()` method should be used. + /// 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() @@ -368,13 +383,14 @@ impl HttpRequest { /// 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) }; + 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); } } - unsafe{ mem::transmute(&self.as_ref().query) } + unsafe { mem::transmute(&self.as_ref().query) } } /// The query string in the URL. @@ -412,7 +428,7 @@ impl HttpRequest { if let Ok(cookies) = self.cookies() { for cookie in cookies { if cookie.name() == name { - return Some(cookie) + return Some(cookie); } } } @@ -423,16 +439,17 @@ impl HttpRequest { /// /// Params is a container for url parameters. /// Route supports glob patterns: * for a single wildcard segment and :param - /// for matching storing that segment of the request url in the Params object. + /// for matching storing that segment of the request url in the Params + /// object. #[inline] pub fn match_info(&self) -> &Params { - unsafe{ mem::transmute(&self.as_ref().params) } + unsafe { mem::transmute(&self.as_ref().params) } } /// Get mutable reference to request's Params. #[inline] pub fn match_info_mut(&mut self) -> &mut Params { - unsafe{ mem::transmute(&mut self.as_mut().params) } + unsafe { mem::transmute(&mut self.as_mut().params) } } /// Checks if a connection should be kept alive. @@ -444,7 +461,7 @@ impl HttpRequest { pub(crate) fn upgrade(&self) -> bool { if let Some(conn) = self.as_ref().headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { - return s.to_lowercase().contains("upgrade") + return s.to_lowercase().contains("upgrade"); } } self.as_ref().method == Method::CONNECT @@ -479,7 +496,6 @@ impl HttpRequest { } impl Default for HttpRequest<()> { - /// Construct default request fn default() -> HttpRequest { HttpRequest(SharedHttpInnerMessage::default(), None, None) @@ -492,8 +508,7 @@ impl Clone for HttpRequest { } } -impl FromRequest for HttpRequest -{ +impl FromRequest for HttpRequest { type Config = (); type Result = FutureResult; @@ -540,10 +555,13 @@ impl io::Read for HttpRequest { } } Ok(Async::Ready(None)) => Ok(0), - Ok(Async::NotReady) => - Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready")), - Err(e) => - Err(io::Error::new(io::ErrorKind::Other, failure::Error::from(e).compat())), + Ok(Async::NotReady) => { + Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready")) + } + Err(e) => Err(io::Error::new( + io::ErrorKind::Other, + failure::Error::from(e).compat(), + )), } } else { Ok(0) @@ -556,8 +574,12 @@ impl AsyncRead for HttpRequest {} impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = writeln!( - f, "\nHttpRequest {:?} {}:{}", - self.as_ref().version, self.as_ref().method, self.path_decoded()); + f, + "\nHttpRequest {:?} {}:{}", + self.as_ref().version, + self.as_ref().method, + self.path_decoded() + ); if !self.query_string().is_empty() { let _ = writeln!(f, " query: ?{:?}", self.query_string()); } @@ -575,11 +597,11 @@ impl fmt::Debug for HttpRequest { #[cfg(test)] mod tests { use super::*; - use http::{Uri, HttpTryFrom}; - use router::Resource; + use http::{HttpTryFrom, Uri}; use resource::ResourceHandler; - use test::TestRequest; + use router::Resource; use server::ServerSettings; + use test::TestRequest; #[test] fn test_debug() { @@ -652,12 +674,19 @@ mod tests { #[test] fn test_url_for() { let req2 = HttpRequest::default(); - assert_eq!(req2.url_for("unknown", &["test"]), - Err(UrlGenerationError::RouterNotAvailable)); + assert_eq!( + req2.url_for("unknown", &["test"]), + Err(UrlGenerationError::RouterNotAvailable) + ); let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = vec!((Resource::new("index", "/user/{name}.{ext}"), Some(resource))); + let routes = vec![ + ( + Resource::new("index", "/user/{name}.{ext}"), + Some(resource), + ), + ]; let (router, _) = Router::new("/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -665,12 +694,19 @@ mod tests { let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") .finish_with_router(router); - assert_eq!(req.url_for("unknown", &["test"]), - Err(UrlGenerationError::ResourceNotFound)); - assert_eq!(req.url_for("index", &["test"]), - Err(UrlGenerationError::NotEnoughElements)); + 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"); + assert_eq!( + url.ok().unwrap().as_str(), + "http://www.rust-lang.org/user/test.html" + ); } #[test] @@ -679,15 +715,22 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; + let routes = vec![ + ( + Resource::new("index", "/user/{name}.{ext}"), + Some(resource), + ), + ]; let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); 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"); + assert_eq!( + url.ok().unwrap().as_str(), + "http://www.rust-lang.org/prefix/user/test.html" + ); } #[test] @@ -697,12 +740,19 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![ - (Resource::external("youtube", "https://youtube.com/watch/{video_id}"), None)]; + ( + Resource::external("youtube", "https://youtube.com/watch/{video_id}"), + None, + ), + ]; let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); assert!(!router.has_route("https://youtube.com/watch/unknown")); 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"); + assert_eq!( + url.ok().unwrap().as_str(), + "https://youtube.com/watch/oHg5SJYRHA0" + ); } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 90243e4d..c53975e1 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -1,30 +1,29 @@ //! Http response -use std::{mem, str, fmt}; -use std::rc::Rc; -use std::io::Write; use std::cell::UnsafeCell; use std::collections::VecDeque; +use std::io::Write; +use std::rc::Rc; +use std::{fmt, mem, str}; +use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; -use bytes::{Bytes, BytesMut, BufMut}; use futures::Stream; -use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError}; use http::header::{self, HeaderName, HeaderValue}; -use serde_json; +use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; +use serde_json; use body::Body; +use client::ClientResponse; use error::Error; use handler::Responder; -use header::{Header, IntoHeaderValue, ContentEncoding}; -use httprequest::HttpRequest; +use header::{ContentEncoding, Header, IntoHeaderValue}; use httpmessage::HttpMessage; -use client::ClientResponse; +use httprequest::HttpRequest; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; - /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] pub enum ConnectionType { @@ -37,7 +36,10 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct HttpResponse(Option>, Rc>); +pub struct HttpResponse( + Option>, + Rc>, +); impl Drop for HttpResponse { fn drop(&mut self) { @@ -48,7 +50,6 @@ impl Drop for HttpResponse { } impl HttpResponse { - #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] fn get_ref(&self) -> &InnerHttpResponse { @@ -103,7 +104,7 @@ impl HttpResponse { response, pool, err: None, - cookies: None, // TODO: convert set-cookie headers + cookies: None, // TODO: convert set-cookie headers } } @@ -149,7 +150,10 @@ impl HttpResponse { if let Some(reason) = self.get_ref().reason { reason } else { - self.get_ref().status.canonical_reason().unwrap_or("") + self.get_ref() + .status + .canonical_reason() + .unwrap_or("") } } @@ -241,9 +245,13 @@ impl HttpResponse { impl fmt::Debug for HttpResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!(f, "\nHttpResponse {:?} {}{}", - self.get_ref().version, self.get_ref().status, - self.get_ref().reason.unwrap_or("")); + let res = writeln!( + f, + "\nHttpResponse {:?} {}{}", + self.get_ref().version, + self.get_ref().status, + self.get_ref().reason.unwrap_or("") + ); let _ = writeln!(f, " encoding: {:?}", self.get_ref().encoding); let _ = writeln!(f, " headers:"); for (key, val) in self.get_ref().headers.iter() { @@ -299,11 +307,12 @@ impl HttpResponseBuilder { /// fn main() {} /// ``` #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self - { + pub fn set(&mut self, hdr: H) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { match hdr.try_into() { - Ok(value) => { parts.headers.append(H::name(), value); } + Ok(value) => { + parts.headers.append(H::name(), value); + } Err(e) => self.err = Some(e.into()), } } @@ -325,16 +334,17 @@ impl HttpResponseBuilder { /// fn main() {} /// ``` pub fn header(&mut self, key: K, value: V) -> &mut Self - where HeaderName: HttpTryFrom, - V: IntoHeaderValue, + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, { if let Some(parts) = parts(&mut self.response, &self.err) { match HeaderName::try_from(key) { - Ok(key) => { - match value.try_into() { - Ok(value) => { parts.headers.append(key, value); } - Err(e) => self.err = Some(e.into()), + Ok(key) => match value.try_into() { + Ok(value) => { + parts.headers.append(key, value); } + Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), }; @@ -354,8 +364,9 @@ impl HttpResponseBuilder { /// Set content encoding. /// /// 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. + /// 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.response, &self.err) { @@ -408,11 +419,14 @@ impl HttpResponseBuilder { /// Set response content type #[inline] pub fn content_type(&mut self, value: V) -> &mut Self - where HeaderValue: HttpTryFrom + where + HeaderValue: HttpTryFrom, { if let Some(parts) = parts(&mut self.response, &self.err) { match HeaderValue::try_from(value) { - Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); }, + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } Err(e) => self.err = Some(e.into()), }; } @@ -452,12 +466,16 @@ impl HttpResponseBuilder { jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); + self.cookies + .as_mut() + .unwrap() + .add(cookie.into_owned()); } self } - /// Remove 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() { @@ -471,9 +489,11 @@ impl HttpResponseBuilder { self } - /// This method 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: FnOnce(&mut HttpResponseBuilder) + where + F: FnOnce(&mut HttpResponseBuilder), { if value { f(self); @@ -481,9 +501,11 @@ impl HttpResponseBuilder { self } - /// This method calls provided closure with builder reference if value is Some. + /// This method calls provided closure with builder reference if value is + /// Some. pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where F: FnOnce(T, &mut HttpResponseBuilder) + where + F: FnOnce(T, &mut HttpResponseBuilder), { if let Some(val) = value { f(val, self); @@ -494,8 +516,8 @@ impl HttpResponseBuilder { /// Set write buffer capacity /// /// This parameter makes sense only for streaming response - /// or actor. If write buffer reaches specified capacity, stream or actor get - /// paused. + /// or actor. If write buffer reaches specified capacity, stream or actor + /// get paused. /// /// Default write buffer capacity is 64kb pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { @@ -510,9 +532,11 @@ impl HttpResponseBuilder { /// `HttpResponseBuilder` can not be used after this call. pub fn body>(&mut self, body: B) -> HttpResponse { if let Some(e) = self.err.take() { - return Error::from(e).into() + return Error::from(e).into(); } - let mut response = self.response.take().expect("cannot reuse response builder"); + let mut response = self.response + .take() + .expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { @@ -530,10 +554,13 @@ impl HttpResponseBuilder { /// /// `HttpResponseBuilder` can not be used after this call. pub fn streaming(&mut self, stream: S) -> HttpResponse - where S: Stream + 'static, - E: Into, + where + S: Stream + 'static, + E: Into, { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) + self.body(Body::Streaming(Box::new( + stream.map_err(|e| e.into()), + ))) } /// Set a json body and generate `HttpResponse` @@ -542,19 +569,19 @@ impl HttpResponseBuilder { pub fn json(&mut self, value: T) -> HttpResponse { match serde_json::to_string(&value) { Ok(body) => { - let contains = - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; + let contains = if let Some(parts) = parts(&mut self.response, &self.err) + { + parts.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; if !contains { self.header(header::CONTENT_TYPE, "application/json"); } self.body(body) - }, - Err(e) => Error::from(e).into() + } + Err(e) => Error::from(e).into(), } } @@ -579,11 +606,11 @@ impl HttpResponseBuilder { #[inline] #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] -fn parts<'a>(parts: &'a mut Option>, err: &Option) - -> Option<&'a mut Box> -{ +fn parts<'a>( + parts: &'a mut Option>, err: &Option +) -> Option<&'a mut Box> { if err.is_some() { - return None + return None; } parts.as_mut() } @@ -628,8 +655,8 @@ impl Responder for &'static str { fn respond_to(self, req: HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) + .content_type("text/plain; charset=utf-8") + .body(self)) } } @@ -647,8 +674,8 @@ impl Responder for &'static [u8] { fn respond_to(self, req: HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) + .content_type("application/octet-stream") + .body(self)) } } @@ -666,8 +693,8 @@ impl Responder for String { fn respond_to(self, req: HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) + .content_type("text/plain; charset=utf-8") + .body(self)) } } @@ -685,8 +712,8 @@ impl<'a> Responder for &'a String { fn respond_to(self, req: HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) + .content_type("text/plain; charset=utf-8") + .body(self)) } } @@ -704,8 +731,8 @@ impl Responder for Bytes { fn respond_to(self, req: HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) + .content_type("application/octet-stream") + .body(self)) } } @@ -723,8 +750,8 @@ impl Responder for BytesMut { fn respond_to(self, req: HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) + .content_type("application/octet-stream") + .body(self)) } } @@ -745,7 +772,9 @@ impl<'a> From<&'a ClientResponse> for HttpResponseBuilder { impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { fn from(req: &'a HttpRequest) -> HttpResponseBuilder { if let Some(router) = req.router() { - router.server_settings().get_response_builder(StatusCode::OK) + router + .server_settings() + .get_response_builder(StatusCode::OK) } else { HttpResponse::Ok() } @@ -768,7 +797,6 @@ struct InnerHttpResponse { } impl InnerHttpResponse { - #[inline] fn new(status: StatusCode, body: Body) -> InnerHttpResponse { InnerHttpResponse { @@ -793,38 +821,41 @@ pub(crate) struct HttpResponsePool(VecDeque>); thread_local!(static POOL: Rc> = HttpResponsePool::pool()); impl HttpResponsePool { - pub fn pool() -> Rc> { - Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(128)))) + Rc::new(UnsafeCell::new(HttpResponsePool( + VecDeque::with_capacity(128), + ))) } #[inline] - pub fn get_builder(pool: &Rc>, status: StatusCode) - -> HttpResponseBuilder - { - let p = unsafe{&mut *pool.as_ref().get()}; + pub fn get_builder( + pool: &Rc>, status: StatusCode + ) -> HttpResponseBuilder { + let p = unsafe { &mut *pool.as_ref().get() }; if let Some(mut msg) = p.0.pop_front() { msg.status = status; HttpResponseBuilder { response: Some(msg), pool: Some(Rc::clone(pool)), err: None, - cookies: None } + cookies: None, + } } else { let msg = Box::new(InnerHttpResponse::new(status, Body::Empty)); HttpResponseBuilder { response: Some(msg), pool: Some(Rc::clone(pool)), err: None, - cookies: None } + cookies: None, + } } } #[inline] - pub fn get_response(pool: &Rc>, - status: StatusCode, body: Body) -> HttpResponse - { - let p = unsafe{&mut *pool.as_ref().get()}; + pub fn get_response( + pool: &Rc>, status: StatusCode, body: Body + ) -> HttpResponse { + let p = unsafe { &mut *pool.as_ref().get() }; if let Some(mut msg) = p.0.pop_front() { msg.status = status; msg.body = body; @@ -847,9 +878,10 @@ impl HttpResponsePool { #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))] - fn release(pool: &Rc>, mut inner: Box) - { - let pool = unsafe{&mut *pool.as_ref().get()}; + fn release( + pool: &Rc>, mut inner: Box + ) { + let pool = unsafe { &mut *pool.as_ref().get() }; if pool.0.len() < 128 { inner.headers.clear(); inner.version = None; @@ -868,12 +900,12 @@ impl HttpResponsePool { #[cfg(test)] mod tests { use super::*; - use std::str::FromStr; - use time::Duration; - use http::{Method, Uri}; - use http::header::{COOKIE, CONTENT_TYPE, HeaderValue}; use body::Binary; use http; + use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use http::{Method, Uri}; + use std::str::FromStr; + use time::Duration; #[test] fn test_debug() { @@ -892,25 +924,37 @@ mod tests { headers.insert(COOKIE, HeaderValue::from_static("cookie2=value2")); let req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); let cookies = req.cookies().unwrap(); let resp = HttpResponse::Ok() - .cookie(http::Cookie::build("name", "value") + .cookie( + http::Cookie::build("name", "value") .domain("www.rust-lang.org") .path("/test") .http_only(true) .max_age(Duration::days(1)) - .finish()) + .finish(), + ) .del_cookie(&cookies[0]) .finish(); - let mut val: Vec<_> = resp.headers().get_all("Set-Cookie") - .iter().map(|v| v.to_str().unwrap().to_owned()).collect(); + 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("cookie2=; Max-Age=0;")); assert_eq!( - val[1],"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400"); + val[1], + "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" + ); } #[test] @@ -931,15 +975,21 @@ mod tests { #[test] fn test_force_close() { - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let resp = HttpResponse::build(StatusCode::OK) + .force_close() + .finish(); assert!(!resp.keep_alive().unwrap()) } #[test] fn test_content_type() { let resp = HttpResponse::build(StatusCode::OK) - .content_type("text/plain").body(Body::Empty); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") + .content_type("text/plain") + .body(Body::Empty); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + "text/plain" + ) } #[test] @@ -947,25 +997,29 @@ mod tests { let resp = HttpResponse::build(StatusCode::OK).finish(); assert_eq!(resp.content_encoding(), None); - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] { let resp = HttpResponse::build(StatusCode::OK) - .content_encoding(ContentEncoding::Br).finish(); + .content_encoding(ContentEncoding::Br) + .finish(); assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); } let resp = HttpResponse::build(StatusCode::OK) - .content_encoding(ContentEncoding::Gzip).finish(); + .content_encoding(ContentEncoding::Gzip) + .finish(); assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); } #[test] fn test_json() { - let resp = HttpResponse::build(StatusCode::OK) - .json(vec!["v1", "v2", "v3"]); + let resp = HttpResponse::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(*resp.body(), Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))); + assert_eq!( + *resp.body(), + Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) + ); } #[test] @@ -975,7 +1029,10 @@ mod tests { .json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!(*resp.body(), Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]"))); + assert_eq!( + *resp.body(), + Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) + ); } impl Body { @@ -993,91 +1050,152 @@ mod tests { let resp: HttpResponse = "test".into(); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + 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 = "test".respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + 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(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref())); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(b"test".as_ref()) + ); let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref())); + 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(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + 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())); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from("test".to_owned()) + ); - let resp: HttpResponse = "test".to_owned().respond_to(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(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + 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())); + 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(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + 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())); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(&"test".to_owned()) + ); - let resp: HttpResponse = (&"test".to_owned()).respond_to(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(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + 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())); + 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); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + 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"))); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(Bytes::from_static(b"test")) + ); let b = Bytes::from_static(b"test"); let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + 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"))); + 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); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(BytesMut::from("test")) + ); let b = BytesMut::from("test"); let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream")); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(BytesMut::from("test")) + ); } #[test] diff --git a/src/info.rs b/src/info.rs index 7e1b40f0..76288539 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,13 +1,12 @@ -use std::str::FromStr; use http::header::{self, HeaderName}; use httpmessage::HttpMessage; use httprequest::HttpRequest; +use std::str::FromStr; const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST"; const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; - /// `HttpRequest` connection information pub struct ConnectionInfo<'a> { scheme: &'a str, @@ -17,7 +16,6 @@ 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> { @@ -55,8 +53,9 @@ impl<'a> ConnectionInfo<'a> { // scheme if scheme.is_none() { - if let Some(h) = req.headers().get( - HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) { + 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()); } @@ -75,7 +74,9 @@ impl<'a> ConnectionInfo<'a> { // host if host.is_none() { - if let Some(h) = req.headers().get(HeaderName::from_str(X_FORWARDED_HOST).unwrap()) { + 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()); } @@ -97,13 +98,15 @@ 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 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() { // get peeraddr from socketaddr + if remote.is_none() { + // get peeraddr from socketaddr peer = req.peer_addr().map(|addr| format!("{}", addr)); } } @@ -176,7 +179,9 @@ mod tests { 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")); + "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"); @@ -185,7 +190,9 @@ mod tests { let mut req = HttpRequest::default(); req.headers_mut().insert( - header::HOST, HeaderValue::from_static("rust-lang.org")); + header::HOST, + HeaderValue::from_static("rust-lang.org"), + ); let info = ConnectionInfo::new(&req); assert_eq!(info.scheme(), "http"); @@ -194,20 +201,26 @@ mod tests { let mut req = HttpRequest::default(); req.headers_mut().insert( - HeaderName::from_str(X_FORWARDED_FOR).unwrap(), HeaderValue::from_static("192.0.2.60")); + 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")); + 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")); + 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/json.rs b/src/json.rs index 646a0111..4eb51fb8 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,16 +1,16 @@ +use bytes::{Bytes, BytesMut}; +use futures::{Future, Poll, Stream}; +use http::header::CONTENT_LENGTH; use std::fmt; use std::ops::{Deref, DerefMut}; -use bytes::{Bytes, BytesMut}; -use futures::{Poll, Future, Stream}; -use http::header::CONTENT_LENGTH; use mime; -use serde_json; use serde::Serialize; use serde::de::DeserializeOwned; +use serde_json; use error::{Error, JsonPayloadError, PayloadError}; -use handler::{Responder, FromRequest}; +use handler::{FromRequest, Responder}; use http::StatusCode; use httpmessage::HttpMessage; use httprequest::HttpRequest; @@ -18,80 +18,15 @@ use httpresponse::HttpResponse; /// Json helper /// -/// Json can be used for two different purpose. First is for json response generation -/// and second is for extracting typed information from request's payload. -pub struct Json(pub T); - -impl Json { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Json { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Json { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl fmt::Debug for Json where T: fmt::Debug { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Json: {:?}", self.0) - } -} - -impl fmt::Display for Json where T: fmt::Display { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -/// 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*. +/// Json can be used for two different purpose. First is for json response +/// generation and second is for extracting typed information from request's +/// payload. /// -/// ```rust -/// # extern crate actix_web; -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// # -/// #[derive(Serialize)] -/// struct MyObj { -/// name: String, -/// } +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. /// -/// fn index(req: HttpRequest) -> Result> { -/// Ok(Json(MyObj{name: req.match_info().query("name")?})) -/// } -/// # fn main() {} -/// ``` -impl Responder for Json { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: HttpRequest) -> Result { - let body = serde_json::to_string(&self.0)?; - - Ok(req.build_response(StatusCode::OK) - .content_type("application/json") - .body(body)) - } -} - -/// To extract typed information from request's body, the type `T` must implement the -/// `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction process. +/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction +/// process. /// /// ## Example /// @@ -116,11 +51,88 @@ impl Responder for Json { /// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor /// } /// ``` +/// +/// 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 Json { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl Deref for Json { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Json { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Json +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Json: {:?}", self.0) + } +} + +impl fmt::Display for Json +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl Responder for Json { + type Item = HttpResponse; + type Error = Error; + + fn respond_to(self, req: HttpRequest) -> Result { + let body = serde_json::to_string(&self.0)?; + + Ok(req.build_response(StatusCode::OK) + .content_type("application/json") + .body(body)) + } +} + impl FromRequest for Json - where T: DeserializeOwned + 'static, S: 'static +where + T: DeserializeOwned + 'static, + S: 'static, { type Config = JsonConfig; - type Result = Box>; + type Result = Box>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { @@ -128,7 +140,8 @@ impl FromRequest for Json JsonBody::new(req.clone()) .limit(cfg.limit) .from_err() - .map(Json)) + .map(Json), + ) } } @@ -163,7 +176,6 @@ pub struct JsonConfig { } impl JsonConfig { - /// Change max size of payload. By default max size is 256Kb pub fn limit(&mut self, limit: usize) -> &mut Self { self.limit = limit; @@ -173,7 +185,7 @@ impl JsonConfig { impl Default for JsonConfig { fn default() -> Self { - JsonConfig{limit: 262_144} + JsonConfig { limit: 262_144 } } } @@ -208,17 +220,16 @@ impl Default for JsonConfig { /// } /// # fn main() {} /// ``` -pub struct JsonBody{ +pub struct JsonBody { limit: usize, req: Option, - fut: Option>>, + fut: Option>>, } impl JsonBody { - /// Create `JsonBody` for request. pub fn new(req: T) -> Self { - JsonBody{ + JsonBody { limit: 262_144, req: Some(req), fut: None, @@ -233,7 +244,8 @@ impl JsonBody { } impl Future for JsonBody - where T: HttpMessage + Stream + 'static +where + T: HttpMessage + Stream + 'static, { type Item = U; type Error = JsonPayloadError; @@ -259,7 +271,7 @@ impl Future for JsonBody false }; if !json { - return Err(JsonPayloadError::ContentType) + return Err(JsonPayloadError::ContentType); } let limit = self.limit; @@ -276,7 +288,10 @@ impl Future for JsonBody self.fut = Some(Box::new(fut)); } - self.fut.as_mut().expect("JsonBody could not be used second time").poll() + self.fut + .as_mut() + .expect("JsonBody could not be used second time") + .poll() } } @@ -284,11 +299,11 @@ impl Future for JsonBody mod tests { use super::*; use bytes::Bytes; - use http::header; use futures::Async; + use http::header; - use with::{With, ExtractorConfig}; use handler::Handler; + use with::{ExtractorConfig, With}; impl PartialEq for JsonPayloadError { fn eq(&self, other: &JsonPayloadError) -> bool { @@ -313,59 +328,100 @@ mod tests { #[test] fn test_json() { - let json = Json(MyObject{name: "test".to_owned()}); + 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"); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "application/json" + ); } #[test] fn test_json_body() { let req = HttpRequest::default(); let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); + assert_eq!( + json.poll().err().unwrap(), + JsonPayloadError::ContentType + ); let mut req = HttpRequest::default(); - req.headers_mut().insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text")); + req.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + ); let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); + assert_eq!( + json.poll().err().unwrap(), + JsonPayloadError::ContentType + ); let mut req = HttpRequest::default(); - req.headers_mut().insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json")); - req.headers_mut().insert(header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000")); + req.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ); + req.headers_mut().insert( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ); let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); let mut req = HttpRequest::default(); - req.headers_mut().insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json")); - req.headers_mut().insert(header::CONTENT_LENGTH, - header::HeaderValue::from_static("16")); - req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); + req.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ); + req.headers_mut().insert( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ); + req.payload_mut() + .unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); let mut json = req.json::(); - assert_eq!(json.poll().ok().unwrap(), - Async::Ready(MyObject{name: "test".to_owned()})); + assert_eq!( + json.poll().ok().unwrap(), + Async::Ready(MyObject { + name: "test".to_owned() + }) + ); } #[test] fn test_with_json() { let mut cfg = ExtractorConfig::<_, Json>::default(); cfg.limit(4096); - let mut handler = With::new(|data: Json| {data}, cfg); + let mut handler = With::new(|data: Json| data, cfg); let req = HttpRequest::default(); - let err = handler.handle(req).as_response().unwrap().error().is_some(); + let err = handler + .handle(req) + .as_response() + .unwrap() + .error() + .is_some(); assert!(err); let mut req = HttpRequest::default(); - req.headers_mut().insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json")); - req.headers_mut().insert(header::CONTENT_LENGTH, - header::HeaderValue::from_static("16")); - req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); - let ok = handler.handle(req).as_response().unwrap().error().is_none(); + req.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ); + req.headers_mut().insert( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ); + req.payload_mut() + .unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); + let ok = handler + .handle(req) + .as_response() + .unwrap() + .error() + .is_none(); assert!(ok) } } diff --git a/src/lib.rs b/src/lib.rs index 60b7c9f8..fff68afa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,17 +64,17 @@ #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error ))] -#![cfg_attr(feature = "cargo-clippy", allow( - decimal_literal_representation,suspicious_arithmetic_impl,))] +#![cfg_attr(feature = "cargo-clippy", + allow(decimal_literal_representation, suspicious_arithmetic_impl))] #[macro_use] extern crate log; -extern crate time; extern crate base64; -extern crate bytes; extern crate byteorder; -extern crate sha1; +extern crate bytes; extern crate regex; +extern crate sha1; +extern crate time; #[macro_use] extern crate bitflags; #[macro_use] @@ -83,46 +83,49 @@ extern crate failure; extern crate lazy_static; #[macro_use] extern crate futures; -extern crate futures_cpupool; -extern crate tokio_io; -extern crate tokio_core; -extern crate mio; -extern crate net2; extern crate cookie; +extern crate futures_cpupool; extern crate http as modhttp; -extern crate httparse; extern crate http_range; +extern crate httparse; +extern crate language_tags; +extern crate libc; extern crate mime; extern crate mime_guess; -extern crate language_tags; +extern crate mio; +extern crate net2; extern crate rand; +extern crate tokio_core; +extern crate tokio_io; extern crate url; -extern crate libc; -#[macro_use] extern crate serde; -extern crate serde_json; -extern crate serde_urlencoded; -extern crate flate2; -#[cfg(feature="brotli")] +#[macro_use] +extern crate serde; +#[cfg(feature = "brotli")] extern crate brotli2; extern crate encoding; -extern crate percent_encoding; -extern crate smallvec; -extern crate num_cpus; +extern crate flate2; extern crate h2 as http2; +extern crate num_cpus; +extern crate percent_encoding; +extern crate serde_json; +extern crate serde_urlencoded; +extern crate smallvec; extern crate trust_dns_resolver; -#[macro_use] extern crate actix; +#[macro_use] +extern crate actix; #[cfg(test)] -#[macro_use] extern crate serde_derive; +#[macro_use] +extern crate serde_derive; -#[cfg(feature="tls")] +#[cfg(feature = "tls")] extern crate native_tls; -#[cfg(feature="tls")] +#[cfg(feature = "tls")] extern crate tokio_tls; -#[cfg(feature="openssl")] +#[cfg(feature = "openssl")] extern crate openssl; -#[cfg(feature="openssl")] +#[cfg(feature = "openssl")] extern crate tokio_openssl; mod application; @@ -138,33 +141,33 @@ mod httprequest; mod httpresponse; mod info; mod json; -mod route; -mod router; -mod resource; mod param; mod payload; mod pipeline; +mod resource; +mod route; +mod router; mod with; pub mod client; -pub mod fs; -pub mod ws; pub mod error; -pub mod multipart; +pub mod fs; pub mod middleware; +pub mod multipart; pub mod pred; -pub mod test; pub mod server; -pub use extractor::{Path, Form, Query}; -pub use error::{Error, Result, ResponseError}; -pub use body::{Body, Binary}; -pub use json::Json; +pub mod test; +pub mod ws; pub use application::App; +pub use body::{Binary, Body}; +pub use context::HttpContext; +pub use error::{Error, ResponseError, Result}; +pub use extractor::{Form, Path, Query}; +pub use handler::{AsyncResponder, Either, FromRequest, FutureResponse, Responder, State}; pub use httpmessage::HttpMessage; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; -pub use handler::{Either, Responder, AsyncResponder, FromRequest, FutureResponse, State}; -pub use context::HttpContext; +pub use json::Json; #[doc(hidden)] pub mod httpcodes; @@ -173,39 +176,39 @@ pub mod httpcodes; #[allow(deprecated)] pub use application::Application; -#[cfg(feature="openssl")] +#[cfg(feature = "openssl")] pub(crate) const HAS_OPENSSL: bool = true; -#[cfg(not(feature="openssl"))] +#[cfg(not(feature = "openssl"))] pub(crate) const HAS_OPENSSL: bool = false; -#[cfg(feature="tls")] +#[cfg(feature = "tls")] pub(crate) const HAS_TLS: bool = true; -#[cfg(not(feature="tls"))] +#[cfg(not(feature = "tls"))] pub(crate) const HAS_TLS: bool = false; 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::*; -//! ``` + //! 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::*; + //! ``` pub use body::BodyStream; pub use context::Drain; - pub use json::{JsonBody, JsonConfig}; - pub use info::ConnectionInfo; - pub use handler::{Handler, Reply}; pub use extractor::{FormConfig, PayloadConfig}; - pub use route::Route; - pub use router::{Router, Resource, ResourceType}; - pub use resource::ResourceHandler; - pub use param::{FromParam, Params}; - pub use httpmessage::{UrlEncoded, MessageBody}; + pub use handler::{Handler, Reply}; + pub use httpmessage::{MessageBody, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; + pub use info::ConnectionInfo; + pub use json::{JsonBody, JsonConfig}; + pub use param::{FromParam, Params}; + pub use resource::ResourceHandler; + pub use route::Route; + pub use router::{Resource, ResourceType, Router}; } pub mod http { @@ -215,15 +218,15 @@ pub mod http { pub use modhttp::{Method, StatusCode, Version}; #[doc(hidden)] - pub use modhttp::{uri, Uri, Error, Extensions, HeaderMap, HttpTryFrom}; + pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri}; - pub use http_range::HttpRange; pub use cookie::{Cookie, CookieBuilder}; + pub use http_range::HttpRange; pub use helpers::NormalizePath; pub mod header { - pub use ::header::*; + pub use header::*; } pub use header::ContentEncoding; pub use httpresponse::ConnectionType; diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 473f6f96..b99e1a8b 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -7,7 +7,8 @@ //! //! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. //! 2. Use any of the builder methods to set fields in the backend. -//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend. +//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the +//! constructed backend. //! //! Cors middleware could be used as parameter for `App::middleware()` or //! `ResourceHandler::middleware()` methods. But you have to use @@ -40,65 +41,69 @@ //! .register()); //! } //! ``` -//! In this example custom *CORS* middleware get registered for "/index.html" endpoint. +//! In this example custom *CORS* middleware get registered for "/index.html" +//! endpoint. //! //! Cors middleware automatically handle *OPTIONS* preflight request. use std::collections::HashSet; use std::iter::FromIterator; use std::rc::Rc; -use http::{self, Method, HttpTryFrom, Uri, StatusCode}; use http::header::{self, HeaderName, HeaderValue}; +use http::{self, HttpTryFrom, Method, StatusCode, Uri}; use application::App; -use error::{Result, ResponseError}; -use resource::ResourceHandler; +use error::{ResponseError, Result}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; +use resource::ResourceHandler; /// A set of errors that can occur during processing CORS #[derive(Debug, Fail)] pub enum CorsError { /// The HTTP request header `Origin` is required but was not provided - #[fail(display="The HTTP request header `Origin` is required but was not provided")] + #[fail(display = "The HTTP request header `Origin` is required but was not provided")] MissingOrigin, /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display="The HTTP request header `Origin` could not be parsed correctly.")] + #[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")] BadOrigin, - /// The request header `Access-Control-Request-Method` is required but is missing - #[fail(display="The request header `Access-Control-Request-Method` is required but is missing")] + /// The request header `Access-Control-Request-Method` is required but is + /// missing + #[fail(display = "The request header `Access-Control-Request-Method` is required but is missing")] MissingRequestMethod, /// The request header `Access-Control-Request-Method` has an invalid value - #[fail(display="The request header `Access-Control-Request-Method` has an invalid value")] + #[fail(display = "The request header `Access-Control-Request-Method` has an invalid value")] BadRequestMethod, - /// The request header `Access-Control-Request-Headers` has an invalid value - #[fail(display="The request header `Access-Control-Request-Headers` has an invalid value")] + /// The request header `Access-Control-Request-Headers` has an invalid + /// value + #[fail(display = "The request header `Access-Control-Request-Headers` has an invalid value")] BadRequestHeaders, - /// The request header `Access-Control-Request-Headers` is required but is missing. - #[fail(display="The request header `Access-Control-Request-Headers` is required but is + /// The request header `Access-Control-Request-Headers` is required but is + /// missing. + #[fail(display = "The request header `Access-Control-Request-Headers` is required but is missing")] MissingRequestHeaders, /// Origin is not allowed to make this request - #[fail(display="Origin is not allowed to make this request")] + #[fail(display = "Origin is not allowed to make this request")] OriginNotAllowed, /// Requested method is not allowed - #[fail(display="Requested method is not allowed")] + #[fail(display = "Requested method is not allowed")] MethodNotAllowed, /// One or more headers requested are not allowed - #[fail(display="One or more headers requested are not allowed")] + #[fail(display = "One or more headers requested are not allowed")] HeadersNotAllowed, } impl ResponseError for CorsError { - fn error_response(&self) -> HttpResponse { HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self)) } } -/// An enum signifying that some of type T is allowed, or `All` (everything is allowed). +/// An enum signifying that some of type T is allowed, or `All` (everything is +/// allowed). /// /// `Default` is implemented for this enum and is `All`. #[derive(Clone, Debug, Eq, PartialEq)] @@ -166,9 +171,16 @@ impl Default for Cors { origins: AllOrSome::default(), origins_str: None, methods: HashSet::from_iter( - vec![Method::GET, Method::HEAD, - Method::POST, Method::OPTIONS, Method::PUT, - Method::PATCH, Method::DELETE].into_iter()), + vec![ + Method::GET, + Method::HEAD, + Method::POST, + Method::OPTIONS, + Method::PUT, + Method::PATCH, + Method::DELETE, + ].into_iter(), + ), headers: AllOrSome::All, expose_hdrs: None, max_age: None, @@ -177,7 +189,9 @@ impl Default for Cors { supports_credentials: false, vary_header: true, }; - Cors{inner: Rc::new(inner)} + Cors { + inner: Rc::new(inner), + } } } @@ -247,11 +261,13 @@ impl Cors { /// This method register cors middleware with resource and /// adds route for *OPTIONS* preflight requests. /// - /// It is possible to register *Cors* middleware with `ResourceHandler::middleware()` - /// method, but in that case *Cors* middleware wont be able to handle *OPTIONS* - /// requests. + /// It is possible to register *Cors* middleware with + /// `ResourceHandler::middleware()` method, but in that case *Cors* + /// middleware wont be able to handle *OPTIONS* requests. pub fn register(self, resource: &mut ResourceHandler) { - resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok()); + resource + .method(Method::OPTIONS) + .h(|_| HttpResponse::Ok()); resource.middleware(self); } @@ -260,28 +276,32 @@ impl Cors { if let Ok(origin) = hdr.to_str() { return match self.inner.origins { AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_origins) => { - allowed_origins - .get(origin) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::OriginNotAllowed) - } + AllOrSome::Some(ref allowed_origins) => allowed_origins + .get(origin) + .and_then(|_| Some(())) + .ok_or_else(|| CorsError::OriginNotAllowed), }; } Err(CorsError::BadOrigin) } else { return match self.inner.origins { AllOrSome::All => Ok(()), - _ => Err(CorsError::MissingOrigin) - } + _ => Err(CorsError::MissingOrigin), + }; } } - fn validate_allowed_method(&self, req: &mut HttpRequest) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { + fn validate_allowed_method( + &self, req: &mut HttpRequest + ) -> Result<(), CorsError> { + if let Some(hdr) = req.headers() + .get(header::ACCESS_CONTROL_REQUEST_METHOD) + { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { - return self.inner.methods.get(&method) + return self.inner + .methods + .get(&method) .and_then(|_| Some(())) .ok_or_else(|| CorsError::MethodNotAllowed); } @@ -292,24 +312,28 @@ impl Cors { } } - fn validate_allowed_headers(&self, req: &mut HttpRequest) -> Result<(), CorsError> { + fn validate_allowed_headers( + &self, req: &mut HttpRequest + ) -> Result<(), CorsError> { match self.inner.headers { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { + if let Some(hdr) = req.headers() + .get(header::ACCESS_CONTROL_REQUEST_HEADERS) + { if let Ok(headers) = hdr.to_str() { let mut hdrs = HashSet::new(); for hdr in headers.split(',') { match HeaderName::try_from(hdr.trim()) { Ok(hdr) => hdrs.insert(hdr), - Err(_) => return Err(CorsError::BadRequestHeaders) + Err(_) => return Err(CorsError::BadRequestHeaders), }; } if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { - return Err(CorsError::HeadersNotAllowed) + return Err(CorsError::HeadersNotAllowed); } - return Ok(()) + return Ok(()); } Err(CorsError::BadRequestHeaders) } else { @@ -321,7 +345,6 @@ impl Cors { } impl Middleware for Cors { - fn start(&self, req: &mut HttpRequest) -> Result { if self.inner.preflight && Method::OPTIONS == *req.method() { self.validate_origin(req)?; @@ -330,9 +353,17 @@ impl Middleware for Cors { // allowed headers let headers = if let Some(headers) = self.inner.headers.as_ref() { - Some(HeaderValue::try_from(&headers.iter().fold( - String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]).unwrap()) - } else if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { + Some( + HeaderValue::try_from( + &headers + .iter() + .fold(String::new(), |s, v| s + "," + v.as_str()) + .as_str()[1..], + ).unwrap(), + ) + } else if let Some(hdr) = req.headers() + .get(header::ACCESS_CONTROL_REQUEST_HEADERS) + { Some(hdr.clone()) } else { None @@ -342,31 +373,44 @@ impl Middleware for Cors { HttpResponse::Ok() .if_some(self.inner.max_age.as_ref(), |max_age, resp| { let _ = resp.header( - header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());}) + header::ACCESS_CONTROL_MAX_AGE, + format!("{}", max_age).as_str(), + ); + }) .if_some(headers, |headers, resp| { - let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); }) + let _ = + resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); + }) .if_true(self.inner.origins.is_all(), |resp| { if self.inner.send_wildcard { resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); } else { let origin = req.headers().get(header::ORIGIN).unwrap(); resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); + header::ACCESS_CONTROL_ALLOW_ORIGIN, + origin.clone(), + ); } }) .if_true(self.inner.origins.is_some(), |resp| { resp.header( header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone()); + self.inner.origins_str.as_ref().unwrap().clone(), + ); }) .if_true(self.inner.supports_credentials, |resp| { resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); }) .header( header::ACCESS_CONTROL_ALLOW_METHODS, - &self.inner.methods.iter().fold( - String::new(), |s, v| s + "," + v.as_str()).as_str()[1..]) - .finish())) + &self.inner + .methods + .iter() + .fold(String::new(), |s, v| s + "," + v.as_str()) + .as_str()[1..], + ) + .finish(), + )) } else { self.validate_origin(req)?; @@ -374,32 +418,40 @@ impl Middleware for Cors { } } - fn response(&self, req: &mut HttpRequest, mut resp: HttpResponse) -> Result { + fn response( + &self, req: &mut HttpRequest, mut resp: HttpResponse + ) -> Result { match self.inner.origins { AllOrSome::All => { if self.inner.send_wildcard { resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*")); + header::ACCESS_CONTROL_ALLOW_ORIGIN, + HeaderValue::from_static("*"), + ); } else if let Some(origin) = req.headers().get(header::ORIGIN) { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); + resp.headers_mut() + .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); } } AllOrSome::Some(_) => { resp.headers_mut().insert( header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone()); + self.inner.origins_str.as_ref().unwrap().clone(), + ); } } if let Some(ref expose) = self.inner.expose_hdrs { resp.headers_mut().insert( header::ACCESS_CONTROL_EXPOSE_HEADERS, - HeaderValue::try_from(expose.as_str()).unwrap()); + HeaderValue::try_from(expose.as_str()).unwrap(), + ); } if self.inner.supports_credentials { resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_CREDENTIALS, HeaderValue::from_static("true")); + header::ACCESS_CONTROL_ALLOW_CREDENTIALS, + HeaderValue::from_static("true"), + ); } if self.inner.vary_header { let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) { @@ -416,13 +468,15 @@ impl Middleware for Cors { } } -/// Structure that follows the builder pattern for building `Cors` middleware structs. +/// Structure that follows the builder pattern for building `Cors` middleware +/// structs. /// /// To construct a cors: /// /// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. /// 2. Use any of the builder methods to set fields in the backend. -/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend. +/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the +/// constructed backend. /// /// # Example /// @@ -442,7 +496,7 @@ impl Middleware for Cors { /// .finish(); /// # } /// ``` -pub struct CorsBuilder { +pub struct CorsBuilder { cors: Option, methods: bool, error: Option, @@ -451,26 +505,26 @@ pub struct CorsBuilder { app: Option>, } -fn cors<'a>(parts: &'a mut Option, err: &Option) - -> Option<&'a mut Inner> -{ +fn cors<'a>( + parts: &'a mut Option, err: &Option +) -> Option<&'a mut Inner> { if err.is_some() { - return None + return None; } parts.as_mut() } impl CorsBuilder { - /// Add an origin that are allowed to make requests. /// Will be verified against the `Origin` request header. /// /// When `All` is set, and `send_wildcard` is set, "*" will be sent in - /// the `Access-Control-Allow-Origin` response header. Otherwise, the client's `Origin` request - /// header will be echoed back in the `Access-Control-Allow-Origin` response header. + /// the `Access-Control-Allow-Origin` response header. Otherwise, the + /// client's `Origin` request header will be echoed back in the + /// `Access-Control-Allow-Origin` response header. /// - /// When `Some` is set, the client's `Origin` request header will be checked in a - /// case-sensitive manner. + /// When `Some` is set, the client's `Origin` request header will be + /// checked in a case-sensitive manner. /// /// This is the `list of origins` in the /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). @@ -497,15 +551,17 @@ impl CorsBuilder { self } - /// Set a list of methods which the allowed origins are allowed to access for - /// requests. + /// Set a list of methods which the allowed origins are allowed to access + /// for requests. /// /// This is the `list of methods` in the /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]` pub fn allowed_methods(&mut self, methods: U) -> &mut CorsBuilder - where U: IntoIterator, Method: HttpTryFrom + where + U: IntoIterator, + Method: HttpTryFrom, { self.methods = true; if let Some(cors) = cors(&mut self.cors, &self.error) { @@ -513,20 +569,21 @@ impl CorsBuilder { match Method::try_from(m) { Ok(method) => { cors.methods.insert(method); - }, + } Err(e) => { self.error = Some(e.into()); - break + break; } } - }; + } } self } /// Set an allowed header pub fn allowed_header(&mut self, header: H) -> &mut CorsBuilder - where HeaderName: HttpTryFrom + where + HeaderName: HttpTryFrom, { if let Some(cors) = cors(&mut self.cors, &self.error) { match HeaderName::try_from(header) { @@ -547,15 +604,18 @@ impl CorsBuilder { /// Set a list of header field names which can be used when /// this resource is accessed by allowed origins. /// - /// If `All` is set, whatever is requested by the client in `Access-Control-Request-Headers` - /// will be echoed back in the `Access-Control-Allow-Headers` header. + /// If `All` is set, whatever is requested by the client in + /// `Access-Control-Request-Headers` will be echoed back in the + /// `Access-Control-Allow-Headers` header. /// /// This is the `list of headers` in the /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// Defaults to `All`. pub fn allowed_headers(&mut self, headers: U) -> &mut CorsBuilder - where U: IntoIterator, HeaderName: HttpTryFrom + where + U: IntoIterator, + HeaderName: HttpTryFrom, { if let Some(cors) = cors(&mut self.cors, &self.error) { for h in headers { @@ -570,32 +630,35 @@ impl CorsBuilder { } Err(e) => { self.error = Some(e.into()); - break + break; } } - }; + } } self } - /// Set a list of headers which are safe to expose to the API of a CORS API specification. - /// This corresponds to the `Access-Control-Expose-Headers` response header. + /// Set a list of headers which are safe to expose to the API of a CORS API + /// specification. This corresponds to the + /// `Access-Control-Expose-Headers` response header. /// /// This is the `list of exposed headers` in the /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// This defaults to an empty set. pub fn expose_headers(&mut self, headers: U) -> &mut CorsBuilder - where U: IntoIterator, HeaderName: HttpTryFrom + where + U: IntoIterator, + HeaderName: HttpTryFrom, { for h in headers { match HeaderName::try_from(h) { Ok(method) => { self.expose_hdrs.insert(method); - }, + } Err(e) => { self.error = Some(e.into()); - break + break; } } } @@ -615,16 +678,17 @@ impl CorsBuilder { /// Set a wildcard origins /// - /// If send wildcard is set and the `allowed_origins` parameter is `All`, a wildcard - /// `Access-Control-Allow-Origin` response header is sent, rather than the request’s - /// `Origin` header. + /// If send wildcard is set and the `allowed_origins` parameter is `All`, a + /// wildcard `Access-Control-Allow-Origin` response header is sent, + /// rather than the request’s `Origin` header. /// /// This is the `supports credentials flag` in the /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// - /// This **CANNOT** be used in conjunction with `allowed_origins` set to `All` and - /// `allow_credentials` set to `true`. Depending on the mode of usage, this will either result - /// in an `Error::CredentialsWithWildcardOrigin` error during actix launch or runtime. + /// This **CANNOT** be used in conjunction with `allowed_origins` set to + /// `All` and `allow_credentials` set to `true`. Depending on the mode + /// of usage, this will either result in an `Error:: + /// CredentialsWithWildcardOrigin` error during actix launch or runtime. /// /// Defaults to `false`. pub fn send_wildcard(&mut self) -> &mut CorsBuilder { @@ -636,11 +700,12 @@ impl CorsBuilder { /// Allows users to make authenticated requests /// - /// If true, injects the `Access-Control-Allow-Credentials` header in responses. - /// This allows cookies and credentials to be submitted across domains. + /// If true, injects the `Access-Control-Allow-Credentials` header in + /// responses. This allows cookies and credentials to be submitted + /// across domains. /// - /// This option cannot be used in conjunction with an `allowed_origin` set to `All` - /// and `send_wildcards` set to `true`. + /// This option cannot be used in conjunction with an `allowed_origin` set + /// to `All` and `send_wildcards` set to `true`. /// /// Defaults to `false`. /// @@ -713,7 +778,8 @@ impl CorsBuilder { /// } /// ``` pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder - where F: FnOnce(&mut ResourceHandler) -> R + 'static + where + F: FnOnce(&mut ResourceHandler) -> R + 'static, { // add resource handler let mut handler = ResourceHandler::default(); @@ -725,9 +791,15 @@ impl CorsBuilder { fn construct(&mut self) -> Cors { if !self.methods { - self.allowed_methods(vec![Method::GET, Method::HEAD, - Method::POST, Method::OPTIONS, Method::PUT, - Method::PATCH, Method::DELETE]); + self.allowed_methods(vec![ + Method::GET, + Method::HEAD, + Method::POST, + Method::OPTIONS, + Method::PUT, + Method::PATCH, + Method::DELETE, + ]); } if let Some(e) = self.error.take() { @@ -741,16 +813,23 @@ impl CorsBuilder { } if let AllOrSome::Some(ref origins) = cors.origins { - let s = origins.iter().fold(String::new(), |s, v| s + &format!("{}", v)); + let s = origins + .iter() + .fold(String::new(), |s, v| s + &format!("{}", v)); cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap()); } if !self.expose_hdrs.is_empty() { cors.expose_hdrs = Some( - self.expose_hdrs.iter().fold( - String::new(), |s, v| s + v.as_str())[1..].to_owned()); + self.expose_hdrs + .iter() + .fold(String::new(), |s, v| s + v.as_str())[1..] + .to_owned(), + ); + } + Cors { + inner: Rc::new(cors), } - Cors{inner: Rc::new(cors)} } /// Finishes building and returns the built `Cors` instance. @@ -758,13 +837,16 @@ impl CorsBuilder { /// This method panics in case of any configuration error. pub fn finish(&mut self) -> Cors { if !self.resources.is_empty() { - panic!("CorsBuilder::resource() was used, - to construct CORS `.register(app)` method should be used"); + panic!( + "CorsBuilder::resource() was used, + to construct CORS `.register(app)` method should be used" + ); } self.construct() } - /// Finishes building Cors middleware and register middleware for application + /// Finishes building Cors middleware and register middleware for + /// application /// /// This method panics in case of any configuration error or if non of /// resources are registered. @@ -774,8 +856,9 @@ impl CorsBuilder { } let cors = self.construct(); - let mut app = self.app.take().expect( - "CorsBuilder has to be constructed with Cors::for_app(app)"); + let mut app = self.app + .take() + .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); // register resources for (path, mut resource) in self.resources.drain(..) { @@ -787,7 +870,6 @@ impl CorsBuilder { } } - #[cfg(test)] mod tests { use super::*; @@ -845,8 +927,8 @@ mod tests { #[test] fn validate_origin_allows_all_origins() { let cors = Cors::default(); - let mut req = TestRequest::with_header( - "Origin", "https://www.example.com").finish(); + let mut req = + TestRequest::with_header("Origin", "https://www.example.com").finish(); assert!(cors.start(&mut req).ok().unwrap().is_done()) } @@ -861,8 +943,7 @@ mod tests { .allowed_header(header::CONTENT_TYPE) .finish(); - let mut req = TestRequest::with_header( - "Origin", "https://www.example.com") + let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .finish(); @@ -877,23 +958,35 @@ mod tests { let mut req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "AUTHORIZATION,ACCEPT") + .header( + header::ACCESS_CONTROL_REQUEST_HEADERS, + "AUTHORIZATION,ACCEPT", + ) .method(Method::OPTIONS) .finish(); let resp = cors.start(&mut req).unwrap().response(); assert_eq!( &b"*"[..], - resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()); + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); assert_eq!( &b"3600"[..], - resp.headers().get(header::ACCESS_CONTROL_MAX_AGE).unwrap().as_bytes()); + resp.headers() + .get(header::ACCESS_CONTROL_MAX_AGE) + .unwrap() + .as_bytes() + ); //assert_eq!( // &b"authorization,accept,content-type"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap().as_bytes()); - //assert_eq!( + // resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap(). + // as_bytes()); assert_eq!( // &b"POST,GET,OPTIONS"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap().as_bytes()); + // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap(). + // as_bytes()); Rc::get_mut(&mut cors.inner).unwrap().preflight = false; assert!(cors.start(&mut req).unwrap().is_done()); @@ -903,7 +996,8 @@ mod tests { #[should_panic(expected = "MissingOrigin")] fn test_validate_missing_origin() { let cors = Cors::build() - .allowed_origin("https://www.example.com").finish(); + .allowed_origin("https://www.example.com") + .finish(); let mut req = HttpRequest::default(); cors.start(&mut req).unwrap(); @@ -913,7 +1007,8 @@ mod tests { #[should_panic(expected = "OriginNotAllowed")] fn test_validate_not_allowed_origin() { let cors = Cors::build() - .allowed_origin("https://www.example.com").finish(); + .allowed_origin("https://www.example.com") + .finish(); let mut req = TestRequest::with_header("Origin", "https://www.unknown.com") .method(Method::GET) @@ -924,7 +1019,8 @@ mod tests { #[test] fn test_validate_origin() { let cors = Cors::build() - .allowed_origin("https://www.example.com").finish(); + .allowed_origin("https://www.example.com") + .finish(); let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::GET) @@ -940,16 +1036,23 @@ mod tests { let mut req = TestRequest::default().method(Method::GET).finish(); let resp: HttpResponse = HttpResponse::Ok().into(); let resp = cors.response(&mut req, resp).unwrap().response(); - assert!(resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).is_none()); + assert!( + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .is_none() + ); - let mut req = TestRequest::with_header( - "Origin", "https://www.example.com") + let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .finish(); let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], - resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()); + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); } #[test] @@ -963,8 +1066,7 @@ mod tests { .allowed_header(header::CONTENT_TYPE) .finish(); - let mut req = TestRequest::with_header( - "Origin", "https://www.example.com") + let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .finish(); @@ -972,10 +1074,15 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"*"[..], - resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()); + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); assert_eq!( &b"Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes()); + resp.headers().get(header::VARY).unwrap().as_bytes() + ); let resp: HttpResponse = HttpResponse::Ok() .header(header::VARY, "Accept") @@ -983,7 +1090,8 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"Accept, Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes()); + resp.headers().get(header::VARY).unwrap().as_bytes() + ); let cors = Cors::build() .disable_vary_header() @@ -993,25 +1101,33 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], - resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()); + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); } #[test] fn cors_resource() { - let mut srv = test::TestServer::with_factory( - || App::new() - .configure( - |app| Cors::for_app(app) - .allowed_origin("https://www.example.com") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register())); + let mut srv = test::TestServer::with_factory(|| { + App::new().configure(|app| { + Cors::for_app(app) + .allowed_origin("https://www.example.com") + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + .register() + }) + }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); - let request = srv.get().uri(srv.url("/test")) - .header("ORIGIN", "https://www.example.com").finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test")) + .header("ORIGIN", "https://www.example.com") + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::OK); } diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 2e600f3d..b0eb4a3d 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -48,8 +48,8 @@ use std::borrow::Cow; use std::collections::HashSet; use bytes::Bytes; -use error::{Result, ResponseError}; -use http::{HeaderMap, HttpTryFrom, Uri, header}; +use error::{ResponseError, Result}; +use http::{header, HeaderMap, HttpTryFrom, Uri}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -59,13 +59,13 @@ use middleware::{Middleware, Started}; #[derive(Debug, Fail)] pub enum CsrfError { /// The HTTP request header `Origin` was required but not provided. - #[fail(display="Origin header required")] + #[fail(display = "Origin header required")] MissingOrigin, /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display="Could not parse Origin header")] + #[fail(display = "Could not parse Origin header")] BadOrigin, /// The cross-site request was denied. - #[fail(display="Cross-site request denied")] + #[fail(display = "Cross-site request denied")] CsrDenied, } @@ -80,15 +80,14 @@ fn uri_origin(uri: &Uri) -> Option { (Some(scheme), Some(host), Some(port)) => { Some(format!("{}://{}:{}", scheme, host, port)) } - (Some(scheme), Some(host), None) => { - Some(format!("{}://{}", scheme, host)) - } - _ => None + (Some(scheme), Some(host), None) => Some(format!("{}://{}", scheme, host)), + _ => None, } } fn origin(headers: &HeaderMap) -> Option, CsrfError>> { - headers.get(header::ORIGIN) + headers + .get(header::ORIGIN) .map(|origin| { origin .to_str() @@ -96,15 +95,14 @@ fn origin(headers: &HeaderMap) -> Option, CsrfError>> { .map(|o| o.into()) }) .or_else(|| { - headers.get(header::REFERER) - .map(|referer| { - Uri::try_from(Bytes::from(referer.as_bytes())) - .ok() - .as_ref() - .and_then(uri_origin) - .ok_or(CsrfError::BadOrigin) - .map(|o| o.into()) - }) + headers.get(header::REFERER).map(|referer| { + Uri::try_from(Bytes::from(referer.as_bytes())) + .ok() + .as_ref() + .and_then(uri_origin) + .ok_or(CsrfError::BadOrigin) + .map(|o| o.into()) + }) }) } @@ -194,7 +192,8 @@ impl CsrfFilter { let is_upgrade = req.headers().contains_key(header::UPGRADE); let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade); - if is_safe || (self.allow_xhr && req.headers().contains_key("x-requested-with")) { + if is_safe || (self.allow_xhr && req.headers().contains_key("x-requested-with")) + { Ok(()) } else if let Some(header) = origin(req.headers()) { match header { @@ -225,8 +224,7 @@ mod tests { #[test] fn test_safe() { - let csrf = CsrfFilter::new() - .allowed_origin("https://www.example.com"); + let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::HEAD) @@ -237,8 +235,7 @@ mod tests { #[test] fn test_csrf() { - let csrf = CsrfFilter::new() - .allowed_origin("https://www.example.com"); + let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::POST) @@ -249,11 +246,12 @@ mod tests { #[test] fn test_referer() { - let csrf = CsrfFilter::new() - .allowed_origin("https://www.example.com"); + let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - let mut req = TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param") - .method(Method::POST) + let mut req = TestRequest::with_header( + "Referer", + "https://www.example.com/some/path?query=param", + ).method(Method::POST) .finish(); assert!(csrf.start(&mut req).is_ok()); @@ -261,8 +259,7 @@ mod tests { #[test] fn test_upgrade() { - let strict_csrf = CsrfFilter::new() - .allowed_origin("https://www.example.com"); + let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let lax_csrf = CsrfFilter::new() .allowed_origin("https://www.example.com") diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 5399b29d..4e17a553 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -1,11 +1,11 @@ //! Default response headers -use http::{HeaderMap, HttpTryFrom}; use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; +use http::{HeaderMap, HttpTryFrom}; use error::Result; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middleware::{Response, Middleware}; +use middleware::{Middleware, Response}; /// `Middleware` for setting default response headers. /// @@ -27,14 +27,17 @@ use middleware::{Response, Middleware}; /// .finish(); /// } /// ``` -pub struct DefaultHeaders{ +pub struct DefaultHeaders { ct: bool, headers: HeaderMap, } impl Default for DefaultHeaders { fn default() -> Self { - DefaultHeaders{ct: false, headers: HeaderMap::new()} + DefaultHeaders { + ct: false, + headers: HeaderMap::new(), + } } } @@ -48,15 +51,16 @@ impl DefaultHeaders { #[inline] #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] pub fn header(mut self, key: K, value: V) -> Self - where HeaderName: HttpTryFrom, - HeaderValue: HttpTryFrom + where + HeaderName: HttpTryFrom, + HeaderValue: HttpTryFrom, { match HeaderName::try_from(key) { - Ok(key) => { - match HeaderValue::try_from(value) { - Ok(value) => { self.headers.append(key, value); } - Err(_) => panic!("Can not create header value"), + Ok(key) => match HeaderValue::try_from(value) { + Ok(value) => { + self.headers.append(key, value); } + Err(_) => panic!("Can not create header value"), }, Err(_) => panic!("Can not create header name"), } @@ -71,8 +75,9 @@ impl DefaultHeaders { } impl Middleware for DefaultHeaders { - - fn response(&self, _: &mut HttpRequest, mut resp: HttpResponse) -> Result { + fn response( + &self, _: &mut HttpRequest, mut resp: HttpResponse + ) -> Result { for (key, value) in self.headers.iter() { if !resp.headers().contains_key(key) { resp.headers_mut().insert(key, value.clone()); @@ -81,7 +86,9 @@ impl Middleware for DefaultHeaders { // default content-type if self.ct && !resp.headers().contains_key(CONTENT_TYPE) { resp.headers_mut().insert( - CONTENT_TYPE, HeaderValue::from_static("application/octet-stream")); + CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ); } Ok(Response::Done(resp)) } @@ -94,8 +101,7 @@ mod tests { #[test] fn test_default_headers() { - let mw = DefaultHeaders::new() - .header(CONTENT_TYPE, "0001"); + let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); let mut req = HttpRequest::default(); @@ -106,7 +112,9 @@ mod tests { }; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); + let resp = HttpResponse::Ok() + .header(CONTENT_TYPE, "0002") + .finish(); let resp = match mw.response(&mut req, resp) { Ok(Response::Done(resp)) => resp, _ => panic!(), diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index db3c70f3..fdc43ed2 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -6,14 +6,13 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response}; - type ErrorHandler = Fn(&mut HttpRequest, HttpResponse) -> Result; /// `Middleware` for allowing custom handlers for responses. /// -/// You can use `ErrorHandlers::handler()` method to register a custom error handler -/// for specific status code. You can modify existing response or create completly new -/// one. +/// You can use `ErrorHandlers::handler()` method to register a custom error +/// handler for specific status code. You can modify existing response or +/// create completly new one. /// /// ## Example /// @@ -53,7 +52,6 @@ impl Default for ErrorHandlers { } impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance pub fn new() -> Self { ErrorHandlers::default() @@ -61,7 +59,8 @@ impl ErrorHandlers { /// Register error handler for specified status code pub fn handler(mut self, status: StatusCode, handler: F) -> Self - where F: Fn(&mut HttpRequest, HttpResponse) -> Result + 'static + where + F: Fn(&mut HttpRequest, HttpResponse) -> Result + 'static, { self.handlers.insert(status, Box::new(handler)); self @@ -69,8 +68,9 @@ impl ErrorHandlers { } impl Middleware for ErrorHandlers { - - fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Result { + fn response( + &self, req: &mut HttpRequest, resp: HttpResponse + ) -> Result { if let Some(handler) = self.handlers.get(&resp.status()) { handler(req, resp) } else { @@ -90,11 +90,11 @@ mod tests { builder.header(CONTENT_TYPE, "0001"); Ok(Response::Done(builder.into())) } - + #[test] fn test_handler() { - let mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = + ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); let mut req = HttpRequest::default(); let resp = HttpResponse::InternalServerError().finish(); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 32d2ef4d..d9d96a1c 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -4,14 +4,14 @@ use std::fmt; use std::fmt::{Display, Formatter}; use libc; -use time; use regex::Regex; +use time; use error::Result; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middleware::{Middleware, Started, Finished}; +use middleware::{Finished, Middleware, Started}; /// `Middleware` for logging request and response info to the terminal. /// `Logger` middleware uses standard log crate to log information. You should @@ -21,7 +21,8 @@ use middleware::{Middleware, Started, Finished}; /// ## Usage /// /// Create `Logger` middleware with the specified `format`. -/// Default `Logger` could be created with `default` method, it uses the default format: +/// Default `Logger` could be created with `default` method, it uses the +/// default format: /// /// ```ignore /// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T @@ -59,7 +60,8 @@ use middleware::{Middleware, Started, Finished}; /// /// `%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 /// @@ -76,7 +78,9 @@ pub struct Logger { impl Logger { /// Create `Logger` middleware with the specified `format`. pub fn new(format: &str) -> Logger { - Logger { format: Format::new(format) } + Logger { + format: Format::new(format), + } } } @@ -87,14 +91,15 @@ impl Default for Logger { /// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` fn default() -> Logger { - Logger { format: Format::default() } + Logger { + format: Format::default(), + } } } struct StartTime(time::Tm); impl Logger { - fn log(&self, req: &mut HttpRequest, resp: &HttpResponse) { let entry_time = req.extensions().get::().unwrap().0; @@ -109,7 +114,6 @@ impl Logger { } impl Middleware for Logger { - fn start(&self, req: &mut HttpRequest) -> Result { req.extensions().insert(StartTime(time::now())); Ok(Started::Done) @@ -153,29 +157,26 @@ impl Format { idx = m.end(); if let Some(key) = cap.get(2) { - results.push( - match cap.get(3).unwrap().as_str() { - "i" => FormatText::RequestHeader(key.as_str().to_owned()), - "o" => FormatText::ResponseHeader(key.as_str().to_owned()), - "e" => FormatText::EnvironHeader(key.as_str().to_owned()), - _ => unreachable!(), - }) + results.push(match cap.get(3).unwrap().as_str() { + "i" => FormatText::RequestHeader(key.as_str().to_owned()), + "o" => FormatText::ResponseHeader(key.as_str().to_owned()), + "e" => FormatText::EnvironHeader(key.as_str().to_owned()), + _ => unreachable!(), + }) } else { let m = cap.get(1).unwrap(); - results.push( - match m.as_str() { - "%" => FormatText::Percent, - "a" => FormatText::RemoteAddr, - "t" => FormatText::RequestTime, - "P" => FormatText::Pid, - "r" => FormatText::RequestLine, - "s" => FormatText::ResponseStatus, - "b" => FormatText::ResponseSize, - "T" => FormatText::Time, - "D" => FormatText::TimeMillis, - _ => FormatText::Str(m.as_str().to_owned()), - } - ); + results.push(match m.as_str() { + "%" => FormatText::Percent, + "a" => FormatText::RemoteAddr, + "t" => FormatText::RequestTime, + "P" => FormatText::Pid, + "r" => FormatText::RequestLine, + "s" => FormatText::ResponseStatus, + "b" => FormatText::ResponseSize, + "T" => FormatText::Time, + "D" => FormatText::TimeMillis, + _ => FormatText::Str(m.as_str().to_owned()), + }); } } if idx != s.len() { @@ -207,12 +208,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), FormatText::Percent => "%".fmt(fmt), @@ -220,26 +219,33 @@ impl FormatText { if req.query_string().is_empty() { fmt.write_fmt(format_args!( "{} {} {:?}", - req.method(), req.path(), req.version())) + req.method(), + req.path(), + req.version() + )) } else { fmt.write_fmt(format_args!( "{} {}?{} {:?}", - req.method(), req.path(), req.query_string(), req.version())) + req.method(), + req.path(), + req.query_string(), + req.version() + )) } - }, + } FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt), FormatText::ResponseSize => resp.response_size().fmt(fmt), - FormatText::Pid => unsafe{libc::getpid().fmt(fmt)}, + FormatText::Pid => unsafe { libc::getpid().fmt(fmt) }, FormatText::Time => { let rt = time::now() - entry_time; let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0; fmt.write_fmt(format_args!("{:.6}", rt)) - }, + } FormatText::TimeMillis => { let rt = time::now() - entry_time; let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0; fmt.write_fmt(format_args!("{:.6}", rt)) - }, + } FormatText::RemoteAddr => { if let Some(remote) = req.connection_info().remote() { return remote.fmt(fmt); @@ -247,14 +253,17 @@ impl FormatText { "-".fmt(fmt) } } - FormatText::RequestTime => { - entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]") - .unwrap() - .fmt(fmt) - } + FormatText::RequestTime => entry_time + .strftime("[%d/%b/%Y:%H:%M:%S %z]") + .unwrap() + .fmt(fmt), FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { - if let Ok(s) = val.to_str() { s } else { "-" } + if let Ok(s) = val.to_str() { + s + } else { + "-" + } } else { "-" }; @@ -262,7 +271,11 @@ impl FormatText { } FormatText::ResponseHeader(ref name) => { let s = if let Some(val) = resp.headers().get(name) { - if let Ok(s) = val.to_str() { s } else { "-" } + if let Ok(s) = val.to_str() { + s + } else { + "-" + } } else { "-" }; @@ -279,8 +292,7 @@ impl FormatText { } } -pub(crate) struct FormatDisplay<'a>( - &'a Fn(&mut Formatter) -> Result<(), fmt::Error>); +pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>); impl<'a> fmt::Display for FormatDisplay<'a> { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { @@ -291,19 +303,27 @@ impl<'a> fmt::Display for FormatDisplay<'a> { #[cfg(test)] mod tests { use super::*; + use http::header::{self, HeaderMap}; + use http::{Method, StatusCode, Uri, Version}; use std::str::FromStr; use time; - use http::{Method, Version, StatusCode, Uri}; - use http::header::{self, HeaderMap}; #[test] fn test_logger() { let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); let mut headers = HeaderMap::new(); - headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); + 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, None); + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); let resp = HttpResponse::build(StatusCode::OK) .header("X-Test", "ttt") .force_close() @@ -333,10 +353,20 @@ mod tests { let format = Format::default(); let mut headers = HeaderMap::new(); - headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); + 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, None); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); + let resp = HttpResponse::build(StatusCode::OK) + .force_close() + .finish(); let entry_time = time::now(); let render = |fmt: &mut Formatter| { @@ -351,9 +381,15 @@ mod tests { assert!(s.contains("ACTIX-WEB")); let req = HttpRequest::new( - Method::GET, Uri::from_str("/?test").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + Method::GET, + Uri::from_str("/?test").unwrap(), + Version::HTTP_11, + HeaderMap::new(), + None, + ); + let resp = HttpResponse::build(StatusCode::OK) + .force_close() + .finish(); let entry_time = time::now(); let render = |fmt: &mut Formatter| { diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 49d4d706..d41660ee 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -7,19 +7,19 @@ use httpresponse::HttpResponse; mod logger; -#[cfg(feature = "session")] -mod session; -mod defaultheaders; -mod errhandlers; pub mod cors; pub mod csrf; -pub use self::logger::Logger; -pub use self::errhandlers::ErrorHandlers; +mod defaultheaders; +mod errhandlers; +#[cfg(feature = "session")] +mod session; pub use self::defaultheaders::DefaultHeaders; +pub use self::errhandlers::ErrorHandlers; +pub use self::logger::Logger; #[cfg(feature = "session")] -pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage, - CookieSessionError, CookieSessionBackend}; +pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession, + Session, SessionBackend, SessionImpl, SessionStorage}; /// Middleware start result pub enum Started { @@ -29,7 +29,7 @@ pub enum Started { /// handler execution halts. Response(HttpResponse), /// Execution completed, runs future to completion. - Future(Box, Error=Error>>), + Future(Box, Error = Error>>), } /// Middleware execution result @@ -37,7 +37,7 @@ pub enum Response { /// New http response got generated Done(HttpResponse), /// Result is a future that resolves to a new http response - Future(Box>), + Future(Box>), } /// Middleware finish result @@ -45,13 +45,12 @@ pub enum Finished { /// Execution completed Done, /// Execution completed, but run future to completion - Future(Box>), + Future(Box>), } /// Middleware definition #[allow(unused_variables)] pub trait Middleware: 'static { - /// 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) -> Result { @@ -60,7 +59,9 @@ pub trait Middleware: 'static { /// Method is called when handler returns response, /// but before sending http message to peer. - fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Result { + fn response( + &self, req: &mut HttpRequest, resp: HttpResponse + ) -> Result { Ok(Response::Done(resp)) } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 70b0aff6..a7ca8061 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -1,21 +1,21 @@ +use std::collections::HashMap; +use std::marker::PhantomData; use std::rc::Rc; use std::sync::Arc; -use std::marker::PhantomData; -use std::collections::HashMap; +use cookie::{Cookie, CookieJar, Key}; +use futures::Future; +use futures::future::{FutureResult, err as FutErr, ok as FutOk}; +use http::header::{self, HeaderValue}; +use serde::{Deserialize, Serialize}; use serde_json; use serde_json::error::Error as JsonError; -use serde::{Serialize, Deserialize}; -use http::header::{self, HeaderValue}; use time::Duration; -use cookie::{CookieJar, Cookie, Key}; -use futures::Future; -use futures::future::{FutureResult, ok as FutOk, err as FutErr}; -use error::{Result, Error, ResponseError}; +use error::{Error, ResponseError, Result}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middleware::{Middleware, Started, Response}; +use middleware::{Middleware, Response, Started}; /// The helper trait to obtain your session data from a request. /// @@ -40,14 +40,13 @@ pub trait RequestSession { } impl RequestSession for HttpRequest { - fn session(&mut self) -> Session { if let Some(s_impl) = self.extensions().get_mut::>() { if let Some(s) = Arc::get_mut(s_impl) { - return Session(s.0.as_mut()) + return Session(s.0.as_mut()); } } - Session(unsafe{&mut DUMMY}) + Session(unsafe { &mut DUMMY }) } } @@ -76,7 +75,6 @@ impl RequestSession for HttpRequest { pub struct Session<'a>(&'a mut SessionImpl); impl<'a> Session<'a> { - /// Get a `value` from the session. pub fn get>(&'a self, key: &str) -> Result> { if let Some(s) = self.0.get(key) { @@ -136,24 +134,25 @@ impl> SessionStorage { } impl> Middleware for SessionStorage { - fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); - let fut = self.0.from_request(&mut req) - .then(move |res| { - match res { - Ok(sess) => { - req.extensions().insert(Arc::new(SessionImplBox(Box::new(sess)))); - FutOk(None) - }, - Err(err) => FutErr(err) + let fut = self.0 + .from_request(&mut req) + .then(move |res| match res { + Ok(sess) => { + req.extensions() + .insert(Arc::new(SessionImplBox(Box::new(sess)))); + FutOk(None) } + Err(err) => FutErr(err), }); Ok(Started::Future(Box::new(fut))) } - fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Result { + fn response( + &self, req: &mut HttpRequest, resp: HttpResponse + ) -> Result { if let Some(s_box) = req.extensions().remove::>() { s_box.0.write(resp) } else { @@ -165,7 +164,6 @@ impl> Middleware for SessionStorage { /// A simple key-value storage interface that is internally used by `Session`. #[doc(hidden)] pub trait SessionImpl: 'static { - fn get(&self, key: &str) -> Option<&str>; fn set(&mut self, key: &str, value: String); @@ -182,7 +180,7 @@ pub trait SessionImpl: 'static { #[doc(hidden)] pub trait SessionBackend: Sized + 'static { type Session: SessionImpl; - type ReadFuture: Future; + type ReadFuture: Future; /// Parse the session from request and load data from a storage backend. fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; @@ -194,8 +192,9 @@ struct DummySessionImpl; static mut DUMMY: DummySessionImpl = DummySessionImpl; impl SessionImpl for DummySessionImpl { - - fn get(&self, _: &str) -> Option<&str> { None } + fn get(&self, _: &str) -> Option<&str> { + None + } fn set(&mut self, _: &str, _: String) {} fn remove(&mut self, _: &str) {} fn clear(&mut self) {} @@ -215,17 +214,16 @@ pub struct CookieSession { #[derive(Fail, Debug)] pub enum CookieSessionError { /// Size of the serialized session is greater than 4000 bytes. - #[fail(display="Size of the serialized session is greater than 4000 bytes.")] + #[fail(display = "Size of the serialized session is greater than 4000 bytes.")] Overflow, /// Fail to serialize session. - #[fail(display="Fail to serialize session")] + #[fail(display = "Fail to serialize session")] Serialize(JsonError), } impl ResponseError for CookieSessionError {} impl SessionImpl for CookieSession { - fn get(&self, key: &str) -> Option<&str> { if let Some(s) = self.state.get(key) { Some(s) @@ -259,7 +257,7 @@ impl SessionImpl for CookieSession { enum CookieSecurity { Signed, - Private + Private, } struct CookieSessionInner { @@ -273,7 +271,6 @@ struct CookieSessionInner { } impl CookieSessionInner { - fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { CookieSessionInner { security, @@ -286,11 +283,13 @@ impl CookieSessionInner { } } - fn set_cookie(&self, resp: &mut HttpResponse, state: &HashMap) -> Result<()> { - let value = serde_json::to_string(&state) - .map_err(CookieSessionError::Serialize)?; + fn set_cookie( + &self, resp: &mut HttpResponse, state: &HashMap + ) -> Result<()> { + let value = + serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; if value.len() > 4064 { - return Err(CookieSessionError::Overflow.into()) + return Err(CookieSessionError::Overflow.into()); } let mut cookie = Cookie::new(self.name.clone(), value); @@ -330,7 +329,9 @@ impl CookieSessionInner { let cookie_opt = match self.security { CookieSecurity::Signed => jar.signed(&self.key).get(&self.name), - CookieSecurity::Private => jar.private(&self.key).get(&self.name), + CookieSecurity::Private => { + jar.private(&self.key).get(&self.name) + } }; if let Some(cookie) = cookie_opt { if let Ok(val) = serde_json::from_str(cookie.value()) { @@ -347,20 +348,24 @@ impl CookieSessionInner { /// Use cookies for session storage. /// /// `CookieSessionBackend` creates sessions which are limited to storing -/// fewer than 4000 bytes of data (as the payload must fit into a single cookie). -/// An Internal Server Error is generated if the session contains more than 4000 bytes. +/// fewer than 4000 bytes of data (as the payload must fit into a single +/// cookie). An Internal Server Error is generated if the session contains more +/// than 4000 bytes. /// -/// A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor. +/// A cookie may have a security policy of *signed* or *private*. Each has a +/// respective `CookieSessionBackend` constructor. /// /// A *signed* cookie is stored on the client as plaintext alongside -/// a signature such that the cookie may be viewed but not modified by the client. +/// a signature such that the cookie may be viewed but not modified by the +/// client. /// /// A *private* cookie is stored on the client as encrypted text /// such that it may neither be viewed nor modified by the client. /// /// The constructors take a key as an argument. -/// This is the private key for cookie session - when this value is changed, all session data is lost. -/// The constructors will panic if the key is less than 32 bytes in length. +/// This is the private key for cookie session - when this value is changed, +/// all session data is lost. The constructors will panic if the key is less +/// than 32 bytes in length. /// /// /// # Example @@ -380,21 +385,24 @@ impl CookieSessionInner { pub struct CookieSessionBackend(Rc); impl CookieSessionBackend { - /// Construct new *signed* `CookieSessionBackend` instance. /// /// Panics if key length is less than 32 bytes. pub fn signed(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend( - Rc::new(CookieSessionInner::new(key, CookieSecurity::Signed))) + CookieSessionBackend(Rc::new(CookieSessionInner::new( + key, + CookieSecurity::Signed, + ))) } /// Construct new *private* `CookieSessionBackend` instance. /// /// Panics if key length is less than 32 bytes. pub fn private(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend( - Rc::new(CookieSessionInner::new(key, CookieSecurity::Private))) + CookieSessionBackend(Rc::new(CookieSessionInner::new( + key, + CookieSecurity::Private, + ))) } /// Sets the `path` field in the session cookie being built. @@ -432,17 +440,15 @@ impl CookieSessionBackend { } impl SessionBackend for CookieSessionBackend { - type Session = CookieSession; type ReadFuture = FutureResult; fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { let state = self.0.load(req); - FutOk( - CookieSession { - changed: false, - inner: Rc::clone(&self.0), - state, - }) + FutOk(CookieSession { + changed: false, + inner: Rc::clone(&self.0), + state, + }) } } diff --git a/src/multipart.rs b/src/multipart.rs index a8d0c6e7..87d4b1ad 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -1,18 +1,18 @@ //! Multipart requests support -use std::{cmp, fmt}; -use std::rc::Rc; use std::cell::RefCell; use std::marker::PhantomData; +use std::rc::Rc; +use std::{cmp, fmt}; -use mime; -use httparse; use bytes::Bytes; +use futures::task::{current as current_task, Task}; +use futures::{Async, Poll, Stream}; use http::HttpTryFrom; use http::header::{self, HeaderMap, HeaderName, HeaderValue}; -use futures::{Async, Stream, Poll}; -use futures::task::{Task, current as current_task}; +use httparse; +use mime; -use error::{ParseError, PayloadError, MultipartError}; +use error::{MultipartError, ParseError, PayloadError}; use payload::PayloadHelper; const MAX_HEADERS: usize = 32; @@ -85,33 +85,36 @@ impl Multipart<()> { } } -impl Multipart where S: Stream { - +impl Multipart +where + S: Stream, +{ /// Create multipart instance for boundary. pub fn new(boundary: Result, stream: S) -> Multipart { match boundary { Ok(boundary) => Multipart { error: None, safety: Safety::new(), - inner: Some(Rc::new(RefCell::new( - InnerMultipart { - boundary, - payload: PayloadRef::new(PayloadHelper::new(stream)), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - }))) + inner: Some(Rc::new(RefCell::new(InnerMultipart { + boundary, + payload: PayloadRef::new(PayloadHelper::new(stream)), + state: InnerState::FirstBoundary, + item: InnerMultipartItem::None, + }))), + }, + Err(err) => Multipart { + error: Some(err), + safety: Safety::new(), + inner: None, }, - Err(err) => - Multipart { - error: Some(err), - safety: Safety::new(), - inner: None, - } } } } -impl Stream for Multipart where S: Stream { +impl Stream for Multipart +where + S: Stream, +{ type Item = MultipartItem; type Error = MultipartError; @@ -119,17 +122,22 @@ impl Stream for Multipart where S: Stream if let Some(err) = self.error.take() { Err(err) } else if self.safety.current() { - self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) + self.inner + .as_mut() + .unwrap() + .borrow_mut() + .poll(&self.safety) } else { Ok(Async::NotReady) } } } -impl InnerMultipart where S: Stream { - - fn read_headers(payload: &mut PayloadHelper) -> Poll - { +impl InnerMultipart +where + S: Stream, +{ + fn read_headers(payload: &mut PayloadHelper) -> Poll { match payload.read_until(b"\r\n\r\n")? { Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), @@ -144,10 +152,10 @@ impl InnerMultipart where S: Stream { if let Ok(value) = HeaderValue::try_from(h.value) { headers.append(name, value); } else { - return Err(ParseError::Header.into()) + return Err(ParseError::Header.into()); } } else { - return Err(ParseError::Header.into()) + return Err(ParseError::Header.into()); } } Ok(Async::Ready(headers)) @@ -159,23 +167,21 @@ impl InnerMultipart where S: Stream { } } - fn read_boundary(payload: &mut PayloadHelper, boundary: &str) - -> Poll - { + fn read_boundary( + payload: &mut PayloadHelper, boundary: &str + ) -> Poll { // TODO: need to read epilogue match payload.readline()? { Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(Some(chunk)) => { - if chunk.len() == boundary.len() + 4 && - &chunk[..2] == b"--" && - &chunk[2..boundary.len()+2] == boundary.as_bytes() + if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--" + && &chunk[2..boundary.len() + 2] == boundary.as_bytes() { Ok(Async::Ready(false)) - } else if chunk.len() == boundary.len() + 6 && - &chunk[..2] == b"--" && - &chunk[2..boundary.len()+2] == boundary.as_bytes() && - &chunk[boundary.len()+2..boundary.len()+4] == b"--" + } else if chunk.len() == boundary.len() + 6 && &chunk[..2] == b"--" + && &chunk[2..boundary.len() + 2] == boundary.as_bytes() + && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" { Ok(Async::Ready(true)) } else { @@ -185,9 +191,9 @@ impl InnerMultipart where S: Stream { } } - fn skip_until_boundary(payload: &mut PayloadHelper, boundary: &str) - -> Poll - { + fn skip_until_boundary( + payload: &mut PayloadHelper, boundary: &str + ) -> Poll { let mut eof = false; loop { match payload.readline()? { @@ -197,22 +203,25 @@ impl InnerMultipart where S: Stream { //% (self._boundary)) } if chunk.len() < boundary.len() { - continue + continue; } - if &chunk[..2] == b"--" && &chunk[2..chunk.len()-2] == boundary.as_bytes() { + if &chunk[..2] == b"--" + && &chunk[2..chunk.len() - 2] == boundary.as_bytes() + { break; } else { - if chunk.len() < boundary.len() + 2{ - continue + if chunk.len() < boundary.len() + 2 { + continue; } let b: &[u8] = boundary.as_ref(); - if &chunk[..boundary.len()] == b && - &chunk[boundary.len()..boundary.len()+2] == b"--" { - eof = true; - break; - } + if &chunk[..boundary.len()] == b + && &chunk[boundary.len()..boundary.len() + 2] == b"--" + { + eof = true; + break; + } } - }, + } Async::NotReady => return Ok(Async::NotReady), Async::Ready(None) => return Err(MultipartError::Incomplete), } @@ -220,7 +229,9 @@ impl InnerMultipart where S: Stream { Ok(Async::Ready(eof)) } - fn poll(&mut self, safety: &Safety) -> Poll>, MultipartError> { + fn poll( + &mut self, safety: &Safety + ) -> Poll>, MultipartError> { if self.state == InnerState::Eof { Ok(Async::Ready(None)) } else { @@ -236,14 +247,14 @@ impl InnerMultipart where S: Stream { Async::Ready(Some(_)) => continue, Async::Ready(None) => true, } - }, + } InnerMultipartItem::Multipart(ref mut multipart) => { match multipart.borrow_mut().poll(safety)? { Async::NotReady => return Ok(Async::NotReady), Async::Ready(Some(_)) => continue, Async::Ready(None) => true, } - }, + } _ => false, }; if stop { @@ -259,7 +270,10 @@ impl InnerMultipart where S: Stream { match self.state { // read until first boundary InnerState::FirstBoundary => { - match InnerMultipart::skip_until_boundary(payload, &self.boundary)? { + match InnerMultipart::skip_until_boundary( + payload, + &self.boundary, + )? { Async::Ready(eof) => { if eof { self.state = InnerState::Eof; @@ -267,10 +281,10 @@ impl InnerMultipart where S: Stream { } else { self.state = InnerState::Headers; } - }, + } Async::NotReady => return Ok(Async::NotReady), } - }, + } // read boundary InnerState::Boundary => { match InnerMultipart::read_boundary(payload, &self.boundary)? { @@ -290,18 +304,19 @@ impl InnerMultipart where S: Stream { // read field headers for next field if self.state == InnerState::Headers { - if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? { + if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? + { self.state = InnerState::Boundary; headers } else { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } } else { unreachable!() } } else { debug!("NotReady: field is in flight"); - return Ok(Async::NotReady) + return Ok(Async::NotReady); }; // content type @@ -319,32 +334,37 @@ impl InnerMultipart where S: Stream { // nested multipart stream if mt.type_() == mime::MULTIPART { let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) { - Rc::new(RefCell::new( - InnerMultipart { - payload: self.payload.clone(), - boundary: boundary.as_str().to_owned(), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - })) + Rc::new(RefCell::new(InnerMultipart { + payload: self.payload.clone(), + boundary: boundary.as_str().to_owned(), + state: InnerState::FirstBoundary, + item: InnerMultipartItem::None, + })) } else { - return Err(MultipartError::Boundary) + return Err(MultipartError::Boundary); }; self.item = InnerMultipartItem::Multipart(Rc::clone(&inner)); - Ok(Async::Ready(Some( - MultipartItem::Nested( - Multipart{safety: safety.clone(), - error: None, - inner: Some(inner)})))) + Ok(Async::Ready(Some(MultipartItem::Nested(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)?)); + self.payload.clone(), + self.boundary.clone(), + &headers, + )?)); self.item = InnerMultipartItem::Field(Rc::clone(&field)); - Ok(Async::Ready(Some( - MultipartItem::Field( - Field::new(safety.clone(), headers, mt, field))))) + Ok(Async::Ready(Some(MultipartItem::Field(Field::new( + safety.clone(), + headers, + mt, + field, + ))))) } } } @@ -365,11 +385,20 @@ pub struct Field { safety: Safety, } -impl Field where S: Stream { - - fn new(safety: Safety, headers: HeaderMap, - ct: mime::Mime, inner: Rc>>) -> Self { - Field {ct, headers, inner, safety} +impl Field +where + S: Stream, +{ + fn new( + safety: Safety, headers: HeaderMap, ct: mime::Mime, + inner: Rc>>, + ) -> Self { + Field { + ct, + headers, + inner, + safety, + } } pub fn headers(&self) -> &HeaderMap { @@ -381,7 +410,10 @@ impl Field where S: Stream { } } -impl Stream for Field where S: Stream { +impl Stream for Field +where + S: Stream, +{ type Item = Bytes; type Error = MultipartError; @@ -413,20 +445,22 @@ struct InnerField { length: Option, } -impl InnerField where S: Stream { - - fn new(payload: PayloadRef, boundary: String, headers: &HeaderMap) - -> Result, PayloadError> - { +impl InnerField +where + S: Stream, +{ + fn new( + payload: PayloadRef, boundary: String, headers: &HeaderMap + ) -> Result, PayloadError> { let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { Some(len) } else { - return Err(PayloadError::Incomplete) + return Err(PayloadError::Incomplete); } } else { - return Err(PayloadError::Incomplete) + return Err(PayloadError::Incomplete); } } else { None @@ -436,14 +470,15 @@ impl InnerField where S: Stream { boundary, payload: Some(payload), eof: false, - length: len }) + length: len, + }) } /// Reads body part content chunk of the specified size. /// The body part must has `Content-Length` header with proper value. - fn read_len(payload: &mut PayloadHelper, size: &mut u64) - -> Poll, MultipartError> - { + fn read_len( + payload: &mut PayloadHelper, size: &mut u64 + ) -> Poll, MultipartError> { if *size == 0 { Ok(Async::Ready(None)) } else { @@ -458,17 +493,17 @@ impl InnerField where S: Stream { payload.unread_data(chunk); } Ok(Async::Ready(Some(ch))) - }, - Err(err) => Err(err.into()) + } + Err(err) => Err(err.into()), } } } /// Reads content chunk of body part with unknown length. /// The `Content-Length` header for body part is not necessary. - fn read_stream(payload: &mut PayloadHelper, boundary: &str) - -> Poll, MultipartError> - { + fn read_stream( + payload: &mut PayloadHelper, boundary: &str + ) -> Poll, MultipartError> { match payload.read_until(b"\r")? { Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), @@ -479,8 +514,8 @@ impl InnerField where S: Stream { Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(Some(chunk)) => { - if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && - &chunk[4..] == boundary.as_bytes() + if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" + && &chunk[4..] == boundary.as_bytes() { payload.unread_data(chunk); Ok(Async::Ready(None)) @@ -501,7 +536,7 @@ impl InnerField where S: Stream { fn poll(&mut self, s: &Safety) -> Poll, MultipartError> { if self.payload.is_none() { - return Ok(Async::Ready(None)) + return Ok(Async::Ready(None)); } let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { @@ -543,7 +578,10 @@ struct PayloadRef { payload: Rc>, } -impl PayloadRef where S: Stream { +impl PayloadRef +where + S: Stream, +{ fn new(payload: PayloadHelper) -> PayloadRef { PayloadRef { payload: Rc::new(payload), @@ -551,11 +589,12 @@ impl PayloadRef where S: Stream { } fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadHelper> - where 'a: 'b + where + 'a: 'b, { if s.current() { - let payload: &mut PayloadHelper = unsafe { - &mut *(self.payload.as_ref() as *const _ as *mut _)}; + let payload: &mut PayloadHelper = + unsafe { &mut *(self.payload.as_ref() as *const _ as *mut _) }; Some(payload) } else { None @@ -571,8 +610,9 @@ impl Clone for PayloadRef { } } -/// Counter. It tracks of number of clones of payloads and give access to payload only -/// to top most task panics if Safety get destroyed and it not top most task. +/// Counter. It tracks of number of clones of payloads and give access to +/// payload only to top most task panics if Safety get destroyed and it not top +/// most task. #[derive(Debug)] struct Safety { task: Option, @@ -593,7 +633,6 @@ impl Safety { fn current(&self) -> bool { Rc::strong_count(&self.payload) == self.level } - } impl Clone for Safety { @@ -624,8 +663,8 @@ mod tests { use super::*; use bytes::Bytes; use futures::future::{lazy, result}; - use tokio_core::reactor::Core; use payload::{Payload, PayloadWriter}; + use tokio_core::reactor::Core; #[test] fn test_boundary() { @@ -636,8 +675,10 @@ mod tests { } let mut headers = HeaderMap::new(); - headers.insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("test")); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("test"), + ); match Multipart::boundary(&headers) { Err(MultipartError::ParseContentType) => (), @@ -647,7 +688,8 @@ mod tests { let mut headers = HeaderMap::new(); headers.insert( header::CONTENT_TYPE, - header::HeaderValue::from_static("multipart/mixed")); + header::HeaderValue::from_static("multipart/mixed"), + ); match Multipart::boundary(&headers) { Err(MultipartError::Boundary) => (), _ => unreachable!("should not happen"), @@ -657,18 +699,24 @@ mod tests { headers.insert( header::CONTENT_TYPE, header::HeaderValue::from_static( - "multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"")); + "multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"", + ), + ); - assert_eq!(Multipart::boundary(&headers).unwrap(), - "5c02368e880e436dab70ed54e1c58209"); + assert_eq!( + Multipart::boundary(&headers).unwrap(), + "5c02368e880e436dab70ed54e1c58209" + ); } #[test] fn test_multipart() { - Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); + Core::new() + .unwrap() + .run(lazy(|| { + let (mut sender, payload) = Payload::new(false); - let bytes = Bytes::from( + let bytes = Bytes::from( "testasdadsad\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ @@ -677,63 +725,64 @@ mod tests { Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ data\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n"); - sender.feed_data(bytes); + sender.feed_data(bytes); - let mut multipart = Multipart::new( - Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()), payload); - match multipart.poll() { - Ok(Async::Ready(Some(item))) => { - match item { + let mut multipart = Multipart::new( + Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()), + payload, + ); + match multipart.poll() { + Ok(Async::Ready(Some(item))) => match item { MultipartItem::Field(mut field) => { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); match field.poll() { - Ok(Async::Ready(Some(chunk))) => - assert_eq!(chunk, "test"), - _ => unreachable!() + Ok(Async::Ready(Some(chunk))) => { + assert_eq!(chunk, "test") + } + _ => unreachable!(), } match field.poll() { Ok(Async::Ready(None)) => (), - _ => unreachable!() + _ => unreachable!(), } - }, - _ => unreachable!() - } + } + _ => unreachable!(), + }, + _ => unreachable!(), } - _ => unreachable!() - } - match multipart.poll() { - Ok(Async::Ready(Some(item))) => { - match item { + match multipart.poll() { + Ok(Async::Ready(Some(item))) => match item { MultipartItem::Field(mut field) => { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); match field.poll() { - Ok(Async::Ready(Some(chunk))) => - assert_eq!(chunk, "data"), - _ => unreachable!() + Ok(Async::Ready(Some(chunk))) => { + assert_eq!(chunk, "data") + } + _ => unreachable!(), } match field.poll() { Ok(Async::Ready(None)) => (), - _ => unreachable!() + _ => unreachable!(), } - }, - _ => unreachable!() - } + } + _ => unreachable!(), + }, + _ => unreachable!(), } - _ => unreachable!() - } - match multipart.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!() - } + match multipart.poll() { + Ok(Async::Ready(None)) => (), + _ => unreachable!(), + } - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); + let res: Result<(), ()> = Ok(()); + result(res) + })) + .unwrap(); } } diff --git a/src/param.rs b/src/param.rs index b3476ae5..41100763 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,16 +1,16 @@ -use std; -use std::ops::Index; -use std::path::PathBuf; -use std::str::FromStr; -use std::slice::Iter; -use std::borrow::Cow; use http::StatusCode; use smallvec::SmallVec; +use std; +use std::borrow::Cow; +use std::ops::Index; +use std::path::PathBuf; +use std::slice::Iter; +use std::str::FromStr; -use error::{ResponseError, UriSegmentError, InternalError}; +use error::{InternalError, ResponseError, UriSegmentError}; - -/// A trait to abstract the idea of creating a new instance of a type from a path parameter. +/// 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; @@ -26,7 +26,6 @@ pub trait FromParam: Sized { pub struct Params<'a>(SmallVec<[(Cow<'a, str>, Cow<'a, str>); 3]>); impl<'a> Params<'a> { - pub(crate) fn new() -> Params<'a> { Params(SmallVec::new()) } @@ -36,7 +35,9 @@ impl<'a> Params<'a> { } pub(crate) fn add(&mut self, name: N, value: V) - where N: Into>, V: Into>, + where + N: Into>, + V: Into>, { self.0.push((name.into(), value.into())); } @@ -55,7 +56,7 @@ impl<'a> Params<'a> { pub fn get(&'a self, key: &str) -> Option<&'a str> { for item in self.0.iter() { if key == item.0 { - return Some(item.1.as_ref()) + return Some(item.1.as_ref()); } } None @@ -63,7 +64,8 @@ impl<'a> Params<'a> { /// Get matched `FromParam` compatible parameter by name. /// - /// If keyed parameter is not available empty string is used as default value. + /// If keyed parameter is not available empty string is used as default + /// value. /// /// ```rust /// # extern crate actix_web; @@ -74,8 +76,7 @@ impl<'a> Params<'a> { /// } /// # fn main() {} /// ``` - pub fn query(&'a 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) } else { @@ -93,7 +94,8 @@ impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> { type Output = str; fn index(&self, name: &'b str) -> &str { - self.get(name).expect("Value for parameter is not available") + self.get(name) + .expect("Value for parameter is not available") } } @@ -118,9 +120,9 @@ impl<'a, 'c: 'a> Index for &'c Params<'a> { /// * 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. +/// 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; @@ -130,19 +132,19 @@ impl FromParam for PathBuf { if segment == ".." { buf.pop(); } else if segment.starts_with('.') { - return Err(UriSegmentError::BadStart('.')) + return Err(UriSegmentError::BadStart('.')); } else if segment.starts_with('*') { - return Err(UriSegmentError::BadStart('*')) + return Err(UriSegmentError::BadStart('*')); } else if segment.ends_with(':') { - return Err(UriSegmentError::BadEnd(':')) + return Err(UriSegmentError::BadEnd(':')); } else if segment.ends_with('>') { - return Err(UriSegmentError::BadEnd('>')) + return Err(UriSegmentError::BadEnd('>')); } else if segment.ends_with('<') { - return Err(UriSegmentError::BadEnd('<')) + return Err(UriSegmentError::BadEnd('<')); } else if segment.is_empty() { - continue + continue; } else if cfg!(windows) && segment.contains('\\') { - return Err(UriSegmentError::BadChar('\\')) + return Err(UriSegmentError::BadChar('\\')); } else { buf.push(segment) } @@ -162,7 +164,7 @@ macro_rules! FROM_STR { .map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST)) } } - } + }; } FROM_STR!(u8); @@ -192,14 +194,33 @@ mod tests { #[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"]))); + 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/payload.rs b/src/payload.rs index 8afff81c..a394c106 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -1,18 +1,17 @@ //! Payload stream -use std::cmp; -use std::rc::{Rc, Weak}; -use std::cell::RefCell; -use std::collections::VecDeque; use bytes::{Bytes, BytesMut}; +use futures::task::{current as current_task, Task}; use futures::{Async, Poll, Stream}; -use futures::task::{Task, current as current_task}; +use std::cell::RefCell; +use std::cmp; +use std::collections::VecDeque; +use std::rc::{Rc, Weak}; use error::PayloadError; /// max buffer size 32k pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; - #[derive(Debug, PartialEq)] pub(crate) enum PayloadStatus { Read, @@ -22,9 +21,9 @@ pub(crate) enum PayloadStatus { /// Buffered stream of bytes chunks /// -/// Payload stores chunks in a vector. First chunk can be received with `.readany()` method. -/// Payload stream is not thread safe. Payload does not notify current task when -/// new data is available. +/// Payload stores chunks in a vector. First chunk can be received with +/// `.readany()` method. Payload stream is not thread safe. Payload does not +/// notify current task when new data is available. /// /// Payload stream can be used as `HttpResponse` body stream. #[derive(Debug)] @@ -33,10 +32,10 @@ pub struct Payload { } impl Payload { - /// Create payload stream. /// - /// This method construct two objects responsible for bytes stream generation. + /// This method construct two objects responsible for bytes stream + /// generation. /// /// * `PayloadSender` - *Sender* side of the stream /// @@ -44,13 +43,20 @@ impl Payload { pub fn new(eof: bool) -> (PayloadSender, Payload) { let shared = Rc::new(RefCell::new(Inner::new(eof))); - (PayloadSender{inner: Rc::downgrade(&shared)}, Payload{inner: shared}) + ( + PayloadSender { + inner: Rc::downgrade(&shared), + }, + Payload { inner: shared }, + ) } /// Create empty payload #[doc(hidden)] pub fn empty() -> Payload { - Payload{inner: Rc::new(RefCell::new(Inner::new(true)))} + Payload { + inner: Rc::new(RefCell::new(Inner::new(true))), + } } /// Indicates EOF of payload @@ -103,13 +109,14 @@ impl Stream for Payload { impl Clone for Payload { fn clone(&self) -> Payload { - Payload{inner: Rc::clone(&self.inner)} + Payload { + inner: Rc::clone(&self.inner), + } } } /// Payload writer interface. pub(crate) trait PayloadWriter { - /// Set stream error. fn set_error(&mut self, err: PayloadError); @@ -129,7 +136,6 @@ pub struct PayloadSender { } impl PayloadWriter for PayloadSender { - #[inline] fn set_error(&mut self, err: PayloadError) { if let Some(shared) = self.inner.upgrade() { @@ -186,7 +192,6 @@ struct Inner { } impl Inner { - fn new(eof: bool) -> Self { Inner { eof, @@ -292,8 +297,10 @@ pub struct PayloadHelper { stream: S, } -impl PayloadHelper where S: Stream { - +impl PayloadHelper +where + S: Stream, +{ pub fn new(stream: S) -> Self { PayloadHelper { len: 0, @@ -309,16 +316,14 @@ impl PayloadHelper where S: Stream { #[inline] fn poll_stream(&mut self) -> Poll { - self.stream.poll().map(|res| { - match res { - Async::Ready(Some(data)) => { - self.len += data.len(); - self.items.push_back(data); - Async::Ready(true) - }, - Async::Ready(None) => Async::Ready(false), - Async::NotReady => Async::NotReady, + self.stream.poll().map(|res| match res { + Async::Ready(Some(data)) => { + self.len += data.len(); + self.items.push_back(data); + Async::Ready(true) } + Async::Ready(None) => Async::Ready(false), + Async::NotReady => Async::NotReady, }) } @@ -373,11 +378,9 @@ impl PayloadHelper where S: Stream { let buf = chunk.split_to(size); self.items.push_front(chunk); Ok(Async::Ready(Some(buf))) - } - else if size == chunk.len() { + } else if size == chunk.len() { Ok(Async::Ready(Some(chunk))) - } - else { + } else { let mut buf = BytesMut::with_capacity(size); buf.extend_from_slice(&chunk); @@ -408,7 +411,7 @@ impl PayloadHelper where S: Stream { let mut len = 0; while len < size { let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size-len, chunk.len()); + let rem = cmp::min(size - len, chunk.len()); len += rem; if rem < chunk.len() { chunk.split_to(rem); @@ -427,7 +430,7 @@ impl PayloadHelper where S: Stream { buf.extend_from_slice(&chunk[..rem]); } if buf.len() == size { - return Ok(Async::Ready(Some(buf))) + return Ok(Async::Ready(Some(buf))); } } } @@ -454,8 +457,8 @@ impl PayloadHelper where S: Stream { idx += 1; if idx == line.len() { num = no; - offset = pos+1; - length += pos+1; + offset = pos + 1; + length += pos + 1; found = true; break; } @@ -483,7 +486,7 @@ impl PayloadHelper where S: Stream { } } self.len -= length; - return Ok(Async::Ready(Some(buf.freeze()))) + return Ok(Async::Ready(Some(buf.freeze()))); } } @@ -505,171 +508,217 @@ impl PayloadHelper where S: Stream { #[allow(dead_code)] pub fn remaining(&mut self) -> Bytes { - self.items.iter_mut() + self.items + .iter_mut() .fold(BytesMut::new(), |mut b, c| { b.extend_from_slice(c); b - }).freeze() + }) + .freeze() } } #[cfg(test)] mod tests { use super::*; - use std::io; use failure::Fail; use futures::future::{lazy, result}; + use std::io; use tokio_core::reactor::Core; #[test] fn test_error() { - let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into(); + let err: PayloadError = + io::Error::new(io::ErrorKind::Other, "ParseError").into(); assert_eq!(format!("{}", err), "ParseError"); assert_eq!(format!("{}", err.cause().unwrap()), "ParseError"); let err = PayloadError::Incomplete; - assert_eq!(format!("{}", err), "A payload reached EOF, but is not complete."); + assert_eq!( + format!("{}", err), + "A payload reached EOF, but is not complete." + ); } #[test] fn test_basic() { - Core::new().unwrap().run(lazy(|| { - let (_, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + Core::new() + .unwrap() + .run(lazy(|| { + let (_, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert_eq!(payload.len, 0); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + assert_eq!(payload.len, 0); + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); + let res: Result<(), ()> = Ok(()); + result(res) + })) + .unwrap(); } #[test] fn test_eof() { - Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + Core::new() + .unwrap() + .run(lazy(|| { + let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - sender.feed_data(Bytes::from("data")); - sender.feed_eof(); + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + sender.feed_data(Bytes::from("data")); + sender.feed_eof(); - assert_eq!(Async::Ready(Some(Bytes::from("data"))), - payload.readany().ok().unwrap()); - assert_eq!(payload.len, 0); - assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); + assert_eq!( + Async::Ready(Some(Bytes::from("data"))), + payload.readany().ok().unwrap() + ); + assert_eq!(payload.len, 0); + assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); + let res: Result<(), ()> = Ok(()); + result(res) + })) + .unwrap(); } #[test] fn test_err() { - Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + Core::new() + .unwrap() + .run(lazy(|| { + let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - sender.set_error(PayloadError::Incomplete); - payload.readany().err().unwrap(); - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); + sender.set_error(PayloadError::Incomplete); + payload.readany().err().unwrap(); + let res: Result<(), ()> = Ok(()); + result(res) + })) + .unwrap(); } #[test] fn test_readany() { - Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + Core::new() + .unwrap() + .run(lazy(|| { + let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); - assert_eq!(Async::Ready(Some(Bytes::from("line1"))), - payload.readany().ok().unwrap()); - assert_eq!(payload.len, 0); + assert_eq!( + Async::Ready(Some(Bytes::from("line1"))), + payload.readany().ok().unwrap() + ); + assert_eq!(payload.len, 0); - assert_eq!(Async::Ready(Some(Bytes::from("line2"))), - payload.readany().ok().unwrap()); - assert_eq!(payload.len, 0); + assert_eq!( + Async::Ready(Some(Bytes::from("line2"))), + payload.readany().ok().unwrap() + ); + assert_eq!(payload.len, 0); - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); + let res: Result<(), ()> = Ok(()); + result(res) + })) + .unwrap(); } #[test] fn test_readexactly() { - Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + Core::new() + .unwrap() + .run(lazy(|| { + let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); + assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); - assert_eq!(Async::Ready(Some(Bytes::from_static(b"li"))), - payload.read_exact(2).ok().unwrap()); - assert_eq!(payload.len, 3); + assert_eq!( + Async::Ready(Some(Bytes::from_static(b"li"))), + payload.read_exact(2).ok().unwrap() + ); + assert_eq!(payload.len, 3); - assert_eq!(Async::Ready(Some(Bytes::from_static(b"ne1l"))), - payload.read_exact(4).ok().unwrap()); - assert_eq!(payload.len, 4); + assert_eq!( + Async::Ready(Some(Bytes::from_static(b"ne1l"))), + payload.read_exact(4).ok().unwrap() + ); + assert_eq!(payload.len, 4); - sender.set_error(PayloadError::Incomplete); - payload.read_exact(10).err().unwrap(); + sender.set_error(PayloadError::Incomplete); + payload.read_exact(10).err().unwrap(); - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); + let res: Result<(), ()> = Ok(()); + result(res) + })) + .unwrap(); } #[test] fn test_readuntil() { - Core::new().unwrap().run(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + Core::new() + .unwrap() + .run(lazy(|| { + let (mut sender, payload) = Payload::new(false); + let mut payload = PayloadHelper::new(payload); - assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); + assert_eq!( + Async::NotReady, + payload.read_until(b"ne").ok().unwrap() + ); - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); - assert_eq!(Async::Ready(Some(Bytes::from("line"))), - payload.read_until(b"ne").ok().unwrap()); - assert_eq!(payload.len, 1); + assert_eq!( + Async::Ready(Some(Bytes::from("line"))), + payload.read_until(b"ne").ok().unwrap() + ); + assert_eq!(payload.len, 1); - assert_eq!(Async::Ready(Some(Bytes::from("1line2"))), - payload.read_until(b"2").ok().unwrap()); - assert_eq!(payload.len, 0); + assert_eq!( + Async::Ready(Some(Bytes::from("1line2"))), + payload.read_until(b"2").ok().unwrap() + ); + assert_eq!(payload.len, 0); - sender.set_error(PayloadError::Incomplete); - payload.read_until(b"b").err().unwrap(); + sender.set_error(PayloadError::Incomplete); + payload.read_until(b"b").err().unwrap(); - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); + let res: Result<(), ()> = Ok(()); + result(res) + })) + .unwrap(); } #[test] fn test_unread_data() { - Core::new().unwrap().run(lazy(|| { - let (_, mut payload) = Payload::new(false); + Core::new() + .unwrap() + .run(lazy(|| { + let (_, mut payload) = Payload::new(false); - payload.unread_data(Bytes::from("data")); - assert!(!payload.is_empty()); - assert_eq!(payload.len(), 4); + payload.unread_data(Bytes::from("data")); + assert!(!payload.is_empty()); + assert_eq!(payload.len(), 4); - assert_eq!(Async::Ready(Some(Bytes::from("data"))), - payload.poll().ok().unwrap()); + assert_eq!( + Async::Ready(Some(Bytes::from("data"))), + payload.poll().ok().unwrap() + ); - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); + let res: Result<(), ()> = Ok(()); + result(res) + })) + .unwrap(); } } diff --git a/src/pipeline.rs b/src/pipeline.rs index 842d519a..1e5685ba 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,22 +1,22 @@ -use std::{io, mem}; -use std::rc::Rc; use std::cell::UnsafeCell; use std::marker::PhantomData; +use std::rc::Rc; +use std::{io, mem}; -use log::Level::Debug; -use futures::{Async, Poll, Future, Stream}; use futures::unsync::oneshot; +use futures::{Async, Future, Poll, Stream}; +use log::Level::Debug; +use application::Inner; use body::{Body, BodyStream}; -use context::{Frame, ActorHttpContext}; +use context::{ActorHttpContext, Frame}; use error::Error; -use header::ContentEncoding; use handler::{Reply, ReplyItem}; +use header::ContentEncoding; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middleware::{Middleware, Finished, Started, Response}; -use application::Inner; -use server::{Writer, WriterState, HttpHandlerTask}; +use middleware::{Finished, Middleware, Response, Started}; +use server::{HttpHandlerTask, Writer, WriterState}; #[derive(Debug, Clone, Copy)] pub(crate) enum HandlerType { @@ -26,7 +26,6 @@ pub(crate) enum HandlerType { } pub(crate) trait PipelineHandler { - fn encoding(&self) -> ContentEncoding; fn handle(&mut self, req: HttpRequest, htype: HandlerType) -> Reply; @@ -46,7 +45,6 @@ enum PipelineState { } impl> PipelineState { - fn is_response(&self) -> bool { match *self { PipelineState::Response(_) => true, @@ -61,10 +59,12 @@ impl> PipelineState { PipelineState::RunMiddlewares(ref mut state) => state.poll(info), PipelineState::Finishing(ref mut state) => state.poll(info), PipelineState::Completed(ref mut state) => state.poll(info), - PipelineState::Response(_) | PipelineState::None | PipelineState::Error => None, + PipelineState::Response(_) | PipelineState::None | PipelineState::Error => { + None + } } } -} +} struct PipelineInfo { req: HttpRequest, @@ -92,7 +92,9 @@ impl PipelineInfo { #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] fn req_mut(&self) -> &mut HttpRequest { #[allow(mutable_transmutes)] - unsafe{mem::transmute(&self.req)} + unsafe { + mem::transmute(&self.req) + } } fn poll_context(&mut self) -> Poll<(), Error> { @@ -109,18 +111,18 @@ impl PipelineInfo { } impl> Pipeline { - - pub fn new(req: HttpRequest, - mws: Rc>>>, - handler: Rc>, htype: HandlerType) -> Pipeline - { + pub fn new( + req: HttpRequest, mws: Rc>>>, + handler: Rc>, htype: HandlerType, + ) -> Pipeline { let mut info = PipelineInfo { - req, mws, + req, + mws, count: 0, error: None, context: None, disconnected: None, - encoding: unsafe{&*handler.get()}.encoding(), + encoding: unsafe { &*handler.get() }.encoding(), }; let state = StartMiddlewares::init(&mut info, handler, htype); @@ -131,30 +133,33 @@ impl> Pipeline { impl Pipeline<(), Inner<()>> { pub fn error>(err: R) -> Box { Box::new(Pipeline::<(), Inner<()>>( - PipelineInfo::new(HttpRequest::default()), ProcessResponse::init(err.into()))) + 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::None + | PipelineState::Error + | PipelineState::Starting(_) + | PipelineState::Handler(_) + | PipelineState::RunMiddlewares(_) + | PipelineState::Response(_) => true, PipelineState::Finishing(_) | PipelineState::Completed(_) => false, } } } impl> HttpHandlerTask for Pipeline { - fn disconnected(&mut self) { self.0.disconnected = Some(true); } fn poll_io(&mut self, io: &mut Writer) -> Poll { - let info: &mut PipelineInfo<_> = unsafe{ mem::transmute(&mut self.0) }; + let info: &mut PipelineInfo<_> = unsafe { mem::transmute(&mut self.0) }; loop { if self.1.is_response() { @@ -164,9 +169,9 @@ impl> HttpHandlerTask for Pipeline { Ok(state) => { self.1 = state; if let Some(error) = self.0.error.take() { - return Err(error) + return Err(error); } else { - return Ok(Async::Ready(self.is_done())) + return Ok(Async::Ready(self.is_done())); } } Err(state) => { @@ -177,11 +182,10 @@ impl> HttpHandlerTask for Pipeline { } } match self.1 { - PipelineState::None => - return Ok(Async::Ready(true)), - PipelineState::Error => - return Err(io::Error::new( - io::ErrorKind::Other, "Internal error").into()), + PipelineState::None => return Ok(Async::Ready(true)), + PipelineState::Error => { + return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()) + } _ => (), } @@ -193,7 +197,7 @@ impl> HttpHandlerTask for Pipeline { } fn poll(&mut self) -> Poll<(), Error> { - let info: &mut PipelineInfo<_> = unsafe{ mem::transmute(&mut self.0) }; + let info: &mut PipelineInfo<_> = unsafe { mem::transmute(&mut self.0) }; loop { match self.1 { @@ -212,7 +216,7 @@ impl> HttpHandlerTask for Pipeline { } } -type Fut = Box, Error=Error>>; +type Fut = Box, Error = Error>>; /// Middlewares start executor struct StartMiddlewares { @@ -223,41 +227,40 @@ struct StartMiddlewares { } impl> StartMiddlewares { - - fn init(info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType) - -> PipelineState - { - // execute middlewares, we need this stage because middlewares could be non-async - // and we can move to next state immediately + fn init( + info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType + ) -> PipelineState { + // execute middlewares, we need this stage because middlewares could be + // non-async and we can move to next state immediately let len = info.mws.len() as u16; loop { if info.count == len { - let reply = unsafe{&mut *hnd.get()}.handle(info.req.clone(), htype); - return WaitingResponse::init(info, reply) + let reply = unsafe { &mut *hnd.get() }.handle(info.req.clone(), htype); + return WaitingResponse::init(info, reply); } else { match info.mws[info.count as usize].start(&mut info.req) { - Ok(Started::Done) => - info.count += 1, - Ok(Started::Response(resp)) => - return RunMiddlewares::init(info, resp), - Ok(Started::Future(mut fut)) => - match fut.poll() { - Ok(Async::NotReady) => - return PipelineState::Starting(StartMiddlewares { - hnd, htype, - fut: Some(fut), - _s: PhantomData}), - Ok(Async::Ready(resp)) => { - if let Some(resp) = resp { - return RunMiddlewares::init(info, resp); - } - info.count += 1; + Ok(Started::Done) => info.count += 1, + Ok(Started::Response(resp)) => { + return RunMiddlewares::init(info, resp) + } + Ok(Started::Future(mut fut)) => match fut.poll() { + Ok(Async::NotReady) => { + return PipelineState::Starting(StartMiddlewares { + hnd, + htype, + fut: Some(fut), + _s: PhantomData, + }) + } + Ok(Async::Ready(resp)) => { + if let Some(resp) = resp { + return RunMiddlewares::init(info, resp); } - Err(err) => - return ProcessResponse::init(err.into()), - }, - Err(err) => - return ProcessResponse::init(err.into()), + info.count += 1; + } + Err(err) => return ProcessResponse::init(err.into()), + }, + Err(err) => return ProcessResponse::init(err.into()), } } } @@ -274,29 +277,28 @@ impl> StartMiddlewares { return Some(RunMiddlewares::init(info, resp)); } if info.count == len { - let reply = unsafe{ - &mut *self.hnd.get()}.handle(info.req.clone(), self.htype); + let reply = unsafe { &mut *self.hnd.get() } + .handle(info.req.clone(), self.htype); return Some(WaitingResponse::init(info, reply)); } else { loop { match info.mws[info.count as usize].start(info.req_mut()) { - Ok(Started::Done) => - info.count += 1, + Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { return Some(RunMiddlewares::init(info, resp)); - }, + } Ok(Started::Future(fut)) => { self.fut = Some(fut); - continue 'outer - }, - Err(err) => + continue 'outer; + } + Err(err) => { return Some(ProcessResponse::init(err.into())) + } } } } } - Err(err) => - return Some(ProcessResponse::init(err.into())) + Err(err) => return Some(ProcessResponse::init(err.into())), } } } @@ -304,31 +306,29 @@ impl> StartMiddlewares { // waiting for response struct WaitingResponse { - fut: Box>, + fut: Box>, _s: PhantomData, _h: PhantomData, } impl WaitingResponse { - #[inline] fn init(info: &mut PipelineInfo, reply: Reply) -> PipelineState { match reply.into() { - ReplyItem::Message(resp) => - RunMiddlewares::init(info, resp), - ReplyItem::Future(fut) => - PipelineState::Handler( - WaitingResponse { fut, _s: PhantomData, _h: PhantomData }), + ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), + ReplyItem::Future(fut) => PipelineState::Handler(WaitingResponse { + fut, + _s: PhantomData, + _h: PhantomData, + }), } } fn poll(&mut self, info: &mut PipelineInfo) -> Option> { match self.fut.poll() { Ok(Async::NotReady) => None, - Ok(Async::Ready(response)) => - Some(RunMiddlewares::init(info, response)), - Err(err) => - Some(ProcessResponse::init(err.into())), + Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), + Err(err) => Some(ProcessResponse::init(err.into())), } } } @@ -336,13 +336,12 @@ impl WaitingResponse { /// Middlewares response executor struct RunMiddlewares { curr: usize, - fut: Option>>, + fut: Option>>, _s: PhantomData, _h: PhantomData, } impl RunMiddlewares { - fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState { if info.count == 0 { return ProcessResponse::init(resp); @@ -354,21 +353,24 @@ impl RunMiddlewares { resp = match info.mws[curr].response(info.req_mut(), resp) { Err(err) => { info.count = (curr + 1) as u16; - return ProcessResponse::init(err.into()) + return ProcessResponse::init(err.into()); } Ok(Response::Done(r)) => { curr += 1; if curr == len { - return ProcessResponse::init(r) + return ProcessResponse::init(r); } else { r } - }, + } Ok(Response::Future(fut)) => { - return PipelineState::RunMiddlewares( - RunMiddlewares { curr, fut: Some(fut), - _s: PhantomData, _h: PhantomData }) - }, + return PipelineState::RunMiddlewares(RunMiddlewares { + curr, + fut: Some(fut), + _s: PhantomData, + _h: PhantomData, + }) + } }; } } @@ -379,15 +381,12 @@ impl RunMiddlewares { loop { // poll latest fut let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None - } + Ok(Async::NotReady) => return None, Ok(Async::Ready(resp)) => { self.curr += 1; resp } - Err(err) => - return Some(ProcessResponse::init(err.into())), + Err(err) => return Some(ProcessResponse::init(err.into())), }; loop { @@ -395,16 +394,15 @@ impl RunMiddlewares { return Some(ProcessResponse::init(resp)); } else { match info.mws[self.curr].response(info.req_mut(), resp) { - Err(err) => - return Some(ProcessResponse::init(err.into())), + Err(err) => return Some(ProcessResponse::init(err.into())), Ok(Response::Done(r)) => { self.curr += 1; resp = r - }, + } Ok(Response::Future(fut)) => { self.fut = Some(fut); - break - }, + break; + } } } } @@ -451,42 +449,56 @@ enum IOState { } impl ProcessResponse { - #[inline] fn init(resp: HttpResponse) -> PipelineState { - PipelineState::Response( - ProcessResponse{ resp, - iostate: IOState::Response, - running: RunningState::Running, - drain: None, _s: PhantomData, _h: PhantomData}) + PipelineState::Response(ProcessResponse { + resp, + iostate: IOState::Response, + running: RunningState::Running, + drain: None, + _s: PhantomData, + _h: PhantomData, + }) } - fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo) - -> Result, PipelineState> - { + fn poll_io( + mut self, io: &mut Writer, info: &mut PipelineInfo + ) -> Result, PipelineState> { loop { if self.drain.is_none() && self.running != RunningState::Paused { // if task is paused, write buffer is probably full 'inner: loop { let result = match mem::replace(&mut self.iostate, IOState::Done) { IOState::Response => { - let encoding = self.resp.content_encoding().unwrap_or(info.encoding); + let encoding = + self.resp.content_encoding().unwrap_or(info.encoding); - let result = match io.start(info.req_mut().get_inner(), - &mut self.resp, encoding) - { + let result = match io.start( + info.req_mut().get_inner(), + &mut self.resp, + encoding, + ) { Ok(res) => res, Err(err) => { info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, self.resp)) + return Ok(FinishingMiddlewares::init( + info, + self.resp, + )); } }; if let Some(err) = self.resp.error() { if self.resp.status().is_server_error() { - error!("Error occured during request handling: {}", err); + error!( + "Error occured during request handling: {}", + err + ); } else { - warn!("Error occured during request handling: {}", err); + warn!( + "Error occured during request handling: {}", + err + ); } if log_enabled!(Debug) { debug!("{:?}", err); @@ -497,44 +509,48 @@ impl ProcessResponse { match self.resp.replace_body(Body::Empty) { Body::Streaming(stream) => { self.iostate = IOState::Payload(stream); - continue 'inner - }, - Body::Actor(ctx) => { - self.iostate = IOState::Actor(ctx); - continue 'inner - }, + continue 'inner; + } + Body::Actor(ctx) => { + self.iostate = IOState::Actor(ctx); + continue 'inner; + } _ => (), } result - }, - IOState::Payload(mut body) => { - match body.poll() { - Ok(Async::Ready(None)) => { - if let Err(err) = io.write_eof() { + } + IOState::Payload(mut body) => match body.poll() { + Ok(Async::Ready(None)) => { + if let Err(err) = io.write_eof() { + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init( + info, + self.resp, + )); + } + break; + } + Ok(Async::Ready(Some(chunk))) => { + self.iostate = IOState::Payload(body); + match io.write(chunk.into()) { + Err(err) => { info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, self.resp)) - } - break - }, - Ok(Async::Ready(Some(chunk))) => { - self.iostate = IOState::Payload(body); - match io.write(chunk.into()) { - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, self.resp)) - }, - Ok(result) => result + return Ok(FinishingMiddlewares::init( + info, + self.resp, + )); } + Ok(result) => result, } - Ok(Async::NotReady) => { - self.iostate = IOState::Payload(body); - break - }, - Err(err) => { - info.error = Some(err); - return Ok(FinishingMiddlewares::init(info, self.resp)) - } + } + Ok(Async::NotReady) => { + self.iostate = IOState::Payload(body); + break; + } + Err(err) => { + info.error = Some(err); + return Ok(FinishingMiddlewares::init(info, self.resp)); } }, IOState::Actor(mut ctx) => { @@ -545,7 +561,7 @@ impl ProcessResponse { Ok(Async::Ready(Some(vec))) => { if vec.is_empty() { self.iostate = IOState::Actor(ctx); - break + break; } let mut res = None; for frame in vec { @@ -555,40 +571,49 @@ impl ProcessResponse { if let Err(err) = io.write_eof() { info.error = Some(err.into()); return Ok( - FinishingMiddlewares::init(info, self.resp)) + FinishingMiddlewares::init( + info, + self.resp, + ), + ); } - break 'inner - }, + break 'inner; + } Frame::Chunk(Some(chunk)) => { match io.write(chunk) { Err(err) => { info.error = Some(err.into()); return Ok( - FinishingMiddlewares::init(info, self.resp)) - }, + FinishingMiddlewares::init( + info, + self.resp, + ), + ); + } Ok(result) => res = Some(result), } - }, + } Frame::Drain(fut) => self.drain = Some(fut), } } self.iostate = IOState::Actor(ctx); if self.drain.is_some() { self.running.resume(); - break 'inner + break 'inner; } res.unwrap() - }, - Ok(Async::Ready(None)) => { - break } + Ok(Async::Ready(None)) => break, Ok(Async::NotReady) => { self.iostate = IOState::Actor(ctx); - break + break; } Err(err) => { info.error = Some(err); - return Ok(FinishingMiddlewares::init(info, self.resp)) + return Ok(FinishingMiddlewares::init( + info, + self.resp, + )); } } } @@ -598,11 +623,9 @@ impl ProcessResponse { match result { WriterState::Pause => { self.running.pause(); - break + break; } - WriterState::Done => { - self.running.resume() - }, + WriterState::Done => self.running.resume(), } } } @@ -618,17 +641,16 @@ impl ProcessResponse { let _ = tx.send(()); } // restart io processing - continue - }, - Ok(Async::NotReady) => - return Err(PipelineState::Response(self)), + continue; + } + Ok(Async::NotReady) => return Err(PipelineState::Response(self)), Err(err) => { info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, self.resp)) + return Ok(FinishingMiddlewares::init(info, self.resp)); } } } - break + break; } // response is completed @@ -638,7 +660,7 @@ impl ProcessResponse { Ok(_) => (), Err(err) => { info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, self.resp)) + return Ok(FinishingMiddlewares::init(info, self.resp)); } } self.resp.set_response_size(io.written()); @@ -652,19 +674,22 @@ impl ProcessResponse { /// Middlewares start executor struct FinishingMiddlewares { resp: HttpResponse, - fut: Option>>, + fut: Option>>, _s: PhantomData, _h: PhantomData, } impl FinishingMiddlewares { - fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { if info.count == 0 { Completed::init(info) } else { - let mut state = FinishingMiddlewares{resp, fut: None, - _s: PhantomData, _h: PhantomData}; + let mut state = FinishingMiddlewares { + resp, + fut: None, + _s: PhantomData, + _h: PhantomData, + }; if let Some(st) = state.poll(info) { st } else { @@ -678,12 +703,8 @@ impl FinishingMiddlewares { // poll latest fut let not_ready = if let Some(ref mut fut) = self.fut { match fut.poll() { - Ok(Async::NotReady) => { - true - }, - Ok(Async::Ready(())) => { - false - }, + Ok(Async::NotReady) => true, + Ok(Async::Ready(())) => false, Err(err) => { error!("Middleware finish error: {}", err); false @@ -701,12 +722,12 @@ impl FinishingMiddlewares { match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) { Finished::Done => { if info.count == 0 { - return Some(Completed::init(info)) + return Some(Completed::init(info)); } } Finished::Future(fut) => { self.fut = Some(fut); - }, + } } } } @@ -716,7 +737,6 @@ impl FinishingMiddlewares { struct Completed(PhantomData, PhantomData); impl Completed { - #[inline] fn init(info: &mut PipelineInfo) -> PipelineState { if let Some(ref err) = info.error { @@ -745,15 +765,23 @@ mod tests { use super::*; use actix::*; use context::HttpContext; - use tokio_core::reactor::Core; use futures::future::{lazy, result}; + use tokio_core::reactor::Core; impl PipelineState { fn is_none(&self) -> Option { - if let PipelineState::None = *self { Some(true) } else { None } + if let PipelineState::None = *self { + Some(true) + } else { + None + } } fn completed(self) -> Option> { - if let PipelineState::Completed(c) = self { Some(c) } else { None } + if let PipelineState::Completed(c) = self { + Some(c) + } else { + None + } } } @@ -764,28 +792,35 @@ mod tests { #[test] fn test_completed() { - Core::new().unwrap().run(lazy(|| { - let mut info = PipelineInfo::new(HttpRequest::default()); - Completed::<(), Inner<()>>::init(&mut info).is_none().unwrap(); + Core::new() + .unwrap() + .run(lazy(|| { + let mut info = PipelineInfo::new(HttpRequest::default()); + Completed::<(), Inner<()>>::init(&mut info) + .is_none() + .unwrap(); - let req = HttpRequest::default(); - let mut ctx = HttpContext::new(req.clone(), MyActor); - let addr: Addr = ctx.address(); - let mut info = PipelineInfo::new(req); - info.context = Some(Box::new(ctx)); - let mut state = Completed::<(), Inner<()>>::init(&mut info).completed().unwrap(); + let req = HttpRequest::default(); + let mut ctx = HttpContext::new(req.clone(), MyActor); + let addr: Addr = ctx.address(); + let mut info = PipelineInfo::new(req); + info.context = Some(Box::new(ctx)); + let mut state = Completed::<(), Inner<()>>::init(&mut info) + .completed() + .unwrap(); - assert!(state.poll(&mut info).is_none()); - let pp = Pipeline(info, PipelineState::Completed(state)); - assert!(!pp.is_done()); + assert!(state.poll(&mut info).is_none()); + let pp = Pipeline(info, PipelineState::Completed(state)); + assert!(!pp.is_done()); - let Pipeline(mut info, st) = pp; - let mut st = st.completed().unwrap(); - drop(addr); + let Pipeline(mut info, st) = pp; + let mut st = st.completed().unwrap(); + drop(addr); - assert!(st.poll(&mut info).unwrap().is_none().unwrap()); + assert!(st.poll(&mut info).unwrap().is_none().unwrap()); - result(Ok::<_, ()>(())) - })).unwrap(); + result(Ok::<_, ()>(())) + })) + .unwrap(); } } diff --git a/src/pred.rs b/src/pred.rs index 7bc8e187..a712bba6 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -1,20 +1,18 @@ //! Route match predicates #![allow(non_snake_case)] -use std::marker::PhantomData; use http; use http::{header, HttpTryFrom}; use httpmessage::HttpMessage; use httprequest::HttpRequest; +use std::marker::PhantomData; /// Trait defines resource route predicate. /// Predicate can modify request object. It is also possible to /// to store extra attributes on request by using `Extensions` container, /// Extensions container available via `HttpRequest::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. @@ -30,8 +28,7 @@ pub trait Predicate { /// .f(|r| HttpResponse::MethodNotAllowed())); /// } /// ``` -pub fn Any + 'static>(pred: P) -> AnyPredicate -{ +pub fn Any + 'static>(pred: P) -> AnyPredicate { AnyPredicate(vec![Box::new(pred)]) } @@ -50,7 +47,7 @@ impl Predicate for AnyPredicate { fn check(&self, req: &mut HttpRequest) -> bool { for p in &self.0 { if p.check(req) { - return true + return true; } } false @@ -90,7 +87,7 @@ impl Predicate for AllPredicate { fn check(&self, req: &mut HttpRequest) -> bool { for p in &self.0 { if !p.check(req) { - return false + return false; } } true @@ -98,8 +95,7 @@ impl Predicate for AllPredicate { } /// Return predicate that matches if supplied predicate does not match. -pub fn Not + 'static>(pred: P) -> NotPredicate -{ +pub fn Not + 'static>(pred: P) -> NotPredicate { NotPredicate(Box::new(pred)) } @@ -172,21 +168,29 @@ 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) -> HeaderPredicate -{ - HeaderPredicate(header::HeaderName::try_from(name).unwrap(), - header::HeaderValue::from_static(value), - PhantomData) +/// Return predicate that matches if request contains specified header and +/// value. +pub fn Header( + name: &'static str, value: &'static str +) -> HeaderPredicate { + HeaderPredicate( + header::HeaderName::try_from(name).unwrap(), + header::HeaderValue::from_static(value), + PhantomData, + ) } #[doc(hidden)] -pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); +pub 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 + return val == self.1; } false } @@ -195,17 +199,24 @@ impl Predicate for HeaderPredicate { #[cfg(test)] mod tests { use super::*; - use std::str::FromStr; - use http::{Uri, Version, Method}; use http::header::{self, HeaderMap}; + use http::{Method, Uri, Version}; + use std::str::FromStr; #[test] fn test_header() { let mut headers = HeaderMap::new(); - headers.insert(header::TRANSFER_ENCODING, - header::HeaderValue::from_static("chunked")); + 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); + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); let pred = Header("transfer-encoding", "chunked"); assert!(pred.check(&mut req)); @@ -220,11 +231,19 @@ mod tests { #[test] fn test_methods() { let mut req = HttpRequest::new( - Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + HeaderMap::new(), + None, + ); let mut req2 = HttpRequest::new( - Method::POST, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + Method::POST, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + HeaderMap::new(), + None, + ); assert!(Get().check(&mut req)); assert!(!Get().check(&mut req2)); @@ -232,44 +251,72 @@ mod tests { assert!(!Post().check(&mut req)); let mut r = HttpRequest::new( - Method::PUT, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + Method::PUT, + Uri::from_str("/").unwrap(), + 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(), None); + Method::DELETE, + Uri::from_str("/").unwrap(), + 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(), None); + Method::HEAD, + Uri::from_str("/").unwrap(), + 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(), None); + Method::OPTIONS, + Uri::from_str("/").unwrap(), + 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(), None); + Method::CONNECT, + Uri::from_str("/").unwrap(), + 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(), None); + Method::PATCH, + Uri::from_str("/").unwrap(), + 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(), None); + Method::TRACE, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + HeaderMap::new(), + None, + ); assert!(Trace().check(&mut r)); assert!(!Trace().check(&mut req)); } @@ -277,8 +324,12 @@ mod tests { #[test] fn test_preds() { let mut r = HttpRequest::new( - Method::TRACE, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); + Method::TRACE, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + HeaderMap::new(), + None, + ); assert!(Not(Get()).check(&mut r)); assert!(!Not(Trace()).check(&mut r)); diff --git a/src/resource.rs b/src/resource.rs index 19a1b057..c7b886a9 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,15 +1,15 @@ -use std::rc::Rc; use std::marker::PhantomData; +use std::rc::Rc; -use smallvec::SmallVec; use http::{Method, StatusCode}; +use smallvec::SmallVec; -use pred; -use route::Route; -use handler::{Reply, Handler, Responder, FromRequest}; -use middleware::Middleware; +use handler::{FromRequest, Handler, Reply, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; +use middleware::Middleware; +use pred; +use route::Route; /// *Resource* is an entry in route table which corresponds to requested URL. /// @@ -18,8 +18,8 @@ use httpresponse::HttpResponse; /// 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 considered matched and route handler get called. +/// and check all predicates for specific route, if request matches all +/// predicates route route considered matched and route handler get called. /// /// ```rust /// # extern crate actix_web; @@ -31,7 +31,7 @@ use httpresponse::HttpResponse; /// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok())) /// .finish(); /// } -pub struct ResourceHandler { +pub struct ResourceHandler { name: String, state: PhantomData, routes: SmallVec<[Route; 3]>, @@ -44,18 +44,19 @@ impl Default for ResourceHandler { name: String::new(), state: PhantomData, routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()) } + middlewares: Rc::new(Vec::new()), + } } } impl ResourceHandler { - pub(crate) fn default_not_found() -> Self { ResourceHandler { name: String::new(), state: PhantomData, routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()) } + middlewares: Rc::new(Vec::new()), + } } /// Set resource name @@ -69,9 +70,9 @@ impl ResourceHandler { } impl ResourceHandler { - /// Register a new route and return mutable reference to *Route* object. - /// *Route* is used for route configuration, i.e. adding predicates, setting up handler. + /// *Route* is used for route configuration, i.e. adding predicates, + /// setting up handler. /// /// ```rust /// # extern crate actix_web; @@ -131,7 +132,10 @@ impl ResourceHandler { /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Method(method)) + self.routes + .last_mut() + .unwrap() + .filter(pred::Method(method)) } /// Register a new route and add handler object. @@ -154,8 +158,9 @@ impl ResourceHandler { /// Application::resource("/", |r| r.route().f(index) /// ``` pub fn f(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: Responder + 'static, + where + F: Fn(HttpRequest) -> R + 'static, + R: Responder + 'static, { self.routes.push(Route::default()); self.routes.last_mut().unwrap().f(handler) @@ -169,9 +174,10 @@ impl ResourceHandler { /// Application::resource("/", |r| r.route().with(index) /// ``` pub fn with(&mut self, handler: F) - where F: Fn(T) -> R + 'static, - R: Responder + 'static, - T: FromRequest + 'static, + where + F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, { self.routes.push(Route::default()); self.routes.last_mut().unwrap().with(handler); @@ -182,13 +188,14 @@ impl ResourceHandler { /// This is similar to `App's` middlewares, but /// middlewares get invoked on resource level. pub fn middleware>(&mut self, mw: M) { - Rc::get_mut(&mut self.middlewares).unwrap().push(Box::new(mw)); + Rc::get_mut(&mut self.middlewares) + .unwrap() + .push(Box::new(mw)); } - pub(crate) fn handle(&mut self, - mut req: HttpRequest, - default: Option<&mut ResourceHandler>) -> Reply - { + pub(crate) fn handle( + &mut self, mut req: HttpRequest, default: Option<&mut ResourceHandler> + ) -> Reply { for route in &mut self.routes { if route.check(&mut req) { return if self.middlewares.is_empty() { diff --git a/src/route.rs b/src/route.rs index 86614c0e..b7b84ad0 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,17 +1,18 @@ +use futures::{Async, Future, Poll}; +use std::marker::PhantomData; use std::mem; use std::rc::Rc; -use std::marker::PhantomData; -use futures::{Async, Future, Poll}; use error::Error; -use pred::Predicate; +use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyItem, Responder, + RouteHandler, WrapHandler}; use http::StatusCode; -use handler::{Reply, ReplyItem, Handler, FromRequest, - Responder, RouteHandler, AsyncHandler, WrapHandler}; -use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use with::{With, With2, With3, ExtractorConfig}; +use middleware::{Middleware, Response as MiddlewareResponse, + Started as MiddlewareStarted}; +use pred::Predicate; +use with::{ExtractorConfig, With, With2, With3}; /// Resource route definition /// @@ -23,7 +24,6 @@ pub struct Route { } impl Default for Route { - fn default() -> Route { Route { preds: Vec::new(), @@ -33,12 +33,11 @@ impl Default for Route { } impl Route { - #[inline] pub(crate) fn check(&self, req: &mut HttpRequest) -> bool { for pred in &self.preds { if !pred.check(req) { - return false + return false; } } true @@ -50,9 +49,9 @@ impl Route { } #[inline] - pub(crate) fn compose(&mut self, - req: HttpRequest, - mws: Rc>>>) -> Reply { + pub(crate) fn compose( + &mut self, req: HttpRequest, mws: Rc>>> + ) -> Reply { Reply::async(Compose::new(req, mws, self.handler.clone())) } @@ -86,18 +85,20 @@ impl Route { /// Set handler function. Usually call to this method is last call /// during route configuration, so it does not return reference to self. pub fn f(&mut self, handler: F) - where F: Fn(HttpRequest) -> R + 'static, - R: Responder + 'static, + where + F: Fn(HttpRequest) -> R + 'static, + R: Responder + 'static, { self.handler = InnerHandler::new(handler); } /// Set async handler function. pub fn a(&mut self, handler: H) - where H: Fn(HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static + where + H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static, { self.handler = InnerHandler::async(handler); } @@ -128,9 +129,10 @@ impl Route { /// } /// ``` pub fn with(&mut self, handler: F) -> ExtractorConfig - where F: Fn(T) -> R + 'static, - R: Responder + 'static, - T: FromRequest + 'static, + where + F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, { let cfg = ExtractorConfig::default(); self.h(With::new(handler, Clone::clone(&cfg))); @@ -167,43 +169,58 @@ impl Route { /// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor /// } /// ``` - pub fn with2(&mut self, handler: F) - -> (ExtractorConfig, ExtractorConfig) - where F: Fn(T1, T2) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, + pub fn with2( + &mut self, handler: F + ) -> (ExtractorConfig, ExtractorConfig) + where + F: Fn(T1, T2) -> R + 'static, + R: Responder + 'static, + T1: FromRequest + 'static, + T2: FromRequest + 'static, { let cfg1 = ExtractorConfig::default(); let cfg2 = ExtractorConfig::default(); - self.h(With2::new(handler, Clone::clone(&cfg1), Clone::clone(&cfg2))); + self.h(With2::new( + handler, + Clone::clone(&cfg1), + Clone::clone(&cfg2), + )); (cfg1, cfg2) } /// Set handler function, use request extractor for all paramters. - pub fn with3(&mut self, handler: F) - -> (ExtractorConfig, ExtractorConfig, ExtractorConfig) - where F: Fn(T1, T2, T3) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, + pub fn with3( + &mut self, handler: F + ) -> ( + ExtractorConfig, + ExtractorConfig, + ExtractorConfig, + ) + where + F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, { let cfg1 = ExtractorConfig::default(); let cfg2 = ExtractorConfig::default(); let cfg3 = ExtractorConfig::default(); self.h(With3::new( - handler, Clone::clone(&cfg1), Clone::clone(&cfg2), Clone::clone(&cfg3))); + handler, + Clone::clone(&cfg1), + Clone::clone(&cfg2), + Clone::clone(&cfg3), + )); (cfg1, cfg2, cfg3) } } -/// `RouteHandler` wrapper. This struct is required because it needs to be shared -/// for resource level middlewares. +/// `RouteHandler` wrapper. This struct is required because it needs to be +/// shared for resource level middlewares. struct InnerHandler(Rc>>); impl InnerHandler { - #[inline] fn new>(h: H) -> Self { InnerHandler(Rc::new(Box::new(WrapHandler::new(h)))) @@ -211,10 +228,11 @@ impl InnerHandler { #[inline] fn async(h: H) -> Self - where H: Fn(HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static + where + H: Fn(HttpRequest) -> F + 'static, + F: Future + 'static, + R: Responder + 'static, + E: Into + 'static, { InnerHandler(Rc::new(Box::new(AsyncHandler::new(h)))) } @@ -237,7 +255,6 @@ impl Clone for InnerHandler { } } - /// Compose resource level middlewares with route handler. struct Compose { info: ComposeInfo, @@ -270,14 +287,18 @@ impl ComposeState { } impl Compose { - fn new(req: HttpRequest, - mws: Rc>>>, - handler: InnerHandler) -> Self - { - let mut info = ComposeInfo { count: 0, req, mws, handler }; + fn new( + req: HttpRequest, mws: Rc>>>, handler: InnerHandler + ) -> Self { + let mut info = ComposeInfo { + count: 0, + req, + mws, + handler, + }; let state = StartMiddlewares::init(&mut info); - Compose {state, info} + Compose { state, info } } } @@ -289,12 +310,12 @@ impl Future for Compose { loop { if let ComposeState::Response(ref mut resp) = self.state { let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)) + return Ok(Async::Ready(resp)); } if let Some(state) = self.state.poll(&mut self.info) { self.state = state; } else { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } } } @@ -306,51 +327,47 @@ struct StartMiddlewares { _s: PhantomData, } -type Fut = Box, Error=Error>>; +type Fut = Box, Error = Error>>; impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { let len = info.mws.len(); loop { if info.count == len { let reply = info.handler.handle(info.req.clone()); - return WaitingResponse::init(info, reply) + return WaitingResponse::init(info, reply); } else { match info.mws[info.count].start(&mut info.req) { - Ok(MiddlewareStarted::Done) => - info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => - return RunMiddlewares::init(info, resp), - Ok(MiddlewareStarted::Future(mut fut)) => - match fut.poll() { - Ok(Async::NotReady) => - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData}), - Ok(Async::Ready(resp)) => { - if let Some(resp) = resp { - return RunMiddlewares::init(info, resp); - } - info.count += 1; + Ok(MiddlewareStarted::Done) => info.count += 1, + Ok(MiddlewareStarted::Response(resp)) => { + return RunMiddlewares::init(info, resp) + } + Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() { + Ok(Async::NotReady) => { + return ComposeState::Starting(StartMiddlewares { + fut: Some(fut), + _s: PhantomData, + }) + } + Ok(Async::Ready(resp)) => { + if let Some(resp) = resp { + return RunMiddlewares::init(info, resp); } - Err(err) => - return Response::init(err.into()), - }, - Err(err) => - return Response::init(err.into()), + info.count += 1; + } + Err(err) => return Response::init(err.into()), + }, + Err(err) => return Response::init(err.into()), } } } } - fn poll(&mut self, info: &mut ComposeInfo) -> Option> - { + fn poll(&mut self, info: &mut ComposeInfo) -> Option> { let len = info.mws.len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => - return None, + Ok(Async::NotReady) => return None, Ok(Async::Ready(resp)) => { info.count += 1; if let Some(resp) = resp { @@ -362,23 +379,20 @@ impl StartMiddlewares { } else { loop { match info.mws[info.count].start(&mut info.req) { - Ok(MiddlewareStarted::Done) => - info.count += 1, + Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return Some(RunMiddlewares::init(info, resp)); - }, + } Ok(MiddlewareStarted::Future(fut)) => { self.fut = Some(fut); - continue 'outer - }, - Err(err) => - return Some(Response::init(err.into())) + continue 'outer; + } + Err(err) => return Some(Response::init(err.into())), } } } } - Err(err) => - return Some(Response::init(err.into())) + Err(err) => return Some(Response::init(err.into())), } } } @@ -386,44 +400,39 @@ impl StartMiddlewares { // waiting for response struct WaitingResponse { - fut: Box>, + fut: Box>, _s: PhantomData, } impl WaitingResponse { - #[inline] fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { match reply.into() { - ReplyItem::Message(resp) => - RunMiddlewares::init(info, resp), - ReplyItem::Future(fut) => - ComposeState::Handler( - WaitingResponse { fut, _s: PhantomData }), + ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), + ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse { + fut, + _s: PhantomData, + }), } } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { match self.fut.poll() { Ok(Async::NotReady) => None, - Ok(Async::Ready(response)) => - Some(RunMiddlewares::init(info, response)), - Err(err) => - Some(Response::init(err.into())), + Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), + Err(err) => Some(Response::init(err.into())), } } } - /// Middlewares response executor struct RunMiddlewares { curr: usize, - fut: Option>>, + fut: Option>>, _s: PhantomData, } impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { let mut curr = 0; let len = info.mws.len(); @@ -432,40 +441,39 @@ impl RunMiddlewares { resp = match info.mws[curr].response(&mut info.req, resp) { Err(err) => { info.count = curr + 1; - return Response::init(err.into()) - }, + return Response::init(err.into()); + } Ok(MiddlewareResponse::Done(r)) => { curr += 1; if curr == len { - return Response::init(r) + return Response::init(r); } else { r } - }, + } Ok(MiddlewareResponse::Future(fut)) => { - return ComposeState::RunMiddlewares( - RunMiddlewares { curr, fut: Some(fut), _s: PhantomData }) - }, + return ComposeState::RunMiddlewares(RunMiddlewares { + curr, + fut: Some(fut), + _s: PhantomData, + }) + } }; } } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> - { + + fn poll(&mut self, info: &mut ComposeInfo) -> Option> { let len = info.mws.len(); loop { // poll latest fut let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None - } + Ok(Async::NotReady) => return None, Ok(Async::Ready(resp)) => { self.curr += 1; resp } - Err(err) => - return Some(Response::init(err.into())), + Err(err) => return Some(Response::init(err.into())), }; loop { @@ -473,16 +481,15 @@ impl RunMiddlewares { return Some(Response::init(resp)); } else { match info.mws[self.curr].response(&mut info.req, resp) { - Err(err) => - return Some(Response::init(err.into())), + Err(err) => return Some(Response::init(err.into())), Ok(MiddlewareResponse::Done(r)) => { self.curr += 1; resp = r - }, + } Ok(MiddlewareResponse::Future(fut)) => { self.fut = Some(fut); - break - }, + break; + } } } } @@ -496,9 +503,10 @@ struct Response { } impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Response( - Response{resp: Some(resp), _s: PhantomData}) + ComposeState::Response(Response { + resp: Some(resp), + _s: PhantomData, + }) } } diff --git a/src/router.rs b/src/router.rs index b8e6baf0..74225a1a 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,15 +1,15 @@ +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; use std::mem; use std::rc::Rc; -use std::hash::{Hash, Hasher}; -use std::collections::HashMap; -use regex::{Regex, escape}; use percent_encoding::percent_decode; +use regex::{escape, Regex}; -use param::Params; use error::UrlGenerationError; -use resource::ResourceHandler; use httprequest::HttpRequest; +use param::Params; +use resource::ResourceHandler; use server::ServerSettings; /// Interface for application router. @@ -25,11 +25,10 @@ struct Inner { impl Router { /// Create new router - pub fn new(prefix: &str, - settings: ServerSettings, - map: Vec<(Resource, Option>)>) - -> (Router, Vec>) - { + pub fn new( + prefix: &str, settings: ServerSettings, + map: Vec<(Resource, Option>)>, + ) -> (Router, Vec>) { let prefix = prefix.trim().trim_right_matches('/').to_owned(); let mut named = HashMap::new(); let mut patterns = Vec::new(); @@ -48,8 +47,16 @@ impl Router { } let prefix_len = prefix.len(); - (Router(Rc::new( - Inner{ prefix, prefix_len, named, patterns, srv: settings })), resources) + ( + Router(Rc::new(Inner { + prefix, + prefix_len, + named, + patterns, + srv: settings, + })), + resources, + ) } /// Router prefix @@ -71,16 +78,18 @@ impl Router { /// Query for matched resource pub fn recognize(&self, req: &mut HttpRequest) -> Option { if self.0.prefix_len > req.path().len() { - return None + return None; } - let path: &str = unsafe{mem::transmute(&req.path()[self.0.prefix_len..])}; + let path: &str = unsafe { mem::transmute(&req.path()[self.0.prefix_len..]) }; let route_path = if path.is_empty() { "/" } else { path }; - let p = percent_decode(route_path.as_bytes()).decode_utf8().unwrap(); + let p = percent_decode(route_path.as_bytes()) + .decode_utf8() + .unwrap(); for (idx, pattern) in self.0.patterns.iter().enumerate() { if pattern.match_with_params(p.as_ref(), req.match_info_mut()) { req.set_resource(idx); - return Some(idx) + return Some(idx); } } None @@ -97,7 +106,7 @@ impl Router { for pattern in &self.0.patterns { if pattern.is_match(path) { - return true + return true; } } false @@ -105,12 +114,14 @@ impl Router { /// 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, - I: AsRef, + /// 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, + I: AsRef, { if let Some(pattern) = self.0.named.get(name) { pattern.0.resource_path(self, elements) @@ -196,7 +207,7 @@ impl Resource { let tp = if is_dynamic { let re = match Regex::new(&pattern) { Ok(re) => re, - Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err) + Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), }; let names = re.capture_names() .filter_map(|name| name.map(|name| name.to_owned())) @@ -237,9 +248,9 @@ impl Resource { } } - pub fn match_with_params<'a>(&'a self, path: &'a str, params: &'a mut Params<'a>) - -> bool - { + pub fn match_with_params<'a>( + &'a self, path: &'a str, params: &'a mut Params<'a> + ) -> bool { match self.tp { PatternType::Static(ref s) => s == path, PatternType::Dynamic(ref re, ref names) => { @@ -248,7 +259,7 @@ impl Resource { for capture in captures.iter() { if let Some(ref m) = capture { if idx != 0 { - params.add(names[idx-1].as_str(), m.as_str()); + params.add(names[idx - 1].as_str(), m.as_str()); } idx += 1; } @@ -262,10 +273,12 @@ impl Resource { } /// Build reousrce path. - pub fn resource_path(&self, router: &Router, elements: U) - -> Result - where U: IntoIterator, - I: AsRef, + pub fn resource_path( + &self, router: &Router, elements: U + ) -> Result + where + U: IntoIterator, + I: AsRef, { let mut iter = elements.into_iter(); let mut path = if self.rtp != ResourceType::External { @@ -280,7 +293,7 @@ impl Resource { if let Some(val) = iter.next() { path.push_str(val.as_ref()) } else { - return Err(UrlGenerationError::NotEnoughElements) + return Err(UrlGenerationError::NotEnoughElements); } } } @@ -374,20 +387,35 @@ mod tests { #[test] fn test_recognizer() { let routes = vec![ - (Resource::new("", "/name"), - Some(ResourceHandler::default())), - (Resource::new("", "/name/{val}"), - Some(ResourceHandler::default())), - (Resource::new("", "/name/{val}/index.html"), - Some(ResourceHandler::default())), - (Resource::new("", "/file/{file}.{ext}"), - Some(ResourceHandler::default())), - (Resource::new("", "/v{val}/{val2}/index.html"), - Some(ResourceHandler::default())), - (Resource::new("", "/v/{tail:.*}"), - Some(ResourceHandler::default())), - (Resource::new("", "{test}/index.html"), - Some(ResourceHandler::default()))]; + ( + Resource::new("", "/name"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/name/{val}"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/name/{val}/index.html"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/file/{file}.{ext}"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/v{val}/{val2}/index.html"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/v/{tail:.*}"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "{test}/index.html"), + Some(ResourceHandler::default()), + ), + ]; let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); let mut req = TestRequest::with_uri("/name").finish(); @@ -415,7 +443,10 @@ mod tests { let mut req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); assert_eq!(rec.recognize(&mut req), Some(5)); - assert_eq!(req.match_info().get("tail").unwrap(), "blah-blah/index.html"); + assert_eq!( + req.match_info().get("tail").unwrap(), + "blah-blah/index.html" + ); let mut req = TestRequest::with_uri("/bbb/index.html").finish(); assert_eq!(rec.recognize(&mut req), Some(6)); @@ -425,8 +456,15 @@ mod tests { #[test] fn test_recognizer_2() { let routes = vec![ - (Resource::new("", "/index.json"), Some(ResourceHandler::default())), - (Resource::new("", "/{source}.json"), Some(ResourceHandler::default()))]; + ( + Resource::new("", "/index.json"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/{source}.json"), + Some(ResourceHandler::default()), + ), + ]; let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); let mut req = TestRequest::with_uri("/index.json").finish(); @@ -439,8 +477,15 @@ mod tests { #[test] fn test_recognizer_with_prefix() { let routes = vec![ - (Resource::new("", "/name"), Some(ResourceHandler::default())), - (Resource::new("", "/name/{val}"), Some(ResourceHandler::default()))]; + ( + Resource::new("", "/name"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/name/{val}"), + Some(ResourceHandler::default()), + ), + ]; let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes); let mut req = TestRequest::with_uri("/name").finish(); @@ -456,8 +501,15 @@ mod tests { // same patterns let routes = vec![ - (Resource::new("", "/name"), Some(ResourceHandler::default())), - (Resource::new("", "/name/{val}"), Some(ResourceHandler::default()))]; + ( + Resource::new("", "/name"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/name/{val}"), + Some(ResourceHandler::default()), + ), + ]; let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes); let mut req = TestRequest::with_uri("/name").finish(); @@ -525,18 +577,25 @@ mod tests { #[test] fn test_request_resource() { let routes = vec![ - (Resource::new("r1", "/index.json"), Some(ResourceHandler::default())), - (Resource::new("r2", "/test.json"), Some(ResourceHandler::default()))]; + ( + Resource::new("r1", "/index.json"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("r2", "/test.json"), + Some(ResourceHandler::default()), + ), + ]; let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); - let mut req = TestRequest::with_uri("/index.json") - .finish_with_router(router.clone()); + let mut req = + TestRequest::with_uri("/index.json").finish_with_router(router.clone()); assert_eq!(router.recognize(&mut req), Some(0)); let resource = req.resource(); assert_eq!(resource.name(), "r1"); - let mut req = TestRequest::with_uri("/test.json") - .finish_with_router(router.clone()); + let mut req = + TestRequest::with_uri("/test.json").finish_with_router(router.clone()); assert_eq!(router.recognize(&mut req), Some(1)); let resource = req.resource(); assert_eq!(resource.name(), "r2"); diff --git a/src/server/channel.rs b/src/server/channel.rs index 49ea586e..7a4bc64b 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -1,17 +1,16 @@ -use std::{ptr, mem, time, io}; +use std::net::{Shutdown, SocketAddr}; use std::rc::Rc; -use std::net::{SocketAddr, Shutdown}; +use std::{io, mem, ptr, time}; -use bytes::{Bytes, BytesMut, Buf, BufMut}; -use futures::{Future, Poll, Async}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; -use super::{h1, h2, utils, HttpHandler, IoStream}; use super::settings::WorkerSettings; +use super::{utils, HttpHandler, IoStream, h1, h2}; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; - enum HttpProtocol { H1(h1::Http1), H2(h2::Http2), @@ -24,27 +23,47 @@ enum ProtocolKind { } #[doc(hidden)] -pub struct HttpChannel where T: IoStream, H: HttpHandler + 'static { +pub struct HttpChannel +where + T: IoStream, + H: HttpHandler + 'static, +{ proto: Option>, node: Option>>, } -impl HttpChannel where T: IoStream, H: HttpHandler + 'static +impl HttpChannel +where + T: IoStream, + H: HttpHandler + 'static, { - pub(crate) fn new(settings: Rc>, - mut io: T, peer: Option, http2: bool) -> HttpChannel - { + pub(crate) fn new( + settings: Rc>, mut io: T, peer: Option, + http2: bool, + ) -> HttpChannel { settings.add_channel(); let _ = io.set_nodelay(true); if http2 { HttpChannel { - node: None, proto: Some(HttpProtocol::H2( - h2::Http2::new(settings, io, peer, Bytes::new()))) } + node: None, + proto: Some(HttpProtocol::H2(h2::Http2::new( + settings, + io, + peer, + Bytes::new(), + ))), + } } else { HttpChannel { - node: None, proto: Some(HttpProtocol::Unknown( - settings, peer, io, BytesMut::with_capacity(4096))) } + node: None, + proto: Some(HttpProtocol::Unknown( + settings, + peer, + io, + BytesMut::with_capacity(4096), + )), + } } } @@ -55,15 +74,16 @@ impl HttpChannel where T: IoStream, H: HttpHandler + 'static 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() - } + Some(HttpProtocol::H2(ref mut h2)) => h2.shutdown(), _ => (), } } } -impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'static +impl Future for HttpChannel +where + T: IoStream, + H: HttpHandler + 'static, { type Item = (); type Error = (); @@ -73,12 +93,15 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta let el = self as *mut _; self.node = Some(Node::new(el)); let _ = match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => - self.node.as_ref().map(|n| h1.settings().head().insert(n)), - Some(HttpProtocol::H2(ref mut h2)) => - self.node.as_ref().map(|n| h2.settings().head().insert(n)), - Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => - self.node.as_ref().map(|n| settings.head().insert(n)), + Some(HttpProtocol::H1(ref mut h1)) => self.node + .as_ref() + .map(|n| h1.settings().head().insert(n)), + Some(HttpProtocol::H2(ref mut h2)) => self.node + .as_ref() + .map(|n| h2.settings().head().insert(n)), + Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => { + self.node.as_ref().map(|n| settings.head().insert(n)) + } None => unreachable!(), }; } @@ -90,30 +113,35 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta Ok(Async::Ready(())) | Err(_) => { h1.settings().remove_channel(); self.node.as_mut().map(|n| n.remove()); - }, + } _ => (), } - return result - }, + return result; + } Some(HttpProtocol::H2(ref mut h2)) => { let result = h2.poll(); match result { Ok(Async::Ready(())) | Err(_) => { h2.settings().remove_channel(); self.node.as_mut().map(|n| n.remove()); - }, + } _ => (), } - return result - }, - Some(HttpProtocol::Unknown(ref mut settings, _, ref mut io, ref mut buf)) => { + return result; + } + Some(HttpProtocol::Unknown( + ref mut settings, + _, + ref mut io, + ref mut buf, + )) => { match utils::read_from_io(io, buf) { Ok(Async::Ready(0)) | Err(_) => { debug!("Ignored premature client disconnection"); settings.remove_channel(); self.node.as_mut().map(|n| n.remove()); - return Err(()) - }, + return Err(()); + } _ => (), } @@ -126,7 +154,7 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta } else { return Ok(Async::NotReady); } - }, + } None => unreachable!(), }; @@ -134,30 +162,36 @@ impl Future for HttpChannel where T: IoStream, H: HttpHandler + 'sta if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { match kind { ProtocolKind::Http1 => { - self.proto = Some( - HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf))); - return self.poll() - }, + self.proto = Some(HttpProtocol::H1(h1::Http1::new( + settings, + io, + addr, + buf, + ))); + return self.poll(); + } ProtocolKind::Http2 => { - self.proto = Some( - HttpProtocol::H2(h2::Http2::new(settings, io, addr, buf.freeze()))); - return self.poll() - }, + self.proto = Some(HttpProtocol::H2(h2::Http2::new( + settings, + io, + addr, + buf.freeze(), + ))); + return self.poll(); + } } } unreachable!() } } -pub(crate) struct Node -{ +pub(crate) struct Node { next: Option<*mut Node<()>>, prev: Option<*mut Node<()>>, element: *mut T, } -impl Node -{ +impl Node { fn new(el: *mut T) -> Self { Node { next: None, @@ -194,9 +228,7 @@ impl Node } } - impl Node<()> { - pub(crate) fn head() -> Self { Node { next: None, @@ -205,7 +237,11 @@ impl Node<()> { } } - pub(crate) fn traverse(&self) where T: IoStream, 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 { @@ -214,30 +250,39 @@ impl Node<()> { next = n.next.as_ref(); if !n.element.is_null() { - let ch: &mut HttpChannel = mem::transmute( - &mut *(n.element as *mut _)); + let ch: &mut HttpChannel = + mem::transmute(&mut *(n.element as *mut _)); ch.shutdown(); } } } else { - return + return; } } } } /// Wrapper for `AsyncRead + AsyncWrite` types -pub(crate) struct WrapperStream where T: AsyncRead + AsyncWrite + 'static { - io: T, +pub(crate) struct WrapperStream +where + T: AsyncRead + AsyncWrite + 'static, +{ + io: T, } -impl WrapperStream where T: AsyncRead + AsyncWrite + 'static { +impl WrapperStream +where + T: AsyncRead + AsyncWrite + 'static, +{ pub fn new(io: T) -> Self { - WrapperStream{ io } + WrapperStream { io } } } -impl IoStream for WrapperStream where T: AsyncRead + AsyncWrite + 'static { +impl IoStream for WrapperStream +where + T: AsyncRead + AsyncWrite + 'static, +{ #[inline] fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { Ok(()) @@ -252,14 +297,20 @@ impl IoStream for WrapperStream where T: AsyncRead + AsyncWrite + 'static } } -impl io::Read for WrapperStream where T: AsyncRead + AsyncWrite + 'static { +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 { +impl io::Write for WrapperStream +where + T: AsyncRead + AsyncWrite + 'static, +{ #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.io.write(buf) @@ -270,14 +321,20 @@ impl io::Write for WrapperStream where T: AsyncRead + AsyncWrite + 'static } } -impl AsyncRead for WrapperStream where T: AsyncRead + AsyncWrite + 'static { +impl AsyncRead for WrapperStream +where + T: AsyncRead + AsyncWrite + 'static, +{ #[inline] fn read_buf(&mut self, buf: &mut B) -> Poll { self.io.read_buf(buf) } } -impl AsyncWrite for WrapperStream where T: AsyncRead + AsyncWrite + 'static { +impl AsyncWrite for WrapperStream +where + T: AsyncRead + AsyncWrite + 'static, +{ #[inline] fn shutdown(&mut self) -> Poll<(), io::Error> { self.io.shutdown() diff --git a/src/server/encoding.rs b/src/server/encoding.rs index fd2ca432..b9da1def 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -1,25 +1,24 @@ -use std::{io, cmp, mem}; -use std::io::{Read, Write}; use std::fmt::Write as FmtWrite; +use std::io::{Read, Write}; use std::str::FromStr; +use std::{cmp, io, mem}; -use bytes::{Bytes, BytesMut, BufMut}; -use http::{Version, Method, HttpTryFrom}; -use http::header::{HeaderMap, HeaderValue, - ACCEPT_ENCODING, CONNECTION, - CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; +#[cfg(feature = "brotli")] +use brotli2::write::{BrotliDecoder, BrotliEncoder}; +use bytes::{BufMut, Bytes, BytesMut}; use flate2::Compression; use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder}; -#[cfg(feature="brotli")] -use brotli2::write::{BrotliDecoder, BrotliEncoder}; +use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; +use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONNECTION, + CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; +use http::{HttpTryFrom, Method, Version}; -use header::ContentEncoding; -use body::{Body, Binary}; +use body::{Binary, Body}; use error::PayloadError; +use header::ContentEncoding; use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; -use payload::{PayloadSender, PayloadWriter, PayloadStatus}; +use payload::{PayloadSender, PayloadStatus, PayloadWriter}; use super::shared::SharedBytes; @@ -29,7 +28,6 @@ pub(crate) enum PayloadType { } impl PayloadType { - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { // check content-encoding let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { @@ -43,8 +41,9 @@ impl PayloadType { }; match enc { - ContentEncoding::Auto | ContentEncoding::Identity => - PayloadType::Sender(sender), + ContentEncoding::Auto | ContentEncoding::Identity => { + PayloadType::Sender(sender) + } _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), } } @@ -84,7 +83,6 @@ impl PayloadWriter for PayloadType { } } - /// Payload wrapper with content decompression support pub(crate) struct EncodedPayload { inner: PayloadSender, @@ -94,12 +92,15 @@ pub(crate) struct EncodedPayload { impl EncodedPayload { pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { - EncodedPayload{ inner, error: false, payload: PayloadStream::new(enc) } + EncodedPayload { + inner, + error: false, + payload: PayloadStream::new(enc), + } } } impl PayloadWriter for EncodedPayload { - fn set_error(&mut self, err: PayloadError) { self.inner.set_error(err) } @@ -110,7 +111,7 @@ impl PayloadWriter for EncodedPayload { Err(err) => { self.error = true; self.set_error(PayloadError::Io(err)); - }, + } Ok(value) => { if let Some(b) = value { self.inner.feed_data(b); @@ -123,7 +124,7 @@ impl PayloadWriter for EncodedPayload { fn feed_data(&mut self, data: Bytes) { if self.error { - return + return; } match self.payload.feed_data(data) { @@ -145,7 +146,7 @@ impl PayloadWriter for EncodedPayload { pub(crate) enum Decoder { Deflate(Box>), Gzip(Option>>), - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] Br(Box>), Identity, } @@ -190,7 +191,9 @@ pub(crate) struct Writer { impl Writer { fn new() -> Writer { - Writer{buf: BytesMut::with_capacity(8192)} + Writer { + buf: BytesMut::with_capacity(8192), + } } fn take(&mut self) -> Bytes { self.buf.take().freeze() @@ -216,65 +219,64 @@ pub(crate) struct PayloadStream { impl PayloadStream { pub fn new(enc: ContentEncoding) -> PayloadStream { let dec = match enc { - #[cfg(feature="brotli")] - ContentEncoding::Br => Decoder::Br( - Box::new(BrotliDecoder::new(Writer::new()))), - ContentEncoding::Deflate => Decoder::Deflate( - Box::new(DeflateDecoder::new(Writer::new()))), + #[cfg(feature = "brotli")] + ContentEncoding::Br => { + Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) + } + ContentEncoding::Deflate => { + Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new()))) + } ContentEncoding::Gzip => Decoder::Gzip(None), _ => Decoder::Identity, }; - PayloadStream{ decoder: dec, dst: BytesMut::new() } + PayloadStream { + decoder: dec, + dst: BytesMut::new(), + } } } impl PayloadStream { - pub fn feed_eof(&mut self) -> io::Result> { match self.decoder { - #[cfg(feature="brotli")] - Decoder::Br(ref mut decoder) => { - match decoder.finish() { - Ok(mut writer) => { - let b = writer.take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(e) => Err(e), + #[cfg(feature = "brotli")] + Decoder::Br(ref mut decoder) => match decoder.finish() { + Ok(mut writer) => { + let b = writer.take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } } + Err(e) => Err(e), }, Decoder::Gzip(ref mut decoder) => { if let Some(ref mut decoder) = *decoder { decoder.as_mut().get_mut().eof = true; self.dst.reserve(8192); - match decoder.read(unsafe{self.dst.bytes_mut()}) { - Ok(n) => { - unsafe{self.dst.advance_mut(n)}; - return Ok(Some(self.dst.take().freeze())) + match decoder.read(unsafe { self.dst.bytes_mut() }) { + Ok(n) => { + unsafe { self.dst.advance_mut(n) }; + return Ok(Some(self.dst.take().freeze())); } - Err(e) => - return Err(e), + Err(e) => return Err(e), } } else { Ok(None) } - }, - Decoder::Deflate(ref mut decoder) => { - match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(e) => Err(e), + } + Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } } + Err(e) => Err(e), }, Decoder::Identity => Ok(None), } @@ -282,66 +284,67 @@ impl PayloadStream { pub fn feed_data(&mut self, data: Bytes) -> io::Result> { match self.decoder { - #[cfg(feature="brotli")] - Decoder::Br(ref mut decoder) => { - match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(e) => Err(e) + #[cfg(feature = "brotli")] + Decoder::Br(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } } + Err(e) => Err(e), }, Decoder::Gzip(ref mut decoder) => { if decoder.is_none() { - *decoder = Some( - Box::new(GzDecoder::new( - Wrapper{buf: BytesMut::from(data), eof: false}))); + *decoder = Some(Box::new(GzDecoder::new(Wrapper { + buf: BytesMut::from(data), + eof: false, + }))); } else { let _ = decoder.as_mut().unwrap().write(&data); } loop { self.dst.reserve(8192); - match decoder.as_mut() - .as_mut().unwrap().read(unsafe{self.dst.bytes_mut()}) + match decoder + .as_mut() + .as_mut() + .unwrap() + .read(unsafe { self.dst.bytes_mut() }) { - Ok(n) => { + Ok(n) => { if n != 0 { - unsafe{self.dst.advance_mut(n)}; + unsafe { self.dst.advance_mut(n) }; } if n == 0 { return Ok(Some(self.dst.take().freeze())); } } Err(e) => { - if e.kind() == io::ErrorKind::WouldBlock && !self.dst.is_empty() + if e.kind() == io::ErrorKind::WouldBlock + && !self.dst.is_empty() { return Ok(Some(self.dst.take().freeze())); } - return Err(e) + return Err(e); } } } - }, - Decoder::Deflate(ref mut decoder) => { - match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - }, - Err(e) => Err(e), + } + Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } } + Err(e) => Err(e), }, Decoder::Identity => Ok(Some(data)), } @@ -351,33 +354,33 @@ impl PayloadStream { pub(crate) enum ContentEncoder { Deflate(DeflateEncoder), Gzip(GzEncoder), - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] Br(BrotliEncoder), Identity(TransferEncoding), } impl ContentEncoder { - pub fn empty(bytes: SharedBytes) -> ContentEncoder { ContentEncoder::Identity(TransferEncoding::eof(bytes)) } - pub fn for_server(buf: SharedBytes, - req: &HttpInnerMessage, - resp: &mut HttpResponse, - response_encoding: ContentEncoding) -> ContentEncoder - { + pub fn for_server( + buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse, + response_encoding: ContentEncoding, + ) -> ContentEncoder { let version = resp.version().unwrap_or_else(|| req.version); let is_head = req.method == Method::HEAD; let mut body = resp.replace_body(Body::Empty); let has_body = match body { Body::Empty => false, - Body::Binary(ref bin) => - !(response_encoding == ContentEncoding::Auto && bin.len() < 96), + Body::Binary(ref bin) => { + !(response_encoding == ContentEncoding::Auto && bin.len() < 96) + } _ => true, }; - // Enable content encoding only if response does not contain Content-Encoding header + // Enable content encoding only if response does not contain Content-Encoding + // header let mut encoding = if has_body { let encoding = match response_encoding { ContentEncoding::Auto => { @@ -396,7 +399,9 @@ impl ContentEncoder { }; if encoding.is_compression() { resp.headers_mut().insert( - CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); + CONTENT_ENCODING, + HeaderValue::from_static(encoding.as_str()), + ); } encoding } else { @@ -409,23 +414,27 @@ impl ContentEncoder { resp.headers_mut().remove(CONTENT_LENGTH); } TransferEncoding::length(0, buf) - }, + } Body::Binary(ref mut bytes) => { if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) + || encoding == ContentEncoding::Auto) { let tmp = SharedBytes::default(); let transfer = TransferEncoding::eof(tmp.clone()); let mut enc = match encoding { ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::fast())), - ContentEncoding::Gzip => ContentEncoder::Gzip( - GzEncoder::new(transfer, Compression::fast())), - #[cfg(feature="brotli")] - ContentEncoding::Br => ContentEncoder::Br( - BrotliEncoder::new(transfer, 3)), + DeflateEncoder::new(transfer, Compression::fast()), + ), + ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( + transfer, + Compression::fast(), + )), + #[cfg(feature = "brotli")] + ContentEncoding::Br => { + ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) + } ContentEncoding::Identity => ContentEncoder::Identity(transfer), - ContentEncoding::Auto => unreachable!() + ContentEncoding::Auto => unreachable!(), }; // TODO return error! let _ = enc.write(bytes.clone()); @@ -438,7 +447,9 @@ impl ContentEncoder { let mut b = BytesMut::new(); let _ = write!(b, "{}", bytes.len()); resp.headers_mut().insert( - CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); + CONTENT_LENGTH, + HeaderValue::try_from(b.freeze()).unwrap(), + ); } else { // resp.headers_mut().remove(CONTENT_LENGTH); } @@ -449,8 +460,8 @@ impl ContentEncoder { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); } else { - resp.headers_mut().insert( - CONNECTION, HeaderValue::from_static("upgrade")); + resp.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("upgrade")); } if encoding != ContentEncoding::Identity { encoding = ContentEncoding::Identity; @@ -470,20 +481,24 @@ impl ContentEncoder { } match encoding { - ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::fast())), - ContentEncoding::Gzip => ContentEncoder::Gzip( - GzEncoder::new(transfer, Compression::fast())), - #[cfg(feature="brotli")] - ContentEncoding::Br => ContentEncoder::Br( - BrotliEncoder::new(transfer, 3)), - ContentEncoding::Identity | ContentEncoding::Auto => - ContentEncoder::Identity(transfer), + ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( + transfer, + Compression::fast(), + )), + ContentEncoding::Gzip => { + ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast())) + } + #[cfg(feature = "brotli")] + ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)), + ContentEncoding::Identity | ContentEncoding::Auto => { + ContentEncoder::Identity(transfer) + } } } - fn streaming_encoding(buf: SharedBytes, version: Version, - resp: &mut HttpResponse) -> TransferEncoding { + fn streaming_encoding( + buf: SharedBytes, version: Version, resp: &mut HttpResponse + ) -> TransferEncoding { match resp.chunked() { Some(true) => { // Enable transfer encoding @@ -492,13 +507,12 @@ impl ContentEncoder { resp.headers_mut().remove(TRANSFER_ENCODING); TransferEncoding::eof(buf) } else { - resp.headers_mut().insert( - TRANSFER_ENCODING, HeaderValue::from_static("chunked")); + resp.headers_mut() + .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); TransferEncoding::chunked(buf) } - }, - Some(false) => - TransferEncoding::eof(buf), + } + Some(false) => TransferEncoding::eof(buf), None => { // if Content-Length is specified, then use it as length hint let (len, chunked) = @@ -530,9 +544,11 @@ impl ContentEncoder { match version { Version::HTTP_11 => { resp.headers_mut().insert( - TRANSFER_ENCODING, HeaderValue::from_static("chunked")); + TRANSFER_ENCODING, + HeaderValue::from_static("chunked"), + ); TransferEncoding::chunked(buf) - }, + } _ => { resp.headers_mut().remove(TRANSFER_ENCODING); TransferEncoding::eof(buf) @@ -545,11 +561,10 @@ impl ContentEncoder { } impl ContentEncoder { - #[inline] pub fn is_eof(&self) -> bool { match *self { - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(), ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(), ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(), @@ -561,39 +576,35 @@ impl ContentEncoder { #[inline(always)] pub fn write_eof(&mut self) -> Result<(), io::Error> { let encoder = mem::replace( - self, ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty()))); + self, + ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())), + ); match encoder { - #[cfg(feature="brotli")] - ContentEncoder::Br(encoder) => { - match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(()) - }, - Err(err) => Err(err), - } - } - ContentEncoder::Gzip(encoder) => { - match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(()) - }, - Err(err) => Err(err), + #[cfg(feature = "brotli")] + ContentEncoder::Br(encoder) => match encoder.finish() { + Ok(mut writer) => { + writer.encode_eof(); + *self = ContentEncoder::Identity(writer); + Ok(()) } + Err(err) => Err(err), }, - ContentEncoder::Deflate(encoder) => { - match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(()) - }, - Err(err) => Err(err), + ContentEncoder::Gzip(encoder) => match encoder.finish() { + Ok(mut writer) => { + writer.encode_eof(); + *self = ContentEncoder::Identity(writer); + Ok(()) } + Err(err) => Err(err), + }, + ContentEncoder::Deflate(encoder) => match encoder.finish() { + Ok(mut writer) => { + writer.encode_eof(); + *self = ContentEncoder::Identity(writer); + Ok(()) + } + Err(err) => Err(err), }, ContentEncoder::Identity(mut writer) => { writer.encode_eof(); @@ -607,23 +618,23 @@ impl ContentEncoder { #[inline(always)] pub fn write(&mut self, data: Binary) -> Result<(), io::Error> { match *self { - #[cfg(feature="brotli")] + #[cfg(feature = "brotli")] ContentEncoder::Br(ref mut encoder) => { match encoder.write_all(data.as_ref()) { Ok(_) => Ok(()), Err(err) => { trace!("Error decoding br encoding: {}", err); Err(err) - }, + } } - }, + } ContentEncoder::Gzip(ref mut encoder) => { match encoder.write_all(data.as_ref()) { Ok(_) => Ok(()), Err(err) => { trace!("Error decoding gzip encoding: {}", err); Err(err) - }, + } } } ContentEncoder::Deflate(ref mut encoder) => { @@ -632,7 +643,7 @@ impl ContentEncoder { Err(err) => { trace!("Error decoding deflate encoding: {}", err); Err(err) - }, + } } } ContentEncoder::Identity(ref mut encoder) => { @@ -665,7 +676,6 @@ enum TransferEncodingKind { } impl TransferEncoding { - #[inline] pub fn eof(bytes: SharedBytes) -> TransferEncoding { TransferEncoding { @@ -707,7 +717,7 @@ impl TransferEncoding { let eof = msg.is_empty(); self.buffer.extend(msg); Ok(eof) - }, + } TransferEncodingKind::Chunked(ref mut eof) => { if *eof { return Ok(true); @@ -726,21 +736,22 @@ impl TransferEncoding { self.buffer.extend_from_slice(b"\r\n"); } Ok(*eof) - }, + } TransferEncodingKind::Length(ref mut remaining) => { if *remaining > 0 { if msg.is_empty() { - return Ok(*remaining == 0) + return Ok(*remaining == 0); } let len = cmp::min(*remaining, msg.len() as u64); - self.buffer.extend(msg.take().split_to(len as usize).into()); + self.buffer + .extend(msg.take().split_to(len as usize).into()); *remaining -= len as u64; Ok(*remaining == 0) } else { Ok(true) } - }, + } } } @@ -754,13 +765,12 @@ impl TransferEncoding { *eof = true; self.buffer.extend_from_slice(b"0\r\n\r\n"); } - }, + } } } } impl io::Write for TransferEncoding { - #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { self.encode(Binary::from_slice(buf))?; @@ -773,7 +783,6 @@ impl io::Write for TransferEncoding { } } - struct AcceptEncoding { encoding: ContentEncoding, quality: f64, @@ -817,27 +826,31 @@ impl AcceptEncoding { _ => match f64::from_str(parts[1]) { Ok(q) => q, Err(_) => 0.0, - } + }, }; - Some(AcceptEncoding{ encoding, quality }) + Some(AcceptEncoding { + encoding, + quality, + }) } /// Parse a raw Accept-Encoding header value into an ordered list. pub fn parse(raw: &str) -> ContentEncoding { - let mut encodings: Vec<_> = - raw.replace(' ', "").split(',').map(|l| AcceptEncoding::new(l)).collect(); + let mut encodings: Vec<_> = raw.replace(' ', "") + .split(',') + .map(|l| AcceptEncoding::new(l)) + .collect(); encodings.sort(); for enc in encodings { if let Some(enc) = enc { - return enc.encoding + return enc.encoding; } } ContentEncoding::Identity } } - #[cfg(test)] mod tests { use super::*; @@ -846,9 +859,13 @@ mod tests { fn test_chunked_te() { let bytes = SharedBytes::default(); let mut enc = TransferEncoding::chunked(bytes.clone()); - assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap()); + assert!(!enc.encode(Binary::from(b"test".as_ref())) + .ok() + .unwrap()); assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap()); - assert_eq!(bytes.get_mut().take().freeze(), - Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n")); + assert_eq!( + bytes.get_mut().take().freeze(), + Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") + ); } } diff --git a/src/server/h1.rs b/src/server/h1.rs index 66fdf8a7..aa27d029 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -1,30 +1,30 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] -use std::{self, io}; -use std::rc::Rc; -use std::net::SocketAddr; -use std::time::Duration; use std::collections::VecDeque; +use std::net::SocketAddr; +use std::rc::Rc; +use std::time::Duration; +use std::{self, io}; use actix::Arbiter; -use httparse; -use http::{Uri, Method, Version, HttpTryFrom, HeaderMap}; -use http::header::{self, HeaderName, HeaderValue}; use bytes::{Bytes, BytesMut}; -use futures::{Future, Poll, Async}; +use futures::{Async, Future, Poll}; +use http::header::{self, HeaderName, HeaderValue}; +use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use httparse; use tokio_core::reactor::Timeout; -use pipeline::Pipeline; +use error::{ParseError, PayloadError, ResponseError}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use error::{ParseError, PayloadError, ResponseError}; -use payload::{Payload, PayloadWriter, PayloadStatus}; +use payload::{Payload, PayloadStatus, PayloadWriter}; +use pipeline::Pipeline; -use super::{utils, Writer}; -use super::h1writer::H1Writer; use super::encoding::PayloadType; +use super::h1writer::H1Writer; use super::settings::WorkerSettings; use super::{HttpHandler, HttpHandlerTask, IoStream}; +use super::{utils, Writer}; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; @@ -64,21 +64,24 @@ struct Entry { } impl Http1 - where T: IoStream, H: HttpHandler + 'static +where + T: IoStream, + H: HttpHandler + 'static, { - pub fn new(settings: Rc>, - stream: T, - addr: Option, read_buf: BytesMut) -> Self - { + pub fn new( + settings: Rc>, stream: T, addr: Option, + read_buf: BytesMut, + ) -> Self { let bytes = settings.get_shared_bytes(); - Http1{ flags: Flags::KEEPALIVE, - stream: H1Writer::new(stream, bytes, Rc::clone(&settings)), - reader: Reader::new(), - tasks: VecDeque::new(), - keepalive_timer: None, - addr, - read_buf, - settings, + Http1 { + flags: Flags::KEEPALIVE, + stream: H1Writer::new(stream, bytes, Rc::clone(&settings)), + reader: Reader::new(), + tasks: VecDeque::new(), + keepalive_timer: None, + addr, + read_buf, + settings, } } @@ -110,7 +113,7 @@ impl Http1 Ok(Async::Ready(_)) => return Ok(Async::Ready(())), Err(err) => { debug!("Error sending data: {}", err); - return Err(()) + return Err(()); } } } @@ -120,8 +123,8 @@ impl Http1 Async::Ready(true) => (), Async::Ready(false) => { self.flags.insert(Flags::SHUTDOWN); - return self.poll() - }, + return self.poll(); + } Async::NotReady => return Ok(Async::NotReady), } } @@ -130,12 +133,15 @@ impl Http1 // TODO: refactor pub fn poll_io(&mut self) -> Poll { // read incoming data - let need_read = if !self.flags.intersects(Flags::ERROR) && - self.tasks.len() < MAX_PIPELINED_MESSAGES + let need_read = if !self.flags.intersects(Flags::ERROR) + && self.tasks.len() < MAX_PIPELINED_MESSAGES { 'outer: loop { - match self.reader.parse(self.stream.get_mut(), - &mut self.read_buf, &self.settings) { + match self.reader.parse( + self.stream.get_mut(), + &mut self.read_buf, + &self.settings, + ) { Ok(Async::Ready(mut req)) => { self.flags.insert(Flags::STARTED); @@ -149,19 +155,22 @@ impl Http1 for h in self.settings.handlers().iter_mut() { req = match h.handle(req) { Ok(pipe) => { - self.tasks.push_back( - Entry {pipe, flags: EntryFlags::empty()}); - continue 'outer - }, + self.tasks.push_back(Entry { + pipe, + flags: EntryFlags::empty(), + }); + continue 'outer; + } Err(req) => req, } } - self.tasks.push_back( - Entry {pipe: Pipeline::error(HttpResponse::NotFound()), - flags: EntryFlags::empty()}); - continue - }, + self.tasks.push_back(Entry { + pipe: Pipeline::error(HttpResponse::NotFound()), + flags: EntryFlags::empty(), + }); + continue; + } Ok(Async::NotReady) => (), Err(err) => { trace!("Parse error: {:?}", err); @@ -176,23 +185,24 @@ impl Http1 self.flags.remove(Flags::KEEPALIVE); self.keepalive_timer.take(); - // on parse error, stop reading stream but tasks need to be completed + // on parse error, stop reading stream but tasks need to be + // completed self.flags.insert(Flags::ERROR); match err { ReaderError::Disconnect => (), - _ => - if self.tasks.is_empty() { - if let ReaderError::Error(err) = err { - self.tasks.push_back( - Entry {pipe: Pipeline::error(err.error_response()), - flags: EntryFlags::empty()}); - } + _ => if self.tasks.is_empty() { + if let ReaderError::Error(err) = err { + self.tasks.push_back(Entry { + pipe: Pipeline::error(err.error_response()), + flags: EntryFlags::empty(), + }); } + }, } - }, + } } - break + break; } false } else { @@ -211,9 +221,9 @@ impl Http1 // io is corrupted, send buffer if item.flags.contains(EntryFlags::ERROR) { if let Ok(Async::NotReady) = self.stream.poll_completed(true) { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } - return Err(()) + return Err(()); } match item.pipe.poll_io(&mut self.stream) { @@ -228,11 +238,12 @@ impl Http1 self.stream.reset(); if ready { - item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED); + item.flags + .insert(EntryFlags::EOF | EntryFlags::FINISHED); } else { item.flags.insert(EntryFlags::FINISHED); } - }, + } // no more IO for this iteration Ok(Async::NotReady) => { if self.reader.need_read() == PayloadStatus::Read && !retry { @@ -248,9 +259,9 @@ impl Http1 // check stream state, we still can have valid data in buffer if let Ok(Async::NotReady) = self.stream.poll_completed(true) { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } - return Err(()) + return Err(()); } } } else if !item.flags.contains(EntryFlags::FINISHED) { @@ -269,15 +280,18 @@ impl Http1 // cleanup finished tasks let mut popped = false; while !self.tasks.is_empty() { - if self.tasks[0].flags.contains(EntryFlags::EOF | EntryFlags::FINISHED) { + if self.tasks[0] + .flags + .contains(EntryFlags::EOF | EntryFlags::FINISHED) + { popped = true; self.tasks.pop_front(); } else { - break + break; } } if need_read && popped { - return self.poll_io() + return self.poll_io(); } // check stream state @@ -286,7 +300,7 @@ impl Http1 Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); - return Err(()) + return Err(()); } _ => (), } @@ -295,20 +309,21 @@ impl Http1 // deal with keep-alive if self.tasks.is_empty() { // no keep-alive situations - if self.flags.contains(Flags::ERROR) || - (!self.flags.contains(Flags::KEEPALIVE) - || !self.settings.keep_alive_enabled()) && - self.flags.contains(Flags::STARTED) + if self.flags.contains(Flags::ERROR) + || (!self.flags.contains(Flags::KEEPALIVE) + || !self.settings.keep_alive_enabled()) + && self.flags.contains(Flags::STARTED) { - return Ok(Async::Ready(false)) + return Ok(Async::Ready(false)); } // start keep-alive timer let keep_alive = self.settings.keep_alive(); if self.keepalive_timer.is_none() && keep_alive > 0 { trace!("Start keep-alive timer"); - let mut timer = Timeout::new( - Duration::new(keep_alive, 0), Arbiter::handle()).unwrap(); + let mut timer = + Timeout::new(Duration::new(keep_alive, 0), Arbiter::handle()) + .unwrap(); // register timer let _ = timer.poll(); self.keepalive_timer = Some(timer); @@ -342,9 +357,7 @@ enum ReaderError { impl Reader { pub fn new() -> Reader { - Reader { - payload: None, - } + Reader { payload: None } } #[inline] @@ -357,36 +370,37 @@ impl Reader { } #[inline] - fn decode(&mut self, buf: &mut BytesMut, payload: &mut PayloadInfo) - -> Result - { + fn decode( + &mut self, buf: &mut BytesMut, payload: &mut PayloadInfo + ) -> Result { while !buf.is_empty() { match payload.decoder.decode(buf) { Ok(Async::Ready(Some(bytes))) => { payload.tx.feed_data(bytes); if payload.decoder.is_eof() { payload.tx.feed_eof(); - return Ok(Decoding::Ready) + return Ok(Decoding::Ready); } - }, + } Ok(Async::Ready(None)) => { payload.tx.feed_eof(); - return Ok(Decoding::Ready) - }, + return Ok(Decoding::Ready); + } Ok(Async::NotReady) => return Ok(Decoding::NotReady), Err(err) => { payload.tx.set_error(err.into()); - return Err(ReaderError::Payload) + return Err(ReaderError::Payload); } } } Ok(Decoding::NotReady) } - pub fn parse(&mut self, io: &mut T, - buf: &mut BytesMut, - settings: &WorkerSettings) -> Poll - where T: IoStream + pub fn parse( + &mut self, io: &mut T, buf: &mut BytesMut, settings: &WorkerSettings + ) -> Poll + where + T: IoStream, { match self.need_read() { PayloadStatus::Read => (), @@ -403,14 +417,14 @@ impl Reader { payload.tx.set_error(PayloadError::Incomplete); // http channel should not deal with payload errors - return Err(ReaderError::Payload) - }, + return Err(ReaderError::Payload); + } Ok(Async::NotReady) => true, Err(err) => { payload.tx.set_error(err.into()); // http channel should not deal with payload errors - return Err(ReaderError::Payload) + return Err(ReaderError::Payload); } _ => false, }; @@ -420,24 +434,24 @@ impl Reader { payload.tx.feed_data(bytes); if payload.decoder.is_eof() { payload.tx.feed_eof(); - break 'buf true + break 'buf true; } - }, + } Ok(Async::Ready(None)) => { payload.tx.feed_eof(); - break 'buf true - }, + break 'buf true; + } Ok(Async::NotReady) => { // if buffer is full then // socket still can contain more data if not_ready { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } - continue 'buf - }, + continue 'buf; + } Err(err) => { payload.tx.set_error(err.into()); - return Err(ReaderError::Payload) + return Err(ReaderError::Payload); } } } @@ -446,7 +460,9 @@ impl Reader { false } }; - if done { self.payload = None } + if done { + self.payload = None + } // if buf is empty parse_message will always return NotReady, let's avoid that if buf.is_empty() { @@ -454,7 +470,7 @@ impl Reader { Ok(Async::Ready(0)) => return Err(ReaderError::Disconnect), Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(ReaderError::Error(err.into())) + Err(err) => return Err(ReaderError::Error(err.into())), } }; @@ -469,7 +485,7 @@ impl Reader { } } return Ok(Async::Ready(msg)); - }, + } Async::NotReady => { if buf.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); @@ -479,18 +495,19 @@ impl Reader { 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 parse_message(buf: &mut BytesMut, settings: &WorkerSettings) - -> Poll<(HttpRequest, Option), ParseError> { + fn parse_message( + buf: &mut BytesMut, settings: &WorkerSettings + ) -> Poll<(HttpRequest, Option), ParseError> { // Parse http message let mut has_te = false; let mut has_upgrade = false; @@ -498,15 +515,17 @@ impl Reader { let msg = { let bytes_ptr = buf.as_ref().as_ptr() as usize; let mut headers: [httparse::Header; MAX_HEADERS] = - unsafe{std::mem::uninitialized()}; + unsafe { std::mem::uninitialized() }; let (len, method, path, version, headers_len) = { - let b = unsafe{ let b: &[u8] = buf; std::mem::transmute(b) }; + 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::from_bytes( - req.method.unwrap().as_bytes()) + let method = Method::from_bytes(req.method.unwrap().as_bytes()) .map_err(|_| ParseError::Method)?; let path = Uri::try_from(req.path.unwrap())?; let version = if req.version.unwrap() == 1 { @@ -535,10 +554,12 @@ impl Reader { let v_end = v_start + header.value.len(); let value = unsafe { HeaderValue::from_shared_unchecked( - slice.slice(v_start, v_end)) }; + slice.slice(v_start, v_end), + ) + }; msg_mut.headers.append(name, value); } else { - return Err(ParseError::Header) + return Err(ParseError::Header); } } @@ -555,17 +576,20 @@ impl Reader { Some(Decoder::chunked()) } else if has_length { // Content-Length - let len = msg.get_ref().headers.get(header::CONTENT_LENGTH).unwrap(); + let len = msg.get_ref() + .headers + .get(header::CONTENT_LENGTH) + .unwrap(); 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) + return Err(ParseError::Header); } } else { debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header) + return Err(ParseError::Header); } } else if has_upgrade || msg.get_ref().method == Method::CONNECT { // upgrade(websocket) or connect @@ -581,7 +605,10 @@ impl Reader { decoder, }; msg.get_mut().payload = Some(payload); - Ok(Async::Ready((HttpRequest::from_message(msg), Some(info)))) + Ok(Async::Ready(( + HttpRequest::from_message(msg), + Some(info), + ))) } else { Ok(Async::Ready((HttpRequest::from_message(msg), None))) } @@ -612,21 +639,28 @@ pub struct Decoder { impl Decoder { pub fn length(x: u64) -> Decoder { - Decoder { kind: Kind::Length(x) } + Decoder { + kind: Kind::Length(x), + } } pub fn chunked() -> Decoder { - Decoder { kind: Kind::Chunked(ChunkedState::Size, 0) } + Decoder { + kind: Kind::Chunked(ChunkedState::Size, 0), + } } pub fn eof() -> Decoder { - Decoder { kind: Kind::Eof(false) } + Decoder { + kind: Kind::Eof(false), + } } } #[derive(Debug, Clone, PartialEq)] enum Kind { - /// A Reader used when a Content-Length header is passed with a positive integer. + /// A Reader used when a Content-Length header is passed with a positive + /// integer. Length(u64), /// A Reader used when Transfer-Encoding is `chunked`. Chunked(ChunkedState, u64), @@ -664,7 +698,9 @@ enum ChunkedState { impl Decoder { pub fn is_eof(&self) -> bool { match self.kind { - Kind::Length(0) | Kind::Chunked(ChunkedState::End, _) | Kind::Eof(true) => true, + Kind::Length(0) | Kind::Chunked(ChunkedState::End, _) | Kind::Eof(true) => { + true + } _ => false, } } @@ -676,7 +712,7 @@ impl Decoder { Ok(Async::Ready(None)) } else { if body.is_empty() { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } let len = body.len() as u64; let buf; @@ -734,9 +770,9 @@ macro_rules! byte ( ); impl ChunkedState { - fn step(&self, body: &mut BytesMut, size: &mut u64, buf: &mut Option) - -> Poll - { + fn step( + &self, body: &mut BytesMut, size: &mut u64, buf: &mut Option + ) -> Poll { use self::ChunkedState::*; match *self { Size => ChunkedState::read_size(body, size), @@ -770,8 +806,10 @@ impl ChunkedState { b';' => return Ok(Async::Ready(ChunkedState::Extension)), b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)), _ => { - return Err(io::Error::new(io::ErrorKind::InvalidInput, - "Invalid chunk size line: Invalid Size")); + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size line: Invalid Size", + )); } } Ok(Async::Ready(ChunkedState::Size)) @@ -783,10 +821,10 @@ impl ChunkedState { b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)), b';' => Ok(Async::Ready(ChunkedState::Extension)), b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), - _ => { - Err(io::Error::new(io::ErrorKind::InvalidInput, - "Invalid chunk size linear white space")) - } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size linear white space", + )), } } fn read_extension(rdr: &mut BytesMut) -> Poll { @@ -795,17 +833,22 @@ impl ChunkedState { _ => Ok(Async::Ready(ChunkedState::Extension)), // no supported extensions } } - fn read_size_lf(rdr: &mut BytesMut, size: &mut u64) -> Poll { + fn read_size_lf( + rdr: &mut BytesMut, size: &mut u64 + ) -> Poll { match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), - _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk size LF")), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size LF", + )), } } - fn read_body(rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option) - -> Poll - { + fn read_body( + rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option + ) -> Poll { trace!("Chunked read, remaining={:?}", rem); let len = rdr.len() as u64; @@ -832,41 +875,53 @@ impl ChunkedState { fn read_body_cr(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)), - _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk body CR")), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk body CR", + )), } } fn read_body_lf(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\n' => Ok(Async::Ready(ChunkedState::Size)), - _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk body LF")), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk body LF", + )), } } fn read_end_cr(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\r' => Ok(Async::Ready(ChunkedState::EndLf)), - _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk end CR")), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk end CR", + )), } } fn read_end_lf(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\n' => Ok(Async::Ready(ChunkedState::End)), - _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk end LF")), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk end LF", + )), } } } #[cfg(test)] mod tests { - use std::{io, cmp, time}; - use std::net::Shutdown; - use bytes::{Bytes, BytesMut, Buf}; + use bytes::{Buf, Bytes, BytesMut}; use futures::{Async, Stream}; + use http::{Method, Version}; + use std::net::Shutdown; + use std::{cmp, io, time}; use tokio_io::{AsyncRead, AsyncWrite}; - use http::{Version, Method}; use super::*; - use httpmessage::HttpMessage; use application::HttpApplication; + use httpmessage::HttpMessage; use server::settings::WorkerSettings; use server::{IoStream, KeepAlive}; @@ -919,70 +974,77 @@ mod tests { } } impl io::Write for Buffer { - fn write(&mut self, buf: &[u8]) -> io::Result {Ok(buf.len())} - fn flush(&mut self) -> io::Result<()> {Ok(())} + 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 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) => (), - Err(err) => unreachable!("Unexpected error: {:?}", err), - _ => unreachable!("Should not be ready"), - }) + ($e:expr) => { + match $e { + Ok(Async::NotReady) => (), + Err(err) => unreachable!("Unexpected error: {:?}", err), + _ => unreachable!("Should not be ready"), + } + }; } macro_rules! parse_ready { - ($e:expr) => ({ - let settings = WorkerSettings::::new( - Vec::new(), KeepAlive::Os); + ($e:expr) => {{ + let settings: WorkerSettings = + WorkerSettings::new(Vec::new(), KeepAlive::Os); match Reader::new().parse($e, &mut BytesMut::new(), &settings) { Ok(Async::Ready(req)) => req, Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), } - }) + }}; } macro_rules! reader_parse_ready { - ($e:expr) => ( + ($e:expr) => { match $e { Ok(Async::Ready(req)) => req, Ok(_) => unreachable!("Eof during parsing http request"), - Err(err) => unreachable!("Error during parsing http request: {:?}", err), + Err(err) => { + unreachable!("Error during parsing http request: {:?}", err) + } } - ) + }; } macro_rules! expect_parse_err { - ($e:expr) => ({ + ($e:expr) => {{ let mut buf = BytesMut::new(); - let settings = WorkerSettings::::new( - Vec::new(), KeepAlive::Os); + let settings: WorkerSettings = + WorkerSettings::new(Vec::new(), KeepAlive::Os); match Reader::new().parse($e, &mut buf, &settings) { Err(err) => match err { ReaderError::Error(_) => (), _ => unreachable!("Parse error expected"), }, - _ => { - unreachable!("Error expected") - } - }} - ) + _ => unreachable!("Error expected"), + } + }}; } #[test] 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(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); match reader.parse(&mut buf, &mut readbuf, &settings) { @@ -999,8 +1061,7 @@ 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(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); match reader.parse(&mut buf, &mut readbuf, &settings) { @@ -1023,8 +1084,7 @@ 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(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); match reader.parse(&mut buf, &mut readbuf, &settings) { @@ -1041,8 +1101,7 @@ 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(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); match reader.parse(&mut buf, &mut readbuf, &settings) { @@ -1058,11 +1117,10 @@ mod tests { #[test] fn test_parse_body_crlf() { - let mut buf = Buffer::new( - "\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); + 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(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); match reader.parse(&mut buf, &mut readbuf, &settings) { @@ -1080,8 +1138,7 @@ 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(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } @@ -1101,8 +1158,7 @@ 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(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } @@ -1119,7 +1175,10 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); + assert_eq!( + req.headers().get("test").unwrap().as_bytes(), + b"value" + ); } Ok(_) | Err(_) => unreachable!("Error during parsing http request"), } @@ -1130,16 +1189,19 @@ mod tests { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ Set-Cookie: c1=cookie1\r\n\ - Set-Cookie: c2=cookie2\r\n\r\n"); + Set-Cookie: c2=cookie2\r\n\r\n", + ); let mut readbuf = BytesMut::new(); - let settings = WorkerSettings::::new( - Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); match reader.parse(&mut buf, &mut readbuf, &settings) { Ok(Async::Ready(req)) => { - let val: Vec<_> = req.headers().get_all("Set-Cookie") - .iter().map(|v| v.to_str().unwrap().to_owned()).collect(); + let val: Vec<_> = req.headers() + .get_all("Set-Cookie") + .iter() + .map(|v| v.to_str().unwrap().to_owned()) + .collect(); assert_eq!(val[0], "c1=cookie1"); assert_eq!(val[1], "c2=cookie2"); } @@ -1167,7 +1229,8 @@ mod tests { fn test_conn_close() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - connection: close\r\n\r\n"); + connection: close\r\n\r\n", + ); let req = parse_ready!(&mut buf); assert!(!req.keep_alive()); @@ -1177,7 +1240,8 @@ mod tests { fn test_conn_close_1_0() { let mut buf = Buffer::new( "GET /test HTTP/1.0\r\n\ - connection: close\r\n\r\n"); + connection: close\r\n\r\n", + ); let req = parse_ready!(&mut buf); assert!(!req.keep_alive()); @@ -1187,7 +1251,8 @@ mod tests { fn test_conn_keep_alive_1_0() { let mut buf = Buffer::new( "GET /test HTTP/1.0\r\n\ - connection: keep-alive\r\n\r\n"); + connection: keep-alive\r\n\r\n", + ); let req = parse_ready!(&mut buf); assert!(req.keep_alive()); @@ -1197,7 +1262,8 @@ mod tests { fn test_conn_keep_alive_1_1() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - connection: keep-alive\r\n\r\n"); + connection: keep-alive\r\n\r\n", + ); let req = parse_ready!(&mut buf); assert!(req.keep_alive()); @@ -1207,7 +1273,8 @@ mod tests { fn test_conn_other_1_0() { let mut buf = Buffer::new( "GET /test HTTP/1.0\r\n\ - connection: other\r\n\r\n"); + connection: other\r\n\r\n", + ); let req = parse_ready!(&mut buf); assert!(!req.keep_alive()); @@ -1217,7 +1284,8 @@ mod tests { fn test_conn_other_1_1() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - connection: other\r\n\r\n"); + connection: other\r\n\r\n", + ); let req = parse_ready!(&mut buf); assert!(req.keep_alive()); @@ -1228,7 +1296,8 @@ mod tests { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ upgrade: websockets\r\n\ - connection: upgrade\r\n\r\n"); + connection: upgrade\r\n\r\n", + ); let req = parse_ready!(&mut buf); assert!(!req.payload().eof()); @@ -1239,7 +1308,8 @@ mod tests { fn test_conn_upgrade_connect_method() { let mut buf = Buffer::new( "CONNECT /test HTTP/1.1\r\n\ - content-type: text/plain\r\n\r\n"); + content-type: text/plain\r\n\r\n", + ); let req = parse_ready!(&mut buf); assert!(req.upgrade()); @@ -1250,7 +1320,8 @@ mod tests { fn test_request_chunked() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"); + transfer-encoding: chunked\r\n\r\n", + ); let req = parse_ready!(&mut buf); if let Ok(val) = req.chunked() { @@ -1262,7 +1333,8 @@ mod tests { // type in chunked let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - transfer-encoding: chnked\r\n\r\n"); + transfer-encoding: chnked\r\n\r\n", + ); let req = parse_ready!(&mut buf); if let Ok(val) = req.chunked() { @@ -1276,7 +1348,8 @@ mod tests { fn test_headers_content_length_err_1() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - content-length: line\r\n\r\n"); + content-length: line\r\n\r\n", + ); expect_parse_err!(&mut buf) } @@ -1285,7 +1358,8 @@ mod tests { fn test_headers_content_length_err_2() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - content-length: -1\r\n\r\n"); + content-length: -1\r\n\r\n", + ); expect_parse_err!(&mut buf); } @@ -1294,7 +1368,8 @@ mod tests { fn test_invalid_header() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - test line\r\n\r\n"); + test line\r\n\r\n", + ); expect_parse_err!(&mut buf); } @@ -1303,7 +1378,8 @@ mod tests { fn test_invalid_name() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - test[]: line\r\n\r\n"); + test[]: line\r\n\r\n", + ); expect_parse_err!(&mut buf); } @@ -1320,28 +1396,34 @@ mod tests { "GET /test HTTP/1.1\r\n\ connection: upgrade\r\n\ upgrade: websocket\r\n\r\n\ - some raw data"); + some raw data", + ); 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().readall().unwrap().as_ref(), + b"some raw data" + ); } #[test] fn test_http_request_parser_utf8() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - x-test: теÑÑ‚\r\n\r\n"); + x-test: теÑÑ‚\r\n\r\n", + ); let req = parse_ready!(&mut buf); - assert_eq!(req.headers().get("x-test").unwrap().as_bytes(), - "теÑÑ‚".as_bytes()); + assert_eq!( + req.headers().get("x-test").unwrap().as_bytes(), + "теÑÑ‚".as_bytes() + ); } #[test] fn test_http_request_parser_two_slashes() { - let mut buf = Buffer::new( - "GET //path HTTP/1.1\r\n\r\n"); + let mut buf = Buffer::new("GET //path HTTP/1.1\r\n\r\n"); let req = parse_ready!(&mut buf); assert_eq!(req.path(), "//path"); @@ -1349,8 +1431,7 @@ mod tests { #[test] fn test_http_request_parser_bad_method() { - let mut buf = Buffer::new( - "!12%()+=~$ /get HTTP/1.1\r\n\r\n"); + let mut buf = Buffer::new("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); expect_parse_err!(&mut buf); } @@ -1366,13 +1447,14 @@ mod tests { fn test_http_request_chunked_payload() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"); + transfer-encoding: chunked\r\n\r\n", + ); let mut readbuf = BytesMut::new(); - let settings = WorkerSettings::::new( - Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); - let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + let mut req = + reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); assert!(!req.payload().eof()); @@ -1380,7 +1462,10 @@ mod tests { let _ = req.payload_mut().poll(); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(!req.payload().eof()); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); + assert_eq!( + req.payload_mut().readall().unwrap().as_ref(), + b"dataline" + ); assert!(req.payload().eof()); } @@ -1388,21 +1473,23 @@ mod tests { fn test_http_request_chunked_payload_and_next_message() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"); + transfer-encoding: chunked\r\n\r\n", + ); let mut readbuf = BytesMut::new(); - let settings = WorkerSettings::::new( - Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); - let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + let mut req = + reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); assert!(!req.payload().eof()); buf.feed_data( "4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ POST /test2 HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"); + transfer-encoding: chunked\r\n\r\n", + ); let _ = req.payload_mut().poll(); let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); @@ -1410,7 +1497,10 @@ mod tests { assert!(req2.chunked().unwrap()); assert!(!req2.payload().eof()); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); + assert_eq!( + req.payload_mut().readall().unwrap().as_ref(), + b"dataline" + ); assert!(req.payload().eof()); } @@ -1418,13 +1508,14 @@ mod tests { fn test_http_request_chunked_payload_chunks() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"); + transfer-encoding: chunked\r\n\r\n", + ); let mut readbuf = BytesMut::new(); - let settings = WorkerSettings::::new( - Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); - let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + let mut req = + reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); req.payload_mut().set_read_buffer_capacity(0); assert!(req.chunked().unwrap()); assert!(!req.payload().eof()); @@ -1457,7 +1548,10 @@ mod tests { let _ = req.payload_mut().poll(); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); + assert_eq!( + req.payload_mut().readall().unwrap().as_ref(), + b"dataline" + ); assert!(!req.payload().eof()); buf.feed_data("\r\n"); @@ -1470,13 +1564,14 @@ mod tests { fn test_parse_chunked_payload_chunk_extension() { let mut buf = Buffer::new( "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"); + transfer-encoding: chunked\r\n\r\n", + ); let mut readbuf = BytesMut::new(); - let settings = WorkerSettings::::new( - Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); let mut reader = Reader::new(); - let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + let mut req = + reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(req.chunked().unwrap()); assert!(!req.payload().eof()); @@ -1484,7 +1579,10 @@ mod tests { let _ = req.payload_mut().poll(); not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); assert!(!req.payload().eof()); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"dataline"); + assert_eq!( + req.payload_mut().readall().unwrap().as_ref(), + b"dataline" + ); assert!(req.payload().eof()); } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index ef2a6089..ee2717bb 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -1,22 +1,22 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] -use std::{io, mem}; -use std::rc::Rc; use bytes::BufMut; use futures::{Async, Poll}; -use tokio_io::AsyncWrite; -use http::{Method, Version}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE}; +use http::{Method, Version}; +use std::rc::Rc; +use std::{io, mem}; +use tokio_io::AsyncWrite; -use body::{Body, Binary}; +use super::encoding::ContentEncoder; +use super::helpers; +use super::settings::WorkerSettings; +use super::shared::SharedBytes; +use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; +use body::{Binary, Body}; use header::ContentEncoding; use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; -use super::helpers; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use super::shared::SharedBytes; -use super::encoding::ContentEncoder; -use super::settings::WorkerSettings; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific @@ -41,10 +41,9 @@ pub(crate) struct H1Writer { } impl H1Writer { - - pub fn new(stream: T, buf: SharedBytes, settings: Rc>) - -> H1Writer - { + pub fn new( + stream: T, buf: SharedBytes, settings: Rc> + ) -> H1Writer { H1Writer { flags: Flags::empty(), encoder: ContentEncoder::empty(buf.clone()), @@ -80,11 +79,11 @@ impl H1Writer { match self.stream.write(&data[written..]) { Ok(0) => { self.disconnected(); - return Err(io::Error::new(io::ErrorKind::WriteZero, "")) - }, + return Err(io::Error::new(io::ErrorKind::WriteZero, "")); + } Ok(n) => { written += n; - }, + } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { return Ok(written) } @@ -96,19 +95,18 @@ impl H1Writer { } impl Writer for H1Writer { - #[inline] fn written(&self) -> u64 { self.written } - fn start(&mut self, - req: &mut HttpInnerMessage, - msg: &mut HttpResponse, - encoding: ContentEncoding) -> io::Result - { + fn start( + &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, + encoding: ContentEncoding, + ) -> io::Result { // prepare task - self.encoder = ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); + self.encoder = + ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { self.flags.insert(Flags::STARTED | Flags::KEEPALIVE); } else { @@ -119,15 +117,18 @@ impl Writer for H1Writer { let version = msg.version().unwrap_or_else(|| req.version); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); - msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade")); + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive else if self.flags.contains(Flags::KEEPALIVE) { if version < Version::HTTP_11 { - msg.headers_mut().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_mut().insert(CONNECTION, HeaderValue::from_static("close")); + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("close")); } let body = msg.replace_body(Body::Empty); @@ -137,12 +138,14 @@ impl Writer for H1Writer { let reason = msg.reason().as_bytes(); let mut is_bin = if let Body::Binary(ref bytes) = body { buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE - + bytes.len() + reason.len()); + 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len() + + reason.len(), + ); true } else { buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len()); + 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), + ); false }; @@ -151,51 +154,50 @@ impl Writer for H1Writer { SharedBytes::extend_from_slice_(buffer, reason); match body { - Body::Empty => - if req.method != Method::HEAD { - SharedBytes::put_slice(buffer, b"\r\ncontent-length: 0\r\n"); - } else { - SharedBytes::put_slice(buffer, b"\r\n"); - }, - Body::Binary(ref bytes) => - helpers::write_content_length(bytes.len(), &mut buffer), - _ => - SharedBytes::put_slice(buffer, b"\r\n"), + Body::Empty => if req.method != Method::HEAD { + SharedBytes::put_slice(buffer, b"\r\ncontent-length: 0\r\n"); + } else { + SharedBytes::put_slice(buffer, b"\r\n"); + }, + Body::Binary(ref bytes) => { + helpers::write_content_length(bytes.len(), &mut buffer) + } + _ => SharedBytes::put_slice(buffer, b"\r\n"), } // write headers let mut pos = 0; let mut has_date = false; let mut remaining = buffer.remaining_mut(); - let mut buf: &mut [u8] = unsafe{ mem::transmute(buffer.bytes_mut()) }; + let mut buf: &mut [u8] = unsafe { mem::transmute(buffer.bytes_mut()) }; for (key, value) in msg.headers() { if is_bin && key == CONTENT_LENGTH { is_bin = false; - continue + continue; } has_date = has_date || key == DATE; let v = value.as_ref(); let k = key.as_str().as_bytes(); let len = k.len() + v.len() + 4; if len > remaining { - unsafe{buffer.advance_mut(pos)}; + unsafe { buffer.advance_mut(pos) }; pos = 0; buffer.reserve(len); remaining = buffer.remaining_mut(); - buf = unsafe{ mem::transmute(buffer.bytes_mut()) }; + buf = unsafe { mem::transmute(buffer.bytes_mut()) }; } - buf[pos..pos+k.len()].copy_from_slice(k); + buf[pos..pos + k.len()].copy_from_slice(k); pos += k.len(); - buf[pos..pos+2].copy_from_slice(b": "); + buf[pos..pos + 2].copy_from_slice(b": "); pos += 2; - buf[pos..pos+v.len()].copy_from_slice(v); + buf[pos..pos + v.len()].copy_from_slice(v); pos += v.len(); - buf[pos..pos+2].copy_from_slice(b"\r\n"); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); pos += 2; remaining -= len; } - unsafe{buffer.advance_mut(pos)}; + unsafe { buffer.advance_mut(pos) }; // optimized date header, set_date writes \r\n if !has_date { @@ -256,8 +258,10 @@ impl Writer for H1Writer { self.encoder.write_eof()?; if !self.encoder.is_eof() { - Err(io::Error::new(io::ErrorKind::Other, - "Last payload item, but eof is not reached")) + Err(io::Error::new( + io::ErrorKind::Other, + "Last payload item, but eof is not reached", + )) } else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE { Ok(WriterState::Pause) } else { @@ -268,11 +272,11 @@ impl Writer for H1Writer { #[inline] fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { if !self.buffer.is_empty() { - let buf: &[u8] = unsafe{mem::transmute(self.buffer.as_ref())}; + let buf: &[u8] = unsafe { mem::transmute(self.buffer.as_ref()) }; let written = self.write_data(buf)?; let _ = self.buffer.split_to(written); if self.buffer.len() > self.buffer_capacity { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } } if shutdown { diff --git a/src/server/h2.rs b/src/server/h2.rs index 77ddf084..08a97626 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -1,30 +1,30 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] -use std::{io, cmp, mem}; -use std::rc::Rc; -use std::io::{Read, Write}; -use std::time::Duration; -use std::net::SocketAddr; use std::collections::VecDeque; +use std::io::{Read, Write}; +use std::net::SocketAddr; +use std::rc::Rc; +use std::time::Duration; +use std::{cmp, io, mem}; use actix::Arbiter; -use modhttp::request::Parts; -use http2::{Reason, RecvStream}; -use http2::server::{self, Connection, Handshake, SendResponse}; use bytes::{Buf, Bytes}; -use futures::{Async, Poll, Future, Stream}; -use tokio_io::{AsyncRead, AsyncWrite}; +use futures::{Async, Future, Poll, Stream}; +use http2::server::{self, Connection, Handshake, SendResponse}; +use http2::{Reason, RecvStream}; +use modhttp::request::Parts; use tokio_core::reactor::Timeout; +use tokio_io::{AsyncRead, AsyncWrite}; -use pipeline::Pipeline; use error::PayloadError; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use payload::{Payload, PayloadWriter, PayloadStatus}; +use payload::{Payload, PayloadStatus, PayloadWriter}; +use pipeline::Pipeline; -use super::h2writer::H2Writer; use super::encoding::PayloadType; +use super::h2writer::H2Writer; use super::settings::WorkerSettings; use super::{HttpHandler, HttpHandlerTask, Writer}; @@ -35,9 +35,10 @@ bitflags! { } /// HTTP/2 Transport -pub(crate) -struct Http2 - where T: AsyncRead + AsyncWrite + 'static, H: 'static +pub(crate) struct Http2 +where + T: AsyncRead + AsyncWrite + 'static, + H: 'static, { flags: Flags, settings: Rc>, @@ -54,20 +55,23 @@ enum State { } impl Http2 - where T: AsyncRead + AsyncWrite + 'static, - H: HttpHandler + 'static +where + T: AsyncRead + AsyncWrite + 'static, + H: HttpHandler + 'static, { - pub fn new(settings: Rc>, - io: T, - addr: Option, buf: Bytes) -> Self - { - Http2{ flags: Flags::empty(), - tasks: VecDeque::new(), - state: State::Handshake( - server::handshake(IoWrapper{unread: Some(buf), inner: io})), - keepalive_timer: None, - addr, - settings, + pub fn new( + settings: Rc>, io: T, addr: Option, buf: Bytes + ) -> Self { + Http2 { + flags: Flags::empty(), + tasks: VecDeque::new(), + state: State::Handshake(server::handshake(IoWrapper { + unread: Some(buf), + inner: io, + })), + keepalive_timer: None, + addr, + settings, } } @@ -89,7 +93,7 @@ impl Http2 match timeout.poll() { Ok(Async::Ready(_)) => { trace!("Keep-alive timeout, close connection"); - return Ok(Async::Ready(())) + return Ok(Async::Ready(())); } Ok(Async::NotReady) => (), Err(_) => unreachable!(), @@ -111,29 +115,30 @@ impl Http2 Ok(Async::Ready(ready)) => { if ready { item.flags.insert( - EntryFlags::EOF | EntryFlags::FINISHED); + EntryFlags::EOF | EntryFlags::FINISHED, + ); } else { item.flags.insert(EntryFlags::EOF); } not_ready = false; - }, + } Ok(Async::NotReady) => { if item.payload.need_read() == PayloadStatus::Read && !retry { - continue + continue; } - }, + } Err(err) => { error!("Unhandled error: {}", err); item.flags.insert( - EntryFlags::EOF | - EntryFlags::ERROR | - EntryFlags::WRITE_DONE); + EntryFlags::EOF | EntryFlags::ERROR + | EntryFlags::WRITE_DONE, + ); item.stream.reset(Reason::INTERNAL_ERROR); } } - break + break; } } else if !item.flags.contains(EntryFlags::FINISHED) { match item.task.poll() { @@ -141,11 +146,12 @@ impl Http2 Ok(Async::Ready(_)) => { not_ready = false; item.flags.insert(EntryFlags::FINISHED); - }, + } Err(err) => { item.flags.insert( - EntryFlags::ERROR | EntryFlags::WRITE_DONE | - EntryFlags::FINISHED); + EntryFlags::ERROR | EntryFlags::WRITE_DONE + | EntryFlags::FINISHED, + ); error!("Unhandled error: {}", err); } } @@ -167,13 +173,13 @@ impl Http2 // cleanup finished tasks while !self.tasks.is_empty() { - if self.tasks[0].flags.contains(EntryFlags::EOF) && - self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) || - self.tasks[0].flags.contains(EntryFlags::ERROR) + if self.tasks[0].flags.contains(EntryFlags::EOF) + && self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) + || self.tasks[0].flags.contains(EntryFlags::ERROR) { self.tasks.pop_front(); } else { - break + break; } } @@ -186,7 +192,7 @@ impl Http2 for entry in &mut self.tasks { entry.task.disconnected() } - }, + } Ok(Async::Ready(Some((req, resp)))) => { not_ready = false; let (parts, body) = req.into_parts(); @@ -194,8 +200,13 @@ impl Http2 // stop keepalive timer self.keepalive_timer.take(); - self.tasks.push_back( - Entry::new(parts, body, resp, self.addr, &self.settings)); + self.tasks.push_back(Entry::new( + parts, + body, + resp, + self.addr, + &self.settings, + )); } Ok(Async::NotReady) => { // start keep-alive timer @@ -213,12 +224,13 @@ impl Http2 } } else { // keep-alive disable, drop connection - return conn.poll_close().map_err( - |e| error!("Error during connection close: {}", e)) + return conn.poll_close().map_err(|e| { + error!("Error during connection close: {}", e) + }); } } else { // keep-alive unset, rely on operating system - return Ok(Async::NotReady) + return Ok(Async::NotReady); } } Err(err) => { @@ -228,16 +240,17 @@ impl Http2 entry.task.disconnected() } self.keepalive_timer.take(); - }, + } } } if not_ready { - if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) { - return conn.poll_close().map_err( - |e| error!("Error during connection close: {}", e)) + if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) + { + return conn.poll_close() + .map_err(|e| error!("Error during connection close: {}", e)); } else { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } } } @@ -246,14 +259,11 @@ impl Http2 // handshake self.state = if let State::Handshake(ref mut handshake) = self.state { match handshake.poll() { - Ok(Async::Ready(conn)) => { - State::Connection(conn) - }, - Ok(Async::NotReady) => - return Ok(Async::NotReady), + Ok(Async::Ready(conn)) => State::Connection(conn), + Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { trace!("Error handling connection: {}", err); - return Err(()) + return Err(()); } } } else { @@ -283,12 +293,12 @@ struct Entry { } impl Entry { - fn new(parts: Parts, - recv: RecvStream, - resp: SendResponse, - addr: Option, - settings: &Rc>) -> Entry - where H: HttpHandler + 'static + fn new( + parts: Parts, recv: RecvStream, resp: SendResponse, + addr: Option, settings: &Rc>, + ) -> Entry + where + H: HttpHandler + 'static, { // Payload and Content-Encoding let (psender, payload) = Payload::new(false); @@ -312,18 +322,22 @@ impl Entry { req = match h.handle(req) { Ok(t) => { task = Some(t); - break - }, + break; + } Err(req) => req, } } - Entry {task: task.unwrap_or_else(|| Pipeline::error(HttpResponse::NotFound())), - payload: psender, - stream: H2Writer::new( - resp, settings.get_shared_bytes(), Rc::clone(settings)), - flags: EntryFlags::empty(), - recv, + Entry { + task: task.unwrap_or_else(|| Pipeline::error(HttpResponse::NotFound())), + payload: psender, + stream: H2Writer::new( + resp, + settings.get_shared_bytes(), + Rc::clone(settings), + ), + flags: EntryFlags::empty(), + recv, } } @@ -340,14 +354,12 @@ impl Entry { match self.recv.poll() { Ok(Async::Ready(Some(chunk))) => { self.payload.feed_data(chunk); - }, + } Ok(Async::Ready(None)) => { self.flags.insert(EntryFlags::REOF); - }, - Ok(Async::NotReady) => (), - Err(err) => { - self.payload.set_error(PayloadError::Http2(err)) } + Ok(Async::NotReady) => (), + Err(err) => self.payload.set_error(PayloadError::Http2(err)), } } } diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 10deadaf..168fd8af 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -1,25 +1,25 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] -use std::{io, cmp}; -use std::rc::Rc; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; -use http2::{Reason, SendStream}; use http2::server::SendResponse; +use http2::{Reason, SendStream}; use modhttp::Response; +use std::rc::Rc; +use std::{cmp, io}; -use http::{Version, HttpTryFrom}; -use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LENGTH}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::{HttpTryFrom, Version}; -use body::{Body, Binary}; +use super::encoding::ContentEncoder; +use super::helpers; +use super::settings::WorkerSettings; +use super::shared::SharedBytes; +use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; +use body::{Binary, Body}; use header::ContentEncoding; use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; -use super::helpers; -use super::encoding::ContentEncoder; -use super::shared::SharedBytes; -use super::settings::WorkerSettings; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; const CHUNK_SIZE: usize = 16_384; @@ -44,10 +44,9 @@ pub(crate) struct H2Writer { } impl H2Writer { - - pub fn new(respond: SendResponse, - buf: SharedBytes, settings: Rc>) -> H2Writer - { + pub fn new( + respond: SendResponse, buf: SharedBytes, settings: Rc> + ) -> H2Writer { H2Writer { respond, settings, @@ -68,19 +67,18 @@ impl H2Writer { } impl Writer for H2Writer { - fn written(&self) -> u64 { self.written } - fn start(&mut self, - req: &mut HttpInnerMessage, - msg: &mut HttpResponse, - encoding: ContentEncoding) -> io::Result - { + fn start( + &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, + encoding: ContentEncoding, + ) -> io::Result { // prepare response self.flags.insert(Flags::STARTED); - self.encoder = ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); + self.encoder = + ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); if let Body::Empty = *msg.body() { self.flags.insert(Flags::EOF); } @@ -93,7 +91,8 @@ impl Writer for H2Writer { if !msg.headers().contains_key(DATE) { let mut bytes = BytesMut::with_capacity(29); self.settings.set_date_simple(&mut bytes); - msg.headers_mut().insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); + msg.headers_mut() + .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); } let body = msg.replace_body(Body::Empty); @@ -104,11 +103,13 @@ impl Writer for H2Writer { let l = val.len(); msg.headers_mut().insert( CONTENT_LENGTH, - HeaderValue::try_from(val.split_to(l-2).freeze()).unwrap()); + HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), + ); } Body::Empty => { - msg.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - }, + msg.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + } _ => (), } @@ -119,11 +120,11 @@ impl Writer for H2Writer { resp.headers_mut().insert(key, value.clone()); } - match self.respond.send_response(resp, self.flags.contains(Flags::EOF)) { - Ok(stream) => - self.stream = Some(stream), - Err(_) => - return Err(io::Error::new(io::ErrorKind::Other, "err")), + match self.respond + .send_response(resp, self.flags.contains(Flags::EOF)) + { + Ok(stream) => self.stream = Some(stream), + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), } trace!("Response: {:?}", msg); @@ -169,8 +170,10 @@ impl Writer for H2Writer { 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")) + Err(io::Error::new( + io::ErrorKind::Other, + "Last payload item, but eof is not reached", + )) } else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE { Ok(WriterState::Pause) } else { @@ -197,17 +200,18 @@ impl Writer for H2Writer { Ok(Async::Ready(Some(cap))) => { let len = self.buffer.len(); let bytes = self.buffer.split_to(cmp::min(cap, len)); - let eof = self.buffer.is_empty() && self.flags.contains(Flags::EOF); + let eof = + self.buffer.is_empty() && self.flags.contains(Flags::EOF); self.written += bytes.len() as u64; if let Err(e) = stream.send_data(bytes.freeze(), eof) { - return Err(io::Error::new(io::ErrorKind::Other, e)) + return Err(io::Error::new(io::ErrorKind::Other, e)); } else if !self.buffer.is_empty() { let cap = cmp::min(self.buffer.len(), CHUNK_SIZE); stream.reserve_capacity(cap); } else { self.flags.remove(Flags::RESERVED); - return Ok(Async::NotReady) + return Ok(Async::NotReady); } } Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), diff --git a/src/server/helpers.rs b/src/server/helpers.rs index c50317a9..bb8730ec 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -1,9 +1,9 @@ -use std::{mem, ptr, slice}; -use std::cell::RefCell; -use std::rc::Rc; -use std::collections::VecDeque; use bytes::{BufMut, BytesMut}; use http::Version; +use std::cell::RefCell; +use std::collections::VecDeque; +use std::rc::Rc; +use std::{mem, ptr, slice}; use httprequest::HttpInnerMessage; @@ -35,7 +35,9 @@ impl SharedMessagePool { } pub(crate) struct SharedHttpInnerMessage( - Option>, Option>); + Option>, + Option>, +); impl Drop for SharedHttpInnerMessage { fn drop(&mut self) { @@ -50,26 +52,25 @@ impl Drop for SharedHttpInnerMessage { } impl Clone for SharedHttpInnerMessage { - fn clone(&self) -> SharedHttpInnerMessage { SharedHttpInnerMessage(self.0.clone(), self.1.clone()) } } impl Default for SharedHttpInnerMessage { - fn default() -> SharedHttpInnerMessage { SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None) } } impl SharedHttpInnerMessage { - pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage { SharedHttpInnerMessage(Some(Rc::new(msg)), None) } - pub fn new(msg: Rc, pool: Rc) -> SharedHttpInnerMessage { + pub fn new( + msg: Rc, pool: Rc + ) -> SharedHttpInnerMessage { SharedHttpInnerMessage(Some(msg), Some(pool)) } @@ -78,7 +79,7 @@ impl SharedHttpInnerMessage { #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] pub fn get_mut(&self) -> &mut HttpInnerMessage { let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref(); - unsafe{mem::transmute(r)} + unsafe { mem::transmute(r) } } #[inline(always)] @@ -88,20 +89,23 @@ impl SharedHttpInnerMessage { } } -const DEC_DIGITS_LUT: &[u8] = - b"0001020304050607080910111213141516171819\ +const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 2021222324252627282930313233343536373839\ 4041424344454647484950515253545556575859\ 6061626364656667686970717273747576777879\ 8081828384858687888990919293949596979899"; pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; 13] = [b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', - 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', - Version::HTTP_09 => {buf[5] = b'0'; buf[7] = b'9';}, + Version::HTTP_09 => { + buf[5] = b'0'; + buf[7] = b'9'; + } _ => (), } @@ -124,7 +128,11 @@ 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 as isize), buf_ptr.offset(curr), 2); + ptr::copy_nonoverlapping( + lut_ptr.offset(d1 as isize), + buf_ptr.offset(curr), + 2, + ); } } @@ -137,30 +145,41 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM /// NOTE: bytes object has to contain enough space 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']; + 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.put_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 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); + DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), + buf.as_mut_ptr().offset(18), + 2, + ); } bytes.put_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']; + 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(19), 2)}; + unsafe { + ptr::copy_nonoverlapping( + DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), + buf.as_mut_ptr().offset(19), + 2, + ) + }; // decode last 1 buf[18] = (n as u8) + b'0'; @@ -216,12 +235,13 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { } unsafe { - bytes.extend_from_slice( - slice::from_raw_parts(buf_ptr.offset(curr), 41 - curr as usize)); + bytes.extend_from_slice(slice::from_raw_parts( + buf_ptr.offset(curr), + 41 - curr as usize, + )); } } - #[cfg(test)] mod tests { use super::*; @@ -231,33 +251,63 @@ mod tests { let mut bytes = BytesMut::new(); bytes.reserve(50); write_content_length(0, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 0\r\n"[..] + ); bytes.reserve(50); write_content_length(9, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 9\r\n"[..] + ); bytes.reserve(50); write_content_length(10, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 10\r\n"[..] + ); bytes.reserve(50); write_content_length(99, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 99\r\n"[..] + ); bytes.reserve(50); write_content_length(100, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 100\r\n"[..] + ); bytes.reserve(50); write_content_length(101, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 101\r\n"[..] + ); bytes.reserve(50); write_content_length(998, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 998\r\n"[..] + ); bytes.reserve(50); write_content_length(1000, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 1000\r\n"[..] + ); bytes.reserve(50); write_content_length(1001, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 1001\r\n"[..] + ); bytes.reserve(50); write_content_length(5909, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 5909\r\n"[..] + ); } } diff --git a/src/server/mod.rs b/src/server/mod.rs index bbaa7f74..85faf77b 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,27 +1,27 @@ //! Http server -use std::{time, io}; use std::net::Shutdown; +use std::{io, time}; use actix; use futures::Poll; -use tokio_io::{AsyncRead, AsyncWrite}; use tokio_core::net::TcpStream; +use tokio_io::{AsyncRead, AsyncWrite}; -mod srv; -mod worker; mod channel; pub(crate) mod encoding; pub(crate) mod h1; -mod h2; mod h1writer; +mod h2; mod h2writer; -mod settings; pub(crate) mod helpers; +mod settings; pub(crate) mod shared; +mod srv; pub(crate) mod utils; +mod worker; -pub use self::srv::HttpServer; pub use self::settings::ServerSettings; +pub use self::srv::HttpServer; use body::Binary; use error::Error; @@ -56,9 +56,10 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// } /// ``` pub fn new(factory: F) -> HttpServer - where F: Fn() -> U + Sync + Send + 'static, - U: IntoIterator + 'static, - H: IntoHttpHandler + 'static +where + F: Fn() -> U + Sync + Send + 'static, + U: IntoIterator + 'static, + H: IntoHttpHandler + 'static, { HttpServer::new(factory) } @@ -107,7 +108,7 @@ pub struct ResumeServer; /// /// If server starts with `spawn()` method, then spawned thread get terminated. pub struct StopServer { - pub graceful: bool + pub graceful: bool, } impl actix::Message for StopServer { @@ -117,7 +118,6 @@ impl actix::Message for StopServer { /// Low level http request handler #[allow(unused_variables)] pub trait HttpHandler: 'static { - /// Handle request fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest>; } @@ -130,7 +130,6 @@ impl HttpHandler for Box { #[doc(hidden)] pub trait HttpHandlerTask { - /// Poll task, this method is used before or after *io* object is available fn poll(&mut self) -> Poll<(), Error>; @@ -170,8 +169,10 @@ pub enum WriterState { pub trait Writer { fn written(&self) -> u64; - fn start(&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, encoding: ContentEncoding) - -> io::Result; + fn start( + &mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, + encoding: ContentEncoding, + ) -> io::Result; fn write(&mut self, payload: Binary) -> io::Result; @@ -207,10 +208,10 @@ impl IoStream for TcpStream { } } -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] use tokio_openssl::SslStream; -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] impl IoStream for SslStream { #[inline] fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { @@ -229,10 +230,10 @@ impl IoStream for SslStream { } } -#[cfg(feature="tls")] +#[cfg(feature = "tls")] use tokio_tls::TlsStream; -#[cfg(feature="tls")] +#[cfg(feature = "tls")] impl IoStream for TlsStream { #[inline] fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { diff --git a/src/server/settings.rs b/src/server/settings.rs index 07b00042..1b57db1a 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -1,19 +1,19 @@ -use std::{fmt, mem, net}; +use bytes::BytesMut; +use futures_cpupool::{Builder, CpuPool}; +use http::StatusCode; +use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; use std::fmt::Write; use std::rc::Rc; use std::sync::Arc; -use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; +use std::{fmt, mem, net}; use time; -use bytes::BytesMut; -use http::StatusCode; -use futures_cpupool::{Builder, CpuPool}; -use super::helpers; use super::KeepAlive; use super::channel::Node; +use super::helpers; use super::shared::{SharedBytes, SharedBytesPool}; use body::Body; -use httpresponse::{HttpResponse, HttpResponsePool, HttpResponseBuilder}; +use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; /// Various server settings #[derive(Clone)] @@ -71,9 +71,9 @@ impl Default for ServerSettings { impl ServerSettings { /// Crate server settings instance - pub(crate) fn new(addr: Option, host: &Option, secure: bool) - -> ServerSettings - { + pub(crate) fn new( + addr: Option, host: &Option, secure: bool + ) -> ServerSettings { let host = if let Some(ref host) = *host { host.clone() } else if let Some(ref addr) = addr { @@ -83,7 +83,13 @@ impl ServerSettings { }; let cpu_pool = Arc::new(InnerCpuPool::new()); let responses = HttpResponsePool::pool(); - ServerSettings { addr, secure, host, cpu_pool, responses } + ServerSettings { + addr, + secure, + host, + cpu_pool, + responses, + } } /// Returns the socket address of the local half of this TCP connection @@ -112,12 +118,13 @@ impl ServerSettings { } #[inline] - pub(crate) fn get_response_builder(&self, status: StatusCode) -> HttpResponseBuilder { + pub(crate) fn get_response_builder( + &self, status: StatusCode + ) -> HttpResponseBuilder { HttpResponsePool::get_builder(&self.responses, status) } } - // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; @@ -141,7 +148,8 @@ impl WorkerSettings { }; WorkerSettings { - keep_alive, ka_enabled, + keep_alive, + ka_enabled, h: RefCell::new(h), bytes: Rc::new(SharedBytesPool::new()), messages: Rc::new(helpers::SharedMessagePool::new()), @@ -176,7 +184,10 @@ impl WorkerSettings { } pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage { - helpers::SharedHttpInnerMessage::new(self.messages.get(), Rc::clone(&self.messages)) + helpers::SharedHttpInnerMessage::new( + self.messages.get(), + Rc::clone(&self.messages), + ) } pub fn add_channel(&self) { @@ -186,26 +197,26 @@ impl WorkerSettings { pub fn remove_channel(&self) { let num = self.channels.get(); if num > 0 { - self.channels.set(num-1); + self.channels.set(num - 1); } else { error!("Number of removed channels is bigger than added channel. Bug in actix-web"); } } pub fn update_date(&self) { - unsafe{&mut *self.date.get()}.update(); + unsafe { &mut *self.date.get() }.update(); } pub fn set_date(&self, dst: &mut BytesMut) { let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(&(unsafe{&*self.date.get()}.bytes)); + buf[6..35].copy_from_slice(&(unsafe { &*self.date.get() }.bytes)); buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } pub fn set_date_simple(&self, dst: &mut BytesMut) { - dst.extend_from_slice(&(unsafe{&*self.date.get()}.bytes)); + dst.extend_from_slice(&(unsafe { &*self.date.get() }.bytes)); } } @@ -216,7 +227,10 @@ struct Date { impl Date { fn new() -> Date { - let mut date = Date{bytes: [0; DATE_VALUE_LENGTH], pos: 0}; + let mut date = Date { + bytes: [0; DATE_VALUE_LENGTH], + pos: 0, + }; date.update(); date } @@ -235,14 +249,16 @@ impl fmt::Write for Date { } } - #[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()); + assert_eq!( + DATE_VALUE_LENGTH, + "Sun, 06 Nov 1994 08:49:37 GMT".len() + ); } #[test] diff --git a/src/server/shared.rs b/src/server/shared.rs index 6773abcd..a3ddc378 100644 --- a/src/server/shared.rs +++ b/src/server/shared.rs @@ -1,12 +1,11 @@ -use std::{io, mem}; -use std::cell::RefCell; -use std::rc::Rc; -use std::collections::VecDeque; use bytes::{BufMut, BytesMut}; +use std::cell::RefCell; +use std::collections::VecDeque; +use std::rc::Rc; +use std::{io, mem}; use body::Binary; - /// Internal use only! unsafe #[derive(Debug)] pub(crate) struct SharedBytesPool(RefCell>>); @@ -34,8 +33,7 @@ impl SharedBytesPool { } #[derive(Debug)] -pub(crate) struct SharedBytes( - Option>, Option>); +pub(crate) struct SharedBytes(Option>, Option>); impl Drop for SharedBytes { fn drop(&mut self) { @@ -50,7 +48,6 @@ impl Drop for SharedBytes { } impl SharedBytes { - pub fn empty() -> Self { SharedBytes(None, None) } @@ -64,7 +61,7 @@ impl SharedBytes { #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] pub(crate) fn get_mut(&self) -> &mut BytesMut { let r: &BytesMut = self.0.as_ref().unwrap().as_ref(); - unsafe{mem::transmute(r)} + unsafe { mem::transmute(r) } } #[inline] diff --git a/src/server/srv.rs b/src/server/srv.rs index f8915e0d..314dc836 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -1,31 +1,33 @@ -use std::{io, net, thread}; use std::rc::Rc; -use std::sync::{Arc, mpsc as sync_mpsc}; +use std::sync::{mpsc as sync_mpsc, Arc}; use std::time::Duration; +use std::{io, net, thread}; -use actix::prelude::*; use actix::actors::signal; -use futures::{Future, Sink, Stream}; +use actix::prelude::*; use futures::sync::mpsc; -use tokio_io::{AsyncRead, AsyncWrite}; +use futures::{Future, Sink, Stream}; use mio; -use num_cpus; use net2::TcpBuilder; +use num_cpus; +use tokio_io::{AsyncRead, AsyncWrite}; -#[cfg(feature="tls")] +#[cfg(feature = "tls")] use native_tls::TlsAcceptor; -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] use openssl::ssl::{AlpnError, SslAcceptorBuilder}; +use super::channel::{HttpChannel, WrapperStream}; +use super::settings::{ServerSettings, WorkerSettings}; +use super::worker::{Conn, StopWorker, StreamHandlerType, Worker}; use super::{IntoHttpHandler, IoStream, KeepAlive}; use super::{PauseServer, ResumeServer, StopServer}; -use super::channel::{HttpChannel, WrapperStream}; -use super::worker::{Conn, Worker, StreamHandlerType, StopWorker}; -use super::settings::{ServerSettings, WorkerSettings}; /// An HTTP Server -pub struct HttpServer where H: IntoHttpHandler + 'static +pub struct HttpServer +where + H: IntoHttpHandler + 'static, { h: Option>>, threads: usize, @@ -33,7 +35,7 @@ pub struct HttpServer where H: IntoHttpHandler + 'static host: Option, keep_alive: KeepAlive, factory: Arc Vec + Send + Sync>, - #[cfg_attr(feature="cargo-clippy", allow(type_complexity))] + #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] workers: Vec<(usize, Addr>)>, sockets: Vec<(net::SocketAddr, net::TcpListener)>, accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, @@ -44,8 +46,16 @@ pub struct HttpServer where H: IntoHttpHandler + 'static no_signals: bool, } -unsafe impl Sync for HttpServer where H: IntoHttpHandler {} -unsafe impl Send for HttpServer where H: IntoHttpHandler {} +unsafe impl Sync for HttpServer +where + H: IntoHttpHandler, +{ +} +unsafe impl Send for HttpServer +where + H: IntoHttpHandler, +{ +} #[derive(Clone)] struct Info { @@ -57,41 +67,47 @@ enum ServerCommand { WorkerDied(usize, Info), } -impl Actor for HttpServer where H: IntoHttpHandler { +impl Actor for HttpServer +where + H: IntoHttpHandler, +{ type Context = Context; } -impl HttpServer where H: IntoHttpHandler + 'static +impl HttpServer +where + H: IntoHttpHandler + 'static, { /// Create new http server with application factory pub fn new(factory: F) -> Self - where F: Fn() -> U + Sync + Send + 'static, - U: IntoIterator + 'static, + where + F: Fn() -> U + Sync + Send + 'static, + U: IntoIterator + 'static, { - let f = move || { - (factory)().into_iter().collect() - }; + let f = move || (factory)().into_iter().collect(); - HttpServer{ h: None, - threads: num_cpus::get(), - backlog: 2048, - host: None, - keep_alive: KeepAlive::Os, - factory: Arc::new(f), - workers: Vec::new(), - sockets: Vec::new(), - accept: Vec::new(), - exit: false, - shutdown_timeout: 30, - signals: None, - no_http2: false, - no_signals: false, + HttpServer { + h: None, + threads: num_cpus::get(), + backlog: 2048, + host: None, + keep_alive: KeepAlive::Os, + factory: Arc::new(f), + workers: Vec::new(), + sockets: Vec::new(), + accept: Vec::new(), + exit: false, + shutdown_timeout: 30, + signals: None, + no_http2: false, + no_signals: false, } } /// Set number of workers to start. /// - /// By default http server uses number of available logical cpu as threads count. + /// 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 @@ -101,7 +117,8 @@ impl HttpServer where H: IntoHttpHandler + 'static /// /// 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. + /// attempting to connect. It should only affect servers under significant + /// load. /// /// Generally set in the 64-2048 range. Default value is 2048. /// @@ -121,9 +138,9 @@ impl HttpServer where H: IntoHttpHandler + 'static /// 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. + /// 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 @@ -152,8 +169,9 @@ impl HttpServer where H: IntoHttpHandler + 'static /// 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. + /// 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 { @@ -192,7 +210,7 @@ impl HttpServer where H: IntoHttpHandler + 'static Ok(lst) => { succ = true; self.sockets.push((lst.local_addr().unwrap(), lst)); - }, + } Err(e) => err = Some(e), } } @@ -201,16 +219,19 @@ impl HttpServer where H: IntoHttpHandler + 'static if let Some(e) = err.take() { Err(e) } else { - Err(io::Error::new(io::ErrorKind::Other, "Can not bind to address.")) + Err(io::Error::new( + io::ErrorKind::Other, + "Can not bind to address.", + )) } } else { Ok(self) } } - fn start_workers(&mut self, settings: &ServerSettings, handler: &StreamHandlerType) - -> Vec<(usize, mpsc::UnboundedSender>)> - { + fn start_workers( + &mut self, settings: &ServerSettings, handler: &StreamHandlerType + ) -> Vec<(usize, mpsc::UnboundedSender>)> { // start workers let mut workers = Vec::new(); for idx in 0..self.threads { @@ -223,7 +244,8 @@ impl HttpServer where H: IntoHttpHandler + 'static let addr = Arbiter::start(move |ctx: &mut Context<_>| { let apps: Vec<_> = (*factory)() .into_iter() - .map(|h| h.into_handler(s.clone())).collect(); + .map(|h| h.into_handler(s.clone())) + .collect(); ctx.add_message_stream(rx); Worker::new(apps, h, ka) }); @@ -248,12 +270,12 @@ impl HttpServer where H: IntoHttpHandler + 'static } } -impl HttpServer -{ +impl HttpServer { /// Start listening for incoming connections. /// /// This method starts number of http handler workers in separate threads. - /// For each address this method starts separate thread which does `accept()` in a loop. + /// For each address this method starts separate thread which does + /// `accept()` in a loop. /// /// This methods panics if no socket addresses get bound. /// @@ -277,8 +299,7 @@ impl HttpServer /// let _ = sys.run(); // <- Run actix system, this method actually starts all async processes /// } /// ``` - pub fn start(mut self) -> Addr - { + pub fn start(mut self) -> Addr { if self.sockets.is_empty() { panic!("HttpServer::bind() has to be called before start()"); } else { @@ -287,15 +308,22 @@ impl HttpServer self.sockets.drain(..).collect(); let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let workers = self.start_workers(&settings, &StreamHandlerType::Normal); - let info = Info{addr: addrs[0].0, handler: StreamHandlerType::Normal}; + let info = Info { + addr: addrs[0].0, + handler: StreamHandlerType::Normal, + }; // start acceptors threads for (addr, sock) in addrs { info!("Starting server on http://{}", addr); - self.accept.push( - start_accept_thread( - sock, addr, self.backlog, - tx.clone(), info.clone(), workers.clone())); + self.accept.push(start_accept_thread( + sock, + addr, + self.backlog, + tx.clone(), + info.clone(), + workers.clone(), + )); } // start http server actor @@ -304,16 +332,17 @@ impl HttpServer ctx.add_stream(rx); self }); - signals.map(|signals| signals.do_send( - signal::Subscribe(addr.clone().recipient()))); + signals.map(|signals| { + signals.do_send(signal::Subscribe(addr.clone().recipient())) + }); addr } } /// Spawn new thread and start listening for incoming connections. /// - /// This method spawns new thread and starts new actix system. Other than that it is - /// similar to `start()` method. This method blocks. + /// This method spawns new thread and starts new actix system. Other than + /// that it is similar to `start()` method. This method blocks. /// /// This methods panics if no socket addresses get bound. /// @@ -344,28 +373,38 @@ impl HttpServer } } -#[cfg(feature="tls")] -impl HttpServer -{ +#[cfg(feature = "tls")] +impl HttpServer { /// Start listening for incoming tls connections. pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result> { if self.sockets.is_empty() { - Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) + Err(io::Error::new( + io::ErrorKind::Other, + "No socket addresses are bound", + )) } else { let (tx, rx) = mpsc::unbounded(); - let addrs: Vec<(net::SocketAddr, net::TcpListener)> = 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::Tls(acceptor.clone())); - let info = Info{addr: addrs[0].0, handler: StreamHandlerType::Tls(acceptor)}; + let workers = + self.start_workers(&settings, &StreamHandlerType::Tls(acceptor.clone())); + let info = Info { + addr: addrs[0].0, + handler: StreamHandlerType::Tls(acceptor), + }; // start acceptors threads for (addr, sock) in addrs { info!("Starting server on https://{}", addr); - self.accept.push( - start_accept_thread( - sock, addr, self.backlog, - tx.clone(), info.clone(), workers.clone())); + self.accept.push(start_accept_thread( + sock, + addr, + self.backlog, + tx.clone(), + info.clone(), + workers.clone(), + )); } // start http server actor @@ -374,23 +413,27 @@ impl HttpServer ctx.add_stream(rx); self }); - signals.map(|signals| signals.do_send( - signal::Subscribe(addr.clone().recipient()))); + signals.map(|signals| { + signals.do_send(signal::Subscribe(addr.clone().recipient())) + }); Ok(addr) } } } -#[cfg(feature="alpn")] -impl HttpServer -{ +#[cfg(feature = "alpn")] +impl HttpServer { /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn start_ssl(mut self, mut builder: SslAcceptorBuilder) -> io::Result> - { + pub fn start_ssl( + mut self, mut builder: SslAcceptorBuilder + ) -> io::Result> { if self.sockets.is_empty() { - Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) + Err(io::Error::new( + io::ErrorKind::Other, + "No socket addresses are bound", + )) } else { // alpn support if !self.no_http2 { @@ -407,19 +450,29 @@ impl HttpServer let (tx, rx) = mpsc::unbounded(); let acceptor = builder.build(); - let addrs: Vec<(net::SocketAddr, net::TcpListener)> = 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::Alpn(acceptor.clone())); - let info = Info{addr: addrs[0].0, handler: StreamHandlerType::Alpn(acceptor)}; + &settings, + &StreamHandlerType::Alpn(acceptor.clone()), + ); + let info = Info { + addr: addrs[0].0, + handler: StreamHandlerType::Alpn(acceptor), + }; // start acceptors threads for (addr, sock) in addrs { info!("Starting server on https://{}", addr); - self.accept.push( - start_accept_thread( - sock, addr, self.backlog, - tx.clone(), info.clone(), workers.clone())); + self.accept.push(start_accept_thread( + sock, + addr, + self.backlog, + tx.clone(), + info.clone(), + workers.clone(), + )); } // start http server actor @@ -428,22 +481,23 @@ impl HttpServer ctx.add_stream(rx); self }); - signals.map(|signals| signals.do_send( - signal::Subscribe(addr.clone().recipient()))); + signals.map(|signals| { + signals.do_send(signal::Subscribe(addr.clone().recipient())) + }); Ok(addr) } } } -impl HttpServer -{ +impl HttpServer { /// Start listening for incoming connections from a stream. /// /// This method uses only one thread for handling incoming connections. pub fn start_incoming(mut self, stream: S, secure: bool) -> Addr - where S: Stream + 'static, - T: AsyncRead + AsyncWrite + 'static, - A: 'static + where + S: Stream + 'static, + T: AsyncRead + AsyncWrite + 'static, + A: 'static, { let (tx, rx) = mpsc::unbounded(); @@ -452,15 +506,22 @@ impl HttpServer self.sockets.drain(..).collect(); let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false); let workers = self.start_workers(&settings, &StreamHandlerType::Normal); - let info = Info{addr: addrs[0].0, handler: StreamHandlerType::Normal}; + let info = Info { + addr: addrs[0].0, + handler: StreamHandlerType::Normal, + }; // start acceptors threads for (addr, sock) in addrs { info!("Starting server on http://{}", addr); - self.accept.push( - start_accept_thread( - sock, addr, self.backlog, - tx.clone(), info.clone(), workers.clone())); + self.accept.push(start_accept_thread( + sock, + addr, + self.backlog, + tx.clone(), + info.clone(), + workers.clone(), + )); } } @@ -468,21 +529,24 @@ impl HttpServer let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); let settings = ServerSettings::new(Some(addr), &self.host, secure); let apps: Vec<_> = (*self.factory)() - .into_iter().map(|h| h.into_handler(settings.clone())).collect(); + .into_iter() + .map(|h| h.into_handler(settings.clone())) + .collect(); self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive))); // start server let signals = self.subscribe_to_signals(); let addr: Addr = HttpServer::create(move |ctx| { ctx.add_stream(rx); - ctx.add_message_stream( - stream - .map_err(|_| ()) - .map(move |(t, _)| Conn{io: WrapperStream::new(t), peer: None, http2: false})); + ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn { + io: WrapperStream::new(t), + peer: None, + http2: false, + })); self }); - signals.map(|signals| signals.do_send( - signal::Subscribe(addr.clone().recipient()))); + signals + .map(|signals| signals.do_send(signal::Subscribe(addr.clone().recipient()))); addr } } @@ -490,8 +554,7 @@ impl HttpServer /// Signals support /// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)` /// message to `System` actor. -impl Handler for HttpServer -{ +impl Handler for HttpServer { type Result = (); fn handle(&mut self, msg: signal::Signal, ctx: &mut Context) { @@ -499,17 +562,17 @@ impl Handler for HttpServer signal::SignalType::Int => { info!("SIGINT received, exiting"); self.exit = true; - Handler::::handle(self, StopServer{graceful: false}, ctx); + Handler::::handle(self, StopServer { graceful: false }, ctx); } signal::SignalType::Term => { info!("SIGTERM received, stopping"); self.exit = true; - Handler::::handle(self, StopServer{graceful: true}, ctx); + Handler::::handle(self, StopServer { graceful: true }, ctx); } signal::SignalType::Quit => { info!("SIGQUIT received, exiting"); self.exit = true; - Handler::::handle(self, StopServer{graceful: false}, ctx); + Handler::::handle(self, StopServer { graceful: false }, ctx); } _ => (), } @@ -517,8 +580,7 @@ impl Handler for HttpServer } /// Commands from accept threads -impl StreamHandler for HttpServer -{ +impl StreamHandler for HttpServer { fn finished(&mut self, _: &mut Context) {} fn handle(&mut self, msg: ServerCommand, _: &mut Context) { match msg { @@ -528,7 +590,7 @@ impl StreamHandler for HttpServer if self.workers[i].0 == idx { self.workers.swap_remove(i); found = true; - break + break; } } @@ -541,21 +603,23 @@ impl StreamHandler for HttpServer for i in 0..self.workers.len() { if self.workers[i].0 == new_idx { new_idx += 1; - continue 'found + continue 'found; } } - break + break; } let h = info.handler; let ka = self.keep_alive; let factory = Arc::clone(&self.factory); - let settings = ServerSettings::new(Some(info.addr), &self.host, false); + let settings = + ServerSettings::new(Some(info.addr), &self.host, false); let addr = Arbiter::start(move |ctx: &mut Context<_>| { let apps: Vec<_> = (*factory)() .into_iter() - .map(|h| h.into_handler(settings.clone())).collect(); + .map(|h| h.into_handler(settings.clone())) + .collect(); ctx.add_message_stream(rx); Worker::new(apps, h, ka) }); @@ -566,30 +630,32 @@ impl StreamHandler for HttpServer self.workers.push((new_idx, addr)); } - }, + } } } } impl Handler> for HttpServer - where T: IoStream, - H: IntoHttpHandler, +where + T: IoStream, + H: IntoHttpHandler, { type Result = (); fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { - Arbiter::handle().spawn( - HttpChannel::new( - Rc::clone(self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2)); + Arbiter::handle().spawn(HttpChannel::new( + Rc::clone(self.h.as_ref().unwrap()), + msg.io, + msg.peer, + msg.http2, + )); } } -impl Handler for HttpServer -{ +impl Handler for HttpServer { type Result = (); - fn handle(&mut self, _: PauseServer, _: &mut Context) - { + 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()); @@ -597,8 +663,7 @@ impl Handler for HttpServer } } -impl Handler for HttpServer -{ +impl Handler for HttpServer { type Result = (); fn handle(&mut self, _: ResumeServer, _: &mut Context) { @@ -609,8 +674,7 @@ impl Handler for HttpServer } } -impl Handler for HttpServer -{ +impl Handler for HttpServer { type Result = actix::Response<(), ()>; fn handle(&mut self, msg: StopServer, ctx: &mut Context) -> Self::Result { @@ -630,7 +694,9 @@ impl Handler for HttpServer }; for worker in &self.workers { let tx2 = tx.clone(); - worker.1.send(StopWorker{graceful: dur}) + worker + .1 + .send(StopWorker { graceful: dur }) .into_actor(self) .then(move |_, slf, ctx| { slf.workers.pop(); @@ -645,12 +711,12 @@ impl Handler for HttpServer } } actix::fut::ok(()) - }).spawn(ctx); + }) + .spawn(ctx); } if !self.workers.is_empty() { - Response::async( - rx.into_future().map(|_| ()).map_err(|_| ())) + Response::async(rx.into_future().map(|_| ()).map_err(|_| ())) } else { // we need to stop system if server was spawned if self.exit { @@ -673,156 +739,184 @@ enum Command { fn start_accept_thread( sock: net::TcpListener, addr: net::SocketAddr, backlog: i32, srv: mpsc::UnboundedSender, info: Info, - mut workers: Vec<(usize, mpsc::UnboundedSender>)>) - -> (mio::SetReadiness, sync_mpsc::Sender) -{ + mut workers: Vec<(usize, mpsc::UnboundedSender>)>, +) -> (mio::SetReadiness, sync_mpsc::Sender) { let (tx, rx) = sync_mpsc::channel(); let (reg, readiness) = mio::Registration::new2(); // start accept thread - #[cfg_attr(feature="cargo-clippy", allow(cyclomatic_complexity))] - let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn(move || { - const SRV: mio::Token = mio::Token(0); - const CMD: mio::Token = mio::Token(1); + #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] + let _ = thread::Builder::new() + .name(format!("Accept on {}", addr)) + .spawn(move || { + const SRV: mio::Token = mio::Token(0); + const CMD: mio::Token = mio::Token(1); - let mut server = Some( - mio::net::TcpListener::from_std(sock) - .expect("Can not create mio::net::TcpListener")); + let mut server = Some( + mio::net::TcpListener::from_std(sock) + .expect("Can not create mio::net::TcpListener"), + ); - // Create a poll instance - let poll = match mio::Poll::new() { - Ok(poll) => poll, - Err(err) => panic!("Can not create mio::Poll: {}", err), - }; + // 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 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 incoming commands - if let Err(err) = poll.register(®, CMD, - mio::Ready::readable(), mio::PollOpt::edge()) { - panic!("Can not register Registration: {}", err); - } - - // Create storage for events - let mut events = mio::Events::with_capacity(128); - - // Sleep on error - let sleep = Duration::from_millis(100); - - let mut next = 0; - loop { - if let Err(err) = poll.poll(&mut events, None) { - panic!("Poll error: {}", err); - } - - for event in events.iter() { - match event.token() { - SRV => if let Some(ref server) = server { - loop { - match server.accept_std() { - Ok((sock, addr)) => { - let mut msg = Conn{ - io: sock, peer: Some(addr), http2: false}; - while !workers.is_empty() { - match workers[next].1.unbounded_send(msg) { - Ok(_) => (), - Err(err) => { - let _ = srv.unbounded_send( - ServerCommand::WorkerDied( - workers[next].0, info.clone())); - msg = err.into_inner(); - workers.swap_remove(next); - if workers.is_empty() { - error!("No workers"); - thread::sleep(sleep); - break - } else if workers.len() <= next { - next = 0; - } - continue - } - } - next = (next + 1) % workers.len(); - break - } - }, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => - break, - Err(ref e) if connection_error(e) => - continue, - Err(e) => { - error!("Error accepting connection: {}", e); - // sleep after error - thread::sleep(sleep); - break - } - } - } - }, - 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_std(lst) - .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 => { - if let Some(server) = server.take() { - let _ = poll.deregister(&server); - } - return - }, - Command::Worker(idx, addr) => { - workers.push((idx, addr)); - }, - }, - Err(err) => match err { - sync_mpsc::TryRecvError::Empty => (), - sync_mpsc::TryRecvError::Disconnected => { - if let Some(server) = server.take() { - let _ = poll.deregister(&server); - } - return - }, - } - }, - _ => unreachable!(), + // Start listening for incoming connections + 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 incoming commands + if let Err(err) = poll.register( + ®, + CMD, + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { + panic!("Can not register Registration: {}", err); + } + + // Create storage for events + let mut events = mio::Events::with_capacity(128); + + // Sleep on error + let sleep = Duration::from_millis(100); + + let mut next = 0; + loop { + if let Err(err) = poll.poll(&mut events, None) { + panic!("Poll error: {}", err); + } + + for event in events.iter() { + match event.token() { + SRV => if let Some(ref server) = server { + loop { + match server.accept_std() { + Ok((sock, addr)) => { + let mut msg = Conn { + io: sock, + peer: Some(addr), + http2: false, + }; + while !workers.is_empty() { + match workers[next].1.unbounded_send(msg) { + Ok(_) => (), + Err(err) => { + let _ = srv.unbounded_send( + ServerCommand::WorkerDied( + workers[next].0, + info.clone(), + ), + ); + msg = err.into_inner(); + workers.swap_remove(next); + if workers.is_empty() { + error!("No workers"); + thread::sleep(sleep); + break; + } else if workers.len() <= next { + next = 0; + } + continue; + } + } + next = (next + 1) % workers.len(); + break; + } + } + Err(ref e) + if e.kind() == io::ErrorKind::WouldBlock => + { + break + } + Err(ref e) if connection_error(e) => continue, + Err(e) => { + error!("Error accepting connection: {}", e); + // sleep after error + thread::sleep(sleep); + break; + } + } + } + }, + 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_std(lst).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 => { + if let Some(server) = server.take() { + let _ = poll.deregister(&server); + } + return; + } + Command::Worker(idx, addr) => { + workers.push((idx, addr)); + } + }, + Err(err) => match err { + sync_mpsc::TryRecvError::Empty => (), + sync_mpsc::TryRecvError::Disconnected => { + if let Some(server) = server.take() { + let _ = poll.deregister(&server); + } + return; + } + }, + }, + _ => unreachable!(), + } + } + } + }); (readiness, tx) } -fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result { +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()?, @@ -840,7 +934,7 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result bool { - e.kind() == io::ErrorKind::ConnectionRefused || - e.kind() == io::ErrorKind::ConnectionAborted || - e.kind() == io::ErrorKind::ConnectionReset + e.kind() == io::ErrorKind::ConnectionRefused + || e.kind() == io::ErrorKind::ConnectionAborted + || e.kind() == io::ErrorKind::ConnectionReset } diff --git a/src/server/utils.rs b/src/server/utils.rs index bbc890e9..430fb211 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -1,14 +1,15 @@ -use std::io; -use bytes::{BytesMut, BufMut}; +use bytes::{BufMut, BytesMut}; use futures::{Async, Poll}; +use std::io; use super::IoStream; const LW_BUFFER_SIZE: usize = 4096; const HW_BUFFER_SIZE: usize = 32_768; - -pub fn read_from_io(io: &mut T, buf: &mut BytesMut) -> Poll { +pub fn read_from_io( + io: &mut T, buf: &mut BytesMut +) -> Poll { unsafe { if buf.remaining_mut() < LW_BUFFER_SIZE { buf.reserve(HW_BUFFER_SIZE); @@ -17,7 +18,7 @@ pub fn read_from_io(io: &mut T, buf: &mut BytesMut) -> Poll { buf.advance_mut(n); Ok(Async::Ready(n)) - }, + } Err(e) => { if e.kind() == io::ErrorKind::WouldBlock { Ok(Async::NotReady) diff --git a/src/server/worker.rs b/src/server/worker.rs index 3fe9cec1..a6ec0711 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,31 +1,30 @@ -use std::{net, time}; -use std::rc::Rc; use futures::Future; use futures::unsync::oneshot; +use net2::TcpStreamExt; +use std::rc::Rc; +use std::{net, time}; use tokio_core::net::TcpStream; use tokio_core::reactor::Handle; -use net2::TcpStreamExt; -#[cfg(any(feature="tls", feature="alpn"))] +#[cfg(any(feature = "tls", feature = "alpn"))] use futures::future; -#[cfg(feature="tls")] +#[cfg(feature = "tls")] use native_tls::TlsAcceptor; -#[cfg(feature="tls")] +#[cfg(feature = "tls")] use tokio_tls::TlsAcceptorExt; -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] use openssl::ssl::SslAcceptor; -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] use tokio_openssl::SslAcceptorExt; -use actix::*; use actix::msgs::StopArbiter; +use actix::*; -use server::{HttpHandler, KeepAlive}; use server::channel::HttpChannel; use server::settings::WorkerSettings; - +use server::{HttpHandler, KeepAlive}; #[derive(Message)] pub(crate) struct Conn { @@ -46,9 +45,12 @@ impl Message for StopWorker { /// Http worker /// -/// Worker accepts Socket objects via unbounded channel and start requests processing. -pub(crate) -struct Worker where H: HttpHandler + 'static { +/// Worker accepts Socket objects via unbounded channel and start requests +/// processing. +pub(crate) struct Worker +where + H: HttpHandler + 'static, +{ settings: Rc>, hnd: Handle, handler: StreamHandlerType, @@ -56,10 +58,9 @@ struct Worker where H: HttpHandler + 'static { } impl Worker { - - pub(crate) fn new(h: Vec, handler: StreamHandlerType, keep_alive: KeepAlive) - -> Worker - { + pub(crate) fn new( + h: Vec, handler: StreamHandlerType, keep_alive: KeepAlive + ) -> Worker { let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { Some(time::Duration::new(val as u64, 0)) } else { @@ -76,11 +77,14 @@ impl Worker { fn update_time(&self, ctx: &mut Context) { self.settings.update_date(); - ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); + 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) { + 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.settings.num_channels(); @@ -99,7 +103,10 @@ impl Worker { } } -impl Actor for Worker where H: HttpHandler + 'static { +impl Actor for Worker +where + H: HttpHandler + 'static, +{ type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { @@ -108,22 +115,24 @@ impl Actor for Worker where H: HttpHandler + 'static { } impl Handler> for Worker - where H: HttpHandler + 'static, +where + H: HttpHandler + 'static, { type Result = (); - fn handle(&mut self, msg: Conn, _: &mut Context) - { + fn handle(&mut self, msg: Conn, _: &mut Context) { if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { error!("Can not set socket keep-alive option"); } - self.handler.handle(Rc::clone(&self.settings), &self.hnd, msg); + self.handler + .handle(Rc::clone(&self.settings), &self.hnd, msg); } } /// `StopWorker` message handler impl Handler for Worker - where H: HttpHandler + 'static, +where + H: HttpHandler + 'static, { type Result = Response; @@ -148,17 +157,16 @@ impl Handler for Worker #[derive(Clone)] pub(crate) enum StreamHandlerType { Normal, - #[cfg(feature="tls")] + #[cfg(feature = "tls")] Tls(TlsAcceptor), - #[cfg(feature="alpn")] + #[cfg(feature = "alpn")] Alpn(SslAcceptor), } impl StreamHandlerType { - - fn handle(&mut self, - h: Rc>, - hnd: &Handle, msg: Conn) { + fn handle( + &mut self, h: Rc>, hnd: &Handle, msg: Conn + ) { match *self { StreamHandlerType::Normal => { let _ = msg.io.set_nodelay(true); @@ -167,7 +175,7 @@ impl StreamHandlerType { hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); } - #[cfg(feature="tls")] + #[cfg(feature = "tls")] StreamHandlerType::Tls(ref acceptor) => { let Conn { io, peer, http2 } = msg; let _ = io.set_nodelay(true); @@ -177,16 +185,21 @@ impl StreamHandlerType { 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), + 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")] + #[cfg(feature = "alpn")] StreamHandlerType::Alpn(ref acceptor) => { let Conn { io, peer, .. } = msg; let _ = io.set_nodelay(true); @@ -197,20 +210,26 @@ impl StreamHandlerType { 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() + 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), + Arbiter::handle().spawn(HttpChannel::new( + h, + io, + peer, + http2, + )); + } + Err(err) => { + trace!("Error during handling tls connection: {}", err) + } }; future::result(Ok(())) - }) + }), ); } } diff --git a/src/test.rs b/src/test.rs index 2a12657c..c93e721b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,37 +1,37 @@ //! 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::sync::mpsc; +use std::{net, thread}; -use actix::{Actor, Arbiter, Addr, Syn, System, SystemRunner, Unsync, msgs}; +use actix::{msgs, Actor, Addr, Arbiter, Syn, System, SystemRunner, Unsync}; use cookie::Cookie; -use http::{Uri, Method, Version, HeaderMap, HttpTryFrom}; -use http::header::HeaderName; use futures::Future; +use http::header::HeaderName; +use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use net2::TcpBuilder; use tokio_core::net::TcpListener; use tokio_core::reactor::Core; -use net2::TcpBuilder; -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] use openssl::ssl::SslAcceptor; -use ws; -use body::Binary; -use error::Error; -use header::{Header, IntoHeaderValue}; -use handler::{Handler, Responder, ReplyItem}; -use middleware::Middleware; use application::{App, HttpApplication}; -use param::Params; -use router::Router; -use payload::Payload; -use resource::ResourceHandler; +use body::Binary; +use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; +use error::Error; +use handler::{Handler, ReplyItem, Responder}; +use header::{Header, IntoHeaderValue}; use httprequest::HttpRequest; use httpresponse::HttpResponse; +use middleware::Middleware; +use param::Params; +use payload::Payload; +use resource::ResourceHandler; +use router::Router; use server::{HttpServer, IntoHttpHandler, ServerSettings}; -use client::{ClientRequest, ClientRequestBuilder, ClientConnector}; +use ws; /// The `TestServer` type. /// @@ -69,20 +69,20 @@ pub struct TestServer { } impl TestServer { - /// Start new test server /// /// This method 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<()>) + where + F: Sync + Send + 'static + Fn(&mut TestApp<()>), { - TestServerBuilder::new(||()).start(config) + TestServerBuilder::new(|| ()).start(config) } /// Create test server builder pub fn build() -> TestServerBuilder<()> { - TestServerBuilder::new(||()) + TestServerBuilder::new(|| ()) } /// Create test server builder with specific state factory @@ -91,17 +91,19 @@ impl TestServer { /// Also it can be used for external dependecy initialization, /// like creating sync actors for diesel integration. pub fn build_with_state(state: F) -> TestServerBuilder - where F: Fn() -> S + Sync + Send + 'static, - S: 'static, + where + F: Fn() -> S + Sync + Send + 'static, + S: 'static, { TestServerBuilder::new(state) } /// Start new test server with application factory pub fn with_factory(factory: F) -> Self - where F: Fn() -> U + Sync + Send + 'static, - U: IntoIterator + 'static, - H: IntoHttpHandler + 'static, + where + F: Fn() -> U + Sync + Send + 'static, + U: IntoIterator + 'static, + H: IntoHttpHandler + 'static, { let (tx, rx) = mpsc::channel(); @@ -110,8 +112,8 @@ impl TestServer { let sys = System::new("actix-test-server"); 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(); + let tcp = + TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap(); HttpServer::new(factory) .disable_signals() @@ -134,15 +136,15 @@ impl TestServer { } fn get_conn() -> Addr { - #[cfg(feature="alpn")] + #[cfg(feature = "alpn")] { - use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode}; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); ClientConnector::with_connector(builder.build()).start() } - #[cfg(not(feature="alpn"))] + #[cfg(not(feature = "alpn"))] { ClientConnector::default().start() } @@ -166,9 +168,19 @@ impl TestServer { /// Construct test server url pub fn url(&self, uri: &str) -> String { if uri.starts_with('/') { - format!("{}://{}{}", if self.ssl {"https"} else {"http"}, self.addr, uri) + format!( + "{}://{}{}", + if self.ssl { "https" } else { "http" }, + self.addr, + uri + ) } else { - format!("{}://{}/{}", if self.ssl {"https"} else {"http"}, self.addr, uri) + format!( + "{}://{}/{}", + if self.ssl { "https" } else { "http" }, + self.addr, + uri + ) } } @@ -182,16 +194,20 @@ impl TestServer { /// Execute future on current core pub fn execute(&mut self, fut: F) -> Result - where F: Future + where + F: Future, { self.system.run_until_complete(fut) } /// Connect to websocket server - pub fn ws(&mut self) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { + pub fn ws( + &mut self + ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { let url = self.url("/"); self.system.run_until_complete( - ws::Client::with_connector(url, self.conn.clone()).connect()) + ws::Client::with_connector(url, self.conn.clone()).connect(), + ) } /// Create `GET` request @@ -231,23 +247,23 @@ impl Drop for TestServer { /// builder-like pattern. pub struct TestServerBuilder { state: Box S + Sync + Send + 'static>, - #[cfg(feature="alpn")] + #[cfg(feature = "alpn")] ssl: Option, } impl TestServerBuilder { - pub fn new(state: F) -> TestServerBuilder - where F: Fn() -> S + Sync + Send + 'static + where + F: Fn() -> S + Sync + Send + 'static, { TestServerBuilder { state: Box::new(state), - #[cfg(feature="alpn")] + #[cfg(feature = "alpn")] ssl: None, } } - #[cfg(feature="alpn")] + #[cfg(feature = "alpn")] /// Create ssl server pub fn ssl(mut self, ssl: SslAcceptor) -> Self { self.ssl = Some(ssl); @@ -257,13 +273,14 @@ impl TestServerBuilder { #[allow(unused_mut)] /// Configure test application and run test server pub fn start(mut self, config: F) -> TestServer - where F: Sync + Send + 'static + Fn(&mut TestApp), + where + F: Sync + Send + 'static + Fn(&mut TestApp), { let (tx, rx) = mpsc::channel(); - #[cfg(feature="alpn")] + #[cfg(feature = "alpn")] let ssl = self.ssl.is_some(); - #[cfg(not(feature="alpn"))] + #[cfg(not(feature = "alpn"))] let ssl = false; // run server in separate thread @@ -272,38 +289,38 @@ impl TestServerBuilder { 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(); + let tcp = + TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap(); let state = self.state; let srv = HttpServer::new(move || { let mut app = TestApp::new(state()); config(&mut app); - vec![app]}) - .disable_signals(); + vec![app] + }).disable_signals(); - #[cfg(feature="alpn")] + #[cfg(feature = "alpn")] { - use std::io; use futures::Stream; + use std::io; use tokio_openssl::SslAcceptorExt; let ssl = self.ssl.take(); if let Some(ssl) = ssl { srv.start_incoming( - tcp.incoming() - .and_then(move |(sock, addr)| { - ssl.accept_async(sock) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) - .map(move |s| (s, addr)) - }), - false); + tcp.incoming().and_then(move |(sock, addr)| { + ssl.accept_async(sock) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + .map(move |s| (s, addr)) + }), + false, + ); } else { srv.start_incoming(tcp.incoming(), false); } } - #[cfg(not(feature="alpn"))] + #[cfg(not(feature = "alpn"))] { srv.start_incoming(tcp.incoming(), false); } @@ -326,24 +343,30 @@ impl TestServerBuilder { } /// Test application helper for testing request handlers. -pub struct TestApp { +pub struct TestApp { app: Option>, } impl TestApp { fn new(state: S) -> TestApp { let app = App::with_state(state); - TestApp{app: Some(app)} + 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))); + 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 + where + T: Middleware + 'static, { self.app = Some(self.app.take().unwrap().middleware(mw)); self @@ -352,7 +375,8 @@ impl TestApp { /// Register resource. This method is similar /// to `App::resource()` method. pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp - where F: FnOnce(&mut ResourceHandler) -> R + 'static + where + F: FnOnce(&mut ResourceHandler) -> R + 'static, { self.app = Some(self.app.take().unwrap().resource(path, f)); self @@ -419,7 +443,6 @@ pub struct TestRequest { } impl Default for TestRequest<()> { - fn default() -> TestRequest<()> { TestRequest { state: (), @@ -435,28 +458,27 @@ impl Default for TestRequest<()> { } impl TestRequest<()> { - /// Create TestRequest and set request uri pub fn with_uri(path: &str) -> TestRequest<()> { TestRequest::default().uri(path) } /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest<()> - { + pub fn with_hdr(hdr: H) -> TestRequest<()> { TestRequest::default().set(hdr) } /// Create TestRequest and set header pub fn with_header(key: K, value: V) -> TestRequest<()> - where HeaderName: HttpTryFrom, V: IntoHeaderValue, + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, { TestRequest::default().header(key, value) } } impl TestRequest { - /// Start HttpRequest build process with application state pub fn with_state(state: S) -> TestRequest { TestRequest { @@ -490,23 +512,24 @@ impl TestRequest { } /// Set a header - pub fn set(mut self, hdr: H) -> Self - { + pub fn set(mut self, hdr: H) -> Self { if let Ok(value) = hdr.try_into() { self.headers.append(H::name(), value); - return self + return self; } panic!("Can not set header"); } /// Set a header pub fn header(mut self, key: K, value: V) -> Self - where HeaderName: HttpTryFrom, V: IntoHeaderValue + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, { if let Ok(key) = HeaderName::try_from(key) { if let Ok(value) = value.try_into() { self.headers.append(key, value); - return self + return self; } } panic!("Can not create header"); @@ -529,7 +552,16 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` instance pub fn finish(self) -> HttpRequest { - let TestRequest { state, method, uri, version, headers, params, cookies, payload } = self; + 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; @@ -540,8 +572,16 @@ impl TestRequest { #[cfg(test)] /// Complete request creation and generate `HttpRequest` instance pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest { - let TestRequest { state, method, uri, - version, headers, params, cookies, payload } = self; + 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; @@ -553,18 +593,16 @@ impl TestRequest { /// 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> - { + 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.drop_state()) { - Ok(resp) => { - match resp.into().into() { - ReplyItem::Message(resp) => Ok(resp), - ReplyItem::Future(_) => panic!("Async handler is not supported."), - } + Ok(resp) => match resp.into().into() { + ReplyItem::Message(resp) => Ok(resp), + ReplyItem::Future(_) => panic!("Async handler is not supported."), }, Err(err) => Err(err), } @@ -575,24 +613,23 @@ impl TestRequest { /// /// 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 + 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.drop_state()) { - Ok(reply) => match reply.into().into() { - ReplyItem::Message(resp) => Ok(resp), - _ => panic!("Nested async replies are not supported"), - }, - Err(e) => Err(e), - } + Ok(r) => match r.respond_to(req.drop_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), } diff --git a/src/with.rs b/src/with.rs index 5e117225..07e9efc4 100644 --- a/src/with.rs +++ b/src/with.rs @@ -1,33 +1,37 @@ -use std::rc::Rc; +use futures::{Async, Future, Poll}; use std::cell::UnsafeCell; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; -use futures::{Async, Future, Poll}; +use std::rc::Rc; use error::Error; -use handler::{Handler, FromRequest, Reply, ReplyItem, Responder}; +use handler::{FromRequest, Handler, Reply, ReplyItem, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; pub struct ExtractorConfig> { - cfg: Rc> + cfg: Rc>, } impl> Default for ExtractorConfig { fn default() -> Self { - ExtractorConfig { cfg: Rc::new(UnsafeCell::new(T::Config::default())) } + ExtractorConfig { + cfg: Rc::new(UnsafeCell::new(T::Config::default())), + } } } impl> Clone for ExtractorConfig { fn clone(&self) -> Self { - ExtractorConfig { cfg: Rc::clone(&self.cfg) } + ExtractorConfig { + cfg: Rc::clone(&self.cfg), + } } } impl> AsRef for ExtractorConfig { fn as_ref(&self) -> &T::Config { - unsafe{&*self.cfg.get()} + unsafe { &*self.cfg.get() } } } @@ -35,18 +39,21 @@ impl> Deref for ExtractorConfig { type Target = T::Config; fn deref(&self) -> &T::Config { - unsafe{&*self.cfg.get()} + unsafe { &*self.cfg.get() } } } impl> DerefMut for ExtractorConfig { fn deref_mut(&mut self) -> &mut T::Config { - unsafe{&mut *self.cfg.get()} + unsafe { &mut *self.cfg.get() } } } pub struct With - where F: Fn(T) -> R, T: FromRequest, S: 'static, +where + F: Fn(T) -> R, + T: FromRequest, + S: 'static, { hnd: Rc>, cfg: ExtractorConfig, @@ -54,23 +61,31 @@ pub struct With } impl With - where F: Fn(T) -> R, T: FromRequest, S: 'static, +where + F: Fn(T) -> R, + T: FromRequest, + S: 'static, { pub fn new(f: F, cfg: ExtractorConfig) -> Self { - With{cfg, hnd: Rc::new(UnsafeCell::new(f)), _s: PhantomData} + With { + cfg, + hnd: Rc::new(UnsafeCell::new(f)), + _s: PhantomData, + } } } impl Handler for With - where F: Fn(T) -> R + 'static, - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static +where + F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, + S: 'static, { type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut{ + let mut fut = WithHandlerFut { req, started: false, hnd: Rc::clone(&self.hnd), @@ -88,31 +103,33 @@ impl Handler for With } struct WithHandlerFut - where F: Fn(T) -> R, - R: Responder, - T: FromRequest + 'static, - S: 'static +where + F: Fn(T) -> R, + R: Responder, + T: FromRequest + 'static, + S: 'static, { started: bool, hnd: Rc>, cfg: ExtractorConfig, req: HttpRequest, - fut1: Option>>, - fut2: Option>>, + fut1: Option>>, + fut2: Option>>, } impl Future for WithHandlerFut - where F: Fn(T) -> R, - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static +where + F: Fn(T) -> R, + R: Responder + 'static, + T: FromRequest + 'static, + S: 'static, { type Item = HttpResponse; type Error = Error; fn poll(&mut self) -> Poll { if let Some(ref mut fut) = self.fut2 { - return fut.poll() + return fut.poll(); } let item = if !self.started { @@ -122,8 +139,8 @@ impl Future for WithHandlerFut Ok(Async::Ready(item)) => item, Ok(Async::NotReady) => { self.fut1 = Some(Box::new(fut)); - return Ok(Async::NotReady) - }, + return Ok(Async::NotReady); + } Err(e) => return Err(e), } } else { @@ -133,7 +150,7 @@ impl Future for WithHandlerFut } }; - let hnd: &mut F = unsafe{&mut *self.hnd.get()}; + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; let item = match (*hnd)(item).respond_to(self.req.drop_state()) { Ok(item) => item.into(), Err(e) => return Err(e.into()), @@ -150,8 +167,11 @@ impl Future for WithHandlerFut } pub struct With2 - where F: Fn(T1, T2) -> R, - T1: FromRequest + 'static, T2: FromRequest + 'static, S: 'static +where + F: Fn(T1, T2) -> R, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + S: 'static, { hnd: Rc>, cfg1: ExtractorConfig, @@ -160,26 +180,36 @@ pub struct With2 } impl With2 - where F: Fn(T1, T2) -> R, - T1: FromRequest + 'static, T2: FromRequest + 'static, S: 'static +where + F: Fn(T1, T2) -> R, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + S: 'static, { - pub fn new(f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig) -> Self { - With2{hnd: Rc::new(UnsafeCell::new(f)), - cfg1, cfg2, _s: PhantomData} + pub fn new( + f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig + ) -> Self { + With2 { + hnd: Rc::new(UnsafeCell::new(f)), + cfg1, + cfg2, + _s: PhantomData, + } } } impl Handler for With2 - where F: Fn(T1, T2) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - S: 'static +where + F: Fn(T1, T2) -> R + 'static, + R: Responder + 'static, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + S: 'static, { type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut2{ + let mut fut = WithHandlerFut2 { req, started: false, hnd: Rc::clone(&self.hnd), @@ -199,11 +229,12 @@ impl Handler for With2 } struct WithHandlerFut2 - where F: Fn(T1, T2) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - S: 'static +where + F: Fn(T1, T2) -> R + 'static, + R: Responder + 'static, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + S: 'static, { started: bool, hnd: Rc>, @@ -211,24 +242,25 @@ struct WithHandlerFut2 cfg2: ExtractorConfig, req: HttpRequest, item: Option, - fut1: Option>>, - fut2: Option>>, - fut3: Option>>, + fut1: Option>>, + fut2: Option>>, + fut3: Option>>, } impl Future for WithHandlerFut2 - where F: Fn(T1, T2) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - S: 'static +where + F: Fn(T1, T2) -> R + 'static, + R: Responder + 'static, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + S: 'static, { type Item = HttpResponse; type Error = Error; fn poll(&mut self) -> Poll { if let Some(ref mut fut) = self.fut3 { - return fut.poll() + return fut.poll(); } if !self.started { @@ -236,32 +268,32 @@ impl Future for WithHandlerFut2 let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); match fut.poll() { Ok(Async::Ready(item1)) => { - let mut fut = T2::from_request(&self.req,self.cfg2.as_ref()); + let mut fut = T2::from_request(&self.req, self.cfg2.as_ref()); match fut.poll() { Ok(Async::Ready(item2)) => { - let hnd: &mut F = unsafe{&mut *self.hnd.get()}; - match (*hnd)(item1, item2) - .respond_to(self.req.drop_state()) + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; + match (*hnd)(item1, item2).respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyItem::Message(resp) => - return Ok(Async::Ready(resp)), + ReplyItem::Message(resp) => { + return Ok(Async::Ready(resp)) + } ReplyItem::Future(fut) => { self.fut3 = Some(fut); - return self.poll() + return self.poll(); } }, Err(e) => return Err(e.into()), } - }, + } Ok(Async::NotReady) => { self.item = Some(item1); self.fut2 = Some(Box::new(fut)); return Ok(Async::NotReady); - }, + } Err(e) => return Err(e), } - }, + } Ok(Async::NotReady) => { self.fut1 = Some(Box::new(fut)); return Ok(Async::NotReady); @@ -275,9 +307,11 @@ impl Future for WithHandlerFut2 Async::Ready(item) => { self.item = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new( - T2::from_request(&self.req, self.cfg2.as_ref()))); - }, + self.fut2 = Some(Box::new(T2::from_request( + &self.req, + self.cfg2.as_ref(), + ))); + } Async::NotReady => return Ok(Async::NotReady), } } @@ -287,7 +321,7 @@ impl Future for WithHandlerFut2 Async::NotReady => return Ok(Async::NotReady), }; - let hnd: &mut F = unsafe{&mut *self.hnd.get()}; + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; let item = match (*hnd)(self.item.take().unwrap(), item) .respond_to(self.req.drop_state()) { @@ -305,11 +339,12 @@ impl Future for WithHandlerFut2 } pub struct With3 - where F: Fn(T1, T2, T3) -> R, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - S: 'static +where + F: Fn(T1, T2, T3) -> R, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, + S: 'static, { hnd: Rc>, cfg1: ExtractorConfig, @@ -318,33 +353,44 @@ pub struct With3 _s: PhantomData, } - impl With3 - where F: Fn(T1, T2, T3) -> R, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - S: 'static +where + F: Fn(T1, T2, T3) -> R, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, + S: 'static, { - pub fn new(f: F, cfg1: ExtractorConfig, - cfg2: ExtractorConfig, cfg3: ExtractorConfig) -> Self - { - With3{hnd: Rc::new(UnsafeCell::new(f)), cfg1, cfg2, cfg3, _s: PhantomData} + pub fn new( + f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig, + cfg3: ExtractorConfig, + ) -> Self { + With3 { + hnd: Rc::new(UnsafeCell::new(f)), + cfg1, + cfg2, + cfg3, + _s: PhantomData, + } } } impl Handler for With3 - where F: Fn(T1, T2, T3) -> R + 'static, - R: Responder + 'static, - T1: FromRequest, - T2: FromRequest, - T3: FromRequest, - T1: 'static, T2: 'static, T3: 'static, S: 'static +where + F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: FromRequest, + T2: FromRequest, + T3: FromRequest, + T1: 'static, + T2: 'static, + T3: 'static, + S: 'static, { type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut3{ + let mut fut = WithHandlerFut3 { req, hnd: Rc::clone(&self.hnd), cfg1: self.cfg1.clone(), @@ -367,12 +413,13 @@ impl Handler for With3 } struct WithHandlerFut3 - where F: Fn(T1, T2, T3) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - S: 'static +where + F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, + S: 'static, { hnd: Rc>, req: HttpRequest, @@ -382,26 +429,27 @@ struct WithHandlerFut3 started: bool, item1: Option, item2: Option, - fut1: Option>>, - fut2: Option>>, - fut3: Option>>, - fut4: Option>>, + fut1: Option>>, + fut2: Option>>, + fut3: Option>>, + fut4: Option>>, } impl Future for WithHandlerFut3 - where F: Fn(T1, T2, T3) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - S: 'static +where + F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, + S: 'static, { type Item = HttpResponse; type Error = Error; fn poll(&mut self) -> Poll { if let Some(ref mut fut) = self.fut4 { - return fut.poll() + return fut.poll(); } if !self.started { @@ -412,41 +460,43 @@ impl Future for WithHandlerFut3 let mut fut = T2::from_request(&self.req, self.cfg2.as_ref()); match fut.poll() { Ok(Async::Ready(item2)) => { - let mut fut = T3::from_request(&self.req, self.cfg3.as_ref()); + let mut fut = + T3::from_request(&self.req, self.cfg3.as_ref()); match fut.poll() { Ok(Async::Ready(item3)) => { - let hnd: &mut F = unsafe{&mut *self.hnd.get()}; + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(item1, item2, item3) .respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyItem::Message(resp) => - return Ok(Async::Ready(resp)), + ReplyItem::Message(resp) => { + return Ok(Async::Ready(resp)) + } ReplyItem::Future(fut) => { self.fut4 = Some(fut); - return self.poll() + return self.poll(); } }, Err(e) => return Err(e.into()), } - }, + } Ok(Async::NotReady) => { self.item1 = Some(item1); self.item2 = Some(item2); self.fut3 = Some(Box::new(fut)); return Ok(Async::NotReady); - }, + } Err(e) => return Err(e), } - }, + } Ok(Async::NotReady) => { self.item1 = Some(item1); self.fut2 = Some(Box::new(fut)); return Ok(Async::NotReady); - }, + } Err(e) => return Err(e), } - }, + } Ok(Async::NotReady) => { self.fut1 = Some(Box::new(fut)); return Ok(Async::NotReady); @@ -460,9 +510,11 @@ impl Future for WithHandlerFut3 Async::Ready(item) => { self.item1 = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new( - T2::from_request(&self.req, self.cfg2.as_ref()))); - }, + self.fut2 = Some(Box::new(T2::from_request( + &self.req, + self.cfg2.as_ref(), + ))); + } Async::NotReady => return Ok(Async::NotReady), } } @@ -472,9 +524,11 @@ impl Future for WithHandlerFut3 Async::Ready(item) => { self.item2 = Some(item); self.fut2.take(); - self.fut3 = Some(Box::new( - T3::from_request(&self.req, self.cfg3.as_ref()))); - }, + self.fut3 = Some(Box::new(T3::from_request( + &self.req, + self.cfg3.as_ref(), + ))); + } Async::NotReady => return Ok(Async::NotReady), } } @@ -484,11 +538,12 @@ impl Future for WithHandlerFut3 Async::NotReady => return Ok(Async::NotReady), }; - let hnd: &mut F = unsafe{&mut *self.hnd.get()}; - let item = match (*hnd)(self.item1.take().unwrap(), - self.item2.take().unwrap(), - item) - .respond_to(self.req.drop_state()) + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; + let item = match (*hnd)( + self.item1.take().unwrap(), + self.item2.take().unwrap(), + item, + ).respond_to(self.req.drop_state()) { Ok(item) => item.into(), Err(err) => return Err(err.into()), diff --git a/src/ws/client.rs b/src/ws/client.rs index 7372832f..522ae02a 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -1,67 +1,65 @@ //! Http client request -use std::{fmt, io, str}; -use std::rc::Rc; use std::cell::UnsafeCell; +use std::rc::Rc; use std::time::Duration; +use std::{fmt, io, str}; use base64; -use rand; +use byteorder::{ByteOrder, NetworkEndian}; use bytes::Bytes; use cookie::Cookie; -use byteorder::{ByteOrder, NetworkEndian}; -use http::{HttpTryFrom, StatusCode, Error as HttpError}; -use http::header::{self, HeaderName, HeaderValue}; -use sha1::Sha1; -use futures::{Async, Future, Poll, Stream}; use futures::unsync::mpsc::{unbounded, UnboundedSender}; +use futures::{Async, Future, Poll, Stream}; +use http::header::{self, HeaderName, HeaderValue}; +use http::{Error as HttpError, HttpTryFrom, StatusCode}; +use rand; +use sha1::Sha1; use actix::prelude::*; -use body::{Body, Binary}; +use body::{Binary, Body}; use error::{Error, UrlParseError}; use header::IntoHeaderValue; -use payload::PayloadHelper; use httpmessage::HttpMessage; +use payload::PayloadHelper; -use client::{ClientRequest, ClientRequestBuilder, ClientResponse, - ClientConnector, SendRequest, SendRequestError, - HttpResponseParserError}; +use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, + HttpResponseParserError, SendRequest, SendRequestError}; -use super::{Message, ProtocolError}; use super::frame::Frame; use super::proto::{CloseCode, OpCode}; - +use super::{Message, ProtocolError}; /// Websocket client error #[derive(Fail, Debug)] pub enum ClientError { - #[fail(display="Invalid url")] + #[fail(display = "Invalid url")] InvalidUrl, - #[fail(display="Invalid response status")] + #[fail(display = "Invalid response status")] InvalidResponseStatus(StatusCode), - #[fail(display="Invalid upgrade header")] + #[fail(display = "Invalid upgrade header")] InvalidUpgradeHeader, - #[fail(display="Invalid connection header")] + #[fail(display = "Invalid connection header")] InvalidConnectionHeader(HeaderValue), - #[fail(display="Missing CONNECTION header")] + #[fail(display = "Missing CONNECTION header")] MissingConnectionHeader, - #[fail(display="Missing SEC-WEBSOCKET-ACCEPT header")] + #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] MissingWebSocketAcceptHeader, - #[fail(display="Invalid challenge response")] + #[fail(display = "Invalid challenge response")] InvalidChallengeResponse(String, HeaderValue), - #[fail(display="Http parsing error")] + #[fail(display = "Http parsing error")] Http(Error), - #[fail(display="Url parsing error")] + #[fail(display = "Url parsing error")] Url(UrlParseError), - #[fail(display="Response parsing error")] + #[fail(display = "Response parsing error")] ResponseParseError(HttpResponseParserError), - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] SendRequest(SendRequestError), - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] Protocol(#[cause] ProtocolError), - #[fail(display="{}", _0)] + #[fail(display = "{}", _0)] Io(io::Error), - #[fail(display="Disconnected")] + #[fail(display = "Disconnected")] Disconnected, } @@ -117,14 +115,15 @@ pub struct Client { } impl Client { - /// Create new websocket connection pub fn new>(uri: S) -> Client { Client::with_connector(uri, ClientConnector::from_registry()) } /// Create new websocket connection with custom `ClientConnector` - pub fn with_connector>(uri: S, conn: Addr) -> Client { + pub fn with_connector>( + uri: S, conn: Addr + ) -> Client { let mut cl = Client { request: ClientRequest::build(), err: None, @@ -140,11 +139,13 @@ impl Client { /// Set supported websocket protocols pub fn protocols(mut self, protos: U) -> Self - where U: IntoIterator + 'static, - V: AsRef + where + U: IntoIterator + 'static, + V: AsRef, { - let mut protos = protos.into_iter() - .fold(String::new(), |acc, s| {acc + s.as_ref() + ","}); + let mut protos = protos + .into_iter() + .fold(String::new(), |acc, s| acc + s.as_ref() + ","); protos.pop(); self.protocols = Some(protos); self @@ -158,7 +159,8 @@ impl Client { /// Set request Origin pub fn origin(mut self, origin: V) -> Self - where HeaderValue: HttpTryFrom + where + HeaderValue: HttpTryFrom, { match HeaderValue::try_from(origin) { Ok(value) => self.origin = Some(value), @@ -185,7 +187,9 @@ impl Client { /// Set request header pub fn header(mut self, key: K, value: V) -> Self - where HeaderName: HttpTryFrom, V: IntoHeaderValue + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, { self.request.header(key, value); self @@ -204,8 +208,7 @@ impl Client { pub fn connect(&mut self) -> ClientHandshake { if let Some(e) = self.err.take() { ClientHandshake::error(e) - } - else if let Some(e) = self.http_err.take() { + } else if let Some(e) = self.http_err.take() { ClientHandshake::error(Error::from(e).into()) } else { // origin @@ -216,11 +219,13 @@ impl Client { self.request.upgrade(); self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::CONNECTION, "upgrade"); - self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); + self.request + .set_header(header::SEC_WEBSOCKET_VERSION, "13"); self.request.with_connector(self.conn.clone()); if let Some(protocols) = self.protocols.take() { - self.request.set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); + self.request + .set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); } let request = match self.request.finish() { Ok(req) => req, @@ -228,14 +233,16 @@ impl Client { }; if request.uri().host().is_none() { - return ClientHandshake::error(ClientError::InvalidUrl) + return ClientHandshake::error(ClientError::InvalidUrl); } if let Some(scheme) = request.uri().scheme_part() { - if scheme != "http" && scheme != "https" && scheme != "ws" && scheme != "wss" { - return ClientHandshake::error(ClientError::InvalidUrl) + if scheme != "http" && scheme != "https" && scheme != "ws" + && scheme != "wss" + { + return ClientHandshake::error(ClientError::InvalidUrl); } } else { - return ClientHandshake::error(ClientError::InvalidUrl) + return ClientHandshake::error(ClientError::InvalidUrl); } // start handshake @@ -263,8 +270,7 @@ pub struct ClientHandshake { } impl ClientHandshake { - fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake - { + fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake { // Generate a random key for the `Sec-WebSocket-Key` header. // a base64-encoded (see Section 4 of [RFC4648]) value that, // when decoded, is 16 bytes in length (RFC 6455) @@ -273,12 +279,13 @@ impl ClientHandshake { request.headers_mut().insert( header::SEC_WEBSOCKET_KEY, - HeaderValue::try_from(key.as_str()).unwrap()); + HeaderValue::try_from(key.as_str()).unwrap(), + ); let (tx, rx) = unbounded(); - request.set_body(Body::Streaming( - Box::new(rx.map_err(|_| io::Error::new( - io::ErrorKind::Other, "disconnected").into())))); + request.set_body(Body::Streaming(Box::new(rx.map_err(|_| { + io::Error::new(io::ErrorKind::Other, "disconnected").into() + })))); ClientHandshake { key, @@ -329,20 +336,20 @@ impl Future for ClientHandshake { fn poll(&mut self) -> Poll { if let Some(err) = self.error.take() { - return Err(err) + return Err(err); } let resp = match self.request.as_mut().unwrap().poll()? { Async::Ready(response) => { self.request.take(); response - }, - Async::NotReady => return Ok(Async::NotReady) + } + Async::NotReady => return Ok(Async::NotReady), }; // verify response if resp.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(ClientError::InvalidResponseStatus(resp.status())) + return Err(ClientError::InvalidResponseStatus(resp.status())); } // Check for "UPGRADE" to websocket header let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { @@ -356,26 +363,25 @@ impl Future for ClientHandshake { }; if !has_hdr { trace!("Invalid upgrade header"); - return Err(ClientError::InvalidUpgradeHeader) + return Err(ClientError::InvalidUpgradeHeader); } // Check for "CONNECTION" header if let Some(conn) = resp.headers().get(header::CONNECTION) { if let Ok(s) = conn.to_str() { if !s.to_lowercase().contains("upgrade") { trace!("Invalid connection header: {}", s); - return Err(ClientError::InvalidConnectionHeader(conn.clone())) + return Err(ClientError::InvalidConnectionHeader(conn.clone())); } } else { trace!("Invalid connection header: {:?}", conn); - return Err(ClientError::InvalidConnectionHeader(conn.clone())) + return Err(ClientError::InvalidConnectionHeader(conn.clone())); } } else { trace!("Missing connection header"); - return Err(ClientError::MissingConnectionHeader) + return Err(ClientError::MissingConnectionHeader); } - if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT) - { + if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT) { // field is constructed by concatenating /key/ // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -386,12 +392,17 @@ impl Future for ClientHandshake { if key.as_bytes() != encoded.as_bytes() { trace!( "Invalid challenge response: expected: {} received: {:?}", - encoded, key); - return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); + encoded, + key + ); + return Err(ClientError::InvalidChallengeResponse( + encoded, + key.clone(), + )); } } else { trace!("Missing SEC-WEBSOCKET-ACCEPT header"); - return Err(ClientError::MissingWebSocketAcceptHeader) + return Err(ClientError::MissingWebSocketAcceptHeader); }; let inner = Inner { @@ -401,13 +412,16 @@ impl Future for ClientHandshake { }; let inner = Rc::new(UnsafeCell::new(inner)); - Ok(Async::Ready( - (ClientReader{inner: Rc::clone(&inner), max_size: self.max_size}, - ClientWriter{inner}))) + Ok(Async::Ready(( + ClientReader { + inner: Rc::clone(&inner), + max_size: self.max_size, + }, + ClientWriter { inner }, + ))) } } - pub struct ClientReader { inner: Rc>, max_size: usize, @@ -422,7 +436,7 @@ impl fmt::Debug for ClientReader { impl ClientReader { #[inline] fn as_mut(&mut self) -> &mut Inner { - unsafe{ &mut *self.inner.get() } + unsafe { &mut *self.inner.get() } } } @@ -434,7 +448,7 @@ impl Stream for ClientReader { let max_size = self.max_size; let inner = self.as_mut(); if inner.closed { - return Ok(Async::Ready(None)) + return Ok(Async::Ready(None)); } // read @@ -447,31 +461,29 @@ impl Stream for ClientReader { OpCode::Continue => { inner.closed = true; Err(ProtocolError::NoContinuation) - }, + } OpCode::Bad => { inner.closed = true; Err(ProtocolError::BadOpCode) - }, + } OpCode::Close => { inner.closed = true; let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; - Ok(Async::Ready(Some(Message::Close(CloseCode::from(code))))) - }, - OpCode::Ping => - Ok(Async::Ready(Some( - Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into())))), - OpCode::Pong => - Ok(Async::Ready(Some( - Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into())))), - OpCode::Binary => - Ok(Async::Ready(Some(Message::Binary(payload)))), + Ok(Async::Ready(Some(Message::Close(CloseCode::from( + code, + ))))) + } + OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( + String::from_utf8_lossy(payload.as_ref()).into(), + )))), + OpCode::Pong => Ok(Async::Ready(Some(Message::Pong( + String::from_utf8_lossy(payload.as_ref()).into(), + )))), + OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))), OpCode::Text => { let tmp = Vec::from(payload.as_ref()); match String::from_utf8(tmp) { - Ok(s) => - Ok(Async::Ready(Some(Message::Text(s)))), + Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), Err(_) => { inner.closed = true; Err(ProtocolError::BadEncoding) @@ -491,18 +503,17 @@ impl Stream for ClientReader { } pub struct ClientWriter { - inner: Rc> + inner: Rc>, } impl ClientWriter { #[inline] fn as_mut(&mut self) -> &mut Inner { - unsafe{ &mut *self.inner.get() } + unsafe { &mut *self.inner.get() } } } impl ClientWriter { - /// Write payload #[inline] fn write(&mut self, mut data: Binary) { @@ -528,13 +539,23 @@ impl ClientWriter { /// Send ping frame #[inline] pub fn ping(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true)); + self.write(Frame::message( + Vec::from(message), + OpCode::Ping, + true, + true, + )); } /// Send pong frame #[inline] pub fn pong(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true)); + self.write(Frame::message( + Vec::from(message), + OpCode::Pong, + true, + true, + )); } /// Send close frame diff --git a/src/ws/context.rs b/src/ws/context.rs index 92151c0d..ef333b41 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -1,25 +1,26 @@ -use std::mem; -use futures::{Async, Poll}; use futures::sync::oneshot::Sender; use futures::unsync::oneshot; +use futures::{Async, Poll}; use smallvec::SmallVec; +use std::mem; -use actix::{Actor, ActorState, ActorContext, AsyncContext, - Addr, Handler, Message, Syn, Unsync, SpawnHandle}; +use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::fut::ActorFuture; -use actix::dev::{ContextImpl, ToEnvelope, SyncEnvelope}; +use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, + SpawnHandle, Syn, Unsync}; -use body::{Body, Binary}; +use body::{Binary, Body}; +use context::{ActorHttpContext, Drain, Frame as ContextFrame}; use error::{Error, ErrorInternalServerError}; use httprequest::HttpRequest; -use context::{Frame as ContextFrame, ActorHttpContext, Drain}; use ws::frame::Frame; -use ws::proto::{OpCode, CloseCode}; - +use ws::proto::{CloseCode, OpCode}; /// Execution context for `WebSockets` actors -pub struct WebsocketContext where A: Actor>, +pub struct WebsocketContext +where + A: Actor>, { inner: ContextImpl, stream: Option>, @@ -27,7 +28,9 @@ pub struct WebsocketContext where A: Actor ActorContext for WebsocketContext where A: Actor +impl ActorContext for WebsocketContext +where + A: Actor, { fn stop(&mut self) { self.inner.stop(); @@ -40,16 +43,20 @@ impl ActorContext for WebsocketContext where A: Actor } } -impl AsyncContext for WebsocketContext where A: Actor +impl AsyncContext for WebsocketContext +where + A: Actor, { fn spawn(&mut self, fut: F) -> SpawnHandle - where F: ActorFuture + 'static + where + F: ActorFuture + 'static, { self.inner.spawn(fut) } fn wait(&mut self, fut: F) - where F: ActorFuture + 'static + where + F: ActorFuture + 'static, { self.inner.wait(fut) } @@ -57,8 +64,8 @@ impl AsyncContext for WebsocketContext where A: Actor bool { - self.inner.waiting() || self.inner.state() == ActorState::Stopping || - self.inner.state() == ActorState::Stopped + self.inner.waiting() || self.inner.state() == ActorState::Stopping + || self.inner.state() == ActorState::Stopped } fn cancel_future(&mut self, handle: SpawnHandle) -> bool { @@ -78,8 +85,10 @@ impl AsyncContext for WebsocketContext where A: Actor WebsocketContext where A: Actor { - +impl WebsocketContext +where + A: Actor, +{ #[inline] pub fn new(req: HttpRequest, actor: A) -> WebsocketContext { WebsocketContext::from_request(req).actor(actor) @@ -101,8 +110,10 @@ impl WebsocketContext where A: Actor { } } -impl WebsocketContext where A: Actor { - +impl WebsocketContext +where + A: Actor, +{ /// Write payload #[inline] fn write(&mut self, data: Binary) { @@ -145,13 +156,23 @@ impl WebsocketContext where A: Actor { /// Send ping frame #[inline] pub fn ping(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Ping, true, false)); + self.write(Frame::message( + Vec::from(message), + OpCode::Ping, + true, + false, + )); } /// Send pong frame #[inline] pub fn pong(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Pong, true, false)); + self.write(Frame::message( + Vec::from(message), + OpCode::Pong, + true, + false, + )); } /// Send close frame @@ -191,8 +212,11 @@ impl WebsocketContext where A: Actor { } } -impl ActorHttpContext for WebsocketContext where A: Actor, S: 'static { - +impl ActorHttpContext for WebsocketContext +where + A: Actor, + S: 'static, +{ #[inline] fn disconnected(&mut self) { self.disconnected = true; @@ -200,12 +224,11 @@ impl ActorHttpContext for WebsocketContext where A: Actor Poll>, Error> { - let ctx: &mut WebsocketContext = unsafe { - mem::transmute(self as &mut WebsocketContext) - }; + let ctx: &mut WebsocketContext = + unsafe { mem::transmute(self as &mut WebsocketContext) }; if self.inner.alive() && self.inner.poll(ctx).is_err() { - return Err(ErrorInternalServerError("error")) + return Err(ErrorInternalServerError("error")); } // frames @@ -220,8 +243,10 @@ impl ActorHttpContext for WebsocketContext where A: Actor ToEnvelope for WebsocketContext - where A: Actor> + Handler, - M: Message + Send + 'static, M::Result: Send +where + A: Actor> + Handler, + M: Message + Send + 'static, + M::Result: Send, { fn pack(msg: M, tx: Option>) -> SyncEnvelope { SyncEnvelope::new(msg, tx) @@ -229,7 +254,9 @@ impl ToEnvelope for WebsocketContext } impl From> for Body - where A: Actor>, S: 'static +where + A: Actor>, + S: 'static, { fn from(ctx: WebsocketContext) -> Body { Body::Actor(Box::new(ctx)) diff --git a/src/ws/frame.rs b/src/ws/frame.rs index cb3e82fe..d9159e54 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,17 +1,17 @@ -use std::{fmt, mem, ptr}; -use std::iter::FromIterator; -use bytes::{Bytes, BytesMut, BufMut}; -use byteorder::{ByteOrder, BigEndian, NetworkEndian}; +use byteorder::{BigEndian, ByteOrder, NetworkEndian}; +use bytes::{BufMut, Bytes, BytesMut}; use futures::{Async, Poll, Stream}; use rand; +use std::iter::FromIterator; +use std::{fmt, mem, ptr}; use body::Binary; -use error::{PayloadError}; +use error::PayloadError; use payload::PayloadHelper; use ws::ProtocolError; -use ws::proto::{OpCode, CloseCode}; use ws::mask::apply_mask; +use ws::proto::{CloseCode, OpCode}; /// A struct representing a `WebSocket` frame. #[derive(Debug)] @@ -22,7 +22,6 @@ pub struct Frame { } impl Frame { - /// Destruct frame pub fn unpack(self) -> (bool, OpCode, Binary) { (self.finished, self.opcode, self.payload) @@ -40,20 +39,22 @@ impl Frame { Vec::new() } else { Vec::from_iter( - raw[..].iter() + raw[..] + .iter() .chain(reason.as_bytes().iter()) - .cloned()) + .cloned(), + ) }; Frame::message(payload, OpCode::Close, true, genmask) } - #[cfg_attr(feature="cargo-clippy", allow(type_complexity))] - fn read_copy_md(pl: &mut PayloadHelper, - server: bool, - max_size: usize + #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] + fn read_copy_md( + pl: &mut PayloadHelper, server: bool, max_size: usize ) -> Poll)>, ProtocolError> - where S: Stream + where + S: Stream, { let mut idx = 2; let buf = match pl.copy(2)? { @@ -68,16 +69,16 @@ impl Frame { // check masking let masked = second & 0x80 != 0; if !masked && server { - return Err(ProtocolError::UnmaskedFrame) + return Err(ProtocolError::UnmaskedFrame); } else if masked && !server { - return Err(ProtocolError::MaskedFrame) + return Err(ProtocolError::MaskedFrame); } // Op code let opcode = OpCode::from(first & 0x0F); if let OpCode::Bad = opcode { - return Err(ProtocolError::InvalidOpcode(first & 0x0F)) + return Err(ProtocolError::InvalidOpcode(first & 0x0F)); } let len = second & 0x7F; @@ -105,7 +106,7 @@ impl Frame { // check for max allowed size if length > max_size { - return Err(ProtocolError::Overflow) + return Err(ProtocolError::Overflow); } let mask = if server { @@ -115,25 +116,32 @@ impl Frame { Async::NotReady => return Ok(Async::NotReady), }; - let mask: &[u8] = &buf[idx..idx+4]; - let mask_u32: u32 = unsafe {ptr::read_unaligned(mask.as_ptr() as *const u32)}; + let mask: &[u8] = &buf[idx..idx + 4]; + let mask_u32: u32 = + unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) }; idx += 4; Some(mask_u32) } else { None }; - Ok(Async::Ready(Some((idx, finished, opcode, length, mask)))) + Ok(Async::Ready(Some(( + idx, + finished, + opcode, + length, + mask, + )))) } - fn read_chunk_md(chunk: &[u8], server: bool, max_size: usize) - -> Poll<(usize, bool, OpCode, usize, Option), ProtocolError> - { + fn read_chunk_md( + chunk: &[u8], server: bool, max_size: usize + ) -> Poll<(usize, bool, OpCode, usize, Option), ProtocolError> { let chunk_len = chunk.len(); let mut idx = 2; if chunk_len < 2 { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } let first = chunk[0]; @@ -143,29 +151,29 @@ impl Frame { // check masking let masked = second & 0x80 != 0; if !masked && server { - return Err(ProtocolError::UnmaskedFrame) + return Err(ProtocolError::UnmaskedFrame); } else if masked && !server { - return Err(ProtocolError::MaskedFrame) + return Err(ProtocolError::MaskedFrame); } // Op code let opcode = OpCode::from(first & 0x0F); if let OpCode::Bad = opcode { - return Err(ProtocolError::InvalidOpcode(first & 0x0F)) + return Err(ProtocolError::InvalidOpcode(first & 0x0F)); } let len = second & 0x7F; let length = if len == 126 { if chunk_len < 4 { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } let len = NetworkEndian::read_uint(&chunk[idx..], 2) as usize; idx += 2; len } else if len == 127 { if chunk_len < 10 { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } let len = NetworkEndian::read_uint(&chunk[idx..], 8) as usize; idx += 8; @@ -176,16 +184,17 @@ impl Frame { // check for max allowed size if length > max_size { - return Err(ProtocolError::Overflow) + return Err(ProtocolError::Overflow); } let mask = if server { if chunk_len < idx + 4 { - return Ok(Async::NotReady) + return Ok(Async::NotReady); } - let mask: &[u8] = &chunk[idx..idx+4]; - let mask_u32: u32 = unsafe {ptr::read_unaligned(mask.as_ptr() as *const u32)}; + let mask: &[u8] = &chunk[idx..idx + 4]; + let mask_u32: u32 = + unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) }; idx += 4; Some(mask_u32) } else { @@ -196,9 +205,11 @@ impl Frame { } /// Parse the input stream into a frame. - pub fn parse(pl: &mut PayloadHelper, server: bool, max_size: usize) - -> Poll, ProtocolError> - where S: Stream + pub fn parse( + pl: &mut PayloadHelper, server: bool, max_size: usize + ) -> Poll, ProtocolError> + where + S: Stream, { // try to parse ws frame md from one chunk let result = match pl.get_chunk()? { @@ -229,7 +240,10 @@ impl Frame { // no need for body if length == 0 { return Ok(Async::Ready(Some(Frame { - finished, opcode, payload: Binary::from("") }))); + finished, + opcode, + payload: Binary::from(""), + }))); } let data = match pl.read_exact(length)? { @@ -245,26 +259,32 @@ impl Frame { } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Async::Ready(Some(Frame::default()))) + return Ok(Async::Ready(Some(Frame::default()))); } - _ => () + _ => (), } // unmask if let Some(mask) = mask { #[allow(mutable_transmutes)] - let p: &mut [u8] = unsafe{let ptr: &[u8] = &data; mem::transmute(ptr)}; + let p: &mut [u8] = unsafe { + let ptr: &[u8] = &data; + mem::transmute(ptr) + }; apply_mask(p, mask); } Ok(Async::Ready(Some(Frame { - finished, opcode, payload: data.into() }))) + finished, + opcode, + payload: data.into(), + }))) } /// Generate binary representation - pub fn message>(data: B, code: OpCode, - finished: bool, genmask: bool) -> Binary - { + pub fn message>( + data: B, code: OpCode, finished: bool, genmask: bool + ) -> Binary { let payload = data.into(); let one: u8 = if finished { 0x80 | Into::::into(code) @@ -286,19 +306,19 @@ impl Frame { let mut buf = BytesMut::with_capacity(p_len + 4); buf.put_slice(&[one, two | 126]); { - let buf_mut = unsafe{buf.bytes_mut()}; + let buf_mut = unsafe { buf.bytes_mut() }; BigEndian::write_u16(&mut buf_mut[..2], payload_len as u16); } - unsafe{buf.advance_mut(2)}; + unsafe { buf.advance_mut(2) }; buf } else { let mut buf = BytesMut::with_capacity(p_len + 10); buf.put_slice(&[one, two | 127]); { - let buf_mut = unsafe{buf.bytes_mut()}; + let buf_mut = unsafe { buf.bytes_mut() }; BigEndian::write_u64(&mut buf_mut[..8], payload_len as u64); } - unsafe{buf.advance_mut(8)}; + unsafe { buf.advance_mut(8) }; buf }; @@ -308,7 +328,7 @@ impl Frame { { let buf_mut = buf.bytes_mut(); *(buf_mut as *mut _ as *mut u32) = mask; - buf_mut[4..payload_len+4].copy_from_slice(payload.as_ref()); + buf_mut[4..payload_len + 4].copy_from_slice(payload.as_ref()); apply_mask(&mut buf_mut[4..], mask); } buf.advance_mut(payload_len + 4); @@ -333,7 +353,8 @@ impl Default for Frame { impl fmt::Display for Frame { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, + write!( + f, " final: {} @@ -341,11 +362,15 @@ impl fmt::Display for Frame { payload length: {} payload: 0x{} ", - self.finished, - self.opcode, - self.payload.len(), - self.payload.as_ref().iter().map( - |byte| format!("{:x}", byte)).collect::()) + self.finished, + self.opcode, + self.payload.len(), + self.payload + .as_ref() + .iter() + .map(|byte| format!("{:x}", byte)) + .collect::() + ) } } @@ -360,7 +385,7 @@ mod tests { _ => false, } } - + fn extract(frm: Poll, ProtocolError>) -> Frame { match frm { Ok(Async::Ready(Some(frame))) => frame, @@ -370,8 +395,9 @@ mod tests { #[test] fn test_parse() { - let mut buf = PayloadHelper::new( - once(Ok(BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]).freeze()))); + let mut buf = PayloadHelper::new(once(Ok(BytesMut::from( + &[0b0000_0001u8, 0b0000_0001u8][..], + ).freeze()))); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); diff --git a/src/ws/mask.rs b/src/ws/mask.rs index 33216bf2..f78258fa 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -20,7 +20,7 @@ fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { /// Faster version of `apply_mask()` which operates on 8-byte blocks. #[inline] -#[cfg_attr(feature="cargo-clippy", allow(cast_lossless))] +#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { let mut ptr = buf.as_mut_ptr(); let mut len = buf.len(); @@ -85,13 +85,16 @@ fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { // Possible last block. if len > 0 { - unsafe { xor_mem(ptr, mask_u32, len); } + unsafe { + xor_mem(ptr, mask_u32, len); + } } } #[inline] -// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so inefficient, -// it could be done better. The compiler does not see that len is limited to 3. +// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so +// inefficient, it could be done better. The compiler does not see that len is +// limited to 3. unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) { let mut b: u32 = uninitialized(); #[allow(trivial_casts)] @@ -103,19 +106,17 @@ unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) { #[cfg(test)] mod tests { + use super::{apply_mask_fallback, apply_mask_fast32}; use std::ptr; - use super::{apply_mask_fallback, apply_mask_fast32}; #[test] fn test_apply_mask() { - let mask = [ - 0x6d, 0xb6, 0xb2, 0x80, - ]; - let mask_u32: u32 = unsafe {ptr::read_unaligned(mask.as_ptr() as *const u32)}; + let mask = [0x6d, 0xb6, 0xb2, 0x80]; + let mask_u32: u32 = unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) }; let unmasked = vec![ - 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, - 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, 0x12, 0x03, + 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, + 0x74, 0xf9, 0x12, 0x03, ]; // Check masking with proper alignment. diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 9c5c74c5..97162b19 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -1,7 +1,8 @@ //! `WebSocket` support for Actix //! -//! To setup a `WebSocket`, first do web socket handshake then on success convert `Payload` -//! into a `WsStream` stream and then use `WsWriter` to communicate with the peer. +//! To setup a `WebSocket`, first do web socket handshake then on success +//! convert `Payload` into a `WsStream` stream and then use `WsWriter` to +//! communicate with the peer. //! //! ## Example //! @@ -42,62 +43,61 @@ //! # .finish(); //! # } //! ``` -use bytes::Bytes; -use http::{Method, StatusCode, header}; -use futures::{Async, Poll, Stream}; use byteorder::{ByteOrder, NetworkEndian}; +use bytes::Bytes; +use futures::{Async, Poll, Stream}; +use http::{header, Method, StatusCode}; use actix::{Actor, AsyncContext, StreamHandler}; use body::Binary; -use payload::PayloadHelper; use error::{Error, PayloadError, ResponseError}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; +use payload::PayloadHelper; -mod frame; -mod proto; -mod context; -mod mask; mod client; +mod context; +mod frame; +mod mask; +mod proto; -pub use self::frame::Frame; -pub use self::proto::OpCode; -pub use self::proto::CloseCode; +pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter}; pub use self::context::WebsocketContext; -pub use self::client::{Client, ClientError, - ClientReader, ClientWriter, ClientHandshake}; +pub use self::frame::Frame; +pub use self::proto::CloseCode; +pub use self::proto::OpCode; /// Websocket protocol errors #[derive(Fail, Debug)] pub enum ProtocolError { /// Received an unmasked frame from client - #[fail(display="Received an unmasked frame from client")] + #[fail(display = "Received an unmasked frame from client")] UnmaskedFrame, /// Received a masked frame from server - #[fail(display="Received a masked frame from server")] + #[fail(display = "Received a masked frame from server")] MaskedFrame, /// Encountered invalid opcode - #[fail(display="Invalid opcode: {}", _0)] + #[fail(display = "Invalid opcode: {}", _0)] InvalidOpcode(u8), /// Invalid control frame length - #[fail(display="Invalid control frame length: {}", _0)] + #[fail(display = "Invalid control frame length: {}", _0)] InvalidLength(usize), /// Bad web socket op code - #[fail(display="Bad web socket op code")] + #[fail(display = "Bad web socket op code")] BadOpCode, /// A payload reached size limit. - #[fail(display="A payload reached size limit.")] + #[fail(display = "A payload reached size limit.")] Overflow, /// Continuation is not supported - #[fail(display="Continuation is not supported.")] + #[fail(display = "Continuation is not supported.")] NoContinuation, /// Bad utf-8 encoding - #[fail(display="Bad utf-8 encoding.")] + #[fail(display = "Bad utf-8 encoding.")] BadEncoding, /// Payload error - #[fail(display="Payload error: {}", _0)] + #[fail(display = "Payload error: {}", _0)] Payload(#[cause] PayloadError), } @@ -113,42 +113,46 @@ impl From for ProtocolError { #[derive(Fail, PartialEq, Debug)] pub enum HandshakeError { /// Only get method is allowed - #[fail(display="Method not allowed")] + #[fail(display = "Method not allowed")] GetMethodRequired, /// Upgrade header if not set to websocket - #[fail(display="Websocket upgrade is expected")] + #[fail(display = "Websocket upgrade is expected")] NoWebsocketUpgrade, /// Connection header is not set to upgrade - #[fail(display="Connection upgrade is expected")] + #[fail(display = "Connection upgrade is expected")] NoConnectionUpgrade, /// Websocket version header is not set - #[fail(display="Websocket version header is required")] + #[fail(display = "Websocket version header is required")] NoVersionHeader, /// Unsupported websocket version - #[fail(display="Unsupported version")] + #[fail(display = "Unsupported version")] UnsupportedVersion, /// Websocket key is not set or wrong - #[fail(display="Unknown websocket key")] + #[fail(display = "Unknown websocket key")] BadWebsocketKey, } impl ResponseError for HandshakeError { - fn error_response(&self) -> HttpResponse { match *self { - HandshakeError::GetMethodRequired => { - HttpResponse::MethodNotAllowed().header(header::ALLOW, "GET").finish() - } + HandshakeError::GetMethodRequired => HttpResponse::MethodNotAllowed() + .header(header::ALLOW, "GET") + .finish(), HandshakeError::NoWebsocketUpgrade => HttpResponse::BadRequest() - .reason("No WebSocket UPGRADE header found").finish(), + .reason("No WebSocket UPGRADE header found") + .finish(), HandshakeError::NoConnectionUpgrade => HttpResponse::BadRequest() - .reason("No CONNECTION upgrade").finish(), + .reason("No CONNECTION upgrade") + .finish(), HandshakeError::NoVersionHeader => HttpResponse::BadRequest() - .reason("Websocket version header is required").finish(), + .reason("Websocket version header is required") + .finish(), HandshakeError::UnsupportedVersion => HttpResponse::BadRequest() - .reason("Unsupported version").finish(), + .reason("Unsupported version") + .finish(), HandshakeError::BadWebsocketKey => HttpResponse::BadRequest() - .reason("Handshake error").finish(), + .reason("Handshake error") + .finish(), } } } @@ -165,8 +169,9 @@ pub enum Message { /// Do websocket handshake and start actor pub fn start(req: HttpRequest, actor: A) -> Result - where A: Actor> + StreamHandler, - S: 'static +where + A: Actor> + StreamHandler, + S: 'static, { let mut resp = handshake(&req)?; let stream = WsStream::new(req.clone()); @@ -185,10 +190,12 @@ pub fn start(req: HttpRequest, actor: A) -> Result // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. -pub fn handshake(req: &HttpRequest) -> Result { +pub fn handshake( + req: &HttpRequest +) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { - return Err(HandshakeError::GetMethodRequired) + return Err(HandshakeError::GetMethodRequired); } // Check for "UPGRADE" to websocket header @@ -202,17 +209,19 @@ pub fn handshake(req: &HttpRequest) -> Result(req: &HttpRequest) -> Result(req: &HttpRequest) -> Result { max_size: usize, } -impl WsStream where S: Stream { +impl WsStream +where + S: Stream, +{ /// Create new websocket frames stream pub fn new(stream: S) -> WsStream { - WsStream { rx: PayloadHelper::new(stream), - closed: false, - max_size: 65_536, + WsStream { + rx: PayloadHelper::new(stream), + closed: false, + max_size: 65_536, } } @@ -267,13 +280,16 @@ impl WsStream where S: Stream { } } -impl Stream for WsStream where S: Stream { +impl Stream for WsStream +where + S: Stream, +{ type Item = Message; type Error = ProtocolError; fn poll(&mut self) -> Poll, Self::Error> { if self.closed { - return Ok(Async::Ready(None)) + return Ok(Async::Ready(None)); } match Frame::parse(&mut self.rx, true, self.max_size) { @@ -283,7 +299,7 @@ impl Stream for WsStream where S: Stream { // continuation is not supported if !finished { self.closed = true; - return Err(ProtocolError::NoContinuation) + return Err(ProtocolError::NoContinuation); } match opcode { @@ -295,23 +311,21 @@ impl Stream for WsStream where S: Stream { OpCode::Close => { self.closed = true; let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; - Ok(Async::Ready( - Some(Message::Close(CloseCode::from(code))))) - }, - OpCode::Ping => - Ok(Async::Ready(Some( - Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into())))), - OpCode::Pong => - Ok(Async::Ready(Some( - Message::Pong(String::from_utf8_lossy(payload.as_ref()).into())))), - OpCode::Binary => - Ok(Async::Ready(Some(Message::Binary(payload)))), + Ok(Async::Ready(Some(Message::Close(CloseCode::from( + code, + ))))) + } + OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( + String::from_utf8_lossy(payload.as_ref()).into(), + )))), + OpCode::Pong => Ok(Async::Ready(Some(Message::Pong( + String::from_utf8_lossy(payload.as_ref()).into(), + )))), + OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))), OpCode::Text => { let tmp = Vec::from(payload.as_ref()); match String::from_utf8(tmp) { - Ok(s) => - Ok(Async::Ready(Some(Message::Text(s)))), + Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), Err(_) => { self.closed = true; Err(ProtocolError::BadEncoding) @@ -333,77 +347,168 @@ impl Stream for WsStream where S: Stream { #[cfg(test)] mod tests { use super::*; + use http::{header, HeaderMap, Method, Uri, Version}; use std::str::FromStr; - 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(), None); - assert_eq!(HandshakeError::GetMethodRequired, handshake(&req).err().unwrap()); + let req = HttpRequest::new( + Method::POST, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + HeaderMap::new(), + None, + ); + assert_eq!( + HandshakeError::GetMethodRequired, + handshake(&req).err().unwrap() + ); - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, HeaderMap::new(), None); - assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); + let req = HttpRequest::new( + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + HeaderMap::new(), + None, + ); + assert_eq!( + HandshakeError::NoWebsocketUpgrade, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, - header::HeaderValue::from_static("test")); - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("test"), + ); + let req = HttpRequest::new( + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); + assert_eq!( + HandshakeError::NoWebsocketUpgrade, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, - header::HeaderValue::from_static("websocket")); - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert_eq!(HandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap()); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + let req = HttpRequest::new( + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); + assert_eq!( + HandshakeError::NoConnectionUpgrade, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, - header::HeaderValue::from_static("websocket")); - headers.insert(header::CONNECTION, - header::HeaderValue::from_static("upgrade")); - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert_eq!(HandshakeError::NoVersionHeader, handshake(&req).err().unwrap()); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ); + let req = HttpRequest::new( + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); + assert_eq!( + HandshakeError::NoVersionHeader, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, - header::HeaderValue::from_static("websocket")); - headers.insert(header::CONNECTION, - header::HeaderValue::from_static("upgrade")); - headers.insert(header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("5")); - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert_eq!(HandshakeError::UnsupportedVersion, handshake(&req).err().unwrap()); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ); + headers.insert( + header::SEC_WEBSOCKET_VERSION, + header::HeaderValue::from_static("5"), + ); + let req = HttpRequest::new( + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); + assert_eq!( + HandshakeError::UnsupportedVersion, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, - header::HeaderValue::from_static("websocket")); - headers.insert(header::CONNECTION, - header::HeaderValue::from_static("upgrade")); - headers.insert(header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13")); - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert_eq!(HandshakeError::BadWebsocketKey, handshake(&req).err().unwrap()); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ); + headers.insert( + header::SEC_WEBSOCKET_VERSION, + header::HeaderValue::from_static("13"), + ); + let req = HttpRequest::new( + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); + assert_eq!( + HandshakeError::BadWebsocketKey, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, - header::HeaderValue::from_static("websocket")); - headers.insert(header::CONNECTION, - header::HeaderValue::from_static("upgrade")); - headers.insert(header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13")); - headers.insert(header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13")); - let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(), - Version::HTTP_11, headers, None); - assert_eq!(StatusCode::SWITCHING_PROTOCOLS, - handshake(&req).unwrap().finish().status()); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ); + headers.insert( + header::SEC_WEBSOCKET_VERSION, + header::HeaderValue::from_static("13"), + ); + headers.insert( + header::SEC_WEBSOCKET_KEY, + header::HeaderValue::from_static("13"), + ); + let req = HttpRequest::new( + Method::GET, + Uri::from_str("/").unwrap(), + Version::HTTP_11, + headers, + None, + ); + assert_eq!( + StatusCode::SWITCHING_PROTOCOLS, + handshake(&req).unwrap().finish().status() + ); } #[test] diff --git a/src/ws/proto.rs b/src/ws/proto.rs index 5f077a4b..0c2eab0f 100644 --- a/src/ws/proto.rs +++ b/src/ws/proto.rs @@ -1,7 +1,7 @@ -use std::fmt; -use std::convert::{Into, From}; -use sha1; use base64; +use sha1; +use std::convert::{From, Into}; +use std::fmt; use self::OpCode::*; /// Operation codes as part of rfc6455. @@ -26,52 +26,54 @@ pub enum OpCode { impl fmt::Display for OpCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Continue => write!(f, "CONTINUE"), - Text => write!(f, "TEXT"), - Binary => write!(f, "BINARY"), - Close => write!(f, "CLOSE"), - Ping => write!(f, "PING"), - Pong => write!(f, "PONG"), - Bad => write!(f, "BAD"), + Continue => write!(f, "CONTINUE"), + Text => write!(f, "TEXT"), + Binary => write!(f, "BINARY"), + Close => write!(f, "CLOSE"), + Ping => write!(f, "PING"), + Pong => write!(f, "PONG"), + Bad => write!(f, "BAD"), } } } impl Into for OpCode { - fn into(self) -> u8 { match self { - Continue => 0, - Text => 1, - Binary => 2, - Close => 8, - Ping => 9, - Pong => 10, - Bad => { - debug_assert!(false, "Attempted to convert invalid opcode to u8. This is a bug."); - 8 // if this somehow happens, a close frame will help us tear down quickly + Continue => 0, + Text => 1, + Binary => 2, + Close => 8, + Ping => 9, + Pong => 10, + Bad => { + debug_assert!( + false, + "Attempted to convert invalid opcode to u8. This is a bug." + ); + 8 // if this somehow happens, a close frame will help us tear down quickly } } } } impl From for OpCode { - fn from(byte: u8) -> OpCode { match byte { - 0 => Continue, - 1 => Text, - 2 => Binary, - 8 => Close, - 9 => Ping, - 10 => Pong, - _ => Bad + 0 => Continue, + 1 => Text, + 2 => Binary, + 8 => Close, + 9 => Ping, + 10 => Pong, + _ => Bad, } } } use self::CloseCode::*; -/// Status code used to indicate why an endpoint is closing the `WebSocket` connection. +/// Status code used to indicate why an endpoint is closing the `WebSocket` +/// connection. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum CloseCode { /// Indicates a normal closure, meaning that the purpose for @@ -125,12 +127,13 @@ pub enum CloseCode { /// it encountered an unexpected condition that prevented it from /// fulfilling the request. Error, - /// Indicates that the server is restarting. A client may choose to reconnect, - /// and if it does, it should use a randomized delay of 5-30 seconds between attempts. + /// Indicates that the server is restarting. A client may choose to + /// reconnect, and if it does, it should use a randomized delay of 5-30 + /// seconds between attempts. Restart, - /// Indicates that the server is overloaded and the client should either connect - /// to a different IP (when multiple targets exist), or reconnect to the same IP - /// when a user has performed an action. + /// Indicates that the server is overloaded and the client should either + /// connect to a different IP (when multiple targets exist), or + /// reconnect to the same IP when a user has performed an action. Again, #[doc(hidden)] Tls, @@ -141,31 +144,29 @@ pub enum CloseCode { } impl Into for CloseCode { - fn into(self) -> u16 { match self { - Normal => 1000, - Away => 1001, - Protocol => 1002, - Unsupported => 1003, - Status => 1005, - Abnormal => 1006, - Invalid => 1007, - Policy => 1008, - Size => 1009, - Extension => 1010, - Error => 1011, - Restart => 1012, - Again => 1013, - Tls => 1015, - Empty => 0, - Other(code) => code, + Normal => 1000, + Away => 1001, + Protocol => 1002, + Unsupported => 1003, + Status => 1005, + Abnormal => 1006, + Invalid => 1007, + Policy => 1008, + Size => 1009, + Extension => 1010, + Error => 1011, + Restart => 1012, + Again => 1013, + Tls => 1015, + Empty => 0, + Other(code) => code, } } } impl From for CloseCode { - fn from(code: u16) -> CloseCode { match code { 1000 => Normal, @@ -182,7 +183,7 @@ impl From for CloseCode { 1012 => Restart, 1013 => Again, 1015 => Tls, - 0 => Empty, + 0 => Empty, _ => Other(code), } } @@ -200,7 +201,6 @@ pub(crate) fn hash_key(key: &[u8]) -> String { base64::encode(&hasher.digest().bytes()) } - #[cfg(test)] mod test { #![allow(unused_imports, unused_variables, dead_code)] @@ -210,9 +210,9 @@ mod test { ($from:expr => $opcode:pat) => { match OpCode::from($from) { e @ $opcode => (), - e => unreachable!("{:?}", e) + e => unreachable!("{:?}", e), } - } + }; } macro_rules! opcode_from { @@ -220,9 +220,9 @@ mod test { let res: u8 = $from.into(); match res { e @ $opcode => (), - e => unreachable!("{:?}", e) + e => unreachable!("{:?}", e), } - } + }; } #[test] diff --git a/tests/test_client.rs b/tests/test_client.rs index c0e0e6da..b9154cc4 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -1,49 +1,46 @@ extern crate actix; extern crate actix_web; extern crate bytes; -extern crate futures; extern crate flate2; +extern crate futures; extern crate rand; use std::io::Read; use bytes::Bytes; +use flate2::read::GzDecoder; use futures::Future; use futures::stream::once; -use flate2::read::GzDecoder; use rand::Rng; use actix_web::*; - -const STR: &str = - "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; #[test] fn test_simple() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| HttpResponse::Ok().body(STR))); + let mut srv = + test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let request = srv.get().header("x-test", "111").finish().unwrap(); let repr = format!("{:?}", request); @@ -68,23 +65,26 @@ fn test_simple() { #[test] fn test_with_query_parameter() { - let mut srv = test::TestServer::new( - |app| app.handler(|req: HttpRequest| match req.query().get("qp") { + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| match req.query().get("qp") { Some(_) => HttpResponse::Ok().finish(), None => HttpResponse::BadRequest().finish(), - })); + }) + }); - let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/?qp=5").as_str()) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); } - #[test] fn test_no_decompress() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| HttpResponse::Ok().body(STR))); + let mut srv = + test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -111,19 +111,23 @@ fn test_no_decompress() { #[test] fn test_client_gzip_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Deflate) + .body(bytes)) + }) + .responder() + }) + }); // client request let request = srv.post() .content_encoding(http::ContentEncoding::Gzip) - .body(STR).unwrap(); + .body(STR) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -136,19 +140,23 @@ fn test_client_gzip_encoding() { fn test_client_gzip_encoding_large() { let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Deflate) + .body(bytes)) + }) + .responder() + }) + }); // client request let request = srv.post() .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()).unwrap(); + .body(data.clone()) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -164,19 +172,23 @@ fn test_client_gzip_encoding_large_random() { .take(100_000) .collect::(); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Deflate) + .body(bytes)) + }) + .responder() + }) + }); // client request let request = srv.post() .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()).unwrap(); + .body(data.clone()) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -185,22 +197,26 @@ fn test_client_gzip_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] #[test] fn test_client_brotli_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Gzip) + .body(bytes)) + }) + .responder() + }) + }); // client request let request = srv.client(http::Method::POST, "/") .content_encoding(http::ContentEncoding::Br) - .body(STR).unwrap(); + .body(STR) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -209,7 +225,7 @@ fn test_client_brotli_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] #[test] fn test_client_brotli_encoding_large_random() { let data = rand::thread_rng() @@ -217,19 +233,23 @@ fn test_client_brotli_encoding_large_random() { .take(70_000) .collect::(); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(move |bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(move |bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Gzip) + .body(bytes)) + }) + .responder() + }) + }); // client request let request = srv.client(http::Method::POST, "/") .content_encoding(http::ContentEncoding::Br) - .body(data.clone()).unwrap(); + .body(data.clone()) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -239,22 +259,26 @@ fn test_client_brotli_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] #[test] fn test_client_deflate_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Br) + .body(bytes)) + }) + .responder() + }) + }); // client request let request = srv.post() .content_encoding(http::ContentEncoding::Deflate) - .body(STR).unwrap(); + .body(STR) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -263,7 +287,7 @@ fn test_client_deflate_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] #[test] fn test_client_deflate_encoding_large_random() { let data = rand::thread_rng() @@ -271,19 +295,23 @@ fn test_client_deflate_encoding_large_random() { .take(70_000) .collect::(); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Br) + .body(bytes)) + }) + .responder() + }) + }); // client request let request = srv.post() .content_encoding(http::ContentEncoding::Deflate) - .body(data.clone()).unwrap(); + .body(data.clone()) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -294,20 +322,25 @@ fn test_client_deflate_encoding_large_random() { #[test] fn test_client_streaming_explicit() { - let mut srv = test::TestServer::new( - |app| app.handler( - |req: HttpRequest| req.body() + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() .map_err(Error::from) .and_then(|body| { Ok(HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Identity) - .body(body))}) - .responder())); + .chunked() + .content_encoding(http::ContentEncoding::Identity) + .body(body)) + }) + .responder() + }) + }); let body = once(Ok(Bytes::from_static(STR.as_ref()))); - let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); + let request = srv.get() + .body(Body::Streaming(Box::new(body))) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -318,12 +351,14 @@ fn test_client_streaming_explicit() { #[test] fn test_body_streaming_implicit() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| { + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); HttpResponse::Ok() .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body)))})); + .body(Body::Streaming(Box::new(body))) + }) + }); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -338,7 +373,7 @@ fn test_body_streaming_implicit() { fn test_client_cookie_handling() { use actix_web::http::Cookie; fn err() -> Error { - use std::io::{ErrorKind, Error as IoError}; + use std::io::{Error as IoError, ErrorKind}; // stub some generic error Error::from(IoError::from(ErrorKind::NotFound)) } @@ -352,13 +387,12 @@ fn test_client_cookie_handling() { // Q: are all these clones really necessary? A: Yes, possibly let cookie1b = cookie1.clone(); let cookie2b = cookie2.clone(); - let mut srv = test::TestServer::new( - move |app| { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); - app.handler(move |req: HttpRequest| { - // Check cookies were sent correctly - req.cookie("cookie1").ok_or_else(err) + let mut srv = test::TestServer::new(move |app| { + let cookie1 = cookie1b.clone(); + let cookie2 = cookie2b.clone(); + app.handler(move |req: HttpRequest| { + // Check cookies were sent correctly + req.cookie("cookie1").ok_or_else(err) .and_then(|c1| if c1.value() == "value1" { Ok(()) } else { @@ -376,8 +410,8 @@ fn test_client_cookie_handling() { .cookie(cookie2.clone()) .finish() ) - }) - }); + }) + }); let request = srv.get() .cookie(cookie1.clone()) diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 909c9ddf..12cf9709 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -1,11 +1,12 @@ extern crate actix; extern crate actix_web; -extern crate tokio_core; +extern crate bytes; extern crate futures; extern crate h2; extern crate http; -extern crate bytes; -#[macro_use] extern crate serde_derive; +extern crate tokio_core; +#[macro_use] +extern crate serde_derive; use actix_web::*; use bytes::Bytes; @@ -19,15 +20,16 @@ struct PParam { #[test] fn test_path_extractor() { let mut srv = test::TestServer::new(|app| { - app.resource( - "/{username}/index.html", |r| r.with( - |p: Path| format!("Welcome {}!", p.username))); - } - ); + app.resource("/{username}/index.html", |r| { + r.with(|p: Path| format!("Welcome {}!", p.username)) + }); + }); // client request - let request = srv.get().uri(srv.url("/test/index.html")) - .finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -39,15 +41,16 @@ fn test_path_extractor() { #[test] fn test_query_extractor() { let mut srv = test::TestServer::new(|app| { - app.resource( - "/index.html", |r| r.with( - |p: Query| format!("Welcome {}!", p.username))); - } - ); + app.resource("/index.html", |r| { + r.with(|p: Query| format!("Welcome {}!", p.username)) + }); + }); // client request - let request = srv.get().uri(srv.url("/index.html?username=test")) - .finish().unwrap(); + let request = srv.get() + .uri(srv.url("/index.html?username=test")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -56,8 +59,10 @@ fn test_query_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); // client request - let request = srv.get().uri(srv.url("/index.html")) - .finish().unwrap(); + let request = srv.get() + .uri(srv.url("/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -65,16 +70,18 @@ fn test_query_extractor() { #[test] fn test_path_and_query_extractor() { let mut srv = test::TestServer::new(|app| { - app.resource( - "/{username}/index.html", |r| r.route().with2( - |p: Path, q: Query| - format!("Welcome {} - {}!", p.username, q.username))); - } - ); + app.resource("/{username}/index.html", |r| { + r.route().with2(|p: Path, q: Query| { + format!("Welcome {} - {}!", p.username, q.username) + }) + }); + }); // client request - let request = srv.get().uri(srv.url("/test1/index.html?username=test2")) - .finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test1/index.html?username=test2")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -83,8 +90,10 @@ fn test_path_and_query_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); // client request - let request = srv.get().uri(srv.url("/test1/index.html")) - .finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test1/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -92,16 +101,19 @@ fn test_path_and_query_extractor() { #[test] fn test_path_and_query_extractor2() { let mut srv = test::TestServer::new(|app| { - app.resource( - "/{username}/index.html", |r| r.route().with3( - |_: HttpRequest, p: Path, q: Query| - format!("Welcome {} - {}!", p.username, q.username))); - } - ); + app.resource("/{username}/index.html", |r| { + r.route() + .with3(|_: HttpRequest, p: Path, q: Query| { + format!("Welcome {} - {}!", p.username, q.username) + }) + }); + }); // client request - let request = srv.get().uri(srv.url("/test1/index.html?username=test2")) - .finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test1/index.html?username=test2")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -110,8 +122,10 @@ fn test_path_and_query_extractor2() { assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); // client request - let request = srv.get().uri(srv.url("/test1/index.html")) - .finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test1/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -123,8 +137,10 @@ fn test_non_ascii_route() { }); // client request - let request = srv.get().uri(srv.url("/中文/index.html")) - .finish().unwrap(); + let request = srv.get() + .uri(srv.url("/中文/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); diff --git a/tests/test_server.rs b/tests/test_server.rs index 6234a7ea..a2ff63cc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,60 +1,58 @@ extern crate actix; extern crate actix_web; -extern crate tokio_core; +extern crate bytes; +extern crate flate2; extern crate futures; extern crate h2; extern crate http as modhttp; -extern crate bytes; -extern crate flate2; extern crate rand; +extern crate tokio_core; -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] extern crate brotli2; -use std::{net, thread, time}; -use std::io::{Read, Write}; -use std::sync::{Arc, mpsc}; -use std::sync::atomic::{AtomicUsize, Ordering}; +#[cfg(feature = "brotli")] +use brotli2::write::{BrotliDecoder, BrotliEncoder}; +use bytes::{Bytes, BytesMut}; use flate2::Compression; use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, DeflateEncoder, DeflateDecoder}; -#[cfg(feature="brotli")] -use brotli2::write::{BrotliEncoder, BrotliDecoder}; -use futures::{Future, Stream}; +use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; use futures::stream::once; +use futures::{Future, Stream}; use h2::client as h2client; -use bytes::{Bytes, BytesMut}; use modhttp::Request; +use rand::Rng; +use std::io::{Read, Write}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{mpsc, Arc}; +use std::{net, thread, time}; use tokio_core::net::TcpStream; use tokio_core::reactor::Core; -use rand::Rng; use actix::System; use actix_web::*; - -const STR: &str = - "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; #[test] fn test_start() { @@ -63,11 +61,13 @@ fn test_start() { thread::spawn(move || { let sys = System::new("test"); - let srv = server::new( - || vec![App::new() - .resource( - "/", |r| r.method(http::Method::GET) - .f(|_|HttpResponse::Ok()))]); + let srv = server::new(|| { + vec![ + App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + }), + ] + }); let srv = srv.bind("127.0.0.1:0").unwrap(); let addr = srv.addrs()[0]; @@ -79,7 +79,9 @@ fn test_start() { let mut sys = System::new("test-server"); { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()).finish().unwrap(); + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); let response = sys.run_until_complete(req.send()).unwrap(); assert!(response.status().is_success()); } @@ -94,7 +96,9 @@ fn test_start() { thread::sleep(time::Duration::from_millis(200)); { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()).finish().unwrap(); + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); let response = sys.run_until_complete(req.send()).unwrap(); assert!(response.status().is_success()); } @@ -108,10 +112,13 @@ fn test_shutdown() { thread::spawn(move || { let sys = System::new("test"); - let srv = server::new( - || vec![App::new() - .resource( - "/", |r| r.method(http::Method::GET).f(|_| HttpResponse::Ok()))]); + let srv = server::new(|| { + vec![ + App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + }), + ] + }); let srv = srv.bind("127.0.0.1:0").unwrap(); let addr = srv.addrs()[0]; @@ -124,9 +131,11 @@ fn test_shutdown() { let mut sys = System::new("test-server"); { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()).finish().unwrap(); + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); let response = sys.run_until_complete(req.send()).unwrap(); - srv_addr.do_send(server::StopServer{graceful: true}); + srv_addr.do_send(server::StopServer { graceful: true }); assert!(response.status().is_success()); } @@ -146,30 +155,31 @@ fn test_simple() { fn test_headers() { let data = STR.repeat(10); let srv_data = Arc::new(data.clone()); - let mut srv = test::TestServer::new( - move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - let mut builder = HttpResponse::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST "); - } - builder.body(data.as_ref())}) - }); + let mut srv = test::TestServer::new(move |app| { + let data = srv_data.clone(); + app.handler(move |_| { + let mut builder = HttpResponse::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + builder.body(data.as_ref()) + }) + }); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -182,8 +192,8 @@ fn test_headers() { #[test] fn test_body() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| HttpResponse::Ok().body(STR))); + let mut srv = + test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -196,11 +206,13 @@ fn test_body() { #[test] fn test_body_gzip() { - let mut srv = test::TestServer::new( - |app| app.handler( - |_| HttpResponse::Ok() + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { + HttpResponse::Ok() .content_encoding(http::ContentEncoding::Gzip) - .body(STR))); + .body(STR) + }) + }); let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -221,13 +233,14 @@ fn test_body_gzip_large() { let data = STR.repeat(10); let srv_data = Arc::new(data.clone()); - let mut srv = test::TestServer::new( - move |app| { - let data = srv_data.clone(); - app.handler( - move |_| HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()))}); + let mut srv = test::TestServer::new(move |app| { + let data = srv_data.clone(); + app.handler(move |_| { + HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Gzip) + .body(data.as_ref()) + }) + }); let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -251,13 +264,14 @@ fn test_body_gzip_large_random() { .collect::(); let srv_data = Arc::new(data.clone()); - let mut srv = test::TestServer::new( - move |app| { - let data = srv_data.clone(); - app.handler( - move |_| HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()))}); + let mut srv = test::TestServer::new(move |app| { + let data = srv_data.clone(); + app.handler(move |_| { + HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Gzip) + .body(data.as_ref()) + }) + }); let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -276,12 +290,14 @@ fn test_body_gzip_large_random() { #[test] fn test_body_chunked_implicit() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| { + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); HttpResponse::Ok() .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body)))})); + .body(Body::Streaming(Box::new(body))) + }) + }); let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -297,15 +313,17 @@ fn test_body_chunked_implicit() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] #[test] fn test_body_br_streaming() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| { + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); HttpResponse::Ok() .content_encoding(http::ContentEncoding::Br) - .body(Body::Streaming(Box::new(body)))})); + .body(Body::Streaming(Box::new(body))) + }) + }); let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -323,17 +341,23 @@ fn test_body_br_streaming() { #[test] fn test_head_empty() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| { + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { HttpResponse::Ok() - .content_length(STR.len() as u64).finish()})); + .content_length(STR.len() as u64) + .finish() + }) + }); let request = srv.head().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); { - let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } @@ -344,18 +368,24 @@ fn test_head_empty() { #[test] fn test_head_binary() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| { + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) - .content_length(100).body(STR)})); + .content_length(100) + .body(STR) + }) + }); let request = srv.head().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); { - let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } @@ -366,32 +396,38 @@ fn test_head_binary() { #[test] fn test_head_binary2() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| { + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(STR) - })); + }) + }); let request = srv.head().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); { - let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } } #[test] fn test_body_length() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| { + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); HttpResponse::Ok() .content_length(STR.len() as u64) .content_encoding(http::ContentEncoding::Identity) - .body(Body::Streaming(Box::new(body)))})); + .body(Body::Streaming(Box::new(body))) + }) + }); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -404,13 +440,15 @@ fn test_body_length() { #[test] fn test_body_chunked_explicit() { - let mut srv = test::TestServer::new( - |app| app.handler(|_| { + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); HttpResponse::Ok() .chunked() .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body)))})); + .body(Body::Streaming(Box::new(body))) + }) + }); let request = srv.get().disable_decompress().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -428,11 +466,13 @@ fn test_body_chunked_explicit() { #[test] fn test_body_deflate() { - let mut srv = test::TestServer::new( - |app| app.handler( - |_| HttpResponse::Ok() + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { + HttpResponse::Ok() .content_encoding(http::ContentEncoding::Deflate) - .body(STR))); + .body(STR) + }) + }); // client request let request = srv.get().disable_decompress().finish().unwrap(); @@ -449,14 +489,16 @@ fn test_body_deflate() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] #[test] fn test_body_brotli() { - let mut srv = test::TestServer::new( - |app| app.handler( - |_| HttpResponse::Ok() + let mut srv = test::TestServer::new(|app| { + app.handler(|_| { + HttpResponse::Ok() .content_encoding(http::ContentEncoding::Br) - .body(STR))); + .body(STR) + }) + }); // client request let request = srv.get().disable_decompress().finish().unwrap(); @@ -475,14 +517,17 @@ fn test_body_brotli() { #[test] fn test_gzip_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }) + .responder() + }) + }); // client request let mut e = GzEncoder::new(Vec::new(), Compression::default()); @@ -491,7 +536,8 @@ fn test_gzip_encoding() { let request = srv.post() .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()).unwrap(); + .body(enc.clone()) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -503,14 +549,17 @@ fn test_gzip_encoding() { #[test] fn test_gzip_encoding_large() { let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }) + .responder() + }) + }); // client request let mut e = GzEncoder::new(Vec::new(), Compression::default()); @@ -519,7 +568,8 @@ fn test_gzip_encoding_large() { let request = srv.post() .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()).unwrap(); + .body(enc.clone()) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -535,14 +585,17 @@ fn test_reading_gzip_encoding_large_random() { .take(60_000) .collect::(); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }) + .responder() + }) + }); // client request let mut e = GzEncoder::new(Vec::new(), Compression::default()); @@ -551,7 +604,8 @@ fn test_reading_gzip_encoding_large_random() { let request = srv.post() .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()).unwrap(); + .body(enc.clone()) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -563,14 +617,17 @@ fn test_reading_gzip_encoding_large_random() { #[test] fn test_reading_deflate_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }) + .responder() + }) + }); let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); e.write_all(STR.as_ref()).unwrap(); @@ -579,7 +636,8 @@ fn test_reading_deflate_encoding() { // client request let request = srv.post() .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc).unwrap(); + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -591,14 +649,17 @@ fn test_reading_deflate_encoding() { #[test] fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }) + .responder() + }) + }); let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); e.write_all(data.as_ref()).unwrap(); @@ -607,7 +668,8 @@ fn test_reading_deflate_encoding_large() { // client request let request = srv.post() .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc).unwrap(); + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -623,14 +685,17 @@ fn test_reading_deflate_encoding_large_random() { .take(160_000) .collect::(); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }) + .responder() + }) + }); let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); e.write_all(data.as_ref()).unwrap(); @@ -639,7 +704,8 @@ fn test_reading_deflate_encoding_large_random() { // client request let request = srv.post() .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc).unwrap(); + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -649,17 +715,20 @@ fn test_reading_deflate_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] #[test] fn test_brotli_encoding() { - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }) + .responder() + }) + }); let mut e = BrotliEncoder::new(Vec::new(), 5); e.write_all(STR.as_ref()).unwrap(); @@ -668,7 +737,8 @@ fn test_brotli_encoding() { // client request let request = srv.post() .header(http::header::CONTENT_ENCODING, "br") - .body(enc).unwrap(); + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -677,18 +747,21 @@ fn test_brotli_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature="brotli")] +#[cfg(feature = "brotli")] #[test] fn test_brotli_encoding_large() { let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| app.handler(|req: HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder()} - )); + let mut srv = test::TestServer::new(|app| { + app.handler(|req: HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }) + .responder() + }) + }); let mut e = BrotliEncoder::new(Vec::new(), 5); e.write_all(data.as_ref()).unwrap(); @@ -697,7 +770,8 @@ fn test_brotli_encoding_large() { // client request let request = srv.post() .header(http::header::CONTENT_ENCODING, "br") - .body(enc).unwrap(); + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -708,48 +782,46 @@ fn test_brotli_encoding_large() { #[test] fn test_h2() { - let srv = test::TestServer::new(|app| app.handler(|_|{ - HttpResponse::Ok().body(STR) - })); + let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let addr = srv.addr(); let mut core = Core::new().unwrap(); let handle = core.handle(); let tcp = TcpStream::connect(&addr, &handle); - let tcp = tcp.then(|res| { - h2client::handshake(res.unwrap()) - }).then(move |res| { - let (mut client, h2) = res.unwrap(); + let tcp = tcp.then(|res| h2client::handshake(res.unwrap())) + .then(move |res| { + let (mut client, h2) = res.unwrap(); - let request = Request::builder() - .uri(format!("https://{}/", addr).as_str()) - .body(()) - .unwrap(); - let (response, _) = client.send_request(request, false).unwrap(); + let request = Request::builder() + .uri(format!("https://{}/", addr).as_str()) + .body(()) + .unwrap(); + let (response, _) = client.send_request(request, false).unwrap(); - // Spawn a task to run the conn... - handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); + // Spawn a task to run the conn... + handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); - response.and_then(|response| { - assert_eq!(response.status(), http::StatusCode::OK); + response.and_then(|response| { + assert_eq!(response.status(), http::StatusCode::OK); - let (_, body) = response.into_parts(); + let (_, body) = response.into_parts(); - body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { - b.extend(c); - Ok(b) + body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { + b.extend(c); + Ok(b) + }) }) - }) - }); + }); let _res = core.run(tcp); // assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref())); } #[test] fn test_application() { - let mut srv = test::TestServer::with_factory( - || App::new().resource("/", |r| r.f(|_| HttpResponse::Ok()))); + let mut srv = test::TestServer::with_factory(|| { + App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())) + }); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -764,17 +836,28 @@ struct MiddlewareTest { impl middleware::Middleware for MiddlewareTest { fn start(&self, _: &mut HttpRequest) -> Result { - self.start.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); + self.start.store( + self.start.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); Ok(middleware::Started::Done) } - fn response(&self, _: &mut HttpRequest, resp: HttpResponse) -> Result { - self.response.store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); + fn response( + &self, _: &mut HttpRequest, resp: HttpResponse + ) -> Result { + self.response.store( + self.response.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); Ok(middleware::Response::Done(resp)) } fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); + self.finish.store( + self.finish.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); middleware::Finished::Done } } @@ -789,12 +872,13 @@ fn test_middlewares() { let act_num2 = Arc::clone(&num2); let act_num3 = Arc::clone(&num3); - let mut 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(|_| HttpResponse::Ok()) - ); + let mut 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(|_| HttpResponse::Ok()) + }); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -805,7 +889,6 @@ fn test_middlewares() { assert_eq!(num3.load(Ordering::Relaxed), 1); } - #[test] fn test_resource_middlewares() { let num1 = Arc::new(AtomicUsize::new(0)); @@ -816,13 +899,13 @@ fn test_resource_middlewares() { let act_num2 = Arc::clone(&num2); let act_num3 = Arc::clone(&num3); - let mut 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(|_| HttpResponse::Ok()) - ); + let mut 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(|_| HttpResponse::Ok()) + }); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 6ebb69bd..2126543e 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -1,19 +1,19 @@ extern crate actix; extern crate actix_web; +extern crate bytes; extern crate futures; extern crate http; -extern crate bytes; extern crate rand; use bytes::Bytes; use futures::Stream; use rand::Rng; -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] extern crate openssl; -use actix_web::*; use actix::prelude::*; +use actix_web::*; struct Ws; @@ -22,7 +22,6 @@ impl Actor for Ws { } impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { ws::Message::Ping(msg) => ctx.pong(&msg), @@ -36,8 +35,7 @@ impl StreamHandler for Ws { #[test] fn test_simple() { - let mut srv = test::TestServer::new( - |app| app.handler(|req| ws::start(req, Ws))); + let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (reader, mut writer) = srv.ws().unwrap(); writer.text("text"); @@ -46,7 +44,12 @@ fn test_simple() { writer.binary(b"text".as_ref()); let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Binary(Bytes::from_static(b"text").into()))); + assert_eq!( + item, + Some(ws::Message::Binary( + Bytes::from_static(b"text").into() + )) + ); writer.ping("ping"); let (item, reader) = srv.execute(reader.into_future()).unwrap(); @@ -64,8 +67,7 @@ fn test_large_text() { .take(65_536) .collect::(); - let mut srv = test::TestServer::new( - |app| app.handler(|req| ws::start(req, Ws))); + let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (mut reader, mut writer) = srv.ws().unwrap(); for _ in 0..100 { @@ -83,15 +85,17 @@ fn test_large_bin() { .take(65_536) .collect::(); - let mut srv = test::TestServer::new( - |app| app.handler(|req| ws::start(req, Ws))); + let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (mut reader, mut writer) = srv.ws().unwrap(); for _ in 0..100 { writer.binary(data.clone()); let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; - assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone())))); + assert_eq!( + item, + Some(ws::Message::Binary(Binary::from(data.clone()))) + ); } } @@ -115,18 +119,19 @@ impl Ws2 { } else { ctx.text("0".repeat(65_536)); } - ctx.drain().and_then(|_, act, ctx| { - act.count += 1; - if act.count != 10_000 { - act.send(ctx); - } - actix::fut::ok(()) - }).wait(ctx); + ctx.drain() + .and_then(|_, act, ctx| { + act.count += 1; + if act.count != 10_000 { + act.send(ctx); + } + actix::fut::ok(()) + }) + .wait(ctx); } } impl StreamHandler for Ws2 { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { ws::Message::Ping(msg) => ctx.pong(&msg), @@ -142,8 +147,17 @@ impl StreamHandler for Ws2 { fn test_server_send_text() { let data = Some(ws::Message::Text("0".repeat(65_536))); - let mut srv = test::TestServer::new( - |app| app.handler(|req| ws::start(req, Ws2{count:0, bin: false}))); + let mut srv = test::TestServer::new(|app| { + app.handler(|req| { + ws::start( + req, + Ws2 { + count: 0, + bin: false, + }, + ) + }) + }); let (mut reader, _writer) = srv.ws().unwrap(); for _ in 0..10_000 { @@ -157,8 +171,17 @@ fn test_server_send_text() { fn test_server_send_bin() { let data = Some(ws::Message::Binary(Binary::from("0".repeat(65_536)))); - let mut srv = test::TestServer::new( - |app| app.handler(|req| ws::start(req, Ws2{count:0, bin: true}))); + let mut srv = test::TestServer::new(|app| { + app.handler(|req| { + ws::start( + req, + Ws2 { + count: 0, + bin: true, + }, + ) + }) + }); let (mut reader, _writer) = srv.ws().unwrap(); for _ in 0..10_000 { @@ -169,19 +192,33 @@ fn test_server_send_bin() { } #[test] -#[cfg(feature="alpn")] +#[cfg(feature = "alpn")] fn test_ws_server_ssl() { extern crate openssl; - use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype}; + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_private_key_file("tests/key.pem", SslFiletype::PEM).unwrap(); - builder.set_certificate_chain_file("tests/cert.pem").unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); let mut srv = test::TestServer::build() .ssl(builder.build()) - .start(|app| app.handler(|req| ws::start(req, Ws2{count:0, bin: false}))); + .start(|app| { + app.handler(|req| { + ws::start( + req, + Ws2 { + count: 0, + bin: false, + }, + ) + }) + }); let (mut reader, _writer) = srv.ws().unwrap(); let data = Some(ws::Message::Text("0".repeat(65_536))); diff --git a/tools/wsload/src/wsclient.rs b/tools/wsload/src/wsclient.rs index ab5cbe76..6c431f2d 100644 --- a/tools/wsload/src/wsclient.rs +++ b/tools/wsload/src/wsclient.rs @@ -3,25 +3,24 @@ #![allow(unused_variables)] extern crate actix; extern crate actix_web; +extern crate clap; extern crate env_logger; extern crate futures; -extern crate tokio_core; -extern crate url; -extern crate clap; +extern crate num_cpus; extern crate rand; extern crate time; -extern crate num_cpus; +extern crate tokio_core; +extern crate url; -use std::time::Duration; -use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; use futures::Future; use rand::{thread_rng, Rng}; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; use actix::prelude::*; use actix_web::ws; - fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); let _ = env_logger::init(); @@ -62,16 +61,20 @@ fn main() { let sample_rate = parse_u64_default(matches.value_of("sample-rate"), 1) as usize; let perf_counters = Arc::new(PerfCounters::new()); - let payload = Arc::new(thread_rng() - .gen_ascii_chars() - .take(payload_size) - .collect::()); + let payload = Arc::new( + thread_rng() + .gen_ascii_chars() + .take(payload_size) + .collect::(), + ); let sys = actix::System::new("ws-client"); - let _: () = Perf{counters: perf_counters.clone(), - payload: payload.len(), - sample_rate_secs: sample_rate}.start(); + let _: () = Perf { + counters: perf_counters.clone(), + payload: payload.len(), + sample_rate_secs: sample_rate, + }.start(); for t in 0..threads { let pl = payload.clone(); @@ -79,46 +82,54 @@ fn main() { let perf = perf_counters.clone(); let addr = Arbiter::new(format!("test {}", t)); - addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> { - for _ in 0..concurrency { - let pl2 = pl.clone(); - let perf2 = perf.clone(); - let ws2 = ws.clone(); + addr.do_send(actix::msgs::Execute::new( + move || -> Result<(), ()> { + for _ in 0..concurrency { + let pl2 = pl.clone(); + let perf2 = perf.clone(); + let ws2 = ws.clone(); - Arbiter::handle().spawn( - ws::Client::new(&ws) - .write_buffer_capacity(0) - .connect() - .map_err(|e| { - println!("Error: {}", e); - //Arbiter::system().do_send(actix::msgs::SystemExit(0)); - () - }) - .map(move |(reader, writer)| { - let addr: Addr = ChatClient::create(move |ctx| { - ChatClient::add_stream(reader, ctx); - ChatClient{url: ws2, - conn: writer, - payload: pl2, - bin: bin, - ts: time::precise_time_ns(), - perf_counters: perf2, - sent: 0, - max_payload_size: max_payload_size, - } - }); - }) - ); - } - Ok(()) - })); + Arbiter::handle().spawn( + ws::Client::new(&ws) + .write_buffer_capacity(0) + .connect() + .map_err(|e| { + println!("Error: {}", e); + //Arbiter::system().do_send(actix::msgs::SystemExit(0)); + () + }) + .map(move |(reader, writer)| { + let addr: Addr = + ChatClient::create(move |ctx| { + ChatClient::add_stream(reader, ctx); + ChatClient { + url: ws2, + conn: writer, + payload: pl2, + bin: bin, + ts: time::precise_time_ns(), + perf_counters: perf2, + sent: 0, + max_payload_size: max_payload_size, + } + }); + }), + ); + } + Ok(()) + }, + )); } let res = sys.run(); } fn parse_u64_default(input: Option<&str>, default: u64) -> u64 { - input.map(|v| v.parse().expect(&format!("not a valid number: {}", v))) + input + .map(|v| { + v.parse() + .expect(&format!("not a valid number: {}", v)) + }) .unwrap_or(default) } @@ -138,29 +149,32 @@ impl Actor for Perf { impl Perf { fn sample_rate(&self, ctx: &mut Context) { - ctx.run_later(Duration::new(self.sample_rate_secs as u64, 0), |act, ctx| { - let req_count = act.counters.pull_request_count(); - if req_count != 0 { - let conns = act.counters.pull_connections_count(); - let latency = act.counters.pull_latency_ns(); - let latency_max = act.counters.pull_latency_max_ns(); - println!( - "rate: {}, conns: {}, throughput: {:?} kb, latency: {}, latency max: {}", - req_count / act.sample_rate_secs, - conns / act.sample_rate_secs, - (((req_count * act.payload) as f64) / 1024.0) / - act.sample_rate_secs as f64, - time::Duration::nanoseconds((latency / req_count as u64) as i64), - time::Duration::nanoseconds(latency_max as i64) - ); - } + ctx.run_later( + Duration::new(self.sample_rate_secs as u64, 0), + |act, ctx| { + let req_count = act.counters.pull_request_count(); + if req_count != 0 { + let conns = act.counters.pull_connections_count(); + let latency = act.counters.pull_latency_ns(); + let latency_max = act.counters.pull_latency_max_ns(); + println!( + "rate: {}, conns: {}, throughput: {:?} kb, latency: {}, latency max: {}", + req_count / act.sample_rate_secs, + conns / act.sample_rate_secs, + (((req_count * act.payload) as f64) / 1024.0) + / act.sample_rate_secs as f64, + time::Duration::nanoseconds((latency / req_count as u64) as i64), + time::Duration::nanoseconds(latency_max as i64) + ); + } - act.sample_rate(ctx); - }); + act.sample_rate(ctx); + }, + ); } } -struct ChatClient{ +struct ChatClient { url: String, conn: ws::ClientWriter, payload: Arc, @@ -181,7 +195,6 @@ impl Actor for ChatClient { } impl ChatClient { - fn send_text(&mut self) -> bool { self.sent += self.payload.len(); @@ -193,7 +206,8 @@ impl ChatClient { let max_payload_size = self.max_payload_size; Arbiter::handle().spawn( - ws::Client::new(&self.url).connect() + ws::Client::new(&self.url) + .connect() .map_err(|e| { println!("Error: {}", e); Arbiter::system().do_send(actix::msgs::SystemExit(0)); @@ -202,17 +216,18 @@ impl ChatClient { .map(move |(reader, writer)| { let addr: Addr = ChatClient::create(move |ctx| { ChatClient::add_stream(reader, ctx); - ChatClient{url: ws, - conn: writer, - payload: pl, - bin: bin, - ts: time::precise_time_ns(), - perf_counters: perf_counters, - sent: 0, - max_payload_size: max_payload_size, + ChatClient { + url: ws, + conn: writer, + payload: pl, + bin: bin, + ts: time::precise_time_ns(), + perf_counters: perf_counters, + sent: 0, + max_payload_size: max_payload_size, } }); - }) + }), ); false } else { @@ -229,7 +244,6 @@ impl ChatClient { /// Handle server websocket messages impl StreamHandler for ChatClient { - fn finished(&mut self, ctx: &mut Context) { ctx.stop() } @@ -239,25 +253,25 @@ impl StreamHandler for ChatClient { ws::Message::Text(txt) => { if txt == self.payload.as_ref().as_str() { self.perf_counters.register_request(); - self.perf_counters.register_latency(time::precise_time_ns() - self.ts); + self.perf_counters + .register_latency(time::precise_time_ns() - self.ts); if !self.send_text() { ctx.stop(); } } else { println!("not eaqual"); } - }, - _ => () + } + _ => (), } } } - pub struct PerfCounters { req: AtomicUsize, conn: AtomicUsize, lat: AtomicUsize, - lat_max: AtomicUsize + lat_max: AtomicUsize, } impl PerfCounters { @@ -299,7 +313,11 @@ impl PerfCounters { self.lat.fetch_add(nanos, Ordering::SeqCst); loop { let current = self.lat_max.load(Ordering::SeqCst); - if current >= nanos || self.lat_max.compare_and_swap(current, nanos, Ordering::SeqCst) == current { + if current >= nanos + || self.lat_max + .compare_and_swap(current, nanos, Ordering::SeqCst) + == current + { break; } } From a8567da3e266e873f70b58466574ac8ddd283ac3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 16:20:23 -0700 Subject: [PATCH 0101/1635] move guide to separate repo; update links --- .travis.yml | 21 +- README.md | 8 +- guide/book.toml | 7 - guide/src/SUMMARY.md | 16 -- guide/src/qs_1.md | 32 --- guide/src/qs_10.md | 251 ----------------- guide/src/qs_12.md | 54 ---- guide/src/qs_13.md | 46 --- guide/src/qs_14.md | 128 --------- guide/src/qs_2.md | 100 ------- guide/src/qs_3.md | 110 -------- guide/src/qs_3_5.md | 210 -------------- guide/src/qs_4.md | 313 --------------------- guide/src/qs_4_5.md | 153 ---------- guide/src/qs_5.md | 654 ------------------------------------------- guide/src/qs_7.md | 357 ----------------------- guide/src/qs_8.md | 176 ------------ guide/src/qs_9.md | 48 ---- src/lib.rs | 2 +- 19 files changed, 7 insertions(+), 2679 deletions(-) delete mode 100644 guide/book.toml delete mode 100644 guide/src/SUMMARY.md delete mode 100644 guide/src/qs_1.md delete mode 100644 guide/src/qs_10.md delete mode 100644 guide/src/qs_12.md delete mode 100644 guide/src/qs_13.md delete mode 100644 guide/src/qs_14.md delete mode 100644 guide/src/qs_2.md delete mode 100644 guide/src/qs_3.md delete mode 100644 guide/src/qs_3_5.md delete mode 100644 guide/src/qs_4.md delete mode 100644 guide/src/qs_4_5.md delete mode 100644 guide/src/qs_5.md delete mode 100644 guide/src/qs_7.md delete mode 100644 guide/src/qs_8.md delete mode 100644 guide/src/qs_9.md diff --git a/.travis.yml b/.travis.yml index 90804412..a69ce188 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,27 +33,12 @@ before_install: # Add clippy before_script: - - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then - ( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false ); - fi - export PATH=$PATH:~/.cargo/bin script: - | - if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo clean - USE_SKEPTIC=1 cargo test --features=alpn - else - cargo clean - cargo test -- --nocapture - # --features=alpn - fi - - - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then - cargo clippy - fi + cargo clean + cargo test --features="alpn,tls" -- --nocapture # Upload docs after_success: @@ -61,8 +46,6 @@ after_success: if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then cargo doc --features "alpn, tls, session" --no-deps && echo "" > target/doc/index.html && - curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.2/mdbook-v0.1.2-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin && - cd guide && mdbook build -d ../target/doc/guide && cd .. && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && echo "Uploaded documentation" diff --git a/README.md b/README.md index 9eb11248..6e07e57c 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,17 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Multipart streams * Static assets * SSL support with OpenSSL or `native-tls` -* Middlewares ([Logger](https://actix.rs/actix-web/guide/qs_10.html#logging), - [Session](https://actix.rs/actix-web/guide/qs_10.html#user-sessions), +* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging), + [Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions), [Redis sessions](https://github.com/actix/actix-redis), - [DefaultHeaders](https://actix.rs/actix-web/guide/qs_10.html#default-headers), + [DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers), [CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html), [CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html)) * Built on top of [Actix actor framework](https://github.com/actix/actix) ## Documentation & community resources -* [User Guide](https://actix.rs/actix-web/guide/) +* [User Guide](https://actix.rs/book/actix-web/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) diff --git a/guide/book.toml b/guide/book.toml deleted file mode 100644 index 5549978d..00000000 --- a/guide/book.toml +++ /dev/null @@ -1,7 +0,0 @@ -[book] -title = "Actix web" -description = "Actix web framework guide" -author = "Actix Project and Contributors" - -[output.html] -google-analytics = "UA-110322332-1" diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md deleted file mode 100644 index d76840f9..00000000 --- a/guide/src/SUMMARY.md +++ /dev/null @@ -1,16 +0,0 @@ -# Summary - -[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) -- [URL Dispatch](./qs_5.md) -- [Request & Response](./qs_7.md) -- [Testing](./qs_8.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_1.md b/guide/src/qs_1.md deleted file mode 100644 index b5c3ca0f..00000000 --- a/guide/src/qs_1.md +++ /dev/null @@ -1,32 +0,0 @@ -# Quick start - -## Install Rust - -Before we begin, we need to install Rust using [rustup](https://www.rustup.rs/): - -```bash -curl https://sh.rustup.rs -sSf | sh -``` - -If you already have rustup installed, run this command to ensure you have the latest version of Rust: - -```bash -rustup update -``` - -Actix web framework requires rust version 1.21 and up. - -## Running Examples - -The fastest way to start experimenting with actix web is to clone the -[repository](https://github.com/actix/actix-web) and run the included examples. - -The following set of commands runs the `basics` example: - -```bash -git clone https://github.com/actix/example -cd examples/basics -cargo run -``` - -Check [examples/](https://github.com/actix/examples/tree/master/) directory for more examples. diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md deleted file mode 100644 index 3cbd0938..00000000 --- a/guide/src/qs_10.md +++ /dev/null @@ -1,251 +0,0 @@ -# Middleware - -Actix's middleware system allows us to add additional behavior to request/response processing. -Middleware can hook into an incoming request process, enabling us to modify requests -as well as halt request processing to return a response early. - -Middleware can also hook into response processing. - -Typically, middleware is involved in the following actions: - -* Pre-process the Request -* Post-process a Response -* Modify application state -* Access external services (redis, logging, sessions) - -Middleware is registered for each application and executed in same order as -registration. In general, a *middleware* is a type that implements the -[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method -in this trait has a default implementation. Each method can return a result immediately -or a *future* object. - -The following demonstrates using middleware to add request and response headers: - -```rust -# extern crate http; -# extern crate actix_web; -use http::{header, HttpTryFrom}; -use actix_web::{App, HttpRequest, HttpResponse, Result}; -use actix_web::middleware::{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) -> Result { - req.headers_mut().insert( - header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain")); - Ok(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) -> Result { - resp.headers_mut().insert( - header::HeaderName::try_from("X-VERSION").unwrap(), - header::HeaderValue::from_static("0.2")); - Ok(Response::Done(resp)) - } -} - -fn main() { - App::new() - .middleware(Headers) // <- Register middleware, this method can be called multiple times - .resource("/", |r| r.f(|_| HttpResponse::Ok())); -} -``` - -> Actix provides several useful middlewares, such as *logging*, *user sessions*, etc. - -## Logging - -Logging is implemented as a middleware. -It is common to register a logging middleware as the first middleware for the application. -Logging middleware must be registered for each application. - -The `Logger` middleware uses the standard log crate to log information. You should enable logger -for *actix_web* package to see access log ([env_logger](https://docs.rs/env_logger/*/env_logger/) -or similar). - -### Usage - -Create `Logger` middleware with the specified `format`. -Default `Logger` can be created with `default` method, it uses the default format: - -```ignore - %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T -``` - -```rust -# extern crate actix_web; -extern crate env_logger; -use actix_web::App; -use actix_web::middleware::Logger; - -fn main() { - std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - - App::new() - .middleware(Logger::default()) - .middleware(Logger::new("%a %{User-Agent}i")) - .finish(); -} -``` - -The following is an example of the default logging format: - -``` -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 - - `%%` 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 - -To set default response headers, the `DefaultHeaders` middleware can be used. The -*DefaultHeaders* middleware does not set the header if response headers already contain -a specified header. - -```rust -# extern crate actix_web; -use actix_web::{http, middleware, App, HttpResponse}; - -fn main() { - let app = App::new() - .middleware( - middleware::DefaultHeaders::new() - .header("X-Version", "0.2")) - .resource("/test", |r| { - r.method(http::Method::GET).f(|req| HttpResponse::Ok()); - r.method(http::Method::HEAD).f(|req| HttpResponse::MethodNotAllowed()); - }) - .finish(); -} -``` - -## User sessions - -Actix provides a general solution for session management. The -[**SessionStorage**](../actix_web/middleware/struct.SessionStorage.html) middleware can be -used with different backend types to store session data in different backends. - -> By default, only cookie session backend is implemented. Other backend implementations -> can be added. - -[**CookieSessionBackend**](../actix_web/middleware/struct.CookieSessionBackend.html) -uses cookies as session storage. `CookieSessionBackend` creates sessions which -are limited to storing fewer than 4000 bytes of data, as the payload must fit into a -single cookie. An internal server error is generated if a session contains more than 4000 bytes. - -A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor. - -A *signed* cookie may be viewed but not modified by the client. A *private* cookie may neither be viewed nor modified by the client. - -The constructors take a key as an argument. This is the private key for cookie session - when this value is changed, all session data is lost. - - - -In general, you create a -`SessionStorage` middleware and initialize it with specific backend implementation, -such as a `CookieSessionBackend`. To access session data, -[*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session) - must be used. This method returns a -[*Session*](../actix_web/middleware/struct.Session.html) object, which allows us to get or set -session data. - -```rust -# extern crate actix; -# extern crate actix_web; -use actix_web::{server, App, HttpRequest, Result}; -use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend}; - -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"); - server::new( - || App::new() - .middleware(SessionStorage::new( // <- create session middleware - CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend - .secure(false) - ))) - .bind("127.0.0.1:59880").unwrap() - .start(); -# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); -# let _ = sys.run(); -} -``` - -## Error handlers - -`ErrorHandlers` middleware allows us to provide custom handlers for responses. - -You can use the `ErrorHandlers::handler()` method to register a custom error handler -for a specific status code. You can modify an existing response or create a completly new -one. The error handler can return a response immediately or return a future that resolves -into a response. - -```rust -# extern crate actix_web; -use actix_web::{ - App, HttpRequest, HttpResponse, Result, - http, middleware::Response, middleware::ErrorHandlers}; - -fn render_500(_: &mut HttpRequest, resp: HttpResponse) -> Result { - let mut builder = resp.into_builder(); - builder.header(http::header::CONTENT_TYPE, "application/json"); - Ok(Response::Done(builder.into())) -} - -fn main() { - let app = App::new() - .middleware( - ErrorHandlers::new() - .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500)) - .resource("/test", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); - }) - .finish(); -} -``` diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md deleted file mode 100644 index 1b2a98c4..00000000 --- a/guide/src/qs_12.md +++ /dev/null @@ -1,54 +0,0 @@ -# Static file handling - -## Individual file - -It is possible to serve static files with a custom path pattern and `NamedFile`. To -match a path tail, we can use a `[.*]` regex. - -```rust -# extern crate actix_web; -use std::path::PathBuf; -use actix_web::{App, HttpRequest, Result, http::Method, fs::NamedFile}; - -fn index(req: HttpRequest) -> Result { - let path: PathBuf = req.match_info().query("tail")?; - Ok(NamedFile::open(path)?) -} - -fn main() { - App::new() - .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` - -## Directory - -To serve files from specific directories and sub-directories, `StaticFiles` can be used. -`StaticFiles` must be registered with an `App::handler()` method, otherwise -it will be unable to serve sub-paths. - -```rust -# extern crate actix_web; -use actix_web::*; - -fn main() { - App::new() - .handler( - "/static", - fs::StaticFiles::new(".") - .show_files_listing()) - .finish(); -} -``` - -The parameter is the base directory. By default files listing for sub-directories -is disabled. Attempt to load directory listing will return *404 Not Found* response. -To enable files listing, use -[*StaticFiles::show_files_listing()*](../actix_web/s/struct.StaticFiles.html#method.show_files_listing) -method. - -Instead of showing files listing for directory, it is possible to redirect -to a specific index file. Use the -[*StaticFiles::index_file()*](../actix_web/s/struct.StaticFiles.html#method.index_file) -method to configure this redirect. diff --git a/guide/src/qs_13.md b/guide/src/qs_13.md deleted file mode 100644 index 963d5598..00000000 --- a/guide/src/qs_13.md +++ /dev/null @@ -1,46 +0,0 @@ -# HTTP/2.0 - -Actix web automatically upgrades connections to *HTTP/2.0* if possible. - -## Negotiation - -*HTTP/2.0* protocol over tls without prior knowledge requires -[tls alpn](https://tools.ietf.org/html/rfc7301). - -> Currently, only `rust-openssl` has support. - -`alpn` negotiation requires enabling the feature. When enabled, `HttpServer` provides the -[serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method. - -```toml -[dependencies] -actix-web = { version = "0.3.3", features=["alpn"] } -openssl = { version="0.10", features = ["v110"] } -``` - -```rust,ignore -use std::fs::File; -use actix_web::*; -use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype}; - -fn main() { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); - builder.set_certificate_chain_file("cert.pem").unwrap(); - - HttpServer::new( - || App::new() - .resource("/index.html", |r| r.f(index))) - .bind("127.0.0.1:8080").unwrap(); - .serve_ssl(builder).unwrap(); -} -``` - -Upgrades 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) - -> Check out [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls) -> for a concrete example. diff --git a/guide/src/qs_14.md b/guide/src/qs_14.md deleted file mode 100644 index 0d1998e4..00000000 --- a/guide/src/qs_14.md +++ /dev/null @@ -1,128 +0,0 @@ -# Database integration - -## Diesel - -At the moment, Diesel 1.0 does not support asynchronous operations, -but it possible to use the `actix` synchronous actor system as a database interface api. - -Technically, sync actors are worker style actors. Multiple sync actors -can be run in parallel and process messages from same queue. Sync actors work in mpsc mode. - -Let's create a simple database api that can insert a new user row into a SQLite table. -We must define a sync actor and a connection that this actor will use. The same approach -can be used for other databases. - -```rust,ignore -use actix::prelude::*; - -struct DbExecutor(SqliteConnection); - -impl Actor for DbExecutor { - type Context = SyncContext; -} -``` - -This is the definition of our actor. Now, we must define the *create user* message and response. - -```rust,ignore -struct CreateUser { - name: String, -} - -impl Message for CreateUser { - type Result = Result; -} -``` - -We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we will receive a -`User` model instance. Next, we must define the handler implementation for this message. - -```rust,ignore -impl Handler for DbExecutor { - type Result = Result; - - fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result - { - 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 diesel 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"); - - Ok(items.pop().unwrap()) - } -} -``` - -That's it! Now, we can use the *DbExecutor* actor from any http handler or middleware. -All we need is to start *DbExecutor* actors and store the address in a state where http handler -can access it. - -```rust,ignore -/// This is state where we will store *DbExecutor* address. -struct State { - db: Addr, -} - -fn main() { - let sys = actix::System::new("diesel-example"); - - // Start 3 parallel db executors - let addr = SyncArbiter::start(3, || { - DbExecutor(SqliteConnection::establish("test.db").unwrap()) - }); - - // Start http server - HttpServer::new(move || { - App::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(); -} -``` - -We will use the address in a request handler. The handle returns a future object; -thus, we receive the message response asynchronously. -`Route::a()` must be used for async handler registration. - - -```rust,ignore -/// Async handler -fn index(req: HttpRequest) -> Box> { - let name = &req.match_info()["name"]; - - // Send message to `DbExecutor` actor - req.state().db.send(CreateUser{name: name.to_owned()}) - .from_err() - .and_then(|res| { - match res { - Ok(user) => Ok(HttpResponse::Ok().json(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()) - } - }) - .responder() -} -``` - -> A full example is available in the -> [examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/). - -> More information on sync actors can be found in the -> [actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html). diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md deleted file mode 100644 index a5f3d277..00000000 --- a/guide/src/qs_2.md +++ /dev/null @@ -1,100 +0,0 @@ -# Getting Started - -Let’s write our first actix web application! - -## Hello, world! - -Start by creating a new binary-based Cargo project and changing into the new directory: - -```bash -cargo new hello-world --bin -cd hello-world -``` - -Now, add actix and actix web as dependencies of your project by ensuring your Cargo.toml -contains the following: - -```toml -[dependencies] -actix = "0.5" -actix-web = "0.5" -``` - -In order to implement a web server, we first need to create a request handler. - -A request handler is a function that accepts an `HttpRequest` instance as its only parameter -and returns a type that can be converted into `HttpResponse`: - -Filename: src/main.rs -```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 -# extern crate actix_web; -# use actix_web::*; -# fn index(req: HttpRequest) -> &'static str { -# "Hello world!" -# } -# fn main() { - App::new() - .resource("/", |r| r.f(index)); -# } -``` - -After that, the application instance can be used with `HttpServer` to listen for incoming -connections. The server accepts a function that should return an `HttpHandler` instance. -For simplicity `server::new` could be used, this function is shortcut for `HttpServer::new`: - -```rust,ignore - server::new( - || App::new() - .resource("/", |r| r.f(index))) - .bind("127.0.0.1:8088")? - .run(); -``` - -That's it! Now, compile and run the program with `cargo run`. -Head over to ``http://localhost:8088/`` to see the results. - -The full source of src/main.rs is listed below: - -```rust -# use std::thread; -extern crate actix_web; -use actix_web::{server, App, HttpRequest, HttpResponse}; - -fn index(req: HttpRequest) -> &'static str { - "Hello world!" -} - -fn main() { -# // In the doctest suite we can't run blocking code - deliberately leak a thread -# // If copying this example in show-all mode, make sure you skip the thread spawn -# // call. -# thread::spawn(|| { - server::new( - || App::new() - .resource("/", |r| r.f(index))) - .bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088") - .run(); -# }); -} -``` - -> **Note**: actix web is built upon [actix](https://github.com/actix/actix), -> an [actor model](https://en.wikipedia.org/wiki/Actor_model) framework in Rust. - -`actix::System` initializes actor system, `HttpServer` is an actor and must run within a -properly configured actix system. - -> For more information, check out the [actix documentation](https://actix.github.io/actix/actix/) -> and [actix guide](https://actix.github.io/actix/guide/). diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md deleted file mode 100644 index d5c0b325..00000000 --- a/guide/src/qs_3.md +++ /dev/null @@ -1,110 +0,0 @@ -# Application - -Actix web provides various primitives to build web servers and applications with Rust. -It provides routing, middlewares, pre-processing of requests, post-processing of responses, -websocket protocol handling, multipart streams, etc. - -All actix web servers are built around the `App` instance. -It is used for registering routes for resources and middlewares. -It also stores application state shared across all handlers within same application. - -Applications act as a namespace for all routes, i.e all routes for a specific application -have the same url path prefix. The application prefix always contains a leading "/" slash. -If a supplied prefix does not contain leading slash, it is automatically inserted. -The prefix should consist of value path segments. - -> For an application with prefix `/app`, -> any request with the paths `/app`, `/app/`, or `/app/test` would match; -> however, the path `/application` would not match. - -```rust,ignore -# extern crate actix_web; -# extern crate tokio_core; -# use actix_web::{*, http::Method}; -# fn index(req: HttpRequest) -> &'static str { -# "Hello world!" -# } -# fn main() { - let app = App::new() - .prefix("/app") - .resource("/index.html", |r| r.method(Method::GET).f(index)) - .finish() -# } -``` - -In this example, an application with the `/app` prefix and a `index.html` resource -are created. This resource is available through the `/app/index.html` url. - -> For more information, check the -> [URL Dispatch](./qs_5.html#using-a-application-prefix-to-compose-applications) section. - -Multiple applications can be served with one server: - -```rust -# extern crate actix_web; -# extern crate tokio_core; -# use tokio_core::net::TcpStream; -# use std::net::SocketAddr; -use actix_web::{server, App, HttpResponse}; - -fn main() { - server::new(|| vec![ - App::new() - .prefix("/app1") - .resource("/", |r| r.f(|r| HttpResponse::Ok())), - App::new() - .prefix("/app2") - .resource("/", |r| r.f(|r| HttpResponse::Ok())), - App::new() - .resource("/", |r| r.f(|r| HttpResponse::Ok())), - ]); -} -``` - -All `/app1` requests route to the first application, `/app2` to the second, and all other to the third. -**Applications get matched based on registration order**. If an application with a more generic -prefix is registered before a less generic one, it would effectively block the less generic -application matching. For example, if an `App` with the prefix `"/"` was registered -as the first application, it would match all incoming requests. - -## State - -Application state is shared with all routes and resources within the same application. -When using an http actor,state can be accessed with the `HttpRequest::state()` as read-only, -but interior mutability with `RefCell` can be used to achieve state mutability. -State is also available for route matching predicates and middlewares. - -Let's write a simple application that uses shared state. We are going to store request count -in the state: - -```rust -# extern crate actix; -# extern crate actix_web; -# -use std::cell::Cell; -use actix_web::{App, HttpRequest, http}; - -// 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() { - App::with_state(AppState{counter: Cell::new(0)}) - .resource("/", |r| r.method(http::Method::GET).f(index)) - .finish(); -} -``` - -> **Note**: http server accepts an application factory rather than an application -> instance. Http server constructs an application instance for each thread, thus application state -> must be constructed multiple times. If you want to share state between different threads, a -> shared object should be used, e.g. `Arc`. Application state does not need to be `Send` and `Sync`, -> but the application factory must be `Send` + `Sync`. diff --git a/guide/src/qs_3_5.md b/guide/src/qs_3_5.md deleted file mode 100644 index 82e83ff1..00000000 --- a/guide/src/qs_3_5.md +++ /dev/null @@ -1,210 +0,0 @@ -# Server - -The [**HttpServer**](../actix_web/server/struct.HttpServer.html) type is responsible for -serving http requests. - -`HttpServer` accepts an application factory as a parameter, and the -application factory must have `Send` + `Sync` boundaries. More about that in the -*multi-threading* section. - -To bind to a specific socket address, `bind()` must be used, and it may be called multiple times. -To start the http server, one of the start methods. - -- use `start()` for a simple server -- use `start_tls()` or `start_ssl()` for a ssl server - -`HttpServer` is an actix actor. It must be initialized within a properly configured actix system: - -```rust -# extern crate actix; -# extern crate actix_web; -use actix_web::{server::HttpServer, App, HttpResponse}; - -fn main() { - let sys = actix::System::new("guide"); - - HttpServer::new( - || App::new() - .resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .bind("127.0.0.1:59080").unwrap() - .start(); - -# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); - let _ = sys.run(); -} -``` - -> It is possible to start a server in a separate thread with the `spawn()` method. In that -> case the server spawns a new thread and creates a new actix system in it. To stop -> this server, send a `StopServer` message. - -`HttpServer` is implemented as an actix actor. It is possible to communicate with the server -via a messaging system. All start methods, e.g. `start()` and `start_ssl()`, return the -address of the started http server. It accepts 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 std::thread; -use std::sync::mpsc; -use actix_web::{server, App, HttpResponse}; - -fn main() { - let (tx, rx) = mpsc::channel(); - - thread::spawn(move || { - let sys = actix::System::new("http-server"); - let addr = server::new( - || App::new() - .resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .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 = rx.recv().unwrap(); - let _ = addr.send( - server::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server. -} -``` - -## Multi-threading - -`HttpServer` automatically starts an number of http workers, by default -this number is equal to number of logical CPUs in the system. This number -can be overridden with the `HttpServer::threads()` method. - -```rust -# extern crate actix_web; -# extern crate tokio_core; -use actix_web::{App, HttpResponse, server::HttpServer}; - -fn main() { - HttpServer::new( - || App::new() - .resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .threads(4); // <- Start 4 workers -} -``` - -The server creates a 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 factories must be `Send` + `Sync`. - -## SSL - -There are two features for ssl server: `tls` and `alpn`. The `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() { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); - builder.set_certificate_chain_file("cert.pem").unwrap(); - - server::new( - || App::new() - .resource("/index.html", |r| r.f(index))) - .bind("127.0.0.1:8080").unwrap() - .serve_ssl(builder).unwrap(); -} -``` - -> **Note**: the *HTTP/2.0* protocol requires -> [tls alpn](https://tools.ietf.org/html/rfc7301). -> At the moment, only `openssl` has `alpn` support. -> For a full example, check out -> [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls). - -## Keep-Alive - -Actix can wait for requests on a keep-alive connection. - -> *keep alive* connection behavior is defined by server settings. - -- `75`, `Some(75)`, `KeepAlive::Timeout(75)` - enable 75 second *keep alive* timer. -- `None` or `KeepAlive::Disabled` - disable *keep alive*. -- `KeepAlive::Tcp(75)` - use `SO_KEEPALIVE` socket option. - -```rust -# extern crate actix_web; -# extern crate tokio_core; -use actix_web::{server, App, HttpResponse}; - -fn main() { - server::new(|| - App::new() - .resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .keep_alive(75); // <- Set keep-alive to 75 seconds - - server::new(|| - App::new() - .resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .keep_alive(server::KeepAlive::Tcp(75)); // <- Use `SO_KEEPALIVE` socket option. - - server::new(|| - App::new() - .resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .keep_alive(None); // <- Disable keep-alive -} -``` - -If the first option is selected, then *keep alive* state is -calculated based on the response's *connection-type*. By default -`HttpResponse::connection_type` is not defined. In that case *keep alive* is -defined by the 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* can be change with `HttpResponseBuilder::connection_type()` method. - -```rust -# extern crate actix_web; -use actix_web::{HttpRequest, HttpResponse, http}; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .connection_type(http::ConnectionType::Close) // <- Close connection - .force_close() // <- Alternative method - .finish() -} -# fn main() {} -``` - -## Graceful shutdown - -`HttpServer` supports graceful shutdown. After receiving a stop signal, workers -have a specific amount of time to finish serving requests. Any workers still alive after the -timeout are force-dropped. By default the shutdown timeout is set to 30 seconds. -You can change this parameter with the `HttpServer::shutdown_timeout()` method. - -You can send a stop message to the server with the server address and specify if you want -graceful shutdown or not. The `start()` methods returns address of the server. - -`HttpServer` 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 - -> It is possible to disable signal handling with `HttpServer::disable_signals()` method. diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md deleted file mode 100644 index 1a1ff617..00000000 --- a/guide/src/qs_4.md +++ /dev/null @@ -1,313 +0,0 @@ -# Handler - -A request handler can be any object that implements -[`Handler` trait](../actix_web/dev/trait.Handler.html). - -Request handling happens in two stages. First the handler object is called, -returning any object that implements the -[`Responder` trait](../actix_web/trait.Responder.html#foreign-impls). -Then, `respond_to()` is called on the returned object, converting itself to a `Reply` or `Error`. - -By default actix provides `Responder` implementations for some standard types, -such as `&'static str`, `String`, etc. - -> For a complete list of implementations, check -> [*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls). - -Examples of valid handlers: - -```rust,ignore -fn index(req: HttpRequest) -> &'static str { - "Hello world!" -} -``` - -```rust,ignore -fn index(req: HttpRequest) -> String { - "Hello world!".to_owned() -} -``` - -```rust,ignore -fn index(req: HttpRequest) -> Bytes { - Bytes::from_static("Hello world!") -} -``` - -```rust,ignore -fn index(req: HttpRequest) -> Box> { - ... -} -``` - -*Handler* trait is generic over *S*, which defines the application state's type. -Application state is accessible from the handler with the `HttpRequest::state()` method; -however, state is accessible as a read-only reference. If you need mutable access to state, -it must be implemented. - -> **Note**: Alternatively, the handler can mutably access its own state because the `handle` method takes -> mutable reference to *self*. **Beware**, actix creates multiple copies -> of the application state and the handlers, unique for each thread. If you run your -> application in several threads, actix will create the same amount as number of threads -> of application state objects and handler objects. - -Here is an example of a handler that stores the number of processed requests: - -```rust -# extern crate actix_web; -use actix_web::{App, HttpRequest, HttpResponse, 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; - HttpResponse::Ok().into() - } -} -# fn main() {} -``` - -Although this handler will work, `self.0` will be different depending on the number of threads and -number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`. - -```rust -# extern crate actix; -# extern crate actix_web; -use actix_web::{server, App, HttpRequest, HttpResponse, dev::Handler}; -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 { - self.0.fetch_add(1, Ordering::Relaxed); - HttpResponse::Ok().into() - } -} - -fn main() { - let sys = actix::System::new("example"); - - let inc = Arc::new(AtomicUsize::new(0)); - - server::new( - move || { - let cloned = inc.clone(); - App::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().do_send(actix::msgs::SystemExit(0)); - let _ = sys.run(); -} -``` - -> Be careful with synchronization primitives like `Mutex` or `RwLock`. Actix web framework -> handles requests 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 the [actix](https://actix.github.io/actix/actix/) actor system. - -## Response with custom type - -To return a custom type directly from a handler function, the type needs to implement the `Responder` trait. - -Let's create a response for a custom type that serializes to an `application/json` response: - -```rust -# extern crate actix; -# extern crate actix_web; -extern crate serde; -extern crate serde_json; -#[macro_use] extern crate serde_derive; -use actix_web::{server, App, HttpRequest, HttpResponse, Error, Responder, http}; - -#[derive(Serialize)] -struct MyObj { - name: &'static str, -} - -/// Responder -impl Responder for MyObj { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: HttpRequest) -> Result { - let body = serde_json::to_string(&self)?; - - // Create response and set content type - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) - } -} - -/// Because `MyObj` implements `Responder`, it is possible to return it directly -fn index(req: HttpRequest) -> MyObj { - MyObj{name: "user"} -} - -fn main() { - let sys = actix::System::new("example"); - - server::new( - || App::new() - .resource("/", |r| r.method(http::Method::GET).f(index))) - .bind("127.0.0.1:8088").unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8088"); -# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); - let _ = sys.run(); -} -``` - -## Async handlers - -There are two different types of async handlers. Response objects can be generated asynchronously -or more precisely, any type that implements the [*Responder*](../actix_web/trait.Responder.html) trait. - -In this case, the handler must return a `Future` object that resolves to the *Responder* type, i.e: - -```rust -# extern crate actix_web; -# extern crate futures; -# extern crate bytes; -# use actix_web::*; -# use bytes::Bytes; -# use futures::stream::once; -# use futures::future::{Future, result}; -fn index(req: HttpRequest) -> Box> { - - result(Ok(HttpResponse::Ok() - .content_type("text/html") - .body(format!("Hello!")))) - .responder() -} - -fn index2(req: HttpRequest) -> Box> { - result(Ok("Welcome!")) - .responder() -} - -fn main() { - App::new() - .resource("/async", |r| r.route().a(index)) - .resource("/", |r| r.route().a(index2)) - .finish(); -} -``` - -Or the response body can be generated asynchronously. In this case, body -must implement the 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))) -} - -fn main() { - App::new() - .resource("/async", |r| r.f(index)) - .finish(); -} -``` - -Both methods can be combined. (i.e Async response with streaming body) - -It is possible to return a `Result` where the `Result::Item` type can be `Future`. -In this example, the `index` handler can return an error immediately or return a -future that resolves to a `HttpResponse`. - -```rust -# extern crate actix_web; -# extern crate futures; -# extern crate bytes; -# use actix_web::*; -# use bytes::Bytes; -# use futures::stream::once; -# use futures::future::{Future, result}; -fn index(req: HttpRequest) -> Result>, Error> { - if is_error() { - Err(error::ErrorBadRequest("bad request")) - } else { - Ok(Box::new( - result(Ok(HttpResponse::Ok() - .content_type("text/html") - .body(format!("Hello!")))))) - } -} -# -# fn is_error() -> bool { true } -# fn main() { -# App::new() -# .resource("/async", |r| r.route().f(index)) -# .finish(); -# } -``` - -## Different return types (Either) - -Sometimes, you need to return different types of responses. For example, -you can error check and return errors, return async responses, or any result that requires two different types. - -For this case, the [`Either`](../actix_web/enum.Either.html) type can be used. -`Either` allows combining two different responder types into a single type. - -```rust -# extern crate actix_web; -# extern crate futures; -# use actix_web::*; -# use futures::future::Future; -use futures::future::result; -use actix_web::{Either, Error, HttpResponse}; - -type RegisterResult = Either>>; - -fn index(req: HttpRequest) -> RegisterResult { - if is_a_variant() { // <- choose variant A - Either::A( - HttpResponse::BadRequest().body("Bad data")) - } else { - Either::B( // <- variant B - result(Ok(HttpResponse::Ok() - .content_type("text/html") - .body(format!("Hello!")))).responder()) - } -} -# fn is_a_variant() -> bool { true } -# fn main() { -# App::new() -# .resource("/register", |r| r.f(index)) -# .finish(); -# } -``` - -## Tokio core handle - -Any actix web handler runs within a properly configured -[actix system](https://actix.github.io/actix/actix/struct.System.html) -and [arbiter](https://actix.github.io/actix/actix/struct.Arbiter.html). -You can always get access to the tokio handle via the -[Arbiter::handle()](https://actix.github.io/actix/actix/struct.Arbiter.html#method.handle) -method. diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md deleted file mode 100644 index 4bc82451..00000000 --- a/guide/src/qs_4_5.md +++ /dev/null @@ -1,153 +0,0 @@ -# Errors - -Actix uses the [`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 the `ResponseError` trait can be returned as an error value. -`Handler` can return an `Result` object. By default, actix provides a -`Responder` implementation for compatible result types. Here is the implementation -definition: - -```rust,ignore -impl> Responder for Result -``` - -Any error that implements `ResponseError` can be converted into an `Error` object. - -For example, if the *handler* function returns `io::Error`, it would be converted -into an `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() { -# App::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 is implement the `ResponseError` trait -for the custom error type. The `ResponseError` trait has a default implementation -for the `error_response()` method: it generates a *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 -} - -/// Use default implementation for `error_response()` method -impl error::ResponseError for MyError {} - -fn index(req: HttpRequest) -> Result<&'static str, MyError> { - Err(MyError{name: "test"}) -} -# -# fn main() { -# App::new() -# .resource(r"/a/index.html", |r| r.f(index)) -# .finish(); -# } -``` - -In this example the *index* handler always returns a *500* response. But it is easy -to return different responses for different types of errors. - -```rust -# extern crate actix_web; -#[macro_use] extern crate failure; -use actix_web::{App, HttpRequest, HttpResponse, http, error}; - -#[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( - http::StatusCode::INTERNAL_SERVER_ERROR), - MyError::BadClientData => HttpResponse::new( - http::StatusCode::BAD_REQUEST), - MyError::Timeout => HttpResponse::new( - http::StatusCode::GATEWAY_TIMEOUT), - } - } -} - -fn index(req: HttpRequest) -> Result<&'static str, MyError> { - Err(MyError::BadClientData) -} -# -# fn main() { -# App::new() -# .resource(r"/a/index.html", |r| r.f(index)) -# .finish(); -# } -``` - -## Error helpers - -Actix provides a set of error helper types. It is possible to use them for generating -specific error responses. We can use the helper types for the first example with a 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(|e| error::ErrorBadRequest(e))?) -} -# fn main() { -# App::new() -# .resource(r"/a/index.html", |r| r.f(index)) -# .finish(); -# } -``` - -In this example, a *BAD REQUEST* response is generated for the `MyError` error. - -## Error logging - -Actix logs all errors with the log level `WARN`. If log level set to `DEBUG` -and `RUST_BACKTRACE` is enabled, the backtrace gets logged. The Error type uses -the cause's error backtrace if available. If the underlying failure does not provide -a backtrace, a new backtrace is constructed pointing to that conversion point -(rather than the origin of the error). This construction only happens if there -is no underlying backtrace; if it does have a backtrace, no new backtrace is constructed. - -You can enable backtrace and debug logging with following command: - -``` ->> RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run -``` diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md deleted file mode 100644 index 96f8b39b..00000000 --- a/guide/src/qs_5.md +++ /dev/null @@ -1,654 +0,0 @@ -# URL Dispatch - -URL dispatch provides a simple way for mapping URLs to `Handler` code using a simple pattern -matching language. 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 the -> `Handler` trait, defined in your application, that receives the request and returns -> a response object. More information is available in the [handler section](../qs_4.html). - -## Resource configuration - -Resource configuration is the act of adding a new resources 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*. -It does not match against the *QUERY* portion (the portion following the scheme and -port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*). - -The [App::route](../actix_web/struct.App.html#method.route) method provides -simple way of registering routes. This method adds a single route to application -routing table. This method accepts a *path pattern*, -*http method* and a handler function. `route()` method could be called multiple times -for the same path, in that case, multiple routes register for the same resource path. - -```rust -# extern crate actix_web; -use actix_web::{App, HttpRequest, HttpResponse, http::Method}; - -fn index(req: HttpRequest) -> HttpResponse { - unimplemented!() -} - -fn main() { - App::new() - .route("/user/{name}", Method::GET, index) - .route("/user/{name}", Method::POST, index) - .finish(); -} -``` - -While *App::route()* provides simple way of registering routes, to access -complete resource configuration, different method has to be used. -The [App::resource](../actix_web/struct.App.html#method.resource) method -adds a single resource to application routing table. This method accepts a *path pattern* -and a resource configuration function. - -```rust -# extern crate actix_web; -use actix_web::{App, HttpRequest, HttpResponse, http::Method}; - -fn index(req: HttpRequest) -> HttpResponse { - unimplemented!() -} - -fn main() { - App::new() - .resource("/prefix", |r| r.f(index)) - .resource("/user/{name}", - |r| r.method(Method::GET).f(|req| HttpResponse::Ok())) - .finish(); -} -``` - -The *Configuration function* has the following type: - -```rust,ignore - FnOnce(&mut Resource<_>) -> () -``` - -The *Configuration function* can set a name and register specific routes. -If a resource does not contain any route or does not have any matching routes, it -returns *NOT FOUND* http response. - -## Configuring a Route - -Resource contains a set of routes. Each route in turn has a set of predicates and a handler. -New routes can be created with `Resource::route()` method which returns a reference -to new *Route* instance. By default the *route* does not contain any predicates, so matches -all requests and the default handler is `HttpNotFound`. - -The application routes incoming requests based on route criteria which are defined during -resource registration and route registration. Resource matches all routes it contains in -the order the routes were registered via `Resource::route()`. - -> A *Route* can contain any number of *predicates* but only one handler. - -```rust -# extern crate actix_web; -# use actix_web::*; - -fn main() { - App::new() - .resource("/path", |resource| - resource.route() - .filter(pred::Get()) - .filter(pred::Header("content-type", "text/plain")) - .f(|req| HttpResponse::Ok()) - ) - .finish(); -} -``` - -In this example, `HttpResponse::Ok()` is returned for *GET* requests. -If a request contains `Content-Type` header, the value of this header is *text/plain*, -and path equals to `/path`, Resource calls handle of the first matching route. - -If a resource can not match any route, a "NOT FOUND" response is returned. - -[*Resource::route()*](../actix_web/struct.Resource.html#method.route) returns a -[*Route*](../actix_web/struct.Route.html) object. Route can be configured with a -builder-like pattern. Following configuration methods are available: - -* [*Route::filter()*](../actix_web/struct.Route.html#method.filter) registers a new predicate. - Any number of predicates can be registered for each route. - -* [*Route::f()*](../actix_web/struct.Route.html#method.f) registers handler function - for this route. Only one handler can be registered. Usually handler registration - is the last config operation. Handler function can be a function or closure and has the type - `Fn(HttpRequest) -> R + 'static` - -* [*Route::h()*](../actix_web/struct.Route.html#method.h) registers a handler object - that implements the `Handler` trait. This is similar to `f()` method - only one handler can - be registered. Handler registration is the last config operation. - -* [*Route::a()*](../actix_web/struct.Route.html#method.a) registers an async handler - function for this route. Only one handler can be registered. Handler registration - is the last config operation. Handler function can be a function or closure and has the type - `Fn(HttpRequest) -> Future + 'static` - -## 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 declaration present in the system, actix checks -the request's path against the pattern declared. This checking happens in the order that -the routes were declared via `App::resource()` method. If resource can not be found, -the *default resource* is used as the 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 -the route is invoked. If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned. - -## Resource pattern syntax - -The syntax of the pattern matching language used by 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**: 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 values can be retrieved with -[`Params::get()`](../actix_web/dev/struct.Params.html#method.get). - -Any matched parameter can be deserialized into a specific type if the type -implements the `FromParam` trait. For example most standard integer types -the trait, i.e.: - -```rust -# extern crate actix_web; -use actix_web::*; - -fn index(req: HttpRequest) -> Result { - let v1: u8 = req.match_info().query("v1")?; - let v2: u8 = req.match_info().query("v2")?; - Ok(format!("Values {} {}", v1, v2)) -} - -fn main() { - App::new() - .resource(r"/a/{v1}/{v2}/", |r| r.f(index)) - .finish(); -} -``` - -For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2". - -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_web; -use std::path::PathBuf; -use actix_web::{App, HttpRequest, Result, http::Method}; - -fn index(req: HttpRequest) -> Result { - let path: PathBuf = req.match_info().query("tail")?; - Ok(format!("Path {:?}", path)) -} - -fn main() { - App::new() - .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` - -List of `FromParam` implementations can be found in -[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls) - -## Path information extractor - -Actix provides functionality for type safe path information extraction. -[Path](../actix_web/struct.Path.html) extracts information, destination type -could be defined in several different forms. Simplest approach is to use -`tuple` type. Each element in tuple must correpond to one element from -path pattern. i.e. you can match path pattern `/{id}/{username}/` against -`Pyth<(u32, String)>` type, but `Path<(String, String, String)>` type will -always fail. - -```rust -# extern crate actix_web; -use actix_web::{App, Path, Result, http::Method}; - -// extract path info using serde -fn index(info: Path<(String, u32)>) -> Result { - Ok(format!("Welcome {}! id: {}", info.0, info.1)) -} - -fn main() { - let app = App::new() - .resource("/{username}/{id}/index.html", // <- define path parameters - |r| r.method(Method::GET).with(index)); -} -``` - - -It also possible to extract path pattern information to a struct. In this case, -this struct must implement *serde's *`Deserialize` trait. - -```rust -# extern crate actix_web; -#[macro_use] extern crate serde_derive; -use actix_web::{App, Path, Result, http::Method}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -// extract path info using serde -fn index(info: Path) -> Result { - Ok(format!("Welcome {}!", info.username)) -} - -fn main() { - let app = App::new() - .resource("/{username}/index.html", // <- define path parameters - |r| r.method(Method::GET).with(index)); -} -``` - -[Query](../actix_web/struct.Query.html) provides similar functionality for -request query parameters. - - -## 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::{App, Result, HttpRequest, HttpResponse, http::Method, http::header}; -# -fn index(req: HttpRequest) -> Result { - let url = req.url_for("foo", &["1", "2", "3"])?; // <- generate url for "foo" resource - Ok(HttpResponse::Found() - .header(header::LOCATION, url.as_str()) - .finish()) -} - -fn main() { - let app = App::new() - .resource("/test/{a}/{b}/{c}", |r| { - r.name("foo"); // <- set resource name, then it could be used in `url_for` - r.method(Method::GET).f(|_| HttpResponse::Ok()); - }) - .route("/test/", Method::GET, index) - .finish(); -} -``` - -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 returns [*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, can 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::{App, HttpRequest, HttpResponse, Error}; - -fn index(mut req: HttpRequest) -> Result { - let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; - assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); - Ok(HttpResponse::Ok().into()) -} - -fn main() { - let app = App::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: - -* 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 doesn't have one, it will -be appended automatically. - -If *merge* is *true*, merge multiple consecutive slashes in the path into one. - -This handler designed to be used as a handler for application's *default resource*. - -```rust -# extern crate actix_web; -# #[macro_use] extern crate serde_derive; -# use actix_web::*; -use actix_web::http::NormalizePath; -# -# fn index(req: HttpRequest) -> HttpResponse { -# HttpResponse::Ok().into() -# } -fn main() { - let app = App::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/`. - -In this example, the path normalization handler is registered for all methods, -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::{App, HttpRequest, http::Method, http::NormalizePath}; -# -# fn index(req: HttpRequest) -> &'static str { -# "test" -# } -fn main() { - let app = App::new() - .resource("/resource/", |r| r.f(index)) - .default_resource(|r| r.method(Method::GET).h(NormalizePath::default())) - .finish(); -} -``` - -## Using an Application Prefix to Compose Applications - -The `App::prefix()` method allows to set a 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. - -For example: - -```rust -# extern crate actix_web; -# use actix_web::*; -# -fn show_users(req: HttpRequest) -> HttpResponse { - unimplemented!() -} - -fn main() { - App::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 a predicate as a simple function that accepts a *request* object reference -and returns *true* or *false*. Formally, a predicate is any object that implements the -[`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 a simple predicate that check that a request contains a specific *header*: - -```rust -# extern crate actix_web; -# use actix_web::*; -use actix_web::{http, pred::Predicate, App, HttpRequest}; - -struct ContentTypeHeader; - -impl Predicate for ContentTypeHeader { - - fn check(&self, req: &mut HttpRequest) -> bool { - req.headers().contains_key(http::header::CONTENT_TYPE) - } -} - -fn main() { - App::new() - .resource("/index.html", |r| - r.route() - .filter(ContentTypeHeader) - .f(|_| HttpResponse::Ok())); -} -``` - -In this example, *index* handler will be called only if request contains *CONTENT-TYPE* header. - -Predicates have access to the application's state via `HttpRequest::state()`. -Also predicates can store extra information in -[request extensions](../actix_web/struct.HttpRequest.html#method.extensions). - -### Modifying 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::{pred, App, HttpResponse}; - -fn main() { - App::new() - .resource("/index.html", |r| - r.route() - .filter(pred::Not(pred::Get())) - .f(|req| HttpResponse::MethodNotAllowed())) - .finish(); -} -``` - -The `Any` predicate accepts a list of predicates and matches if any of the supplied -predicates match. i.e: - -```rust,ignore - pred::Any(pred::Get()).or(pred::Post()) -``` - -The `All` predicate accepts a list of predicates and matches if all of the supplied -predicates match. i.e: - -```rust,ignore - pred::All(pred::Get()).and(pred::Header("content-type", "plain/text")) -``` - -## Changing the default Not Found response - -If the path pattern can not be found in the routing table or a resource can not find matching -route, the default resource is used. The default response is *NOT FOUND*. -It is possible to override the *NOT FOUND* response with `App::default_resource()`. -This method accepts a *configuration function* same as normal resource configuration -with `App::resource()` method. - -```rust -# extern crate actix_web; -use actix_web::{App, HttpResponse, http::Method, pred}; - -fn main() { - App::new() - .default_resource(|r| { - r.method(Method::GET).f(|req| HttpResponse::NotFound()); - r.route().filter(pred::Not(pred::Get())) - .f(|req| HttpResponse::MethodNotAllowed()); - }) -# .finish(); -} -``` diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md deleted file mode 100644 index b07a25d6..00000000 --- a/guide/src/qs_7.md +++ /dev/null @@ -1,357 +0,0 @@ -# Request & Response - -## Response - -A builder-like pattern is used to construct an instance of `HttpResponse`. -`HttpResponse` provides several methods that return a `HttpResponseBuilder` instance, -which implements various convenience methods for building responses. - -> Check the [documentation](../actix_web/dev/struct.HttpResponseBuilder.html) -> for type descriptions. - -The methods `.body`, `.finish`, and `.json` finalize response creation and -return a constructed *HttpResponse* instance. If this methods is called on the same -builder instance multiple times, the builder will panic. - -```rust -# extern crate actix_web; -use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding}; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_encoding(ContentEncoding::Br) - .content_type("plain/text") - .header("X-Hdr", "sample") - .body("data") -} -# fn main() {} -``` - -## Content encoding - -Actix automatically *compresses*/*decompresses* payloads. The following codecs are supported: - -* Brotli -* Gzip -* Deflate -* Identity - -If request headers contain a `Content-Encoding` header, the request payload is decompressed -according to the header value. Multiple codecs are not supported, -i.e: `Content-Encoding: br, gzip`. - -Response payload is compressed based on the *content_encoding* parameter. -By default, `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected, -then the compression depends on the request's `Accept-Encoding` header. - -> `ContentEncoding::Identity` can be used to disable compression. -> If another content encoding is selected, the compression is enforced for that codec. - -For example, to enable `brotli` use `ContentEncoding::Br`: - -```rust -# extern crate actix_web; -use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding}; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_encoding(ContentEncoding::Br) - .body("data") -} -# fn main() {} -``` - -In this case we explicitly disable content compression -by setting content encoding to a `Identity` value: - -```rust -# extern crate actix_web; -use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding}; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_encoding(ContentEncoding::Identity) // <- disable compression - .body("data") -} -# fn main() {} -``` - -Also it is possible to set default content encoding on application level, by -default `ContentEncoding::Auto` is used, which implies automatic content compression -negotiation. - -```rust -# extern crate actix_web; -use actix_web::{App, HttpRequest, HttpResponse, http::ContentEncoding}; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .body("data") -} -fn main() { - let app = App::new() - .default_encoding(ContentEncoding::Identity) // <- disable compression for all routes - .resource("/index.html", |r| r.with(index)); -} -``` - -## JSON Request - -There are several options for json body deserialization. - -The first option is to use *Json* extractor. First, you define a handler function -that accepts `Json` as a parameter, then, you use the `.with()` method for registering -this handler. It is also possible to accept arbitrary valid json object by -using `serde_json::Value` as a type `T`. - -```rust -# extern crate actix_web; -#[macro_use] extern crate serde_derive; -use actix_web::{App, Json, Result, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -/// extract `Info` using serde -fn index(info: Json) -> Result { - Ok(format!("Welcome {}!", info.username)) -} - -fn main() { - let app = App::new().resource( - "/index.html", - |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor -} -``` - -Another option is to use *HttpResponse::json()*. This method returns a -[*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into -the deserialized value. - -```rust -# extern crate actix; -# extern crate actix_web; -# extern crate futures; -# 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(HttpResponse::Ok().json(val)) // <- send response - }) - .responder() -} -# fn main() {} -``` - -You may also manually load the payload into memory and then deserialize it. - -In the following example, we will deserialize a *MyObj* struct. We need to load the request -body first and then deserialize the json into an object. - -```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(req: HttpRequest) -> Box> { - // `concat2` will asynchronously read each chunk of the request body and - // return a single, concatenated, chunk - req.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)?; - Ok(HttpResponse::Ok().json(obj)) // <- send response - }) - .responder() -} -# fn main() {} -``` - -> A complete example for both options is available in -> [examples directory](https://github.com/actix/actix-web/tree/master/examples/json/). - -## JSON Response - -The `Json` type allows 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::{App, HttpRequest, Json, Result, http::Method}; - -#[derive(Serialize)] -struct MyObj { - name: String, -} - -fn index(req: HttpRequest) -> Result> { - Ok(Json(MyObj{name: req.match_info().query("name")?})) -} - -fn main() { - App::new() - .resource(r"/a/{name}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` - -## Chunked transfer encoding - -Actix automatically decodes *chunked* encoding. `HttpRequest::payload()` already contains -the decoded byte stream. If the request payload is compressed with one of the supported -compression codecs (br, gzip, deflate), then the byte stream is decompressed. - -Chunked encoding on a response can be enabled with `HttpResponseBuilder::chunked()`. -This takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies. -If the response payload compression is enabled and a streaming body is used, chunked encoding -is enabled automatically. - -> Enabling chunked encoding for *HTTP/2.0* responses is forbidden. - -```rust -# extern crate bytes; -# extern crate actix_web; -# extern crate futures; -# use futures::Stream; -use actix_web::*; -use bytes::Bytes; -use futures::stream::once; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .chunked() - .body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data")))))) -} -# fn main() {} -``` - -## Multipart body - -Actix provides multipart stream support. -[*Multipart*](../actix_web/multipart/struct.Multipart.html) is implemented as -a stream of multipart items. Each item can be a -[*Field*](../actix_web/multipart/struct.Field.html) or a nested *Multipart* stream. -`HttpResponse::multipart()` returns the *Multipart* stream for the current request. - -The following demonstrates multipart stream handling for a simple form: - -```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.headers(), 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(()))) - } - } - }) -} -``` - -> A full example is available in the -> [examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/). - -## Urlencoded body - -Actix provides support for *application/x-www-form-urlencoded* encoded bodies. -`HttpResponse::urlencoded()` returns a -[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, which resolves -to the deserialized instance. The type of the instance must implement the -`Deserialize` trait from *serde*. - -The *UrlEncoded* future can resolve into an 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; -#[macro_use] extern crate serde_derive; -use actix_web::*; -use futures::future::{Future, ok}; - -#[derive(Deserialize)] -struct FormData { - username: String, -} - -fn index(mut req: HttpRequest) -> Box> { - req.urlencoded::() // <- get UrlEncoded future - .from_err() - .and_then(|data| { // <- deserialized instance - println!("USERNAME: {:?}", data.username); - ok(HttpResponse::Ok().into()) - }) - .responder() -} -# fn main() {} -``` - -## Streaming request - -*HttpRequest* is a stream of `Bytes` objects. It can be used to read the request -body payload. - -In the following example, we read and print the request payload chunk by chunk: - -```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> { - req.from_err() - .fold((), |_, chunk| { - println!("Chunk: {:?}", chunk); - result::<_, error::PayloadError>(Ok(())) - }) - .map(|_| HttpResponse::Ok().finish()) - .responder() -} -# fn main() {} -``` diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md deleted file mode 100644 index f80fb8eb..00000000 --- a/guide/src/qs_8.md +++ /dev/null @@ -1,176 +0,0 @@ -# Testing - -Every application should be well tested. Actix provides tools to perform unit and -integration tests. - -## Unit tests - -For unit testing, actix provides a request builder type and a simple handler runner. -[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements a builder-like pattern. -You can generate a `HttpRequest` instance with `finish()`, or you can -run your handler with `run()` or `run_async()`. - -```rust -# extern crate actix_web; -use actix_web::{http, test, HttpRequest, HttpResponse, HttpMessage}; - -fn index(req: HttpRequest) -> HttpResponse { - if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) { - if let Ok(s) = hdr.to_str() { - return HttpResponse::Ok().into() - } - } - HttpResponse::BadRequest().into() -} - -fn main() { - let resp = test::TestRequest::with_header("content-type", "text/plain") - .run(index) - .unwrap(); - assert_eq!(resp.status(), http::StatusCode::OK); - - let resp = test::TestRequest::default() - .run(index) - .unwrap(); - assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); -} -``` - -## Integration tests - -There are several methods for testing your application. Actix provides -[*TestServer*](../actix_web/test/struct.TestServer.html), which can be used -to run the application with specific handlers in a real http server. - -`TestServer::get()`, `TestServer::post()`, and `TestServer::client()` -methods can be used to send requests to the test server. - -A simple form `TestServer` can be configured to use a handler. -`TestServer::new` method accepts a configuration function, and the only argument -for this function is a *test application* instance. - -> Check the [api documentation](../actix_web/test/struct.TestApp.html) for more information. - -```rust -# extern crate actix_web; -use actix_web::{HttpRequest, HttpResponse, HttpMessage}; -use actix_web::test::TestServer; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok().into() -} - -fn main() { - let mut srv = TestServer::new(|app| app.handler(index)); // <- Start new test server - - let request = srv.get().finish().unwrap(); // <- create client request - let response = srv.execute(request.send()).unwrap(); // <- send request to the server - assert!(response.status().is_success()); // <- check response - - let bytes = srv.execute(response.body()).unwrap(); // <- read response body -} -``` - -The other option is to use an application factory. In this case, you need to pass the factory -function the same way as you would for real http server configuration. - -```rust -# extern crate actix_web; -use actix_web::{http, test, App, HttpRequest, HttpResponse}; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok().into() -} - -/// This function get called by http server. -fn create_app() -> App { - App::new() - .resource("/test", |r| r.h(index)) -} - -fn main() { - let mut srv = test::TestServer::with_factory(create_app); // <- Start new test server - - let request = srv.client( - http::Method::GET, "/test").finish().unwrap(); // <- create client request - let response = srv.execute(request.send()).unwrap(); // <- send request to the server - - assert!(response.status().is_success()); // <- check response -} -``` - -If you need more complex application configuration, use the `TestServer::build_with_state()` -method. For example, you may need to initialize application state or start `SyncActor`'s for diesel -interation. This method accepts a closure that constructs the application state, -and it runs when the actix system is configured. Thus, you can initialize any additional actors. - -```rust,ignore -#[test] -fn test() { - let srv = TestServer::build_with_state(|| { // <- construct builder with config closure - // we can start diesel actors - let addr = SyncArbiter::start(3, || { - DbExecutor(SqliteConnection::establish("test.db").unwrap()) - }); - // then we can construct custom state, or it could be `()` - MyState{addr: addr} - }) - .start(|app| { // <- register server handlers and start test server - app.resource( - "/{username}/index.html", |r| r.with( - |p: Path| format!("Welcome {}!", p.username))); - }); - - // now we can run our test code -); -``` - -## WebSocket server tests - -It is possible to register a *handler* with `TestApp::handler()`, which -initiates a web socket connection. *TestServer* provides the method `ws()`, which connects to -the websocket server and returns ws reader and writer objects. *TestServer* also -provides an `execute()` method, which runs future objects to completion and returns -result of the future computation. - -The following example demonstrates how to test a websocket handler: - -```rust -# extern crate actix; -# extern crate actix_web; -# extern crate futures; -# extern crate http; -# extern crate bytes; - -use actix_web::*; -use futures::Stream; -# use actix::prelude::*; - -struct Ws; // <- WebSocket actor - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -impl StreamHandler for Ws { - - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Text(text) => ctx.text(text), - _ => (), - } - } -} - -fn main() { - let mut srv = test::TestServer::new( // <- start our server with ws handler - |app| app.handler(|req| ws::start(req, Ws))); - - let (reader, mut writer) = srv.ws().unwrap(); // <- connect to ws server - - writer.text("text"); // <- send message to server - - let (item, reader) = srv.execute(reader.into_future()).unwrap(); // <- wait for one message - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); -} -``` diff --git a/guide/src/qs_9.md b/guide/src/qs_9.md deleted file mode 100644 index e0d71f12..00000000 --- a/guide/src/qs_9.md +++ /dev/null @@ -1,48 +0,0 @@ -# WebSockets - -Actix supports WebSockets out-of-the-box. It is possible to convert a 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 simpler to handle websocket communications -with an http actor. - -The following is an example of a simple websocket echo server: - -```rust -# extern crate actix; -# extern crate actix_web; -use actix::*; -use actix_web::*; - -/// Define http actor -struct Ws; - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -/// Handler for ws::Message message -impl StreamHandler for Ws { - - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - _ => (), - } - } -} - -fn main() { - App::new() - .resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) // <- register websocket route - .finish(); -} -``` - -> A simple websocket echo server example is available in the -> [examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket). - -> An example chat server with the ability to chat over a websocket or tcp connection -> is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/) diff --git a/src/lib.rs b/src/lib.rs index fff68afa..1e32dcc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ //! Besides the API documentation (which you are currently looking //! at!), several other resources are available: //! -//! * [User Guide](https://actix.rs/actix-web/guide/) +//! * [User Guide](https://actix.rs/book/actix-web/) //! * [Chat on gitter](https://gitter.im/actix/actix) //! * [GitHub repository](https://github.com/actix/actix-web) //! * [Cargo package](https://crates.io/crates/actix-web) From ebc1f6eff9359827436f153ab79e367e71f9b80a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 16:21:57 -0700 Subject: [PATCH 0102/1635] drop skeptic --- Cargo.toml | 2 -- build.rs | 39 --------------------------------------- 2 files changed, 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3544a67e..77b1b790 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,11 +93,9 @@ tokio-openssl = { version="0.2", optional = true } [dev-dependencies] env_logger = "0.5" -skeptic = "0.13" serde_derive = "1.0" [build-dependencies] -skeptic = "0.13" version_check = "0.1" [profile.release] diff --git a/build.rs b/build.rs index ee1fe22d..3b3001f9 100644 --- a/build.rs +++ b/build.rs @@ -1,44 +1,5 @@ -extern crate skeptic; extern crate version_check; -use std::{env, fs}; - -#[cfg(unix)] -fn main() { - println!("cargo:rerun-if-env-changed=USE_SKEPTIC"); - let f = env::var("OUT_DIR").unwrap() + "/skeptic-tests.rs"; - if env::var("USE_SKEPTIC").is_ok() { - let _ = fs::remove_file(f); - // 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_3_5.md", - "guide/src/qs_4.md", - "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", - "guide/src/qs_13.md", - "guide/src/qs_14.md", - ]); - } else { - let _ = fs::File::create(f); - } - - match version_check::is_nightly() { - Some(true) => println!("cargo:rustc-cfg=actix_nightly"), - Some(false) => (), - None => (), - }; -} - -#[cfg(not(unix))] fn main() { match version_check::is_nightly() { Some(true) => println!("cargo:rustc-cfg=actix_nightly"), From 827ca5eada6758cd968999c10cabb549b642b731 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 16:36:39 -0700 Subject: [PATCH 0103/1635] remove skeptic tests --- tests/skeptic.rs | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 tests/skeptic.rs diff --git a/tests/skeptic.rs b/tests/skeptic.rs deleted file mode 100644 index a0e0f9b3..00000000 --- a/tests/skeptic.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(unix)] -include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs")); From 333b4f57d3f9a6e32b3042ad03300098c538ad17 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 17:00:18 -0700 Subject: [PATCH 0104/1635] use different directory for tests --- src/fs.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 4e7305b0..5f734d00 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -668,7 +668,7 @@ mod tests { fn test_redirect_to_index() { let mut st = StaticFiles::new(".").index_file("index.html"); let mut req = HttpRequest::default(); - req.match_info_mut().add("tail", "guide"); + req.match_info_mut().add("tail", "tests"); let resp = st.handle(req) .respond_to(HttpRequest::default()) @@ -677,11 +677,11 @@ mod tests { assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers().get(header::LOCATION).unwrap(), - "/guide/index.html" + "/tests/index.html" ); let mut req = HttpRequest::default(); - req.match_info_mut().add("tail", "guide/"); + req.match_info_mut().add("tail", "tests/"); let resp = st.handle(req) .respond_to(HttpRequest::default()) @@ -690,7 +690,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers().get(header::LOCATION).unwrap(), - "/guide/index.html" + "/tests/index.html" ); } From 5140fea8d1424c9887cd85d945be1e9c884ffb9e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 19:10:42 -0700 Subject: [PATCH 0105/1635] allow to use castom error handler for json extractor --- src/error.rs | 37 ++++++++++++++++++++++++++++++++++--- src/httpmessage.rs | 5 +++-- src/json.rs | 36 +++++++++++++++++++++++++++--------- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/error.rs b/src/error.rs index 7435b504..796183fc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ //! Error and Result module +use std::cell::RefCell; use std::io::Error as IoError; use std::str::Utf8Error; use std::string::FromUtf8Error; @@ -545,18 +546,31 @@ impl From for UrlGenerationError { /// ``` pub struct InternalError { cause: T, - status: StatusCode, + status: InternalErrorType, backtrace: Backtrace, } unsafe impl Sync for InternalError {} unsafe impl Send for InternalError {} +enum InternalErrorType { + Status(StatusCode), + Response(RefCell>), +} + impl InternalError { pub fn new(cause: T, status: StatusCode) -> Self { InternalError { cause, - status, + status: InternalErrorType::Status(status), + backtrace: Backtrace::new(), + } + } + + pub fn from_response(cause: T, response: HttpResponse) -> Self { + InternalError { + cause, + status: InternalErrorType::Response(RefCell::new(Some(response))), backtrace: Backtrace::new(), } } @@ -594,7 +608,16 @@ where T: Send + Sync + fmt::Debug + 'static, { fn error_response(&self) -> HttpResponse { - HttpResponse::new(self.status) + match self.status { + InternalErrorType::Status(st) => HttpResponse::new(st), + InternalErrorType::Response(ref resp) => { + if let Some(resp) = resp.borrow_mut().take() { + resp + } else { + HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) + } + } + } } } @@ -859,4 +882,12 @@ mod tests { _ => env::remove_var(NAME), } } + + #[test] + fn test_internal_error() { + let err = InternalError::from_response( + ExpectError::Encoding, HttpResponse::Ok().into()); + let resp: HttpResponse = err.error_response(); + assert_eq!(resp.status(), StatusCode::OK); + } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 0b40a812..b590172b 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -118,11 +118,12 @@ pub trait HttpMessage { /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; - /// use actix_web::*; /// use bytes::Bytes; /// use futures::future::Future; + /// use actix_web::{HttpMessage, HttpRequest, HttpResponse, + /// FutureResponse, AsyncResponder}; /// - /// fn index(mut req: HttpRequest) -> Box> { + /// fn index(mut req: HttpRequest) -> FutureResponse { /// req.body() // <- get Body future /// .limit(1024) // <- change max size of the body to a 1kb /// .from_err() diff --git a/src/json.rs b/src/json.rs index 4eb51fb8..73128b97 100644 --- a/src/json.rs +++ b/src/json.rs @@ -2,6 +2,7 @@ use bytes::{Bytes, BytesMut}; use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; use std::fmt; +use std::rc::Rc; use std::ops::{Deref, DerefMut}; use mime; @@ -131,15 +132,17 @@ where T: DeserializeOwned + 'static, S: 'static, { - type Config = JsonConfig; + type Config = JsonConfig; type Result = Box>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + let req = req.clone(); + let err = Rc::clone(&cfg.ehandler); Box::new( JsonBody::new(req.clone()) .limit(cfg.limit) - .from_err() + .map_err(move |e| (*err)(e, req)) .map(Json), ) } @@ -150,7 +153,7 @@ where /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Json, Result, http}; +/// use actix_web::{App, Json, HttpResponse, Result, http, error}; /// /// #[derive(Deserialize)] /// struct Info { @@ -167,25 +170,40 @@ where /// "/index.html", |r| { /// r.method(http::Method::POST) /// .with(index) -/// .limit(4096);} // <- change json extractor configuration -/// ); +/// .limit(4096) // <- change json extractor configuration +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// }); +/// }); /// } /// ``` -pub struct JsonConfig { +pub struct JsonConfig { limit: usize, + ehandler: Rc) -> Error>, } -impl JsonConfig { +impl JsonConfig { /// Change max size of payload. By default max size is 256Kb pub fn limit(&mut self, limit: usize) -> &mut Self { self.limit = limit; self } + + /// Set custom error handler + pub fn error_handler(&mut self, f: F) -> &mut Self + where + F: Fn(JsonPayloadError, HttpRequest) -> Error + 'static + { + self.ehandler = Rc::new(f); + self + } } -impl Default for JsonConfig { +impl Default for JsonConfig { fn default() -> Self { - JsonConfig { limit: 262_144 } + JsonConfig { limit: 262_144, + ehandler: Rc::new(|e, _| e.into()) } } } From a5b5ff089426b50015b26614440fb4b440952bb8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 19:14:14 -0700 Subject: [PATCH 0106/1635] update doc strings --- src/error.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/error.rs b/src/error.rs index 796183fc..7158d7e7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -559,6 +559,7 @@ enum InternalErrorType { } impl InternalError { + /// Create `InternalError` instance pub fn new(cause: T, status: StatusCode) -> Self { InternalError { cause, @@ -567,6 +568,7 @@ impl InternalError { } } + /// Create `InternalError` with predefined `HttpResponse` pub fn from_response(cause: T, response: HttpResponse) -> Self { InternalError { cause, From 634c5723a0638e50fd8c09a8f2929b353a934779 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 19:19:30 -0700 Subject: [PATCH 0107/1635] update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index bc1b68d8..8c074110 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ ## 0.5.2 (2018-04-xx) +* Add support for custom handling of Json extractor errors #181 + * Fix StaticFiles does not support percent encoded paths #177 From a9ea649348d210922fa8d51d8cb7eef5c6dcb902 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Apr 2018 19:46:14 -0700 Subject: [PATCH 0108/1635] Allow to configure StaticFiles CpuPool, via static method or env variable --- CHANGES.md | 2 ++ src/error.rs | 2 +- src/fs.rs | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8c074110..453b7b2f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ ## 0.5.2 (2018-04-xx) +* Allow to configure StaticFiles's CpuPool, via static method or env variable + * Add support for custom handling of Json extractor errors #181 * Fix StaticFiles does not support percent encoded paths #177 diff --git a/src/error.rs b/src/error.rs index 7158d7e7..aafd9b4b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -568,7 +568,7 @@ impl InternalError { } } - /// Create `InternalError` with predefined `HttpResponse` + /// Create `InternalError` with predefined `HttpResponse`. pub fn from_response(cause: T, response: HttpResponse) -> Self { InternalError { cause, diff --git a/src/fs.rs b/src/fs.rs index 5f734d00..e865a6dd 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -6,7 +6,7 @@ use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::time::{SystemTime, UNIX_EPOCH}; -use std::{cmp, io}; +use std::{cmp, env, io}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; @@ -26,6 +26,9 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use param::FromParam; +/// Env variable for default cpu pool size for `StaticFiles` +const ENV_CPU_POOL_VAR: &str = "ACTIX_FS_POOL"; + /// A file with an associated name; responds with the Content-Type based on the /// file extension. #[derive(Debug)] @@ -445,12 +448,37 @@ pub struct StaticFiles { } lazy_static! { - static ref DEFAULT_CPUPOOL: Mutex = Mutex::new(CpuPool::new(20)); + static ref DEFAULT_CPUPOOL: Mutex = { + let default = match env::var(ENV_CPU_POOL_VAR) { + Ok(val) => { + if let Ok(val) = val.parse() { + val + } else { + error!("Can not parse ACTIX_FS_POOL value"); + 20 + } + }, + Err(_) => 20, + }; + Mutex::new(CpuPool::new(default)) + }; } impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory. + /// + /// `StaticFile` uses `CpuPool` for blocking filesystem operations. + /// By default pool with 20 threads is used. + /// Pool size can be changed by setting ACTIX_FS_POOL environment variable. pub fn new>(dir: T) -> StaticFiles { + // use default CpuPool + let pool = { DEFAULT_CPUPOOL.lock().unwrap().clone() }; + + StaticFiles::with_pool(dir, pool) + } + + /// Create new `StaticFiles` instance for specified base directory and `CpuPool`. + pub fn with_pool>(dir: T, pool: CpuPool) -> StaticFiles { let dir = dir.into(); let (dir, access) = match dir.canonicalize() { @@ -468,9 +496,6 @@ impl StaticFiles { } }; - // use default CpuPool - let pool = { DEFAULT_CPUPOOL.lock().unwrap().clone() }; - StaticFiles { directory: dir, accessible: access, From 58cc0dfbc5fad1cab85e61724c98dd4655bb43d8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Apr 2018 10:22:09 -0700 Subject: [PATCH 0109/1635] Fix Client Request with custom Body Stream halting on certain size requests #176 --- CHANGES.md | 2 ++ src/client/pipeline.rs | 13 ++++++++----- src/client/writer.rs | 4 ++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 453b7b2f..1fae933b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,8 @@ * Fix StaticFiles does not support percent encoded paths #177 +* Fix Client Request with custom Body Stream halting on certain size requests #176 + ## 0.5.1 (2018-04-12) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 05fcf812..1db68b43 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -184,6 +184,7 @@ impl Future for SendRequest { parser: Some(HttpResponseParser::default()), parser_buf: BytesMut::new(), disconnected: false, + body_completed: false, drain: None, decompress: None, should_decompress: self.req.response_decompress(), @@ -217,6 +218,7 @@ impl Future for SendRequest { pub(crate) struct Pipeline { body: IoBody, + body_completed: bool, conn: Option, writer: HttpClientWriter, parser: Option, @@ -394,7 +396,7 @@ impl Pipeline { IoBody::Payload(mut body) => match body.poll()? { Async::Ready(None) => { self.writer.write_eof()?; - self.disconnected = true; + self.body_completed = true; break; } Async::Ready(Some(chunk)) => { @@ -421,8 +423,7 @@ impl Pipeline { for frame in vec { match frame { Frame::Chunk(None) => { - // info.context = Some(ctx); - self.disconnected = true; + self.body_completed = true; self.writer.write_eof()?; break 'outter; } @@ -451,7 +452,7 @@ impl Pipeline { } } IoBody::Done => { - self.disconnected = true; + self.body_completed = true; done = true; break; } @@ -472,7 +473,9 @@ impl Pipeline { .poll_completed(self.conn.as_mut().unwrap(), false) { Ok(Async::Ready(_)) => { - if self.disconnected { + if self.disconnected + || (self.body_completed && self.writer.is_completed()) + { self.write_state = RunningState::Done; } else { self.write_state.resume(); diff --git a/src/client/writer.rs b/src/client/writer.rs index 8d554b9b..d0300d59 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -61,6 +61,10 @@ impl HttpClientWriter { self.buffer.take(); } + pub fn is_completed(&mut self) -> bool { + self.buffer.is_empty() + } + // pub fn keepalive(&self) -> bool { // self.flags.contains(Flags::KEEPALIVE) && // !self.flags.contains(Flags::UPGRADE) } From 79818560b2dfe7b1578e730c10d49c32904b35ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Apr 2018 09:30:59 -0700 Subject: [PATCH 0110/1635] cleanup doc strings; prepare release --- CHANGES.md | 2 +- Cargo.toml | 2 +- Makefile | 12 ------------ src/client/writer.rs | 2 +- src/extractor.rs | 21 +++++++++++++++++---- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1fae933b..d8174476 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ # Changes -## 0.5.2 (2018-04-xx) +## 0.5.2 (2018-04-16) * Allow to configure StaticFiles's CpuPool, via static method or env variable diff --git a/Cargo.toml b/Cargo.toml index 77b1b790..f49cbf1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.5.1" +version = "0.5.2" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/Makefile b/Makefile index fdc3cbbc..47886bbe 100644 --- a/Makefile +++ b/Makefile @@ -10,17 +10,5 @@ build: test: build clippy cargo test $(CARGO_FLAGS) -skeptic: - USE_SKEPTIC=1 cargo test $(CARGO_FLAGS) - -# cd examples/word-count && python setup.py install && pytest -v tests - -clippy: - if $$CLIPPY; then cargo clippy $(CARGO_FLAGS); fi - doc: build cargo doc --no-deps $(CARGO_FLAGS) - cd guide; mdbook build -d ../target/doc/guide/; cd .. - -book: - cd guide; mdbook build -d ../target/doc/guide/; cd .. diff --git a/src/client/writer.rs b/src/client/writer.rs index d0300d59..48e4cc71 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -61,7 +61,7 @@ impl HttpClientWriter { self.buffer.take(); } - pub fn is_completed(&mut self) -> bool { + pub fn is_completed(&self) -> bool { self.buffer.is_empty() } diff --git a/src/extractor.rs b/src/extractor.rs index 9415299b..098b4d8f 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -315,13 +315,18 @@ impl Default for FormConfig { /// ```rust /// extern crate bytes; /// # extern crate actix_web; -/// use actix_web::{App, Result}; +/// use actix_web::{http, App, Result}; /// /// /// extract text data from request /// fn index(body: bytes::Bytes) -> Result { /// Ok(format!("Body {:?}!", body)) /// } -/// # fn main() {} +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", |r| +/// r.method(http::Method::GET).with(index)) +/// } /// ``` impl FromRequest for Bytes { type Config = PayloadConfig; @@ -354,13 +359,21 @@ impl FromRequest for Bytes { /// /// ```rust /// # extern crate actix_web; -/// use actix_web::{App, Result}; +/// use actix_web::{http, App, Result}; /// /// /// extract text data from request /// fn index(body: String) -> Result { /// Ok(format!("Body {}!", body)) /// } -/// # fn main() {} +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", |r| { +/// r.method(http::Method::GET) +/// .with(index) // <- register handler with extractor params +/// .limit(4096); // <- limit size of the payload +/// }) +/// } /// ``` impl FromRequest for String { type Config = PayloadConfig; From 30a36bed9d58e41bc7fc24d9ee0107ebb9bdab96 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Apr 2018 09:50:37 -0700 Subject: [PATCH 0111/1635] fix doc example --- src/extractor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 098b4d8f..659aa01c 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -325,7 +325,7 @@ impl Default for FormConfig { /// fn main() { /// let app = App::new().resource( /// "/index.html", |r| -/// r.method(http::Method::GET).with(index)) +/// r.method(http::Method::GET).with(index)); /// } /// ``` impl FromRequest for Bytes { @@ -372,7 +372,7 @@ impl FromRequest for Bytes { /// r.method(http::Method::GET) /// .with(index) // <- register handler with extractor params /// .limit(4096); // <- limit size of the payload -/// }) +/// }); /// } /// ``` impl FromRequest for String { From 6a7b097bcf70f08100b4f4818c153c2cc6b85eef Mon Sep 17 00:00:00 2001 From: Aleksey Ivanov Date: Tue, 17 Apr 2018 16:01:34 +0300 Subject: [PATCH 0112/1635] Fix route in App::resource example --- src/application.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/application.rs b/src/application.rs index ff37b78f..f78335f1 100644 --- a/src/application.rs +++ b/src/application.rs @@ -314,7 +314,7 @@ where /// /// fn main() { /// let app = App::new() - /// .resource("/test", |r| { + /// .resource("/users/{userid}/{friend}", |r| { /// r.get().f(|_| HttpResponse::Ok()); /// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// }); From 3a79505a448ccf2e795ffc49d80a5fd27501081b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Apr 2018 07:51:06 -0700 Subject: [PATCH 0113/1635] update doc string --- src/httprequest.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 88ff2549..d3334602 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -438,9 +438,9 @@ impl HttpRequest { /// Get a reference to the Params object. /// /// Params is a container for url parameters. - /// Route supports glob patterns: * for a single wildcard segment and :param - /// for matching storing that segment of the request url in the Params - /// object. + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Params { unsafe { mem::transmute(&self.as_ref().params) } From a826d113eed19c3b4b3b25dd275bf1ea77ebc4b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Apr 2018 12:55:13 -0700 Subject: [PATCH 0114/1635] add custom request path quoter #182 --- CHANGES.md | 4 + src/application.rs | 2 + src/error.rs | 4 +- src/fs.rs | 9 +-- src/httprequest.rs | 28 +++---- src/json.rs | 10 ++- src/lib.rs | 1 + src/router.rs | 6 +- src/server/h1.rs | 5 +- src/server/h2.rs | 3 +- src/uri.rs | 175 +++++++++++++++++++++++++++++++++++++++++ tests/test_handlers.rs | 24 ++++++ 12 files changed, 236 insertions(+), 35 deletions(-) create mode 100644 src/uri.rs diff --git a/CHANGES.md b/CHANGES.md index d8174476..67a8694c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 0.5.3 (2018-04-xx) + +* Impossible to quote slashes in path parameters #182 + ## 0.5.2 (2018-04-16) diff --git a/src/application.rs b/src/application.rs index f78335f1..411b738e 100644 --- a/src/application.rs +++ b/src/application.rs @@ -418,6 +418,8 @@ where /// `/app/test` would match, but the path `/application` would /// not. /// + /// Path tail is available as `tail` parameter in request's match_dict. + /// /// ```rust /// # extern crate actix_web; /// use actix_web::{http, App, HttpRequest, HttpResponse}; diff --git a/src/error.rs b/src/error.rs index aafd9b4b..da56c35c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -888,7 +888,9 @@ mod tests { #[test] fn test_internal_error() { let err = InternalError::from_response( - ExpectError::Encoding, HttpResponse::Ok().into()); + ExpectError::Encoding, + HttpResponse::Ok().into(), + ); let resp: HttpResponse = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } diff --git a/src/fs.rs b/src/fs.rs index e865a6dd..ce0e42d5 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -15,7 +15,6 @@ use bytes::{BufMut, Bytes, BytesMut}; use futures::{Async, Future, Poll, Stream}; use futures_cpupool::{CpuFuture, CpuPool}; use mime_guess::get_mime_type; -use percent_encoding::percent_decode; use error::Error; use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler}; @@ -457,7 +456,7 @@ lazy_static! { error!("Can not parse ACTIX_FS_POOL value"); 20 } - }, + } Err(_) => 20, }; Mutex::new(CpuPool::new(default)) @@ -477,7 +476,8 @@ impl StaticFiles { StaticFiles::with_pool(dir, pool) } - /// Create new `StaticFiles` instance for specified base directory and `CpuPool`. + /// Create new `StaticFiles` instance for specified base directory and + /// `CpuPool`. pub fn with_pool>(dir: T, pool: CpuPool) -> StaticFiles { let dir = dir.into(); @@ -543,8 +543,7 @@ impl Handler for StaticFiles { } else { let relpath = match req.match_info() .get("tail") - .map(|tail| percent_decode(tail.as_bytes()).decode_utf8().unwrap()) - .map(|tail| PathBuf::from_param(tail.as_ref())) + .map(|tail| PathBuf::from_param(tail)) { Some(Ok(path)) => path, _ => return Ok(self.default.handle(req)), diff --git a/src/httprequest.rs b/src/httprequest.rs index d3334602..62efa483 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -6,8 +6,6 @@ use futures::future::{result, FutureResult}; use futures::{Async, Poll, Stream}; use futures_cpupool::CpuPool; use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version}; -use percent_encoding::percent_decode; -use std::borrow::Cow; use std::net::SocketAddr; use std::rc::Rc; use std::{cmp, fmt, io, mem, str}; @@ -24,11 +22,12 @@ use param::Params; use payload::Payload; use router::{Resource, Router}; use server::helpers::SharedHttpInnerMessage; +use uri::Url as InnerUrl; pub struct HttpInnerMessage { pub version: Version, pub method: Method, - pub uri: Uri, + pub(crate) url: InnerUrl, pub headers: HeaderMap, pub extensions: Extensions, pub params: Params<'static>, @@ -51,7 +50,7 @@ impl Default for HttpInnerMessage { fn default() -> HttpInnerMessage { HttpInnerMessage { method: Method::GET, - uri: Uri::default(), + url: InnerUrl::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), params: Params::new(), @@ -116,10 +115,11 @@ impl HttpRequest<()> { method: Method, uri: Uri, version: Version, headers: HeaderMap, payload: Option, ) -> HttpRequest { + let url = InnerUrl::new(uri); HttpRequest( SharedHttpInnerMessage::from_message(HttpInnerMessage { method, - uri, + url, version, headers, payload, @@ -241,15 +241,17 @@ impl HttpRequest { /// Read the Request Uri. #[inline] pub fn uri(&self) -> &Uri { - &self.as_ref().uri + self.as_ref().url.uri() } + #[doc(hidden)] + #[deprecated(since = "0.5.3")] /// Returns mutable the Request Uri. /// /// This might be useful for middlewares, e.g. path normalization. #[inline] pub fn uri_mut(&mut self) -> &mut Uri { - &mut self.as_mut().uri + self.as_mut().url.uri_mut() } /// Read the Request method. @@ -275,15 +277,7 @@ impl HttpRequest { /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.uri().path() - } - - /// Percent decoded path of this Request. - #[inline] - pub fn path_decoded(&self) -> Cow { - percent_decode(self.uri().path().as_bytes()) - .decode_utf8() - .unwrap() + self.as_ref().url.path() } /// Get *ConnectionInfo* for correct request. @@ -578,7 +572,7 @@ impl fmt::Debug for HttpRequest { "\nHttpRequest {:?} {}:{}", self.as_ref().version, self.as_ref().method, - self.path_decoded() + self.path() ); if !self.query_string().is_empty() { let _ = writeln!(f, " query: ?{:?}", self.query_string()); diff --git a/src/json.rs b/src/json.rs index 73128b97..96ac415f 100644 --- a/src/json.rs +++ b/src/json.rs @@ -2,8 +2,8 @@ use bytes::{Bytes, BytesMut}; use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; use std::fmt; -use std::rc::Rc; use std::ops::{Deref, DerefMut}; +use std::rc::Rc; use mime; use serde::Serialize; @@ -193,7 +193,7 @@ impl JsonConfig { /// Set custom error handler pub fn error_handler(&mut self, f: F) -> &mut Self where - F: Fn(JsonPayloadError, HttpRequest) -> Error + 'static + F: Fn(JsonPayloadError, HttpRequest) -> Error + 'static, { self.ehandler = Rc::new(f); self @@ -202,8 +202,10 @@ impl JsonConfig { impl Default for JsonConfig { fn default() -> Self { - JsonConfig { limit: 262_144, - ehandler: Rc::new(|e, _| e.into()) } + JsonConfig { + limit: 262_144, + ehandler: Rc::new(|e, _| e.into()), + } } } diff --git a/src/lib.rs b/src/lib.rs index 1e32dcc7..13be3ef4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,6 +147,7 @@ mod pipeline; mod resource; mod route; mod router; +mod uri; mod with; pub mod client; diff --git a/src/router.rs b/src/router.rs index 74225a1a..4257d739 100644 --- a/src/router.rs +++ b/src/router.rs @@ -3,7 +3,6 @@ use std::hash::{Hash, Hasher}; use std::mem; use std::rc::Rc; -use percent_encoding::percent_decode; use regex::{escape, Regex}; use error::UrlGenerationError; @@ -82,12 +81,9 @@ impl Router { } let path: &str = unsafe { mem::transmute(&req.path()[self.0.prefix_len..]) }; let route_path = if path.is_empty() { "/" } else { path }; - let p = percent_decode(route_path.as_bytes()) - .decode_utf8() - .unwrap(); for (idx, pattern) in self.0.patterns.iter().enumerate() { - if pattern.match_with_params(p.as_ref(), req.match_info_mut()) { + if pattern.match_with_params(route_path, req.match_info_mut()) { req.set_resource(idx); return Some(idx); } diff --git a/src/server/h1.rs b/src/server/h1.rs index aa27d029..c60762b6 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -19,6 +19,7 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use payload::{Payload, PayloadStatus, PayloadWriter}; use pipeline::Pipeline; +use uri::Url; use super::encoding::PayloadType; use super::h1writer::H1Writer; @@ -527,7 +528,7 @@ impl Reader { httparse::Status::Complete(len) => { let method = Method::from_bytes(req.method.unwrap().as_bytes()) .map_err(|_| ParseError::Method)?; - let path = Uri::try_from(req.path.unwrap())?; + let path = Url::new(Uri::try_from(req.path.unwrap())?); let version = if req.version.unwrap() == 1 { Version::HTTP_11 } else { @@ -563,7 +564,7 @@ impl Reader { } } - msg_mut.uri = path; + msg_mut.url = path; msg_mut.method = method; msg_mut.version = version; } diff --git a/src/server/h2.rs b/src/server/h2.rs index 08a97626..a5ac2cfc 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -22,6 +22,7 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use payload::{Payload, PayloadStatus, PayloadWriter}; use pipeline::Pipeline; +use uri::Url; use super::encoding::PayloadType; use super::h2writer::H2Writer; @@ -304,7 +305,7 @@ impl Entry { let (psender, payload) = Payload::new(false); let msg = settings.get_http_message(); - msg.get_mut().uri = parts.uri; + msg.get_mut().url = Url::new(parts.uri); msg.get_mut().method = parts.method; msg.get_mut().version = parts.version; msg.get_mut().headers = parts.headers; diff --git a/src/uri.rs b/src/uri.rs new file mode 100644 index 00000000..d30fe5cb --- /dev/null +++ b/src/uri.rs @@ -0,0 +1,175 @@ +use http::Uri; + +#[allow(dead_code)] +const GEN_DELIMS: &[u8] = b":/?#[]@"; +#[allow(dead_code)] +const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; +#[allow(dead_code)] +const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; +#[allow(dead_code)] +const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; +#[allow(dead_code)] +const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz + ABCDEFGHIJKLMNOPQRSTUVWXYZ + 1234567890 + -._~"; +const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz + ABCDEFGHIJKLMNOPQRSTUVWXYZ + 1234567890 + -._~ + !$'()*,"; +const QS: &[u8] = b"+&=;b"; + +#[inline] +fn bit_at(array: &[u8], ch: u8) -> bool { + array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0 +} + +#[inline] +fn set_bit(array: &mut [u8], ch: u8) { + array[(ch >> 3) as usize] |= 1 << (ch & 7) +} + +lazy_static! { + static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; +} + +#[derive(Default)] +pub(crate) struct Url { + uri: Uri, + path: Option, +} + +impl Url { + pub fn new(uri: Uri) -> Url { + let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); + + Url { uri, path } + } + + pub fn uri(&self) -> &Uri { + &self.uri + } + + pub fn uri_mut(&mut self) -> &mut Uri { + &mut self.uri + } + + pub fn path(&self) -> &str { + if let Some(ref s) = self.path { + s + } else { + self.uri.path() + } + } +} + +pub(crate) struct Quoter { + safe_table: [u8; 16], + protected_table: [u8; 16], +} + +impl Quoter { + pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { + let mut q = Quoter { + safe_table: [0; 16], + protected_table: [0; 16], + }; + + // prepare safe table + for i in 0..128 { + if ALLOWED.contains(&i) { + set_bit(&mut q.safe_table, i); + } + if QS.contains(&i) { + set_bit(&mut q.safe_table, i); + } + } + + for ch in safe { + set_bit(&mut q.safe_table, *ch) + } + + // prepare protected table + for ch in protected { + set_bit(&mut q.safe_table, *ch); + set_bit(&mut q.protected_table, *ch); + } + + q + } + + pub fn requote(&self, val: &[u8]) -> Option { + let mut has_pct = 0; + let mut pct = [b'%', 0, 0]; + let mut idx = 0; + let mut cloned: Option> = None; + + let len = val.len(); + while idx < len { + let ch = val[idx]; + + if has_pct != 0 { + pct[has_pct] = val[idx]; + has_pct += 1; + if has_pct == 3 { + has_pct = 0; + let buf = cloned.as_mut().unwrap(); + + if let Some(ch) = restore_ch(pct[1], pct[2]) { + if ch < 128 { + if bit_at(&self.protected_table, ch) { + buf.extend_from_slice(&pct); + idx += 1; + continue; + } + + if bit_at(&self.safe_table, ch) { + buf.push(ch); + idx += 1; + continue; + } + } + buf.push(ch); + } else { + buf.extend_from_slice(&pct[..]); + } + } + } else if ch == b'%' { + has_pct = 1; + if cloned.is_none() { + let mut c = Vec::with_capacity(len); + c.extend_from_slice(&val[..idx]); + cloned = Some(c); + } + } else if let Some(ref mut cloned) = cloned { + cloned.push(ch) + } + idx += 1; + } + + if let Some(data) = cloned { + Some(unsafe { String::from_utf8_unchecked(data) }) + } else { + None + } + } +} + +#[inline] +fn from_hex(v: u8) -> Option { + if v >= b'0' && v <= b'9' { + Some(v - 0x30) // ord('0') == 0x30 + } else if v >= b'A' && v <= b'F' { + Some(v - 0x41 + 10) // ord('A') == 0x41 + } else if v > b'a' && v <= b'f' { + Some(v - 0x61 + 10) // ord('a') == 0x61 + } else { + None + } +} + +#[inline] +fn restore_ch(d1: u8, d2: u8) -> Option { + from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2))) +} diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 12cf9709..7a9abe97 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -148,3 +148,27 @@ fn test_non_ascii_route() { let bytes = srv.execute(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(b"success")); } + +#[test] +fn test_unsafe_path_route() { + let mut srv = test::TestServer::new(|app| { + app.resource("/test/{url}", |r| { + r.f(|r| format!("success: {}", &r.match_info()["url"])) + }); + }); + + // client request + let request = srv.get() + .uri(srv.url("/test/http%3A%2F%2Fexample.com")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!( + bytes, + Bytes::from_static(b"success: http:%2F%2Fexample.com") + ); +} From 65b819787668046866ffdfb181ad52ea7f516ec1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Apr 2018 13:59:55 -0700 Subject: [PATCH 0115/1635] better doc string for Application::with_state() --- Cargo.toml | 2 +- src/application.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f49cbf1d..adb1060b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.5.2" +version = "0.5.3" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/application.rs b/src/application.rs index 411b738e..5c7b3c93 100644 --- a/src/application.rs +++ b/src/application.rs @@ -177,6 +177,13 @@ where /// /// State is shared with all resources within same application and /// could be accessed with `HttpRequest::state()` method. + /// + /// **Note**: http server accepts an application factory rather than + /// an application instance. Http server constructs an application + /// instance for each thread, thus application state must be constructed multiple + /// times. If you want to share state between different threads, a + /// shared object should be used, e.g. `Arc`. Application state does not + /// need to be `Send` and `Sync`. pub fn with_state(state: S) -> App { App { parts: Some(ApplicationParts { From 5b4b885fd6079b6e909212217d319515bc445cc1 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 17 Apr 2018 23:20:47 +0100 Subject: [PATCH 0116/1635] Replace use of try!() with ? --- src/header/common/content_range.rs | 12 ++++++------ src/header/shared/quality_item.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs index ea0e3274..999307e2 100644 --- a/src/header/common/content_range.rs +++ b/src/header/common/content_range.rs @@ -171,16 +171,16 @@ impl Display for ContentRangeSpec { range, instance_length, } => { - try!(f.write_str("bytes ")); + f.write_str("bytes ")?; match range { Some((first_byte, last_byte)) => { - try!(write!(f, "{}-{}", first_byte, last_byte)); + write!(f, "{}-{}", first_byte, last_byte)?; } None => { - try!(f.write_str("*")); + f.write_str("*")?; } }; - try!(f.write_str("/")); + f.write_str("/")?; if let Some(v) = instance_length { write!(f, "{}", v) } else { @@ -191,8 +191,8 @@ impl Display for ContentRangeSpec { ref unit, ref resp, } => { - try!(f.write_str(unit)); - try!(f.write_str(" ")); + f.write_str(unit)?; + f.write_str(" ")?; f.write_str(resp) } } diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs index 6dca77fe..5f1e5977 100644 --- a/src/header/shared/quality_item.rs +++ b/src/header/shared/quality_item.rs @@ -59,7 +59,7 @@ impl cmp::PartialOrd for QualityItem { impl fmt::Display for QualityItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(fmt::Display::fmt(&self.item, f)); + fmt::Display::fmt(&self.item, f)?; match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), From bf9a90293fe4c725a532f6ad461bf6775932270a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Apr 2018 16:22:25 -0700 Subject: [PATCH 0117/1635] fix doc strings --- src/extractor.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 659aa01c..1aef7ac5 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -25,7 +25,7 @@ use httprequest::HttpRequest; /// # extern crate futures; /// use actix_web::{App, Path, Result, http}; /// -/// /// extract path info from "/{username}/{count}/?index.html" url +/// /// extract path info from "/{username}/{count}/index.html" url /// /// {username} - deserializes to a String /// /// {count} - - deserializes to a u32 /// fn index(info: Path<(String, u32)>) -> Result { @@ -34,7 +34,7 @@ use httprequest::HttpRequest; /// /// fn main() { /// let app = App::new().resource( -/// "/{username}/{count}/?index.html", // <- define path parameters +/// "/{username}/{count}/index.html", // <- define path parameters /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// } /// ``` @@ -195,9 +195,6 @@ where /// /// ## Example /// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; From a9a54ac4c66e31894bff1e1bedf9bde50b57aed3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Apr 2018 10:45:59 -0700 Subject: [PATCH 0118/1635] prep release --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 67a8694c..7a8e3b44 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## 0.5.3 (2018-04-xx) +## 0.5.3 (2018-04-18) * Impossible to quote slashes in path parameters #182 From 022f9800edc1797bea746a16319392cc00620ff7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Apr 2018 10:49:03 -0700 Subject: [PATCH 0119/1635] formatting --- src/application.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/application.rs b/src/application.rs index 5c7b3c93..268b10af 100644 --- a/src/application.rs +++ b/src/application.rs @@ -180,10 +180,10 @@ where /// /// **Note**: http server accepts an application factory rather than /// an application instance. Http server constructs an application - /// instance for each thread, thus application state must be constructed multiple - /// times. If you want to share state between different threads, a - /// shared object should be used, e.g. `Arc`. Application state does not - /// need to be `Send` and `Sync`. + /// instance for each thread, thus application state must be constructed + /// multiple times. If you want to share state between different + /// threads, a shared object should be used, e.g. `Arc`. Application + /// state does not need to be `Send` and `Sync`. pub fn with_state(state: S) -> App { App { parts: Some(ApplicationParts { From f907be585e0aaad21e1289bdb5c84c27b1a85550 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Apr 2018 14:15:53 -0700 Subject: [PATCH 0120/1635] Middleware response() is not invoked if there was an error in async handler #187 --- CHANGES.md | 5 +++++ src/httprequest.rs | 2 ++ src/pipeline.rs | 2 +- src/route.rs | 2 +- tests/test_server.rs | 33 ++++++++++++++++++++++++++++++++- 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7a8e3b44..c2f73fe2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.5.4 (2018-04-xx) + +* Middleware response() is not invoked if there was an error in async handler #187 + + ## 0.5.3 (2018-04-18) * Impossible to quote slashes in path parameters #182 diff --git a/src/httprequest.rs b/src/httprequest.rs index 62efa483..08e8f2bc 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -590,6 +590,8 @@ impl fmt::Debug for HttpRequest { #[cfg(test)] mod tests { + #![allow(deprecated)] + use super::*; use http::{HttpTryFrom, Uri}; use resource::ResourceHandler; diff --git a/src/pipeline.rs b/src/pipeline.rs index 1e5685ba..3e90a6dc 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -328,7 +328,7 @@ impl WaitingResponse { match self.fut.poll() { Ok(Async::NotReady) => None, Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), - Err(err) => Some(ProcessResponse::init(err.into())), + Err(err) => Some(RunMiddlewares::init(info, err.into())), } } } diff --git a/src/route.rs b/src/route.rs index b7b84ad0..526eb137 100644 --- a/src/route.rs +++ b/src/route.rs @@ -420,7 +420,7 @@ impl WaitingResponse { match self.fut.poll() { Ok(Async::NotReady) => None, Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), - Err(err) => Some(Response::init(err.into())), + Err(err) => Some(RunMiddlewares::init(info, err.into())), } } } diff --git a/tests/test_server.rs b/tests/test_server.rs index a2ff63cc..162f193b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -18,7 +18,7 @@ use flate2::Compression; use flate2::read::GzDecoder; use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; use futures::stream::once; -use futures::{Future, Stream}; +use futures::{future, Future, Stream}; use h2::client as h2client; use modhttp::Request; use rand::Rng; @@ -915,3 +915,34 @@ fn test_resource_middlewares() { assert_eq!(num2.load(Ordering::Relaxed), 1); // assert_eq!(num3.load(Ordering::Relaxed), 1); } + + +fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse { + future::result(Err(error::ErrorBadRequest("TEST"))).responder() +} + +#[test] +fn test_middleware_async_error() { + let req = Arc::new(AtomicUsize::new(0)); + let resp = Arc::new(AtomicUsize::new(0)); + let fin = Arc::new(AtomicUsize::new(0)); + + let act_req = Arc::clone(&req); + let act_resp = Arc::clone(&resp); + let act_fin = Arc::clone(&fin); + + let mut srv = test::TestServer::new(move |app| { + app.middleware(MiddlewareTest { + start: Arc::clone(&act_req), + response: Arc::clone(&act_resp), + finish: Arc::clone(&act_fin), + }).handler(index_test_middleware_async_error)}); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + + assert_eq!(req.load(Ordering::Relaxed), 1); + assert_eq!(resp.load(Ordering::Relaxed), 1); + assert_eq!(fin.load(Ordering::Relaxed), 1); +} From e9bdba57a0fff218e16668314a55f4ea18e433dc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Apr 2018 19:05:24 -0700 Subject: [PATCH 0121/1635] Add identity service middleware --- CHANGES.md | 2 + Cargo.toml | 2 +- src/httprequest.rs | 13 ++ src/middleware/cors.rs | 4 +- src/middleware/identity.rs | 391 +++++++++++++++++++++++++++++++++++++ src/middleware/mod.rs | 1 + 6 files changed, 410 insertions(+), 3 deletions(-) create mode 100644 src/middleware/identity.rs diff --git a/CHANGES.md b/CHANGES.md index c2f73fe2..1bc29ae1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ## 0.5.4 (2018-04-xx) +* Add identity service middleware + * Middleware response() is not invoked if there was an error in async handler #187 diff --git a/Cargo.toml b/Cargo.toml index adb1060b..73a9c453 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.5.3" +version = "0.5.4" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/httprequest.rs b/src/httprequest.rs index 08e8f2bc..e917b5c8 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -201,6 +201,19 @@ impl HttpRequest { &mut self.as_mut().extensions } + /// Request extensions + #[inline] + #[doc(hidden)] + pub fn extensions_ro(&self) -> &Extensions { + &self.as_ref().extensions + } + + /// Mutable refernece to a the request's extensions + #[inline] + pub fn extensions_mut(&mut self) -> &mut Extensions { + &mut self.as_mut().extensions + } + /// Default `CpuPool` #[inline] #[doc(hidden)] diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index b99e1a8b..243ea1e8 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -7,8 +7,8 @@ //! //! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. //! 2. Use any of the builder methods to set fields in the backend. -//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -//! constructed backend. +//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the +//! constructed backend. //! //! Cors middleware could be used as parameter for `App::middleware()` or //! `ResourceHandler::middleware()` methods. But you have to use diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs new file mode 100644 index 00000000..f88b3f97 --- /dev/null +++ b/src/middleware/identity.rs @@ -0,0 +1,391 @@ +//! Request identity service for Actix applications. +//! +//! [**IdentityService**](struct.IdentityService.html) middleware can be +//! used with different policies types to store identity information. +//! +//! Bu default, only cookie identity policy is implemented. Other backend implementations +//! can be added separately. +//! +//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) +//! uses cookies as identity storage. +//! +//! To access current request identity +//! [**RequestIdentity**](trait.RequestIdentity.html) should be used. +//! *HttpRequest* implements *RequestIdentity* trait. +//! +//! ```rust +//! use actix_web::*; +//! use actix_web::middleware::identity::RequestIdentity; +//! use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy}; +//! +//! fn index(req: HttpRequest) -> Result { +//! // access request identity +//! if let Some(id) = req.identity() { +//! Ok(format!("Welcome! {}", id)) +//! } else { +//! Ok("Welcome Anonymous!".to_owned()) +//! } +//! } +//! +//! fn login(mut req: HttpRequest) -> HttpResponse { +//! req.remember("User1".to_owned()); // <- remember identity +//! HttpResponse::Ok().finish() +//! } +//! +//! fn logout(mut req: HttpRequest) -> HttpResponse { +//! req.forget(); // <- remove identity +//! HttpResponse::Ok().finish() +//! } +//! +//! fn main() { +//! let app = App::new().middleware( +//! IdentityService::new( // <- create identity middleware +//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend +//! .name("auth-cookie") +//! .secure(false)) +//! ); +//! } +//! ``` +use std::rc::Rc; + +use cookie::{Cookie, CookieJar, Key}; +use futures::Future; +use futures::future::{FutureResult, err as FutErr, ok as FutOk}; +use time::Duration; + +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use error::{Error, Result}; +use http::header::{self, HeaderValue}; +use middleware::{Middleware, Response, Started}; + + +/// The helper trait to obtain your identity from a request. +/// +/// ```rust +/// use actix_web::*; +/// use actix_web::middleware::identity::RequestIdentity; +/// +/// fn index(req: HttpRequest) -> Result { +/// // access request identity +/// if let Some(id) = req.identity() { +/// Ok(format!("Welcome! {}", id)) +/// } else { +/// Ok("Welcome Anonymous!".to_owned()) +/// } +/// } +/// +/// fn login(mut req: HttpRequest) -> HttpResponse { +/// req.remember("User1".to_owned()); // <- remember identity +/// HttpResponse::Ok().finish() +/// } +/// +/// fn logout(mut req: HttpRequest) -> HttpResponse { +/// req.forget(); // <- remove identity +/// HttpResponse::Ok().finish() +/// } +/// # fn main() {} +/// ``` +pub trait RequestIdentity { + + /// Return the claimed identity of the user associated request or + /// ``None`` if no identity can be found associated with the request. + fn identity(&self) -> Option<&str>; + + /// Remember identity. + fn remember(&mut self, identity: String); + + /// This method is used to 'forget' the current identity on subsequent requests. + fn forget(&mut self); +} + +impl RequestIdentity for HttpRequest { + fn identity(&self) -> Option<&str> { + if let Some(id) = self.extensions_ro().get::() { + return id.0.identity() + } + None + } + + fn remember(&mut self, identity: String) { + if let Some(id) = self.extensions_mut().get_mut::() { + return id.0.remember(identity) + } + } + + fn forget(&mut self) { + if let Some(id) = self.extensions_mut().get_mut::() { + return id.0.forget() + } + } +} + +/// An identity +pub trait Identity: 'static { + + fn identity(&self) -> Option<&str>; + + fn remember(&mut self, key: String); + + fn forget(&mut self); + + /// Write session to storage backend. + fn write(&mut self, resp: HttpResponse) -> Result; +} + +/// Identity policy definition. +pub trait IdentityPolicy: Sized + 'static { + type Identity: Identity; + type Future: Future; + + /// Parse the session from request and load data from a service identity. + fn from_request(&self, request: &mut HttpRequest) -> Self::Future; +} + +/// Request identity middleware +/// +/// ```rust +/// # extern crate actix; +/// # extern crate actix_web; +/// use actix_web::App; +/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy}; +/// +/// fn main() { +/// let app = App::new().middleware( +/// IdentityService::new( // <- create identity middleware +/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend +/// .name("auth-cookie") +/// .secure(false)) +/// ); +/// } +/// ``` +pub struct IdentityService { + backend: T, +} + +impl IdentityService { + /// Create new identity service with specified backend. + pub fn new(backend: T) -> Self { + IdentityService { backend } + } +} + +struct IdentityBox(Box); + +#[doc(hidden)] +unsafe impl Send for IdentityBox {} +#[doc(hidden)] +unsafe impl Sync for IdentityBox {} + + +impl> Middleware for IdentityService { + fn start(&self, req: &mut HttpRequest) -> Result { + let mut req = req.clone(); + + let fut = self.backend + .from_request(&mut req) + .then(move |res| match res { + Ok(id) => { + req.extensions().insert(IdentityBox(Box::new(id))); + FutOk(None) + } + Err(err) => FutErr(err), + }); + Ok(Started::Future(Box::new(fut))) + } + + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Result { + if let Some(mut id) = req.extensions().remove::() { + id.0.write(resp) + } else { + Ok(Response::Done(resp)) + } + } +} + +#[doc(hidden)] +/// Identity that uses private cookies as identity storage. +pub struct CookieIdentity { + changed: bool, + identity: Option, + inner: Rc, +} + +impl Identity for CookieIdentity { + fn identity(&self) -> Option<&str> { + self.identity.as_ref().map(|s| s.as_ref()) + } + + fn remember(&mut self, value: String) { + self.changed = true; + self.identity = Some(value); + } + + fn forget(&mut self) { + self.changed = true; + self.identity = None; + } + + fn write(&mut self, mut resp: HttpResponse) -> Result { + if self.changed { + let _ = self.inner.set_cookie(&mut resp, self.identity.take()); + } + Ok(Response::Done(resp)) + } +} + +struct CookieIdentityInner { + key: Key, + name: String, + path: String, + domain: Option, + secure: bool, + max_age: Option, +} + +impl CookieIdentityInner { + fn new(key: &[u8]) -> CookieIdentityInner { + CookieIdentityInner { + key: Key::from_master(key), + name: "actix-identity".to_owned(), + path: "/".to_owned(), + domain: None, + secure: true, + max_age: None, + } + } + + fn set_cookie(&self, resp: &mut HttpResponse, id: Option) -> Result<()> { + let some = id.is_some(); + { + let id = id.unwrap_or_else(String::new); + let mut cookie = Cookie::new(self.name.clone(), id); + cookie.set_path(self.path.clone()); + cookie.set_secure(self.secure); + cookie.set_http_only(true); + + if let Some(ref domain) = self.domain { + cookie.set_domain(domain.clone()); + } + + if let Some(max_age) = self.max_age { + cookie.set_max_age(max_age); + } + + let mut jar = CookieJar::new(); + if some { + jar.private(&self.key).add(cookie); + } else { + jar.add_original(cookie.clone()); + jar.private(&self.key).remove(cookie); + } + + for cookie in jar.delta() { + let val = HeaderValue::from_str(&cookie.to_string())?; + resp.headers_mut().append(header::SET_COOKIE, val); + } + } + + Ok(()) + } + + fn load(&self, req: &mut HttpRequest) -> Option { + if let Ok(cookies) = req.cookies() { + for cookie in cookies { + if cookie.name() == self.name { + let mut jar = CookieJar::new(); + jar.add_original(cookie.clone()); + + let cookie_opt = jar.private(&self.key).get(&self.name); + if let Some(cookie) = cookie_opt { + return Some(cookie.value().into()) + } + } + } + } + None + } +} + +/// Use cookies for request identity storage. +/// +/// The constructors take a key as an argument. +/// This is the private key for cookie - when this value is changed, +/// all identities are lost. The constructors will panic if the key is less +/// than 32 bytes in length. +/// +/// # Example +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::App; +/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy}; +/// +/// fn main() { +/// let app = App::new().middleware( +/// IdentityService::new( // <- create identity middleware +/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy +/// .domain("www.rust-lang.org") +/// .name("actix_auth") +/// .path("/") +/// .secure(true))); +/// } +/// ``` +pub struct CookieIdentityPolicy(Rc); + +impl CookieIdentityPolicy { + + /// Construct new `CookieIdentityPolicy` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn new(key: &[u8]) -> CookieIdentityPolicy { + CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key))) + } + + /// Sets the `path` field in the session cookie being built. + pub fn path>(mut self, value: S) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().path = value.into(); + self + } + + /// Sets the `name` field in the session cookie being built. + pub fn name>(mut self, value: S) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().name = value.into(); + self + } + + /// Sets the `domain` field in the session cookie being built. + pub fn domain>(mut self, value: S) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); + self + } + + /// Sets the `secure` field in the session cookie being built. + /// + /// If the `secure` field is set, a cookie will only be transmitted when the + /// connection is secure - i.e. `https` + pub fn secure(mut self, value: bool) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().secure = value; + self + } + + /// Sets the `max-age` field in the session cookie being built. + pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); + self + } +} + +impl IdentityPolicy for CookieIdentityPolicy { + type Identity = CookieIdentity; + type Future = FutureResult; + + fn from_request(&self, req: &mut HttpRequest) -> Self::Future { + let identity = self.0.load(req); + FutOk(CookieIdentity { + identity, + changed: false, + inner: Rc::clone(&self.0), + }) + } +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index d41660ee..d38e3054 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -9,6 +9,7 @@ mod logger; pub mod cors; pub mod csrf; +pub mod identity; mod defaultheaders; mod errhandlers; #[cfg(feature = "session")] From ce1081432b8354e0d3f20586ccdd5b53adb43b15 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Apr 2018 20:11:49 -0700 Subject: [PATCH 0122/1635] export session module --- src/middleware/mod.rs | 5 ++- src/middleware/session.rs | 65 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index d38e3054..803f103d 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -9,16 +9,19 @@ mod logger; pub mod cors; pub mod csrf; +#[cfg(feature = "session")] pub mod identity; mod defaultheaders; mod errhandlers; #[cfg(feature = "session")] -mod session; +pub mod session; pub use self::defaultheaders::DefaultHeaders; pub use self::errhandlers::ErrorHandlers; pub use self::logger::Logger; #[cfg(feature = "session")] +#[doc(hidden)] +#[deprecated(since = "0.5.4", note="please use `actix_web::middleware::session` instead")] pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage}; diff --git a/src/middleware/session.rs b/src/middleware/session.rs index a7ca8061..60cbc476 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -1,3 +1,68 @@ +//! User sessions. +//! +//! Actix provides a general solution for session management. The +//! [**SessionStorage**](struct.SessionStorage.html) +//! middleware can be used with different backend types to store session +//! data in different backends. +//! +//! By default, only cookie session backend is implemented. Other +//! backend implementations can be added. +//! +//! [**CookieSessionBackend**](struct.CookieSessionBackend.html) +//! uses cookies as session storage. `CookieSessionBackend` creates sessions which +//! are limited to storing fewer than 4000 bytes of data, as the payload must fit into a +//! single cookie. An internal server error is generated if a session contains +//! more than 4000 bytes. +//! +//! A cookie may have a security policy of *signed* or *private*. Each has +//! a respective `CookieSessionBackend` constructor. +//! +//! A *signed* cookie may be viewed but not modified by the client. A *private* +//! cookie may neither be viewed nor modified by the client. +//! +//! The constructors take a key as an argument. This is the private key +//! for cookie session - when this value is changed, all session data is lost. +//! +//! In general, you create a `SessionStorage` middleware and initialize it +//! with specific backend implementation, such as a `CookieSessionBackend`. +//! To access session data, +//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session) +//! must be used. This method returns a +//! [*Session*](struct.Session.html) object, which allows us to get or set +//! session data. +//! +//! ```rust +//! # extern crate actix; +//! # extern crate actix_web; +//! use actix_web::{server, App, HttpRequest, Result}; +//! use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend}; +//! +//! 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"); +//! server::new( +//! || App::new() +//! .middleware(SessionStorage::new( // <- create session middleware +//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend +//! .secure(false) +//! ))) +//! .bind("127.0.0.1:59880").unwrap() +//! .start(); +//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +//! let _ = sys.run(); +//! } +//! ``` use std::collections::HashMap; use std::marker::PhantomData; use std::rc::Rc; From 48b02abee7a28e84a5c8b368ccec30c2f5a0e67b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Apr 2018 20:16:29 -0700 Subject: [PATCH 0123/1635] fmt --- src/middleware/identity.rs | 44 ++++++++++++++++++-------------------- src/middleware/mod.rs | 7 +++--- src/middleware/session.rs | 8 +++---- tests/test_server.rs | 4 ++-- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index f88b3f97..e428847a 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -3,8 +3,8 @@ //! [**IdentityService**](struct.IdentityService.html) middleware can be //! used with different policies types to store identity information. //! -//! Bu default, only cookie identity policy is implemented. Other backend implementations -//! can be added separately. +//! Bu default, only cookie identity policy is implemented. Other backend +//! implementations can be added separately. //! //! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) //! uses cookies as identity storage. @@ -14,9 +14,9 @@ //! *HttpRequest* implements *RequestIdentity* trait. //! //! ```rust -//! use actix_web::*; //! use actix_web::middleware::identity::RequestIdentity; -//! use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy}; +//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; +//! use actix_web::*; //! //! fn index(req: HttpRequest) -> Result { //! // access request identity @@ -33,17 +33,17 @@ //! } //! //! fn logout(mut req: HttpRequest) -> HttpResponse { -//! req.forget(); // <- remove identity +//! req.forget(); // <- remove identity //! HttpResponse::Ok().finish() //! } //! //! fn main() { -//! let app = App::new().middleware( -//! IdentityService::new( // <- create identity middleware -//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend +//! let app = App::new().middleware(IdentityService::new( +//! // <- create identity middleware +//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend //! .name("auth-cookie") -//! .secure(false)) -//! ); +//! .secure(false), +//! )); //! } //! ``` use std::rc::Rc; @@ -53,13 +53,12 @@ use futures::Future; use futures::future::{FutureResult, err as FutErr, ok as FutOk}; use time::Duration; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; use error::{Error, Result}; use http::header::{self, HeaderValue}; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; - /// The helper trait to obtain your identity from a request. /// /// ```rust @@ -87,7 +86,6 @@ use middleware::{Middleware, Response, Started}; /// # fn main() {} /// ``` pub trait RequestIdentity { - /// Return the claimed identity of the user associated request or /// ``None`` if no identity can be found associated with the request. fn identity(&self) -> Option<&str>; @@ -95,34 +93,34 @@ pub trait RequestIdentity { /// Remember identity. fn remember(&mut self, identity: String); - /// This method is used to 'forget' the current identity on subsequent requests. + /// This method is used to 'forget' the current identity on subsequent + /// requests. fn forget(&mut self); } impl RequestIdentity for HttpRequest { fn identity(&self) -> Option<&str> { if let Some(id) = self.extensions_ro().get::() { - return id.0.identity() + return id.0.identity(); } None } fn remember(&mut self, identity: String) { if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.remember(identity) + return id.0.remember(identity); } } fn forget(&mut self) { if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.forget() + return id.0.forget(); } } } /// An identity pub trait Identity: 'static { - fn identity(&self) -> Option<&str>; fn remember(&mut self, key: String); @@ -177,7 +175,6 @@ unsafe impl Send for IdentityBox {} #[doc(hidden)] unsafe impl Sync for IdentityBox {} - impl> Middleware for IdentityService { fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); @@ -194,7 +191,9 @@ impl> Middleware for IdentityService { Ok(Started::Future(Box::new(fut))) } - fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Result { + fn response( + &self, req: &mut HttpRequest, resp: HttpResponse + ) -> Result { if let Some(mut id) = req.extensions().remove::() { id.0.write(resp) } else { @@ -298,7 +297,7 @@ impl CookieIdentityInner { let cookie_opt = jar.private(&self.key).get(&self.name); if let Some(cookie) = cookie_opt { - return Some(cookie.value().into()) + return Some(cookie.value().into()); } } } @@ -334,7 +333,6 @@ impl CookieIdentityInner { pub struct CookieIdentityPolicy(Rc); impl CookieIdentityPolicy { - /// Construct new `CookieIdentityPolicy` instance. /// /// Panics if key length is less than 32 bytes. diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 803f103d..c437b254 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -9,11 +9,11 @@ mod logger; pub mod cors; pub mod csrf; -#[cfg(feature = "session")] -pub mod identity; mod defaultheaders; mod errhandlers; #[cfg(feature = "session")] +pub mod identity; +#[cfg(feature = "session")] pub mod session; pub use self::defaultheaders::DefaultHeaders; pub use self::errhandlers::ErrorHandlers; @@ -21,7 +21,8 @@ pub use self::logger::Logger; #[cfg(feature = "session")] #[doc(hidden)] -#[deprecated(since = "0.5.4", note="please use `actix_web::middleware::session` instead")] +#[deprecated(since = "0.5.4", + note = "please use `actix_web::middleware::session` instead")] pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage}; diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 60cbc476..6c041b4b 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -9,10 +9,10 @@ //! backend implementations can be added. //! //! [**CookieSessionBackend**](struct.CookieSessionBackend.html) -//! uses cookies as session storage. `CookieSessionBackend` creates sessions which -//! are limited to storing fewer than 4000 bytes of data, as the payload must fit into a -//! single cookie. An internal server error is generated if a session contains -//! more than 4000 bytes. +//! uses cookies as session storage. `CookieSessionBackend` creates sessions +//! which are limited to storing fewer than 4000 bytes of data, as the payload +//! must fit into a single cookie. An internal server error is generated if a +//! session contains more than 4000 bytes. //! //! A cookie may have a security policy of *signed* or *private*. Each has //! a respective `CookieSessionBackend` constructor. diff --git a/tests/test_server.rs b/tests/test_server.rs index 162f193b..cfbff6d8 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -916,7 +916,6 @@ fn test_resource_middlewares() { // assert_eq!(num3.load(Ordering::Relaxed), 1); } - fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse { future::result(Err(error::ErrorBadRequest("TEST"))).responder() } @@ -936,7 +935,8 @@ fn test_middleware_async_error() { start: Arc::clone(&act_req), response: Arc::clone(&act_resp), finish: Arc::clone(&act_fin), - }).handler(index_test_middleware_async_error)}); + }).handler(index_test_middleware_async_error) + }); let request = srv.get().finish().unwrap(); let response = srv.execute(request.send()).unwrap(); From 813d1d6e6653308cf97c88b78fa6395b5981622a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Apr 2018 20:41:03 -0700 Subject: [PATCH 0124/1635] doc strings layout --- src/middleware/logger.rs | 1 + src/middleware/session.rs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d9d96a1c..28964718 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -14,6 +14,7 @@ use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Started}; /// `Middleware` for logging request and response info to the terminal. +/// /// `Logger` middleware uses standard log crate to log information. You should /// enable logger for `actix_web` package to see access log. /// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 6c041b4b..c71ed5a6 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -52,11 +52,11 @@ //! fn main() { //! let sys = actix::System::new("basic-example"); //! server::new( -//! || App::new() -//! .middleware(SessionStorage::new( // <- create session middleware -//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend -//! .secure(false) -//! ))) +//! || App::new().middleware( +//! SessionStorage::new( // <- create session middleware +//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend +//! .secure(false) +//! ))) //! .bind("127.0.0.1:59880").unwrap() //! .start(); //! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); From 2c8d987241fea923f2cb7e35355385abf96af629 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 19 Apr 2018 07:55:09 -0700 Subject: [PATCH 0125/1635] Use Display formatting for InternalError Display implementation #188 --- CHANGES.md | 4 +++- src/error.rs | 32 ++++++++++++++++---------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1bc29ae1..3745051d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,13 @@ # Changes -## 0.5.4 (2018-04-xx) +## 0.5.4 (2018-04-19) * Add identity service middleware * Middleware response() is not invoked if there was an error in async handler #187 +* Use Display formatting for InternalError Display implementation #188 + ## 0.5.3 (2018-04-18) diff --git a/src/error.rs b/src/error.rs index da56c35c..5f660c48 100644 --- a/src/error.rs +++ b/src/error.rs @@ -580,7 +580,7 @@ impl InternalError { impl Fail for InternalError where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { fn backtrace(&self) -> Option<&Backtrace> { Some(&self.backtrace) @@ -598,16 +598,16 @@ where impl fmt::Display for InternalError where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Display + 'static, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.cause, f) + fmt::Display::fmt(&self.cause, f) } } impl ResponseError for InternalError where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { fn error_response(&self) -> HttpResponse { match self.status { @@ -625,7 +625,7 @@ where impl Responder for InternalError where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { type Item = HttpResponse; type Error = Error; @@ -640,7 +640,7 @@ where #[allow(non_snake_case)] pub fn ErrorBadRequest(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::BAD_REQUEST).into() } @@ -650,7 +650,7 @@ where #[allow(non_snake_case)] pub fn ErrorUnauthorized(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::UNAUTHORIZED).into() } @@ -660,7 +660,7 @@ where #[allow(non_snake_case)] pub fn ErrorForbidden(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::FORBIDDEN).into() } @@ -670,7 +670,7 @@ where #[allow(non_snake_case)] pub fn ErrorNotFound(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::NOT_FOUND).into() } @@ -680,7 +680,7 @@ where #[allow(non_snake_case)] pub fn ErrorMethodNotAllowed(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() } @@ -690,7 +690,7 @@ where #[allow(non_snake_case)] pub fn ErrorRequestTimeout(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into() } @@ -700,7 +700,7 @@ where #[allow(non_snake_case)] pub fn ErrorConflict(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::CONFLICT).into() } @@ -710,7 +710,7 @@ where #[allow(non_snake_case)] pub fn ErrorGone(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::GONE).into() } @@ -720,7 +720,7 @@ where #[allow(non_snake_case)] pub fn ErrorPreconditionFailed(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() } @@ -730,7 +730,7 @@ where #[allow(non_snake_case)] pub fn ErrorExpectationFailed(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() } @@ -740,7 +740,7 @@ where #[allow(non_snake_case)] pub fn ErrorInternalServerError(err: T) -> Error where - T: Send + Sync + fmt::Debug + 'static, + T: Send + Sync + fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() } From 01a0f3f5a05f295b20493cb49ba4c4ca4cc4d839 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 19 Apr 2018 09:54:22 -0700 Subject: [PATCH 0126/1635] remove unused dependency --- Cargo.toml | 1 - src/lib.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 73a9c453..fe83dc97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,6 @@ futures = "0.1" futures-cpupool = "0.1" tokio-io = "0.1" tokio-core = "0.1" -trust-dns-resolver = "0.8" # native-tls native-tls = { version="0.1", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 13be3ef4..1a0ac8ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,6 @@ extern crate percent_encoding; extern crate serde_json; extern crate serde_urlencoded; extern crate smallvec; -extern crate trust_dns_resolver; #[macro_use] extern crate actix; From 2579c498652b4f328828f6f0b28270ecd01a7541 Mon Sep 17 00:00:00 2001 From: Derrick Lee Date: Thu, 19 Apr 2018 18:51:01 -0700 Subject: [PATCH 0127/1635] Update README links to use new guide --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6e07e57c..d06a4fcd 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. -* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/actix-web/guide/qs_13.html) protocols +* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols * Streaming and pipelining * Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/actix-web/guide/qs_9.html) support +* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support * Transparent content compression/decompression (br, gzip, deflate) -* Configurable [request routing](https://actix.rs/actix-web/guide/qs_5.html) +* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html) * Graceful server shutdown * Multipart streams * Static assets From 5528cf62f05397087ba3140206f9f418556e565d Mon Sep 17 00:00:00 2001 From: Nathan Fox Date: Fri, 20 Apr 2018 21:30:18 -0400 Subject: [PATCH 0128/1635] check if close code exists before reading it --- src/ws/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 97162b19..f79f3f77 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -310,10 +310,14 @@ where } OpCode::Close => { self.closed = true; - let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; - Ok(Async::Ready(Some(Message::Close(CloseCode::from( - code, - ))))) + let close_code = if payload.len() >= 2{ + let raw_code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; + CloseCode::from(raw_code) + }else{ + CloseCode::Status + }; + + Ok(Async::Ready(Some(Message::Close(close_code)))) } OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( String::from_utf8_lossy(payload.as_ref()).into(), From dc9a24a189f5b32e0815b09d290f6154a7871341 Mon Sep 17 00:00:00 2001 From: Nathan Fox Date: Fri, 20 Apr 2018 21:55:07 -0400 Subject: [PATCH 0129/1635] add websocket empty close status test --- tests/test_ws.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 2126543e..61f4af42 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -60,6 +60,16 @@ fn test_simple() { assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal))); } +#[test] +fn test_empty_close_code() { + let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); + let (reader, mut writer) = srv.ws().unwrap(); + + writer.close(ws::CloseCode::Empty, ""); + let (item, _) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Status))); +} + #[test] fn test_large_text() { let data = rand::thread_rng() From 2adf8a3a48c75ba8eaa835d78001a2223df92c71 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 21 Apr 2018 07:56:11 -0700 Subject: [PATCH 0130/1635] add changelog entry --- CHANGES.md | 5 +++++ Cargo.toml | 2 +- src/ws/mod.rs | 7 ++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3745051d..ca1581f5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.5.5 (2018-04-xx) + +* Fix panic when Websocket is closed with no error code #191 + + ## 0.5.4 (2018-04-19) * Add identity service middleware diff --git a/Cargo.toml b/Cargo.toml index fe83dc97..78c0d723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.5.4" +version = "0.5.5" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/ws/mod.rs b/src/ws/mod.rs index f79f3f77..06b77112 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -310,10 +310,11 @@ where } OpCode::Close => { self.closed = true; - let close_code = if payload.len() >= 2{ - let raw_code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; + let close_code = if payload.len() >= 2 { + let raw_code = + NetworkEndian::read_uint(payload.as_ref(), 2) as u16; CloseCode::from(raw_code) - }else{ + } else { CloseCode::Status }; From 59244b203c1948b9ff8986b066b74ceaa83826a6 Mon Sep 17 00:00:00 2001 From: Brandur Date: Sat, 21 Apr 2018 08:41:06 -0700 Subject: [PATCH 0131/1635] Let CSRF's `allowed_origin()` be specified as a type supporting `Into` A very minor addition: I'm using this middleware on specific resources, and given a non-static string, I often have to `clone()` already to get a string into a closure. Take this code for example: ``` rust let server = actix_web::server::new(move || { let csrf_origin_graphql = csrf_origin.clone(); ... .resource("/graphql", move |r| { r.middleware( csrf::CsrfFilter::new().allowed_origin(csrf_origin_graphql.as_str()), ); r.method(Method::POST).a(graphql::handlers::graphql_post); }) ``` Letting `allowed_origin()` take an `Into` instead of `&str` would prevent a second `clone()` in the code above, and also make the code a little nicer to read (you eliminate the `.as_str()` above). This is a pattern that seems to be common throughout actix-web already anyway, so it should also be fine to have here. --- src/middleware/csrf.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index b0eb4a3d..9ff23b53 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -150,8 +150,8 @@ impl CsrfFilter { /// Add an origin that is allowed to make requests. Will be verified /// against the `Origin` request header. - pub fn allowed_origin(mut self, origin: &str) -> CsrfFilter { - self.origins.insert(origin.to_owned()); + pub fn allowed_origin>(mut self, origin: T) -> CsrfFilter { + self.origins.insert(origin.into()); self } From de8a09254d913334ab6f59269767df530369f2f7 Mon Sep 17 00:00:00 2001 From: Nathan Fox Date: Sat, 21 Apr 2018 16:50:27 -0400 Subject: [PATCH 0132/1635] use Optional with websocket close reason --- src/ws/client.rs | 13 ++++----- src/ws/context.rs | 6 ++--- src/ws/frame.rs | 69 ++++++++++++++++++++++++++++++----------------- src/ws/mod.rs | 16 +++-------- src/ws/proto.rs | 38 ++++++++++++++++---------- tests/test_ws.rs | 12 ++++----- 6 files changed, 87 insertions(+), 67 deletions(-) diff --git a/src/ws/client.rs b/src/ws/client.rs index 522ae02a..5a8f10e0 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -5,7 +5,6 @@ use std::time::Duration; use std::{fmt, io, str}; use base64; -use byteorder::{ByteOrder, NetworkEndian}; use bytes::Bytes; use cookie::Cookie; use futures::unsync::mpsc::{unbounded, UnboundedSender}; @@ -27,7 +26,7 @@ use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientRespons HttpResponseParserError, SendRequest, SendRequestError}; use super::frame::Frame; -use super::proto::{CloseCode, OpCode}; +use super::proto::{CloseReason, OpCode}; use super::{Message, ProtocolError}; /// Websocket client error @@ -468,10 +467,8 @@ impl Stream for ClientReader { } OpCode::Close => { inner.closed = true; - let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; - Ok(Async::Ready(Some(Message::Close(CloseCode::from( - code, - ))))) + let close_reason = Frame::parse_close_payload(&payload); + Ok(Async::Ready(Some(Message::Close(close_reason)))) } OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( String::from_utf8_lossy(payload.as_ref()).into(), @@ -560,7 +557,7 @@ impl ClientWriter { /// Send close frame #[inline] - pub fn close(&mut self, code: CloseCode, reason: &str) { - self.write(Frame::close(code, reason, true)); + pub fn close(&mut self, reason: Option) { + self.write(Frame::close(reason, true)); } } diff --git a/src/ws/context.rs b/src/ws/context.rs index ef333b41..b3831258 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -15,7 +15,7 @@ use error::{Error, ErrorInternalServerError}; use httprequest::HttpRequest; use ws::frame::Frame; -use ws::proto::{CloseCode, OpCode}; +use ws::proto::{CloseReason, OpCode}; /// Execution context for `WebSockets` actors pub struct WebsocketContext @@ -177,8 +177,8 @@ where /// Send close frame #[inline] - pub fn close(&mut self, code: CloseCode, reason: &str) { - self.write(Frame::close(code, reason, false)); + pub fn close(&mut self, reason: Option) { + self.write(Frame::close(reason, false)); } /// Returns drain future diff --git a/src/ws/frame.rs b/src/ws/frame.rs index d9159e54..07e59286 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -11,7 +11,7 @@ use payload::PayloadHelper; use ws::ProtocolError; use ws::mask::apply_mask; -use ws::proto::{CloseCode, OpCode}; +use ws::proto::{CloseCode, CloseReason, OpCode}; /// A struct representing a `WebSocket` frame. #[derive(Debug)] @@ -29,24 +29,22 @@ impl Frame { /// Create a new Close control frame. #[inline] - pub fn close(code: CloseCode, reason: &str, genmask: bool) -> Binary { - let raw: [u8; 2] = unsafe { - let u: u16 = code.into(); - mem::transmute(u.to_be()) - }; + pub fn close(reason: Option, genmask: bool) -> Binary { + let payload:Vec = match reason { + None => Vec::new(), + Some(reason) => { + let mut code_bytes = [0; 2]; + NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); - let payload = if let CloseCode::Empty = code { - Vec::new() - } else { - Vec::from_iter( - raw[..] - .iter() - .chain(reason.as_bytes().iter()) - .cloned(), - ) - }; + let mut payload = Vec::from(&code_bytes[..]); + if let Some(description) = reason.description{ + payload.extend(description.as_bytes()); + } + payload + } + }; - Frame::message(payload, OpCode::Close, true, genmask) + Frame::message(payload, OpCode::Close, true, genmask) } #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] @@ -281,6 +279,22 @@ impl Frame { }))) } + /// Parse the payload of a close frame. + pub fn parse_close_payload(payload: &Binary) -> Option { + if payload.len() >= 2 { + let raw_code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; + let code = CloseCode::from(raw_code); + let description = if payload.len() > 2 { + Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into()) + } else { + None + }; + Some(CloseReason { code, description }) + } else { + None + } + } + /// Generate binary representation pub fn message>( data: B, code: OpCode, finished: bool, genmask: bool @@ -516,12 +530,19 @@ mod tests { assert_eq!(frame, v.into()); } - #[test] - fn test_close_frame() { - let frame = Frame::close(CloseCode::Normal, "data", false); + #[test] + fn test_close_frame() { + let reason = (CloseCode::Normal, "data"); + let frame = Frame::close(Some(reason.into()), false); - let mut v = vec![136u8, 6u8, 3u8, 232u8]; - v.extend(b"data"); - assert_eq!(frame, v.into()); - } + let mut v = vec![136u8, 6u8, 3u8, 232u8]; + v.extend(b"data"); + assert_eq!(frame, v.into()); + } + + #[test] + fn test_empty_close_frame() { + let frame = Frame::close(None, false); + assert_eq!(frame, vec![0x88, 0x00].into()); + } } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 06b77112..7db1cf3b 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -66,8 +66,7 @@ mod proto; pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter}; pub use self::context::WebsocketContext; pub use self::frame::Frame; -pub use self::proto::CloseCode; -pub use self::proto::OpCode; +pub use self::proto::{CloseCode, CloseReason, OpCode}; /// Websocket protocol errors #[derive(Fail, Debug)] @@ -164,7 +163,7 @@ pub enum Message { Binary(Binary), Ping(String), Pong(String), - Close(CloseCode), + Close(Option), } /// Do websocket handshake and start actor @@ -310,15 +309,8 @@ where } OpCode::Close => { self.closed = true; - let close_code = if payload.len() >= 2 { - let raw_code = - NetworkEndian::read_uint(payload.as_ref(), 2) as u16; - CloseCode::from(raw_code) - } else { - CloseCode::Status - }; - - Ok(Async::Ready(Some(Message::Close(close_code)))) + let close_reason = Frame::parse_close_payload(&payload); + Ok(Async::Ready(Some(Message::Close(close_reason)))) } OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( String::from_utf8_lossy(payload.as_ref()).into(), diff --git a/src/ws/proto.rs b/src/ws/proto.rs index 0c2eab0f..0dbb5fda 100644 --- a/src/ws/proto.rs +++ b/src/ws/proto.rs @@ -90,10 +90,6 @@ pub enum CloseCode { /// endpoint that understands only text data MAY send this if it /// receives a binary message). Unsupported, - /// Indicates that no status code was included in a closing frame. This - /// close code makes it possible to use a single method, `on_close` to - /// handle even cases where no close code was provided. - Status, /// Indicates an abnormal closure. If the abnormal closure was due to an /// error, this close code will not be used. Instead, the `on_error` method /// of the handler will be called with the error. However, if the connection @@ -138,8 +134,6 @@ pub enum CloseCode { #[doc(hidden)] Tls, #[doc(hidden)] - Empty, - #[doc(hidden)] Other(u16), } @@ -150,7 +144,6 @@ impl Into for CloseCode { Away => 1001, Protocol => 1002, Unsupported => 1003, - Status => 1005, Abnormal => 1006, Invalid => 1007, Policy => 1008, @@ -160,7 +153,6 @@ impl Into for CloseCode { Restart => 1012, Again => 1013, Tls => 1015, - Empty => 0, Other(code) => code, } } @@ -173,7 +165,6 @@ impl From for CloseCode { 1001 => Away, 1002 => Protocol, 1003 => Unsupported, - 1005 => Status, 1006 => Abnormal, 1007 => Invalid, 1008 => Policy, @@ -183,12 +174,35 @@ impl From for CloseCode { 1012 => Restart, 1013 => Again, 1015 => Tls, - 0 => Empty, _ => Other(code), } } } +#[derive(Debug, PartialEq)] +pub struct CloseReason { + pub code: CloseCode, + pub description: Option, +} + +impl From for CloseReason { + fn from(code: CloseCode) -> Self { + CloseReason { + code, + description: None, + } + } +} + +impl > From<(CloseCode, T)> for CloseReason { + fn from(info: (CloseCode, T)) -> Self { + CloseReason{ + code: info.0, + description: Some(info.1.into()) + } + } +} + static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // TODO: hash is always same size, we dont need String @@ -269,7 +283,6 @@ mod test { assert_eq!(CloseCode::from(1001u16), CloseCode::Away); assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol); assert_eq!(CloseCode::from(1003u16), CloseCode::Unsupported); - assert_eq!(CloseCode::from(1005u16), CloseCode::Status); assert_eq!(CloseCode::from(1006u16), CloseCode::Abnormal); assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid); assert_eq!(CloseCode::from(1008u16), CloseCode::Policy); @@ -279,7 +292,6 @@ mod test { assert_eq!(CloseCode::from(1012u16), CloseCode::Restart); assert_eq!(CloseCode::from(1013u16), CloseCode::Again); assert_eq!(CloseCode::from(1015u16), CloseCode::Tls); - assert_eq!(CloseCode::from(0u16), CloseCode::Empty); assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000)); } @@ -289,7 +301,6 @@ mod test { assert_eq!(1001u16, Into::::into(CloseCode::Away)); assert_eq!(1002u16, Into::::into(CloseCode::Protocol)); assert_eq!(1003u16, Into::::into(CloseCode::Unsupported)); - assert_eq!(1005u16, Into::::into(CloseCode::Status)); assert_eq!(1006u16, Into::::into(CloseCode::Abnormal)); assert_eq!(1007u16, Into::::into(CloseCode::Invalid)); assert_eq!(1008u16, Into::::into(CloseCode::Policy)); @@ -299,7 +310,6 @@ mod test { assert_eq!(1012u16, Into::::into(CloseCode::Restart)); assert_eq!(1013u16, Into::::into(CloseCode::Again)); assert_eq!(1015u16, Into::::into(CloseCode::Tls)); - assert_eq!(0u16, Into::::into(CloseCode::Empty)); assert_eq!(2000u16, Into::::into(CloseCode::Other(2000))); } } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 61f4af42..624f9159 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -27,7 +27,7 @@ impl StreamHandler for Ws { ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason, ""), + ws::Message::Close(reason) => ctx.close(reason), _ => (), } } @@ -55,9 +55,9 @@ fn test_simple() { let (item, reader) = srv.execute(reader.into_future()).unwrap(); assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - writer.close(ws::CloseCode::Normal, ""); + writer.close(Some(ws::CloseCode::Normal.into())); let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal))); + assert_eq!(item, Some(ws::Message::Close(Some(ws::CloseCode::Normal.into())))); } #[test] @@ -65,9 +65,9 @@ fn test_empty_close_code() { let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (reader, mut writer) = srv.ws().unwrap(); - writer.close(ws::CloseCode::Empty, ""); + writer.close(None); let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Status))); + assert_eq!(item, Some(ws::Message::Close(None))); } #[test] @@ -147,7 +147,7 @@ impl StreamHandler for Ws2 { ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason, ""), + ws::Message::Close(reason) => ctx.close(reason), _ => (), } } From f6fd9e70f96039ccc6116414e475a21966765b01 Mon Sep 17 00:00:00 2001 From: Nathan Fox Date: Sat, 21 Apr 2018 16:53:55 -0400 Subject: [PATCH 0133/1635] code cleanup --- src/ws/frame.rs | 3 +-- src/ws/mod.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 07e59286..dfef26b0 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -2,7 +2,6 @@ use byteorder::{BigEndian, ByteOrder, NetworkEndian}; use bytes::{BufMut, Bytes, BytesMut}; use futures::{Async, Poll, Stream}; use rand; -use std::iter::FromIterator; use std::{fmt, mem, ptr}; use body::Binary; @@ -30,7 +29,7 @@ impl Frame { /// Create a new Close control frame. #[inline] pub fn close(reason: Option, genmask: bool) -> Binary { - let payload:Vec = match reason { + let payload = match reason { None => Vec::new(), Some(reason) => { let mut code_bytes = [0; 2]; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 7db1cf3b..93fd431c 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -43,7 +43,6 @@ //! # .finish(); //! # } //! ``` -use byteorder::{ByteOrder, NetworkEndian}; use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{header, Method, StatusCode}; From b7b61afaccf718ecb37cbc67260ad59ef34908c3 Mon Sep 17 00:00:00 2001 From: Nathan Fox Date: Sat, 21 Apr 2018 17:20:23 -0400 Subject: [PATCH 0134/1635] add ws close description parse test --- src/ws/proto.rs | 2 +- tests/test_ws.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ws/proto.rs b/src/ws/proto.rs index 0dbb5fda..954f3f4a 100644 --- a/src/ws/proto.rs +++ b/src/ws/proto.rs @@ -179,7 +179,7 @@ impl From for CloseCode { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, Eq, PartialEq, Clone)] pub struct CloseReason { pub code: CloseCode, pub description: Option, diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 624f9159..1283b2e8 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -70,6 +70,17 @@ fn test_empty_close_code() { assert_eq!(item, Some(ws::Message::Close(None))); } +#[test] +fn test_close_description() { + let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); + let (reader, mut writer) = srv.ws().unwrap(); + + let close_reason:ws::CloseReason = (ws::CloseCode::Normal, "close description").into(); + writer.close(Some(close_reason.clone())); + let (item, _) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Close(Some(close_reason)))); +} + #[test] fn test_large_text() { let data = rand::thread_rng() From f8b75c157fafeb244f2b1bd741a82508cde18b78 Mon Sep 17 00:00:00 2001 From: Nathan Fox Date: Sun, 22 Apr 2018 11:43:47 -0400 Subject: [PATCH 0135/1635] fix style --- src/ws/frame.rs | 52 ++++++++++++++++++++++++------------------------- src/ws/proto.rs | 28 +++++++++++++------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/ws/frame.rs b/src/ws/frame.rs index dfef26b0..de78b31d 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -29,21 +29,21 @@ impl Frame { /// Create a new Close control frame. #[inline] pub fn close(reason: Option, genmask: bool) -> Binary { - let payload = match reason { - None => Vec::new(), - Some(reason) => { - let mut code_bytes = [0; 2]; - NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); + let payload = match reason { + None => Vec::new(), + Some(reason) => { + let mut code_bytes = [0; 2]; + NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); - let mut payload = Vec::from(&code_bytes[..]); - if let Some(description) = reason.description{ - payload.extend(description.as_bytes()); - } - payload - } - }; + let mut payload = Vec::from(&code_bytes[..]); + if let Some(description) = reason.description{ + payload.extend(description.as_bytes()); + } + payload + } + }; - Frame::message(payload, OpCode::Close, true, genmask) + Frame::message(payload, OpCode::Close, true, genmask) } #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] @@ -529,19 +529,19 @@ mod tests { assert_eq!(frame, v.into()); } - #[test] - fn test_close_frame() { - let reason = (CloseCode::Normal, "data"); - let frame = Frame::close(Some(reason.into()), false); + #[test] + fn test_close_frame() { + let reason = (CloseCode::Normal, "data"); + let frame = Frame::close(Some(reason.into()), false); - let mut v = vec![136u8, 6u8, 3u8, 232u8]; - v.extend(b"data"); - assert_eq!(frame, v.into()); - } + let mut v = vec![136u8, 6u8, 3u8, 232u8]; + v.extend(b"data"); + assert_eq!(frame, v.into()); + } - #[test] - fn test_empty_close_frame() { - let frame = Frame::close(None, false); - assert_eq!(frame, vec![0x88, 0x00].into()); - } + #[test] + fn test_empty_close_frame() { + let frame = Frame::close(None, false); + assert_eq!(frame, vec![0x88, 0x00].into()); + } } diff --git a/src/ws/proto.rs b/src/ws/proto.rs index 954f3f4a..2851fb9e 100644 --- a/src/ws/proto.rs +++ b/src/ws/proto.rs @@ -181,26 +181,26 @@ impl From for CloseCode { #[derive(Debug, Eq, PartialEq, Clone)] pub struct CloseReason { - pub code: CloseCode, - pub description: Option, + pub code: CloseCode, + pub description: Option, } impl From for CloseReason { - fn from(code: CloseCode) -> Self { - CloseReason { - code, - description: None, - } - } + fn from(code: CloseCode) -> Self { + CloseReason { + code, + description: None, + } + } } impl > From<(CloseCode, T)> for CloseReason { - fn from(info: (CloseCode, T)) -> Self { - CloseReason{ - code: info.0, - description: Some(info.1.into()) - } - } + fn from(info: (CloseCode, T)) -> Self { + CloseReason{ + code: info.0, + description: Some(info.1.into()) + } + } } static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; From f8af3ef7f4be60ae35800adeedc8dcf8597e1f2a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 22 Apr 2018 15:28:04 -0700 Subject: [PATCH 0136/1635] refactor keep-alive --- src/httprequest.rs | 39 +++++++++-------------- src/server/encoding.rs | 5 +-- src/server/h1.rs | 70 +++++++++++++++++++++++++++++------------- src/server/h1writer.rs | 4 +-- 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index e917b5c8..ee2bd5a7 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -37,6 +37,7 @@ pub struct HttpInnerMessage { pub addr: Option, pub payload: Option, pub info: Option>, + pub keep_alive: bool, resource: RouterResource, } @@ -56,11 +57,12 @@ impl Default for HttpInnerMessage { params: Params::new(), query: Params::new(), query_loaded: false, - cookies: None, addr: None, + cookies: None, payload: None, extensions: Extensions::new(), info: None, + keep_alive: true, resource: RouterResource::Notset, } } @@ -70,20 +72,7 @@ impl HttpInnerMessage { /// 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() { - 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 - } + self.keep_alive } #[inline] @@ -91,12 +80,12 @@ impl HttpInnerMessage { 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; + self.query_loaded = false; + self.cookies = None; self.payload = None; + self.keep_alive = true; self.resource = RouterResource::Notset; } } @@ -126,10 +115,11 @@ impl HttpRequest<()> { params: Params::new(), query: Params::new(), query_loaded: false, + extensions: Extensions::new(), cookies: None, addr: None, - extensions: Extensions::new(), info: None, + keep_alive: true, resource: RouterResource::Notset, }), None, @@ -377,13 +367,13 @@ impl HttpRequest { /// 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() + pub fn peer_addr(&self) -> Option { + self.as_ref().addr } #[inline] pub(crate) fn set_peer_addr(&mut self, addr: Option) { - self.as_mut().addr = addr + self.as_mut().addr = addr; } /// Get a reference to the Params object. @@ -392,6 +382,7 @@ impl HttpRequest { if !self.as_ref().query_loaded { let params: &mut Params = unsafe { mem::transmute(&mut self.as_mut().query) }; + params.clear(); self.as_mut().query_loaded = true; for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { params.add(key, val); @@ -425,9 +416,9 @@ impl HttpRequest { } } } - msg.cookies = Some(cookies) + msg.cookies = Some(cookies); } - Ok(self.as_ref().cookies.as_ref().unwrap()) + Ok(&self.as_ref().cookies.as_ref().unwrap()) } /// Return request cookie. diff --git a/src/server/encoding.rs b/src/server/encoding.rs index b9da1def..7c886fe5 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -9,7 +9,7 @@ use bytes::{BufMut, Bytes, BytesMut}; use flate2::Compression; use flate2::read::GzDecoder; use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; -use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONNECTION, +use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{HttpTryFrom, Method, Version}; @@ -459,9 +459,6 @@ impl ContentEncoder { 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; diff --git a/src/server/h1.rs b/src/server/h1.rs index c60762b6..ec0b1938 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -510,9 +510,10 @@ impl Reader { buf: &mut BytesMut, settings: &WorkerSettings ) -> Poll<(HttpRequest, Option), ParseError> { // Parse http message - let mut has_te = false; let mut has_upgrade = false; - let mut has_length = false; + let mut chunked = false; + let mut content_length = None; + let msg = { let bytes_ptr = buf.as_ref().as_ptr() as usize; let mut headers: [httparse::Header; MAX_HEADERS] = @@ -546,10 +547,10 @@ impl Reader { let msg = settings.get_http_message(); { let msg_mut = msg.get_mut(); + msg_mut.keep_alive = version != Version::HTTP_10; + for header in headers[..headers_len].iter() { if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) { - has_te = has_te || name == header::TRANSFER_ENCODING; - has_length = has_length || name == header::CONTENT_LENGTH; has_upgrade = has_upgrade || name == header::UPGRADE; let v_start = header.value.as_ptr() as usize - bytes_ptr; let v_end = v_start + header.value.len(); @@ -558,6 +559,47 @@ impl Reader { slice.slice(v_start, v_end), ) }; + match name { + header::CONTENT_LENGTH => { + if let Ok(s) = value.to_str() { + if let Ok(len) = s.parse::() { + content_length = Some(len) + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header); + } + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header); + } + }, + // transfer-encoding + header::TRANSFER_ENCODING => { + if let Ok(s) = value.to_str() { + chunked = s.to_lowercase().contains("chunked"); + } else { + return Err(ParseError::Header) + } + }, + // connection keep-alive state + header::CONNECTION => { + msg_mut.keep_alive = if let Ok(conn) = value.to_str() { + if version == Version::HTTP_10 + && conn.contains("keep-alive") + { + true + } else { + version == Version::HTTP_11 + && !(conn.contains("close") + || conn.contains("upgrade")) + } + } else { + false + }; + }, + _ => (), + } + msg_mut.headers.append(name, value); } else { return Err(ParseError::Header); @@ -572,26 +614,12 @@ impl Reader { }; // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = if has_te && chunked(&msg.get_mut().headers)? { + let decoder = if chunked { // Chunked encoding Some(Decoder::chunked()) - } else if has_length { + } else if let Some(len) = content_length { // Content-Length - let len = msg.get_ref() - .headers - .get(header::CONTENT_LENGTH) - .unwrap(); - 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); - } + Some(Decoder::length(len)) } else if has_upgrade || msg.get_ref().method == Method::CONNECT { // upgrade(websocket) or connect Some(Decoder::eof()) diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index ee2717bb..3d94d44c 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -2,8 +2,6 @@ use bytes::BufMut; use futures::{Async, Poll}; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE}; -use http::{Method, Version}; use std::rc::Rc; use std::{io, mem}; use tokio_io::AsyncWrite; @@ -17,6 +15,8 @@ use body::{Binary, Body}; use header::ContentEncoding; use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; +use http::{Method, Version}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE}; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific From bcd03a9c6263649e7623749169586c516b41156c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Apr 2018 09:16:46 -0700 Subject: [PATCH 0137/1635] link to askama example --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d06a4fcd..01b72424 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,9 @@ fn main() { * [Stateful](https://github.com/actix/examples/tree/master/state/) * [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) * [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) -* [Simple websocket session](https://github.com/actix/examples/tree/master/websocket/) -* [Tera templates](https://github.com/actix/examples/tree/master/template_tera/) +* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) +* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / + [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates * [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) * [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) * [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) From 2477afcf309da0fa16df113054c18fd2b1960c59 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Apr 2018 09:29:15 -0700 Subject: [PATCH 0138/1635] Allow to use rust backend for flate2 crate #199 --- CHANGES.md | 3 ++- Cargo.toml | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ca1581f5..f86a05d4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,10 @@ # Changes -## 0.5.5 (2018-04-xx) +## 0.5.5 (2018-04-24) * Fix panic when Websocket is closed with no error code #191 +* Allow to use rust backend for flate2 crate #199 ## 0.5.4 (2018-04-19) diff --git a/Cargo.toml b/Cargo.toml index 78c0d723..1a7c1387 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ name = "actix_web" path = "src/lib.rs" [features] -default = ["session", "brotli"] +default = ["session", "brotli", "flate2-c"] # tls tls = ["native-tls", "tokio-tls"] @@ -40,13 +40,18 @@ session = ["cookie/secure"] # brotli encoding brotli = ["brotli2"] +# miniz-sys backend for flate2 crate +flate2-c = ["flate2/miniz-sys"] + +# rust-backend for flate2 crate +flate2-rust = ["flate2/rust_backend"] + [dependencies] actix = "^0.5.5" base64 = "0.9" bitflags = "1.0" failure = "0.1.1" -flate2 = "1.0" h2 = "0.1" http = "^0.1.5" httparse = "1.2" @@ -71,6 +76,7 @@ lazy_static = "1.0" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.10", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } +flate2 = { version="1.0", default-features = false } # io mio = "^0.6.13" From b66566f610a5ac6543af62bcc580ce5c0de65aed Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Apr 2018 09:32:19 -0700 Subject: [PATCH 0139/1635] comments --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a7c1387..56364210 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,16 +34,16 @@ tls = ["native-tls", "tokio-tls"] # openssl alpn = ["openssl", "tokio-openssl"] -# sessions +# sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] -# brotli encoding +# brotli encoding, requires c compiler brotli = ["brotli2"] # miniz-sys backend for flate2 crate flate2-c = ["flate2/miniz-sys"] -# rust-backend for flate2 crate +# rust backend for flate2 crate flate2-rust = ["flate2/rust_backend"] [dependencies] From 2e7d323e1a25f50c2a7e21e62427fce160db4022 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Apr 2018 09:34:38 -0700 Subject: [PATCH 0140/1635] add r2d2 example link --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 01b72424..ed818d95 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ fn main() { * [Tera](https://github.com/actix/examples/tree/master/template_tera/) / [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates * [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) +* [r2d2](https://github.com/actix/examples/tree/master/r2d2/) * [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) * [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) * [Json](https://github.com/actix/examples/tree/master/json/) From 5ca904d1dbc83350696dca7e5c7ecec887300fbc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Apr 2018 12:24:04 -0700 Subject: [PATCH 0141/1635] make flate crate optional --- Cargo.toml | 2 +- src/client/writer.rs | 16 +++++++++++----- src/header/mod.rs | 14 +++++++++++--- src/lib.rs | 7 +++++-- src/server/encoding.rs | 31 +++++++++++++++++++++++++++---- 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56364210..e6c241f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ lazy_static = "1.0" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.10", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } -flate2 = { version="1.0", default-features = false } +flate2 = { version="1.0", optional = true, default-features = false } # io mio = "^0.6.13" diff --git a/src/client/writer.rs b/src/client/writer.rs index 48e4cc71..36c9d6ee 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -7,8 +7,10 @@ use std::io::{self, Write}; #[cfg(feature = "brotli")] use brotli2::write::BrotliEncoder; use bytes::{BufMut, BytesMut}; -use flate2::Compression; +#[cfg(feature = "flate2")] use flate2::write::{DeflateEncoder, GzEncoder}; +#[cfg(feature = "flate2")] +use flate2::Compression; use futures::{Async, Poll}; use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; @@ -18,9 +20,9 @@ use tokio_io::AsyncWrite; use body::{Binary, Body}; use header::ContentEncoding; -use server::WriterState; use server::encoding::{ContentEncoder, TransferEncoding}; use server::shared::SharedBytes; +use server::WriterState; use client::ClientRequest; @@ -70,7 +72,7 @@ impl HttpClientWriter { // !self.flags.contains(Flags::UPGRADE) } fn write_to_stream( - &mut self, stream: &mut T + &mut self, stream: &mut T, ) -> io::Result { while !self.buffer.is_empty() { match stream.write(self.buffer.as_ref()) { @@ -191,7 +193,7 @@ impl HttpClientWriter { #[inline] pub fn poll_completed( - &mut self, stream: &mut T, shutdown: bool + &mut self, stream: &mut T, shutdown: bool, ) -> Poll<(), io::Error> { match self.write_to_stream(stream) { Ok(WriterState::Done) => { @@ -222,9 +224,11 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder let tmp = SharedBytes::default(); let transfer = TransferEncoding::eof(tmp.clone()); let mut enc = match encoding { + #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate( DeflateEncoder::new(transfer, Compression::default()), ), + #[cfg(feature = "flate2")] ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( transfer, Compression::default(), @@ -283,10 +287,12 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder req.replace_body(body); match encoding { + #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( transfer, Compression::default(), )), + #[cfg(feature = "flate2")] ContentEncoding::Gzip => { ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default())) } @@ -299,7 +305,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder } fn streaming_encoding( - buf: SharedBytes, version: Version, req: &mut ClientRequest + buf: SharedBytes, version: Version, req: &mut ClientRequest, ) -> TransferEncoding { if req.chunked() { // Enable transfer encoding diff --git a/src/header/mod.rs b/src/header/mod.rs index 3564da9e..7d791c7b 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -6,8 +6,8 @@ use std::str::FromStr; use bytes::{Bytes, BytesMut}; use mime::Mime; -use modhttp::Error as HttpError; use modhttp::header::GetAll; +use modhttp::Error as HttpError; pub use modhttp::header::*; @@ -116,8 +116,10 @@ pub enum ContentEncoding { #[cfg(feature = "brotli")] Br, /// A format using the zlib structure with deflate algorithm + #[cfg(feature = "flate2")] Deflate, /// Gzip algorithm + #[cfg(feature = "flate2")] Gzip, /// Indicates the identity function (i.e. no compression, nor modification) Identity, @@ -137,7 +139,9 @@ impl ContentEncoding { match *self { #[cfg(feature = "brotli")] ContentEncoding::Br => "br", + #[cfg(feature = "flate2")] ContentEncoding::Gzip => "gzip", + #[cfg(feature = "flate2")] ContentEncoding::Deflate => "deflate", ContentEncoding::Identity | ContentEncoding::Auto => "identity", } @@ -149,7 +153,9 @@ impl ContentEncoding { match *self { #[cfg(feature = "brotli")] ContentEncoding::Br => 1.1, + #[cfg(feature = "flate2")] ContentEncoding::Gzip => 1.0, + #[cfg(feature = "flate2")] ContentEncoding::Deflate => 0.9, ContentEncoding::Identity | ContentEncoding::Auto => 0.1, } @@ -159,10 +165,12 @@ impl ContentEncoding { // TODO: remove memory allocation impl<'a> From<&'a str> for ContentEncoding { fn from(s: &'a str) -> ContentEncoding { - match s.trim().to_lowercase().as_ref() { + match AsRef::::as_ref(&s.trim().to_lowercase()) { #[cfg(feature = "brotli")] "br" => ContentEncoding::Br, + #[cfg(feature = "flate2")] "gzip" => ContentEncoding::Gzip, + #[cfg(feature = "flate2")] "deflate" => ContentEncoding::Deflate, _ => ContentEncoding::Identity, } @@ -202,7 +210,7 @@ impl fmt::Write for Writer { #[doc(hidden)] /// Reads a comma-delimited raw header into a Vec. pub fn from_comma_delimited( - all: GetAll + all: GetAll, ) -> Result, ParseError> { let mut result = Vec::new(); for h in all { diff --git a/src/lib.rs b/src/lib.rs index 1a0ac8ad..2efac129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,8 +64,10 @@ #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error ))] -#![cfg_attr(feature = "cargo-clippy", - allow(decimal_literal_representation, suspicious_arithmetic_impl))] +#![cfg_attr( + feature = "cargo-clippy", + allow(decimal_literal_representation, suspicious_arithmetic_impl) +)] #[macro_use] extern crate log; @@ -103,6 +105,7 @@ extern crate serde; #[cfg(feature = "brotli")] extern crate brotli2; extern crate encoding; +#[cfg(feature = "flate2")] extern crate flate2; extern crate h2 as http2; extern crate num_cpus; diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 7c886fe5..ae69ae07 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -6,11 +6,14 @@ use std::{cmp, io, mem}; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{BufMut, Bytes, BytesMut}; -use flate2::Compression; +#[cfg(feature = "flate2")] use flate2::read::GzDecoder; +#[cfg(feature = "flate2")] use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; -use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, - CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; +#[cfg(feature = "flate2")] +use flate2::Compression; +use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, + CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{HttpTryFrom, Method, Version}; use body::{Binary, Body}; @@ -144,7 +147,9 @@ impl PayloadWriter for EncodedPayload { } pub(crate) enum Decoder { + #[cfg(feature = "flate2")] Deflate(Box>), + #[cfg(feature = "flate2")] Gzip(Option>>), #[cfg(feature = "brotli")] Br(Box>), @@ -223,9 +228,11 @@ impl PayloadStream { ContentEncoding::Br => { Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) } + #[cfg(feature = "flate2")] ContentEncoding::Deflate => { Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new()))) } + #[cfg(feature = "flate2")] ContentEncoding::Gzip => Decoder::Gzip(None), _ => Decoder::Identity, }; @@ -251,6 +258,7 @@ impl PayloadStream { } Err(e) => Err(e), }, + #[cfg(feature = "flate2")] Decoder::Gzip(ref mut decoder) => { if let Some(ref mut decoder) = *decoder { decoder.as_mut().get_mut().eof = true; @@ -267,6 +275,7 @@ impl PayloadStream { Ok(None) } } + #[cfg(feature = "flate2")] Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); @@ -297,6 +306,7 @@ impl PayloadStream { } Err(e) => Err(e), }, + #[cfg(feature = "flate2")] Decoder::Gzip(ref mut decoder) => { if decoder.is_none() { *decoder = Some(Box::new(GzDecoder::new(Wrapper { @@ -334,6 +344,7 @@ impl PayloadStream { } } } + #[cfg(feature = "flate2")] Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; @@ -352,7 +363,9 @@ impl PayloadStream { } pub(crate) enum ContentEncoder { + #[cfg(feature = "flate2")] Deflate(DeflateEncoder), + #[cfg(feature = "flate2")] Gzip(GzEncoder), #[cfg(feature = "brotli")] Br(BrotliEncoder), @@ -422,9 +435,11 @@ impl ContentEncoder { let tmp = SharedBytes::default(); let transfer = TransferEncoding::eof(tmp.clone()); let mut enc = match encoding { + #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate( DeflateEncoder::new(transfer, Compression::fast()), ), + #[cfg(feature = "flate2")] ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( transfer, Compression::fast(), @@ -478,10 +493,12 @@ impl ContentEncoder { } match encoding { + #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( transfer, Compression::fast(), )), + #[cfg(feature = "flate2")] ContentEncoding::Gzip => { ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast())) } @@ -494,7 +511,7 @@ impl ContentEncoder { } fn streaming_encoding( - buf: SharedBytes, version: Version, resp: &mut HttpResponse + buf: SharedBytes, version: Version, resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { @@ -563,7 +580,9 @@ impl ContentEncoder { match *self { #[cfg(feature = "brotli")] ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(), + #[cfg(feature = "flate2")] ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(), + #[cfg(feature = "flate2")] ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(), ContentEncoder::Identity(ref encoder) => encoder.is_eof(), } @@ -587,6 +606,7 @@ impl ContentEncoder { } Err(err) => Err(err), }, + #[cfg(feature = "flate2")] ContentEncoder::Gzip(encoder) => match encoder.finish() { Ok(mut writer) => { writer.encode_eof(); @@ -595,6 +615,7 @@ impl ContentEncoder { } Err(err) => Err(err), }, + #[cfg(feature = "flate2")] ContentEncoder::Deflate(encoder) => match encoder.finish() { Ok(mut writer) => { writer.encode_eof(); @@ -625,6 +646,7 @@ impl ContentEncoder { } } } + #[cfg(feature = "flate2")] ContentEncoder::Gzip(ref mut encoder) => { match encoder.write_all(data.as_ref()) { Ok(_) => Ok(()), @@ -634,6 +656,7 @@ impl ContentEncoder { } } } + #[cfg(feature = "flate2")] ContentEncoder::Deflate(ref mut encoder) => { match encoder.write_all(data.as_ref()) { Ok(_) => Ok(()), From fa9edf218013578e0e00ae5550dabcfe47e53436 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Apr 2018 12:25:31 -0700 Subject: [PATCH 0142/1635] prep release --- CHANGES.md | 5 +++++ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f86a05d4..6d4be932 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.5.6 (2018-04-24) + +* Make flate2 crate optional #200 + + ## 0.5.5 (2018-04-24) * Fix panic when Websocket is closed with no error code #191 diff --git a/Cargo.toml b/Cargo.toml index e6c241f8..425ec701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.5.5" +version = "0.5.6" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From c5b9bed478d9c033006cad058e8c22d8a1fc4a7d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 26 Apr 2018 08:01:08 -0700 Subject: [PATCH 0143/1635] update changes --- CHANGES.md | 5 +++++ Cargo.toml | 2 +- MIGRATION-0.4-0.5.md => MIGRATION.md | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) rename MIGRATION-0.4-0.5.md => MIGRATION.md (85%) diff --git a/CHANGES.md b/CHANGES.md index 6d4be932..fb58d0ae 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.6.0 (...) + +* Websocket CloseCode Empty/Status is ambiguous #193 + + ## 0.5.6 (2018-04-24) * Make flate2 crate optional #200 diff --git a/Cargo.toml b/Cargo.toml index 425ec701..e8f70fd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.5.6" +version = "0.6.0-dev" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/MIGRATION-0.4-0.5.md b/MIGRATION.md similarity index 85% rename from MIGRATION-0.4-0.5.md rename to MIGRATION.md index d618e054..63c4989e 100644 --- a/MIGRATION-0.4-0.5.md +++ b/MIGRATION.md @@ -1,4 +1,10 @@ -# Migration from 0.4 to 0.5 +## Migration from 0.5 to 0.6 + +* `ws::Message::Close` now includes optional close reason. + `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. + + +## Migration from 0.4 to 0.5 * `HttpResponseBuilder::body()`, `.finish()`, `.json()` methods return `HttpResponse` instead of `Result` From fd876efa68a8b568a5cb01fe3628919967a60764 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 26 Apr 2018 09:05:07 -0700 Subject: [PATCH 0144/1635] allow to access application state during configuration stage --- src/application.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/application.rs b/src/application.rs index 268b10af..68aa4a57 100644 --- a/src/application.rs +++ b/src/application.rs @@ -200,6 +200,12 @@ where } } + /// Get reference to the application state + pub fn state(&self) -> &S { + let parts = self.parts.as_ref().expect("Use after finish"); + &parts.state + } + /// Set application prefix. /// /// Only requests that match the application's prefix get From 492c0725640387975913f14662a855ac28c74b28 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Fri, 27 Apr 2018 09:49:55 +0200 Subject: [PATCH 0145/1635] Add Content-Disposition to NamedFile (fixes #172) --- src/fs.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index ce0e42d5..19f8f9ee 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -205,6 +205,9 @@ impl Responder for NamedFile { resp.set(header::ContentType(get_mime_type( &ext.to_string_lossy(), ))); + }).if_some(self.path().file_name(), |file_name, resp| { + resp.header("Content-Disposition", + format!("attachment; filename={}", file_name.to_string_lossy())); }); let reader = ChunkedReadFile { size: self.md.len(), @@ -256,12 +259,14 @@ impl Responder for NamedFile { resp.set(header::ContentType(get_mime_type( &ext.to_string_lossy(), ))); + }).if_some(self.path().file_name(), |file_name, resp| { + resp.header("Content-Disposition", + format!("attachment; filename={}", file_name.to_string_lossy())); }).if_some(last_modified, |lm, resp| { resp.set(header::LastModified(lm)); - }) - .if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); + }).if_some(etag, |etag, resp| { + resp.set(header::ETag(etag)); + }); if precondition_failed { return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); @@ -612,7 +617,11 @@ mod tests { assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" - ) + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=Cargo.toml" + ); } #[test] @@ -634,6 +643,10 @@ mod tests { resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=Cargo.toml" + ); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } From a38c3985f6101b60253a3b74e8f27661b6b7be5e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 28 Apr 2018 22:20:32 -0700 Subject: [PATCH 0146/1635] refactor http1 parser --- src/client/parser.rs | 33 +- src/server/h1.rs | 1348 +++++++++++---------------------------- src/server/h1decoder.rs | 487 ++++++++++++++ src/server/mod.rs | 1 + 4 files changed, 896 insertions(+), 973 deletions(-) create mode 100644 src/server/h1decoder.rs diff --git a/src/client/parser.rs b/src/client/parser.rs index 0d4da4c4..f81aed11 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -7,18 +7,18 @@ use std::mem; use error::{ParseError, PayloadError}; -use server::h1::{chunked, Decoder}; +use server::h1decoder::EncodingDecoder; use server::{utils, IoStream}; -use super::ClientResponse; use super::response::ClientMessage; +use super::ClientResponse; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; #[derive(Default)] pub struct HttpResponseParser { - decoder: Option, + decoder: Option, } #[derive(Debug, Fail)] @@ -32,7 +32,7 @@ pub enum HttpResponseParserError { impl HttpResponseParser { pub fn parse( - &mut self, io: &mut T, buf: &mut BytesMut + &mut self, io: &mut T, buf: &mut BytesMut, ) -> Poll where T: IoStream, @@ -75,7 +75,7 @@ impl HttpResponseParser { } pub fn parse_payload( - &mut self, io: &mut T, buf: &mut BytesMut + &mut self, io: &mut T, buf: &mut BytesMut, ) -> Poll, PayloadError> where T: IoStream, @@ -113,8 +113,8 @@ impl HttpResponseParser { } fn parse_message( - buf: &mut BytesMut - ) -> Poll<(ClientResponse, Option), ParseError> { + buf: &mut BytesMut, + ) -> Poll<(ClientResponse, Option), ParseError> { // Parse http message let bytes_ptr = buf.as_ref().as_ptr() as usize; let mut headers: [httparse::Header; MAX_HEADERS] = @@ -160,12 +160,12 @@ impl HttpResponseParser { } let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { - Some(Decoder::eof()) + Some(EncodingDecoder::eof()) } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { // Content-Length if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { - Some(Decoder::length(len)) + Some(EncodingDecoder::length(len)) } else { debug!("illegal Content-Length: {:?}", len); return Err(ParseError::Header); @@ -176,7 +176,7 @@ impl HttpResponseParser { } } else if chunked(&hdrs)? { // Chunked encoding - Some(Decoder::chunked()) + Some(EncodingDecoder::chunked()) } else { None }; @@ -204,3 +204,16 @@ impl HttpResponseParser { } } } + +/// Check if request has chunked transfer encoding +pub 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) + } +} diff --git a/src/server/h1.rs b/src/server/h1.rs index ec0b1938..e411a788 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -4,32 +4,29 @@ use std::collections::VecDeque; use std::net::SocketAddr; use std::rc::Rc; use std::time::Duration; -use std::{self, io}; +use std::{io, mem}; use actix::Arbiter; -use bytes::{Bytes, BytesMut}; +use bytes::{BufMut, BytesMut}; use futures::{Async, Future, Poll}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use httparse; use tokio_core::reactor::Timeout; -use error::{ParseError, PayloadError, ResponseError}; +use error::PayloadError; use httprequest::HttpRequest; use httpresponse::HttpResponse; use payload::{Payload, PayloadStatus, PayloadWriter}; use pipeline::Pipeline; -use uri::Url; use super::encoding::PayloadType; +use super::h1decoder::{DecoderError, H1Decoder, Message}; use super::h1writer::H1Writer; use super::settings::WorkerSettings; +use super::Writer; use super::{HttpHandler, HttpHandlerTask, IoStream}; -use super::{utils, Writer}; -const MAX_BUFFER_SIZE: usize = 131_072; -const MAX_HEADERS: usize = 96; const MAX_PIPELINED_MESSAGES: usize = 16; +const LW_BUFFER_SIZE: usize = 4096; +const HW_BUFFER_SIZE: usize = 32_768; bitflags! { struct Flags: u8 { @@ -37,6 +34,7 @@ bitflags! { const ERROR = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const SHUTDOWN = 0b0000_1000; + const DISCONNECTED = 0b0001_0000; } } @@ -53,8 +51,9 @@ pub(crate) struct Http1 { settings: Rc>, addr: Option, stream: H1Writer, - reader: Reader, - read_buf: BytesMut, + decoder: H1Decoder, + payload: Option, + buf: BytesMut, tasks: VecDeque, keepalive_timer: Option, } @@ -71,29 +70,42 @@ where { pub fn new( settings: Rc>, stream: T, addr: Option, - read_buf: BytesMut, + buf: BytesMut, ) -> Self { let bytes = settings.get_shared_bytes(); Http1 { flags: Flags::KEEPALIVE, stream: H1Writer::new(stream, bytes, Rc::clone(&settings)), - reader: Reader::new(), + decoder: H1Decoder::new(), + payload: None, tasks: VecDeque::new(), keepalive_timer: None, addr, - read_buf, + buf, settings, } } + #[inline] pub fn settings(&self) -> &WorkerSettings { self.settings.as_ref() } + #[inline] pub(crate) fn io(&mut self) -> &mut T { self.stream.get_mut() } + #[inline] + fn can_read(&self) -> bool { + if let Some(ref info) = self.payload { + info.need_read() == PayloadStatus::Read + } else { + true + } + } + + #[inline] pub fn poll(&mut self) -> Poll<(), ()> { // keep-alive timer if let Some(ref mut timer) = self.keepalive_timer { @@ -119,9 +131,13 @@ where } } + self.poll_io(); + loop { - match self.poll_io()? { - Async::Ready(true) => (), + match self.poll_handler()? { + Async::Ready(true) => { + self.poll_io(); + } Async::Ready(false) => { self.flags.insert(Flags::SHUTDOWN); return self.poll(); @@ -131,93 +147,48 @@ where } } - // TODO: refactor - pub fn poll_io(&mut self) -> Poll { - // read incoming data - let need_read = if !self.flags.intersects(Flags::ERROR) - && self.tasks.len() < MAX_PIPELINED_MESSAGES + #[inline] + pub fn poll_io(&mut self) { + // read io from socket + if !self.flags.intersects(Flags::ERROR) + && self.tasks.len() < MAX_PIPELINED_MESSAGES && self.can_read() { - 'outer: loop { - match self.reader.parse( - self.stream.get_mut(), - &mut self.read_buf, - &self.settings, - ) { - Ok(Async::Ready(mut req)) => { - self.flags.insert(Flags::STARTED); - - // set remote addr - req.set_peer_addr(self.addr); - - // stop keepalive timer - self.keepalive_timer.take(); - - // start request processing - for h in self.settings.handlers().iter_mut() { - req = match h.handle(req) { - Ok(pipe) => { - self.tasks.push_back(Entry { - pipe, - flags: EntryFlags::empty(), - }); - continue 'outer; - } - Err(req) => req, - } - } - - self.tasks.push_back(Entry { - pipe: Pipeline::error(HttpResponse::NotFound()), - flags: EntryFlags::empty(), - }); - continue; + match self.read() { + Ok(true) | Err(_) => { + // notify all tasks + self.stream.disconnected(); + for entry in &mut self.tasks { + entry.pipe.disconnected() } - Ok(Async::NotReady) => (), - Err(err) => { - trace!("Parse error: {:?}", err); + // kill keepalive + self.flags.remove(Flags::KEEPALIVE); + self.keepalive_timer.take(); - // notify all tasks - self.stream.disconnected(); - for entry in &mut self.tasks { - entry.pipe.disconnected() - } + // on parse error, stop reading stream but tasks need to be + // completed + self.flags.insert(Flags::ERROR); - // kill keepalive - self.flags.remove(Flags::KEEPALIVE); - self.keepalive_timer.take(); - - // on parse error, stop reading stream but tasks need to be - // completed - self.flags.insert(Flags::ERROR); - - match err { - ReaderError::Disconnect => (), - _ => if self.tasks.is_empty() { - if let ReaderError::Error(err) = err { - self.tasks.push_back(Entry { - pipe: Pipeline::error(err.error_response()), - flags: EntryFlags::empty(), - }); - } - }, - } + if let Some(ref mut payload) = self.payload { + payload.set_error(PayloadError::Incomplete); } } - break; + Ok(false) => { + self.parse(); + } } - false - } else { - true - }; + } + } - let retry = self.reader.need_read() == PayloadStatus::Read; + pub fn poll_handler(&mut self) -> Poll { + let retry = self.can_read(); // check in-flight messages let mut io = false; let mut idx = 0; while idx < self.tasks.len() { - let item = &mut self.tasks[idx]; + let item: &mut Entry = unsafe { mem::transmute(&mut self.tasks[idx]) }; + // only one task can do io operation in http/1 if !io && !item.flags.contains(EntryFlags::EOF) { // io is corrupted, send buffer if item.flags.contains(EntryFlags::ERROR) { @@ -247,7 +218,8 @@ where } // no more IO for this iteration Ok(Async::NotReady) => { - if self.reader.need_read() == PayloadStatus::Read && !retry { + // check if previously read backpressure was enabled + if self.can_read() && !retry { return Ok(Async::Ready(true)); } io = true; @@ -279,20 +251,20 @@ where } // cleanup finished tasks - let mut popped = false; + let max = self.tasks.len() >= MAX_PIPELINED_MESSAGES; while !self.tasks.is_empty() { if self.tasks[0] .flags .contains(EntryFlags::EOF | EntryFlags::FINISHED) { - popped = true; self.tasks.pop_front(); } else { break; } } - if need_read && popped { - return self.poll_io(); + // read more message + if max && self.tasks.len() >= MAX_PIPELINED_MESSAGES { + return Ok(Async::Ready(true)); } // check stream state @@ -332,736 +304,167 @@ where } Ok(Async::NotReady) } -} -struct Reader { - payload: Option, -} + pub fn parse(&mut self) { + 'outer: loop { + match self.decoder.decode(&mut self.buf, &self.settings) { + Ok(Some(Message::Message { msg, payload })) => { + self.flags.insert(Flags::STARTED); -enum Decoding { - Ready, - NotReady, -} + if payload { + let (ps, pl) = Payload::new(false); + msg.get_mut().payload = Some(pl); + self.payload = + Some(PayloadType::new(&msg.get_ref().headers, ps)); + } -struct PayloadInfo { - tx: PayloadType, - decoder: Decoder, -} + let mut req = HttpRequest::from_message(msg); -#[derive(Debug)] -enum ReaderError { - Disconnect, - Payload, - PayloadDropped, - Error(ParseError), -} + // set remote addr + req.set_peer_addr(self.addr); -impl Reader { - pub fn new() -> Reader { - Reader { payload: None } - } + // stop keepalive timer + self.keepalive_timer.take(); - #[inline] - fn need_read(&self) -> PayloadStatus { - if let Some(ref info) = self.payload { - info.tx.need_read() - } else { - PayloadStatus::Read + // search handler for request + for h in self.settings.handlers().iter_mut() { + req = match h.handle(req) { + Ok(pipe) => { + self.tasks.push_back(Entry { + pipe, + flags: EntryFlags::empty(), + }); + continue 'outer; + } + Err(req) => req, + } + } + + // handler is not found + self.tasks.push_back(Entry { + pipe: Pipeline::error(HttpResponse::NotFound()), + flags: EntryFlags::empty(), + }); + } + Ok(Some(Message::Chunk(chunk))) => { + if let Some(ref mut payload) = self.payload { + payload.feed_data(chunk); + } else { + error!("Internal server error: unexpected payload chunk"); + self.flags.insert(Flags::ERROR); + } + } + Ok(Some(Message::Eof)) => { + if let Some(ref mut payload) = self.payload { + payload.feed_eof(); + } else { + error!("Internal server error: unexpected eof"); + self.flags.insert(Flags::ERROR); + } + } + Ok(None) => break, + Err(e) => { + self.flags.insert(Flags::ERROR); + if let Some(ref mut payload) = self.payload { + let e = match e { + DecoderError::Io(e) => PayloadError::Io(e), + DecoderError::Error(_) => PayloadError::EncodingCorrupted, + }; + payload.set_error(e); + } + } + } } } #[inline] - fn decode( - &mut self, buf: &mut BytesMut, payload: &mut PayloadInfo - ) -> Result { - while !buf.is_empty() { - match payload.decoder.decode(buf) { - Ok(Async::Ready(Some(bytes))) => { - payload.tx.feed_data(bytes); - if payload.decoder.is_eof() { - payload.tx.feed_eof(); - return Ok(Decoding::Ready); - } - } - Ok(Async::Ready(None)) => { - payload.tx.feed_eof(); - return Ok(Decoding::Ready); - } - Ok(Async::NotReady) => return Ok(Decoding::NotReady), - Err(err) => { - payload.tx.set_error(err.into()); - return Err(ReaderError::Payload); - } - } - } - Ok(Decoding::NotReady) - } - - pub fn parse( - &mut self, io: &mut T, buf: &mut BytesMut, settings: &WorkerSettings - ) -> Poll - where - T: IoStream, - { - match self.need_read() { - PayloadStatus::Read => (), - PayloadStatus::Pause => return Ok(Async::NotReady), - PayloadStatus::Dropped => return Err(ReaderError::PayloadDropped), - } - - // read payload - let done = { - if let Some(ref mut payload) = self.payload { - 'buf: loop { - let not_ready = match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - payload.tx.set_error(PayloadError::Incomplete); - - // http channel should not deal with payload errors - return Err(ReaderError::Payload); - } - Ok(Async::NotReady) => true, - Err(err) => { - payload.tx.set_error(err.into()); - - // http channel should not deal with payload errors - return Err(ReaderError::Payload); - } - _ => false, - }; - loop { - match payload.decoder.decode(buf) { - Ok(Async::Ready(Some(bytes))) => { - payload.tx.feed_data(bytes); - if payload.decoder.is_eof() { - payload.tx.feed_eof(); - break 'buf true; - } - } - Ok(Async::Ready(None)) => { - payload.tx.feed_eof(); - break 'buf true; - } - Ok(Async::NotReady) => { - // if buffer is full then - // socket still can contain more data - if not_ready { - return Ok(Async::NotReady); - } - continue 'buf; - } - Err(err) => { - payload.tx.set_error(err.into()); - return Err(ReaderError::Payload); - } - } - } - } - } else { - false - } - }; - if done { - self.payload = None - } - - // if buf is empty parse_message will always return NotReady, let's avoid that - if buf.is_empty() { - match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => return Err(ReaderError::Disconnect), - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(ReaderError::Error(err.into())), - } - }; - + fn read(&mut self) -> io::Result { loop { - match Reader::parse_message(buf, settings).map_err(ReaderError::Error)? { - Async::Ready((msg, decoder)) => { - // process payload - if let Some(mut payload) = decoder { - match self.decode(buf, &mut payload)? { - Decoding::Ready => (), - Decoding::NotReady => self.payload = Some(payload), - } - } - return Ok(Async::Ready(msg)); + unsafe { + if self.buf.remaining_mut() < LW_BUFFER_SIZE { + self.buf.reserve(HW_BUFFER_SIZE); } - Async::NotReady => { - if buf.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ReaderError::Error(ParseError::TooLarge)); - } - match utils::read_from_io(io, buf) { - Ok(Async::Ready(0)) => { - debug!("Ignored premature client disconnection"); - return Err(ReaderError::Disconnect); - } - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(ReaderError::Error(err.into())), - } - } - } - } - } - - fn parse_message( - buf: &mut BytesMut, settings: &WorkerSettings - ) -> Poll<(HttpRequest, Option), ParseError> { - // Parse http message - let mut has_upgrade = false; - let mut chunked = false; - let mut content_length = None; - - let msg = { - let bytes_ptr = buf.as_ref().as_ptr() as usize; - let mut headers: [httparse::Header; MAX_HEADERS] = - unsafe { std::mem::uninitialized() }; - - 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::from_bytes(req.method.unwrap().as_bytes()) - .map_err(|_| ParseError::Method)?; - let path = Url::new(Uri::try_from(req.path.unwrap())?); - let version = if req.version.unwrap() == 1 { - Version::HTTP_11 + match self.stream.get_mut().read(self.buf.bytes_mut()) { + Ok(n) => { + if n == 0 { + return Ok(true); } else { - Version::HTTP_10 - }; - (len, method, path, version, req.headers.len()) - } - httparse::Status::Partial => return Ok(Async::NotReady), - } - }; - - let slice = buf.split_to(len).freeze(); - - // convert headers - let msg = settings.get_http_message(); - { - let msg_mut = msg.get_mut(); - msg_mut.keep_alive = version != Version::HTTP_10; - - for header in headers[..headers_len].iter() { - if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) { - has_upgrade = has_upgrade || name == header::UPGRADE; - 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), - ) - }; - match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { - if let Ok(len) = s.parse::() { - content_length = Some(len) - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - }, - // transfer-encoding - header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str() { - chunked = s.to_lowercase().contains("chunked"); - } else { - return Err(ParseError::Header) - } - }, - // connection keep-alive state - header::CONNECTION => { - msg_mut.keep_alive = if let Ok(conn) = value.to_str() { - if version == Version::HTTP_10 - && conn.contains("keep-alive") - { - true - } else { - version == Version::HTTP_11 - && !(conn.contains("close") - || conn.contains("upgrade")) - } - } else { - false - }; - }, - _ => (), + self.buf.advance_mut(n); } - - msg_mut.headers.append(name, value); - } else { - return Err(ParseError::Header); } - } - - msg_mut.url = path; - msg_mut.method = method; - msg_mut.version = version; - } - msg - }; - - // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = if chunked { - // Chunked encoding - Some(Decoder::chunked()) - } else if let Some(len) = content_length { - // Content-Length - Some(Decoder::length(len)) - } else if has_upgrade || msg.get_ref().method == Method::CONNECT { - // upgrade(websocket) or 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_ref().headers, psender), - decoder, - }; - msg.get_mut().payload = Some(payload); - Ok(Async::Ready(( - HttpRequest::from_message(msg), - Some(info), - ))) - } else { - Ok(Async::Ready((HttpRequest::from_message(msg), None))) - } - } -} - -/// Check if request has chunked transfer encoding -pub 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* -/// include a Content-Length header. -#[derive(Debug, Clone, PartialEq)] -pub struct Decoder { - kind: Kind, -} - -impl Decoder { - pub fn length(x: u64) -> Decoder { - Decoder { - kind: Kind::Length(x), - } - } - - pub fn chunked() -> Decoder { - Decoder { - kind: Kind::Chunked(ChunkedState::Size, 0), - } - } - - pub fn eof() -> Decoder { - Decoder { - kind: Kind::Eof(false), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -enum Kind { - /// A Reader used when a Content-Length header is passed with a positive - /// integer. - Length(u64), - /// A Reader used when Transfer-Encoding is `chunked`. - Chunked(ChunkedState, u64), - /// A Reader used for responses that don't indicate a length or chunked. - /// - /// Note: This should only used for `Response`s. It is illegal for a - /// `Request` to be made with both `Content-Length` and - /// `Transfer-Encoding: chunked` missing, as explained from the spec: - /// - /// > If a Transfer-Encoding header field is present in a response and - /// > the chunked transfer coding is not the final encoding, the - /// > message body length is determined by reading the connection until - /// > it is closed by the server. If a Transfer-Encoding header field - /// > is present in a request and the chunked transfer coding is not - /// > the final encoding, the message body length cannot be determined - /// > reliably; the server MUST respond with the 400 (Bad Request) - /// > status code and then close the connection. - Eof(bool), -} - -#[derive(Debug, PartialEq, Clone)] -enum ChunkedState { - Size, - SizeLws, - Extension, - SizeLf, - Body, - BodyCr, - BodyLf, - EndCr, - EndLf, - End, -} - -impl Decoder { - pub fn is_eof(&self) -> bool { - match self.kind { - Kind::Length(0) | Kind::Chunked(ChunkedState::End, _) | Kind::Eof(true) => { - true - } - _ => false, - } - } - - pub fn decode(&mut self, body: &mut BytesMut) -> Poll, io::Error> { - match self.kind { - Kind::Length(ref mut remaining) => { - if *remaining == 0 { - Ok(Async::Ready(None)) - } else { - if body.is_empty() { - return Ok(Async::NotReady); - } - let len = body.len() as u64; - let buf; - if *remaining > len { - buf = body.take().freeze(); - *remaining -= len; - } else { - buf = body.split_to(*remaining as usize).freeze(); - *remaining = 0; - } - trace!("Length read: {}", buf.len()); - Ok(Async::Ready(Some(buf))) - } - } - Kind::Chunked(ref mut state, ref mut size) => { - loop { - let mut buf = None; - // advances the chunked state - *state = try_ready!(state.step(body, size, &mut buf)); - if *state == ChunkedState::End { - trace!("End of chunked stream"); - return Ok(Async::Ready(None)); - } - if let Some(buf) = buf { - return Ok(Async::Ready(Some(buf))); - } - if body.is_empty() { - return Ok(Async::NotReady); + Err(e) => { + return if e.kind() == io::ErrorKind::WouldBlock { + Ok(false) + } else { + Err(e) + }; } } } - Kind::Eof(ref mut is_eof) => { - if *is_eof { - Ok(Async::Ready(None)) - } else if !body.is_empty() { - Ok(Async::Ready(Some(body.take().freeze()))) - } else { - Ok(Async::NotReady) - } - } - } - } -} - -macro_rules! byte ( - ($rdr:ident) => ({ - if $rdr.len() > 0 { - let b = $rdr[0]; - $rdr.split_to(1); - b - } else { - return Ok(Async::NotReady) - } - }) -); - -impl ChunkedState { - fn step( - &self, body: &mut BytesMut, size: &mut u64, buf: &mut Option - ) -> Poll { - use self::ChunkedState::*; - match *self { - Size => ChunkedState::read_size(body, size), - SizeLws => ChunkedState::read_size_lws(body), - Extension => ChunkedState::read_extension(body), - SizeLf => ChunkedState::read_size_lf(body, size), - Body => ChunkedState::read_body(body, size, buf), - BodyCr => ChunkedState::read_body_cr(body), - BodyLf => ChunkedState::read_body_lf(body), - EndCr => ChunkedState::read_end_cr(body), - EndLf => ChunkedState::read_end_lf(body), - End => Ok(Async::Ready(ChunkedState::End)), - } - } - fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll { - let radix = 16; - match byte!(rdr) { - b @ b'0'...b'9' => { - *size *= radix; - *size += u64::from(b - b'0'); - } - b @ b'a'...b'f' => { - *size *= radix; - *size += u64::from(b + 10 - b'a'); - } - b @ b'A'...b'F' => { - *size *= radix; - *size += u64::from(b + 10 - b'A'); - } - b'\t' | b' ' => return Ok(Async::Ready(ChunkedState::SizeLws)), - b';' => return Ok(Async::Ready(ChunkedState::Extension)), - b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)), - _ => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size line: Invalid Size", - )); - } - } - Ok(Async::Ready(ChunkedState::Size)) - } - fn read_size_lws(rdr: &mut BytesMut) -> Poll { - trace!("read_size_lws"); - match byte!(rdr) { - // LWS can follow the chunk size, but no more digits can come - b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)), - b';' => Ok(Async::Ready(ChunkedState::Extension)), - b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size linear white space", - )), - } - } - fn read_extension(rdr: &mut BytesMut) -> Poll { - 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 { - match byte!(rdr) { - b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), - b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size LF", - )), - } - } - - fn read_body( - rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option - ) -> Poll { - trace!("Chunked read, remaining={:?}", rem); - - let len = rdr.len() as u64; - if len == 0 { - Ok(Async::Ready(ChunkedState::Body)) - } else { - let slice; - if *rem > len { - slice = rdr.take().freeze(); - *rem -= len; - } else { - slice = rdr.split_to(*rem as usize).freeze(); - *rem = 0; - } - *buf = Some(slice); - if *rem > 0 { - Ok(Async::Ready(ChunkedState::Body)) - } else { - Ok(Async::Ready(ChunkedState::BodyCr)) - } - } - } - - fn read_body_cr(rdr: &mut BytesMut) -> Poll { - match byte!(rdr) { - b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body CR", - )), - } - } - fn read_body_lf(rdr: &mut BytesMut) -> Poll { - match byte!(rdr) { - b'\n' => Ok(Async::Ready(ChunkedState::Size)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body LF", - )), - } - } - fn read_end_cr(rdr: &mut BytesMut) -> Poll { - match byte!(rdr) { - b'\r' => Ok(Async::Ready(ChunkedState::EndLf)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end CR", - )), - } - } - fn read_end_lf(rdr: &mut BytesMut) -> Poll { - match byte!(rdr) { - b'\n' => Ok(Async::Ready(ChunkedState::End)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end LF", - )), } } } #[cfg(test)] mod tests { - use bytes::{Buf, Bytes, BytesMut}; - use futures::{Async, Stream}; + use bytes::{Bytes, BytesMut}; use http::{Method, Version}; - use std::net::Shutdown; - use std::{cmp, io, time}; - use tokio_io::{AsyncRead, AsyncWrite}; use super::*; use application::HttpApplication; use httpmessage::HttpMessage; + use server::h1decoder::Message; + use server::helpers::SharedHttpInnerMessage; use server::settings::WorkerSettings; - use server::{IoStream, KeepAlive}; + use server::KeepAlive; - struct Buffer { - buf: Bytes, - err: Option, - } - - impl Buffer { - fn new(data: &'static str) -> Buffer { - Buffer { - buf: Bytes::from(data), - err: None, + impl Message { + fn message(self) -> SharedHttpInnerMessage { + match self { + Message::Message { msg, payload: _ } => msg, + _ => panic!("error"), } } - fn feed_data(&mut self, data: &'static str) { - let mut b = BytesMut::from(self.buf.as_ref()); - b.extend(data.as_bytes()); - self.buf = b.take().freeze(); - } - } - - impl AsyncRead for Buffer {} - impl io::Read for Buffer { - fn read(&mut self, dst: &mut [u8]) -> Result { - if self.buf.is_empty() { - if self.err.is_some() { - Err(self.err.take().unwrap()) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - let size = cmp::min(self.buf.len(), dst.len()); - let b = self.buf.split_to(size); - dst[..size].copy_from_slice(&b); - Ok(size) + fn is_payload(&self) -> bool { + match *self { + Message::Message { msg: _, payload } => payload, + _ => panic!("error"), } } - } - - impl IoStream for Buffer { - fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { - Ok(()) - } - fn set_nodelay(&mut self, _: bool) -> io::Result<()> { - Ok(()) - } - fn set_linger(&mut 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) => (), - Err(err) => unreachable!("Unexpected error: {:?}", err), - _ => unreachable!("Should not be ready"), + fn chunk(self) -> Bytes { + match self { + Message::Chunk(chunk) => chunk, + _ => panic!("error"), } - }; + } + fn eof(&self) -> bool { + match *self { + Message::Eof => true, + _ => false, + } + } } macro_rules! parse_ready { ($e:expr) => {{ let settings: WorkerSettings = WorkerSettings::new(Vec::new(), KeepAlive::Os); - match Reader::new().parse($e, &mut BytesMut::new(), &settings) { - Ok(Async::Ready(req)) => req, + match H1Decoder::new().decode($e, &settings) { + Ok(Some(msg)) => HttpRequest::from_message(msg.message()), Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), } }}; } - macro_rules! reader_parse_ready { - ($e:expr) => { - match $e { - Ok(Async::Ready(req)) => req, - Ok(_) => unreachable!("Eof during parsing http request"), - Err(err) => { - unreachable!("Error during parsing http request: {:?}", err) - } - } - }; - } - macro_rules! expect_parse_err { ($e:expr) => {{ - let mut buf = BytesMut::new(); let settings: WorkerSettings = WorkerSettings::new(Vec::new(), KeepAlive::Os); - match Reader::new().parse($e, &mut buf, &settings) { + match H1Decoder::new().decode($e, &settings) { Err(err) => match err { - ReaderError::Error(_) => (), + DecoderError::Error(_) => (), _ => unreachable!("Parse error expected"), }, _ => unreachable!("Error expected"), @@ -1071,13 +474,13 @@ mod tests { #[test] fn test_parse() { - let mut buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n"); - let mut readbuf = BytesMut::new(); + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); - let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::Ready(req)) => { + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf, &settings) { + Ok(Some(msg)) => { + let req = HttpRequest::from_message(msg.message()); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -1088,19 +491,19 @@ mod tests { #[test] fn test_parse_partial() { - let mut buf = Buffer::new("PUT /test HTTP/1"); - let mut readbuf = BytesMut::new(); + let mut buf = BytesMut::from("PUT /test HTTP/1"); let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); - let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::NotReady) => (), + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf, &settings) { + Ok(None) => (), _ => unreachable!("Error"), } - buf.feed_data(".1\r\n\r\n"); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::Ready(req)) => { + buf.extend(b".1\r\n\r\n"); + match reader.decode(&mut buf, &settings) { + Ok(Some(msg)) => { + let mut req = HttpRequest::from_message(msg.message()); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::PUT); assert_eq!(req.path(), "/test"); @@ -1111,13 +514,13 @@ mod tests { #[test] fn test_parse_post() { - let mut buf = Buffer::new("POST /test2 HTTP/1.0\r\n\r\n"); - let mut readbuf = BytesMut::new(); + let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); - let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::Ready(req)) => { + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf, &settings) { + Ok(Some(msg)) => { + let mut req = HttpRequest::from_message(msg.message()); assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); assert_eq!(req.path(), "/test2"); @@ -1128,17 +531,26 @@ mod tests { #[test] 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 mut buf = + BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); - let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::Ready(mut req)) => { + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf, &settings) { + Ok(Some(msg)) => { + let mut req = HttpRequest::from_message(msg.message()); 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!( + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk() + .as_ref(), + b"body" + ); } Ok(_) | Err(_) => unreachable!("Error during parsing http request"), } @@ -1147,17 +559,25 @@ mod tests { #[test] fn test_parse_body_crlf() { let mut buf = - Buffer::new("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut readbuf = BytesMut::new(); + BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); - let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::Ready(mut req)) => { + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf, &settings) { + Ok(Some(msg)) => { + let mut req = HttpRequest::from_message(msg.message()); 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!( + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk() + .as_ref(), + b"body" + ); } Ok(_) | Err(_) => unreachable!("Error during parsing http request"), } @@ -1165,16 +585,15 @@ mod tests { #[test] fn test_parse_partial_eof() { - let mut buf = Buffer::new("GET /test HTTP/1.1\r\n"); - let mut readbuf = BytesMut::new(); + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let mut reader = H1Decoder::new(); + assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - let mut reader = Reader::new(); - not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } - - buf.feed_data("\r\n"); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::Ready(req)) => { + buf.extend(b"\r\n"); + match reader.decode(&mut buf, &settings) { + Ok(Some(msg)) => { + let req = HttpRequest::from_message(msg.message()); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -1185,22 +604,22 @@ mod tests { #[test] fn test_headers_split_field() { - let mut buf = Buffer::new("GET /test HTTP/1.1\r\n"); - let mut readbuf = BytesMut::new(); + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); - let mut reader = Reader::new(); - not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } + let mut reader = H1Decoder::new(); + assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } - buf.feed_data("t"); - not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } + buf.extend(b"t"); + assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } - buf.feed_data("es"); - not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) } + buf.extend(b"es"); + assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } - buf.feed_data("t: value\r\n\r\n"); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::Ready(req)) => { + buf.extend(b"t: value\r\n\r\n"); + match reader.decode(&mut buf, &settings) { + Ok(Some(msg)) => { + let req = HttpRequest::from_message(msg.message()); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -1215,32 +634,28 @@ mod tests { #[test] fn test_headers_multi_value() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ 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(), KeepAlive::Os); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let req = HttpRequest::from_message(msg.message()); - let mut reader = Reader::new(); - match reader.parse(&mut buf, &mut readbuf, &settings) { - Ok(Async::Ready(req)) => { - let val: Vec<_> = req.headers() - .get_all("Set-Cookie") - .iter() - .map(|v| v.to_str().unwrap().to_owned()) - .collect(); - assert_eq!(val[0], "c1=cookie1"); - assert_eq!(val[1], "c2=cookie2"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let val: Vec<_> = req.headers() + .get_all("Set-Cookie") + .iter() + .map(|v| v.to_str().unwrap().to_owned()) + .collect(); + assert_eq!(val[0], "c1=cookie1"); + assert_eq!(val[1], "c2=cookie2"); } #[test] fn test_conn_default_1_0() { - let mut buf = Buffer::new("GET /test HTTP/1.0\r\n\r\n"); + let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); let req = parse_ready!(&mut buf); assert!(!req.keep_alive()); @@ -1248,7 +663,7 @@ mod tests { #[test] fn test_conn_default_1_1() { - let mut buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n"); + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); let req = parse_ready!(&mut buf); assert!(req.keep_alive()); @@ -1256,7 +671,7 @@ mod tests { #[test] fn test_conn_close() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: close\r\n\r\n", ); @@ -1267,7 +682,7 @@ mod tests { #[test] fn test_conn_close_1_0() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: close\r\n\r\n", ); @@ -1278,7 +693,7 @@ mod tests { #[test] fn test_conn_keep_alive_1_0() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: keep-alive\r\n\r\n", ); @@ -1289,7 +704,7 @@ mod tests { #[test] fn test_conn_keep_alive_1_1() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: keep-alive\r\n\r\n", ); @@ -1300,7 +715,7 @@ mod tests { #[test] fn test_conn_other_1_0() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: other\r\n\r\n", ); @@ -1311,7 +726,7 @@ mod tests { #[test] fn test_conn_other_1_1() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: other\r\n\r\n", ); @@ -1322,32 +737,30 @@ mod tests { #[test] fn test_conn_upgrade() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ upgrade: websockets\r\n\ connection: upgrade\r\n\r\n", ); let req = parse_ready!(&mut buf); - assert!(!req.payload().eof()); assert!(req.upgrade()); } #[test] fn test_conn_upgrade_connect_method() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "CONNECT /test HTTP/1.1\r\n\ content-type: text/plain\r\n\r\n", ); let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert!(!req.payload().eof()); } #[test] fn test_request_chunked() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); @@ -1360,7 +773,7 @@ mod tests { } // type in chunked - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ transfer-encoding: chnked\r\n\r\n", ); @@ -1375,7 +788,7 @@ mod tests { #[test] fn test_headers_content_length_err_1() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ content-length: line\r\n\r\n", ); @@ -1385,7 +798,7 @@ mod tests { #[test] fn test_headers_content_length_err_2() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ content-length: -1\r\n\r\n", ); @@ -1395,7 +808,7 @@ mod tests { #[test] fn test_invalid_header() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ test line\r\n\r\n", ); @@ -1405,7 +818,7 @@ mod tests { #[test] fn test_invalid_name() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ test[]: line\r\n\r\n", ); @@ -1415,30 +828,39 @@ mod tests { #[test] fn test_http_request_bad_status_line() { - let mut buf = Buffer::new("getpath \r\n\r\n"); + let mut buf = BytesMut::from("getpath \r\n\r\n"); expect_parse_err!(&mut buf); } #[test] fn test_http_request_upgrade() { - let mut buf = Buffer::new( + let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: upgrade\r\n\ upgrade: websocket\r\n\r\n\ some raw data", ); - let mut req = parse_ready!(&mut buf); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = HttpRequest::from_message(msg.message()); assert!(!req.keep_alive()); assert!(req.upgrade()); assert_eq!( - req.payload_mut().readall().unwrap().as_ref(), + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk() + .as_ref(), b"some raw data" ); } #[test] fn test_http_request_parser_utf8() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ x-test: теÑÑ‚\r\n\r\n", ); @@ -1452,7 +874,7 @@ mod tests { #[test] fn test_http_request_parser_two_slashes() { - let mut buf = Buffer::new("GET //path HTTP/1.1\r\n\r\n"); + let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); let req = parse_ready!(&mut buf); assert_eq!(req.path(), "//path"); @@ -1460,175 +882,175 @@ mod tests { #[test] fn test_http_request_parser_bad_method() { - let mut buf = Buffer::new("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); + let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); expect_parse_err!(&mut buf); } #[test] fn test_http_request_parser_bad_version() { - let mut buf = Buffer::new("GET //get HT/11\r\n\r\n"); + let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); expect_parse_err!(&mut buf); } #[test] fn test_http_request_chunked_payload() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "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(), KeepAlive::Os); - let mut reader = Reader::new(); - let mut req = - reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = HttpRequest::from_message(msg.message()); assert!(req.chunked().unwrap()); - assert!(!req.payload().eof()); - buf.feed_data("4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); - let _ = req.payload_mut().poll(); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - assert!(!req.payload().eof()); + buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); assert_eq!( - req.payload_mut().readall().unwrap().as_ref(), - b"dataline" + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk() + .as_ref(), + b"data" + ); + assert_eq!( + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk() + .as_ref(), + b"line" + ); + assert!( + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .eof() ); - assert!(req.payload().eof()); } #[test] fn test_http_request_chunked_payload_and_next_message() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "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(), KeepAlive::Os); - - let mut reader = Reader::new(); - - let mut req = - reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = HttpRequest::from_message(msg.message()); assert!(req.chunked().unwrap()); - assert!(!req.payload().eof()); - buf.feed_data( - "4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ - POST /test2 HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", + buf.extend( + b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ + POST /test2 HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n" + .iter(), ); - let _ = req.payload_mut().poll(); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"line"); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert!(msg.eof()); - let req2 = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert!(msg.is_payload()); + let req2 = HttpRequest::from_message(msg.message()); + assert!(req2.chunked().unwrap()); assert_eq!(*req2.method(), Method::POST); assert!(req2.chunked().unwrap()); - assert!(!req2.payload().eof()); - - assert_eq!( - req.payload_mut().readall().unwrap().as_ref(), - b"dataline" - ); - assert!(req.payload().eof()); } #[test] fn test_http_request_chunked_payload_chunks() { - let mut buf = Buffer::new( + let mut buf = BytesMut::from( "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(), KeepAlive::Os); - let mut reader = Reader::new(); - let mut req = - reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - req.payload_mut().set_read_buffer_capacity(0); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = HttpRequest::from_message(msg.message()); assert!(req.chunked().unwrap()); - assert!(!req.payload().eof()); - buf.feed_data("4\r\n1111\r\n"); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - assert_eq!(req.payload_mut().readall().unwrap().as_ref(), b"1111"); + buf.extend(b"4\r\n1111\r\n"); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"1111"); - buf.feed_data("4\r\ndata\r"); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + buf.extend(b"4\r\ndata\r"); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); - buf.feed_data("\n4"); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + buf.extend(b"\n4"); + assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - buf.feed_data("\r"); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - buf.feed_data("\n"); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + buf.extend(b"\r"); + assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + buf.extend(b"\n"); + assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - buf.feed_data("li"); - 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, &settings)); + buf.extend(b"li"); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"li"); //trailers //buf.feed_data("test: test\r\n"); //not_ready!(reader.parse(&mut buf, &mut readbuf)); - let _ = req.payload_mut().poll(); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + buf.extend(b"ne\r\n0\r\n"); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"ne"); + assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - assert_eq!( - req.payload_mut().readall().unwrap().as_ref(), - b"dataline" + buf.extend(b"\r\n"); + assert!( + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .eof() ); - assert!(!req.payload().eof()); - - buf.feed_data("\r\n"); - let _ = req.payload_mut().poll(); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - assert!(req.payload().eof()); } #[test] fn test_parse_chunked_payload_chunk_extension() { - let mut buf = Buffer::new( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", + let mut buf = BytesMut::from( + &"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(), KeepAlive::Os); - let mut reader = Reader::new(); - let mut req = - reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = HttpRequest::from_message(msg.message()); assert!(req.chunked().unwrap()); - assert!(!req.payload().eof()); - buf.feed_data("4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let _ = req.payload_mut().poll(); - not_ready!(reader.parse(&mut buf, &mut readbuf, &settings)); - assert!(!req.payload().eof()); - assert_eq!( - req.payload_mut().readall().unwrap().as_ref(), - b"dataline" - ); - assert!(req.payload().eof()); + buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") + let chunk = reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk(); + assert_eq!(chunk, Bytes::from_static(b"data")); + let chunk = reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk(); + assert_eq!(chunk, Bytes::from_static(b"line")); + let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + assert!(msg.eof()); } - - /*#[test] - #[should_panic] - fn test_parse_multiline() { - let mut buf = Buffer::new( - "GET /test HTTP/1.1\r\n\ - test: line\r\n \ - continue\r\n\ - test2: data\r\n\ - \r\n", false); - - let mut reader = Reader::new(); - match reader.parse(&mut buf) { - Ok(res) => (), - Err(err) => unreachable!("{:?}", err), - } - }*/ } diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs new file mode 100644 index 00000000..d610afc6 --- /dev/null +++ b/src/server/h1decoder.rs @@ -0,0 +1,487 @@ +use std::{io, mem}; + +use bytes::{Bytes, BytesMut}; +use futures::{Async, Poll}; +use httparse; + +use super::helpers::SharedHttpInnerMessage; +use super::settings::WorkerSettings; +use error::ParseError; +use http::header::{HeaderName, HeaderValue}; +use http::{header, HttpTryFrom, Method, Uri, Version}; +use uri::Url; + +const MAX_BUFFER_SIZE: usize = 131_072; +const MAX_HEADERS: usize = 96; + +pub(crate) struct H1Decoder { + decoder: Option, +} + +pub(crate) enum Message { + Message { + msg: SharedHttpInnerMessage, + payload: bool, + }, + Chunk(Bytes), + Eof, +} + +#[derive(Debug)] +pub(crate) enum DecoderError { + Io(io::Error), + Error(ParseError), +} + +impl From for DecoderError { + fn from(err: io::Error) -> DecoderError { + DecoderError::Io(err) + } +} + +impl H1Decoder { + pub fn new() -> H1Decoder { + H1Decoder { decoder: None } + } + + pub fn decode( + &mut self, src: &mut BytesMut, settings: &WorkerSettings, + ) -> Result, DecoderError> { + // read payload + if self.decoder.is_some() { + match self.decoder.as_mut().unwrap().decode(src)? { + Async::Ready(Some(bytes)) => return Ok(Some(Message::Chunk(bytes))), + Async::Ready(None) => { + self.decoder.take(); + return Ok(Some(Message::Eof)); + } + Async::NotReady => return Ok(None), + } + } + + match self.parse_message(src, settings) + .map_err(DecoderError::Error)? + { + Async::Ready((msg, decoder)) => { + if let Some(decoder) = decoder { + self.decoder = Some(decoder); + Ok(Some(Message::Message { + msg, + payload: true, + })) + } else { + Ok(Some(Message::Message { + msg, + payload: false, + })) + } + } + Async::NotReady => { + if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + Err(DecoderError::Error(ParseError::TooLarge)) + } else { + Ok(None) + } + } + } + } + + fn parse_message( + &self, buf: &mut BytesMut, settings: &WorkerSettings, + ) -> Poll<(SharedHttpInnerMessage, Option), ParseError> { + // Parse http message + let mut has_upgrade = false; + let mut chunked = false; + let mut content_length = None; + + let msg = { + let bytes_ptr = buf.as_ref().as_ptr() as usize; + let mut headers: [httparse::Header; MAX_HEADERS] = + unsafe { mem::uninitialized() }; + + let (len, method, path, version, headers_len) = { + let b = unsafe { + let b: &[u8] = buf; + mem::transmute(b) + }; + let mut req = httparse::Request::new(&mut headers); + match req.parse(b)? { + httparse::Status::Complete(len) => { + let method = Method::from_bytes(req.method.unwrap().as_bytes()) + .map_err(|_| ParseError::Method)?; + let path = Url::new(Uri::try_from(req.path.unwrap())?); + 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(Async::NotReady), + } + }; + + let slice = buf.split_to(len).freeze(); + + // convert headers + let msg = settings.get_http_message(); + { + let msg_mut = msg.get_mut(); + msg_mut.keep_alive = version != Version::HTTP_10; + + for header in headers[..headers_len].iter() { + if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) { + has_upgrade = has_upgrade || name == header::UPGRADE; + + 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), + ) + }; + match name { + header::CONTENT_LENGTH => { + if let Ok(s) = value.to_str() { + if let Ok(len) = s.parse::() { + content_length = Some(len) + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header); + } + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header); + } + } + // transfer-encoding + header::TRANSFER_ENCODING => { + if let Ok(s) = value.to_str() { + chunked = s.to_lowercase().contains("chunked"); + } else { + return Err(ParseError::Header); + } + } + // connection keep-alive state + header::CONNECTION => { + msg_mut.keep_alive = if let Ok(conn) = value.to_str() { + if version == Version::HTTP_10 + && conn.contains("keep-alive") + { + true + } else { + version == Version::HTTP_11 + && !(conn.contains("close") + || conn.contains("upgrade")) + } + } else { + false + }; + } + _ => (), + } + + msg_mut.headers.append(name, value); + } else { + return Err(ParseError::Header); + } + } + + msg_mut.url = path; + msg_mut.method = method; + msg_mut.version = version; + } + msg + }; + + // https://tools.ietf.org/html/rfc7230#section-3.3.3 + let decoder = if chunked { + // Chunked encoding + Some(EncodingDecoder::chunked()) + } else if let Some(len) = content_length { + // Content-Length + Some(EncodingDecoder::length(len)) + } else if has_upgrade || msg.get_ref().method == Method::CONNECT { + // upgrade(websocket) or connect + Some(EncodingDecoder::eof()) + } else { + None + }; + + Ok(Async::Ready((msg, decoder))) + } +} + +/// Decoders to handle different Transfer-Encodings. +/// +/// If a message body does not include a Transfer-Encoding, it *should* +/// include a Content-Length header. +#[derive(Debug, Clone, PartialEq)] +pub struct EncodingDecoder { + kind: Kind, +} + +impl EncodingDecoder { + pub fn length(x: u64) -> EncodingDecoder { + EncodingDecoder { + kind: Kind::Length(x), + } + } + + pub fn chunked() -> EncodingDecoder { + EncodingDecoder { + kind: Kind::Chunked(ChunkedState::Size, 0), + } + } + + pub fn eof() -> EncodingDecoder { + EncodingDecoder { + kind: Kind::Eof(false), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +enum Kind { + /// A Reader used when a Content-Length header is passed with a positive + /// integer. + Length(u64), + /// A Reader used when Transfer-Encoding is `chunked`. + Chunked(ChunkedState, u64), + /// A Reader used for responses that don't indicate a length or chunked. + /// + /// Note: This should only used for `Response`s. It is illegal for a + /// `Request` to be made with both `Content-Length` and + /// `Transfer-Encoding: chunked` missing, as explained from the spec: + /// + /// > If a Transfer-Encoding header field is present in a response and + /// > the chunked transfer coding is not the final encoding, the + /// > message body length is determined by reading the connection until + /// > it is closed by the server. If a Transfer-Encoding header field + /// > is present in a request and the chunked transfer coding is not + /// > the final encoding, the message body length cannot be determined + /// > reliably; the server MUST respond with the 400 (Bad Request) + /// > status code and then close the connection. + Eof(bool), +} + +#[derive(Debug, PartialEq, Clone)] +enum ChunkedState { + Size, + SizeLws, + Extension, + SizeLf, + Body, + BodyCr, + BodyLf, + EndCr, + EndLf, + End, +} + +impl EncodingDecoder { + pub fn decode(&mut self, body: &mut BytesMut) -> Poll, io::Error> { + match self.kind { + Kind::Length(ref mut remaining) => { + if *remaining == 0 { + Ok(Async::Ready(None)) + } else { + if body.is_empty() { + return Ok(Async::NotReady); + } + let len = body.len() as u64; + let buf; + if *remaining > len { + buf = body.take().freeze(); + *remaining -= len; + } else { + buf = body.split_to(*remaining as usize).freeze(); + *remaining = 0; + } + trace!("Length read: {}", buf.len()); + Ok(Async::Ready(Some(buf))) + } + } + Kind::Chunked(ref mut state, ref mut size) => { + loop { + let mut buf = None; + // advances the chunked state + *state = try_ready!(state.step(body, size, &mut buf)); + if *state == ChunkedState::End { + trace!("End of chunked stream"); + return Ok(Async::Ready(None)); + } + if let Some(buf) = buf { + return Ok(Async::Ready(Some(buf))); + } + if body.is_empty() { + return Ok(Async::NotReady); + } + } + } + Kind::Eof(ref mut is_eof) => { + if *is_eof { + Ok(Async::Ready(None)) + } else if !body.is_empty() { + Ok(Async::Ready(Some(body.take().freeze()))) + } else { + Ok(Async::NotReady) + } + } + } + } +} + +macro_rules! byte ( + ($rdr:ident) => ({ + if $rdr.len() > 0 { + let b = $rdr[0]; + $rdr.split_to(1); + b + } else { + return Ok(Async::NotReady) + } + }) +); + +impl ChunkedState { + fn step( + &self, body: &mut BytesMut, size: &mut u64, buf: &mut Option, + ) -> Poll { + use self::ChunkedState::*; + match *self { + Size => ChunkedState::read_size(body, size), + SizeLws => ChunkedState::read_size_lws(body), + Extension => ChunkedState::read_extension(body), + SizeLf => ChunkedState::read_size_lf(body, size), + Body => ChunkedState::read_body(body, size, buf), + BodyCr => ChunkedState::read_body_cr(body), + BodyLf => ChunkedState::read_body_lf(body), + EndCr => ChunkedState::read_end_cr(body), + EndLf => ChunkedState::read_end_lf(body), + End => Ok(Async::Ready(ChunkedState::End)), + } + } + fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll { + let radix = 16; + match byte!(rdr) { + b @ b'0'...b'9' => { + *size *= radix; + *size += u64::from(b - b'0'); + } + b @ b'a'...b'f' => { + *size *= radix; + *size += u64::from(b + 10 - b'a'); + } + b @ b'A'...b'F' => { + *size *= radix; + *size += u64::from(b + 10 - b'A'); + } + b'\t' | b' ' => return Ok(Async::Ready(ChunkedState::SizeLws)), + b';' => return Ok(Async::Ready(ChunkedState::Extension)), + b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)), + _ => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size line: Invalid Size", + )); + } + } + Ok(Async::Ready(ChunkedState::Size)) + } + fn read_size_lws(rdr: &mut BytesMut) -> Poll { + trace!("read_size_lws"); + match byte!(rdr) { + // LWS can follow the chunk size, but no more digits can come + b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)), + b';' => Ok(Async::Ready(ChunkedState::Extension)), + b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size linear white space", + )), + } + } + fn read_extension(rdr: &mut BytesMut) -> Poll { + 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 { + match byte!(rdr) { + b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), + b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size LF", + )), + } + } + + fn read_body( + rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option, + ) -> Poll { + trace!("Chunked read, remaining={:?}", rem); + + let len = rdr.len() as u64; + if len == 0 { + Ok(Async::Ready(ChunkedState::Body)) + } else { + let slice; + if *rem > len { + slice = rdr.take().freeze(); + *rem -= len; + } else { + slice = rdr.split_to(*rem as usize).freeze(); + *rem = 0; + } + *buf = Some(slice); + if *rem > 0 { + Ok(Async::Ready(ChunkedState::Body)) + } else { + Ok(Async::Ready(ChunkedState::BodyCr)) + } + } + } + + fn read_body_cr(rdr: &mut BytesMut) -> Poll { + match byte!(rdr) { + b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk body CR", + )), + } + } + fn read_body_lf(rdr: &mut BytesMut) -> Poll { + match byte!(rdr) { + b'\n' => Ok(Async::Ready(ChunkedState::Size)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk body LF", + )), + } + } + fn read_end_cr(rdr: &mut BytesMut) -> Poll { + match byte!(rdr) { + b'\r' => Ok(Async::Ready(ChunkedState::EndLf)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk end CR", + )), + } + } + fn read_end_lf(rdr: &mut BytesMut) -> Poll { + match byte!(rdr) { + b'\n' => Ok(Async::Ready(ChunkedState::End)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk end LF", + )), + } + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 85faf77b..36d80e2d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -10,6 +10,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; mod channel; pub(crate) mod encoding; pub(crate) mod h1; +pub(crate) mod h1decoder; mod h1writer; mod h2; mod h2writer; From de49796fd191d755b63c1818524ca2e762b62399 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 28 Apr 2018 22:55:47 -0700 Subject: [PATCH 0147/1635] clippy warnings; fmt --- rustfmt.toml | 6 +- src/application.rs | 131 +++++---------------- src/body.rs | 4 +- src/client/connector.rs | 37 +++--- src/client/pipeline.rs | 14 +-- src/client/request.rs | 21 +--- src/client/response.rs | 21 ++-- src/client/writer.rs | 17 +-- src/context.rs | 9 +- src/de.rs | 49 ++++---- src/error.rs | 16 +-- src/extractor.rs | 73 ++++-------- src/fs.rs | 110 +++++------------- src/handler.rs | 17 ++- src/header/shared/entity.rs | 31 ++--- src/header/shared/httpdate.rs | 24 +--- src/header/shared/quality_item.rs | 27 ++--- src/helpers.rs | 186 +++++------------------------- src/httpcodes.rs | 161 ++++++++++++++------------ src/httpmessage.rs | 45 +++----- src/httprequest.rs | 41 ++----- src/httpresponse.rs | 115 +++++------------- src/info.rs | 18 ++- src/json.rs | 55 +++------ src/middleware/cors.rs | 126 +++++++------------- src/middleware/csrf.rs | 10 +- src/middleware/defaultheaders.rs | 6 +- src/middleware/errhandlers.rs | 4 +- src/middleware/identity.rs | 24 ++-- src/middleware/logger.rs | 27 ++--- src/middleware/mod.rs | 7 +- src/middleware/session.rs | 23 ++-- src/multipart.rs | 25 ++-- src/param.rs | 18 +-- src/payload.rs | 14 +-- src/pipeline.rs | 55 +++++---- src/pred.rs | 8 +- src/resource.rs | 11 +- src/route.rs | 25 ++-- src/router.rs | 88 +++++--------- src/server/channel.rs | 41 ++++--- src/server/encoding.rs | 13 +-- src/server/h1.rs | 77 ++++--------- src/server/h1decoder.rs | 45 ++++---- src/server/h1writer.rs | 21 ++-- src/server/h2.rs | 2 +- src/server/h2writer.rs | 9 +- src/server/helpers.rs | 59 +++------- src/server/settings.rs | 11 +- src/server/shared.rs | 4 +- src/server/srv.rs | 75 ++++++------ src/server/utils.rs | 2 +- src/server/worker.rs | 98 ++++++++-------- src/test.rs | 27 +++-- src/uri.rs | 5 +- src/with.rs | 36 +++--- src/ws/client.rs | 33 ++---- src/ws/context.rs | 14 +-- src/ws/frame.rs | 25 ++-- src/ws/mask.rs | 6 +- src/ws/mod.rs | 121 ++++++------------- src/ws/proto.rs | 6 +- tests/test_client.rs | 45 ++------ tests/test_handlers.rs | 60 +++------- tests/test_server.rs | 153 +++++++++--------------- tests/test_ws.rs | 57 +++------ tools/wsload/src/wsclient.rs | 110 ++++++++---------- 67 files changed, 988 insertions(+), 1866 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 98d2ba7d..97c6a5aa 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,7 +1,7 @@ max_width = 89 reorder_imports = true -reorder_imports_in_group = true -reorder_imported_names = true +#reorder_imports_in_group = true +#reorder_imported_names = true wrap_comments = true fn_args_density = "Compressed" -#use_small_heuristics = false +use_small_heuristics = false diff --git a/src/application.rs b/src/application.rs index 68aa4a57..33c4453c 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,6 +1,5 @@ use std::cell::UnsafeCell; use std::collections::HashMap; -use std::mem; use std::rc::Rc; use handler::Reply; @@ -74,7 +73,7 @@ impl HttpApplication { if m { let path: &'static str = unsafe { - mem::transmute(&req.path()[inner.prefix + prefix.len()..]) + &*(&req.path()[inner.prefix + prefix.len()..] as *const _) }; if path.is_empty() { req.match_info_mut().add("tail", ""); @@ -112,12 +111,7 @@ impl HttpHandler for HttpApplication { let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); let tp = self.get_handler(&mut req); let inner = Rc::clone(&self.inner); - Ok(Box::new(Pipeline::new( - req, - Rc::clone(&self.middlewares), - inner, - tp, - ))) + Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner, tp))) } else { Err(req) } @@ -280,7 +274,7 @@ where { { let parts: &mut ApplicationParts = unsafe { - mem::transmute(self.parts.as_mut().expect("Use after finish")) + &mut *(self.parts.as_mut().expect("Use after finish") as *mut _) }; // get resource handler @@ -455,20 +449,14 @@ where } let parts = self.parts.as_mut().expect("Use after finish"); - parts - .handlers - .push((path, Box::new(WrapHandler::new(handler)))); + parts.handlers.push((path, Box::new(WrapHandler::new(handler)))); } self } /// Register a middleware. pub fn middleware>(mut self, mw: M) -> App { - self.parts - .as_mut() - .expect("Use after finish") - .middlewares - .push(Box::new(mw)); + self.parts.as_mut().expect("Use after finish").middlewares.push(Box::new(mw)); self } @@ -623,9 +611,8 @@ mod tests { #[test] fn test_default_resource() { - let mut app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); + let mut app = + App::new().resource("/test", |r| r.f(|_| HttpResponse::Ok())).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -633,20 +620,14 @@ mod tests { let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); let mut app = App::new() .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::METHOD_NOT_ALLOWED - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::METHOD_NOT_ALLOWED); } #[test] @@ -660,9 +641,8 @@ mod tests { #[test] fn test_state() { - let mut app = App::with_state(10) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .finish(); + let mut app = + App::with_state(10).resource("/", |r| r.f(|_| HttpResponse::Ok())).finish(); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); let resp = app.run(req); @@ -694,9 +674,7 @@ mod tests { #[test] fn test_handler() { - let mut app = App::new() - .handler("/test", |_| HttpResponse::Ok()) - .finish(); + let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -712,24 +690,16 @@ mod tests { let req = TestRequest::with_uri("/testapp").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + 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 - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); } #[test] fn test_handler2() { - let mut app = App::new() - .handler("test", |_| HttpResponse::Ok()) - .finish(); + let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -745,17 +715,11 @@ mod tests { let req = TestRequest::with_uri("/testapp").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + 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 - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); } #[test] @@ -779,68 +743,41 @@ mod tests { let req = TestRequest::with_uri("/prefix/testapp").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/prefix/blah").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); } #[test] fn test_route() { let mut app = App::new() - .route("/test", Method::GET, |_: HttpRequest| { - HttpResponse::Ok() - }) - .route("/test", Method::POST, |_: HttpRequest| { - HttpResponse::Created() - }) + .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) + .route("/test", Method::POST, |_: HttpRequest| HttpResponse::Created()) .finish(); - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::GET).finish(); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::POST).finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::CREATED - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); } #[test] fn test_handler_prefix() { - let mut app = App::new() - .prefix("/app") - .handler("/test", |_| HttpResponse::Ok()) - .finish(); + let mut app = + App::new().prefix("/app").handler("/test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/test").finish(); let resp = app.run(req); @@ -856,16 +793,10 @@ mod tests { let req = TestRequest::with_uri("/app/testapp").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + 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 - ); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); } } diff --git a/src/body.rs b/src/body.rs index cf54361d..6974fc7a 100644 --- a/src/body.rs +++ b/src/body.rs @@ -258,9 +258,7 @@ impl Responder for Binary { type Error = Error; fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok() - .content_type("application/octet-stream") - .body(self)) + Ok(HttpResponse::Ok().content_type("application/octet-stream").body(self)) } } diff --git a/src/client/connector.rs b/src/client/connector.rs index e9eb0813..07ecca93 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -94,13 +94,17 @@ pub struct Pause { impl Pause { /// Create message with pause duration parameter pub fn new(time: Duration) -> Pause { - Pause { time: Some(time) } + Pause { + time: Some(time), + } } } impl Default for Pause { fn default() -> Pause { - Pause { time: None } + Pause { + time: None, + } } } @@ -427,8 +431,7 @@ impl ClientConnector { } else { 0 }; - self.acquired_per_host - .insert(key.clone(), per_host + 1); + self.acquired_per_host.insert(key.clone(), per_host + 1); } fn release_key(&mut self, key: &Key) { @@ -439,8 +442,7 @@ impl ClientConnector { return; }; if per_host > 1 { - self.acquired_per_host - .insert(key.clone(), per_host - 1); + self.acquired_per_host.insert(key.clone(), per_host - 1); } else { self.acquired_per_host.remove(key); } @@ -516,9 +518,7 @@ impl ClientConnector { fn collect_periodic(&mut self, ctx: &mut Context) { self.collect(true); // re-schedule next collect period - ctx.run_later(Duration::from_secs(1), |act, ctx| { - act.collect_periodic(ctx) - }); + ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); // send stats let stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); @@ -570,7 +570,7 @@ impl ClientConnector { } fn wait_for( - &mut self, key: Key, wait: Duration, conn_timeout: Duration + &mut self, key: Key, wait: Duration, conn_timeout: Duration, ) -> oneshot::Receiver> { // connection is not available, wait let (tx, rx) = oneshot::channel(); @@ -583,10 +583,7 @@ impl ClientConnector { wait, conn_timeout, }; - self.waiters - .entry(key) - .or_insert_with(VecDeque::new) - .push_back(waiter); + self.waiters.entry(key).or_insert_with(VecDeque::new).push_back(waiter); rx } } @@ -810,7 +807,7 @@ impl fut::ActorFuture for Maintenance { type Actor = ClientConnector; fn poll( - &mut self, act: &mut ClientConnector, ctx: &mut Context + &mut self, act: &mut ClientConnector, ctx: &mut Context, ) -> Poll { // check pause duration let done = if let Some(Some(ref pause)) = act.paused { @@ -1105,10 +1102,7 @@ impl Pool { if self.to_close.borrow().is_empty() { None } else { - Some(mem::replace( - &mut *self.to_close.borrow_mut(), - Vec::new(), - )) + Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new())) } } @@ -1116,10 +1110,7 @@ impl Pool { if self.to_release.borrow().is_empty() { None } else { - Some(mem::replace( - &mut *self.to_release.borrow_mut(), - Vec::new(), - )) + Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new())) } } diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 1db68b43..a9468805 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -18,9 +18,9 @@ use error::Error; use error::PayloadError; use header::ContentEncoding; use httpmessage::HttpMessage; -use server::WriterState; use server::encoding::PayloadStream; use server::shared::SharedBytes; +use server::WriterState; /// A set of errors that can occur during request sending and response reading #[derive(Fail, Debug)] @@ -80,7 +80,7 @@ impl SendRequest { } pub(crate) fn with_connector( - req: ClientRequest, conn: Addr + req: ClientRequest, conn: Addr, ) -> SendRequest { SendRequest { req, @@ -269,11 +269,7 @@ impl Pipeline { #[inline] fn parse(&mut self) -> Poll { if let Some(ref mut conn) = self.conn { - match self.parser - .as_mut() - .unwrap() - .parse(conn, &mut self.parser_buf) - { + match self.parser.as_mut().unwrap().parse(conn, &mut self.parser_buf) { Ok(Async::Ready(resp)) => { // check content-encoding if self.should_decompress { @@ -469,9 +465,7 @@ impl Pipeline { } // flush io but only if we need to - match self.writer - .poll_completed(self.conn.as_mut().unwrap(), false) - { + match self.writer.poll_completed(self.conn.as_mut().unwrap(), false) { Ok(Async::Ready(_)) => { if self.disconnected || (self.body_completed && self.writer.is_completed()) diff --git a/src/client/request.rs b/src/client/request.rs index 526a8d99..c32b0fad 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -499,10 +499,7 @@ impl ClientRequestBuilder { jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { - self.cookies - .as_mut() - .unwrap() - .add(cookie.into_owned()); + self.cookies.as_mut().unwrap().add(cookie.into_owned()); } self } @@ -594,11 +591,7 @@ impl ClientRequestBuilder { if self.default_headers { // enable br only for https let https = if let Some(parts) = parts(&mut self.request, &self.err) { - parts - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true) + parts.uri.scheme_part().map(|s| s == &uri::Scheme::HTTPS).unwrap_or(true) } else { true }; @@ -610,9 +603,7 @@ impl ClientRequestBuilder { } } - let mut request = self.request - .take() - .expect("cannot reuse request builder"); + let mut request = self.request.take().expect("cannot reuse request builder"); // set cookies if let Some(ref mut jar) = self.cookies { @@ -657,9 +648,7 @@ impl ClientRequestBuilder { S: Stream + 'static, E: Into, { - self.body(Body::Streaming(Box::new( - stream.map_err(|e| e.into()), - ))) + self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) } /// Set an empty body and generate `ClientRequest` @@ -682,7 +671,7 @@ impl ClientRequestBuilder { #[inline] fn parts<'a>( - parts: &'a mut Option, err: &Option + parts: &'a mut Option, err: &Option, ) -> Option<&'a mut ClientRequest> { if err.is_some() { return None; diff --git a/src/client/response.rs b/src/client/response.rs index 4d186d19..f76d058e 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -103,12 +103,7 @@ impl ClientResponse { impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!( - f, - "\nClientResponse {:?} {}", - self.version(), - self.status() - ); + let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status()); let _ = writeln!(f, " headers:"); for (key, val) in self.headers().iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); @@ -138,14 +133,12 @@ mod tests { #[test] fn test_debug() { let resp = ClientResponse::new(ClientMessage::default()); - resp.as_mut().headers.insert( - header::COOKIE, - HeaderValue::from_static("cookie1=value1"), - ); - resp.as_mut().headers.insert( - header::COOKIE, - HeaderValue::from_static("cookie2=value2"), - ); + resp.as_mut() + .headers + .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1")); + resp.as_mut() + .headers + .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2")); let dbg = format!("{:?}", resp); assert!(dbg.contains("ClientResponse")); diff --git a/src/client/writer.rs b/src/client/writer.rs index 36c9d6ee..adcc454e 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -114,10 +114,7 @@ impl HttpClientWriter { self.buffer, "{} {} {:?}\r", msg.method(), - msg.uri() - .path_and_query() - .map(|u| u.as_str()) - .unwrap_or("/"), + msg.uri().path_and_query().map(|u| u.as_str()).unwrap_or("/"), msg.version() )?; @@ -253,10 +250,8 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder } let mut b = BytesMut::new(); let _ = write!(b, "{}", bytes.len()); - req.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(b.freeze()).unwrap(), - ); + req.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); TransferEncoding::eof(buf) } Body::Streaming(_) | Body::Actor(_) => { @@ -279,10 +274,8 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder }; if encoding.is_compression() { - req.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); + req.headers_mut() + .insert(CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); } req.replace_body(body); diff --git a/src/context.rs b/src/context.rs index b095c29b..933fed50 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,7 +3,6 @@ use futures::unsync::oneshot; use futures::{Async, Future, Poll}; use smallvec::SmallVec; use std::marker::PhantomData; -use std::mem; use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::fut::ActorFuture; @@ -174,7 +173,9 @@ where if self.stream.is_none() { self.stream = Some(SmallVec::new()); } - self.stream.as_mut().map(|s| s.push(frame)); + if let Some(s) = self.stream.as_mut() { + s.push(frame) + } self.inner.modify(); } @@ -199,7 +200,7 @@ where fn poll(&mut self) -> Poll>, Error> { let ctx: &mut HttpContext = - unsafe { mem::transmute(self as &mut HttpContext) }; + unsafe { &mut *(self as &mut HttpContext as *mut _) }; if self.inner.alive() { match self.inner.poll(ctx) { @@ -261,7 +262,7 @@ impl ActorFuture for Drain { #[inline] fn poll( - &mut self, _: &mut A, _: &mut ::Context + &mut self, _: &mut A, _: &mut ::Context, ) -> Poll { self.fut.poll().map_err(|_| ()) } diff --git a/src/de.rs b/src/de.rs index 47a3f4ff..fa81b77e 100644 --- a/src/de.rs +++ b/src/de.rs @@ -41,7 +41,9 @@ pub struct PathDeserializer<'de, S: 'de> { impl<'de, S: 'de> PathDeserializer<'de, S> { pub fn new(req: &'de HttpRequest) -> Self { - PathDeserializer { req } + PathDeserializer { + req, + } } } @@ -59,7 +61,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], visitor: V + self, _: &'static str, _: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, @@ -75,7 +77,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } fn deserialize_unit_struct( - self, _: &'static str, visitor: V + self, _: &'static str, visitor: V, ) -> Result where V: Visitor<'de>, @@ -84,7 +86,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } fn deserialize_newtype_struct( - self, _: &'static str, visitor: V + self, _: &'static str, visitor: V, ) -> Result where V: Visitor<'de>, @@ -93,7 +95,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } fn deserialize_tuple( - self, len: usize, visitor: V + self, len: usize, visitor: V, ) -> Result where V: Visitor<'de>, @@ -114,7 +116,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } fn deserialize_tuple_struct( - self, _: &'static str, len: usize, visitor: V + self, _: &'static str, len: usize, visitor: V, ) -> Result where V: Visitor<'de>, @@ -135,7 +137,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], _: V + self, _: &'static str, _: &'static [&'static str], _: V, ) -> Result where V: Visitor<'de>, @@ -202,11 +204,12 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { where K: de::DeserializeSeed<'de>, { - self.current = self.params - .next() - .map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); + self.current = + self.params.next().map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); match self.current { - Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), + Some((key, _)) => Ok(Some(seed.deserialize(Key { + key, + })?)), None => Ok(None), } } @@ -216,7 +219,9 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { V: de::DeserializeSeed<'de>, { if let Some((_, value)) = self.current.take() { - seed.deserialize(Value { value }) + seed.deserialize(Value { + value, + }) } else { Err(de::value::Error::custom("unexpected item")) } @@ -301,7 +306,7 @@ impl<'de> Deserializer<'de> for Value<'de> { } fn deserialize_unit_struct( - self, _: &'static str, visitor: V + self, _: &'static str, visitor: V, ) -> Result where V: Visitor<'de>, @@ -331,7 +336,7 @@ impl<'de> Deserializer<'de> for Value<'de> { } fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], visitor: V + self, _: &'static str, _: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, @@ -342,7 +347,7 @@ impl<'de> Deserializer<'de> for Value<'de> { } fn deserialize_newtype_struct( - self, _: &'static str, visitor: V + self, _: &'static str, visitor: V, ) -> Result where V: Visitor<'de>, @@ -358,7 +363,7 @@ impl<'de> Deserializer<'de> for Value<'de> { } fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], _: V + self, _: &'static str, _: &'static [&'static str], _: V, ) -> Result where V: Visitor<'de>, @@ -367,14 +372,12 @@ impl<'de> Deserializer<'de> for Value<'de> { } fn deserialize_tuple_struct( - self, _: &'static str, _: usize, _: V + self, _: &'static str, _: usize, _: V, ) -> Result where V: Visitor<'de>, { - Err(de::value::Error::custom( - "unsupported type: tuple struct", - )) + Err(de::value::Error::custom("unsupported type: tuple struct")) } unsupported_type!(deserialize_any, "any"); @@ -416,7 +419,9 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { V: de::DeserializeSeed<'de>, { Ok(( - seed.deserialize(Key { key: self.value })?, + seed.deserialize(Key { + key: self.value, + })?, UnitVariant, )) } @@ -446,7 +451,7 @@ impl<'de> de::VariantAccess<'de> for UnitVariant { } fn struct_variant( - self, _: &'static [&'static str], _: V + self, _: &'static [&'static str], _: V, ) -> Result where V: Visitor<'de>, diff --git a/src/error.rs b/src/error.rs index 5f660c48..2fa4ba2e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -68,12 +68,7 @@ impl fmt::Debug for Error { if let Some(bt) = self.cause.backtrace() { write!(f, "{:?}\n\n{:?}", &self.cause, bt) } else { - write!( - f, - "{:?}\n\n{:?}", - &self.cause, - self.backtrace.as_ref().unwrap() - ) + write!(f, "{:?}\n\n{:?}", &self.cause, self.backtrace.as_ref().unwrap()) } } } @@ -298,17 +293,16 @@ pub enum HttpRangeError { /// 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` - #[fail(display = "First-byte-pos of all of the byte-range-spec values is greater than the content size")] + #[fail( + display = "First-byte-pos of all of the byte-range-spec values is greater than the content size" + )] NoOverlap, } /// Return `BadRequest` for `HttpRangeError` impl ResponseError for HttpRangeError { fn error_response(&self) -> HttpResponse { - HttpResponse::with_body( - StatusCode::BAD_REQUEST, - "Invalid Range header provided", - ) + HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided") } } diff --git a/src/extractor.rs b/src/extractor.rs index 1aef7ac5..7dd59038 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -110,7 +110,9 @@ where result( de::Deserialize::deserialize(PathDeserializer::new(&req)) .map_err(|e| e.into()) - .map(|inner| Path { inner }), + .map(|inner| Path { + inner, + }), ) } } @@ -246,12 +248,7 @@ where #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new( - UrlEncoded::new(req.clone()) - .limit(cfg.limit) - .from_err() - .map(Form), - ) + Box::new(UrlEncoded::new(req.clone()).limit(cfg.limit).from_err().map(Form)) } } @@ -296,7 +293,9 @@ impl FormConfig { impl Default for FormConfig { fn default() -> Self { - FormConfig { limit: 262_144 } + FormConfig { + limit: 262_144, + } } } @@ -337,11 +336,7 @@ impl FromRequest for Bytes { return Either::A(result(Err(e))); } - Either::B(Box::new( - MessageBody::new(req.clone()) - .limit(cfg.limit) - .from_err(), - )) + Either::B(Box::new(MessageBody::new(req.clone()).limit(cfg.limit).from_err())) } } @@ -387,18 +382,14 @@ impl FromRequest for String { // check charset let encoding = match req.encoding() { Err(_) => { - return Either::A(result(Err(ErrorBadRequest( - "Unknown request charset", - )))) + return Either::A(result(Err(ErrorBadRequest("Unknown request charset")))) } Ok(encoding) => encoding, }; Either::B(Box::new( - MessageBody::new(req.clone()) - .limit(cfg.limit) - .from_err() - .and_then(move |body| { + MessageBody::new(req.clone()).limit(cfg.limit).from_err().and_then( + move |body| { let enc: *const Encoding = encoding as *const Encoding; if enc == UTF_8 { Ok(str::from_utf8(body.as_ref()) @@ -409,7 +400,8 @@ impl FromRequest for String { .decode(&body, DecoderTrap::Strict) .map_err(|_| ErrorBadRequest("Can not decode body"))?) } - }), + }, + ), )) } } @@ -485,8 +477,7 @@ mod tests { fn test_bytes() { let cfg = PayloadConfig::default(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); match Bytes::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { @@ -500,8 +491,7 @@ mod tests { fn test_string() { let cfg = PayloadConfig::default(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); match String::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { @@ -518,8 +508,7 @@ mod tests { "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); let mut cfg = FormConfig::default(); cfg.limit(4096); @@ -573,17 +562,11 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push(( - Resource::new("index", "/{key}/{value}/"), - Some(resource), - )); + routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req, &()) - .poll() - .unwrap() - { + match Path::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); @@ -591,10 +574,7 @@ mod tests { _ => unreachable!(), } - match Path::<(String, String)>::from_request(&req, &()) - .poll() - .unwrap() - { + match Path::<(String, String)>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); @@ -620,10 +600,7 @@ mod tests { _ => unreachable!(), } - match Path::<(String, u8)>::from_request(&req, &()) - .poll() - .unwrap() - { + match Path::<(String, u8)>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, 32); @@ -631,15 +608,9 @@ mod tests { _ => unreachable!(), } - match Path::>::from_request(&req, &()) - .poll() - .unwrap() - { + match Path::>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { - assert_eq!( - s.into_inner(), - vec!["name".to_owned(), "32".to_owned()] - ); + assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]); } _ => unreachable!(), } diff --git a/src/fs.rs b/src/fs.rs index ce0e42d5..700b69ed 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -202,15 +202,12 @@ impl Responder for NamedFile { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type( - &ext.to_string_lossy(), - ))); + resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); }); let reader = ChunkedReadFile { size: self.md.len(), offset: 0, - cpu_pool: self.cpu_pool - .unwrap_or_else(|| req.cpu_pool().clone()), + cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, }; @@ -253,9 +250,7 @@ impl Responder for NamedFile { let mut resp = HttpResponse::build(self.status_code); resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type( - &ext.to_string_lossy(), - ))); + resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); }).if_some(last_modified, |lm, resp| { resp.set(header::LastModified(lm)); }) @@ -275,8 +270,7 @@ impl Responder for NamedFile { let reader = ChunkedReadFile { size: self.md.len(), offset: 0, - cpu_pool: self.cpu_pool - .unwrap_or_else(|| req.cpu_pool().clone()), + cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, }; @@ -344,7 +338,10 @@ pub struct Directory { impl Directory { pub fn new(base: PathBuf, path: PathBuf) -> Directory { - Directory { base, path } + Directory { + base, + path, + } } fn can_list(&self, entry: &io::Result) -> bool { @@ -414,9 +411,7 @@ impl Responder for Directory { \n", index_of, index_of, body ); - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) + Ok(HttpResponse::Ok().content_type("text/html; charset=utf-8").body(html)) } } @@ -541,13 +536,12 @@ impl Handler for StaticFiles { if !self.accessible { Ok(self.default.handle(req)) } else { - let relpath = match req.match_info() - .get("tail") - .map(|tail| PathBuf::from_param(tail)) - { - Some(Ok(path)) => path, - _ => return Ok(self.default.handle(req)), - }; + let relpath = + match req.match_info().get("tail").map(|tail| PathBuf::from_param(tail)) + { + Some(Ok(path)) => path, + _ => return Ok(self.default.handle(req)), + }; // full filepath let path = self.directory.join(&relpath).canonicalize()?; @@ -597,9 +591,8 @@ mod tests { #[test] fn test_named_file() { assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); + let mut file = + NamedFile::open("Cargo.toml").unwrap().set_cpu_pool(CpuPool::new(1)); { file.file(); let _f: &File = &file; @@ -609,10 +602,7 @@ mod tests { } let resp = file.respond_to(HttpRequest::default()).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ) + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml") } #[test] @@ -630,10 +620,7 @@ mod tests { } let resp = file.respond_to(HttpRequest::default()).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml"); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } @@ -676,9 +663,7 @@ mod tests { req.match_info_mut().add("tail", ""); st.show_index = true; - let resp = st.handle(req) - .respond_to(HttpRequest::default()) - .unwrap(); + let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -694,28 +679,18 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tests"); - let resp = st.handle(req) - .respond_to(HttpRequest::default()) - .unwrap(); + let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" - ); + assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/tests/index.html"); let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tests/"); - let resp = st.handle(req) - .respond_to(HttpRequest::default()) - .unwrap(); + let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" - ); + assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/tests/index.html"); } #[test] @@ -724,9 +699,7 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tools/wsload"); - let resp = st.handle(req) - .respond_to(HttpRequest::default()) - .unwrap(); + let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( @@ -746,23 +719,13 @@ mod tests { let request = srv.get().uri(srv.url("/public")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); + let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); assert_eq!(loc, "/public/Cargo.toml"); let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); + let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); assert_eq!(loc, "/public/Cargo.toml"); } @@ -775,23 +738,13 @@ mod tests { let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); + let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); assert_eq!(loc, "/test/Cargo.toml"); let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); + let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); assert_eq!(loc, "/test/Cargo.toml"); } @@ -801,10 +754,7 @@ mod tests { App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) }); - let request = srv.get() - .uri(srv.url("/test/%43argo.toml")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/test/%43argo.toml")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::OK); } diff --git a/src/handler.rs b/src/handler.rs index 6da1f886..854f3a11 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,5 +1,5 @@ -use futures::Poll; use futures::future::{err, ok, Future, FutureResult}; +use futures::Poll; use std::marker::PhantomData; use std::ops::Deref; @@ -296,14 +296,13 @@ where #[inline] 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), - }); + 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)) } } diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs index 347c4c02..c80bb182 100644 --- a/src/header/shared/entity.rs +++ b/src/header/shared/entity.rs @@ -57,7 +57,10 @@ impl EntityTag { /// If the tag contains invalid characters. pub fn new(weak: bool, tag: String) -> EntityTag { assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - EntityTag { weak, tag } + EntityTag { + weak, + tag, + } } /// Constructs a new weak EntityTag. @@ -196,11 +199,7 @@ mod tests { fn test_etag_parse_failures() { // Expected failures assert!("no-dquotes".parse::().is_err()); - assert!( - "w/\"the-first-w-is-case-sensitive\"" - .parse::() - .is_err() - ); + assert!("w/\"the-first-w-is-case-sensitive\"".parse::().is_err()); assert!("".parse::().is_err()); assert!("\"unmatched-dquotes1".parse::().is_err()); assert!("unmatched-dquotes2\"".parse::().is_err()); @@ -209,26 +208,14 @@ mod tests { #[test] fn test_etag_fmt() { - assert_eq!( - format!("{}", EntityTag::strong("foobar".to_owned())), - "\"foobar\"" - ); - assert_eq!( - format!("{}", EntityTag::strong("".to_owned())), - "\"\"" - ); + assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\""); + assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); assert_eq!( format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\"" ); - assert_eq!( - format!("{}", EntityTag::weak("\u{0065}".to_owned())), - "W/\"\x65\"" - ); - assert_eq!( - format!("{}", EntityTag::weak("".to_owned())), - "W/\"\"" - ); + assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\""); + assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); } #[test] diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs index 5de1e3f9..a6309ae0 100644 --- a/src/header/shared/httpdate.rs +++ b/src/header/shared/httpdate.rs @@ -64,11 +64,7 @@ impl IntoHeaderValue for HttpDate { fn try_into(self) -> Result { let mut wrt = BytesMut::with_capacity(29).writer(); write!(wrt, "{}", self.0.rfc822()).unwrap(); - unsafe { - Ok(HeaderValue::from_shared_unchecked( - wrt.get_mut().take().freeze(), - )) - } + unsafe { Ok(HeaderValue::from_shared_unchecked(wrt.get_mut().take().freeze())) } } } @@ -104,24 +100,12 @@ mod tests { #[test] fn test_date() { + assert_eq!("Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), NOV_07); assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT" - .parse::() - .unwrap(), - NOV_07 - ); - assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" - .parse::() - .unwrap(), - NOV_07 - ); - assert_eq!( - "Sun Nov 7 08:48:37 1994" - .parse::() - .unwrap(), + "Sunday, 07-Nov-94 08:48:37 GMT".parse::().unwrap(), NOV_07 ); + assert_eq!("Sun Nov 7 08:48:37 1994".parse::().unwrap(), NOV_07); assert!("this-is-no-date".parse::().is_err()); } } diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs index 5f1e5977..1734db45 100644 --- a/src/header/shared/quality_item.rs +++ b/src/header/shared/quality_item.rs @@ -47,7 +47,10 @@ impl QualityItem { /// The item can be of any type. /// The quality should be a value in the range [0, 1]. pub fn new(item: T, quality: Quality) -> QualityItem { - QualityItem { item, quality } + QualityItem { + item, + quality, + } } } @@ -63,11 +66,7 @@ impl fmt::Display for QualityItem { match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), - x => write!( - f, - "; q=0.{}", - format!("{:03}", x).trim_right_matches('0') - ), + x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), } } } @@ -120,10 +119,7 @@ fn from_f32(f: f32) -> Quality { // this function is only used internally. A check that `f` is within range // should be done before calling this method. Just in case, this // debug_assert should catch if we were forgetful - debug_assert!( - f >= 0f32 && f <= 1f32, - "q value must be between 0.0 and 1.0" - ); + debug_assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0"); Quality((f * 1000f32) as u16) } @@ -156,10 +152,7 @@ mod internal { impl IntoQuality for f32 { fn into_quality(self) -> Quality { - assert!( - self >= 0f32 && self <= 1f32, - "float must be between 0.0 and 1.0" - ); + assert!(self >= 0f32 && self <= 1f32, "float must be between 0.0 and 1.0"); super::from_f32(self) } } @@ -295,10 +288,6 @@ mod tests { #[test] fn test_fuzzing_bugs() { assert!("99999;".parse::>().is_err()); - assert!( - "\x0d;;;=\u{d6aa}==" - .parse::>() - .is_err() - ) + assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) } } diff --git a/src/helpers.rs b/src/helpers.rs index fda28f38..dc392d7a 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -190,16 +190,8 @@ mod tests { // trailing slashes let params = vec![ ("/resource1", "", StatusCode::OK), - ( - "/resource1/", - "/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2", - "/resource2/", - StatusCode::MOVED_PERMANENTLY, - ), + ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), + ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), ("/resource2/", "", StatusCode::OK), ("/resource1?p1=1&p2=2", "", StatusCode::OK), ( @@ -222,11 +214,7 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap() + r.headers().get(header::LOCATION).unwrap().to_str().unwrap() ); } } @@ -238,11 +226,7 @@ mod tests { .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, - )) + r.h(NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY)) }) .finish(); @@ -276,46 +260,14 @@ mod tests { // trailing slashes let params = vec![ ("/resource1/a/b", "", StatusCode::OK), - ( - "/resource1/", - "/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource1//", - "/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b//", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), + ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), + ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), ("/resource1/a/b?p=1", "", StatusCode::OK), ( "//resource1//a//b?p=1", @@ -356,11 +308,7 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap() + r.headers().get(header::LOCATION).unwrap().to_str().unwrap() ); } } @@ -379,88 +327,24 @@ mod tests { // trailing slashes let params = vec![ ("/resource1/a/b", "", StatusCode::OK), - ( - "/resource1/a/b/", - "/resource1/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", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2/a/b", - "/resource2/a/b/", - StatusCode::MOVED_PERMANENTLY, - ), + ("/resource1/a/b/", "/resource1/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", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/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, - ), + ("//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", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), + ("/resource1/a/b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), ( "//resource2//a//b?p=1", "/resource2/a/b/?p=1", @@ -496,11 +380,7 @@ mod tests { "/resource1/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", @@ -540,11 +420,7 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap() + r.headers().get(header::LOCATION).unwrap().to_str().unwrap() ); } } diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 058d1d2f..6ad9a28c 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -14,32 +14,37 @@ pub const HttpOk: StaticResponse = StaticResponse(StatusCode::OK); pub const HttpCreated: StaticResponse = StaticResponse(StatusCode::CREATED); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::Accepted()` instead")] pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::pNonAuthoritativeInformation()` instead")] +#[deprecated( + since = "0.5.0", + note = "please use `HttpResponse::pNonAuthoritativeInformation()` instead" +)] pub const HttpNonAuthoritativeInformation: StaticResponse = StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::NoContent()` instead")] pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::ResetContent()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::ResetContent()` instead")] pub const HttpResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::PartialContent()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::PartialContent()` instead" +)] pub const HttpPartialContent: StaticResponse = StaticResponse(StatusCode::PARTIAL_CONTENT); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::MultiStatus()` instead")] pub const HttpMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::AlreadyReported()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::AlreadyReported()` instead" +)] pub const HttpAlreadyReported: StaticResponse = StaticResponse(StatusCode::ALREADY_REPORTED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::MultipleChoices()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::MultipleChoices()` instead" +)] pub const HttpMultipleChoices: StaticResponse = StaticResponse(StatusCode::MULTIPLE_CHOICES); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::MovedPermanently()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::MovedPermanently()` instead" +)] pub const HttpMovedPermanently: StaticResponse = StaticResponse(StatusCode::MOVED_PERMANENTLY); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::Found()` instead")] @@ -50,106 +55,125 @@ pub const HttpSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER); pub const HttpNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::UseProxy()` instead")] pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::TemporaryRedirect()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::TemporaryRedirect()` instead" +)] pub const HttpTemporaryRedirect: StaticResponse = StaticResponse(StatusCode::TEMPORARY_REDIRECT); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::PermanentRedirect()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::PermanentRedirect()` instead" +)] pub const HttpPermanentRedirect: StaticResponse = StaticResponse(StatusCode::PERMANENT_REDIRECT); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadRequest()` instead")] pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::Unauthorized()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Unauthorized()` instead")] pub const HttpUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::PaymentRequired()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::PaymentRequired()` instead" +)] pub const HttpPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::Forbidden()` instead")] pub const HttpForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotFound()` instead")] pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::MethodNotAllowed()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::MethodNotAllowed()` instead" +)] pub const HttpMethodNotAllowed: StaticResponse = StaticResponse(StatusCode::METHOD_NOT_ALLOWED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::NotAcceptable()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::NotAcceptable()` instead" +)] pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::ProxyAuthenticationRequired()` instead")] +#[deprecated( + since = "0.5.0", + note = "please use `HttpResponse::ProxyAuthenticationRequired()` instead" +)] pub const HttpProxyAuthenticationRequired: StaticResponse = StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::RequestTimeout()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::RequestTimeout()` instead" +)] pub const HttpRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::Conflict()` instead")] pub const HttpConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::Gone()` instead")] pub const HttpGone: StaticResponse = StaticResponse(StatusCode::GONE); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::LengthRequired()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::LengthRequired()` instead" +)] pub const HttpLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::PreconditionFailed()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::PreconditionFailed()` instead" +)] pub const HttpPreconditionFailed: StaticResponse = StaticResponse(StatusCode::PRECONDITION_FAILED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::PayloadTooLarge()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::PayloadTooLarge()` instead" +)] pub const HttpPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::UriTooLong()` instead")] pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::UnsupportedMediaType()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::UnsupportedMediaType()` instead" +)] pub const HttpUnsupportedMediaType: StaticResponse = StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::RangeNotSatisfiable()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::RangeNotSatisfiable()` instead" +)] pub const HttpRangeNotSatisfiable: StaticResponse = StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::ExpectationFailed()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::ExpectationFailed()` instead" +)] pub const HttpExpectationFailed: StaticResponse = StaticResponse(StatusCode::EXPECTATION_FAILED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::InternalServerError()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::InternalServerError()` instead" +)] pub const HttpInternalServerError: StaticResponse = StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::NotImplemented()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::NotImplemented()` instead" +)] pub const HttpNotImplemented: StaticResponse = StaticResponse(StatusCode::NOT_IMPLEMENTED); #[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadGateway()` instead")] pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::ServiceUnavailable()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::ServiceUnavailable()` instead" +)] pub const HttpServiceUnavailable: StaticResponse = StaticResponse(StatusCode::SERVICE_UNAVAILABLE); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::GatewayTimeout()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::GatewayTimeout()` instead" +)] pub const HttpGatewayTimeout: StaticResponse = StaticResponse(StatusCode::GATEWAY_TIMEOUT); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::VersionNotSupported()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::VersionNotSupported()` instead" +)] pub const HttpVersionNotSupported: StaticResponse = StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::VariantAlsoNegotiates()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::VariantAlsoNegotiates()` instead" +)] pub const HttpVariantAlsoNegotiates: StaticResponse = StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::InsufficientStorage()` instead")] +#[deprecated( + since = "0.5.0", note = "please use `HttpResponse::InsufficientStorage()` instead" +)] pub const HttpInsufficientStorage: StaticResponse = StaticResponse(StatusCode::INSUFFICIENT_STORAGE); -#[deprecated(since = "0.5.0", - note = "please use `HttpResponse::LoopDetected()` instead")] +#[deprecated(since = "0.5.0", note = "please use `HttpResponse::LoopDetected()` instead")] pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED); #[deprecated(since = "0.5.0", note = "please use `HttpResponse` instead")] @@ -221,10 +245,7 @@ impl HttpResponse { STATIC_RESP!(Ok, StatusCode::OK); STATIC_RESP!(Created, StatusCode::CREATED); STATIC_RESP!(Accepted, StatusCode::ACCEPTED); - STATIC_RESP!( - NonAuthoritativeInformation, - StatusCode::NON_AUTHORITATIVE_INFORMATION - ); + STATIC_RESP!(NonAuthoritativeInformation, StatusCode::NON_AUTHORITATIVE_INFORMATION); STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); @@ -249,10 +270,7 @@ impl HttpResponse { STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - STATIC_RESP!( - ProxyAuthenticationRequired, - StatusCode::PROXY_AUTHENTICATION_REQUIRED - ); + STATIC_RESP!(ProxyAuthenticationRequired, StatusCode::PROXY_AUTHENTICATION_REQUIRED); STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); STATIC_RESP!(Conflict, StatusCode::CONFLICT); STATIC_RESP!(Gone, StatusCode::GONE); @@ -260,10 +278,7 @@ impl HttpResponse { STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); - STATIC_RESP!( - UnsupportedMediaType, - StatusCode::UNSUPPORTED_MEDIA_TYPE - ); + STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); @@ -272,14 +287,8 @@ impl HttpResponse { STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - STATIC_RESP!( - VersionNotSupported, - StatusCode::HTTP_VERSION_NOT_SUPPORTED - ); - STATIC_RESP!( - VariantAlsoNegotiates, - StatusCode::VARIANT_ALSO_NEGOTIATES - ); + STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); + STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index b590172b..c8c836d9 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,8 +1,8 @@ use bytes::{Bytes, BytesMut}; -use encoding::EncodingRef; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::types::{DecoderTrap, Encoding}; +use encoding::EncodingRef; use futures::{Future, Poll, Stream}; use http::{header, HeaderMap}; use http_range::HttpRange; @@ -96,10 +96,8 @@ pub trait HttpMessage { /// `size` is full size of response (file). fn range(&self, size: u64) -> Result, HttpRangeError> { if let Some(range) = self.headers().get(header::RANGE) { - HttpRange::parse( - unsafe { str::from_utf8_unchecked(range.as_bytes()) }, - size, - ).map_err(|e| e.into()) + HttpRange::parse(unsafe { str::from_utf8_unchecked(range.as_bytes()) }, size) + .map_err(|e| e.into()) } else { Ok(Vec::new()) } @@ -325,10 +323,7 @@ where )); } - self.fut - .as_mut() - .expect("UrlEncoded could not be used second time") - .poll() + self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() } } @@ -385,8 +380,7 @@ where if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Err(UrlencodedError::ContentType); } - let encoding = req.encoding() - .map_err(|_| UrlencodedError::ContentType)?; + let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; // future let limit = self.limit; @@ -415,18 +409,15 @@ where self.fut = Some(Box::new(fut)); } - self.fut - .as_mut() - .expect("UrlEncoded could not be used second time") - .poll() + self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() } } #[cfg(test)] mod tests { use super::*; - use encoding::Encoding; use encoding::all::ISO_8859_2; + use encoding::Encoding; use futures::Async; use http::{Method, Uri, Version}; use httprequest::HttpRequest; @@ -488,19 +479,13 @@ mod tests { #[test] fn test_encoding_error() { let req = TestRequest::with_header("content-type", "applicatjson").finish(); - assert_eq!( - Some(ContentTypeError::ParseError), - req.encoding().err() - ); + assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); let req = TestRequest::with_header( "content-type", "application/json; charset=kkkttktk", ).finish(); - assert_eq!( - Some(ContentTypeError::UnknownEncoding), - req.encoding().err() - ); + assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err()); } #[test] @@ -621,8 +606,7 @@ mod tests { "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); let result = req.urlencoded::().poll().ok().unwrap(); assert_eq!( @@ -637,8 +621,7 @@ mod tests { "application/x-www-form-urlencoded; charset=utf-8", ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); let result = req.urlencoded().poll().ok().unwrap(); assert_eq!( @@ -664,16 +647,14 @@ mod tests { } let mut req = HttpRequest::default(); - req.payload_mut() - .unread_data(Bytes::from_static(b"test")); + 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")), _ => unreachable!("error"), } let mut req = HttpRequest::default(); - req.payload_mut() - .unread_data(Bytes::from_static(b"11111111111111")); + req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); match req.body().limit(5).poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), diff --git a/src/httprequest.rs b/src/httprequest.rs index ee2bd5a7..d41a748a 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,4 +1,5 @@ //! HTTP Request message related code. +#![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))] use bytes::Bytes; use cookie::Cookie; use failure; @@ -314,7 +315,7 @@ impl HttpRequest { /// } /// ``` pub fn url_for( - &self, name: &str, elements: U + &self, name: &str, elements: U, ) -> Result where U: IntoIterator, @@ -326,12 +327,7 @@ impl HttpRequest { let path = self.router().unwrap().resource_path(name, elements)?; if path.starts_with('/') { let conn = self.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) + Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?) } else { Ok(Url::parse(&path)?) } @@ -681,12 +677,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = vec![ - ( - Resource::new("index", "/user/{name}.{ext}"), - Some(resource), - ), - ]; + let routes = + vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; let (router, _) = Router::new("/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -715,12 +707,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = vec![ - ( - Resource::new("index", "/user/{name}.{ext}"), - Some(resource), - ), - ]; + let routes = + vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/prefix/user/test.html")); @@ -739,20 +727,15 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = vec![ - ( - Resource::external("youtube", "https://youtube.com/watch/{video_id}"), - None, - ), - ]; + let routes = vec![( + Resource::external("youtube", "https://youtube.com/watch/{video_id}"), + None, + )]; let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); assert!(!router.has_route("https://youtube.com/watch/unknown")); 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" - ); + assert_eq!(url.ok().unwrap().as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index c53975e1..a1f1cfb4 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -150,10 +150,7 @@ impl HttpResponse { if let Some(reason) = self.get_ref().reason { reason } else { - self.get_ref() - .status - .canonical_reason() - .unwrap_or("") + self.get_ref().status.canonical_reason().unwrap_or("") } } @@ -466,10 +463,7 @@ impl HttpResponseBuilder { jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { - self.cookies - .as_mut() - .unwrap() - .add(cookie.into_owned()); + self.cookies.as_mut().unwrap().add(cookie.into_owned()); } self } @@ -534,9 +528,7 @@ impl HttpResponseBuilder { if let Some(e) = self.err.take() { return Error::from(e).into(); } - let mut response = self.response - .take() - .expect("cannot reuse response builder"); + let mut response = self.response.take().expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { @@ -558,9 +550,7 @@ impl HttpResponseBuilder { S: Stream + 'static, E: Into, { - self.body(Body::Streaming(Box::new( - stream.map_err(|e| e.into()), - ))) + self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) } /// Set a json body and generate `HttpResponse` @@ -607,7 +597,7 @@ impl HttpResponseBuilder { #[inline] #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] fn parts<'a>( - parts: &'a mut Option>, err: &Option + parts: &'a mut Option>, err: &Option, ) -> Option<&'a mut Box> { if err.is_some() { return None; @@ -643,9 +633,7 @@ impl Responder for HttpResponseBuilder { impl From<&'static str> for HttpResponse { fn from(val: &'static str) -> Self { - HttpResponse::Ok() - .content_type("text/plain; charset=utf-8") - .body(val) + HttpResponse::Ok().content_type("text/plain; charset=utf-8").body(val) } } @@ -662,9 +650,7 @@ impl Responder for &'static str { impl From<&'static [u8]> for HttpResponse { fn from(val: &'static [u8]) -> Self { - HttpResponse::Ok() - .content_type("application/octet-stream") - .body(val) + HttpResponse::Ok().content_type("application/octet-stream").body(val) } } @@ -681,9 +667,7 @@ impl Responder for &'static [u8] { impl From for HttpResponse { fn from(val: String) -> Self { - HttpResponse::Ok() - .content_type("text/plain; charset=utf-8") - .body(val) + HttpResponse::Ok().content_type("text/plain; charset=utf-8").body(val) } } @@ -719,9 +703,7 @@ impl<'a> Responder for &'a String { impl From for HttpResponse { fn from(val: Bytes) -> Self { - HttpResponse::Ok() - .content_type("application/octet-stream") - .body(val) + HttpResponse::Ok().content_type("application/octet-stream").body(val) } } @@ -738,9 +720,7 @@ impl Responder for Bytes { impl From for HttpResponse { fn from(val: BytesMut) -> Self { - HttpResponse::Ok() - .content_type("application/octet-stream") - .body(val) + HttpResponse::Ok().content_type("application/octet-stream").body(val) } } @@ -772,9 +752,7 @@ impl<'a> From<&'a ClientResponse> for HttpResponseBuilder { impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { fn from(req: &'a HttpRequest) -> HttpResponseBuilder { if let Some(router) = req.router() { - router - .server_settings() - .get_response_builder(StatusCode::OK) + router.server_settings().get_response_builder(StatusCode::OK) } else { HttpResponse::Ok() } @@ -822,14 +800,12 @@ thread_local!(static POOL: Rc> = HttpResponsePool:: impl HttpResponsePool { pub fn pool() -> Rc> { - Rc::new(UnsafeCell::new(HttpResponsePool( - VecDeque::with_capacity(128), - ))) + Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(128)))) } #[inline] pub fn get_builder( - pool: &Rc>, status: StatusCode + pool: &Rc>, status: StatusCode, ) -> HttpResponseBuilder { let p = unsafe { &mut *pool.as_ref().get() }; if let Some(mut msg) = p.0.pop_front() { @@ -853,7 +829,7 @@ impl HttpResponsePool { #[inline] pub fn get_response( - pool: &Rc>, status: StatusCode, body: Body + pool: &Rc>, status: StatusCode, body: Body, ) -> HttpResponse { let p = unsafe { &mut *pool.as_ref().get() }; if let Some(mut msg) = p.0.pop_front() { @@ -879,7 +855,7 @@ impl HttpResponsePool { #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))] fn release( - pool: &Rc>, mut inner: Box + pool: &Rc>, mut inner: Box, ) { let pool = unsafe { &mut *pool.as_ref().get() }; if pool.0.len() < 128 { @@ -975,9 +951,7 @@ mod tests { #[test] fn test_force_close() { - let resp = HttpResponse::build(StatusCode::OK) - .force_close() - .finish(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); assert!(!resp.keep_alive().unwrap()) } @@ -986,10 +960,7 @@ mod tests { let resp = HttpResponse::build(StatusCode::OK) .content_type("text/plain") .body(Body::Empty); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - "text/plain" - ) + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } #[test] @@ -1073,10 +1044,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().binary().unwrap(), - &Binary::from(b"test".as_ref()) - ); + assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref())); let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -1085,10 +1053,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().binary().unwrap(), - &Binary::from(b"test".as_ref()) - ); + 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); @@ -1097,26 +1062,16 @@ mod tests { 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()) - ); + assert_eq!(resp.body().binary().unwrap(), &Binary::from("test".to_owned())); - let resp: HttpResponse = "test" - .to_owned() - .respond_to(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(CONTENT_TYPE).unwrap(), 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()) - ); + assert_eq!(resp.body().binary().unwrap(), &Binary::from("test".to_owned())); let resp: HttpResponse = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1125,25 +1080,17 @@ mod tests { 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()) - ); + assert_eq!(resp.body().binary().unwrap(), &Binary::from(&"test".to_owned())); - let resp: HttpResponse = (&"test".to_owned()) - .respond_to(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(CONTENT_TYPE).unwrap(), 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()) - ); + assert_eq!(resp.body().binary().unwrap(), &Binary::from(&"test".to_owned())); let b = Bytes::from_static(b"test"); let resp: HttpResponse = b.into(); @@ -1179,10 +1126,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().binary().unwrap(), - &Binary::from(BytesMut::from("test")) - ); + assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); let b = BytesMut::from("test"); let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); @@ -1192,10 +1136,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().binary().unwrap(), - &Binary::from(BytesMut::from("test")) - ); + assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); } #[test] diff --git a/src/info.rs b/src/info.rs index 76288539..7d3affab 100644 --- a/src/info.rs +++ b/src/info.rs @@ -53,8 +53,8 @@ impl<'a> ConnectionInfo<'a> { // scheme if scheme.is_none() { - if let Some(h) = req.headers() - .get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) + 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()); @@ -74,8 +74,8 @@ impl<'a> ConnectionInfo<'a> { // host if host.is_none() { - if let Some(h) = req.headers() - .get(HeaderName::from_str(X_FORWARDED_HOST).unwrap()) + 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()); @@ -98,8 +98,8 @@ 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 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()); @@ -189,10 +189,8 @@ mod tests { 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"), - ); + req.headers_mut() + .insert(header::HOST, HeaderValue::from_static("rust-lang.org")); let info = ConnectionInfo::new(&req); assert_eq!(info.scheme(), "http"); diff --git a/src/json.rs b/src/json.rs index 96ac415f..9f0906c1 100644 --- a/src/json.rs +++ b/src/json.rs @@ -6,8 +6,8 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use mime; -use serde::Serialize; use serde::de::DeserializeOwned; +use serde::Serialize; use serde_json; use error::{Error, JsonPayloadError, PayloadError}; @@ -308,10 +308,7 @@ where self.fut = Some(Box::new(fut)); } - self.fut - .as_mut() - .expect("JsonBody could not be used second time") - .poll() + self.fut.as_mut().expect("JsonBody could not be used second time").poll() } } @@ -362,10 +359,7 @@ mod tests { fn test_json_body() { let req = HttpRequest::default(); let mut json = req.json::(); - assert_eq!( - json.poll().err().unwrap(), - JsonPayloadError::ContentType - ); + assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); let mut req = HttpRequest::default(); req.headers_mut().insert( @@ -373,20 +367,15 @@ mod tests { header::HeaderValue::from_static("application/text"), ); let mut json = req.json::(); - assert_eq!( - json.poll().err().unwrap(), - JsonPayloadError::ContentType - ); + assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); let mut req = HttpRequest::default(); req.headers_mut().insert( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), ); - req.headers_mut().insert( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ); + req.headers_mut() + .insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("10000")); let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); @@ -395,12 +384,9 @@ mod tests { header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), ); - req.headers_mut().insert( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ); - req.payload_mut() - .unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); + 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(), @@ -417,12 +403,7 @@ mod tests { let mut handler = With::new(|data: Json| data, cfg); let req = HttpRequest::default(); - let err = handler - .handle(req) - .as_response() - .unwrap() - .error() - .is_some(); + let err = handler.handle(req).as_response().unwrap().error().is_some(); assert!(err); let mut req = HttpRequest::default(); @@ -430,18 +411,10 @@ mod tests { header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), ); - req.headers_mut().insert( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ); - req.payload_mut() - .unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); - let ok = handler - .handle(req) - .as_response() - .unwrap() - .error() - .is_none(); + req.headers_mut() + .insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("16")); + req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); + let ok = handler.handle(req).as_response().unwrap().error().is_none(); assert!(ok) } } diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 243ea1e8..aa0bd494 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -64,26 +64,36 @@ use resource::ResourceHandler; #[derive(Debug, Fail)] pub enum CorsError { /// The HTTP request header `Origin` is required but was not provided - #[fail(display = "The HTTP request header `Origin` is required but was not provided")] + #[fail( + display = "The HTTP request header `Origin` is required but was not provided" + )] MissingOrigin, /// The HTTP request header `Origin` could not be parsed correctly. #[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")] BadOrigin, /// The request header `Access-Control-Request-Method` is required but is /// missing - #[fail(display = "The request header `Access-Control-Request-Method` is required but is missing")] + #[fail( + display = "The request header `Access-Control-Request-Method` is required but is missing" + )] MissingRequestMethod, /// The request header `Access-Control-Request-Method` has an invalid value - #[fail(display = "The request header `Access-Control-Request-Method` has an invalid value")] + #[fail( + display = "The request header `Access-Control-Request-Method` has an invalid value" + )] BadRequestMethod, /// The request header `Access-Control-Request-Headers` has an invalid /// value - #[fail(display = "The request header `Access-Control-Request-Headers` has an invalid value")] + #[fail( + display = "The request header `Access-Control-Request-Headers` has an invalid value" + )] BadRequestHeaders, /// The request header `Access-Control-Request-Headers` is required but is /// missing. - #[fail(display = "The request header `Access-Control-Request-Headers` is required but is - missing")] + #[fail( + display = "The request header `Access-Control-Request-Headers` is required but is + missing" + )] MissingRequestHeaders, /// Origin is not allowed to make this request #[fail(display = "Origin is not allowed to make this request")] @@ -265,9 +275,7 @@ impl Cors { /// `ResourceHandler::middleware()` method, but in that case *Cors* /// middleware wont be able to handle *OPTIONS* requests. pub fn register(self, resource: &mut ResourceHandler) { - resource - .method(Method::OPTIONS) - .h(|_| HttpResponse::Ok()); + resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok()); resource.middleware(self); } @@ -292,11 +300,9 @@ impl Cors { } fn validate_allowed_method( - &self, req: &mut HttpRequest + &self, req: &mut HttpRequest, ) -> Result<(), CorsError> { - if let Some(hdr) = req.headers() - .get(header::ACCESS_CONTROL_REQUEST_METHOD) - { + if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { return self.inner @@ -313,13 +319,13 @@ impl Cors { } fn validate_allowed_headers( - &self, req: &mut HttpRequest + &self, req: &mut HttpRequest, ) -> Result<(), CorsError> { match self.inner.headers { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = req.headers() - .get(header::ACCESS_CONTROL_REQUEST_HEADERS) + if let Some(hdr) = + req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { if let Ok(headers) = hdr.to_str() { let mut hdrs = HashSet::new(); @@ -361,8 +367,8 @@ impl Middleware for Cors { .as_str()[1..], ).unwrap(), ) - } else if let Some(hdr) = req.headers() - .get(header::ACCESS_CONTROL_REQUEST_HEADERS) + } else if let Some(hdr) = + req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { Some(hdr.clone()) } else { @@ -419,7 +425,7 @@ impl Middleware for Cors { } fn response( - &self, req: &mut HttpRequest, mut resp: HttpResponse + &self, req: &mut HttpRequest, mut resp: HttpResponse, ) -> Result { match self.inner.origins { AllOrSome::All => { @@ -506,7 +512,7 @@ pub struct CorsBuilder { } fn cors<'a>( - parts: &'a mut Option, err: &Option + parts: &'a mut Option, err: &Option, ) -> Option<&'a mut Inner> { if err.is_some() { return None; @@ -813,17 +819,13 @@ impl CorsBuilder { } if let AllOrSome::Some(ref origins) = cors.origins { - let s = origins - .iter() - .fold(String::new(), |s, v| s + &format!("{}", v)); + let s = origins.iter().fold(String::new(), |s, v| s + &v.to_string()); cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap()); } if !self.expose_hdrs.is_empty() { cors.expose_hdrs = Some( - self.expose_hdrs - .iter() - .fold(String::new(), |s, v| s + v.as_str())[1..] + self.expose_hdrs.iter().fold(String::new(), |s, v| s + v.as_str())[1..] .to_owned(), ); } @@ -901,27 +903,19 @@ mod tests { #[test] #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] fn cors_validates_illegal_allow_credentials() { - Cors::build() - .supports_credentials() - .send_wildcard() - .finish(); + Cors::build().supports_credentials().send_wildcard().finish(); } #[test] #[should_panic(expected = "No resources are registered")] fn no_resource() { - Cors::build() - .supports_credentials() - .send_wildcard() - .register(); + Cors::build().supports_credentials().send_wildcard().register(); } #[test] #[should_panic(expected = "Cors::for_app(app)")] fn no_resource2() { - Cors::build() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register(); + Cors::build().resource("/test", |r| r.f(|_| HttpResponse::Ok())).register(); } #[test] @@ -958,27 +952,18 @@ mod tests { let mut req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ) + .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "AUTHORIZATION,ACCEPT") .method(Method::OPTIONS) .finish(); let resp = cors.start(&mut req).unwrap().response(); assert_eq!( &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() + resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes() ); assert_eq!( &b"3600"[..], - resp.headers() - .get(header::ACCESS_CONTROL_MAX_AGE) - .unwrap() - .as_bytes() + resp.headers().get(header::ACCESS_CONTROL_MAX_AGE).unwrap().as_bytes() ); //assert_eq!( // &b"authorization,accept,content-type"[..], @@ -995,9 +980,7 @@ mod tests { #[test] #[should_panic(expected = "MissingOrigin")] fn test_validate_missing_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); + let cors = Cors::build().allowed_origin("https://www.example.com").finish(); let mut req = HttpRequest::default(); cors.start(&mut req).unwrap(); @@ -1006,9 +989,7 @@ mod tests { #[test] #[should_panic(expected = "OriginNotAllowed")] fn test_validate_not_allowed_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); + let cors = Cors::build().allowed_origin("https://www.example.com").finish(); let mut req = TestRequest::with_header("Origin", "https://www.unknown.com") .method(Method::GET) @@ -1018,9 +999,7 @@ mod tests { #[test] fn test_validate_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); + let cors = Cors::build().allowed_origin("https://www.example.com").finish(); let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::GET) @@ -1036,11 +1015,7 @@ mod tests { let mut req = TestRequest::default().method(Method::GET).finish(); let resp: HttpResponse = HttpResponse::Ok().into(); let resp = cors.response(&mut req, resp).unwrap().response(); - assert!( - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_none() - ); + assert!(resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).is_none()); let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) @@ -1048,10 +1023,7 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() + resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes() ); } @@ -1074,19 +1046,12 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() + resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes() ); + assert_eq!(&b"Origin"[..], resp.headers().get(header::VARY).unwrap().as_bytes()); - let resp: HttpResponse = HttpResponse::Ok() - .header(header::VARY, "Accept") - .finish(); + let resp: HttpResponse = + HttpResponse::Ok().header(header::VARY, "Accept").finish(); let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"Accept, Origin"[..], @@ -1101,10 +1066,7 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() + resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes() ); } diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 9ff23b53..255b4564 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -89,10 +89,7 @@ fn origin(headers: &HeaderMap) -> Option, CsrfError>> { headers .get(header::ORIGIN) .map(|origin| { - origin - .to_str() - .map_err(|_| CsrfError::BadOrigin) - .map(|o| o.into()) + origin.to_str().map_err(|_| CsrfError::BadOrigin).map(|o| o.into()) }) .or_else(|| { headers.get(header::REFERER).map(|referer| { @@ -261,9 +258,8 @@ mod tests { fn test_upgrade() { let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - let lax_csrf = CsrfFilter::new() - .allowed_origin("https://www.example.com") - .allow_upgrade(); + let lax_csrf = + CsrfFilter::new().allowed_origin("https://www.example.com").allow_upgrade(); let mut req = TestRequest::with_header("Origin", "https://cswsh.com") .header("Connection", "Upgrade") diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 4e17a553..ebe3ea1d 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -76,7 +76,7 @@ impl DefaultHeaders { impl Middleware for DefaultHeaders { fn response( - &self, _: &mut HttpRequest, mut resp: HttpResponse + &self, _: &mut HttpRequest, mut resp: HttpResponse, ) -> Result { for (key, value) in self.headers.iter() { if !resp.headers().contains_key(key) { @@ -112,9 +112,7 @@ mod tests { }; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let resp = HttpResponse::Ok() - .header(CONTENT_TYPE, "0002") - .finish(); + let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); let resp = match mw.response(&mut req, resp) { Ok(Response::Done(resp)) => resp, _ => panic!(), diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index fdc43ed2..22d0e1af 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -69,7 +69,7 @@ impl ErrorHandlers { impl Middleware for ErrorHandlers { fn response( - &self, req: &mut HttpRequest, resp: HttpResponse + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(handler) = self.handlers.get(&resp.status()) { handler(req, resp) @@ -82,8 +82,8 @@ impl Middleware for ErrorHandlers { #[cfg(test)] mod tests { use super::*; - use http::StatusCode; use http::header::CONTENT_TYPE; + use http::StatusCode; fn render_500(_: &mut HttpRequest, resp: HttpResponse) -> Result { let mut builder = resp.into_builder(); diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index e428847a..06c5a4fa 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -49,8 +49,8 @@ use std::rc::Rc; use cookie::{Cookie, CookieJar, Key}; +use futures::future::{err as FutErr, ok as FutOk, FutureResult}; use futures::Future; -use futures::future::{FutureResult, err as FutErr, ok as FutOk}; use time::Duration; use error::{Error, Result}; @@ -164,7 +164,9 @@ pub struct IdentityService { impl IdentityService { /// Create new identity service with specified backend. pub fn new(backend: T) -> Self { - IdentityService { backend } + IdentityService { + backend, + } } } @@ -179,20 +181,18 @@ impl> Middleware for IdentityService { fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); - let fut = self.backend - .from_request(&mut req) - .then(move |res| match res { - Ok(id) => { - req.extensions().insert(IdentityBox(Box::new(id))); - FutOk(None) - } - Err(err) => FutErr(err), - }); + let fut = self.backend.from_request(&mut req).then(move |res| match res { + Ok(id) => { + req.extensions().insert(IdentityBox(Box::new(id))); + FutOk(None) + } + Err(err) => FutErr(err), + }); Ok(Started::Future(Box::new(fut))) } fn response( - &self, req: &mut HttpRequest, resp: HttpResponse + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(mut id) = req.extensions().remove::() { id.0.write(resp) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 28964718..7b5d6c4c 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -254,10 +254,9 @@ impl FormatText { "-".fmt(fmt) } } - FormatText::RequestTime => entry_time - .strftime("[%d/%b/%Y:%H:%M:%S %z]") - .unwrap() - .fmt(fmt), + FormatText::RequestTime => { + entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]").unwrap().fmt(fmt) + } FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { if let Ok(s) = val.to_str() { @@ -314,10 +313,8 @@ mod tests { let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); let mut headers = HeaderMap::new(); - headers.insert( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ); + headers + .insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -354,10 +351,8 @@ mod tests { let format = Format::default(); let mut headers = HeaderMap::new(); - headers.insert( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ); + headers + .insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -365,9 +360,7 @@ mod tests { headers, None, ); - let resp = HttpResponse::build(StatusCode::OK) - .force_close() - .finish(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); let entry_time = time::now(); let render = |fmt: &mut Formatter| { @@ -388,9 +381,7 @@ mod tests { HeaderMap::new(), None, ); - let resp = HttpResponse::build(StatusCode::OK) - .force_close() - .finish(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); let entry_time = time::now(); let render = |fmt: &mut Formatter| { diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index c437b254..b9d3847d 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -21,8 +21,9 @@ pub use self::logger::Logger; #[cfg(feature = "session")] #[doc(hidden)] -#[deprecated(since = "0.5.4", - note = "please use `actix_web::middleware::session` instead")] +#[deprecated( + since = "0.5.4", note = "please use `actix_web::middleware::session` instead" +)] pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage}; @@ -65,7 +66,7 @@ pub trait Middleware: 'static { /// Method is called when handler returns response, /// but before sending http message to peer. fn response( - &self, req: &mut HttpRequest, resp: HttpResponse + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { Ok(Response::Done(resp)) } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index c71ed5a6..9cc7acb1 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -69,8 +69,8 @@ use std::rc::Rc; use std::sync::Arc; use cookie::{Cookie, CookieJar, Key}; +use futures::future::{err as FutErr, ok as FutOk, FutureResult}; use futures::Future; -use futures::future::{FutureResult, err as FutErr, ok as FutOk}; use http::header::{self, HeaderValue}; use serde::{Deserialize, Serialize}; use serde_json; @@ -202,21 +202,18 @@ impl> Middleware for SessionStorage { fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); - let fut = self.0 - .from_request(&mut req) - .then(move |res| match res { - Ok(sess) => { - req.extensions() - .insert(Arc::new(SessionImplBox(Box::new(sess)))); - FutOk(None) - } - Err(err) => FutErr(err), - }); + let fut = self.0.from_request(&mut req).then(move |res| match res { + Ok(sess) => { + req.extensions().insert(Arc::new(SessionImplBox(Box::new(sess)))); + FutOk(None) + } + Err(err) => FutErr(err), + }); Ok(Started::Future(Box::new(fut))) } fn response( - &self, req: &mut HttpRequest, resp: HttpResponse + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(s_box) = req.extensions().remove::>() { s_box.0.write(resp) @@ -349,7 +346,7 @@ impl CookieSessionInner { } fn set_cookie( - &self, resp: &mut HttpResponse, state: &HashMap + &self, resp: &mut HttpResponse, state: &HashMap, ) -> Result<()> { let value = serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; diff --git a/src/multipart.rs b/src/multipart.rs index 87d4b1ad..0fc98000 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -7,8 +7,8 @@ use std::{cmp, fmt}; use bytes::Bytes; use futures::task::{current as current_task, Task}; use futures::{Async, Poll, Stream}; -use http::HttpTryFrom; use http::header::{self, HeaderMap, HeaderName, HeaderValue}; +use http::HttpTryFrom; use httparse; use mime; @@ -122,11 +122,7 @@ where if let Some(err) = self.error.take() { Err(err) } else if self.safety.current() { - self.inner - .as_mut() - .unwrap() - .borrow_mut() - .poll(&self.safety) + self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) } else { Ok(Async::NotReady) } @@ -168,7 +164,7 @@ where } fn read_boundary( - payload: &mut PayloadHelper, boundary: &str + payload: &mut PayloadHelper, boundary: &str, ) -> Poll { // TODO: need to read epilogue match payload.readline()? { @@ -192,7 +188,7 @@ where } fn skip_until_boundary( - payload: &mut PayloadHelper, boundary: &str + payload: &mut PayloadHelper, boundary: &str, ) -> Poll { let mut eof = false; loop { @@ -230,7 +226,7 @@ where } fn poll( - &mut self, safety: &Safety + &mut self, safety: &Safety, ) -> Poll>, MultipartError> { if self.state == InnerState::Eof { Ok(Async::Ready(None)) @@ -450,7 +446,7 @@ where S: Stream, { fn new( - payload: PayloadRef, boundary: String, headers: &HeaderMap + payload: PayloadRef, boundary: String, headers: &HeaderMap, ) -> Result, PayloadError> { let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { if let Ok(s) = len.to_str() { @@ -477,7 +473,7 @@ where /// Reads body part content chunk of the specified size. /// The body part must has `Content-Length` header with proper value. fn read_len( - payload: &mut PayloadHelper, size: &mut u64 + payload: &mut PayloadHelper, size: &mut u64, ) -> Poll, MultipartError> { if *size == 0 { Ok(Async::Ready(None)) @@ -502,7 +498,7 @@ where /// Reads content chunk of body part with unknown length. /// The `Content-Length` header for body part is not necessary. fn read_stream( - payload: &mut PayloadHelper, boundary: &str + payload: &mut PayloadHelper, boundary: &str, ) -> Poll, MultipartError> { match payload.read_until(b"\r")? { Async::NotReady => Ok(Async::NotReady), @@ -675,10 +671,7 @@ mod tests { } let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("test"), - ); + headers.insert(header::CONTENT_TYPE, header::HeaderValue::from_static("test")); match Multipart::boundary(&headers) { Err(MultipartError::ParseContentType) => (), diff --git a/src/param.rs b/src/param.rs index 41100763..99cc3def 100644 --- a/src/param.rs +++ b/src/param.rs @@ -94,8 +94,7 @@ impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> { type Output = str; fn index(&self, name: &'b str) -> &str { - self.get(name) - .expect("Value for parameter is not available") + self.get(name).expect("Value for parameter is not available") } } @@ -202,18 +201,9 @@ mod tests { 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("/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"])) diff --git a/src/payload.rs b/src/payload.rs index a394c106..d3b5a59b 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -47,7 +47,9 @@ impl Payload { PayloadSender { inner: Rc::downgrade(&shared), }, - Payload { inner: shared }, + Payload { + inner: shared, + }, ) } @@ -534,10 +536,7 @@ mod tests { assert_eq!(format!("{}", err.cause().unwrap()), "ParseError"); let err = PayloadError::Incomplete; - assert_eq!( - format!("{}", err), - "A payload reached EOF, but is not complete." - ); + assert_eq!(format!("{}", err), "A payload reached EOF, but is not complete."); } #[test] @@ -671,10 +670,7 @@ mod tests { let (mut sender, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); - assert_eq!( - Async::NotReady, - payload.read_until(b"ne").ok().unwrap() - ); + assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); diff --git a/src/pipeline.rs b/src/pipeline.rs index 3e90a6dc..36cb037a 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -67,7 +67,7 @@ impl> PipelineState { } struct PipelineInfo { - req: HttpRequest, + req: UnsafeCell>, count: u16, mws: Rc>>>, context: Option>, @@ -79,7 +79,7 @@ struct PipelineInfo { impl PipelineInfo { fn new(req: HttpRequest) -> PipelineInfo { PipelineInfo { - req, + req: UnsafeCell::new(req), count: 0, mws: Rc::new(Vec::new()), error: None, @@ -89,11 +89,17 @@ impl PipelineInfo { } } + #[inline] + fn req(&self) -> &HttpRequest { + unsafe { &*self.req.get() } + } + + #[inline] #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] fn req_mut(&self) -> &mut HttpRequest { #[allow(mutable_transmutes)] unsafe { - mem::transmute(&self.req) + &mut *self.req.get() } } @@ -116,8 +122,8 @@ impl> Pipeline { handler: Rc>, htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { - req, mws, + req: UnsafeCell::new(req), count: 0, error: None, context: None, @@ -159,7 +165,7 @@ impl> HttpHandlerTask for Pipeline { } fn poll_io(&mut self, io: &mut Writer) -> Poll { - let info: &mut PipelineInfo<_> = unsafe { mem::transmute(&mut self.0) }; + let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) }; loop { if self.1.is_response() { @@ -197,7 +203,7 @@ impl> HttpHandlerTask for Pipeline { } fn poll(&mut self) -> Poll<(), Error> { - let info: &mut PipelineInfo<_> = unsafe { mem::transmute(&mut self.0) }; + let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) }; loop { match self.1 { @@ -228,17 +234,17 @@ struct StartMiddlewares { impl> StartMiddlewares { fn init( - info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType + info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType, ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately let len = info.mws.len() as u16; loop { if info.count == len { - let reply = unsafe { &mut *hnd.get() }.handle(info.req.clone(), htype); + let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype); return WaitingResponse::init(info, reply); } else { - match info.mws[info.count as usize].start(&mut info.req) { + match info.mws[info.count as usize].start(info.req_mut()) { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { return RunMiddlewares::init(info, resp) @@ -278,7 +284,7 @@ impl> StartMiddlewares { } if info.count == len { let reply = unsafe { &mut *self.hnd.get() } - .handle(info.req.clone(), self.htype); + .handle(info.req().clone(), self.htype); return Some(WaitingResponse::init(info, reply)); } else { loop { @@ -462,7 +468,7 @@ impl ProcessResponse { } fn poll_io( - mut self, io: &mut Writer, info: &mut PipelineInfo + mut self, io: &mut Writer, info: &mut PipelineInfo, ) -> Result, PipelineState> { loop { if self.drain.is_none() && self.running != RunningState::Paused { @@ -482,8 +488,7 @@ impl ProcessResponse { Err(err) => { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, - self.resp, + info, self.resp, )); } }; @@ -525,8 +530,7 @@ impl ProcessResponse { if let Err(err) = io.write_eof() { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, - self.resp, + info, self.resp, )); } break; @@ -537,8 +541,7 @@ impl ProcessResponse { Err(err) => { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, - self.resp, + info, self.resp, )); } Ok(result) => result, @@ -572,8 +575,7 @@ impl ProcessResponse { info.error = Some(err.into()); return Ok( FinishingMiddlewares::init( - info, - self.resp, + info, self.resp, ), ); } @@ -585,8 +587,7 @@ impl ProcessResponse { info.error = Some(err.into()); return Ok( FinishingMiddlewares::init( - info, - self.resp, + info, self.resp, ), ); } @@ -611,8 +612,7 @@ impl ProcessResponse { Err(err) => { info.error = Some(err); return Ok(FinishingMiddlewares::init( - info, - self.resp, + info, self.resp, )); } } @@ -796,18 +796,15 @@ mod tests { .unwrap() .run(lazy(|| { let mut info = PipelineInfo::new(HttpRequest::default()); - Completed::<(), Inner<()>>::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: Addr = ctx.address(); let mut info = PipelineInfo::new(req); info.context = Some(Box::new(ctx)); - let mut state = Completed::<(), Inner<()>>::init(&mut info) - .completed() - .unwrap(); + let mut state = + Completed::<(), Inner<()>>::init(&mut info).completed().unwrap(); assert!(state.poll(&mut info).is_none()); let pp = Pipeline(info, PipelineState::Completed(state)); diff --git a/src/pred.rs b/src/pred.rs index a712bba6..90a0d61f 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -171,7 +171,7 @@ pub fn Method(method: http::Method) -> MethodPredicate { /// Return predicate that matches if request contains specified header and /// value. pub fn Header( - name: &'static str, value: &'static str + name: &'static str, value: &'static str, ) -> HeaderPredicate { HeaderPredicate( header::HeaderName::try_from(name).unwrap(), @@ -181,11 +181,7 @@ pub fn Header( } #[doc(hidden)] -pub struct HeaderPredicate( - header::HeaderName, - header::HeaderValue, - PhantomData, -); +pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); impl Predicate for HeaderPredicate { fn check(&self, req: &mut HttpRequest) -> bool { diff --git a/src/resource.rs b/src/resource.rs index c7b886a9..7ce44c0f 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -132,10 +132,7 @@ impl ResourceHandler { /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); - self.routes - .last_mut() - .unwrap() - .filter(pred::Method(method)) + self.routes.last_mut().unwrap().filter(pred::Method(method)) } /// Register a new route and add handler object. @@ -188,13 +185,11 @@ impl ResourceHandler { /// This is similar to `App's` middlewares, but /// middlewares get invoked on resource level. pub fn middleware>(&mut self, mw: M) { - Rc::get_mut(&mut self.middlewares) - .unwrap() - .push(Box::new(mw)); + Rc::get_mut(&mut self.middlewares).unwrap().push(Box::new(mw)); } pub(crate) fn handle( - &mut self, mut req: HttpRequest, default: Option<&mut ResourceHandler> + &mut self, mut req: HttpRequest, default: Option<&mut ResourceHandler>, ) -> Reply { for route in &mut self.routes { if route.check(&mut req) { diff --git a/src/route.rs b/src/route.rs index 526eb137..346edecd 100644 --- a/src/route.rs +++ b/src/route.rs @@ -50,7 +50,7 @@ impl Route { #[inline] pub(crate) fn compose( - &mut self, req: HttpRequest, mws: Rc>>> + &mut self, req: HttpRequest, mws: Rc>>>, ) -> Reply { Reply::async(Compose::new(req, mws, self.handler.clone())) } @@ -170,7 +170,7 @@ impl Route { /// } /// ``` pub fn with2( - &mut self, handler: F + &mut self, handler: F, ) -> (ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2) -> R + 'static, @@ -180,22 +180,14 @@ impl Route { { let cfg1 = ExtractorConfig::default(); let cfg2 = ExtractorConfig::default(); - self.h(With2::new( - handler, - Clone::clone(&cfg1), - Clone::clone(&cfg2), - )); + self.h(With2::new(handler, Clone::clone(&cfg1), Clone::clone(&cfg2))); (cfg1, cfg2) } /// Set handler function, use request extractor for all paramters. pub fn with3( - &mut self, handler: F - ) -> ( - ExtractorConfig, - ExtractorConfig, - ExtractorConfig, - ) + &mut self, handler: F, + ) -> (ExtractorConfig, ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2, T3) -> R + 'static, R: Responder + 'static, @@ -288,7 +280,7 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>, handler: InnerHandler + req: HttpRequest, mws: Rc>>>, handler: InnerHandler, ) -> Self { let mut info = ComposeInfo { count: 0, @@ -298,7 +290,10 @@ impl Compose { }; let state = StartMiddlewares::init(&mut info); - Compose { state, info } + Compose { + state, + info, + } } } diff --git a/src/router.rs b/src/router.rs index 4257d739..35f9d7f5 100644 --- a/src/router.rs +++ b/src/router.rs @@ -80,7 +80,11 @@ impl Router { return None; } let path: &str = unsafe { mem::transmute(&req.path()[self.0.prefix_len..]) }; - let route_path = if path.is_empty() { "/" } else { path }; + let route_path = if path.is_empty() { + "/" + } else { + path + }; for (idx, pattern) in self.0.patterns.iter().enumerate() { if pattern.match_with_params(route_path, req.match_info_mut()) { @@ -98,7 +102,11 @@ impl Router { /// following path would be recognizable `/test/name` but `has_route()` call /// would return `false`. pub fn has_route(&self, path: &str) -> bool { - let path = if path.is_empty() { "/" } else { path }; + let path = if path.is_empty() { + "/" + } else { + path + }; for pattern in &self.0.patterns { if pattern.is_match(path) { @@ -113,7 +121,7 @@ impl Router { /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. /// url_for) for detailed information. pub fn resource_path( - &self, name: &str, elements: U + &self, name: &str, elements: U, ) -> Result where U: IntoIterator, @@ -245,7 +253,7 @@ impl Resource { } pub fn match_with_params<'a>( - &'a self, path: &'a str, params: &'a mut Params<'a> + &'a self, path: &'a str, params: &'a mut Params<'a>, ) -> bool { match self.tp { PatternType::Static(ref s) => s == path, @@ -270,7 +278,7 @@ impl Resource { /// Build reousrce path. pub fn resource_path( - &self, router: &Router, elements: U + &self, router: &Router, elements: U, ) -> Result where U: IntoIterator, @@ -383,34 +391,19 @@ mod tests { #[test] fn test_recognizer() { let routes = vec![ - ( - Resource::new("", "/name"), - Some(ResourceHandler::default()), - ), - ( - Resource::new("", "/name/{val}"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/name"), Some(ResourceHandler::default())), + (Resource::new("", "/name/{val}"), Some(ResourceHandler::default())), ( Resource::new("", "/name/{val}/index.html"), Some(ResourceHandler::default()), ), - ( - Resource::new("", "/file/{file}.{ext}"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/file/{file}.{ext}"), Some(ResourceHandler::default())), ( Resource::new("", "/v{val}/{val2}/index.html"), Some(ResourceHandler::default()), ), - ( - Resource::new("", "/v/{tail:.*}"), - Some(ResourceHandler::default()), - ), - ( - Resource::new("", "{test}/index.html"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/v/{tail:.*}"), Some(ResourceHandler::default())), + (Resource::new("", "{test}/index.html"), Some(ResourceHandler::default())), ]; let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); @@ -439,10 +432,7 @@ mod tests { let mut req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); assert_eq!(rec.recognize(&mut req), Some(5)); - assert_eq!( - req.match_info().get("tail").unwrap(), - "blah-blah/index.html" - ); + assert_eq!(req.match_info().get("tail").unwrap(), "blah-blah/index.html"); let mut req = TestRequest::with_uri("/bbb/index.html").finish(); assert_eq!(rec.recognize(&mut req), Some(6)); @@ -452,14 +442,8 @@ mod tests { #[test] fn test_recognizer_2() { let routes = vec![ - ( - Resource::new("", "/index.json"), - Some(ResourceHandler::default()), - ), - ( - Resource::new("", "/{source}.json"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/index.json"), Some(ResourceHandler::default())), + (Resource::new("", "/{source}.json"), Some(ResourceHandler::default())), ]; let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); @@ -473,14 +457,8 @@ mod tests { #[test] fn test_recognizer_with_prefix() { let routes = vec![ - ( - Resource::new("", "/name"), - Some(ResourceHandler::default()), - ), - ( - Resource::new("", "/name/{val}"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/name"), Some(ResourceHandler::default())), + (Resource::new("", "/name/{val}"), Some(ResourceHandler::default())), ]; let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes); @@ -497,14 +475,8 @@ mod tests { // same patterns let routes = vec![ - ( - Resource::new("", "/name"), - Some(ResourceHandler::default()), - ), - ( - Resource::new("", "/name/{val}"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/name"), Some(ResourceHandler::default())), + (Resource::new("", "/name/{val}"), Some(ResourceHandler::default())), ]; let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes); @@ -573,14 +545,8 @@ mod tests { #[test] fn test_request_resource() { let routes = vec![ - ( - Resource::new("r1", "/index.json"), - Some(ResourceHandler::default()), - ), - ( - Resource::new("r2", "/test.json"), - Some(ResourceHandler::default()), - ), + (Resource::new("r1", "/index.json"), Some(ResourceHandler::default())), + (Resource::new("r2", "/test.json"), Some(ResourceHandler::default())), ]; let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); diff --git a/src/server/channel.rs b/src/server/channel.rs index 7a4bc64b..03ec69d9 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -7,7 +7,7 @@ use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use super::settings::WorkerSettings; -use super::{utils, HttpHandler, IoStream, h1, h2}; +use super::{h1, h2, utils, HttpHandler, IoStream}; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; @@ -93,12 +93,12 @@ where let el = self as *mut _; self.node = Some(Node::new(el)); let _ = match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => self.node - .as_ref() - .map(|n| h1.settings().head().insert(n)), - Some(HttpProtocol::H2(ref mut h2)) => self.node - .as_ref() - .map(|n| h2.settings().head().insert(n)), + Some(HttpProtocol::H1(ref mut h1)) => { + self.node.as_ref().map(|n| h1.settings().head().insert(n)) + } + Some(HttpProtocol::H2(ref mut h2)) => { + self.node.as_ref().map(|n| h2.settings().head().insert(n)) + } Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => { self.node.as_ref().map(|n| settings.head().insert(n)) } @@ -112,7 +112,9 @@ where match result { Ok(Async::Ready(())) | Err(_) => { h1.settings().remove_channel(); - self.node.as_mut().map(|n| n.remove()); + if let Some(n) = self.node.as_mut() { + n.remove() + }; } _ => (), } @@ -123,7 +125,9 @@ where match result { Ok(Async::Ready(())) | Err(_) => { h2.settings().remove_channel(); - self.node.as_mut().map(|n| n.remove()); + if let Some(n) = self.node.as_mut() { + n.remove() + }; } _ => (), } @@ -139,7 +143,9 @@ where Ok(Async::Ready(0)) | Err(_) => { debug!("Ignored premature client disconnection"); settings.remove_channel(); - self.node.as_mut().map(|n| n.remove()); + if let Some(n) = self.node.as_mut() { + n.remove() + }; return Err(()); } _ => (), @@ -162,12 +168,8 @@ where if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { match kind { ProtocolKind::Http1 => { - self.proto = Some(HttpProtocol::H1(h1::Http1::new( - settings, - io, - addr, - buf, - ))); + self.proto = + Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf))); return self.poll(); } ProtocolKind::Http2 => { @@ -204,7 +206,8 @@ impl Node { #[allow(mutable_transmutes)] unsafe { if let Some(ref next2) = self.next { - let n: &mut Node<()> = mem::transmute(next2.as_ref().unwrap()); + let n: &mut Node<()> = + &mut *(next2.as_ref().unwrap() as *const _ as *mut _); n.prev = Some(next as *const _ as *mut _); } let slf: &mut Node = mem::transmute(self); @@ -275,7 +278,9 @@ where T: AsyncRead + AsyncWrite + 'static, { pub fn new(io: T) -> Self { - WrapperStream { io } + WrapperStream { + io, + } } } diff --git a/src/server/encoding.rs b/src/server/encoding.rs index ae69ae07..6e450f71 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -763,8 +763,7 @@ impl TransferEncoding { return Ok(*remaining == 0); } let len = cmp::min(*remaining, msg.len() as u64); - self.buffer - .extend(msg.take().split_to(len as usize).into()); + self.buffer.extend(msg.take().split_to(len as usize).into()); *remaining -= len as u64; Ok(*remaining == 0) @@ -856,10 +855,8 @@ impl AcceptEncoding { /// Parse a raw Accept-Encoding header value into an ordered list. pub fn parse(raw: &str) -> ContentEncoding { - let mut encodings: Vec<_> = raw.replace(' ', "") - .split(',') - .map(|l| AcceptEncoding::new(l)) - .collect(); + let mut encodings: Vec<_> = + raw.replace(' ', "").split(',').map(|l| AcceptEncoding::new(l)).collect(); encodings.sort(); for enc in encodings { @@ -879,9 +876,7 @@ mod tests { fn test_chunked_te() { let bytes = SharedBytes::default(); let mut enc = TransferEncoding::chunked(bytes.clone()); - assert!(!enc.encode(Binary::from(b"test".as_ref())) - .ok() - .unwrap()); + assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap()); assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap()); assert_eq!( bytes.get_mut().take().freeze(), diff --git a/src/server/h1.rs b/src/server/h1.rs index e411a788..4a197603 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -210,8 +210,7 @@ where self.stream.reset(); if ready { - item.flags - .insert(EntryFlags::EOF | EntryFlags::FINISHED); + item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED); } else { item.flags.insert(EntryFlags::FINISHED); } @@ -253,10 +252,7 @@ where // cleanup finished tasks let max = self.tasks.len() >= MAX_PIPELINED_MESSAGES; while !self.tasks.is_empty() { - if self.tasks[0] - .flags - .contains(EntryFlags::EOF | EntryFlags::FINISHED) - { + if self.tasks[0].flags.contains(EntryFlags::EOF | EntryFlags::FINISHED) { self.tasks.pop_front(); } else { break; @@ -308,7 +304,10 @@ where pub fn parse(&mut self) { 'outer: loop { match self.decoder.decode(&mut self.buf, &self.settings) { - Ok(Some(Message::Message { msg, payload })) => { + Ok(Some(Message::Message { + msg, + payload, + })) => { self.flags.insert(Flags::STARTED); if payload { @@ -421,13 +420,19 @@ mod tests { impl Message { fn message(self) -> SharedHttpInnerMessage { match self { - Message::Message { msg, payload: _ } => msg, + Message::Message { + msg, + payload: _, + } => msg, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - Message::Message { msg: _, payload } => payload, + Message::Message { + msg: _, + payload, + } => payload, _ => panic!("error"), } } @@ -623,10 +628,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!( - req.headers().get("test").unwrap().as_bytes(), - b"value" - ); + assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); } Ok(_) | Err(_) => unreachable!("Error during parsing http request"), } @@ -848,12 +850,7 @@ mod tests { assert!(!req.keep_alive()); assert!(req.upgrade()); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf, &settings).unwrap().unwrap().chunk().as_ref(), b"some raw data" ); } @@ -910,30 +907,14 @@ mod tests { buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf, &settings).unwrap().unwrap().chunk().as_ref(), b"data" ); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf, &settings).unwrap().unwrap().chunk().as_ref(), b"line" ); - assert!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .eof() - ); + assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); } #[test] @@ -1014,13 +995,7 @@ mod tests { assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); buf.extend(b"\r\n"); - assert!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .eof() - ); + assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); } #[test] @@ -1038,17 +1013,9 @@ mod tests { assert!(req.chunked().unwrap()); buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk(); + let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk(); + let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"line")); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.eof()); diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index d610afc6..3895c8c7 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -41,7 +41,9 @@ impl From for DecoderError { impl H1Decoder { pub fn new() -> H1Decoder { - H1Decoder { decoder: None } + H1Decoder { + decoder: None, + } } pub fn decode( @@ -59,9 +61,7 @@ impl H1Decoder { } } - match self.parse_message(src, settings) - .map_err(DecoderError::Error)? - { + match self.parse_message(src, settings).map_err(DecoderError::Error)? { Async::Ready((msg, decoder)) => { if let Some(decoder) = decoder { self.decoder = Some(decoder); @@ -103,7 +103,7 @@ impl H1Decoder { let (len, method, path, version, headers_len) = { let b = unsafe { let b: &[u8] = buf; - mem::transmute(b) + &*(b as *const [u8]) }; let mut req = httparse::Request::new(&mut headers); match req.parse(b)? { @@ -415,10 +415,9 @@ impl ChunkedState { match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size LF", - )), + _ => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk size LF")) + } } } @@ -451,37 +450,33 @@ impl ChunkedState { fn read_body_cr(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body CR", - )), + _ => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk body CR")) + } } } fn read_body_lf(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\n' => Ok(Async::Ready(ChunkedState::Size)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body LF", - )), + _ => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk body LF")) + } } } fn read_end_cr(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\r' => Ok(Async::Ready(ChunkedState::EndLf)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end CR", - )), + _ => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk end CR")) + } } } fn read_end_lf(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\n' => Ok(Async::Ready(ChunkedState::End)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end LF", - )), + _ => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk end LF")) + } } } } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 3d94d44c..08d40d09 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -2,8 +2,8 @@ use bytes::BufMut; use futures::{Async, Poll}; +use std::io; use std::rc::Rc; -use std::{io, mem}; use tokio_io::AsyncWrite; use super::encoding::ContentEncoder; @@ -13,10 +13,10 @@ use super::shared::SharedBytes; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; use header::ContentEncoding; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE}; +use http::{Method, Version}; use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; -use http::{Method, Version}; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE}; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific @@ -42,7 +42,7 @@ pub(crate) struct H1Writer { impl H1Writer { pub fn new( - stream: T, buf: SharedBytes, settings: Rc> + stream: T, buf: SharedBytes, settings: Rc>, ) -> H1Writer { H1Writer { flags: Flags::empty(), @@ -117,8 +117,7 @@ impl Writer for H1Writer { let version = msg.version().unwrap_or_else(|| req.version); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")); + msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive else if self.flags.contains(Flags::KEEPALIVE) { @@ -127,8 +126,7 @@ impl Writer for H1Writer { .insert(CONNECTION, HeaderValue::from_static("keep-alive")); } } else if version >= Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("close")); + msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("close")); } let body = msg.replace_body(Body::Empty); @@ -169,7 +167,7 @@ impl Writer for H1Writer { let mut pos = 0; let mut has_date = false; let mut remaining = buffer.remaining_mut(); - let mut buf: &mut [u8] = unsafe { mem::transmute(buffer.bytes_mut()) }; + let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; for (key, value) in msg.headers() { if is_bin && key == CONTENT_LENGTH { is_bin = false; @@ -184,7 +182,7 @@ impl Writer for H1Writer { pos = 0; buffer.reserve(len); remaining = buffer.remaining_mut(); - buf = unsafe { mem::transmute(buffer.bytes_mut()) }; + buf = unsafe { &mut *(buffer.bytes_mut() as *mut _) }; } buf[pos..pos + k.len()].copy_from_slice(k); @@ -272,7 +270,8 @@ impl Writer for H1Writer { #[inline] fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { if !self.buffer.is_empty() { - let buf: &[u8] = unsafe { mem::transmute(self.buffer.as_ref()) }; + let buf: &[u8] = + unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) }; let written = self.write_data(buf)?; let _ = self.buffer.split_to(written); if self.buffer.len() > self.buffer_capacity { diff --git a/src/server/h2.rs b/src/server/h2.rs index a5ac2cfc..e2013357 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -61,7 +61,7 @@ where H: HttpHandler + 'static, { pub fn new( - settings: Rc>, io: T, addr: Option, buf: Bytes + settings: Rc>, io: T, addr: Option, buf: Bytes, ) -> Self { Http2 { flags: Flags::empty(), diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 168fd8af..a9dc06fd 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -45,7 +45,7 @@ pub(crate) struct H2Writer { impl H2Writer { pub fn new( - respond: SendResponse, buf: SharedBytes, settings: Rc> + respond: SendResponse, buf: SharedBytes, settings: Rc>, ) -> H2Writer { H2Writer { respond, @@ -107,8 +107,7 @@ impl Writer for H2Writer { ); } Body::Empty => { - msg.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + msg.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); } _ => (), } @@ -120,9 +119,7 @@ impl Writer for H2Writer { resp.headers_mut().insert(key, value.clone()); } - match self.respond - .send_response(resp, self.flags.contains(Flags::EOF)) - { + match self.respond.send_response(resp, self.flags.contains(Flags::EOF)) { Ok(stream) => self.stream = Some(stream), Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), } diff --git a/src/server/helpers.rs b/src/server/helpers.rs index bb8730ec..ae8c2be8 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -69,7 +69,7 @@ impl SharedHttpInnerMessage { } pub fn new( - msg: Rc, pool: Rc + msg: Rc, pool: Rc, ) -> SharedHttpInnerMessage { SharedHttpInnerMessage(Some(msg), Some(pool)) } @@ -79,7 +79,7 @@ impl SharedHttpInnerMessage { #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] pub fn get_mut(&self) -> &mut HttpInnerMessage { let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref(); - unsafe { mem::transmute(r) } + unsafe { &mut *(r as *const _ as *mut _) } } #[inline(always)] @@ -96,9 +96,8 @@ const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 8081828384858687888990919293949596979899"; pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; 13] = [ - b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', 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', @@ -251,63 +250,33 @@ mod tests { let mut bytes = BytesMut::new(); bytes.reserve(50); write_content_length(0, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 0\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); bytes.reserve(50); write_content_length(9, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 9\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); bytes.reserve(50); write_content_length(10, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 10\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); bytes.reserve(50); write_content_length(99, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 99\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); bytes.reserve(50); write_content_length(100, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 100\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); bytes.reserve(50); write_content_length(101, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 101\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); bytes.reserve(50); write_content_length(998, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 998\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); bytes.reserve(50); write_content_length(1000, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 1000\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); bytes.reserve(50); write_content_length(1001, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 1001\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); bytes.reserve(50); write_content_length(5909, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 5909\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); } } diff --git a/src/server/settings.rs b/src/server/settings.rs index 1b57db1a..291fcf13 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -8,10 +8,10 @@ use std::sync::Arc; use std::{fmt, mem, net}; use time; -use super::KeepAlive; use super::channel::Node; use super::helpers; use super::shared::{SharedBytes, SharedBytesPool}; +use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; @@ -72,7 +72,7 @@ impl Default for ServerSettings { impl ServerSettings { /// Crate server settings instance pub(crate) fn new( - addr: Option, host: &Option, secure: bool + addr: Option, host: &Option, secure: bool, ) -> ServerSettings { let host = if let Some(ref host) = *host { host.clone() @@ -119,7 +119,7 @@ impl ServerSettings { #[inline] pub(crate) fn get_response_builder( - &self, status: StatusCode + &self, status: StatusCode, ) -> HttpResponseBuilder { HttpResponsePool::get_builder(&self.responses, status) } @@ -255,10 +255,7 @@ mod tests { #[test] fn test_date_len() { - assert_eq!( - DATE_VALUE_LENGTH, - "Sun, 06 Nov 1994 08:49:37 GMT".len() - ); + assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); } #[test] diff --git a/src/server/shared.rs b/src/server/shared.rs index a3ddc378..2d7e285b 100644 --- a/src/server/shared.rs +++ b/src/server/shared.rs @@ -1,8 +1,8 @@ use bytes::{BufMut, BytesMut}; use std::cell::RefCell; use std::collections::VecDeque; +use std::io; use std::rc::Rc; -use std::{io, mem}; use body::Binary; @@ -61,7 +61,7 @@ impl SharedBytes { #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] pub(crate) fn get_mut(&self) -> &mut BytesMut { let r: &BytesMut = self.0.as_ref().unwrap().as_ref(); - unsafe { mem::transmute(r) } + unsafe { &mut *(r as *const _ as *mut _) } } #[inline] diff --git a/src/server/srv.rs b/src/server/srv.rs index 314dc836..276e1e20 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -219,10 +219,7 @@ where if let Some(e) = err.take() { Err(e) } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Can not bind to address.", - )) + Err(io::Error::new(io::ErrorKind::Other, "Can not bind to address.")) } } else { Ok(self) @@ -230,7 +227,7 @@ where } fn start_workers( - &mut self, settings: &ServerSettings, handler: &StreamHandlerType + &mut self, settings: &ServerSettings, handler: &StreamHandlerType, ) -> Vec<(usize, mpsc::UnboundedSender>)> { // start workers let mut workers = Vec::new(); @@ -332,9 +329,9 @@ impl HttpServer { ctx.add_stream(rx); self }); - signals.map(|signals| { + if let Some(signals) = signals { signals.do_send(signal::Subscribe(addr.clone().recipient())) - }); + } addr } } @@ -378,10 +375,7 @@ impl HttpServer { /// Start listening for incoming tls connections. pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result> { if self.sockets.is_empty() { - Err(io::Error::new( - io::ErrorKind::Other, - "No socket addresses are bound", - )) + Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) } else { let (tx, rx) = mpsc::unbounded(); let addrs: Vec<(net::SocketAddr, net::TcpListener)> = @@ -427,13 +421,10 @@ impl HttpServer { /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn start_ssl( - mut self, mut builder: SslAcceptorBuilder + mut self, mut builder: SslAcceptorBuilder, ) -> io::Result> { if self.sockets.is_empty() { - Err(io::Error::new( - io::ErrorKind::Other, - "No socket addresses are bound", - )) + Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) } else { // alpn support if !self.no_http2 { @@ -545,8 +536,9 @@ impl HttpServer { })); self }); - signals - .map(|signals| signals.do_send(signal::Subscribe(addr.clone().recipient()))); + if let Some(signals) = signals { + signals.do_send(signal::Subscribe(addr.clone().recipient())) + } addr } } @@ -562,17 +554,35 @@ impl Handler for HttpServer { signal::SignalType::Int => { info!("SIGINT received, exiting"); self.exit = true; - Handler::::handle(self, StopServer { graceful: false }, ctx); + Handler::::handle( + self, + StopServer { + graceful: false, + }, + ctx, + ); } signal::SignalType::Term => { info!("SIGTERM received, stopping"); self.exit = true; - Handler::::handle(self, StopServer { graceful: true }, ctx); + Handler::::handle( + self, + StopServer { + graceful: true, + }, + ctx, + ); } signal::SignalType::Quit => { info!("SIGQUIT received, exiting"); self.exit = true; - Handler::::handle(self, StopServer { graceful: false }, ctx); + Handler::::handle( + self, + StopServer { + graceful: false, + }, + ctx, + ); } _ => (), } @@ -696,7 +706,9 @@ impl Handler for HttpServer { let tx2 = tx.clone(); worker .1 - .send(StopWorker { graceful: dur }) + .send(StopWorker { + graceful: dur, + }) .into_actor(self) .then(move |_, slf, ctx| { slf.workers.pop(); @@ -746,9 +758,8 @@ fn start_accept_thread( // start accept thread #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - let _ = thread::Builder::new() - .name(format!("Accept on {}", addr)) - .spawn(move || { + let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn( + move || { const SRV: mio::Token = mio::Token(0); const CMD: mio::Token = mio::Token(1); @@ -773,12 +784,9 @@ fn start_accept_thread( } // Start listening for incoming commands - if let Err(err) = poll.register( - ®, - CMD, - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { + if let Err(err) = + poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge()) + { panic!("Can not register Registration: {}", err); } @@ -909,13 +917,14 @@ fn start_accept_thread( } } } - }); + }, + ); (readiness, tx) } fn create_tcp_listener( - addr: net::SocketAddr, backlog: i32 + addr: net::SocketAddr, backlog: i32, ) -> io::Result { let builder = match addr { net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, diff --git a/src/server/utils.rs b/src/server/utils.rs index 430fb211..e0e7e7f6 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -8,7 +8,7 @@ const LW_BUFFER_SIZE: usize = 4096; const HW_BUFFER_SIZE: usize = 32_768; pub fn read_from_io( - io: &mut T, buf: &mut BytesMut + io: &mut T, buf: &mut BytesMut, ) -> Poll { unsafe { if buf.remaining_mut() < LW_BUFFER_SIZE { diff --git a/src/server/worker.rs b/src/server/worker.rs index a6ec0711..16eb2946 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,5 +1,5 @@ -use futures::Future; use futures::unsync::oneshot; +use futures::Future; use net2::TcpStreamExt; use std::rc::Rc; use std::{net, time}; @@ -59,7 +59,7 @@ where impl Worker { pub(crate) fn new( - h: Vec, handler: StreamHandlerType, keep_alive: KeepAlive + h: Vec, handler: StreamHandlerType, keep_alive: KeepAlive, ) -> Worker { let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { Some(time::Duration::new(val as u64, 0)) @@ -77,13 +77,11 @@ impl Worker { fn update_time(&self, ctx: &mut Context) { self.settings.update_date(); - ctx.run_later(time::Duration::new(1, 0), |slf, ctx| { - slf.update_time(ctx) - }); + 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 + &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| { @@ -124,8 +122,7 @@ where if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { error!("Can not set socket keep-alive option"); } - self.handler - .handle(Rc::clone(&self.settings), &self.hnd, msg); + self.handler.handle(Rc::clone(&self.settings), &self.hnd, msg); } } @@ -165,7 +162,7 @@ pub(crate) enum StreamHandlerType { impl StreamHandlerType { fn handle( - &mut self, h: Rc>, hnd: &Handle, msg: Conn + &mut self, h: Rc>, hnd: &Handle, msg: Conn, ) { match *self { StreamHandlerType::Normal => { @@ -177,60 +174,57 @@ impl StreamHandlerType { } #[cfg(feature = "tls")] StreamHandlerType::Tls(ref acceptor) => { - let Conn { io, peer, http2 } = msg; + let Conn { + io, + peer, + http2, + } = msg; let _ = io.set_nodelay(true); 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(())) - }), - ); + 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 Conn { + io, + peer, + .. + } = msg; let _ = io.set_nodelay(true); 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(())) - }), - ); + 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/src/test.rs b/src/test.rs index c93e721b..d8ae8067 100644 --- a/src/test.rs +++ b/src/test.rs @@ -170,14 +170,22 @@ impl TestServer { if uri.starts_with('/') { format!( "{}://{}{}", - if self.ssl { "https" } else { "http" }, + if self.ssl { + "https" + } else { + "http" + }, self.addr, uri ) } else { format!( "{}://{}/{}", - if self.ssl { "https" } else { "http" }, + if self.ssl { + "https" + } else { + "http" + }, self.addr, uri ) @@ -202,7 +210,7 @@ impl TestServer { /// Connect to websocket server pub fn ws( - &mut self + &mut self, ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { let url = self.url("/"); self.system.run_until_complete( @@ -350,17 +358,14 @@ pub struct TestApp { impl TestApp { fn new(state: S) -> TestApp { let app = App::with_state(state); - TestApp { app: Some(app) } + 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)), - ); + self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler))); } /// Register middleware @@ -594,7 +599,7 @@ impl TestRequest { /// /// This method panics is handler returns actor or async result. pub fn run>( - self, mut h: H + self, mut h: H, ) -> Result>::Result as Responder>::Error> { let req = self.finish(); let resp = h.handle(req.clone()); diff --git a/src/uri.rs b/src/uri.rs index d30fe5cb..f2e16cec 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -44,7 +44,10 @@ impl Url { pub fn new(uri: Uri) -> Url { let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); - Url { uri, path } + Url { + uri, + path, + } } pub fn uri(&self) -> &Uri { diff --git a/src/with.rs b/src/with.rs index 07e9efc4..a18139dc 100644 --- a/src/with.rs +++ b/src/with.rs @@ -187,7 +187,7 @@ where S: 'static, { pub fn new( - f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig + f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig, ) -> Self { With2 { hnd: Rc::new(UnsafeCell::new(f)), @@ -307,10 +307,8 @@ where Async::Ready(item) => { self.item = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new(T2::from_request( - &self.req, - self.cfg2.as_ref(), - ))); + self.fut2 = + Some(Box::new(T2::from_request(&self.req, self.cfg2.as_ref()))); } Async::NotReady => return Ok(Async::NotReady), } @@ -510,10 +508,8 @@ where Async::Ready(item) => { self.item1 = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new(T2::from_request( - &self.req, - self.cfg2.as_ref(), - ))); + self.fut2 = + Some(Box::new(T2::from_request(&self.req, self.cfg2.as_ref()))); } Async::NotReady => return Ok(Async::NotReady), } @@ -524,10 +520,8 @@ where Async::Ready(item) => { self.item2 = Some(item); self.fut2.take(); - self.fut3 = Some(Box::new(T3::from_request( - &self.req, - self.cfg3.as_ref(), - ))); + self.fut3 = + Some(Box::new(T3::from_request(&self.req, self.cfg3.as_ref()))); } Async::NotReady => return Ok(Async::NotReady), } @@ -539,15 +533,13 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - let item = match (*hnd)( - self.item1.take().unwrap(), - self.item2.take().unwrap(), - item, - ).respond_to(self.req.drop_state()) - { - Ok(item) => item.into(), - Err(err) => return Err(err.into()), - }; + let item = + match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item) + .respond_to(self.req.drop_state()) + { + Ok(item) => item.into(), + Err(err) => return Err(err.into()), + }; match item.into() { ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), diff --git a/src/ws/client.rs b/src/ws/client.rs index 5a8f10e0..174ee4a1 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -121,7 +121,7 @@ impl Client { /// Create new websocket connection with custom `ClientConnector` pub fn with_connector>( - uri: S, conn: Addr + uri: S, conn: Addr, ) -> Client { let mut cl = Client { request: ClientRequest::build(), @@ -142,9 +142,8 @@ impl Client { U: IntoIterator + 'static, V: AsRef, { - let mut protos = protos - .into_iter() - .fold(String::new(), |acc, s| acc + s.as_ref() + ","); + let mut protos = + protos.into_iter().fold(String::new(), |acc, s| acc + s.as_ref() + ","); protos.pop(); self.protocols = Some(protos); self @@ -218,8 +217,7 @@ impl Client { self.request.upgrade(); self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::CONNECTION, "upgrade"); - self.request - .set_header(header::SEC_WEBSOCKET_VERSION, "13"); + self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); self.request.with_connector(self.conn.clone()); if let Some(protocols) = self.protocols.take() { @@ -394,10 +392,7 @@ impl Future for ClientHandshake { encoded, key ); - return Err(ClientError::InvalidChallengeResponse( - encoded, - key.clone(), - )); + return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); } } else { trace!("Missing SEC-WEBSOCKET-ACCEPT header"); @@ -416,7 +411,9 @@ impl Future for ClientHandshake { inner: Rc::clone(&inner), max_size: self.max_size, }, - ClientWriter { inner }, + ClientWriter { + inner, + }, ))) } } @@ -536,23 +533,13 @@ impl ClientWriter { /// Send ping frame #[inline] pub fn ping(&mut self, message: &str) { - self.write(Frame::message( - Vec::from(message), - OpCode::Ping, - true, - true, - )); + self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true)); } /// Send pong frame #[inline] pub fn pong(&mut self, message: &str) { - self.write(Frame::message( - Vec::from(message), - OpCode::Pong, - true, - true, - )); + self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true)); } /// Send close frame diff --git a/src/ws/context.rs b/src/ws/context.rs index b3831258..f76532cc 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -156,23 +156,13 @@ where /// Send ping frame #[inline] pub fn ping(&mut self, message: &str) { - self.write(Frame::message( - Vec::from(message), - OpCode::Ping, - true, - false, - )); + self.write(Frame::message(Vec::from(message), OpCode::Ping, true, false)); } /// Send pong frame #[inline] pub fn pong(&mut self, message: &str) { - self.write(Frame::message( - Vec::from(message), - OpCode::Pong, - true, - false, - )); + self.write(Frame::message(Vec::from(message), OpCode::Pong, true, false)); } /// Send close frame diff --git a/src/ws/frame.rs b/src/ws/frame.rs index de78b31d..5c4d5e4a 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -8,9 +8,9 @@ use body::Binary; use error::PayloadError; use payload::PayloadHelper; -use ws::ProtocolError; use ws::mask::apply_mask; use ws::proto::{CloseCode, CloseReason, OpCode}; +use ws::ProtocolError; /// A struct representing a `WebSocket` frame. #[derive(Debug)] @@ -36,7 +36,7 @@ impl Frame { NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); let mut payload = Vec::from(&code_bytes[..]); - if let Some(description) = reason.description{ + if let Some(description) = reason.description { payload.extend(description.as_bytes()); } payload @@ -48,7 +48,7 @@ impl Frame { #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] fn read_copy_md( - pl: &mut PayloadHelper, server: bool, max_size: usize + pl: &mut PayloadHelper, server: bool, max_size: usize, ) -> Poll)>, ProtocolError> where S: Stream, @@ -122,17 +122,11 @@ impl Frame { None }; - Ok(Async::Ready(Some(( - idx, - finished, - opcode, - length, - mask, - )))) + Ok(Async::Ready(Some((idx, finished, opcode, length, mask)))) } fn read_chunk_md( - chunk: &[u8], server: bool, max_size: usize + chunk: &[u8], server: bool, max_size: usize, ) -> Poll<(usize, bool, OpCode, usize, Option), ProtocolError> { let chunk_len = chunk.len(); @@ -203,7 +197,7 @@ impl Frame { /// Parse the input stream into a frame. pub fn parse( - pl: &mut PayloadHelper, server: bool, max_size: usize + pl: &mut PayloadHelper, server: bool, max_size: usize, ) -> Poll, ProtocolError> where S: Stream, @@ -288,7 +282,10 @@ impl Frame { } else { None }; - Some(CloseReason { code, description }) + Some(CloseReason { + code, + description, + }) } else { None } @@ -296,7 +293,7 @@ impl Frame { /// Generate binary representation pub fn message>( - data: B, code: OpCode, finished: bool, genmask: bool + data: B, code: OpCode, finished: bool, genmask: bool, ) -> Binary { let payload = data.into(); let one: u8 = if finished { diff --git a/src/ws/mask.rs b/src/ws/mask.rs index f78258fa..13246d97 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -28,7 +28,11 @@ fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { // Possible first unaligned block. let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3); let mask_u32 = if head > 0 { - let n = if head > 4 { head - 4 } else { head }; + let n = if head > 4 { + head - 4 + } else { + head + }; let mask_u32 = if n > 0 { unsafe { diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 93fd431c..42cd3589 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -133,24 +133,24 @@ pub enum HandshakeError { impl ResponseError for HandshakeError { fn error_response(&self) -> HttpResponse { match *self { - HandshakeError::GetMethodRequired => HttpResponse::MethodNotAllowed() - .header(header::ALLOW, "GET") - .finish(), + HandshakeError::GetMethodRequired => { + HttpResponse::MethodNotAllowed().header(header::ALLOW, "GET").finish() + } HandshakeError::NoWebsocketUpgrade => HttpResponse::BadRequest() .reason("No WebSocket UPGRADE header found") .finish(), - HandshakeError::NoConnectionUpgrade => HttpResponse::BadRequest() - .reason("No CONNECTION upgrade") - .finish(), + HandshakeError::NoConnectionUpgrade => { + HttpResponse::BadRequest().reason("No CONNECTION upgrade").finish() + } HandshakeError::NoVersionHeader => HttpResponse::BadRequest() .reason("Websocket version header is required") .finish(), - HandshakeError::UnsupportedVersion => HttpResponse::BadRequest() - .reason("Unsupported version") - .finish(), - HandshakeError::BadWebsocketKey => HttpResponse::BadRequest() - .reason("Handshake error") - .finish(), + HandshakeError::UnsupportedVersion => { + HttpResponse::BadRequest().reason("Unsupported version").finish() + } + HandshakeError::BadWebsocketKey => { + HttpResponse::BadRequest().reason("Handshake error").finish() + } } } } @@ -189,7 +189,7 @@ where // /// the returned response headers contain the first protocol in this list // /// which the server also knows. pub fn handshake( - req: &HttpRequest + req: &HttpRequest, ) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { @@ -216,9 +216,7 @@ pub fn handshake( } // check supported version - if !req.headers() - .contains_key(header::SEC_WEBSOCKET_VERSION) - { + if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) { return Err(HandshakeError::NoVersionHeader); } let supported_ver = { @@ -355,10 +353,7 @@ mod tests { HeaderMap::new(), None, ); - assert_eq!( - HandshakeError::GetMethodRequired, - handshake(&req).err().unwrap() - ); + assert_eq!(HandshakeError::GetMethodRequired, handshake(&req).err().unwrap()); let req = HttpRequest::new( Method::GET, @@ -367,16 +362,10 @@ mod tests { HeaderMap::new(), None, ); - assert_eq!( - HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() - ); + assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("test"), - ); + headers.insert(header::UPGRADE, header::HeaderValue::from_static("test")); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -384,16 +373,10 @@ mod tests { headers, None, ); - assert_eq!( - HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() - ); + assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); + headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -401,20 +384,11 @@ mod tests { headers, None, ); - assert_eq!( - HandshakeError::NoConnectionUpgrade, - handshake(&req).err().unwrap() - ); + assert_eq!(HandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - headers.insert( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ); + headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); + headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -422,20 +396,11 @@ mod tests { headers, None, ); - assert_eq!( - HandshakeError::NoVersionHeader, - handshake(&req).err().unwrap() - ); + assert_eq!(HandshakeError::NoVersionHeader, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - headers.insert( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ); + headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); + headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); headers.insert( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), @@ -447,20 +412,11 @@ mod tests { headers, None, ); - assert_eq!( - HandshakeError::UnsupportedVersion, - handshake(&req).err().unwrap() - ); + assert_eq!(HandshakeError::UnsupportedVersion, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - headers.insert( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ); + headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); + headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); headers.insert( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), @@ -472,28 +428,17 @@ mod tests { headers, None, ); - assert_eq!( - HandshakeError::BadWebsocketKey, - handshake(&req).err().unwrap() - ); + assert_eq!(HandshakeError::BadWebsocketKey, handshake(&req).err().unwrap()); let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - headers.insert( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ); + headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); + headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); headers.insert( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), ); - headers.insert( - header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13"), - ); + headers + .insert(header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13")); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), diff --git a/src/ws/proto.rs b/src/ws/proto.rs index 2851fb9e..6e07ca7e 100644 --- a/src/ws/proto.rs +++ b/src/ws/proto.rs @@ -194,11 +194,11 @@ impl From for CloseReason { } } -impl > From<(CloseCode, T)> for CloseReason { +impl> From<(CloseCode, T)> for CloseReason { fn from(info: (CloseCode, T)) -> Self { - CloseReason{ + CloseReason { code: info.0, - description: Some(info.1.into()) + description: Some(info.1.into()), } } } diff --git a/tests/test_client.rs b/tests/test_client.rs index b9154cc4..fc6007b0 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -9,8 +9,8 @@ use std::io::Read; use bytes::Bytes; use flate2::read::GzDecoder; -use futures::Future; use futures::stream::once; +use futures::Future; use rand::Rng; use actix_web::*; @@ -72,10 +72,7 @@ fn test_with_query_parameter() { }) }); - let request = srv.get() - .uri(srv.url("/?qp=5").as_str()) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -124,10 +121,8 @@ fn test_client_gzip_encoding() { }); // client request - let request = srv.post() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - .unwrap(); + let request = + srv.post().content_encoding(http::ContentEncoding::Gzip).body(STR).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -167,10 +162,7 @@ fn test_client_gzip_encoding_large() { #[test] fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(100_000) - .collect::(); + let data = rand::thread_rng().gen_ascii_chars().take(100_000).collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -228,10 +220,7 @@ fn test_client_brotli_encoding() { #[cfg(feature = "brotli")] #[test] fn test_client_brotli_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); + let data = rand::thread_rng().gen_ascii_chars().take(70_000).collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -275,10 +264,8 @@ fn test_client_deflate_encoding() { }); // client request - let request = srv.post() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - .unwrap(); + let request = + srv.post().content_encoding(http::ContentEncoding::Deflate).body(STR).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -290,10 +277,7 @@ fn test_client_deflate_encoding() { #[cfg(feature = "brotli")] #[test] fn test_client_deflate_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); + let data = rand::thread_rng().gen_ascii_chars().take(70_000).collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -338,9 +322,7 @@ fn test_client_streaming_explicit() { let body = once(Ok(Bytes::from_static(STR.as_ref()))); - let request = srv.get() - .body(Body::Streaming(Box::new(body))) - .unwrap(); + let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -413,11 +395,8 @@ fn test_client_cookie_handling() { }) }); - let request = srv.get() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - .unwrap(); + let request = + srv.get().cookie(cookie1.clone()).cookie(cookie2.clone()).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); let c1 = response.cookie("cookie1").expect("Missing cookie1"); diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 7a9abe97..65d72724 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -26,10 +26,7 @@ fn test_path_extractor() { }); // client request - let request = srv.get() - .uri(srv.url("/test/index.html")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -47,10 +44,7 @@ fn test_query_extractor() { }); // client request - let request = srv.get() - .uri(srv.url("/index.html?username=test")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/index.html?username=test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -59,10 +53,7 @@ fn test_query_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); // client request - let request = srv.get() - .uri(srv.url("/index.html")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/index.html")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -78,10 +69,8 @@ fn test_path_and_query_extractor() { }); // client request - let request = srv.get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); + let request = + srv.get().uri(srv.url("/test1/index.html?username=test2")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -90,10 +79,7 @@ fn test_path_and_query_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); // client request - let request = srv.get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/test1/index.html")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -102,18 +88,15 @@ fn test_path_and_query_extractor() { fn test_path_and_query_extractor2() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route() - .with3(|_: HttpRequest, p: Path, q: Query| { - format!("Welcome {} - {}!", p.username, q.username) - }) + r.route().with3(|_: HttpRequest, p: Path, q: Query| { + format!("Welcome {} - {}!", p.username, q.username) + }) }); }); // client request - let request = srv.get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); + let request = + srv.get().uri(srv.url("/test1/index.html?username=test2")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -122,10 +105,7 @@ fn test_path_and_query_extractor2() { assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); // client request - let request = srv.get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/test1/index.html")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -137,10 +117,7 @@ fn test_non_ascii_route() { }); // client request - let request = srv.get() - .uri(srv.url("/中文/index.html")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/中文/index.html")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -158,17 +135,12 @@ fn test_unsafe_path_route() { }); // client request - let request = srv.get() - .uri(srv.url("/test/http%3A%2F%2Fexample.com")) - .finish() - .unwrap(); + let request = + srv.get().uri(srv.url("/test/http%3A%2F%2Fexample.com")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"success: http:%2F%2Fexample.com") - ); + assert_eq!(bytes, Bytes::from_static(b"success: http:%2F%2Fexample.com")); } diff --git a/tests/test_server.rs b/tests/test_server.rs index cfbff6d8..7bb8a6cd 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -14,9 +14,9 @@ extern crate brotli2; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{Bytes, BytesMut}; -use flate2::Compression; use flate2::read::GzDecoder; use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; +use flate2::Compression; use futures::stream::once; use futures::{future, Future, Stream}; use h2::client as h2client; @@ -62,11 +62,9 @@ fn test_start() { thread::spawn(move || { let sys = System::new("test"); let srv = server::new(|| { - vec![ - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }), - ] + vec![App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + })] }); let srv = srv.bind("127.0.0.1:0").unwrap(); @@ -113,11 +111,9 @@ fn test_shutdown() { thread::spawn(move || { let sys = System::new("test"); let srv = server::new(|| { - vec![ - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }), - ] + vec![App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + })] }); let srv = srv.bind("127.0.0.1:0").unwrap(); @@ -135,7 +131,9 @@ fn test_shutdown() { .finish() .unwrap(); let response = sys.run_until_complete(req.send()).unwrap(); - srv_addr.do_send(server::StopServer { graceful: true }); + srv_addr.do_send(server::StopServer { + graceful: true, + }); assert!(response.status().is_success()); } @@ -208,9 +206,7 @@ fn test_body() { fn test_body_gzip() { let mut srv = test::TestServer::new(|app| { app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) + HttpResponse::Ok().content_encoding(http::ContentEncoding::Gzip).body(STR) }) }); @@ -258,10 +254,7 @@ fn test_body_gzip_large() { #[test] fn test_body_gzip_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); + let data = rand::thread_rng().gen_ascii_chars().take(70_000).collect::(); let srv_data = Arc::new(data.clone()); let mut srv = test::TestServer::new(move |app| { @@ -342,11 +335,7 @@ fn test_body_br_streaming() { #[test] fn test_head_empty() { let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_length(STR.len() as u64) - .finish() - }) + app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish()) }); let request = srv.head().finish().unwrap(); @@ -354,10 +343,7 @@ fn test_head_empty() { assert!(response.status().is_success()); { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); + let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } @@ -382,10 +368,7 @@ fn test_head_binary() { assert!(response.status().is_success()); { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); + let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } @@ -409,10 +392,7 @@ fn test_head_binary2() { assert!(response.status().is_success()); { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); + let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } } @@ -468,9 +448,7 @@ fn test_body_chunked_explicit() { fn test_body_deflate() { let mut srv = test::TestServer::new(|app| { app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) + HttpResponse::Ok().content_encoding(http::ContentEncoding::Deflate).body(STR) }) }); @@ -494,9 +472,7 @@ fn test_body_deflate() { fn test_body_brotli() { let mut srv = test::TestServer::new(|app| { app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(STR) + HttpResponse::Ok().content_encoding(http::ContentEncoding::Br).body(STR) }) }); @@ -580,10 +556,7 @@ fn test_gzip_encoding_large() { #[test] fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(60_000) - .collect::(); + let data = rand::thread_rng().gen_ascii_chars().take(60_000).collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -634,10 +607,8 @@ fn test_reading_deflate_encoding() { let enc = e.finish().unwrap(); // client request - let request = srv.post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); + let request = + srv.post().header(http::header::CONTENT_ENCODING, "deflate").body(enc).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -666,10 +637,8 @@ fn test_reading_deflate_encoding_large() { let enc = e.finish().unwrap(); // client request - let request = srv.post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); + let request = + srv.post().header(http::header::CONTENT_ENCODING, "deflate").body(enc).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -680,10 +649,7 @@ fn test_reading_deflate_encoding_large() { #[test] fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(160_000) - .collect::(); + let data = rand::thread_rng().gen_ascii_chars().take(160_000).collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -702,10 +668,8 @@ fn test_reading_deflate_encoding_large_random() { let enc = e.finish().unwrap(); // client request - let request = srv.post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); + let request = + srv.post().header(http::header::CONTENT_ENCODING, "deflate").body(enc).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -735,10 +699,8 @@ fn test_brotli_encoding() { let enc = e.finish().unwrap(); // client request - let request = srv.post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); + let request = + srv.post().header(http::header::CONTENT_ENCODING, "br").body(enc).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -768,10 +730,8 @@ fn test_brotli_encoding_large() { let enc = e.finish().unwrap(); // client request - let request = srv.post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); + let request = + srv.post().header(http::header::CONTENT_ENCODING, "br").body(enc).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -789,30 +749,29 @@ fn test_h2() { let handle = core.handle(); let tcp = TcpStream::connect(&addr, &handle); - let tcp = tcp.then(|res| h2client::handshake(res.unwrap())) - .then(move |res| { - let (mut client, h2) = res.unwrap(); + let tcp = tcp.then(|res| h2client::handshake(res.unwrap())).then(move |res| { + let (mut client, h2) = res.unwrap(); - let request = Request::builder() - .uri(format!("https://{}/", addr).as_str()) - .body(()) - .unwrap(); - let (response, _) = client.send_request(request, false).unwrap(); + let request = Request::builder() + .uri(format!("https://{}/", addr).as_str()) + .body(()) + .unwrap(); + let (response, _) = client.send_request(request, false).unwrap(); - // Spawn a task to run the conn... - handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); + // Spawn a task to run the conn... + handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); - response.and_then(|response| { - assert_eq!(response.status(), http::StatusCode::OK); + response.and_then(|response| { + assert_eq!(response.status(), http::StatusCode::OK); - let (_, body) = response.into_parts(); + let (_, body) = response.into_parts(); - body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { - b.extend(c); - Ok(b) - }) + body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { + b.extend(c); + Ok(b) }) - }); + }) + }); let _res = core.run(tcp); // assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref())); } @@ -836,28 +795,20 @@ struct MiddlewareTest { impl middleware::Middleware for MiddlewareTest { fn start(&self, _: &mut HttpRequest) -> Result { - self.start.store( - self.start.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); + self.start.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Started::Done) } fn response( - &self, _: &mut HttpRequest, resp: HttpResponse + &self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { - self.response.store( - self.response.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); + self.response + .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Response::Done(resp)) } fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish.store( - self.finish.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); + self.finish.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middleware::Finished::Done } } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 1283b2e8..563f8f12 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -44,12 +44,7 @@ fn test_simple() { writer.binary(b"text".as_ref()); let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary( - Bytes::from_static(b"text").into() - )) - ); + assert_eq!(item, Some(ws::Message::Binary(Bytes::from_static(b"text").into()))); writer.ping("ping"); let (item, reader) = srv.execute(reader.into_future()).unwrap(); @@ -75,7 +70,8 @@ fn test_close_description() { let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (reader, mut writer) = srv.ws().unwrap(); - let close_reason:ws::CloseReason = (ws::CloseCode::Normal, "close description").into(); + let close_reason: ws::CloseReason = + (ws::CloseCode::Normal, "close description").into(); writer.close(Some(close_reason.clone())); let (item, _) = srv.execute(reader.into_future()).unwrap(); assert_eq!(item, Some(ws::Message::Close(Some(close_reason)))); @@ -83,10 +79,7 @@ fn test_close_description() { #[test] fn test_large_text() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(65_536) - .collect::(); + let data = rand::thread_rng().gen_ascii_chars().take(65_536).collect::(); let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (mut reader, mut writer) = srv.ws().unwrap(); @@ -101,10 +94,7 @@ fn test_large_text() { #[test] fn test_large_bin() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(65_536) - .collect::(); + let data = rand::thread_rng().gen_ascii_chars().take(65_536).collect::(); let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (mut reader, mut writer) = srv.ws().unwrap(); @@ -113,10 +103,7 @@ fn test_large_bin() { writer.binary(data.clone()); let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; - assert_eq!( - item, - Some(ws::Message::Binary(Binary::from(data.clone()))) - ); + assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone())))); } } @@ -220,26 +207,20 @@ fn test_ws_server_ssl() { // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); + builder.set_private_key_file("tests/key.pem", SslFiletype::PEM).unwrap(); + builder.set_certificate_chain_file("tests/cert.pem").unwrap(); - let mut srv = test::TestServer::build() - .ssl(builder.build()) - .start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); + let mut srv = test::TestServer::build().ssl(builder.build()).start(|app| { + app.handler(|req| { + ws::start( + req, + Ws2 { + count: 0, + bin: false, + }, + ) + }) + }); let (mut reader, _writer) = srv.ws().unwrap(); let data = Some(ws::Message::Text("0".repeat(65_536))); diff --git a/tools/wsload/src/wsclient.rs b/tools/wsload/src/wsclient.rs index 6c431f2d..0fc36ac9 100644 --- a/tools/wsload/src/wsclient.rs +++ b/tools/wsload/src/wsclient.rs @@ -14,8 +14,8 @@ extern crate url; use futures::Future; use rand::{thread_rng, Rng}; -use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; use std::time::Duration; use actix::prelude::*; @@ -61,12 +61,8 @@ fn main() { let sample_rate = parse_u64_default(matches.value_of("sample-rate"), 1) as usize; let perf_counters = Arc::new(PerfCounters::new()); - let payload = Arc::new( - thread_rng() - .gen_ascii_chars() - .take(payload_size) - .collect::(), - ); + let payload = + Arc::new(thread_rng().gen_ascii_chars().take(payload_size).collect::()); let sys = actix::System::new("ws-client"); @@ -82,43 +78,40 @@ fn main() { let perf = perf_counters.clone(); let addr = Arbiter::new(format!("test {}", t)); - addr.do_send(actix::msgs::Execute::new( - move || -> Result<(), ()> { - for _ in 0..concurrency { - let pl2 = pl.clone(); - let perf2 = perf.clone(); - let ws2 = ws.clone(); + addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> { + for _ in 0..concurrency { + let pl2 = pl.clone(); + let perf2 = perf.clone(); + let ws2 = ws.clone(); - Arbiter::handle().spawn( - ws::Client::new(&ws) - .write_buffer_capacity(0) - .connect() - .map_err(|e| { - println!("Error: {}", e); - //Arbiter::system().do_send(actix::msgs::SystemExit(0)); - () - }) - .map(move |(reader, writer)| { - let addr: Addr = - ChatClient::create(move |ctx| { - ChatClient::add_stream(reader, ctx); - ChatClient { - url: ws2, - conn: writer, - payload: pl2, - bin: bin, - ts: time::precise_time_ns(), - perf_counters: perf2, - sent: 0, - max_payload_size: max_payload_size, - } - }); - }), - ); - } - Ok(()) - }, - )); + Arbiter::handle().spawn( + ws::Client::new(&ws) + .write_buffer_capacity(0) + .connect() + .map_err(|e| { + println!("Error: {}", e); + //Arbiter::system().do_send(actix::msgs::SystemExit(0)); + () + }) + .map(move |(reader, writer)| { + let addr: Addr = ChatClient::create(move |ctx| { + ChatClient::add_stream(reader, ctx); + ChatClient { + url: ws2, + conn: writer, + payload: pl2, + bin: bin, + ts: time::precise_time_ns(), + perf_counters: perf2, + sent: 0, + max_payload_size: max_payload_size, + } + }); + }), + ); + } + Ok(()) + })); } let res = sys.run(); @@ -126,10 +119,7 @@ fn main() { fn parse_u64_default(input: Option<&str>, default: u64) -> u64 { input - .map(|v| { - v.parse() - .expect(&format!("not a valid number: {}", v)) - }) + .map(|v| v.parse().expect(&format!("not a valid number: {}", v))) .unwrap_or(default) } @@ -149,15 +139,13 @@ impl Actor for Perf { impl Perf { fn sample_rate(&self, ctx: &mut Context) { - ctx.run_later( - Duration::new(self.sample_rate_secs as u64, 0), - |act, ctx| { - let req_count = act.counters.pull_request_count(); - if req_count != 0 { - let conns = act.counters.pull_connections_count(); - let latency = act.counters.pull_latency_ns(); - let latency_max = act.counters.pull_latency_max_ns(); - println!( + ctx.run_later(Duration::new(self.sample_rate_secs as u64, 0), |act, ctx| { + let req_count = act.counters.pull_request_count(); + if req_count != 0 { + let conns = act.counters.pull_connections_count(); + let latency = act.counters.pull_latency_ns(); + let latency_max = act.counters.pull_latency_max_ns(); + println!( "rate: {}, conns: {}, throughput: {:?} kb, latency: {}, latency max: {}", req_count / act.sample_rate_secs, conns / act.sample_rate_secs, @@ -166,11 +154,10 @@ impl Perf { time::Duration::nanoseconds((latency / req_count as u64) as i64), time::Duration::nanoseconds(latency_max as i64) ); - } + } - act.sample_rate(ctx); - }, - ); + act.sample_rate(ctx); + }); } } @@ -314,8 +301,7 @@ impl PerfCounters { loop { let current = self.lat_max.load(Ordering::SeqCst); if current >= nanos - || self.lat_max - .compare_and_swap(current, nanos, Ordering::SeqCst) + || self.lat_max.compare_and_swap(current, nanos, Ordering::SeqCst) == current { break; From d98d723f973cf31d0580da036fd0ee1edbd7c492 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 08:24:19 -0700 Subject: [PATCH 0148/1635] bump rustc version requirements --- .appveyor.yml | 2 +- .travis.yml | 8 +------- CHANGES.md | 2 ++ README.md | 2 +- src/lib.rs | 2 +- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index f9e79ce7..4f26315f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -59,4 +59,4 @@ build: false # Equivalent to Travis' `script` phase test_script: - - cargo test --no-default-features + - cargo test --no-default-features --features="flate2-rust" diff --git a/.travis.yml b/.travis.yml index a69ce188..890d7564 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,19 +8,13 @@ cache: matrix: include: - - rust: 1.21.0 + - rust: 1.22.1 - rust: stable - rust: beta - rust: nightly allow_failures: - rust: nightly -#rust: -# - 1.21.0 -# - stable -# - beta -# - nightly-2018-01-03 - env: global: # - RUSTFLAGS="-C link-dead-code" diff --git a/CHANGES.md b/CHANGES.md index fb58d0ae..9d3e0a10 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ * Websocket CloseCode Empty/Status is ambiguous #193 +* Add Content-Disposition to NamedFile #204 + ## 0.5.6 (2018-04-24) diff --git a/README.md b/README.md index ed818d95..34790cd0 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.21 or later +* Minimum supported Rust version: 1.22 or later ## Example diff --git a/src/lib.rs b/src/lib.rs index 2efac129..379b5ac4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ //! * SSL support with OpenSSL or `native-tls` //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) //! * Built on top of [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.21 or later +//! * Supported Rust version: 1.22 or later #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error From c72d1381a6e77fc2f06a21aeb4688e3d289fd37e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 09:09:08 -0700 Subject: [PATCH 0149/1635] clippy warnings --- .travis.yml | 2 +- rustfmt.toml | 4 +- src/application.rs | 126 +++++++++++++----- src/body.rs | 4 +- src/client/connector.rs | 35 +++-- src/client/parser.rs | 2 +- src/client/pipeline.rs | 12 +- src/client/request.rs | 19 ++- src/client/response.rs | 21 ++- src/client/writer.rs | 17 ++- src/de.rs | 25 ++-- src/error.rs | 12 +- src/extractor.rs | 73 +++++++---- src/fs.rs | 208 ++++++++++++++++++++---------- src/handler.rs | 15 ++- src/header/shared/entity.rs | 31 +++-- src/header/shared/httpdate.rs | 24 +++- src/header/shared/quality_item.rs | 27 ++-- src/helpers.rs | 186 +++++++++++++++++++++----- src/httpcodes.rs | 25 +++- src/httpmessage.rs | 41 ++++-- src/httprequest.rs | 24 +++- src/httpresponse.rs | 107 +++++++++++---- src/info.rs | 18 +-- src/json.rs | 53 ++++++-- src/middleware/cors.rs | 96 ++++++++++---- src/middleware/csrf.rs | 10 +- src/middleware/defaultheaders.rs | 4 +- src/middleware/identity.rs | 20 +-- src/middleware/logger.rs | 27 ++-- src/middleware/session.rs | 17 ++- src/multipart.rs | 11 +- src/param.rs | 18 ++- src/payload.rs | 14 +- src/pipeline.rs | 9 +- src/pred.rs | 6 +- src/resource.rs | 9 +- src/route.rs | 36 +++--- src/router.rs | 85 ++++++++---- src/server/channel.rs | 32 ++--- src/server/encoding.rs | 13 +- src/server/h1.rs | 130 +++++++++++-------- src/server/h1decoder.rs | 43 +++--- src/server/h1writer.rs | 6 +- src/server/h2writer.rs | 7 +- src/server/helpers.rs | 55 ++++++-- src/server/settings.rs | 5 +- src/server/srv.rs | 60 ++++----- src/server/worker.rs | 82 ++++++------ src/test.rs | 23 ++-- src/uri.rs | 5 +- src/with.rs | 34 +++-- src/ws/client.rs | 31 +++-- src/ws/context.rs | 22 +++- src/ws/frame.rs | 10 +- src/ws/mask.rs | 7 +- src/ws/mod.rs | 119 ++++++++++++----- tests/test_client.rs | 43 ++++-- tests/test_handlers.rs | 60 ++++++--- tests/test_server.rs | 133 ++++++++++++------- tests/test_ws.rs | 59 ++++++--- tools/wsload/src/wsclient.rs | 108 +++++++++------- 62 files changed, 1742 insertions(+), 818 deletions(-) diff --git a/.travis.yml b/.travis.yml index 890d7564..2dedae4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,11 @@ cache: matrix: include: - - rust: 1.22.1 - rust: stable - rust: beta - rust: nightly allow_failures: + - rust: 1.21.0 - rust: nightly env: diff --git a/rustfmt.toml b/rustfmt.toml index 97c6a5aa..6db67ed2 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,7 +1,5 @@ max_width = 89 reorder_imports = true -#reorder_imports_in_group = true -#reorder_imported_names = true wrap_comments = true fn_args_density = "Compressed" -use_small_heuristics = false +#use_small_heuristics = false diff --git a/src/application.rs b/src/application.rs index 33c4453c..4ee5157d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -111,7 +111,12 @@ impl HttpHandler for HttpApplication { let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); let tp = self.get_handler(&mut req); let inner = Rc::clone(&self.inner); - Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner, tp))) + Ok(Box::new(Pipeline::new( + req, + Rc::clone(&self.middlewares), + inner, + tp, + ))) } else { Err(req) } @@ -449,14 +454,20 @@ where } let parts = self.parts.as_mut().expect("Use after finish"); - parts.handlers.push((path, Box::new(WrapHandler::new(handler)))); + parts + .handlers + .push((path, Box::new(WrapHandler::new(handler)))); } self } /// Register a middleware. pub fn middleware>(mut self, mw: M) -> App { - self.parts.as_mut().expect("Use after finish").middlewares.push(Box::new(mw)); + self.parts + .as_mut() + .expect("Use after finish") + .middlewares + .push(Box::new(mw)); self } @@ -611,8 +622,9 @@ mod tests { #[test] fn test_default_resource() { - let mut app = - App::new().resource("/test", |r| r.f(|_| HttpResponse::Ok())).finish(); + let mut app = App::new() + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + .finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -620,14 +632,20 @@ mod tests { let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); let mut app = App::new() .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::METHOD_NOT_ALLOWED); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::METHOD_NOT_ALLOWED + ); } #[test] @@ -641,8 +659,9 @@ mod tests { #[test] fn test_state() { - let mut app = - App::with_state(10).resource("/", |r| r.f(|_| HttpResponse::Ok())).finish(); + let mut app = App::with_state(10) + .resource("/", |r| r.f(|_| HttpResponse::Ok())) + .finish(); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); let resp = app.run(req); @@ -674,7 +693,9 @@ mod tests { #[test] fn test_handler() { - let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); + let mut app = App::new() + .handler("/test", |_| HttpResponse::Ok()) + .finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -690,16 +711,24 @@ mod tests { let req = TestRequest::with_uri("/testapp").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + 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); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } #[test] fn test_handler2() { - let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); + let mut app = App::new() + .handler("test", |_| HttpResponse::Ok()) + .finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -715,11 +744,17 @@ mod tests { let req = TestRequest::with_uri("/testapp").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + 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); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } #[test] @@ -743,41 +778,68 @@ mod tests { let req = TestRequest::with_uri("/prefix/testapp").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); let req = TestRequest::with_uri("/prefix/blah").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } #[test] fn test_route() { let mut app = App::new() - .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) - .route("/test", Method::POST, |_: HttpRequest| HttpResponse::Created()) + .route("/test", Method::GET, |_: HttpRequest| { + HttpResponse::Ok() + }) + .route("/test", Method::POST, |_: HttpRequest| { + HttpResponse::Created() + }) .finish(); - let req = TestRequest::with_uri("/test").method(Method::GET).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .finish(); let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test").method(Method::POST).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::CREATED); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::CREATED + ); - let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::HEAD) + .finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } #[test] fn test_handler_prefix() { - let mut app = - App::new().prefix("/app").handler("/test", |_| HttpResponse::Ok()).finish(); + let mut app = App::new() + .prefix("/app") + .handler("/test", |_| HttpResponse::Ok()) + .finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); let req = TestRequest::with_uri("/app/test").finish(); let resp = app.run(req); @@ -793,10 +855,16 @@ mod tests { let req = TestRequest::with_uri("/app/testapp").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); + 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); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); } } diff --git a/src/body.rs b/src/body.rs index 6974fc7a..cf54361d 100644 --- a/src/body.rs +++ b/src/body.rs @@ -258,7 +258,9 @@ impl Responder for Binary { type Error = Error; fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok().content_type("application/octet-stream").body(self)) + Ok(HttpResponse::Ok() + .content_type("application/octet-stream") + .body(self)) } } diff --git a/src/client/connector.rs b/src/client/connector.rs index 07ecca93..d9fa46d1 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -94,17 +94,13 @@ pub struct Pause { impl Pause { /// Create message with pause duration parameter pub fn new(time: Duration) -> Pause { - Pause { - time: Some(time), - } + Pause { time: Some(time) } } } impl Default for Pause { fn default() -> Pause { - Pause { - time: None, - } + Pause { time: None } } } @@ -431,7 +427,8 @@ impl ClientConnector { } else { 0 }; - self.acquired_per_host.insert(key.clone(), per_host + 1); + self.acquired_per_host + .insert(key.clone(), per_host + 1); } fn release_key(&mut self, key: &Key) { @@ -442,7 +439,8 @@ impl ClientConnector { return; }; if per_host > 1 { - self.acquired_per_host.insert(key.clone(), per_host - 1); + self.acquired_per_host + .insert(key.clone(), per_host - 1); } else { self.acquired_per_host.remove(key); } @@ -518,7 +516,9 @@ impl ClientConnector { fn collect_periodic(&mut self, ctx: &mut Context) { self.collect(true); // re-schedule next collect period - ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); + ctx.run_later(Duration::from_secs(1), |act, ctx| { + act.collect_periodic(ctx) + }); // send stats let stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); @@ -583,7 +583,10 @@ impl ClientConnector { wait, conn_timeout, }; - self.waiters.entry(key).or_insert_with(VecDeque::new).push_back(waiter); + self.waiters + .entry(key) + .or_insert_with(VecDeque::new) + .push_back(waiter); rx } } @@ -828,7 +831,7 @@ impl fut::ActorFuture for Maintenance { act.collect_waiters(); // check waiters - let tmp: &mut ClientConnector = unsafe { mem::transmute(act as &mut _) }; + let tmp: &mut ClientConnector = unsafe { &mut *(act as *mut _) }; for (key, waiters) in &mut tmp.waiters { while let Some(waiter) = waiters.pop_front() { @@ -1102,7 +1105,10 @@ impl Pool { if self.to_close.borrow().is_empty() { None } else { - Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new())) + Some(mem::replace( + &mut *self.to_close.borrow_mut(), + Vec::new(), + )) } } @@ -1110,7 +1116,10 @@ impl Pool { if self.to_release.borrow().is_empty() { None } else { - Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new())) + Some(mem::replace( + &mut *self.to_release.borrow_mut(), + Vec::new(), + )) } } diff --git a/src/client/parser.rs b/src/client/parser.rs index f81aed11..b292e8d4 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -123,7 +123,7 @@ impl HttpResponseParser { let (len, version, status, headers_len) = { let b = unsafe { let b: &[u8] = buf; - mem::transmute(b) + &*(b as *const _) }; let mut resp = httparse::Response::new(&mut headers); match resp.parse(b)? { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index a9468805..60eb4f8c 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -269,7 +269,11 @@ impl Pipeline { #[inline] fn parse(&mut self) -> Poll { if let Some(ref mut conn) = self.conn { - match self.parser.as_mut().unwrap().parse(conn, &mut self.parser_buf) { + match self.parser + .as_mut() + .unwrap() + .parse(conn, &mut self.parser_buf) + { Ok(Async::Ready(resp)) => { // check content-encoding if self.should_decompress { @@ -301,7 +305,7 @@ impl Pipeline { return Ok(Async::Ready(None)); } let conn: &mut Connection = - unsafe { mem::transmute(self.conn.as_mut().unwrap()) }; + unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) }; let mut need_run = false; @@ -465,7 +469,9 @@ impl Pipeline { } // flush io but only if we need to - match self.writer.poll_completed(self.conn.as_mut().unwrap(), false) { + match self.writer + .poll_completed(self.conn.as_mut().unwrap(), false) + { Ok(Async::Ready(_)) => { if self.disconnected || (self.body_completed && self.writer.is_completed()) diff --git a/src/client/request.rs b/src/client/request.rs index c32b0fad..4eaf8002 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -499,7 +499,10 @@ impl ClientRequestBuilder { jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); + self.cookies + .as_mut() + .unwrap() + .add(cookie.into_owned()); } self } @@ -591,7 +594,11 @@ impl ClientRequestBuilder { if self.default_headers { // enable br only for https let https = if let Some(parts) = parts(&mut self.request, &self.err) { - parts.uri.scheme_part().map(|s| s == &uri::Scheme::HTTPS).unwrap_or(true) + parts + .uri + .scheme_part() + .map(|s| s == &uri::Scheme::HTTPS) + .unwrap_or(true) } else { true }; @@ -603,7 +610,9 @@ impl ClientRequestBuilder { } } - let mut request = self.request.take().expect("cannot reuse request builder"); + let mut request = self.request + .take() + .expect("cannot reuse request builder"); // set cookies if let Some(ref mut jar) = self.cookies { @@ -648,7 +657,9 @@ impl ClientRequestBuilder { S: Stream + 'static, E: Into, { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) + self.body(Body::Streaming(Box::new( + stream.map_err(|e| e.into()), + ))) } /// Set an empty body and generate `ClientRequest` diff --git a/src/client/response.rs b/src/client/response.rs index f76d058e..4d186d19 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -103,7 +103,12 @@ impl ClientResponse { impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status()); + let res = writeln!( + f, + "\nClientResponse {:?} {}", + self.version(), + self.status() + ); let _ = writeln!(f, " headers:"); for (key, val) in self.headers().iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); @@ -133,12 +138,14 @@ mod tests { #[test] fn test_debug() { let resp = ClientResponse::new(ClientMessage::default()); - resp.as_mut() - .headers - .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1")); - resp.as_mut() - .headers - .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2")); + resp.as_mut().headers.insert( + header::COOKIE, + HeaderValue::from_static("cookie1=value1"), + ); + resp.as_mut().headers.insert( + header::COOKIE, + HeaderValue::from_static("cookie2=value2"), + ); let dbg = format!("{:?}", resp); assert!(dbg.contains("ClientResponse")); diff --git a/src/client/writer.rs b/src/client/writer.rs index adcc454e..36c9d6ee 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -114,7 +114,10 @@ impl HttpClientWriter { self.buffer, "{} {} {:?}\r", msg.method(), - msg.uri().path_and_query().map(|u| u.as_str()).unwrap_or("/"), + msg.uri() + .path_and_query() + .map(|u| u.as_str()) + .unwrap_or("/"), msg.version() )?; @@ -250,8 +253,10 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder } let mut b = BytesMut::new(); let _ = write!(b, "{}", bytes.len()); - req.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); + req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(b.freeze()).unwrap(), + ); TransferEncoding::eof(buf) } Body::Streaming(_) | Body::Actor(_) => { @@ -274,8 +279,10 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder }; if encoding.is_compression() { - req.headers_mut() - .insert(CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str())); + req.headers_mut().insert( + CONTENT_ENCODING, + HeaderValue::from_static(encoding.as_str()), + ); } req.replace_body(body); diff --git a/src/de.rs b/src/de.rs index fa81b77e..3ab3646e 100644 --- a/src/de.rs +++ b/src/de.rs @@ -41,9 +41,7 @@ pub struct PathDeserializer<'de, S: 'de> { impl<'de, S: 'de> PathDeserializer<'de, S> { pub fn new(req: &'de HttpRequest) -> Self { - PathDeserializer { - req, - } + PathDeserializer { req } } } @@ -204,12 +202,11 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { where K: de::DeserializeSeed<'de>, { - self.current = - self.params.next().map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); + self.current = self.params + .next() + .map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); match self.current { - Some((key, _)) => Ok(Some(seed.deserialize(Key { - key, - })?)), + Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), None => Ok(None), } } @@ -219,9 +216,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { V: de::DeserializeSeed<'de>, { if let Some((_, value)) = self.current.take() { - seed.deserialize(Value { - value, - }) + seed.deserialize(Value { value }) } else { Err(de::value::Error::custom("unexpected item")) } @@ -377,7 +372,9 @@ impl<'de> Deserializer<'de> for Value<'de> { where V: Visitor<'de>, { - Err(de::value::Error::custom("unsupported type: tuple struct")) + Err(de::value::Error::custom( + "unsupported type: tuple struct", + )) } unsupported_type!(deserialize_any, "any"); @@ -419,9 +416,7 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { V: de::DeserializeSeed<'de>, { Ok(( - seed.deserialize(Key { - key: self.value, - })?, + seed.deserialize(Key { key: self.value })?, UnitVariant, )) } diff --git a/src/error.rs b/src/error.rs index 2fa4ba2e..3004adef 100644 --- a/src/error.rs +++ b/src/error.rs @@ -68,7 +68,12 @@ impl fmt::Debug for Error { if let Some(bt) = self.cause.backtrace() { write!(f, "{:?}\n\n{:?}", &self.cause, bt) } else { - write!(f, "{:?}\n\n{:?}", &self.cause, self.backtrace.as_ref().unwrap()) + write!( + f, + "{:?}\n\n{:?}", + &self.cause, + self.backtrace.as_ref().unwrap() + ) } } } @@ -302,7 +307,10 @@ pub enum HttpRangeError { /// Return `BadRequest` for `HttpRangeError` impl ResponseError for HttpRangeError { fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided") + HttpResponse::with_body( + StatusCode::BAD_REQUEST, + "Invalid Range header provided", + ) } } diff --git a/src/extractor.rs b/src/extractor.rs index 7dd59038..1aef7ac5 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -110,9 +110,7 @@ where result( de::Deserialize::deserialize(PathDeserializer::new(&req)) .map_err(|e| e.into()) - .map(|inner| Path { - inner, - }), + .map(|inner| Path { inner }), ) } } @@ -248,7 +246,12 @@ where #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(UrlEncoded::new(req.clone()).limit(cfg.limit).from_err().map(Form)) + Box::new( + UrlEncoded::new(req.clone()) + .limit(cfg.limit) + .from_err() + .map(Form), + ) } } @@ -293,9 +296,7 @@ impl FormConfig { impl Default for FormConfig { fn default() -> Self { - FormConfig { - limit: 262_144, - } + FormConfig { limit: 262_144 } } } @@ -336,7 +337,11 @@ impl FromRequest for Bytes { return Either::A(result(Err(e))); } - Either::B(Box::new(MessageBody::new(req.clone()).limit(cfg.limit).from_err())) + Either::B(Box::new( + MessageBody::new(req.clone()) + .limit(cfg.limit) + .from_err(), + )) } } @@ -382,14 +387,18 @@ impl FromRequest for String { // check charset let encoding = match req.encoding() { Err(_) => { - return Either::A(result(Err(ErrorBadRequest("Unknown request charset")))) + return Either::A(result(Err(ErrorBadRequest( + "Unknown request charset", + )))) } Ok(encoding) => encoding, }; Either::B(Box::new( - MessageBody::new(req.clone()).limit(cfg.limit).from_err().and_then( - move |body| { + MessageBody::new(req.clone()) + .limit(cfg.limit) + .from_err() + .and_then(move |body| { let enc: *const Encoding = encoding as *const Encoding; if enc == UTF_8 { Ok(str::from_utf8(body.as_ref()) @@ -400,8 +409,7 @@ impl FromRequest for String { .decode(&body, DecoderTrap::Strict) .map_err(|_| ErrorBadRequest("Can not decode body"))?) } - }, - ), + }), )) } } @@ -477,7 +485,8 @@ mod tests { fn test_bytes() { let cfg = PayloadConfig::default(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); match Bytes::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { @@ -491,7 +500,8 @@ mod tests { fn test_string() { let cfg = PayloadConfig::default(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); match String::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { @@ -508,7 +518,8 @@ mod tests { "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); let mut cfg = FormConfig::default(); cfg.limit(4096); @@ -562,11 +573,17 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); + routes.push(( + Resource::new("index", "/{key}/{value}/"), + Some(resource), + )); let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req, &()).poll().unwrap() { + match Path::::from_request(&req, &()) + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); @@ -574,7 +591,10 @@ mod tests { _ => unreachable!(), } - match Path::<(String, String)>::from_request(&req, &()).poll().unwrap() { + match Path::<(String, String)>::from_request(&req, &()) + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); @@ -600,7 +620,10 @@ mod tests { _ => unreachable!(), } - match Path::<(String, u8)>::from_request(&req, &()).poll().unwrap() { + match Path::<(String, u8)>::from_request(&req, &()) + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, 32); @@ -608,9 +631,15 @@ mod tests { _ => unreachable!(), } - match Path::>::from_request(&req, &()).poll().unwrap() { + match Path::>::from_request(&req, &()) + .poll() + .unwrap() + { Async::Ready(s) => { - assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]); + assert_eq!( + s.into_inner(), + vec!["name".to_owned(), "32".to_owned()] + ); } _ => unreachable!(), } diff --git a/src/fs.rs b/src/fs.rs index f9b39f7c..0a526d57 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -15,7 +15,7 @@ use bytes::{BufMut, Bytes, BytesMut}; use futures::{Async, Future, Poll, Stream}; use futures_cpupool::{CpuFuture, CpuPool}; use mime; -use mime_guess::{guess_mime_type, get_mime_type}; +use mime_guess::{get_mime_type, guess_mime_type}; use error::Error; use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler}; @@ -203,24 +203,29 @@ impl Responder for NamedFile { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); + resp.set(header::ContentType(get_mime_type( + &ext.to_string_lossy(), + ))); }).if_some(self.path().file_name(), |file_name, resp| { - let mime_type = guess_mime_type(self.path()); - let inline_or_attachment = match mime_type.type_() { - mime::IMAGE | mime::TEXT => "inline", - _ => "attachment", - }; - resp.header( - "Content-Disposition", - format!("{inline_or_attachment}; filename={filename}", - inline_or_attachment=inline_or_attachment, - filename=file_name.to_string_lossy()) - ); - }); + let mime_type = guess_mime_type(self.path()); + let inline_or_attachment = match mime_type.type_() { + mime::IMAGE | mime::TEXT => "inline", + _ => "attachment", + }; + resp.header( + "Content-Disposition", + format!( + "{inline_or_attachment}; filename={filename}", + inline_or_attachment = inline_or_attachment, + filename = file_name.to_string_lossy() + ), + ); + }); let reader = ChunkedReadFile { size: self.md.len(), offset: 0, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), + cpu_pool: self.cpu_pool + .unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, }; @@ -263,24 +268,30 @@ impl Responder for NamedFile { let mut resp = HttpResponse::build(self.status_code); resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); + resp.set(header::ContentType(get_mime_type( + &ext.to_string_lossy(), + ))); }).if_some(self.path().file_name(), |file_name, resp| { - let mime_type = guess_mime_type(self.path()); - let inline_or_attachment = match mime_type.type_() { - mime::IMAGE | mime::TEXT => "inline", - _ => "attachment", - }; - resp.header( - "Content-Disposition", - format!("{inline_or_attachment}; filename={filename}", - inline_or_attachment=inline_or_attachment, - filename=file_name.to_string_lossy()) - ); - }).if_some(last_modified, |lm, resp| { + let mime_type = guess_mime_type(self.path()); + let inline_or_attachment = match mime_type.type_() { + mime::IMAGE | mime::TEXT => "inline", + _ => "attachment", + }; + resp.header( + "Content-Disposition", + format!( + "{inline_or_attachment}; filename={filename}", + inline_or_attachment = inline_or_attachment, + filename = file_name.to_string_lossy() + ), + ); + }) + .if_some(last_modified, |lm, resp| { resp.set(header::LastModified(lm)); - }).if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); + }) + .if_some(etag, |etag, resp| { + resp.set(header::ETag(etag)); + }); if precondition_failed { return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); @@ -294,7 +305,8 @@ impl Responder for NamedFile { let reader = ChunkedReadFile { size: self.md.len(), offset: 0, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), + cpu_pool: self.cpu_pool + .unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, }; @@ -362,10 +374,7 @@ pub struct Directory { impl Directory { pub fn new(base: PathBuf, path: PathBuf) -> Directory { - Directory { - base, - path, - } + Directory { base, path } } fn can_list(&self, entry: &io::Result) -> bool { @@ -435,7 +444,9 @@ impl Responder for Directory { \n", index_of, index_of, body ); - Ok(HttpResponse::Ok().content_type("text/html; charset=utf-8").body(html)) + Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html)) } } @@ -560,12 +571,13 @@ impl Handler for StaticFiles { if !self.accessible { Ok(self.default.handle(req)) } else { - let relpath = - match req.match_info().get("tail").map(|tail| PathBuf::from_param(tail)) - { - Some(Ok(path)) => path, - _ => return Ok(self.default.handle(req)), - }; + let relpath = match req.match_info() + .get("tail") + .map(|tail| PathBuf::from_param(tail)) + { + Some(Ok(path)) => path, + _ => return Ok(self.default.handle(req)), + }; // full filepath let path = self.directory.join(&relpath).canonicalize()?; @@ -615,8 +627,9 @@ mod tests { #[test] fn test_named_file_text() { assert!(NamedFile::open("test--").is_err()); - let mut file = - NamedFile::open("Cargo.toml").unwrap().set_cpu_pool(CpuPool::new(1)); + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_cpu_pool(CpuPool::new(1)); { file.file(); let _f: &File = &file; @@ -626,17 +639,23 @@ mod tests { } let resp = file.respond_to(HttpRequest::default()).unwrap(); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml"); assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers() + .get(header::CONTENT_DISPOSITION) + .unwrap(), "inline; filename=Cargo.toml" ); } #[test] fn test_named_file_image() { - let mut file = - NamedFile::open("tests/test.png").unwrap().set_cpu_pool(CpuPool::new(1)); + let mut file = NamedFile::open("tests/test.png") + .unwrap() + .set_cpu_pool(CpuPool::new(1)); { file.file(); let _f: &File = &file; @@ -646,17 +665,23 @@ mod tests { } let resp = file.respond_to(HttpRequest::default()).unwrap(); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "image/png"); assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers() + .get(header::CONTENT_DISPOSITION) + .unwrap(), "inline; filename=test.png" ); } #[test] fn test_named_file_binary() { - let mut file = - NamedFile::open("tests/test.binary").unwrap().set_cpu_pool(CpuPool::new(1)); + let mut file = NamedFile::open("tests/test.binary") + .unwrap() + .set_cpu_pool(CpuPool::new(1)); { file.file(); let _f: &File = &file; @@ -666,9 +691,14 @@ mod tests { } let resp = file.respond_to(HttpRequest::default()).unwrap(); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/octet-stream"); assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "application/octet-stream" + ); + assert_eq!( + resp.headers() + .get(header::CONTENT_DISPOSITION) + .unwrap(), "attachment; filename=test.binary" ); } @@ -688,9 +718,14 @@ mod tests { } let resp = file.respond_to(HttpRequest::default()).unwrap(); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml"); assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers() + .get(header::CONTENT_DISPOSITION) + .unwrap(), "inline; filename=Cargo.toml" ); assert_eq!(resp.status(), StatusCode::NOT_FOUND); @@ -735,7 +770,9 @@ mod tests { req.match_info_mut().add("tail", ""); st.show_index = true; - let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(req) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -751,18 +788,28 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tests"); - let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(req) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/tests/index.html"); + assert_eq!( + resp.headers().get(header::LOCATION).unwrap(), + "/tests/index.html" + ); let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tests/"); - let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(req) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/tests/index.html"); + assert_eq!( + resp.headers().get(header::LOCATION).unwrap(), + "/tests/index.html" + ); } #[test] @@ -771,7 +818,9 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tools/wsload"); - let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap(); + let resp = st.handle(req) + .respond_to(HttpRequest::default()) + .unwrap(); let resp = resp.as_response().expect("HTTP Response"); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( @@ -791,13 +840,23 @@ mod tests { let request = srv.get().uri(srv.url("/public")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + let loc = response + .headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(); assert_eq!(loc, "/public/Cargo.toml"); let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + let loc = response + .headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(); assert_eq!(loc, "/public/Cargo.toml"); } @@ -810,13 +869,23 @@ mod tests { let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + let loc = response + .headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(); assert_eq!(loc, "/test/Cargo.toml"); let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::FOUND); - let loc = response.headers().get(header::LOCATION).unwrap().to_str().unwrap(); + let loc = response + .headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(); assert_eq!(loc, "/test/Cargo.toml"); } @@ -826,7 +895,10 @@ mod tests { App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) }); - let request = srv.get().uri(srv.url("/test/%43argo.toml")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test/%43argo.toml")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::OK); } diff --git a/src/handler.rs b/src/handler.rs index 854f3a11..2304687d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -296,13 +296,14 @@ where #[inline] 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), - }); + 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)) } } diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs index c80bb182..347c4c02 100644 --- a/src/header/shared/entity.rs +++ b/src/header/shared/entity.rs @@ -57,10 +57,7 @@ impl EntityTag { /// If the tag contains invalid characters. pub fn new(weak: bool, tag: String) -> EntityTag { assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - EntityTag { - weak, - tag, - } + EntityTag { weak, tag } } /// Constructs a new weak EntityTag. @@ -199,7 +196,11 @@ mod tests { fn test_etag_parse_failures() { // Expected failures assert!("no-dquotes".parse::().is_err()); - assert!("w/\"the-first-w-is-case-sensitive\"".parse::().is_err()); + assert!( + "w/\"the-first-w-is-case-sensitive\"" + .parse::() + .is_err() + ); assert!("".parse::().is_err()); assert!("\"unmatched-dquotes1".parse::().is_err()); assert!("unmatched-dquotes2\"".parse::().is_err()); @@ -208,14 +209,26 @@ mod tests { #[test] fn test_etag_fmt() { - assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\""); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); + assert_eq!( + format!("{}", EntityTag::strong("foobar".to_owned())), + "\"foobar\"" + ); + assert_eq!( + format!("{}", EntityTag::strong("".to_owned())), + "\"\"" + ); assert_eq!( format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\"" ); - assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\""); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); + assert_eq!( + format!("{}", EntityTag::weak("\u{0065}".to_owned())), + "W/\"\x65\"" + ); + assert_eq!( + format!("{}", EntityTag::weak("".to_owned())), + "W/\"\"" + ); } #[test] diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs index a6309ae0..5de1e3f9 100644 --- a/src/header/shared/httpdate.rs +++ b/src/header/shared/httpdate.rs @@ -64,7 +64,11 @@ impl IntoHeaderValue for HttpDate { fn try_into(self) -> Result { let mut wrt = BytesMut::with_capacity(29).writer(); write!(wrt, "{}", self.0.rfc822()).unwrap(); - unsafe { Ok(HeaderValue::from_shared_unchecked(wrt.get_mut().take().freeze())) } + unsafe { + Ok(HeaderValue::from_shared_unchecked( + wrt.get_mut().take().freeze(), + )) + } } } @@ -100,12 +104,24 @@ mod tests { #[test] fn test_date() { - assert_eq!("Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), NOV_07); assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT".parse::().unwrap(), + "Sun, 07 Nov 1994 08:48:37 GMT" + .parse::() + .unwrap(), + NOV_07 + ); + assert_eq!( + "Sunday, 07-Nov-94 08:48:37 GMT" + .parse::() + .unwrap(), + NOV_07 + ); + assert_eq!( + "Sun Nov 7 08:48:37 1994" + .parse::() + .unwrap(), NOV_07 ); - assert_eq!("Sun Nov 7 08:48:37 1994".parse::().unwrap(), NOV_07); assert!("this-is-no-date".parse::().is_err()); } } diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs index 1734db45..5f1e5977 100644 --- a/src/header/shared/quality_item.rs +++ b/src/header/shared/quality_item.rs @@ -47,10 +47,7 @@ impl QualityItem { /// The item can be of any type. /// The quality should be a value in the range [0, 1]. pub fn new(item: T, quality: Quality) -> QualityItem { - QualityItem { - item, - quality, - } + QualityItem { item, quality } } } @@ -66,7 +63,11 @@ impl fmt::Display for QualityItem { match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), + x => write!( + f, + "; q=0.{}", + format!("{:03}", x).trim_right_matches('0') + ), } } } @@ -119,7 +120,10 @@ fn from_f32(f: f32) -> Quality { // this function is only used internally. A check that `f` is within range // should be done before calling this method. Just in case, this // debug_assert should catch if we were forgetful - debug_assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0"); + debug_assert!( + f >= 0f32 && f <= 1f32, + "q value must be between 0.0 and 1.0" + ); Quality((f * 1000f32) as u16) } @@ -152,7 +156,10 @@ mod internal { impl IntoQuality for f32 { fn into_quality(self) -> Quality { - assert!(self >= 0f32 && self <= 1f32, "float must be between 0.0 and 1.0"); + assert!( + self >= 0f32 && self <= 1f32, + "float must be between 0.0 and 1.0" + ); super::from_f32(self) } } @@ -288,6 +295,10 @@ mod tests { #[test] fn test_fuzzing_bugs() { assert!("99999;".parse::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) + assert!( + "\x0d;;;=\u{d6aa}==" + .parse::>() + .is_err() + ) } } diff --git a/src/helpers.rs b/src/helpers.rs index dc392d7a..fda28f38 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -190,8 +190,16 @@ mod tests { // trailing slashes let params = vec![ ("/resource1", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), + ( + "/resource1/", + "/resource1", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/resource2", + "/resource2/", + StatusCode::MOVED_PERMANENTLY, + ), ("/resource2/", "", StatusCode::OK), ("/resource1?p1=1&p2=2", "", StatusCode::OK), ( @@ -214,7 +222,11 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() + r.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap() ); } } @@ -226,7 +238,11 @@ mod tests { .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)) + r.h(NormalizePath::new( + false, + true, + StatusCode::MOVED_PERMANENTLY, + )) }) .finish(); @@ -260,14 +276,46 @@ mod tests { // trailing slashes let params = vec![ ("/resource1/a/b", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ( + "/resource1/", + "/resource1", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/resource1//", + "/resource1", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "//resource1//a//b", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "//resource1//a//b/", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "//resource1//a//b//", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "///resource1//a//b", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a///b", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a//b/", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), ("/resource1/a/b?p=1", "", StatusCode::OK), ( "//resource1//a//b?p=1", @@ -308,7 +356,11 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() + r.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap() ); } } @@ -327,24 +379,88 @@ mod tests { // trailing slashes let params = vec![ ("/resource1/a/b", "", StatusCode::OK), - ("/resource1/a/b/", "/resource1/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", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ( + "/resource1/a/b/", + "/resource1/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", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "///resource1//a//b/", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a///b", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/////resource1/a///b/", + "/resource1/a/b", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/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), + ( + "//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", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ( + "/resource1/a/b/?p=1", + "/resource1/a/b?p=1", + StatusCode::MOVED_PERMANENTLY, + ), ( "//resource2//a//b?p=1", "/resource2/a/b/?p=1", @@ -380,7 +496,11 @@ mod tests { "/resource1/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", @@ -420,7 +540,11 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() + r.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap() ); } } diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 6ad9a28c..f003b29a 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -245,7 +245,10 @@ impl HttpResponse { STATIC_RESP!(Ok, StatusCode::OK); STATIC_RESP!(Created, StatusCode::CREATED); STATIC_RESP!(Accepted, StatusCode::ACCEPTED); - STATIC_RESP!(NonAuthoritativeInformation, StatusCode::NON_AUTHORITATIVE_INFORMATION); + STATIC_RESP!( + NonAuthoritativeInformation, + StatusCode::NON_AUTHORITATIVE_INFORMATION + ); STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); @@ -270,7 +273,10 @@ impl HttpResponse { STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - STATIC_RESP!(ProxyAuthenticationRequired, StatusCode::PROXY_AUTHENTICATION_REQUIRED); + STATIC_RESP!( + ProxyAuthenticationRequired, + StatusCode::PROXY_AUTHENTICATION_REQUIRED + ); STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); STATIC_RESP!(Conflict, StatusCode::CONFLICT); STATIC_RESP!(Gone, StatusCode::GONE); @@ -278,7 +284,10 @@ impl HttpResponse { STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); - STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); + STATIC_RESP!( + UnsupportedMediaType, + StatusCode::UNSUPPORTED_MEDIA_TYPE + ); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); @@ -287,8 +296,14 @@ impl HttpResponse { STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); - STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); + STATIC_RESP!( + VersionNotSupported, + StatusCode::HTTP_VERSION_NOT_SUPPORTED + ); + STATIC_RESP!( + VariantAlsoNegotiates, + StatusCode::VARIANT_ALSO_NEGOTIATES + ); STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index c8c836d9..d80ed703 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -96,8 +96,10 @@ pub trait HttpMessage { /// `size` is full size of response (file). fn range(&self, size: u64) -> Result, HttpRangeError> { if let Some(range) = self.headers().get(header::RANGE) { - HttpRange::parse(unsafe { str::from_utf8_unchecked(range.as_bytes()) }, size) - .map_err(|e| e.into()) + HttpRange::parse( + unsafe { str::from_utf8_unchecked(range.as_bytes()) }, + size, + ).map_err(|e| e.into()) } else { Ok(Vec::new()) } @@ -323,7 +325,10 @@ where )); } - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() + self.fut + .as_mut() + .expect("UrlEncoded could not be used second time") + .poll() } } @@ -380,7 +385,8 @@ where if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Err(UrlencodedError::ContentType); } - let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; + let encoding = req.encoding() + .map_err(|_| UrlencodedError::ContentType)?; // future let limit = self.limit; @@ -409,7 +415,10 @@ where self.fut = Some(Box::new(fut)); } - self.fut.as_mut().expect("UrlEncoded could not be used second time").poll() + self.fut + .as_mut() + .expect("UrlEncoded could not be used second time") + .poll() } } @@ -479,13 +488,19 @@ mod tests { #[test] fn test_encoding_error() { let req = TestRequest::with_header("content-type", "applicatjson").finish(); - assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); + assert_eq!( + Some(ContentTypeError::ParseError), + req.encoding().err() + ); let req = TestRequest::with_header( "content-type", "application/json; charset=kkkttktk", ).finish(); - assert_eq!(Some(ContentTypeError::UnknownEncoding), req.encoding().err()); + assert_eq!( + Some(ContentTypeError::UnknownEncoding), + req.encoding().err() + ); } #[test] @@ -606,7 +621,8 @@ mod tests { "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); let result = req.urlencoded::().poll().ok().unwrap(); assert_eq!( @@ -621,7 +637,8 @@ mod tests { "application/x-www-form-urlencoded; charset=utf-8", ).header(header::CONTENT_LENGTH, "11") .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + req.payload_mut() + .unread_data(Bytes::from_static(b"hello=world")); let result = req.urlencoded().poll().ok().unwrap(); assert_eq!( @@ -647,14 +664,16 @@ mod tests { } let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"test")); + 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")), _ => unreachable!("error"), } let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"11111111111111")); + req.payload_mut() + .unread_data(Bytes::from_static(b"11111111111111")); match req.body().limit(5).poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), diff --git a/src/httprequest.rs b/src/httprequest.rs index d41a748a..1ed59eb7 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -327,7 +327,12 @@ impl HttpRequest { let path = self.router().unwrap().resource_path(name, elements)?; if path.starts_with('/') { let conn = self.connection_info(); - Ok(Url::parse(&format!("{}://{}{}", conn.scheme(), conn.host(), path))?) + Ok(Url::parse(&format!( + "{}://{}{}", + conn.scheme(), + conn.host(), + path + ))?) } else { Ok(Url::parse(&path)?) } @@ -677,8 +682,10 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = - vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; + let routes = vec![( + Resource::new("index", "/user/{name}.{ext}"), + Some(resource), + )]; let (router, _) = Router::new("/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -707,8 +714,10 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = - vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; + let routes = vec![( + Resource::new("index", "/user/{name}.{ext}"), + Some(resource), + )]; let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/prefix/user/test.html")); @@ -736,6 +745,9 @@ mod tests { 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"); + assert_eq!( + url.ok().unwrap().as_str(), + "https://youtube.com/watch/oHg5SJYRHA0" + ); } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index a1f1cfb4..f776612d 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -150,7 +150,10 @@ impl HttpResponse { if let Some(reason) = self.get_ref().reason { reason } else { - self.get_ref().status.canonical_reason().unwrap_or("") + self.get_ref() + .status + .canonical_reason() + .unwrap_or("") } } @@ -463,7 +466,10 @@ impl HttpResponseBuilder { jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); + self.cookies + .as_mut() + .unwrap() + .add(cookie.into_owned()); } self } @@ -528,7 +534,9 @@ impl HttpResponseBuilder { if let Some(e) = self.err.take() { return Error::from(e).into(); } - let mut response = self.response.take().expect("cannot reuse response builder"); + let mut response = self.response + .take() + .expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { @@ -550,7 +558,9 @@ impl HttpResponseBuilder { S: Stream + 'static, E: Into, { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) + self.body(Body::Streaming(Box::new( + stream.map_err(|e| e.into()), + ))) } /// Set a json body and generate `HttpResponse` @@ -633,7 +643,9 @@ impl Responder for HttpResponseBuilder { impl From<&'static str> for HttpResponse { fn from(val: &'static str) -> Self { - HttpResponse::Ok().content_type("text/plain; charset=utf-8").body(val) + HttpResponse::Ok() + .content_type("text/plain; charset=utf-8") + .body(val) } } @@ -650,7 +662,9 @@ impl Responder for &'static str { impl From<&'static [u8]> for HttpResponse { fn from(val: &'static [u8]) -> Self { - HttpResponse::Ok().content_type("application/octet-stream").body(val) + HttpResponse::Ok() + .content_type("application/octet-stream") + .body(val) } } @@ -667,7 +681,9 @@ impl Responder for &'static [u8] { impl From for HttpResponse { fn from(val: String) -> Self { - HttpResponse::Ok().content_type("text/plain; charset=utf-8").body(val) + HttpResponse::Ok() + .content_type("text/plain; charset=utf-8") + .body(val) } } @@ -703,7 +719,9 @@ impl<'a> Responder for &'a String { impl From for HttpResponse { fn from(val: Bytes) -> Self { - HttpResponse::Ok().content_type("application/octet-stream").body(val) + HttpResponse::Ok() + .content_type("application/octet-stream") + .body(val) } } @@ -720,7 +738,9 @@ impl Responder for Bytes { impl From for HttpResponse { fn from(val: BytesMut) -> Self { - HttpResponse::Ok().content_type("application/octet-stream").body(val) + HttpResponse::Ok() + .content_type("application/octet-stream") + .body(val) } } @@ -752,7 +772,9 @@ impl<'a> From<&'a ClientResponse> for HttpResponseBuilder { impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { fn from(req: &'a HttpRequest) -> HttpResponseBuilder { if let Some(router) = req.router() { - router.server_settings().get_response_builder(StatusCode::OK) + router + .server_settings() + .get_response_builder(StatusCode::OK) } else { HttpResponse::Ok() } @@ -800,7 +822,9 @@ thread_local!(static POOL: Rc> = HttpResponsePool:: impl HttpResponsePool { pub fn pool() -> Rc> { - Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity(128)))) + Rc::new(UnsafeCell::new(HttpResponsePool( + VecDeque::with_capacity(128), + ))) } #[inline] @@ -951,7 +975,9 @@ mod tests { #[test] fn test_force_close() { - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let resp = HttpResponse::build(StatusCode::OK) + .force_close() + .finish(); assert!(!resp.keep_alive().unwrap()) } @@ -960,7 +986,10 @@ mod tests { let resp = HttpResponse::build(StatusCode::OK) .content_type("text/plain") .body(Body::Empty); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + "text/plain" + ) } #[test] @@ -1044,7 +1073,10 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref())); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(b"test".as_ref()) + ); let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -1053,7 +1085,10 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref())); + 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); @@ -1062,16 +1097,26 @@ mod tests { 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())); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from("test".to_owned()) + ); - let resp: HttpResponse = "test".to_owned().respond_to(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(CONTENT_TYPE).unwrap(), 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())); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from("test".to_owned()) + ); let resp: HttpResponse = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1080,17 +1125,25 @@ mod tests { 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())); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(&"test".to_owned()) + ); - let resp: HttpResponse = - (&"test".to_owned()).respond_to(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(CONTENT_TYPE).unwrap(), 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())); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(&"test".to_owned()) + ); let b = Bytes::from_static(b"test"); let resp: HttpResponse = b.into(); @@ -1126,7 +1179,10 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(BytesMut::from("test")) + ); let b = BytesMut::from("test"); let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); @@ -1136,7 +1192,10 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test"))); + assert_eq!( + resp.body().binary().unwrap(), + &Binary::from(BytesMut::from("test")) + ); } #[test] diff --git a/src/info.rs b/src/info.rs index 7d3affab..76288539 100644 --- a/src/info.rs +++ b/src/info.rs @@ -53,8 +53,8 @@ impl<'a> ConnectionInfo<'a> { // scheme if scheme.is_none() { - if let Some(h) = - req.headers().get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) + 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()); @@ -74,8 +74,8 @@ impl<'a> ConnectionInfo<'a> { // host if host.is_none() { - if let Some(h) = - req.headers().get(HeaderName::from_str(X_FORWARDED_HOST).unwrap()) + 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()); @@ -98,8 +98,8 @@ 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 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()); @@ -189,8 +189,10 @@ mod tests { 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")); + req.headers_mut().insert( + header::HOST, + HeaderValue::from_static("rust-lang.org"), + ); let info = ConnectionInfo::new(&req); assert_eq!(info.scheme(), "http"); diff --git a/src/json.rs b/src/json.rs index 9f0906c1..ec3ad7ce 100644 --- a/src/json.rs +++ b/src/json.rs @@ -308,7 +308,10 @@ where self.fut = Some(Box::new(fut)); } - self.fut.as_mut().expect("JsonBody could not be used second time").poll() + self.fut + .as_mut() + .expect("JsonBody could not be used second time") + .poll() } } @@ -359,7 +362,10 @@ mod tests { fn test_json_body() { let req = HttpRequest::default(); let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); + assert_eq!( + json.poll().err().unwrap(), + JsonPayloadError::ContentType + ); let mut req = HttpRequest::default(); req.headers_mut().insert( @@ -367,15 +373,20 @@ mod tests { header::HeaderValue::from_static("application/text"), ); let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); + assert_eq!( + json.poll().err().unwrap(), + JsonPayloadError::ContentType + ); let mut req = HttpRequest::default(); req.headers_mut().insert( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), ); - req.headers_mut() - .insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("10000")); + req.headers_mut().insert( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ); let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); @@ -384,9 +395,12 @@ mod tests { header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), ); - req.headers_mut() - .insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("16")); - req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); + 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(), @@ -403,7 +417,12 @@ mod tests { let mut handler = With::new(|data: Json| data, cfg); let req = HttpRequest::default(); - let err = handler.handle(req).as_response().unwrap().error().is_some(); + let err = handler + .handle(req) + .as_response() + .unwrap() + .error() + .is_some(); assert!(err); let mut req = HttpRequest::default(); @@ -411,10 +430,18 @@ mod tests { header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), ); - req.headers_mut() - .insert(header::CONTENT_LENGTH, header::HeaderValue::from_static("16")); - req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); - let ok = handler.handle(req).as_response().unwrap().error().is_none(); + req.headers_mut().insert( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ); + req.payload_mut() + .unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); + let ok = handler + .handle(req) + .as_response() + .unwrap() + .error() + .is_none(); assert!(ok) } } diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index aa0bd494..5b503630 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -275,7 +275,9 @@ impl Cors { /// `ResourceHandler::middleware()` method, but in that case *Cors* /// middleware wont be able to handle *OPTIONS* requests. pub fn register(self, resource: &mut ResourceHandler) { - resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok()); + resource + .method(Method::OPTIONS) + .h(|_| HttpResponse::Ok()); resource.middleware(self); } @@ -302,7 +304,9 @@ impl Cors { fn validate_allowed_method( &self, req: &mut HttpRequest, ) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { + if let Some(hdr) = req.headers() + .get(header::ACCESS_CONTROL_REQUEST_METHOD) + { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { return self.inner @@ -324,8 +328,8 @@ impl Cors { match self.inner.headers { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + if let Some(hdr) = req.headers() + .get(header::ACCESS_CONTROL_REQUEST_HEADERS) { if let Ok(headers) = hdr.to_str() { let mut hdrs = HashSet::new(); @@ -367,8 +371,8 @@ impl Middleware for Cors { .as_str()[1..], ).unwrap(), ) - } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + } else if let Some(hdr) = req.headers() + .get(header::ACCESS_CONTROL_REQUEST_HEADERS) { Some(hdr.clone()) } else { @@ -819,13 +823,17 @@ impl CorsBuilder { } if let AllOrSome::Some(ref origins) = cors.origins { - let s = origins.iter().fold(String::new(), |s, v| s + &v.to_string()); + let s = origins + .iter() + .fold(String::new(), |s, v| s + &v.to_string()); cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap()); } if !self.expose_hdrs.is_empty() { cors.expose_hdrs = Some( - self.expose_hdrs.iter().fold(String::new(), |s, v| s + v.as_str())[1..] + self.expose_hdrs + .iter() + .fold(String::new(), |s, v| s + v.as_str())[1..] .to_owned(), ); } @@ -903,19 +911,27 @@ mod tests { #[test] #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] fn cors_validates_illegal_allow_credentials() { - Cors::build().supports_credentials().send_wildcard().finish(); + Cors::build() + .supports_credentials() + .send_wildcard() + .finish(); } #[test] #[should_panic(expected = "No resources are registered")] fn no_resource() { - Cors::build().supports_credentials().send_wildcard().register(); + Cors::build() + .supports_credentials() + .send_wildcard() + .register(); } #[test] #[should_panic(expected = "Cors::for_app(app)")] fn no_resource2() { - Cors::build().resource("/test", |r| r.f(|_| HttpResponse::Ok())).register(); + Cors::build() + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + .register(); } #[test] @@ -952,18 +968,27 @@ mod tests { let mut req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "AUTHORIZATION,ACCEPT") + .header( + header::ACCESS_CONTROL_REQUEST_HEADERS, + "AUTHORIZATION,ACCEPT", + ) .method(Method::OPTIONS) .finish(); let resp = cors.start(&mut req).unwrap().response(); assert_eq!( &b"*"[..], - resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes() + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() ); assert_eq!( &b"3600"[..], - resp.headers().get(header::ACCESS_CONTROL_MAX_AGE).unwrap().as_bytes() + resp.headers() + .get(header::ACCESS_CONTROL_MAX_AGE) + .unwrap() + .as_bytes() ); //assert_eq!( // &b"authorization,accept,content-type"[..], @@ -980,7 +1005,9 @@ mod tests { #[test] #[should_panic(expected = "MissingOrigin")] fn test_validate_missing_origin() { - let cors = Cors::build().allowed_origin("https://www.example.com").finish(); + let cors = Cors::build() + .allowed_origin("https://www.example.com") + .finish(); let mut req = HttpRequest::default(); cors.start(&mut req).unwrap(); @@ -989,7 +1016,9 @@ mod tests { #[test] #[should_panic(expected = "OriginNotAllowed")] fn test_validate_not_allowed_origin() { - let cors = Cors::build().allowed_origin("https://www.example.com").finish(); + let cors = Cors::build() + .allowed_origin("https://www.example.com") + .finish(); let mut req = TestRequest::with_header("Origin", "https://www.unknown.com") .method(Method::GET) @@ -999,7 +1028,9 @@ mod tests { #[test] fn test_validate_origin() { - let cors = Cors::build().allowed_origin("https://www.example.com").finish(); + let cors = Cors::build() + .allowed_origin("https://www.example.com") + .finish(); let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::GET) @@ -1015,7 +1046,11 @@ mod tests { let mut req = TestRequest::default().method(Method::GET).finish(); let resp: HttpResponse = HttpResponse::Ok().into(); let resp = cors.response(&mut req, resp).unwrap().response(); - assert!(resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).is_none()); + assert!( + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .is_none() + ); let mut req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) @@ -1023,7 +1058,10 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], - resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes() + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() ); } @@ -1046,12 +1084,19 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"*"[..], - resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes() + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + assert_eq!( + &b"Origin"[..], + resp.headers().get(header::VARY).unwrap().as_bytes() ); - assert_eq!(&b"Origin"[..], resp.headers().get(header::VARY).unwrap().as_bytes()); - let resp: HttpResponse = - HttpResponse::Ok().header(header::VARY, "Accept").finish(); + let resp: HttpResponse = HttpResponse::Ok() + .header(header::VARY, "Accept") + .finish(); let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"Accept, Origin"[..], @@ -1066,7 +1111,10 @@ mod tests { let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], - resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes() + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() ); } diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 255b4564..9ff23b53 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -89,7 +89,10 @@ fn origin(headers: &HeaderMap) -> Option, CsrfError>> { headers .get(header::ORIGIN) .map(|origin| { - origin.to_str().map_err(|_| CsrfError::BadOrigin).map(|o| o.into()) + origin + .to_str() + .map_err(|_| CsrfError::BadOrigin) + .map(|o| o.into()) }) .or_else(|| { headers.get(header::REFERER).map(|referer| { @@ -258,8 +261,9 @@ mod tests { fn test_upgrade() { let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - let lax_csrf = - CsrfFilter::new().allowed_origin("https://www.example.com").allow_upgrade(); + let lax_csrf = CsrfFilter::new() + .allowed_origin("https://www.example.com") + .allow_upgrade(); let mut req = TestRequest::with_header("Origin", "https://cswsh.com") .header("Connection", "Upgrade") diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index ebe3ea1d..bab5ff0b 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -112,7 +112,9 @@ mod tests { }; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); + let resp = HttpResponse::Ok() + .header(CONTENT_TYPE, "0002") + .finish(); let resp = match mw.response(&mut req, resp) { Ok(Response::Done(resp)) => resp, _ => panic!(), diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 06c5a4fa..50df4df4 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -164,9 +164,7 @@ pub struct IdentityService { impl IdentityService { /// Create new identity service with specified backend. pub fn new(backend: T) -> Self { - IdentityService { - backend, - } + IdentityService { backend } } } @@ -181,13 +179,15 @@ impl> Middleware for IdentityService { fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); - let fut = self.backend.from_request(&mut req).then(move |res| match res { - Ok(id) => { - req.extensions().insert(IdentityBox(Box::new(id))); - FutOk(None) - } - Err(err) => FutErr(err), - }); + let fut = self.backend + .from_request(&mut req) + .then(move |res| match res { + Ok(id) => { + req.extensions().insert(IdentityBox(Box::new(id))); + FutOk(None) + } + Err(err) => FutErr(err), + }); Ok(Started::Future(Box::new(fut))) } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 7b5d6c4c..28964718 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -254,9 +254,10 @@ impl FormatText { "-".fmt(fmt) } } - FormatText::RequestTime => { - entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]").unwrap().fmt(fmt) - } + FormatText::RequestTime => entry_time + .strftime("[%d/%b/%Y:%H:%M:%S %z]") + .unwrap() + .fmt(fmt), FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { if let Ok(s) = val.to_str() { @@ -313,8 +314,10 @@ mod tests { let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); let mut headers = HeaderMap::new(); - headers - .insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); + headers.insert( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + ); let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -351,8 +354,10 @@ mod tests { let format = Format::default(); let mut headers = HeaderMap::new(); - headers - .insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB")); + headers.insert( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + ); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -360,7 +365,9 @@ mod tests { headers, None, ); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let resp = HttpResponse::build(StatusCode::OK) + .force_close() + .finish(); let entry_time = time::now(); let render = |fmt: &mut Formatter| { @@ -381,7 +388,9 @@ mod tests { HeaderMap::new(), None, ); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let resp = HttpResponse::build(StatusCode::OK) + .force_close() + .finish(); let entry_time = time::now(); let render = |fmt: &mut Formatter| { diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 9cc7acb1..4aaf1b7a 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -202,13 +202,16 @@ impl> Middleware for SessionStorage { fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); - let fut = self.0.from_request(&mut req).then(move |res| match res { - Ok(sess) => { - req.extensions().insert(Arc::new(SessionImplBox(Box::new(sess)))); - FutOk(None) - } - Err(err) => FutErr(err), - }); + let fut = self.0 + .from_request(&mut req) + .then(move |res| match res { + Ok(sess) => { + req.extensions() + .insert(Arc::new(SessionImplBox(Box::new(sess)))); + FutOk(None) + } + Err(err) => FutErr(err), + }); Ok(Started::Future(Box::new(fut))) } diff --git a/src/multipart.rs b/src/multipart.rs index 0fc98000..056332b5 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -122,7 +122,11 @@ where if let Some(err) = self.error.take() { Err(err) } else if self.safety.current() { - self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) + self.inner + .as_mut() + .unwrap() + .borrow_mut() + .poll(&self.safety) } else { Ok(Async::NotReady) } @@ -671,7 +675,10 @@ mod tests { } let mut headers = HeaderMap::new(); - headers.insert(header::CONTENT_TYPE, header::HeaderValue::from_static("test")); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("test"), + ); match Multipart::boundary(&headers) { Err(MultipartError::ParseContentType) => (), diff --git a/src/param.rs b/src/param.rs index 99cc3def..41100763 100644 --- a/src/param.rs +++ b/src/param.rs @@ -94,7 +94,8 @@ impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> { type Output = str; fn index(&self, name: &'b str) -> &str { - self.get(name).expect("Value for parameter is not available") + self.get(name) + .expect("Value for parameter is not available") } } @@ -201,9 +202,18 @@ mod tests { 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("/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"])) diff --git a/src/payload.rs b/src/payload.rs index d3b5a59b..a394c106 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -47,9 +47,7 @@ impl Payload { PayloadSender { inner: Rc::downgrade(&shared), }, - Payload { - inner: shared, - }, + Payload { inner: shared }, ) } @@ -536,7 +534,10 @@ mod tests { assert_eq!(format!("{}", err.cause().unwrap()), "ParseError"); let err = PayloadError::Incomplete; - assert_eq!(format!("{}", err), "A payload reached EOF, but is not complete."); + assert_eq!( + format!("{}", err), + "A payload reached EOF, but is not complete." + ); } #[test] @@ -670,7 +671,10 @@ mod tests { let (mut sender, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); - assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); + assert_eq!( + Async::NotReady, + payload.read_until(b"ne").ok().unwrap() + ); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); diff --git a/src/pipeline.rs b/src/pipeline.rs index 36cb037a..aefd979d 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -796,15 +796,18 @@ mod tests { .unwrap() .run(lazy(|| { let mut info = PipelineInfo::new(HttpRequest::default()); - Completed::<(), Inner<()>>::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: Addr = ctx.address(); let mut info = PipelineInfo::new(req); info.context = Some(Box::new(ctx)); - let mut state = - Completed::<(), Inner<()>>::init(&mut info).completed().unwrap(); + let mut state = Completed::<(), Inner<()>>::init(&mut info) + .completed() + .unwrap(); assert!(state.poll(&mut info).is_none()); let pp = Pipeline(info, PipelineState::Completed(state)); diff --git a/src/pred.rs b/src/pred.rs index 90a0d61f..34792e36 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -181,7 +181,11 @@ pub fn Header( } #[doc(hidden)] -pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); +pub struct HeaderPredicate( + header::HeaderName, + header::HeaderValue, + PhantomData, +); impl Predicate for HeaderPredicate { fn check(&self, req: &mut HttpRequest) -> bool { diff --git a/src/resource.rs b/src/resource.rs index 7ce44c0f..a6e6b731 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -132,7 +132,10 @@ impl ResourceHandler { /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Method(method)) + self.routes + .last_mut() + .unwrap() + .filter(pred::Method(method)) } /// Register a new route and add handler object. @@ -185,7 +188,9 @@ impl ResourceHandler { /// This is similar to `App's` middlewares, but /// middlewares get invoked on resource level. pub fn middleware>(&mut self, mw: M) { - Rc::get_mut(&mut self.middlewares).unwrap().push(Box::new(mw)); + Rc::get_mut(&mut self.middlewares) + .unwrap() + .push(Box::new(mw)); } pub(crate) fn handle( diff --git a/src/route.rs b/src/route.rs index 346edecd..681ee1cb 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,6 +1,6 @@ use futures::{Async, Future, Poll}; +use std::cell::UnsafeCell; use std::marker::PhantomData; -use std::mem; use std::rc::Rc; use error::Error; @@ -180,14 +180,22 @@ impl Route { { let cfg1 = ExtractorConfig::default(); let cfg2 = ExtractorConfig::default(); - self.h(With2::new(handler, Clone::clone(&cfg1), Clone::clone(&cfg2))); + self.h(With2::new( + handler, + Clone::clone(&cfg1), + Clone::clone(&cfg2), + )); (cfg1, cfg2) } /// Set handler function, use request extractor for all paramters. pub fn with3( &mut self, handler: F, - ) -> (ExtractorConfig, ExtractorConfig, ExtractorConfig) + ) -> ( + ExtractorConfig, + ExtractorConfig, + ExtractorConfig, + ) where F: Fn(T1, T2, T3) -> R + 'static, R: Responder + 'static, @@ -210,12 +218,14 @@ impl Route { /// `RouteHandler` wrapper. This struct is required because it needs to be /// shared for resource level middlewares. -struct InnerHandler(Rc>>); +struct InnerHandler(Rc>>>); impl InnerHandler { #[inline] fn new>(h: H) -> Self { - InnerHandler(Rc::new(Box::new(WrapHandler::new(h)))) + InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new( + h, + ))))) } #[inline] @@ -226,16 +236,15 @@ impl InnerHandler { R: Responder + 'static, E: Into + 'static, { - InnerHandler(Rc::new(Box::new(AsyncHandler::new(h)))) + InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new( + h, + ))))) } #[inline] pub fn handle(&self, req: HttpRequest) -> Reply { - // reason: handler is unique per thread, - // handler get called from async code only - #[allow(mutable_transmutes)] - #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] - let h: &mut Box> = unsafe { mem::transmute(self.0.as_ref()) }; + // reason: handler is unique per thread, handler get called from async code only + let h = unsafe { &mut *self.0.as_ref().get() }; h.handle(req) } } @@ -290,10 +299,7 @@ impl Compose { }; let state = StartMiddlewares::init(&mut info); - Compose { - state, - info, - } + Compose { state, info } } } diff --git a/src/router.rs b/src/router.rs index 35f9d7f5..dd29e4bc 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; -use std::mem; use std::rc::Rc; use regex::{escape, Regex}; @@ -79,12 +78,8 @@ impl Router { if self.0.prefix_len > req.path().len() { return None; } - let path: &str = unsafe { mem::transmute(&req.path()[self.0.prefix_len..]) }; - let route_path = if path.is_empty() { - "/" - } else { - path - }; + let path = unsafe { &*(&req.path()[self.0.prefix_len..] as *const str) }; + let route_path = if path.is_empty() { "/" } else { path }; for (idx, pattern) in self.0.patterns.iter().enumerate() { if pattern.match_with_params(route_path, req.match_info_mut()) { @@ -102,11 +97,7 @@ impl Router { /// following path would be recognizable `/test/name` but `has_route()` call /// would return `false`. pub fn has_route(&self, path: &str) -> bool { - let path = if path.is_empty() { - "/" - } else { - path - }; + let path = if path.is_empty() { "/" } else { path }; for pattern in &self.0.patterns { if pattern.is_match(path) { @@ -391,19 +382,34 @@ mod tests { #[test] fn test_recognizer() { let routes = vec![ - (Resource::new("", "/name"), Some(ResourceHandler::default())), - (Resource::new("", "/name/{val}"), Some(ResourceHandler::default())), + ( + Resource::new("", "/name"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/name/{val}"), + Some(ResourceHandler::default()), + ), ( Resource::new("", "/name/{val}/index.html"), Some(ResourceHandler::default()), ), - (Resource::new("", "/file/{file}.{ext}"), Some(ResourceHandler::default())), + ( + Resource::new("", "/file/{file}.{ext}"), + Some(ResourceHandler::default()), + ), ( Resource::new("", "/v{val}/{val2}/index.html"), Some(ResourceHandler::default()), ), - (Resource::new("", "/v/{tail:.*}"), Some(ResourceHandler::default())), - (Resource::new("", "{test}/index.html"), Some(ResourceHandler::default())), + ( + Resource::new("", "/v/{tail:.*}"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "{test}/index.html"), + Some(ResourceHandler::default()), + ), ]; let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); @@ -432,7 +438,10 @@ mod tests { let mut req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); assert_eq!(rec.recognize(&mut req), Some(5)); - assert_eq!(req.match_info().get("tail").unwrap(), "blah-blah/index.html"); + assert_eq!( + req.match_info().get("tail").unwrap(), + "blah-blah/index.html" + ); let mut req = TestRequest::with_uri("/bbb/index.html").finish(); assert_eq!(rec.recognize(&mut req), Some(6)); @@ -442,8 +451,14 @@ mod tests { #[test] fn test_recognizer_2() { let routes = vec![ - (Resource::new("", "/index.json"), Some(ResourceHandler::default())), - (Resource::new("", "/{source}.json"), Some(ResourceHandler::default())), + ( + Resource::new("", "/index.json"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/{source}.json"), + Some(ResourceHandler::default()), + ), ]; let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); @@ -457,8 +472,14 @@ mod tests { #[test] fn test_recognizer_with_prefix() { let routes = vec![ - (Resource::new("", "/name"), Some(ResourceHandler::default())), - (Resource::new("", "/name/{val}"), Some(ResourceHandler::default())), + ( + Resource::new("", "/name"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/name/{val}"), + Some(ResourceHandler::default()), + ), ]; let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes); @@ -475,8 +496,14 @@ mod tests { // same patterns let routes = vec![ - (Resource::new("", "/name"), Some(ResourceHandler::default())), - (Resource::new("", "/name/{val}"), Some(ResourceHandler::default())), + ( + Resource::new("", "/name"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("", "/name/{val}"), + Some(ResourceHandler::default()), + ), ]; let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes); @@ -545,8 +572,14 @@ mod tests { #[test] fn test_request_resource() { let routes = vec![ - (Resource::new("r1", "/index.json"), Some(ResourceHandler::default())), - (Resource::new("r2", "/test.json"), Some(ResourceHandler::default())), + ( + Resource::new("r1", "/index.json"), + Some(ResourceHandler::default()), + ), + ( + Resource::new("r2", "/test.json"), + Some(ResourceHandler::default()), + ), ]; let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); diff --git a/src/server/channel.rs b/src/server/channel.rs index 03ec69d9..e5d226ed 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -1,6 +1,6 @@ use std::net::{Shutdown, SocketAddr}; use std::rc::Rc; -use std::{io, mem, ptr, time}; +use std::{io, ptr, time}; use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::{Async, Future, Poll}; @@ -93,12 +93,12 @@ where let el = self as *mut _; self.node = Some(Node::new(el)); let _ = match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => { - self.node.as_ref().map(|n| h1.settings().head().insert(n)) - } - Some(HttpProtocol::H2(ref mut h2)) => { - self.node.as_ref().map(|n| h2.settings().head().insert(n)) - } + Some(HttpProtocol::H1(ref mut h1)) => self.node + .as_ref() + .map(|n| h1.settings().head().insert(n)), + Some(HttpProtocol::H2(ref mut h2)) => self.node + .as_ref() + .map(|n| h2.settings().head().insert(n)), Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => { self.node.as_ref().map(|n| settings.head().insert(n)) } @@ -168,8 +168,9 @@ where if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { match kind { ProtocolKind::Http1 => { - self.proto = - Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf))); + self.proto = Some(HttpProtocol::H1(h1::Http1::new( + settings, io, addr, buf, + ))); return self.poll(); } ProtocolKind::Http2 => { @@ -210,10 +211,11 @@ impl Node { &mut *(next2.as_ref().unwrap() as *const _ as *mut _); n.prev = Some(next as *const _ as *mut _); } - let slf: &mut Node = mem::transmute(self); + let slf: &mut Node = &mut *(self as *const _ as *mut _); + slf.next = Some(next as *const _ as *mut _); - let next: &mut Node = mem::transmute(next); + let next: &mut Node = &mut *(next as *const _ as *mut _); next.prev = Some(slf as *const _ as *mut _); } } @@ -249,12 +251,12 @@ impl Node<()> { loop { if let Some(n) = next { unsafe { - let n: &Node<()> = mem::transmute(n.as_ref().unwrap()); + let n: &Node<()> = &*(n.as_ref().unwrap() as *const _); next = n.next.as_ref(); if !n.element.is_null() { let ch: &mut HttpChannel = - mem::transmute(&mut *(n.element as *mut _)); + &mut *(&mut *(n.element as *mut _) as *mut () as *mut _); ch.shutdown(); } } @@ -278,9 +280,7 @@ where T: AsyncRead + AsyncWrite + 'static, { pub fn new(io: T) -> Self { - WrapperStream { - io, - } + WrapperStream { io } } } diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 6e450f71..ae69ae07 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -763,7 +763,8 @@ impl TransferEncoding { return Ok(*remaining == 0); } let len = cmp::min(*remaining, msg.len() as u64); - self.buffer.extend(msg.take().split_to(len as usize).into()); + self.buffer + .extend(msg.take().split_to(len as usize).into()); *remaining -= len as u64; Ok(*remaining == 0) @@ -855,8 +856,10 @@ impl AcceptEncoding { /// Parse a raw Accept-Encoding header value into an ordered list. pub fn parse(raw: &str) -> ContentEncoding { - let mut encodings: Vec<_> = - raw.replace(' ', "").split(',').map(|l| AcceptEncoding::new(l)).collect(); + let mut encodings: Vec<_> = raw.replace(' ', "") + .split(',') + .map(|l| AcceptEncoding::new(l)) + .collect(); encodings.sort(); for enc in encodings { @@ -876,7 +879,9 @@ mod tests { fn test_chunked_te() { let bytes = SharedBytes::default(); let mut enc = TransferEncoding::chunked(bytes.clone()); - assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap()); + assert!(!enc.encode(Binary::from(b"test".as_ref())) + .ok() + .unwrap()); assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap()); assert_eq!( bytes.get_mut().take().freeze(), diff --git a/src/server/h1.rs b/src/server/h1.rs index 4a197603..f420ecb7 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -1,10 +1,8 @@ -#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] - use std::collections::VecDeque; +use std::io; use std::net::SocketAddr; use std::rc::Rc; use std::time::Duration; -use std::{io, mem}; use actix::Arbiter; use bytes::{BufMut, BytesMut}; @@ -153,28 +151,25 @@ where if !self.flags.intersects(Flags::ERROR) && self.tasks.len() < MAX_PIPELINED_MESSAGES && self.can_read() { - match self.read() { - Ok(true) | Err(_) => { - // notify all tasks - self.stream.disconnected(); - for entry in &mut self.tasks { - entry.pipe.disconnected() - } - // kill keepalive - self.flags.remove(Flags::KEEPALIVE); - self.keepalive_timer.take(); - - // on parse error, stop reading stream but tasks need to be - // completed - self.flags.insert(Flags::ERROR); - - if let Some(ref mut payload) = self.payload { - payload.set_error(PayloadError::Incomplete); - } + if self.read() { + // notify all tasks + self.stream.disconnected(); + for entry in &mut self.tasks { + entry.pipe.disconnected() } - Ok(false) => { - self.parse(); + // kill keepalive + self.flags.remove(Flags::KEEPALIVE); + self.keepalive_timer.take(); + + // on parse error, stop reading stream but tasks need to be + // completed + self.flags.insert(Flags::ERROR); + + if let Some(ref mut payload) = self.payload { + payload.set_error(PayloadError::Incomplete); } + } else { + self.parse(); } } } @@ -186,7 +181,7 @@ where let mut io = false; let mut idx = 0; while idx < self.tasks.len() { - let item: &mut Entry = unsafe { mem::transmute(&mut self.tasks[idx]) }; + let item: &mut Entry = unsafe { &mut *(&mut self.tasks[idx] as *mut _) }; // only one task can do io operation in http/1 if !io && !item.flags.contains(EntryFlags::EOF) { @@ -210,7 +205,8 @@ where self.stream.reset(); if ready { - item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED); + item.flags + .insert(EntryFlags::EOF | EntryFlags::FINISHED); } else { item.flags.insert(EntryFlags::FINISHED); } @@ -252,7 +248,10 @@ where // cleanup finished tasks let max = self.tasks.len() >= MAX_PIPELINED_MESSAGES; while !self.tasks.is_empty() { - if self.tasks[0].flags.contains(EntryFlags::EOF | EntryFlags::FINISHED) { + if self.tasks[0] + .flags + .contains(EntryFlags::EOF | EntryFlags::FINISHED) + { self.tasks.pop_front(); } else { break; @@ -277,7 +276,7 @@ where // deal with keep-alive if self.tasks.is_empty() { - // no keep-alive situations + // no keep-alive if self.flags.contains(Flags::ERROR) || (!self.flags.contains(Flags::KEEPALIVE) || !self.settings.keep_alive_enabled()) @@ -304,10 +303,7 @@ where pub fn parse(&mut self) { 'outer: loop { match self.decoder.decode(&mut self.buf, &self.settings) { - Ok(Some(Message::Message { - msg, - payload, - })) => { + Ok(Some(Message::Message { msg, payload })) => { self.flags.insert(Flags::STARTED); if payload { @@ -377,7 +373,7 @@ where } #[inline] - fn read(&mut self) -> io::Result { + fn read(&mut self) -> bool { loop { unsafe { if self.buf.remaining_mut() < LW_BUFFER_SIZE { @@ -386,16 +382,16 @@ where match self.stream.get_mut().read(self.buf.bytes_mut()) { Ok(n) => { if n == 0 { - return Ok(true); + return true; } else { self.buf.advance_mut(n); } } Err(e) => { return if e.kind() == io::ErrorKind::WouldBlock { - Ok(false) + false } else { - Err(e) + true }; } } @@ -420,19 +416,13 @@ mod tests { impl Message { fn message(self) -> SharedHttpInnerMessage { match self { - Message::Message { - msg, - payload: _, - } => msg, + Message::Message { msg, payload: _ } => msg, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - Message::Message { - msg: _, - payload, - } => payload, + Message::Message { msg: _, payload } => payload, _ => panic!("error"), } } @@ -628,7 +618,10 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); + assert_eq!( + req.headers().get("test").unwrap().as_bytes(), + b"value" + ); } Ok(_) | Err(_) => unreachable!("Error during parsing http request"), } @@ -850,7 +843,12 @@ mod tests { assert!(!req.keep_alive()); assert!(req.upgrade()); assert_eq!( - reader.decode(&mut buf, &settings).unwrap().unwrap().chunk().as_ref(), + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk() + .as_ref(), b"some raw data" ); } @@ -907,14 +905,30 @@ mod tests { buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); assert_eq!( - reader.decode(&mut buf, &settings).unwrap().unwrap().chunk().as_ref(), + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk() + .as_ref(), b"data" ); assert_eq!( - reader.decode(&mut buf, &settings).unwrap().unwrap().chunk().as_ref(), + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk() + .as_ref(), b"line" ); - assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); + assert!( + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .eof() + ); } #[test] @@ -995,7 +1009,13 @@ mod tests { assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); buf.extend(b"\r\n"); - assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); + assert!( + reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .eof() + ); } #[test] @@ -1013,9 +1033,17 @@ mod tests { assert!(req.chunked().unwrap()); buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); + let chunk = reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk(); assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); + let chunk = reader + .decode(&mut buf, &settings) + .unwrap() + .unwrap() + .chunk(); assert_eq!(chunk, Bytes::from_static(b"line")); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.eof()); diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 3895c8c7..7ff6e8b9 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -41,9 +41,7 @@ impl From for DecoderError { impl H1Decoder { pub fn new() -> H1Decoder { - H1Decoder { - decoder: None, - } + H1Decoder { decoder: None } } pub fn decode( @@ -61,7 +59,9 @@ impl H1Decoder { } } - match self.parse_message(src, settings).map_err(DecoderError::Error)? { + match self.parse_message(src, settings) + .map_err(DecoderError::Error)? + { Async::Ready((msg, decoder)) => { if let Some(decoder) = decoder { self.decoder = Some(decoder); @@ -415,9 +415,10 @@ impl ChunkedState { match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), - _ => { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk size LF")) - } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size LF", + )), } } @@ -450,33 +451,37 @@ impl ChunkedState { fn read_body_cr(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)), - _ => { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk body CR")) - } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk body CR", + )), } } fn read_body_lf(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\n' => Ok(Async::Ready(ChunkedState::Size)), - _ => { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk body LF")) - } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk body LF", + )), } } fn read_end_cr(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\r' => Ok(Async::Ready(ChunkedState::EndLf)), - _ => { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk end CR")) - } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk end CR", + )), } } fn read_end_lf(rdr: &mut BytesMut) -> Poll { match byte!(rdr) { b'\n' => Ok(Async::Ready(ChunkedState::End)), - _ => { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid chunk end LF")) - } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk end LF", + )), } } } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 08d40d09..c0fa0609 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -117,7 +117,8 @@ impl Writer for H1Writer { let version = msg.version().unwrap_or_else(|| req.version); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); - msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade")); + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive else if self.flags.contains(Flags::KEEPALIVE) { @@ -126,7 +127,8 @@ impl Writer for H1Writer { .insert(CONNECTION, HeaderValue::from_static("keep-alive")); } } else if version >= Version::HTTP_11 { - msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("close")); + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("close")); } let body = msg.replace_body(Body::Empty); diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index a9dc06fd..575d4176 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -107,7 +107,8 @@ impl Writer for H2Writer { ); } Body::Empty => { - msg.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + msg.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); } _ => (), } @@ -119,7 +120,9 @@ impl Writer for H2Writer { resp.headers_mut().insert(key, value.clone()); } - match self.respond.send_response(resp, self.flags.contains(Flags::EOF)) { + match self.respond + .send_response(resp, self.flags.contains(Flags::EOF)) + { Ok(stream) => self.stream = Some(stream), Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), } diff --git a/src/server/helpers.rs b/src/server/helpers.rs index ae8c2be8..c579ec07 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -96,8 +96,9 @@ const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 8081828384858687888990919293949596979899"; pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; 13] = - [b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', 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', @@ -250,33 +251,63 @@ mod tests { let mut bytes = BytesMut::new(); bytes.reserve(50); write_content_length(0, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 0\r\n"[..] + ); bytes.reserve(50); write_content_length(9, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 9\r\n"[..] + ); bytes.reserve(50); write_content_length(10, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 10\r\n"[..] + ); bytes.reserve(50); write_content_length(99, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 99\r\n"[..] + ); bytes.reserve(50); write_content_length(100, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 100\r\n"[..] + ); bytes.reserve(50); write_content_length(101, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 101\r\n"[..] + ); bytes.reserve(50); write_content_length(998, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 998\r\n"[..] + ); bytes.reserve(50); write_content_length(1000, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 1000\r\n"[..] + ); bytes.reserve(50); write_content_length(1001, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 1001\r\n"[..] + ); bytes.reserve(50); write_content_length(5909, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); + assert_eq!( + bytes.take().freeze(), + b"\r\ncontent-length: 5909\r\n"[..] + ); } } diff --git a/src/server/settings.rs b/src/server/settings.rs index 291fcf13..cd17681b 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -255,7 +255,10 @@ mod tests { #[test] fn test_date_len() { - assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); + assert_eq!( + DATE_VALUE_LENGTH, + "Sun, 06 Nov 1994 08:49:37 GMT".len() + ); } #[test] diff --git a/src/server/srv.rs b/src/server/srv.rs index 276e1e20..57699b20 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -219,7 +219,10 @@ where if let Some(e) = err.take() { Err(e) } else { - Err(io::Error::new(io::ErrorKind::Other, "Can not bind to address.")) + Err(io::Error::new( + io::ErrorKind::Other, + "Can not bind to address.", + )) } } else { Ok(self) @@ -375,7 +378,10 @@ impl HttpServer { /// Start listening for incoming tls connections. pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result> { if self.sockets.is_empty() { - Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) + Err(io::Error::new( + io::ErrorKind::Other, + "No socket addresses are bound", + )) } else { let (tx, rx) = mpsc::unbounded(); let addrs: Vec<(net::SocketAddr, net::TcpListener)> = @@ -424,7 +430,10 @@ impl HttpServer { mut self, mut builder: SslAcceptorBuilder, ) -> io::Result> { if self.sockets.is_empty() { - Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound")) + Err(io::Error::new( + io::ErrorKind::Other, + "No socket addresses are bound", + )) } else { // alpn support if !self.no_http2 { @@ -554,35 +563,17 @@ impl Handler for HttpServer { signal::SignalType::Int => { info!("SIGINT received, exiting"); self.exit = true; - Handler::::handle( - self, - StopServer { - graceful: false, - }, - ctx, - ); + Handler::::handle(self, StopServer { graceful: false }, ctx); } signal::SignalType::Term => { info!("SIGTERM received, stopping"); self.exit = true; - Handler::::handle( - self, - StopServer { - graceful: true, - }, - ctx, - ); + Handler::::handle(self, StopServer { graceful: true }, ctx); } signal::SignalType::Quit => { info!("SIGQUIT received, exiting"); self.exit = true; - Handler::::handle( - self, - StopServer { - graceful: false, - }, - ctx, - ); + Handler::::handle(self, StopServer { graceful: false }, ctx); } _ => (), } @@ -706,9 +697,7 @@ impl Handler for HttpServer { let tx2 = tx.clone(); worker .1 - .send(StopWorker { - graceful: dur, - }) + .send(StopWorker { graceful: dur }) .into_actor(self) .then(move |_, slf, ctx| { slf.workers.pop(); @@ -758,8 +747,9 @@ fn start_accept_thread( // start accept thread #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - let _ = thread::Builder::new().name(format!("Accept on {}", addr)).spawn( - move || { + let _ = thread::Builder::new() + .name(format!("Accept on {}", addr)) + .spawn(move || { const SRV: mio::Token = mio::Token(0); const CMD: mio::Token = mio::Token(1); @@ -784,9 +774,12 @@ fn start_accept_thread( } // Start listening for incoming commands - if let Err(err) = - poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge()) - { + if let Err(err) = poll.register( + ®, + CMD, + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { panic!("Can not register Registration: {}", err); } @@ -917,8 +910,7 @@ fn start_accept_thread( } } } - }, - ); + }); (readiness, tx) } diff --git a/src/server/worker.rs b/src/server/worker.rs index 16eb2946..f10f79cb 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -77,7 +77,9 @@ impl Worker { fn update_time(&self, ctx: &mut Context) { self.settings.update_date(); - ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); + ctx.run_later(time::Duration::new(1, 0), |slf, ctx| { + slf.update_time(ctx) + }); } fn shutdown_timeout( @@ -122,7 +124,8 @@ where if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { error!("Can not set socket keep-alive option"); } - self.handler.handle(Rc::clone(&self.settings), &self.hnd, msg); + self.handler + .handle(Rc::clone(&self.settings), &self.hnd, msg); } } @@ -174,57 +177,52 @@ impl StreamHandlerType { } #[cfg(feature = "tls")] StreamHandlerType::Tls(ref acceptor) => { - let Conn { - io, - peer, - http2, - } = msg; + let Conn { io, peer, http2 } = msg; let _ = io.set_nodelay(true); 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(())) - })); + 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 Conn { io, peer, .. } = msg; let _ = io.set_nodelay(true); 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(())) - })); + 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/src/test.rs b/src/test.rs index d8ae8067..13209f1d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -170,22 +170,14 @@ impl TestServer { if uri.starts_with('/') { format!( "{}://{}{}", - if self.ssl { - "https" - } else { - "http" - }, + if self.ssl { "https" } else { "http" }, self.addr, uri ) } else { format!( "{}://{}/{}", - if self.ssl { - "https" - } else { - "http" - }, + if self.ssl { "https" } else { "http" }, self.addr, uri ) @@ -358,14 +350,17 @@ pub struct TestApp { impl TestApp { fn new(state: S) -> TestApp { let app = App::with_state(state); - TestApp { - app: Some(app), - } + 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))); + self.app = Some( + self.app + .take() + .unwrap() + .resource("/", |r| r.h(handler)), + ); } /// Register middleware diff --git a/src/uri.rs b/src/uri.rs index f2e16cec..d30fe5cb 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -44,10 +44,7 @@ impl Url { pub fn new(uri: Uri) -> Url { let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); - Url { - uri, - path, - } + Url { uri, path } } pub fn uri(&self) -> &Uri { diff --git a/src/with.rs b/src/with.rs index a18139dc..a35d1a3b 100644 --- a/src/with.rs +++ b/src/with.rs @@ -307,8 +307,10 @@ where Async::Ready(item) => { self.item = Some(item); self.fut1.take(); - self.fut2 = - Some(Box::new(T2::from_request(&self.req, self.cfg2.as_ref()))); + self.fut2 = Some(Box::new(T2::from_request( + &self.req, + self.cfg2.as_ref(), + ))); } Async::NotReady => return Ok(Async::NotReady), } @@ -508,8 +510,10 @@ where Async::Ready(item) => { self.item1 = Some(item); self.fut1.take(); - self.fut2 = - Some(Box::new(T2::from_request(&self.req, self.cfg2.as_ref()))); + self.fut2 = Some(Box::new(T2::from_request( + &self.req, + self.cfg2.as_ref(), + ))); } Async::NotReady => return Ok(Async::NotReady), } @@ -520,8 +524,10 @@ where Async::Ready(item) => { self.item2 = Some(item); self.fut2.take(); - self.fut3 = - Some(Box::new(T3::from_request(&self.req, self.cfg3.as_ref()))); + self.fut3 = Some(Box::new(T3::from_request( + &self.req, + self.cfg3.as_ref(), + ))); } Async::NotReady => return Ok(Async::NotReady), } @@ -533,13 +539,15 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - let item = - match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item) - .respond_to(self.req.drop_state()) - { - Ok(item) => item.into(), - Err(err) => return Err(err.into()), - }; + let item = match (*hnd)( + self.item1.take().unwrap(), + self.item2.take().unwrap(), + item, + ).respond_to(self.req.drop_state()) + { + Ok(item) => item.into(), + Err(err) => return Err(err.into()), + }; match item.into() { ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), diff --git a/src/ws/client.rs b/src/ws/client.rs index 174ee4a1..8a4abcae 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -142,8 +142,9 @@ impl Client { U: IntoIterator + 'static, V: AsRef, { - let mut protos = - protos.into_iter().fold(String::new(), |acc, s| acc + s.as_ref() + ","); + let mut protos = protos + .into_iter() + .fold(String::new(), |acc, s| acc + s.as_ref() + ","); protos.pop(); self.protocols = Some(protos); self @@ -217,7 +218,8 @@ impl Client { self.request.upgrade(); self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::CONNECTION, "upgrade"); - self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); + self.request + .set_header(header::SEC_WEBSOCKET_VERSION, "13"); self.request.with_connector(self.conn.clone()); if let Some(protocols) = self.protocols.take() { @@ -392,7 +394,10 @@ impl Future for ClientHandshake { encoded, key ); - return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); + return Err(ClientError::InvalidChallengeResponse( + encoded, + key.clone(), + )); } } else { trace!("Missing SEC-WEBSOCKET-ACCEPT header"); @@ -411,9 +416,7 @@ impl Future for ClientHandshake { inner: Rc::clone(&inner), max_size: self.max_size, }, - ClientWriter { - inner, - }, + ClientWriter { inner }, ))) } } @@ -533,13 +536,23 @@ impl ClientWriter { /// Send ping frame #[inline] pub fn ping(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true)); + self.write(Frame::message( + Vec::from(message), + OpCode::Ping, + true, + true, + )); } /// Send pong frame #[inline] pub fn pong(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true)); + self.write(Frame::message( + Vec::from(message), + OpCode::Pong, + true, + true, + )); } /// Send close frame diff --git a/src/ws/context.rs b/src/ws/context.rs index f76532cc..b5a2456c 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -2,7 +2,6 @@ use futures::sync::oneshot::Sender; use futures::unsync::oneshot; use futures::{Async, Poll}; use smallvec::SmallVec; -use std::mem; use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::fut::ActorFuture; @@ -156,13 +155,23 @@ where /// Send ping frame #[inline] pub fn ping(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Ping, true, false)); + self.write(Frame::message( + Vec::from(message), + OpCode::Ping, + true, + false, + )); } /// Send pong frame #[inline] pub fn pong(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Pong, true, false)); + self.write(Frame::message( + Vec::from(message), + OpCode::Pong, + true, + false, + )); } /// Send close frame @@ -190,7 +199,9 @@ where if self.stream.is_none() { self.stream = Some(SmallVec::new()); } - self.stream.as_mut().map(|s| s.push(frame)); + if let Some(s) = self.stream.as_mut() { + s.push(frame) + } self.inner.modify(); } @@ -214,8 +225,7 @@ where } fn poll(&mut self) -> Poll>, Error> { - let ctx: &mut WebsocketContext = - unsafe { mem::transmute(self as &mut WebsocketContext) }; + let ctx: &mut WebsocketContext = unsafe { &mut *(self as *mut _) }; if self.inner.alive() && self.inner.poll(ctx).is_err() { return Err(ErrorInternalServerError("error")); diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 5c4d5e4a..a5c02442 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,8 +1,9 @@ +#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] use byteorder::{BigEndian, ByteOrder, NetworkEndian}; use bytes::{BufMut, Bytes, BytesMut}; use futures::{Async, Poll, Stream}; use rand; -use std::{fmt, mem, ptr}; +use std::{fmt, ptr}; use body::Binary; use error::PayloadError; @@ -122,7 +123,9 @@ impl Frame { None }; - Ok(Async::Ready(Some((idx, finished, opcode, length, mask)))) + Ok(Async::Ready(Some(( + idx, finished, opcode, length, mask, + )))) } fn read_chunk_md( @@ -257,10 +260,9 @@ impl Frame { // unmask if let Some(mask) = mask { - #[allow(mutable_transmutes)] let p: &mut [u8] = unsafe { let ptr: &[u8] = &data; - mem::transmute(ptr) + &mut *(ptr as *const _ as *mut _) }; apply_mask(p, mask); } diff --git a/src/ws/mask.rs b/src/ws/mask.rs index 13246d97..18a90675 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,4 +1,5 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) +#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] use std::cmp::min; use std::mem::uninitialized; use std::ptr::copy_nonoverlapping; @@ -28,11 +29,7 @@ fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { // Possible first unaligned block. let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3); let mask_u32 = if head > 0 { - let n = if head > 4 { - head - 4 - } else { - head - }; + let n = if head > 4 { head - 4 } else { head }; let mask_u32 = if n > 0 { unsafe { diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 42cd3589..402f2bdf 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -133,24 +133,24 @@ pub enum HandshakeError { impl ResponseError for HandshakeError { fn error_response(&self) -> HttpResponse { match *self { - HandshakeError::GetMethodRequired => { - HttpResponse::MethodNotAllowed().header(header::ALLOW, "GET").finish() - } + HandshakeError::GetMethodRequired => HttpResponse::MethodNotAllowed() + .header(header::ALLOW, "GET") + .finish(), HandshakeError::NoWebsocketUpgrade => HttpResponse::BadRequest() .reason("No WebSocket UPGRADE header found") .finish(), - HandshakeError::NoConnectionUpgrade => { - HttpResponse::BadRequest().reason("No CONNECTION upgrade").finish() - } + HandshakeError::NoConnectionUpgrade => HttpResponse::BadRequest() + .reason("No CONNECTION upgrade") + .finish(), HandshakeError::NoVersionHeader => HttpResponse::BadRequest() .reason("Websocket version header is required") .finish(), - HandshakeError::UnsupportedVersion => { - HttpResponse::BadRequest().reason("Unsupported version").finish() - } - HandshakeError::BadWebsocketKey => { - HttpResponse::BadRequest().reason("Handshake error").finish() - } + HandshakeError::UnsupportedVersion => HttpResponse::BadRequest() + .reason("Unsupported version") + .finish(), + HandshakeError::BadWebsocketKey => HttpResponse::BadRequest() + .reason("Handshake error") + .finish(), } } } @@ -216,7 +216,9 @@ pub fn handshake( } // check supported version - if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) { + if !req.headers() + .contains_key(header::SEC_WEBSOCKET_VERSION) + { return Err(HandshakeError::NoVersionHeader); } let supported_ver = { @@ -353,7 +355,10 @@ mod tests { HeaderMap::new(), None, ); - assert_eq!(HandshakeError::GetMethodRequired, handshake(&req).err().unwrap()); + assert_eq!( + HandshakeError::GetMethodRequired, + handshake(&req).err().unwrap() + ); let req = HttpRequest::new( Method::GET, @@ -362,10 +367,16 @@ mod tests { HeaderMap::new(), None, ); - assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); + assert_eq!( + HandshakeError::NoWebsocketUpgrade, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, header::HeaderValue::from_static("test")); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("test"), + ); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -373,10 +384,16 @@ mod tests { headers, None, ); - assert_eq!(HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap()); + assert_eq!( + HandshakeError::NoWebsocketUpgrade, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -384,11 +401,20 @@ mod tests { headers, None, ); - assert_eq!(HandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap()); + assert_eq!( + HandshakeError::NoConnectionUpgrade, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); - headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), @@ -396,11 +422,20 @@ mod tests { headers, None, ); - assert_eq!(HandshakeError::NoVersionHeader, handshake(&req).err().unwrap()); + assert_eq!( + HandshakeError::NoVersionHeader, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); - headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ); headers.insert( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), @@ -412,11 +447,20 @@ mod tests { headers, None, ); - assert_eq!(HandshakeError::UnsupportedVersion, handshake(&req).err().unwrap()); + assert_eq!( + HandshakeError::UnsupportedVersion, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); - headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ); headers.insert( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), @@ -428,17 +472,28 @@ mod tests { headers, None, ); - assert_eq!(HandshakeError::BadWebsocketKey, handshake(&req).err().unwrap()); + assert_eq!( + HandshakeError::BadWebsocketKey, + handshake(&req).err().unwrap() + ); let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, header::HeaderValue::from_static("websocket")); - headers.insert(header::CONNECTION, header::HeaderValue::from_static("upgrade")); + headers.insert( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ); + headers.insert( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ); headers.insert( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), ); - headers - .insert(header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13")); + headers.insert( + header::SEC_WEBSOCKET_KEY, + header::HeaderValue::from_static("13"), + ); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), diff --git a/tests/test_client.rs b/tests/test_client.rs index fc6007b0..3c4c8586 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -72,7 +72,10 @@ fn test_with_query_parameter() { }) }); - let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/?qp=5").as_str()) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -121,8 +124,10 @@ fn test_client_gzip_encoding() { }); // client request - let request = - srv.post().content_encoding(http::ContentEncoding::Gzip).body(STR).unwrap(); + let request = srv.post() + .content_encoding(http::ContentEncoding::Gzip) + .body(STR) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -162,7 +167,10 @@ fn test_client_gzip_encoding_large() { #[test] fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng().gen_ascii_chars().take(100_000).collect::(); + let data = rand::thread_rng() + .gen_ascii_chars() + .take(100_000) + .collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -220,7 +228,10 @@ fn test_client_brotli_encoding() { #[cfg(feature = "brotli")] #[test] fn test_client_brotli_encoding_large_random() { - let data = rand::thread_rng().gen_ascii_chars().take(70_000).collect::(); + let data = rand::thread_rng() + .gen_ascii_chars() + .take(70_000) + .collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -264,8 +275,10 @@ fn test_client_deflate_encoding() { }); // client request - let request = - srv.post().content_encoding(http::ContentEncoding::Deflate).body(STR).unwrap(); + let request = srv.post() + .content_encoding(http::ContentEncoding::Deflate) + .body(STR) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -277,7 +290,10 @@ fn test_client_deflate_encoding() { #[cfg(feature = "brotli")] #[test] fn test_client_deflate_encoding_large_random() { - let data = rand::thread_rng().gen_ascii_chars().take(70_000).collect::(); + let data = rand::thread_rng() + .gen_ascii_chars() + .take(70_000) + .collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -322,7 +338,9 @@ fn test_client_streaming_explicit() { let body = once(Ok(Bytes::from_static(STR.as_ref()))); - let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); + let request = srv.get() + .body(Body::Streaming(Box::new(body))) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -395,8 +413,11 @@ fn test_client_cookie_handling() { }) }); - let request = - srv.get().cookie(cookie1.clone()).cookie(cookie2.clone()).finish().unwrap(); + let request = srv.get() + .cookie(cookie1.clone()) + .cookie(cookie2.clone()) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); let c1 = response.cookie("cookie1").expect("Missing cookie1"); diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 65d72724..7a9abe97 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -26,7 +26,10 @@ fn test_path_extractor() { }); // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -44,7 +47,10 @@ fn test_query_extractor() { }); // client request - let request = srv.get().uri(srv.url("/index.html?username=test")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/index.html?username=test")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -53,7 +59,10 @@ fn test_query_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); // client request - let request = srv.get().uri(srv.url("/index.html")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -69,8 +78,10 @@ fn test_path_and_query_extractor() { }); // client request - let request = - srv.get().uri(srv.url("/test1/index.html?username=test2")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test1/index.html?username=test2")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -79,7 +90,10 @@ fn test_path_and_query_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); // client request - let request = srv.get().uri(srv.url("/test1/index.html")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test1/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -88,15 +102,18 @@ fn test_path_and_query_extractor() { fn test_path_and_query_extractor2() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with3(|_: HttpRequest, p: Path, q: Query| { - format!("Welcome {} - {}!", p.username, q.username) - }) + r.route() + .with3(|_: HttpRequest, p: Path, q: Query| { + format!("Welcome {} - {}!", p.username, q.username) + }) }); }); // client request - let request = - srv.get().uri(srv.url("/test1/index.html?username=test2")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test1/index.html?username=test2")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -105,7 +122,10 @@ fn test_path_and_query_extractor2() { assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); // client request - let request = srv.get().uri(srv.url("/test1/index.html")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test1/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -117,7 +137,10 @@ fn test_non_ascii_route() { }); // client request - let request = srv.get().uri(srv.url("/中文/index.html")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/中文/index.html")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -135,12 +158,17 @@ fn test_unsafe_path_route() { }); // client request - let request = - srv.get().uri(srv.url("/test/http%3A%2F%2Fexample.com")).finish().unwrap(); + let request = srv.get() + .uri(srv.url("/test/http%3A%2F%2Fexample.com")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"success: http:%2F%2Fexample.com")); + assert_eq!( + bytes, + Bytes::from_static(b"success: http:%2F%2Fexample.com") + ); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 7bb8a6cd..19b6c919 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -131,9 +131,7 @@ fn test_shutdown() { .finish() .unwrap(); let response = sys.run_until_complete(req.send()).unwrap(); - srv_addr.do_send(server::StopServer { - graceful: true, - }); + srv_addr.do_send(server::StopServer { graceful: true }); assert!(response.status().is_success()); } @@ -206,7 +204,9 @@ fn test_body() { fn test_body_gzip() { let mut srv = test::TestServer::new(|app| { app.handler(|_| { - HttpResponse::Ok().content_encoding(http::ContentEncoding::Gzip).body(STR) + HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Gzip) + .body(STR) }) }); @@ -254,7 +254,10 @@ fn test_body_gzip_large() { #[test] fn test_body_gzip_large_random() { - let data = rand::thread_rng().gen_ascii_chars().take(70_000).collect::(); + let data = rand::thread_rng() + .gen_ascii_chars() + .take(70_000) + .collect::(); let srv_data = Arc::new(data.clone()); let mut srv = test::TestServer::new(move |app| { @@ -335,7 +338,11 @@ fn test_body_br_streaming() { #[test] fn test_head_empty() { let mut srv = test::TestServer::new(|app| { - app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish()) + app.handler(|_| { + HttpResponse::Ok() + .content_length(STR.len() as u64) + .finish() + }) }); let request = srv.head().finish().unwrap(); @@ -343,7 +350,10 @@ fn test_head_empty() { assert!(response.status().is_success()); { - let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } @@ -368,7 +378,10 @@ fn test_head_binary() { assert!(response.status().is_success()); { - let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } @@ -392,7 +405,10 @@ fn test_head_binary2() { assert!(response.status().is_success()); { - let len = response.headers().get(http::header::CONTENT_LENGTH).unwrap(); + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } } @@ -448,7 +464,9 @@ fn test_body_chunked_explicit() { fn test_body_deflate() { let mut srv = test::TestServer::new(|app| { app.handler(|_| { - HttpResponse::Ok().content_encoding(http::ContentEncoding::Deflate).body(STR) + HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Deflate) + .body(STR) }) }); @@ -472,7 +490,9 @@ fn test_body_deflate() { fn test_body_brotli() { let mut srv = test::TestServer::new(|app| { app.handler(|_| { - HttpResponse::Ok().content_encoding(http::ContentEncoding::Br).body(STR) + HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Br) + .body(STR) }) }); @@ -556,7 +576,10 @@ fn test_gzip_encoding_large() { #[test] fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng().gen_ascii_chars().take(60_000).collect::(); + let data = rand::thread_rng() + .gen_ascii_chars() + .take(60_000) + .collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -607,8 +630,10 @@ fn test_reading_deflate_encoding() { let enc = e.finish().unwrap(); // client request - let request = - srv.post().header(http::header::CONTENT_ENCODING, "deflate").body(enc).unwrap(); + let request = srv.post() + .header(http::header::CONTENT_ENCODING, "deflate") + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -637,8 +662,10 @@ fn test_reading_deflate_encoding_large() { let enc = e.finish().unwrap(); // client request - let request = - srv.post().header(http::header::CONTENT_ENCODING, "deflate").body(enc).unwrap(); + let request = srv.post() + .header(http::header::CONTENT_ENCODING, "deflate") + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -649,7 +676,10 @@ fn test_reading_deflate_encoding_large() { #[test] fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng().gen_ascii_chars().take(160_000).collect::(); + let data = rand::thread_rng() + .gen_ascii_chars() + .take(160_000) + .collect::(); let mut srv = test::TestServer::new(|app| { app.handler(|req: HttpRequest| { @@ -668,8 +698,10 @@ fn test_reading_deflate_encoding_large_random() { let enc = e.finish().unwrap(); // client request - let request = - srv.post().header(http::header::CONTENT_ENCODING, "deflate").body(enc).unwrap(); + let request = srv.post() + .header(http::header::CONTENT_ENCODING, "deflate") + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -699,8 +731,10 @@ fn test_brotli_encoding() { let enc = e.finish().unwrap(); // client request - let request = - srv.post().header(http::header::CONTENT_ENCODING, "br").body(enc).unwrap(); + let request = srv.post() + .header(http::header::CONTENT_ENCODING, "br") + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -730,8 +764,10 @@ fn test_brotli_encoding_large() { let enc = e.finish().unwrap(); // client request - let request = - srv.post().header(http::header::CONTENT_ENCODING, "br").body(enc).unwrap(); + let request = srv.post() + .header(http::header::CONTENT_ENCODING, "br") + .body(enc) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -749,29 +785,30 @@ fn test_h2() { let handle = core.handle(); let tcp = TcpStream::connect(&addr, &handle); - let tcp = tcp.then(|res| h2client::handshake(res.unwrap())).then(move |res| { - let (mut client, h2) = res.unwrap(); + let tcp = tcp.then(|res| h2client::handshake(res.unwrap())) + .then(move |res| { + let (mut client, h2) = res.unwrap(); - let request = Request::builder() - .uri(format!("https://{}/", addr).as_str()) - .body(()) - .unwrap(); - let (response, _) = client.send_request(request, false).unwrap(); + let request = Request::builder() + .uri(format!("https://{}/", addr).as_str()) + .body(()) + .unwrap(); + let (response, _) = client.send_request(request, false).unwrap(); - // Spawn a task to run the conn... - handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); + // Spawn a task to run the conn... + handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); - response.and_then(|response| { - assert_eq!(response.status(), http::StatusCode::OK); + response.and_then(|response| { + assert_eq!(response.status(), http::StatusCode::OK); - let (_, body) = response.into_parts(); + let (_, body) = response.into_parts(); - body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { - b.extend(c); - Ok(b) + body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { + b.extend(c); + Ok(b) + }) }) - }) - }); + }); let _res = core.run(tcp); // assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref())); } @@ -795,20 +832,28 @@ struct MiddlewareTest { impl middleware::Middleware for MiddlewareTest { fn start(&self, _: &mut HttpRequest) -> Result { - self.start.store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); + self.start.store( + self.start.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); Ok(middleware::Started::Done) } fn response( &self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { - self.response - .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); + self.response.store( + self.response.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); Ok(middleware::Response::Done(resp)) } fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish.store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); + self.finish.store( + self.finish.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); middleware::Finished::Done } } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 563f8f12..9dbc11b0 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -44,7 +44,12 @@ fn test_simple() { writer.binary(b"text".as_ref()); let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Binary(Bytes::from_static(b"text").into()))); + assert_eq!( + item, + Some(ws::Message::Binary( + Bytes::from_static(b"text").into() + )) + ); writer.ping("ping"); let (item, reader) = srv.execute(reader.into_future()).unwrap(); @@ -52,7 +57,10 @@ fn test_simple() { writer.close(Some(ws::CloseCode::Normal.into())); let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(Some(ws::CloseCode::Normal.into())))); + assert_eq!( + item, + Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + ); } #[test] @@ -79,7 +87,10 @@ fn test_close_description() { #[test] fn test_large_text() { - let data = rand::thread_rng().gen_ascii_chars().take(65_536).collect::(); + let data = rand::thread_rng() + .gen_ascii_chars() + .take(65_536) + .collect::(); let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (mut reader, mut writer) = srv.ws().unwrap(); @@ -94,7 +105,10 @@ fn test_large_text() { #[test] fn test_large_bin() { - let data = rand::thread_rng().gen_ascii_chars().take(65_536).collect::(); + let data = rand::thread_rng() + .gen_ascii_chars() + .take(65_536) + .collect::(); let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); let (mut reader, mut writer) = srv.ws().unwrap(); @@ -103,7 +117,10 @@ fn test_large_bin() { writer.binary(data.clone()); let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; - assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone())))); + assert_eq!( + item, + Some(ws::Message::Binary(Binary::from(data.clone()))) + ); } } @@ -207,20 +224,26 @@ fn test_ws_server_ssl() { // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_private_key_file("tests/key.pem", SslFiletype::PEM).unwrap(); - builder.set_certificate_chain_file("tests/cert.pem").unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); - let mut srv = test::TestServer::build().ssl(builder.build()).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); + let mut srv = test::TestServer::build() + .ssl(builder.build()) + .start(|app| { + app.handler(|req| { + ws::start( + req, + Ws2 { + count: 0, + bin: false, + }, + ) + }) + }); let (mut reader, _writer) = srv.ws().unwrap(); let data = Some(ws::Message::Text("0".repeat(65_536))); diff --git a/tools/wsload/src/wsclient.rs b/tools/wsload/src/wsclient.rs index 0fc36ac9..d8d7b660 100644 --- a/tools/wsload/src/wsclient.rs +++ b/tools/wsload/src/wsclient.rs @@ -61,8 +61,12 @@ fn main() { let sample_rate = parse_u64_default(matches.value_of("sample-rate"), 1) as usize; let perf_counters = Arc::new(PerfCounters::new()); - let payload = - Arc::new(thread_rng().gen_ascii_chars().take(payload_size).collect::()); + let payload = Arc::new( + thread_rng() + .gen_ascii_chars() + .take(payload_size) + .collect::(), + ); let sys = actix::System::new("ws-client"); @@ -78,40 +82,43 @@ fn main() { let perf = perf_counters.clone(); let addr = Arbiter::new(format!("test {}", t)); - addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> { - for _ in 0..concurrency { - let pl2 = pl.clone(); - let perf2 = perf.clone(); - let ws2 = ws.clone(); + addr.do_send(actix::msgs::Execute::new( + move || -> Result<(), ()> { + for _ in 0..concurrency { + let pl2 = pl.clone(); + let perf2 = perf.clone(); + let ws2 = ws.clone(); - Arbiter::handle().spawn( - ws::Client::new(&ws) - .write_buffer_capacity(0) - .connect() - .map_err(|e| { - println!("Error: {}", e); - //Arbiter::system().do_send(actix::msgs::SystemExit(0)); - () - }) - .map(move |(reader, writer)| { - let addr: Addr = ChatClient::create(move |ctx| { - ChatClient::add_stream(reader, ctx); - ChatClient { - url: ws2, - conn: writer, - payload: pl2, - bin: bin, - ts: time::precise_time_ns(), - perf_counters: perf2, - sent: 0, - max_payload_size: max_payload_size, - } - }); - }), - ); - } - Ok(()) - })); + Arbiter::handle().spawn( + ws::Client::new(&ws) + .write_buffer_capacity(0) + .connect() + .map_err(|e| { + println!("Error: {}", e); + //Arbiter::system().do_send(actix::msgs::SystemExit(0)); + () + }) + .map(move |(reader, writer)| { + let addr: Addr = + ChatClient::create(move |ctx| { + ChatClient::add_stream(reader, ctx); + ChatClient { + url: ws2, + conn: writer, + payload: pl2, + bin: bin, + ts: time::precise_time_ns(), + perf_counters: perf2, + sent: 0, + max_payload_size: max_payload_size, + } + }); + }), + ); + } + Ok(()) + }, + )); } let res = sys.run(); @@ -119,7 +126,10 @@ fn main() { fn parse_u64_default(input: Option<&str>, default: u64) -> u64 { input - .map(|v| v.parse().expect(&format!("not a valid number: {}", v))) + .map(|v| { + v.parse() + .expect(&format!("not a valid number: {}", v)) + }) .unwrap_or(default) } @@ -139,13 +149,15 @@ impl Actor for Perf { impl Perf { fn sample_rate(&self, ctx: &mut Context) { - ctx.run_later(Duration::new(self.sample_rate_secs as u64, 0), |act, ctx| { - let req_count = act.counters.pull_request_count(); - if req_count != 0 { - let conns = act.counters.pull_connections_count(); - let latency = act.counters.pull_latency_ns(); - let latency_max = act.counters.pull_latency_max_ns(); - println!( + ctx.run_later( + Duration::new(self.sample_rate_secs as u64, 0), + |act, ctx| { + let req_count = act.counters.pull_request_count(); + if req_count != 0 { + let conns = act.counters.pull_connections_count(); + let latency = act.counters.pull_latency_ns(); + let latency_max = act.counters.pull_latency_max_ns(); + println!( "rate: {}, conns: {}, throughput: {:?} kb, latency: {}, latency max: {}", req_count / act.sample_rate_secs, conns / act.sample_rate_secs, @@ -154,10 +166,11 @@ impl Perf { time::Duration::nanoseconds((latency / req_count as u64) as i64), time::Duration::nanoseconds(latency_max as i64) ); - } + } - act.sample_rate(ctx); - }); + act.sample_rate(ctx); + }, + ); } } @@ -301,7 +314,8 @@ impl PerfCounters { loop { let current = self.lat_max.load(Ordering::SeqCst); if current >= nanos - || self.lat_max.compare_and_swap(current, nanos, Ordering::SeqCst) + || self.lat_max + .compare_and_swap(current, nanos, Ordering::SeqCst) == current { break; From 03ded62337e5a8e948cf0f57f48a9f917a4d0a57 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 14:13:46 -0700 Subject: [PATCH 0150/1635] bump minimum supported rustc version because of minor version change of parking_lot crate --- .appveyor.yml | 8 ++++---- .travis.yml | 2 +- README.md | 2 +- src/lib.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 4f26315f..e06f90ca 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,13 +4,13 @@ environment: matrix: # Stable channel - TARGET: i686-pc-windows-gnu - CHANNEL: 1.21.0 + CHANNEL: 1.24.0 - TARGET: i686-pc-windows-msvc - CHANNEL: 1.21.0 + CHANNEL: 1.24.0 - TARGET: x86_64-pc-windows-gnu - CHANNEL: 1.21.0 + CHANNEL: 1.24.0 - TARGET: x86_64-pc-windows-msvc - CHANNEL: 1.21.0 + CHANNEL: 1.24.0 # Stable channel - TARGET: i686-pc-windows-gnu CHANNEL: stable diff --git a/.travis.yml b/.travis.yml index 2dedae4e..19fe1b26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,11 @@ cache: matrix: include: + - rust: 1.24.0 - rust: stable - rust: beta - rust: nightly allow_failures: - - rust: 1.21.0 - rust: nightly env: diff --git a/README.md b/README.md index 34790cd0..05757da4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.22 or later +* Minimum supported Rust version: 1.24 or later ## Example diff --git a/src/lib.rs b/src/lib.rs index 379b5ac4..431c60c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ //! * SSL support with OpenSSL or `native-tls` //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) //! * Built on top of [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.22 or later +//! * Supported Rust version: 1.24 or later #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error From aa757a5be8a88eccc88ad5f0785ee35ec7d9ecac Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 14:21:50 -0700 Subject: [PATCH 0151/1635] Allow to access Error's backtrace object --- CHANGES.md | 2 ++ src/error.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 9d3e0a10..a771ce37 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Add Content-Disposition to NamedFile #204 +* Allow to access Error's backtrace object + ## 0.5.6 (2018-04-24) diff --git a/src/error.rs b/src/error.rs index 3004adef..37dc3d89 100644 --- a/src/error.rs +++ b/src/error.rs @@ -45,6 +45,16 @@ impl Error { pub fn cause(&self) -> &ResponseError { self.cause.as_ref() } + + /// Returns a reference to the Backtrace carried by this error, if it + /// carries one. + pub fn backtrace(&self) -> Option<&Backtrace> { + if let Some(bt) = self.cause.backtrace() { + Some(bt) + } else { + self.backtrace.as_ref() + } + } } /// Error that can be converted to `HttpResponse` @@ -793,6 +803,13 @@ mod tests { assert_eq!(format!("{}", e.cause().unwrap()), desc); } + #[test] + fn test_backtrace() { + let orig = ErrorBadRequest("err"); + let e: Error = orig.into(); + assert!(e.backtrace().is_some()); + } + #[test] fn test_error_cause() { let orig = io::Error::new(io::ErrorKind::Other, "other"); From 368730f5f10b2cc5ad29793ea765bbb0cef8982d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 19:35:50 -0700 Subject: [PATCH 0152/1635] Add route scopes #202 --- CHANGES.md | 2 ++ src/application.rs | 50 ++++++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 2 ++ src/route.rs | 3 ++- src/server/h1.rs | 6 +----- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a771ce37..8d3861a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ## 0.6.0 (...) +* Add route scopes #202 + * Websocket CloseCode Empty/Status is ambiguous #193 * Add Content-Disposition to NamedFile #204 diff --git a/src/application.rs b/src/application.rs index 4ee5157d..7de2a258 100644 --- a/src/application.rs +++ b/src/application.rs @@ -2,8 +2,7 @@ use std::cell::UnsafeCell; use std::collections::HashMap; use std::rc::Rc; -use handler::Reply; -use handler::{FromRequest, Handler, Responder, RouteHandler, WrapHandler}; +use handler::{FromRequest, Handler, Reply, Responder, RouteHandler, WrapHandler}; use header::ContentEncoding; use http::Method; use httprequest::HttpRequest; @@ -11,6 +10,7 @@ use middleware::Middleware; use pipeline::{HandlerType, Pipeline, PipelineHandler}; use resource::ResourceHandler; use router::{Resource, Router}; +use scope::Scope; use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings}; #[deprecated(since = "0.5.0", note = "please use `actix_web::App` instead")] @@ -76,9 +76,9 @@ impl HttpApplication { &*(&req.path()[inner.prefix + prefix.len()..] as *const _) }; if path.is_empty() { - req.match_info_mut().add("tail", ""); + req.match_info_mut().add("tail", "/"); } else { - req.match_info_mut().add("tail", path.split_at(1).1); + req.match_info_mut().add("tail", path); } return HandlerType::Handler(idx); } @@ -300,6 +300,48 @@ where self } + /// Configure scope for common root path. + /// + /// Scopes collect multiple paths under a common path prefix. + /// Scope path can not contain variable path segments as resources. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpRequest, HttpResponse}; + /// + /// fn main() { + /// let app = App::new() + /// .scope("/app", |scope| { + /// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) + /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) + /// }); + /// } + /// ``` + /// + /// In the above example three routes get registered: + /// * /app/path1 - reponds to all http method + /// * /app/path2 - `GET` requests + /// * /app/path3 - `HEAD` requests + /// + pub fn scope(mut self, path: &str, f: F) -> App + where + F: FnOnce(Scope) -> Scope, + { + { + let scope = Box::new(f(Scope::new())); + + let mut path = path.trim().trim_right_matches('/').to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/') + } + let parts = self.parts.as_mut().expect("Use after finish"); + + parts.handlers.push((path, scope)); + } + self + } + /// Configure resource for a specific path. /// /// Resources may have variable path segments. For example, a diff --git a/src/lib.rs b/src/lib.rs index 431c60c1..04371193 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,6 +149,7 @@ mod pipeline; mod resource; mod route; mod router; +mod scope; mod uri; mod with; @@ -171,6 +172,7 @@ pub use httpmessage::HttpMessage; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; +pub use scope::Scope; #[doc(hidden)] pub mod httpcodes; diff --git a/src/route.rs b/src/route.rs index 681ee1cb..27f0133b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,8 +1,9 @@ -use futures::{Async, Future, Poll}; use std::cell::UnsafeCell; use std::marker::PhantomData; use std::rc::Rc; +use futures::{Async, Future, Poll}; + use error::Error; use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyItem, Responder, RouteHandler, WrapHandler}; diff --git a/src/server/h1.rs b/src/server/h1.rs index f420ecb7..5f70bd0d 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -388,11 +388,7 @@ where } } Err(e) => { - return if e.kind() == io::ErrorKind::WouldBlock { - false - } else { - true - }; + return e.kind() == io::ErrorKind::WouldBlock; } } } From 4a29f12876004b1e229aee83c3256de149bb5e1f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 19:39:28 -0700 Subject: [PATCH 0153/1635] update doc string; missing file --- src/scope.rs | 601 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 601 insertions(+) create mode 100644 src/scope.rs diff --git a/src/scope.rs b/src/scope.rs new file mode 100644 index 00000000..eae5d55a --- /dev/null +++ b/src/scope.rs @@ -0,0 +1,601 @@ +use std::cell::UnsafeCell; +use std::marker::PhantomData; +use std::rc::Rc; + +use futures::{Async, Future, Poll}; + +use error::Error; +use handler::{FromRequest, Reply, ReplyItem, Responder, RouteHandler}; +use http::Method; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middleware::{Finished as MiddlewareFinished, Middleware, + Response as MiddlewareResponse, Started as MiddlewareStarted}; +use resource::ResourceHandler; +use router::Resource; + +type ScopeResources = Rc>>)>>; + +/// Resources scope +/// +/// Scope is a set of resources with common root path. +/// Scopes collect multiple paths under a common path prefix. +/// Scope path can not contain variable path segments as resources. +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::{http, App, HttpRequest, HttpResponse}; +/// +/// fn main() { +/// let app = App::new() +/// .scope("/app", |scope| { +/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) +/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) +/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) +/// }); +/// } +/// ``` +/// +/// In the above example three routes get registered: +/// * /app/path1 - reponds to all http method +/// * /app/path2 - `GET` requests +/// * /app/path3 - `HEAD` requests +/// +pub struct Scope { + middlewares: Rc>>>, + default: Rc>>, + resources: ScopeResources, +} + +impl Default for Scope { + fn default() -> Scope { + Scope::new() + } +} + +impl Scope { + pub fn new() -> Scope { + Scope { + resources: Rc::new(Vec::new()), + middlewares: Rc::new(Vec::new()), + default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), + } + } + + /// Configure route for a specific path. + /// + /// This is a simplified version of the `Scope::resource()` method. + /// Handler functions need to accept one request extractor + /// argument. + /// + /// This method could be called multiple times, in that case + /// multiple routes would be registered for same resource path. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; + /// + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .scope("/app", |scope| { + /// scope.route("/test1", http::Method::GET, index) + /// .route("/test2", http::Method::POST, + /// |_: HttpRequest| HttpResponse::MethodNotAllowed()) + /// }); + /// } + /// ``` + pub fn route(mut self, path: &str, method: Method, f: F) -> Scope + where + F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, + { + // get resource handler + let slf: &Scope = unsafe { &*(&self as *const _) }; + for &(ref pattern, ref resource) in slf.resources.iter() { + if pattern.pattern() == path { + let resource = unsafe { &mut *resource.get() }; + resource.method(method).with(f); + return self; + } + } + + let mut handler = ResourceHandler::default(); + handler.method(method).with(f); + let pattern = Resource::new(handler.get_name(), path); + Rc::get_mut(&mut self.resources) + .expect("Can not use after configuration") + .push((pattern, Rc::new(UnsafeCell::new(handler)))); + + self + } + + /// Configure resource for a specific path. + /// + /// This method is similar to an `App::resource()` method. + /// Resources may have variable path segments. Resource path uses scope + /// path as a path prefix. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// + /// fn main() { + /// let app = App::new() + /// .scope("/api", |scope| { + /// scope.resource("/users/{userid}/{friend}", |r| { + /// r.get().f(|_| HttpResponse::Ok()); + /// r.head().f(|_| HttpResponse::MethodNotAllowed()); + /// r.route() + /// .filter(pred::Any(pred::Get()).or(pred::Put())) + /// .filter(pred::Header("Content-Type", "text/plain")) + /// .f(|_| HttpResponse::Ok()) + /// }) + /// }); + /// } + /// ``` + pub fn resource(mut self, path: &str, f: F) -> Scope + where + F: FnOnce(&mut ResourceHandler) -> R + 'static, + { + // add resource handler + let mut handler = ResourceHandler::default(); + f(&mut handler); + + let pattern = Resource::new(handler.get_name(), path); + Rc::get_mut(&mut self.resources) + .expect("Can not use after configuration") + .push((pattern, Rc::new(UnsafeCell::new(handler)))); + + self + } + + /// Default resource to be used if no matching route could be found. + pub fn default_resource(self, f: F) -> Scope + where + F: FnOnce(&mut ResourceHandler) -> R + 'static, + { + let default = unsafe { &mut *self.default.as_ref().get() }; + f(default); + self + } + + /// Register a scope middleware + /// + /// This is similar to `App's` middlewares, but + /// middlewares get invoked on scope level. + /// + /// *Note* `Middleware::finish()` is fired right after response get + /// prepared. It does not wait until body get sent to peer. + pub fn middleware>(mut self, mw: M) -> Scope { + Rc::get_mut(&mut self.middlewares) + .expect("Can not use after configuration") + .push(Box::new(mw)); + self + } +} + +impl RouteHandler for Scope { + fn handle(&mut self, mut req: HttpRequest) -> Reply { + let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; + let path = if path == "" { "/" } else { path }; + + for (pattern, resource) in self.resources.iter() { + if pattern.match_with_params(path, req.match_info_mut()) { + let default = unsafe { &mut *self.default.as_ref().get() }; + + if self.middlewares.is_empty() { + let resource = unsafe { &mut *resource.get() }; + return resource.handle(req, Some(default)); + } else { + return Reply::async(Compose::new( + req, + Rc::clone(&self.middlewares), + Rc::clone(&resource), + Some(Rc::clone(&self.default)), + )); + } + } + } + + let default = unsafe { &mut *self.default.as_ref().get() }; + if self.middlewares.is_empty() { + default.handle(req, None) + } else { + Reply::async(Compose::new( + req, + Rc::clone(&self.middlewares), + Rc::clone(&self.default), + None, + )) + } + } +} + +/// Compose resource level middlewares with route handler. +struct Compose { + info: ComposeInfo, + state: ComposeState, +} + +struct ComposeInfo { + count: usize, + req: HttpRequest, + mws: Rc>>>, + default: Option>>>, + resource: Rc>>, +} + +enum ComposeState { + Starting(StartMiddlewares), + Handler(WaitingResponse), + RunMiddlewares(RunMiddlewares), + Finishing(FinishingMiddlewares), + Completed(Response), +} + +impl ComposeState { + fn poll(&mut self, info: &mut ComposeInfo) -> Option> { + match *self { + ComposeState::Starting(ref mut state) => state.poll(info), + ComposeState::Handler(ref mut state) => state.poll(info), + ComposeState::RunMiddlewares(ref mut state) => state.poll(info), + ComposeState::Finishing(ref mut state) => state.poll(info), + ComposeState::Completed(_) => None, + } + } +} + +impl Compose { + fn new( + req: HttpRequest, mws: Rc>>>, + resource: Rc>>, + default: Option>>>, + ) -> Self { + let mut info = ComposeInfo { + count: 0, + req, + mws, + resource, + default, + }; + let state = StartMiddlewares::init(&mut info); + + Compose { state, info } + } +} + +impl Future for Compose { + type Item = HttpResponse; + type Error = Error; + + fn poll(&mut self) -> Poll { + loop { + if let ComposeState::Completed(ref mut resp) = self.state { + let resp = resp.resp.take().unwrap(); + return Ok(Async::Ready(resp)); + } + if let Some(state) = self.state.poll(&mut self.info) { + self.state = state; + } else { + return Ok(Async::NotReady); + } + } + } +} + +/// Middlewares start executor +struct StartMiddlewares { + fut: Option, + _s: PhantomData, +} + +type Fut = Box, Error = Error>>; + +impl StartMiddlewares { + fn init(info: &mut ComposeInfo) -> ComposeState { + let len = info.mws.len(); + loop { + if info.count == len { + let resource = unsafe { &mut *info.resource.get() }; + let reply = if let Some(ref default) = info.default { + let d = unsafe { &mut *default.as_ref().get() }; + resource.handle(info.req.clone(), Some(d)) + } else { + resource.handle(info.req.clone(), None) + }; + return WaitingResponse::init(info, reply); + } else { + match info.mws[info.count].start(&mut info.req) { + Ok(MiddlewareStarted::Done) => info.count += 1, + Ok(MiddlewareStarted::Response(resp)) => { + return RunMiddlewares::init(info, resp) + } + Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() { + Ok(Async::NotReady) => { + return ComposeState::Starting(StartMiddlewares { + fut: Some(fut), + _s: PhantomData, + }) + } + Ok(Async::Ready(resp)) => { + if let Some(resp) = resp { + return RunMiddlewares::init(info, resp); + } + info.count += 1; + } + Err(err) => return Response::init(err.into()), + }, + Err(err) => return Response::init(err.into()), + } + } + } + } + + fn poll(&mut self, info: &mut ComposeInfo) -> Option> { + let len = info.mws.len(); + 'outer: loop { + match self.fut.as_mut().unwrap().poll() { + Ok(Async::NotReady) => return None, + Ok(Async::Ready(resp)) => { + info.count += 1; + if let Some(resp) = resp { + return Some(RunMiddlewares::init(info, resp)); + } + if info.count == len { + let resource = unsafe { &mut *info.resource.get() }; + let reply = if let Some(ref default) = info.default { + let d = unsafe { &mut *default.as_ref().get() }; + resource.handle(info.req.clone(), Some(d)) + } else { + resource.handle(info.req.clone(), None) + }; + return Some(WaitingResponse::init(info, reply)); + } else { + loop { + match info.mws[info.count].start(&mut info.req) { + Ok(MiddlewareStarted::Done) => info.count += 1, + Ok(MiddlewareStarted::Response(resp)) => { + return Some(RunMiddlewares::init(info, resp)); + } + Ok(MiddlewareStarted::Future(fut)) => { + self.fut = Some(fut); + continue 'outer; + } + Err(err) => return Some(Response::init(err.into())), + } + } + } + } + Err(err) => return Some(Response::init(err.into())), + } + } + } +} + +// waiting for response +struct WaitingResponse { + fut: Box>, + _s: PhantomData, +} + +impl WaitingResponse { + #[inline] + fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { + match reply.into() { + ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), + ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse { + fut, + _s: PhantomData, + }), + } + } + + fn poll(&mut self, info: &mut ComposeInfo) -> Option> { + match self.fut.poll() { + Ok(Async::NotReady) => None, + Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), + Err(err) => Some(RunMiddlewares::init(info, err.into())), + } + } +} + +/// Middlewares response executor +struct RunMiddlewares { + curr: usize, + fut: Option>>, + _s: PhantomData, +} + +impl RunMiddlewares { + fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { + let mut curr = 0; + let len = info.mws.len(); + + loop { + resp = match info.mws[curr].response(&mut info.req, resp) { + Err(err) => { + info.count = curr + 1; + return FinishingMiddlewares::init(info, err.into()); + } + Ok(MiddlewareResponse::Done(r)) => { + curr += 1; + if curr == len { + return FinishingMiddlewares::init(info, r); + } else { + r + } + } + Ok(MiddlewareResponse::Future(fut)) => { + return ComposeState::RunMiddlewares(RunMiddlewares { + curr, + fut: Some(fut), + _s: PhantomData, + }) + } + }; + } + } + + fn poll(&mut self, info: &mut ComposeInfo) -> Option> { + let len = info.mws.len(); + + loop { + // poll latest fut + let mut resp = match self.fut.as_mut().unwrap().poll() { + Ok(Async::NotReady) => return None, + Ok(Async::Ready(resp)) => { + self.curr += 1; + resp + } + Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), + }; + + loop { + if self.curr == len { + return Some(FinishingMiddlewares::init(info, resp)); + } else { + match info.mws[self.curr].response(&mut info.req, resp) { + Err(err) => { + return Some(FinishingMiddlewares::init(info, err.into())) + } + Ok(MiddlewareResponse::Done(r)) => { + self.curr += 1; + resp = r + } + Ok(MiddlewareResponse::Future(fut)) => { + self.fut = Some(fut); + break; + } + } + } + } + } + } +} + +/// Middlewares start executor +struct FinishingMiddlewares { + resp: Option, + fut: Option>>, + _s: PhantomData, +} + +impl FinishingMiddlewares { + fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { + if info.count == 0 { + Response::init(resp) + } else { + let mut state = FinishingMiddlewares { + resp: Some(resp), + fut: None, + _s: PhantomData, + }; + if let Some(st) = state.poll(info) { + st + } else { + ComposeState::Finishing(state) + } + } + } + + fn poll(&mut self, info: &mut ComposeInfo) -> Option> { + loop { + // poll latest fut + let not_ready = if let Some(ref mut fut) = self.fut { + match fut.poll() { + Ok(Async::NotReady) => true, + Ok(Async::Ready(())) => false, + Err(err) => { + error!("Middleware finish error: {}", err); + false + } + } + } else { + false + }; + if not_ready { + return None; + } + self.fut = None; + info.count -= 1; + + match info.mws[info.count as usize] + .finish(&mut info.req, self.resp.as_ref().unwrap()) + { + MiddlewareFinished::Done => { + if info.count == 0 { + return Some(Response::init(self.resp.take().unwrap())); + } + } + MiddlewareFinished::Future(fut) => { + self.fut = Some(fut); + } + } + } + } +} + +struct Response { + resp: Option, + _s: PhantomData, +} + +impl Response { + fn init(resp: HttpResponse) -> ComposeState { + ComposeState::Completed(Response { + resp: Some(resp), + _s: PhantomData, + }) + } +} + +#[cfg(test)] +mod tests { + use application::App; + use http::StatusCode; + use httpresponse::HttpResponse; + use test::TestRequest; + + #[test] + fn test_scope() { + let mut app = App::new() + .scope("/app", |scope| { + scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + }) + .finish(); + + let req = TestRequest::with_uri("/app/path1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + } + + #[test] + fn test_default_resource() { + let mut app = App::new() + .scope("/app", |scope| { + scope + .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + .default_resource(|r| r.f(|_| HttpResponse::BadRequest())) + }) + .finish(); + + let req = TestRequest::with_uri("/app/path2").finish(); + let resp = app.run(req); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::BAD_REQUEST + ); + + let req = TestRequest::with_uri("/path2").finish(); + let resp = app.run(req); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::NOT_FOUND + ); + } +} From 9c1bda3eca294ec644154f8a619f221e72f12a90 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 19:49:26 -0700 Subject: [PATCH 0154/1635] fix stable compiler compatibility --- src/application.rs | 8 ++++---- src/scope.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/application.rs b/src/application.rs index 7de2a258..6c0482aa 100644 --- a/src/application.rs +++ b/src/application.rs @@ -319,10 +319,10 @@ where /// } /// ``` /// - /// In the above example three routes get registered: - /// * /app/path1 - reponds to all http method - /// * /app/path2 - `GET` requests - /// * /app/path3 - `HEAD` requests + /// In the above example, three routes get added: + /// * /app/path1 + /// * /app/path2 + /// * /app/path3 /// pub fn scope(mut self, path: &str, f: F) -> App where diff --git a/src/scope.rs b/src/scope.rs index eae5d55a..b671eaac 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -184,7 +184,7 @@ impl RouteHandler for Scope { let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; let path = if path == "" { "/" } else { path }; - for (pattern, resource) in self.resources.iter() { + for &(ref pattern, ref resource) in self.resources.iter() { if pattern.match_with_params(path, req.match_info_mut()) { let default = unsafe { &mut *self.default.as_ref().get() }; From 91235ac81631958a1a03d4e39ae90af07d05bf9d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 20:34:59 -0700 Subject: [PATCH 0155/1635] fix reading from socket --- src/fs.rs | 2 +- src/server/h1.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 0a526d57..2aa1b979 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -573,7 +573,7 @@ impl Handler for StaticFiles { } else { let relpath = match req.match_info() .get("tail") - .map(|tail| PathBuf::from_param(tail)) + .map(|tail| PathBuf::from_param(tail.trim_left_matches('/'))) { Some(Ok(path)) => path, _ => return Ok(self.default.handle(req)), diff --git a/src/server/h1.rs b/src/server/h1.rs index 5f70bd0d..8c793ed5 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -388,7 +388,7 @@ where } } Err(e) => { - return e.kind() == io::ErrorKind::WouldBlock; + return e.kind() != io::ErrorKind::WouldBlock; } } } From ab4e889f9603525731ecd388acaa24efe1f94e04 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 20:50:38 -0700 Subject: [PATCH 0156/1635] add middleware finished handler for route middleware --- src/resource.rs | 3 ++ src/route.rs | 101 +++++++++++++++++++++++++++++++++++++++++------- src/scope.rs | 2 +- 3 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/resource.rs b/src/resource.rs index a6e6b731..e4dfbb2d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -187,6 +187,9 @@ impl ResourceHandler { /// /// This is similar to `App's` middlewares, but /// middlewares get invoked on resource level. + /// + /// *Note* `Middleware::finish()` fires right after response get + /// prepared. It does not wait until body get sent to peer. pub fn middleware>(&mut self, mw: M) { Rc::get_mut(&mut self.middlewares) .unwrap() diff --git a/src/route.rs b/src/route.rs index 27f0133b..6c4ba419 100644 --- a/src/route.rs +++ b/src/route.rs @@ -10,8 +10,8 @@ use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyItem, Responder, use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middleware::{Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted}; +use middleware::{Finished as MiddlewareFinished, Middleware, + Response as MiddlewareResponse, Started as MiddlewareStarted}; use pred::Predicate; use with::{ExtractorConfig, With, With2, With3}; @@ -274,7 +274,8 @@ enum ComposeState { Starting(StartMiddlewares), Handler(WaitingResponse), RunMiddlewares(RunMiddlewares), - Response(Response), + Finishing(FinishingMiddlewares), + Completed(Response), } impl ComposeState { @@ -283,7 +284,8 @@ impl ComposeState { ComposeState::Starting(ref mut state) => state.poll(info), ComposeState::Handler(ref mut state) => state.poll(info), ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Response(_) => None, + ComposeState::Finishing(ref mut state) => state.poll(info), + ComposeState::Completed(_) => None, } } } @@ -310,7 +312,7 @@ impl Future for Compose { fn poll(&mut self) -> Poll { loop { - if let ComposeState::Response(ref mut resp) = self.state { + if let ComposeState::Completed(ref mut resp) = self.state { let resp = resp.resp.take().unwrap(); return Ok(Async::Ready(resp)); } @@ -357,9 +359,9 @@ impl StartMiddlewares { } info.count += 1; } - Err(err) => return Response::init(err.into()), + Err(err) => return FinishingMiddlewares::init(info, err.into()), }, - Err(err) => return Response::init(err.into()), + Err(err) => return FinishingMiddlewares::init(info, err.into()), } } } @@ -389,12 +391,17 @@ impl StartMiddlewares { self.fut = Some(fut); continue 'outer; } - Err(err) => return Some(Response::init(err.into())), + Err(err) => { + return Some(FinishingMiddlewares::init( + info, + err.into(), + )) + } } } } } - Err(err) => return Some(Response::init(err.into())), + Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), } } } @@ -443,12 +450,12 @@ impl RunMiddlewares { resp = match info.mws[curr].response(&mut info.req, resp) { Err(err) => { info.count = curr + 1; - return Response::init(err.into()); + return FinishingMiddlewares::init(info, err.into()); } Ok(MiddlewareResponse::Done(r)) => { curr += 1; if curr == len { - return Response::init(r); + return FinishingMiddlewares::init(info, r); } else { r } @@ -475,15 +482,17 @@ impl RunMiddlewares { self.curr += 1; resp } - Err(err) => return Some(Response::init(err.into())), + Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), }; loop { if self.curr == len { - return Some(Response::init(resp)); + return Some(FinishingMiddlewares::init(info, resp)); } else { match info.mws[self.curr].response(&mut info.req, resp) { - Err(err) => return Some(Response::init(err.into())), + Err(err) => { + return Some(FinishingMiddlewares::init(info, err.into())) + } Ok(MiddlewareResponse::Done(r)) => { self.curr += 1; resp = r @@ -499,6 +508,68 @@ impl RunMiddlewares { } } +/// Middlewares start executor +struct FinishingMiddlewares { + resp: Option, + fut: Option>>, + _s: PhantomData, +} + +impl FinishingMiddlewares { + fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { + if info.count == 0 { + Response::init(resp) + } else { + let mut state = FinishingMiddlewares { + resp: Some(resp), + fut: None, + _s: PhantomData, + }; + if let Some(st) = state.poll(info) { + st + } else { + ComposeState::Finishing(state) + } + } + } + + fn poll(&mut self, info: &mut ComposeInfo) -> Option> { + loop { + // poll latest fut + let not_ready = if let Some(ref mut fut) = self.fut { + match fut.poll() { + Ok(Async::NotReady) => true, + Ok(Async::Ready(())) => false, + Err(err) => { + error!("Middleware finish error: {}", err); + false + } + } + } else { + false + }; + if not_ready { + return None; + } + self.fut = None; + info.count -= 1; + + match info.mws[info.count as usize] + .finish(&mut info.req, self.resp.as_ref().unwrap()) + { + MiddlewareFinished::Done => { + if info.count == 0 { + return Some(Response::init(self.resp.take().unwrap())); + } + } + MiddlewareFinished::Future(fut) => { + self.fut = Some(fut); + } + } + } + } +} + struct Response { resp: Option, _s: PhantomData, @@ -506,7 +577,7 @@ struct Response { impl Response { fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Response(Response { + ComposeState::Completed(Response { resp: Some(resp), _s: PhantomData, }) diff --git a/src/scope.rs b/src/scope.rs index b671eaac..29d51518 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -169,7 +169,7 @@ impl Scope { /// This is similar to `App's` middlewares, but /// middlewares get invoked on scope level. /// - /// *Note* `Middleware::finish()` is fired right after response get + /// *Note* `Middleware::finish()` fires right after response get /// prepared. It does not wait until body get sent to peer. pub fn middleware>(mut self, mw: M) -> Scope { Rc::get_mut(&mut self.middlewares) From eefbe19651ebda57f278b97b000cce7689eab8e3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 21:05:10 -0700 Subject: [PATCH 0157/1635] remove deprecated types and methods --- src/application.rs | 3 - src/httpcodes.rs | 258 +------------------------------------- src/httprequest.rs | 21 ---- src/lib.rs | 8 +- src/middleware/mod.rs | 8 -- src/middleware/session.rs | 10 +- src/uri.rs | 4 - 7 files changed, 10 insertions(+), 302 deletions(-) diff --git a/src/application.rs b/src/application.rs index 6c0482aa..0b1e0ec1 100644 --- a/src/application.rs +++ b/src/application.rs @@ -13,9 +13,6 @@ use router::{Resource, Router}; use scope::Scope; use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings}; -#[deprecated(since = "0.5.0", note = "please use `actix_web::App` instead")] -pub type Application = App; - /// Application pub struct HttpApplication { state: Rc, diff --git a/src/httpcodes.rs b/src/httpcodes.rs index f003b29a..6d1c5ed1 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -1,237 +1,8 @@ //! Basic http responses -#![allow(non_upper_case_globals, deprecated)] +#![allow(non_upper_case_globals)] use http::StatusCode; - -use body::Body; -use error::Error; -use handler::{Handler, Reply, Responder, RouteHandler}; -use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseBuilder}; -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Ok()` instead")] -pub const HttpOk: StaticResponse = StaticResponse(StatusCode::OK); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Created()` instead")] -pub const HttpCreated: StaticResponse = StaticResponse(StatusCode::CREATED); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Accepted()` instead")] -pub const HttpAccepted: StaticResponse = StaticResponse(StatusCode::ACCEPTED); -#[deprecated( - since = "0.5.0", - note = "please use `HttpResponse::pNonAuthoritativeInformation()` instead" -)] -pub const HttpNonAuthoritativeInformation: StaticResponse = - StaticResponse(StatusCode::NON_AUTHORITATIVE_INFORMATION); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NoContent()` instead")] -pub const HttpNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::ResetContent()` instead")] -pub const HttpResetContent: StaticResponse = StaticResponse(StatusCode::RESET_CONTENT); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::PartialContent()` instead" -)] -pub const HttpPartialContent: StaticResponse = - StaticResponse(StatusCode::PARTIAL_CONTENT); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::MultiStatus()` instead")] -pub const HttpMultiStatus: StaticResponse = StaticResponse(StatusCode::MULTI_STATUS); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::AlreadyReported()` instead" -)] -pub const HttpAlreadyReported: StaticResponse = - StaticResponse(StatusCode::ALREADY_REPORTED); - -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::MultipleChoices()` instead" -)] -pub const HttpMultipleChoices: StaticResponse = - StaticResponse(StatusCode::MULTIPLE_CHOICES); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::MovedPermanently()` instead" -)] -pub const HttpMovedPermanently: StaticResponse = - StaticResponse(StatusCode::MOVED_PERMANENTLY); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Found()` instead")] -pub const HttpFound: StaticResponse = StaticResponse(StatusCode::FOUND); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::SeeOther()` instead")] -pub const HttpSeeOther: StaticResponse = StaticResponse(StatusCode::SEE_OTHER); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotModified()` instead")] -pub const HttpNotModified: StaticResponse = StaticResponse(StatusCode::NOT_MODIFIED); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::UseProxy()` instead")] -pub const HttpUseProxy: StaticResponse = StaticResponse(StatusCode::USE_PROXY); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::TemporaryRedirect()` instead" -)] -pub const HttpTemporaryRedirect: StaticResponse = - StaticResponse(StatusCode::TEMPORARY_REDIRECT); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::PermanentRedirect()` instead" -)] -pub const HttpPermanentRedirect: StaticResponse = - StaticResponse(StatusCode::PERMANENT_REDIRECT); - -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadRequest()` instead")] -pub const HttpBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Unauthorized()` instead")] -pub const HttpUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::PaymentRequired()` instead" -)] -pub const HttpPaymentRequired: StaticResponse = - StaticResponse(StatusCode::PAYMENT_REQUIRED); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Forbidden()` instead")] -pub const HttpForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::NotFound()` instead")] -pub const HttpNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::MethodNotAllowed()` instead" -)] -pub const HttpMethodNotAllowed: StaticResponse = - StaticResponse(StatusCode::METHOD_NOT_ALLOWED); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::NotAcceptable()` instead" -)] -pub const HttpNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); -#[deprecated( - since = "0.5.0", - note = "please use `HttpResponse::ProxyAuthenticationRequired()` instead" -)] -pub const HttpProxyAuthenticationRequired: StaticResponse = - StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::RequestTimeout()` instead" -)] -pub const HttpRequestTimeout: StaticResponse = - StaticResponse(StatusCode::REQUEST_TIMEOUT); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Conflict()` instead")] -pub const HttpConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::Gone()` instead")] -pub const HttpGone: StaticResponse = StaticResponse(StatusCode::GONE); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::LengthRequired()` instead" -)] -pub const HttpLengthRequired: StaticResponse = - StaticResponse(StatusCode::LENGTH_REQUIRED); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::PreconditionFailed()` instead" -)] -pub const HttpPreconditionFailed: StaticResponse = - StaticResponse(StatusCode::PRECONDITION_FAILED); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::PayloadTooLarge()` instead" -)] -pub const HttpPayloadTooLarge: StaticResponse = - StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::UriTooLong()` instead")] -pub const HttpUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::UnsupportedMediaType()` instead" -)] -pub const HttpUnsupportedMediaType: StaticResponse = - StaticResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::RangeNotSatisfiable()` instead" -)] -pub const HttpRangeNotSatisfiable: StaticResponse = - StaticResponse(StatusCode::RANGE_NOT_SATISFIABLE); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::ExpectationFailed()` instead" -)] -pub const HttpExpectationFailed: StaticResponse = - StaticResponse(StatusCode::EXPECTATION_FAILED); - -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::InternalServerError()` instead" -)] -pub const HttpInternalServerError: StaticResponse = - StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::NotImplemented()` instead" -)] -pub const HttpNotImplemented: StaticResponse = - StaticResponse(StatusCode::NOT_IMPLEMENTED); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::BadGateway()` instead")] -pub const HttpBadGateway: StaticResponse = StaticResponse(StatusCode::BAD_GATEWAY); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::ServiceUnavailable()` instead" -)] -pub const HttpServiceUnavailable: StaticResponse = - StaticResponse(StatusCode::SERVICE_UNAVAILABLE); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::GatewayTimeout()` instead" -)] -pub const HttpGatewayTimeout: StaticResponse = - StaticResponse(StatusCode::GATEWAY_TIMEOUT); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::VersionNotSupported()` instead" -)] -pub const HttpVersionNotSupported: StaticResponse = - StaticResponse(StatusCode::HTTP_VERSION_NOT_SUPPORTED); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::VariantAlsoNegotiates()` instead" -)] -pub const HttpVariantAlsoNegotiates: StaticResponse = - StaticResponse(StatusCode::VARIANT_ALSO_NEGOTIATES); -#[deprecated( - since = "0.5.0", note = "please use `HttpResponse::InsufficientStorage()` instead" -)] -pub const HttpInsufficientStorage: StaticResponse = - StaticResponse(StatusCode::INSUFFICIENT_STORAGE); -#[deprecated(since = "0.5.0", note = "please use `HttpResponse::LoopDetected()` instead")] -pub const HttpLoopDetected: StaticResponse = StaticResponse(StatusCode::LOOP_DETECTED); - -#[deprecated(since = "0.5.0", note = "please use `HttpResponse` instead")] -#[derive(Copy, Clone, Debug)] -pub struct StaticResponse(StatusCode); - -impl StaticResponse { - pub fn build(&self) -> HttpResponseBuilder { - HttpResponse::build(self.0) - } - pub fn build_from(&self, req: &HttpRequest) -> HttpResponseBuilder { - req.build_response(self.0) - } - pub fn with_reason(self, reason: &'static str) -> HttpResponse { - let mut resp = HttpResponse::new(self.0); - resp.set_reason(reason); - resp - } - pub fn with_body>(self, body: B) -> HttpResponse { - HttpResponse::with_body(self.0, body.into()) - } -} - -impl Handler for StaticResponse { - type Result = HttpResponse; - - fn handle(&mut self, _: HttpRequest) -> HttpResponse { - HttpResponse::new(self.0) - } -} - -impl RouteHandler for StaticResponse { - fn handle(&mut self, _: HttpRequest) -> Reply { - Reply::response(HttpResponse::new(self.0)) - } -} - -impl Responder for StaticResponse { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: HttpRequest) -> Result { - Ok(self.build().finish()) - } -} - -impl From for HttpResponse { - fn from(st: StaticResponse) -> Self { - HttpResponse::new(st.0) - } -} - -impl From for Reply { - fn from(st: StaticResponse) -> Self { - HttpResponse::new(st.0).into() - } -} - macro_rules! STATIC_RESP { ($name:ident, $status:expr) => { #[allow(non_snake_case)] @@ -310,34 +81,13 @@ impl HttpResponse { #[cfg(test)] mod tests { - use super::{Body, HttpBadRequest, HttpOk, HttpResponse}; + use body::Body; use http::StatusCode; + use httpresponse::HttpResponse; #[test] fn test_build() { - let resp = HttpOk.build().body(Body::Empty); + let resp = HttpResponse::Ok().body(Body::Empty); assert_eq!(resp.status(), StatusCode::OK); } - - #[test] - fn test_response() { - let resp: HttpResponse = HttpOk.into(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_from() { - let resp: HttpResponse = HttpOk.into(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_with_reason() { - let resp: HttpResponse = HttpOk.into(); - assert_eq!(resp.reason(), "OK"); - - let resp = HttpBadRequest.with_reason("test"); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - assert_eq!(resp.reason(), "test"); - } } diff --git a/src/httprequest.rs b/src/httprequest.rs index 1ed59eb7..ad7864a7 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -248,16 +248,6 @@ impl HttpRequest { self.as_ref().url.uri() } - #[doc(hidden)] - #[deprecated(since = "0.5.3")] - /// Returns mutable the Request Uri. - /// - /// This might be useful for middlewares, e.g. path normalization. - #[inline] - pub fn uri_mut(&mut self) -> &mut Uri { - self.as_mut().url.uri_mut() - } - /// Read the Request method. #[inline] pub fn method(&self) -> &Method { @@ -595,10 +585,7 @@ impl fmt::Debug for HttpRequest { #[cfg(test)] mod tests { - #![allow(deprecated)] - use super::*; - use http::{HttpTryFrom, Uri}; use resource::ResourceHandler; use router::Resource; use server::ServerSettings; @@ -611,14 +598,6 @@ mod tests { assert!(dbg.contains("HttpRequest")); } - #[test] - fn test_uri_mut() { - let mut req = HttpRequest::default(); - assert_eq!(req.path(), "/"); - *req.uri_mut() = Uri::try_from("/test").unwrap(); - assert_eq!(req.path(), "/test"); - } - #[test] fn test_no_request_cookies() { let req = HttpRequest::default(); diff --git a/src/lib.rs b/src/lib.rs index 04371193..5e5d349a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,6 +138,7 @@ mod extractor; mod handler; mod header; mod helpers; +mod httpcodes; mod httpmessage; mod httprequest; mod httpresponse; @@ -174,13 +175,6 @@ pub use httpresponse::HttpResponse; pub use json::Json; pub use scope::Scope; -#[doc(hidden)] -pub mod httpcodes; - -#[doc(hidden)] -#[allow(deprecated)] -pub use application::Application; - #[cfg(feature = "openssl")] pub(crate) const HAS_OPENSSL: bool = true; #[cfg(not(feature = "openssl"))] diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index b9d3847d..f097484f 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -19,14 +19,6 @@ pub use self::defaultheaders::DefaultHeaders; pub use self::errhandlers::ErrorHandlers; pub use self::logger::Logger; -#[cfg(feature = "session")] -#[doc(hidden)] -#[deprecated( - since = "0.5.4", note = "please use `actix_web::middleware::session` instead" -)] -pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession, - Session, SessionBackend, SessionImpl, SessionStorage}; - /// Middleware start result pub enum Started { /// Execution completed diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 4aaf1b7a..4e2f9976 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -35,7 +35,7 @@ //! # extern crate actix; //! # extern crate actix_web; //! use actix_web::{server, App, HttpRequest, Result}; -//! use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend}; +//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; //! //! fn index(mut req: HttpRequest) -> Result<&'static str> { //! // access session data @@ -86,7 +86,7 @@ use middleware::{Middleware, Response, Started}; /// /// ```rust /// use actix_web::*; -/// use actix_web::middleware::RequestSession; +/// use actix_web::middleware::session::RequestSession; /// /// fn index(mut req: HttpRequest) -> Result<&'static str> { /// // access session data @@ -123,7 +123,7 @@ impl RequestSession for HttpRequest { /// /// ```rust /// use actix_web::*; -/// use actix_web::middleware::RequestSession; +/// use actix_web::middleware::session::RequestSession; /// /// fn index(mut req: HttpRequest) -> Result<&'static str> { /// // access session data @@ -179,7 +179,7 @@ unsafe impl Sync for SessionImplBox {} /// # extern crate actix; /// # extern crate actix_web; /// use actix_web::App; -/// use actix_web::middleware::{SessionStorage, CookieSessionBackend}; +/// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend}; /// /// fn main() { /// let app = App::new().middleware( @@ -437,7 +437,7 @@ impl CookieSessionInner { /// /// ```rust /// # extern crate actix_web; -/// use actix_web::middleware::CookieSessionBackend; +/// use actix_web::middleware::session::CookieSessionBackend; /// /// # fn main() { /// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32]) diff --git a/src/uri.rs b/src/uri.rs index d30fe5cb..ce147024 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -51,10 +51,6 @@ impl Url { &self.uri } - pub fn uri_mut(&mut self) -> &mut Uri { - &mut self.uri - } - pub fn path(&self) -> &str { if let Some(ref s) = self.path { s From 25b245ac72818d9741efa3949ba4439ae63f571f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 22:19:52 -0700 Subject: [PATCH 0158/1635] allow to use custom state for scope --- src/scope.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 9 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 29d51518..f1d27159 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -42,6 +42,7 @@ type ScopeResources = Rc>>)>> /// * /app/path3 - `HEAD` requests /// pub struct Scope { + handler: Option>>>, middlewares: Rc>>>, default: Rc>>, resources: ScopeResources, @@ -56,12 +57,54 @@ impl Default for Scope { impl Scope { pub fn new() -> Scope { Scope { + handler: None, resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), } } + /// Create scope with new state. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; + /// + /// struct AppState; + /// + /// fn index(req: HttpRequest) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .scope("/app", |scope| { + /// scope.with_state(AppState, |scope| { + /// scope.resource("/test1", |r| r.f(index)) + /// }) + /// }); + /// } + /// ``` + pub fn with_state(mut self, state: T, f: F) -> Scope + where + F: FnOnce(Scope) -> Scope, + { + let scope = Scope { + handler: None, + resources: Rc::new(Vec::new()), + middlewares: Rc::new(Vec::new()), + default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), + }; + let scope = f(scope); + + self.handler = Some(UnsafeCell::new(Box::new(Wrapper { + scope, + state: Rc::new(state), + }))); + + self + } + /// Configure route for a specific path. /// /// This is a simplified version of the `Scope::resource()` method. @@ -184,6 +227,7 @@ impl RouteHandler for Scope { let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; let path = if path == "" { "/" } else { path }; + // recognize paths for &(ref pattern, ref resource) in self.resources.iter() { if pattern.match_with_params(path, req.match_info_mut()) { let default = unsafe { &mut *self.default.as_ref().get() }; @@ -202,20 +246,39 @@ impl RouteHandler for Scope { } } - let default = unsafe { &mut *self.default.as_ref().get() }; - if self.middlewares.is_empty() { - default.handle(req, None) + // nested scope + if let Some(ref handler) = self.handler { + let hnd: &mut RouteHandler<_> = unsafe { (&mut *(handler.get())).as_mut() }; + hnd.handle(req) } else { - Reply::async(Compose::new( - req, - Rc::clone(&self.middlewares), - Rc::clone(&self.default), - None, - )) + // default handler + let default = unsafe { &mut *self.default.as_ref().get() }; + if self.middlewares.is_empty() { + default.handle(req, None) + } else { + Reply::async(Compose::new( + req, + Rc::clone(&self.middlewares), + Rc::clone(&self.default), + None, + )) + } } } } +struct Wrapper { + state: Rc, + scope: Scope, +} + +impl RouteHandler for Wrapper { + fn handle(&mut self, req: HttpRequest) -> Reply { + self.scope + .handle(req.change_state(Rc::clone(&self.state))) + } +} + /// Compose resource level middlewares with route handler. struct Compose { info: ComposeInfo, @@ -574,6 +637,26 @@ mod tests { assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); } + #[test] + fn test_scope_with_state() { + struct State; + + let mut app = App::new() + .scope("/app", |scope| { + scope.with_state(State, |scope| { + scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/path1").finish(); + let resp = app.run(req); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::CREATED + ); + } + #[test] fn test_default_resource() { let mut app = App::new() From bfd46e6a7139b91824c56616b1a685d700188369 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 29 Apr 2018 22:28:16 -0700 Subject: [PATCH 0159/1635] update doc string --- src/scope.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/scope.rs b/src/scope.rs index f1d27159..46eb9e97 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -66,6 +66,9 @@ impl Scope { /// Create scope with new state. /// + /// Scope can have only one nested scope with new state. Every call + /// destroys previously created scope with state. + /// /// ```rust /// # extern crate actix_web; /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; From d43ca96c5c9aec9468002d8ae93ca478842d69c0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 30 Apr 2018 19:51:55 -0700 Subject: [PATCH 0160/1635] Allow to use ssl and non-ssl connections with the same HttpServer #206 --- Cargo.toml | 1 + MIGRATION.md | 3 + src/lib.rs | 1 + src/scope.rs | 2 +- src/server/srv.rs | 313 ++++++++++++++++++++----------------------- src/server/worker.rs | 25 +++- 6 files changed, 174 insertions(+), 171 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e8f70fd4..265e0a33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ bytes = "0.4" byteorder = "1" futures = "0.1" futures-cpupool = "0.1" +slab = "0.4" tokio-io = "0.1" tokio-core = "0.1" diff --git a/MIGRATION.md b/MIGRATION.md index 63c4989e..4e8cad36 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -3,6 +3,9 @@ * `ws::Message::Close` now includes optional close reason. `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. +* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. + Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. + ## Migration from 0.4 to 0.5 diff --git a/src/lib.rs b/src/lib.rs index 5e5d349a..14e2cfc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,7 @@ extern crate mime_guess; extern crate mio; extern crate net2; extern crate rand; +extern crate slab; extern crate tokio_core; extern crate tokio_io; extern crate url; diff --git a/src/scope.rs b/src/scope.rs index 46eb9e97..6f730e91 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -71,7 +71,7 @@ impl Scope { /// /// ```rust /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; + /// use actix_web::{App, HttpRequest}; /// /// struct AppState; /// diff --git a/src/server/srv.rs b/src/server/srv.rs index 57699b20..4ba263e7 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -10,6 +10,7 @@ use futures::{Future, Sink, Stream}; use mio; use net2::TcpBuilder; use num_cpus; +use slab::Slab; use tokio_io::{AsyncRead, AsyncWrite}; #[cfg(feature = "tls")] @@ -20,7 +21,7 @@ use openssl::ssl::{AlpnError, SslAcceptorBuilder}; use super::channel::{HttpChannel, WrapperStream}; use super::settings::{ServerSettings, WorkerSettings}; -use super::worker::{Conn, StopWorker, StreamHandlerType, Worker}; +use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker}; use super::{IntoHttpHandler, IoStream, KeepAlive}; use super::{PauseServer, ResumeServer, StopServer}; @@ -37,7 +38,7 @@ where factory: Arc Vec + Send + Sync>, #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] workers: Vec<(usize, Addr>)>, - sockets: Vec<(net::SocketAddr, net::TcpListener)>, + sockets: Vec, accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, exit: bool, shutdown_timeout: u16, @@ -57,14 +58,8 @@ where { } -#[derive(Clone)] -struct Info { - addr: net::SocketAddr, - handler: StreamHandlerType, -} - enum ServerCommand { - WorkerDied(usize, Info), + WorkerDied(usize, Slab), } impl Actor for HttpServer @@ -74,6 +69,12 @@ where type Context = Context; } +struct Socket { + lst: net::TcpListener, + addr: net::SocketAddr, + tp: StreamHandlerType, +} + impl HttpServer where H: IntoHttpHandler + 'static, @@ -187,7 +188,7 @@ where /// Get addresses of bound sockets. pub fn addrs(&self) -> Vec { - self.sockets.iter().map(|s| s.0).collect() + self.sockets.iter().map(|s| s.addr).collect() } /// Use listener for accepting incoming connection requests @@ -195,21 +196,29 @@ where /// HttpServer does not change any configuration for TcpListener, /// it needs to be configured before passing it to listen() method. pub fn listen(mut self, lst: net::TcpListener) -> Self { - self.sockets.push((lst.local_addr().unwrap(), lst)); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + lst, + tp: StreamHandlerType::Normal, + }); self } - /// 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 { + fn bind2(&mut self, addr: S) -> io::Result> { let mut err = None; let mut succ = false; + let mut sockets = Vec::new(); for addr in addr.to_socket_addrs()? { match create_tcp_listener(addr, self.backlog) { Ok(lst) => { succ = true; - self.sockets.push((lst.local_addr().unwrap(), lst)); + let addr = lst.local_addr().unwrap(); + sockets.push(Socket { + lst, + addr, + tp: StreamHandlerType::Normal, + }); } Err(e) => err = Some(e), } @@ -225,12 +234,65 @@ where )) } } else { - Ok(self) + Ok(sockets) } } + /// 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 sockets = self.bind2(addr)?; + self.sockets.extend(sockets); + Ok(self) + } + + #[cfg(feature = "tls")] + /// The ssl socket address to bind + /// + /// To mind multiple addresses this method can be call multiple times. + pub fn bind_tls( + mut self, addr: S, acceptor: TlsAcceptor, + ) -> io::Result { + let sockets = self.bind2(addr)?; + self.sockets.extend(sockets.into_iter().map(|mut s| { + s.tp = StreamHandlerType::Tls(acceptor.clone()); + s + })); + Ok(self) + } + + #[cfg(feature = "alpn")] + /// Start listening for incoming tls connections. + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn bind_ssl( + mut self, addr: S, mut builder: SslAcceptorBuilder, + ) -> io::Result { + // alpn support + if !self.no_http2 { + builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } + }); + } + + let acceptor = builder.build(); + let sockets = self.bind2(addr)?; + self.sockets.extend(sockets.into_iter().map(|mut s| { + s.tp = StreamHandlerType::Alpn(acceptor.clone()); + s + })); + Ok(self) + } + fn start_workers( - &mut self, settings: &ServerSettings, handler: &StreamHandlerType, + &mut self, settings: &ServerSettings, sockets: &Slab, ) -> Vec<(usize, mpsc::UnboundedSender>)> { // start workers let mut workers = Vec::new(); @@ -238,8 +300,8 @@ where let s = settings.clone(); let (tx, rx) = mpsc::unbounded::>(); - let h = handler.clone(); let ka = self.keep_alive; + let socks = sockets.clone(); let factory = Arc::clone(&self.factory); let addr = Arbiter::start(move |ctx: &mut Context<_>| { let apps: Vec<_> = (*factory)() @@ -247,7 +309,7 @@ where .map(|h| h.into_handler(s.clone())) .collect(); ctx.add_message_stream(rx); - Worker::new(apps, h, ka) + Worker::new(apps, socks, ka) }); workers.push((idx, tx)); self.workers.push((idx, addr)); @@ -304,24 +366,32 @@ impl HttpServer { panic!("HttpServer::bind() has to be called before start()"); } else { let (tx, rx) = mpsc::unbounded(); - 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); - let info = Info { - addr: addrs[0].0, - handler: StreamHandlerType::Normal, - }; + + let mut socks = Slab::new(); + let mut addrs: Vec<(usize, Socket)> = Vec::new(); + + for socket in self.sockets.drain(..) { + let entry = socks.vacant_entry(); + let token = entry.key(); + entry.insert(SocketInfo { + addr: socket.addr, + htype: socket.tp.clone(), + }); + addrs.push((token, socket)); + } + + let settings = ServerSettings::new(Some(addrs[0].1.addr), &self.host, false); + let workers = self.start_workers(&settings, &socks); // start acceptors threads - for (addr, sock) in addrs { - info!("Starting server on http://{}", addr); + for (token, sock) in addrs { + info!("Starting server on http://{}", sock.addr); self.accept.push(start_accept_thread( + token, sock, - addr, self.backlog, tx.clone(), - info.clone(), + socks.clone(), workers.clone(), )); } @@ -373,55 +443,30 @@ impl HttpServer { } } +#[doc(hidden)] #[cfg(feature = "tls")] +#[deprecated( + since = "0.6.0", note = "please use `actix_web::HttpServer::bind_tls` instead" +)] impl HttpServer { /// Start listening for incoming tls connections. pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result> { - if self.sockets.is_empty() { - Err(io::Error::new( - io::ErrorKind::Other, - "No socket addresses are bound", - )) - } else { - let (tx, rx) = mpsc::unbounded(); - 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::Tls(acceptor.clone())); - let info = Info { - addr: addrs[0].0, - handler: StreamHandlerType::Tls(acceptor), - }; - - // start acceptors threads - for (addr, sock) in addrs { - info!("Starting server on https://{}", addr); - self.accept.push(start_accept_thread( - sock, - addr, - self.backlog, - tx.clone(), - info.clone(), - workers.clone(), - )); + for sock in &mut self.sockets { + match sock.tp { + StreamHandlerType::Normal => (), + _ => continue, } - - // start http server actor - let signals = self.subscribe_to_signals(); - let addr: Addr = Actor::create(|ctx| { - ctx.add_stream(rx); - self - }); - signals.map(|signals| { - signals.do_send(signal::Subscribe(addr.clone().recipient())) - }); - Ok(addr) + sock.tp = StreamHandlerType::Tls(acceptor.clone()); } + Ok(self.start()) } } +#[doc(hidden)] #[cfg(feature = "alpn")] +#[deprecated( + since = "0.6.0", note = "please use `actix_web::HttpServer::bind_ssl` instead" +)] impl HttpServer { /// Start listening for incoming tls connections. /// @@ -429,63 +474,28 @@ impl HttpServer { pub fn start_ssl( mut self, mut builder: SslAcceptorBuilder, ) -> io::Result> { - if self.sockets.is_empty() { - Err(io::Error::new( - io::ErrorKind::Other, - "No socket addresses are bound", - )) - } else { - // alpn support - if !self.no_http2 { - builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - } - - let (tx, rx) = mpsc::unbounded(); - let acceptor = builder.build(); - 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::Alpn(acceptor.clone()), - ); - let info = Info { - addr: addrs[0].0, - handler: StreamHandlerType::Alpn(acceptor), - }; - - // start acceptors threads - for (addr, sock) in addrs { - info!("Starting server on https://{}", addr); - self.accept.push(start_accept_thread( - sock, - addr, - self.backlog, - tx.clone(), - info.clone(), - workers.clone(), - )); - } - - // start http server actor - let signals = self.subscribe_to_signals(); - let addr: Addr = Actor::create(|ctx| { - ctx.add_stream(rx); - self + // alpn support + if !self.no_http2 { + builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } }); - signals.map(|signals| { - signals.do_send(signal::Subscribe(addr.clone().recipient())) - }); - Ok(addr) } + + let acceptor = builder.build(); + for sock in &mut self.sockets { + match sock.tp { + StreamHandlerType::Normal => (), + _ => continue, + } + sock.tp = StreamHandlerType::Alpn(acceptor.clone()); + } + Ok(self.start()) } } @@ -499,32 +509,6 @@ impl HttpServer { T: AsyncRead + AsyncWrite + 'static, A: 'static, { - let (tx, rx) = mpsc::unbounded(); - - if !self.sockets.is_empty() { - 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); - let info = Info { - addr: addrs[0].0, - handler: StreamHandlerType::Normal, - }; - - // start acceptors threads - for (addr, sock) in addrs { - info!("Starting server on http://{}", addr); - self.accept.push(start_accept_thread( - sock, - addr, - self.backlog, - tx.clone(), - info.clone(), - workers.clone(), - )); - } - } - // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); let settings = ServerSettings::new(Some(addr), &self.host, secure); @@ -537,9 +521,9 @@ impl HttpServer { // start server let signals = self.subscribe_to_signals(); let addr: Addr = HttpServer::create(move |ctx| { - ctx.add_stream(rx); ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn { io: WrapperStream::new(t), + token: 0, peer: None, http2: false, })); @@ -585,7 +569,7 @@ impl StreamHandler for HttpServer { fn finished(&mut self, _: &mut Context) {} fn handle(&mut self, msg: ServerCommand, _: &mut Context) { match msg { - ServerCommand::WorkerDied(idx, info) => { + ServerCommand::WorkerDied(idx, socks) => { let mut found = false; for i in 0..self.workers.len() { if self.workers[i].0 == idx { @@ -610,11 +594,10 @@ impl StreamHandler for HttpServer { break; } - let h = info.handler; let ka = self.keep_alive; let factory = Arc::clone(&self.factory); let settings = - ServerSettings::new(Some(info.addr), &self.host, false); + ServerSettings::new(Some(socks[0].addr), &self.host, false); let addr = Arbiter::start(move |ctx: &mut Context<_>| { let apps: Vec<_> = (*factory)() @@ -622,7 +605,7 @@ impl StreamHandler for HttpServer { .map(|h| h.into_handler(settings.clone())) .collect(); ctx.add_message_stream(rx); - Worker::new(apps, h, ka) + Worker::new(apps, socks, ka) }); for item in &self.accept { let _ = item.1.send(Command::Worker(new_idx, tx.clone())); @@ -738,8 +721,8 @@ enum Command { } fn start_accept_thread( - sock: net::TcpListener, addr: net::SocketAddr, backlog: i32, - srv: mpsc::UnboundedSender, info: Info, + token: usize, sock: Socket, backlog: i32, srv: mpsc::UnboundedSender, + socks: Slab, mut workers: Vec<(usize, mpsc::UnboundedSender>)>, ) -> (mio::SetReadiness, sync_mpsc::Sender) { let (tx, rx) = sync_mpsc::channel(); @@ -748,13 +731,14 @@ fn start_accept_thread( // start accept thread #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] let _ = thread::Builder::new() - .name(format!("Accept on {}", addr)) + .name(format!("Accept on {}", sock.addr)) .spawn(move || { const SRV: mio::Token = mio::Token(0); const CMD: mio::Token = mio::Token(1); + let addr = sock.addr; let mut server = Some( - mio::net::TcpListener::from_std(sock) + mio::net::TcpListener::from_std(sock.lst) .expect("Can not create mio::net::TcpListener"), ); @@ -800,9 +784,10 @@ fn start_accept_thread( SRV => if let Some(ref server) = server { loop { match server.accept_std() { - Ok((sock, addr)) => { + Ok((io, addr)) => { let mut msg = Conn { - io: sock, + io, + token, peer: Some(addr), http2: false, }; @@ -813,7 +798,7 @@ fn start_accept_thread( let _ = srv.unbounded_send( ServerCommand::WorkerDied( workers[next].0, - info.clone(), + socks.clone(), ), ); msg = err.into_inner(); diff --git a/src/server/worker.rs b/src/server/worker.rs index f10f79cb..67f4645c 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,6 +1,7 @@ use futures::unsync::oneshot; use futures::Future; use net2::TcpStreamExt; +use slab::Slab; use std::rc::Rc; use std::{net, time}; use tokio_core::net::TcpStream; @@ -29,10 +30,17 @@ use server::{HttpHandler, KeepAlive}; #[derive(Message)] pub(crate) struct Conn { pub io: T, + pub token: usize, pub peer: Option, pub http2: bool, } +#[derive(Clone)] +pub(crate) struct SocketInfo { + pub addr: net::SocketAddr, + pub htype: StreamHandlerType, +} + /// Stop worker message. Returns `true` on successful shutdown /// and `false` if some connections still alive. pub(crate) struct StopWorker { @@ -53,13 +61,13 @@ where { settings: Rc>, hnd: Handle, - handler: StreamHandlerType, + socks: Slab, tcp_ka: Option, } impl Worker { pub(crate) fn new( - h: Vec, handler: StreamHandlerType, keep_alive: KeepAlive, + h: Vec, socks: Slab, keep_alive: KeepAlive, ) -> Worker { let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { Some(time::Duration::new(val as u64, 0)) @@ -70,7 +78,7 @@ impl Worker { Worker { settings: Rc::new(WorkerSettings::new(h, keep_alive)), hnd: Arbiter::handle().clone(), - handler, + socks, tcp_ka, } } @@ -124,8 +132,11 @@ where if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { error!("Can not set socket keep-alive option"); } - self.handler - .handle(Rc::clone(&self.settings), &self.hnd, msg); + self.socks.get_mut(msg.token).unwrap().htype.handle( + Rc::clone(&self.settings), + &self.hnd, + msg, + ); } } @@ -177,7 +188,9 @@ impl StreamHandlerType { } #[cfg(feature = "tls")] StreamHandlerType::Tls(ref acceptor) => { - let Conn { io, peer, http2 } = msg; + let Conn { + io, peer, http2, .. + } = msg; let _ = io.set_nodelay(true); let io = TcpStream::from_stream(io, hnd) .expect("failed to associate TCP stream"); From 70d0c5c700392f517ffab14d28b83840537f00be Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 30 Apr 2018 19:56:17 -0700 Subject: [PATCH 0161/1635] update changes --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 8d3861a3..ddc9dc84 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ * Add route scopes #202 +* Allow to use ssl and non-ssl connections at the same time #206 + * Websocket CloseCode Empty/Status is ambiguous #193 * Add Content-Disposition to NamedFile #204 From 48e05a2d872b37b5463d9a78c9b3e487813a16c1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 30 Apr 2018 22:04:24 -0700 Subject: [PATCH 0162/1635] add nested scope support --- src/application.rs | 11 +-- src/httprequest.rs | 46 ++++++++----- src/param.rs | 16 +++++ src/router.rs | 1 + src/scope.rs | 146 ++++++++++++++++++++++++++++++++-------- src/server/h1decoder.rs | 8 ++- tests/test_client.rs | 1 + 7 files changed, 176 insertions(+), 53 deletions(-) diff --git a/src/application.rs b/src/application.rs index 0b1e0ec1..bd6c4d00 100644 --- a/src/application.rs +++ b/src/application.rs @@ -69,9 +69,11 @@ impl HttpApplication { }; if m { - let path: &'static str = unsafe { - &*(&req.path()[inner.prefix + prefix.len()..] as *const _) - }; + let prefix_len = inner.prefix + prefix.len(); + let path: &'static str = + unsafe { &*(&req.path()[prefix_len..] as *const _) }; + + req.set_prefix_len(prefix_len as u16); if path.is_empty() { req.match_info_mut().add("tail", "/"); } else { @@ -881,8 +883,9 @@ mod tests { ); let req = TestRequest::with_uri("/app/test").finish(); - let resp = app.run(req); + let resp = app.run(req.clone()); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(req.prefix_len(), 9); let req = TestRequest::with_uri("/app/test/").finish(); let resp = app.run(req); diff --git a/src/httprequest.rs b/src/httprequest.rs index ad7864a7..5b3c6619 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -25,20 +25,27 @@ use router::{Resource, Router}; use server::helpers::SharedHttpInnerMessage; use uri::Url as InnerUrl; +bitflags! { + pub(crate) struct MessageFlags: u8 { + const QUERY = 0b0000_0001; + const KEEPALIVE = 0b0000_0010; + } +} + pub struct HttpInnerMessage { pub version: Version, pub method: Method, pub(crate) url: InnerUrl, + pub(crate) flags: MessageFlags, pub headers: HeaderMap, 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>, - pub keep_alive: bool, + pub prefix: u16, resource: RouterResource, } @@ -55,15 +62,15 @@ impl Default for HttpInnerMessage { url: InnerUrl::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), + flags: MessageFlags::empty(), params: Params::new(), query: Params::new(), - query_loaded: false, addr: None, cookies: None, payload: None, extensions: Extensions::new(), info: None, - keep_alive: true, + prefix: 0, resource: RouterResource::Notset, } } @@ -73,7 +80,7 @@ impl HttpInnerMessage { /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { - self.keep_alive + self.flags.contains(MessageFlags::KEEPALIVE) } #[inline] @@ -83,10 +90,10 @@ impl HttpInnerMessage { self.params.clear(); self.addr = None; self.info = None; - self.query_loaded = false; + self.flags = MessageFlags::empty(); self.cookies = None; self.payload = None; - self.keep_alive = true; + self.prefix = 0; self.resource = RouterResource::Notset; } } @@ -115,12 +122,12 @@ impl HttpRequest<()> { payload, params: Params::new(), query: Params::new(), - query_loaded: false, extensions: Extensions::new(), cookies: None, addr: None, info: None, - keep_alive: true, + prefix: 0, + flags: MessageFlags::empty(), resource: RouterResource::Notset, }), None, @@ -234,12 +241,13 @@ impl HttpRequest { } #[doc(hidden)] - pub fn prefix_len(&self) -> usize { - if let Some(router) = self.router() { - router.prefix().len() - } else { - 0 - } + pub fn prefix_len(&self) -> u16 { + self.as_ref().prefix as u16 + } + + #[doc(hidden)] + pub fn set_prefix_len(&mut self, len: u16) { + self.as_mut().prefix = len; } /// Read the Request Uri. @@ -367,14 +375,16 @@ impl HttpRequest { self.as_mut().addr = addr; } + #[doc(hidden)] + #[deprecated(since = "0.6.0", note = "please use `Query` extractor")] /// 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 { + if !self.as_ref().flags.contains(MessageFlags::QUERY) { + self.as_mut().flags.insert(MessageFlags::QUERY); let params: &mut Params = unsafe { mem::transmute(&mut self.as_mut().query) }; params.clear(); - self.as_mut().query_loaded = true; for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { params.add(key, val); } @@ -443,7 +453,7 @@ impl HttpRequest { /// Checks if a connection should be kept alive. pub fn keep_alive(&self) -> bool { - self.as_ref().keep_alive() + self.as_ref().flags.contains(MessageFlags::KEEPALIVE) } /// Check if request requires connection upgrade diff --git a/src/param.rs b/src/param.rs index 41100763..386c0c33 100644 --- a/src/param.rs +++ b/src/param.rs @@ -42,6 +42,22 @@ impl<'a> Params<'a> { self.0.push((name.into(), value.into())); } + pub(crate) fn set(&mut self, name: N, value: V) + where + N: Into>, + V: Into>, + { + let name = name.into(); + let value = value.into(); + for item in &mut self.0 { + if item.0 == name { + item.1 = value; + return; + } + } + self.0.push((name, value)); + } + /// Check if there are any matched patterns pub fn is_empty(&self) -> bool { self.0.is_empty() diff --git a/src/router.rs b/src/router.rs index dd29e4bc..6c5a7da6 100644 --- a/src/router.rs +++ b/src/router.rs @@ -84,6 +84,7 @@ impl Router { for (idx, pattern) in self.0.patterns.iter().enumerate() { if pattern.match_with_params(route_path, req.match_info_mut()) { req.set_resource(idx); + req.set_prefix_len(self.0.prefix_len as u16); return Some(idx); } } diff --git a/src/scope.rs b/src/scope.rs index 6f730e91..ff630e68 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -14,6 +14,7 @@ use middleware::{Finished as MiddlewareFinished, Middleware, use resource::ResourceHandler; use router::Resource; +type Route = UnsafeCell>>; type ScopeResources = Rc>>)>>; /// Resources scope @@ -42,7 +43,7 @@ type ScopeResources = Rc>>)>> /// * /app/path3 - `HEAD` requests /// pub struct Scope { - handler: Option>>>, + nested: Vec<(String, Route)>, middlewares: Rc>>>, default: Rc>>, resources: ScopeResources, @@ -57,17 +58,14 @@ impl Default for Scope { impl Scope { pub fn new() -> Scope { Scope { - handler: None, + nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), } } - /// Create scope with new state. - /// - /// Scope can have only one nested scope with new state. Every call - /// destroys previously created scope with state. + /// Create nested scope with new state. /// /// ```rust /// # extern crate actix_web; @@ -82,28 +80,78 @@ impl Scope { /// fn main() { /// let app = App::new() /// .scope("/app", |scope| { - /// scope.with_state(AppState, |scope| { + /// scope.with_state("/state2", AppState, |scope| { /// scope.resource("/test1", |r| r.f(index)) /// }) /// }); /// } /// ``` - pub fn with_state(mut self, state: T, f: F) -> Scope + pub fn with_state(mut self, path: &str, state: T, f: F) -> Scope where F: FnOnce(Scope) -> Scope, { let scope = Scope { - handler: None, + nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), }; let scope = f(scope); - self.handler = Some(UnsafeCell::new(Box::new(Wrapper { + let mut path = path.trim().trim_right_matches('/').to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/') + } + + let handler = UnsafeCell::new(Box::new(Wrapper { scope, state: Rc::new(state), - }))); + })); + self.nested.push((path, handler)); + + self + } + + /// Create nested scope. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{App, HttpRequest}; + /// + /// struct AppState; + /// + /// fn index(req: HttpRequest) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::with_state(AppState) + /// .scope("/app", |scope| { + /// scope.nested("/v1", |scope| { + /// scope.resource("/test1", |r| r.f(index)) + /// }) + /// }); + /// } + /// ``` + pub fn nested(mut self, path: &str, f: F) -> Scope + where + F: FnOnce(Scope) -> Scope, + { + let scope = Scope { + nested: Vec::new(), + resources: Rc::new(Vec::new()), + middlewares: Rc::new(Vec::new()), + default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), + }; + let scope = f(scope); + + let mut path = path.trim().trim_right_matches('/').to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/') + } + + self.nested + .push((path, UnsafeCell::new(Box::new(scope)))); self } @@ -249,24 +297,46 @@ impl RouteHandler for Scope { } } - // nested scope - if let Some(ref handler) = self.handler { - let hnd: &mut RouteHandler<_> = unsafe { (&mut *(handler.get())).as_mut() }; - hnd.handle(req) - } else { - // default handler - let default = unsafe { &mut *self.default.as_ref().get() }; - if self.middlewares.is_empty() { - default.handle(req, None) - } else { - Reply::async(Compose::new( - req, - Rc::clone(&self.middlewares), - Rc::clone(&self.default), - None, - )) + // nested scopes + for &(ref prefix, ref handler) in &self.nested { + let len = req.prefix_len() as usize; + let m = { + let path = &req.path()[len..]; + path.starts_with(prefix) + && (path.len() == prefix.len() + || path.split_at(prefix.len()).1.starts_with('/')) + }; + + if m { + let prefix_len = len + prefix.len(); + let path: &'static str = + unsafe { &*(&req.path()[prefix_len..] as *const _) }; + + req.set_prefix_len(prefix_len as u16); + if path.is_empty() { + req.match_info_mut().set("tail", "/"); + } else { + req.match_info_mut().set("tail", path); + } + + let hnd: &mut RouteHandler<_> = + unsafe { (&mut *(handler.get())).as_mut() }; + return hnd.handle(req); } } + + // default handler + let default = unsafe { &mut *self.default.as_ref().get() }; + if self.middlewares.is_empty() { + default.handle(req, None) + } else { + Reply::async(Compose::new( + req, + Rc::clone(&self.middlewares), + Rc::clone(&self.default), + None, + )) + } } } @@ -646,13 +716,31 @@ mod tests { let mut app = App::new() .scope("/app", |scope| { - scope.with_state(State, |scope| { + scope.with_state("/t1", State, |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) }) }) .finish(); - let req = TestRequest::with_uri("/app/path1").finish(); + let req = TestRequest::with_uri("/app/t1/path1").finish(); + let resp = app.run(req); + assert_eq!( + resp.as_response().unwrap().status(), + StatusCode::CREATED + ); + } + + #[test] + fn test_nested_scope() { + let mut app = App::new() + .scope("/app", |scope| { + scope.nested("/t1", |scope| { + scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/t1/path1").finish(); let resp = app.run(req); assert_eq!( resp.as_response().unwrap().status(), diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 7ff6e8b9..375923d0 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -9,6 +9,7 @@ use super::settings::WorkerSettings; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HttpTryFrom, Method, Uri, Version}; +use httprequest::MessageFlags; use uri::Url; const MAX_BUFFER_SIZE: usize = 131_072; @@ -128,7 +129,9 @@ impl H1Decoder { let msg = settings.get_http_message(); { let msg_mut = msg.get_mut(); - msg_mut.keep_alive = version != Version::HTTP_10; + msg_mut + .flags + .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); for header in headers[..headers_len].iter() { if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) { @@ -165,7 +168,7 @@ impl H1Decoder { } // connection keep-alive state header::CONNECTION => { - msg_mut.keep_alive = if let Ok(conn) = value.to_str() { + let ka = if let Ok(conn) = value.to_str() { if version == Version::HTTP_10 && conn.contains("keep-alive") { @@ -178,6 +181,7 @@ impl H1Decoder { } else { false }; + msg_mut.flags.set(MessageFlags::KEEPALIVE, ka); } _ => (), } diff --git a/tests/test_client.rs b/tests/test_client.rs index 3c4c8586..09465684 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -1,3 +1,4 @@ +#![allow(deprecated)] extern crate actix; extern crate actix_web; extern crate bytes; From d9a4fadaae135ed443cdd2eaa5802bde2e174b16 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 1 May 2018 09:05:50 -0700 Subject: [PATCH 0163/1635] make HttpRequest::extensions() readonly --- MIGRATION.md | 3 +++ src/httprequest.rs | 9 +-------- src/middleware/identity.rs | 6 +++--- src/middleware/logger.rs | 2 +- src/middleware/session.rs | 6 +++--- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 4e8cad36..45c35d7d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -6,6 +6,9 @@ * `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. +* `HttpRequest::extensions()` returns read only reference to the request's Extension + `HttpRequest::extensions_mut()` returns mutable reference. + ## Migration from 0.4 to 0.5 diff --git a/src/httprequest.rs b/src/httprequest.rs index 5b3c6619..ab2c99fe 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -195,17 +195,10 @@ impl HttpRequest { /// Request extensions #[inline] - pub fn extensions(&mut self) -> &mut Extensions { + pub fn extensions(&self) -> &Extensions { &mut self.as_mut().extensions } - /// Request extensions - #[inline] - #[doc(hidden)] - pub fn extensions_ro(&self) -> &Extensions { - &self.as_ref().extensions - } - /// Mutable refernece to a the request's extensions #[inline] pub fn extensions_mut(&mut self) -> &mut Extensions { diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 50df4df4..ce18e858 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -100,7 +100,7 @@ pub trait RequestIdentity { impl RequestIdentity for HttpRequest { fn identity(&self) -> Option<&str> { - if let Some(id) = self.extensions_ro().get::() { + if let Some(id) = self.extensions().get::() { return id.0.identity(); } None @@ -183,7 +183,7 @@ impl> Middleware for IdentityService { .from_request(&mut req) .then(move |res| match res { Ok(id) => { - req.extensions().insert(IdentityBox(Box::new(id))); + req.extensions_mut().insert(IdentityBox(Box::new(id))); FutOk(None) } Err(err) => FutErr(err), @@ -194,7 +194,7 @@ impl> Middleware for IdentityService { fn response( &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { - if let Some(mut id) = req.extensions().remove::() { + if let Some(mut id) = req.extensions_mut().remove::() { id.0.write(resp) } else { Ok(Response::Done(resp)) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 28964718..adfc3d2b 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -116,7 +116,7 @@ impl Logger { impl Middleware for Logger { fn start(&self, req: &mut HttpRequest) -> Result { - req.extensions().insert(StartTime(time::now())); + req.extensions_mut().insert(StartTime(time::now())); Ok(Started::Done) } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 4e2f9976..c0c36631 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -106,7 +106,7 @@ pub trait RequestSession { impl RequestSession for HttpRequest { fn session(&mut self) -> Session { - if let Some(s_impl) = self.extensions().get_mut::>() { + if let Some(s_impl) = self.extensions_mut().get_mut::>() { if let Some(s) = Arc::get_mut(s_impl) { return Session(s.0.as_mut()); } @@ -206,7 +206,7 @@ impl> Middleware for SessionStorage { .from_request(&mut req) .then(move |res| match res { Ok(sess) => { - req.extensions() + req.extensions_mut() .insert(Arc::new(SessionImplBox(Box::new(sess)))); FutOk(None) } @@ -218,7 +218,7 @@ impl> Middleware for SessionStorage { fn response( &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { - if let Some(s_box) = req.extensions().remove::>() { + if let Some(s_box) = req.extensions_mut().remove::>() { s_box.0.write(resp) } else { Ok(Response::Done(resp)) From 9b6343d54bdad8739320832eb39bacea35cb2d6c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 1 May 2018 09:40:23 -0700 Subject: [PATCH 0164/1635] refactor session impl --- src/middleware/session.rs | 100 ++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index c0c36631..13948ac2 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -63,6 +63,7 @@ //! let _ = sys.run(); //! } //! ``` +use std::cell::RefCell; use std::collections::HashMap; use std::marker::PhantomData; use std::rc::Rc; @@ -72,7 +73,8 @@ use cookie::{Cookie, CookieJar, Key}; use futures::future::{err as FutErr, ok as FutOk, FutureResult}; use futures::Future; use http::header::{self, HeaderValue}; -use serde::{Deserialize, Serialize}; +use serde::de::DeserializeOwned; +use serde::Serialize; use serde_json; use serde_json::error::Error as JsonError; use time::Duration; @@ -101,17 +103,15 @@ use middleware::{Middleware, Response, Started}; /// # fn main() {} /// ``` pub trait RequestSession { - fn session(&mut self) -> Session; + fn session(&self) -> Session; } impl RequestSession for HttpRequest { - fn session(&mut self) -> Session { - if let Some(s_impl) = self.extensions_mut().get_mut::>() { - if let Some(s) = Arc::get_mut(s_impl) { - return Session(s.0.as_mut()); - } + fn session(&self) -> Session { + if let Some(s_impl) = self.extensions().get::>() { + return Session(SessionInner::Session(Arc::clone(&s_impl))); } - Session(unsafe { &mut DUMMY }) + Session(SessionInner::None) } } @@ -137,41 +137,65 @@ impl RequestSession for HttpRequest { /// } /// # fn main() {} /// ``` -pub struct Session<'a>(&'a mut SessionImpl); +pub struct Session(SessionInner); -impl<'a> Session<'a> { +enum SessionInner { + Session(Arc), + None, +} + +impl Session { /// Get a `value` from the session. - pub fn get>(&'a self, key: &str) -> Result> { - if let Some(s) = self.0.get(key) { - Ok(Some(serde_json::from_str(s)?)) - } else { - Ok(None) + pub fn get(&self, key: &str) -> Result> { + match self.0 { + SessionInner::Session(ref sess) => { + if let Some(s) = sess.as_ref().0.borrow().get(key) { + Ok(Some(serde_json::from_str(s)?)) + } else { + Ok(None) + } + } + SessionInner::None => Ok(None), } } /// Set a `value` from the session. - pub fn set(&mut self, key: &str, value: T) -> Result<()> { - self.0.set(key, serde_json::to_string(&value)?); - Ok(()) + pub fn set(&self, key: &str, value: T) -> Result<()> { + match self.0 { + SessionInner::Session(ref sess) => { + sess.as_ref() + .0 + .borrow_mut() + .set(key, serde_json::to_string(&value)?); + Ok(()) + } + SessionInner::None => Ok(()), + } } /// Remove value from the session. - pub fn remove(&'a mut self, key: &str) { - self.0.remove(key) + pub fn remove(&self, key: &str) { + match self.0 { + SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key), + SessionInner::None => (), + } } /// Clear the session. - pub fn clear(&'a mut self) { - self.0.clear() + pub fn clear(&self) { + match self.0 { + SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(), + SessionInner::None => (), + } } } -struct SessionImplBox(Box); +struct SessionImplCell(RefCell>); #[doc(hidden)] -unsafe impl Send for SessionImplBox {} +unsafe impl Send for SessionImplCell {} #[doc(hidden)] -unsafe impl Sync for SessionImplBox {} +unsafe impl Sync for SessionImplCell {} /// Session storage middleware /// @@ -206,8 +230,9 @@ impl> Middleware for SessionStorage { .from_request(&mut req) .then(move |res| match res { Ok(sess) => { - req.extensions_mut() - .insert(Arc::new(SessionImplBox(Box::new(sess)))); + req.extensions_mut().insert(Arc::new(SessionImplCell( + RefCell::new(Box::new(sess)), + ))); FutOk(None) } Err(err) => FutErr(err), @@ -218,8 +243,8 @@ impl> Middleware for SessionStorage { fn response( &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { - if let Some(s_box) = req.extensions_mut().remove::>() { - s_box.0.write(resp) + if let Some(s_box) = req.extensions_mut().remove::>() { + s_box.0.borrow_mut().write(resp) } else { Ok(Response::Done(resp)) } @@ -251,23 +276,6 @@ pub trait SessionBackend: Sized + 'static { fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; } -/// Dummy session impl, does not do anything -struct DummySessionImpl; - -static mut DUMMY: DummySessionImpl = DummySessionImpl; - -impl SessionImpl for DummySessionImpl { - fn get(&self, _: &str) -> Option<&str> { - None - } - fn set(&mut self, _: &str, _: String) {} - fn remove(&mut self, _: &str) {} - fn clear(&mut self) {} - fn write(&self, resp: HttpResponse) -> Result { - Ok(Response::Done(resp)) - } -} - /// Session that uses signed cookies as session storage pub struct CookieSession { changed: bool, From e01102bda253bb7dc57fa57a354dd17048bcb959 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 1 May 2018 11:45:46 -0700 Subject: [PATCH 0165/1635] no need for mut --- src/httprequest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index ab2c99fe..3b65d718 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -196,7 +196,7 @@ impl HttpRequest { /// Request extensions #[inline] pub fn extensions(&self) -> &Extensions { - &mut self.as_mut().extensions + &self.as_ref().extensions } /// Mutable refernece to a the request's extensions From 195246573e64d26f9b4e95eb626f838a85acd031 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 1 May 2018 13:15:35 -0700 Subject: [PATCH 0166/1635] rename threads to workers --- MIGRATION.md | 2 ++ src/server/srv.rs | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 45c35d7d..24ccdb3a 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -3,6 +3,8 @@ * `ws::Message::Close` now includes optional close reason. `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. +* `HttpServer::threads()` renamed to `HttpServer::workers()`. + * `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. diff --git a/src/server/srv.rs b/src/server/srv.rs index 4ba263e7..df397841 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -109,11 +109,17 @@ where /// /// By default http server uses number of available logical cpu as threads /// count. - pub fn threads(mut self, num: usize) -> Self { + pub fn workers(mut self, num: usize) -> Self { self.threads = num; self } + #[doc(hidden)] + #[deprecated(since = "0.6.0", note = "please use `HttpServer::workers()` instead")] + pub fn threads(self, num: usize) -> Self { + self.workers(num) + } + /// Set the maximum number of pending connections. /// /// This refers to the number of clients that can be waiting to be served. From 8d65468c58fcc85aab1ff0d7352839ee9530b02e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 1 May 2018 17:19:15 -0700 Subject: [PATCH 0167/1635] refactor FromRequest trait --- MIGRATION.md | 4 + src/application.rs | 102 ++++++----------- src/extractor.rs | 157 +++++++++---------------- src/fs.rs | 14 +-- src/handler.rs | 114 +++++++++++------- src/helpers.rs | 8 +- src/httprequest.rs | 22 ++-- src/json.rs | 18 +-- src/pipeline.rs | 8 +- src/resource.rs | 2 +- src/route.rs | 9 +- src/scope.rs | 29 ++--- src/test.rs | 1 + src/with.rs | 279 ++++++++++++++++++++++++++++----------------- 14 files changed, 384 insertions(+), 383 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 24ccdb3a..f343028b 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -11,6 +11,10 @@ * `HttpRequest::extensions()` returns read only reference to the request's Extension `HttpRequest::extensions_mut()` returns mutable reference. +* `FromRequest::from_request()` accepts mutable reference to a request + +* `FromRequest::Result` has to implement `Into>` + ## Migration from 0.4 to 0.5 diff --git a/src/application.rs b/src/application.rs index bd6c4d00..dbb31a56 100644 --- a/src/application.rs +++ b/src/application.rs @@ -6,6 +6,7 @@ use handler::{FromRequest, Handler, Reply, Responder, RouteHandler, WrapHandler} use header::ContentEncoding; use http::Method; use httprequest::HttpRequest; +use httpresponse::HttpResponse; use middleware::Middleware; use pipeline::{HandlerType, Pipeline, PipelineHandler}; use resource::ResourceHandler; @@ -36,7 +37,9 @@ impl PipelineHandler for Inner { self.encoding } - fn handle(&mut self, req: HttpRequest, htype: HandlerType) -> Reply { + fn handle( + &mut self, req: HttpRequest, htype: HandlerType, + ) -> Reply { match htype { HandlerType::Normal(idx) => { self.resources[idx].handle(req, Some(&mut self.default)) @@ -87,7 +90,7 @@ impl HttpApplication { } #[cfg(test)] - pub(crate) fn run(&mut self, mut req: HttpRequest) -> Reply { + pub(crate) fn run(&mut self, mut req: HttpRequest) -> Reply { let tp = self.get_handler(&mut req); unsafe { &mut *self.inner.get() }.handle(req, tp) } @@ -669,24 +672,18 @@ mod tests { let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let mut app = App::new() .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); let req = TestRequest::with_uri("/blah").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::METHOD_NOT_ALLOWED - ); + assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); } #[test] @@ -706,7 +703,7 @@ mod tests { let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); } #[test] @@ -740,29 +737,23 @@ mod tests { let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/test/").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().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); + assert_eq!(resp.as_msg().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 - ); + assert_eq!(resp.as_msg().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 - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] @@ -773,29 +764,23 @@ mod tests { let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/test/").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().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); + assert_eq!(resp.as_msg().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 - ); + assert_eq!(resp.as_msg().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 - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] @@ -807,29 +792,23 @@ mod tests { let req = TestRequest::with_uri("/prefix/test").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/prefix/test/").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/prefix/test/app").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/prefix/testapp").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/prefix/blah").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] @@ -847,25 +826,19 @@ mod tests { .method(Method::GET) .finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::CREATED - ); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); let req = TestRequest::with_uri("/test") .method(Method::HEAD) .finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] @@ -877,36 +850,27 @@ mod tests { let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/test").finish(); let resp = app.run(req.clone()); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(req.prefix_len(), 9); let req = TestRequest::with_uri("/app/test/").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().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); + assert_eq!(resp.as_msg().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 - ); + assert_eq!(resp.as_msg().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 - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } } diff --git a/src/extractor.rs b/src/extractor.rs index 1aef7ac5..4ed88489 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -4,14 +4,14 @@ use std::str; use bytes::Bytes; use encoding::all::UTF_8; use encoding::types::{DecoderTrap, Encoding}; -use futures::future::{result, Future, FutureResult}; +use futures::future::Future; use mime::Mime; use serde::de::{self, DeserializeOwned}; use serde_urlencoded; use de::PathDeserializer; use error::{Error, ErrorBadRequest}; -use handler::{Either, FromRequest}; +use handler::FromRequest; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; @@ -102,16 +102,14 @@ where S: 'static, { type Config = (); - type Result = FutureResult; + type Result = Result; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &mut HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); - result( - de::Deserialize::deserialize(PathDeserializer::new(&req)) - .map_err(|e| e.into()) - .map(|inner| Path { inner }), - ) + de::Deserialize::deserialize(PathDeserializer::new(&req)) + .map_err(|e| e.into()) + .map(|inner| Path { inner }) } } @@ -172,16 +170,14 @@ where S: 'static, { type Config = (); - type Result = FutureResult; + type Result = Result; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &mut HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); - result( - serde_urlencoded::from_str::(req.query_string()) - .map_err(|e| e.into()) - .map(Query), - ) + serde_urlencoded::from_str::(req.query_string()) + .map_err(|e| e.into()) + .map(Query) } } @@ -245,7 +241,7 @@ where type Result = Box>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result { Box::new( UrlEncoded::new(req.clone()) .limit(cfg.limit) @@ -327,17 +323,14 @@ impl Default for FormConfig { /// ``` impl FromRequest for Bytes { type Config = PayloadConfig; - type Result = - Either, Box>>; + type Result = Result>, Error>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result { // check content-type - if let Err(e) = cfg.check_mimetype(req) { - return Either::A(result(Err(e))); - } + cfg.check_mimetype(req)?; - Either::B(Box::new( + Ok(Box::new( MessageBody::new(req.clone()) .limit(cfg.limit) .from_err(), @@ -374,27 +367,17 @@ impl FromRequest for Bytes { /// ``` impl FromRequest for String { type Config = PayloadConfig; - type Result = - Either, Box>>; + type Result = Result>, Error>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result { // check content-type - if let Err(e) = cfg.check_mimetype(req) { - return Either::A(result(Err(e))); - } + cfg.check_mimetype(req)?; // check charset - let encoding = match req.encoding() { - Err(_) => { - return Either::A(result(Err(ErrorBadRequest( - "Unknown request charset", - )))) - } - Ok(encoding) => encoding, - }; + let encoding = req.encoding()?; - Either::B(Box::new( + Ok(Box::new( MessageBody::new(req.clone()) .limit(cfg.limit) .from_err() @@ -488,7 +471,11 @@ mod tests { req.payload_mut() .unread_data(Bytes::from_static(b"hello=world")); - match Bytes::from_request(&req, &cfg).poll().unwrap() { + match Bytes::from_request(&mut req, &cfg) + .unwrap() + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s, Bytes::from_static(b"hello=world")); } @@ -503,7 +490,11 @@ mod tests { req.payload_mut() .unread_data(Bytes::from_static(b"hello=world")); - match String::from_request(&req, &cfg).poll().unwrap() { + match String::from_request(&mut req, &cfg) + .unwrap() + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s, "hello=world"); } @@ -523,7 +514,10 @@ mod tests { let mut cfg = FormConfig::default(); cfg.limit(4096); - match Form::::from_request(&req, &cfg).poll().unwrap() { + match Form::::from_request(&mut req, &cfg) + .poll() + .unwrap() + { Async::Ready(s) => { assert_eq!(s.hello, "world"); } @@ -580,69 +574,31 @@ mod tests { let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req, &()) - .poll() - .unwrap() - { - Async::Ready(s) => { - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - } - _ => unreachable!(), - } + let s = Path::::from_request(&mut req, &()).unwrap(); + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); - match Path::<(String, String)>::from_request(&req, &()) - .poll() - .unwrap() - { - Async::Ready(s) => { - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); - } - _ => unreachable!(), - } + let s = Path::<(String, String)>::from_request(&mut req, &()).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); - match Query::::from_request(&req, &()).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.id, "test"); - } - _ => unreachable!(), - } + let s = Query::::from_request(&mut req, &()).unwrap(); + assert_eq!(s.id, "test"); let mut req = TestRequest::with_uri("/name/32/").finish(); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req, &()).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - } - _ => unreachable!(), - } + let s = Path::::from_request(&mut req, &()).unwrap(); + assert_eq!(s.as_ref().key, "name"); + assert_eq!(s.value, 32); - match Path::<(String, u8)>::from_request(&req, &()) - .poll() - .unwrap() - { - Async::Ready(s) => { - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - } - _ => unreachable!(), - } + let s = Path::<(String, u8)>::from_request(&mut req, &()).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); - match Path::>::from_request(&req, &()) - .poll() - .unwrap() - { - Async::Ready(s) => { - assert_eq!( - s.into_inner(), - vec!["name".to_owned(), "32".to_owned()] - ); - } - _ => unreachable!(), - } + let res = Path::>::from_request(&mut req, &()).unwrap(); + assert_eq!(res[0], "name".to_owned()); + assert_eq!(res[1], "32".to_owned()); } #[test] @@ -656,11 +612,6 @@ mod tests { let mut req = TestRequest::with_uri("/32/").finish(); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req, &()).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.into_inner(), 32); - } - _ => unreachable!(), - } + assert_eq!(*Path::::from_request(&mut req, &()).unwrap(), 32); } } diff --git a/src/fs.rs b/src/fs.rs index 2aa1b979..007e97f6 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -565,7 +565,7 @@ impl StaticFiles { } impl Handler for StaticFiles { - type Result = Result; + type Result = Result, Error>; fn handle(&mut self, req: HttpRequest) -> Self::Result { if !self.accessible { @@ -755,7 +755,7 @@ mod tests { let resp = st.handle(HttpRequest::default()) .respond_to(HttpRequest::default()) .unwrap(); - let resp = resp.as_response().expect("HTTP Response"); + let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); st.accessible = true; @@ -763,7 +763,7 @@ mod tests { let resp = st.handle(HttpRequest::default()) .respond_to(HttpRequest::default()) .unwrap(); - let resp = resp.as_response().expect("HTTP Response"); + let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let mut req = HttpRequest::default(); @@ -773,7 +773,7 @@ mod tests { let resp = st.handle(req) .respond_to(HttpRequest::default()) .unwrap(); - let resp = resp.as_response().expect("HTTP Response"); + let resp = resp.as_msg(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8" @@ -791,7 +791,7 @@ mod tests { let resp = st.handle(req) .respond_to(HttpRequest::default()) .unwrap(); - let resp = resp.as_response().expect("HTTP Response"); + let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers().get(header::LOCATION).unwrap(), @@ -804,7 +804,7 @@ mod tests { let resp = st.handle(req) .respond_to(HttpRequest::default()) .unwrap(); - let resp = resp.as_response().expect("HTTP Response"); + let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers().get(header::LOCATION).unwrap(), @@ -821,7 +821,7 @@ mod tests { let resp = st.handle(req) .respond_to(HttpRequest::default()) .unwrap(); - let resp = resp.as_response().expect("HTTP Response"); + let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers().get(header::LOCATION).unwrap(), diff --git a/src/handler.rs b/src/handler.rs index 2304687d..dae80e9f 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,4 +1,4 @@ -use futures::future::{err, ok, Future, FutureResult}; +use futures::future::{err, ok, Future}; use futures::Poll; use std::marker::PhantomData; use std::ops::Deref; @@ -22,7 +22,7 @@ pub trait Handler: 'static { /// 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; + type Item: Into>; /// The associated error which can be returned. type Error: Into; @@ -42,10 +42,10 @@ where type Config: Default; /// Future that resolves to a Self - type Result: Future; + type Result: Into>; /// Convert request to a Self - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; + fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result; } /// Combines two different responder types into a single type @@ -88,10 +88,10 @@ where A: Responder, B: Responder, { - type Item = Reply; + type Item = Reply; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: HttpRequest) -> Result, Error> { match self { Either::A(a) => match a.respond_to(req) { Ok(val) => Ok(val.into()), @@ -177,66 +177,86 @@ where } } -/// Represents response process. -pub struct Reply(ReplyItem); +/// Represents reply process. +/// +/// Reply could be in tree different forms. +/// * Message(T) - ready item +/// * Error(Error) - error happen during reply process +/// * Future - reply process completes in the future +pub struct Reply(ReplyItem); -pub(crate) enum ReplyItem { - Message(HttpResponse), - Future(Box>), +pub(crate) enum ReplyItem { + Error(Error), + Message(T), + Future(Box>), } -impl Reply { +impl Reply { /// Create async response #[inline] - pub fn async(fut: F) -> Reply + pub fn async(fut: F) -> Reply where - F: Future + 'static, + F: Future + 'static, { Reply(ReplyItem::Future(Box::new(fut))) } /// Send response #[inline] - pub fn response>(response: R) -> Reply { + pub fn response>(response: R) -> Reply { Reply(ReplyItem::Message(response.into())) } + /// Send error #[inline] - pub(crate) fn into(self) -> ReplyItem { + pub fn error>(err: R) -> Reply { + Reply(ReplyItem::Error(err.into())) + } + + #[inline] + pub(crate) fn into(self) -> ReplyItem { self.0 } #[cfg(test)] - pub(crate) fn as_response(&self) -> Option<&HttpResponse> { + pub(crate) fn as_msg(&self) -> &T { match self.0 { - ReplyItem::Message(ref resp) => Some(resp), + ReplyItem::Message(ref resp) => resp, + _ => panic!(), + } + } + + #[cfg(test)] + pub(crate) fn as_err(&self) -> Option<&Error> { + match self.0 { + ReplyItem::Error(ref err) => Some(err), _ => None, } } } -impl Responder for Reply { - type Item = Reply; +impl Responder for Reply { + type Item = Reply; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result, Error> { Ok(self) } } impl Responder for HttpResponse { - type Item = Reply; + type Item = Reply; type Error = Error; #[inline] - fn respond_to(self, _: HttpRequest) -> Result { + fn respond_to(self, _: HttpRequest) -> Result, Error> { Ok(Reply(ReplyItem::Message(self))) } } -impl From for Reply { +impl From for Reply { #[inline] - fn from(resp: HttpResponse) -> Reply { + fn from(resp: T) -> Reply { Reply(ReplyItem::Message(resp)) } } @@ -256,29 +276,41 @@ impl> Responder for Result { } } -impl> From> for Reply { +impl> From, E>> for Reply { #[inline] - fn from(res: Result) -> Self { + fn from(res: Result, E>) -> Self { match res { Ok(val) => val, - Err(err) => Reply(ReplyItem::Message(err.into().into())), + Err(err) => Reply(ReplyItem::Error(err.into())), } } } -impl> From> for Reply { +impl> From> for Reply { #[inline] - fn from(res: Result) -> Self { + fn from(res: Result) -> Self { match res { Ok(val) => Reply(ReplyItem::Message(val)), - Err(err) => Reply(ReplyItem::Message(err.into().into())), + Err(err) => Reply(ReplyItem::Error(err.into())), } } } -impl From>> for Reply { +impl> From>, E>> + for Reply +{ #[inline] - fn from(fut: Box>) -> Reply { + fn from(res: Result>, E>) -> Self { + match res { + Ok(fut) => Reply(ReplyItem::Future(fut)), + Err(err) => Reply(ReplyItem::Error(err.into())), + } + } +} + +impl From>> for Reply { + #[inline] + fn from(fut: Box>) -> Reply { Reply(ReplyItem::Future(fut)) } } @@ -291,11 +323,11 @@ where I: Responder + 'static, E: Into + 'static, { - type Item = Reply; + type Item = Reply; type Error = Error; #[inline] - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: HttpRequest) -> Result, Error> { let fut = self.map_err(|e| e.into()) .then(move |r| match r.respond_to(req) { Ok(reply) => match reply.into().0 { @@ -310,7 +342,7 @@ where /// Trait defines object that could be registered as resource route pub(crate) trait RouteHandler: 'static { - fn handle(&mut self, req: HttpRequest) -> Reply; + fn handle(&mut self, req: HttpRequest) -> Reply; } /// Route handler wrapper for Handler @@ -344,7 +376,7 @@ where R: Responder + 'static, S: 'static, { - fn handle(&mut self, req: HttpRequest) -> Reply { + fn handle(&mut self, req: HttpRequest) -> Reply { let req2 = req.drop_state(); match self.h.handle(req).respond_to(req2) { Ok(reply) => reply.into(), @@ -390,7 +422,7 @@ where E: Into + 'static, S: 'static, { - fn handle(&mut self, req: HttpRequest) -> Reply { + fn handle(&mut self, req: HttpRequest) -> Reply { let req2 = req.drop_state(); let fut = (self.h)(req).map_err(|e| e.into()).then(move |r| { match r.respond_to(req2) { @@ -449,10 +481,10 @@ impl Deref for State { impl FromRequest for State { type Config = (); - type Result = FutureResult; + type Result = State; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - ok(State(req.clone())) + fn from_request(req: &mut HttpRequest, _: &Self::Config) -> Self::Result { + State(req.clone()).into() } } diff --git a/src/helpers.rs b/src/helpers.rs index fda28f38..9db0e863 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -217,7 +217,7 @@ mod tests { for (path, target, code) in params { let req = app.prepare_request(TestRequest::with_uri(path).finish()); let resp = app.run(req); - let r = resp.as_response().unwrap(); + let r = resp.as_msg(); assert_eq!(r.status(), code); if !target.is_empty() { assert_eq!( @@ -260,7 +260,7 @@ mod tests { for (path, code) in params { let req = app.prepare_request(TestRequest::with_uri(path).finish()); let resp = app.run(req); - let r = resp.as_response().unwrap(); + let r = resp.as_msg(); assert_eq!(r.status(), code); } } @@ -351,7 +351,7 @@ mod tests { for (path, target, code) in params { let req = app.prepare_request(TestRequest::with_uri(path).finish()); let resp = app.run(req); - let r = resp.as_response().unwrap(); + let r = resp.as_msg(); assert_eq!(r.status(), code); if !target.is_empty() { assert_eq!( @@ -535,7 +535,7 @@ mod tests { for (path, target, code) in params { let req = app.prepare_request(TestRequest::with_uri(path).finish()); let resp = app.run(req); - let r = resp.as_response().unwrap(); + let r = resp.as_msg(); assert_eq!(r.status(), code); if !target.is_empty() { assert_eq!( diff --git a/src/httprequest.rs b/src/httprequest.rs index 3b65d718..2225b4bb 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,20 +1,20 @@ //! HTTP Request message related code. #![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))] -use bytes::Bytes; -use cookie::Cookie; -use failure; -use futures::future::{result, FutureResult}; -use futures::{Async, Poll, Stream}; -use futures_cpupool::CpuPool; -use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version}; use std::net::SocketAddr; use std::rc::Rc; use std::{cmp, fmt, io, mem, str}; + +use bytes::Bytes; +use cookie::Cookie; +use failure; +use futures::{Async, Poll, Stream}; +use futures_cpupool::CpuPool; +use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version}; use tokio_io::AsyncRead; use url::{form_urlencoded, Url}; use body::Body; -use error::{CookieParseError, Error, PayloadError, UrlGenerationError}; +use error::{CookieParseError, PayloadError, UrlGenerationError}; use handler::FromRequest; use httpmessage::HttpMessage; use httpresponse::{HttpResponse, HttpResponseBuilder}; @@ -502,11 +502,11 @@ impl Clone for HttpRequest { impl FromRequest for HttpRequest { type Config = (); - type Result = FutureResult; + type Result = Self; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - result(Ok(req.clone())) + fn from_request(req: &mut HttpRequest, _: &Self::Config) -> Self::Result { + req.clone() } } diff --git a/src/json.rs b/src/json.rs index ec3ad7ce..24d1c9c4 100644 --- a/src/json.rs +++ b/src/json.rs @@ -136,7 +136,7 @@ where type Result = Box>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result { let req = req.clone(); let err = Rc::clone(&cfg.ehandler); Box::new( @@ -417,13 +417,7 @@ mod tests { let mut handler = With::new(|data: Json| data, cfg); let req = HttpRequest::default(); - let err = handler - .handle(req) - .as_response() - .unwrap() - .error() - .is_some(); - assert!(err); + assert!(handler.handle(req).as_err().is_some()); let mut req = HttpRequest::default(); req.headers_mut().insert( @@ -436,12 +430,6 @@ mod tests { ); req.payload_mut() .unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); - let ok = handler - .handle(req) - .as_response() - .unwrap() - .error() - .is_none(); - assert!(ok) + assert!(handler.handle(req).as_err().is_none()) } } diff --git a/src/pipeline.rs b/src/pipeline.rs index aefd979d..cea02073 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -28,7 +28,8 @@ pub(crate) enum HandlerType { pub(crate) trait PipelineHandler { fn encoding(&self) -> ContentEncoding; - fn handle(&mut self, req: HttpRequest, htype: HandlerType) -> Reply; + fn handle(&mut self, req: HttpRequest, htype: HandlerType) + -> Reply; } pub(crate) struct Pipeline(PipelineInfo, PipelineState); @@ -319,8 +320,11 @@ struct WaitingResponse { impl WaitingResponse { #[inline] - fn init(info: &mut PipelineInfo, reply: Reply) -> PipelineState { + fn init( + info: &mut PipelineInfo, reply: Reply, + ) -> PipelineState { match reply.into() { + ReplyItem::Error(err) => RunMiddlewares::init(info, err.into()), ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), ReplyItem::Future(fut) => PipelineState::Handler(WaitingResponse { fut, diff --git a/src/resource.rs b/src/resource.rs index e4dfbb2d..e78a4546 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -198,7 +198,7 @@ impl ResourceHandler { pub(crate) fn handle( &mut self, mut req: HttpRequest, default: Option<&mut ResourceHandler>, - ) -> Reply { + ) -> Reply { for route in &mut self.routes { if route.check(&mut req) { return if self.middlewares.is_empty() { diff --git a/src/route.rs b/src/route.rs index 6c4ba419..7d39cc10 100644 --- a/src/route.rs +++ b/src/route.rs @@ -45,14 +45,14 @@ impl Route { } #[inline] - pub(crate) fn handle(&mut self, req: HttpRequest) -> Reply { + pub(crate) fn handle(&mut self, req: HttpRequest) -> Reply { self.handler.handle(req) } #[inline] pub(crate) fn compose( &mut self, req: HttpRequest, mws: Rc>>>, - ) -> Reply { + ) -> Reply { Reply::async(Compose::new(req, mws, self.handler.clone())) } @@ -243,7 +243,7 @@ impl InnerHandler { } #[inline] - pub fn handle(&self, req: HttpRequest) -> Reply { + pub fn handle(&self, req: HttpRequest) -> Reply { // reason: handler is unique per thread, handler get called from async code only let h = unsafe { &mut *self.0.as_ref().get() }; h.handle(req) @@ -415,8 +415,9 @@ struct WaitingResponse { impl WaitingResponse { #[inline] - fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { + fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { match reply.into() { + ReplyItem::Error(err) => RunMiddlewares::init(info, err.into()), ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse { fut, diff --git a/src/scope.rs b/src/scope.rs index ff630e68..38e6c062 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -274,7 +274,7 @@ impl Scope { } impl RouteHandler for Scope { - fn handle(&mut self, mut req: HttpRequest) -> Reply { + fn handle(&mut self, mut req: HttpRequest) -> Reply { let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; let path = if path == "" { "/" } else { path }; @@ -346,7 +346,7 @@ struct Wrapper { } impl RouteHandler for Wrapper { - fn handle(&mut self, req: HttpRequest) -> Reply { + fn handle(&mut self, req: HttpRequest) -> Reply { self.scope .handle(req.change_state(Rc::clone(&self.state))) } @@ -521,9 +521,10 @@ struct WaitingResponse { impl WaitingResponse { #[inline] - fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { + fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { match reply.into() { ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), + ReplyItem::Error(err) => RunMiddlewares::init(info, err.into()), ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse { fut, _s: PhantomData, @@ -707,7 +708,7 @@ mod tests { let req = TestRequest::with_uri("/app/path1").finish(); let resp = app.run(req); - assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); + assert_eq!(resp.as_msg().status(), StatusCode::OK); } #[test] @@ -724,10 +725,7 @@ mod tests { let req = TestRequest::with_uri("/app/t1/path1").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::CREATED - ); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } #[test] @@ -742,10 +740,7 @@ mod tests { let req = TestRequest::with_uri("/app/t1/path1").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::CREATED - ); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } #[test] @@ -760,16 +755,10 @@ mod tests { let req = TestRequest::with_uri("/app/path2").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::BAD_REQUEST - ); + assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/path2").finish(); let resp = app.run(req); - assert_eq!( - resp.as_response().unwrap().status(), - StatusCode::NOT_FOUND - ); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } } diff --git a/src/test.rs b/src/test.rs index 13209f1d..0de7f634 100644 --- a/src/test.rs +++ b/src/test.rs @@ -602,6 +602,7 @@ impl TestRequest { match resp.respond_to(req.drop_state()) { Ok(resp) => match resp.into().into() { ReplyItem::Message(resp) => Ok(resp), + ReplyItem::Error(err) => Ok(err.into()), ReplyItem::Future(_) => panic!("Async handler is not supported."), }, Err(err) => Err(err), diff --git a/src/with.rs b/src/with.rs index a35d1a3b..a3b07ac0 100644 --- a/src/with.rs +++ b/src/with.rs @@ -82,7 +82,7 @@ where T: FromRequest + 'static, S: 'static, { - type Result = Reply; + type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { let mut fut = WithHandlerFut { @@ -97,7 +97,7 @@ where match fut.poll() { Ok(Async::Ready(resp)) => Reply::response(resp), Ok(Async::NotReady) => Reply::async(fut), - Err(e) => Reply::response(e), + Err(e) => Reply::error::(e), } } } @@ -134,14 +134,14 @@ where let item = if !self.started { self.started = true; - let mut fut = T::from_request(&self.req, self.cfg.as_ref()); - match fut.poll() { - Ok(Async::Ready(item)) => item, - Ok(Async::NotReady) => { - self.fut1 = Some(Box::new(fut)); - return Ok(Async::NotReady); + let reply = T::from_request(&mut self.req, self.cfg.as_ref()).into(); + match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.fut1 = Some(fut); + return self.poll(); } - Err(e) => return Err(e), } } else { match self.fut1.as_mut().unwrap().poll()? { @@ -157,6 +157,7 @@ where }; match item.into() { + ReplyItem::Error(err) => Err(err), ReplyItem::Message(resp) => Ok(Async::Ready(resp)), ReplyItem::Future(fut) => { self.fut2 = Some(fut); @@ -206,7 +207,7 @@ where T2: FromRequest + 'static, S: 'static, { - type Result = Reply; + type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { let mut fut = WithHandlerFut2 { @@ -265,52 +266,68 @@ where if !self.started { self.started = true; - let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); - match fut.poll() { - Ok(Async::Ready(item1)) => { - let mut fut = T2::from_request(&self.req, self.cfg2.as_ref()); - match fut.poll() { - Ok(Async::Ready(item2)) => { - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(item1, item2).respond_to(self.req.drop_state()) - { - Ok(item) => match item.into().into() { - ReplyItem::Message(resp) => { - return Ok(Async::Ready(resp)) - } - ReplyItem::Future(fut) => { - self.fut3 = Some(fut); - return self.poll(); - } - }, - Err(e) => return Err(e.into()), - } - } - Ok(Async::NotReady) => { - self.item = Some(item1); - self.fut2 = Some(Box::new(fut)); - return Ok(Async::NotReady); - } - Err(e) => return Err(e), + let reply = T1::from_request(&mut self.req, self.cfg1.as_ref()).into(); + let item1 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.fut1 = Some(fut); + return self.poll(); + } + }; + + let reply = T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); + let item2 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.item = Some(item1); + self.fut2 = Some(fut); + return self.poll(); + } + }; + + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; + match (*hnd)(item1, item2).respond_to(self.req.drop_state()) { + Ok(item) => match item.into().into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => { + self.fut3 = Some(fut); + return self.poll(); } - } - Ok(Async::NotReady) => { - self.fut1 = Some(Box::new(fut)); - return Ok(Async::NotReady); - } - Err(e) => return Err(e), + }, + Err(e) => return Err(e.into()), } } if self.fut1.is_some() { match self.fut1.as_mut().unwrap().poll()? { Async::Ready(item) => { - self.item = Some(item); - self.fut1.take(); - self.fut2 = Some(Box::new(T2::from_request( - &self.req, - self.cfg2.as_ref(), - ))); + let reply = + T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); + let item2 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.item = Some(item); + self.fut2 = Some(fut); + return self.poll(); + } + }; + + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; + match (*hnd)(item, item2).respond_to(self.req.drop_state()) { + Ok(item) => match item.into().into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => { + self.fut3 = Some(fut); + return self.poll(); + } + }, + Err(e) => return Err(e.into()), + } } Async::NotReady => return Ok(Async::NotReady), } @@ -330,6 +347,7 @@ where }; match item.into() { + ReplyItem::Error(err) => return Err(err), ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), ReplyItem::Future(fut) => self.fut3 = Some(fut), } @@ -387,7 +405,7 @@ where T3: 'static, S: 'static, { - type Result = Reply; + type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { let mut fut = WithHandlerFut3 { @@ -454,54 +472,50 @@ where if !self.started { self.started = true; - let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); - match fut.poll() { - Ok(Async::Ready(item1)) => { - let mut fut = T2::from_request(&self.req, self.cfg2.as_ref()); - match fut.poll() { - Ok(Async::Ready(item2)) => { - let mut fut = - T3::from_request(&self.req, self.cfg3.as_ref()); - match fut.poll() { - Ok(Async::Ready(item3)) => { - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(item1, item2, item3) - .respond_to(self.req.drop_state()) - { - Ok(item) => match item.into().into() { - ReplyItem::Message(resp) => { - return Ok(Async::Ready(resp)) - } - ReplyItem::Future(fut) => { - self.fut4 = Some(fut); - return self.poll(); - } - }, - Err(e) => return Err(e.into()), - } - } - Ok(Async::NotReady) => { - self.item1 = Some(item1); - self.item2 = Some(item2); - self.fut3 = Some(Box::new(fut)); - return Ok(Async::NotReady); - } - Err(e) => return Err(e), - } - } - Ok(Async::NotReady) => { - self.item1 = Some(item1); - self.fut2 = Some(Box::new(fut)); - return Ok(Async::NotReady); - } - Err(e) => return Err(e), + let reply = T1::from_request(&mut self.req, self.cfg1.as_ref()).into(); + let item1 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.fut1 = Some(fut); + return self.poll(); + } + }; + + let reply = T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); + let item2 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.item1 = Some(item1); + self.fut2 = Some(fut); + return self.poll(); + } + }; + + let reply = T3::from_request(&mut self.req, self.cfg3.as_ref()).into(); + let item3 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.item1 = Some(item1); + self.item2 = Some(item2); + self.fut3 = Some(fut); + return self.poll(); + } + }; + + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; + match (*hnd)(item1, item2, item3).respond_to(self.req.drop_state()) { + Ok(item) => match item.into().into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => { + self.fut4 = Some(fut); + return self.poll(); } - } - Ok(Async::NotReady) => { - self.fut1 = Some(Box::new(fut)); - return Ok(Async::NotReady); - } - Err(e) => return Err(e), + }, + Err(e) => return Err(e.into()), } } @@ -510,10 +524,42 @@ where Async::Ready(item) => { self.item1 = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new(T2::from_request( - &self.req, - self.cfg2.as_ref(), - ))); + let reply = + T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); + let item2 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.fut2 = Some(fut); + return self.poll(); + } + }; + + let reply = + T3::from_request(&mut self.req, self.cfg3.as_ref()).into(); + let item3 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.item2 = Some(item2); + self.fut3 = Some(fut); + return self.poll(); + } + }; + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; + match (*hnd)(self.item1.take().unwrap(), item2, item3) + .respond_to(self.req.drop_state()) + { + Ok(item) => match item.into().into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => { + self.fut4 = Some(fut); + return self.poll(); + } + }, + Err(e) => return Err(e.into()), + } } Async::NotReady => return Ok(Async::NotReady), } @@ -522,12 +568,32 @@ where if self.fut2.is_some() { match self.fut2.as_mut().unwrap().poll()? { Async::Ready(item) => { - self.item2 = Some(item); self.fut2.take(); - self.fut3 = Some(Box::new(T3::from_request( - &self.req, - self.cfg3.as_ref(), - ))); + let reply = + T3::from_request(&mut self.req, self.cfg3.as_ref()).into(); + let item3 = match reply.into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(msg) => msg, + ReplyItem::Future(fut) => { + self.item2 = Some(item); + self.fut3 = Some(fut); + return self.poll(); + } + }; + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; + match (*hnd)(self.item1.take().unwrap(), item, item3) + .respond_to(self.req.drop_state()) + { + Ok(item) => match item.into().into() { + ReplyItem::Error(err) => return Err(err), + ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => { + self.fut4 = Some(fut); + return self.poll(); + } + }, + Err(e) => return Err(e.into()), + } } Async::NotReady => return Ok(Async::NotReady), } @@ -550,6 +616,7 @@ where }; match item.into() { + ReplyItem::Error(err) => return Ok(Async::Ready(err.into())), ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), ReplyItem::Future(fut) => self.fut4 = Some(fut), } From a1958deaae7910f901d2ce4e6ecd8636869dbe15 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 1 May 2018 17:30:06 -0700 Subject: [PATCH 0168/1635] add impl Future for Reply --- src/handler.rs | 30 ++++++++++++++++++++++++++++-- src/pipeline.rs | 1 + src/route.rs | 1 + src/scope.rs | 1 + src/test.rs | 1 + src/with.rs | 18 ++++++++++++++++++ 6 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index dae80e9f..15f975b6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,8 +1,10 @@ -use futures::future::{err, ok, Future}; -use futures::Poll; use std::marker::PhantomData; +use std::mem; use std::ops::Deref; +use futures::future::{err, ok, Future}; +use futures::{Async, Poll}; + use error::Error; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -185,7 +187,31 @@ where /// * Future - reply process completes in the future pub struct Reply(ReplyItem); +impl Future for Reply { + type Item = T; + type Error = Error; + + fn poll(&mut self) -> Poll { + let item = mem::replace(&mut self.0, ReplyItem::None); + + match item { + ReplyItem::Error(err) => Err(err), + ReplyItem::Message(msg) => Ok(Async::Ready(msg)), + ReplyItem::Future(mut fut) => match fut.poll() { + Ok(Async::NotReady) => { + self.0 = ReplyItem::Future(fut); + Ok(Async::NotReady) + } + Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)), + Err(err) => Err(err), + }, + ReplyItem::None => panic!("use after resolve"), + } + } +} + pub(crate) enum ReplyItem { + None, Error(Error), Message(T), Future(Box>), diff --git a/src/pipeline.rs b/src/pipeline.rs index cea02073..398738e6 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -331,6 +331,7 @@ impl WaitingResponse { _s: PhantomData, _h: PhantomData, }), + ReplyItem::None => panic!("use after resolve"), } } diff --git a/src/route.rs b/src/route.rs index 7d39cc10..d5137c57 100644 --- a/src/route.rs +++ b/src/route.rs @@ -423,6 +423,7 @@ impl WaitingResponse { fut, _s: PhantomData, }), + ReplyItem::None => panic!("use after resolve"), } } diff --git a/src/scope.rs b/src/scope.rs index 38e6c062..23c95e68 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -529,6 +529,7 @@ impl WaitingResponse { fut, _s: PhantomData, }), + ReplyItem::None => panic!("use after resolve"), } } diff --git a/src/test.rs b/src/test.rs index 0de7f634..6b62a5ce 100644 --- a/src/test.rs +++ b/src/test.rs @@ -604,6 +604,7 @@ impl TestRequest { ReplyItem::Message(resp) => Ok(resp), ReplyItem::Error(err) => Ok(err.into()), ReplyItem::Future(_) => panic!("Async handler is not supported."), + ReplyItem::None => panic!("use after resolve"), }, Err(err) => Err(err), } diff --git a/src/with.rs b/src/with.rs index a3b07ac0..bf0d77d2 100644 --- a/src/with.rs +++ b/src/with.rs @@ -142,6 +142,7 @@ where self.fut1 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), } } else { match self.fut1.as_mut().unwrap().poll()? { @@ -163,6 +164,7 @@ where self.fut2 = Some(fut); self.poll() } + ReplyItem::None => panic!("use after resolve"), } } } @@ -274,6 +276,7 @@ where self.fut1 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let reply = T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); @@ -285,6 +288,7 @@ where self.fut2 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; @@ -296,6 +300,7 @@ where self.fut3 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -314,6 +319,7 @@ where self.fut2 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; @@ -325,6 +331,7 @@ where self.fut3 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -350,6 +357,7 @@ where ReplyItem::Error(err) => return Err(err), ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), ReplyItem::Future(fut) => self.fut3 = Some(fut), + ReplyItem::None => panic!("use after resolve"), } self.poll() @@ -480,6 +488,7 @@ where self.fut1 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let reply = T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); @@ -491,6 +500,7 @@ where self.fut2 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let reply = T3::from_request(&mut self.req, self.cfg3.as_ref()).into(); @@ -503,6 +513,7 @@ where self.fut3 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; @@ -514,6 +525,7 @@ where self.fut4 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -533,6 +545,7 @@ where self.fut2 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let reply = @@ -545,6 +558,7 @@ where self.fut3 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(self.item1.take().unwrap(), item2, item3) @@ -557,6 +571,7 @@ where self.fut4 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -579,6 +594,7 @@ where self.fut3 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(self.item1.take().unwrap(), item, item3) @@ -591,6 +607,7 @@ where self.fut4 = Some(fut); return self.poll(); } + ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -619,6 +636,7 @@ where ReplyItem::Error(err) => return Ok(Async::Ready(err.into())), ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), ReplyItem::Future(fut) => self.fut4 = Some(fut), + ReplyItem::None => panic!("use after resolve"), } self.poll() From 80f385e703d1c8f8d927dfc7ce6a8fe7c7a2629d Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Wed, 2 May 2018 08:25:31 +0300 Subject: [PATCH 0169/1635] Add WsWriter trait `WsWriter` trait is a common interface for writing to a websocket and it's implemented for both: `WebScoketContext` and `ClientWriter`. --- src/lib.rs | 1 + src/ws/client.rs | 26 +++++++------- src/ws/context.rs | 87 +++++++++++++++++++++++++---------------------- src/ws/mod.rs | 14 ++++++++ 4 files changed, 75 insertions(+), 53 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 14e2cfc4..5bef60c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,6 +175,7 @@ pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; pub use scope::Scope; +pub use ws::WsWriter; #[cfg(feature = "openssl")] pub(crate) const HAS_OPENSSL: bool = true; diff --git a/src/ws/client.rs b/src/ws/client.rs index 8a4abcae..07b44b4d 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -27,7 +27,7 @@ use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientRespons use super::frame::Frame; use super::proto::{CloseReason, OpCode}; -use super::{Message, ProtocolError}; +use super::{Message, ProtocolError, WsWriter}; /// Websocket client error #[derive(Fail, Debug)] @@ -503,13 +503,6 @@ pub struct ClientWriter { inner: Rc>, } -impl ClientWriter { - #[inline] - fn as_mut(&mut self) -> &mut Inner { - unsafe { &mut *self.inner.get() } - } -} - impl ClientWriter { /// Write payload #[inline] @@ -521,21 +514,28 @@ impl ClientWriter { } } + #[inline] + fn as_mut(&mut self) -> &mut Inner { + unsafe { &mut *self.inner.get() } + } +} + +impl WsWriter for ClientWriter { /// Send text frame #[inline] - pub fn text>(&mut self, text: T) { + fn text>(&mut self, text: T) { self.write(Frame::message(text.into(), OpCode::Text, true, true)); } /// Send binary frame #[inline] - pub fn binary>(&mut self, data: B) { + fn binary>(&mut self, data: B) { self.write(Frame::message(data, OpCode::Binary, true, true)); } /// Send ping frame #[inline] - pub fn ping(&mut self, message: &str) { + fn ping(&mut self, message: &str) { self.write(Frame::message( Vec::from(message), OpCode::Ping, @@ -546,7 +546,7 @@ impl ClientWriter { /// Send pong frame #[inline] - pub fn pong(&mut self, message: &str) { + fn pong(&mut self, message: &str) { self.write(Frame::message( Vec::from(message), OpCode::Pong, @@ -557,7 +557,7 @@ impl ClientWriter { /// Send close frame #[inline] - pub fn close(&mut self, reason: Option) { + fn close(&mut self, reason: Option) { self.write(Frame::close(reason, true)); } } diff --git a/src/ws/context.rs b/src/ws/context.rs index b5a2456c..723b215a 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -13,6 +13,7 @@ use context::{ActorHttpContext, Drain, Frame as ContextFrame}; use error::{Error, ErrorInternalServerError}; use httprequest::HttpRequest; +use ws::WsWriter; use ws::frame::Frame; use ws::proto::{CloseReason, OpCode}; @@ -140,46 +141,6 @@ where &mut self.request } - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write(Frame::message(text.into(), OpCode::Text, true, false)); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write(Frame::message(data, OpCode::Binary, true, false)); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &str) { - self.write(Frame::message( - Vec::from(message), - OpCode::Ping, - true, - false, - )); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &str) { - self.write(Frame::message( - Vec::from(message), - OpCode::Pong, - true, - false, - )); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write(Frame::close(reason, false)); - } - /// Returns drain future pub fn drain(&mut self) -> Drain { let (tx, rx) = oneshot::channel(); @@ -213,6 +174,52 @@ where } } +impl WsWriter for WebsocketContext +where + A: Actor, + S: 'static, +{ + /// Send text frame + #[inline] + fn text>(&mut self, text: T) { + self.write(Frame::message(text.into(), OpCode::Text, true, false)); + } + + /// Send binary frame + #[inline] + fn binary>(&mut self, data: B) { + self.write(Frame::message(data, OpCode::Binary, true, false)); + } + + /// Send ping frame + #[inline] + fn ping(&mut self, message: &str) { + self.write(Frame::message( + Vec::from(message), + OpCode::Ping, + true, + false, + )); + } + + /// Send pong frame + #[inline] + fn pong(&mut self, message: &str) { + self.write(Frame::message( + Vec::from(message), + OpCode::Pong, + true, + false, + )); + } + + /// Send close frame + #[inline] + fn close(&mut self, reason: Option) { + self.write(Frame::close(reason, false)); + } +} + impl ActorHttpContext for WebsocketContext where A: Actor, diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 402f2bdf..a39b95b1 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -340,6 +340,20 @@ where } } +/// Common writing methods for a websocket. +pub trait WsWriter { + /// Send a text + fn text>(&mut self, text: T); + /// Send a binary + fn binary>(&mut self, data: B); + /// Send a ping message + fn ping(&mut self, message: &str); + /// Send a pong message + fn pong(&mut self, message: &str); + /// Close the connection + fn close(&mut self, reason: Option); +} + #[cfg(test)] mod tests { use super::*; From 76b644365fd6fa74e1617dc16f6a36c1eeb85a0d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 06:07:30 -0700 Subject: [PATCH 0170/1635] use read only ref for FromRequest; remove unnecessary static --- src/error.rs | 3 +-- src/extractor.rs | 33 ++++++++++++++------------------- src/handler.rs | 20 ++++++++++++-------- src/httprequest.rs | 4 ++-- src/json.rs | 2 +- src/with.rs | 24 ++++++++++-------------- 6 files changed, 40 insertions(+), 46 deletions(-) diff --git a/src/error.rs b/src/error.rs index 37dc3d89..963abd3b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -805,8 +805,7 @@ mod tests { #[test] fn test_backtrace() { - let orig = ErrorBadRequest("err"); - let e: Error = orig.into(); + let e = ErrorBadRequest("err"); assert!(e.backtrace().is_some()); } diff --git a/src/extractor.rs b/src/extractor.rs index 4ed88489..aff15c46 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -99,13 +99,12 @@ impl Path { impl FromRequest for Path where T: DeserializeOwned, - S: 'static, { type Config = (); type Result = Result; #[inline] - fn from_request(req: &mut HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); de::Deserialize::deserialize(PathDeserializer::new(&req)) .map_err(|e| e.into()) @@ -167,13 +166,12 @@ impl Query { impl FromRequest for Query where T: de::DeserializeOwned, - S: 'static, { type Config = (); type Result = Result; #[inline] - fn from_request(req: &mut HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); serde_urlencoded::from_str::(req.query_string()) .map_err(|e| e.into()) @@ -241,7 +239,7 @@ where type Result = Box>; #[inline] - fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { Box::new( UrlEncoded::new(req.clone()) .limit(cfg.limit) @@ -326,7 +324,7 @@ impl FromRequest for Bytes { type Result = Result>, Error>; #[inline] - fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { // check content-type cfg.check_mimetype(req)?; @@ -370,7 +368,7 @@ impl FromRequest for String { type Result = Result>, Error>; #[inline] - fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { // check content-type cfg.check_mimetype(req)?; @@ -471,7 +469,7 @@ mod tests { req.payload_mut() .unread_data(Bytes::from_static(b"hello=world")); - match Bytes::from_request(&mut req, &cfg) + match Bytes::from_request(&req, &cfg) .unwrap() .poll() .unwrap() @@ -490,7 +488,7 @@ mod tests { req.payload_mut() .unread_data(Bytes::from_static(b"hello=world")); - match String::from_request(&mut req, &cfg) + match String::from_request(&req, &cfg) .unwrap() .poll() .unwrap() @@ -514,10 +512,7 @@ mod tests { let mut cfg = FormConfig::default(); cfg.limit(4096); - match Form::::from_request(&mut req, &cfg) - .poll() - .unwrap() - { + match Form::::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.hello, "world"); } @@ -574,29 +569,29 @@ mod tests { let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); - let s = Path::::from_request(&mut req, &()).unwrap(); + let s = Path::::from_request(&req, &()).unwrap(); assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); - let s = Path::<(String, String)>::from_request(&mut req, &()).unwrap(); + let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); - let s = Query::::from_request(&mut req, &()).unwrap(); + let s = Query::::from_request(&req, &()).unwrap(); assert_eq!(s.id, "test"); let mut req = TestRequest::with_uri("/name/32/").finish(); assert!(router.recognize(&mut req).is_some()); - let s = Path::::from_request(&mut req, &()).unwrap(); + let s = Path::::from_request(&req, &()).unwrap(); assert_eq!(s.as_ref().key, "name"); assert_eq!(s.value, 32); - let s = Path::<(String, u8)>::from_request(&mut req, &()).unwrap(); + let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); assert_eq!(s.0, "name"); assert_eq!(s.1, 32); - let res = Path::>::from_request(&mut req, &()).unwrap(); + let res = Path::>::from_default(&req).unwrap(); assert_eq!(res[0], "name".to_owned()); assert_eq!(res[1], "32".to_owned()); } diff --git a/src/handler.rs b/src/handler.rs index 15f975b6..216faa29 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -36,10 +36,7 @@ pub trait Responder { /// Trait implemented by types that can be extracted from request. /// /// Types that implement this trait can be used with `Route::with()` method. -pub trait FromRequest: Sized -where - S: 'static, -{ +pub trait FromRequest: Sized { /// Configuration for conversion process type Config: Default; @@ -47,7 +44,14 @@ where type Result: Into>; /// Convert request to a Self - fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result; + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; + + /// Convert request to a Self + /// + /// This method uses default extractor configuration + fn from_default(req: &HttpRequest) -> Self::Result { + Self::from_request(req, &Self::Config::default()) + } } /// Combines two different responder types into a single type @@ -505,12 +509,12 @@ impl Deref for State { } } -impl FromRequest for State { +impl FromRequest for State { type Config = (); type Result = State; #[inline] - fn from_request(req: &mut HttpRequest, _: &Self::Config) -> Self::Result { - State(req.clone()).into() + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + State(req.clone()) } } diff --git a/src/httprequest.rs b/src/httprequest.rs index 2225b4bb..12e5da1d 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -500,12 +500,12 @@ impl Clone for HttpRequest { } } -impl FromRequest for HttpRequest { +impl FromRequest for HttpRequest { type Config = (); type Result = Self; #[inline] - fn from_request(req: &mut HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { req.clone() } } diff --git a/src/json.rs b/src/json.rs index 24d1c9c4..27c99c64 100644 --- a/src/json.rs +++ b/src/json.rs @@ -136,7 +136,7 @@ where type Result = Box>; #[inline] - fn from_request(req: &mut HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { let req = req.clone(); let err = Rc::clone(&cfg.ehandler); Box::new( diff --git a/src/with.rs b/src/with.rs index bf0d77d2..e522d97e 100644 --- a/src/with.rs +++ b/src/with.rs @@ -134,7 +134,7 @@ where let item = if !self.started { self.started = true; - let reply = T::from_request(&mut self.req, self.cfg.as_ref()).into(); + let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -268,7 +268,7 @@ where if !self.started { self.started = true; - let reply = T1::from_request(&mut self.req, self.cfg1.as_ref()).into(); + let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into(); let item1 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -279,7 +279,7 @@ where ReplyItem::None => panic!("use after resolve"), }; - let reply = T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); + let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -309,8 +309,7 @@ where if self.fut1.is_some() { match self.fut1.as_mut().unwrap().poll()? { Async::Ready(item) => { - let reply = - T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); + let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -480,7 +479,7 @@ where if !self.started { self.started = true; - let reply = T1::from_request(&mut self.req, self.cfg1.as_ref()).into(); + let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into(); let item1 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -491,7 +490,7 @@ where ReplyItem::None => panic!("use after resolve"), }; - let reply = T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); + let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -503,7 +502,7 @@ where ReplyItem::None => panic!("use after resolve"), }; - let reply = T3::from_request(&mut self.req, self.cfg3.as_ref()).into(); + let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -536,8 +535,7 @@ where Async::Ready(item) => { self.item1 = Some(item); self.fut1.take(); - let reply = - T2::from_request(&mut self.req, self.cfg2.as_ref()).into(); + let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -548,8 +546,7 @@ where ReplyItem::None => panic!("use after resolve"), }; - let reply = - T3::from_request(&mut self.req, self.cfg3.as_ref()).into(); + let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, @@ -584,8 +581,7 @@ where match self.fut2.as_mut().unwrap().poll()? { Async::Ready(item) => { self.fut2.take(); - let reply = - T3::from_request(&mut self.req, self.cfg3.as_ref()).into(); + let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { ReplyItem::Error(err) => return Err(err), ReplyItem::Message(msg) => msg, From 1aadfee6f705211bbd20debc1b8e3df15fb27661 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 06:09:50 -0700 Subject: [PATCH 0171/1635] rename from_default to extract --- src/extractor.rs | 2 +- src/handler.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index aff15c46..12ae9e98 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -591,7 +591,7 @@ mod tests { assert_eq!(s.0, "name"); assert_eq!(s.1, 32); - let res = Path::>::from_default(&req).unwrap(); + let res = Path::>::extract(&req).unwrap(); assert_eq!(res[0], "name".to_owned()); assert_eq!(res[1], "32".to_owned()); } diff --git a/src/handler.rs b/src/handler.rs index 216faa29..07281b6b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -49,7 +49,7 @@ pub trait FromRequest: Sized { /// Convert request to a Self /// /// This method uses default extractor configuration - fn from_default(req: &HttpRequest) -> Self::Result { + fn extract(req: &HttpRequest) -> Self::Result { Self::from_request(req, &Self::Config::default()) } } From 31e23d4ab173711ac66c78241a8fbb52187228cb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 06:28:38 -0700 Subject: [PATCH 0172/1635] add query deprecation info --- MIGRATION.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index f343028b..791a6f12 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -15,6 +15,12 @@ * `FromRequest::Result` has to implement `Into>` +* `HttpRequest::query()` is deprecated. Use `Query` extractor. + + ```rust + let q = Query::>::extract(req); + ``` + ## Migration from 0.4 to 0.5 From a38acb41e5c01197b6a3a948be228d8d95f1206c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 06:30:06 -0700 Subject: [PATCH 0173/1635] better query example --- MIGRATION.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 791a6f12..4ecff7b2 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -17,6 +17,14 @@ * `HttpRequest::query()` is deprecated. Use `Query` extractor. + ```rust + fn index(q: Query>) -> Result<..> { + ... + } + ``` + + or + ```rust let q = Query::>::extract(req); ``` From 4ca5d8bcfc7e5b32caeb8a585ec206f16f64329f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 13:38:25 -0700 Subject: [PATCH 0174/1635] add FromRequest impl for tuples of various length --- src/extractor.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 12ae9e98..9d707e22 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,17 +1,18 @@ +use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::str; use bytes::Bytes; use encoding::all::UTF_8; use encoding::types::{DecoderTrap, Encoding}; -use futures::future::Future; +use futures::{Async, Future, Poll}; use mime::Mime; use serde::de::{self, DeserializeOwned}; use serde_urlencoded; use de::PathDeserializer; use error::{Error, ErrorBadRequest}; -use handler::FromRequest; +use handler::{FromRequest, Reply}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; @@ -445,6 +446,123 @@ impl Default for PayloadConfig { } } +macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { + + /// FromRequest implementation for tuple + impl + 'static),+> FromRequest for ($($T,)+) + where + S: 'static, + { + type Config = ($($T::Config,)+); + type Result = Box>; + + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + Box::new($fut_type { + s: PhantomData, + items: <($(Option<$T>,)+)>::default(), + futs: ($(Some($T::from_request(req, &cfg.$n).into()),)+), + }) + } + } + + struct $fut_type),+> + where + S: 'static, + { + s: PhantomData, + items: ($(Option<$T>,)+), + futs: ($(Option>,)+), + } + + impl),+> Future for $fut_type + where + S: 'static, + { + type Item = ($($T,)+); + type Error = Error; + + fn poll(&mut self) -> Poll { + let mut ready = true; + + $( + if self.futs.$n.is_some() { + match self.futs.$n.as_mut().unwrap().poll() { + Ok(Async::Ready(item)) => { + self.items.$n = Some(item); + self.futs.$n.take(); + } + Ok(Async::NotReady) => ready = false, + Err(e) => return Err(e), + } + } + )+ + + if ready { + Ok(Async::Ready( + ($(self.items.$n.take().unwrap(),)+) + )) + } else { + Ok(Async::NotReady) + } + } + } +}); + +tuple_from_req!(TupleFromRequest1, (0, A)); +tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); +tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); +tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); +tuple_from_req!( + TupleFromRequest5, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E) +); +tuple_from_req!( + TupleFromRequest6, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F) +); +tuple_from_req!( + TupleFromRequest7, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F), + (6, G) +); +tuple_from_req!( + TupleFromRequest8, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F), + (6, G), + (7, H) +); +tuple_from_req!( + TupleFromRequest9, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F), + (6, G), + (7, H), + (8, I) +); + #[cfg(test)] mod tests { use super::*; @@ -609,4 +727,37 @@ mod tests { assert_eq!(*Path::::from_request(&mut req, &()).unwrap(), 32); } + + #[test] + fn test_tuple_extract() { + let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); + + let mut resource = ResourceHandler::<()>::default(); + resource.name("index"); + let mut routes = Vec::new(); + routes.push(( + Resource::new("index", "/{key}/{value}/"), + Some(resource), + )); + let (router, _) = Router::new("", ServerSettings::default(), routes); + assert!(router.recognize(&mut req).is_some()); + + let res = match <(Path<(String, String)>,)>::extract(&req).poll() { + Ok(Async::Ready(res)) => res, + _ => panic!("error"), + }; + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); + + let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req) + .poll() + { + Ok(Async::Ready(res)) => res, + _ => panic!("error"), + }; + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); + assert_eq!((res.1).0, "name"); + assert_eq!((res.1).1, "user1"); + } } From 35a4078434596b956aa0141af509f793d33d4ff6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 13:43:51 -0700 Subject: [PATCH 0175/1635] update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index ddc9dc84..7a563153 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ * Allow to access Error's backtrace object +* Various extractor usability improvements #207 + ## 0.5.6 (2018-04-24) From 32a28664492ce9ebd3265ba90092362469b6f32b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 15:53:07 -0700 Subject: [PATCH 0176/1635] Allow to override files listing renderer for #203 --- CHANGES.md | 2 + src/fs.rs | 120 +++++++++++++++++++++++------------------- src/middleware/mod.rs | 2 +- 3 files changed, 69 insertions(+), 55 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7a563153..3955251d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ * Allow to access Error's backtrace object +* Allow to override files listing renderer for `StaticFiles` #203 + * Various extractor usability improvements #207 diff --git a/src/fs.rs b/src/fs.rs index 007e97f6..9d2ea301 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -365,11 +365,14 @@ impl Stream for ChunkedReadFile { } } +type DirectoryRenderer = + Fn(&Directory, &HttpRequest) -> Result; + /// A directory; responds with the generated directory listing. #[derive(Debug)] pub struct Directory { - base: PathBuf, - path: PathBuf, + pub base: PathBuf, + pub path: PathBuf, } impl Directory { @@ -377,7 +380,7 @@ impl Directory { Directory { base, path } } - fn can_list(&self, entry: &io::Result) -> bool { + pub fn is_visible(&self, entry: &io::Result) -> bool { if let Ok(ref entry) = *entry { if let Some(name) = entry.file_name().to_str() { if name.starts_with('.') { @@ -393,61 +396,58 @@ impl Directory { } } -impl Responder for Directory { - type Item = HttpResponse; - type Error = io::Error; +fn directory_listing( + dir: &Directory, req: &HttpRequest, +) -> Result { + let index_of = format!("Index of {}", req.path()); + let mut body = String::new(); + let base = Path::new(req.path()); - 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()); + for entry in dir.path.read_dir()? { + if dir.is_visible(&entry) { + let entry = entry.unwrap(); + let p = match entry.path().strip_prefix(&dir.path) { + Ok(p) => base.join(p), + Err(_) => continue, + }; + // show file url as relative to static path + let file_url = format!("{}", p.to_string_lossy()); - for entry in self.path.read_dir()? { - if self.can_list(&entry) { - let entry = entry.unwrap(); - let p = match entry.path().strip_prefix(&self.path) { - 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() - ); - } + // 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 { - continue; + let _ = write!( + body, + "
  • {}
  • ", + file_url, + entry.file_name().to_string_lossy() + ); } + } else { + continue; } } - - let html = format!( - "\ - {}\ -

    {}

    \ -
      \ - {}\ -
    \n", - index_of, index_of, body - ); - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) } + + let html = format!( + "\ + {}\ +

    {}

    \ +
      \ + {}\ +
    \n", + index_of, index_of, body + ); + Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html)) } /// Static files handling @@ -472,6 +472,7 @@ pub struct StaticFiles { show_index: bool, cpu_pool: CpuPool, default: Box>, + renderer: Box>, _chunk_size: usize, _follow_symlinks: bool, } @@ -535,6 +536,7 @@ impl StaticFiles { default: Box::new(WrapHandler::new(|_| { HttpResponse::new(StatusCode::NOT_FOUND) })), + renderer: Box::new(directory_listing), _chunk_size: 0, _follow_symlinks: false, } @@ -548,6 +550,17 @@ impl StaticFiles { self } + /// Set custom directory renderer + pub fn files_listing_renderer(mut self, f: F) -> Self + where + for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) + -> Result + + 'static, + { + self.renderer = Box::new(f); + self + } + /// Set index file /// /// Redirects to specific index file for directory "/" instead of @@ -601,9 +614,8 @@ impl Handler for StaticFiles { .finish() .respond_to(req.drop_state()) } else if self.show_index { - Directory::new(self.directory.clone(), path) - .respond_to(req.drop_state())? - .respond_to(req.drop_state()) + let dir = Directory::new(self.directory.clone(), path); + Ok((*self.renderer)(&dir, &req)?.into()) } else { Ok(self.default.handle(req)) } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index f097484f..2551ded1 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -21,7 +21,7 @@ pub use self::logger::Logger; /// Middleware start result pub enum Started { - /// Execution completed + /// Middleware is completed, continue to next middleware Done, /// New http response got generated. If middleware generates response /// handler execution halts. From 7036656ae4b63385b36d2a5207b0566647b17121 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 16:33:29 -0700 Subject: [PATCH 0177/1635] make Reply generic over error too --- src/handler.rs | 84 +++++++++++++++---------------- src/pipeline.rs | 9 ++-- src/route.rs | 9 ++-- src/scope.rs | 9 ++-- src/test.rs | 11 ++--- src/with.rs | 128 +++++++++++++++++++++--------------------------- 6 files changed, 112 insertions(+), 138 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 07281b6b..a6a7eadb 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,5 +1,4 @@ use std::marker::PhantomData; -use std::mem; use std::ops::Deref; use futures::future::{err, ok, Future}; @@ -189,77 +188,74 @@ where /// * Message(T) - ready item /// * Error(Error) - error happen during reply process /// * Future - reply process completes in the future -pub struct Reply(ReplyItem); +pub struct Reply(Option>); -impl Future for Reply { - type Item = T; - type Error = Error; +impl Future for Reply { + type Item = I; + type Error = E; - fn poll(&mut self) -> Poll { - let item = mem::replace(&mut self.0, ReplyItem::None); - - match item { - ReplyItem::Error(err) => Err(err), - ReplyItem::Message(msg) => Ok(Async::Ready(msg)), - ReplyItem::Future(mut fut) => match fut.poll() { + fn poll(&mut self) -> Poll { + let res = self.0.take().expect("use after resolve"); + match res { + ReplyResult::Ok(msg) => Ok(Async::Ready(msg)), + ReplyResult::Err(err) => Err(err), + ReplyResult::Future(mut fut) => match fut.poll() { Ok(Async::NotReady) => { - self.0 = ReplyItem::Future(fut); + self.0 = Some(ReplyResult::Future(fut)); Ok(Async::NotReady) } Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)), Err(err) => Err(err), }, - ReplyItem::None => panic!("use after resolve"), } } } -pub(crate) enum ReplyItem { - None, - Error(Error), - Message(T), - Future(Box>), +pub(crate) enum ReplyResult { + Ok(I), + Err(E), + Future(Box>), } -impl Reply { +impl Reply { /// Create async response #[inline] - pub fn async(fut: F) -> Reply + pub fn async(fut: F) -> Reply where - F: Future + 'static, + F: Future + 'static, { - Reply(ReplyItem::Future(Box::new(fut))) + Reply(Some(ReplyResult::Future(Box::new(fut)))) } /// Send response #[inline] - pub fn response>(response: R) -> Reply { - Reply(ReplyItem::Message(response.into())) + pub fn response>(response: R) -> Reply { + Reply(Some(ReplyResult::Ok(response.into()))) } /// Send error #[inline] - pub fn error>(err: R) -> Reply { - Reply(ReplyItem::Error(err.into())) + pub fn error>(err: R) -> Reply { + Reply(Some(ReplyResult::Err(err.into()))) } #[inline] - pub(crate) fn into(self) -> ReplyItem { - self.0 + pub(crate) fn into(self) -> ReplyResult { + self.0.expect("use after resolve") } #[cfg(test)] pub(crate) fn as_msg(&self) -> &T { match self.0 { - ReplyItem::Message(ref resp) => resp, + ReplyResult::Ok(ref resp) => resp, _ => panic!(), } } #[cfg(test)] - pub(crate) fn as_err(&self) -> Option<&Error> { + pub(crate) fn as_err(&self) -> Option<&E> { match self.0 { - ReplyItem::Error(ref err) => Some(err), + ReplyResult::Err(ref err) => Some(err), _ => None, } } @@ -280,14 +276,14 @@ impl Responder for HttpResponse { #[inline] fn respond_to(self, _: HttpRequest) -> Result, Error> { - Ok(Reply(ReplyItem::Message(self))) + Ok(Reply(Some(ReplyResult::Ok(self)))) } } impl From for Reply { #[inline] fn from(resp: T) -> Reply { - Reply(ReplyItem::Message(resp)) + Reply(Some(ReplyResult::Ok(resp))) } } @@ -311,7 +307,7 @@ impl> From, E>> for Reply { fn from(res: Result, E>) -> Self { match res { Ok(val) => val, - Err(err) => Reply(ReplyItem::Error(err.into())), + Err(err) => Reply(Some(ReplyResult::Err(err.into()))), } } } @@ -320,8 +316,8 @@ impl> From> for Reply { #[inline] fn from(res: Result) -> Self { match res { - Ok(val) => Reply(ReplyItem::Message(val)), - Err(err) => Reply(ReplyItem::Error(err.into())), + Ok(val) => Reply(Some(ReplyResult::Ok(val))), + Err(err) => Reply(Some(ReplyResult::Err(err.into()))), } } } @@ -332,8 +328,8 @@ impl> From>, E>> #[inline] fn from(res: Result>, E>) -> Self { match res { - Ok(fut) => Reply(ReplyItem::Future(fut)), - Err(err) => Reply(ReplyItem::Error(err.into())), + Ok(fut) => Reply(Some(ReplyResult::Future(fut))), + Err(err) => Reply(Some(ReplyResult::Err(err.into()))), } } } @@ -341,7 +337,7 @@ impl> From>, E>> impl From>> for Reply { #[inline] fn from(fut: Box>) -> Reply { - Reply(ReplyItem::Future(fut)) + Reply(Some(ReplyResult::Future(fut))) } } @@ -360,8 +356,8 @@ where fn respond_to(self, req: HttpRequest) -> Result, Error> { 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), + Ok(reply) => match reply.into().into() { + ReplyResult::Ok(resp) => ok(resp), _ => panic!("Nested async replies are not supported"), }, Err(e) => err(e), @@ -456,8 +452,8 @@ where let req2 = req.drop_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), + Ok(reply) => match reply.into().into() { + ReplyResult::Ok(resp) => ok(resp), _ => panic!("Nested async replies are not supported"), }, Err(e) => err(e), diff --git a/src/pipeline.rs b/src/pipeline.rs index 398738e6..b6ddfff3 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -11,7 +11,7 @@ use application::Inner; use body::{Body, BodyStream}; use context::{ActorHttpContext, Frame}; use error::Error; -use handler::{Reply, ReplyItem}; +use handler::{Reply, ReplyResult}; use header::ContentEncoding; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -324,14 +324,13 @@ impl WaitingResponse { info: &mut PipelineInfo, reply: Reply, ) -> PipelineState { match reply.into() { - ReplyItem::Error(err) => RunMiddlewares::init(info, err.into()), - ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), - ReplyItem::Future(fut) => PipelineState::Handler(WaitingResponse { + ReplyResult::Err(err) => RunMiddlewares::init(info, err.into()), + ReplyResult::Ok(resp) => RunMiddlewares::init(info, resp), + ReplyResult::Future(fut) => PipelineState::Handler(WaitingResponse { fut, _s: PhantomData, _h: PhantomData, }), - ReplyItem::None => panic!("use after resolve"), } } diff --git a/src/route.rs b/src/route.rs index d5137c57..7dda988a 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use futures::{Async, Future, Poll}; use error::Error; -use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyItem, Responder, +use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyResult, Responder, RouteHandler, WrapHandler}; use http::StatusCode; use httprequest::HttpRequest; @@ -417,13 +417,12 @@ impl WaitingResponse { #[inline] fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { match reply.into() { - ReplyItem::Error(err) => RunMiddlewares::init(info, err.into()), - ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), - ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse { + ReplyResult::Err(err) => RunMiddlewares::init(info, err.into()), + ReplyResult::Ok(resp) => RunMiddlewares::init(info, resp), + ReplyResult::Future(fut) => ComposeState::Handler(WaitingResponse { fut, _s: PhantomData, }), - ReplyItem::None => panic!("use after resolve"), } } diff --git a/src/scope.rs b/src/scope.rs index 23c95e68..f48308f2 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use futures::{Async, Future, Poll}; use error::Error; -use handler::{FromRequest, Reply, ReplyItem, Responder, RouteHandler}; +use handler::{FromRequest, Reply, ReplyResult, Responder, RouteHandler}; use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -523,13 +523,12 @@ impl WaitingResponse { #[inline] fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { match reply.into() { - ReplyItem::Message(resp) => RunMiddlewares::init(info, resp), - ReplyItem::Error(err) => RunMiddlewares::init(info, err.into()), - ReplyItem::Future(fut) => ComposeState::Handler(WaitingResponse { + ReplyResult::Ok(resp) => RunMiddlewares::init(info, resp), + ReplyResult::Err(err) => RunMiddlewares::init(info, err.into()), + ReplyResult::Future(fut) => ComposeState::Handler(WaitingResponse { fut, _s: PhantomData, }), - ReplyItem::None => panic!("use after resolve"), } } diff --git a/src/test.rs b/src/test.rs index 6b62a5ce..08d05ba8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -21,7 +21,7 @@ use application::{App, HttpApplication}; use body::Binary; use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; use error::Error; -use handler::{Handler, ReplyItem, Responder}; +use handler::{Handler, ReplyResult, Responder}; use header::{Header, IntoHeaderValue}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -601,10 +601,9 @@ impl TestRequest { match resp.respond_to(req.drop_state()) { Ok(resp) => match resp.into().into() { - ReplyItem::Message(resp) => Ok(resp), - ReplyItem::Error(err) => Ok(err.into()), - ReplyItem::Future(_) => panic!("Async handler is not supported."), - ReplyItem::None => panic!("use after resolve"), + ReplyResult::Ok(resp) => Ok(resp), + ReplyResult::Err(err) => Ok(err.into()), + ReplyResult::Future(_) => panic!("Async handler is not supported."), }, Err(err) => Err(err), } @@ -628,7 +627,7 @@ impl TestRequest { match core.run(fut) { Ok(r) => match r.respond_to(req.drop_state()) { Ok(reply) => match reply.into().into() { - ReplyItem::Message(resp) => Ok(resp), + ReplyResult::Ok(resp) => Ok(resp), _ => panic!("Nested async replies are not supported"), }, Err(e) => Err(e), diff --git a/src/with.rs b/src/with.rs index e522d97e..3ee9a6a1 100644 --- a/src/with.rs +++ b/src/with.rs @@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use error::Error; -use handler::{FromRequest, Handler, Reply, ReplyItem, Responder}; +use handler::{FromRequest, Handler, Reply, ReplyResult, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -136,13 +136,12 @@ where self.started = true; let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.fut1 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), } } else { match self.fut1.as_mut().unwrap().poll()? { @@ -158,13 +157,12 @@ where }; match item.into() { - ReplyItem::Error(err) => Err(err), - ReplyItem::Message(resp) => Ok(Async::Ready(resp)), - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => Err(err), + ReplyResult::Ok(resp) => Ok(Async::Ready(resp)), + ReplyResult::Future(fut) => { self.fut2 = Some(fut); self.poll() } - ReplyItem::None => panic!("use after resolve"), } } } @@ -270,37 +268,34 @@ where self.started = true; let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into(); let item1 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.fut1 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.item = Some(item1); self.fut2 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(item1, item2).respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), + ReplyResult::Future(fut) => { self.fut3 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -311,26 +306,24 @@ where Async::Ready(item) => { let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.item = Some(item); self.fut2 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(item, item2).respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), + ReplyResult::Future(fut) => { self.fut3 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -353,10 +346,9 @@ where }; match item.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), - ReplyItem::Future(fut) => self.fut3 = Some(fut), - ReplyItem::None => panic!("use after resolve"), + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), + ReplyResult::Future(fut) => self.fut3 = Some(fut), } self.poll() @@ -481,50 +473,46 @@ where self.started = true; let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into(); let item1 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.fut1 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.item1 = Some(item1); self.fut2 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.item1 = Some(item1); self.item2 = Some(item2); self.fut3 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(item1, item2, item3).respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), + ReplyResult::Future(fut) => { self.fut4 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -537,38 +525,35 @@ where self.fut1.take(); let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.fut2 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.item2 = Some(item2); self.fut3 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(self.item1.take().unwrap(), item2, item3) .respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), + ReplyResult::Future(fut) => { self.fut4 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -583,27 +568,25 @@ where self.fut2.take(); let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(msg) => msg, - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(msg) => msg, + ReplyResult::Future(fut) => { self.item2 = Some(item); self.fut3 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(self.item1.take().unwrap(), item, item3) .respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyItem::Error(err) => return Err(err), - ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), - ReplyItem::Future(fut) => { + ReplyResult::Err(err) => return Err(err), + ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), + ReplyResult::Future(fut) => { self.fut4 = Some(fut); return self.poll(); } - ReplyItem::None => panic!("use after resolve"), }, Err(e) => return Err(e.into()), } @@ -629,10 +612,9 @@ where }; match item.into() { - ReplyItem::Error(err) => return Ok(Async::Ready(err.into())), - ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), - ReplyItem::Future(fut) => self.fut4 = Some(fut), - ReplyItem::None => panic!("use after resolve"), + ReplyResult::Err(err) => return Ok(Async::Ready(err.into())), + ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), + ReplyResult::Future(fut) => self.fut4 = Some(fut), } self.poll() From 3623383e8389bbd8ef8aa9c5edffdd96fa10d8d2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 16:48:42 -0700 Subject: [PATCH 0178/1635] fix tests --- src/handler.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index a6a7eadb..cafb9086 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -245,8 +245,8 @@ impl Reply { } #[cfg(test)] - pub(crate) fn as_msg(&self) -> &T { - match self.0 { + pub(crate) fn as_msg(&self) -> &I { + match self.0.as_ref().unwrap() { ReplyResult::Ok(ref resp) => resp, _ => panic!(), } @@ -254,7 +254,7 @@ impl Reply { #[cfg(test)] pub(crate) fn as_err(&self) -> Option<&E> { - match self.0 { + match self.0.as_ref().unwrap() { ReplyResult::Err(ref err) => Some(err), _ => None, } From 58079b5bbe5d808554a56e3383168ba51ffa6548 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 2 May 2018 19:11:44 -0700 Subject: [PATCH 0179/1635] add session test --- src/handler.rs | 4 ++-- src/middleware/session.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index cafb9086..0a1bd9f5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -247,7 +247,7 @@ impl Reply { #[cfg(test)] pub(crate) fn as_msg(&self) -> &I { match self.0.as_ref().unwrap() { - ReplyResult::Ok(ref resp) => resp, + &ReplyResult::Ok(ref resp) => resp, _ => panic!(), } } @@ -255,7 +255,7 @@ impl Reply { #[cfg(test)] pub(crate) fn as_err(&self) -> Option<&E> { match self.0.as_ref().unwrap() { - ReplyResult::Err(ref err) => Some(err), + &ReplyResult::Err(ref err) => Some(err), _ => None, } } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 13948ac2..b926842f 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -37,7 +37,7 @@ //! use actix_web::{server, App, HttpRequest, Result}; //! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; //! -//! fn index(mut req: HttpRequest) -> Result<&'static str> { +//! fn index(req: HttpRequest) -> Result<&'static str> { //! // access session data //! if let Some(count) = req.session().get::("counter")? { //! println!("SESSION value: {}", count); @@ -525,3 +525,30 @@ impl SessionBackend for CookieSessionBackend { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use application::App; + use test; + + #[test] + fn cookie_session() { + let mut srv = test::TestServer::with_factory(|| { + App::new() + .middleware(SessionStorage::new( + CookieSessionBackend::signed(&[0; 32]).secure(false), + )) + .resource("/", |r| { + r.f(|req| { + let _ = req.session().set("counter", 100); + "test" + }) + }) + }); + + let request = srv.get().uri(srv.url("/")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.cookie("actix-session").is_some()); + } +} From acd7380865968ac2e04a020bed0790506e46f3f3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 3 May 2018 16:22:08 -0700 Subject: [PATCH 0180/1635] rename Reply to a AsyncResult --- src/application.rs | 6 +- src/extractor.rs | 4 +- src/fs.rs | 4 +- src/handler.rs | 119 ++++++++++++++++++++-------------------- src/lib.rs | 2 +- src/pipeline.rs | 15 ++--- src/resource.rs | 6 +- src/route.rs | 22 ++++---- src/scope.rs | 24 ++++---- src/test.rs | 16 +++--- src/with.rs | 134 ++++++++++++++++++++++----------------------- 11 files changed, 176 insertions(+), 176 deletions(-) diff --git a/src/application.rs b/src/application.rs index dbb31a56..76ae1ba7 100644 --- a/src/application.rs +++ b/src/application.rs @@ -2,7 +2,7 @@ use std::cell::UnsafeCell; use std::collections::HashMap; use std::rc::Rc; -use handler::{FromRequest, Handler, Reply, Responder, RouteHandler, WrapHandler}; +use handler::{AsyncResult, FromRequest, Handler, Responder, RouteHandler, WrapHandler}; use header::ContentEncoding; use http::Method; use httprequest::HttpRequest; @@ -39,7 +39,7 @@ impl PipelineHandler for Inner { fn handle( &mut self, req: HttpRequest, htype: HandlerType, - ) -> Reply { + ) -> AsyncResult { match htype { HandlerType::Normal(idx) => { self.resources[idx].handle(req, Some(&mut self.default)) @@ -90,7 +90,7 @@ impl HttpApplication { } #[cfg(test)] - pub(crate) fn run(&mut self, mut req: HttpRequest) -> Reply { + pub(crate) fn run(&mut self, mut req: HttpRequest) -> AsyncResult { let tp = self.get_handler(&mut req); unsafe { &mut *self.inner.get() }.handle(req, tp) } diff --git a/src/extractor.rs b/src/extractor.rs index 9d707e22..c83b238d 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -12,7 +12,7 @@ use serde_urlencoded; use de::PathDeserializer; use error::{Error, ErrorBadRequest}; -use handler::{FromRequest, Reply}; +use handler::{AsyncResult, FromRequest}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; @@ -471,7 +471,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { { s: PhantomData, items: ($(Option<$T>,)+), - futs: ($(Option>,)+), + futs: ($(Option>,)+), } impl),+> Future for $fut_type diff --git a/src/fs.rs b/src/fs.rs index 9d2ea301..348a9979 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -18,7 +18,7 @@ use mime; use mime_guess::{get_mime_type, guess_mime_type}; use error::Error; -use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler}; +use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use header; use http::{Method, StatusCode}; use httpmessage::HttpMessage; @@ -578,7 +578,7 @@ impl StaticFiles { } impl Handler for StaticFiles { - type Result = Result, Error>; + type Result = Result, Error>; fn handle(&mut self, req: HttpRequest) -> Self::Result { if !self.accessible { diff --git a/src/handler.rs b/src/handler.rs index 0a1bd9f5..632d63ab 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -23,12 +23,12 @@ pub trait Handler: 'static { /// 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>; + type Item: Into>; /// The associated error which can be returned. type Error: Into; - /// Convert itself to `Reply` or `Error`. + /// Convert itself to `AsyncResult` or `Error`. fn respond_to(self, req: HttpRequest) -> Result; } @@ -40,7 +40,7 @@ pub trait FromRequest: Sized { type Config: Default; /// Future that resolves to a Self - type Result: Into>; + type Result: Into>; /// Convert request to a Self fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; @@ -93,10 +93,10 @@ where A: Responder, B: Responder, { - type Item = Reply; + type Item = AsyncResult; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result, Error> { + fn respond_to(self, req: HttpRequest) -> Result, Error> { match self { Either::A(a) => match a.respond_to(req) { Ok(val) => Ok(val.into()), @@ -182,26 +182,26 @@ where } } -/// Represents reply process. +/// Represents async result /// -/// Reply could be in tree different forms. -/// * Message(T) - ready item -/// * Error(Error) - error happen during reply process -/// * Future - reply process completes in the future -pub struct Reply(Option>); +/// Result could be in tree different forms. +/// * Ok(T) - ready item +/// * Err(E) - error happen during reply process +/// * Future - reply process completes in the future +pub struct AsyncResult(Option>); -impl Future for Reply { +impl Future for AsyncResult { type Item = I; type Error = E; fn poll(&mut self) -> Poll { let res = self.0.take().expect("use after resolve"); match res { - ReplyResult::Ok(msg) => Ok(Async::Ready(msg)), - ReplyResult::Err(err) => Err(err), - ReplyResult::Future(mut fut) => match fut.poll() { + AsyncResultItem::Ok(msg) => Ok(Async::Ready(msg)), + AsyncResultItem::Err(err) => Err(err), + AsyncResultItem::Future(mut fut) => match fut.poll() { Ok(Async::NotReady) => { - self.0 = Some(ReplyResult::Future(fut)); + self.0 = Some(AsyncResultItem::Future(fut)); Ok(Async::NotReady) } Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)), @@ -211,43 +211,40 @@ impl Future for Reply { } } -pub(crate) enum ReplyResult { +pub(crate) enum AsyncResultItem { Ok(I), Err(E), Future(Box>), } -impl Reply { +impl AsyncResult { /// Create async response #[inline] - pub fn async(fut: F) -> Reply - where - F: Future + 'static, - { - Reply(Some(ReplyResult::Future(Box::new(fut)))) + pub fn async(fut: Box>) -> AsyncResult { + AsyncResult(Some(AsyncResultItem::Future(fut))) } /// Send response #[inline] - pub fn response>(response: R) -> Reply { - Reply(Some(ReplyResult::Ok(response.into()))) + pub fn ok>(ok: R) -> AsyncResult { + AsyncResult(Some(AsyncResultItem::Ok(ok.into()))) } /// Send error #[inline] - pub fn error>(err: R) -> Reply { - Reply(Some(ReplyResult::Err(err.into()))) + pub fn error>(err: R) -> AsyncResult { + AsyncResult(Some(AsyncResultItem::Err(err.into()))) } #[inline] - pub(crate) fn into(self) -> ReplyResult { + pub(crate) fn into(self) -> AsyncResultItem { self.0.expect("use after resolve") } #[cfg(test)] pub(crate) fn as_msg(&self) -> &I { match self.0.as_ref().unwrap() { - &ReplyResult::Ok(ref resp) => resp, + &AsyncResultItem::Ok(ref resp) => resp, _ => panic!(), } } @@ -255,35 +252,35 @@ impl Reply { #[cfg(test)] pub(crate) fn as_err(&self) -> Option<&E> { match self.0.as_ref().unwrap() { - &ReplyResult::Err(ref err) => Some(err), + &AsyncResultItem::Err(ref err) => Some(err), _ => None, } } } -impl Responder for Reply { - type Item = Reply; +impl Responder for AsyncResult { + type Item = AsyncResult; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result, Error> { + fn respond_to(self, _: HttpRequest) -> Result, Error> { Ok(self) } } impl Responder for HttpResponse { - type Item = Reply; + type Item = AsyncResult; type Error = Error; #[inline] - fn respond_to(self, _: HttpRequest) -> Result, Error> { - Ok(Reply(Some(ReplyResult::Ok(self)))) + fn respond_to(self, _: HttpRequest) -> Result, Error> { + Ok(AsyncResult(Some(AsyncResultItem::Ok(self)))) } } -impl From for Reply { +impl From for AsyncResult { #[inline] - fn from(resp: T) -> Reply { - Reply(Some(ReplyResult::Ok(resp))) + fn from(resp: T) -> AsyncResult { + AsyncResult(Some(AsyncResultItem::Ok(resp))) } } @@ -302,42 +299,42 @@ impl> Responder for Result { } } -impl> From, E>> for Reply { +impl> From, E>> for AsyncResult { #[inline] - fn from(res: Result, E>) -> Self { + fn from(res: Result, E>) -> Self { match res { Ok(val) => val, - Err(err) => Reply(Some(ReplyResult::Err(err.into()))), + Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), } } } -impl> From> for Reply { +impl> From> for AsyncResult { #[inline] fn from(res: Result) -> Self { match res { - Ok(val) => Reply(Some(ReplyResult::Ok(val))), - Err(err) => Reply(Some(ReplyResult::Err(err.into()))), + Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))), + Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), } } } impl> From>, E>> - for Reply + for AsyncResult { #[inline] fn from(res: Result>, E>) -> Self { match res { - Ok(fut) => Reply(Some(ReplyResult::Future(fut))), - Err(err) => Reply(Some(ReplyResult::Err(err.into()))), + Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(fut))), + Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), } } } -impl From>> for Reply { +impl From>> for AsyncResult { #[inline] - fn from(fut: Box>) -> Reply { - Reply(Some(ReplyResult::Future(fut))) + fn from(fut: Box>) -> AsyncResult { + AsyncResult(Some(AsyncResultItem::Future(fut))) } } @@ -349,26 +346,26 @@ where I: Responder + 'static, E: Into + 'static, { - type Item = Reply; + type Item = AsyncResult; type Error = Error; #[inline] - fn respond_to(self, req: HttpRequest) -> Result, Error> { + fn respond_to(self, req: HttpRequest) -> Result, Error> { let fut = self.map_err(|e| e.into()) .then(move |r| match r.respond_to(req) { Ok(reply) => match reply.into().into() { - ReplyResult::Ok(resp) => ok(resp), + AsyncResultItem::Ok(resp) => ok(resp), _ => panic!("Nested async replies are not supported"), }, Err(e) => err(e), }); - Ok(Reply::async(fut)) + Ok(AsyncResult::async(Box::new(fut))) } } /// Trait defines object that could be registered as resource route pub(crate) trait RouteHandler: 'static { - fn handle(&mut self, req: HttpRequest) -> Reply; + fn handle(&mut self, req: HttpRequest) -> AsyncResult; } /// Route handler wrapper for Handler @@ -402,11 +399,11 @@ where R: Responder + 'static, S: 'static, { - fn handle(&mut self, req: HttpRequest) -> Reply { + fn handle(&mut self, req: HttpRequest) -> AsyncResult { let req2 = req.drop_state(); match self.h.handle(req).respond_to(req2) { Ok(reply) => reply.into(), - Err(err) => Reply::response(err.into()), + Err(err) => AsyncResult::ok(err.into()), } } } @@ -448,18 +445,18 @@ where E: Into + 'static, S: 'static, { - fn handle(&mut self, req: HttpRequest) -> Reply { + fn handle(&mut self, req: HttpRequest) -> AsyncResult { let req2 = req.drop_state(); let fut = (self.h)(req).map_err(|e| e.into()).then(move |r| { match r.respond_to(req2) { Ok(reply) => match reply.into().into() { - ReplyResult::Ok(resp) => ok(resp), + AsyncResultItem::Ok(resp) => ok(resp), _ => panic!("Nested async replies are not supported"), }, Err(e) => err(e), } }); - Reply::async(fut) + AsyncResult::async(Box::new(fut)) } } diff --git a/src/lib.rs b/src/lib.rs index 14e2cfc4..5dd0309f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,7 +200,7 @@ pub mod dev { pub use body::BodyStream; pub use context::Drain; pub use extractor::{FormConfig, PayloadConfig}; - pub use handler::{Handler, Reply}; + pub use handler::{AsyncResult, Handler}; pub use httpmessage::{MessageBody, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; pub use info::ConnectionInfo; diff --git a/src/pipeline.rs b/src/pipeline.rs index b6ddfff3..14b05093 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -11,7 +11,7 @@ use application::Inner; use body::{Body, BodyStream}; use context::{ActorHttpContext, Frame}; use error::Error; -use handler::{Reply, ReplyResult}; +use handler::{AsyncResult, AsyncResultItem}; use header::ContentEncoding; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -28,8 +28,9 @@ pub(crate) enum HandlerType { pub(crate) trait PipelineHandler { fn encoding(&self) -> ContentEncoding; - fn handle(&mut self, req: HttpRequest, htype: HandlerType) - -> Reply; + fn handle( + &mut self, req: HttpRequest, htype: HandlerType, + ) -> AsyncResult; } pub(crate) struct Pipeline(PipelineInfo, PipelineState); @@ -321,12 +322,12 @@ struct WaitingResponse { impl WaitingResponse { #[inline] fn init( - info: &mut PipelineInfo, reply: Reply, + info: &mut PipelineInfo, reply: AsyncResult, ) -> PipelineState { match reply.into() { - ReplyResult::Err(err) => RunMiddlewares::init(info, err.into()), - ReplyResult::Ok(resp) => RunMiddlewares::init(info, resp), - ReplyResult::Future(fut) => PipelineState::Handler(WaitingResponse { + AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), + AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), + AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse { fut, _s: PhantomData, _h: PhantomData, diff --git a/src/resource.rs b/src/resource.rs index e78a4546..fb08afd9 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use http::{Method, StatusCode}; use smallvec::SmallVec; -use handler::{FromRequest, Handler, Reply, Responder}; +use handler::{AsyncResult, FromRequest, Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::Middleware; @@ -198,7 +198,7 @@ impl ResourceHandler { pub(crate) fn handle( &mut self, mut req: HttpRequest, default: Option<&mut ResourceHandler>, - ) -> Reply { + ) -> AsyncResult { for route in &mut self.routes { if route.check(&mut req) { return if self.middlewares.is_empty() { @@ -211,7 +211,7 @@ impl ResourceHandler { if let Some(resource) = default { resource.handle(req, None) } else { - Reply::response(HttpResponse::new(StatusCode::NOT_FOUND)) + AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) } } } diff --git a/src/route.rs b/src/route.rs index 7dda988a..2558fa68 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,8 +5,8 @@ use std::rc::Rc; use futures::{Async, Future, Poll}; use error::Error; -use handler::{AsyncHandler, FromRequest, Handler, Reply, ReplyResult, Responder, - RouteHandler, WrapHandler}; +use handler::{AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, + Responder, RouteHandler, WrapHandler}; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -45,15 +45,15 @@ impl Route { } #[inline] - pub(crate) fn handle(&mut self, req: HttpRequest) -> Reply { + pub(crate) fn handle(&mut self, req: HttpRequest) -> AsyncResult { self.handler.handle(req) } #[inline] pub(crate) fn compose( &mut self, req: HttpRequest, mws: Rc>>>, - ) -> Reply { - Reply::async(Compose::new(req, mws, self.handler.clone())) + ) -> AsyncResult { + AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) } /// Add match predicate to route. @@ -243,7 +243,7 @@ impl InnerHandler { } #[inline] - pub fn handle(&self, req: HttpRequest) -> Reply { + pub fn handle(&self, req: HttpRequest) -> AsyncResult { // reason: handler is unique per thread, handler get called from async code only let h = unsafe { &mut *self.0.as_ref().get() }; h.handle(req) @@ -415,11 +415,13 @@ struct WaitingResponse { impl WaitingResponse { #[inline] - fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { + fn init( + info: &mut ComposeInfo, reply: AsyncResult, + ) -> ComposeState { match reply.into() { - ReplyResult::Err(err) => RunMiddlewares::init(info, err.into()), - ReplyResult::Ok(resp) => RunMiddlewares::init(info, resp), - ReplyResult::Future(fut) => ComposeState::Handler(WaitingResponse { + AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), + AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), + AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { fut, _s: PhantomData, }), diff --git a/src/scope.rs b/src/scope.rs index f48308f2..bab2ac94 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use futures::{Async, Future, Poll}; use error::Error; -use handler::{FromRequest, Reply, ReplyResult, Responder, RouteHandler}; +use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler}; use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -274,7 +274,7 @@ impl Scope { } impl RouteHandler for Scope { - fn handle(&mut self, mut req: HttpRequest) -> Reply { + fn handle(&mut self, mut req: HttpRequest) -> AsyncResult { let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; let path = if path == "" { "/" } else { path }; @@ -287,12 +287,12 @@ impl RouteHandler for Scope { let resource = unsafe { &mut *resource.get() }; return resource.handle(req, Some(default)); } else { - return Reply::async(Compose::new( + return AsyncResult::async(Box::new(Compose::new( req, Rc::clone(&self.middlewares), Rc::clone(&resource), Some(Rc::clone(&self.default)), - )); + ))); } } } @@ -330,12 +330,12 @@ impl RouteHandler for Scope { if self.middlewares.is_empty() { default.handle(req, None) } else { - Reply::async(Compose::new( + AsyncResult::async(Box::new(Compose::new( req, Rc::clone(&self.middlewares), Rc::clone(&self.default), None, - )) + ))) } } } @@ -346,7 +346,7 @@ struct Wrapper { } impl RouteHandler for Wrapper { - fn handle(&mut self, req: HttpRequest) -> Reply { + fn handle(&mut self, req: HttpRequest) -> AsyncResult { self.scope .handle(req.change_state(Rc::clone(&self.state))) } @@ -521,11 +521,13 @@ struct WaitingResponse { impl WaitingResponse { #[inline] - fn init(info: &mut ComposeInfo, reply: Reply) -> ComposeState { + fn init( + info: &mut ComposeInfo, reply: AsyncResult, + ) -> ComposeState { match reply.into() { - ReplyResult::Ok(resp) => RunMiddlewares::init(info, resp), - ReplyResult::Err(err) => RunMiddlewares::init(info, err.into()), - ReplyResult::Future(fut) => ComposeState::Handler(WaitingResponse { + AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), + AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), + AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { fut, _s: PhantomData, }), diff --git a/src/test.rs b/src/test.rs index 08d05ba8..57edcbf0 100644 --- a/src/test.rs +++ b/src/test.rs @@ -21,7 +21,7 @@ use application::{App, HttpApplication}; use body::Binary; use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; use error::Error; -use handler::{Handler, ReplyResult, Responder}; +use handler::{AsyncResultItem, Handler, Responder}; use header::{Header, IntoHeaderValue}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -593,19 +593,17 @@ impl TestRequest { /// 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> { + pub fn run>(self, mut h: H) -> Result { let req = self.finish(); let resp = h.handle(req.clone()); match resp.respond_to(req.drop_state()) { Ok(resp) => match resp.into().into() { - ReplyResult::Ok(resp) => Ok(resp), - ReplyResult::Err(err) => Ok(err.into()), - ReplyResult::Future(_) => panic!("Async handler is not supported."), + AsyncResultItem::Ok(resp) => Ok(resp), + AsyncResultItem::Err(err) => Err(err), + AsyncResultItem::Future(_) => panic!("Async handler is not supported."), }, - Err(err) => Err(err), + Err(err) => Err(err.into()), } } @@ -627,7 +625,7 @@ impl TestRequest { match core.run(fut) { Ok(r) => match r.respond_to(req.drop_state()) { Ok(reply) => match reply.into().into() { - ReplyResult::Ok(resp) => Ok(resp), + AsyncResultItem::Ok(resp) => Ok(resp), _ => panic!("Nested async replies are not supported"), }, Err(e) => Err(e), diff --git a/src/with.rs b/src/with.rs index 3ee9a6a1..0f7d0774 100644 --- a/src/with.rs +++ b/src/with.rs @@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use error::Error; -use handler::{FromRequest, Handler, Reply, ReplyResult, Responder}; +use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -82,7 +82,7 @@ where T: FromRequest + 'static, S: 'static, { - type Result = Reply; + type Result = AsyncResult; fn handle(&mut self, req: HttpRequest) -> Self::Result { let mut fut = WithHandlerFut { @@ -95,9 +95,9 @@ where }; match fut.poll() { - Ok(Async::Ready(resp)) => Reply::response(resp), - Ok(Async::NotReady) => Reply::async(fut), - Err(e) => Reply::error::(e), + Ok(Async::Ready(resp)) => AsyncResult::ok(resp), + Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), + Err(e) => AsyncResult::error::(e), } } } @@ -136,9 +136,9 @@ where self.started = true; let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.fut1 = Some(fut); return self.poll(); } @@ -157,9 +157,9 @@ where }; match item.into() { - ReplyResult::Err(err) => Err(err), - ReplyResult::Ok(resp) => Ok(Async::Ready(resp)), - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => Err(err), + AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => { self.fut2 = Some(fut); self.poll() } @@ -207,7 +207,7 @@ where T2: FromRequest + 'static, S: 'static, { - type Result = Reply; + type Result = AsyncResult; fn handle(&mut self, req: HttpRequest) -> Self::Result { let mut fut = WithHandlerFut2 { @@ -222,9 +222,9 @@ where fut3: None, }; match fut.poll() { - Ok(Async::Ready(resp)) => Reply::response(resp), - Ok(Async::NotReady) => Reply::async(fut), - Err(e) => Reply::response(e), + Ok(Async::Ready(resp)) => AsyncResult::ok(resp), + Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), + Err(e) => AsyncResult::ok(e), } } } @@ -268,9 +268,9 @@ where self.started = true; let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into(); let item1 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.fut1 = Some(fut); return self.poll(); } @@ -278,9 +278,9 @@ where let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.item = Some(item1); self.fut2 = Some(fut); return self.poll(); @@ -290,9 +290,9 @@ where let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(item1, item2).respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => { self.fut3 = Some(fut); return self.poll(); } @@ -306,9 +306,9 @@ where Async::Ready(item) => { let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.item = Some(item); self.fut2 = Some(fut); return self.poll(); @@ -318,9 +318,9 @@ where let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(item, item2).respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => { self.fut3 = Some(fut); return self.poll(); } @@ -346,9 +346,9 @@ where }; match item.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), - ReplyResult::Future(fut) => self.fut3 = Some(fut), + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => self.fut3 = Some(fut), } self.poll() @@ -404,7 +404,7 @@ where T3: 'static, S: 'static, { - type Result = Reply; + type Result = AsyncResult; fn handle(&mut self, req: HttpRequest) -> Self::Result { let mut fut = WithHandlerFut3 { @@ -422,9 +422,9 @@ where fut4: None, }; match fut.poll() { - Ok(Async::Ready(resp)) => Reply::response(resp), - Ok(Async::NotReady) => Reply::async(fut), - Err(e) => Reply::response(e), + Ok(Async::Ready(resp)) => AsyncResult::ok(resp), + Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), + Err(e) => AsyncResult::error(e), } } } @@ -473,9 +473,9 @@ where self.started = true; let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into(); let item1 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.fut1 = Some(fut); return self.poll(); } @@ -483,9 +483,9 @@ where let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.item1 = Some(item1); self.fut2 = Some(fut); return self.poll(); @@ -494,9 +494,9 @@ where let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.item1 = Some(item1); self.item2 = Some(item2); self.fut3 = Some(fut); @@ -507,9 +507,9 @@ where let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(item1, item2, item3).respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => { self.fut4 = Some(fut); return self.poll(); } @@ -525,9 +525,9 @@ where self.fut1.take(); let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); let item2 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.fut2 = Some(fut); return self.poll(); } @@ -535,9 +535,9 @@ where let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.item2 = Some(item2); self.fut3 = Some(fut); return self.poll(); @@ -548,9 +548,9 @@ where .respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => { self.fut4 = Some(fut); return self.poll(); } @@ -568,9 +568,9 @@ where self.fut2.take(); let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); let item3 = match reply.into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(msg) => msg, - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { self.item2 = Some(item); self.fut3 = Some(fut); return self.poll(); @@ -581,9 +581,9 @@ where .respond_to(self.req.drop_state()) { Ok(item) => match item.into().into() { - ReplyResult::Err(err) => return Err(err), - ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), - ReplyResult::Future(fut) => { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => { self.fut4 = Some(fut); return self.poll(); } @@ -612,9 +612,9 @@ where }; match item.into() { - ReplyResult::Err(err) => return Ok(Async::Ready(err.into())), - ReplyResult::Ok(resp) => return Ok(Async::Ready(resp)), - ReplyResult::Future(fut) => self.fut4 = Some(fut), + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => self.fut4 = Some(fut), } self.poll() From b07d0e712f3467d417ce615617126aa5d18b7d06 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 3 May 2018 16:26:42 -0700 Subject: [PATCH 0181/1635] always provide backtrace for error --- src/error.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/error.rs b/src/error.rs index 963abd3b..cc29fe63 100644 --- a/src/error.rs +++ b/src/error.rs @@ -48,11 +48,11 @@ impl Error { /// Returns a reference to the Backtrace carried by this error, if it /// carries one. - pub fn backtrace(&self) -> Option<&Backtrace> { + pub fn backtrace(&self) -> &Backtrace { if let Some(bt) = self.cause.backtrace() { - Some(bt) + bt } else { - self.backtrace.as_ref() + self.backtrace.as_ref().unwrap() } } } @@ -806,7 +806,7 @@ mod tests { #[test] fn test_backtrace() { let e = ErrorBadRequest("err"); - assert!(e.backtrace().is_some()); + let _ = e.backtrace(); } #[test] From f37880d89c098f8a4fa03a7a003e3d33064a7bff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 4 May 2018 11:44:22 -0700 Subject: [PATCH 0182/1635] refactor Responder trait --- src/body.rs | 4 ++-- src/error.rs | 2 +- src/fs.rs | 16 +++++++-------- src/handler.rs | 50 +++++++++++++++++++++++++++------------------ src/httpresponse.rs | 14 ++++++------- src/json.rs | 4 ++-- src/test.rs | 6 +++--- src/with.rs | 22 +++++++++----------- 8 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/body.rs b/src/body.rs index cf54361d..063c93ac 100644 --- a/src/body.rs +++ b/src/body.rs @@ -257,8 +257,8 @@ impl Responder for Binary { type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { - Ok(HttpResponse::Ok() + fn respond_to(self, req: &HttpRequest) -> Result { + Ok(HttpResponse::build_from(req) .content_type("application/octet-stream") .body(self)) } diff --git a/src/error.rs b/src/error.rs index cc29fe63..5ef2ddd7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -642,7 +642,7 @@ where type Item = HttpResponse; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result { + fn respond_to(self, _: &HttpRequest) -> Result { Err(self.into()) } } diff --git a/src/fs.rs b/src/fs.rs index 348a9979..de1a60a3 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -161,7 +161,7 @@ impl DerefMut for NamedFile { } /// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { +fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { match req.get_header::() { None | Some(header::IfMatch::Any) => true, Some(header::IfMatch::Items(ref items)) => { @@ -178,7 +178,7 @@ fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } /// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { +fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { match req.get_header::() { Some(header::IfNoneMatch::Any) => false, Some(header::IfNoneMatch::Items(ref items)) => { @@ -199,7 +199,7 @@ impl Responder for NamedFile { type Item = HttpResponse; type Error = io::Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.if_some(self.path().extension(), |ext, resp| { @@ -244,7 +244,7 @@ impl Responder for NamedFile { let last_modified = self.last_modified(); // check preconditions - let precondition_failed = if !any_match(etag.as_ref(), &req) { + let precondition_failed = if !any_match(etag.as_ref(), req) { true } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = (last_modified, req.get_header()) @@ -255,7 +255,7 @@ impl Responder for NamedFile { }; // check last modified - let not_modified = if !none_match(etag.as_ref(), &req) { + let not_modified = if !none_match(etag.as_ref(), req) { true } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = (last_modified, req.get_header()) @@ -612,7 +612,7 @@ impl Handler for StaticFiles { HttpResponse::Found() .header(header::LOCATION, new_path.as_str()) .finish() - .respond_to(req.drop_state()) + .respond_to(&req) } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); Ok((*self.renderer)(&dir, &req)?.into()) @@ -622,8 +622,8 @@ impl Handler for StaticFiles { } else { NamedFile::open(path)? .set_cpu_pool(self.cpu_pool.clone()) - .respond_to(req.drop_state())? - .respond_to(req.drop_state()) + .respond_to(&req)? + .respond_to(&req) } } } diff --git a/src/handler.rs b/src/handler.rs index 632d63ab..7b88248b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -29,7 +29,9 @@ pub trait Responder { type Error: Into; /// Convert itself to `AsyncResult` or `Error`. - fn respond_to(self, req: HttpRequest) -> Result; + fn respond_to( + self, req: &HttpRequest, + ) -> Result; } /// Trait implemented by types that can be extracted from request. @@ -96,7 +98,9 @@ where type Item = AsyncResult; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result, Error> { + fn respond_to( + self, req: &HttpRequest, + ) -> Result, Error> { match self { Either::A(a) => match a.respond_to(req) { Ok(val) => Ok(val.into()), @@ -232,7 +236,7 @@ impl AsyncResult { /// Send error #[inline] - pub fn error>(err: R) -> AsyncResult { + pub fn err>(err: R) -> AsyncResult { AsyncResult(Some(AsyncResultItem::Err(err.into()))) } @@ -262,7 +266,9 @@ impl Responder for AsyncResult { type Item = AsyncResult; type Error = Error; - fn respond_to(self, _: HttpRequest) -> Result, Error> { + fn respond_to( + self, _: &HttpRequest, + ) -> Result, Error> { Ok(self) } } @@ -272,7 +278,9 @@ impl Responder for HttpResponse { type Error = Error; #[inline] - fn respond_to(self, _: HttpRequest) -> Result, Error> { + fn respond_to( + self, _: &HttpRequest, + ) -> Result, Error> { Ok(AsyncResult(Some(AsyncResultItem::Ok(self)))) } } @@ -288,7 +296,7 @@ impl> Responder for Result { type Item = ::Item; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { match self { Ok(val) => match val.respond_to(req) { Ok(val) => Ok(val), @@ -350,9 +358,12 @@ where type Error = Error; #[inline] - fn respond_to(self, req: HttpRequest) -> Result, Error> { + fn respond_to( + self, req: &HttpRequest, + ) -> Result, Error> { + let req = req.clone(); let fut = self.map_err(|e| e.into()) - .then(move |r| match r.respond_to(req) { + .then(move |r| match r.respond_to(&req) { Ok(reply) => match reply.into().into() { AsyncResultItem::Ok(resp) => ok(resp), _ => panic!("Nested async replies are not supported"), @@ -363,7 +374,7 @@ where } } -/// Trait defines object that could be registered as resource route +// /// Trait defines object that could be registered as resource route pub(crate) trait RouteHandler: 'static { fn handle(&mut self, req: HttpRequest) -> AsyncResult; } @@ -400,10 +411,9 @@ where S: 'static, { fn handle(&mut self, req: HttpRequest) -> AsyncResult { - let req2 = req.drop_state(); - match self.h.handle(req).respond_to(req2) { + match self.h.handle(req.clone()).respond_to(&req) { Ok(reply) => reply.into(), - Err(err) => AsyncResult::ok(err.into()), + Err(err) => AsyncResult::err(err.into()), } } } @@ -446,16 +456,16 @@ where S: 'static, { fn handle(&mut self, req: HttpRequest) -> AsyncResult { - let req2 = req.drop_state(); - let fut = (self.h)(req).map_err(|e| e.into()).then(move |r| { - match r.respond_to(req2) { + let fut = (self.h)(req.clone()) + .map_err(|e| e.into()) + .then(move |r| match r.respond_to(&req) { Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => ok(resp), - _ => panic!("Nested async replies are not supported"), + AsyncResultItem::Ok(resp) => Either::A(ok(resp)), + AsyncResultItem::Err(e) => Either::A(err(e)), + AsyncResultItem::Future(fut) => Either::B(fut), }, - Err(e) => err(e), - } - }); + Err(e) => Either::A(err(e)), + }); AsyncResult::async(Box::new(fut)) } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index f776612d..00db2775 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -636,7 +636,7 @@ impl Responder for HttpResponseBuilder { type Error = Error; #[inline] - fn respond_to(mut self, _: HttpRequest) -> Result { + fn respond_to(mut self, _: &HttpRequest) -> Result { Ok(self.finish()) } } @@ -653,7 +653,7 @@ impl Responder for &'static str { type Item = HttpResponse; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) @@ -672,7 +672,7 @@ impl Responder for &'static [u8] { type Item = HttpResponse; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) @@ -691,7 +691,7 @@ impl Responder for String { type Item = HttpResponse; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) @@ -710,7 +710,7 @@ impl<'a> Responder for &'a String { type Item = HttpResponse; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) @@ -729,7 +729,7 @@ impl Responder for Bytes { type Item = HttpResponse; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) @@ -748,7 +748,7 @@ impl Responder for BytesMut { type Item = HttpResponse; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { Ok(req.build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) diff --git a/src/json.rs b/src/json.rs index 27c99c64..6711de39 100644 --- a/src/json.rs +++ b/src/json.rs @@ -118,7 +118,7 @@ impl Responder for Json { type Item = HttpResponse; type Error = Error; - fn respond_to(self, req: HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Result { let body = serde_json::to_string(&self.0)?; Ok(req.build_response(StatusCode::OK) @@ -351,7 +351,7 @@ mod tests { let json = Json(MyObject { name: "test".to_owned(), }); - let resp = json.respond_to(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/test.rs b/src/test.rs index 57edcbf0..c712edd6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -478,7 +478,7 @@ impl TestRequest<()> { } } -impl TestRequest { +impl TestRequest { /// Start HttpRequest build process with application state pub fn with_state(state: S) -> TestRequest { TestRequest { @@ -597,7 +597,7 @@ impl TestRequest { let req = self.finish(); let resp = h.handle(req.clone()); - match resp.respond_to(req.drop_state()) { + match resp.respond_to(&req) { Ok(resp) => match resp.into().into() { AsyncResultItem::Ok(resp) => Ok(resp), AsyncResultItem::Err(err) => Err(err), @@ -623,7 +623,7 @@ impl TestRequest { let mut core = Core::new().unwrap(); match core.run(fut) { - Ok(r) => match r.respond_to(req.drop_state()) { + Ok(r) => match r.respond_to(&req) { Ok(reply) => match reply.into().into() { AsyncResultItem::Ok(resp) => Ok(resp), _ => panic!("Nested async replies are not supported"), diff --git a/src/with.rs b/src/with.rs index 0f7d0774..fa3d7d80 100644 --- a/src/with.rs +++ b/src/with.rs @@ -97,7 +97,7 @@ where match fut.poll() { Ok(Async::Ready(resp)) => AsyncResult::ok(resp), Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::error::(e), + Err(e) => AsyncResult::err(e), } } } @@ -151,7 +151,7 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - let item = match (*hnd)(item).respond_to(self.req.drop_state()) { + let item = match (*hnd)(item).respond_to(&self.req) { Ok(item) => item.into(), Err(e) => return Err(e.into()), }; @@ -288,7 +288,7 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(item1, item2).respond_to(self.req.drop_state()) { + match (*hnd)(item1, item2).respond_to(&self.req) { Ok(item) => match item.into().into() { AsyncResultItem::Err(err) => return Err(err), AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), @@ -316,7 +316,7 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(item, item2).respond_to(self.req.drop_state()) { + match (*hnd)(item, item2).respond_to(&self.req) { Ok(item) => match item.into().into() { AsyncResultItem::Err(err) => return Err(err), AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), @@ -338,9 +338,7 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - let item = match (*hnd)(self.item.take().unwrap(), item) - .respond_to(self.req.drop_state()) - { + let item = match (*hnd)(self.item.take().unwrap(), item).respond_to(&self.req) { Ok(item) => item.into(), Err(err) => return Err(err.into()), }; @@ -424,7 +422,7 @@ where match fut.poll() { Ok(Async::Ready(resp)) => AsyncResult::ok(resp), Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::error(e), + Err(e) => AsyncResult::err(e), } } } @@ -505,7 +503,7 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(item1, item2, item3).respond_to(self.req.drop_state()) { + match (*hnd)(item1, item2, item3).respond_to(&self.req) { Ok(item) => match item.into().into() { AsyncResultItem::Err(err) => return Err(err), AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), @@ -545,7 +543,7 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(self.item1.take().unwrap(), item2, item3) - .respond_to(self.req.drop_state()) + .respond_to(&self.req) { Ok(item) => match item.into().into() { AsyncResultItem::Err(err) => return Err(err), @@ -578,7 +576,7 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; match (*hnd)(self.item1.take().unwrap(), item, item3) - .respond_to(self.req.drop_state()) + .respond_to(&self.req) { Ok(item) => match item.into().into() { AsyncResultItem::Err(err) => return Err(err), @@ -605,7 +603,7 @@ where self.item1.take().unwrap(), self.item2.take().unwrap(), item, - ).respond_to(self.req.drop_state()) + ).respond_to(&self.req) { Ok(item) => item.into(), Err(err) => return Err(err.into()), From 03d6b04eefab2aa65f5c0ea40a191a4c7ea70fa3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 4 May 2018 12:11:38 -0700 Subject: [PATCH 0183/1635] update tests --- src/fs.rs | 24 ++++++++++++------------ src/httpresponse.rs | 19 ++++++------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index de1a60a3..bb808ab7 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -650,7 +650,7 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(HttpRequest::default()).unwrap(); + let resp = file.respond_to(&HttpRequest::default()).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" @@ -676,7 +676,7 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(HttpRequest::default()).unwrap(); + let resp = file.respond_to(&HttpRequest::default()).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "image/png" @@ -702,7 +702,7 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(HttpRequest::default()).unwrap(); + let resp = file.respond_to(&HttpRequest::default()).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/octet-stream" @@ -729,7 +729,7 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(HttpRequest::default()).unwrap(); + let resp = file.respond_to(&HttpRequest::default()).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" @@ -748,7 +748,7 @@ mod tests { let req = TestRequest::default().method(Method::POST).finish(); let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.only_get().respond_to(req).unwrap(); + let resp = file.only_get().respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } @@ -756,7 +756,7 @@ mod tests { fn test_named_file_any_method() { let req = TestRequest::default().method(Method::POST).finish(); let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.respond_to(req).unwrap(); + let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::OK); } @@ -765,7 +765,7 @@ mod tests { let mut st = StaticFiles::new(".").show_files_listing(); st.accessible = false; let resp = st.handle(HttpRequest::default()) - .respond_to(HttpRequest::default()) + .respond_to(&HttpRequest::default()) .unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); @@ -773,7 +773,7 @@ mod tests { st.accessible = true; st.show_index = false; let resp = st.handle(HttpRequest::default()) - .respond_to(HttpRequest::default()) + .respond_to(&HttpRequest::default()) .unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); @@ -783,7 +783,7 @@ mod tests { st.show_index = true; let resp = st.handle(req) - .respond_to(HttpRequest::default()) + .respond_to(&HttpRequest::default()) .unwrap(); let resp = resp.as_msg(); assert_eq!( @@ -801,7 +801,7 @@ mod tests { req.match_info_mut().add("tail", "tests"); let resp = st.handle(req) - .respond_to(HttpRequest::default()) + .respond_to(&HttpRequest::default()) .unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); @@ -814,7 +814,7 @@ mod tests { req.match_info_mut().add("tail", "tests/"); let resp = st.handle(req) - .respond_to(HttpRequest::default()) + .respond_to(&HttpRequest::default()) .unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); @@ -831,7 +831,7 @@ mod tests { req.match_info_mut().add("tail", "tools/wsload"); let resp = st.handle(req) - .respond_to(HttpRequest::default()) + .respond_to(&HttpRequest::default()) .unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 00db2775..8097fc93 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -1057,7 +1057,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); - let resp: HttpResponse = "test".respond_to(req.clone()).ok().unwrap(); + let resp: HttpResponse = "test".respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1078,7 +1078,7 @@ mod tests { &Binary::from(b"test".as_ref()) ); - let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap(); + let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1102,11 +1102,7 @@ mod tests { &Binary::from("test".to_owned()) ); - let resp: HttpResponse = "test" - .to_owned() - .respond_to(req.clone()) - .ok() - .unwrap(); + let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1130,10 +1126,7 @@ mod tests { &Binary::from(&"test".to_owned()) ); - let resp: HttpResponse = (&"test".to_owned()) - .respond_to(req.clone()) - .ok() - .unwrap(); + let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1159,7 +1152,7 @@ mod tests { ); let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); + let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1185,7 +1178,7 @@ mod tests { ); let b = BytesMut::from("test"); - let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap(); + let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), From f66cf16823880085c5d061ec3589c27f4a20de20 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 4 May 2018 12:25:06 -0700 Subject: [PATCH 0184/1635] upgrade regex --- .travis.yml | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 19fe1b26..415d792d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ script: # Upload docs after_success: - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then cargo doc --features "alpn, tls, session" --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && @@ -46,7 +46,7 @@ after_success: fi - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) USE_SKEPTIC=1 cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) diff --git a/Cargo.toml b/Cargo.toml index 265e0a33..a0bdf708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ mime_guess = "2.0.0-alpha" num_cpus = "1.0" percent-encoding = "1.0" rand = "0.4" -regex = "0.2" +regex = "1.0" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.5" From bd6e18b7fef1076247c737b9d5e8ad30d43bd1d2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 4 May 2018 13:38:17 -0700 Subject: [PATCH 0185/1635] update migration doc --- MIGRATION.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 4ecff7b2..e2da4004 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -15,6 +15,10 @@ * `FromRequest::Result` has to implement `Into>` +* [`Responder::respond_to()`]( + https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) + is generic over `S` + * `HttpRequest::query()` is deprecated. Use `Query` extractor. ```rust From 0af4d01fe432014337b55e8d081a7dc971b5e7ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 5 May 2018 12:18:43 -0700 Subject: [PATCH 0186/1635] move middleware tests to seprate module --- src/pipeline.rs | 5 +- src/route.rs | 5 +- src/scope.rs | 5 +- tests/test_middleware.rs | 375 +++++++++++++++++++++++++++++++++++++++ tests/test_server.rs | 122 +------------ 5 files changed, 388 insertions(+), 124 deletions(-) create mode 100644 tests/test_middleware.rs diff --git a/src/pipeline.rs b/src/pipeline.rs index 14b05093..1b621882 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -722,8 +722,11 @@ impl FinishingMiddlewares { return None; } self.fut = None; - info.count -= 1; + if info.count == 0 { + return Some(Completed::init(info)); + } + info.count -= 1; match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) { Finished::Done => { if info.count == 0 { diff --git a/src/route.rs b/src/route.rs index 2558fa68..1623702d 100644 --- a/src/route.rs +++ b/src/route.rs @@ -555,8 +555,11 @@ impl FinishingMiddlewares { return None; } self.fut = None; - info.count -= 1; + if info.count == 0 { + return Some(Response::init(self.resp.take().unwrap())); + } + info.count -= 1; match info.mws[info.count as usize] .finish(&mut info.req, self.resp.as_ref().unwrap()) { diff --git a/src/scope.rs b/src/scope.rs index bab2ac94..fedc7ced 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -661,8 +661,11 @@ impl FinishingMiddlewares { return None; } self.fut = None; - info.count -= 1; + if info.count == 0 { + return Some(Response::init(self.resp.take().unwrap())); + } + info.count -= 1; match info.mws[info.count as usize] .finish(&mut info.req, self.resp.as_ref().unwrap()) { diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs new file mode 100644 index 00000000..189b8533 --- /dev/null +++ b/tests/test_middleware.rs @@ -0,0 +1,375 @@ +extern crate actix; +extern crate actix_web; +extern crate futures; +extern crate tokio_core; + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +use actix::*; +use actix_web::*; +use futures::{future, Future}; +use tokio_core::reactor::Timeout; + +struct MiddlewareTest { + start: Arc, + response: Arc, + finish: Arc, +} + +impl middleware::Middleware for MiddlewareTest { + fn start(&self, _: &mut HttpRequest) -> Result { + self.start.store( + self.start.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); + Ok(middleware::Started::Done) + } + + fn response( + &self, _: &mut HttpRequest, resp: HttpResponse, + ) -> Result { + self.response.store( + self.response.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); + Ok(middleware::Response::Done(resp)) + } + + fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { + self.finish.store( + self.finish.load(Ordering::Relaxed) + 1, + Ordering::Relaxed, + ); + middleware::Finished::Done + } +} + +#[test] +fn test_middleware() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut 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(|_| HttpResponse::Ok()) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_resource_middleware() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut 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(|_| HttpResponse::Ok()) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_scope_middleware() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/scope", |scope| { + scope + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }) + }); + + let request = srv.get() + .uri(srv.url("/scope/test")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse { + future::result(Err(error::ErrorBadRequest("TEST"))).responder() +} + +#[test] +fn test_middleware_async_error() { + let req = Arc::new(AtomicUsize::new(0)); + let resp = Arc::new(AtomicUsize::new(0)); + let fin = Arc::new(AtomicUsize::new(0)); + + let act_req = Arc::clone(&req); + let act_resp = Arc::clone(&resp); + let act_fin = Arc::clone(&fin); + + let mut srv = test::TestServer::new(move |app| { + app.middleware(MiddlewareTest { + start: Arc::clone(&act_req), + response: Arc::clone(&act_resp), + finish: Arc::clone(&act_fin), + }).handler(index_test_middleware_async_error) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + + assert_eq!(req.load(Ordering::Relaxed), 1); + assert_eq!(resp.load(Ordering::Relaxed), 1); + assert_eq!(fin.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_scope_middleware_async_error() { + let req = Arc::new(AtomicUsize::new(0)); + let resp = Arc::new(AtomicUsize::new(0)); + let fin = Arc::new(AtomicUsize::new(0)); + + let act_req = Arc::clone(&req); + let act_resp = Arc::clone(&resp); + let act_fin = Arc::clone(&fin); + + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/scope", |scope| { + scope + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_req), + response: Arc::clone(&act_resp), + finish: Arc::clone(&act_fin), + }) + .resource("/test", |r| r.f(index_test_middleware_async_error)) + }) + }); + + let request = srv.get() + .uri(srv.url("/scope/test")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + + assert_eq!(req.load(Ordering::Relaxed), 1); + assert_eq!(resp.load(Ordering::Relaxed), 1); + assert_eq!(fin.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_resource_middleware_async_error() { + let req = Arc::new(AtomicUsize::new(0)); + let resp = Arc::new(AtomicUsize::new(0)); + let fin = Arc::new(AtomicUsize::new(0)); + + let act_req = Arc::clone(&req); + let act_resp = Arc::clone(&resp); + let act_fin = Arc::clone(&fin); + + let mut srv = test::TestServer::with_factory(move || { + let mw = MiddlewareAsyncTest { + start: Arc::clone(&act_req), + response: Arc::clone(&act_resp), + finish: Arc::clone(&act_fin), + }; + + App::new().resource("/test", move |r| { + r.middleware(mw); + r.h(index_test_middleware_async_error); + }) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + + assert_eq!(req.load(Ordering::Relaxed), 1); + assert_eq!(resp.load(Ordering::Relaxed), 1); + assert_eq!(fin.load(Ordering::Relaxed), 1); +} + +struct MiddlewareAsyncTest { + start: Arc, + response: Arc, + finish: Arc, +} + +impl middleware::Middleware for MiddlewareAsyncTest { + fn start(&self, _: &mut HttpRequest) -> Result { + let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap(); + + let start = Arc::clone(&self.start); + Ok(middleware::Started::Future(Box::new( + to.from_err().and_then(move |_| { + start.fetch_add(1, Ordering::Relaxed); + Ok(None) + }), + ))) + } + + fn response( + &self, _: &mut HttpRequest, resp: HttpResponse, + ) -> Result { + let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap(); + + let response = Arc::clone(&self.response); + Ok(middleware::Response::Future(Box::new( + to.from_err().and_then(move |_| { + response.fetch_add(1, Ordering::Relaxed); + Ok(resp) + }), + ))) + } + + fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { + let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap(); + + let finish = Arc::clone(&self.finish); + middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| { + finish.fetch_add(1, Ordering::Relaxed); + Ok(()) + }))) + } +} + +#[test] +fn test_async_middleware() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::new(move |app| { + app.middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }).handler(|_| HttpResponse::Ok()) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + + thread::sleep(Duration::from_millis(20)); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_async_scope_middleware() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/scope", |scope| { + scope + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }) + }); + + let request = srv.get() + .uri(srv.url("/scope/test")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + + thread::sleep(Duration::from_millis(20)); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_async_resource_middleware() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw = MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new().resource("/test", move |r| { + r.middleware(mw); + r.h(|_| HttpResponse::Ok()); + }) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + + thread::sleep(Duration::from_millis(20)); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 19b6c919..e61cedd3 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -18,12 +18,11 @@ use flate2::read::GzDecoder; use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; use flate2::Compression; use futures::stream::once; -use futures::{future, Future, Stream}; +use futures::{Future, Stream}; use h2::client as h2client; use modhttp::Request; use rand::Rng; use std::io::{Read, Write}; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{mpsc, Arc}; use std::{net, thread, time}; use tokio_core::net::TcpStream; @@ -823,122 +822,3 @@ fn test_application() { let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); } - -struct MiddlewareTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareTest { - fn start(&self, _: &mut HttpRequest) -> Result { - self.start.store( - self.start.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); - Ok(middleware::Started::Done) - } - - fn response( - &self, _: &mut HttpRequest, resp: HttpResponse, - ) -> Result { - self.response.store( - self.response.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); - Ok(middleware::Response::Done(resp)) - } - - fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish.store( - self.finish.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); - middleware::Finished::Done - } -} - -#[test] -fn test_middlewares() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut 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(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middlewares() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut 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(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - // assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse { - future::result(Err(error::ErrorBadRequest("TEST"))).responder() -} - -#[test] -fn test_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).handler(index_test_middleware_async_error) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} From a7c40024ce5057d41651d7c4e98bd6251ebbbaee Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 5 May 2018 18:40:16 -0700 Subject: [PATCH 0187/1635] async handle middleware test --- tests/test_middleware.rs | 111 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 189b8533..3216856e 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -135,6 +135,117 @@ fn test_scope_middleware() { assert_eq!(num3.load(Ordering::Relaxed), 1); } +#[test] +fn test_middleware_async_handler() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new() + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/", |r| { + r.route().a(|_| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(|_| Ok(HttpResponse::Ok())) + }) + }) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + thread::sleep(Duration::from_millis(20)); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_resource_middleware_async_handler() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw = MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new().resource("/test", |r| { + r.middleware(mw); + r.route().a(|_| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(|_| Ok(HttpResponse::Ok())) + }) + }) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_scope_middleware_async_handler() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/scope", |scope| { + scope + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/test", |r| { + r.route().a(|_| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(|_| Ok(HttpResponse::Ok())) + }) + }) + }) + }); + + let request = srv.get() + .uri(srv.url("/scope/test")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse { future::result(Err(error::ErrorBadRequest("TEST"))).responder() } From 45325a5f75eb9b376e884756b49fca821e6e01f4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 6 May 2018 08:33:41 -0700 Subject: [PATCH 0188/1635] more middleware tests --- tests/test_middleware.rs | 223 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 221 insertions(+), 2 deletions(-) diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 3216856e..9a6bf004 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -74,6 +74,38 @@ fn test_middleware() { assert_eq!(num3.load(Ordering::Relaxed), 1); } +#[test] +fn test_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::new(move |app| { + app.middleware(MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }).middleware(MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .handler(|_| HttpResponse::Ok()) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} + #[test] fn test_resource_middleware() { let num1 = Arc::new(AtomicUsize::new(0)); @@ -101,6 +133,38 @@ fn test_resource_middleware() { assert_eq!(num3.load(Ordering::Relaxed), 1); } +#[test] +fn test_resource_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::new(move |app| { + app.middleware(MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }).middleware(MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .handler(|_| HttpResponse::Ok()) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} + #[test] fn test_scope_middleware() { let num1 = Arc::new(AtomicUsize::new(0)); @@ -114,7 +178,7 @@ fn test_scope_middleware() { let mut srv = test::TestServer::with_factory(move || { App::new().scope("/scope", |scope| { scope - .middleware(MiddlewareAsyncTest { + .middleware(MiddlewareTest { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), @@ -135,6 +199,45 @@ fn test_scope_middleware() { assert_eq!(num3.load(Ordering::Relaxed), 1); } +#[test] +fn test_scope_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/scope", |scope| { + scope + .middleware(MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .middleware(MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }) + }); + + let request = srv.get() + .uri(srv.url("/scope/test")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} + #[test] fn test_middleware_async_handler() { let num1 = Arc::new(AtomicUsize::new(0)); @@ -416,6 +519,42 @@ fn test_async_middleware() { assert_eq!(num3.load(Ordering::Relaxed), 1); } +#[test] +fn test_async_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new() + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + + thread::sleep(Duration::from_millis(30)); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} + #[test] fn test_async_scope_middleware() { let num1 = Arc::new(AtomicUsize::new(0)); @@ -452,6 +591,47 @@ fn test_async_scope_middleware() { assert_eq!(num3.load(Ordering::Relaxed), 1); } +#[test] +fn test_async_scope_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/scope", |scope| { + scope + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }) + }); + + let request = srv.get() + .uri(srv.url("/scope/test")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + + thread::sleep(Duration::from_millis(20)); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} + #[test] fn test_async_resource_middleware() { let num1 = Arc::new(AtomicUsize::new(0)); @@ -481,6 +661,45 @@ fn test_async_resource_middleware() { assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); - thread::sleep(Duration::from_millis(20)); + thread::sleep(Duration::from_millis(40)); assert_eq!(num3.load(Ordering::Relaxed), 1); } + +#[test] +fn test_async_resource_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw1 = MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + let mw2 = MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new().resource("/test", move |r| { + r.middleware(mw1); + r.middleware(mw2); + r.h(|_| HttpResponse::Ok()); + }) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + + thread::sleep(Duration::from_millis(40)); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} From cd11293c1f1d88515797aa31bf8351e0a3c44144 Mon Sep 17 00:00:00 2001 From: Alexander Andreev Date: Sun, 6 May 2018 19:07:30 +0300 Subject: [PATCH 0189/1635] spelling check --- src/error.rs | 10 +++++----- src/extractor.rs | 2 +- src/header/shared/charset.rs | 2 +- src/httprequest.rs | 14 +++++++------- src/lib.rs | 2 +- src/middleware/errhandlers.rs | 2 +- src/route.rs | 6 +++--- src/router.rs | 6 +++--- src/test.rs | 2 +- src/ws/client.rs | 4 ++-- tools/wsload/src/wsclient.rs | 2 +- 11 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/error.rs b/src/error.rs index 5ef2ddd7..5d886ec7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -310,7 +310,7 @@ pub enum HttpRangeError { /// 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, } @@ -392,7 +392,7 @@ impl ResponseError for ExpectError { } } -/// A set of error that can occure during parsing content type +/// A set of error that can occurred during parsing content type #[derive(Fail, PartialEq, Debug)] pub enum ContentTypeError { /// Can not parse content type @@ -542,7 +542,7 @@ impl From for UrlGenerationError { /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" -/// response as opposite to *INNTERNAL SERVER ERROR* which is defined by +/// response as opposite to *INTERNAL SERVER ERROR* which is defined by /// default. /// /// ```rust @@ -850,7 +850,7 @@ mod tests { } macro_rules! from { - ($from:expr => $error:pat) => { + ($from: expr => $error: pat) => { match ParseError::from($from) { e @ $error => { assert!(format!("{}", e).len() >= 5); @@ -861,7 +861,7 @@ mod tests { } macro_rules! from_and_cause { - ($from:expr => $error:pat) => { + ($from: expr => $error: pat) => { match ParseError::from($from) { e @ $error => { let desc = format!("{}", e.cause().unwrap()); diff --git a/src/extractor.rs b/src/extractor.rs index c83b238d..1f06f782 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -715,7 +715,7 @@ mod tests { } #[test] - fn test_extract_path_signle() { + fn test_extract_path_single() { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs index bab9d65d..21ee637b 100644 --- a/src/header/shared/charset.rs +++ b/src/header/shared/charset.rs @@ -7,7 +7,7 @@ use self::Charset::*; /// A Mime charset. /// -/// The string representation is normalised to upper case. +/// The string representation is normalized to upper case. /// /// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. /// diff --git a/src/httprequest.rs b/src/httprequest.rs index 12e5da1d..42adc63a 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -199,7 +199,7 @@ impl HttpRequest { &self.as_ref().extensions } - /// Mutable refernece to a the request's extensions + /// Mutable reference to a the request's extensions #[inline] pub fn extensions_mut(&mut self) -> &mut Extensions { &mut self.as_mut().extensions @@ -665,8 +665,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![( - Resource::new("index", "/user/{name}.{ext}"), - Some(resource), + Resource::new("index", "/user/{name}.{ext}"), + Some(resource), )]; let (router, _) = Router::new("/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); @@ -697,8 +697,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![( - Resource::new("index", "/user/{name}.{ext}"), - Some(resource), + Resource::new("index", "/user/{name}.{ext}"), + Some(resource), )]; let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); @@ -719,8 +719,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![( - Resource::external("youtube", "https://youtube.com/watch/{video_id}"), - None, + Resource::external("youtube", "https://youtube.com/watch/{video_id}"), + None, )]; let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); assert!(!router.has_route("https://youtube.com/watch/unknown")); diff --git a/src/lib.rs b/src/lib.rs index 180e29af..92a319bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ //! * [HttpRequest](struct.HttpRequest.html) and //! [HttpResponse](struct.HttpResponse.html): These structs //! represent HTTP requests and responses and expose various methods -//! for inspecting, creating and otherwise utilising them. +//! for inspecting, creating and otherwise utilizing them. //! //! ## Features //! diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 22d0e1af..757b3815 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -12,7 +12,7 @@ type ErrorHandler = Fn(&mut HttpRequest, HttpResponse) -> Result /// /// You can use `ErrorHandlers::handler()` method to register a custom error /// handler for specific status code. You can modify existing response or -/// create completly new one. +/// create completely new one. /// /// ## Example /// diff --git a/src/route.rs b/src/route.rs index 1623702d..d17f13f1 100644 --- a/src/route.rs +++ b/src/route.rs @@ -104,7 +104,7 @@ impl Route { self.handler = InnerHandler::async(handler); } - /// Set handler function, use request extractor for paramters. + /// Set handler function, use request extractor for parameters. /// /// ```rust /// # extern crate bytes; @@ -140,7 +140,7 @@ impl Route { cfg } - /// Set handler function, use request extractor for both paramters. + /// Set handler function, use request extractor for both parameters. /// /// ```rust /// # extern crate bytes; @@ -189,7 +189,7 @@ impl Route { (cfg1, cfg2) } - /// Set handler function, use request extractor for all paramters. + /// Set handler function, use request extractor for all parameters. pub fn with3( &mut self, handler: F, ) -> ( diff --git a/src/router.rs b/src/router.rs index 6c5a7da6..2c7d5c32 100644 --- a/src/router.rs +++ b/src/router.rs @@ -150,7 +150,7 @@ enum PatternType { pub enum ResourceType { /// Normal resource Normal, - /// Resource for applicaiton default handler + /// Resource for application default handler Default, /// External resource External, @@ -158,7 +158,7 @@ pub enum ResourceType { Unset, } -/// Reslource type describes an entry in resources table +/// Resource type describes an entry in resources table #[derive(Clone)] pub struct Resource { tp: PatternType, @@ -268,7 +268,7 @@ impl Resource { } } - /// Build reousrce path. + /// Build resource path. pub fn resource_path( &self, router: &Router, elements: U, ) -> Result diff --git a/src/test.rs b/src/test.rs index c712edd6..4e739839 100644 --- a/src/test.rs +++ b/src/test.rs @@ -88,7 +88,7 @@ impl TestServer { /// Create test server builder with specific state factory /// /// This method can be used for constructing application state. - /// Also it can be used for external dependecy initialization, + /// Also it can be used for external dependency initialization, /// like creating sync actors for diesel integration. pub fn build_with_state(state: F) -> TestServerBuilder where diff --git a/src/ws/client.rs b/src/ws/client.rs index 07b44b4d..64aaa8e4 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -256,9 +256,9 @@ struct Inner { closed: bool, } -/// Future that implementes client websocket handshake process. +/// Future that implemented client websocket handshake process. /// -/// It resolves to a pair of `ClientReadr` and `ClientWriter` that +/// It resolves to a pair of `ClientReader` and `ClientWriter` that /// can be used for reading and writing websocket frames. pub struct ClientHandshake { request: Option, diff --git a/tools/wsload/src/wsclient.rs b/tools/wsload/src/wsclient.rs index d8d7b660..186d6317 100644 --- a/tools/wsload/src/wsclient.rs +++ b/tools/wsload/src/wsclient.rs @@ -259,7 +259,7 @@ impl StreamHandler for ChatClient { ctx.stop(); } } else { - println!("not eaqual"); + println!("not equal"); } } _ => (), From c54f045b3959dfaea7dfbaee7f7b272d1c973de1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 6 May 2018 15:11:36 -0700 Subject: [PATCH 0190/1635] more handler tests --- src/server/h1.rs | 2 +- tests/test_handlers.rs | 133 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 8c793ed5..4f502605 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -350,7 +350,7 @@ where } } Ok(Some(Message::Eof)) => { - if let Some(ref mut payload) = self.payload { + if let Some(ref mut payload) = self.payload.take() { payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 7a9abe97..adba95d7 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -7,10 +7,17 @@ extern crate http; extern crate tokio_core; #[macro_use] extern crate serde_derive; +extern crate serde_json; +use std::time::Duration; + +use actix::*; use actix_web::*; use bytes::Bytes; +use futures::Future; use http::StatusCode; +use serde_json::Value; +use tokio_core::reactor::Timeout; #[derive(Deserialize)] struct PParam { @@ -130,6 +137,132 @@ fn test_path_and_query_extractor2() { assert_eq!(response.status(), StatusCode::BAD_REQUEST); } +#[test] +fn test_path_and_query_extractor2_async() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with3( + |p: Path, _: Query, data: Json| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| { + Ok(format!("Welcome {} - {}!", p.username, data.0)) + }) + .responder() + }, + ) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html?username=test2")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!( + bytes, + Bytes::from_static(b"Welcome test1 - {\"test\":1}!") + ); +} + +#[test] +fn test_path_and_query_extractor3_async() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with2(|p: Path, data: Json| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| { + Ok(format!("Welcome {} - {}!", p.username, data.0)) + }) + .responder() + }) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); +} + +#[test] +fn test_path_and_query_extractor4_async() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with2(|data: Json, p: Path| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| { + Ok(format!("Welcome {} - {}!", p.username, data.0)) + }) + .responder() + }) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); +} + +#[test] +fn test_path_and_query_extractor2_async2() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with3( + |p: Path, data: Json, _: Query| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| { + Ok(format!("Welcome {} - {}!", p.username, data.0)) + }) + .responder() + }, + ) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html?username=test2")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!( + bytes, + Bytes::from_static(b"Welcome test1 - {\"test\":1}!") + ); + + // client request + let request = srv.get() + .uri(srv.url("/test1/index.html")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); +} + #[test] fn test_non_ascii_route() { let mut srv = test::TestServer::new(|app| { From fa81d9700457864be8a73b9953e970ca04888e61 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 6 May 2018 20:05:31 -0700 Subject: [PATCH 0191/1635] more handler tests --- src/server/h1.rs | 6 +-- tests/test_handlers.rs | 114 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 4f502605..8d862b39 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -165,7 +165,7 @@ where // completed self.flags.insert(Flags::ERROR); - if let Some(ref mut payload) = self.payload { + if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } } else { @@ -350,7 +350,7 @@ where } } Ok(Some(Message::Eof)) => { - if let Some(ref mut payload) = self.payload.take() { + if let Some(mut payload) = self.payload.take() { payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); @@ -360,7 +360,7 @@ where Ok(None) => break, Err(e) => { self.flags.insert(Flags::ERROR); - if let Some(ref mut payload) = self.payload { + if let Some(mut payload) = self.payload.take() { let e = match e { DecoderError::Io(e) => PayloadError::Io(e), DecoderError::Error(_) => PayloadError::EncodingCorrupted, diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index adba95d7..8aea34d0 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -74,6 +74,33 @@ fn test_query_extractor() { assert_eq!(response.status(), StatusCode::BAD_REQUEST); } +#[test] +fn test_async_extractor_async() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with(|data: Json| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| Ok(format!("{}", data.0))) + .responder() + }) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}")); +} + #[test] fn test_path_and_query_extractor() { let mut srv = test::TestServer::new(|app| { @@ -263,6 +290,93 @@ fn test_path_and_query_extractor2_async2() { assert_eq!(response.status(), StatusCode::BAD_REQUEST); } +#[test] +fn test_path_and_query_extractor2_async3() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with3( + |data: Json, p: Path, _: Query| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| { + Ok(format!("Welcome {} - {}!", p.username, data.0)) + }) + .responder() + }, + ) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html?username=test2")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!( + bytes, + Bytes::from_static(b"Welcome test1 - {\"test\":1}!") + ); + + // client request + let request = srv.get() + .uri(srv.url("/test1/index.html")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); +} + +#[test] +fn test_path_and_query_extractor2_async4() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route() + .with(|data: (Json, Path, Query)| { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| { + Ok(format!( + "Welcome {} - {}!", + data.1.username, + (data.0).0 + )) + }) + .responder() + }) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html?username=test2")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!( + bytes, + Bytes::from_static(b"Welcome test1 - {\"test\":1}!") + ); + + // client request + let request = srv.get() + .uri(srv.url("/test1/index.html")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); +} + #[test] fn test_non_ascii_route() { let mut srv = test::TestServer::new(|app| { From 599fd6af93f7fd90996de86d9db7714081266041 Mon Sep 17 00:00:00 2001 From: Alexander Andreev Date: Mon, 7 May 2018 20:53:45 +0300 Subject: [PATCH 0192/1635] fix formatting --- src/error.rs | 4 ++-- src/httprequest.rs | 12 ++++++------ src/ws/client.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/error.rs b/src/error.rs index 5d886ec7..9482e067 100644 --- a/src/error.rs +++ b/src/error.rs @@ -310,7 +310,7 @@ pub enum HttpRangeError { /// 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, } @@ -392,7 +392,7 @@ impl ResponseError for ExpectError { } } -/// A set of error that can occurred during parsing content type +/// A set of error that can occure during parsing content type #[derive(Fail, PartialEq, Debug)] pub enum ContentTypeError { /// Can not parse content type diff --git a/src/httprequest.rs b/src/httprequest.rs index 42adc63a..229c0687 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -665,8 +665,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![( - Resource::new("index", "/user/{name}.{ext}"), - Some(resource), + Resource::new("index", "/user/{name}.{ext}"), + Some(resource), )]; let (router, _) = Router::new("/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); @@ -697,8 +697,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![( - Resource::new("index", "/user/{name}.{ext}"), - Some(resource), + Resource::new("index", "/user/{name}.{ext}"), + Some(resource), )]; let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); @@ -719,8 +719,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![( - Resource::external("youtube", "https://youtube.com/watch/{video_id}"), - None, + Resource::external("youtube", "https://youtube.com/watch/{video_id}"), + None, )]; let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); assert!(!router.has_route("https://youtube.com/watch/unknown")); diff --git a/src/ws/client.rs b/src/ws/client.rs index 64aaa8e4..aa392a90 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -256,7 +256,7 @@ struct Inner { closed: bool, } -/// Future that implemented client websocket handshake process. +/// Future that implementes client websocket handshake process. /// /// It resolves to a pair of `ClientReader` and `ClientWriter` that /// can be used for reading and writing websocket frames. From a817ddb57b358748b47797b2d11b22236edf471d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 7 May 2018 13:50:43 -0700 Subject: [PATCH 0193/1635] add variable segments support for scope prefix --- src/application.rs | 98 +++++++++++++++++-------- src/error.rs | 4 +- src/router.rs | 114 +++++++++++++++++++++++++++--- src/scope.rs | 149 ++++++++++++++++++++++++++++++++++----- src/ws/context.rs | 2 +- tests/test_middleware.rs | 2 +- 6 files changed, 306 insertions(+), 63 deletions(-) diff --git a/src/application.rs b/src/application.rs index 76ae1ba7..20dfa790 100644 --- a/src/application.rs +++ b/src/application.rs @@ -29,7 +29,12 @@ pub(crate) struct Inner { default: ResourceHandler, encoding: ContentEncoding, resources: Vec>, - handlers: Vec<(String, Box>)>, + handlers: Vec>, +} + +enum PrefixHandlerType { + Handler(String, Box>), + Scope(Resource, Box>), } impl PipelineHandler for Inner { @@ -44,7 +49,10 @@ impl PipelineHandler for Inner { HandlerType::Normal(idx) => { self.resources[idx].handle(req, Some(&mut self.default)) } - HandlerType::Handler(idx) => self.handlers[idx].1.handle(req), + HandlerType::Handler(idx) => match self.handlers[idx] { + PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req), + PrefixHandlerType::Scope(_, ref mut hnd) => hnd.handle(req), + }, HandlerType::Default => self.default.handle(req, None), } } @@ -62,27 +70,49 @@ impl HttpApplication { HandlerType::Normal(idx) } else { let inner = self.as_ref(); + let path: &'static str = + unsafe { &*(&req.path()[inner.prefix..] as *const _) }; + let path_len = path.len(); for idx in 0..inner.handlers.len() { - let &(ref prefix, _) = &inner.handlers[idx]; - let m = { - let path = &req.path()[inner.prefix..]; - path.starts_with(prefix) - && (path.len() == prefix.len() - || path.split_at(prefix.len()).1.starts_with('/')) - }; + match &inner.handlers[idx] { + PrefixHandlerType::Handler(ref prefix, _) => { + let m = { + path.starts_with(prefix) + && (path_len == prefix.len() + || path.split_at(prefix.len()).1.starts_with('/')) + }; - if m { - let prefix_len = inner.prefix + prefix.len(); - let path: &'static str = - unsafe { &*(&req.path()[prefix_len..] as *const _) }; + if m { + let prefix_len = inner.prefix + prefix.len(); + let path: &'static str = + unsafe { &*(&req.path()[prefix_len..] as *const _) }; - req.set_prefix_len(prefix_len as u16); - if path.is_empty() { - req.match_info_mut().add("tail", "/"); - } else { - req.match_info_mut().add("tail", path); + req.set_prefix_len(prefix_len as u16); + if path.is_empty() { + req.match_info_mut().add("tail", "/"); + } else { + req.match_info_mut().add("tail", path); + } + return HandlerType::Handler(idx); + } + } + PrefixHandlerType::Scope(ref pattern, _) => { + if let Some(prefix_len) = + pattern.match_prefix_with_params(path, req.match_info_mut()) + { + let prefix_len = inner.prefix + prefix_len - 1; + let path: &'static str = + unsafe { &*(&req.path()[prefix_len..] as *const _) }; + + req.set_prefix_len(prefix_len as u16); + if path.is_empty() { + req.match_info_mut().set("tail", "/"); + } else { + req.match_info_mut().set("tail", path); + } + return HandlerType::Handler(idx); + } } - return HandlerType::Handler(idx); } } HandlerType::Default @@ -131,7 +161,7 @@ struct ApplicationParts { settings: ServerSettings, default: ResourceHandler, resources: Vec<(Resource, Option>)>, - handlers: Vec<(String, Box>)>, + handlers: Vec>, external: HashMap, encoding: ContentEncoding, middlewares: Vec>>, @@ -305,7 +335,7 @@ where /// Configure scope for common root path. /// /// Scopes collect multiple paths under a common path prefix. - /// Scope path can not contain variable path segments as resources. + /// Scope path can contain variable path segments as resources. /// /// ```rust /// # extern crate actix_web; @@ -313,7 +343,7 @@ where /// /// fn main() { /// let app = App::new() - /// .scope("/app", |scope| { + /// .scope("/{project_id}", |scope| { /// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) @@ -322,9 +352,9 @@ where /// ``` /// /// In the above example, three routes get added: - /// * /app/path1 - /// * /app/path2 - /// * /app/path3 + /// * /{project_id}/path1 + /// * /{project_id}/path2 + /// * /{project_id}/path3 /// pub fn scope(mut self, path: &str, f: F) -> App where @@ -337,9 +367,15 @@ where if !path.is_empty() && !path.starts_with('/') { path.insert(0, '/') } + if !path.ends_with('/') { + path.push('/'); + } let parts = self.parts.as_mut().expect("Use after finish"); - parts.handlers.push((path, scope)); + parts.handlers.push(PrefixHandlerType::Scope( + Resource::prefix("", &path), + scope, + )); } self } @@ -496,11 +532,15 @@ where if !path.is_empty() && !path.starts_with('/') { path.insert(0, '/') } + if path.len() > 1 && path.ends_with('/') { + path.pop(); + } let parts = self.parts.as_mut().expect("Use after finish"); - parts - .handlers - .push((path, Box::new(WrapHandler::new(handler)))); + parts.handlers.push(PrefixHandlerType::Handler( + path, + Box::new(WrapHandler::new(handler)), + )); } self } diff --git a/src/error.rs b/src/error.rs index 9482e067..8c46774e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -850,7 +850,7 @@ mod tests { } macro_rules! from { - ($from: expr => $error: pat) => { + ($from:expr => $error:pat) => { match ParseError::from($from) { e @ $error => { assert!(format!("{}", e).len() >= 5); @@ -861,7 +861,7 @@ mod tests { } macro_rules! from_and_cause { - ($from: expr => $error: pat) => { + ($from:expr => $error:pat) => { match ParseError::from($from) { e @ $error => { let desc = format!("{}", e.cause().unwrap()); diff --git a/src/router.rs b/src/router.rs index 2c7d5c32..1e7126b6 100644 --- a/src/router.rs +++ b/src/router.rs @@ -142,7 +142,8 @@ enum PatternElement { #[derive(Clone, Debug)] enum PatternType { Static(String), - Dynamic(Regex, Vec), + Prefix(String), + Dynamic(Regex, Vec, usize), } #[derive(Debug, Copy, Clone, PartialEq)] @@ -173,14 +174,23 @@ impl Resource { /// /// Panics if path pattern is wrong. pub fn new(name: &str, path: &str) -> Self { - Resource::with_prefix(name, path, "/") + Resource::with_prefix(name, path, "/", false) + } + + /// Parse path pattern and create new `Resource` instance. + /// + /// Use `prefix` type instead of `static`. + /// + /// Panics if path regex pattern is wrong. + pub fn prefix(name: &str, path: &str) -> Self { + Resource::with_prefix(name, path, "/", true) } /// Construct external resource /// /// Panics if path pattern is wrong. pub fn external(name: &str, path: &str) -> Self { - let mut resource = Resource::with_prefix(name, path, "/"); + let mut resource = Resource::with_prefix(name, path, "/", false); resource.rtp = ResourceType::External; resource } @@ -197,8 +207,9 @@ impl Resource { } /// Parse path pattern and create new `Resource` instance with custom prefix - pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self { - let (pattern, elements, is_dynamic) = Resource::parse(path, prefix); + pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self { + let (pattern, elements, is_dynamic, len) = + Resource::parse(path, prefix, for_prefix); let tp = if is_dynamic { let re = match Regex::new(&pattern) { @@ -208,7 +219,9 @@ impl Resource { let names = re.capture_names() .filter_map(|name| name.map(|name| name.to_owned())) .collect(); - PatternType::Dynamic(re, names) + PatternType::Dynamic(re, names, len) + } else if for_prefix { + PatternType::Prefix(pattern.clone()) } else { PatternType::Static(pattern.clone()) }; @@ -240,7 +253,8 @@ impl Resource { pub fn is_match(&self, path: &str) -> bool { match self.tp { PatternType::Static(ref s) => s == path, - PatternType::Dynamic(ref re, _) => re.is_match(path), + PatternType::Dynamic(ref re, _, _) => re.is_match(path), + PatternType::Prefix(ref s) => path.starts_with(s), } } @@ -249,7 +263,7 @@ impl Resource { ) -> bool { match self.tp { PatternType::Static(ref s) => s == path, - PatternType::Dynamic(ref re, ref names) => { + PatternType::Dynamic(ref re, ref names, _) => { if let Some(captures) = re.captures(path) { let mut idx = 0; for capture in captures.iter() { @@ -265,6 +279,42 @@ impl Resource { false } } + PatternType::Prefix(ref s) => path.starts_with(s), + } + } + + pub fn match_prefix_with_params<'a>( + &'a self, path: &'a str, params: &'a mut Params<'a>, + ) -> Option { + match self.tp { + PatternType::Static(ref s) => if s == path { + Some(s.len()) + } else { + None + }, + PatternType::Dynamic(ref re, ref names, len) => { + if let Some(captures) = re.captures(path) { + let mut idx = 0; + let mut pos = 0; + for capture in captures.iter() { + if let Some(ref m) = capture { + if idx != 0 { + params.add(names[idx - 1].as_str(), m.as_str()); + } + idx += 1; + pos = m.end(); + } + } + Some(pos + len) + } else { + None + } + } + PatternType::Prefix(ref s) => if path.starts_with(s) { + Some(s.len()) + } else { + None + }, } } @@ -297,7 +347,9 @@ impl Resource { Ok(path) } - fn parse(pattern: &str, prefix: &str) -> (String, Vec, bool) { + fn parse( + pattern: &str, prefix: &str, for_prefix: bool, + ) -> (String, Vec, bool, usize) { const DEFAULT_PATTERN: &str = "[^/]+"; let mut re1 = String::from("^") + prefix; @@ -309,6 +361,7 @@ impl Resource { let mut param_pattern = String::from(DEFAULT_PATTERN); let mut is_dynamic = false; let mut elems = Vec::new(); + let mut len = 0; for (index, ch) in pattern.chars().enumerate() { // All routes must have a leading slash so its optional to have one @@ -325,6 +378,7 @@ impl Resource { param_name.clear(); param_pattern = String::from(DEFAULT_PATTERN); + len = 0; in_param_pattern = false; in_param = false; } else if ch == ':' { @@ -348,16 +402,19 @@ impl Resource { re1.push_str(escape(&ch.to_string()).as_str()); re2.push(ch); el.push(ch); + len += 1; } } let re = if is_dynamic { - re1.push('$'); + if !for_prefix { + re1.push('$'); + } re1 } else { re2 }; - (re, elems, is_dynamic) + (re, elems, is_dynamic, len) } } @@ -570,6 +627,41 @@ mod tests { assert_eq!(req.match_info().get("id").unwrap(), "adahg32"); } + #[test] + fn test_resource_prefix() { + let re = Resource::prefix("test", "/name"); + assert!(re.is_match("/name")); + assert!(re.is_match("/name/")); + assert!(re.is_match("/name/test/test")); + assert!(re.is_match("/name1")); + assert!(re.is_match("/name~")); + + let re = Resource::prefix("test", "/name/"); + assert!(re.is_match("/name/")); + assert!(re.is_match("/name/gs")); + assert!(!re.is_match("/name")); + } + + #[test] + fn test_reousrce_prefix_dynamic() { + let re = Resource::prefix("test", "/{name}/"); + assert!(re.is_match("/name/")); + assert!(re.is_match("/name/gs")); + assert!(!re.is_match("/name")); + + let mut req = TestRequest::with_uri("/test2/").finish(); + assert!(re.match_with_params("/test2/", req.match_info_mut())); + assert_eq!(&req.match_info()["name"], "test2"); + + let mut req = + TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); + assert!(re.match_with_params( + "/test2/subpath1/subpath2/index.html", + req.match_info_mut() + )); + assert_eq!(&req.match_info()["name"], "test2"); + } + #[test] fn test_request_resource() { let routes = vec![ diff --git a/src/scope.rs b/src/scope.rs index fedc7ced..b9ee0e8e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -21,7 +21,12 @@ type ScopeResources = Rc>>)>> /// /// Scope is a set of resources with common root path. /// Scopes collect multiple paths under a common path prefix. -/// Scope path can not contain variable path segments as resources. +/// Scope path can contain variable path segments as resources. +/// Scope prefix is always complete path segment, i.e `/app` would +/// be converted to a `/app/` and it would not match `/app` path. +/// +/// You can use variable path segments with `Path` extractor, also variable +/// segments are available in `HttpRequest::match_info()`. /// /// ```rust /// # extern crate actix_web; @@ -29,7 +34,7 @@ type ScopeResources = Rc>>)>> /// /// fn main() { /// let app = App::new() -/// .scope("/app", |scope| { +/// .scope("/{project_id}/", |scope| { /// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) @@ -38,12 +43,12 @@ type ScopeResources = Rc>>)>> /// ``` /// /// In the above example three routes get registered: -/// * /app/path1 - reponds to all http method -/// * /app/path2 - `GET` requests -/// * /app/path3 - `HEAD` requests +/// * /{project_id}/path1 - reponds to all http method +/// * /{project_id}/path2 - `GET` requests +/// * /{project_id}/path3 - `HEAD` requests /// pub struct Scope { - nested: Vec<(String, Route)>, + nested: Vec<(Resource, Route)>, middlewares: Rc>>>, default: Rc>>, resources: ScopeResources, @@ -102,12 +107,16 @@ impl Scope { if !path.is_empty() && !path.starts_with('/') { path.insert(0, '/') } + if !path.ends_with('/') { + path.push('/'); + } let handler = UnsafeCell::new(Box::new(Wrapper { scope, state: Rc::new(state), })); - self.nested.push((path, handler)); + self.nested + .push((Resource::prefix("", &path), handler)); self } @@ -149,9 +158,14 @@ impl Scope { if !path.is_empty() && !path.starts_with('/') { path.insert(0, '/') } + if !path.ends_with('/') { + path.push('/'); + } - self.nested - .push((path, UnsafeCell::new(Box::new(scope)))); + self.nested.push(( + Resource::prefix("", &path), + UnsafeCell::new(Box::new(scope)), + )); self } @@ -298,17 +312,14 @@ impl RouteHandler for Scope { } // nested scopes - for &(ref prefix, ref handler) in &self.nested { - let len = req.prefix_len() as usize; - let m = { - let path = &req.path()[len..]; - path.starts_with(prefix) - && (path.len() == prefix.len() - || path.split_at(prefix.len()).1.starts_with('/')) - }; + let len = req.prefix_len() as usize; + let path: &'static str = unsafe { &*(&req.path()[len..] as *const _) }; - if m { - let prefix_len = len + prefix.len(); + for &(ref prefix, ref handler) in &self.nested { + if let Some(prefix_len) = + prefix.match_prefix_with_params(path, req.match_info_mut()) + { + let prefix_len = len + prefix_len - 1; let path: &'static str = unsafe { &*(&req.path()[prefix_len..] as *const _) }; @@ -698,7 +709,10 @@ impl Response { #[cfg(test)] mod tests { + use bytes::Bytes; + use application::App; + use body::Body; use http::StatusCode; use httpresponse::HttpResponse; use test::TestRequest; @@ -716,6 +730,36 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::OK); } + #[test] + fn test_scope_variable_segment() { + let mut app = App::new() + .scope("/ab-{project}", |scope| { + scope.resource("/path1", |r| { + r.f(|r| { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + }) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/ab-project1/path1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + match resp.as_msg().body() { + Body::Binary(ref b) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project1")); + } + _ => panic!(), + } + + let req = TestRequest::with_uri("/aa-project1/path1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + } + #[test] fn test_scope_with_state() { struct State; @@ -748,6 +792,73 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } + #[test] + fn test_nested_scope_with_variable_segment() { + let mut app = App::new() + .scope("/app", |scope| { + scope.nested("/{project_id}", |scope| { + scope.resource("/path1", |r| { + r.f(|r| { + HttpResponse::Created().body(format!( + "project: {}", + &r.match_info()["project_id"] + )) + }) + }) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/project_1/path1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); + + match resp.as_msg().body() { + Body::Binary(ref b) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project_1")); + } + _ => panic!(), + } + } + + #[test] + fn test_nested2_scope_with_variable_segment() { + let mut app = App::new() + .scope("/app", |scope| { + scope.nested("/{project}", |scope| { + scope.nested("/{id}", |scope| { + scope.resource("/path1", |r| { + r.f(|r| { + HttpResponse::Created().body(format!( + "project: {} - {}", + &r.match_info()["project"], + &r.match_info()["id"], + )) + }) + }) + }) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/test/1/path1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); + + match resp.as_msg().body() { + Body::Binary(ref b) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); + } + _ => panic!(), + } + + let req = TestRequest::with_uri("/app/test/1/path2").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + } + #[test] fn test_default_resource() { let mut app = App::new() diff --git a/src/ws/context.rs b/src/ws/context.rs index 723b215a..6114a030 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -13,9 +13,9 @@ use context::{ActorHttpContext, Drain, Frame as ContextFrame}; use error::{Error, ErrorInternalServerError}; use httprequest::HttpRequest; -use ws::WsWriter; use ws::frame::Frame; use ws::proto::{CloseReason, OpCode}; +use ws::WsWriter; /// Execution context for `WebSockets` actors pub struct WebsocketContext diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 9a6bf004..99151afd 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -551,7 +551,7 @@ fn test_async_middleware_multiple() { assert_eq!(num1.load(Ordering::Relaxed), 2); assert_eq!(num2.load(Ordering::Relaxed), 2); - thread::sleep(Duration::from_millis(30)); + thread::sleep(Duration::from_millis(50)); assert_eq!(num3.load(Ordering::Relaxed), 2); } From c755d71a8b25c4243232ff8599349ee9d603918a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 7 May 2018 14:40:04 -0700 Subject: [PATCH 0194/1635] add filters support to scopes --- src/application.rs | 21 ++++-- src/scope.rs | 169 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 169 insertions(+), 21 deletions(-) diff --git a/src/application.rs b/src/application.rs index 20dfa790..4b5747a4 100644 --- a/src/application.rs +++ b/src/application.rs @@ -9,6 +9,7 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::Middleware; use pipeline::{HandlerType, Pipeline, PipelineHandler}; +use pred::Predicate; use resource::ResourceHandler; use router::{Resource, Router}; use scope::Scope; @@ -34,7 +35,7 @@ pub(crate) struct Inner { enum PrefixHandlerType { Handler(String, Box>), - Scope(Resource, Box>), + Scope(Resource, Box>, Vec>>), } impl PipelineHandler for Inner { @@ -51,7 +52,7 @@ impl PipelineHandler for Inner { } HandlerType::Handler(idx) => match self.handlers[idx] { PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req), - PrefixHandlerType::Scope(_, ref mut hnd) => hnd.handle(req), + PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req), }, HandlerType::Default => self.default.handle(req, None), } @@ -73,8 +74,8 @@ impl HttpApplication { let path: &'static str = unsafe { &*(&req.path()[inner.prefix..] as *const _) }; let path_len = path.len(); - for idx in 0..inner.handlers.len() { - match &inner.handlers[idx] { + 'outer: for idx in 0..inner.handlers.len() { + match inner.handlers[idx] { PrefixHandlerType::Handler(ref prefix, _) => { let m = { path.starts_with(prefix) @@ -96,10 +97,16 @@ impl HttpApplication { return HandlerType::Handler(idx); } } - PrefixHandlerType::Scope(ref pattern, _) => { + PrefixHandlerType::Scope(ref pattern, _, ref filters) => { if let Some(prefix_len) = pattern.match_prefix_with_params(path, req.match_info_mut()) { + for filter in filters { + if !filter.check(req) { + continue 'outer; + } + } + let prefix_len = inner.prefix + prefix_len - 1; let path: &'static str = unsafe { &*(&req.path()[prefix_len..] as *const _) }; @@ -361,7 +368,7 @@ where F: FnOnce(Scope) -> Scope, { { - let scope = Box::new(f(Scope::new())); + let mut scope = Box::new(f(Scope::new())); let mut path = path.trim().trim_right_matches('/').to_owned(); if !path.is_empty() && !path.starts_with('/') { @@ -372,9 +379,11 @@ where } let parts = self.parts.as_mut().expect("Use after finish"); + let filters = scope.take_filters(); parts.handlers.push(PrefixHandlerType::Scope( Resource::prefix("", &path), scope, + filters, )); } self diff --git a/src/scope.rs b/src/scope.rs index b9ee0e8e..74009841 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,5 +1,6 @@ use std::cell::UnsafeCell; use std::marker::PhantomData; +use std::mem; use std::rc::Rc; use futures::{Async, Future, Poll}; @@ -11,11 +12,13 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted}; +use pred::Predicate; use resource::ResourceHandler; use router::Resource; type Route = UnsafeCell>>; type ScopeResources = Rc>>)>>; +type NestedInfo = (Resource, Route, Vec>>); /// Resources scope /// @@ -25,8 +28,8 @@ type ScopeResources = Rc>>)>> /// Scope prefix is always complete path segment, i.e `/app` would /// be converted to a `/app/` and it would not match `/app` path. /// -/// You can use variable path segments with `Path` extractor, also variable -/// segments are available in `HttpRequest::match_info()`. +/// You can get variable path segments from HttpRequest::match_info()`. +/// `Path` extractor also is able to extract scope level variable segments. /// /// ```rust /// # extern crate actix_web; @@ -48,7 +51,8 @@ type ScopeResources = Rc>>)>> /// * /{project_id}/path3 - `HEAD` requests /// pub struct Scope { - nested: Vec<(Resource, Route)>, + filters: Vec>>, + nested: Vec>, middlewares: Rc>>>, default: Rc>>, resources: ScopeResources, @@ -63,6 +67,7 @@ impl Default for Scope { impl Scope { pub fn new() -> Scope { Scope { + filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), @@ -70,6 +75,36 @@ impl Scope { } } + #[inline] + pub(crate) fn take_filters(&mut self) -> Vec>> { + mem::replace(&mut self.filters, Vec::new()) + } + + /// Add match predicate to scoupe. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, pred, App, HttpRequest, HttpResponse, Path}; + /// + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .scope("/app", |scope| { + /// scope.filter(pred::Header("content-type", "text/plain")) + /// .route("/test1", http::Method::GET, index) + /// .route("/test2", http::Method::POST, + /// |_: HttpRequest| HttpResponse::MethodNotAllowed()) + /// }); + /// } + /// ``` + pub fn filter + 'static>(mut self, p: T) -> Self { + self.filters.push(Box::new(p)); + self + } + /// Create nested scope with new state. /// /// ```rust @@ -96,12 +131,13 @@ impl Scope { F: FnOnce(Scope) -> Scope, { let scope = Scope { + filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), }; - let scope = f(scope); + let mut scope = f(scope); let mut path = path.trim().trim_right_matches('/').to_owned(); if !path.is_empty() && !path.starts_with('/') { @@ -111,12 +147,14 @@ impl Scope { path.push('/'); } - let handler = UnsafeCell::new(Box::new(Wrapper { - scope, - state: Rc::new(state), - })); + let state = Rc::new(state); + let filters: Vec>> = vec![Box::new(FiltersWrapper { + state: Rc::clone(&state), + filters: scope.take_filters(), + })]; + let handler = UnsafeCell::new(Box::new(Wrapper { scope, state })); self.nested - .push((Resource::prefix("", &path), handler)); + .push((Resource::prefix("", &path), handler, filters)); self } @@ -147,12 +185,13 @@ impl Scope { F: FnOnce(Scope) -> Scope, { let scope = Scope { + filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), }; - let scope = f(scope); + let mut scope = f(scope); let mut path = path.trim().trim_right_matches('/').to_owned(); if !path.is_empty() && !path.starts_with('/') { @@ -162,9 +201,11 @@ impl Scope { path.push('/'); } + let filters = scope.take_filters(); self.nested.push(( Resource::prefix("", &path), UnsafeCell::new(Box::new(scope)), + filters, )); self @@ -315,10 +356,15 @@ impl RouteHandler for Scope { let len = req.prefix_len() as usize; let path: &'static str = unsafe { &*(&req.path()[len..] as *const _) }; - for &(ref prefix, ref handler) in &self.nested { + 'outer: for &(ref prefix, ref handler, ref filters) in &self.nested { if let Some(prefix_len) = prefix.match_prefix_with_params(path, req.match_info_mut()) { + for filter in filters { + if !filter.check(&mut req) { + continue 'outer; + } + } let prefix_len = len + prefix_len - 1; let path: &'static str = unsafe { &*(&req.path()[prefix_len..] as *const _) }; @@ -363,6 +409,23 @@ impl RouteHandler for Wrapper { } } +struct FiltersWrapper { + state: Rc, + filters: Vec>>, +} + +impl Predicate for FiltersWrapper { + fn check(&self, req: &mut HttpRequest) -> bool { + let mut req = req.change_state(Rc::clone(&self.state)); + for filter in &self.filters { + if !filter.check(&mut req) { + return false; + } + } + true + } +} + /// Compose resource level middlewares with route handler. struct Compose { info: ComposeInfo, @@ -713,8 +776,9 @@ mod tests { use application::App; use body::Body; - use http::StatusCode; + use http::{Method, StatusCode}; use httpresponse::HttpResponse; + use pred; use test::TestRequest; #[test] @@ -730,6 +794,29 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::OK); } + #[test] + fn test_scope_filter() { + let mut app = App::new() + .scope("/app", |scope| { + scope + .filter(pred::Get()) + .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + }) + .finish(); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::GET) + .finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + } + #[test] fn test_scope_variable_segment() { let mut app = App::new() @@ -748,7 +835,7 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::OK); match resp.as_msg().body() { - Body::Binary(ref b) => { + &Body::Binary(ref b) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: project1")); } @@ -777,6 +864,33 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } + #[test] + fn test_scope_with_state_filter() { + struct State; + + let mut app = App::new() + .scope("/app", |scope| { + scope.with_state("/t1", State, |scope| { + scope + .filter(pred::Get()) + .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::POST) + .finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::GET) + .finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + } + #[test] fn test_nested_scope() { let mut app = App::new() @@ -792,6 +906,31 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } + #[test] + fn test_nested_scope_filter() { + let mut app = App::new() + .scope("/app", |scope| { + scope.nested("/t1", |scope| { + scope + .filter(pred::Get()) + .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::POST) + .finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::GET) + .finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + } + #[test] fn test_nested_scope_with_variable_segment() { let mut app = App::new() @@ -814,7 +953,7 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); match resp.as_msg().body() { - Body::Binary(ref b) => { + &Body::Binary(ref b) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: project_1")); } @@ -847,7 +986,7 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); match resp.as_msg().body() { - Body::Binary(ref b) => { + &Body::Binary(ref b) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); } From 72908d974c642785f83a8b0d28d800ab26308cee Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 7 May 2018 15:19:03 -0700 Subject: [PATCH 0195/1635] test for Scope::route(); prep release --- CHANGES.md | 2 +- Cargo.toml | 2 +- src/scope.rs | 36 +++++++++++++++++++++++++++--------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3955251d..c0ba7dd2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## 0.6.0 (...) +## 0.6.0 (2018-05-08) * Add route scopes #202 diff --git a/Cargo.toml b/Cargo.toml index a0bdf708..fe63ef8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.0-dev" +version = "0.6.0" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/scope.rs b/src/scope.rs index 74009841..e5d160bb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -28,7 +28,7 @@ type NestedInfo = (Resource, Route, Vec>>); /// Scope prefix is always complete path segment, i.e `/app` would /// be converted to a `/app/` and it would not match `/app` path. /// -/// You can get variable path segments from HttpRequest::match_info()`. +/// You can get variable path segments from `HttpRequest::match_info()`. /// `Path` extractor also is able to extract scope level variable segments. /// /// ```rust @@ -50,6 +50,7 @@ type NestedInfo = (Resource, Route, Vec>>); /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests /// +#[derive(Default)] pub struct Scope { filters: Vec>>, nested: Vec>, @@ -58,12 +59,7 @@ pub struct Scope { resources: ScopeResources, } -impl Default for Scope { - fn default() -> Scope { - Scope::new() - } -} - +#[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] impl Scope { pub fn new() -> Scope { Scope { @@ -319,7 +315,7 @@ impl Scope { /// middlewares get invoked on scope level. /// /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to peer. + /// prepared. It does not wait until body get sent to the peer. pub fn middleware>(mut self, mw: M) -> Scope { Rc::get_mut(&mut self.middlewares) .expect("Can not use after configuration") @@ -333,7 +329,7 @@ impl RouteHandler for Scope { let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; let path = if path == "" { "/" } else { path }; - // recognize paths + // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { if pattern.match_with_params(path, req.match_info_mut()) { let default = unsafe { &mut *self.default.as_ref().get() }; @@ -777,6 +773,7 @@ mod tests { use application::App; use body::Body; use http::{Method, StatusCode}; + use httprequest::HttpRequest; use httpresponse::HttpResponse; use pred; use test::TestRequest; @@ -794,6 +791,27 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::OK); } + #[test] + fn test_scope_route() { + let mut app = App::new() + .scope("app", |scope| { + scope.route("/path1", Method::GET, |r: HttpRequest<_>| { + HttpResponse::Ok() + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/path1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + } + #[test] fn test_scope_filter() { let mut app = App::new() From 3c6c1268c98225b153d6e7ae87b54477d4fc1fc5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 7 May 2018 15:54:29 -0700 Subject: [PATCH 0196/1635] travis cover report --- .travis.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 415d792d..94cdb08b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,17 @@ before_script: script: - | + if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then cargo clean cargo test --features="alpn,tls" -- --nocapture + fi + - | + if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then + bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) + USE_SKEPTIC=1 cargo tarpaulin --out Xml + bash <(curl -s https://codecov.io/bash) + echo "Uploaded code coverage" + fi # Upload docs after_success: @@ -44,11 +53,3 @@ after_success: ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && echo "Uploaded documentation" fi - - - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then - bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - USE_SKEPTIC=1 cargo tarpaulin --out Xml - bash <(curl -s https://codecov.io/bash) - echo "Uploaded code coverage" - fi From 8cda36286673c887e6ae4b4eb17d18cef71d4a42 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 7 May 2018 16:09:41 -0700 Subject: [PATCH 0197/1635] simplify pipeline --- src/pipeline.rs | 25 ++++++++----------------- src/route.rs | 21 ++++++--------------- src/scope.rs | 37 +++++++++++++++++++------------------ 3 files changed, 33 insertions(+), 50 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index 1b621882..25fe11e1 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -251,23 +251,14 @@ impl> StartMiddlewares { Ok(Started::Response(resp)) => { return RunMiddlewares::init(info, resp) } - Ok(Started::Future(mut fut)) => match fut.poll() { - Ok(Async::NotReady) => { - return PipelineState::Starting(StartMiddlewares { - hnd, - htype, - fut: Some(fut), - _s: PhantomData, - }) - } - Ok(Async::Ready(resp)) => { - if let Some(resp) = resp { - return RunMiddlewares::init(info, resp); - } - info.count += 1; - } - Err(err) => return ProcessResponse::init(err.into()), - }, + Ok(Started::Future(fut)) => { + return PipelineState::Starting(StartMiddlewares { + hnd, + htype, + fut: Some(fut), + _s: PhantomData, + }) + } Err(err) => return ProcessResponse::init(err.into()), } } diff --git a/src/route.rs b/src/route.rs index d17f13f1..215a7f22 100644 --- a/src/route.rs +++ b/src/route.rs @@ -346,21 +346,12 @@ impl StartMiddlewares { Ok(MiddlewareStarted::Response(resp)) => { return RunMiddlewares::init(info, resp) } - Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() { - Ok(Async::NotReady) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }) - } - Ok(Async::Ready(resp)) => { - if let Some(resp) = resp { - return RunMiddlewares::init(info, resp); - } - info.count += 1; - } - Err(err) => return FinishingMiddlewares::init(info, err.into()), - }, + Ok(MiddlewareStarted::Future(fut)) => { + return ComposeState::Starting(StartMiddlewares { + fut: Some(fut), + _s: PhantomData, + }) + } Err(err) => return FinishingMiddlewares::init(info, err.into()), } } diff --git a/src/scope.rs b/src/scope.rs index e5d160bb..c399ac76 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -521,21 +521,12 @@ impl StartMiddlewares { Ok(MiddlewareStarted::Response(resp)) => { return RunMiddlewares::init(info, resp) } - Ok(MiddlewareStarted::Future(mut fut)) => match fut.poll() { - Ok(Async::NotReady) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }) - } - Ok(Async::Ready(resp)) => { - if let Some(resp) = resp { - return RunMiddlewares::init(info, resp); - } - info.count += 1; - } - Err(err) => return Response::init(err.into()), - }, + Ok(MiddlewareStarted::Future(fut)) => { + return ComposeState::Starting(StartMiddlewares { + fut: Some(fut), + _s: PhantomData, + }) + } Err(err) => return Response::init(err.into()), } } @@ -795,9 +786,13 @@ mod tests { fn test_scope_route() { let mut app = App::new() .scope("app", |scope| { - scope.route("/path1", Method::GET, |r: HttpRequest<_>| { - HttpResponse::Ok() - }) + scope + .route("/path1", Method::GET, |r: HttpRequest<_>| { + HttpResponse::Ok() + }) + .route("/path1", Method::DELETE, |r: HttpRequest<_>| { + HttpResponse::Ok() + }) }) .finish(); @@ -805,6 +800,12 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .finish(); From ecda97aadd0c5bee5e66a89d32e62860f4e56096 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 05:54:06 -0700 Subject: [PATCH 0198/1635] update doc string --- src/client/connector.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/connector.rs b/src/client/connector.rs index d9fa46d1..dea00ea2 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -347,6 +347,7 @@ impl ClientConnector { /// Keep-alive period is the period between connection usage. If /// the delay between repeated usages of the same connection /// exceeds this period, the connection is closed. + /// Default keep-alive period is 15 seconds. pub fn conn_keep_alive(mut self, dur: Duration) -> Self { self.conn_keep_alive = dur; self @@ -356,6 +357,7 @@ impl ClientConnector { /// /// Connection lifetime is max lifetime of any opened connection /// until it is closed regardless of keep-alive period. + /// Default lifetime period is 75 seconds. pub fn conn_lifetime(mut self, dur: Duration) -> Self { self.conn_lifetime = dur; self From b3cc43bb9b598e145c3c9e4be6ebc79acec28eff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 13:41:04 -0700 Subject: [PATCH 0199/1635] Fix connector's default keep-alive and lifetime settings #212 --- CHANGES.md | 4 ++++ MIGRATION.md | 14 ++++++++++++++ src/client/connector.rs | 8 ++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c0ba7dd2..a96fd331 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 0.6.1 (2018-05-xx) + +* Fix connector's default `keep-alive` and `lifetime` settings #212 + ## 0.6.0 (2018-05-08) * Add route scopes #202 diff --git a/MIGRATION.md b/MIGRATION.md index e2da4004..984f3a3d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -11,6 +11,17 @@ * `HttpRequest::extensions()` returns read only reference to the request's Extension `HttpRequest::extensions_mut()` returns mutable reference. +* Instead of + + `use actix_web::middleware::{ + CookieSessionBackend, CookieSessionError, RequestSession, + Session, SessionBackend, SessionImpl, SessionStorage};` + + use `actix_web::middleware::session` + + `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, + RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` + * `FromRequest::from_request()` accepts mutable reference to a request * `FromRequest::Result` has to implement `Into>` @@ -33,6 +44,9 @@ let q = Query::>::extract(req); ``` +* Websocket operations are implemented as `WsWriter` trait. + you need to use `use actix_web::ws::WsWriter` + ## Migration from 0.4 to 0.5 diff --git a/src/client/connector.rs b/src/client/connector.rs index dea00ea2..e082c9ed 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -223,8 +223,8 @@ impl Default for ClientConnector { pool: Rc::new(Pool::new(Rc::clone(&_modified))), pool_modified: _modified, connector: builder.build().unwrap(), - conn_lifetime: Duration::from_secs(15), - conn_keep_alive: Duration::from_secs(75), + conn_lifetime: Duration::from_secs(75), + conn_keep_alive: Duration::from_secs(15), limit: 100, limit_per_host: 0, acquired: 0, @@ -243,8 +243,8 @@ impl Default for ClientConnector { subscriber: None, pool: Rc::new(Pool::new(Rc::clone(&_modified))), pool_modified: _modified, - conn_lifetime: Duration::from_secs(15), - conn_keep_alive: Duration::from_secs(75), + conn_lifetime: Duration::from_secs(75), + conn_keep_alive: Duration::from_secs(15), limit: 100, limit_per_host: 0, acquired: 0, From 6f75b0e95e11c6d34288796433795de3cfdbd617 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Tue, 8 May 2018 22:45:28 +0200 Subject: [PATCH 0200/1635] let Path::from_request() fail with ErrorNotFound --- CHANGES.md | 2 ++ src/extractor.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a96fd331..a42cbe6a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ * Fix connector's default `keep-alive` and `lifetime` settings #212 +* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214 + ## 0.6.0 (2018-05-08) * Add route scopes #202 diff --git a/src/extractor.rs b/src/extractor.rs index 1f06f782..a08e9667 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -11,7 +11,7 @@ use serde::de::{self, DeserializeOwned}; use serde_urlencoded; use de::PathDeserializer; -use error::{Error, ErrorBadRequest}; +use error::{Error, ErrorNotFound, ErrorBadRequest}; use handler::{AsyncResult, FromRequest}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; @@ -108,7 +108,7 @@ where fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); de::Deserialize::deserialize(PathDeserializer::new(&req)) - .map_err(|e| e.into()) + .map_err(ErrorNotFound) .map(|inner| Path { inner }) } } From 47d80382b2830d075cf6298c5496c5eac2dc978f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 15:44:50 -0700 Subject: [PATCH 0201/1635] Fix http/2 payload streaming #215 --- CHANGES.md | 4 +++- Cargo.toml | 2 +- src/pipeline.rs | 4 ++-- src/scope.rs | 4 ++-- src/server/h2.rs | 25 ++++++++++++++----------- tests/test_server.rs | 2 +- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a42cbe6a..7be1c98e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes -## 0.6.1 (2018-05-xx) +## 0.6.1 (2018-05-08) + +* Fix http/2 payload streaming #215 * Fix connector's default `keep-alive` and `lifetime` settings #212 diff --git a/Cargo.toml b/Cargo.toml index fe63ef8a..d88fc4aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.0" +version = "0.6.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/pipeline.rs b/src/pipeline.rs index 25fe11e1..e00c0617 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -492,8 +492,8 @@ impl ProcessResponse { if let Some(err) = self.resp.error() { if self.resp.status().is_server_error() { error!( - "Error occured during request handling: {}", - err + "Error occured during request handling, status: {} {}", + self.resp.status(), err ); } else { warn!( diff --git a/src/scope.rs b/src/scope.rs index c399ac76..c5aefb69 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -787,10 +787,10 @@ mod tests { let mut app = App::new() .scope("app", |scope| { scope - .route("/path1", Method::GET, |r: HttpRequest<_>| { + .route("/path1", Method::GET, |_: HttpRequest<_>| { HttpResponse::Ok() }) - .route("/path1", Method::DELETE, |r: HttpRequest<_>| { + .route("/path1", Method::DELETE, |_: HttpRequest<_>| { HttpResponse::Ok() }) }) diff --git a/src/server/h2.rs b/src/server/h2.rs index e2013357..fc7824b2 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -343,24 +343,27 @@ impl Entry { } fn poll_payload(&mut self) { - if !self.flags.contains(EntryFlags::REOF) { - if self.payload.need_read() == PayloadStatus::Read { - if let Err(err) = self.recv.release_capacity().release_capacity(32_768) { - self.payload.set_error(PayloadError::Http2(err)) - } - } else if let Err(err) = self.recv.release_capacity().release_capacity(0) { - self.payload.set_error(PayloadError::Http2(err)) - } - + while !self.flags.contains(EntryFlags::REOF) + && self.payload.need_read() == PayloadStatus::Read + { match self.recv.poll() { Ok(Async::Ready(Some(chunk))) => { + let l = chunk.len(); self.payload.feed_data(chunk); + if let Err(err) = self.recv.release_capacity().release_capacity(l) { + self.payload.set_error(PayloadError::Http2(err)); + break; + } } Ok(Async::Ready(None)) => { self.flags.insert(EntryFlags::REOF); + self.payload.feed_eof(); + } + Ok(Async::NotReady) => break, + Err(err) => { + self.payload.set_error(PayloadError::Http2(err)); + break; } - Ok(Async::NotReady) => (), - Err(err) => self.payload.set_error(PayloadError::Http2(err)), } } } diff --git a/tests/test_server.rs b/tests/test_server.rs index e61cedd3..863f800a 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -809,7 +809,7 @@ fn test_h2() { }) }); let _res = core.run(tcp); - // assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref())); + // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); } #[test] From 54c33a7aff8063f60d6648aa50863081287cba96 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 16:30:34 -0700 Subject: [PATCH 0202/1635] Allow to exclude certain endpoints from logging #211 --- CHANGES.md | 2 ++ src/middleware/logger.rs | 35 +++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7be1c98e..cc8151dd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214 +* Allow to exclude certain endpoints from logging #211 + ## 0.6.0 (2018-05-08) * Add route scopes #202 diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index adfc3d2b..086c232c 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -1,7 +1,7 @@ //! Request logging middleware +use std::collections::HashSet; use std::env; -use std::fmt; -use std::fmt::{Display, Formatter}; +use std::fmt::{self, Display, Formatter}; use libc; use regex::Regex; @@ -74,6 +74,7 @@ use middleware::{Finished, Middleware, Started}; /// pub struct Logger { format: Format, + exclude: HashSet, } impl Logger { @@ -81,8 +82,15 @@ impl Logger { pub fn new(format: &str) -> Logger { Logger { format: Format::new(format), + exclude: HashSet::new(), } } + + /// Ignore and do not log access info for specified path. + pub fn exclude>(mut self, path: T) -> Self { + self.exclude.insert(path.into()); + self + } } impl Default for Logger { @@ -94,6 +102,7 @@ impl Default for Logger { fn default() -> Logger { Logger { format: Format::default(), + exclude: HashSet::new(), } } } @@ -102,21 +111,23 @@ struct StartTime(time::Tm); impl Logger { fn log(&self, req: &mut HttpRequest, resp: &HttpResponse) { - let entry_time = req.extensions().get::().unwrap().0; - - let render = |fmt: &mut Formatter| { - for unit in &self.format.0 { - unit.render(fmt, req, resp, entry_time)?; - } - Ok(()) - }; - info!("{}", FormatDisplay(&render)); + if let Some(entry_time) = req.extensions().get::() { + let render = |fmt: &mut Formatter| { + for unit in &self.format.0 { + unit.render(fmt, req, resp, entry_time.0)?; + } + Ok(()) + }; + info!("{}", FormatDisplay(&render)); + } } } impl Middleware for Logger { fn start(&self, req: &mut HttpRequest) -> Result { - req.extensions_mut().insert(StartTime(time::now())); + if !self.exclude.contains(req.path()) { + req.extensions_mut().insert(StartTime(time::now())); + } Ok(Started::Done) } From 7c395fcc83c6dfab8913a9e95007cccc1e9f2eba Mon Sep 17 00:00:00 2001 From: Luke Cowell Date: Tue, 8 May 2018 17:40:18 -0700 Subject: [PATCH 0203/1635] replace typo `scoupe` with `scope` --- src/scope.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scope.rs b/src/scope.rs index c5aefb69..aecfd6bf 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -76,7 +76,7 @@ impl Scope { mem::replace(&mut self.filters, Vec::new()) } - /// Add match predicate to scoupe. + /// Add match predicate to scope. /// /// ```rust /// # extern crate actix_web; From c26c5fd9a4b47db5de0a22652021c3e897d3fe41 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 18:34:36 -0700 Subject: [PATCH 0204/1635] prep release --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index cc8151dd..a2d3ae25 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ * Allow to exclude certain endpoints from logging #211 + ## 0.6.0 (2018-05-08) * Add route scopes #202 From 7c4941f8681caac7296c1d6312023fae62aa5653 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 18:48:09 -0700 Subject: [PATCH 0205/1635] update migration doc --- MIGRATION.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 984f3a3d..f6829a50 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,7 @@ ## Migration from 0.5 to 0.6 +* `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` + * `ws::Message::Close` now includes optional close reason. `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. From be12d5e6fc8e3474e65f25a50f88d4668ad754b1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 9 May 2018 05:48:06 -0700 Subject: [PATCH 0206/1635] make WsWriter trait optional --- CHANGES.md | 5 +++ src/lib.rs | 2 ++ src/ws/client.rs | 34 ++++++++++++++++-- src/ws/context.rs | 90 +++++++++++++++++++++++++++++++---------------- src/ws/mod.rs | 10 +++--- 5 files changed, 104 insertions(+), 37 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a2d3ae25..51267c76 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.6.2 (2018-05-09) + +* WsWriter trait is optional. + + ## 0.6.1 (2018-05-08) * Fix http/2 payload streaming #215 diff --git a/src/lib.rs b/src/lib.rs index 92a319bc..62d8fd83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,6 +175,8 @@ pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; pub use scope::Scope; + +#[doc(hidden)] pub use ws::WsWriter; #[cfg(feature = "openssl")] diff --git a/src/ws/client.rs b/src/ws/client.rs index aa392a90..780de03c 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -518,9 +518,7 @@ impl ClientWriter { fn as_mut(&mut self) -> &mut Inner { unsafe { &mut *self.inner.get() } } -} -impl WsWriter for ClientWriter { /// Send text frame #[inline] fn text>(&mut self, text: T) { @@ -561,3 +559,35 @@ impl WsWriter for ClientWriter { self.write(Frame::close(reason, true)); } } + +impl WsWriter for ClientWriter { + /// Send text frame + #[inline] + fn send_text>(&mut self, text: T) { + self.text(text) + } + + /// Send binary frame + #[inline] + fn send_binary>(&mut self, data: B) { + self.binary(data) + } + + /// Send ping frame + #[inline] + fn send_ping(&mut self, message: &str) { + self.ping(message) + } + + /// Send pong frame + #[inline] + fn send_pong(&mut self, message: &str) { + self.pong(message) + } + + /// Send close frame + #[inline] + fn send_close(&mut self, reason: Option) { + self.close(reason); + } +} diff --git a/src/ws/context.rs b/src/ws/context.rs index 6114a030..36f73e99 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -149,36 +149,6 @@ where Drain::new(rx) } - /// Check if connection still open - #[inline] - pub fn connected(&self) -> bool { - !self.disconnected - } - - #[inline] - fn add_frame(&mut self, frame: ContextFrame) { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - if let Some(s) = self.stream.as_mut() { - s.push(frame) - } - self.inner.modify(); - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } -} - -impl WsWriter for WebsocketContext -where - A: Actor, - S: 'static, -{ /// Send text frame #[inline] fn text>(&mut self, text: T) { @@ -218,6 +188,66 @@ where fn close(&mut self, reason: Option) { self.write(Frame::close(reason, false)); } + + /// Check if connection still open + #[inline] + pub fn connected(&self) -> bool { + !self.disconnected + } + + #[inline] + fn add_frame(&mut self, frame: ContextFrame) { + if self.stream.is_none() { + self.stream = Some(SmallVec::new()); + } + if let Some(s) = self.stream.as_mut() { + s.push(frame) + } + self.inner.modify(); + } + + /// Handle of the running future + /// + /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. + pub fn handle(&self) -> SpawnHandle { + self.inner.curr_handle() + } +} + +impl WsWriter for WebsocketContext +where + A: Actor, + S: 'static, +{ + /// Send text frame + #[inline] + fn send_text>(&mut self, text: T) { + self.text(text) + } + + /// Send binary frame + #[inline] + fn send_binary>(&mut self, data: B) { + self.binary(data) + } + + /// Send ping frame + #[inline] + fn send_ping(&mut self, message: &str) { + self.ping(message) + } + + /// Send pong frame + #[inline] + fn send_pong(&mut self, message: &str) { + self.pong(message) + } + + /// Send close frame + #[inline] + fn send_close(&mut self, reason: Option) { + self.close(reason) + } } impl ActorHttpContext for WebsocketContext diff --git a/src/ws/mod.rs b/src/ws/mod.rs index a39b95b1..9fb40dd9 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -343,15 +343,15 @@ where /// Common writing methods for a websocket. pub trait WsWriter { /// Send a text - fn text>(&mut self, text: T); + fn send_text>(&mut self, text: T); /// Send a binary - fn binary>(&mut self, data: B); + fn send_binary>(&mut self, data: B); /// Send a ping message - fn ping(&mut self, message: &str); + fn send_ping(&mut self, message: &str); /// Send a pong message - fn pong(&mut self, message: &str); + fn send_pong(&mut self, message: &str); /// Close the connection - fn close(&mut self, reason: Option); + fn send_close(&mut self, reason: Option); } #[cfg(test)] From b748bf3b0d99ee1ea72ac46b7870a589014109e1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 9 May 2018 06:05:16 -0700 Subject: [PATCH 0207/1635] make api public --- src/ws/client.rs | 10 +++++----- src/ws/context.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ws/client.rs b/src/ws/client.rs index 780de03c..92087efa 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -521,19 +521,19 @@ impl ClientWriter { /// Send text frame #[inline] - fn text>(&mut self, text: T) { + pub fn text>(&mut self, text: T) { self.write(Frame::message(text.into(), OpCode::Text, true, true)); } /// Send binary frame #[inline] - fn binary>(&mut self, data: B) { + pub fn binary>(&mut self, data: B) { self.write(Frame::message(data, OpCode::Binary, true, true)); } /// Send ping frame #[inline] - fn ping(&mut self, message: &str) { + pub fn ping(&mut self, message: &str) { self.write(Frame::message( Vec::from(message), OpCode::Ping, @@ -544,7 +544,7 @@ impl ClientWriter { /// Send pong frame #[inline] - fn pong(&mut self, message: &str) { + pub fn pong(&mut self, message: &str) { self.write(Frame::message( Vec::from(message), OpCode::Pong, @@ -555,7 +555,7 @@ impl ClientWriter { /// Send close frame #[inline] - fn close(&mut self, reason: Option) { + pub fn close(&mut self, reason: Option) { self.write(Frame::close(reason, true)); } } diff --git a/src/ws/context.rs b/src/ws/context.rs index 36f73e99..79c3aa35 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -151,19 +151,19 @@ where /// Send text frame #[inline] - fn text>(&mut self, text: T) { + pub fn text>(&mut self, text: T) { self.write(Frame::message(text.into(), OpCode::Text, true, false)); } /// Send binary frame #[inline] - fn binary>(&mut self, data: B) { + pub fn binary>(&mut self, data: B) { self.write(Frame::message(data, OpCode::Binary, true, false)); } /// Send ping frame #[inline] - fn ping(&mut self, message: &str) { + pub fn ping(&mut self, message: &str) { self.write(Frame::message( Vec::from(message), OpCode::Ping, @@ -174,7 +174,7 @@ where /// Send pong frame #[inline] - fn pong(&mut self, message: &str) { + pub fn pong(&mut self, message: &str) { self.write(Frame::message( Vec::from(message), OpCode::Pong, @@ -185,7 +185,7 @@ where /// Send close frame #[inline] - fn close(&mut self, reason: Option) { + pub fn close(&mut self, reason: Option) { self.write(Frame::close(reason, false)); } From b043c346326417782e7c1c176f025f3397a04248 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 9 May 2018 06:05:44 -0700 Subject: [PATCH 0208/1635] bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d88fc4aa..8c474cca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.1" +version = "0.6.2" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From e58b38fd13eddf086f96919e99fd2c63cc8e058f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 9 May 2018 06:12:16 -0700 Subject: [PATCH 0209/1635] deprecate WsWrite from top level mod --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 62d8fd83..8436a278 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,6 +177,7 @@ pub use json::Json; pub use scope::Scope; #[doc(hidden)] +#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")] pub use ws::WsWriter; #[cfg(feature = "openssl")] From 18575ee1ee26bfc54bea891d452b0a51f4b42b73 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 9 May 2018 16:27:31 -0700 Subject: [PATCH 0210/1635] Add Router::with_async() method for async handler registration --- CHANGES.md | 5 ++ build.rs | 11 +++- src/resource.rs | 21 +++++++ src/route.rs | 72 ++++++++++++++++++++- src/with.rs | 139 +++++++++++++++++++++++++++++++++++++++++ tests/test_handlers.rs | 78 +++++++++++++++++++++++ 6 files changed, 324 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 51267c76..cf4df3e3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.6.3 (2018-05-xx) + +* Add `Router::with_async()` method for async handler registration. + + ## 0.6.2 (2018-05-09) * WsWriter trait is optional. diff --git a/build.rs b/build.rs index 3b3001f9..7cb25c73 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,17 @@ extern crate version_check; fn main() { + let mut has_impl_trait = true; + + match version_check::is_min_version("1.26.0") { + Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"), + _ => (), + }; match version_check::is_nightly() { - Some(true) => println!("cargo:rustc-cfg=actix_nightly"), + Some(true) => { + println!("cargo:rustc-cfg=actix_nightly"); + println!("cargo:rustc-cfg=actix_impl_trait"); + } Some(false) => (), None => (), }; diff --git a/src/resource.rs b/src/resource.rs index fb08afd9..e52760f4 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,9 +1,11 @@ use std::marker::PhantomData; use std::rc::Rc; +use futures::Future; use http::{Method, StatusCode}; use smallvec::SmallVec; +use error::Error; use handler::{AsyncResult, FromRequest, Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -183,6 +185,25 @@ impl ResourceHandler { self.routes.last_mut().unwrap().with(handler); } + /// Register a new route and add async handler. + /// + /// This is shortcut for: + /// + /// ```rust,ignore + /// Application::resource("/", |r| r.route().with_async(index) + /// ``` + pub fn with_async(&mut self, handler: F) + where + F: Fn(T) -> R + 'static, + R: Future + 'static, + I: Responder + 'static, + E: Into + 'static, + T: FromRequest + 'static, + { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().with_async(handler); + } + /// Register a resource middleware /// /// This is similar to `App's` middlewares, but diff --git a/src/route.rs b/src/route.rs index 215a7f22..4ff3279e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -13,7 +13,7 @@ use httpresponse::HttpResponse; use middleware::{Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted}; use pred::Predicate; -use with::{ExtractorConfig, With, With2, With3}; +use with::{ExtractorConfig, With, With2, With3, WithAsync}; /// Resource route definition /// @@ -129,6 +129,34 @@ impl Route { /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// } /// ``` + /// + /// It is possible to use tuples for specifing multiple extractors for one + /// handler function. + /// + /// ```rust + /// # extern crate bytes; + /// # extern crate actix_web; + /// # extern crate futures; + /// #[macro_use] extern crate serde_derive; + /// # use std::collections::HashMap; + /// use actix_web::{http, App, Query, Path, Result, Json}; + /// + /// #[derive(Deserialize)] + /// struct Info { + /// username: String, + /// } + /// + /// /// extract path info using serde + /// fn index(info: (Path, Query>, Json)) -> Result { + /// Ok(format!("Welcome {}!", info.0.username)) + /// } + /// + /// fn main() { + /// let app = App::new().resource( + /// "/{username}/index.html", // <- define path parameters + /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor + /// } + /// ``` pub fn with(&mut self, handler: F) -> ExtractorConfig where F: Fn(T) -> R + 'static, @@ -140,6 +168,47 @@ impl Route { cfg } + /// Set async handler function, use request extractor for parameters. + /// + /// ```rust + /// # extern crate bytes; + /// # extern crate actix_web; + /// # extern crate futures; + /// #[macro_use] extern crate serde_derive; + /// use actix_web::{App, Path, Error, http}; + /// use futures::Future; + /// + /// #[derive(Deserialize)] + /// struct Info { + /// username: String, + /// } + /// + /// /// extract path info using serde + /// fn index(info: Path) -> Box> { + /// unimplemented!() + /// } + /// + /// fn main() { + /// let app = App::new().resource( + /// "/{username}/index.html", // <- define path parameters + /// |r| r.method(http::Method::GET) + /// .with_async(index)); // <- use `with` extractor + /// } + /// ``` + pub fn with_async(&mut self, handler: F) -> ExtractorConfig + where + F: Fn(T) -> R + 'static, + R: Future + 'static, + I: Responder + 'static, + E: Into + 'static, + T: FromRequest + 'static, + { + let cfg = ExtractorConfig::default(); + self.h(WithAsync::new(handler, Clone::clone(&cfg))); + cfg + } + + #[doc(hidden)] /// Set handler function, use request extractor for both parameters. /// /// ```rust @@ -189,6 +258,7 @@ impl Route { (cfg1, cfg2) } + #[doc(hidden)] /// Set handler function, use request extractor for all parameters. pub fn with3( &mut self, handler: F, diff --git a/src/with.rs b/src/with.rs index fa3d7d80..dca600bb 100644 --- a/src/with.rs +++ b/src/with.rs @@ -167,6 +167,145 @@ where } } +pub struct WithAsync +where + F: Fn(T) -> R, + R: Future, + I: Responder, + E: Into, + T: FromRequest, + S: 'static, +{ + hnd: Rc>, + cfg: ExtractorConfig, + _s: PhantomData, +} + +impl WithAsync +where + F: Fn(T) -> R, + R: Future, + I: Responder, + E: Into, + T: FromRequest, + S: 'static, +{ + pub fn new(f: F, cfg: ExtractorConfig) -> Self { + WithAsync { + cfg, + hnd: Rc::new(UnsafeCell::new(f)), + _s: PhantomData, + } + } +} + +impl Handler for WithAsync +where + F: Fn(T) -> R + 'static, + R: Future + 'static, + I: Responder + 'static, + E: Into + 'static, + T: FromRequest + 'static, + S: 'static, +{ + type Result = AsyncResult; + + fn handle(&mut self, req: HttpRequest) -> Self::Result { + let mut fut = WithAsyncHandlerFut { + req, + started: false, + hnd: Rc::clone(&self.hnd), + cfg: self.cfg.clone(), + fut1: None, + fut2: None, + fut3: None, + }; + + match fut.poll() { + Ok(Async::Ready(resp)) => AsyncResult::ok(resp), + Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), + Err(e) => AsyncResult::err(e), + } + } +} + +struct WithAsyncHandlerFut +where + F: Fn(T) -> R, + R: Future + 'static, + I: Responder + 'static, + E: Into + 'static, + T: FromRequest + 'static, + S: 'static, +{ + started: bool, + hnd: Rc>, + cfg: ExtractorConfig, + req: HttpRequest, + fut1: Option>>, + fut2: Option, + fut3: Option>>, +} + +impl Future for WithAsyncHandlerFut +where + F: Fn(T) -> R, + R: Future + 'static, + I: Responder + 'static, + E: Into + 'static, + T: FromRequest + 'static, + S: 'static, +{ + type Item = HttpResponse; + type Error = Error; + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut3 { + return fut.poll(); + } + + if self.fut2.is_some() { + return match self.fut2.as_mut().unwrap().poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(r)) => match r.respond_to(&self.req) { + Ok(r) => match r.into().into() { + AsyncResultItem::Err(err) => Err(err), + AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => { + self.fut3 = Some(fut); + self.poll() + } + }, + Err(e) => Err(e.into()), + }, + Err(e) => Err(e.into()), + }; + } + + let item = if !self.started { + self.started = true; + let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); + match reply.into() { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(msg) => msg, + AsyncResultItem::Future(fut) => { + self.fut1 = Some(fut); + return self.poll(); + } + } + } else { + match self.fut1.as_mut().unwrap().poll()? { + Async::Ready(item) => item, + Async::NotReady => return Ok(Async::NotReady), + } + }; + + let hnd: &mut F = unsafe { &mut *self.hnd.get() }; + self.fut2 = Some((*hnd)(item)); + self.poll() + } +} + pub struct With2 where F: Fn(T1, T2) -> R, diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 8aea34d0..42a9f3ac 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -9,6 +9,7 @@ extern crate tokio_core; extern crate serde_derive; extern crate serde_json; +use std::io; use std::time::Duration; use actix::*; @@ -377,6 +378,83 @@ fn test_path_and_query_extractor2_async4() { assert_eq!(response.status(), StatusCode::BAD_REQUEST); } +#[cfg(actix_impl_trait)] +fn test_impl_trait( + data: (Json, Path, Query), +) -> impl Future { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| { + Ok(format!( + "Welcome {} - {}!", + data.1.username, + (data.0).0 + )) + }) +} + +#[cfg(actix_impl_trait)] +fn test_impl_trait_err( + _data: (Json, Path, Query), +) -> impl Future { + Timeout::new(Duration::from_millis(10), &Arbiter::handle()) + .unwrap() + .and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other"))) +} + +#[cfg(actix_impl_trait)] +#[test] +fn test_path_and_query_extractor2_async4_impl_trait() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with_async(test_impl_trait) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html?username=test2")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!( + bytes, + Bytes::from_static(b"Welcome test1 - {\"test\":1}!") + ); + + // client request + let request = srv.get() + .uri(srv.url("/test1/index.html")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); +} + +#[cfg(actix_impl_trait)] +#[test] +fn test_path_and_query_extractor2_async4_impl_trait_err() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with_async(test_impl_trait_err) + }); + }); + + // client request + let request = srv.post() + .uri(srv.url("/test1/index.html?username=test2")) + .header("content-type", "application/json") + .body("{\"test\": 1}") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); +} + #[test] fn test_non_ascii_route() { let mut srv = test::TestServer::new(|app| { From 8b473745cb4b27ae4773393cc0b0024bf389f196 Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 10 May 2018 11:26:38 -0400 Subject: [PATCH 0211/1635] added error response functions for 501,502,503,504 --- src/error.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/error.rs b/src/error.rs index 8c46774e..2ad4f6b9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -757,6 +757,48 @@ where InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() } +/// Helper function that creates wrapper of any error and +/// generate *NOT IMPLEMENTED* response. +#[allow(non_snake_case)] +pub fn ErrorNotImplemented(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *BAD GATEWAY* response. +#[allow(non_snake_case)] +pub fn ErrorBadGateway(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::BAD_GATEWAY).into() +} + + +/// Helper function that creates wrapper of any error and +/// generate *SERVICE UNAVAILABLE* response. +#[allow(non_snake_case)] +pub fn ErrorServiceUnavailable(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *GATEWAY TIMEOUT* response. +#[allow(non_snake_case)] +pub fn ErrorGatewayTimeout(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() +} + + #[cfg(test)] mod tests { use super::*; From 2f244ea028280119022ac2be0b0d5cf63da86b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Gr=C3=B6ber?= Date: Thu, 10 May 2018 18:12:59 +0200 Subject: [PATCH 0212/1635] fix order of name and id in readme example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05757da4..096f9802 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ extern crate actix_web; use actix_web::{http, server, App, Path}; fn index(info: Path<(u32, String)>) -> String { - format!("Hello {}! id:{}", info.0, info.1) + format!("Hello {}! id:{}", info.1, info.0) } fn main() { From 76f021a6e3fcf6b44abc98d8615396234aadf44d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 10 May 2018 09:13:26 -0700 Subject: [PATCH 0213/1635] add tests for ErrorXXX helpers --- CHANGES.md | 2 ++ build.rs | 2 -- src/error.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cf4df3e3..db938c93 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ * Add `Router::with_async()` method for async handler registration. +* Added error response functions for 501,502,503,504 + ## 0.6.2 (2018-05-09) diff --git a/build.rs b/build.rs index 7cb25c73..c8457944 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,6 @@ extern crate version_check; fn main() { - let mut has_impl_trait = true; - match version_check::is_min_version("1.26.0") { Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"), _ => (), diff --git a/src/error.rs b/src/error.rs index 2ad4f6b9..fe979672 100644 --- a/src/error.rs +++ b/src/error.rs @@ -777,7 +777,6 @@ where InternalError::new(err, StatusCode::BAD_GATEWAY).into() } - /// Helper function that creates wrapper of any error and /// generate *SERVICE UNAVAILABLE* response. #[allow(non_snake_case)] @@ -798,7 +797,6 @@ where InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() } - #[cfg(test)] mod tests { use super::*; @@ -954,4 +952,52 @@ mod tests { let resp: HttpResponse = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } + + #[test] + fn test_error_helpers() { + let r: HttpResponse = ErrorBadRequest("err").into(); + assert_eq!(r.status(), StatusCode::BAD_REQUEST); + + let r: HttpResponse = ErrorUnauthorized("err").into(); + assert_eq!(r.status(), StatusCode::UNAUTHORIZED); + + let r: HttpResponse = ErrorForbidden("err").into(); + assert_eq!(r.status(), StatusCode::FORBIDDEN); + + let r: HttpResponse = ErrorNotFound("err").into(); + assert_eq!(r.status(), StatusCode::NOT_FOUND); + + let r: HttpResponse = ErrorMethodNotAllowed("err").into(); + assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); + + let r: HttpResponse = ErrorRequestTimeout("err").into(); + assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); + + let r: HttpResponse = ErrorConflict("err").into(); + assert_eq!(r.status(), StatusCode::CONFLICT); + + let r: HttpResponse = ErrorGone("err").into(); + assert_eq!(r.status(), StatusCode::GONE); + + let r: HttpResponse = ErrorPreconditionFailed("err").into(); + assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); + + let r: HttpResponse = ErrorExpectationFailed("err").into(); + assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); + + let r: HttpResponse = ErrorInternalServerError("err").into(); + assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); + + let r: HttpResponse = ErrorNotImplemented("err").into(); + assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED); + + let r: HttpResponse = ErrorBadGateway("err").into(); + assert_eq!(r.status(), StatusCode::BAD_GATEWAY); + + let r: HttpResponse = ErrorServiceUnavailable("err").into(); + assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE); + + let r: HttpResponse = ErrorGatewayTimeout("err").into(); + assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); + } } From 92f993e05438c9c989fd32f91d12011bf21add52 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 10 May 2018 09:37:38 -0700 Subject: [PATCH 0214/1635] Fix client request timeout handling --- CHANGES.md | 2 ++ src/client/mod.rs | 1 + src/client/pipeline.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index db938c93..fd7f0e2d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Added error response functions for 501,502,503,504 +* Fix client request timeout handling + ## 0.6.2 (2018-05-09) diff --git a/src/client/mod.rs b/src/client/mod.rs index 436fcf20..2116ae36 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -49,6 +49,7 @@ use httpresponse::HttpResponse; impl ResponseError for SendRequestError { fn error_response(&self) -> HttpResponse { match *self { + SendRequestError::Timeout => HttpResponse::GatewayTimeout(), SendRequestError::Connector(_) => HttpResponse::BadGateway(), _ => HttpResponse::InternalServerError(), }.into() diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 60eb4f8c..6a36bdd2 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -194,6 +194,7 @@ impl Future for SendRequest { self.state = State::Send(pl); } State::Send(mut pl) => { + pl.poll_timeout()?; pl.poll_write().map_err(|e| { io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()) })?; @@ -315,7 +316,7 @@ impl Pipeline { { Async::NotReady => need_run = true, Async::Ready(_) => { - let _ = self.poll_timeout().map_err(|e| { + self.poll_timeout().map_err(|e| { io::Error::new(io::ErrorKind::Other, format!("{}", e)) })?; } @@ -371,16 +372,15 @@ impl Pipeline { } } - fn poll_timeout(&mut self) -> Poll<(), SendRequestError> { + fn poll_timeout(&mut self) -> Result<(), SendRequestError> { if self.timeout.is_some() { match self.timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(())) => Err(SendRequestError::Timeout), - Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(())) => return Err(SendRequestError::Timeout), + Ok(Async::NotReady) => (), Err(_) => unreachable!(), } - } else { - Ok(Async::NotReady) } + Ok(()) } #[inline] From d8fa43034f5d988b196f7f3c244ffdb6f40766ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 10 May 2018 11:00:22 -0700 Subject: [PATCH 0215/1635] export ExtractorConfig type --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 8436a278..e050130f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -213,6 +213,7 @@ pub mod dev { pub use resource::ResourceHandler; pub use route::Route; pub use router::{Resource, ResourceType, Router}; + pub use with::ExtractorConfig; } pub mod http { From b6039b0bff0d1f4ec79a81a19fbdd95a7abbefac Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 10 May 2018 11:04:03 -0700 Subject: [PATCH 0216/1635] add doc string --- src/with.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/with.rs b/src/with.rs index dca600bb..099bcea4 100644 --- a/src/with.rs +++ b/src/with.rs @@ -9,6 +9,36 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; +/// Extractor configuration +/// +/// `Route::with()` and `Route::with_async()` returns instance +/// of the `ExtractorConfig` type. It could be used for extractor configuration. +/// +/// In this example `Form` configured. +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Form, Result, http}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// fn index(form: Form) -> Result { +/// Ok(format!("Welcome {}!", form.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", |r| { +/// r.method(http::Method::GET) +/// .with(index) +/// .limit(4096);} // <- change form extractor configuration +/// ); +/// } +/// ``` pub struct ExtractorConfig> { cfg: Rc>, } From 4b1a471b35881937a62f3f511051e02e7252cd50 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 10 May 2018 13:03:43 -0700 Subject: [PATCH 0217/1635] add more examples for extractor config --- src/with.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/with.rs b/src/with.rs index 099bcea4..fa4f4dc4 100644 --- a/src/with.rs +++ b/src/with.rs @@ -39,6 +39,32 @@ use httpresponse::HttpResponse; /// ); /// } /// ``` +/// +/// Same could be donce with multiple extractors +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Form, Path, Result, http}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// fn index(data: (Path<(String,)>, Form)) -> Result { +/// Ok(format!("Welcome {}!", data.1.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", |r| { +/// r.method(http::Method::GET) +/// .with(index) +/// .1.limit(4096);} // <- change form extractor configuration +/// ); +/// } +/// ``` pub struct ExtractorConfig> { cfg: Rc>, } From 961969854353911be6207c05315d3b1bc86d9f0b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 10 May 2018 13:04:56 -0700 Subject: [PATCH 0218/1635] doc string --- src/route.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/route.rs b/src/route.rs index 4ff3279e..1322d108 100644 --- a/src/route.rs +++ b/src/route.rs @@ -169,6 +169,8 @@ impl Route { } /// Set async handler function, use request extractor for parameters. + /// Also this method needs to be used if your handler function returns + /// `impl Future<>` /// /// ```rust /// # extern crate bytes; From a38afa0cec54ffe3d25ee0505e65918e6cc4b87e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 10 May 2018 13:05:56 -0700 Subject: [PATCH 0219/1635] --no-count for tarpaulin --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94cdb08b..39f74d87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - USE_SKEPTIC=1 cargo tarpaulin --out Xml + USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 095ad328ee7e3d24b23cef3493ce73fa1340dfd8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 10 May 2018 15:45:06 -0700 Subject: [PATCH 0220/1635] prepare release --- CHANGES.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fd7f0e2d..df577a99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## 0.6.3 (2018-05-xx) +## 0.6.3 (2018-05-10) * Add `Router::with_async()` method for async handler registration. diff --git a/Cargo.toml b/Cargo.toml index 8c474cca..24a50051 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.2" +version = "0.6.3" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 487a713ca0b29e07b13fef45984927e9f279eb88 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 11 May 2018 15:01:15 -0700 Subject: [PATCH 0221/1635] update doc string --- src/handler.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 7b88248b..a10a6f9c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -492,14 +492,15 @@ where /// } /// /// /// extract path info using serde -/// fn index(state: State, info: Path) -> String { -/// format!("{} {}!", state.msg, info.username) +/// fn index(data: (State, Path)) -> String { +/// let (state, path) = data; +/// format!("{} {}!", state.msg, path.username) /// } /// /// fn main() { /// let app = App::with_state(MyApp{msg: "Welcome"}).resource( /// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor +/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// } /// ``` pub struct State(HttpRequest); From f735da504b2842428563201f1b60534d6bc96570 Mon Sep 17 00:00:00 2001 From: skorgu Date: Fri, 11 May 2018 20:36:54 -0400 Subject: [PATCH 0222/1635] Include mention of http client in README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 096f9802..00d9953a 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. [CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html), [CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html)) * Built on top of [Actix actor framework](https://github.com/actix/actix) +* Includes an asynchronous [HTTP client](https://github.com/actix/actix-web/blob/master/src/client/mod.rs) ## Documentation & community resources From 9306631d6e3345e4a79a66f79886b67157fe751a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 11 May 2018 21:19:48 -0700 Subject: [PATCH 0223/1635] Fix segfault in ServerSettings::get_response_builder() --- CHANGES.md | 5 +++++ Cargo.toml | 2 +- src/server/settings.rs | 13 ++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index df577a99..f0f7cd7e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.6.4 (2018-05-11) + +* Fix segfault in ServerSettings::get_response_builder() + + ## 0.6.3 (2018-05-10) * Add `Router::with_async()` method for async handler registration. diff --git a/Cargo.toml b/Cargo.toml index 24a50051..6fd07355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.3" +version = "0.6.4" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/server/settings.rs b/src/server/settings.rs index cd17681b..f75033c1 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -16,7 +16,6 @@ use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; /// Various server settings -#[derive(Clone)] pub struct ServerSettings { addr: Option, secure: bool, @@ -28,6 +27,18 @@ pub struct ServerSettings { unsafe impl Sync for ServerSettings {} unsafe impl Send for ServerSettings {} +impl Clone for ServerSettings { + fn clone(&self) -> Self { + ServerSettings { + addr: self.addr, + secure: self.secure, + host: self.host.clone(), + cpu_pool: self.cpu_pool.clone(), + responses: HttpResponsePool::pool(), + } + } +} + struct InnerCpuPool { cpu_pool: UnsafeCell>, } From d65a03f6ac3c7175e14b0d9be07038563bd954c4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 13 May 2018 08:43:09 -0700 Subject: [PATCH 0224/1635] use latest nightly for appveyor --- .appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index e06f90ca..7933588a 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -31,13 +31,13 @@ environment: CHANNEL: beta # Nightly channel - TARGET: i686-pc-windows-gnu - CHANNEL: nightly-2017-12-21 + CHANNEL: nightly - TARGET: i686-pc-windows-msvc - CHANNEL: nightly-2017-12-21 + CHANNEL: nightly - TARGET: x86_64-pc-windows-gnu - CHANNEL: nightly-2017-12-21 + CHANNEL: nightly - TARGET: x86_64-pc-windows-msvc - CHANNEL: nightly-2017-12-21 + CHANNEL: nightly # Install Rust and Cargo # (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml) From 5ea2d68438fe1812afd80a70c6d5cc7b427de19e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 15 May 2018 07:55:36 -0700 Subject: [PATCH 0225/1635] h1 decoder blocks on error #222 --- src/server/h1.rs | 46 ++++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 8d862b39..b6de5cc5 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -67,7 +67,9 @@ where H: HttpHandler + 'static, { pub fn new( - settings: Rc>, stream: T, addr: Option, + settings: Rc>, + stream: T, + addr: Option, buf: BytesMut, ) -> Self { let bytes = settings.get_shared_bytes(); @@ -149,7 +151,8 @@ where pub fn poll_io(&mut self) { // read io from socket if !self.flags.intersects(Flags::ERROR) - && self.tasks.len() < MAX_PIPELINED_MESSAGES && self.can_read() + && self.tasks.len() < MAX_PIPELINED_MESSAGES + && self.can_read() { if self.read() { // notify all tasks @@ -205,8 +208,7 @@ where self.stream.reset(); if ready { - item.flags - .insert(EntryFlags::EOF | EntryFlags::FINISHED); + item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED); } else { item.flags.insert(EntryFlags::FINISHED); } @@ -347,6 +349,7 @@ where } else { error!("Internal server error: unexpected payload chunk"); self.flags.insert(Flags::ERROR); + break; } } Ok(Some(Message::Eof)) => { @@ -355,6 +358,7 @@ where } else { error!("Internal server error: unexpected eof"); self.flags.insert(Flags::ERROR); + break; } } Ok(None) => break, @@ -367,6 +371,7 @@ where }; payload.set_error(e); } + break; } } } @@ -614,10 +619,7 @@ mod tests { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); - assert_eq!( - req.headers().get("test").unwrap().as_bytes(), - b"value" - ); + assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); } Ok(_) | Err(_) => unreachable!("Error during parsing http request"), } @@ -918,13 +920,7 @@ mod tests { .as_ref(), b"line" ); - assert!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .eof() - ); + assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); } #[test] @@ -1005,13 +1001,7 @@ mod tests { assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); buf.extend(b"\r\n"); - assert!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .eof() - ); + assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); } #[test] @@ -1029,17 +1019,9 @@ mod tests { assert!(req.chunked().unwrap()); buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk(); + let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk(); + let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"line")); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.eof()); From 953a0d4e4ab1db724faea5b9196e391414e34920 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 15 May 2018 09:29:59 -0700 Subject: [PATCH 0226/1635] add test case for #222 --- CHANGES.md | 5 +++ src/server/h1.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f0f7cd7e..a8c4e1e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.6.5 (2018-05-15) + +* Fix error handling during request decoding #222 + + ## 0.6.4 (2018-05-11) * Fix segfault in ServerSettings::get_response_builder() diff --git a/src/server/h1.rs b/src/server/h1.rs index b6de5cc5..933ce0a8 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -403,8 +403,12 @@ where #[cfg(test)] mod tests { - use bytes::{Bytes, BytesMut}; + use std::net::Shutdown; + use std::{cmp, time}; + + use bytes::{Buf, Bytes, BytesMut}; use http::{Method, Version}; + use tokio_io::{AsyncRead, AsyncWrite}; use super::*; use application::HttpApplication; @@ -468,6 +472,101 @@ mod tests { }}; } + struct Buffer { + buf: Bytes, + err: Option, + } + + impl Buffer { + fn new(data: &'static str) -> Buffer { + Buffer { + buf: Bytes::from(data), + err: None, + } + } + fn feed_data(&mut self, data: &'static str) { + let mut b = BytesMut::from(self.buf.as_ref()); + b.extend(data.as_bytes()); + self.buf = b.take().freeze(); + } + } + + impl AsyncRead for Buffer {} + impl io::Read for Buffer { + fn read(&mut self, dst: &mut [u8]) -> Result { + if self.buf.is_empty() { + if self.err.is_some() { + Err(self.err.take().unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + let size = cmp::min(self.buf.len(), dst.len()); + let b = self.buf.split_to(size); + dst[..size].copy_from_slice(&b); + Ok(size) + } + } + } + + impl IoStream for Buffer { + fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { + Ok(()) + } + fn set_nodelay(&mut self, _: bool) -> io::Result<()> { + Ok(()) + } + fn set_linger(&mut 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) + } + } + + #[test] + fn test_req_parse() { + let buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n"); + let readbuf = BytesMut::new(); + let settings = Rc::new(WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + )); + + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); + h1.poll_io(); + h1.parse(); + assert_eq!(h1.tasks.len(), 1); + } + + #[test] + fn test_req_parse_err() { + let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); + let readbuf = BytesMut::new(); + let settings = Rc::new(WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + )); + + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); + h1.poll_io(); + h1.parse(); + assert!(h1.flags.contains(Flags::ERROR)); + } + #[test] fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); From ef89430f9b63190973d1aac94ed53a7dc0042802 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 15 May 2018 09:53:58 -0700 Subject: [PATCH 0227/1635] undeprecate query() and store query in extensions --- MIGRATION.md | 2 +- src/httprequest.rs | 43 +++++++++++++++++++++---------------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index f6829a50..f93e83c2 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -32,7 +32,7 @@ https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) is generic over `S` -* `HttpRequest::query()` is deprecated. Use `Query` extractor. +* Use `Query` extractor instead of HttpRequest::query()`. ```rust fn index(q: Query>) -> Result<..> { diff --git a/src/httprequest.rs b/src/httprequest.rs index 229c0687..fbca52ca 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -27,7 +27,6 @@ use uri::Url as InnerUrl; bitflags! { pub(crate) struct MessageFlags: u8 { - const QUERY = 0b0000_0001; const KEEPALIVE = 0b0000_0010; } } @@ -41,7 +40,6 @@ pub struct HttpInnerMessage { pub extensions: Extensions, pub params: Params<'static>, pub cookies: Option>>, - pub query: Params<'static>, pub addr: Option, pub payload: Option, pub info: Option>, @@ -49,6 +47,8 @@ pub struct HttpInnerMessage { resource: RouterResource, } +struct Query(Params<'static>); + #[derive(Debug, Copy, Clone, PartialEq)] enum RouterResource { Notset, @@ -64,7 +64,6 @@ impl Default for HttpInnerMessage { headers: HeaderMap::with_capacity(16), flags: MessageFlags::empty(), params: Params::new(), - query: Params::new(), addr: None, cookies: None, payload: None, @@ -109,7 +108,10 @@ impl HttpRequest<()> { /// Construct a new Request. #[inline] pub fn new( - method: Method, uri: Uri, version: Version, headers: HeaderMap, + method: Method, + uri: Uri, + version: Version, + headers: HeaderMap, payload: Option, ) -> HttpRequest { let url = InnerUrl::new(uri); @@ -121,7 +123,6 @@ impl HttpRequest<()> { headers, payload, params: Params::new(), - query: Params::new(), extensions: Extensions::new(), cookies: None, addr: None, @@ -306,7 +307,9 @@ impl HttpRequest { /// } /// ``` pub fn url_for( - &self, name: &str, elements: U, + &self, + name: &str, + elements: U, ) -> Result where U: IntoIterator, @@ -369,20 +372,20 @@ impl HttpRequest { } #[doc(hidden)] - #[deprecated(since = "0.6.0", note = "please use `Query` extractor")] /// Get a reference to the Params object. /// Params is a container for url query parameters. - pub fn query(&self) -> &Params { - if !self.as_ref().flags.contains(MessageFlags::QUERY) { - self.as_mut().flags.insert(MessageFlags::QUERY); - let params: &mut Params = - unsafe { mem::transmute(&mut self.as_mut().query) }; - params.clear(); + pub fn query<'a>(&'a self) -> &'a Params { + if let None = self.extensions().get::() { + let mut params: Params<'a> = Params::new(); for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { params.add(key, val); } + let params: Params<'static> = unsafe { mem::transmute(params) }; + self.as_mut().extensions.insert(Query(params)); } - unsafe { mem::transmute(&self.as_ref().query) } + let params: &Params<'a> = + unsafe { mem::transmute(&self.extensions().get::().unwrap().0) }; + params } /// The query string in the URL. @@ -664,10 +667,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = vec![( - Resource::new("index", "/user/{name}.{ext}"), - Some(resource), - )]; + let routes = + vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; let (router, _) = Router::new("/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/test/unknown")); @@ -696,10 +697,8 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = vec![( - Resource::new("index", "/user/{name}.{ext}"), - Some(resource), - )]; + let routes = + vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); assert!(router.has_route("/user/test.html")); assert!(!router.has_route("/prefix/user/test.html")); From b9d870645f1d25e1b981d0fa7c9afafbab601c74 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 15 May 2018 10:09:48 -0700 Subject: [PATCH 0228/1635] store cookies in extensions --- src/httprequest.rs | 17 ++++++++++------- src/test.rs | 15 +++++---------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index fbca52ca..0c3ee31d 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -39,7 +39,6 @@ pub struct HttpInnerMessage { pub headers: HeaderMap, pub extensions: Extensions, pub params: Params<'static>, - pub cookies: Option>>, pub addr: Option, pub payload: Option, pub info: Option>, @@ -48,6 +47,7 @@ pub struct HttpInnerMessage { } struct Query(Params<'static>); +struct Cookies(Vec>); #[derive(Debug, Copy, Clone, PartialEq)] enum RouterResource { @@ -65,7 +65,6 @@ impl Default for HttpInnerMessage { flags: MessageFlags::empty(), params: Params::new(), addr: None, - cookies: None, payload: None, extensions: Extensions::new(), info: None, @@ -90,7 +89,6 @@ impl HttpInnerMessage { self.addr = None; self.info = None; self.flags = MessageFlags::empty(); - self.cookies = None; self.payload = None; self.prefix = 0; self.resource = RouterResource::Notset; @@ -124,7 +122,6 @@ impl HttpRequest<()> { payload, params: Params::new(), extensions: Extensions::new(), - cookies: None, addr: None, info: None, prefix: 0, @@ -402,7 +399,7 @@ impl HttpRequest { /// Load request cookies. pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { - if self.as_ref().cookies.is_none() { + if let None = self.extensions().get::() { let msg = self.as_mut(); let mut cookies = Vec::new(); for hdr in msg.headers.get_all(header::COOKIE) { @@ -413,9 +410,9 @@ impl HttpRequest { } } } - msg.cookies = Some(cookies); + msg.extensions.insert(Cookies(cookies)); } - Ok(&self.as_ref().cookies.as_ref().unwrap()) + Ok(&self.extensions().get::().unwrap().0) } /// Return request cookie. @@ -430,6 +427,12 @@ impl HttpRequest { None } + pub(crate) fn set_cookies(&mut self, cookies: Option>>) { + if let Some(cookies) = cookies { + self.extensions_mut().insert(Cookies(cookies)); + } + } + /// Get a reference to the Params object. /// /// Params is a container for url parameters. diff --git a/src/test.rs b/src/test.rs index 4e739839..135135f7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -355,12 +355,7 @@ impl TestApp { /// Register handler for "/" pub fn handler>(&mut self, handler: H) { - self.app = Some( - self.app - .take() - .unwrap() - .resource("/", |r| r.h(handler)), - ); + self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler))); } /// Register middleware @@ -562,8 +557,8 @@ impl TestRequest { cookies, payload, } = self; - let req = HttpRequest::new(method, uri, version, headers, payload); - req.as_mut().cookies = cookies; + let mut req = HttpRequest::new(method, uri, version, headers, payload); + req.set_cookies(cookies); req.as_mut().params = params; let (router, _) = Router::new::("/", ServerSettings::default(), Vec::new()); req.with_state(Rc::new(state), router) @@ -583,8 +578,8 @@ impl TestRequest { payload, } = self; - let req = HttpRequest::new(method, uri, version, headers, payload); - req.as_mut().cookies = cookies; + let mut req = HttpRequest::new(method, uri, version, headers, payload); + req.set_cookies(cookies); req.as_mut().params = params; req.with_state(Rc::new(state), router) } From d6787e6c561e416f08d1dc5c1c4125aa17730c69 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 15 May 2018 10:20:32 -0700 Subject: [PATCH 0229/1635] prepare release --- Cargo.toml | 2 +- README.md | 2 +- src/lib.rs | 20 ++++++++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6fd07355..595fc9a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.4" +version = "0.6.5" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/README.md b/README.md index 00d9953a..51a3ae35 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. [DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers), [CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html), [CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html)) -* Built on top of [Actix actor framework](https://github.com/actix/actix) * Includes an asynchronous [HTTP client](https://github.com/actix/actix-web/blob/master/src/client/mod.rs) +* Built on top of [Actix actor framework](https://github.com/actix/actix) ## Documentation & community resources diff --git a/src/lib.rs b/src/lib.rs index e050130f..8ef1e2df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,21 @@ //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) //! * Built on top of [Actix actor framework](https://github.com/actix/actix) //! * Supported Rust version: 1.24 or later - +//! +//! ## Package feature +//! +//! * `tls` - enables ssl support via `native-tls` crate +//! * `alpn` - enables ssl support via `openssl` crate, require for `http/2` +//! support +//! * `session` - enables session support, includes `ring` crate as +//! dependency +//! * `brotli` - enables `brotli` compression support, requires `c` +//! compiler +//! * `flate-c` - enables `gzip`, `deflate` compression support, requires +//! `c` compiler +//! * `flate-rust` - experimental rust based implementation for +//! `gzip`, `deflate` compression. +//! #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error ))] @@ -169,7 +183,9 @@ pub use body::{Binary, Body}; pub use context::HttpContext; pub use error::{Error, ResponseError, Result}; pub use extractor::{Form, Path, Query}; -pub use handler::{AsyncResponder, Either, FromRequest, FutureResponse, Responder, State}; +pub use handler::{ + AsyncResponder, Either, FromRequest, FutureResponse, Responder, State, +}; pub use httpmessage::HttpMessage; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; From f82fa08d723005d5e79c71797d7aa6df2bb4705e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 15 May 2018 16:41:46 -0700 Subject: [PATCH 0230/1635] various optimizations --- src/body.rs | 18 ++++++++ src/httpresponse.rs | 95 ++++++++++++++---------------------------- src/pipeline.rs | 28 +++++++++---- src/server/encoding.rs | 81 +++++++++++++++++++---------------- src/server/h1.rs | 34 ++++++++++++++- src/server/h1writer.rs | 20 +++++---- 6 files changed, 161 insertions(+), 115 deletions(-) diff --git a/src/body.rs b/src/body.rs index 063c93ac..5ce0d129 100644 --- a/src/body.rs +++ b/src/body.rs @@ -62,10 +62,28 @@ impl Body { } } + /// Is this binary empy. + #[inline] + pub fn is_empty(&self) -> bool { + match *self { + Body::Empty => true, + _ => false, + } + } + /// Create body from slice (copy) pub fn from_slice(s: &[u8]) -> Body { Body::Binary(Binary::Bytes(Bytes::from(s))) } + + /// Is this binary body. + #[inline] + pub(crate) fn binary(self) -> Binary { + match self { + Body::Binary(b) => b, + _ => panic!(), + } + } } impl PartialEq for Body { diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 8097fc93..a71f53fb 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -466,10 +466,7 @@ impl HttpResponseBuilder { jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { - self.cookies - .as_mut() - .unwrap() - .add(cookie.into_owned()); + self.cookies.as_mut().unwrap().add(cookie.into_owned()); } self } @@ -534,9 +531,7 @@ impl HttpResponseBuilder { if let Some(e) = self.err.take() { return Error::from(e).into(); } - let mut response = self.response - .take() - .expect("cannot reuse response builder"); + let mut response = self.response.take().expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { @@ -558,9 +553,7 @@ impl HttpResponseBuilder { S: Stream + 'static, E: Into, { - self.body(Body::Streaming(Box::new( - stream.map_err(|e| e.into()), - ))) + self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) } /// Set a json body and generate `HttpResponse` @@ -607,7 +600,8 @@ impl HttpResponseBuilder { #[inline] #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] fn parts<'a>( - parts: &'a mut Option>, err: &Option, + parts: &'a mut Option>, + err: &Option, ) -> Option<&'a mut Box> { if err.is_some() { return None; @@ -822,14 +816,15 @@ thread_local!(static POOL: Rc> = HttpResponsePool:: impl HttpResponsePool { pub fn pool() -> Rc> { - Rc::new(UnsafeCell::new(HttpResponsePool( - VecDeque::with_capacity(128), - ))) + Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity( + 128, + )))) } #[inline] pub fn get_builder( - pool: &Rc>, status: StatusCode, + pool: &Rc>, + status: StatusCode, ) -> HttpResponseBuilder { let p = unsafe { &mut *pool.as_ref().get() }; if let Some(mut msg) = p.0.pop_front() { @@ -853,7 +848,9 @@ impl HttpResponsePool { #[inline] pub fn get_response( - pool: &Rc>, status: StatusCode, body: Body, + pool: &Rc>, + status: StatusCode, + body: Body, ) -> HttpResponse { let p = unsafe { &mut *pool.as_ref().get() }; if let Some(mut msg) = p.0.pop_front() { @@ -879,7 +876,8 @@ impl HttpResponsePool { #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))] fn release( - pool: &Rc>, mut inner: Box, + pool: &Rc>, + mut inner: Box, ) { let pool = unsafe { &mut *pool.as_ref().get() }; if pool.0.len() < 128 { @@ -975,9 +973,7 @@ mod tests { #[test] fn test_force_close() { - let resp = HttpResponse::build(StatusCode::OK) - .force_close() - .finish(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); assert!(!resp.keep_alive().unwrap()) } @@ -986,10 +982,7 @@ mod tests { let resp = HttpResponse::build(StatusCode::OK) .content_type("text/plain") .body(Body::Empty); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - "text/plain" - ) + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } #[test] @@ -1036,10 +1029,10 @@ mod tests { } impl Body { - pub(crate) fn binary(&self) -> Option<&Binary> { + pub(crate) fn bin_ref(&self) -> &Binary { match *self { - Body::Binary(ref bin) => Some(bin), - _ => None, + Body::Binary(ref bin) => bin, + _ => panic!(), } } } @@ -1055,7 +1048,7 @@ mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); + assert_eq!(resp.body().bin_ref(), &Binary::from("test")); let resp: HttpResponse = "test".respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -1064,7 +1057,7 @@ mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().binary().unwrap(), &Binary::from("test")); + assert_eq!(resp.body().bin_ref(), &Binary::from("test")); let resp: HttpResponse = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1073,10 +1066,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().binary().unwrap(), - &Binary::from(b"test".as_ref()) - ); + assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -1085,10 +1075,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().binary().unwrap(), - &Binary::from(b"test".as_ref()) - ); + assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); let resp: HttpResponse = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1097,10 +1084,7 @@ mod tests { 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()) - ); + assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -1109,10 +1093,7 @@ mod tests { 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()) - ); + assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); let resp: HttpResponse = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1121,10 +1102,7 @@ mod tests { 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()) - ); + assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -1133,10 +1111,7 @@ mod tests { 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()) - ); + assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); let b = Bytes::from_static(b"test"); let resp: HttpResponse = b.into(); @@ -1147,7 +1122,7 @@ mod tests { ); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( - resp.body().binary().unwrap(), + resp.body().bin_ref(), &Binary::from(Bytes::from_static(b"test")) ); @@ -1160,7 +1135,7 @@ mod tests { ); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( - resp.body().binary().unwrap(), + resp.body().bin_ref(), &Binary::from(Bytes::from_static(b"test")) ); @@ -1172,10 +1147,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().binary().unwrap(), - &Binary::from(BytesMut::from("test")) - ); + assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); let b = BytesMut::from("test"); let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); @@ -1185,10 +1157,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().binary().unwrap(), - &Binary::from(BytesMut::from("test")) - ); + assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); } #[test] diff --git a/src/pipeline.rs b/src/pipeline.rs index e00c0617..4d5d405c 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -29,7 +29,9 @@ pub(crate) trait PipelineHandler { fn encoding(&self) -> ContentEncoding; fn handle( - &mut self, req: HttpRequest, htype: HandlerType, + &mut self, + req: HttpRequest, + htype: HandlerType, ) -> AsyncResult; } @@ -120,8 +122,10 @@ impl PipelineInfo { impl> Pipeline { pub fn new( - req: HttpRequest, mws: Rc>>>, - handler: Rc>, htype: HandlerType, + req: HttpRequest, + mws: Rc>>>, + handler: Rc>, + htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { mws, @@ -148,6 +152,7 @@ impl Pipeline<(), Inner<()>> { } impl Pipeline { + #[inline] fn is_done(&self) -> bool { match self.1 { PipelineState::None @@ -192,7 +197,9 @@ impl> HttpHandlerTask for Pipeline { match self.1 { PipelineState::None => return Ok(Async::Ready(true)), PipelineState::Error => { - return Err(io::Error::new(io::ErrorKind::Other, "Internal error").into()) + return Err( + io::Error::new(io::ErrorKind::Other, "Internal error").into() + ) } _ => (), } @@ -236,7 +243,9 @@ struct StartMiddlewares { impl> StartMiddlewares { fn init( - info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType, + info: &mut PipelineInfo, + hnd: Rc>, + htype: HandlerType, ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately @@ -313,7 +322,8 @@ struct WaitingResponse { impl WaitingResponse { #[inline] fn init( - info: &mut PipelineInfo, reply: AsyncResult, + info: &mut PipelineInfo, + reply: AsyncResult, ) -> PipelineState { match reply.into() { AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), @@ -344,6 +354,7 @@ struct RunMiddlewares { } impl RunMiddlewares { + #[inline] fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState { if info.count == 0 { return ProcessResponse::init(resp); @@ -464,7 +475,9 @@ impl ProcessResponse { } fn poll_io( - mut self, io: &mut Writer, info: &mut PipelineInfo, + mut self, + io: &mut Writer, + info: &mut PipelineInfo, ) -> Result, PipelineState> { loop { if self.drain.is_none() && self.running != RunningState::Paused { @@ -676,6 +689,7 @@ struct FinishingMiddlewares { } impl FinishingMiddlewares { + #[inline] fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { if info.count == 0 { Completed::init(info) diff --git a/src/server/encoding.rs b/src/server/encoding.rs index ae69ae07..662a8c4b 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -12,8 +12,10 @@ use flate2::read::GzDecoder; use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; -use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, - CONTENT_LENGTH, TRANSFER_ENCODING}; +use http::header::{ + HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, + TRANSFER_ENCODING, +}; use http::{HttpTryFrom, Method, Version}; use body::{Binary, Body}; @@ -378,16 +380,19 @@ impl ContentEncoder { } pub fn for_server( - buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse, + buf: SharedBytes, + req: &HttpInnerMessage, + resp: &mut HttpResponse, response_encoding: ContentEncoding, ) -> ContentEncoder { let version = resp.version().unwrap_or_else(|| req.version); let is_head = req.method == Method::HEAD; - let mut body = resp.replace_body(Body::Empty); - let has_body = match body { + let mut len = 0; + let has_body = match resp.body() { Body::Empty => false, Body::Binary(ref bin) => { - !(response_encoding == ContentEncoding::Auto && bin.len() < 96) + len = bin.len(); + !(response_encoding == ContentEncoding::Auto && len < 96) } _ => true, }; @@ -421,14 +426,14 @@ impl ContentEncoder { ContentEncoding::Identity }; - let mut transfer = match body { + let mut transfer = match resp.body() { Body::Empty => { if req.method != Method::HEAD { resp.headers_mut().remove(CONTENT_LENGTH); } TransferEncoding::length(0, buf) } - Body::Binary(ref mut bytes) => { + Body::Binary(_) => { if !(encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto) { @@ -448,19 +453,26 @@ impl ContentEncoder { ContentEncoding::Br => { ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) } - ContentEncoding::Identity => ContentEncoder::Identity(transfer), - ContentEncoding::Auto => unreachable!(), + ContentEncoding::Identity | ContentEncoding::Auto => { + unreachable!() + } }; - // TODO return error! - let _ = enc.write(bytes.clone()); - let _ = enc.write_eof(); - *bytes = Binary::from(tmp.take()); + let bin = resp.replace_body(Body::Empty).binary(); + + // TODO return error! + let _ = enc.write(bin); + let _ = enc.write_eof(); + let body = tmp.take(); + len = body.len(); + encoding = ContentEncoding::Identity; + resp.replace_body(Binary::from(body)); } + if is_head { let mut b = BytesMut::new(); - let _ = write!(b, "{}", bytes.len()); + let _ = write!(b, "{}", len); resp.headers_mut().insert( CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap(), @@ -485,11 +497,10 @@ impl ContentEncoder { } } }; - // + // check for head response if is_head { + resp.set_body(Body::Empty); transfer.kind = TransferEncodingKind::Length(0); - } else { - resp.replace_body(body); } match encoding { @@ -511,7 +522,9 @@ impl ContentEncoder { } fn streaming_encoding( - buf: SharedBytes, version: Version, resp: &mut HttpResponse, + buf: SharedBytes, + version: Version, + resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { @@ -590,7 +603,7 @@ impl ContentEncoder { #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[inline(always)] - pub fn write_eof(&mut self) -> Result<(), io::Error> { + pub fn write_eof(&mut self) -> Result { let encoder = mem::replace( self, ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())), @@ -602,7 +615,7 @@ impl ContentEncoder { Ok(mut writer) => { writer.encode_eof(); *self = ContentEncoder::Identity(writer); - Ok(()) + Ok(true) } Err(err) => Err(err), }, @@ -611,7 +624,7 @@ impl ContentEncoder { Ok(mut writer) => { writer.encode_eof(); *self = ContentEncoder::Identity(writer); - Ok(()) + Ok(true) } Err(err) => Err(err), }, @@ -620,14 +633,14 @@ impl ContentEncoder { Ok(mut writer) => { writer.encode_eof(); *self = ContentEncoder::Identity(writer); - Ok(()) + Ok(true) } Err(err) => Err(err), }, ContentEncoder::Identity(mut writer) => { - writer.encode_eof(); + let res = writer.encode_eof(); *self = ContentEncoder::Identity(writer); - Ok(()) + Ok(res) } } } @@ -763,8 +776,7 @@ impl TransferEncoding { return Ok(*remaining == 0); } let len = cmp::min(*remaining, msg.len() as u64); - self.buffer - .extend(msg.take().split_to(len as usize).into()); + self.buffer.extend(msg.take().split_to(len as usize).into()); *remaining -= len as u64; Ok(*remaining == 0) @@ -777,14 +789,16 @@ impl TransferEncoding { /// Encode eof. Return `EOF` state of encoder #[inline] - pub fn encode_eof(&mut self) { + pub fn encode_eof(&mut self) -> bool { match self.kind { - TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (), + TransferEncodingKind::Eof => true, + TransferEncodingKind::Length(rem) => rem == 0, TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; self.buffer.extend_from_slice(b"0\r\n\r\n"); } + true } } } @@ -848,10 +862,7 @@ impl AcceptEncoding { Err(_) => 0.0, }, }; - Some(AcceptEncoding { - encoding, - quality, - }) + Some(AcceptEncoding { encoding, quality }) } /// Parse a raw Accept-Encoding header value into an ordered list. @@ -879,9 +890,7 @@ mod tests { fn test_chunked_te() { let bytes = SharedBytes::default(); let mut enc = TransferEncoding::chunked(bytes.clone()); - assert!(!enc.encode(Binary::from(b"test".as_ref())) - .ok() - .unwrap()); + assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap()); assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap()); assert_eq!( bytes.get_mut().take().freeze(), diff --git a/src/server/h1.rs b/src/server/h1.rs index 933ce0a8..9418616c 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -148,6 +148,7 @@ where } #[inline] + /// read data from stream pub fn poll_io(&mut self) { // read io from socket if !self.flags.intersects(Flags::ERROR) @@ -210,7 +211,7 @@ where if ready { item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED); } else { - item.flags.insert(EntryFlags::FINISHED); + item.flags.insert(EntryFlags::EOF); } } // no more IO for this iteration @@ -326,7 +327,36 @@ where // search handler for request for h in self.settings.handlers().iter_mut() { req = match h.handle(req) { - Ok(pipe) => { + Ok(mut pipe) => { + if self.tasks.is_empty() { + match pipe.poll_io(&mut self.stream) { + Ok(Async::Ready(ready)) => { + // override keep-alive state + if self.stream.keepalive() { + self.flags.insert(Flags::KEEPALIVE); + } else { + self.flags.remove(Flags::KEEPALIVE); + } + // prepare stream for next response + self.stream.reset(); + + if !ready { + let item = Entry { + pipe, + flags: EntryFlags::EOF, + }; + self.tasks.push_back(item); + } + continue 'outer; + } + Ok(Async::NotReady) => {} + Err(err) => { + error!("Unhandled error: {}", err); + self.flags.intersects(Flags::ERROR); + return; + } + } + } self.tasks.push_back(Entry { pipe, flags: EntryFlags::empty(), diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index c0fa0609..ec5bfde1 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -42,7 +42,9 @@ pub(crate) struct H1Writer { impl H1Writer { pub fn new( - stream: T, buf: SharedBytes, settings: Rc>, + stream: T, + buf: SharedBytes, + settings: Rc>, ) -> H1Writer { H1Writer { flags: Flags::empty(), @@ -101,7 +103,9 @@ impl Writer for H1Writer { } fn start( - &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, + &mut self, + req: &mut HttpInnerMessage, + msg: &mut HttpResponse, encoding: ContentEncoding, ) -> io::Result { // prepare task @@ -138,7 +142,9 @@ impl Writer for H1Writer { let reason = msg.reason().as_bytes(); let mut is_bin = if let Body::Binary(ref bytes) = body { buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len() + 256 + + msg.headers().len() * AVERAGE_HEADER_SIZE + + bytes.len() + reason.len(), ); true @@ -255,9 +261,7 @@ impl Writer for H1Writer { } fn write_eof(&mut self) -> io::Result { - self.encoder.write_eof()?; - - if !self.encoder.is_eof() { + if !self.encoder.write_eof()? { Err(io::Error::new( io::ErrorKind::Other, "Last payload item, but eof is not reached", @@ -276,7 +280,9 @@ impl Writer for H1Writer { unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) }; let written = self.write_data(buf)?; let _ = self.buffer.split_to(written); - if self.buffer.len() > self.buffer_capacity { + if shutdown && !self.buffer.is_empty() + || (self.buffer.len() > self.buffer_capacity) + { return Ok(Async::NotReady); } } From 0d36b8f82602b1d9a5bddd0dcd47a6b4a0abb3c9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 15 May 2018 19:07:43 -0700 Subject: [PATCH 0231/1635] fix 1.24 compatibility --- src/error.rs | 7 ++----- src/server/encoding.rs | 10 +++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index fe979672..1ec394e3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -88,7 +88,7 @@ impl fmt::Debug for Error { } } -/// `HttpResponse` for `Error` +/// Convert `Error` to a `HttpResponse` instance impl From for HttpResponse { fn from(err: Error) -> Self { HttpResponse::from_error(err) @@ -317,10 +317,7 @@ pub enum HttpRangeError { /// Return `BadRequest` for `HttpRangeError` impl ResponseError for HttpRangeError { fn error_response(&self) -> HttpResponse { - HttpResponse::with_body( - StatusCode::BAD_REQUEST, - "Invalid Range header provided", - ) + HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided") } } diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 662a8c4b..07438b50 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -389,8 +389,8 @@ impl ContentEncoder { let is_head = req.method == Method::HEAD; let mut len = 0; let has_body = match resp.body() { - Body::Empty => false, - Body::Binary(ref bin) => { + &Body::Empty => false, + &Body::Binary(ref bin) => { len = bin.len(); !(response_encoding == ContentEncoding::Auto && len < 96) } @@ -427,13 +427,13 @@ impl ContentEncoder { }; let mut transfer = match resp.body() { - Body::Empty => { + &Body::Empty => { if req.method != Method::HEAD { resp.headers_mut().remove(CONTENT_LENGTH); } TransferEncoding::length(0, buf) } - Body::Binary(_) => { + &Body::Binary(_) => { if !(encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto) { @@ -482,7 +482,7 @@ impl ContentEncoder { } TransferEncoding::eof(buf) } - Body::Streaming(_) | Body::Actor(_) => { + &Body::Streaming(_) | &Body::Actor(_) => { if resp.upgrade() { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); From 03e758cee4973208d0b957cc2dbe1bd3abcef756 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 15 May 2018 19:08:34 -0700 Subject: [PATCH 0232/1635] bump version --- CHANGES.md | 5 +++++ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a8c4e1e5..f450c70d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.6.6 (2018-05-xx) + +.. + + ## 0.6.5 (2018-05-15) * Fix error handling during request decoding #222 diff --git a/Cargo.toml b/Cargo.toml index 595fc9a1..2ba3cc0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.5" +version = "0.6.6" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 6e976153e79be73a25f3ab3c25666c4606062dbf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 16 May 2018 15:20:47 +0200 Subject: [PATCH 0233/1635] Add support for listen_tls/listen_ssl --- src/server/srv.rs | 58 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/server/srv.rs b/src/server/srv.rs index df397841..c76ec749 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -25,6 +25,20 @@ use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker}; use super::{IntoHttpHandler, IoStream, KeepAlive}; use super::{PauseServer, ResumeServer, StopServer}; +#[cfg(feature = "alpn")] +fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> { + builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } + }); + Ok(()) +} + /// An HTTP Server pub struct HttpServer where @@ -211,6 +225,40 @@ where self } + #[cfg(feature = "tls")] + /// Use listener for accepting incoming tls connection requests + /// + /// HttpServer does not change any configuration for TcpListener, + /// it needs to be configured before passing it to listen() method. + pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + lst, + tp: StreamHandlerType::Tls(acceptor.clone()), + }); + self + } + + #[cfg(feature = "alpn")] + /// Use listener for accepting incoming tls connection requests + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn listen_ssl(mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder) -> io::Result { + // alpn support + if !self.no_http2 { + configure_alpn(&mut builder)?; + } + let acceptor = builder.build(); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + lst, + tp: StreamHandlerType::Alpn(acceptor.clone()), + }); + Ok(self) + } + fn bind2(&mut self, addr: S) -> io::Result> { let mut err = None; let mut succ = false; @@ -277,15 +325,7 @@ where ) -> io::Result { // alpn support if !self.no_http2 { - builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); + configure_alpn(&mut builder)?; } let acceptor = builder.build(); From 7bb7d85c1d1c19002b5887e3e319a08c07565ee0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 16 May 2018 16:17:27 +0200 Subject: [PATCH 0234/1635] Added support for returning addresses plus scheme from the server --- src/server/srv.rs | 10 ++++++++++ src/server/worker.rs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/server/srv.rs b/src/server/srv.rs index c76ec749..30b7b4e4 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -211,6 +211,16 @@ where self.sockets.iter().map(|s| s.addr).collect() } + /// Get addresses of bound sockets and the scheme for it. + /// + /// This is useful when the server is bound from different sources + /// with some sockets listening on http and some listening on https + /// and the user should be presented with an enumeration of which + /// socket requires which protocol. + pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { + self.sockets.iter().map(|s| (s.addr, s.tp.scheme())).collect() + } + /// Use listener for accepting incoming connection requests /// /// HttpServer does not change any configuration for TcpListener, diff --git a/src/server/worker.rs b/src/server/worker.rs index 67f4645c..a926a6c8 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -239,4 +239,14 @@ impl StreamHandlerType { } } } + + pub(crate) fn scheme(&self) -> &'static str { + match *self { + StreamHandlerType::Normal => "http", + #[cfg(feature = "tls")] + StreamHandlerType::Tls(ref acceptor) => "https", + #[cfg(feature = "alpn")] + StreamHandlerType::Alpn(ref acceptor) => "https", + } + } } From b393ddf879b0d955b3bbea74291534680519850b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 16 May 2018 11:00:29 -0700 Subject: [PATCH 0235/1635] fix panic during middleware execution #226 --- CHANGES.md | 4 +- src/pipeline.rs | 12 +-- src/route.rs | 47 ++++++----- src/scope.rs | 35 ++++---- tests/test_middleware.rs | 169 ++++++++++++++++++++++++++++++--------- 5 files changed, 185 insertions(+), 82 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f450c70d..aa4d7c45 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,8 @@ # Changes -## 0.6.6 (2018-05-xx) +## 0.6.6 (2018-05-16) -.. +* Panic during middleware execution #226 ## 0.6.5 (2018-05-15) diff --git a/src/pipeline.rs b/src/pipeline.rs index 4d5d405c..82ec45a7 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -284,12 +284,12 @@ impl> StartMiddlewares { if let Some(resp) = resp { return Some(RunMiddlewares::init(info, resp)); } - if info.count == len { - let reply = unsafe { &mut *self.hnd.get() } - .handle(info.req().clone(), self.htype); - return Some(WaitingResponse::init(info, reply)); - } else { - loop { + loop { + if info.count == len { + let reply = unsafe { &mut *self.hnd.get() } + .handle(info.req().clone(), self.htype); + return Some(WaitingResponse::init(info, reply)); + } else { match info.mws[info.count as usize].start(info.req_mut()) { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { diff --git a/src/route.rs b/src/route.rs index 1322d108..b109fd60 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,13 +5,17 @@ use std::rc::Rc; use futures::{Async, Future, Poll}; use error::Error; -use handler::{AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, - Responder, RouteHandler, WrapHandler}; +use handler::{ + AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, + RouteHandler, WrapHandler, +}; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middleware::{Finished as MiddlewareFinished, Middleware, - Response as MiddlewareResponse, Started as MiddlewareStarted}; +use middleware::{ + Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, + Started as MiddlewareStarted, +}; use pred::Predicate; use with::{ExtractorConfig, With, With2, With3, WithAsync}; @@ -51,7 +55,9 @@ impl Route { #[inline] pub(crate) fn compose( - &mut self, req: HttpRequest, mws: Rc>>>, + &mut self, + req: HttpRequest, + mws: Rc>>>, ) -> AsyncResult { AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) } @@ -242,7 +248,8 @@ impl Route { /// } /// ``` pub fn with2( - &mut self, handler: F, + &mut self, + handler: F, ) -> (ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2) -> R + 'static, @@ -263,7 +270,8 @@ impl Route { #[doc(hidden)] /// Set handler function, use request extractor for all parameters. pub fn with3( - &mut self, handler: F, + &mut self, + handler: F, ) -> ( ExtractorConfig, ExtractorConfig, @@ -296,9 +304,7 @@ struct InnerHandler(Rc>>>); impl InnerHandler { #[inline] fn new>(h: H) -> Self { - InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new( - h, - ))))) + InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(h))))) } #[inline] @@ -309,9 +315,7 @@ impl InnerHandler { R: Responder + 'static, E: Into + 'static, { - InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new( - h, - ))))) + InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(h))))) } #[inline] @@ -364,7 +368,9 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>, handler: InnerHandler, + req: HttpRequest, + mws: Rc>>>, + handler: InnerHandler, ) -> Self { let mut info = ComposeInfo { count: 0, @@ -440,11 +446,11 @@ impl StartMiddlewares { if let Some(resp) = resp { return Some(RunMiddlewares::init(info, resp)); } - if info.count == len { - let reply = info.handler.handle(info.req.clone()); - return Some(WaitingResponse::init(info, reply)); - } else { - loop { + loop { + if info.count == len { + let reply = info.handler.handle(info.req.clone()); + return Some(WaitingResponse::init(info, reply)); + } else { match info.mws[info.count].start(&mut info.req) { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { @@ -479,7 +485,8 @@ struct WaitingResponse { impl WaitingResponse { #[inline] fn init( - info: &mut ComposeInfo, reply: AsyncResult, + info: &mut ComposeInfo, + reply: AsyncResult, ) -> ComposeState { match reply.into() { AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), diff --git a/src/scope.rs b/src/scope.rs index aecfd6bf..00bcadad 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -10,8 +10,10 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middleware::{Finished as MiddlewareFinished, Middleware, - Response as MiddlewareResponse, Started as MiddlewareStarted}; +use middleware::{ + Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, + Started as MiddlewareStarted, +}; use pred::Predicate; use resource::ResourceHandler; use router::Resource; @@ -400,8 +402,7 @@ struct Wrapper { impl RouteHandler for Wrapper { fn handle(&mut self, req: HttpRequest) -> AsyncResult { - self.scope - .handle(req.change_state(Rc::clone(&self.state))) + self.scope.handle(req.change_state(Rc::clone(&self.state))) } } @@ -458,7 +459,8 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>, + req: HttpRequest, + mws: Rc>>>, resource: Rc>>, default: Option>>>, ) -> Self { @@ -543,17 +545,17 @@ impl StartMiddlewares { if let Some(resp) = resp { return Some(RunMiddlewares::init(info, resp)); } - if info.count == len { - let resource = unsafe { &mut *info.resource.get() }; - let reply = if let Some(ref default) = info.default { - let d = unsafe { &mut *default.as_ref().get() }; - resource.handle(info.req.clone(), Some(d)) + loop { + if info.count == len { + let resource = unsafe { &mut *info.resource.get() }; + let reply = if let Some(ref default) = info.default { + let d = unsafe { &mut *default.as_ref().get() }; + resource.handle(info.req.clone(), Some(d)) + } else { + resource.handle(info.req.clone(), None) + }; + return Some(WaitingResponse::init(info, reply)); } else { - resource.handle(info.req.clone(), None) - }; - return Some(WaitingResponse::init(info, reply)); - } else { - loop { match info.mws[info.count].start(&mut info.req) { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { @@ -583,7 +585,8 @@ struct WaitingResponse { impl WaitingResponse { #[inline] fn init( - info: &mut ComposeInfo, reply: AsyncResult, + info: &mut ComposeInfo, + reply: AsyncResult, ) -> ComposeState { match reply.into() { AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 99151afd..2c9160b6 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -21,28 +21,24 @@ struct MiddlewareTest { impl middleware::Middleware for MiddlewareTest { fn start(&self, _: &mut HttpRequest) -> Result { - self.start.store( - self.start.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); + self.start + .store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Started::Done) } fn response( - &self, _: &mut HttpRequest, resp: HttpResponse, + &self, + _: &mut HttpRequest, + resp: HttpResponse, ) -> Result { - self.response.store( - self.response.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); + self.response + .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Response::Done(resp)) } fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish.store( - self.finish.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); + self.finish + .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middleware::Finished::Done } } @@ -187,10 +183,7 @@ fn test_scope_middleware() { }) }); - let request = srv.get() - .uri(srv.url("/scope/test")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -226,10 +219,7 @@ fn test_scope_middleware_multiple() { }) }); - let request = srv.get() - .uri(srv.url("/scope/test")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -337,10 +327,7 @@ fn test_scope_middleware_async_handler() { }) }); - let request = srv.get() - .uri(srv.url("/scope/test")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -402,10 +389,7 @@ fn test_scope_middleware_async_error() { }) }); - let request = srv.get() - .uri(srv.url("/scope/test")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); @@ -466,7 +450,9 @@ impl middleware::Middleware for MiddlewareAsyncTest { } fn response( - &self, _: &mut HttpRequest, resp: HttpResponse, + &self, + _: &mut HttpRequest, + resp: HttpResponse, ) -> Result { let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap(); @@ -555,6 +541,42 @@ fn test_async_middleware_multiple() { assert_eq!(num3.load(Ordering::Relaxed), 2); } +#[test] +fn test_async_sync_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new() + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .middleware(MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + + thread::sleep(Duration::from_millis(50)); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} + #[test] fn test_async_scope_middleware() { let num1 = Arc::new(AtomicUsize::new(0)); @@ -577,10 +599,7 @@ fn test_async_scope_middleware() { }) }); - let request = srv.get() - .uri(srv.url("/scope/test")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -618,10 +637,45 @@ fn test_async_scope_middleware_multiple() { }) }); - let request = srv.get() - .uri(srv.url("/scope/test")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + + thread::sleep(Duration::from_millis(20)); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} + +#[test] +fn test_async_async_scope_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/scope", |scope| { + scope + .middleware(MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .middleware(MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }) + }); + + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -703,3 +757,42 @@ fn test_async_resource_middleware_multiple() { thread::sleep(Duration::from_millis(40)); assert_eq!(num3.load(Ordering::Relaxed), 2); } + +#[test] +fn test_async_sync_resource_middleware_multiple() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw1 = MiddlewareAsyncTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + let mw2 = MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new().resource("/test", move |r| { + r.middleware(mw1); + r.middleware(mw2); + r.h(|_| HttpResponse::Ok()); + }) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + assert_eq!(num1.load(Ordering::Relaxed), 2); + assert_eq!(num2.load(Ordering::Relaxed), 2); + + thread::sleep(Duration::from_millis(40)); + assert_eq!(num3.load(Ordering::Relaxed), 2); +} From fe2b50a9efaaa50f7024fd304c9de0c70ab8b292 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 16 May 2018 11:02:50 -0700 Subject: [PATCH 0236/1635] update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index aa4d7c45..39467e01 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ * Panic during middleware execution #226 +* Add support for listen_tls/listen_ssl #224 + ## 0.6.5 (2018-05-15) From b4252f8fd18f8ae30bba066f771a019fc12260ab Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 16 May 2018 21:02:51 -0700 Subject: [PATCH 0237/1635] implement extractor for Session --- CHANGES.md | 4 ++- src/middleware/session.rs | 58 ++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 39467e01..07bd7b32 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,13 @@ # Changes -## 0.6.6 (2018-05-16) +## 0.6.6 (2018-05-17) * Panic during middleware execution #226 * Add support for listen_tls/listen_ssl #224 +* Implement extractor for `Session` + ## 0.6.5 (2018-05-15) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index b926842f..4565cc11 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -80,6 +80,7 @@ use serde_json::error::Error as JsonError; use time::Duration; use error::{Error, ResponseError, Result}; +use handler::FromRequest; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; @@ -190,6 +191,16 @@ impl Session { } } +impl FromRequest for Session { + type Config = (); + type Result = Session; + + #[inline] + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + req.session() + } +} + struct SessionImplCell(RefCell>); #[doc(hidden)] @@ -226,22 +237,21 @@ impl> Middleware for SessionStorage { fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); - let fut = self.0 - .from_request(&mut req) - .then(move |res| match res { - Ok(sess) => { - req.extensions_mut().insert(Arc::new(SessionImplCell( - RefCell::new(Box::new(sess)), - ))); - FutOk(None) - } - Err(err) => FutErr(err), - }); + let fut = self.0.from_request(&mut req).then(move |res| match res { + Ok(sess) => { + req.extensions_mut() + .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess))))); + FutOk(None) + } + Err(err) => FutErr(err), + }); Ok(Started::Future(Box::new(fut))) } fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, + &self, + req: &mut HttpRequest, + resp: HttpResponse, ) -> Result { if let Some(s_box) = req.extensions_mut().remove::>() { s_box.0.borrow_mut().write(resp) @@ -357,7 +367,9 @@ impl CookieSessionInner { } fn set_cookie( - &self, resp: &mut HttpResponse, state: &HashMap, + &self, + resp: &mut HttpResponse, + state: &HashMap, ) -> Result<()> { let value = serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; @@ -551,4 +563,24 @@ mod tests { let response = srv.execute(request.send()).unwrap(); assert!(response.cookie("actix-session").is_some()); } + + #[test] + fn cookie_session_extractor() { + let mut srv = test::TestServer::with_factory(|| { + App::new() + .middleware(SessionStorage::new( + CookieSessionBackend::signed(&[0; 32]).secure(false), + )) + .resource("/", |r| { + r.with(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + }) + }) + }); + + let request = srv.get().uri(srv.url("/")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.cookie("actix-session").is_some()); + } } From 8de1f60347638bb29080a16042758ac883a6bad4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 16 May 2018 21:05:59 -0700 Subject: [PATCH 0238/1635] add session extractor doc api --- src/middleware/session.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 4565cc11..6225bc34 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -191,6 +191,24 @@ impl Session { } } +/// Extractor implementation for Session type. +/// +/// ```rust +/// # use actix_web::*; +/// use actix_web::middleware::session::Session; +/// +/// fn index(session: Session) -> Result<&'static str> { +/// // access session data +/// if let Some(count) = session.get::("counter")? { +/// session.set("counter", count+1)?; +/// } else { +/// session.set("counter", 1)?; +/// } +/// +/// Ok("Welcome!") +/// } +/// # fn main() {} +/// ``` impl FromRequest for Session { type Config = (); type Result = Session; From f3ece74406b4cbccac79bc5be2dadae8ac3d7b7f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 17 May 2018 10:58:08 -0700 Subject: [PATCH 0239/1635] better error handling --- src/server/channel.rs | 23 ++++++++++++----------- src/server/h1.rs | 3 +-- src/server/h1decoder.rs | 39 +++++++++++++++++++++------------------ 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index e5d226ed..9c30fe01 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -38,7 +38,9 @@ where H: HttpHandler + 'static, { pub(crate) fn new( - settings: Rc>, mut io: T, peer: Option, + settings: Rc>, + mut io: T, + peer: Option, http2: bool, ) -> HttpChannel { settings.add_channel(); @@ -61,7 +63,7 @@ where settings, peer, io, - BytesMut::with_capacity(4096), + BytesMut::with_capacity(8192), )), } } @@ -93,12 +95,12 @@ where let el = self as *mut _; self.node = Some(Node::new(el)); let _ = match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => self.node - .as_ref() - .map(|n| h1.settings().head().insert(n)), - Some(HttpProtocol::H2(ref mut h2)) => self.node - .as_ref() - .map(|n| h2.settings().head().insert(n)), + Some(HttpProtocol::H1(ref mut h1)) => { + self.node.as_ref().map(|n| h1.settings().head().insert(n)) + } + Some(HttpProtocol::H2(ref mut h2)) => { + self.node.as_ref().map(|n| h2.settings().head().insert(n)) + } Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => { self.node.as_ref().map(|n| settings.head().insert(n)) } @@ -168,9 +170,8 @@ where if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { match kind { ProtocolKind::Http1 => { - self.proto = Some(HttpProtocol::H1(h1::Http1::new( - settings, io, addr, buf, - ))); + self.proto = + Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf))); return self.poll(); } ProtocolKind::Http2 => { diff --git a/src/server/h1.rs b/src/server/h1.rs index 9418616c..46ec3473 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -162,7 +162,6 @@ where entry.pipe.disconnected() } // kill keepalive - self.flags.remove(Flags::KEEPALIVE); self.keepalive_timer.take(); // on parse error, stop reading stream but tasks need to be @@ -352,7 +351,7 @@ where Ok(Async::NotReady) => {} Err(err) => { error!("Unhandled error: {}", err); - self.flags.intersects(Flags::ERROR); + self.flags.insert(Flags::ERROR); return; } } diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 375923d0..0d83bfbd 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -46,7 +46,9 @@ impl H1Decoder { } pub fn decode( - &mut self, src: &mut BytesMut, settings: &WorkerSettings, + &mut self, + src: &mut BytesMut, + settings: &WorkerSettings, ) -> Result, DecoderError> { // read payload if self.decoder.is_some() { @@ -64,18 +66,11 @@ impl H1Decoder { .map_err(DecoderError::Error)? { Async::Ready((msg, decoder)) => { - if let Some(decoder) = decoder { - self.decoder = Some(decoder); - Ok(Some(Message::Message { - msg, - payload: true, - })) - } else { - Ok(Some(Message::Message { - msg, - payload: false, - })) - } + self.decoder = decoder; + Ok(Some(Message::Message { + msg, + payload: self.decoder.is_some(), + })) } Async::NotReady => { if src.len() >= MAX_BUFFER_SIZE { @@ -89,7 +84,9 @@ impl H1Decoder { } fn parse_message( - &self, buf: &mut BytesMut, settings: &WorkerSettings, + &self, + buf: &mut BytesMut, + settings: &WorkerSettings, ) -> Poll<(SharedHttpInnerMessage, Option), ParseError> { // Parse http message let mut has_upgrade = false; @@ -148,7 +145,7 @@ impl H1Decoder { header::CONTENT_LENGTH => { if let Ok(s) = value.to_str() { if let Ok(len) = s.parse::() { - content_length = Some(len) + content_length = Some(len); } else { debug!("illegal Content-Length: {:?}", len); return Err(ParseError::Header); @@ -351,7 +348,10 @@ macro_rules! byte ( impl ChunkedState { fn step( - &self, body: &mut BytesMut, size: &mut u64, buf: &mut Option, + &self, + body: &mut BytesMut, + size: &mut u64, + buf: &mut Option, ) -> Poll { use self::ChunkedState::*; match *self { @@ -414,7 +414,8 @@ impl ChunkedState { } } fn read_size_lf( - rdr: &mut BytesMut, size: &mut u64, + rdr: &mut BytesMut, + size: &mut u64, ) -> Poll { match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), @@ -427,7 +428,9 @@ impl ChunkedState { } fn read_body( - rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option, + rdr: &mut BytesMut, + rem: &mut u64, + buf: &mut Option, ) -> Poll { trace!("Chunked read, remaining={:?}", rem); From 2d83f79433a6abaf4dced8dd60b9cb54aa54b72c Mon Sep 17 00:00:00 2001 From: qrvaelet Date: Thu, 17 May 2018 20:09:41 +0200 Subject: [PATCH 0240/1635] NamedFile: added ranges support, content-length support --- src/fs.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index bb808ab7..106d187a 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -20,7 +20,7 @@ use mime_guess::{get_mime_type, guess_mime_type}; use error::Error; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use header; -use http::{Method, StatusCode}; +use http::{HttpRange, Method, StatusCode}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -209,7 +209,7 @@ impl Responder for NamedFile { }).if_some(self.path().file_name(), |file_name, resp| { let mime_type = guess_mime_type(self.path()); let inline_or_attachment = match mime_type.type_() { - mime::IMAGE | mime::TEXT => "inline", + mime::IMAGE | mime::TEXT | mime::VIDEO => "inline", _ => "attachment", }; resp.header( @@ -228,6 +228,7 @@ impl Responder for NamedFile { .unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, + counter: 0, }; return Ok(resp.streaming(reader)); } @@ -274,7 +275,7 @@ impl Responder for NamedFile { }).if_some(self.path().file_name(), |file_name, resp| { let mime_type = guess_mime_type(self.path()); let inline_or_attachment = match mime_type.type_() { - mime::IMAGE | mime::TEXT => "inline", + mime::IMAGE | mime::TEXT | mime::VIDEO => "inline", _ => "attachment", }; resp.header( @@ -292,6 +293,31 @@ impl Responder for NamedFile { .if_some(etag, |etag, resp| { resp.set(header::ETag(etag)); }); + + // TODO: Debug, enabling "accept-ranges: bytes" causes problems with + // certain clients when not using the ranges header. + //resp.header(header::ACCEPT_RANGES, format!("bytes")); + + let mut length = self.md.len(); + let mut offset = 0; + + // check for ranges header + if let Some(ranges) = req.headers().get(header::RANGE) { + if let Ok(rangesheader) = ranges.to_str() { + if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { + length = rangesvec[0].length - 1; + offset = rangesvec[0].start; + resp.header(header::RANGE, format!("bytes={}-{}/{}", offset, offset+length, self.md.len())); + } else { + resp.header(header::RANGE, format!("*/{}", length)); + return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); + }; + } else { + return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); + }; + }; + + resp.header(header::CONTENT_LENGTH, format!("{}", length)); if precondition_failed { return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); @@ -303,12 +329,16 @@ impl Responder for NamedFile { Ok(resp.finish()) } else { let reader = ChunkedReadFile { - size: self.md.len(), - offset: 0, + size: length, + offset: offset, cpu_pool: self.cpu_pool .unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, + counter: 0, + }; + if offset != 0 || length != self.md.len() { + return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); }; Ok(resp.streaming(reader)) } @@ -323,6 +353,7 @@ pub struct ChunkedReadFile { cpu_pool: CpuPool, file: Option, fut: Option>, + counter: u64, } impl Stream for ChunkedReadFile { @@ -336,6 +367,7 @@ impl Stream for ChunkedReadFile { self.fut.take(); self.file = Some(file); self.offset += bytes.len() as u64; + self.counter += bytes.len() as u64; Ok(Async::Ready(Some(bytes))) } Async::NotReady => Ok(Async::NotReady), @@ -344,14 +376,16 @@ impl Stream for ChunkedReadFile { let size = self.size; let offset = self.offset; + let counter = self.counter; - if size == offset { + if size == counter { Ok(Async::Ready(None)) } else { let mut file = self.file.take().expect("Use after completion"); self.fut = Some(self.cpu_pool.spawn_fn(move || { - let max_bytes = cmp::min(size.saturating_sub(offset), 65_536) as usize; - let mut buf = BytesMut::with_capacity(max_bytes); + let max_bytes: usize; + max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; + let mut buf = BytesMut::from(Vec::with_capacity(max_bytes)); file.seek(io::SeekFrom::Start(offset))?; let nbytes = file.read(unsafe { buf.bytes_mut() })?; if nbytes == 0 { @@ -742,6 +776,49 @@ mod tests { ); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } + + #[test] + fn test_named_file_ranges_status_code() { + let mut srv = test::TestServer::with_factory(|| { + App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) + }); + + let request = srv.get() + .uri(srv.url("/t%65st/Cargo.toml")) + .header(header::RANGE, "bytes=10-20") + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); + } + + #[test] + fn test_named_file_ranges_headers() { + let mut srv = test::TestServer::with_factory(|| { + App::new().handler("test", StaticFiles::new(".").index_file("tests/test.binary")) + }); + + let request = srv.get() + .uri(srv.url("/t%65st/tests/test.binary")) + .header(header::RANGE, "bytes=10-20") + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + let contentlength = response.headers().get(header::CONTENT_LENGTH).unwrap().to_str().unwrap(); + + assert_eq!(contentlength, "10"); + + let request = srv.get() + .uri(srv.url("/t%65st/tests/test.binary")) + .header(header::RANGE, "bytes=10-20") + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + let range = response.headers().get(header::RANGE).unwrap().to_str().unwrap(); + + assert_eq!(range, "bytes=10-20/100"); + } #[test] fn test_named_file_not_allowed() { From 564cc15c04a16e68140b295f4e582d4888d91a43 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 17 May 2018 12:20:04 -0700 Subject: [PATCH 0241/1635] update changes --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 07bd7b32..650423f7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Implement extractor for `Session` +* Ranges header support for NamedFile #60 + ## 0.6.5 (2018-05-15) From 45e9aaa46248b38570f53d6c16c5279f00054839 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 17 May 2018 12:20:20 -0700 Subject: [PATCH 0242/1635] rustfmt 0.7 --- src/application.rs | 24 ++---- src/client/connector.rs | 26 ++---- src/client/mod.rs | 6 +- src/client/pipeline.rs | 12 ++- src/client/request.rs | 13 +-- src/client/response.rs | 21 ++--- src/client/writer.rs | 11 ++- src/context.rs | 9 +- src/de.rs | 16 ++-- src/extractor.rs | 37 ++------- src/fs.rs | 132 ++++++++++++++++-------------- src/handler.rs | 16 ++-- src/header/shared/encoding.rs | 5 +- src/header/shared/entity.rs | 16 ++-- src/header/shared/httpdate.rs | 8 +- src/header/shared/quality_item.rs | 12 +-- src/helpers.rs | 42 ++-------- src/httpcodes.rs | 15 +--- src/httpmessage.rs | 24 +++--- src/httprequest.rs | 9 +- src/httpresponse.rs | 34 ++++---- src/info.rs | 15 ++-- src/json.rs | 16 ++-- src/middleware/cors.rs | 33 ++++---- src/middleware/defaultheaders.rs | 4 +- src/middleware/identity.rs | 3 +- src/middleware/logger.rs | 8 +- src/middleware/session.rs | 8 +- src/multipart.rs | 15 ++-- src/payload.rs | 5 +- src/pipeline.rs | 21 ++--- src/pred.rs | 6 +- src/resource.rs | 5 +- src/route.rs | 17 ++-- src/router.rs | 18 ++-- src/scope.rs | 6 +- src/server/channel.rs | 4 +- src/server/encoding.rs | 11 +-- src/server/h1.rs | 7 +- src/server/h1decoder.rs | 23 ++---- src/server/h1writer.rs | 8 +- src/server/h2.rs | 9 +- src/server/h2writer.rs | 3 +- src/server/helpers.rs | 50 +++-------- src/server/settings.rs | 5 +- src/server/srv.rs | 18 ++-- src/server/worker.rs | 67 +++++++-------- src/with.rs | 16 ++-- src/ws/client.rs | 32 +++----- src/ws/context.rs | 9 +- src/ws/frame.rs | 9 +- src/ws/mod.rs | 13 ++- tests/test_client.rs | 33 ++++---- tests/test_handlers.rs | 130 +++++++++++++---------------- tests/test_middleware.rs | 8 +- tests/test_server.rs | 33 ++++---- tests/test_ws.rs | 33 +++----- tools/wsload/src/wsclient.rs | 77 ++++++++--------- 58 files changed, 508 insertions(+), 758 deletions(-) diff --git a/src/application.rs b/src/application.rs index 4b5747a4..481430aa 100644 --- a/src/application.rs +++ b/src/application.rs @@ -780,9 +780,7 @@ mod tests { #[test] fn test_handler() { - let mut app = App::new() - .handler("/test", |_| HttpResponse::Ok()) - .finish(); + let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -807,9 +805,7 @@ mod tests { #[test] fn test_handler2() { - let mut app = App::new() - .handler("test", |_| HttpResponse::Ok()) - .finish(); + let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -863,29 +859,21 @@ mod tests { #[test] fn test_route() { let mut app = App::new() - .route("/test", Method::GET, |_: HttpRequest| { - HttpResponse::Ok() - }) + .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) .route("/test", Method::POST, |_: HttpRequest| { HttpResponse::Created() }) .finish(); - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::GET).finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::POST).finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } diff --git a/src/client/connector.rs b/src/client/connector.rs index e082c9ed..6389b897 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -8,8 +8,10 @@ use std::{fmt, io, mem, time}; use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; use actix::fut::WrapFuture; use actix::registry::ArbiterService; -use actix::{fut, Actor, ActorFuture, ActorResponse, Arbiter, AsyncContext, Context, - ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn}; +use actix::{ + fut, Actor, ActorFuture, ActorResponse, Arbiter, AsyncContext, Context, + ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn, +}; use futures::task::{current as current_task, Task}; use futures::unsync::oneshot; @@ -429,8 +431,7 @@ impl ClientConnector { } else { 0 }; - self.acquired_per_host - .insert(key.clone(), per_host + 1); + self.acquired_per_host.insert(key.clone(), per_host + 1); } fn release_key(&mut self, key: &Key) { @@ -441,8 +442,7 @@ impl ClientConnector { return; }; if per_host > 1 { - self.acquired_per_host - .insert(key.clone(), per_host - 1); + self.acquired_per_host.insert(key.clone(), per_host - 1); } else { self.acquired_per_host.remove(key); } @@ -518,9 +518,7 @@ impl ClientConnector { fn collect_periodic(&mut self, ctx: &mut Context) { self.collect(true); // re-schedule next collect period - ctx.run_later(Duration::from_secs(1), |act, ctx| { - act.collect_periodic(ctx) - }); + ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); // send stats let stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); @@ -1107,10 +1105,7 @@ impl Pool { if self.to_close.borrow().is_empty() { None } else { - Some(mem::replace( - &mut *self.to_close.borrow_mut(), - Vec::new(), - )) + Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new())) } } @@ -1118,10 +1113,7 @@ impl Pool { if self.to_release.borrow().is_empty() { None } else { - Some(mem::replace( - &mut *self.to_release.borrow_mut(), - Vec::new(), - )) + Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new())) } } diff --git a/src/client/mod.rs b/src/client/mod.rs index 2116ae36..9fd885fa 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -33,8 +33,10 @@ mod request; mod response; mod writer; -pub use self::connector::{ClientConnector, ClientConnectorError, ClientConnectorStats, - Connect, Connection, Pause, Resume}; +pub use self::connector::{ + ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection, + Pause, Resume, +}; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 6a36bdd2..dae7bbaf 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -270,7 +270,8 @@ impl Pipeline { #[inline] fn parse(&mut self) -> Poll { if let Some(ref mut conn) = self.conn { - match self.parser + match self + .parser .as_mut() .unwrap() .parse(conn, &mut self.parser_buf) @@ -311,7 +312,8 @@ impl Pipeline { let mut need_run = false; // need write? - match self.poll_write() + match self + .poll_write() .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? { Async::NotReady => need_run = true, @@ -325,7 +327,8 @@ impl Pipeline { // need read? if self.parser.is_some() { loop { - match self.parser + match self + .parser .as_mut() .unwrap() .parse_payload(conn, &mut self.parser_buf)? @@ -469,7 +472,8 @@ impl Pipeline { } // flush io but only if we need to - match self.writer + match self + .writer .poll_completed(self.conn.as_mut().unwrap(), false) { Ok(Async::Ready(_)) => { diff --git a/src/client/request.rs b/src/client/request.rs index 4eaf8002..2f9ce12f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -499,10 +499,7 @@ impl ClientRequestBuilder { jar.add(cookie.into_owned()); self.cookies = Some(jar) } else { - self.cookies - .as_mut() - .unwrap() - .add(cookie.into_owned()); + self.cookies.as_mut().unwrap().add(cookie.into_owned()); } self } @@ -610,9 +607,7 @@ impl ClientRequestBuilder { } } - let mut request = self.request - .take() - .expect("cannot reuse request builder"); + let mut request = self.request.take().expect("cannot reuse request builder"); // set cookies if let Some(ref mut jar) = self.cookies { @@ -657,9 +652,7 @@ impl ClientRequestBuilder { S: Stream + 'static, E: Into, { - self.body(Body::Streaming(Box::new( - stream.map_err(|e| e.into()), - ))) + self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) } /// Set an empty body and generate `ClientRequest` diff --git a/src/client/response.rs b/src/client/response.rs index 4d186d19..f76d058e 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -103,12 +103,7 @@ impl ClientResponse { impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!( - f, - "\nClientResponse {:?} {}", - self.version(), - self.status() - ); + let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status()); let _ = writeln!(f, " headers:"); for (key, val) in self.headers().iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); @@ -138,14 +133,12 @@ mod tests { #[test] fn test_debug() { let resp = ClientResponse::new(ClientMessage::default()); - resp.as_mut().headers.insert( - header::COOKIE, - HeaderValue::from_static("cookie1=value1"), - ); - resp.as_mut().headers.insert( - header::COOKIE, - HeaderValue::from_static("cookie2=value2"), - ); + resp.as_mut() + .headers + .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1")); + resp.as_mut() + .headers + .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2")); let dbg = format!("{:?}", resp); assert!(dbg.contains("ClientResponse")); diff --git a/src/client/writer.rs b/src/client/writer.rs index 36c9d6ee..addc0324 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -12,8 +12,9 @@ use flate2::write::{DeflateEncoder, GzEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; use futures::{Async, Poll}; -use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, - TRANSFER_ENCODING}; +use http::header::{ + HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; use http::{HttpTryFrom, Version}; use time::{self, Duration}; use tokio_io::AsyncWrite; @@ -253,10 +254,8 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder } let mut b = BytesMut::new(); let _ = write!(b, "{}", bytes.len()); - req.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(b.freeze()).unwrap(), - ); + req.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); TransferEncoding::eof(buf) } Body::Streaming(_) | Body::Actor(_) => { diff --git a/src/context.rs b/src/context.rs index 933fed50..375e8ef1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,8 +6,10 @@ use std::marker::PhantomData; use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::fut::ActorFuture; -use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, - SpawnHandle, Syn, Unsync}; +use actix::{ + Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, + Syn, Unsync, +}; use body::{Binary, Body}; use error::{Error, ErrorInternalServerError}; @@ -80,7 +82,8 @@ where #[doc(hidden)] #[inline] fn waiting(&self) -> bool { - self.inner.waiting() || self.inner.state() == ActorState::Stopping + self.inner.waiting() + || self.inner.state() == ActorState::Stopping || self.inner.state() == ActorState::Stopped } #[inline] diff --git a/src/de.rs b/src/de.rs index 3ab3646e..ad332787 100644 --- a/src/de.rs +++ b/src/de.rs @@ -202,7 +202,8 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { where K: de::DeserializeSeed<'de>, { - self.current = self.params + self.current = self + .params .next() .map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); match self.current { @@ -336,9 +337,7 @@ impl<'de> Deserializer<'de> for Value<'de> { where V: Visitor<'de>, { - visitor.visit_enum(ValueEnum { - value: self.value, - }) + visitor.visit_enum(ValueEnum { value: self.value }) } fn deserialize_newtype_struct( @@ -372,9 +371,7 @@ impl<'de> Deserializer<'de> for Value<'de> { where V: Visitor<'de>, { - Err(de::value::Error::custom( - "unsupported type: tuple struct", - )) + Err(de::value::Error::custom("unsupported type: tuple struct")) } unsupported_type!(deserialize_any, "any"); @@ -415,10 +412,7 @@ impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { where V: de::DeserializeSeed<'de>, { - Ok(( - seed.deserialize(Key { key: self.value })?, - UnitVariant, - )) + Ok((seed.deserialize(Key { key: self.value })?, UnitVariant)) } } diff --git a/src/extractor.rs b/src/extractor.rs index a08e9667..fc9145b9 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -11,7 +11,7 @@ use serde::de::{self, DeserializeOwned}; use serde_urlencoded; use de::PathDeserializer; -use error::{Error, ErrorNotFound, ErrorBadRequest}; +use error::{Error, ErrorBadRequest, ErrorNotFound}; use handler::{AsyncResult, FromRequest}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; @@ -330,9 +330,7 @@ impl FromRequest for Bytes { cfg.check_mimetype(req)?; Ok(Box::new( - MessageBody::new(req.clone()) - .limit(cfg.limit) - .from_err(), + MessageBody::new(req.clone()).limit(cfg.limit).from_err(), )) } } @@ -512,14 +510,7 @@ tuple_from_req!(TupleFromRequest1, (0, A)); tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); -tuple_from_req!( - TupleFromRequest5, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E) -); +tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); tuple_from_req!( TupleFromRequest6, (0, A), @@ -587,11 +578,7 @@ mod tests { req.payload_mut() .unread_data(Bytes::from_static(b"hello=world")); - match Bytes::from_request(&req, &cfg) - .unwrap() - .poll() - .unwrap() - { + match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { Async::Ready(s) => { assert_eq!(s, Bytes::from_static(b"hello=world")); } @@ -606,11 +593,7 @@ mod tests { req.payload_mut() .unread_data(Bytes::from_static(b"hello=world")); - match String::from_request(&req, &cfg) - .unwrap() - .poll() - .unwrap() - { + match String::from_request(&req, &cfg).unwrap().poll().unwrap() { Async::Ready(s) => { assert_eq!(s, "hello=world"); } @@ -680,10 +663,7 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push(( - Resource::new("index", "/{key}/{value}/"), - Some(resource), - )); + routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); @@ -735,10 +715,7 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push(( - Resource::new("index", "/{key}/{value}/"), - Some(resource), - )); + routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); diff --git a/src/fs.rs b/src/fs.rs index 106d187a..2de35994 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -203,29 +203,26 @@ impl Responder for NamedFile { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type( - &ext.to_string_lossy(), - ))); + resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); }).if_some(self.path().file_name(), |file_name, resp| { - let mime_type = guess_mime_type(self.path()); - let inline_or_attachment = match mime_type.type_() { - mime::IMAGE | mime::TEXT | mime::VIDEO => "inline", - _ => "attachment", - }; - resp.header( - "Content-Disposition", - format!( - "{inline_or_attachment}; filename={filename}", - inline_or_attachment = inline_or_attachment, - filename = file_name.to_string_lossy() - ), - ); - }); + let mime_type = guess_mime_type(self.path()); + let inline_or_attachment = match mime_type.type_() { + mime::IMAGE | mime::TEXT | mime::VIDEO => "inline", + _ => "attachment", + }; + resp.header( + "Content-Disposition", + format!( + "{inline_or_attachment}; filename={filename}", + inline_or_attachment = inline_or_attachment, + filename = file_name.to_string_lossy() + ), + ); + }); let reader = ChunkedReadFile { size: self.md.len(), offset: 0, - cpu_pool: self.cpu_pool - .unwrap_or_else(|| req.cpu_pool().clone()), + cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, counter: 0, @@ -269,9 +266,7 @@ impl Responder for NamedFile { let mut resp = HttpResponse::build(self.status_code); resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type( - &ext.to_string_lossy(), - ))); + resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); }).if_some(self.path().file_name(), |file_name, resp| { let mime_type = guess_mime_type(self.path()); let inline_or_attachment = match mime_type.type_() { @@ -293,9 +288,9 @@ impl Responder for NamedFile { .if_some(etag, |etag, resp| { resp.set(header::ETag(etag)); }); - - // TODO: Debug, enabling "accept-ranges: bytes" causes problems with - // certain clients when not using the ranges header. + + // TODO: Debug, enabling "accept-ranges: bytes" causes problems with + // certain clients when not using the ranges header. //resp.header(header::ACCEPT_RANGES, format!("bytes")); let mut length = self.md.len(); @@ -307,7 +302,15 @@ impl Responder for NamedFile { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { length = rangesvec[0].length - 1; offset = rangesvec[0].start; - resp.header(header::RANGE, format!("bytes={}-{}/{}", offset, offset+length, self.md.len())); + resp.header( + header::RANGE, + format!( + "bytes={}-{}/{}", + offset, + offset + length, + self.md.len() + ), + ); } else { resp.header(header::RANGE, format!("*/{}", length)); return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); @@ -331,8 +334,7 @@ impl Responder for NamedFile { let reader = ChunkedReadFile { size: length, offset: offset, - cpu_pool: self.cpu_pool - .unwrap_or_else(|| req.cpu_pool().clone()), + cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, counter: 0, @@ -618,7 +620,8 @@ impl Handler for StaticFiles { if !self.accessible { Ok(self.default.handle(req)) } else { - let relpath = match req.match_info() + let relpath = match req + .match_info() .get("tail") .map(|tail| PathBuf::from_param(tail.trim_left_matches('/'))) { @@ -690,9 +693,7 @@ mod tests { "text/x-toml" ); assert_eq!( - resp.headers() - .get(header::CONTENT_DISPOSITION) - .unwrap(), + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), "inline; filename=Cargo.toml" ); } @@ -716,9 +717,7 @@ mod tests { "image/png" ); assert_eq!( - resp.headers() - .get(header::CONTENT_DISPOSITION) - .unwrap(), + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), "inline; filename=test.png" ); } @@ -742,9 +741,7 @@ mod tests { "application/octet-stream" ); assert_eq!( - resp.headers() - .get(header::CONTENT_DISPOSITION) - .unwrap(), + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), "attachment; filename=test.binary" ); } @@ -769,21 +766,20 @@ mod tests { "text/x-toml" ); assert_eq!( - resp.headers() - .get(header::CONTENT_DISPOSITION) - .unwrap(), + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), "inline; filename=Cargo.toml" ); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } - + #[test] fn test_named_file_ranges_status_code() { let mut srv = test::TestServer::with_factory(|| { App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) }); - let request = srv.get() + let request = srv + .get() .uri(srv.url("/t%65st/Cargo.toml")) .header(header::RANGE, "bytes=10-20") .finish() @@ -796,26 +792,41 @@ mod tests { #[test] fn test_named_file_ranges_headers() { let mut srv = test::TestServer::with_factory(|| { - App::new().handler("test", StaticFiles::new(".").index_file("tests/test.binary")) + App::new().handler( + "test", + StaticFiles::new(".").index_file("tests/test.binary"), + ) }); - let request = srv.get() + let request = srv + .get() .uri(srv.url("/t%65st/tests/test.binary")) .header(header::RANGE, "bytes=10-20") .finish() .unwrap(); let response = srv.execute(request.send()).unwrap(); - let contentlength = response.headers().get(header::CONTENT_LENGTH).unwrap().to_str().unwrap(); + let contentlength = response + .headers() + .get(header::CONTENT_LENGTH) + .unwrap() + .to_str() + .unwrap(); assert_eq!(contentlength, "10"); - let request = srv.get() + let request = srv + .get() .uri(srv.url("/t%65st/tests/test.binary")) .header(header::RANGE, "bytes=10-20") .finish() .unwrap(); let response = srv.execute(request.send()).unwrap(); - let range = response.headers().get(header::RANGE).unwrap().to_str().unwrap(); + let range = response + .headers() + .get(header::RANGE) + .unwrap() + .to_str() + .unwrap(); assert_eq!(range, "bytes=10-20/100"); } @@ -841,7 +852,8 @@ mod tests { fn test_static_files() { let mut st = StaticFiles::new(".").show_files_listing(); st.accessible = false; - let resp = st.handle(HttpRequest::default()) + let resp = st + .handle(HttpRequest::default()) .respond_to(&HttpRequest::default()) .unwrap(); let resp = resp.as_msg(); @@ -849,7 +861,8 @@ mod tests { st.accessible = true; st.show_index = false; - let resp = st.handle(HttpRequest::default()) + let resp = st + .handle(HttpRequest::default()) .respond_to(&HttpRequest::default()) .unwrap(); let resp = resp.as_msg(); @@ -859,9 +872,7 @@ mod tests { req.match_info_mut().add("tail", ""); st.show_index = true; - let resp = st.handle(req) - .respond_to(&HttpRequest::default()) - .unwrap(); + let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -877,9 +888,7 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tests"); - let resp = st.handle(req) - .respond_to(&HttpRequest::default()) - .unwrap(); + let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( @@ -890,9 +899,7 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tests/"); - let resp = st.handle(req) - .respond_to(&HttpRequest::default()) - .unwrap(); + let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( @@ -907,9 +914,7 @@ mod tests { let mut req = HttpRequest::default(); req.match_info_mut().add("tail", "tools/wsload"); - let resp = st.handle(req) - .respond_to(&HttpRequest::default()) - .unwrap(); + let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( @@ -984,7 +989,8 @@ mod tests { App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) }); - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test/%43argo.toml")) .finish() .unwrap(); diff --git a/src/handler.rs b/src/handler.rs index a10a6f9c..759291a2 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -362,7 +362,8 @@ where self, req: &HttpRequest, ) -> Result, Error> { let req = req.clone(); - let fut = self.map_err(|e| e.into()) + let fut = self + .map_err(|e| e.into()) .then(move |r| match r.respond_to(&req) { Ok(reply) => match reply.into().into() { AsyncResultItem::Ok(resp) => ok(resp), @@ -397,10 +398,7 @@ where S: 'static, { pub fn new(h: H) -> Self { - WrapHandler { - h, - s: PhantomData, - } + WrapHandler { h, s: PhantomData } } } @@ -456,16 +454,16 @@ where S: 'static, { fn handle(&mut self, req: HttpRequest) -> AsyncResult { - let fut = (self.h)(req.clone()) - .map_err(|e| e.into()) - .then(move |r| match r.respond_to(&req) { + let fut = (self.h)(req.clone()).map_err(|e| e.into()).then(move |r| { + match r.respond_to(&req) { Ok(reply) => match reply.into().into() { AsyncResultItem::Ok(resp) => Either::A(ok(resp)), AsyncResultItem::Err(e) => Either::A(err(e)), AsyncResultItem::Future(fut) => Either::B(fut), }, Err(e) => Either::A(err(e)), - }); + } + }); AsyncResult::async(Box::new(fut)) } } diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs index e4abe470..64027d8a 100644 --- a/src/header/shared/encoding.rs +++ b/src/header/shared/encoding.rs @@ -1,8 +1,9 @@ use std::fmt; use std::str; -pub use self::Encoding::{Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, - Identity, Trailers}; +pub use self::Encoding::{ + Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, +}; /// A value to represent an encoding used in `Transfer-Encoding` /// or `Accept-Encoding` header. diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs index 347c4c02..a83ce195 100644 --- a/src/header/shared/entity.rs +++ b/src/header/shared/entity.rs @@ -132,7 +132,8 @@ impl FromStr for EntityTag { return Err(::error::ParseError::Header); } // The etag is weak if its first char is not a DQUOTE. - if slice.len() >= 2 && slice.starts_with('"') + if slice.len() >= 2 + && slice.starts_with('"') && check_slice_validity(&slice[1..length - 1]) { // No need to check if the last char is a DQUOTE, @@ -141,7 +142,8 @@ impl FromStr for EntityTag { weak: false, tag: slice[1..length - 1].to_owned(), }); - } else if slice.len() >= 4 && slice.starts_with("W/\"") + } else if slice.len() >= 4 + && slice.starts_with("W/\"") && check_slice_validity(&slice[3..length - 1]) { return Ok(EntityTag { @@ -213,10 +215,7 @@ mod tests { format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\"" ); - assert_eq!( - format!("{}", EntityTag::strong("".to_owned())), - "\"\"" - ); + assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); assert_eq!( format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\"" @@ -225,10 +224,7 @@ mod tests { format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\"" ); - assert_eq!( - format!("{}", EntityTag::weak("".to_owned())), - "W/\"\"" - ); + assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); } #[test] diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs index 5de1e3f9..60075e1a 100644 --- a/src/header/shared/httpdate.rs +++ b/src/header/shared/httpdate.rs @@ -105,9 +105,7 @@ mod tests { #[test] fn test_date() { assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT" - .parse::() - .unwrap(), + "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), NOV_07 ); assert_eq!( @@ -117,9 +115,7 @@ mod tests { NOV_07 ); assert_eq!( - "Sun Nov 7 08:48:37 1994" - .parse::() - .unwrap(), + "Sun Nov 7 08:48:37 1994".parse::().unwrap(), NOV_07 ); assert!("this-is-no-date".parse::().is_err()); diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs index 5f1e5977..a9488e81 100644 --- a/src/header/shared/quality_item.rs +++ b/src/header/shared/quality_item.rs @@ -63,11 +63,7 @@ impl fmt::Display for QualityItem { match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), - x => write!( - f, - "; q=0.{}", - format!("{:03}", x).trim_right_matches('0') - ), + x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), } } } @@ -295,10 +291,6 @@ mod tests { #[test] fn test_fuzzing_bugs() { assert!("99999;".parse::>().is_err()); - assert!( - "\x0d;;;=\u{d6aa}==" - .parse::>() - .is_err() - ) + assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) } } diff --git a/src/helpers.rs b/src/helpers.rs index 9db0e863..c94c24d9 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -190,16 +190,8 @@ mod tests { // trailing slashes let params = vec![ ("/resource1", "", StatusCode::OK), - ( - "/resource1/", - "/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource2", - "/resource2/", - StatusCode::MOVED_PERMANENTLY, - ), + ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), + ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), ("/resource2/", "", StatusCode::OK), ("/resource1?p1=1&p2=2", "", StatusCode::OK), ( @@ -222,11 +214,7 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap() + r.headers().get(header::LOCATION).unwrap().to_str().unwrap() ); } } @@ -276,16 +264,8 @@ mod tests { // trailing slashes let params = vec![ ("/resource1/a/b", "", StatusCode::OK), - ( - "/resource1/", - "/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/resource1//", - "/resource1", - StatusCode::MOVED_PERMANENTLY, - ), + ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), + ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), ( "//resource1//a//b", "/resource1/a/b", @@ -356,11 +336,7 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap() + r.headers().get(header::LOCATION).unwrap().to_str().unwrap() ); } } @@ -540,11 +516,7 @@ mod tests { if !target.is_empty() { assert_eq!( target, - r.headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap() + r.headers().get(header::LOCATION).unwrap().to_str().unwrap() ); } } diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 6d1c5ed1..2933cf17 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -55,10 +55,7 @@ impl HttpResponse { STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); - STATIC_RESP!( - UnsupportedMediaType, - StatusCode::UNSUPPORTED_MEDIA_TYPE - ); + STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); @@ -67,14 +64,8 @@ impl HttpResponse { STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - STATIC_RESP!( - VersionNotSupported, - StatusCode::HTTP_VERSION_NOT_SUPPORTED - ); - STATIC_RESP!( - VariantAlsoNegotiates, - StatusCode::VARIANT_ALSO_NEGOTIATES - ); + STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); + STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index d80ed703..2f23e653 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -11,7 +11,9 @@ use serde::de::DeserializeOwned; use serde_urlencoded; use std::str; -use error::{ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError}; +use error::{ + ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError, +}; use header::Header; use json::JsonBody; use multipart::Multipart; @@ -96,10 +98,8 @@ pub trait HttpMessage { /// `size` is full size of response (file). fn range(&self, size: u64) -> Result, HttpRangeError> { if let Some(range) = self.headers().get(header::RANGE) { - HttpRange::parse( - unsafe { str::from_utf8_unchecked(range.as_bytes()) }, - size, - ).map_err(|e| e.into()) + HttpRange::parse(unsafe { str::from_utf8_unchecked(range.as_bytes()) }, size) + .map_err(|e| e.into()) } else { Ok(Vec::new()) } @@ -385,12 +385,12 @@ where if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Err(UrlencodedError::ContentType); } - let encoding = req.encoding() - .map_err(|_| UrlencodedError::ContentType)?; + let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; // future let limit = self.limit; - let fut = req.from_err() + let fut = req + .from_err() .fold(BytesMut::new(), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { Err(UrlencodedError::Overflow) @@ -488,10 +488,7 @@ mod tests { #[test] fn test_encoding_error() { let req = TestRequest::with_header("content-type", "applicatjson").finish(); - assert_eq!( - Some(ContentTypeError::ParseError), - req.encoding().err() - ); + assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); let req = TestRequest::with_header( "content-type", @@ -664,8 +661,7 @@ mod tests { } let mut req = HttpRequest::default(); - req.payload_mut() - .unread_data(Bytes::from_static(b"test")); + 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")), _ => unreachable!("error"), diff --git a/src/httprequest.rs b/src/httprequest.rs index 0c3ee31d..a21c9229 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -106,10 +106,7 @@ impl HttpRequest<()> { /// Construct a new Request. #[inline] pub fn new( - method: Method, - uri: Uri, - version: Version, - headers: HeaderMap, + method: Method, uri: Uri, version: Version, headers: HeaderMap, payload: Option, ) -> HttpRequest { let url = InnerUrl::new(uri); @@ -304,9 +301,7 @@ impl HttpRequest { /// } /// ``` pub fn url_for( - &self, - name: &str, - elements: U, + &self, name: &str, elements: U, ) -> Result where U: IntoIterator, diff --git a/src/httpresponse.rs b/src/httpresponse.rs index a71f53fb..428ca014 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -600,8 +600,7 @@ impl HttpResponseBuilder { #[inline] #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] fn parts<'a>( - parts: &'a mut Option>, - err: &Option, + parts: &'a mut Option>, err: &Option, ) -> Option<&'a mut Box> { if err.is_some() { return None; @@ -648,7 +647,8 @@ impl Responder for &'static str { type Error = Error; fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req.build_response(StatusCode::OK) + Ok(req + .build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) } @@ -667,7 +667,8 @@ impl Responder for &'static [u8] { type Error = Error; fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req.build_response(StatusCode::OK) + Ok(req + .build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) } @@ -686,7 +687,8 @@ impl Responder for String { type Error = Error; fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req.build_response(StatusCode::OK) + Ok(req + .build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) } @@ -705,7 +707,8 @@ impl<'a> Responder for &'a String { type Error = Error; fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req.build_response(StatusCode::OK) + Ok(req + .build_response(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(self)) } @@ -724,7 +727,8 @@ impl Responder for Bytes { type Error = Error; fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req.build_response(StatusCode::OK) + Ok(req + .build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) } @@ -743,7 +747,8 @@ impl Responder for BytesMut { type Error = Error; fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req.build_response(StatusCode::OK) + Ok(req + .build_response(StatusCode::OK) .content_type("application/octet-stream") .body(self)) } @@ -823,8 +828,7 @@ impl HttpResponsePool { #[inline] pub fn get_builder( - pool: &Rc>, - status: StatusCode, + pool: &Rc>, status: StatusCode, ) -> HttpResponseBuilder { let p = unsafe { &mut *pool.as_ref().get() }; if let Some(mut msg) = p.0.pop_front() { @@ -848,9 +852,7 @@ impl HttpResponsePool { #[inline] pub fn get_response( - pool: &Rc>, - status: StatusCode, - body: Body, + pool: &Rc>, status: StatusCode, body: Body, ) -> HttpResponse { let p = unsafe { &mut *pool.as_ref().get() }; if let Some(mut msg) = p.0.pop_front() { @@ -876,8 +878,7 @@ impl HttpResponsePool { #[inline(always)] #[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))] fn release( - pool: &Rc>, - mut inner: Box, + pool: &Rc>, mut inner: Box, ) { let pool = unsafe { &mut *pool.as_ref().get() }; if pool.0.len() < 128 { @@ -942,7 +943,8 @@ mod tests { .del_cookie(&cookies[0]) .finish(); - let mut val: Vec<_> = resp.headers() + let mut val: Vec<_> = resp + .headers() .get_all("Set-Cookie") .iter() .map(|v| v.to_str().unwrap().to_owned()) diff --git a/src/info.rs b/src/info.rs index 76288539..05d35f47 100644 --- a/src/info.rs +++ b/src/info.rs @@ -53,7 +53,8 @@ impl<'a> ConnectionInfo<'a> { // scheme if scheme.is_none() { - if let Some(h) = req.headers() + if let Some(h) = req + .headers() .get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) { if let Ok(h) = h.to_str() { @@ -74,7 +75,8 @@ impl<'a> ConnectionInfo<'a> { // host if host.is_none() { - if let Some(h) = req.headers() + if let Some(h) = req + .headers() .get(HeaderName::from_str(X_FORWARDED_HOST).unwrap()) { if let Ok(h) = h.to_str() { @@ -98,7 +100,8 @@ impl<'a> ConnectionInfo<'a> { // remote addr if remote.is_none() { - if let Some(h) = req.headers() + if let Some(h) = req + .headers() .get(HeaderName::from_str(X_FORWARDED_FOR).unwrap()) { if let Ok(h) = h.to_str() { @@ -189,10 +192,8 @@ mod tests { 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"), - ); + req.headers_mut() + .insert(header::HOST, HeaderValue::from_static("rust-lang.org")); let info = ConnectionInfo::new(&req); assert_eq!(info.scheme(), "http"); diff --git a/src/json.rs b/src/json.rs index 6711de39..e48c27ef 100644 --- a/src/json.rs +++ b/src/json.rs @@ -121,7 +121,8 @@ impl Responder for Json { fn respond_to(self, req: &HttpRequest) -> Result { let body = serde_json::to_string(&self.0)?; - Ok(req.build_response(StatusCode::OK) + Ok(req + .build_response(StatusCode::OK) .content_type("application/json") .body(body)) } @@ -295,7 +296,8 @@ where } let limit = self.limit; - let fut = req.from_err() + let fut = req + .from_err() .fold(BytesMut::new(), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { Err(JsonPayloadError::Overflow) @@ -362,10 +364,7 @@ mod tests { fn test_json_body() { let req = HttpRequest::default(); let mut json = req.json::(); - assert_eq!( - json.poll().err().unwrap(), - JsonPayloadError::ContentType - ); + assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); let mut req = HttpRequest::default(); req.headers_mut().insert( @@ -373,10 +372,7 @@ mod tests { header::HeaderValue::from_static("application/text"), ); let mut json = req.json::(); - assert_eq!( - json.poll().err().unwrap(), - JsonPayloadError::ContentType - ); + assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); let mut req = HttpRequest::default(); req.headers_mut().insert( diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 5b503630..a7b0110f 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -275,9 +275,7 @@ impl Cors { /// `ResourceHandler::middleware()` method, but in that case *Cors* /// middleware wont be able to handle *OPTIONS* requests. pub fn register(self, resource: &mut ResourceHandler) { - resource - .method(Method::OPTIONS) - .h(|_| HttpResponse::Ok()); + resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok()); resource.middleware(self); } @@ -304,12 +302,11 @@ impl Cors { fn validate_allowed_method( &self, req: &mut HttpRequest, ) -> Result<(), CorsError> { - if let Some(hdr) = req.headers() - .get(header::ACCESS_CONTROL_REQUEST_METHOD) - { + if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { - return self.inner + return self + .inner .methods .get(&method) .and_then(|_| Some(())) @@ -328,8 +325,8 @@ impl Cors { match self.inner.headers { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = req.headers() - .get(header::ACCESS_CONTROL_REQUEST_HEADERS) + if let Some(hdr) = + req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { if let Ok(headers) = hdr.to_str() { let mut hdrs = HashSet::new(); @@ -371,8 +368,8 @@ impl Middleware for Cors { .as_str()[1..], ).unwrap(), ) - } else if let Some(hdr) = req.headers() - .get(header::ACCESS_CONTROL_REQUEST_HEADERS) + } else if let Some(hdr) = + req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) { Some(hdr.clone()) } else { @@ -413,7 +410,8 @@ impl Middleware for Cors { }) .header( header::ACCESS_CONTROL_ALLOW_METHODS, - &self.inner + &self + .inner .methods .iter() .fold(String::new(), |s, v| s + "," + v.as_str()) @@ -866,7 +864,8 @@ impl CorsBuilder { } let cors = self.construct(); - let mut app = self.app + let mut app = self + .app .take() .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); @@ -1094,9 +1093,8 @@ mod tests { resp.headers().get(header::VARY).unwrap().as_bytes() ); - let resp: HttpResponse = HttpResponse::Ok() - .header(header::VARY, "Accept") - .finish(); + let resp: HttpResponse = + HttpResponse::Ok().header(header::VARY, "Accept").finish(); let resp = cors.response(&mut req, resp).unwrap().response(); assert_eq!( &b"Accept, Origin"[..], @@ -1133,7 +1131,8 @@ mod tests { let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test")) .header("ORIGIN", "https://www.example.com") .finish() diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index bab5ff0b..ebe3ea1d 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -112,9 +112,7 @@ mod tests { }; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let resp = HttpResponse::Ok() - .header(CONTENT_TYPE, "0002") - .finish(); + let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); let resp = match mw.response(&mut req, resp) { Ok(Response::Done(resp)) => resp, _ => panic!(), diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index ce18e858..36317ebc 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -179,7 +179,8 @@ impl> Middleware for IdentityService { fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); - let fut = self.backend + let fut = self + .backend .from_request(&mut req) .then(move |res| match res { Ok(id) => { diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 086c232c..985a5dfe 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -376,9 +376,7 @@ mod tests { headers, None, ); - let resp = HttpResponse::build(StatusCode::OK) - .force_close() - .finish(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); let entry_time = time::now(); let render = |fmt: &mut Formatter| { @@ -399,9 +397,7 @@ mod tests { HeaderMap::new(), None, ); - let resp = HttpResponse::build(StatusCode::OK) - .force_close() - .finish(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); let entry_time = time::now(); let render = |fmt: &mut Formatter| { diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 6225bc34..ba385d83 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -267,9 +267,7 @@ impl> Middleware for SessionStorage { } fn response( - &self, - req: &mut HttpRequest, - resp: HttpResponse, + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(s_box) = req.extensions_mut().remove::>() { s_box.0.borrow_mut().write(resp) @@ -385,9 +383,7 @@ impl CookieSessionInner { } fn set_cookie( - &self, - resp: &mut HttpResponse, - state: &HashMap, + &self, resp: &mut HttpResponse, state: &HashMap, ) -> Result<()> { let value = serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; diff --git a/src/multipart.rs b/src/multipart.rs index 056332b5..365a101c 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -122,11 +122,7 @@ where if let Some(err) = self.error.take() { Err(err) } else if self.safety.current() { - self.inner - .as_mut() - .unwrap() - .borrow_mut() - .poll(&self.safety) + self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) } else { Ok(Async::NotReady) } @@ -175,11 +171,13 @@ where Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(Some(chunk)) => { - if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--" + if chunk.len() == boundary.len() + 4 + && &chunk[..2] == b"--" && &chunk[2..boundary.len() + 2] == boundary.as_bytes() { Ok(Async::Ready(false)) - } else if chunk.len() == boundary.len() + 6 && &chunk[..2] == b"--" + } else if chunk.len() == boundary.len() + 6 + && &chunk[..2] == b"--" && &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" { @@ -514,7 +512,8 @@ where Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(Some(chunk)) => { - if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" + if &chunk[..2] == b"\r\n" + && &chunk[2..4] == b"--" && &chunk[4..] == boundary.as_bytes() { payload.unread_data(chunk); diff --git a/src/payload.rs b/src/payload.rs index a394c106..dd0b197b 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -671,10 +671,7 @@ mod tests { let (mut sender, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); - assert_eq!( - Async::NotReady, - payload.read_until(b"ne").ok().unwrap() - ); + assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); diff --git a/src/pipeline.rs b/src/pipeline.rs index 82ec45a7..f5c338e6 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -29,9 +29,7 @@ pub(crate) trait PipelineHandler { fn encoding(&self) -> ContentEncoding; fn handle( - &mut self, - req: HttpRequest, - htype: HandlerType, + &mut self, req: HttpRequest, htype: HandlerType, ) -> AsyncResult; } @@ -122,10 +120,8 @@ impl PipelineInfo { impl> Pipeline { pub fn new( - req: HttpRequest, - mws: Rc>>>, - handler: Rc>, - htype: HandlerType, + req: HttpRequest, mws: Rc>>>, + handler: Rc>, htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { mws, @@ -243,9 +239,7 @@ struct StartMiddlewares { impl> StartMiddlewares { fn init( - info: &mut PipelineInfo, - hnd: Rc>, - htype: HandlerType, + info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType, ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately @@ -322,8 +316,7 @@ struct WaitingResponse { impl WaitingResponse { #[inline] fn init( - info: &mut PipelineInfo, - reply: AsyncResult, + info: &mut PipelineInfo, reply: AsyncResult, ) -> PipelineState { match reply.into() { AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), @@ -475,9 +468,7 @@ impl ProcessResponse { } fn poll_io( - mut self, - io: &mut Writer, - info: &mut PipelineInfo, + mut self, io: &mut Writer, info: &mut PipelineInfo, ) -> Result, PipelineState> { loop { if self.drain.is_none() && self.running != RunningState::Paused { diff --git a/src/pred.rs b/src/pred.rs index 34792e36..90a0d61f 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -181,11 +181,7 @@ pub fn Header( } #[doc(hidden)] -pub struct HeaderPredicate( - header::HeaderName, - header::HeaderValue, - PhantomData, -); +pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); impl Predicate for HeaderPredicate { fn check(&self, req: &mut HttpRequest) -> bool { diff --git a/src/resource.rs b/src/resource.rs index e52760f4..7b1a7502 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -134,10 +134,7 @@ impl ResourceHandler { /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); - self.routes - .last_mut() - .unwrap() - .filter(pred::Method(method)) + self.routes.last_mut().unwrap().filter(pred::Method(method)) } /// Register a new route and add handler object. diff --git a/src/route.rs b/src/route.rs index b109fd60..ff19db80 100644 --- a/src/route.rs +++ b/src/route.rs @@ -55,9 +55,7 @@ impl Route { #[inline] pub(crate) fn compose( - &mut self, - req: HttpRequest, - mws: Rc>>>, + &mut self, req: HttpRequest, mws: Rc>>>, ) -> AsyncResult { AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) } @@ -248,8 +246,7 @@ impl Route { /// } /// ``` pub fn with2( - &mut self, - handler: F, + &mut self, handler: F, ) -> (ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2) -> R + 'static, @@ -270,8 +267,7 @@ impl Route { #[doc(hidden)] /// Set handler function, use request extractor for all parameters. pub fn with3( - &mut self, - handler: F, + &mut self, handler: F, ) -> ( ExtractorConfig, ExtractorConfig, @@ -368,9 +364,7 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, - mws: Rc>>>, - handler: InnerHandler, + req: HttpRequest, mws: Rc>>>, handler: InnerHandler, ) -> Self { let mut info = ComposeInfo { count: 0, @@ -485,8 +479,7 @@ struct WaitingResponse { impl WaitingResponse { #[inline] fn init( - info: &mut ComposeInfo, - reply: AsyncResult, + info: &mut ComposeInfo, reply: AsyncResult, ) -> ComposeState { match reply.into() { AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), diff --git a/src/router.rs b/src/router.rs index 1e7126b6..44fde0a4 100644 --- a/src/router.rs +++ b/src/router.rs @@ -216,7 +216,8 @@ impl Resource { Ok(re) => re, Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), }; - let names = re.capture_names() + let names = re + .capture_names() .filter_map(|name| name.map(|name| name.to_owned())) .collect(); PatternType::Dynamic(re, names, len) @@ -440,10 +441,7 @@ mod tests { #[test] fn test_recognizer() { let routes = vec![ - ( - Resource::new("", "/name"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/name"), Some(ResourceHandler::default())), ( Resource::new("", "/name/{val}"), Some(ResourceHandler::default()), @@ -530,10 +528,7 @@ mod tests { #[test] fn test_recognizer_with_prefix() { let routes = vec![ - ( - Resource::new("", "/name"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/name"), Some(ResourceHandler::default())), ( Resource::new("", "/name/{val}"), Some(ResourceHandler::default()), @@ -554,10 +549,7 @@ mod tests { // same patterns let routes = vec![ - ( - Resource::new("", "/name"), - Some(ResourceHandler::default()), - ), + (Resource::new("", "/name"), Some(ResourceHandler::default())), ( Resource::new("", "/name/{val}"), Some(ResourceHandler::default()), diff --git a/src/scope.rs b/src/scope.rs index 00bcadad..7cf9c646 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -459,8 +459,7 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, - mws: Rc>>>, + req: HttpRequest, mws: Rc>>>, resource: Rc>>, default: Option>>>, ) -> Self { @@ -585,8 +584,7 @@ struct WaitingResponse { impl WaitingResponse { #[inline] fn init( - info: &mut ComposeInfo, - reply: AsyncResult, + info: &mut ComposeInfo, reply: AsyncResult, ) -> ComposeState { match reply.into() { AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), diff --git a/src/server/channel.rs b/src/server/channel.rs index 9c30fe01..34f6733c 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -38,9 +38,7 @@ where H: HttpHandler + 'static, { pub(crate) fn new( - settings: Rc>, - mut io: T, - peer: Option, + settings: Rc>, mut io: T, peer: Option, http2: bool, ) -> HttpChannel { settings.add_channel(); diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 07438b50..17209041 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -380,9 +380,7 @@ impl ContentEncoder { } pub fn for_server( - buf: SharedBytes, - req: &HttpInnerMessage, - resp: &mut HttpResponse, + buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse, response_encoding: ContentEncoding, ) -> ContentEncoder { let version = resp.version().unwrap_or_else(|| req.version); @@ -522,9 +520,7 @@ impl ContentEncoder { } fn streaming_encoding( - buf: SharedBytes, - version: Version, - resp: &mut HttpResponse, + buf: SharedBytes, version: Version, resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { @@ -867,7 +863,8 @@ impl AcceptEncoding { /// Parse a raw Accept-Encoding header value into an ordered list. pub fn parse(raw: &str) -> ContentEncoding { - let mut encodings: Vec<_> = raw.replace(' ', "") + let mut encodings: Vec<_> = raw + .replace(' ', "") .split(',') .map(|l| AcceptEncoding::new(l)) .collect(); diff --git a/src/server/h1.rs b/src/server/h1.rs index 46ec3473..491c667c 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -67,9 +67,7 @@ where H: HttpHandler + 'static, { pub fn new( - settings: Rc>, - stream: T, - addr: Option, + settings: Rc>, stream: T, addr: Option, buf: BytesMut, ) -> Self { let bytes = settings.get_shared_bytes(); @@ -765,7 +763,8 @@ mod tests { let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); let req = HttpRequest::from_message(msg.message()); - let val: Vec<_> = req.headers() + let val: Vec<_> = req + .headers() .get_all("Set-Cookie") .iter() .map(|v| v.to_str().unwrap().to_owned()) diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 0d83bfbd..976a079e 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -46,9 +46,7 @@ impl H1Decoder { } pub fn decode( - &mut self, - src: &mut BytesMut, - settings: &WorkerSettings, + &mut self, src: &mut BytesMut, settings: &WorkerSettings, ) -> Result, DecoderError> { // read payload if self.decoder.is_some() { @@ -62,7 +60,8 @@ impl H1Decoder { } } - match self.parse_message(src, settings) + match self + .parse_message(src, settings) .map_err(DecoderError::Error)? { Async::Ready((msg, decoder)) => { @@ -84,9 +83,7 @@ impl H1Decoder { } fn parse_message( - &self, - buf: &mut BytesMut, - settings: &WorkerSettings, + &self, buf: &mut BytesMut, settings: &WorkerSettings, ) -> Poll<(SharedHttpInnerMessage, Option), ParseError> { // Parse http message let mut has_upgrade = false; @@ -348,10 +345,7 @@ macro_rules! byte ( impl ChunkedState { fn step( - &self, - body: &mut BytesMut, - size: &mut u64, - buf: &mut Option, + &self, body: &mut BytesMut, size: &mut u64, buf: &mut Option, ) -> Poll { use self::ChunkedState::*; match *self { @@ -414,8 +408,7 @@ impl ChunkedState { } } fn read_size_lf( - rdr: &mut BytesMut, - size: &mut u64, + rdr: &mut BytesMut, size: &mut u64, ) -> Poll { match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), @@ -428,9 +421,7 @@ impl ChunkedState { } fn read_body( - rdr: &mut BytesMut, - rem: &mut u64, - buf: &mut Option, + rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option, ) -> Poll { trace!("Chunked read, remaining={:?}", rem); diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index ec5bfde1..5bb23dd9 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -42,9 +42,7 @@ pub(crate) struct H1Writer { impl H1Writer { pub fn new( - stream: T, - buf: SharedBytes, - settings: Rc>, + stream: T, buf: SharedBytes, settings: Rc>, ) -> H1Writer { H1Writer { flags: Flags::empty(), @@ -103,9 +101,7 @@ impl Writer for H1Writer { } fn start( - &mut self, - req: &mut HttpInnerMessage, - msg: &mut HttpResponse, + &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding, ) -> io::Result { // prepare task diff --git a/src/server/h2.rs b/src/server/h2.rs index fc7824b2..c730ac40 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -133,7 +133,8 @@ where Err(err) => { error!("Unhandled error: {}", err); item.flags.insert( - EntryFlags::EOF | EntryFlags::ERROR + EntryFlags::EOF + | EntryFlags::ERROR | EntryFlags::WRITE_DONE, ); item.stream.reset(Reason::INTERNAL_ERROR); @@ -150,7 +151,8 @@ where } Err(err) => { item.flags.insert( - EntryFlags::ERROR | EntryFlags::WRITE_DONE + EntryFlags::ERROR + | EntryFlags::WRITE_DONE | EntryFlags::FINISHED, ); error!("Unhandled error: {}", err); @@ -248,7 +250,8 @@ where if not_ready { if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) { - return conn.poll_close() + return conn + .poll_close() .map_err(|e| error!("Error during connection close: {}", e)); } else { return Ok(Async::NotReady); diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 575d4176..a20d7759 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -120,7 +120,8 @@ impl Writer for H2Writer { resp.headers_mut().insert(key, value.clone()); } - match self.respond + match self + .respond .send_response(resp, self.flags.contains(Flags::EOF)) { Ok(stream) => self.stream = Some(stream), diff --git a/src/server/helpers.rs b/src/server/helpers.rs index c579ec07..7f2f4734 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -251,63 +251,33 @@ mod tests { let mut bytes = BytesMut::new(); bytes.reserve(50); write_content_length(0, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 0\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); bytes.reserve(50); write_content_length(9, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 9\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); bytes.reserve(50); write_content_length(10, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 10\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); bytes.reserve(50); write_content_length(99, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 99\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); bytes.reserve(50); write_content_length(100, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 100\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); bytes.reserve(50); write_content_length(101, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 101\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); bytes.reserve(50); write_content_length(998, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 998\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); bytes.reserve(50); write_content_length(1000, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 1000\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); bytes.reserve(50); write_content_length(1001, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 1001\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); bytes.reserve(50); write_content_length(5909, &mut bytes); - assert_eq!( - bytes.take().freeze(), - b"\r\ncontent-length: 5909\r\n"[..] - ); + assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); } } diff --git a/src/server/settings.rs b/src/server/settings.rs index f75033c1..59917b87 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -266,10 +266,7 @@ mod tests { #[test] fn test_date_len() { - assert_eq!( - DATE_VALUE_LENGTH, - "Sun, 06 Nov 1994 08:49:37 GMT".len() - ); + assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); } #[test] diff --git a/src/server/srv.rs b/src/server/srv.rs index 30b7b4e4..22091e22 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -218,7 +218,10 @@ where /// and the user should be presented with an enumeration of which /// socket requires which protocol. pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.sockets.iter().map(|s| (s.addr, s.tp.scheme())).collect() + self.sockets + .iter() + .map(|s| (s.addr, s.tp.scheme())) + .collect() } /// Use listener for accepting incoming connection requests @@ -254,7 +257,9 @@ where /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_ssl(mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder) -> io::Result { + pub fn listen_ssl( + mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder, + ) -> io::Result { // alpn support if !self.no_http2 { configure_alpn(&mut builder)?; @@ -814,12 +819,9 @@ fn start_accept_thread( } // Start listening for incoming commands - if let Err(err) = poll.register( - ®, - CMD, - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { + if let Err(err) = + poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge()) + { panic!("Can not register Registration: {}", err); } diff --git a/src/server/worker.rs b/src/server/worker.rs index a926a6c8..012acd6e 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -85,9 +85,7 @@ impl Worker { fn update_time(&self, ctx: &mut Context) { self.settings.update_date(); - ctx.run_later(time::Duration::new(1, 0), |slf, ctx| { - slf.update_time(ctx) - }); + ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); } fn shutdown_timeout( @@ -195,18 +193,17 @@ impl StreamHandlerType { 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(())) - }), - ); + 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) => { @@ -215,27 +212,25 @@ impl StreamHandlerType { 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(())) - }), - ); + 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/src/with.rs b/src/with.rs index fa4f4dc4..ea549e31 100644 --- a/src/with.rs +++ b/src/with.rs @@ -794,15 +794,13 @@ where }; let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - let item = match (*hnd)( - self.item1.take().unwrap(), - self.item2.take().unwrap(), - item, - ).respond_to(&self.req) - { - Ok(item) => item.into(), - Err(err) => return Err(err.into()), - }; + let item = + match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item) + .respond_to(&self.req) + { + Ok(item) => item.into(), + Err(err) => return Err(err.into()), + }; match item.into() { AsyncResultItem::Err(err) => return Err(err), diff --git a/src/ws/client.rs b/src/ws/client.rs index 92087efa..1f35c186 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -22,8 +22,10 @@ use header::IntoHeaderValue; use httpmessage::HttpMessage; use payload::PayloadHelper; -use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, - HttpResponseParserError, SendRequest, SendRequestError}; +use client::{ + ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, + HttpResponseParserError, SendRequest, SendRequestError, +}; use super::frame::Frame; use super::proto::{CloseReason, OpCode}; @@ -218,8 +220,7 @@ impl Client { self.request.upgrade(); self.request.set_header(header::UPGRADE, "websocket"); self.request.set_header(header::CONNECTION, "upgrade"); - self.request - .set_header(header::SEC_WEBSOCKET_VERSION, "13"); + self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); self.request.with_connector(self.conn.clone()); if let Some(protocols) = self.protocols.take() { @@ -235,7 +236,9 @@ impl Client { return ClientHandshake::error(ClientError::InvalidUrl); } if let Some(scheme) = request.uri().scheme_part() { - if scheme != "http" && scheme != "https" && scheme != "ws" + if scheme != "http" + && scheme != "https" + && scheme != "ws" && scheme != "wss" { return ClientHandshake::error(ClientError::InvalidUrl); @@ -394,10 +397,7 @@ impl Future for ClientHandshake { encoded, key ); - return Err(ClientError::InvalidChallengeResponse( - encoded, - key.clone(), - )); + return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); } } else { trace!("Missing SEC-WEBSOCKET-ACCEPT header"); @@ -534,23 +534,13 @@ impl ClientWriter { /// Send ping frame #[inline] pub fn ping(&mut self, message: &str) { - self.write(Frame::message( - Vec::from(message), - OpCode::Ping, - true, - true, - )); + self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true)); } /// Send pong frame #[inline] pub fn pong(&mut self, message: &str) { - self.write(Frame::message( - Vec::from(message), - OpCode::Pong, - true, - true, - )); + self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true)); } /// Send close frame diff --git a/src/ws/context.rs b/src/ws/context.rs index 79c3aa35..226d93a1 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -5,8 +5,10 @@ use smallvec::SmallVec; use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; use actix::fut::ActorFuture; -use actix::{Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, - SpawnHandle, Syn, Unsync}; +use actix::{ + Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, + Syn, Unsync, +}; use body::{Binary, Body}; use context::{ActorHttpContext, Drain, Frame as ContextFrame}; @@ -64,7 +66,8 @@ where #[doc(hidden)] #[inline] fn waiting(&self) -> bool { - self.inner.waiting() || self.inner.state() == ActorState::Stopping + self.inner.waiting() + || self.inner.state() == ActorState::Stopping || self.inner.state() == ActorState::Stopped } diff --git a/src/ws/frame.rs b/src/ws/frame.rs index a5c02442..8eaef72d 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -123,9 +123,7 @@ impl Frame { None }; - Ok(Async::Ready(Some(( - idx, finished, opcode, length, mask, - )))) + Ok(Async::Ready(Some((idx, finished, opcode, length, mask)))) } fn read_chunk_md( @@ -284,10 +282,7 @@ impl Frame { } else { None }; - Some(CloseReason { - code, - description, - }) + Some(CloseReason { code, description }) } else { None } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 9fb40dd9..7f72dea1 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -62,7 +62,9 @@ mod frame; mod mask; mod proto; -pub use self::client::{Client, ClientError, ClientHandshake, ClientReader, ClientWriter}; +pub use self::client::{ + Client, ClientError, ClientHandshake, ClientReader, ClientWriter, +}; pub use self::context::WebsocketContext; pub use self::frame::Frame; pub use self::proto::{CloseCode, CloseReason, OpCode}; @@ -216,9 +218,7 @@ pub fn handshake( } // check supported version - if !req.headers() - .contains_key(header::SEC_WEBSOCKET_VERSION) - { + if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) { return Err(HandshakeError::NoVersionHeader); } let supported_ver = { @@ -387,10 +387,7 @@ mod tests { ); let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("test"), - ); + headers.insert(header::UPGRADE, header::HeaderValue::from_static("test")); let req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), diff --git a/tests/test_client.rs b/tests/test_client.rs index 09465684..5496e59f 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -73,10 +73,7 @@ fn test_with_query_parameter() { }) }); - let request = srv.get() - .uri(srv.url("/?qp=5").as_str()) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -125,7 +122,8 @@ fn test_client_gzip_encoding() { }); // client request - let request = srv.post() + let request = srv + .post() .content_encoding(http::ContentEncoding::Gzip) .body(STR) .unwrap(); @@ -154,7 +152,8 @@ fn test_client_gzip_encoding_large() { }); // client request - let request = srv.post() + let request = srv + .post() .content_encoding(http::ContentEncoding::Gzip) .body(data.clone()) .unwrap(); @@ -186,7 +185,8 @@ fn test_client_gzip_encoding_large_random() { }); // client request - let request = srv.post() + let request = srv + .post() .content_encoding(http::ContentEncoding::Gzip) .body(data.clone()) .unwrap(); @@ -214,7 +214,8 @@ fn test_client_brotli_encoding() { }); // client request - let request = srv.client(http::Method::POST, "/") + let request = srv + .client(http::Method::POST, "/") .content_encoding(http::ContentEncoding::Br) .body(STR) .unwrap(); @@ -247,7 +248,8 @@ fn test_client_brotli_encoding_large_random() { }); // client request - let request = srv.client(http::Method::POST, "/") + let request = srv + .client(http::Method::POST, "/") .content_encoding(http::ContentEncoding::Br) .body(data.clone()) .unwrap(); @@ -276,7 +278,8 @@ fn test_client_deflate_encoding() { }); // client request - let request = srv.post() + let request = srv + .post() .content_encoding(http::ContentEncoding::Deflate) .body(STR) .unwrap(); @@ -309,7 +312,8 @@ fn test_client_deflate_encoding_large_random() { }); // client request - let request = srv.post() + let request = srv + .post() .content_encoding(http::ContentEncoding::Deflate) .body(data.clone()) .unwrap(); @@ -339,9 +343,7 @@ fn test_client_streaming_explicit() { let body = once(Ok(Bytes::from_static(STR.as_ref()))); - let request = srv.get() - .body(Body::Streaming(Box::new(body))) - .unwrap(); + let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -414,7 +416,8 @@ fn test_client_cookie_handling() { }) }); - let request = srv.get() + let request = srv + .get() .cookie(cookie1.clone()) .cookie(cookie2.clone()) .finish() diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 42a9f3ac..11565fd3 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -34,10 +34,7 @@ fn test_path_extractor() { }); // client request - let request = srv.get() - .uri(srv.url("/test/index.html")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -55,7 +52,8 @@ fn test_query_extractor() { }); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/index.html?username=test")) .finish() .unwrap(); @@ -67,10 +65,7 @@ fn test_query_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); // client request - let request = srv.get() - .uri(srv.url("/index.html")) - .finish() - .unwrap(); + let request = srv.get().uri(srv.url("/index.html")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } @@ -89,7 +84,8 @@ fn test_async_extractor_async() { }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -113,7 +109,8 @@ fn test_path_and_query_extractor() { }); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test1/index.html?username=test2")) .finish() .unwrap(); @@ -125,7 +122,8 @@ fn test_path_and_query_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test1/index.html")) .finish() .unwrap(); @@ -145,7 +143,8 @@ fn test_path_and_query_extractor2() { }); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test1/index.html?username=test2")) .finish() .unwrap(); @@ -157,7 +156,8 @@ fn test_path_and_query_extractor2() { assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test1/index.html")) .finish() .unwrap(); @@ -169,21 +169,21 @@ fn test_path_and_query_extractor2() { fn test_path_and_query_extractor2_async() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with3( - |p: Path, _: Query, data: Json| { + r.route() + .with3(|p: Path, _: Query, data: Json| { Timeout::new(Duration::from_millis(10), &Arbiter::handle()) .unwrap() .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) .responder() - }, - ) + }) }); }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html?username=test2")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -193,10 +193,7 @@ fn test_path_and_query_extractor2_async() { // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"Welcome test1 - {\"test\":1}!") - ); + assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); } #[test] @@ -215,7 +212,8 @@ fn test_path_and_query_extractor3_async() { }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -240,7 +238,8 @@ fn test_path_and_query_extractor4_async() { }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -253,21 +252,21 @@ fn test_path_and_query_extractor4_async() { fn test_path_and_query_extractor2_async2() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with3( - |p: Path, data: Json, _: Query| { + r.route() + .with3(|p: Path, data: Json, _: Query| { Timeout::new(Duration::from_millis(10), &Arbiter::handle()) .unwrap() .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) .responder() - }, - ) + }) }); }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html?username=test2")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -277,13 +276,11 @@ fn test_path_and_query_extractor2_async2() { // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"Welcome test1 - {\"test\":1}!") - ); + assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test1/index.html")) .finish() .unwrap(); @@ -295,21 +292,21 @@ fn test_path_and_query_extractor2_async2() { fn test_path_and_query_extractor2_async3() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with3( - |data: Json, p: Path, _: Query| { + r.route() + .with3(|data: Json, p: Path, _: Query| { Timeout::new(Duration::from_millis(10), &Arbiter::handle()) .unwrap() .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) .responder() - }, - ) + }) }); }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html?username=test2")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -319,13 +316,11 @@ fn test_path_and_query_extractor2_async3() { // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"Welcome test1 - {\"test\":1}!") - ); + assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test1/index.html")) .finish() .unwrap(); @@ -342,11 +337,7 @@ fn test_path_and_query_extractor2_async4() { Timeout::new(Duration::from_millis(10), &Arbiter::handle()) .unwrap() .and_then(move |_| { - Ok(format!( - "Welcome {} - {}!", - data.1.username, - (data.0).0 - )) + Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)) }) .responder() }) @@ -354,7 +345,8 @@ fn test_path_and_query_extractor2_async4() { }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html?username=test2")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -364,13 +356,11 @@ fn test_path_and_query_extractor2_async4() { // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"Welcome test1 - {\"test\":1}!") - ); + assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test1/index.html")) .finish() .unwrap(); @@ -384,13 +374,7 @@ fn test_impl_trait( ) -> impl Future { Timeout::new(Duration::from_millis(10), &Arbiter::handle()) .unwrap() - .and_then(move |_| { - Ok(format!( - "Welcome {} - {}!", - data.1.username, - (data.0).0 - )) - }) + .and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))) } #[cfg(actix_impl_trait)] @@ -412,7 +396,8 @@ fn test_path_and_query_extractor2_async4_impl_trait() { }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html?username=test2")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -422,13 +407,11 @@ fn test_path_and_query_extractor2_async4_impl_trait() { // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"Welcome test1 - {\"test\":1}!") - ); + assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test1/index.html")) .finish() .unwrap(); @@ -446,7 +429,8 @@ fn test_path_and_query_extractor2_async4_impl_trait_err() { }); // client request - let request = srv.post() + let request = srv + .post() .uri(srv.url("/test1/index.html?username=test2")) .header("content-type", "application/json") .body("{\"test\": 1}") @@ -462,7 +446,8 @@ fn test_non_ascii_route() { }); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/中文/index.html")) .finish() .unwrap(); @@ -483,7 +468,8 @@ fn test_unsafe_path_route() { }); // client request - let request = srv.get() + let request = srv + .get() .uri(srv.url("/test/http%3A%2F%2Fexample.com")) .finish() .unwrap(); diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 2c9160b6..8435e746 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -27,9 +27,7 @@ impl middleware::Middleware for MiddlewareTest { } fn response( - &self, - _: &mut HttpRequest, - resp: HttpResponse, + &self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { self.response .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); @@ -450,9 +448,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { } fn response( - &self, - _: &mut HttpRequest, - resp: HttpResponse, + &self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap(); diff --git a/tests/test_server.rs b/tests/test_server.rs index 863f800a..7eb0bfaa 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -337,11 +337,7 @@ fn test_body_br_streaming() { #[test] fn test_head_empty() { let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_length(STR.len() as u64) - .finish() - }) + app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish()) }); let request = srv.head().finish().unwrap(); @@ -529,7 +525,8 @@ fn test_gzip_encoding() { e.write_all(STR.as_ref()).unwrap(); let enc = e.finish().unwrap(); - let request = srv.post() + let request = srv + .post() .header(http::header::CONTENT_ENCODING, "gzip") .body(enc.clone()) .unwrap(); @@ -561,7 +558,8 @@ fn test_gzip_encoding_large() { e.write_all(data.as_ref()).unwrap(); let enc = e.finish().unwrap(); - let request = srv.post() + let request = srv + .post() .header(http::header::CONTENT_ENCODING, "gzip") .body(enc.clone()) .unwrap(); @@ -597,7 +595,8 @@ fn test_reading_gzip_encoding_large_random() { e.write_all(data.as_ref()).unwrap(); let enc = e.finish().unwrap(); - let request = srv.post() + let request = srv + .post() .header(http::header::CONTENT_ENCODING, "gzip") .body(enc.clone()) .unwrap(); @@ -629,7 +628,8 @@ fn test_reading_deflate_encoding() { let enc = e.finish().unwrap(); // client request - let request = srv.post() + let request = srv + .post() .header(http::header::CONTENT_ENCODING, "deflate") .body(enc) .unwrap(); @@ -661,7 +661,8 @@ fn test_reading_deflate_encoding_large() { let enc = e.finish().unwrap(); // client request - let request = srv.post() + let request = srv + .post() .header(http::header::CONTENT_ENCODING, "deflate") .body(enc) .unwrap(); @@ -697,7 +698,8 @@ fn test_reading_deflate_encoding_large_random() { let enc = e.finish().unwrap(); // client request - let request = srv.post() + let request = srv + .post() .header(http::header::CONTENT_ENCODING, "deflate") .body(enc) .unwrap(); @@ -730,7 +732,8 @@ fn test_brotli_encoding() { let enc = e.finish().unwrap(); // client request - let request = srv.post() + let request = srv + .post() .header(http::header::CONTENT_ENCODING, "br") .body(enc) .unwrap(); @@ -763,7 +766,8 @@ fn test_brotli_encoding_large() { let enc = e.finish().unwrap(); // client request - let request = srv.post() + let request = srv + .post() .header(http::header::CONTENT_ENCODING, "br") .body(enc) .unwrap(); @@ -784,7 +788,8 @@ fn test_h2() { let handle = core.handle(); let tcp = TcpStream::connect(&addr, &handle); - let tcp = tcp.then(|res| h2client::handshake(res.unwrap())) + let tcp = tcp + .then(|res| h2client::handshake(res.unwrap())) .then(move |res| { let (mut client, h2) = res.unwrap(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 9dbc11b0..0d75bc3f 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -46,9 +46,7 @@ fn test_simple() { let (item, reader) = srv.execute(reader.into_future()).unwrap(); assert_eq!( item, - Some(ws::Message::Binary( - Bytes::from_static(b"text").into() - )) + Some(ws::Message::Binary(Bytes::from_static(b"text").into())) ); writer.ping("ping"); @@ -117,10 +115,7 @@ fn test_large_bin() { writer.binary(data.clone()); let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; - assert_eq!( - item, - Some(ws::Message::Binary(Binary::from(data.clone()))) - ); + assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone())))); } } @@ -231,19 +226,17 @@ fn test_ws_server_ssl() { .set_certificate_chain_file("tests/cert.pem") .unwrap(); - let mut srv = test::TestServer::build() - .ssl(builder.build()) - .start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); + let mut srv = test::TestServer::build().ssl(builder.build()).start(|app| { + app.handler(|req| { + ws::start( + req, + Ws2 { + count: 0, + bin: false, + }, + ) + }) + }); let (mut reader, _writer) = srv.ws().unwrap(); let data = Some(ws::Message::Text("0".repeat(65_536))); diff --git a/tools/wsload/src/wsclient.rs b/tools/wsload/src/wsclient.rs index 186d6317..f28156b8 100644 --- a/tools/wsload/src/wsclient.rs +++ b/tools/wsload/src/wsclient.rs @@ -82,43 +82,40 @@ fn main() { let perf = perf_counters.clone(); let addr = Arbiter::new(format!("test {}", t)); - addr.do_send(actix::msgs::Execute::new( - move || -> Result<(), ()> { - for _ in 0..concurrency { - let pl2 = pl.clone(); - let perf2 = perf.clone(); - let ws2 = ws.clone(); + addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> { + for _ in 0..concurrency { + let pl2 = pl.clone(); + let perf2 = perf.clone(); + let ws2 = ws.clone(); - Arbiter::handle().spawn( - ws::Client::new(&ws) - .write_buffer_capacity(0) - .connect() - .map_err(|e| { - println!("Error: {}", e); - //Arbiter::system().do_send(actix::msgs::SystemExit(0)); - () - }) - .map(move |(reader, writer)| { - let addr: Addr = - ChatClient::create(move |ctx| { - ChatClient::add_stream(reader, ctx); - ChatClient { - url: ws2, - conn: writer, - payload: pl2, - bin: bin, - ts: time::precise_time_ns(), - perf_counters: perf2, - sent: 0, - max_payload_size: max_payload_size, - } - }); - }), - ); - } - Ok(()) - }, - )); + Arbiter::handle().spawn( + ws::Client::new(&ws) + .write_buffer_capacity(0) + .connect() + .map_err(|e| { + println!("Error: {}", e); + //Arbiter::system().do_send(actix::msgs::SystemExit(0)); + () + }) + .map(move |(reader, writer)| { + let addr: Addr = ChatClient::create(move |ctx| { + ChatClient::add_stream(reader, ctx); + ChatClient { + url: ws2, + conn: writer, + payload: pl2, + bin: bin, + ts: time::precise_time_ns(), + perf_counters: perf2, + sent: 0, + max_payload_size: max_payload_size, + } + }); + }), + ); + } + Ok(()) + })); } let res = sys.run(); @@ -126,10 +123,7 @@ fn main() { fn parse_u64_default(input: Option<&str>, default: u64) -> u64 { input - .map(|v| { - v.parse() - .expect(&format!("not a valid number: {}", v)) - }) + .map(|v| v.parse().expect(&format!("not a valid number: {}", v))) .unwrap_or(default) } @@ -314,7 +308,8 @@ impl PerfCounters { loop { let current = self.lat_max.load(Ordering::SeqCst); if current >= nanos - || self.lat_max + || self + .lat_max .compare_and_swap(current, nanos, Ordering::SeqCst) == current { From 16906c595120f9524107fd777ee099d2dd68e886 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 17 May 2018 12:23:37 -0700 Subject: [PATCH 0243/1635] clippy warnings --- src/fs.rs | 2 +- src/httprequest.rs | 4 ++-- src/server/encoding.rs | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 2de35994..4234ff36 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -332,8 +332,8 @@ impl Responder for NamedFile { Ok(resp.finish()) } else { let reader = ChunkedReadFile { + offset, size: length, - offset: offset, cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, diff --git a/src/httprequest.rs b/src/httprequest.rs index a21c9229..0a14ca04 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -367,7 +367,7 @@ impl HttpRequest { /// Get a reference to the Params object. /// Params is a container for url query parameters. pub fn query<'a>(&'a self) -> &'a Params { - if let None = self.extensions().get::() { + if self.extensions().get::().is_none() { let mut params: Params<'a> = Params::new(); for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { params.add(key, val); @@ -394,7 +394,7 @@ impl HttpRequest { /// Load request cookies. pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { - if let None = self.extensions().get::() { + if self.extensions().get::().is_none() { let msg = self.as_mut(); let mut cookies = Vec::new(); for hdr in msg.headers.get_all(header::COOKIE) { diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 17209041..58747565 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -386,6 +386,8 @@ impl ContentEncoder { let version = resp.version().unwrap_or_else(|| req.version); let is_head = req.method == Method::HEAD; let mut len = 0; + + #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] let has_body = match resp.body() { &Body::Empty => false, &Body::Binary(ref bin) => { @@ -424,6 +426,7 @@ impl ContentEncoder { ContentEncoding::Identity }; + #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] let mut transfer = match resp.body() { &Body::Empty => { if req.method != Method::HEAD { From 537b420d3572b4858d48da0d855f718264f4a378 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 17 May 2018 18:33:48 -0700 Subject: [PATCH 0244/1635] Fix compilation with --no-default-features --- CHANGES.md | 5 +++ src/server/encoding.rs | 71 ++++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 650423f7..f371eb28 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.6.7 (2018-05-17) + +* Fix compilation with --no-default-features + + ## 0.6.6 (2018-05-17) * Panic during middleware execution #226 diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 58747565..ea47bf71 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -33,6 +33,7 @@ pub(crate) enum PayloadType { } impl PayloadType { + #[cfg(any(feature = "brotli", feature = "flate2"))] pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { // check content-encoding let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { @@ -52,6 +53,11 @@ impl PayloadType { _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), } } + + #[cfg(not(any(feature = "brotli", feature = "flate2")))] + pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { + PayloadType::Sender(sender) + } } impl PayloadWriter for PayloadType { @@ -399,6 +405,7 @@ impl ContentEncoder { // Enable content encoding only if response does not contain Content-Encoding // header + #[cfg(any(feature = "brotli", feature = "flate2"))] let mut encoding = if has_body { let encoding = match response_encoding { ContentEncoding::Auto => { @@ -425,6 +432,8 @@ impl ContentEncoder { } else { ContentEncoding::Identity }; + #[cfg(not(any(feature = "brotli", feature = "flate2")))] + let mut encoding = ContentEncoding::Identity; #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] let mut transfer = match resp.body() { @@ -435,40 +444,42 @@ impl ContentEncoder { TransferEncoding::length(0, buf) } &Body::Binary(_) => { - if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) + #[cfg(any(feature = "brotli", feature = "flate2"))] { - let tmp = SharedBytes::default(); - let transfer = TransferEncoding::eof(tmp.clone()); - let mut enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::fast()), - ), - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( - transfer, - Compression::fast(), - )), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) - } - ContentEncoding::Identity | ContentEncoding::Auto => { - unreachable!() - } - }; + if !(encoding == ContentEncoding::Identity + || encoding == ContentEncoding::Auto) + { + let tmp = SharedBytes::default(); + let transfer = TransferEncoding::eof(tmp.clone()); + let mut enc = match encoding { + #[cfg(feature = "flate2")] + ContentEncoding::Deflate => ContentEncoder::Deflate( + DeflateEncoder::new(transfer, Compression::fast()), + ), + #[cfg(feature = "flate2")] + ContentEncoding::Gzip => ContentEncoder::Gzip( + GzEncoder::new(transfer, Compression::fast()), + ), + #[cfg(feature = "brotli")] + ContentEncoding::Br => { + ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) + } + ContentEncoding::Identity | ContentEncoding::Auto => { + unreachable!() + } + }; - let bin = resp.replace_body(Body::Empty).binary(); + let bin = resp.replace_body(Body::Empty).binary(); - // TODO return error! - let _ = enc.write(bin); - let _ = enc.write_eof(); - let body = tmp.take(); - len = body.len(); + // TODO return error! + let _ = enc.write(bin); + let _ = enc.write_eof(); + let body = tmp.take(); + len = body.len(); - encoding = ContentEncoding::Identity; - resp.replace_body(Binary::from(body)); + encoding = ContentEncoding::Identity; + resp.replace_body(Binary::from(body)); + } } if is_head { From 9b7ea836d0a0d850b03ad693c25578697c922076 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 17 May 2018 18:34:09 -0700 Subject: [PATCH 0245/1635] bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2ba3cc0a..70a9ec44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.6" +version = "0.6.7" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 0126ac46fc234f3c62e96eeb58fcb50be9db51ee Mon Sep 17 00:00:00 2001 From: Sindre Johansen Date: Sun, 20 May 2018 14:43:26 +0200 Subject: [PATCH 0246/1635] Fix some typos in server/srv.rs Hello! This looks like a great library, thanks for creating it! While reading through the documentation I found a few typos. --- src/server/srv.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/srv.rs b/src/server/srv.rs index 22091e22..7ed533bf 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -309,7 +309,7 @@ where /// The socket address to bind /// - /// To mind multiple addresses this method can be call multiple times. + /// To bind multiple addresses this method can be called multiple times. pub fn bind(mut self, addr: S) -> io::Result { let sockets = self.bind2(addr)?; self.sockets.extend(sockets); @@ -319,7 +319,7 @@ where #[cfg(feature = "tls")] /// The ssl socket address to bind /// - /// To mind multiple addresses this method can be call multiple times. + /// To bind multiple addresses this method can be called multiple times. pub fn bind_tls( mut self, addr: S, acceptor: TlsAcceptor, ) -> io::Result { From b68687044e710ac18834b6074f2c4dbe58a4aa04 Mon Sep 17 00:00:00 2001 From: qrvaelet Date: Sat, 19 May 2018 23:28:48 +0200 Subject: [PATCH 0247/1635] range header syntax fix, change range to content-range in responses, enabled accept ranges, tests for content-range, content-length, and range status code --- src/fs.rs | 116 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 99 insertions(+), 17 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 4234ff36..c01f97b9 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -289,30 +289,28 @@ impl Responder for NamedFile { resp.set(header::ETag(etag)); }); - // TODO: Debug, enabling "accept-ranges: bytes" causes problems with - // certain clients when not using the ranges header. - //resp.header(header::ACCEPT_RANGES, format!("bytes")); + resp.header(header::ACCEPT_RANGES, "bytes"); let mut length = self.md.len(); let mut offset = 0; - // check for ranges header + // check for range header if let Some(ranges) = req.headers().get(header::RANGE) { if let Ok(rangesheader) = ranges.to_str() { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { - length = rangesvec[0].length - 1; + length = rangesvec[0].length; offset = rangesvec[0].start; resp.header( - header::RANGE, + header::CONTENT_RANGE, format!( - "bytes={}-{}/{}", - offset, - offset + length, + "bytes {}-{}/{}", + offset, + offset + length - 1, self.md.len() - ), + ) ); } else { - resp.header(header::RANGE, format!("*/{}", length)); + resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); }; } else { @@ -778,6 +776,7 @@ mod tests { App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) }); + // Valid range header let request = srv .get() .uri(srv.url("/t%65st/Cargo.toml")) @@ -787,10 +786,21 @@ mod tests { let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); + + // Invalid range header + let request = srv + .get() + .uri(srv.url("/t%65st/Cargo.toml")) + .header(header::RANGE, "bytes=1-0") + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); } #[test] - fn test_named_file_ranges_headers() { + fn test_named_file_content_range_headers() { let mut srv = test::TestServer::with_factory(|| { App::new().handler( "test", @@ -798,13 +808,64 @@ mod tests { ) }); + // Valid range header let request = srv .get() .uri(srv.url("/t%65st/tests/test.binary")) .header(header::RANGE, "bytes=10-20") .finish() .unwrap(); + let response = srv.execute(request.send()).unwrap(); + + let contentrange = response + .headers() + .get(header::CONTENT_RANGE) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentrange, "bytes 10-20/100"); + + // Invalid range header + let request = srv + .get() + .uri(srv.url("/t%65st/tests/test.binary")) + .header(header::RANGE, "bytes=10-5") + .finish() + .unwrap(); + + let response = srv.execute(request.send()).unwrap(); + + let contentrange = response + .headers() + .get(header::CONTENT_RANGE) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentrange, "bytes */100"); + } + + #[test] + fn test_named_file_content_length_headers() { + let mut srv = test::TestServer::with_factory(|| { + App::new().handler( + "test", + StaticFiles::new(".").index_file("tests/test.binary"), + ) + }); + + // Valid range header + let request = srv + .get() + .uri(srv.url("/t%65st/tests/test.binary")) + .header(header::RANGE, "bytes=10-20") + .finish() + .unwrap(); + + let response = srv.execute(request.send()).unwrap(); + let contentlength = response .headers() .get(header::CONTENT_LENGTH) @@ -812,23 +873,44 @@ mod tests { .to_str() .unwrap(); - assert_eq!(contentlength, "10"); + assert_eq!(contentlength, "11"); + // Invalid range header let request = srv .get() .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") + .header(header::RANGE, "bytes=10-8") .finish() .unwrap(); + let response = srv.execute(request.send()).unwrap(); - let range = response + + let contentlength = response .headers() - .get(header::RANGE) + .get(header::CONTENT_LENGTH) .unwrap() .to_str() .unwrap(); - assert_eq!(range, "bytes=10-20/100"); + assert_eq!(contentlength, "0"); + + // Without range header + let request = srv + .get() + .uri(srv.url("/t%65st/tests/test.binary")) + .finish() + .unwrap(); + + let response = srv.execute(request.send()).unwrap(); + + let contentlength = response + .headers() + .get(header::CONTENT_LENGTH) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentlength, "100"); } #[test] From 082ff460416380fe05814119848e3e0dd3d2e55b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 20 May 2018 17:04:08 -0700 Subject: [PATCH 0248/1635] Fix scope resource path extractor #234 --- CHANGES.md | 3 ++ src/param.rs | 10 ++++++ src/scope.rs | 1 + tests/test_handlers.rs | 74 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f371eb28..0e663644 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Changes +* Fix scope resource path extractor #234 + + ## 0.6.7 (2018-05-17) * Fix compilation with --no-default-features diff --git a/src/param.rs b/src/param.rs index 386c0c33..119ad59c 100644 --- a/src/param.rs +++ b/src/param.rs @@ -58,6 +58,16 @@ impl<'a> Params<'a> { self.0.push((name, value)); } + pub(crate) fn remove(&mut self, name: &str) + { + for idx in (0..self.0.len()).rev() { + if self.0[idx].0 == name { + self.0.remove(idx); + return + } + } + } + /// Check if there are any matched patterns pub fn is_empty(&self) -> bool { self.0.is_empty() diff --git a/src/scope.rs b/src/scope.rs index 7cf9c646..edbf81df 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -336,6 +336,7 @@ impl RouteHandler for Scope { if pattern.match_with_params(path, req.match_info_mut()) { let default = unsafe { &mut *self.default.as_ref().get() }; + req.match_info_mut().remove("tail"); if self.middlewares.is_empty() { let resource = unsafe { &mut *resource.get() }; return resource.handle(req, Some(default)); diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 11565fd3..cc6524d4 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -368,6 +368,80 @@ fn test_path_and_query_extractor2_async4() { assert_eq!(response.status(), StatusCode::BAD_REQUEST); } +#[test] +fn test_scope_and_path_extractor() { + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/sc", |scope| { + scope.resource("/{num}/index.html", |r| { + r.route() + .with(|p: Path<(usize,)>| { + format!("Welcome {}!", p.0) + }) + }) + }) + }); + + // client request + let request = srv + .get() + .uri(srv.url("/sc/10/index.html")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"Welcome 10!")); + + // client request + let request = srv + .get() + .uri(srv.url("/sc/test1/index.html")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); +} + +#[test] +fn test_nested_scope_and_path_extractor() { + let mut srv = test::TestServer::with_factory(move || { + App::new().scope("/sc", |scope| { + scope.nested("/{num}", |scope| { + scope.resource("/{num}/index.html", |r| { + r.route() + .with(|p: Path<(usize, usize)>| { + format!("Welcome {} {}!", p.0, p.1) + }) + }) + }) + }) + }); + + // client request + let request = srv + .get() + .uri(srv.url("/sc/10/12/index.html")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!")); + + // client request + let request = srv + .get() + .uri(srv.url("/sc/10/test1/index.html")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); +} + #[cfg(actix_impl_trait)] fn test_impl_trait( data: (Json, Path, Query), From 483db7028cb5e23d38318366446c55af7a6367a4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 20 May 2018 20:37:19 -0700 Subject: [PATCH 0249/1635] expose low level data --- src/server/h1writer.rs | 20 +++++++++++++++----- src/server/h2writer.rs | 10 ++++++++++ src/server/helpers.rs | 2 +- src/server/mod.rs | 20 +++++++++++++++++--- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 5bb23dd9..6f10fc71 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -1,6 +1,6 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] -use bytes::BufMut; +use bytes::{BytesMut, BufMut}; use futures::{Async, Poll}; use std::io; use std::rc::Rc; @@ -45,7 +45,7 @@ impl H1Writer { stream: T, buf: SharedBytes, settings: Rc>, ) -> H1Writer { H1Writer { - flags: Flags::empty(), + flags: Flags::KEEPALIVE, encoder: ContentEncoder::empty(buf.clone()), written: 0, headers_size: 0, @@ -62,7 +62,7 @@ impl H1Writer { pub fn reset(&mut self) { self.written = 0; - self.flags = Flags::empty(); + self.flags = Flags::KEEPALIVE; } pub fn disconnected(&mut self) { @@ -100,6 +100,16 @@ impl Writer for H1Writer { self.written } + #[inline] + fn set_date(&self, dst: &mut BytesMut) { + self.settings.set_date(dst) + } + + #[inline] + fn buffer(&self) -> &mut BytesMut { + self.buffer.get_mut() + } + fn start( &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding, @@ -108,9 +118,9 @@ impl Writer for H1Writer { self.encoder = ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { - self.flags.insert(Flags::STARTED | Flags::KEEPALIVE); + self.flags = Flags::STARTED | Flags::KEEPALIVE; } else { - self.flags.insert(Flags::STARTED); + self.flags = Flags::STARTED; } // Connection upgrade diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index a20d7759..5fc13154 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -71,6 +71,16 @@ impl Writer for H2Writer { self.written } + #[inline] + fn set_date(&self, dst: &mut BytesMut) { + self.settings.set_date(dst) + } + + #[inline] + fn buffer(&self) -> &mut BytesMut { + self.buffer.get_mut() + } + fn start( &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding, diff --git a/src/server/helpers.rs b/src/server/helpers.rs index 7f2f4734..f447a6ca 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -143,7 +143,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM } /// NOTE: bytes object has to contain enough space -pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { +pub 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', diff --git a/src/server/mod.rs b/src/server/mod.rs index 36d80e2d..51ec3216 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -3,7 +3,8 @@ use std::net::Shutdown; use std::{io, time}; use actix; -use futures::Poll; +use bytes::BytesMut; +use futures::{Async, Poll}; use tokio_core::net::TcpStream; use tokio_io::{AsyncRead, AsyncWrite}; @@ -24,6 +25,9 @@ mod worker; pub use self::settings::ServerSettings; pub use self::srv::HttpServer; +#[doc(hidden)] +pub use self::helpers::write_content_length; + use body::Binary; use error::Error; use header::ContentEncoding; @@ -132,13 +136,15 @@ impl HttpHandler for Box { #[doc(hidden)] pub trait HttpHandlerTask { /// Poll task, this method is used before or after *io* object is available - fn poll(&mut self) -> Poll<(), Error>; + fn poll(&mut self) -> Poll<(), Error>{ + Ok(Async::Ready(())) + } /// Poll task when *io* object is available fn poll_io(&mut self, io: &mut Writer) -> Poll; /// Connection is disconnected - fn disconnected(&mut self); + fn disconnected(&mut self) {} } /// Conversion helper trait @@ -168,8 +174,16 @@ pub enum WriterState { #[doc(hidden)] /// Stream writer pub trait Writer { + /// number of bytes written to the stream fn written(&self) -> u64; + #[doc(hidden)] + fn set_date(&self, st: &mut BytesMut); + + #[doc(hidden)] + #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] + fn buffer(&self) -> &mut BytesMut; + fn start( &mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, encoding: ContentEncoding, From 285c73e95ea4a011673bcd4f84a26d2aee84e592 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 20 May 2018 20:47:20 -0700 Subject: [PATCH 0250/1635] Re-use tcp listener on pause/resume --- CHANGES.md | 2 ++ src/server/srv.rs | 16 +++------------- src/server/worker.rs | 4 ++-- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0e663644..7a751b64 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ * Fix scope resource path extractor #234 +* Re-use tcp listener on pause/resume + ## 0.6.7 (2018-05-17) diff --git a/src/server/srv.rs b/src/server/srv.rs index 7ed533bf..18663103 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -450,7 +450,6 @@ impl HttpServer { self.accept.push(start_accept_thread( token, sock, - self.backlog, tx.clone(), socks.clone(), workers.clone(), @@ -782,7 +781,7 @@ enum Command { } fn start_accept_thread( - token: usize, sock: Socket, backlog: i32, srv: mpsc::UnboundedSender, + token: usize, sock: Socket, srv: mpsc::UnboundedSender, socks: Slab, mut workers: Vec<(usize, mpsc::UnboundedSender>)>, ) -> (mio::SetReadiness, sync_mpsc::Sender) { @@ -892,8 +891,8 @@ fn start_accept_thread( }, CMD => match rx.try_recv() { Ok(cmd) => match cmd { - Command::Pause => if let Some(server) = server.take() { - if let Err(err) = poll.deregister(&server) { + Command::Pause => if let Some(ref server) = server { + if let Err(err) = poll.deregister(server) { error!( "Can not deregister server socket {}", err @@ -906,15 +905,6 @@ fn start_accept_thread( } }, Command::Resume => { - let lst = create_tcp_listener(addr, backlog) - .expect("Can not create net::TcpListener"); - - server = Some( - mio::net::TcpListener::from_std(lst).expect( - "Can not create mio::net::TcpListener", - ), - ); - if let Some(ref server) = server { if let Err(err) = poll.register( server, diff --git a/src/server/worker.rs b/src/server/worker.rs index 012acd6e..f045074d 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -239,9 +239,9 @@ impl StreamHandlerType { match *self { StreamHandlerType::Normal => "http", #[cfg(feature = "tls")] - StreamHandlerType::Tls(ref acceptor) => "https", + StreamHandlerType::Tls(_) => "https", #[cfg(feature = "alpn")] - StreamHandlerType::Alpn(ref acceptor) => "https", + StreamHandlerType::Alpn(_) => "https", } } } From 14d1b8e2b60b3035a44970e400b4867520485230 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 20 May 2018 21:09:54 -0700 Subject: [PATCH 0251/1635] prepare release --- CHANGES.md | 2 ++ Cargo.toml | 2 +- tests/test_server.rs | 8 +++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7a751b64..13d31397 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## 0.6.8 (2018-05-20) + * Fix scope resource path extractor #234 * Re-use tcp listener on pause/resume diff --git a/Cargo.toml b/Cargo.toml index 70a9ec44..9352abed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.7" +version = "0.6.8" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/tests/test_server.rs b/tests/test_server.rs index 7eb0bfaa..b0fd7d4f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -86,7 +86,13 @@ fn test_start() { // pause let _ = srv_addr.send(server::PauseServer).wait(); thread::sleep(time::Duration::from_millis(200)); - assert!(net::TcpStream::connect(addr).is_err()); + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .timeout(time::Duration::from_millis(200)) + .finish() + .unwrap(); + assert!(sys.run_until_complete(req.send()).is_err()); + } // resume let _ = srv_addr.send(server::ResumeServer).wait(); From a9728abfc8094b42a45edad4a2ac0dacf945a2f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 20 May 2018 21:10:50 -0700 Subject: [PATCH 0252/1635] run coverage report on 1.24 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39f74d87..f930f508 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,12 +31,12 @@ before_script: script: - | - if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then + if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then cargo clean cargo test --features="alpn,tls" -- --nocapture fi - | - if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "1.24.0" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count bash <(curl -s https://codecov.io/bash) From 577a5098754caa03cd52e746e5acfd647d1a404c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 21 May 2018 16:12:33 -0700 Subject: [PATCH 0253/1635] increase delay --- tests/test_server.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index b0fd7d4f..0d210572 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -96,8 +96,7 @@ fn test_start() { // resume let _ = srv_addr.send(server::ResumeServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - + thread::sleep(time::Duration::from_millis(400)); { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) .finish() From 90968d4333392f56b2b57427ddb39dde07e812b4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 21 May 2018 18:54:17 -0700 Subject: [PATCH 0254/1635] Drop connection if request's payload is not fulle consumed #236 --- CHANGES.md | 3 +++ src/server/h1.rs | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 13d31397..674a9954 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Changes +* Drop connection if request's payload is not fulle consumed #236 + + ## 0.6.8 (2018-05-20) * Fix scope resource path extractor #234 diff --git a/src/server/h1.rs b/src/server/h1.rs index 491c667c..d7edd2dc 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -270,7 +270,12 @@ where debug!("Error sending data: {}", err); return Err(()); } - _ => (), + Ok(Async::Ready(_)) => { + // non consumed payload in that case close connection + if self.payload.is_some() && self.tasks.is_empty() { + return Ok(Async::Ready(false)) + } + } } } From 76d790425fef4b7a5c444d767789cfc4e1886ae8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 21 May 2018 19:07:56 -0700 Subject: [PATCH 0255/1635] bump version --- CHANGES.md | 2 ++ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 674a9954..372f4350 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## 0.6.9 (2018-05-22) + * Drop connection if request's payload is not fulle consumed #236 diff --git a/Cargo.toml b/Cargo.toml index 9352abed..2d721da2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.8" +version = "0.6.9" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 2159158c30b459ce27195d58c0b28600b51721a5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 21 May 2018 20:50:10 -0700 Subject: [PATCH 0256/1635] Fix streaming response with body compression --- CHANGES.md | 4 +++- src/fs.rs | 21 ++++++++++++++++++++- src/server/encoding.rs | 5 +++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 372f4350..afa86b7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,9 @@ ## 0.6.9 (2018-05-22) -* Drop connection if request's payload is not fulle consumed #236 +* Drop connection if request's payload is not fully consumed #236 + +* Fix streaming response with body compression ## 0.6.8 (2018-05-20) diff --git a/src/fs.rs b/src/fs.rs index c01f97b9..779b419a 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -20,7 +20,7 @@ use mime_guess::{get_mime_type, guess_mime_type}; use error::Error; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use header; -use http::{HttpRange, Method, StatusCode}; +use http::{HttpRange, Method, StatusCode, ContentEncoding}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -300,6 +300,7 @@ impl Responder for NamedFile { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { length = rangesvec[0].length; offset = rangesvec[0].start; + resp.content_encoding(ContentEncoding::Identity); resp.header( header::CONTENT_RANGE, format!( @@ -898,6 +899,7 @@ mod tests { let request = srv .get() .uri(srv.url("/t%65st/tests/test.binary")) + .no_default_headers() .finish() .unwrap(); @@ -911,6 +913,23 @@ mod tests { .unwrap(); assert_eq!(contentlength, "100"); + + // chunked + let request = srv + .get() + .uri(srv.url("/t%65st/tests/test.binary")) + .finish() + .unwrap(); + + let response = srv.execute(request.send()).unwrap(); + + let te = response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(); + assert_eq!(te, "chunked"); } #[test] diff --git a/src/server/encoding.rs b/src/server/encoding.rs index ea47bf71..34c589fc 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -505,6 +505,11 @@ impl ContentEncoder { } TransferEncoding::eof(buf) } else { + if !(encoding == ContentEncoding::Identity + || encoding == ContentEncoding::Auto) + { + resp.headers_mut().remove(CONTENT_LENGTH); + } ContentEncoder::streaming_encoding(buf, version, resp) } } From db0091ba6fce1ba257d0a47c216de2bfbe5986f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 21 May 2018 21:01:52 -0700 Subject: [PATCH 0257/1635] disable server test for windows --- tests/test_server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_server.rs b/tests/test_server.rs index 0d210572..1fcbfd5e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -54,6 +54,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[test] +#[cfg(unix)] fn test_start() { let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); From ac2470351270371453fa568eb75ce43b61abe7aa Mon Sep 17 00:00:00 2001 From: Max Frai Date: Wed, 23 May 2018 09:12:23 +0300 Subject: [PATCH 0258/1635] Add ability to set encoding for exact NamedFile. --- src/fs.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/fs.rs b/src/fs.rs index 779b419a..02e0e577 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -38,6 +38,7 @@ pub struct NamedFile { md: Metadata, modified: Option, cpu_pool: Option, + encoding: Option, only_get: bool, status_code: StatusCode, } @@ -58,12 +59,14 @@ impl NamedFile { let path = path.as_ref().to_path_buf(); let modified = md.modified().ok(); let cpu_pool = None; + let encoding = None; Ok(NamedFile { path, file, md, modified, cpu_pool, + encoding, only_get: false, status_code: StatusCode::OK, }) @@ -114,6 +117,19 @@ impl NamedFile { self } + // Set content encoding for serving this file + #[inline] + pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { + self.encoding = Some(enc); + self + } + + // Get content encoding used for serving this file + #[inline] + pub fn content_encoding(&self) -> Option { + self.encoding + } + fn etag(&self) -> Option { // This etag format is similar to Apache's. self.modified.as_ref().map(|mtime| { @@ -219,6 +235,9 @@ impl Responder for NamedFile { ), ); }); + if let Some(current_encoding) = self.encoding { + resp.content_encoding(current_encoding); + } let reader = ChunkedReadFile { size: self.md.len(), offset: 0, @@ -264,6 +283,9 @@ impl Responder for NamedFile { }; let mut resp = HttpResponse::build(self.status_code); + if let Some(current_encoding) = self.encoding { + resp.content_encoding(current_encoding); + } resp.if_some(self.path().extension(), |ext, resp| { resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); @@ -941,6 +963,20 @@ mod tests { assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } + #[test] + fn test_named_file_content_encoding() { + let req = TestRequest::default().method(Method::GET).finish(); + let file = NamedFile::open("Cargo.toml").unwrap(); + + assert!(file.content_encoding().is_none()); + let resp = file.set_content_encoding(ContentEncoding::Identity) + .respond_to(&req) + .unwrap(); + + assert!(resp.content_encoding().is_some()); + assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); + } + #[test] fn test_named_file_any_method() { let req = TestRequest::default().method(Method::POST).finish(); From 2479b14aba31f535ff014db0370f0be51c82d28a Mon Sep 17 00:00:00 2001 From: Aleksey Ivanov Date: Wed, 23 May 2018 19:07:42 +0300 Subject: [PATCH 0259/1635] Fix TestServer::post --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 135135f7..5e99652b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -217,7 +217,7 @@ impl TestServer { /// Create `POST` request pub fn post(&self) -> ClientRequestBuilder { - ClientRequest::get(self.url("/").as_str()) + ClientRequest::post(self.url("/").as_str()) } /// Create `HEAD` request From eb5dbd43aee2cbb161e2e5f65e4a811f6d796254 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 23 May 2018 10:37:17 -0700 Subject: [PATCH 0260/1635] update changelog --- CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index afa86b7a..4bcc5a72 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.6.10] - 2018-05-xx + +### Fixed + +* `TestServer::post()` actually sends `GET` request #240 + + ## 0.6.9 (2018-05-22) * Drop connection if request's payload is not fully consumed #236 From 72757887c9ca53f342955955b5db6fead453896e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 23 May 2018 11:20:12 -0700 Subject: [PATCH 0261/1635] update doc links --- README.md | 14 +++++++------- src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 51a3ae35..ae0bc645 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. -* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols +* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols * Streaming and pipelining * Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support +* Client/server [WebSockets](https://actix.rs/docs/websockets/) support * Transparent content compression/decompression (br, gzip, deflate) -* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html) +* Configurable [request routing](https://actix.rs/docs/url-dispatch/) * Graceful server shutdown * Multipart streams * Static assets @@ -18,12 +18,12 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. [DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers), [CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html), [CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html)) -* Includes an asynchronous [HTTP client](https://github.com/actix/actix-web/blob/master/src/client/mod.rs) +* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Built on top of [Actix actor framework](https://github.com/actix/actix) ## Documentation & community resources -* [User Guide](https://actix.rs/book/actix-web/) +* [User Guide](https://actix.rs/docs/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) @@ -34,9 +34,9 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. ```rust extern crate actix_web; -use actix_web::{http, server, App, Path}; +use actix_web::{http, server, App, Path, Responder}; -fn index(info: Path<(u32, String)>) -> String { +fn index(info: Path<(u32, String)>) -> impl Responder { format!("Hello {}! id:{}", info.1, info.0) } diff --git a/src/lib.rs b/src/lib.rs index 8ef1e2df..9912d4ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ //! Besides the API documentation (which you are currently looking //! at!), several other resources are available: //! -//! * [User Guide](https://actix.rs/book/actix-web/) +//! * [User Guide](https://actix.rs/docs/) //! * [Chat on gitter](https://gitter.im/actix/actix) //! * [GitHub repository](https://github.com/actix/actix-web) //! * [Cargo package](https://crates.io/crates/actix-web) From 68eb2f26c9d0b3f4c07344697adb5889a1fdd334 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 23 May 2018 13:21:29 -0700 Subject: [PATCH 0262/1635] Allow to use path without traling slashes for scope registration #241 --- CHANGES.md | 4 ++ src/application.rs | 16 +------ src/fs.rs | 10 ++-- src/param.rs | 5 +- src/router.rs | 10 +++- src/scope.rs | 102 +++++++++++++++++++++++++++++++---------- src/server/encoding.rs | 2 +- src/server/h1.rs | 2 +- src/server/h1writer.rs | 2 +- src/server/mod.rs | 2 +- tests/test_handlers.rs | 11 ++--- 11 files changed, 107 insertions(+), 59 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4bcc5a72..0bf97683 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## [0.6.10] - 2018-05-xx +### Added + +* Allow to use path without traling slashes for scope registration #241 + ### Fixed * `TestServer::post()` actually sends `GET` request #240 diff --git a/src/application.rs b/src/application.rs index 481430aa..94fb70fb 100644 --- a/src/application.rs +++ b/src/application.rs @@ -107,16 +107,12 @@ impl HttpApplication { } } - let prefix_len = inner.prefix + prefix_len - 1; + let prefix_len = inner.prefix + prefix_len; let path: &'static str = unsafe { &*(&req.path()[prefix_len..] as *const _) }; req.set_prefix_len(prefix_len as u16); - if path.is_empty() { - req.match_info_mut().set("tail", "/"); - } else { - req.match_info_mut().set("tail", path); - } + req.match_info_mut().set("tail", path); return HandlerType::Handler(idx); } } @@ -369,14 +365,6 @@ where { { let mut scope = Box::new(f(Scope::new())); - - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/') - } - if !path.ends_with('/') { - path.push('/'); - } let parts = self.parts.as_mut().expect("Use after finish"); let filters = scope.take_filters(); diff --git a/src/fs.rs b/src/fs.rs index 779b419a..cebad492 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -20,7 +20,7 @@ use mime_guess::{get_mime_type, guess_mime_type}; use error::Error; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use header; -use http::{HttpRange, Method, StatusCode, ContentEncoding}; +use http::{ContentEncoding, HttpRange, Method, StatusCode}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -304,11 +304,11 @@ impl Responder for NamedFile { resp.header( header::CONTENT_RANGE, format!( - "bytes {}-{}/{}", - offset, - offset + length - 1, + "bytes {}-{}/{}", + offset, + offset + length - 1, self.md.len() - ) + ), ); } else { resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); diff --git a/src/param.rs b/src/param.rs index 119ad59c..fd07acf4 100644 --- a/src/param.rs +++ b/src/param.rs @@ -58,12 +58,11 @@ impl<'a> Params<'a> { self.0.push((name, value)); } - pub(crate) fn remove(&mut self, name: &str) - { + pub(crate) fn remove(&mut self, name: &str) { for idx in (0..self.0.len()).rev() { if self.0[idx].0 == name { self.0.remove(idx); - return + return; } } } diff --git a/src/router.rs b/src/router.rs index 44fde0a4..602326f8 100644 --- a/src/router.rs +++ b/src/router.rs @@ -311,8 +311,16 @@ impl Resource { None } } - PatternType::Prefix(ref s) => if path.starts_with(s) { + PatternType::Prefix(ref s) => if path == s { Some(s.len()) + } else if path.starts_with(s) && (s.ends_with('/') || + path.split_at(s.len()).1.starts_with('/')) + { + if s.ends_with('/') { + Some(s.len()-1) + } else { + Some(s.len()) + } } else { None }, diff --git a/src/scope.rs b/src/scope.rs index edbf81df..839c4746 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -137,14 +137,6 @@ impl Scope { }; let mut scope = f(scope); - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/') - } - if !path.ends_with('/') { - path.push('/'); - } - let state = Rc::new(state); let filters: Vec>> = vec![Box::new(FiltersWrapper { state: Rc::clone(&state), @@ -191,14 +183,6 @@ impl Scope { }; let mut scope = f(scope); - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/') - } - if !path.ends_with('/') { - path.push('/'); - } - let filters = scope.take_filters(); self.nested.push(( Resource::prefix("", &path), @@ -253,7 +237,12 @@ impl Scope { let mut handler = ResourceHandler::default(); handler.method(method).with(f); - let pattern = Resource::new(handler.get_name(), path); + let pattern = Resource::with_prefix( + handler.get_name(), + path, + if path.is_empty() { "" } else { "/" }, + false, + ); Rc::get_mut(&mut self.resources) .expect("Can not use after configuration") .push((pattern, Rc::new(UnsafeCell::new(handler)))); @@ -293,7 +282,12 @@ impl Scope { let mut handler = ResourceHandler::default(); f(&mut handler); - let pattern = Resource::new(handler.get_name(), path); + let pattern = Resource::with_prefix( + handler.get_name(), + path, + if path.is_empty() { "" } else { "/" }, + false, + ); Rc::get_mut(&mut self.resources) .expect("Can not use after configuration") .push((pattern, Rc::new(UnsafeCell::new(handler)))); @@ -329,7 +323,6 @@ impl Scope { impl RouteHandler for Scope { fn handle(&mut self, mut req: HttpRequest) -> AsyncResult { let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; - let path = if path == "" { "/" } else { path }; // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { @@ -364,16 +357,12 @@ impl RouteHandler for Scope { continue 'outer; } } - let prefix_len = len + prefix_len - 1; + let prefix_len = len + prefix_len; let path: &'static str = unsafe { &*(&req.path()[prefix_len..] as *const _) }; req.set_prefix_len(prefix_len as u16); - if path.is_empty() { - req.match_info_mut().set("tail", "/"); - } else { - req.match_info_mut().set("tail", path); - } + req.match_info_mut().set("tail", path); let hnd: &mut RouteHandler<_> = unsafe { (&mut *(handler.get())).as_mut() }; @@ -784,6 +773,25 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::OK); } + #[test] + fn test_scope_root() { + let mut app = App::new() + .scope("/app", |scope| { + scope + .resource("", |r| r.f(|_| HttpResponse::Ok())) + .resource("/", |r| r.f(|_| HttpResponse::Created())) + }) + .finish(); + + let req = TestRequest::with_uri("/app").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); + } + #[test] fn test_scope_route() { let mut app = App::new() @@ -885,6 +893,29 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } + #[test] + fn test_scope_with_state_root() { + struct State; + + let mut app = App::new() + .scope("/app", |scope| { + scope.with_state("/t1", State, |scope| { + scope + .resource("", |r| r.f(|_| HttpResponse::Ok())) + .resource("/", |r| r.f(|_| HttpResponse::Created())) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/t1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/t1/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); + } + #[test] fn test_scope_with_state_filter() { struct State; @@ -927,6 +958,27 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } + #[test] + fn test_nested_scope_root() { + let mut app = App::new() + .scope("/app", |scope| { + scope.nested("/t1", |scope| { + scope + .resource("", |r| r.f(|_| HttpResponse::Ok())) + .resource("/", |r| r.f(|_| HttpResponse::Created())) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/t1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/t1/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); + } + #[test] fn test_nested_scope_filter() { let mut app = App::new() diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 34c589fc..4379a4ba 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -506,7 +506,7 @@ impl ContentEncoder { TransferEncoding::eof(buf) } else { if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) + || encoding == ContentEncoding::Auto) { resp.headers_mut().remove(CONTENT_LENGTH); } diff --git a/src/server/h1.rs b/src/server/h1.rs index d7edd2dc..7b4d8a97 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -273,7 +273,7 @@ where Ok(Async::Ready(_)) => { // non consumed payload in that case close connection if self.payload.is_some() && self.tasks.is_empty() { - return Ok(Async::Ready(false)) + return Ok(Async::Ready(false)); } } } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 6f10fc71..648a164f 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -1,6 +1,6 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] -use bytes::{BytesMut, BufMut}; +use bytes::{BufMut, BytesMut}; use futures::{Async, Poll}; use std::io; use std::rc::Rc; diff --git a/src/server/mod.rs b/src/server/mod.rs index 51ec3216..7347b725 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -136,7 +136,7 @@ impl HttpHandler for Box { #[doc(hidden)] pub trait HttpHandlerTask { /// Poll task, this method is used before or after *io* object is available - fn poll(&mut self) -> Poll<(), Error>{ + fn poll(&mut self) -> Poll<(), Error> { Ok(Async::Ready(())) } diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index cc6524d4..9507f1e9 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -374,9 +374,7 @@ fn test_scope_and_path_extractor() { App::new().scope("/sc", |scope| { scope.resource("/{num}/index.html", |r| { r.route() - .with(|p: Path<(usize,)>| { - format!("Welcome {}!", p.0) - }) + .with(|p: Path<(usize,)>| format!("Welcome {}!", p.0)) }) }) }); @@ -410,10 +408,9 @@ fn test_nested_scope_and_path_extractor() { App::new().scope("/sc", |scope| { scope.nested("/{num}", |scope| { scope.resource("/{num}/index.html", |r| { - r.route() - .with(|p: Path<(usize, usize)>| { - format!("Welcome {} {}!", p.0, p.1) - }) + r.route().with(|p: Path<(usize, usize)>| { + format!("Welcome {} {}!", p.0, p.1) + }) }) }) }) From 3b08b16c113b398e630790c205d3bad1246476a2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 23 May 2018 13:21:54 -0700 Subject: [PATCH 0263/1635] bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2d721da2..2b561acb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.9" +version = "0.6.10" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 17f1a2b92a733fc3698908edafeb6dc21d334fb2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 23 May 2018 14:11:01 -0700 Subject: [PATCH 0264/1635] more scope tests --- src/router.rs | 6 ++-- src/scope.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/router.rs b/src/router.rs index 602326f8..81a78e57 100644 --- a/src/router.rs +++ b/src/router.rs @@ -313,11 +313,11 @@ impl Resource { } PatternType::Prefix(ref s) => if path == s { Some(s.len()) - } else if path.starts_with(s) && (s.ends_with('/') || - path.split_at(s.len()).1.starts_with('/')) + } else if path.starts_with(s) + && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) { if s.ends_with('/') { - Some(s.len()-1) + Some(s.len() - 1) } else { Some(s.len()) } diff --git a/src/scope.rs b/src/scope.rs index 839c4746..dba490b2 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -792,6 +792,40 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } + #[test] + fn test_scope_root2() { + let mut app = App::new() + .scope("/app/", |scope| { + scope.resource("", |r| r.f(|_| HttpResponse::Ok())) + }) + .finish(); + + let req = TestRequest::with_uri("/app").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + } + + #[test] + fn test_scope_root3() { + let mut app = App::new() + .scope("/app/", |scope| { + scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) + }) + .finish(); + + let req = TestRequest::with_uri("/app").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + } + #[test] fn test_scope_route() { let mut app = App::new() @@ -916,6 +950,48 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } + #[test] + fn test_scope_with_state_root2() { + struct State; + + let mut app = App::new() + .scope("/app", |scope| { + scope.with_state("/t1/", State, |scope| { + scope.resource("", |r| r.f(|_| HttpResponse::Ok())) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/t1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/t1/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + } + + #[test] + fn test_scope_with_state_root3() { + struct State; + + let mut app = App::new() + .scope("/app", |scope| { + scope.with_state("/t1/", State, |scope| { + scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) + }) + }) + .finish(); + + let req = TestRequest::with_uri("/app/t1").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/t1/").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + } + #[test] fn test_scope_with_state_filter() { struct State; From 556646aaec1dccb277a93d7ee7274e986bcdf619 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 24 May 2018 07:56:51 -0700 Subject: [PATCH 0265/1635] update changelog --- CHANGES.md | 4 +++- src/fs.rs | 13 ++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0bf97683..2c1e94be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,13 @@ # Changes -## [0.6.10] - 2018-05-xx +## [0.6.10] - 2018-05-24 ### Added * Allow to use path without traling slashes for scope registration #241 +* Allow to set encoding for exact NamedFile #239 + ### Fixed * `TestServer::post()` actually sends `GET` request #240 diff --git a/src/fs.rs b/src/fs.rs index e7d4a9f7..a8f66ded 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -117,19 +117,13 @@ impl NamedFile { self } - // Set content encoding for serving this file + /// Set content encoding for serving this file #[inline] pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { self.encoding = Some(enc); self } - // Get content encoding used for serving this file - #[inline] - pub fn content_encoding(&self) -> Option { - self.encoding - } - fn etag(&self) -> Option { // This etag format is similar to Apache's. self.modified.as_ref().map(|mtime| { @@ -968,8 +962,9 @@ mod tests { let req = TestRequest::default().method(Method::GET).finish(); let file = NamedFile::open("Cargo.toml").unwrap(); - assert!(file.content_encoding().is_none()); - let resp = file.set_content_encoding(ContentEncoding::Identity) + assert!(file.encoding.is_none()); + let resp = file + .set_content_encoding(ContentEncoding::Identity) .respond_to(&req) .unwrap(); From 9f9e0b98ad42d8b94ac25581973d0675d4fd5a28 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 24 May 2018 08:55:10 -0700 Subject: [PATCH 0266/1635] change homepage link --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2b561acb..8ebc6355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] -homepage = "https://github.com/actix/actix-web" +homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-web/" categories = ["network-programming", "asynchronous", From bf63be3bcd1206c0e083228a252569c587da6ca9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 24 May 2018 09:24:04 -0700 Subject: [PATCH 0267/1635] bump version --- CHANGES.md | 3 +++ Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 2c1e94be..4f267d6d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Changes +## [0.7.0] - 2018-xx-xx + + ## [0.6.10] - 2018-05-24 ### Added diff --git a/Cargo.toml b/Cargo.toml index 8ebc6355..e1a11c2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.6.10" +version = "0.7.0-dev" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 111b6835fae2bc678e50a6b3581009a0c8be1219 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 24 May 2018 11:06:15 -0700 Subject: [PATCH 0268/1635] fix comment --- src/header/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/header/mod.rs b/src/header/mod.rs index 7d791c7b..6b98d324 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -40,7 +40,7 @@ pub trait IntoHeaderValue: Sized { /// The type returned in the event of a conversion error. type Error: Into; - /// Cast from PyObject to a concrete Python object type. + /// Try to convert value to a Header value. fn try_into(self) -> Result; } From 36f933ce1df5efc52cff6615b37fdcf3290f845d Mon Sep 17 00:00:00 2001 From: svartalf Date: Thu, 24 May 2018 21:36:17 +0300 Subject: [PATCH 0269/1635] Updating docs for HttpResponseBuilder::del_cookie --- src/httpresponse.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 428ca014..ec3c9562 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -458,7 +458,6 @@ impl HttpResponseBuilder { /// .finish()) /// .finish() /// } - /// fn main() {} /// ``` pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { @@ -471,8 +470,22 @@ impl HttpResponseBuilder { self } - /// Remove cookie, cookie has to be cookie from `HttpRequest::cookies()` - /// method. + /// Remove cookie + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, HttpRequest, HttpResponse, Result}; + /// + /// fn index(req: HttpRequest) -> HttpResponse { + /// let mut builder = HttpResponse::Ok(); + /// + /// if let Some(cookie) = req.cookie("name") { + /// builder.del_cookie(cookie); + /// } + /// + /// builder.finish() + /// } + /// ``` pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { { if self.cookies.is_none() { From 690169db8917c3e47b297edce8af4fd8c48210eb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 24 May 2018 21:03:16 -0700 Subject: [PATCH 0270/1635] migrate to tokio --- .travis.yml | 6 +-- CHANGES.md | 4 ++ Cargo.toml | 10 +++-- README.md | 2 +- src/client/connector.rs | 15 ++++--- src/client/mod.rs | 4 +- src/client/pipeline.rs | 12 +++--- src/client/request.rs | 2 +- src/error.rs | 4 ++ src/lib.rs | 5 ++- src/multipart.rs | 6 +-- src/payload.rs | 30 +++++++------- src/pipeline.rs | 6 +-- src/server/h1.rs | 10 ++--- src/server/h2.rs | 14 +++---- src/server/mod.rs | 2 +- src/server/srv.rs | 9 ++-- src/server/worker.rs | 89 ++++++++++++++++++++-------------------- src/test.rs | 18 ++++---- tests/test_handlers.rs | 36 +++++++--------- tests/test_middleware.rs | 22 ++++------ tests/test_server.rs | 18 ++++---- 22 files changed, 162 insertions(+), 162 deletions(-) diff --git a/.travis.yml b/.travis.yml index f930f508..fd9bfc0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ cache: matrix: include: - - rust: 1.24.0 + - rust: 1.25.0 - rust: stable - rust: beta - rust: nightly @@ -31,12 +31,12 @@ before_script: script: - | - if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then + if [[ "$TRAVIS_RUST_VERSION" != "1.25.0" ]]; then cargo clean cargo test --features="alpn,tls" -- --nocapture fi - | - if [[ "$TRAVIS_RUST_VERSION" == "1.24.0" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "1.25.0" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count bash <(curl -s https://codecov.io/bash) diff --git a/CHANGES.md b/CHANGES.md index 4f267d6d..8fd1689b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## [0.7.0] - 2018-xx-xx +### Changed + +* Migrate to tokio + ## [0.6.10] - 2018-05-24 diff --git a/Cargo.toml b/Cargo.toml index e1a11c2a..603bd1bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,8 @@ flate2-c = ["flate2/miniz-sys"] flate2-rust = ["flate2/rust_backend"] [dependencies] -actix = "^0.5.5" +#actix = "^0.6.0" +actix = { git="https://github.com/actix/actix.git" } base64 = "0.9" bitflags = "1.0" @@ -62,7 +63,7 @@ mime = "0.3" mime_guess = "2.0.0-alpha" num_cpus = "1.0" percent-encoding = "1.0" -rand = "0.4" +rand = "0.5" regex = "1.0" serde = "1.0" serde_json = "1.0" @@ -86,8 +87,11 @@ byteorder = "1" futures = "0.1" futures-cpupool = "0.1" slab = "0.4" +tokio = "0.1" tokio-io = "0.1" -tokio-core = "0.1" +tokio-tcp = "0.1" +tokio-timer = "0.2" +tokio-reactor = "0.1" # native-tls native-tls = { version="0.1", optional = true } diff --git a/README.md b/README.md index ae0bc645..9629da4e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.24 or later +* Minimum supported Rust version: 1.25 or later ## Example diff --git a/src/client/connector.rs b/src/client/connector.rs index 6389b897..e9301ae9 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -9,16 +9,16 @@ use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; use actix::fut::WrapFuture; use actix::registry::ArbiterService; use actix::{ - fut, Actor, ActorFuture, ActorResponse, Arbiter, AsyncContext, Context, - ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn, + fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context, ContextFutureSpawner, + Handler, Message, Recipient, Supervised, Syn, }; use futures::task::{current as current_task, Task}; use futures::unsync::oneshot; use futures::{Async, Future, Poll}; use http::{Error as HttpError, HttpTryFrom, Uri}; -use tokio_core::reactor::Timeout; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::Delay; #[cfg(feature = "alpn")] use openssl::ssl::{Error as OpensslError, SslConnector, SslMethod}; @@ -190,8 +190,8 @@ pub struct ClientConnector { available: HashMap>, to_close: Vec, waiters: HashMap>, - wait_timeout: Option<(Instant, Timeout)>, - paused: Option>, + wait_timeout: Option<(Instant, Delay)>, + paused: Option>, } impl Actor for ClientConnector { @@ -563,8 +563,7 @@ impl ClientConnector { } } - let mut timeout = - Timeout::new(time - Instant::now(), Arbiter::handle()).unwrap(); + let mut timeout = Delay::new(time); let _ = timeout.poll(); self.wait_timeout = Some((time, timeout)); } @@ -597,7 +596,7 @@ impl Handler for ClientConnector { fn handle(&mut self, msg: Pause, _: &mut Self::Context) { if let Some(time) = msg.time { let when = Instant::now() + time; - let mut timeout = Timeout::new(time, Arbiter::handle()).unwrap(); + let mut timeout = Delay::new(when); let _ = timeout.poll(); self.paused = Some(Some((when, timeout))); } else if self.paused.is_none() { diff --git a/src/client/mod.rs b/src/client/mod.rs index 9fd885fa..36a876cc 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -10,7 +10,7 @@ //! fn main() { //! let sys = actix::System::new("test"); //! -//! actix::Arbiter::handle().spawn({ +//! actix::Arbiter::spawn({ //! client::get("http://www.rust-lang.org") // <- Create request builder //! .header("User-Agent", "Actix-web") //! .finish().unwrap() @@ -70,7 +70,7 @@ impl ResponseError for SendRequestError { /// fn main() { /// let sys = actix::System::new("test"); /// -/// actix::Arbiter::handle().spawn({ +/// actix::Arbiter::spawn({ /// client::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index dae7bbaf..c7528073 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -2,9 +2,9 @@ use bytes::{Bytes, BytesMut}; use futures::unsync::oneshot; use futures::{Async, Future, Poll}; use http::header::CONTENT_ENCODING; -use std::time::Duration; +use std::time::{Duration, Instant}; use std::{io, mem}; -use tokio_core::reactor::Timeout; +use tokio_timer::Delay; use actix::prelude::*; @@ -71,7 +71,7 @@ pub struct SendRequest { conn: Addr, conn_timeout: Duration, wait_timeout: Duration, - timeout: Option, + timeout: Option, } impl SendRequest { @@ -108,7 +108,7 @@ impl SendRequest { /// Request timeout is the total time before a response must be received. /// Default value is 5 seconds. pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(Timeout::new(timeout, Arbiter::handle()).unwrap()); + self.timeout = Some(Delay::new(Instant::now() + timeout)); self } @@ -174,7 +174,7 @@ impl Future for SendRequest { }; let timeout = self.timeout.take().unwrap_or_else(|| { - Timeout::new(Duration::from_secs(5), Arbiter::handle()).unwrap() + Delay::new(Instant::now() + Duration::from_secs(5)) }); let pl = Box::new(Pipeline { @@ -229,7 +229,7 @@ pub(crate) struct Pipeline { decompress: Option, should_decompress: bool, write_state: RunningState, - timeout: Option, + timeout: Option, } enum IoBody { diff --git a/src/client/request.rs b/src/client/request.rs index 2f9ce12f..9a3d0fb1 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -34,7 +34,7 @@ use httprequest::HttpRequest; /// fn main() { /// let sys = actix::System::new("test"); /// -/// actix::Arbiter::handle().spawn({ +/// actix::Arbiter::spawn({ /// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() diff --git a/src/error.rs b/src/error.rs index 1ec394e3..6d739702 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,6 +16,7 @@ use http_range::HttpRangeParseError; use httparse; use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; +use tokio_timer::Error as TimerError; pub use url::ParseError as UrlParseError; // re-exports @@ -126,6 +127,9 @@ impl From for Error { /// `InternalServerError` for `JsonError` impl ResponseError for JsonError {} +/// `InternalServerError` for `TimerError` +impl ResponseError for TimerError {} + /// `InternalServerError` for `UrlParseError` impl ResponseError for UrlParseError {} diff --git a/src/lib.rs b/src/lib.rs index 9912d4ad..4dd1b215 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,8 +112,11 @@ extern crate mio; extern crate net2; extern crate rand; extern crate slab; -extern crate tokio_core; +extern crate tokio; extern crate tokio_io; +extern crate tokio_reactor; +extern crate tokio_tcp; +extern crate tokio_timer; extern crate url; #[macro_use] extern crate serde; diff --git a/src/multipart.rs b/src/multipart.rs index 365a101c..106961ae 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -663,7 +663,7 @@ mod tests { use bytes::Bytes; use futures::future::{lazy, result}; use payload::{Payload, PayloadWriter}; - use tokio_core::reactor::Core; + use tokio::runtime::current_thread::Runtime; #[test] fn test_boundary() { @@ -710,9 +710,9 @@ mod tests { #[test] fn test_multipart() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); let bytes = Bytes::from( diff --git a/src/payload.rs b/src/payload.rs index dd0b197b..12a4ae26 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -524,7 +524,7 @@ mod tests { use failure::Fail; use futures::future::{lazy, result}; use std::io; - use tokio_core::reactor::Core; + use tokio::runtime::current_thread::Runtime; #[test] fn test_error() { @@ -542,9 +542,9 @@ mod tests { #[test] fn test_basic() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let (_, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); @@ -559,9 +559,9 @@ mod tests { #[test] fn test_eof() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); @@ -584,9 +584,9 @@ mod tests { #[test] fn test_err() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); @@ -602,9 +602,9 @@ mod tests { #[test] fn test_readany() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); @@ -631,9 +631,9 @@ mod tests { #[test] fn test_readexactly() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); @@ -665,9 +665,9 @@ mod tests { #[test] fn test_readuntil() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); let mut payload = PayloadHelper::new(payload); @@ -699,9 +699,9 @@ mod tests { #[test] fn test_unread_data() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let (_, mut payload) = Payload::new(false); payload.unread_data(Bytes::from("data")); diff --git a/src/pipeline.rs b/src/pipeline.rs index f5c338e6..e315d4c0 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -770,7 +770,7 @@ mod tests { use actix::*; use context::HttpContext; use futures::future::{lazy, result}; - use tokio_core::reactor::Core; + use tokio::runtime::current_thread::Runtime; impl PipelineState { fn is_none(&self) -> Option { @@ -796,9 +796,9 @@ mod tests { #[test] fn test_completed() { - Core::new() + Runtime::new() .unwrap() - .run(lazy(|| { + .block_on(lazy(|| { let mut info = PipelineInfo::new(HttpRequest::default()); Completed::<(), Inner<()>>::init(&mut info) .is_none() diff --git a/src/server/h1.rs b/src/server/h1.rs index 7b4d8a97..e5fa2fe9 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -2,12 +2,11 @@ use std::collections::VecDeque; use std::io; use std::net::SocketAddr; use std::rc::Rc; -use std::time::Duration; +use std::time::{Duration, Instant}; -use actix::Arbiter; use bytes::{BufMut, BytesMut}; use futures::{Async, Future, Poll}; -use tokio_core::reactor::Timeout; +use tokio_timer::Delay; use error::PayloadError; use httprequest::HttpRequest; @@ -53,7 +52,7 @@ pub(crate) struct Http1 { payload: Option, buf: BytesMut, tasks: VecDeque, - keepalive_timer: Option, + keepalive_timer: Option, } struct Entry { @@ -295,8 +294,7 @@ where if self.keepalive_timer.is_none() && keep_alive > 0 { trace!("Start keep-alive timer"); let mut timer = - Timeout::new(Duration::new(keep_alive, 0), Arbiter::handle()) - .unwrap(); + Delay::new(Instant::now() + Duration::new(keep_alive, 0)); // register timer let _ = timer.poll(); self.keepalive_timer = Some(timer); diff --git a/src/server/h2.rs b/src/server/h2.rs index c730ac40..a73cc599 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -4,17 +4,16 @@ use std::collections::VecDeque; use std::io::{Read, Write}; use std::net::SocketAddr; use std::rc::Rc; -use std::time::Duration; +use std::time::{Duration, Instant}; use std::{cmp, io, mem}; -use actix::Arbiter; use bytes::{Buf, Bytes}; use futures::{Async, Future, Poll, Stream}; use http2::server::{self, Connection, Handshake, SendResponse}; use http2::{Reason, RecvStream}; use modhttp::request::Parts; -use tokio_core::reactor::Timeout; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::Delay; use error::PayloadError; use httpmessage::HttpMessage; @@ -46,7 +45,7 @@ where addr: Option, state: State>, tasks: VecDeque>, - keepalive_timer: Option, + keepalive_timer: Option, } enum State { @@ -218,9 +217,10 @@ where 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( - Duration::new(keep_alive, 0), - Arbiter::handle()).unwrap(); + let mut timeout = Delay::new( + Instant::now() + + Duration::new(keep_alive, 0), + ); // register timeout let _ = timeout.poll(); self.keepalive_timer = Some(timeout); diff --git a/src/server/mod.rs b/src/server/mod.rs index 7347b725..36f7cfb1 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -5,8 +5,8 @@ use std::{io, time}; use actix; use bytes::BytesMut; use futures::{Async, Poll}; -use tokio_core::net::TcpStream; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_tcp::TcpStream; mod channel; pub(crate) mod encoding; diff --git a/src/server/srv.rs b/src/server/srv.rs index 18663103..94e132e1 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -563,11 +563,10 @@ impl HttpServer { /// Start listening for incoming connections from a stream. /// /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(mut self, stream: S, secure: bool) -> Addr + pub fn start_incoming(mut self, stream: S, secure: bool) -> Addr where - S: Stream + 'static, + S: Stream + 'static, T: AsyncRead + AsyncWrite + 'static, - A: 'static, { // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); @@ -581,7 +580,7 @@ impl HttpServer { // start server let signals = self.subscribe_to_signals(); let addr: Addr = HttpServer::create(move |ctx| { - ctx.add_message_stream(stream.map_err(|_| ()).map(move |(t, _)| Conn { + ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn { io: WrapperStream::new(t), token: 0, peer: None, @@ -687,7 +686,7 @@ where type Result = (); fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { - Arbiter::handle().spawn(HttpChannel::new( + Arbiter::spawn(HttpChannel::new( Rc::clone(self.h.as_ref().unwrap()), msg.io, msg.peer, diff --git a/src/server/worker.rs b/src/server/worker.rs index f045074d..64e4c403 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -4,8 +4,8 @@ use net2::TcpStreamExt; use slab::Slab; use std::rc::Rc; use std::{net, time}; -use tokio_core::net::TcpStream; -use tokio_core::reactor::Handle; +use tokio_reactor::Handle; +use tokio_tcp::TcpStream; #[cfg(any(feature = "tls", feature = "alpn"))] use futures::future; @@ -60,7 +60,6 @@ where H: HttpHandler + 'static, { settings: Rc>, - hnd: Handle, socks: Slab, tcp_ka: Option, } @@ -77,7 +76,6 @@ impl Worker { Worker { settings: Rc::new(WorkerSettings::new(h, keep_alive)), - hnd: Arbiter::handle().clone(), socks, tcp_ka, } @@ -130,11 +128,11 @@ where if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { error!("Can not set socket keep-alive option"); } - self.socks.get_mut(msg.token).unwrap().htype.handle( - Rc::clone(&self.settings), - &self.hnd, - msg, - ); + self.socks + .get_mut(msg.token) + .unwrap() + .htype + .handle(Rc::clone(&self.settings), msg); } } @@ -174,15 +172,15 @@ pub(crate) enum StreamHandlerType { impl StreamHandlerType { fn handle( - &mut self, h: Rc>, hnd: &Handle, msg: Conn, + &mut self, h: Rc>, msg: Conn, ) { match *self { StreamHandlerType::Normal => { let _ = msg.io.set_nodelay(true); - let io = TcpStream::from_stream(msg.io, hnd) + let io = TcpStream::from_std(msg.io, &Handle::default()) .expect("failed to associate TCP stream"); - hnd.spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); + Arbiter::spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); } #[cfg(feature = "tls")] StreamHandlerType::Tls(ref acceptor) => { @@ -190,47 +188,50 @@ impl StreamHandlerType { io, peer, http2, .. } = msg; let _ = io.set_nodelay(true); - let io = TcpStream::from_stream(io, hnd) + let io = TcpStream::from_std(io, &Handle::default()) .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(())) - })); + Arbiter::spawn(TlsAcceptorExt::accept_async(acceptor, io).then( + move |res| { + match res { + Ok(io) => { + Arbiter::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.set_nodelay(true); - let io = TcpStream::from_stream(io, hnd) + let io = TcpStream::from_std(io, &Handle::default()) .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(())) - })); + Arbiter::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::spawn(HttpChannel::new(h, io, peer, http2)); + } + Err(err) => { + trace!("Error during handling tls connection: {}", err) + } + }; + future::result(Ok(())) + }, + )); } } } diff --git a/src/test.rs b/src/test.rs index 5e99652b..f950c17b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -11,8 +11,9 @@ use futures::Future; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; -use tokio_core::net::TcpListener; -use tokio_core::reactor::Core; +use tokio::runtime::current_thread::Runtime; +use tokio_reactor::Handle; +use tokio_tcp::TcpListener; #[cfg(feature = "alpn")] use openssl::ssl::SslAcceptor; @@ -112,8 +113,7 @@ impl TestServer { let sys = System::new("actix-test-server"); 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(); + let tcp = TcpListener::from_std(tcp, &Handle::default()).unwrap(); HttpServer::new(factory) .disable_signals() @@ -289,8 +289,7 @@ impl TestServerBuilder { 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(); + let tcp = TcpListener::from_std(tcp, &Handle::default()).unwrap(); let state = self.state; @@ -309,10 +308,9 @@ impl TestServerBuilder { let ssl = self.ssl.take(); if let Some(ssl) = ssl { srv.start_incoming( - tcp.incoming().and_then(move |(sock, addr)| { + tcp.incoming().and_then(move |sock| { ssl.accept_async(sock) .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) - .map(move |s| (s, addr)) }), false, ); @@ -616,8 +614,8 @@ impl TestRequest { let req = self.finish(); let fut = h(req.clone()); - let mut core = Core::new().unwrap(); - match core.run(fut) { + let mut core = Runtime::new().unwrap(); + match core.block_on(fut) { Ok(r) => match r.respond_to(&req) { Ok(reply) => match reply.into().into() { AsyncResultItem::Ok(resp) => Ok(resp), diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 9507f1e9..8544b9bf 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -4,21 +4,20 @@ extern crate bytes; extern crate futures; extern crate h2; extern crate http; -extern crate tokio_core; +extern crate tokio_timer; #[macro_use] extern crate serde_derive; extern crate serde_json; use std::io; -use std::time::Duration; +use std::time::{Duration, Instant}; -use actix::*; use actix_web::*; use bytes::Bytes; use futures::Future; use http::StatusCode; use serde_json::Value; -use tokio_core::reactor::Timeout; +use tokio_timer::Delay; #[derive(Deserialize)] struct PParam { @@ -75,8 +74,7 @@ fn test_async_extractor_async() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { r.route().with(|data: Json| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| Ok(format!("{}", data.0))) .responder() }) @@ -171,8 +169,7 @@ fn test_path_and_query_extractor2_async() { app.resource("/{username}/index.html", |r| { r.route() .with3(|p: Path, _: Query, data: Json| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) @@ -201,8 +198,7 @@ fn test_path_and_query_extractor3_async() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { r.route().with2(|p: Path, data: Json| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) @@ -227,8 +223,7 @@ fn test_path_and_query_extractor4_async() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { r.route().with2(|data: Json, p: Path| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) @@ -254,8 +249,7 @@ fn test_path_and_query_extractor2_async2() { app.resource("/{username}/index.html", |r| { r.route() .with3(|p: Path, data: Json, _: Query| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) @@ -294,8 +288,7 @@ fn test_path_and_query_extractor2_async3() { app.resource("/{username}/index.html", |r| { r.route() .with3(|data: Json, p: Path, _: Query| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) @@ -334,8 +327,7 @@ fn test_path_and_query_extractor2_async4() { app.resource("/{username}/index.html", |r| { r.route() .with(|data: (Json, Path, Query)| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)) }) @@ -443,8 +435,8 @@ fn test_nested_scope_and_path_extractor() { fn test_impl_trait( data: (Json, Path, Query), ) -> impl Future { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) .and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))) } @@ -452,8 +444,8 @@ fn test_impl_trait( fn test_impl_trait_err( _data: (Json, Path, Query), ) -> impl Future { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) .and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other"))) } diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 8435e746..4996542e 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -1,17 +1,16 @@ extern crate actix; extern crate actix_web; extern crate futures; -extern crate tokio_core; +extern crate tokio_timer; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; -use std::time::Duration; +use std::time::{Duration, Instant}; -use actix::*; use actix_web::*; use futures::{future, Future}; -use tokio_core::reactor::Timeout; +use tokio_timer::Delay; struct MiddlewareTest { start: Arc, @@ -245,8 +244,7 @@ fn test_middleware_async_handler() { }) .resource("/", |r| { r.route().a(|_| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(|_| Ok(HttpResponse::Ok())) }) }) @@ -281,8 +279,7 @@ fn test_resource_middleware_async_handler() { App::new().resource("/test", |r| { r.middleware(mw); r.route().a(|_| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(|_| Ok(HttpResponse::Ok())) }) }) @@ -317,8 +314,7 @@ fn test_scope_middleware_async_handler() { }) .resource("/test", |r| { r.route().a(|_| { - Timeout::new(Duration::from_millis(10), &Arbiter::handle()) - .unwrap() + Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(|_| Ok(HttpResponse::Ok())) }) }) @@ -436,7 +432,7 @@ struct MiddlewareAsyncTest { impl middleware::Middleware for MiddlewareAsyncTest { fn start(&self, _: &mut HttpRequest) -> Result { - let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap(); + let to = Delay::new(Instant::now() + Duration::from_millis(10)); let start = Arc::clone(&self.start); Ok(middleware::Started::Future(Box::new( @@ -450,7 +446,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { fn response( &self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { - let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap(); + let to = Delay::new(Instant::now() + Duration::from_millis(10)); let response = Arc::clone(&self.response); Ok(middleware::Response::Future(Box::new( @@ -462,7 +458,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { } fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { - let to = Timeout::new(Duration::from_millis(10), &Arbiter::handle()).unwrap(); + let to = Delay::new(Instant::now() + Duration::from_millis(10)); let finish = Arc::clone(&self.finish); middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| { diff --git a/tests/test_server.rs b/tests/test_server.rs index 1fcbfd5e..c02642a1 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -6,7 +6,9 @@ extern crate futures; extern crate h2; extern crate http as modhttp; extern crate rand; -extern crate tokio_core; +extern crate tokio; +extern crate tokio_reactor; +extern crate tokio_tcp; #[cfg(feature = "brotli")] extern crate brotli2; @@ -25,8 +27,9 @@ use rand::Rng; use std::io::{Read, Write}; use std::sync::{mpsc, Arc}; use std::{net, thread, time}; -use tokio_core::net::TcpStream; -use tokio_core::reactor::Core; +use tokio::executor::current_thread; +use tokio::runtime::current_thread::Runtime; +use tokio_tcp::TcpStream; use actix::System; use actix_web::*; @@ -790,9 +793,8 @@ fn test_h2() { let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let addr = srv.addr(); - let mut core = Core::new().unwrap(); - let handle = core.handle(); - let tcp = TcpStream::connect(&addr, &handle); + let mut core = Runtime::new().unwrap(); + let tcp = TcpStream::connect(&addr); let tcp = tcp .then(|res| h2client::handshake(res.unwrap())) @@ -806,7 +808,7 @@ fn test_h2() { let (response, _) = client.send_request(request, false).unwrap(); // Spawn a task to run the conn... - handle.spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); + current_thread::spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); response.and_then(|response| { assert_eq!(response.status(), http::StatusCode::OK); @@ -819,7 +821,7 @@ fn test_h2() { }) }) }); - let _res = core.run(tcp); + let _res = core.block_on(tcp); // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); } From f48702042bdd21ed626951a64ebcf8f6375ade6e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 24 May 2018 21:09:20 -0700 Subject: [PATCH 0271/1635] min rustc version --- .appveyor.yml | 9 --------- .travis.yml | 7 +++---- CHANGES.md | 2 ++ README.md | 2 +- src/lib.rs | 2 +- 5 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 7933588a..4bcd7732 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,15 +3,6 @@ environment: PROJECT_NAME: actix matrix: # Stable channel - - TARGET: i686-pc-windows-gnu - CHANNEL: 1.24.0 - - TARGET: i686-pc-windows-msvc - CHANNEL: 1.24.0 - - TARGET: x86_64-pc-windows-gnu - CHANNEL: 1.24.0 - - TARGET: x86_64-pc-windows-msvc - CHANNEL: 1.24.0 - # Stable channel - TARGET: i686-pc-windows-gnu CHANNEL: stable - TARGET: i686-pc-windows-msvc diff --git a/.travis.yml b/.travis.yml index fd9bfc0b..50dc82f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ cache: matrix: include: - - rust: 1.25.0 - rust: stable - rust: beta - rust: nightly @@ -31,12 +30,12 @@ before_script: script: - | - if [[ "$TRAVIS_RUST_VERSION" != "1.25.0" ]]; then + if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then cargo clean cargo test --features="alpn,tls" -- --nocapture fi - | - if [[ "$TRAVIS_RUST_VERSION" == "1.25.0" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count bash <(curl -s https://codecov.io/bash) @@ -46,7 +45,7 @@ script: # Upload docs after_success: - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then cargo doc --features "alpn, tls, session" --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && diff --git a/CHANGES.md b/CHANGES.md index 8fd1689b..c1f164b6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Migrate to tokio +* Min rustc version is 1.26 + ## [0.6.10] - 2018-05-24 diff --git a/README.md b/README.md index 9629da4e..427838c1 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.25 or later +* Minimum supported Rust version: 1.26 or later ## Example diff --git a/src/lib.rs b/src/lib.rs index 4dd1b215..a428b08b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ //! * SSL support with OpenSSL or `native-tls` //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) //! * Built on top of [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.24 or later +//! * Supported Rust version: 1.26 or later //! //! ## Package feature //! From 255cd4917d0066850154085a5e63bd3beecc5933 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 24 May 2018 22:04:14 -0700 Subject: [PATCH 0272/1635] fix doc test --- src/client/connector.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index e9301ae9..1d4f6dbf 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -286,9 +286,9 @@ impl ClientConnector { /// /// // Start `ClientConnector` with custom `SslConnector` /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn: Address<_> = ClientConnector::with_connector(ssl_conn).start(); + /// let conn: Addr = ClientConnector::with_connector(ssl_conn).start(); /// - /// Arbiter::handle().spawn({ + /// Arbiter::spawn( /// conn.send( /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host /// .map_err(|_| ()) @@ -299,7 +299,7 @@ impl ClientConnector { /// # Arbiter::system().do_send(actix::msgs::SystemExit(0)); /// Ok(()) /// }) - /// }); + /// ); /// /// sys.run(); /// } From 4dcecd907b64e9cff3bd2dbc4143bf0cafb0ba72 Mon Sep 17 00:00:00 2001 From: Bruno Bigras Date: Fri, 25 May 2018 19:15:00 -0400 Subject: [PATCH 0273/1635] Add same-site to CookieSessionBackend closes #247 --- src/middleware/session.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/middleware/session.rs b/src/middleware/session.rs index ba385d83..3e6e9890 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -69,7 +69,7 @@ use std::marker::PhantomData; use std::rc::Rc; use std::sync::Arc; -use cookie::{Cookie, CookieJar, Key}; +use cookie::{Cookie, CookieJar, Key, SameSite}; use futures::future::{err as FutErr, ok as FutOk, FutureResult}; use futures::Future; use http::header::{self, HeaderValue}; @@ -367,6 +367,7 @@ struct CookieSessionInner { domain: Option, secure: bool, max_age: Option, + same_site: Option, } impl CookieSessionInner { @@ -379,6 +380,7 @@ impl CookieSessionInner { domain: None, secure: true, max_age: None, + same_site: None, } } @@ -404,6 +406,10 @@ impl CookieSessionInner { cookie.set_max_age(max_age); } + if let Some(same_site) = self.same_site { + cookie.set_same_site(same_site); + } + let mut jar = CookieJar::new(); match self.security { @@ -531,6 +537,12 @@ impl CookieSessionBackend { self } + /// Sets the `same_site` field in the session cookie being built. + pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().same_site = Some(value); + self + } + /// Sets the `max-age` field in the session cookie being built. pub fn max_age(mut self, value: Duration) -> CookieSessionBackend { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); From be2ceb7c6602238377f2009c52e013d36b6c1992 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 27 May 2018 05:02:49 -0700 Subject: [PATCH 0274/1635] update actix Addr; make ClientConnector thread safe --- src/client/connector.rs | 321 +++++++++++++++------------------------- src/client/pipeline.rs | 6 +- src/client/request.rs | 6 +- src/context.rs | 19 +-- src/pipeline.rs | 2 +- src/server/srv.rs | 22 +-- src/test.rs | 8 +- src/ws/client.rs | 6 +- src/ws/context.rs | 20 +-- 9 files changed, 157 insertions(+), 253 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 1d4f6dbf..12a36dee 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,20 +1,17 @@ -use std::cell::{Cell, RefCell}; use std::collections::{HashMap, VecDeque}; use std::net::Shutdown; -use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt, io, mem, time}; use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; use actix::fut::WrapFuture; -use actix::registry::ArbiterService; +use actix::registry::SystemService; use actix::{ fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context, ContextFutureSpawner, - Handler, Message, Recipient, Supervised, Syn, + Handler, Message, Recipient, StreamHandler, Supervised, }; -use futures::task::{current as current_task, Task}; -use futures::unsync::oneshot; +use futures::sync::{mpsc, oneshot}; use futures::{Async, Future, Poll}; use http::{Error as HttpError, HttpTryFrom, Uri}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -167,6 +164,21 @@ struct Waiter { conn_timeout: Duration, } +enum Paused { + No, + Yes, + Timeout(Instant, Delay), +} + +impl Paused { + fn is_paused(&self) -> bool { + match *self { + Paused::No => false, + _ => true, + } + } +} + /// `ClientConnector` type is responsible for transport layer of a /// client connection. pub struct ClientConnector { @@ -176,10 +188,10 @@ pub struct ClientConnector { connector: TlsConnector, stats: ClientConnectorStats, - subscriber: Option>, + subscriber: Option>, - pool: Rc, - pool_modified: Rc>, + acq_tx: mpsc::UnboundedSender, + acq_rx: Option>, conn_lifetime: Duration, conn_keep_alive: Duration, @@ -191,7 +203,7 @@ pub struct ClientConnector { to_close: Vec, waiters: HashMap>, wait_timeout: Option<(Instant, Delay)>, - paused: Option>, + paused: Paused, } impl Actor for ClientConnector { @@ -199,17 +211,18 @@ impl Actor for ClientConnector { fn started(&mut self, ctx: &mut Self::Context) { self.collect_periodic(ctx); + ctx.add_stream(self.acq_rx.take().unwrap()); ctx.spawn(Maintenance); } } impl Supervised for ClientConnector {} -impl ArbiterService for ClientConnector {} +impl SystemService for ClientConnector {} impl Default for ClientConnector { fn default() -> ClientConnector { - let _modified = Rc::new(Cell::new(false)); + let (tx, rx) = mpsc::unbounded(); #[cfg(all(feature = "alpn"))] { @@ -222,8 +235,8 @@ impl Default for ClientConnector { ClientConnector { stats: ClientConnectorStats::default(), subscriber: None, - pool: Rc::new(Pool::new(Rc::clone(&_modified))), - pool_modified: _modified, + acq_tx: tx, + acq_rx: Some(rx), connector: builder.build().unwrap(), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), @@ -235,7 +248,7 @@ impl Default for ClientConnector { to_close: Vec::new(), waiters: HashMap::new(), wait_timeout: None, - paused: None, + paused: Paused::No, } } @@ -243,8 +256,8 @@ impl Default for ClientConnector { ClientConnector { stats: ClientConnectorStats::default(), subscriber: None, - pool: Rc::new(Pool::new(Rc::clone(&_modified))), - pool_modified: _modified, + acq_tx: tx, + acq_rx: Some(rx), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), limit: 100, @@ -255,7 +268,7 @@ impl Default for ClientConnector { to_close: Vec::new(), waiters: HashMap::new(), wait_timeout: None, - paused: None, + paused: Paused::No, } } } @@ -286,7 +299,7 @@ impl ClientConnector { /// /// // Start `ClientConnector` with custom `SslConnector` /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn: Addr = ClientConnector::with_connector(ssl_conn).start(); + /// let conn = ClientConnector::with_connector(ssl_conn).start(); /// /// Arbiter::spawn( /// conn.send( @@ -305,13 +318,14 @@ impl ClientConnector { /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { - let modified = Rc::new(Cell::new(false)); + let (tx, rx) = mpsc::unbounded(); + ClientConnector { connector, stats: ClientConnectorStats::default(), subscriber: None, - pool: Rc::new(Pool::new(Rc::clone(&modified))), - pool_modified: modified, + pool_tx: tx, + pool_rx: Some(rx), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), limit: 100, @@ -322,7 +336,7 @@ impl ClientConnector { to_close: Vec::new(), waiters: HashMap::new(), wait_timeout: None, - paused: None, + paused: Paused::No, } } @@ -366,7 +380,7 @@ impl ClientConnector { } /// Subscribe for connector stats. Only one subscriber is supported. - pub fn stats(mut self, subs: Recipient) -> Self { + pub fn stats(mut self, subs: Recipient) -> Self { self.subscriber = Some(subs); self } @@ -448,75 +462,18 @@ impl ClientConnector { } } - fn collect(&mut self, periodic: bool) { - let now = Instant::now(); - - if self.pool_modified.get() { - // collect half acquire keys - if let Some(keys) = self.pool.collect_keys() { - for key in keys { - self.release_key(&key); - } - } - - // collect connections for close - if let Some(to_close) = self.pool.collect_close() { - for conn in to_close { - self.release_key(&conn.key); - self.to_close.push(conn); - self.stats.closed += 1; - } - } - - // connection connections - if let Some(to_release) = self.pool.collect_release() { - for conn in to_release { - self.release_key(&conn.key); - - // check connection lifetime and the return to available pool - if (now - conn.ts) < self.conn_lifetime { - self.available - .entry(conn.key.clone()) - .or_insert_with(VecDeque::new) - .push_back(Conn(Instant::now(), conn)); - } - } - } - } - - // check keep-alive - for conns in self.available.values_mut() { - while !conns.is_empty() { - if (now > conns[0].0) && (now - conns[0].0) > self.conn_keep_alive - || (now - conns[0].1.ts) > self.conn_lifetime - { - let conn = conns.pop_front().unwrap().1; - self.to_close.push(conn); - self.stats.closed += 1; - } else { - break; - } - } - } - - // check connections for shutdown - if periodic { - let mut idx = 0; - while idx < self.to_close.len() { - match AsyncWrite::shutdown(&mut self.to_close[idx]) { - Ok(Async::NotReady) => idx += 1, - _ => { - self.to_close.swap_remove(idx); - } - } - } - } - - self.pool_modified.set(false); - } - fn collect_periodic(&mut self, ctx: &mut Context) { - self.collect(true); + // check connections for shutdown + let mut idx = 0; + while idx < self.to_close.len() { + match AsyncWrite::shutdown(&mut self.to_close[idx]) { + Ok(Async::NotReady) => idx += 1, + _ => { + self.to_close.swap_remove(idx); + } + } + } + // re-schedule next collect period ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); @@ -598,9 +555,9 @@ impl Handler for ClientConnector { let when = Instant::now() + time; let mut timeout = Delay::new(when); let _ = timeout.poll(); - self.paused = Some(Some((when, timeout))); - } else if self.paused.is_none() { - self.paused = Some(None); + self.paused = Paused::Timeout(when, timeout); + } else { + self.paused = Paused::Yes; } } } @@ -609,7 +566,7 @@ impl Handler for ClientConnector { type Result = (); fn handle(&mut self, _: Resume, _: &mut Self::Context) { - self.paused.take(); + self.paused = Paused::No; } } @@ -617,10 +574,6 @@ impl Handler for ClientConnector { type Result = ActorResponse; fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result { - if self.pool_modified.get() { - self.collect(false); - } - let uri = &msg.uri; let wait_timeout = msg.wait_timeout; let conn_timeout = msg.conn_timeout; @@ -646,11 +599,6 @@ impl Handler for ClientConnector { return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)); } - // check if pool has task reference - if self.pool.task.borrow().is_none() { - *self.pool.task.borrow_mut() = Some(current_task()); - } - let host = uri.host().unwrap().to_owned(); let port = uri.port().unwrap_or_else(|| proto.port()); let key = Key { @@ -660,7 +608,7 @@ impl Handler for ClientConnector { }; // check pause state - if self.paused.is_some() { + if self.paused.is_paused() { let rx = self.wait_for(key, wait_timeout, conn_timeout); self.stats.waits += 1; return ActorResponse::async( @@ -678,7 +626,7 @@ impl Handler for ClientConnector { match self.acquire(&key) { Acquire::Acquired(mut conn) => { // use existing connection - conn.pool = Some(AcquiredConn(key, Some(Rc::clone(&self.pool)))); + conn.pool = Some(AcquiredConn(key, Some(self.acq_tx.clone()))); self.stats.reused += 1; return ActorResponse::async(fut::ok(conn)); } @@ -695,7 +643,7 @@ impl Handler for ClientConnector { }), ); } - Acquire::Available => Some(Rc::clone(&self.pool)), + Acquire::Available => Some(self.acq_tx.clone()), } } else { None @@ -801,6 +749,49 @@ impl Handler for ClientConnector { } } +impl StreamHandler for ClientConnector { + fn handle(&mut self, msg: AcquiredConnOperation, _: &mut Context) { + let now = Instant::now(); + + match msg { + AcquiredConnOperation::Close(conn) => { + self.release_key(&conn.key); + self.to_close.push(conn); + self.stats.closed += 1; + } + AcquiredConnOperation::Release(conn) => { + self.release_key(&conn.key); + + // check connection lifetime and the return to available pool + if (Instant::now() - conn.ts) < self.conn_lifetime { + self.available + .entry(conn.key.clone()) + .or_insert_with(VecDeque::new) + .push_back(Conn(Instant::now(), conn)); + } + } + AcquiredConnOperation::ReleaseKey(key) => { + self.release_key(&key); + } + } + + // check keep-alive + for conns in self.available.values_mut() { + while !conns.is_empty() { + if (now > conns[0].0) && (now - conns[0].0) > self.conn_keep_alive + || (now - conns[0].1.ts) > self.conn_lifetime + { + let conn = conns.pop_front().unwrap().1; + self.to_close.push(conn); + self.stats.closed += 1; + } else { + break; + } + } + } + } +} + struct Maintenance; impl fut::ActorFuture for Maintenance { @@ -812,18 +803,10 @@ impl fut::ActorFuture for Maintenance { &mut self, act: &mut ClientConnector, ctx: &mut Context, ) -> Poll { // check pause duration - let done = if let Some(Some(ref pause)) = act.paused { - pause.0 <= Instant::now() - } else { - false - }; - if done { - act.paused.take(); - } - - // collect connections - if act.pool_modified.get() { - act.collect(false); + if let Paused::Timeout(inst, _) = act.paused { + if inst <= Instant::now() { + act.paused = Paused::No; + } } // collect wait timers @@ -843,7 +826,7 @@ impl fut::ActorFuture for Maintenance { // use existing connection act.stats.reused += 1; conn.pool = - Some(AcquiredConn(key.clone(), Some(Rc::clone(&act.pool)))); + Some(AcquiredConn(key.clone(), Some(act.acq_tx.clone()))); let _ = waiter.tx.send(Ok(conn)); } Acquire::NotAvailable => { @@ -851,7 +834,7 @@ impl fut::ActorFuture for Maintenance { break; } Acquire::Available => { - let conn = AcquiredConn(key.clone(), Some(Rc::clone(&act.pool))); + let conn = AcquiredConn(key.clone(), Some(act.acq_tx.clone())); fut::WrapFuture::::actfuture( Connector::from_registry().send( @@ -1050,100 +1033,38 @@ enum Acquire { NotAvailable, } -struct AcquiredConn(Key, Option>); +enum AcquiredConnOperation { + Close(Connection), + Release(Connection), + ReleaseKey(Key), +} + +struct AcquiredConn(Key, Option>); impl AcquiredConn { fn close(&mut self, conn: Connection) { - if let Some(pool) = self.1.take() { - pool.close(conn); + if let Some(tx) = self.1.take() { + let _ = tx.unbounded_send(AcquiredConnOperation::Close(conn)); } } fn release(&mut self, conn: Connection) { - if let Some(pool) = self.1.take() { - pool.release(conn); + if let Some(tx) = self.1.take() { + let _ = tx.unbounded_send(AcquiredConnOperation::Release(conn)); } } } impl Drop for AcquiredConn { fn drop(&mut self) { - if let Some(pool) = self.1.take() { - pool.release_key(self.0.clone()); - } - } -} - -pub struct Pool { - keys: RefCell>, - to_close: RefCell>, - to_release: RefCell>, - task: RefCell>, - modified: Rc>, -} - -impl Pool { - fn new(modified: Rc>) -> Pool { - Pool { - modified, - keys: RefCell::new(Vec::new()), - to_close: RefCell::new(Vec::new()), - to_release: RefCell::new(Vec::new()), - task: RefCell::new(None), - } - } - - fn collect_keys(&self) -> Option> { - if self.keys.borrow().is_empty() { - None - } else { - Some(mem::replace(&mut *self.keys.borrow_mut(), Vec::new())) - } - } - - fn collect_close(&self) -> Option> { - if self.to_close.borrow().is_empty() { - None - } else { - Some(mem::replace(&mut *self.to_close.borrow_mut(), Vec::new())) - } - } - - fn collect_release(&self) -> Option> { - if self.to_release.borrow().is_empty() { - None - } else { - Some(mem::replace(&mut *self.to_release.borrow_mut(), Vec::new())) - } - } - - fn close(&self, conn: Connection) { - self.modified.set(true); - self.to_close.borrow_mut().push(conn); - if let Some(ref task) = *self.task.borrow() { - task.notify() - } - } - - fn release(&self, conn: Connection) { - self.modified.set(true); - self.to_release.borrow_mut().push(conn); - if let Some(ref task) = *self.task.borrow() { - task.notify() - } - } - - fn release_key(&self, key: Key) { - self.modified.set(true); - self.keys.borrow_mut().push(key); - if let Some(ref task) = *self.task.borrow() { - task.notify() + if let Some(tx) = self.1.take() { + let _ = tx.unbounded_send(AcquiredConnOperation::ReleaseKey(self.0.clone())); } } } pub struct Connection { key: Key, - stream: Box, + stream: Box, pool: Option, ts: Instant, } @@ -1155,7 +1076,7 @@ impl fmt::Debug for Connection { } impl Connection { - fn new(key: Key, pool: Option, stream: Box) -> Self { + fn new(key: Key, pool: Option, stream: Box) -> Self { Connection { key, stream, @@ -1168,7 +1089,7 @@ impl Connection { &mut *self.stream } - pub fn from_stream(io: T) -> Connection { + pub fn from_stream(io: T) -> Connection { Connection::new(Key::empty(), None, Box::new(io)) } diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index c7528073..3f3d425d 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -56,7 +56,7 @@ impl From for SendRequestError { enum State { New, - Connect(actix::dev::Request), + Connect(actix::dev::Request), Connection(Connection), Send(Box), None, @@ -68,7 +68,7 @@ enum State { pub struct SendRequest { req: ClientRequest, state: State, - conn: Addr, + conn: Addr, conn_timeout: Duration, wait_timeout: Duration, timeout: Option, @@ -80,7 +80,7 @@ impl SendRequest { } pub(crate) fn with_connector( - req: ClientRequest, conn: Addr, + req: ClientRequest, conn: Addr, ) -> SendRequest { SendRequest { req, diff --git a/src/client/request.rs b/src/client/request.rs index 9a3d0fb1..adb1b29f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -3,7 +3,7 @@ use std::io::Write; use std::time::Duration; use std::{fmt, mem}; -use actix::{Addr, Unsync}; +use actix::Addr; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::Stream; @@ -67,7 +67,7 @@ pub struct ClientRequest { enum ConnectionType { Default, - Connector(Addr), + Connector(Addr), Connection(Connection), } @@ -541,7 +541,7 @@ impl ClientRequestBuilder { } /// Send request using custom connector - pub fn with_connector(&mut self, conn: Addr) -> &mut Self { + pub fn with_connector(&mut self, conn: Addr) -> &mut Self { if let Some(parts) = parts(&mut self.request, &self.err) { parts.conn = ConnectionType::Connector(conn); } diff --git a/src/context.rs b/src/context.rs index 375e8ef1..e298287b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -4,11 +4,10 @@ use futures::{Async, Future, Poll}; use smallvec::SmallVec; use std::marker::PhantomData; -use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; +use actix::dev::{ContextImpl, Envelope, ToEnvelope}; use actix::fut::ActorFuture; use actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, - Syn, Unsync, }; use body::{Binary, Body}; @@ -90,15 +89,9 @@ where fn cancel_future(&mut self, handle: SpawnHandle) -> bool { self.inner.cancel_future(handle) } - #[doc(hidden)] #[inline] - fn unsync_address(&mut self) -> Addr { - self.inner.unsync_address() - } - #[doc(hidden)] - #[inline] - fn sync_address(&mut self) -> Addr { - self.inner.sync_address() + fn address(&mut self) -> Addr { + self.inner.address() } } @@ -223,14 +216,14 @@ where } } -impl ToEnvelope for HttpContext +impl ToEnvelope for HttpContext where A: Actor> + Handler, M: Message + Send + 'static, M::Result: Send, { - fn pack(msg: M, tx: Option>) -> SyncEnvelope { - SyncEnvelope::new(msg, tx) + fn pack(msg: M, tx: Option>) -> Envelope { + Envelope::new(msg, tx) } } diff --git a/src/pipeline.rs b/src/pipeline.rs index e315d4c0..e5152de5 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -806,7 +806,7 @@ mod tests { let req = HttpRequest::default(); let mut ctx = HttpContext::new(req.clone(), MyActor); - let addr: Addr = ctx.address(); + let addr = ctx.address(); let mut info = PipelineInfo::new(req); info.context = Some(Box::new(ctx)); let mut state = Completed::<(), Inner<()>>::init(&mut info) diff --git a/src/server/srv.rs b/src/server/srv.rs index 94e132e1..42b9d77d 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -51,12 +51,12 @@ where keep_alive: KeepAlive, factory: Arc Vec + Send + Sync>, #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] - workers: Vec<(usize, Addr>)>, + workers: Vec<(usize, Addr>)>, sockets: Vec, accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, exit: bool, shutdown_timeout: u16, - signals: Option>, + signals: Option>, no_http2: bool, no_signals: bool, } @@ -177,7 +177,7 @@ where } /// Set alternative address for `ProcessSignals` actor. - pub fn signals(mut self, addr: Addr) -> Self { + pub fn signals(mut self, addr: Addr) -> Self { self.signals = Some(addr); self } @@ -380,12 +380,12 @@ where } // subscribe to os signals - fn subscribe_to_signals(&self) -> Option> { + fn subscribe_to_signals(&self) -> Option> { if !self.no_signals { if let Some(ref signals) = self.signals { Some(signals.clone()) } else { - Some(Arbiter::system_registry().get::()) + Some(Arbiter::registry().get::()) } } else { None @@ -422,7 +422,7 @@ impl HttpServer { /// let _ = sys.run(); // <- Run actix system, this method actually starts all async processes /// } /// ``` - pub fn start(mut self) -> Addr { + pub fn start(mut self) -> Addr { if self.sockets.is_empty() { panic!("HttpServer::bind() has to be called before start()"); } else { @@ -458,7 +458,7 @@ impl HttpServer { // start http server actor let signals = self.subscribe_to_signals(); - let addr: Addr = Actor::create(move |ctx| { + let addr = Actor::create(move |ctx| { ctx.add_stream(rx); self }); @@ -510,7 +510,7 @@ impl HttpServer { )] impl HttpServer { /// Start listening for incoming tls connections. - pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result> { + pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result> { for sock in &mut self.sockets { match sock.tp { StreamHandlerType::Normal => (), @@ -533,7 +533,7 @@ impl HttpServer { /// This method sets alpn protocols to "h2" and "http/1.1" pub fn start_ssl( mut self, mut builder: SslAcceptorBuilder, - ) -> io::Result> { + ) -> io::Result> { // alpn support if !self.no_http2 { builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; @@ -563,7 +563,7 @@ impl HttpServer { /// Start listening for incoming connections from a stream. /// /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(mut self, stream: S, secure: bool) -> Addr + pub fn start_incoming(mut self, stream: S, secure: bool) -> Addr where S: Stream + 'static, T: AsyncRead + AsyncWrite + 'static, @@ -579,7 +579,7 @@ impl HttpServer { // start server let signals = self.subscribe_to_signals(); - let addr: Addr = HttpServer::create(move |ctx| { + let addr = HttpServer::create(move |ctx| { ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn { io: WrapperStream::new(t), token: 0, diff --git a/src/test.rs b/src/test.rs index f950c17b..bd2135cc 100644 --- a/src/test.rs +++ b/src/test.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use std::sync::mpsc; use std::{net, thread}; -use actix::{msgs, Actor, Addr, Arbiter, Syn, System, SystemRunner, Unsync}; +use actix::{msgs, Actor, Addr, Arbiter, System, SystemRunner}; use cookie::Cookie; use futures::Future; use http::header::HeaderName; @@ -64,9 +64,9 @@ pub struct TestServer { addr: net::SocketAddr, thread: Option>, system: SystemRunner, - server_sys: Addr, + server_sys: Addr, ssl: bool, - conn: Addr, + conn: Addr, } impl TestServer { @@ -135,7 +135,7 @@ impl TestServer { } } - fn get_conn() -> Addr { + fn get_conn() -> Addr { #[cfg(feature = "alpn")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; diff --git a/src/ws/client.rs b/src/ws/client.rs index 1f35c186..f8366752 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -111,7 +111,7 @@ pub struct Client { http_err: Option, origin: Option, protocols: Option, - conn: Addr, + conn: Addr, max_size: usize, } @@ -122,9 +122,7 @@ impl Client { } /// Create new websocket connection with custom `ClientConnector` - pub fn with_connector>( - uri: S, conn: Addr, - ) -> Client { + pub fn with_connector>(uri: S, conn: Addr) -> Client { let mut cl = Client { request: ClientRequest::build(), err: None, diff --git a/src/ws/context.rs b/src/ws/context.rs index 226d93a1..03af169d 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -3,11 +3,10 @@ use futures::unsync::oneshot; use futures::{Async, Poll}; use smallvec::SmallVec; -use actix::dev::{ContextImpl, SyncEnvelope, ToEnvelope}; +use actix::dev::{ContextImpl, Envelope, ToEnvelope}; use actix::fut::ActorFuture; use actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, - Syn, Unsync, }; use body::{Binary, Body}; @@ -75,16 +74,9 @@ where self.inner.cancel_future(handle) } - #[doc(hidden)] #[inline] - fn unsync_address(&mut self) -> Addr { - self.inner.unsync_address() - } - - #[doc(hidden)] - #[inline] - fn sync_address(&mut self) -> Addr { - self.inner.sync_address() + fn address(&mut self) -> Addr { + self.inner.address() } } @@ -282,14 +274,14 @@ where } } -impl ToEnvelope for WebsocketContext +impl ToEnvelope for WebsocketContext where A: Actor> + Handler, M: Message + Send + 'static, M::Result: Send, { - fn pack(msg: M, tx: Option>) -> SyncEnvelope { - SyncEnvelope::new(msg, tx) + fn pack(msg: M, tx: Option>) -> Envelope { + Envelope::new(msg, tx) } } From fb582a6bca152d854a047743cf529168aa24d132 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 27 May 2018 05:18:37 -0700 Subject: [PATCH 0275/1635] fix connector --- src/client/connector.rs | 42 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 12a36dee..a7d226bd 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -222,8 +222,6 @@ impl SystemService for ClientConnector {} impl Default for ClientConnector { fn default() -> ClientConnector { - let (tx, rx) = mpsc::unbounded(); - #[cfg(all(feature = "alpn"))] { let builder = SslConnector::builder(SslMethod::tls()).unwrap(); @@ -231,6 +229,7 @@ impl Default for ClientConnector { } #[cfg(all(feature = "tls", not(feature = "alpn")))] { + let (tx, rx) = mpsc::unbounded(); let builder = TlsConnector::builder().unwrap(); ClientConnector { stats: ClientConnectorStats::default(), @@ -253,22 +252,25 @@ impl Default for ClientConnector { } #[cfg(not(any(feature = "alpn", feature = "tls")))] - ClientConnector { - stats: ClientConnectorStats::default(), - subscriber: None, - acq_tx: tx, - acq_rx: Some(rx), - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: HashMap::new(), - wait_timeout: None, - paused: Paused::No, + { + let (tx, rx) = mpsc::unbounded(); + ClientConnector { + stats: ClientConnectorStats::default(), + subscriber: None, + acq_tx: tx, + acq_rx: Some(rx), + conn_lifetime: Duration::from_secs(75), + conn_keep_alive: Duration::from_secs(15), + limit: 100, + limit_per_host: 0, + acquired: 0, + acquired_per_host: HashMap::new(), + available: HashMap::new(), + to_close: Vec::new(), + waiters: HashMap::new(), + wait_timeout: None, + paused: Paused::No, + } } } } @@ -324,8 +326,8 @@ impl ClientConnector { connector, stats: ClientConnectorStats::default(), subscriber: None, - pool_tx: tx, - pool_rx: Some(rx), + acq_tx: tx, + acq_rx: Some(rx), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), limit: 100, From 6b43fc7068e4d523fc0592315fcaf3503234c1a0 Mon Sep 17 00:00:00 2001 From: Matthijs Brobbel Date: Tue, 29 May 2018 18:11:10 +0200 Subject: [PATCH 0276/1635] Fix typo in httpresponse.rs --- src/httpresponse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index ec3c9562..af5a20c5 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -86,7 +86,7 @@ impl HttpResponse { HttpResponsePool::with_body(status, body.into()) } - /// Constructs a error response + /// Constructs an error response #[inline] pub fn from_error(error: Error) -> HttpResponse { let mut resp = error.cause().error_response(); From ecd05662c0a6e36ecd840d107c07f6adbcf821d8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 May 2018 10:31:37 -0700 Subject: [PATCH 0277/1635] use new actix system api --- src/client/mod.rs | 26 +++------ src/client/pipeline.rs | 5 +- src/client/request.rs | 13 ++--- src/context.rs | 2 +- src/middleware/session.rs | 22 +++---- src/pipeline.rs | 2 +- src/server/mod.rs | 14 ++--- src/server/srv.rs | 32 +++++----- src/server/worker.rs | 15 +++-- src/test.rs | 120 +++++++++++++++++++++----------------- src/ws/context.rs | 2 +- tests/test_server.rs | 80 ++++++++++++------------- tests/test_ws.rs | 5 +- 13 files changed, 173 insertions(+), 165 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 36a876cc..96033a21 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,16 +1,15 @@ //! Http client api //! -//! ```rust +//! ```rust,ignore //! # extern crate actix; //! # extern crate actix_web; //! # extern crate futures; +//! # extern crate tokio; //! # use futures::Future; //! use actix_web::client; //! //! fn main() { -//! let sys = actix::System::new("test"); -//! -//! actix::Arbiter::spawn({ +//! tokio::run({ //! client::get("http://www.rust-lang.org") // <- Create request builder //! .header("User-Agent", "Actix-web") //! .finish().unwrap() @@ -18,12 +17,9 @@ //! .map_err(|_| ()) //! .and_then(|response| { // <- server http response //! println!("Response: {:?}", response); -//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); //! Ok(()) //! }) //! }); -//! -//! sys.run(); //! } //! ``` mod connector; @@ -60,30 +56,24 @@ impl ResponseError for SendRequestError { /// Create request builder for `GET` requests /// -/// ```rust +/// ```rust,ignore /// # extern crate actix; /// # extern crate actix_web; /// # extern crate futures; -/// # use futures::Future; +/// # use futures::{future, Future}; /// use actix_web::client; /// /// fn main() { -/// let sys = actix::System::new("test"); -/// -/// actix::Arbiter::spawn({ +/// tokio::run( /// client::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() /// .send() // <- Send http request /// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response +/// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); -/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); /// Ok(()) -/// }) -/// }); -/// -/// sys.run(); +/// })); /// } /// ``` pub fn get>(uri: U) -> ClientRequestBuilder { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 3f3d425d..5543aa4c 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -380,7 +380,10 @@ impl Pipeline { match self.timeout.as_mut().unwrap().poll() { Ok(Async::Ready(())) => return Err(SendRequestError::Timeout), Ok(Async::NotReady) => (), - Err(_) => unreachable!(), + Err(e) => { + println!("err: {:?}", e); + return Err(SendRequestError::Timeout); + } } } Ok(()) diff --git a/src/client/request.rs b/src/client/request.rs index adb1b29f..d4baa254 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -24,7 +24,7 @@ use httprequest::HttpRequest; /// An HTTP Client Request /// -/// ```rust +/// ```rust,ignore /// # extern crate actix; /// # extern crate actix_web; /// # extern crate futures; @@ -32,22 +32,17 @@ use httprequest::HttpRequest; /// use actix_web::client::ClientRequest; /// /// fn main() { -/// let sys = actix::System::new("test"); -/// -/// actix::Arbiter::spawn({ +/// tokio::run( /// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() /// .send() // <- Send http request /// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response +/// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); -/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); /// Ok(()) /// }) -/// }); -/// -/// sys.run(); +/// ); /// } /// ``` pub struct ClientRequest { diff --git a/src/context.rs b/src/context.rs index e298287b..76594e7b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -90,7 +90,7 @@ where self.inner.cancel_future(handle) } #[inline] - fn address(&mut self) -> Addr { + fn address(&self) -> Addr { self.inner.address() } } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 3e6e9890..57f42a11 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -50,17 +50,17 @@ //! } //! //! fn main() { -//! let sys = actix::System::new("basic-example"); -//! server::new( -//! || App::new().middleware( -//! SessionStorage::new( // <- create session middleware -//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend -//! .secure(false) -//! ))) -//! .bind("127.0.0.1:59880").unwrap() -//! .start(); -//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); -//! let _ = sys.run(); +//! actix::System::run(|| { +//! server::new( +//! || App::new().middleware( +//! SessionStorage::new( // <- create session middleware +//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend +//! .secure(false) +//! ))) +//! .bind("127.0.0.1:59880").unwrap() +//! .start(); +//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +//! }); //! } //! ``` use std::cell::RefCell; diff --git a/src/pipeline.rs b/src/pipeline.rs index e5152de5..be85dc33 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -805,7 +805,7 @@ mod tests { .unwrap(); let req = HttpRequest::default(); - let mut ctx = HttpContext::new(req.clone(), MyActor); + let ctx = HttpContext::new(req.clone(), MyActor); let addr = ctx.address(); let mut info = PipelineInfo::new(req); info.context = Some(Box::new(ctx)); diff --git a/src/server/mod.rs b/src/server/mod.rs index 36f7cfb1..26876483 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -48,16 +48,16 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// use actix_web::{server, App, HttpResponse}; /// /// fn main() { -/// let sys = actix::System::new("guide"); +/// actix::System::run(|| { /// -/// server::new( -/// || App::new() -/// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090").unwrap() -/// .start(); +/// server::new( +/// || App::new() +/// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) +/// .bind("127.0.0.1:59090").unwrap() +/// .start(); /// /// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); -/// let _ = sys.run(); +/// }); /// } /// ``` pub fn new(factory: F) -> HttpServer diff --git a/src/server/srv.rs b/src/server/srv.rs index 42b9d77d..df6a4b9d 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -410,16 +410,16 @@ impl HttpServer { /// use actix_web::{server, App, HttpResponse}; /// /// fn main() { - /// let sys = actix::System::new("example"); // <- create Actix system + /// // Run actix system, this method actually starts all async processes + /// actix::System::run(|| { /// - /// server::new( - /// || App::new() - /// .resource("/", |r| r.h(|_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") - /// .start(); + /// server::new( + /// || App::new() + /// .resource("/", |r| r.h(|_| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") + /// .start(); /// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); - /// - /// let _ = sys.run(); // <- Run actix system, this method actually starts all async processes + /// }); /// } /// ``` pub fn start(mut self) -> Addr { @@ -496,9 +496,11 @@ impl HttpServer { self.no_signals = false; let _ = thread::spawn(move || { - let sys = System::new("http-server"); - self.start(); - let _ = sys.run(); + System::new("http-server") + .config(|| { + self.start(); + }) + .run(); }).join(); } } @@ -565,7 +567,7 @@ impl HttpServer { /// This method uses only one thread for handling incoming connections. pub fn start_incoming(mut self, stream: S, secure: bool) -> Addr where - S: Stream + 'static, + S: Stream + Send + 'static, T: AsyncRead + AsyncWrite + 'static, { // set server settings @@ -588,6 +590,7 @@ impl HttpServer { })); self }); + if let Some(signals) = signals { signals.do_send(signal::Subscribe(addr.clone().recipient())) } @@ -686,12 +689,13 @@ where type Result = (); fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new( + unimplemented!(); + /*Arbiter::spawn(HttpChannel::new( Rc::clone(self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2, - )); + ));*/ } } diff --git a/src/server/worker.rs b/src/server/worker.rs index 64e4c403..1ca42ccc 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,9 +1,10 @@ -use futures::unsync::oneshot; +use futures::sync::oneshot; use futures::Future; use net2::TcpStreamExt; use slab::Slab; use std::rc::Rc; use std::{net, time}; +use tokio::executor::current_thread; use tokio_reactor::Handle; use tokio_tcp::TcpStream; @@ -180,7 +181,7 @@ impl StreamHandlerType { let io = TcpStream::from_std(msg.io, &Handle::default()) .expect("failed to associate TCP stream"); - Arbiter::spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); + current_thread::spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); } #[cfg(feature = "tls")] StreamHandlerType::Tls(ref acceptor) => { @@ -194,9 +195,9 @@ impl StreamHandlerType { Arbiter::spawn(TlsAcceptorExt::accept_async(acceptor, io).then( move |res| { match res { - Ok(io) => { - Arbiter::spawn(HttpChannel::new(h, io, peer, http2)) - } + Ok(io) => current_thread::spawn(HttpChannel::new( + h, io, peer, http2, + )), Err(err) => { trace!("Error during handling tls connection: {}", err) } @@ -223,7 +224,9 @@ impl StreamHandlerType { } else { false }; - Arbiter::spawn(HttpChannel::new(h, io, peer, http2)); + current_thread::spawn(HttpChannel::new( + h, io, peer, http2, + )); } Err(err) => { trace!("Error during handling tls connection: {}", err) diff --git a/src/test.rs b/src/test.rs index bd2135cc..89fa632e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -5,15 +5,13 @@ use std::str::FromStr; use std::sync::mpsc; use std::{net, thread}; -use actix::{msgs, Actor, Addr, Arbiter, System, SystemRunner}; +use actix::{msgs, Actor, Addr, Arbiter, System}; use cookie::Cookie; use futures::Future; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; -use tokio_reactor::Handle; -use tokio_tcp::TcpListener; #[cfg(feature = "alpn")] use openssl::ssl::SslAcceptor; @@ -63,10 +61,10 @@ use ws; pub struct TestServer { addr: net::SocketAddr, thread: Option>, - system: SystemRunner, server_sys: Addr, ssl: bool, conn: Addr, + rt: Runtime, } impl TestServer { @@ -113,25 +111,31 @@ impl TestServer { let sys = System::new("actix-test-server"); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - let tcp = TcpListener::from_std(tcp, &Handle::default()).unwrap(); - HttpServer::new(factory) - .disable_signals() - .start_incoming(tcp.incoming(), false); + sys.config(move || { + HttpServer::new(factory) + .disable_signals() + .listen(tcp) + .start(); - tx.send((Arbiter::system(), local_addr)).unwrap(); - let _ = sys.run(); + tx.send(( + Arbiter::system(), + local_addr, + TestServer::get_conn(), + Arbiter::registry().clone(), + )).unwrap(); + }).run(); }); - let sys = System::new("actix-test"); - let (server_sys, addr) = rx.recv().unwrap(); + let (server_sys, addr, conn, reg) = rx.recv().unwrap(); + Arbiter::set_system_reg(reg); TestServer { addr, server_sys, + conn, ssl: false, - conn: TestServer::get_conn(), thread: Some(join), - system: sys, + rt: Runtime::new().unwrap(), } } @@ -197,7 +201,7 @@ impl TestServer { where F: Future, { - self.system.run_until_complete(fut) + self.rt.block_on(fut) } /// Connect to websocket server @@ -205,9 +209,8 @@ impl TestServer { &mut self, ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { let url = self.url("/"); - self.system.run_until_complete( - ws::Client::with_connector(url, self.conn.clone()).connect(), - ) + self.rt + .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) } /// Create `GET` request @@ -285,57 +288,64 @@ impl TestServerBuilder { // run server in separate thread let join = thread::spawn(move || { - let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - let tcp = TcpListener::from_std(tcp, &Handle::default()).unwrap(); let state = self.state; - let srv = HttpServer::new(move || { - let mut app = TestApp::new(state()); - config(&mut app); - vec![app] - }).disable_signals(); + System::new("actix-test-server") + .config(move || { + let srv = HttpServer::new(move || { + let mut app = TestApp::new(state()); + config(&mut app); + vec![app] + }).workers(1) + .disable_signals(); - #[cfg(feature = "alpn")] - { - use futures::Stream; - use std::io; - use tokio_openssl::SslAcceptorExt; + tx.send(( + Arbiter::system(), + local_addr, + TestServer::get_conn(), + Arbiter::registry().clone(), + )).unwrap(); - let ssl = self.ssl.take(); - if let Some(ssl) = ssl { - srv.start_incoming( - tcp.incoming().and_then(move |sock| { - ssl.accept_async(sock) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) - }), - false, - ); - } else { - srv.start_incoming(tcp.incoming(), false); - } - } - #[cfg(not(feature = "alpn"))] - { - srv.start_incoming(tcp.incoming(), false); - } + #[cfg(feature = "alpn")] + { + use futures::Stream; + use std::io; + use tokio_openssl::SslAcceptorExt; - tx.send((Arbiter::system(), local_addr)).unwrap(); - let _ = sys.run(); + let ssl = self.ssl.take(); + if let Some(ssl) = ssl { + srv.start_incoming( + tcp.incoming().and_then(move |sock| { + ssl.accept_async(sock).map_err(|e| { + io::Error::new(io::ErrorKind::Other, e) + }) + }), + false, + ); + } else { + srv.start_incoming(tcp.incoming(), false); + } + } + #[cfg(not(feature = "alpn"))] + { + srv.listen(tcp).start(); + } + }) + .run(); }); - let system = System::new("actix-test"); - let (server_sys, addr) = rx.recv().unwrap(); + let (server_sys, addr, conn, reg) = rx.recv().unwrap(); + Arbiter::set_system_reg(reg); TestServer { addr, - server_sys, ssl, - system, - conn: TestServer::get_conn(), + conn, + server_sys, thread: Some(join), + rt: Runtime::new().unwrap(), } } } diff --git a/src/ws/context.rs b/src/ws/context.rs index 03af169d..22323f49 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -75,7 +75,7 @@ where } #[inline] - fn address(&mut self) -> Addr { + fn address(&self) -> Addr { self.inner.address() } } diff --git a/tests/test_server.rs b/tests/test_server.rs index c02642a1..120eef06 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,5 +1,7 @@ extern crate actix; extern crate actix_web; +#[cfg(feature = "brotli")] +extern crate brotli2; extern crate bytes; extern crate flate2; extern crate futures; @@ -10,8 +12,9 @@ extern crate tokio; extern crate tokio_reactor; extern crate tokio_tcp; -#[cfg(feature = "brotli")] -extern crate brotli2; +use std::io::{Read, Write}; +use std::sync::{mpsc, Arc}; +use std::{net, thread, time}; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; @@ -23,10 +26,8 @@ use futures::stream::once; use futures::{Future, Stream}; use h2::client as h2client; use modhttp::Request; +use rand::distributions::Alphanumeric; use rand::Rng; -use std::io::{Read, Write}; -use std::sync::{mpsc, Arc}; -use std::{net, thread, time}; use tokio::executor::current_thread; use tokio::runtime::current_thread::Runtime; use tokio_tcp::TcpStream; @@ -62,28 +63,29 @@ fn test_start() { let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); - thread::spawn(move || { - let sys = System::new("test"); - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); + thread::spawn(|| { + System::run(move || { + let srv = server::new(|| { + vec![App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + })] + }); - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.start(); - let _ = tx.send((addr, srv_addr)); - sys.run(); + let srv = srv.bind("127.0.0.1:0").unwrap(); + let addr = srv.addrs()[0]; + let srv_addr = srv.start(); + let _ = tx.send((addr, srv_addr)); + }); }); let (addr, srv_addr) = rx.recv().unwrap(); - let mut sys = System::new("test-server"); + let _sys = System::new("test-server"); + let mut rt = Runtime::new().unwrap(); { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) .finish() .unwrap(); - let response = sys.run_until_complete(req.send()).unwrap(); + let response = rt.block_on(req.send()).unwrap(); assert!(response.status().is_success()); } @@ -95,7 +97,7 @@ fn test_start() { .timeout(time::Duration::from_millis(200)) .finish() .unwrap(); - assert!(sys.run_until_complete(req.send()).is_err()); + assert!(rt.block_on(req.send()).is_err()); } // resume @@ -105,7 +107,7 @@ fn test_start() { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) .finish() .unwrap(); - let response = sys.run_until_complete(req.send()).unwrap(); + let response = rt.block_on(req.send()).unwrap(); assert!(response.status().is_success()); } } @@ -116,29 +118,29 @@ fn test_shutdown() { let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); - thread::spawn(move || { - let sys = System::new("test"); - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); + thread::spawn(|| { + System::run(move || { + let srv = server::new(|| { + vec![App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + })] + }); - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.shutdown_timeout(1).start(); - let _ = tx.send((addr, srv_addr)); - sys.run(); + let srv = srv.bind("127.0.0.1:0").unwrap(); + let addr = srv.addrs()[0]; + let srv_addr = srv.shutdown_timeout(1).start(); + let _ = tx.send((addr, srv_addr)); + }); }); let (addr, srv_addr) = rx.recv().unwrap(); - let mut sys = System::new("test-server"); - + let _sys = System::new("test-server"); + let mut rt = Runtime::new().unwrap(); { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) .finish() .unwrap(); - let response = sys.run_until_complete(req.send()).unwrap(); + let response = rt.block_on(req.send()).unwrap(); srv_addr.do_send(server::StopServer { graceful: true }); assert!(response.status().is_success()); } @@ -263,7 +265,7 @@ fn test_body_gzip_large() { #[test] fn test_body_gzip_large_random() { let data = rand::thread_rng() - .gen_ascii_chars() + .sample_iter(&Alphanumeric) .take(70_000) .collect::(); let srv_data = Arc::new(data.clone()); @@ -583,7 +585,7 @@ fn test_gzip_encoding_large() { #[test] fn test_reading_gzip_encoding_large_random() { let data = rand::thread_rng() - .gen_ascii_chars() + .sample_iter(&Alphanumeric) .take(60_000) .collect::(); @@ -686,7 +688,7 @@ fn test_reading_deflate_encoding_large() { #[test] fn test_reading_deflate_encoding_large_random() { let data = rand::thread_rng() - .gen_ascii_chars() + .sample_iter(&Alphanumeric) .take(160_000) .collect::(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 0d75bc3f..4f9565dd 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -7,6 +7,7 @@ extern crate rand; use bytes::Bytes; use futures::Stream; +use rand::distributions::Alphanumeric; use rand::Rng; #[cfg(feature = "alpn")] @@ -86,7 +87,7 @@ fn test_close_description() { #[test] fn test_large_text() { let data = rand::thread_rng() - .gen_ascii_chars() + .sample_iter(&Alphanumeric) .take(65_536) .collect::(); @@ -104,7 +105,7 @@ fn test_large_text() { #[test] fn test_large_bin() { let data = rand::thread_rng() - .gen_ascii_chars() + .sample_iter(&Alphanumeric) .take(65_536) .collect::(); From 844be8d9dda82a5395bd9f15ad85f98d3c7262ee Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 May 2018 10:59:24 -0700 Subject: [PATCH 0278/1635] fix ssl test server --- src/server/worker.rs | 4 ++-- src/test.rs | 24 ++++++------------------ tests/test_ws.rs | 2 +- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/server/worker.rs b/src/server/worker.rs index 1ca42ccc..d9cca29f 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -192,7 +192,7 @@ impl StreamHandlerType { let io = TcpStream::from_std(io, &Handle::default()) .expect("failed to associate TCP stream"); - Arbiter::spawn(TlsAcceptorExt::accept_async(acceptor, io).then( + current_thread::spawn(TlsAcceptorExt::accept_async(acceptor, io).then( move |res| { match res { Ok(io) => current_thread::spawn(HttpChannel::new( @@ -213,7 +213,7 @@ impl StreamHandlerType { let io = TcpStream::from_std(io, &Handle::default()) .expect("failed to associate TCP stream"); - Arbiter::spawn(SslAcceptorExt::accept_async(acceptor, io).then( + current_thread::spawn(SslAcceptorExt::accept_async(acceptor, io).then( move |res| { match res { Ok(io) => { diff --git a/src/test.rs b/src/test.rs index 89fa632e..28403625 100644 --- a/src/test.rs +++ b/src/test.rs @@ -14,7 +14,7 @@ use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; #[cfg(feature = "alpn")] -use openssl::ssl::SslAcceptor; +use openssl::ssl::SslAcceptorBuilder; use application::{App, HttpApplication}; use body::Binary; @@ -251,7 +251,7 @@ impl Drop for TestServer { pub struct TestServerBuilder { state: Box S + Sync + Send + 'static>, #[cfg(feature = "alpn")] - ssl: Option, + ssl: Option, } impl TestServerBuilder { @@ -268,7 +268,7 @@ impl TestServerBuilder { #[cfg(feature = "alpn")] /// Create ssl server - pub fn ssl(mut self, ssl: SslAcceptor) -> Self { + pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self { self.ssl = Some(ssl); self } @@ -291,10 +291,9 @@ impl TestServerBuilder { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - let state = self.state; - System::new("actix-test-server") .config(move || { + let state = self.state; let srv = HttpServer::new(move || { let mut app = TestApp::new(state()); config(&mut app); @@ -311,22 +310,11 @@ impl TestServerBuilder { #[cfg(feature = "alpn")] { - use futures::Stream; - use std::io; - use tokio_openssl::SslAcceptorExt; - let ssl = self.ssl.take(); if let Some(ssl) = ssl { - srv.start_incoming( - tcp.incoming().and_then(move |sock| { - ssl.accept_async(sock).map_err(|e| { - io::Error::new(io::ErrorKind::Other, e) - }) - }), - false, - ); + srv.listen_ssl(tcp, ssl).unwrap().start(); } else { - srv.start_incoming(tcp.incoming(), false); + srv.listen(tcp).start(); } } #[cfg(not(feature = "alpn"))] diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 4f9565dd..dd65d4a5 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -227,7 +227,7 @@ fn test_ws_server_ssl() { .set_certificate_chain_file("tests/cert.pem") .unwrap(); - let mut srv = test::TestServer::build().ssl(builder.build()).start(|app| { + let mut srv = test::TestServer::build().ssl(builder).start(|app| { app.handler(|req| { ws::start( req, From a64205e502b08b39e8f5feb15ae3086156bdb979 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 May 2018 16:32:39 -0700 Subject: [PATCH 0279/1635] refactor TransferEncoding; allow to use client api with threaded tokio runtime --- src/body.rs | 41 +---------- src/client/body.rs | 94 ++++++++++++++++++++++++ src/client/mod.rs | 16 ++++- src/client/pipeline.rs | 110 ++++++++++------------------- src/client/request.rs | 29 +++++--- src/client/writer.rs | 111 ++++++++++++++++------------- src/context.rs | 2 +- src/fs.rs | 1 - src/pipeline.rs | 2 +- src/server/encoding.rs | 157 ++++++++++++++++++++++------------------- src/server/h1writer.rs | 8 +-- src/server/h2writer.rs | 8 +-- src/server/shared.rs | 9 --- src/test.rs | 12 +--- src/ws/client.rs | 8 +-- src/ws/context.rs | 3 +- tests/test_client.rs | 5 +- tests/test_server.rs | 18 ++--- 18 files changed, 344 insertions(+), 290 deletions(-) create mode 100644 src/client/body.rs diff --git a/src/body.rs b/src/body.rs index 5ce0d129..3838d8d0 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,6 +1,5 @@ use bytes::{Bytes, BytesMut}; use futures::Stream; -use std::rc::Rc; use std::sync::Arc; use std::{fmt, mem}; @@ -35,10 +34,8 @@ pub enum Binary { /// Static slice Slice(&'static [u8]), /// Shared string body - SharedString(Rc), - /// Shared string body #[doc(hidden)] - ArcSharedString(Arc), + SharedString(Arc), /// Shared vec body SharedVec(Arc>), } @@ -140,7 +137,6 @@ impl Binary { Binary::Bytes(ref bytes) => bytes.len(), Binary::Slice(slice) => slice.len(), Binary::SharedString(ref s) => s.len(), - Binary::ArcSharedString(ref s) => s.len(), Binary::SharedVec(ref s) => s.len(), } } @@ -162,7 +158,6 @@ impl Clone for Binary { Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()), Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)), Binary::SharedString(ref s) => Binary::SharedString(s.clone()), - Binary::ArcSharedString(ref s) => Binary::ArcSharedString(s.clone()), Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()), } } @@ -174,7 +169,6 @@ impl Into for Binary { Binary::Bytes(bytes) => bytes, Binary::Slice(slice) => Bytes::from(slice), Binary::SharedString(s) => Bytes::from(s.as_str()), - Binary::ArcSharedString(s) => Bytes::from(s.as_str()), Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())), } } @@ -222,27 +216,15 @@ impl From for Binary { } } -impl From> for Binary { - fn from(body: Rc) -> Binary { - Binary::SharedString(body) - } -} - -impl<'a> From<&'a Rc> for Binary { - fn from(body: &'a Rc) -> Binary { - Binary::SharedString(Rc::clone(body)) - } -} - impl From> for Binary { fn from(body: Arc) -> Binary { - Binary::ArcSharedString(body) + Binary::SharedString(body) } } impl<'a> From<&'a Arc> for Binary { fn from(body: &'a Arc) -> Binary { - Binary::ArcSharedString(Arc::clone(body)) + Binary::SharedString(Arc::clone(body)) } } @@ -265,7 +247,6 @@ impl AsRef<[u8]> for Binary { Binary::Bytes(ref bytes) => bytes.as_ref(), Binary::Slice(slice) => slice, Binary::SharedString(ref s) => s.as_bytes(), - Binary::ArcSharedString(ref s) => s.as_bytes(), Binary::SharedVec(ref s) => s.as_ref().as_ref(), } } @@ -324,22 +305,6 @@ mod tests { assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test"); } - #[test] - fn test_ref_string() { - let b = Rc::new("test".to_owned()); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), b"test"); - } - - #[test] - fn test_rc_string() { - let b = Rc::new("test".to_owned()); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), b"test"); - } - #[test] fn test_arc_string() { let b = Arc::new("test".to_owned()); diff --git a/src/client/body.rs b/src/client/body.rs new file mode 100644 index 00000000..c79d7ad5 --- /dev/null +++ b/src/client/body.rs @@ -0,0 +1,94 @@ +use std::fmt; + +use bytes::Bytes; +use futures::Stream; + +use body::Binary; +use context::ActorHttpContext; +use error::Error; + +/// Type represent streaming body +pub type ClientBodyStream = Box + Send>; + +/// Represents various types of http message body. +pub enum ClientBody { + /// Empty response. `Content-Length` header is set to `0` + Empty, + /// Specific response body. + Binary(Binary), + /// Unspecified streaming response. Developer is responsible for setting + /// right `Content-Length` or `Transfer-Encoding` headers. + Streaming(ClientBodyStream), + /// Special body type for actor response. + Actor(Box), +} + +impl ClientBody { + /// Does this body streaming. + #[inline] + pub fn is_streaming(&self) -> bool { + match *self { + ClientBody::Streaming(_) | ClientBody::Actor(_) => true, + _ => false, + } + } + + /// Is this binary body. + #[inline] + pub fn is_binary(&self) -> bool { + match *self { + ClientBody::Binary(_) => true, + _ => false, + } + } + + /// Is this binary empy. + #[inline] + pub fn is_empty(&self) -> bool { + match *self { + ClientBody::Empty => true, + _ => false, + } + } + + /// Create body from slice (copy) + pub fn from_slice(s: &[u8]) -> ClientBody { + ClientBody::Binary(Binary::Bytes(Bytes::from(s))) + } +} + +impl PartialEq for ClientBody { + fn eq(&self, other: &ClientBody) -> bool { + match *self { + ClientBody::Empty => match *other { + ClientBody::Empty => true, + _ => false, + }, + ClientBody::Binary(ref b) => match *other { + ClientBody::Binary(ref b2) => b == b2, + _ => false, + }, + ClientBody::Streaming(_) | ClientBody::Actor(_) => false, + } + } +} + +impl fmt::Debug for ClientBody { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ClientBody::Empty => write!(f, "ClientBody::Empty"), + ClientBody::Binary(ref b) => write!(f, "ClientBody::Binary({:?})", b), + ClientBody::Streaming(_) => write!(f, "ClientBody::Streaming(_)"), + ClientBody::Actor(_) => write!(f, "ClientBody::Actor(_)"), + } + } +} + +impl From for ClientBody +where + T: Into, +{ + fn from(b: T) -> ClientBody { + ClientBody::Binary(b.into()) + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index 96033a21..8aded011 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,11 +1,12 @@ //! Http client api //! -//! ```rust,ignore +//! ```rust //! # extern crate actix; //! # extern crate actix_web; //! # extern crate futures; //! # extern crate tokio; //! # use futures::Future; +//! # use std::process; //! use actix_web::client; //! //! fn main() { @@ -17,11 +18,14 @@ //! .map_err(|_| ()) //! .and_then(|response| { // <- server http response //! println!("Response: {:?}", response); +//! # process::exit(0); //! Ok(()) //! }) //! }); //! } //! ``` + +mod body; mod connector; mod parser; mod pipeline; @@ -29,6 +33,7 @@ mod request; mod response; mod writer; +pub use self::body::{ClientBody, ClientBodyStream}; pub use self::connector::{ ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection, Pause, Resume, @@ -56,11 +61,15 @@ impl ResponseError for SendRequestError { /// Create request builder for `GET` requests /// -/// ```rust,ignore +/// +/// ```rust /// # extern crate actix; /// # extern crate actix_web; /// # extern crate futures; -/// # use futures::{future, Future}; +/// # extern crate tokio; +/// # extern crate env_logger; +/// # use futures::Future; +/// # use std::process; /// use actix_web::client; /// /// fn main() { @@ -72,6 +81,7 @@ impl ResponseError for SendRequestError { /// .map_err(|_| ()) /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); +/// # process::exit(0); /// Ok(()) /// })); /// } diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 5543aa4c..a2105ecb 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -1,5 +1,5 @@ use bytes::{Bytes, BytesMut}; -use futures::unsync::oneshot; +use futures::sync::oneshot; use futures::{Async, Future, Poll}; use http::header::CONTENT_ENCODING; use std::time::{Duration, Instant}; @@ -8,18 +8,16 @@ use tokio_timer::Delay; use actix::prelude::*; -use super::HttpClientWriter; -use super::{ClientConnector, ClientConnectorError, Connect, Connection}; -use super::{ClientRequest, ClientResponse}; -use super::{HttpResponseParser, HttpResponseParserError}; -use body::{Body, BodyStream}; -use context::{ActorHttpContext, Frame}; +use super::{ + ClientBody, ClientBodyStream, ClientConnector, ClientConnectorError, ClientRequest, + ClientResponse, Connect, Connection, HttpClientWriter, HttpResponseParser, + HttpResponseParserError, +}; use error::Error; use error::PayloadError; use header::ContentEncoding; use httpmessage::HttpMessage; use server::encoding::PayloadStream; -use server::shared::SharedBytes; use server::WriterState; /// A set of errors that can occur during request sending and response reading @@ -68,7 +66,7 @@ enum State { pub struct SendRequest { req: ClientRequest, state: State, - conn: Addr, + conn: Option>, conn_timeout: Duration, wait_timeout: Duration, timeout: Option, @@ -76,7 +74,14 @@ pub struct SendRequest { impl SendRequest { pub(crate) fn new(req: ClientRequest) -> SendRequest { - SendRequest::with_connector(req, ClientConnector::from_registry()) + SendRequest { + req, + conn: None, + state: State::New, + timeout: None, + wait_timeout: Duration::from_secs(5), + conn_timeout: Duration::from_secs(1), + } } pub(crate) fn with_connector( @@ -84,7 +89,7 @@ impl SendRequest { ) -> SendRequest { SendRequest { req, - conn, + conn: Some(conn), state: State::New, timeout: None, wait_timeout: Duration::from_secs(5), @@ -96,7 +101,7 @@ impl SendRequest { SendRequest { req, state: State::Connection(conn), - conn: ClientConnector::from_registry(), + conn: None, timeout: None, wait_timeout: Duration::from_secs(5), conn_timeout: Duration::from_secs(1), @@ -142,7 +147,12 @@ impl Future for SendRequest { match state { State::New => { - self.state = State::Connect(self.conn.send(Connect { + let conn = if let Some(conn) = self.conn.take() { + conn + } else { + ClientConnector::from_registry() + }; + self.state = State::Connect(conn.send(Connect { uri: self.req.uri().clone(), wait_timeout: self.wait_timeout, conn_timeout: self.conn_timeout, @@ -160,16 +170,16 @@ impl Future for SendRequest { Err(_) => { return Err(SendRequestError::Connector( ClientConnectorError::Disconnected, - )) + )); } }, State::Connection(conn) => { - let mut writer = HttpClientWriter::new(SharedBytes::default()); + let mut writer = HttpClientWriter::new(); writer.start(&mut self.req)?; - let body = match self.req.replace_body(Body::Empty) { - Body::Streaming(stream) => IoBody::Payload(stream), - Body::Actor(ctx) => IoBody::Actor(ctx), + let body = match self.req.replace_body(ClientBody::Empty) { + ClientBody::Streaming(stream) => IoBody::Payload(stream), + ClientBody::Actor(_) => panic!("Client actor is not supported"), _ => IoBody::Done, }; @@ -208,7 +218,9 @@ impl Future for SendRequest { self.state = State::Send(pl); return Ok(Async::NotReady); } - Err(err) => return Err(SendRequestError::ParseError(err)), + Err(err) => { + return Err(SendRequestError::ParseError(err)); + } } } State::None => unreachable!(), @@ -233,8 +245,7 @@ pub(crate) struct Pipeline { } enum IoBody { - Payload(BodyStream), - Actor(Box), + Payload(ClientBodyStream), Done, } @@ -380,10 +391,7 @@ impl Pipeline { match self.timeout.as_mut().unwrap().poll() { Ok(Async::Ready(())) => return Err(SendRequestError::Timeout), Ok(Async::NotReady) => (), - Err(e) => { - println!("err: {:?}", e); - return Err(SendRequestError::Timeout); - } + Err(_) => return Err(SendRequestError::Timeout), } } Ok(()) @@ -397,66 +405,24 @@ impl Pipeline { let mut done = false; if self.drain.is_none() && self.write_state != RunningState::Paused { - 'outter: loop { + loop { let result = match mem::replace(&mut self.body, IoBody::Done) { - IoBody::Payload(mut body) => match body.poll()? { + IoBody::Payload(mut stream) => match stream.poll()? { Async::Ready(None) => { self.writer.write_eof()?; self.body_completed = true; break; } Async::Ready(Some(chunk)) => { - self.body = IoBody::Payload(body); - self.writer.write(chunk.into())? + self.body = IoBody::Payload(stream); + self.writer.write(chunk.as_ref())? } Async::NotReady => { done = true; - self.body = IoBody::Payload(body); + self.body = IoBody::Payload(stream); break; } }, - IoBody::Actor(mut ctx) => { - if self.disconnected { - ctx.disconnected(); - } - match ctx.poll()? { - Async::Ready(Some(vec)) => { - if vec.is_empty() { - self.body = IoBody::Actor(ctx); - break; - } - let mut res = None; - for frame in vec { - match frame { - Frame::Chunk(None) => { - self.body_completed = true; - self.writer.write_eof()?; - break 'outter; - } - Frame::Chunk(Some(chunk)) => { - res = Some(self.writer.write(chunk)?) - } - Frame::Drain(fut) => self.drain = Some(fut), - } - } - self.body = IoBody::Actor(ctx); - if self.drain.is_some() { - self.write_state.resume(); - break; - } - res.unwrap() - } - Async::Ready(None) => { - done = true; - break; - } - Async::NotReady => { - done = true; - self.body = IoBody::Actor(ctx); - break; - } - } - } IoBody::Done => { self.body_completed = true; done = true; diff --git a/src/client/request.rs b/src/client/request.rs index d4baa254..97b97e01 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -12,9 +12,9 @@ use serde::Serialize; use serde_json; use url::Url; +use super::body::ClientBody; use super::connector::{ClientConnector, Connection}; use super::pipeline::SendRequest; -use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; use http::header::{self, HeaderName, HeaderValue}; @@ -24,11 +24,13 @@ use httprequest::HttpRequest; /// An HTTP Client Request /// -/// ```rust,ignore +/// ```rust /// # extern crate actix; /// # extern crate actix_web; /// # extern crate futures; +/// # extern crate tokio; /// # use futures::Future; +/// # use std::process; /// use actix_web::client::ClientRequest; /// /// fn main() { @@ -40,6 +42,7 @@ use httprequest::HttpRequest; /// .map_err(|_| ()) /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); +/// # process::exit(0); /// Ok(()) /// }) /// ); @@ -50,7 +53,7 @@ pub struct ClientRequest { method: Method, version: Version, headers: HeaderMap, - body: Body, + body: ClientBody, chunked: bool, upgrade: bool, timeout: Option, @@ -73,7 +76,7 @@ impl Default for ClientRequest { method: Method::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - body: Body::Empty, + body: ClientBody::Empty, chunked: false, upgrade: false, timeout: None, @@ -217,17 +220,17 @@ impl ClientRequest { /// Get body of this response #[inline] - pub fn body(&self) -> &Body { + pub fn body(&self) -> &ClientBody { &self.body } /// Set a body - pub fn set_body>(&mut self, body: B) { + pub fn set_body>(&mut self, body: B) { self.body = body.into(); } /// Extract body, replace it with `Empty` - pub(crate) fn replace_body(&mut self, body: Body) -> Body { + pub(crate) fn replace_body(&mut self, body: ClientBody) -> ClientBody { mem::replace(&mut self.body, body) } @@ -578,7 +581,9 @@ impl ClientRequestBuilder { /// Set a body and generate `ClientRequest`. /// /// `ClientRequestBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> Result { + pub fn body>( + &mut self, body: B, + ) -> Result { if let Some(e) = self.err.take() { return Err(e.into()); } @@ -644,17 +649,19 @@ impl ClientRequestBuilder { /// `ClientRequestBuilder` can not be used after this call. pub fn streaming(&mut self, stream: S) -> Result where - S: Stream + 'static, + S: Stream + Send + 'static, E: Into, { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) + self.body(ClientBody::Streaming(Box::new( + stream.map_err(|e| e.into()), + ))) } /// Set an empty body and generate `ClientRequest` /// /// `ClientRequestBuilder` can not be used after this call. pub fn finish(&mut self) -> Result { - self.body(Body::Empty) + self.body(ClientBody::Empty) } /// This method construct new `ClientRequestBuilder` diff --git a/src/client/writer.rs b/src/client/writer.rs index addc0324..dfbce1f3 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -19,13 +19,12 @@ use http::{HttpTryFrom, Version}; use time::{self, Duration}; use tokio_io::AsyncWrite; -use body::{Binary, Body}; +use body::Binary; use header::ContentEncoding; use server::encoding::{ContentEncoder, TransferEncoding}; -use server::shared::SharedBytes; use server::WriterState; -use client::ClientRequest; +use client::{ClientBody, ClientRequest}; const AVERAGE_HEADER_SIZE: usize = 30; @@ -42,20 +41,20 @@ pub(crate) struct HttpClientWriter { flags: Flags, written: u64, headers_size: u32, - buffer: SharedBytes, + buffer: Box, buffer_capacity: usize, encoder: ContentEncoder, } impl HttpClientWriter { - pub fn new(buffer: SharedBytes) -> HttpClientWriter { - let encoder = ContentEncoder::Identity(TransferEncoding::eof(buffer.clone())); + pub fn new() -> HttpClientWriter { + let encoder = ContentEncoder::Identity(TransferEncoding::eof()); HttpClientWriter { flags: Flags::empty(), written: 0, headers_size: 0, buffer_capacity: 0, - buffer, + buffer: Box::new(BytesMut::new()), encoder, } } @@ -98,12 +97,23 @@ impl HttpClientWriter { } } +pub struct Writer<'a>(pub &'a mut BytesMut); + +impl<'a> io::Write for Writer<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + impl HttpClientWriter { pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> { // prepare task self.flags.insert(Flags::STARTED); - self.encoder = content_encoder(self.buffer.clone(), msg); - + self.encoder = content_encoder(self.buffer.as_mut(), msg); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); } @@ -112,7 +122,7 @@ impl HttpClientWriter { { // status line writeln!( - self.buffer, + Writer(&mut self.buffer), "{} {} {:?}\r", msg.method(), msg.uri() @@ -120,40 +130,41 @@ impl HttpClientWriter { .map(|u| u.as_str()) .unwrap_or("/"), msg.version() - )?; + ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; // write headers - let mut buffer = self.buffer.get_mut(); - if let Body::Binary(ref bytes) = *msg.body() { - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); + if let ClientBody::Binary(ref bytes) = *msg.body() { + self.buffer + .reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); } else { - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); + self.buffer + .reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); } 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"); + self.buffer.reserve(k.len() + v.len() + 4); + self.buffer.put_slice(k); + self.buffer.put_slice(b": "); + self.buffer.put_slice(v); + self.buffer.put_slice(b"\r\n"); } // set date header if !msg.headers().contains_key(DATE) { - buffer.extend_from_slice(b"date: "); - set_date(&mut buffer); - buffer.extend_from_slice(b"\r\n\r\n"); + self.buffer.extend_from_slice(b"date: "); + set_date(&mut self.buffer); + self.buffer.extend_from_slice(b"\r\n\r\n"); } else { - buffer.extend_from_slice(b"\r\n"); + self.buffer.extend_from_slice(b"\r\n"); } - self.headers_size = buffer.len() as u32; + self.headers_size = self.buffer.len() as u32; if msg.body().is_binary() { - if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { + if let ClientBody::Binary(bytes) = msg.replace_body(ClientBody::Empty) { self.written += bytes.len() as u64; - self.encoder.write(bytes)?; + self.encoder.write(bytes.as_ref())?; } } else { self.buffer_capacity = msg.write_buffer_capacity(); @@ -162,7 +173,7 @@ impl HttpClientWriter { Ok(()) } - pub fn write(&mut self, payload: Binary) -> io::Result { + pub fn write(&mut self, payload: &[u8]) -> io::Result { self.written += payload.len() as u64; if !self.flags.contains(Flags::DISCONNECTED) { if self.flags.contains(Flags::UPGRADE) { @@ -210,20 +221,21 @@ impl HttpClientWriter { } } -fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder { +fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncoder { let version = req.version(); - let mut body = req.replace_body(Body::Empty); + let mut body = req.replace_body(ClientBody::Empty); let mut encoding = req.content_encoding(); - let transfer = match body { - Body::Empty => { + let mut transfer = match body { + ClientBody::Empty => { req.headers_mut().remove(CONTENT_LENGTH); - TransferEncoding::length(0, buf) + TransferEncoding::length(0) } - Body::Binary(ref mut bytes) => { + ClientBody::Binary(ref mut bytes) => { if encoding.is_compression() { - let tmp = SharedBytes::default(); - let transfer = TransferEncoding::eof(tmp.clone()); + let mut tmp = BytesMut::new(); + let mut transfer = TransferEncoding::eof(); + transfer.set_buffer(&mut tmp); let mut enc = match encoding { #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate( @@ -242,7 +254,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder ContentEncoding::Auto => unreachable!(), }; // TODO return error! - let _ = enc.write(bytes.clone()); + let _ = enc.write(bytes.as_ref()); let _ = enc.write_eof(); *bytes = Binary::from(tmp.take()); @@ -256,9 +268,9 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder let _ = write!(b, "{}", bytes.len()); req.headers_mut() .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof(buf) + TransferEncoding::eof() } - Body::Streaming(_) | Body::Actor(_) => { + ClientBody::Streaming(_) | ClientBody::Actor(_) => { if req.upgrade() { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); @@ -270,9 +282,9 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder encoding = ContentEncoding::Identity; req.headers_mut().remove(CONTENT_ENCODING); } - TransferEncoding::eof(buf) + TransferEncoding::eof() } else { - streaming_encoding(buf, version, req) + streaming_encoding(version, req) } } }; @@ -283,6 +295,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder HeaderValue::from_static(encoding.as_str()), ); } + transfer.set_buffer(buf); req.replace_body(body); match encoding { @@ -303,19 +316,17 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder } } -fn streaming_encoding( - buf: SharedBytes, version: Version, req: &mut ClientRequest, -) -> TransferEncoding { +fn streaming_encoding(version: Version, req: &mut ClientRequest) -> TransferEncoding { if req.chunked() { // Enable transfer encoding req.headers_mut().remove(CONTENT_LENGTH); if version == Version::HTTP_2 { req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) + TransferEncoding::eof() } else { req.headers_mut() .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) + TransferEncoding::chunked() } } else { // if Content-Length is specified, then use it as length hint @@ -338,9 +349,9 @@ fn streaming_encoding( if !chunked { if let Some(len) = len { - TransferEncoding::length(len, buf) + TransferEncoding::length(len) } else { - TransferEncoding::eof(buf) + TransferEncoding::eof() } } else { // Enable transfer encoding @@ -348,11 +359,11 @@ fn streaming_encoding( Version::HTTP_11 => { req.headers_mut() .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) + TransferEncoding::chunked() } _ => { req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) + TransferEncoding::eof() } } } diff --git a/src/context.rs b/src/context.rs index 76594e7b..b40b4bbb 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,5 +1,5 @@ +use futures::sync::oneshot; use futures::sync::oneshot::Sender; -use futures::unsync::oneshot; use futures::{Async, Future, Poll}; use smallvec::SmallVec; use std::marker::PhantomData; diff --git a/src/fs.rs b/src/fs.rs index a8f66ded..8a362107 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -801,7 +801,6 @@ mod tests { .finish() .unwrap(); let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); // Invalid range header diff --git a/src/pipeline.rs b/src/pipeline.rs index be85dc33..289c5fcb 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::rc::Rc; use std::{io, mem}; -use futures::unsync::oneshot; +use futures::sync::oneshot; use futures::{Async, Future, Poll, Stream}; use log::Level::Debug; diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 4379a4ba..6d814482 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -1,7 +1,7 @@ use std::fmt::Write as FmtWrite; use std::io::{Read, Write}; use std::str::FromStr; -use std::{cmp, io, mem}; +use std::{cmp, io, mem, ptr}; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; @@ -25,8 +25,6 @@ use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use payload::{PayloadSender, PayloadStatus, PayloadWriter}; -use super::shared::SharedBytes; - pub(crate) enum PayloadType { Sender(PayloadSender), Encoding(Box), @@ -381,12 +379,12 @@ pub(crate) enum ContentEncoder { } impl ContentEncoder { - pub fn empty(bytes: SharedBytes) -> ContentEncoder { - ContentEncoder::Identity(TransferEncoding::eof(bytes)) + pub fn empty() -> ContentEncoder { + ContentEncoder::Identity(TransferEncoding::eof()) } pub fn for_server( - buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse, + buf: &mut BytesMut, req: &HttpInnerMessage, resp: &mut HttpResponse, response_encoding: ContentEncoding, ) -> ContentEncoder { let version = resp.version().unwrap_or_else(|| req.version); @@ -441,7 +439,7 @@ impl ContentEncoder { if req.method != Method::HEAD { resp.headers_mut().remove(CONTENT_LENGTH); } - TransferEncoding::length(0, buf) + TransferEncoding::length(0) } &Body::Binary(_) => { #[cfg(any(feature = "brotli", feature = "flate2"))] @@ -449,8 +447,9 @@ impl ContentEncoder { if !(encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto) { - let tmp = SharedBytes::default(); - let transfer = TransferEncoding::eof(tmp.clone()); + let mut tmp = BytesMut::default(); + let mut transfer = TransferEncoding::eof(); + transfer.set_buffer(&mut tmp); let mut enc = match encoding { #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate( @@ -472,7 +471,7 @@ impl ContentEncoder { let bin = resp.replace_body(Body::Empty).binary(); // TODO return error! - let _ = enc.write(bin); + let _ = enc.write(bin.as_ref()); let _ = enc.write_eof(); let body = tmp.take(); len = body.len(); @@ -492,7 +491,7 @@ impl ContentEncoder { } else { // resp.headers_mut().remove(CONTENT_LENGTH); } - TransferEncoding::eof(buf) + TransferEncoding::eof() } &Body::Streaming(_) | &Body::Actor(_) => { if resp.upgrade() { @@ -503,14 +502,14 @@ impl ContentEncoder { encoding = ContentEncoding::Identity; resp.headers_mut().remove(CONTENT_ENCODING); } - TransferEncoding::eof(buf) + TransferEncoding::eof() } else { if !(encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto) { resp.headers_mut().remove(CONTENT_LENGTH); } - ContentEncoder::streaming_encoding(buf, version, resp) + ContentEncoder::streaming_encoding(version, resp) } } }; @@ -519,6 +518,7 @@ impl ContentEncoder { resp.set_body(Body::Empty); transfer.kind = TransferEncodingKind::Length(0); } + transfer.set_buffer(buf); match encoding { #[cfg(feature = "flate2")] @@ -539,7 +539,7 @@ impl ContentEncoder { } fn streaming_encoding( - buf: SharedBytes, version: Version, resp: &mut HttpResponse, + version: Version, resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { @@ -547,14 +547,14 @@ impl ContentEncoder { resp.headers_mut().remove(CONTENT_LENGTH); if version == Version::HTTP_2 { resp.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) + TransferEncoding::eof() } else { resp.headers_mut() .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) + TransferEncoding::chunked() } } - Some(false) => TransferEncoding::eof(buf), + Some(false) => TransferEncoding::eof(), None => { // if Content-Length is specified, then use it as length hint let (len, chunked) = @@ -577,9 +577,9 @@ impl ContentEncoder { if !chunked { if let Some(len) = len { - TransferEncoding::length(len, buf) + TransferEncoding::length(len) } else { - TransferEncoding::eof(buf) + TransferEncoding::eof() } } else { // Enable transfer encoding @@ -589,11 +589,11 @@ impl ContentEncoder { TRANSFER_ENCODING, HeaderValue::from_static("chunked"), ); - TransferEncoding::chunked(buf) + TransferEncoding::chunked() } _ => { resp.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) + TransferEncoding::eof() } } } @@ -619,10 +619,8 @@ impl ContentEncoder { #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[inline(always)] pub fn write_eof(&mut self) -> Result { - let encoder = mem::replace( - self, - ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())), - ); + let encoder = + mem::replace(self, ContentEncoder::Identity(TransferEncoding::eof())); match encoder { #[cfg(feature = "brotli")] @@ -662,38 +660,32 @@ impl ContentEncoder { #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[inline(always)] - pub fn write(&mut self, data: Binary) -> Result<(), io::Error> { + pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => { - match encoder.write_all(data.as_ref()) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding br encoding: {}", err); - Err(err) - } + ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(()), + Err(err) => { + trace!("Error decoding br encoding: {}", err); + Err(err) } - } + }, #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => { - match encoder.write_all(data.as_ref()) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding gzip encoding: {}", err); - Err(err) - } + ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(()), + Err(err) => { + trace!("Error decoding gzip encoding: {}", err); + Err(err) } - } + }, #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => { - match encoder.write_all(data.as_ref()) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding deflate encoding: {}", err); - Err(err) - } + ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(()), + Err(err) => { + trace!("Error decoding deflate encoding: {}", err); + Err(err) } - } + }, ContentEncoder::Identity(ref mut encoder) => { encoder.encode(data)?; Ok(()) @@ -705,10 +697,12 @@ impl ContentEncoder { /// Encoders to handle different Transfer-Encodings. #[derive(Debug, Clone)] pub(crate) struct TransferEncoding { + buf: *mut BytesMut, kind: TransferEncodingKind, - buffer: SharedBytes, } +unsafe impl Send for TransferEncoding {} + #[derive(Debug, PartialEq, Clone)] enum TransferEncodingKind { /// An Encoder for when Transfer-Encoding includes `chunked`. @@ -724,27 +718,31 @@ enum TransferEncodingKind { } impl TransferEncoding { + pub(crate) fn set_buffer(&mut self, buf: *mut BytesMut) { + self.buf = buf; + } + #[inline] - pub fn eof(bytes: SharedBytes) -> TransferEncoding { + pub fn eof() -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Eof, - buffer: bytes, + buf: ptr::null_mut(), } } #[inline] - pub fn chunked(bytes: SharedBytes) -> TransferEncoding { + pub fn chunked() -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Chunked(false), - buffer: bytes, + buf: ptr::null_mut(), } } #[inline] - pub fn length(len: u64, bytes: SharedBytes) -> TransferEncoding { + pub fn length(len: u64) -> TransferEncoding { TransferEncoding { kind: TransferEncodingKind::Length(len), - buffer: bytes, + buf: ptr::null_mut(), } } @@ -759,11 +757,13 @@ impl TransferEncoding { /// Encode message. Return `EOF` state of encoder #[inline] - pub fn encode(&mut self, mut msg: Binary) -> io::Result { + pub fn encode(&mut self, msg: &[u8]) -> io::Result { match self.kind { TransferEncodingKind::Eof => { let eof = msg.is_empty(); - self.buffer.extend(msg); + debug_assert!(!self.buf.is_null()); + let buf = unsafe { &mut *self.buf }; + buf.extend(msg); Ok(eof) } TransferEncodingKind::Chunked(ref mut eof) => { @@ -773,15 +773,20 @@ impl TransferEncoding { if msg.is_empty() { *eof = true; - self.buffer.extend_from_slice(b"0\r\n\r\n"); + debug_assert!(!self.buf.is_null()); + let buf = unsafe { &mut *self.buf }; + buf.extend_from_slice(b"0\r\n\r\n"); } else { let mut buf = BytesMut::new(); writeln!(&mut buf, "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - self.buffer.reserve(buf.len() + msg.len() + 2); - self.buffer.extend(buf.into()); - self.buffer.extend(msg); - self.buffer.extend_from_slice(b"\r\n"); + + debug_assert!(!self.buf.is_null()); + let b = unsafe { &mut *self.buf }; + b.reserve(buf.len() + msg.len() + 2); + b.extend_from_slice(buf.as_ref()); + b.extend_from_slice(msg); + b.extend_from_slice(b"\r\n"); } Ok(*eof) } @@ -791,7 +796,9 @@ impl TransferEncoding { return Ok(*remaining == 0); } let len = cmp::min(*remaining, msg.len() as u64); - self.buffer.extend(msg.take().split_to(len as usize).into()); + + debug_assert!(!self.buf.is_null()); + unsafe { &mut *self.buf }.extend(&msg[..len as usize]); *remaining -= len as u64; Ok(*remaining == 0) @@ -811,7 +818,10 @@ impl TransferEncoding { TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; - self.buffer.extend_from_slice(b"0\r\n\r\n"); + + debug_assert!(!self.buf.is_null()); + let buf = unsafe { &mut *self.buf }; + buf.extend_from_slice(b"0\r\n\r\n"); } true } @@ -822,7 +832,7 @@ impl TransferEncoding { impl io::Write for TransferEncoding { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { - self.encode(Binary::from_slice(buf))?; + self.encode(buf)?; Ok(buf.len()) } @@ -904,12 +914,15 @@ mod tests { #[test] fn test_chunked_te() { - let bytes = SharedBytes::default(); - let mut enc = TransferEncoding::chunked(bytes.clone()); - assert!(!enc.encode(Binary::from(b"test".as_ref())).ok().unwrap()); - assert!(enc.encode(Binary::from(b"".as_ref())).ok().unwrap()); + let mut bytes = BytesMut::new(); + let mut enc = TransferEncoding::chunked(); + { + enc.set_buffer(&mut bytes); + assert!(!enc.encode(b"test").ok().unwrap()); + assert!(enc.encode(b"").ok().unwrap()); + } assert_eq!( - bytes.get_mut().take().freeze(), + bytes.take().freeze(), Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") ); } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 648a164f..a144a2ff 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -46,7 +46,7 @@ impl H1Writer { ) -> H1Writer { H1Writer { flags: Flags::KEEPALIVE, - encoder: ContentEncoder::empty(buf.clone()), + encoder: ContentEncoder::empty(), written: 0, headers_size: 0, buffer: buf, @@ -116,7 +116,7 @@ impl Writer for H1Writer { ) -> io::Result { // prepare task self.encoder = - ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); + ContentEncoder::for_server(self.buffer.get_mut(), req, msg, encoding); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { self.flags = Flags::STARTED | Flags::KEEPALIVE; } else { @@ -223,7 +223,7 @@ impl Writer for H1Writer { if let Body::Binary(bytes) = body { self.written = bytes.len() as u64; - self.encoder.write(bytes)?; + self.encoder.write(bytes.as_ref())?; } else { // capacity, makes sense only for streaming or actor self.buffer_capacity = msg.write_buffer_capacity(); @@ -251,7 +251,7 @@ impl Writer for H1Writer { } } else { // TODO: add warning, write after EOF - self.encoder.write(payload)?; + self.encoder.write(payload.as_ref())?; } } else { // could be response to EXCEPT header diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 5fc13154..78a1ce18 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -51,7 +51,7 @@ impl H2Writer { respond, settings, stream: None, - encoder: ContentEncoder::empty(buf.clone()), + encoder: ContentEncoder::empty(), flags: Flags::empty(), written: 0, buffer: buf, @@ -88,7 +88,7 @@ impl Writer for H2Writer { // prepare response self.flags.insert(Flags::STARTED); self.encoder = - ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); + ContentEncoder::for_server(self.buffer.get_mut(), req, msg, encoding); if let Body::Empty = *msg.body() { self.flags.insert(Flags::EOF); } @@ -143,7 +143,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)?; + self.encoder.write(bytes.as_ref())?; if let Some(ref mut stream) = self.stream { self.flags.insert(Flags::RESERVED); stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); @@ -162,7 +162,7 @@ impl Writer for H2Writer { if !self.flags.contains(Flags::DISCONNECTED) { if self.flags.contains(Flags::STARTED) { // TODO: add warning, write after EOF - self.encoder.write(payload)?; + self.encoder.write(payload.as_ref())?; } else { // might be response for EXCEPT self.buffer.extend_from_slice(payload.as_ref()) diff --git a/src/server/shared.rs b/src/server/shared.rs index 2d7e285b..54f7b1e6 100644 --- a/src/server/shared.rs +++ b/src/server/shared.rs @@ -48,10 +48,6 @@ 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)) } @@ -87,11 +83,6 @@ impl SharedBytes { self.get_mut().take() } - #[inline] - pub fn reserve(&self, cnt: usize) { - self.get_mut().reserve(cnt) - } - #[inline] #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] pub fn extend(&self, data: Binary) { diff --git a/src/test.rs b/src/test.rs index 28403625..558695ad 100644 --- a/src/test.rs +++ b/src/test.rs @@ -60,7 +60,6 @@ use ws; /// ``` pub struct TestServer { addr: net::SocketAddr, - thread: Option>, server_sys: Addr, ssl: bool, conn: Addr, @@ -107,7 +106,7 @@ impl TestServer { let (tx, rx) = mpsc::channel(); // run server in separate thread - let join = thread::spawn(move || { + thread::spawn(move || { let sys = System::new("actix-test-server"); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); @@ -134,7 +133,6 @@ impl TestServer { server_sys, conn, ssl: false, - thread: Some(join), rt: Runtime::new().unwrap(), } } @@ -190,10 +188,7 @@ impl TestServer { /// Stop http server fn stop(&mut self) { - if let Some(handle) = self.thread.take() { - self.server_sys.do_send(msgs::SystemExit(0)); - let _ = handle.join(); - } + self.server_sys.do_send(msgs::SystemExit(0)); } /// Execute future on current core @@ -287,7 +282,7 @@ impl TestServerBuilder { let ssl = false; // run server in separate thread - let join = thread::spawn(move || { + thread::spawn(move || { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); @@ -332,7 +327,6 @@ impl TestServerBuilder { ssl, conn, server_sys, - thread: Some(join), rt: Runtime::new().unwrap(), } } diff --git a/src/ws/client.rs b/src/ws/client.rs index f8366752..fcf6ed40 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -7,7 +7,7 @@ use std::{fmt, io, str}; use base64; use bytes::Bytes; use cookie::Cookie; -use futures::unsync::mpsc::{unbounded, UnboundedSender}; +use futures::sync::mpsc::{unbounded, UnboundedSender}; use futures::{Async, Future, Poll, Stream}; use http::header::{self, HeaderName, HeaderValue}; use http::{Error as HttpError, HttpTryFrom, StatusCode}; @@ -16,14 +16,14 @@ use sha1::Sha1; use actix::prelude::*; -use body::{Binary, Body}; +use body::Binary; use error::{Error, UrlParseError}; use header::IntoHeaderValue; use httpmessage::HttpMessage; use payload::PayloadHelper; use client::{ - ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, + ClientBody, ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, HttpResponseParserError, SendRequest, SendRequestError, }; @@ -283,7 +283,7 @@ impl ClientHandshake { ); let (tx, rx) = unbounded(); - request.set_body(Body::Streaming(Box::new(rx.map_err(|_| { + request.set_body(ClientBody::Streaming(Box::new(rx.map_err(|_| { io::Error::new(io::ErrorKind::Other, "disconnected").into() })))); diff --git a/src/ws/context.rs b/src/ws/context.rs index 22323f49..2d7802b0 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -1,5 +1,4 @@ -use futures::sync::oneshot::Sender; -use futures::unsync::oneshot; +use futures::sync::oneshot::{self, Sender}; use futures::{Async, Poll}; use smallvec::SmallVec; diff --git a/tests/test_client.rs b/tests/test_client.rs index 5496e59f..9b9fb0e8 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -343,7 +343,10 @@ fn test_client_streaming_explicit() { let body = once(Ok(Bytes::from_static(STR.as_ref()))); - let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); + let request = srv + .get() + .body(client::ClientBody::Streaming(Box::new(body))) + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); diff --git a/tests/test_server.rs b/tests/test_server.rs index 120eef06..ed2848f7 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -32,7 +32,7 @@ use tokio::executor::current_thread; use tokio::runtime::current_thread::Runtime; use tokio_tcp::TcpStream; -use actix::System; +use actix::{msgs::SystemExit, Arbiter, System}; use actix_web::*; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -74,12 +74,11 @@ fn test_start() { let srv = srv.bind("127.0.0.1:0").unwrap(); let addr = srv.addrs()[0]; let srv_addr = srv.start(); - let _ = tx.send((addr, srv_addr)); + let _ = tx.send((addr, srv_addr, Arbiter::system())); }); }); - let (addr, srv_addr) = rx.recv().unwrap(); + let (addr, srv_addr, sys) = rx.recv().unwrap(); - let _sys = System::new("test-server"); let mut rt = Runtime::new().unwrap(); { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) @@ -102,7 +101,7 @@ fn test_start() { // resume let _ = srv_addr.send(server::ResumeServer).wait(); - thread::sleep(time::Duration::from_millis(400)); + thread::sleep(time::Duration::from_millis(200)); { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) .finish() @@ -110,6 +109,8 @@ fn test_start() { let response = rt.block_on(req.send()).unwrap(); assert!(response.status().is_success()); } + + let _ = sys.send(SystemExit(0)).wait(); } #[test] @@ -129,12 +130,11 @@ fn test_shutdown() { let srv = srv.bind("127.0.0.1:0").unwrap(); let addr = srv.addrs()[0]; let srv_addr = srv.shutdown_timeout(1).start(); - let _ = tx.send((addr, srv_addr)); + let _ = tx.send((addr, srv_addr, Arbiter::system())); }); }); - let (addr, srv_addr) = rx.recv().unwrap(); + let (addr, srv_addr, sys) = rx.recv().unwrap(); - let _sys = System::new("test-server"); let mut rt = Runtime::new().unwrap(); { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) @@ -147,6 +147,8 @@ fn test_shutdown() { thread::sleep(time::Duration::from_millis(1000)); assert!(net::TcpStream::connect(addr).is_err()); + + let _ = sys.send(SystemExit(0)).wait(); } #[test] From 34fd9f81488b6d858c90c7b62fe8b4b81b4956ff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 May 2018 18:18:05 -0700 Subject: [PATCH 0280/1635] travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 50dc82f7..fe5f8974 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ env: before_install: - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl - sudo apt-get update -qq - - sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev + - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev # Add clippy before_script: From dde266b9efe1a1f51fc0ee0eee65f3baa015bacc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 May 2018 18:31:39 -0700 Subject: [PATCH 0281/1635] fix doc string --- src/client/connector.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index a7d226bd..39f83488 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -288,8 +288,10 @@ impl ClientConnector { /// # extern crate actix; /// # extern crate actix_web; /// # extern crate futures; + /// # extern crate tokio; /// # use futures::Future; /// # use std::io::Write; + /// # use std::process; /// extern crate openssl; /// use actix::prelude::*; /// use actix_web::client::{Connect, ClientConnector}; @@ -297,13 +299,12 @@ impl ClientConnector { /// use openssl::ssl::{SslMethod, SslConnector}; /// /// fn main() { - /// let sys = System::new("test"); + /// tokio::run(future::lazy(|| /// - /// // Start `ClientConnector` with custom `SslConnector` - /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn = ClientConnector::with_connector(ssl_conn).start(); + /// // Start `ClientConnector` with custom `SslConnector` + /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); + /// let conn = ClientConnector::with_connector(ssl_conn).start(); /// - /// Arbiter::spawn( /// conn.send( /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host /// .map_err(|_| ()) @@ -311,12 +312,10 @@ impl ClientConnector { /// if let Ok(mut stream) = res { /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); /// } - /// # Arbiter::system().do_send(actix::msgs::SystemExit(0)); + /// # process::exit(0); /// Ok(()) /// }) /// ); - /// - /// sys.run(); /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { From 77becb9bc0a9610c561c9c1d6d4ab15323375a82 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 May 2018 18:48:29 -0700 Subject: [PATCH 0282/1635] fix doc string --- src/client/connector.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 39f83488..65d4ded0 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -289,7 +289,7 @@ impl ClientConnector { /// # extern crate actix_web; /// # extern crate futures; /// # extern crate tokio; - /// # use futures::Future; + /// # use futures::{future, Future}; /// # use std::io::Write; /// # use std::process; /// extern crate openssl; @@ -299,7 +299,7 @@ impl ClientConnector { /// use openssl::ssl::{SslMethod, SslConnector}; /// /// fn main() { - /// tokio::run(future::lazy(|| + /// tokio::run(future::lazy(|| { /// /// // Start `ClientConnector` with custom `SslConnector` /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); @@ -315,7 +315,7 @@ impl ClientConnector { /// # process::exit(0); /// Ok(()) /// }) - /// ); + /// })); /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { From 80965d7a9ad62fddb02f8b9dc87110448e5ef8a5 Mon Sep 17 00:00:00 2001 From: Douman Date: Thu, 31 May 2018 20:43:14 +0300 Subject: [PATCH 0283/1635] Re-export actix dependency. Closes #260 (#264) - Re-export actix's prelude into actix namespace - Removing implicit dependency on root's actix module --- src/client/connector.rs | 10 ++++++---- src/client/mod.rs | 2 -- src/client/pipeline.rs | 4 +++- src/client/request.rs | 1 - src/context.rs | 8 +++++--- src/httpmessage.rs | 2 +- src/lib.rs | 8 +++++++- src/middleware/identity.rs | 1 - src/middleware/session.rs | 1 - src/server/mod.rs | 6 +++--- src/server/srv.rs | 11 ++++++----- src/server/worker.rs | 6 ++++-- src/test.rs | 5 +++-- src/ws/client.rs | 4 +++- src/ws/context.rs | 8 +++++--- src/ws/mod.rs | 7 ++++--- 16 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 65d4ded0..82d932e4 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,12 +1,14 @@ +extern crate actix; + use std::collections::{HashMap, VecDeque}; use std::net::Shutdown; use std::time::{Duration, Instant}; use std::{fmt, io, mem, time}; -use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; -use actix::fut::WrapFuture; -use actix::registry::SystemService; -use actix::{ +use self::actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; +use self::actix::fut::WrapFuture; +use self::actix::registry::SystemService; +use self::actix::{ fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context, ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, }; diff --git a/src/client/mod.rs b/src/client/mod.rs index 8aded011..0e7befc3 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,7 +1,6 @@ //! Http client api //! //! ```rust -//! # extern crate actix; //! # extern crate actix_web; //! # extern crate futures; //! # extern crate tokio; @@ -63,7 +62,6 @@ impl ResponseError for SendRequestError { /// /// /// ```rust -/// # extern crate actix; /// # extern crate actix_web; /// # extern crate futures; /// # extern crate tokio; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index a2105ecb..84593677 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -1,3 +1,5 @@ +extern crate actix; + use bytes::{Bytes, BytesMut}; use futures::sync::oneshot; use futures::{Async, Future, Poll}; @@ -6,7 +8,7 @@ use std::time::{Duration, Instant}; use std::{io, mem}; use tokio_timer::Delay; -use actix::prelude::*; +use self::actix::prelude::*; use super::{ ClientBody, ClientBodyStream, ClientConnector, ClientConnectorError, ClientRequest, diff --git a/src/client/request.rs b/src/client/request.rs index 97b97e01..eebf8e00 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -25,7 +25,6 @@ use httprequest::HttpRequest; /// An HTTP Client Request /// /// ```rust -/// # extern crate actix; /// # extern crate actix_web; /// # extern crate futures; /// # extern crate tokio; diff --git a/src/context.rs b/src/context.rs index b40b4bbb..3f8cf5ee 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,12 +1,14 @@ +extern crate actix; + use futures::sync::oneshot; use futures::sync::oneshot::Sender; use futures::{Async, Future, Poll}; use smallvec::SmallVec; use std::marker::PhantomData; -use actix::dev::{ContextImpl, Envelope, ToEnvelope}; -use actix::fut::ActorFuture; -use actix::{ +use self::actix::dev::{ContextImpl, Envelope, ToEnvelope}; +use self::actix::fut::ActorFuture; +use self::actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, }; diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 2f23e653..2f0a9c99 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -229,8 +229,8 @@ pub trait HttpMessage { /// # extern crate env_logger; /// # extern crate futures; /// # use std::str; - /// # use actix::*; /// # use actix_web::*; + /// # use actix::*; /// # use futures::{Future, Stream}; /// # use futures::future::{ok, result, Either}; /// fn index(mut req: HttpRequest) -> Box> { diff --git a/src/lib.rs b/src/lib.rs index a428b08b..8d728f99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,7 +132,7 @@ extern crate serde_json; extern crate serde_urlencoded; extern crate smallvec; #[macro_use] -extern crate actix; +pub extern crate actix as actix_inner; #[cfg(test)] #[macro_use] @@ -195,6 +195,12 @@ pub use httpresponse::HttpResponse; pub use json::Json; pub use scope::Scope; +pub mod actix { + //! Re-exports [actix's](https://docs.rs/actix) prelude + + pub use actix_inner::prelude::*; +} + #[doc(hidden)] #[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")] pub use ws::WsWriter; diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 36317ebc..54d97a1c 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -143,7 +143,6 @@ pub trait IdentityPolicy: Sized + 'static { /// Request identity middleware /// /// ```rust -/// # extern crate actix; /// # extern crate actix_web; /// use actix_web::App; /// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy}; diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 57f42a11..f80d1f11 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -229,7 +229,6 @@ unsafe impl Sync for SessionImplCell {} /// Session storage middleware /// /// ```rust -/// # extern crate actix; /// # extern crate actix_web; /// use actix_web::App; /// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend}; diff --git a/src/server/mod.rs b/src/server/mod.rs index 26876483..32138f30 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,8 +1,9 @@ //! Http server +extern crate actix; + use std::net::Shutdown; use std::{io, time}; -use actix; use bytes::BytesMut; use futures::{Async, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -42,9 +43,8 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// This is shortcut for `server::HttpServer::new()` method. /// /// ```rust -/// # extern crate actix; /// # extern crate actix_web; -/// use actix::*; +/// use actix_web::actix::*; /// use actix_web::{server, App, HttpResponse}; /// /// fn main() { diff --git a/src/server/srv.rs b/src/server/srv.rs index df6a4b9d..5ea28d6c 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -1,10 +1,12 @@ +extern crate actix; + use std::rc::Rc; use std::sync::{mpsc as sync_mpsc, Arc}; use std::time::Duration; use std::{io, net, thread}; -use actix::actors::signal; -use actix::prelude::*; +use self::actix::actors::signal; +use self::actix::prelude::*; use futures::sync::mpsc; use futures::{Future, Sink, Stream}; use mio; @@ -19,7 +21,7 @@ use native_tls::TlsAcceptor; #[cfg(feature = "alpn")] use openssl::ssl::{AlpnError, SslAcceptorBuilder}; -use super::channel::{HttpChannel, WrapperStream}; +use super::channel::{WrapperStream}; use super::settings::{ServerSettings, WorkerSettings}; use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker}; use super::{IntoHttpHandler, IoStream, KeepAlive}; @@ -405,8 +407,8 @@ impl HttpServer { /// This method requires to run within properly configured `Actix` system. /// /// ```rust - /// extern crate actix; /// extern crate actix_web; + /// extern crate actix; /// use actix_web::{server, App, HttpResponse}; /// /// fn main() { @@ -478,7 +480,6 @@ impl HttpServer { /// /// ```rust,ignore /// # extern crate futures; - /// # extern crate actix; /// # extern crate actix_web; /// # use futures::Future; /// use actix_web::*; diff --git a/src/server/worker.rs b/src/server/worker.rs index d9cca29f..636f859a 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,3 +1,5 @@ +extern crate actix; + use futures::sync::oneshot; use futures::Future; use net2::TcpStreamExt; @@ -21,8 +23,8 @@ use openssl::ssl::SslAcceptor; #[cfg(feature = "alpn")] use tokio_openssl::SslAcceptorExt; -use actix::msgs::StopArbiter; -use actix::*; +use self::actix::msgs::StopArbiter; +use self::actix::*; use server::channel::HttpChannel; use server::settings::WorkerSettings; diff --git a/src/test.rs b/src/test.rs index 558695ad..b022e35e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,11 +1,13 @@ //! Various helpers for Actix applications to use during testing. +extern crate actix; + use std::rc::Rc; use std::str::FromStr; use std::sync::mpsc; use std::{net, thread}; -use actix::{msgs, Actor, Addr, Arbiter, System}; +use self::actix::{msgs, Actor, Addr, Arbiter, System}; use cookie::Cookie; use futures::Future; use http::header::HeaderName; @@ -40,7 +42,6 @@ use ws; /// # Examples /// /// ```rust -/// # extern crate actix; /// # extern crate actix_web; /// # use actix_web::*; /// # diff --git a/src/ws/client.rs b/src/ws/client.rs index fcf6ed40..ac77a28f 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -1,4 +1,6 @@ //! Http client request +extern crate actix; + use std::cell::UnsafeCell; use std::rc::Rc; use std::time::Duration; @@ -14,7 +16,7 @@ use http::{Error as HttpError, HttpTryFrom, StatusCode}; use rand; use sha1::Sha1; -use actix::prelude::*; +use self::actix::prelude::*; use body::Binary; use error::{Error, UrlParseError}; diff --git a/src/ws/context.rs b/src/ws/context.rs index 2d7802b0..48f37f22 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -1,10 +1,12 @@ +extern crate actix; + use futures::sync::oneshot::{self, Sender}; use futures::{Async, Poll}; use smallvec::SmallVec; -use actix::dev::{ContextImpl, Envelope, ToEnvelope}; -use actix::fut::ActorFuture; -use actix::{ +use self::actix::dev::{ContextImpl, Envelope, ToEnvelope}; +use self::actix::fut::ActorFuture; +use self::actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, }; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 7f72dea1..61ec7df9 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -7,9 +7,8 @@ //! ## Example //! //! ```rust -//! # extern crate actix; //! # extern crate actix_web; -//! # use actix::*; +//! # use actix_web::actix::*; //! # use actix_web::*; //! use actix_web::{ws, HttpRequest, HttpResponse}; //! @@ -43,11 +42,13 @@ //! # .finish(); //! # } //! ``` +extern crate actix; + use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{header, Method, StatusCode}; -use actix::{Actor, AsyncContext, StreamHandler}; +use self::actix::{Actor, AsyncContext, StreamHandler}; use body::Binary; use error::{Error, PayloadError, ResponseError}; From 154cd3c5de553d1bbf05124aaf944f190574a9e8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Jun 2018 09:36:16 -0700 Subject: [PATCH 0284/1635] better actix mod re-exports --- src/client/connector.rs | 17 +++++----------- src/client/pipeline.rs | 6 ++---- src/httpmessage.rs | 19 +++++++++--------- src/lib.rs | 22 ++++++++++----------- src/middleware/session.rs | 27 +++++++++++++------------- src/server/mod.rs | 12 +++++------- src/server/srv.rs | 41 ++++++++++++++++++--------------------- src/server/worker.rs | 6 ++---- src/test.rs | 12 +++++------- src/ws/client.rs | 4 +--- src/ws/mod.rs | 5 +---- 11 files changed, 74 insertions(+), 97 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 82d932e4..4f142fde 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,16 +1,12 @@ -extern crate actix; - use std::collections::{HashMap, VecDeque}; use std::net::Shutdown; use std::time::{Duration, Instant}; use std::{fmt, io, mem, time}; -use self::actix::actors::{Connect as ResolveConnect, Connector, ConnectorError}; -use self::actix::fut::WrapFuture; -use self::actix::registry::SystemService; -use self::actix::{ +use actix::resolver::{Connect as ResolveConnect, Connector, ConnectorError}; +use actix::{ fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context, ContextFutureSpawner, - Handler, Message, Recipient, StreamHandler, Supervised, + Handler, Message, Recipient, StreamHandler, Supervised, SystemService, WrapFuture, }; use futures::sync::{mpsc, oneshot}; @@ -287,7 +283,6 @@ impl ClientConnector { /// /// ```rust /// # #![cfg(feature="alpn")] - /// # extern crate actix; /// # extern crate actix_web; /// # extern crate futures; /// # extern crate tokio; @@ -295,14 +290,12 @@ impl ClientConnector { /// # use std::io::Write; /// # use std::process; /// extern crate openssl; - /// use actix::prelude::*; - /// use actix_web::client::{Connect, ClientConnector}; + /// use actix_web::client::{ClientConnector, Connect}; /// - /// use openssl::ssl::{SslMethod, SslConnector}; + /// use openssl::ssl::{SslConnector, SslMethod}; /// /// fn main() { /// tokio::run(future::lazy(|| { - /// /// // Start `ClientConnector` with custom `SslConnector` /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); /// let conn = ClientConnector::with_connector(ssl_conn).start(); diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 84593677..c2caf83c 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -1,5 +1,3 @@ -extern crate actix; - use bytes::{Bytes, BytesMut}; use futures::sync::oneshot; use futures::{Async, Future, Poll}; @@ -8,7 +6,7 @@ use std::time::{Duration, Instant}; use std::{io, mem}; use tokio_timer::Delay; -use self::actix::prelude::*; +use actix::{Addr, Request, SystemService}; use super::{ ClientBody, ClientBodyStream, ClientConnector, ClientConnectorError, ClientRequest, @@ -56,7 +54,7 @@ impl From for SendRequestError { enum State { New, - Connect(actix::dev::Request), + Connect(Request), Connection(Connection), Send(Box), None, diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 2f0a9c99..49d6a8a7 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -118,10 +118,11 @@ pub trait HttpMessage { /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; + /// use actix_web::{ + /// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse, + /// }; /// use bytes::Bytes; /// use futures::future::Future; - /// use actix_web::{HttpMessage, HttpRequest, HttpResponse, - /// FutureResponse, AsyncResponder}; /// /// fn index(mut req: HttpRequest) -> FutureResponse { /// req.body() // <- get Body future @@ -158,7 +159,7 @@ pub trait HttpMessage { /// # extern crate futures; /// # use futures::Future; /// # use std::collections::HashMap; - /// use actix_web::{HttpMessage, HttpRequest, HttpResponse, FutureResponse}; + /// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse}; /// /// fn index(mut req: HttpRequest) -> FutureResponse { /// Box::new( @@ -167,7 +168,8 @@ pub trait HttpMessage { /// .and_then(|params| { // <- url encoded parameters /// println!("==== BODY ==== {:?}", params); /// Ok(HttpResponse::Ok().into()) - /// })) + /// }), + /// ) /// } /// # fn main() {} /// ``` @@ -193,14 +195,14 @@ pub trait HttpMessage { /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; /// use actix_web::*; - /// use futures::future::{Future, ok}; + /// use futures::future::{ok, Future}; /// /// #[derive(Deserialize, Debug)] /// struct MyObj { /// name: String, /// } /// - /// fn index(mut req: HttpRequest) -> Box> { + /// fn index(mut req: HttpRequest) -> Box> { /// req.json() // <- get JsonBody future /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value @@ -224,16 +226,15 @@ pub trait HttpMessage { /// ## Server example /// /// ```rust - /// # extern crate actix; /// # extern crate actix_web; /// # extern crate env_logger; /// # extern crate futures; /// # use std::str; /// # use actix_web::*; - /// # use actix::*; + /// # use actix_web::actix::*; /// # use futures::{Future, Stream}; /// # use futures::future::{ok, result, Either}; - /// fn index(mut req: HttpRequest) -> Box> { + /// 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) => { diff --git a/src/lib.rs b/src/lib.rs index 8d728f99..e8876a9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,17 +6,17 @@ //! # use std::thread; //! //! fn index(info: Path<(String, u32)>) -> String { -//! format!("Hello {}! id:{}", info.0, info.1) +//! format!("Hello {}! id:{}", info.0, info.1) //! } //! //! fn main() { -//! # thread::spawn(|| { -//! server::new( -//! || App::new() -//! .resource("/{name}/{id}/index.html", |r| r.with(index))) -//! .bind("127.0.0.1:8080").unwrap() +//! //#### # thread::spawn(|| { +//! server::new(|| { +//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index)) +//! }).bind("127.0.0.1:8080") +//! .unwrap() //! .run(); -//! # }); +//! //#### # }); //! } //! ``` //! @@ -198,13 +198,13 @@ pub use scope::Scope; pub mod actix { //! Re-exports [actix's](https://docs.rs/actix) prelude + pub use actix_inner::actors::resolver; + pub use actix_inner::actors::signal; + pub use actix_inner::fut; + pub use actix_inner::msgs; pub use actix_inner::prelude::*; } -#[doc(hidden)] -#[deprecated(since = "0.6.2", note = "please use `use actix_web::ws::WsWriter`")] -pub use ws::WsWriter; - #[cfg(feature = "openssl")] pub(crate) const HAS_OPENSSL: bool = true; #[cfg(not(feature = "openssl"))] diff --git a/src/middleware/session.rs b/src/middleware/session.rs index f80d1f11..38be1235 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -32,8 +32,7 @@ //! session data. //! //! ```rust -//! # extern crate actix; -//! # extern crate actix_web; +//! //#### # extern crate actix_web; //! use actix_web::{server, App, HttpRequest, Result}; //! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; //! @@ -59,7 +58,7 @@ //! ))) //! .bind("127.0.0.1:59880").unwrap() //! .start(); -//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +//! //#### # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); //! }); //! } //! ``` @@ -88,13 +87,13 @@ use middleware::{Middleware, Response, Started}; /// The helper trait to obtain your session data from a request. /// /// ```rust -/// use actix_web::*; /// use actix_web::middleware::session::RequestSession; +/// use actix_web::*; /// /// 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)?; +/// req.session().set("counter", count + 1)?; /// } else { /// req.session().set("counter", 1)?; /// } @@ -123,13 +122,13 @@ impl RequestSession for HttpRequest { /// method. `RequestSession` trait is implemented for `HttpRequest`. /// /// ```rust -/// use actix_web::*; /// use actix_web::middleware::session::RequestSession; +/// use actix_web::*; /// /// 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)?; +/// req.session().set("counter", count + 1)?; /// } else { /// req.session().set("counter", 1)?; /// } @@ -200,7 +199,7 @@ impl Session { /// fn index(session: Session) -> Result<&'static str> { /// // access session data /// if let Some(count) = session.get::("counter")? { -/// session.set("counter", count+1)?; +/// session.set("counter", count + 1)?; /// } else { /// session.set("counter", 1)?; /// } @@ -230,15 +229,15 @@ unsafe impl Sync for SessionImplCell {} /// /// ```rust /// # extern crate actix_web; +/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage}; /// use actix_web::App; -/// use actix_web::middleware::session::{SessionStorage, CookieSessionBackend}; /// /// fn main() { -/// let app = App::new().middleware( -/// SessionStorage::new( // <- create session middleware -/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend -/// .secure(false)) -/// ); +/// let app = App::new().middleware(SessionStorage::new( +/// // <- create session middleware +/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend +/// .secure(false), +/// )); /// } /// ``` pub struct SessionStorage(T, PhantomData); diff --git a/src/server/mod.rs b/src/server/mod.rs index 32138f30..44022931 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,4 @@ //! Http server -extern crate actix; - use std::net::Shutdown; use std::{io, time}; @@ -29,6 +27,7 @@ pub use self::srv::HttpServer; #[doc(hidden)] pub use self::helpers::write_content_length; +use actix::Message; use body::Binary; use error::Error; use header::ContentEncoding; @@ -43,9 +42,8 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// This is shortcut for `server::HttpServer::new()` method. /// /// ```rust -/// # extern crate actix_web; -/// use actix_web::actix::*; -/// use actix_web::{server, App, HttpResponse}; +/// //#### # extern crate actix_web; +/// use actix_web::{actix, server, App, HttpResponse}; /// /// fn main() { /// actix::System::run(|| { @@ -56,7 +54,7 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// .bind("127.0.0.1:59090").unwrap() /// .start(); /// -/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +/// //#### # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); /// }); /// } /// ``` @@ -116,7 +114,7 @@ pub struct StopServer { pub graceful: bool, } -impl actix::Message for StopServer { +impl Message for StopServer { type Result = Result<(), ()>; } diff --git a/src/server/srv.rs b/src/server/srv.rs index 5ea28d6c..ba8c83e6 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -1,12 +1,13 @@ -extern crate actix; - use std::rc::Rc; use std::sync::{mpsc as sync_mpsc, Arc}; use std::time::Duration; use std::{io, net, thread}; -use self::actix::actors::signal; -use self::actix::prelude::*; +use actix::{ + fut, msgs, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, + ContextFutureSpawner, Handler, Response, StreamHandler, System, WrapFuture, +}; + use futures::sync::mpsc; use futures::{Future, Sink, Stream}; use mio; @@ -21,7 +22,7 @@ use native_tls::TlsAcceptor; #[cfg(feature = "alpn")] use openssl::ssl::{AlpnError, SslAcceptorBuilder}; -use super::channel::{WrapperStream}; +use super::channel::WrapperStream; use super::settings::{ServerSettings, WorkerSettings}; use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker}; use super::{IntoHttpHandler, IoStream, KeepAlive}; @@ -408,20 +409,17 @@ impl HttpServer { /// /// ```rust /// extern crate actix_web; - /// extern crate actix; - /// use actix_web::{server, App, HttpResponse}; + /// use actix_web::{actix, server, App, HttpResponse}; /// /// fn main() { /// // Run actix system, this method actually starts all async processes /// actix::System::run(|| { - /// - /// server::new( - /// || App::new() - /// .resource("/", |r| r.h(|_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") + /// server::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0") + /// .expect("Can not bind to 127.0.0.1:0") /// .start(); - /// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); - /// }); + /// //#### # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); + /// }); /// } /// ``` pub fn start(mut self) -> Addr { @@ -485,10 +483,9 @@ impl HttpServer { /// use actix_web::*; /// /// fn main() { - /// HttpServer::new( - /// || App::new() - /// .resource("/", |r| r.h(|_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") + /// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0") + /// .expect("Can not bind to 127.0.0.1:0") /// .run(); /// } /// ``` @@ -723,7 +720,7 @@ impl Handler for HttpServer { } impl Handler for HttpServer { - type Result = actix::Response<(), ()>; + type Result = Response<(), ()>; fn handle(&mut self, msg: StopServer, ctx: &mut Context) -> Self::Result { // stop accept threads @@ -754,11 +751,11 @@ impl Handler for HttpServer { // we need to stop system if server was spawned if slf.exit { ctx.run_later(Duration::from_millis(300), |_, _| { - Arbiter::system().do_send(actix::msgs::SystemExit(0)) + Arbiter::system().do_send(msgs::SystemExit(0)) }); } } - actix::fut::ok(()) + fut::ok(()) }) .spawn(ctx); } @@ -769,7 +766,7 @@ impl Handler for HttpServer { // we need to stop system if server was spawned if self.exit { ctx.run_later(Duration::from_millis(300), |_, _| { - Arbiter::system().do_send(actix::msgs::SystemExit(0)) + Arbiter::system().do_send(msgs::SystemExit(0)) }); } Response::reply(Ok(())) diff --git a/src/server/worker.rs b/src/server/worker.rs index 636f859a..5a3f8858 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,5 +1,3 @@ -extern crate actix; - use futures::sync::oneshot; use futures::Future; use net2::TcpStreamExt; @@ -23,8 +21,8 @@ use openssl::ssl::SslAcceptor; #[cfg(feature = "alpn")] use tokio_openssl::SslAcceptorExt; -use self::actix::msgs::StopArbiter; -use self::actix::*; +use actix::msgs::StopArbiter; +use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response}; use server::channel::HttpChannel; use server::settings::WorkerSettings; diff --git a/src/test.rs b/src/test.rs index b022e35e..ced447f5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,13 +1,11 @@ //! Various helpers for Actix applications to use during testing. - -extern crate actix; - use std::rc::Rc; use std::str::FromStr; use std::sync::mpsc; use std::{net, thread}; -use self::actix::{msgs, Actor, Addr, Arbiter, System}; +use actix_inner::{msgs, Actor, Addr, Arbiter, System}; + use cookie::Cookie; use futures::Future; use http::header::HeaderName; @@ -409,11 +407,11 @@ impl Iterator for TestApp { /// /// fn main() { /// let resp = TestRequest::with_header("content-type", "text/plain") -/// .run(index).unwrap(); +/// .run(index) +/// .unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// -/// let resp = TestRequest::default() -/// .run(index).unwrap(); +/// let resp = TestRequest::default().run(index).unwrap(); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` diff --git a/src/ws/client.rs b/src/ws/client.rs index ac77a28f..f5541beb 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -1,6 +1,4 @@ //! Http client request -extern crate actix; - use std::cell::UnsafeCell; use std::rc::Rc; use std::time::Duration; @@ -16,7 +14,7 @@ use http::{Error as HttpError, HttpTryFrom, StatusCode}; use rand; use sha1::Sha1; -use self::actix::prelude::*; +use actix::{Addr, SystemService}; use body::Binary; use error::{Error, UrlParseError}; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 61ec7df9..4c079dad 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -25,7 +25,6 @@ //! //! // Handler for ws::Message messages //! impl StreamHandler for Ws { -//! //! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { //! match msg { //! ws::Message::Ping(msg) => ctx.pong(&msg), @@ -42,13 +41,11 @@ //! # .finish(); //! # } //! ``` -extern crate actix; - use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{header, Method, StatusCode}; -use self::actix::{Actor, AsyncContext, StreamHandler}; +use super::actix::{Actor, AsyncContext, StreamHandler}; use body::Binary; use error::{Error, PayloadError, ResponseError}; From 3f5a39a5b7116c890a1e105d08b6ac111d954237 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Jun 2018 09:37:14 -0700 Subject: [PATCH 0285/1635] cargo fmt --- src/application.rs | 91 ++++++++++++++++---------------- src/client/mod.rs | 3 +- src/client/request.rs | 14 +++-- src/error.rs | 4 +- src/extractor.rs | 48 ++++++++--------- src/handler.rs | 42 ++++++++------- src/httprequest.rs | 6 +-- src/httpresponse.rs | 9 ++-- src/json.rs | 23 ++++---- src/middleware/cors.rs | 37 +++++++------ src/middleware/csrf.rs | 13 +++-- src/middleware/defaultheaders.rs | 9 ++-- src/middleware/errhandlers.rs | 16 +++--- src/middleware/identity.rs | 27 +++++----- src/middleware/logger.rs | 2 +- src/param.rs | 4 +- src/pred.rs | 20 ++++--- src/resource.rs | 13 ++--- src/route.rs | 48 +++++++++-------- src/scope.rs | 81 ++++++++++++++-------------- src/with.rs | 20 +++---- 21 files changed, 279 insertions(+), 251 deletions(-) diff --git a/src/application.rs b/src/application.rs index 94fb70fb..ab1548bf 100644 --- a/src/application.rs +++ b/src/application.rs @@ -267,8 +267,8 @@ where /// let app = App::new() /// .prefix("/app") /// .resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); + /// r.get().f(|_| HttpResponse::Ok()); + /// r.head().f(|_| HttpResponse::MethodNotAllowed()); /// }) /// .finish(); /// } @@ -300,10 +300,12 @@ where /// /// fn main() { /// let app = App::new() - /// .route("/test", http::Method::GET, - /// |_: HttpRequest| HttpResponse::Ok()) - /// .route("/test", http::Method::POST, - /// |_: HttpRequest| HttpResponse::MethodNotAllowed()); + /// .route("/test", http::Method::GET, |_: HttpRequest| { + /// HttpResponse::Ok() + /// }) + /// .route("/test", http::Method::POST, |_: HttpRequest| { + /// HttpResponse::MethodNotAllowed() + /// }); /// } /// ``` pub fn route(mut self, path: &str, method: Method, f: F) -> App @@ -345,12 +347,12 @@ where /// use actix_web::{http, App, HttpRequest, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .scope("/{project_id}", |scope| { - /// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) - /// }); + /// let app = App::new().scope("/{project_id}", |scope| { + /// scope + /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) + /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) + /// }); /// } /// ``` /// @@ -402,11 +404,10 @@ where /// use actix_web::{http, App, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }); + /// let app = App::new().resource("/users/{userid}/{friend}", |r| { + /// r.get().f(|_| HttpResponse::Ok()); + /// r.head().f(|_| HttpResponse::MethodNotAllowed()); + /// }); /// } /// ``` pub fn resource(mut self, path: &str, f: F) -> App @@ -469,9 +470,9 @@ where /// use actix_web::{App, HttpRequest, HttpResponse, Result}; /// /// fn index(mut req: HttpRequest) -> Result { - /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; - /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); - /// Ok(HttpResponse::Ok().into()) + /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; + /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); + /// Ok(HttpResponse::Ok().into()) /// } /// /// fn main() { @@ -514,13 +515,11 @@ where /// use actix_web::{http, App, HttpRequest, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .handler("/app", |req: HttpRequest| { - /// match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }}); + /// let app = App::new().handler("/app", |req: HttpRequest| match *req.method() { + /// http::Method::GET => HttpResponse::Ok(), + /// http::Method::POST => HttpResponse::MethodNotAllowed(), + /// _ => HttpResponse::NotFound(), + /// }); /// } /// ``` pub fn handler>(mut self, path: &str, handler: H) -> App { @@ -561,15 +560,14 @@ where /// /// ```rust /// # extern crate actix_web; - /// use actix_web::{App, HttpResponse, fs, middleware}; + /// use actix_web::{fs, middleware, App, HttpResponse}; /// /// // this function could be located in different module /// fn config(app: App) -> App { - /// app - /// .resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) + /// app.resource("/test", |r| { + /// r.get().f(|_| HttpResponse::Ok()); + /// r.head().f(|_| HttpResponse::MethodNotAllowed()); + /// }) /// } /// /// fn main() { @@ -636,19 +634,22 @@ where /// struct State2; /// /// fn main() { - /// # thread::spawn(|| { - /// server::new(|| { vec![ - /// App::with_state(State1) - /// .prefix("/app1") - /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - /// .boxed(), - /// App::with_state(State2) - /// .prefix("/app2") - /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - /// .boxed() ]}) - /// .bind("127.0.0.1:8080").unwrap() + /// //#### # thread::spawn(|| { + /// server::new(|| { + /// vec![ + /// App::with_state(State1) + /// .prefix("/app1") + /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) + /// .boxed(), + /// App::with_state(State2) + /// .prefix("/app2") + /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) + /// .boxed(), + /// ] + /// }).bind("127.0.0.1:8080") + /// .unwrap() /// .run() - /// # }); + /// //#### # }); /// } /// ``` pub fn boxed(mut self) -> Box { diff --git a/src/client/mod.rs b/src/client/mod.rs index 0e7befc3..3c956733 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -81,7 +81,8 @@ impl ResponseError for SendRequestError { /// println!("Response: {:?}", response); /// # process::exit(0); /// Ok(()) -/// })); +/// }), +/// ); /// } /// ``` pub fn get>(uri: U) -> ClientRequestBuilder { diff --git a/src/client/request.rs b/src/client/request.rs index eebf8e00..4fef3e5d 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -43,7 +43,7 @@ use httprequest::HttpRequest; /// println!("Response: {:?}", response); /// # process::exit(0); /// Ok(()) -/// }) +/// }), /// ); /// } /// ``` @@ -344,7 +344,8 @@ impl ClientRequestBuilder { /// let req = client::ClientRequest::build() /// .set(http::header::Date::now()) /// .set(http::header::ContentType(mime::TEXT_HTML)) - /// .finish().unwrap(); + /// .finish() + /// .unwrap(); /// } /// ``` #[doc(hidden)] @@ -376,7 +377,8 @@ impl ClientRequestBuilder { /// let req = ClientRequest::build() /// .header("X-TEST", "value") /// .header(header::CONTENT_TYPE, "application/json") - /// .finish().unwrap(); + /// .finish() + /// .unwrap(); /// } /// ``` pub fn header(&mut self, key: K, value: V) -> &mut Self @@ -486,8 +488,10 @@ impl ClientRequestBuilder { /// .path("/") /// .secure(true) /// .http_only(true) - /// .finish()) - /// .finish().unwrap(); + /// .finish(), + /// ) + /// .finish() + /// .unwrap(); /// } /// ``` pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { diff --git a/src/error.rs b/src/error.rs index 6d739702..a4c513fb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -552,8 +552,8 @@ impl From for UrlGenerationError { /// use actix_web::fs::NamedFile; /// /// fn index(req: HttpRequest) -> Result { -/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; -/// Ok(f) +/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; +/// Ok(f) /// } /// # fn main() {} /// ``` diff --git a/src/extractor.rs b/src/extractor.rs index fc9145b9..caf34332 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -24,7 +24,7 @@ use httprequest::HttpRequest; /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; -/// use actix_web::{App, Path, Result, http}; +/// use actix_web::{http, App, Path, Result}; /// /// /// extract path info from "/{username}/{count}/index.html" url /// /// {username} - deserializes to a String @@ -35,8 +35,9 @@ use httprequest::HttpRequest; /// /// fn main() { /// let app = App::new().resource( -/// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor +/// "/{username}/{count}/index.html", // <- define path parameters +/// |r| r.method(http::Method::GET).with(index), +/// ); // <- use `with` extractor /// } /// ``` /// @@ -48,7 +49,7 @@ use httprequest::HttpRequest; /// # extern crate actix_web; /// # extern crate futures; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Path, Result, http}; +/// use actix_web::{http, App, Path, Result}; /// /// #[derive(Deserialize)] /// struct Info { @@ -62,8 +63,9 @@ use httprequest::HttpRequest; /// /// fn main() { /// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor +/// "/{username}/index.html", // <- define path parameters +/// |r| r.method(http::Method::GET).with(index), +/// ); // <- use `with` extractor /// } /// ``` pub struct Path { @@ -118,13 +120,13 @@ where /// ## Example /// /// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; +/// //#### # extern crate bytes; +/// //#### # extern crate actix_web; +/// //#### # extern crate futures; +/// //#### #[macro_use] extern crate serde_derive; /// use actix_web::{App, Query, http}; /// -/// #[derive(Deserialize)] +/// //#### #[derive(Deserialize)] /// struct Info { /// username: String, /// } @@ -255,7 +257,7 @@ where /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Form, Result, http}; +/// use actix_web::{http, App, Form, Result}; /// /// #[derive(Deserialize)] /// struct FormData { @@ -270,10 +272,10 @@ where /// /// fn main() { /// let app = App::new().resource( -/// "/index.html", |r| { -/// r.method(http::Method::GET) -/// .with(index) -/// .limit(4096);} // <- change form extractor configuration +/// "/index.html", +/// |r| { +/// r.method(http::Method::GET).with(index).limit(4096); +/// }, // <- change form extractor configuration /// ); /// } /// ``` @@ -315,9 +317,8 @@ impl Default for FormConfig { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", |r| -/// r.method(http::Method::GET).with(index)); +/// let app = App::new() +/// .resource("/index.html", |r| r.method(http::Method::GET).with(index)); /// } /// ``` impl FromRequest for Bytes { @@ -354,12 +355,11 @@ impl FromRequest for Bytes { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", |r| { -/// r.method(http::Method::GET) +/// let app = App::new().resource("/index.html", |r| { +/// r.method(http::Method::GET) /// .with(index) // <- register handler with extractor params -/// .limit(4096); // <- limit size of the payload -/// }); +/// .limit(4096); // <- limit size of the payload +/// }); /// } /// ``` impl FromRequest for String { diff --git a/src/handler.rs b/src/handler.rs index 759291a2..bae50937 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -61,22 +61,24 @@ pub trait FromRequest: Sized { /// # extern crate actix_web; /// # extern crate futures; /// # use futures::future::Future; +/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse}; /// use futures::future::result; -/// use actix_web::{Either, Error, HttpRequest, HttpResponse, AsyncResponder}; -/// -/// type RegisterResult = Either>>; /// +/// type RegisterResult = +/// Either>>; /// /// fn index(req: HttpRequest) -> RegisterResult { -/// if is_a_variant() { // <- choose variant A -/// Either::A( -/// HttpResponse::BadRequest().body("Bad data")) +/// if is_a_variant() { +/// // <- choose variant A +/// Either::A(HttpResponse::BadRequest().body("Bad data")) /// } else { -/// Either::B( // <- variant B +/// Either::B( +/// // <- variant B /// result(Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!"))) -/// .responder()) +/// .content_type("text/html") +/// .body("Hello!"))) +/// .responder(), +/// ) /// } /// } /// # fn is_a_variant() -> bool { true } @@ -138,16 +140,17 @@ where /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; -/// use futures::future::Future; /// use actix_web::{ -/// App, HttpRequest, HttpResponse, HttpMessage, Error, AsyncResponder}; +/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse, +/// }; +/// use futures::future::Future; /// /// #[derive(Deserialize, Debug)] /// struct MyObj { /// name: String, /// } /// -/// fn index(mut req: HttpRequest) -> Box> { +/// fn index(mut req: HttpRequest) -> Box> { /// req.json() // <- get JsonBody future /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value @@ -479,10 +482,12 @@ where /// # extern crate actix_web; /// # extern crate futures; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Path, State, http}; +/// use actix_web::{http, App, Path, State}; /// /// /// Application state -/// struct MyApp {msg: &'static str} +/// struct MyApp { +/// msg: &'static str, +/// } /// /// #[derive(Deserialize)] /// struct Info { @@ -496,9 +501,10 @@ where /// } /// /// fn main() { -/// let app = App::with_state(MyApp{msg: "Welcome"}).resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor +/// let app = App::with_state(MyApp { msg: "Welcome" }).resource( +/// "/{username}/index.html", // <- define path parameters +/// |r| r.method(http::Method::GET).with(index), +/// ); // <- use `with` extractor /// } /// ``` pub struct State(HttpRequest); diff --git a/src/httprequest.rs b/src/httprequest.rs index 0a14ca04..b75bbbe4 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -283,9 +283,9 @@ impl HttpRequest { /// Generate url for named resource /// /// ```rust - /// # extern crate actix_web; - /// # use actix_web::{App, HttpRequest, HttpResponse, http}; - /// # + /// //#### # extern crate actix_web; + /// //#### # use actix_web::{App, HttpRequest, HttpResponse, http}; + /// //#### # /// fn index(req: HttpRequest) -> HttpResponse { /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource /// HttpResponse::Ok().into() diff --git a/src/httpresponse.rs b/src/httpresponse.rs index af5a20c5..ac84b2be 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -297,11 +297,13 @@ impl HttpResponseBuilder { /// /// ```rust /// # extern crate actix_web; - /// use actix_web::{HttpRequest, HttpResponse, Result, http}; + /// use actix_web::{http, HttpRequest, HttpResponse, Result}; /// /// fn index(req: HttpRequest) -> Result { /// Ok(HttpResponse::Ok() - /// .set(http::header::IfModifiedSince("Sun, 07 Nov 1994 08:48:37 GMT".parse()?)) + /// .set(http::header::IfModifiedSince( + /// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?, + /// )) /// .finish()) /// } /// fn main() {} @@ -455,7 +457,8 @@ impl HttpResponseBuilder { /// .path("/") /// .secure(true) /// .http_only(true) - /// .finish()) + /// .finish(), + /// ) /// .finish() /// } /// ``` diff --git a/src/json.rs b/src/json.rs index e48c27ef..8e5cc293 100644 --- a/src/json.rs +++ b/src/json.rs @@ -32,11 +32,11 @@ use httpresponse::HttpResponse; /// ## Example /// /// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; +/// //#### # extern crate actix_web; +/// //#### #[macro_use] extern crate serde_derive; /// use actix_web::{App, Json, Result, http}; /// -/// #[derive(Deserialize)] +/// //#### #[derive(Deserialize)] /// struct Info { /// username: String, /// } @@ -69,7 +69,9 @@ use httpresponse::HttpResponse; /// } /// /// fn index(req: HttpRequest) -> Result> { -/// Ok(Json(MyObj{name: req.match_info().query("name")?})) +/// Ok(Json(MyObj { +/// name: req.match_info().query("name")?, +/// })) /// } /// # fn main() {} /// ``` @@ -154,7 +156,7 @@ where /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Json, HttpResponse, Result, http, error}; +/// use actix_web::{error, http, App, HttpResponse, Json, Result}; /// /// #[derive(Deserialize)] /// struct Info { @@ -167,16 +169,15 @@ where /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", |r| { -/// r.method(http::Method::POST) +/// let app = App::new().resource("/index.html", |r| { +/// r.method(http::Method::POST) /// .with(index) /// .limit(4096) // <- change json extractor configuration /// .error_handler(|err, req| { // <- create custom error response /// error::InternalError::from_response( /// err, HttpResponse::Conflict().finish()).into() /// }); -/// }); +/// }); /// } /// ``` pub struct JsonConfig { @@ -223,15 +224,15 @@ impl Default for JsonConfig { /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; +/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse}; /// use futures::future::Future; -/// use actix_web::{AsyncResponder, HttpRequest, HttpResponse, HttpMessage, Error}; /// /// #[derive(Deserialize, Debug)] /// struct MyObj { /// name: String, /// } /// -/// fn index(mut req: HttpRequest) -> Box> { +/// fn index(mut req: HttpRequest) -> Box> { /// req.json() // <- get JsonBody future /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index a7b0110f..93c8aeb5 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -19,16 +19,16 @@ //! //! ```rust //! # extern crate actix_web; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; //! use actix_web::middleware::cors::Cors; +//! use actix_web::{http, App, HttpRequest, HttpResponse}; //! //! fn index(mut req: HttpRequest) -> &'static str { -//! "Hello world" +//! "Hello world" //! } //! //! fn main() { -//! let app = App::new() -//! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder +//! let app = App::new().configure(|app| { +//! Cors::for_app(app) // <- Construct CORS middleware builder //! .allowed_origin("https://www.rust-lang.org/") //! .allowed_methods(vec!["GET", "POST"]) //! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) @@ -38,7 +38,8 @@ //! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); //! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); //! }) -//! .register()); +//! .register() +//! }); //! } //! ``` //! In this example custom *CORS* middleware get registered for "/index.html" @@ -232,18 +233,20 @@ impl Cors { /// /// ```rust /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; /// use actix_web::middleware::cors::Cors; + /// use actix_web::{http, App, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .configure(|app| Cors::for_app(app) // <- Construct CORS builder + /// let app = App::new().configure( + /// |app| { + /// Cors::for_app(app) // <- Construct CORS builder /// .allowed_origin("https://www.rust-lang.org/") /// .resource("/resource", |r| { // register resource /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); /// }) - /// .register() // construct CORS and return application instance - /// ); + /// .register() + /// }, // construct CORS and return application instance + /// ); /// } /// ``` pub fn for_app(app: App) -> CorsBuilder { @@ -491,8 +494,8 @@ impl Middleware for Cors { /// ```rust /// # extern crate http; /// # extern crate actix_web; -/// use http::header; /// use actix_web::middleware::cors; +/// use http::header; /// /// # fn main() { /// let cors = cors::Cors::build() @@ -764,12 +767,13 @@ impl CorsBuilder { /// /// ```rust /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; /// use actix_web::middleware::cors::Cors; + /// use actix_web::{http, App, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .configure(|app| Cors::for_app(app) // <- Construct CORS builder + /// let app = App::new().configure( + /// |app| { + /// Cors::for_app(app) // <- Construct CORS builder /// .allowed_origin("https://www.rust-lang.org/") /// .allowed_methods(vec!["GET", "POST"]) /// .allowed_header(http::header::CONTENT_TYPE) @@ -781,8 +785,9 @@ impl CorsBuilder { /// r.method(http::Method::HEAD) /// .f(|_| HttpResponse::MethodNotAllowed()); /// }) - /// .register() // construct CORS and return application instance - /// ); + /// .register() + /// }, // construct CORS and return application instance + /// ); /// } /// ``` pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 9ff23b53..670ec1c1 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -22,8 +22,8 @@ //! //! ``` //! # extern crate actix_web; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; //! use actix_web::middleware::csrf; +//! use actix_web::{http, App, HttpRequest, HttpResponse}; //! //! fn handle_post(_: HttpRequest) -> &'static str { //! "This action should only be triggered with requests from the same site" @@ -32,8 +32,8 @@ //! fn main() { //! let app = App::new() //! .middleware( -//! csrf::CsrfFilter::new() -//! .allowed_origin("https://www.example.com")) +//! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"), +//! ) //! .resource("/", |r| { //! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); //! r.method(http::Method::POST).f(handle_post); @@ -120,13 +120,12 @@ fn origin(headers: &HeaderMap) -> Option, CsrfError>> { /// # Example /// /// ``` -/// use actix_web::App; /// use actix_web::middleware::csrf; +/// use actix_web::App; /// /// # fn main() { -/// let app = App::new().middleware( -/// csrf::CsrfFilter::new() -/// .allowed_origin("https://www.example.com")); +/// let app = App::new() +/// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com")); /// # } /// ``` #[derive(Default)] diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index ebe3ea1d..dca8dfbe 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -17,12 +17,11 @@ use middleware::{Middleware, Response}; /// /// fn main() { /// let app = App::new() -/// .middleware( -/// middleware::DefaultHeaders::new() -/// .header("X-Version", "0.2")) +/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) /// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); +/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); +/// r.method(http::Method::HEAD) +/// .f(|_| HttpResponse::MethodNotAllowed()); /// }) /// .finish(); /// } diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 757b3815..42f75a3d 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -18,23 +18,25 @@ type ErrorHandler = Fn(&mut HttpRequest, HttpResponse) -> Result /// /// ```rust /// # extern crate actix_web; +/// use actix_web::middleware::{ErrorHandlers, Response}; /// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; -/// use actix_web::middleware::{Response, ErrorHandlers}; /// /// fn render_500(_: &mut HttpRequest, resp: HttpResponse) -> Result { -/// let mut builder = resp.into_builder(); -/// builder.header(http::header::CONTENT_TYPE, "application/json"); -/// Ok(Response::Done(builder.into())) +/// let mut builder = resp.into_builder(); +/// builder.header(http::header::CONTENT_TYPE, "application/json"); +/// Ok(Response::Done(builder.into())) /// } /// /// fn main() { /// let app = App::new() /// .middleware( /// ErrorHandlers::new() -/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500)) +/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), +/// ) /// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); +/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); +/// r.method(http::Method::HEAD) +/// .f(|_| HttpResponse::MethodNotAllowed()); /// }) /// .finish(); /// } diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 54d97a1c..c8505d68 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -62,8 +62,8 @@ use middleware::{Middleware, Response, Started}; /// The helper trait to obtain your identity from a request. /// /// ```rust -/// use actix_web::*; /// use actix_web::middleware::identity::RequestIdentity; +/// use actix_web::*; /// /// fn index(req: HttpRequest) -> Result { /// // access request identity @@ -80,7 +80,7 @@ use middleware::{Middleware, Response, Started}; /// } /// /// fn logout(mut req: HttpRequest) -> HttpResponse { -/// req.forget(); // <- remove identity +/// req.forget(); // <- remove identity /// HttpResponse::Ok().finish() /// } /// # fn main() {} @@ -144,16 +144,16 @@ pub trait IdentityPolicy: Sized + 'static { /// /// ```rust /// # extern crate actix_web; +/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; /// use actix_web::App; -/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy}; /// /// fn main() { -/// let app = App::new().middleware( -/// IdentityService::new( // <- create identity middleware -/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend +/// let app = App::new().middleware(IdentityService::new( +/// // <- create identity middleware +/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend /// .name("auth-cookie") -/// .secure(false)) -/// ); +/// .secure(false), +/// )); /// } /// ``` pub struct IdentityService { @@ -317,17 +317,18 @@ impl CookieIdentityInner { /// /// ```rust /// # extern crate actix_web; +/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; /// use actix_web::App; -/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy}; /// /// fn main() { -/// let app = App::new().middleware( -/// IdentityService::new( // <- create identity middleware -/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy +/// let app = App::new().middleware(IdentityService::new( +/// // <- create identity middleware +/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy /// .domain("www.rust-lang.org") /// .name("actix_auth") /// .path("/") -/// .secure(true))); +/// .secure(true), +/// )); /// } /// ``` pub struct CookieIdentityPolicy(Rc); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 985a5dfe..a731d695 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -31,8 +31,8 @@ use middleware::{Finished, Middleware, Started}; /// ```rust /// # extern crate actix_web; /// extern crate env_logger; -/// use actix_web::App; /// use actix_web::middleware::Logger; +/// use actix_web::App; /// /// fn main() { /// std::env::set_var("RUST_LOG", "actix_web=info"); diff --git a/src/param.rs b/src/param.rs index fd07acf4..48d1f3a3 100644 --- a/src/param.rs +++ b/src/param.rs @@ -96,8 +96,8 @@ impl<'a> Params<'a> { /// # 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)) + /// let ivalue: isize = req.match_info().query("val")?; + /// Ok(format!("isuze value: {:?}", ivalue)) /// } /// # fn main() {} /// ``` diff --git a/src/pred.rs b/src/pred.rs index 90a0d61f..206a7941 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -22,10 +22,11 @@ pub trait Predicate { /// use actix_web::{pred, App, HttpResponse}; /// /// fn main() { -/// App::new() -/// .resource("/index.html", |r| r.route() +/// App::new().resource("/index.html", |r| { +/// r.route() /// .filter(pred::Any(pred::Get()).or(pred::Post())) -/// .f(|r| HttpResponse::MethodNotAllowed())); +/// .f(|r| HttpResponse::MethodNotAllowed()) +/// }); /// } /// ``` pub fn Any + 'static>(pred: P) -> AnyPredicate { @@ -61,11 +62,14 @@ impl Predicate for AnyPredicate { /// use actix_web::{pred, App, HttpResponse}; /// /// fn main() { -/// App::new() -/// .resource("/index.html", |r| r.route() -/// .filter(pred::All(pred::Get()) -/// .and(pred::Header("content-type", "plain/text"))) -/// .f(|_| HttpResponse::MethodNotAllowed())); +/// App::new().resource("/index.html", |r| { +/// r.route() +/// .filter( +/// pred::All(pred::Get()) +/// .and(pred::Header("content-type", "plain/text")), +/// ) +/// .f(|_| HttpResponse::MethodNotAllowed()) +/// }); /// } /// ``` pub fn All + 'static>(pred: P) -> AllPredicate { diff --git a/src/resource.rs b/src/resource.rs index 7b1a7502..df6a05f2 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -24,7 +24,7 @@ use route::Route; /// predicates route route considered matched and route handler get called. /// /// ```rust -/// # extern crate actix_web; +/// //#### # extern crate actix_web; /// use actix_web::{App, HttpResponse, http}; /// /// fn main() { @@ -82,11 +82,12 @@ impl ResourceHandler { /// /// fn main() { /// let app = App::new() - /// .resource( - /// "/", |r| r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|r| HttpResponse::Ok())) + /// .resource("/", |r| { + /// r.route() + /// .filter(pred::Any(pred::Get()).or(pred::Put())) + /// .filter(pred::Header("Content-Type", "text/plain")) + /// .f(|r| HttpResponse::Ok()) + /// }) /// .finish(); /// } /// ``` diff --git a/src/route.rs b/src/route.rs index ff19db80..040baa2d 100644 --- a/src/route.rs +++ b/src/route.rs @@ -66,13 +66,12 @@ impl Route { /// # extern crate actix_web; /// # use actix_web::*; /// # fn main() { - /// App::new() - /// .resource("/path", |r| - /// r.route() - /// .filter(pred::Get()) - /// .filter(pred::Header("content-type", "text/plain")) - /// .f(|req| HttpResponse::Ok()) - /// ) + /// App::new().resource("/path", |r| { + /// r.route() + /// .filter(pred::Get()) + /// .filter(pred::Header("content-type", "text/plain")) + /// .f(|req| HttpResponse::Ok()) + /// }) /// # .finish(); /// # } /// ``` @@ -115,7 +114,7 @@ impl Route { /// # extern crate actix_web; /// # extern crate futures; /// #[macro_use] extern crate serde_derive; - /// use actix_web::{App, Path, Result, http}; + /// use actix_web::{http, App, Path, Result}; /// /// #[derive(Deserialize)] /// struct Info { @@ -129,8 +128,9 @@ impl Route { /// /// fn main() { /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor + /// "/{username}/index.html", // <- define path parameters + /// |r| r.method(http::Method::GET).with(index), + /// ); // <- use `with` extractor /// } /// ``` /// @@ -143,7 +143,7 @@ impl Route { /// # extern crate futures; /// #[macro_use] extern crate serde_derive; /// # use std::collections::HashMap; - /// use actix_web::{http, App, Query, Path, Result, Json}; + /// use actix_web::{http, App, Json, Path, Query, Result}; /// /// #[derive(Deserialize)] /// struct Info { @@ -151,14 +151,17 @@ impl Route { /// } /// /// /// extract path info using serde - /// fn index(info: (Path, Query>, Json)) -> Result { + /// fn index( + /// info: (Path, Query>, Json), + /// ) -> Result { /// Ok(format!("Welcome {}!", info.0.username)) /// } /// /// fn main() { /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor + /// "/{username}/index.html", // <- define path parameters + /// |r| r.method(http::Method::GET).with(index), + /// ); // <- use `with` extractor /// } /// ``` pub fn with(&mut self, handler: F) -> ExtractorConfig @@ -181,7 +184,7 @@ impl Route { /// # extern crate actix_web; /// # extern crate futures; /// #[macro_use] extern crate serde_derive; - /// use actix_web::{App, Path, Error, http}; + /// use actix_web::{http, App, Error, Path}; /// use futures::Future; /// /// #[derive(Deserialize)] @@ -190,15 +193,15 @@ impl Route { /// } /// /// /// extract path info using serde - /// fn index(info: Path) -> Box> { + /// fn index(info: Path) -> Box> { /// unimplemented!() /// } /// /// fn main() { /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET) - /// .with_async(index)); // <- use `with` extractor + /// "/{username}/index.html", // <- define path parameters + /// |r| r.method(http::Method::GET).with_async(index), + /// ); // <- use `with` extractor /// } /// ``` pub fn with_async(&mut self, handler: F) -> ExtractorConfig @@ -222,7 +225,7 @@ impl Route { /// # extern crate actix_web; /// # extern crate futures; /// #[macro_use] extern crate serde_derive; - /// use actix_web::{App, Query, Path, Result, http}; + /// use actix_web::{http, App, Path, Query, Result}; /// /// #[derive(Deserialize)] /// struct PParam { @@ -241,8 +244,9 @@ impl Route { /// /// fn main() { /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor + /// "/{username}/index.html", // <- define path parameters + /// |r| r.method(http::Method::GET).with2(index), + /// ); // <- use `with` extractor /// } /// ``` pub fn with2( diff --git a/src/scope.rs b/src/scope.rs index dba490b2..9452191f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -38,12 +38,12 @@ type NestedInfo = (Resource, Route, Vec>>); /// use actix_web::{http, App, HttpRequest, HttpResponse}; /// /// fn main() { -/// let app = App::new() -/// .scope("/{project_id}/", |scope| { -/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) -/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) -/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) -/// }); +/// let app = App::new().scope("/{project_id}/", |scope| { +/// scope +/// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) +/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) +/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) +/// }); /// } /// ``` /// @@ -89,13 +89,14 @@ impl Scope { /// } /// /// fn main() { - /// let app = App::new() - /// .scope("/app", |scope| { - /// scope.filter(pred::Header("content-type", "text/plain")) - /// .route("/test1", http::Method::GET, index) - /// .route("/test2", http::Method::POST, - /// |_: HttpRequest| HttpResponse::MethodNotAllowed()) - /// }); + /// let app = App::new().scope("/app", |scope| { + /// scope + /// .filter(pred::Header("content-type", "text/plain")) + /// .route("/test1", http::Method::GET, index) + /// .route("/test2", http::Method::POST, |_: HttpRequest| { + /// HttpResponse::MethodNotAllowed() + /// }) + /// }); /// } /// ``` pub fn filter + 'static>(mut self, p: T) -> Self { @@ -116,12 +117,11 @@ impl Scope { /// } /// /// fn main() { - /// let app = App::new() - /// .scope("/app", |scope| { - /// scope.with_state("/state2", AppState, |scope| { - /// scope.resource("/test1", |r| r.f(index)) - /// }) - /// }); + /// let app = App::new().scope("/app", |scope| { + /// scope.with_state("/state2", AppState, |scope| { + /// scope.resource("/test1", |r| r.f(index)) + /// }) + /// }); /// } /// ``` pub fn with_state(mut self, path: &str, state: T, f: F) -> Scope @@ -162,12 +162,9 @@ impl Scope { /// } /// /// fn main() { - /// let app = App::with_state(AppState) - /// .scope("/app", |scope| { - /// scope.nested("/v1", |scope| { - /// scope.resource("/test1", |r| r.f(index)) - /// }) - /// }); + /// let app = App::with_state(AppState).scope("/app", |scope| { + /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index))) + /// }); /// } /// ``` pub fn nested(mut self, path: &str, f: F) -> Scope @@ -211,12 +208,13 @@ impl Scope { /// } /// /// fn main() { - /// let app = App::new() - /// .scope("/app", |scope| { - /// scope.route("/test1", http::Method::GET, index) - /// .route("/test2", http::Method::POST, - /// |_: HttpRequest| HttpResponse::MethodNotAllowed()) - /// }); + /// let app = App::new().scope("/app", |scope| { + /// scope.route("/test1", http::Method::GET, index).route( + /// "/test2", + /// http::Method::POST, + /// |_: HttpRequest| HttpResponse::MethodNotAllowed(), + /// ) + /// }); /// } /// ``` pub fn route(mut self, path: &str, method: Method, f: F) -> Scope @@ -261,17 +259,16 @@ impl Scope { /// use actix_web::*; /// /// fn main() { - /// let app = App::new() - /// .scope("/api", |scope| { - /// scope.resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|_| HttpResponse::Ok()) - /// }) - /// }); + /// let app = App::new().scope("/api", |scope| { + /// scope.resource("/users/{userid}/{friend}", |r| { + /// r.get().f(|_| HttpResponse::Ok()); + /// r.head().f(|_| HttpResponse::MethodNotAllowed()); + /// r.route() + /// .filter(pred::Any(pred::Get()).or(pred::Put())) + /// .filter(pred::Header("Content-Type", "text/plain")) + /// .f(|_| HttpResponse::Ok()) + /// }) + /// }); /// } /// ``` pub fn resource(mut self, path: &str, f: F) -> Scope diff --git a/src/with.rs b/src/with.rs index ea549e31..f7d75e5d 100644 --- a/src/with.rs +++ b/src/with.rs @@ -19,7 +19,7 @@ use httpresponse::HttpResponse; /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Form, Result, http}; +/// use actix_web::{http, App, Form, Result}; /// /// #[derive(Deserialize)] /// struct FormData { @@ -32,10 +32,10 @@ use httpresponse::HttpResponse; /// /// fn main() { /// let app = App::new().resource( -/// "/index.html", |r| { -/// r.method(http::Method::GET) -/// .with(index) -/// .limit(4096);} // <- change form extractor configuration +/// "/index.html", +/// |r| { +/// r.method(http::Method::GET).with(index).limit(4096); +/// }, // <- change form extractor configuration /// ); /// } /// ``` @@ -45,7 +45,7 @@ use httpresponse::HttpResponse; /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Form, Path, Result, http}; +/// use actix_web::{http, App, Form, Path, Result}; /// /// #[derive(Deserialize)] /// struct FormData { @@ -58,10 +58,10 @@ use httpresponse::HttpResponse; /// /// fn main() { /// let app = App::new().resource( -/// "/index.html", |r| { -/// r.method(http::Method::GET) -/// .with(index) -/// .1.limit(4096);} // <- change form extractor configuration +/// "/index.html", +/// |r| { +/// r.method(http::Method::GET).with(index).1.limit(4096); +/// }, // <- change form extractor configuration /// ); /// } /// ``` From c8930b7b6b5022fc230acd3c587413a8aa13bb42 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Jun 2018 10:27:23 -0700 Subject: [PATCH 0286/1635] fix rustfmt formatting --- rustfmt.toml | 2 +- src/application.rs | 4 ++-- src/extractor.rs | 10 +++++----- src/httpmessage.rs | 2 +- src/httprequest.rs | 6 +++--- src/json.rs | 6 +++--- src/lib.rs | 4 ++-- src/middleware/session.rs | 6 +++--- src/resource.rs | 2 +- src/server/mod.rs | 4 ++-- src/server/srv.rs | 2 +- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 6db67ed2..4fff285e 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,5 @@ max_width = 89 reorder_imports = true -wrap_comments = true +#wrap_comments = true fn_args_density = "Compressed" #use_small_heuristics = false diff --git a/src/application.rs b/src/application.rs index ab1548bf..fffa5d83 100644 --- a/src/application.rs +++ b/src/application.rs @@ -634,7 +634,7 @@ where /// struct State2; /// /// fn main() { - /// //#### # thread::spawn(|| { + /// # thread::spawn(|| { /// server::new(|| { /// vec![ /// App::with_state(State1) @@ -649,7 +649,7 @@ where /// }).bind("127.0.0.1:8080") /// .unwrap() /// .run() - /// //#### # }); + /// # }); /// } /// ``` pub fn boxed(mut self) -> Box { diff --git a/src/extractor.rs b/src/extractor.rs index caf34332..3d77853a 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -120,13 +120,13 @@ where /// ## Example /// /// ```rust -/// //#### # extern crate bytes; -/// //#### # extern crate actix_web; -/// //#### # extern crate futures; -/// //#### #[macro_use] extern crate serde_derive; +/// # extern crate bytes; +/// # extern crate actix_web; +/// # extern crate futures; +/// #[macro_use] extern crate serde_derive; /// use actix_web::{App, Query, http}; /// -/// //#### #[derive(Deserialize)] +/// #[derive(Deserialize)] /// struct Info { /// username: String, /// } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 49d6a8a7..83994e34 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -231,7 +231,7 @@ pub trait HttpMessage { /// # extern crate futures; /// # use std::str; /// # use actix_web::*; - /// # use actix_web::actix::*; + /// # use actix_web::actix::fut::FinishStream; /// # use futures::{Future, Stream}; /// # use futures::future::{ok, result, Either}; /// fn index(mut req: HttpRequest) -> Box> { diff --git a/src/httprequest.rs b/src/httprequest.rs index b75bbbe4..0a14ca04 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -283,9 +283,9 @@ impl HttpRequest { /// Generate url for named resource /// /// ```rust - /// //#### # extern crate actix_web; - /// //#### # use actix_web::{App, HttpRequest, HttpResponse, http}; - /// //#### # + /// # extern crate actix_web; + /// # use actix_web::{App, HttpRequest, HttpResponse, http}; + /// # /// fn index(req: HttpRequest) -> HttpResponse { /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource /// HttpResponse::Ok().into() diff --git a/src/json.rs b/src/json.rs index 8e5cc293..d0e12c04 100644 --- a/src/json.rs +++ b/src/json.rs @@ -32,11 +32,11 @@ use httpresponse::HttpResponse; /// ## Example /// /// ```rust -/// //#### # extern crate actix_web; -/// //#### #[macro_use] extern crate serde_derive; +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; /// use actix_web::{App, Json, Result, http}; /// -/// //#### #[derive(Deserialize)] +/// #[derive(Deserialize)] /// struct Info { /// username: String, /// } diff --git a/src/lib.rs b/src/lib.rs index e8876a9c..6c765960 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,13 +10,13 @@ //! } //! //! fn main() { -//! //#### # thread::spawn(|| { +//! # thread::spawn(|| { //! server::new(|| { //! App::new().resource("/{name}/{id}/index.html", |r| r.with(index)) //! }).bind("127.0.0.1:8080") //! .unwrap() //! .run(); -//! //#### # }); +//! # }); //! } //! ``` //! diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 38be1235..bfc40dd5 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -32,8 +32,8 @@ //! session data. //! //! ```rust -//! //#### # extern crate actix_web; -//! use actix_web::{server, App, HttpRequest, Result}; +//! # extern crate actix_web; +//! use actix_web::{actix, server, App, HttpRequest, Result}; //! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; //! //! fn index(req: HttpRequest) -> Result<&'static str> { @@ -58,7 +58,7 @@ //! ))) //! .bind("127.0.0.1:59880").unwrap() //! .start(); -//! //#### # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); //! }); //! } //! ``` diff --git a/src/resource.rs b/src/resource.rs index df6a05f2..0920beeb 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -24,7 +24,7 @@ use route::Route; /// predicates route route considered matched and route handler get called. /// /// ```rust -/// //#### # extern crate actix_web; +/// # extern crate actix_web; /// use actix_web::{App, HttpResponse, http}; /// /// fn main() { diff --git a/src/server/mod.rs b/src/server/mod.rs index 44022931..cdfebab5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -42,7 +42,7 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// This is shortcut for `server::HttpServer::new()` method. /// /// ```rust -/// //#### # extern crate actix_web; +/// # extern crate actix_web; /// use actix_web::{actix, server, App, HttpResponse}; /// /// fn main() { @@ -54,7 +54,7 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// .bind("127.0.0.1:59090").unwrap() /// .start(); /// -/// //#### # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); /// }); /// } /// ``` diff --git a/src/server/srv.rs b/src/server/srv.rs index ba8c83e6..665babd7 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -418,7 +418,7 @@ impl HttpServer { /// .bind("127.0.0.1:0") /// .expect("Can not bind to 127.0.0.1:0") /// .start(); - /// //#### # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); + /// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); /// }); /// } /// ``` From 3e0a71101c0c604832a59ba2c030d0bc45991d31 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Jun 2018 10:54:23 -0700 Subject: [PATCH 0287/1635] drop with2 and with3 --- src/header/common/mod.rs | 2 - src/header/shared/charset.rs | 2 - src/header/shared/quality_item.rs | 2 - src/route.rs | 81 +----- src/with.rs | 450 ------------------------------ tests/test_handlers.rs | 29 +- 6 files changed, 17 insertions(+), 549 deletions(-) diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index e86bf896..08f8e0cc 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -74,8 +74,6 @@ macro_rules! test_header { ($id:ident, $raw:expr) => { #[test] fn $id() { - #[allow(unused, deprecated)] - use std::ascii::AsciiExt; use test; let raw = $raw; let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs index 21ee637b..540dc4f2 100644 --- a/src/header/shared/charset.rs +++ b/src/header/shared/charset.rs @@ -1,5 +1,3 @@ -#![allow(unused, deprecated)] -use std::ascii::AsciiExt; use std::fmt::{self, Display}; use std::str::FromStr; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs index a9488e81..80bd7e1c 100644 --- a/src/header/shared/quality_item.rs +++ b/src/header/shared/quality_item.rs @@ -1,5 +1,3 @@ -#![allow(unused, deprecated)] -use std::ascii::AsciiExt; use std::cmp; use std::default::Default; use std::fmt; diff --git a/src/route.rs b/src/route.rs index 040baa2d..3039d089 100644 --- a/src/route.rs +++ b/src/route.rs @@ -17,7 +17,7 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; -use with::{ExtractorConfig, With, With2, With3, WithAsync}; +use with::{ExtractorConfig, With, WithAsync}; /// Resource route definition /// @@ -216,85 +216,6 @@ impl Route { self.h(WithAsync::new(handler, Clone::clone(&cfg))); cfg } - - #[doc(hidden)] - /// Set handler function, use request extractor for both parameters. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Path, Query, Result}; - /// - /// #[derive(Deserialize)] - /// struct PParam { - /// username: String, - /// } - /// - /// #[derive(Deserialize)] - /// struct QParam { - /// count: u32, - /// } - /// - /// /// extract path and query information using serde - /// fn index(p: Path, q: Query) -> Result { - /// Ok(format!("Welcome {}!", p.username)) - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with2(index), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with2( - &mut self, handler: F, - ) -> (ExtractorConfig, ExtractorConfig) - where - F: Fn(T1, T2) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - { - let cfg1 = ExtractorConfig::default(); - let cfg2 = ExtractorConfig::default(); - self.h(With2::new( - handler, - Clone::clone(&cfg1), - Clone::clone(&cfg2), - )); - (cfg1, cfg2) - } - - #[doc(hidden)] - /// Set handler function, use request extractor for all parameters. - pub fn with3( - &mut self, handler: F, - ) -> ( - ExtractorConfig, - ExtractorConfig, - ExtractorConfig, - ) - where - F: Fn(T1, T2, T3) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - { - let cfg1 = ExtractorConfig::default(); - let cfg2 = ExtractorConfig::default(); - let cfg3 = ExtractorConfig::default(); - self.h(With3::new( - handler, - Clone::clone(&cfg1), - Clone::clone(&cfg2), - Clone::clone(&cfg3), - )); - (cfg1, cfg2, cfg3) - } } /// `RouteHandler` wrapper. This struct is required because it needs to be diff --git a/src/with.rs b/src/with.rs index f7d75e5d..c32f0a3b 100644 --- a/src/with.rs +++ b/src/with.rs @@ -361,453 +361,3 @@ where self.poll() } } - -pub struct With2 -where - F: Fn(T1, T2) -> R, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - S: 'static, -{ - hnd: Rc>, - cfg1: ExtractorConfig, - cfg2: ExtractorConfig, - _s: PhantomData, -} - -impl With2 -where - F: Fn(T1, T2) -> R, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - S: 'static, -{ - pub fn new( - f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig, - ) -> Self { - With2 { - hnd: Rc::new(UnsafeCell::new(f)), - cfg1, - cfg2, - _s: PhantomData, - } - } -} - -impl Handler for With2 -where - F: Fn(T1, T2) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&mut self, req: HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut2 { - req, - started: false, - hnd: Rc::clone(&self.hnd), - cfg1: self.cfg1.clone(), - cfg2: self.cfg2.clone(), - item: None, - fut1: None, - fut2: None, - fut3: None, - }; - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::ok(e), - } - } -} - -struct WithHandlerFut2 -where - F: Fn(T1, T2) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg1: ExtractorConfig, - cfg2: ExtractorConfig, - req: HttpRequest, - item: Option, - fut1: Option>>, - fut2: Option>>, - fut3: Option>>, -} - -impl Future for WithHandlerFut2 -where - F: Fn(T1, T2) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut3 { - return fut.poll(); - } - - if !self.started { - self.started = true; - let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into(); - let item1 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - }; - - let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); - let item2 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.item = Some(item1); - self.fut2 = Some(fut); - return self.poll(); - } - }; - - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(item1, item2).respond_to(&self.req) { - Ok(item) => match item.into().into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut3 = Some(fut); - return self.poll(); - } - }, - Err(e) => return Err(e.into()), - } - } - - if self.fut1.is_some() { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => { - let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); - let item2 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.item = Some(item); - self.fut2 = Some(fut); - return self.poll(); - } - }; - - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(item, item2).respond_to(&self.req) { - Ok(item) => match item.into().into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut3 = Some(fut); - return self.poll(); - } - }, - Err(e) => return Err(e.into()), - } - } - Async::NotReady => return Ok(Async::NotReady), - } - } - - let item = match self.fut2.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - }; - - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - let item = match (*hnd)(self.item.take().unwrap(), item).respond_to(&self.req) { - Ok(item) => item.into(), - Err(err) => return Err(err.into()), - }; - - match item.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => self.fut3 = Some(fut), - } - - self.poll() - } -} - -pub struct With3 -where - F: Fn(T1, T2, T3) -> R, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - S: 'static, -{ - hnd: Rc>, - cfg1: ExtractorConfig, - cfg2: ExtractorConfig, - cfg3: ExtractorConfig, - _s: PhantomData, -} - -impl With3 -where - F: Fn(T1, T2, T3) -> R, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - S: 'static, -{ - pub fn new( - f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig, - cfg3: ExtractorConfig, - ) -> Self { - With3 { - hnd: Rc::new(UnsafeCell::new(f)), - cfg1, - cfg2, - cfg3, - _s: PhantomData, - } - } -} - -impl Handler for With3 -where - F: Fn(T1, T2, T3) -> R + 'static, - R: Responder + 'static, - T1: FromRequest, - T2: FromRequest, - T3: FromRequest, - T1: 'static, - T2: 'static, - T3: 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&mut self, req: HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut3 { - req, - hnd: Rc::clone(&self.hnd), - cfg1: self.cfg1.clone(), - cfg2: self.cfg2.clone(), - cfg3: self.cfg3.clone(), - started: false, - item1: None, - item2: None, - fut1: None, - fut2: None, - fut3: None, - fut4: None, - }; - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithHandlerFut3 -where - F: Fn(T1, T2, T3) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - S: 'static, -{ - hnd: Rc>, - req: HttpRequest, - cfg1: ExtractorConfig, - cfg2: ExtractorConfig, - cfg3: ExtractorConfig, - started: bool, - item1: Option, - item2: Option, - fut1: Option>>, - fut2: Option>>, - fut3: Option>>, - fut4: Option>>, -} - -impl Future for WithHandlerFut3 -where - F: Fn(T1, T2, T3) -> R + 'static, - R: Responder + 'static, - T1: FromRequest + 'static, - T2: FromRequest + 'static, - T3: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut4 { - return fut.poll(); - } - - if !self.started { - self.started = true; - let reply = T1::from_request(&self.req, self.cfg1.as_ref()).into(); - let item1 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - }; - - let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); - let item2 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.item1 = Some(item1); - self.fut2 = Some(fut); - return self.poll(); - } - }; - - let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); - let item3 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.item1 = Some(item1); - self.item2 = Some(item2); - self.fut3 = Some(fut); - return self.poll(); - } - }; - - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(item1, item2, item3).respond_to(&self.req) { - Ok(item) => match item.into().into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut4 = Some(fut); - return self.poll(); - } - }, - Err(e) => return Err(e.into()), - } - } - - if self.fut1.is_some() { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => { - self.item1 = Some(item); - self.fut1.take(); - let reply = T2::from_request(&self.req, self.cfg2.as_ref()).into(); - let item2 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut2 = Some(fut); - return self.poll(); - } - }; - - let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); - let item3 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.item2 = Some(item2); - self.fut3 = Some(fut); - return self.poll(); - } - }; - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(self.item1.take().unwrap(), item2, item3) - .respond_to(&self.req) - { - Ok(item) => match item.into().into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut4 = Some(fut); - return self.poll(); - } - }, - Err(e) => return Err(e.into()), - } - } - Async::NotReady => return Ok(Async::NotReady), - } - } - - if self.fut2.is_some() { - match self.fut2.as_mut().unwrap().poll()? { - Async::Ready(item) => { - self.fut2.take(); - let reply = T3::from_request(&self.req, self.cfg3.as_ref()).into(); - let item3 = match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.item2 = Some(item); - self.fut3 = Some(fut); - return self.poll(); - } - }; - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - match (*hnd)(self.item1.take().unwrap(), item, item3) - .respond_to(&self.req) - { - Ok(item) => match item.into().into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut4 = Some(fut); - return self.poll(); - } - }, - Err(e) => return Err(e.into()), - } - } - Async::NotReady => return Ok(Async::NotReady), - } - } - - let item = match self.fut3.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - }; - - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - let item = - match (*hnd)(self.item1.take().unwrap(), self.item2.take().unwrap(), item) - .respond_to(&self.req) - { - Ok(item) => item.into(), - Err(err) => return Err(err.into()), - }; - - match item.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => self.fut4 = Some(fut), - } - - self.poll() - } -} diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 8544b9bf..5ece53ee 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -100,7 +100,7 @@ fn test_async_extractor_async() { fn test_path_and_query_extractor() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with2(|p: Path, q: Query| { + r.route().with(|(p, q): (Path, Query)| { format!("Welcome {} - {}!", p.username, q.username) }) }); @@ -134,7 +134,7 @@ fn test_path_and_query_extractor2() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { r.route() - .with3(|_: HttpRequest, p: Path, q: Query| { + .with(|(_r, p, q): (HttpRequest, Path, Query)| { format!("Welcome {} - {}!", p.username, q.username) }) }); @@ -167,14 +167,15 @@ fn test_path_and_query_extractor2() { fn test_path_and_query_extractor2_async() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route() - .with3(|p: Path, _: Query, data: Json| { + r.route().with( + |(p, _q, data): (Path, Query, Json)| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) .responder() - }) + }, + ) }); }); @@ -197,7 +198,7 @@ fn test_path_and_query_extractor2_async() { fn test_path_and_query_extractor3_async() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with2(|p: Path, data: Json| { + r.route().with(|(p, data): (Path, Json)| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) @@ -222,7 +223,7 @@ fn test_path_and_query_extractor3_async() { fn test_path_and_query_extractor4_async() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with2(|data: Json, p: Path| { + r.route().with(|(data, p): (Json, Path)| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) @@ -247,14 +248,15 @@ fn test_path_and_query_extractor4_async() { fn test_path_and_query_extractor2_async2() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route() - .with3(|p: Path, data: Json, _: Query| { + r.route().with( + |(p, data, _q): (Path, Json, Query)| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) .responder() - }) + }, + ) }); }); @@ -286,14 +288,15 @@ fn test_path_and_query_extractor2_async2() { fn test_path_and_query_extractor2_async3() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route() - .with3(|data: Json, p: Path, _: Query| { + r.route().with( + |(data, p, _q): (Json, Path, Query)| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) }) .responder() - }) + }, + ) }); }); From 009ee4b3db8bdcad5f465c44e91b6cee1f1a2303 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Jun 2018 10:55:54 -0700 Subject: [PATCH 0288/1635] update changelog --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c1f164b6..78db1afe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,10 @@ * Min rustc version is 1.26 +### Removed + +* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. + ## [0.6.10] - 2018-05-24 From 8452c7a0442a126ad1b1bd15bf372f2a5f0d0b47 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Jun 2018 11:22:40 -0700 Subject: [PATCH 0289/1635] fix doc api example --- src/client/connector.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/connector.rs b/src/client/connector.rs index 4f142fde..36d18052 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -289,6 +289,7 @@ impl ClientConnector { /// # use futures::{future, Future}; /// # use std::io::Write; /// # use std::process; + /// # use actix_web::actix::Actor; /// extern crate openssl; /// use actix_web::client::{ClientConnector, Connect}; /// From 8f42fec9b231503f24b7b5d92f44605928d66567 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Jun 2018 12:17:13 -0700 Subject: [PATCH 0290/1635] stable compat --- tests/test_server.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index ed2848f7..2eccadef 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -32,7 +32,7 @@ use tokio::executor::current_thread; use tokio::runtime::current_thread::Runtime; use tokio_tcp::TcpStream; -use actix::{msgs::SystemExit, Arbiter, System}; +use actix::{msgs, Arbiter, System}; use actix_web::*; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -110,7 +110,7 @@ fn test_start() { assert!(response.status().is_success()); } - let _ = sys.send(SystemExit(0)).wait(); + let _ = sys.send(msgs::SystemExit(0)).wait(); } #[test] @@ -148,7 +148,7 @@ fn test_shutdown() { thread::sleep(time::Duration::from_millis(1000)); assert!(net::TcpStream::connect(addr).is_err()); - let _ = sys.send(SystemExit(0)).wait(); + let _ = sys.send(msgs::SystemExit(0)).wait(); } #[test] From f414a491dd89976268b1fc62180e5beada0deef5 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sat, 2 Jun 2018 00:57:07 +0200 Subject: [PATCH 0291/1635] Fix some ResourceHandler docs Re-enables code blocks as doc tests to prevent them failing in the future. --- src/resource.rs | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/resource.rs b/src/resource.rs index 0920beeb..2e2b8c6b 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -130,8 +130,11 @@ impl ResourceHandler { /// /// This is shortcut for: /// - /// ```rust,ignore - /// Application::resource("/", |r| r.route().filter(pred::Get()).f(index) + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index)); /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); @@ -142,8 +145,11 @@ impl ResourceHandler { /// /// This is shortcut for: /// - /// ```rust,ignore - /// Application::resource("/", |r| r.route().h(handler) + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// # fn handler(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// App::new().resource("/", |r| r.route().h(handler)); /// ``` pub fn h>(&mut self, handler: H) { self.routes.push(Route::default()); @@ -154,8 +160,11 @@ impl ResourceHandler { /// /// This is shortcut for: /// - /// ```rust,ignore - /// Application::resource("/", |r| r.route().f(index) + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// App::new().resource("/", |r| r.route().f(index)); /// ``` pub fn f(&mut self, handler: F) where @@ -170,8 +179,11 @@ impl ResourceHandler { /// /// This is shortcut for: /// - /// ```rust,ignore - /// Application::resource("/", |r| r.route().with(index) + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// App::new().resource("/", |r| r.route().with(index)); /// ``` pub fn with(&mut self, handler: F) where @@ -187,8 +199,15 @@ impl ResourceHandler { /// /// This is shortcut for: /// - /// ```rust,ignore - /// Application::resource("/", |r| r.route().with_async(index) + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// # use actix_web::*; + /// # use futures::future::Future; + /// # fn index(req: HttpRequest) -> Box> { + /// # unimplemented!() + /// # } + /// App::new().resource("/", |r| r.route().with_async(index)); /// ``` pub fn with_async(&mut self, handler: F) where From d912bf8771adca12dfd70fc1def29982a29b2e1a Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sat, 2 Jun 2018 00:57:24 +0200 Subject: [PATCH 0292/1635] Add more docs to ResourceHandler API --- src/resource.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/resource.rs b/src/resource.rs index 2e2b8c6b..af3a9e67 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -128,6 +128,14 @@ impl ResourceHandler { /// Register a new route and add method check to route. /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// + /// App::new().resource("/", |r| r.method(http::Method::GET).f(index)); + /// ``` + /// /// This is shortcut for: /// /// ```rust @@ -143,6 +151,14 @@ impl ResourceHandler { /// Register a new route and add handler object. /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// fn handler(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// + /// App::new().resource("/", |r| r.h(handler)); + /// ``` + /// /// This is shortcut for: /// /// ```rust @@ -158,6 +174,14 @@ impl ResourceHandler { /// Register a new route and add handler function. /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// + /// App::new().resource("/", |r| r.f(index)); + /// ``` + /// /// This is shortcut for: /// /// ```rust @@ -177,6 +201,14 @@ impl ResourceHandler { /// Register a new route and add handler. /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// + /// App::new().resource("/", |r| r.with(index)); + /// ``` + /// /// This is shortcut for: /// /// ```rust @@ -197,6 +229,19 @@ impl ResourceHandler { /// Register a new route and add async handler. /// + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// use actix_web::*; + /// use futures::future::Future; + /// + /// fn index(req: HttpRequest) -> Box> { + /// unimplemented!() + /// } + /// + /// App::new().resource("/", |r| r.with_async(index)); + /// ``` + /// /// This is shortcut for: /// /// ```rust From 9c9eb6203155f62b181ff8ad1d1e3c4a529ddec4 Mon Sep 17 00:00:00 2001 From: Josh Leeb-du Toit Date: Sat, 2 Jun 2018 13:37:29 +1000 Subject: [PATCH 0293/1635] Update Middleware trait to use `&mut self` --- CHANGES.md | 2 ++ src/application.rs | 6 ++--- src/middleware/cors.rs | 18 ++++++------- src/middleware/csrf.rs | 12 ++++----- src/middleware/defaultheaders.rs | 4 +-- src/middleware/errhandlers.rs | 4 +-- src/middleware/identity.rs | 4 +-- src/middleware/logger.rs | 6 ++--- src/middleware/mod.rs | 6 ++--- src/middleware/session.rs | 4 +-- src/pipeline.rs | 33 ++++++++++++++--------- src/resource.rs | 10 ++++--- src/route.rs | 35 ++++++++++++++----------- src/scope.rs | 45 ++++++++++++++++++-------------- tests/test_middleware.rs | 12 ++++----- 15 files changed, 111 insertions(+), 90 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 78db1afe..5b9e16bd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Min rustc version is 1.26 +* Use `&mut self` instead of `&self` for Middleware trait + ### Removed * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. diff --git a/src/application.rs b/src/application.rs index fffa5d83..90c70bd1 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,4 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::collections::HashMap; use std::rc::Rc; @@ -22,7 +22,7 @@ pub struct HttpApplication { prefix_len: usize, router: Router, inner: Rc>>, - middlewares: Rc>>>, + middlewares: Rc>>>>, } pub(crate) struct Inner { @@ -612,7 +612,7 @@ where HttpApplication { state: Rc::new(parts.state), router: router.clone(), - middlewares: Rc::new(parts.middlewares), + middlewares: Rc::new(RefCell::new(parts.middlewares)), prefix, prefix_len, inner, diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 93c8aeb5..cc705c55 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -355,7 +355,7 @@ impl Cors { } impl Middleware for Cors { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&mut self, req: &mut HttpRequest) -> Result { if self.inner.preflight && Method::OPTIONS == *req.method() { self.validate_origin(req)?; self.validate_allowed_method(req)?; @@ -430,7 +430,7 @@ impl Middleware for Cors { } fn response( - &self, req: &mut HttpRequest, mut resp: HttpResponse, + &mut self, req: &mut HttpRequest, mut resp: HttpResponse, ) -> Result { match self.inner.origins { AllOrSome::All => { @@ -940,7 +940,7 @@ mod tests { #[test] fn validate_origin_allows_all_origins() { - let cors = Cors::default(); + let mut cors = Cors::default(); let mut req = TestRequest::with_header("Origin", "https://www.example.com").finish(); @@ -1009,7 +1009,7 @@ mod tests { #[test] #[should_panic(expected = "MissingOrigin")] fn test_validate_missing_origin() { - let cors = Cors::build() + let mut cors = Cors::build() .allowed_origin("https://www.example.com") .finish(); @@ -1020,7 +1020,7 @@ mod tests { #[test] #[should_panic(expected = "OriginNotAllowed")] fn test_validate_not_allowed_origin() { - let cors = Cors::build() + let mut cors = Cors::build() .allowed_origin("https://www.example.com") .finish(); @@ -1032,7 +1032,7 @@ mod tests { #[test] fn test_validate_origin() { - let cors = Cors::build() + let mut cors = Cors::build() .allowed_origin("https://www.example.com") .finish(); @@ -1045,7 +1045,7 @@ mod tests { #[test] fn test_no_origin_response() { - let cors = Cors::build().finish(); + let mut cors = Cors::build().finish(); let mut req = TestRequest::default().method(Method::GET).finish(); let resp: HttpResponse = HttpResponse::Ok().into(); @@ -1071,7 +1071,7 @@ mod tests { #[test] fn test_response() { - let cors = Cors::build() + let mut cors = Cors::build() .send_wildcard() .disable_preflight() .max_age(3600) @@ -1106,7 +1106,7 @@ mod tests { resp.headers().get(header::VARY).unwrap().as_bytes() ); - let cors = Cors::build() + let mut cors = Cors::build() .disable_vary_header() .allowed_origin("https://www.example.com") .finish(); diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 670ec1c1..8c2b06e7 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -209,7 +209,7 @@ impl CsrfFilter { } impl Middleware for CsrfFilter { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&mut self, req: &mut HttpRequest) -> Result { self.validate(req)?; Ok(Started::Done) } @@ -223,7 +223,7 @@ mod tests { #[test] fn test_safe() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); + let mut csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::HEAD) @@ -234,7 +234,7 @@ mod tests { #[test] fn test_csrf() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); + let mut csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::POST) @@ -245,7 +245,7 @@ mod tests { #[test] fn test_referer() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); + let mut csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header( "Referer", @@ -258,9 +258,9 @@ mod tests { #[test] fn test_upgrade() { - let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); + let mut strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - let lax_csrf = CsrfFilter::new() + let mut lax_csrf = CsrfFilter::new() .allowed_origin("https://www.example.com") .allow_upgrade(); diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index dca8dfbe..acccc552 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -75,7 +75,7 @@ impl DefaultHeaders { impl Middleware for DefaultHeaders { fn response( - &self, _: &mut HttpRequest, mut resp: HttpResponse, + &mut self, _: &mut HttpRequest, mut resp: HttpResponse, ) -> Result { for (key, value) in self.headers.iter() { if !resp.headers().contains_key(key) { @@ -100,7 +100,7 @@ mod tests { #[test] fn test_default_headers() { - let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); + let mut mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); let mut req = HttpRequest::default(); diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 42f75a3d..7e56b368 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -71,7 +71,7 @@ impl ErrorHandlers { impl Middleware for ErrorHandlers { fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, + &mut self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(handler) = self.handlers.get(&resp.status()) { handler(req, resp) @@ -95,7 +95,7 @@ mod tests { #[test] fn test_handler() { - let mw = + let mut mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); let mut req = HttpRequest::default(); diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index c8505d68..76d1894e 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -175,7 +175,7 @@ unsafe impl Send for IdentityBox {} unsafe impl Sync for IdentityBox {} impl> Middleware for IdentityService { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&mut self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); let fut = self @@ -192,7 +192,7 @@ impl> Middleware for IdentityService { } fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, + &mut self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(mut id) = req.extensions_mut().remove::() { id.0.write(resp) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index a731d695..ab9ae4a0 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -124,14 +124,14 @@ impl Logger { } impl Middleware for Logger { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&mut self, req: &mut HttpRequest) -> Result { if !self.exclude.contains(req.path()) { req.extensions_mut().insert(StartTime(time::now())); } Ok(Started::Done) } - fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { + fn finish(&mut self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { self.log(req, resp); Finished::Done } @@ -322,7 +322,7 @@ mod tests { #[test] fn test_logger() { - let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); + let mut logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); let mut headers = HeaderMap::new(); headers.insert( diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 2551ded1..7fd33932 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -51,20 +51,20 @@ pub enum Finished { pub trait Middleware: 'static { /// 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) -> Result { + fn start(&mut self, req: &mut HttpRequest) -> Result { Ok(Started::Done) } /// Method is called when handler returns response, /// but before sending http message to peer. fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, + &mut self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { Ok(Response::Done(resp)) } /// Method is called after body stream get sent to peer. - fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { + fn finish(&mut self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { Finished::Done } } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index bfc40dd5..5c986767 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -250,7 +250,7 @@ impl> SessionStorage { } impl> Middleware for SessionStorage { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&mut self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); let fut = self.0.from_request(&mut req).then(move |res| match res { @@ -265,7 +265,7 @@ impl> Middleware for SessionStorage { } fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, + &mut self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(s_box) = req.extensions_mut().remove::>() { s_box.0.borrow_mut().write(resp) diff --git a/src/pipeline.rs b/src/pipeline.rs index 289c5fcb..f9ec9713 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,4 +1,4 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::marker::PhantomData; use std::rc::Rc; use std::{io, mem}; @@ -71,7 +71,7 @@ impl> PipelineState { struct PipelineInfo { req: UnsafeCell>, count: u16, - mws: Rc>>>, + mws: Rc>>>>, context: Option>, error: Option, disconnected: Option, @@ -83,7 +83,7 @@ impl PipelineInfo { PipelineInfo { req: UnsafeCell::new(req), count: 0, - mws: Rc::new(Vec::new()), + mws: Rc::new(RefCell::new(Vec::new())), error: None, context: None, disconnected: None, @@ -120,7 +120,7 @@ impl PipelineInfo { impl> Pipeline { pub fn new( - req: HttpRequest, mws: Rc>>>, + req: HttpRequest, mws: Rc>>>>, handler: Rc>, htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { @@ -243,13 +243,14 @@ impl> StartMiddlewares { ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately - let len = info.mws.len() as u16; + let len = info.mws.borrow().len() as u16; loop { if info.count == len { let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype); return WaitingResponse::init(info, reply); } else { - match info.mws[info.count as usize].start(info.req_mut()) { + let state = info.mws.borrow_mut()[info.count as usize].start(info.req_mut()); + match state { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { return RunMiddlewares::init(info, resp) @@ -269,7 +270,7 @@ impl> StartMiddlewares { } fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - let len = info.mws.len() as u16; + let len = info.mws.borrow().len() as u16; 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return None, @@ -284,7 +285,9 @@ impl> StartMiddlewares { .handle(info.req().clone(), self.htype); return Some(WaitingResponse::init(info, reply)); } else { - match info.mws[info.count as usize].start(info.req_mut()) { + let state = info.mws.borrow_mut()[info.count as usize] + .start(info.req_mut()); + match state { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { return Some(RunMiddlewares::init(info, resp)); @@ -353,10 +356,11 @@ impl RunMiddlewares { return ProcessResponse::init(resp); } let mut curr = 0; - let len = info.mws.len(); + let len = info.mws.borrow().len(); loop { - resp = match info.mws[curr].response(info.req_mut(), resp) { + let state = info.mws.borrow_mut()[curr].response(info.req_mut(), resp); + resp = match state { Err(err) => { info.count = (curr + 1) as u16; return ProcessResponse::init(err.into()); @@ -382,7 +386,7 @@ impl RunMiddlewares { } fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - let len = info.mws.len(); + let len = info.mws.borrow().len(); loop { // poll latest fut @@ -399,7 +403,8 @@ impl RunMiddlewares { if self.curr == len { return Some(ProcessResponse::init(resp)); } else { - match info.mws[self.curr].response(info.req_mut(), resp) { + let state = info.mws.borrow_mut()[self.curr].response(info.req_mut(), resp); + match state { Err(err) => return Some(ProcessResponse::init(err.into())), Ok(Response::Done(r)) => { self.curr += 1; @@ -723,7 +728,9 @@ impl FinishingMiddlewares { } info.count -= 1; - match info.mws[info.count as usize].finish(info.req_mut(), &self.resp) { + let state = info.mws.borrow_mut()[info.count as usize] + .finish(info.req_mut(), &self.resp); + match state { Finished::Done => { if info.count == 0 { return Some(Completed::init(info)); diff --git a/src/resource.rs b/src/resource.rs index af3a9e67..2b9c8538 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,5 +1,6 @@ use std::marker::PhantomData; use std::rc::Rc; +use std::cell::RefCell; use futures::Future; use http::{Method, StatusCode}; @@ -37,7 +38,7 @@ pub struct ResourceHandler { name: String, state: PhantomData, routes: SmallVec<[Route; 3]>, - middlewares: Rc>>>, + middlewares: Rc>>>>, } impl Default for ResourceHandler { @@ -46,7 +47,7 @@ impl Default for ResourceHandler { name: String::new(), state: PhantomData, routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()), + middlewares: Rc::new(RefCell::new(Vec::new())), } } } @@ -57,7 +58,7 @@ impl ResourceHandler { name: String::new(), state: PhantomData, routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()), + middlewares: Rc::new(RefCell::new(Vec::new())), } } @@ -276,6 +277,7 @@ impl ResourceHandler { pub fn middleware>(&mut self, mw: M) { Rc::get_mut(&mut self.middlewares) .unwrap() + .borrow_mut() .push(Box::new(mw)); } @@ -284,7 +286,7 @@ impl ResourceHandler { ) -> AsyncResult { for route in &mut self.routes { if route.check(&mut req) { - return if self.middlewares.is_empty() { + return if self.middlewares.borrow().is_empty() { route.handle(req) } else { route.compose(req, Rc::clone(&self.middlewares)) diff --git a/src/route.rs b/src/route.rs index 3039d089..27aae8df 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,4 +1,4 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::marker::PhantomData; use std::rc::Rc; @@ -55,7 +55,7 @@ impl Route { #[inline] pub(crate) fn compose( - &mut self, req: HttpRequest, mws: Rc>>>, + &mut self, req: HttpRequest, mws: Rc>>>>, ) -> AsyncResult { AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) } @@ -263,7 +263,7 @@ struct Compose { struct ComposeInfo { count: usize, req: HttpRequest, - mws: Rc>>>, + mws: Rc>>>>, handler: InnerHandler, } @@ -289,7 +289,7 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>, handler: InnerHandler, + req: HttpRequest, mws: Rc>>>>, handler: InnerHandler, ) -> Self { let mut info = ComposeInfo { count: 0, @@ -332,13 +332,14 @@ type Fut = Box, Error = Error>>; impl StartMiddlewares { fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); + let len = info.mws.borrow().len(); loop { if info.count == len { let reply = info.handler.handle(info.req.clone()); return WaitingResponse::init(info, reply); } else { - match info.mws[info.count].start(&mut info.req) { + let state = info.mws.borrow_mut()[info.count].start(&mut info.req); + match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return RunMiddlewares::init(info, resp) @@ -356,7 +357,7 @@ impl StartMiddlewares { } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); + let len = info.mws.borrow().len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return None, @@ -370,7 +371,8 @@ impl StartMiddlewares { let reply = info.handler.handle(info.req.clone()); return Some(WaitingResponse::init(info, reply)); } else { - match info.mws[info.count].start(&mut info.req) { + let state = info.mws.borrow_mut()[info.count].start(&mut info.req); + match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return Some(RunMiddlewares::init(info, resp)); @@ -435,10 +437,11 @@ struct RunMiddlewares { impl RunMiddlewares { fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { let mut curr = 0; - let len = info.mws.len(); + let len = info.mws.borrow().len(); loop { - resp = match info.mws[curr].response(&mut info.req, resp) { + let state = info.mws.borrow_mut()[curr].response(&mut info.req, resp); + resp = match state { Err(err) => { info.count = curr + 1; return FinishingMiddlewares::init(info, err.into()); @@ -463,7 +466,7 @@ impl RunMiddlewares { } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); + let len = info.mws.borrow().len(); loop { // poll latest fut @@ -480,7 +483,8 @@ impl RunMiddlewares { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { - match info.mws[self.curr].response(&mut info.req, resp) { + let state = info.mws.borrow_mut()[self.curr].response(&mut info.req, resp); + match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) } @@ -548,9 +552,10 @@ impl FinishingMiddlewares { } info.count -= 1; - match info.mws[info.count as usize] - .finish(&mut info.req, self.resp.as_ref().unwrap()) - { + + let state = info.mws.borrow_mut()[info.count as usize] + .finish(&mut info.req, self.resp.as_ref().unwrap()); + match state { MiddlewareFinished::Done => { if info.count == 0 { return Some(Response::init(self.resp.take().unwrap())); diff --git a/src/scope.rs b/src/scope.rs index 9452191f..8f8d6995 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,4 +1,4 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::marker::PhantomData; use std::mem; use std::rc::Rc; @@ -56,7 +56,7 @@ type NestedInfo = (Resource, Route, Vec>>); pub struct Scope { filters: Vec>>, nested: Vec>, - middlewares: Rc>>>, + middlewares: Rc>>>>, default: Rc>>, resources: ScopeResources, } @@ -68,7 +68,7 @@ impl Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), - middlewares: Rc::new(Vec::new()), + middlewares: Rc::new(RefCell::new(Vec::new())), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), } } @@ -132,7 +132,7 @@ impl Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), - middlewares: Rc::new(Vec::new()), + middlewares: Rc::new(RefCell::new(Vec::new())), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), }; let mut scope = f(scope); @@ -175,7 +175,7 @@ impl Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), - middlewares: Rc::new(Vec::new()), + middlewares: Rc::new(RefCell::new(Vec::new())), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), }; let mut scope = f(scope); @@ -312,6 +312,7 @@ impl Scope { pub fn middleware>(mut self, mw: M) -> Scope { Rc::get_mut(&mut self.middlewares) .expect("Can not use after configuration") + .borrow_mut() .push(Box::new(mw)); self } @@ -327,7 +328,7 @@ impl RouteHandler for Scope { let default = unsafe { &mut *self.default.as_ref().get() }; req.match_info_mut().remove("tail"); - if self.middlewares.is_empty() { + if self.middlewares.borrow().is_empty() { let resource = unsafe { &mut *resource.get() }; return resource.handle(req, Some(default)); } else { @@ -369,7 +370,7 @@ impl RouteHandler for Scope { // default handler let default = unsafe { &mut *self.default.as_ref().get() }; - if self.middlewares.is_empty() { + if self.middlewares.borrow().is_empty() { default.handle(req, None) } else { AsyncResult::async(Box::new(Compose::new( @@ -419,7 +420,7 @@ struct Compose { struct ComposeInfo { count: usize, req: HttpRequest, - mws: Rc>>>, + mws: Rc>>>>, default: Option>>>, resource: Rc>>, } @@ -446,7 +447,7 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>, + req: HttpRequest, mws: Rc>>>>, resource: Rc>>, default: Option>>>, ) -> Self { @@ -492,7 +493,7 @@ type Fut = Box, Error = Error>>; impl StartMiddlewares { fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); + let len = info.mws.borrow().len(); loop { if info.count == len { let resource = unsafe { &mut *info.resource.get() }; @@ -504,7 +505,8 @@ impl StartMiddlewares { }; return WaitingResponse::init(info, reply); } else { - match info.mws[info.count].start(&mut info.req) { + let state = info.mws.borrow_mut()[info.count].start(&mut info.req); + match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return RunMiddlewares::init(info, resp) @@ -522,7 +524,7 @@ impl StartMiddlewares { } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); + let len = info.mws.borrow().len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return None, @@ -542,7 +544,8 @@ impl StartMiddlewares { }; return Some(WaitingResponse::init(info, reply)); } else { - match info.mws[info.count].start(&mut info.req) { + let state = info.mws.borrow_mut()[info.count].start(&mut info.req); + match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return Some(RunMiddlewares::init(info, resp)); @@ -602,10 +605,11 @@ struct RunMiddlewares { impl RunMiddlewares { fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { let mut curr = 0; - let len = info.mws.len(); + let len = info.mws.borrow().len(); loop { - resp = match info.mws[curr].response(&mut info.req, resp) { + let state = info.mws.borrow_mut()[curr].response(&mut info.req, resp); + resp = match state { Err(err) => { info.count = curr + 1; return FinishingMiddlewares::init(info, err.into()); @@ -630,7 +634,7 @@ impl RunMiddlewares { } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); + let len = info.mws.borrow().len(); loop { // poll latest fut @@ -647,7 +651,8 @@ impl RunMiddlewares { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { - match info.mws[self.curr].response(&mut info.req, resp) { + let state = info.mws.borrow_mut()[self.curr].response(&mut info.req, resp); + match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) } @@ -715,9 +720,9 @@ impl FinishingMiddlewares { } info.count -= 1; - match info.mws[info.count as usize] - .finish(&mut info.req, self.resp.as_ref().unwrap()) - { + let state = info.mws.borrow_mut()[info.count as usize] + .finish(&mut info.req, self.resp.as_ref().unwrap()); + match state { MiddlewareFinished::Done => { if info.count == 0 { return Some(Response::init(self.resp.take().unwrap())); diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 4996542e..3f802b3a 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -19,21 +19,21 @@ struct MiddlewareTest { } impl middleware::Middleware for MiddlewareTest { - fn start(&self, _: &mut HttpRequest) -> Result { + fn start(&mut self, _: &mut HttpRequest) -> Result { self.start .store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Started::Done) } fn response( - &self, _: &mut HttpRequest, resp: HttpResponse, + &mut self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { self.response .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Response::Done(resp)) } - fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { + fn finish(&mut self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { self.finish .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middleware::Finished::Done @@ -431,7 +431,7 @@ struct MiddlewareAsyncTest { } impl middleware::Middleware for MiddlewareAsyncTest { - fn start(&self, _: &mut HttpRequest) -> Result { + fn start(&mut self, _: &mut HttpRequest) -> Result { let to = Delay::new(Instant::now() + Duration::from_millis(10)); let start = Arc::clone(&self.start); @@ -444,7 +444,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { } fn response( - &self, _: &mut HttpRequest, resp: HttpResponse, + &mut self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { let to = Delay::new(Instant::now() + Duration::from_millis(10)); @@ -457,7 +457,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { ))) } - fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { + fn finish(&mut self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { let to = Delay::new(Instant::now() + Duration::from_millis(10)); let finish = Arc::clone(&self.finish); From 47b7be4fd396f15248a03cc8f81e743cda01f35e Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sat, 2 Jun 2018 15:50:45 +0200 Subject: [PATCH 0294/1635] Add warning for missing API docs --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 6c765960..a1b61105 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,7 @@ feature = "cargo-clippy", allow(decimal_literal_representation, suspicious_arithmetic_impl) )] +#![warn(missing_docs)] #[macro_use] extern crate log; From 890a7e70d68a86fddbd2b44ddc37184204d67393 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sat, 2 Jun 2018 15:51:58 +0200 Subject: [PATCH 0295/1635] Add missing API docs These were written without much knowledge of the actix-web internals! Please review carefully! --- src/body.rs | 2 ++ src/client/connector.rs | 11 +++++++++++ src/context.rs | 7 +++++++ src/error.rs | 4 ++++ src/fs.rs | 4 ++++ src/handler.rs | 1 + src/header/common/date.rs | 1 + src/header/mod.rs | 2 ++ src/httpcodes.rs | 2 +- src/httpmessage.rs | 1 + src/lib.rs | 1 + src/middleware/cors.rs | 1 + src/middleware/identity.rs | 8 ++++++++ src/middleware/session.rs | 1 + src/multipart.rs | 2 ++ src/router.rs | 3 +++ src/scope.rs | 2 ++ src/server/mod.rs | 1 + src/server/srv.rs | 2 +- src/test.rs | 1 + src/ws/client.rs | 16 ++++++++++++++++ src/ws/context.rs | 3 +++ src/ws/mod.rs | 5 +++++ src/ws/proto.rs | 3 +++ 24 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/body.rs b/src/body.rs index 3838d8d0..a93db1e9 100644 --- a/src/body.rs +++ b/src/body.rs @@ -127,11 +127,13 @@ impl From> for Body { impl Binary { #[inline] + /// Returns `true` if body is empty pub fn is_empty(&self) -> bool { self.len() == 0 } #[inline] + /// Length of body in bytes pub fn len(&self) -> usize { match *self { Binary::Bytes(ref bytes) => bytes.len(), diff --git a/src/client/connector.rs b/src/client/connector.rs index 36d18052..431d4df3 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -31,11 +31,17 @@ use {HAS_OPENSSL, HAS_TLS}; /// Client connector usage stats #[derive(Default, Message)] pub struct ClientConnectorStats { + /// Number of waited-on connections pub waits: usize, + /// Number of reused connections pub reused: usize, + /// Number of opened connections pub opened: usize, + /// Number of closed connections pub closed: usize, + /// Number of connections with errors pub errors: usize, + /// Number of connection timeouts pub timeouts: usize, } @@ -1059,6 +1065,7 @@ impl Drop for AcquiredConn { } } +/// HTTP client connection pub struct Connection { key: Key, stream: Box, @@ -1082,20 +1089,24 @@ impl Connection { } } + /// Raw IO stream pub fn stream(&mut self) -> &mut IoStream { &mut *self.stream } + /// Create a new connection from an IO Stream pub fn from_stream(io: T) -> Connection { Connection::new(Key::empty(), None, Box::new(io)) } + /// Close connection pool pub fn close(mut self) { if let Some(mut pool) = self.pool.take() { pool.close(self) } } + /// Release this connection from the connection pool pub fn release(mut self) { if let Some(mut pool) = self.pool.take() { pool.release(self) diff --git a/src/context.rs b/src/context.rs index 3f8cf5ee..e56c1737 100644 --- a/src/context.rs +++ b/src/context.rs @@ -102,9 +102,12 @@ where A: Actor, { #[inline] + /// Create a new HTTP Context from a request and an actor pub fn new(req: HttpRequest, actor: A) -> HttpContext { HttpContext::from_request(req).actor(actor) } + + /// Create a new HTTP Context from a request pub fn from_request(req: HttpRequest) -> HttpContext { HttpContext { inner: ContextImpl::new(None), @@ -113,7 +116,9 @@ where disconnected: false, } } + #[inline] + /// Set the actor of this context pub fn actor(mut self, actor: A) -> HttpContext { self.inner.set_actor(actor); self @@ -239,12 +244,14 @@ where } } +/// Consume a future pub struct Drain { fut: oneshot::Receiver<()>, _a: PhantomData, } impl Drain { + /// Create a drain from a future pub fn new(fut: oneshot::Receiver<()>) -> Self { Drain { fut, diff --git a/src/error.rs b/src/error.rs index a4c513fb..481cf326 100644 --- a/src/error.rs +++ b/src/error.rs @@ -521,12 +521,16 @@ impl ResponseError for UriSegmentError { /// Errors which can occur when attempting to generate resource uri. #[derive(Fail, Debug, PartialEq)] pub enum UrlGenerationError { + /// Resource not found #[fail(display = "Resource not found")] ResourceNotFound, + /// Not all path pattern covered #[fail(display = "Not all path pattern covered")] NotEnoughElements, + /// Router is not available #[fail(display = "Router is not available")] RouterNotAvailable, + /// URL parse error #[fail(display = "{}", _0)] ParseError(#[cause] UrlParseError), } diff --git a/src/fs.rs b/src/fs.rs index 8a362107..a4418bce 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -422,15 +422,19 @@ type DirectoryRenderer = /// A directory; responds with the generated directory listing. #[derive(Debug)] pub struct Directory { + /// Base directory pub base: PathBuf, + /// Path of subdirectory to generate listing for pub path: PathBuf, } impl Directory { + /// Create a new directory pub fn new(base: PathBuf, path: PathBuf) -> Directory { Directory { base, path } } + /// Is this entry visible from this directory? pub fn is_visible(&self, entry: &io::Result) -> bool { if let Ok(ref entry) = *entry { if let Some(name) = entry.file_name().to_str() { diff --git a/src/handler.rs b/src/handler.rs index bae50937..8b550059 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -162,6 +162,7 @@ where /// # fn main() {} /// ``` pub trait AsyncResponder: Sized { + /// Convert to a boxed future fn responder(self) -> Box>; } diff --git a/src/header/common/date.rs b/src/header/common/date.rs index d130f064..88a47bc3 100644 --- a/src/header/common/date.rs +++ b/src/header/common/date.rs @@ -35,6 +35,7 @@ header! { } impl Date { + /// Create a date instance set to the current system time pub fn now() -> Date { Date(SystemTime::now().into()) } diff --git a/src/header/mod.rs b/src/header/mod.rs index 6b98d324..a9c42e29 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -127,6 +127,7 @@ pub enum ContentEncoding { impl ContentEncoding { #[inline] + /// Is the content compressed? pub fn is_compression(&self) -> bool { match *self { ContentEncoding::Identity | ContentEncoding::Auto => false, @@ -135,6 +136,7 @@ impl ContentEncoding { } #[inline] + /// Convert content encoding to string pub fn as_str(&self) -> &'static str { match *self { #[cfg(feature = "brotli")] diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 2933cf17..41e57d1e 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -5,7 +5,7 @@ use httpresponse::{HttpResponse, HttpResponseBuilder}; macro_rules! STATIC_RESP { ($name:ident, $status:expr) => { - #[allow(non_snake_case)] + #[allow(non_snake_case, missing_docs)] pub fn $name() -> HttpResponseBuilder { HttpResponse::build($status) } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 83994e34..1e57d386 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -341,6 +341,7 @@ pub struct UrlEncoded { } impl UrlEncoded { + /// Create a new future to URL encode a request pub fn new(req: T) -> UrlEncoded { UrlEncoded { req: Some(req), diff --git a/src/lib.rs b/src/lib.rs index a1b61105..88c6bd4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -256,6 +256,7 @@ pub mod http { pub use helpers::NormalizePath; + /// Various http headers pub mod header { pub use header::*; } diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 93c8aeb5..b9f99f72 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -207,6 +207,7 @@ impl Default for Cors { } impl Cors { + /// Build a new CORS middleware instance pub fn build() -> CorsBuilder<()> { CorsBuilder { cors: Some(Inner { diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index c8505d68..c77f2c1c 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -121,10 +121,15 @@ impl RequestIdentity for HttpRequest { /// An identity pub trait Identity: 'static { + /// Return the claimed identity of the user associated request or + /// ``None`` if no identity can be found associated with the request. fn identity(&self) -> Option<&str>; + /// Remember identity. fn remember(&mut self, key: String); + /// This method is used to 'forget' the current identity on subsequent + /// requests. fn forget(&mut self); /// Write session to storage backend. @@ -133,7 +138,10 @@ pub trait Identity: 'static { /// Identity policy definition. pub trait IdentityPolicy: Sized + 'static { + /// The associated identity type Identity: Identity; + + /// The return type of the middleware type Future: Future; /// Parse the session from request and load data from a service identity. diff --git a/src/middleware/session.rs b/src/middleware/session.rs index bfc40dd5..1e8686c0 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -103,6 +103,7 @@ use middleware::{Middleware, Response, Started}; /// # fn main() {} /// ``` pub trait RequestSession { + /// Get the session from the request fn session(&self) -> Session; } diff --git a/src/multipart.rs b/src/multipart.rs index 106961ae..37244d80 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -399,10 +399,12 @@ where } } + /// Get a map of headers pub fn headers(&self) -> &HeaderMap { &self.headers } + /// Get the content type of the field pub fn content_type(&self) -> &mime::Mime { &self.ct } diff --git a/src/router.rs b/src/router.rs index 81a78e57..f65e3155 100644 --- a/src/router.rs +++ b/src/router.rs @@ -251,6 +251,7 @@ impl Resource { &self.pattern } + /// Is this path a match against this resource? pub fn is_match(&self, path: &str) -> bool { match self.tp { PatternType::Static(ref s) => s == path, @@ -259,6 +260,7 @@ impl Resource { } } + /// Are the given path and parameters a match against this resource? pub fn match_with_params<'a>( &'a self, path: &'a str, params: &'a mut Params<'a>, ) -> bool { @@ -284,6 +286,7 @@ impl Resource { } } + /// Is the given path a prefix match and do the parameters match against this resource? pub fn match_prefix_with_params<'a>( &'a self, path: &'a str, params: &'a mut Params<'a>, ) -> Option { diff --git a/src/scope.rs b/src/scope.rs index 9452191f..c103f2f8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -63,6 +63,8 @@ pub struct Scope { #[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] impl Scope { + /// Create a new scope + // TODO: Why is this not exactly the default impl? pub fn new() -> Scope { Scope { filters: Vec::new(), diff --git a/src/server/mod.rs b/src/server/mod.rs index cdfebab5..8a3f0364 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -111,6 +111,7 @@ pub struct ResumeServer; /// /// If server starts with `spawn()` method, then spawned thread get terminated. pub struct StopServer { + /// Whether to try and shut down gracefully pub graceful: bool, } diff --git a/src/server/srv.rs b/src/server/srv.rs index 665babd7..24874f15 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -686,7 +686,7 @@ where { type Result = (); - fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { + fn handle(&mut self, _msg: Conn, _: &mut Context) -> Self::Result { unimplemented!(); /*Arbiter::spawn(HttpChannel::new( Rc::clone(self.h.as_ref().unwrap()), diff --git a/src/test.rs b/src/test.rs index ced447f5..d8fd5773 100644 --- a/src/test.rs +++ b/src/test.rs @@ -249,6 +249,7 @@ pub struct TestServerBuilder { } impl TestServerBuilder { + /// Create a new test server pub fn new(state: F) -> TestServerBuilder where F: Fn() -> S + Sync + Send + 'static, diff --git a/src/ws/client.rs b/src/ws/client.rs index f5541beb..d1f1402a 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -34,32 +34,46 @@ use super::{Message, ProtocolError, WsWriter}; /// Websocket client error #[derive(Fail, Debug)] pub enum ClientError { + /// Invalid url #[fail(display = "Invalid url")] InvalidUrl, + /// Invalid response status #[fail(display = "Invalid response status")] InvalidResponseStatus(StatusCode), + /// Invalid upgrade header #[fail(display = "Invalid upgrade header")] InvalidUpgradeHeader, + /// Invalid connection header #[fail(display = "Invalid connection header")] InvalidConnectionHeader(HeaderValue), + /// Missing CONNECTION header #[fail(display = "Missing CONNECTION header")] MissingConnectionHeader, + /// Missing SEC-WEBSOCKET-ACCEPT header #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] MissingWebSocketAcceptHeader, + /// Invalid challenge response #[fail(display = "Invalid challenge response")] InvalidChallengeResponse(String, HeaderValue), + /// Http parsing error #[fail(display = "Http parsing error")] Http(Error), + /// Url parsing error #[fail(display = "Url parsing error")] Url(UrlParseError), + /// Response parsing error #[fail(display = "Response parsing error")] ResponseParseError(HttpResponseParserError), + /// Send request error #[fail(display = "{}", _0)] SendRequest(SendRequestError), + /// Protocol error #[fail(display = "{}", _0)] Protocol(#[cause] ProtocolError), + /// IO Error #[fail(display = "{}", _0)] Io(io::Error), + /// "Disconnected" #[fail(display = "Disconnected")] Disconnected, } @@ -419,6 +433,7 @@ impl Future for ClientHandshake { } } +/// Websocket reader client pub struct ClientReader { inner: Rc>, max_size: usize, @@ -497,6 +512,7 @@ impl Stream for ClientReader { } } +/// Websocket writer client pub struct ClientWriter { inner: Rc>, } diff --git a/src/ws/context.rs b/src/ws/context.rs index 48f37f22..9237eab4 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -86,10 +86,12 @@ where A: Actor, { #[inline] + /// Create a new Websocket context from a request and an actor pub fn new(req: HttpRequest, actor: A) -> WebsocketContext { WebsocketContext::from_request(req).actor(actor) } + /// Create a new Websocket context from a request pub fn from_request(req: HttpRequest) -> WebsocketContext { WebsocketContext { inner: ContextImpl::new(None), @@ -100,6 +102,7 @@ where } #[inline] + /// Set actor for this execution context pub fn actor(mut self, actor: A) -> WebsocketContext { self.inner.set_actor(actor); self diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 4c079dad..558ecb51 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -158,10 +158,15 @@ impl ResponseError for HandshakeError { /// `WebSocket` Message #[derive(Debug, PartialEq, Message)] pub enum Message { + /// Text message Text(String), + /// Binary message Binary(Binary), + /// Ping message Ping(String), + /// Pong message Pong(String), + /// Close message with optional reason Close(Option), } diff --git a/src/ws/proto.rs b/src/ws/proto.rs index 6e07ca7e..35fbbe3e 100644 --- a/src/ws/proto.rs +++ b/src/ws/proto.rs @@ -180,8 +180,11 @@ impl From for CloseCode { } #[derive(Debug, Eq, PartialEq, Clone)] +/// Reason for closing the connection pub struct CloseReason { + /// Exit code pub code: CloseCode, + /// Optional description of the exit code pub description: Option, } From 0ff5f5f44825ba0213bd569594a46d107c6efa53 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 09:01:51 -0700 Subject: [PATCH 0296/1635] update migration --- MIGRATION.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index f93e83c2..6436593e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,14 @@ +## 0.7 + +* Middleware trait uses `&mut self` instead of `&self`. + +* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. + + `fn index((query, json): (Query<..>, Json impl Responder` + +* Removed deprecated `HttpServer::threads()`, use `HttpServer::workers()` instead. + + ## Migration from 0.5 to 0.6 * `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` From 3bfed36fccefa166fa700f9782aab6bdebc0d168 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 09:14:47 -0700 Subject: [PATCH 0297/1635] do not re-export actix_inner --- src/lib.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 88c6bd4c..2772309e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,7 +133,7 @@ extern crate serde_json; extern crate serde_urlencoded; extern crate smallvec; #[macro_use] -pub extern crate actix as actix_inner; +extern crate actix as actix_inner; #[cfg(test)] #[macro_use] @@ -197,13 +197,14 @@ pub use json::Json; pub use scope::Scope; pub mod actix { - //! Re-exports [actix's](https://docs.rs/actix) prelude + //! Re-exports [actix's](https://docs.rs/actix/) prelude - pub use actix_inner::actors::resolver; - pub use actix_inner::actors::signal; - pub use actix_inner::fut; - pub use actix_inner::msgs; - pub use actix_inner::prelude::*; + extern crate actix; + pub use self::actix::actors::resolver; + pub use self::actix::actors::signal; + pub use self::actix::fut; + pub use self::actix::msgs; + pub use self::actix::prelude::*; } #[cfg(feature = "openssl")] From cede81791512c65620f2fffb87e156ee5fe54367 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 09:15:44 -0700 Subject: [PATCH 0298/1635] update changelog --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5b9e16bd..879040c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,11 @@ ## [0.7.0] - 2018-xx-xx +### Added + +* Re-export `actix::prelude::*` as `actix_web::actix` module. + + ### Changed * Migrate to tokio From 0457fe4d616017a0ae4cb93d5218d765a5d1a954 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 09:19:13 -0700 Subject: [PATCH 0299/1635] add System changes to migration guide --- MIGRATION.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 6436593e..3855f025 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,28 @@ ## 0.7 +* `actix::System` has new api. + + Instead of + + ```run + fn main() { + let sys = actix::System::new(..); + + HttpServer::new(|| ...).start() + sys.run(); + } + ``` + + Server must be initialized within system run closure: + + ```run + fn main() { + actix::System::run(|| { + HttpServer::new(|| ...).start() + }); + } + ``` + * Middleware trait uses `&mut self` instead of `&self`. * Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. From 33326ea41b0a60f2eacb762c96136a21baf44fbc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 09:25:11 -0700 Subject: [PATCH 0300/1635] fix layout --- MIGRATION.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 3855f025..bba3f60f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -2,20 +2,21 @@ * `actix::System` has new api. - Instead of + Instead of - ```run + ```rust fn main() { let sys = actix::System::new(..); HttpServer::new(|| ...).start() + sys.run(); } ``` Server must be initialized within system run closure: - ```run + ```rust fn main() { actix::System::run(|| { HttpServer::new(|| ...).start() @@ -32,7 +33,7 @@ * Removed deprecated `HttpServer::threads()`, use `HttpServer::workers()` instead. -## Migration from 0.5 to 0.6 +## 0.6 * `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` @@ -84,7 +85,7 @@ you need to use `use actix_web::ws::WsWriter` -## Migration from 0.4 to 0.5 +## 0.5 * `HttpResponseBuilder::body()`, `.finish()`, `.json()` methods return `HttpResponse` instead of `Result` From 8d905c8504d91fb6859fd5b12273da7287da1634 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 09:28:32 -0700 Subject: [PATCH 0301/1635] add links to migration --- MIGRATION.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index bba3f60f..0b5252d9 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -24,13 +24,27 @@ } ``` -* Middleware trait uses `&mut self` instead of `&self`. +* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) + trait uses `&mut self` instead of `&self`. * Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. + + instead of - `fn index((query, json): (Query<..>, Json impl Responder` + ```rust + fn index(query: Query<..>, info: Json impl Responder {} + ``` -* Removed deprecated `HttpServer::threads()`, use `HttpServer::workers()` instead. + use tuple of extractors: + + ```rust + fn index((query, json): (Query<..>, Json impl Responder {} + ``` + +* Removed deprecated `HttpServer::threads()`, use + [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. + + ## 0.6 From 4a39216aa7a1c232d49e4c34d8f932bf1596f876 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 11:44:09 -0700 Subject: [PATCH 0302/1635] fixed HttpRequest::url_for for a named route with no variables #265 --- src/httprequest.rs | 34 +++++++++++++++++++++--- src/router.rs | 66 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 0a14ca04..a618abdc 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -311,6 +311,7 @@ impl HttpRequest { Err(UrlGenerationError::RouterNotAvailable) } else { let path = self.router().unwrap().resource_path(name, elements)?; + println!("==== {:?}", path); if path.starts_with('/') { let conn = self.connection_info(); Ok(Url::parse(&format!( @@ -325,6 +326,15 @@ impl HttpRequest { } } + /// Generate url for named resource + /// + /// This method is similar to `HttpRequest::url_for()` but it can be used + /// for urls that do not contain variable parts. + pub fn url_for_static(&self, name: &str) -> Result { + const NO_PARAMS: [&str; 0] = []; + self.url_for(name, &NO_PARAMS) + } + /// This method returns reference to current `Router` object. #[inline] pub fn router(&self) -> Option<&Router> { @@ -695,20 +705,38 @@ mod tests { let mut resource = ResourceHandler::<()>::default(); resource.name("index"); - let routes = - vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; + let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))]; let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); 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"]); + let url = req.url_for("index", &["test"]); assert_eq!( url.ok().unwrap().as_str(), "http://www.rust-lang.org/prefix/user/test.html" ); } + #[test] + fn test_url_for_static() { + let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish(); + + let mut resource = ResourceHandler::<()>::default(); + resource.name("index"); + let routes = vec![(Resource::new("index", "/index.html"), Some(resource))]; + let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); + assert!(router.has_route("/index.html")); + assert!(!router.has_route("/prefix/index.html")); + + let req = req.with_state(Rc::new(()), router); + let url = req.url_for_static("index"); + assert_eq!( + url.ok().unwrap().as_str(), + "http://www.rust-lang.org/prefix/index.html" + ); + } + #[test] fn test_url_for_external() { let req = HttpRequest::default(); diff --git a/src/router.rs b/src/router.rs index f65e3155..c2ce4f54 100644 --- a/src/router.rs +++ b/src/router.rs @@ -211,6 +211,8 @@ impl Resource { let (pattern, elements, is_dynamic, len) = Resource::parse(path, prefix, for_prefix); + println!("ELEMENT: {:?} {:?} {:?}", pattern, elements, is_dynamic); + let tp = if is_dynamic { let re = match Regex::new(&pattern) { Ok(re) => re, @@ -338,22 +340,42 @@ impl Resource { U: IntoIterator, I: AsRef, { - let mut iter = elements.into_iter(); - let mut path = if self.rtp != ResourceType::External { - format!("{}/", router.prefix()) - } 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); + let mut path = match self.tp { + PatternType::Prefix(ref p) => p.to_owned(), + PatternType::Static(ref p) => p.to_owned(), + PatternType::Dynamic(..) => { + let mut path = String::new(); + let mut iter = elements.into_iter(); + for el in &self.elements { + println!("EL: {:?}", el); + 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); + } + } } } + path + } + }; + + if self.rtp != ResourceType::External { + let prefix = router.prefix(); + if prefix.ends_with('/') { + if path.starts_with('/') { + path.insert_str(0, &prefix[..prefix.len() - 1]); + } else { + path.insert_str(0, prefix); + } + } else { + if !path.starts_with('/') { + path.insert(0, '/'); + } + path.insert_str(0, prefix); } } Ok(path) @@ -418,6 +440,10 @@ impl Resource { } } + if !el.is_empty() { + elems.push(PatternElement::Str(el.clone())); + } + let re = if is_dynamic { if !for_prefix { re1.push('$'); @@ -450,7 +476,7 @@ mod tests { use test::TestRequest; #[test] - fn test_recognizer() { + fn test_recognizer10() { let routes = vec![ (Resource::new("", "/name"), Some(ResourceHandler::default())), ( @@ -473,6 +499,10 @@ mod tests { Resource::new("", "/v/{tail:.*}"), Some(ResourceHandler::default()), ), + ( + Resource::new("", "/test2/{test}.html"), + Some(ResourceHandler::default()), + ), ( Resource::new("", "{test}/index.html"), Some(ResourceHandler::default()), @@ -510,8 +540,12 @@ mod tests { "blah-blah/index.html" ); - let mut req = TestRequest::with_uri("/bbb/index.html").finish(); + let mut req = TestRequest::with_uri("/test2/index.html").finish(); assert_eq!(rec.recognize(&mut req), Some(6)); + assert_eq!(req.match_info().get("test").unwrap(), "index"); + + let mut req = TestRequest::with_uri("/bbb/index.html").finish(); + assert_eq!(rec.recognize(&mut req), Some(7)); assert_eq!(req.match_info().get("test").unwrap(), "bbb"); } From 593a66324fee5d17fb0f400c5ca3f368c4888734 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 11:45:37 -0700 Subject: [PATCH 0303/1635] update changelog --- CHANGES.md | 12 ++++++++++-- MIGRATION.md | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 879040c5..d1e4e6f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,20 +6,28 @@ * Re-export `actix::prelude::*` as `actix_web::actix` module. +* `HttpRequest::url_for_static()` for a named route with no variables segments + ### Changed -* Migrate to tokio - * Min rustc version is 1.26 +* Use tokio instead of tokio-core + * Use `&mut self` instead of `&self` for Middleware trait + ### Removed * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. +### Fixed + +* `HttpRequest::url_for()` for a named route with no variables segments #265 + + ## [0.6.10] - 2018-05-24 ### Added diff --git a/MIGRATION.md b/MIGRATION.md index 0b5252d9..91f8a800 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -35,7 +35,7 @@ fn index(query: Query<..>, info: Json impl Responder {} ``` - use tuple of extractors: + use tuple of extractors and use `.with()` for registration: ```rust fn index((query, json): (Query<..>, Json impl Responder {} From dcb561584d73868f0c283584b5cb52864e4b883e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 11:55:27 -0700 Subject: [PATCH 0304/1635] remove debug print --- src/httprequest.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index a618abdc..1d9f6245 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -283,9 +283,9 @@ impl HttpRequest { /// Generate url for named resource /// /// ```rust - /// # extern crate actix_web; - /// # use actix_web::{App, HttpRequest, HttpResponse, http}; - /// # + /// //#### # extern crate actix_web; + /// //#### # use actix_web::{App, HttpRequest, HttpResponse, http}; + /// //#### # /// fn index(req: HttpRequest) -> HttpResponse { /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource /// HttpResponse::Ok().into() @@ -311,7 +311,6 @@ impl HttpRequest { Err(UrlGenerationError::RouterNotAvailable) } else { let path = self.router().unwrap().resource_path(name, elements)?; - println!("==== {:?}", path); if path.starts_with('/') { let conn = self.connection_info(); Ok(Url::parse(&format!( From 3c472a2f66881cc213e6b12e1e70f8e309c2b8ec Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 11:57:49 -0700 Subject: [PATCH 0305/1635] remove debug prints --- src/router.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/router.rs b/src/router.rs index c2ce4f54..c9ff9d70 100644 --- a/src/router.rs +++ b/src/router.rs @@ -211,8 +211,6 @@ impl Resource { let (pattern, elements, is_dynamic, len) = Resource::parse(path, prefix, for_prefix); - println!("ELEMENT: {:?} {:?} {:?}", pattern, elements, is_dynamic); - let tp = if is_dynamic { let re = match Regex::new(&pattern) { Ok(re) => re, @@ -347,7 +345,6 @@ impl Resource { let mut path = String::new(); let mut iter = elements.into_iter(); for el in &self.elements { - println!("EL: {:?}", el); match *el { PatternElement::Str(ref s) => path.push_str(s), PatternElement::Var(_) => { From fce8dd275ab0c86559a098af3c2d2b58ef17ff1f Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 2 Jun 2018 22:20:22 +0300 Subject: [PATCH 0306/1635] Specialize ResponseError for PayloadError Closes #257 --- src/error.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 481cf326..174530ca 100644 --- a/src/error.rs +++ b/src/error.rs @@ -294,7 +294,14 @@ impl From for PayloadError { } /// `InternalServerError` for `PayloadError` -impl ResponseError for PayloadError {} +impl ResponseError for PayloadError { + fn error_response(&self) -> HttpResponse { + match *self { + PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), + _ => HttpResponse::new(StatusCode::BAD_REQUEST) + } + } +} /// Return `BadRequest` for `cookie::ParseError` impl ResponseError for cookie::ParseError { From 2a9b57f489fa82142d3b7072fa86e8b80c79f18b Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 2 Jun 2018 22:27:43 +0300 Subject: [PATCH 0307/1635] Correct docstring --- src/error.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 174530ca..6bb94af5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -293,7 +293,10 @@ impl From for PayloadError { } } -/// `InternalServerError` for `PayloadError` +/// `PayloadError` returns two possible results: +/// +/// - `Overflow` returns `PayloadTooLarge` +/// - Other errors returns `BadRequest` impl ResponseError for PayloadError { fn error_response(&self) -> HttpResponse { match *self { From 7ab23d082dcd05d758685130247c80dbc1a05100 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 13:45:29 -0700 Subject: [PATCH 0308/1635] fix doc test --- src/httprequest.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 1d9f6245..d852bc74 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -283,9 +283,9 @@ impl HttpRequest { /// Generate url for named resource /// /// ```rust - /// //#### # extern crate actix_web; - /// //#### # use actix_web::{App, HttpRequest, HttpResponse, http}; - /// //#### # + /// # extern crate actix_web; + /// # use actix_web::{App, HttpRequest, HttpResponse, http}; + /// # /// fn index(req: HttpRequest) -> HttpResponse { /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource /// HttpResponse::Ok().into() From 8b8a3ac01d79d513c78ce4938ab612347a875dd6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 14:57:36 -0700 Subject: [PATCH 0309/1635] Support chunked encoding for UrlEncoded body #262 --- src/httpmessage.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 1e57d386..a9d68d3a 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -149,7 +149,6 @@ pub trait HttpMessage { /// Returns error: /// /// * content type is not `application/x-www-form-urlencoded` - /// * transfer encoding is `chunked`. /// * content-length is greater than 256k /// /// ## Server example @@ -367,9 +366,7 @@ where fn poll(&mut self) -> Poll { if let Some(req) = self.req.take() { - if req.chunked().unwrap_or(false) { - return Err(UrlencodedError::Chunked); - } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let 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 { @@ -577,13 +574,6 @@ mod tests { #[test] fn test_urlencoded_error() { - let req = - TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); - assert_eq!( - req.urlencoded::().poll().err().unwrap(), - UrlencodedError::Chunked - ); - let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", From 698f0a1849eefc5bf53e285c62a5273420695339 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 14:57:56 -0700 Subject: [PATCH 0310/1635] update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d1e4e6f6..367354a9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,8 @@ ### Fixed +* Support chunked encoding for UrlEncoded body #262 + * `HttpRequest::url_for()` for a named route with no variables segments #265 From 7e0706a9426708e60df412558f17f34785c8441e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 14:58:15 -0700 Subject: [PATCH 0311/1635] implement Debug for Form, Query, Path extractors --- src/extractor.rs | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 3d77853a..175e948b 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; -use std::str; +use std::{fmt, str}; use bytes::Bytes; use encoding::all::UTF_8; @@ -115,6 +115,18 @@ where } } +impl fmt::Debug for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl fmt::Display for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + /// Extract typed information from from the request's query. /// /// ## Example @@ -175,13 +187,24 @@ where #[inline] fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - let req = req.clone(); serde_urlencoded::from_str::(req.query_string()) .map_err(|e| e.into()) .map(Query) } } +impl fmt::Debug for Query { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for Query { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + /// Extract typed information from the request's body. /// /// To extract typed information from request's body, the type `T` must @@ -252,6 +275,18 @@ where } } +impl fmt::Debug for Form { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for Form { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + /// Form extractor configuration /// /// ```rust From b799677532e46618704caa47aebd948ae26618ef Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 15:10:48 -0700 Subject: [PATCH 0312/1635] better error messages for overflow errors --- src/error.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index 6bb94af5..cfb6a028 100644 --- a/src/error.rs +++ b/src/error.rs @@ -301,7 +301,7 @@ impl ResponseError for PayloadError { fn error_response(&self) -> HttpResponse { match *self { PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => HttpResponse::new(StatusCode::BAD_REQUEST) + _ => HttpResponse::new(StatusCode::BAD_REQUEST), } } } @@ -427,8 +427,10 @@ pub enum UrlencodedError { /// Can not decode chunked transfer encoding #[fail(display = "Can not decode chunked transfer encoding")] Chunked, - /// Payload size is bigger than 256k - #[fail(display = "Payload size is bigger than 256k")] + /// Payload size is bigger than allowed. (default: 256kB) + #[fail( + display = "Urlencoded payload size is bigger than allowed. (default: 256kB)" + )] Overflow, /// Payload size is now known #[fail(display = "Payload size is now known")] @@ -468,8 +470,8 @@ 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")] + /// Payload size is bigger than allowed. (default: 256kB) + #[fail(display = "Json payload size is bigger than allowed. (default: 256kB)")] Overflow, /// Content type error #[fail(display = "Content type error")] From ea018e0ad6b45d42e30c023a18da8a15207ff551 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Jun 2018 16:03:23 -0700 Subject: [PATCH 0313/1635] better examle in doc string --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2772309e..5d3767a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,10 @@ //! for Rust. //! //! ```rust -//! use actix_web::{server, App, Path}; +//! use actix_web::{server, App, Path, Responder}; //! # use std::thread; //! -//! fn index(info: Path<(String, u32)>) -> String { +//! fn index(info: Path<(String, u32)>) -> impl Responder { //! format!("Hello {}! id:{}", info.0, info.1) //! } //! From 86be54df71d8b4b97a7c569d6274e6fe4bf8aae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Sun, 3 Jun 2018 15:48:00 +0200 Subject: [PATCH 0314/1635] add default value for header User-Agent in requests --- src/client/request.rs | 11 ++++++++++- tests/test_client.rs | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/client/request.rs b/src/client/request.rs index 4fef3e5d..d3f6ebdb 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -506,7 +506,7 @@ impl ClientRequestBuilder { } /// Do not add default request headers. - /// By default `Accept-Encoding` header is set. + /// By default `Accept-Encoding` and `User-Agent` headers are set. pub fn no_default_headers(&mut self) -> &mut Self { self.default_headers = false; self @@ -608,6 +608,15 @@ impl ClientRequestBuilder { } else { self.header(header::ACCEPT_ENCODING, "gzip, deflate"); } + + let contains = if let Some(parts) = parts(&mut self.request, &self.err) { + parts.headers.contains_key(header::USER_AGENT) + } else { + true + }; + if !contains { + self.header(header::USER_AGENT, "Actix-web"); + } } let mut request = self.request.take().expect("cannot reuse request builder"); diff --git a/tests/test_client.rs b/tests/test_client.rs index 9b9fb0e8..cd47583c 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -432,3 +432,21 @@ fn test_client_cookie_handling() { let c2 = response.cookie("cookie2").expect("Missing cookie2"); assert_eq!(c2, &cookie2); } + +#[test] +fn test_default_headers() { + let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + + let request = srv.get().finish().unwrap(); + let repr = format!("{:?}", request); + assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); + assert!(repr.contains("\"user-agent\": \"Actix-web\"")); + + let request_override = srv.get() + .header("User-Agent", "test") + .finish() + .unwrap(); + let repr_override = format!("{:?}", request_override); + assert!(repr_override.contains("\"user-agent\": \"test\"")); + assert!(!repr_override.contains("\"user-agent\": \"Actix-web\"")); +} From 268c5d9238c2c43e174969605ac8c9a93523fc3a Mon Sep 17 00:00:00 2001 From: Matthijs Brobbel Date: Sun, 3 Jun 2018 20:28:08 +0200 Subject: [PATCH 0315/1635] Fix typo --- src/middleware/identity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index d1e52d46..706dd9fc 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -3,7 +3,7 @@ //! [**IdentityService**](struct.IdentityService.html) middleware can be //! used with different policies types to store identity information. //! -//! Bu default, only cookie identity policy is implemented. Other backend +//! By default, only cookie identity policy is implemented. Other backend //! implementations can be added separately. //! //! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) From ca3fb11f8b0fddaeb817ab0e7fa0b52f0f0af50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Mon, 4 Jun 2018 08:15:04 +0200 Subject: [PATCH 0316/1635] add actix-web version in header --- src/client/request.rs | 5 ++++- tests/test_client.rs | 12 ++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index d3f6ebdb..bb338482 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -615,7 +615,10 @@ impl ClientRequestBuilder { true }; if !contains { - self.header(header::USER_AGENT, "Actix-web"); + self.header( + header::USER_AGENT, + concat!("Actix-web/", env!("CARGO_PKG_VERSION")), + ); } } diff --git a/tests/test_client.rs b/tests/test_client.rs index cd47583c..3128bb94 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -440,7 +440,11 @@ fn test_default_headers() { let request = srv.get().finish().unwrap(); let repr = format!("{:?}", request); assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(repr.contains("\"user-agent\": \"Actix-web\"")); + assert!(repr.contains(concat!( + "\"user-agent\": \"Actix-web/", + env!("CARGO_PKG_VERSION"), + "\"" + ))); let request_override = srv.get() .header("User-Agent", "test") @@ -448,5 +452,9 @@ fn test_default_headers() { .unwrap(); let repr_override = format!("{:?}", request_override); assert!(repr_override.contains("\"user-agent\": \"test\"")); - assert!(!repr_override.contains("\"user-agent\": \"Actix-web\"")); + assert!(!repr_override.contains(concat!( + "\"user-agent\": \"Actix-web/", + env!("CARGO_PKG_VERSION"), + "\"" + ))); } From b07c50860a33eb1eced1c35faf535c1325ad37d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Mon, 4 Jun 2018 22:34:07 +0200 Subject: [PATCH 0317/1635] update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 367354a9..7de9470c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ * Use `&mut self` instead of `&self` for Middleware trait +* Added header `User-Agent: Actix-web/` to default headers when building a request ### Removed From 984791187a1b8149124995a837f304a18de5b440 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Jun 2018 13:42:47 -0700 Subject: [PATCH 0318/1635] Middleware::response is not invoked if error result was returned by another Middleware::start #255 --- src/middleware/errhandlers.rs | 27 +++++ src/pipeline.rs | 12 +- src/route.rs | 18 +-- src/scope.rs | 14 ++- tests/test_middleware.rs | 217 +++++++++++++++++++++++++++++++++- 5 files changed, 267 insertions(+), 21 deletions(-) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 7e56b368..e1b48418 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -84,8 +84,12 @@ impl Middleware for ErrorHandlers { #[cfg(test)] mod tests { use super::*; + use error::{Error, ErrorInternalServerError}; use http::header::CONTENT_TYPE; use http::StatusCode; + use httpmessage::HttpMessage; + use middleware::Started; + use test; fn render_500(_: &mut HttpRequest, resp: HttpResponse) -> Result { let mut builder = resp.into_builder(); @@ -113,4 +117,27 @@ mod tests { }; assert!(!resp.headers().contains_key(CONTENT_TYPE)); } + + struct MiddlewareOne; + + impl Middleware for MiddlewareOne { + fn start(&mut self, _req: &mut HttpRequest) -> Result { + Err(ErrorInternalServerError("middleware error")) + } + } + + #[test] + fn test_middleware_start_error() { + let mut srv = test::TestServer::new(move |app| { + app.middleware( + ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), + ).middleware(MiddlewareOne) + .handler(|_| HttpResponse::Ok()) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001"); + } } diff --git a/src/pipeline.rs b/src/pipeline.rs index f9ec9713..8be7ee83 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -249,7 +249,8 @@ impl> StartMiddlewares { let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype); return WaitingResponse::init(info, reply); } else { - let state = info.mws.borrow_mut()[info.count as usize].start(info.req_mut()); + let state = + info.mws.borrow_mut()[info.count as usize].start(info.req_mut()); match state { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { @@ -263,7 +264,7 @@ impl> StartMiddlewares { _s: PhantomData, }) } - Err(err) => return ProcessResponse::init(err.into()), + Err(err) => return RunMiddlewares::init(info, err.into()), } } } @@ -297,13 +298,13 @@ impl> StartMiddlewares { continue 'outer; } Err(err) => { - return Some(ProcessResponse::init(err.into())) + return Some(RunMiddlewares::init(info, err.into())) } } } } } - Err(err) => return Some(ProcessResponse::init(err.into())), + Err(err) => return Some(RunMiddlewares::init(info, err.into())), } } } @@ -403,7 +404,8 @@ impl RunMiddlewares { if self.curr == len { return Some(ProcessResponse::init(resp)); } else { - let state = info.mws.borrow_mut()[self.curr].response(info.req_mut(), resp); + let state = + info.mws.borrow_mut()[self.curr].response(info.req_mut(), resp); match state { Err(err) => return Some(ProcessResponse::init(err.into())), Ok(Response::Done(r)) => { diff --git a/src/route.rs b/src/route.rs index 27aae8df..44ac8280 100644 --- a/src/route.rs +++ b/src/route.rs @@ -289,7 +289,8 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>>, handler: InnerHandler, + req: HttpRequest, mws: Rc>>>>, + handler: InnerHandler, ) -> Self { let mut info = ComposeInfo { count: 0, @@ -350,7 +351,7 @@ impl StartMiddlewares { _s: PhantomData, }) } - Err(err) => return FinishingMiddlewares::init(info, err.into()), + Err(err) => return RunMiddlewares::init(info, err.into()), } } } @@ -371,7 +372,8 @@ impl StartMiddlewares { let reply = info.handler.handle(info.req.clone()); return Some(WaitingResponse::init(info, reply)); } else { - let state = info.mws.borrow_mut()[info.count].start(&mut info.req); + let state = + info.mws.borrow_mut()[info.count].start(&mut info.req); match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { @@ -382,16 +384,13 @@ impl StartMiddlewares { continue 'outer; } Err(err) => { - return Some(FinishingMiddlewares::init( - info, - err.into(), - )) + return Some(RunMiddlewares::init(info, err.into())) } } } } } - Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), + Err(err) => return Some(RunMiddlewares::init(info, err.into())), } } } @@ -483,7 +482,8 @@ impl RunMiddlewares { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { - let state = info.mws.borrow_mut()[self.curr].response(&mut info.req, resp); + let state = + info.mws.borrow_mut()[self.curr].response(&mut info.req, resp); match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) diff --git a/src/scope.rs b/src/scope.rs index 69c46e48..a4011302 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -519,7 +519,7 @@ impl StartMiddlewares { _s: PhantomData, }) } - Err(err) => return Response::init(err.into()), + Err(err) => return RunMiddlewares::init(info, err.into()), } } } @@ -546,7 +546,8 @@ impl StartMiddlewares { }; return Some(WaitingResponse::init(info, reply)); } else { - let state = info.mws.borrow_mut()[info.count].start(&mut info.req); + let state = + info.mws.borrow_mut()[info.count].start(&mut info.req); match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { @@ -556,12 +557,14 @@ impl StartMiddlewares { self.fut = Some(fut); continue 'outer; } - Err(err) => return Some(Response::init(err.into())), + Err(err) => { + return Some(RunMiddlewares::init(info, err.into())) + } } } } } - Err(err) => return Some(Response::init(err.into())), + Err(err) => return Some(RunMiddlewares::init(info, err.into())), } } } @@ -653,7 +656,8 @@ impl RunMiddlewares { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { - let state = info.mws.borrow_mut()[self.curr].response(&mut info.req, resp); + let state = + info.mws.borrow_mut()[self.curr].response(&mut info.req, resp); match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 3f802b3a..d64e4fee 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -8,6 +8,7 @@ use std::sync::Arc; use std::thread; use std::time::{Duration, Instant}; +use actix_web::error::{Error, ErrorInternalServerError}; use actix_web::*; use futures::{future, Future}; use tokio_timer::Delay; @@ -33,7 +34,9 @@ impl middleware::Middleware for MiddlewareTest { Ok(middleware::Response::Done(resp)) } - fn finish(&mut self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { + fn finish( + &mut self, _: &mut HttpRequest, _: &HttpResponse, + ) -> middleware::Finished { self.finish .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middleware::Finished::Done @@ -457,7 +460,9 @@ impl middleware::Middleware for MiddlewareAsyncTest { ))) } - fn finish(&mut self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { + fn finish( + &mut self, _: &mut HttpRequest, _: &HttpResponse, + ) -> middleware::Finished { let to = Delay::new(Instant::now() + Duration::from_millis(10)); let finish = Arc::clone(&self.finish); @@ -788,3 +793,211 @@ fn test_async_sync_resource_middleware_multiple() { thread::sleep(Duration::from_millis(40)); assert_eq!(num3.load(Ordering::Relaxed), 2); } + +struct MiddlewareWithErr; + +impl middleware::Middleware for MiddlewareWithErr { + fn start( + &mut self, _req: &mut HttpRequest, + ) -> Result { + Err(ErrorInternalServerError("middleware error")) + } +} + +struct MiddlewareAsyncWithErr; + +impl middleware::Middleware for MiddlewareAsyncWithErr { + fn start( + &mut self, _req: &mut HttpRequest, + ) -> Result { + Ok(middleware::Started::Future(Box::new(future::err( + ErrorInternalServerError("middleware error"), + )))) + } +} + +#[test] +fn test_middleware_chain_with_error() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw1 = MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new() + .middleware(mw1) + .middleware(MiddlewareWithErr) + .resource("/test", |r| r.h(|_| HttpResponse::Ok())) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_middleware_async_chain_with_error() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw1 = MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new() + .middleware(mw1) + .middleware(MiddlewareAsyncWithErr) + .resource("/test", |r| r.h(|_| HttpResponse::Ok())) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_scope_middleware_chain_with_error() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw1 = MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new().scope("/scope", |scope| { + scope + .middleware(mw1) + .middleware(MiddlewareWithErr) + .resource("/test", |r| r.h(|_| HttpResponse::Ok())) + }) + }); + + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_scope_middleware_async_chain_with_error() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw1 = MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new().scope("/scope", |scope| { + scope + .middleware(mw1) + .middleware(MiddlewareAsyncWithErr) + .resource("/test", |r| r.h(|_| HttpResponse::Ok())) + }) + }); + + let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_resource_middleware_chain_with_error() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw1 = MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new().resource("/test", move |r| { + r.middleware(mw1); + r.middleware(MiddlewareWithErr); + r.h(|_| HttpResponse::Ok()); + }) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_resource_middleware_async_chain_with_error() { + let num1 = Arc::new(AtomicUsize::new(0)); + let num2 = Arc::new(AtomicUsize::new(0)); + let num3 = Arc::new(AtomicUsize::new(0)); + + let act_num1 = Arc::clone(&num1); + let act_num2 = Arc::clone(&num2); + let act_num3 = Arc::clone(&num3); + + let mut srv = test::TestServer::with_factory(move || { + let mw1 = MiddlewareTest { + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }; + App::new().resource("/test", move |r| { + r.middleware(mw1); + r.middleware(MiddlewareAsyncWithErr); + r.h(|_| HttpResponse::Ok()); + }) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert_eq!(num1.load(Ordering::Relaxed), 1); + assert_eq!(num2.load(Ordering::Relaxed), 1); + assert_eq!(num3.load(Ordering::Relaxed), 1); +} From ae7a0e993dab9b467e00f9484cfa5cf70d267eeb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Jun 2018 13:43:52 -0700 Subject: [PATCH 0319/1635] update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 367354a9..d74bdd9b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,6 +29,8 @@ * `HttpRequest::url_for()` for a named route with no variables segments #265 +* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255 + ## [0.6.10] - 2018-05-24 From f94fd9ebeeb382771558c3643f7e3206026d2265 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Jun 2018 07:39:47 -0700 Subject: [PATCH 0320/1635] CORS: Do not validate Origin header on non-OPTION requests #271 --- src/middleware/cors.rs | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 3549ba11..45459656 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -424,7 +424,10 @@ impl Middleware for Cors { .finish(), )) } else { - self.validate_origin(req)?; + // Only check requests with a origin header. + if req.headers().contains_key(header::ORIGIN) { + self.validate_origin(req)?; + } Ok(Started::Done) } @@ -1007,16 +1010,15 @@ mod tests { assert!(cors.start(&mut req).unwrap().is_done()); } - #[test] - #[should_panic(expected = "MissingOrigin")] - fn test_validate_missing_origin() { - let mut cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let mut req = HttpRequest::default(); - cors.start(&mut req).unwrap(); - } + // #[test] + // #[should_panic(expected = "MissingOrigin")] + // fn test_validate_missing_origin() { + // let mut cors = Cors::build() + // .allowed_origin("https://www.example.com") + // .finish(); + // let mut req = HttpRequest::default(); + // cors.start(&mut req).unwrap(); + // } #[test] #[should_panic(expected = "OriginNotAllowed")] @@ -1133,10 +1135,19 @@ mod tests { }) }); - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let request = srv + .get() + .uri(srv.url("/test")) + .header("ORIGIN", "https://www.example2.com") + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let request = srv .get() .uri(srv.url("/test")) From 960a8c425de69197912f38ed06a1173e01ee9d68 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Jun 2018 07:40:11 -0700 Subject: [PATCH 0321/1635] update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 04f1d2bb..d79a1d86 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,8 @@ * `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255 +* CORS: Do not validate Origin header on non-OPTION requests #271 + ## [0.6.10] - 2018-05-24 From d1da227ac51d049687bf7f252e1096212c415607 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Jun 2018 08:52:46 -0700 Subject: [PATCH 0322/1635] fix multipart boundary parsing #282 --- src/multipart.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/multipart.rs b/src/multipart.rs index 37244d80..f310327f 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -513,7 +513,7 @@ where match payload.read_exact(boundary.len() + 4)? { Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(chunk)) => { + Async::Ready(Some(mut chunk)) => { if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && &chunk[4..] == boundary.as_bytes() @@ -521,7 +521,10 @@ where payload.unread_data(chunk); Ok(Async::Ready(None)) } else { - Ok(Async::Ready(Some(chunk))) + // \r might be part of data stream + let ch = chunk.split_to(1); + payload.unread_data(chunk); + Ok(Async::Ready(Some(ch))) } } } From e5f7e4e481a9a45bab45490d75176d691a33c6be Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Jun 2018 08:55:28 -0700 Subject: [PATCH 0323/1635] update changelog --- CHANGES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d79a1d86..ea2aca9d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,7 +24,7 @@ * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. -### Fixed +## [0.6.11] - 2018-06-xx * Support chunked encoding for UrlEncoded body #262 @@ -34,6 +34,8 @@ * CORS: Do not validate Origin header on non-OPTION requests #271 +* Fix multipart upload "Incomplete" error #282 + ## [0.6.10] - 2018-05-24 From 2b616808c7cc69cc6ac8873d6d199739db62fad4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Jun 2018 08:59:30 -0700 Subject: [PATCH 0324/1635] metadata for docs.rs --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 603bd1bf..9cd3304f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,9 @@ flate2-c = ["flate2/miniz-sys"] # rust backend for flate2 crate flate2-rust = ["flate2/rust_backend"] +[package.metadata.docs.rs] +features = ["tls", "alpn", "session", "brotli", "flate2-c"] + [dependencies] #actix = "^0.6.0" actix = { git="https://github.com/actix/actix.git" } From 6467d34a3298d267a1003712499dfd69eaa8d65a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Jun 2018 09:45:07 -0700 Subject: [PATCH 0325/1635] update release date --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ea2aca9d..f46fa9cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,7 +24,7 @@ * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. -## [0.6.11] - 2018-06-xx +## [0.6.11] - 2018-06-05 * Support chunked encoding for UrlEncoded body #262 From 2d0b609c6868fd4a1e7c01001da0c6625a722053 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Jun 2018 10:08:42 -0700 Subject: [PATCH 0326/1635] travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fe5f8974..42e6bc6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count + USE_SKEPTIC=1 cargo tarpaulin --features="alpn,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 936ba2a3682f9c7becd287392fcc66014c16dd32 Mon Sep 17 00:00:00 2001 From: axon-q Date: Wed, 6 Jun 2018 14:06:01 +0000 Subject: [PATCH 0327/1635] multipart: parse and validate Content-Disposition --- Cargo.toml | 1 + src/error.rs | 3 + src/header/common/content_disposition.rs | 84 +++++++++------- src/header/common/mod.rs | 4 +- src/header/mod.rs | 120 +++++++++++++++++++++++ src/lib.rs | 2 + src/multipart.rs | 23 ++++- 7 files changed, 193 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9cd3304f..9d080f53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,7 @@ time = "0.1" encoding = "0.2" language-tags = "0.2" lazy_static = "1.0" +unicase = "2.1" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.10", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } diff --git a/src/error.rs b/src/error.rs index cfb6a028..f4de3681 100644 --- a/src/error.rs +++ b/src/error.rs @@ -353,6 +353,9 @@ pub enum MultipartError { /// Can not parse Content-Type header #[fail(display = "Can not parse Content-Type header")] ParseContentType, + /// Can not parse Content-Disposition header + #[fail(display = "Can not parse Content-Disposition header")] + ParseContentDisposition, /// Multipart boundary is not found #[fail(display = "Multipart boundary is not found")] Boundary, diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index 0fcd6ee0..4d1a0c6d 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -7,13 +7,14 @@ // IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml use language_tags::LanguageTag; -use std::fmt; use unicase; -use header::{Header, Raw, parsing}; -use header::parsing::{parse_extended_value, http_percent_encode}; +use header; +use header::{Header, IntoHeaderValue, Writer}; use header::shared::Charset; +use std::fmt::{self, Write}; + /// The implied disposition of the content of the HTTP body. #[derive(Clone, Debug, PartialEq)] pub enum DispositionType { @@ -88,19 +89,14 @@ pub struct ContentDisposition { /// Disposition parameters pub parameters: Vec, } - -impl Header for ContentDisposition { - fn header_name() -> &'static str { - static NAME: &'static str = "Content-Disposition"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - parsing::from_one_raw_str(raw).and_then(|s: String| { +impl ContentDisposition { + /// Parse a raw Content-Disposition header value + pub fn from_raw(hv: Option<&header::HeaderValue>) -> Result { + header::from_one_raw_str(hv).and_then(|s: String| { let mut sections = s.split(';'); let disposition = match sections.next() { Some(s) => s.trim(), - None => return Err(::Error::Header), + None => return Err(::error::ParseError::Header), }; let mut cd = ContentDisposition { @@ -120,13 +116,13 @@ impl Header for ContentDisposition { let key = if let Some(key) = parts.next() { key.trim() } else { - return Err(::Error::Header); + return Err(::error::ParseError::Header); }; let val = if let Some(val) = parts.next() { val.trim() } else { - return Err(::Error::Header); + return Err(::error::ParseError::Header); }; cd.parameters.push( @@ -135,7 +131,7 @@ impl Header for ContentDisposition { Charset::Ext("UTF-8".to_owned()), None, val.trim_matches('"').as_bytes().to_owned()) } else if unicase::eq_ascii(&*key, "filename*") { - let extended_value = try!(parse_extended_value(val)); + let extended_value = try!(header::parse_extended_value(val)); DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value) } else { DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned()) @@ -146,10 +142,25 @@ impl Header for ContentDisposition { Ok(cd) }) } +} - #[inline] - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) +impl IntoHeaderValue for ContentDisposition { + type Error = header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + header::HeaderValue::from_shared(writer.take()) + } +} + +impl Header for ContentDisposition { + fn name() -> header::HeaderName { + header::CONTENT_DISPOSITION + } + + fn parse(msg: &T) -> Result { + Self::from_raw(msg.headers().get(Self::name())) } } @@ -183,7 +194,7 @@ impl fmt::Display for ContentDisposition { try!(write!(f, "{}", lang)); }; try!(write!(f, "'")); - try!(http_percent_encode(f, bytes)) + try!(header::http_percent_encode(f, bytes)) } }, DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)), @@ -196,15 +207,14 @@ impl fmt::Display for ContentDisposition { #[cfg(test)] mod tests { use super::{ContentDisposition,DispositionType,DispositionParam}; - use ::header::Header; - use ::header::shared::Charset; - + use header::HeaderValue; + use header::shared::Charset; #[test] - fn test_parse_header() { - assert!(ContentDisposition::parse_header(&"".into()).is_err()); + fn test_from_raw() { + assert!(ContentDisposition::from_raw(Some(&HeaderValue::from_static(""))).is_err()); - let a = "form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let a = HeaderValue::from_static("form-data; dummy=3; name=upload;\r\n filename=\"sample.png\""); + let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); let b = ContentDisposition { disposition: DispositionType::Ext("form-data".to_owned()), parameters: vec![ @@ -217,8 +227,8 @@ mod tests { }; assert_eq!(a, b); - let a = "attachment; filename=\"image.jpg\"".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); + let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ @@ -229,8 +239,8 @@ mod tests { }; assert_eq!(a, b); - let a = "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let a = HeaderValue::from_static("attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); + let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ @@ -246,18 +256,18 @@ mod tests { #[test] fn test_display() { let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; - let a = as_string.into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); let display_rendered = format!("{}",a); assert_eq!(as_string, display_rendered); - let a = "attachment; filename*=UTF-8''black%20and%20white.csv".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let a = HeaderValue::from_static("attachment; filename*=UTF-8''black%20and%20white.csv"); + let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); let display_rendered = format!("{}",a); assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered); - let a = "attachment; filename=colourful.csv".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let a = HeaderValue::from_static("attachment; filename=colourful.csv"); + let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); let display_rendered = format!("{}",a); assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered); } diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index 08f8e0cc..e6185b5a 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -13,7 +13,7 @@ pub use self::accept_language::AcceptLanguage; pub use self::accept::Accept; pub use self::allow::Allow; pub use self::cache_control::{CacheControl, CacheDirective}; -//pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; +pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; pub use self::content_language::ContentLanguage; pub use self::content_range::{ContentRange, ContentRangeSpec}; pub use self::content_type::ContentType; @@ -334,7 +334,7 @@ mod accept_language; mod accept; mod allow; mod cache_control; -//mod content_disposition; +mod content_disposition; mod content_language; mod content_range; mod content_type; diff --git a/src/header/mod.rs b/src/header/mod.rs index a9c42e29..e4d4e049 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -8,6 +8,7 @@ use bytes::{Bytes, BytesMut}; use mime::Mime; use modhttp::header::GetAll; use modhttp::Error as HttpError; +use percent_encoding; pub use modhttp::header::*; @@ -259,3 +260,122 @@ where } Ok(()) } + +// From hyper v0.11.27 src/header/parsing.rs + +/// An extended header parameter value (i.e., tagged with a character set and optionally, +/// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +#[derive(Clone, Debug, PartialEq)] +pub struct ExtendedValue { + /// The character set that is used to encode the `value` to a string. + pub charset: Charset, + /// The human language details of the `value`, if available. + pub language_tag: Option, + /// The parameter value, as expressed in octets. + pub value: Vec, +} + +/// Parses extended header parameter values (`ext-value`), as defined in +/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +/// +/// Extended values are denoted by parameter names that end with `*`. +/// +/// ## ABNF +/// +/// ```text +/// ext-value = charset "'" [ language ] "'" value-chars +/// ; like RFC 2231's +/// ; (see [RFC2231], Section 7) +/// +/// charset = "UTF-8" / "ISO-8859-1" / mime-charset +/// +/// mime-charset = 1*mime-charsetc +/// mime-charsetc = ALPHA / DIGIT +/// / "!" / "#" / "$" / "%" / "&" +/// / "+" / "-" / "^" / "_" / "`" +/// / "{" / "}" / "~" +/// ; as in Section 2.3 of [RFC2978] +/// ; except that the single quote is not included +/// ; SHOULD be registered in the IANA charset registry +/// +/// language = +/// +/// value-chars = *( pct-encoded / attr-char ) +/// +/// pct-encoded = "%" HEXDIG HEXDIG +/// ; see [RFC3986], Section 2.1 +/// +/// attr-char = ALPHA / DIGIT +/// / "!" / "#" / "$" / "&" / "+" / "-" / "." +/// / "^" / "_" / "`" / "|" / "~" +/// ; token except ( "*" / "'" / "%" ) +/// ``` +pub fn parse_extended_value(val: &str) -> Result { + + // Break into three pieces separated by the single-quote character + let mut parts = val.splitn(3,'\''); + + // Interpret the first piece as a Charset + let charset: Charset = match parts.next() { + None => return Err(::error::ParseError::Header), + Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?, + }; + + // Interpret the second piece as a language tag + let lang: Option = match parts.next() { + None => return Err(::error::ParseError::Header), + Some("") => None, + Some(s) => match s.parse() { + Ok(lt) => Some(lt), + Err(_) => return Err(::error::ParseError::Header), + } + }; + + // Interpret the third piece as a sequence of value characters + let value: Vec = match parts.next() { + None => return Err(::error::ParseError::Header), + Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), + }; + + Ok(ExtendedValue { + charset: charset, + language_tag: lang, + value: value, + }) +} + + +impl fmt::Display for ExtendedValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let encoded_value = + percent_encoding::percent_encode(&self.value[..], self::percent_encoding_http::HTTP_VALUE); + if let Some(ref lang) = self.language_tag { + write!(f, "{}'{}'{}", self.charset, lang, encoded_value) + } else { + write!(f, "{}''{}", self.charset, encoded_value) + } + } +} + +/// Percent encode a sequence of bytes with a character set defined in +/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] +/// +/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 +pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { + let encoded = percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} +mod percent_encoding_http { + use percent_encoding; + + // internal module because macro is hard-coded to make a public item + // but we don't want to public export this item + define_encode_set! { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 5d3767a2..25b4ef77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,7 @@ extern crate tokio_io; extern crate tokio_reactor; extern crate tokio_tcp; extern crate tokio_timer; +extern crate unicase; extern crate url; #[macro_use] extern crate serde; @@ -128,6 +129,7 @@ extern crate encoding; extern crate flate2; extern crate h2 as http2; extern crate num_cpus; +#[macro_use] extern crate percent_encoding; extern crate serde_json; extern crate serde_urlencoded; diff --git a/src/multipart.rs b/src/multipart.rs index f310327f..632a40c2 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -7,7 +7,7 @@ use std::{cmp, fmt}; use bytes::Bytes; use futures::task::{current as current_task, Task}; use futures::{Async, Poll, Stream}; -use http::header::{self, HeaderMap, HeaderName, HeaderValue}; +use http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; use http::HttpTryFrom; use httparse; use mime; @@ -362,7 +362,7 @@ where headers, mt, field, - ))))) + )?)))) } } } @@ -378,6 +378,7 @@ impl Drop for InnerMultipart { /// A single field in a multipart stream pub struct Field { ct: mime::Mime, + cd: ContentDisposition, headers: HeaderMap, inner: Rc>>, safety: Safety, @@ -390,13 +391,20 @@ where fn new( safety: Safety, headers: HeaderMap, ct: mime::Mime, inner: Rc>>, - ) -> Self { - Field { + ) -> Result { + // RFC 7578: 'Each part MUST contain a Content-Disposition header field + // where the disposition type is "form-data".' + let cd = ContentDisposition::from_raw( + headers.get(::http::header::CONTENT_DISPOSITION) + ).map_err(|_| MultipartError::ParseContentDisposition)?; + + Ok(Field { ct, + cd, headers, inner, safety, - } + }) } /// Get a map of headers @@ -408,6 +416,11 @@ where pub fn content_type(&self) -> &mime::Mime { &self.ct } + + /// Get the content disposition of the field + pub fn content_disposition(&self) -> &ContentDisposition { + &self.cd + } } impl Stream for Field From 82c888df22d4a7edefbfdef14f80bb8715b84d17 Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 09:10:46 +0000 Subject: [PATCH 0328/1635] fix test --- src/header/common/content_disposition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index 4d1a0c6d..b26644e5 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -213,7 +213,7 @@ mod tests { fn test_from_raw() { assert!(ContentDisposition::from_raw(Some(&HeaderValue::from_static(""))).is_err()); - let a = HeaderValue::from_static("form-data; dummy=3; name=upload;\r\n filename=\"sample.png\""); + let a = HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\""); let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); let b = ContentDisposition { disposition: DispositionType::Ext("form-data".to_owned()), From c0c1817b5c9cbcdd4c74c1671dd20bdd3eda7486 Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 10:33:00 +0000 Subject: [PATCH 0329/1635] remove unicase dependency --- Cargo.toml | 1 - src/header/common/content_disposition.rs | 12 +++++------- src/lib.rs | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9d080f53..9cd3304f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,6 @@ time = "0.1" encoding = "0.2" language-tags = "0.2" lazy_static = "1.0" -unicase = "2.1" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.10", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index b26644e5..b2563c0d 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -7,8 +7,6 @@ // IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml use language_tags::LanguageTag; -use unicase; - use header; use header::{Header, IntoHeaderValue, Writer}; use header::shared::Charset; @@ -100,9 +98,9 @@ impl ContentDisposition { }; let mut cd = ContentDisposition { - disposition: if unicase::eq_ascii(&*disposition, "inline") { + disposition: if disposition.eq_ignore_ascii_case("inline") { DispositionType::Inline - } else if unicase::eq_ascii(&*disposition, "attachment") { + } else if disposition.eq_ignore_ascii_case("attachment") { DispositionType::Attachment } else { DispositionType::Ext(disposition.to_owned()) @@ -126,11 +124,11 @@ impl ContentDisposition { }; cd.parameters.push( - if unicase::eq_ascii(&*key, "filename") { + if key.eq_ignore_ascii_case("filename") { DispositionParam::Filename( Charset::Ext("UTF-8".to_owned()), None, val.trim_matches('"').as_bytes().to_owned()) - } else if unicase::eq_ascii(&*key, "filename*") { + } else if key.eq_ignore_ascii_case("filename*") { let extended_value = try!(header::parse_extended_value(val)); DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value) } else { @@ -177,7 +175,7 @@ impl fmt::Display for ContentDisposition { let mut use_simple_format: bool = false; if opt_lang.is_none() { if let Charset::Ext(ref ext) = *charset { - if unicase::eq_ascii(&**ext, "utf-8") { + if ext.eq_ignore_ascii_case("utf-8") { use_simple_format = true; } } diff --git a/src/lib.rs b/src/lib.rs index 25b4ef77..5b142030 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,7 +118,6 @@ extern crate tokio_io; extern crate tokio_reactor; extern crate tokio_tcp; extern crate tokio_timer; -extern crate unicase; extern crate url; #[macro_use] extern crate serde; From 5a37a8b813a6aa606a3269bf5b141125812cce42 Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 10:55:36 +0000 Subject: [PATCH 0330/1635] restore hyper tests --- src/header/mod.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/header/mod.rs b/src/header/mod.rs index e4d4e049..8a7dd5bd 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -379,3 +379,78 @@ mod percent_encoding_http { } } } + +#[cfg(test)] +mod tests { + use header::shared::Charset; + use super::{ExtendedValue, parse_extended_value}; + use language_tags::LanguageTag; + + #[test] + fn test_parse_extended_value_with_encoding_and_language_tag() { + let expected_language_tag = "en".parse::().unwrap(); + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode character U+00A3 (POUND SIGN) + let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Iso_8859_1, extended_value.charset); + assert!(extended_value.language_tag.is_some()); + assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); + assert_eq!(vec![163, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); + } + + #[test] + fn test_parse_extended_value_with_encoding() { + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) + // and U+20AC (EURO SIGN) + let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); + assert!(extended_value.language_tag.is_none()); + assert_eq!(vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); + } + + #[test] + fn test_parse_extended_value_missing_language_tag_and_encoding() { + // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 + let result = parse_extended_value("foo%20bar.html"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted() { + let result = parse_extended_value("UTF-8'missing third part"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted_blank() { + let result = parse_extended_value("blank second part'"); + assert!(result.is_err()); + } + + #[test] + fn test_fmt_extended_value_with_encoding_and_language_tag() { + let extended_value = ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: Some("en".parse().expect("Could not parse language tag")), + value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], + }; + assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); + } + + #[test] + fn test_fmt_extended_value_with_encoding() { + let extended_value = ExtendedValue { + charset: Charset::Ext("UTF-8".to_string()), + language_tag: None, + value: vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's'], + }; + assert_eq!("UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", + format!("{}", extended_value)); + } +} From 31a301c9a6e39ec1995ebdbefefb1a79c4a84754 Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 11:38:35 +0000 Subject: [PATCH 0331/1635] fix multipart test --- src/multipart.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/multipart.rs b/src/multipart.rs index 632a40c2..9727ec0a 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -736,9 +736,11 @@ mod tests { let bytes = Bytes::from( "testasdadsad\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ test\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ data\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n"); @@ -751,6 +753,12 @@ mod tests { match multipart.poll() { Ok(Async::Ready(Some(item))) => match item { MultipartItem::Field(mut field) => { + { + use http::header::{DispositionType, DispositionParam}; + let cd = field.content_disposition(); + assert_eq!(cd.disposition, DispositionType::Ext("form-data".into())); + assert_eq!(cd.parameters[0], DispositionParam::Ext("name".into(), "file".into())); + } assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); From a6e07c06b6ecd80ddd53cd9f2fb100b2486a8452 Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 12:32:49 +0000 Subject: [PATCH 0332/1635] move CD parsing to Content-Type parsing location --- src/multipart.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/multipart.rs b/src/multipart.rs index 9727ec0a..a92c235a 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -317,6 +317,13 @@ where return Ok(Async::NotReady); }; + // content disposition + // RFC 7578: 'Each part MUST contain a Content-Disposition header field + // where the disposition type is "form-data".' + let cd = ContentDisposition::from_raw( + headers.get(::http::header::CONTENT_DISPOSITION) + ).map_err(|_| MultipartError::ParseContentDisposition)?; + // content type let mut mt = mime::APPLICATION_OCTET_STREAM; if let Some(content_type) = headers.get(header::CONTENT_TYPE) { @@ -360,9 +367,10 @@ where Ok(Async::Ready(Some(MultipartItem::Field(Field::new( safety.clone(), headers, + cd, mt, field, - )?)))) + ))))) } } } @@ -377,8 +385,8 @@ impl Drop for InnerMultipart { /// A single field in a multipart stream pub struct Field { - ct: mime::Mime, cd: ContentDisposition, + ct: mime::Mime, headers: HeaderMap, inner: Rc>>, safety: Safety, @@ -389,22 +397,16 @@ where S: Stream, { fn new( - safety: Safety, headers: HeaderMap, ct: mime::Mime, + safety: Safety, headers: HeaderMap, cd: ContentDisposition, ct: mime::Mime, inner: Rc>>, - ) -> Result { - // RFC 7578: 'Each part MUST contain a Content-Disposition header field - // where the disposition type is "form-data".' - let cd = ContentDisposition::from_raw( - headers.get(::http::header::CONTENT_DISPOSITION) - ).map_err(|_| MultipartError::ParseContentDisposition)?; - - Ok(Field { - ct, + ) -> Self { + Field { cd, + ct, headers, inner, safety, - }) + } } /// Get a map of headers From 97b5410aad12890b3bc4b3bfd90967d35399c524 Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 12:55:35 +0000 Subject: [PATCH 0333/1635] remove Option from ContentDisposition::from_raw() argument --- src/header/common/content_disposition.rs | 24 ++++++++++++++---------- src/multipart.rs | 8 +++++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index b2563c0d..bc5014f5 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -89,8 +89,8 @@ pub struct ContentDisposition { } impl ContentDisposition { /// Parse a raw Content-Disposition header value - pub fn from_raw(hv: Option<&header::HeaderValue>) -> Result { - header::from_one_raw_str(hv).and_then(|s: String| { + pub fn from_raw(hv: &header::HeaderValue) -> Result { + header::from_one_raw_str(Some(hv)).and_then(|s: String| { let mut sections = s.split(';'); let disposition = match sections.next() { Some(s) => s.trim(), @@ -158,7 +158,11 @@ impl Header for ContentDisposition { } fn parse(msg: &T) -> Result { - Self::from_raw(msg.headers().get(Self::name())) + if let Some(h) = msg.headers().get(Self::name()) { + Self::from_raw(&h) + } else { + Err(::error::ParseError::Header) + } } } @@ -209,10 +213,10 @@ mod tests { use header::shared::Charset; #[test] fn test_from_raw() { - assert!(ContentDisposition::from_raw(Some(&HeaderValue::from_static(""))).is_err()); + assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); let a = HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\""); - let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Ext("form-data".to_owned()), parameters: vec![ @@ -226,7 +230,7 @@ mod tests { assert_eq!(a, b); let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); - let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ @@ -238,7 +242,7 @@ mod tests { assert_eq!(a, b); let a = HeaderValue::from_static("attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ @@ -255,17 +259,17 @@ mod tests { fn test_display() { let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!(as_string, display_rendered); let a = HeaderValue::from_static("attachment; filename*=UTF-8''black%20and%20white.csv"); - let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered); let a = HeaderValue::from_static("attachment; filename=colourful.csv"); - let a: ContentDisposition = ContentDisposition::from_raw(Some(&a)).unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered); } diff --git a/src/multipart.rs b/src/multipart.rs index a92c235a..542c6c3d 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -320,9 +320,11 @@ where // content disposition // RFC 7578: 'Each part MUST contain a Content-Disposition header field // where the disposition type is "form-data".' - let cd = ContentDisposition::from_raw( - headers.get(::http::header::CONTENT_DISPOSITION) - ).map_err(|_| MultipartError::ParseContentDisposition)?; + let cd = match headers.get(::http::header::CONTENT_DISPOSITION) { + Some(content_disposition) => ContentDisposition::from_raw(content_disposition) + .map_err(|_| MultipartError::ParseContentDisposition)?, + None => return Err(MultipartError::ParseContentDisposition) + }; // content type let mut mt = mime::APPLICATION_OCTET_STREAM; From 789af0bbf208aae09a6a15d26d6f97a56cd5fbdf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jun 2018 18:53:27 +0200 Subject: [PATCH 0334/1635] Added improved failure interoperability with downcasting (#285) Deprecates Error::cause and introduces failure interoperability functions and downcasting. --- src/error.rs | 110 +++++++++++++++++++++++++++++++++++++++++--- src/httpresponse.rs | 2 +- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index cfb6a028..d08093fb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -34,21 +34,42 @@ use httpresponse::HttpResponse; /// `Result`. pub type Result = result::Result; -/// General purpose actix web error +/// General purpose actix web error. +/// +/// An actix web error is used to carry errors from `failure` or `std::error` +/// through actix in a convenient way. It can be created through through +/// converting errors with `into()`. +/// +/// Whenever it is created from an external object a response error is created +/// for it that can be used to create an http response from it this means that +/// if you have access to an actix `Error` you can always get a +/// `ResponseError` reference from it. pub struct Error { cause: Box, backtrace: Option, } 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 + /// Deprecated way to reference the underlying response error. + #[deprecated(since = "0.6.0", note = "please use `Error::as_response_error()` instead")] pub fn cause(&self) -> &ResponseError { self.cause.as_ref() } + /// Returns a reference to the underlying cause of this `Error` as `Fail` + pub fn as_fail(&self) -> &Fail { + self.cause.as_fail() + } + + /// Returns the reference to the underlying `ResponseError`. + pub fn as_response_error(&self) -> &ResponseError { + self.cause.as_ref() + } + /// Returns a reference to the Backtrace carried by this error, if it /// carries one. + /// + /// This uses the same `Backtrace` type that `failure` uses. pub fn backtrace(&self) -> &Backtrace { if let Some(bt) = self.cause.backtrace() { bt @@ -56,10 +77,61 @@ impl Error { self.backtrace.as_ref().unwrap() } } + + /// Attempts to downcast this `Error` to a particular `Fail` type by reference. + /// + /// If the underlying error is not of type `T`, this will return `None`. + pub fn downcast_ref(&self) -> Option<&T> { + // in the most trivial way the cause is directly of the requested type. + if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) { + return Some(rv); + } + + // in the more complex case the error has been constructed from a failure + // error. This happens because we implement From by + // calling compat() and then storing it here. In failure this is + // represented by a failure::Error being wrapped in a failure::Compat. + // + // So we first downcast into that compat, to then further downcast through + // the failure's Error downcasting system into the original failure. + // + // This currently requires a transmute. This could be avoided if failure + // provides a deref: https://github.com/rust-lang-nursery/failure/pull/213 + let compat: Option<&failure::Compat> = Fail::downcast_ref(self.cause.as_fail()); + if let Some(compat) = compat { + pub struct CompatWrappedError { + error: failure::Error, + } + let compat: &CompatWrappedError = unsafe { + ::std::mem::transmute(compat) + }; + compat.error.downcast_ref() + } else { + None + } + } +} + +/// Helper trait to downcast a response error into a fail. +/// +/// This is currently not exposed because it's unclear if this is the best way to +/// achieve the downcasting on `Error` for which this is needed. +#[doc(hidden)] +pub trait InternalResponseErrorAsFail { + #[doc(hidden)] + fn as_fail(&self) -> &Fail; + #[doc(hidden)] + fn as_mut_fail(&mut self) -> &mut Fail; +} + +#[doc(hidden)] +impl InternalResponseErrorAsFail for T { + fn as_fail(&self) -> &Fail { self } + fn as_mut_fail(&mut self) -> &mut Fail { self } } /// Error that can be converted to `HttpResponse` -pub trait ResponseError: Fail { +pub trait ResponseError: Fail + InternalResponseErrorAsFail { /// Create response for error /// /// Internal server error is generated by default. @@ -853,7 +925,7 @@ mod tests { } #[test] - fn test_cause() { + fn test_as_fail() { let orig = io::Error::new(io::ErrorKind::Other, "other"); let desc = orig.description().to_owned(); let e = ParseError::Io(orig); @@ -871,7 +943,7 @@ mod tests { let orig = io::Error::new(io::ErrorKind::Other, "other"); let desc = orig.description().to_owned(); let e = Error::from(orig); - assert_eq!(format!("{}", e.cause()), desc); + assert_eq!(format!("{}", e.as_fail()), desc); } #[test] @@ -970,6 +1042,32 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } + #[test] + fn test_error_downcasting_direct() { + #[derive(Debug, Fail)] + #[fail(display = "demo error")] + struct DemoError; + + impl ResponseError for DemoError {} + + let err: Error = DemoError.into(); + let err_ref: &DemoError = err.downcast_ref().unwrap(); + assert_eq!(err_ref.to_string(), "demo error"); + } + + #[test] + fn test_error_downcasting_compat() { + #[derive(Debug, Fail)] + #[fail(display = "demo error")] + struct DemoError; + + impl ResponseError for DemoError {} + + let err: Error = failure::Error::from(DemoError).into(); + let err_ref: &DemoError = err.downcast_ref().unwrap(); + assert_eq!(err_ref.to_string(), "demo error"); + } + #[test] fn test_error_helpers() { let r: HttpResponse = ErrorBadRequest("err").into(); diff --git a/src/httpresponse.rs b/src/httpresponse.rs index ac84b2be..ce036027 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -89,7 +89,7 @@ impl HttpResponse { /// Constructs an error response #[inline] pub fn from_error(error: Error) -> HttpResponse { - let mut resp = error.cause().error_response(); + let mut resp = error.as_response_error().error_response(); resp.get_mut().error = Some(error); resp } From 56e0dc06c16bba83b78116a4b18c9b7c0040cc1f Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 17:29:46 +0000 Subject: [PATCH 0335/1635] defer parsing until user method call --- src/error.rs | 3 --- src/multipart.rs | 29 +++++++++++------------------ 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/error.rs b/src/error.rs index f4de3681..cfb6a028 100644 --- a/src/error.rs +++ b/src/error.rs @@ -353,9 +353,6 @@ pub enum MultipartError { /// Can not parse Content-Type header #[fail(display = "Can not parse Content-Type header")] ParseContentType, - /// Can not parse Content-Disposition header - #[fail(display = "Can not parse Content-Disposition header")] - ParseContentDisposition, /// Multipart boundary is not found #[fail(display = "Multipart boundary is not found")] Boundary, diff --git a/src/multipart.rs b/src/multipart.rs index 542c6c3d..9c5c0380 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -317,15 +317,6 @@ where return Ok(Async::NotReady); }; - // content disposition - // RFC 7578: 'Each part MUST contain a Content-Disposition header field - // where the disposition type is "form-data".' - let cd = match headers.get(::http::header::CONTENT_DISPOSITION) { - Some(content_disposition) => ContentDisposition::from_raw(content_disposition) - .map_err(|_| MultipartError::ParseContentDisposition)?, - None => return Err(MultipartError::ParseContentDisposition) - }; - // content type let mut mt = mime::APPLICATION_OCTET_STREAM; if let Some(content_type) = headers.get(header::CONTENT_TYPE) { @@ -369,7 +360,6 @@ where Ok(Async::Ready(Some(MultipartItem::Field(Field::new( safety.clone(), headers, - cd, mt, field, ))))) @@ -387,7 +377,6 @@ impl Drop for InnerMultipart { /// A single field in a multipart stream pub struct Field { - cd: ContentDisposition, ct: mime::Mime, headers: HeaderMap, inner: Rc>>, @@ -399,11 +388,10 @@ where S: Stream, { fn new( - safety: Safety, headers: HeaderMap, cd: ContentDisposition, ct: mime::Mime, + safety: Safety, headers: HeaderMap, ct: mime::Mime, inner: Rc>>, ) -> Self { Field { - cd, ct, headers, inner, @@ -421,9 +409,15 @@ where &self.ct } - /// Get the content disposition of the field - pub fn content_disposition(&self) -> &ContentDisposition { - &self.cd + /// Get the content disposition of the field, if it exists + pub fn content_disposition(&self) -> Option { + // RFC 7578: 'Each part MUST contain a Content-Disposition header field + // where the disposition type is "form-data".' + if let Some(content_disposition) = self.headers.get(::http::header::CONTENT_DISPOSITION) { + ContentDisposition::from_raw(content_disposition).ok() + } else { + None + } } } @@ -744,7 +738,6 @@ mod tests { Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ test\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ data\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n"); @@ -759,7 +752,7 @@ mod tests { MultipartItem::Field(mut field) => { { use http::header::{DispositionType, DispositionParam}; - let cd = field.content_disposition(); + let cd = field.content_disposition().unwrap(); assert_eq!(cd.disposition, DispositionType::Ext("form-data".into())); assert_eq!(cd.parameters[0], DispositionParam::Ext("name".into(), "file".into())); } From e970846167a80942d4d957f46fa5c9df069fac97 Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 17:59:35 +0000 Subject: [PATCH 0336/1635] update changelog --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f46fa9cb..bf33040e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ ### Added +* Add `.content_disposition()` method to parse Content-Disposition of + multipart fields + * Re-export `actix::prelude::*` as `actix_web::actix` module. * `HttpRequest::url_for_static()` for a named route with no variables segments From a11f3c112f7e9da8fc3f1dce4577d1da7f35fe88 Mon Sep 17 00:00:00 2001 From: axon-q Date: Thu, 7 Jun 2018 21:16:28 +0000 Subject: [PATCH 0337/1635] fix doc test --- src/header/common/content_disposition.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index bc5014f5..93102d46 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -68,17 +68,16 @@ pub enum DispositionParam { /// # Example /// /// ``` -/// use hyper::header::{Headers, ContentDisposition, DispositionType, DispositionParam, Charset}; +/// use actix_web::http::header::{ContentDisposition, DispositionType, DispositionParam, Charset}; /// -/// let mut headers = Headers::new(); -/// headers.set(ContentDisposition { +/// let cd = ContentDisposition { /// disposition: DispositionType::Attachment, /// parameters: vec![DispositionParam::Filename( /// Charset::Iso_8859_1, // The character set for the bytes of the filename /// None, // The optional language tag (see `language-tag` crate) /// b"\xa9 Copyright 1989.txt".to_vec() // the actual bytes of the filename /// )] -/// }); +/// }; /// ``` #[derive(Clone, Debug, PartialEq)] pub struct ContentDisposition { From f7bd6eeedcd0705b31c2bca71fe92092179b9a4f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Jun 2018 19:46:38 -0700 Subject: [PATCH 0338/1635] add application filters --- src/application.rs | 66 +++++++++++++++++++++++++++++++++++++++++++--- src/httprequest.rs | 6 +++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/application.rs b/src/application.rs index 90c70bd1..c9171008 100644 --- a/src/application.rs +++ b/src/application.rs @@ -22,6 +22,7 @@ pub struct HttpApplication { prefix_len: usize, router: Router, inner: Rc>>, + filters: Option>>>, middlewares: Rc>>>>, } @@ -143,11 +144,21 @@ impl HttpHandler for HttpApplication { || path.split_at(self.prefix_len).1.starts_with('/')) }; if m { - let mut req = req.with_state(Rc::clone(&self.state), self.router.clone()); - let tp = self.get_handler(&mut req); + let mut req2 = + req.clone_with_state(Rc::clone(&self.state), self.router.clone()); + + if let Some(ref filters) = self.filters { + for filter in filters { + if !filter.check(&mut req2) { + return Err(req); + } + } + } + + let tp = self.get_handler(&mut req2); let inner = Rc::clone(&self.inner); Ok(Box::new(Pipeline::new( - req, + req2, Rc::clone(&self.middlewares), inner, tp, @@ -168,6 +179,7 @@ struct ApplicationParts { external: HashMap, encoding: ContentEncoding, middlewares: Vec>>, + filters: Vec>>, } /// Structure that follows the builder pattern for building application @@ -190,6 +202,7 @@ impl App<()> { handlers: Vec::new(), external: HashMap::new(), encoding: ContentEncoding::Auto, + filters: Vec::new(), middlewares: Vec::new(), }), } @@ -229,6 +242,7 @@ where handlers: Vec::new(), external: HashMap::new(), middlewares: Vec::new(), + filters: Vec::new(), encoding: ContentEncoding::Auto, }), } @@ -285,6 +299,26 @@ where self } + /// Add match predicate to application. + /// + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::*; + /// # fn main() { + /// App::new() + /// .filter(pred::Get()) + /// .resource("/path", |r| r.f(|_| HttpResponse::Ok())) + /// # .finish(); + /// # } + /// ``` + pub fn filter + 'static>(mut self, p: T) -> App { + { + let parts = self.parts.as_mut().expect("Use after finish"); + parts.filters.push(Box::new(p)); + } + self + } + /// Configure route for a specific path. /// /// This is a simplified version of the `App::resource()` method. @@ -608,6 +642,11 @@ where handlers: parts.handlers, resources, })); + let filters = if parts.filters.is_empty() { + None + } else { + Some(parts.filters) + }; HttpApplication { state: Rc::new(parts.state), @@ -616,6 +655,7 @@ where prefix, prefix_len, inner, + filters, } } @@ -700,7 +740,8 @@ mod tests { use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; - use test::TestRequest; + use pred; + use test::{TestRequest, TestServer}; #[test] fn test_default_resource() { @@ -899,4 +940,21 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } + + #[test] + fn test_filter() { + let mut srv = TestServer::with_factory(|| { + App::new() + .filter(pred::Get()) + .handler("/test", |_| HttpResponse::Ok()) + }); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::OK); + + let request = srv.post().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); + } } diff --git a/src/httprequest.rs b/src/httprequest.rs index d852bc74..a54a9958 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -141,6 +141,12 @@ impl HttpRequest<()> { pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { HttpRequest(self.0, Some(state), Some(router)) } + + pub(crate) fn clone_with_state( + &self, state: Rc, router: Router, + ) -> HttpRequest { + HttpRequest(self.0.clone(), Some(state), Some(router)) + } } impl HttpMessage for HttpRequest { From 60d40df54560b6aa37e4c046e0f9737f7fd7c559 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Jun 2018 19:46:46 -0700 Subject: [PATCH 0339/1635] fix clippy warning --- src/error.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index d08093fb..4b2dae72 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,7 +36,7 @@ pub type Result = result::Result; /// General purpose actix web error. /// -/// An actix web error is used to carry errors from `failure` or `std::error` +/// An actix web error is used to carry errors from `failure` or `std::error` /// through actix in a convenient way. It can be created through through /// converting errors with `into()`. /// @@ -51,7 +51,9 @@ pub struct Error { impl Error { /// Deprecated way to reference the underlying response error. - #[deprecated(since = "0.6.0", note = "please use `Error::as_response_error()` instead")] + #[deprecated( + since = "0.6.0", note = "please use `Error::as_response_error()` instead" + )] pub fn cause(&self) -> &ResponseError { self.cause.as_ref() } @@ -97,14 +99,14 @@ impl Error { // // This currently requires a transmute. This could be avoided if failure // provides a deref: https://github.com/rust-lang-nursery/failure/pull/213 - let compat: Option<&failure::Compat> = Fail::downcast_ref(self.cause.as_fail()); + let compat: Option<&failure::Compat> = + Fail::downcast_ref(self.cause.as_fail()); if let Some(compat) = compat { pub struct CompatWrappedError { error: failure::Error, } - let compat: &CompatWrappedError = unsafe { - ::std::mem::transmute(compat) - }; + let compat: &CompatWrappedError = + unsafe { &*(compat as *const _ as *const CompatWrappedError) }; compat.error.downcast_ref() } else { None @@ -126,8 +128,12 @@ pub trait InternalResponseErrorAsFail { #[doc(hidden)] impl InternalResponseErrorAsFail for T { - fn as_fail(&self) -> &Fail { self } - fn as_mut_fail(&mut self) -> &mut Fail { self } + fn as_fail(&self) -> &Fail { + self + } + fn as_mut_fail(&mut self) -> &mut Fail { + self + } } /// Error that can be converted to `HttpResponse` From f7ef8ae5a5831fabc9aa176f1927c2698c5bb375 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Jun 2018 20:00:54 -0700 Subject: [PATCH 0340/1635] add Host predicate --- src/application.rs | 2 +- src/pred.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/application.rs b/src/application.rs index c9171008..eb078746 100644 --- a/src/application.rs +++ b/src/application.rs @@ -306,7 +306,7 @@ where /// # use actix_web::*; /// # fn main() { /// App::new() - /// .filter(pred::Get()) + /// .filter(pred::Hoat("www.rust-lang.org")) /// .resource("/path", |r| r.f(|_| HttpResponse::Ok())) /// # .finish(); /// # } diff --git a/src/pred.rs b/src/pred.rs index 206a7941..020052e2 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -196,6 +196,45 @@ impl Predicate for HeaderPredicate { } } +/// Return predicate that matches if request contains specified Host name. +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::{pred, App, HttpResponse}; +/// +/// fn main() { +/// App::new().resource("/index.html", |r| { +/// r.route() +/// .filter(pred::Host("www.rust-lang.org")) +/// .f(|_| HttpResponse::MethodNotAllowed()) +/// }); +/// } +/// ``` +pub fn Host>(host: H) -> HostPredicate { + HostPredicate(host.as_ref().to_string(), None, PhantomData) +} + +#[doc(hidden)] +pub struct HostPredicate(String, Option, PhantomData); + +impl HostPredicate { + /// Set reuest scheme to match + pub fn scheme>(&mut self, scheme: H) { + self.1 = Some(scheme.as_ref().to_string()) + } +} + +impl Predicate for HostPredicate { + fn check(&self, req: &mut HttpRequest) -> bool { + let info = req.connection_info(); + if let Some(ref scheme) = self.1 { + self.0 == info.host() && scheme == info.scheme() + } else { + self.0 == info.host() + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -228,6 +267,28 @@ mod tests { assert!(!pred.check(&mut req)); } + #[test] + fn test_host() { + 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, + None, + ); + + let pred = Host("www.rust-lang.org"); + assert!(pred.check(&mut req)); + + let pred = Host("localhost"); + assert!(!pred.check(&mut req)); + } + #[test] fn test_methods() { let mut req = HttpRequest::new( From ce40ab307b539af33f1cfbfa647968ac22fd0db2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Jun 2018 20:09:08 -0700 Subject: [PATCH 0341/1635] update changes --- CHANGES.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f46fa9cb..403e3296 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,11 +19,23 @@ * Added header `User-Agent: Actix-web/` to default headers when building a request + ### Removed * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. +## [0.6.12] - 2018-06-07 + +### Added + +* Add `Host` filter #287 + +* Allow to filter applications + +* Improved failure interoperability with downcasting #285 + + ## [0.6.11] - 2018-06-05 * Support chunked encoding for UrlEncoded body #262 From f9f2ed04abef3aa8ac25ed4ffbf408ba98d2b726 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Jun 2018 20:22:23 -0700 Subject: [PATCH 0342/1635] fix doc test --- src/application.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/application.rs b/src/application.rs index eb078746..e16f85cd 100644 --- a/src/application.rs +++ b/src/application.rs @@ -306,7 +306,7 @@ where /// # use actix_web::*; /// # fn main() { /// App::new() - /// .filter(pred::Hoat("www.rust-lang.org")) + /// .filter(pred::Host("www.rust-lang.org")) /// .resource("/path", |r| r.f(|_| HttpResponse::Ok())) /// # .finish(); /// # } From efb5d13280570b5e6b7d1d5c1369f4bd20532860 Mon Sep 17 00:00:00 2001 From: michael Date: Thu, 7 Jun 2018 23:55:08 -0400 Subject: [PATCH 0343/1635] readme: link to TechEmpower r16 benchmarks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 427838c1..158df61b 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ You may consider checking out ## Benchmarks -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext) +* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) * Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). From 9151d61eda2a7b4fcce581faef5e22393dd49720 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Jun 2018 16:33:57 -0700 Subject: [PATCH 0344/1635] allow to use custom resolver for ClientConnector --- CHANGES.md | 4 +++- src/client/connector.rs | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2fee070c..9171a237 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,7 +28,7 @@ * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. -## [0.6.12] - 2018-06-07 +## [0.6.12] - 2018-06-08 ### Added @@ -38,6 +38,8 @@ * Improved failure interoperability with downcasting #285 +* Allow to use custom resolver for `ClientConnector` + ## [0.6.11] - 2018-06-05 diff --git a/src/client/connector.rs b/src/client/connector.rs index 431d4df3..61dda22e 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -5,8 +5,9 @@ use std::{fmt, io, mem, time}; use actix::resolver::{Connect as ResolveConnect, Connector, ConnectorError}; use actix::{ - fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context, ContextFutureSpawner, - Handler, Message, Recipient, StreamHandler, Supervised, SystemService, WrapFuture, + fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context, + ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, + SystemService, WrapFuture, }; use futures::sync::{mpsc, oneshot}; @@ -197,6 +198,7 @@ pub struct ClientConnector { acq_tx: mpsc::UnboundedSender, acq_rx: Option>, + resolver: Addr, conn_lifetime: Duration, conn_keep_alive: Duration, limit: usize, @@ -240,6 +242,7 @@ impl Default for ClientConnector { subscriber: None, acq_tx: tx, acq_rx: Some(rx), + resolver: Connector::from_registry(), connector: builder.build().unwrap(), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), @@ -263,6 +266,7 @@ impl Default for ClientConnector { subscriber: None, acq_tx: tx, acq_rx: Some(rx), + resolver: Connector::from_registry(), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), limit: 100, @@ -329,6 +333,7 @@ impl ClientConnector { subscriber: None, acq_tx: tx, acq_rx: Some(rx), + resolver: Connector::from_registry(), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), limit: 100, @@ -388,6 +393,12 @@ impl ClientConnector { self } + /// Use custom resolver actor + pub fn resolver(mut self, addr: Addr) -> Self { + self.resolver = addr; + self + } + fn acquire(&mut self, key: &Key) -> Acquire { // check limits if self.limit > 0 { @@ -655,7 +666,7 @@ impl Handler for ClientConnector { { ActorResponse::async( - Connector::from_registry() + self.resolver .send( ResolveConnect::host_and_port(&conn.0.host, port) .timeout(conn_timeout), From 3751656722ca4f0a220a6555ec37d270e6b6d06c Mon Sep 17 00:00:00 2001 From: axon-q Date: Sat, 9 Jun 2018 11:20:06 +0000 Subject: [PATCH 0345/1635] expose fs::file_extension_to_mime() function --- src/fs.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/fs.rs b/src/fs.rs index a4418bce..30755778 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -29,6 +29,14 @@ use param::FromParam; /// Env variable for default cpu pool size for `StaticFiles` const ENV_CPU_POOL_VAR: &str = "ACTIX_FS_POOL"; +/// Return the MIME type associated with a filename extension (case-insensitive). +/// If `ext` is empty or no associated type for the extension was found, returns +/// the type `application/octet-stream`. +#[inline] +pub fn file_extension_to_mime(ext: &str) -> mime::Mime { + get_mime_type(ext) +} + /// A file with an associated name; responds with the Content-Type based on the /// file extension. #[derive(Debug)] @@ -692,6 +700,18 @@ mod tests { use http::{header, Method, StatusCode}; use test::{self, TestRequest}; + #[test] + fn test_file_extension_to_mime() { + let m = file_extension_to_mime("jpg"); + assert_eq!(m, mime::IMAGE_JPEG); + + let m = file_extension_to_mime("invalid extension!!"); + assert_eq!(m, mime::APPLICATION_OCTET_STREAM); + + let m = file_extension_to_mime(""); + assert_eq!(m, mime::APPLICATION_OCTET_STREAM); + } + #[test] fn test_named_file_text() { assert!(NamedFile::open("test--").is_err()); From 1fdf6d13be9037cf82b90305f43dc02b604780a1 Mon Sep 17 00:00:00 2001 From: axon-q Date: Sat, 9 Jun 2018 13:38:21 +0000 Subject: [PATCH 0346/1635] content_disposition: add doc example --- src/header/common/content_disposition.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index 93102d46..0edebfed 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -70,7 +70,7 @@ pub enum DispositionParam { /// ``` /// use actix_web::http::header::{ContentDisposition, DispositionType, DispositionParam, Charset}; /// -/// let cd = ContentDisposition { +/// let cd1 = ContentDisposition { /// disposition: DispositionType::Attachment, /// parameters: vec![DispositionParam::Filename( /// Charset::Iso_8859_1, // The character set for the bytes of the filename @@ -78,6 +78,15 @@ pub enum DispositionParam { /// b"\xa9 Copyright 1989.txt".to_vec() // the actual bytes of the filename /// )] /// }; +/// +/// let cd2 = ContentDisposition { +/// disposition: DispositionType::Inline, +/// parameters: vec![DispositionParam::Filename( +/// Charset::Ext("UTF-8".to_owned()), +/// None, +/// "\u{2764}".as_bytes().to_vec() +/// )] +/// }; /// ``` #[derive(Clone, Debug, PartialEq)] pub struct ContentDisposition { From 8681a346c62084e324fc460845a44945047ce43b Mon Sep 17 00:00:00 2001 From: axon-q Date: Sat, 9 Jun 2018 13:48:36 +0000 Subject: [PATCH 0347/1635] fs: refactor Content-Type and Content-Disposition handling --- src/fs.rs | 174 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 132 insertions(+), 42 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 30755778..fd8abfaf 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -37,12 +37,13 @@ pub fn file_extension_to_mime(ext: &str) -> mime::Mime { get_mime_type(ext) } -/// A file with an associated name; responds with the Content-Type based on the -/// file extension. +/// A file with an associated name. #[derive(Debug)] pub struct NamedFile { path: PathBuf, file: File, + content_type: mime::Mime, + content_disposition: header::ContentDisposition, md: Metadata, modified: Option, cpu_pool: Option, @@ -62,15 +63,48 @@ impl NamedFile { /// let file = NamedFile::open("foo.txt"); /// ``` pub fn open>(path: P) -> io::Result { - let file = File::open(path.as_ref())?; - let md = file.metadata()?; + use header::{ContentDisposition, DispositionType, DispositionParam}; let path = path.as_ref().to_path_buf(); + + // Get the name of the file and use it to construct default Content-Type + // and Content-Disposition values + let content_type; + let content_disposition; + { + let filename = match path.file_name() { + Some(name) => name.to_string_lossy(), + None => return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Provided path has no filename")), + }; + + content_type = guess_mime_type(&path); + let disposition_type = match content_type.type_() { + mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, + _ => DispositionType::Attachment, + }; + content_disposition = ContentDisposition { + disposition: disposition_type, + parameters: vec![ + DispositionParam::Filename( + header::Charset::Ext("UTF-8".to_owned()), + None, + filename.as_bytes().to_vec(), + ) + ], + }; + } + + let file = File::open(&path)?; + let md = file.metadata()?; let modified = md.modified().ok(); let cpu_pool = None; let encoding = None; Ok(NamedFile { path, file, + content_type, + content_disposition, md, modified, cpu_pool, @@ -125,6 +159,27 @@ impl NamedFile { self } + /// Set the MIME Content-Type for serving this file. By default + /// the Content-Type is inferred from the filename extension. + #[inline] + pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { + self.content_type = mime_type; + self + } + + /// Set the Content-Disposition for serving this file. This allows + /// changing the inline/attachment disposition as well as the filename + /// sent to the peer. By default the disposition is `inline` for text, + /// image, and video content types, and `attachment` otherwise, and + /// the filename is taken from the path provided in the `open` method + /// after converting it to UTF-8 using + /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). + #[inline] + pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { + self.content_disposition = cd; + self + } + /// Set content encoding for serving this file #[inline] pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { @@ -220,23 +275,10 @@ impl Responder for NamedFile { fn respond_to(self, req: &HttpRequest) -> Result { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); - resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); - }).if_some(self.path().file_name(), |file_name, resp| { - let mime_type = guess_mime_type(self.path()); - let inline_or_attachment = match mime_type.type_() { - mime::IMAGE | mime::TEXT | mime::VIDEO => "inline", - _ => "attachment", - }; - resp.header( - "Content-Disposition", - format!( - "{inline_or_attachment}; filename={filename}", - inline_or_attachment = inline_or_attachment, - filename = file_name.to_string_lossy() - ), - ); - }); + + resp.set(header::ContentType(self.content_type.clone())); + resp.header("Content-Disposition", format!("{}", &self.content_disposition)); + if let Some(current_encoding) = self.encoding { resp.content_encoding(current_encoding); } @@ -289,23 +331,10 @@ impl Responder for NamedFile { resp.content_encoding(current_encoding); } - resp.if_some(self.path().extension(), |ext, resp| { - resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); - }).if_some(self.path().file_name(), |file_name, resp| { - let mime_type = guess_mime_type(self.path()); - let inline_or_attachment = match mime_type.type_() { - mime::IMAGE | mime::TEXT | mime::VIDEO => "inline", - _ => "attachment", - }; - resp.header( - "Content-Disposition", - format!( - "{inline_or_attachment}; filename={filename}", - inline_or_attachment = inline_or_attachment, - filename = file_name.to_string_lossy() - ), - ); - }) + resp.set(header::ContentType(self.content_type.clone())); + resp.header("Content-Disposition", format!("{}", &self.content_disposition)); + + resp .if_some(last_modified, |lm, resp| { resp.set(header::LastModified(lm)); }) @@ -733,7 +762,32 @@ mod tests { ); assert_eq!( resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=Cargo.toml" + "inline; filename=\"Cargo.toml\"" + ); + } + + #[test] + fn test_named_file_set_content_type() { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_content_type(mime::TEXT_XML) + .set_cpu_pool(CpuPool::new(1)); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let resp = file.respond_to(&HttpRequest::default()).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/xml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" ); } @@ -757,7 +811,43 @@ mod tests { ); assert_eq!( resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=test.png" + "inline; filename=\"test.png\"" + ); + } + + #[test] + fn test_named_file_image_attachment() { + use header::{ContentDisposition, DispositionType, DispositionParam}; + let cd = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![ + DispositionParam::Filename( + header::Charset::Ext("UTF-8".to_owned()), + None, + "test.png".as_bytes().to_vec(), + ) + ], + }; + let mut file = NamedFile::open("tests/test.png") + .unwrap() + .set_content_disposition(cd) + .set_cpu_pool(CpuPool::new(1)); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let resp = file.respond_to(&HttpRequest::default()).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.png\"" ); } @@ -781,7 +871,7 @@ mod tests { ); assert_eq!( resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=test.binary" + "attachment; filename=\"test.binary\"" ); } @@ -806,7 +896,7 @@ mod tests { ); assert_eq!( resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=Cargo.toml" + "inline; filename=\"Cargo.toml\"" ); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } From fee203b4029a7c8a1878b88be2f7aacc6df5511d Mon Sep 17 00:00:00 2001 From: axon-q Date: Sat, 9 Jun 2018 14:02:05 +0000 Subject: [PATCH 0348/1635] update changelog --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 9171a237..29046f8f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,12 @@ ### Added +* Add `.set_content_type()` and `.set_content_disposition()` methods + to `fs::NamedFile` to allow overriding the values inferred by default + +* Add `fs::file_extension_to_mime()` helper function to get the MIME + type for a file extension + * Add `.content_disposition()` method to parse Content-Disposition of multipart fields From aee24d4af027a6def09a6facf0f3765a9237efd2 Mon Sep 17 00:00:00 2001 From: axon-q Date: Sat, 9 Jun 2018 14:47:06 +0000 Subject: [PATCH 0349/1635] minor syntax changes --- src/fs.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index fd8abfaf..10145952 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -68,8 +68,7 @@ impl NamedFile { // Get the name of the file and use it to construct default Content-Type // and Content-Disposition values - let content_type; - let content_disposition; + let (content_type, content_disposition) = { let filename = match path.file_name() { Some(name) => name.to_string_lossy(), @@ -78,12 +77,12 @@ impl NamedFile { "Provided path has no filename")), }; - content_type = guess_mime_type(&path); - let disposition_type = match content_type.type_() { + let ct = guess_mime_type(&path); + let disposition_type = match ct.type_() { mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, _ => DispositionType::Attachment, }; - content_disposition = ContentDisposition { + let cd = ContentDisposition { disposition: disposition_type, parameters: vec![ DispositionParam::Filename( @@ -93,7 +92,8 @@ impl NamedFile { ) ], }; - } + (ct, cd) + }; let file = File::open(&path)?; let md = file.metadata()?; @@ -275,9 +275,8 @@ impl Responder for NamedFile { fn respond_to(self, req: &HttpRequest) -> Result { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); - - resp.set(header::ContentType(self.content_type.clone())); - resp.header("Content-Disposition", format!("{}", &self.content_disposition)); + resp.set(header::ContentType(self.content_type.clone())) + .header("Content-Disposition", format!("{}", &self.content_disposition)); if let Some(current_encoding) = self.encoding { resp.content_encoding(current_encoding); @@ -327,13 +326,13 @@ impl Responder for NamedFile { }; let mut resp = HttpResponse::build(self.status_code); + resp.set(header::ContentType(self.content_type.clone())) + .header("Content-Disposition", format!("{}", &self.content_disposition)); + if let Some(current_encoding) = self.encoding { resp.content_encoding(current_encoding); } - resp.set(header::ContentType(self.content_type.clone())); - resp.header("Content-Disposition", format!("{}", &self.content_disposition)); - resp .if_some(last_modified, |lm, resp| { resp.set(header::LastModified(lm)); From 818d0bc1871ccf3f49996d6fe22d5d034eba76c7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Jun 2018 07:53:46 -0700 Subject: [PATCH 0350/1635] new StreamHandler impl --- src/client/connector.rs | 29 +++++++++++++++++++---------- src/server/srv.rs | 12 +++++++----- src/ws/mod.rs | 10 +++++----- tests/test_ws.rs | 30 ++++++++++++++++++------------ 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 61dda22e..13539b1e 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -5,7 +5,7 @@ use std::{fmt, io, mem, time}; use actix::resolver::{Connect as ResolveConnect, Connector, ConnectorError}; use actix::{ - fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context, + fut, Actor, ActorContext, ActorFuture, ActorResponse, Addr, AsyncContext, Context, ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, SystemService, WrapFuture, }; @@ -198,7 +198,7 @@ pub struct ClientConnector { acq_tx: mpsc::UnboundedSender, acq_rx: Option>, - resolver: Addr, + resolver: Option>, conn_lifetime: Duration, conn_keep_alive: Duration, limit: usize, @@ -216,6 +216,9 @@ impl Actor for ClientConnector { type Context = Context; fn started(&mut self, ctx: &mut Self::Context) { + if self.resolver.is_none() { + self.resolver = Some(Connector::from_registry()) + } self.collect_periodic(ctx); ctx.add_stream(self.acq_rx.take().unwrap()); ctx.spawn(Maintenance); @@ -242,7 +245,7 @@ impl Default for ClientConnector { subscriber: None, acq_tx: tx, acq_rx: Some(rx), - resolver: Connector::from_registry(), + resolver: None, connector: builder.build().unwrap(), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), @@ -266,7 +269,7 @@ impl Default for ClientConnector { subscriber: None, acq_tx: tx, acq_rx: Some(rx), - resolver: Connector::from_registry(), + resolver: None, conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), limit: 100, @@ -333,7 +336,7 @@ impl ClientConnector { subscriber: None, acq_tx: tx, acq_rx: Some(rx), - resolver: Connector::from_registry(), + resolver: None, conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), limit: 100, @@ -395,7 +398,7 @@ impl ClientConnector { /// Use custom resolver actor pub fn resolver(mut self, addr: Addr) -> Self { - self.resolver = addr; + self.resolver = Some(addr); self } @@ -667,6 +670,8 @@ impl Handler for ClientConnector { { ActorResponse::async( self.resolver + .as_ref() + .unwrap() .send( ResolveConnect::host_and_port(&conn.0.host, port) .timeout(conn_timeout), @@ -764,16 +769,19 @@ impl Handler for ClientConnector { } impl StreamHandler for ClientConnector { - fn handle(&mut self, msg: AcquiredConnOperation, _: &mut Context) { + fn handle( + &mut self, msg: Result, ()>, + ctx: &mut Context, + ) { let now = Instant::now(); match msg { - AcquiredConnOperation::Close(conn) => { + Ok(Some(AcquiredConnOperation::Close(conn))) => { self.release_key(&conn.key); self.to_close.push(conn); self.stats.closed += 1; } - AcquiredConnOperation::Release(conn) => { + Ok(Some(AcquiredConnOperation::Release(conn))) => { self.release_key(&conn.key); // check connection lifetime and the return to available pool @@ -784,9 +792,10 @@ impl StreamHandler for ClientConnector { .push_back(Conn(Instant::now(), conn)); } } - AcquiredConnOperation::ReleaseKey(key) => { + Ok(Some(AcquiredConnOperation::ReleaseKey(key))) => { self.release_key(&key); } + _ => ctx.stop(), } // check keep-alive diff --git a/src/server/srv.rs b/src/server/srv.rs index 24874f15..21722c33 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -4,8 +4,8 @@ use std::time::Duration; use std::{io, net, thread}; use actix::{ - fut, msgs, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, - ContextFutureSpawner, Handler, Response, StreamHandler, System, WrapFuture, + fut, msgs, signal, Actor, ActorContext, ActorFuture, Addr, Arbiter, AsyncContext, + Context, ContextFutureSpawner, Handler, Response, StreamHandler, System, WrapFuture, }; use futures::sync::mpsc; @@ -626,10 +626,11 @@ impl Handler for HttpServer { /// Commands from accept threads impl StreamHandler for HttpServer { - fn finished(&mut self, _: &mut Context) {} - fn handle(&mut self, msg: ServerCommand, _: &mut Context) { + fn handle( + &mut self, msg: Result, ()>, ctx: &mut Context, + ) { match msg { - ServerCommand::WorkerDied(idx, socks) => { + Ok(Some(ServerCommand::WorkerDied(idx, socks))) => { let mut found = false; for i in 0..self.workers.len() { if self.workers[i].0 == idx { @@ -675,6 +676,7 @@ impl StreamHandler for HttpServer { self.workers.push((new_idx, addr)); } } + _ => ctx.stop(), } } } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 558ecb51..c68cf300 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -25,12 +25,12 @@ //! //! // Handler for ws::Message messages //! impl StreamHandler for Ws { -//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { +//! fn handle(&mut self, msg: Result, ws::ProtocolError>, ctx: &mut Self::Context) { //! match msg { -//! ws::Message::Ping(msg) => ctx.pong(&msg), -//! ws::Message::Text(text) => ctx.text(text), -//! ws::Message::Binary(bin) => ctx.binary(bin), -//! _ => (), +//! Ok(Some(ws::Message::Ping(msg))) => ctx.pong(&msg), +//! Ok(Some(ws::Message::Text(text))) => ctx.text(text), +//! Ok(Some(ws::Message::Binary(bin))) => ctx.binary(bin), +//! _ => ctx.stop(), //! } //! } //! } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index dd65d4a5..eeeffb7a 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -23,13 +23,16 @@ impl Actor for Ws { } impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + fn handle( + &mut self, msg: Result, ws::ProtocolError>, + ctx: &mut Self::Context, + ) { match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), + Ok(Some(ws::Message::Ping(msg))) => ctx.pong(&msg), + Ok(Some(ws::Message::Text(text))) => ctx.text(text), + Ok(Some(ws::Message::Binary(bin))) => ctx.binary(bin), + Ok(Some(ws::Message::Close(reason))) => ctx.close(reason), + _ => ctx.stop(), } } } @@ -153,13 +156,16 @@ impl Ws2 { } impl StreamHandler for Ws2 { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + fn handle( + &mut self, msg: Result, ws::ProtocolError>, + ctx: &mut Self::Context, + ) { match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), + Ok(Some(ws::Message::Ping(msg))) => ctx.pong(&msg), + Ok(Some(ws::Message::Text(text))) => ctx.text(text), + Ok(Some(ws::Message::Binary(bin))) => ctx.binary(bin), + Ok(Some(ws::Message::Close(reason))) => ctx.close(reason), + _ => ctx.stop(), } } } From 87a822e093388efb4ef7db1081c51c8e0ca9c79c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Jun 2018 10:14:13 -0700 Subject: [PATCH 0351/1635] fix deprecated warnings --- src/client/connector.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 13539b1e..4408818c 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -3,7 +3,7 @@ use std::net::Shutdown; use std::time::{Duration, Instant}; use std::{fmt, io, mem, time}; -use actix::resolver::{Connect as ResolveConnect, Connector, ConnectorError}; +use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; use actix::{ fut, Actor, ActorContext, ActorFuture, ActorResponse, Addr, AsyncContext, Context, ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, @@ -139,7 +139,7 @@ pub enum ClientConnectorError { /// Connection error #[fail(display = "{}", _0)] - Connector(#[cause] ConnectorError), + Connector(#[cause] ResolverError), /// Connection took too long #[fail(display = "Timeout while establishing connection")] @@ -154,10 +154,10 @@ pub enum ClientConnectorError { IoError(#[cause] io::Error), } -impl From for ClientConnectorError { - fn from(err: ConnectorError) -> ClientConnectorError { +impl From for ClientConnectorError { + fn from(err: ResolverError) -> ClientConnectorError { match err { - ConnectorError::Timeout => ClientConnectorError::Timeout, + ResolverError::Timeout => ClientConnectorError::Timeout, _ => ClientConnectorError::Connector(err), } } @@ -198,7 +198,7 @@ pub struct ClientConnector { acq_tx: mpsc::UnboundedSender, acq_rx: Option>, - resolver: Option>, + resolver: Option>, conn_lifetime: Duration, conn_keep_alive: Duration, limit: usize, @@ -217,7 +217,7 @@ impl Actor for ClientConnector { fn started(&mut self, ctx: &mut Self::Context) { if self.resolver.is_none() { - self.resolver = Some(Connector::from_registry()) + self.resolver = Some(Resolver::from_registry()) } self.collect_periodic(ctx); ctx.add_stream(self.acq_rx.take().unwrap()); @@ -397,7 +397,7 @@ impl ClientConnector { } /// Use custom resolver actor - pub fn resolver(mut self, addr: Addr) -> Self { + pub fn resolver(mut self, addr: Addr) -> Self { self.resolver = Some(addr); self } @@ -860,7 +860,7 @@ impl fut::ActorFuture for Maintenance { let conn = AcquiredConn(key.clone(), Some(act.acq_tx.clone())); fut::WrapFuture::::actfuture( - Connector::from_registry().send( + Resolver::from_registry().send( ResolveConnect::host_and_port(&conn.0.host, conn.0.port) .timeout(waiter.conn_timeout), ), From 9dd66dfc223b284a9da981c64eea6e1bca1d3a57 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Jun 2018 10:24:34 -0700 Subject: [PATCH 0352/1635] better name for error --- MIGRATION.md | 3 ++- src/client/connector.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 91f8a800..628f0590 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -44,7 +44,8 @@ * Removed deprecated `HttpServer::threads()`, use [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. - +* Renamed `client::ClientConnectorError::Connector` to + `client::ClientConnectorError::Resolver` ## 0.6 diff --git a/src/client/connector.rs b/src/client/connector.rs index 4408818c..5b844486 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -137,9 +137,9 @@ pub enum ClientConnectorError { #[fail(display = "{}", _0)] SslError(#[cause] TlsError), - /// Connection error + /// Resolver error #[fail(display = "{}", _0)] - Connector(#[cause] ResolverError), + Resolver(#[cause] ResolverError), /// Connection took too long #[fail(display = "Timeout while establishing connection")] @@ -158,7 +158,7 @@ impl From for ClientConnectorError { fn from(err: ResolverError) -> ClientConnectorError { match err { ResolverError::Timeout => ClientConnectorError::Timeout, - _ => ClientConnectorError::Connector(err), + _ => ClientConnectorError::Resolver(err), } } } From 9afc3b6737c2e9e524ca6697cf3905a258251290 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Jun 2018 10:31:19 -0700 Subject: [PATCH 0353/1635] api docs link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 158df61b..af66baea 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [User Guide](https://actix.rs/docs/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (Releases)](https://docs.rs/actix-web/) +* [API Documentation (Releases)](https://docs.rs/actix-web/0.6.11/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) * Minimum supported Rust version: 1.26 or later From 0d54b6f38e271e65401bb73eed75c13dcc6ec365 Mon Sep 17 00:00:00 2001 From: David McNeil Date: Mon, 11 Jun 2018 05:05:41 -0600 Subject: [PATCH 0354/1635] Implement Responder for Option #294 (#297) --- src/application.rs | 18 ++++++++++++++++++ src/handler.rs | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/application.rs b/src/application.rs index e16f85cd..b9fa3e32 100644 --- a/src/application.rs +++ b/src/application.rs @@ -737,6 +737,7 @@ impl Iterator for App { #[cfg(test)] mod tests { use super::*; + use body::{Body, Binary}; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -957,4 +958,21 @@ mod tests { let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); } + + #[test] + fn test_option_responder() { + let mut app = App::new() + .resource("/none", |r| r.f(|_| -> Option<&'static str> { None })) + .resource("/some", |r| r.f(|_| Some("some"))) + .finish(); + + let req = TestRequest::with_uri("/none").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/some").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); + } } diff --git a/src/handler.rs b/src/handler.rs index 8b550059..d330e071 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -5,6 +5,7 @@ use futures::future::{err, ok, Future}; use futures::{Async, Poll}; use error::Error; +use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -132,6 +133,26 @@ where } } +impl Responder for Option +where + T: Responder, +{ + type Item = AsyncResult; + type Error = Error; + + fn respond_to( + self, req: &HttpRequest, + ) -> Result, Error> { + match self { + Some(t) => match t.respond_to(req) { + Ok(val) => Ok(val.into()), + Err(err) => Err(err.into()), + }, + None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()), + } + } +} + /// Convenience trait that converts `Future` object to a `Boxed` future /// /// For example loading json from request's body is async operation. From ef420a8bdf693cd0b45f9e0056f0ddcab99434d2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Jun 2018 12:21:09 -0700 Subject: [PATCH 0355/1635] fix docs.rs --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 5b142030..c3b9fc7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ //! #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error + extern_prelude, ))] #![cfg_attr( feature = "cargo-clippy", From a0344eebeb38b31746327575c754fa84ed563a9a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Jun 2018 18:52:54 -0700 Subject: [PATCH 0356/1635] InternalError can trigger memory unsafety #301 --- src/error.rs | 30 +++++++++++++----------------- src/httpresponse.rs | 13 ++++++++++++- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/error.rs b/src/error.rs index 4b2dae72..9a019568 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,8 @@ //! Error and Result module -use std::cell::RefCell; use std::io::Error as IoError; use std::str::Utf8Error; use std::string::FromUtf8Error; +use std::sync::Mutex; use std::{fmt, io, result}; use actix::MailboxError; @@ -24,7 +24,7 @@ pub use cookie::ParseError as CookieParseError; use handler::Responder; use httprequest::HttpRequest; -use httpresponse::HttpResponse; +use httpresponse::{HttpResponse, InnerHttpResponse}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -80,7 +80,8 @@ impl Error { } } - /// Attempts to downcast this `Error` to a particular `Fail` type by reference. + /// Attempts to downcast this `Error` to a particular `Fail` type by + /// reference. /// /// If the underlying error is not of type `T`, this will return `None`. pub fn downcast_ref(&self) -> Option<&T> { @@ -116,8 +117,8 @@ impl Error { /// Helper trait to downcast a response error into a fail. /// -/// This is currently not exposed because it's unclear if this is the best way to -/// achieve the downcasting on `Error` for which this is needed. +/// This is currently not exposed because it's unclear if this is the best way +/// to achieve the downcasting on `Error` for which this is needed. #[doc(hidden)] pub trait InternalResponseErrorAsFail { #[doc(hidden)] @@ -190,11 +191,9 @@ impl From for Error { } /// Compatibility for `failure::Error` -impl ResponseError for failure::Compat -where - T: fmt::Display + fmt::Debug + Sync + Send + 'static, -{ -} +impl ResponseError for failure::Compat where + T: fmt::Display + fmt::Debug + Sync + Send + 'static +{} impl From for Error { fn from(err: failure::Error) -> Error { @@ -657,12 +656,9 @@ pub struct InternalError { backtrace: Backtrace, } -unsafe impl Sync for InternalError {} -unsafe impl Send for InternalError {} - enum InternalErrorType { Status(StatusCode), - Response(RefCell>), + Response(Mutex>>), } impl InternalError { @@ -679,7 +675,7 @@ impl InternalError { pub fn from_response(cause: T, response: HttpResponse) -> Self { InternalError { cause, - status: InternalErrorType::Response(RefCell::new(Some(response))), + status: InternalErrorType::Response(Mutex::new(Some(response.into_inner()))), backtrace: Backtrace::new(), } } @@ -720,8 +716,8 @@ where match self.status { InternalErrorType::Status(st) => HttpResponse::new(st), InternalErrorType::Response(ref resp) => { - if let Some(resp) = resp.borrow_mut().take() { - resp + if let Some(resp) = resp.lock().unwrap().take() { + HttpResponse::from_inner(resp) } else { HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index ce036027..1a62232b 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -241,6 +241,14 @@ impl HttpResponse { pub fn set_write_buffer_capacity(&mut self, cap: usize) { self.get_mut().write_capacity = cap; } + + pub(crate) fn into_inner(mut self) -> Box { + self.0.take().unwrap() + } + + pub(crate) fn from_inner(inner: Box) -> HttpResponse { + HttpResponse(Some(inner), HttpResponsePool::pool()) + } } impl fmt::Debug for HttpResponse { @@ -797,7 +805,7 @@ impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { } #[derive(Debug)] -struct InnerHttpResponse { +pub(crate) struct InnerHttpResponse { version: Option, headers: HeaderMap, status: StatusCode, @@ -811,6 +819,9 @@ struct InnerHttpResponse { error: Option, } +unsafe impl Sync for InnerHttpResponse {} +unsafe impl Send for InnerHttpResponse {} + impl InnerHttpResponse { #[inline] fn new(status: StatusCode, body: Body) -> InnerHttpResponse { From 9b012b33047999294627180c6744da41714b9408 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Jun 2018 19:44:11 -0700 Subject: [PATCH 0357/1635] do not allow stream or actor responses for internal error #301 --- src/error.rs | 17 +++++++++++++++-- src/httpresponse.rs | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 9a019568..f3327c2b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,7 @@ use std::io::Error as IoError; use std::str::Utf8Error; use std::string::FromUtf8Error; use std::sync::Mutex; -use std::{fmt, io, result}; +use std::{fmt, io, mem, result}; use actix::MailboxError; use cookie; @@ -22,6 +22,7 @@ pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; +use body::Body; use handler::Responder; use httprequest::HttpRequest; use httpresponse::{HttpResponse, InnerHttpResponse}; @@ -673,9 +674,21 @@ impl InternalError { /// Create `InternalError` with predefined `HttpResponse`. pub fn from_response(cause: T, response: HttpResponse) -> Self { + let mut resp = response.into_inner(); + let body = mem::replace(&mut resp.body, Body::Empty); + match body { + Body::Empty => (), + Body::Binary(mut bin) => { + resp.body = Body::Binary(bin.take().into()); + } + Body::Streaming(_) | Body::Actor(_) => { + error!("Streaming or Actor body is not support by error response"); + } + } + InternalError { cause, - status: InternalErrorType::Response(Mutex::new(Some(response.into_inner()))), + status: InternalErrorType::Response(Mutex::new(Some(resp))), backtrace: Backtrace::new(), } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 1a62232b..f42be2b9 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -810,7 +810,7 @@ pub(crate) struct InnerHttpResponse { headers: HeaderMap, status: StatusCode, reason: Option<&'static str>, - body: Body, + pub(crate) body: Body, chunked: Option, encoding: Option, connection_type: Option, From 48f77578ea2a6e53cb469cef89f849ded9578458 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Mon, 11 Jun 2018 21:54:54 -0700 Subject: [PATCH 0358/1635] fix url in example --- src/ws/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ws/client.rs b/src/ws/client.rs index d1f1402a..6cb66173 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -118,7 +118,7 @@ impl From for ClientError { /// /// Example of `WebSocket` client usage is available in /// [websocket example]( -/// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24) +/// https://github.com/actix/examples/blob/master/websocket/src/client.rs#L24) pub struct Client { request: ClientRequestBuilder, err: Option, From 4d69e6d0b49608e92f1fe876117bd3ad172ab922 Mon Sep 17 00:00:00 2001 From: axon-q Date: Tue, 12 Jun 2018 13:47:49 +0000 Subject: [PATCH 0359/1635] fs: minor cleanups to content_disposition --- src/fs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 10145952..35c78b73 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -276,7 +276,7 @@ impl Responder for NamedFile { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) - .header("Content-Disposition", format!("{}", &self.content_disposition)); + .header(header::CONTENT_DISPOSITION, self.content_disposition.to_string()); if let Some(current_encoding) = self.encoding { resp.content_encoding(current_encoding); @@ -327,7 +327,7 @@ impl Responder for NamedFile { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) - .header("Content-Disposition", format!("{}", &self.content_disposition)); + .header(header::CONTENT_DISPOSITION, self.content_disposition.to_string()); if let Some(current_encoding) = self.encoding { resp.content_encoding(current_encoding); From e414a52b5102e8d364510393582af6a86f345d05 Mon Sep 17 00:00:00 2001 From: axon-q Date: Tue, 12 Jun 2018 13:48:23 +0000 Subject: [PATCH 0360/1635] content_disposition: remove unnecessary allocations --- src/header/common/content_disposition.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index 0edebfed..ff04ef56 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -193,8 +193,9 @@ impl fmt::Display for ContentDisposition { } } if use_simple_format { + use std::str; try!(write!(f, "; filename=\"{}\"", - match String::from_utf8(bytes.clone()) { + match str::from_utf8(bytes) { Ok(s) => s, Err(_) => return Err(fmt::Error), })); From d8e1fd102de6c1014f9dad688344da4f9d0b6009 Mon Sep 17 00:00:00 2001 From: axon-q Date: Tue, 12 Jun 2018 13:49:07 +0000 Subject: [PATCH 0361/1635] add cookie methods to HttpResponse --- CHANGES.md | 5 +++ src/httpresponse.rs | 104 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 29046f8f..f7a75354 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Added +* Add methods to `HttpResponse` to retrieve, add, and delete cookies + * Add `.set_content_type()` and `.set_content_disposition()` methods to `fs::NamedFile` to allow overriding the values inferred by default @@ -22,6 +24,9 @@ * Min rustc version is 1.26 +* `HttpResponse::into_builder()` now moves cookies into the builder + instead of dropping them + * Use tokio instead of tokio-core * Use `&mut self` instead of `&self` for Middleware trait diff --git a/src/httpresponse.rs b/src/httpresponse.rs index f42be2b9..40a8cc61 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -97,14 +97,25 @@ impl HttpResponse { /// Convert `HttpResponse` to a `HttpResponseBuilder` #[inline] pub fn into_builder(mut self) -> HttpResponseBuilder { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + for c in self.cookies() { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } + } + let response = self.0.take(); let pool = Some(Rc::clone(&self.1)); - HttpResponseBuilder { response, pool, err: None, - cookies: None, // TODO: convert set-cookie headers + cookies: jar, } } @@ -132,6 +143,49 @@ impl HttpResponse { &mut self.get_mut().headers } + /// Get an iterator for the cookies set by this response + #[inline] + pub fn cookies(&self) -> CookieIter { + CookieIter { + iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter() + } + } + + /// Add a cookie to this response + #[inline] + pub fn add_cookie(&mut self, cookie: Cookie) -> Result<(), HttpError> { + let h = &mut self.get_mut().headers; + HeaderValue::from_str(&cookie.to_string()) + .map(|c| { h.append(header::SET_COOKIE, c); }) + .map_err(|e| e.into()) + } + + /// Remove all cookies with the given name from this response. Returns + /// the number of cookies removed. + #[inline] + pub fn del_cookie(&mut self, name: &str) -> usize { + let h = &mut self.get_mut().headers; + let vals: Vec = h.get_all(header::SET_COOKIE) + .iter() + .map(|v| v.to_owned()) + .collect(); + h.remove(header::SET_COOKIE); + + let mut count: usize = 0; + for v in vals { + if let Ok(s) = v.to_str() { + if let Ok(c) = Cookie::parse(s) { + if c.name() == name { + count += 1; + continue; + } + } + } + h.append(header::SET_COOKIE, v); + } + return count; + } + /// Get the response status code #[inline] pub fn status(&self) -> StatusCode { @@ -269,6 +323,24 @@ impl fmt::Debug for HttpResponse { } } +pub struct CookieIter<'a> { + iter: header::ValueIter<'a, HeaderValue>, +} + +impl<'a> Iterator for CookieIter<'a> { + type Item = Cookie<'a>; + + #[inline] + fn next(&mut self) -> Option> { + for v in self.iter.by_ref() { + if let Some(c) = (|| Cookie::parse(v.to_str().ok()?).ok())() { + return Some(c); + } + } + None + } +} + /// An HTTP response builder /// /// This type can be used to construct an instance of `HttpResponse` through a @@ -984,6 +1056,27 @@ mod tests { ); } + + #[test] + fn test_update_response_cookies() { + let mut r = HttpResponse::Ok() + .cookie(http::Cookie::new("original", "val100")) + .finish(); + + r.add_cookie(http::Cookie::new("cookie2", "val200")).unwrap(); + r.add_cookie(http::Cookie::new("cookie2", "val250")).unwrap(); + r.add_cookie(http::Cookie::new("cookie3", "val300")).unwrap(); + + assert_eq!(r.cookies().count(), 4); + r.del_cookie("cookie2"); + + let mut iter = r.cookies(); + let v = iter.next().unwrap(); + assert_eq!((v.name(), v.value()), ("original", "val100")); + let v = iter.next().unwrap(); + assert_eq!((v.name(), v.value()), ("cookie3", "val300")); + } + #[test] fn test_basic_builder() { let resp = HttpResponse::Ok() @@ -1191,11 +1284,16 @@ mod tests { #[test] fn test_into_builder() { - let resp: HttpResponse = "test".into(); + let mut resp: HttpResponse = "test".into(); assert_eq!(resp.status(), StatusCode::OK); + resp.add_cookie(http::Cookie::new("cookie1", "val100")).unwrap(); + let mut builder = resp.into_builder(); let resp = builder.status(StatusCode::BAD_REQUEST).finish(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let cookie = resp.cookies().next().unwrap(); + assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100")); } } From d4d3add17d18e19137ec3058962c3142d6bed076 Mon Sep 17 00:00:00 2001 From: Ozgur Akkurt Date: Tue, 12 Jun 2018 19:30:00 +0300 Subject: [PATCH 0362/1635] add ClientRequestBuilder::form() --- src/client/request.rs | 19 ++++++++++++++++++ src/error.rs | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/client/request.rs b/src/client/request.rs index bb338482..bc8feb3e 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -10,6 +10,7 @@ use futures::Stream; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use serde::Serialize; use serde_json; +use serde_urlencoded; use url::Url; use super::body::ClientBody; @@ -658,6 +659,24 @@ impl ClientRequestBuilder { self.body(body) } + + /// Set a urlencoded body and generate `ClientRequest` + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn form(&mut self, value: T) -> Result { + let body = serde_urlencoded::to_string(&value)?; + + let contains = if let Some(parts) = parts(&mut self.request, &self.err) { + parts.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; + if !contains { + self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); + } + + self.body(body) + } /// Set a streaming body and generate `ClientRequest`. /// diff --git a/src/error.rs b/src/error.rs index f3327c2b..39e66a0d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,6 +16,7 @@ use http_range::HttpRangeParseError; use httparse; use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; +use serde_urlencoded::ser::Error as FormError; use tokio_timer::Error as TimerError; pub use url::ParseError as UrlParseError; @@ -205,6 +206,9 @@ impl From for Error { /// `InternalServerError` for `JsonError` impl ResponseError for JsonError {} +/// `InternalServerError` for `FormError` +impl ResponseError for FormError {} + /// `InternalServerError` for `TimerError` impl ResponseError for TimerError {} @@ -586,6 +590,47 @@ impl From for JsonPayloadError { } } +/// A set of errors that can occur during parsing json payloads +#[derive(Fail, Debug)] +pub enum FormPayloadError { + /// Payload size is bigger than allowed. (default: 256kB) + #[fail(display = "Form payload size is bigger than allowed. (default: 256kB)")] + Overflow, + /// Content type error + #[fail(display = "Content type error")] + ContentType, + /// Deserialize error + #[fail(display = "Form deserialize error: {}", _0)] + Deserialize(#[cause] FormError), + /// Payload error + #[fail(display = "Error that occur during reading payload: {}", _0)] + Payload(#[cause] PayloadError), +} + +/// Return `BadRequest` for `UrlencodedError` +impl ResponseError for FormPayloadError { + fn error_response(&self) -> HttpResponse { + match *self { + FormPayloadError::Overflow => { + HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) + } + _ => HttpResponse::new(StatusCode::BAD_REQUEST), + } + } +} + +impl From for FormPayloadError { + fn from(err: PayloadError) -> FormPayloadError { + FormPayloadError::Payload(err) + } +} + +impl From for FormPayloadError { + fn from(err: FormError) -> FormPayloadError { + FormPayloadError::Deserialize(err) + } +} + /// Errors which can occur when attempting to interpret a segment string as a /// valid path segment. #[derive(Fail, Debug, PartialEq)] From 8af082d8732816a3b3e8711a45a65038cef0abdc Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Tue, 12 Jun 2018 20:26:09 +0300 Subject: [PATCH 0363/1635] remove FormPayloadError --- src/error.rs | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/src/error.rs b/src/error.rs index 39e66a0d..bbafb1c4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -590,47 +590,6 @@ impl From for JsonPayloadError { } } -/// A set of errors that can occur during parsing json payloads -#[derive(Fail, Debug)] -pub enum FormPayloadError { - /// Payload size is bigger than allowed. (default: 256kB) - #[fail(display = "Form payload size is bigger than allowed. (default: 256kB)")] - Overflow, - /// Content type error - #[fail(display = "Content type error")] - ContentType, - /// Deserialize error - #[fail(display = "Form deserialize error: {}", _0)] - Deserialize(#[cause] FormError), - /// Payload error - #[fail(display = "Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), -} - -/// Return `BadRequest` for `UrlencodedError` -impl ResponseError for FormPayloadError { - fn error_response(&self) -> HttpResponse { - match *self { - FormPayloadError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), - } - } -} - -impl From for FormPayloadError { - fn from(err: PayloadError) -> FormPayloadError { - FormPayloadError::Payload(err) - } -} - -impl From for FormPayloadError { - fn from(err: FormError) -> FormPayloadError { - FormPayloadError::Deserialize(err) - } -} - /// Errors which can occur when attempting to interpret a segment string as a /// valid path segment. #[derive(Fail, Debug, PartialEq)] From 9cc7651c222fd61df1e6550594d24e54e494f542 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Tue, 12 Jun 2018 20:32:16 +0300 Subject: [PATCH 0364/1635] add change to CHANGES.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f7a75354..e9970d8f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Added +* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. + * Add methods to `HttpResponse` to retrieve, add, and delete cookies * Add `.set_content_type()` and `.set_content_disposition()` methods From ffca4164639e80c947c190f679dda4ef216d7c33 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Tue, 12 Jun 2018 22:16:20 +0300 Subject: [PATCH 0365/1635] add test for ClientRequestBuilder::form() --- tests/test_handlers.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 5ece53ee..a031b5cc 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -96,6 +96,35 @@ fn test_async_extractor_async() { assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}")); } +#[derive(Deserialize)] +struct FormData { + username: String, +} + +#[test] +fn test_form_extractor() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with(|form: Form| { + Ok(format!("{}", form.username)) + }) + }); + }); + + // client request + let request = srv + .post() + .uri(srv.url("/test1/index.html")) + .form(FormData{username: "test".into_string()}) + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"test")); +} + #[test] fn test_path_and_query_extractor() { let mut srv = test::TestServer::new(|app| { From 94283a73c2a524904faaca2b2c1ebe8118ac7755 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Tue, 12 Jun 2018 22:31:33 +0300 Subject: [PATCH 0366/1635] make into_string, to_string --- tests/test_handlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index a031b5cc..d59c7130 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -115,7 +115,7 @@ fn test_form_extractor() { let request = srv .post() .uri(srv.url("/test1/index.html")) - .form(FormData{username: "test".into_string()}) + .form(FormData{username: "test".to_string()}) .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); From e6bbda0efcbc3787f0407816d01c4647beb6bdd2 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Tue, 12 Jun 2018 22:42:15 +0300 Subject: [PATCH 0367/1635] add serialize --- tests/test_handlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index d59c7130..7bef3900 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -96,7 +96,7 @@ fn test_async_extractor_async() { assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}")); } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] struct FormData { username: String, } From ed7cbaa772fdbb0fcd3b89d695a576db671ee897 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Tue, 12 Jun 2018 23:04:54 +0300 Subject: [PATCH 0368/1635] fix form_extractor test --- tests/test_handlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 7bef3900..8f1ee943 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -106,7 +106,7 @@ fn test_form_extractor() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { r.route().with(|form: Form| { - Ok(format!("{}", form.username)) + format!("{}", form.username) }) }); }); From 748ff389e465fc17c77ed0746f7556651f6b0306 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Wed, 13 Jun 2018 00:47:47 +0300 Subject: [PATCH 0369/1635] Allow to override Form extractor error --- src/extractor.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 175e948b..0cdcb3af 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::{fmt, str}; +use std::rc::Rc; use bytes::Bytes; use encoding::all::UTF_8; @@ -11,7 +12,7 @@ use serde::de::{self, DeserializeOwned}; use serde_urlencoded; use de::PathDeserializer; -use error::{Error, ErrorBadRequest, ErrorNotFound}; +use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError}; use handler::{AsyncResult, FromRequest}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; @@ -261,15 +262,17 @@ where T: DeserializeOwned + 'static, S: 'static, { - type Config = FormConfig; + type Config = FormConfig; type Result = Box>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + let req = req.clone(); + let err = Rc::clone(&cfg.ehandler); Box::new( UrlEncoded::new(req.clone()) .limit(cfg.limit) - .from_err() + .map_err(move |e| (*err)(e, req)) .map(Form), ) } @@ -314,21 +317,34 @@ impl fmt::Display for Form { /// ); /// } /// ``` -pub struct FormConfig { +pub struct FormConfig { limit: usize, + ehandler: Rc) -> Error>, } -impl FormConfig { +impl FormConfig { /// Change max size of payload. By default max size is 256Kb pub fn limit(&mut self, limit: usize) -> &mut Self { self.limit = limit; self } + + /// Set custom error handler + pub fn error_handler(&mut self, f: F) -> &mut Self + where + F: Fn(UrlencodedError, HttpRequest) -> Error + 'static, + { + self.ehandler = Rc::new(f); + self + } } -impl Default for FormConfig { +impl Default for FormConfig { fn default() -> Self { - FormConfig { limit: 262_144 } + FormConfig { + limit: 262_144, + ehandler: Rc::new(|e, _| e.into()), + } } } From 99092fdf06b5fbcaa4e78272d0f714319a26ab62 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Jun 2018 14:47:45 -0700 Subject: [PATCH 0370/1635] http/2 end-of-frame is not set if body is empty bytes #307 --- CHANGES.md | 7 +++++++ src/server/h2writer.rs | 46 ++++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 29046f8f..b2cb6456 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,6 +34,13 @@ * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. +## [0.6.13] - 2018-06-11 + +* http/2 end-of-frame is not set if body is empty bytes #307 + +* InternalError can trigger memory unsafety #301 + + ## [0.6.12] - 2018-06-08 ### Added diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 78a1ce18..f019f75b 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -89,9 +89,6 @@ impl Writer for H2Writer { self.flags.insert(Flags::STARTED); self.encoder = ContentEncoder::for_server(self.buffer.get_mut(), req, msg, encoding); - if let Body::Empty = *msg.body() { - self.flags.insert(Flags::EOF); - } // http2 specific msg.headers_mut().remove(CONNECTION); @@ -108,15 +105,22 @@ impl Writer for H2Writer { 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(), - ); + if bytes.is_empty() { + msg.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + self.flags.insert(Flags::EOF); + } else { + 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 => { + self.flags.insert(Flags::EOF); msg.headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); } @@ -141,14 +145,18 @@ impl Writer for H2Writer { trace!("Response: {:?}", msg); 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 { - self.flags.insert(Flags::RESERVED); - stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); + if bytes.is_empty() { + Ok(WriterState::Done) + } else { + 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 { + self.flags.insert(Flags::RESERVED); + stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); + } + Ok(WriterState::Pause) } - Ok(WriterState::Pause) } else { msg.replace_body(body); self.buffer_capacity = msg.write_buffer_capacity(); @@ -177,10 +185,8 @@ impl Writer for H2Writer { } fn write_eof(&mut self) -> io::Result { - self.encoder.write_eof()?; - self.flags.insert(Flags::EOF); - if !self.encoder.is_eof() { + if !self.encoder.write_eof()? { Err(io::Error::new( io::ErrorKind::Other, "Last payload item, but eof is not reached", From 0a080d9fb4ff7659606ca41f49f00370f5f1f3aa Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Wed, 13 Jun 2018 01:33:28 +0300 Subject: [PATCH 0371/1635] add test for form extractor --- CHANGES.md | 2 ++ tests/test_handlers.rs | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0bb37b94..5265d9d7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. +* Add method to configure custom error handler to Form extractor. + * Add methods to `HttpResponse` to retrieve, add, and delete cookies * Add `.set_content_type()` and `.set_content_disposition()` methods diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 8f1ee943..e6738e8a 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -125,6 +125,30 @@ fn test_form_extractor() { assert_eq!(bytes, Bytes::from_static(b"test")); } +#[test] +fn test_form_extractor2() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with(|form: Form| { + format!("{}", form.username) + }).error_handler(|err, res| { + error::InternalError::from_response( + err, HttpResponse::Conflict().finish()).into() + }); + }); + }); + + // client request + let request = srv + .post() + .uri(srv.url("/test1/index.html")) + .header("content-type", "application/x-www-form-urlencoded") + .body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq") + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_client_error()); +} + #[test] fn test_path_and_query_extractor() { let mut srv = test::TestServer::new(|app| { From 6c765739d08c4e414369cc220456a70a7f0c6fb7 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Wed, 13 Jun 2018 20:43:03 +0300 Subject: [PATCH 0372/1635] add HttpMessage::readlines() --- src/httpmessage.rs | 97 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index a9d68d3a..a8ae50ed 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -3,7 +3,7 @@ use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::types::{DecoderTrap, Encoding}; use encoding::EncodingRef; -use futures::{Future, Poll, Stream}; +use futures::{Future, Poll, Stream, Async}; use http::{header, HeaderMap}; use http_range::HttpRange; use mime::Mime; @@ -260,6 +260,101 @@ pub trait HttpMessage { let boundary = Multipart::boundary(self.headers()); Multipart::new(boundary, self) } + + /// Return stream of lines. + fn readlines(self) -> Readlines + where + Self: Stream + Sized, + { + Readlines::new(self) + } +} + +/// Stream to read request line by line. +pub struct Readlines +where + T: HttpMessage + Stream + 'static, +{ + req: T, + buff: Vec, + limit: usize, +} + +impl Readlines +where + T: HttpMessage + Stream + 'static, +{ + /// Create a new stream to read request line by line. + fn new(req: T) -> Self { + Readlines { + req, + buff: Vec::with_capacity(256), + limit: 262_144, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Stream for Readlines +where + T: HttpMessage + Stream + 'static, +{ + type Item = String; + type Error = ReadlinesError; + + fn poll(&mut self) -> Poll, Self::Error> { + match self.req.poll() { + Ok(Async::Ready(Some(bytes))) => { + for b in bytes.iter() { + if *b == '\n' as u8 { + self.buff.push(*b); + let line = str::from_utf8(&*self.buff)?.to_owned(); + self.buff.clear(); + return Ok(Async::Ready(Some(line))); + } else { + self.buff.push(*b); + } + if self.limit < self.buff.len() { + return Err(ReadlinesError::LimitOverflow); + } + } + Ok(Async::NotReady) + }, + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + if self.buff.len() == 0 { + return Ok(Async::Ready(None)); + } + let line = str::from_utf8(&*self.buff)?.to_owned(); + self.buff.clear(); + return Ok(Async::Ready(Some(line))) + }, + Err(e) => Err(ReadlinesError::from(e)), + } + } +} + +pub enum ReadlinesError { + EncodingError, + PayloadError(PayloadError), + LimitOverflow, +} + +impl From for ReadlinesError { + fn from(err: PayloadError) -> Self { + ReadlinesError::PayloadError(err) + } +} + +impl From for ReadlinesError { + fn from(_: str::Utf8Error) -> Self { + ReadlinesError::EncodingError + } } /// Future that resolves to a complete http message body. From 6d95e34552e48b79aa6319a1ee71a8208ac985b9 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Wed, 13 Jun 2018 20:45:31 +0300 Subject: [PATCH 0373/1635] add HttpMessage::readlines() --- src/httpmessage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index a8ae50ed..7f6b50c6 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -288,7 +288,7 @@ where fn new(req: T) -> Self { Readlines { req, - buff: Vec::with_capacity(256), + buff: Vec::with_capacity(262_144), limit: 262_144, } } From f8854f951c27d9266fdabf857d55d77a7581db6a Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Wed, 13 Jun 2018 20:31:20 +0100 Subject: [PATCH 0374/1635] remove duplication of `App::with_state` in `App::new` --- src/application.rs | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/application.rs b/src/application.rs index b9fa3e32..5555ee39 100644 --- a/src/application.rs +++ b/src/application.rs @@ -192,20 +192,7 @@ impl App<()> { /// Create application with empty state. Application can /// be configured with a builder-like pattern. pub fn new() -> App<()> { - App { - parts: Some(ApplicationParts { - state: (), - prefix: "/".to_owned(), - settings: ServerSettings::default(), - default: ResourceHandler::default_not_found(), - resources: Vec::new(), - handlers: Vec::new(), - external: HashMap::new(), - encoding: ContentEncoding::Auto, - filters: Vec::new(), - middlewares: Vec::new(), - }), - } + App::with_state(()) } } @@ -737,7 +724,7 @@ impl Iterator for App { #[cfg(test)] mod tests { use super::*; - use body::{Body, Binary}; + use body::{Binary, Body}; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -811,7 +798,9 @@ mod tests { #[test] fn test_handler() { - let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); + let mut app = App::new() + .handler("/test", |_| HttpResponse::Ok()) + .finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -836,7 +825,9 @@ mod tests { #[test] fn test_handler2() { - let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); + let mut app = App::new() + .handler("test", |_| HttpResponse::Ok()) + .finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -890,21 +881,29 @@ mod tests { #[test] fn test_route() { let mut app = App::new() - .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) + .route("/test", Method::GET, |_: HttpRequest| { + HttpResponse::Ok() + }) .route("/test", Method::POST, |_: HttpRequest| { HttpResponse::Created() }) .finish(); - let req = TestRequest::with_uri("/test").method(Method::GET).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test").method(Method::POST).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::HEAD) + .finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -973,6 +972,9 @@ mod tests { let req = TestRequest::with_uri("/some").finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); + assert_eq!( + resp.as_msg().body(), + &Body::Binary(Binary::Slice(b"some")) + ); } } From ad9aacf5213440a11f5a20021586729c4b31d8a0 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Wed, 13 Jun 2018 22:41:35 +0300 Subject: [PATCH 0375/1635] change poll method of Readlines --- src/httpmessage.rs | 96 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 17 deletions(-) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 7f6b50c6..91ea09f6 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -13,6 +13,7 @@ use std::str; use error::{ ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError, + Error, ErrorBadRequest }; use header::Header; use json::JsonBody; @@ -276,7 +277,7 @@ where T: HttpMessage + Stream + 'static, { req: T, - buff: Vec, + buff: BytesMut, limit: usize, } @@ -288,12 +289,12 @@ where fn new(req: T) -> Self { Readlines { req, - buff: Vec::with_capacity(262_144), + buff: BytesMut::with_capacity(262_144), limit: 262_144, } } - /// Change max size of payload. By default max size is 256Kb + /// Change max line size. By default max size is 256Kb pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self @@ -308,21 +309,63 @@ where type Error = ReadlinesError; fn poll(&mut self) -> Poll, Self::Error> { + let encoding = self.req.encoding()?; + // check if there is a newline in the buffer + let mut found: Option = None; + for (ind, b) in self.buff.iter().enumerate() { + if *b == '\n' as u8 { + found = Some(ind); + break; + } + } + if let Some(ind) = found { + // check if line is longer than limit + if ind+1 > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&self.buff.split_to(ind+1)) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned() + } else { + encoding + .decode(&self.buff.split_to(ind+1), DecoderTrap::Strict) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + }; + return Ok(Async::Ready(Some(line))); + } + // poll req for more bytes match self.req.poll() { - Ok(Async::Ready(Some(bytes))) => { - for b in bytes.iter() { + Ok(Async::Ready(Some(mut bytes))) => { + // check if there is a newline in bytes + let mut found: Option = None; + for (ind, b) in bytes.iter().enumerate() { if *b == '\n' as u8 { - self.buff.push(*b); - let line = str::from_utf8(&*self.buff)?.to_owned(); - self.buff.clear(); - return Ok(Async::Ready(Some(line))); - } else { - self.buff.push(*b); - } - if self.limit < self.buff.len() { - return Err(ReadlinesError::LimitOverflow); + found = Some(ind); + break; } } + if let Some(ind) = found { + // check if line is longer than limit + if ind+1 > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&bytes.split_to(ind+1)) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned() + } else { + encoding + .decode(&bytes.split_to(ind+1), DecoderTrap::Strict) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + }; + // extend buffer with rest of the bytes; + self.buff.extend_from_slice(&bytes); + return Ok(Async::Ready(Some(line))); + } + self.buff.extend_from_slice(&bytes); Ok(Async::NotReady) }, Ok(Async::NotReady) => Ok(Async::NotReady), @@ -330,7 +373,19 @@ where if self.buff.len() == 0 { return Ok(Async::Ready(None)); } - let line = str::from_utf8(&*self.buff)?.to_owned(); + if self.buff.len() > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&self.buff) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned() + } else { + encoding + .decode(&self.buff, DecoderTrap::Strict) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + }; self.buff.clear(); return Ok(Async::Ready(Some(line))) }, @@ -343,6 +398,7 @@ pub enum ReadlinesError { EncodingError, PayloadError(PayloadError), LimitOverflow, + ContentTypeError(ContentTypeError), } impl From for ReadlinesError { @@ -351,12 +407,18 @@ impl From for ReadlinesError { } } -impl From for ReadlinesError { - fn from(_: str::Utf8Error) -> Self { +impl From for ReadlinesError { + fn from(_: Error) -> Self { ReadlinesError::EncodingError } } +impl From for ReadlinesError { + fn from(err: ContentTypeError) -> Self { + ReadlinesError::ContentTypeError(err) + } +} + /// Future that resolves to a complete http message body. pub struct MessageBody { limit: usize, From 1bee528018605f0eec143fcf6bd432e2c4b124c0 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Wed, 13 Jun 2018 22:59:36 +0300 Subject: [PATCH 0376/1635] move ReadlinesError to error module --- src/error.rs | 26 ++++++++++++++++++++++++++ src/httpmessage.rs | 27 +-------------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/error.rs b/src/error.rs index bbafb1c4..4cbfe39e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -590,6 +590,32 @@ impl From for JsonPayloadError { } } +/// Error type returned when reading body as lines. +pub enum ReadlinesError { + EncodingError, + PayloadError(PayloadError), + LimitOverflow, + ContentTypeError(ContentTypeError), +} + +impl From for ReadlinesError { + fn from(err: PayloadError) -> Self { + ReadlinesError::PayloadError(err) + } +} + +impl From for ReadlinesError { + fn from(_: Error) -> Self { + ReadlinesError::EncodingError + } +} + +impl From for ReadlinesError { + fn from(err: ContentTypeError) -> Self { + ReadlinesError::ContentTypeError(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/httpmessage.rs b/src/httpmessage.rs index 91ea09f6..a380f3b9 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -13,7 +13,7 @@ use std::str; use error::{ ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError, - Error, ErrorBadRequest + Error, ErrorBadRequest, ReadlinesError }; use header::Header; use json::JsonBody; @@ -394,31 +394,6 @@ where } } -pub enum ReadlinesError { - EncodingError, - PayloadError(PayloadError), - LimitOverflow, - ContentTypeError(ContentTypeError), -} - -impl From for ReadlinesError { - fn from(err: PayloadError) -> Self { - ReadlinesError::PayloadError(err) - } -} - -impl From for ReadlinesError { - fn from(_: Error) -> Self { - ReadlinesError::EncodingError - } -} - -impl From for ReadlinesError { - fn from(err: ContentTypeError) -> Self { - ReadlinesError::ContentTypeError(err) - } -} - /// Future that resolves to a complete http message body. pub struct MessageBody { limit: usize, From cb77f7e688985fa1f8d677c2a3013994ed487a60 Mon Sep 17 00:00:00 2001 From: Dursun Akkurt Date: Thu, 14 Jun 2018 00:19:48 +0300 Subject: [PATCH 0377/1635] Add `HttpMessage::readlines()` --- CHANGES.md | 2 ++ src/error.rs | 10 +++--- src/httpmessage.rs | 84 ++++++++++++++++++++++++++++++++-------------- 3 files changed, 64 insertions(+), 32 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5265d9d7..ee109fe0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Added +* Add `HttpMessage::readlines()` for reading line by line. + * Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. * Add method to configure custom error handler to Form extractor. diff --git a/src/error.rs b/src/error.rs index 4cbfe39e..c272a2dc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -592,9 +592,13 @@ impl From for JsonPayloadError { /// Error type returned when reading body as lines. pub enum ReadlinesError { + /// Error when decoding a line. EncodingError, + /// Payload error. PayloadError(PayloadError), + /// Line limit exceeded. LimitOverflow, + /// ContentType error. ContentTypeError(ContentTypeError), } @@ -604,12 +608,6 @@ impl From for ReadlinesError { } } -impl From for ReadlinesError { - fn from(_: Error) -> Self { - ReadlinesError::EncodingError - } -} - impl From for ReadlinesError { fn from(err: ContentTypeError) -> Self { ReadlinesError::ContentTypeError(err) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index a380f3b9..82c50d77 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -13,7 +13,7 @@ use std::str; use error::{ ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError, - Error, ErrorBadRequest, ReadlinesError + ReadlinesError }; use header::Header; use json::JsonBody; @@ -279,6 +279,7 @@ where req: T, buff: BytesMut, limit: usize, + checked_buff: bool, } impl Readlines @@ -291,6 +292,7 @@ where req, buff: BytesMut::with_capacity(262_144), limit: 262_144, + checked_buff: true, } } @@ -311,29 +313,32 @@ where fn poll(&mut self) -> Poll, Self::Error> { let encoding = self.req.encoding()?; // check if there is a newline in the buffer - let mut found: Option = None; - for (ind, b) in self.buff.iter().enumerate() { - if *b == '\n' as u8 { - found = Some(ind); - break; + if !self.checked_buff { + let mut found: Option = None; + for (ind, b) in self.buff.iter().enumerate() { + if *b == '\n' as u8 { + found = Some(ind); + break; + } } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind+1 > self.limit { - return Err(ReadlinesError::LimitOverflow); + if let Some(ind) = found { + // check if line is longer than limit + if ind+1 > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&self.buff.split_to(ind+1)) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + encoding + .decode(&self.buff.split_to(ind+1), DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + return Ok(Async::Ready(Some(line))); } - let enc: *const Encoding = encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff.split_to(ind+1)) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned() - } else { - encoding - .decode(&self.buff.split_to(ind+1), DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - }; - return Ok(Async::Ready(Some(line))); + self.checked_buff = true; } // poll req for more bytes match self.req.poll() { @@ -354,15 +359,16 @@ where let enc: *const Encoding = encoding as *const Encoding; let line = if enc == UTF_8 { str::from_utf8(&bytes.split_to(ind+1)) - .map_err(|_| ErrorBadRequest("Can not decode body"))? + .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { encoding .decode(&bytes.split_to(ind+1), DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))? + .map_err(|_| ReadlinesError::EncodingError)? }; // extend buffer with rest of the bytes; self.buff.extend_from_slice(&bytes); + self.checked_buff = false; return Ok(Async::Ready(Some(line))); } self.buff.extend_from_slice(&bytes); @@ -379,12 +385,12 @@ where let enc: *const Encoding = encoding as *const Encoding; let line = if enc == UTF_8 { str::from_utf8(&self.buff) - .map_err(|_| ErrorBadRequest("Can not decode body"))? + .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { encoding .decode(&self.buff, DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))? + .map_err(|_| ReadlinesError::EncodingError)? }; self.buff.clear(); return Ok(Async::Ready(Some(line))) @@ -799,4 +805,30 @@ mod tests { _ => unreachable!("error"), } } + + #[test] + fn test_readlines() { + let mut req = HttpRequest::default(); + req.payload_mut().unread_data(Bytes::from_static( + b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ + industry. Lorem Ipsum has been the industry's standard dummy\n\ + Contrary to popular belief, Lorem Ipsum is not simply random text." + )); + let mut r = Readlines::new(req); + match r.poll().ok().unwrap() { + Async::Ready(Some(s)) => assert_eq!(s, + "Lorem Ipsum is simply dummy text of the printing and typesetting\n"), + _ => unreachable!("error"), + } + match r.poll().ok().unwrap() { + Async::Ready(Some(s)) => assert_eq!(s, + "industry. Lorem Ipsum has been the industry's standard dummy\n"), + _ => unreachable!("error"), + } + match r.poll().ok().unwrap() { + Async::Ready(Some(s)) => assert_eq!(s, + "Contrary to popular belief, Lorem Ipsum is not simply random text."), + _ => unreachable!("error"), + } + } } From 8261cf437deb4cf761c8fa412b94701e324b65f4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Jun 2018 23:37:19 -0700 Subject: [PATCH 0378/1635] update actix api --- src/header/mod.rs | 50 +++++++++++++++++++++++++-------------- src/httpresponse.rs | 28 +++++++++++++--------- src/middleware/session.rs | 2 +- src/server/mod.rs | 2 +- src/server/srv.rs | 31 +++++++++--------------- src/server/worker.rs | 4 ++-- src/test.rs | 31 ++++++++---------------- tests/test_handlers.rs | 23 ++++++++++-------- tests/test_middleware.rs | 12 +++++----- tests/test_server.rs | 12 ++++++---- 10 files changed, 100 insertions(+), 95 deletions(-) diff --git a/src/header/mod.rs b/src/header/mod.rs index 8a7dd5bd..847cb53b 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -311,9 +311,8 @@ pub struct ExtendedValue { /// ; token except ( "*" / "'" / "%" ) /// ``` pub fn parse_extended_value(val: &str) -> Result { - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3,'\''); + let mut parts = val.splitn(3, '\''); // Interpret the first piece as a Charset let charset: Charset = match parts.next() { @@ -322,13 +321,13 @@ pub fn parse_extended_value(val: &str) -> Result = match parts.next() { + let language_tag: Option = match parts.next() { None => return Err(::error::ParseError::Header), Some("") => None, Some(s) => match s.parse() { Ok(lt) => Some(lt), Err(_) => return Err(::error::ParseError::Header), - } + }, }; // Interpret the third piece as a sequence of value characters @@ -338,17 +337,18 @@ pub fn parse_extended_value(val: &str) -> Result fmt::Result { - let encoded_value = - percent_encoding::percent_encode(&self.value[..], self::percent_encoding_http::HTTP_VALUE); + let encoded_value = percent_encoding::percent_encode( + &self.value[..], + self::percent_encoding_http::HTTP_VALUE, + ); if let Some(ref lang) = self.language_tag { write!(f, "{}'{}'{}", self.charset, lang, encoded_value) } else { @@ -362,7 +362,8 @@ impl fmt::Display for ExtendedValue { /// /// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + let encoded = + percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); fmt::Display::fmt(&encoded, f) } mod percent_encoding_http { @@ -382,8 +383,8 @@ mod percent_encoding_http { #[cfg(test)] mod tests { + use super::{parse_extended_value, ExtendedValue}; use header::shared::Charset; - use super::{ExtendedValue, parse_extended_value}; use language_tags::LanguageTag; #[test] @@ -397,7 +398,10 @@ mod tests { assert_eq!(Charset::Iso_8859_1, extended_value.charset); assert!(extended_value.language_tag.is_some()); assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!(vec![163, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); + assert_eq!( + vec![163, b' ', b'r', b'a', b't', b'e', b's'], + extended_value.value + ); } #[test] @@ -410,7 +414,13 @@ mod tests { let extended_value = result.unwrap(); assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); assert!(extended_value.language_tag.is_none()); - assert_eq!(vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); + assert_eq!( + vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + extended_value.value + ); } #[test] @@ -447,10 +457,14 @@ mod tests { let extended_value = ExtendedValue { charset: Charset::Ext("UTF-8".to_string()), language_tag: None, - value: vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's'], + value: vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], }; - assert_eq!("UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value)); + assert_eq!( + "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", + format!("{}", extended_value) + ); } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 40a8cc61..a0eb46a6 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -147,16 +147,18 @@ impl HttpResponse { #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter() + iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter(), } } /// Add a cookie to this response #[inline] - pub fn add_cookie(&mut self, cookie: Cookie) -> Result<(), HttpError> { + pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { let h = &mut self.get_mut().headers; HeaderValue::from_str(&cookie.to_string()) - .map(|c| { h.append(header::SET_COOKIE, c); }) + .map(|c| { + h.append(header::SET_COOKIE, c); + }) .map_err(|e| e.into()) } @@ -165,7 +167,8 @@ impl HttpResponse { #[inline] pub fn del_cookie(&mut self, name: &str) -> usize { let h = &mut self.get_mut().headers; - let vals: Vec = h.get_all(header::SET_COOKIE) + let vals: Vec = h + .get_all(header::SET_COOKIE) .iter() .map(|v| v.to_owned()) .collect(); @@ -183,7 +186,7 @@ impl HttpResponse { } h.append(header::SET_COOKIE, v); } - return count; + count } /// Get the response status code @@ -333,7 +336,7 @@ impl<'a> Iterator for CookieIter<'a> { #[inline] fn next(&mut self) -> Option> { for v in self.iter.by_ref() { - if let Some(c) = (|| Cookie::parse(v.to_str().ok()?).ok())() { + if let Ok(c) = Cookie::parse(v.to_str().ok()?) { return Some(c); } } @@ -1056,16 +1059,18 @@ mod tests { ); } - #[test] fn test_update_response_cookies() { let mut r = HttpResponse::Ok() .cookie(http::Cookie::new("original", "val100")) .finish(); - r.add_cookie(http::Cookie::new("cookie2", "val200")).unwrap(); - r.add_cookie(http::Cookie::new("cookie2", "val250")).unwrap(); - r.add_cookie(http::Cookie::new("cookie3", "val300")).unwrap(); + r.add_cookie(&http::Cookie::new("cookie2", "val200")) + .unwrap(); + r.add_cookie(&http::Cookie::new("cookie2", "val250")) + .unwrap(); + r.add_cookie(&http::Cookie::new("cookie3", "val300")) + .unwrap(); assert_eq!(r.cookies().count(), 4); r.del_cookie("cookie2"); @@ -1287,7 +1292,8 @@ mod tests { let mut resp: HttpResponse = "test".into(); assert_eq!(resp.status(), StatusCode::OK); - resp.add_cookie(http::Cookie::new("cookie1", "val100")).unwrap(); + resp.add_cookie(&http::Cookie::new("cookie1", "val100")) + .unwrap(); let mut builder = resp.into_builder(); let resp = builder.status(StatusCode::BAD_REQUEST).finish(); diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 2bf02e38..bd96a236 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -58,7 +58,7 @@ //! ))) //! .bind("127.0.0.1:59880").unwrap() //! .start(); -//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +//! # actix::System::current().stop(); //! }); //! } //! ``` diff --git a/src/server/mod.rs b/src/server/mod.rs index 8a3f0364..0c27d55e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -54,7 +54,7 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// .bind("127.0.0.1:59090").unwrap() /// .start(); /// -/// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); +/// # actix::System::current().stop(); /// }); /// } /// ``` diff --git a/src/server/srv.rs b/src/server/srv.rs index 21722c33..6bb64ee2 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -4,8 +4,8 @@ use std::time::Duration; use std::{io, net, thread}; use actix::{ - fut, msgs, signal, Actor, ActorContext, ActorFuture, Addr, Arbiter, AsyncContext, - Context, ContextFutureSpawner, Handler, Response, StreamHandler, System, WrapFuture, + fut, signal, Actor, ActorContext, ActorFuture, Addr, Arbiter, AsyncContext, Context, + ContextFutureSpawner, Handler, Response, StreamHandler, System, WrapFuture, }; use futures::sync::mpsc; @@ -64,16 +64,8 @@ where no_signals: bool, } -unsafe impl Sync for HttpServer -where - H: IntoHttpHandler, -{ -} -unsafe impl Send for HttpServer -where - H: IntoHttpHandler, -{ -} +unsafe impl Sync for HttpServer where H: IntoHttpHandler {} +unsafe impl Send for HttpServer where H: IntoHttpHandler {} enum ServerCommand { WorkerDied(usize, Slab), @@ -170,10 +162,9 @@ where self } - /// Send `SystemExit` message to actix system + /// Stop actix system. /// - /// `SystemExit` message stops currently running system arbiter and all - /// nested arbiters. + /// `SystemExit` message stops currently running system. pub fn system_exit(mut self) -> Self { self.exit = true; self @@ -388,7 +379,7 @@ where if let Some(ref signals) = self.signals { Some(signals.clone()) } else { - Some(Arbiter::registry().get::()) + Some(System::current().registry().get::()) } } else { None @@ -418,7 +409,7 @@ impl HttpServer { /// .bind("127.0.0.1:0") /// .expect("Can not bind to 127.0.0.1:0") /// .start(); - /// # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0)); + /// # actix::System::current().stop(); /// }); /// } /// ``` @@ -597,7 +588,7 @@ impl HttpServer { } /// Signals support -/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and send `SystemExit(0)` +/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system /// message to `System` actor. impl Handler for HttpServer { type Result = (); @@ -753,7 +744,7 @@ impl Handler for HttpServer { // we need to stop system if server was spawned if slf.exit { ctx.run_later(Duration::from_millis(300), |_, _| { - Arbiter::system().do_send(msgs::SystemExit(0)) + System::current().stop(); }); } } @@ -768,7 +759,7 @@ impl Handler for HttpServer { // we need to stop system if server was spawned if self.exit { ctx.run_later(Duration::from_millis(300), |_, _| { - Arbiter::system().do_send(msgs::SystemExit(0)) + System::current().stop(); }); } Response::reply(Ok(())) diff --git a/src/server/worker.rs b/src/server/worker.rs index 5a3f8858..3d4ee863 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -95,14 +95,14 @@ impl Worker { let num = slf.settings.num_channels(); if num == 0 { let _ = tx.send(true); - Arbiter::arbiter().do_send(StopArbiter(0)); + Arbiter::current().do_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); slf.settings.head().traverse::(); let _ = tx.send(false); - Arbiter::arbiter().do_send(StopArbiter(0)); + Arbiter::current().do_send(StopArbiter(0)); } }); } diff --git a/src/test.rs b/src/test.rs index d8fd5773..b8372f6a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use std::sync::mpsc; use std::{net, thread}; -use actix_inner::{msgs, Actor, Addr, Arbiter, System}; +use actix_inner::{Actor, Addr, System}; use cookie::Cookie; use futures::Future; @@ -59,7 +59,6 @@ use ws; /// ``` pub struct TestServer { addr: net::SocketAddr, - server_sys: Addr, ssl: bool, conn: Addr, rt: Runtime, @@ -116,20 +115,15 @@ impl TestServer { .listen(tcp) .start(); - tx.send(( - Arbiter::system(), - local_addr, - TestServer::get_conn(), - Arbiter::registry().clone(), - )).unwrap(); + tx.send((System::current(), local_addr, TestServer::get_conn())) + .unwrap(); }).run(); }); - let (server_sys, addr, conn, reg) = rx.recv().unwrap(); - Arbiter::set_system_reg(reg); + let (system, addr, conn) = rx.recv().unwrap(); + System::set_current(system); TestServer { addr, - server_sys, conn, ssl: false, rt: Runtime::new().unwrap(), @@ -187,7 +181,7 @@ impl TestServer { /// Stop http server fn stop(&mut self) { - self.server_sys.do_send(msgs::SystemExit(0)); + System::current().stop(); } /// Execute future on current core @@ -296,12 +290,8 @@ impl TestServerBuilder { }).workers(1) .disable_signals(); - tx.send(( - Arbiter::system(), - local_addr, - TestServer::get_conn(), - Arbiter::registry().clone(), - )).unwrap(); + tx.send((System::current(), local_addr, TestServer::get_conn())) + .unwrap(); #[cfg(feature = "alpn")] { @@ -320,13 +310,12 @@ impl TestServerBuilder { .run(); }); - let (server_sys, addr, conn, reg) = rx.recv().unwrap(); - Arbiter::set_system_reg(reg); + let (system, addr, conn) = rx.recv().unwrap(); + System::set_current(system); TestServer { addr, ssl, conn, - server_sys, rt: Runtime::new().unwrap(), } } diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index e6738e8a..116112e2 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -105,9 +105,8 @@ struct FormData { fn test_form_extractor() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with(|form: Form| { - format!("{}", form.username) - }) + r.route() + .with(|form: Form| format!("{}", form.username)) }); }); @@ -115,7 +114,9 @@ fn test_form_extractor() { let request = srv .post() .uri(srv.url("/test1/index.html")) - .form(FormData{username: "test".to_string()}) + .form(FormData { + username: "test".to_string(), + }) .unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -129,12 +130,14 @@ fn test_form_extractor() { fn test_form_extractor2() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with(|form: Form| { - format!("{}", form.username) - }).error_handler(|err, res| { - error::InternalError::from_response( - err, HttpResponse::Conflict().finish()).into() - }); + r.route() + .with(|form: Form| format!("{}", form.username)) + .error_handler(|err, _| { + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish(), + ).into() + }); }); }); diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index d64e4fee..bdcde148 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -839,7 +839,7 @@ fn test_middleware_chain_with_error() { }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + srv.execute(request.send()).unwrap(); assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); @@ -869,7 +869,7 @@ fn test_middleware_async_chain_with_error() { }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + srv.execute(request.send()).unwrap(); assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); @@ -901,7 +901,7 @@ fn test_scope_middleware_chain_with_error() { }); let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + srv.execute(request.send()).unwrap(); assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); @@ -933,7 +933,7 @@ fn test_scope_middleware_async_chain_with_error() { }); let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + srv.execute(request.send()).unwrap(); assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); @@ -964,7 +964,7 @@ fn test_resource_middleware_chain_with_error() { }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + srv.execute(request.send()).unwrap(); assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); @@ -995,7 +995,7 @@ fn test_resource_middleware_async_chain_with_error() { }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + srv.execute(request.send()).unwrap(); assert_eq!(num1.load(Ordering::Relaxed), 1); assert_eq!(num2.load(Ordering::Relaxed), 1); diff --git a/tests/test_server.rs b/tests/test_server.rs index 2eccadef..50864735 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -32,7 +32,7 @@ use tokio::executor::current_thread; use tokio::runtime::current_thread::Runtime; use tokio_tcp::TcpStream; -use actix::{msgs, Arbiter, System}; +use actix::System; use actix_web::*; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -74,10 +74,11 @@ fn test_start() { let srv = srv.bind("127.0.0.1:0").unwrap(); let addr = srv.addrs()[0]; let srv_addr = srv.start(); - let _ = tx.send((addr, srv_addr, Arbiter::system())); + let _ = tx.send((addr, srv_addr, System::current())); }); }); let (addr, srv_addr, sys) = rx.recv().unwrap(); + System::set_current(sys.clone()); let mut rt = Runtime::new().unwrap(); { @@ -110,7 +111,7 @@ fn test_start() { assert!(response.status().is_success()); } - let _ = sys.send(msgs::SystemExit(0)).wait(); + let _ = sys.stop(); } #[test] @@ -130,10 +131,11 @@ fn test_shutdown() { let srv = srv.bind("127.0.0.1:0").unwrap(); let addr = srv.addrs()[0]; let srv_addr = srv.shutdown_timeout(1).start(); - let _ = tx.send((addr, srv_addr, Arbiter::system())); + let _ = tx.send((addr, srv_addr, System::current())); }); }); let (addr, srv_addr, sys) = rx.recv().unwrap(); + System::set_current(sys.clone()); let mut rt = Runtime::new().unwrap(); { @@ -148,7 +150,7 @@ fn test_shutdown() { thread::sleep(time::Duration::from_millis(1000)); assert!(net::TcpStream::connect(addr).is_err()); - let _ = sys.send(msgs::SystemExit(0)).wait(); + let _ = sys.stop(); } #[test] From 342a1946051b66345c4aeae212a15877c32ca800 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Jun 2018 22:56:27 +0600 Subject: [PATCH 0379/1635] fix handling ServerCommand #316 --- src/server/srv.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/server/srv.rs b/src/server/srv.rs index 6bb64ee2..c5aca757 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -4,7 +4,7 @@ use std::time::Duration; use std::{io, net, thread}; use actix::{ - fut, signal, Actor, ActorContext, ActorFuture, Addr, Arbiter, AsyncContext, Context, + fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, ContextFutureSpawner, Handler, Response, StreamHandler, System, WrapFuture, }; @@ -617,9 +617,7 @@ impl Handler for HttpServer { /// Commands from accept threads impl StreamHandler for HttpServer { - fn handle( - &mut self, msg: Result, ()>, ctx: &mut Context, - ) { + fn handle(&mut self, msg: Result, ()>, _: &mut Context) { match msg { Ok(Some(ServerCommand::WorkerDied(idx, socks))) => { let mut found = false; @@ -667,7 +665,7 @@ impl StreamHandler for HttpServer { self.workers.push((new_idx, addr)); } } - _ => ctx.stop(), + _ => (), } } } From e4443226f694035c7d6752389be45f4e5e781b0d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 02:58:56 +0600 Subject: [PATCH 0380/1635] update actix usage --- src/httprequest.rs | 2 +- src/server/srv.rs | 52 +++++++++++++++++------------------ src/server/worker.rs | 2 ++ src/test.rs | 65 +++++++++++++++++++++----------------------- 4 files changed, 59 insertions(+), 62 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index a54a9958..0cbdbb25 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -105,7 +105,7 @@ pub struct HttpRequest(SharedHttpInnerMessage, Option>, Option { /// Construct a new Request. #[inline] - pub fn new( + pub(crate) fn new( method: Method, uri: Uri, version: Version, headers: HeaderMap, payload: Option, ) -> HttpRequest { diff --git a/src/server/srv.rs b/src/server/srv.rs index c5aca757..c1cf0a18 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -4,8 +4,8 @@ use std::time::Duration; use std::{io, net, thread}; use actix::{ - fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, - ContextFutureSpawner, Handler, Response, StreamHandler, System, WrapFuture, + fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler, + Response, StreamHandler, System, WrapFuture, }; use futures::sync::mpsc; @@ -64,8 +64,7 @@ where no_signals: bool, } -unsafe impl Sync for HttpServer where H: IntoHttpHandler {} -unsafe impl Send for HttpServer where H: IntoHttpHandler {} +unsafe impl Send for HttpServer {} enum ServerCommand { WorkerDied(usize, Slab), @@ -485,11 +484,9 @@ impl HttpServer { self.no_signals = false; let _ = thread::spawn(move || { - System::new("http-server") - .config(|| { - self.start(); - }) - .run(); + let sys = System::new("http-server"); + self.start(); + sys.run(); }).join(); } } @@ -557,7 +554,7 @@ impl HttpServer { pub fn start_incoming(mut self, stream: S, secure: bool) -> Addr where S: Stream + Send + 'static, - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Send + 'static, { // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); @@ -730,25 +727,26 @@ impl Handler for HttpServer { }; for worker in &self.workers { let tx2 = tx.clone(); - worker - .1 - .send(StopWorker { graceful: dur }) - .into_actor(self) - .then(move |_, slf, ctx| { - slf.workers.pop(); - if slf.workers.is_empty() { - let _ = tx2.send(()); + ctx.spawn( + worker + .1 + .send(StopWorker { graceful: dur }) + .into_actor(self) + .then(move |_, slf, ctx| { + slf.workers.pop(); + if slf.workers.is_empty() { + let _ = tx2.send(()); - // we need to stop system if server was spawned - if slf.exit { - ctx.run_later(Duration::from_millis(300), |_, _| { - System::current().stop(); - }); + // we need to stop system if server was spawned + if slf.exit { + ctx.run_later(Duration::from_millis(300), |_, _| { + System::current().stop(); + }); + } } - } - fut::ok(()) - }) - .spawn(ctx); + fut::ok(()) + }), + ); } if !self.workers.is_empty() { diff --git a/src/server/worker.rs b/src/server/worker.rs index 3d4ee863..6cf2bbd6 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -65,6 +65,8 @@ where tcp_ka: Option, } +unsafe impl Send for Worker {} + impl Worker { pub(crate) fn new( h: Vec, socks: Slab, keep_alive: KeepAlive, diff --git a/src/test.rs b/src/test.rs index b8372f6a..cf9b7f1c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -109,15 +109,14 @@ impl TestServer { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - sys.config(move || { - HttpServer::new(factory) - .disable_signals() - .listen(tcp) - .start(); + HttpServer::new(factory) + .disable_signals() + .listen(tcp) + .start(); - tx.send((System::current(), local_addr, TestServer::get_conn())) - .unwrap(); - }).run(); + tx.send((System::current(), local_addr, TestServer::get_conn())) + .unwrap(); + sys.run(); }); let (system, addr, conn) = rx.recv().unwrap(); @@ -280,34 +279,32 @@ impl TestServerBuilder { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - System::new("actix-test-server") - .config(move || { - let state = self.state; - let srv = HttpServer::new(move || { - let mut app = TestApp::new(state()); - config(&mut app); - vec![app] - }).workers(1) - .disable_signals(); + let sys = System::new("actix-test-server"); + let state = self.state; + let srv = HttpServer::new(move || { + let mut app = TestApp::new(state()); + config(&mut app); + vec![app] + }).workers(1) + .disable_signals(); - tx.send((System::current(), local_addr, TestServer::get_conn())) - .unwrap(); + tx.send((System::current(), local_addr, TestServer::get_conn())) + .unwrap(); - #[cfg(feature = "alpn")] - { - let ssl = self.ssl.take(); - if let Some(ssl) = ssl { - srv.listen_ssl(tcp, ssl).unwrap().start(); - } else { - srv.listen(tcp).start(); - } - } - #[cfg(not(feature = "alpn"))] - { - srv.listen(tcp).start(); - } - }) - .run(); + #[cfg(feature = "alpn")] + { + let ssl = self.ssl.take(); + if let Some(ssl) = ssl { + srv.listen_ssl(tcp, ssl).unwrap().start(); + } else { + srv.listen(tcp).start(); + } + } + #[cfg(not(feature = "alpn"))] + { + srv.listen(tcp).start(); + } + sys.run(); }); let (system, addr, conn) = rx.recv().unwrap(); From 33050f55a3fa53bc1e51f1996c5ef53e71c331a1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 03:10:44 +0600 Subject: [PATCH 0381/1635] remove Context::actor() method --- src/context.rs | 27 ++++++++++++++++----------- src/ws/context.rs | 27 ++++++++++++++++----------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/context.rs b/src/context.rs index e56c1737..601cfe58 100644 --- a/src/context.rs +++ b/src/context.rs @@ -104,24 +104,29 @@ where #[inline] /// Create a new HTTP Context from a request and an actor pub fn new(req: HttpRequest, actor: A) -> HttpContext { - HttpContext::from_request(req).actor(actor) - } - - /// Create a new HTTP Context from a request - pub fn from_request(req: HttpRequest) -> HttpContext { HttpContext { - inner: ContextImpl::new(None), + inner: ContextImpl::new(Some(actor)), stream: None, request: req, disconnected: false, } } - #[inline] - /// Set the actor of this context - pub fn actor(mut self, actor: A) -> HttpContext { - self.inner.set_actor(actor); - self + /// Create a new HTTP Context + pub fn with_factory(req: HttpRequest, f: F) -> HttpContext + where + F: FnOnce(&mut Self) -> A + 'static, + { + let mut ctx = HttpContext { + inner: ContextImpl::new(None), + stream: None, + request: req, + disconnected: false, + }; + + let act = f(&mut ctx); + ctx.inner.set_actor(act); + ctx } } diff --git a/src/ws/context.rs b/src/ws/context.rs index 9237eab4..34346f1e 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -88,24 +88,29 @@ where #[inline] /// Create a new Websocket context from a request and an actor pub fn new(req: HttpRequest, actor: A) -> WebsocketContext { - WebsocketContext::from_request(req).actor(actor) - } - - /// Create a new Websocket context from a request - pub fn from_request(req: HttpRequest) -> WebsocketContext { WebsocketContext { - inner: ContextImpl::new(None), + inner: ContextImpl::new(Some(actor)), stream: None, request: req, disconnected: false, } } - #[inline] - /// Set actor for this execution context - pub fn actor(mut self, actor: A) -> WebsocketContext { - self.inner.set_actor(actor); - self + /// Create a new Websocket context + pub fn with_factory(req: HttpRequest, f: F) -> Self + where + F: FnOnce(&mut Self) -> A + 'static, + { + let mut ctx = WebsocketContext { + inner: ContextImpl::new(None), + stream: None, + request: req, + disconnected: false, + }; + + let act = f(&mut ctx); + ctx.inner.set_actor(act); + ctx } } From 879b2b5bde0be05a0809f797ded86b16a38f07f0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 03:22:08 +0600 Subject: [PATCH 0382/1635] port Extensions from http crate #315 --- Cargo.toml | 1 + src/extensions.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++ src/httprequest.rs | 3 +- src/lib.rs | 3 ++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/extensions.rs diff --git a/Cargo.toml b/Cargo.toml index 9cd3304f..fe5dfba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ base64 = "0.9" bitflags = "1.0" failure = "0.1.1" h2 = "0.1" +fnv = "1.0.5" http = "^0.1.5" httparse = "1.2" http-range = "0.1" diff --git a/src/extensions.rs b/src/extensions.rs new file mode 100644 index 00000000..1c64623f --- /dev/null +++ b/src/extensions.rs @@ -0,0 +1,90 @@ +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt; +use std::hash::BuildHasherDefault; + +use fnv::FnvHasher; + +type AnyMap = HashMap, BuildHasherDefault>; + +/// A type map of request extensions. +pub struct Extensions { + map: AnyMap, +} + +impl Extensions { + /// Create an empty `Extensions`. + #[inline] + pub(crate) fn new() -> Extensions { + Extensions { + map: HashMap::default(), + } + } + + /// Insert a type into this `Extensions`. + /// + /// If a extension of this type already existed, it will + /// be returned. + pub fn insert(&mut self, val: T) { + self.map.insert(TypeId::of::(), Box::new(val)); + } + + /// Get a reference to a type previously inserted on this `Extensions`. + pub fn get(&self) -> Option<&T> { + self.map + .get(&TypeId::of::()) + .and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref()) + } + + /// Get a mutable reference to a type previously inserted on this `Extensions`. + pub fn get_mut(&mut self) -> Option<&mut T> { + self.map + .get_mut(&TypeId::of::()) + .and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut()) + } + + /// Remove a type from this `Extensions`. + /// + /// If a extension of this type existed, it will be returned. + pub fn remove(&mut self) -> Option { + self.map.remove(&TypeId::of::()).and_then(|boxed| { + //TODO: we can use unsafe and remove double checking the type id + (boxed as Box) + .downcast() + .ok() + .map(|boxed| *boxed) + }) + } + + /// Clear the `Extensions` of all inserted extensions. + #[inline] + pub fn clear(&mut self) { + self.map.clear(); + } +} + +impl fmt::Debug for Extensions { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Extensions").finish() + } +} + +#[test] +fn test_extensions() { + #[derive(Debug, PartialEq)] + struct MyType(i32); + + let mut extensions = Extensions::new(); + + extensions.insert(5i32); + extensions.insert(MyType(10)); + + assert_eq!(extensions.get(), Some(&5i32)); + assert_eq!(extensions.get_mut(), Some(&mut 5i32)); + + assert_eq!(extensions.remove::(), Some(5i32)); + assert!(extensions.get::().is_none()); + + assert_eq!(extensions.get::(), None); + assert_eq!(extensions.get(), Some(&MyType(10))); +} diff --git a/src/httprequest.rs b/src/httprequest.rs index 0cbdbb25..dded35f6 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -9,12 +9,13 @@ use cookie::Cookie; use failure; use futures::{Async, Poll, Stream}; use futures_cpupool::CpuPool; -use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version}; +use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; use tokio_io::AsyncRead; use url::{form_urlencoded, Url}; use body::Body; use error::{CookieParseError, PayloadError, UrlGenerationError}; +use extensions::Extensions; use handler::FromRequest; use httpmessage::HttpMessage; use httpresponse::{HttpResponse, HttpResponseBuilder}; diff --git a/src/lib.rs b/src/lib.rs index c3b9fc7a..b3e143a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,7 @@ extern crate time; extern crate bitflags; #[macro_use] extern crate failure; +extern crate fnv; #[macro_use] extern crate lazy_static; #[macro_use] @@ -155,6 +156,7 @@ mod application; mod body; mod context; mod de; +mod extensions; mod extractor; mod handler; mod header; @@ -188,6 +190,7 @@ pub use application::App; pub use body::{Binary, Body}; pub use context::HttpContext; pub use error::{Error, ResponseError, Result}; +pub use extensions::Extensions; pub use extractor::{Form, Path, Query}; pub use handler::{ AsyncResponder, Either, FromRequest, FutureResponse, Responder, State, From f3a73d7ddef0826aa1e587c2f4aabde27ef9e165 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 03:24:08 +0600 Subject: [PATCH 0383/1635] update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index ee109fe0..9f165163 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -39,6 +39,8 @@ * Added header `User-Agent: Actix-web/` to default headers when building a request +* port `Extensions` type from http create, we dont need `Send + Sync` + ### Removed From a7a062fb684d5aca8eb946646cad3b249661e78c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 03:26:34 +0600 Subject: [PATCH 0384/1635] clippy warnings --- src/httpmessage.rs | 58 ++++++++++++++++-------------- src/server/srv.rs | 89 ++++++++++++++++++++++------------------------ 2 files changed, 75 insertions(+), 72 deletions(-) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 82c50d77..1ce04b47 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -3,7 +3,7 @@ use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::types::{DecoderTrap, Encoding}; use encoding::EncodingRef; -use futures::{Future, Poll, Stream, Async}; +use futures::{Async, Future, Poll, Stream}; use http::{header, HeaderMap}; use http_range::HttpRange; use mime::Mime; @@ -12,8 +12,8 @@ use serde_urlencoded; use std::str; use error::{ - ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError, - ReadlinesError + ContentTypeError, HttpRangeError, ParseError, PayloadError, ReadlinesError, + UrlencodedError, }; use header::Header; use json::JsonBody; @@ -261,7 +261,7 @@ pub trait HttpMessage { let boundary = Multipart::boundary(self.headers()); Multipart::new(boundary, self) } - + /// Return stream of lines. fn readlines(self) -> Readlines where @@ -295,7 +295,7 @@ where checked_buff: true, } } - + /// Change max line size. By default max size is 256Kb pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; @@ -309,31 +309,31 @@ where { type Item = String; type Error = ReadlinesError; - + fn poll(&mut self) -> Poll, Self::Error> { let encoding = self.req.encoding()?; // check if there is a newline in the buffer if !self.checked_buff { let mut found: Option = None; for (ind, b) in self.buff.iter().enumerate() { - if *b == '\n' as u8 { + if *b == b'\n' { found = Some(ind); break; } } if let Some(ind) = found { // check if line is longer than limit - if ind+1 > self.limit { + if ind + 1 > self.limit { return Err(ReadlinesError::LimitOverflow); } let enc: *const Encoding = encoding as *const Encoding; let line = if enc == UTF_8 { - str::from_utf8(&self.buff.split_to(ind+1)) + str::from_utf8(&self.buff.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { encoding - .decode(&self.buff.split_to(ind+1), DecoderTrap::Strict) + .decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; return Ok(Async::Ready(Some(line))); @@ -346,24 +346,24 @@ where // check if there is a newline in bytes let mut found: Option = None; for (ind, b) in bytes.iter().enumerate() { - if *b == '\n' as u8 { + if *b == b'\n' { found = Some(ind); break; } } if let Some(ind) = found { // check if line is longer than limit - if ind+1 > self.limit { + if ind + 1 > self.limit { return Err(ReadlinesError::LimitOverflow); } let enc: *const Encoding = encoding as *const Encoding; let line = if enc == UTF_8 { - str::from_utf8(&bytes.split_to(ind+1)) + str::from_utf8(&bytes.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { encoding - .decode(&bytes.split_to(ind+1), DecoderTrap::Strict) + .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; // extend buffer with rest of the bytes; @@ -373,10 +373,10 @@ where } self.buff.extend_from_slice(&bytes); Ok(Async::NotReady) - }, + } Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::Ready(None)) => { - if self.buff.len() == 0 { + if self.buff.is_empty() { return Ok(Async::Ready(None)); } if self.buff.len() > self.limit { @@ -393,8 +393,8 @@ where .map_err(|_| ReadlinesError::EncodingError)? }; self.buff.clear(); - return Ok(Async::Ready(Some(line))) - }, + Ok(Async::Ready(Some(line))) + } Err(e) => Err(ReadlinesError::from(e)), } } @@ -805,29 +805,35 @@ mod tests { _ => unreachable!("error"), } } - + #[test] fn test_readlines() { let mut req = HttpRequest::default(); req.payload_mut().unread_data(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ - Contrary to popular belief, Lorem Ipsum is not simply random text." + Contrary to popular belief, Lorem Ipsum is not simply random text.", )); let mut r = Readlines::new(req); match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!(s, - "Lorem Ipsum is simply dummy text of the printing and typesetting\n"), + Async::Ready(Some(s)) => assert_eq!( + s, + "Lorem Ipsum is simply dummy text of the printing and typesetting\n" + ), _ => unreachable!("error"), } match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!(s, - "industry. Lorem Ipsum has been the industry's standard dummy\n"), + Async::Ready(Some(s)) => assert_eq!( + s, + "industry. Lorem Ipsum has been the industry's standard dummy\n" + ), _ => unreachable!("error"), } match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!(s, - "Contrary to popular belief, Lorem Ipsum is not simply random text."), + Async::Ready(Some(s)) => assert_eq!( + s, + "Contrary to popular belief, Lorem Ipsum is not simply random text." + ), _ => unreachable!("error"), } } diff --git a/src/server/srv.rs b/src/server/srv.rs index c1cf0a18..897dbb09 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -615,54 +615,51 @@ impl Handler for HttpServer { /// Commands from accept threads impl StreamHandler for HttpServer { fn handle(&mut self, msg: Result, ()>, _: &mut Context) { - match msg { - Ok(Some(ServerCommand::WorkerDied(idx, socks))) => { - let mut found = false; - for i in 0..self.workers.len() { - if self.workers[i].0 == idx { - self.workers.swap_remove(i); - found = true; - break; - } - } - - if found { - error!("Worker has died {:?}, restarting", idx); - let (tx, rx) = mpsc::unbounded::>(); - - let mut new_idx = self.workers.len(); - 'found: loop { - for i in 0..self.workers.len() { - if self.workers[i].0 == new_idx { - new_idx += 1; - continue 'found; - } - } - break; - } - - let ka = self.keep_alive; - let factory = Arc::clone(&self.factory); - let settings = - ServerSettings::new(Some(socks[0].addr), &self.host, false); - - let addr = Arbiter::start(move |ctx: &mut Context<_>| { - let apps: Vec<_> = (*factory)() - .into_iter() - .map(|h| h.into_handler(settings.clone())) - .collect(); - ctx.add_message_stream(rx); - Worker::new(apps, socks, ka) - }); - for item in &self.accept { - let _ = item.1.send(Command::Worker(new_idx, tx.clone())); - let _ = item.0.set_readiness(mio::Ready::readable()); - } - - self.workers.push((new_idx, addr)); + if let Ok(Some(ServerCommand::WorkerDied(idx, socks))) = msg { + let mut found = false; + for i in 0..self.workers.len() { + if self.workers[i].0 == idx { + self.workers.swap_remove(i); + found = true; + break; } } - _ => (), + + if found { + error!("Worker has died {:?}, restarting", idx); + let (tx, rx) = mpsc::unbounded::>(); + + let mut new_idx = self.workers.len(); + 'found: loop { + for i in 0..self.workers.len() { + if self.workers[i].0 == new_idx { + new_idx += 1; + continue 'found; + } + } + break; + } + + let ka = self.keep_alive; + let factory = Arc::clone(&self.factory); + let settings = + ServerSettings::new(Some(socks[0].addr), &self.host, false); + + let addr = Arbiter::start(move |ctx: &mut Context<_>| { + let apps: Vec<_> = (*factory)() + .into_iter() + .map(|h| h.into_handler(settings.clone())) + .collect(); + ctx.add_message_stream(rx); + Worker::new(apps, socks, ka) + }); + for item in &self.accept { + let _ = item.1.send(Command::Worker(new_idx, tx.clone())); + let _ = item.0.set_readiness(mio::Ready::readable()); + } + + self.workers.push((new_idx, addr)); + } } } } From 70244c29e0398a38d934a35d39f3a52eee91e350 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 04:09:07 +0600 Subject: [PATCH 0385/1635] update doc api examples --- MIGRATION.md | 24 ------------------------ src/client/connector.rs | 14 +++++++------- src/client/mod.rs | 8 ++++---- src/server/mod.rs | 14 +++++++------- src/server/srv.rs | 16 ++++++++-------- 5 files changed, 26 insertions(+), 50 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 628f0590..175d82b3 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,29 +1,5 @@ ## 0.7 -* `actix::System` has new api. - - Instead of - - ```rust - fn main() { - let sys = actix::System::new(..); - - HttpServer::new(|| ...).start() - - sys.run(); - } - ``` - - Server must be initialized within system run closure: - - ```rust - fn main() { - actix::System::run(|| { - HttpServer::new(|| ...).start() - }); - } - ``` - * [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) trait uses `&mut self` instead of `&self`. diff --git a/src/client/connector.rs b/src/client/connector.rs index 5b844486..2727cc12 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -298,7 +298,6 @@ impl ClientConnector { /// # #![cfg(feature="alpn")] /// # extern crate actix_web; /// # extern crate futures; - /// # extern crate tokio; /// # use futures::{future, Future}; /// # use std::io::Write; /// # use std::process; @@ -309,11 +308,13 @@ impl ClientConnector { /// use openssl::ssl::{SslConnector, SslMethod}; /// /// fn main() { - /// tokio::run(future::lazy(|| { - /// // Start `ClientConnector` with custom `SslConnector` - /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn = ClientConnector::with_connector(ssl_conn).start(); + /// let mut sys = actix_web::actix::System::new("test"); /// + /// // Start `ClientConnector` with custom `SslConnector` + /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); + /// let conn = ClientConnector::with_connector(ssl_conn).start(); + /// + /// sys.block_on( /// conn.send( /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host /// .map_err(|_| ()) @@ -321,10 +322,9 @@ impl ClientConnector { /// if let Ok(mut stream) = res { /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); /// } - /// # process::exit(0); /// Ok(()) /// }) - /// })); + /// ); /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { diff --git a/src/client/mod.rs b/src/client/mod.rs index 3c956733..b40fa2ec 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -9,7 +9,9 @@ //! use actix_web::client; //! //! fn main() { -//! tokio::run({ +//! let mut sys = actix_web::actix::System::new("test"); +//! +//! sys.block_on( //! client::get("http://www.rust-lang.org") // <- Create request builder //! .header("User-Agent", "Actix-web") //! .finish().unwrap() @@ -17,13 +19,11 @@ //! .map_err(|_| ()) //! .and_then(|response| { // <- server http response //! println!("Response: {:?}", response); -//! # process::exit(0); //! Ok(()) //! }) -//! }); +//! ); //! } //! ``` - mod body; mod connector; mod parser; diff --git a/src/server/mod.rs b/src/server/mod.rs index 0c27d55e..978454ab 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -46,16 +46,16 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// use actix_web::{actix, server, App, HttpResponse}; /// /// fn main() { -/// actix::System::run(|| { +/// let sys = actix::System::new("example"); // <- create Actix system /// -/// server::new( -/// || App::new() -/// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090").unwrap() -/// .start(); +/// server::new( +/// || App::new() +/// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) +/// .bind("127.0.0.1:59090").unwrap() +/// .start(); /// /// # actix::System::current().stop(); -/// }); +/// sys.run(); /// } /// ``` pub fn new(factory: F) -> HttpServer diff --git a/src/server/srv.rs b/src/server/srv.rs index 897dbb09..0e51e2dc 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -402,14 +402,14 @@ impl HttpServer { /// use actix_web::{actix, server, App, HttpResponse}; /// /// fn main() { - /// // Run actix system, this method actually starts all async processes - /// actix::System::run(|| { - /// server::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .start(); - /// # actix::System::current().stop(); - /// }); + /// let sys = actix::System::new("example"); // <- create Actix system + /// + /// server::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0") + /// .expect("Can not bind to 127.0.0.1:0") + /// .start(); + /// # actix::System::current().stop(); + /// sys.run(); // <- Run actix system, this method starts all async processes /// } /// ``` pub fn start(mut self) -> Addr { From 0f2aac1a27d20e08cdc43bc7167799175a64c50b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 08:32:22 +0600 Subject: [PATCH 0386/1635] remove unneed Send and Sync --- src/middleware/identity.rs | 5 ----- src/middleware/session.rs | 5 ----- src/ws/client.rs | 19 ++++--------------- 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 706dd9fc..58cc0de4 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -177,11 +177,6 @@ impl IdentityService { struct IdentityBox(Box); -#[doc(hidden)] -unsafe impl Send for IdentityBox {} -#[doc(hidden)] -unsafe impl Sync for IdentityBox {} - impl> Middleware for IdentityService { fn start(&mut self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); diff --git a/src/middleware/session.rs b/src/middleware/session.rs index bd96a236..bb6c8223 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -221,11 +221,6 @@ impl FromRequest for Session { struct SessionImplCell(RefCell>); -#[doc(hidden)] -unsafe impl Send for SessionImplCell {} -#[doc(hidden)] -unsafe impl Sync for SessionImplCell {} - /// Session storage middleware /// /// ```rust diff --git a/src/ws/client.rs b/src/ws/client.rs index 6cb66173..7cf0095d 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -445,20 +445,13 @@ impl fmt::Debug for ClientReader { } } -impl ClientReader { - #[inline] - fn as_mut(&mut self) -> &mut Inner { - unsafe { &mut *self.inner.get() } - } -} - impl Stream for ClientReader { type Item = Message; type Error = ProtocolError; fn poll(&mut self) -> Poll, Self::Error> { let max_size = self.max_size; - let inner = self.as_mut(); + let inner: &mut Inner = unsafe { &mut *self.inner.get() }; if inner.closed { return Ok(Async::Ready(None)); } @@ -521,18 +514,14 @@ impl ClientWriter { /// Write payload #[inline] fn write(&mut self, mut data: Binary) { - if !self.as_mut().closed { - let _ = self.as_mut().tx.unbounded_send(data.take()); + let inner: &mut Inner = unsafe { &mut *self.inner.get() }; + if !inner.closed { + let _ = inner.tx.unbounded_send(data.take()); } else { warn!("Trying to write to disconnected response"); } } - #[inline] - fn as_mut(&mut self) -> &mut Inner { - unsafe { &mut *self.inner.get() } - } - /// Send text frame #[inline] pub fn text>(&mut self, text: T) { From b6ed778775a83f9813ff067678dea72e02cf6756 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 08:48:50 +0600 Subject: [PATCH 0387/1635] remove HttpMessage::range() --- CHANGES.md | 2 + Cargo.toml | 1 - src/error.rs | 43 ----- src/fs.rs | 435 +++++++++++++++++++++++++++++++++++++++++---- src/httpmessage.rs | 31 +--- src/lib.rs | 2 - 6 files changed, 407 insertions(+), 107 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9f165163..be17ef3d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -46,6 +46,8 @@ * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. +* Remove `HttpMessage::range()` + ## [0.6.13] - 2018-06-11 diff --git a/Cargo.toml b/Cargo.toml index fe5dfba0..b8157421 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,6 @@ h2 = "0.1" fnv = "1.0.5" http = "^0.1.5" httparse = "1.2" -http-range = "0.1" libc = "0.2" log = "0.4" mime = "0.3" diff --git a/src/error.rs b/src/error.rs index c272a2dc..395418d9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,7 +12,6 @@ use futures::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; use http2::Error as Http2Error; -use http_range::HttpRangeParseError; use httparse; use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; @@ -395,37 +394,6 @@ impl ResponseError for cookie::ParseError { } } -/// Http range header parsing error -#[derive(Fail, PartialEq, Debug)] -pub enum HttpRangeError { - /// Returned if range is invalid. - #[fail(display = "Range header is invalid")] - 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` - #[fail( - display = "First-byte-pos of all of the byte-range-spec values is greater than the content size" - )] - NoOverlap, -} - -/// Return `BadRequest` for `HttpRangeError` -impl ResponseError for HttpRangeError { - fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::BAD_REQUEST, "Invalid Range header provided") - } -} - -impl From for HttpRangeError { - fn from(err: HttpRangeParseError) -> HttpRangeError { - match err { - HttpRangeParseError::InvalidRange => HttpRangeError::InvalidRange, - HttpRangeParseError::NoOverlap => HttpRangeError::NoOverlap, - } - } -} - /// A set of errors that can occur during parsing multipart streams #[derive(Fail, Debug)] pub enum MultipartError { @@ -953,9 +921,6 @@ mod tests { let resp: HttpResponse = ParseError::Incomplete.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HttpRangeError::InvalidRange.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = CookieParseError::EmptyName.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); @@ -1005,14 +970,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[test] - fn test_range_error() { - let e: HttpRangeError = HttpRangeParseError::InvalidRange.into(); - assert_eq!(e, HttpRangeError::InvalidRange); - let e: HttpRangeError = HttpRangeParseError::NoOverlap.into(); - assert_eq!(e, HttpRangeError::NoOverlap); - } - #[test] fn test_expect_error() { let resp: HttpResponse = ExpectError::Encoding.error_response(); diff --git a/src/fs.rs b/src/fs.rs index 35c78b73..68d6977c 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -20,7 +20,7 @@ use mime_guess::{get_mime_type, guess_mime_type}; use error::Error; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use header; -use http::{ContentEncoding, HttpRange, Method, StatusCode}; +use http::{ContentEncoding, Method, StatusCode}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -63,18 +63,20 @@ impl NamedFile { /// let file = NamedFile::open("foo.txt"); /// ``` pub fn open>(path: P) -> io::Result { - use header::{ContentDisposition, DispositionType, DispositionParam}; + use header::{ContentDisposition, DispositionParam, DispositionType}; let path = path.as_ref().to_path_buf(); // Get the name of the file and use it to construct default Content-Type // and Content-Disposition values - let (content_type, content_disposition) = - { + let (content_type, content_disposition) = { let filename = match path.file_name() { Some(name) => name.to_string_lossy(), - None => return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Provided path has no filename")), + None => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Provided path has no filename", + )) + } }; let ct = guess_mime_type(&path); @@ -84,13 +86,11 @@ impl NamedFile { }; let cd = ContentDisposition { disposition: disposition_type, - parameters: vec![ - DispositionParam::Filename( - header::Charset::Ext("UTF-8".to_owned()), - None, - filename.as_bytes().to_vec(), - ) - ], + parameters: vec![DispositionParam::Filename( + header::Charset::Ext("UTF-8".to_owned()), + None, + filename.as_bytes().to_vec(), + )], }; (ct, cd) }; @@ -276,7 +276,10 @@ impl Responder for NamedFile { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) - .header(header::CONTENT_DISPOSITION, self.content_disposition.to_string()); + .header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); if let Some(current_encoding) = self.encoding { resp.content_encoding(current_encoding); @@ -327,19 +330,20 @@ impl Responder for NamedFile { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) - .header(header::CONTENT_DISPOSITION, self.content_disposition.to_string()); + .header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); if let Some(current_encoding) = self.encoding { resp.content_encoding(current_encoding); } - resp - .if_some(last_modified, |lm, resp| { - resp.set(header::LastModified(lm)); - }) - .if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); + resp.if_some(last_modified, |lm, resp| { + resp.set(header::LastModified(lm)); + }).if_some(etag, |etag, resp| { + resp.set(header::ETag(etag)); + }); resp.header(header::ACCEPT_RANGES, "bytes"); @@ -721,6 +725,101 @@ impl Handler for StaticFiles { } } +/// HTTP Range header representation. +#[derive(Debug, Clone, Copy)] +struct HttpRange { + pub start: u64, + pub length: u64, +} + +static PREFIX: &'static str = "bytes="; +const PREFIX_LEN: usize = 6; + +impl HttpRange { + /// Parses Range HTTP header string as per RFC 2616. + /// + /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). + /// `size` is full size of response (file). + fn parse(header: &str, size: u64) -> Result, ()> { + if header.is_empty() { + return Ok(Vec::new()); + } + if !header.starts_with(PREFIX) { + return Err(()); + } + + let size_sig = size as i64; + let mut no_overlap = false; + + let all_ranges: Vec> = header[PREFIX_LEN..] + .split(',') + .map(|x| x.trim()) + .filter(|x| !x.is_empty()) + .map(|ra| { + let mut start_end_iter = ra.split('-'); + + let start_str = start_end_iter.next().ok_or(())?.trim(); + let end_str = start_end_iter.next().ok_or(())?.trim(); + + if start_str.is_empty() { + // If no start is specified, end specifies the + // range start relative to the end of the file. + let mut length: i64 = try!(end_str.parse().map_err(|_| ())); + + if length > size_sig { + length = size_sig; + } + + Ok(Some(HttpRange { + start: (size_sig - length) as u64, + length: length as u64, + })) + } else { + let start: i64 = start_str.parse().map_err(|_| ())?; + + if start < 0 { + return Err(()); + } + if start >= size_sig { + no_overlap = true; + return Ok(None); + } + + let length = if end_str.is_empty() { + // If no end is specified, range extends to end of the file. + size_sig - start + } else { + let mut end: i64 = end_str.parse().map_err(|_| ())?; + + if start > end { + return Err(()); + } + + if end >= size_sig { + end = size_sig - 1; + } + + end - start + 1 + }; + + Ok(Some(HttpRange { + start: start as u64, + length: length as u64, + })) + } + }) + .collect::>()?; + + let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); + + if no_overlap && ranges.is_empty() { + return Err(()); + } + + Ok(ranges) + } +} + #[cfg(test)] mod tests { use super::*; @@ -816,16 +915,14 @@ mod tests { #[test] fn test_named_file_image_attachment() { - use header::{ContentDisposition, DispositionType, DispositionParam}; + use header::{ContentDisposition, DispositionParam, DispositionType}; let cd = ContentDisposition { disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::Filename( - header::Charset::Ext("UTF-8".to_owned()), - None, - "test.png".as_bytes().to_vec(), - ) - ], + parameters: vec![DispositionParam::Filename( + header::Charset::Ext("UTF-8".to_owned()), + None, + "test.png".as_bytes().to_vec(), + )], }; let mut file = NamedFile::open("tests/test.png") .unwrap() @@ -1241,4 +1338,280 @@ mod tests { let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::OK); } + + struct T(&'static str, u64, Vec); + + #[test] + fn test_parse() { + let tests = vec![ + T("", 0, vec![]), + T("", 1000, vec![]), + T("foo", 0, vec![]), + T("bytes=", 0, vec![]), + T("bytes=7", 10, vec![]), + T("bytes= 7 ", 10, vec![]), + T("bytes=1-", 0, vec![]), + T("bytes=5-4", 10, vec![]), + T("bytes=0-2,5-4", 10, vec![]), + T("bytes=2-5,4-3", 10, vec![]), + T("bytes=--5,4--3", 10, vec![]), + T("bytes=A-", 10, vec![]), + T("bytes=A- ", 10, vec![]), + T("bytes=A-Z", 10, vec![]), + T("bytes= -Z", 10, vec![]), + T("bytes=5-Z", 10, vec![]), + T("bytes=Ran-dom, garbage", 10, vec![]), + T("bytes=0x01-0x02", 10, vec![]), + T("bytes= ", 10, vec![]), + T("bytes= , , , ", 10, vec![]), + T( + "bytes=0-9", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=0-", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=5-", + 10, + vec![HttpRange { + start: 5, + length: 5, + }], + ), + T( + "bytes=0-20", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=15-,0-5", + 10, + vec![HttpRange { + start: 0, + length: 6, + }], + ), + T( + "bytes=1-2,5-", + 10, + vec![ + HttpRange { + start: 1, + length: 2, + }, + HttpRange { + start: 5, + length: 5, + }, + ], + ), + T( + "bytes=-2 , 7-", + 11, + vec![ + HttpRange { + start: 9, + length: 2, + }, + HttpRange { + start: 7, + length: 4, + }, + ], + ), + T( + "bytes=0-0 ,2-2, 7-", + 11, + vec![ + HttpRange { + start: 0, + length: 1, + }, + HttpRange { + start: 2, + length: 1, + }, + HttpRange { + start: 7, + length: 4, + }, + ], + ), + T( + "bytes=-5", + 10, + vec![HttpRange { + start: 5, + length: 5, + }], + ), + T( + "bytes=-15", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=0-499", + 10000, + vec![HttpRange { + start: 0, + length: 500, + }], + ), + T( + "bytes=500-999", + 10000, + vec![HttpRange { + start: 500, + length: 500, + }], + ), + T( + "bytes=-500", + 10000, + vec![HttpRange { + start: 9500, + length: 500, + }], + ), + T( + "bytes=9500-", + 10000, + vec![HttpRange { + start: 9500, + length: 500, + }], + ), + T( + "bytes=0-0,-1", + 10000, + vec![ + HttpRange { + start: 0, + length: 1, + }, + HttpRange { + start: 9999, + length: 1, + }, + ], + ), + T( + "bytes=500-600,601-999", + 10000, + vec![ + HttpRange { + start: 500, + length: 101, + }, + HttpRange { + start: 601, + length: 399, + }, + ], + ), + T( + "bytes=500-700,601-999", + 10000, + vec![ + HttpRange { + start: 500, + length: 201, + }, + HttpRange { + start: 601, + length: 399, + }, + ], + ), + // Match Apache laxity: + T( + "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", + 11, + vec![ + HttpRange { + start: 1, + length: 2, + }, + HttpRange { + start: 4, + length: 2, + }, + HttpRange { + start: 7, + length: 2, + }, + ], + ), + ]; + + for t in tests { + let header = t.0; + let size = t.1; + let expected = t.2; + + let res = HttpRange::parse(header, size); + + if res.is_err() { + if expected.is_empty() { + continue; + } else { + assert!( + false, + "parse({}, {}) returned error {:?}", + header, + size, + res.unwrap_err() + ); + } + } + + let got = res.unwrap(); + + if got.len() != expected.len() { + assert!( + false, + "len(parseRange({}, {})) = {}, want {}", + header, + size, + got.len(), + expected.len() + ); + continue; + } + + for i in 0..expected.len() { + if got[i].start != expected[i].start { + assert!( + false, + "parseRange({}, {})[{}].start = {}, want {}", + header, size, i, got[i].start, expected[i].start + ) + } + if got[i].length != expected[i].length { + assert!( + false, + "parseRange({}, {})[{}].length = {}, want {}", + header, size, i, got[i].length, expected[i].length + ) + } + } + } + } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 1ce04b47..cac82f04 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -5,15 +5,13 @@ use encoding::types::{DecoderTrap, Encoding}; use encoding::EncodingRef; use futures::{Async, Future, Poll, Stream}; use http::{header, HeaderMap}; -use http_range::HttpRange; use mime::Mime; use serde::de::DeserializeOwned; use serde_urlencoded; use std::str; use error::{ - ContentTypeError, HttpRangeError, ParseError, PayloadError, ReadlinesError, - UrlencodedError, + ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, }; use header::Header; use json::JsonBody; @@ -95,17 +93,6 @@ pub trait HttpMessage { } } - /// Parses Range HTTP header string as per RFC 2616. - /// `size` is full size of response (file). - fn range(&self, size: u64) -> Result, HttpRangeError> { - if let Some(range) = self.headers().get(header::RANGE) { - HttpRange::parse(unsafe { str::from_utf8_unchecked(range.as_bytes()) }, size) - .map_err(|e| e.into()) - } else { - Ok(Vec::new()) - } - } - /// Load http message body. /// /// By default only 256Kb payload reads to a memory, then @@ -637,22 +624,6 @@ mod tests { ); } - #[test] - fn test_no_request_range_header() { - let req = HttpRequest::default(); - let ranges = req.range(100).unwrap(); - assert!(ranges.is_empty()); - } - - #[test] - fn test_request_range_header() { - let req = TestRequest::with_header(header::RANGE, "bytes=0-4").finish(); - let ranges = req.range(100).unwrap(); - assert_eq!(ranges.len(), 1); - assert_eq!(ranges[0].start, 0); - assert_eq!(ranges[0].length, 5); - } - #[test] fn test_chunked() { let req = HttpRequest::default(); diff --git a/src/lib.rs b/src/lib.rs index b3e143a5..6e8cc246 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,7 +105,6 @@ extern crate futures; extern crate cookie; extern crate futures_cpupool; extern crate http as modhttp; -extern crate http_range; extern crate httparse; extern crate language_tags; extern crate libc; @@ -258,7 +257,6 @@ pub mod http { pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri}; pub use cookie::{Cookie, CookieBuilder}; - pub use http_range::HttpRange; pub use helpers::NormalizePath; From c3f295182fdc99051d575d406fc436a6f721fb31 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 08:54:30 +0600 Subject: [PATCH 0388/1635] use HashMap for HttpRequest::query() --- CHANGES.md | 2 ++ src/httprequest.rs | 16 +++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index be17ef3d..b8ca7d47 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -41,6 +41,8 @@ * port `Extensions` type from http create, we dont need `Send + Sync` +* `HttpRequest::query()` returns `&HashMap` + ### Removed diff --git a/src/httprequest.rs b/src/httprequest.rs index dded35f6..4d6ed87e 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,5 +1,6 @@ //! HTTP Request message related code. #![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))] +use std::collections::HashMap; use std::net::SocketAddr; use std::rc::Rc; use std::{cmp, fmt, io, mem, str}; @@ -47,7 +48,7 @@ pub struct HttpInnerMessage { resource: RouterResource, } -struct Query(Params<'static>); +struct Query(HashMap); struct Cookies(Vec>); #[derive(Debug, Copy, Clone, PartialEq)] @@ -382,18 +383,15 @@ impl HttpRequest { #[doc(hidden)] /// Get a reference to the Params object. /// Params is a container for url query parameters. - pub fn query<'a>(&'a self) -> &'a Params { + pub fn query(&self) -> &HashMap { if self.extensions().get::().is_none() { - let mut params: Params<'a> = Params::new(); + let mut query = HashMap::new(); for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { - params.add(key, val); + query.insert(key.as_ref().to_string(), val.to_string()); } - let params: Params<'static> = unsafe { mem::transmute(params) }; - self.as_mut().extensions.insert(Query(params)); + self.as_mut().extensions.insert(Query(query)); } - let params: &Params<'a> = - unsafe { mem::transmute(&self.extensions().get::().unwrap().0) }; - params + &self.extensions().get::().unwrap().0 } /// The query string in the URL. From 38fe8bebec6bdf00eaa3ad16c3dc81dcb07500e4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 08:57:51 +0600 Subject: [PATCH 0389/1635] fix doc string --- src/httprequest.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 4d6ed87e..080e4436 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -381,8 +381,7 @@ impl HttpRequest { } #[doc(hidden)] - /// Get a reference to the Params object. - /// Params is a container for url query parameters. + /// url query parameters. pub fn query(&self) -> &HashMap { if self.extensions().get::().is_none() { let mut query = HashMap::new(); From e1db47d550138dff8e496bd7f81debeaef20c438 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Jun 2018 23:51:20 +0600 Subject: [PATCH 0390/1635] refactor server settings --- src/server/settings.rs | 71 ++++++++++++++++++------------------------ src/server/srv.rs | 9 ++++-- 2 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/server/settings.rs b/src/server/settings.rs index 59917b87..c4037b33 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -1,11 +1,11 @@ -use bytes::BytesMut; -use futures_cpupool::{Builder, CpuPool}; -use http::StatusCode; use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; use std::fmt::Write; use std::rc::Rc; -use std::sync::Arc; use std::{fmt, mem, net}; + +use bytes::BytesMut; +use futures_cpupool::{Builder, CpuPool}; +use http::StatusCode; use time; use super::channel::Node; @@ -20,54 +20,22 @@ pub struct ServerSettings { addr: Option, secure: bool, host: String, - cpu_pool: Arc, + cpu_pool: UnsafeCell>, responses: Rc>, } -unsafe impl Sync for ServerSettings {} -unsafe impl Send for ServerSettings {} - impl Clone for ServerSettings { fn clone(&self) -> Self { ServerSettings { addr: self.addr, secure: self.secure, host: self.host.clone(), - cpu_pool: self.cpu_pool.clone(), + cpu_pool: UnsafeCell::new(None), responses: HttpResponsePool::pool(), } } } -struct InnerCpuPool { - cpu_pool: UnsafeCell>, -} - -impl fmt::Debug for InnerCpuPool { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "CpuPool") - } -} - -impl InnerCpuPool { - fn new() -> Self { - InnerCpuPool { - cpu_pool: UnsafeCell::new(None), - } - } - fn cpu_pool(&self) -> &CpuPool { - unsafe { - let val = &mut *self.cpu_pool.get(); - if val.is_none() { - *val = Some(Builder::new().create()); - } - val.as_ref().unwrap() - } - } -} - -unsafe impl Sync for InnerCpuPool {} - impl Default for ServerSettings { fn default() -> Self { ServerSettings { @@ -75,7 +43,7 @@ impl Default for ServerSettings { secure: false, host: "localhost:8080".to_owned(), responses: HttpResponsePool::pool(), - cpu_pool: Arc::new(InnerCpuPool::new()), + cpu_pool: UnsafeCell::new(None), } } } @@ -92,7 +60,7 @@ impl ServerSettings { } else { "localhost".to_owned() }; - let cpu_pool = Arc::new(InnerCpuPool::new()); + let cpu_pool = UnsafeCell::new(None); let responses = HttpResponsePool::pool(); ServerSettings { addr, @@ -103,6 +71,21 @@ impl ServerSettings { } } + pub(crate) fn parts(&self) -> (Option, String, bool) { + (self.addr, self.host.clone(), self.secure) + } + + pub(crate) fn from_parts(parts: (Option, String, bool)) -> Self { + let (addr, host, secure) = parts; + ServerSettings { + addr, + host, + secure, + cpu_pool: UnsafeCell::new(None), + responses: HttpResponsePool::pool(), + } + } + /// Returns the socket address of the local half of this TCP connection pub fn local_addr(&self) -> Option { self.addr @@ -120,7 +103,13 @@ impl ServerSettings { /// Returns default `CpuPool` for server pub fn cpu_pool(&self) -> &CpuPool { - self.cpu_pool.cpu_pool() + unsafe { + let val = &mut *self.cpu_pool.get(); + if val.is_none() { + *val = Some(Builder::new().pool_size(2).create()); + } + val.as_ref().unwrap() + } } #[inline] diff --git a/src/server/srv.rs b/src/server/srv.rs index 0e51e2dc..89f57b89 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -351,13 +351,15 @@ where // start workers let mut workers = Vec::new(); for idx in 0..self.threads { - let s = settings.clone(); let (tx, rx) = mpsc::unbounded::>(); let ka = self.keep_alive; let socks = sockets.clone(); let factory = Arc::clone(&self.factory); + let parts = settings.parts(); + let addr = Arbiter::start(move |ctx: &mut Context<_>| { + let s = ServerSettings::from_parts(parts); let apps: Vec<_> = (*factory)() .into_iter() .map(|h| h.into_handler(s.clone())) @@ -642,10 +644,11 @@ impl StreamHandler for HttpServer { let ka = self.keep_alive; let factory = Arc::clone(&self.factory); - let settings = - ServerSettings::new(Some(socks[0].addr), &self.host, false); + let host = self.host.clone(); + let addr = socks[0].addr; let addr = Arbiter::start(move |ctx: &mut Context<_>| { + let settings = ServerSettings::new(Some(addr), &host, false); let apps: Vec<_> = (*factory)() .into_iter() .map(|h| h.into_handler(settings.clone())) From ea118edf5663aec6ff2a1930e3712ad4966d647d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 00:01:41 +0600 Subject: [PATCH 0391/1635] do not use references in ConnectionInfo --- src/httprequest.rs | 16 ++++++---------- src/info.rs | 27 ++++++++++++++------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 080e4436..50154432 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -43,13 +43,13 @@ pub struct HttpInnerMessage { pub params: Params<'static>, pub addr: Option, pub payload: Option, - pub info: Option>, pub prefix: u16, resource: RouterResource, } struct Query(HashMap); struct Cookies(Vec>); +struct Info(ConnectionInfo); #[derive(Debug, Copy, Clone, PartialEq)] enum RouterResource { @@ -69,7 +69,6 @@ impl Default for HttpInnerMessage { addr: None, payload: None, extensions: Extensions::new(), - info: None, prefix: 0, resource: RouterResource::Notset, } @@ -89,7 +88,6 @@ impl HttpInnerMessage { self.extensions.clear(); self.params.clear(); self.addr = None; - self.info = None; self.flags = MessageFlags::empty(); self.payload = None; self.prefix = 0; @@ -122,7 +120,6 @@ impl HttpRequest<()> { params: Params::new(), extensions: Extensions::new(), addr: None, - info: None, prefix: 0, flags: MessageFlags::empty(), resource: RouterResource::Notset, @@ -280,12 +277,12 @@ impl HttpRequest { /// Get *ConnectionInfo* for correct request. pub fn connection_info(&self) -> &ConnectionInfo { - if self.as_ref().info.is_none() { - let info: ConnectionInfo<'static> = - unsafe { mem::transmute(ConnectionInfo::new(self)) }; - self.as_mut().info = Some(info); + if self.extensions().get::().is_none() { + self.as_mut() + .extensions + .insert(Info(ConnectionInfo::new(self))); } - self.as_ref().info.as_ref().unwrap() + &self.extensions().get::().unwrap().0 } /// Generate url for named resource @@ -380,7 +377,6 @@ impl HttpRequest { self.as_mut().addr = addr; } - #[doc(hidden)] /// url query parameters. pub fn query(&self) -> &HashMap { if self.extensions().get::().is_none() { diff --git a/src/info.rs b/src/info.rs index 05d35f47..dad10b64 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,24 +1,25 @@ +use std::str::FromStr; + use http::header::{self, HeaderName}; use httpmessage::HttpMessage; use httprequest::HttpRequest; -use std::str::FromStr; const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST"; const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; /// `HttpRequest` connection information -pub struct ConnectionInfo<'a> { - scheme: &'a str, - host: &'a str, - remote: Option<&'a str>, +pub struct ConnectionInfo { + scheme: String, + host: String, + remote: Option, peer: Option, } -impl<'a> ConnectionInfo<'a> { +impl ConnectionInfo { /// Create *ConnectionInfo* instance for a request. #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - pub fn new(req: &'a HttpRequest) -> ConnectionInfo<'a> { + pub fn new(req: &HttpRequest) -> ConnectionInfo { let mut host = None; let mut scheme = None; let mut remote = None; @@ -115,9 +116,9 @@ impl<'a> ConnectionInfo<'a> { } ConnectionInfo { - scheme: scheme.unwrap_or("http"), - host: host.unwrap_or("localhost"), - remote, + scheme: scheme.unwrap_or("http").to_owned(), + host: host.unwrap_or("localhost").to_owned(), + remote: remote.map(|s| s.to_owned()), peer, } } @@ -131,7 +132,7 @@ impl<'a> ConnectionInfo<'a> { /// - Uri #[inline] pub fn scheme(&self) -> &str { - self.scheme + &self.scheme } /// Hostname of the request. @@ -144,7 +145,7 @@ impl<'a> ConnectionInfo<'a> { /// - Uri /// - Server hostname pub fn host(&self) -> &str { - self.host + &self.host } /// Remote IP of client initiated HTTP request. @@ -156,7 +157,7 @@ impl<'a> ConnectionInfo<'a> { /// - peer name of opened socket #[inline] pub fn remote(&self) -> Option<&str> { - if let Some(r) = self.remote { + if let Some(ref r) = self.remote { Some(r) } else if let Some(ref peer) = self.peer { Some(peer) From 9d114d785e2403071ec08f24c7e2436f3a08593a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 00:19:07 +0600 Subject: [PATCH 0392/1635] remove Clone from ExtractorConfig --- src/route.rs | 8 ++++---- src/with.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/route.rs b/src/route.rs index 44ac8280..524b66ef 100644 --- a/src/route.rs +++ b/src/route.rs @@ -170,8 +170,8 @@ impl Route { R: Responder + 'static, T: FromRequest + 'static, { - let cfg = ExtractorConfig::default(); - self.h(With::new(handler, Clone::clone(&cfg))); + let cfg = ExtractorConfig::::default(); + self.h(With::new(handler, cfg.clone())); cfg } @@ -212,8 +212,8 @@ impl Route { E: Into + 'static, T: FromRequest + 'static, { - let cfg = ExtractorConfig::default(); - self.h(WithAsync::new(handler, Clone::clone(&cfg))); + let cfg = ExtractorConfig::::default(); + self.h(WithAsync::new(handler, cfg.clone())); cfg } } diff --git a/src/with.rs b/src/with.rs index c32f0a3b..4cb1546a 100644 --- a/src/with.rs +++ b/src/with.rs @@ -77,8 +77,8 @@ impl> Default for ExtractorConfig { } } -impl> Clone for ExtractorConfig { - fn clone(&self) -> Self { +impl> ExtractorConfig { + pub(crate) fn clone(&self) -> Self { ExtractorConfig { cfg: Rc::clone(&self.cfg), } From daed502ee5d055203c45a7eeea4e163a5b520549 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 01:03:07 +0600 Subject: [PATCH 0393/1635] make mut api private --- src/httprequest.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 50154432..e201d9cb 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -265,7 +265,7 @@ impl HttpRequest { /// ///This is intended to be used by middleware. #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { + pub(crate) fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.as_mut().headers } @@ -450,7 +450,7 @@ impl HttpRequest { /// Get mutable reference to request's Params. #[inline] - pub fn match_info_mut(&mut self) -> &mut Params { + pub(crate) fn match_info_mut(&mut self) -> &mut Params { unsafe { mem::transmute(&mut self.as_mut().params) } } From f0f19c14d28ab5efb2b1ba7fe4b7093adf104572 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 01:03:47 +0600 Subject: [PATCH 0394/1635] remove wsclient --- Cargo.toml | 1 - tools/wsload/Cargo.toml | 21 --- tools/wsload/src/wsclient.rs | 320 ----------------------------------- 3 files changed, 342 deletions(-) delete mode 100644 tools/wsload/Cargo.toml delete mode 100644 tools/wsload/src/wsclient.rs diff --git a/Cargo.toml b/Cargo.toml index b8157421..c0a85b89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,5 +119,4 @@ codegen-units = 1 [workspace] members = [ "./", - "tools/wsload/", ] diff --git a/tools/wsload/Cargo.toml b/tools/wsload/Cargo.toml deleted file mode 100644 index ff782817..00000000 --- a/tools/wsload/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "wsclient" -version = "0.1.0" -authors = ["Nikolay Kim "] -workspace = "../.." - -[[bin]] -name = "wsclient" -path = "src/wsclient.rs" - -[dependencies] -env_logger = "*" -futures = "0.1" -clap = "2" -url = "1.6" -rand = "0.4" -time = "*" -num_cpus = "1" -tokio-core = "0.1" -actix = "0.5" -actix-web = { path="../../" } diff --git a/tools/wsload/src/wsclient.rs b/tools/wsload/src/wsclient.rs deleted file mode 100644 index f28156b8..00000000 --- a/tools/wsload/src/wsclient.rs +++ /dev/null @@ -1,320 +0,0 @@ -//! Simple websocket client. - -#![allow(unused_variables)] -extern crate actix; -extern crate actix_web; -extern crate clap; -extern crate env_logger; -extern crate futures; -extern crate num_cpus; -extern crate rand; -extern crate time; -extern crate tokio_core; -extern crate url; - -use futures::Future; -use rand::{thread_rng, Rng}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::time::Duration; - -use actix::prelude::*; -use actix_web::ws; - -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); - - let matches = clap::App::new("ws tool") - .version("0.1") - .about("Applies load to websocket server") - .args_from_usage( - " 'WebSocket url' - [bin]... -b, 'use binary frames' - -s, --size=[NUMBER] 'size of PUBLISH packet payload to send in KB' - -w, --warm-up=[SECONDS] 'seconds before counter values are considered for reporting' - -r, --sample-rate=[SECONDS] 'seconds between average reports' - -c, --concurrency=[NUMBER] 'number of websocket connections to open and use concurrently for sending' - -t, --threads=[NUMBER] 'number of threads to use' - --max-payload=[NUMBER] 'max size of payload before reconnect KB'", - ) - .get_matches(); - - let bin: bool = matches.value_of("bin").is_some(); - let ws_url = matches.value_of("url").unwrap().to_owned(); - let _ = url::Url::parse(&ws_url).map_err(|e| { - println!("Invalid url: {}", ws_url); - std::process::exit(0); - }); - - let threads = parse_u64_default(matches.value_of("threads"), num_cpus::get() as u64); - let concurrency = parse_u64_default(matches.value_of("concurrency"), 1); - let payload_size: usize = match matches.value_of("size") { - Some(s) => parse_u64_default(Some(s), 1) as usize * 1024, - None => 1024, - }; - let max_payload_size: usize = match matches.value_of("max-payload") { - Some(s) => parse_u64_default(Some(s), 0) as usize * 1024, - None => 0, - }; - let warmup_seconds = parse_u64_default(matches.value_of("warm-up"), 2) as u64; - let sample_rate = parse_u64_default(matches.value_of("sample-rate"), 1) as usize; - - let perf_counters = Arc::new(PerfCounters::new()); - let payload = Arc::new( - thread_rng() - .gen_ascii_chars() - .take(payload_size) - .collect::(), - ); - - let sys = actix::System::new("ws-client"); - - let _: () = Perf { - counters: perf_counters.clone(), - payload: payload.len(), - sample_rate_secs: sample_rate, - }.start(); - - for t in 0..threads { - let pl = payload.clone(); - let ws = ws_url.clone(); - let perf = perf_counters.clone(); - let addr = Arbiter::new(format!("test {}", t)); - - addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> { - for _ in 0..concurrency { - let pl2 = pl.clone(); - let perf2 = perf.clone(); - let ws2 = ws.clone(); - - Arbiter::handle().spawn( - ws::Client::new(&ws) - .write_buffer_capacity(0) - .connect() - .map_err(|e| { - println!("Error: {}", e); - //Arbiter::system().do_send(actix::msgs::SystemExit(0)); - () - }) - .map(move |(reader, writer)| { - let addr: Addr = ChatClient::create(move |ctx| { - ChatClient::add_stream(reader, ctx); - ChatClient { - url: ws2, - conn: writer, - payload: pl2, - bin: bin, - ts: time::precise_time_ns(), - perf_counters: perf2, - sent: 0, - max_payload_size: max_payload_size, - } - }); - }), - ); - } - Ok(()) - })); - } - - let res = sys.run(); -} - -fn parse_u64_default(input: Option<&str>, default: u64) -> u64 { - input - .map(|v| v.parse().expect(&format!("not a valid number: {}", v))) - .unwrap_or(default) -} - -struct Perf { - counters: Arc, - payload: usize, - sample_rate_secs: usize, -} - -impl Actor for Perf { - type Context = Context; - - fn started(&mut self, ctx: &mut Context) { - self.sample_rate(ctx); - } -} - -impl Perf { - fn sample_rate(&self, ctx: &mut Context) { - ctx.run_later( - Duration::new(self.sample_rate_secs as u64, 0), - |act, ctx| { - let req_count = act.counters.pull_request_count(); - if req_count != 0 { - let conns = act.counters.pull_connections_count(); - let latency = act.counters.pull_latency_ns(); - let latency_max = act.counters.pull_latency_max_ns(); - println!( - "rate: {}, conns: {}, throughput: {:?} kb, latency: {}, latency max: {}", - req_count / act.sample_rate_secs, - conns / act.sample_rate_secs, - (((req_count * act.payload) as f64) / 1024.0) - / act.sample_rate_secs as f64, - time::Duration::nanoseconds((latency / req_count as u64) as i64), - time::Duration::nanoseconds(latency_max as i64) - ); - } - - act.sample_rate(ctx); - }, - ); - } -} - -struct ChatClient { - url: String, - conn: ws::ClientWriter, - payload: Arc, - ts: u64, - bin: bool, - perf_counters: Arc, - sent: usize, - max_payload_size: usize, -} - -impl Actor for ChatClient { - type Context = Context; - - fn started(&mut self, ctx: &mut Context) { - self.send_text(); - self.perf_counters.register_connection(); - } -} - -impl ChatClient { - fn send_text(&mut self) -> bool { - self.sent += self.payload.len(); - - if self.max_payload_size > 0 && self.sent > self.max_payload_size { - let ws = self.url.clone(); - let pl = self.payload.clone(); - let bin = self.bin; - let perf_counters = self.perf_counters.clone(); - let max_payload_size = self.max_payload_size; - - Arbiter::handle().spawn( - ws::Client::new(&self.url) - .connect() - .map_err(|e| { - println!("Error: {}", e); - Arbiter::system().do_send(actix::msgs::SystemExit(0)); - () - }) - .map(move |(reader, writer)| { - let addr: Addr = ChatClient::create(move |ctx| { - ChatClient::add_stream(reader, ctx); - ChatClient { - url: ws, - conn: writer, - payload: pl, - bin: bin, - ts: time::precise_time_ns(), - perf_counters: perf_counters, - sent: 0, - max_payload_size: max_payload_size, - } - }); - }), - ); - false - } else { - self.ts = time::precise_time_ns(); - if self.bin { - self.conn.binary(&self.payload); - } else { - self.conn.text(&self.payload); - } - true - } - } -} - -/// Handle server websocket messages -impl StreamHandler for ChatClient { - fn finished(&mut self, ctx: &mut Context) { - ctx.stop() - } - - fn handle(&mut self, msg: ws::Message, ctx: &mut Context) { - match msg { - ws::Message::Text(txt) => { - if txt == self.payload.as_ref().as_str() { - self.perf_counters.register_request(); - self.perf_counters - .register_latency(time::precise_time_ns() - self.ts); - if !self.send_text() { - ctx.stop(); - } - } else { - println!("not equal"); - } - } - _ => (), - } - } -} - -pub struct PerfCounters { - req: AtomicUsize, - conn: AtomicUsize, - lat: AtomicUsize, - lat_max: AtomicUsize, -} - -impl PerfCounters { - pub fn new() -> PerfCounters { - PerfCounters { - req: AtomicUsize::new(0), - conn: AtomicUsize::new(0), - lat: AtomicUsize::new(0), - lat_max: AtomicUsize::new(0), - } - } - - pub fn pull_request_count(&self) -> usize { - self.req.swap(0, Ordering::SeqCst) - } - - pub fn pull_connections_count(&self) -> usize { - self.conn.swap(0, Ordering::SeqCst) - } - - pub fn pull_latency_ns(&self) -> u64 { - self.lat.swap(0, Ordering::SeqCst) as u64 - } - - pub fn pull_latency_max_ns(&self) -> u64 { - self.lat_max.swap(0, Ordering::SeqCst) as u64 - } - - pub fn register_request(&self) { - self.req.fetch_add(1, Ordering::SeqCst); - } - - pub fn register_connection(&self) { - self.conn.fetch_add(1, Ordering::SeqCst); - } - - pub fn register_latency(&self, nanos: u64) { - let nanos = nanos as usize; - self.lat.fetch_add(nanos, Ordering::SeqCst); - loop { - let current = self.lat_max.load(Ordering::SeqCst); - if current >= nanos - || self - .lat_max - .compare_and_swap(current, nanos, Ordering::SeqCst) - == current - { - break; - } - } - } -} From 6ec83526122b94fa3afd2a832b98b8ab061ead07 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 01:05:02 +0600 Subject: [PATCH 0395/1635] method only for tests --- src/httprequest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index e201d9cb..519e085f 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -264,7 +264,7 @@ impl HttpRequest { ///Returns mutable Request's headers. /// ///This is intended to be used by middleware. - #[inline] + #[cfg(test)] pub(crate) fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.as_mut().headers } From a5bbc455c05ca3b7b978937367e32b9ffef49b95 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 04:41:41 +0600 Subject: [PATCH 0396/1635] cleanup mut transform --- src/fs.rs | 6 +++--- src/httprequest.rs | 24 ++++++++++-------------- src/pipeline.rs | 2 +- src/server/h1.rs | 2 +- src/server/h1decoder.rs | 2 +- src/server/h2.rs | 2 +- src/server/helpers.rs | 9 +++------ 7 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 68d6977c..9962f68b 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1251,16 +1251,16 @@ mod tests { #[test] fn test_redirect_to_index_nested() { - let mut st = StaticFiles::new(".").index_file("Cargo.toml"); + let mut st = StaticFiles::new(".").index_file("mod.rs"); let mut req = HttpRequest::default(); - req.match_info_mut().add("tail", "tools/wsload"); + req.match_info_mut().add("tail", "src/client"); let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers().get(header::LOCATION).unwrap(), - "/tools/wsload/Cargo.toml" + "/src/client/mod.rs" ); } diff --git a/src/httprequest.rs b/src/httprequest.rs index 519e085f..cf6869f2 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -170,23 +170,16 @@ impl HttpRequest { /// get mutable reference for inner message /// mutable reference should not be returned as result for request's method - #[inline(always)] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - pub(crate) fn as_mut(&self) -> &mut HttpInnerMessage { + #[inline] + pub(crate) fn as_mut(&mut self) -> &mut HttpInnerMessage { self.0.get_mut() } - #[inline(always)] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] + #[inline] fn as_ref(&self) -> &HttpInnerMessage { self.0.get_ref() } - #[inline] - pub(crate) fn get_inner(&mut self) -> &mut HttpInnerMessage { - self.as_mut() - } - /// Shared application state #[inline] pub fn state(&self) -> &S { @@ -278,7 +271,8 @@ impl HttpRequest { /// Get *ConnectionInfo* for correct request. pub fn connection_info(&self) -> &ConnectionInfo { if self.extensions().get::().is_none() { - self.as_mut() + let mut req = self.clone(); + req.as_mut() .extensions .insert(Info(ConnectionInfo::new(self))); } @@ -384,7 +378,8 @@ impl HttpRequest { for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { query.insert(key.as_ref().to_string(), val.to_string()); } - self.as_mut().extensions.insert(Query(query)); + let mut req = self.clone(); + req.as_mut().extensions.insert(Query(query)); } &self.extensions().get::().unwrap().0 } @@ -404,7 +399,8 @@ impl HttpRequest { /// Load request cookies. pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { if self.extensions().get::().is_none() { - let msg = self.as_mut(); + let mut req = self.clone(); + let msg = req.as_mut(); let mut cookies = Vec::new(); for hdr in msg.headers.get_all(header::COOKIE) { let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; @@ -479,7 +475,7 @@ impl HttpRequest { } #[cfg(test)] - pub(crate) fn payload(&self) -> &Payload { + pub(crate) fn payload(&mut self) -> &Payload { let msg = self.as_mut(); if msg.payload.is_none() { msg.payload = Some(Payload::empty()); diff --git a/src/pipeline.rs b/src/pipeline.rs index 8be7ee83..e5eb51d5 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -487,7 +487,7 @@ impl ProcessResponse { self.resp.content_encoding().unwrap_or(info.encoding); let result = match io.start( - info.req_mut().get_inner(), + info.req_mut().as_mut(), &mut self.resp, encoding, ) { diff --git a/src/server/h1.rs b/src/server/h1.rs index e5fa2fe9..689d64dc 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -306,7 +306,7 @@ where pub fn parse(&mut self) { 'outer: loop { match self.decoder.decode(&mut self.buf, &self.settings) { - Ok(Some(Message::Message { msg, payload })) => { + Ok(Some(Message::Message { mut msg, payload })) => { self.flags.insert(Flags::STARTED); if payload { diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 976a079e..77da36af 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -120,7 +120,7 @@ impl H1Decoder { let slice = buf.split_to(len).freeze(); // convert headers - let msg = settings.get_http_message(); + let mut msg = settings.get_http_message(); { let msg_mut = msg.get_mut(); msg_mut diff --git a/src/server/h2.rs b/src/server/h2.rs index a73cc599..e194bc7d 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -307,7 +307,7 @@ impl Entry { // Payload and Content-Encoding let (psender, payload) = Payload::new(false); - let msg = settings.get_http_message(); + let mut msg = settings.get_http_message(); msg.get_mut().url = Url::new(parts.uri); msg.get_mut().method = parts.method; msg.get_mut().version = parts.version; diff --git a/src/server/helpers.rs b/src/server/helpers.rs index f447a6ca..cf497edd 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -74,16 +74,13 @@ impl SharedHttpInnerMessage { SharedHttpInnerMessage(Some(msg), Some(pool)) } - #[inline(always)] - #[allow(mutable_transmutes)] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - pub fn get_mut(&self) -> &mut HttpInnerMessage { + #[inline] + pub fn get_mut(&mut self) -> &mut HttpInnerMessage { let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref(); unsafe { &mut *(r as *const _ as *mut _) } } - #[inline(always)] - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] + #[inline] pub fn get_ref(&self) -> &HttpInnerMessage { self.0.as_ref().unwrap() } From ef15646bd7036e9a666c1fd080163068f7110e19 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 04:56:18 +0600 Subject: [PATCH 0397/1635] refactor edfault cpu pool --- Cargo.toml | 1 + src/fs.rs | 28 ++++------------------------ src/lib.rs | 1 + src/server/mod.rs | 2 +- src/server/settings.rs | 28 +++++++++++++++++++++++++--- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0a85b89..c1195777 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,7 @@ time = "0.1" encoding = "0.2" language-tags = "0.2" lazy_static = "1.0" +parking_lot = "0.5" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.10", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } diff --git a/src/fs.rs b/src/fs.rs index 9962f68b..9d5659f7 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -4,9 +4,8 @@ use std::fs::{DirEntry, File, Metadata}; use std::io::{Read, Seek}; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; -use std::sync::Mutex; use std::time::{SystemTime, UNIX_EPOCH}; -use std::{cmp, env, io}; +use std::{cmp, io}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; @@ -25,9 +24,7 @@ use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use param::FromParam; - -/// Env variable for default cpu pool size for `StaticFiles` -const ENV_CPU_POOL_VAR: &str = "ACTIX_FS_POOL"; +use server::settings::DEFAULT_CPUPOOL; /// Return the MIME type associated with a filename extension (case-insensitive). /// If `ext` is empty or no associated type for the extension was found, returns @@ -572,32 +569,15 @@ pub struct StaticFiles { _follow_symlinks: bool, } -lazy_static! { - static ref DEFAULT_CPUPOOL: Mutex = { - let default = match env::var(ENV_CPU_POOL_VAR) { - Ok(val) => { - if let Ok(val) = val.parse() { - val - } else { - error!("Can not parse ACTIX_FS_POOL value"); - 20 - } - } - Err(_) => 20, - }; - Mutex::new(CpuPool::new(default)) - }; -} - impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory. /// /// `StaticFile` uses `CpuPool` for blocking filesystem operations. /// By default pool with 20 threads is used. - /// Pool size can be changed by setting ACTIX_FS_POOL environment variable. + /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. pub fn new>(dir: T) -> StaticFiles { // use default CpuPool - let pool = { DEFAULT_CPUPOOL.lock().unwrap().clone() }; + let pool = { DEFAULT_CPUPOOL.lock().clone() }; StaticFiles::with_pool(dir, pool) } diff --git a/src/lib.rs b/src/lib.rs index 6e8cc246..dc956ac9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,6 +112,7 @@ extern crate mime; extern crate mime_guess; extern crate mio; extern crate net2; +extern crate parking_lot; extern crate rand; extern crate slab; extern crate tokio; diff --git a/src/server/mod.rs b/src/server/mod.rs index 978454ab..82ba24e2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -15,7 +15,7 @@ mod h1writer; mod h2; mod h2writer; pub(crate) mod helpers; -mod settings; +pub(crate) mod settings; pub(crate) mod shared; mod srv; pub(crate) mod utils; diff --git a/src/server/settings.rs b/src/server/settings.rs index c4037b33..7ea08c9d 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -1,11 +1,12 @@ use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; use std::fmt::Write; use std::rc::Rc; -use std::{fmt, mem, net}; +use std::{env, fmt, mem, net}; use bytes::BytesMut; -use futures_cpupool::{Builder, CpuPool}; +use futures_cpupool::CpuPool; use http::StatusCode; +use parking_lot::Mutex; use time; use super::channel::Node; @@ -15,6 +16,26 @@ use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; +/// Env variable for default cpu pool size +const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL"; + +lazy_static! { + pub(crate) static ref DEFAULT_CPUPOOL: Mutex = { + let default = match env::var(ENV_CPU_POOL_VAR) { + Ok(val) => { + if let Ok(val) = val.parse() { + val + } else { + error!("Can not parse ACTIX_CPU_POOL value"); + 20 + } + } + Err(_) => 20, + }; + Mutex::new(CpuPool::new(default)) + }; +} + /// Various server settings pub struct ServerSettings { addr: Option, @@ -106,7 +127,8 @@ impl ServerSettings { unsafe { let val = &mut *self.cpu_pool.get(); if val.is_none() { - *val = Some(Builder::new().pool_size(2).create()); + let pool = DEFAULT_CPUPOOL.lock().clone(); + *val = Some(pool); } val.as_ref().unwrap() } From 26f37ec2e3a804c2f2087200146964ffe6780baf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 05:45:54 +0600 Subject: [PATCH 0398/1635] refactor HttpHandlerTask trait --- src/application.rs | 64 +++++++++++++++++++++---------------------- src/pipeline.rs | 11 +++++--- src/server/channel.rs | 2 +- src/server/h1.rs | 48 +++++++++++++++++++++++++------- src/server/h2.rs | 42 +++++++++++++++++++++++----- src/server/mod.rs | 19 ++++++++++--- 6 files changed, 128 insertions(+), 58 deletions(-) diff --git a/src/application.rs b/src/application.rs index 5555ee39..e53382de 100644 --- a/src/application.rs +++ b/src/application.rs @@ -26,7 +26,8 @@ pub struct HttpApplication { middlewares: Rc>>>>, } -pub(crate) struct Inner { +#[doc(hidden)] +pub struct Inner { prefix: usize, default: ResourceHandler, encoding: ContentEncoding, @@ -136,7 +137,11 @@ impl HttpApplication { } impl HttpHandler for HttpApplication { - fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest> { + type Task = Pipeline>; + + fn handle( + &mut self, req: HttpRequest, + ) -> Result>, HttpRequest> { let m = { let path = req.path(); path.starts_with(&self.prefix) @@ -157,12 +162,7 @@ impl HttpHandler for HttpApplication { let tp = self.get_handler(&mut req2); let inner = Rc::clone(&self.inner); - Ok(Box::new(Pipeline::new( - req2, - Rc::clone(&self.middlewares), - inner, - tp, - ))) + Ok(Pipeline::new(req2, Rc::clone(&self.middlewares), inner, tp)) } else { Err(req) } @@ -679,8 +679,23 @@ where /// # }); /// } /// ``` - pub fn boxed(mut self) -> Box { - Box::new(self.finish()) + pub fn boxed(mut self) -> Box>> { + Box::new(BoxedApplication { app: self.finish() }) + } +} + +struct BoxedApplication { + app: HttpApplication, +} + +impl HttpHandler for BoxedApplication { + type Task = Box; + + fn handle(&mut self, req: HttpRequest) -> Result { + self.app.handle(req).map(|t| { + let task: Self::Task = Box::new(t); + task + }) } } @@ -798,9 +813,7 @@ mod tests { #[test] fn test_handler() { - let mut app = App::new() - .handler("/test", |_| HttpResponse::Ok()) - .finish(); + let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -825,9 +838,7 @@ mod tests { #[test] fn test_handler2() { - let mut app = App::new() - .handler("test", |_| HttpResponse::Ok()) - .finish(); + let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -881,29 +892,21 @@ mod tests { #[test] fn test_route() { let mut app = App::new() - .route("/test", Method::GET, |_: HttpRequest| { - HttpResponse::Ok() - }) + .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) .route("/test", Method::POST, |_: HttpRequest| { HttpResponse::Created() }) .finish(); - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::GET).finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::POST).finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .finish(); + let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -972,9 +975,6 @@ mod tests { let req = TestRequest::with_uri("/some").finish(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - assert_eq!( - resp.as_msg().body(), - &Body::Binary(Binary::Slice(b"some")) - ); + assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); } } diff --git a/src/pipeline.rs b/src/pipeline.rs index e5eb51d5..d08a739d 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -18,14 +18,16 @@ use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Response, Started}; use server::{HttpHandlerTask, Writer, WriterState}; +#[doc(hidden)] #[derive(Debug, Clone, Copy)] -pub(crate) enum HandlerType { +pub enum HandlerType { Normal(usize), Handler(usize), Default, } -pub(crate) trait PipelineHandler { +#[doc(hidden)] +pub trait PipelineHandler { fn encoding(&self) -> ContentEncoding; fn handle( @@ -33,7 +35,8 @@ pub(crate) trait PipelineHandler { ) -> AsyncResult; } -pub(crate) struct Pipeline(PipelineInfo, PipelineState); +#[doc(hidden)] +pub struct Pipeline(PipelineInfo, PipelineState); enum PipelineState { None, @@ -207,7 +210,7 @@ impl> HttpHandlerTask for Pipeline { } } - fn poll(&mut self) -> Poll<(), Error> { + fn poll_completed(&mut self) -> Poll<(), Error> { let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) }; loop { diff --git a/src/server/channel.rs b/src/server/channel.rs index 34f6733c..a38ecc93 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -11,7 +11,7 @@ use super::{h1, h2, utils, HttpHandler, IoStream}; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; -enum HttpProtocol { +enum HttpProtocol { H1(h1::Http1), H2(h2::Http2), Unknown(Rc>, Option, T, BytesMut), diff --git a/src/server/h1.rs b/src/server/h1.rs index 689d64dc..ababda6b 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -8,7 +8,7 @@ use bytes::{BufMut, BytesMut}; use futures::{Async, Future, Poll}; use tokio_timer::Delay; -use error::PayloadError; +use error::{Error, PayloadError}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use payload::{Payload, PayloadStatus, PayloadWriter}; @@ -43,7 +43,7 @@ bitflags! { } } -pub(crate) struct Http1 { +pub(crate) struct Http1 { flags: Flags, settings: Rc>, addr: Option, @@ -51,12 +51,38 @@ pub(crate) struct Http1 { decoder: H1Decoder, payload: Option, buf: BytesMut, - tasks: VecDeque, + tasks: VecDeque>, keepalive_timer: Option, } -struct Entry { - pipe: Box, +enum EntryPipe { + Task(H::Task), + Error(Box), +} + +impl EntryPipe { + fn disconnected(&mut self) { + match *self { + EntryPipe::Task(ref mut task) => task.disconnected(), + EntryPipe::Error(ref mut task) => task.disconnected(), + } + } + fn poll_io(&mut self, io: &mut Writer) -> Poll { + match *self { + EntryPipe::Task(ref mut task) => task.poll_io(io), + EntryPipe::Error(ref mut task) => task.poll_io(io), + } + } + fn poll_completed(&mut self) -> Poll<(), Error> { + match *self { + EntryPipe::Task(ref mut task) => task.poll_completed(), + EntryPipe::Error(ref mut task) => task.poll_completed(), + } + } +} + +struct Entry { + pipe: EntryPipe, flags: EntryFlags, } @@ -181,7 +207,7 @@ where let mut io = false; let mut idx = 0; while idx < self.tasks.len() { - let item: &mut Entry = unsafe { &mut *(&mut self.tasks[idx] as *mut _) }; + let item: &mut Entry = unsafe { &mut *(&mut self.tasks[idx] as *mut _) }; // only one task can do io operation in http/1 if !io && !item.flags.contains(EntryFlags::EOF) { @@ -232,7 +258,7 @@ where } } } else if !item.flags.contains(EntryFlags::FINISHED) { - match item.pipe.poll() { + match item.pipe.poll_completed() { Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => item.flags.insert(EntryFlags::FINISHED), Err(err) => { @@ -342,7 +368,7 @@ where if !ready { let item = Entry { - pipe, + pipe: EntryPipe::Task(pipe), flags: EntryFlags::EOF, }; self.tasks.push_back(item); @@ -358,7 +384,7 @@ where } } self.tasks.push_back(Entry { - pipe, + pipe: EntryPipe::Task(pipe), flags: EntryFlags::empty(), }); continue 'outer; @@ -369,7 +395,9 @@ where // handler is not found self.tasks.push_back(Entry { - pipe: Pipeline::error(HttpResponse::NotFound()), + pipe: EntryPipe::Error( + Pipeline::error(HttpResponse::NotFound()), + ), flags: EntryFlags::empty(), }); } diff --git a/src/server/h2.rs b/src/server/h2.rs index e194bc7d..993376ef 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -15,7 +15,7 @@ use modhttp::request::Parts; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -use error::PayloadError; +use error::{Error, PayloadError}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -38,7 +38,7 @@ bitflags! { pub(crate) struct Http2 where T: AsyncRead + AsyncWrite + 'static, - H: 'static, + H: HttpHandler + 'static, { flags: Flags, settings: Rc>, @@ -142,7 +142,7 @@ where break; } } else if !item.flags.contains(EntryFlags::FINISHED) { - match item.task.poll() { + match item.task.poll_completed() { Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => { not_ready = false; @@ -288,15 +288,41 @@ bitflags! { } } -struct Entry { - task: Box, +enum EntryPipe { + Task(H::Task), + Error(Box), +} + +impl EntryPipe { + fn disconnected(&mut self) { + match *self { + EntryPipe::Task(ref mut task) => task.disconnected(), + EntryPipe::Error(ref mut task) => task.disconnected(), + } + } + fn poll_io(&mut self, io: &mut Writer) -> Poll { + match *self { + EntryPipe::Task(ref mut task) => task.poll_io(io), + EntryPipe::Error(ref mut task) => task.poll_io(io), + } + } + fn poll_completed(&mut self) -> Poll<(), Error> { + match *self { + EntryPipe::Task(ref mut task) => task.poll_completed(), + EntryPipe::Error(ref mut task) => task.poll_completed(), + } + } +} + +struct Entry { + task: EntryPipe, payload: PayloadType, recv: RecvStream, stream: H2Writer, flags: EntryFlags, } -impl Entry { +impl Entry { fn new( parts: Parts, recv: RecvStream, resp: SendResponse, addr: Option, settings: &Rc>, @@ -333,7 +359,9 @@ impl Entry { } Entry { - task: task.unwrap_or_else(|| Pipeline::error(HttpResponse::NotFound())), + task: task.map(EntryPipe::Task).unwrap_or_else(|| { + EntryPipe::Error(Pipeline::error(HttpResponse::NotFound())) + }), payload: psender, stream: H2Writer::new( resp, diff --git a/src/server/mod.rs b/src/server/mod.rs index 82ba24e2..91b4d1fa 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -122,20 +122,25 @@ impl Message for StopServer { /// Low level http request handler #[allow(unused_variables)] pub trait HttpHandler: 'static { + /// Request handling task + type Task: HttpHandlerTask; + /// Handle request - fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest>; + fn handle(&mut self, req: HttpRequest) -> Result; } -impl HttpHandler for Box { +impl HttpHandler for Box>> { + type Task = Box; + fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest> { self.as_mut().handle(req) } } -#[doc(hidden)] +/// Low level http request handler pub trait HttpHandlerTask { /// Poll task, this method is used before or after *io* object is available - fn poll(&mut self) -> Poll<(), Error> { + fn poll_completed(&mut self) -> Poll<(), Error> { Ok(Async::Ready(())) } @@ -146,6 +151,12 @@ pub trait HttpHandlerTask { fn disconnected(&mut self) {} } +impl HttpHandlerTask for Box { + fn poll_io(&mut self, io: &mut Writer) -> Poll { + self.as_mut().poll_io(io) + } +} + /// Conversion helper trait pub trait IntoHttpHandler { /// The associated type which is result of conversion. From 68cd5bdf686a5290827d441e25824195be99b3a2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Jun 2018 09:18:03 +0600 Subject: [PATCH 0399/1635] use actix 0.6 --- Cargo.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c1195777..ad7e1612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,9 +50,7 @@ flate2-rust = ["flate2/rust_backend"] features = ["tls", "alpn", "session", "brotli", "flate2-c"] [dependencies] -#actix = "^0.6.0" -actix = { git="https://github.com/actix/actix.git" } - +actix = "0.6" base64 = "0.9" bitflags = "1.0" failure = "0.1.1" @@ -77,7 +75,7 @@ time = "0.1" encoding = "0.2" language-tags = "0.2" lazy_static = "1.0" -parking_lot = "0.5" +parking_lot = "0.6" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.10", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } From 261ad31b9ad58060189fc9f7121c7d2422c964b9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Jun 2018 07:44:01 +0600 Subject: [PATCH 0400/1635] remove some unsafe code --- src/pipeline.rs | 4 +-- src/server/channel.rs | 1 - src/server/encoding.rs | 2 -- src/server/h1writer.rs | 60 ++++++++++++++++++++++-------------------- src/server/h2writer.rs | 4 +-- src/server/helpers.rs | 18 ++++++------- src/server/mod.rs | 5 ++-- src/server/shared.rs | 45 +++++++++++-------------------- src/server/srv.rs | 10 +++---- src/server/worker.rs | 2 -- 10 files changed, 64 insertions(+), 87 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index d08a739d..91e2143e 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -547,7 +547,7 @@ impl ProcessResponse { } Ok(Async::Ready(Some(chunk))) => { self.iostate = IOState::Payload(body); - match io.write(chunk.into()) { + match io.write(&chunk.into()) { Err(err) => { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( @@ -592,7 +592,7 @@ impl ProcessResponse { break 'inner; } Frame::Chunk(Some(chunk)) => { - match io.write(chunk) { + match io.write(&chunk) { Err(err) => { info.error = Some(err.into()); return Ok( diff --git a/src/server/channel.rs b/src/server/channel.rs index a38ecc93..d236963b 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -203,7 +203,6 @@ impl Node { } fn insert(&self, next: &Node) { - #[allow(mutable_transmutes)] unsafe { if let Some(ref next2) = self.next { let n: &mut Node<()> = diff --git a/src/server/encoding.rs b/src/server/encoding.rs index 6d814482..db0fd0c3 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -701,8 +701,6 @@ pub(crate) struct TransferEncoding { kind: TransferEncodingKind, } -unsafe impl Send for TransferEncoding {} - #[derive(Debug, PartialEq, Clone)] enum TransferEncodingKind { /// An Encoder for when Transfer-Encoding includes `chunked`. diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index a144a2ff..01477464 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -106,7 +106,7 @@ impl Writer for H1Writer { } #[inline] - fn buffer(&self) -> &mut BytesMut { + fn buffer(&mut self) -> &mut BytesMut { self.buffer.get_mut() } @@ -181,35 +181,37 @@ impl Writer for H1Writer { let mut pos = 0; let mut has_date = false; let mut remaining = buffer.remaining_mut(); - let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in msg.headers() { - if is_bin && key == CONTENT_LENGTH { - is_bin = false; - continue; - } - has_date = has_date || key == DATE; - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { buffer.advance_mut(pos) }; - pos = 0; - buffer.reserve(len); - remaining = buffer.remaining_mut(); - buf = unsafe { &mut *(buffer.bytes_mut() as *mut _) }; - } + unsafe { + let mut buf = &mut *(buffer.bytes_mut() as *mut [u8]); + for (key, value) in msg.headers() { + if is_bin && key == CONTENT_LENGTH { + is_bin = false; + continue; + } + has_date = has_date || key == DATE; + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + let len = k.len() + v.len() + 4; + if len > remaining { + buffer.advance_mut(pos); + pos = 0; + buffer.reserve(len); + remaining = buffer.remaining_mut(); + buf = &mut *(buffer.bytes_mut() as *mut _); + } - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; + } + buffer.advance_mut(pos); } - unsafe { buffer.advance_mut(pos) }; // optimized date header, set_date writes \r\n if !has_date { @@ -233,7 +235,7 @@ impl Writer for H1Writer { Ok(WriterState::Done) } - fn write(&mut self, payload: Binary) -> io::Result { + fn write(&mut self, payload: &Binary) -> io::Result { self.written += payload.len() as u64; if !self.flags.contains(Flags::DISCONNECTED) { if self.flags.contains(Flags::STARTED) { diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index f019f75b..c2731b11 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -77,7 +77,7 @@ impl Writer for H2Writer { } #[inline] - fn buffer(&self) -> &mut BytesMut { + fn buffer(&mut self) -> &mut BytesMut { self.buffer.get_mut() } @@ -164,7 +164,7 @@ impl Writer for H2Writer { } } - fn write(&mut self, payload: Binary) -> io::Result { + fn write(&mut self, payload: &Binary) -> io::Result { self.written = payload.len() as u64; if !self.flags.contains(Flags::DISCONNECTED) { diff --git a/src/server/helpers.rs b/src/server/helpers.rs index cf497edd..939785f4 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -7,7 +7,7 @@ use std::{mem, ptr, slice}; use httprequest::HttpInnerMessage; -/// Internal use only! unsafe +/// Internal use only! pub(crate) struct SharedMessagePool(RefCell>>); impl SharedMessagePool { @@ -189,14 +189,14 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) { } pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { - let mut curr: isize = 39; - 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 { + let mut curr: isize = 39; + let mut buf: [u8; 41] = 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(); + // eagerly decode 4 characters at a time while n >= 10_000 { let rem = (n % 10_000) as isize; @@ -229,9 +229,7 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { 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), 41 - curr as usize, diff --git a/src/server/mod.rs b/src/server/mod.rs index 91b4d1fa..c0dabb26 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -191,15 +191,14 @@ pub trait Writer { fn set_date(&self, st: &mut BytesMut); #[doc(hidden)] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] - fn buffer(&self) -> &mut BytesMut; + fn buffer(&mut self) -> &mut BytesMut; fn start( &mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, encoding: ContentEncoding, ) -> io::Result; - fn write(&mut self, payload: Binary) -> io::Result; + fn write(&mut self, payload: &Binary) -> io::Result; fn write_eof(&mut self) -> io::Result; diff --git a/src/server/shared.rs b/src/server/shared.rs index 54f7b1e6..a36c4617 100644 --- a/src/server/shared.rs +++ b/src/server/shared.rs @@ -6,58 +6,52 @@ use std::rc::Rc; use body::Binary; -/// Internal use only! unsafe #[derive(Debug)] -pub(crate) struct SharedBytesPool(RefCell>>); +pub(crate) struct SharedBytesPool(RefCell>); impl SharedBytesPool { pub fn new() -> SharedBytesPool { SharedBytesPool(RefCell::new(VecDeque::with_capacity(128))) } - pub fn get_bytes(&self) -> Rc { + pub fn get_bytes(&self) -> BytesMut { if let Some(bytes) = self.0.borrow_mut().pop_front() { bytes } else { - Rc::new(BytesMut::new()) + BytesMut::new() } } - pub fn release_bytes(&self, mut bytes: Rc) { + pub fn release_bytes(&self, mut bytes: BytesMut) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { - Rc::get_mut(&mut bytes).unwrap().clear(); + bytes.clear(); v.push_front(bytes); } } } #[derive(Debug)] -pub(crate) struct SharedBytes(Option>, Option>); +pub(crate) struct SharedBytes(Option, Option>); impl Drop for SharedBytes { fn drop(&mut self) { if let Some(pool) = self.1.take() { if let Some(bytes) = self.0.take() { - if Rc::strong_count(&bytes) == 1 { - pool.release_bytes(bytes); - } + pool.release_bytes(bytes); } } } } impl SharedBytes { - pub fn new(bytes: Rc, pool: Rc) -> SharedBytes { + pub fn new(bytes: BytesMut, pool: Rc) -> SharedBytes { SharedBytes(Some(bytes), Some(pool)) } - #[inline(always)] - #[allow(mutable_transmutes)] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))] - pub(crate) fn get_mut(&self) -> &mut BytesMut { - let r: &BytesMut = self.0.as_ref().unwrap().as_ref(); - unsafe { &mut *(r as *const _ as *mut _) } + #[inline] + pub(crate) fn get_mut(&mut self) -> &mut BytesMut { + self.0.as_mut().unwrap() } #[inline] @@ -75,17 +69,16 @@ impl SharedBytes { self.0.as_ref().unwrap().as_ref() } - pub fn split_to(&self, n: usize) -> BytesMut { + pub fn split_to(&mut self, n: usize) -> BytesMut { self.get_mut().split_to(n) } - pub fn take(&self) -> BytesMut { + pub fn take(&mut self) -> BytesMut { self.get_mut().take() } #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] - pub fn extend(&self, data: Binary) { + pub fn extend(&mut self, data: &Binary) { let buf = self.get_mut(); let data = data.as_ref(); buf.reserve(data.len()); @@ -93,7 +86,7 @@ impl SharedBytes { } #[inline] - pub fn extend_from_slice(&self, data: &[u8]) { + pub fn extend_from_slice(&mut self, data: &[u8]) { let buf = self.get_mut(); buf.reserve(data.len()); SharedBytes::put_slice(buf, data); @@ -117,13 +110,7 @@ impl SharedBytes { 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()) + SharedBytes(Some(BytesMut::new()), None) } } diff --git a/src/server/srv.rs b/src/server/srv.rs index 89f57b89..cd670366 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -64,8 +64,6 @@ where no_signals: bool, } -unsafe impl Send for HttpServer {} - enum ServerCommand { WorkerDied(usize, Slab), } @@ -485,11 +483,9 @@ impl HttpServer { self.exit = true; self.no_signals = false; - let _ = thread::spawn(move || { - let sys = System::new("http-server"); - self.start(); - sys.run(); - }).join(); + let sys = System::new("http-server"); + self.start(); + sys.run(); } } diff --git a/src/server/worker.rs b/src/server/worker.rs index 6cf2bbd6..3d4ee863 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -65,8 +65,6 @@ where tcp_ka: Option, } -unsafe impl Send for Worker {} - impl Worker { pub(crate) fn new( h: Vec, socks: Slab, keep_alive: KeepAlive, From 362b14c2f734c33a7b96cc21ba82b50b7767c2eb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Jun 2018 09:36:17 +0600 Subject: [PATCH 0401/1635] remove unsafe cell from ws client --- src/client/pipeline.rs | 6 +++--- src/extensions.rs | 1 - src/ws/client.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index c2caf83c..de1ff128 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -317,9 +317,6 @@ impl Pipeline { if self.conn.is_none() { return Ok(Async::Ready(None)); } - let conn: &mut Connection = - unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) }; - let mut need_run = false; // need write? @@ -337,6 +334,9 @@ impl Pipeline { // need read? if self.parser.is_some() { + let conn: &mut Connection = + unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) }; + loop { match self .parser diff --git a/src/extensions.rs b/src/extensions.rs index 1c64623f..7fdd142b 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -48,7 +48,6 @@ impl Extensions { /// If a extension of this type existed, it will be returned. pub fn remove(&mut self) -> Option { self.map.remove(&TypeId::of::()).and_then(|boxed| { - //TODO: we can use unsafe and remove double checking the type id (boxed as Box) .downcast() .ok() diff --git a/src/ws/client.rs b/src/ws/client.rs index 7cf0095d..5087c885 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -1,5 +1,5 @@ //! Http client request -use std::cell::UnsafeCell; +use std::cell::RefCell; use std::rc::Rc; use std::time::Duration; use std::{fmt, io, str}; @@ -422,7 +422,7 @@ impl Future for ClientHandshake { closed: false, }; - let inner = Rc::new(UnsafeCell::new(inner)); + let inner = Rc::new(RefCell::new(inner)); Ok(Async::Ready(( ClientReader { inner: Rc::clone(&inner), @@ -435,7 +435,7 @@ impl Future for ClientHandshake { /// Websocket reader client pub struct ClientReader { - inner: Rc>, + inner: Rc>, max_size: usize, } @@ -451,7 +451,7 @@ impl Stream for ClientReader { fn poll(&mut self) -> Poll, Self::Error> { let max_size = self.max_size; - let inner: &mut Inner = unsafe { &mut *self.inner.get() }; + let mut inner = self.inner.borrow_mut(); if inner.closed { return Ok(Async::Ready(None)); } @@ -507,14 +507,14 @@ impl Stream for ClientReader { /// Websocket writer client pub struct ClientWriter { - inner: Rc>, + inner: Rc>, } impl ClientWriter { /// Write payload #[inline] fn write(&mut self, mut data: Binary) { - let inner: &mut Inner = unsafe { &mut *self.inner.get() }; + let inner = self.inner.borrow_mut(); if !inner.closed { let _ = inner.tx.unbounded_send(data.take()); } else { From 247e8727cbaa12742a95bea56319812888a4bb75 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Jun 2018 10:15:16 +0600 Subject: [PATCH 0402/1635] ClientBody is not needed --- src/client/body.rs | 94 ------------------------------------------ src/client/mod.rs | 7 ++-- src/client/pipeline.rs | 67 +++++++++++++++++++++++++----- src/client/request.rs | 35 +++++++--------- src/client/writer.rs | 16 +++---- src/ws/client.rs | 6 +-- tests/test_client.rs | 10 +---- 7 files changed, 88 insertions(+), 147 deletions(-) delete mode 100644 src/client/body.rs diff --git a/src/client/body.rs b/src/client/body.rs deleted file mode 100644 index c79d7ad5..00000000 --- a/src/client/body.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::fmt; - -use bytes::Bytes; -use futures::Stream; - -use body::Binary; -use context::ActorHttpContext; -use error::Error; - -/// Type represent streaming body -pub type ClientBodyStream = Box + Send>; - -/// Represents various types of http message body. -pub enum ClientBody { - /// Empty response. `Content-Length` header is set to `0` - Empty, - /// Specific response body. - Binary(Binary), - /// Unspecified streaming response. Developer is responsible for setting - /// right `Content-Length` or `Transfer-Encoding` headers. - Streaming(ClientBodyStream), - /// Special body type for actor response. - Actor(Box), -} - -impl ClientBody { - /// Does this body streaming. - #[inline] - pub fn is_streaming(&self) -> bool { - match *self { - ClientBody::Streaming(_) | ClientBody::Actor(_) => true, - _ => false, - } - } - - /// Is this binary body. - #[inline] - pub fn is_binary(&self) -> bool { - match *self { - ClientBody::Binary(_) => true, - _ => false, - } - } - - /// Is this binary empy. - #[inline] - pub fn is_empty(&self) -> bool { - match *self { - ClientBody::Empty => true, - _ => false, - } - } - - /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> ClientBody { - ClientBody::Binary(Binary::Bytes(Bytes::from(s))) - } -} - -impl PartialEq for ClientBody { - fn eq(&self, other: &ClientBody) -> bool { - match *self { - ClientBody::Empty => match *other { - ClientBody::Empty => true, - _ => false, - }, - ClientBody::Binary(ref b) => match *other { - ClientBody::Binary(ref b2) => b == b2, - _ => false, - }, - ClientBody::Streaming(_) | ClientBody::Actor(_) => false, - } - } -} - -impl fmt::Debug for ClientBody { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ClientBody::Empty => write!(f, "ClientBody::Empty"), - ClientBody::Binary(ref b) => write!(f, "ClientBody::Binary({:?})", b), - ClientBody::Streaming(_) => write!(f, "ClientBody::Streaming(_)"), - ClientBody::Actor(_) => write!(f, "ClientBody::Actor(_)"), - } - } -} - -impl From for ClientBody -where - T: Into, -{ - fn from(b: T) -> ClientBody { - ClientBody::Binary(b.into()) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs index b40fa2ec..bbc2d5f3 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -24,7 +24,6 @@ //! ); //! } //! ``` -mod body; mod connector; mod parser; mod pipeline; @@ -32,7 +31,6 @@ mod request; mod response; mod writer; -pub use self::body::{ClientBody, ClientBodyStream}; pub use self::connector::{ ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection, Pause, Resume, @@ -71,7 +69,9 @@ impl ResponseError for SendRequestError { /// use actix_web::client; /// /// fn main() { -/// tokio::run( +/// let mut sys = actix_web::actix::System::new("test"); +/// +/// sys.block_on( /// client::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() @@ -79,7 +79,6 @@ impl ResponseError for SendRequestError { /// .map_err(|_| ()) /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); -/// # process::exit(0); /// Ok(()) /// }), /// ); diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index de1ff128..76075b52 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -9,10 +9,11 @@ use tokio_timer::Delay; use actix::{Addr, Request, SystemService}; use super::{ - ClientBody, ClientBodyStream, ClientConnector, ClientConnectorError, ClientRequest, - ClientResponse, Connect, Connection, HttpClientWriter, HttpResponseParser, - HttpResponseParserError, + ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect, + Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError, }; +use body::{Body, BodyStream}; +use context::{ActorHttpContext, Frame}; use error::Error; use error::PayloadError; use header::ContentEncoding; @@ -177,9 +178,9 @@ impl Future for SendRequest { let mut writer = HttpClientWriter::new(); writer.start(&mut self.req)?; - let body = match self.req.replace_body(ClientBody::Empty) { - ClientBody::Streaming(stream) => IoBody::Payload(stream), - ClientBody::Actor(_) => panic!("Client actor is not supported"), + let body = match self.req.replace_body(Body::Empty) { + Body::Streaming(stream) => IoBody::Payload(stream), + Body::Actor(ctx) => IoBody::Actor(ctx), _ => IoBody::Done, }; @@ -245,7 +246,8 @@ pub(crate) struct Pipeline { } enum IoBody { - Payload(ClientBodyStream), + Payload(BodyStream), + Actor(Box), Done, } @@ -405,24 +407,67 @@ impl Pipeline { let mut done = false; if self.drain.is_none() && self.write_state != RunningState::Paused { - loop { + 'outter: loop { let result = match mem::replace(&mut self.body, IoBody::Done) { - IoBody::Payload(mut stream) => match stream.poll()? { + IoBody::Payload(mut body) => match body.poll()? { Async::Ready(None) => { self.writer.write_eof()?; self.body_completed = true; break; } Async::Ready(Some(chunk)) => { - self.body = IoBody::Payload(stream); + self.body = IoBody::Payload(body); self.writer.write(chunk.as_ref())? } Async::NotReady => { done = true; - self.body = IoBody::Payload(stream); + self.body = IoBody::Payload(body); break; } }, + IoBody::Actor(mut ctx) => { + if self.disconnected { + ctx.disconnected(); + } + match ctx.poll()? { + Async::Ready(Some(vec)) => { + if vec.is_empty() { + self.body = IoBody::Actor(ctx); + break; + } + let mut res = None; + for frame in vec { + match frame { + Frame::Chunk(None) => { + self.body_completed = true; + self.writer.write_eof()?; + break 'outter; + } + Frame::Chunk(Some(chunk)) => { + res = + Some(self.writer.write(chunk.as_ref())?) + } + Frame::Drain(fut) => self.drain = Some(fut), + } + } + self.body = IoBody::Actor(ctx); + if self.drain.is_some() { + self.write_state.resume(); + break; + } + res.unwrap() + } + Async::Ready(None) => { + done = true; + break; + } + Async::NotReady => { + done = true; + self.body = IoBody::Actor(ctx); + break; + } + } + } IoBody::Done => { self.body_completed = true; done = true; diff --git a/src/client/request.rs b/src/client/request.rs index bc8feb3e..d82aa606 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -13,9 +13,9 @@ use serde_json; use serde_urlencoded; use url::Url; -use super::body::ClientBody; use super::connector::{ClientConnector, Connection}; use super::pipeline::SendRequest; +use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; use http::header::{self, HeaderName, HeaderValue}; @@ -34,7 +34,9 @@ use httprequest::HttpRequest; /// use actix_web::client::ClientRequest; /// /// fn main() { -/// tokio::run( +/// let mut sys = actix_web::actix::System::new("test"); +/// +/// sys.block_on( /// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() @@ -42,7 +44,6 @@ use httprequest::HttpRequest; /// .map_err(|_| ()) /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); -/// # process::exit(0); /// Ok(()) /// }), /// ); @@ -53,7 +54,7 @@ pub struct ClientRequest { method: Method, version: Version, headers: HeaderMap, - body: ClientBody, + body: Body, chunked: bool, upgrade: bool, timeout: Option, @@ -76,7 +77,7 @@ impl Default for ClientRequest { method: Method::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - body: ClientBody::Empty, + body: Body::Empty, chunked: false, upgrade: false, timeout: None, @@ -220,17 +221,17 @@ impl ClientRequest { /// Get body of this response #[inline] - pub fn body(&self) -> &ClientBody { + pub fn body(&self) -> &Body { &self.body } /// Set a body - pub fn set_body>(&mut self, body: B) { + pub fn set_body>(&mut self, body: B) { self.body = body.into(); } /// Extract body, replace it with `Empty` - pub(crate) fn replace_body(&mut self, body: ClientBody) -> ClientBody { + pub(crate) fn replace_body(&mut self, body: Body) -> Body { mem::replace(&mut self.body, body) } @@ -585,9 +586,7 @@ impl ClientRequestBuilder { /// Set a body and generate `ClientRequest`. /// /// `ClientRequestBuilder` can not be used after this call. - pub fn body>( - &mut self, body: B, - ) -> Result { + pub fn body>(&mut self, body: B) -> Result { if let Some(e) = self.err.take() { return Err(e.into()); } @@ -659,13 +658,13 @@ impl ClientRequestBuilder { self.body(body) } - + /// Set a urlencoded body and generate `ClientRequest` /// /// `ClientRequestBuilder` can not be used after this call. pub fn form(&mut self, value: T) -> Result { let body = serde_urlencoded::to_string(&value)?; - + let contains = if let Some(parts) = parts(&mut self.request, &self.err) { parts.headers.contains_key(header::CONTENT_TYPE) } else { @@ -674,7 +673,7 @@ impl ClientRequestBuilder { if !contains { self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); } - + self.body(body) } @@ -683,19 +682,17 @@ impl ClientRequestBuilder { /// `ClientRequestBuilder` can not be used after this call. pub fn streaming(&mut self, stream: S) -> Result where - S: Stream + Send + 'static, + S: Stream + 'static, E: Into, { - self.body(ClientBody::Streaming(Box::new( - stream.map_err(|e| e.into()), - ))) + self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) } /// Set an empty body and generate `ClientRequest` /// /// `ClientRequestBuilder` can not be used after this call. pub fn finish(&mut self) -> Result { - self.body(ClientBody::Empty) + self.body(Body::Empty) } /// This method construct new `ClientRequestBuilder` diff --git a/src/client/writer.rs b/src/client/writer.rs index dfbce1f3..f9961a79 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -19,12 +19,12 @@ use http::{HttpTryFrom, Version}; use time::{self, Duration}; use tokio_io::AsyncWrite; -use body::Binary; +use body::{Binary, Body}; use header::ContentEncoding; use server::encoding::{ContentEncoder, TransferEncoding}; use server::WriterState; -use client::{ClientBody, ClientRequest}; +use client::ClientRequest; const AVERAGE_HEADER_SIZE: usize = 30; @@ -133,7 +133,7 @@ impl HttpClientWriter { ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; // write headers - if let ClientBody::Binary(ref bytes) = *msg.body() { + if let Body::Binary(ref bytes) = *msg.body() { self.buffer .reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); } else { @@ -162,7 +162,7 @@ impl HttpClientWriter { self.headers_size = self.buffer.len() as u32; if msg.body().is_binary() { - if let ClientBody::Binary(bytes) = msg.replace_body(ClientBody::Empty) { + if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { self.written += bytes.len() as u64; self.encoder.write(bytes.as_ref())?; } @@ -223,15 +223,15 @@ impl HttpClientWriter { fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncoder { let version = req.version(); - let mut body = req.replace_body(ClientBody::Empty); + let mut body = req.replace_body(Body::Empty); let mut encoding = req.content_encoding(); let mut transfer = match body { - ClientBody::Empty => { + Body::Empty => { req.headers_mut().remove(CONTENT_LENGTH); TransferEncoding::length(0) } - ClientBody::Binary(ref mut bytes) => { + Body::Binary(ref mut bytes) => { if encoding.is_compression() { let mut tmp = BytesMut::new(); let mut transfer = TransferEncoding::eof(); @@ -270,7 +270,7 @@ fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncode .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); TransferEncoding::eof() } - ClientBody::Streaming(_) | ClientBody::Actor(_) => { + Body::Streaming(_) | Body::Actor(_) => { if req.upgrade() { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); diff --git a/src/ws/client.rs b/src/ws/client.rs index 5087c885..e9b7cf82 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -16,14 +16,14 @@ use sha1::Sha1; use actix::{Addr, SystemService}; -use body::Binary; +use body::{Binary, Body}; use error::{Error, UrlParseError}; use header::IntoHeaderValue; use httpmessage::HttpMessage; use payload::PayloadHelper; use client::{ - ClientBody, ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, + ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, HttpResponseParserError, SendRequest, SendRequestError, }; @@ -297,7 +297,7 @@ impl ClientHandshake { ); let (tx, rx) = unbounded(); - request.set_body(ClientBody::Streaming(Box::new(rx.map_err(|_| { + request.set_body(Body::Streaming(Box::new(rx.map_err(|_| { io::Error::new(io::ErrorKind::Other, "disconnected").into() })))); diff --git a/tests/test_client.rs b/tests/test_client.rs index 3128bb94..0d058c51 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -343,10 +343,7 @@ fn test_client_streaming_explicit() { let body = once(Ok(Bytes::from_static(STR.as_ref()))); - let request = srv - .get() - .body(client::ClientBody::Streaming(Box::new(body))) - .unwrap(); + let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -446,10 +443,7 @@ fn test_default_headers() { "\"" ))); - let request_override = srv.get() - .header("User-Agent", "test") - .finish() - .unwrap(); + let request_override = srv.get().header("User-Agent", "test").finish().unwrap(); let repr_override = format!("{:?}", request_override); assert!(repr_override.contains("\"user-agent\": \"test\"")); assert!(!repr_override.contains(concat!( From 5c42b0902f9cc38b1e6e7c8a53637f1ca781a170 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Jun 2018 12:07:07 +0600 Subject: [PATCH 0403/1635] better doc api examples --- Cargo.toml | 3 ++- src/client/connector.rs | 13 ++++++------- src/client/mod.rs | 18 ++++++++---------- src/client/request.rs | 9 ++++----- src/lib.rs | 1 + 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ad7e1612..61259c79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,8 @@ flate2-rust = ["flate2/rust_backend"] features = ["tls", "alpn", "session", "brotli", "flate2-c"] [dependencies] -actix = "0.6" +actix = "0.6.1" + base64 = "0.9" bitflags = "1.0" failure = "0.1.1" diff --git a/src/client/connector.rs b/src/client/connector.rs index 2727cc12..2d70ff06 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -303,18 +303,16 @@ impl ClientConnector { /// # use std::process; /// # use actix_web::actix::Actor; /// extern crate openssl; - /// use actix_web::client::{ClientConnector, Connect}; + /// use actix_web::{actix, client::ClientConnector, client::Connect}; /// /// use openssl::ssl::{SslConnector, SslMethod}; /// /// fn main() { - /// let mut sys = actix_web::actix::System::new("test"); + /// actix::run(|| { + /// // Start `ClientConnector` with custom `SslConnector` + /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); + /// let conn = ClientConnector::with_connector(ssl_conn).start(); /// - /// // Start `ClientConnector` with custom `SslConnector` - /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn = ClientConnector::with_connector(ssl_conn).start(); - /// - /// sys.block_on( /// conn.send( /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host /// .map_err(|_| ()) @@ -322,6 +320,7 @@ impl ClientConnector { /// if let Ok(mut stream) = res { /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); /// } + /// # actix::System::current().stop(); /// Ok(()) /// }) /// ); diff --git a/src/client/mod.rs b/src/client/mod.rs index bbc2d5f3..5685e093 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -6,19 +6,18 @@ //! # extern crate tokio; //! # use futures::Future; //! # use std::process; -//! use actix_web::client; +//! use actix_web::{actix, client}; //! //! fn main() { -//! let mut sys = actix_web::actix::System::new("test"); -//! -//! sys.block_on( -//! client::get("http://www.rust-lang.org") // <- Create request builder +//! actix::run( +//! || client::get("http://www.rust-lang.org") // <- Create request builder //! .header("User-Agent", "Actix-web") //! .finish().unwrap() //! .send() // <- Send http request //! .map_err(|_| ()) //! .and_then(|response| { // <- server http response //! println!("Response: {:?}", response); +//! # actix::System::current().stop(); //! Ok(()) //! }) //! ); @@ -66,19 +65,18 @@ impl ResponseError for SendRequestError { /// # extern crate env_logger; /// # use futures::Future; /// # use std::process; -/// use actix_web::client; +/// use actix_web::{actix, client}; /// /// fn main() { -/// let mut sys = actix_web::actix::System::new("test"); -/// -/// sys.block_on( -/// client::get("http://www.rust-lang.org") // <- Create request builder +/// actix::run( +/// || client::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() /// .send() // <- Send http request /// .map_err(|_| ()) /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); +/// # actix::System::current().stop(); /// Ok(()) /// }), /// ); diff --git a/src/client/request.rs b/src/client/request.rs index d82aa606..351b4b6d 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -31,19 +31,18 @@ use httprequest::HttpRequest; /// # extern crate tokio; /// # use futures::Future; /// # use std::process; -/// use actix_web::client::ClientRequest; +/// use actix_web::{actix, client}; /// /// fn main() { -/// let mut sys = actix_web::actix::System::new("test"); -/// -/// sys.block_on( -/// ClientRequest::get("http://www.rust-lang.org") // <- Create request builder +/// actix::run( +/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() /// .send() // <- Send http request /// .map_err(|_| ()) /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); +/// # actix::System::current().stop(); /// Ok(()) /// }), /// ); diff --git a/src/lib.rs b/src/lib.rs index dc956ac9..95f5a4ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -210,6 +210,7 @@ pub mod actix { pub use self::actix::fut; pub use self::actix::msgs; pub use self::actix::prelude::*; + pub use self::actix::{run, spawn}; } #[cfg(feature = "openssl")] From 27b6af2800ca368cda314a94ff1936d5142bc782 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Jun 2018 16:45:26 +0600 Subject: [PATCH 0404/1635] refactor route matching --- src/application.rs | 37 ++++---- src/de.rs | 17 ++-- src/fs.rs | 8 +- src/httprequest.rs | 7 +- src/param.rs | 129 ++++++++++++++++++---------- src/router.rs | 204 +++++++++++++++++++++++++++++---------------- src/scope.rs | 22 ++--- src/test.rs | 4 +- src/uri.rs | 9 +- 9 files changed, 262 insertions(+), 175 deletions(-) diff --git a/src/application.rs b/src/application.rs index e53382de..ca5e8786 100644 --- a/src/application.rs +++ b/src/application.rs @@ -73,35 +73,32 @@ impl HttpApplication { HandlerType::Normal(idx) } else { let inner = self.as_ref(); - let path: &'static str = - unsafe { &*(&req.path()[inner.prefix..] as *const _) }; - let path_len = path.len(); + req.match_info_mut().set_tail(0); + 'outer: for idx in 0..inner.handlers.len() { match inner.handlers[idx] { PrefixHandlerType::Handler(ref prefix, _) => { let m = { + let path = &req.path()[inner.prefix..]; + let path_len = path.len(); + path.starts_with(prefix) && (path_len == prefix.len() || path.split_at(prefix.len()).1.starts_with('/')) }; if m { - let prefix_len = inner.prefix + prefix.len(); - let path: &'static str = - unsafe { &*(&req.path()[prefix_len..] as *const _) }; - - req.set_prefix_len(prefix_len as u16); - if path.is_empty() { - req.match_info_mut().add("tail", "/"); - } else { - req.match_info_mut().add("tail", path); - } + let prefix_len = (inner.prefix + prefix.len()) as u16; + let url = req.url().clone(); + req.set_prefix_len(prefix_len); + req.match_info_mut().set_url(url); + req.match_info_mut().set_tail(prefix_len); return HandlerType::Handler(idx); } } PrefixHandlerType::Scope(ref pattern, _, ref filters) => { if let Some(prefix_len) = - pattern.match_prefix_with_params(path, req.match_info_mut()) + pattern.match_prefix_with_params(req, inner.prefix) { for filter in filters { if !filter.check(req) { @@ -109,12 +106,12 @@ impl HttpApplication { } } - let prefix_len = inner.prefix + prefix_len; - let path: &'static str = - unsafe { &*(&req.path()[prefix_len..] as *const _) }; - - req.set_prefix_len(prefix_len as u16); - req.match_info_mut().set("tail", path); + let prefix_len = (inner.prefix + prefix_len) as u16; + let url = req.url().clone(); + req.set_prefix_len(prefix_len); + let params = req.match_info_mut(); + params.set_tail(prefix_len); + params.set_url(url); return HandlerType::Handler(idx); } } diff --git a/src/de.rs b/src/de.rs index ad332787..ecb2fa9a 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,9 +1,7 @@ use serde::de::{self, Deserializer, Error as DeError, Visitor}; -use std::borrow::Cow; -use std::convert::AsRef; -use std::slice::Iter; use httprequest::HttpRequest; +use param::ParamsIter; macro_rules! unsupported_type { ($trait_fn:ident, $name:expr) => { @@ -191,7 +189,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } struct ParamsDeserializer<'de> { - params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>, + params: ParamsIter<'de>, current: Option<(&'de str, &'de str)>, } @@ -202,10 +200,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { where K: de::DeserializeSeed<'de>, { - self.current = self - .params - .next() - .map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); + self.current = self.params.next().map(|ref item| (item.0, item.1)); match self.current { Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), None => Ok(None), @@ -381,7 +376,7 @@ impl<'de> Deserializer<'de> for Value<'de> { } struct ParamsSeq<'de> { - params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>, + params: ParamsIter<'de>, } impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { @@ -392,9 +387,7 @@ impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { T: de::DeserializeSeed<'de>, { match self.params.next() { - Some(item) => Ok(Some(seed.deserialize(Value { - value: item.1.as_ref(), - })?)), + Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)), None => Ok(None), } } diff --git a/src/fs.rs b/src/fs.rs index 9d5659f7..e1cc58a5 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1190,7 +1190,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::NOT_FOUND); let mut req = HttpRequest::default(); - req.match_info_mut().add("tail", ""); + req.match_info_mut().add_static("tail", ""); st.show_index = true; let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); @@ -1207,7 +1207,7 @@ mod tests { fn test_redirect_to_index() { let mut st = StaticFiles::new(".").index_file("index.html"); let mut req = HttpRequest::default(); - req.match_info_mut().add("tail", "tests"); + req.match_info_mut().add_static("tail", "tests"); let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); @@ -1218,7 +1218,7 @@ mod tests { ); let mut req = HttpRequest::default(); - req.match_info_mut().add("tail", "tests/"); + req.match_info_mut().add_static("tail", "tests/"); let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); @@ -1233,7 +1233,7 @@ mod tests { fn test_redirect_to_index_nested() { let mut st = StaticFiles::new(".").index_file("mod.rs"); let mut req = HttpRequest::default(); - req.match_info_mut().add("tail", "src/client"); + req.match_info_mut().add_static("tail", "src/client"); let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); diff --git a/src/httprequest.rs b/src/httprequest.rs index cf6869f2..9f051aa8 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -40,7 +40,7 @@ pub struct HttpInnerMessage { pub(crate) flags: MessageFlags, pub headers: HeaderMap, pub extensions: Extensions, - pub params: Params<'static>, + pub params: Params, pub addr: Option, pub payload: Option, pub prefix: u16, @@ -268,6 +268,11 @@ impl HttpRequest { self.as_ref().url.path() } + #[inline] + pub(crate) fn url(&self) -> &InnerUrl { + &self.as_ref().url + } + /// Get *ConnectionInfo* for correct request. pub fn connection_info(&self) -> &ConnectionInfo { if self.extensions().get::().is_none() { diff --git a/src/param.rs b/src/param.rs index 48d1f3a3..02c1aea8 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,13 +1,12 @@ use http::StatusCode; use smallvec::SmallVec; use std; -use std::borrow::Cow; use std::ops::Index; use std::path::PathBuf; -use std::slice::Iter; use std::str::FromStr; use error::{InternalError, ResponseError, UriSegmentError}; +use uri::Url; /// A trait to abstract the idea of creating a new instance of a type from a /// path parameter. @@ -19,72 +18,78 @@ pub trait FromParam: Sized { fn from_param(s: &str) -> Result; } +#[derive(Debug, Clone, Copy)] +pub(crate) enum ParamItem { + Static(&'static str), + UrlSegment(u16, u16), +} + /// Route match information /// /// If resource path contains variable patterns, `Params` stores this variables. #[derive(Debug)] -pub struct Params<'a>(SmallVec<[(Cow<'a, str>, Cow<'a, str>); 3]>); +pub struct Params { + url: Url, + pub(crate) tail: u16, + segments: SmallVec<[(&'static str, ParamItem); 3]>, +} -impl<'a> Params<'a> { - pub(crate) fn new() -> Params<'a> { - Params(SmallVec::new()) +impl Params { + pub(crate) fn new() -> Params { + Params { + url: Url::default(), + tail: 0, + segments: SmallVec::new(), + } } pub(crate) fn clear(&mut self) { - self.0.clear(); + self.segments.clear(); } - pub(crate) fn add(&mut self, name: N, value: V) - where - N: Into>, - V: Into>, - { - self.0.push((name.into(), value.into())); + pub(crate) fn set_url(&mut self, url: Url) { + self.url = url; } - pub(crate) fn set(&mut self, name: N, value: V) - where - N: Into>, - V: Into>, - { - let name = name.into(); - let value = value.into(); - for item in &mut self.0 { - if item.0 == name { - item.1 = value; - return; - } - } - self.0.push((name, value)); + pub(crate) fn set_tail(&mut self, tail: u16) { + self.tail = tail; } - pub(crate) fn remove(&mut self, name: &str) { - for idx in (0..self.0.len()).rev() { - if self.0[idx].0 == name { - self.0.remove(idx); - return; - } - } + pub(crate) fn add(&mut self, name: &'static str, value: ParamItem) { + self.segments.push((name, value)); + } + + pub(crate) fn add_static(&mut self, name: &'static str, value: &'static str) { + self.segments.push((name, ParamItem::Static(value))); } /// Check if there are any matched patterns pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.segments.is_empty() } /// Check number of extracted parameters pub fn len(&self) -> usize { - self.0.len() + self.segments.len() } /// Get matched parameter by name without type conversion - pub fn get(&'a self, key: &str) -> Option<&'a str> { - for item in self.0.iter() { + pub fn get(&self, key: &str) -> Option<&str> { + for item in self.segments.iter() { if key == item.0 { - return Some(item.1.as_ref()); + return match item.1 { + ParamItem::Static(s) => Some(s), + ParamItem::UrlSegment(s, e) => { + Some(&self.url.path()[(s as usize)..(e as usize)]) + } + }; } } - None + if key == "tail" { + Some(&self.url.path()[(self.tail as usize)..]) + } else { + None + } } /// Get matched `FromParam` compatible parameter by name. @@ -101,7 +106,7 @@ impl<'a> Params<'a> { /// } /// # fn main() {} /// ``` - pub fn query(&'a self, key: &str) -> Result::Err> { + pub fn query(&self, key: &str) -> Result::Err> { if let Some(s) = self.get(key) { T::from_param(s) } else { @@ -110,12 +115,41 @@ impl<'a> Params<'a> { } /// Return iterator to items in parameter container - pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> { - self.0.iter() + pub fn iter(&self) -> ParamsIter { + ParamsIter { + idx: 0, + params: self, + } } } -impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> { +#[derive(Debug)] +pub struct ParamsIter<'a> { + idx: usize, + params: &'a Params, +} + +impl<'a> Iterator for ParamsIter<'a> { + type Item = (&'a str, &'a str); + + #[inline] + fn next(&mut self) -> Option<(&'a str, &'a str)> { + if self.idx < self.params.len() { + let idx = self.idx; + let res = match self.params.segments[idx].1 { + ParamItem::Static(s) => s, + ParamItem::UrlSegment(s, e) => { + &self.params.url.path()[(s as usize)..(e as usize)] + } + }; + self.idx += 1; + return Some((self.params.segments[idx].0, res)); + } + None + } +} + +impl<'a, 'b> Index<&'b str> for &'a Params { type Output = str; fn index(&self, name: &'b str) -> &str { @@ -124,11 +158,14 @@ impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> { } } -impl<'a, 'c: 'a> Index for &'c Params<'a> { +impl<'a> Index for &'a Params { type Output = str; fn index(&self, idx: usize) -> &str { - self.0[idx].1.as_ref() + match self.segments[idx].1 { + ParamItem::Static(s) => s, + ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)], + } } } diff --git a/src/router.rs b/src/router.rs index c9ff9d70..6592f64e 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; +use std::mem; use std::rc::Rc; use regex::{escape, Regex}; use error::UrlGenerationError; use httprequest::HttpRequest; -use param::Params; +use param::ParamItem; use resource::ResourceHandler; use server::ServerSettings; @@ -78,11 +79,10 @@ impl Router { if self.0.prefix_len > req.path().len() { return None; } - let path = unsafe { &*(&req.path()[self.0.prefix_len..] as *const str) }; - let route_path = if path.is_empty() { "/" } else { path }; - for (idx, pattern) in self.0.patterns.iter().enumerate() { - if pattern.match_with_params(route_path, req.match_info_mut()) { + if pattern.match_with_params(req, self.0.prefix_len, true) { + let url = req.url().clone(); + req.match_info_mut().set_url(url); req.set_resource(idx); req.set_prefix_len(self.0.prefix_len as u16); return Some(idx); @@ -261,73 +261,128 @@ impl Resource { } /// Are the given path and parameters a match against this resource? - pub fn match_with_params<'a>( - &'a self, path: &'a str, params: &'a mut Params<'a>, + pub fn match_with_params( + &self, req: &mut HttpRequest, plen: usize, insert: bool, ) -> bool { - match self.tp { - PatternType::Static(ref s) => s == path, - PatternType::Dynamic(ref re, ref names, _) => { - if let Some(captures) = re.captures(path) { - let mut idx = 0; - for capture in captures.iter() { - if let Some(ref m) = capture { - if idx != 0 { - params.add(names[idx - 1].as_str(), m.as_str()); - } - idx += 1; - } - } - true + let mut segments: [ParamItem; 24] = unsafe { mem::uninitialized() }; + + let (names, segments_len) = { + let path = &req.path()[plen..]; + if insert { + if path.is_empty() { + "/" } else { - false + path } + } else { + path + }; + + match self.tp { + PatternType::Static(ref s) => return s == path, + PatternType::Dynamic(ref re, ref names, _) => { + if let Some(captures) = re.captures(path) { + let mut idx = 0; + let mut passed = false; + for capture in captures.iter() { + if let Some(ref m) = capture { + if !passed { + passed = true; + continue; + } + segments[idx] = ParamItem::UrlSegment( + (plen + m.start()) as u16, + (plen + m.end()) as u16, + ); + idx += 1; + } + } + (names, idx) + } else { + return false; + } + } + PatternType::Prefix(ref s) => return path.starts_with(s), } - PatternType::Prefix(ref s) => path.starts_with(s), + }; + + let len = req.path().len(); + let params = req.match_info_mut(); + params.set_tail(len as u16); + for idx in 0..segments_len { + let name = unsafe { &*(names[idx].as_str() as *const _) }; + params.add(name, segments[idx]); } + true } /// Is the given path a prefix match and do the parameters match against this resource? - pub fn match_prefix_with_params<'a>( - &'a self, path: &'a str, params: &'a mut Params<'a>, + pub fn match_prefix_with_params( + &self, req: &mut HttpRequest, plen: usize, ) -> Option { - match self.tp { - PatternType::Static(ref s) => if s == path { - Some(s.len()) - } else { - None - }, - PatternType::Dynamic(ref re, ref names, len) => { - if let Some(captures) = re.captures(path) { - let mut idx = 0; - let mut pos = 0; - for capture in captures.iter() { - if let Some(ref m) = capture { - if idx != 0 { - params.add(names[idx - 1].as_str(), m.as_str()); - } - idx += 1; - pos = m.end(); - } - } - Some(pos + len) + let mut segments: [ParamItem; 24] = unsafe { mem::uninitialized() }; + + let (names, segments_len, tail_len) = { + let path = &req.path()[plen..]; + let path = if path.is_empty() { "/" } else { path }; + + match self.tp { + PatternType::Static(ref s) => if s == path { + return Some(s.len()); } else { - None + return None; + }, + PatternType::Dynamic(ref re, ref names, len) => { + if let Some(captures) = re.captures(path) { + let mut idx = 0; + let mut pos = 0; + let mut passed = false; + for capture in captures.iter() { + if let Some(ref m) = capture { + if !passed { + passed = true; + continue; + } + + segments[idx] = ParamItem::UrlSegment( + (plen + m.start()) as u16, + (plen + m.end()) as u16, + ); + idx += 1; + pos = m.end(); + } + } + (names, idx, pos + len) + } else { + return None; + } + } + PatternType::Prefix(ref s) => { + return if path == s { + Some(s.len()) + } else if path.starts_with(s) + && (s.ends_with('/') + || path.split_at(s.len()).1.starts_with('/')) + { + if s.ends_with('/') { + Some(s.len() - 1) + } else { + Some(s.len()) + } + } else { + None + } } } - PatternType::Prefix(ref s) => if path == s { - Some(s.len()) - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - Some(s.len() - 1) - } else { - Some(s.len()) - } - } else { - None - }, + }; + + let params = req.match_info_mut(); + params.set_tail(tail_len as u16); + for idx in 0..segments_len { + let name = unsafe { &*(names[idx].as_str() as *const _) }; + params.add(name, segments[idx]); } + Some(tail_len) } /// Build resource path. @@ -634,20 +689,22 @@ mod tests { #[test] fn test_parse_param() { - let mut req = HttpRequest::default(); - let re = Resource::new("test", "/user/{id}"); 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")); - req.match_info_mut().clear(); - assert!(re.match_with_params("/user/profile", req.match_info_mut())); + let mut req = TestRequest::with_uri("/user/profile").finish(); + let url = req.url().clone(); + req.match_info_mut().set_url(url); + assert!(re.match_with_params(&mut req, 0, true)); assert_eq!(req.match_info().get("id").unwrap(), "profile"); - req.match_info_mut().clear(); - assert!(re.match_with_params("/user/1245125", req.match_info_mut())); + let mut req = TestRequest::with_uri("/user/1245125").finish(); + let url = req.url().clone(); + req.match_info_mut().set_url(url); + assert!(re.match_with_params(&mut req, 0, true)); assert_eq!(req.match_info().get("id").unwrap(), "1245125"); let re = Resource::new("test", "/v{version}/resource/{id}"); @@ -655,8 +712,10 @@ mod tests { assert!(!re.is_match("/v/resource/1")); assert!(!re.is_match("/resource")); - req.match_info_mut().clear(); - assert!(re.match_with_params("/v151/resource/adahg32", req.match_info_mut())); + let mut req = TestRequest::with_uri("/v151/resource/adahg32").finish(); + let url = req.url().clone(); + req.match_info_mut().set_url(url); + assert!(re.match_with_params(&mut req, 0, true)); assert_eq!(req.match_info().get("version").unwrap(), "151"); assert_eq!(req.match_info().get("id").unwrap(), "adahg32"); } @@ -684,15 +743,16 @@ mod tests { assert!(!re.is_match("/name")); let mut req = TestRequest::with_uri("/test2/").finish(); - assert!(re.match_with_params("/test2/", req.match_info_mut())); + let url = req.url().clone(); + req.match_info_mut().set_url(url); + assert!(re.match_with_params(&mut req, 0, true)); assert_eq!(&req.match_info()["name"], "test2"); let mut req = TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); - assert!(re.match_with_params( - "/test2/subpath1/subpath2/index.html", - req.match_info_mut() - )); + let url = req.url().clone(); + req.match_info_mut().set_url(url); + assert!(re.match_with_params(&mut req, 0, true)); assert_eq!(&req.match_info()["name"], "test2"); } diff --git a/src/scope.rs b/src/scope.rs index a4011302..37fb7890 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -322,14 +322,13 @@ impl Scope { impl RouteHandler for Scope { fn handle(&mut self, mut req: HttpRequest) -> AsyncResult { - let path = unsafe { &*(&req.match_info()["tail"] as *const _) }; + let tail = req.match_info().tail as usize; // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { - if pattern.match_with_params(path, req.match_info_mut()) { + if pattern.match_with_params(&mut req, tail, false) { let default = unsafe { &mut *self.default.as_ref().get() }; - req.match_info_mut().remove("tail"); if self.middlewares.borrow().is_empty() { let resource = unsafe { &mut *resource.get() }; return resource.handle(req, Some(default)); @@ -346,23 +345,18 @@ impl RouteHandler for Scope { // nested scopes let len = req.prefix_len() as usize; - let path: &'static str = unsafe { &*(&req.path()[len..] as *const _) }; - 'outer: for &(ref prefix, ref handler, ref filters) in &self.nested { - if let Some(prefix_len) = - prefix.match_prefix_with_params(path, req.match_info_mut()) - { + if let Some(prefix_len) = prefix.match_prefix_with_params(&mut req, len) { for filter in filters { if !filter.check(&mut req) { continue 'outer; } } - let prefix_len = len + prefix_len; - let path: &'static str = - unsafe { &*(&req.path()[prefix_len..] as *const _) }; - - req.set_prefix_len(prefix_len as u16); - req.match_info_mut().set("tail", path); + let url = req.url().clone(); + let prefix_len = (len + prefix_len) as u16; + req.set_prefix_len(prefix_len); + req.match_info_mut().set_tail(prefix_len); + req.match_info_mut().set_url(url); let hnd: &mut RouteHandler<_> = unsafe { (&mut *(handler.get())).as_mut() }; diff --git a/src/test.rs b/src/test.rs index cf9b7f1c..19e682d8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -408,7 +408,7 @@ pub struct TestRequest { method: Method, uri: Uri, headers: HeaderMap, - params: Params<'static>, + params: Params, cookies: Option>>, payload: Option, } @@ -508,7 +508,7 @@ impl TestRequest { /// Set request path pattern parameter pub fn param(mut self, name: &'static str, value: &'static str) -> Self { - self.params.add(name, value); + self.params.add_static(name, value); self } diff --git a/src/uri.rs b/src/uri.rs index ce147024..61ee9352 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -1,4 +1,5 @@ use http::Uri; +use std::rc::Rc; #[allow(dead_code)] const GEN_DELIMS: &[u8] = b":/?#[]@"; @@ -34,10 +35,10 @@ lazy_static! { static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; } -#[derive(Default)] +#[derive(Default, Clone, Debug)] pub(crate) struct Url { uri: Uri, - path: Option, + path: Option>, } impl Url { @@ -95,7 +96,7 @@ impl Quoter { q } - pub fn requote(&self, val: &[u8]) -> Option { + pub fn requote(&self, val: &[u8]) -> Option> { let mut has_pct = 0; let mut pct = [b'%', 0, 0]; let mut idx = 0; @@ -145,7 +146,7 @@ impl Quoter { } if let Some(data) = cloned { - Some(unsafe { String::from_utf8_unchecked(data) }) + Some(unsafe { Rc::new(String::from_utf8_unchecked(data)) }) } else { None } From 877e177b603934f86ccdf0ad8d8e2e7e45459d05 Mon Sep 17 00:00:00 2001 From: Konrad Borowski Date: Tue, 19 Jun 2018 13:42:44 +0200 Subject: [PATCH 0405/1635] Remove use of unsafe from Pipeline#poll --- src/client/pipeline.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 76075b52..e77894b2 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -336,8 +336,7 @@ impl Pipeline { // need read? if self.parser.is_some() { - let conn: &mut Connection = - unsafe { &mut *(self.conn.as_mut().unwrap() as *mut _) }; + let conn: &mut Connection = self.conn.as_mut().unwrap(); loop { match self From e884e7e84e965106424b781f11260dc38adc860b Mon Sep 17 00:00:00 2001 From: Konrad Borowski Date: Tue, 19 Jun 2018 14:11:53 +0200 Subject: [PATCH 0406/1635] Remove some uses of unsafe from Frame::message --- src/ws/frame.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 8eaef72d..5e88758d 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,5 +1,5 @@ #![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] -use byteorder::{BigEndian, ByteOrder, NetworkEndian}; +use byteorder::{ByteOrder, NetworkEndian}; use bytes::{BufMut, Bytes, BytesMut}; use futures::{Async, Poll, Stream}; use rand; @@ -312,20 +312,12 @@ impl Frame { } else if payload_len <= 65_535 { let mut buf = BytesMut::with_capacity(p_len + 4); buf.put_slice(&[one, two | 126]); - { - let buf_mut = unsafe { buf.bytes_mut() }; - BigEndian::write_u16(&mut buf_mut[..2], payload_len as u16); - } - unsafe { buf.advance_mut(2) }; + buf.put_u16_be(payload_len as u16); buf } else { let mut buf = BytesMut::with_capacity(p_len + 10); buf.put_slice(&[one, two | 127]); - { - let buf_mut = unsafe { buf.bytes_mut() }; - BigEndian::write_u64(&mut buf_mut[..8], payload_len as u64); - } - unsafe { buf.advance_mut(8) }; + buf.put_u64_be(payload_len as u64); buf }; From bfb93cae669547cccd9acbdb6d44c8d1eee2dcf3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Jun 2018 19:19:31 +0600 Subject: [PATCH 0407/1635] Update connector.rs --- src/client/connector.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 2d70ff06..e094fd0c 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -294,7 +294,7 @@ impl ClientConnector { /// With `with_connector` method it is possible to use a custom /// `SslConnector` object. /// - /// ```rust + /// ```rust,ignore /// # #![cfg(feature="alpn")] /// # extern crate actix_web; /// # extern crate futures; From a69c1e3de5c9753e3c12ab73f677518701908c39 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Jun 2018 23:46:58 +0600 Subject: [PATCH 0408/1635] remove unsafe from scope impl --- src/application.rs | 17 +++++--- src/resource.rs | 19 ++++----- src/scope.rs | 93 ++++++++++++++++++++++++------------------ src/server/h1writer.rs | 10 ++--- src/server/shared.rs | 25 ++---------- 5 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/application.rs b/src/application.rs index ca5e8786..3c64e925 100644 --- a/src/application.rs +++ b/src/application.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use handler::{AsyncResult, FromRequest, Handler, Responder, RouteHandler, WrapHandler}; use header::ContentEncoding; -use http::Method; +use http::{Method, StatusCode}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::Middleware; @@ -49,14 +49,21 @@ impl PipelineHandler for Inner { &mut self, req: HttpRequest, htype: HandlerType, ) -> AsyncResult { match htype { - HandlerType::Normal(idx) => { - self.resources[idx].handle(req, Some(&mut self.default)) - } + HandlerType::Normal(idx) => match self.resources[idx].handle(req) { + Ok(result) => result, + Err(req) => match self.default.handle(req) { + Ok(result) => result, + Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), + }, + }, HandlerType::Handler(idx) => match self.handlers[idx] { PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req), PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req), }, - HandlerType::Default => self.default.handle(req, None), + HandlerType::Default => match self.default.handle(req) { + Ok(result) => result, + Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), + }, } } } diff --git a/src/resource.rs b/src/resource.rs index 2b9c8538..49e9ab0c 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,9 +1,9 @@ +use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; -use std::cell::RefCell; use futures::Future; -use http::{Method, StatusCode}; +use http::Method; use smallvec::SmallVec; use error::Error; @@ -282,21 +282,18 @@ impl ResourceHandler { } pub(crate) fn handle( - &mut self, mut req: HttpRequest, default: Option<&mut ResourceHandler>, - ) -> AsyncResult { + &mut self, mut req: HttpRequest, + ) -> Result, HttpRequest> { for route in &mut self.routes { if route.check(&mut req) { return if self.middlewares.borrow().is_empty() { - route.handle(req) + Ok(route.handle(req)) } else { - route.compose(req, Rc::clone(&self.middlewares)) + Ok(route.compose(req, Rc::clone(&self.middlewares))) }; } } - if let Some(resource) = default { - resource.handle(req, None) - } else { - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) - } + + Err(req) } } diff --git a/src/scope.rs b/src/scope.rs index 37fb7890..e57db763 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -7,7 +7,7 @@ use futures::{Async, Future, Poll}; use error::Error; use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler}; -use http::Method; +use http::{Method, StatusCode}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{ @@ -18,8 +18,9 @@ use pred::Predicate; use resource::ResourceHandler; use router::Resource; +type ScopeResource = Rc>>; type Route = UnsafeCell>>; -type ScopeResources = Rc>>)>>; +type ScopeResources = Rc)>>; type NestedInfo = (Resource, Route, Vec>>); /// Resources scope @@ -57,7 +58,7 @@ pub struct Scope { filters: Vec>>, nested: Vec>, middlewares: Rc>>>>, - default: Rc>>, + default: Option>, resources: ScopeResources, } @@ -71,7 +72,7 @@ impl Scope { nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(RefCell::new(Vec::new())), - default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), + default: None, } } @@ -135,7 +136,7 @@ impl Scope { nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(RefCell::new(Vec::new())), - default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), + default: None, }; let mut scope = f(scope); @@ -178,7 +179,7 @@ impl Scope { nested: Vec::new(), resources: Rc::new(Vec::new()), middlewares: Rc::new(RefCell::new(Vec::new())), - default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), + default: None, }; let mut scope = f(scope); @@ -229,8 +230,7 @@ impl Scope { let slf: &Scope = unsafe { &*(&self as *const _) }; for &(ref pattern, ref resource) in slf.resources.iter() { if pattern.pattern() == path { - let resource = unsafe { &mut *resource.get() }; - resource.method(method).with(f); + resource.borrow_mut().method(method).with(f); return self; } } @@ -245,7 +245,7 @@ impl Scope { ); Rc::get_mut(&mut self.resources) .expect("Can not use after configuration") - .push((pattern, Rc::new(UnsafeCell::new(handler)))); + .push((pattern, Rc::new(RefCell::new(handler)))); self } @@ -289,18 +289,21 @@ impl Scope { ); Rc::get_mut(&mut self.resources) .expect("Can not use after configuration") - .push((pattern, Rc::new(UnsafeCell::new(handler)))); + .push((pattern, Rc::new(RefCell::new(handler)))); self } /// Default resource to be used if no matching route could be found. - pub fn default_resource(self, f: F) -> Scope + pub fn default_resource(mut self, f: F) -> Scope where F: FnOnce(&mut ResourceHandler) -> R + 'static, { - let default = unsafe { &mut *self.default.as_ref().get() }; - f(default); + if self.default.is_none() { + self.default = + Some(Rc::new(RefCell::new(ResourceHandler::default_not_found()))); + } + f(&mut *self.default.as_ref().unwrap().borrow_mut()); self } @@ -327,17 +330,27 @@ impl RouteHandler for Scope { // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { if pattern.match_with_params(&mut req, tail, false) { - let default = unsafe { &mut *self.default.as_ref().get() }; - if self.middlewares.borrow().is_empty() { - let resource = unsafe { &mut *resource.get() }; - return resource.handle(req, Some(default)); + return match resource.borrow_mut().handle(req) { + Ok(result) => result, + Err(req) => { + if let Some(ref default) = self.default { + match default.borrow_mut().handle(req) { + Ok(result) => result, + Err(_) => AsyncResult::ok(HttpResponse::new( + StatusCode::NOT_FOUND, + )), + } + } else { + AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) + } + } + }; } else { return AsyncResult::async(Box::new(Compose::new( req, Rc::clone(&self.middlewares), Rc::clone(&resource), - Some(Rc::clone(&self.default)), ))); } } @@ -365,16 +378,23 @@ impl RouteHandler for Scope { } // default handler - let default = unsafe { &mut *self.default.as_ref().get() }; if self.middlewares.borrow().is_empty() { - default.handle(req, None) - } else { + if let Some(ref default) = self.default { + match default.borrow_mut().handle(req) { + Ok(result) => result, + Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), + } + } else { + AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) + } + } else if let Some(ref default) = self.default { AsyncResult::async(Box::new(Compose::new( req, Rc::clone(&self.middlewares), - Rc::clone(&self.default), - None, + Rc::clone(default), ))) + } else { + unimplemented!() } } } @@ -417,8 +437,7 @@ struct ComposeInfo { count: usize, req: HttpRequest, mws: Rc>>>>, - default: Option>>>, - resource: Rc>>, + resource: Rc>>, } enum ComposeState { @@ -444,15 +463,13 @@ impl ComposeState { impl Compose { fn new( req: HttpRequest, mws: Rc>>>>, - resource: Rc>>, - default: Option>>>, + resource: Rc>>, ) -> Self { let mut info = ComposeInfo { count: 0, req, mws, resource, - default, }; let state = StartMiddlewares::init(&mut info); @@ -492,12 +509,10 @@ impl StartMiddlewares { let len = info.mws.borrow().len(); loop { if info.count == len { - let resource = unsafe { &mut *info.resource.get() }; - let reply = if let Some(ref default) = info.default { - let d = unsafe { &mut *default.as_ref().get() }; - resource.handle(info.req.clone(), Some(d)) - } else { - resource.handle(info.req.clone(), None) + let reply = { + let req = info.req.clone(); + let mut resource = info.resource.borrow_mut(); + resource.handle(req).unwrap() }; return WaitingResponse::init(info, reply); } else { @@ -531,12 +546,10 @@ impl StartMiddlewares { } loop { if info.count == len { - let resource = unsafe { &mut *info.resource.get() }; - let reply = if let Some(ref default) = info.default { - let d = unsafe { &mut *default.as_ref().get() }; - resource.handle(info.req.clone(), Some(d)) - } else { - resource.handle(info.req.clone(), None) + let reply = { + let req = info.req.clone(); + let mut resource = info.resource.borrow_mut(); + resource.handle(req).unwrap() }; return Some(WaitingResponse::init(info, reply)); } else { diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 01477464..d174964b 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -163,18 +163,18 @@ impl Writer for H1Writer { // status line helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); - SharedBytes::extend_from_slice_(buffer, reason); + buffer.extend_from_slice(reason); match body { Body::Empty => if req.method != Method::HEAD { - SharedBytes::put_slice(buffer, b"\r\ncontent-length: 0\r\n"); + buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"); } else { - SharedBytes::put_slice(buffer, b"\r\n"); + buffer.extend_from_slice(b"\r\n"); }, Body::Binary(ref bytes) => { helpers::write_content_length(bytes.len(), &mut buffer) } - _ => SharedBytes::put_slice(buffer, b"\r\n"), + _ => buffer.extend_from_slice(b"\r\n"), } // write headers @@ -218,7 +218,7 @@ impl Writer for H1Writer { self.settings.set_date(&mut buffer); } else { // msg eof - SharedBytes::extend_from_slice_(buffer, b"\r\n"); + buffer.extend_from_slice(b"\r\n"); } self.headers_size = buffer.len() as u32; } diff --git a/src/server/shared.rs b/src/server/shared.rs index a36c4617..064130fb 100644 --- a/src/server/shared.rs +++ b/src/server/shared.rs @@ -1,9 +1,10 @@ -use bytes::{BufMut, BytesMut}; use std::cell::RefCell; use std::collections::VecDeque; use std::io; use std::rc::Rc; +use bytes::BytesMut; + use body::Binary; #[derive(Debug)] @@ -80,31 +81,13 @@ impl SharedBytes { #[inline] pub fn extend(&mut self, data: &Binary) { let buf = self.get_mut(); - let data = data.as_ref(); - buf.reserve(data.len()); - SharedBytes::put_slice(buf, data); + buf.extend_from_slice(data.as_ref()); } #[inline] pub fn extend_from_slice(&mut self, data: &[u8]) { let buf = self.get_mut(); - buf.reserve(data.len()); - SharedBytes::put_slice(buf, data); - } - - #[inline] - pub(crate) fn put_slice(buf: &mut BytesMut, src: &[u8]) { - let len = src.len(); - unsafe { - buf.bytes_mut()[..len].copy_from_slice(src); - buf.advance_mut(len); - } - } - - #[inline] - pub(crate) fn extend_from_slice_(buf: &mut BytesMut, data: &[u8]) { - buf.reserve(data.len()); - SharedBytes::put_slice(buf, data); + buf.extend_from_slice(data); } } From 311f0b23a9f787f3dc809babb81df4f8d59afd82 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Jun 2018 00:36:32 +0600 Subject: [PATCH 0409/1635] cleanup more code --- src/fs.rs | 9 ++++----- src/httpmessage.rs | 6 ++---- src/httprequest.rs | 7 +++---- src/uri.rs | 2 ++ 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index e1cc58a5..639626c5 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -10,7 +10,7 @@ use std::{cmp, io}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -use bytes::{BufMut, Bytes, BytesMut}; +use bytes::Bytes; use futures::{Async, Future, Poll, Stream}; use futures_cpupool::{CpuFuture, CpuPool}; use mime; @@ -439,14 +439,13 @@ impl Stream for ChunkedReadFile { self.fut = Some(self.cpu_pool.spawn_fn(move || { let max_bytes: usize; max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - let mut buf = BytesMut::from(Vec::with_capacity(max_bytes)); + let mut buf = Vec::with_capacity(max_bytes); file.seek(io::SeekFrom::Start(offset))?; - let nbytes = file.read(unsafe { buf.bytes_mut() })?; + let nbytes = file.read(buf.as_mut_slice())?; if nbytes == 0 { return Err(io::ErrorKind::UnexpectedEof.into()); } - unsafe { buf.advance_mut(nbytes) }; - Ok((file, buf.freeze())) + Ok((file, Bytes::from(buf))) })); self.poll() } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index cac82f04..5917e7fb 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -634,13 +634,11 @@ mod tests { assert!(req.chunked().unwrap()); let mut headers = HeaderMap::new(); - let s = unsafe { - str::from_utf8_unchecked(b"some va\xadscc\xacas0xsdasdlue".as_ref()) - }; + let hdr = Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"); headers.insert( header::TRANSFER_ENCODING, - header::HeaderValue::from_str(s).unwrap(), + header::HeaderValue::from_shared(hdr).unwrap(), ); let req = HttpRequest::new( Method::GET, diff --git a/src/httprequest.rs b/src/httprequest.rs index 9f051aa8..d511d090 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,9 +1,8 @@ //! HTTP Request message related code. -#![cfg_attr(feature = "cargo-clippy", allow(transmute_ptr_to_ptr))] use std::collections::HashMap; use std::net::SocketAddr; use std::rc::Rc; -use std::{cmp, fmt, io, mem, str}; +use std::{cmp, fmt, io, str}; use bytes::Bytes; use cookie::Cookie; @@ -446,13 +445,13 @@ impl HttpRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Params { - unsafe { mem::transmute(&self.as_ref().params) } + &self.as_ref().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) } + &mut self.as_mut().params } /// Checks if a connection should be kept alive. diff --git a/src/uri.rs b/src/uri.rs index 61ee9352..aa6f767d 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -146,6 +146,8 @@ impl Quoter { } if let Some(data) = cloned { + // we get data from http::Uri, which does utf-8 checks already + // this code only decodes valid pct encoded values Some(unsafe { Rc::new(String::from_utf8_unchecked(data)) }) } else { None From 2f917f37000a1ec72250aa5ec087df00f07e7538 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Jun 2018 01:27:41 +0600 Subject: [PATCH 0410/1635] various cleanups and comments --- src/application.rs | 49 +++++++++++++++++++---------------- src/error.rs | 14 ++-------- src/header/shared/entity.rs | 2 +- src/header/shared/httpdate.rs | 6 +---- src/httpresponse.rs | 16 ++++++++++++ src/param.rs | 5 ++-- src/pipeline.rs | 44 +++++++++++++++++-------------- src/router.rs | 38 +++++++++++++-------------- src/scope.rs | 42 ++++++++++++++++++------------ src/ws/mask.rs | 46 ++++++++++++++------------------ 10 files changed, 137 insertions(+), 125 deletions(-) diff --git a/src/application.rs b/src/application.rs index 3c64e925..93008b3d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,4 @@ -use std::cell::{RefCell, UnsafeCell}; +use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -21,7 +21,7 @@ pub struct HttpApplication { prefix: String, prefix_len: usize, router: Router, - inner: Rc>>, + inner: Rc>>, filters: Option>>>, middlewares: Rc>>>>, } @@ -69,17 +69,12 @@ impl PipelineHandler for Inner { } impl HttpApplication { - #[inline] - fn as_ref(&self) -> &Inner { - unsafe { &*self.inner.get() } - } - #[inline] fn get_handler(&self, req: &mut HttpRequest) -> HandlerType { if let Some(idx) = self.router.recognize(req) { HandlerType::Normal(idx) } else { - let inner = self.as_ref(); + let inner = self.inner.borrow(); req.match_info_mut().set_tail(0); 'outer: for idx in 0..inner.handlers.len() { @@ -131,7 +126,7 @@ impl HttpApplication { #[cfg(test)] pub(crate) fn run(&mut self, mut req: HttpRequest) -> AsyncResult { let tp = self.get_handler(&mut req); - unsafe { &mut *self.inner.get() }.handle(req, tp) + self.inner.borrow_mut().handle(req, tp) } #[cfg(test)] @@ -340,24 +335,32 @@ where T: FromRequest + 'static, { { - let parts: &mut ApplicationParts = unsafe { - &mut *(self.parts.as_mut().expect("Use after finish") as *mut _) - }; + let parts = self.parts.as_mut().expect("Use after finish"); // get resource handler - for &mut (ref pattern, ref mut handler) in &mut parts.resources { - if let Some(ref mut handler) = *handler { - if pattern.pattern() == path { - handler.method(method).with(f); - return self; - } + let mut found = false; + for &mut (ref pattern, ref handler) in &mut parts.resources { + if handler.is_some() && pattern.pattern() == path { + found = true; + break; } } - let mut handler = ResourceHandler::default(); - handler.method(method).with(f); - let pattern = Resource::new(handler.get_name(), path); - parts.resources.push((pattern, Some(handler))); + if !found { + let mut handler = ResourceHandler::default(); + handler.method(method).with(f); + let pattern = Resource::new(handler.get_name(), path); + parts.resources.push((pattern, Some(handler))); + } else { + for &mut (ref pattern, ref mut handler) in &mut parts.resources { + if let Some(ref mut handler) = *handler { + if pattern.pattern() == path { + handler.method(method).with(f); + break; + } + } + } + } } self } @@ -626,7 +629,7 @@ where let (router, resources) = Router::new(&prefix, parts.settings, resources); - let inner = Rc::new(UnsafeCell::new(Inner { + let inner = Rc::new(RefCell::new(Inner { prefix: prefix_len, default: parts.default, encoding: parts.encoding, diff --git a/src/error.rs b/src/error.rs index 395418d9..f011733b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,7 @@ use std::io::Error as IoError; use std::str::Utf8Error; use std::string::FromUtf8Error; use std::sync::Mutex; -use std::{fmt, io, mem, result}; +use std::{fmt, io, result}; use actix::MailboxError; use cookie; @@ -22,7 +22,6 @@ pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; -use body::Body; use handler::Responder; use httprequest::HttpRequest; use httpresponse::{HttpResponse, InnerHttpResponse}; @@ -671,16 +670,7 @@ impl InternalError { /// Create `InternalError` with predefined `HttpResponse`. pub fn from_response(cause: T, response: HttpResponse) -> Self { let mut resp = response.into_inner(); - let body = mem::replace(&mut resp.body, Body::Empty); - match body { - Body::Empty => (), - Body::Binary(mut bin) => { - resp.body = Body::Binary(bin.take().into()); - } - Body::Streaming(_) | Body::Actor(_) => { - error!("Streaming or Actor body is not support by error response"); - } - } + resp.drop_unsupported_body(); InternalError { cause, diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs index a83ce195..0d3b0a4e 100644 --- a/src/header/shared/entity.rs +++ b/src/header/shared/entity.rs @@ -161,7 +161,7 @@ impl IntoHeaderValue for EntityTag { fn try_into(self) -> Result { let mut wrt = Writer::new(); write!(wrt, "{}", self).unwrap(); - unsafe { Ok(HeaderValue::from_shared_unchecked(wrt.take())) } + HeaderValue::from_shared(wrt.take()) } } diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs index 60075e1a..7fd26b12 100644 --- a/src/header/shared/httpdate.rs +++ b/src/header/shared/httpdate.rs @@ -64,11 +64,7 @@ impl IntoHeaderValue for HttpDate { fn try_into(self) -> Result { let mut wrt = BytesMut::with_capacity(29).writer(); write!(wrt, "{}", self.0.rfc822()).unwrap(); - unsafe { - Ok(HeaderValue::from_shared_unchecked( - wrt.get_mut().take().freeze(), - )) - } + HeaderValue::from_shared(wrt.get_mut().take().freeze()) } } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index a0eb46a6..8caf470c 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -894,7 +894,9 @@ pub(crate) struct InnerHttpResponse { error: Option, } +/// This is here only because `failure::Fail: Send + Sync` which looks insane to me unsafe impl Sync for InnerHttpResponse {} +/// This is here only because `failure::Fail: Send + Sync` which looks insane to me unsafe impl Send for InnerHttpResponse {} impl InnerHttpResponse { @@ -914,6 +916,20 @@ impl InnerHttpResponse { error: None, } } + + /// This is for failure, we can not have Send + Sync on Streaming and Actor response + pub(crate) fn drop_unsupported_body(&mut self) { + let body = mem::replace(&mut self.body, Body::Empty); + match body { + Body::Empty => (), + Body::Binary(mut bin) => { + self.body = Body::Binary(bin.take().into()); + } + Body::Streaming(_) | Body::Actor(_) => { + error!("Streaming or Actor body is not support by error response"); + } + } + } } /// Internal use only! unsafe diff --git a/src/param.rs b/src/param.rs index 02c1aea8..1329ff68 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,10 +1,11 @@ -use http::StatusCode; -use smallvec::SmallVec; use std; use std::ops::Index; use std::path::PathBuf; use std::str::FromStr; +use http::StatusCode; +use smallvec::SmallVec; + use error::{InternalError, ResponseError, UriSegmentError}; use uri::Url; diff --git a/src/pipeline.rs b/src/pipeline.rs index 91e2143e..2e03c8f6 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -124,7 +124,7 @@ impl PipelineInfo { impl> Pipeline { pub fn new( req: HttpRequest, mws: Rc>>>>, - handler: Rc>, htype: HandlerType, + handler: Rc>, htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { mws, @@ -133,7 +133,7 @@ impl> Pipeline { error: None, context: None, disconnected: None, - encoding: unsafe { &*handler.get() }.encoding(), + encoding: handler.borrow().encoding(), }; let state = StartMiddlewares::init(&mut info, handler, htype); @@ -171,13 +171,12 @@ impl> HttpHandlerTask for Pipeline { } fn poll_io(&mut self, io: &mut Writer) -> Poll { - let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) }; + let mut state = mem::replace(&mut self.1, PipelineState::None); loop { - if self.1.is_response() { - let state = mem::replace(&mut self.1, PipelineState::None); + if state.is_response() { if let PipelineState::Response(st) = state { - match st.poll_io(io, info) { + match st.poll_io(io, &mut self.0) { Ok(state) => { self.1 = state; if let Some(error) = self.0.error.take() { @@ -193,7 +192,7 @@ impl> HttpHandlerTask for Pipeline { } } } - match self.1 { + match state { PipelineState::None => return Ok(Async::Ready(true)), PipelineState::Error => { return Err( @@ -203,27 +202,32 @@ impl> HttpHandlerTask for Pipeline { _ => (), } - match self.1.poll(info) { - Some(state) => self.1 = state, - None => return Ok(Async::NotReady), + match state.poll(&mut self.0) { + Some(st) => state = st, + None => { + return { + self.1 = state; + Ok(Async::NotReady) + } + } } } } fn poll_completed(&mut self) -> Poll<(), Error> { - let info: &mut PipelineInfo<_> = unsafe { &mut *(&mut self.0 as *mut _) }; - + let mut state = mem::replace(&mut self.1, PipelineState::None); loop { - match self.1 { + match state { PipelineState::None | PipelineState::Error => { return Ok(Async::Ready(())) } _ => (), } - if let Some(state) = self.1.poll(info) { - self.1 = state; + if let Some(st) = state.poll(&mut self.0) { + state = st; } else { + self.1 = state; return Ok(Async::NotReady); } } @@ -234,7 +238,7 @@ type Fut = Box, Error = Error>>; /// Middlewares start executor struct StartMiddlewares { - hnd: Rc>, + hnd: Rc>, htype: HandlerType, fut: Option, _s: PhantomData, @@ -242,14 +246,14 @@ struct StartMiddlewares { impl> StartMiddlewares { fn init( - info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType, + info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType, ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately let len = info.mws.borrow().len() as u16; loop { if info.count == len { - let reply = unsafe { &mut *hnd.get() }.handle(info.req().clone(), htype); + let reply = hnd.borrow_mut().handle(info.req().clone(), htype); return WaitingResponse::init(info, reply); } else { let state = @@ -285,7 +289,9 @@ impl> StartMiddlewares { } loop { if info.count == len { - let reply = unsafe { &mut *self.hnd.get() } + let reply = self + .hnd + .borrow_mut() .handle(info.req().clone(), self.htype); return Some(WaitingResponse::init(info, reply)); } else { diff --git a/src/router.rs b/src/router.rs index 6592f64e..0ae17808 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; -use std::mem; use std::rc::Rc; use regex::{escape, Regex}; +use smallvec::SmallVec; use error::UrlGenerationError; use httprequest::HttpRequest; @@ -264,9 +264,9 @@ impl Resource { pub fn match_with_params( &self, req: &mut HttpRequest, plen: usize, insert: bool, ) -> bool { - let mut segments: [ParamItem; 24] = unsafe { mem::uninitialized() }; + let mut segments: SmallVec<[ParamItem; 5]> = SmallVec::new(); - let (names, segments_len) = { + let names = { let path = &req.path()[plen..]; if insert { if path.is_empty() { @@ -282,7 +282,6 @@ impl Resource { PatternType::Static(ref s) => return s == path, PatternType::Dynamic(ref re, ref names, _) => { if let Some(captures) = re.captures(path) { - let mut idx = 0; let mut passed = false; for capture in captures.iter() { if let Some(ref m) = capture { @@ -290,14 +289,13 @@ impl Resource { passed = true; continue; } - segments[idx] = ParamItem::UrlSegment( + segments.push(ParamItem::UrlSegment( (plen + m.start()) as u16, (plen + m.end()) as u16, - ); - idx += 1; + )); } } - (names, idx) + names } else { return false; } @@ -309,9 +307,11 @@ impl Resource { let len = req.path().len(); let params = req.match_info_mut(); params.set_tail(len as u16); - for idx in 0..segments_len { + for (idx, segment) in segments.iter().enumerate() { + // reason: Router is part of App, which is unique per thread + // app is alive during whole life of tthread let name = unsafe { &*(names[idx].as_str() as *const _) }; - params.add(name, segments[idx]); + params.add(name, *segment); } true } @@ -320,9 +320,9 @@ impl Resource { pub fn match_prefix_with_params( &self, req: &mut HttpRequest, plen: usize, ) -> Option { - let mut segments: [ParamItem; 24] = unsafe { mem::uninitialized() }; + let mut segments: SmallVec<[ParamItem; 5]> = SmallVec::new(); - let (names, segments_len, tail_len) = { + let (names, tail_len) = { let path = &req.path()[plen..]; let path = if path.is_empty() { "/" } else { path }; @@ -334,7 +334,6 @@ impl Resource { }, PatternType::Dynamic(ref re, ref names, len) => { if let Some(captures) = re.captures(path) { - let mut idx = 0; let mut pos = 0; let mut passed = false; for capture in captures.iter() { @@ -344,15 +343,14 @@ impl Resource { continue; } - segments[idx] = ParamItem::UrlSegment( + segments.push(ParamItem::UrlSegment( (plen + m.start()) as u16, (plen + m.end()) as u16, - ); - idx += 1; + )); pos = m.end(); } } - (names, idx, pos + len) + (names, pos + len) } else { return None; } @@ -378,9 +376,11 @@ impl Resource { let params = req.match_info_mut(); params.set_tail(tail_len as u16); - for idx in 0..segments_len { + for (idx, segment) in segments.iter().enumerate() { + // reason: Router is part of App, which is unique per thread + // app is alive during whole life of tthread let name = unsafe { &*(names[idx].as_str() as *const _) }; - params.add(name, segments[idx]); + params.add(name, *segment); } Some(tail_len) } diff --git a/src/scope.rs b/src/scope.rs index e57db763..6651992d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -226,27 +226,35 @@ impl Scope { R: Responder + 'static, T: FromRequest + 'static, { - // get resource handler - let slf: &Scope = unsafe { &*(&self as *const _) }; - for &(ref pattern, ref resource) in slf.resources.iter() { + // check if we have resource handler + let mut found = false; + for &(ref pattern, _) in self.resources.iter() { if pattern.pattern() == path { - resource.borrow_mut().method(method).with(f); - return self; + found = true; + break; } } - let mut handler = ResourceHandler::default(); - handler.method(method).with(f); - let pattern = Resource::with_prefix( - handler.get_name(), - path, - if path.is_empty() { "" } else { "/" }, - false, - ); - Rc::get_mut(&mut self.resources) - .expect("Can not use after configuration") - .push((pattern, Rc::new(RefCell::new(handler)))); - + if found { + for &(ref pattern, ref resource) in self.resources.iter() { + if pattern.pattern() == path { + resource.borrow_mut().method(method).with(f); + break; + } + } + } else { + let mut handler = ResourceHandler::default(); + handler.method(method).with(f); + let pattern = Resource::with_prefix( + handler.get_name(), + path, + if path.is_empty() { "" } else { "/" }, + false, + ); + Rc::get_mut(&mut self.resources) + .expect("Can not use after configuration") + .push((pattern, Rc::new(RefCell::new(handler)))); + } self } diff --git a/src/ws/mask.rs b/src/ws/mask.rs index 18a90675..e99b950c 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -7,7 +7,7 @@ use std::ptr::copy_nonoverlapping; /// Mask/unmask a frame. #[inline] pub fn apply_mask(buf: &mut [u8], mask: u32) { - apply_mask_fast32(buf, mask) + unsafe { apply_mask_fast32(buf, mask) } } /// A safe unoptimized mask application. @@ -20,9 +20,11 @@ fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { } /// Faster version of `apply_mask()` which operates on 8-byte blocks. +/// +/// unsafe because uses pointer math and bit operations for performance #[inline] #[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] -fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { +unsafe fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { let mut ptr = buf.as_mut_ptr(); let mut len = buf.len(); @@ -32,10 +34,8 @@ fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { let n = if head > 4 { head - 4 } else { head }; let mask_u32 = if n > 0 { - unsafe { - xor_mem(ptr, mask_u32, n); - ptr = ptr.offset(head as isize); - } + xor_mem(ptr, mask_u32, n); + ptr = ptr.offset(head as isize); len -= n; if cfg!(target_endian = "big") { mask_u32.rotate_left(8 * n as u32) @@ -47,11 +47,9 @@ fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { }; if head > 4 { - unsafe { - *(ptr as *mut u32) ^= mask_u32; - ptr = ptr.offset(4); - len -= 4; - } + *(ptr as *mut u32) ^= mask_u32; + ptr = ptr.offset(4); + len -= 4; } mask_u32 } else { @@ -68,27 +66,21 @@ fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { mask_u64 = mask_u64 << 32 | mask_u32 as u64; while len >= 8 { - unsafe { - *(ptr as *mut u64) ^= mask_u64; - ptr = ptr.offset(8); - len -= 8; - } + *(ptr as *mut u64) ^= mask_u64; + ptr = ptr.offset(8); + len -= 8; } } while len >= 4 { - unsafe { - *(ptr as *mut u32) ^= mask_u32; - ptr = ptr.offset(4); - len -= 4; - } + *(ptr as *mut u32) ^= mask_u32; + ptr = ptr.offset(4); + len -= 4; } // Possible last block. if len > 0 { - unsafe { - xor_mem(ptr, mask_u32, len); - } + xor_mem(ptr, mask_u32, len); } } @@ -107,7 +99,7 @@ unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) { #[cfg(test)] mod tests { - use super::{apply_mask_fallback, apply_mask_fast32}; + use super::{apply_mask, apply_mask_fallback}; use std::ptr; #[test] @@ -126,7 +118,7 @@ mod tests { apply_mask_fallback(&mut masked, &mask); let mut masked_fast = unmasked.clone(); - apply_mask_fast32(&mut masked_fast, mask_u32); + apply_mask(&mut masked_fast, mask_u32); assert_eq!(masked, masked_fast); } @@ -137,7 +129,7 @@ mod tests { apply_mask_fallback(&mut masked[1..], &mask); let mut masked_fast = unmasked.clone(); - apply_mask_fast32(&mut masked_fast[1..], mask_u32); + apply_mask(&mut masked_fast[1..], mask_u32); assert_eq!(masked, masked_fast); } From 234c60d473a529f353ab51c04f3c7cb9e402fb70 Mon Sep 17 00:00:00 2001 From: Jef Date: Wed, 20 Jun 2018 10:50:56 +0200 Subject: [PATCH 0411/1635] Fix some unsoundness This improves the sound implementation of `fn route`. Previously this function would iterate twice but we can reduce the overhead without using `unsafe`. --- src/application.rs | 41 +++++++++++++++++++++-------------------- src/multipart.rs | 8 ++++---- src/server/h1writer.rs | 31 ++++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/application.rs b/src/application.rs index 93008b3d..b28c1829 100644 --- a/src/application.rs +++ b/src/application.rs @@ -335,33 +335,34 @@ where T: FromRequest + 'static, { { - let parts = self.parts.as_mut().expect("Use after finish"); + let parts: &mut ApplicationParts = self.parts.as_mut().expect("Use after finish"); - // get resource handler - let mut found = false; - for &mut (ref pattern, ref handler) in &mut parts.resources { - if handler.is_some() && pattern.pattern() == path { - found = true; - break; - } - } + let out = { + // get resource handler + let mut iterator = parts.resources.iter_mut(); - if !found { - let mut handler = ResourceHandler::default(); - handler.method(method).with(f); - let pattern = Resource::new(handler.get_name(), path); - parts.resources.push((pattern, Some(handler))); - } else { - for &mut (ref pattern, ref mut handler) in &mut parts.resources { - if let Some(ref mut handler) = *handler { - if pattern.pattern() == path { - handler.method(method).with(f); - break; + loop { + if let Some(&mut (ref pattern, ref mut handler)) = iterator.next() { + if let Some(ref mut handler) = *handler { + if pattern.pattern() == path { + handler.method(method).with(f); + break None; + } } + } else { + let mut handler = ResourceHandler::default(); + handler.method(method).with(f); + let pattern = Resource::new(handler.get_name(), path); + break Some((pattern, Some(handler))); } } + }; + + if let Some(out) = out { + parts.resources.push(out); } } + self } diff --git a/src/multipart.rs b/src/multipart.rs index 9c5c0380..7c93b565 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -1,5 +1,5 @@ //! Multipart requests support -use std::cell::RefCell; +use std::cell::{RefCell, UnsafeCell}; use std::marker::PhantomData; use std::rc::Rc; use std::{cmp, fmt}; @@ -590,7 +590,7 @@ where } struct PayloadRef { - payload: Rc>, + payload: Rc>>, } impl PayloadRef @@ -599,7 +599,7 @@ where { fn new(payload: PayloadHelper) -> PayloadRef { PayloadRef { - payload: Rc::new(payload), + payload: Rc::new(payload.into()), } } @@ -609,7 +609,7 @@ where { if s.current() { let payload: &mut PayloadHelper = - unsafe { &mut *(self.payload.as_ref() as *const _ as *mut _) }; + unsafe { &mut *self.payload.get() }; Some(payload) } else { None diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index d174964b..ebb0fff3 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -73,12 +73,11 @@ impl H1Writer { self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) } - fn write_data(&mut self, data: &[u8]) -> io::Result { + fn write_data(stream: &mut T, data: &[u8]) -> io::Result { let mut written = 0; while written < data.len() { - match self.stream.write(&data[written..]) { + match stream.write(&data[written..]) { Ok(0) => { - self.disconnected(); return Err(io::Error::new(io::ErrorKind::WriteZero, "")); } Ok(n) => { @@ -243,7 +242,16 @@ impl Writer for H1Writer { if self.flags.contains(Flags::UPGRADE) { if self.buffer.is_empty() { let pl: &[u8] = payload.as_ref(); - let n = self.write_data(pl)?; + let n = match Self::write_data(&mut self.stream, pl) { + Err(err) => { + if err.kind() == io::ErrorKind::WriteZero { + self.disconnected(); + } + + return Err(err); + } + Ok(val) => val, + }; if n < pl.len() { self.buffer.extend_from_slice(&pl[n..]); return Ok(WriterState::Done); @@ -284,9 +292,18 @@ impl Writer for H1Writer { #[inline] fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { if !self.buffer.is_empty() { - let buf: &[u8] = - unsafe { &mut *(self.buffer.as_ref() as *const _ as *mut _) }; - let written = self.write_data(buf)?; + let written = { + match Self::write_data(&mut self.stream, self.buffer.as_ref()) { + Err(err) => { + if err.kind() == io::ErrorKind::WriteZero { + self.disconnected(); + } + + return Err(err); + } + Ok(val) => val, + } + }; let _ = self.buffer.split_to(written); if shutdown && !self.buffer.is_empty() || (self.buffer.len() > self.buffer_capacity) From da237611cb429da5fb42ab9d6be52837d1de3dda Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Wed, 20 Jun 2018 13:14:53 +0200 Subject: [PATCH 0412/1635] remove unnecessary use of unsafe in read_from_io --- src/server/utils.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/server/utils.rs b/src/server/utils.rs index e0e7e7f6..b0470d76 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -1,5 +1,5 @@ use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; +use futures::Poll; use std::io; use super::IoStream; @@ -10,22 +10,8 @@ const HW_BUFFER_SIZE: usize = 32_768; pub fn read_from_io( io: &mut T, buf: &mut BytesMut, ) -> Poll { - unsafe { - 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 { - Ok(Async::NotReady) - } else { - Err(e) - } - } - } + if buf.remaining_mut() < LW_BUFFER_SIZE { + buf.reserve(HW_BUFFER_SIZE); } + io.read_buf(buf) } From bd8cbfff351e7a5b4ae06135c4290113308bb848 Mon Sep 17 00:00:00 2001 From: Thomas Broadley Date: Wed, 20 Jun 2018 21:05:26 -0400 Subject: [PATCH 0413/1635] docs: fix typos --- CHANGES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b8ca7d47..bf5cbe9c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -39,7 +39,7 @@ * Added header `User-Agent: Actix-web/` to default headers when building a request -* port `Extensions` type from http create, we dont need `Send + Sync` +* port `Extensions` type from http create, we don't need `Send + Sync` * `HttpRequest::query()` returns `&HashMap` @@ -88,7 +88,7 @@ ### Added -* Allow to use path without traling slashes for scope registration #241 +* Allow to use path without trailing slashes for scope registration #241 * Allow to set encoding for exact NamedFile #239 @@ -449,7 +449,7 @@ * Server multi-threading -* Gracefull shutdown support +* Graceful shutdown support ## 0.2.1 (2017-11-03) From f815c1c096fe60a8d638d125c3c1bb08c11c081a Mon Sep 17 00:00:00 2001 From: Josh Leeb-du Toit Date: Sun, 3 Jun 2018 17:48:12 +1000 Subject: [PATCH 0414/1635] Add test for default_resource scope propagation --- src/scope.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/scope.rs b/src/scope.rs index 6651992d..6cc4929e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1188,4 +1188,27 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } + + #[test] + fn test_default_resource_propagation() { + let mut app = App::new() + .scope("/app1", |scope| { + scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) + }) + .scope("/app2", |scope| scope) + .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) + .finish(); + + let req = TestRequest::with_uri("/non-exist").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); + + let req = TestRequest::with_uri("/app1/non-exist").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); + + let req = TestRequest::with_uri("/app2/non-exist").finish(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); + } } From cfe6725eb4c8365502cab6be3a4eb9894deddb4a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 09:38:59 +0600 Subject: [PATCH 0415/1635] Allow to disable masking for websockets client --- CHANGES.md | 4 ++++ src/ws/client.rs | 22 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b8ca7d47..30a31bfb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -50,6 +50,10 @@ * Remove `HttpMessage::range()` +## [0.6.14] - 2018-06-21 + +* Allow to disable masking for websockets client + ## [0.6.13] - 2018-06-11 diff --git a/src/ws/client.rs b/src/ws/client.rs index e9b7cf82..6a4fcf7c 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -127,6 +127,7 @@ pub struct Client { protocols: Option, conn: Addr, max_size: usize, + no_masking: bool, } impl Client { @@ -144,6 +145,7 @@ impl Client { origin: None, protocols: None, max_size: 65_536, + no_masking: false, conn, }; cl.request.uri(uri.as_ref()); @@ -198,6 +200,12 @@ impl Client { self } + /// Disable payload masking. By default ws client masks frame payload. + pub fn no_masking(mut self) -> Self { + self.no_masking = true; + self + } + /// Set request header pub fn header(mut self, key: K, value: V) -> Self where @@ -260,7 +268,7 @@ impl Client { } // start handshake - ClientHandshake::new(request, self.max_size) + ClientHandshake::new(request, self.max_size, self.no_masking) } } } @@ -281,10 +289,13 @@ pub struct ClientHandshake { key: String, error: Option, max_size: usize, + no_masking: bool, } impl ClientHandshake { - fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake { + fn new( + mut request: ClientRequest, max_size: usize, no_masking: bool, + ) -> ClientHandshake { // Generate a random key for the `Sec-WebSocket-Key` header. // a base64-encoded (see Section 4 of [RFC4648]) value that, // when decoded, is 16 bytes in length (RFC 6455) @@ -304,6 +315,7 @@ impl ClientHandshake { ClientHandshake { key, max_size, + no_masking, request: Some(request.send()), tx: Some(tx), error: None, @@ -317,6 +329,7 @@ impl ClientHandshake { tx: None, error: Some(err), max_size: 0, + no_masking: false, } } @@ -427,6 +440,7 @@ impl Future for ClientHandshake { ClientReader { inner: Rc::clone(&inner), max_size: self.max_size, + no_masking: self.no_masking, }, ClientWriter { inner }, ))) @@ -437,6 +451,7 @@ impl Future for ClientHandshake { pub struct ClientReader { inner: Rc>, max_size: usize, + no_masking: bool, } impl fmt::Debug for ClientReader { @@ -451,13 +466,14 @@ impl Stream for ClientReader { fn poll(&mut self) -> Poll, Self::Error> { let max_size = self.max_size; + let no_masking = self.no_masking; let mut inner = self.inner.borrow_mut(); if inner.closed { return Ok(Async::Ready(None)); } // read - match Frame::parse(&mut inner.rx, false, max_size) { + match Frame::parse(&mut inner.rx, no_masking, max_size) { Ok(Async::Ready(Some(frame))) => { let (_finished, opcode, payload) = frame.unpack(); From 8b0fbb85d135435c62325408f7e00a1d80b6f63a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 09:47:28 +0600 Subject: [PATCH 0416/1635] SendRequest execution fails with the entered unreachable code #329 --- CHANGES.md | 6 ++++++ src/client/pipeline.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 30a31bfb..b8c4b7e6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -52,8 +52,14 @@ ## [0.6.14] - 2018-06-21 +### Added + * Allow to disable masking for websockets client +### Fixed + +* SendRequest execution fails with the "internal error: entered unreachable code" #329 + ## [0.6.13] - 2018-06-11 diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index e77894b2..50318a16 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -392,7 +392,7 @@ impl Pipeline { match self.timeout.as_mut().unwrap().poll() { Ok(Async::Ready(())) => return Err(SendRequestError::Timeout), Ok(Async::NotReady) => (), - Err(_) => return Err(SendRequestError::Timeout), + Err(e) => return Err(e.into()), } } Ok(()) From 1be27e17f88b0f64ad374fd655f53dbdfbd39744 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 10:05:20 +0600 Subject: [PATCH 0417/1635] convert timer error to io error --- src/client/pipeline.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 50318a16..4173c7d2 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -392,7 +392,7 @@ impl Pipeline { match self.timeout.as_mut().unwrap().poll() { Ok(Async::Ready(())) => return Err(SendRequestError::Timeout), Ok(Async::NotReady) => (), - Err(e) => return Err(e.into()), + Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()), } } Ok(()) From c2c4a5ba3f696f58ea67355da75c09de7b182e9f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 10:45:24 +0600 Subject: [PATCH 0418/1635] fix failure Send+Sync compatibility --- src/error.rs | 10 +++--- src/httpresponse.rs | 74 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/src/error.rs b/src/error.rs index f011733b..3141ad2a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,7 +24,7 @@ pub use cookie::ParseError as CookieParseError; use handler::Responder; use httprequest::HttpRequest; -use httpresponse::{HttpResponse, InnerHttpResponse}; +use httpresponse::{HttpResponse, HttpResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -654,7 +654,7 @@ pub struct InternalError { enum InternalErrorType { Status(StatusCode), - Response(Mutex>>), + Response(Mutex>), } impl InternalError { @@ -669,9 +669,7 @@ impl InternalError { /// Create `InternalError` with predefined `HttpResponse`. pub fn from_response(cause: T, response: HttpResponse) -> Self { - let mut resp = response.into_inner(); - resp.drop_unsupported_body(); - + let resp = response.into_parts(); InternalError { cause, status: InternalErrorType::Response(Mutex::new(Some(resp))), @@ -716,7 +714,7 @@ where InternalErrorType::Status(st) => HttpResponse::new(st), InternalErrorType::Response(ref resp) => { if let Some(resp) = resp.lock().unwrap().take() { - HttpResponse::from_inner(resp) + HttpResponse::from_parts(resp) } else { HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) } diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 8caf470c..4e139ef6 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -299,12 +299,15 @@ impl HttpResponse { self.get_mut().write_capacity = cap; } - pub(crate) fn into_inner(mut self) -> Box { - self.0.take().unwrap() + pub(crate) fn into_parts(mut self) -> HttpResponseParts { + self.0.take().unwrap().into_parts() } - pub(crate) fn from_inner(inner: Box) -> HttpResponse { - HttpResponse(Some(inner), HttpResponsePool::pool()) + pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse { + HttpResponse( + Some(Box::new(InnerHttpResponse::from_parts(parts))), + HttpResponsePool::pool(), + ) } } @@ -880,12 +883,12 @@ impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { } #[derive(Debug)] -pub(crate) struct InnerHttpResponse { +struct InnerHttpResponse { version: Option, headers: HeaderMap, status: StatusCode, reason: Option<&'static str>, - pub(crate) body: Body, + body: Body, chunked: Option, encoding: Option, connection_type: Option, @@ -894,10 +897,16 @@ pub(crate) struct InnerHttpResponse { error: Option, } -/// This is here only because `failure::Fail: Send + Sync` which looks insane to me -unsafe impl Sync for InnerHttpResponse {} -/// This is here only because `failure::Fail: Send + Sync` which looks insane to me -unsafe impl Send for InnerHttpResponse {} +pub(crate) struct HttpResponseParts { + version: Option, + headers: HeaderMap, + status: StatusCode, + reason: Option<&'static str>, + body: Option, + encoding: Option, + connection_type: Option, + error: Option, +} impl InnerHttpResponse { #[inline] @@ -918,16 +927,47 @@ impl InnerHttpResponse { } /// This is for failure, we can not have Send + Sync on Streaming and Actor response - pub(crate) fn drop_unsupported_body(&mut self) { - let body = mem::replace(&mut self.body, Body::Empty); - match body { - Body::Empty => (), - Body::Binary(mut bin) => { - self.body = Body::Binary(bin.take().into()); - } + fn into_parts(mut self) -> HttpResponseParts { + let body = match mem::replace(&mut self.body, Body::Empty) { + Body::Empty => None, + Body::Binary(mut bin) => Some(bin.take()), Body::Streaming(_) | Body::Actor(_) => { error!("Streaming or Actor body is not support by error response"); + None } + }; + + HttpResponseParts { + body, + version: self.version, + headers: self.headers, + status: self.status, + reason: self.reason, + encoding: self.encoding, + connection_type: self.connection_type, + error: self.error, + } + } + + fn from_parts(parts: HttpResponseParts) -> InnerHttpResponse { + let body = if let Some(ref body) = parts.body { + Body::Binary(body.clone().into()) + } else { + Body::Empty + }; + + InnerHttpResponse { + body, + status: parts.status, + version: parts.version, + headers: parts.headers, + reason: parts.reason, + chunked: None, + encoding: parts.encoding, + connection_type: parts.connection_type, + response_size: 0, + write_capacity: MAX_WRITE_BUFFER_SIZE, + error: parts.error, } } } From ebc59cf7b941a9004e1d37a97dbd407fd74db125 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 11:20:21 +0600 Subject: [PATCH 0419/1635] add unsafe checks #331 --- src/with.rs | 80 +++++++++++++++++++++++++++++------------- tests/test_handlers.rs | 22 ++++++++++++ 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/src/with.rs b/src/with.rs index 4cb1546a..423e558a 100644 --- a/src/with.rs +++ b/src/with.rs @@ -111,8 +111,18 @@ where T: FromRequest, S: 'static, { - hnd: Rc>, + hnd: Rc>, cfg: ExtractorConfig, +} + +pub struct WithHnd +where + F: Fn(T) -> R, + T: FromRequest, + S: 'static, +{ + hnd: Rc>, + _t: PhantomData, _s: PhantomData, } @@ -125,8 +135,11 @@ where pub fn new(f: F, cfg: ExtractorConfig) -> Self { With { cfg, - hnd: Rc::new(UnsafeCell::new(f)), - _s: PhantomData, + hnd: Rc::new(WithHnd { + hnd: Rc::new(UnsafeCell::new(f)), + _t: PhantomData, + _s: PhantomData, + }), } } } @@ -166,7 +179,7 @@ where S: 'static, { started: bool, - hnd: Rc>, + hnd: Rc>, cfg: ExtractorConfig, req: HttpRequest, fut1: Option>>, @@ -206,20 +219,28 @@ where } }; - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - let item = match (*hnd)(item).respond_to(&self.req) { - Ok(item) => item.into(), - Err(e) => return Err(e.into()), - }; - - match item.into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut2 = Some(fut); - self.poll() + let fut = { + // clone handler, inicrease ref counter + let h = self.hnd.as_ref().hnd.clone(); + // Enforce invariants before entering unsafe code. + // Only two references could exists With struct owns one, and line above + if Rc::weak_count(&h) != 0 && Rc::strong_count(&h) != 2 { + panic!("Multiple copies of handler are in use") } - } + let hnd: &mut F = unsafe { &mut *h.as_ref().get() }; + let item = match (*hnd)(item).respond_to(&self.req) { + Ok(item) => item.into(), + Err(e) => return Err(e.into()), + }; + + match item.into() { + AsyncResultItem::Err(err) => return Err(err), + AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => fut, + } + }; + self.fut2 = Some(fut); + self.poll() } } @@ -232,9 +253,8 @@ where T: FromRequest, S: 'static, { - hnd: Rc>, + hnd: Rc>, cfg: ExtractorConfig, - _s: PhantomData, } impl WithAsync @@ -249,8 +269,11 @@ where pub fn new(f: F, cfg: ExtractorConfig) -> Self { WithAsync { cfg, - hnd: Rc::new(UnsafeCell::new(f)), - _s: PhantomData, + hnd: Rc::new(WithHnd { + hnd: Rc::new(UnsafeCell::new(f)), + _s: PhantomData, + _t: PhantomData, + }), } } } @@ -295,7 +318,7 @@ where S: 'static, { started: bool, - hnd: Rc>, + hnd: Rc>, cfg: ExtractorConfig, req: HttpRequest, fut1: Option>>, @@ -356,8 +379,17 @@ where } }; - let hnd: &mut F = unsafe { &mut *self.hnd.get() }; - self.fut2 = Some((*hnd)(item)); + self.fut2 = { + // clone handler, inicrease ref counter + let h = self.hnd.as_ref().hnd.clone(); + // Enforce invariants before entering unsafe code. + // Only two references could exists With struct owns one, and line above + if Rc::weak_count(&h) != 0 && Rc::strong_count(&h) != 2 { + panic!("Multiple copies of handler are in use") + } + let hnd: &mut F = unsafe { &mut *h.as_ref().get() }; + Some((*hnd)(item)) + }; self.poll() } } diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 116112e2..57309f83 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -42,6 +42,28 @@ fn test_path_extractor() { assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); } +#[test] +fn test_async_handler() { + let mut srv = test::TestServer::new(|app| { + app.resource("/{username}/index.html", |r| { + r.route().with(|p: Path| { + Delay::new(Instant::now() + Duration::from_millis(10)) + .and_then(move |_| Ok(format!("Welcome {}!", p.username))) + .responder() + }) + }); + }); + + // client request + let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); +} + #[test] fn test_query_extractor() { let mut srv = test::TestServer::new(|app| { From 75eec8bd4f0cf0277d8934ffe7c69cfd8dec0396 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 11:23:13 +0600 Subject: [PATCH 0420/1635] fix condition --- src/with.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/with.rs b/src/with.rs index 423e558a..4f53a316 100644 --- a/src/with.rs +++ b/src/with.rs @@ -224,7 +224,7 @@ where let h = self.hnd.as_ref().hnd.clone(); // Enforce invariants before entering unsafe code. // Only two references could exists With struct owns one, and line above - if Rc::weak_count(&h) != 0 && Rc::strong_count(&h) != 2 { + if Rc::weak_count(&h) != 0 || Rc::strong_count(&h) != 2 { panic!("Multiple copies of handler are in use") } let hnd: &mut F = unsafe { &mut *h.as_ref().get() }; @@ -384,7 +384,7 @@ where let h = self.hnd.as_ref().hnd.clone(); // Enforce invariants before entering unsafe code. // Only two references could exists With struct owns one, and line above - if Rc::weak_count(&h) != 0 && Rc::strong_count(&h) != 2 { + if Rc::weak_count(&h) != 0 || Rc::strong_count(&h) != 2 { panic!("Multiple copies of handler are in use") } let hnd: &mut F = unsafe { &mut *h.as_ref().get() }; From 0093b7ea5ae5f6e23a247adce968ce1694c4e745 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 11:47:01 +0600 Subject: [PATCH 0421/1635] refactor extractor configuration #331 --- MIGRATION.md | 32 +++++++++++++ src/extractor.rs | 9 ++-- src/lib.rs | 1 - src/route.rs | 95 +++++++++++++++++++++++++++++++++---- src/with.rs | 119 +++++------------------------------------------ 5 files changed, 134 insertions(+), 122 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 175d82b3..73e2d565 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -23,6 +23,38 @@ * Renamed `client::ClientConnectorError::Connector` to `client::ClientConnectorError::Resolver` +* `Route::with()` does not return `ExtractorConfig`, to configure + extractor use `Route::with_config()` + + instead of + + ```rust + fn main() { + let app = App::new().resource("/index.html", |r| { + r.method(http::Method::GET) + .with(index) + .limit(4096); // <- limit size of the payload + }); + } + ``` + + use + + ```rust + + fn main() { + let app = App::new().resource("/index.html", |r| { + r.method(http::Method::GET) + .with_config(index, |cfg| { // <- register handler + cfg.limit(4096); // <- limit size of the payload + }) + }); + } + ``` + +* `Route::with_async()` does not return `ExtractorConfig`, to configure + extractor use `Route::with_async_config()` + ## 0.6 diff --git a/src/extractor.rs b/src/extractor.rs index 0cdcb3af..425bc785 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; -use std::{fmt, str}; use std::rc::Rc; +use std::{fmt, str}; use bytes::Bytes; use encoding::all::UTF_8; @@ -328,7 +328,7 @@ impl FormConfig { self.limit = limit; self } - + /// Set custom error handler pub fn error_handler(&mut self, f: F) -> &mut Self where @@ -408,8 +408,9 @@ impl FromRequest for Bytes { /// fn main() { /// let app = App::new().resource("/index.html", |r| { /// r.method(http::Method::GET) -/// .with(index) // <- register handler with extractor params -/// .limit(4096); // <- limit size of the payload +/// .with_config(index, |cfg| { // <- register handler with extractor params +/// cfg.limit(4096); // <- limit size of the payload +/// }) /// }); /// } /// ``` diff --git a/src/lib.rs b/src/lib.rs index 95f5a4ee..90b74381 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,7 +246,6 @@ pub mod dev { pub use resource::ResourceHandler; pub use route::Route; pub use router::{Resource, ResourceType, Router}; - pub use with::ExtractorConfig; } pub mod http { diff --git a/src/route.rs b/src/route.rs index 524b66ef..7ce1104c 100644 --- a/src/route.rs +++ b/src/route.rs @@ -17,7 +17,7 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; -use with::{ExtractorConfig, With, WithAsync}; +use with::{With, WithAsync}; /// Resource route definition /// @@ -164,15 +164,49 @@ impl Route { /// ); // <- use `with` extractor /// } /// ``` - pub fn with(&mut self, handler: F) -> ExtractorConfig + pub fn with(&mut self, handler: F) where F: Fn(T) -> R + 'static, R: Responder + 'static, T: FromRequest + 'static, { - let cfg = ExtractorConfig::::default(); - self.h(With::new(handler, cfg.clone())); - cfg + self.h(With::new(handler, ::default())); + } + + /// Set handler function. Same as `.with()` but it allows to configure + /// extractor. + /// + /// ```rust + /// # extern crate bytes; + /// # extern crate actix_web; + /// # extern crate futures; + /// #[macro_use] extern crate serde_derive; + /// use actix_web::{http, App, Path, Result}; + /// + /// /// extract text data from request + /// fn index(body: String) -> Result { + /// Ok(format!("Body {}!", body)) + /// } + /// + /// fn main() { + /// let app = App::new().resource("/index.html", |r| { + /// r.method(http::Method::GET) + /// .with_config(index, |cfg| { // <- register handler + /// cfg.limit(4096); // <- limit size of the payload + /// }) + /// }); + /// } + /// ``` + pub fn with_config(&mut self, handler: F, cfg_f: C) + where + F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, + C: FnOnce(&mut T::Config), + { + let mut cfg = ::default(); + cfg_f(&mut cfg); + self.h(With::new(handler, cfg)); } /// Set async handler function, use request extractor for parameters. @@ -204,7 +238,7 @@ impl Route { /// ); // <- use `with` extractor /// } /// ``` - pub fn with_async(&mut self, handler: F) -> ExtractorConfig + pub fn with_async(&mut self, handler: F) where F: Fn(T) -> R + 'static, R: Future + 'static, @@ -212,9 +246,52 @@ impl Route { E: Into + 'static, T: FromRequest + 'static, { - let cfg = ExtractorConfig::::default(); - self.h(WithAsync::new(handler, cfg.clone())); - cfg + self.h(WithAsync::new(handler, ::default())); + } + + /// Set async handler function, use request extractor for parameters. + /// This method allows to configure extractor. + /// + /// ```rust + /// # extern crate bytes; + /// # extern crate actix_web; + /// # extern crate futures; + /// #[macro_use] extern crate serde_derive; + /// use actix_web::{http, App, Error, Path}; + /// use futures::Future; + /// + /// #[derive(Deserialize)] + /// struct Info { + /// username: String, + /// } + /// + /// /// extract path info using serde + /// fn index(info: Form) -> Box> { + /// unimplemented!() + /// } + /// + /// fn main() { + /// let app = App::new().resource( + /// "/{username}/index.html", // <- define path parameters + /// |r| r.method(http::Method::GET) + /// .with_async_config(index, |cfg| { + /// cfg.limit(4096); + /// }), + /// ); // <- use `with` extractor + /// } + /// ``` + pub fn with_async_config(&mut self, handler: F, cfg: C) + where + F: Fn(T) -> R + 'static, + R: Future + 'static, + I: Responder + 'static, + E: Into + 'static, + T: FromRequest + 'static, + C: FnOnce(&mut T::Config), + { + let mut extractor_cfg = ::default(); + cfg(&mut extractor_cfg); + self.h(WithAsync::new(handler, extractor_cfg)); } } diff --git a/src/with.rs b/src/with.rs index 4f53a316..126958b5 100644 --- a/src/with.rs +++ b/src/with.rs @@ -1,7 +1,6 @@ use futures::{Async, Future, Poll}; use std::cell::UnsafeCell; use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; use std::rc::Rc; use error::Error; @@ -9,110 +8,14 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -/// Extractor configuration -/// -/// `Route::with()` and `Route::with_async()` returns instance -/// of the `ExtractorConfig` type. It could be used for extractor configuration. -/// -/// In this example `Form` configured. -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Form, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| { -/// r.method(http::Method::GET).with(index).limit(4096); -/// }, // <- change form extractor configuration -/// ); -/// } -/// ``` -/// -/// Same could be donce with multiple extractors -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Form, Path, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// fn index(data: (Path<(String,)>, Form)) -> Result { -/// Ok(format!("Welcome {}!", data.1.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| { -/// r.method(http::Method::GET).with(index).1.limit(4096); -/// }, // <- change form extractor configuration -/// ); -/// } -/// ``` -pub struct ExtractorConfig> { - cfg: Rc>, -} - -impl> Default for ExtractorConfig { - fn default() -> Self { - ExtractorConfig { - cfg: Rc::new(UnsafeCell::new(T::Config::default())), - } - } -} - -impl> ExtractorConfig { - pub(crate) fn clone(&self) -> Self { - ExtractorConfig { - cfg: Rc::clone(&self.cfg), - } - } -} - -impl> AsRef for ExtractorConfig { - fn as_ref(&self) -> &T::Config { - unsafe { &*self.cfg.get() } - } -} - -impl> Deref for ExtractorConfig { - type Target = T::Config; - - fn deref(&self) -> &T::Config { - unsafe { &*self.cfg.get() } - } -} - -impl> DerefMut for ExtractorConfig { - fn deref_mut(&mut self) -> &mut T::Config { - unsafe { &mut *self.cfg.get() } - } -} - -pub struct With +pub(crate) struct With where F: Fn(T) -> R, T: FromRequest, S: 'static, { hnd: Rc>, - cfg: ExtractorConfig, + cfg: Rc, } pub struct WithHnd @@ -132,9 +35,9 @@ where T: FromRequest, S: 'static, { - pub fn new(f: F, cfg: ExtractorConfig) -> Self { + pub fn new(f: F, cfg: T::Config) -> Self { With { - cfg, + cfg: Rc::new(cfg), hnd: Rc::new(WithHnd { hnd: Rc::new(UnsafeCell::new(f)), _t: PhantomData, @@ -180,7 +83,7 @@ where { started: bool, hnd: Rc>, - cfg: ExtractorConfig, + cfg: Rc, req: HttpRequest, fut1: Option>>, fut2: Option>>, @@ -244,7 +147,7 @@ where } } -pub struct WithAsync +pub(crate) struct WithAsync where F: Fn(T) -> R, R: Future, @@ -254,7 +157,7 @@ where S: 'static, { hnd: Rc>, - cfg: ExtractorConfig, + cfg: Rc, } impl WithAsync @@ -266,9 +169,9 @@ where T: FromRequest, S: 'static, { - pub fn new(f: F, cfg: ExtractorConfig) -> Self { + pub fn new(f: F, cfg: T::Config) -> Self { WithAsync { - cfg, + cfg: Rc::new(cfg), hnd: Rc::new(WithHnd { hnd: Rc::new(UnsafeCell::new(f)), _s: PhantomData, @@ -294,7 +197,7 @@ where req, started: false, hnd: Rc::clone(&self.hnd), - cfg: self.cfg.clone(), + cfg: Rc::clone(&self.cfg), fut1: None, fut2: None, fut3: None, @@ -319,7 +222,7 @@ where { started: bool, hnd: Rc>, - cfg: ExtractorConfig, + cfg: Rc, req: HttpRequest, fut1: Option>>, fut2: Option, From 8e160ebda777dfefd14c66e4619e5938046d1c35 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 11:49:36 +0600 Subject: [PATCH 0422/1635] clippy warning --- src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 3141ad2a..6d8d3b04 100644 --- a/src/error.rs +++ b/src/error.rs @@ -654,7 +654,7 @@ pub struct InternalError { enum InternalErrorType { Status(StatusCode), - Response(Mutex>), + Response(Box>>), } impl InternalError { @@ -672,7 +672,7 @@ impl InternalError { let resp = response.into_parts(); InternalError { cause, - status: InternalErrorType::Response(Mutex::new(Some(resp))), + status: InternalErrorType::Response(Box::new(Mutex::new(Some(resp)))), backtrace: Backtrace::new(), } } From b7d813eeba571bf9aff9973eeb0fdb6e51eb5f8b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 12:04:00 +0600 Subject: [PATCH 0423/1635] update tests --- src/extractor.rs | 6 ++++-- src/json.rs | 17 +++++++++-------- src/route.rs | 4 ++-- tests/test_handlers.rs | 19 +++++++++++-------- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 425bc785..5ace390d 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -312,8 +312,10 @@ impl fmt::Display for Form { /// let app = App::new().resource( /// "/index.html", /// |r| { -/// r.method(http::Method::GET).with(index).limit(4096); -/// }, // <- change form extractor configuration +/// r.method(http::Method::GET) +/// // register form handler and change form extractor configuration +/// .with_config(index, |cfg| {cfg.limit(4096);}) +/// }, /// ); /// } /// ``` diff --git a/src/json.rs b/src/json.rs index d0e12c04..0b5cb96e 100644 --- a/src/json.rs +++ b/src/json.rs @@ -171,12 +171,13 @@ where /// fn main() { /// let app = App::new().resource("/index.html", |r| { /// r.method(http::Method::POST) -/// .with(index) -/// .limit(4096) // <- change json extractor configuration -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }); +/// .with_config(index, |cfg| { +/// cfg.limit(4096) // <- change json extractor configuration +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// }); +/// }) /// }); /// } /// ``` @@ -326,7 +327,7 @@ mod tests { use http::header; use handler::Handler; - use with::{ExtractorConfig, With}; + use with::With; impl PartialEq for JsonPayloadError { fn eq(&self, other: &JsonPayloadError) -> bool { @@ -409,7 +410,7 @@ mod tests { #[test] fn test_with_json() { - let mut cfg = ExtractorConfig::<_, Json>::default(); + let mut cfg = JsonConfig::default(); cfg.limit(4096); let mut handler = With::new(|data: Json| data, cfg); diff --git a/src/route.rs b/src/route.rs index 7ce1104c..4c82926e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -257,7 +257,7 @@ impl Route { /// # extern crate actix_web; /// # extern crate futures; /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Path}; + /// use actix_web::{http, App, Error, Form}; /// use futures::Future; /// /// #[derive(Deserialize)] @@ -318,7 +318,7 @@ impl InnerHandler { #[inline] pub fn handle(&self, req: HttpRequest) -> AsyncResult { - // reason: handler is unique per thread, handler get called from async code only + // reason: handler is unique per thread, handler get called from sync code only let h = unsafe { &mut *self.0.as_ref().get() }; h.handle(req) } diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 57309f83..95bd5be2 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -152,14 +152,17 @@ fn test_form_extractor() { fn test_form_extractor2() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route() - .with(|form: Form| format!("{}", form.username)) - .error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ).into() - }); + r.route().with_config( + |form: Form| format!("{}", form.username), + |cfg| { + cfg.error_handler(|err, _| { + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish(), + ).into() + }); + }, + ); }); }); From 58d1f4a4aa4048734bd6b4a36b12b054fdeb0400 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 13:34:36 +0600 Subject: [PATCH 0424/1635] switch to actix master --- Cargo.toml | 3 ++- src/client/connector.rs | 6 +++--- src/httpresponse.rs | 2 +- src/router.rs | 4 ++-- src/server/srv.rs | 6 +++--- tests/test_ws.rs | 30 ++++++++++++------------------ 6 files changed, 23 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 61259c79..f4ca9262 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,8 @@ flate2-rust = ["flate2/rust_backend"] features = ["tls", "alpn", "session", "brotli", "flate2-c"] [dependencies] -actix = "0.6.1" +# actix = "0.6.1" +actix = { git="https://github.com/actix/actix.git" } base64 = "0.9" bitflags = "1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index e094fd0c..58b6331d 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -6,7 +6,7 @@ use std::{fmt, io, mem, time}; use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; use actix::{ fut, Actor, ActorContext, ActorFuture, ActorResponse, Addr, AsyncContext, Context, - ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, + ContextFutureSpawner, Handler, Message, Recipient, StreamHandler2, Supervised, SystemService, WrapFuture, }; @@ -220,7 +220,7 @@ impl Actor for ClientConnector { self.resolver = Some(Resolver::from_registry()) } self.collect_periodic(ctx); - ctx.add_stream(self.acq_rx.take().unwrap()); + ctx.add_stream2(self.acq_rx.take().unwrap()); ctx.spawn(Maintenance); } } @@ -767,7 +767,7 @@ impl Handler for ClientConnector { } } -impl StreamHandler for ClientConnector { +impl StreamHandler2 for ClientConnector { fn handle( &mut self, msg: Result, ()>, ctx: &mut Context, diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 4e139ef6..333e6c4a 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -972,7 +972,7 @@ impl InnerHttpResponse { } } -/// Internal use only! unsafe +/// Internal use only! pub(crate) struct HttpResponsePool(VecDeque>); thread_local!(static POOL: Rc> = HttpResponsePool::pool()); diff --git a/src/router.rs b/src/router.rs index 0ae17808..e04956e9 100644 --- a/src/router.rs +++ b/src/router.rs @@ -309,7 +309,7 @@ impl Resource { params.set_tail(len as u16); for (idx, segment) in segments.iter().enumerate() { // reason: Router is part of App, which is unique per thread - // app is alive during whole life of tthread + // app is alive during whole life of a thread let name = unsafe { &*(names[idx].as_str() as *const _) }; params.add(name, *segment); } @@ -378,7 +378,7 @@ impl Resource { params.set_tail(tail_len as u16); for (idx, segment) in segments.iter().enumerate() { // reason: Router is part of App, which is unique per thread - // app is alive during whole life of tthread + // app is alive during whole life of a thread let name = unsafe { &*(names[idx].as_str() as *const _) }; params.add(name, *segment); } diff --git a/src/server/srv.rs b/src/server/srv.rs index cd670366..d5c94ea8 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -5,7 +5,7 @@ use std::{io, net, thread}; use actix::{ fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler, - Response, StreamHandler, System, WrapFuture, + Response, StreamHandler2, System, WrapFuture, }; use futures::sync::mpsc; @@ -449,7 +449,7 @@ impl HttpServer { // start http server actor let signals = self.subscribe_to_signals(); let addr = Actor::create(move |ctx| { - ctx.add_stream(rx); + ctx.add_stream2(rx); self }); if let Some(signals) = signals { @@ -611,7 +611,7 @@ impl Handler for HttpServer { } /// Commands from accept threads -impl StreamHandler for HttpServer { +impl StreamHandler2 for HttpServer { fn handle(&mut self, msg: Result, ()>, _: &mut Context) { if let Ok(Some(ServerCommand::WorkerDied(idx, socks))) = msg { let mut found = false; diff --git a/tests/test_ws.rs b/tests/test_ws.rs index eeeffb7a..dd65d4a5 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -23,16 +23,13 @@ impl Actor for Ws { } impl StreamHandler for Ws { - fn handle( - &mut self, msg: Result, ws::ProtocolError>, - ctx: &mut Self::Context, - ) { + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { - Ok(Some(ws::Message::Ping(msg))) => ctx.pong(&msg), - Ok(Some(ws::Message::Text(text))) => ctx.text(text), - Ok(Some(ws::Message::Binary(bin))) => ctx.binary(bin), - Ok(Some(ws::Message::Close(reason))) => ctx.close(reason), - _ => ctx.stop(), + ws::Message::Ping(msg) => ctx.pong(&msg), + ws::Message::Text(text) => ctx.text(text), + ws::Message::Binary(bin) => ctx.binary(bin), + ws::Message::Close(reason) => ctx.close(reason), + _ => (), } } } @@ -156,16 +153,13 @@ impl Ws2 { } impl StreamHandler for Ws2 { - fn handle( - &mut self, msg: Result, ws::ProtocolError>, - ctx: &mut Self::Context, - ) { + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { - Ok(Some(ws::Message::Ping(msg))) => ctx.pong(&msg), - Ok(Some(ws::Message::Text(text))) => ctx.text(text), - Ok(Some(ws::Message::Binary(bin))) => ctx.binary(bin), - Ok(Some(ws::Message::Close(reason))) => ctx.close(reason), - _ => ctx.stop(), + ws::Message::Ping(msg) => ctx.pong(&msg), + ws::Message::Text(text) => ctx.text(text), + ws::Message::Binary(bin) => ctx.binary(bin), + ws::Message::Close(reason) => ctx.close(reason), + _ => (), } } } From b5594ae2a5e16b1848619a0edb957ecd82039e16 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 14:11:00 +0600 Subject: [PATCH 0425/1635] Fix doc api example --- src/ws/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ws/mod.rs b/src/ws/mod.rs index c68cf300..558ecb51 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -25,12 +25,12 @@ //! //! // Handler for ws::Message messages //! impl StreamHandler for Ws { -//! fn handle(&mut self, msg: Result, ws::ProtocolError>, ctx: &mut Self::Context) { +//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { //! match msg { -//! Ok(Some(ws::Message::Ping(msg))) => ctx.pong(&msg), -//! Ok(Some(ws::Message::Text(text))) => ctx.text(text), -//! Ok(Some(ws::Message::Binary(bin))) => ctx.binary(bin), -//! _ => ctx.stop(), +//! ws::Message::Ping(msg) => ctx.pong(&msg), +//! ws::Message::Text(text) => ctx.text(text), +//! ws::Message::Binary(bin) => ctx.binary(bin), +//! _ => (), //! } //! } //! } From c5e8c1b710aad692f2baef654261bfa11a70d11e Mon Sep 17 00:00:00 2001 From: Josh Leeb-du Toit Date: Thu, 21 Jun 2018 18:17:27 +1000 Subject: [PATCH 0426/1635] Propagate default resources to underlying scopes --- src/application.rs | 24 ++++++++++++++++-------- src/handler.rs | 11 +++++++++++ src/scope.rs | 8 ++++++++ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/application.rs b/src/application.rs index 93008b3d..b22fe8bb 100644 --- a/src/application.rs +++ b/src/application.rs @@ -29,7 +29,7 @@ pub struct HttpApplication { #[doc(hidden)] pub struct Inner { prefix: usize, - default: ResourceHandler, + default: Rc>>, encoding: ContentEncoding, resources: Vec>, handlers: Vec>, @@ -51,7 +51,7 @@ impl PipelineHandler for Inner { match htype { HandlerType::Normal(idx) => match self.resources[idx].handle(req) { Ok(result) => result, - Err(req) => match self.default.handle(req) { + Err(req) => match self.default.borrow_mut().handle(req) { Ok(result) => result, Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), }, @@ -60,7 +60,7 @@ impl PipelineHandler for Inner { PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req), PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req), }, - HandlerType::Default => match self.default.handle(req) { + HandlerType::Default => match self.default.borrow_mut().handle(req) { Ok(result) => result, Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), }, @@ -172,7 +172,7 @@ struct ApplicationParts { state: S, prefix: String, settings: ServerSettings, - default: ResourceHandler, + default: Rc>>, resources: Vec<(Resource, Option>)>, handlers: Vec>, external: HashMap, @@ -223,7 +223,7 @@ where state, prefix: "/".to_owned(), settings: ServerSettings::default(), - default: ResourceHandler::default_not_found(), + default: Rc::new(RefCell::new(ResourceHandler::default_not_found())), resources: Vec::new(), handlers: Vec::new(), external: HashMap::new(), @@ -473,7 +473,7 @@ where { { let parts = self.parts.as_mut().expect("Use after finish"); - f(&mut parts.default); + f(&mut parts.default.borrow_mut()); } self } @@ -614,7 +614,7 @@ where /// Finish application configuration and create `HttpHandler` object. pub fn finish(&mut self) -> HttpApplication { - let parts = self.parts.take().expect("Use after finish"); + let mut parts = self.parts.take().expect("Use after finish"); let prefix = parts.prefix.trim().trim_right_matches('/'); let (prefix, prefix_len) = if prefix.is_empty() { ("/".to_owned(), 0) @@ -627,11 +627,19 @@ where resources.push((pattern, None)); } + for ref mut handler in parts.handlers.iter_mut() { + if let PrefixHandlerType::Scope(_, ref mut route_handler, _) = handler { + if !route_handler.has_default_resource() { + route_handler.default_resource(Rc::clone(&parts.default)); + } + }; + } + let (router, resources) = Router::new(&prefix, parts.settings, resources); let inner = Rc::new(RefCell::new(Inner { prefix: prefix_len, - default: parts.default, + default: Rc::clone(&parts.default), encoding: parts.encoding, handlers: parts.handlers, resources, diff --git a/src/handler.rs b/src/handler.rs index d330e071..4428ce83 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,5 +1,7 @@ +use std::cell::RefCell; use std::marker::PhantomData; use std::ops::Deref; +use std::rc::Rc; use futures::future::{err, ok, Future}; use futures::{Async, Poll}; @@ -8,6 +10,7 @@ use error::Error; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; +use resource::ResourceHandler; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] @@ -403,6 +406,14 @@ where // /// Trait defines object that could be registered as resource route pub(crate) trait RouteHandler: 'static { fn handle(&mut self, req: HttpRequest) -> AsyncResult; + + fn has_default_resource(&self) -> bool { + false + } + + fn default_resource(&mut self, default: Rc>>) { + unimplemented!() + } } /// Route handler wrapper for Handler diff --git a/src/scope.rs b/src/scope.rs index 6cc4929e..70fb1728 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -405,6 +405,14 @@ impl RouteHandler for Scope { unimplemented!() } } + + fn has_default_resource(&self) -> bool { + self.default.is_some() + } + + fn default_resource(&mut self, default: ScopeResource) { + self.default = Some(default); + } } struct Wrapper { From 03387672648eb5644c423c46544b69926e499e3c Mon Sep 17 00:00:00 2001 From: Josh Leeb-du Toit Date: Thu, 21 Jun 2018 19:37:34 +1000 Subject: [PATCH 0427/1635] Update CHANGES for default scope propagation --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b8ca7d47..0022a7df 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,8 @@ * `HttpRequest::url_for_static()` for a named route with no variables segments +* Propagation of the application's default resource to scopes that haven't set a default resource. + ### Changed From 3de928459273a3c56f2a2ff823a17d549096d8be Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 17:07:54 +0600 Subject: [PATCH 0428/1635] Handler::handle uses &self instead of mutabble reference --- CHANGES.md | 2 +- MIGRATION.md | 2 ++ src/application.rs | 23 ++++++++++++----------- src/fs.rs | 2 +- src/handler.rs | 13 ++++++------- src/helpers.rs | 2 +- src/resource.rs | 4 ++-- src/route.rs | 4 ++-- src/scope.rs | 43 ++++++++++++++++++++++++------------------- src/server/mod.rs | 6 +++--- src/test.rs | 2 +- src/with.rs | 4 ++-- 12 files changed, 57 insertions(+), 50 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4bbfe5bd..71a5ae59 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -37,7 +37,7 @@ * Use tokio instead of tokio-core -* Use `&mut self` instead of `&self` for Middleware trait +* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self` * Added header `User-Agent: Actix-web/` to default headers when building a request diff --git a/MIGRATION.md b/MIGRATION.md index 73e2d565..3b61e98c 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -17,6 +17,8 @@ fn index((query, json): (Query<..>, Json impl Responder {} ``` +* `Handler::handle()` uses `&self` instead of `&mut self` + * Removed deprecated `HttpServer::threads()`, use [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. diff --git a/src/application.rs b/src/application.rs index 9ce99e5b..fdeb164c 100644 --- a/src/application.rs +++ b/src/application.rs @@ -29,7 +29,7 @@ pub struct HttpApplication { #[doc(hidden)] pub struct Inner { prefix: usize, - default: Rc>>, + default: Rc>, encoding: ContentEncoding, resources: Vec>, handlers: Vec>, @@ -51,7 +51,7 @@ impl PipelineHandler for Inner { match htype { HandlerType::Normal(idx) => match self.resources[idx].handle(req) { Ok(result) => result, - Err(req) => match self.default.borrow_mut().handle(req) { + Err(req) => match self.default.handle(req) { Ok(result) => result, Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), }, @@ -60,7 +60,7 @@ impl PipelineHandler for Inner { PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req), PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req), }, - HandlerType::Default => match self.default.borrow_mut().handle(req) { + HandlerType::Default => match self.default.handle(req) { Ok(result) => result, Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), }, @@ -138,9 +138,7 @@ impl HttpApplication { impl HttpHandler for HttpApplication { type Task = Pipeline>; - fn handle( - &mut self, req: HttpRequest, - ) -> Result>, HttpRequest> { + fn handle(&self, req: HttpRequest) -> Result>, HttpRequest> { let m = { let path = req.path(); path.starts_with(&self.prefix) @@ -172,7 +170,7 @@ struct ApplicationParts { state: S, prefix: String, settings: ServerSettings, - default: Rc>>, + default: Rc>, resources: Vec<(Resource, Option>)>, handlers: Vec>, external: HashMap, @@ -223,7 +221,7 @@ where state, prefix: "/".to_owned(), settings: ServerSettings::default(), - default: Rc::new(RefCell::new(ResourceHandler::default_not_found())), + default: Rc::new(ResourceHandler::default_not_found()), resources: Vec::new(), handlers: Vec::new(), external: HashMap::new(), @@ -335,7 +333,8 @@ where T: FromRequest + 'static, { { - let parts: &mut ApplicationParts = self.parts.as_mut().expect("Use after finish"); + let parts: &mut ApplicationParts = + self.parts.as_mut().expect("Use after finish"); let out = { // get resource handler @@ -474,7 +473,9 @@ where { { let parts = self.parts.as_mut().expect("Use after finish"); - f(&mut parts.default.borrow_mut()); + let default = Rc::get_mut(&mut parts.default) + .expect("Multiple App instance references are not allowed"); + f(default); } self } @@ -707,7 +708,7 @@ struct BoxedApplication { impl HttpHandler for BoxedApplication { type Task = Box; - fn handle(&mut self, req: HttpRequest) -> Result { + fn handle(&self, req: HttpRequest) -> Result { self.app.handle(req).map(|t| { let task: Self::Task = Box::new(t); task diff --git a/src/fs.rs b/src/fs.rs index 639626c5..61fa207e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -654,7 +654,7 @@ impl StaticFiles { impl Handler for StaticFiles { type Result = Result, Error>; - fn handle(&mut self, req: HttpRequest) -> Self::Result { + fn handle(&self, req: HttpRequest) -> Self::Result { if !self.accessible { Ok(self.default.handle(req)) } else { diff --git a/src/handler.rs b/src/handler.rs index 4428ce83..61dd5694 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::marker::PhantomData; use std::ops::Deref; use std::rc::Rc; @@ -19,7 +18,7 @@ pub trait Handler: 'static { type Result: Responder; /// Handle request - fn handle(&mut self, req: HttpRequest) -> Self::Result; + fn handle(&self, req: HttpRequest) -> Self::Result; } /// Trait implemented by types that generate responses for clients. @@ -209,7 +208,7 @@ where { type Result = R; - fn handle(&mut self, req: HttpRequest) -> R { + fn handle(&self, req: HttpRequest) -> R { (self)(req) } } @@ -405,13 +404,13 @@ where // /// Trait defines object that could be registered as resource route pub(crate) trait RouteHandler: 'static { - fn handle(&mut self, req: HttpRequest) -> AsyncResult; + fn handle(&self, req: HttpRequest) -> AsyncResult; fn has_default_resource(&self) -> bool { false } - fn default_resource(&mut self, default: Rc>>) { + fn default_resource(&mut self, _: Rc>) { unimplemented!() } } @@ -444,7 +443,7 @@ where R: Responder + 'static, S: 'static, { - fn handle(&mut self, req: HttpRequest) -> AsyncResult { + fn handle(&self, req: HttpRequest) -> AsyncResult { match self.h.handle(req.clone()).respond_to(&req) { Ok(reply) => reply.into(), Err(err) => AsyncResult::err(err.into()), @@ -489,7 +488,7 @@ where E: Into + 'static, S: 'static, { - fn handle(&mut self, req: HttpRequest) -> AsyncResult { + fn handle(&self, req: HttpRequest) -> AsyncResult { let fut = (self.h)(req.clone()).map_err(|e| e.into()).then(move |r| { match r.respond_to(&req) { Ok(reply) => match reply.into().into() { diff --git a/src/helpers.rs b/src/helpers.rs index c94c24d9..8dbf1b90 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -86,7 +86,7 @@ impl NormalizePath { impl Handler for NormalizePath { type Result = HttpResponse; - fn handle(&mut self, req: HttpRequest) -> Self::Result { + fn handle(&self, req: HttpRequest) -> Self::Result { if let Some(router) = req.router() { let query = req.query_string(); if self.merge { diff --git a/src/resource.rs b/src/resource.rs index 49e9ab0c..570b7909 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -282,9 +282,9 @@ impl ResourceHandler { } pub(crate) fn handle( - &mut self, mut req: HttpRequest, + &self, mut req: HttpRequest, ) -> Result, HttpRequest> { - for route in &mut self.routes { + for route in &self.routes { if route.check(&mut req) { return if self.middlewares.borrow().is_empty() { Ok(route.handle(req)) diff --git a/src/route.rs b/src/route.rs index 4c82926e..42d68f19 100644 --- a/src/route.rs +++ b/src/route.rs @@ -49,13 +49,13 @@ impl Route { } #[inline] - pub(crate) fn handle(&mut self, req: HttpRequest) -> AsyncResult { + pub(crate) fn handle(&self, req: HttpRequest) -> AsyncResult { self.handler.handle(req) } #[inline] pub(crate) fn compose( - &mut self, req: HttpRequest, mws: Rc>>>>, + &self, req: HttpRequest, mws: Rc>>>>, ) -> AsyncResult { AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) } diff --git a/src/scope.rs b/src/scope.rs index 70fb1728..23e4a723 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -18,7 +18,7 @@ use pred::Predicate; use resource::ResourceHandler; use router::Resource; -type ScopeResource = Rc>>; +type ScopeResource = Rc>; type Route = UnsafeCell>>; type ScopeResources = Rc)>>; type NestedInfo = (Resource, Route, Vec>>); @@ -236,9 +236,13 @@ impl Scope { } if found { - for &(ref pattern, ref resource) in self.resources.iter() { + let resources = Rc::get_mut(&mut self.resources) + .expect("Multiple scope references are not allowed"); + for &mut (ref pattern, ref mut resource) in resources.iter_mut() { if pattern.pattern() == path { - resource.borrow_mut().method(method).with(f); + let res = Rc::get_mut(resource) + .expect("Multiple scope references are not allowed"); + res.method(method).with(f); break; } } @@ -253,7 +257,7 @@ impl Scope { ); Rc::get_mut(&mut self.resources) .expect("Can not use after configuration") - .push((pattern, Rc::new(RefCell::new(handler)))); + .push((pattern, Rc::new(handler))); } self } @@ -297,7 +301,7 @@ impl Scope { ); Rc::get_mut(&mut self.resources) .expect("Can not use after configuration") - .push((pattern, Rc::new(RefCell::new(handler)))); + .push((pattern, Rc::new(handler))); self } @@ -308,10 +312,13 @@ impl Scope { F: FnOnce(&mut ResourceHandler) -> R + 'static, { if self.default.is_none() { - self.default = - Some(Rc::new(RefCell::new(ResourceHandler::default_not_found()))); + self.default = Some(Rc::new(ResourceHandler::default_not_found())); + } + { + let default = Rc::get_mut(self.default.as_mut().unwrap()) + .expect("Multiple copies of default handler"); + f(default); } - f(&mut *self.default.as_ref().unwrap().borrow_mut()); self } @@ -332,18 +339,18 @@ impl Scope { } impl RouteHandler for Scope { - fn handle(&mut self, mut req: HttpRequest) -> AsyncResult { + fn handle(&self, mut req: HttpRequest) -> AsyncResult { let tail = req.match_info().tail as usize; // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { if pattern.match_with_params(&mut req, tail, false) { if self.middlewares.borrow().is_empty() { - return match resource.borrow_mut().handle(req) { + return match resource.handle(req) { Ok(result) => result, Err(req) => { if let Some(ref default) = self.default { - match default.borrow_mut().handle(req) { + match default.handle(req) { Ok(result) => result, Err(_) => AsyncResult::ok(HttpResponse::new( StatusCode::NOT_FOUND, @@ -388,7 +395,7 @@ impl RouteHandler for Scope { // default handler if self.middlewares.borrow().is_empty() { if let Some(ref default) = self.default { - match default.borrow_mut().handle(req) { + match default.handle(req) { Ok(result) => result, Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), } @@ -421,7 +428,7 @@ struct Wrapper { } impl RouteHandler for Wrapper { - fn handle(&mut self, req: HttpRequest) -> AsyncResult { + fn handle(&self, req: HttpRequest) -> AsyncResult { self.scope.handle(req.change_state(Rc::clone(&self.state))) } } @@ -453,7 +460,7 @@ struct ComposeInfo { count: usize, req: HttpRequest, mws: Rc>>>>, - resource: Rc>>, + resource: Rc>, } enum ComposeState { @@ -479,7 +486,7 @@ impl ComposeState { impl Compose { fn new( req: HttpRequest, mws: Rc>>>>, - resource: Rc>>, + resource: Rc>, ) -> Self { let mut info = ComposeInfo { count: 0, @@ -527,8 +534,7 @@ impl StartMiddlewares { if info.count == len { let reply = { let req = info.req.clone(); - let mut resource = info.resource.borrow_mut(); - resource.handle(req).unwrap() + info.resource.handle(req).unwrap() }; return WaitingResponse::init(info, reply); } else { @@ -564,8 +570,7 @@ impl StartMiddlewares { if info.count == len { let reply = { let req = info.req.clone(); - let mut resource = info.resource.borrow_mut(); - resource.handle(req).unwrap() + info.resource.handle(req).unwrap() }; return Some(WaitingResponse::init(info, reply)); } else { diff --git a/src/server/mod.rs b/src/server/mod.rs index c0dabb26..b65fc3a8 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -126,14 +126,14 @@ pub trait HttpHandler: 'static { type Task: HttpHandlerTask; /// Handle request - fn handle(&mut self, req: HttpRequest) -> Result; + fn handle(&self, req: HttpRequest) -> Result; } impl HttpHandler for Box>> { type Task = Box; - fn handle(&mut self, req: HttpRequest) -> Result, HttpRequest> { - self.as_mut().handle(req) + fn handle(&self, req: HttpRequest) -> Result, HttpRequest> { + self.as_ref().handle(req) } } diff --git a/src/test.rs b/src/test.rs index 19e682d8..7bf0f149 100644 --- a/src/test.rs +++ b/src/test.rs @@ -564,7 +564,7 @@ impl TestRequest { /// with generated request. /// /// This method panics is handler returns actor or async result. - pub fn run>(self, mut h: H) -> Result { + pub fn run>(self, h: H) -> Result { let req = self.finish(); let resp = h.handle(req.clone()); diff --git a/src/with.rs b/src/with.rs index 126958b5..ac220958 100644 --- a/src/with.rs +++ b/src/with.rs @@ -56,7 +56,7 @@ where { type Result = AsyncResult; - fn handle(&mut self, req: HttpRequest) -> Self::Result { + fn handle(&self, req: HttpRequest) -> Self::Result { let mut fut = WithHandlerFut { req, started: false, @@ -192,7 +192,7 @@ where { type Result = AsyncResult; - fn handle(&mut self, req: HttpRequest) -> Self::Result { + fn handle(&self, req: HttpRequest) -> Self::Result { let mut fut = WithAsyncHandlerFut { req, started: false, From 65ca563579afc3b55279847cdd4ff6df41ee0e08 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 23:06:23 +0600 Subject: [PATCH 0429/1635] use read only self for Middleware --- src/application.rs | 59 ++++++++++++------------ src/fs.rs | 4 +- src/helpers.rs | 8 ++-- src/json.rs | 2 +- src/middleware/cors.rs | 18 ++++---- src/middleware/csrf.rs | 12 ++--- src/middleware/defaultheaders.rs | 4 +- src/middleware/errhandlers.rs | 6 +-- src/middleware/identity.rs | 4 +- src/middleware/logger.rs | 6 +-- src/middleware/mod.rs | 6 +-- src/middleware/session.rs | 4 +- src/pipeline.rs | 48 +++++++++----------- src/resource.rs | 10 ++--- src/route.rs | 29 ++++++------ src/scope.rs | 77 +++++++++++++++----------------- src/test.rs | 2 +- tests/test_middleware.rs | 24 ++++------ 18 files changed, 150 insertions(+), 173 deletions(-) diff --git a/src/application.rs b/src/application.rs index fdeb164c..bdc55fe7 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -21,9 +20,9 @@ pub struct HttpApplication { prefix: String, prefix_len: usize, router: Router, - inner: Rc>>, + inner: Rc>, filters: Option>>>, - middlewares: Rc>>>>, + middlewares: Rc>>>, } #[doc(hidden)] @@ -41,12 +40,13 @@ enum PrefixHandlerType { } impl PipelineHandler for Inner { + #[inline] fn encoding(&self) -> ContentEncoding { self.encoding } fn handle( - &mut self, req: HttpRequest, htype: HandlerType, + &self, req: HttpRequest, htype: HandlerType, ) -> AsyncResult { match htype { HandlerType::Normal(idx) => match self.resources[idx].handle(req) { @@ -57,8 +57,8 @@ impl PipelineHandler for Inner { }, }, HandlerType::Handler(idx) => match self.handlers[idx] { - PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req), - PrefixHandlerType::Scope(_, ref mut hnd, _) => hnd.handle(req), + PrefixHandlerType::Handler(_, ref hnd) => hnd.handle(req), + PrefixHandlerType::Scope(_, ref hnd, _) => hnd.handle(req), }, HandlerType::Default => match self.default.handle(req) { Ok(result) => result, @@ -74,14 +74,13 @@ impl HttpApplication { if let Some(idx) = self.router.recognize(req) { HandlerType::Normal(idx) } else { - let inner = self.inner.borrow(); req.match_info_mut().set_tail(0); - 'outer: for idx in 0..inner.handlers.len() { - match inner.handlers[idx] { + 'outer: for idx in 0..self.inner.handlers.len() { + match self.inner.handlers[idx] { PrefixHandlerType::Handler(ref prefix, _) => { let m = { - let path = &req.path()[inner.prefix..]; + let path = &req.path()[self.inner.prefix..]; let path_len = path.len(); path.starts_with(prefix) @@ -90,7 +89,7 @@ impl HttpApplication { }; if m { - let prefix_len = (inner.prefix + prefix.len()) as u16; + let prefix_len = (self.inner.prefix + prefix.len()) as u16; let url = req.url().clone(); req.set_prefix_len(prefix_len); req.match_info_mut().set_url(url); @@ -100,7 +99,7 @@ impl HttpApplication { } PrefixHandlerType::Scope(ref pattern, _, ref filters) => { if let Some(prefix_len) = - pattern.match_prefix_with_params(req, inner.prefix) + pattern.match_prefix_with_params(req, self.inner.prefix) { for filter in filters { if !filter.check(req) { @@ -108,7 +107,7 @@ impl HttpApplication { } } - let prefix_len = (inner.prefix + prefix_len) as u16; + let prefix_len = (self.inner.prefix + prefix_len) as u16; let url = req.url().clone(); req.set_prefix_len(prefix_len); let params = req.match_info_mut(); @@ -124,9 +123,9 @@ impl HttpApplication { } #[cfg(test)] - pub(crate) fn run(&mut self, mut req: HttpRequest) -> AsyncResult { + pub(crate) fn run(&self, mut req: HttpRequest) -> AsyncResult { let tp = self.get_handler(&mut req); - self.inner.borrow_mut().handle(req, tp) + self.inner.handle(req, tp) } #[cfg(test)] @@ -629,7 +628,7 @@ where resources.push((pattern, None)); } - for ref mut handler in parts.handlers.iter_mut() { + for handler in &mut parts.handlers { if let PrefixHandlerType::Scope(_, ref mut route_handler, _) = handler { if !route_handler.has_default_resource() { route_handler.default_resource(Rc::clone(&parts.default)); @@ -639,13 +638,13 @@ where let (router, resources) = Router::new(&prefix, parts.settings, resources); - let inner = Rc::new(RefCell::new(Inner { + let inner = Rc::new(Inner { prefix: prefix_len, default: Rc::clone(&parts.default), encoding: parts.encoding, handlers: parts.handlers, resources, - })); + }); let filters = if parts.filters.is_empty() { None } else { @@ -655,7 +654,7 @@ where HttpApplication { state: Rc::new(parts.state), router: router.clone(), - middlewares: Rc::new(RefCell::new(parts.middlewares)), + middlewares: Rc::new(parts.middlewares), prefix, prefix_len, inner, @@ -765,7 +764,7 @@ mod tests { #[test] fn test_default_resource() { - let mut app = App::new() + let app = App::new() .resource("/test", |r| r.f(|_| HttpResponse::Ok())) .finish(); @@ -777,7 +776,7 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let mut app = App::new() + let app = App::new() .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); let req = TestRequest::with_uri("/blah").finish(); @@ -787,7 +786,7 @@ mod tests { #[test] fn test_unhandled_prefix() { - let mut app = App::new() + let app = App::new() .prefix("/test") .resource("/test", |r| r.f(|_| HttpResponse::Ok())) .finish(); @@ -796,7 +795,7 @@ mod tests { #[test] fn test_state() { - let mut app = App::with_state(10) + let app = App::with_state(10) .resource("/", |r| r.f(|_| HttpResponse::Ok())) .finish(); let req = @@ -807,7 +806,7 @@ mod tests { #[test] fn test_prefix() { - let mut app = App::new() + let app = App::new() .prefix("/test") .resource("/blah", |r| r.f(|_| HttpResponse::Ok())) .finish(); @@ -830,7 +829,7 @@ mod tests { #[test] fn test_handler() { - let mut app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); + let app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -855,7 +854,7 @@ mod tests { #[test] fn test_handler2() { - let mut app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); + let app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); let req = TestRequest::with_uri("/test").finish(); let resp = app.run(req); @@ -880,7 +879,7 @@ mod tests { #[test] fn test_handler_with_prefix() { - let mut app = App::new() + let app = App::new() .prefix("prefix") .handler("/test", |_| HttpResponse::Ok()) .finish(); @@ -908,7 +907,7 @@ mod tests { #[test] fn test_route() { - let mut app = App::new() + let app = App::new() .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) .route("/test", Method::POST, |_: HttpRequest| { HttpResponse::Created() @@ -930,7 +929,7 @@ mod tests { #[test] fn test_handler_prefix() { - let mut app = App::new() + let app = App::new() .prefix("/app") .handler("/test", |_| HttpResponse::Ok()) .finish(); @@ -980,7 +979,7 @@ mod tests { #[test] fn test_option_responder() { - let mut app = App::new() + let app = App::new() .resource("/none", |r| r.f(|_| -> Option<&'static str> { None })) .resource("/some", |r| r.f(|_| Some("some"))) .finish(); diff --git a/src/fs.rs b/src/fs.rs index 61fa207e..c5a7de61 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1204,7 +1204,7 @@ mod tests { #[test] fn test_redirect_to_index() { - let mut st = StaticFiles::new(".").index_file("index.html"); + let st = StaticFiles::new(".").index_file("index.html"); let mut req = HttpRequest::default(); req.match_info_mut().add_static("tail", "tests"); @@ -1230,7 +1230,7 @@ mod tests { #[test] fn test_redirect_to_index_nested() { - let mut st = StaticFiles::new(".").index_file("mod.rs"); + let st = StaticFiles::new(".").index_file("mod.rs"); let mut req = HttpRequest::default(); req.match_info_mut().add_static("tail", "src/client"); diff --git a/src/helpers.rs b/src/helpers.rs index 8dbf1b90..0b35f047 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -181,7 +181,7 @@ mod tests { #[test] fn test_normalize_path_trailing_slashes() { - let mut app = App::new() + let app = App::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())) @@ -222,7 +222,7 @@ mod tests { #[test] fn test_normalize_path_trailing_slashes_disabled() { - let mut app = App::new() + let app = App::new() .resource("/resource1", |r| r.method(Method::GET).f(index)) .resource("/resource2/", |r| r.method(Method::GET).f(index)) .default_resource(|r| { @@ -255,7 +255,7 @@ mod tests { #[test] fn test_normalize_path_merge_slashes() { - let mut app = App::new() + let app = App::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())) @@ -344,7 +344,7 @@ mod tests { #[test] fn test_normalize_path_merge_and_append_slashes() { - let mut app = App::new() + let app = App::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/json.rs b/src/json.rs index 0b5cb96e..3f9188c1 100644 --- a/src/json.rs +++ b/src/json.rs @@ -412,7 +412,7 @@ mod tests { fn test_with_json() { let mut cfg = JsonConfig::default(); cfg.limit(4096); - let mut handler = With::new(|data: Json| data, cfg); + let handler = With::new(|data: Json| data, cfg); let req = HttpRequest::default(); assert!(handler.handle(req).as_err().is_some()); diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 45459656..734f7be4 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -356,7 +356,7 @@ impl Cors { } impl Middleware for Cors { - fn start(&mut self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &mut HttpRequest) -> Result { if self.inner.preflight && Method::OPTIONS == *req.method() { self.validate_origin(req)?; self.validate_allowed_method(req)?; @@ -434,7 +434,7 @@ impl Middleware for Cors { } fn response( - &mut self, req: &mut HttpRequest, mut resp: HttpResponse, + &self, req: &mut HttpRequest, mut resp: HttpResponse, ) -> Result { match self.inner.origins { AllOrSome::All => { @@ -944,7 +944,7 @@ mod tests { #[test] fn validate_origin_allows_all_origins() { - let mut cors = Cors::default(); + let cors = Cors::default(); let mut req = TestRequest::with_header("Origin", "https://www.example.com").finish(); @@ -1013,7 +1013,7 @@ mod tests { // #[test] // #[should_panic(expected = "MissingOrigin")] // fn test_validate_missing_origin() { - // let mut cors = Cors::build() + // let cors = Cors::build() // .allowed_origin("https://www.example.com") // .finish(); // let mut req = HttpRequest::default(); @@ -1023,7 +1023,7 @@ mod tests { #[test] #[should_panic(expected = "OriginNotAllowed")] fn test_validate_not_allowed_origin() { - let mut cors = Cors::build() + let cors = Cors::build() .allowed_origin("https://www.example.com") .finish(); @@ -1035,7 +1035,7 @@ mod tests { #[test] fn test_validate_origin() { - let mut cors = Cors::build() + let cors = Cors::build() .allowed_origin("https://www.example.com") .finish(); @@ -1048,7 +1048,7 @@ mod tests { #[test] fn test_no_origin_response() { - let mut cors = Cors::build().finish(); + let cors = Cors::build().finish(); let mut req = TestRequest::default().method(Method::GET).finish(); let resp: HttpResponse = HttpResponse::Ok().into(); @@ -1074,7 +1074,7 @@ mod tests { #[test] fn test_response() { - let mut cors = Cors::build() + let cors = Cors::build() .send_wildcard() .disable_preflight() .max_age(3600) @@ -1109,7 +1109,7 @@ mod tests { resp.headers().get(header::VARY).unwrap().as_bytes() ); - let mut cors = Cors::build() + let cors = Cors::build() .disable_vary_header() .allowed_origin("https://www.example.com") .finish(); diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 8c2b06e7..670ec1c1 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -209,7 +209,7 @@ impl CsrfFilter { } impl Middleware for CsrfFilter { - fn start(&mut self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &mut HttpRequest) -> Result { self.validate(req)?; Ok(Started::Done) } @@ -223,7 +223,7 @@ mod tests { #[test] fn test_safe() { - let mut csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); + let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::HEAD) @@ -234,7 +234,7 @@ mod tests { #[test] fn test_csrf() { - let mut csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); + let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::POST) @@ -245,7 +245,7 @@ mod tests { #[test] fn test_referer() { - let mut csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); + let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); let mut req = TestRequest::with_header( "Referer", @@ -258,9 +258,9 @@ mod tests { #[test] fn test_upgrade() { - let mut strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); + let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - let mut lax_csrf = CsrfFilter::new() + let lax_csrf = CsrfFilter::new() .allowed_origin("https://www.example.com") .allow_upgrade(); diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index acccc552..dca8dfbe 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -75,7 +75,7 @@ impl DefaultHeaders { impl Middleware for DefaultHeaders { fn response( - &mut self, _: &mut HttpRequest, mut resp: HttpResponse, + &self, _: &mut HttpRequest, mut resp: HttpResponse, ) -> Result { for (key, value) in self.headers.iter() { if !resp.headers().contains_key(key) { @@ -100,7 +100,7 @@ mod tests { #[test] fn test_default_headers() { - let mut mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); + let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); let mut req = HttpRequest::default(); diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index e1b48418..fe148fdd 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -71,7 +71,7 @@ impl ErrorHandlers { impl Middleware for ErrorHandlers { fn response( - &mut self, req: &mut HttpRequest, resp: HttpResponse, + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(handler) = self.handlers.get(&resp.status()) { handler(req, resp) @@ -99,7 +99,7 @@ mod tests { #[test] fn test_handler() { - let mut mw = + let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); let mut req = HttpRequest::default(); @@ -121,7 +121,7 @@ mod tests { struct MiddlewareOne; impl Middleware for MiddlewareOne { - fn start(&mut self, _req: &mut HttpRequest) -> Result { + fn start(&self, _req: &mut HttpRequest) -> Result { Err(ErrorInternalServerError("middleware error")) } } diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 58cc0de4..f4089428 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -178,7 +178,7 @@ impl IdentityService { struct IdentityBox(Box); impl> Middleware for IdentityService { - fn start(&mut self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); let fut = self @@ -195,7 +195,7 @@ impl> Middleware for IdentityService { } fn response( - &mut self, req: &mut HttpRequest, resp: HttpResponse, + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(mut id) = req.extensions_mut().remove::() { id.0.write(resp) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index ab9ae4a0..a731d695 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -124,14 +124,14 @@ impl Logger { } impl Middleware for Logger { - fn start(&mut self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &mut HttpRequest) -> Result { if !self.exclude.contains(req.path()) { req.extensions_mut().insert(StartTime(time::now())); } Ok(Started::Done) } - fn finish(&mut self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { + fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { self.log(req, resp); Finished::Done } @@ -322,7 +322,7 @@ mod tests { #[test] fn test_logger() { - let mut logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); + let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); let mut headers = HeaderMap::new(); headers.insert( diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 7fd33932..2551ded1 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -51,20 +51,20 @@ pub enum Finished { pub trait Middleware: 'static { /// Method is called when request is ready. It may return /// future, which should resolve before next middleware get called. - fn start(&mut self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &mut HttpRequest) -> Result { Ok(Started::Done) } /// Method is called when handler returns response, /// but before sending http message to peer. fn response( - &mut self, req: &mut HttpRequest, resp: HttpResponse, + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { Ok(Response::Done(resp)) } /// Method is called after body stream get sent to peer. - fn finish(&mut self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { + fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { Finished::Done } } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index bb6c8223..bd10b3c2 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -246,7 +246,7 @@ impl> SessionStorage { } impl> Middleware for SessionStorage { - fn start(&mut self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &mut HttpRequest) -> Result { let mut req = req.clone(); let fut = self.0.from_request(&mut req).then(move |res| match res { @@ -261,7 +261,7 @@ impl> Middleware for SessionStorage { } fn response( - &mut self, req: &mut HttpRequest, resp: HttpResponse, + &self, req: &mut HttpRequest, resp: HttpResponse, ) -> Result { if let Some(s_box) = req.extensions_mut().remove::>() { s_box.0.borrow_mut().write(resp) diff --git a/src/pipeline.rs b/src/pipeline.rs index 2e03c8f6..fe5e1d02 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,4 +1,4 @@ -use std::cell::{RefCell, UnsafeCell}; +use std::cell::UnsafeCell; use std::marker::PhantomData; use std::rc::Rc; use std::{io, mem}; @@ -31,7 +31,7 @@ pub trait PipelineHandler { fn encoding(&self) -> ContentEncoding; fn handle( - &mut self, req: HttpRequest, htype: HandlerType, + &self, req: HttpRequest, htype: HandlerType, ) -> AsyncResult; } @@ -74,7 +74,7 @@ impl> PipelineState { struct PipelineInfo { req: UnsafeCell>, count: u16, - mws: Rc>>>>, + mws: Rc>>>, context: Option>, error: Option, disconnected: Option, @@ -86,7 +86,7 @@ impl PipelineInfo { PipelineInfo { req: UnsafeCell::new(req), count: 0, - mws: Rc::new(RefCell::new(Vec::new())), + mws: Rc::new(Vec::new()), error: None, context: None, disconnected: None, @@ -123,8 +123,8 @@ impl PipelineInfo { impl> Pipeline { pub fn new( - req: HttpRequest, mws: Rc>>>>, - handler: Rc>, htype: HandlerType, + req: HttpRequest, mws: Rc>>>, handler: Rc, + htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { mws, @@ -133,7 +133,7 @@ impl> Pipeline { error: None, context: None, disconnected: None, - encoding: handler.borrow().encoding(), + encoding: handler.encoding(), }; let state = StartMiddlewares::init(&mut info, handler, htype); @@ -238,7 +238,7 @@ type Fut = Box, Error = Error>>; /// Middlewares start executor struct StartMiddlewares { - hnd: Rc>, + hnd: Rc, htype: HandlerType, fut: Option, _s: PhantomData, @@ -246,18 +246,17 @@ struct StartMiddlewares { impl> StartMiddlewares { fn init( - info: &mut PipelineInfo, hnd: Rc>, htype: HandlerType, + info: &mut PipelineInfo, hnd: Rc, htype: HandlerType, ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately - let len = info.mws.borrow().len() as u16; + let len = info.mws.len() as u16; loop { if info.count == len { - let reply = hnd.borrow_mut().handle(info.req().clone(), htype); + let reply = hnd.handle(info.req().clone(), htype); return WaitingResponse::init(info, reply); } else { - let state = - info.mws.borrow_mut()[info.count as usize].start(info.req_mut()); + let state = info.mws[info.count as usize].start(info.req_mut()); match state { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { @@ -278,7 +277,7 @@ impl> StartMiddlewares { } fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - let len = info.mws.borrow().len() as u16; + let len = info.mws.len() as u16; 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return None, @@ -289,14 +288,11 @@ impl> StartMiddlewares { } loop { if info.count == len { - let reply = self - .hnd - .borrow_mut() - .handle(info.req().clone(), self.htype); + let reply = self.hnd.handle(info.req().clone(), self.htype); return Some(WaitingResponse::init(info, reply)); } else { - let state = info.mws.borrow_mut()[info.count as usize] - .start(info.req_mut()); + let state = + info.mws[info.count as usize].start(info.req_mut()); match state { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { @@ -366,10 +362,10 @@ impl RunMiddlewares { return ProcessResponse::init(resp); } let mut curr = 0; - let len = info.mws.borrow().len(); + let len = info.mws.len(); loop { - let state = info.mws.borrow_mut()[curr].response(info.req_mut(), resp); + let state = info.mws[curr].response(info.req_mut(), resp); resp = match state { Err(err) => { info.count = (curr + 1) as u16; @@ -396,7 +392,7 @@ impl RunMiddlewares { } fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - let len = info.mws.borrow().len(); + let len = info.mws.len(); loop { // poll latest fut @@ -413,8 +409,7 @@ impl RunMiddlewares { if self.curr == len { return Some(ProcessResponse::init(resp)); } else { - let state = - info.mws.borrow_mut()[self.curr].response(info.req_mut(), resp); + let state = info.mws[self.curr].response(info.req_mut(), resp); match state { Err(err) => return Some(ProcessResponse::init(err.into())), Ok(Response::Done(r)) => { @@ -739,8 +734,7 @@ impl FinishingMiddlewares { } info.count -= 1; - let state = info.mws.borrow_mut()[info.count as usize] - .finish(info.req_mut(), &self.resp); + let state = info.mws[info.count as usize].finish(info.req_mut(), &self.resp); match state { Finished::Done => { if info.count == 0 { diff --git a/src/resource.rs b/src/resource.rs index 570b7909..2eae570c 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; @@ -38,7 +37,7 @@ pub struct ResourceHandler { name: String, state: PhantomData, routes: SmallVec<[Route; 3]>, - middlewares: Rc>>>>, + middlewares: Rc>>>, } impl Default for ResourceHandler { @@ -47,7 +46,7 @@ impl Default for ResourceHandler { name: String::new(), state: PhantomData, routes: SmallVec::new(), - middlewares: Rc::new(RefCell::new(Vec::new())), + middlewares: Rc::new(Vec::new()), } } } @@ -58,7 +57,7 @@ impl ResourceHandler { name: String::new(), state: PhantomData, routes: SmallVec::new(), - middlewares: Rc::new(RefCell::new(Vec::new())), + middlewares: Rc::new(Vec::new()), } } @@ -277,7 +276,6 @@ impl ResourceHandler { pub fn middleware>(&mut self, mw: M) { Rc::get_mut(&mut self.middlewares) .unwrap() - .borrow_mut() .push(Box::new(mw)); } @@ -286,7 +284,7 @@ impl ResourceHandler { ) -> Result, HttpRequest> { for route in &self.routes { if route.check(&mut req) { - return if self.middlewares.borrow().is_empty() { + return if self.middlewares.is_empty() { Ok(route.handle(req)) } else { Ok(route.compose(req, Rc::clone(&self.middlewares))) diff --git a/src/route.rs b/src/route.rs index 42d68f19..80fb17d7 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,4 +1,4 @@ -use std::cell::{RefCell, UnsafeCell}; +use std::cell::UnsafeCell; use std::marker::PhantomData; use std::rc::Rc; @@ -55,7 +55,7 @@ impl Route { #[inline] pub(crate) fn compose( - &self, req: HttpRequest, mws: Rc>>>>, + &self, req: HttpRequest, mws: Rc>>>, ) -> AsyncResult { AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) } @@ -340,7 +340,7 @@ struct Compose { struct ComposeInfo { count: usize, req: HttpRequest, - mws: Rc>>>>, + mws: Rc>>>, handler: InnerHandler, } @@ -366,8 +366,7 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>>, - handler: InnerHandler, + req: HttpRequest, mws: Rc>>>, handler: InnerHandler, ) -> Self { let mut info = ComposeInfo { count: 0, @@ -410,13 +409,13 @@ type Fut = Box, Error = Error>>; impl StartMiddlewares { fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.borrow().len(); + let len = info.mws.len(); loop { if info.count == len { let reply = info.handler.handle(info.req.clone()); return WaitingResponse::init(info, reply); } else { - let state = info.mws.borrow_mut()[info.count].start(&mut info.req); + let state = info.mws[info.count].start(&mut info.req); match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { @@ -435,7 +434,7 @@ impl StartMiddlewares { } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.borrow().len(); + let len = info.mws.len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return None, @@ -449,8 +448,7 @@ impl StartMiddlewares { let reply = info.handler.handle(info.req.clone()); return Some(WaitingResponse::init(info, reply)); } else { - let state = - info.mws.borrow_mut()[info.count].start(&mut info.req); + let state = info.mws[info.count].start(&mut info.req); match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { @@ -513,10 +511,10 @@ struct RunMiddlewares { impl RunMiddlewares { fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { let mut curr = 0; - let len = info.mws.borrow().len(); + let len = info.mws.len(); loop { - let state = info.mws.borrow_mut()[curr].response(&mut info.req, resp); + let state = info.mws[curr].response(&mut info.req, resp); resp = match state { Err(err) => { info.count = curr + 1; @@ -542,7 +540,7 @@ impl RunMiddlewares { } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.borrow().len(); + let len = info.mws.len(); loop { // poll latest fut @@ -559,8 +557,7 @@ impl RunMiddlewares { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { - let state = - info.mws.borrow_mut()[self.curr].response(&mut info.req, resp); + let state = info.mws[self.curr].response(&mut info.req, resp); match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) @@ -630,7 +627,7 @@ impl FinishingMiddlewares { info.count -= 1; - let state = info.mws.borrow_mut()[info.count as usize] + let state = info.mws[info.count as usize] .finish(&mut info.req, self.resp.as_ref().unwrap()); match state { MiddlewareFinished::Done => { diff --git a/src/scope.rs b/src/scope.rs index 23e4a723..a56d753b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,4 +1,4 @@ -use std::cell::{RefCell, UnsafeCell}; +use std::cell::UnsafeCell; use std::marker::PhantomData; use std::mem; use std::rc::Rc; @@ -57,7 +57,7 @@ type NestedInfo = (Resource, Route, Vec>>); pub struct Scope { filters: Vec>>, nested: Vec>, - middlewares: Rc>>>>, + middlewares: Rc>>>, default: Option>, resources: ScopeResources, } @@ -71,7 +71,7 @@ impl Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), - middlewares: Rc::new(RefCell::new(Vec::new())), + middlewares: Rc::new(Vec::new()), default: None, } } @@ -135,7 +135,7 @@ impl Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), - middlewares: Rc::new(RefCell::new(Vec::new())), + middlewares: Rc::new(Vec::new()), default: None, }; let mut scope = f(scope); @@ -178,7 +178,7 @@ impl Scope { filters: Vec::new(), nested: Vec::new(), resources: Rc::new(Vec::new()), - middlewares: Rc::new(RefCell::new(Vec::new())), + middlewares: Rc::new(Vec::new()), default: None, }; let mut scope = f(scope); @@ -332,7 +332,6 @@ impl Scope { pub fn middleware>(mut self, mw: M) -> Scope { Rc::get_mut(&mut self.middlewares) .expect("Can not use after configuration") - .borrow_mut() .push(Box::new(mw)); self } @@ -345,7 +344,7 @@ impl RouteHandler for Scope { // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { if pattern.match_with_params(&mut req, tail, false) { - if self.middlewares.borrow().is_empty() { + if self.middlewares.is_empty() { return match resource.handle(req) { Ok(result) => result, Err(req) => { @@ -393,7 +392,7 @@ impl RouteHandler for Scope { } // default handler - if self.middlewares.borrow().is_empty() { + if self.middlewares.is_empty() { if let Some(ref default) = self.default { match default.handle(req) { Ok(result) => result, @@ -459,7 +458,7 @@ struct Compose { struct ComposeInfo { count: usize, req: HttpRequest, - mws: Rc>>>>, + mws: Rc>>>, resource: Rc>, } @@ -485,7 +484,7 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>>, + req: HttpRequest, mws: Rc>>>, resource: Rc>, ) -> Self { let mut info = ComposeInfo { @@ -529,7 +528,7 @@ type Fut = Box, Error = Error>>; impl StartMiddlewares { fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.borrow().len(); + let len = info.mws.len(); loop { if info.count == len { let reply = { @@ -538,7 +537,7 @@ impl StartMiddlewares { }; return WaitingResponse::init(info, reply); } else { - let state = info.mws.borrow_mut()[info.count].start(&mut info.req); + let state = info.mws[info.count].start(&mut info.req); match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { @@ -557,7 +556,7 @@ impl StartMiddlewares { } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.borrow().len(); + let len = info.mws.len(); 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return None, @@ -574,8 +573,7 @@ impl StartMiddlewares { }; return Some(WaitingResponse::init(info, reply)); } else { - let state = - info.mws.borrow_mut()[info.count].start(&mut info.req); + let state = info.mws[info.count].start(&mut info.req); match state { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { @@ -638,10 +636,10 @@ struct RunMiddlewares { impl RunMiddlewares { fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { let mut curr = 0; - let len = info.mws.borrow().len(); + let len = info.mws.len(); loop { - let state = info.mws.borrow_mut()[curr].response(&mut info.req, resp); + let state = info.mws[curr].response(&mut info.req, resp); resp = match state { Err(err) => { info.count = curr + 1; @@ -667,7 +665,7 @@ impl RunMiddlewares { } fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.borrow().len(); + let len = info.mws.len(); loop { // poll latest fut @@ -684,8 +682,7 @@ impl RunMiddlewares { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { - let state = - info.mws.borrow_mut()[self.curr].response(&mut info.req, resp); + let state = info.mws[self.curr].response(&mut info.req, resp); match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) @@ -754,7 +751,7 @@ impl FinishingMiddlewares { } info.count -= 1; - let state = info.mws.borrow_mut()[info.count as usize] + let state = info.mws[info.count as usize] .finish(&mut info.req, self.resp.as_ref().unwrap()); match state { MiddlewareFinished::Done => { @@ -798,7 +795,7 @@ mod tests { #[test] fn test_scope() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) }) @@ -811,7 +808,7 @@ mod tests { #[test] fn test_scope_root() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope .resource("", |r| r.f(|_| HttpResponse::Ok())) @@ -830,7 +827,7 @@ mod tests { #[test] fn test_scope_root2() { - let mut app = App::new() + let app = App::new() .scope("/app/", |scope| { scope.resource("", |r| r.f(|_| HttpResponse::Ok())) }) @@ -847,7 +844,7 @@ mod tests { #[test] fn test_scope_root3() { - let mut app = App::new() + let app = App::new() .scope("/app/", |scope| { scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) }) @@ -864,7 +861,7 @@ mod tests { #[test] fn test_scope_route() { - let mut app = App::new() + let app = App::new() .scope("app", |scope| { scope .route("/path1", Method::GET, |_: HttpRequest<_>| { @@ -895,7 +892,7 @@ mod tests { #[test] fn test_scope_filter() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope .filter(pred::Get()) @@ -918,7 +915,7 @@ mod tests { #[test] fn test_scope_variable_segment() { - let mut app = App::new() + let app = App::new() .scope("/ab-{project}", |scope| { scope.resource("/path1", |r| { r.f(|r| { @@ -950,7 +947,7 @@ mod tests { fn test_scope_with_state() { struct State; - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.with_state("/t1", State, |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) @@ -967,7 +964,7 @@ mod tests { fn test_scope_with_state_root() { struct State; - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.with_state("/t1", State, |scope| { scope @@ -990,7 +987,7 @@ mod tests { fn test_scope_with_state_root2() { struct State; - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.with_state("/t1/", State, |scope| { scope.resource("", |r| r.f(|_| HttpResponse::Ok())) @@ -1011,7 +1008,7 @@ mod tests { fn test_scope_with_state_root3() { struct State; - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.with_state("/t1/", State, |scope| { scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) @@ -1032,7 +1029,7 @@ mod tests { fn test_scope_with_state_filter() { struct State; - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.with_state("/t1", State, |scope| { scope @@ -1057,7 +1054,7 @@ mod tests { #[test] fn test_nested_scope() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) @@ -1072,7 +1069,7 @@ mod tests { #[test] fn test_nested_scope_root() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { scope @@ -1093,7 +1090,7 @@ mod tests { #[test] fn test_nested_scope_filter() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { scope @@ -1118,7 +1115,7 @@ mod tests { #[test] fn test_nested_scope_with_variable_segment() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.nested("/{project_id}", |scope| { scope.resource("/path1", |r| { @@ -1148,7 +1145,7 @@ mod tests { #[test] fn test_nested2_scope_with_variable_segment() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope.nested("/{project}", |scope| { scope.nested("/{id}", |scope| { @@ -1185,7 +1182,7 @@ mod tests { #[test] fn test_default_resource() { - let mut app = App::new() + let app = App::new() .scope("/app", |scope| { scope .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) @@ -1204,7 +1201,7 @@ mod tests { #[test] fn test_default_resource_propagation() { - let mut app = App::new() + let app = App::new() .scope("/app1", |scope| { scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) }) diff --git a/src/test.rs b/src/test.rs index 7bf0f149..58790f6d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -564,7 +564,7 @@ impl TestRequest { /// with generated request. /// /// This method panics is handler returns actor or async result. - pub fn run>(self, h: H) -> Result { + pub fn run>(self, h: &H) -> Result { let req = self.finish(); let resp = h.handle(req.clone()); diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index bdcde148..806211ea 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -20,23 +20,21 @@ struct MiddlewareTest { } impl middleware::Middleware for MiddlewareTest { - fn start(&mut self, _: &mut HttpRequest) -> Result { + fn start(&self, _: &mut HttpRequest) -> Result { self.start .store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Started::Done) } fn response( - &mut self, _: &mut HttpRequest, resp: HttpResponse, + &self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { self.response .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Response::Done(resp)) } - fn finish( - &mut self, _: &mut HttpRequest, _: &HttpResponse, - ) -> middleware::Finished { + fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { self.finish .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middleware::Finished::Done @@ -434,7 +432,7 @@ struct MiddlewareAsyncTest { } impl middleware::Middleware for MiddlewareAsyncTest { - fn start(&mut self, _: &mut HttpRequest) -> Result { + fn start(&self, _: &mut HttpRequest) -> Result { let to = Delay::new(Instant::now() + Duration::from_millis(10)); let start = Arc::clone(&self.start); @@ -447,7 +445,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { } fn response( - &mut self, _: &mut HttpRequest, resp: HttpResponse, + &self, _: &mut HttpRequest, resp: HttpResponse, ) -> Result { let to = Delay::new(Instant::now() + Duration::from_millis(10)); @@ -460,9 +458,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { ))) } - fn finish( - &mut self, _: &mut HttpRequest, _: &HttpResponse, - ) -> middleware::Finished { + fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { let to = Delay::new(Instant::now() + Duration::from_millis(10)); let finish = Arc::clone(&self.finish); @@ -797,9 +793,7 @@ fn test_async_sync_resource_middleware_multiple() { struct MiddlewareWithErr; impl middleware::Middleware for MiddlewareWithErr { - fn start( - &mut self, _req: &mut HttpRequest, - ) -> Result { + fn start(&self, _req: &mut HttpRequest) -> Result { Err(ErrorInternalServerError("middleware error")) } } @@ -807,9 +801,7 @@ impl middleware::Middleware for MiddlewareWithErr { struct MiddlewareAsyncWithErr; impl middleware::Middleware for MiddlewareAsyncWithErr { - fn start( - &mut self, _req: &mut HttpRequest, - ) -> Result { + fn start(&self, _req: &mut HttpRequest) -> Result { Ok(middleware::Started::Future(Box::new(future::err( ErrorInternalServerError("middleware error"), )))) From c9069e9a3c2e8975c8f888754856ec33a2b52aff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 23:21:28 +0600 Subject: [PATCH 0430/1635] remove unneeded UnsafeCell --- src/route.rs | 11 +++----- src/scope.rs | 17 ++++------- src/test.rs | 4 +-- src/with.rs | 79 +++++++++++++++------------------------------------- 4 files changed, 33 insertions(+), 78 deletions(-) diff --git a/src/route.rs b/src/route.rs index 80fb17d7..fed4deb5 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,4 +1,3 @@ -use std::cell::UnsafeCell; use std::marker::PhantomData; use std::rc::Rc; @@ -297,12 +296,12 @@ impl Route { /// `RouteHandler` wrapper. This struct is required because it needs to be /// shared for resource level middlewares. -struct InnerHandler(Rc>>>); +struct InnerHandler(Rc>>); impl InnerHandler { #[inline] fn new>(h: H) -> Self { - InnerHandler(Rc::new(UnsafeCell::new(Box::new(WrapHandler::new(h))))) + InnerHandler(Rc::new(Box::new(WrapHandler::new(h)))) } #[inline] @@ -313,14 +312,12 @@ impl InnerHandler { R: Responder + 'static, E: Into + 'static, { - InnerHandler(Rc::new(UnsafeCell::new(Box::new(AsyncHandler::new(h))))) + InnerHandler(Rc::new(Box::new(AsyncHandler::new(h)))) } #[inline] pub fn handle(&self, req: HttpRequest) -> AsyncResult { - // reason: handler is unique per thread, handler get called from sync code only - let h = unsafe { &mut *self.0.as_ref().get() }; - h.handle(req) + self.0.handle(req) } } diff --git a/src/scope.rs b/src/scope.rs index a56d753b..c9e7dfb9 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,4 +1,3 @@ -use std::cell::UnsafeCell; use std::marker::PhantomData; use std::mem; use std::rc::Rc; @@ -19,7 +18,7 @@ use resource::ResourceHandler; use router::Resource; type ScopeResource = Rc>; -type Route = UnsafeCell>>; +type Route = Box>; type ScopeResources = Rc)>>; type NestedInfo = (Resource, Route, Vec>>); @@ -145,7 +144,7 @@ impl Scope { state: Rc::clone(&state), filters: scope.take_filters(), })]; - let handler = UnsafeCell::new(Box::new(Wrapper { scope, state })); + let handler = Box::new(Wrapper { scope, state }); self.nested .push((Resource::prefix("", &path), handler, filters)); @@ -184,11 +183,8 @@ impl Scope { let mut scope = f(scope); let filters = scope.take_filters(); - self.nested.push(( - Resource::prefix("", &path), - UnsafeCell::new(Box::new(scope)), - filters, - )); + self.nested + .push((Resource::prefix("", &path), Box::new(scope), filters)); self } @@ -384,10 +380,7 @@ impl RouteHandler for Scope { req.set_prefix_len(prefix_len); req.match_info_mut().set_tail(prefix_len); req.match_info_mut().set_url(url); - - let hnd: &mut RouteHandler<_> = - unsafe { (&mut *(handler.get())).as_mut() }; - return hnd.handle(req); + return handler.handle(req); } } diff --git a/src/test.rs b/src/test.rs index 58790f6d..b7ed4e79 100644 --- a/src/test.rs +++ b/src/test.rs @@ -394,11 +394,11 @@ impl Iterator for TestApp { /// /// fn main() { /// let resp = TestRequest::with_header("content-type", "text/plain") -/// .run(index) +/// .run(&index) /// .unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// -/// let resp = TestRequest::default().run(index).unwrap(); +/// let resp = TestRequest::default().run(&index).unwrap(); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` diff --git a/src/with.rs b/src/with.rs index ac220958..c475ca01 100644 --- a/src/with.rs +++ b/src/with.rs @@ -1,5 +1,4 @@ use futures::{Async, Future, Poll}; -use std::cell::UnsafeCell; use std::marker::PhantomData; use std::rc::Rc; @@ -14,18 +13,8 @@ where T: FromRequest, S: 'static, { - hnd: Rc>, + hnd: Rc, cfg: Rc, -} - -pub struct WithHnd -where - F: Fn(T) -> R, - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - _t: PhantomData, _s: PhantomData, } @@ -38,11 +27,8 @@ where pub fn new(f: F, cfg: T::Config) -> Self { With { cfg: Rc::new(cfg), - hnd: Rc::new(WithHnd { - hnd: Rc::new(UnsafeCell::new(f)), - _t: PhantomData, - _s: PhantomData, - }), + hnd: Rc::new(f), + _s: PhantomData, } } } @@ -82,7 +68,7 @@ where S: 'static, { started: bool, - hnd: Rc>, + hnd: Rc, cfg: Rc, req: HttpRequest, fut1: Option>>, @@ -122,28 +108,19 @@ where } }; - let fut = { - // clone handler, inicrease ref counter - let h = self.hnd.as_ref().hnd.clone(); - // Enforce invariants before entering unsafe code. - // Only two references could exists With struct owns one, and line above - if Rc::weak_count(&h) != 0 || Rc::strong_count(&h) != 2 { - panic!("Multiple copies of handler are in use") - } - let hnd: &mut F = unsafe { &mut *h.as_ref().get() }; - let item = match (*hnd)(item).respond_to(&self.req) { - Ok(item) => item.into(), - Err(e) => return Err(e.into()), - }; - - match item.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(resp) => return Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => fut, - } + let item = match (*self.hnd)(item).respond_to(&self.req) { + Ok(item) => item.into(), + Err(e) => return Err(e.into()), }; - self.fut2 = Some(fut); - self.poll() + + match item.into() { + AsyncResultItem::Err(err) => Err(err), + AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), + AsyncResultItem::Future(fut) => { + self.fut2 = Some(fut); + self.poll() + } + } } } @@ -156,8 +133,9 @@ where T: FromRequest, S: 'static, { - hnd: Rc>, + hnd: Rc, cfg: Rc, + _s: PhantomData, } impl WithAsync @@ -172,11 +150,8 @@ where pub fn new(f: F, cfg: T::Config) -> Self { WithAsync { cfg: Rc::new(cfg), - hnd: Rc::new(WithHnd { - hnd: Rc::new(UnsafeCell::new(f)), - _s: PhantomData, - _t: PhantomData, - }), + hnd: Rc::new(f), + _s: PhantomData, } } } @@ -221,7 +196,7 @@ where S: 'static, { started: bool, - hnd: Rc>, + hnd: Rc, cfg: Rc, req: HttpRequest, fut1: Option>>, @@ -282,17 +257,7 @@ where } }; - self.fut2 = { - // clone handler, inicrease ref counter - let h = self.hnd.as_ref().hnd.clone(); - // Enforce invariants before entering unsafe code. - // Only two references could exists With struct owns one, and line above - if Rc::weak_count(&h) != 0 || Rc::strong_count(&h) != 2 { - panic!("Multiple copies of handler are in use") - } - let hnd: &mut F = unsafe { &mut *h.as_ref().get() }; - Some((*hnd)(item)) - }; + self.fut2 = Some((*self.hnd)(item)); self.poll() } } From 50fbef88fce113a2d0c2972509f1f5ba6f7e9913 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Jun 2018 23:51:25 +0600 Subject: [PATCH 0431/1635] cleanup srver pipeline --- src/pipeline.rs | 168 ++++++++++++++++++++++++++---------------------- 1 file changed, 90 insertions(+), 78 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index fe5e1d02..45843682 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,4 +1,3 @@ -use std::cell::UnsafeCell; use std::marker::PhantomData; use std::rc::Rc; use std::{io, mem}; @@ -36,7 +35,11 @@ pub trait PipelineHandler { } #[doc(hidden)] -pub struct Pipeline(PipelineInfo, PipelineState); +pub struct Pipeline( + PipelineInfo, + PipelineState, + Rc>>>, +); enum PipelineState { None, @@ -57,12 +60,14 @@ impl> PipelineState { } } - fn poll(&mut self, info: &mut PipelineInfo) -> Option> { + fn poll( + &mut self, info: &mut PipelineInfo, mws: &[Box>], + ) -> Option> { match *self { - PipelineState::Starting(ref mut state) => state.poll(info), - PipelineState::Handler(ref mut state) => state.poll(info), - PipelineState::RunMiddlewares(ref mut state) => state.poll(info), - PipelineState::Finishing(ref mut state) => state.poll(info), + PipelineState::Starting(ref mut state) => state.poll(info, mws), + PipelineState::Handler(ref mut state) => state.poll(info, mws), + PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws), + PipelineState::Finishing(ref mut state) => state.poll(info, mws), PipelineState::Completed(ref mut state) => state.poll(info), PipelineState::Response(_) | PipelineState::None | PipelineState::Error => { None @@ -72,9 +77,8 @@ impl> PipelineState { } struct PipelineInfo { - req: UnsafeCell>, + req: HttpRequest, count: u16, - mws: Rc>>>, context: Option>, error: Option, disconnected: Option, @@ -84,9 +88,8 @@ struct PipelineInfo { impl PipelineInfo { fn new(req: HttpRequest) -> PipelineInfo { PipelineInfo { - req: UnsafeCell::new(req), + req, count: 0, - mws: Rc::new(Vec::new()), error: None, context: None, disconnected: None, @@ -94,20 +97,6 @@ impl PipelineInfo { } } - #[inline] - fn req(&self) -> &HttpRequest { - unsafe { &*self.req.get() } - } - - #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] - fn req_mut(&self) -> &mut HttpRequest { - #[allow(mutable_transmutes)] - unsafe { - &mut *self.req.get() - } - } - fn poll_context(&mut self) -> Poll<(), Error> { if let Some(ref mut context) = self.context { match context.poll() { @@ -127,17 +116,16 @@ impl> Pipeline { htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { - mws, - req: UnsafeCell::new(req), + req, count: 0, error: None, context: None, disconnected: None, encoding: handler.encoding(), }; - let state = StartMiddlewares::init(&mut info, handler, htype); + let state = StartMiddlewares::init(&mut info, &mws, handler, htype); - Pipeline(info, state) + Pipeline(info, state, mws) } } @@ -146,6 +134,7 @@ impl Pipeline<(), Inner<()>> { Box::new(Pipeline::<(), Inner<()>>( PipelineInfo::new(HttpRequest::default()), ProcessResponse::init(err.into()), + Rc::new(Vec::new()), )) } } @@ -176,7 +165,7 @@ impl> HttpHandlerTask for Pipeline { loop { if state.is_response() { if let PipelineState::Response(st) = state { - match st.poll_io(io, &mut self.0) { + match st.poll_io(io, &mut self.0, &self.2) { Ok(state) => { self.1 = state; if let Some(error) = self.0.error.take() { @@ -202,7 +191,7 @@ impl> HttpHandlerTask for Pipeline { _ => (), } - match state.poll(&mut self.0) { + match state.poll(&mut self.0, &self.2) { Some(st) => state = st, None => { return { @@ -224,7 +213,7 @@ impl> HttpHandlerTask for Pipeline { _ => (), } - if let Some(st) = state.poll(&mut self.0) { + if let Some(st) = state.poll(&mut self.0, &self.2) { state = st; } else { self.1 = state; @@ -246,21 +235,22 @@ struct StartMiddlewares { impl> StartMiddlewares { fn init( - info: &mut PipelineInfo, hnd: Rc, htype: HandlerType, + info: &mut PipelineInfo, mws: &[Box>], hnd: Rc, + htype: HandlerType, ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately - let len = info.mws.len() as u16; + let len = mws.len() as u16; loop { if info.count == len { - let reply = hnd.handle(info.req().clone(), htype); - return WaitingResponse::init(info, reply); + let reply = hnd.handle(info.req.clone(), htype); + return WaitingResponse::init(info, mws, reply); } else { - let state = info.mws[info.count as usize].start(info.req_mut()); + let state = mws[info.count as usize].start(&mut info.req); match state { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { - return RunMiddlewares::init(info, resp) + return RunMiddlewares::init(info, mws, resp) } Ok(Started::Future(fut)) => { return PipelineState::Starting(StartMiddlewares { @@ -270,46 +260,51 @@ impl> StartMiddlewares { _s: PhantomData, }) } - Err(err) => return RunMiddlewares::init(info, err.into()), + Err(err) => return RunMiddlewares::init(info, mws, err.into()), } } } } - fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - let len = info.mws.len() as u16; + fn poll( + &mut self, info: &mut PipelineInfo, mws: &[Box>], + ) -> Option> { + let len = mws.len() as u16; 'outer: loop { match self.fut.as_mut().unwrap().poll() { Ok(Async::NotReady) => return None, Ok(Async::Ready(resp)) => { info.count += 1; if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, resp)); + return Some(RunMiddlewares::init(info, mws, resp)); } loop { if info.count == len { - let reply = self.hnd.handle(info.req().clone(), self.htype); - return Some(WaitingResponse::init(info, reply)); + let reply = self.hnd.handle(info.req.clone(), self.htype); + return Some(WaitingResponse::init(info, mws, reply)); } else { - let state = - info.mws[info.count as usize].start(info.req_mut()); + let state = mws[info.count as usize].start(&mut info.req); match state { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { - return Some(RunMiddlewares::init(info, resp)); + return Some(RunMiddlewares::init(info, mws, resp)); } Ok(Started::Future(fut)) => { self.fut = Some(fut); continue 'outer; } Err(err) => { - return Some(RunMiddlewares::init(info, err.into())) + return Some(RunMiddlewares::init( + info, + mws, + err.into(), + )) } } } } } - Err(err) => return Some(RunMiddlewares::init(info, err.into())), + Err(err) => return Some(RunMiddlewares::init(info, mws, err.into())), } } } @@ -325,11 +320,12 @@ struct WaitingResponse { impl WaitingResponse { #[inline] fn init( - info: &mut PipelineInfo, reply: AsyncResult, + info: &mut PipelineInfo, mws: &[Box>], + reply: AsyncResult, ) -> PipelineState { match reply.into() { - AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), + AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()), + AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp), AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse { fut, _s: PhantomData, @@ -338,11 +334,15 @@ impl WaitingResponse { } } - fn poll(&mut self, info: &mut PipelineInfo) -> Option> { + fn poll( + &mut self, info: &mut PipelineInfo, mws: &[Box>], + ) -> Option> { match self.fut.poll() { Ok(Async::NotReady) => None, - Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), - Err(err) => Some(RunMiddlewares::init(info, err.into())), + Ok(Async::Ready(response)) => { + Some(RunMiddlewares::init(info, mws, response)) + } + Err(err) => Some(RunMiddlewares::init(info, mws, err.into())), } } } @@ -357,15 +357,17 @@ struct RunMiddlewares { impl RunMiddlewares { #[inline] - fn init(info: &mut PipelineInfo, mut resp: HttpResponse) -> PipelineState { + fn init( + info: &mut PipelineInfo, mws: &[Box>], mut resp: HttpResponse, + ) -> PipelineState { if info.count == 0 { return ProcessResponse::init(resp); } let mut curr = 0; - let len = info.mws.len(); + let len = mws.len(); loop { - let state = info.mws[curr].response(info.req_mut(), resp); + let state = mws[curr].response(&mut info.req, resp); resp = match state { Err(err) => { info.count = (curr + 1) as u16; @@ -391,8 +393,10 @@ impl RunMiddlewares { } } - fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - let len = info.mws.len(); + fn poll( + &mut self, info: &mut PipelineInfo, mws: &[Box>], + ) -> Option> { + let len = mws.len(); loop { // poll latest fut @@ -409,7 +413,7 @@ impl RunMiddlewares { if self.curr == len { return Some(ProcessResponse::init(resp)); } else { - let state = info.mws[self.curr].response(info.req_mut(), resp); + let state = mws[self.curr].response(&mut info.req, resp); match state { Err(err) => return Some(ProcessResponse::init(err.into())), Ok(Response::Done(r)) => { @@ -480,6 +484,7 @@ impl ProcessResponse { fn poll_io( mut self, io: &mut Writer, info: &mut PipelineInfo, + mws: &[Box>], ) -> Result, PipelineState> { loop { if self.drain.is_none() && self.running != RunningState::Paused { @@ -491,7 +496,7 @@ impl ProcessResponse { self.resp.content_encoding().unwrap_or(info.encoding); let result = match io.start( - info.req_mut().as_mut(), + info.req.as_mut(), &mut self.resp, encoding, ) { @@ -499,7 +504,7 @@ impl ProcessResponse { Err(err) => { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, self.resp, + info, mws, self.resp, )); } }; @@ -541,7 +546,7 @@ impl ProcessResponse { if let Err(err) = io.write_eof() { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, self.resp, + info, mws, self.resp, )); } break; @@ -552,7 +557,7 @@ impl ProcessResponse { Err(err) => { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, self.resp, + info, mws, self.resp, )); } Ok(result) => result, @@ -564,7 +569,9 @@ impl ProcessResponse { } Err(err) => { info.error = Some(err); - return Ok(FinishingMiddlewares::init(info, self.resp)); + return Ok(FinishingMiddlewares::init( + info, mws, self.resp, + )); } }, IOState::Actor(mut ctx) => { @@ -586,7 +593,7 @@ impl ProcessResponse { info.error = Some(err.into()); return Ok( FinishingMiddlewares::init( - info, self.resp, + info, mws, self.resp, ), ); } @@ -598,7 +605,7 @@ impl ProcessResponse { info.error = Some(err.into()); return Ok( FinishingMiddlewares::init( - info, self.resp, + info, mws, self.resp, ), ); } @@ -623,7 +630,7 @@ impl ProcessResponse { Err(err) => { info.error = Some(err); return Ok(FinishingMiddlewares::init( - info, self.resp, + info, mws, self.resp, )); } } @@ -657,7 +664,7 @@ impl ProcessResponse { Ok(Async::NotReady) => return Err(PipelineState::Response(self)), Err(err) => { info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, self.resp)); + return Ok(FinishingMiddlewares::init(info, mws, self.resp)); } } } @@ -671,11 +678,11 @@ impl ProcessResponse { Ok(_) => (), Err(err) => { info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, self.resp)); + return Ok(FinishingMiddlewares::init(info, mws, self.resp)); } } self.resp.set_response_size(io.written()); - Ok(FinishingMiddlewares::init(info, self.resp)) + Ok(FinishingMiddlewares::init(info, mws, self.resp)) } _ => Err(PipelineState::Response(self)), } @@ -692,7 +699,9 @@ struct FinishingMiddlewares { impl FinishingMiddlewares { #[inline] - fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { + fn init( + info: &mut PipelineInfo, mws: &[Box>], resp: HttpResponse, + ) -> PipelineState { if info.count == 0 { Completed::init(info) } else { @@ -702,7 +711,7 @@ impl FinishingMiddlewares { _s: PhantomData, _h: PhantomData, }; - if let Some(st) = state.poll(info) { + if let Some(st) = state.poll(info, mws) { st } else { PipelineState::Finishing(state) @@ -710,7 +719,9 @@ impl FinishingMiddlewares { } } - fn poll(&mut self, info: &mut PipelineInfo) -> Option> { + fn poll( + &mut self, info: &mut PipelineInfo, mws: &[Box>], + ) -> Option> { loop { // poll latest fut let not_ready = if let Some(ref mut fut) = self.fut { @@ -734,7 +745,7 @@ impl FinishingMiddlewares { } info.count -= 1; - let state = info.mws[info.count as usize].finish(info.req_mut(), &self.resp); + let state = mws[info.count as usize].finish(&mut info.req, &self.resp); match state { Finished::Done => { if info.count == 0 { @@ -826,10 +837,11 @@ mod tests { .unwrap(); assert!(state.poll(&mut info).is_none()); - let pp = Pipeline(info, PipelineState::Completed(state)); + let pp = + Pipeline(info, PipelineState::Completed(state), Rc::new(Vec::new())); assert!(!pp.is_done()); - let Pipeline(mut info, st) = pp; + let Pipeline(mut info, st, mws) = pp; let mut st = st.completed().unwrap(); drop(addr); From 17c033030ba6deda3f32755f06e4da04f46ab532 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Jun 2018 08:55:19 +0600 Subject: [PATCH 0432/1635] Revert "remove unnecessary use of unsafe in read_from_io" This reverts commit da237611cb429da5fb42ab9d6be52837d1de3dda. --- src/server/utils.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/server/utils.rs b/src/server/utils.rs index b0470d76..e0e7e7f6 100644 --- a/src/server/utils.rs +++ b/src/server/utils.rs @@ -1,5 +1,5 @@ use bytes::{BufMut, BytesMut}; -use futures::Poll; +use futures::{Async, Poll}; use std::io; use super::IoStream; @@ -10,8 +10,22 @@ const HW_BUFFER_SIZE: usize = 32_768; pub fn read_from_io( io: &mut T, buf: &mut BytesMut, ) -> Poll { - if buf.remaining_mut() < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE); + unsafe { + 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 { + Ok(Async::NotReady) + } else { + Err(e) + } + } + } } - io.read_buf(buf) } From edd22bb2790fb18ebce07d79996a99a1ec44d821 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Jun 2018 09:01:20 +0600 Subject: [PATCH 0433/1635] refactor read_from_io --- src/client/parser.rs | 8 ++++---- src/server/channel.rs | 4 ++-- src/server/mod.rs | 27 +++++++++++++++++++++++++-- src/server/utils.rs | 31 ------------------------------- 4 files changed, 31 insertions(+), 39 deletions(-) delete mode 100644 src/server/utils.rs diff --git a/src/client/parser.rs b/src/client/parser.rs index b292e8d4..1638d8eb 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -8,7 +8,7 @@ use std::mem; use error::{ParseError, PayloadError}; use server::h1decoder::EncodingDecoder; -use server::{utils, IoStream}; +use server::IoStream; use super::response::ClientMessage; use super::ClientResponse; @@ -39,7 +39,7 @@ impl HttpResponseParser { { // if buf is empty parse_message will always return NotReady, let's avoid that if buf.is_empty() { - match utils::read_from_io(io, buf) { + match io.read_available(buf) { Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect), Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), @@ -59,7 +59,7 @@ impl HttpResponseParser { if buf.capacity() >= MAX_BUFFER_SIZE { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } - match utils::read_from_io(io, buf) { + match io.read_available(buf) { Ok(Async::Ready(0)) => { return Err(HttpResponseParserError::Disconnect) } @@ -83,7 +83,7 @@ impl HttpResponseParser { if self.decoder.is_some() { loop { // read payload - let (not_ready, stream_finished) = match utils::read_from_io(io, buf) { + let (not_ready, stream_finished) = match io.read_available(buf) { Ok(Async::Ready(0)) => (false, true), Err(err) => return Err(err.into()), Ok(Async::NotReady) => (true, false), diff --git a/src/server/channel.rs b/src/server/channel.rs index d236963b..26061352 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -7,7 +7,7 @@ use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use super::settings::WorkerSettings; -use super::{h1, h2, utils, HttpHandler, IoStream}; +use super::{h1, h2, HttpHandler, IoStream}; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; @@ -139,7 +139,7 @@ where ref mut io, ref mut buf, )) => { - match utils::read_from_io(io, buf) { + match io.read_available(buf) { Ok(Async::Ready(0)) | Err(_) => { debug!("Ignored premature client disconnection"); settings.remove_channel(); diff --git a/src/server/mod.rs b/src/server/mod.rs index b65fc3a8..c98579d0 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,7 +2,7 @@ use std::net::Shutdown; use std::{io, time}; -use bytes::BytesMut; +use bytes::{BufMut, BytesMut}; use futures::{Async, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; @@ -18,7 +18,6 @@ pub(crate) mod helpers; pub(crate) mod settings; pub(crate) mod shared; mod srv; -pub(crate) mod utils; mod worker; pub use self::settings::ServerSettings; @@ -37,6 +36,9 @@ use httpresponse::HttpResponse; /// max buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; +const LW_BUFFER_SIZE: usize = 4096; +const HW_BUFFER_SIZE: usize = 32_768; + /// Create new http server with application factory. /// /// This is shortcut for `server::HttpServer::new()` method. @@ -213,6 +215,27 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>; fn set_linger(&mut self, dur: Option) -> io::Result<()>; + + fn read_available(&mut self, buf: &mut BytesMut) -> Poll { + unsafe { + if buf.remaining_mut() < LW_BUFFER_SIZE { + buf.reserve(HW_BUFFER_SIZE); + } + match self.read(buf.bytes_mut()) { + Ok(n) => { + buf.advance_mut(n); + Ok(Async::Ready(n)) + } + Err(e) => { + if e.kind() == io::ErrorKind::WouldBlock { + Ok(Async::NotReady) + } else { + Err(e) + } + } + } + } + } } impl IoStream for TcpStream { diff --git a/src/server/utils.rs b/src/server/utils.rs deleted file mode 100644 index e0e7e7f6..00000000 --- a/src/server/utils.rs +++ /dev/null @@ -1,31 +0,0 @@ -use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; -use std::io; - -use super::IoStream; - -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 32_768; - -pub fn read_from_io( - io: &mut T, buf: &mut BytesMut, -) -> Poll { - unsafe { - 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 { - Ok(Async::NotReady) - } else { - Err(e) - } - } - } - } -} From fc7238baee828054e1cd10f862483fca428abcb4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Jun 2018 11:30:40 +0600 Subject: [PATCH 0434/1635] refactor read_from_io --- src/client/parser.rs | 16 ++++---- src/server/channel.rs | 2 +- src/server/h1.rs | 94 ++++++++++++++++++++++--------------------- src/server/mod.rs | 39 +++++++++++------- 4 files changed, 84 insertions(+), 67 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index 1638d8eb..4668f58a 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -40,8 +40,10 @@ impl HttpResponseParser { // if buf is empty parse_message will always return NotReady, let's avoid that if buf.is_empty() { match io.read_available(buf) { - Ok(Async::Ready(0)) => return Err(HttpResponseParserError::Disconnect), - Ok(Async::Ready(_)) => (), + Ok(Async::Ready(true)) => { + return Err(HttpResponseParserError::Disconnect) + } + Ok(Async::Ready(false)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => return Err(HttpResponseParserError::Error(err.into())), } @@ -60,10 +62,10 @@ impl HttpResponseParser { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } match io.read_available(buf) { - Ok(Async::Ready(0)) => { + Ok(Async::Ready(true)) => { return Err(HttpResponseParserError::Disconnect) } - Ok(Async::Ready(_)) => (), + Ok(Async::Ready(false)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { return Err(HttpResponseParserError::Error(err.into())) @@ -84,10 +86,10 @@ impl HttpResponseParser { loop { // read payload let (not_ready, stream_finished) = match io.read_available(buf) { - Ok(Async::Ready(0)) => (false, true), - Err(err) => return Err(err.into()), + Ok(Async::Ready(true)) => (false, true), + Ok(Async::Ready(false)) => (false, false), Ok(Async::NotReady) => (true, false), - _ => (false, false), + Err(err) => return Err(err.into()), }; match self.decoder.as_mut().unwrap().decode(buf) { diff --git a/src/server/channel.rs b/src/server/channel.rs index 26061352..1439ddcb 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -140,7 +140,7 @@ where ref mut buf, )) => { match io.read_available(buf) { - Ok(Async::Ready(0)) | Err(_) => { + Ok(Async::Ready(true)) | Err(_) => { debug!("Ignored premature client disconnection"); settings.remove_channel(); if let Some(n) = self.node.as_mut() { diff --git a/src/server/h1.rs b/src/server/h1.rs index ababda6b..87eeccb0 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -1,10 +1,9 @@ use std::collections::VecDeque; -use std::io; use std::net::SocketAddr; use std::rc::Rc; use std::time::{Duration, Instant}; -use bytes::{BufMut, BytesMut}; +use bytes::BytesMut; use futures::{Async, Future, Poll}; use tokio_timer::Delay; @@ -22,8 +21,6 @@ use super::Writer; use super::{HttpHandler, HttpHandlerTask, IoStream}; const MAX_PIPELINED_MESSAGES: usize = 16; -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 32_768; bitflags! { struct Flags: u8 { @@ -32,6 +29,7 @@ bitflags! { const KEEPALIVE = 0b0000_0100; const SHUTDOWN = 0b0000_1000; const DISCONNECTED = 0b0001_0000; + const POLLED = 0b0010_0000; } } @@ -173,29 +171,58 @@ where #[inline] /// read data from stream pub fn poll_io(&mut self) { + if !self.flags.contains(Flags::POLLED) { + self.parse(); + self.flags.insert(Flags::POLLED); + return; + } // read io from socket if !self.flags.intersects(Flags::ERROR) && self.tasks.len() < MAX_PIPELINED_MESSAGES && self.can_read() { - if self.read() { - // notify all tasks - self.stream.disconnected(); - for entry in &mut self.tasks { - entry.pipe.disconnected() - } - // kill keepalive - self.keepalive_timer.take(); + let res = self.stream.get_mut().read_available(&mut self.buf); + match res { + //self.stream.get_mut().read_available(&mut self.buf) { + Ok(Async::Ready(disconnected)) => { + if disconnected { + // notify all tasks + self.stream.disconnected(); + for entry in &mut self.tasks { + entry.pipe.disconnected() + } + // kill keepalive + self.keepalive_timer.take(); - // on parse error, stop reading stream but tasks need to be - // completed - self.flags.insert(Flags::ERROR); + // on parse error, stop reading stream but tasks need to be + // completed + self.flags.insert(Flags::ERROR); - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } + } else { + self.parse(); + } + } + Ok(Async::NotReady) => (), + Err(_) => { + // notify all tasks + self.stream.disconnected(); + for entry in &mut self.tasks { + entry.pipe.disconnected() + } + // kill keepalive + self.keepalive_timer.take(); + + // on parse error, stop reading stream but tasks need to be + // completed + self.flags.insert(Flags::ERROR); + + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } } - } else { - self.parse(); } } } @@ -434,35 +461,12 @@ where } } } - - #[inline] - fn read(&mut self) -> bool { - loop { - unsafe { - if self.buf.remaining_mut() < LW_BUFFER_SIZE { - self.buf.reserve(HW_BUFFER_SIZE); - } - match self.stream.get_mut().read(self.buf.bytes_mut()) { - Ok(n) => { - if n == 0 { - return true; - } else { - self.buf.advance_mut(n); - } - } - Err(e) => { - return e.kind() != io::ErrorKind::WouldBlock; - } - } - } - } - } } #[cfg(test)] mod tests { use std::net::Shutdown; - use std::{cmp, time}; + use std::{cmp, io, time}; use bytes::{Buf, Bytes, BytesMut}; use http::{Method, Version}; @@ -606,7 +610,7 @@ mod tests { let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); h1.poll_io(); - h1.parse(); + h1.poll_io(); assert_eq!(h1.tasks.len(), 1); } @@ -621,7 +625,7 @@ mod tests { let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); h1.poll_io(); - h1.parse(); + h1.poll_io(); assert!(h1.flags.contains(Flags::ERROR)); } diff --git a/src/server/mod.rs b/src/server/mod.rs index c98579d0..6ecc75d1 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -216,21 +216,32 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn set_linger(&mut self, dur: Option) -> io::Result<()>; - fn read_available(&mut self, buf: &mut BytesMut) -> Poll { - unsafe { - if buf.remaining_mut() < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE); - } - match self.read(buf.bytes_mut()) { - Ok(n) => { - buf.advance_mut(n); - Ok(Async::Ready(n)) + fn read_available(&mut self, buf: &mut BytesMut) -> Poll { + let mut read_some = false; + loop { + unsafe { + if buf.remaining_mut() < LW_BUFFER_SIZE { + buf.reserve(HW_BUFFER_SIZE); } - Err(e) => { - if e.kind() == io::ErrorKind::WouldBlock { - Ok(Async::NotReady) - } else { - Err(e) + match self.read(buf.bytes_mut()) { + Ok(n) => { + if n == 0 { + return Ok(Async::Ready(!read_some)); + } else { + read_some = true; + buf.advance_mut(n); + } + } + Err(e) => { + return if e.kind() == io::ErrorKind::WouldBlock { + if read_some { + Ok(Async::Ready(false)) + } else { + Ok(Async::NotReady) + } + } else { + Err(e) + }; } } } From 6c445759233e60f2ecb24232839df21cc8686ee4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Jun 2018 11:44:38 +0600 Subject: [PATCH 0435/1635] transmute names once --- src/router.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/router.rs b/src/router.rs index e04956e9..9aa173f7 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; +use std::mem; use std::rc::Rc; use regex::{escape, Regex}; @@ -143,7 +144,7 @@ enum PatternElement { enum PatternType { Static(String), Prefix(String), - Dynamic(Regex, Vec, usize), + Dynamic(Regex, Vec<&'static str>, usize), } #[derive(Debug, Copy, Clone, PartialEq)] @@ -216,9 +217,17 @@ impl Resource { Ok(re) => re, Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), }; + // actix creates one router per thread let names = re .capture_names() - .filter_map(|name| name.map(|name| name.to_owned())) + .filter_map(|name| { + name.map(|name| { + let name = name.to_owned(); + let s: &'static str = unsafe { mem::transmute(name.as_str()) }; + mem::forget(name); + s + }) + }) .collect(); PatternType::Dynamic(re, names, len) } else if for_prefix { @@ -308,10 +317,7 @@ impl Resource { let params = req.match_info_mut(); params.set_tail(len as u16); for (idx, segment) in segments.iter().enumerate() { - // reason: Router is part of App, which is unique per thread - // app is alive during whole life of a thread - let name = unsafe { &*(names[idx].as_str() as *const _) }; - params.add(name, *segment); + params.add(names[idx], *segment); } true } @@ -377,10 +383,7 @@ impl Resource { let params = req.match_info_mut(); params.set_tail(tail_len as u16); for (idx, segment) in segments.iter().enumerate() { - // reason: Router is part of App, which is unique per thread - // app is alive during whole life of a thread - let name = unsafe { &*(names[idx].as_str() as *const _) }; - params.add(name, *segment); + params.add(names[idx], *segment); } Some(tail_len) } From 765c38e7b9adf7151d7aeef2c176bf190073b18f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Jun 2018 11:47:33 +0600 Subject: [PATCH 0436/1635] remove libc dependency --- Cargo.toml | 1 - src/lib.rs | 1 - src/middleware/logger.rs | 6 ------ 3 files changed, 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4ca9262..787ade1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,6 @@ h2 = "0.1" fnv = "1.0.5" http = "^0.1.5" httparse = "1.2" -libc = "0.2" log = "0.4" mime = "0.3" mime_guess = "2.0.0-alpha" diff --git a/src/lib.rs b/src/lib.rs index 90b74381..7cb2b908 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,6 @@ extern crate futures_cpupool; extern crate http as modhttp; extern crate httparse; extern crate language_tags; -extern crate libc; extern crate mime; extern crate mime_guess; extern crate mio; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index a731d695..c5701ef8 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -3,7 +3,6 @@ use std::collections::HashSet; use std::env; use std::fmt::{self, Display, Formatter}; -use libc; use regex::Regex; use time; @@ -53,8 +52,6 @@ use middleware::{Finished, Middleware, Started}; /// /// `%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 @@ -181,7 +178,6 @@ impl Format { "%" => FormatText::Percent, "a" => FormatText::RemoteAddr, "t" => FormatText::RequestTime, - "P" => FormatText::Pid, "r" => FormatText::RequestLine, "s" => FormatText::ResponseStatus, "b" => FormatText::ResponseSize, @@ -205,7 +201,6 @@ impl Format { #[derive(Debug, Clone)] pub enum FormatText { Str(String), - Pid, Percent, RequestLine, RequestTime, @@ -247,7 +242,6 @@ impl FormatText { } FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt), FormatText::ResponseSize => resp.response_size().fmt(fmt), - FormatText::Pid => unsafe { libc::getpid().fmt(fmt) }, FormatText::Time => { let rt = time::now() - entry_time; let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0; From dda6ee95dff239a386cb7d6d8f052b21545e1875 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Jun 2018 09:33:32 +0200 Subject: [PATCH 0437/1635] Changes the router to use atoms internally (#341) --- Cargo.toml | 7 ++++--- src/lib.rs | 1 + src/param.rs | 23 ++++++++++++----------- src/router.rs | 20 ++++++++------------ 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 787ade1e..e60d0793 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,9 @@ license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] build = "build.rs" +[package.metadata.docs.rs] +features = ["tls", "alpn", "session", "brotli", "flate2-c"] + [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } appveyor = { repository = "fafhrd91/actix-web-hdy9d" } @@ -46,9 +49,6 @@ flate2-c = ["flate2/miniz-sys"] # rust backend for flate2 crate flate2-rust = ["flate2/rust_backend"] -[package.metadata.docs.rs] -features = ["tls", "alpn", "session", "brotli", "flate2-c"] - [dependencies] # actix = "0.6.1" actix = { git="https://github.com/actix/actix.git" } @@ -103,6 +103,7 @@ tokio-tls = { version="0.1", optional = true } # openssl openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } +string_cache = "0.7.3" [dev-dependencies] env_logger = "0.5" diff --git a/src/lib.rs b/src/lib.rs index 7cb2b908..5c7bfbac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,6 +114,7 @@ extern crate net2; extern crate parking_lot; extern crate rand; extern crate slab; +extern crate string_cache; extern crate tokio; extern crate tokio_io; extern crate tokio_reactor; diff --git a/src/param.rs b/src/param.rs index 1329ff68..54325ee1 100644 --- a/src/param.rs +++ b/src/param.rs @@ -5,6 +5,7 @@ use std::str::FromStr; use http::StatusCode; use smallvec::SmallVec; +use string_cache::DefaultAtom as Atom; use error::{InternalError, ResponseError, UriSegmentError}; use uri::Url; @@ -19,9 +20,9 @@ pub trait FromParam: Sized { fn from_param(s: &str) -> Result; } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub(crate) enum ParamItem { - Static(&'static str), + Static(Atom), UrlSegment(u16, u16), } @@ -32,7 +33,7 @@ pub(crate) enum ParamItem { pub struct Params { url: Url, pub(crate) tail: u16, - segments: SmallVec<[(&'static str, ParamItem); 3]>, + segments: SmallVec<[(Atom, ParamItem); 3]>, } impl Params { @@ -56,12 +57,12 @@ impl Params { self.tail = tail; } - pub(crate) fn add(&mut self, name: &'static str, value: ParamItem) { + pub(crate) fn add(&mut self, name: Atom, value: ParamItem) { self.segments.push((name, value)); } - pub(crate) fn add_static(&mut self, name: &'static str, value: &'static str) { - self.segments.push((name, ParamItem::Static(value))); + pub(crate) fn add_static(&mut self, name: &str, value: &'static str) { + self.segments.push((Atom::from(name), ParamItem::Static(Atom::from(value)))); } /// Check if there are any matched patterns @@ -77,9 +78,9 @@ impl Params { /// Get matched parameter by name without type conversion pub fn get(&self, key: &str) -> Option<&str> { for item in self.segments.iter() { - if key == item.0 { + if key == &item.0 { return match item.1 { - ParamItem::Static(s) => Some(s), + ParamItem::Static(ref s) => Some(&s), ParamItem::UrlSegment(s, e) => { Some(&self.url.path()[(s as usize)..(e as usize)]) } @@ -138,13 +139,13 @@ impl<'a> Iterator for ParamsIter<'a> { if self.idx < self.params.len() { let idx = self.idx; let res = match self.params.segments[idx].1 { - ParamItem::Static(s) => s, + ParamItem::Static(ref s) => &s, ParamItem::UrlSegment(s, e) => { &self.params.url.path()[(s as usize)..(e as usize)] } }; self.idx += 1; - return Some((self.params.segments[idx].0, res)); + return Some((&self.params.segments[idx].0, res)); } None } @@ -164,7 +165,7 @@ impl<'a> Index for &'a Params { fn index(&self, idx: usize) -> &str { match self.segments[idx].1 { - ParamItem::Static(s) => s, + ParamItem::Static(ref s) => &s, ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)], } } diff --git a/src/router.rs b/src/router.rs index 9aa173f7..03e299e1 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; use std::hash::{Hash, Hasher}; -use std::mem; use std::rc::Rc; use regex::{escape, Regex}; @@ -12,6 +11,8 @@ use param::ParamItem; use resource::ResourceHandler; use server::ServerSettings; +use string_cache::DefaultAtom as Atom; + /// Interface for application router. pub struct Router(Rc); @@ -144,7 +145,7 @@ enum PatternElement { enum PatternType { Static(String), Prefix(String), - Dynamic(Regex, Vec<&'static str>, usize), + Dynamic(Regex, Vec, usize), } #[derive(Debug, Copy, Clone, PartialEq)] @@ -221,12 +222,7 @@ impl Resource { let names = re .capture_names() .filter_map(|name| { - name.map(|name| { - let name = name.to_owned(); - let s: &'static str = unsafe { mem::transmute(name.as_str()) }; - mem::forget(name); - s - }) + name.map(|name| Atom::from(name)) }) .collect(); PatternType::Dynamic(re, names, len) @@ -316,8 +312,8 @@ impl Resource { let len = req.path().len(); let params = req.match_info_mut(); params.set_tail(len as u16); - for (idx, segment) in segments.iter().enumerate() { - params.add(names[idx], *segment); + for (idx, segment) in segments.into_iter().enumerate() { + params.add(names[idx].clone(), segment); } true } @@ -382,8 +378,8 @@ impl Resource { let params = req.match_info_mut(); params.set_tail(tail_len as u16); - for (idx, segment) in segments.iter().enumerate() { - params.add(names[idx], *segment); + for (idx, segment) in segments.into_iter().enumerate() { + params.add(names[idx].clone(), segment); } Some(tail_len) } From 7bc7b4839b1ead19d8a2e8937ba78cff9ed307bc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 22 Jun 2018 11:32:32 +0200 Subject: [PATCH 0438/1635] Switch from fnv to a identity hasher in extensions (#342) --- Cargo.toml | 1 - src/extensions.rs | 30 +++++++++++++++++++++++++++--- src/lib.rs | 1 - 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e60d0793..aa523e17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,6 @@ base64 = "0.9" bitflags = "1.0" failure = "0.1.1" h2 = "0.1" -fnv = "1.0.5" http = "^0.1.5" httparse = "1.2" log = "0.4" diff --git a/src/extensions.rs b/src/extensions.rs index 7fdd142b..02487325 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,11 +1,35 @@ use std::any::{Any, TypeId}; use std::collections::HashMap; +use std::hash::{BuildHasherDefault, Hasher}; use std::fmt; -use std::hash::BuildHasherDefault; -use fnv::FnvHasher; +struct IdHasher { + id: u64, +} -type AnyMap = HashMap, BuildHasherDefault>; +impl Default for IdHasher { + fn default() -> IdHasher { + IdHasher { id: 0 } + } +} + +impl Hasher for IdHasher { + fn write(&mut self, bytes: &[u8]) { + for &x in bytes { + self.id.wrapping_add(x as u64); + } + } + + fn write_u64(&mut self, u: u64) { + self.id = u; + } + + fn finish(&self) -> u64 { + self.id + } +} + +type AnyMap = HashMap, BuildHasherDefault>; /// A type map of request extensions. pub struct Extensions { diff --git a/src/lib.rs b/src/lib.rs index 5c7bfbac..22016c11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,6 @@ extern crate time; extern crate bitflags; #[macro_use] extern crate failure; -extern crate fnv; #[macro_use] extern crate lazy_static; #[macro_use] From 4fadff63f4b1eec0dd3646df4beb69adb52c5204 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Jun 2018 09:57:03 +0600 Subject: [PATCH 0439/1635] Use Box::leak for dynamic param names --- Cargo.toml | 1 - src/lib.rs | 1 - src/middleware/csrf.rs | 4 ++-- src/param.rs | 13 ++++++------- src/router.rs | 14 ++++++++------ 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aa523e17..19f3ebdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,6 @@ tokio-tls = { version="0.1", optional = true } # openssl openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } -string_cache = "0.7.3" [dev-dependencies] env_logger = "0.5" diff --git a/src/lib.rs b/src/lib.rs index 22016c11..92ff1319 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,7 +113,6 @@ extern crate net2; extern crate parking_lot; extern crate rand; extern crate slab; -extern crate string_cache; extern crate tokio; extern crate tokio_io; extern crate tokio_reactor; diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 670ec1c1..faa763e2 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -15,7 +15,7 @@ //! the allowed origins. //! //! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr) -//! if you want to allow requests with unsafe methods via +//! if you want to allow requests with unprotected methods via //! [CORS](../cors/struct.Cors.html). //! //! # Example @@ -175,7 +175,7 @@ impl CsrfFilter { /// /// The filter is conservative by default, but it should be safe to allow /// missing `Origin` headers because a cross-site attacker cannot prevent - /// the browser from sending `Origin` on unsafe requests. + /// the browser from sending `Origin` on unprotected requests. pub fn allow_missing_origin(mut self) -> CsrfFilter { self.allow_missing_origin = true; self diff --git a/src/param.rs b/src/param.rs index 54325ee1..1003c642 100644 --- a/src/param.rs +++ b/src/param.rs @@ -5,7 +5,6 @@ use std::str::FromStr; use http::StatusCode; use smallvec::SmallVec; -use string_cache::DefaultAtom as Atom; use error::{InternalError, ResponseError, UriSegmentError}; use uri::Url; @@ -22,7 +21,7 @@ pub trait FromParam: Sized { #[derive(Debug, Clone)] pub(crate) enum ParamItem { - Static(Atom), + Static(&'static str), UrlSegment(u16, u16), } @@ -33,7 +32,7 @@ pub(crate) enum ParamItem { pub struct Params { url: Url, pub(crate) tail: u16, - segments: SmallVec<[(Atom, ParamItem); 3]>, + segments: SmallVec<[(&'static str, ParamItem); 3]>, } impl Params { @@ -57,12 +56,12 @@ impl Params { self.tail = tail; } - pub(crate) fn add(&mut self, name: Atom, value: ParamItem) { + pub(crate) fn add(&mut self, name: &'static str, value: ParamItem) { self.segments.push((name, value)); } - pub(crate) fn add_static(&mut self, name: &str, value: &'static str) { - self.segments.push((Atom::from(name), ParamItem::Static(Atom::from(value)))); + pub(crate) fn add_static(&mut self, name: &'static str, value: &'static str) { + self.segments.push((name, ParamItem::Static(value))); } /// Check if there are any matched patterns @@ -78,7 +77,7 @@ impl Params { /// Get matched parameter by name without type conversion pub fn get(&self, key: &str) -> Option<&str> { for item in self.segments.iter() { - if key == &item.0 { + if key == item.0 { return match item.1 { ParamItem::Static(ref s) => Some(&s), ParamItem::UrlSegment(s, e) => { diff --git a/src/router.rs b/src/router.rs index 03e299e1..e37e46b4 100644 --- a/src/router.rs +++ b/src/router.rs @@ -11,8 +11,6 @@ use param::ParamItem; use resource::ResourceHandler; use server::ServerSettings; -use string_cache::DefaultAtom as Atom; - /// Interface for application router. pub struct Router(Rc); @@ -145,7 +143,7 @@ enum PatternElement { enum PatternType { Static(String), Prefix(String), - Dynamic(Regex, Vec, usize), + Dynamic(Regex, Vec<&'static str>, usize), } #[derive(Debug, Copy, Clone, PartialEq)] @@ -222,7 +220,11 @@ impl Resource { let names = re .capture_names() .filter_map(|name| { - name.map(|name| Atom::from(name)) + name.map(|name| { + let s: &'static str = + Box::leak(name.to_owned().into_boxed_str()); + s + }) }) .collect(); PatternType::Dynamic(re, names, len) @@ -313,7 +315,7 @@ impl Resource { let params = req.match_info_mut(); params.set_tail(len as u16); for (idx, segment) in segments.into_iter().enumerate() { - params.add(names[idx].clone(), segment); + params.add(names[idx], segment); } true } @@ -379,7 +381,7 @@ impl Resource { let params = req.match_info_mut(); params.set_tail(tail_len as u16); for (idx, segment) in segments.into_iter().enumerate() { - params.add(names[idx].clone(), segment); + params.add(names[idx], segment); } Some(tail_len) } From 756227896b354d3f9ad7d66f181ce4b58d513268 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Jun 2018 10:13:09 +0600 Subject: [PATCH 0440/1635] update set_date impl --- src/server/h1writer.rs | 6 +++--- src/server/h2writer.rs | 4 ++-- src/server/settings.rs | 24 ++++++++++++------------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index ebb0fff3..bf91a030 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -101,7 +101,7 @@ impl Writer for H1Writer { #[inline] fn set_date(&self, dst: &mut BytesMut) { - self.settings.set_date(dst) + self.settings.set_date(dst, true) } #[inline] @@ -214,7 +214,7 @@ impl Writer for H1Writer { // optimized date header, set_date writes \r\n if !has_date { - self.settings.set_date(&mut buffer); + self.settings.set_date(&mut buffer, true); } else { // msg eof buffer.extend_from_slice(b"\r\n"); @@ -298,7 +298,7 @@ impl Writer for H1Writer { if err.kind() == io::ErrorKind::WriteZero { self.disconnected(); } - + return Err(err); } Ok(val) => val, diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index c2731b11..c816c12a 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -73,7 +73,7 @@ impl Writer for H2Writer { #[inline] fn set_date(&self, dst: &mut BytesMut) { - self.settings.set_date(dst) + self.settings.set_date(dst, true) } #[inline] @@ -97,7 +97,7 @@ 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); - self.settings.set_date_simple(&mut bytes); + self.settings.set_date(&mut bytes, false); msg.headers_mut() .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); } diff --git a/src/server/settings.rs b/src/server/settings.rs index 7ea08c9d..cf58e432 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -229,16 +229,16 @@ impl WorkerSettings { unsafe { &mut *self.date.get() }.update(); } - pub fn set_date(&self, dst: &mut BytesMut) { - let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; - buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(&(unsafe { &*self.date.get() }.bytes)); - buf[35..].copy_from_slice(b"\r\n\r\n"); - dst.extend_from_slice(&buf); - } - - pub fn set_date_simple(&self, dst: &mut BytesMut) { - dst.extend_from_slice(&(unsafe { &*self.date.get() }.bytes)); + pub fn set_date(&self, dst: &mut BytesMut, full: bool) { + if full { + let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; + buf[..6].copy_from_slice(b"date: "); + buf[6..35].copy_from_slice(&(unsafe { &*self.date.get() }.bytes)); + buf[35..].copy_from_slice(b"\r\n\r\n"); + dst.extend_from_slice(&buf); + } else { + dst.extend_from_slice(&(unsafe { &*self.date.get() }.bytes)); + } } } @@ -284,9 +284,9 @@ mod tests { fn test_date() { let settings = WorkerSettings::<()>::new(Vec::new(), KeepAlive::Os); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1); + settings.set_date(&mut buf1, true); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2); + settings.set_date(&mut buf2, true); assert_eq!(buf1, buf2); } } From d1318a35a0ac65529d4483f4bf6d21d06effeb06 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Jun 2018 10:29:23 +0600 Subject: [PATCH 0441/1635] remove unnecessary unsafes --- Cargo.toml | 2 +- src/extensions.rs | 4 ++-- src/ws/frame.rs | 24 ++++++++---------------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19f3ebdd..c7f8c458 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,7 +85,7 @@ flate2 = { version="1.0", optional = true, default-features = false } mio = "^0.6.13" net2 = "0.2" bytes = "0.4" -byteorder = "1" +byteorder = "1.2" futures = "0.1" futures-cpupool = "0.1" slab = "0.4" diff --git a/src/extensions.rs b/src/extensions.rs index 02487325..da7b5ba2 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,7 +1,7 @@ use std::any::{Any, TypeId}; use std::collections::HashMap; -use std::hash::{BuildHasherDefault, Hasher}; use std::fmt; +use std::hash::{BuildHasherDefault, Hasher}; struct IdHasher { id: u64, @@ -16,7 +16,7 @@ impl Default for IdHasher { impl Hasher for IdHasher { fn write(&mut self, bytes: &[u8]) { for &x in bytes { - self.id.wrapping_add(x as u64); + self.id.wrapping_add(u64::from(x)); } } diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 5e88758d..871cc761 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,9 +1,8 @@ -#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] -use byteorder::{ByteOrder, NetworkEndian}; +use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; use bytes::{BufMut, Bytes, BytesMut}; use futures::{Async, Poll, Stream}; use rand; -use std::{fmt, ptr}; +use std::fmt; use body::Binary; use error::PayloadError; @@ -115,8 +114,7 @@ impl Frame { }; let mask: &[u8] = &buf[idx..idx + 4]; - let mask_u32: u32 = - unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) }; + let mask_u32 = LittleEndian::read_u32(mask); idx += 4; Some(mask_u32) } else { @@ -185,8 +183,7 @@ impl Frame { } let mask: &[u8] = &chunk[idx..idx + 4]; - let mask_u32: u32 = - unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) }; + let mask_u32 = LittleEndian::read_u32(mask); idx += 4; Some(mask_u32) } else { @@ -323,15 +320,10 @@ impl Frame { if genmask { let mask = rand::random::(); - unsafe { - { - let buf_mut = buf.bytes_mut(); - *(buf_mut as *mut _ as *mut u32) = mask; - buf_mut[4..payload_len + 4].copy_from_slice(payload.as_ref()); - apply_mask(&mut buf_mut[4..], mask); - } - buf.advance_mut(payload_len + 4); - } + buf.put_u32_le(mask); + buf.extend_from_slice(payload.as_ref()); + let pos = buf.len() - payload_len; + apply_mask(&mut buf[pos..], mask); buf.into() } else { buf.put_slice(payload.as_ref()); From ff0ab733e4cf2a463c09f60bcc87cdbae00ade8e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Jun 2018 11:51:02 +0600 Subject: [PATCH 0442/1635] remove unsafe from mask --- src/server/mod.rs | 6 +++--- src/server/settings.rs | 18 ++++++++++-------- src/ws/mask.rs | 22 ++++++++++------------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 6ecc75d1..bffdf427 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -219,10 +219,10 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn read_available(&mut self, buf: &mut BytesMut) -> Poll { let mut read_some = false; loop { + if buf.remaining_mut() < LW_BUFFER_SIZE { + buf.reserve(HW_BUFFER_SIZE); + } unsafe { - if buf.remaining_mut() < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE); - } match self.read(buf.bytes_mut()) { Ok(n) => { if n == 0 { diff --git a/src/server/settings.rs b/src/server/settings.rs index cf58e432..ca5acb91 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -230,14 +230,16 @@ impl WorkerSettings { } pub fn set_date(&self, dst: &mut BytesMut, full: bool) { - if full { - let mut buf: [u8; 39] = unsafe { mem::uninitialized() }; - buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(&(unsafe { &*self.date.get() }.bytes)); - buf[35..].copy_from_slice(b"\r\n\r\n"); - dst.extend_from_slice(&buf); - } else { - dst.extend_from_slice(&(unsafe { &*self.date.get() }.bytes)); + unsafe { + if full { + let mut buf: [u8; 39] = mem::uninitialized(); + buf[..6].copy_from_slice(b"date: "); + buf[6..35].copy_from_slice(&(*self.date.get()).bytes); + buf[35..].copy_from_slice(b"\r\n\r\n"); + dst.extend_from_slice(&buf); + } else { + dst.extend_from_slice(&(*self.date.get()).bytes); + } } } } diff --git a/src/ws/mask.rs b/src/ws/mask.rs index e99b950c..d5d5ee92 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -10,15 +10,6 @@ pub fn apply_mask(buf: &mut [u8], mask: u32) { unsafe { apply_mask_fast32(buf, mask) } } -/// A safe unoptimized mask application. -#[inline] -#[allow(dead_code)] -fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { - for (i, byte) in buf.iter_mut().enumerate() { - *byte ^= mask[i & 3]; - } -} - /// Faster version of `apply_mask()` which operates on 8-byte blocks. /// /// unsafe because uses pointer math and bit operations for performance @@ -99,13 +90,20 @@ unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) { #[cfg(test)] mod tests { - use super::{apply_mask, apply_mask_fallback}; - use std::ptr; + use super::apply_mask; + use byteorder::{ByteOrder, LittleEndian}; + + /// A safe unoptimized mask application. + fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { + for (i, byte) in buf.iter_mut().enumerate() { + *byte ^= mask[i & 3]; + } + } #[test] fn test_apply_mask() { let mask = [0x6d, 0xb6, 0xb2, 0x80]; - let mask_u32: u32 = unsafe { ptr::read_unaligned(mask.as_ptr() as *const u32) }; + let mask_u32: u32 = LittleEndian::read_u32(&mask); let unmasked = vec![ 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, From a5369aed8b04f8a898ec1e8b0f1b46249c5a1dab Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 23 Jun 2018 08:16:52 +0200 Subject: [PATCH 0443/1635] Changes a leaked box into an Rc and makes resource() return an Option (#343) --- src/httprequest.rs | 10 +++------- src/param.rs | 11 ++++++----- src/router.rs | 27 ++++++--------------------- 3 files changed, 15 insertions(+), 33 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index d511d090..ffd13919 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -94,10 +94,6 @@ impl HttpInnerMessage { } } -lazy_static! { - static ref RESOURCE: Resource = Resource::unset(); -} - /// An HTTP Request pub struct HttpRequest(SharedHttpInnerMessage, Option>, Option); @@ -345,13 +341,13 @@ impl HttpRequest { /// This method returns reference to matched `Resource` object. #[inline] - pub fn resource(&self) -> &Resource { + pub fn resource(&self) -> Option<&Resource> { if let Some(ref router) = self.2 { if let RouterResource::Normal(idx) = self.as_ref().resource { - return router.get_resource(idx as usize); + return Some(router.get_resource(idx as usize)); } } - &*RESOURCE + None } pub(crate) fn set_resource(&mut self, res: usize) { diff --git a/src/param.rs b/src/param.rs index 1003c642..76262c2a 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,4 +1,5 @@ use std; +use std::rc::Rc; use std::ops::Index; use std::path::PathBuf; use std::str::FromStr; @@ -32,7 +33,7 @@ pub(crate) enum ParamItem { pub struct Params { url: Url, pub(crate) tail: u16, - segments: SmallVec<[(&'static str, ParamItem); 3]>, + segments: SmallVec<[(Rc, ParamItem); 3]>, } impl Params { @@ -56,12 +57,12 @@ impl Params { self.tail = tail; } - pub(crate) fn add(&mut self, name: &'static str, value: ParamItem) { + pub(crate) fn add(&mut self, name: Rc, value: ParamItem) { self.segments.push((name, value)); } - pub(crate) fn add_static(&mut self, name: &'static str, value: &'static str) { - self.segments.push((name, ParamItem::Static(value))); + pub(crate) fn add_static(&mut self, name: &str, value: &'static str) { + self.segments.push((Rc::new(name.to_string()), ParamItem::Static(value))); } /// Check if there are any matched patterns @@ -77,7 +78,7 @@ impl Params { /// Get matched parameter by name without type conversion pub fn get(&self, key: &str) -> Option<&str> { for item in self.segments.iter() { - if key == item.0 { + if key == item.0.as_str() { return match item.1 { ParamItem::Static(ref s) => Some(&s), ParamItem::UrlSegment(s, e) => { diff --git a/src/router.rs b/src/router.rs index e37e46b4..fcbb0d44 100644 --- a/src/router.rs +++ b/src/router.rs @@ -143,7 +143,7 @@ enum PatternElement { enum PatternType { Static(String), Prefix(String), - Dynamic(Regex, Vec<&'static str>, usize), + Dynamic(Regex, Vec>, usize), } #[derive(Debug, Copy, Clone, PartialEq)] @@ -195,17 +195,6 @@ impl Resource { resource } - /// Unset resource type - pub(crate) fn unset() -> Resource { - Resource { - tp: PatternType::Static("".to_owned()), - rtp: ResourceType::Unset, - name: "".to_owned(), - pattern: "".to_owned(), - elements: Vec::new(), - } - } - /// Parse path pattern and create new `Resource` instance with custom prefix pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self { let (pattern, elements, is_dynamic, len) = @@ -220,11 +209,7 @@ impl Resource { let names = re .capture_names() .filter_map(|name| { - name.map(|name| { - let s: &'static str = - Box::leak(name.to_owned().into_boxed_str()); - s - }) + name.map(|name| Rc::new(name.to_owned())) }) .collect(); PatternType::Dynamic(re, names, len) @@ -315,7 +300,7 @@ impl Resource { let params = req.match_info_mut(); params.set_tail(len as u16); for (idx, segment) in segments.into_iter().enumerate() { - params.add(names[idx], segment); + params.add(names[idx].clone(), segment); } true } @@ -381,7 +366,7 @@ impl Resource { let params = req.match_info_mut(); params.set_tail(tail_len as u16); for (idx, segment) in segments.into_iter().enumerate() { - params.add(names[idx], segment); + params.add(names[idx].clone(), segment); } Some(tail_len) } @@ -774,13 +759,13 @@ mod tests { let mut req = TestRequest::with_uri("/index.json").finish_with_router(router.clone()); assert_eq!(router.recognize(&mut req), Some(0)); - let resource = req.resource(); + let resource = req.resource().unwrap(); assert_eq!(resource.name(), "r1"); let mut req = TestRequest::with_uri("/test.json").finish_with_router(router.clone()); assert_eq!(router.recognize(&mut req), Some(1)); - let resource = req.resource(); + let resource = req.resource().unwrap(); assert_eq!(resource.name(), "r2"); } } From e3dc6f0ca80d91038e7089bd78f447ba11d2982e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Jun 2018 12:28:55 +0600 Subject: [PATCH 0444/1635] refactor h1decoder --- src/client/parser.rs | 37 +++++++++++++++-------------- src/server/h1decoder.rs | 52 ++++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index 4668f58a..f5390cc3 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -1,13 +1,14 @@ +use std::mem; + use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http::header::{self, HeaderName, HeaderValue}; -use http::{HeaderMap, HttpTryFrom, StatusCode, Version}; +use http::{HeaderMap, StatusCode, Version}; use httparse; -use std::mem; use error::{ParseError, PayloadError}; -use server::h1decoder::EncodingDecoder; +use server::h1decoder::{EncodingDecoder, HeaderIndex}; use server::IoStream; use super::response::ClientMessage; @@ -117,24 +118,23 @@ impl HttpResponseParser { fn parse_message( buf: &mut BytesMut, ) -> Poll<(ClientResponse, Option), ParseError> { - // Parse http message - let bytes_ptr = buf.as_ref().as_ptr() as usize; - let mut headers: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; + // Unsafe: we read only this data only after httparse parses headers into. + // performance bump for pipeline benchmarks. + let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; let (len, version, status, headers_len) = { - let b = unsafe { - let b: &[u8] = buf; - &*(b as *const _) - }; - let mut resp = httparse::Response::new(&mut headers); - match resp.parse(b)? { + let mut parsed: [httparse::Header; MAX_HEADERS] = + unsafe { mem::uninitialized() }; + + let mut resp = httparse::Response::new(&mut parsed); + match resp.parse(buf)? { httparse::Status::Complete(len) => { let version = if resp.version.unwrap_or(1) == 1 { Version::HTTP_11 } else { Version::HTTP_10 }; + HeaderIndex::record(buf, resp.headers, &mut headers); let status = StatusCode::from_u16(resp.code.unwrap()) .map_err(|_| ParseError::Status)?; @@ -148,12 +148,13 @@ impl HttpResponseParser { // convert headers let mut hdrs = HeaderMap::new(); - 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(); + for idx in headers[..headers_len].iter() { + if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) { + // Unsafe: httparse check header value for valid utf-8 let value = unsafe { - HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end)) + HeaderValue::from_shared_unchecked( + slice.slice(idx.value.0, idx.value.1), + ) }; hdrs.append(name, value); } else { diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 77da36af..9815b936 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -91,17 +91,17 @@ impl H1Decoder { let mut content_length = None; let msg = { - let bytes_ptr = buf.as_ref().as_ptr() as usize; - let mut headers: [httparse::Header; MAX_HEADERS] = + // Unsafe: we read only this data only after httparse parses headers into. + // performance bump for pipeline benchmarks. + let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; let (len, method, path, version, headers_len) = { - let b = unsafe { - let b: &[u8] = buf; - &*(b as *const [u8]) - }; - let mut req = httparse::Request::new(&mut headers); - match req.parse(b)? { + let mut parsed: [httparse::Header; MAX_HEADERS] = + unsafe { mem::uninitialized() }; + + let mut req = httparse::Request::new(&mut parsed); + match req.parse(buf)? { httparse::Status::Complete(len) => { let method = Method::from_bytes(req.method.unwrap().as_bytes()) .map_err(|_| ParseError::Method)?; @@ -111,6 +111,8 @@ impl H1Decoder { } else { Version::HTTP_10 }; + HeaderIndex::record(buf, req.headers, &mut headers); + (len, method, path, version, req.headers.len()) } httparse::Status::Partial => return Ok(Async::NotReady), @@ -127,15 +129,15 @@ impl H1Decoder { .flags .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); - for header in headers[..headers_len].iter() { - if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) { + for idx in headers[..headers_len].iter() { + if let Ok(name) = + HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) + { has_upgrade = has_upgrade || name == header::UPGRADE; - - let v_start = header.value.as_ptr() as usize - bytes_ptr; - let v_end = v_start + header.value.len(); + // Unsafe: httparse check header value for valid utf-8 let value = unsafe { HeaderValue::from_shared_unchecked( - slice.slice(v_start, v_end), + slice.slice(idx.value.0, idx.value.1), ) }; match name { @@ -211,6 +213,28 @@ impl H1Decoder { } } +#[derive(Clone, Copy)] +pub(crate) struct HeaderIndex { + pub(crate) name: (usize, usize), + pub(crate) value: (usize, usize), +} + +impl HeaderIndex { + pub(crate) fn record( + bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndex], + ) { + 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); + } + } +} + /// Decoders to handle different Transfer-Encodings. /// /// If a message body does not include a Transfer-Encoding, it *should* From cf38183dcb345942bdef6b0cd5e403a99c7de5d6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Jun 2018 12:40:21 +0600 Subject: [PATCH 0445/1635] refactor client connector waiters maintenance --- src/client/connector.rs | 237 ++++++++++++++++++++-------------------- 1 file changed, 120 insertions(+), 117 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 58b6331d..f8c7ba7c 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -207,7 +207,7 @@ pub struct ClientConnector { acquired_per_host: HashMap, available: HashMap>, to_close: Vec, - waiters: HashMap>, + waiters: Option>>, wait_timeout: Option<(Instant, Delay)>, paused: Paused, } @@ -255,7 +255,7 @@ impl Default for ClientConnector { acquired_per_host: HashMap::new(), available: HashMap::new(), to_close: Vec::new(), - waiters: HashMap::new(), + waiters: Some(HashMap::new()), wait_timeout: None, paused: Paused::No, } @@ -278,7 +278,7 @@ impl Default for ClientConnector { acquired_per_host: HashMap::new(), available: HashMap::new(), to_close: Vec::new(), - waiters: HashMap::new(), + waiters: Some(HashMap::new()), wait_timeout: None, paused: Paused::No, } @@ -344,7 +344,7 @@ impl ClientConnector { acquired_per_host: HashMap::new(), available: HashMap::new(), to_close: Vec::new(), - waiters: HashMap::new(), + waiters: Some(HashMap::new()), wait_timeout: None, paused: Paused::No, } @@ -504,7 +504,7 @@ impl ClientConnector { let now = Instant::now(); let mut next = None; - for waiters in self.waiters.values_mut() { + for waiters in self.waiters.as_mut().unwrap().values_mut() { let mut idx = 0; while idx < waiters.len() { if waiters[idx].wait <= now { @@ -556,6 +556,8 @@ impl ClientConnector { conn_timeout, }; self.waiters + .as_mut() + .unwrap() .entry(key) .or_insert_with(VecDeque::new) .push_back(waiter); @@ -835,9 +837,9 @@ impl fut::ActorFuture for Maintenance { act.collect_waiters(); // check waiters - let tmp: &mut ClientConnector = unsafe { &mut *(act as *mut _) }; + let mut waiters = act.waiters.take().unwrap(); - for (key, waiters) in &mut tmp.waiters { + for (key, waiters) in &mut waiters { while let Some(waiter) = waiters.pop_front() { if waiter.tx.is_canceled() { continue; @@ -865,118 +867,118 @@ impl fut::ActorFuture for Maintenance { ), ).map_err(|_, _, _| ()) .and_then(move |res, act, _| { - #[cfg_attr(rustfmt, rustfmt_skip)] - #[cfg(feature = "alpn")] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&key.host, stream) - .then(move |res| { - match res { - Err(e) => { - let _ = waiter.tx.send( - Err(ClientConnectorError::SslError(e))); - } - Ok(stream) => { - let _ = waiter.tx.send( - Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - )), - ); - } - } - Ok(()) - }) - .actfuture(), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } + #[cfg(feature = "alpn")] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + fut::Either::A( + act.connector + .connect_async(&key.host, stream) + .then(move |res| { + match res { + Err(e) => { + let _ = waiter.tx.send( + Err(ClientConnectorError::SslError(e))); + } + Ok(stream) => { + let _ = waiter.tx.send( + Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + )), + ); + } + } + Ok(()) + }) + .actfuture(), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) + } + } + } - #[cfg_attr(rustfmt, rustfmt_skip)] - #[cfg(all(feature = "tls", not(feature = "alpn")))] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&conn.0.host, stream) - .then(|res| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send( - Ok(Connection::new( - conn.0.clone(), Some(conn), - Box::new(stream), - )), - ); - } - } - Ok(()) - }) - .into_actor(act), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } + #[cfg(all(feature = "tls", not(feature = "alpn")))] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + fut::Either::A( + act.connector + .connect_async(&conn.0.host, stream) + .then(|res| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e), + )); + } + Ok(stream) => { + let _ = waiter.tx.send( + Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + )), + ); + } + } + Ok(()) + }) + .into_actor(act), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) + } + } + } - #[cfg_attr(rustfmt, rustfmt_skip)] - #[cfg(not(any(feature = "alpn", feature = "tls")))] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::err(()) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let _ = waiter.tx.send(Err(ClientConnectorError::SslIsNotSupported)); - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - }; - fut::ok(()) - } - } + #[cfg(not(any(feature = "alpn", feature = "tls")))] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::err(()) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslIsNotSupported, + )); + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + }; + fut::ok(()) + } + } }) .spawn(ctx); } @@ -984,6 +986,7 @@ impl fut::ActorFuture for Maintenance { } } + act.waiters = Some(waiters); Ok(Async::NotReady) } } From 348491b18c9a2f0920595a27732537f248e924e2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Jun 2018 17:59:45 +0600 Subject: [PATCH 0446/1635] fix alpn connector --- src/client/connector.rs | 11 ++++++----- src/pipeline.rs | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index f8c7ba7c..6c32d898 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -837,9 +837,9 @@ impl fut::ActorFuture for Maintenance { act.collect_waiters(); // check waiters - let mut waiters = act.waiters.take().unwrap(); + let mut act_waiters = act.waiters.take().unwrap(); - for (key, waiters) in &mut waiters { + for (key, ref mut waiters) in &mut act_waiters { while let Some(waiter) = waiters.pop_front() { if waiter.tx.is_canceled() { continue; @@ -858,10 +858,11 @@ impl fut::ActorFuture for Maintenance { break; } Acquire::Available => { + let key = key.clone(); let conn = AcquiredConn(key.clone(), Some(act.acq_tx.clone())); fut::WrapFuture::::actfuture( - Resolver::from_registry().send( + act.resolver.as_ref().unwrap().send( ResolveConnect::host_and_port(&conn.0.host, conn.0.port) .timeout(waiter.conn_timeout), ), @@ -898,7 +899,7 @@ impl fut::ActorFuture for Maintenance { } Ok(()) }) - .actfuture(), + .into_actor(act), ) } else { let _ = waiter.tx.send(Ok(Connection::new( @@ -986,7 +987,7 @@ impl fut::ActorFuture for Maintenance { } } - act.waiters = Some(waiters); + act.waiters = Some(act_waiters); Ok(Async::NotReady) } } diff --git a/src/pipeline.rs b/src/pipeline.rs index 45843682..87400e29 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -841,7 +841,7 @@ mod tests { Pipeline(info, PipelineState::Completed(state), Rc::new(Vec::new())); assert!(!pp.is_done()); - let Pipeline(mut info, st, mws) = pp; + let Pipeline(mut info, st, _) = pp; let mut st = st.completed().unwrap(); drop(addr); From 45682c04a873266aa732b72ce3b11dc58bd9bb55 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Jun 2018 08:54:01 +0600 Subject: [PATCH 0447/1635] refactor content encoder --- src/client/writer.rs | 115 +++++++++--------- src/fs.rs | 1 + src/server/encoding.rs | 261 ++++++++++++++++++++++++++++++----------- src/server/h1writer.rs | 34 +++--- src/server/h2writer.rs | 19 ++- src/server/shared.rs | 11 +- 6 files changed, 278 insertions(+), 163 deletions(-) diff --git a/src/client/writer.rs b/src/client/writer.rs index f9961a79..bf626513 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -21,7 +21,8 @@ use tokio_io::AsyncWrite; use body::{Binary, Body}; use header::ContentEncoding; -use server::encoding::{ContentEncoder, TransferEncoding}; +use server::encoding::{ContentEncoder, Output, TransferEncoding}; +use server::shared::SharedBytes; use server::WriterState; use client::ClientRequest; @@ -41,21 +42,18 @@ pub(crate) struct HttpClientWriter { flags: Flags, written: u64, headers_size: u32, - buffer: Box, + buffer: Output, buffer_capacity: usize, - encoder: ContentEncoder, } impl HttpClientWriter { pub fn new() -> HttpClientWriter { - let encoder = ContentEncoder::Identity(TransferEncoding::eof()); HttpClientWriter { flags: Flags::empty(), written: 0, headers_size: 0, buffer_capacity: 0, - buffer: Box::new(BytesMut::new()), - encoder, + buffer: Output::Buffer(SharedBytes::empty()), } } @@ -75,7 +73,7 @@ impl HttpClientWriter { &mut self, stream: &mut T, ) -> io::Result { while !self.buffer.is_empty() { - match stream.write(self.buffer.as_ref()) { + match stream.write(self.buffer.as_ref().as_ref()) { Ok(0) => { self.disconnected(); return Ok(WriterState::Done); @@ -113,16 +111,18 @@ impl HttpClientWriter { pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> { // prepare task self.flags.insert(Flags::STARTED); - self.encoder = content_encoder(self.buffer.as_mut(), msg); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); } // render message { + // output buffer + let buffer = self.buffer.get_mut(); + // status line writeln!( - Writer(&mut self.buffer), + Writer(buffer), "{} {} {:?}\r", msg.method(), msg.uri() @@ -134,41 +134,41 @@ impl HttpClientWriter { // write headers if let Body::Binary(ref bytes) = *msg.body() { - self.buffer - .reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); + buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); } else { - self.buffer - .reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); + buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); } for (key, value) in msg.headers() { let v = value.as_ref(); let k = key.as_str().as_bytes(); - self.buffer.reserve(k.len() + v.len() + 4); - self.buffer.put_slice(k); - self.buffer.put_slice(b": "); - self.buffer.put_slice(v); - self.buffer.put_slice(b"\r\n"); + 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"); } // set date header if !msg.headers().contains_key(DATE) { - self.buffer.extend_from_slice(b"date: "); - set_date(&mut self.buffer); - self.buffer.extend_from_slice(b"\r\n\r\n"); + buffer.extend_from_slice(b"date: "); + set_date(buffer); + buffer.extend_from_slice(b"\r\n\r\n"); } else { - self.buffer.extend_from_slice(b"\r\n"); + buffer.extend_from_slice(b"\r\n"); } - self.headers_size = self.buffer.len() as u32; + } + self.headers_size = self.buffer.len() as u32; - if msg.body().is_binary() { - if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { - self.written += bytes.len() as u64; - self.encoder.write(bytes.as_ref())?; - } - } else { - self.buffer_capacity = msg.write_buffer_capacity(); + self.buffer = content_encoder(self.buffer.take(), msg); + + if msg.body().is_binary() { + if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { + self.written += bytes.len() as u64; + self.buffer.write(bytes.as_ref())?; } + } else { + self.buffer_capacity = msg.write_buffer_capacity(); } Ok(()) } @@ -176,11 +176,7 @@ impl HttpClientWriter { pub fn write(&mut self, payload: &[u8]) -> io::Result { self.written += payload.len() as u64; if !self.flags.contains(Flags::DISCONNECTED) { - if self.flags.contains(Flags::UPGRADE) { - self.buffer.extend(payload); - } else { - self.encoder.write(payload)?; - } + self.buffer.write(payload)?; } if self.buffer.len() > self.buffer_capacity { @@ -191,9 +187,7 @@ impl HttpClientWriter { } pub fn write_eof(&mut self) -> io::Result<()> { - self.encoder.write_eof()?; - - if self.encoder.is_eof() { + if self.buffer.write_eof()? { Ok(()) } else { Err(io::Error::new( @@ -221,21 +215,20 @@ impl HttpClientWriter { } } -fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncoder { +fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> Output { let version = req.version(); let mut body = req.replace_body(Body::Empty); let mut encoding = req.content_encoding(); - let mut transfer = match body { + let transfer = match body { Body::Empty => { req.headers_mut().remove(CONTENT_LENGTH); - TransferEncoding::length(0) + TransferEncoding::length(0, buf) } Body::Binary(ref mut bytes) => { if encoding.is_compression() { - let mut tmp = BytesMut::new(); - let mut transfer = TransferEncoding::eof(); - transfer.set_buffer(&mut tmp); + let mut tmp = SharedBytes::empty(); + let mut transfer = TransferEncoding::eof(tmp); let mut enc = match encoding { #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate( @@ -256,7 +249,7 @@ fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncode // TODO return error! let _ = enc.write(bytes.as_ref()); let _ = enc.write_eof(); - *bytes = Binary::from(tmp.take()); + *bytes = Binary::from(enc.buf_mut().take()); req.headers_mut().insert( CONTENT_ENCODING, @@ -268,7 +261,7 @@ fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncode let _ = write!(b, "{}", bytes.len()); req.headers_mut() .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof() + TransferEncoding::eof(buf) } Body::Streaming(_) | Body::Actor(_) => { if req.upgrade() { @@ -282,9 +275,9 @@ fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncode encoding = ContentEncoding::Identity; req.headers_mut().remove(CONTENT_ENCODING); } - TransferEncoding::eof() + TransferEncoding::eof(buf) } else { - streaming_encoding(version, req) + streaming_encoding(buf, version, req) } } }; @@ -295,10 +288,9 @@ fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncode HeaderValue::from_static(encoding.as_str()), ); } - transfer.set_buffer(buf); req.replace_body(body); - match encoding { + let enc = match encoding { #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( transfer, @@ -310,23 +302,24 @@ fn content_encoder(buf: &mut BytesMut, req: &mut ClientRequest) -> ContentEncode } #[cfg(feature = "brotli")] ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)), - ContentEncoding::Identity | ContentEncoding::Auto => { - ContentEncoder::Identity(transfer) - } - } + ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer), + }; + Output::Encoder(enc) } -fn streaming_encoding(version: Version, req: &mut ClientRequest) -> TransferEncoding { +fn streaming_encoding( + buf: SharedBytes, version: Version, req: &mut ClientRequest, +) -> TransferEncoding { if req.chunked() { // Enable transfer encoding req.headers_mut().remove(CONTENT_LENGTH); if version == Version::HTTP_2 { req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof() + TransferEncoding::eof(buf) } else { req.headers_mut() .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked() + TransferEncoding::chunked(buf) } } else { // if Content-Length is specified, then use it as length hint @@ -349,9 +342,9 @@ fn streaming_encoding(version: Version, req: &mut ClientRequest) -> TransferEnco if !chunked { if let Some(len) = len { - TransferEncoding::length(len) + TransferEncoding::length(len, buf) } else { - TransferEncoding::eof() + TransferEncoding::eof(buf) } } else { // Enable transfer encoding @@ -359,11 +352,11 @@ fn streaming_encoding(version: Version, req: &mut ClientRequest) -> TransferEnco Version::HTTP_11 => { req.headers_mut() .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked() + TransferEncoding::chunked(buf) } _ => { req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof() + TransferEncoding::eof(buf) } } } diff --git a/src/fs.rs b/src/fs.rs index c5a7de61..bf9079cc 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1127,6 +1127,7 @@ mod tests { let response = srv.execute(request.send()).unwrap(); + println!("RESP: {:?}", response); let te = response .headers() .get(header::TRANSFER_ENCODING) diff --git a/src/server/encoding.rs b/src/server/encoding.rs index db0fd0c3..e7dc7a1a 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -1,7 +1,7 @@ use std::fmt::Write as FmtWrite; use std::io::{Read, Write}; use std::str::FromStr; -use std::{cmp, io, mem, ptr}; +use std::{cmp, io, mem}; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; @@ -25,6 +25,8 @@ use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use payload::{PayloadSender, PayloadStatus, PayloadWriter}; +use super::shared::SharedBytes; + pub(crate) enum PayloadType { Sender(PayloadSender), Encoding(Box), @@ -368,6 +370,83 @@ impl PayloadStream { } } +pub(crate) enum Output { + Buffer(SharedBytes), + Encoder(ContentEncoder), + TE(TransferEncoding), + Empty, +} + +impl Output { + pub fn take(&mut self) -> SharedBytes { + match mem::replace(self, Output::Empty) { + Output::Buffer(bytes) => bytes, + _ => panic!(), + } + } + pub fn as_ref(&mut self) -> &SharedBytes { + match self { + Output::Buffer(ref mut bytes) => bytes, + Output::Encoder(ref mut enc) => enc.buf_ref(), + Output::TE(ref mut te) => te.buf_ref(), + Output::Empty => panic!(), + } + } + pub fn get_mut(&mut self) -> &mut BytesMut { + match self { + Output::Buffer(ref mut bytes) => bytes.get_mut(), + _ => panic!(), + } + } + pub fn split_to(&mut self, cap: usize) -> BytesMut { + match self { + Output::Buffer(ref mut bytes) => bytes.split_to(cap), + Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap), + Output::TE(ref mut te) => te.buf_mut().split_to(cap), + Output::Empty => BytesMut::new(), + } + } + + pub fn len(&self) -> usize { + match self { + Output::Buffer(ref bytes) => bytes.len(), + Output::Encoder(ref enc) => enc.len(), + Output::TE(ref te) => te.len(), + Output::Empty => 0, + } + } + + pub fn is_empty(&self) -> bool { + match self { + Output::Buffer(ref bytes) => bytes.is_empty(), + Output::Encoder(ref enc) => enc.is_empty(), + Output::TE(ref te) => te.is_empty(), + Output::Empty => true, + } + } + + pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { + match self { + Output::Buffer(ref mut bytes) => { + bytes.extend_from_slice(data); + Ok(()) + } + Output::Encoder(ref mut enc) => enc.write(data), + Output::TE(ref mut te) => te.encode(data).map(|_| ()), + Output::Empty => Ok(()), + } + } + + pub fn write_eof(&mut self) -> Result { + match self { + Output::Buffer(_) => Ok(true), + Output::Encoder(ref mut enc) => enc.write_eof(), + Output::TE(ref mut te) => Ok(te.encode_eof()), + Output::Empty => Ok(true), + } + } +} + pub(crate) enum ContentEncoder { #[cfg(feature = "flate2")] Deflate(DeflateEncoder), @@ -379,14 +458,10 @@ pub(crate) enum ContentEncoder { } impl ContentEncoder { - pub fn empty() -> ContentEncoder { - ContentEncoder::Identity(TransferEncoding::eof()) - } - pub fn for_server( - buf: &mut BytesMut, req: &HttpInnerMessage, resp: &mut HttpResponse, + buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse, response_encoding: ContentEncoding, - ) -> ContentEncoder { + ) -> Output { let version = resp.version().unwrap_or_else(|| req.version); let is_head = req.method == Method::HEAD; let mut len = 0; @@ -439,7 +514,7 @@ impl ContentEncoder { if req.method != Method::HEAD { resp.headers_mut().remove(CONTENT_LENGTH); } - TransferEncoding::length(0) + TransferEncoding::length(0, buf) } &Body::Binary(_) => { #[cfg(any(feature = "brotli", feature = "flate2"))] @@ -447,9 +522,8 @@ impl ContentEncoder { if !(encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto) { - let mut tmp = BytesMut::default(); - let mut transfer = TransferEncoding::eof(); - transfer.set_buffer(&mut tmp); + let mut tmp = SharedBytes::empty(); + let mut transfer = TransferEncoding::eof(tmp); let mut enc = match encoding { #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate( @@ -473,7 +547,7 @@ impl ContentEncoder { // TODO return error! let _ = enc.write(bin.as_ref()); let _ = enc.write_eof(); - let body = tmp.take(); + let body = enc.buf_mut().take(); len = body.len(); encoding = ContentEncoding::Identity; @@ -491,7 +565,7 @@ impl ContentEncoder { } else { // resp.headers_mut().remove(CONTENT_LENGTH); } - TransferEncoding::eof() + TransferEncoding::eof(buf) } &Body::Streaming(_) | &Body::Actor(_) => { if resp.upgrade() { @@ -502,14 +576,14 @@ impl ContentEncoder { encoding = ContentEncoding::Identity; resp.headers_mut().remove(CONTENT_ENCODING); } - TransferEncoding::eof() + TransferEncoding::eof(buf) } else { if !(encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto) { resp.headers_mut().remove(CONTENT_LENGTH); } - ContentEncoder::streaming_encoding(version, resp) + ContentEncoder::streaming_encoding(buf, version, resp) } } }; @@ -518,9 +592,8 @@ impl ContentEncoder { resp.set_body(Body::Empty); transfer.kind = TransferEncodingKind::Length(0); } - transfer.set_buffer(buf); - match encoding { + let enc = match encoding { #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( transfer, @@ -533,13 +606,14 @@ impl ContentEncoder { #[cfg(feature = "brotli")] ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)), ContentEncoding::Identity | ContentEncoding::Auto => { - ContentEncoder::Identity(transfer) + return Output::TE(transfer) } - } + }; + Output::Encoder(enc) } fn streaming_encoding( - version: Version, resp: &mut HttpResponse, + buf: SharedBytes, version: Version, resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { @@ -547,14 +621,14 @@ impl ContentEncoder { resp.headers_mut().remove(CONTENT_LENGTH); 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) } } - Some(false) => TransferEncoding::eof(), + Some(false) => TransferEncoding::eof(buf), None => { // if Content-Length is specified, then use it as length hint let (len, chunked) = @@ -577,9 +651,9 @@ impl ContentEncoder { if !chunked { if let Some(len) = len { - TransferEncoding::length(len) + TransferEncoding::length(len, buf) } else { - TransferEncoding::eof() + TransferEncoding::eof(buf) } } else { // Enable transfer encoding @@ -589,11 +663,11 @@ impl ContentEncoder { TRANSFER_ENCODING, HeaderValue::from_static("chunked"), ); - TransferEncoding::chunked() + TransferEncoding::chunked(buf) } _ => { resp.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof() + TransferEncoding::eof(buf) } } } @@ -604,15 +678,54 @@ impl ContentEncoder { impl ContentEncoder { #[inline] - pub fn is_eof(&self) -> bool { + pub fn len(&self) -> usize { match *self { #[cfg(feature = "brotli")] - ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(), + ContentEncoder::Br(ref encoder) => encoder.get_ref().len(), #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(), + ContentEncoder::Deflate(ref encoder) => encoder.get_ref().len(), #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(), - ContentEncoder::Identity(ref encoder) => encoder.is_eof(), + ContentEncoder::Gzip(ref encoder) => encoder.get_ref().len(), + ContentEncoder::Identity(ref encoder) => encoder.len(), + } + } + + #[inline] + pub fn is_empty(&self) -> bool { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(ref encoder) => encoder.get_ref().is_empty(), + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_empty(), + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_empty(), + ContentEncoder::Identity(ref encoder) => encoder.is_empty(), + } + } + + #[inline] + pub(crate) fn buf_mut(&mut self) -> &mut BytesMut { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_mut(), + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_mut(), + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_mut(), + ContentEncoder::Identity(ref mut encoder) => encoder.buf_mut(), + } + } + + #[inline] + pub(crate) fn buf_ref(&mut self) -> &SharedBytes { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(), + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_ref(), + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_ref(), + ContentEncoder::Identity(ref mut encoder) => encoder.buf_ref(), } } @@ -620,7 +733,7 @@ impl ContentEncoder { #[inline(always)] pub fn write_eof(&mut self) -> Result { let encoder = - mem::replace(self, ContentEncoder::Identity(TransferEncoding::eof())); + mem::replace(self, ContentEncoder::Identity(TransferEncoding::empty())); match encoder { #[cfg(feature = "brotli")] @@ -695,9 +808,9 @@ impl ContentEncoder { } /// Encoders to handle different Transfer-Encodings. -#[derive(Debug, Clone)] +#[derive(Debug)] pub(crate) struct TransferEncoding { - buf: *mut BytesMut, + buf: Option, kind: TransferEncodingKind, } @@ -716,40 +829,55 @@ enum TransferEncodingKind { } impl TransferEncoding { - pub(crate) fn set_buffer(&mut self, buf: *mut BytesMut) { - self.buf = buf; + fn take(self) -> SharedBytes { + self.buf.unwrap() + } + + fn buf_ref(&mut self) -> &SharedBytes { + self.buf.as_ref().unwrap() + } + + fn len(&self) -> usize { + self.buf.as_ref().unwrap().len() + } + + fn is_empty(&self) -> bool { + self.buf.as_ref().unwrap().is_empty() + } + + fn buf_mut(&mut self) -> &mut BytesMut { + self.buf.as_mut().unwrap().get_mut() } #[inline] - pub fn eof() -> TransferEncoding { + pub fn empty() -> TransferEncoding { TransferEncoding { + buf: None, kind: TransferEncodingKind::Eof, - buf: ptr::null_mut(), } } #[inline] - pub fn chunked() -> TransferEncoding { + pub fn eof(buf: SharedBytes) -> TransferEncoding { TransferEncoding { + buf: Some(buf), + kind: TransferEncodingKind::Eof, + } + } + + #[inline] + pub fn chunked(buf: SharedBytes) -> TransferEncoding { + TransferEncoding { + buf: Some(buf), kind: TransferEncodingKind::Chunked(false), - buf: ptr::null_mut(), } } #[inline] - pub fn length(len: u64) -> TransferEncoding { + pub fn length(len: u64, buf: SharedBytes) -> TransferEncoding { TransferEncoding { + buf: Some(buf), kind: TransferEncodingKind::Length(len), - buf: ptr::null_mut(), - } - } - - #[inline] - pub fn is_eof(&self) -> bool { - match self.kind { - TransferEncodingKind::Eof => true, - TransferEncodingKind::Chunked(ref eof) => *eof, - TransferEncodingKind::Length(ref remaining) => *remaining == 0, } } @@ -759,9 +887,7 @@ impl TransferEncoding { match self.kind { TransferEncodingKind::Eof => { let eof = msg.is_empty(); - debug_assert!(!self.buf.is_null()); - let buf = unsafe { &mut *self.buf }; - buf.extend(msg); + self.buf.as_mut().unwrap().extend_from_slice(msg); Ok(eof) } TransferEncodingKind::Chunked(ref mut eof) => { @@ -771,16 +897,13 @@ impl TransferEncoding { if msg.is_empty() { *eof = true; - debug_assert!(!self.buf.is_null()); - let buf = unsafe { &mut *self.buf }; - buf.extend_from_slice(b"0\r\n\r\n"); + self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n"); } else { let mut buf = BytesMut::new(); writeln!(&mut buf, "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - debug_assert!(!self.buf.is_null()); - let b = unsafe { &mut *self.buf }; + let b = self.buf.as_mut().unwrap(); b.reserve(buf.len() + msg.len() + 2); b.extend_from_slice(buf.as_ref()); b.extend_from_slice(msg); @@ -795,8 +918,10 @@ impl TransferEncoding { } let len = cmp::min(*remaining, msg.len() as u64); - debug_assert!(!self.buf.is_null()); - unsafe { &mut *self.buf }.extend(&msg[..len as usize]); + self.buf + .as_mut() + .unwrap() + .extend_from_slice(&msg[..len as usize]); *remaining -= len as u64; Ok(*remaining == 0) @@ -816,10 +941,7 @@ impl TransferEncoding { TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; - - debug_assert!(!self.buf.is_null()); - let buf = unsafe { &mut *self.buf }; - buf.extend_from_slice(b"0\r\n\r\n"); + self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n"); } true } @@ -912,15 +1034,14 @@ mod tests { #[test] fn test_chunked_te() { - let mut bytes = BytesMut::new(); - let mut enc = TransferEncoding::chunked(); + let bytes = SharedBytes::empty(); + let mut enc = TransferEncoding::chunked(bytes); { - enc.set_buffer(&mut bytes); assert!(!enc.encode(b"test").ok().unwrap()); assert!(enc.encode(b"").ok().unwrap()); } assert_eq!( - bytes.take().freeze(), + enc.buf_mut().take().freeze(), Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") ); } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index bf91a030..36571d88 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -6,7 +6,7 @@ use std::io; use std::rc::Rc; use tokio_io::AsyncWrite; -use super::encoding::ContentEncoder; +use super::encoding::{ContentEncoder, Output}; use super::helpers; use super::settings::WorkerSettings; use super::shared::SharedBytes; @@ -32,10 +32,9 @@ bitflags! { pub(crate) struct H1Writer { flags: Flags, stream: T, - encoder: ContentEncoder, written: u64, headers_size: u32, - buffer: SharedBytes, + buffer: Output, buffer_capacity: usize, settings: Rc>, } @@ -46,10 +45,9 @@ impl H1Writer { ) -> H1Writer { H1Writer { flags: Flags::KEEPALIVE, - encoder: ContentEncoder::empty(), written: 0, headers_size: 0, - buffer: buf, + buffer: Output::Buffer(buf), buffer_capacity: 0, stream, settings, @@ -66,7 +64,7 @@ impl H1Writer { } pub fn disconnected(&mut self) { - self.buffer.take(); + self.buffer = Output::Empty; } pub fn keepalive(&self) -> bool { @@ -106,7 +104,8 @@ impl Writer for H1Writer { #[inline] fn buffer(&mut self) -> &mut BytesMut { - self.buffer.get_mut() + //self.buffer.get_mut() + unimplemented!() } fn start( @@ -114,8 +113,6 @@ impl Writer for H1Writer { encoding: ContentEncoding, ) -> io::Result { // prepare task - self.encoder = - ContentEncoder::for_server(self.buffer.get_mut(), req, msg, encoding); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { self.flags = Flags::STARTED | Flags::KEEPALIVE; } else { @@ -143,7 +140,9 @@ impl Writer for H1Writer { // render message { + // output buffer let mut buffer = self.buffer.get_mut(); + let reason = msg.reason().as_bytes(); let mut is_bin = if let Body::Binary(ref bytes) = body { buffer.reserve( @@ -222,9 +221,12 @@ impl Writer for H1Writer { self.headers_size = buffer.len() as u32; } + // output encoding + self.buffer = ContentEncoder::for_server(self.buffer.take(), req, msg, encoding); + if let Body::Binary(bytes) = body { self.written = bytes.len() as u64; - self.encoder.write(bytes.as_ref())?; + self.buffer.write(bytes.as_ref())?; } else { // capacity, makes sense only for streaming or actor self.buffer_capacity = msg.write_buffer_capacity(); @@ -253,19 +255,19 @@ impl Writer for H1Writer { Ok(val) => val, }; if n < pl.len() { - self.buffer.extend_from_slice(&pl[n..]); + self.buffer.write(&pl[n..]); return Ok(WriterState::Done); } } else { - self.buffer.extend(payload); + self.buffer.write(payload.as_ref()); } } else { // TODO: add warning, write after EOF - self.encoder.write(payload.as_ref())?; + self.buffer.write(payload.as_ref())?; } } else { // could be response to EXCEPT header - self.buffer.extend_from_slice(payload.as_ref()) + self.buffer.write(payload.as_ref()); } } @@ -277,7 +279,7 @@ impl Writer for H1Writer { } fn write_eof(&mut self) -> io::Result { - if !self.encoder.write_eof()? { + if !self.buffer.write_eof()? { Err(io::Error::new( io::ErrorKind::Other, "Last payload item, but eof is not reached", @@ -293,7 +295,7 @@ impl Writer for H1Writer { fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { if !self.buffer.is_empty() { let written = { - match Self::write_data(&mut self.stream, self.buffer.as_ref()) { + match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) { Err(err) => { if err.kind() == io::ErrorKind::WriteZero { self.disconnected(); diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index c816c12a..e5f579b2 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -11,7 +11,7 @@ use std::{cmp, io}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{HttpTryFrom, Version}; -use super::encoding::ContentEncoder; +use super::encoding::{ContentEncoder, Output}; use super::helpers; use super::settings::WorkerSettings; use super::shared::SharedBytes; @@ -35,10 +35,9 @@ bitflags! { pub(crate) struct H2Writer { respond: SendResponse, stream: Option>, - encoder: ContentEncoder, flags: Flags, written: u64, - buffer: SharedBytes, + buffer: Output, buffer_capacity: usize, settings: Rc>, } @@ -51,10 +50,9 @@ impl H2Writer { respond, settings, stream: None, - encoder: ContentEncoder::empty(), flags: Flags::empty(), written: 0, - buffer: buf, + buffer: Output::Buffer(buf), buffer_capacity: 0, } } @@ -87,8 +85,7 @@ impl Writer for H2Writer { ) -> io::Result { // prepare response self.flags.insert(Flags::STARTED); - self.encoder = - ContentEncoder::for_server(self.buffer.get_mut(), req, msg, encoding); + self.buffer = ContentEncoder::for_server(self.buffer.take(), req, msg, encoding); // http2 specific msg.headers_mut().remove(CONNECTION); @@ -150,7 +147,7 @@ impl Writer for H2Writer { } else { self.flags.insert(Flags::EOF); self.written = bytes.len() as u64; - self.encoder.write(bytes.as_ref())?; + self.buffer.write(bytes.as_ref())?; if let Some(ref mut stream) = self.stream { self.flags.insert(Flags::RESERVED); stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); @@ -170,10 +167,10 @@ impl Writer for H2Writer { if !self.flags.contains(Flags::DISCONNECTED) { if self.flags.contains(Flags::STARTED) { // TODO: add warning, write after EOF - self.encoder.write(payload.as_ref())?; + self.buffer.write(payload.as_ref())?; } else { // might be response for EXCEPT - self.buffer.extend_from_slice(payload.as_ref()) + error!("Not supported"); } } @@ -186,7 +183,7 @@ impl Writer for H2Writer { fn write_eof(&mut self) -> io::Result { self.flags.insert(Flags::EOF); - if !self.encoder.write_eof()? { + if !self.buffer.write_eof()? { Err(io::Error::new( io::ErrorKind::Other, "Last payload item, but eof is not reached", diff --git a/src/server/shared.rs b/src/server/shared.rs index 064130fb..4d6a59d8 100644 --- a/src/server/shared.rs +++ b/src/server/shared.rs @@ -5,8 +5,6 @@ use std::rc::Rc; use bytes::BytesMut; -use body::Binary; - #[derive(Debug)] pub(crate) struct SharedBytesPool(RefCell>); @@ -50,6 +48,10 @@ impl SharedBytes { SharedBytes(Some(bytes), Some(pool)) } + pub fn empty() -> SharedBytes { + SharedBytes(Some(BytesMut::new()), None) + } + #[inline] pub(crate) fn get_mut(&mut self) -> &mut BytesMut { self.0.as_mut().unwrap() @@ -79,9 +81,8 @@ impl SharedBytes { } #[inline] - pub fn extend(&mut self, data: &Binary) { - let buf = self.get_mut(); - buf.extend_from_slice(data.as_ref()); + pub fn reserve(&mut self, cap: usize) { + self.get_mut().reserve(cap); } #[inline] From 40ca9ba9c5a9aebd03c83a33c321b7645a00f1cc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Jun 2018 10:30:58 +0600 Subject: [PATCH 0448/1635] simplify write buffer --- Cargo.toml | 2 + src/client/writer.rs | 14 +++--- src/fs.rs | 1 - src/server/encoding.rs | 82 +++++++++++++++++++++++-------- src/server/h1.rs | 3 +- src/server/h1writer.rs | 30 ++++++------ src/server/h2.rs | 6 +-- src/server/h2writer.rs | 17 ++++--- src/server/mod.rs | 1 - src/server/settings.rs | 35 +++++++++++-- src/server/shared.rs | 109 ----------------------------------------- 11 files changed, 130 insertions(+), 170 deletions(-) delete mode 100644 src/server/shared.rs diff --git a/Cargo.toml b/Cargo.toml index c7f8c458..d4221cbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,8 @@ tokio-tls = { version="0.1", optional = true } openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } +backtrace="*" + [dev-dependencies] env_logger = "0.5" serde_derive = "1.0" diff --git a/src/client/writer.rs b/src/client/writer.rs index bf626513..65328979 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -22,7 +22,6 @@ use tokio_io::AsyncWrite; use body::{Binary, Body}; use header::ContentEncoding; use server::encoding::{ContentEncoder, Output, TransferEncoding}; -use server::shared::SharedBytes; use server::WriterState; use client::ClientRequest; @@ -53,7 +52,7 @@ impl HttpClientWriter { written: 0, headers_size: 0, buffer_capacity: 0, - buffer: Output::Buffer(SharedBytes::empty()), + buffer: Output::Buffer(BytesMut::new()), } } @@ -110,6 +109,7 @@ impl<'a> io::Write for Writer<'a> { impl HttpClientWriter { pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> { // prepare task + self.buffer = content_encoder(self.buffer.take(), msg); self.flags.insert(Flags::STARTED); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); @@ -118,7 +118,7 @@ impl HttpClientWriter { // render message { // output buffer - let buffer = self.buffer.get_mut(); + let buffer = self.buffer.as_mut(); // status line writeln!( @@ -160,8 +160,6 @@ impl HttpClientWriter { } self.headers_size = self.buffer.len() as u32; - self.buffer = content_encoder(self.buffer.take(), msg); - if msg.body().is_binary() { if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { self.written += bytes.len() as u64; @@ -215,7 +213,7 @@ impl HttpClientWriter { } } -fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> Output { +fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { let version = req.version(); let mut body = req.replace_body(Body::Empty); let mut encoding = req.content_encoding(); @@ -227,7 +225,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> Output { } Body::Binary(ref mut bytes) => { if encoding.is_compression() { - let mut tmp = SharedBytes::empty(); + let mut tmp = BytesMut::new(); let mut transfer = TransferEncoding::eof(tmp); let mut enc = match encoding { #[cfg(feature = "flate2")] @@ -308,7 +306,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> Output { } fn streaming_encoding( - buf: SharedBytes, version: Version, req: &mut ClientRequest, + buf: BytesMut, version: Version, req: &mut ClientRequest, ) -> TransferEncoding { if req.chunked() { // Enable transfer encoding diff --git a/src/fs.rs b/src/fs.rs index bf9079cc..c5a7de61 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1127,7 +1127,6 @@ mod tests { let response = srv.execute(request.send()).unwrap(); - println!("RESP: {:?}", response); let te = response .headers() .get(header::TRANSFER_ENCODING) diff --git a/src/server/encoding.rs b/src/server/encoding.rs index e7dc7a1a..5acce762 100644 --- a/src/server/encoding.rs +++ b/src/server/encoding.rs @@ -1,7 +1,7 @@ use std::fmt::Write as FmtWrite; use std::io::{Read, Write}; use std::str::FromStr; -use std::{cmp, io, mem}; +use std::{cmp, fmt, io, mem}; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; @@ -25,8 +25,6 @@ use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; use payload::{PayloadSender, PayloadStatus, PayloadWriter}; -use super::shared::SharedBytes; - pub(crate) enum PayloadType { Sender(PayloadSender), Encoding(Box), @@ -370,21 +368,34 @@ impl PayloadStream { } } +#[derive(Debug)] pub(crate) enum Output { - Buffer(SharedBytes), + Buffer(BytesMut), Encoder(ContentEncoder), TE(TransferEncoding), Empty, } impl Output { - pub fn take(&mut self) -> SharedBytes { + pub fn take(&mut self) -> BytesMut { match mem::replace(self, Output::Empty) { Output::Buffer(bytes) => bytes, + Output::Encoder(mut enc) => enc.take_buf(), + Output::TE(mut te) => te.take(), _ => panic!(), } } - pub fn as_ref(&mut self) -> &SharedBytes { + + pub fn take_option(&mut self) -> Option { + match mem::replace(self, Output::Empty) { + Output::Buffer(bytes) => Some(bytes), + Output::Encoder(mut enc) => Some(enc.take_buf()), + Output::TE(mut te) => Some(te.take()), + _ => None, + } + } + + pub fn as_ref(&mut self) -> &BytesMut { match self { Output::Buffer(ref mut bytes) => bytes, Output::Encoder(ref mut enc) => enc.buf_ref(), @@ -392,9 +403,11 @@ impl Output { Output::Empty => panic!(), } } - pub fn get_mut(&mut self) -> &mut BytesMut { + pub fn as_mut(&mut self) -> &mut BytesMut { match self { - Output::Buffer(ref mut bytes) => bytes.get_mut(), + Output::Buffer(ref mut bytes) => bytes, + Output::Encoder(ref mut enc) => enc.buf_mut(), + Output::TE(ref mut te) => te.buf_mut(), _ => panic!(), } } @@ -457,9 +470,23 @@ pub(crate) enum ContentEncoder { Identity(TransferEncoding), } +impl fmt::Debug for ContentEncoder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), + ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"), + } + } +} + impl ContentEncoder { pub fn for_server( - buf: SharedBytes, req: &HttpInnerMessage, resp: &mut HttpResponse, + buf: BytesMut, req: &HttpInnerMessage, resp: &mut HttpResponse, response_encoding: ContentEncoding, ) -> Output { let version = resp.version().unwrap_or_else(|| req.version); @@ -522,7 +549,7 @@ impl ContentEncoder { if !(encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto) { - let mut tmp = SharedBytes::empty(); + let mut tmp = BytesMut::new(); let mut transfer = TransferEncoding::eof(tmp); let mut enc = match encoding { #[cfg(feature = "flate2")] @@ -613,7 +640,7 @@ impl ContentEncoder { } fn streaming_encoding( - buf: SharedBytes, version: Version, resp: &mut HttpResponse, + buf: BytesMut, version: Version, resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { @@ -703,6 +730,19 @@ impl ContentEncoder { } } + #[inline] + pub(crate) fn take_buf(&mut self) -> BytesMut { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), + ContentEncoder::Identity(ref mut encoder) => encoder.take(), + } + } + #[inline] pub(crate) fn buf_mut(&mut self) -> &mut BytesMut { match *self { @@ -717,7 +757,7 @@ impl ContentEncoder { } #[inline] - pub(crate) fn buf_ref(&mut self) -> &SharedBytes { + pub(crate) fn buf_ref(&mut self) -> &BytesMut { match *self { #[cfg(feature = "brotli")] ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(), @@ -810,7 +850,7 @@ impl ContentEncoder { /// Encoders to handle different Transfer-Encodings. #[derive(Debug)] pub(crate) struct TransferEncoding { - buf: Option, + buf: Option, kind: TransferEncodingKind, } @@ -829,11 +869,11 @@ enum TransferEncodingKind { } impl TransferEncoding { - fn take(self) -> SharedBytes { - self.buf.unwrap() + fn take(&mut self) -> BytesMut { + self.buf.take().unwrap() } - fn buf_ref(&mut self) -> &SharedBytes { + fn buf_ref(&mut self) -> &BytesMut { self.buf.as_ref().unwrap() } @@ -846,7 +886,7 @@ impl TransferEncoding { } fn buf_mut(&mut self) -> &mut BytesMut { - self.buf.as_mut().unwrap().get_mut() + self.buf.as_mut().unwrap() } #[inline] @@ -858,7 +898,7 @@ impl TransferEncoding { } #[inline] - pub fn eof(buf: SharedBytes) -> TransferEncoding { + pub fn eof(buf: BytesMut) -> TransferEncoding { TransferEncoding { buf: Some(buf), kind: TransferEncodingKind::Eof, @@ -866,7 +906,7 @@ impl TransferEncoding { } #[inline] - pub fn chunked(buf: SharedBytes) -> TransferEncoding { + pub fn chunked(buf: BytesMut) -> TransferEncoding { TransferEncoding { buf: Some(buf), kind: TransferEncodingKind::Chunked(false), @@ -874,7 +914,7 @@ impl TransferEncoding { } #[inline] - pub fn length(len: u64, buf: SharedBytes) -> TransferEncoding { + pub fn length(len: u64, buf: BytesMut) -> TransferEncoding { TransferEncoding { buf: Some(buf), kind: TransferEncodingKind::Length(len), @@ -1034,7 +1074,7 @@ mod tests { #[test] fn test_chunked_te() { - let bytes = SharedBytes::empty(); + let bytes = BytesMut::new(); let mut enc = TransferEncoding::chunked(bytes); { assert!(!enc.encode(b"test").ok().unwrap()); diff --git a/src/server/h1.rs b/src/server/h1.rs index 87eeccb0..8bca504c 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -93,10 +93,9 @@ where settings: Rc>, stream: T, addr: Option, buf: BytesMut, ) -> Self { - let bytes = settings.get_shared_bytes(); Http1 { flags: Flags::KEEPALIVE, - stream: H1Writer::new(stream, bytes, Rc::clone(&settings)), + stream: H1Writer::new(stream, Rc::clone(&settings)), decoder: H1Decoder::new(), payload: None, tasks: VecDeque::new(), diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 36571d88..502793f7 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -9,7 +9,6 @@ use tokio_io::AsyncWrite; use super::encoding::{ContentEncoder, Output}; use super::helpers; use super::settings::WorkerSettings; -use super::shared::SharedBytes; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; use header::ContentEncoding; @@ -40,14 +39,12 @@ pub(crate) struct H1Writer { } impl H1Writer { - pub fn new( - stream: T, buf: SharedBytes, settings: Rc>, - ) -> H1Writer { + pub fn new(stream: T, settings: Rc>) -> H1Writer { H1Writer { flags: Flags::KEEPALIVE, written: 0, headers_size: 0, - buffer: Output::Buffer(buf), + buffer: Output::Buffer(settings.get_bytes()), buffer_capacity: 0, stream, settings, @@ -91,6 +88,14 @@ impl H1Writer { } } +impl Drop for H1Writer { + fn drop(&mut self) { + if let Some(bytes) = self.buffer.take_option() { + self.settings.release_bytes(bytes); + } + } +} + impl Writer for H1Writer { #[inline] fn written(&self) -> u64 { @@ -104,8 +109,7 @@ impl Writer for H1Writer { #[inline] fn buffer(&mut self) -> &mut BytesMut { - //self.buffer.get_mut() - unimplemented!() + self.buffer.as_mut() } fn start( @@ -113,6 +117,7 @@ impl Writer for H1Writer { encoding: ContentEncoding, ) -> io::Result { // prepare task + self.buffer = ContentEncoder::for_server(self.buffer.take(), req, msg, encoding); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { self.flags = Flags::STARTED | Flags::KEEPALIVE; } else { @@ -141,7 +146,7 @@ impl Writer for H1Writer { // render message { // output buffer - let mut buffer = self.buffer.get_mut(); + let mut buffer = self.buffer.as_mut(); let reason = msg.reason().as_bytes(); let mut is_bin = if let Body::Binary(ref bytes) = body { @@ -221,9 +226,6 @@ impl Writer for H1Writer { self.headers_size = buffer.len() as u32; } - // output encoding - self.buffer = ContentEncoder::for_server(self.buffer.take(), req, msg, encoding); - if let Body::Binary(bytes) = body { self.written = bytes.len() as u64; self.buffer.write(bytes.as_ref())?; @@ -255,11 +257,11 @@ impl Writer for H1Writer { Ok(val) => val, }; if n < pl.len() { - self.buffer.write(&pl[n..]); + self.buffer.write(&pl[n..])?; return Ok(WriterState::Done); } } else { - self.buffer.write(payload.as_ref()); + self.buffer.write(payload.as_ref())?; } } else { // TODO: add warning, write after EOF @@ -267,7 +269,7 @@ impl Writer for H1Writer { } } else { // could be response to EXCEPT header - self.buffer.write(payload.as_ref()); + self.buffer.write(payload.as_ref())?; } } diff --git a/src/server/h2.rs b/src/server/h2.rs index 993376ef..1904734c 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -363,11 +363,7 @@ impl Entry { EntryPipe::Error(Pipeline::error(HttpResponse::NotFound())) }), payload: psender, - stream: H2Writer::new( - resp, - settings.get_shared_bytes(), - Rc::clone(settings), - ), + stream: H2Writer::new(resp, Rc::clone(settings)), flags: EntryFlags::empty(), recv, } diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index e5f579b2..c44af51a 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -14,7 +14,6 @@ use http::{HttpTryFrom, Version}; use super::encoding::{ContentEncoder, Output}; use super::helpers; use super::settings::WorkerSettings; -use super::shared::SharedBytes; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; use header::ContentEncoding; @@ -44,16 +43,16 @@ pub(crate) struct H2Writer { impl H2Writer { pub fn new( - respond: SendResponse, buf: SharedBytes, settings: Rc>, + respond: SendResponse, settings: Rc>, ) -> H2Writer { H2Writer { - respond, - settings, stream: None, flags: Flags::empty(), written: 0, - buffer: Output::Buffer(buf), + buffer: Output::Buffer(settings.get_bytes()), buffer_capacity: 0, + respond, + settings, } } @@ -64,6 +63,12 @@ impl H2Writer { } } +impl Drop for H2Writer { + fn drop(&mut self) { + self.settings.release_bytes(self.buffer.take()); + } +} + impl Writer for H2Writer { fn written(&self) -> u64 { self.written @@ -76,7 +81,7 @@ impl Writer for H2Writer { #[inline] fn buffer(&mut self) -> &mut BytesMut { - self.buffer.get_mut() + self.buffer.as_mut() } fn start( diff --git a/src/server/mod.rs b/src/server/mod.rs index bffdf427..1bbf460b 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -16,7 +16,6 @@ mod h2; mod h2writer; pub(crate) mod helpers; pub(crate) mod settings; -pub(crate) mod shared; mod srv; mod worker; diff --git a/src/server/settings.rs b/src/server/settings.rs index ca5acb91..0fc81fe5 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -1,4 +1,5 @@ use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; +use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; use std::{env, fmt, mem, net}; @@ -11,7 +12,6 @@ use time; use super::channel::Node; use super::helpers; -use super::shared::{SharedBytes, SharedBytesPool}; use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; @@ -201,8 +201,12 @@ impl WorkerSettings { self.ka_enabled } - pub fn get_shared_bytes(&self) -> SharedBytes { - SharedBytes::new(self.bytes.get_bytes(), Rc::clone(&self.bytes)) + pub fn get_bytes(&self) -> BytesMut { + self.bytes.get_bytes() + } + + pub fn release_bytes(&self, bytes: BytesMut) { + self.bytes.release_bytes(bytes) } pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage { @@ -273,6 +277,31 @@ impl fmt::Write for Date { } } +#[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) -> BytesMut { + if let Some(bytes) = self.0.borrow_mut().pop_front() { + bytes + } else { + BytesMut::new() + } + } + + pub fn release_bytes(&self, mut bytes: BytesMut) { + let v = &mut self.0.borrow_mut(); + if v.len() < 128 { + bytes.clear(); + v.push_front(bytes); + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/server/shared.rs b/src/server/shared.rs deleted file mode 100644 index 4d6a59d8..00000000 --- a/src/server/shared.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::cell::RefCell; -use std::collections::VecDeque; -use std::io; -use std::rc::Rc; - -use bytes::BytesMut; - -#[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) -> BytesMut { - if let Some(bytes) = self.0.borrow_mut().pop_front() { - bytes - } else { - BytesMut::new() - } - } - - pub fn release_bytes(&self, mut bytes: BytesMut) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - bytes.clear(); - v.push_front(bytes); - } - } -} - -#[derive(Debug)] -pub(crate) struct SharedBytes(Option, Option>); - -impl Drop for SharedBytes { - fn drop(&mut self) { - if let Some(pool) = self.1.take() { - if let Some(bytes) = self.0.take() { - pool.release_bytes(bytes); - } - } - } -} - -impl SharedBytes { - pub fn new(bytes: BytesMut, pool: Rc) -> SharedBytes { - SharedBytes(Some(bytes), Some(pool)) - } - - pub fn empty() -> SharedBytes { - SharedBytes(Some(BytesMut::new()), None) - } - - #[inline] - pub(crate) fn get_mut(&mut self) -> &mut BytesMut { - self.0.as_mut().unwrap() - } - - #[inline] - pub fn len(&self) -> usize { - self.0.as_ref().unwrap().len() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.0.as_ref().unwrap().is_empty() - } - - #[inline] - pub fn as_ref(&self) -> &[u8] { - self.0.as_ref().unwrap().as_ref() - } - - pub fn split_to(&mut self, n: usize) -> BytesMut { - self.get_mut().split_to(n) - } - - pub fn take(&mut self) -> BytesMut { - self.get_mut().take() - } - - #[inline] - pub fn reserve(&mut self, cap: usize) { - self.get_mut().reserve(cap); - } - - #[inline] - pub fn extend_from_slice(&mut self, data: &[u8]) { - let buf = self.get_mut(); - buf.extend_from_slice(data); - } -} - -impl Default for SharedBytes { - fn default() -> Self { - SharedBytes(Some(BytesMut::new()), None) - } -} - -impl io::Write for SharedBytes { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} From 33260c7b3583d341baecebff968f846d7aafbb88 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Jun 2018 10:42:20 +0600 Subject: [PATCH 0449/1635] split encoding module --- src/client/pipeline.rs | 2 +- src/client/writer.rs | 2 +- src/server/h1.rs | 2 +- src/server/h1writer.rs | 2 +- src/server/h2.rs | 4 +- src/server/h2writer.rs | 2 +- src/server/input.rs | 357 +++++++++++++++++++++++++ src/server/mod.rs | 3 +- src/server/{encoding.rs => output.rs} | 359 +------------------------- 9 files changed, 371 insertions(+), 362 deletions(-) create mode 100644 src/server/input.rs rename src/server/{encoding.rs => output.rs} (70%) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 4173c7d2..2886b42f 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -18,7 +18,7 @@ use error::Error; use error::PayloadError; use header::ContentEncoding; use httpmessage::HttpMessage; -use server::encoding::PayloadStream; +use server::input::PayloadStream; use server::WriterState; /// A set of errors that can occur during request sending and response reading diff --git a/src/client/writer.rs b/src/client/writer.rs index 65328979..d42a07d5 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -21,7 +21,7 @@ use tokio_io::AsyncWrite; use body::{Binary, Body}; use header::ContentEncoding; -use server::encoding::{ContentEncoder, Output, TransferEncoding}; +use server::output::{ContentEncoder, Output, TransferEncoding}; use server::WriterState; use client::ClientRequest; diff --git a/src/server/h1.rs b/src/server/h1.rs index 8bca504c..e358f84b 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -13,9 +13,9 @@ use httpresponse::HttpResponse; use payload::{Payload, PayloadStatus, PayloadWriter}; use pipeline::Pipeline; -use super::encoding::PayloadType; use super::h1decoder::{DecoderError, H1Decoder, Message}; use super::h1writer::H1Writer; +use super::input::PayloadType; use super::settings::WorkerSettings; use super::Writer; use super::{HttpHandler, HttpHandlerTask, IoStream}; diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 502793f7..5f5d6ec5 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -6,8 +6,8 @@ use std::io; use std::rc::Rc; use tokio_io::AsyncWrite; -use super::encoding::{ContentEncoder, Output}; use super::helpers; +use super::output::{ContentEncoder, Output}; use super::settings::WorkerSettings; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; diff --git a/src/server/h2.rs b/src/server/h2.rs index 1904734c..c2a38572 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -1,5 +1,3 @@ -#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] - use std::collections::VecDeque; use std::io::{Read, Write}; use std::net::SocketAddr; @@ -23,8 +21,8 @@ use payload::{Payload, PayloadStatus, PayloadWriter}; use pipeline::Pipeline; use uri::Url; -use super::encoding::PayloadType; use super::h2writer::H2Writer; +use super::input::PayloadType; use super::settings::WorkerSettings; use super::{HttpHandler, HttpHandlerTask, Writer}; diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index c44af51a..db7755ba 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -11,8 +11,8 @@ use std::{cmp, io}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{HttpTryFrom, Version}; -use super::encoding::{ContentEncoder, Output}; use super::helpers; +use super::output::{ContentEncoder, Output}; use super::settings::WorkerSettings; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; diff --git a/src/server/input.rs b/src/server/input.rs new file mode 100644 index 00000000..8c11c246 --- /dev/null +++ b/src/server/input.rs @@ -0,0 +1,357 @@ +use std::io::{Read, Write}; +use std::{cmp, io}; + +#[cfg(feature = "brotli")] +use brotli2::write::BrotliDecoder; +use bytes::{BufMut, Bytes, BytesMut}; +use error::PayloadError; +#[cfg(feature = "flate2")] +use flate2::read::GzDecoder; +#[cfg(feature = "flate2")] +use flate2::write::DeflateDecoder; +use header::ContentEncoding; +use http::header::{HeaderMap, CONTENT_ENCODING}; +use payload::{PayloadSender, PayloadStatus, PayloadWriter}; + +pub(crate) enum PayloadType { + Sender(PayloadSender), + Encoding(Box), +} + +impl PayloadType { + #[cfg(any(feature = "brotli", feature = "flate2"))] + pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { + // check content-encoding + let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { + if let Ok(enc) = enc.to_str() { + ContentEncoding::from(enc) + } else { + ContentEncoding::Auto + } + } else { + ContentEncoding::Auto + }; + + match enc { + ContentEncoding::Auto | ContentEncoding::Identity => { + PayloadType::Sender(sender) + } + _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), + } + } + + #[cfg(not(any(feature = "brotli", feature = "flate2")))] + pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { + PayloadType::Sender(sender) + } +} + +impl PayloadWriter for PayloadType { + #[inline] + fn set_error(&mut self, err: PayloadError) { + match *self { + PayloadType::Sender(ref mut sender) => sender.set_error(err), + PayloadType::Encoding(ref mut enc) => enc.set_error(err), + } + } + + #[inline] + fn feed_eof(&mut self) { + match *self { + PayloadType::Sender(ref mut sender) => sender.feed_eof(), + PayloadType::Encoding(ref mut enc) => enc.feed_eof(), + } + } + + #[inline] + fn feed_data(&mut self, data: Bytes) { + match *self { + PayloadType::Sender(ref mut sender) => sender.feed_data(data), + PayloadType::Encoding(ref mut enc) => enc.feed_data(data), + } + } + + #[inline] + fn need_read(&self) -> PayloadStatus { + match *self { + PayloadType::Sender(ref sender) => sender.need_read(), + PayloadType::Encoding(ref enc) => enc.need_read(), + } + } +} + +/// Payload wrapper with content decompression support +pub(crate) struct EncodedPayload { + inner: PayloadSender, + error: bool, + payload: PayloadStream, +} + +impl EncodedPayload { + pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { + EncodedPayload { + inner, + error: false, + payload: PayloadStream::new(enc), + } + } +} + +impl PayloadWriter for EncodedPayload { + fn set_error(&mut self, err: PayloadError) { + self.inner.set_error(err) + } + + fn feed_eof(&mut self) { + if !self.error { + match self.payload.feed_eof() { + Err(err) => { + self.error = true; + self.set_error(PayloadError::Io(err)); + } + Ok(value) => { + if let Some(b) = value { + self.inner.feed_data(b); + } + self.inner.feed_eof(); + } + } + } + } + + fn feed_data(&mut self, data: Bytes) { + if self.error { + return; + } + + match self.payload.feed_data(data) { + Ok(Some(b)) => self.inner.feed_data(b), + Ok(None) => (), + Err(e) => { + self.error = true; + self.set_error(e.into()); + } + } + } + + #[inline] + fn need_read(&self) -> PayloadStatus { + self.inner.need_read() + } +} + +pub(crate) enum Decoder { + #[cfg(feature = "flate2")] + Deflate(Box>), + #[cfg(feature = "flate2")] + Gzip(Option>>), + #[cfg(feature = "brotli")] + Br(Box>), + Identity, +} + +// should go after write::GzDecoder get implemented +#[derive(Debug)] +pub(crate) struct Wrapper { + pub buf: BytesMut, + pub eof: bool, +} + +impl io::Read for Wrapper { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = cmp::min(buf.len(), self.buf.len()); + buf[..len].copy_from_slice(&self.buf[..len]); + self.buf.split_to(len); + if len == 0 { + if self.eof { + Ok(0) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + Ok(len) + } + } +} + +impl io::Write for Wrapper { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub(crate) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer { + buf: BytesMut::with_capacity(8192), + } + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl io::Write for Writer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Payload stream with decompression support +pub(crate) struct PayloadStream { + decoder: Decoder, + dst: BytesMut, +} + +impl PayloadStream { + pub fn new(enc: ContentEncoding) -> PayloadStream { + let dec = match enc { + #[cfg(feature = "brotli")] + ContentEncoding::Br => { + Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) + } + #[cfg(feature = "flate2")] + ContentEncoding::Deflate => { + Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new()))) + } + #[cfg(feature = "flate2")] + ContentEncoding::Gzip => Decoder::Gzip(None), + _ => Decoder::Identity, + }; + PayloadStream { + decoder: dec, + dst: BytesMut::new(), + } + } +} + +impl PayloadStream { + pub fn feed_eof(&mut self) -> io::Result> { + match self.decoder { + #[cfg(feature = "brotli")] + Decoder::Br(ref mut decoder) => match decoder.finish() { + Ok(mut writer) => { + let b = writer.take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + #[cfg(feature = "flate2")] + Decoder::Gzip(ref mut decoder) => { + if let Some(ref mut decoder) = *decoder { + decoder.as_mut().get_mut().eof = true; + + self.dst.reserve(8192); + match decoder.read(unsafe { self.dst.bytes_mut() }) { + Ok(n) => { + unsafe { self.dst.advance_mut(n) }; + return Ok(Some(self.dst.take().freeze())); + } + Err(e) => return Err(e), + } + } else { + Ok(None) + } + } + #[cfg(feature = "flate2")] + Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + Decoder::Identity => Ok(None), + } + } + + pub fn feed_data(&mut self, data: Bytes) -> io::Result> { + match self.decoder { + #[cfg(feature = "brotli")] + Decoder::Br(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + #[cfg(feature = "flate2")] + Decoder::Gzip(ref mut decoder) => { + if decoder.is_none() { + *decoder = Some(Box::new(GzDecoder::new(Wrapper { + buf: BytesMut::from(data), + eof: false, + }))); + } else { + let _ = decoder.as_mut().unwrap().write(&data); + } + + loop { + self.dst.reserve(8192); + match decoder + .as_mut() + .as_mut() + .unwrap() + .read(unsafe { self.dst.bytes_mut() }) + { + Ok(n) => { + if n != 0 { + unsafe { self.dst.advance_mut(n) }; + } + if n == 0 { + return Ok(Some(self.dst.take().freeze())); + } + } + Err(e) => { + if e.kind() == io::ErrorKind::WouldBlock + && !self.dst.is_empty() + { + return Ok(Some(self.dst.take().freeze())); + } + return Err(e); + } + } + } + } + #[cfg(feature = "flate2")] + Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + Decoder::Identity => Ok(Some(data)), + } + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 1bbf460b..f10dacc2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -8,13 +8,14 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; mod channel; -pub(crate) mod encoding; pub(crate) mod h1; pub(crate) mod h1decoder; mod h1writer; mod h2; mod h2writer; pub(crate) mod helpers; +pub(crate) mod input; +pub(crate) mod output; pub(crate) mod settings; mod srv; mod worker; diff --git a/src/server/encoding.rs b/src/server/output.rs similarity index 70% rename from src/server/encoding.rs rename to src/server/output.rs index 5acce762..7908dd38 100644 --- a/src/server/encoding.rs +++ b/src/server/output.rs @@ -1,372 +1,24 @@ use std::fmt::Write as FmtWrite; -use std::io::{Read, Write}; +use std::io::Write; use std::str::FromStr; use std::{cmp, fmt, io, mem}; #[cfg(feature = "brotli")] -use brotli2::write::{BrotliDecoder, BrotliEncoder}; -use bytes::{BufMut, Bytes, BytesMut}; +use brotli2::write::BrotliEncoder; +use bytes::BytesMut; #[cfg(feature = "flate2")] -use flate2::read::GzDecoder; -#[cfg(feature = "flate2")] -use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; +use flate2::write::{DeflateEncoder, GzEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; use http::header::{ - HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, - TRANSFER_ENCODING, + HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; use http::{HttpTryFrom, Method, Version}; use body::{Binary, Body}; -use error::PayloadError; use header::ContentEncoding; use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; -use payload::{PayloadSender, PayloadStatus, PayloadWriter}; - -pub(crate) enum PayloadType { - Sender(PayloadSender), - Encoding(Box), -} - -impl PayloadType { - #[cfg(any(feature = "brotli", feature = "flate2"))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - // check content-encoding - let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - ContentEncoding::from(enc) - } else { - ContentEncoding::Auto - } - } else { - ContentEncoding::Auto - }; - - match enc { - ContentEncoding::Auto | ContentEncoding::Identity => { - PayloadType::Sender(sender) - } - _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), - } - } - - #[cfg(not(any(feature = "brotli", feature = "flate2")))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - PayloadType::Sender(sender) - } -} - -impl PayloadWriter for PayloadType { - #[inline] - fn set_error(&mut self, err: PayloadError) { - match *self { - PayloadType::Sender(ref mut sender) => sender.set_error(err), - PayloadType::Encoding(ref mut enc) => enc.set_error(err), - } - } - - #[inline] - fn feed_eof(&mut self) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_eof(), - PayloadType::Encoding(ref mut enc) => enc.feed_eof(), - } - } - - #[inline] - fn feed_data(&mut self, data: Bytes) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_data(data), - PayloadType::Encoding(ref mut enc) => enc.feed_data(data), - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - match *self { - PayloadType::Sender(ref sender) => sender.need_read(), - PayloadType::Encoding(ref enc) => enc.need_read(), - } - } -} - -/// Payload wrapper with content decompression support -pub(crate) struct EncodedPayload { - inner: PayloadSender, - error: bool, - payload: PayloadStream, -} - -impl EncodedPayload { - pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { - EncodedPayload { - inner, - error: false, - payload: PayloadStream::new(enc), - } - } -} - -impl PayloadWriter for EncodedPayload { - fn set_error(&mut self, err: PayloadError) { - self.inner.set_error(err) - } - - fn feed_eof(&mut self) { - if !self.error { - match self.payload.feed_eof() { - Err(err) => { - self.error = true; - self.set_error(PayloadError::Io(err)); - } - Ok(value) => { - if let Some(b) = value { - self.inner.feed_data(b); - } - self.inner.feed_eof(); - } - } - } - } - - fn feed_data(&mut self, data: Bytes) { - if self.error { - return; - } - - match self.payload.feed_data(data) { - Ok(Some(b)) => self.inner.feed_data(b), - Ok(None) => (), - Err(e) => { - self.error = true; - self.set_error(e.into()); - } - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - self.inner.need_read() - } -} - -pub(crate) enum Decoder { - #[cfg(feature = "flate2")] - Deflate(Box>), - #[cfg(feature = "flate2")] - Gzip(Option>>), - #[cfg(feature = "brotli")] - Br(Box>), - Identity, -} - -// should go after write::GzDecoder get implemented -#[derive(Debug)] -pub(crate) struct Wrapper { - pub buf: BytesMut, - pub eof: bool, -} - -impl io::Read for Wrapper { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let len = cmp::min(buf.len(), self.buf.len()); - buf[..len].copy_from_slice(&self.buf[..len]); - self.buf.split_to(len); - if len == 0 { - if self.eof { - Ok(0) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - Ok(len) - } - } -} - -impl io::Write for Wrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::with_capacity(8192), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl io::Write for Writer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -/// Payload stream with decompression support -pub(crate) struct PayloadStream { - decoder: Decoder, - dst: BytesMut, -} - -impl PayloadStream { - pub fn new(enc: ContentEncoding) -> PayloadStream { - let dec = match enc { - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => Decoder::Gzip(None), - _ => Decoder::Identity, - }; - PayloadStream { - decoder: dec, - dst: BytesMut::new(), - } - } -} - -impl PayloadStream { - pub fn feed_eof(&mut self) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.finish() { - Ok(mut writer) => { - let b = writer.take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => { - if let Some(ref mut decoder) = *decoder { - decoder.as_mut().get_mut().eof = true; - - self.dst.reserve(8192); - match decoder.read(unsafe { self.dst.bytes_mut() }) { - Ok(n) => { - unsafe { self.dst.advance_mut(n) }; - return Ok(Some(self.dst.take().freeze())); - } - Err(e) => return Err(e), - } - } else { - Ok(None) - } - } - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(None), - } - } - - pub fn feed_data(&mut self, data: Bytes) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => { - if decoder.is_none() { - *decoder = Some(Box::new(GzDecoder::new(Wrapper { - buf: BytesMut::from(data), - eof: false, - }))); - } else { - let _ = decoder.as_mut().unwrap().write(&data); - } - - loop { - self.dst.reserve(8192); - match decoder - .as_mut() - .as_mut() - .unwrap() - .read(unsafe { self.dst.bytes_mut() }) - { - Ok(n) => { - if n != 0 { - unsafe { self.dst.advance_mut(n) }; - } - if n == 0 { - return Ok(Some(self.dst.take().freeze())); - } - } - Err(e) => { - if e.kind() == io::ErrorKind::WouldBlock - && !self.dst.is_empty() - { - return Ok(Some(self.dst.take().freeze())); - } - return Err(e); - } - } - } - } - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(Some(data)), - } - } -} #[derive(Debug)] pub(crate) enum Output { @@ -1071,6 +723,7 @@ impl AcceptEncoding { #[cfg(test)] mod tests { use super::*; + use bytes::Bytes; #[test] fn test_chunked_te() { From 989cd61236cc3a766221cfaa6a8b0dd660fcba83 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Jun 2018 10:59:01 +0600 Subject: [PATCH 0450/1635] handle empty te --- src/server/output.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/output.rs b/src/server/output.rs index 7908dd38..e84b55b8 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -644,7 +644,9 @@ impl TransferEncoding { impl io::Write for TransferEncoding { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { - self.encode(buf)?; + if self.buf.is_some() { + self.encode(buf)?; + } Ok(buf.len()) } From 8e8a68f90b49d0d01aa522efd8546c15ef453669 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Jun 2018 22:05:44 +0600 Subject: [PATCH 0451/1635] add empty output stream --- src/client/writer.rs | 78 ++++++++++++++++------------- src/server/h1writer.rs | 8 ++- src/server/h2writer.rs | 4 +- src/server/output.rs | 109 ++++++++++++++++++++++------------------- 4 files changed, 107 insertions(+), 92 deletions(-) diff --git a/src/client/writer.rs b/src/client/writer.rs index d42a07d5..173b47e1 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -221,45 +221,53 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { let transfer = match body { Body::Empty => { req.headers_mut().remove(CONTENT_LENGTH); - TransferEncoding::length(0, buf) + return Output::Empty(buf); } Body::Binary(ref mut bytes) => { - if encoding.is_compression() { - let mut tmp = BytesMut::new(); - let mut transfer = TransferEncoding::eof(tmp); - let mut enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::default()), - ), - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( - transfer, - Compression::default(), - )), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - ContentEncoder::Br(BrotliEncoder::new(transfer, 5)) - } - ContentEncoding::Identity => ContentEncoder::Identity(transfer), - ContentEncoding::Auto => unreachable!(), - }; - // TODO return error! - let _ = enc.write(bytes.as_ref()); - let _ = enc.write_eof(); - *bytes = Binary::from(enc.buf_mut().take()); + #[cfg(any(feature = "flate2", feature = "brotli"))] + { + if encoding.is_compression() { + let mut tmp = BytesMut::new(); + let mut transfer = TransferEncoding::eof(tmp); + let mut enc = match encoding { + #[cfg(feature = "flate2")] + ContentEncoding::Deflate => ContentEncoder::Deflate( + DeflateEncoder::new(transfer, Compression::default()), + ), + #[cfg(feature = "flate2")] + ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( + transfer, + Compression::default(), + )), + #[cfg(feature = "brotli")] + ContentEncoding::Br => { + ContentEncoder::Br(BrotliEncoder::new(transfer, 5)) + } + ContentEncoding::Auto | ContentEncoding::Identity => { + unreachable!() + } + }; + // TODO return error! + let _ = enc.write(bytes.as_ref()); + let _ = enc.write_eof(); + *bytes = Binary::from(enc.buf_mut().take()); - req.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); - encoding = ContentEncoding::Identity; + req.headers_mut().insert( + CONTENT_ENCODING, + HeaderValue::from_static(encoding.as_str()), + ); + encoding = ContentEncoding::Identity; + } + let mut b = BytesMut::new(); + let _ = write!(b, "{}", bytes.len()); + req.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); + TransferEncoding::eof(buf) + } + #[cfg(not(any(feature = "flate2", feature = "brotli")))] + { + TransferEncoding::eof(buf) } - let mut b = BytesMut::new(); - let _ = write!(b, "{}", bytes.len()); - req.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof(buf) } Body::Streaming(_) | Body::Actor(_) => { if req.upgrade() { diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 5f5d6ec5..b87891c2 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -7,7 +7,7 @@ use std::rc::Rc; use tokio_io::AsyncWrite; use super::helpers; -use super::output::{ContentEncoder, Output}; +use super::output::Output; use super::settings::WorkerSettings; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; @@ -60,9 +60,7 @@ impl H1Writer { self.flags = Flags::KEEPALIVE; } - pub fn disconnected(&mut self) { - self.buffer = Output::Empty; - } + pub fn disconnected(&mut self) {} pub fn keepalive(&self) -> bool { self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) @@ -117,7 +115,7 @@ impl Writer for H1Writer { encoding: ContentEncoding, ) -> io::Result { // prepare task - self.buffer = ContentEncoder::for_server(self.buffer.take(), req, msg, encoding); + self.buffer.for_server(req, msg, encoding); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { self.flags = Flags::STARTED | Flags::KEEPALIVE; } else { diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index db7755ba..9a02bbf4 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -12,7 +12,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{HttpTryFrom, Version}; use super::helpers; -use super::output::{ContentEncoder, Output}; +use super::output::Output; use super::settings::WorkerSettings; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; @@ -90,7 +90,7 @@ impl Writer for H2Writer { ) -> io::Result { // prepare response self.flags.insert(Flags::STARTED); - self.buffer = ContentEncoder::for_server(self.buffer.take(), req, msg, encoding); + self.buffer.for_server(req, msg, encoding); // http2 specific msg.headers_mut().remove(CONNECTION); diff --git a/src/server/output.rs b/src/server/output.rs index e84b55b8..ad0b80b6 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -22,71 +22,79 @@ use httpresponse::HttpResponse; #[derive(Debug)] pub(crate) enum Output { + Empty(BytesMut), Buffer(BytesMut), Encoder(ContentEncoder), TE(TransferEncoding), - Empty, + Done, } impl Output { pub fn take(&mut self) -> BytesMut { - match mem::replace(self, Output::Empty) { + match mem::replace(self, Output::Done) { + Output::Empty(bytes) => bytes, Output::Buffer(bytes) => bytes, Output::Encoder(mut enc) => enc.take_buf(), Output::TE(mut te) => te.take(), - _ => panic!(), + Output::Done => panic!(), } } pub fn take_option(&mut self) -> Option { - match mem::replace(self, Output::Empty) { + match mem::replace(self, Output::Done) { + Output::Empty(bytes) => Some(bytes), Output::Buffer(bytes) => Some(bytes), Output::Encoder(mut enc) => Some(enc.take_buf()), Output::TE(mut te) => Some(te.take()), - _ => None, + Output::Done => None, } } pub fn as_ref(&mut self) -> &BytesMut { match self { + Output::Empty(ref mut bytes) => bytes, Output::Buffer(ref mut bytes) => bytes, Output::Encoder(ref mut enc) => enc.buf_ref(), Output::TE(ref mut te) => te.buf_ref(), - Output::Empty => panic!(), + Output::Done => panic!(), } } pub fn as_mut(&mut self) -> &mut BytesMut { match self { + Output::Empty(ref mut bytes) => bytes, Output::Buffer(ref mut bytes) => bytes, Output::Encoder(ref mut enc) => enc.buf_mut(), Output::TE(ref mut te) => te.buf_mut(), - _ => panic!(), + Output::Done => panic!(), } } pub fn split_to(&mut self, cap: usize) -> BytesMut { match self { + Output::Empty(ref mut bytes) => bytes.split_to(cap), Output::Buffer(ref mut bytes) => bytes.split_to(cap), Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap), Output::TE(ref mut te) => te.buf_mut().split_to(cap), - Output::Empty => BytesMut::new(), + Output::Done => BytesMut::new(), } } pub fn len(&self) -> usize { match self { + Output::Empty(ref bytes) => bytes.len(), Output::Buffer(ref bytes) => bytes.len(), Output::Encoder(ref enc) => enc.len(), Output::TE(ref te) => te.len(), - Output::Empty => 0, + Output::Done => 0, } } pub fn is_empty(&self) -> bool { match self { + Output::Empty(ref bytes) => bytes.is_empty(), Output::Buffer(ref bytes) => bytes.is_empty(), Output::Encoder(ref enc) => enc.is_empty(), Output::TE(ref te) => te.is_empty(), - Output::Empty => true, + Output::Done => true, } } @@ -98,7 +106,7 @@ impl Output { } Output::Encoder(ref mut enc) => enc.write(data), Output::TE(ref mut te) => te.encode(data).map(|_| ()), - Output::Empty => Ok(()), + Output::Empty(_) | Output::Done => Ok(()), } } @@ -107,40 +115,15 @@ impl Output { Output::Buffer(_) => Ok(true), Output::Encoder(ref mut enc) => enc.write_eof(), Output::TE(ref mut te) => Ok(te.encode_eof()), - Output::Empty => Ok(true), + Output::Empty(_) | Output::Done => Ok(true), } } -} -pub(crate) enum ContentEncoder { - #[cfg(feature = "flate2")] - Deflate(DeflateEncoder), - #[cfg(feature = "flate2")] - Gzip(GzEncoder), - #[cfg(feature = "brotli")] - Br(BrotliEncoder), - Identity(TransferEncoding), -} - -impl fmt::Debug for ContentEncoder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), - ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"), - } - } -} - -impl ContentEncoder { pub fn for_server( - buf: BytesMut, req: &HttpInnerMessage, resp: &mut HttpResponse, + &mut self, req: &HttpInnerMessage, resp: &mut HttpResponse, response_encoding: ContentEncoding, - ) -> Output { + ) { + let buf = self.take(); let version = resp.version().unwrap_or_else(|| req.version); let is_head = req.method == Method::HEAD; let mut len = 0; @@ -188,12 +171,13 @@ impl ContentEncoder { let mut encoding = ContentEncoding::Identity; #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] - let mut transfer = match resp.body() { + let transfer = match resp.body() { &Body::Empty => { if req.method != Method::HEAD { resp.headers_mut().remove(CONTENT_LENGTH); } - TransferEncoding::length(0, buf) + *self = Output::Empty(buf); + return; } &Body::Binary(_) => { #[cfg(any(feature = "brotli", feature = "flate2"))] @@ -228,8 +212,6 @@ impl ContentEncoder { let _ = enc.write_eof(); let body = enc.buf_mut().take(); len = body.len(); - - encoding = ContentEncoding::Identity; resp.replace_body(Binary::from(body)); } } @@ -241,10 +223,11 @@ impl ContentEncoder { CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap(), ); + *self = Output::Empty(buf); } else { - // resp.headers_mut().remove(CONTENT_LENGTH); + *self = Output::Buffer(buf); } - TransferEncoding::eof(buf) + return; } &Body::Streaming(_) | &Body::Actor(_) => { if resp.upgrade() { @@ -262,14 +245,15 @@ impl ContentEncoder { { resp.headers_mut().remove(CONTENT_LENGTH); } - ContentEncoder::streaming_encoding(buf, version, resp) + Output::streaming_encoding(buf, version, resp) } } }; // check for head response if is_head { resp.set_body(Body::Empty); - transfer.kind = TransferEncodingKind::Length(0); + *self = Output::Empty(transfer.buf.unwrap()); + return; } let enc = match encoding { @@ -285,10 +269,11 @@ impl ContentEncoder { #[cfg(feature = "brotli")] ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)), ContentEncoding::Identity | ContentEncoding::Auto => { - return Output::TE(transfer) + *self = Output::TE(transfer); + return; } }; - Output::Encoder(enc) + *self = Output::Encoder(enc); } fn streaming_encoding( @@ -355,6 +340,30 @@ impl ContentEncoder { } } +pub(crate) enum ContentEncoder { + #[cfg(feature = "flate2")] + Deflate(DeflateEncoder), + #[cfg(feature = "flate2")] + Gzip(GzEncoder), + #[cfg(feature = "brotli")] + Br(BrotliEncoder), + Identity(TransferEncoding), +} + +impl fmt::Debug for ContentEncoder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), + ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"), + } + } +} + impl ContentEncoder { #[inline] pub fn len(&self) -> usize { From c0cdc39ba9ccdc3c7b0243fccd118fdeed44163e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Jun 2018 22:21:04 +0600 Subject: [PATCH 0452/1635] do not store cookies on client response --- src/client/response.rs | 47 ++++++++++++++---------------------------- src/server/settings.rs | 3 +++ tests/test_client.rs | 4 ++-- 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/client/response.rs b/src/client/response.rs index f76d058e..28d6f51b 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,5 +1,3 @@ -use std::cell::UnsafeCell; -use std::rc::Rc; use std::{fmt, str}; use bytes::Bytes; @@ -32,64 +30,49 @@ impl Default for ClientMessage { } /// An HTTP Client response -pub struct ClientResponse(Rc>, Option>); +pub struct ClientResponse(ClientMessage, Option>); impl HttpMessage for ClientResponse { /// Get the headers from the response. #[inline] fn headers(&self) -> &HeaderMap { - &self.as_ref().headers + &self.0.headers } } impl ClientResponse { pub(crate) fn new(msg: ClientMessage) -> ClientResponse { - ClientResponse(Rc::new(UnsafeCell::new(msg)), None) + ClientResponse(msg, None) } pub(crate) fn set_pipeline(&mut self, pl: Box) { self.1 = Some(pl); } - #[inline] - fn as_ref(&self) -> &ClientMessage { - unsafe { &*self.0.get() } - } - - #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))] - fn as_mut(&self) -> &mut ClientMessage { - unsafe { &mut *self.0.get() } - } - /// Get the HTTP version of this response. #[inline] pub fn version(&self) -> Version { - self.as_ref().version + self.0.version } /// Get the status from the server. #[inline] pub fn status(&self) -> StatusCode { - self.as_ref().status + self.0.status } /// Load response cookies. - pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { - if self.as_ref().cookies.is_none() { - let msg = self.as_mut(); - let mut cookies = Vec::new(); - for val in msg.headers.get_all(header::SET_COOKIE).iter() { - let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?; - cookies.push(Cookie::parse_encoded(s)?.into_owned()); - } - msg.cookies = Some(cookies) + pub fn cookies(&self) -> Result>, CookieParseError> { + let mut cookies = Vec::new(); + for val in self.0.headers.get_all(header::SET_COOKIE).iter() { + let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?; + cookies.push(Cookie::parse_encoded(s)?.into_owned()); } - Ok(self.as_ref().cookies.as_ref().unwrap()) + Ok(cookies) } /// Return request cookie. - pub fn cookie(&self, name: &str) -> Option<&Cookie> { + pub fn cookie(&self, name: &str) -> Option { if let Ok(cookies) = self.cookies() { for cookie in cookies { if cookie.name() == name { @@ -132,11 +115,11 @@ mod tests { #[test] fn test_debug() { - let resp = ClientResponse::new(ClientMessage::default()); - resp.as_mut() + let mut resp = ClientResponse::new(ClientMessage::default()); + resp.0 .headers .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1")); - resp.as_mut() + resp.0 .headers .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2")); diff --git a/src/server/settings.rs b/src/server/settings.rs index 0fc81fe5..4ec1cde2 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -124,6 +124,7 @@ impl ServerSettings { /// Returns default `CpuPool` for server pub fn cpu_pool(&self) -> &CpuPool { + // Unsafe: ServerSetting is !Sync, DEFAULT_CPUPOOL is protected by Mutex unsafe { let val = &mut *self.cpu_pool.get(); if val.is_none() { @@ -230,10 +231,12 @@ impl WorkerSettings { } pub fn update_date(&self) { + // Unsafe: WorkerSetting is !Sync and !Send unsafe { &mut *self.date.get() }.update(); } pub fn set_date(&self, dst: &mut BytesMut, full: bool) { + // Unsafe: WorkerSetting is !Sync and !Send unsafe { if full { let mut buf: [u8; 39] = mem::uninitialized(); diff --git a/tests/test_client.rs b/tests/test_client.rs index 0d058c51..dc147ccd 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -425,9 +425,9 @@ fn test_client_cookie_handling() { let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, &cookie1); + assert_eq!(c1, cookie1); let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, &cookie2); + assert_eq!(c2, cookie2); } #[test] From d1b73e30e079796e5c79472f79af4bd1e0218e51 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Jun 2018 22:27:30 +0600 Subject: [PATCH 0453/1635] update comments --- src/multipart.rs | 21 +++++++++++++++------ src/uri.rs | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/multipart.rs b/src/multipart.rs index 7c93b565..1735085d 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -413,7 +413,9 @@ where pub fn content_disposition(&self) -> Option { // RFC 7578: 'Each part MUST contain a Content-Disposition header field // where the disposition type is "form-data".' - if let Some(content_disposition) = self.headers.get(::http::header::CONTENT_DISPOSITION) { + if let Some(content_disposition) = + self.headers.get(::http::header::CONTENT_DISPOSITION) + { ContentDisposition::from_raw(content_disposition).ok() } else { None @@ -607,9 +609,10 @@ where where 'a: 'b, { + // Unsafe: Invariant is inforced by Safety Safety is used as ref counter, + // only top most ref can have mutable access to payload. if s.current() { - let payload: &mut PayloadHelper = - unsafe { &mut *self.payload.get() }; + let payload: &mut PayloadHelper = unsafe { &mut *self.payload.get() }; Some(payload) } else { None @@ -751,10 +754,16 @@ mod tests { Ok(Async::Ready(Some(item))) => match item { MultipartItem::Field(mut field) => { { - use http::header::{DispositionType, DispositionParam}; + use http::header::{DispositionParam, DispositionType}; let cd = field.content_disposition().unwrap(); - assert_eq!(cd.disposition, DispositionType::Ext("form-data".into())); - assert_eq!(cd.parameters[0], DispositionParam::Ext("name".into(), "file".into())); + assert_eq!( + cd.disposition, + DispositionType::Ext("form-data".into()) + ); + assert_eq!( + cd.parameters[0], + DispositionParam::Ext("name".into(), "file".into()) + ); } assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); diff --git a/src/uri.rs b/src/uri.rs index aa6f767d..752ddad8 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -146,7 +146,7 @@ impl Quoter { } if let Some(data) = cloned { - // we get data from http::Uri, which does utf-8 checks already + // Unsafe: we get data from http::Uri, which does utf-8 checks already // this code only decodes valid pct encoded values Some(unsafe { Rc::new(String::from_utf8_unchecked(data)) }) } else { From 32212bad1f36790bfbb16be823624a41d9e2327b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Jun 2018 09:08:28 +0600 Subject: [PATCH 0454/1635] simplify http response pool --- src/application.rs | 3 +- src/httpresponse.rs | 113 ++++++++++++++++------------------------- src/pipeline.rs | 39 +++++++++----- src/server/settings.rs | 10 ++-- 4 files changed, 77 insertions(+), 88 deletions(-) diff --git a/src/application.rs b/src/application.rs index bdc55fe7..906e8f9a 100644 --- a/src/application.rs +++ b/src/application.rs @@ -636,7 +636,8 @@ where }; } - let (router, resources) = Router::new(&prefix, parts.settings, resources); + let (router, resources) = + Router::new(&prefix, parts.settings.clone(), resources); let inner = Rc::new(Inner { prefix: prefix_len, diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 333e6c4a..7ed82a8d 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -1,8 +1,7 @@ //! Http response -use std::cell::UnsafeCell; +use std::cell::RefCell; use std::collections::VecDeque; use std::io::Write; -use std::rc::Rc; use std::{fmt, mem, str}; use bytes::{BufMut, Bytes, BytesMut}; @@ -36,30 +35,17 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct HttpResponse( - Option>, - Rc>, -); - -impl Drop for HttpResponse { - fn drop(&mut self) { - if let Some(inner) = self.0.take() { - HttpResponsePool::release(&self.1, inner) - } - } -} +pub struct HttpResponse(Box); impl HttpResponse { - #[inline(always)] - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] + #[inline] fn get_ref(&self) -> &InnerHttpResponse { - self.0.as_ref().unwrap() + self.0.as_ref() } - #[inline(always)] - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] + #[inline] fn get_mut(&mut self) -> &mut InnerHttpResponse { - self.0.as_mut().unwrap() + self.0.as_mut() } /// Create http response builder with specific status. @@ -96,7 +82,7 @@ impl HttpResponse { /// Convert `HttpResponse` to a `HttpResponseBuilder` #[inline] - pub fn into_builder(mut self) -> HttpResponseBuilder { + pub fn into_builder(self) -> HttpResponseBuilder { // If this response has cookies, load them into a jar let mut jar: Option = None; for c in self.cookies() { @@ -109,11 +95,8 @@ impl HttpResponse { } } - let response = self.0.take(); - let pool = Some(Rc::clone(&self.1)); HttpResponseBuilder { - response, - pool, + response: Some(self.0), err: None, cookies: jar, } @@ -299,15 +282,12 @@ impl HttpResponse { self.get_mut().write_capacity = cap; } - pub(crate) fn into_parts(mut self) -> HttpResponseParts { - self.0.take().unwrap().into_parts() + pub(crate) fn into_parts(self) -> HttpResponseParts { + self.0.into_parts() } pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse { - HttpResponse( - Some(Box::new(InnerHttpResponse::from_parts(parts))), - HttpResponsePool::pool(), - ) + HttpResponse(Box::new(InnerHttpResponse::from_parts(parts))) } } @@ -353,7 +333,6 @@ impl<'a> Iterator for CookieIter<'a> { /// builder-like pattern. pub struct HttpResponseBuilder { response: Option>, - pool: Option>>, err: Option, cookies: Option, } @@ -643,7 +622,7 @@ impl HttpResponseBuilder { } } response.body = body.into(); - HttpResponse(Some(response), self.pool.take().unwrap()) + HttpResponse(response) } #[inline] @@ -692,7 +671,6 @@ impl HttpResponseBuilder { pub fn take(&mut self) -> HttpResponseBuilder { HttpResponseBuilder { response: self.response.take(), - pool: self.pool.take(), err: self.err.take(), cookies: self.cookies.take(), } @@ -973,27 +951,28 @@ impl InnerHttpResponse { } /// Internal use only! -pub(crate) struct HttpResponsePool(VecDeque>); +pub(crate) struct HttpResponsePool(RefCell>>); -thread_local!(static POOL: Rc> = HttpResponsePool::pool()); +thread_local!(static POOL: &'static HttpResponsePool = HttpResponsePool::pool()); impl HttpResponsePool { - pub fn pool() -> Rc> { - Rc::new(UnsafeCell::new(HttpResponsePool(VecDeque::with_capacity( - 128, - )))) + fn pool() -> &'static HttpResponsePool { + let pool = HttpResponsePool(RefCell::new(VecDeque::with_capacity(128))); + Box::leak(Box::new(pool)) + } + + pub fn get_pool() -> &'static HttpResponsePool { + POOL.with(|p| *p) } #[inline] pub fn get_builder( - pool: &Rc>, status: StatusCode, + pool: &'static HttpResponsePool, status: StatusCode, ) -> HttpResponseBuilder { - let p = unsafe { &mut *pool.as_ref().get() }; - if let Some(mut msg) = p.0.pop_front() { + if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; HttpResponseBuilder { response: Some(msg), - pool: Some(Rc::clone(pool)), err: None, cookies: None, } @@ -1001,7 +980,6 @@ impl HttpResponsePool { let msg = Box::new(InnerHttpResponse::new(status, Body::Empty)); HttpResponseBuilder { response: Some(msg), - pool: Some(Rc::clone(pool)), err: None, cookies: None, } @@ -1010,16 +988,15 @@ impl HttpResponsePool { #[inline] pub fn get_response( - pool: &Rc>, status: StatusCode, body: Body, + pool: &'static HttpResponsePool, status: StatusCode, body: Body, ) -> HttpResponse { - let p = unsafe { &mut *pool.as_ref().get() }; - if let Some(mut msg) = p.0.pop_front() { + if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; msg.body = body; - HttpResponse(Some(msg), Rc::clone(pool)) + HttpResponse(msg) } else { let msg = Box::new(InnerHttpResponse::new(status, body)); - HttpResponse(Some(msg), Rc::clone(pool)) + HttpResponse(msg) } } @@ -1033,24 +1010,24 @@ impl HttpResponsePool { POOL.with(|pool| HttpResponsePool::get_response(pool, status, body)) } - #[inline(always)] - #[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))] - fn release( - pool: &Rc>, mut inner: Box, - ) { - let pool = unsafe { &mut *pool.as_ref().get() }; - if pool.0.len() < 128 { - inner.headers.clear(); - inner.version = None; - inner.chunked = None; - inner.reason = None; - inner.encoding = None; - inner.connection_type = None; - inner.response_size = 0; - inner.error = None; - inner.write_capacity = MAX_WRITE_BUFFER_SIZE; - pool.0.push_front(inner); - } + #[inline] + pub(crate) fn release(resp: HttpResponse) { + let mut inner = resp.0; + POOL.with(|pool| { + let mut p = pool.0.borrow_mut(); + if p.len() < 128 { + inner.headers.clear(); + inner.version = None; + inner.chunked = None; + inner.reason = None; + inner.encoding = None; + inner.connection_type = None; + inner.response_size = 0; + inner.error = None; + inner.write_capacity = MAX_WRITE_BUFFER_SIZE; + p.push_front(inner); + } + }); } } diff --git a/src/pipeline.rs b/src/pipeline.rs index 87400e29..9f38f768 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -13,7 +13,7 @@ use error::Error; use handler::{AsyncResult, AsyncResultItem}; use header::ContentEncoding; use httprequest::HttpRequest; -use httpresponse::HttpResponse; +use httpresponse::{HttpResponse, HttpResponsePool}; use middleware::{Finished, Middleware, Response, Started}; use server::{HttpHandlerTask, Writer, WriterState}; @@ -691,7 +691,7 @@ impl ProcessResponse { /// Middlewares start executor struct FinishingMiddlewares { - resp: HttpResponse, + resp: Option, fut: Option>>, _s: PhantomData, _h: PhantomData, @@ -703,10 +703,10 @@ impl FinishingMiddlewares { info: &mut PipelineInfo, mws: &[Box>], resp: HttpResponse, ) -> PipelineState { if info.count == 0 { - Completed::init(info) + Completed::init(info, resp) } else { let mut state = FinishingMiddlewares { - resp, + resp: Some(resp), fut: None, _s: PhantomData, _h: PhantomData, @@ -741,15 +741,16 @@ impl FinishingMiddlewares { } self.fut = None; if info.count == 0 { - return Some(Completed::init(info)); + return Some(Completed::init(info, self.resp.take().unwrap())); } info.count -= 1; - let state = mws[info.count as usize].finish(&mut info.req, &self.resp); + let state = mws[info.count as usize] + .finish(&mut info.req, self.resp.as_ref().unwrap()); match state { Finished::Done => { if info.count == 0 { - return Some(Completed::init(info)); + return Some(Completed::init(info, self.resp.take().unwrap())); } } Finished::Future(fut) => { @@ -761,19 +762,20 @@ impl FinishingMiddlewares { } #[derive(Debug)] -struct Completed(PhantomData, PhantomData); +struct Completed(PhantomData, PhantomData, Option); impl Completed { #[inline] - fn init(info: &mut PipelineInfo) -> PipelineState { + fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { if let Some(ref err) = info.error { error!("Error occurred during request handling: {}", err); } if info.context.is_none() { + HttpResponsePool::release(resp); PipelineState::None } else { - PipelineState::Completed(Completed(PhantomData, PhantomData)) + PipelineState::Completed(Completed(PhantomData, PhantomData, Some(resp))) } } @@ -781,8 +783,14 @@ impl Completed { fn poll(&mut self, info: &mut PipelineInfo) -> Option> { match info.poll_context() { Ok(Async::NotReady) => None, - Ok(Async::Ready(())) => Some(PipelineState::None), - Err(_) => Some(PipelineState::Error), + Ok(Async::Ready(())) => { + HttpResponsePool::release(self.2.take().unwrap()); + Some(PipelineState::None) + } + Err(_) => { + HttpResponsePool::release(self.2.take().unwrap()); + Some(PipelineState::Error) + } } } } @@ -793,6 +801,7 @@ mod tests { use actix::*; use context::HttpContext; use futures::future::{lazy, result}; + use http::StatusCode; use tokio::runtime::current_thread::Runtime; impl PipelineState { @@ -823,16 +832,18 @@ mod tests { .unwrap() .block_on(lazy(|| { let mut info = PipelineInfo::new(HttpRequest::default()); - Completed::<(), Inner<()>>::init(&mut info) + let resp = HttpResponse::new(StatusCode::OK); + Completed::<(), Inner<()>>::init(&mut info, resp) .is_none() .unwrap(); let req = HttpRequest::default(); let ctx = HttpContext::new(req.clone(), MyActor); let addr = ctx.address(); + let resp = HttpResponse::new(StatusCode::OK); let mut info = PipelineInfo::new(req); info.context = Some(Box::new(ctx)); - let mut state = Completed::<(), Inner<()>>::init(&mut info) + let mut state = Completed::<(), Inner<()>>::init(&mut info, resp) .completed() .unwrap(); diff --git a/src/server/settings.rs b/src/server/settings.rs index 4ec1cde2..31750b22 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -42,7 +42,7 @@ pub struct ServerSettings { secure: bool, host: String, cpu_pool: UnsafeCell>, - responses: Rc>, + responses: &'static HttpResponsePool, } impl Clone for ServerSettings { @@ -52,7 +52,7 @@ impl Clone for ServerSettings { secure: self.secure, host: self.host.clone(), cpu_pool: UnsafeCell::new(None), - responses: HttpResponsePool::pool(), + responses: HttpResponsePool::get_pool(), } } } @@ -63,7 +63,7 @@ impl Default for ServerSettings { addr: None, secure: false, host: "localhost:8080".to_owned(), - responses: HttpResponsePool::pool(), + responses: HttpResponsePool::get_pool(), cpu_pool: UnsafeCell::new(None), } } @@ -82,7 +82,7 @@ impl ServerSettings { "localhost".to_owned() }; let cpu_pool = UnsafeCell::new(None); - let responses = HttpResponsePool::pool(); + let responses = HttpResponsePool::get_pool(); ServerSettings { addr, secure, @@ -103,7 +103,7 @@ impl ServerSettings { host, secure, cpu_pool: UnsafeCell::new(None), - responses: HttpResponsePool::pool(), + responses: HttpResponsePool::get_pool(), } } From 800c404c72a254ebac67b04b3588c6dae19f3a53 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Jun 2018 10:10:02 +0600 Subject: [PATCH 0455/1635] explicit response release --- src/httpresponse.rs | 53 ++++++++++++++++++++++++++------------------- src/pipeline.rs | 34 ++++++++++++----------------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 7ed82a8d..3f6dce76 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -35,7 +35,7 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct HttpResponse(Box); +pub struct HttpResponse(Box, &'static HttpResponsePool); impl HttpResponse { #[inline] @@ -96,6 +96,7 @@ impl HttpResponse { } HttpResponseBuilder { + pool: self.1, response: Some(self.0), err: None, cookies: jar, @@ -282,12 +283,19 @@ impl HttpResponse { self.get_mut().write_capacity = cap; } + pub(crate) fn release(self) { + self.1.release(self.0); + } + pub(crate) fn into_parts(self) -> HttpResponseParts { self.0.into_parts() } pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse { - HttpResponse(Box::new(InnerHttpResponse::from_parts(parts))) + HttpResponse( + Box::new(InnerHttpResponse::from_parts(parts)), + HttpResponsePool::get_pool(), + ) } } @@ -332,6 +340,7 @@ impl<'a> Iterator for CookieIter<'a> { /// This type can be used to construct an instance of `HttpResponse` through a /// builder-like pattern. pub struct HttpResponseBuilder { + pool: &'static HttpResponsePool, response: Option>, err: Option, cookies: Option, @@ -622,7 +631,7 @@ impl HttpResponseBuilder { } } response.body = body.into(); - HttpResponse(response) + HttpResponse(response, self.pool) } #[inline] @@ -670,6 +679,7 @@ impl HttpResponseBuilder { /// This method construct new `HttpResponseBuilder` pub fn take(&mut self) -> HttpResponseBuilder { HttpResponseBuilder { + pool: self.pool, response: self.response.take(), err: self.err.take(), cookies: self.cookies.take(), @@ -972,6 +982,7 @@ impl HttpResponsePool { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; HttpResponseBuilder { + pool, response: Some(msg), err: None, cookies: None, @@ -979,6 +990,7 @@ impl HttpResponsePool { } else { let msg = Box::new(InnerHttpResponse::new(status, Body::Empty)); HttpResponseBuilder { + pool, response: Some(msg), err: None, cookies: None, @@ -993,10 +1005,10 @@ impl HttpResponsePool { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; msg.body = body; - HttpResponse(msg) + HttpResponse(msg, pool) } else { let msg = Box::new(InnerHttpResponse::new(status, body)); - HttpResponse(msg) + HttpResponse(msg, pool) } } @@ -1011,23 +1023,20 @@ impl HttpResponsePool { } #[inline] - pub(crate) fn release(resp: HttpResponse) { - let mut inner = resp.0; - POOL.with(|pool| { - let mut p = pool.0.borrow_mut(); - if p.len() < 128 { - inner.headers.clear(); - inner.version = None; - inner.chunked = None; - inner.reason = None; - inner.encoding = None; - inner.connection_type = None; - inner.response_size = 0; - inner.error = None; - inner.write_capacity = MAX_WRITE_BUFFER_SIZE; - p.push_front(inner); - } - }); + fn release(&self, mut inner: Box) { + let mut p = self.0.borrow_mut(); + if p.len() < 128 { + inner.headers.clear(); + inner.version = None; + inner.chunked = None; + inner.reason = None; + inner.encoding = None; + inner.connection_type = None; + inner.response_size = 0; + inner.error = None; + inner.write_capacity = MAX_WRITE_BUFFER_SIZE; + p.push_front(inner); + } } } diff --git a/src/pipeline.rs b/src/pipeline.rs index 9f38f768..19275944 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -13,7 +13,7 @@ use error::Error; use handler::{AsyncResult, AsyncResultItem}; use header::ContentEncoding; use httprequest::HttpRequest; -use httpresponse::{HttpResponse, HttpResponsePool}; +use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Response, Started}; use server::{HttpHandlerTask, Writer, WriterState}; @@ -703,7 +703,8 @@ impl FinishingMiddlewares { info: &mut PipelineInfo, mws: &[Box>], resp: HttpResponse, ) -> PipelineState { if info.count == 0 { - Completed::init(info, resp) + resp.release(); + Completed::init(info) } else { let mut state = FinishingMiddlewares { resp: Some(resp), @@ -741,7 +742,8 @@ impl FinishingMiddlewares { } self.fut = None; if info.count == 0 { - return Some(Completed::init(info, self.resp.take().unwrap())); + self.resp.take().unwrap().release(); + return Some(Completed::init(info)); } info.count -= 1; @@ -750,7 +752,8 @@ impl FinishingMiddlewares { match state { Finished::Done => { if info.count == 0 { - return Some(Completed::init(info, self.resp.take().unwrap())); + self.resp.take().unwrap().release(); + return Some(Completed::init(info)); } } Finished::Future(fut) => { @@ -762,20 +765,19 @@ impl FinishingMiddlewares { } #[derive(Debug)] -struct Completed(PhantomData, PhantomData, Option); +struct Completed(PhantomData, PhantomData); impl Completed { #[inline] - fn init(info: &mut PipelineInfo, resp: HttpResponse) -> PipelineState { + fn init(info: &mut PipelineInfo) -> PipelineState { if let Some(ref err) = info.error { error!("Error occurred during request handling: {}", err); } if info.context.is_none() { - HttpResponsePool::release(resp); PipelineState::None } else { - PipelineState::Completed(Completed(PhantomData, PhantomData, Some(resp))) + PipelineState::Completed(Completed(PhantomData, PhantomData)) } } @@ -783,14 +785,8 @@ impl Completed { fn poll(&mut self, info: &mut PipelineInfo) -> Option> { match info.poll_context() { Ok(Async::NotReady) => None, - Ok(Async::Ready(())) => { - HttpResponsePool::release(self.2.take().unwrap()); - Some(PipelineState::None) - } - Err(_) => { - HttpResponsePool::release(self.2.take().unwrap()); - Some(PipelineState::Error) - } + Ok(Async::Ready(())) => Some(PipelineState::None), + Err(_) => Some(PipelineState::Error), } } } @@ -832,18 +828,16 @@ mod tests { .unwrap() .block_on(lazy(|| { let mut info = PipelineInfo::new(HttpRequest::default()); - let resp = HttpResponse::new(StatusCode::OK); - Completed::<(), Inner<()>>::init(&mut info, resp) + Completed::<(), Inner<()>>::init(&mut info) .is_none() .unwrap(); let req = HttpRequest::default(); let ctx = HttpContext::new(req.clone(), MyActor); let addr = ctx.address(); - let resp = HttpResponse::new(StatusCode::OK); let mut info = PipelineInfo::new(req); info.context = Some(Box::new(ctx)); - let mut state = Completed::<(), Inner<()>>::init(&mut info, resp) + let mut state = Completed::<(), Inner<()>>::init(&mut info) .completed() .unwrap(); From a9425a866bdb7d2be8cd2ab0ab9232541f93258a Mon Sep 17 00:00:00 2001 From: Douman Date: Mon, 25 Jun 2018 19:41:23 +0300 Subject: [PATCH 0456/1635] Fix duplicate tail of StaticFiles with index_file Map from 0.6 to master --- src/fs.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index c5a7de61..574f2cff 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -676,10 +676,6 @@ impl Handler for StaticFiles { // TODO: It'd be nice if there were a good usable URL manipulation // library let mut new_path: String = req.path().to_owned(); - for el in relpath.iter() { - new_path.push_str(&el.to_string_lossy()); - new_path.push('/'); - } if !new_path.ends_with('/') { new_path.push('/'); } @@ -1205,8 +1201,7 @@ mod tests { #[test] fn test_redirect_to_index() { let st = StaticFiles::new(".").index_file("index.html"); - let mut req = HttpRequest::default(); - req.match_info_mut().add_static("tail", "tests"); + let req = TestRequest::default().uri("/tests").finish(); let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); @@ -1216,8 +1211,7 @@ mod tests { "/tests/index.html" ); - let mut req = HttpRequest::default(); - req.match_info_mut().add_static("tail", "tests/"); + let req = TestRequest::default().uri("/tests/").finish(); let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); @@ -1230,16 +1224,15 @@ mod tests { #[test] fn test_redirect_to_index_nested() { - let st = StaticFiles::new(".").index_file("mod.rs"); - let mut req = HttpRequest::default(); - req.match_info_mut().add_static("tail", "src/client"); + let st = StaticFiles::new(".").index_file("Cargo.toml"); + let req = TestRequest::default().uri("/tools/wsload").finish(); let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers().get(header::LOCATION).unwrap(), - "/src/client/mod.rs" + "/tools/wsload/Cargo.toml" ); } From 0f27389e7207fb286ac47cd81f7b6a5e0622cf29 Mon Sep 17 00:00:00 2001 From: ousado Date: Tue, 26 Jun 2018 07:09:12 +0200 Subject: [PATCH 0457/1635] set length of vector to max_bytes (closes #345) (#346) --- src/fs.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fs.rs b/src/fs.rs index 574f2cff..7ac0effa 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -440,11 +440,14 @@ impl Stream for ChunkedReadFile { let max_bytes: usize; max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; let mut buf = Vec::with_capacity(max_bytes); + // safe because memory is initialized/overwritten immediately + unsafe { buf.set_len(max_bytes); } file.seek(io::SeekFrom::Start(offset))?; let nbytes = file.read(buf.as_mut_slice())?; if nbytes == 0 { return Err(io::ErrorKind::UnexpectedEof.into()); } + unsafe { buf.set_len(nbytes); } Ok((file, Bytes::from(buf))) })); self.poll() From 0be54485970fb31d81b5ea2ca28846da1038212e Mon Sep 17 00:00:00 2001 From: Gowee Date: Sat, 30 Jun 2018 20:01:48 +0800 Subject: [PATCH 0458/1635] Properly escape special characters in fs/directory_listing. (#355) --- Cargo.toml | 1 + src/fs.rs | 11 ++++++++--- src/lib.rs | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d4221cbc..a2aea4fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ base64 = "0.9" bitflags = "1.0" failure = "0.1.1" h2 = "0.1" +htmlescape = "0.3" http = "^0.1.5" httparse = "1.2" log = "0.4" diff --git a/src/fs.rs b/src/fs.rs index 7ac0effa..f37134fe 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -15,6 +15,8 @@ use futures::{Async, Future, Poll, Stream}; use futures_cpupool::{CpuFuture, CpuPool}; use mime; use mime_guess::{get_mime_type, guess_mime_type}; +use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; +use htmlescape::encode_minimal as escape_html_entity; use error::Error; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; @@ -505,7 +507,10 @@ fn directory_listing( Err(_) => continue, }; // show file url as relative to static path - let file_url = format!("{}", p.to_string_lossy()); + let file_url = utf8_percent_encode(&p.to_string_lossy(), DEFAULT_ENCODE_SET) + .to_string(); + // " -- " & -- & ' -- ' < -- < > -- > + let file_name = escape_html_entity(&entry.file_name().to_string_lossy()); // if file is a directory, add '/' to the end of the name if let Ok(metadata) = entry.metadata() { @@ -514,14 +519,14 @@ fn directory_listing( body, "
  • {}/
  • ", file_url, - entry.file_name().to_string_lossy() + file_name ); } else { let _ = write!( body, "
  • {}
  • ", file_url, - entry.file_name().to_string_lossy() + file_name ); } } else { diff --git a/src/lib.rs b/src/lib.rs index 92ff1319..85df48dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,6 +103,7 @@ extern crate lazy_static; extern crate futures; extern crate cookie; extern crate futures_cpupool; +extern crate htmlescape; extern crate http as modhttp; extern crate httparse; extern crate language_tags; From 445ea043dd3af69547c7a367aba7b65412b549e1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Jul 2018 23:32:29 +0600 Subject: [PATCH 0459/1635] remove unsafes --- src/fs.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index f37134fe..d2ac4b9b 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -13,10 +13,10 @@ use std::os::unix::fs::MetadataExt; use bytes::Bytes; use futures::{Async, Future, Poll, Stream}; use futures_cpupool::{CpuFuture, CpuPool}; +use htmlescape::encode_minimal as escape_html_entity; use mime; use mime_guess::{get_mime_type, guess_mime_type}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; -use htmlescape::encode_minimal as escape_html_entity; use error::Error; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; @@ -442,14 +442,11 @@ impl Stream for ChunkedReadFile { let max_bytes: usize; max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; let mut buf = Vec::with_capacity(max_bytes); - // safe because memory is initialized/overwritten immediately - unsafe { buf.set_len(max_bytes); } file.seek(io::SeekFrom::Start(offset))?; - let nbytes = file.read(buf.as_mut_slice())?; + let nbytes = file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; if nbytes == 0 { return Err(io::ErrorKind::UnexpectedEof.into()); } - unsafe { buf.set_len(nbytes); } Ok((file, Bytes::from(buf))) })); self.poll() @@ -518,15 +515,13 @@ fn directory_listing( let _ = write!( body, "
  • {}/
  • ", - file_url, - file_name + file_url, file_name ); } else { let _ = write!( body, "
  • {}
  • ", - file_url, - file_name + file_url, file_name ); } } else { @@ -805,6 +800,8 @@ impl HttpRange { #[cfg(test)] mod tests { + use std::fs; + use super::*; use application::App; use http::{header, Method, StatusCode}; @@ -1130,14 +1127,19 @@ mod tests { .unwrap(); let response = srv.execute(request.send()).unwrap(); + { + let te = response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(); + assert_eq!(te, "chunked"); + } - let te = response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(te, "chunked"); + let bytes = srv.execute(response.body()).unwrap(); + let data = Bytes::from(fs::read("tests/test.binary").unwrap()); + assert_eq!(bytes, data); } #[test] From fec6047ddc4179b68831892b12b031cb23248a5c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Jun 2018 10:58:04 +0600 Subject: [PATCH 0460/1635] refactor HttpRequest mutability --- src/application.rs | 218 ++++++------ src/client/mod.rs | 1 + src/client/pipeline.rs | 16 +- src/client/response.rs | 31 +- src/error.rs | 3 - src/extractor.rs | 68 ++-- src/fs.rs | 63 ++-- src/handler.rs | 25 +- src/helpers.rs | 124 ++++--- src/httpmessage.rs | 403 ++++++++++++---------- src/httprequest.rs | 496 ++++++++------------------- src/httpresponse.rs | 35 +- src/info.rs | 103 +++--- src/json.rs | 197 ++++++----- src/lib.rs | 2 + src/middleware/cors.rs | 68 ++-- src/middleware/csrf.rs | 25 +- src/middleware/defaultheaders.rs | 11 +- src/middleware/errhandlers.rs | 19 +- src/middleware/identity.rs | 60 ++-- src/middleware/logger.rs | 44 +-- src/middleware/mod.rs | 9 +- src/middleware/session.rs | 11 +- src/param.rs | 23 +- src/payload.rs | 17 +- src/pipeline.rs | 98 +++--- src/pred.rs | 196 ++++------- src/resource.rs | 45 ++- src/route.rs | 66 ++-- src/router.rs | 567 ++++++++++++++++++------------- src/scope.rs | 228 ++++++------- src/server/error.rs | 25 ++ src/server/h1.rs | 148 +++++--- src/server/h1decoder.rs | 29 +- src/server/h1writer.rs | 61 ++-- src/server/h2.rs | 29 +- src/server/h2writer.rs | 16 +- src/server/helpers.rs | 84 ----- src/server/message.rs | 220 ++++++++++++ src/server/mod.rs | 18 +- src/server/output.rs | 74 ++-- src/server/settings.rs | 28 +- src/server/srv.rs | 26 +- src/server/worker.rs | 5 +- src/test.rs | 72 +++- src/with.rs | 8 +- src/ws/client.rs | 6 +- src/ws/mod.rs | 196 ++++------- tests/test_client.rs | 20 +- tests/test_middleware.rs | 38 +-- tests/test_server.rs | 20 +- 51 files changed, 2239 insertions(+), 2156 deletions(-) create mode 100644 src/server/error.rs create mode 100644 src/server/message.rs diff --git a/src/application.rs b/src/application.rs index 906e8f9a..2cf7a4fa 100644 --- a/src/application.rs +++ b/src/application.rs @@ -7,12 +7,13 @@ use http::{Method, StatusCode}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::Middleware; +use param::Params; use pipeline::{HandlerType, Pipeline, PipelineHandler}; use pred::Predicate; use resource::ResourceHandler; -use router::{Resource, Router}; +use router::{Resource, RouteInfo, Router}; use scope::Scope; -use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, ServerSettings}; +use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request, ServerSettings}; /// Application pub struct HttpApplication { @@ -46,36 +47,34 @@ impl PipelineHandler for Inner { } fn handle( - &self, req: HttpRequest, htype: HandlerType, + &self, req: &HttpRequest, htype: HandlerType, ) -> AsyncResult { match htype { - HandlerType::Normal(idx) => match self.resources[idx].handle(req) { - Ok(result) => result, - Err(req) => match self.default.handle(req) { - Ok(result) => result, - Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), - }, - }, + HandlerType::Normal(idx) => { + if let Some(id) = self.resources[idx].get_route_id(req) { + return self.resources[idx].handle(id, req); + } + } HandlerType::Handler(idx) => match self.handlers[idx] { - PrefixHandlerType::Handler(_, ref hnd) => hnd.handle(req), - PrefixHandlerType::Scope(_, ref hnd, _) => hnd.handle(req), - }, - HandlerType::Default => match self.default.handle(req) { - Ok(result) => result, - Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), + PrefixHandlerType::Handler(_, ref hnd) => return hnd.handle(req), + PrefixHandlerType::Scope(_, ref hnd, _) => return hnd.handle(req), }, + _ => (), + } + if let Some(id) = self.default.get_route_id(req) { + self.default.handle(id, req) + } else { + AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) } } } impl HttpApplication { #[inline] - fn get_handler(&self, req: &mut HttpRequest) -> HandlerType { - if let Some(idx) = self.router.recognize(req) { - HandlerType::Normal(idx) + fn get_handler(&self, req: &Request) -> (RouteInfo, HandlerType) { + if let Some((idx, info)) = self.router.recognize(req) { + (info, HandlerType::Normal(idx)) } else { - req.match_info_mut().set_tail(0); - 'outer: for idx in 0..self.inner.handlers.len() { match self.inner.handlers[idx] { PrefixHandlerType::Handler(ref prefix, _) => { @@ -90,77 +89,68 @@ impl HttpApplication { if m { let prefix_len = (self.inner.prefix + prefix.len()) as u16; - let url = req.url().clone(); - req.set_prefix_len(prefix_len); - req.match_info_mut().set_url(url); - req.match_info_mut().set_tail(prefix_len); - return HandlerType::Handler(idx); + let info = self.router.route_info(req, prefix_len); + return (info, HandlerType::Handler(idx)); } } PrefixHandlerType::Scope(ref pattern, _, ref filters) => { - if let Some(prefix_len) = + if let Some(params) = pattern.match_prefix_with_params(req, self.inner.prefix) { for filter in filters { - if !filter.check(req) { + if !filter.check(req, &self.state) { continue 'outer; } } - - let prefix_len = (self.inner.prefix + prefix_len) as u16; - let url = req.url().clone(); - req.set_prefix_len(prefix_len); - let params = req.match_info_mut(); - params.set_tail(prefix_len); - params.set_url(url); - return HandlerType::Handler(idx); + let info = self + .router + .route_info_params(params, self.inner.prefix as u16); + return (info, HandlerType::Handler(idx)); } } } } - HandlerType::Default + ( + self.router.default_route_info(self.inner.prefix as u16), + HandlerType::Default, + ) } } #[cfg(test)] - pub(crate) fn run(&self, mut req: HttpRequest) -> AsyncResult { - let tp = self.get_handler(&mut req); - self.inner.handle(req, tp) - } + pub(crate) fn run(&self, mut req: Request) -> AsyncResult { + let (info, tp) = self.get_handler(&req); + let req = HttpRequest::new(req, Rc::clone(&self.state), info); - #[cfg(test)] - pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest { - req.with_state(Rc::clone(&self.state), self.router.clone()) + self.inner.handle(&req, tp) } } impl HttpHandler for HttpApplication { type Task = Pipeline>; - fn handle(&self, req: HttpRequest) -> Result>, HttpRequest> { + fn handle(&self, mut msg: Request) -> Result>, Request> { let m = { - let path = req.path(); + let path = msg.path(); path.starts_with(&self.prefix) && (path.len() == self.prefix_len || path.split_at(self.prefix_len).1.starts_with('/')) }; if m { - let mut req2 = - req.clone_with_state(Rc::clone(&self.state), self.router.clone()); - if let Some(ref filters) = self.filters { for filter in filters { - if !filter.check(&mut req2) { - return Err(req); + if !filter.check(&msg, &self.state) { + return Err(msg); } } } - let tp = self.get_handler(&mut req2); + let (info, tp) = self.get_handler(&msg); let inner = Rc::clone(&self.inner); - Ok(Pipeline::new(req2, Rc::clone(&self.middlewares), inner, tp)) + let req = HttpRequest::new(msg, Rc::clone(&self.state), info); + Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner, tp)) } else { - Err(req) + Err(msg) } } } @@ -168,7 +158,6 @@ impl HttpHandler for HttpApplication { struct ApplicationParts { state: S, prefix: String, - settings: ServerSettings, default: Rc>, resources: Vec<(Resource, Option>)>, handlers: Vec>, @@ -219,7 +208,6 @@ where parts: Some(ApplicationParts { state, prefix: "/".to_owned(), - settings: ServerSettings::default(), default: Rc::new(ResourceHandler::default_not_found()), resources: Vec::new(), handlers: Vec::new(), @@ -498,7 +486,7 @@ where /// # extern crate actix_web; /// use actix_web::{App, HttpRequest, HttpResponse, Result}; /// - /// fn index(mut req: HttpRequest) -> Result { + /// fn index(req: &HttpRequest) -> Result { /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); /// Ok(HttpResponse::Ok().into()) @@ -544,7 +532,7 @@ where /// use actix_web::{http, App, HttpRequest, HttpResponse}; /// /// fn main() { - /// let app = App::new().handler("/app", |req: HttpRequest| match *req.method() { + /// let app = App::new().handler("/app", |req: &HttpRequest| match *req.method() { /// http::Method::GET => HttpResponse::Ok(), /// http::Method::POST => HttpResponse::MethodNotAllowed(), /// _ => HttpResponse::NotFound(), @@ -636,8 +624,7 @@ where }; } - let (router, resources) = - Router::new(&prefix, parts.settings.clone(), resources); + let (router, resources) = Router::new(&prefix, resources); let inner = Rc::new(Inner { prefix: prefix_len, @@ -708,7 +695,7 @@ struct BoxedApplication { impl HttpHandler for BoxedApplication { type Task = Box; - fn handle(&self, req: HttpRequest) -> Result { + fn handle(&self, req: Request) -> Result { self.app.handle(req).map(|t| { let task: Self::Task = Box::new(t); task @@ -719,11 +706,7 @@ impl HttpHandler for BoxedApplication { impl IntoHttpHandler for App { type Handler = HttpApplication; - fn into_handler(mut self, settings: ServerSettings) -> HttpApplication { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.settings = settings; - } + fn into_handler(mut self) -> HttpApplication { self.finish() } } @@ -731,11 +714,7 @@ impl IntoHttpHandler for App { impl<'a, S: 'static> IntoHttpHandler for &'a mut App { type Handler = HttpApplication; - fn into_handler(self, settings: ServerSettings) -> HttpApplication { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.settings = settings; - } + fn into_handler(self) -> HttpApplication { self.finish() } } @@ -769,18 +748,18 @@ mod tests { .resource("/test", |r| r.f(|_| HttpResponse::Ok())) .finish(); - let req = TestRequest::with_uri("/test").finish(); + let req = TestRequest::with_uri("/test").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/blah").finish(); + let req = TestRequest::with_uri("/blah").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let app = App::new() .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); - let req = TestRequest::with_uri("/blah").finish(); + let req = TestRequest::with_uri("/blah").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); } @@ -791,7 +770,8 @@ mod tests { .prefix("/test") .resource("/test", |r| r.f(|_| HttpResponse::Ok())) .finish(); - assert!(app.handle(HttpRequest::default()).is_err()); + let ctx = TestRequest::default().request(); + assert!(app.handle(ctx).is_err()); } #[test] @@ -799,8 +779,7 @@ mod tests { let app = App::with_state(10) .resource("/", |r| r.f(|_| HttpResponse::Ok())) .finish(); - let req = - HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); + let req = TestRequest::with_state(10).request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } @@ -811,69 +790,73 @@ mod tests { .prefix("/test") .resource("/blah", |r| r.f(|_| HttpResponse::Ok())) .finish(); - let req = TestRequest::with_uri("/test").finish(); + let req = TestRequest::with_uri("/test").request(); let resp = app.handle(req); assert!(resp.is_ok()); - let req = TestRequest::with_uri("/test/").finish(); + let req = TestRequest::with_uri("/test/").request(); let resp = app.handle(req); assert!(resp.is_ok()); - let req = TestRequest::with_uri("/test/blah").finish(); + let req = TestRequest::with_uri("/test/blah").request(); let resp = app.handle(req); assert!(resp.is_ok()); - let req = TestRequest::with_uri("/testing").finish(); + let req = TestRequest::with_uri("/testing").request(); let resp = app.handle(req); assert!(resp.is_err()); } #[test] fn test_handler() { - let app = App::new().handler("/test", |_| HttpResponse::Ok()).finish(); + let app = App::new() + .handler("/test", |_: &_| HttpResponse::Ok()) + .finish(); - let req = TestRequest::with_uri("/test").finish(); + let req = TestRequest::with_uri("/test").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test/").finish(); + let req = TestRequest::with_uri("/test/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test/app").finish(); + let req = TestRequest::with_uri("/test/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/testapp").finish(); + let req = TestRequest::with_uri("/testapp").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/blah").finish(); + let req = TestRequest::with_uri("/blah").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } #[test] fn test_handler2() { - let app = App::new().handler("test", |_| HttpResponse::Ok()).finish(); + let app = App::new() + .handler("test", |_: &_| HttpResponse::Ok()) + .finish(); - let req = TestRequest::with_uri("/test").finish(); + let req = TestRequest::with_uri("/test").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test/").finish(); + let req = TestRequest::with_uri("/test/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test/app").finish(); + let req = TestRequest::with_uri("/test/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/testapp").finish(); + let req = TestRequest::with_uri("/testapp").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/blah").finish(); + let req = TestRequest::with_uri("/blah").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -882,26 +865,26 @@ mod tests { fn test_handler_with_prefix() { let app = App::new() .prefix("prefix") - .handler("/test", |_| HttpResponse::Ok()) + .handler("/test", |_: &_| HttpResponse::Ok()) .finish(); - let req = TestRequest::with_uri("/prefix/test").finish(); + let req = TestRequest::with_uri("/prefix/test").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/prefix/test/").finish(); + let req = TestRequest::with_uri("/prefix/test/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/prefix/test/app").finish(); + let req = TestRequest::with_uri("/prefix/test/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/prefix/testapp").finish(); + let req = TestRequest::with_uri("/prefix/testapp").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/prefix/blah").finish(); + let req = TestRequest::with_uri("/prefix/blah").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -915,15 +898,19 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/test").method(Method::GET).finish(); + let req = TestRequest::with_uri("/test").method(Method::GET).request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/test").method(Method::POST).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/test").method(Method::HEAD).finish(); + let req = TestRequest::with_uri("/test") + .method(Method::HEAD) + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -932,31 +919,30 @@ mod tests { fn test_handler_prefix() { let app = App::new() .prefix("/app") - .handler("/test", |_| HttpResponse::Ok()) + .handler("/test", |_: &_| HttpResponse::Ok()) .finish(); - let req = TestRequest::with_uri("/test").finish(); + let req = TestRequest::with_uri("/test").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/test").finish(); - let resp = app.run(req.clone()); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - assert_eq!(req.prefix_len(), 9); - - let req = TestRequest::with_uri("/app/test/").finish(); + let req = TestRequest::with_uri("/app/test").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/test/app").finish(); + let req = TestRequest::with_uri("/app/test/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/testapp").finish(); + let req = TestRequest::with_uri("/app/test/app").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/testapp").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/blah").finish(); + let req = TestRequest::with_uri("/app/blah").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -966,7 +952,7 @@ mod tests { let mut srv = TestServer::with_factory(|| { App::new() .filter(pred::Get()) - .handler("/test", |_| HttpResponse::Ok()) + .handler("/test", |_: &_| HttpResponse::Ok()) }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); @@ -985,11 +971,11 @@ mod tests { .resource("/some", |r| r.f(|_| Some("some"))) .finish(); - let req = TestRequest::with_uri("/none").finish(); + let req = TestRequest::with_uri("/none").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/some").finish(); + let req = TestRequest::with_uri("/some").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); diff --git a/src/client/mod.rs b/src/client/mod.rs index 5685e093..a0713fe3 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -35,6 +35,7 @@ pub use self::connector::{ Pause, Resume, }; pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; +pub(crate) use self::pipeline::Pipeline; pub use self::pipeline::{SendRequest, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 2886b42f..fbbce454 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -1,6 +1,6 @@ use bytes::{Bytes, BytesMut}; use futures::sync::oneshot; -use futures::{Async, Future, Poll}; +use futures::{Async, Future, Poll, Stream}; use http::header::CONTENT_ENCODING; use std::time::{Duration, Instant}; use std::{io, mem}; @@ -230,7 +230,7 @@ impl Future for SendRequest { } } -pub(crate) struct Pipeline { +pub struct Pipeline { body: IoBody, body_completed: bool, conn: Option, @@ -315,7 +315,7 @@ impl Pipeline { } #[inline] - pub fn poll(&mut self) -> Poll, PayloadError> { + pub(crate) fn poll(&mut self) -> Poll, PayloadError> { if self.conn.is_none() { return Ok(Async::Ready(None)); } @@ -522,3 +522,13 @@ impl Drop for Pipeline { } } } + +/// Future that resolves to a complete request body. +impl Stream for Box { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + Pipeline::poll(self) + } +} diff --git a/src/client/response.rs b/src/client/response.rs index 28d6f51b..a5c23014 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::{fmt, str}; use bytes::Bytes; @@ -30,23 +31,33 @@ impl Default for ClientMessage { } /// An HTTP Client response -pub struct ClientResponse(ClientMessage, Option>); +pub struct ClientResponse(ClientMessage, RefCell>>); impl HttpMessage for ClientResponse { + type Stream = Box; + /// Get the headers from the response. #[inline] fn headers(&self) -> &HeaderMap { &self.0.headers } + + #[inline] + fn payload(&self) -> Box { + self.1 + .borrow_mut() + .take() + .expect("Payload is already consumed.") + } } impl ClientResponse { pub(crate) fn new(msg: ClientMessage) -> ClientResponse { - ClientResponse(msg, None) + ClientResponse(msg, RefCell::new(None)) } pub(crate) fn set_pipeline(&mut self, pl: Box) { - self.1 = Some(pl); + *self.1.borrow_mut() = Some(pl); } /// Get the HTTP version of this response. @@ -95,20 +106,6 @@ impl fmt::Debug for ClientResponse { } } -/// Future that resolves to a complete request body. -impl Stream for ClientResponse { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - if let Some(ref mut pl) = self.1 { - pl.poll() - } else { - Ok(Async::Ready(None)) - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/error.rs b/src/error.rs index 6d8d3b04..129de76b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -612,9 +612,6 @@ pub enum UrlGenerationError { /// Not all path pattern covered #[fail(display = "Not all path pattern covered")] NotEnoughElements, - /// Router is not available - #[fail(display = "Router is not available")] - RouterNotAvailable, /// URL parse error #[fail(display = "{}", _0)] ParseError(#[cause] UrlParseError), diff --git a/src/extractor.rs b/src/extractor.rs index 5ace390d..1b75f9f1 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -267,12 +267,12 @@ where #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req = req.clone(); + let req2 = req.clone(); let err = Rc::clone(&cfg.ehandler); Box::new( - UrlEncoded::new(req.clone()) + UrlEncoded::new(req) .limit(cfg.limit) - .map_err(move |e| (*err)(e, req)) + .map_err(move |e| (*err)(e, &req2)) .map(Form), ) } @@ -321,7 +321,7 @@ impl fmt::Display for Form { /// ``` pub struct FormConfig { limit: usize, - ehandler: Rc) -> Error>, + ehandler: Rc) -> Error>, } impl FormConfig { @@ -334,7 +334,7 @@ impl FormConfig { /// Set custom error handler pub fn error_handler(&mut self, f: F) -> &mut Self where - F: Fn(UrlencodedError, HttpRequest) -> Error + 'static, + F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, { self.ehandler = Rc::new(f); self @@ -383,9 +383,7 @@ impl FromRequest for Bytes { // check content-type cfg.check_mimetype(req)?; - Ok(Box::new( - MessageBody::new(req.clone()).limit(cfg.limit).from_err(), - )) + Ok(Box::new(MessageBody::new(req).limit(cfg.limit).from_err())) } } @@ -429,7 +427,7 @@ impl FromRequest for String { let encoding = req.encoding()?; Ok(Box::new( - MessageBody::new(req.clone()) + MessageBody::new(req) .limit(cfg.limit) .from_err() .and_then(move |body| { @@ -617,7 +615,6 @@ mod tests { use mime; use resource::ResourceHandler; use router::{Resource, Router}; - use server::ServerSettings; use test::TestRequest; #[derive(Deserialize, Debug, PartialEq)] @@ -628,9 +625,9 @@ mod tests { #[test] fn test_bytes() { let cfg = PayloadConfig::default(); - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); + let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { Async::Ready(s) => { @@ -643,9 +640,9 @@ mod tests { #[test] fn test_string() { let cfg = PayloadConfig::default(); - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); + let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); match String::from_request(&req, &cfg).unwrap().poll().unwrap() { Async::Ready(s) => { @@ -657,13 +654,12 @@ mod tests { #[test] fn test_form() { - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) .finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); let mut cfg = FormConfig::default(); cfg.limit(4096); @@ -677,7 +673,7 @@ mod tests { #[test] fn test_payload_config() { - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); let mut cfg = PayloadConfig::default(); cfg.mimetype(mime::APPLICATION_JSON); assert!(cfg.check_mimetype(&req).is_err()); @@ -712,14 +708,15 @@ mod tests { #[test] fn test_request_extract() { - let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); + let req = TestRequest::with_uri("/name/user1/?id=test").finish(); let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); - let (router, _) = Router::new("", ServerSettings::default(), routes); - assert!(router.recognize(&mut req).is_some()); + let (router, _) = Router::new("", routes); + let info = router.recognize(&req).unwrap().1; + let req = req.with_route_info(info); let s = Path::::from_request(&req, &()).unwrap(); assert_eq!(s.key, "name"); @@ -732,8 +729,9 @@ mod tests { let s = Query::::from_request(&req, &()).unwrap(); assert_eq!(s.id, "test"); - let mut req = TestRequest::with_uri("/name/32/").finish(); - assert!(router.recognize(&mut req).is_some()); + let req = TestRequest::with_uri("/name/32/").finish_with_router(router.clone()); + let info = router.recognize(&req).unwrap().1; + let req = req.with_route_info(info); let s = Path::::from_request(&req, &()).unwrap(); assert_eq!(s.as_ref().key, "name"); @@ -754,24 +752,26 @@ mod tests { resource.name("index"); let mut routes = Vec::new(); routes.push((Resource::new("index", "/{value}/"), Some(resource))); - let (router, _) = Router::new("", ServerSettings::default(), routes); + let (router, _) = Router::new("", routes); - let mut req = TestRequest::with_uri("/32/").finish(); - assert!(router.recognize(&mut req).is_some()); - - assert_eq!(*Path::::from_request(&mut req, &()).unwrap(), 32); + let req = TestRequest::with_uri("/32/").finish_with_router(router.clone()); + let info = router.recognize(&req).unwrap().1; + let req = req.with_route_info(info); + assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); } #[test] fn test_tuple_extract() { - let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); - let (router, _) = Router::new("", ServerSettings::default(), routes); - assert!(router.recognize(&mut req).is_some()); + let (router, _) = Router::new("", routes); + + let mut req = TestRequest::with_uri("/name/user1/?id=test") + .finish_with_router(router.clone()); + let info = router.recognize(&req).unwrap().1; + let req = req.with_route_info(info); let res = match <(Path<(String, String)>,)>::extract(&req).poll() { Ok(Async::Ready(res)) => res, diff --git a/src/fs.rs b/src/fs.rs index d2ac4b9b..66378823 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -2,6 +2,7 @@ use std::fmt::Write; use std::fs::{DirEntry, File, Metadata}; use std::io::{Read, Seek}; +use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -19,7 +20,9 @@ use mime_guess::{get_mime_type, guess_mime_type}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use error::Error; -use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; +use handler::{ + AsyncResult, AsyncResultItem, Handler, Responder, RouteHandler, WrapHandler, +}; use header; use http::{ContentEncoding, Method, StatusCode}; use httpmessage::HttpMessage; @@ -610,7 +613,7 @@ impl StaticFiles { index: None, show_index: false, cpu_pool: pool, - default: Box::new(WrapHandler::new(|_| { + default: Box::new(WrapHandler::new(|_: &_| { HttpResponse::new(StatusCode::NOT_FOUND) })), renderer: Box::new(directory_listing), @@ -657,7 +660,7 @@ impl StaticFiles { impl Handler for StaticFiles { type Result = Result, Error>; - fn handle(&self, req: HttpRequest) -> Self::Result { + fn handle(&self, req: &HttpRequest) -> Self::Result { if !self.accessible { Ok(self.default.handle(req)) } else { @@ -667,7 +670,9 @@ impl Handler for StaticFiles { .map(|tail| PathBuf::from_param(tail.trim_left_matches('/'))) { Some(Ok(path)) => path, - _ => return Ok(self.default.handle(req)), + _ => { + return Ok(self.default.handle(req)); + } }; // full filepath @@ -833,7 +838,8 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(&HttpRequest::default()).unwrap(); + let req = TestRequest::default().finish(); + let resp = file.respond_to(&req).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" @@ -858,7 +864,8 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(&HttpRequest::default()).unwrap(); + let req = TestRequest::default().finish(); + let resp = file.respond_to(&req).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/xml" @@ -882,7 +889,8 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(&HttpRequest::default()).unwrap(); + let req = TestRequest::default().finish(); + let resp = file.respond_to(&req).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "image/png" @@ -916,7 +924,8 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(&HttpRequest::default()).unwrap(); + let req = TestRequest::default().finish(); + let resp = file.respond_to(&req).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "image/png" @@ -940,7 +949,8 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(&HttpRequest::default()).unwrap(); + let req = TestRequest::default().finish(); + let resp = file.respond_to(&req).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/octet-stream" @@ -965,7 +975,8 @@ mod tests { let _f: &mut File = &mut file; } - let resp = file.respond_to(&HttpRequest::default()).unwrap(); + let req = TestRequest::default().finish(); + let resp = file.respond_to(&req).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml" @@ -1136,7 +1147,6 @@ mod tests { .unwrap(); assert_eq!(te, "chunked"); } - let bytes = srv.execute(response.body()).unwrap(); let data = Bytes::from(fs::read("tests/test.binary").unwrap()); assert_eq!(bytes, data); @@ -1178,27 +1188,22 @@ mod tests { fn test_static_files() { let mut st = StaticFiles::new(".").show_files_listing(); st.accessible = false; - let resp = st - .handle(HttpRequest::default()) - .respond_to(&HttpRequest::default()) - .unwrap(); + let req = TestRequest::default().finish(); + let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); st.accessible = true; st.show_index = false; - let resp = st - .handle(HttpRequest::default()) - .respond_to(&HttpRequest::default()) - .unwrap(); + let req = TestRequest::default().finish(); + let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut req = HttpRequest::default(); - req.match_info_mut().add_static("tail", ""); + let req = TestRequest::default().param("tail", "").finish(); st.show_index = true; - let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); + let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -1213,7 +1218,7 @@ mod tests { let st = StaticFiles::new(".").index_file("index.html"); let req = TestRequest::default().uri("/tests").finish(); - let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); + let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( @@ -1222,8 +1227,7 @@ mod tests { ); let req = TestRequest::default().uri("/tests/").finish(); - - let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); + let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( @@ -1234,15 +1238,14 @@ mod tests { #[test] fn test_redirect_to_index_nested() { - let st = StaticFiles::new(".").index_file("Cargo.toml"); - let req = TestRequest::default().uri("/tools/wsload").finish(); - - let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); + let st = StaticFiles::new(".").index_file("mod.rs"); + let req = TestRequest::default().uri("/src/client").finish(); + let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers().get(header::LOCATION).unwrap(), - "/tools/wsload/Cargo.toml" + "/src/client/mod.rs" ); } diff --git a/src/handler.rs b/src/handler.rs index 61dd5694..c7fd6398 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -10,6 +10,7 @@ use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; use resource::ResourceHandler; +use server::Request; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] @@ -18,7 +19,7 @@ pub trait Handler: 'static { type Result: Responder; /// Handle request - fn handle(&self, req: HttpRequest) -> Self::Result; + fn handle(&self, req: &HttpRequest) -> Self::Result; } /// Trait implemented by types that generate responses for clients. @@ -203,12 +204,12 @@ where /// Handler for Fn() impl Handler for F where - F: Fn(HttpRequest) -> R + 'static, + F: Fn(&HttpRequest) -> R + 'static, R: Responder + 'static, { type Result = R; - fn handle(&self, req: HttpRequest) -> R { + fn handle(&self, req: &HttpRequest) -> R { (self)(req) } } @@ -402,9 +403,8 @@ where } } -// /// Trait defines object that could be registered as resource route pub(crate) trait RouteHandler: 'static { - fn handle(&self, req: HttpRequest) -> AsyncResult; + fn handle(&self, &HttpRequest) -> AsyncResult; fn has_default_resource(&self) -> bool { false @@ -443,8 +443,8 @@ where R: Responder + 'static, S: 'static, { - fn handle(&self, req: HttpRequest) -> AsyncResult { - match self.h.handle(req.clone()).respond_to(&req) { + fn handle(&self, req: &HttpRequest) -> AsyncResult { + match self.h.handle(req).respond_to(req) { Ok(reply) => reply.into(), Err(err) => AsyncResult::err(err.into()), } @@ -454,7 +454,7 @@ where /// Async route handler pub(crate) struct AsyncHandler where - H: Fn(HttpRequest) -> F + 'static, + H: Fn(&HttpRequest) -> F + 'static, F: Future + 'static, R: Responder + 'static, E: Into + 'static, @@ -466,7 +466,7 @@ where impl AsyncHandler where - H: Fn(HttpRequest) -> F + 'static, + H: Fn(&HttpRequest) -> F + 'static, F: Future + 'static, R: Responder + 'static, E: Into + 'static, @@ -482,14 +482,15 @@ where impl RouteHandler for AsyncHandler where - H: Fn(HttpRequest) -> F + 'static, + H: Fn(&HttpRequest) -> F + 'static, F: Future + 'static, R: Responder + 'static, E: Into + 'static, S: 'static, { - fn handle(&self, req: HttpRequest) -> AsyncResult { - let fut = (self.h)(req.clone()).map_err(|e| e.into()).then(move |r| { + fn handle(&self, req: &HttpRequest) -> AsyncResult { + let req = req.clone(); + let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| { match r.respond_to(&req) { Ok(reply) => match reply.into().into() { AsyncResultItem::Ok(resp) => Either::A(ok(resp)), diff --git a/src/helpers.rs b/src/helpers.rs index 0b35f047..50a9bcf6 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -36,7 +36,7 @@ use httpresponse::HttpResponse; /// # use actix_web::*; /// use actix_web::http::NormalizePath; /// -/// # fn index(req: HttpRequest) -> HttpResponse { +/// # fn index(req: &HttpRequest) -> HttpResponse { /// # HttpResponse::Ok().into() /// # } /// fn main() { @@ -86,57 +86,41 @@ impl NormalizePath { impl Handler for NormalizePath { type Result = HttpResponse; - 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()) { + fn handle(&self, req: &HttpRequest) -> Self::Result { + 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 req.route().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()) + .finish(); + } + // merge slashes and append trailing slash + if self.append && !p.ends_with('/') { + let p = p.as_ref().to_owned() + "/"; + if req.route().has_route(&p) { let p = if !query.is_empty() { p + "?" + query } else { p }; return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_ref()) + .header(header::LOCATION, p.as_str()) .finish(); } - // merge slashes and append trailing slash - 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()) - .finish(); - } - } + } - // try to remove trailing slash - if p.ends_with('/') { - let p = p.as_ref().trim_right_matches('/'); - if router.has_route(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } else if p.ends_with('/') { - // try to remove trailing slash + // try to remove trailing slash + if p.ends_with('/') { let p = p.as_ref().trim_right_matches('/'); - if router.has_route(p) { + if req.route().has_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -148,22 +132,36 @@ impl Handler for NormalizePath { }.finish(); } } - } - // append trailing slash - 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 if p.ends_with('/') { + // try to remove trailing slash + let p = p.as_ref().trim_right_matches('/'); + if req.route().has_route(p) { + let mut req = HttpResponse::build(self.redirect); + return if !query.is_empty() { + req.header( + header::LOCATION, + (p.to_owned() + "?" + query).as_str(), + ) } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); + req.header(header::LOCATION, p) + }.finish(); } } } + // append trailing slash + if self.append && !req.path().ends_with('/') { + let p = req.path().to_owned() + "/"; + if req.route().has_route(&p) { + let p = if !query.is_empty() { + p + "?" + query + } else { + p + }; + return HttpResponse::build(self.redirect) + .header(header::LOCATION, p.as_str()) + .finish(); + } + } HttpResponse::new(self.not_found) } } @@ -175,7 +173,7 @@ mod tests { use http::{header, Method}; use test::TestRequest; - fn index(_req: HttpRequest) -> HttpResponse { + fn index(_req: &HttpRequest) -> HttpResponse { HttpResponse::new(StatusCode::OK) } @@ -207,9 +205,9 @@ mod tests { ("/resource2/?p1=1&p2=2", "", StatusCode::OK), ]; for (path, target, code) in params { - let req = app.prepare_request(TestRequest::with_uri(path).finish()); + let req = TestRequest::with_uri(path).request(); let resp = app.run(req); - let r = resp.as_msg(); + let r = &resp.as_msg(); assert_eq!(r.status(), code); if !target.is_empty() { assert_eq!( @@ -246,9 +244,9 @@ mod tests { ("/resource2/?p1=1&p2=2", StatusCode::OK), ]; for (path, code) in params { - let req = app.prepare_request(TestRequest::with_uri(path).finish()); + let req = TestRequest::with_uri(path).request(); let resp = app.run(req); - let r = resp.as_msg(); + let r = &resp.as_msg(); assert_eq!(r.status(), code); } } @@ -329,9 +327,9 @@ mod tests { ), ]; for (path, target, code) in params { - let req = app.prepare_request(TestRequest::with_uri(path).finish()); + let req = TestRequest::with_uri(path).request(); let resp = app.run(req); - let r = resp.as_msg(); + let r = &resp.as_msg(); assert_eq!(r.status(), code); if !target.is_empty() { assert_eq!( @@ -509,9 +507,9 @@ mod tests { ), ]; for (path, target, code) in params { - let req = app.prepare_request(TestRequest::with_uri(path).finish()); + let req = TestRequest::with_uri(path).request(); let resp = app.run(req); - let r = resp.as_msg(); + let r = &resp.as_msg(); assert_eq!(r.status(), code); if !target.is_empty() { assert_eq!( diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 5917e7fb..8ed48de2 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -16,12 +16,19 @@ use error::{ use header::Header; use json::JsonBody; use multipart::Multipart; +use payload::Payload; /// Trait that implements general purpose operations on http messages -pub trait HttpMessage { +pub trait HttpMessage: Sized { + /// Type of message payload stream + type Stream: Stream + Sized; + /// Read the message headers. fn headers(&self) -> &HeaderMap; + /// Message payload stream + fn payload(&self) -> Self::Stream; + #[doc(hidden)] /// Get a header fn get_header(&self) -> Option @@ -123,10 +130,7 @@ pub trait HttpMessage { /// } /// # fn main() {} /// ``` - fn body(self) -> MessageBody - where - Self: Stream + Sized, - { + fn body(&self) -> MessageBody { MessageBody::new(self) } @@ -160,10 +164,7 @@ pub trait HttpMessage { /// } /// # fn main() {} /// ``` - fn urlencoded(self) -> UrlEncoded - where - Self: Stream + Sized, - { + fn urlencoded(&self) -> UrlEncoded { UrlEncoded::new(self) } @@ -199,10 +200,7 @@ pub trait HttpMessage { /// } /// # fn main() {} /// ``` - fn json(self) -> JsonBody - where - Self: Stream + Sized, - { + fn json(&self) -> JsonBody { JsonBody::new(self) } @@ -241,45 +239,42 @@ pub trait HttpMessage { /// } /// # fn main() {} /// ``` - fn multipart(self) -> Multipart - where - Self: Stream + Sized, - { + fn multipart(&self) -> Multipart { let boundary = Multipart::boundary(self.headers()); - Multipart::new(boundary, self) + Multipart::new(boundary, self.payload()) } /// Return stream of lines. - fn readlines(self) -> Readlines - where - Self: Stream + Sized, - { + fn readlines(&self) -> Readlines { Readlines::new(self) } } /// Stream to read request line by line. -pub struct Readlines -where - T: HttpMessage + Stream + 'static, -{ - req: T, +pub struct Readlines { + stream: T::Stream, buff: BytesMut, limit: usize, checked_buff: bool, + encoding: EncodingRef, + err: Option, } -impl Readlines -where - T: HttpMessage + Stream + 'static, -{ +impl Readlines { /// Create a new stream to read request line by line. - fn new(req: T) -> Self { + fn new(req: &T) -> Self { + let encoding = match req.encoding() { + Ok(enc) => enc, + Err(err) => return Self::err(req, err.into()), + }; + Readlines { - req, + stream: req.payload(), buff: BytesMut::with_capacity(262_144), limit: 262_144, checked_buff: true, + err: None, + encoding, } } @@ -288,17 +283,28 @@ where self.limit = limit; self } + + fn err(req: &T, err: ReadlinesError) -> Self { + Readlines { + stream: req.payload(), + buff: BytesMut::with_capacity(262_144), + limit: 262_144, + checked_buff: true, + encoding: UTF_8, + err: Some(err), + } + } } -impl Stream for Readlines -where - T: HttpMessage + Stream + 'static, -{ +impl Stream for Readlines { type Item = String; type Error = ReadlinesError; fn poll(&mut self) -> Poll, Self::Error> { - let encoding = self.req.encoding()?; + if let Some(err) = self.err.take() { + return Err(err); + } + // check if there is a newline in the buffer if !self.checked_buff { let mut found: Option = None; @@ -313,13 +319,13 @@ where if ind + 1 > self.limit { return Err(ReadlinesError::LimitOverflow); } - let enc: *const Encoding = encoding as *const Encoding; + let enc: *const Encoding = self.encoding as *const Encoding; let line = if enc == UTF_8 { str::from_utf8(&self.buff.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { - encoding + self.encoding .decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; @@ -328,7 +334,7 @@ where self.checked_buff = true; } // poll req for more bytes - match self.req.poll() { + match self.stream.poll() { Ok(Async::Ready(Some(mut bytes))) => { // check if there is a newline in bytes let mut found: Option = None; @@ -343,13 +349,13 @@ where if ind + 1 > self.limit { return Err(ReadlinesError::LimitOverflow); } - let enc: *const Encoding = encoding as *const Encoding; + let enc: *const Encoding = self.encoding as *const Encoding; let line = if enc == UTF_8 { str::from_utf8(&bytes.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { - encoding + self.encoding .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; @@ -369,13 +375,13 @@ where if self.buff.len() > self.limit { return Err(ReadlinesError::LimitOverflow); } - let enc: *const Encoding = encoding as *const Encoding; + let enc: *const Encoding = self.encoding as *const Encoding; let line = if enc == UTF_8 { str::from_utf8(&self.buff) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { - encoding + self.encoding .decode(&self.buff, DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; @@ -388,19 +394,36 @@ where } /// Future that resolves to a complete http message body. -pub struct MessageBody { +pub struct MessageBody { limit: usize, - req: Option, + length: Option, + stream: Option, + err: Option, fut: Option>>, } -impl MessageBody { - /// Create `RequestBody` for request. - pub fn new(req: T) -> MessageBody { +impl MessageBody { + /// Create `MessageBody` for request. + pub fn new(req: &T) -> MessageBody { + let mut len = None; + if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = l.to_str() { + if let Ok(l) = s.parse::() { + len = Some(l) + } else { + return Self::err(PayloadError::UnknownLength); + } + } else { + return Self::err(PayloadError::UnknownLength); + } + } + MessageBody { limit: 262_144, - req: Some(req), + length: len, + stream: Some(req.payload()), fut: None, + err: None, } } @@ -409,68 +432,114 @@ impl MessageBody { self.limit = limit; self } + + fn err(e: PayloadError) -> Self { + MessageBody { + stream: None, + limit: 262_144, + fut: None, + err: Some(e), + length: None, + } + } } impl Future for MessageBody where - T: HttpMessage + Stream + 'static, + T: HttpMessage + 'static, { type Item = Bytes; type Error = PayloadError; fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(PayloadError::Overflow); - } - } else { - return Err(PayloadError::UnknownLength); - } - } else { - return Err(PayloadError::UnknownLength); - } - } - - // future - let limit = self.limit; - self.fut = Some(Box::new( - req.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .map(|body| body.freeze()), - )); + if let Some(ref mut fut) = self.fut { + return fut.poll(); } - self.fut - .as_mut() - .expect("UrlEncoded could not be used second time") - .poll() + if let Some(err) = self.err.take() { + return Err(err); + } + + if let Some(len) = self.length.take() { + if len > self.limit { + return Err(PayloadError::Overflow); + } + } + + // future + let limit = self.limit; + self.fut = Some(Box::new( + self.stream + .take() + .expect("Can not be used second time") + .from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(PayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .map(|body| body.freeze()), + )); + self.poll() } } /// Future that resolves to a parsed urlencoded values. -pub struct UrlEncoded { - req: Option, +pub struct UrlEncoded { + stream: Option, limit: usize, + length: Option, + encoding: EncodingRef, + err: Option, fut: Option>>, } -impl UrlEncoded { +impl UrlEncoded { /// Create a new future to URL encode a request - pub fn new(req: T) -> UrlEncoded { + pub fn new(req: &T) -> UrlEncoded { + // check content type + if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { + return Self::err(UrlencodedError::ContentType); + } + let encoding = match req.encoding() { + Ok(enc) => enc, + Err(_) => return Self::err(UrlencodedError::ContentType), + }; + + let mut len = None; + if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = l.to_str() { + if let Ok(l) = s.parse::() { + len = Some(l) + } else { + return Self::err(UrlencodedError::UnknownLength); + } + } else { + return Self::err(UrlencodedError::UnknownLength); + } + }; + UrlEncoded { - req: Some(req), + encoding, + stream: Some(req.payload()), + limit: 262_144, + length: len, + fut: None, + err: None, + } + } + + fn err(e: UrlencodedError) -> Self { + UrlEncoded { + stream: None, limit: 262_144, fut: None, + err: Some(e), + length: None, + encoding: UTF_8, } } @@ -483,66 +552,58 @@ impl UrlEncoded { impl Future for UrlEncoded where - T: HttpMessage + Stream + 'static, + T: HttpMessage + 'static, U: DeserializeOwned + 'static, { type Item = U; type Error = UrlencodedError; fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > 262_144 { - return Err(UrlencodedError::Overflow); - } - } else { - return Err(UrlencodedError::UnknownLength); - } - } else { - return Err(UrlencodedError::UnknownLength); - } - } - - // check content type - if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { - return Err(UrlencodedError::ContentType); - } - let encoding = req.encoding().map_err(|_| UrlencodedError::ContentType)?; - - // future - let limit = self.limit; - let fut = req - .from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(move |body| { - let enc: *const Encoding = encoding as *const Encoding; - if enc == UTF_8 { - serde_urlencoded::from_bytes::(&body) - .map_err(|_| UrlencodedError::Parse) - } else { - let body = encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| UrlencodedError::Parse)?; - serde_urlencoded::from_str::(&body) - .map_err(|_| UrlencodedError::Parse) - } - }); - self.fut = Some(Box::new(fut)); + if let Some(ref mut fut) = self.fut { + return fut.poll(); } - self.fut - .as_mut() + if let Some(err) = self.err.take() { + return Err(err); + } + + // payload size + let limit = self.limit; + if let Some(len) = self.length.take() { + if len > limit { + return Err(UrlencodedError::Overflow); + } + } + + // future + let encoding = self.encoding; + let fut = self + .stream + .take() .expect("UrlEncoded could not be used second time") - .poll() + .from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(UrlencodedError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .and_then(move |body| { + if (encoding as *const Encoding) == UTF_8 { + serde_urlencoded::from_bytes::(&body) + .map_err(|_| UrlencodedError::Parse) + } else { + let body = encoding + .decode(&body, DecoderTrap::Strict) + .map_err(|_| UrlencodedError::Parse)?; + serde_urlencoded::from_str::(&body) + .map_err(|_| UrlencodedError::Parse) + } + }); + self.fut = Some(Box::new(fut)); + self.poll() } } @@ -566,7 +627,7 @@ mod tests { TestRequest::with_header("content-type", "application/json; charset=utf=8") .finish(); assert_eq!(req.content_type(), "application/json"); - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); assert_eq!(req.content_type(), ""); } @@ -574,7 +635,7 @@ mod tests { fn test_mime_type() { let req = TestRequest::with_header("content-type", "application/json").finish(); assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); assert_eq!(req.mime_type().unwrap(), None); let req = TestRequest::with_header("content-type", "application/json; charset=utf-8") @@ -596,7 +657,7 @@ mod tests { #[test] fn test_encoding() { - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); let req = TestRequest::with_header("content-type", "application/json").finish(); @@ -626,27 +687,19 @@ mod tests { #[test] fn test_chunked() { - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); assert!(!req.chunked().unwrap()); let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); assert!(req.chunked().unwrap()); - let mut headers = HeaderMap::new(); - let hdr = Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"); - - headers.insert( - header::TRANSFER_ENCODING, - header::HeaderValue::from_shared(hdr).unwrap(), - ); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + let req = TestRequest::default() + .header( + header::TRANSFER_ENCODING, + Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), + ) + .finish(); assert!(req.chunked().is_err()); } @@ -716,9 +769,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) .finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); let result = req.urlencoded::().poll().ok().unwrap(); assert_eq!( @@ -732,9 +784,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ).header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) .finish(); - req.payload_mut() - .unread_data(Bytes::from_static(b"hello=world")); let result = req.urlencoded().poll().ok().unwrap(); assert_eq!( @@ -759,16 +810,17 @@ mod tests { _ => unreachable!("error"), } - let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static(b"test")); + let req = TestRequest::default() + .set_payload(Bytes::from_static(b"test")) + .finish(); match req.body().poll().ok().unwrap() { Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), _ => unreachable!("error"), } - let mut req = HttpRequest::default(); - req.payload_mut() - .unread_data(Bytes::from_static(b"11111111111111")); + let mut req = TestRequest::default() + .set_payload(Bytes::from_static(b"11111111111111")) + .finish(); match req.body().limit(5).poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), @@ -777,13 +829,14 @@ mod tests { #[test] fn test_readlines() { - let mut req = HttpRequest::default(); - req.payload_mut().unread_data(Bytes::from_static( - b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ - industry. Lorem Ipsum has been the industry's standard dummy\n\ - Contrary to popular belief, Lorem Ipsum is not simply random text.", - )); - let mut r = Readlines::new(req); + let req = TestRequest::default() + .set_payload(Bytes::from_static( + b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ + industry. Lorem Ipsum has been the industry's standard dummy\n\ + Contrary to popular belief, Lorem Ipsum is not simply random text.", + )) + .finish(); + let mut r = Readlines::new(&req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/httprequest.rs b/src/httprequest.rs index ffd13919..3cfcb68a 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,6 +1,8 @@ //! HTTP Request message related code. +use std::cell::{Cell, Ref, RefCell, RefMut}; use std::collections::HashMap; use std::net::SocketAddr; +use std::ops::Deref; use std::rc::Rc; use std::{cmp, fmt, io, str}; @@ -22,261 +24,158 @@ use httpresponse::{HttpResponse, HttpResponseBuilder}; use info::ConnectionInfo; use param::Params; use payload::Payload; -use router::{Resource, Router}; -use server::helpers::SharedHttpInnerMessage; +use router::{Resource, RouteInfo, Router}; +use server::message::{MessageFlags, Request}; use uri::Url as InnerUrl; -bitflags! { - pub(crate) struct MessageFlags: u8 { - const KEEPALIVE = 0b0000_0010; - } -} - -pub struct HttpInnerMessage { - pub version: Version, - pub method: Method, - pub(crate) url: InnerUrl, - pub(crate) flags: MessageFlags, - pub headers: HeaderMap, - pub extensions: Extensions, - pub params: Params, - pub addr: Option, - pub payload: Option, - pub prefix: u16, - resource: RouterResource, -} - struct Query(HashMap); struct Cookies(Vec>); -struct Info(ConnectionInfo); #[derive(Debug, Copy, Clone, PartialEq)] -enum RouterResource { +pub(crate) enum RouterResource { Notset, Normal(u16), } -impl Default for HttpInnerMessage { - fn default() -> HttpInnerMessage { - HttpInnerMessage { - method: Method::GET, - url: InnerUrl::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - flags: MessageFlags::empty(), - params: Params::new(), - addr: None, - payload: None, - extensions: Extensions::new(), - prefix: 0, - resource: RouterResource::Notset, +/// An HTTP Request +pub struct HttpRequest { + req: Rc, + state: Rc, + route: RouteInfo, +} + +impl HttpMessage for HttpRequest { + type Stream = Payload; + + #[inline] + fn headers(&self) -> &HeaderMap { + self.req.headers() + } + + #[inline] + fn payload(&self) -> Payload { + if let Some(payload) = self.req.inner.payload.borrow_mut().take() { + payload + } else { + Payload::empty() } } } -impl HttpInnerMessage { - /// Checks if a connection should be kept alive. - #[inline] - pub fn keep_alive(&self) -> bool { - self.flags.contains(MessageFlags::KEEPALIVE) - } +impl Deref for HttpRequest { + type Target = Request; - #[inline] - pub(crate) fn reset(&mut self) { - self.headers.clear(); - self.extensions.clear(); - self.params.clear(); - self.addr = None; - self.flags = MessageFlags::empty(); - self.payload = None; - self.prefix = 0; - self.resource = RouterResource::Notset; - } -} - -/// An HTTP Request -pub struct HttpRequest(SharedHttpInnerMessage, Option>, Option); - -impl HttpRequest<()> { - /// Construct a new Request. - #[inline] - pub(crate) fn new( - method: Method, uri: Uri, version: Version, headers: HeaderMap, - payload: Option, - ) -> HttpRequest { - let url = InnerUrl::new(uri); - HttpRequest( - SharedHttpInnerMessage::from_message(HttpInnerMessage { - method, - url, - version, - headers, - payload, - params: Params::new(), - extensions: Extensions::new(), - addr: None, - prefix: 0, - flags: MessageFlags::empty(), - resource: RouterResource::Notset, - }), - None, - None, - ) - } - - #[inline(always)] - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest { - HttpRequest(msg, None, None) - } - - #[inline] - /// Construct new http request with state. - pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { - HttpRequest(self.0, Some(state), Some(router)) - } - - pub(crate) fn clone_with_state( - &self, state: Rc, router: Router, - ) -> HttpRequest { - HttpRequest(self.0.clone(), Some(state), Some(router)) - } -} - -impl HttpMessage for HttpRequest { - #[inline] - fn headers(&self) -> &HeaderMap { - &self.as_ref().headers + fn deref(&self) -> &Request { + self.req.as_ref() } } impl HttpRequest { + #[inline] + pub(crate) fn new(req: Request, state: Rc, route: RouteInfo) -> HttpRequest { + HttpRequest { + state, + route, + req: Rc::new(req), + } + } + #[inline] /// Construct new http request with state. - pub fn change_state(&self, state: Rc) -> HttpRequest { - HttpRequest(self.0.clone(), Some(state), self.2.clone()) + pub(crate) fn with_state(&self, state: Rc) -> HttpRequest { + HttpRequest { + state, + req: self.req.clone(), + route: self.route.clone(), + } } #[inline] - /// Construct new http request without state. - pub fn drop_state(&self) -> HttpRequest { - HttpRequest(self.0.clone(), None, self.2.clone()) - } - - /// get mutable reference for inner message - /// mutable reference should not be returned as result for request's method - #[inline] - pub(crate) fn as_mut(&mut self) -> &mut HttpInnerMessage { - self.0.get_mut() - } - - #[inline] - fn as_ref(&self) -> &HttpInnerMessage { - self.0.get_ref() + /// Construct new http request with new RouteInfo. + pub(crate) fn with_route_info(&self, route: RouteInfo) -> HttpRequest { + HttpRequest { + route, + req: self.req.clone(), + state: self.state.clone(), + } } /// Shared application state #[inline] pub fn state(&self) -> &S { - self.1.as_ref().unwrap() + &self.state + } + + #[inline] + /// Server request + pub fn request(&self) -> &Request { + &self.req } /// Request extensions #[inline] - pub fn extensions(&self) -> &Extensions { - &self.as_ref().extensions + pub fn extensions(&self) -> Ref { + self.req.extensions() } /// Mutable reference to a the request's extensions #[inline] - pub fn extensions_mut(&mut self) -> &mut Extensions { - &mut self.as_mut().extensions + pub fn extensions_mut(&self) -> RefMut { + self.req.extensions_mut() } /// Default `CpuPool` #[inline] #[doc(hidden)] pub fn cpu_pool(&self) -> &CpuPool { - self.router() - .expect("HttpRequest has to have Router instance") - .server_settings() - .cpu_pool() + self.req.server_settings().cpu_pool() } + #[inline] /// Create http response pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse { - if let Some(router) = self.router() { - router.server_settings().get_response(status, body) - } else { - HttpResponse::with_body(status, body) - } + self.req.server_settings().get_response(status, body) } + #[inline] /// Create http response builder pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder { - if let Some(router) = self.router() { - router.server_settings().get_response_builder(status) - } else { - HttpResponse::build(status) - } - } - - #[doc(hidden)] - pub fn prefix_len(&self) -> u16 { - self.as_ref().prefix as u16 - } - - #[doc(hidden)] - pub fn set_prefix_len(&mut self, len: u16) { - self.as_mut().prefix = len; + self.req.server_settings().get_response_builder(status) } /// Read the Request Uri. #[inline] pub fn uri(&self) -> &Uri { - self.as_ref().url.uri() + self.request().inner.url.uri() } /// Read the Request method. #[inline] pub fn method(&self) -> &Method { - &self.as_ref().method + &self.request().inner.method } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.as_ref().version - } - - ///Returns mutable Request's headers. - /// - ///This is intended to be used by middleware. - #[cfg(test)] - pub(crate) fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.as_mut().headers + self.request().inner.version } /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.as_ref().url.path() + self.request().inner.url.path() } #[inline] pub(crate) fn url(&self) -> &InnerUrl { - &self.as_ref().url + &self.request().inner.url } - /// Get *ConnectionInfo* for correct request. - pub fn connection_info(&self) -> &ConnectionInfo { - if self.extensions().get::().is_none() { - let mut req = self.clone(); - req.as_mut() - .extensions - .insert(Info(ConnectionInfo::new(self))); - } - &self.extensions().get::().unwrap().0 + /// Get *ConnectionInfo* for the correct request. + #[inline] + pub fn connection_info(&self) -> Ref { + self.request().connection_info() } /// Generate url for named resource @@ -306,22 +205,7 @@ impl HttpRequest { U: IntoIterator, I: AsRef, { - if self.router().is_none() { - Err(UrlGenerationError::RouterNotAvailable) - } else { - let path = self.router().unwrap().resource_path(name, elements)?; - if path.starts_with('/') { - let conn = self.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) - } else { - Ok(Url::parse(&path)?) - } - } + self.route.url_for(&self, name, elements) } /// Generate url for named resource @@ -333,25 +217,16 @@ impl HttpRequest { self.url_for(name, &NO_PARAMS) } - /// This method returns reference to current `Router` object. + /// This method returns reference to current `RouteInfo` object. #[inline] - pub fn router(&self) -> Option<&Router> { - self.2.as_ref() + pub fn route(&self) -> &RouteInfo { + &self.route } /// This method returns reference to matched `Resource` object. #[inline] pub fn resource(&self) -> Option<&Resource> { - if let Some(ref router) = self.2 { - if let RouterResource::Normal(idx) = self.as_ref().resource { - return Some(router.get_resource(idx as usize)); - } - } - None - } - - pub(crate) fn set_resource(&mut self, res: usize) { - self.as_mut().resource = RouterResource::Normal(res as u16); + self.route.resource() } /// Peer socket address @@ -363,25 +238,20 @@ impl HttpRequest { /// be used. #[inline] pub fn peer_addr(&self) -> Option { - self.as_ref().addr - } - - #[inline] - pub(crate) fn set_peer_addr(&mut self, addr: Option) { - self.as_mut().addr = addr; + self.request().inner.addr } /// url query parameters. - pub fn query(&self) -> &HashMap { + pub fn query(&self) -> Ref> { if self.extensions().get::().is_none() { let mut query = HashMap::new(); for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { query.insert(key.as_ref().to_string(), val.to_string()); } let mut req = self.clone(); - req.as_mut().extensions.insert(Query(query)); + self.extensions_mut().insert(Query(query)); } - &self.extensions().get::().unwrap().0 + Ref::map(self.extensions(), |ext| &ext.get::().unwrap().0) } /// The query string in the URL. @@ -397,12 +267,12 @@ impl HttpRequest { } /// Load request cookies. - pub fn cookies(&self) -> Result<&Vec>, CookieParseError> { - if self.extensions().get::().is_none() { + #[inline] + pub fn cookies(&self) -> Result>>, CookieParseError> { + if self.extensions().get::().is_none() { let mut req = self.clone(); - let msg = req.as_mut(); let mut cookies = Vec::new(); - for hdr in msg.headers.get_all(header::COOKIE) { + for hdr in self.request().inner.headers.get_all(header::COOKIE) { let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; for cookie_str in s.split(';').map(|s| s.trim()) { if !cookie_str.is_empty() { @@ -410,17 +280,20 @@ impl HttpRequest { } } } - msg.extensions.insert(Cookies(cookies)); + self.extensions_mut().insert(Cookies(cookies)); } - Ok(&self.extensions().get::().unwrap().0) + Ok(Ref::map(self.extensions(), |ext| { + &ext.get::().unwrap().0 + })) } /// Return request cookie. - pub fn cookie(&self, name: &str) -> Option<&Cookie> { + #[inline] + pub fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { - for cookie in cookies { + for cookie in cookies.iter() { if cookie.name() == name { - return Some(cookie); + return Some(cookie.to_owned()); } } } @@ -441,68 +314,31 @@ impl HttpRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Params { - &self.as_ref().params - } - - /// Get mutable reference to request's Params. - #[inline] - pub(crate) fn match_info_mut(&mut self) -> &mut Params { - &mut self.as_mut().params - } - - /// Checks if a connection should be kept alive. - pub fn keep_alive(&self) -> bool { - self.as_ref().flags.contains(MessageFlags::KEEPALIVE) + &self.route.match_info() } /// Check if request requires connection upgrade pub(crate) fn upgrade(&self) -> bool { - 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.as_ref().method == Method::CONNECT + self.request().upgrade() } /// Set read buffer capacity /// /// Default buffer capacity is 32Kb. pub fn set_read_buffer_capacity(&mut self, cap: usize) { - if let Some(ref mut payload) = self.as_mut().payload { + if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() { payload.set_read_buffer_capacity(cap) } } - - #[cfg(test)] - pub(crate) fn payload(&mut self) -> &Payload { - let msg = self.as_mut(); - if msg.payload.is_none() { - msg.payload = Some(Payload::empty()); - } - msg.payload.as_ref().unwrap() - } - - #[cfg(test)] - pub(crate) fn payload_mut(&mut self) -> &mut Payload { - let msg = self.as_mut(); - if msg.payload.is_none() { - msg.payload = Some(Payload::empty()); - } - msg.payload.as_mut().unwrap() - } -} - -impl Default for HttpRequest<()> { - /// Construct default request - fn default() -> HttpRequest { - HttpRequest(SharedHttpInnerMessage::default(), None, None) - } } impl Clone for HttpRequest { fn clone(&self) -> HttpRequest { - HttpRequest(self.0.clone(), self.1.clone(), self.2.clone()) + HttpRequest { + req: self.req.clone(), + state: self.state.clone(), + route: self.route.clone(), + } } } @@ -516,76 +352,23 @@ impl FromRequest for HttpRequest { } } -impl Stream for HttpRequest { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, PayloadError> { - let msg = self.as_mut(); - if msg.payload.is_none() { - Ok(Async::Ready(None)) - } else { - msg.payload.as_mut().unwrap().poll() - } - } -} - -impl io::Read for HttpRequest { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - if self.as_mut().payload.is_some() { - match self.as_mut().payload.as_mut().unwrap().poll() { - Ok(Async::Ready(Some(mut b))) => { - let i = cmp::min(b.len(), buf.len()); - buf.copy_from_slice(&b.split_to(i)[..i]); - - if !b.is_empty() { - self.as_mut().payload.as_mut().unwrap().unread_data(b); - } - - if i < buf.len() { - match self.read(&mut buf[i..]) { - Ok(n) => Ok(i + n), - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(i), - Err(e) => Err(e), - } - } else { - Ok(i) - } - } - Ok(Async::Ready(None)) => Ok(0), - Ok(Async::NotReady) => { - Err(io::Error::new(io::ErrorKind::WouldBlock, "Not ready")) - } - Err(e) => Err(io::Error::new( - io::ErrorKind::Other, - failure::Error::from(e).compat(), - )), - } - } else { - Ok(0) - } - } -} - -impl AsyncRead for HttpRequest {} - impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = writeln!( f, "\nHttpRequest {:?} {}:{}", - self.as_ref().version, - self.as_ref().method, + self.version(), + self.method(), self.path() ); if !self.query_string().is_empty() { let _ = writeln!(f, " query: ?{:?}", self.query_string()); } if !self.match_info().is_empty() { - let _ = writeln!(f, " params: {:?}", self.as_ref().params); + let _ = writeln!(f, " params: {:?}", self.match_info()); } let _ = writeln!(f, " headers:"); - for (key, val) in self.as_ref().headers.iter() { + for (key, val) in self.headers().iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } res @@ -597,7 +380,6 @@ mod tests { use super::*; use resource::ResourceHandler; use router::Resource; - use server::ServerSettings; use test::TestRequest; #[test] @@ -609,7 +391,7 @@ mod tests { #[test] fn test_no_request_cookies() { - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); assert!(req.cookies().unwrap().is_empty()); } @@ -648,33 +430,27 @@ mod tests { #[test] fn test_request_match_info() { - let mut req = TestRequest::with_uri("/value/?id=test").finish(); - let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let mut routes = Vec::new(); routes.push((Resource::new("index", "/{key}/"), Some(resource))); - let (router, _) = Router::new("", ServerSettings::default(), routes); - assert!(router.recognize(&mut req).is_some()); + let (router, _) = Router::new("", routes); - assert_eq!(req.match_info().get("key"), Some("value")); + let req = TestRequest::with_uri("/value/?id=test").finish(); + let info = router.recognize(&req).unwrap().1; + assert_eq!(info.match_info().get("key"), Some("value")); } #[test] fn test_url_for() { - let req2 = HttpRequest::default(); - assert_eq!( - req2.url_for("unknown", &["test"]), - Err(UrlGenerationError::RouterNotAvailable) - ); - let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; - let (router, _) = Router::new("/", ServerSettings::default(), routes); - assert!(router.has_route("/user/test.html")); - assert!(!router.has_route("/test/unknown")); + let (router, _) = Router::new("/", routes); + let info = router.default_route_info(0); + assert!(info.has_route("/user/test.html")); + assert!(!info.has_route("/test/unknown")); let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") .finish_with_router(router); @@ -696,16 +472,16 @@ mod tests { #[test] fn test_url_for_with_prefix() { - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish(); - let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))]; - let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); - assert!(router.has_route("/user/test.html")); - assert!(!router.has_route("/prefix/user/test.html")); + let (router, _) = Router::new("/prefix/", routes); + let info = router.default_route_info(0); + assert!(info.has_route("/user/test.html")); + assert!(!info.has_route("/prefix/user/test.html")); - let req = req.with_state(Rc::new(()), router); + let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") + .finish_with_router(router); let url = req.url_for("index", &["test"]); assert_eq!( url.ok().unwrap().as_str(), @@ -715,16 +491,17 @@ mod tests { #[test] fn test_url_for_static() { - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish(); - let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![(Resource::new("index", "/index.html"), Some(resource))]; - let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); - assert!(router.has_route("/index.html")); - assert!(!router.has_route("/prefix/index.html")); + let (router, _) = Router::new("/prefix/", routes); + let info = router.default_route_info(0); + assert!(info.has_route("/index.html")); + assert!(!info.has_route("/prefix/index.html")); - let req = req.with_state(Rc::new(()), router); + let req = TestRequest::default() + .header(header::HOST, "www.rust-lang.org") + .finish_with_router(router); let url = req.url_for_static("index"); assert_eq!( url.ok().unwrap().as_str(), @@ -734,18 +511,17 @@ mod tests { #[test] fn test_url_for_external() { - let req = HttpRequest::default(); - let mut resource = ResourceHandler::<()>::default(); resource.name("index"); let routes = vec![( Resource::external("youtube", "https://youtube.com/watch/{video_id}"), None, )]; - let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); - assert!(!router.has_route("https://youtube.com/watch/unknown")); + let router = Router::new::<()>("", routes).0; + let info = router.default_route_info(0); + assert!(!info.has_route("https://youtube.com/watch/unknown")); - let req = req.with_state(Rc::new(()), router); + let req = TestRequest::default().finish_with_router(router); let url = req.url_for("youtube", &["oHg5SJYRHA0"]); assert_eq!( url.ok().unwrap().as_str(), diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 3f6dce76..71db8767 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -553,10 +553,10 @@ impl HttpResponseBuilder { /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse, Result}; /// - /// fn index(req: HttpRequest) -> HttpResponse { + /// fn index(req: &HttpRequest) -> HttpResponse { /// let mut builder = HttpResponse::Ok(); /// - /// if let Some(cookie) = req.cookie("name") { + /// if let Some(ref cookie) = req.cookie("name") { /// builder.del_cookie(cookie); /// } /// @@ -860,13 +860,9 @@ impl<'a> From<&'a ClientResponse> for HttpResponseBuilder { impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { fn from(req: &'a HttpRequest) -> HttpResponseBuilder { - if let Some(router) = req.router() { - router - .server_settings() - .get_response_builder(StatusCode::OK) - } else { - HttpResponse::Ok() - } + req.request() + .server_settings() + .get_response_builder(StatusCode::OK) } } @@ -1050,6 +1046,8 @@ mod tests { use std::str::FromStr; use time::Duration; + use test::TestRequest; + #[test] fn test_debug() { let resp = HttpResponse::Ok() @@ -1062,17 +1060,10 @@ mod tests { #[test] fn test_response_cookies() { - let mut headers = HeaderMap::new(); - headers.insert(COOKIE, HeaderValue::from_static("cookie1=value1")); - headers.insert(COOKIE, HeaderValue::from_static("cookie2=value2")); - - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + let req = TestRequest::default() + .header(COOKIE, "cookie1=value1") + .header(COOKIE, "cookie2=value2") + .finish(); let cookies = req.cookies().unwrap(); let resp = HttpResponse::Ok() @@ -1094,7 +1085,7 @@ mod tests { .map(|v| v.to_str().unwrap().to_owned()) .collect(); val.sort(); - assert!(val[0].starts_with("cookie2=; Max-Age=0;")); + 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" @@ -1208,7 +1199,7 @@ mod tests { #[test] fn test_into_response() { - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); let resp: HttpResponse = "test".into(); assert_eq!(resp.status(), StatusCode::OK); diff --git a/src/info.rs b/src/info.rs index dad10b64..5d43b8e9 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,14 +1,17 @@ +use std::rc::Rc; use std::str::FromStr; use http::header::{self, HeaderName}; use httpmessage::HttpMessage; use httprequest::HttpRequest; +use server::Request; -const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; -const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST"; -const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; +const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; +const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; +const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; /// `HttpRequest` connection information +#[derive(Clone, Default)] pub struct ConnectionInfo { scheme: String, host: String, @@ -19,7 +22,7 @@ pub struct ConnectionInfo { impl ConnectionInfo { /// Create *ConnectionInfo* instance for a request. #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - pub fn new(req: &HttpRequest) -> ConnectionInfo { + pub fn update(&mut self, req: &Request) { let mut host = None; let mut scheme = None; let mut remote = None; @@ -56,7 +59,7 @@ impl ConnectionInfo { if scheme.is_none() { if let Some(h) = req .headers() - .get(HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) + .get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) { if let Ok(h) = h.to_str() { scheme = h.split(',').next().map(|v| v.trim()); @@ -64,12 +67,8 @@ impl ConnectionInfo { } if scheme.is_none() { scheme = req.uri().scheme_part().map(|a| a.as_str()); - if scheme.is_none() { - if let Some(router) = req.router() { - if router.server_settings().secure() { - scheme = Some("https") - } - } + if scheme.is_none() && req.server_settings().secure() { + scheme = Some("https") } } } @@ -78,7 +77,7 @@ impl ConnectionInfo { if host.is_none() { if let Some(h) = req .headers() - .get(HeaderName::from_str(X_FORWARDED_HOST).unwrap()) + .get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) { if let Ok(h) = h.to_str() { host = h.split(',').next().map(|v| v.trim()); @@ -91,9 +90,7 @@ impl ConnectionInfo { if host.is_none() { host = req.uri().authority_part().map(|a| a.as_str()); if host.is_none() { - if let Some(router) = req.router() { - host = Some(router.server_settings().host()); - } + host = Some(req.server_settings().host()); } } } @@ -103,7 +100,7 @@ impl ConnectionInfo { if remote.is_none() { if let Some(h) = req .headers() - .get(HeaderName::from_str(X_FORWARDED_FOR).unwrap()) + .get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) { if let Ok(h) = h.to_str() { remote = h.split(',').next().map(|v| v.trim()); @@ -115,12 +112,10 @@ impl ConnectionInfo { } } - ConnectionInfo { - scheme: scheme.unwrap_or("http").to_owned(), - host: host.unwrap_or("localhost").to_owned(), - remote: remote.map(|s| s.to_owned()), - peer, - } + self.scheme = scheme.unwrap_or("http").to_owned(); + self.host = host.unwrap_or("localhost").to_owned(); + self.remote = remote.map(|s| s.to_owned()); + self.peer = peer; } /// Scheme of the request. @@ -171,59 +166,59 @@ impl ConnectionInfo { mod tests { use super::*; use http::header::HeaderValue; + use test::TestRequest; #[test] fn test_forwarded() { - let req = HttpRequest::default(); - let info = ConnectionInfo::new(&req); + let req = TestRequest::default().request(); + let mut info = ConnectionInfo::default(); + info.update(&req); assert_eq!(info.scheme(), "http"); - assert_eq!(info.host(), "localhost"); + assert_eq!(info.host(), "localhost:8080"); - let mut req = HttpRequest::default(); - req.headers_mut().insert( - header::FORWARDED, - HeaderValue::from_static( + let req = TestRequest::default() + .header( + header::FORWARDED, "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", - ), - ); + ) + .request(); - let info = ConnectionInfo::new(&req); + let mut info = ConnectionInfo::default(); + info.update(&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 req = TestRequest::default() + .header(header::HOST, "rust-lang.org") + .request(); - let info = ConnectionInfo::new(&req); + let mut info = ConnectionInfo::default(); + info.update(&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); + let req = TestRequest::default() + .header(X_FORWARDED_FOR, "192.0.2.60") + .request(); + let mut info = ConnectionInfo::default(); + info.update(&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); + let req = TestRequest::default() + .header(X_FORWARDED_HOST, "192.0.2.60") + .request(); + let mut info = ConnectionInfo::default(); + info.update(&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); + let mut req = TestRequest::default() + .header(X_FORWARDED_PROTO, "https") + .request(); + let mut info = ConnectionInfo::default(); + info.update(&req); assert_eq!(info.scheme(), "https"); } } diff --git a/src/json.rs b/src/json.rs index 3f9188c1..e9083cdd 100644 --- a/src/json.rs +++ b/src/json.rs @@ -140,12 +140,12 @@ where #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req = req.clone(); + let req2 = req.clone(); let err = Rc::clone(&cfg.ehandler); Box::new( - JsonBody::new(req.clone()) + JsonBody::new(req) .limit(cfg.limit) - .map_err(move |e| (*err)(e, req)) + .map_err(move |e| (*err)(e, &req2)) .map(Json), ) } @@ -183,7 +183,7 @@ where /// ``` pub struct JsonConfig { limit: usize, - ehandler: Rc) -> Error>, + ehandler: Rc) -> Error>, } impl JsonConfig { @@ -196,7 +196,7 @@ impl JsonConfig { /// Set custom error handler pub fn error_handler(&mut self, f: F) -> &mut Self where - F: Fn(JsonPayloadError, HttpRequest) -> Error + 'static, + F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, { self.ehandler = Rc::new(f); self @@ -243,19 +243,48 @@ impl Default for JsonConfig { /// } /// # fn main() {} /// ``` -pub struct JsonBody { +pub struct JsonBody { limit: usize, - req: Option, + length: Option, + stream: Option, + err: Option, fut: Option>>, } -impl JsonBody { +impl JsonBody { /// Create `JsonBody` for request. - pub fn new(req: T) -> Self { + pub fn new(req: &T) -> Self { + // check content-type + let json = if let Ok(Some(mime)) = req.mime_type() { + mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) + } else { + false + }; + if !json { + return JsonBody { + limit: 262_144, + length: None, + stream: None, + fut: None, + err: Some(JsonPayloadError::ContentType), + }; + } + + let mut len = None; + if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Ok(s) = l.to_str() { + if let Ok(l) = s.parse::() { + len = Some(l) + } + } + } + JsonBody { limit: 262_144, - req: Some(req), + length: len, + stream: Some(req.payload()), fut: None, + err: None, } } @@ -266,56 +295,42 @@ impl JsonBody { } } -impl Future for JsonBody -where - T: HttpMessage + Stream + 'static, -{ +impl Future for JsonBody { type Item = U; type Error = JsonPayloadError; fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if let Some(len) = req.headers().get(CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(JsonPayloadError::Overflow); - } - } else { - return Err(JsonPayloadError::Overflow); - } - } - } - // check content-type - - let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) - } else { - false - }; - if !json { - return Err(JsonPayloadError::ContentType); - } - - let limit = self.limit; - let fut = req - .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)); + if let Some(ref mut fut) = self.fut { + return fut.poll(); } - self.fut - .as_mut() + if let Some(err) = self.err.take() { + return Err(err); + } + + let limit = self.limit; + if let Some(len) = self.length.take() { + if len > limit { + return Err(JsonPayloadError::Overflow); + } + } + + let fut = self + .stream + .take() .expect("JsonBody could not be used second time") - .poll() + .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.poll() } } @@ -327,6 +342,7 @@ mod tests { use http::header; use handler::Handler; + use test::TestRequest; use with::With; impl PartialEq for JsonPayloadError { @@ -355,7 +371,7 @@ mod tests { let json = Json(MyObject { name: "test".to_owned(), }); - let resp = json.respond_to(&HttpRequest::default()).unwrap(); + let resp = json.respond_to(&TestRequest::default().finish()).unwrap(); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/json" @@ -364,41 +380,44 @@ mod tests { #[test] fn test_json_body() { - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let mut req = HttpRequest::default(); - req.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ); + let req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + ) + .finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let mut req = HttpRequest::default(); - req.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ); - req.headers_mut().insert( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ); + let req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ) + .finish(); let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - let mut req = HttpRequest::default(); - req.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ); - req.headers_mut().insert( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ); - req.payload_mut() - .unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); + let req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); + let mut json = req.json::(); assert_eq!( json.poll().ok().unwrap(), @@ -414,20 +433,18 @@ mod tests { cfg.limit(4096); let handler = With::new(|data: Json| data, cfg); - let req = HttpRequest::default(); - assert!(handler.handle(req).as_err().is_some()); + let req = TestRequest::default().finish(); + assert!(handler.handle(&req).as_err().is_some()); - let mut req = HttpRequest::default(); - req.headers_mut().insert( + let req = TestRequest::with_header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ); - req.headers_mut().insert( + ).header( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ); - req.payload_mut() - .unread_data(Bytes::from_static(b"{\"name\": \"test\"}")); - assert!(handler.handle(req).as_err().is_none()) + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); + assert!(handler.handle(&req).as_err().is_none()) } } diff --git a/src/lib.rs b/src/lib.rs index 85df48dd..5ed1bcef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,7 @@ allow(decimal_literal_representation, suspicious_arithmetic_impl) )] #![warn(missing_docs)] +#![allow(unused_mut, unused_imports, unused_variables, dead_code)] #[macro_use] extern crate log; @@ -199,6 +200,7 @@ pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; pub use scope::Scope; +pub use server::Request; pub mod actix { //! Re-exports [actix's](https://docs.rs/actix/) prelude diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 734f7be4..09ca8120 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -60,6 +60,7 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; use resource::ResourceHandler; +use server::Request; /// A set of errors that can occur during processing CORS #[derive(Debug, Fail)] @@ -279,11 +280,13 @@ impl Cors { /// `ResourceHandler::middleware()` method, but in that case *Cors* /// middleware wont be able to handle *OPTIONS* requests. pub fn register(self, resource: &mut ResourceHandler) { - resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok()); + resource + .method(Method::OPTIONS) + .h(|_: &_| HttpResponse::Ok()); resource.middleware(self); } - fn validate_origin(&self, req: &mut HttpRequest) -> Result<(), CorsError> { + fn validate_origin(&self, req: &Request) -> Result<(), CorsError> { if let Some(hdr) = req.headers().get(header::ORIGIN) { if let Ok(origin) = hdr.to_str() { return match self.inner.origins { @@ -303,9 +306,7 @@ impl Cors { } } - fn validate_allowed_method( - &self, req: &mut HttpRequest, - ) -> Result<(), CorsError> { + fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> { if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { @@ -323,9 +324,7 @@ impl Cors { } } - fn validate_allowed_headers( - &self, req: &mut HttpRequest, - ) -> Result<(), CorsError> { + fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> { match self.inner.headers { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { @@ -356,11 +355,11 @@ impl Cors { } impl Middleware for Cors { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &HttpRequest) -> Result { if self.inner.preflight && Method::OPTIONS == *req.method() { self.validate_origin(req)?; - self.validate_allowed_method(req)?; - self.validate_allowed_headers(req)?; + self.validate_allowed_method(&req)?; + self.validate_allowed_headers(&req)?; // allowed headers let headers = if let Some(headers) = self.inner.headers.as_ref() { @@ -434,7 +433,7 @@ impl Middleware for Cors { } fn response( - &self, req: &mut HttpRequest, mut resp: HttpResponse, + &self, req: &HttpRequest, mut resp: HttpResponse, ) -> Result { match self.inner.origins { AllOrSome::All => { @@ -945,10 +944,9 @@ mod tests { #[test] fn validate_origin_allows_all_origins() { let cors = Cors::default(); - let mut req = - TestRequest::with_header("Origin", "https://www.example.com").finish(); + let req = TestRequest::with_header("Origin", "https://www.example.com").finish(); - assert!(cors.start(&mut req).ok().unwrap().is_done()) + assert!(cors.start(&req).ok().unwrap().is_done()) } #[test] @@ -961,20 +959,20 @@ mod tests { .allowed_header(header::CONTENT_TYPE) .finish(); - let mut req = TestRequest::with_header("Origin", "https://www.example.com") + let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .finish(); - assert!(cors.start(&mut req).is_err()); + assert!(cors.start(&req).is_err()); - let mut req = TestRequest::with_header("Origin", "https://www.example.com") + let req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") .method(Method::OPTIONS) .finish(); - assert!(cors.start(&mut req).is_err()); + assert!(cors.start(&req).is_err()); - let mut req = TestRequest::with_header("Origin", "https://www.example.com") + let req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .header( header::ACCESS_CONTROL_REQUEST_HEADERS, @@ -983,7 +981,7 @@ mod tests { .method(Method::OPTIONS) .finish(); - let resp = cors.start(&mut req).unwrap().response(); + let resp = cors.start(&req).unwrap().response(); assert_eq!( &b"*"[..], resp.headers() @@ -1007,7 +1005,7 @@ mod tests { // as_bytes()); Rc::get_mut(&mut cors.inner).unwrap().preflight = false; - assert!(cors.start(&mut req).unwrap().is_done()); + assert!(cors.start(&req).unwrap().is_done()); } // #[test] @@ -1017,7 +1015,7 @@ mod tests { // .allowed_origin("https://www.example.com") // .finish(); // let mut req = HttpRequest::default(); - // cors.start(&mut req).unwrap(); + // cors.start(&req).unwrap(); // } #[test] @@ -1027,10 +1025,10 @@ mod tests { .allowed_origin("https://www.example.com") .finish(); - let mut req = TestRequest::with_header("Origin", "https://www.unknown.com") + let req = TestRequest::with_header("Origin", "https://www.unknown.com") .method(Method::GET) .finish(); - cors.start(&mut req).unwrap(); + cors.start(&req).unwrap(); } #[test] @@ -1039,30 +1037,30 @@ mod tests { .allowed_origin("https://www.example.com") .finish(); - let mut req = TestRequest::with_header("Origin", "https://www.example.com") + let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::GET) .finish(); - assert!(cors.start(&mut req).unwrap().is_done()); + assert!(cors.start(&req).unwrap().is_done()); } #[test] fn test_no_origin_response() { let cors = Cors::build().finish(); - let mut req = TestRequest::default().method(Method::GET).finish(); + let req = TestRequest::default().method(Method::GET).finish(); let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&mut req, resp).unwrap().response(); + let resp = cors.response(&req, resp).unwrap().response(); assert!( resp.headers() .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .is_none() ); - let mut req = TestRequest::with_header("Origin", "https://www.example.com") + let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .finish(); - let resp = cors.response(&mut req, resp).unwrap().response(); + let resp = cors.response(&req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], resp.headers() @@ -1083,12 +1081,12 @@ mod tests { .allowed_header(header::CONTENT_TYPE) .finish(); - let mut req = TestRequest::with_header("Origin", "https://www.example.com") + let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .finish(); let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&mut req, resp).unwrap().response(); + let resp = cors.response(&req, resp).unwrap().response(); assert_eq!( &b"*"[..], resp.headers() @@ -1103,7 +1101,7 @@ mod tests { let resp: HttpResponse = HttpResponse::Ok().header(header::VARY, "Accept").finish(); - let resp = cors.response(&mut req, resp).unwrap().response(); + let resp = cors.response(&req, resp).unwrap().response(); assert_eq!( &b"Accept, Origin"[..], resp.headers().get(header::VARY).unwrap().as_bytes() @@ -1114,7 +1112,7 @@ mod tests { .allowed_origin("https://www.example.com") .finish(); let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&mut req, resp).unwrap().response(); + let resp = cors.response(&req, resp).unwrap().response(); assert_eq!( &b"https://www.example.com"[..], resp.headers() diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index faa763e2..0062bd02 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -25,7 +25,7 @@ //! use actix_web::middleware::csrf; //! use actix_web::{http, App, HttpRequest, HttpResponse}; //! -//! fn handle_post(_: HttpRequest) -> &'static str { +//! fn handle_post(_: &HttpRequest) -> &'static str { //! "This action should only be triggered with requests from the same site" //! } //! @@ -54,6 +54,7 @@ use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Started}; +use server::Request; /// Potential cross-site request forgery detected. #[derive(Debug, Fail)] @@ -187,7 +188,7 @@ impl CsrfFilter { self } - fn validate(&self, req: &mut HttpRequest) -> Result<(), CsrfError> { + fn validate(&self, req: &Request) -> Result<(), CsrfError> { let is_upgrade = req.headers().contains_key(header::UPGRADE); let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade); @@ -209,7 +210,7 @@ impl CsrfFilter { } impl Middleware for CsrfFilter { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &HttpRequest) -> Result { self.validate(req)?; Ok(Started::Done) } @@ -225,35 +226,35 @@ mod tests { fn test_safe() { let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - let mut req = TestRequest::with_header("Origin", "https://www.w3.org") + let req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::HEAD) .finish(); - assert!(csrf.start(&mut req).is_ok()); + assert!(csrf.start(&req).is_ok()); } #[test] fn test_csrf() { let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - let mut req = TestRequest::with_header("Origin", "https://www.w3.org") + let req = TestRequest::with_header("Origin", "https://www.w3.org") .method(Method::POST) .finish(); - assert!(csrf.start(&mut req).is_err()); + assert!(csrf.start(&req).is_err()); } #[test] fn test_referer() { let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( "Referer", "https://www.example.com/some/path?query=param", ).method(Method::POST) .finish(); - assert!(csrf.start(&mut req).is_ok()); + assert!(csrf.start(&req).is_ok()); } #[test] @@ -264,13 +265,13 @@ mod tests { .allowed_origin("https://www.example.com") .allow_upgrade(); - let mut req = TestRequest::with_header("Origin", "https://cswsh.com") + let req = TestRequest::with_header("Origin", "https://cswsh.com") .header("Connection", "Upgrade") .header("Upgrade", "websocket") .method(Method::GET) .finish(); - assert!(strict_csrf.start(&mut req).is_err()); - assert!(lax_csrf.start(&mut req).is_ok()); + assert!(strict_csrf.start(&req).is_err()); + assert!(lax_csrf.start(&req).is_ok()); } } diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index dca8dfbe..a33fa6a3 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -74,9 +74,7 @@ impl DefaultHeaders { } impl Middleware for DefaultHeaders { - fn response( - &self, _: &mut HttpRequest, mut resp: HttpResponse, - ) -> Result { + fn response(&self, _: &HttpRequest, mut resp: HttpResponse) -> Result { for (key, value) in self.headers.iter() { if !resp.headers().contains_key(key) { resp.headers_mut().insert(key, value.clone()); @@ -97,22 +95,23 @@ impl Middleware for DefaultHeaders { mod tests { use super::*; use http::header::CONTENT_TYPE; + use test::TestRequest; #[test] fn test_default_headers() { let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); - let mut req = HttpRequest::default(); + let req = TestRequest::default().finish(); let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&mut req, resp) { + let resp = match mw.response(&req, resp) { Ok(Response::Done(resp)) => resp, _ => panic!(), }; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); - let resp = match mw.response(&mut req, resp) { + let resp = match mw.response(&req, resp) { Ok(Response::Done(resp)) => resp, _ => panic!(), }; diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index fe148fdd..a9ebe21c 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -6,7 +6,7 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response}; -type ErrorHandler = Fn(&mut HttpRequest, HttpResponse) -> Result; +type ErrorHandler = Fn(&HttpRequest, HttpResponse) -> Result; /// `Middleware` for allowing custom handlers for responses. /// @@ -21,7 +21,7 @@ type ErrorHandler = Fn(&mut HttpRequest, HttpResponse) -> Result /// use actix_web::middleware::{ErrorHandlers, Response}; /// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; /// -/// fn render_500(_: &mut HttpRequest, resp: HttpResponse) -> Result { +/// fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { /// let mut builder = resp.into_builder(); /// builder.header(http::header::CONTENT_TYPE, "application/json"); /// Ok(Response::Done(builder.into())) @@ -62,7 +62,7 @@ impl ErrorHandlers { /// Register error handler for specified status code pub fn handler(mut self, status: StatusCode, handler: F) -> Self where - F: Fn(&mut HttpRequest, HttpResponse) -> Result + 'static, + F: Fn(&HttpRequest, HttpResponse) -> Result + 'static, { self.handlers.insert(status, Box::new(handler)); self @@ -70,9 +70,7 @@ impl ErrorHandlers { } impl Middleware for ErrorHandlers { - fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, - ) -> Result { + fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { if let Some(handler) = self.handlers.get(&resp.status()) { handler(req, resp) } else { @@ -91,7 +89,10 @@ mod tests { use middleware::Started; use test; - fn render_500(_: &mut HttpRequest, resp: HttpResponse) -> Result { + use server::Request; + use test::TestRequest; + + fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { let mut builder = resp.into_builder(); builder.header(CONTENT_TYPE, "0001"); Ok(Response::Done(builder.into())) @@ -102,7 +103,7 @@ mod tests { let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut req = HttpRequest::default(); + let mut req = TestRequest::default().finish(); let resp = HttpResponse::InternalServerError().finish(); let resp = match mw.response(&mut req, resp) { Ok(Response::Done(resp)) => resp, @@ -121,7 +122,7 @@ mod tests { struct MiddlewareOne; impl Middleware for MiddlewareOne { - fn start(&self, _req: &mut HttpRequest) -> Result { + fn start(&self, _: &HttpRequest) -> Result { Err(ErrorInternalServerError("middleware error")) } } diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index f4089428..c8554244 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -46,6 +46,7 @@ //! )); //! } //! ``` +use std::cell::RefCell; use std::rc::Rc; use cookie::{Cookie, CookieJar, Key}; @@ -58,6 +59,7 @@ use http::header::{self, HeaderValue}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; +use server::Request; /// The helper trait to obtain your identity from a request. /// @@ -88,32 +90,32 @@ use middleware::{Middleware, Response, Started}; pub trait RequestIdentity { /// Return the claimed identity of the user associated request or /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option<&str>; + fn identity(&self) -> Option; /// Remember identity. - fn remember(&mut self, identity: String); + fn remember(&self, identity: String); /// This method is used to 'forget' the current identity on subsequent /// requests. - fn forget(&mut self); + fn forget(&self); } impl RequestIdentity for HttpRequest { - fn identity(&self) -> Option<&str> { + fn identity(&self) -> Option { if let Some(id) = self.extensions().get::() { - return id.0.identity(); + return id.0.identity().map(|s| s.to_owned()); } None } - fn remember(&mut self, identity: String) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.remember(identity); + fn remember(&self, identity: String) { + if let Some(mut id) = self.extensions_mut().get_mut::() { + return id.0.as_mut().remember(identity); } } - fn forget(&mut self) { - if let Some(id) = self.extensions_mut().get_mut::() { + fn forget(&self) { + if let Some(mut id) = self.extensions_mut().get_mut::() { return id.0.forget(); } } @@ -145,7 +147,7 @@ pub trait IdentityPolicy: Sized + 'static { type Future: Future; /// Parse the session from request and load data from a service identity. - fn from_request(&self, request: &mut HttpRequest) -> Self::Future; + fn from_request(&self, request: &HttpRequest) -> Self::Future; } /// Request identity middleware @@ -178,27 +180,21 @@ impl IdentityService { struct IdentityBox(Box); impl> Middleware for IdentityService { - fn start(&self, req: &mut HttpRequest) -> Result { - let mut req = req.clone(); - - let fut = self - .backend - .from_request(&mut req) - .then(move |res| match res { - Ok(id) => { - req.extensions_mut().insert(IdentityBox(Box::new(id))); - FutOk(None) - } - Err(err) => FutErr(err), - }); + fn start(&self, req: &HttpRequest) -> Result { + let req = req.clone(); + let fut = self.backend.from_request(&req).then(move |res| match res { + Ok(id) => { + req.extensions_mut().insert(IdentityBox(Box::new(id))); + FutOk(None) + } + Err(err) => FutErr(err), + }); Ok(Started::Future(Box::new(fut))) } - fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, - ) -> Result { - if let Some(mut id) = req.extensions_mut().remove::() { - id.0.write(resp) + fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { + if let Some(ref mut id) = req.extensions_mut().get_mut::() { + id.0.as_mut().write(resp) } else { Ok(Response::Done(resp)) } @@ -291,9 +287,9 @@ impl CookieIdentityInner { Ok(()) } - fn load(&self, req: &mut HttpRequest) -> Option { + fn load(&self, req: &HttpRequest) -> Option { if let Ok(cookies) = req.cookies() { - for cookie in cookies { + for cookie in cookies.iter() { if cookie.name() == self.name { let mut jar = CookieJar::new(); jar.add_original(cookie.clone()); @@ -382,7 +378,7 @@ impl IdentityPolicy for CookieIdentityPolicy { type Identity = CookieIdentity; type Future = FutureResult; - fn from_request(&self, req: &mut HttpRequest) -> Self::Future { + fn from_request(&self, req: &HttpRequest) -> Self::Future { let identity = self.0.load(req); FutOk(CookieIdentity { identity, diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index c5701ef8..dbad60a1 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -11,6 +11,7 @@ use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Started}; +use server::Request; /// `Middleware` for logging request and response info to the terminal. /// @@ -107,7 +108,7 @@ impl Default for Logger { struct StartTime(time::Tm); impl Logger { - fn log(&self, req: &mut HttpRequest, resp: &HttpResponse) { + fn log(&self, req: &HttpRequest, resp: &HttpResponse) { if let Some(entry_time) = req.extensions().get::() { let render = |fmt: &mut Formatter| { for unit in &self.format.0 { @@ -121,14 +122,14 @@ impl Logger { } impl Middleware for Logger { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &HttpRequest) -> Result { if !self.exclude.contains(req.path()) { req.extensions_mut().insert(StartTime(time::now())); } Ok(Started::Done) } - fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished { + fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { self.log(req, resp); Finished::Done } @@ -312,34 +313,27 @@ mod tests { use http::header::{self, HeaderMap}; use http::{Method, StatusCode, Uri, Version}; use std::str::FromStr; + use test::TestRequest; use time; #[test] fn test_logger() { let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - let mut headers = HeaderMap::new(); - headers.insert( + let req = TestRequest::with_header( header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"), - ); - let mut req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + ).finish(); let resp = HttpResponse::build(StatusCode::OK) .header("X-Test", "ttt") .force_close() .finish(); - match logger.start(&mut req) { + match logger.start(&req) { Ok(Started::Done) => (), _ => panic!(), }; - match logger.finish(&mut req, &resp) { + match logger.finish(&req, &resp) { Finished::Done => (), _ => panic!(), } @@ -358,18 +352,10 @@ mod tests { fn test_default_format() { let format = Format::default(); - let mut headers = HeaderMap::new(); - headers.insert( + let req = TestRequest::with_header( header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"), - ); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + ).finish(); let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); let entry_time = time::now(); @@ -384,13 +370,7 @@ mod tests { assert!(s.contains("200 0")); assert!(s.contains("ACTIX-WEB")); - 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 = HttpResponse::build(StatusCode::OK).force_close().finish(); let entry_time = time::now(); diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 2551ded1..237a0eb3 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -4,6 +4,7 @@ use futures::Future; use error::{Error, Result}; use httprequest::HttpRequest; use httpresponse::HttpResponse; +use server::Request; mod logger; @@ -51,20 +52,18 @@ pub enum Finished { pub trait Middleware: 'static { /// 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) -> Result { + fn start(&self, req: &HttpRequest) -> Result { Ok(Started::Done) } /// Method is called when handler returns response, /// but before sending http message to peer. - fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, - ) -> Result { + fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { Ok(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: &HttpRequest, resp: &HttpResponse) -> Finished { Finished::Done } } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index bd10b3c2..af3d03cc 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -83,6 +83,7 @@ use handler::FromRequest; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; +use server::Request; /// The helper trait to obtain your session data from a request. /// @@ -246,7 +247,7 @@ impl> SessionStorage { } impl> Middleware for SessionStorage { - fn start(&self, req: &mut HttpRequest) -> Result { + fn start(&self, req: &HttpRequest) -> Result { let mut req = req.clone(); let fut = self.0.from_request(&mut req).then(move |res| match res { @@ -260,10 +261,8 @@ impl> Middleware for SessionStorage { Ok(Started::Future(Box::new(fut))) } - fn response( - &self, req: &mut HttpRequest, resp: HttpResponse, - ) -> Result { - if let Some(s_box) = req.extensions_mut().remove::>() { + fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { + if let Some(s_box) = req.extensions().get::>() { s_box.0.borrow_mut().write(resp) } else { Ok(Response::Done(resp)) @@ -421,7 +420,7 @@ impl CookieSessionInner { fn load(&self, req: &mut HttpRequest) -> HashMap { if let Ok(cookies) = req.cookies() { - for cookie in cookies { + for cookie in cookies.iter() { if cookie.name() == self.name { let mut jar = CookieJar::new(); jar.add_original(cookie.clone()); diff --git a/src/param.rs b/src/param.rs index 76262c2a..649345ee 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,7 +1,7 @@ use std; -use std::rc::Rc; use std::ops::Index; use std::path::PathBuf; +use std::rc::Rc; use std::str::FromStr; use http::StatusCode; @@ -29,11 +29,11 @@ pub(crate) enum ParamItem { /// Route match information /// /// If resource path contains variable patterns, `Params` stores this variables. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Params { url: Url, pub(crate) tail: u16, - segments: SmallVec<[(Rc, ParamItem); 3]>, + pub(crate) segments: SmallVec<[(Rc, ParamItem); 3]>, } impl Params { @@ -45,6 +45,14 @@ impl Params { } } + pub(crate) fn with_url(url: &Url) -> Params { + Params { + url: url.clone(), + tail: 0, + segments: SmallVec::new(), + } + } + pub(crate) fn clear(&mut self) { self.segments.clear(); } @@ -62,7 +70,8 @@ impl Params { } pub(crate) fn add_static(&mut self, name: &str, value: &'static str) { - self.segments.push((Rc::new(name.to_string()), ParamItem::Static(value))); + self.segments + .push((Rc::new(name.to_string()), ParamItem::Static(value))); } /// Check if there are any matched patterns @@ -151,16 +160,16 @@ impl<'a> Iterator for ParamsIter<'a> { } } -impl<'a, 'b> Index<&'b str> for &'a Params { +impl<'a> Index<&'a str> for Params { type Output = str; - fn index(&self, name: &'b str) -> &str { + fn index(&self, name: &'a str) -> &str { self.get(name) .expect("Value for parameter is not available") } } -impl<'a> Index for &'a Params { +impl Index for Params { type Output = str; fn index(&self, idx: usize) -> &str { diff --git a/src/payload.rs b/src/payload.rs index 12a4ae26..fd4e57af 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -59,20 +59,14 @@ impl Payload { } } - /// Indicates EOF of payload - #[inline] - pub fn eof(&self) -> bool { - self.inner.borrow().eof() - } - /// Length of the data in this payload - #[inline] + #[cfg(test)] pub fn len(&self) -> usize { self.inner.borrow().len() } /// Is payload empty - #[inline] + #[cfg(test)] pub fn is_empty(&self) -> bool { self.inner.borrow().len() == 0 } @@ -225,12 +219,7 @@ impl Inner { } } - #[inline] - fn eof(&self) -> bool { - self.items.is_empty() && self.eof - } - - #[inline] + #[cfg(test)] fn len(&self) -> usize { self.len } diff --git a/src/pipeline.rs b/src/pipeline.rs index 19275944..6f3d4807 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -15,7 +15,7 @@ use header::ContentEncoding; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Response, Started}; -use server::{HttpHandlerTask, Writer, WriterState}; +use server::{HttpHandlerTask, Request, Writer, WriterState}; #[doc(hidden)] #[derive(Debug, Clone, Copy)] @@ -29,13 +29,11 @@ pub enum HandlerType { pub trait PipelineHandler { fn encoding(&self) -> ContentEncoding; - fn handle( - &self, req: HttpRequest, htype: HandlerType, - ) -> AsyncResult; + fn handle(&self, &HttpRequest, HandlerType) -> AsyncResult; } #[doc(hidden)] -pub struct Pipeline( +pub struct Pipeline( PipelineInfo, PipelineState, Rc>>>, @@ -76,7 +74,7 @@ impl> PipelineState { } } -struct PipelineInfo { +struct PipelineInfo { req: HttpRequest, count: u16, context: Option>, @@ -85,7 +83,7 @@ struct PipelineInfo { encoding: ContentEncoding, } -impl PipelineInfo { +impl PipelineInfo { fn new(req: HttpRequest) -> PipelineInfo { PipelineInfo { req, @@ -129,16 +127,6 @@ impl> Pipeline { } } -impl Pipeline<(), Inner<()>> { - pub fn error>(err: R) -> Box { - Box::new(Pipeline::<(), Inner<()>>( - PipelineInfo::new(HttpRequest::default()), - ProcessResponse::init(err.into()), - Rc::new(Vec::new()), - )) - } -} - impl Pipeline { #[inline] fn is_done(&self) -> bool { @@ -241,16 +229,16 @@ impl> StartMiddlewares { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately let len = mws.len() as u16; + loop { if info.count == len { - let reply = hnd.handle(info.req.clone(), htype); + let reply = hnd.handle(&info.req, htype); return WaitingResponse::init(info, mws, reply); } else { - let state = mws[info.count as usize].start(&mut info.req); - match state { + match mws[info.count as usize].start(&info.req) { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { - return RunMiddlewares::init(info, mws, resp) + return RunMiddlewares::init(info, mws, resp); } Ok(Started::Future(fut)) => { return PipelineState::Starting(StartMiddlewares { @@ -260,7 +248,9 @@ impl> StartMiddlewares { _s: PhantomData, }) } - Err(err) => return RunMiddlewares::init(info, mws, err.into()), + Err(err) => { + return RunMiddlewares::init(info, mws, err.into()); + } } } } @@ -270,9 +260,12 @@ impl> StartMiddlewares { &mut self, info: &mut PipelineInfo, mws: &[Box>], ) -> Option> { let len = mws.len() as u16; + 'outer: loop { match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, + Ok(Async::NotReady) => { + return None; + } Ok(Async::Ready(resp)) => { info.count += 1; if let Some(resp) = resp { @@ -280,11 +273,11 @@ impl> StartMiddlewares { } loop { if info.count == len { - let reply = self.hnd.handle(info.req.clone(), self.htype); + let reply = self.hnd.handle(&info.req, self.htype); return Some(WaitingResponse::init(info, mws, reply)); } else { - let state = mws[info.count as usize].start(&mut info.req); - match state { + let res = mws[info.count as usize].start(&info.req); + match res { Ok(Started::Done) => info.count += 1, Ok(Started::Response(resp)) => { return Some(RunMiddlewares::init(info, mws, resp)); @@ -298,13 +291,15 @@ impl> StartMiddlewares { info, mws, err.into(), - )) + )); } } } } } - Err(err) => return Some(RunMiddlewares::init(info, mws, err.into())), + Err(err) => { + return Some(RunMiddlewares::init(info, mws, err.into())); + } } } } @@ -324,8 +319,8 @@ impl WaitingResponse { reply: AsyncResult, ) -> PipelineState { match reply.into() { - AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()), AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp), + AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()), AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse { fut, _s: PhantomData, @@ -339,9 +334,7 @@ impl WaitingResponse { ) -> Option> { match self.fut.poll() { Ok(Async::NotReady) => None, - Ok(Async::Ready(response)) => { - Some(RunMiddlewares::init(info, mws, response)) - } + Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, mws, resp)), Err(err) => Some(RunMiddlewares::init(info, mws, err.into())), } } @@ -367,7 +360,7 @@ impl RunMiddlewares { let len = mws.len(); loop { - let state = mws[curr].response(&mut info.req, resp); + let state = mws[curr].response(&info.req, resp); resp = match state { Err(err) => { info.count = (curr + 1) as u16; @@ -387,7 +380,7 @@ impl RunMiddlewares { fut: Some(fut), _s: PhantomData, _h: PhantomData, - }) + }); } }; } @@ -413,7 +406,7 @@ impl RunMiddlewares { if self.curr == len { return Some(ProcessResponse::init(resp)); } else { - let state = mws[self.curr].response(&mut info.req, resp); + let state = mws[self.curr].response(&info.req, resp); match state { Err(err) => return Some(ProcessResponse::init(err.into())), Ok(Response::Done(r)) => { @@ -495,19 +488,16 @@ impl ProcessResponse { let encoding = self.resp.content_encoding().unwrap_or(info.encoding); - let result = match io.start( - info.req.as_mut(), - &mut self.resp, - encoding, - ) { - Ok(res) => res, - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, mws, self.resp, - )); - } - }; + let result = + match io.start(&info.req, &mut self.resp, encoding) { + Ok(res) => res, + Err(err) => { + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init( + info, mws, self.resp, + )); + } + }; if let Some(err) = self.resp.error() { if self.resp.status().is_server_error() { @@ -747,8 +737,8 @@ impl FinishingMiddlewares { } info.count -= 1; - let state = mws[info.count as usize] - .finish(&mut info.req, self.resp.as_ref().unwrap()); + let state = + mws[info.count as usize].finish(&info.req, self.resp.as_ref().unwrap()); match state { Finished::Done => { if info.count == 0 { @@ -797,9 +787,10 @@ mod tests { use actix::*; use context::HttpContext; use futures::future::{lazy, result}; - use http::StatusCode; use tokio::runtime::current_thread::Runtime; + use test::TestRequest; + impl PipelineState { fn is_none(&self) -> Option { if let PipelineState::None = *self { @@ -827,12 +818,13 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let mut info = PipelineInfo::new(HttpRequest::default()); + let req = TestRequest::default().finish(); + let mut info = PipelineInfo::new(req); Completed::<(), Inner<()>>::init(&mut info) .is_none() .unwrap(); - let req = HttpRequest::default(); + let req = TestRequest::default().finish(); let ctx = HttpContext::new(req.clone(), MyActor); let addr = ctx.address(); let mut info = PipelineInfo::new(req); diff --git a/src/pred.rs b/src/pred.rs index 020052e2..5d47922f 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -1,10 +1,12 @@ //! Route match predicates #![allow(non_snake_case)] +use std::marker::PhantomData; + use http; use http::{header, HttpTryFrom}; use httpmessage::HttpMessage; use httprequest::HttpRequest; -use std::marker::PhantomData; +use server::message::Request; /// Trait defines resource route predicate. /// Predicate can modify request object. It is also possible to @@ -12,7 +14,7 @@ use std::marker::PhantomData; /// Extensions container available via `HttpRequest::extensions()` method. pub trait Predicate { /// Check if request matches predicate - fn check(&self, &mut HttpRequest) -> bool; + fn check(&self, &Request, &S) -> bool; } /// Return predicate that matches if any of supplied predicate matches. @@ -45,9 +47,9 @@ impl AnyPredicate { } impl Predicate for AnyPredicate { - fn check(&self, req: &mut HttpRequest) -> bool { + fn check(&self, req: &Request, state: &S) -> bool { for p in &self.0 { - if p.check(req) { + if p.check(req, state) { return true; } } @@ -88,9 +90,9 @@ impl AllPredicate { } impl Predicate for AllPredicate { - fn check(&self, req: &mut HttpRequest) -> bool { + fn check(&self, req: &Request, state: &S) -> bool { for p in &self.0 { - if !p.check(req) { + if !p.check(req, state) { return false; } } @@ -107,8 +109,8 @@ pub fn Not + 'static>(pred: P) -> NotPredicate { pub struct NotPredicate(Box>); impl Predicate for NotPredicate { - fn check(&self, req: &mut HttpRequest) -> bool { - !self.0.check(req) + fn check(&self, req: &Request, state: &S) -> bool { + !self.0.check(req, state) } } @@ -117,7 +119,7 @@ impl Predicate for NotPredicate { pub struct MethodPredicate(http::Method, PhantomData); impl Predicate for MethodPredicate { - fn check(&self, req: &mut HttpRequest) -> bool { + fn check(&self, req: &Request, _: &S) -> bool { *req.method() == self.0 } } @@ -188,7 +190,7 @@ pub fn Header( pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); impl Predicate for HeaderPredicate { - fn check(&self, req: &mut HttpRequest) -> bool { + fn check(&self, req: &Request, _: &S) -> bool { if let Some(val) = req.headers().get(&self.0) { return val == self.1; } @@ -225,7 +227,7 @@ impl HostPredicate { } impl Predicate for HostPredicate { - fn check(&self, req: &mut HttpRequest) -> bool { + fn check(&self, req: &Request, _: &S) -> bool { let info = req.connection_info(); if let Some(ref scheme) = self.1 { self.0 == info.host() && scheme == info.scheme() @@ -237,168 +239,96 @@ impl Predicate for HostPredicate { #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; use http::header::{self, HeaderMap}; use http::{Method, Uri, Version}; - use std::str::FromStr; + use test::TestRequest; #[test] fn test_header() { - let mut headers = HeaderMap::new(); - headers.insert( + let req = TestRequest::with_header( header::TRANSFER_ENCODING, header::HeaderValue::from_static("chunked"), - ); - let mut req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + ).finish(); let pred = Header("transfer-encoding", "chunked"); - assert!(pred.check(&mut req)); + assert!(pred.check(&req, req.state())); let pred = Header("transfer-encoding", "other"); - assert!(!pred.check(&mut req)); + assert!(!pred.check(&req, req.state())); let pred = Header("content-type", "other"); - assert!(!pred.check(&mut req)); + assert!(!pred.check(&req, req.state())); } #[test] fn test_host() { - 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, - None, - ); + let req = TestRequest::default() + .header( + header::HOST, + header::HeaderValue::from_static("www.rust-lang.org"), + ) + .finish(); let pred = Host("www.rust-lang.org"); - assert!(pred.check(&mut req)); + assert!(pred.check(&req, req.state())); let pred = Host("localhost"); - assert!(!pred.check(&mut req)); + assert!(!pred.check(&req, req.state())); } #[test] fn test_methods() { - let mut req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); - let mut req2 = HttpRequest::new( - Method::POST, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); + let req = TestRequest::default().finish(); + let req2 = TestRequest::default().method(Method::POST).finish(); - assert!(Get().check(&mut req)); - assert!(!Get().check(&mut req2)); - assert!(Post().check(&mut req2)); - assert!(!Post().check(&mut req)); + assert!(Get().check(&req, req.state())); + assert!(!Get().check(&req2, req2.state())); + assert!(Post().check(&req2, req2.state())); + assert!(!Post().check(&req, req.state())); - let mut r = HttpRequest::new( - Method::PUT, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); - assert!(Put().check(&mut r)); - assert!(!Put().check(&mut req)); + let r = TestRequest::default().method(Method::PUT).finish(); + assert!(Put().check(&r, r.state())); + assert!(!Put().check(&req, req.state())); - let mut r = HttpRequest::new( - Method::DELETE, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); - assert!(Delete().check(&mut r)); - assert!(!Delete().check(&mut req)); + let r = TestRequest::default().method(Method::DELETE).finish(); + assert!(Delete().check(&r, r.state())); + assert!(!Delete().check(&req, req.state())); - let mut r = HttpRequest::new( - Method::HEAD, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); - assert!(Head().check(&mut r)); - assert!(!Head().check(&mut req)); + let r = TestRequest::default().method(Method::HEAD).finish(); + assert!(Head().check(&r, r.state())); + assert!(!Head().check(&req, req.state())); - let mut r = HttpRequest::new( - Method::OPTIONS, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); - assert!(Options().check(&mut r)); - assert!(!Options().check(&mut req)); + let r = TestRequest::default().method(Method::OPTIONS).finish(); + assert!(Options().check(&r, r.state())); + assert!(!Options().check(&req, req.state())); - let mut r = HttpRequest::new( - Method::CONNECT, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); - assert!(Connect().check(&mut r)); - assert!(!Connect().check(&mut req)); + let r = TestRequest::default().method(Method::CONNECT).finish(); + assert!(Connect().check(&r, r.state())); + assert!(!Connect().check(&req, req.state())); - let mut r = HttpRequest::new( - Method::PATCH, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); - assert!(Patch().check(&mut r)); - assert!(!Patch().check(&mut req)); + let r = TestRequest::default().method(Method::PATCH).finish(); + assert!(Patch().check(&r, r.state())); + assert!(!Patch().check(&req, req.state())); - let mut r = HttpRequest::new( - Method::TRACE, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); - assert!(Trace().check(&mut r)); - assert!(!Trace().check(&mut req)); + let r = TestRequest::default().method(Method::TRACE).finish(); + assert!(Trace().check(&r, r.state())); + assert!(!Trace().check(&req, req.state())); } #[test] fn test_preds() { - let mut r = HttpRequest::new( - Method::TRACE, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); + let r = TestRequest::default().method(Method::TRACE).finish(); - assert!(Not(Get()).check(&mut r)); - assert!(!Not(Trace()).check(&mut r)); + assert!(Not(Get()).check(&r, r.state())); + assert!(!Not(Trace()).check(&r, r.state())); - assert!(All(Trace()).and(Trace()).check(&mut r)); - assert!(!All(Get()).and(Trace()).check(&mut r)); + assert!(All(Trace()).and(Trace()).check(&r, r.state())); + assert!(!All(Get()).and(Trace()).check(&r, r.state())); - assert!(Any(Get()).or(Trace()).check(&mut r)); - assert!(!Any(Get()).or(Get()).check(&mut r)); + assert!(Any(Get()).or(Trace()).check(&r, r.state())); + assert!(!Any(Get()).or(Get()).check(&r, r.state())); } } diff --git a/src/resource.rs b/src/resource.rs index 2eae570c..4e8ecf55 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -12,6 +12,10 @@ use httpresponse::HttpResponse; use middleware::Middleware; use pred; use route::Route; +use server::Request; + +#[derive(Copy, Clone)] +pub(crate) struct RouteId(usize); /// *Resource* is an entry in route table which corresponds to requested URL. /// @@ -131,7 +135,7 @@ impl ResourceHandler { /// ```rust /// # extern crate actix_web; /// use actix_web::*; - /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } /// /// App::new().resource("/", |r| r.method(http::Method::GET).f(index)); /// ``` @@ -141,7 +145,7 @@ impl ResourceHandler { /// ```rust /// # extern crate actix_web; /// # use actix_web::*; - /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index)); /// ``` pub fn method(&mut self, method: Method) -> &mut Route { @@ -154,7 +158,7 @@ impl ResourceHandler { /// ```rust /// # extern crate actix_web; /// use actix_web::*; - /// fn handler(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() } /// /// App::new().resource("/", |r| r.h(handler)); /// ``` @@ -164,7 +168,7 @@ impl ResourceHandler { /// ```rust /// # extern crate actix_web; /// # use actix_web::*; - /// # fn handler(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// # fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().resource("/", |r| r.route().h(handler)); /// ``` pub fn h>(&mut self, handler: H) { @@ -177,7 +181,7 @@ impl ResourceHandler { /// ```rust /// # extern crate actix_web; /// use actix_web::*; - /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } /// /// App::new().resource("/", |r| r.f(index)); /// ``` @@ -187,12 +191,12 @@ impl ResourceHandler { /// ```rust /// # extern crate actix_web; /// # use actix_web::*; - /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } + /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().resource("/", |r| r.route().f(index)); /// ``` pub fn f(&mut self, handler: F) where - F: Fn(HttpRequest) -> R + 'static, + F: Fn(&HttpRequest) -> R + 'static, R: Responder + 'static, { self.routes.push(Route::default()); @@ -279,19 +283,24 @@ impl ResourceHandler { .push(Box::new(mw)); } - pub(crate) fn handle( - &self, mut req: HttpRequest, - ) -> Result, HttpRequest> { - for route in &self.routes { - if route.check(&mut req) { - return if self.middlewares.is_empty() { - Ok(route.handle(req)) - } else { - Ok(route.compose(req, Rc::clone(&self.middlewares))) - }; + #[inline] + pub(crate) fn get_route_id(&self, req: &HttpRequest) -> Option { + for idx in 0..self.routes.len() { + if (&self.routes[idx]).check(req) { + return Some(RouteId(idx)); } } + None + } - Err(req) + #[inline] + pub(crate) fn handle( + &self, id: RouteId, req: &HttpRequest, + ) -> AsyncResult { + if self.middlewares.is_empty() { + (&self.routes[id.0]).handle(req) + } else { + (&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares)) + } } } diff --git a/src/route.rs b/src/route.rs index fed4deb5..bf880a3c 100644 --- a/src/route.rs +++ b/src/route.rs @@ -16,6 +16,7 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; +use server::Request; use with::{With, WithAsync}; /// Resource route definition @@ -31,16 +32,17 @@ impl Default for Route { fn default() -> Route { Route { preds: Vec::new(), - handler: InnerHandler::new(|_| HttpResponse::new(StatusCode::NOT_FOUND)), + handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)), } } } impl Route { #[inline] - pub(crate) fn check(&self, req: &mut HttpRequest) -> bool { + pub(crate) fn check(&self, req: &HttpRequest) -> bool { + let state = req.state(); for pred in &self.preds { - if !pred.check(req) { + if !pred.check(req, state) { return false; } } @@ -48,7 +50,7 @@ impl Route { } #[inline] - pub(crate) fn handle(&self, req: HttpRequest) -> AsyncResult { + pub(crate) fn handle(&self, req: &HttpRequest) -> AsyncResult { self.handler.handle(req) } @@ -89,7 +91,7 @@ impl Route { /// during route configuration, so it does not return reference to self. pub fn f(&mut self, handler: F) where - F: Fn(HttpRequest) -> R + 'static, + F: Fn(&HttpRequest) -> R + 'static, R: Responder + 'static, { self.handler = InnerHandler::new(handler); @@ -98,7 +100,7 @@ impl Route { /// Set async handler function. pub fn a(&mut self, handler: H) where - H: Fn(HttpRequest) -> F + 'static, + H: Fn(&HttpRequest) -> F + 'static, F: Future + 'static, R: Responder + 'static, E: Into + 'static, @@ -307,7 +309,7 @@ impl InnerHandler { #[inline] fn async(h: H) -> Self where - H: Fn(HttpRequest) -> F + 'static, + H: Fn(&HttpRequest) -> F + 'static, F: Future + 'static, R: Responder + 'static, E: Into + 'static, @@ -316,7 +318,7 @@ impl InnerHandler { } #[inline] - pub fn handle(&self, req: HttpRequest) -> AsyncResult { + pub fn handle(&self, req: &HttpRequest) -> AsyncResult { self.0.handle(req) } } @@ -407,24 +409,27 @@ type Fut = Box, Error = Error>>; impl StartMiddlewares { fn init(info: &mut ComposeInfo) -> ComposeState { let len = info.mws.len(); + loop { if info.count == len { - let reply = info.handler.handle(info.req.clone()); + let reply = info.handler.handle(&info.req); return WaitingResponse::init(info, reply); } else { - let state = info.mws[info.count].start(&mut info.req); - match state { + let result = info.mws[info.count].start(&info.req); + match result { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp) + return RunMiddlewares::init(info, resp); } Ok(MiddlewareStarted::Future(fut)) => { return ComposeState::Starting(StartMiddlewares { fut: Some(fut), _s: PhantomData, - }) + }); + } + Err(err) => { + return RunMiddlewares::init(info, err.into()); } - Err(err) => return RunMiddlewares::init(info, err.into()), } } } @@ -432,9 +437,12 @@ impl StartMiddlewares { fn poll(&mut self, info: &mut ComposeInfo) -> Option> { let len = info.mws.len(); + 'outer: loop { match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, + Ok(Async::NotReady) => { + return None; + } Ok(Async::Ready(resp)) => { info.count += 1; if let Some(resp) = resp { @@ -442,11 +450,11 @@ impl StartMiddlewares { } loop { if info.count == len { - let reply = info.handler.handle(info.req.clone()); + let reply = info.handler.handle(&info.req); return Some(WaitingResponse::init(info, reply)); } else { - let state = info.mws[info.count].start(&mut info.req); - match state { + let result = info.mws[info.count].start(&info.req); + match result { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return Some(RunMiddlewares::init(info, resp)); @@ -456,21 +464,25 @@ impl StartMiddlewares { continue 'outer; } Err(err) => { - return Some(RunMiddlewares::init(info, err.into())) + return Some(RunMiddlewares::init(info, err.into())); } } } } } - Err(err) => return Some(RunMiddlewares::init(info, err.into())), + Err(err) => { + return Some(RunMiddlewares::init(info, err.into())); + } } } } } +type HandlerFuture = Future; + // waiting for response struct WaitingResponse { - fut: Box>, + fut: Box, _s: PhantomData, } @@ -480,8 +492,8 @@ impl WaitingResponse { info: &mut ComposeInfo, reply: AsyncResult, ) -> ComposeState { match reply.into() { - AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), + AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { fut, _s: PhantomData, @@ -492,7 +504,7 @@ impl WaitingResponse { fn poll(&mut self, info: &mut ComposeInfo) -> Option> { match self.fut.poll() { Ok(Async::NotReady) => None, - Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), + Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), Err(err) => Some(RunMiddlewares::init(info, err.into())), } } @@ -511,7 +523,7 @@ impl RunMiddlewares { let len = info.mws.len(); loop { - let state = info.mws[curr].response(&mut info.req, resp); + let state = info.mws[curr].response(&info.req, resp); resp = match state { Err(err) => { info.count = curr + 1; @@ -530,7 +542,7 @@ impl RunMiddlewares { curr, fut: Some(fut), _s: PhantomData, - }) + }); } }; } @@ -554,7 +566,7 @@ impl RunMiddlewares { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { - let state = info.mws[self.curr].response(&mut info.req, resp); + let state = info.mws[self.curr].response(&info.req, resp); match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) @@ -625,7 +637,7 @@ impl FinishingMiddlewares { info.count -= 1; let state = info.mws[info.count as usize] - .finish(&mut info.req, self.resp.as_ref().unwrap()); + .finish(&info.req, self.resp.as_ref().unwrap()); match state { MiddlewareFinished::Done => { if info.count == 0 { diff --git a/src/router.rs b/src/router.rs index fcbb0d44..f4da7da0 100644 --- a/src/router.rs +++ b/src/router.rs @@ -4,29 +4,133 @@ use std::rc::Rc; use regex::{escape, Regex}; use smallvec::SmallVec; +use url::Url; use error::UrlGenerationError; use httprequest::HttpRequest; -use param::ParamItem; +use param::{ParamItem, Params}; use resource::ResourceHandler; -use server::ServerSettings; +use server::Request; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub(crate) enum RouterResource { + Notset, + Normal(u16), +} /// Interface for application router. pub struct Router(Rc); +#[derive(Clone)] +pub struct RouteInfo { + router: Rc, + resource: RouterResource, + prefix: u16, + params: Params, +} + +impl RouteInfo { + /// This method returns reference to matched `Resource` object. + #[inline] + pub fn resource(&self) -> Option<&Resource> { + if let RouterResource::Normal(idx) = self.resource { + Some(&self.router.patterns[idx as usize]) + } else { + None + } + } + + /// Get a reference to the Params object. + /// + /// Params is a container for url parameters. + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. + #[inline] + pub fn match_info(&self) -> &Params { + &self.params + } + + #[doc(hidden)] + #[inline] + pub fn prefix_len(&self) -> u16 { + self.prefix + } + + #[inline] + pub(crate) fn merge(&self, mut params: Params) -> RouteInfo { + let mut p = self.params.clone(); + p.set_tail(params.tail); + for item in ¶ms.segments { + p.add(item.0.clone(), item.1.clone()); + } + + RouteInfo { + params: p, + router: self.router.clone(), + resource: self.resource, + prefix: self.prefix, + } + } + + /// Generate url for named resource + /// + /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. + /// url_for) for detailed information. + pub fn url_for( + &self, req: &Request, name: &str, elements: U, + ) -> Result + where + U: IntoIterator, + I: AsRef, + { + if let Some(pattern) = self.router.named.get(name) { + let path = pattern.0.resource_path(elements, &self.router.prefix)?; + if path.starts_with('/') { + let conn = req.connection_info(); + Ok(Url::parse(&format!( + "{}://{}{}", + conn.scheme(), + conn.host(), + path + ))?) + } else { + Ok(Url::parse(&path)?) + } + } else { + Err(UrlGenerationError::ResourceNotFound) + } + } + + /// 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 path = if path.is_empty() { "/" } else { path }; + + for pattern in &self.router.patterns { + if pattern.is_match(path) { + return true; + } + } + false + } +} + struct Inner { prefix: String, prefix_len: usize, named: HashMap, patterns: Vec, - srv: ServerSettings, } impl Router { /// Create new router pub fn new( - prefix: &str, settings: ServerSettings, - map: Vec<(Resource, Option>)>, + prefix: &str, map: Vec<(Resource, Option>)>, ) -> (Router, Vec>) { let prefix = prefix.trim().trim_right_matches('/').to_owned(); let mut named = HashMap::new(); @@ -52,7 +156,6 @@ impl Router { prefix_len, named, patterns, - srv: settings, })), resources, ) @@ -64,67 +167,61 @@ impl Router { &self.0.prefix } - /// Server settings - #[inline] - pub fn server_settings(&self) -> &ServerSettings { - &self.0.srv - } - pub(crate) fn get_resource(&self, idx: usize) -> &Resource { &self.0.patterns[idx] } + pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> RouteInfo { + let mut params = Params::with_url(req.url()); + params.set_tail(prefix); + + RouteInfo { + params, + router: self.0.clone(), + resource: RouterResource::Notset, + prefix: 0, + } + } + + pub(crate) fn route_info_params(&self, params: Params, prefix: u16) -> RouteInfo { + RouteInfo { + params, + prefix, + router: self.0.clone(), + resource: RouterResource::Notset, + } + } + + pub(crate) fn default_route_info(&self, prefix: u16) -> RouteInfo { + RouteInfo { + prefix, + router: self.0.clone(), + resource: RouterResource::Notset, + params: Params::new(), + } + } + /// Query for matched resource - pub fn recognize(&self, req: &mut HttpRequest) -> Option { + pub fn recognize(&self, req: &Request) -> Option<(usize, RouteInfo)> { if self.0.prefix_len > req.path().len() { return None; } for (idx, pattern) in self.0.patterns.iter().enumerate() { - if pattern.match_with_params(req, self.0.prefix_len, true) { - let url = req.url().clone(); - req.match_info_mut().set_url(url); - req.set_resource(idx); - req.set_prefix_len(self.0.prefix_len as u16); - return Some(idx); + if let Some(params) = pattern.match_with_params(req, self.0.prefix_len, true) + { + return Some(( + idx, + RouteInfo { + params, + router: self.0.clone(), + resource: RouterResource::Normal(idx as u16), + prefix: self.0.prefix_len as u16, + }, + )); } } None } - - /// 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 path = if path.is_empty() { "/" } else { path }; - - for pattern in &self.0.patterns { - if pattern.is_match(path) { - return true; - } - } - false - } - - /// 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, - I: AsRef, - { - if let Some(pattern) = self.0.named.get(name) { - pattern.0.resource_path(self, elements) - } else { - Err(UrlGenerationError::ResourceNotFound) - } - } } impl Clone for Router { @@ -160,7 +257,7 @@ pub enum ResourceType { } /// Resource type describes an entry in resources table -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Resource { tp: PatternType, rtp: ResourceType, @@ -208,9 +305,7 @@ impl Resource { // actix creates one router per thread let names = re .capture_names() - .filter_map(|name| { - name.map(|name| Rc::new(name.to_owned())) - }) + .filter_map(|name| name.map(|name| Rc::new(name.to_owned()))) .collect(); PatternType::Dynamic(re, names, len) } else if for_prefix { @@ -253,127 +348,128 @@ impl Resource { } /// Are the given path and parameters a match against this resource? - pub fn match_with_params( - &self, req: &mut HttpRequest, plen: usize, insert: bool, - ) -> bool { - let mut segments: SmallVec<[ParamItem; 5]> = SmallVec::new(); - - let names = { - let path = &req.path()[plen..]; - if insert { - if path.is_empty() { - "/" - } else { - path - } + pub fn match_with_params( + &self, req: &Request, plen: usize, insert: bool, + ) -> Option { + let path = &req.path()[plen..]; + if insert { + if path.is_empty() { + "/" } else { path - }; - - match self.tp { - PatternType::Static(ref s) => return s == path, - PatternType::Dynamic(ref re, ref names, _) => { - if let Some(captures) = re.captures(path) { - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - segments.push(ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - )); - } - } - names - } else { - return false; - } - } - PatternType::Prefix(ref s) => return path.starts_with(s), } + } else { + path }; - let len = req.path().len(); - let params = req.match_info_mut(); - params.set_tail(len as u16); - for (idx, segment) in segments.into_iter().enumerate() { - params.add(names[idx].clone(), segment); + match self.tp { + PatternType::Static(ref s) => if s != path { + None + } else { + Some(Params::with_url(req.url())) + }, + PatternType::Dynamic(ref re, ref names, _) => { + if let Some(captures) = re.captures(path) { + let mut params = Params::with_url(req.url()); + let mut idx = 0; + let mut passed = false; + for capture in captures.iter() { + if let Some(ref m) = capture { + if !passed { + passed = true; + continue; + } + params.add( + names[idx].clone(), + ParamItem::UrlSegment( + (plen + m.start()) as u16, + (plen + m.end()) as u16, + ), + ); + idx += 1; + } + } + params.set_tail(req.path().len() as u16); + Some(params) + } else { + None + } + } + PatternType::Prefix(ref s) => if !path.starts_with(s) { + None + } else { + Some(Params::with_url(req.url())) + }, } - true } /// Is the given path a prefix match and do the parameters match against this resource? - pub fn match_prefix_with_params( - &self, req: &mut HttpRequest, plen: usize, - ) -> Option { - let mut segments: SmallVec<[ParamItem; 5]> = SmallVec::new(); + pub fn match_prefix_with_params( + &self, req: &Request, plen: usize, + ) -> Option { + let path = &req.path()[plen..]; + let path = if path.is_empty() { "/" } else { path }; - let (names, tail_len) = { - let path = &req.path()[plen..]; - let path = if path.is_empty() { "/" } else { path }; + match self.tp { + PatternType::Static(ref s) => if s == path { + Some(Params::with_url(req.url())) + } else { + None + }, + PatternType::Dynamic(ref re, ref names, len) => { + if let Some(captures) = re.captures(path) { + let mut params = Params::with_url(req.url()); + let mut pos = 0; + let mut passed = false; + let mut idx = 0; + for capture in captures.iter() { + if let Some(ref m) = capture { + if !passed { + passed = true; + continue; + } - match self.tp { - PatternType::Static(ref s) => if s == path { - return Some(s.len()); - } else { - return None; - }, - PatternType::Dynamic(ref re, ref names, len) => { - if let Some(captures) = re.captures(path) { - let mut pos = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - segments.push(ParamItem::UrlSegment( + params.add( + names[idx].clone(), + ParamItem::UrlSegment( (plen + m.start()) as u16, (plen + m.end()) as u16, - )); - pos = m.end(); - } + ), + ); + idx += 1; + pos = m.end(); } - (names, pos + len) - } else { - return None; - } - } - PatternType::Prefix(ref s) => { - return if path == s { - Some(s.len()) - } else if path.starts_with(s) - && (s.ends_with('/') - || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - Some(s.len() - 1) - } else { - Some(s.len()) - } - } else { - None } + params.set_tail((plen + pos + len) as u16); + Some(params) + } else { + None } } - }; - - let params = req.match_info_mut(); - params.set_tail(tail_len as u16); - for (idx, segment) in segments.into_iter().enumerate() { - params.add(names[idx].clone(), segment); + PatternType::Prefix(ref s) => { + let len = if path == s { + s.len() + } else if path.starts_with(s) + && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) + { + if s.ends_with('/') { + s.len() - 1 + } else { + s.len() + } + } else { + return None; + }; + let mut params = Params::with_url(req.url()); + params.set_tail((plen + len) as u16); + Some(params) + } } - Some(tail_len) } /// Build resource path. pub fn resource_path( - &self, router: &Router, elements: U, + &self, elements: U, prefix: &str, ) -> Result where U: IntoIterator, @@ -402,7 +498,6 @@ impl Resource { }; if self.rtp != ResourceType::External { - let prefix = router.prefix(); if prefix.ends_with('/') { if path.starts_with('/') { path.insert_str(0, &prefix[..prefix.len() - 1]); @@ -546,44 +641,57 @@ mod tests { Some(ResourceHandler::default()), ), ]; - let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); + let (rec, _) = Router::new::<()>("", routes); - let mut req = TestRequest::with_uri("/name").finish(); - assert_eq!(rec.recognize(&mut req), Some(0)); + let req = TestRequest::with_uri("/name").finish(); + assert_eq!(rec.recognize(&req).unwrap().0, 0); assert!(req.match_info().is_empty()); - let mut req = TestRequest::with_uri("/name/value").finish(); - assert_eq!(rec.recognize(&mut req), Some(1)); + let req = TestRequest::with_uri("/name/value").finish(); + let info = rec.recognize(&req).unwrap().1; + let req = req.with_route_info(info); assert_eq!(req.match_info().get("val").unwrap(), "value"); assert_eq!(&req.match_info()["val"], "value"); - let mut req = TestRequest::with_uri("/name/value2/index.html").finish(); - assert_eq!(rec.recognize(&mut req), Some(2)); + let req = TestRequest::with_uri("/name/value2/index.html").finish(); + let info = rec.recognize(&req).unwrap(); + assert_eq!(info.0, 2); + let req = req.with_route_info(info.1); assert_eq!(req.match_info().get("val").unwrap(), "value2"); - let mut req = TestRequest::with_uri("/file/file.gz").finish(); - assert_eq!(rec.recognize(&mut req), Some(3)); + let req = TestRequest::with_uri("/file/file.gz").finish(); + let info = rec.recognize(&req).unwrap(); + assert_eq!(info.0, 3); + let req = req.with_route_info(info.1); assert_eq!(req.match_info().get("file").unwrap(), "file"); assert_eq!(req.match_info().get("ext").unwrap(), "gz"); - let mut req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); - assert_eq!(rec.recognize(&mut req), Some(4)); + let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); + let info = rec.recognize(&req).unwrap(); + assert_eq!(info.0, 4); + let req = req.with_route_info(info.1); assert_eq!(req.match_info().get("val").unwrap(), "test"); assert_eq!(req.match_info().get("val2").unwrap(), "ttt"); - let mut req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); - assert_eq!(rec.recognize(&mut req), Some(5)); + let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); + let info = rec.recognize(&req).unwrap(); + assert_eq!(info.0, 5); + let req = req.with_route_info(info.1); assert_eq!( req.match_info().get("tail").unwrap(), "blah-blah/index.html" ); - let mut req = TestRequest::with_uri("/test2/index.html").finish(); - assert_eq!(rec.recognize(&mut req), Some(6)); + let req = TestRequest::with_uri("/test2/index.html").finish(); + let info = rec.recognize(&req).unwrap(); + assert_eq!(info.0, 6); + let req = req.with_route_info(info.1); assert_eq!(req.match_info().get("test").unwrap(), "index"); - let mut req = TestRequest::with_uri("/bbb/index.html").finish(); - assert_eq!(rec.recognize(&mut req), Some(7)); + let req = TestRequest::with_uri("/bbb/index.html").finish(); + let info = rec.recognize(&req).unwrap(); + assert_eq!(info.0, 7); + let req = req.with_route_info(info.1); assert_eq!(req.match_info().get("test").unwrap(), "bbb"); } @@ -599,13 +707,13 @@ mod tests { Some(ResourceHandler::default()), ), ]; - let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); + let (rec, _) = Router::new::<()>("", routes); - let mut req = TestRequest::with_uri("/index.json").finish(); - assert_eq!(rec.recognize(&mut req), Some(0)); + let req = TestRequest::with_uri("/index.json").finish(); + assert_eq!(rec.recognize(&req).unwrap().0, 0); - let mut req = TestRequest::with_uri("/test.json").finish(); - assert_eq!(rec.recognize(&mut req), Some(1)); + let req = TestRequest::with_uri("/test.json").finish(); + assert_eq!(rec.recognize(&req).unwrap().0, 1); } #[test] @@ -617,16 +725,18 @@ mod tests { Some(ResourceHandler::default()), ), ]; - let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes); + let (rec, _) = Router::new::<()>("/test", routes); - let mut req = TestRequest::with_uri("/name").finish(); - assert!(rec.recognize(&mut req).is_none()); + let req = TestRequest::with_uri("/name").finish(); + assert!(rec.recognize(&req).is_none()); - let mut req = TestRequest::with_uri("/test/name").finish(); - assert_eq!(rec.recognize(&mut req), Some(0)); + let req = TestRequest::with_uri("/test/name").finish(); + assert_eq!(rec.recognize(&req).unwrap().0, 0); - let mut req = TestRequest::with_uri("/test/name/value").finish(); - assert_eq!(rec.recognize(&mut req), Some(1)); + let req = TestRequest::with_uri("/test/name/value").finish(); + let info = rec.recognize(&req).unwrap(); + assert_eq!(info.0, 1); + let req = req.with_route_info(info.1); assert_eq!(req.match_info().get("val").unwrap(), "value"); assert_eq!(&req.match_info()["val"], "value"); @@ -638,16 +748,18 @@ mod tests { Some(ResourceHandler::default()), ), ]; - let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes); + let (rec, _) = Router::new::<()>("/test2", 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_eq!(rec.recognize(&mut req), Some(0)); - let mut req = TestRequest::with_uri("/test2/name-test").finish(); - assert!(rec.recognize(&mut req).is_none()); - let mut req = TestRequest::with_uri("/test2/name/ttt").finish(); - assert_eq!(rec.recognize(&mut req), Some(1)); + let req = TestRequest::with_uri("/name").finish(); + assert!(rec.recognize(&req).is_none()); + let req = TestRequest::with_uri("/test2/name").finish(); + assert_eq!(rec.recognize(&req).unwrap().0, 0); + let req = TestRequest::with_uri("/test2/name-test").finish(); + assert!(rec.recognize(&req).is_none()); + let req = TestRequest::with_uri("/test2/name/ttt").finish(); + let info = rec.recognize(&req).unwrap(); + assert_eq!(info.0, 1); + let req = req.with_route_info(info.1); assert_eq!(&req.match_info()["val"], "ttt"); } @@ -681,29 +793,23 @@ mod tests { assert!(!re.is_match("/user/2345/")); assert!(!re.is_match("/user/2345/sdg")); - let mut req = TestRequest::with_uri("/user/profile").finish(); - let url = req.url().clone(); - req.match_info_mut().set_url(url); - assert!(re.match_with_params(&mut req, 0, true)); - assert_eq!(req.match_info().get("id").unwrap(), "profile"); + let req = TestRequest::with_uri("/user/profile").finish(); + let info = re.match_with_params(&req, 0, true).unwrap(); + assert_eq!(info.get("id").unwrap(), "profile"); - let mut req = TestRequest::with_uri("/user/1245125").finish(); - let url = req.url().clone(); - req.match_info_mut().set_url(url); - assert!(re.match_with_params(&mut req, 0, true)); - assert_eq!(req.match_info().get("id").unwrap(), "1245125"); + let req = TestRequest::with_uri("/user/1245125").finish(); + let info = re.match_with_params(&req, 0, true).unwrap(); + assert_eq!(info.get("id").unwrap(), "1245125"); let re = Resource::new("test", "/v{version}/resource/{id}"); assert!(re.is_match("/v1/resource/320120")); assert!(!re.is_match("/v/resource/1")); assert!(!re.is_match("/resource")); - let mut req = TestRequest::with_uri("/v151/resource/adahg32").finish(); - let url = req.url().clone(); - req.match_info_mut().set_url(url); - assert!(re.match_with_params(&mut req, 0, true)); - assert_eq!(req.match_info().get("version").unwrap(), "151"); - assert_eq!(req.match_info().get("id").unwrap(), "adahg32"); + let req = TestRequest::with_uri("/v151/resource/adahg32").finish(); + let info = re.match_with_params(&req, 0, true).unwrap(); + assert_eq!(info.get("version").unwrap(), "151"); + assert_eq!(info.get("id").unwrap(), "adahg32"); } #[test] @@ -728,18 +834,15 @@ mod tests { assert!(re.is_match("/name/gs")); assert!(!re.is_match("/name")); - let mut req = TestRequest::with_uri("/test2/").finish(); - let url = req.url().clone(); - req.match_info_mut().set_url(url); - assert!(re.match_with_params(&mut req, 0, true)); - assert_eq!(&req.match_info()["name"], "test2"); + let req = TestRequest::with_uri("/test2/").finish(); + let info = re.match_with_params(&req, 0, true).unwrap(); + assert_eq!(&info["name"], "test2"); + assert_eq!(&info[0], "test2"); - let mut req = - TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); - let url = req.url().clone(); - req.match_info_mut().set_url(url); - assert!(re.match_with_params(&mut req, 0, true)); - assert_eq!(&req.match_info()["name"], "test2"); + let req = TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); + let info = re.match_with_params(&req, 0, true).unwrap(); + assert_eq!(&info["name"], "test2"); + assert_eq!(&info[0], "test2"); } #[test] @@ -754,18 +857,18 @@ mod tests { Some(ResourceHandler::default()), ), ]; - let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); + let (router, _) = Router::new::<()>("", routes); - let mut req = - TestRequest::with_uri("/index.json").finish_with_router(router.clone()); - assert_eq!(router.recognize(&mut req), Some(0)); - let resource = req.resource().unwrap(); + let req = TestRequest::with_uri("/index.json").finish(); + assert_eq!(router.recognize(&req).unwrap().0, 0); + let info = router.recognize(&req).unwrap().1; + let resource = info.resource().unwrap(); assert_eq!(resource.name(), "r1"); - let mut req = - TestRequest::with_uri("/test.json").finish_with_router(router.clone()); - assert_eq!(router.recognize(&mut req), Some(1)); - let resource = req.resource().unwrap(); + let req = TestRequest::with_uri("/test.json").finish(); + assert_eq!(router.recognize(&req).unwrap().0, 1); + let info = router.recognize(&req).unwrap().1; + let resource = info.resource().unwrap(); assert_eq!(resource.name(), "r2"); } } diff --git a/src/scope.rs b/src/scope.rs index c9e7dfb9..5db1562b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -14,8 +14,9 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; -use resource::ResourceHandler; +use resource::{ResourceHandler, RouteId}; use router::Resource; +use server::Request; type ScopeResource = Rc>; type Route = Box>; @@ -114,7 +115,7 @@ impl Scope { /// /// struct AppState; /// - /// fn index(req: HttpRequest) -> &'static str { + /// fn index(req: &HttpRequest) -> &'static str { /// "Welcome!" /// } /// @@ -159,7 +160,7 @@ impl Scope { /// /// struct AppState; /// - /// fn index(req: HttpRequest) -> &'static str { + /// fn index(req: &HttpRequest) -> &'static str { /// "Welcome!" /// } /// @@ -334,75 +335,61 @@ impl Scope { } impl RouteHandler for Scope { - fn handle(&self, mut req: HttpRequest) -> AsyncResult { + fn handle(&self, req: &HttpRequest) -> AsyncResult { let tail = req.match_info().tail as usize; // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { - if pattern.match_with_params(&mut req, tail, false) { - if self.middlewares.is_empty() { - return match resource.handle(req) { - Ok(result) => result, - Err(req) => { - if let Some(ref default) = self.default { - match default.handle(req) { - Ok(result) => result, - Err(_) => AsyncResult::ok(HttpResponse::new( - StatusCode::NOT_FOUND, - )), - } - } else { - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) - } - } - }; - } else { - return AsyncResult::async(Box::new(Compose::new( - req, - Rc::clone(&self.middlewares), - Rc::clone(&resource), - ))); + if let Some(params) = pattern.match_with_params(req, tail, false) { + let req2 = req.with_route_info(req.route().merge(params)); + if let Some(id) = resource.get_route_id(&req2) { + if self.middlewares.is_empty() { + return resource.handle(id, &req2); + } else { + return AsyncResult::async(Box::new(Compose::new( + id, + req2, + Rc::clone(&self.middlewares), + Rc::clone(&resource), + ))); + } } } } // nested scopes - let len = req.prefix_len() as usize; + let len = req.route().prefix_len() as usize; 'outer: for &(ref prefix, ref handler, ref filters) in &self.nested { - if let Some(prefix_len) = prefix.match_prefix_with_params(&mut req, len) { + if let Some(params) = prefix.match_prefix_with_params(req, tail) { + let req2 = req.with_route_info(req.route().merge(params)); + + let state = req.state(); for filter in filters { - if !filter.check(&mut req) { + if !filter.check(&req2, state) { continue 'outer; } } - let url = req.url().clone(); - let prefix_len = (len + prefix_len) as u16; - req.set_prefix_len(prefix_len); - req.match_info_mut().set_tail(prefix_len); - req.match_info_mut().set_url(url); - return handler.handle(req); + return handler.handle(&req2); } } // default handler - if self.middlewares.is_empty() { - if let Some(ref default) = self.default { - match default.handle(req) { - Ok(result) => result, - Err(_) => AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)), + if let Some(ref resource) = self.default { + if let Some(id) = resource.get_route_id(req) { + if self.middlewares.is_empty() { + return resource.handle(id, req); + } else { + return AsyncResult::async(Box::new(Compose::new( + id, + req.clone(), + Rc::clone(&self.middlewares), + Rc::clone(resource), + ))); } - } else { - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) } - } else if let Some(ref default) = self.default { - AsyncResult::async(Box::new(Compose::new( - req, - Rc::clone(&self.middlewares), - Rc::clone(default), - ))) - } else { - unimplemented!() } + + AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) } fn has_default_resource(&self) -> bool { @@ -420,8 +407,9 @@ struct Wrapper { } impl RouteHandler for Wrapper { - fn handle(&self, req: HttpRequest) -> AsyncResult { - self.scope.handle(req.change_state(Rc::clone(&self.state))) + fn handle(&self, req: &HttpRequest) -> AsyncResult { + let req = req.with_state(Rc::clone(&self.state)); + self.scope.handle(&req) } } @@ -431,10 +419,9 @@ struct FiltersWrapper { } impl Predicate for FiltersWrapper { - fn check(&self, req: &mut HttpRequest) -> bool { - let mut req = req.change_state(Rc::clone(&self.state)); + fn check(&self, req: &Request, _: &S2) -> bool { for filter in &self.filters { - if !filter.check(&mut req) { + if !filter.check(&req, &self.state) { return false; } } @@ -450,6 +437,7 @@ struct Compose { struct ComposeInfo { count: usize, + id: RouteId, req: HttpRequest, mws: Rc>>>, resource: Rc>, @@ -477,14 +465,15 @@ impl ComposeState { impl Compose { fn new( - req: HttpRequest, mws: Rc>>>, + id: RouteId, req: HttpRequest, mws: Rc>>>, resource: Rc>, ) -> Self { let mut info = ComposeInfo { - count: 0, - req, + id, mws, + req, resource, + count: 0, }; let state = StartMiddlewares::init(&mut info); @@ -522,27 +511,27 @@ type Fut = Box, Error = Error>>; impl StartMiddlewares { fn init(info: &mut ComposeInfo) -> ComposeState { let len = info.mws.len(); + loop { if info.count == len { - let reply = { - let req = info.req.clone(); - info.resource.handle(req).unwrap() - }; + let reply = info.resource.handle(info.id, &info.req); return WaitingResponse::init(info, reply); } else { - let state = info.mws[info.count].start(&mut info.req); - match state { + let result = info.mws[info.count].start(&info.req); + match result { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp) + return RunMiddlewares::init(info, resp); } Ok(MiddlewareStarted::Future(fut)) => { return ComposeState::Starting(StartMiddlewares { fut: Some(fut), _s: PhantomData, - }) + }); + } + Err(err) => { + return RunMiddlewares::init(info, err.into()); } - Err(err) => return RunMiddlewares::init(info, err.into()), } } } @@ -550,24 +539,25 @@ impl StartMiddlewares { fn poll(&mut self, info: &mut ComposeInfo) -> Option> { let len = info.mws.len(); + 'outer: loop { match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, + Ok(Async::NotReady) => { + return None; + } Ok(Async::Ready(resp)) => { info.count += 1; + if let Some(resp) = resp { return Some(RunMiddlewares::init(info, resp)); } loop { if info.count == len { - let reply = { - let req = info.req.clone(); - info.resource.handle(req).unwrap() - }; + let reply = { info.resource.handle(info.id, &info.req) }; return Some(WaitingResponse::init(info, reply)); } else { - let state = info.mws[info.count].start(&mut info.req); - match state { + let result = info.mws[info.count].start(&info.req); + match result { Ok(MiddlewareStarted::Done) => info.count += 1, Ok(MiddlewareStarted::Response(resp)) => { return Some(RunMiddlewares::init(info, resp)); @@ -577,13 +567,15 @@ impl StartMiddlewares { continue 'outer; } Err(err) => { - return Some(RunMiddlewares::init(info, err.into())) + return Some(RunMiddlewares::init(info, err.into())); } } } } } - Err(err) => return Some(RunMiddlewares::init(info, err.into())), + Err(err) => { + return Some(RunMiddlewares::init(info, err.into())); + } } } } @@ -613,7 +605,7 @@ impl WaitingResponse { fn poll(&mut self, info: &mut ComposeInfo) -> Option> { match self.fut.poll() { Ok(Async::NotReady) => None, - Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)), + Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), Err(err) => Some(RunMiddlewares::init(info, err.into())), } } @@ -632,7 +624,7 @@ impl RunMiddlewares { let len = info.mws.len(); loop { - let state = info.mws[curr].response(&mut info.req, resp); + let state = info.mws[curr].response(&info.req, resp); resp = match state { Err(err) => { info.count = curr + 1; @@ -651,7 +643,7 @@ impl RunMiddlewares { curr, fut: Some(fut), _s: PhantomData, - }) + }); } }; } @@ -675,7 +667,7 @@ impl RunMiddlewares { if self.curr == len { return Some(FinishingMiddlewares::init(info, resp)); } else { - let state = info.mws[self.curr].response(&mut info.req, resp); + let state = info.mws[self.curr].response(&info.req, resp); match state { Err(err) => { return Some(FinishingMiddlewares::init(info, err.into())) @@ -745,7 +737,7 @@ impl FinishingMiddlewares { info.count -= 1; let state = info.mws[info.count as usize] - .finish(&mut info.req, self.resp.as_ref().unwrap()); + .finish(&info.req, self.resp.as_ref().unwrap()); match state { MiddlewareFinished::Done => { if info.count == 0 { @@ -794,7 +786,7 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/path1").finish(); + let req = TestRequest::with_uri("/app/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } @@ -809,11 +801,11 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app").finish(); + let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/").finish(); + let req = TestRequest::with_uri("/app/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } @@ -826,11 +818,11 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app").finish(); + let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/").finish(); + let req = TestRequest::with_uri("/app/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } @@ -843,11 +835,11 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app").finish(); + let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/").finish(); + let req = TestRequest::with_uri("/app/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -866,19 +858,19 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/path1").finish(); + let req = TestRequest::with_uri("/app/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::DELETE) - .finish(); + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) - .finish(); + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -895,13 +887,13 @@ mod tests { let req = TestRequest::with_uri("/app/path1") .method(Method::POST) - .finish(); + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/path1") .method(Method::GET) - .finish(); + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } @@ -919,7 +911,7 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/ab-project1/path1").finish(); + let req = TestRequest::with_uri("/ab-project1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); @@ -931,7 +923,7 @@ mod tests { _ => panic!(), } - let req = TestRequest::with_uri("/aa-project1/path1").finish(); + let req = TestRequest::with_uri("/aa-project1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -948,7 +940,7 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/t1/path1").finish(); + let req = TestRequest::with_uri("/app/t1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } @@ -967,11 +959,11 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/t1").finish(); + let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/t1/").finish(); + let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } @@ -988,11 +980,11 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/t1").finish(); + let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/t1/").finish(); + let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } @@ -1009,11 +1001,11 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/t1").finish(); + let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/t1/").finish(); + let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -1034,13 +1026,13 @@ mod tests { let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) - .finish(); + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::GET) - .finish(); + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } @@ -1055,7 +1047,7 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/t1/path1").finish(); + let req = TestRequest::with_uri("/app/t1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } @@ -1072,11 +1064,11 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/t1").finish(); + let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/t1/").finish(); + let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } @@ -1095,13 +1087,13 @@ mod tests { let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) - .finish(); + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::GET) - .finish(); + .request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::OK); } @@ -1123,7 +1115,7 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/project_1/path1").finish(); + let req = TestRequest::with_uri("/app/project_1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); @@ -1156,7 +1148,7 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/test/1/path1").finish(); + let req = TestRequest::with_uri("/app/test/1/path1").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::CREATED); @@ -1168,7 +1160,7 @@ mod tests { _ => panic!(), } - let req = TestRequest::with_uri("/app/test/1/path2").finish(); + let req = TestRequest::with_uri("/app/test/1/path2").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -1183,11 +1175,11 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/path2").finish(); + let req = TestRequest::with_uri("/app/path2").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/path2").finish(); + let req = TestRequest::with_uri("/path2").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } @@ -1202,15 +1194,15 @@ mod tests { .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); - let req = TestRequest::with_uri("/non-exist").finish(); + let req = TestRequest::with_uri("/non-exist").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - let req = TestRequest::with_uri("/app1/non-exist").finish(); + let req = TestRequest::with_uri("/app1/non-exist").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/app2/non-exist").finish(); + let req = TestRequest::with_uri("/app2/non-exist").request(); let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); } diff --git a/src/server/error.rs b/src/server/error.rs new file mode 100644 index 00000000..79b3e4f0 --- /dev/null +++ b/src/server/error.rs @@ -0,0 +1,25 @@ +use futures::{Async, Poll}; + +use super::{helpers, HttpHandlerTask, Writer}; +use http::{StatusCode, Version}; +use httpresponse::HttpResponse; +use Error; + +pub(crate) struct ServerError(Version, StatusCode); + +impl ServerError { + pub fn err(ver: Version, status: StatusCode) -> Box { + Box::new(ServerError(ver, status)) + } +} + +impl HttpHandlerTask for ServerError { + fn poll_io(&mut self, io: &mut Writer) -> Poll { + { + let mut bytes = io.buffer(); + helpers::write_status_line(self.0, self.1.as_u16(), bytes); + } + io.set_date(); + Ok(Async::Ready(true)) + } +} diff --git a/src/server/h1.rs b/src/server/h1.rs index e358f84b..a9b3100f 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -8,11 +8,13 @@ use futures::{Async, Future, Poll}; use tokio_timer::Delay; use error::{Error, PayloadError}; +use http::{StatusCode, Version}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use payload::{Payload, PayloadStatus, PayloadWriter}; use pipeline::Pipeline; +use super::error::ServerError; use super::h1decoder::{DecoderError, H1Decoder, Message}; use super::h1writer::H1Writer; use super::input::PayloadType; @@ -180,9 +182,7 @@ where && self.tasks.len() < MAX_PIPELINED_MESSAGES && self.can_read() { - let res = self.stream.get_mut().read_available(&mut self.buf); - match res { - //self.stream.get_mut().read_available(&mut self.buf) { + match self.stream.get_mut().read_available(&mut self.buf) { Ok(Async::Ready(disconnected)) => { if disconnected { // notify all tasks @@ -363,22 +363,19 @@ where if payload { let (ps, pl) = Payload::new(false); - msg.get_mut().payload = Some(pl); - self.payload = - Some(PayloadType::new(&msg.get_ref().headers, ps)); + *msg.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); } - let mut req = HttpRequest::from_message(msg); - // set remote addr - req.set_peer_addr(self.addr); + msg.inner.addr = self.addr; // stop keepalive timer self.keepalive_timer.take(); // search handler for request for h in self.settings.handlers().iter_mut() { - req = match h.handle(req) { + msg = match h.handle(msg) { Ok(mut pipe) => { if self.tasks.is_empty() { match pipe.poll_io(&mut self.stream) { @@ -415,15 +412,16 @@ where }); continue 'outer; } - Err(req) => req, + Err(msg) => msg, } } // handler is not found self.tasks.push_back(Entry { - pipe: EntryPipe::Error( - Pipeline::error(HttpResponse::NotFound()), - ), + pipe: EntryPipe::Error(ServerError::err( + Version::HTTP_11, + StatusCode::NOT_FOUND, + )), flags: EntryFlags::empty(), }); } @@ -475,12 +473,11 @@ mod tests { use application::HttpApplication; use httpmessage::HttpMessage; use server::h1decoder::Message; - use server::helpers::SharedHttpInnerMessage; - use server::settings::WorkerSettings; - use server::KeepAlive; + use server::settings::{ServerSettings, WorkerSettings}; + use server::{KeepAlive, Request}; impl Message { - fn message(self) -> SharedHttpInnerMessage { + fn message(self) -> Request { match self { Message::Message { msg, payload: _ } => msg, _ => panic!("error"), @@ -509,9 +506,9 @@ mod tests { macro_rules! parse_ready { ($e:expr) => {{ let settings: WorkerSettings = - WorkerSettings::new(Vec::new(), KeepAlive::Os); + WorkerSettings::new(Vec::new(), KeepAlive::Os, ServerSettings::default()); match H1Decoder::new().decode($e, &settings) { - Ok(Some(msg)) => HttpRequest::from_message(msg.message()), + Ok(Some(msg)) => msg.message(), Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), } @@ -521,7 +518,7 @@ mod tests { macro_rules! expect_parse_err { ($e:expr) => {{ let settings: WorkerSettings = - WorkerSettings::new(Vec::new(), KeepAlive::Os); + WorkerSettings::new(Vec::new(), KeepAlive::Os, ServerSettings::default()); match H1Decoder::new().decode($e, &settings) { Err(err) => match err { @@ -605,6 +602,7 @@ mod tests { let settings = Rc::new(WorkerSettings::::new( Vec::new(), KeepAlive::Os, + ServerSettings::default(), )); let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); @@ -620,6 +618,7 @@ mod tests { let settings = Rc::new(WorkerSettings::::new( Vec::new(), KeepAlive::Os, + ServerSettings::default(), )); let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); @@ -631,12 +630,16 @@ mod tests { #[test] fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { Ok(Some(msg)) => { - let req = HttpRequest::from_message(msg.message()); + let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -648,7 +651,11 @@ mod tests { #[test] fn test_parse_partial() { let mut buf = BytesMut::from("PUT /test HTTP/1"); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { @@ -659,7 +666,7 @@ mod tests { buf.extend(b".1\r\n\r\n"); match reader.decode(&mut buf, &settings) { Ok(Some(msg)) => { - let mut req = HttpRequest::from_message(msg.message()); + let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::PUT); assert_eq!(req.path(), "/test"); @@ -671,12 +678,16 @@ mod tests { #[test] fn test_parse_post() { let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { Ok(Some(msg)) => { - let mut req = HttpRequest::from_message(msg.message()); + let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); assert_eq!(req.path(), "/test2"); @@ -689,12 +700,16 @@ mod tests { fn test_parse_body() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { Ok(Some(msg)) => { - let mut req = HttpRequest::from_message(msg.message()); + let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -716,12 +731,16 @@ mod tests { fn test_parse_body_crlf() { let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { Ok(Some(msg)) => { - let mut req = HttpRequest::from_message(msg.message()); + let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -742,14 +761,18 @@ mod tests { #[test] fn test_parse_partial_eof() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); buf.extend(b"\r\n"); match reader.decode(&mut buf, &settings) { Ok(Some(msg)) => { - let req = HttpRequest::from_message(msg.message()); + let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -761,7 +784,11 @@ mod tests { #[test] fn test_headers_split_field() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } @@ -775,7 +802,7 @@ mod tests { buf.extend(b"t: value\r\n\r\n"); match reader.decode(&mut buf, &settings) { Ok(Some(msg)) => { - let req = HttpRequest::from_message(msg.message()); + let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -792,10 +819,14 @@ mod tests { Set-Cookie: c1=cookie1\r\n\ Set-Cookie: c2=cookie2\r\n\r\n", ); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - let req = HttpRequest::from_message(msg.message()); + let req = msg.message(); let val: Vec<_> = req .headers() @@ -988,7 +1019,11 @@ mod tests { #[test] fn test_http_request_upgrade() { - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: upgrade\r\n\ @@ -998,7 +1033,7 @@ mod tests { let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.is_payload()); - let req = HttpRequest::from_message(msg.message()); + let req = msg.message(); assert!(!req.keep_alive()); assert!(req.upgrade()); assert_eq!( @@ -1054,12 +1089,16 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.is_payload()); - let req = HttpRequest::from_message(msg.message()); + let req = msg.message(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); @@ -1090,11 +1129,15 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.is_payload()); - let req = HttpRequest::from_message(msg.message()); + let req = msg.message(); assert!(req.chunked().unwrap()); buf.extend( @@ -1112,7 +1155,7 @@ mod tests { let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.is_payload()); - let req2 = HttpRequest::from_message(msg.message()); + let req2 = msg.message(); assert!(req2.chunked().unwrap()); assert_eq!(*req2.method(), Method::POST); assert!(req2.chunked().unwrap()); @@ -1124,12 +1167,16 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.is_payload()); - let req = HttpRequest::from_message(msg.message()); + let req = msg.message(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\n1111\r\n"); @@ -1171,13 +1218,16 @@ mod tests { &"GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n"[..], ); - let settings = WorkerSettings::::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.is_payload()); - let req = HttpRequest::from_message(msg.message()); - assert!(req.chunked().unwrap()); + assert!(msg.message().chunked().unwrap()); buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 9815b936..9f14bb47 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -4,12 +4,11 @@ use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use httparse; -use super::helpers::SharedHttpInnerMessage; +use super::message::{MessageFlags, Request}; use super::settings::WorkerSettings; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HttpTryFrom, Method, Uri, Version}; -use httprequest::MessageFlags; use uri::Url; const MAX_BUFFER_SIZE: usize = 131_072; @@ -20,10 +19,7 @@ pub(crate) struct H1Decoder { } pub(crate) enum Message { - Message { - msg: SharedHttpInnerMessage, - payload: bool, - }, + Message { msg: Request, payload: bool }, Chunk(Bytes), Eof, } @@ -84,7 +80,7 @@ impl H1Decoder { fn parse_message( &self, buf: &mut BytesMut, settings: &WorkerSettings, - ) -> Poll<(SharedHttpInnerMessage, Option), ParseError> { + ) -> Poll<(Request, Option), ParseError> { // Parse http message let mut has_upgrade = false; let mut chunked = false; @@ -122,11 +118,12 @@ impl H1Decoder { let slice = buf.split_to(len).freeze(); // convert headers - let mut msg = settings.get_http_message(); + let mut msg = settings.get_request_context(); { - let msg_mut = msg.get_mut(); - msg_mut + let inner = &mut msg.inner; + inner .flags + .get_mut() .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); for idx in headers[..headers_len].iter() { @@ -177,20 +174,20 @@ impl H1Decoder { } else { false }; - msg_mut.flags.set(MessageFlags::KEEPALIVE, ka); + inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); } _ => (), } - msg_mut.headers.append(name, value); + inner.headers.append(name, value); } else { return Err(ParseError::Header); } } - msg_mut.url = path; - msg_mut.method = method; - msg_mut.version = version; + inner.url = path; + inner.method = method; + inner.version = version; } msg }; @@ -202,7 +199,7 @@ impl H1Decoder { } else if let Some(len) = content_length { // Content-Length Some(EncodingDecoder::length(len)) - } else if has_upgrade || msg.get_ref().method == Method::CONNECT { + } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect Some(EncodingDecoder::eof()) } else { diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index b87891c2..70797804 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -7,14 +7,16 @@ use std::rc::Rc; use tokio_io::AsyncWrite; use super::helpers; -use super::output::Output; +use super::output::{Output, ResponseInfo, ResponseLength}; use super::settings::WorkerSettings; +use super::Request; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; use header::ContentEncoding; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE}; +use http::header::{ + HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; use http::{Method, Version}; -use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific @@ -101,8 +103,8 @@ impl Writer for H1Writer { } #[inline] - fn set_date(&self, dst: &mut BytesMut) { - self.settings.set_date(dst, true) + fn set_date(&mut self) { + self.settings.set_date(self.buffer.as_mut(), true) } #[inline] @@ -111,11 +113,11 @@ impl Writer for H1Writer { } fn start( - &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, - encoding: ContentEncoding, + &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding, ) -> io::Result { // prepare task - self.buffer.for_server(req, msg, encoding); + let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); + self.buffer.for_server(&mut info, &req.inner, msg, encoding); if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { self.flags = Flags::STARTED | Flags::KEEPALIVE; } else { @@ -123,7 +125,7 @@ impl Writer for H1Writer { } // Connection upgrade - let version = msg.version().unwrap_or_else(|| req.version); + let version = msg.version().unwrap_or_else(|| req.inner.version); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); msg.headers_mut() @@ -166,16 +168,29 @@ impl Writer for H1Writer { helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); buffer.extend_from_slice(reason); - match body { - Body::Empty => if req.method != Method::HEAD { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"); - } else { - buffer.extend_from_slice(b"\r\n"); - }, - Body::Binary(ref bytes) => { - helpers::write_content_length(bytes.len(), &mut buffer) + // content length + match info.length { + ResponseLength::Chunked => { + buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - _ => buffer.extend_from_slice(b"\r\n"), + ResponseLength::Zero => { + buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") + } + ResponseLength::Length(len) => { + helpers::write_content_length(len, &mut buffer) + } + ResponseLength::Length64(len) => { + let s = format!("{}", len); + buffer.extend_from_slice(b"\r\ncontent-length: "); + buffer.extend_from_slice(s.as_ref()); + buffer.extend_from_slice(b"\r\n"); + } + ResponseLength::None => buffer.extend_from_slice(b"\r\n"), + } + if let Some(ce) = info.content_encoding { + buffer.extend_from_slice(b"content-encoding: "); + buffer.extend_from_slice(ce.as_ref()); + buffer.extend_from_slice(b"\r\n"); } // write headers @@ -185,9 +200,13 @@ impl Writer for H1Writer { unsafe { let mut buf = &mut *(buffer.bytes_mut() as *mut [u8]); for (key, value) in msg.headers() { - if is_bin && key == CONTENT_LENGTH { - is_bin = false; - continue; + match *key { + TRANSFER_ENCODING | CONTENT_ENCODING => continue, + CONTENT_LENGTH => match info.length { + ResponseLength::None => (), + _ => continue, + }, + _ => (), } has_date = has_date || key == DATE; let v = value.as_ref(); diff --git a/src/server/h2.rs b/src/server/h2.rs index c2a38572..a8f28fbf 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -14,6 +14,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use error::{Error, PayloadError}; +use http::{StatusCode, Version}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -21,6 +22,7 @@ use payload::{Payload, PayloadStatus, PayloadWriter}; use pipeline::Pipeline; use uri::Url; +use super::error::ServerError; use super::h2writer::H2Writer; use super::input::PayloadType; use super::settings::WorkerSettings; @@ -331,34 +333,35 @@ impl Entry { // Payload and Content-Encoding let (psender, payload) = Payload::new(false); - let mut msg = settings.get_http_message(); - msg.get_mut().url = Url::new(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; - - let mut req = HttpRequest::from_message(msg); + let mut msg = settings.get_request_context(); + msg.inner.url = Url::new(parts.uri); + msg.inner.method = parts.method; + msg.inner.version = parts.version; + msg.inner.headers = parts.headers; + *msg.inner.payload.borrow_mut() = Some(payload); + msg.inner.addr = addr; // Payload sender - let psender = PayloadType::new(req.headers(), psender); + let psender = PayloadType::new(msg.headers(), psender); // start request processing let mut task = None; for h in settings.handlers().iter_mut() { - req = match h.handle(req) { + msg = match h.handle(msg) { Ok(t) => { task = Some(t); break; } - Err(req) => req, + Err(msg) => msg, } } Entry { task: task.map(EntryPipe::Task).unwrap_or_else(|| { - EntryPipe::Error(Pipeline::error(HttpResponse::NotFound())) + EntryPipe::Error(ServerError::err( + Version::HTTP_2, + StatusCode::NOT_FOUND, + )) }), payload: psender, stream: H2Writer::new(resp, Rc::clone(settings)), diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 9a02bbf4..c4fc5997 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -9,15 +9,15 @@ use std::rc::Rc; use std::{cmp, io}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::{HttpTryFrom, Version}; +use http::{HttpTryFrom, Method, Version}; use super::helpers; -use super::output::Output; +use super::message::Request; +use super::output::{Output, ResponseInfo}; use super::settings::WorkerSettings; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; use header::ContentEncoding; -use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; const CHUNK_SIZE: usize = 16_384; @@ -75,8 +75,8 @@ impl Writer for H2Writer { } #[inline] - fn set_date(&self, dst: &mut BytesMut) { - self.settings.set_date(dst, true) + fn set_date(&mut self) { + self.settings.set_date(self.buffer.as_mut(), true) } #[inline] @@ -85,12 +85,12 @@ impl Writer for H2Writer { } fn start( - &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, - encoding: ContentEncoding, + &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding, ) -> io::Result { // prepare response self.flags.insert(Flags::STARTED); - self.buffer.for_server(req, msg, encoding); + let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); + self.buffer.for_server(&mut info, &req.inner, msg, encoding); // http2 specific msg.headers_mut().remove(CONNECTION); diff --git a/src/server/helpers.rs b/src/server/helpers.rs index 939785f4..03bbc831 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -1,91 +1,7 @@ use bytes::{BufMut, BytesMut}; use http::Version; -use std::cell::RefCell; -use std::collections::VecDeque; -use std::rc::Rc; use std::{mem, ptr, slice}; -use httprequest::HttpInnerMessage; - -/// Internal use only! -pub(crate) struct SharedMessagePool(RefCell>>); - -impl SharedMessagePool { - pub fn new() -> SharedMessagePool { - SharedMessagePool(RefCell::new(VecDeque::with_capacity(128))) - } - - #[inline] - pub fn get(&self) -> Rc { - if let Some(msg) = self.0.borrow_mut().pop_front() { - msg - } else { - Rc::new(HttpInnerMessage::default()) - } - } - - #[inline] - pub fn release(&self, mut msg: Rc) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - Rc::get_mut(&mut msg).unwrap().reset(); - v.push_front(msg); - } - } -} - -pub(crate) struct SharedHttpInnerMessage( - Option>, - Option>, -); - -impl Drop for SharedHttpInnerMessage { - 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 Clone for SharedHttpInnerMessage { - fn clone(&self) -> SharedHttpInnerMessage { - SharedHttpInnerMessage(self.0.clone(), self.1.clone()) - } -} - -impl Default for SharedHttpInnerMessage { - fn default() -> SharedHttpInnerMessage { - SharedHttpInnerMessage(Some(Rc::new(HttpInnerMessage::default())), None) - } -} - -impl SharedHttpInnerMessage { - pub fn from_message(msg: HttpInnerMessage) -> SharedHttpInnerMessage { - SharedHttpInnerMessage(Some(Rc::new(msg)), None) - } - - pub fn new( - msg: Rc, pool: Rc, - ) -> SharedHttpInnerMessage { - SharedHttpInnerMessage(Some(msg), Some(pool)) - } - - #[inline] - pub fn get_mut(&mut self) -> &mut HttpInnerMessage { - let r: &HttpInnerMessage = self.0.as_ref().unwrap().as_ref(); - unsafe { &mut *(r as *const _ as *mut _) } - } - - #[inline] - pub fn get_ref(&self) -> &HttpInnerMessage { - self.0.as_ref().unwrap() - } -} - const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 2021222324252627282930313233343536373839\ 4041424344454647484950515253545556575859\ diff --git a/src/server/message.rs b/src/server/message.rs new file mode 100644 index 00000000..73b23873 --- /dev/null +++ b/src/server/message.rs @@ -0,0 +1,220 @@ +use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::collections::VecDeque; +use std::net::SocketAddr; + +use http::{header, HeaderMap, Method, Uri, Version}; + +use extensions::Extensions; +use httpmessage::HttpMessage; +use info::ConnectionInfo; +use payload::Payload; +use server::ServerSettings; +use uri::Url as InnerUrl; + +bitflags! { + pub(crate) struct MessageFlags: u8 { + const KEEPALIVE = 0b0000_0001; + const CONN_INFO = 0b0000_0010; + } +} + +/// Request's context +pub struct Request { + pub(crate) inner: Box, +} + +pub(crate) struct InnerRequest { + pub(crate) version: Version, + pub(crate) method: Method, + pub(crate) url: InnerUrl, + pub(crate) flags: Cell, + pub(crate) headers: HeaderMap, + pub(crate) extensions: RefCell, + pub(crate) addr: Option, + pub(crate) info: RefCell, + pub(crate) payload: RefCell>, + pub(crate) settings: ServerSettings, +} + +impl HttpMessage for Request { + type Stream = Payload; + + fn headers(&self) -> &HeaderMap { + &self.inner.headers + } + + #[inline] + fn payload(&self) -> Payload { + if let Some(payload) = self.inner.payload.borrow_mut().take() { + payload + } else { + Payload::empty() + } + } +} + +impl Request { + /// Create new RequestContext instance + pub fn new(settings: ServerSettings) -> Request { + Request { + inner: Box::new(InnerRequest { + settings, + method: Method::GET, + url: InnerUrl::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + flags: Cell::new(MessageFlags::empty()), + addr: None, + info: RefCell::new(ConnectionInfo::default()), + payload: RefCell::new(None), + extensions: RefCell::new(Extensions::new()), + }), + } + } + + #[inline] + pub(crate) fn url(&self) -> &InnerUrl { + &self.inner.url + } + + /// Read the Request Uri. + #[inline] + pub fn uri(&self) -> &Uri { + self.inner.url.uri() + } + + /// Read the Request method. + #[inline] + pub fn method(&self) -> &Method { + &self.inner.method + } + + /// Read the Request Version. + #[inline] + pub fn version(&self) -> Version { + self.inner.version + } + + /// The target path of this Request. + #[inline] + pub fn path(&self) -> &str { + self.inner.url.path() + } + + #[inline] + /// Returns Request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.inner.headers + } + + #[inline] + /// Returns mutable Request's headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.inner.headers + } + + /// Peer socket address + /// + /// Peer address is actual socket address, if proxy is used in front of + /// actix http server, then peer address would be address of this proxy. + /// + /// To get client connection information `connection_info()` method should + /// be used. + pub fn peer_addr(&self) -> Option { + self.inner.addr + } + + /// Checks if a connection should be kept alive. + #[inline] + pub fn keep_alive(&self) -> bool { + self.inner.flags.get().contains(MessageFlags::KEEPALIVE) + } + + /// Request extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.inner.extensions.borrow() + } + + /// Mutable reference to a the request's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.inner.extensions.borrow_mut() + } + + /// Check if request requires connection upgrade + pub fn upgrade(&self) -> bool { + if let Some(conn) = self.inner.headers.get(header::CONNECTION) { + if let Ok(s) = conn.to_str() { + return s.to_lowercase().contains("upgrade"); + } + } + self.inner.method == Method::CONNECT + } + + /// Get *ConnectionInfo* for the correct request. + pub fn connection_info(&self) -> Ref { + if self.inner.flags.get().contains(MessageFlags::CONN_INFO) { + self.inner.info.borrow() + } else { + let mut flags = self.inner.flags.get(); + flags.insert(MessageFlags::CONN_INFO); + self.inner.flags.set(flags); + self.inner.info.borrow_mut().update(self); + self.inner.info.borrow() + } + } + + /// Server settings + #[inline] + pub fn server_settings(&self) -> &ServerSettings { + &self.inner.settings + } + + #[inline] + pub(crate) fn reset(&mut self) { + self.inner.headers.clear(); + self.inner.extensions.borrow_mut().clear(); + self.inner.flags.set(MessageFlags::empty()); + *self.inner.payload.borrow_mut() = None; + } +} + +pub(crate) struct RequestPool(RefCell>, RefCell); + +thread_local!(static POOL: &'static RequestPool = RequestPool::create()); + +impl RequestPool { + fn create() -> &'static RequestPool { + let pool = RequestPool( + RefCell::new(VecDeque::with_capacity(128)), + RefCell::new(ServerSettings::default()), + ); + Box::leak(Box::new(pool)) + } + + pub fn pool(settings: ServerSettings) -> &'static RequestPool { + POOL.with(|p| { + *p.1.borrow_mut() = settings; + *p + }) + } + + #[inline] + pub fn get(&self) -> Request { + if let Some(msg) = self.0.borrow_mut().pop_front() { + msg + } else { + Request::new(self.1.borrow().clone()) + } + } + + #[inline] + pub fn release(&self, mut msg: Request) { + let v = &mut self.0.borrow_mut(); + if v.len() < 128 { + msg.reset(); + v.push_front(msg); + } + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index f10dacc2..4f95ffb7 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -8,6 +8,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; mod channel; +mod error; pub(crate) mod h1; pub(crate) mod h1decoder; mod h1writer; @@ -15,11 +16,13 @@ mod h2; mod h2writer; pub(crate) mod helpers; pub(crate) mod input; +pub(crate) mod message; pub(crate) mod output; pub(crate) mod settings; mod srv; mod worker; +pub use self::message::Request; pub use self::settings::ServerSettings; pub use self::srv::HttpServer; @@ -30,7 +33,7 @@ use actix::Message; use body::Binary; use error::Error; use header::ContentEncoding; -use httprequest::{HttpInnerMessage, HttpRequest}; +use httprequest::HttpRequest; use httpresponse::HttpResponse; /// max buffer size 64k @@ -128,13 +131,13 @@ pub trait HttpHandler: 'static { type Task: HttpHandlerTask; /// Handle request - fn handle(&self, req: HttpRequest) -> Result; + fn handle(&self, req: Request) -> Result; } impl HttpHandler for Box>> { type Task = Box; - fn handle(&self, req: HttpRequest) -> Result, HttpRequest> { + fn handle(&self, req: Request) -> Result, Request> { self.as_ref().handle(req) } } @@ -165,13 +168,13 @@ pub trait IntoHttpHandler { type Handler: HttpHandler; /// Convert into `HttpHandler` object. - fn into_handler(self, settings: ServerSettings) -> Self::Handler; + fn into_handler(self) -> Self::Handler; } impl IntoHttpHandler for T { type Handler = T; - fn into_handler(self, _: ServerSettings) -> Self::Handler { + fn into_handler(self) -> Self::Handler { self } } @@ -190,14 +193,13 @@ pub trait Writer { fn written(&self) -> u64; #[doc(hidden)] - fn set_date(&self, st: &mut BytesMut); + fn set_date(&mut self); #[doc(hidden)] fn buffer(&mut self) -> &mut BytesMut; fn start( - &mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, - encoding: ContentEncoding, + &mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding, ) -> io::Result; fn write(&mut self, payload: &Binary) -> io::Result; diff --git a/src/server/output.rs b/src/server/output.rs index ad0b80b6..1d9ee92a 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -15,11 +15,37 @@ use http::header::{ }; use http::{HttpTryFrom, Method, Version}; +use super::message::{InnerRequest, Request}; use body::{Binary, Body}; use header::ContentEncoding; -use httprequest::HttpInnerMessage; use httpresponse::HttpResponse; +#[derive(Debug)] +pub(crate) enum ResponseLength { + Chunked, + Zero, + Length(usize), + Length64(u64), + None, +} + +#[derive(Debug)] +pub(crate) struct ResponseInfo { + head: bool, + pub length: ResponseLength, + pub content_encoding: Option<&'static str>, +} + +impl ResponseInfo { + pub fn new(head: bool) -> Self { + ResponseInfo { + head, + length: ResponseLength::None, + content_encoding: None, + } + } +} + #[derive(Debug)] pub(crate) enum Output { Empty(BytesMut), @@ -119,13 +145,12 @@ impl Output { } } - pub fn for_server( - &mut self, req: &HttpInnerMessage, resp: &mut HttpResponse, + pub(crate) fn for_server( + &mut self, info: &mut ResponseInfo, req: &InnerRequest, resp: &mut HttpResponse, response_encoding: ContentEncoding, ) { let buf = self.take(); let version = resp.version().unwrap_or_else(|| req.version); - let is_head = req.method == Method::HEAD; let mut len = 0; #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] @@ -158,10 +183,7 @@ impl Output { encoding => encoding, }; if encoding.is_compression() { - resp.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); + info.content_encoding = Some(encoding.as_str()); } encoding } else { @@ -173,8 +195,8 @@ impl Output { #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] let transfer = match resp.body() { &Body::Empty => { - if req.method != Method::HEAD { - resp.headers_mut().remove(CONTENT_LENGTH); + if !info.head { + info.length = ResponseLength::Zero; } *self = Output::Empty(buf); return; @@ -216,13 +238,8 @@ impl Output { } } - if is_head { - let mut b = BytesMut::new(); - let _ = write!(b, "{}", len); - resp.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(b.freeze()).unwrap(), - ); + info.length = ResponseLength::Length(len); + if info.head { *self = Output::Empty(buf); } else { *self = Output::Buffer(buf); @@ -236,7 +253,7 @@ impl Output { } if encoding != ContentEncoding::Identity { encoding = ContentEncoding::Identity; - resp.headers_mut().remove(CONTENT_ENCODING); + info.content_encoding.take(); } TransferEncoding::eof(buf) } else { @@ -245,12 +262,12 @@ impl Output { { resp.headers_mut().remove(CONTENT_LENGTH); } - Output::streaming_encoding(buf, version, resp) + Output::streaming_encoding(info, buf, version, resp) } } }; // check for head response - if is_head { + if info.head { resp.set_body(Body::Empty); *self = Output::Empty(transfer.buf.unwrap()); return; @@ -277,18 +294,17 @@ impl Output { } fn streaming_encoding( - buf: BytesMut, version: Version, resp: &mut HttpResponse, + info: &mut ResponseInfo, buf: BytesMut, version: Version, + resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { // Enable transfer encoding - resp.headers_mut().remove(CONTENT_LENGTH); if version == Version::HTTP_2 { - resp.headers_mut().remove(TRANSFER_ENCODING); + info.length = ResponseLength::None; TransferEncoding::eof(buf) } else { - resp.headers_mut() - .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); + info.length = ResponseLength::Chunked; TransferEncoding::chunked(buf) } } @@ -315,6 +331,7 @@ impl Output { if !chunked { if let Some(len) = len { + info.length = ResponseLength::Length64(len); TransferEncoding::length(len, buf) } else { TransferEncoding::eof(buf) @@ -323,14 +340,11 @@ impl Output { // Enable transfer encoding match version { Version::HTTP_11 => { - resp.headers_mut().insert( - TRANSFER_ENCODING, - HeaderValue::from_static("chunked"), - ); + info.length = ResponseLength::Chunked; TransferEncoding::chunked(buf) } _ => { - resp.headers_mut().remove(TRANSFER_ENCODING); + info.length = ResponseLength::None; TransferEncoding::eof(buf) } } diff --git a/src/server/settings.rs b/src/server/settings.rs index 31750b22..7f38088c 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -12,6 +12,7 @@ use time; use super::channel::Node; use super::helpers; +use super::message::{Request, RequestPool}; use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; @@ -156,14 +157,17 @@ pub(crate) struct WorkerSettings { keep_alive: u64, ka_enabled: bool, bytes: Rc, - messages: Rc, + messages: &'static RequestPool, channels: Cell, node: Box>, date: UnsafeCell, + settings: ServerSettings, } impl WorkerSettings { - pub(crate) fn new(h: Vec, keep_alive: KeepAlive) -> WorkerSettings { + pub(crate) fn new( + h: Vec, keep_alive: KeepAlive, settings: ServerSettings, + ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), KeepAlive::Os | KeepAlive::Tcp(_) => (0, true), @@ -171,14 +175,15 @@ impl WorkerSettings { }; WorkerSettings { - keep_alive, - ka_enabled, h: RefCell::new(h), bytes: Rc::new(SharedBytesPool::new()), - messages: Rc::new(helpers::SharedMessagePool::new()), + messages: RequestPool::pool(settings.clone()), channels: Cell::new(0), node: Box::new(Node::head()), date: UnsafeCell::new(Date::new()), + keep_alive, + ka_enabled, + settings, } } @@ -210,11 +215,8 @@ impl WorkerSettings { self.bytes.release_bytes(bytes) } - pub fn get_http_message(&self) -> helpers::SharedHttpInnerMessage { - helpers::SharedHttpInnerMessage::new( - self.messages.get(), - Rc::clone(&self.messages), - ) + pub fn get_request_context(&self) -> Request { + self.messages.get() } pub fn add_channel(&self) { @@ -316,7 +318,11 @@ mod tests { #[test] fn test_date() { - let settings = WorkerSettings::<()>::new(Vec::new(), KeepAlive::Os); + let settings = WorkerSettings::<()>::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf1, true); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); diff --git a/src/server/srv.rs b/src/server/srv.rs index d5c94ea8..77223892 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -358,12 +358,10 @@ where let addr = Arbiter::start(move |ctx: &mut Context<_>| { let s = ServerSettings::from_parts(parts); - let apps: Vec<_> = (*factory)() - .into_iter() - .map(|h| h.into_handler(s.clone())) - .collect(); + let apps: Vec<_> = + (*factory)().into_iter().map(|h| h.into_handler()).collect(); ctx.add_message_stream(rx); - Worker::new(apps, socks, ka) + Worker::new(apps, socks, ka, s) }); workers.push((idx, tx)); self.workers.push((idx, addr)); @@ -404,7 +402,7 @@ impl HttpServer { /// fn main() { /// let sys = actix::System::new("example"); // <- create Actix system /// - /// server::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) + /// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok()))) /// .bind("127.0.0.1:0") /// .expect("Can not bind to 127.0.0.1:0") /// .start(); @@ -559,9 +557,13 @@ impl HttpServer { let settings = ServerSettings::new(Some(addr), &self.host, secure); let apps: Vec<_> = (*self.factory)() .into_iter() - .map(|h| h.into_handler(settings.clone())) + .map(|h| h.into_handler()) .collect(); - self.h = Some(Rc::new(WorkerSettings::new(apps, self.keep_alive))); + self.h = Some(Rc::new(WorkerSettings::new( + apps, + self.keep_alive, + settings, + ))); // start server let signals = self.subscribe_to_signals(); @@ -645,12 +647,10 @@ impl StreamHandler2 for HttpServer { let addr = Arbiter::start(move |ctx: &mut Context<_>| { let settings = ServerSettings::new(Some(addr), &host, false); - let apps: Vec<_> = (*factory)() - .into_iter() - .map(|h| h.into_handler(settings.clone())) - .collect(); + let apps: Vec<_> = + (*factory)().into_iter().map(|h| h.into_handler()).collect(); ctx.add_message_stream(rx); - Worker::new(apps, socks, ka) + Worker::new(apps, socks, ka, settings) }); for item in &self.accept { let _ = item.1.send(Command::Worker(new_idx, tx.clone())); diff --git a/src/server/worker.rs b/src/server/worker.rs index 3d4ee863..8fd3fe60 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -25,7 +25,7 @@ use actix::msgs::StopArbiter; use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response}; use server::channel::HttpChannel; -use server::settings::WorkerSettings; +use server::settings::{ServerSettings, WorkerSettings}; use server::{HttpHandler, KeepAlive}; #[derive(Message)] @@ -68,6 +68,7 @@ where impl Worker { pub(crate) fn new( h: Vec, socks: Slab, keep_alive: KeepAlive, + settings: ServerSettings, ) -> Worker { let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { Some(time::Duration::new(val as u64, 0)) @@ -76,7 +77,7 @@ impl Worker { }; Worker { - settings: Rc::new(WorkerSettings::new(h, keep_alive)), + settings: Rc::new(WorkerSettings::new(h, keep_alive, settings)), socks, tcp_ka, } diff --git a/src/test.rs b/src/test.rs index b7ed4e79..5fc06f65 100644 --- a/src/test.rs +++ b/src/test.rs @@ -22,14 +22,15 @@ use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; use error::Error; use handler::{AsyncResultItem, Handler, Responder}; use header::{Header, IntoHeaderValue}; -use httprequest::HttpRequest; +use httprequest::{HttpRequest, RouterResource}; use httpresponse::HttpResponse; use middleware::Middleware; use param::Params; use payload::Payload; use resource::ResourceHandler; use router::Router; -use server::{HttpServer, IntoHttpHandler, ServerSettings}; +use server::{HttpServer, IntoHttpHandler, Request, ServerSettings}; +use uri::Url as InnerUrl; use ws; /// The `TestServer` type. @@ -43,7 +44,7 @@ use ws; /// # extern crate actix_web; /// # use actix_web::*; /// # -/// # fn my_handler(req: HttpRequest) -> HttpResponse { +/// # fn my_handler(req: &HttpRequest) -> HttpResponse { /// # HttpResponse::Ok().into() /// # } /// # @@ -330,8 +331,12 @@ impl TestApp { } /// Register handler for "/" - pub fn handler>(&mut self, handler: H) { - self.app = Some(self.app.take().unwrap().resource("/", |r| r.h(handler))); + pub fn handler(&mut self, handler: F) + where + F: Fn(&HttpRequest) -> R + 'static, + R: Responder + 'static, + { + self.app = Some(self.app.take().unwrap().resource("/", |r| r.f(handler))); } /// Register middleware @@ -357,8 +362,8 @@ impl TestApp { impl IntoHttpHandler for TestApp { type Handler = HttpApplication; - fn into_handler(mut self, settings: ServerSettings) -> HttpApplication { - self.app.take().unwrap().into_handler(settings) + fn into_handler(mut self) -> HttpApplication { + self.app.take().unwrap().into_handler() } } @@ -384,7 +389,7 @@ impl Iterator for TestApp { /// # use actix_web::*; /// use actix_web::test::TestRequest; /// -/// fn index(req: HttpRequest) -> HttpResponse { +/// fn index(req: &HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { /// HttpResponse::Ok().into() /// } else { @@ -533,11 +538,19 @@ impl TestRequest { cookies, payload, } = self; - let mut req = HttpRequest::new(method, uri, version, headers, payload); + let (router, _) = Router::new::("/", Vec::new()); + + let mut req = Request::new(ServerSettings::default()); + req.inner.method = method; + req.inner.url = InnerUrl::new(uri); + req.inner.version = version; + req.inner.headers = headers; + *req.inner.payload.borrow_mut() = payload; + + let mut req = + HttpRequest::new(req, Rc::new(state), router.route_info_params(params, 0)); req.set_cookies(cookies); - req.as_mut().params = params; - let (router, _) = Router::new::("/", ServerSettings::default(), Vec::new()); - req.with_state(Rc::new(state), router) + req } #[cfg(test)] @@ -554,10 +567,37 @@ impl TestRequest { payload, } = self; - let mut req = HttpRequest::new(method, uri, version, headers, payload); + let mut req = Request::new(ServerSettings::default()); + req.inner.method = method; + req.inner.url = InnerUrl::new(uri); + req.inner.version = version; + req.inner.headers = headers; + *req.inner.payload.borrow_mut() = payload; + let mut req = + HttpRequest::new(req, Rc::new(state), router.route_info_params(params, 0)); req.set_cookies(cookies); - req.as_mut().params = params; - req.with_state(Rc::new(state), router) + req + } + + /// Complete request creation and generate server `Request` instance + pub fn request(self) -> Request { + let TestRequest { + state, + method, + uri, + version, + headers, + params, + cookies, + payload, + } = self; + let mut req = Request::new(ServerSettings::default()); + req.inner.method = method; + req.inner.url = InnerUrl::new(uri); + req.inner.version = version; + req.inner.headers = headers; + *req.inner.payload.borrow_mut() = payload; + req } /// This method generates `HttpRequest` instance and runs handler @@ -566,7 +606,7 @@ impl TestRequest { /// This method panics is handler returns actor or async result. pub fn run>(self, h: &H) -> Result { let req = self.finish(); - let resp = h.handle(req.clone()); + let resp = h.handle(&req); match resp.respond_to(&req) { Ok(resp) => match resp.into().into() { diff --git a/src/with.rs b/src/with.rs index c475ca01..0af626c8 100644 --- a/src/with.rs +++ b/src/with.rs @@ -42,9 +42,9 @@ where { type Result = AsyncResult; - fn handle(&self, req: HttpRequest) -> Self::Result { + fn handle(&self, req: &HttpRequest) -> Self::Result { let mut fut = WithHandlerFut { - req, + req: req.clone(), started: false, hnd: Rc::clone(&self.hnd), cfg: self.cfg.clone(), @@ -167,9 +167,9 @@ where { type Result = AsyncResult; - fn handle(&self, req: HttpRequest) -> Self::Result { + fn handle(&self, req: &HttpRequest) -> Self::Result { let mut fut = WithAsyncHandlerFut { - req, + req: req.clone(), started: false, hnd: Rc::clone(&self.hnd), cfg: Rc::clone(&self.cfg), diff --git a/src/ws/client.rs b/src/ws/client.rs index 6a4fcf7c..251b0edd 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -24,7 +24,7 @@ use payload::PayloadHelper; use client::{ ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, - HttpResponseParserError, SendRequest, SendRequestError, + HttpResponseParserError, Pipeline, SendRequest, SendRequestError, }; use super::frame::Frame; @@ -275,7 +275,7 @@ impl Client { struct Inner { tx: UnboundedSender, - rx: PayloadHelper, + rx: PayloadHelper>, closed: bool, } @@ -431,7 +431,7 @@ impl Future for ClientHandshake { let inner = Inner { tx: self.tx.take().unwrap(), - rx: PayloadHelper::new(resp), + rx: PayloadHelper::new(resp.payload()), closed: false, }; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 558ecb51..bc99414d 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -13,7 +13,7 @@ //! use actix_web::{ws, HttpRequest, HttpResponse}; //! //! // do websocket handshake and start actor -//! fn ws_index(req: HttpRequest) -> Result { +//! fn ws_index(req: &HttpRequest) -> Result { //! ws::start(req, Ws) //! } //! @@ -171,15 +171,15 @@ pub enum Message { } /// Do websocket handshake and start actor -pub fn start(req: HttpRequest, actor: A) -> Result +pub fn start(req: &HttpRequest, actor: A) -> Result where A: Actor> + StreamHandler, S: 'static, { - let mut resp = handshake(&req)?; - let stream = WsStream::new(req.clone()); + let mut resp = handshake(req)?; + let stream = WsStream::new(req.payload()); - let mut ctx = WebsocketContext::new(req, actor); + let mut ctx = WebsocketContext::new(req.clone(), actor); ctx.add_stream(stream); Ok(resp.body(ctx)) @@ -359,162 +359,116 @@ pub trait WsWriter { #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; use http::{header, HeaderMap, Method, Uri, Version}; - use std::str::FromStr; + use test::TestRequest; #[test] fn test_handshake() { - let req = HttpRequest::new( - Method::POST, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); + let req = TestRequest::default().method(Method::POST).finish(); assert_eq!( HandshakeError::GetMethodRequired, handshake(&req).err().unwrap() ); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - HeaderMap::new(), - None, - ); + let req = TestRequest::default().finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap() ); - let mut headers = HeaderMap::new(); - headers.insert(header::UPGRADE, header::HeaderValue::from_static("test")); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + let req = TestRequest::default() + .header(header::UPGRADE, header::HeaderValue::from_static("test")) + .finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap() ); - let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + let req = TestRequest::default() + .header( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ) + .finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap() ); - let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - headers.insert( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + let req = TestRequest::default() + .header( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ) + .header( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ) + .finish(); assert_eq!( HandshakeError::NoVersionHeader, handshake(&req).err().unwrap() ); - let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - headers.insert( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ); - headers.insert( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("5"), - ); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + let req = TestRequest::default() + .header( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ) + .header( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ) + .header( + header::SEC_WEBSOCKET_VERSION, + header::HeaderValue::from_static("5"), + ) + .finish(); assert_eq!( HandshakeError::UnsupportedVersion, handshake(&req).err().unwrap() ); - let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - headers.insert( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ); - headers.insert( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + let req = TestRequest::default() + .header( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ) + .header( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ) + .header( + header::SEC_WEBSOCKET_VERSION, + header::HeaderValue::from_static("13"), + ) + .finish(); assert_eq!( HandshakeError::BadWebsocketKey, handshake(&req).err().unwrap() ); - let mut headers = HeaderMap::new(); - headers.insert( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ); - headers.insert( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ); - headers.insert( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ); - headers.insert( - header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13"), - ); - let req = HttpRequest::new( - Method::GET, - Uri::from_str("/").unwrap(), - Version::HTTP_11, - headers, - None, - ); + let req = TestRequest::default() + .header( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ) + .header( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ) + .header( + header::SEC_WEBSOCKET_VERSION, + header::HeaderValue::from_static("13"), + ) + .header( + header::SEC_WEBSOCKET_KEY, + header::HeaderValue::from_static("13"), + ) + .finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, handshake(&req).unwrap().finish().status() diff --git a/tests/test_client.rs b/tests/test_client.rs index dc147ccd..c4575c87 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -67,7 +67,7 @@ fn test_simple() { #[test] fn test_with_query_parameter() { let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| match req.query().get("qp") { + app.handler(|req: &HttpRequest| match req.query().get("qp") { Some(_) => HttpResponse::Ok().finish(), None => HttpResponse::BadRequest().finish(), }) @@ -110,7 +110,7 @@ fn test_no_decompress() { #[test] fn test_client_gzip_encoding() { let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -140,7 +140,7 @@ fn test_client_gzip_encoding_large() { let data = STR.repeat(10); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -173,7 +173,7 @@ fn test_client_gzip_encoding_large_random() { .collect::(); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -202,7 +202,7 @@ fn test_client_gzip_encoding_large_random() { #[test] fn test_client_brotli_encoding() { let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -236,7 +236,7 @@ fn test_client_brotli_encoding_large_random() { .collect::(); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(move |bytes: Bytes| { Ok(HttpResponse::Ok() @@ -266,7 +266,7 @@ fn test_client_brotli_encoding_large_random() { #[test] fn test_client_deflate_encoding() { let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -300,7 +300,7 @@ fn test_client_deflate_encoding_large_random() { .collect::(); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -328,7 +328,7 @@ fn test_client_deflate_encoding_large_random() { #[test] fn test_client_streaming_explicit() { let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .map_err(Error::from) .and_then(|body| { @@ -393,7 +393,7 @@ fn test_client_cookie_handling() { let mut srv = test::TestServer::new(move |app| { let cookie1 = cookie1b.clone(); let cookie2 = cookie2b.clone(); - app.handler(move |req: HttpRequest| { + app.handler(move |req: &HttpRequest| { // Check cookies were sent correctly req.cookie("cookie1").ok_or_else(err) .and_then(|c1| if c1.value() == "value1" { diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 806211ea..170495c6 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -20,21 +20,21 @@ struct MiddlewareTest { } impl middleware::Middleware for MiddlewareTest { - fn start(&self, _: &mut HttpRequest) -> Result { + fn start(&self, _: &HttpRequest) -> Result { self.start .store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Started::Done) } fn response( - &self, _: &mut HttpRequest, resp: HttpResponse, + &self, _: &HttpRequest, resp: HttpResponse, ) -> Result { self.response .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); Ok(middleware::Response::Done(resp)) } - fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { + fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { self.finish .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); middleware::Finished::Done @@ -331,7 +331,7 @@ fn test_scope_middleware_async_handler() { assert_eq!(num3.load(Ordering::Relaxed), 1); } -fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse { +fn index_test_middleware_async_error(_: &HttpRequest) -> FutureResponse { future::result(Err(error::ErrorBadRequest("TEST"))).responder() } @@ -412,7 +412,7 @@ fn test_resource_middleware_async_error() { App::new().resource("/test", move |r| { r.middleware(mw); - r.h(index_test_middleware_async_error); + r.f(index_test_middleware_async_error); }) }); @@ -432,7 +432,7 @@ struct MiddlewareAsyncTest { } impl middleware::Middleware for MiddlewareAsyncTest { - fn start(&self, _: &mut HttpRequest) -> Result { + fn start(&self, _: &HttpRequest) -> Result { let to = Delay::new(Instant::now() + Duration::from_millis(10)); let start = Arc::clone(&self.start); @@ -445,7 +445,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { } fn response( - &self, _: &mut HttpRequest, resp: HttpResponse, + &self, _: &HttpRequest, resp: HttpResponse, ) -> Result { let to = Delay::new(Instant::now() + Duration::from_millis(10)); @@ -458,7 +458,7 @@ impl middleware::Middleware for MiddlewareAsyncTest { ))) } - fn finish(&self, _: &mut HttpRequest, _: &HttpResponse) -> middleware::Finished { + fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { let to = Delay::new(Instant::now() + Duration::from_millis(10)); let finish = Arc::clone(&self.finish); @@ -697,7 +697,7 @@ fn test_async_resource_middleware() { }; App::new().resource("/test", move |r| { r.middleware(mw); - r.h(|_| HttpResponse::Ok()); + r.f(|_| HttpResponse::Ok()); }) }); @@ -736,7 +736,7 @@ fn test_async_resource_middleware_multiple() { App::new().resource("/test", move |r| { r.middleware(mw1); r.middleware(mw2); - r.h(|_| HttpResponse::Ok()); + r.f(|_| HttpResponse::Ok()); }) }); @@ -775,7 +775,7 @@ fn test_async_sync_resource_middleware_multiple() { App::new().resource("/test", move |r| { r.middleware(mw1); r.middleware(mw2); - r.h(|_| HttpResponse::Ok()); + r.f(|_| HttpResponse::Ok()); }) }); @@ -793,7 +793,7 @@ fn test_async_sync_resource_middleware_multiple() { struct MiddlewareWithErr; impl middleware::Middleware for MiddlewareWithErr { - fn start(&self, _req: &mut HttpRequest) -> Result { + fn start(&self, _: &HttpRequest) -> Result { Err(ErrorInternalServerError("middleware error")) } } @@ -801,7 +801,7 @@ impl middleware::Middleware for MiddlewareWithErr { struct MiddlewareAsyncWithErr; impl middleware::Middleware for MiddlewareAsyncWithErr { - fn start(&self, _req: &mut HttpRequest) -> Result { + fn start(&self, _: &HttpRequest) -> Result { Ok(middleware::Started::Future(Box::new(future::err( ErrorInternalServerError("middleware error"), )))) @@ -827,7 +827,7 @@ fn test_middleware_chain_with_error() { App::new() .middleware(mw1) .middleware(MiddlewareWithErr) - .resource("/test", |r| r.h(|_| HttpResponse::Ok())) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); @@ -857,7 +857,7 @@ fn test_middleware_async_chain_with_error() { App::new() .middleware(mw1) .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.h(|_| HttpResponse::Ok())) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); @@ -888,7 +888,7 @@ fn test_scope_middleware_chain_with_error() { scope .middleware(mw1) .middleware(MiddlewareWithErr) - .resource("/test", |r| r.h(|_| HttpResponse::Ok())) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) }) }); @@ -920,7 +920,7 @@ fn test_scope_middleware_async_chain_with_error() { scope .middleware(mw1) .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.h(|_| HttpResponse::Ok())) + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) }) }); @@ -951,7 +951,7 @@ fn test_resource_middleware_chain_with_error() { App::new().resource("/test", move |r| { r.middleware(mw1); r.middleware(MiddlewareWithErr); - r.h(|_| HttpResponse::Ok()); + r.f(|_| HttpResponse::Ok()); }) }); @@ -982,7 +982,7 @@ fn test_resource_middleware_async_chain_with_error() { App::new().resource("/test", move |r| { r.middleware(mw1); r.middleware(MiddlewareAsyncWithErr); - r.h(|_| HttpResponse::Ok()); + r.f(|_| HttpResponse::Ok()); }) }); diff --git a/tests/test_server.rs b/tests/test_server.rs index 50864735..4fb73a6a 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -368,8 +368,8 @@ fn test_head_empty() { } // read response - //let bytes = srv.execute(response.body()).unwrap(); - //assert!(bytes.is_empty()); + // let bytes = srv.execute(response.body()).unwrap(); + // assert!(bytes.is_empty()); } #[test] @@ -524,7 +524,7 @@ fn test_body_brotli() { #[test] fn test_gzip_encoding() { let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -557,7 +557,7 @@ fn test_gzip_encoding() { fn test_gzip_encoding_large() { let data = STR.repeat(10); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -594,7 +594,7 @@ fn test_reading_gzip_encoding_large_random() { .collect::(); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -627,7 +627,7 @@ fn test_reading_gzip_encoding_large_random() { #[test] fn test_reading_deflate_encoding() { let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -660,7 +660,7 @@ fn test_reading_deflate_encoding() { fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -697,7 +697,7 @@ fn test_reading_deflate_encoding_large_random() { .collect::(); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -731,7 +731,7 @@ fn test_reading_deflate_encoding_large_random() { #[test] fn test_brotli_encoding() { let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() @@ -765,7 +765,7 @@ fn test_brotli_encoding() { fn test_brotli_encoding_large() { let data = STR.repeat(10); let mut srv = test::TestServer::new(|app| { - app.handler(|req: HttpRequest| { + app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { Ok(HttpResponse::Ok() From 09aabc7b3b23be7c4ae02dada73138b2b57a97e5 Mon Sep 17 00:00:00 2001 From: Gorm Casper Date: Wed, 4 Jul 2018 10:17:44 +0200 Subject: [PATCH 0461/1635] plain/text -> text/plain in comment (#362) --- src/pred.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pred.rs b/src/pred.rs index 5d47922f..3b7b99c4 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -68,7 +68,7 @@ impl Predicate for AnyPredicate { /// r.route() /// .filter( /// pred::All(pred::Get()) -/// .and(pred::Header("content-type", "plain/text")), +/// .and(pred::Header("content-type", "text/plain")), /// ) /// .f(|_| HttpResponse::MethodNotAllowed()) /// }); @@ -177,7 +177,8 @@ pub fn Method(method: http::Method) -> MethodPredicate { /// Return predicate that matches if request contains specified header and /// value. pub fn Header( - name: &'static str, value: &'static str, + name: &'static str, + value: &'static str, ) -> HeaderPredicate { HeaderPredicate( header::HeaderName::try_from(name).unwrap(), From 4c5a63965e72afc838d8a6d4e9346ff33420451d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Jul 2018 17:04:23 +0600 Subject: [PATCH 0462/1635] use new actix context api --- Cargo.toml | 2 - src/context.rs | 79 +++++++++++++++++++------------- src/lib.rs | 1 - src/pipeline.rs | 69 ---------------------------- src/ws/context.rs | 113 ++++++++++++++++++++++++++++------------------ src/ws/mod.rs | 6 +-- 6 files changed, 119 insertions(+), 151 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2aea4fd..fa243bc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,8 +104,6 @@ tokio-tls = { version="0.1", optional = true } openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } -backtrace="*" - [dev-dependencies] env_logger = "0.5" serde_derive = "1.0" diff --git a/src/context.rs b/src/context.rs index 601cfe58..d13cd417 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,7 +6,9 @@ use futures::{Async, Future, Poll}; use smallvec::SmallVec; use std::marker::PhantomData; -use self::actix::dev::{ContextImpl, Envelope, ToEnvelope}; +use self::actix::dev::{ + AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, +}; use self::actix::fut::ActorFuture; use self::actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, @@ -41,7 +43,7 @@ pub struct HttpContext where A: Actor>, { - inner: ContextImpl, + inner: ContextParts, stream: Option>, request: HttpRequest, disconnected: bool, @@ -103,30 +105,32 @@ where { #[inline] /// Create a new HTTP Context from a request and an actor - pub fn new(req: HttpRequest, actor: A) -> HttpContext { - HttpContext { - inner: ContextImpl::new(Some(actor)), + pub fn new(req: HttpRequest, actor: A) -> Body { + let mb = Mailbox::default(); + let ctx = HttpContext { + inner: ContextParts::new(mb.sender_producer()), stream: None, request: req, disconnected: false, - } + }; + Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb))) } /// Create a new HTTP Context - pub fn with_factory(req: HttpRequest, f: F) -> HttpContext + pub fn with_factory(req: HttpRequest, f: F) -> Body where F: FnOnce(&mut Self) -> A + 'static, { + let mb = Mailbox::default(); let mut ctx = HttpContext { - inner: ContextImpl::new(None), + inner: ContextParts::new(mb.sender_producer()), stream: None, request: req, disconnected: false, }; let act = f(&mut ctx); - ctx.inner.set_actor(act); - ctx + Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb))) } } @@ -165,7 +169,6 @@ where /// Returns drain future pub fn drain(&mut self) -> Drain { let (tx, rx) = oneshot::channel(); - self.inner.modify(); self.add_frame(Frame::Drain(tx)); Drain::new(rx) } @@ -184,7 +187,6 @@ where if let Some(s) = self.stream.as_mut() { s.push(frame) } - self.inner.modify(); } /// Handle of the running future @@ -195,32 +197,55 @@ where } } -impl ActorHttpContext for HttpContext +impl AsyncContextParts for HttpContext where A: Actor, +{ + fn parts(&mut self) -> &mut ContextParts { + &mut self.inner + } +} + +struct HttpContextFut +where + A: Actor>, +{ + fut: ContextFut>, +} + +impl HttpContextFut +where + A: Actor>, +{ + fn new(ctx: HttpContext, act: A, mailbox: Mailbox) -> Self { + let fut = ContextFut::new(ctx, act, mailbox); + HttpContextFut { fut } + } +} + +impl ActorHttpContext for HttpContextFut +where + A: Actor>, S: 'static, { #[inline] fn disconnected(&mut self) { - self.disconnected = true; - self.stop(); + self.fut.ctx().disconnected = true; + self.fut.ctx().stop(); } fn poll(&mut self) -> Poll>, Error> { - let ctx: &mut HttpContext = - unsafe { &mut *(self as &mut HttpContext as *mut _) }; - - if self.inner.alive() { - match self.inner.poll(ctx) { + if self.fut.alive() { + match self.fut.poll() { Ok(Async::NotReady) | Ok(Async::Ready(())) => (), Err(_) => return Err(ErrorInternalServerError("error")), } } // frames - if let Some(data) = self.stream.take() { + if let Some(data) = self.fut.ctx().stream.take() { Ok(Async::Ready(Some(data))) - } else if self.inner.alive() { + } else if self.fut.alive() { Ok(Async::NotReady) } else { Ok(Async::Ready(None)) @@ -239,16 +264,6 @@ where } } -impl From> for Body -where - A: Actor>, - S: 'static, -{ - fn from(ctx: HttpContext) -> Body { - Body::Actor(Box::new(ctx)) - } -} - /// Consume a future pub struct Drain { fut: oneshot::Receiver<()>, diff --git a/src/lib.rs b/src/lib.rs index 5ed1bcef..218cabf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,6 @@ allow(decimal_literal_representation, suspicious_arithmetic_impl) )] #![warn(missing_docs)] -#![allow(unused_mut, unused_imports, unused_variables, dead_code)] #[macro_use] extern crate log; diff --git a/src/pipeline.rs b/src/pipeline.rs index 6f3d4807..0ba25806 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -780,72 +780,3 @@ impl Completed { } } } - -#[cfg(test)] -mod tests { - use super::*; - use actix::*; - use context::HttpContext; - use futures::future::{lazy, result}; - use tokio::runtime::current_thread::Runtime; - - use test::TestRequest; - - impl PipelineState { - fn is_none(&self) -> Option { - if let PipelineState::None = *self { - Some(true) - } else { - None - } - } - fn completed(self) -> Option> { - if let PipelineState::Completed(c) = self { - Some(c) - } else { - None - } - } - } - - struct MyActor; - impl Actor for MyActor { - type Context = HttpContext; - } - - #[test] - fn test_completed() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let req = TestRequest::default().finish(); - let mut info = PipelineInfo::new(req); - Completed::<(), Inner<()>>::init(&mut info) - .is_none() - .unwrap(); - - let req = TestRequest::default().finish(); - let ctx = HttpContext::new(req.clone(), MyActor); - let addr = ctx.address(); - let mut info = PipelineInfo::new(req); - info.context = Some(Box::new(ctx)); - let mut state = Completed::<(), Inner<()>>::init(&mut info) - .completed() - .unwrap(); - - assert!(state.poll(&mut info).is_none()); - let pp = - Pipeline(info, PipelineState::Completed(state), Rc::new(Vec::new())); - assert!(!pp.is_done()); - - let Pipeline(mut info, st, _) = pp; - let mut st = st.completed().unwrap(); - drop(addr); - - assert!(st.poll(&mut info).unwrap().is_none().unwrap()); - - result(Ok::<_, ()>(())) - })) - .unwrap(); - } -} diff --git a/src/ws/context.rs b/src/ws/context.rs index 34346f1e..91a23e0f 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -1,30 +1,35 @@ extern crate actix; +use bytes::Bytes; use futures::sync::oneshot::{self, Sender}; -use futures::{Async, Poll}; +use futures::{Async, Future, Poll, Stream}; use smallvec::SmallVec; -use self::actix::dev::{ContextImpl, Envelope, ToEnvelope}; +use self::actix::dev::{ + AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, + ToEnvelope, +}; use self::actix::fut::ActorFuture; use self::actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, + Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, + Message as ActixMessage, SpawnHandle, }; use body::{Binary, Body}; use context::{ActorHttpContext, Drain, Frame as ContextFrame}; -use error::{Error, ErrorInternalServerError}; +use error::{Error, ErrorInternalServerError, PayloadError}; use httprequest::HttpRequest; use ws::frame::Frame; use ws::proto::{CloseReason, OpCode}; -use ws::WsWriter; +use ws::{Message, ProtocolError, WsStream, WsWriter}; /// Execution context for `WebSockets` actors pub struct WebsocketContext where A: Actor>, { - inner: ContextImpl, + inner: ContextParts, stream: Option>, request: HttpRequest, disconnected: bool, @@ -87,30 +92,41 @@ where { #[inline] /// Create a new Websocket context from a request and an actor - pub fn new(req: HttpRequest, actor: A) -> WebsocketContext { - WebsocketContext { - inner: ContextImpl::new(Some(actor)), - stream: None, - request: req, - disconnected: false, - } - } - - /// Create a new Websocket context - pub fn with_factory(req: HttpRequest, f: F) -> Self + pub fn new

    (req: HttpRequest, actor: A, stream: WsStream

    ) -> Body where - F: FnOnce(&mut Self) -> A + 'static, + A: StreamHandler, + P: Stream + 'static, { + let mb = Mailbox::default(); let mut ctx = WebsocketContext { - inner: ContextImpl::new(None), + inner: ContextParts::new(mb.sender_producer()), stream: None, request: req, disconnected: false, }; + ctx.add_stream(stream); + + Body::Actor(Box::new(WebsocketContextFut::new(ctx, actor, mb))) + } + + /// Create a new Websocket context + pub fn with_factory(req: HttpRequest, stream: WsStream

    , f: F) -> Body + where + F: FnOnce(&mut Self) -> A + 'static, + A: StreamHandler, + P: Stream + 'static, + { + let mb = Mailbox::default(); + let mut ctx = WebsocketContext { + inner: ContextParts::new(mb.sender_producer()), + stream: None, + request: req, + disconnected: false, + }; + ctx.add_stream(stream); let act = f(&mut ctx); - ctx.inner.set_actor(act); - ctx + Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb))) } } @@ -127,7 +143,6 @@ where } let stream = self.stream.as_mut().unwrap(); stream.push(ContextFrame::Chunk(Some(data))); - self.inner.modify(); } else { warn!("Trying to write to disconnected response"); } @@ -148,7 +163,6 @@ where /// Returns drain future pub fn drain(&mut self) -> Drain { let (tx, rx) = oneshot::channel(); - self.inner.modify(); self.add_frame(ContextFrame::Drain(tx)); Drain::new(rx) } @@ -207,7 +221,6 @@ where if let Some(s) = self.stream.as_mut() { s.push(frame) } - self.inner.modify(); } /// Handle of the running future @@ -254,28 +267,52 @@ where } } -impl ActorHttpContext for WebsocketContext +impl AsyncContextParts for WebsocketContext where A: Actor, +{ + fn parts(&mut self) -> &mut ContextParts { + &mut self.inner + } +} + +struct WebsocketContextFut +where + A: Actor>, +{ + fut: ContextFut>, +} + +impl WebsocketContextFut +where + A: Actor>, +{ + fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox) -> Self { + let fut = ContextFut::new(ctx, act, mailbox); + WebsocketContextFut { fut } + } +} + +impl ActorHttpContext for WebsocketContextFut +where + A: Actor>, S: 'static, { #[inline] fn disconnected(&mut self) { - self.disconnected = true; - self.stop(); + self.fut.ctx().disconnected = true; + self.fut.ctx().stop(); } fn poll(&mut self) -> Poll>, Error> { - let ctx: &mut WebsocketContext = unsafe { &mut *(self as *mut _) }; - - if self.inner.alive() && self.inner.poll(ctx).is_err() { + if self.fut.alive() && self.fut.poll().is_err() { return Err(ErrorInternalServerError("error")); } // frames - if let Some(data) = self.stream.take() { + if let Some(data) = self.fut.ctx().stream.take() { Ok(Async::Ready(Some(data))) - } else if self.inner.alive() { + } else if self.fut.alive() { Ok(Async::NotReady) } else { Ok(Async::Ready(None)) @@ -286,20 +323,10 @@ where impl ToEnvelope for WebsocketContext where A: Actor> + Handler, - M: Message + Send + 'static, + M: ActixMessage + Send + 'static, M::Result: Send, { fn pack(msg: M, tx: Option>) -> Envelope { Envelope::new(msg, tx) } } - -impl From> for Body -where - A: Actor>, - S: 'static, -{ - fn from(ctx: WebsocketContext) -> Body { - Body::Actor(Box::new(ctx)) - } -} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index bc99414d..63b7ab0a 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -179,10 +179,8 @@ where let mut resp = handshake(req)?; let stream = WsStream::new(req.payload()); - let mut ctx = WebsocketContext::new(req.clone(), actor); - ctx.add_stream(stream); - - Ok(resp.body(ctx)) + let body = WebsocketContext::new(req.clone(), actor, stream); + Ok(resp.body(body)) } /// Prepare `WebSocket` handshake response. From 6fd686ef98214a998596c76ec38d90680f487af7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Jul 2018 21:01:27 +0600 Subject: [PATCH 0463/1635] cleanup warnings --- Cargo.toml | 2 +- src/application.rs | 20 ++++---- src/client/connector.rs | 20 ++++---- src/client/response.rs | 4 +- src/context.rs | 2 +- src/extractor.rs | 2 +- src/fs.rs | 5 +- src/handler.rs | 1 - src/httpmessage.rs | 10 ++-- src/httprequest.rs | 38 ++++------------ src/httpresponse.rs | 2 - src/info.rs | 8 +--- src/json.rs | 4 +- src/middleware/csrf.rs | 1 - src/middleware/errhandlers.rs | 5 +- src/middleware/identity.rs | 6 +-- src/middleware/logger.rs | 10 ++-- src/middleware/mod.rs | 1 - src/middleware/session.rs | 1 - src/param.rs | 4 -- src/pipeline.rs | 14 +----- src/pred.rs | 7 +-- src/resource.rs | 1 - src/route.rs | 1 - src/router.rs | 24 ++-------- src/scope.rs | 5 +- src/server/error.rs | 3 +- src/server/h1.rs | 3 -- src/server/h1writer.rs | 6 +-- src/server/h2.rs | 4 -- src/server/message.rs | 4 +- src/server/mod.rs | 1 - src/server/output.rs | 8 ++-- src/server/settings.rs | 5 +- src/server/srv.rs | 86 ++++++++++++++++++----------------- src/test.rs | 10 ++-- src/ws/client.rs | 4 +- src/ws/context.rs | 2 +- src/ws/mod.rs | 8 ++-- 39 files changed, 116 insertions(+), 226 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fa243bc9..13dbb76d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ flate2-c = ["flate2/miniz-sys"] flate2-rust = ["flate2/rust_backend"] [dependencies] -# actix = "0.6.1" +# actix = "0.7.0" actix = { git="https://github.com/actix/actix.git" } base64 = "0.9" diff --git a/src/application.rs b/src/application.rs index 2cf7a4fa..aa9a74d1 100644 --- a/src/application.rs +++ b/src/application.rs @@ -7,13 +7,12 @@ use http::{Method, StatusCode}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::Middleware; -use param::Params; use pipeline::{HandlerType, Pipeline, PipelineHandler}; use pred::Predicate; use resource::ResourceHandler; use router::{Resource, RouteInfo, Router}; use scope::Scope; -use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request, ServerSettings}; +use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; /// Application pub struct HttpApplication { @@ -102,23 +101,20 @@ impl HttpApplication { continue 'outer; } } - let info = self - .router - .route_info_params(params, self.inner.prefix as u16); - return (info, HandlerType::Handler(idx)); + return ( + self.router.route_info_params(params), + HandlerType::Handler(idx), + ); } } } } - ( - self.router.default_route_info(self.inner.prefix as u16), - HandlerType::Default, - ) + (self.router.default_route_info(), HandlerType::Default) } } #[cfg(test)] - pub(crate) fn run(&self, mut req: Request) -> AsyncResult { + pub(crate) fn run(&self, req: Request) -> AsyncResult { let (info, tp) = self.get_handler(&req); let req = HttpRequest::new(req, Rc::clone(&self.state), info); @@ -129,7 +125,7 @@ impl HttpApplication { impl HttpHandler for HttpApplication { type Task = Pipeline>; - fn handle(&self, mut msg: Request) -> Result>, Request> { + fn handle(&self, msg: Request) -> Result>, Request> { let m = { let path = msg.path(); path.starts_with(&self.prefix) diff --git a/src/client/connector.rs b/src/client/connector.rs index 6c32d898..3bcc57c7 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -5,8 +5,8 @@ use std::{fmt, io, mem, time}; use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; use actix::{ - fut, Actor, ActorContext, ActorFuture, ActorResponse, Addr, AsyncContext, Context, - ContextFutureSpawner, Handler, Message, Recipient, StreamHandler2, Supervised, + fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context, + ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, SystemService, WrapFuture, }; @@ -220,7 +220,7 @@ impl Actor for ClientConnector { self.resolver = Some(Resolver::from_registry()) } self.collect_periodic(ctx); - ctx.add_stream2(self.acq_rx.take().unwrap()); + ctx.add_stream(self.acq_rx.take().unwrap()); ctx.spawn(Maintenance); } } @@ -769,20 +769,17 @@ impl Handler for ClientConnector { } } -impl StreamHandler2 for ClientConnector { - fn handle( - &mut self, msg: Result, ()>, - ctx: &mut Context, - ) { +impl StreamHandler for ClientConnector { + fn handle(&mut self, msg: AcquiredConnOperation, _: &mut Context) { let now = Instant::now(); match msg { - Ok(Some(AcquiredConnOperation::Close(conn))) => { + AcquiredConnOperation::Close(conn) => { self.release_key(&conn.key); self.to_close.push(conn); self.stats.closed += 1; } - Ok(Some(AcquiredConnOperation::Release(conn))) => { + AcquiredConnOperation::Release(conn) => { self.release_key(&conn.key); // check connection lifetime and the return to available pool @@ -793,10 +790,9 @@ impl StreamHandler2 for ClientConnector { .push_back(Conn(Instant::now(), conn)); } } - Ok(Some(AcquiredConnOperation::ReleaseKey(key))) => { + AcquiredConnOperation::ReleaseKey(key) => { self.release_key(&key); } - _ => ctx.stop(), } // check keep-alive diff --git a/src/client/response.rs b/src/client/response.rs index a5c23014..0c094a2a 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,13 +1,11 @@ use std::cell::RefCell; use std::{fmt, str}; -use bytes::Bytes; use cookie::Cookie; -use futures::{Async, Poll, Stream}; use http::header::{self, HeaderValue}; use http::{HeaderMap, StatusCode, Version}; -use error::{CookieParseError, PayloadError}; +use error::CookieParseError; use httpmessage::HttpMessage; use super::pipeline::Pipeline; diff --git a/src/context.rs b/src/context.rs index d13cd417..71a5af2d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -105,7 +105,7 @@ where { #[inline] /// Create a new HTTP Context from a request and an actor - pub fn new(req: HttpRequest, actor: A) -> Body { + pub fn create(req: HttpRequest, actor: A) -> Body { let mb = Mailbox::default(); let ctx = HttpContext { inner: ContextParts::new(mb.sender_producer()), diff --git a/src/extractor.rs b/src/extractor.rs index 1b75f9f1..8e0a9659 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -768,7 +768,7 @@ mod tests { routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); let (router, _) = Router::new("", routes); - let mut req = TestRequest::with_uri("/name/user1/?id=test") + let req = TestRequest::with_uri("/name/user1/?id=test") .finish_with_router(router.clone()); let info = router.recognize(&req).unwrap().1; let req = req.with_route_info(info); diff --git a/src/fs.rs b/src/fs.rs index 66378823..b6ba4706 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -2,7 +2,6 @@ use std::fmt::Write; use std::fs::{DirEntry, File, Metadata}; use std::io::{Read, Seek}; -use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -20,9 +19,7 @@ use mime_guess::{get_mime_type, guess_mime_type}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use error::Error; -use handler::{ - AsyncResult, AsyncResultItem, Handler, Responder, RouteHandler, WrapHandler, -}; +use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use header; use http::{ContentEncoding, Method, StatusCode}; use httpmessage::HttpMessage; diff --git a/src/handler.rs b/src/handler.rs index c7fd6398..690d7166 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -10,7 +10,6 @@ use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; use resource::ResourceHandler; -use server::Request; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 8ed48de2..4da0163e 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -16,7 +16,6 @@ use error::{ use header::Header; use json::JsonBody; use multipart::Multipart; -use payload::Payload; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { @@ -613,10 +612,7 @@ mod tests { use encoding::all::ISO_8859_2; use encoding::Encoding; use futures::Async; - use http::{Method, Uri, Version}; - use httprequest::HttpRequest; use mime; - use std::str::FromStr; use test::TestRequest; #[test] @@ -765,7 +761,7 @@ mod tests { #[test] fn test_urlencoded() { - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") @@ -780,7 +776,7 @@ mod tests { }) ); - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ).header(header::CONTENT_LENGTH, "11") @@ -818,7 +814,7 @@ mod tests { _ => unreachable!("error"), } - let mut req = TestRequest::default() + let req = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).poll().err().unwrap() { diff --git a/src/httprequest.rs b/src/httprequest.rs index 3cfcb68a..18656855 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -1,22 +1,18 @@ //! HTTP Request message related code. -use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::cell::{Ref, RefMut}; use std::collections::HashMap; use std::net::SocketAddr; use std::ops::Deref; use std::rc::Rc; -use std::{cmp, fmt, io, str}; +use std::{fmt, str}; -use bytes::Bytes; use cookie::Cookie; -use failure; -use futures::{Async, Poll, Stream}; use futures_cpupool::CpuPool; use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; -use tokio_io::AsyncRead; use url::{form_urlencoded, Url}; use body::Body; -use error::{CookieParseError, PayloadError, UrlGenerationError}; +use error::{CookieParseError, UrlGenerationError}; use extensions::Extensions; use handler::FromRequest; use httpmessage::HttpMessage; @@ -24,19 +20,12 @@ use httpresponse::{HttpResponse, HttpResponseBuilder}; use info::ConnectionInfo; use param::Params; use payload::Payload; -use router::{Resource, RouteInfo, Router}; -use server::message::{MessageFlags, Request}; -use uri::Url as InnerUrl; +use router::{Resource, RouteInfo}; +use server::Request; struct Query(HashMap); struct Cookies(Vec>); -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum RouterResource { - Notset, - Normal(u16), -} - /// An HTTP Request pub struct HttpRequest { req: Rc, @@ -167,11 +156,6 @@ impl HttpRequest { self.request().inner.url.path() } - #[inline] - pub(crate) fn url(&self) -> &InnerUrl { - &self.request().inner.url - } - /// Get *ConnectionInfo* for the correct request. #[inline] pub fn connection_info(&self) -> Ref { @@ -248,7 +232,6 @@ impl HttpRequest { for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { query.insert(key.as_ref().to_string(), val.to_string()); } - let mut req = self.clone(); self.extensions_mut().insert(Query(query)); } Ref::map(self.extensions(), |ext| &ext.get::().unwrap().0) @@ -270,7 +253,6 @@ impl HttpRequest { #[inline] pub fn cookies(&self) -> Result>>, CookieParseError> { if self.extensions().get::().is_none() { - let mut req = self.clone(); let mut cookies = Vec::new(); for hdr in self.request().inner.headers.get_all(header::COOKIE) { let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; @@ -379,7 +361,7 @@ impl fmt::Debug for HttpRequest { mod tests { use super::*; use resource::ResourceHandler; - use router::Resource; + use router::{Resource, Router}; use test::TestRequest; #[test] @@ -448,7 +430,7 @@ mod tests { let routes = vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; let (router, _) = Router::new("/", routes); - let info = router.default_route_info(0); + let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); assert!(!info.has_route("/test/unknown")); @@ -476,7 +458,7 @@ mod tests { resource.name("index"); let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))]; let (router, _) = Router::new("/prefix/", routes); - let info = router.default_route_info(0); + let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); assert!(!info.has_route("/prefix/user/test.html")); @@ -495,7 +477,7 @@ mod tests { resource.name("index"); let routes = vec![(Resource::new("index", "/index.html"), Some(resource))]; let (router, _) = Router::new("/prefix/", routes); - let info = router.default_route_info(0); + let info = router.default_route_info(); assert!(info.has_route("/index.html")); assert!(!info.has_route("/prefix/index.html")); @@ -518,7 +500,7 @@ mod tests { None, )]; let router = Router::new::<()>("", routes).0; - let info = router.default_route_info(0); + let info = router.default_route_info(); assert!(!info.has_route("https://youtube.com/watch/unknown")); let req = TestRequest::default().finish_with_router(router); diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 71db8767..83c128d7 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -1042,8 +1042,6 @@ mod tests { use body::Binary; use http; use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use http::{Method, Uri}; - use std::str::FromStr; use time::Duration; use test::TestRequest; diff --git a/src/info.rs b/src/info.rs index 5d43b8e9..b15ba988 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,9 +1,4 @@ -use std::rc::Rc; -use std::str::FromStr; - use http::header::{self, HeaderName}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; use server::Request; const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; @@ -165,7 +160,6 @@ impl ConnectionInfo { #[cfg(test)] mod tests { use super::*; - use http::header::HeaderValue; use test::TestRequest; #[test] @@ -214,7 +208,7 @@ mod tests { assert_eq!(info.host(), "192.0.2.60"); assert_eq!(info.remote(), None); - let mut req = TestRequest::default() + let req = TestRequest::default() .header(X_FORWARDED_PROTO, "https") .request(); let mut info = ConnectionInfo::default(); diff --git a/src/json.rs b/src/json.rs index e9083cdd..485d0b3e 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,4 +1,4 @@ -use bytes::{Bytes, BytesMut}; +use bytes::BytesMut; use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; use std::fmt; @@ -10,7 +10,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; -use error::{Error, JsonPayloadError, PayloadError}; +use error::{Error, JsonPayloadError}; use handler::{FromRequest, Responder}; use http::StatusCode; use httpmessage::HttpMessage; diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 0062bd02..cda1d324 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -50,7 +50,6 @@ use std::collections::HashSet; use bytes::Bytes; use error::{ResponseError, Result}; use http::{header, HeaderMap, HttpTryFrom, Uri}; -use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Started}; diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index a9ebe21c..83c66aae 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -87,10 +87,7 @@ mod tests { use http::StatusCode; use httpmessage::HttpMessage; use middleware::Started; - use test; - - use server::Request; - use test::TestRequest; + use test::{self, TestRequest}; fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { let mut builder = resp.into_builder(); diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index c8554244..d890bebe 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -46,7 +46,6 @@ //! )); //! } //! ``` -use std::cell::RefCell; use std::rc::Rc; use cookie::{Cookie, CookieJar, Key}; @@ -59,7 +58,6 @@ use http::header::{self, HeaderValue}; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; -use server::Request; /// The helper trait to obtain your identity from a request. /// @@ -109,13 +107,13 @@ impl RequestIdentity for HttpRequest { } fn remember(&self, identity: String) { - if let Some(mut id) = self.extensions_mut().get_mut::() { + if let Some(id) = self.extensions_mut().get_mut::() { return id.0.as_mut().remember(identity); } } fn forget(&self) { - if let Some(mut id) = self.extensions_mut().get_mut::() { + if let Some(id) = self.extensions_mut().get_mut::() { return id.0.forget(); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index dbad60a1..103cbf37 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -11,7 +11,6 @@ use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Started}; -use server::Request; /// `Middleware` for logging request and response info to the terminal. /// @@ -309,13 +308,12 @@ impl<'a> fmt::Display for FormatDisplay<'a> { #[cfg(test)] mod tests { - use super::*; - use http::header::{self, HeaderMap}; - use http::{Method, StatusCode, Uri, Version}; - use std::str::FromStr; - use test::TestRequest; use time; + use super::*; + use http::{header, StatusCode}; + use test::TestRequest; + #[test] fn test_logger() { let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 237a0eb3..c69dbb3e 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -4,7 +4,6 @@ use futures::Future; use error::{Error, Result}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use server::Request; mod logger; diff --git a/src/middleware/session.rs b/src/middleware/session.rs index af3d03cc..9661c2bf 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -83,7 +83,6 @@ use handler::FromRequest; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; -use server::Request; /// The helper trait to obtain your session data from a request. /// diff --git a/src/param.rs b/src/param.rs index 649345ee..c58d9e78 100644 --- a/src/param.rs +++ b/src/param.rs @@ -57,10 +57,6 @@ impl Params { self.segments.clear(); } - pub(crate) fn set_url(&mut self, url: Url) { - self.url = url; - } - pub(crate) fn set_tail(&mut self, tail: u16) { self.tail = tail; } diff --git a/src/pipeline.rs b/src/pipeline.rs index 0ba25806..528680f5 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -6,7 +6,6 @@ use futures::sync::oneshot; use futures::{Async, Future, Poll, Stream}; use log::Level::Debug; -use application::Inner; use body::{Body, BodyStream}; use context::{ActorHttpContext, Frame}; use error::Error; @@ -15,7 +14,7 @@ use header::ContentEncoding; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Response, Started}; -use server::{HttpHandlerTask, Request, Writer, WriterState}; +use server::{HttpHandlerTask, Writer, WriterState}; #[doc(hidden)] #[derive(Debug, Clone, Copy)] @@ -84,17 +83,6 @@ struct PipelineInfo { } impl PipelineInfo { - fn new(req: HttpRequest) -> PipelineInfo { - PipelineInfo { - req, - count: 0, - error: None, - context: None, - disconnected: None, - encoding: ContentEncoding::Auto, - } - } - fn poll_context(&mut self) -> Poll<(), Error> { if let Some(ref mut context) = self.context { match context.poll() { diff --git a/src/pred.rs b/src/pred.rs index 5d47922f..ebc48c34 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -4,8 +4,6 @@ use std::marker::PhantomData; use http; use http::{header, HttpTryFrom}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; use server::message::Request; /// Trait defines resource route predicate. @@ -239,11 +237,8 @@ impl Predicate for HostPredicate { #[cfg(test)] mod tests { - use std::str::FromStr; - use super::*; - use http::header::{self, HeaderMap}; - use http::{Method, Uri, Version}; + use http::{header, Method}; use test::TestRequest; #[test] diff --git a/src/resource.rs b/src/resource.rs index 4e8ecf55..cbf3d985 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -12,7 +12,6 @@ use httpresponse::HttpResponse; use middleware::Middleware; use pred; use route::Route; -use server::Request; #[derive(Copy, Clone)] pub(crate) struct RouteId(usize); diff --git a/src/route.rs b/src/route.rs index bf880a3c..d383d90b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -16,7 +16,6 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; -use server::Request; use with::{With, WithAsync}; /// Resource route definition diff --git a/src/router.rs b/src/router.rs index f4da7da0..0edf5184 100644 --- a/src/router.rs +++ b/src/router.rs @@ -3,11 +3,9 @@ use std::hash::{Hash, Hasher}; use std::rc::Rc; use regex::{escape, Regex}; -use smallvec::SmallVec; use url::Url; use error::UrlGenerationError; -use httprequest::HttpRequest; use param::{ParamItem, Params}; use resource::ResourceHandler; use server::Request; @@ -25,7 +23,6 @@ pub struct Router(Rc); pub struct RouteInfo { router: Rc, resource: RouterResource, - prefix: u16, params: Params, } @@ -51,14 +48,8 @@ impl RouteInfo { &self.params } - #[doc(hidden)] #[inline] - pub fn prefix_len(&self) -> u16 { - self.prefix - } - - #[inline] - pub(crate) fn merge(&self, mut params: Params) -> RouteInfo { + pub(crate) fn merge(&self, params: &Params) -> RouteInfo { let mut p = self.params.clone(); p.set_tail(params.tail); for item in ¶ms.segments { @@ -69,7 +60,6 @@ impl RouteInfo { params: p, router: self.router.clone(), resource: self.resource, - prefix: self.prefix, } } @@ -167,10 +157,6 @@ impl Router { &self.0.prefix } - pub(crate) fn get_resource(&self, idx: usize) -> &Resource { - &self.0.patterns[idx] - } - pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> RouteInfo { let mut params = Params::with_url(req.url()); params.set_tail(prefix); @@ -179,22 +165,19 @@ impl Router { params, router: self.0.clone(), resource: RouterResource::Notset, - prefix: 0, } } - pub(crate) fn route_info_params(&self, params: Params, prefix: u16) -> RouteInfo { + pub(crate) fn route_info_params(&self, params: Params) -> RouteInfo { RouteInfo { params, - prefix, router: self.0.clone(), resource: RouterResource::Notset, } } - pub(crate) fn default_route_info(&self, prefix: u16) -> RouteInfo { + pub(crate) fn default_route_info(&self) -> RouteInfo { RouteInfo { - prefix, router: self.0.clone(), resource: RouterResource::Notset, params: Params::new(), @@ -215,7 +198,6 @@ impl Router { params, router: self.0.clone(), resource: RouterResource::Normal(idx as u16), - prefix: self.0.prefix_len as u16, }, )); } diff --git a/src/scope.rs b/src/scope.rs index 5db1562b..ffa58157 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -341,7 +341,7 @@ impl RouteHandler for Scope { // recognize resources for &(ref pattern, ref resource) in self.resources.iter() { if let Some(params) = pattern.match_with_params(req, tail, false) { - let req2 = req.with_route_info(req.route().merge(params)); + let req2 = req.with_route_info(req.route().merge(¶ms)); if let Some(id) = resource.get_route_id(&req2) { if self.middlewares.is_empty() { return resource.handle(id, &req2); @@ -358,10 +358,9 @@ impl RouteHandler for Scope { } // nested scopes - let len = req.route().prefix_len() as usize; 'outer: for &(ref prefix, ref handler, ref filters) in &self.nested { if let Some(params) = prefix.match_prefix_with_params(req, tail) { - let req2 = req.with_route_info(req.route().merge(params)); + let req2 = req.with_route_info(req.route().merge(¶ms)); let state = req.state(); for filter in filters { diff --git a/src/server/error.rs b/src/server/error.rs index 79b3e4f0..b3c79a06 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -2,7 +2,6 @@ use futures::{Async, Poll}; use super::{helpers, HttpHandlerTask, Writer}; use http::{StatusCode, Version}; -use httpresponse::HttpResponse; use Error; pub(crate) struct ServerError(Version, StatusCode); @@ -16,7 +15,7 @@ impl ServerError { impl HttpHandlerTask for ServerError { fn poll_io(&mut self, io: &mut Writer) -> Poll { { - let mut bytes = io.buffer(); + let bytes = io.buffer(); helpers::write_status_line(self.0, self.1.as_u16(), bytes); } io.set_date(); diff --git a/src/server/h1.rs b/src/server/h1.rs index a9b3100f..c3d44c30 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -9,10 +9,7 @@ use tokio_timer::Delay; use error::{Error, PayloadError}; use http::{StatusCode, Version}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; use payload::{Payload, PayloadStatus, PayloadWriter}; -use pipeline::Pipeline; use super::error::ServerError; use super::h1decoder::{DecoderError, H1Decoder, Message}; diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 70797804..d25f236d 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -149,20 +149,18 @@ impl Writer for H1Writer { let mut buffer = self.buffer.as_mut(); let reason = msg.reason().as_bytes(); - let mut is_bin = if let Body::Binary(ref bytes) = body { + if let Body::Binary(ref bytes) = body { buffer.reserve( 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len() + reason.len(), ); - true } else { buffer.reserve( 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), ); - false - }; + } // status line helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); diff --git a/src/server/h2.rs b/src/server/h2.rs index a8f28fbf..001a46b7 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -15,11 +15,7 @@ use tokio_timer::Delay; use error::{Error, PayloadError}; use http::{StatusCode, Version}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; use payload::{Payload, PayloadStatus, PayloadWriter}; -use pipeline::Pipeline; use uri::Url; use super::error::ServerError; diff --git a/src/server/message.rs b/src/server/message.rs index 73b23873..61f41893 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -172,7 +172,8 @@ impl Request { } #[inline] - pub(crate) fn reset(&mut self) { + /// Reset request instance + pub fn reset(&mut self) { self.inner.headers.clear(); self.inner.extensions.borrow_mut().clear(); self.inner.flags.set(MessageFlags::empty()); @@ -210,6 +211,7 @@ impl RequestPool { } #[inline] + /// Release request instance pub fn release(&self, mut msg: Request) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { diff --git a/src/server/mod.rs b/src/server/mod.rs index 4f95ffb7..a302f5e7 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -33,7 +33,6 @@ use actix::Message; use body::Binary; use error::Error; use header::ContentEncoding; -use httprequest::HttpRequest; use httpresponse::HttpResponse; /// max buffer size 64k diff --git a/src/server/output.rs b/src/server/output.rs index 1d9ee92a..597faf34 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -10,12 +10,10 @@ use bytes::BytesMut; use flate2::write::{DeflateEncoder, GzEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; -use http::header::{ - HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Method, Version}; +use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH}; +use http::Version; -use super::message::{InnerRequest, Request}; +use super::message::InnerRequest; use body::{Binary, Body}; use header::ContentEncoding; use httpresponse::HttpResponse; diff --git a/src/server/settings.rs b/src/server/settings.rs index 7f38088c..ceb362ac 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -11,7 +11,6 @@ use parking_lot::Mutex; use time; use super::channel::Node; -use super::helpers; use super::message::{Request, RequestPool}; use super::KeepAlive; use body::Body; @@ -161,7 +160,6 @@ pub(crate) struct WorkerSettings { channels: Cell, node: Box>, date: UnsafeCell, - settings: ServerSettings, } impl WorkerSettings { @@ -177,13 +175,12 @@ impl WorkerSettings { WorkerSettings { h: RefCell::new(h), bytes: Rc::new(SharedBytesPool::new()), - messages: RequestPool::pool(settings.clone()), + messages: RequestPool::pool(settings), channels: Cell::new(0), node: Box::new(Node::head()), date: UnsafeCell::new(Date::new()), keep_alive, ka_enabled, - settings, } } diff --git a/src/server/srv.rs b/src/server/srv.rs index 77223892..dc732121 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -5,7 +5,7 @@ use std::{io, net, thread}; use actix::{ fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler, - Response, StreamHandler2, System, WrapFuture, + Response, StreamHandler, System, WrapFuture, }; use futures::sync::mpsc; @@ -447,7 +447,7 @@ impl HttpServer { // start http server actor let signals = self.subscribe_to_signals(); let addr = Actor::create(move |ctx| { - ctx.add_stream2(rx); + ctx.add_stream(rx); self }); if let Some(signals) = signals { @@ -613,51 +613,55 @@ impl Handler for HttpServer { } /// Commands from accept threads -impl StreamHandler2 for HttpServer { - fn handle(&mut self, msg: Result, ()>, _: &mut Context) { - if let Ok(Some(ServerCommand::WorkerDied(idx, socks))) = msg { - let mut found = false; - for i in 0..self.workers.len() { - if self.workers[i].0 == idx { - self.workers.swap_remove(i); - found = true; - break; - } - } +impl StreamHandler for HttpServer { + fn finished(&mut self, _: &mut Context) {} - if found { - error!("Worker has died {:?}, restarting", idx); - let (tx, rx) = mpsc::unbounded::>(); - - let mut new_idx = self.workers.len(); - 'found: loop { - for i in 0..self.workers.len() { - if self.workers[i].0 == new_idx { - new_idx += 1; - continue 'found; - } + fn handle(&mut self, msg: ServerCommand, _: &mut Context) { + match msg { + ServerCommand::WorkerDied(idx, socks) => { + let mut found = false; + for i in 0..self.workers.len() { + if self.workers[i].0 == idx { + self.workers.swap_remove(i); + found = true; + break; } - break; } - let ka = self.keep_alive; - let factory = Arc::clone(&self.factory); - let host = self.host.clone(); - let addr = socks[0].addr; + if found { + error!("Worker has died {:?}, restarting", idx); + let (tx, rx) = mpsc::unbounded::>(); - let addr = Arbiter::start(move |ctx: &mut Context<_>| { - let settings = ServerSettings::new(Some(addr), &host, false); - let apps: Vec<_> = - (*factory)().into_iter().map(|h| h.into_handler()).collect(); - ctx.add_message_stream(rx); - Worker::new(apps, socks, ka, settings) - }); - for item in &self.accept { - let _ = item.1.send(Command::Worker(new_idx, tx.clone())); - let _ = item.0.set_readiness(mio::Ready::readable()); + let mut new_idx = self.workers.len(); + 'found: loop { + for i in 0..self.workers.len() { + if self.workers[i].0 == new_idx { + new_idx += 1; + continue 'found; + } + } + break; + } + + let ka = self.keep_alive; + let factory = Arc::clone(&self.factory); + let host = self.host.clone(); + let addr = socks[0].addr; + + let addr = Arbiter::start(move |ctx: &mut Context<_>| { + let settings = ServerSettings::new(Some(addr), &host, false); + let apps: Vec<_> = + (*factory)().into_iter().map(|h| h.into_handler()).collect(); + ctx.add_message_stream(rx); + Worker::new(apps, socks, ka, settings) + }); + for item in &self.accept { + let _ = item.1.send(Command::Worker(new_idx, tx.clone())); + let _ = item.0.set_readiness(mio::Ready::readable()); + } + + self.workers.push((new_idx, addr)); } - - self.workers.push((new_idx, addr)); } } } diff --git a/src/test.rs b/src/test.rs index 5fc06f65..bc34472e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -22,7 +22,7 @@ use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; use error::Error; use handler::{AsyncResultItem, Handler, Responder}; use header::{Header, IntoHeaderValue}; -use httprequest::{HttpRequest, RouterResource}; +use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::Middleware; use param::Params; @@ -548,7 +548,7 @@ impl TestRequest { *req.inner.payload.borrow_mut() = payload; let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(params, 0)); + HttpRequest::new(req, Rc::new(state), router.route_info_params(params)); req.set_cookies(cookies); req } @@ -574,7 +574,7 @@ impl TestRequest { req.inner.headers = headers; *req.inner.payload.borrow_mut() = payload; let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(params, 0)); + HttpRequest::new(req, Rc::new(state), router.route_info_params(params)); req.set_cookies(cookies); req } @@ -582,14 +582,12 @@ impl TestRequest { /// Complete request creation and generate server `Request` instance pub fn request(self) -> Request { let TestRequest { - state, method, uri, version, headers, - params, - cookies, payload, + .. } = self; let mut req = Request::new(ServerSettings::default()); req.inner.method = method; diff --git a/src/ws/client.rs b/src/ws/client.rs index 251b0edd..4295905a 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -23,8 +23,8 @@ use httpmessage::HttpMessage; use payload::PayloadHelper; use client::{ - ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse, - HttpResponseParserError, Pipeline, SendRequest, SendRequestError, + ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError, + Pipeline, SendRequest, SendRequestError, }; use super::frame::Frame; diff --git a/src/ws/context.rs b/src/ws/context.rs index 91a23e0f..0444db99 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -92,7 +92,7 @@ where { #[inline] /// Create a new Websocket context from a request and an actor - pub fn new

    (req: HttpRequest, actor: A, stream: WsStream

    ) -> Body + pub fn create

    (req: HttpRequest, actor: A, stream: WsStream

    ) -> Body where A: StreamHandler, P: Stream + 'static, diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 63b7ab0a..05099971 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -45,7 +45,7 @@ use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{header, Method, StatusCode}; -use super::actix::{Actor, AsyncContext, StreamHandler}; +use super::actix::{Actor, StreamHandler}; use body::Binary; use error::{Error, PayloadError, ResponseError}; @@ -179,7 +179,7 @@ where let mut resp = handshake(req)?; let stream = WsStream::new(req.payload()); - let body = WebsocketContext::new(req.clone(), actor, stream); + let body = WebsocketContext::create(req.clone(), actor, stream); Ok(resp.body(body)) } @@ -357,10 +357,8 @@ pub trait WsWriter { #[cfg(test)] mod tests { - use std::str::FromStr; - use super::*; - use http::{header, HeaderMap, Method, Uri, Version}; + use http::{header, Method}; use test::TestRequest; #[test] From 5d791142390ff55887d7d7e8157f3b4615498427 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Jul 2018 22:52:49 +0600 Subject: [PATCH 0464/1635] optimize Request handling --- src/httprequest.rs | 32 ++++++++----- src/server/h1.rs | 2 +- src/server/h1decoder.rs | 4 +- src/server/h2.rs | 17 ++++--- src/server/message.rs | 102 +++++++++++++++++++++++++--------------- src/server/settings.rs | 4 +- src/test.rs | 52 ++++++++++++-------- 7 files changed, 134 insertions(+), 79 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 18656855..56e95e9e 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -28,7 +28,7 @@ struct Cookies(Vec>); /// An HTTP Request pub struct HttpRequest { - req: Rc, + req: Option, state: Rc, route: RouteInfo, } @@ -38,12 +38,12 @@ impl HttpMessage for HttpRequest { #[inline] fn headers(&self) -> &HeaderMap { - self.req.headers() + self.request().headers() } #[inline] fn payload(&self) -> Payload { - if let Some(payload) = self.req.inner.payload.borrow_mut().take() { + if let Some(payload) = self.request().inner.payload.borrow_mut().take() { payload } else { Payload::empty() @@ -55,7 +55,7 @@ impl Deref for HttpRequest { type Target = Request; fn deref(&self) -> &Request { - self.req.as_ref() + self.request() } } @@ -65,7 +65,7 @@ impl HttpRequest { HttpRequest { state, route, - req: Rc::new(req), + req: Some(req), } } @@ -98,38 +98,40 @@ impl HttpRequest { #[inline] /// Server request pub fn request(&self) -> &Request { - &self.req + self.req.as_ref().unwrap() } /// Request extensions #[inline] pub fn extensions(&self) -> Ref { - self.req.extensions() + self.request().extensions() } /// Mutable reference to a the request's extensions #[inline] pub fn extensions_mut(&self) -> RefMut { - self.req.extensions_mut() + self.request().extensions_mut() } /// Default `CpuPool` #[inline] #[doc(hidden)] pub fn cpu_pool(&self) -> &CpuPool { - self.req.server_settings().cpu_pool() + self.request().server_settings().cpu_pool() } #[inline] /// Create http response pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse { - self.req.server_settings().get_response(status, body) + self.request().server_settings().get_response(status, body) } #[inline] /// Create http response builder pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder { - self.req.server_settings().get_response_builder(status) + self.request() + .server_settings() + .get_response_builder(status) } /// Read the Request Uri. @@ -314,6 +316,14 @@ impl HttpRequest { } } +impl Drop for HttpRequest { + fn drop(&mut self) { + if let Some(req) = self.req.take() { + req.release(); + } + } +} + impl Clone for HttpRequest { fn clone(&self) -> HttpRequest { HttpRequest { diff --git a/src/server/h1.rs b/src/server/h1.rs index c3d44c30..e5559663 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -365,7 +365,7 @@ where } // set remote addr - msg.inner.addr = self.addr; + msg.inner_mut().addr = self.addr; // stop keepalive timer self.keepalive_timer.take(); diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 9f14bb47..977e89a8 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -118,9 +118,9 @@ impl H1Decoder { let slice = buf.split_to(len).freeze(); // convert headers - let mut msg = settings.get_request_context(); + let mut msg = settings.get_request(); { - let inner = &mut msg.inner; + let inner = msg.inner_mut(); inner .flags .get_mut() diff --git a/src/server/h2.rs b/src/server/h2.rs index 001a46b7..d812f6f5 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -329,13 +329,16 @@ impl Entry { // Payload and Content-Encoding let (psender, payload) = Payload::new(false); - let mut msg = settings.get_request_context(); - msg.inner.url = Url::new(parts.uri); - msg.inner.method = parts.method; - msg.inner.version = parts.version; - msg.inner.headers = parts.headers; - *msg.inner.payload.borrow_mut() = Some(payload); - msg.inner.addr = addr; + let mut msg = settings.get_request(); + { + let inner = msg.inner_mut(); + inner.url = Url::new(parts.uri); + inner.method = parts.method; + inner.version = parts.version; + inner.headers = parts.headers; + *inner.payload.borrow_mut() = Some(payload); + inner.addr = addr; + } // Payload sender let psender = PayloadType::new(msg.headers(), psender); diff --git a/src/server/message.rs b/src/server/message.rs index 61f41893..eb1395ac 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -1,6 +1,7 @@ use std::cell::{Cell, Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::net::SocketAddr; +use std::rc::Rc; use http::{header, HeaderMap, Method, Uri, Version}; @@ -19,8 +20,9 @@ bitflags! { } /// Request's context +#[derive(Clone)] pub struct Request { - pub(crate) inner: Box, + pub(crate) inner: Rc, } pub(crate) struct InnerRequest { @@ -34,6 +36,18 @@ pub(crate) struct InnerRequest { pub(crate) info: RefCell, pub(crate) payload: RefCell>, pub(crate) settings: ServerSettings, + pool: &'static RequestPool, +} + +impl InnerRequest { + #[inline] + /// Reset request instance + pub fn reset(&mut self) { + self.headers.clear(); + self.extensions.borrow_mut().clear(); + self.flags.set(MessageFlags::empty()); + *self.payload.borrow_mut() = None; + } } impl HttpMessage for Request { @@ -55,9 +69,10 @@ impl HttpMessage for Request { impl Request { /// Create new RequestContext instance - pub fn new(settings: ServerSettings) -> Request { + pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request { Request { - inner: Box::new(InnerRequest { + inner: Rc::new(InnerRequest { + pool, settings, method: Method::GET, url: InnerUrl::default(), @@ -72,45 +87,55 @@ impl Request { } } + #[inline] + pub(crate) fn inner(&self) -> &InnerRequest { + self.inner.as_ref() + } + + #[inline] + pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest { + Rc::get_mut(&mut self.inner).expect("Multiple copies exist") + } + #[inline] pub(crate) fn url(&self) -> &InnerUrl { - &self.inner.url + &self.inner().url } /// Read the Request Uri. #[inline] pub fn uri(&self) -> &Uri { - self.inner.url.uri() + self.inner().url.uri() } /// Read the Request method. #[inline] pub fn method(&self) -> &Method { - &self.inner.method + &self.inner().method } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner.version + self.inner().version } /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.inner.url.path() + self.inner().url.path() } #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner.headers + &self.inner().headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner.headers + &mut self.inner_mut().headers } /// Peer socket address @@ -121,67 +146,71 @@ impl Request { /// To get client connection information `connection_info()` method should /// be used. pub fn peer_addr(&self) -> Option { - self.inner.addr + self.inner().addr } /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { - self.inner.flags.get().contains(MessageFlags::KEEPALIVE) + self.inner().flags.get().contains(MessageFlags::KEEPALIVE) } /// Request extensions #[inline] pub fn extensions(&self) -> Ref { - self.inner.extensions.borrow() + self.inner().extensions.borrow() } /// Mutable reference to a the request's extensions #[inline] pub fn extensions_mut(&self) -> RefMut { - self.inner.extensions.borrow_mut() + self.inner().extensions.borrow_mut() } /// Check if request requires connection upgrade pub fn upgrade(&self) -> bool { - if let Some(conn) = self.inner.headers.get(header::CONNECTION) { + if let Some(conn) = self.inner().headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { return s.to_lowercase().contains("upgrade"); } } - self.inner.method == Method::CONNECT + self.inner().method == Method::CONNECT } /// Get *ConnectionInfo* for the correct request. pub fn connection_info(&self) -> Ref { - if self.inner.flags.get().contains(MessageFlags::CONN_INFO) { - self.inner.info.borrow() + if self.inner().flags.get().contains(MessageFlags::CONN_INFO) { + self.inner().info.borrow() } else { - let mut flags = self.inner.flags.get(); + let mut flags = self.inner().flags.get(); flags.insert(MessageFlags::CONN_INFO); - self.inner.flags.set(flags); - self.inner.info.borrow_mut().update(self); - self.inner.info.borrow() + self.inner().flags.set(flags); + self.inner().info.borrow_mut().update(self); + self.inner().info.borrow() } } /// Server settings #[inline] pub fn server_settings(&self) -> &ServerSettings { - &self.inner.settings + &self.inner().settings } - #[inline] - /// Reset request instance - pub fn reset(&mut self) { - self.inner.headers.clear(); - self.inner.extensions.borrow_mut().clear(); - self.inner.flags.set(MessageFlags::empty()); - *self.inner.payload.borrow_mut() = None; + pub(crate) fn release(self) { + let mut inner = self.inner; + if let Some(r) = Rc::get_mut(&mut inner) { + r.reset(); + } else { + return; + } + inner.pool.release(inner); } } -pub(crate) struct RequestPool(RefCell>, RefCell); +pub(crate) struct RequestPool( + RefCell>>, + RefCell, +); thread_local!(static POOL: &'static RequestPool = RequestPool::create()); @@ -202,20 +231,19 @@ impl RequestPool { } #[inline] - pub fn get(&self) -> Request { - if let Some(msg) = self.0.borrow_mut().pop_front() { - msg + pub fn get(pool: &'static RequestPool) -> Request { + if let Some(msg) = pool.0.borrow_mut().pop_front() { + Request { inner: msg } } else { - Request::new(self.1.borrow().clone()) + Request::new(pool, pool.1.borrow().clone()) } } #[inline] /// Release request instance - pub fn release(&self, mut msg: Request) { + pub fn release(&self, msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { - msg.reset(); v.push_front(msg); } } diff --git a/src/server/settings.rs b/src/server/settings.rs index ceb362ac..0347241b 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -212,8 +212,8 @@ impl WorkerSettings { self.bytes.release_bytes(bytes) } - pub fn get_request_context(&self) -> Request { - self.messages.get() + pub fn get_request(&self) -> Request { + RequestPool::get(self.messages) } pub fn add_channel(&self) { diff --git a/src/test.rs b/src/test.rs index bc34472e..704292df 100644 --- a/src/test.rs +++ b/src/test.rs @@ -29,7 +29,8 @@ use param::Params; use payload::Payload; use resource::ResourceHandler; use router::Router; -use server::{HttpServer, IntoHttpHandler, Request, ServerSettings}; +use server::message::{Request, RequestPool}; +use server::{HttpServer, IntoHttpHandler, ServerSettings}; use uri::Url as InnerUrl; use ws; @@ -540,12 +541,16 @@ impl TestRequest { } = self; let (router, _) = Router::new::("/", Vec::new()); - let mut req = Request::new(ServerSettings::default()); - req.inner.method = method; - req.inner.url = InnerUrl::new(uri); - req.inner.version = version; - req.inner.headers = headers; - *req.inner.payload.borrow_mut() = payload; + let pool = RequestPool::pool(ServerSettings::default()); + let mut req = RequestPool::get(pool); + { + let inner = req.inner_mut(); + inner.method = method; + inner.url = InnerUrl::new(uri); + inner.version = version; + inner.headers = headers; + *inner.payload.borrow_mut() = payload; + } let mut req = HttpRequest::new(req, Rc::new(state), router.route_info_params(params)); @@ -567,12 +572,16 @@ impl TestRequest { payload, } = self; - let mut req = Request::new(ServerSettings::default()); - req.inner.method = method; - req.inner.url = InnerUrl::new(uri); - req.inner.version = version; - req.inner.headers = headers; - *req.inner.payload.borrow_mut() = payload; + let pool = RequestPool::pool(ServerSettings::default()); + let mut req = RequestPool::get(pool); + { + let inner = req.inner_mut(); + inner.method = method; + inner.url = InnerUrl::new(uri); + inner.version = version; + inner.headers = headers; + *inner.payload.borrow_mut() = payload; + } let mut req = HttpRequest::new(req, Rc::new(state), router.route_info_params(params)); req.set_cookies(cookies); @@ -589,12 +598,17 @@ impl TestRequest { payload, .. } = self; - let mut req = Request::new(ServerSettings::default()); - req.inner.method = method; - req.inner.url = InnerUrl::new(uri); - req.inner.version = version; - req.inner.headers = headers; - *req.inner.payload.borrow_mut() = payload; + + let pool = RequestPool::pool(ServerSettings::default()); + let mut req = RequestPool::get(pool); + { + let inner = req.inner_mut(); + inner.method = method; + inner.url = InnerUrl::new(uri); + inner.version = version; + inner.headers = headers; + *inner.payload.borrow_mut() = payload; + } req } From d5606625a21803c2a627fdf2f2449c1c9d96ee02 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 4 Jul 2018 22:57:40 +0600 Subject: [PATCH 0465/1635] remove public Clone for Request --- src/httprequest.rs | 6 +++--- src/server/message.rs | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 56e95e9e..08de0a8f 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -74,7 +74,7 @@ impl HttpRequest { pub(crate) fn with_state(&self, state: Rc) -> HttpRequest { HttpRequest { state, - req: self.req.clone(), + req: self.req.as_ref().map(|r| r.clone()), route: self.route.clone(), } } @@ -84,7 +84,7 @@ impl HttpRequest { pub(crate) fn with_route_info(&self, route: RouteInfo) -> HttpRequest { HttpRequest { route, - req: self.req.clone(), + req: self.req.as_ref().map(|r| r.clone()), state: self.state.clone(), } } @@ -327,7 +327,7 @@ impl Drop for HttpRequest { impl Clone for HttpRequest { fn clone(&self) -> HttpRequest { HttpRequest { - req: self.req.clone(), + req: self.req.as_ref().map(|r| r.clone()), state: self.state.clone(), route: self.route.clone(), } diff --git a/src/server/message.rs b/src/server/message.rs index eb1395ac..395d7b7c 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -20,7 +20,6 @@ bitflags! { } /// Request's context -#[derive(Clone)] pub struct Request { pub(crate) inner: Rc, } @@ -196,6 +195,12 @@ impl Request { &self.inner().settings } + pub(crate) fn clone(&self) -> Self { + Request { + inner: self.inner.clone(), + } + } + pub(crate) fn release(self) { let mut inner = self.inner; if let Some(r) = Rc::get_mut(&mut inner) { From d7762297da02da64ab49a04260cb3a521888dbdc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 5 Jul 2018 12:02:32 +0600 Subject: [PATCH 0466/1635] update actix dependency --- CHANGES.md | 7 ++++++- Cargo.toml | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 71a5ae59..fa18d898 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -39,11 +39,15 @@ * For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self` +* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest` + * Added header `User-Agent: Actix-web/` to default headers when building a request * port `Extensions` type from http create, we don't need `Send + Sync` -* `HttpRequest::query()` returns `&HashMap` +* `HttpRequest::query()` returns `Ref>` + +* `HttpRequest::cookies()` returns `Ref>>` ### Removed @@ -52,6 +56,7 @@ * Remove `HttpMessage::range()` + ## [0.6.14] - 2018-06-21 ### Added diff --git a/Cargo.toml b/Cargo.toml index 13dbb76d..f9f148c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,8 +50,7 @@ flate2-c = ["flate2/miniz-sys"] flate2-rust = ["flate2/rust_backend"] [dependencies] -# actix = "0.7.0" -actix = { git="https://github.com/actix/actix.git" } +actix = "0.7.0" base64 = "0.9" bitflags = "1.0" From 6af2f5d6429e6855d20fdc5bfe8195850e4b50b1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 5 Jul 2018 12:14:10 +0600 Subject: [PATCH 0467/1635] re-enable start_incoming support --- src/server/srv.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/server/srv.rs b/src/server/srv.rs index dc732121..8582ab1f 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -22,7 +22,7 @@ use native_tls::TlsAcceptor; #[cfg(feature = "alpn")] use openssl::ssl::{AlpnError, SslAcceptorBuilder}; -use super::channel::WrapperStream; +use super::channel::{HttpChannel, WrapperStream}; use super::settings::{ServerSettings, WorkerSettings}; use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker}; use super::{IntoHttpHandler, IoStream, KeepAlive}; @@ -674,14 +674,13 @@ where { type Result = (); - fn handle(&mut self, _msg: Conn, _: &mut Context) -> Self::Result { - unimplemented!(); - /*Arbiter::spawn(HttpChannel::new( + fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { + Arbiter::spawn(HttpChannel::new( Rc::clone(self.h.as_ref().unwrap()), msg.io, msg.peer, msg.http2, - ));*/ + )); } } From 80339147b925cb3e2df762c3826326933028c805 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 5 Jul 2018 12:50:54 +0600 Subject: [PATCH 0468/1635] call disconnect on write error --- src/server/h1.rs | 20 ++++++++++++-------- tests/test_ws.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index e5559663..de2a6e8c 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -124,6 +124,14 @@ where } } + fn notify_disconnect(&mut self) { + // notify all tasks + self.stream.disconnected(); + for entry in &mut self.tasks { + entry.pipe.disconnected() + } + } + #[inline] pub fn poll(&mut self) -> Poll<(), ()> { // keep-alive timer @@ -183,10 +191,7 @@ where Ok(Async::Ready(disconnected)) => { if disconnected { // notify all tasks - self.stream.disconnected(); - for entry in &mut self.tasks { - entry.pipe.disconnected() - } + self.notify_disconnect(); // kill keepalive self.keepalive_timer.take(); @@ -204,10 +209,7 @@ where Ok(Async::NotReady) => (), Err(_) => { // notify all tasks - self.stream.disconnected(); - for entry in &mut self.tasks { - entry.pipe.disconnected() - } + self.notify_disconnect(); // kill keepalive self.keepalive_timer.take(); @@ -285,6 +287,7 @@ where Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => item.flags.insert(EntryFlags::FINISHED), Err(err) => { + self.notify_disconnect(); item.flags.insert(EntryFlags::ERROR); error!("Unhandled error: {}", err); } @@ -316,6 +319,7 @@ where Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); + self.notify_disconnect(); return Err(()); } Ok(Async::Ready(_)) => { diff --git a/tests/test_ws.rs b/tests/test_ws.rs index dd65d4a5..96d97b82 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -9,6 +9,7 @@ use bytes::Bytes; use futures::Stream; use rand::distributions::Alphanumeric; use rand::Rng; +use std::time::Duration; #[cfg(feature = "alpn")] extern crate openssl; @@ -120,6 +121,31 @@ fn test_large_bin() { } } +#[test] +fn test_client_frame_size() { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(131_072) + .collect::(); + + let mut srv = test::TestServer::new(|app| { + app.handler(|req| -> Result { + let mut resp = ws::handshake(req)?; + let stream = ws::WsStream::new(req.payload()).max_size(131_072); + + let body = ws::WebsocketContext::create(req.clone(), Ws, stream); + Ok(resp.body(body)) + }) + }); + let (reader, mut writer) = srv.ws().unwrap(); + + writer.binary(data.clone()); + match srv.execute(reader.into_future()).err().unwrap().0 { + ws::ProtocolError::Overflow => (), + _ => panic!(), + } +} + struct Ws2 { count: usize, bin: bool, From 05a43a855e320a86779bdf7fc761d1133788153e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 5 Jul 2018 13:00:46 +0600 Subject: [PATCH 0469/1635] remove unsafe --- src/server/h1.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index de2a6e8c..6b1a5b9c 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -232,19 +232,17 @@ where let mut io = false; let mut idx = 0; while idx < self.tasks.len() { - let item: &mut Entry = unsafe { &mut *(&mut self.tasks[idx] as *mut _) }; - // only one task can do io operation in http/1 - if !io && !item.flags.contains(EntryFlags::EOF) { + if !io && !self.tasks[idx].flags.contains(EntryFlags::EOF) { // io is corrupted, send buffer - if item.flags.contains(EntryFlags::ERROR) { + if self.tasks[idx].flags.contains(EntryFlags::ERROR) { if let Ok(Async::NotReady) = self.stream.poll_completed(true) { return Ok(Async::NotReady); } return Err(()); } - match item.pipe.poll_io(&mut self.stream) { + match self.tasks[idx].pipe.poll_io(&mut self.stream) { Ok(Async::Ready(ready)) => { // override keep-alive state if self.stream.keepalive() { @@ -256,9 +254,11 @@ where self.stream.reset(); if ready { - item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED); + self.tasks[idx] + .flags + .insert(EntryFlags::EOF | EntryFlags::FINISHED); } else { - item.flags.insert(EntryFlags::EOF); + self.tasks[idx].flags.insert(EntryFlags::EOF); } } // no more IO for this iteration @@ -273,7 +273,7 @@ where // it is not possible to recover from error // during pipe handling, so just drop connection error!("Unhandled error: {}", err); - item.flags.insert(EntryFlags::ERROR); + self.tasks[idx].flags.insert(EntryFlags::ERROR); // check stream state, we still can have valid data in buffer if let Ok(Async::NotReady) = self.stream.poll_completed(true) { @@ -282,13 +282,15 @@ where return Err(()); } } - } else if !item.flags.contains(EntryFlags::FINISHED) { - match item.pipe.poll_completed() { + } else if !self.tasks[idx].flags.contains(EntryFlags::FINISHED) { + match self.tasks[idx].pipe.poll_completed() { Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => item.flags.insert(EntryFlags::FINISHED), + Ok(Async::Ready(_)) => { + self.tasks[idx].flags.insert(EntryFlags::FINISHED) + } Err(err) => { self.notify_disconnect(); - item.flags.insert(EntryFlags::ERROR); + self.tasks[idx].flags.insert(EntryFlags::ERROR); error!("Unhandled error: {}", err); } } From 8058d15624c2a802a8b1f78b466e89ffae868ecd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 5 Jul 2018 13:16:16 +0600 Subject: [PATCH 0470/1635] clippy warnings --- src/client/connector.rs | 12 ++++++------ src/header/mod.rs | 12 ++++++------ src/server/h1decoder.rs | 4 +++- src/server/h1writer.rs | 5 ++++- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 3bcc57c7..1ff0efe5 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1007,22 +1007,22 @@ impl Protocol { } } - fn is_http(&self) -> bool { - match *self { + fn is_http(self) -> bool { + match self { Protocol::Https | Protocol::Http => true, _ => false, } } - fn is_secure(&self) -> bool { - match *self { + fn is_secure(self) -> bool { + match self { Protocol::Https | Protocol::Wss => true, _ => false, } } - fn port(&self) -> u16 { - match *self { + fn port(self) -> u16 { + match self { Protocol::Http | Protocol::Ws => 80, Protocol::Https | Protocol::Wss => 443, } diff --git a/src/header/mod.rs b/src/header/mod.rs index 847cb53b..291bc6ea 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -129,8 +129,8 @@ pub enum ContentEncoding { impl ContentEncoding { #[inline] /// Is the content compressed? - pub fn is_compression(&self) -> bool { - match *self { + pub fn is_compression(self) -> bool { + match self { ContentEncoding::Identity | ContentEncoding::Auto => false, _ => true, } @@ -138,8 +138,8 @@ impl ContentEncoding { #[inline] /// Convert content encoding to string - pub fn as_str(&self) -> &'static str { - match *self { + pub fn as_str(self) -> &'static str { + match self { #[cfg(feature = "brotli")] ContentEncoding::Br => "br", #[cfg(feature = "flate2")] @@ -152,8 +152,8 @@ impl ContentEncoding { #[inline] /// default quality value - pub fn quality(&self) -> f64 { - match *self { + pub fn quality(self) -> f64 { + match self { #[cfg(feature = "brotli")] ContentEncoding::Br => 1.1, #[cfg(feature = "flate2")] diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 977e89a8..d1948a0d 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -130,7 +130,6 @@ impl H1Decoder { if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) { - has_upgrade = has_upgrade || name == header::UPGRADE; // Unsafe: httparse check header value for valid utf-8 let value = unsafe { HeaderValue::from_shared_unchecked( @@ -176,6 +175,9 @@ impl H1Decoder { }; inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); } + header::UPGRADE => { + has_upgrade = true; + } _ => (), } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index d25f236d..d8ef4151 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -204,9 +204,12 @@ impl Writer for H1Writer { ResponseLength::None => (), _ => continue, }, + DATE => { + has_date = true; + } _ => (), } - has_date = has_date || key == DATE; + let v = value.as_ref(); let k = key.as_str().as_bytes(); let len = k.len() + v.len() + 4; From ac3a76cd3224f2b42fd1910e885e8b638f360728 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 5 Jul 2018 13:21:33 +0600 Subject: [PATCH 0471/1635] update httparse version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f9f148c2..91e13d4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ failure = "0.1.1" h2 = "0.1" htmlescape = "0.3" http = "^0.1.5" -httparse = "1.2" +httparse = "1.3" log = "0.4" mime = "0.3" mime_guess = "2.0.0-alpha" From 080f232a0f675e0f9eb13cba8f57133008c7f3b5 Mon Sep 17 00:00:00 2001 From: Tessa Bradbury Date: Thu, 5 Jul 2018 19:34:13 +1000 Subject: [PATCH 0472/1635] Use StaticFile default handler when file is inaccessible (#357) * Use Staticfile default handler on all error paths * Return an error from StaticFiles::new() if directory doesn't exist --- CHANGES.md | 4 ++ src/application.rs | 2 +- src/error.rs | 18 ++++++ src/fs.rs | 158 +++++++++++++++++++++++---------------------- 4 files changed, 103 insertions(+), 79 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fa18d898..b77ae686 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -49,6 +49,10 @@ * `HttpRequest::cookies()` returns `Ref>>` +* `StaticFiles::new()` returns `Result, Error>` instead of `StaticFiles` + +* `StaticFiles` uses the default handler if the file does not exist + ### Removed diff --git a/src/application.rs b/src/application.rs index aa9a74d1..de474dfc 100644 --- a/src/application.rs +++ b/src/application.rs @@ -587,7 +587,7 @@ where /// let app = App::new() /// .middleware(middleware::Logger::default()) /// .configure(config) // <- register resources - /// .handler("/static", fs::StaticFiles::new(".")); + /// .handler("/static", fs::StaticFiles::new(".").unwrap()); /// } /// ``` pub fn configure(self, cfg: F) -> App diff --git a/src/error.rs b/src/error.rs index 129de76b..c024561f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -626,6 +626,24 @@ impl From for UrlGenerationError { } } +/// Errors which can occur when serving static files. +#[derive(Fail, Debug, PartialEq)] +pub enum StaticFileError { + /// Path is not a directory + #[fail(display = "Path is not a directory. Unable to serve static files")] + IsNotDirectory, + /// Cannot render directory + #[fail(display = "Unable to render directory without index file")] + IsDirectory, +} + +/// Return `NotFound` for `StaticFileError` +impl ResponseError for StaticFileError { + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::NOT_FOUND) + } +} + /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/src/fs.rs b/src/fs.rs index b6ba4706..f42ef2e4 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -18,7 +18,7 @@ use mime; use mime_guess::{get_mime_type, guess_mime_type}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; -use error::Error; +use error::{Error, StaticFileError}; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use header; use http::{ContentEncoding, Method, StatusCode}; @@ -555,13 +555,12 @@ fn directory_listing( /// /// fn main() { /// let app = App::new() -/// .handler("/static", fs::StaticFiles::new(".")) +/// .handler("/static", fs::StaticFiles::new(".").unwrap()) /// .finish(); /// } /// ``` pub struct StaticFiles { directory: PathBuf, - accessible: bool, index: Option, show_index: bool, cpu_pool: CpuPool, @@ -577,7 +576,7 @@ impl StaticFiles { /// `StaticFile` uses `CpuPool` for blocking filesystem operations. /// By default pool with 20 threads is used. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. - pub fn new>(dir: T) -> StaticFiles { + pub fn new>(dir: T) -> Result, Error> { // use default CpuPool let pool = { DEFAULT_CPUPOOL.lock().clone() }; @@ -586,27 +585,15 @@ impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory and /// `CpuPool`. - pub fn with_pool>(dir: T, pool: CpuPool) -> StaticFiles { - let dir = dir.into(); + pub fn with_pool>(dir: T, pool: CpuPool) -> Result, Error> { + let dir = dir.into().canonicalize()?; - let (dir, access) = match dir.canonicalize() { - Ok(dir) => { - if dir.is_dir() { - (dir, true) - } else { - warn!("Is not directory `{:?}`", dir); - (dir, false) - } - } - Err(err) => { - warn!("Static files directory `{:?}` error: {}", dir, err); - (dir, false) - } - }; + if !dir.is_dir() { + return Err(StaticFileError::IsNotDirectory.into()) + } - StaticFiles { + Ok(StaticFiles { directory: dir, - accessible: access, index: None, show_index: false, cpu_pool: pool, @@ -616,7 +603,7 @@ impl StaticFiles { renderer: Box::new(directory_listing), _chunk_size: 0, _follow_symlinks: false, - } + }) } /// Show files listing for directories. @@ -652,56 +639,51 @@ impl StaticFiles { self.default = Box::new(WrapHandler::new(handler)); self } + + fn try_handle(&self, req: &HttpRequest) -> Result, Error> { + let tail: String = req.match_info().query("tail")?; + let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; + + // full filepath + let path = self.directory.join(&relpath).canonicalize()?; + + if path.is_dir() { + if let Some(ref redir_index) = self.index { + // TODO: Don't redirect, just return the index content. + // TODO: It'd be nice if there were a good usable URL manipulation + // library + let mut new_path: String = req.path().to_owned(); + if !new_path.ends_with('/') { + new_path.push('/'); + } + new_path.push_str(redir_index); + HttpResponse::Found() + .header(header::LOCATION, new_path.as_str()) + .finish() + .respond_to(&req) + } else if self.show_index { + let dir = Directory::new(self.directory.clone(), path); + Ok((*self.renderer)(&dir, &req)?.into()) + } else { + Err(StaticFileError::IsDirectory.into()) + } + } else { + NamedFile::open(path)? + .set_cpu_pool(self.cpu_pool.clone()) + .respond_to(&req)? + .respond_to(&req) + } + } } impl Handler for StaticFiles { type Result = Result, Error>; fn handle(&self, req: &HttpRequest) -> Self::Result { - if !self.accessible { + self.try_handle(req).or_else(|e| { + debug!("StaticFiles: Failed to handle {}: {}", req.path(), e); Ok(self.default.handle(req)) - } else { - let relpath = match req - .match_info() - .get("tail") - .map(|tail| PathBuf::from_param(tail.trim_left_matches('/'))) - { - Some(Ok(path)) => path, - _ => { - return Ok(self.default.handle(req)); - } - }; - - // full filepath - let path = self.directory.join(&relpath).canonicalize()?; - - if path.is_dir() { - if let Some(ref redir_index) = self.index { - // TODO: Don't redirect, just return the index content. - // TODO: It'd be nice if there were a good usable URL manipulation - // library - let mut new_path: String = req.path().to_owned(); - if !new_path.ends_with('/') { - new_path.push('/'); - } - new_path.push_str(redir_index); - HttpResponse::Found() - .header(header::LOCATION, new_path.as_str()) - .finish() - .respond_to(&req) - } else if self.show_index { - let dir = Directory::new(self.directory.clone(), path); - Ok((*self.renderer)(&dir, &req)?.into()) - } else { - Ok(self.default.handle(req)) - } - } else { - NamedFile::open(path)? - .set_cpu_pool(self.cpu_pool.clone()) - .respond_to(&req)? - .respond_to(&req) - } - } + }) } } @@ -806,6 +788,7 @@ mod tests { use super::*; use application::App; + use body::{Binary, Body}; use http::{header, Method, StatusCode}; use test::{self, TestRequest}; @@ -988,7 +971,7 @@ mod tests { #[test] fn test_named_file_ranges_status_code() { let mut srv = test::TestServer::with_factory(|| { - App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) + App::new().handler("test", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) }); // Valid range header @@ -1018,7 +1001,7 @@ mod tests { let mut srv = test::TestServer::with_factory(|| { App::new().handler( "test", - StaticFiles::new(".").index_file("tests/test.binary"), + StaticFiles::new(".").unwrap().index_file("tests/test.binary"), ) }); @@ -1066,7 +1049,7 @@ mod tests { let mut srv = test::TestServer::with_factory(|| { App::new().handler( "test", - StaticFiles::new(".").index_file("tests/test.binary"), + StaticFiles::new(".").unwrap().index_file("tests/test.binary"), ) }); @@ -1183,14 +1166,12 @@ mod tests { #[test] fn test_static_files() { - let mut st = StaticFiles::new(".").show_files_listing(); - st.accessible = false; - let req = TestRequest::default().finish(); + let mut st = StaticFiles::new(".").unwrap().show_files_listing(); + let req = TestRequest::with_uri("/missing").param("tail", "missing").finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - st.accessible = true; st.show_index = false; let req = TestRequest::default().finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); @@ -1210,9 +1191,30 @@ mod tests { assert!(format!("{:?}", resp.body()).contains("README.md")); } + #[test] + fn test_static_files_bad_directory() { + let st: Result, Error> = StaticFiles::new("missing"); + assert!(st.is_err()); + + let st: Result, Error> = StaticFiles::new("Cargo.toml"); + assert!(st.is_err()); + } + + #[test] + fn test_default_handler_file_missing() { + let st = StaticFiles::new(".").unwrap() + .default_handler(|_: &_| "default content"); + let req = TestRequest::with_uri("/missing").param("tail", "missing").finish(); + + let resp = st.handle(&req).respond_to(&req).unwrap(); + let resp = resp.as_msg(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body(), &Body::Binary(Binary::Slice(b"default content"))); + } + #[test] fn test_redirect_to_index() { - let st = StaticFiles::new(".").index_file("index.html"); + let st = StaticFiles::new(".").unwrap().index_file("index.html"); let req = TestRequest::default().uri("/tests").finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); @@ -1235,7 +1237,7 @@ mod tests { #[test] fn test_redirect_to_index_nested() { - let st = StaticFiles::new(".").index_file("mod.rs"); + let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); let req = TestRequest::default().uri("/src/client").finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); @@ -1251,7 +1253,7 @@ mod tests { let mut srv = test::TestServer::with_factory(|| { App::new() .prefix("public") - .handler("/", StaticFiles::new(".").index_file("Cargo.toml")) + .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) }); let request = srv.get().uri(srv.url("/public")).finish().unwrap(); @@ -1280,7 +1282,7 @@ mod tests { #[test] fn integration_redirect_to_index() { let mut srv = test::TestServer::with_factory(|| { - App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) + App::new().handler("test", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); @@ -1309,7 +1311,7 @@ mod tests { #[test] fn integration_percent_encoded() { let mut srv = test::TestServer::with_factory(|| { - App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) + App::new().handler("test", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) }); let request = srv From 67e4cad2819ab9c57632172d75b9c4262dad6c33 Mon Sep 17 00:00:00 2001 From: Douman Date: Thu, 5 Jul 2018 19:27:18 +0300 Subject: [PATCH 0473/1635] Introduce method to set header if it is missing only (#364) Also let default headers use it. Closes #320 --- src/client/request.rs | 42 +++++++++++++++++++++++++++++------------- tests/test_client.rs | 4 +++- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index 351b4b6d..ef058373 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -421,6 +421,29 @@ impl ClientRequestBuilder { self } + /// Set a header only if it is not yet set. + pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match parts.headers.contains_key(&key) { + false => match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + true => (), + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + /// Set content encoding. /// /// By default `ContentEncoding::Identity` is used. @@ -603,22 +626,15 @@ impl ClientRequestBuilder { }; if https { - self.header(header::ACCEPT_ENCODING, "br, gzip, deflate"); + self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); } else { - self.header(header::ACCEPT_ENCODING, "gzip, deflate"); + self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); } - let contains = if let Some(parts) = parts(&mut self.request, &self.err) { - parts.headers.contains_key(header::USER_AGENT) - } else { - true - }; - if !contains { - self.header( - header::USER_AGENT, - concat!("Actix-web/", env!("CARGO_PKG_VERSION")), - ); - } + self.set_header_if_none( + header::USER_AGENT, + concat!("Actix-web/", env!("CARGO_PKG_VERSION")), + ); } let mut request = self.request.take().expect("cannot reuse request builder"); diff --git a/tests/test_client.rs b/tests/test_client.rs index c4575c87..d128fa31 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -443,9 +443,11 @@ fn test_default_headers() { "\"" ))); - let request_override = srv.get().header("User-Agent", "test").finish().unwrap(); + let request_override = srv.get().header("User-Agent", "test").header("Accept-Encoding", "over_test").finish().unwrap(); let repr_override = format!("{:?}", request_override); assert!(repr_override.contains("\"user-agent\": \"test\"")); + assert!(repr_override.contains("\"accept-encoding\": \"over_test\"")); + assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\"")); assert!(!repr_override.contains(concat!( "\"user-agent\": \"Actix-web/", env!("CARGO_PKG_VERSION"), From 7d96b92aa363bd1d28cb2aec788943d0a6c5971a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Jul 2018 07:46:47 +0600 Subject: [PATCH 0474/1635] add check for usize cast --- src/ws/frame.rs | 18 +++++++--- src/ws/mask.rs | 94 +++++++++++++++++++++++-------------------------- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 871cc761..f71564e8 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -94,9 +94,12 @@ impl Frame { Async::Ready(None) => return Ok(Async::Ready(None)), Async::NotReady => return Ok(Async::NotReady), }; - let len = NetworkEndian::read_uint(&buf[idx..], 8) as usize; + let len = NetworkEndian::read_uint(&buf[idx..], 8); + if len > max_size as u64 { + return Err(ProtocolError::Overflow); + } idx += 8; - len + len as usize } else { len as usize }; @@ -165,9 +168,12 @@ impl Frame { if chunk_len < 10 { return Ok(Async::NotReady); } - let len = NetworkEndian::read_uint(&chunk[idx..], 8) as usize; + let len = NetworkEndian::read_uint(&chunk[idx..], 8); + if len > max_size as u64 { + return Err(ProtocolError::Overflow); + } idx += 8; - len + len as usize } else { len as usize }; @@ -255,6 +261,8 @@ impl Frame { // unmask if let Some(mask) = mask { + // Unsafe: request body stream is owned by WsStream. only one ref to + // bytes exists. Bytes object get freezed in continuous non-overlapping blocks let p: &mut [u8] = unsafe { let ptr: &[u8] = &data; &mut *(ptr as *const _ as *mut _) @@ -272,7 +280,7 @@ impl Frame { /// Parse the payload of a close frame. pub fn parse_close_payload(payload: &Binary) -> Option { if payload.len() >= 2 { - let raw_code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16; + let raw_code = NetworkEndian::read_u16(payload.as_ref()); let code = CloseCode::from(raw_code); let description = if payload.len() > 2 { Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into()) diff --git a/src/ws/mask.rs b/src/ws/mask.rs index d5d5ee92..16f0f6b1 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -4,75 +4,71 @@ use std::cmp::min; use std::mem::uninitialized; use std::ptr::copy_nonoverlapping; -/// Mask/unmask a frame. -#[inline] -pub fn apply_mask(buf: &mut [u8], mask: u32) { - unsafe { apply_mask_fast32(buf, mask) } -} - /// Faster version of `apply_mask()` which operates on 8-byte blocks. /// /// unsafe because uses pointer math and bit operations for performance #[inline] #[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] -unsafe fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) { - let mut ptr = buf.as_mut_ptr(); - let mut len = buf.len(); +pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { + unsafe { + let mut ptr = buf.as_mut_ptr(); + let mut len = buf.len(); - // Possible first unaligned block. - let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3); - let mask_u32 = if head > 0 { - let n = if head > 4 { head - 4 } else { head }; + // Possible first unaligned block. + let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3); + let mask_u32 = if head > 0 { + let n = if head > 4 { head - 4 } else { head }; - let mask_u32 = if n > 0 { - xor_mem(ptr, mask_u32, n); - ptr = ptr.offset(head as isize); - len -= n; - if cfg!(target_endian = "big") { - mask_u32.rotate_left(8 * n as u32) + let mask_u32 = if n > 0 { + xor_mem(ptr, mask_u32, n); + ptr = ptr.offset(head as isize); + len -= n; + if cfg!(target_endian = "big") { + mask_u32.rotate_left(8 * n as u32) + } else { + mask_u32.rotate_right(8 * n as u32) + } } else { - mask_u32.rotate_right(8 * n as u32) + mask_u32 + }; + + if head > 4 { + *(ptr as *mut u32) ^= mask_u32; + ptr = ptr.offset(4); + len -= 4; } + mask_u32 } else { mask_u32 }; - if head > 4 { + if len > 0 { + debug_assert_eq!(ptr as usize % 4, 0); + } + + // Properly aligned middle of the data. + if len >= 8 { + let mut mask_u64 = mask_u32 as u64; + mask_u64 = mask_u64 << 32 | mask_u32 as u64; + + while len >= 8 { + *(ptr as *mut u64) ^= mask_u64; + ptr = ptr.offset(8); + len -= 8; + } + } + + while len >= 4 { *(ptr as *mut u32) ^= mask_u32; ptr = ptr.offset(4); len -= 4; } - mask_u32 - } else { - mask_u32 - }; - if len > 0 { - debug_assert_eq!(ptr as usize % 4, 0); - } - - // Properly aligned middle of the data. - if len >= 8 { - let mut mask_u64 = mask_u32 as u64; - mask_u64 = mask_u64 << 32 | mask_u32 as u64; - - while len >= 8 { - *(ptr as *mut u64) ^= mask_u64; - ptr = ptr.offset(8); - len -= 8; + // Possible last block. + if len > 0 { + xor_mem(ptr, mask_u32, len); } } - - while len >= 4 { - *(ptr as *mut u32) ^= mask_u32; - ptr = ptr.offset(4); - len -= 4; - } - - // Possible last block. - if len > 0 { - xor_mem(ptr, mask_u32, len); - } } #[inline] From 9070d59ea87f97feb70e67a12e84b81192477538 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Jul 2018 08:11:36 +0600 Subject: [PATCH 0475/1635] do not read head payload --- src/client/pipeline.rs | 4 ++++ tests/test_server.rs | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index fbbce454..9828478c 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -17,6 +17,7 @@ use context::{ActorHttpContext, Frame}; use error::Error; use error::PayloadError; use header::ContentEncoding; +use http::Method; use httpmessage::HttpMessage; use server::input::PayloadStream; use server::WriterState; @@ -212,6 +213,9 @@ impl Future for SendRequest { match pl.parse() { Ok(Async::Ready(mut resp)) => { + if self.req.method() == &Method::HEAD { + pl.parser.take(); + } resp.set_pipeline(pl); return Ok(Async::Ready(resp)); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 4fb73a6a..4c50434c 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -368,8 +368,8 @@ fn test_head_empty() { } // read response - // let bytes = srv.execute(response.body()).unwrap(); - // assert!(bytes.is_empty()); + let bytes = srv.execute(response.body()).unwrap(); + assert!(bytes.is_empty()); } #[test] @@ -396,8 +396,8 @@ fn test_head_binary() { } // read response - //let bytes = srv.execute(response.body()).unwrap(); - //assert!(bytes.is_empty()); + let bytes = srv.execute(response.body()).unwrap(); + assert!(bytes.is_empty()); } #[test] From 185e710dc87d407d57004c199c9a7552dde38af8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Jul 2018 08:24:36 +0600 Subject: [PATCH 0476/1635] do not drop content-encoding header in case of identity #363 --- src/server/h1writer.rs | 5 ++++- tests/test_server.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index d8ef4151..724ee3c6 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -199,7 +199,10 @@ impl Writer for H1Writer { let mut buf = &mut *(buffer.bytes_mut() as *mut [u8]); for (key, value) in msg.headers() { match *key { - TRANSFER_ENCODING | CONTENT_ENCODING => continue, + TRANSFER_ENCODING => continue, + CONTENT_ENCODING => if encoding != ContentEncoding::Identity { + continue; + }, CONTENT_LENGTH => match info.length { ResponseLength::None => (), _ => continue, diff --git a/tests/test_server.rs b/tests/test_server.rs index 4c50434c..82a318e5 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -470,6 +470,39 @@ fn test_body_chunked_explicit() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[test] +fn test_body_identity() { + let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + let enc2 = enc.clone(); + + let mut srv = test::TestServer::new(move |app| { + let enc3 = enc2.clone(); + app.handler(move |_| { + HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .header(http::header::CONTENT_ENCODING, "deflate") + .body(enc3.clone()) + }) + }); + + // client request + let request = srv + .get() + .header("accept-encoding", "deflate") + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + + // decode deflate + assert_eq!(bytes, Bytes::from(STR)); +} + #[test] fn test_body_deflate() { let mut srv = test::TestServer::new(|app| { From a5f7a67b4d28028e3ecacadec1e082f6ff81a54c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Jul 2018 08:24:44 +0600 Subject: [PATCH 0477/1635] clippy warnings --- src/client/request.rs | 7 +++---- tests/test_ws.rs | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index ef058373..650f0eea 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -429,14 +429,13 @@ impl ClientRequestBuilder { { if let Some(parts) = parts(&mut self.request, &self.err) { match HeaderName::try_from(key) { - Ok(key) => match parts.headers.contains_key(&key) { - false => match value.try_into() { + Ok(key) => if !parts.headers.contains_key(&key) { + match value.try_into() { Ok(value) => { parts.headers.insert(key, value); } Err(e) => self.err = Some(e.into()), - }, - true => (), + } }, Err(e) => self.err = Some(e.into()), }; diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 96d97b82..66a9153d 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -9,7 +9,6 @@ use bytes::Bytes; use futures::Stream; use rand::distributions::Alphanumeric; use rand::Rng; -use std::time::Duration; #[cfg(feature = "alpn")] extern crate openssl; From cfa470db50f9a41173261262ca91b388bda31562 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Jul 2018 09:21:24 +0600 Subject: [PATCH 0478/1635] close conneciton for head requests --- src/client/pipeline.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 9828478c..2192d474 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -202,6 +202,7 @@ impl Future for SendRequest { should_decompress: self.req.response_decompress(), write_state: RunningState::Running, timeout: Some(timeout), + close: self.req.method() == &Method::HEAD, }); self.state = State::Send(pl); } @@ -247,6 +248,7 @@ pub struct Pipeline { should_decompress: bool, write_state: RunningState, timeout: Option, + close: bool, } enum IoBody { @@ -280,7 +282,11 @@ impl RunningState { impl Pipeline { fn release_conn(&mut self) { if let Some(conn) = self.conn.take() { - conn.release() + if self.close { + conn.close() + } else { + conn.release() + } } } From 1c3b32169eefff2eef4a79ade2be20d9af7a673f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Jul 2018 12:07:16 +0600 Subject: [PATCH 0479/1635] remove stream from WebsocketsContext::with_factory --- src/ws/context.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ws/context.rs b/src/ws/context.rs index 0444db99..ffdd0b55 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -110,11 +110,9 @@ where } /// Create a new Websocket context - pub fn with_factory(req: HttpRequest, stream: WsStream

    , f: F) -> Body + pub fn with_factory(req: HttpRequest, f: F) -> Body where F: FnOnce(&mut Self) -> A + 'static, - A: StreamHandler, - P: Stream + 'static, { let mb = Mailbox::default(); let mut ctx = WebsocketContext { @@ -123,7 +121,6 @@ where request: req, disconnected: false, }; - ctx.add_stream(stream); let act = f(&mut ctx); Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb))) From 5b7aed101a39232a11a62519af1422cb7954027f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Jul 2018 13:54:43 +0600 Subject: [PATCH 0480/1635] remove unsafe --- src/ws/frame.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ws/frame.rs b/src/ws/frame.rs index f71564e8..70065774 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -260,15 +260,14 @@ impl Frame { } // unmask - if let Some(mask) = mask { - // Unsafe: request body stream is owned by WsStream. only one ref to - // bytes exists. Bytes object get freezed in continuous non-overlapping blocks - let p: &mut [u8] = unsafe { - let ptr: &[u8] = &data; - &mut *(ptr as *const _ as *mut _) - }; - apply_mask(p, mask); - } + let data = if let Some(mask) = mask { + let mut buf = BytesMut::new(); + buf.extend_from_slice(&data); + apply_mask(&mut buf, mask); + buf.freeze() + } else { + data + }; Ok(Async::Ready(Some(Frame { finished, From 62ba01fc15ea933613134d342ceb3c1fa38c2ce5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 6 Jul 2018 15:00:14 +0600 Subject: [PATCH 0481/1635] update changes --- CHANGES.md | 2 +- Cargo.toml | 2 +- MIGRATION.md | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b77ae686..813459d6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.0] - 2018-xx-xx +## [0.7.0] - 2018-07-10 ### Added diff --git a/Cargo.toml b/Cargo.toml index 91e13d4b..c660e09b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.0-dev" +version = "0.7.0" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/MIGRATION.md b/MIGRATION.md index 3b61e98c..f04aa2d2 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,7 +1,7 @@ ## 0.7 * [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) - trait uses `&mut self` instead of `&self`. + trait uses `&HttpRequest` instead of `&mut HttpRequest`. * Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. @@ -19,6 +19,8 @@ * `Handler::handle()` uses `&self` instead of `&mut self` +* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value + * Removed deprecated `HttpServer::threads()`, use [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. From 85012f947a33f515c4a66e588b4882f022e521ed Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Fri, 6 Jul 2018 22:28:08 +0100 Subject: [PATCH 0482/1635] Remove reimplementation of `LazyCell` --- Cargo.toml | 1 + src/lib.rs | 1 + src/server/settings.rs | 42 +++++++++++++++++------------------------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c660e09b..4401e671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ time = "0.1" encoding = "0.2" language-tags = "0.2" lazy_static = "1.0" +lazycell = "1.0.0" parking_lot = "0.6" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.10", features=["percent-encode"] } diff --git a/src/lib.rs b/src/lib.rs index 218cabf9..a301227b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,6 +107,7 @@ extern crate htmlescape; extern crate http as modhttp; extern crate httparse; extern crate language_tags; +extern crate lazycell; extern crate mime; extern crate mime_guess; extern crate mio; diff --git a/src/server/settings.rs b/src/server/settings.rs index 0347241b..6ff9c298 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -2,13 +2,14 @@ use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; -use std::{env, fmt, mem, net}; +use std::{env, fmt, net}; use bytes::BytesMut; use futures_cpupool::CpuPool; use http::StatusCode; use parking_lot::Mutex; use time; +use lazycell::LazyCell; use super::channel::Node; use super::message::{Request, RequestPool}; @@ -41,7 +42,7 @@ pub struct ServerSettings { addr: Option, secure: bool, host: String, - cpu_pool: UnsafeCell>, + cpu_pool: LazyCell, responses: &'static HttpResponsePool, } @@ -51,7 +52,7 @@ impl Clone for ServerSettings { addr: self.addr, secure: self.secure, host: self.host.clone(), - cpu_pool: UnsafeCell::new(None), + cpu_pool: LazyCell::new(), responses: HttpResponsePool::get_pool(), } } @@ -64,7 +65,7 @@ impl Default for ServerSettings { secure: false, host: "localhost:8080".to_owned(), responses: HttpResponsePool::get_pool(), - cpu_pool: UnsafeCell::new(None), + cpu_pool: LazyCell::new(), } } } @@ -81,7 +82,7 @@ impl ServerSettings { } else { "localhost".to_owned() }; - let cpu_pool = UnsafeCell::new(None); + let cpu_pool = LazyCell::new(); let responses = HttpResponsePool::get_pool(); ServerSettings { addr, @@ -102,7 +103,7 @@ impl ServerSettings { addr, host, secure, - cpu_pool: UnsafeCell::new(None), + cpu_pool: LazyCell::new(), responses: HttpResponsePool::get_pool(), } } @@ -124,15 +125,7 @@ impl ServerSettings { /// Returns default `CpuPool` for server pub fn cpu_pool(&self) -> &CpuPool { - // Unsafe: ServerSetting is !Sync, DEFAULT_CPUPOOL is protected by Mutex - unsafe { - let val = &mut *self.cpu_pool.get(); - if val.is_none() { - let pool = DEFAULT_CPUPOOL.lock().clone(); - *val = Some(pool); - } - val.as_ref().unwrap() - } + self.cpu_pool.borrow_with(|| DEFAULT_CPUPOOL.lock().clone()) } #[inline] @@ -236,16 +229,15 @@ impl WorkerSettings { pub fn set_date(&self, dst: &mut BytesMut, full: bool) { // Unsafe: WorkerSetting is !Sync and !Send - unsafe { - if full { - let mut buf: [u8; 39] = mem::uninitialized(); - buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(&(*self.date.get()).bytes); - buf[35..].copy_from_slice(b"\r\n\r\n"); - dst.extend_from_slice(&buf); - } else { - dst.extend_from_slice(&(*self.date.get()).bytes); - } + let date_bytes = unsafe { &(*self.date.get()).bytes }; + if full { + let mut buf: [u8; 39] = [0; 39]; + buf[..6].copy_from_slice(b"date: "); + buf[6..35].copy_from_slice(date_bytes); + buf[35..].copy_from_slice(b"\r\n\r\n"); + dst.extend_from_slice(&buf); + } else { + dst.extend_from_slice(date_bytes); } } } From 110605f50bb08b86d789081554c8ce1fe2db2f62 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 8 Jul 2018 09:41:55 +0600 Subject: [PATCH 0483/1635] stop actor context on error #311 --- src/pipeline.rs | 16 +++++++++++++++- src/server/h1.rs | 16 +++++++--------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index 528680f5..66b2f29a 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -580,6 +580,7 @@ impl ProcessResponse { Frame::Chunk(Some(chunk)) => { match io.write(&chunk) { Err(err) => { + info.context = Some(ctx); info.error = Some(err.into()); return Ok( FinishingMiddlewares::init( @@ -606,6 +607,7 @@ impl ProcessResponse { break; } Err(err) => { + info.context = Some(ctx); info.error = Some(err); return Ok(FinishingMiddlewares::init( info, mws, self.resp, @@ -641,6 +643,12 @@ impl ProcessResponse { } Ok(Async::NotReady) => return Err(PipelineState::Response(self)), Err(err) => { + if let IOState::Actor(mut ctx) = + mem::replace(&mut self.iostate, IOState::Done) + { + ctx.disconnected(); + info.context = Some(ctx); + } info.error = Some(err.into()); return Ok(FinishingMiddlewares::init(info, mws, self.resp)); } @@ -755,7 +763,13 @@ impl Completed { if info.context.is_none() { PipelineState::None } else { - PipelineState::Completed(Completed(PhantomData, PhantomData)) + match info.poll_context() { + Ok(Async::NotReady) => { + PipelineState::Completed(Completed(PhantomData, PhantomData)) + } + Ok(Async::Ready(())) => PipelineState::None, + Err(_) => PipelineState::Error, + } } } diff --git a/src/server/h1.rs b/src/server/h1.rs index 6b1a5b9c..5b83dcc0 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -127,8 +127,8 @@ where fn notify_disconnect(&mut self) { // notify all tasks self.stream.disconnected(); - for entry in &mut self.tasks { - entry.pipe.disconnected() + for task in &mut self.tasks { + task.pipe.disconnected(); } } @@ -239,6 +239,7 @@ where if let Ok(Async::NotReady) = self.stream.poll_completed(true) { return Ok(Async::NotReady); } + self.flags.insert(Flags::ERROR); return Err(()); } @@ -272,14 +273,10 @@ where Err(err) => { // it is not possible to recover from error // during pipe handling, so just drop connection - error!("Unhandled error: {}", err); + self.notify_disconnect(); self.tasks[idx].flags.insert(EntryFlags::ERROR); - - // check stream state, we still can have valid data in buffer - if let Ok(Async::NotReady) = self.stream.poll_completed(true) { - return Ok(Async::NotReady); - } - return Err(()); + error!("Unhandled error1: {}", err); + continue; } } } else if !self.tasks[idx].flags.contains(EntryFlags::FINISHED) { @@ -292,6 +289,7 @@ where self.notify_disconnect(); self.tasks[idx].flags.insert(EntryFlags::ERROR); error!("Unhandled error: {}", err); + continue; } } } From 82920e1ac1e6cdd12915fee127665625b7900378 Mon Sep 17 00:00:00 2001 From: Douman Date: Sun, 8 Jul 2018 09:01:44 +0300 Subject: [PATCH 0484/1635] Do not override user settings on signals and stop handling (#375) --- src/server/srv.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/server/srv.rs b/src/server/srv.rs index 8582ab1f..0082f988 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -478,9 +478,6 @@ impl HttpServer { /// } /// ``` pub fn run(mut self) { - self.exit = true; - self.no_signals = false; - let sys = System::new("http-server"); self.start(); sys.run(); From 87824a9cf636c36bb4812416bc91015b0a192f6d Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Sun, 8 Jul 2018 13:46:13 +0100 Subject: [PATCH 0485/1635] Refactor `apply_mask` implementation, removing dead code paths and reducing scope of unsafety --- src/ws/mask.rs | 154 +++++++++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 68 deletions(-) diff --git a/src/ws/mask.rs b/src/ws/mask.rs index 16f0f6b1..2e142d65 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,89 +1,107 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) #![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] -use std::cmp::min; -use std::mem::uninitialized; +use std::slice; use std::ptr::copy_nonoverlapping; +// Holds a slice guaranteed to be shorter than 8 bytes +struct ShortSlice<'a>(&'a mut [u8]); + +impl<'a> ShortSlice<'a> { + unsafe fn new(slice: &'a mut [u8]) -> Self { + // Sanity check for debug builds + debug_assert!(slice.len() < 8); + ShortSlice(slice) + } + fn len(&self) -> usize { + self.0.len() + } +} + /// Faster version of `apply_mask()` which operates on 8-byte blocks. -/// -/// unsafe because uses pointer math and bit operations for performance #[inline] #[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { - unsafe { - let mut ptr = buf.as_mut_ptr(); - let mut len = buf.len(); + // Extend the mask to 64 bits + let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); + // Split the buffer into three segments + let (head, mid, tail) = align_buf(buf); - // Possible first unaligned block. - let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3); - let mask_u32 = if head > 0 { - let n = if head > 4 { head - 4 } else { head }; - - let mask_u32 = if n > 0 { - xor_mem(ptr, mask_u32, n); - ptr = ptr.offset(head as isize); - len -= n; - if cfg!(target_endian = "big") { - mask_u32.rotate_left(8 * n as u32) - } else { - mask_u32.rotate_right(8 * n as u32) - } - } else { - mask_u32 - }; - - if head > 4 { - *(ptr as *mut u32) ^= mask_u32; - ptr = ptr.offset(4); - len -= 4; - } - mask_u32 + // Initial unaligned segment + let head_len = head.len(); + if head_len > 0 { + xor_short(head, mask_u64); + if cfg!(target_endian = "big") { + mask_u64 = mask_u64.rotate_left(8 * head_len as u32); } else { - mask_u32 - }; - - if len > 0 { - debug_assert_eq!(ptr as usize % 4, 0); - } - - // Properly aligned middle of the data. - if len >= 8 { - let mut mask_u64 = mask_u32 as u64; - mask_u64 = mask_u64 << 32 | mask_u32 as u64; - - while len >= 8 { - *(ptr as *mut u64) ^= mask_u64; - ptr = ptr.offset(8); - len -= 8; - } - } - - while len >= 4 { - *(ptr as *mut u32) ^= mask_u32; - ptr = ptr.offset(4); - len -= 4; - } - - // Possible last block. - if len > 0 { - xor_mem(ptr, mask_u32, len); + mask_u64 = mask_u64.rotate_right(8 * head_len as u32); } } + // Aligned segment + for v in mid { + *v ^= mask_u64; + } + // Final unaligned segment + if tail.len() > 0 { + xor_short(tail, mask_u64); + } } #[inline] // TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so -// inefficient, it could be done better. The compiler does not see that len is -// limited to 3. -unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) { - let mut b: u32 = uninitialized(); - #[allow(trivial_casts)] - copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); - b ^= mask; - #[allow(trivial_casts)] - copy_nonoverlapping(&b as *const _ as *const u8, ptr, len); +// inefficient, it could be done better. The compiler does not understand that +// a `ShortSlice` must be smaller than a u64. +fn xor_short(buf: ShortSlice, mask: u64) { + // Unsafe: we know that a `ShortSlice` fits in a u64 + unsafe { + let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len()); + let mut b: u64 = 0; + #[allow(trivial_casts)] + copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); + b ^= mask; + #[allow(trivial_casts)] + copy_nonoverlapping(&b as *const _ as *const u8, ptr, len); + } } +#[inline] +// Unsafe: caller must ensure the buffer has the correct size and alignment +unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { + // Assert correct size and alignment in debug builds + debug_assert!(buf.len() & 0x7 == 0); + debug_assert!(buf.as_ptr() as usize & 0x7 == 0); + + slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) +} + +#[inline] +// Splits a slice into three parts: an unaligned short head and tail, plus an aligned +// u64 mid section. +fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { + let start_ptr = buf.as_ptr() as usize; + let end_ptr = start_ptr + buf.len(); + + // Round *up* to next aligned boundary for start + let start_aligned = (start_ptr+7) & !0x7; + // Round *down* to last aligned boundary for end + let end_aligned = end_ptr & !0x7; + + if end_aligned >= start_aligned { + // We have our three segments (head, mid, tail) + let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr); + let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr); + + // Unsafe: we know the middle section is correctly aligned, and the outer + // sections are smaller than 8 bytes + unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) } + } else { + // We didn't cross even one aligned boundary! + + // Unsafe: The outer sections are smaller than 8 bytes + unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) } + } +} + + #[cfg(test)] mod tests { use super::apply_mask; From bed961fe350c8c34c5d18a0ce87152088a3c1962 Mon Sep 17 00:00:00 2001 From: Douman Date: Wed, 11 Jul 2018 09:23:17 +0300 Subject: [PATCH 0486/1635] Lessen numbers of jobs for AppVeyor --- .appveyor.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 4bcd7732..7addc8c0 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,26 +3,13 @@ environment: PROJECT_NAME: actix matrix: # Stable channel - - TARGET: i686-pc-windows-gnu - CHANNEL: stable - TARGET: i686-pc-windows-msvc CHANNEL: stable - TARGET: x86_64-pc-windows-gnu CHANNEL: stable - TARGET: x86_64-pc-windows-msvc CHANNEL: stable - # Beta channel - - TARGET: i686-pc-windows-gnu - CHANNEL: beta - - TARGET: i686-pc-windows-msvc - CHANNEL: beta - - TARGET: x86_64-pc-windows-gnu - CHANNEL: beta - - TARGET: x86_64-pc-windows-msvc - CHANNEL: beta # Nightly channel - - TARGET: i686-pc-windows-gnu - CHANNEL: nightly - TARGET: i686-pc-windows-msvc CHANNEL: nightly - TARGET: x86_64-pc-windows-gnu From 9aef34e768574d9af4eb33880652749419eba446 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Jul 2018 12:56:35 +0600 Subject: [PATCH 0487/1635] remove & to &mut transmute #385 --- src/server/channel.rs | 29 ++++++++++++++--------------- src/server/settings.rs | 10 +++++----- src/server/srv.rs | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index 1439ddcb..b817b416 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -94,13 +94,13 @@ where self.node = Some(Node::new(el)); let _ = match self.proto { Some(HttpProtocol::H1(ref mut h1)) => { - self.node.as_ref().map(|n| h1.settings().head().insert(n)) + self.node.as_mut().map(|n| h1.settings().head().insert(n)) } Some(HttpProtocol::H2(ref mut h2)) => { - self.node.as_ref().map(|n| h2.settings().head().insert(n)) + self.node.as_mut().map(|n| h2.settings().head().insert(n)) } Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => { - self.node.as_ref().map(|n| settings.head().insert(n)) + self.node.as_mut().map(|n| settings.head().insert(n)) } None => unreachable!(), }; @@ -188,8 +188,8 @@ where } pub(crate) struct Node { - next: Option<*mut Node<()>>, - prev: Option<*mut Node<()>>, + next: Option<*mut Node>, + prev: Option<*mut Node>, element: *mut T, } @@ -202,19 +202,18 @@ impl Node { } } - fn insert(&self, next: &Node) { + fn insert(&mut self, next: &mut Node) { unsafe { - if let Some(ref next2) = self.next { - let n: &mut Node<()> = - &mut *(next2.as_ref().unwrap() as *const _ as *mut _); - n.prev = Some(next as *const _ as *mut _); + let next: *mut Node = next as *const _ as *mut _; + + if let Some(ref mut next2) = self.next { + let n = next2.as_mut().unwrap(); + n.prev = Some(next); } - let slf: &mut Node = &mut *(self as *const _ as *mut _); + self.next = Some(next); - slf.next = Some(next as *const _ as *mut _); - - let next: &mut Node = &mut *(next as *const _ as *mut _); - next.prev = Some(slf as *const _ as *mut _); + let next: &mut Node = &mut *next; + next.prev = Some(self as *mut _); } } diff --git a/src/server/settings.rs b/src/server/settings.rs index 6ff9c298..cc2e1c06 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -7,9 +7,9 @@ use std::{env, fmt, net}; use bytes::BytesMut; use futures_cpupool::CpuPool; use http::StatusCode; +use lazycell::LazyCell; use parking_lot::Mutex; use time; -use lazycell::LazyCell; use super::channel::Node; use super::message::{Request, RequestPool}; @@ -151,7 +151,7 @@ pub(crate) struct WorkerSettings { bytes: Rc, messages: &'static RequestPool, channels: Cell, - node: Box>, + node: RefCell>, date: UnsafeCell, } @@ -170,7 +170,7 @@ impl WorkerSettings { bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), channels: Cell::new(0), - node: Box::new(Node::head()), + node: RefCell::new(Node::head()), date: UnsafeCell::new(Date::new()), keep_alive, ka_enabled, @@ -181,8 +181,8 @@ impl WorkerSettings { self.channels.get() } - pub fn head(&self) -> &Node<()> { - &self.node + pub fn head(&self) -> RefMut> { + self.node.borrow_mut() } pub fn handlers(&self) -> RefMut> { diff --git a/src/server/srv.rs b/src/server/srv.rs index 0082f988..02580d01 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -477,7 +477,7 @@ impl HttpServer { /// .run(); /// } /// ``` - pub fn run(mut self) { + pub fn run(self) { let sys = System::new("http-server"); self.start(); sys.run(); From 28b36c650a9558d6afee231703b5f869b8ddaec0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Jul 2018 13:25:07 +0600 Subject: [PATCH 0488/1635] fix h2 compatibility --- src/server/h2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/h2.rs b/src/server/h2.rs index d812f6f5..2322f755 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -62,7 +62,7 @@ where flags: Flags::empty(), tasks: VecDeque::new(), state: State::Handshake(server::handshake(IoWrapper { - unread: Some(buf), + unread: if buf.is_empty() { None } else { Some(buf) }, inner: io, })), keepalive_timer: None, From f38a370b945972c14ed72bf23b46d7826680d11b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Jul 2018 13:34:40 +0600 Subject: [PATCH 0489/1635] update changes --- CHANGES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 813459d6..5fa6f12b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -61,6 +61,15 @@ * Remove `HttpMessage::range()` +## [0.6.15] - 2018-07-11 + +### Fixed + +* Fix h2 compatibility #352 + +* Fix duplicate tail of StaticFiles with index_file. #344 + + ## [0.6.14] - 2018-06-21 ### Added From d9988f3ab68ad074b7367354419bb2ac582f20c0 Mon Sep 17 00:00:00 2001 From: kingoflolz Date: Wed, 11 Jul 2018 21:21:32 +1000 Subject: [PATCH 0490/1635] fix missing content length fix missing content length when no compression is used --- src/client/writer.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/writer.rs b/src/client/writer.rs index 173b47e1..b691407d 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -266,6 +266,10 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { } #[cfg(not(any(feature = "flate2", feature = "brotli")))] { + let mut b = BytesMut::new(); + let _ = write!(b, "{}", bytes.len()); + req.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); TransferEncoding::eof(buf) } } From 86e44de7874b40c32ab7d79358d059c34367ea01 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Jul 2018 10:29:37 +0600 Subject: [PATCH 0491/1635] pin failure crate --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4401e671..59f101e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,6 @@ actix = "0.7.0" base64 = "0.9" bitflags = "1.0" -failure = "0.1.1" h2 = "0.1" htmlescape = "0.3" http = "^0.1.5" @@ -82,6 +81,8 @@ cookie = { version="0.10", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } flate2 = { version="1.0", optional = true, default-features = false } +failure = "=0.1.1" + # io mio = "^0.6.13" net2 = "0.2" From 8e462c5944320e804db041b8a11ae264ef141a09 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Jul 2018 10:35:09 +0600 Subject: [PATCH 0492/1635] use write instead format --- src/server/h1writer.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 724ee3c6..e8f172f4 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -1,9 +1,10 @@ -#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] +// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] + +use std::io::{self, Write}; +use std::rc::Rc; use bytes::{BufMut, BytesMut}; use futures::{Async, Poll}; -use std::io; -use std::rc::Rc; use tokio_io::AsyncWrite; use super::helpers; @@ -178,9 +179,8 @@ impl Writer for H1Writer { helpers::write_content_length(len, &mut buffer) } ResponseLength::Length64(len) => { - let s = format!("{}", len); buffer.extend_from_slice(b"\r\ncontent-length: "); - buffer.extend_from_slice(s.as_ref()); + write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } ResponseLength::None => buffer.extend_from_slice(b"\r\n"), From db005af1af8f35f405883ac9ee3913731a11b51b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Jul 2018 10:41:49 +0600 Subject: [PATCH 0493/1635] clippy warnings --- src/ws/mask.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ws/mask.rs b/src/ws/mask.rs index 2e142d65..e9bfb3d5 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,7 +1,7 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) #![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] -use std::slice; use std::ptr::copy_nonoverlapping; +use std::slice; // Holds a slice guaranteed to be shorter than 8 bytes struct ShortSlice<'a>(&'a mut [u8]); @@ -50,6 +50,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so // inefficient, it could be done better. The compiler does not understand that // a `ShortSlice` must be smaller than a u64. +#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] fn xor_short(buf: ShortSlice, mask: u64) { // Unsafe: we know that a `ShortSlice` fits in a u64 unsafe { @@ -67,8 +68,8 @@ fn xor_short(buf: ShortSlice, mask: u64) { // Unsafe: caller must ensure the buffer has the correct size and alignment unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { // Assert correct size and alignment in debug builds - debug_assert!(buf.len() & 0x7 == 0); - debug_assert!(buf.as_ptr() as usize & 0x7 == 0); + debug_assert!(buf.len().trailing_zeros() >= 3); + debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3); slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) } @@ -81,10 +82,10 @@ fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { let end_ptr = start_ptr + buf.len(); // Round *up* to next aligned boundary for start - let start_aligned = (start_ptr+7) & !0x7; + let start_aligned = (start_ptr + 7) & !0x7; // Round *down* to last aligned boundary for end let end_aligned = end_ptr & !0x7; - + if end_aligned >= start_aligned { // We have our three segments (head, mid, tail) let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr); @@ -101,7 +102,6 @@ fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { } } - #[cfg(test)] mod tests { use super::apply_mask; From b8b90d9ec9ab449ad2f967604ec8b8a27e646bff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Jul 2018 15:30:01 +0600 Subject: [PATCH 0494/1635] rename ResourceHandler to Resource --- src/application.rs | 38 +++++++------- src/extractor.rs | 16 +++--- src/handler.rs | 4 +- src/httprequest.rs | 35 +++++++------ src/lib.rs | 4 +- src/middleware/cors.rs | 14 +++--- src/resource.rs | 12 ++--- src/router.rs | 110 ++++++++++++++++++++--------------------- src/scope.rs | 32 ++++++------ src/test.rs | 4 +- 10 files changed, 137 insertions(+), 132 deletions(-) diff --git a/src/application.rs b/src/application.rs index de474dfc..96c4ad11 100644 --- a/src/application.rs +++ b/src/application.rs @@ -9,8 +9,8 @@ use httpresponse::HttpResponse; use middleware::Middleware; use pipeline::{HandlerType, Pipeline, PipelineHandler}; use pred::Predicate; -use resource::ResourceHandler; -use router::{Resource, RouteInfo, Router}; +use resource::Resource; +use router::{ResourceDef, RouteInfo, Router}; use scope::Scope; use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; @@ -28,15 +28,15 @@ pub struct HttpApplication { #[doc(hidden)] pub struct Inner { prefix: usize, - default: Rc>, + default: Rc>, encoding: ContentEncoding, - resources: Vec>, + resources: Vec>, handlers: Vec>, } enum PrefixHandlerType { Handler(String, Box>), - Scope(Resource, Box>, Vec>>), + Scope(ResourceDef, Box>, Vec>>), } impl PipelineHandler for Inner { @@ -154,10 +154,10 @@ impl HttpHandler for HttpApplication { struct ApplicationParts { state: S, prefix: String, - default: Rc>, - resources: Vec<(Resource, Option>)>, + default: Rc>, + resources: Vec<(ResourceDef, Option>)>, handlers: Vec>, - external: HashMap, + external: HashMap, encoding: ContentEncoding, middlewares: Vec>>, filters: Vec>>, @@ -204,7 +204,7 @@ where parts: Some(ApplicationParts { state, prefix: "/".to_owned(), - default: Rc::new(ResourceHandler::default_not_found()), + default: Rc::new(Resource::default_not_found()), resources: Vec::new(), handlers: Vec::new(), external: HashMap::new(), @@ -332,9 +332,9 @@ where } } } else { - let mut handler = ResourceHandler::default(); + let mut handler = Resource::default(); handler.method(method).with(f); - let pattern = Resource::new(handler.get_name(), path); + let pattern = ResourceDef::new(handler.get_name(), path); break Some((pattern, Some(handler))); } } @@ -382,7 +382,7 @@ where let filters = scope.take_filters(); parts.handlers.push(PrefixHandlerType::Scope( - Resource::prefix("", &path), + ResourceDef::prefix("", &path), scope, filters, )); @@ -423,16 +423,16 @@ where /// ``` pub fn resource(mut self, path: &str, f: F) -> App where - F: FnOnce(&mut ResourceHandler) -> R + 'static, + F: FnOnce(&mut Resource) -> R + 'static, { { let parts = self.parts.as_mut().expect("Use after finish"); // add resource handler - let mut handler = ResourceHandler::default(); + let mut handler = Resource::default(); f(&mut handler); - let pattern = Resource::new(handler.get_name(), path); + let pattern = ResourceDef::new(handler.get_name(), path); parts.resources.push((pattern, Some(handler))); } self @@ -440,8 +440,8 @@ where /// Configure resource for a specific path. #[doc(hidden)] - pub fn register_resource(&mut self, path: &str, resource: ResourceHandler) { - let pattern = Resource::new(resource.get_name(), path); + pub fn register_resource(&mut self, path: &str, resource: Resource) { + let pattern = ResourceDef::new(resource.get_name(), path); self.parts .as_mut() .expect("Use after finish") @@ -452,7 +452,7 @@ where /// Default resource to be used if no matching route could be found. pub fn default_resource(mut self, f: F) -> App where - F: FnOnce(&mut ResourceHandler) -> R + 'static, + F: FnOnce(&mut Resource) -> R + 'static, { { let parts = self.parts.as_mut().expect("Use after finish"); @@ -508,7 +508,7 @@ where } parts.external.insert( String::from(name.as_ref()), - Resource::external(name.as_ref(), url.as_ref()), + ResourceDef::external(name.as_ref(), url.as_ref()), ); } self diff --git a/src/extractor.rs b/src/extractor.rs index 8e0a9659..683e1526 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -613,8 +613,8 @@ mod tests { use futures::{Async, Future}; use http::header; use mime; - use resource::ResourceHandler; - use router::{Resource, Router}; + use resource::Resource; + use router::{ResourceDef, Router}; use test::TestRequest; #[derive(Deserialize, Debug, PartialEq)] @@ -710,10 +710,10 @@ mod tests { fn test_request_extract() { let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let mut resource = ResourceHandler::<()>::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); + routes.push((ResourceDef::new("index", "/{key}/{value}/"), Some(resource))); let (router, _) = Router::new("", routes); let info = router.recognize(&req).unwrap().1; let req = req.with_route_info(info); @@ -748,10 +748,10 @@ mod tests { #[test] fn test_extract_path_single() { - let mut resource = ResourceHandler::<()>::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push((Resource::new("index", "/{value}/"), Some(resource))); + routes.push((ResourceDef::new("index", "/{value}/"), Some(resource))); let (router, _) = Router::new("", routes); let req = TestRequest::with_uri("/32/").finish_with_router(router.clone()); @@ -762,10 +762,10 @@ mod tests { #[test] fn test_tuple_extract() { - let mut resource = ResourceHandler::<()>::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); + routes.push((ResourceDef::new("index", "/{key}/{value}/"), Some(resource))); let (router, _) = Router::new("", routes); let req = TestRequest::with_uri("/name/user1/?id=test") diff --git a/src/handler.rs b/src/handler.rs index 690d7166..241f4e6a 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -9,7 +9,7 @@ use error::Error; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use resource::ResourceHandler; +use resource::Resource; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] @@ -409,7 +409,7 @@ pub(crate) trait RouteHandler: 'static { false } - fn default_resource(&mut self, _: Rc>) { + fn default_resource(&mut self, _: Rc>) { unimplemented!() } } diff --git a/src/httprequest.rs b/src/httprequest.rs index 08de0a8f..650d3a39 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -20,7 +20,7 @@ use httpresponse::{HttpResponse, HttpResponseBuilder}; use info::ConnectionInfo; use param::Params; use payload::Payload; -use router::{Resource, RouteInfo}; +use router::{ResourceDef, RouteInfo}; use server::Request; struct Query(HashMap); @@ -211,7 +211,7 @@ impl HttpRequest { /// This method returns reference to matched `Resource` object. #[inline] - pub fn resource(&self) -> Option<&Resource> { + pub fn resource(&self) -> Option<&ResourceDef> { self.route.resource() } @@ -370,8 +370,8 @@ impl fmt::Debug for HttpRequest { #[cfg(test)] mod tests { use super::*; - use resource::ResourceHandler; - use router::{Resource, Router}; + use resource::Resource; + use router::{ResourceDef, Router}; use test::TestRequest; #[test] @@ -422,10 +422,10 @@ mod tests { #[test] fn test_request_match_info() { - let mut resource = ResourceHandler::<()>::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); let mut routes = Vec::new(); - routes.push((Resource::new("index", "/{key}/"), Some(resource))); + routes.push((ResourceDef::new("index", "/{key}/"), Some(resource))); let (router, _) = Router::new("", routes); let req = TestRequest::with_uri("/value/?id=test").finish(); @@ -435,10 +435,12 @@ mod tests { #[test] fn test_url_for() { - let mut resource = ResourceHandler::<()>::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); - let routes = - vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))]; + let routes = vec![( + ResourceDef::new("index", "/user/{name}.{ext}"), + Some(resource), + )]; let (router, _) = Router::new("/", routes); let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); @@ -464,9 +466,12 @@ mod tests { #[test] fn test_url_for_with_prefix() { - let mut resource = ResourceHandler::<()>::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); - let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))]; + let routes = vec![( + ResourceDef::new("index", "/user/{name}.html"), + Some(resource), + )]; let (router, _) = Router::new("/prefix/", routes); let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); @@ -483,9 +488,9 @@ mod tests { #[test] fn test_url_for_static() { - let mut resource = ResourceHandler::<()>::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); - let routes = vec![(Resource::new("index", "/index.html"), Some(resource))]; + let routes = vec![(ResourceDef::new("index", "/index.html"), Some(resource))]; let (router, _) = Router::new("/prefix/", routes); let info = router.default_route_info(); assert!(info.has_route("/index.html")); @@ -503,10 +508,10 @@ mod tests { #[test] fn test_url_for_external() { - let mut resource = ResourceHandler::<()>::default(); + let mut resource = Resource::<()>::default(); resource.name("index"); let routes = vec![( - Resource::external("youtube", "https://youtube.com/watch/{video_id}"), + ResourceDef::external("youtube", "https://youtube.com/watch/{video_id}"), None, )]; let router = Router::new::<()>("", routes).0; diff --git a/src/lib.rs b/src/lib.rs index a301227b..a1a09982 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -244,9 +244,9 @@ pub mod dev { pub use info::ConnectionInfo; pub use json::{JsonBody, JsonConfig}; pub use param::{FromParam, Params}; - pub use resource::ResourceHandler; + pub use resource::Resource; pub use route::Route; - pub use router::{Resource, ResourceType, Router}; + pub use router::{ResourceDef, ResourceType, Router}; } pub mod http { diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 09ca8120..3f0f7ef5 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -11,7 +11,7 @@ //! constructed backend. //! //! Cors middleware could be used as parameter for `App::middleware()` or -//! `ResourceHandler::middleware()` methods. But you have to use +//! `Resource::middleware()` methods. But you have to use //! `Cors::for_app()` method to support *preflight* OPTIONS request. //! //! @@ -59,7 +59,7 @@ use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; -use resource::ResourceHandler; +use resource::Resource; use server::Request; /// A set of errors that can occur during processing CORS @@ -277,9 +277,9 @@ impl Cors { /// adds route for *OPTIONS* preflight requests. /// /// It is possible to register *Cors* middleware with - /// `ResourceHandler::middleware()` method, but in that case *Cors* + /// `Resource::middleware()` method, but in that case *Cors* /// middleware wont be able to handle *OPTIONS* requests. - pub fn register(self, resource: &mut ResourceHandler) { + pub fn register(self, resource: &mut Resource) { resource .method(Method::OPTIONS) .h(|_: &_| HttpResponse::Ok()); @@ -515,7 +515,7 @@ pub struct CorsBuilder { methods: bool, error: Option, expose_hdrs: HashSet, - resources: Vec<(String, ResourceHandler)>, + resources: Vec<(String, Resource)>, app: Option>, } @@ -795,10 +795,10 @@ impl CorsBuilder { /// ``` pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder where - F: FnOnce(&mut ResourceHandler) -> R + 'static, + F: FnOnce(&mut Resource) -> R + 'static, { // add resource handler - let mut handler = ResourceHandler::default(); + let mut handler = Resource::default(); f(&mut handler); self.resources.push((path.to_owned(), handler)); diff --git a/src/resource.rs b/src/resource.rs index cbf3d985..2af1029a 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -36,16 +36,16 @@ pub(crate) struct RouteId(usize); /// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok())) /// .finish(); /// } -pub struct ResourceHandler { +pub struct Resource { name: String, state: PhantomData, routes: SmallVec<[Route; 3]>, middlewares: Rc>>>, } -impl Default for ResourceHandler { +impl Default for Resource { fn default() -> Self { - ResourceHandler { + Resource { name: String::new(), state: PhantomData, routes: SmallVec::new(), @@ -54,9 +54,9 @@ impl Default for ResourceHandler { } } -impl ResourceHandler { +impl Resource { pub(crate) fn default_not_found() -> Self { - ResourceHandler { + Resource { name: String::new(), state: PhantomData, routes: SmallVec::new(), @@ -74,7 +74,7 @@ impl ResourceHandler { } } -impl ResourceHandler { +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 index 0edf5184..fad51b15 100644 --- a/src/router.rs +++ b/src/router.rs @@ -7,7 +7,7 @@ use url::Url; use error::UrlGenerationError; use param::{ParamItem, Params}; -use resource::ResourceHandler; +use resource::Resource; use server::Request; #[derive(Debug, Copy, Clone, PartialEq)] @@ -29,7 +29,7 @@ pub struct RouteInfo { impl RouteInfo { /// This method returns reference to matched `Resource` object. #[inline] - pub fn resource(&self) -> Option<&Resource> { + pub fn resource(&self) -> Option<&ResourceDef> { if let RouterResource::Normal(idx) = self.resource { Some(&self.router.patterns[idx as usize]) } else { @@ -113,15 +113,15 @@ impl RouteInfo { struct Inner { prefix: String, prefix_len: usize, - named: HashMap, - patterns: Vec, + named: HashMap, + patterns: Vec, } impl Router { /// Create new router pub fn new( - prefix: &str, map: Vec<(Resource, Option>)>, - ) -> (Router, Vec>) { + prefix: &str, map: Vec<(ResourceDef, Option>)>, + ) -> (Router, Vec>) { let prefix = prefix.trim().trim_right_matches('/').to_owned(); let mut named = HashMap::new(); let mut patterns = Vec::new(); @@ -240,7 +240,7 @@ pub enum ResourceType { /// Resource type describes an entry in resources table #[derive(Clone, Debug)] -pub struct Resource { +pub struct ResourceDef { tp: PatternType, rtp: ResourceType, name: String, @@ -248,12 +248,12 @@ pub struct Resource { elements: Vec, } -impl Resource { +impl ResourceDef { /// Parse path pattern and create new `Resource` instance. /// /// Panics if path pattern is wrong. pub fn new(name: &str, path: &str) -> Self { - Resource::with_prefix(name, path, "/", false) + ResourceDef::with_prefix(name, path, "/", false) } /// Parse path pattern and create new `Resource` instance. @@ -262,14 +262,14 @@ impl Resource { /// /// Panics if path regex pattern is wrong. pub fn prefix(name: &str, path: &str) -> Self { - Resource::with_prefix(name, path, "/", true) + ResourceDef::with_prefix(name, path, "/", true) } /// Construct external resource /// /// Panics if path pattern is wrong. pub fn external(name: &str, path: &str) -> Self { - let mut resource = Resource::with_prefix(name, path, "/", false); + let mut resource = ResourceDef::with_prefix(name, path, "/", false); resource.rtp = ResourceType::External; resource } @@ -277,7 +277,7 @@ impl Resource { /// Parse path pattern and create new `Resource` instance with custom prefix pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self { let (pattern, elements, is_dynamic, len) = - Resource::parse(path, prefix, for_prefix); + ResourceDef::parse(path, prefix, for_prefix); let tp = if is_dynamic { let re = match Regex::new(&pattern) { @@ -296,7 +296,7 @@ impl Resource { PatternType::Static(pattern.clone()) }; - Resource { + ResourceDef { tp, elements, name: name.into(), @@ -571,15 +571,15 @@ impl Resource { } } -impl PartialEq for Resource { - fn eq(&self, other: &Resource) -> bool { +impl PartialEq for ResourceDef { + fn eq(&self, other: &ResourceDef) -> bool { self.pattern == other.pattern } } -impl Eq for Resource {} +impl Eq for ResourceDef {} -impl Hash for Resource { +impl Hash for ResourceDef { fn hash(&self, state: &mut H) { self.pattern.hash(state); } @@ -593,34 +593,34 @@ mod tests { #[test] fn test_recognizer10() { let routes = vec![ - (Resource::new("", "/name"), Some(ResourceHandler::default())), + (ResourceDef::new("", "/name"), Some(Resource::default())), ( - Resource::new("", "/name/{val}"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/name/{val}"), + Some(Resource::default()), ), ( - Resource::new("", "/name/{val}/index.html"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/name/{val}/index.html"), + Some(Resource::default()), ), ( - Resource::new("", "/file/{file}.{ext}"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/file/{file}.{ext}"), + Some(Resource::default()), ), ( - Resource::new("", "/v{val}/{val2}/index.html"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/v{val}/{val2}/index.html"), + Some(Resource::default()), ), ( - Resource::new("", "/v/{tail:.*}"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/v/{tail:.*}"), + Some(Resource::default()), ), ( - Resource::new("", "/test2/{test}.html"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/test2/{test}.html"), + Some(Resource::default()), ), ( - Resource::new("", "{test}/index.html"), - Some(ResourceHandler::default()), + ResourceDef::new("", "{test}/index.html"), + Some(Resource::default()), ), ]; let (rec, _) = Router::new::<()>("", routes); @@ -681,12 +681,12 @@ mod tests { fn test_recognizer_2() { let routes = vec![ ( - Resource::new("", "/index.json"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/index.json"), + Some(Resource::default()), ), ( - Resource::new("", "/{source}.json"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/{source}.json"), + Some(Resource::default()), ), ]; let (rec, _) = Router::new::<()>("", routes); @@ -701,10 +701,10 @@ mod tests { #[test] fn test_recognizer_with_prefix() { let routes = vec![ - (Resource::new("", "/name"), Some(ResourceHandler::default())), + (ResourceDef::new("", "/name"), Some(Resource::default())), ( - Resource::new("", "/name/{val}"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/name/{val}"), + Some(Resource::default()), ), ]; let (rec, _) = Router::new::<()>("/test", routes); @@ -724,10 +724,10 @@ mod tests { // same patterns let routes = vec![ - (Resource::new("", "/name"), Some(ResourceHandler::default())), + (ResourceDef::new("", "/name"), Some(Resource::default())), ( - Resource::new("", "/name/{val}"), - Some(ResourceHandler::default()), + ResourceDef::new("", "/name/{val}"), + Some(Resource::default()), ), ]; let (rec, _) = Router::new::<()>("/test2", routes); @@ -747,29 +747,29 @@ mod tests { #[test] fn test_parse_static() { - let re = Resource::new("test", "/"); + let re = ResourceDef::new("test", "/"); assert!(re.is_match("/")); assert!(!re.is_match("/a")); - let re = Resource::new("test", "/name"); + let re = ResourceDef::new("test", "/name"); assert!(re.is_match("/name")); assert!(!re.is_match("/name1")); assert!(!re.is_match("/name/")); assert!(!re.is_match("/name~")); - let re = Resource::new("test", "/name/"); + let re = ResourceDef::new("test", "/name/"); assert!(re.is_match("/name/")); assert!(!re.is_match("/name")); assert!(!re.is_match("/name/gs")); - let re = Resource::new("test", "/user/profile"); + let re = ResourceDef::new("test", "/user/profile"); assert!(re.is_match("/user/profile")); assert!(!re.is_match("/user/profile/profile")); } #[test] fn test_parse_param() { - let re = Resource::new("test", "/user/{id}"); + let re = ResourceDef::new("test", "/user/{id}"); assert!(re.is_match("/user/profile")); assert!(re.is_match("/user/2345")); assert!(!re.is_match("/user/2345/")); @@ -783,7 +783,7 @@ mod tests { let info = re.match_with_params(&req, 0, true).unwrap(); assert_eq!(info.get("id").unwrap(), "1245125"); - let re = Resource::new("test", "/v{version}/resource/{id}"); + let re = ResourceDef::new("test", "/v{version}/resource/{id}"); assert!(re.is_match("/v1/resource/320120")); assert!(!re.is_match("/v/resource/1")); assert!(!re.is_match("/resource")); @@ -796,14 +796,14 @@ mod tests { #[test] fn test_resource_prefix() { - let re = Resource::prefix("test", "/name"); + let re = ResourceDef::prefix("test", "/name"); assert!(re.is_match("/name")); assert!(re.is_match("/name/")); assert!(re.is_match("/name/test/test")); assert!(re.is_match("/name1")); assert!(re.is_match("/name~")); - let re = Resource::prefix("test", "/name/"); + let re = ResourceDef::prefix("test", "/name/"); assert!(re.is_match("/name/")); assert!(re.is_match("/name/gs")); assert!(!re.is_match("/name")); @@ -811,7 +811,7 @@ mod tests { #[test] fn test_reousrce_prefix_dynamic() { - let re = Resource::prefix("test", "/{name}/"); + let re = ResourceDef::prefix("test", "/{name}/"); assert!(re.is_match("/name/")); assert!(re.is_match("/name/gs")); assert!(!re.is_match("/name")); @@ -831,12 +831,12 @@ mod tests { fn test_request_resource() { let routes = vec![ ( - Resource::new("r1", "/index.json"), - Some(ResourceHandler::default()), + ResourceDef::new("r1", "/index.json"), + Some(Resource::default()), ), ( - Resource::new("r2", "/test.json"), - Some(ResourceHandler::default()), + ResourceDef::new("r2", "/test.json"), + Some(Resource::default()), ), ]; let (router, _) = Router::new::<()>("", routes); diff --git a/src/scope.rs b/src/scope.rs index ffa58157..a4b4307c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -14,14 +14,14 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; -use resource::{ResourceHandler, RouteId}; -use router::Resource; +use resource::{Resource, RouteId}; +use router::ResourceDef; use server::Request; -type ScopeResource = Rc>; +type ScopeResource = Rc>; type Route = Box>; -type ScopeResources = Rc)>>; -type NestedInfo = (Resource, Route, Vec>>); +type ScopeResources = Rc)>>; +type NestedInfo = (ResourceDef, Route, Vec>>); /// Resources scope /// @@ -147,7 +147,7 @@ impl Scope { })]; let handler = Box::new(Wrapper { scope, state }); self.nested - .push((Resource::prefix("", &path), handler, filters)); + .push((ResourceDef::prefix("", &path), handler, filters)); self } @@ -185,7 +185,7 @@ impl Scope { let filters = scope.take_filters(); self.nested - .push((Resource::prefix("", &path), Box::new(scope), filters)); + .push((ResourceDef::prefix("", &path), Box::new(scope), filters)); self } @@ -244,9 +244,9 @@ impl Scope { } } } else { - let mut handler = ResourceHandler::default(); + let mut handler = Resource::default(); handler.method(method).with(f); - let pattern = Resource::with_prefix( + let pattern = ResourceDef::with_prefix( handler.get_name(), path, if path.is_empty() { "" } else { "/" }, @@ -284,13 +284,13 @@ impl Scope { /// ``` pub fn resource(mut self, path: &str, f: F) -> Scope where - F: FnOnce(&mut ResourceHandler) -> R + 'static, + F: FnOnce(&mut Resource) -> R + 'static, { // add resource handler - let mut handler = ResourceHandler::default(); + let mut handler = Resource::default(); f(&mut handler); - let pattern = Resource::with_prefix( + let pattern = ResourceDef::with_prefix( handler.get_name(), path, if path.is_empty() { "" } else { "/" }, @@ -306,10 +306,10 @@ impl Scope { /// Default resource to be used if no matching route could be found. pub fn default_resource(mut self, f: F) -> Scope where - F: FnOnce(&mut ResourceHandler) -> R + 'static, + F: FnOnce(&mut Resource) -> R + 'static, { if self.default.is_none() { - self.default = Some(Rc::new(ResourceHandler::default_not_found())); + self.default = Some(Rc::new(Resource::default_not_found())); } { let default = Rc::get_mut(self.default.as_mut().unwrap()) @@ -439,7 +439,7 @@ struct ComposeInfo { id: RouteId, req: HttpRequest, mws: Rc>>>, - resource: Rc>, + resource: Rc>, } enum ComposeState { @@ -465,7 +465,7 @@ impl ComposeState { impl Compose { fn new( id: RouteId, req: HttpRequest, mws: Rc>>>, - resource: Rc>, + resource: Rc>, ) -> Self { let mut info = ComposeInfo { id, diff --git a/src/test.rs b/src/test.rs index 704292df..4289bca8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -27,7 +27,7 @@ use httpresponse::HttpResponse; use middleware::Middleware; use param::Params; use payload::Payload; -use resource::ResourceHandler; +use resource::Resource; use router::Router; use server::message::{Request, RequestPool}; use server::{HttpServer, IntoHttpHandler, ServerSettings}; @@ -353,7 +353,7 @@ impl TestApp { /// to `App::resource()` method. pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp where - F: FnOnce(&mut ResourceHandler) -> R + 'static, + F: FnOnce(&mut Resource) -> R + 'static, { self.app = Some(self.app.take().unwrap().resource(path, f)); self From 4395add1c756291aa381e03878d76c5f4bc15639 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Jul 2018 00:05:01 +0600 Subject: [PATCH 0495/1635] update travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 42e6bc6f..d6cd29e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: rust -sudo: false +sudo: required dist: trusty cache: From 7d753eeb8c5744dd2f6c0e647c1a73b34a7022dd Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 13 Jul 2018 09:59:09 +0300 Subject: [PATCH 0496/1635] Private serde fork (#390) * Fork serde_urlencoded * Apply enum PR https://github.com/nox/serde_urlencoded/pull/30 * Add test to verify enum in query * Docs are updated to show example of how to use enum. --- Cargo.toml | 5 +- src/extractor.rs | 21 +- src/lib.rs | 2 +- src/serde_urlencoded/de.rs | 298 ++++++++++++++++++ src/serde_urlencoded/mod.rs | 114 +++++++ src/serde_urlencoded/ser/key.rs | 76 +++++ src/serde_urlencoded/ser/mod.rs | 507 ++++++++++++++++++++++++++++++ src/serde_urlencoded/ser/pair.rs | 257 +++++++++++++++ src/serde_urlencoded/ser/part.rs | 222 +++++++++++++ src/serde_urlencoded/ser/value.rs | 59 ++++ tests/test_handlers.rs | 42 +++ 11 files changed, 1595 insertions(+), 8 deletions(-) create mode 100644 src/serde_urlencoded/de.rs create mode 100644 src/serde_urlencoded/mod.rs create mode 100644 src/serde_urlencoded/ser/key.rs create mode 100644 src/serde_urlencoded/ser/mod.rs create mode 100644 src/serde_urlencoded/ser/pair.rs create mode 100644 src/serde_urlencoded/ser/part.rs create mode 100644 src/serde_urlencoded/ser/value.rs diff --git a/Cargo.toml b/Cargo.toml index 59f101e4..e4371254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,6 @@ rand = "0.5" regex = "1.0" serde = "1.0" serde_json = "1.0" -serde_urlencoded = "0.5" sha1 = "0.6" smallvec = "0.6" time = "0.1" @@ -105,6 +104,10 @@ tokio-tls = { version="0.1", optional = true } openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } +# forked url_encoded +itoa = "0.4" +dtoa = "0.4" + [dev-dependencies] env_logger = "0.5" serde_derive = "1.0" diff --git a/src/extractor.rs b/src/extractor.rs index 683e1526..bebfbf20 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -139,15 +139,24 @@ impl fmt::Display for Path { /// #[macro_use] extern crate serde_derive; /// use actix_web::{App, Query, http}; /// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } +/// +///#[derive(Debug, Deserialize)] +///pub enum ResponseType { +/// Token, +/// Code +///} +/// +///#[derive(Deserialize)] +///pub struct AuthRequest { +/// id: u64, +/// response_type: ResponseType, +///} /// /// // use `with` extractor for query info /// // this handler get called only if request's query contains `username` field -/// fn index(info: Query) -> String { -/// format!("Welcome {}!", info.username) +/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` +/// fn index(info: Query) -> String { +/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } /// /// fn main() { diff --git a/src/lib.rs b/src/lib.rs index a1a09982..d61c94f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,7 +133,6 @@ extern crate num_cpus; #[macro_use] extern crate percent_encoding; extern crate serde_json; -extern crate serde_urlencoded; extern crate smallvec; #[macro_use] extern crate actix as actix_inner; @@ -152,6 +151,7 @@ extern crate openssl; #[cfg(feature = "openssl")] extern crate tokio_openssl; +mod serde_urlencoded; mod application; mod body; mod context; diff --git a/src/serde_urlencoded/de.rs b/src/serde_urlencoded/de.rs new file mode 100644 index 00000000..affbfb37 --- /dev/null +++ b/src/serde_urlencoded/de.rs @@ -0,0 +1,298 @@ +//! Deserialization support for the `application/x-www-form-urlencoded` format. + +use serde::de::{self, DeserializeSeed, EnumAccess, IntoDeserializer, VariantAccess, Visitor}; +use serde::de::Error as de_Error; + +use serde::de::value::MapDeserializer; +use std::borrow::Cow; +use std::io::Read; +use url::form_urlencoded::Parse as UrlEncodedParse; +use url::form_urlencoded::parse; + +#[doc(inline)] +pub use serde::de::value::Error; + +/// Deserializes a `application/x-wwww-url-encoded` value from a `&[u8]`. +/// +/// ```ignore +/// let meal = vec![ +/// ("bread".to_owned(), "baguette".to_owned()), +/// ("cheese".to_owned(), "comté".to_owned()), +/// ("meat".to_owned(), "ham".to_owned()), +/// ("fat".to_owned(), "butter".to_owned()), +/// ]; +/// +/// assert_eq!( +/// serde_urlencoded::from_bytes::>( +/// b"bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter"), +/// Ok(meal)); +/// ``` +pub fn from_bytes<'de, T>(input: &'de [u8]) -> Result + where T: de::Deserialize<'de>, +{ + T::deserialize(Deserializer::new(parse(input))) +} + +/// Deserializes a `application/x-wwww-url-encoded` value from a `&str`. +/// +/// ```ignore +/// let meal = vec![ +/// ("bread".to_owned(), "baguette".to_owned()), +/// ("cheese".to_owned(), "comté".to_owned()), +/// ("meat".to_owned(), "ham".to_owned()), +/// ("fat".to_owned(), "butter".to_owned()), +/// ]; +/// +/// assert_eq!( +/// serde_urlencoded::from_str::>( +/// "bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter"), +/// Ok(meal)); +/// ``` +pub fn from_str<'de, T>(input: &'de str) -> Result + where T: de::Deserialize<'de>, +{ + from_bytes(input.as_bytes()) +} + +#[allow(dead_code)] +/// Convenience function that reads all bytes from `reader` and deserializes +/// them with `from_bytes`. +pub fn from_reader(mut reader: R) -> Result + where T: de::DeserializeOwned, + R: Read, +{ + let mut buf = vec![]; + reader.read_to_end(&mut buf) + .map_err(|e| { + de::Error::custom(format_args!("could not read input: {}", e)) + })?; + from_bytes(&buf) +} + +/// A deserializer for the `application/x-www-form-urlencoded` format. +/// +/// * Supported top-level outputs are structs, maps and sequences of pairs, +/// with or without a given length. +/// +/// * Main `deserialize` methods defers to `deserialize_map`. +/// +/// * Everything else but `deserialize_seq` and `deserialize_seq_fixed_size` +/// defers to `deserialize`. +pub struct Deserializer<'de> { + inner: MapDeserializer<'de, PartIterator<'de>, Error>, +} + +impl<'de> Deserializer<'de> { + /// Returns a new `Deserializer`. + pub fn new(parser: UrlEncodedParse<'de>) -> Self { + Deserializer { + inner: MapDeserializer::new(PartIterator(parser)), + } + } +} + +impl<'de> de::Deserializer<'de> for Deserializer<'de> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where V: de::Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where V: de::Visitor<'de>, + { + visitor.visit_map(self.inner) + } + + fn deserialize_seq(self, visitor: V) -> Result + where V: de::Visitor<'de>, + { + visitor.visit_seq(self.inner) + } + + fn deserialize_unit(self, visitor: V) -> Result + where V: de::Visitor<'de>, + { + self.inner.end()?; + visitor.visit_unit() + } + + forward_to_deserialize_any! { + bool + u8 + u16 + u32 + u64 + i8 + i16 + i32 + i64 + f32 + f64 + char + str + string + option + bytes + byte_buf + unit_struct + newtype_struct + tuple_struct + struct + identifier + tuple + enum + ignored_any + } +} + +struct PartIterator<'de>(UrlEncodedParse<'de>); + +impl<'de> Iterator for PartIterator<'de> { + type Item = (Part<'de>, Part<'de>); + + fn next(&mut self) -> Option { + self.0.next().map(|(k, v)| (Part(k), Part(v))) + } +} + +struct Part<'de>(Cow<'de, str>); + +impl<'de> IntoDeserializer<'de> for Part<'de> +{ + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +macro_rules! forward_parsed_value { + ($($ty:ident => $method:ident,)*) => { + $( + fn $method(self, visitor: V) -> Result + where V: de::Visitor<'de> + { + match self.0.parse::<$ty>() { + Ok(val) => val.into_deserializer().$method(visitor), + Err(e) => Err(de::Error::custom(e)) + } + } + )* + } +} + +impl<'de> de::Deserializer<'de> for Part<'de> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where V: de::Visitor<'de>, + { + self.0.into_deserializer().deserialize_any(visitor) + } + + fn deserialize_option(self, visitor: V) -> Result + where V: de::Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_enum(self, _name: &'static str, _variants: &'static [&'static str], visitor: V) -> Result + where V: de::Visitor<'de>, + { + visitor.visit_enum(ValueEnumAccess { value: self.0 }) + } + + forward_to_deserialize_any! { + char + str + string + unit + bytes + byte_buf + unit_struct + newtype_struct + tuple_struct + struct + identifier + tuple + ignored_any + seq + map + } + + forward_parsed_value! { + bool => deserialize_bool, + u8 => deserialize_u8, + u16 => deserialize_u16, + u32 => deserialize_u32, + u64 => deserialize_u64, + i8 => deserialize_i8, + i16 => deserialize_i16, + i32 => deserialize_i32, + i64 => deserialize_i64, + f32 => deserialize_f32, + f64 => deserialize_f64, + } +} + +/// Provides access to a keyword which can be deserialized into an enum variant. The enum variant +/// must be a unit variant, otherwise deserialization will fail. +struct ValueEnumAccess<'de> { + value: Cow<'de, str>, +} + +impl<'de> EnumAccess<'de> for ValueEnumAccess<'de> { + type Error = Error; + type Variant = UnitOnlyVariantAccess; + + fn variant_seed( + self, + seed: V, + ) -> Result<(V::Value, Self::Variant), Self::Error> + where V: DeserializeSeed<'de>, + { + let variant = seed.deserialize(self.value.into_deserializer())?; + Ok((variant, UnitOnlyVariantAccess)) + } +} + +/// A visitor for deserializing the contents of the enum variant. As we only support +/// `unit_variant`, all other variant types will return an error. +struct UnitOnlyVariantAccess; + +impl<'de> VariantAccess<'de> for UnitOnlyVariantAccess { + type Error = Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed(self, _seed: T) -> Result + where T: DeserializeSeed<'de>, + { + Err(Error::custom("expected unit variant")) + } + + fn tuple_variant( + self, + _len: usize, + _visitor: V, + ) -> Result + where V: Visitor<'de>, + { + Err(Error::custom("expected unit variant")) + } + + fn struct_variant( + self, + _fields: &'static [&'static str], + _visitor: V, + ) -> Result + where V: Visitor<'de>, + { + Err(Error::custom("expected unit variant")) + } +} diff --git a/src/serde_urlencoded/mod.rs b/src/serde_urlencoded/mod.rs new file mode 100644 index 00000000..6ee62c2a --- /dev/null +++ b/src/serde_urlencoded/mod.rs @@ -0,0 +1,114 @@ +//! `x-www-form-urlencoded` meets Serde + +extern crate itoa; +extern crate dtoa; + +pub mod de; +pub mod ser; + +#[doc(inline)] +pub use self::de::{Deserializer, from_bytes, from_reader, from_str}; +#[doc(inline)] +pub use self::ser::{Serializer, to_string}; + +#[cfg(test)] +mod tests { + #[test] + fn deserialize_bytes() { + let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; + + assert_eq!(super::from_bytes(b"first=23&last=42"), + Ok(result)); + } + + #[test] + fn deserialize_str() { + let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; + + assert_eq!(super::from_str("first=23&last=42"), + Ok(result)); + } + + #[test] + fn deserialize_reader() { + let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; + + assert_eq!(super::from_reader(b"first=23&last=42" as &[_]), + Ok(result)); + } + + #[test] + fn deserialize_option() { + let result = vec![ + ("first".to_owned(), Some(23)), + ("last".to_owned(), Some(42)), + ]; + assert_eq!(super::from_str("first=23&last=42"), Ok(result)); + } + + #[test] + fn deserialize_unit() { + assert_eq!(super::from_str(""), Ok(())); + assert_eq!(super::from_str("&"), Ok(())); + assert_eq!(super::from_str("&&"), Ok(())); + assert!(super::from_str::<()>("first=23").is_err()); + } + + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] + enum X { + A, + B, + C, + } + + #[test] + fn deserialize_unit_enum() { + let result = vec![ + ("one".to_owned(), X::A), + ("two".to_owned(), X::B), + ("three".to_owned(), X::C) + ]; + + assert_eq!(super::from_str("one=A&two=B&three=C"), Ok(result)); + } + + #[test] + fn serialize_option_map_int() { + let params = &[("first", Some(23)), ("middle", None), ("last", Some(42))]; + + assert_eq!(super::to_string(params), + Ok("first=23&last=42".to_owned())); + } + + #[test] + fn serialize_option_map_string() { + let params = + &[("first", Some("hello")), ("middle", None), ("last", Some("world"))]; + + assert_eq!(super::to_string(params), + Ok("first=hello&last=world".to_owned())); + } + + #[test] + fn serialize_option_map_bool() { + let params = &[("one", Some(true)), ("two", Some(false))]; + + assert_eq!(super::to_string(params), + Ok("one=true&two=false".to_owned())); + } + + #[test] + fn serialize_map_bool() { + let params = &[("one", true), ("two", false)]; + + assert_eq!(super::to_string(params), + Ok("one=true&two=false".to_owned())); + } + + #[test] + fn serialize_unit_enum() { + let params = &[("one", X::A), ("two", X::B), ("three", X::C)]; + assert_eq!(super::to_string(params), + Ok("one=A&two=B&three=C".to_owned())); + } +} diff --git a/src/serde_urlencoded/ser/key.rs b/src/serde_urlencoded/ser/key.rs new file mode 100644 index 00000000..2d138f18 --- /dev/null +++ b/src/serde_urlencoded/ser/key.rs @@ -0,0 +1,76 @@ +use super::super::ser::Error; +use super::super::ser::part::Sink; +use serde::Serialize; +use std::borrow::Cow; +use std::ops::Deref; + +pub enum Key<'key> { + Static(&'static str), + Dynamic(Cow<'key, str>), +} + +impl<'key> Deref for Key<'key> { + type Target = str; + + fn deref(&self) -> &str { + match *self { + Key::Static(key) => key, + Key::Dynamic(ref key) => key, + } + } +} + +impl<'key> From> for Cow<'static, str> { + fn from(key: Key<'key>) -> Self { + match key { + Key::Static(key) => key.into(), + Key::Dynamic(key) => key.into_owned().into(), + } + } +} + +pub struct KeySink { + end: End, +} + +impl KeySink + where End: for<'key> FnOnce(Key<'key>) -> Result +{ + pub fn new(end: End) -> Self { + KeySink { end: end } + } +} + +impl Sink for KeySink + where End: for<'key> FnOnce(Key<'key>) -> Result +{ + type Ok = Ok; + + fn serialize_static_str(self, + value: &'static str) + -> Result { + (self.end)(Key::Static(value)) + } + + fn serialize_str(self, value: &str) -> Result { + (self.end)(Key::Dynamic(value.into())) + } + + fn serialize_string(self, value: String) -> Result { + (self.end)(Key::Dynamic(value.into())) + } + + fn serialize_none(self) -> Result { + Err(self.unsupported()) + } + + fn serialize_some(self, + _value: &T) + -> Result { + Err(self.unsupported()) + } + + fn unsupported(self) -> Error { + Error::Custom("unsupported key".into()) + } +} diff --git a/src/serde_urlencoded/ser/mod.rs b/src/serde_urlencoded/ser/mod.rs new file mode 100644 index 00000000..f8d5e13e --- /dev/null +++ b/src/serde_urlencoded/ser/mod.rs @@ -0,0 +1,507 @@ +//! Serialization support for the `application/x-www-form-urlencoded` format. + +mod key; +mod pair; +mod part; +mod value; + +use serde::ser; +use std::borrow::Cow; +use std::error; +use std::fmt; +use std::str; +use url::form_urlencoded::Serializer as UrlEncodedSerializer; +use url::form_urlencoded::Target as UrlEncodedTarget; + +/// Serializes a value into a `application/x-wwww-url-encoded` `String` buffer. +/// +/// ```ignore +/// let meal = &[ +/// ("bread", "baguette"), +/// ("cheese", "comté"), +/// ("meat", "ham"), +/// ("fat", "butter"), +/// ]; +/// +/// assert_eq!( +/// serde_urlencoded::to_string(meal), +/// Ok("bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter".to_owned())); +/// ``` +pub fn to_string(input: T) -> Result { + let mut urlencoder = UrlEncodedSerializer::new("".to_owned()); + input.serialize(Serializer::new(&mut urlencoder))?; + Ok(urlencoder.finish()) +} + +/// A serializer for the `application/x-www-form-urlencoded` format. +/// +/// * Supported top-level inputs are structs, maps and sequences of pairs, +/// with or without a given length. +/// +/// * Supported keys and values are integers, bytes (if convertible to strings), +/// unit structs and unit variants. +/// +/// * Newtype structs defer to their inner values. +pub struct Serializer<'output, Target: 'output + UrlEncodedTarget> { + urlencoder: &'output mut UrlEncodedSerializer, +} + +impl<'output, Target: 'output + UrlEncodedTarget> Serializer<'output, Target> { + /// Returns a new `Serializer`. + pub fn new(urlencoder: &'output mut UrlEncodedSerializer) -> Self { + Serializer { urlencoder: urlencoder } + } +} + +/// Errors returned during serializing to `application/x-www-form-urlencoded`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + Custom(Cow<'static, str>), + Utf8(str::Utf8Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::Custom(ref msg) => msg.fmt(f), + Error::Utf8(ref err) => write!(f, "invalid UTF-8: {}", err), + } + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + match *self { + Error::Custom(ref msg) => msg, + Error::Utf8(ref err) => error::Error::description(err), + } + } + + /// The lower-level cause of this error, in the case of a `Utf8` error. + fn cause(&self) -> Option<&error::Error> { + match *self { + Error::Custom(_) => None, + Error::Utf8(ref err) => Some(err), + } + } +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::Custom(format!("{}", msg).into()) + } +} + +/// Sequence serializer. +pub struct SeqSerializer<'output, Target: 'output + UrlEncodedTarget> { + urlencoder: &'output mut UrlEncodedSerializer, +} + +/// Tuple serializer. +/// +/// Mostly used for arrays. +pub struct TupleSerializer<'output, Target: 'output + UrlEncodedTarget> { + urlencoder: &'output mut UrlEncodedSerializer, +} + +/// Tuple struct serializer. +/// +/// Never instantiated, tuple structs are not supported. +pub struct TupleStructSerializer<'output, T: 'output + UrlEncodedTarget> { + inner: ser::Impossible<&'output mut UrlEncodedSerializer, Error>, +} + +/// Tuple variant serializer. +/// +/// Never instantiated, tuple variants are not supported. +pub struct TupleVariantSerializer<'output, T: 'output + UrlEncodedTarget> { + inner: ser::Impossible<&'output mut UrlEncodedSerializer, Error>, +} + +/// Map serializer. +pub struct MapSerializer<'output, Target: 'output + UrlEncodedTarget> { + urlencoder: &'output mut UrlEncodedSerializer, + key: Option>, +} + +/// Struct serializer. +pub struct StructSerializer<'output, Target: 'output + UrlEncodedTarget> { + urlencoder: &'output mut UrlEncodedSerializer, +} + +/// Struct variant serializer. +/// +/// Never instantiated, struct variants are not supported. +pub struct StructVariantSerializer<'output, T: 'output + UrlEncodedTarget> { + inner: ser::Impossible<&'output mut UrlEncodedSerializer, Error>, +} + +impl<'output, Target> ser::Serializer for Serializer<'output, Target> + where Target: 'output + UrlEncodedTarget, +{ + type Ok = &'output mut UrlEncodedSerializer; + type Error = Error; + type SerializeSeq = SeqSerializer<'output, Target>; + type SerializeTuple = TupleSerializer<'output, Target>; + type SerializeTupleStruct = TupleStructSerializer<'output, Target>; + type SerializeTupleVariant = TupleVariantSerializer<'output, Target>; + type SerializeMap = MapSerializer<'output, Target>; + type SerializeStruct = StructSerializer<'output, Target>; + type SerializeStructVariant = StructVariantSerializer<'output, Target>; + + /// Returns an error. + fn serialize_bool(self, _v: bool) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_i8(self, _v: i8) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_i16(self, _v: i16) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_i32(self, _v: i32) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_i64(self, _v: i64) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_u8(self, _v: u8) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_u16(self, _v: u16) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_u32(self, _v: u32) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_u64(self, _v: u64) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_f32(self, _v: f32) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_f64(self, _v: f64) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_char(self, _v: char) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_str(self, _value: &str) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_bytes(self, _value: &[u8]) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_unit(self) -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_unit_struct(self, + _name: &'static str) + -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_unit_variant(self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str) + -> Result { + Err(Error::top_level()) + } + + /// Serializes the inner value, ignoring the newtype name. + fn serialize_newtype_struct + (self, + _name: &'static str, + value: &T) + -> Result { + value.serialize(self) + } + + /// Returns an error. + fn serialize_newtype_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T) + -> Result { + Err(Error::top_level()) + } + + /// Returns `Ok`. + fn serialize_none(self) -> Result { + Ok(self.urlencoder) + } + + /// Serializes the given value. + fn serialize_some + (self, + value: &T) + -> Result { + value.serialize(self) + } + + /// Serialize a sequence, given length (if any) is ignored. + fn serialize_seq(self, + _len: Option) + -> Result { + Ok(SeqSerializer { urlencoder: self.urlencoder }) + } + + /// Returns an error. + fn serialize_tuple(self, + _len: usize) + -> Result { + Ok(TupleSerializer { urlencoder: self.urlencoder }) + } + + /// Returns an error. + fn serialize_tuple_struct(self, + _name: &'static str, + _len: usize) + -> Result { + Err(Error::top_level()) + } + + /// Returns an error. + fn serialize_tuple_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize) + -> Result { + Err(Error::top_level()) + } + + /// Serializes a map, given length is ignored. + fn serialize_map(self, + _len: Option) + -> Result { + Ok(MapSerializer { + urlencoder: self.urlencoder, + key: None, + }) + } + + /// Serializes a struct, given length is ignored. + fn serialize_struct(self, + _name: &'static str, + _len: usize) + -> Result { + Ok(StructSerializer { urlencoder: self.urlencoder }) + } + + /// Returns an error. + fn serialize_struct_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize) + -> Result { + Err(Error::top_level()) + } +} + +impl<'output, Target> ser::SerializeSeq for SeqSerializer<'output, Target> + where Target: 'output + UrlEncodedTarget, +{ + type Ok = &'output mut UrlEncodedSerializer; + type Error = Error; + + fn serialize_element(&mut self, + value: &T) + -> Result<(), Error> { + value.serialize(pair::PairSerializer::new(self.urlencoder)) + } + + fn end(self) -> Result { + Ok(self.urlencoder) + } +} + +impl<'output, Target> ser::SerializeTuple for TupleSerializer<'output, Target> + where Target: 'output + UrlEncodedTarget, +{ + type Ok = &'output mut UrlEncodedSerializer; + type Error = Error; + + fn serialize_element(&mut self, + value: &T) + -> Result<(), Error> { + value.serialize(pair::PairSerializer::new(self.urlencoder)) + } + + fn end(self) -> Result { + Ok(self.urlencoder) + } +} + +impl<'output, Target> ser::SerializeTupleStruct + for + TupleStructSerializer<'output, Target> + where Target: 'output + UrlEncodedTarget, +{ + type Ok = &'output mut UrlEncodedSerializer; + type Error = Error; + + fn serialize_field(&mut self, + value: &T) + -> Result<(), Error> { + self.inner.serialize_field(value) + } + + fn end(self) -> Result { + self.inner.end() + } +} + +impl<'output, Target> ser::SerializeTupleVariant + for + TupleVariantSerializer<'output, Target> + where Target: 'output + UrlEncodedTarget, +{ + type Ok = &'output mut UrlEncodedSerializer; + type Error = Error; + + fn serialize_field(&mut self, + value: &T) + -> Result<(), Error> { + self.inner.serialize_field(value) + } + + fn end(self) -> Result { + self.inner.end() + } +} + +impl<'output, Target> ser::SerializeMap for MapSerializer<'output, Target> + where Target: 'output + UrlEncodedTarget, +{ + type Ok = &'output mut UrlEncodedSerializer; + type Error = Error; + + fn serialize_entry + (&mut self, + key: &K, + value: &V) + -> Result<(), Error> { + let key_sink = key::KeySink::new(|key| { + let value_sink = value::ValueSink::new(self.urlencoder, &key); + value.serialize(part::PartSerializer::new(value_sink))?; + self.key = None; + Ok(()) + }); + let entry_serializer = part::PartSerializer::new(key_sink); + key.serialize(entry_serializer) + } + + fn serialize_key(&mut self, + key: &T) + -> Result<(), Error> { + let key_sink = key::KeySink::new(|key| Ok(key.into())); + let key_serializer = part::PartSerializer::new(key_sink); + self.key = Some(key.serialize(key_serializer)?); + Ok(()) + } + + fn serialize_value(&mut self, + value: &T) + -> Result<(), Error> { + { + let key = self.key.as_ref().ok_or_else(|| Error::no_key())?; + let value_sink = value::ValueSink::new(self.urlencoder, &key); + value.serialize(part::PartSerializer::new(value_sink))?; + } + self.key = None; + Ok(()) + } + + fn end(self) -> Result { + Ok(self.urlencoder) + } +} + +impl<'output, Target> ser::SerializeStruct for StructSerializer<'output, Target> + where Target: 'output + UrlEncodedTarget, +{ + type Ok = &'output mut UrlEncodedSerializer; + type Error = Error; + + fn serialize_field(&mut self, + key: &'static str, + value: &T) + -> Result<(), Error> { + let value_sink = value::ValueSink::new(self.urlencoder, key); + value.serialize(part::PartSerializer::new(value_sink)) + } + + fn end(self) -> Result { + Ok(self.urlencoder) + } +} + +impl<'output, Target> ser::SerializeStructVariant + for + StructVariantSerializer<'output, Target> + where Target: 'output + UrlEncodedTarget, +{ + type Ok = &'output mut UrlEncodedSerializer; + type Error = Error; + + fn serialize_field(&mut self, + key: &'static str, + value: &T) + -> Result<(), Error> { + self.inner.serialize_field(key, value) + } + + fn end(self) -> Result { + self.inner.end() + } +} + +impl Error { + fn top_level() -> Self { + let msg = "top-level serializer supports only maps and structs"; + Error::Custom(msg.into()) + } + + fn no_key() -> Self { + let msg = "tried to serialize a value before serializing key"; + Error::Custom(msg.into()) + } +} diff --git a/src/serde_urlencoded/ser/pair.rs b/src/serde_urlencoded/ser/pair.rs new file mode 100644 index 00000000..37f98475 --- /dev/null +++ b/src/serde_urlencoded/ser/pair.rs @@ -0,0 +1,257 @@ +use super::super::ser::Error; +use super::super::ser::key::KeySink; +use super::super::ser::part::PartSerializer; +use super::super::ser::value::ValueSink; +use serde::ser; +use std::borrow::Cow; +use std::mem; +use url::form_urlencoded::Serializer as UrlEncodedSerializer; +use url::form_urlencoded::Target as UrlEncodedTarget; + +pub struct PairSerializer<'target, Target: 'target + UrlEncodedTarget> { + urlencoder: &'target mut UrlEncodedSerializer, + state: PairState, +} + +impl<'target, Target> PairSerializer<'target, Target> + where Target: 'target + UrlEncodedTarget, +{ + pub fn new(urlencoder: &'target mut UrlEncodedSerializer) -> Self { + PairSerializer { + urlencoder: urlencoder, + state: PairState::WaitingForKey, + } + } +} + +impl<'target, Target> ser::Serializer for PairSerializer<'target, Target> + where Target: 'target + UrlEncodedTarget, +{ + type Ok = (); + type Error = Error; + type SerializeSeq = ser::Impossible<(), Error>; + type SerializeTuple = Self; + type SerializeTupleStruct = ser::Impossible<(), Error>; + type SerializeTupleVariant = ser::Impossible<(), Error>; + type SerializeMap = ser::Impossible<(), Error>; + type SerializeStruct = ser::Impossible<(), Error>; + type SerializeStructVariant = ser::Impossible<(), Error>; + + fn serialize_bool(self, _v: bool) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_i8(self, _v: i8) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_i16(self, _v: i16) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_i32(self, _v: i32) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_i64(self, _v: i64) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_u8(self, _v: u8) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_u16(self, _v: u16) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_u32(self, _v: u32) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_u64(self, _v: u64) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_f32(self, _v: f32) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_f64(self, _v: f64) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_char(self, _v: char) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_str(self, _value: &str) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_bytes(self, _value: &[u8]) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_unit(self) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_unit_variant(self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str) + -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_newtype_struct + (self, + _name: &'static str, + value: &T) + -> Result<(), Error> { + value.serialize(self) + } + + fn serialize_newtype_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T) + -> Result<(), Error> { + Err(Error::unsupported_pair()) + } + + fn serialize_none(self) -> Result<(), Error> { + Ok(()) + } + + fn serialize_some(self, + value: &T) + -> Result<(), Error> { + value.serialize(self) + } + + fn serialize_seq(self, + _len: Option) + -> Result { + Err(Error::unsupported_pair()) + } + + fn serialize_tuple(self, len: usize) -> Result { + if len == 2 { + Ok(self) + } else { + Err(Error::unsupported_pair()) + } + } + + fn serialize_tuple_struct(self, + _name: &'static str, + _len: usize) + -> Result { + Err(Error::unsupported_pair()) + } + + fn serialize_tuple_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize) + -> Result { + Err(Error::unsupported_pair()) + } + + fn serialize_map(self, + _len: Option) + -> Result { + Err(Error::unsupported_pair()) + } + + fn serialize_struct(self, + _name: &'static str, + _len: usize) + -> Result { + Err(Error::unsupported_pair()) + } + + fn serialize_struct_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize) + -> Result { + Err(Error::unsupported_pair()) + } +} + +impl<'target, Target> ser::SerializeTuple for PairSerializer<'target, Target> + where Target: 'target + UrlEncodedTarget, +{ + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, + value: &T) + -> Result<(), Error> { + match mem::replace(&mut self.state, PairState::Done) { + PairState::WaitingForKey => { + let key_sink = KeySink::new(|key| Ok(key.into())); + let key_serializer = PartSerializer::new(key_sink); + self.state = PairState::WaitingForValue { + key: value.serialize(key_serializer)?, + }; + Ok(()) + }, + PairState::WaitingForValue { key } => { + let result = { + let value_sink = ValueSink::new(self.urlencoder, &key); + let value_serializer = PartSerializer::new(value_sink); + value.serialize(value_serializer) + }; + if result.is_ok() { + self.state = PairState::Done; + } else { + self.state = PairState::WaitingForValue { key: key }; + } + result + }, + PairState::Done => Err(Error::done()), + } + } + + fn end(self) -> Result<(), Error> { + if let PairState::Done = self.state { + Ok(()) + } else { + Err(Error::not_done()) + } + } +} + +enum PairState { + WaitingForKey, + WaitingForValue { key: Cow<'static, str> }, + Done, +} + +impl Error { + fn done() -> Self { + Error::Custom("this pair has already been serialized".into()) + } + + fn not_done() -> Self { + Error::Custom("this pair has not yet been serialized".into()) + } + + fn unsupported_pair() -> Self { + Error::Custom("unsupported pair".into()) + } +} diff --git a/src/serde_urlencoded/ser/part.rs b/src/serde_urlencoded/ser/part.rs new file mode 100644 index 00000000..2ce352d2 --- /dev/null +++ b/src/serde_urlencoded/ser/part.rs @@ -0,0 +1,222 @@ +use ::serde; + +use super::super::dtoa; +use super::super::itoa; +use super::super::ser::Error; +use std::str; + +pub struct PartSerializer { + sink: S, +} + +impl PartSerializer { + pub fn new(sink: S) -> Self { + PartSerializer { sink: sink } + } +} + +pub trait Sink: Sized { + type Ok; + + fn serialize_static_str(self, + value: &'static str) + -> Result; + + fn serialize_str(self, value: &str) -> Result; + fn serialize_string(self, value: String) -> Result; + fn serialize_none(self) -> Result; + + fn serialize_some + (self, + value: &T) + -> Result; + + fn unsupported(self) -> Error; +} + +impl serde::ser::Serializer for PartSerializer { + type Ok = S::Ok; + type Error = Error; + type SerializeSeq = serde::ser::Impossible; + type SerializeTuple = serde::ser::Impossible; + type SerializeTupleStruct = serde::ser::Impossible; + type SerializeTupleVariant = serde::ser::Impossible; + type SerializeMap = serde::ser::Impossible; + type SerializeStruct = serde::ser::Impossible; + type SerializeStructVariant = serde::ser::Impossible; + + fn serialize_bool(self, v: bool) -> Result { + self.sink.serialize_static_str(if v { "true" } else { "false" }) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_integer(v) + } + + fn serialize_i16(self, v: i16) -> Result { + self.serialize_integer(v) + } + + fn serialize_i32(self, v: i32) -> Result { + self.serialize_integer(v) + } + + fn serialize_i64(self, v: i64) -> Result { + self.serialize_integer(v) + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_integer(v) + } + + fn serialize_u16(self, v: u16) -> Result { + self.serialize_integer(v) + } + + fn serialize_u32(self, v: u32) -> Result { + self.serialize_integer(v) + } + + fn serialize_u64(self, v: u64) -> Result { + self.serialize_integer(v) + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_floating(v) + } + + fn serialize_f64(self, v: f64) -> Result { + self.serialize_floating(v) + } + + fn serialize_char(self, v: char) -> Result { + self.sink.serialize_string(v.to_string()) + } + + fn serialize_str(self, value: &str) -> Result { + self.sink.serialize_str(value) + } + + fn serialize_bytes(self, value: &[u8]) -> Result { + match str::from_utf8(value) { + Ok(value) => self.sink.serialize_str(value), + Err(err) => Err(Error::Utf8(err)), + } + } + + fn serialize_unit(self) -> Result { + Err(self.sink.unsupported()) + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + self.sink.serialize_static_str(name.into()) + } + + fn serialize_unit_variant(self, + _name: &'static str, + _variant_index: u32, + variant: &'static str) + -> Result { + self.sink.serialize_static_str(variant.into()) + } + + fn serialize_newtype_struct + (self, + _name: &'static str, + value: &T) + -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T) + -> Result { + Err(self.sink.unsupported()) + } + + fn serialize_none(self) -> Result { + self.sink.serialize_none() + } + + fn serialize_some(self, + value: &T) + -> Result { + self.sink.serialize_some(value) + } + + fn serialize_seq(self, + _len: Option) + -> Result { + Err(self.sink.unsupported()) + } + + fn serialize_tuple(self, + _len: usize) + -> Result { + Err(self.sink.unsupported()) + } + + fn serialize_tuple_struct(self, + _name: &'static str, + _len: usize) + -> Result { + Err(self.sink.unsupported()) + } + + fn serialize_tuple_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize) + -> Result { + Err(self.sink.unsupported()) + } + + fn serialize_map(self, + _len: Option) + -> Result { + Err(self.sink.unsupported()) + } + + fn serialize_struct(self, + _name: &'static str, + _len: usize) + -> Result { + Err(self.sink.unsupported()) + } + + fn serialize_struct_variant + (self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize) + -> Result { + Err(self.sink.unsupported()) + } +} + +impl PartSerializer { + fn serialize_integer(self, value: I) -> Result + where I: itoa::Integer, + { + let mut buf = [b'\0'; 20]; + let len = itoa::write(&mut buf[..], value).unwrap(); + let part = unsafe { str::from_utf8_unchecked(&buf[0..len]) }; + serde::ser::Serializer::serialize_str(self, part) + } + + fn serialize_floating(self, value: F) -> Result + where F: dtoa::Floating, + { + let mut buf = [b'\0'; 24]; + let len = dtoa::write(&mut buf[..], value).unwrap(); + let part = unsafe { str::from_utf8_unchecked(&buf[0..len]) }; + serde::ser::Serializer::serialize_str(self, part) + } +} diff --git a/src/serde_urlencoded/ser/value.rs b/src/serde_urlencoded/ser/value.rs new file mode 100644 index 00000000..ef63b010 --- /dev/null +++ b/src/serde_urlencoded/ser/value.rs @@ -0,0 +1,59 @@ +use super::super::ser::Error; +use super::super::ser::part::{PartSerializer, Sink}; +use serde::ser::Serialize; +use std::str; +use url::form_urlencoded::Serializer as UrlEncodedSerializer; +use url::form_urlencoded::Target as UrlEncodedTarget; + +pub struct ValueSink<'key, 'target, Target> + where Target: 'target + UrlEncodedTarget, +{ + urlencoder: &'target mut UrlEncodedSerializer, + key: &'key str, +} + +impl<'key, 'target, Target> ValueSink<'key, 'target, Target> + where Target: 'target + UrlEncodedTarget, +{ + pub fn new(urlencoder: &'target mut UrlEncodedSerializer, + key: &'key str) + -> Self { + ValueSink { + urlencoder: urlencoder, + key: key, + } + } +} + +impl<'key, 'target, Target> Sink for ValueSink<'key, 'target, Target> + where Target: 'target + UrlEncodedTarget, +{ + type Ok = (); + + fn serialize_str(self, value: &str) -> Result<(), Error> { + self.urlencoder.append_pair(self.key, value); + Ok(()) + } + + fn serialize_static_str(self, value: &'static str) -> Result<(), Error> { + self.serialize_str(value) + } + + fn serialize_string(self, value: String) -> Result<(), Error> { + self.serialize_str(&value) + } + + fn serialize_none(self) -> Result { + Ok(()) + } + + fn serialize_some(self, + value: &T) + -> Result { + value.serialize(PartSerializer::new(self)) + } + + fn unsupported(self) -> Error { + Error::Custom("unsupported value".into()) + } +} diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 95bd5be2..bc65b93f 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -91,6 +91,48 @@ fn test_query_extractor() { assert_eq!(response.status(), StatusCode::BAD_REQUEST); } +#[derive(Deserialize, Debug)] +pub enum ResponseType { + Token, + Code +} + +#[derive(Debug, Deserialize)] +pub struct AuthRequest { + id: u64, + response_type: ResponseType, +} + +#[test] +fn test_query_enum_extractor() { + let mut srv = test::TestServer::new(|app| { + app.resource("/index.html", |r| { + r.with(|p: Query| format!("{:?}", p.into_inner())) + }); + }); + + // client request + let request = srv + .get() + .uri(srv.url("/index.html?id=64&response_type=Code")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }")); + + let request = srv.get().uri(srv.url("/index.html?id=64&response_type=Co")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + + let request = srv.get().uri(srv.url("/index.html?response_type=Code")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); +} + #[test] fn test_async_extractor_async() { let mut srv = test::TestServer::new(|app| { From 9012cf43fe30c14f0748259f6975411b80a548d8 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 14 Jul 2018 00:05:07 +0200 Subject: [PATCH 0497/1635] error: Fix documentation typo --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index c024561f..461b23e2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,7 +37,7 @@ pub type Result = result::Result; /// General purpose actix web error. /// /// An actix web error is used to carry errors from `failure` or `std::error` -/// through actix in a convenient way. It can be created through through +/// through actix in a convenient way. It can be created through /// converting errors with `into()`. /// /// Whenever it is created from an external object a response error is created From da915972c0e32198c924b716e3904e5066a4966a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 15:12:21 +0600 Subject: [PATCH 0498/1635] refactor router --- src/application.rs | 244 +++++----------- src/extractor.rs | 38 +-- src/handler.rs | 7 +- src/httprequest.rs | 55 ++-- src/middleware/cors.rs | 13 +- src/param.rs | 9 + src/pipeline.rs | 20 +- src/resource.rs | 58 ++-- src/router.rs | 617 ++++++++++++++++++++++++++--------------- src/scope.rs | 215 +++++--------- src/test.rs | 14 +- 11 files changed, 635 insertions(+), 655 deletions(-) diff --git a/src/application.rs b/src/application.rs index 96c4ad11..80ba7f52 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,16 +1,15 @@ -use std::collections::HashMap; use std::rc::Rc; -use handler::{AsyncResult, FromRequest, Handler, Responder, RouteHandler, WrapHandler}; +use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler}; use header::ContentEncoding; -use http::{Method, StatusCode}; +use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::Middleware; -use pipeline::{HandlerType, Pipeline, PipelineHandler}; +use pipeline::{Pipeline, PipelineHandler}; use pred::Predicate; use resource::Resource; -use router::{ResourceDef, RouteInfo, Router}; +use router::{ResourceDef, Router}; use scope::Scope; use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; @@ -19,7 +18,6 @@ pub struct HttpApplication { state: Rc, prefix: String, prefix_len: usize, - router: Router, inner: Rc>, filters: Option>>>, middlewares: Rc>>>, @@ -27,16 +25,8 @@ pub struct HttpApplication { #[doc(hidden)] pub struct Inner { - prefix: usize, - default: Rc>, + router: Router, encoding: ContentEncoding, - resources: Vec>, - handlers: Vec>, -} - -enum PrefixHandlerType { - Handler(String, Box>), - Scope(ResourceDef, Box>, Vec>>), } impl PipelineHandler for Inner { @@ -45,80 +35,21 @@ impl PipelineHandler for Inner { self.encoding } - fn handle( - &self, req: &HttpRequest, htype: HandlerType, - ) -> AsyncResult { - match htype { - HandlerType::Normal(idx) => { - if let Some(id) = self.resources[idx].get_route_id(req) { - return self.resources[idx].handle(id, req); - } - } - HandlerType::Handler(idx) => match self.handlers[idx] { - PrefixHandlerType::Handler(_, ref hnd) => return hnd.handle(req), - PrefixHandlerType::Scope(_, ref hnd, _) => return hnd.handle(req), - }, - _ => (), - } - if let Some(id) = self.default.get_route_id(req) { - self.default.handle(id, req) - } else { - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) - } + fn handle(&self, req: &HttpRequest) -> AsyncResult { + self.router.handle(req) } } impl HttpApplication { - #[inline] - fn get_handler(&self, req: &Request) -> (RouteInfo, HandlerType) { - if let Some((idx, info)) = self.router.recognize(req) { - (info, HandlerType::Normal(idx)) - } else { - 'outer: for idx in 0..self.inner.handlers.len() { - match self.inner.handlers[idx] { - PrefixHandlerType::Handler(ref prefix, _) => { - let m = { - let path = &req.path()[self.inner.prefix..]; - let path_len = path.len(); - - path.starts_with(prefix) - && (path_len == prefix.len() - || path.split_at(prefix.len()).1.starts_with('/')) - }; - - if m { - let prefix_len = (self.inner.prefix + prefix.len()) as u16; - let info = self.router.route_info(req, prefix_len); - return (info, HandlerType::Handler(idx)); - } - } - PrefixHandlerType::Scope(ref pattern, _, ref filters) => { - if let Some(params) = - pattern.match_prefix_with_params(req, self.inner.prefix) - { - for filter in filters { - if !filter.check(req, &self.state) { - continue 'outer; - } - } - return ( - self.router.route_info_params(params), - HandlerType::Handler(idx), - ); - } - } - } - } - (self.router.default_route_info(), HandlerType::Default) - } - } - #[cfg(test)] pub(crate) fn run(&self, req: Request) -> AsyncResult { - let (info, tp) = self.get_handler(&req); + let info = self + .inner + .router + .recognize(&req, &self.state, self.prefix_len); let req = HttpRequest::new(req, Rc::clone(&self.state), info); - self.inner.handle(&req, tp) + self.inner.handle(&req) } } @@ -141,10 +72,14 @@ impl HttpHandler for HttpApplication { } } - let (info, tp) = self.get_handler(&msg); + let info = self + .inner + .router + .recognize(&msg, &self.state, self.prefix_len); + let inner = Rc::clone(&self.inner); let req = HttpRequest::new(msg, Rc::clone(&self.state), info); - Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner, tp)) + Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner)) } else { Err(msg) } @@ -154,10 +89,7 @@ impl HttpHandler for HttpApplication { struct ApplicationParts { state: S, prefix: String, - default: Rc>, - resources: Vec<(ResourceDef, Option>)>, - handlers: Vec>, - external: HashMap, + router: Router, encoding: ContentEncoding, middlewares: Vec>>, filters: Vec>>, @@ -204,10 +136,7 @@ where parts: Some(ApplicationParts { state, prefix: "/".to_owned(), - default: Rc::new(Resource::default_not_found()), - resources: Vec::new(), - handlers: Vec::new(), - external: HashMap::new(), + router: Router::new(), middlewares: Vec::new(), filters: Vec::new(), encoding: ContentEncoding::Auto, @@ -315,35 +244,11 @@ where R: Responder + 'static, T: FromRequest + 'static, { - { - let parts: &mut ApplicationParts = - self.parts.as_mut().expect("Use after finish"); - - let out = { - // get resource handler - let mut iterator = parts.resources.iter_mut(); - - loop { - if let Some(&mut (ref pattern, ref mut handler)) = iterator.next() { - if let Some(ref mut handler) = *handler { - if pattern.pattern() == path { - handler.method(method).with(f); - break None; - } - } - } else { - let mut handler = Resource::default(); - handler.method(method).with(f); - let pattern = ResourceDef::new(handler.get_name(), path); - break Some((pattern, Some(handler))); - } - } - }; - - if let Some(out) = out { - parts.resources.push(out); - } - } + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_route(path, method, f); self } @@ -376,17 +281,12 @@ where where F: FnOnce(Scope) -> Scope, { - { - let mut scope = Box::new(f(Scope::new())); - let parts = self.parts.as_mut().expect("Use after finish"); - - let filters = scope.take_filters(); - parts.handlers.push(PrefixHandlerType::Scope( - ResourceDef::prefix("", &path), - scope, - filters, - )); - } + let scope = f(Scope::new(path)); + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_scope(scope); self } @@ -428,25 +328,25 @@ where { let parts = self.parts.as_mut().expect("Use after finish"); - // add resource handler - let mut handler = Resource::default(); - f(&mut handler); + // create resource + let mut resource = Resource::new(ResourceDef::new(path)); - let pattern = ResourceDef::new(handler.get_name(), path); - parts.resources.push((pattern, Some(handler))); + // configure + f(&mut resource); + + parts.router.register_resource(resource); } self } /// Configure resource for a specific path. #[doc(hidden)] - pub fn register_resource(&mut self, path: &str, resource: Resource) { - let pattern = ResourceDef::new(resource.get_name(), path); + pub fn register_resource(&mut self, resource: Resource) { self.parts .as_mut() .expect("Use after finish") - .resources - .push((pattern, Some(resource))); + .router + .register_resource(resource); } /// Default resource to be used if no matching route could be found. @@ -454,12 +354,16 @@ where where F: FnOnce(&mut Resource) -> R + 'static, { - { - let parts = self.parts.as_mut().expect("Use after finish"); - let default = Rc::get_mut(&mut parts.default) - .expect("Multiple App instance references are not allowed"); - f(default); - } + // create and configure default resource + let mut resource = Resource::new(ResourceDef::new("")); + f(&mut resource); + + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_default_resource(resource.into()); + self } @@ -500,17 +404,11 @@ 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()), - ResourceDef::external(name.as_ref(), url.as_ref()), - ); - } + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_external(name.as_ref(), ResourceDef::external(url.as_ref())); self } @@ -544,12 +442,11 @@ where if path.len() > 1 && path.ends_with('/') { path.pop(); } - let parts = self.parts.as_mut().expect("Use after finish"); - - parts.handlers.push(PrefixHandlerType::Handler( - path, - Box::new(WrapHandler::new(handler)), - )); + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_handler(&path, Box::new(WrapHandler::new(handler)), None); } self } @@ -607,27 +504,11 @@ where (prefix.to_owned(), prefix.len()) }; - let mut resources = parts.resources; - for (_, pattern) in parts.external { - resources.push((pattern, None)); - } - - for handler in &mut parts.handlers { - if let PrefixHandlerType::Scope(_, ref mut route_handler, _) = handler { - if !route_handler.has_default_resource() { - route_handler.default_resource(Rc::clone(&parts.default)); - } - }; - } - - let (router, resources) = Router::new(&prefix, resources); + parts.router.finish(); let inner = Rc::new(Inner { - prefix: prefix_len, - default: Rc::clone(&parts.default), + router: parts.router, encoding: parts.encoding, - handlers: parts.handlers, - resources, }); let filters = if parts.filters.is_empty() { None @@ -637,7 +518,6 @@ where HttpApplication { state: Rc::new(parts.state), - router: router.clone(), middlewares: Rc::new(parts.middlewares), prefix, prefix_len, diff --git a/src/extractor.rs b/src/extractor.rs index bebfbf20..8e4745f8 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -719,12 +719,9 @@ mod tests { fn test_request_extract() { let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let mut resource = Resource::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((ResourceDef::new("index", "/{key}/{value}/"), Some(resource))); - let (router, _) = Router::new("", routes); - let info = router.recognize(&req).unwrap().1; + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); + let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); let s = Path::::from_request(&req, &()).unwrap(); @@ -738,8 +735,10 @@ mod tests { let s = Query::::from_request(&req, &()).unwrap(); assert_eq!(s.id, "test"); - let req = TestRequest::with_uri("/name/32/").finish_with_router(router.clone()); - let info = router.recognize(&req).unwrap().1; + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); + let req = TestRequest::with_uri("/name/32/").finish(); + let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); let s = Path::::from_request(&req, &()).unwrap(); @@ -757,29 +756,22 @@ mod tests { #[test] fn test_extract_path_single() { - let mut resource = Resource::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((ResourceDef::new("index", "/{value}/"), Some(resource))); - let (router, _) = Router::new("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - let req = TestRequest::with_uri("/32/").finish_with_router(router.clone()); - let info = router.recognize(&req).unwrap().1; + let req = TestRequest::with_uri("/32/").finish(); + let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); } #[test] fn test_tuple_extract() { - let mut resource = Resource::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((ResourceDef::new("index", "/{key}/{value}/"), Some(resource))); - let (router, _) = Router::new("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let req = TestRequest::with_uri("/name/user1/?id=test") - .finish_with_router(router.clone()); - let info = router.recognize(&req).unwrap().1; + let req = TestRequest::with_uri("/name/user1/?id=test").finish(); + let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); let res = match <(Path<(String, String)>,)>::extract(&req).poll() { diff --git a/src/handler.rs b/src/handler.rs index 241f4e6a..98d25343 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,6 +1,5 @@ use std::marker::PhantomData; use std::ops::Deref; -use std::rc::Rc; use futures::future::{err, ok, Future}; use futures::{Async, Poll}; @@ -9,7 +8,7 @@ use error::Error; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use resource::Resource; +use resource::DefaultResource; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] @@ -409,9 +408,11 @@ pub(crate) trait RouteHandler: 'static { false } - fn default_resource(&mut self, _: Rc>) { + fn default_resource(&mut self, _: DefaultResource) { unimplemented!() } + + fn finish(&mut self) {} } /// Route handler wrapper for Handler diff --git a/src/httprequest.rs b/src/httprequest.rs index 650d3a39..a0497338 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -81,7 +81,9 @@ impl HttpRequest { #[inline] /// Construct new http request with new RouteInfo. - pub(crate) fn with_route_info(&self, route: RouteInfo) -> HttpRequest { + pub(crate) fn with_route_info(&self, mut route: RouteInfo) -> HttpRequest { + route.merge(&self.route); + HttpRequest { route, req: self.req.as_ref().map(|r| r.clone()), @@ -422,26 +424,21 @@ mod tests { #[test] fn test_request_match_info() { - let mut resource = Resource::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((ResourceDef::new("index", "/{key}/"), Some(resource))); - let (router, _) = Router::new("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/"))); let req = TestRequest::with_uri("/value/?id=test").finish(); - let info = router.recognize(&req).unwrap().1; + let info = router.recognize(&req, &(), 0); assert_eq!(info.match_info().get("key"), Some("value")); } #[test] fn test_url_for() { - let mut resource = Resource::<()>::default(); + let mut router = Router::<()>::new(); + let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}")); resource.name("index"); - let routes = vec![( - ResourceDef::new("index", "/user/{name}.{ext}"), - Some(resource), - )]; - let (router, _) = Router::new("/", routes); + router.register_resource(resource); + let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); assert!(!info.has_route("/test/unknown")); @@ -466,13 +463,12 @@ mod tests { #[test] fn test_url_for_with_prefix() { - let mut resource = Resource::<()>::default(); + let mut resource = Resource::new(ResourceDef::new("/user/{name}.html")); resource.name("index"); - let routes = vec![( - ResourceDef::new("index", "/user/{name}.html"), - Some(resource), - )]; - let (router, _) = Router::new("/prefix/", routes); + let mut router = Router::<()>::new(); + router.set_prefix("/prefix/"); + router.register_resource(resource); + let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); assert!(!info.has_route("/prefix/user/test.html")); @@ -488,10 +484,12 @@ mod tests { #[test] fn test_url_for_static() { - let mut resource = Resource::<()>::default(); + let mut resource = Resource::new(ResourceDef::new("/index.html")); resource.name("index"); - let routes = vec![(ResourceDef::new("index", "/index.html"), Some(resource))]; - let (router, _) = Router::new("/prefix/", routes); + let mut router = Router::<()>::new(); + router.set_prefix("/prefix/"); + router.register_resource(resource); + let info = router.default_route_info(); assert!(info.has_route("/index.html")); assert!(!info.has_route("/prefix/index.html")); @@ -508,13 +506,12 @@ mod tests { #[test] fn test_url_for_external() { - let mut resource = Resource::<()>::default(); - resource.name("index"); - let routes = vec![( - ResourceDef::external("youtube", "https://youtube.com/watch/{video_id}"), - None, - )]; - let router = Router::new::<()>("", routes).0; + let mut router = Router::<()>::new(); + router.register_external( + "youtube", + ResourceDef::external("https://youtube.com/watch/{video_id}"), + ); + let info = router.default_route_info(); assert!(!info.has_route("https://youtube.com/watch/unknown")); diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 3f0f7ef5..052e4da2 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -60,6 +60,7 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; use resource::Resource; +use router::ResourceDef; use server::Request; /// A set of errors that can occur during processing CORS @@ -515,7 +516,7 @@ pub struct CorsBuilder { methods: bool, error: Option, expose_hdrs: HashSet, - resources: Vec<(String, Resource)>, + resources: Vec>, app: Option>, } @@ -798,10 +799,10 @@ impl CorsBuilder { F: FnOnce(&mut Resource) -> R + 'static, { // add resource handler - let mut handler = Resource::default(); - f(&mut handler); + let mut resource = Resource::new(ResourceDef::new(path)); + f(&mut resource); - self.resources.push((path.to_owned(), handler)); + self.resources.push(resource); self } @@ -878,9 +879,9 @@ impl CorsBuilder { .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); // register resources - for (path, mut resource) in self.resources.drain(..) { + for mut resource in self.resources.drain(..) { cors.clone().register(&mut resource); - app.register_resource(&path, resource); + app.register_resource(resource); } app diff --git a/src/param.rs b/src/param.rs index c58d9e78..2704b60d 100644 --- a/src/param.rs +++ b/src/param.rs @@ -61,6 +61,10 @@ impl Params { self.tail = tail; } + pub(crate) fn set_url(&mut self, url: Url) { + self.url = url; + } + pub(crate) fn add(&mut self, name: Rc, value: ParamItem) { self.segments.push((name, value)); } @@ -99,6 +103,11 @@ impl Params { } } + /// Get unprocessed part of path + pub fn unprocessed(&self) -> &str { + &self.url.path()[(self.tail as usize)..] + } + /// Get matched `FromParam` compatible parameter by name. /// /// If keyed parameter is not available empty string is used as default diff --git a/src/pipeline.rs b/src/pipeline.rs index 66b2f29a..dbe9e58a 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -16,19 +16,11 @@ use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Response, Started}; use server::{HttpHandlerTask, Writer, WriterState}; -#[doc(hidden)] -#[derive(Debug, Clone, Copy)] -pub enum HandlerType { - Normal(usize), - Handler(usize), - Default, -} - #[doc(hidden)] pub trait PipelineHandler { fn encoding(&self) -> ContentEncoding; - fn handle(&self, &HttpRequest, HandlerType) -> AsyncResult; + fn handle(&self, &HttpRequest) -> AsyncResult; } #[doc(hidden)] @@ -99,7 +91,6 @@ impl PipelineInfo { impl> Pipeline { pub fn new( req: HttpRequest, mws: Rc>>>, handler: Rc, - htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { req, @@ -109,7 +100,7 @@ impl> Pipeline { disconnected: None, encoding: handler.encoding(), }; - let state = StartMiddlewares::init(&mut info, &mws, handler, htype); + let state = StartMiddlewares::init(&mut info, &mws, handler); Pipeline(info, state, mws) } @@ -204,7 +195,6 @@ type Fut = Box, Error = Error>>; /// Middlewares start executor struct StartMiddlewares { hnd: Rc, - htype: HandlerType, fut: Option, _s: PhantomData, } @@ -212,7 +202,6 @@ struct StartMiddlewares { impl> StartMiddlewares { fn init( info: &mut PipelineInfo, mws: &[Box>], hnd: Rc, - htype: HandlerType, ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately @@ -220,7 +209,7 @@ impl> StartMiddlewares { loop { if info.count == len { - let reply = hnd.handle(&info.req, htype); + let reply = hnd.handle(&info.req); return WaitingResponse::init(info, mws, reply); } else { match mws[info.count as usize].start(&info.req) { @@ -231,7 +220,6 @@ impl> StartMiddlewares { Ok(Started::Future(fut)) => { return PipelineState::Starting(StartMiddlewares { hnd, - htype, fut: Some(fut), _s: PhantomData, }) @@ -261,7 +249,7 @@ impl> StartMiddlewares { } loop { if info.count == len { - let reply = self.hnd.handle(&info.req, self.htype); + let reply = self.hnd.handle(&info.req); return Some(WaitingResponse::init(info, mws, reply)); } else { let res = mws[info.count as usize].start(&info.req); diff --git a/src/resource.rs b/src/resource.rs index 2af1029a..1bf8d88f 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::ops::Deref; use std::rc::Rc; use futures::Future; @@ -12,6 +12,7 @@ use httpresponse::HttpResponse; use middleware::Middleware; use pred; use route::Route; +use router::ResourceDef; #[derive(Copy, Clone)] pub(crate) struct RouteId(usize); @@ -37,40 +38,34 @@ pub(crate) struct RouteId(usize); /// .finish(); /// } pub struct Resource { - name: String, - state: PhantomData, + rdef: ResourceDef, routes: SmallVec<[Route; 3]>, middlewares: Rc>>>, } -impl Default for Resource { - fn default() -> Self { +impl Resource { + /// Create new resource with specified resource definition + pub fn new(rdef: ResourceDef) -> Self { Resource { - name: String::new(), - state: PhantomData, + rdef, routes: SmallVec::new(), middlewares: Rc::new(Vec::new()), } } -} -impl Resource { - pub(crate) fn default_not_found() -> Self { - Resource { - name: String::new(), - state: PhantomData, - routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()), - } + /// Name of the resource + pub(crate) fn get_name(&self) -> &str { + self.rdef.name() } /// Set resource name - pub fn name>(&mut self, name: T) { - self.name = name.into(); + pub fn name(&mut self, name: &str) { + self.rdef.set_name(name); } - pub(crate) fn get_name(&self) -> &str { - &self.name + /// Resource definition + pub fn rdef(&self) -> &ResourceDef { + &self.rdef } } @@ -303,3 +298,26 @@ impl Resource { } } } + +/// Default resource +pub struct DefaultResource(Rc>); + +impl Deref for DefaultResource { + type Target = Resource; + + fn deref(&self) -> &Resource { + self.0.as_ref() + } +} + +impl Clone for DefaultResource { + fn clone(&self) -> Self { + DefaultResource(self.0.clone()) + } +} + +impl From> for DefaultResource { + fn from(res: Resource) -> Self { + DefaultResource(Rc::new(res)) + } +} diff --git a/src/router.rs b/src/router.rs index fad51b15..8a6a263a 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,3 +1,4 @@ +use std::cmp::min; use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::rc::Rc; @@ -6,19 +7,43 @@ use regex::{escape, Regex}; use url::Url; use error::UrlGenerationError; +use handler::{AsyncResult, FromRequest, Responder, RouteHandler}; +use http::{Method, StatusCode}; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; use param::{ParamItem, Params}; -use resource::Resource; +use pred::Predicate; +use resource::{DefaultResource, Resource}; +use scope::Scope; use server::Request; #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) enum RouterResource { - Notset, + Default, Normal(u16), } -/// Interface for application router. -pub struct Router(Rc); +enum ResourcePattern { + Resource(ResourceDef), + Handler(ResourceDef, Option>>>), + Scope(ResourceDef, Vec>>), +} +enum ResourceItem { + Resource(Resource), + Handler(Box>), + Scope(Scope), +} + +/// Interface for application router. +pub struct Router { + defs: Rc, + patterns: Vec>, + resources: Vec>, + default: Option>, +} + +/// Information about current route #[derive(Clone)] pub struct RouteInfo { router: Rc, @@ -27,6 +52,16 @@ pub struct RouteInfo { } impl RouteInfo { + /// Name os the resource + #[inline] + pub fn name(&self) -> &str { + if let RouterResource::Normal(idx) = self.resource { + self.router.patterns[idx as usize].name() + } else { + "" + } + } + /// This method returns reference to matched `Resource` object. #[inline] pub fn resource(&self) -> Option<&ResourceDef> { @@ -49,18 +84,14 @@ impl RouteInfo { } #[inline] - pub(crate) fn merge(&self, params: &Params) -> RouteInfo { - let mut p = self.params.clone(); - p.set_tail(params.tail); - for item in ¶ms.segments { + pub(crate) fn merge(&mut self, info: &RouteInfo) { + let mut p = info.params.clone(); + p.set_tail(self.params.tail); + for item in &self.params.segments { p.add(item.0.clone(), item.1.clone()); } - RouteInfo { - params: p, - router: self.router.clone(), - resource: self.resource, - } + self.params = p; } /// Generate url for named resource @@ -75,7 +106,7 @@ impl RouteInfo { I: AsRef, { if let Some(pattern) = self.router.named.get(name) { - let path = pattern.0.resource_path(elements, &self.router.prefix)?; + let path = pattern.resource_path(elements, &self.router.prefix)?; if path.starts_with('/') { let conn = req.connection_info(); Ok(Url::parse(&format!( @@ -113,102 +144,264 @@ impl RouteInfo { struct Inner { prefix: String, prefix_len: usize, - named: HashMap, + named: HashMap, patterns: Vec, } -impl Router { - /// Create new router - pub fn new( - prefix: &str, map: Vec<(ResourceDef, Option>)>, - ) -> (Router, Vec>) { - let prefix = prefix.trim().trim_right_matches('/').to_owned(); - let mut named = HashMap::new(); - let mut patterns = Vec::new(); - let mut resources = Vec::new(); +impl Default for Router { + fn default() -> Self { + Router::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 { - patterns.push(pattern); - resources.push(resource); - } +impl Router { + pub(crate) fn new() -> Self { + Router { + defs: Rc::new(Inner { + prefix: String::new(), + prefix_len: 0, + named: HashMap::new(), + patterns: Vec::new(), + }), + resources: Vec::new(), + patterns: Vec::new(), + default: None, } - - let prefix_len = prefix.len(); - ( - Router(Rc::new(Inner { - prefix, - prefix_len, - named, - patterns, - })), - resources, - ) } /// Router prefix #[inline] pub fn prefix(&self) -> &str { - &self.0.prefix + &self.defs.prefix } + /// Set router prefix + #[inline] + pub fn set_prefix(&mut self, prefix: &str) { + let prefix = prefix.trim().trim_right_matches('/').to_owned(); + let inner = Rc::get_mut(&mut self.defs).unwrap(); + inner.prefix_len = prefix.len(); + inner.prefix = prefix; + } + + #[inline] + pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> RouteInfo { + RouteInfo { + params, + router: self.defs.clone(), + resource: RouterResource::Normal(idx), + } + } + + #[cfg(test)] pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> RouteInfo { let mut params = Params::with_url(req.url()); params.set_tail(prefix); RouteInfo { params, - router: self.0.clone(), - resource: RouterResource::Notset, - } - } - - pub(crate) fn route_info_params(&self, params: Params) -> RouteInfo { - RouteInfo { - params, - router: self.0.clone(), - resource: RouterResource::Notset, + router: self.defs.clone(), + resource: RouterResource::Default, } } + #[cfg(test)] pub(crate) fn default_route_info(&self) -> RouteInfo { RouteInfo { - router: self.0.clone(), - resource: RouterResource::Notset, params: Params::new(), + router: self.defs.clone(), + resource: RouterResource::Default, } } + pub(crate) fn register_resource(&mut self, resource: Resource) { + { + let inner = Rc::get_mut(&mut self.defs).unwrap(); + + let name = resource.get_name(); + if !name.is_empty() { + if inner.named.contains_key(name) { + panic!("Named resource {:?} is registered.", name); + } + inner.named.insert(name.to_owned(), resource.rdef().clone()); + } + inner.patterns.push(resource.rdef().clone()); + } + self.patterns + .push(ResourcePattern::Resource(resource.rdef().clone())); + self.resources.push(ResourceItem::Resource(resource)); + } + + pub(crate) fn register_scope(&mut self, mut scope: Scope) { + Rc::get_mut(&mut self.defs) + .unwrap() + .patterns + .push(scope.rdef().clone()); + let filters = scope.take_filters(); + self.patterns + .push(ResourcePattern::Scope(scope.rdef().clone(), filters)); + self.resources.push(ResourceItem::Scope(scope)); + } + + pub(crate) fn register_handler( + &mut self, path: &str, hnd: Box>, + filters: Option>>>, + ) { + let rdef = ResourceDef::prefix(path); + Rc::get_mut(&mut self.defs) + .unwrap() + .patterns + .push(rdef.clone()); + self.resources.push(ResourceItem::Handler(hnd)); + self.patterns.push(ResourcePattern::Handler(rdef, filters)); + } + + pub(crate) fn has_default_resource(&self) -> bool { + self.default.is_some() + } + + pub(crate) fn register_default_resource(&mut self, resource: DefaultResource) { + self.default = Some(resource); + } + + pub(crate) fn finish(&mut self) { + if let Some(ref default) = self.default { + for resource in &mut self.resources { + match resource { + ResourceItem::Resource(_) => (), + ResourceItem::Scope(scope) => { + if !scope.has_default_resource() { + scope.default_resource(default.clone()); + } + scope.finish() + } + ResourceItem::Handler(hnd) => { + if !hnd.has_default_resource() { + hnd.default_resource(default.clone()); + } + hnd.finish() + } + } + } + } + } + + pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { + let inner = Rc::get_mut(&mut self.defs).unwrap(); + if inner.named.contains_key(name) { + panic!("Named resource {:?} is registered.", name); + } + inner.named.insert(name.to_owned(), rdef); + } + + pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) + where + F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, + { + let out = { + // get resource handler + let mut iterator = self.resources.iter_mut(); + + loop { + if let Some(ref mut resource) = iterator.next() { + if let ResourceItem::Resource(ref mut resource) = resource { + if resource.rdef().pattern() == path { + resource.method(method).with(f); + break None; + } + } + } else { + let mut resource = Resource::new(ResourceDef::new(path)); + resource.method(method).with(f); + break Some(resource); + } + } + }; + if let Some(out) = out { + self.register_resource(out); + } + } + + /// Handle request + pub fn handle(&self, req: &HttpRequest) -> AsyncResult { + let resource = match req.route().resource { + RouterResource::Normal(idx) => &self.resources[idx as usize], + RouterResource::Default => { + if let Some(ref default) = self.default { + if let Some(id) = default.get_route_id(req) { + return default.handle(id, req); + } + } + return AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)); + } + }; + match resource { + ResourceItem::Resource(ref resource) => { + if let Some(id) = resource.get_route_id(req) { + return resource.handle(id, req); + } + + if let Some(ref default) = self.default { + if let Some(id) = default.get_route_id(req) { + return default.handle(id, req); + } + } + } + ResourceItem::Handler(hnd) => return hnd.handle(req), + ResourceItem::Scope(hnd) => return hnd.handle(req), + } + AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) + } + /// Query for matched resource - pub fn recognize(&self, req: &Request) -> Option<(usize, RouteInfo)> { - if self.0.prefix_len > req.path().len() { - return None; - } - for (idx, pattern) in self.0.patterns.iter().enumerate() { - if let Some(params) = pattern.match_with_params(req, self.0.prefix_len, true) - { - return Some(( - idx, - RouteInfo { - params, - router: self.0.clone(), - resource: RouterResource::Normal(idx as u16), - }, - )); + pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> RouteInfo { + self.match_with_params(req, state, tail, true) + } + + /// Query for matched resource + pub(crate) fn match_with_params( + &self, req: &Request, state: &S, tail: usize, insert: bool, + ) -> RouteInfo { + if tail <= req.path().len() { + 'outer: for (idx, resource) in self.patterns.iter().enumerate() { + match resource { + ResourcePattern::Resource(rdef) => { + if let Some(params) = rdef.match_with_params(req, tail, insert) { + return self.route_info_params(idx as u16, params); + } + } + ResourcePattern::Handler(rdef, filters) => { + if let Some(params) = rdef.match_prefix_with_params(req, tail) { + if let Some(ref filters) = filters { + for filter in filters { + if !filter.check(req, state) { + continue 'outer; + } + } + } + return self.route_info_params(idx as u16, params); + } + } + ResourcePattern::Scope(rdef, filters) => { + if let Some(params) = rdef.match_prefix_with_params(req, tail) { + for filter in filters { + if !filter.check(req, state) { + continue 'outer; + } + } + return self.route_info_params(idx as u16, params); + } + } + } } } - None - } -} - -impl Clone for Router { - fn clone(&self) -> Router { - Router(Rc::clone(&self.0)) + RouteInfo { + params: Params::new(), + router: self.defs.clone(), + resource: RouterResource::Default, + } } } @@ -252,8 +445,8 @@ impl ResourceDef { /// Parse path pattern and create new `Resource` instance. /// /// Panics if path pattern is wrong. - pub fn new(name: &str, path: &str) -> Self { - ResourceDef::with_prefix(name, path, "/", false) + pub fn new(path: &str) -> Self { + ResourceDef::with_prefix(path, "/", false) } /// Parse path pattern and create new `Resource` instance. @@ -261,21 +454,21 @@ impl ResourceDef { /// Use `prefix` type instead of `static`. /// /// Panics if path regex pattern is wrong. - pub fn prefix(name: &str, path: &str) -> Self { - ResourceDef::with_prefix(name, path, "/", true) + pub fn prefix(path: &str) -> Self { + ResourceDef::with_prefix(path, "/", true) } /// Construct external resource /// /// Panics if path pattern is wrong. - pub fn external(name: &str, path: &str) -> Self { - let mut resource = ResourceDef::with_prefix(name, path, "/", false); + pub fn external(path: &str) -> Self { + let mut resource = ResourceDef::with_prefix(path, "/", false); resource.rtp = ResourceType::External; resource } /// Parse path pattern and create new `Resource` instance with custom prefix - pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self { + pub fn with_prefix(path: &str, prefix: &str, for_prefix: bool) -> Self { let (pattern, elements, is_dynamic, len) = ResourceDef::parse(path, prefix, for_prefix); @@ -299,20 +492,25 @@ impl ResourceDef { ResourceDef { tp, elements, - name: name.into(), + name: "".to_string(), rtp: ResourceType::Normal, pattern: path.to_owned(), } } - /// Name of the resource + /// Resource type + pub fn rtype(&self) -> ResourceType { + self.rtp + } + + /// Resource name pub fn name(&self) -> &str { &self.name } - /// Resource type - pub fn rtype(&self) -> ResourceType { - self.rtp + /// Resource name + pub(crate) fn set_name(&mut self, name: &str) { + self.name = name.to_owned(); } /// Path pattern of the resource @@ -443,7 +641,7 @@ impl ResourceDef { return None; }; let mut params = Params::with_url(req.url()); - params.set_tail((plen + len) as u16); + params.set_tail(min(req.path().len(), plen + len) as u16); Some(params) } } @@ -592,184 +790,152 @@ mod tests { #[test] fn test_recognizer10() { - let routes = vec![ - (ResourceDef::new("", "/name"), Some(Resource::default())), - ( - ResourceDef::new("", "/name/{val}"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/name/{val}/index.html"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/file/{file}.{ext}"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/v{val}/{val2}/index.html"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/v/{tail:.*}"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/test2/{test}.html"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "{test}/index.html"), - Some(Resource::default()), - ), - ]; - let (rec, _) = Router::new::<()>("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/name"))); + router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); + router.register_resource(Resource::new(ResourceDef::new( + "/name/{val}/index.html", + ))); + router.register_resource(Resource::new(ResourceDef::new("/file/{file}.{ext}"))); + router.register_resource(Resource::new(ResourceDef::new( + "/v{val}/{val2}/index.html", + ))); + router.register_resource(Resource::new(ResourceDef::new("/v/{tail:.*}"))); + router.register_resource(Resource::new(ResourceDef::new("/test2/{test}.html"))); + router.register_resource(Resource::new(ResourceDef::new("{test}/index.html"))); let req = TestRequest::with_uri("/name").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 0); - assert!(req.match_info().is_empty()); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(0)); + assert!(info.match_info().is_empty()); let req = TestRequest::with_uri("/name/value").finish(); - let info = rec.recognize(&req).unwrap().1; - let req = req.with_route_info(info); - assert_eq!(req.match_info().get("val").unwrap(), "value"); - assert_eq!(&req.match_info()["val"], "value"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.match_info().get("val").unwrap(), "value"); + assert_eq!(&info.match_info()["val"], "value"); let req = TestRequest::with_uri("/name/value2/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 2); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("val").unwrap(), "value2"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(2)); + assert_eq!(info.match_info().get("val").unwrap(), "value2"); let req = TestRequest::with_uri("/file/file.gz").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 3); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("file").unwrap(), "file"); - assert_eq!(req.match_info().get("ext").unwrap(), "gz"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(3)); + assert_eq!(info.match_info().get("file").unwrap(), "file"); + assert_eq!(info.match_info().get("ext").unwrap(), "gz"); let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 4); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("val").unwrap(), "test"); - assert_eq!(req.match_info().get("val2").unwrap(), "ttt"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(4)); + assert_eq!(info.match_info().get("val").unwrap(), "test"); + assert_eq!(info.match_info().get("val2").unwrap(), "ttt"); let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 5); - let req = req.with_route_info(info.1); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(5)); assert_eq!( - req.match_info().get("tail").unwrap(), + info.match_info().get("tail").unwrap(), "blah-blah/index.html" ); let req = TestRequest::with_uri("/test2/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 6); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("test").unwrap(), "index"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(6)); + assert_eq!(info.match_info().get("test").unwrap(), "index"); let req = TestRequest::with_uri("/bbb/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 7); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("test").unwrap(), "bbb"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(7)); + assert_eq!(info.match_info().get("test").unwrap(), "bbb"); } #[test] fn test_recognizer_2() { - let routes = vec![ - ( - ResourceDef::new("", "/index.json"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/{source}.json"), - Some(Resource::default()), - ), - ]; - let (rec, _) = Router::new::<()>("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/index.json"))); + router.register_resource(Resource::new(ResourceDef::new("/{source}.json"))); let req = TestRequest::with_uri("/index.json").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 0); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(0)); let req = TestRequest::with_uri("/test.json").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 1); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(1)); } #[test] fn test_recognizer_with_prefix() { - let routes = vec![ - (ResourceDef::new("", "/name"), Some(Resource::default())), - ( - ResourceDef::new("", "/name/{val}"), - Some(Resource::default()), - ), - ]; - let (rec, _) = Router::new::<()>("/test", routes); + let mut router = Router::<()>::new(); + router.set_prefix("/test"); + router.register_resource(Resource::new(ResourceDef::new("/name"))); + router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); let req = TestRequest::with_uri("/name").finish(); - assert!(rec.recognize(&req).is_none()); + let info = router.recognize(&req, &(), 5); + assert_eq!(info.resource, RouterResource::Default); let req = TestRequest::with_uri("/test/name").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 0); + let info = router.recognize(&req, &(), 5); + assert_eq!(info.resource, RouterResource::Normal(0)); let req = TestRequest::with_uri("/test/name/value").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 1); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("val").unwrap(), "value"); - assert_eq!(&req.match_info()["val"], "value"); + let info = router.recognize(&req, &(), 5); + assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.match_info().get("val").unwrap(), "value"); + assert_eq!(&info.match_info()["val"], "value"); // same patterns - let routes = vec![ - (ResourceDef::new("", "/name"), Some(Resource::default())), - ( - ResourceDef::new("", "/name/{val}"), - Some(Resource::default()), - ), - ]; - let (rec, _) = Router::new::<()>("/test2", routes); + let mut router = Router::<()>::new(); + router.set_prefix("/test2"); + router.register_resource(Resource::new(ResourceDef::new("/name"))); + router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); let req = TestRequest::with_uri("/name").finish(); - assert!(rec.recognize(&req).is_none()); + let info = router.recognize(&req, &(), 6); + assert_eq!(info.resource, RouterResource::Default); + let req = TestRequest::with_uri("/test2/name").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 0); + let info = router.recognize(&req, &(), 6); + assert_eq!(info.resource, RouterResource::Normal(0)); + let req = TestRequest::with_uri("/test2/name-test").finish(); - assert!(rec.recognize(&req).is_none()); + let info = router.recognize(&req, &(), 6); + assert_eq!(info.resource, RouterResource::Default); + let req = TestRequest::with_uri("/test2/name/ttt").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 1); - let req = req.with_route_info(info.1); - assert_eq!(&req.match_info()["val"], "ttt"); + let info = router.recognize(&req, &(), 6); + assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(&info.match_info()["val"], "ttt"); } #[test] fn test_parse_static() { - let re = ResourceDef::new("test", "/"); + let re = ResourceDef::new("/"); assert!(re.is_match("/")); assert!(!re.is_match("/a")); - let re = ResourceDef::new("test", "/name"); + let re = ResourceDef::new("/name"); assert!(re.is_match("/name")); assert!(!re.is_match("/name1")); assert!(!re.is_match("/name/")); assert!(!re.is_match("/name~")); - let re = ResourceDef::new("test", "/name/"); + let re = ResourceDef::new("/name/"); assert!(re.is_match("/name/")); assert!(!re.is_match("/name")); assert!(!re.is_match("/name/gs")); - let re = ResourceDef::new("test", "/user/profile"); + let re = ResourceDef::new("/user/profile"); assert!(re.is_match("/user/profile")); assert!(!re.is_match("/user/profile/profile")); } #[test] fn test_parse_param() { - let re = ResourceDef::new("test", "/user/{id}"); + let re = ResourceDef::new("/user/{id}"); assert!(re.is_match("/user/profile")); assert!(re.is_match("/user/2345")); assert!(!re.is_match("/user/2345/")); @@ -783,7 +949,7 @@ mod tests { let info = re.match_with_params(&req, 0, true).unwrap(); assert_eq!(info.get("id").unwrap(), "1245125"); - let re = ResourceDef::new("test", "/v{version}/resource/{id}"); + let re = ResourceDef::new("/v{version}/resource/{id}"); assert!(re.is_match("/v1/resource/320120")); assert!(!re.is_match("/v/resource/1")); assert!(!re.is_match("/resource")); @@ -796,14 +962,14 @@ mod tests { #[test] fn test_resource_prefix() { - let re = ResourceDef::prefix("test", "/name"); + let re = ResourceDef::prefix("/name"); assert!(re.is_match("/name")); assert!(re.is_match("/name/")); assert!(re.is_match("/name/test/test")); assert!(re.is_match("/name1")); assert!(re.is_match("/name~")); - let re = ResourceDef::prefix("test", "/name/"); + let re = ResourceDef::prefix("/name/"); assert!(re.is_match("/name/")); assert!(re.is_match("/name/gs")); assert!(!re.is_match("/name")); @@ -811,7 +977,7 @@ mod tests { #[test] fn test_reousrce_prefix_dynamic() { - let re = ResourceDef::prefix("test", "/{name}/"); + let re = ResourceDef::prefix("/{name}/"); assert!(re.is_match("/name/")); assert!(re.is_match("/name/gs")); assert!(!re.is_match("/name")); @@ -829,28 +995,23 @@ mod tests { #[test] fn test_request_resource() { - let routes = vec![ - ( - ResourceDef::new("r1", "/index.json"), - Some(Resource::default()), - ), - ( - ResourceDef::new("r2", "/test.json"), - Some(Resource::default()), - ), - ]; - let (router, _) = Router::new::<()>("", routes); + let mut router = Router::<()>::new(); + let mut resource = Resource::new(ResourceDef::new("/index.json")); + resource.name("r1"); + router.register_resource(resource); + let mut resource = Resource::new(ResourceDef::new("/test.json")); + resource.name("r2"); + router.register_resource(resource); let req = TestRequest::with_uri("/index.json").finish(); - assert_eq!(router.recognize(&req).unwrap().0, 0); - let info = router.recognize(&req).unwrap().1; - let resource = info.resource().unwrap(); - assert_eq!(resource.name(), "r1"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(0)); + + assert_eq!(info.name(), "r1"); let req = TestRequest::with_uri("/test.json").finish(); - assert_eq!(router.recognize(&req).unwrap().0, 1); - let info = router.recognize(&req).unwrap().1; - let resource = info.resource().unwrap(); - assert_eq!(resource.name(), "r2"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.name(), "r2"); } } diff --git a/src/scope.rs b/src/scope.rs index a4b4307c..94dbd860 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -6,7 +6,7 @@ use futures::{Async, Future, Poll}; use error::Error; use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler}; -use http::{Method, StatusCode}; +use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{ @@ -14,15 +14,10 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; -use resource::{Resource, RouteId}; -use router::ResourceDef; +use resource::{DefaultResource, Resource}; +use router::{ResourceDef, Router}; use server::Request; -type ScopeResource = Rc>; -type Route = Box>; -type ScopeResources = Rc)>>; -type NestedInfo = (ResourceDef, Route, Vec>>); - /// Resources scope /// /// Scope is a set of resources with common root path. @@ -53,29 +48,31 @@ type NestedInfo = (ResourceDef, Route, Vec>>); /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests /// -#[derive(Default)] -pub struct Scope { +pub struct Scope { + rdef: ResourceDef, + router: Rc>, filters: Vec>>, - nested: Vec>, middlewares: Rc>>>, - default: Option>, - resources: ScopeResources, } #[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] impl Scope { /// Create a new scope // TODO: Why is this not exactly the default impl? - pub fn new() -> Scope { + pub fn new(path: &str) -> Scope { Scope { + rdef: ResourceDef::prefix(path), + router: Rc::new(Router::new()), filters: Vec::new(), - nested: Vec::new(), - resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), - default: None, } } + #[inline] + pub(crate) fn rdef(&self) -> &ResourceDef { + &self.rdef + } + #[inline] pub(crate) fn take_filters(&mut self) -> Vec>> { mem::replace(&mut self.filters, Vec::new()) @@ -132,11 +129,10 @@ impl Scope { F: FnOnce(Scope) -> Scope, { let scope = Scope { + rdef: ResourceDef::prefix(path), filters: Vec::new(), - nested: Vec::new(), - resources: Rc::new(Vec::new()), + router: Rc::new(Router::new()), middlewares: Rc::new(Vec::new()), - default: None, }; let mut scope = f(scope); @@ -146,8 +142,12 @@ impl Scope { filters: scope.take_filters(), })]; let handler = Box::new(Wrapper { scope, state }); - self.nested - .push((ResourceDef::prefix("", &path), handler, filters)); + + Rc::get_mut(&mut self.router).unwrap().register_handler( + path, + handler, + Some(filters), + ); self } @@ -175,17 +175,14 @@ impl Scope { F: FnOnce(Scope) -> Scope, { let scope = Scope { + rdef: ResourceDef::prefix(&path), filters: Vec::new(), - nested: Vec::new(), - resources: Rc::new(Vec::new()), + router: Rc::new(Router::new()), middlewares: Rc::new(Vec::new()), - default: None, }; - let mut scope = f(scope); - - let filters = scope.take_filters(); - self.nested - .push((ResourceDef::prefix("", &path), Box::new(scope), filters)); + Rc::get_mut(&mut self.router) + .unwrap() + .register_scope(f(scope)); self } @@ -223,39 +220,9 @@ impl Scope { R: Responder + 'static, T: FromRequest + 'static, { - // check if we have resource handler - let mut found = false; - for &(ref pattern, _) in self.resources.iter() { - if pattern.pattern() == path { - found = true; - break; - } - } - - if found { - let resources = Rc::get_mut(&mut self.resources) - .expect("Multiple scope references are not allowed"); - for &mut (ref pattern, ref mut resource) in resources.iter_mut() { - if pattern.pattern() == path { - let res = Rc::get_mut(resource) - .expect("Multiple scope references are not allowed"); - res.method(method).with(f); - break; - } - } - } else { - let mut handler = Resource::default(); - handler.method(method).with(f); - let pattern = ResourceDef::with_prefix( - handler.get_name(), - path, - if path.is_empty() { "" } else { "/" }, - false, - ); - Rc::get_mut(&mut self.resources) - .expect("Can not use after configuration") - .push((pattern, Rc::new(handler))); - } + Rc::get_mut(&mut self.router) + .unwrap() + .register_route(path, method, f); self } @@ -286,20 +253,18 @@ impl Scope { where F: FnOnce(&mut Resource) -> R + 'static, { - // add resource handler - let mut handler = Resource::default(); - f(&mut handler); - + // add resource let pattern = ResourceDef::with_prefix( - handler.get_name(), path, if path.is_empty() { "" } else { "/" }, false, ); - Rc::get_mut(&mut self.resources) - .expect("Can not use after configuration") - .push((pattern, Rc::new(handler))); + let mut resource = Resource::new(pattern); + f(&mut resource); + Rc::get_mut(&mut self.router) + .unwrap() + .register_resource(resource); self } @@ -308,14 +273,14 @@ impl Scope { where F: FnOnce(&mut Resource) -> R + 'static, { - if self.default.is_none() { - self.default = Some(Rc::new(Resource::default_not_found())); - } - { - let default = Rc::get_mut(self.default.as_mut().unwrap()) - .expect("Multiple copies of default handler"); - f(default); - } + // create and configure default resource + let mut resource = Resource::new(ResourceDef::new("")); + f(&mut resource); + + Rc::get_mut(&mut self.router) + .expect("Multiple copies of scope router") + .register_default_resource(resource.into()); + self } @@ -339,64 +304,33 @@ impl RouteHandler for Scope { let tail = req.match_info().tail as usize; // recognize resources - for &(ref pattern, ref resource) in self.resources.iter() { - if let Some(params) = pattern.match_with_params(req, tail, false) { - let req2 = req.with_route_info(req.route().merge(¶ms)); - if let Some(id) = resource.get_route_id(&req2) { - if self.middlewares.is_empty() { - return resource.handle(id, &req2); - } else { - return AsyncResult::async(Box::new(Compose::new( - id, - req2, - Rc::clone(&self.middlewares), - Rc::clone(&resource), - ))); - } - } - } + let info = self.router.match_with_params(req, req.state(), tail, false); + let req2 = req.with_route_info(info); + if self.middlewares.is_empty() { + self.router.handle(&req2) + } else { + AsyncResult::async(Box::new(Compose::new( + req2, + Rc::clone(&self.router), + Rc::clone(&self.middlewares), + ))) } - - // nested scopes - 'outer: for &(ref prefix, ref handler, ref filters) in &self.nested { - if let Some(params) = prefix.match_prefix_with_params(req, tail) { - let req2 = req.with_route_info(req.route().merge(¶ms)); - - let state = req.state(); - for filter in filters { - if !filter.check(&req2, state) { - continue 'outer; - } - } - return handler.handle(&req2); - } - } - - // default handler - if let Some(ref resource) = self.default { - if let Some(id) = resource.get_route_id(req) { - if self.middlewares.is_empty() { - return resource.handle(id, req); - } else { - return AsyncResult::async(Box::new(Compose::new( - id, - req.clone(), - Rc::clone(&self.middlewares), - Rc::clone(resource), - ))); - } - } - } - - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) } fn has_default_resource(&self) -> bool { - self.default.is_some() + self.router.has_default_resource() } - fn default_resource(&mut self, default: ScopeResource) { - self.default = Some(default); + fn default_resource(&mut self, default: DefaultResource) { + Rc::get_mut(&mut self.router) + .expect("Can not use after configuration") + .register_default_resource(default); + } + + fn finish(&mut self) { + Rc::get_mut(&mut self.router) + .expect("Can not use after configuration") + .finish(); } } @@ -436,10 +370,9 @@ struct Compose { struct ComposeInfo { count: usize, - id: RouteId, req: HttpRequest, + router: Rc>, mws: Rc>>>, - resource: Rc>, } enum ComposeState { @@ -464,14 +397,12 @@ impl ComposeState { impl Compose { fn new( - id: RouteId, req: HttpRequest, mws: Rc>>>, - resource: Rc>, + req: HttpRequest, router: Rc>, mws: Rc>>>, ) -> Self { let mut info = ComposeInfo { - id, mws, req, - resource, + router, count: 0, }; let state = StartMiddlewares::init(&mut info); @@ -513,7 +444,7 @@ impl StartMiddlewares { loop { if info.count == len { - let reply = info.resource.handle(info.id, &info.req); + let reply = info.router.handle(&info.req); return WaitingResponse::init(info, reply); } else { let result = info.mws[info.count].start(&info.req); @@ -552,7 +483,7 @@ impl StartMiddlewares { } loop { if info.count == len { - let reply = { info.resource.handle(info.id, &info.req) }; + let reply = info.router.handle(&info.req); return Some(WaitingResponse::init(info, reply)); } else { let result = info.mws[info.count].start(&info.req); @@ -979,9 +910,9 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + //let req = TestRequest::with_uri("/app/t1").request(); + //let resp = app.run(req); + //assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); diff --git a/src/test.rs b/src/test.rs index 4289bca8..909d15f3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -535,11 +535,11 @@ impl TestRequest { uri, version, headers, - params, + mut params, cookies, payload, } = self; - let (router, _) = Router::new::("/", Vec::new()); + let router = Router::<()>::new(); let pool = RequestPool::pool(ServerSettings::default()); let mut req = RequestPool::get(pool); @@ -551,23 +551,24 @@ impl TestRequest { inner.headers = headers; *inner.payload.borrow_mut() = payload; } + params.set_url(req.url().clone()); let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(params)); + HttpRequest::new(req, Rc::new(state), router.route_info_params(0, params)); req.set_cookies(cookies); req } #[cfg(test)] /// Complete request creation and generate `HttpRequest` instance - pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest { + pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest { let TestRequest { state, method, uri, version, headers, - params, + mut params, cookies, payload, } = self; @@ -582,8 +583,9 @@ impl TestRequest { inner.headers = headers; *inner.payload.borrow_mut() = payload; } + params.set_url(req.url().clone()); let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(params)); + HttpRequest::new(req, Rc::new(state), router.route_info_params(0, params)); req.set_cookies(cookies); req } From 9570c1cccd25bdf58f6d879e29096d6f0cd6a435 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 15:24:27 +0600 Subject: [PATCH 0499/1635] rename RouteInfo --- src/helpers.rs | 10 +++--- src/httprequest.rs | 34 ++++++++---------- src/lib.rs | 2 +- src/router.rs | 90 +++++++++++++++++++++++----------------------- 4 files changed, 66 insertions(+), 70 deletions(-) diff --git a/src/helpers.rs b/src/helpers.rs index 50a9bcf6..85247123 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -92,7 +92,7 @@ impl Handler for NormalizePath { // merge slashes let p = self.re_merge.replace_all(req.path(), "/"); if p.len() != req.path().len() { - if req.route().has_route(p.as_ref()) { + if req.resource().has_route(p.as_ref()) { let p = if !query.is_empty() { p + "?" + query } else { @@ -105,7 +105,7 @@ impl Handler for NormalizePath { // merge slashes and append trailing slash if self.append && !p.ends_with('/') { let p = p.as_ref().to_owned() + "/"; - if req.route().has_route(&p) { + if req.resource().has_route(&p) { let p = if !query.is_empty() { p + "?" + query } else { @@ -120,7 +120,7 @@ impl Handler for NormalizePath { // try to remove trailing slash if p.ends_with('/') { let p = p.as_ref().trim_right_matches('/'); - if req.route().has_route(p) { + if req.resource().has_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -135,7 +135,7 @@ impl Handler for NormalizePath { } else if p.ends_with('/') { // try to remove trailing slash let p = p.as_ref().trim_right_matches('/'); - if req.route().has_route(p) { + if req.resource().has_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -151,7 +151,7 @@ impl Handler for NormalizePath { // append trailing slash if self.append && !req.path().ends_with('/') { let p = req.path().to_owned() + "/"; - if req.route().has_route(&p) { + if req.resource().has_route(&p) { let p = if !query.is_empty() { p + "?" + query } else { diff --git a/src/httprequest.rs b/src/httprequest.rs index a0497338..91ee9eb1 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -20,7 +20,7 @@ use httpresponse::{HttpResponse, HttpResponseBuilder}; use info::ConnectionInfo; use param::Params; use payload::Payload; -use router::{ResourceDef, RouteInfo}; +use router::ResourceInfo; use server::Request; struct Query(HashMap); @@ -30,7 +30,7 @@ struct Cookies(Vec>); pub struct HttpRequest { req: Option, state: Rc, - route: RouteInfo, + resource: ResourceInfo, } impl HttpMessage for HttpRequest { @@ -61,10 +61,12 @@ impl Deref for HttpRequest { impl HttpRequest { #[inline] - pub(crate) fn new(req: Request, state: Rc, route: RouteInfo) -> HttpRequest { + pub(crate) fn new( + req: Request, state: Rc, resource: ResourceInfo, + ) -> HttpRequest { HttpRequest { state, - route, + resource, req: Some(req), } } @@ -75,17 +77,17 @@ impl HttpRequest { HttpRequest { state, req: self.req.as_ref().map(|r| r.clone()), - route: self.route.clone(), + resource: self.resource.clone(), } } #[inline] /// Construct new http request with new RouteInfo. - pub(crate) fn with_route_info(&self, mut route: RouteInfo) -> HttpRequest { - route.merge(&self.route); + pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest { + resource.merge(&self.resource); HttpRequest { - route, + resource, req: self.req.as_ref().map(|r| r.clone()), state: self.state.clone(), } @@ -193,7 +195,7 @@ impl HttpRequest { U: IntoIterator, I: AsRef, { - self.route.url_for(&self, name, elements) + self.resource.url_for(&self, name, elements) } /// Generate url for named resource @@ -207,14 +209,8 @@ impl HttpRequest { /// This method returns reference to current `RouteInfo` object. #[inline] - pub fn route(&self) -> &RouteInfo { - &self.route - } - - /// This method returns reference to matched `Resource` object. - #[inline] - pub fn resource(&self) -> Option<&ResourceDef> { - self.route.resource() + pub fn resource(&self) -> &ResourceInfo { + &self.resource } /// Peer socket address @@ -300,7 +296,7 @@ impl HttpRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Params { - &self.route.match_info() + &self.resource.match_info() } /// Check if request requires connection upgrade @@ -331,7 +327,7 @@ impl Clone for HttpRequest { HttpRequest { req: self.req.as_ref().map(|r| r.clone()), state: self.state.clone(), - route: self.route.clone(), + resource: self.resource.clone(), } } } diff --git a/src/lib.rs b/src/lib.rs index d61c94f3..34dcd718 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,7 +246,7 @@ pub mod dev { pub use param::{FromParam, Params}; pub use resource::Resource; pub use route::Route; - pub use router::{ResourceDef, ResourceType, Router}; + pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; } pub mod http { diff --git a/src/router.rs b/src/router.rs index 8a6a263a..d93ec9eb 100644 --- a/src/router.rs +++ b/src/router.rs @@ -18,7 +18,7 @@ use scope::Scope; use server::Request; #[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum RouterResource { +pub(crate) enum ResourceId { Default, Normal(u16), } @@ -43,29 +43,29 @@ pub struct Router { default: Option>, } -/// Information about current route +/// Information about current resource #[derive(Clone)] -pub struct RouteInfo { +pub struct ResourceInfo { router: Rc, - resource: RouterResource, + resource: ResourceId, params: Params, } -impl RouteInfo { +impl ResourceInfo { /// Name os the resource #[inline] pub fn name(&self) -> &str { - if let RouterResource::Normal(idx) = self.resource { + if let ResourceId::Normal(idx) = self.resource { self.router.patterns[idx as usize].name() } else { "" } } - /// This method returns reference to matched `Resource` object. + /// This method returns reference to matched `ResourceDef` object. #[inline] - pub fn resource(&self) -> Option<&ResourceDef> { - if let RouterResource::Normal(idx) = self.resource { + pub fn rdef(&self) -> Option<&ResourceDef> { + if let ResourceId::Normal(idx) = self.resource { Some(&self.router.patterns[idx as usize]) } else { None @@ -84,7 +84,7 @@ impl RouteInfo { } #[inline] - pub(crate) fn merge(&mut self, info: &RouteInfo) { + pub(crate) fn merge(&mut self, info: &ResourceInfo) { let mut p = info.params.clone(); p.set_tail(self.params.tail); for item in &self.params.segments { @@ -185,32 +185,32 @@ impl Router { } #[inline] - pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> RouteInfo { - RouteInfo { + pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> ResourceInfo { + ResourceInfo { params, router: self.defs.clone(), - resource: RouterResource::Normal(idx), + resource: ResourceId::Normal(idx), } } #[cfg(test)] - pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> RouteInfo { + pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> ResourceInfo { let mut params = Params::with_url(req.url()); params.set_tail(prefix); - RouteInfo { + ResourceInfo { params, router: self.defs.clone(), - resource: RouterResource::Default, + resource: ResourceId::Default, } } #[cfg(test)] - pub(crate) fn default_route_info(&self) -> RouteInfo { - RouteInfo { + pub(crate) fn default_route_info(&self) -> ResourceInfo { + ResourceInfo { params: Params::new(), router: self.defs.clone(), - resource: RouterResource::Default, + resource: ResourceId::Default, } } @@ -326,9 +326,9 @@ impl Router { /// Handle request pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - let resource = match req.route().resource { - RouterResource::Normal(idx) => &self.resources[idx as usize], - RouterResource::Default => { + let resource = match req.resource().resource { + ResourceId::Normal(idx) => &self.resources[idx as usize], + ResourceId::Default => { if let Some(ref default) = self.default { if let Some(id) = default.get_route_id(req) { return default.handle(id, req); @@ -356,14 +356,14 @@ impl Router { } /// Query for matched resource - pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> RouteInfo { + pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> ResourceInfo { self.match_with_params(req, state, tail, true) } /// Query for matched resource pub(crate) fn match_with_params( &self, req: &Request, state: &S, tail: usize, insert: bool, - ) -> RouteInfo { + ) -> ResourceInfo { if tail <= req.path().len() { 'outer: for (idx, resource) in self.patterns.iter().enumerate() { match resource { @@ -397,10 +397,10 @@ impl Router { } } } - RouteInfo { + ResourceInfo { params: Params::new(), router: self.defs.clone(), - resource: RouterResource::Default, + resource: ResourceId::Default, } } } @@ -806,35 +806,35 @@ mod tests { let req = TestRequest::with_uri("/name").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); assert!(info.match_info().is_empty()); let req = TestRequest::with_uri("/name/value").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(info.match_info().get("val").unwrap(), "value"); assert_eq!(&info.match_info()["val"], "value"); let req = TestRequest::with_uri("/name/value2/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(2)); + assert_eq!(info.resource, ResourceId::Normal(2)); assert_eq!(info.match_info().get("val").unwrap(), "value2"); let req = TestRequest::with_uri("/file/file.gz").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(3)); + assert_eq!(info.resource, ResourceId::Normal(3)); assert_eq!(info.match_info().get("file").unwrap(), "file"); assert_eq!(info.match_info().get("ext").unwrap(), "gz"); let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(4)); + assert_eq!(info.resource, ResourceId::Normal(4)); assert_eq!(info.match_info().get("val").unwrap(), "test"); assert_eq!(info.match_info().get("val2").unwrap(), "ttt"); let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(5)); + assert_eq!(info.resource, ResourceId::Normal(5)); assert_eq!( info.match_info().get("tail").unwrap(), "blah-blah/index.html" @@ -842,12 +842,12 @@ mod tests { let req = TestRequest::with_uri("/test2/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(6)); + assert_eq!(info.resource, ResourceId::Normal(6)); assert_eq!(info.match_info().get("test").unwrap(), "index"); let req = TestRequest::with_uri("/bbb/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(7)); + assert_eq!(info.resource, ResourceId::Normal(7)); assert_eq!(info.match_info().get("test").unwrap(), "bbb"); } @@ -859,11 +859,11 @@ mod tests { let req = TestRequest::with_uri("/index.json").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); let req = TestRequest::with_uri("/test.json").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); } #[test] @@ -875,15 +875,15 @@ mod tests { let req = TestRequest::with_uri("/name").finish(); let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, RouterResource::Default); + assert_eq!(info.resource, ResourceId::Default); let req = TestRequest::with_uri("/test/name").finish(); let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); let req = TestRequest::with_uri("/test/name/value").finish(); let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(info.match_info().get("val").unwrap(), "value"); assert_eq!(&info.match_info()["val"], "value"); @@ -895,19 +895,19 @@ mod tests { let req = TestRequest::with_uri("/name").finish(); let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, RouterResource::Default); + assert_eq!(info.resource, ResourceId::Default); let req = TestRequest::with_uri("/test2/name").finish(); let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); let req = TestRequest::with_uri("/test2/name-test").finish(); let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, RouterResource::Default); + assert_eq!(info.resource, ResourceId::Default); let req = TestRequest::with_uri("/test2/name/ttt").finish(); let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(&info.match_info()["val"], "ttt"); } @@ -1005,13 +1005,13 @@ mod tests { let req = TestRequest::with_uri("/index.json").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); assert_eq!(info.name(), "r1"); let req = TestRequest::with_uri("/test.json").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(info.name(), "r2"); } } From b759dddf5a40aa52b693b99fb512d2139f0bf9c1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 15:50:56 +0600 Subject: [PATCH 0500/1635] simplify application prefix impl --- src/application.rs | 28 +++++++++++++--------------- src/router.rs | 32 +++++++------------------------- src/scope.rs | 2 +- 3 files changed, 21 insertions(+), 41 deletions(-) diff --git a/src/application.rs b/src/application.rs index 80ba7f52..ebf441ec 100644 --- a/src/application.rs +++ b/src/application.rs @@ -58,10 +58,14 @@ impl HttpHandler for HttpApplication { fn handle(&self, msg: Request) -> Result>, Request> { let m = { - let path = msg.path(); - path.starts_with(&self.prefix) - && (path.len() == self.prefix_len - || path.split_at(self.prefix_len).1.starts_with('/')) + if self.prefix_len == 0 { + true + } else { + let path = msg.path(); + path.starts_with(&self.prefix) + && (path.len() == self.prefix_len + || path.split_at(self.prefix_len).1.starts_with('/')) + } }; if m { if let Some(ref filters) = self.filters { @@ -135,7 +139,7 @@ where App { parts: Some(ApplicationParts { state, - prefix: "/".to_owned(), + prefix: "".to_owned(), router: Router::new(), middlewares: Vec::new(), filters: Vec::new(), @@ -498,12 +502,6 @@ where pub fn finish(&mut self) -> HttpApplication { let mut parts = self.parts.take().expect("Use after finish"); let prefix = parts.prefix.trim().trim_right_matches('/'); - let (prefix, prefix_len) = if prefix.is_empty() { - ("/".to_owned(), 0) - } else { - (prefix.to_owned(), prefix.len()) - }; - parts.router.finish(); let inner = Rc::new(Inner { @@ -517,12 +515,12 @@ where }; HttpApplication { - state: Rc::new(parts.state), - middlewares: Rc::new(parts.middlewares), - prefix, - prefix_len, inner, filters, + state: Rc::new(parts.state), + middlewares: Rc::new(parts.middlewares), + prefix: prefix.to_owned(), + prefix_len: prefix.len(), } } diff --git a/src/router.rs b/src/router.rs index d93ec9eb..603bc560 100644 --- a/src/router.rs +++ b/src/router.rs @@ -357,18 +357,11 @@ impl Router { /// Query for matched resource pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> ResourceInfo { - self.match_with_params(req, state, tail, true) - } - - /// Query for matched resource - pub(crate) fn match_with_params( - &self, req: &Request, state: &S, tail: usize, insert: bool, - ) -> ResourceInfo { if tail <= req.path().len() { 'outer: for (idx, resource) in self.patterns.iter().enumerate() { match resource { ResourcePattern::Resource(rdef) => { - if let Some(params) = rdef.match_with_params(req, tail, insert) { + if let Some(params) = rdef.match_with_params(req, tail) { return self.route_info_params(idx as u16, params); } } @@ -528,19 +521,8 @@ impl ResourceDef { } /// Are the given path and parameters a match against this resource? - pub fn match_with_params( - &self, req: &Request, plen: usize, insert: bool, - ) -> Option { + pub fn match_with_params(&self, req: &Request, plen: usize) -> Option { let path = &req.path()[plen..]; - if insert { - if path.is_empty() { - "/" - } else { - path - } - } else { - path - }; match self.tp { PatternType::Static(ref s) => if s != path { @@ -942,11 +924,11 @@ mod tests { assert!(!re.is_match("/user/2345/sdg")); let req = TestRequest::with_uri("/user/profile").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(info.get("id").unwrap(), "profile"); let req = TestRequest::with_uri("/user/1245125").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(info.get("id").unwrap(), "1245125"); let re = ResourceDef::new("/v{version}/resource/{id}"); @@ -955,7 +937,7 @@ mod tests { assert!(!re.is_match("/resource")); let req = TestRequest::with_uri("/v151/resource/adahg32").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(info.get("version").unwrap(), "151"); assert_eq!(info.get("id").unwrap(), "adahg32"); } @@ -983,12 +965,12 @@ mod tests { assert!(!re.is_match("/name")); let req = TestRequest::with_uri("/test2/").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(&info["name"], "test2"); assert_eq!(&info[0], "test2"); let req = TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(&info["name"], "test2"); assert_eq!(&info[0], "test2"); } diff --git a/src/scope.rs b/src/scope.rs index 94dbd860..d9502c94 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -304,7 +304,7 @@ impl RouteHandler for Scope { let tail = req.match_info().tail as usize; // recognize resources - let info = self.router.match_with_params(req, req.state(), tail, false); + let info = self.router.recognize(req, req.state(), tail); let req2 = req.with_route_info(info); if self.middlewares.is_empty() { self.router.handle(&req2) From 42d3e86941f8f9b9aa471eec670d7e2c053eb842 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 16:24:22 +0600 Subject: [PATCH 0501/1635] calculate prefix dynamicly --- src/httprequest.rs | 15 +++++++++------ src/router.rs | 34 ++++++++++++---------------------- src/server/h1.rs | 5 ----- src/test.rs | 21 +++++++++++++++++---- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 91ee9eb1..02edcae9 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -462,14 +462,16 @@ mod tests { let mut resource = Resource::new(ResourceDef::new("/user/{name}.html")); resource.name("index"); let mut router = Router::<()>::new(); - router.set_prefix("/prefix/"); router.register_resource(resource); - let info = router.default_route_info(); + let mut info = router.default_route_info(); + info.set_prefix(7); assert!(info.has_route("/user/test.html")); assert!(!info.has_route("/prefix/user/test.html")); - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") + let req = TestRequest::with_uri("/prefix/test") + .prefix(7) + .header(header::HOST, "www.rust-lang.org") .finish_with_router(router); let url = req.url_for("index", &["test"]); assert_eq!( @@ -483,14 +485,15 @@ mod tests { let mut resource = Resource::new(ResourceDef::new("/index.html")); resource.name("index"); let mut router = Router::<()>::new(); - router.set_prefix("/prefix/"); router.register_resource(resource); - let info = router.default_route_info(); + let mut info = router.default_route_info(); + info.set_prefix(7); assert!(info.has_route("/index.html")); assert!(!info.has_route("/prefix/index.html")); - let req = TestRequest::default() + let req = TestRequest::with_uri("/prefix/test") + .prefix(7) .header(header::HOST, "www.rust-lang.org") .finish_with_router(router); let url = req.url_for_static("index"); diff --git a/src/router.rs b/src/router.rs index 603bc560..468cc236 100644 --- a/src/router.rs +++ b/src/router.rs @@ -49,6 +49,7 @@ pub struct ResourceInfo { router: Rc, resource: ResourceId, params: Params, + prefix: u16, } impl ResourceInfo { @@ -72,6 +73,10 @@ impl ResourceInfo { } } + pub(crate) fn set_prefix(&mut self, prefix: u16) { + self.prefix = prefix; + } + /// Get a reference to the Params object. /// /// Params is a container for url parameters. @@ -91,6 +96,7 @@ impl ResourceInfo { p.add(item.0.clone(), item.1.clone()); } + self.prefix = info.params.tail; self.params = p; } @@ -106,7 +112,8 @@ impl ResourceInfo { I: AsRef, { if let Some(pattern) = self.router.named.get(name) { - let path = pattern.resource_path(elements, &self.router.prefix)?; + let path = + pattern.resource_path(elements, &req.path()[..(self.prefix as usize)])?; if path.starts_with('/') { let conn = req.connection_info(); Ok(Url::parse(&format!( @@ -142,8 +149,6 @@ impl ResourceInfo { } struct Inner { - prefix: String, - prefix_len: usize, named: HashMap, patterns: Vec, } @@ -158,8 +163,6 @@ impl Router { pub(crate) fn new() -> Self { Router { defs: Rc::new(Inner { - prefix: String::new(), - prefix_len: 0, named: HashMap::new(), patterns: Vec::new(), }), @@ -169,25 +172,11 @@ impl Router { } } - /// Router prefix - #[inline] - pub fn prefix(&self) -> &str { - &self.defs.prefix - } - - /// Set router prefix - #[inline] - pub fn set_prefix(&mut self, prefix: &str) { - let prefix = prefix.trim().trim_right_matches('/').to_owned(); - let inner = Rc::get_mut(&mut self.defs).unwrap(); - inner.prefix_len = prefix.len(); - inner.prefix = prefix; - } - #[inline] pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> ResourceInfo { ResourceInfo { params, + prefix: 0, router: self.defs.clone(), resource: ResourceId::Normal(idx), } @@ -200,6 +189,7 @@ impl Router { ResourceInfo { params, + prefix: 0, router: self.defs.clone(), resource: ResourceId::Default, } @@ -211,6 +201,7 @@ impl Router { params: Params::new(), router: self.defs.clone(), resource: ResourceId::Default, + prefix: 0, } } @@ -391,6 +382,7 @@ impl Router { } } ResourceInfo { + prefix: tail as u16, params: Params::new(), router: self.defs.clone(), resource: ResourceId::Default, @@ -851,7 +843,6 @@ mod tests { #[test] fn test_recognizer_with_prefix() { let mut router = Router::<()>::new(); - router.set_prefix("/test"); router.register_resource(Resource::new(ResourceDef::new("/name"))); router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); @@ -871,7 +862,6 @@ mod tests { // same patterns let mut router = Router::<()>::new(); - router.set_prefix("/test2"); router.register_resource(Resource::new(ResourceDef::new("/name"))); router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); diff --git a/src/server/h1.rs b/src/server/h1.rs index 5b83dcc0..511b32bc 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -543,11 +543,6 @@ mod tests { err: None, } } - fn feed_data(&mut self, data: &'static str) { - let mut b = BytesMut::from(self.buf.as_ref()); - b.extend(data.as_bytes()); - self.buf = b.take().freeze(); - } } impl AsyncRead for Buffer {} diff --git a/src/test.rs b/src/test.rs index 909d15f3..c2e5c756 100644 --- a/src/test.rs +++ b/src/test.rs @@ -417,6 +417,7 @@ pub struct TestRequest { params: Params, cookies: Option>>, payload: Option, + prefix: u16, } impl Default for TestRequest<()> { @@ -430,6 +431,7 @@ impl Default for TestRequest<()> { params: Params::new(), cookies: None, payload: None, + prefix: 0, } } } @@ -467,6 +469,7 @@ impl TestRequest { params: Params::new(), cookies: None, payload: None, + prefix: 0, } } @@ -527,6 +530,12 @@ impl TestRequest { self } + /// Set request's prefix + pub fn prefix(mut self, prefix: u16) -> Self { + self.prefix = prefix; + self + } + /// Complete request creation and generate `HttpRequest` instance pub fn finish(self) -> HttpRequest { let TestRequest { @@ -538,6 +547,7 @@ impl TestRequest { mut params, cookies, payload, + prefix, } = self; let router = Router::<()>::new(); @@ -552,9 +562,10 @@ impl TestRequest { *inner.payload.borrow_mut() = payload; } params.set_url(req.url().clone()); + let mut info = router.route_info_params(0, params); + info.set_prefix(prefix); - let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(0, params)); + let mut req = HttpRequest::new(req, Rc::new(state), info); req.set_cookies(cookies); req } @@ -571,6 +582,7 @@ impl TestRequest { mut params, cookies, payload, + prefix, } = self; let pool = RequestPool::pool(ServerSettings::default()); @@ -584,8 +596,9 @@ impl TestRequest { *inner.payload.borrow_mut() = payload; } params.set_url(req.url().clone()); - let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(0, params)); + let mut info = router.route_info_params(0, params); + info.set_prefix(prefix); + let mut req = HttpRequest::new(req, Rc::new(state), info); req.set_cookies(cookies); req } From c43b6e3577d1cb3097b879aa05efdcfbc019c12f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 16:39:15 +0600 Subject: [PATCH 0502/1635] cargo tarpaulin --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d6cd29e1..67cd9d38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,8 @@ script: fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - USE_SKEPTIC=1 cargo tarpaulin --features="alpn,tls" --out Xml --no-count + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin + cargo tarpaulin --features="alpn,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 2214492792aff5e5e2b507b441c885d6e1c48ab9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 18:53:02 +0600 Subject: [PATCH 0503/1635] use assert and restore test case --- .travis.yml | 2 +- src/router.rs | 16 ++++++++++------ src/scope.rs | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 67cd9d38..54a86aa7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin cargo tarpaulin --features="alpn,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" diff --git a/src/router.rs b/src/router.rs index 468cc236..fbdcbc08 100644 --- a/src/router.rs +++ b/src/router.rs @@ -211,9 +211,11 @@ impl Router { let name = resource.get_name(); if !name.is_empty() { - if inner.named.contains_key(name) { - panic!("Named resource {:?} is registered.", name); - } + assert!( + !inner.named.contains_key(name), + "Named resource {:?} is registered.", + name + ); inner.named.insert(name.to_owned(), resource.rdef().clone()); } inner.patterns.push(resource.rdef().clone()); @@ -279,9 +281,11 @@ impl Router { pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { let inner = Rc::get_mut(&mut self.defs).unwrap(); - if inner.named.contains_key(name) { - panic!("Named resource {:?} is registered.", name); - } + assert!( + !inner.named.contains_key(name), + "Named resource {:?} is registered.", + name + ); inner.named.insert(name.to_owned(), rdef); } diff --git a/src/scope.rs b/src/scope.rs index d9502c94..a12bcafa 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -910,9 +910,9 @@ mod tests { }) .finish(); - //let req = TestRequest::with_uri("/app/t1").request(); - //let resp = app.run(req); - //assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/t1").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); From 2e5f62705050d9edc2bab6a8f77670d439858483 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 19:15:36 +0600 Subject: [PATCH 0504/1635] do not force install tarpaulin --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 54a86aa7..67cd9d38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin cargo tarpaulin --features="alpn,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" From 8f645088878380b40485dedff5eb51020aa5f353 Mon Sep 17 00:00:00 2001 From: Mathieu Amiot Date: Tue, 10 Jul 2018 13:05:20 +0200 Subject: [PATCH 0505/1635] Added RouteInfo::has_prefixed_route() method for route matching with prefix awareness --- CHANGES.md | 2 ++ src/httprequest.rs | 7 +++++++ src/router.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5fa6f12b..6c72e3e2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Added +* Add `.has_prefixed_route()` method to `router::RouteInfo` for route matching with prefix awareness + * Add `HttpMessage::readlines()` for reading line by line. * Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. diff --git a/src/httprequest.rs b/src/httprequest.rs index 02edcae9..21688877 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -437,7 +437,9 @@ mod tests { let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); + assert!(info.has_prefixed_route("/user/test.html")); assert!(!info.has_route("/test/unknown")); + assert!(!info.has_prefixed_route("/test/unknown")); let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") .finish_with_router(router); @@ -467,7 +469,9 @@ mod tests { let mut info = router.default_route_info(); info.set_prefix(7); assert!(info.has_route("/user/test.html")); + assert!(!info.has_prefixed_route("/user/test.html")); assert!(!info.has_route("/prefix/user/test.html")); + assert!(info.has_prefixed_route("/prefix/user/test.html")); let req = TestRequest::with_uri("/prefix/test") .prefix(7) @@ -490,7 +494,9 @@ mod tests { let mut info = router.default_route_info(); info.set_prefix(7); assert!(info.has_route("/index.html")); + assert!(!info.has_prefixed_route("/index.html")); assert!(!info.has_route("/prefix/index.html")); + assert!(info.has_prefixed_route("/prefix/index.html")); let req = TestRequest::with_uri("/prefix/test") .prefix(7) @@ -513,6 +519,7 @@ mod tests { let info = router.default_route_info(); assert!(!info.has_route("https://youtube.com/watch/unknown")); + assert!(!info.has_prefixed_route("https://youtube.com/watch/unknown")); let req = TestRequest::default().finish_with_router(router); let url = req.url_for("youtube", &["oHg5SJYRHA0"]); diff --git a/src/router.rs b/src/router.rs index fbdcbc08..56f30494 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,6 +1,7 @@ use std::cmp::min; use std::collections::HashMap; use std::hash::{Hash, Hasher}; +use std::path::Path; use std::rc::Rc; use regex::{escape, Regex}; @@ -146,6 +147,32 @@ impl ResourceInfo { } false } + + /// Check if application contains matching route. + /// + /// This method does take `prefix` into account + /// but behaves like `has_route` in case `prefix` is not set in the router. + /// + /// For example if prefix is `/test` and router contains route `/name`, the + /// following path would be recognizable `/test/name` and `has_prefixed_route()` call + /// would return `true`. + /// It will not match against prefix in case it's not given. For example for `/name` + /// with a `/test` prefix would return `false` + pub fn has_prefixed_route(&self, path: &str) -> bool { + if self.prefix == 0 { + return self.has_route(path); + } + + let path_matcher = Path::new(if path.is_empty() { "/" } else { path }); + let router_prefix = Path::new(&path[..(self.prefix as usize)]); + if let Ok(p) = path_matcher.strip_prefix(router_prefix) { + if let Some(p_str) = p.to_str() { + return self.has_route(&format!("/{}", p_str)); + } + } + + false + } } struct Inner { From 3373847a14e69969e02bc7347f4f5f808432a040 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Jul 2018 00:40:22 +0600 Subject: [PATCH 0506/1635] allocate buffer for request payload extractors --- .travis.yml | 2 +- src/httpmessage.rs | 6 +++--- src/json.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 67cd9d38..54a86aa7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin cargo tarpaulin --features="alpn,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 4da0163e..5db2f075 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -286,7 +286,7 @@ impl Readlines { fn err(req: &T, err: ReadlinesError) -> Self { Readlines { stream: req.payload(), - buff: BytesMut::with_capacity(262_144), + buff: BytesMut::new(), limit: 262_144, checked_buff: true, encoding: UTF_8, @@ -472,7 +472,7 @@ where .take() .expect("Can not be used second time") .from_err() - .fold(BytesMut::new(), move |mut body, chunk| { + .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { Err(PayloadError::Overflow) } else { @@ -581,7 +581,7 @@ where .take() .expect("UrlEncoded could not be used second time") .from_err() - .fold(BytesMut::new(), move |mut body, chunk| { + .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { Err(UrlencodedError::Overflow) } else { diff --git a/src/json.rs b/src/json.rs index 485d0b3e..c76aeaa7 100644 --- a/src/json.rs +++ b/src/json.rs @@ -320,7 +320,7 @@ impl Future for JsonBod .take() .expect("JsonBody could not be used second time") .from_err() - .fold(BytesMut::new(), move |mut body, chunk| { + .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { Err(JsonPayloadError::Overflow) } else { From b7a3fce17b8bf7bf823386e016d908945a3fd6ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Jul 2018 11:10:51 +0600 Subject: [PATCH 0507/1635] simplify has_prefixed_route() --- src/router.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/router.rs b/src/router.rs index 56f30494..fe3ecb94 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,7 +1,6 @@ use std::cmp::min; use std::collections::HashMap; use std::hash::{Hash, Hasher}; -use std::path::Path; use std::rc::Rc; use regex::{escape, Regex}; @@ -159,19 +158,11 @@ impl ResourceInfo { /// It will not match against prefix in case it's not given. For example for `/name` /// with a `/test` prefix would return `false` pub fn has_prefixed_route(&self, path: &str) -> bool { - if self.prefix == 0 { - return self.has_route(path); + let prefix = self.prefix as usize; + if prefix >= path.len() { + return false; } - - let path_matcher = Path::new(if path.is_empty() { "/" } else { path }); - let router_prefix = Path::new(&path[..(self.prefix as usize)]); - if let Ok(p) = path_matcher.strip_prefix(router_prefix) { - if let Some(p_str) = p.to_str() { - return self.has_route(&format!("/{}", p_str)); - } - } - - false + self.has_route(&path[prefix..]) } } From 5888f01317d30c1f98f90ad99f5c75078f24dfd4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Jul 2018 11:13:41 +0600 Subject: [PATCH 0508/1635] use has_prefixed_route for NormalizePath helper --- src/helpers.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/src/helpers.rs b/src/helpers.rs index 85247123..a14ce9ff 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -92,7 +92,7 @@ impl Handler for NormalizePath { // merge slashes let p = self.re_merge.replace_all(req.path(), "/"); if p.len() != req.path().len() { - if req.resource().has_route(p.as_ref()) { + if req.resource().has_prefixed_route(p.as_ref()) { let p = if !query.is_empty() { p + "?" + query } else { @@ -105,7 +105,7 @@ impl Handler for NormalizePath { // merge slashes and append trailing slash if self.append && !p.ends_with('/') { let p = p.as_ref().to_owned() + "/"; - if req.resource().has_route(&p) { + if req.resource().has_prefixed_route(&p) { let p = if !query.is_empty() { p + "?" + query } else { @@ -120,7 +120,7 @@ impl Handler for NormalizePath { // try to remove trailing slash if p.ends_with('/') { let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_route(p) { + if req.resource().has_prefixed_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -135,7 +135,7 @@ impl Handler for NormalizePath { } else if p.ends_with('/') { // try to remove trailing slash let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_route(p) { + if req.resource().has_prefixed_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -151,7 +151,7 @@ impl Handler for NormalizePath { // append trailing slash if self.append && !req.path().ends_with('/') { let p = req.path().to_owned() + "/"; - if req.resource().has_route(&p) { + if req.resource().has_prefixed_route(&p) { let p = if !query.is_empty() { p + "?" + query } else { @@ -218,6 +218,56 @@ mod tests { } } + #[test] + fn test_prefixed_normalize_path_trailing_slashes() { + let app = App::new() + .prefix("/test") + .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![ + ("/test/resource1", "", StatusCode::OK), + ( + "/test/resource1/", + "/test/resource1", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/test/resource2", + "/test/resource2/", + StatusCode::MOVED_PERMANENTLY, + ), + ("/test/resource2/", "", StatusCode::OK), + ("/test/resource1?p1=1&p2=2", "", StatusCode::OK), + ( + "/test/resource1/?p1=1&p2=2", + "/test/resource1?p1=1&p2=2", + StatusCode::MOVED_PERMANENTLY, + ), + ( + "/test/resource2?p1=1&p2=2", + "/test/resource2/?p1=1&p2=2", + StatusCode::MOVED_PERMANENTLY, + ), + ("/test/resource2/?p1=1&p2=2", "", StatusCode::OK), + ]; + for (path, target, code) in params { + let req = TestRequest::with_uri(path).request(); + let resp = app.run(req); + let r = &resp.as_msg(); + 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 = App::new() From 22385505a3b868fcac63e6b7f15eb2bc5d22c71b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Jul 2018 11:17:45 +0600 Subject: [PATCH 0509/1635] clippy warnings and fmt --- src/fs.rs | 49 ++++++-- src/lib.rs | 2 +- src/pred.rs | 3 +- src/serde_urlencoded/de.rs | 79 ++++++------ src/serde_urlencoded/mod.rs | 51 ++++---- src/serde_urlencoded/ser/key.rs | 18 ++- src/serde_urlencoded/ser/mod.rs | 193 ++++++++++++++---------------- src/serde_urlencoded/ser/pair.rs | 100 +++++++--------- src/serde_urlencoded/ser/part.rs | 109 +++++++---------- src/serde_urlencoded/ser/value.rs | 28 ++--- tests/test_client.rs | 7 +- tests/test_handlers.rs | 19 ++- 12 files changed, 327 insertions(+), 331 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index f42ef2e4..14c3818b 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -585,11 +585,13 @@ impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory and /// `CpuPool`. - pub fn with_pool>(dir: T, pool: CpuPool) -> Result, Error> { + pub fn with_pool>( + dir: T, pool: CpuPool, + ) -> Result, Error> { let dir = dir.into().canonicalize()?; if !dir.is_dir() { - return Err(StaticFileError::IsNotDirectory.into()) + return Err(StaticFileError::IsNotDirectory.into()); } Ok(StaticFiles { @@ -640,7 +642,9 @@ impl StaticFiles { self } - fn try_handle(&self, req: &HttpRequest) -> Result, Error> { + fn try_handle( + &self, req: &HttpRequest, + ) -> Result, Error> { let tail: String = req.match_info().query("tail")?; let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; @@ -971,7 +975,10 @@ mod tests { #[test] fn test_named_file_ranges_status_code() { let mut srv = test::TestServer::with_factory(|| { - App::new().handler("test", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) + App::new().handler( + "test", + StaticFiles::new(".").unwrap().index_file("Cargo.toml"), + ) }); // Valid range header @@ -1001,7 +1008,9 @@ mod tests { let mut srv = test::TestServer::with_factory(|| { App::new().handler( "test", - StaticFiles::new(".").unwrap().index_file("tests/test.binary"), + StaticFiles::new(".") + .unwrap() + .index_file("tests/test.binary"), ) }); @@ -1049,7 +1058,9 @@ mod tests { let mut srv = test::TestServer::with_factory(|| { App::new().handler( "test", - StaticFiles::new(".").unwrap().index_file("tests/test.binary"), + StaticFiles::new(".") + .unwrap() + .index_file("tests/test.binary"), ) }); @@ -1167,7 +1178,9 @@ mod tests { #[test] fn test_static_files() { let mut st = StaticFiles::new(".").unwrap().show_files_listing(); - let req = TestRequest::with_uri("/missing").param("tail", "missing").finish(); + let req = TestRequest::with_uri("/missing") + .param("tail", "missing") + .finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); @@ -1202,14 +1215,20 @@ mod tests { #[test] fn test_default_handler_file_missing() { - let st = StaticFiles::new(".").unwrap() + let st = StaticFiles::new(".") + .unwrap() .default_handler(|_: &_| "default content"); - let req = TestRequest::with_uri("/missing").param("tail", "missing").finish(); + let req = TestRequest::with_uri("/missing") + .param("tail", "missing") + .finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body(), &Body::Binary(Binary::Slice(b"default content"))); + assert_eq!( + resp.body(), + &Body::Binary(Binary::Slice(b"default content")) + ); } #[test] @@ -1282,7 +1301,10 @@ mod tests { #[test] fn integration_redirect_to_index() { let mut srv = test::TestServer::with_factory(|| { - App::new().handler("test", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) + App::new().handler( + "test", + StaticFiles::new(".").unwrap().index_file("Cargo.toml"), + ) }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); @@ -1311,7 +1333,10 @@ mod tests { #[test] fn integration_percent_encoded() { let mut srv = test::TestServer::with_factory(|| { - App::new().handler("test", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) + App::new().handler( + "test", + StaticFiles::new(".").unwrap().index_file("Cargo.toml"), + ) }); let request = srv diff --git a/src/lib.rs b/src/lib.rs index 34dcd718..da2249c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,7 +151,6 @@ extern crate openssl; #[cfg(feature = "openssl")] extern crate tokio_openssl; -mod serde_urlencoded; mod application; mod body; mod context; @@ -174,6 +173,7 @@ mod resource; mod route; mod router; mod scope; +mod serde_urlencoded; mod uri; mod with; diff --git a/src/pred.rs b/src/pred.rs index 432150ef..22f12ac2 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -175,8 +175,7 @@ pub fn Method(method: http::Method) -> MethodPredicate { /// Return predicate that matches if request contains specified header and /// value. pub fn Header( - name: &'static str, - value: &'static str, + name: &'static str, value: &'static str, ) -> HeaderPredicate { HeaderPredicate( header::HeaderName::try_from(name).unwrap(), diff --git a/src/serde_urlencoded/de.rs b/src/serde_urlencoded/de.rs index affbfb37..ae14afbf 100644 --- a/src/serde_urlencoded/de.rs +++ b/src/serde_urlencoded/de.rs @@ -1,13 +1,15 @@ //! Deserialization support for the `application/x-www-form-urlencoded` format. -use serde::de::{self, DeserializeSeed, EnumAccess, IntoDeserializer, VariantAccess, Visitor}; use serde::de::Error as de_Error; +use serde::de::{ + self, DeserializeSeed, EnumAccess, IntoDeserializer, VariantAccess, Visitor, +}; use serde::de::value::MapDeserializer; use std::borrow::Cow; use std::io::Read; -use url::form_urlencoded::Parse as UrlEncodedParse; use url::form_urlencoded::parse; +use url::form_urlencoded::Parse as UrlEncodedParse; #[doc(inline)] pub use serde::de::value::Error; @@ -28,7 +30,8 @@ pub use serde::de::value::Error; /// Ok(meal)); /// ``` pub fn from_bytes<'de, T>(input: &'de [u8]) -> Result - where T: de::Deserialize<'de>, +where + T: de::Deserialize<'de>, { T::deserialize(Deserializer::new(parse(input))) } @@ -49,7 +52,8 @@ pub fn from_bytes<'de, T>(input: &'de [u8]) -> Result /// Ok(meal)); /// ``` pub fn from_str<'de, T>(input: &'de str) -> Result - where T: de::Deserialize<'de>, +where + T: de::Deserialize<'de>, { from_bytes(input.as_bytes()) } @@ -58,14 +62,14 @@ pub fn from_str<'de, T>(input: &'de str) -> Result /// Convenience function that reads all bytes from `reader` and deserializes /// them with `from_bytes`. pub fn from_reader(mut reader: R) -> Result - where T: de::DeserializeOwned, - R: Read, +where + T: de::DeserializeOwned, + R: Read, { let mut buf = vec![]; - reader.read_to_end(&mut buf) - .map_err(|e| { - de::Error::custom(format_args!("could not read input: {}", e)) - })?; + reader + .read_to_end(&mut buf) + .map_err(|e| de::Error::custom(format_args!("could not read input: {}", e)))?; from_bytes(&buf) } @@ -95,25 +99,29 @@ impl<'de> de::Deserializer<'de> for Deserializer<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result - where V: de::Visitor<'de>, + where + V: de::Visitor<'de>, { self.deserialize_map(visitor) } fn deserialize_map(self, visitor: V) -> Result - where V: de::Visitor<'de>, + where + V: de::Visitor<'de>, { visitor.visit_map(self.inner) } fn deserialize_seq(self, visitor: V) -> Result - where V: de::Visitor<'de>, + where + V: de::Visitor<'de>, { visitor.visit_seq(self.inner) } fn deserialize_unit(self, visitor: V) -> Result - where V: de::Visitor<'de>, + where + V: de::Visitor<'de>, { self.inner.end()?; visitor.visit_unit() @@ -160,8 +168,7 @@ impl<'de> Iterator for PartIterator<'de> { struct Part<'de>(Cow<'de, str>); -impl<'de> IntoDeserializer<'de> for Part<'de> -{ +impl<'de> IntoDeserializer<'de> for Part<'de> { type Deserializer = Self; fn into_deserializer(self) -> Self::Deserializer { @@ -188,19 +195,24 @@ impl<'de> de::Deserializer<'de> for Part<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result - where V: de::Visitor<'de>, + where + V: de::Visitor<'de>, { self.0.into_deserializer().deserialize_any(visitor) } fn deserialize_option(self, visitor: V) -> Result - where V: de::Visitor<'de>, + where + V: de::Visitor<'de>, { visitor.visit_some(self) } - fn deserialize_enum(self, _name: &'static str, _variants: &'static [&'static str], visitor: V) -> Result - where V: de::Visitor<'de>, + fn deserialize_enum( + self, _name: &'static str, _variants: &'static [&'static str], visitor: V, + ) -> Result + where + V: de::Visitor<'de>, { visitor.visit_enum(ValueEnumAccess { value: self.0 }) } @@ -248,11 +260,9 @@ impl<'de> EnumAccess<'de> for ValueEnumAccess<'de> { type Error = Error; type Variant = UnitOnlyVariantAccess; - fn variant_seed( - self, - seed: V, - ) -> Result<(V::Value, Self::Variant), Self::Error> - where V: DeserializeSeed<'de>, + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: DeserializeSeed<'de>, { let variant = seed.deserialize(self.value.into_deserializer())?; Ok((variant, UnitOnlyVariantAccess)) @@ -271,27 +281,24 @@ impl<'de> VariantAccess<'de> for UnitOnlyVariantAccess { } fn newtype_variant_seed(self, _seed: T) -> Result - where T: DeserializeSeed<'de>, + where + T: DeserializeSeed<'de>, { Err(Error::custom("expected unit variant")) } - fn tuple_variant( - self, - _len: usize, - _visitor: V, - ) -> Result - where V: Visitor<'de>, + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: Visitor<'de>, { Err(Error::custom("expected unit variant")) } fn struct_variant( - self, - _fields: &'static [&'static str], - _visitor: V, + self, _fields: &'static [&'static str], _visitor: V, ) -> Result - where V: Visitor<'de>, + where + V: Visitor<'de>, { Err(Error::custom("expected unit variant")) } diff --git a/src/serde_urlencoded/mod.rs b/src/serde_urlencoded/mod.rs index 6ee62c2a..7e2cf33a 100644 --- a/src/serde_urlencoded/mod.rs +++ b/src/serde_urlencoded/mod.rs @@ -1,15 +1,15 @@ //! `x-www-form-urlencoded` meets Serde -extern crate itoa; extern crate dtoa; +extern crate itoa; pub mod de; pub mod ser; #[doc(inline)] -pub use self::de::{Deserializer, from_bytes, from_reader, from_str}; +pub use self::de::{from_bytes, from_reader, from_str, Deserializer}; #[doc(inline)] -pub use self::ser::{Serializer, to_string}; +pub use self::ser::{to_string, Serializer}; #[cfg(test)] mod tests { @@ -17,24 +17,21 @@ mod tests { fn deserialize_bytes() { let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; - assert_eq!(super::from_bytes(b"first=23&last=42"), - Ok(result)); + assert_eq!(super::from_bytes(b"first=23&last=42"), Ok(result)); } #[test] fn deserialize_str() { let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; - assert_eq!(super::from_str("first=23&last=42"), - Ok(result)); + assert_eq!(super::from_str("first=23&last=42"), Ok(result)); } #[test] fn deserialize_reader() { let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; - assert_eq!(super::from_reader(b"first=23&last=42" as &[_]), - Ok(result)); + assert_eq!(super::from_reader(b"first=23&last=42" as &[_]), Ok(result)); } #[test] @@ -66,7 +63,7 @@ mod tests { let result = vec![ ("one".to_owned(), X::A), ("two".to_owned(), X::B), - ("three".to_owned(), X::C) + ("three".to_owned(), X::C), ]; assert_eq!(super::from_str("one=A&two=B&three=C"), Ok(result)); @@ -76,39 +73,49 @@ mod tests { fn serialize_option_map_int() { let params = &[("first", Some(23)), ("middle", None), ("last", Some(42))]; - assert_eq!(super::to_string(params), - Ok("first=23&last=42".to_owned())); + assert_eq!(super::to_string(params), Ok("first=23&last=42".to_owned())); } #[test] fn serialize_option_map_string() { - let params = - &[("first", Some("hello")), ("middle", None), ("last", Some("world"))]; + let params = &[ + ("first", Some("hello")), + ("middle", None), + ("last", Some("world")), + ]; - assert_eq!(super::to_string(params), - Ok("first=hello&last=world".to_owned())); + assert_eq!( + super::to_string(params), + Ok("first=hello&last=world".to_owned()) + ); } #[test] fn serialize_option_map_bool() { let params = &[("one", Some(true)), ("two", Some(false))]; - assert_eq!(super::to_string(params), - Ok("one=true&two=false".to_owned())); + assert_eq!( + super::to_string(params), + Ok("one=true&two=false".to_owned()) + ); } #[test] fn serialize_map_bool() { let params = &[("one", true), ("two", false)]; - assert_eq!(super::to_string(params), - Ok("one=true&two=false".to_owned())); + assert_eq!( + super::to_string(params), + Ok("one=true&two=false".to_owned()) + ); } #[test] fn serialize_unit_enum() { let params = &[("one", X::A), ("two", X::B), ("three", X::C)]; - assert_eq!(super::to_string(params), - Ok("one=A&two=B&three=C".to_owned())); + assert_eq!( + super::to_string(params), + Ok("one=A&two=B&three=C".to_owned()) + ); } } diff --git a/src/serde_urlencoded/ser/key.rs b/src/serde_urlencoded/ser/key.rs index 2d138f18..48497a55 100644 --- a/src/serde_urlencoded/ser/key.rs +++ b/src/serde_urlencoded/ser/key.rs @@ -1,5 +1,5 @@ -use super::super::ser::Error; use super::super::ser::part::Sink; +use super::super::ser::Error; use serde::Serialize; use std::borrow::Cow; use std::ops::Deref; @@ -34,21 +34,21 @@ pub struct KeySink { } impl KeySink - where End: for<'key> FnOnce(Key<'key>) -> Result +where + End: for<'key> FnOnce(Key<'key>) -> Result, { pub fn new(end: End) -> Self { - KeySink { end: end } + KeySink { end } } } impl Sink for KeySink - where End: for<'key> FnOnce(Key<'key>) -> Result +where + End: for<'key> FnOnce(Key<'key>) -> Result, { type Ok = Ok; - fn serialize_static_str(self, - value: &'static str) - -> Result { + fn serialize_static_str(self, value: &'static str) -> Result { (self.end)(Key::Static(value)) } @@ -64,9 +64,7 @@ impl Sink for KeySink Err(self.unsupported()) } - fn serialize_some(self, - _value: &T) - -> Result { + fn serialize_some(self, _value: &T) -> Result { Err(self.unsupported()) } diff --git a/src/serde_urlencoded/ser/mod.rs b/src/serde_urlencoded/ser/mod.rs index f8d5e13e..b4022d56 100644 --- a/src/serde_urlencoded/ser/mod.rs +++ b/src/serde_urlencoded/ser/mod.rs @@ -49,7 +49,7 @@ pub struct Serializer<'output, Target: 'output + UrlEncodedTarget> { impl<'output, Target: 'output + UrlEncodedTarget> Serializer<'output, Target> { /// Returns a new `Serializer`. pub fn new(urlencoder: &'output mut UrlEncodedSerializer) -> Self { - Serializer { urlencoder: urlencoder } + Serializer { urlencoder } } } @@ -137,7 +137,8 @@ pub struct StructVariantSerializer<'output, T: 'output + UrlEncodedTarget> { } impl<'output, Target> ser::Serializer for Serializer<'output, Target> - where Target: 'output + UrlEncodedTarget, +where + Target: 'output + UrlEncodedTarget, { type Ok = &'output mut UrlEncodedSerializer; type Error = Error; @@ -225,38 +226,29 @@ impl<'output, Target> ser::Serializer for Serializer<'output, Target> } /// Returns an error. - fn serialize_unit_struct(self, - _name: &'static str) - -> Result { + fn serialize_unit_struct(self, _name: &'static str) -> Result { Err(Error::top_level()) } /// Returns an error. - fn serialize_unit_variant(self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str) - -> Result { + fn serialize_unit_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + ) -> Result { Err(Error::top_level()) } /// Serializes the inner value, ignoring the newtype name. - fn serialize_newtype_struct - (self, - _name: &'static str, - value: &T) - -> Result { + fn serialize_newtype_struct( + self, _name: &'static str, value: &T, + ) -> Result { value.serialize(self) } /// Returns an error. - fn serialize_newtype_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _value: &T) - -> Result { + fn serialize_newtype_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _value: &T, + ) -> Result { Err(Error::top_level()) } @@ -266,50 +258,43 @@ impl<'output, Target> ser::Serializer for Serializer<'output, Target> } /// Serializes the given value. - fn serialize_some - (self, - value: &T) - -> Result { + fn serialize_some( + self, value: &T, + ) -> Result { value.serialize(self) } /// Serialize a sequence, given length (if any) is ignored. - fn serialize_seq(self, - _len: Option) - -> Result { - Ok(SeqSerializer { urlencoder: self.urlencoder }) + fn serialize_seq(self, _len: Option) -> Result { + Ok(SeqSerializer { + urlencoder: self.urlencoder, + }) } /// Returns an error. - fn serialize_tuple(self, - _len: usize) - -> Result { - Ok(TupleSerializer { urlencoder: self.urlencoder }) + fn serialize_tuple(self, _len: usize) -> Result { + Ok(TupleSerializer { + urlencoder: self.urlencoder, + }) } /// Returns an error. - fn serialize_tuple_struct(self, - _name: &'static str, - _len: usize) - -> Result { + fn serialize_tuple_struct( + self, _name: &'static str, _len: usize, + ) -> Result { Err(Error::top_level()) } /// Returns an error. - fn serialize_tuple_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize) - -> Result { + fn serialize_tuple_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _len: usize, + ) -> Result { Err(Error::top_level()) } /// Serializes a map, given length is ignored. - fn serialize_map(self, - _len: Option) - -> Result { + fn serialize_map(self, _len: Option) -> Result { Ok(MapSerializer { urlencoder: self.urlencoder, key: None, @@ -317,34 +302,33 @@ impl<'output, Target> ser::Serializer for Serializer<'output, Target> } /// Serializes a struct, given length is ignored. - fn serialize_struct(self, - _name: &'static str, - _len: usize) - -> Result { - Ok(StructSerializer { urlencoder: self.urlencoder }) + fn serialize_struct( + self, _name: &'static str, _len: usize, + ) -> Result { + Ok(StructSerializer { + urlencoder: self.urlencoder, + }) } /// Returns an error. - fn serialize_struct_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize) - -> Result { + fn serialize_struct_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _len: usize, + ) -> Result { Err(Error::top_level()) } } impl<'output, Target> ser::SerializeSeq for SeqSerializer<'output, Target> - where Target: 'output + UrlEncodedTarget, +where + Target: 'output + UrlEncodedTarget, { type Ok = &'output mut UrlEncodedSerializer; type Error = Error; - fn serialize_element(&mut self, - value: &T) - -> Result<(), Error> { + fn serialize_element( + &mut self, value: &T, + ) -> Result<(), Error> { value.serialize(pair::PairSerializer::new(self.urlencoder)) } @@ -354,14 +338,15 @@ impl<'output, Target> ser::SerializeSeq for SeqSerializer<'output, Target> } impl<'output, Target> ser::SerializeTuple for TupleSerializer<'output, Target> - where Target: 'output + UrlEncodedTarget, +where + Target: 'output + UrlEncodedTarget, { type Ok = &'output mut UrlEncodedSerializer; type Error = Error; - fn serialize_element(&mut self, - value: &T) - -> Result<(), Error> { + fn serialize_element( + &mut self, value: &T, + ) -> Result<(), Error> { value.serialize(pair::PairSerializer::new(self.urlencoder)) } @@ -371,16 +356,16 @@ impl<'output, Target> ser::SerializeTuple for TupleSerializer<'output, Target> } impl<'output, Target> ser::SerializeTupleStruct - for - TupleStructSerializer<'output, Target> - where Target: 'output + UrlEncodedTarget, + for TupleStructSerializer<'output, Target> +where + Target: 'output + UrlEncodedTarget, { type Ok = &'output mut UrlEncodedSerializer; type Error = Error; - fn serialize_field(&mut self, - value: &T) - -> Result<(), Error> { + fn serialize_field( + &mut self, value: &T, + ) -> Result<(), Error> { self.inner.serialize_field(value) } @@ -390,16 +375,16 @@ impl<'output, Target> ser::SerializeTupleStruct } impl<'output, Target> ser::SerializeTupleVariant - for - TupleVariantSerializer<'output, Target> - where Target: 'output + UrlEncodedTarget, + for TupleVariantSerializer<'output, Target> +where + Target: 'output + UrlEncodedTarget, { type Ok = &'output mut UrlEncodedSerializer; type Error = Error; - fn serialize_field(&mut self, - value: &T) - -> Result<(), Error> { + fn serialize_field( + &mut self, value: &T, + ) -> Result<(), Error> { self.inner.serialize_field(value) } @@ -409,16 +394,15 @@ impl<'output, Target> ser::SerializeTupleVariant } impl<'output, Target> ser::SerializeMap for MapSerializer<'output, Target> - where Target: 'output + UrlEncodedTarget, +where + Target: 'output + UrlEncodedTarget, { type Ok = &'output mut UrlEncodedSerializer; type Error = Error; - fn serialize_entry - (&mut self, - key: &K, - value: &V) - -> Result<(), Error> { + fn serialize_entry( + &mut self, key: &K, value: &V, + ) -> Result<(), Error> { let key_sink = key::KeySink::new(|key| { let value_sink = value::ValueSink::new(self.urlencoder, &key); value.serialize(part::PartSerializer::new(value_sink))?; @@ -429,20 +413,20 @@ impl<'output, Target> ser::SerializeMap for MapSerializer<'output, Target> key.serialize(entry_serializer) } - fn serialize_key(&mut self, - key: &T) - -> Result<(), Error> { + fn serialize_key( + &mut self, key: &T, + ) -> Result<(), Error> { let key_sink = key::KeySink::new(|key| Ok(key.into())); let key_serializer = part::PartSerializer::new(key_sink); self.key = Some(key.serialize(key_serializer)?); Ok(()) } - fn serialize_value(&mut self, - value: &T) - -> Result<(), Error> { + fn serialize_value( + &mut self, value: &T, + ) -> Result<(), Error> { { - let key = self.key.as_ref().ok_or_else(|| Error::no_key())?; + let key = self.key.as_ref().ok_or_else(Error::no_key)?; let value_sink = value::ValueSink::new(self.urlencoder, &key); value.serialize(part::PartSerializer::new(value_sink))?; } @@ -456,15 +440,15 @@ impl<'output, Target> ser::SerializeMap for MapSerializer<'output, Target> } impl<'output, Target> ser::SerializeStruct for StructSerializer<'output, Target> - where Target: 'output + UrlEncodedTarget, +where + Target: 'output + UrlEncodedTarget, { type Ok = &'output mut UrlEncodedSerializer; type Error = Error; - fn serialize_field(&mut self, - key: &'static str, - value: &T) - -> Result<(), Error> { + fn serialize_field( + &mut self, key: &'static str, value: &T, + ) -> Result<(), Error> { let value_sink = value::ValueSink::new(self.urlencoder, key); value.serialize(part::PartSerializer::new(value_sink)) } @@ -475,17 +459,16 @@ impl<'output, Target> ser::SerializeStruct for StructSerializer<'output, Target> } impl<'output, Target> ser::SerializeStructVariant - for - StructVariantSerializer<'output, Target> - where Target: 'output + UrlEncodedTarget, + for StructVariantSerializer<'output, Target> +where + Target: 'output + UrlEncodedTarget, { type Ok = &'output mut UrlEncodedSerializer; type Error = Error; - fn serialize_field(&mut self, - key: &'static str, - value: &T) - -> Result<(), Error> { + fn serialize_field( + &mut self, key: &'static str, value: &T, + ) -> Result<(), Error> { self.inner.serialize_field(key, value) } diff --git a/src/serde_urlencoded/ser/pair.rs b/src/serde_urlencoded/ser/pair.rs index 37f98475..68db144f 100644 --- a/src/serde_urlencoded/ser/pair.rs +++ b/src/serde_urlencoded/ser/pair.rs @@ -1,7 +1,7 @@ -use super::super::ser::Error; use super::super::ser::key::KeySink; use super::super::ser::part::PartSerializer; use super::super::ser::value::ValueSink; +use super::super::ser::Error; use serde::ser; use std::borrow::Cow; use std::mem; @@ -14,18 +14,20 @@ pub struct PairSerializer<'target, Target: 'target + UrlEncodedTarget> { } impl<'target, Target> PairSerializer<'target, Target> - where Target: 'target + UrlEncodedTarget, +where + Target: 'target + UrlEncodedTarget, { pub fn new(urlencoder: &'target mut UrlEncodedSerializer) -> Self { PairSerializer { - urlencoder: urlencoder, + urlencoder, state: PairState::WaitingForKey, } } } impl<'target, Target> ser::Serializer for PairSerializer<'target, Target> - where Target: 'target + UrlEncodedTarget, +where + Target: 'target + UrlEncodedTarget, { type Ok = (); type Error = Error; @@ -101,29 +103,22 @@ impl<'target, Target> ser::Serializer for PairSerializer<'target, Target> Err(Error::unsupported_pair()) } - fn serialize_unit_variant(self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str) - -> Result<(), Error> { + fn serialize_unit_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + ) -> Result<(), Error> { Err(Error::unsupported_pair()) } - fn serialize_newtype_struct - (self, - _name: &'static str, - value: &T) - -> Result<(), Error> { + fn serialize_newtype_struct( + self, _name: &'static str, value: &T, + ) -> Result<(), Error> { value.serialize(self) } - fn serialize_newtype_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _value: &T) - -> Result<(), Error> { + fn serialize_newtype_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _value: &T, + ) -> Result<(), Error> { Err(Error::unsupported_pair()) } @@ -131,15 +126,11 @@ impl<'target, Target> ser::Serializer for PairSerializer<'target, Target> Ok(()) } - fn serialize_some(self, - value: &T) - -> Result<(), Error> { + fn serialize_some(self, value: &T) -> Result<(), Error> { value.serialize(self) } - fn serialize_seq(self, - _len: Option) - -> Result { + fn serialize_seq(self, _len: Option) -> Result { Err(Error::unsupported_pair()) } @@ -151,56 +142,47 @@ impl<'target, Target> ser::Serializer for PairSerializer<'target, Target> } } - fn serialize_tuple_struct(self, - _name: &'static str, - _len: usize) - -> Result { + fn serialize_tuple_struct( + self, _name: &'static str, _len: usize, + ) -> Result { Err(Error::unsupported_pair()) } - fn serialize_tuple_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize) - -> Result { + fn serialize_tuple_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _len: usize, + ) -> Result { Err(Error::unsupported_pair()) } - fn serialize_map(self, - _len: Option) - -> Result { + fn serialize_map(self, _len: Option) -> Result { Err(Error::unsupported_pair()) } - fn serialize_struct(self, - _name: &'static str, - _len: usize) - -> Result { + fn serialize_struct( + self, _name: &'static str, _len: usize, + ) -> Result { Err(Error::unsupported_pair()) } - fn serialize_struct_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize) - -> Result { + fn serialize_struct_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _len: usize, + ) -> Result { Err(Error::unsupported_pair()) } } impl<'target, Target> ser::SerializeTuple for PairSerializer<'target, Target> - where Target: 'target + UrlEncodedTarget, +where + Target: 'target + UrlEncodedTarget, { type Ok = (); type Error = Error; - fn serialize_element(&mut self, - value: &T) - -> Result<(), Error> { + fn serialize_element( + &mut self, value: &T, + ) -> Result<(), Error> { match mem::replace(&mut self.state, PairState::Done) { PairState::WaitingForKey => { let key_sink = KeySink::new(|key| Ok(key.into())); @@ -209,7 +191,7 @@ impl<'target, Target> ser::SerializeTuple for PairSerializer<'target, Target> key: value.serialize(key_serializer)?, }; Ok(()) - }, + } PairState::WaitingForValue { key } => { let result = { let value_sink = ValueSink::new(self.urlencoder, &key); @@ -219,10 +201,10 @@ impl<'target, Target> ser::SerializeTuple for PairSerializer<'target, Target> if result.is_ok() { self.state = PairState::Done; } else { - self.state = PairState::WaitingForValue { key: key }; + self.state = PairState::WaitingForValue { key }; } result - }, + } PairState::Done => Err(Error::done()), } } diff --git a/src/serde_urlencoded/ser/part.rs b/src/serde_urlencoded/ser/part.rs index 2ce352d2..4874dd34 100644 --- a/src/serde_urlencoded/ser/part.rs +++ b/src/serde_urlencoded/ser/part.rs @@ -1,4 +1,4 @@ -use ::serde; +use serde; use super::super::dtoa; use super::super::itoa; @@ -11,25 +11,22 @@ pub struct PartSerializer { impl PartSerializer { pub fn new(sink: S) -> Self { - PartSerializer { sink: sink } + PartSerializer { sink } } } pub trait Sink: Sized { type Ok; - fn serialize_static_str(self, - value: &'static str) - -> Result; + fn serialize_static_str(self, value: &'static str) -> Result; fn serialize_str(self, value: &str) -> Result; fn serialize_string(self, value: String) -> Result; fn serialize_none(self) -> Result; - fn serialize_some - (self, - value: &T) - -> Result; + fn serialize_some( + self, value: &T, + ) -> Result; fn unsupported(self) -> Error; } @@ -46,7 +43,8 @@ impl serde::ser::Serializer for PartSerializer { type SerializeStructVariant = serde::ser::Impossible; fn serialize_bool(self, v: bool) -> Result { - self.sink.serialize_static_str(if v { "true" } else { "false" }) + self.sink + .serialize_static_str(if v { "true" } else { "false" }) } fn serialize_i8(self, v: i8) -> Result { @@ -109,32 +107,25 @@ impl serde::ser::Serializer for PartSerializer { } fn serialize_unit_struct(self, name: &'static str) -> Result { - self.sink.serialize_static_str(name.into()) + self.sink.serialize_static_str(name) } - fn serialize_unit_variant(self, - _name: &'static str, - _variant_index: u32, - variant: &'static str) - -> Result { - self.sink.serialize_static_str(variant.into()) + fn serialize_unit_variant( + self, _name: &'static str, _variant_index: u32, variant: &'static str, + ) -> Result { + self.sink.serialize_static_str(variant) } - fn serialize_newtype_struct - (self, - _name: &'static str, - value: &T) - -> Result { + fn serialize_newtype_struct( + self, _name: &'static str, value: &T, + ) -> Result { value.serialize(self) } - fn serialize_newtype_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _value: &T) - -> Result { + fn serialize_newtype_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _value: &T, + ) -> Result { Err(self.sink.unsupported()) } @@ -142,68 +133,55 @@ impl serde::ser::Serializer for PartSerializer { self.sink.serialize_none() } - fn serialize_some(self, - value: &T) - -> Result { + fn serialize_some( + self, value: &T, + ) -> Result { self.sink.serialize_some(value) } - fn serialize_seq(self, - _len: Option) - -> Result { + fn serialize_seq(self, _len: Option) -> Result { Err(self.sink.unsupported()) } - fn serialize_tuple(self, - _len: usize) - -> Result { + fn serialize_tuple(self, _len: usize) -> Result { Err(self.sink.unsupported()) } - fn serialize_tuple_struct(self, - _name: &'static str, - _len: usize) - -> Result { + fn serialize_tuple_struct( + self, _name: &'static str, _len: usize, + ) -> Result { Err(self.sink.unsupported()) } - fn serialize_tuple_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize) - -> Result { + fn serialize_tuple_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _len: usize, + ) -> Result { Err(self.sink.unsupported()) } - fn serialize_map(self, - _len: Option) - -> Result { + fn serialize_map(self, _len: Option) -> Result { Err(self.sink.unsupported()) } - fn serialize_struct(self, - _name: &'static str, - _len: usize) - -> Result { + fn serialize_struct( + self, _name: &'static str, _len: usize, + ) -> Result { Err(self.sink.unsupported()) } - fn serialize_struct_variant - (self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize) - -> Result { + fn serialize_struct_variant( + self, _name: &'static str, _variant_index: u32, _variant: &'static str, + _len: usize, + ) -> Result { Err(self.sink.unsupported()) } } impl PartSerializer { fn serialize_integer(self, value: I) -> Result - where I: itoa::Integer, + where + I: itoa::Integer, { let mut buf = [b'\0'; 20]; let len = itoa::write(&mut buf[..], value).unwrap(); @@ -212,7 +190,8 @@ impl PartSerializer { } fn serialize_floating(self, value: F) -> Result - where F: dtoa::Floating, + where + F: dtoa::Floating, { let mut buf = [b'\0'; 24]; let len = dtoa::write(&mut buf[..], value).unwrap(); diff --git a/src/serde_urlencoded/ser/value.rs b/src/serde_urlencoded/ser/value.rs index ef63b010..3c47739f 100644 --- a/src/serde_urlencoded/ser/value.rs +++ b/src/serde_urlencoded/ser/value.rs @@ -1,32 +1,32 @@ -use super::super::ser::Error; use super::super::ser::part::{PartSerializer, Sink}; +use super::super::ser::Error; use serde::ser::Serialize; use std::str; use url::form_urlencoded::Serializer as UrlEncodedSerializer; use url::form_urlencoded::Target as UrlEncodedTarget; pub struct ValueSink<'key, 'target, Target> - where Target: 'target + UrlEncodedTarget, +where + Target: 'target + UrlEncodedTarget, { urlencoder: &'target mut UrlEncodedSerializer, key: &'key str, } impl<'key, 'target, Target> ValueSink<'key, 'target, Target> - where Target: 'target + UrlEncodedTarget, +where + Target: 'target + UrlEncodedTarget, { - pub fn new(urlencoder: &'target mut UrlEncodedSerializer, - key: &'key str) - -> Self { - ValueSink { - urlencoder: urlencoder, - key: key, - } + pub fn new( + urlencoder: &'target mut UrlEncodedSerializer, key: &'key str, + ) -> Self { + ValueSink { urlencoder, key } } } impl<'key, 'target, Target> Sink for ValueSink<'key, 'target, Target> - where Target: 'target + UrlEncodedTarget, +where + Target: 'target + UrlEncodedTarget, { type Ok = (); @@ -47,9 +47,9 @@ impl<'key, 'target, Target> Sink for ValueSink<'key, 'target, Target> Ok(()) } - fn serialize_some(self, - value: &T) - -> Result { + fn serialize_some( + self, value: &T, + ) -> Result { value.serialize(PartSerializer::new(self)) } diff --git a/tests/test_client.rs b/tests/test_client.rs index d128fa31..cf20fb8b 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -443,7 +443,12 @@ fn test_default_headers() { "\"" ))); - let request_override = srv.get().header("User-Agent", "test").header("Accept-Encoding", "over_test").finish().unwrap(); + let request_override = srv + .get() + .header("User-Agent", "test") + .header("Accept-Encoding", "over_test") + .finish() + .unwrap(); let repr_override = format!("{:?}", request_override); assert!(repr_override.contains("\"user-agent\": \"test\"")); assert!(repr_override.contains("\"accept-encoding\": \"over_test\"")); diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index bc65b93f..c86a3e9c 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -94,7 +94,7 @@ fn test_query_extractor() { #[derive(Deserialize, Debug)] pub enum ResponseType { Token, - Code + Code, } #[derive(Debug, Deserialize)] @@ -122,13 +122,24 @@ fn test_query_enum_extractor() { // read response let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }")); + assert_eq!( + bytes, + Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }") + ); - let request = srv.get().uri(srv.url("/index.html?id=64&response_type=Co")).finish().unwrap(); + let request = srv + .get() + .uri(srv.url("/index.html?id=64&response_type=Co")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); - let request = srv.get().uri(srv.url("/index.html?response_type=Code")).finish().unwrap(); + let request = srv + .get() + .uri(srv.url("/index.html?response_type=Code")) + .finish() + .unwrap(); let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } From 2dd57a48d63c62006bc56eef416ba45c9d45a5e5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Jul 2018 11:33:29 +0600 Subject: [PATCH 0510/1635] checks nested scopes in has_resource() --- CHANGES.md | 4 +- src/helpers.rs | 10 +-- src/httprequest.rs | 28 ++++---- src/router.rs | 156 ++++++++++++++++++++++++++++++++++----------- src/scope.rs | 4 ++ 5 files changed, 143 insertions(+), 59 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6c72e3e2..708498f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,10 @@ # Changes -## [0.7.0] - 2018-07-10 +## [0.7.0] - 2018-07-17 ### Added -* Add `.has_prefixed_route()` method to `router::RouteInfo` for route matching with prefix awareness +* Add `.has_prefixed_resource()` method to `router::ResourceInfo` for route matching with prefix awareness * Add `HttpMessage::readlines()` for reading line by line. diff --git a/src/helpers.rs b/src/helpers.rs index a14ce9ff..400b1225 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -92,7 +92,7 @@ impl Handler for NormalizePath { // merge slashes let p = self.re_merge.replace_all(req.path(), "/"); if p.len() != req.path().len() { - if req.resource().has_prefixed_route(p.as_ref()) { + if req.resource().has_prefixed_resource(p.as_ref()) { let p = if !query.is_empty() { p + "?" + query } else { @@ -105,7 +105,7 @@ impl Handler for NormalizePath { // merge slashes and append trailing slash if self.append && !p.ends_with('/') { let p = p.as_ref().to_owned() + "/"; - if req.resource().has_prefixed_route(&p) { + if req.resource().has_prefixed_resource(&p) { let p = if !query.is_empty() { p + "?" + query } else { @@ -120,7 +120,7 @@ impl Handler for NormalizePath { // try to remove trailing slash if p.ends_with('/') { let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_route(p) { + if req.resource().has_prefixed_resource(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -135,7 +135,7 @@ impl Handler for NormalizePath { } else if p.ends_with('/') { // try to remove trailing slash let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_route(p) { + if req.resource().has_prefixed_resource(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -151,7 +151,7 @@ impl Handler for NormalizePath { // append trailing slash if self.append && !req.path().ends_with('/') { let p = req.path().to_owned() + "/"; - if req.resource().has_prefixed_route(&p) { + if req.resource().has_prefixed_resource(&p) { let p = if !query.is_empty() { p + "?" + query } else { diff --git a/src/httprequest.rs b/src/httprequest.rs index 21688877..67afaf03 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -436,10 +436,10 @@ mod tests { router.register_resource(resource); let info = router.default_route_info(); - assert!(info.has_route("/user/test.html")); - assert!(info.has_prefixed_route("/user/test.html")); - assert!(!info.has_route("/test/unknown")); - assert!(!info.has_prefixed_route("/test/unknown")); + assert!(info.has_resource("/user/test.html")); + assert!(info.has_prefixed_resource("/user/test.html")); + assert!(!info.has_resource("/test/unknown")); + assert!(!info.has_prefixed_resource("/test/unknown")); let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") .finish_with_router(router); @@ -468,10 +468,10 @@ mod tests { let mut info = router.default_route_info(); info.set_prefix(7); - assert!(info.has_route("/user/test.html")); - assert!(!info.has_prefixed_route("/user/test.html")); - assert!(!info.has_route("/prefix/user/test.html")); - assert!(info.has_prefixed_route("/prefix/user/test.html")); + assert!(info.has_resource("/user/test.html")); + assert!(!info.has_prefixed_resource("/user/test.html")); + assert!(!info.has_resource("/prefix/user/test.html")); + assert!(info.has_prefixed_resource("/prefix/user/test.html")); let req = TestRequest::with_uri("/prefix/test") .prefix(7) @@ -493,10 +493,10 @@ mod tests { let mut info = router.default_route_info(); info.set_prefix(7); - assert!(info.has_route("/index.html")); - assert!(!info.has_prefixed_route("/index.html")); - assert!(!info.has_route("/prefix/index.html")); - assert!(info.has_prefixed_route("/prefix/index.html")); + assert!(info.has_resource("/index.html")); + assert!(!info.has_prefixed_resource("/index.html")); + assert!(!info.has_resource("/prefix/index.html")); + assert!(info.has_prefixed_resource("/prefix/index.html")); let req = TestRequest::with_uri("/prefix/test") .prefix(7) @@ -518,8 +518,8 @@ mod tests { ); let info = router.default_route_info(); - assert!(!info.has_route("https://youtube.com/watch/unknown")); - assert!(!info.has_prefixed_route("https://youtube.com/watch/unknown")); + assert!(!info.has_resource("https://youtube.com/watch/unknown")); + assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown")); let req = TestRequest::default().finish_with_router(router); let url = req.url_for("youtube", &["oHg5SJYRHA0"]); diff --git a/src/router.rs b/src/router.rs index fe3ecb94..e79dc93d 100644 --- a/src/router.rs +++ b/src/router.rs @@ -37,7 +37,7 @@ enum ResourceItem { /// Interface for application router. pub struct Router { - defs: Rc, + rmap: Rc, patterns: Vec>, resources: Vec>, default: Option>, @@ -46,7 +46,7 @@ pub struct Router { /// Information about current resource #[derive(Clone)] pub struct ResourceInfo { - router: Rc, + rmap: Rc, resource: ResourceId, params: Params, prefix: u16, @@ -57,7 +57,7 @@ impl ResourceInfo { #[inline] pub fn name(&self) -> &str { if let ResourceId::Normal(idx) = self.resource { - self.router.patterns[idx as usize].name() + self.rmap.patterns[idx as usize].0.name() } else { "" } @@ -67,7 +67,7 @@ impl ResourceInfo { #[inline] pub fn rdef(&self) -> Option<&ResourceDef> { if let ResourceId::Normal(idx) = self.resource { - Some(&self.router.patterns[idx as usize]) + Some(&self.rmap.patterns[idx as usize].0) } else { None } @@ -111,7 +111,7 @@ impl ResourceInfo { U: IntoIterator, I: AsRef, { - if let Some(pattern) = self.router.named.get(name) { + if let Some(pattern) = self.rmap.named.get(name) { let path = pattern.resource_path(elements, &req.path()[..(self.prefix as usize)])?; if path.starts_with('/') { @@ -130,24 +130,17 @@ impl ResourceInfo { } } - /// Check if application contains matching route. + /// Check if application contains matching resource. /// /// 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 + /// following path would be recognizable `/test/name` but `has_resource()` call /// would return `false`. - pub fn has_route(&self, path: &str) -> bool { - let path = if path.is_empty() { "/" } else { path }; - - for pattern in &self.router.patterns { - if pattern.is_match(path) { - return true; - } - } - false + pub fn has_resource(&self, path: &str) -> bool { + self.rmap.has_resource(path) } - /// Check if application contains matching route. + /// Check if application contains matching resource. /// /// This method does take `prefix` into account /// but behaves like `has_route` in case `prefix` is not set in the router. @@ -157,18 +150,35 @@ impl ResourceInfo { /// would return `true`. /// It will not match against prefix in case it's not given. For example for `/name` /// with a `/test` prefix would return `false` - pub fn has_prefixed_route(&self, path: &str) -> bool { + pub fn has_prefixed_resource(&self, path: &str) -> bool { let prefix = self.prefix as usize; if prefix >= path.len() { return false; } - self.has_route(&path[prefix..]) + self.rmap.has_resource(&path[prefix..]) } } -struct Inner { +pub(crate) struct ResourceMap { named: HashMap, - patterns: Vec, + patterns: Vec<(ResourceDef, Option>)>, +} + +impl ResourceMap { + pub fn has_resource(&self, path: &str) -> bool { + let path = if path.is_empty() { "/" } else { path }; + + for (pattern, rmap) in &self.patterns { + if let Some(ref rmap) = rmap { + if let Some(plen) = pattern.is_prefix_match(path) { + return rmap.has_resource(&path[plen..]); + } + } else if pattern.is_match(path) { + return true; + } + } + false + } } impl Default for Router { @@ -180,7 +190,7 @@ impl Default for Router { impl Router { pub(crate) fn new() -> Self { Router { - defs: Rc::new(Inner { + rmap: Rc::new(ResourceMap { named: HashMap::new(), patterns: Vec::new(), }), @@ -195,7 +205,7 @@ impl Router { ResourceInfo { params, prefix: 0, - router: self.defs.clone(), + rmap: self.rmap.clone(), resource: ResourceId::Normal(idx), } } @@ -208,7 +218,7 @@ impl Router { ResourceInfo { params, prefix: 0, - router: self.defs.clone(), + rmap: self.rmap.clone(), resource: ResourceId::Default, } } @@ -217,7 +227,7 @@ impl Router { pub(crate) fn default_route_info(&self) -> ResourceInfo { ResourceInfo { params: Params::new(), - router: self.defs.clone(), + rmap: self.rmap.clone(), resource: ResourceId::Default, prefix: 0, } @@ -225,18 +235,18 @@ impl Router { pub(crate) fn register_resource(&mut self, resource: Resource) { { - let inner = Rc::get_mut(&mut self.defs).unwrap(); + let rmap = Rc::get_mut(&mut self.rmap).unwrap(); let name = resource.get_name(); if !name.is_empty() { assert!( - !inner.named.contains_key(name), + !rmap.named.contains_key(name), "Named resource {:?} is registered.", name ); - inner.named.insert(name.to_owned(), resource.rdef().clone()); + rmap.named.insert(name.to_owned(), resource.rdef().clone()); } - inner.patterns.push(resource.rdef().clone()); + rmap.patterns.push((resource.rdef().clone(), None)); } self.patterns .push(ResourcePattern::Resource(resource.rdef().clone())); @@ -244,10 +254,10 @@ impl Router { } pub(crate) fn register_scope(&mut self, mut scope: Scope) { - Rc::get_mut(&mut self.defs) + Rc::get_mut(&mut self.rmap) .unwrap() .patterns - .push(scope.rdef().clone()); + .push((scope.rdef().clone(), Some(scope.router().rmap.clone()))); let filters = scope.take_filters(); self.patterns .push(ResourcePattern::Scope(scope.rdef().clone(), filters)); @@ -259,10 +269,10 @@ impl Router { filters: Option>>>, ) { let rdef = ResourceDef::prefix(path); - Rc::get_mut(&mut self.defs) + Rc::get_mut(&mut self.rmap) .unwrap() .patterns - .push(rdef.clone()); + .push((rdef.clone(), None)); self.resources.push(ResourceItem::Handler(hnd)); self.patterns.push(ResourcePattern::Handler(rdef, filters)); } @@ -298,13 +308,13 @@ impl Router { } pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { - let inner = Rc::get_mut(&mut self.defs).unwrap(); + let rmap = Rc::get_mut(&mut self.rmap).unwrap(); assert!( - !inner.named.contains_key(name), + !rmap.named.contains_key(name), "Named resource {:?} is registered.", name ); - inner.named.insert(name.to_owned(), rdef); + rmap.named.insert(name.to_owned(), rdef); } pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) @@ -406,7 +416,7 @@ impl Router { ResourceInfo { prefix: tail as u16, params: Params::new(), - router: self.defs.clone(), + rmap: self.rmap.clone(), resource: ResourceId::Default, } } @@ -534,6 +544,54 @@ impl ResourceDef { } } + fn is_prefix_match(&self, path: &str) -> Option { + let plen = path.len(); + let path = if path.is_empty() { "/" } else { path }; + + match self.tp { + PatternType::Static(ref s) => if s == path { + Some(plen) + } else { + None + }, + PatternType::Dynamic(ref re, _, len) => { + if let Some(captures) = re.captures(path) { + let mut pos = 0; + let mut passed = false; + for capture in captures.iter() { + if let Some(ref m) = capture { + if !passed { + passed = true; + continue; + } + + pos = m.end(); + } + } + Some(plen + pos + len) + } else { + None + } + } + PatternType::Prefix(ref s) => { + let len = if path == s { + s.len() + } else if path.starts_with(s) + && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) + { + if s.ends_with('/') { + s.len() - 1 + } else { + s.len() + } + } else { + return None; + }; + Some(min(plen, len)) + } + } + } + /// Are the given path and parameters a match against this resource? pub fn match_with_params(&self, req: &Request, plen: usize) -> Option { let path = &req.path()[plen..]; @@ -588,7 +646,9 @@ impl ResourceDef { match self.tp { PatternType::Static(ref s) => if s == path { - Some(Params::with_url(req.url())) + let mut params = Params::with_url(req.url()); + params.set_tail(req.path().len() as u16); + Some(params) } else { None }, @@ -1008,4 +1068,24 @@ mod tests { assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(info.name(), "r2"); } + + #[test] + fn test_has_resource() { + let mut router = Router::<()>::new(); + let scope = Scope::new("/test").resource("/name", |_| "done"); + router.register_scope(scope); + + { + let info = router.default_route_info(); + assert!(!info.has_resource("/test")); + assert!(info.has_resource("/test/name")); + } + + let scope = + Scope::new("/test2").nested("/test10", |s| s.resource("/name", |_| "done")); + router.register_scope(scope); + + let info = router.default_route_info(); + assert!(info.has_resource("/test2/test10/name")); + } } diff --git a/src/scope.rs b/src/scope.rs index a12bcafa..43d07852 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -73,6 +73,10 @@ impl Scope { &self.rdef } + pub(crate) fn router(&self) -> &Router { + self.router.as_ref() + } + #[inline] pub(crate) fn take_filters(&mut self) -> Vec>> { mem::replace(&mut self.filters, Vec::new()) From 2a8c2fb55eca2b404c60f6a96f40ced5741a5d6a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Jul 2018 12:14:24 +0600 Subject: [PATCH 0511/1635] export Payload --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index da2249c8..a740e03e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -244,6 +244,7 @@ pub mod dev { pub use info::ConnectionInfo; pub use json::{JsonBody, JsonConfig}; pub use param::{FromParam, Params}; + pub use payload::{Payload, PayloadHelper}; pub use resource::Resource; pub use route::Route; pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; From bccd7c76715eac46f92191c5fbd4d6391af1aeae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Jul 2018 01:57:57 +0600 Subject: [PATCH 0512/1635] add wait queue size stat to client connector --- src/client/connector.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 1ff0efe5..604af0b8 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -34,6 +34,8 @@ use {HAS_OPENSSL, HAS_TLS}; pub struct ClientConnectorStats { /// Number of waited-on connections pub waits: usize, + /// Size of the wait queue + pub wait_queue: usize, /// Number of reused connections pub reused: usize, /// Number of opened connections @@ -494,8 +496,13 @@ impl ClientConnector { ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); // send stats - let stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); + let mut stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); if let Some(ref mut subscr) = self.subscriber { + if let Some(ref waiters) = self.waiters { + for w in waiters.values() { + stats.wait_queue += w.len(); + } + } let _ = subscr.do_send(stats); } } From 1af5aa3a3ea4d897ca598b0528e1c70ddfc46cff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Jul 2018 02:30:21 +0600 Subject: [PATCH 0513/1635] calculate client request timeout --- src/client/pipeline.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 2192d474..c3f3bf4c 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -71,7 +71,7 @@ pub struct SendRequest { conn: Option>, conn_timeout: Duration, wait_timeout: Duration, - timeout: Option, + timeout: Option, } impl SendRequest { @@ -115,7 +115,7 @@ impl SendRequest { /// Request timeout is the total time before a response must be received. /// Default value is 5 seconds. pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(Delay::new(Instant::now() + timeout)); + self.timeout = Some(timeout); self } @@ -185,9 +185,10 @@ impl Future for SendRequest { _ => IoBody::Done, }; - let timeout = self.timeout.take().unwrap_or_else(|| { - Delay::new(Instant::now() + Duration::from_secs(5)) - }); + let timeout = self + .timeout + .take() + .unwrap_or_else(|| Duration::from_secs(5)); let pl = Box::new(Pipeline { body, @@ -201,7 +202,7 @@ impl Future for SendRequest { decompress: None, should_decompress: self.req.response_decompress(), write_state: RunningState::Running, - timeout: Some(timeout), + timeout: Some(Delay::new(Instant::now() + timeout)), close: self.req.method() == &Method::HEAD, }); self.state = State::Send(pl); From 29a275b0f5594b13ec815215b48cc61712cf0194 Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 17 Jul 2018 08:38:18 +0300 Subject: [PATCH 0514/1635] Session should write percent encoded cookies and add cookie middleware test (#393) * Should write percent encoded cookies to HTTP response * Add cookie middleware test --- src/httpresponse.rs | 4 +-- src/middleware/session.rs | 5 ++- tests/test_middleware.rs | 73 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 83c128d7..2673da2a 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -161,7 +161,7 @@ impl HttpResponse { let mut count: usize = 0; for v in vals { if let Ok(s) = v.to_str() { - if let Ok(c) = Cookie::parse(s) { + if let Ok(c) = Cookie::parse_encoded(s) { if c.name() == name { count += 1; continue; @@ -327,7 +327,7 @@ impl<'a> Iterator for CookieIter<'a> { #[inline] fn next(&mut self) -> Option> { for v in self.iter.by_ref() { - if let Ok(c) = Cookie::parse(v.to_str().ok()?) { + if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { return Some(c); } } diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 9661c2bf..40ba0f4d 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -410,7 +410,7 @@ impl CookieSessionInner { } for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.to_string())?; + let val = HeaderValue::from_str(&cookie.encoded().to_string())?; resp.headers_mut().append(header::SET_COOKIE, val); } @@ -464,6 +464,9 @@ impl CookieSessionInner { /// all session data is lost. The constructors will panic if the key is less /// than 32 bytes in length. /// +/// The backend relies on `cookie` crate to create and read cookies. +/// By default all cookies are percent encoded, but certain symbols may +/// cause troubles when reading cookie, if they are not properly percent encoded. /// /// # Example /// diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 170495c6..9c8ea85d 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -993,3 +993,76 @@ fn test_resource_middleware_async_chain_with_error() { assert_eq!(num2.load(Ordering::Relaxed), 1); assert_eq!(num3.load(Ordering::Relaxed), 1); } + +#[cfg(feature = "session")] +#[test] +fn test_session_storage_middleware() { + use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; + + const SIMPLE_NAME: &'static str = "simple"; + const SIMPLE_PAYLOAD: &'static str = "kantan"; + const COMPLEX_NAME: &'static str = "test"; + const COMPLEX_PAYLOAD: &'static str = "url=https://test.com&generate_204"; + //TODO: investigate how to handle below input + //const COMPLEX_PAYLOAD: &'static str = "FJc%26continue_url%3Dhttp%253A%252F%252Fconnectivitycheck.gstatic.com%252Fgenerate_204"; + + let mut srv = test::TestServer::with_factory(move || { + App::new() + .middleware(SessionStorage::new(CookieSessionBackend::signed(&[0; 32]).secure(false))) + .resource("/index", move |r| { + r.f(|req| { + let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD); + assert!(res.is_ok()); + let value = req.session().get::(COMPLEX_NAME); + assert!(value.is_ok()); + let value = value.unwrap(); + assert!(value.is_some()); + assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); + + let res = req.session().set(SIMPLE_NAME, SIMPLE_PAYLOAD); + assert!(res.is_ok()); + let value = req.session().get::(SIMPLE_NAME); + assert!(value.is_ok()); + let value = value.unwrap(); + assert!(value.is_some()); + assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); + + HttpResponse::Ok() + }) + }).resource("/expect_cookie", move |r| { + r.f(|req| { + let cookies = req.cookies().expect("To get cookies"); + + let value = req.session().get::(SIMPLE_NAME); + assert!(value.is_ok()); + let value = value.unwrap(); + assert!(value.is_some()); + assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); + + let value = req.session().get::(COMPLEX_NAME); + assert!(value.is_ok()); + let value = value.unwrap(); + assert!(value.is_some()); + assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); + + HttpResponse::Ok() + }) + }) + }); + + let request = srv.get().uri(srv.url("/index")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + + assert!(response.headers().contains_key("set-cookie")); + let set_cookie = response.headers().get("set-cookie"); + assert!(set_cookie.is_some()); + let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str"); + + let request = srv.get() + .uri(srv.url("/expect_cookie")) + .header("cookie", set_cookie.split(';').next().unwrap()) + .finish() + .unwrap(); + + srv.execute(request.send()).unwrap(); +} From a7ca5fa5d8ba3499caf41da4f7fc87f7cd85bffb Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 17 Jul 2018 11:10:04 +0300 Subject: [PATCH 0515/1635] Add few missing entries to changelog --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 708498f9..af66895f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,10 @@ ### Changed +* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages. + +* Became possible to use enums with query extractor. Issue [#371](https://github.com/actix/actix-web/issues/371). [Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134) + * Min rustc version is 1.26 * `HttpResponse::into_builder()` now moves cookies into the builder From d43902ee7cfbcfeb3689c72004bb8a9a99c4e639 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Jul 2018 17:23:03 +0600 Subject: [PATCH 0516/1635] proper handling for client connection release --- src/client/connector.rs | 323 +++++++++++++++++++++++----------------- 1 file changed, 188 insertions(+), 135 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 604af0b8..5d66d66d 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -570,6 +570,131 @@ impl ClientConnector { .push_back(waiter); rx } + + fn connect_waiter(&mut self, key: Key, waiter: Waiter, ctx: &mut Context) { + let conn = AcquiredConn(key, Some(self.acq_tx.clone())); + + fut::WrapFuture::::actfuture( + self.resolver.as_ref().unwrap().send( + ResolveConnect::host_and_port(&conn.0.host, conn.0.port) + .timeout(waiter.conn_timeout), + ), + ).map_err(|_, _, _| ()) + .and_then(move |res, act, _| { + #[cfg(feature = "alpn")] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + fut::Either::A( + act.connector + .connect_async(&key.host, stream) + .then(move |res| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e), + )); + } + Ok(stream) => { + let _ = + waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + } + } + Ok(()) + }) + .into_actor(act), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) + } + } + } + + #[cfg(all(feature = "tls", not(feature = "alpn")))] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + fut::Either::A( + act.connector + .connect_async(&conn.0.host, stream) + .then(|res| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e), + )); + } + Ok(stream) => { + let _ = + waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + } + } + Ok(()) + }) + .into_actor(act), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) + } + } + } + + #[cfg(not(any(feature = "alpn", feature = "tls")))] + match res { + Err(err) => { + act.stats.errors += 1; + let _ = waiter.tx.send(Err(err.into())); + fut::err(()) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + let _ = waiter + .tx + .send(Err(ClientConnectorError::SslIsNotSupported)); + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + }; + fut::ok(()) + } + } + }) + .spawn(ctx); + } } impl Handler for ClientConnector { @@ -777,28 +902,78 @@ impl Handler for ClientConnector { } impl StreamHandler for ClientConnector { - fn handle(&mut self, msg: AcquiredConnOperation, _: &mut Context) { + fn handle(&mut self, msg: AcquiredConnOperation, ctx: &mut Context) { let now = Instant::now(); + // check if we have queued up waiters + let waiter = { + let key = match msg { + AcquiredConnOperation::Close(ref conn) => &conn.key, + AcquiredConnOperation::Release(ref conn) => &conn.key, + AcquiredConnOperation::ReleaseKey(ref key) => key, + }; + + if let Some(ref mut waiters) = self.waiters.as_mut().unwrap().get_mut(key) { + loop { + if let Some(waiter) = waiters.pop_front() { + if waiter.tx.is_canceled() { + continue; + } + break Some(waiter); + } else { + break None; + } + } + } else { + None + } + }; + match msg { AcquiredConnOperation::Close(conn) => { - self.release_key(&conn.key); + if let Some(waiter) = waiter { + // create new connection + self.connect_waiter(conn.key.clone(), waiter, ctx); + } else { + self.release_key(&conn.key); + } self.to_close.push(conn); self.stats.closed += 1; } - AcquiredConnOperation::Release(conn) => { - self.release_key(&conn.key); + AcquiredConnOperation::Release(mut conn) => { + let alive = (Instant::now() - conn.ts) < self.conn_lifetime; - // check connection lifetime and the return to available pool - if (Instant::now() - conn.ts) < self.conn_lifetime { - self.available - .entry(conn.key.clone()) - .or_insert_with(VecDeque::new) - .push_back(Conn(Instant::now(), conn)); + if let Some(waiter) = waiter { + // check connection lifetime and the return to available pool + if alive { + // use existing connection + self.stats.reused += 1; + conn.pool = Some(AcquiredConn( + conn.key.clone(), + Some(self.acq_tx.clone()), + )); + let _ = waiter.tx.send(Ok(conn)); + } else { + // create new connection + self.connect_waiter(conn.key.clone(), waiter, ctx); + } + } else { + self.release_key(&conn.key); + if alive { + self.available + .entry(conn.key.clone()) + .or_insert_with(VecDeque::new) + .push_back(Conn(Instant::now(), conn)); + } } } AcquiredConnOperation::ReleaseKey(key) => { - self.release_key(&key); + if let Some(waiter) = waiter { + // create new connection + self.connect_waiter(key, waiter, ctx); + } else { + self.release_key(&key); + } } } @@ -861,130 +1036,8 @@ impl fut::ActorFuture for Maintenance { break; } Acquire::Available => { - let key = key.clone(); - let conn = AcquiredConn(key.clone(), Some(act.acq_tx.clone())); - - fut::WrapFuture::::actfuture( - act.resolver.as_ref().unwrap().send( - ResolveConnect::host_and_port(&conn.0.host, conn.0.port) - .timeout(waiter.conn_timeout), - ), - ).map_err(|_, _, _| ()) - .and_then(move |res, act, _| { - #[cfg(feature = "alpn")] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&key.host, stream) - .then(move |res| { - match res { - Err(e) => { - let _ = waiter.tx.send( - Err(ClientConnectorError::SslError(e))); - } - Ok(stream) => { - let _ = waiter.tx.send( - Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - )), - ); - } - } - Ok(()) - }) - .into_actor(act), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all(feature = "tls", not(feature = "alpn")))] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&conn.0.host, stream) - .then(|res| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send( - Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - )), - ); - } - } - Ok(()) - }) - .into_actor(act), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(not(any(feature = "alpn", feature = "tls")))] - match res { - Err(err) => { - act.stats.errors += 1; - let _ = waiter.tx.send(Err(err.into())); - fut::err(()) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslIsNotSupported, - )); - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - }; - fut::ok(()) - } - } - }) - .spawn(ctx); + // create new connection + act.connect_waiter(key.clone(), waiter, ctx); } } } From 373f2e5028f547dd5aea80d3e99cc54a75272def Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Jul 2018 17:38:16 +0600 Subject: [PATCH 0517/1635] add release stat --- src/client/connector.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client/connector.rs b/src/client/connector.rs index 5d66d66d..b97cfc71 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -46,6 +46,8 @@ pub struct ClientConnectorStats { pub errors: usize, /// Number of connection timeouts pub timeouts: usize, + /// Number of released connections + pub released: usize, } #[derive(Debug)] @@ -904,6 +906,7 @@ impl Handler for ClientConnector { impl StreamHandler for ClientConnector { fn handle(&mut self, msg: AcquiredConnOperation, ctx: &mut Context) { let now = Instant::now(); + self.stats.released += 1; // check if we have queued up waiters let waiter = { From 85672d1379d54dd3521afcd9ab3a2e2faf77e403 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Jul 2018 01:23:56 +0600 Subject: [PATCH 0518/1635] fix client connector wait queue --- src/client/connector.rs | 420 ++++++++++++++++------------------------ src/client/pipeline.rs | 15 +- 2 files changed, 176 insertions(+), 259 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index b97cfc71..c95c47cd 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -46,8 +46,6 @@ pub struct ClientConnectorStats { pub errors: usize, /// Number of connection timeouts pub timeouts: usize, - /// Number of released connections - pub released: usize, } #[derive(Debug)] @@ -413,14 +411,14 @@ impl ClientConnector { } if self.limit_per_host > 0 { if let Some(per_host) = self.acquired_per_host.get(key) { - if self.limit_per_host >= *per_host { + if *per_host >= self.limit_per_host { return Acquire::NotAvailable; } } } } else if self.limit_per_host > 0 { if let Some(per_host) = self.acquired_per_host.get(key) { - if self.limit_per_host >= *per_host { + if *per_host >= self.limit_per_host { return Acquire::NotAvailable; } } @@ -469,7 +467,9 @@ impl ClientConnector { } fn release_key(&mut self, key: &Key) { - self.acquired -= 1; + if self.acquired > 0 { + self.acquired -= 1; + } let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { *per_host } else { @@ -514,23 +514,23 @@ impl ClientConnector { let mut next = None; for waiters in self.waiters.as_mut().unwrap().values_mut() { - let mut idx = 0; - while idx < waiters.len() { - if waiters[idx].wait <= now { + let mut new_waiters = VecDeque::new(); + while let Some(waiter) = waiters.pop_front() { + if waiter.wait <= now { self.stats.timeouts += 1; - let waiter = waiters.swap_remove_back(idx).unwrap(); let _ = waiter.tx.send(Err(ClientConnectorError::Timeout)); } else { if let Some(n) = next { - if waiters[idx].wait < n { - next = Some(waiters[idx].wait); + if waiter.wait < n { + next = Some(waiter.wait); } } else { - next = Some(waiters[idx].wait); + next = Some(waiter.wait); } - idx += 1; + new_waiters.push_back(waiter); } } + *waiters = new_waiters; } if next.is_some() { @@ -573,20 +573,56 @@ impl ClientConnector { rx } - fn connect_waiter(&mut self, key: Key, waiter: Waiter, ctx: &mut Context) { - let conn = AcquiredConn(key, Some(self.acq_tx.clone())); + fn check_availibility(&mut self, ctx: &mut Context) { + // check waiters + let mut act_waiters = self.waiters.take().unwrap(); + for (key, ref mut waiters) in &mut act_waiters { + while let Some(waiter) = waiters.pop_front() { + if waiter.tx.is_canceled() { + continue; + } + + match self.acquire(key) { + Acquire::Acquired(mut conn) => { + // use existing connection + self.stats.reused += 1; + conn.pool = + Some(AcquiredConn(key.clone(), Some(self.acq_tx.clone()))); + let _ = waiter.tx.send(Ok(conn)); + } + Acquire::NotAvailable => { + waiters.push_front(waiter); + break; + } + Acquire::Available => { + // create new connection + self.connect_waiter(key.clone(), waiter, ctx); + } + } + } + } + + self.waiters = Some(act_waiters); + } + + fn connect_waiter(&mut self, key: Key, waiter: Waiter, ctx: &mut Context) { + let conn = AcquiredConn(key.clone(), Some(self.acq_tx.clone())); + + let key2 = key.clone(); fut::WrapFuture::::actfuture( self.resolver.as_ref().unwrap().send( ResolveConnect::host_and_port(&conn.0.host, conn.0.port) .timeout(waiter.conn_timeout), ), - ).map_err(|_, _, _| ()) + ).map_err(move |_, act, _| { + act.release_key(&key2); + () + }) .and_then(move |res, act, _| { #[cfg(feature = "alpn")] match res { Err(err) => { - act.stats.errors += 1; let _ = waiter.tx.send(Err(err.into())); fut::Either::B(fut::err(())) } @@ -596,7 +632,8 @@ impl ClientConnector { fut::Either::A( act.connector .connect_async(&key.host, stream) - .then(move |res| { + .into_actor(act) + .then(move |res, act, _| { match res { Err(e) => { let _ = waiter.tx.send(Err( @@ -612,9 +649,8 @@ impl ClientConnector { ))); } } - Ok(()) - }) - .into_actor(act), + fut::ok(()) + }), ) } else { let _ = waiter.tx.send(Ok(Connection::new( @@ -630,7 +666,6 @@ impl ClientConnector { #[cfg(all(feature = "tls", not(feature = "alpn")))] match res { Err(err) => { - act.stats.errors += 1; let _ = waiter.tx.send(Err(err.into())); fut::Either::B(fut::err(())) } @@ -640,7 +675,8 @@ impl ClientConnector { fut::Either::A( act.connector .connect_async(&conn.0.host, stream) - .then(|res| { + .into_actor(act) + .then(move |res, _, _| { match res { Err(e) => { let _ = waiter.tx.send(Err( @@ -656,9 +692,8 @@ impl ClientConnector { ))); } } - Ok(()) - }) - .into_actor(act), + fut::ok(()) + }), ) } else { let _ = waiter.tx.send(Ok(Connection::new( @@ -674,7 +709,6 @@ impl ClientConnector { #[cfg(not(any(feature = "alpn", feature = "tls")))] match res { Err(err) => { - act.stats.errors += 1; let _ = waiter.tx.send(Err(err.into())); fut::err(()) } @@ -725,7 +759,7 @@ impl Handler for ClientConnector { impl Handler for ClientConnector { type Result = ActorResponse; - fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: Connect, ctx: &mut Self::Context) -> Self::Result { let uri = &msg.uri; let wait_timeout = msg.wait_timeout; let conn_timeout = msg.conn_timeout; @@ -761,239 +795,142 @@ impl Handler for ClientConnector { // check pause state if self.paused.is_paused() { - let rx = self.wait_for(key, wait_timeout, conn_timeout); + let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); self.stats.waits += 1; return ActorResponse::async( rx.map_err(|_| ClientConnectorError::Disconnected) .into_actor(self) - .and_then(|res, _, _| match res { + .and_then(move |res, act, ctx| match res { Ok(conn) => fut::ok(conn), - Err(err) => fut::err(err), + Err(err) => { + match err { + ClientConnectorError::Timeout => (), + _ => { + act.release_key(&key); + } + } + act.stats.errors += 1; + act.check_availibility(ctx); + fut::err(err) + } + }), + ); + } + + // do not re-use websockets connection + if !proto.is_http() { + let (tx, rx) = oneshot::channel(); + let wait = Instant::now() + wait_timeout; + let waiter = Waiter { + tx, + wait, + conn_timeout, + }; + self.connect_waiter(key.clone(), waiter, ctx); + + return ActorResponse::async( + rx.map_err(|_| ClientConnectorError::Disconnected) + .into_actor(self) + .and_then(move |res, act, ctx| match res { + Ok(conn) => fut::ok(conn), + Err(err) => { + act.stats.errors += 1; + act.release_key(&key); + act.check_availibility(ctx); + fut::err(err) + } }), ); } // acquire connection - let pool = if proto.is_http() { - match self.acquire(&key) { - Acquire::Acquired(mut conn) => { - // use existing connection - conn.pool = Some(AcquiredConn(key, Some(self.acq_tx.clone()))); - self.stats.reused += 1; - return ActorResponse::async(fut::ok(conn)); - } - Acquire::NotAvailable => { - // connection is not available, wait - let rx = self.wait_for(key, wait_timeout, conn_timeout); - self.stats.waits += 1; - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(|res, _, _| match res { - Ok(conn) => fut::ok(conn), - Err(err) => fut::err(err), - }), - ); - } - Acquire::Available => Some(self.acq_tx.clone()), + match self.acquire(&key) { + Acquire::Acquired(mut conn) => { + // use existing connection + conn.pool = Some(AcquiredConn(key, Some(self.acq_tx.clone()))); + self.stats.reused += 1; + ActorResponse::async(fut::ok(conn)) } - } else { - None - }; - let conn = AcquiredConn(key, pool); + Acquire::NotAvailable => { + // connection is not available, wait + let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); + self.stats.waits += 1; - { - ActorResponse::async( - self.resolver - .as_ref() - .unwrap() - .send( - ResolveConnect::host_and_port(&conn.0.host, port) - .timeout(conn_timeout), - ) - .into_actor(self) - .map_err(|_, _, _| ClientConnectorError::Disconnected) - .and_then(move |res, act, _| { - #[cfg(feature = "alpn")] - match res { + ActorResponse::async( + rx.map_err(|_| ClientConnectorError::Disconnected) + .into_actor(self) + .and_then(move |res, act, ctx| match res { + Ok(conn) => fut::ok(conn), Err(err) => { - act.stats.opened += 1; - fut::Either::B(fut::err(err.into())) - } - Ok(stream) => { - act.stats.opened += 1; - if proto.is_secure() { - fut::Either::A( - act.connector - .connect_async(&conn.0.host, stream) - .map_err(ClientConnectorError::SslError) - .map(|stream| { - Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ) - }) - .into_actor(act), - ) - } else { - fut::Either::B(fut::ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))) + match err { + ClientConnectorError::Timeout => (), + _ => { + act.release_key(&key); + } } + act.stats.errors += 1; + act.check_availibility(ctx); + fut::err(err) } - } + }), + ) + } + Acquire::Available => { + let (tx, rx) = oneshot::channel(); + let wait = Instant::now() + wait_timeout; + let waiter = Waiter { + tx, + wait, + conn_timeout, + }; + self.connect_waiter(key.clone(), waiter, ctx); - #[cfg(all(feature = "tls", not(feature = "alpn")))] - match res { + ActorResponse::async( + rx.map_err(|_| ClientConnectorError::Disconnected) + .into_actor(self) + .and_then(move |res, act, ctx| match res { + Ok(conn) => fut::ok(conn), Err(err) => { - act.stats.opened += 1; - fut::Either::B(fut::err(err.into())) + act.stats.errors += 1; + act.release_key(&key); + act.check_availibility(ctx); + fut::err(err) } - Ok(stream) => { - act.stats.opened += 1; - if proto.is_secure() { - fut::Either::A( - act.connector - .connect_async(&conn.0.host, stream) - .map_err(ClientConnectorError::SslError) - .map(|stream| { - Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ) - }) - .into_actor(act), - ) - } else { - fut::Either::B(fut::ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))) - } - } - } - - #[cfg(not(any(feature = "alpn", feature = "tls")))] - match res { - Err(err) => { - act.stats.opened += 1; - fut::err(err.into()) - } - Ok(stream) => { - act.stats.opened += 1; - if proto.is_secure() { - fut::err(ClientConnectorError::SslIsNotSupported) - } else { - fut::ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - )) - } - } - } - }), - ) + }), + ) + } } } } impl StreamHandler for ClientConnector { fn handle(&mut self, msg: AcquiredConnOperation, ctx: &mut Context) { - let now = Instant::now(); - self.stats.released += 1; - - // check if we have queued up waiters - let waiter = { - let key = match msg { - AcquiredConnOperation::Close(ref conn) => &conn.key, - AcquiredConnOperation::Release(ref conn) => &conn.key, - AcquiredConnOperation::ReleaseKey(ref key) => key, - }; - - if let Some(ref mut waiters) = self.waiters.as_mut().unwrap().get_mut(key) { - loop { - if let Some(waiter) = waiters.pop_front() { - if waiter.tx.is_canceled() { - continue; - } - break Some(waiter); - } else { - break None; - } - } - } else { - None - } - }; - match msg { AcquiredConnOperation::Close(conn) => { - if let Some(waiter) = waiter { - // create new connection - self.connect_waiter(conn.key.clone(), waiter, ctx); - } else { - self.release_key(&conn.key); - } + self.release_key(&conn.key); self.to_close.push(conn); self.stats.closed += 1; } - AcquiredConnOperation::Release(mut conn) => { - let alive = (Instant::now() - conn.ts) < self.conn_lifetime; - - if let Some(waiter) = waiter { - // check connection lifetime and the return to available pool - if alive { - // use existing connection - self.stats.reused += 1; - conn.pool = Some(AcquiredConn( - conn.key.clone(), - Some(self.acq_tx.clone()), - )); - let _ = waiter.tx.send(Ok(conn)); - } else { - // create new connection - self.connect_waiter(conn.key.clone(), waiter, ctx); - } + AcquiredConnOperation::Release(conn) => { + self.release_key(&conn.key); + if (Instant::now() - conn.ts) < self.conn_lifetime { + self.available + .entry(conn.key.clone()) + .or_insert_with(VecDeque::new) + .push_back(Conn(Instant::now(), conn)); } else { - self.release_key(&conn.key); - if alive { - self.available - .entry(conn.key.clone()) - .or_insert_with(VecDeque::new) - .push_back(Conn(Instant::now(), conn)); - } + self.to_close.push(conn); + self.stats.closed += 1; } } AcquiredConnOperation::ReleaseKey(key) => { - if let Some(waiter) = waiter { - // create new connection - self.connect_waiter(key, waiter, ctx); - } else { - self.release_key(&key); - } + // closed + self.stats.closed += 1; + self.release_key(&key); } } - // check keep-alive - for conns in self.available.values_mut() { - while !conns.is_empty() { - if (now > conns[0].0) && (now - conns[0].0) > self.conn_keep_alive - || (now - conns[0].1.ts) > self.conn_lifetime - { - let conn = conns.pop_front().unwrap().1; - self.to_close.push(conn); - self.stats.closed += 1; - } else { - break; - } - } - } + self.check_availibility(ctx); } } @@ -1018,35 +955,8 @@ impl fut::ActorFuture for Maintenance { act.collect_waiters(); // check waiters - let mut act_waiters = act.waiters.take().unwrap(); + act.check_availibility(ctx); - for (key, ref mut waiters) in &mut act_waiters { - while let Some(waiter) = waiters.pop_front() { - if waiter.tx.is_canceled() { - continue; - } - - match act.acquire(key) { - Acquire::Acquired(mut conn) => { - // use existing connection - act.stats.reused += 1; - conn.pool = - Some(AcquiredConn(key.clone(), Some(act.acq_tx.clone()))); - let _ = waiter.tx.send(Ok(conn)); - } - Acquire::NotAvailable => { - waiters.push_front(waiter); - break; - } - Acquire::Available => { - // create new connection - act.connect_waiter(key.clone(), waiter, ctx); - } - } - } - } - - act.waiters = Some(act_waiters); Ok(Async::NotReady) } } @@ -1181,14 +1091,14 @@ impl Connection { Connection::new(Key::empty(), None, Box::new(io)) } - /// Close connection pool + /// Close connection pub fn close(mut self) { if let Some(mut pool) = self.pool.take() { pool.close(self) } } - /// Release this connection from the connection pool + /// Release this connection to the connection pool pub fn release(mut self) { if let Some(mut pool) = self.pool.take() { pool.release(self) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index c3f3bf4c..e5538b06 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -17,7 +17,7 @@ use context::{ActorHttpContext, Frame}; use error::Error; use error::PayloadError; use header::ContentEncoding; -use http::Method; +use http::{Method, Uri}; use httpmessage::HttpMessage; use server::input::PayloadStream; use server::WriterState; @@ -203,7 +203,8 @@ impl Future for SendRequest { should_decompress: self.req.response_decompress(), write_state: RunningState::Running, timeout: Some(Delay::new(Instant::now() + timeout)), - close: self.req.method() == &Method::HEAD, + meth: self.req.method().clone(), + path: self.req.uri().clone(), }); self.state = State::Send(pl); } @@ -249,7 +250,8 @@ pub struct Pipeline { should_decompress: bool, write_state: RunningState, timeout: Option, - close: bool, + meth: Method, + path: Uri, } enum IoBody { @@ -283,7 +285,7 @@ impl RunningState { impl Pipeline { fn release_conn(&mut self) { if let Some(conn) = self.conn.take() { - if self.close { + if self.meth == Method::HEAD { conn.close() } else { conn.release() @@ -529,6 +531,11 @@ impl Pipeline { impl Drop for Pipeline { fn drop(&mut self) { if let Some(conn) = self.conn.take() { + debug!( + "Client http transaction is not completed, dropping connection: {:?} {:?}", + self.meth, + self.path, + ); conn.close() } } From 6b10e1eff6fb3455a4a56ea07367d18623198283 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 18 Jul 2018 10:01:28 +0600 Subject: [PATCH 0519/1635] rename PayloadHelper --- src/lib.rs | 2 +- src/multipart.rs | 32 ++++++++++++++++---------------- src/payload.rs | 35 +++++++++++++++++++++++------------ src/ws/client.rs | 6 +++--- src/ws/frame.rs | 28 ++++++++++++++-------------- src/ws/mod.rs | 6 +++--- tests/test_middleware.rs | 16 +++++++++++----- 7 files changed, 71 insertions(+), 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a740e03e..b33f1186 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -244,7 +244,7 @@ pub mod dev { pub use info::ConnectionInfo; pub use json::{JsonBody, JsonConfig}; pub use param::{FromParam, Params}; - pub use payload::{Payload, PayloadHelper}; + pub use payload::{Payload, PayloadBuffer}; pub use resource::Resource; pub use route::Route; pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; diff --git a/src/multipart.rs b/src/multipart.rs index 1735085d..d4b6059f 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -13,7 +13,7 @@ use httparse; use mime; use error::{MultipartError, ParseError, PayloadError}; -use payload::PayloadHelper; +use payload::PayloadBuffer; const MAX_HEADERS: usize = 32; @@ -97,7 +97,7 @@ where safety: Safety::new(), inner: Some(Rc::new(RefCell::new(InnerMultipart { boundary, - payload: PayloadRef::new(PayloadHelper::new(stream)), + payload: PayloadRef::new(PayloadBuffer::new(stream)), state: InnerState::FirstBoundary, item: InnerMultipartItem::None, }))), @@ -133,7 +133,7 @@ impl InnerMultipart where S: Stream, { - fn read_headers(payload: &mut PayloadHelper) -> Poll { + fn read_headers(payload: &mut PayloadBuffer) -> Poll { match payload.read_until(b"\r\n\r\n")? { Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), @@ -164,7 +164,7 @@ where } fn read_boundary( - payload: &mut PayloadHelper, boundary: &str, + payload: &mut PayloadBuffer, boundary: &str, ) -> Poll { // TODO: need to read epilogue match payload.readline()? { @@ -190,7 +190,7 @@ where } fn skip_until_boundary( - payload: &mut PayloadHelper, boundary: &str, + payload: &mut PayloadBuffer, boundary: &str, ) -> Poll { let mut eof = false; loop { @@ -490,7 +490,7 @@ where /// Reads body part content chunk of the specified size. /// The body part must has `Content-Length` header with proper value. fn read_len( - payload: &mut PayloadHelper, size: &mut u64, + payload: &mut PayloadBuffer, size: &mut u64, ) -> Poll, MultipartError> { if *size == 0 { Ok(Async::Ready(None)) @@ -503,7 +503,7 @@ where *size -= len; let ch = chunk.split_to(len as usize); if !chunk.is_empty() { - payload.unread_data(chunk); + payload.unprocessed(chunk); } Ok(Async::Ready(Some(ch))) } @@ -515,14 +515,14 @@ where /// Reads content chunk of body part with unknown length. /// The `Content-Length` header for body part is not necessary. fn read_stream( - payload: &mut PayloadHelper, boundary: &str, + payload: &mut PayloadBuffer, boundary: &str, ) -> Poll, MultipartError> { match payload.read_until(b"\r")? { Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), Async::Ready(Some(mut chunk)) => { if chunk.len() == 1 { - payload.unread_data(chunk); + payload.unprocessed(chunk); match payload.read_exact(boundary.len() + 4)? { Async::NotReady => Ok(Async::NotReady), Async::Ready(None) => Err(MultipartError::Incomplete), @@ -531,12 +531,12 @@ where && &chunk[2..4] == b"--" && &chunk[4..] == boundary.as_bytes() { - payload.unread_data(chunk); + payload.unprocessed(chunk); Ok(Async::Ready(None)) } else { // \r might be part of data stream let ch = chunk.split_to(1); - payload.unread_data(chunk); + payload.unprocessed(chunk); Ok(Async::Ready(Some(ch))) } } @@ -544,7 +544,7 @@ where } else { let to = chunk.len() - 1; let ch = chunk.split_to(to); - payload.unread_data(chunk); + payload.unprocessed(chunk); Ok(Async::Ready(Some(ch))) } } @@ -592,27 +592,27 @@ where } struct PayloadRef { - payload: Rc>>, + payload: Rc>>, } impl PayloadRef where S: Stream, { - fn new(payload: PayloadHelper) -> PayloadRef { + fn new(payload: PayloadBuffer) -> PayloadRef { PayloadRef { payload: Rc::new(payload.into()), } } - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadHelper> + fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer> where 'a: 'b, { // Unsafe: Invariant is inforced by Safety Safety is used as ref counter, // only top most ref can have mutable access to payload. if s.current() { - let payload: &mut PayloadHelper = unsafe { &mut *self.payload.get() }; + let payload: &mut PayloadBuffer = unsafe { &mut *self.payload.get() }; Some(payload) } else { None diff --git a/src/payload.rs b/src/payload.rs index fd4e57af..b20bec65 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -280,18 +280,20 @@ impl Inner { } } -pub struct PayloadHelper { +/// Payload buffer +pub struct PayloadBuffer { len: usize, items: VecDeque, stream: S, } -impl PayloadHelper +impl PayloadBuffer where S: Stream, { + /// Create new `PayloadBuffer` instance pub fn new(stream: S) -> Self { - PayloadHelper { + PayloadBuffer { len: 0, items: VecDeque::new(), stream, @@ -316,6 +318,7 @@ where }) } + /// Read first available chunk of bytes #[inline] pub fn readany(&mut self) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { @@ -330,6 +333,7 @@ where } } + /// Check if buffer contains enough bytes #[inline] pub fn can_read(&mut self, size: usize) -> Poll, PayloadError> { if size <= self.len { @@ -343,6 +347,7 @@ where } } + /// Return reference to the first chunk of data #[inline] pub fn get_chunk(&mut self) -> Poll, PayloadError> { if self.items.is_empty() { @@ -358,6 +363,7 @@ where } } + /// Read exact number of bytes #[inline] pub fn read_exact(&mut self, size: usize) -> Poll, PayloadError> { if size <= self.len { @@ -392,8 +398,9 @@ where } } + /// Remove specified amount if bytes from buffer #[inline] - pub fn drop_payload(&mut self, size: usize) { + pub fn drop_bytes(&mut self, size: usize) { if size <= self.len { self.len -= size; @@ -410,6 +417,7 @@ where } } + /// Copy buffered data pub fn copy(&mut self, size: usize) -> Poll, PayloadError> { if size <= self.len { let mut buf = BytesMut::with_capacity(size); @@ -431,6 +439,7 @@ where } } + /// Read until specified ending pub fn read_until(&mut self, line: &[u8]) -> Poll, PayloadError> { let mut idx = 0; let mut num = 0; @@ -486,16 +495,18 @@ where } } + /// Read bytes until new line delimiter pub fn readline(&mut self) -> Poll, PayloadError> { self.read_until(b"\n") } - pub fn unread_data(&mut self, data: Bytes) { + /// Put unprocessed data back to the buffer + pub fn unprocessed(&mut self, data: Bytes) { self.len += data.len(); self.items.push_front(data); } - #[allow(dead_code)] + /// Get remaining data from the buffer pub fn remaining(&mut self) -> Bytes { self.items .iter_mut() @@ -535,7 +546,7 @@ mod tests { .unwrap() .block_on(lazy(|| { let (_, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + let mut payload = PayloadBuffer::new(payload); assert_eq!(payload.len, 0); assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); @@ -552,7 +563,7 @@ mod tests { .unwrap() .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); sender.feed_data(Bytes::from("data")); @@ -577,7 +588,7 @@ mod tests { .unwrap() .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); @@ -595,7 +606,7 @@ mod tests { .unwrap() .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + let mut payload = PayloadBuffer::new(payload); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); @@ -624,7 +635,7 @@ mod tests { .unwrap() .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); @@ -658,7 +669,7 @@ mod tests { .unwrap() .block_on(lazy(|| { let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadHelper::new(payload); + let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); diff --git a/src/ws/client.rs b/src/ws/client.rs index 4295905a..98922047 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -20,7 +20,7 @@ use body::{Binary, Body}; use error::{Error, UrlParseError}; use header::IntoHeaderValue; use httpmessage::HttpMessage; -use payload::PayloadHelper; +use payload::PayloadBuffer; use client::{ ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError, @@ -275,7 +275,7 @@ impl Client { struct Inner { tx: UnboundedSender, - rx: PayloadHelper>, + rx: PayloadBuffer>, closed: bool, } @@ -431,7 +431,7 @@ impl Future for ClientHandshake { let inner = Inner { tx: self.tx.take().unwrap(), - rx: PayloadHelper::new(resp.payload()), + rx: PayloadBuffer::new(resp.payload()), closed: false, }; diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 70065774..006d322f 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -6,7 +6,7 @@ use std::fmt; use body::Binary; use error::PayloadError; -use payload::PayloadHelper; +use payload::PayloadBuffer; use ws::mask::apply_mask; use ws::proto::{CloseCode, CloseReason, OpCode}; @@ -48,7 +48,7 @@ impl Frame { #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] fn read_copy_md( - pl: &mut PayloadHelper, server: bool, max_size: usize, + pl: &mut PayloadBuffer, server: bool, max_size: usize, ) -> Poll)>, ProtocolError> where S: Stream, @@ -201,7 +201,7 @@ impl Frame { /// Parse the input stream into a frame. pub fn parse( - pl: &mut PayloadHelper, server: bool, max_size: usize, + pl: &mut PayloadBuffer, server: bool, max_size: usize, ) -> Poll, ProtocolError> where S: Stream, @@ -230,7 +230,7 @@ impl Frame { } // remove prefix - pl.drop_payload(idx); + pl.drop_bytes(idx); // no need for body if length == 0 { @@ -393,14 +393,14 @@ mod tests { #[test] fn test_parse() { - let mut buf = PayloadHelper::new(once(Ok(BytesMut::from( + let mut buf = PayloadBuffer::new(once(Ok(BytesMut::from( &[0b0000_0001u8, 0b0000_0001u8][..], ).freeze()))); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(b"1"); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -411,7 +411,7 @@ mod tests { #[test] fn test_parse_length0() { let buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -422,13 +422,13 @@ mod tests { #[test] fn test_parse_length2() { let buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); buf.extend(&[0u8, 4u8][..]); buf.extend(b"1234"); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -439,13 +439,13 @@ mod tests { #[test] fn test_parse_length4() { let buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(b"1234"); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -458,7 +458,7 @@ mod tests { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]); buf.extend(b"0001"); buf.extend(b"1"); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, false, 1024).is_err()); @@ -472,7 +472,7 @@ mod tests { fn test_parse_frame_no_mask() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(&[1u8]); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, true, 1024).is_err()); @@ -486,7 +486,7 @@ mod tests { fn test_parse_frame_max_size() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); buf.extend(&[1u8, 1u8]); - let mut buf = PayloadHelper::new(once(Ok(buf.freeze()))); + let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, true, 1).is_err()); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 05099971..ed44e270 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -52,7 +52,7 @@ use error::{Error, PayloadError, ResponseError}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; -use payload::PayloadHelper; +use payload::PayloadBuffer; mod client; mod context; @@ -252,7 +252,7 @@ pub fn handshake( /// Maps `Payload` stream into stream of `ws::Message` items pub struct WsStream { - rx: PayloadHelper, + rx: PayloadBuffer, closed: bool, max_size: usize, } @@ -264,7 +264,7 @@ where /// Create new websocket frames stream pub fn new(stream: S) -> WsStream { WsStream { - rx: PayloadHelper::new(stream), + rx: PayloadBuffer::new(stream), closed: false, max_size: 65_536, } diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 9c8ea85d..4fa1c81d 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -997,7 +997,9 @@ fn test_resource_middleware_async_chain_with_error() { #[cfg(feature = "session")] #[test] fn test_session_storage_middleware() { - use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; + use actix_web::middleware::session::{ + CookieSessionBackend, RequestSession, SessionStorage, + }; const SIMPLE_NAME: &'static str = "simple"; const SIMPLE_PAYLOAD: &'static str = "kantan"; @@ -1008,7 +1010,9 @@ fn test_session_storage_middleware() { let mut srv = test::TestServer::with_factory(move || { App::new() - .middleware(SessionStorage::new(CookieSessionBackend::signed(&[0; 32]).secure(false))) + .middleware(SessionStorage::new( + CookieSessionBackend::signed(&[0; 32]).secure(false), + )) .resource("/index", move |r| { r.f(|req| { let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD); @@ -1029,9 +1033,10 @@ fn test_session_storage_middleware() { HttpResponse::Ok() }) - }).resource("/expect_cookie", move |r| { + }) + .resource("/expect_cookie", move |r| { r.f(|req| { - let cookies = req.cookies().expect("To get cookies"); + let _cookies = req.cookies().expect("To get cookies"); let value = req.session().get::(SIMPLE_NAME); assert!(value.is_ok()); @@ -1058,7 +1063,8 @@ fn test_session_storage_middleware() { assert!(set_cookie.is_some()); let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str"); - let request = srv.get() + let request = srv + .get() .uri(srv.url("/expect_cookie")) .header("cookie", set_cookie.split(';').next().unwrap()) .finish() From 2988a84e5f0f70171667383396993457de2ceb06 Mon Sep 17 00:00:00 2001 From: Douman Date: Thu, 19 Jul 2018 20:03:45 +0300 Subject: [PATCH 0520/1635] Expose leaked private ContentDisposition (#406) --- src/header/shared/charset.rs | 8 ++++---- src/lib.rs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs index 540dc4f2..b679971b 100644 --- a/src/header/shared/charset.rs +++ b/src/header/shared/charset.rs @@ -66,7 +66,7 @@ pub enum Charset { } impl Charset { - fn name(&self) -> &str { + fn label(&self) -> &str { match *self { Us_Ascii => "US-ASCII", Iso_8859_1 => "ISO-8859-1", @@ -90,7 +90,7 @@ impl Charset { Iso_8859_8_E => "ISO-8859-8-E", Iso_8859_8_I => "ISO-8859-8-I", Gb2312 => "GB2312", - Big5 => "5", + Big5 => "big5", Koi8_R => "KOI8-R", Ext(ref s) => s, } @@ -99,7 +99,7 @@ impl Charset { impl Display for Charset { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.name()) + f.write_str(self.label()) } } @@ -129,7 +129,7 @@ impl FromStr for Charset { "ISO-8859-8-E" => Iso_8859_8_E, "ISO-8859-8-I" => Iso_8859_8_I, "GB2312" => Gb2312, - "5" => Big5, + "big5" => Big5, "KOI8-R" => Koi8_R, s => Ext(s.to_owned()), }) diff --git a/src/lib.rs b/src/lib.rs index b33f1186..0ab4a1be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -266,6 +266,7 @@ pub mod http { /// Various http headers pub mod header { pub use header::*; + pub use header::{ContentDisposition, DispositionType, DispositionParam, Charset, LanguageTag}; } pub use header::ContentEncoding; pub use httpresponse::ConnectionType; From 0925a7691ab9df8e6f1ae56ded68bc55514be5b0 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Thu, 19 Jul 2018 19:04:13 +0200 Subject: [PATCH 0521/1635] ws/context: Increase `write()` visibility to public (#402) This type is introduced to avoid confusion between the `.binary()` and `.write_raw()` methods on WebSocket contexts --- src/ws/client.rs | 6 +++--- src/ws/context.rs | 21 +++++++++++++-------- src/ws/frame.rs | 22 ++++++++++++++-------- src/ws/mod.rs | 2 +- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/ws/client.rs b/src/ws/client.rs index 98922047..18789fef 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -27,7 +27,7 @@ use client::{ Pipeline, SendRequest, SendRequestError, }; -use super::frame::Frame; +use super::frame::{Frame, FramedMessage}; use super::proto::{CloseReason, OpCode}; use super::{Message, ProtocolError, WsWriter}; @@ -529,10 +529,10 @@ pub struct ClientWriter { impl ClientWriter { /// Write payload #[inline] - fn write(&mut self, mut data: Binary) { + fn write(&mut self, mut data: FramedMessage) { let inner = self.inner.borrow_mut(); if !inner.closed { - let _ = inner.tx.unbounded_send(data.take()); + let _ = inner.tx.unbounded_send(data.0.take()); } else { warn!("Trying to write to disconnected response"); } diff --git a/src/ws/context.rs b/src/ws/context.rs index ffdd0b55..4db83df5 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -20,7 +20,7 @@ use context::{ActorHttpContext, Drain, Frame as ContextFrame}; use error::{Error, ErrorInternalServerError, PayloadError}; use httprequest::HttpRequest; -use ws::frame::Frame; +use ws::frame::{Frame, FramedMessage}; use ws::proto::{CloseReason, OpCode}; use ws::{Message, ProtocolError, WsStream, WsWriter}; @@ -132,14 +132,19 @@ where A: Actor, { /// Write payload + /// + /// This is a low-level function that accepts framed messages that should + /// be created using `Frame::message()`. If you want to send text or binary + /// data you should prefer the `text()` or `binary()` convenience functions + /// that handle the framing for you. #[inline] - fn write(&mut self, data: Binary) { + pub fn write_raw(&mut self, data: FramedMessage) { if !self.disconnected { if self.stream.is_none() { self.stream = Some(SmallVec::new()); } let stream = self.stream.as_mut().unwrap(); - stream.push(ContextFrame::Chunk(Some(data))); + stream.push(ContextFrame::Chunk(Some(data.0))); } else { warn!("Trying to write to disconnected response"); } @@ -167,19 +172,19 @@ where /// Send text frame #[inline] pub fn text>(&mut self, text: T) { - self.write(Frame::message(text.into(), OpCode::Text, true, false)); + self.write_raw(Frame::message(text.into(), OpCode::Text, true, false)); } /// Send binary frame #[inline] pub fn binary>(&mut self, data: B) { - self.write(Frame::message(data, OpCode::Binary, true, false)); + self.write_raw(Frame::message(data, OpCode::Binary, true, false)); } /// Send ping frame #[inline] pub fn ping(&mut self, message: &str) { - self.write(Frame::message( + self.write_raw(Frame::message( Vec::from(message), OpCode::Ping, true, @@ -190,7 +195,7 @@ where /// Send pong frame #[inline] pub fn pong(&mut self, message: &str) { - self.write(Frame::message( + self.write_raw(Frame::message( Vec::from(message), OpCode::Pong, true, @@ -201,7 +206,7 @@ where /// Send close frame #[inline] pub fn close(&mut self, reason: Option) { - self.write(Frame::close(reason, false)); + self.write_raw(Frame::close(reason, false)); } /// Check if connection still open diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 006d322f..5e4fd829 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -28,7 +28,7 @@ impl Frame { /// Create a new Close control frame. #[inline] - pub fn close(reason: Option, genmask: bool) -> Binary { + pub fn close(reason: Option, genmask: bool) -> FramedMessage { let payload = match reason { None => Vec::new(), Some(reason) => { @@ -295,7 +295,7 @@ impl Frame { /// Generate binary representation pub fn message>( data: B, code: OpCode, finished: bool, genmask: bool, - ) -> Binary { + ) -> FramedMessage { let payload = data.into(); let one: u8 = if finished { 0x80 | Into::::into(code) @@ -325,7 +325,7 @@ impl Frame { buf }; - if genmask { + let binary = if genmask { let mask = rand::random::(); buf.put_u32_le(mask); buf.extend_from_slice(payload.as_ref()); @@ -335,7 +335,9 @@ impl Frame { } else { buf.put_slice(payload.as_ref()); buf.into() - } + }; + + FramedMessage(binary) } } @@ -372,6 +374,10 @@ impl fmt::Display for Frame { } } +/// `WebSocket` message with framing. +#[derive(Debug)] +pub struct FramedMessage(pub(crate) Binary); + #[cfg(test)] mod tests { use super::*; @@ -502,7 +508,7 @@ mod tests { let mut v = vec![137u8, 4u8]; v.extend(b"data"); - assert_eq!(frame, v.into()); + assert_eq!(frame.0, v.into()); } #[test] @@ -511,7 +517,7 @@ mod tests { let mut v = vec![138u8, 4u8]; v.extend(b"data"); - assert_eq!(frame, v.into()); + assert_eq!(frame.0, v.into()); } #[test] @@ -521,12 +527,12 @@ mod tests { let mut v = vec![136u8, 6u8, 3u8, 232u8]; v.extend(b"data"); - assert_eq!(frame, v.into()); + assert_eq!(frame.0, v.into()); } #[test] fn test_empty_close_frame() { let frame = Frame::close(None, false); - assert_eq!(frame, vec![0x88, 0x00].into()); + assert_eq!(frame.0, vec![0x88, 0x00].into()); } } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index ed44e270..6b37bc7e 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -64,7 +64,7 @@ pub use self::client::{ Client, ClientError, ClientHandshake, ClientReader, ClientWriter, }; pub use self::context::WebsocketContext; -pub use self::frame::Frame; +pub use self::frame::{Frame, FramedMessage}; pub use self::proto::{CloseCode, CloseReason, OpCode}; /// Websocket protocol errors From f6e35a04f0f58f5f94f3d37dd9c03428392027b0 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 20 Jul 2018 07:48:57 +0300 Subject: [PATCH 0522/1635] Just a bit of sanity check for short paths (#409) --- src/httprequest.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/httprequest.rs b/src/httprequest.rs index 67afaf03..83017dfa 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -436,6 +436,7 @@ mod tests { router.register_resource(resource); let info = router.default_route_info(); + assert!(!info.has_prefixed_resource("/use/")); assert!(info.has_resource("/user/test.html")); assert!(info.has_prefixed_resource("/user/test.html")); assert!(!info.has_resource("/test/unknown")); @@ -468,6 +469,7 @@ mod tests { let mut info = router.default_route_info(); info.set_prefix(7); + assert!(!info.has_prefixed_resource("/use/")); assert!(info.has_resource("/user/test.html")); assert!(!info.has_prefixed_resource("/user/test.html")); assert!(!info.has_resource("/prefix/user/test.html")); From a751df258966e1fbe409358f16963dd09bf34e98 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 20 Jul 2018 07:49:25 +0300 Subject: [PATCH 0523/1635] Initial config for static files (#405) --- CHANGES.md | 3 + src/fs.rs | 231 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 202 insertions(+), 32 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index af66895f..ca6feee6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ ### Added +* Add `fs::StaticFileConfig` to provide means of customizing static file services. It allows to map +`mime` to `Content-Disposition`, specify whether to use `ETag` and `Last-Modified` and allowed methods. + * Add `.has_prefixed_resource()` method to `router::ResourceInfo` for route matching with prefix awareness * Add `HttpMessage::readlines()` for reading line by line. diff --git a/src/fs.rs b/src/fs.rs index 14c3818b..31b3725b 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -6,6 +6,7 @@ use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; use std::{cmp, io}; +use std::marker::PhantomData; #[cfg(unix)] use std::os::unix::fs::MetadataExt; @@ -27,6 +28,73 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use param::FromParam; use server::settings::DEFAULT_CPUPOOL; +use header::{ContentDisposition, DispositionParam, DispositionType}; + +///Describes `StaticFiles` configiration +/// +///To configure actix's static resources you need +///to define own configiration type and implement any method +///you wish to customize. +///As trait implements reasonable defaults for Actix. +/// +///## Example +/// +///```rust +/// extern crate mime; +/// extern crate actix_web; +/// use actix_web::http::header::DispositionType; +/// use actix_web::fs::{StaticFileConfig, NamedFile}; +/// +/// #[derive(Default)] +/// struct MyConfig; +/// +/// impl StaticFileConfig for MyConfig { +/// fn content_disposition_map(typ: mime::Name) -> DispositionType { +/// DispositionType::Attachment +/// } +/// } +/// +/// let file = NamedFile::open_with_config("foo.txt", MyConfig); +///``` +pub trait StaticFileConfig: Default { + ///Describes mapping for mime type to content disposition header + /// + ///By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. + ///Others are mapped to Attachment + fn content_disposition_map(typ: mime::Name) -> DispositionType { + match typ { + mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, + _ => DispositionType::Attachment, + } + } + + ///Describes whether Actix should attempt to calculate `ETag` + /// + ///Defaults to `true` + fn is_use_etag() -> bool { + true + } + + ///Describes whether Actix should use last modified date of file. + /// + ///Defaults to `true` + fn is_use_last_modifier() -> bool { + true + } + + ///Describes allowed methods to access static resources. + /// + ///By default all methods are allowed + fn is_method_allowed(_method: &Method) -> bool { + true + } +} + +///Default content disposition as described in +///[StaticFileConfig](trait.StaticFileConfig.html) +#[derive(Default)] +pub struct DefaultConfig; +impl StaticFileConfig for DefaultConfig {} /// Return the MIME type associated with a filename extension (case-insensitive). /// If `ext` is empty or no associated type for the extension was found, returns @@ -38,7 +106,7 @@ pub fn file_extension_to_mime(ext: &str) -> mime::Mime { /// A file with an associated name. #[derive(Debug)] -pub struct NamedFile { +pub struct NamedFile { path: PathBuf, file: File, content_type: mime::Mime, @@ -47,8 +115,8 @@ pub struct NamedFile { modified: Option, cpu_pool: Option, encoding: Option, - only_get: bool, status_code: StatusCode, + _cd_map: PhantomData, } impl NamedFile { @@ -62,7 +130,21 @@ impl NamedFile { /// let file = NamedFile::open("foo.txt"); /// ``` pub fn open>(path: P) -> io::Result { - use header::{ContentDisposition, DispositionParam, DispositionType}; + Self::open_with_config(path, DefaultConfig) + } +} + +impl NamedFile { + /// Attempts to open a file in read-only mode using provided configiration. + /// + /// # Examples + /// + /// ```rust + /// use actix_web::fs::{DefaultConfig, NamedFile}; + /// + /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); + /// ``` + pub fn open_with_config>(path: P, _: C) -> io::Result> { let path = path.as_ref().to_path_buf(); // Get the name of the file and use it to construct default Content-Type @@ -79,10 +161,7 @@ impl NamedFile { }; let ct = guess_mime_type(&path); - let disposition_type = match ct.type_() { - mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, - _ => DispositionType::Attachment, - }; + let disposition_type = C::content_disposition_map(ct.type_()); let cd = ContentDisposition { disposition: disposition_type, parameters: vec![DispositionParam::Filename( @@ -108,18 +187,11 @@ impl NamedFile { modified, cpu_pool, encoding, - only_get: false, status_code: StatusCode::OK, + _cd_map: PhantomData }) } - /// Allow only GET and HEAD methods - #[inline] - pub fn only_get(mut self) -> Self { - self.only_get = true; - self - } - /// Returns reference to the underlying `File` object. #[inline] pub fn file(&self) -> &File { @@ -218,7 +290,7 @@ impl NamedFile { } } -impl Deref for NamedFile { +impl Deref for NamedFile { type Target = File; fn deref(&self) -> &File { @@ -226,7 +298,7 @@ impl Deref for NamedFile { } } -impl DerefMut for NamedFile { +impl DerefMut for NamedFile { fn deref_mut(&mut self) -> &mut File { &mut self.file } @@ -267,7 +339,7 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool } } -impl Responder for NamedFile { +impl Responder for NamedFile { type Item = HttpResponse; type Error = io::Error; @@ -294,7 +366,7 @@ impl Responder for NamedFile { return Ok(resp.streaming(reader)); } - if self.only_get && *req.method() != Method::GET && *req.method() != Method::HEAD + if !C::is_method_allowed(req.method()) { return Ok(HttpResponse::MethodNotAllowed() .header(header::CONTENT_TYPE, "text/plain") @@ -302,8 +374,14 @@ impl Responder for NamedFile { .body("This resource only supports GET and HEAD.")); } - let etag = self.etag(); - let last_modified = self.last_modified(); + let etag = match C::is_use_etag() { + true => self.etag(), + false => None, + }; + let last_modified = match C::is_use_last_modifier() { + true => self.last_modified(), + false => None, + }; // check preconditions let precondition_failed = if !any_match(etag.as_ref(), req) { @@ -559,7 +637,7 @@ fn directory_listing( /// .finish(); /// } /// ``` -pub struct StaticFiles { +pub struct StaticFiles { directory: PathBuf, index: Option, show_index: bool, @@ -568,6 +646,7 @@ pub struct StaticFiles { renderer: Box>, _chunk_size: usize, _follow_symlinks: bool, + _cd_map: PhantomData, } impl StaticFiles { @@ -577,10 +656,7 @@ impl StaticFiles { /// By default pool with 20 threads is used. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. pub fn new>(dir: T) -> Result, Error> { - // use default CpuPool - let pool = { DEFAULT_CPUPOOL.lock().clone() }; - - StaticFiles::with_pool(dir, pool) + Self::with_config(dir, DefaultConfig) } /// Create new `StaticFiles` instance for specified base directory and @@ -588,6 +664,26 @@ impl StaticFiles { pub fn with_pool>( dir: T, pool: CpuPool, ) -> Result, Error> { + Self::with_config_pool(dir, pool, DefaultConfig) + } +} + +impl StaticFiles { + /// Create new `StaticFiles` instance for specified base directory. + /// + /// Identical with `new` but allows to specify configiration to use. + pub fn with_config>(dir: T, config: C) -> Result, Error> { + // use default CpuPool + let pool = { DEFAULT_CPUPOOL.lock().clone() }; + + StaticFiles::with_config_pool(dir, pool, config) + } + + /// Create new `StaticFiles` instance for specified base directory with config and + /// `CpuPool`. + pub fn with_config_pool>( + dir: T, pool: CpuPool, _: C + ) -> Result, Error> { let dir = dir.into().canonicalize()?; if !dir.is_dir() { @@ -605,6 +701,7 @@ impl StaticFiles { renderer: Box::new(directory_listing), _chunk_size: 0, _follow_symlinks: false, + _cd_map: PhantomData }) } @@ -631,13 +728,13 @@ impl StaticFiles { /// /// Redirects to specific index file for directory "/" instead of /// showing files listing. - pub fn index_file>(mut self, index: T) -> StaticFiles { + pub fn index_file>(mut self, index: T) -> StaticFiles { self.index = Some(index.into()); self } /// Sets default handler which is used when no matched file could be found. - pub fn default_handler>(mut self, handler: H) -> StaticFiles { + pub fn default_handler>(mut self, handler: H) -> StaticFiles { self.default = Box::new(WrapHandler::new(handler)); self } @@ -672,7 +769,7 @@ impl StaticFiles { Err(StaticFileError::IsDirectory.into()) } } else { - NamedFile::open(path)? + NamedFile::open_with_config(path, C::default())? .set_cpu_pool(self.cpu_pool.clone()) .respond_to(&req)? .respond_to(&req) @@ -680,7 +777,7 @@ impl StaticFiles { } } -impl Handler for StaticFiles { +impl Handler for StaticFiles { type Result = Result, Error>; fn handle(&self, req: &HttpRequest) -> Self::Result { @@ -920,6 +1017,56 @@ mod tests { ); } + #[derive(Default)] + pub struct AllAttachmentConfig; + impl StaticFileConfig for AllAttachmentConfig { + fn content_disposition_map(_typ: mime::Name) -> DispositionType { + DispositionType::Attachment + } + } + + #[derive(Default)] + pub struct AllInlineConfig; + impl StaticFileConfig for AllInlineConfig { + fn content_disposition_map(_typ: mime::Name) -> DispositionType { + DispositionType::Inline + } + } + + #[test] + fn test_named_file_image_attachment_and_custom_config() { + let file = NamedFile::open_with_config("tests/test.png", AllAttachmentConfig) + .unwrap() + .set_cpu_pool(CpuPool::new(1)); + + let req = TestRequest::default().finish(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.png\"" + ); + + let file = NamedFile::open_with_config("tests/test.png", AllInlineConfig) + .unwrap() + .set_cpu_pool(CpuPool::new(1)); + + let req = TestRequest::default().finish(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"test.png\"" + ); + + } + #[test] fn test_named_file_binary() { let mut file = NamedFile::open("tests/test.binary") @@ -1143,12 +1290,32 @@ mod tests { assert_eq!(bytes, data); } + #[derive(Default)] + pub struct OnlyMethodHeadConfig; + impl StaticFileConfig for OnlyMethodHeadConfig { + fn is_method_allowed(method: &Method) -> bool { + match *method { + Method::HEAD => true, + _ => false + } + } + } + #[test] fn test_named_file_not_allowed() { + let file = NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); let req = TestRequest::default().method(Method::POST).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp = file.only_get().respond_to(&req).unwrap(); + let file = NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + let req = TestRequest::default().method(Method::PUT).finish(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let file = NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + let req = TestRequest::default().method(Method::GET).finish(); + let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } From 2043bb5ece54391f898fe7aab5b388d0f268839d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 20 Jul 2018 10:20:41 -0700 Subject: [PATCH 0524/1635] do not reallocate waiters --- src/client/connector.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index c95c47cd..6d391af8 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -509,28 +509,30 @@ impl ClientConnector { } } + // TODO: waiters should be sorted by deadline. maybe timewheel? fn collect_waiters(&mut self) { let now = Instant::now(); let mut next = None; for waiters in self.waiters.as_mut().unwrap().values_mut() { - let mut new_waiters = VecDeque::new(); - while let Some(waiter) = waiters.pop_front() { - if waiter.wait <= now { + let mut idx = 0; + while idx < waiters.len() { + let wait = waiters[idx].wait; + if wait <= now { self.stats.timeouts += 1; + let waiter = waiters.swap_remove_back(idx).unwrap(); let _ = waiter.tx.send(Err(ClientConnectorError::Timeout)); } else { if let Some(n) = next { - if waiter.wait < n { - next = Some(waiter.wait); + if wait < n { + next = Some(wait); } } else { - next = Some(waiter.wait); + next = Some(wait); } - new_waiters.push_back(waiter); + idx += 1; } } - *waiters = new_waiters; } if next.is_some() { From 8cb510293d2103505c08ccc7e1ea5416be4d0bb5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 20 Jul 2018 14:10:41 -0700 Subject: [PATCH 0525/1635] update changes --- CHANGES.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ca6feee6..7564a559 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,19 +1,21 @@ # Changes -## [0.7.0] - 2018-07-17 +## [0.7.0] - 2018-07-21 ### Added -* Add `fs::StaticFileConfig` to provide means of customizing static file services. It allows to map -`mime` to `Content-Disposition`, specify whether to use `ETag` and `Last-Modified` and allowed methods. +* Add `fs::StaticFileConfig` to provide means of customizing static + file services. It allows to map `mime` to `Content-Disposition`, + specify whether to use `ETag` and `Last-Modified` and allowed methods. -* Add `.has_prefixed_resource()` method to `router::ResourceInfo` for route matching with prefix awareness +* Add `.has_prefixed_resource()` method to `router::ResourceInfo` + for route matching with prefix awareness * Add `HttpMessage::readlines()` for reading line by line. * Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. -* Add method to configure custom error handler to Form extractor. +* Add method to configure custom error handler to `Form` extractor. * Add methods to `HttpResponse` to retrieve, add, and delete cookies @@ -35,17 +37,19 @@ ### Changed +* Min rustc version is 1.26 + +* Use tokio instead of tokio-core + * `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages. -* Became possible to use enums with query extractor. Issue [#371](https://github.com/actix/actix-web/issues/371). [Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134) - -* Min rustc version is 1.26 +* Became possible to use enums with query extractor. + Issue [#371](https://github.com/actix/actix-web/issues/371). + [Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134) * `HttpResponse::into_builder()` now moves cookies into the builder instead of dropping them -* Use tokio instead of tokio-core - * For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self` * `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest` From 7138bb2f293a0005e6ef3e77f0512a2891860a08 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 21 Jul 2018 01:00:50 -0700 Subject: [PATCH 0526/1635] update migration --- MIGRATION.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index f04aa2d2..29bf0c34 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,31 @@ ## 0.7 +* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload + use `HttpMessage::payload()` method. + + instead of + + ```rust + fn index(req: HttpRequest) -> impl Responder { + req + .from_err() + .fold(...) + .... + } + ``` + + use `.payload()` + + ```rust + fn index(req: HttpRequest) -> impl Responder { + req + .payload() // <- get request payload stream + .from_err() + .fold(...) + .... + } + ``` + * [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) trait uses `&HttpRequest` instead of `&mut HttpRequest`. From f6499d9ba5c20a6ab78d4f8173082da420393c46 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 21 Jul 2018 04:19:02 -0700 Subject: [PATCH 0527/1635] publish stable docs on actix.rs site --- Cargo.toml | 2 +- Makefile | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4371254..1d6b1663 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-web/" +documentation = "https://actix.rs/api/actix-web/stable/actix_web/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::http-client", diff --git a/Makefile b/Makefile index 47886bbe..e3b8b2cf 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: default build test doc book clean -CARGO_FLAGS := --features "$(FEATURES) alpn" +CARGO_FLAGS := --features "$(FEATURES) alpn tls" default: test diff --git a/README.md b/README.md index af66baea..632a33dc 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [User Guide](https://actix.rs/docs/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (Releases)](https://docs.rs/actix-web/0.6.11/actix_web/) +* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) * Minimum supported Rust version: 1.26 or later From 4862227df9227f87ca2aa0565220778d2ac72c7e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 21 Jul 2018 05:58:08 -0700 Subject: [PATCH 0528/1635] fix not implemented panic #410 --- CHANGES.md | 7 +++++++ Cargo.toml | 2 +- src/application.rs | 2 ++ src/fs.rs | 47 +++++++++++++++++++++++++--------------------- src/handler.rs | 4 +--- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7564a559..d83736eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.7.1] - 2018-07-21 + +### Fixed + +* Fixed default_resource 'not yet implemented' panic #410 + + ## [0.7.0] - 2018-07-21 ### Added diff --git a/Cargo.toml b/Cargo.toml index 1d6b1663..a6b73ee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.0" +version = "0.7.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/application.rs b/src/application.rs index ebf441ec..72ecff3d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -610,6 +610,7 @@ impl Iterator for App { mod tests { use super::*; use body::{Binary, Body}; + use fs; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -631,6 +632,7 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let app = App::new() + .resource("/test", |r| r.f(|_| HttpResponse::Ok())) .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); let req = TestRequest::with_uri("/blah").request(); diff --git a/src/fs.rs b/src/fs.rs index 31b3725b..f23ba12c 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -2,11 +2,11 @@ use std::fmt::Write; use std::fs::{DirEntry, File, Metadata}; use std::io::{Read, Seek}; +use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; use std::{cmp, io}; -use std::marker::PhantomData; #[cfg(unix)] use std::os::unix::fs::MetadataExt; @@ -22,13 +22,13 @@ use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use error::{Error, StaticFileError}; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use header; +use header::{ContentDisposition, DispositionParam, DispositionType}; use http::{ContentEncoding, Method, StatusCode}; use httpmessage::HttpMessage; use httprequest::HttpRequest; use httpresponse::HttpResponse; use param::FromParam; use server::settings::DEFAULT_CPUPOOL; -use header::{ContentDisposition, DispositionParam, DispositionType}; ///Describes `StaticFiles` configiration /// @@ -106,7 +106,7 @@ pub fn file_extension_to_mime(ext: &str) -> mime::Mime { /// A file with an associated name. #[derive(Debug)] -pub struct NamedFile { +pub struct NamedFile { path: PathBuf, file: File, content_type: mime::Mime, @@ -188,7 +188,7 @@ impl NamedFile { cpu_pool, encoding, status_code: StatusCode::OK, - _cd_map: PhantomData + _cd_map: PhantomData, }) } @@ -366,21 +366,22 @@ impl Responder for NamedFile { return Ok(resp.streaming(reader)); } - if !C::is_method_allowed(req.method()) - { + if !C::is_method_allowed(req.method()) { return Ok(HttpResponse::MethodNotAllowed() .header(header::CONTENT_TYPE, "text/plain") .header(header::ALLOW, "GET, HEAD") .body("This resource only supports GET and HEAD.")); } - let etag = match C::is_use_etag() { - true => self.etag(), - false => None, + let etag = if C::is_use_etag() { + self.etag() + } else { + None }; - let last_modified = match C::is_use_last_modifier() { - true => self.last_modified(), - false => None, + let last_modified = if C::is_use_last_modifier() { + self.last_modified() + } else { + None }; // check preconditions @@ -637,7 +638,7 @@ fn directory_listing( /// .finish(); /// } /// ``` -pub struct StaticFiles { +pub struct StaticFiles { directory: PathBuf, index: Option, show_index: bool, @@ -672,7 +673,9 @@ impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory. /// /// Identical with `new` but allows to specify configiration to use. - pub fn with_config>(dir: T, config: C) -> Result, Error> { + pub fn with_config>( + dir: T, config: C, + ) -> Result, Error> { // use default CpuPool let pool = { DEFAULT_CPUPOOL.lock().clone() }; @@ -682,7 +685,7 @@ impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory with config and /// `CpuPool`. pub fn with_config_pool>( - dir: T, pool: CpuPool, _: C + dir: T, pool: CpuPool, _: C, ) -> Result, Error> { let dir = dir.into().canonicalize()?; @@ -701,7 +704,7 @@ impl StaticFiles { renderer: Box::new(directory_listing), _chunk_size: 0, _follow_symlinks: false, - _cd_map: PhantomData + _cd_map: PhantomData, }) } @@ -1064,7 +1067,6 @@ mod tests { resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), "inline; filename=\"test.png\"" ); - } #[test] @@ -1296,24 +1298,27 @@ mod tests { fn is_method_allowed(method: &Method) -> bool { match *method { Method::HEAD => true, - _ => false + _ => false, } } } #[test] fn test_named_file_not_allowed() { - let file = NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + let file = + NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); let req = TestRequest::default().method(Method::POST).finish(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let file = NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + let file = + NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); let req = TestRequest::default().method(Method::PUT).finish(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let file = NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + let file = + NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); let req = TestRequest::default().method(Method::GET).finish(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); diff --git a/src/handler.rs b/src/handler.rs index 98d25343..3ac0c2ab 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -408,9 +408,7 @@ pub(crate) trait RouteHandler: 'static { false } - fn default_resource(&mut self, _: DefaultResource) { - unimplemented!() - } + fn default_resource(&mut self, _: DefaultResource) {} fn finish(&mut self) {} } From 56b924e155b54706dd981dcc4907fd37ff871b5a Mon Sep 17 00:00:00 2001 From: Damjan Georgievski Date: Sat, 21 Jul 2018 15:15:28 +0200 Subject: [PATCH 0529/1635] remove the timestamp from the default logger middleware env_logger and other logging systems will (or should) already add their own timestamp. --- src/middleware/logger.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 103cbf37..b7bb1bb8 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -25,7 +25,7 @@ use middleware::{Finished, Middleware, Started}; /// default format: /// /// ```ignore -/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T +/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` /// ```rust /// # extern crate actix_web; @@ -94,7 +94,7 @@ impl Default for Logger { /// Create `Logger` middleware with format: /// /// ```ignore - /// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T + /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` fn default() -> Logger { Logger { @@ -143,7 +143,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 "%{Referer}i" "%{User-Agent}i" %T"#) + Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#) } } From 6a75a3d68339a084567c5bf62267cfd3d0daa2e9 Mon Sep 17 00:00:00 2001 From: Damjan Georgievski Date: Sat, 21 Jul 2018 16:01:42 +0200 Subject: [PATCH 0530/1635] document the change in the default logger --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index d83736eb..ad06fc03 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### Fixed * Fixed default_resource 'not yet implemented' panic #410 +* removed the timestamp from the default logger middleware ## [0.7.0] - 2018-07-21 From b367f07d56e8f4a7cb5e7bb5af35d03bf9479925 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Mon, 23 Jul 2018 12:29:25 +0300 Subject: [PATCH 0531/1635] Add http_only flag to CookieSessionBackend --- CHANGES.md | 1 + src/middleware/session.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d83736eb..04c004fa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ * Fixed default_resource 'not yet implemented' panic #410 +* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies ## [0.7.0] - 2018-07-21 diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 40ba0f4d..cc7aab6b 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -358,6 +358,7 @@ struct CookieSessionInner { path: String, domain: Option, secure: bool, + http_only: bool, max_age: Option, same_site: Option, } @@ -371,6 +372,7 @@ impl CookieSessionInner { path: "/".to_owned(), domain: None, secure: true, + http_only: true, max_age: None, same_site: None, } @@ -388,7 +390,7 @@ impl CookieSessionInner { let mut cookie = Cookie::new(self.name.clone(), value); cookie.set_path(self.path.clone()); cookie.set_secure(self.secure); - cookie.set_http_only(true); + cookie.set_http_only(self.http_only); if let Some(ref domain) = self.domain { cookie.set_domain(domain.clone()); @@ -532,6 +534,12 @@ impl CookieSessionBackend { self } + /// Sets the `http_only` field in the session cookie being built. + pub fn http_only(mut self, value: bool) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().http_only = value; + self + } + /// Sets the `same_site` field in the session cookie being built. pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend { Rc::get_mut(&mut self.0).unwrap().same_site = Some(value); From c352a69d5433592101790dac0e4e34d1bc7880d8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 23 Jul 2018 13:22:16 -0700 Subject: [PATCH 0532/1635] fix dead links --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 632a33dc..ec8c439e 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Multipart streams * Static assets * SSL support with OpenSSL or `native-tls` -* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging), - [Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions), - [Redis sessions](https://github.com/actix/actix-redis), - [DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers), - [CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html), - [CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html)) +* Middlewares ([Logger,Session,CORS,CSRF,etc](https://actix.rs/docs/middleware/)) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Built on top of [Actix actor framework](https://github.com/actix/actix) From 0099091e9664bc6d6e803ff5c1e1e236ce234a5e Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Mon, 23 Jul 2018 15:07:54 +0200 Subject: [PATCH 0533/1635] remove unnecessary use --- src/application.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/application.rs b/src/application.rs index 72ecff3d..f36adf69 100644 --- a/src/application.rs +++ b/src/application.rs @@ -610,7 +610,6 @@ impl Iterator for App { mod tests { use super::*; use body::{Binary, Body}; - use fs; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; From f4bb7efa89d4f21dfc6c7217fcb280f2b4d322d4 Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Mon, 23 Jul 2018 15:10:30 +0200 Subject: [PATCH 0534/1635] add partialeq, eq, partialord and ord dervie to Path, Form and Query --- src/extractor.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/extractor.rs b/src/extractor.rs index 8e4745f8..7696e992 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -16,7 +16,10 @@ use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError}; use handler::{AsyncResult, FromRequest}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; +use Result; +use futures::future; +#[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's path. /// /// ## Example @@ -128,6 +131,7 @@ impl fmt::Display for Path { } } +#[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from from the request's query. /// /// ## Example @@ -215,6 +219,7 @@ impl fmt::Display for Query { } } +#[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's body. /// /// To extract typed information from request's body, the type `T` must From 1079c5c56202449a449b23ef39880b3816b2a385 Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Mon, 23 Jul 2018 15:19:04 +0200 Subject: [PATCH 0535/1635] Add FromRequest implementation for Result and Option where T:FromRequest --- src/extractor.rs | 218 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/src/extractor.rs b/src/extractor.rs index 7696e992..458b7f1a 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -460,6 +460,126 @@ impl FromRequest for String { } } +/// Optionally extract a field from the request +/// +/// If the FromRequest for T fails, return None rather than returning an error response +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// extern crate rand; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; +/// use actix_web::error::ErrorBadRequest; +/// +/// #[derive(Debug, Deserialize)] +/// struct Thing { name: String } +/// +/// impl FromRequest for Thing { +/// type Config = (); +/// type Result = Result; +/// +/// #[inline] +/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { +/// if rand::random() { +/// Ok(Thing { name: "thingy".into() }) +/// } else { +/// Err(ErrorBadRequest("no luck")) +/// } +/// +/// } +/// } +/// +/// /// extract text data from request +/// fn index(supplied_thing: Option) -> Result { +/// match supplied_thing { +/// // Puns not intended +/// Some(thing) => Ok(format!("Got something: {:?}", thing)), +/// None => Ok(format!("No thing!")) +/// } +/// } +/// +/// fn main() { +/// let app = App::new().resource("/users/:first", |r| { +/// r.method(http::Method::POST).with(index) +/// }); +/// } +/// ``` +impl FromRequest for Option where T: FromRequest { + type Config = T::Config; + type Result = Box, Error = Error>>; + + #[inline] + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + Box::new(T::from_request(req, cfg).into().then( |r| { + match r { + Ok(v) => future::ok(Some(v)), + Err(e) => { +// if true { panic!("{:?}", e.as_response_error()); } + + future::ok(None) + } + } + })) + } +} + +/// Optionally extract a field from the request or extract the Error if unsuccessful +/// +/// If the FromRequest for T fails, inject Err into handler rather than returning an error response +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// extern crate rand; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; +/// use actix_web::error::ErrorBadRequest; +/// +/// #[derive(Debug, Deserialize)] +/// struct Thing { name: String } +/// +/// impl FromRequest for Thing { +/// type Config = (); +/// type Result = Result; +/// +/// #[inline] +/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { +/// if rand::random() { +/// Ok(Thing { name: "thingy".into() }) +/// } else { +/// Err(ErrorBadRequest("no luck")) +/// } +/// +/// } +/// } +/// +/// /// extract text data from request +/// fn index(supplied_thing: Result) -> Result { +/// match supplied_thing { +/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)), +/// Err(e) => Ok(format!("Error extracting thing: {}", e)) +/// } +/// } +/// +/// fn main() { +/// let app = App::new().resource("/users/:first", |r| { +/// r.method(http::Method::POST).with(index) +/// }); +/// } +/// ``` +impl FromRequest for Result where T: FromRequest{ + type Config = T::Config; + type Result = Box, Error = Error>>; + + #[inline] + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + Box::new(T::from_request(req, cfg).into().then( |r| { future::ok(r) })) + } +} + /// Payload configuration for request's payload. pub struct PayloadConfig { limit: usize, @@ -685,6 +805,75 @@ mod tests { } } + #[test] + fn test_option() { + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).finish(); + + let mut cfg = FormConfig::default(); + cfg.limit(4096); + + match Option::>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, None), + _ => unreachable!(), + } + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); + + match Option::>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, Some(Form(Info { hello: "world".into() }))), + _ => unreachable!(), + } + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"bye=world")) + .finish(); + + match Option::>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, None), + _ => unreachable!(), + } + } + + #[test] + fn test_result() { + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); + + match Result::>::from_request(&req, &FormConfig::default()).poll().unwrap() { + Async::Ready(Ok(r)) => assert_eq!(r, Form(Info { hello: "world".into() })), + _ => unreachable!(), + } + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"bye=world")) + .finish(); + + match Result::>::from_request(&req, &FormConfig::default()).poll().unwrap() { + Async::Ready(r) => assert!(r.is_err()), + _ => unreachable!(), + } + } + + + #[test] fn test_payload_config() { let req = TestRequest::default().finish(); @@ -797,4 +986,33 @@ mod tests { assert_eq!((res.1).0, "name"); assert_eq!((res.1).1, "user1"); } + +// #[test] +// fn test_tuple_optional() { +// let mut router = Router::<()>::new(); +// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); +// +// let req = TestRequest::with_uri("/name/?id=test").finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); +// +// let res = match <(Path<(Option, Option)>,)>::extract(&req).wait() { +// Ok(res) => res, +// e => panic!("error {:?}", e), +// }; +// assert_eq!((res.0).0, Some("name".into())); +// assert_eq!((res.0).1, None); +// +// let req = TestRequest::with_uri("/user/?id=test").finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); +// +// let res = match <(Path<(Option, Option)>,)>::extract(&req).wait() { +// Ok(res) => res, +// _ => panic!("error"), +// }; +// assert_eq!((res.0).0, None); +// assert_eq!((res.0).1, Some("user".into())); +// } + } From 35b754a3ab816637013436016bc801ac490c38f7 Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Tue, 24 Jul 2018 09:39:27 +0200 Subject: [PATCH 0536/1635] pr fixes --- src/extractor.rs | 43 +++++-------------------------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 458b7f1a..768edfb7 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -6,7 +6,7 @@ use std::{fmt, str}; use bytes::Bytes; use encoding::all::UTF_8; use encoding::types::{DecoderTrap, Encoding}; -use futures::{Async, Future, Poll}; +use futures::{Async, Future, Poll, future}; use mime::Mime; use serde::de::{self, DeserializeOwned}; use serde_urlencoded; @@ -16,8 +16,6 @@ use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError}; use handler::{AsyncResult, FromRequest}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; -use Result; -use futures::future; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's path. @@ -516,8 +514,6 @@ impl FromRequest for Option where T: FromRequest future::ok(Some(v)), Err(e) => { -// if true { panic!("{:?}", e.as_response_error()); } - future::ok(None) } } @@ -570,9 +566,9 @@ impl FromRequest for Option where T: FromRequest FromRequest for Result where T: FromRequest{ +impl FromRequest for Result where T: FromRequest{ type Config = T::Config; - type Result = Box, Error = Error>>; + type Result = Box, Error = Error>>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { @@ -854,7 +850,7 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .finish(); - match Result::>::from_request(&req, &FormConfig::default()).poll().unwrap() { + match Result::, Error>::from_request(&req, &FormConfig::default()).poll().unwrap() { Async::Ready(Ok(r)) => assert_eq!(r, Form(Info { hello: "world".into() })), _ => unreachable!(), } @@ -866,7 +862,7 @@ mod tests { .set_payload(Bytes::from_static(b"bye=world")) .finish(); - match Result::>::from_request(&req, &FormConfig::default()).poll().unwrap() { + match Result::, Error>::from_request(&req, &FormConfig::default()).poll().unwrap() { Async::Ready(r) => assert!(r.is_err()), _ => unreachable!(), } @@ -986,33 +982,4 @@ mod tests { assert_eq!((res.1).0, "name"); assert_eq!((res.1).1, "user1"); } - -// #[test] -// fn test_tuple_optional() { -// let mut router = Router::<()>::new(); -// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); -// -// let req = TestRequest::with_uri("/name/?id=test").finish(); -// let info = router.recognize(&req, &(), 0); -// let req = req.with_route_info(info); -// -// let res = match <(Path<(Option, Option)>,)>::extract(&req).wait() { -// Ok(res) => res, -// e => panic!("error {:?}", e), -// }; -// assert_eq!((res.0).0, Some("name".into())); -// assert_eq!((res.0).1, None); -// -// let req = TestRequest::with_uri("/user/?id=test").finish(); -// let info = router.recognize(&req, &(), 0); -// let req = req.with_route_info(info); -// -// let res = match <(Path<(Option, Option)>,)>::extract(&req).wait() { -// Ok(res) => res, -// _ => panic!("error"), -// }; -// assert_eq!((res.0).0, None); -// assert_eq!((res.0).1, Some("user".into())); -// } - } From b48a2d4d7b36410297d3c3b3a6e3dffb665715d1 Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Tue, 24 Jul 2018 22:25:48 +0200 Subject: [PATCH 0537/1635] add changes to CHANGES.md --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 04c004fa..15786fb6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## [0.7.1] - 2018-07-21 +### Added + + * Add implementation of `FromRequest` for `Option` and `Result` + ### Fixed * Fixed default_resource 'not yet implemented' panic #410 From b79a9aaec7a3a44dc6f5766e1db9d90147af657d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Jul 2018 14:18:04 -0700 Subject: [PATCH 0538/1635] fix changelog --- CHANGES.md | 11 +++++++++-- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1f9688f6..88256330 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.1] - 2018-07-21 +## [0.7.2] - 2018-07-xx ### Added @@ -8,11 +8,18 @@ ### Fixed -* Fixed default_resource 'not yet implemented' panic #410 * removed the timestamp from the default logger middleware * Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies + +## [0.7.1] - 2018-07-21 + +### Fixed + +* Fixed default_resource 'not yet implemented' panic #410 + + ## [0.7.0] - 2018-07-21 ### Added diff --git a/Cargo.toml b/Cargo.toml index a6b73ee5..6fb2e1a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.1" +version = "0.7.2" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/README.md b/README.md index ec8c439e..4e396cb9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Multipart streams * Static assets * SSL support with OpenSSL or `native-tls` -* Middlewares ([Logger,Session,CORS,CSRF,etc](https://actix.rs/docs/middleware/)) +* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Built on top of [Actix actor framework](https://github.com/actix/actix) From d6abd2fe22f98e22a6ef7eba422d559d029dbf9d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Jul 2018 14:51:48 -0700 Subject: [PATCH 0539/1635] allow to handle empty path for application with prefix --- CHANGES.md | 6 ++++++ src/application.rs | 49 ++++++++++++++++++++++++++++++++++------------ src/router.rs | 2 +- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 88256330..494ad7a6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,12 @@ * Add implementation of `FromRequest` for `Option` and `Result` + * Allow to handle application prefix, i.e. allow to handle `/app` path + for application with `/app` prefix. + Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix) + api doc. + + ### Fixed * removed the timestamp from the default logger middleware diff --git a/src/application.rs b/src/application.rs index f36adf69..a5cd3386 100644 --- a/src/application.rs +++ b/src/application.rs @@ -171,7 +171,9 @@ where /// In the following example only requests with an `/app/` path /// prefix get handled. Requests with path `/app/test/` would be /// handled, while requests with the paths `/application` or - /// `/other/...` would return `NOT FOUND`. + /// `/other/...` would return `NOT FOUND`. It is also possible to + /// handle `/app` path, to do this you can register resource for + /// empty string `""` /// /// ```rust /// # extern crate actix_web; @@ -180,6 +182,8 @@ where /// fn main() { /// let app = App::new() /// .prefix("/app") + /// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path + /// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path /// .resource("/test", |r| { /// r.get().f(|_| HttpResponse::Ok()); /// r.head().f(|_| HttpResponse::MethodNotAllowed()); @@ -822,6 +826,23 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } + #[test] + fn test_option_responder() { + let app = App::new() + .resource("/none", |r| r.f(|_| -> Option<&'static str> { None })) + .resource("/some", |r| r.f(|_| Some("some"))) + .finish(); + + let req = TestRequest::with_uri("/none").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/some").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); + } + #[test] fn test_filter() { let mut srv = TestServer::with_factory(|| { @@ -840,19 +861,21 @@ mod tests { } #[test] - fn test_option_responder() { - let app = App::new() - .resource("/none", |r| r.f(|_| -> Option<&'static str> { None })) - .resource("/some", |r| r.f(|_| Some("some"))) - .finish(); + fn test_prefix_root() { + let mut srv = TestServer::with_factory(|| { + App::new() + .prefix("/test") + .resource("/", |r| r.f(|_| HttpResponse::Ok())) + .resource("", |r| r.f(|_| HttpResponse::Created())) + }); - let req = TestRequest::with_uri("/none").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::OK); - let req = TestRequest::with_uri("/some").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::CREATED); } + } diff --git a/src/router.rs b/src/router.rs index e79dc93d..f3f657b5 100644 --- a/src/router.rs +++ b/src/router.rs @@ -463,7 +463,7 @@ impl ResourceDef { /// /// Panics if path pattern is wrong. pub fn new(path: &str) -> Self { - ResourceDef::with_prefix(path, "/", false) + ResourceDef::with_prefix(path, if path.is_empty() { "" } else { "/" }, false) } /// Parse path pattern and create new `Resource` instance. From 85b275bb2b896624ed52d86cf7b93655704fc57e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Jul 2018 14:52:56 -0700 Subject: [PATCH 0540/1635] fix warnings --- Cargo.toml | 2 +- src/client/connector.rs | 8 ++--- src/client/pipeline.rs | 2 +- src/extractor.rs | 65 ++++++++++++++++++++++++++++------------- 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6fb2e1a2..89a51c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ base64 = "0.9" bitflags = "1.0" h2 = "0.1" htmlescape = "0.3" -http = "^0.1.5" +http = "^0.1.8" httparse = "1.3" log = "0.4" mime = "0.3" diff --git a/src/client/connector.rs b/src/client/connector.rs index 6d391af8..03ad3bd9 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -599,7 +599,7 @@ impl ClientConnector { } Acquire::Available => { // create new connection - self.connect_waiter(key.clone(), waiter, ctx); + self.connect_waiter(&key, waiter, ctx); } } } @@ -608,7 +608,7 @@ impl ClientConnector { self.waiters = Some(act_waiters); } - fn connect_waiter(&mut self, key: Key, waiter: Waiter, ctx: &mut Context) { + fn connect_waiter(&mut self, key: &Key, waiter: Waiter, ctx: &mut Context) { let conn = AcquiredConn(key.clone(), Some(self.acq_tx.clone())); let key2 = key.clone(); @@ -828,7 +828,7 @@ impl Handler for ClientConnector { wait, conn_timeout, }; - self.connect_waiter(key.clone(), waiter, ctx); + self.connect_waiter(&key, waiter, ctx); return ActorResponse::async( rx.map_err(|_| ClientConnectorError::Disconnected) @@ -885,7 +885,7 @@ impl Handler for ClientConnector { wait, conn_timeout, }; - self.connect_waiter(key.clone(), waiter, ctx); + self.connect_waiter(&key, waiter, ctx); ActorResponse::async( rx.map_err(|_| ClientConnectorError::Disconnected) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index e5538b06..394b7a6c 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -216,7 +216,7 @@ impl Future for SendRequest { match pl.parse() { Ok(Async::Ready(mut resp)) => { - if self.req.method() == &Method::HEAD { + if self.req.method() == Method::HEAD { pl.parser.take(); } resp.set_pipeline(pl); diff --git a/src/extractor.rs b/src/extractor.rs index 768edfb7..aa4fdea7 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -6,7 +6,7 @@ use std::{fmt, str}; use bytes::Bytes; use encoding::all::UTF_8; use encoding::types::{DecoderTrap, Encoding}; -use futures::{Async, Future, Poll, future}; +use futures::{future, Async, Future, Poll}; use mime::Mime; use serde::de::{self, DeserializeOwned}; use serde_urlencoded; @@ -504,19 +504,18 @@ impl FromRequest for String { /// }); /// } /// ``` -impl FromRequest for Option where T: FromRequest { +impl FromRequest for Option +where + T: FromRequest, +{ type Config = T::Config; type Result = Box, Error = Error>>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then( |r| { - match r { - Ok(v) => future::ok(Some(v)), - Err(e) => { - future::ok(None) - } - } + Box::new(T::from_request(req, cfg).into().then(|r| match r { + Ok(v) => future::ok(Some(v)), + Err(_) => future::ok(None), })) } } @@ -566,13 +565,16 @@ impl FromRequest for Option where T: FromRequest FromRequest for Result where T: FromRequest{ +impl FromRequest for Result +where + T: FromRequest, +{ type Config = T::Config; type Result = Box, Error = Error>>; #[inline] fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then( |r| { future::ok(r) })) + Box::new(T::from_request(req, cfg).into().then(future::ok)) } } @@ -811,7 +813,10 @@ mod tests { let mut cfg = FormConfig::default(); cfg.limit(4096); - match Option::>::from_request(&req, &cfg).poll().unwrap() { + match Option::>::from_request(&req, &cfg) + .poll() + .unwrap() + { Async::Ready(r) => assert_eq!(r, None), _ => unreachable!(), } @@ -823,8 +828,16 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .finish(); - match Option::>::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(r) => assert_eq!(r, Some(Form(Info { hello: "world".into() }))), + match Option::>::from_request(&req, &cfg) + .poll() + .unwrap() + { + Async::Ready(r) => assert_eq!( + r, + Some(Form(Info { + hello: "world".into() + })) + ), _ => unreachable!(), } @@ -835,7 +848,10 @@ mod tests { .set_payload(Bytes::from_static(b"bye=world")) .finish(); - match Option::>::from_request(&req, &cfg).poll().unwrap() { + match Option::>::from_request(&req, &cfg) + .poll() + .unwrap() + { Async::Ready(r) => assert_eq!(r, None), _ => unreachable!(), } @@ -850,8 +866,16 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .finish(); - match Result::, Error>::from_request(&req, &FormConfig::default()).poll().unwrap() { - Async::Ready(Ok(r)) => assert_eq!(r, Form(Info { hello: "world".into() })), + match Result::, Error>::from_request(&req, &FormConfig::default()) + .poll() + .unwrap() + { + Async::Ready(Ok(r)) => assert_eq!( + r, + Form(Info { + hello: "world".into() + }) + ), _ => unreachable!(), } @@ -862,14 +886,15 @@ mod tests { .set_payload(Bytes::from_static(b"bye=world")) .finish(); - match Result::, Error>::from_request(&req, &FormConfig::default()).poll().unwrap() { + match Result::, Error>::from_request(&req, &FormConfig::default()) + .poll() + .unwrap() + { Async::Ready(r) => assert!(r.is_err()), _ => unreachable!(), } } - - #[test] fn test_payload_config() { let req = TestRequest::default().finish(); From b878613e104a5ae8e958a10c7484401f851bfbee Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 24 Jul 2018 15:49:46 -0700 Subject: [PATCH 0541/1635] fix warning --- src/client/connector.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 03ad3bd9..c2ff328e 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -609,6 +609,7 @@ impl ClientConnector { } fn connect_waiter(&mut self, key: &Key, waiter: Waiter, ctx: &mut Context) { + let key = key.clone(); let conn = AcquiredConn(key.clone(), Some(self.acq_tx.clone())); let key2 = key.clone(); @@ -635,7 +636,7 @@ impl ClientConnector { act.connector .connect_async(&key.host, stream) .into_actor(act) - .then(move |res, act, _| { + .then(move |res, _, _| { match res { Err(e) => { let _ = waiter.tx.send(Err( From e408b68744a10ae02555ea84a8960712b62affb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Miku=C5=82a?= Date: Wed, 25 Jul 2018 17:01:22 +0200 Subject: [PATCH 0542/1635] Update cookie dependency (#422) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 89a51c66..29c2dadb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ lazy_static = "1.0" lazycell = "1.0.0" parking_lot = "0.6" url = { version="1.7", features=["query_encoding"] } -cookie = { version="0.10", features=["percent-encode"] } +cookie = { version="0.11", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } flate2 = { version="1.0", optional = true, default-features = false } From 6048817ba74f5a916bff72c17ec220656ea49c80 Mon Sep 17 00:00:00 2001 From: Douman Date: Wed, 25 Jul 2018 20:22:18 +0300 Subject: [PATCH 0543/1635] Correct flate feature names in documentation --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0ab4a1be..528eb7b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,9 +70,9 @@ //! dependency //! * `brotli` - enables `brotli` compression support, requires `c` //! compiler -//! * `flate-c` - enables `gzip`, `deflate` compression support, requires +//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires //! `c` compiler -//! * `flate-rust` - experimental rust based implementation for +//! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. //! #![cfg_attr(actix_nightly, feature( From f58065082e69f023a73faeed1d646a8ef067e02e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 25 Jul 2018 10:30:55 -0700 Subject: [PATCH 0544/1635] fix missing content-encoding header for h2 connections #421 --- CHANGES.md | 15 ++++--- src/server/h2writer.rs | 94 ++++++++++++++++++++++++------------------ 2 files changed, 65 insertions(+), 44 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 494ad7a6..2b13657a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,19 +4,24 @@ ### Added - * Add implementation of `FromRequest` for `Option` and `Result` +* Add implementation of `FromRequest` for `Option` and `Result` - * Allow to handle application prefix, i.e. allow to handle `/app` path +* Allow to handle application prefix, i.e. allow to handle `/app` path for application with `/app` prefix. Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix) api doc. +* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies + +### Changed + +* Upgrade to cookie 0.11 + +* Removed the timestamp from the default logger middleware ### Fixed -* removed the timestamp from the default logger middleware - -* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies +* Missing response header "content-encoding" #421 ## [0.7.1] - 2018-07-21 diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index c4fc5997..c877250d 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -8,16 +8,18 @@ use modhttp::Response; use std::rc::Rc; use std::{cmp, io}; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{HttpTryFrom, Method, Version}; use super::helpers; use super::message::Request; -use super::output::{Output, ResponseInfo}; +use super::output::{Output, ResponseInfo, ResponseLength}; use super::settings::WorkerSettings; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; use header::ContentEncoding; +use http::header::{ + HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; use httpresponse::HttpResponse; const CHUNK_SIZE: usize = 16_384; @@ -92,50 +94,63 @@ impl Writer for H2Writer { let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); self.buffer.for_server(&mut info, &req.inner, msg, encoding); - // http2 specific - msg.headers_mut().remove(CONNECTION); - msg.headers_mut().remove(TRANSFER_ENCODING); - - // using helpers::date is quite a lot faster - if !msg.headers().contains_key(DATE) { - let mut bytes = BytesMut::with_capacity(29); - self.settings.set_date(&mut bytes, false); - msg.headers_mut() - .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); - } - - let body = msg.replace_body(Body::Empty); - match body { - Body::Binary(ref bytes) => { - if bytes.is_empty() { - msg.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - self.flags.insert(Flags::EOF); - } else { - 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 => { - self.flags.insert(Flags::EOF); - msg.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - } - _ => (), - } - + let mut has_date = false; let mut resp = Response::new(()); *resp.status_mut() = msg.status(); *resp.version_mut() = Version::HTTP_2; for (key, value) in msg.headers().iter() { + match *key { + // http2 specific + CONNECTION | TRANSFER_ENCODING => continue, + CONTENT_ENCODING => if encoding != ContentEncoding::Identity { + continue; + }, + CONTENT_LENGTH => match info.length { + ResponseLength::None => (), + _ => continue, + }, + DATE => has_date = true, + _ => (), + } resp.headers_mut().insert(key, value.clone()); } + // set date header + if !has_date { + let mut bytes = BytesMut::with_capacity(29); + self.settings.set_date(&mut bytes, false); + resp.headers_mut() + .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); + } + + // content length + match info.length { + ResponseLength::Zero => { + resp.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + self.flags.insert(Flags::EOF); + } + ResponseLength::Length(len) => { + let mut val = BytesMut::new(); + helpers::convert_usize(len, &mut val); + let l = val.len(); + resp.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), + ); + } + ResponseLength::Length64(len) => { + let l = format!("{}", len); + resp.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap()); + } + _ => (), + } + if let Some(ce) = info.content_encoding { + resp.headers_mut() + .insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap()); + } + match self .respond .send_response(resp, self.flags.contains(Flags::EOF)) @@ -146,6 +161,7 @@ impl Writer for H2Writer { trace!("Response: {:?}", msg); + let body = msg.replace_body(Body::Empty); if let Body::Binary(bytes) = body { if bytes.is_empty() { Ok(WriterState::Done) From 80fbc2e9ec7fb675ba184921714fc924db5d83a8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 25 Jul 2018 15:38:02 -0700 Subject: [PATCH 0545/1635] Fix stream draining for http/2 connections #290 --- CHANGES.md | 2 ++ src/pipeline.rs | 2 +- src/server/h2.rs | 4 +++- src/server/h2writer.rs | 8 ++++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2b13657a..051ab1cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,8 @@ * Missing response header "content-encoding" #421 +* Fix stream draining for http/2 connections #290 + ## [0.7.1] - 2018-07-21 diff --git a/src/pipeline.rs b/src/pipeline.rs index dbe9e58a..7c277a58 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -409,7 +409,7 @@ struct ProcessResponse { _h: PhantomData, } -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] enum RunningState { Running, Paused, diff --git a/src/server/h2.rs b/src/server/h2.rs index 2322f755..e5355a1f 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -155,7 +155,9 @@ where } } - if !item.flags.contains(EntryFlags::WRITE_DONE) { + if item.flags.contains(EntryFlags::FINISHED) + && !item.flags.contains(EntryFlags::WRITE_DONE) + { match item.stream.poll_completed(false) { Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => { diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index c877250d..ff87b693 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -245,14 +245,18 @@ impl Writer for H2Writer { let cap = cmp::min(self.buffer.len(), CHUNK_SIZE); stream.reserve_capacity(cap); } else { + if eof { + stream.reserve_capacity(0); + continue; + } self.flags.remove(Flags::RESERVED); - return Ok(Async::NotReady); + return Ok(Async::Ready(())); } } Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), } } } - Ok(Async::NotReady) + Ok(Async::Ready(())) } } From b4ed564e5d146cded58ea989c538e29a0968cdb3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 26 Jul 2018 09:11:50 -0700 Subject: [PATCH 0546/1635] update changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 051ab1cc..d63d6010 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.2] - 2018-07-xx +## [0.7.2] - 2018-07-26 ### Added From 196da6d570b1f93b6892f4d7ba11fdd9d8ef630f Mon Sep 17 00:00:00 2001 From: Marat Safin Date: Sun, 29 Jul 2018 09:43:04 +0300 Subject: [PATCH 0547/1635] add rustls --- Cargo.toml | 11 ++- src/client/connector.rs | 153 ++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 14 ++++ src/server/mod.rs | 43 +++++++++++ src/server/srv.rs | 51 ++++++++++++++ src/server/worker.rs | 43 ++++++++++- src/test.rs | 45 +++++++++--- tests/cert.pem | 58 +++++++-------- tests/test_ws.rs | 42 +++++++++++ 9 files changed, 413 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 29c2dadb..54bd0e38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] build = "build.rs" [package.metadata.docs.rs] -features = ["tls", "alpn", "session", "brotli", "flate2-c"] +features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } @@ -37,6 +37,9 @@ tls = ["native-tls", "tokio-tls"] # openssl alpn = ["openssl", "tokio-openssl"] +# rustls +rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"] + # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] @@ -104,6 +107,12 @@ tokio-tls = { version="0.1", optional = true } openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } +#rustls +rustls = { version = "0.13", optional = true } +tokio-rustls = { version = "0.7", optional = true } +webpki = { version = "0.18", optional = true } +webpki-roots = { version = "0.15", optional = true } + # forked url_encoded itoa = "0.4" dtoa = "0.4" diff --git a/src/client/connector.rs b/src/client/connector.rs index c2ff328e..a0054671 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -22,12 +22,25 @@ use openssl::ssl::{Error as OpensslError, SslConnector, SslMethod}; use tokio_openssl::SslConnectorExt; #[cfg(all(feature = "tls", not(feature = "alpn")))] -use native_tls::{Error as TlsError, TlsConnector}; +use native_tls::{Error as TlsError, TlsConnector, TlsStream}; #[cfg(all(feature = "tls", not(feature = "alpn")))] use tokio_tls::TlsConnectorExt; +#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +use rustls::ClientConfig; +#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +use std::io::Error as TLSError; +#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +use std::sync::Arc; +#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +use tokio_rustls::ClientConfigExt; +#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +use webpki::DNSNameRef; +#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +use webpki_roots; + use server::IoStream; -use {HAS_OPENSSL, HAS_TLS}; +use {HAS_OPENSSL, HAS_TLS, HAS_RUSTLS}; /// Client connector usage stats #[derive(Default, Message)] @@ -139,6 +152,11 @@ pub enum ClientConnectorError { #[fail(display = "{}", _0)] SslError(#[cause] TlsError), + /// SSL error + #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + #[fail(display = "{}", _0)] + SslError(#[cause] TLSError), + /// Resolver error #[fail(display = "{}", _0)] Resolver(#[cause] ResolverError), @@ -193,6 +211,8 @@ pub struct ClientConnector { connector: SslConnector, #[cfg(all(feature = "tls", not(feature = "alpn")))] connector: TlsConnector, + #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + connector: Arc, stats: ClientConnectorStats, subscriber: Option>, @@ -262,8 +282,16 @@ impl Default for ClientConnector { paused: Paused::No, } } + #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + { + let mut config = ClientConfig::new(); + config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + ClientConnector::with_connector(Arc::new(config)) + } - #[cfg(not(any(feature = "alpn", feature = "tls")))] + #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] { let (tx, rx) = mpsc::unbounded(); ClientConnector { @@ -325,7 +353,7 @@ impl ClientConnector { /// # actix::System::current().stop(); /// Ok(()) /// }) - /// ); + /// }); /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { @@ -352,6 +380,75 @@ impl ClientConnector { } } + #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + /// Create `ClientConnector` actor with custom `SslConnector` instance. + /// + /// By default `ClientConnector` uses very a simple SSL configuration. + /// With `with_connector` method it is possible to use a custom + /// `SslConnector` object. + /// + /// ```rust + /// # #![cfg(feature = "rust-tls")] + /// # extern crate actix_web; + /// # extern crate futures; + /// # extern crate tokio; + /// # use futures::{future, Future}; + /// # use std::io::Write; + /// # use std::process; + /// # use actix_web::actix::Actor; + /// extern crate rustls; + /// extern crate webpki_roots; + /// use actix_web::{actix, client::ClientConnector, client::Connect}; + /// + /// use rustls::ClientConfig; + /// use std::sync::Arc; + /// + /// fn main() { + /// actix::run(|| { + /// // Start `ClientConnector` with custom `ClientConfig` + /// let mut config = ClientConfig::new(); + /// config + /// .root_store + /// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + /// let conn = ClientConnector::with_connector(Arc::new(config)).start(); + /// + /// conn.send( + /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host + /// .map_err(|_| ()) + /// .and_then(|res| { + /// if let Ok(mut stream) = res { + /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); + /// } + /// # actix::System::current().stop(); + /// Ok(()) + /// }) + /// }); + /// } + /// ``` + pub fn with_connector(connector: Arc) -> ClientConnector { + let (tx, rx) = mpsc::unbounded(); + + ClientConnector { + connector, + stats: ClientConnectorStats::default(), + subscriber: None, + acq_tx: tx, + acq_rx: Some(rx), + resolver: None, + conn_lifetime: Duration::from_secs(75), + conn_keep_alive: Duration::from_secs(15), + limit: 100, + limit_per_host: 0, + acquired: 0, + acquired_per_host: HashMap::new(), + available: HashMap::new(), + to_close: Vec::new(), + waiters: Some(HashMap::new()), + wait_timeout: None, + paused: Paused::No, + } + } + /// Set total number of simultaneous connections. /// /// If limit is 0, the connector has no limit. @@ -709,7 +806,51 @@ impl ClientConnector { } } - #[cfg(not(any(feature = "alpn", feature = "tls")))] + #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + match res { + Err(err) => { + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap(); + fut::Either::A( + act.connector + .connect_async(host, stream) + .into_actor(act) + .then(move |res, _, _| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e), + )); + } + Ok(stream) => { + let _ = + waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + } + } + fut::ok(()) + }), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) + } + } + } + + #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] match res { Err(err) => { let _ = waiter.tx.send(Err(err.into())); @@ -784,7 +925,7 @@ impl Handler for ClientConnector { }; // check ssl availability - if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS { + if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS && !HAS_RUSTLS { return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)); } diff --git a/src/lib.rs b/src/lib.rs index 528eb7b7..626bb95f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,6 +151,15 @@ extern crate openssl; #[cfg(feature = "openssl")] extern crate tokio_openssl; +#[cfg(feature = "rust-tls")] +extern crate rustls; +#[cfg(feature = "rust-tls")] +extern crate tokio_rustls; +#[cfg(feature = "rust-tls")] +extern crate webpki; +#[cfg(feature = "rust-tls")] +extern crate webpki_roots; + mod application; mod body; mod context; @@ -224,6 +233,11 @@ pub(crate) const HAS_TLS: bool = true; #[cfg(not(feature = "tls"))] pub(crate) const HAS_TLS: bool = false; +#[cfg(feature = "rust-tls")] +pub(crate) const HAS_RUSTLS: bool = true; +#[cfg(not(feature = "rust-tls"))] +pub(crate) const HAS_RUSTLS: bool = false; + pub mod dev { //! The `actix-web` prelude for library developers //! diff --git a/src/server/mod.rs b/src/server/mod.rs index a302f5e7..dc8ecd81 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -310,3 +310,46 @@ impl IoStream for TlsStream { self.get_mut().get_mut().set_linger(dur) } } + +#[cfg(feature = "rust-tls")] +use rustls::{ClientSession, ServerSession}; +#[cfg(feature = "rust-tls")] +use tokio_rustls::TlsStream; + +#[cfg(feature = "rust-tls")] +impl IoStream for TlsStream { + #[inline] + fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { + let _ = ::shutdown(self); + Ok(()) + } + + #[inline] + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { + self.get_mut().0.set_nodelay(nodelay) + } + + #[inline] + fn set_linger(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().0.set_linger(dur) + } +} + +#[cfg(feature = "rust-tls")] +impl IoStream for TlsStream { + #[inline] + fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { + let _ = ::shutdown(self); + Ok(()) + } + + #[inline] + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { + self.get_mut().0.set_nodelay(nodelay) + } + + #[inline] + fn set_linger(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().0.set_linger(dur) + } +} diff --git a/src/server/srv.rs b/src/server/srv.rs index 02580d01..d6f5cf4d 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -22,6 +22,9 @@ use native_tls::TlsAcceptor; #[cfg(feature = "alpn")] use openssl::ssl::{AlpnError, SslAcceptorBuilder}; +#[cfg(feature = "rust-tls")] +use rustls::ServerConfig; + use super::channel::{HttpChannel, WrapperStream}; use super::settings::{ServerSettings, WorkerSettings}; use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker}; @@ -42,6 +45,14 @@ fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> { Ok(()) } +#[cfg(all(feature = "rust-tls", not(feature = "alpn")))] +fn configure_alpn(builder: &mut Arc) -> io::Result<()> { + Arc::::get_mut(builder) + .unwrap() + .set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]); + Ok(()) +} + /// An HTTP Server pub struct HttpServer where @@ -265,6 +276,26 @@ where Ok(self) } + #[cfg(all(feature = "rust-tls", not(feature = "alpn")))] + /// Use listener for accepting incoming tls connection requests + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn listen_ssl( + mut self, lst: net::TcpListener, mut builder: Arc, + ) -> io::Result { + // alpn support + if !self.no_http2 { + configure_alpn(&mut builder)?; + } + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + lst, + tp: StreamHandlerType::Rustls(builder.clone()), + }); + Ok(self) + } + fn bind2(&mut self, addr: S) -> io::Result> { let mut err = None; let mut succ = false; @@ -343,6 +374,26 @@ where Ok(self) } + #[cfg(all(feature = "rust-tls", not(feature = "alpn")))] + /// Start listening for incoming tls connections. + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn bind_ssl( + mut self, addr: S, mut builder: Arc, + ) -> io::Result { + // alpn support + if !self.no_http2 { + configure_alpn(&mut builder)?; + } + + let sockets = self.bind2(addr)?; + self.sockets.extend(sockets.into_iter().map(|mut s| { + s.tp = StreamHandlerType::Rustls(builder.clone()); + s + })); + Ok(self) + } + fn start_workers( &mut self, settings: &ServerSettings, sockets: &Slab, ) -> Vec<(usize, mpsc::UnboundedSender>)> { diff --git a/src/server/worker.rs b/src/server/worker.rs index 8fd3fe60..5e753ce5 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -8,7 +8,7 @@ use tokio::executor::current_thread; use tokio_reactor::Handle; use tokio_tcp::TcpStream; -#[cfg(any(feature = "tls", feature = "alpn"))] +#[cfg(any(feature = "tls", feature = "alpn", feature = "rust-tls"))] use futures::future; #[cfg(feature = "tls")] @@ -21,6 +21,13 @@ use openssl::ssl::SslAcceptor; #[cfg(feature = "alpn")] use tokio_openssl::SslAcceptorExt; +#[cfg(feature = "rust-tls")] +use rustls::{ServerConfig, Session}; +#[cfg(feature = "rust-tls")] +use std::sync::Arc; +#[cfg(feature = "rust-tls")] +use tokio_rustls::ServerConfigExt; + use actix::msgs::StopArbiter; use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response}; @@ -170,6 +177,8 @@ pub(crate) enum StreamHandlerType { Tls(TlsAcceptor), #[cfg(feature = "alpn")] Alpn(SslAcceptor), + #[cfg(feature = "rust-tls")] + Rustls(Arc), } impl StreamHandlerType { @@ -237,6 +246,36 @@ impl StreamHandlerType { }, )); } + #[cfg(feature = "rust-tls")] + StreamHandlerType::Rustls(ref acceptor) => { + let Conn { io, peer, .. } = msg; + let _ = io.set_nodelay(true); + let io = TcpStream::from_std(io, &Handle::default()) + .expect("failed to associate TCP stream"); + + current_thread::spawn(ServerConfigExt::accept_async(acceptor, io).then( + move |res| { + match res { + Ok(io) => { + let http2 = if let Some(p) = + io.get_ref().1.get_alpn_protocol() + { + p.len() == 2 && &p == &"h2" + } else { + false + }; + current_thread::spawn(HttpChannel::new( + h, io, peer, http2, + )); + } + Err(err) => { + trace!("Error during handling tls connection: {}", err) + } + }; + future::result(Ok(())) + }, + )); + } } } @@ -247,6 +286,8 @@ impl StreamHandlerType { StreamHandlerType::Tls(_) => "https", #[cfg(feature = "alpn")] StreamHandlerType::Alpn(_) => "https", + #[cfg(feature = "rust-tls")] + StreamHandlerType::Rustls(_) => "https", } } } diff --git a/src/test.rs b/src/test.rs index c2e5c756..f466db2d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,6 +15,10 @@ use tokio::runtime::current_thread::Runtime; #[cfg(feature = "alpn")] use openssl::ssl::SslAcceptorBuilder; +#[cfg(feature = "rust-tls")] +use rustls::ServerConfig; +#[cfg(feature = "rust-tls")] +use std::sync::Arc; use application::{App, HttpApplication}; use body::Binary; @@ -140,7 +144,19 @@ impl TestServer { builder.set_verify(SslVerifyMode::NONE); ClientConnector::with_connector(builder.build()).start() } - #[cfg(not(feature = "alpn"))] + #[cfg(feature = "rust-tls")] + { + use rustls::ClientConfig; + use std::io::BufReader; + use std::fs::File; + let mut config = ClientConfig::new(); + let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); + config + .root_store + .add_pem_file(pem_file).unwrap(); + ClientConnector::with_connector(Arc::new(config)).start() + } + #[cfg(not(any(feature = "alpn", feature = "rust-tls")))] { ClientConnector::default().start() } @@ -165,16 +181,16 @@ impl TestServer { pub fn url(&self, uri: &str) -> String { if uri.starts_with('/') { format!( - "{}://{}{}", + "{}://localhost:{}{}", if self.ssl { "https" } else { "http" }, - self.addr, + self.addr.port(), uri ) } else { format!( - "{}://{}/{}", + "{}://localhost:{}/{}", if self.ssl { "https" } else { "http" }, - self.addr, + self.addr.port(), uri ) } @@ -241,6 +257,8 @@ pub struct TestServerBuilder { state: Box S + Sync + Send + 'static>, #[cfg(feature = "alpn")] ssl: Option, + #[cfg(feature = "rust-tls")] + ssl: Option>, } impl TestServerBuilder { @@ -251,7 +269,7 @@ impl TestServerBuilder { { TestServerBuilder { state: Box::new(state), - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "rust-tls"))] ssl: None, } } @@ -263,6 +281,13 @@ impl TestServerBuilder { self } + #[cfg(feature = "rust-tls")] + /// Create ssl server + pub fn ssl(mut self, ssl: Arc) -> Self { + self.ssl = Some(ssl); + self + } + #[allow(unused_mut)] /// Configure test application and run test server pub fn start(mut self, config: F) -> TestServer @@ -271,9 +296,9 @@ impl TestServerBuilder { { let (tx, rx) = mpsc::channel(); - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "rust-tls"))] let ssl = self.ssl.is_some(); - #[cfg(not(feature = "alpn"))] + #[cfg(not(any(feature = "alpn", feature = "rust-tls")))] let ssl = false; // run server in separate thread @@ -293,7 +318,7 @@ impl TestServerBuilder { tx.send((System::current(), local_addr, TestServer::get_conn())) .unwrap(); - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "rust-tls"))] { let ssl = self.ssl.take(); if let Some(ssl) = ssl { @@ -302,7 +327,7 @@ impl TestServerBuilder { srv.listen(tcp).start(); } } - #[cfg(not(feature = "alpn"))] + #[cfg(not(any(feature = "alpn", feature = "rust-tls")))] { srv.listen(tcp).start(); } diff --git a/tests/cert.pem b/tests/cert.pem index 159aacea..db04fbfa 100644 --- a/tests/cert.pem +++ b/tests/cert.pem @@ -1,31 +1,31 @@ -----BEGIN CERTIFICATE----- -MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww -CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx -NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD -QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY -MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A -MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1 -sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U -NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy -voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr -odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND -xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA -CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI -yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U -UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO -vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un -CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN -BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk -3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI -JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD -JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL -d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu -ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC -CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur -y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7 -YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh -g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt -tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y -1QU= +MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh +bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4 +MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV +BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD +T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF +cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk +L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ +EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU +05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh +4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA +2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng +dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4 +e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT +2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa +TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID +AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB +AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm +ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g +4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1 +hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe +0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq +seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi +7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO +3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5 +XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq +GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr +E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv -----END CERTIFICATE----- diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 66a9153d..1ed80bf7 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -12,6 +12,8 @@ use rand::Rng; #[cfg(feature = "alpn")] extern crate openssl; +#[cfg(feature = "rust-tls")] +extern crate rustls; use actix::prelude::*; use actix_web::*; @@ -272,3 +274,43 @@ fn test_ws_server_ssl() { assert_eq!(item, data); } } + +#[test] +#[cfg(feature = "rust-tls")] +fn test_ws_server_ssl() { + extern crate rustls; + use rustls::{ServerConfig, NoClientAuth}; + use rustls::internal::pemfile::{certs, rsa_private_keys}; + use std::io::BufReader; + use std::sync::Arc; + use std::fs::File; + + // load ssl keys + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = rsa_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + let mut srv = test::TestServer::build().ssl(Arc::new(config)).start(|app| { + app.handler(|req| { + ws::start( + req, + Ws2 { + count: 0, + bin: false, + }, + ) + }) + }); + + let (mut reader, _writer) = srv.ws().unwrap(); + + let data = Some(ws::Message::Text("0".repeat(65_536))); + for _ in 0..10_000 { + let (item, r) = srv.execute(reader.into_future()).unwrap(); + reader = r; + assert_eq!(item, data); + } +} From 4c4d0d2745f1c49ea2d1a2ac50521684d31c846c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 30 Jul 2018 10:23:28 -0700 Subject: [PATCH 0548/1635] update changes --- CHANGES.md | 7 +++++++ Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d63d6010..c5c0499d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.7.3] - 2018-07-xx + +### Added + +* Support HTTP/2 with rustls #36 + + ## [0.7.2] - 2018-07-26 ### Added diff --git a/Cargo.toml b/Cargo.toml index 54bd0e38..139c647a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.2" +version = "0.7.3" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 7bc0ace52d5045f6dc17a084e452dba4631a1d63 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 30 Jul 2018 13:42:42 -0700 Subject: [PATCH 0549/1635] move server accept impl to seprate module --- src/server/accept.rs | 207 ++++++++++++++++++++++++++++++++++++++++++ src/server/mod.rs | 1 + src/server/srv.rs | 212 +++---------------------------------------- 3 files changed, 223 insertions(+), 197 deletions(-) create mode 100644 src/server/accept.rs diff --git a/src/server/accept.rs b/src/server/accept.rs new file mode 100644 index 00000000..a91ca814 --- /dev/null +++ b/src/server/accept.rs @@ -0,0 +1,207 @@ +use std::sync::mpsc as sync_mpsc; +use std::time::Duration; +use std::{io, net, thread}; + +use futures::sync::mpsc; +use mio; +use slab::Slab; + +#[cfg(feature = "tls")] +use native_tls::TlsAcceptor; + +#[cfg(feature = "alpn")] +use openssl::ssl::{AlpnError, SslAcceptorBuilder}; + +#[cfg(feature = "rust-tls")] +use rustls::ServerConfig; + +use super::srv::{ServerCommand, Socket}; +use super::worker::{Conn, SocketInfo}; + +pub(crate) enum Command { + Pause, + Resume, + Stop, + Worker(usize, mpsc::UnboundedSender>), +} + +pub(crate) fn start_accept_thread( + token: usize, sock: Socket, srv: mpsc::UnboundedSender, + socks: Slab, + mut workers: Vec<(usize, mpsc::UnboundedSender>)>, +) -> (mio::SetReadiness, sync_mpsc::Sender) { + let (tx, rx) = sync_mpsc::channel(); + let (reg, readiness) = mio::Registration::new2(); + + // start accept thread + #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] + let _ = thread::Builder::new() + .name(format!("Accept on {}", sock.addr)) + .spawn(move || { + const SRV: mio::Token = mio::Token(0); + const CMD: mio::Token = mio::Token(1); + + let addr = sock.addr; + let mut server = Some( + mio::net::TcpListener::from_std(sock.lst) + .expect("Can not create mio::net::TcpListener"), + ); + + // 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 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 incoming commands + if let Err(err) = + poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge()) + { + panic!("Can not register Registration: {}", err); + } + + // Create storage for events + let mut events = mio::Events::with_capacity(128); + + // Sleep on error + let sleep = Duration::from_millis(100); + + let mut next = 0; + loop { + if let Err(err) = poll.poll(&mut events, None) { + panic!("Poll error: {}", err); + } + + for event in events.iter() { + match event.token() { + SRV => if let Some(ref server) = server { + loop { + match server.accept_std() { + Ok((io, addr)) => { + let mut msg = Conn { + io, + token, + peer: Some(addr), + http2: false, + }; + while !workers.is_empty() { + match workers[next].1.unbounded_send(msg) { + Ok(_) => (), + Err(err) => { + let _ = srv.unbounded_send( + ServerCommand::WorkerDied( + workers[next].0, + socks.clone(), + ), + ); + msg = err.into_inner(); + workers.swap_remove(next); + if workers.is_empty() { + error!("No workers"); + thread::sleep(sleep); + break; + } else if workers.len() <= next { + next = 0; + } + continue; + } + } + next = (next + 1) % workers.len(); + break; + } + } + Err(ref e) + if e.kind() == io::ErrorKind::WouldBlock => + { + break + } + Err(ref e) if connection_error(e) => continue, + Err(e) => { + error!("Error accepting connection: {}", e); + // sleep after error + thread::sleep(sleep); + break; + } + } + } + }, + CMD => match rx.try_recv() { + Ok(cmd) => match cmd { + Command::Pause => if let Some(ref server) = server { + if let Err(err) = poll.deregister(server) { + error!( + "Can not deregister server socket {}", + err + ); + } else { + info!( + "Paused accepting connections on {}", + addr + ); + } + }, + Command::Resume => { + 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 => { + if let Some(server) = server.take() { + let _ = poll.deregister(&server); + } + return; + } + Command::Worker(idx, addr) => { + workers.push((idx, addr)); + } + }, + Err(err) => match err { + sync_mpsc::TryRecvError::Empty => (), + sync_mpsc::TryRecvError::Disconnected => { + if let Some(server) = server.take() { + let _ = poll.deregister(&server); + } + return; + } + }, + }, + _ => unreachable!(), + } + } + } + }); + + (readiness, tx) +} + +/// This function defines errors that are per-connection. Which basically +/// means that if we get this error from `accept()` system call it means +/// next connection might be ready to be accepted. +/// +/// All other errors will incur a timeout before next `accept()` is performed. +/// The timeout is useful to handle resource exhaustion errors like ENFILE +/// and EMFILE. Otherwise, could enter into tight loop. +fn connection_error(e: &io::Error) -> bool { + e.kind() == io::ErrorKind::ConnectionRefused + || e.kind() == io::ErrorKind::ConnectionAborted + || e.kind() == io::ErrorKind::ConnectionReset +} diff --git a/src/server/mod.rs b/src/server/mod.rs index dc8ecd81..a4f5e87d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,6 +7,7 @@ use futures::{Async, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; +pub(crate) mod accept; mod channel; mod error; pub(crate) mod h1; diff --git a/src/server/srv.rs b/src/server/srv.rs index d6f5cf4d..a054d5a7 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use std::sync::{mpsc as sync_mpsc, Arc}; use std::time::Duration; -use std::{io, net, thread}; +use std::{io, net}; use actix::{ fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler, @@ -25,6 +25,7 @@ use openssl::ssl::{AlpnError, SslAcceptorBuilder}; #[cfg(feature = "rust-tls")] use rustls::ServerConfig; +use super::accept::{start_accept_thread, Command}; use super::channel::{HttpChannel, WrapperStream}; use super::settings::{ServerSettings, WorkerSettings}; use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker}; @@ -75,7 +76,7 @@ where no_signals: bool, } -enum ServerCommand { +pub(crate) enum ServerCommand { WorkerDied(usize, Slab), } @@ -86,10 +87,10 @@ where type Context = Context; } -struct Socket { - lst: net::TcpListener, - addr: net::SocketAddr, - tp: StreamHandlerType, +pub(crate) struct Socket { + pub lst: net::TcpListener, + pub addr: net::SocketAddr, + pub tp: StreamHandlerType, } impl HttpServer @@ -132,7 +133,10 @@ where } #[doc(hidden)] - #[deprecated(since = "0.6.0", note = "please use `HttpServer::workers()` instead")] + #[deprecated( + since = "0.6.0", + note = "please use `HttpServer::workers()` instead" + )] pub fn threads(self, num: usize) -> Self { self.workers(num) } @@ -538,7 +542,8 @@ impl HttpServer { #[doc(hidden)] #[cfg(feature = "tls")] #[deprecated( - since = "0.6.0", note = "please use `actix_web::HttpServer::bind_tls` instead" + since = "0.6.0", + note = "please use `actix_web::HttpServer::bind_tls` instead" )] impl HttpServer { /// Start listening for incoming tls connections. @@ -557,7 +562,8 @@ impl HttpServer { #[doc(hidden)] #[cfg(feature = "alpn")] #[deprecated( - since = "0.6.0", note = "please use `actix_web::HttpServer::bind_ssl` instead" + since = "0.6.0", + note = "please use `actix_web::HttpServer::bind_ssl` instead" )] impl HttpServer { /// Start listening for incoming tls connections. @@ -810,181 +816,6 @@ impl Handler for HttpServer { } } -enum Command { - Pause, - Resume, - Stop, - Worker(usize, mpsc::UnboundedSender>), -} - -fn start_accept_thread( - token: usize, sock: Socket, srv: mpsc::UnboundedSender, - socks: Slab, - mut workers: Vec<(usize, mpsc::UnboundedSender>)>, -) -> (mio::SetReadiness, sync_mpsc::Sender) { - let (tx, rx) = sync_mpsc::channel(); - let (reg, readiness) = mio::Registration::new2(); - - // start accept thread - #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - let _ = thread::Builder::new() - .name(format!("Accept on {}", sock.addr)) - .spawn(move || { - const SRV: mio::Token = mio::Token(0); - const CMD: mio::Token = mio::Token(1); - - let addr = sock.addr; - let mut server = Some( - mio::net::TcpListener::from_std(sock.lst) - .expect("Can not create mio::net::TcpListener"), - ); - - // 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 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 incoming commands - if let Err(err) = - poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge()) - { - panic!("Can not register Registration: {}", err); - } - - // Create storage for events - let mut events = mio::Events::with_capacity(128); - - // Sleep on error - let sleep = Duration::from_millis(100); - - let mut next = 0; - loop { - if let Err(err) = poll.poll(&mut events, None) { - panic!("Poll error: {}", err); - } - - for event in events.iter() { - match event.token() { - SRV => if let Some(ref server) = server { - loop { - match server.accept_std() { - Ok((io, addr)) => { - let mut msg = Conn { - io, - token, - peer: Some(addr), - http2: false, - }; - while !workers.is_empty() { - match workers[next].1.unbounded_send(msg) { - Ok(_) => (), - Err(err) => { - let _ = srv.unbounded_send( - ServerCommand::WorkerDied( - workers[next].0, - socks.clone(), - ), - ); - msg = err.into_inner(); - workers.swap_remove(next); - if workers.is_empty() { - error!("No workers"); - thread::sleep(sleep); - break; - } else if workers.len() <= next { - next = 0; - } - continue; - } - } - next = (next + 1) % workers.len(); - break; - } - } - Err(ref e) - if e.kind() == io::ErrorKind::WouldBlock => - { - break - } - Err(ref e) if connection_error(e) => continue, - Err(e) => { - error!("Error accepting connection: {}", e); - // sleep after error - thread::sleep(sleep); - break; - } - } - } - }, - CMD => match rx.try_recv() { - Ok(cmd) => match cmd { - Command::Pause => if let Some(ref server) = server { - if let Err(err) = poll.deregister(server) { - error!( - "Can not deregister server socket {}", - err - ); - } else { - info!( - "Paused accepting connections on {}", - addr - ); - } - }, - Command::Resume => { - 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 => { - if let Some(server) = server.take() { - let _ = poll.deregister(&server); - } - return; - } - Command::Worker(idx, addr) => { - workers.push((idx, addr)); - } - }, - Err(err) => match err { - sync_mpsc::TryRecvError::Empty => (), - sync_mpsc::TryRecvError::Disconnected => { - if let Some(server) = server.take() { - let _ = poll.deregister(&server); - } - return; - } - }, - }, - _ => unreachable!(), - } - } - } - }); - - (readiness, tx) -} - fn create_tcp_listener( addr: net::SocketAddr, backlog: i32, ) -> io::Result { @@ -996,16 +827,3 @@ fn create_tcp_listener( builder.bind(addr)?; Ok(builder.listen(backlog)?) } - -/// This function defines errors that are per-connection. Which basically -/// means that if we get this error from `accept()` system call it means -/// next connection might be ready to be accepted. -/// -/// All other errors will incur a timeout before next `accept()` is performed. -/// The timeout is useful to handle resource exhaustion errors like ENFILE -/// and EMFILE. Otherwise, could enter into tight loop. -fn connection_error(e: &io::Error) -> bool { - e.kind() == io::ErrorKind::ConnectionRefused - || e.kind() == io::ErrorKind::ConnectionAborted - || e.kind() == io::ErrorKind::ConnectionReset -} From 2072c933ba6448966c50ad50887af51f95ee39c2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 30 Jul 2018 15:04:52 -0700 Subject: [PATCH 0550/1635] handle error during request creation --- src/client/request.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index 650f0eea..72aab259 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -316,8 +316,7 @@ impl ClientRequestBuilder { /// Set HTTP method of this request. #[inline] pub fn get_method(&mut self) -> &Method { - let parts = - parts(&mut self.request, &self.err).expect("cannot reuse request builder"); + let parts = self.request.as_ref().expect("cannot reuse request builder"); &parts.method } From 4dba531bf91fc68aa4a3625e4cc205f2ec266f8a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 31 Jul 2018 08:51:24 -0700 Subject: [PATCH 0551/1635] do not override HOST header for client request #428 --- CHANGES.md | 4 ++++ src/client/request.rs | 21 ++++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c5c0499d..8ba3ef56 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ * Support HTTP/2 with rustls #36 +### Fixed + +* Do not override HOST header for client request #428 + ## [0.7.2] - 2018-07-26 diff --git a/src/client/request.rs b/src/client/request.rs index 72aab259..4d506c3f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -291,10 +291,6 @@ impl ClientRequestBuilder { fn _uri(&mut self, url: &str) -> &mut Self { match Uri::try_from(url) { Ok(uri) => { - // set request host header - if let Some(host) = uri.host() { - self.set_header(header::HOST, host); - } if let Some(parts) = parts(&mut self.request, &self.err) { parts.uri = uri; } @@ -629,9 +625,24 @@ impl ClientRequestBuilder { self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); } + // set request host header + if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(host) = parts.uri.host() { + if !parts.headers.contains_key(header::HOST) { + match host.try_into() { + Ok(value) => { + parts.headers.insert(header::HOST, value); + } + Err(e) => self.err = Some(e.into()), + } + } + } + } + + // user agent self.set_header_if_none( header::USER_AGENT, - concat!("Actix-web/", env!("CARGO_PKG_VERSION")), + concat!("actix-web/", env!("CARGO_PKG_VERSION")), ); } From 3bd43090fb7ec452251840c1bd813d6745bf6916 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 31 Jul 2018 09:06:05 -0700 Subject: [PATCH 0552/1635] use new gzdecoder, fixes gz streaming #228 --- CHANGES.md | 2 + Cargo.toml | 2 +- src/server/input.rs | 125 ++++++++++--------------------------------- tests/test_client.rs | 2 +- 4 files changed, 32 insertions(+), 99 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8ba3ef56..95144ce1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ * Do not override HOST header for client request #428 +* Gz streaming, use `flate2::write::GzDecoder` #228 + ## [0.7.2] - 2018-07-26 diff --git a/Cargo.toml b/Cargo.toml index 139c647a..695b2e31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ parking_lot = "0.6" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } -flate2 = { version="1.0", optional = true, default-features = false } +flate2 = { version="^1.0.2", optional = true, default-features = false } failure = "=0.1.1" diff --git a/src/server/input.rs b/src/server/input.rs index 8c11c246..fe62e760 100644 --- a/src/server/input.rs +++ b/src/server/input.rs @@ -1,14 +1,11 @@ -use std::io::{Read, Write}; -use std::{cmp, io}; +use std::io::{self, Write}; #[cfg(feature = "brotli")] use brotli2::write::BrotliDecoder; -use bytes::{BufMut, Bytes, BytesMut}; +use bytes::{Bytes, BytesMut}; use error::PayloadError; #[cfg(feature = "flate2")] -use flate2::read::GzDecoder; -#[cfg(feature = "flate2")] -use flate2::write::DeflateDecoder; +use flate2::write::{DeflateDecoder, GzDecoder}; use header::ContentEncoding; use http::header::{HeaderMap, CONTENT_ENCODING}; use payload::{PayloadSender, PayloadStatus, PayloadWriter}; @@ -144,46 +141,12 @@ pub(crate) enum Decoder { #[cfg(feature = "flate2")] Deflate(Box>), #[cfg(feature = "flate2")] - Gzip(Option>>), + Gzip(Box>), #[cfg(feature = "brotli")] Br(Box>), Identity, } -// should go after write::GzDecoder get implemented -#[derive(Debug)] -pub(crate) struct Wrapper { - pub buf: BytesMut, - pub eof: bool, -} - -impl io::Read for Wrapper { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let len = cmp::min(buf.len(), self.buf.len()); - buf[..len].copy_from_slice(&self.buf[..len]); - self.buf.split_to(len); - if len == 0 { - if self.eof { - Ok(0) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - Ok(len) - } - } -} - -impl io::Write for Wrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - pub(crate) struct Writer { buf: BytesMut, } @@ -212,12 +175,11 @@ impl io::Write for Writer { /// Payload stream with decompression support pub(crate) struct PayloadStream { decoder: Decoder, - dst: BytesMut, } impl PayloadStream { pub fn new(enc: ContentEncoding) -> PayloadStream { - let dec = match enc { + let decoder = match enc { #[cfg(feature = "brotli")] ContentEncoding::Br => { Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) @@ -227,13 +189,12 @@ impl PayloadStream { Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new()))) } #[cfg(feature = "flate2")] - ContentEncoding::Gzip => Decoder::Gzip(None), + ContentEncoding::Gzip => { + Decoder::Gzip(Box::new(GzDecoder::new(Writer::new()))) + } _ => Decoder::Identity, }; - PayloadStream { - decoder: dec, - dst: BytesMut::new(), - } + PayloadStream { decoder } } } @@ -253,22 +214,17 @@ impl PayloadStream { Err(e) => Err(e), }, #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => { - if let Some(ref mut decoder) = *decoder { - decoder.as_mut().get_mut().eof = true; - - self.dst.reserve(8192); - match decoder.read(unsafe { self.dst.bytes_mut() }) { - Ok(n) => { - unsafe { self.dst.advance_mut(n) }; - return Ok(Some(self.dst.take().freeze())); - } - Err(e) => return Err(e), + Decoder::Gzip(ref mut decoder) => match decoder.try_finish() { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) } - } else { - Ok(None) } - } + Err(e) => Err(e), + }, #[cfg(feature = "flate2")] Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { Ok(_) => { @@ -301,43 +257,18 @@ impl PayloadStream { Err(e) => Err(e), }, #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => { - if decoder.is_none() { - *decoder = Some(Box::new(GzDecoder::new(Wrapper { - buf: BytesMut::from(data), - eof: false, - }))); - } else { - let _ = decoder.as_mut().unwrap().write(&data); - } - - loop { - self.dst.reserve(8192); - match decoder - .as_mut() - .as_mut() - .unwrap() - .read(unsafe { self.dst.bytes_mut() }) - { - Ok(n) => { - if n != 0 { - unsafe { self.dst.advance_mut(n) }; - } - if n == 0 { - return Ok(Some(self.dst.take().freeze())); - } - } - Err(e) => { - if e.kind() == io::ErrorKind::WouldBlock - && !self.dst.is_empty() - { - return Ok(Some(self.dst.take().freeze())); - } - return Err(e); - } + Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) } } - } + Err(e) => Err(e), + }, #[cfg(feature = "flate2")] Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { diff --git a/tests/test_client.rs b/tests/test_client.rs index cf20fb8b..5e685699 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -438,7 +438,7 @@ fn test_default_headers() { let repr = format!("{:?}", request); assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); assert!(repr.contains(concat!( - "\"user-agent\": \"Actix-web/", + "\"user-agent\": \"actix-web/", env!("CARGO_PKG_VERSION"), "\"" ))); From 2071ea053293e1f1bfde4e43bfab9137ac62ba48 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 31 Jul 2018 15:40:52 -0700 Subject: [PATCH 0553/1635] HttpRequest::url_for is not working with scopes #429 --- CHANGES.md | 2 + src/application.rs | 3 +- src/extractor.rs | 8 +- src/httprequest.rs | 12 +- src/router.rs | 297 ++++++++++++++++++++++++++++++++++----------- src/scope.rs | 23 ++-- src/test.rs | 8 +- 7 files changed, 257 insertions(+), 96 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 95144ce1..237b4bfb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ * Gz streaming, use `flate2::write::GzDecoder` #228 +* HttpRequest::url_for is not working with scopes #429 + ## [0.7.2] - 2018-07-26 diff --git a/src/application.rs b/src/application.rs index a5cd3386..6885185f 100644 --- a/src/application.rs +++ b/src/application.rs @@ -140,7 +140,7 @@ where parts: Some(ApplicationParts { state, prefix: "".to_owned(), - router: Router::new(), + router: Router::new(ResourceDef::prefix("")), middlewares: Vec::new(), filters: Vec::new(), encoding: ContentEncoding::Auto, @@ -198,6 +198,7 @@ where if !prefix.starts_with('/') { prefix.insert(0, '/') } + parts.router.set_prefix(&prefix); parts.prefix = prefix; } self diff --git a/src/extractor.rs b/src/extractor.rs index aa4fdea7..5c2c7f60 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -934,7 +934,7 @@ mod tests { fn test_request_extract() { let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); @@ -950,7 +950,7 @@ mod tests { let s = Query::::from_request(&req, &()).unwrap(); assert_eq!(s.id, "test"); - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); let req = TestRequest::with_uri("/name/32/").finish(); let info = router.recognize(&req, &(), 0); @@ -971,7 +971,7 @@ mod tests { #[test] fn test_extract_path_single() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); let req = TestRequest::with_uri("/32/").finish(); @@ -982,7 +982,7 @@ mod tests { #[test] fn test_tuple_extract() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); let req = TestRequest::with_uri("/name/user1/?id=test").finish(); diff --git a/src/httprequest.rs b/src/httprequest.rs index 83017dfa..6f3bfe13 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -420,7 +420,7 @@ mod tests { #[test] fn test_request_match_info() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/{key}/"))); let req = TestRequest::with_uri("/value/?id=test").finish(); @@ -430,7 +430,7 @@ mod tests { #[test] fn test_url_for() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}")); resource.name("index"); router.register_resource(resource); @@ -464,7 +464,8 @@ mod tests { fn test_url_for_with_prefix() { let mut resource = Resource::new(ResourceDef::new("/user/{name}.html")); resource.name("index"); - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); + router.set_prefix("/prefix"); router.register_resource(resource); let mut info = router.default_route_info(); @@ -490,7 +491,8 @@ mod tests { fn test_url_for_static() { let mut resource = Resource::new(ResourceDef::new("/index.html")); resource.name("index"); - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); + router.set_prefix("/prefix"); router.register_resource(resource); let mut info = router.default_route_info(); @@ -513,7 +515,7 @@ mod tests { #[test] fn test_url_for_external() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_external( "youtube", ResourceDef::external("https://youtube.com/watch/{video_id}"), diff --git a/src/router.rs b/src/router.rs index f3f657b5..3d112bf6 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::cmp::min; use std::collections::HashMap; use std::hash::{Hash, Hasher}; @@ -111,9 +112,14 @@ impl ResourceInfo { U: IntoIterator, I: AsRef, { - if let Some(pattern) = self.rmap.named.get(name) { - let path = - pattern.resource_path(elements, &req.path()[..(self.prefix as usize)])?; + let mut path = String::new(); + let mut elements = elements.into_iter(); + + if self + .rmap + .patterns_for(name, &mut path, &mut elements)? + .is_some() + { if path.starts_with('/') { let conn = req.connection_info(); Ok(Url::parse(&format!( @@ -160,12 +166,15 @@ impl ResourceInfo { } pub(crate) struct ResourceMap { + root: ResourceDef, + parent: RefCell>>, named: HashMap, patterns: Vec<(ResourceDef, Option>)>, + nested: Vec>, } impl ResourceMap { - pub fn has_resource(&self, path: &str) -> bool { + fn has_resource(&self, path: &str) -> bool { let path = if path.is_empty() { "/" } else { path }; for (pattern, rmap) in &self.patterns { @@ -179,20 +188,91 @@ impl ResourceMap { } false } + + fn patterns_for( + &self, name: &str, path: &mut String, elements: &mut U, + ) -> Result, UrlGenerationError> + where + U: Iterator, + I: AsRef, + { + if self.pattern_for(name, path, elements)?.is_some() { + Ok(Some(())) + } else { + self.parent_pattern_for(name, path, elements) + } + } + + fn pattern_for( + &self, name: &str, path: &mut String, elements: &mut U, + ) -> Result, UrlGenerationError> + where + U: Iterator, + I: AsRef, + { + if let Some(pattern) = self.named.get(name) { + self.fill_root(path, elements)?; + pattern.resource_path(path, elements)?; + Ok(Some(())) + } else { + for rmap in &self.nested { + if rmap.pattern_for(name, path, elements)?.is_some() { + return Ok(Some(())); + } + } + Ok(None) + } + } + + fn fill_root( + &self, path: &mut String, elements: &mut U, + ) -> Result<(), UrlGenerationError> + where + U: Iterator, + I: AsRef, + { + if let Some(ref parent) = *self.parent.borrow() { + parent.fill_root(path, elements)?; + } + self.root.resource_path(path, elements) + } + + fn parent_pattern_for( + &self, name: &str, path: &mut String, elements: &mut U, + ) -> Result, UrlGenerationError> + where + U: Iterator, + I: AsRef, + { + if let Some(ref parent) = *self.parent.borrow() { + if let Some(pattern) = parent.named.get(name) { + self.fill_root(path, elements)?; + pattern.resource_path(path, elements)?; + Ok(Some(())) + } else { + parent.parent_pattern_for(name, path, elements) + } + } else { + Ok(None) + } + } } impl Default for Router { fn default() -> Self { - Router::new() + Router::new(ResourceDef::new("")) } } impl Router { - pub(crate) fn new() -> Self { + pub(crate) fn new(root: ResourceDef) -> Self { Router { rmap: Rc::new(ResourceMap { + root, + parent: RefCell::new(None), named: HashMap::new(), patterns: Vec::new(), + nested: Vec::new(), }), resources: Vec::new(), patterns: Vec::new(), @@ -233,6 +313,10 @@ impl Router { } } + pub(crate) fn set_prefix(&mut self, path: &str) { + Rc::get_mut(&mut self.rmap).unwrap().root = ResourceDef::new(path); + } + pub(crate) fn register_resource(&mut self, resource: Resource) { { let rmap = Rc::get_mut(&mut self.rmap).unwrap(); @@ -258,6 +342,11 @@ impl Router { .unwrap() .patterns .push((scope.rdef().clone(), Some(scope.router().rmap.clone()))); + Rc::get_mut(&mut self.rmap) + .unwrap() + .nested + .push(scope.router().rmap.clone()); + let filters = scope.take_filters(); self.patterns .push(ResourcePattern::Scope(scope.rdef().clone(), filters)); @@ -286,22 +375,25 @@ impl Router { } pub(crate) fn finish(&mut self) { - if let Some(ref default) = self.default { - for resource in &mut self.resources { - match resource { - ResourceItem::Resource(_) => (), - ResourceItem::Scope(scope) => { - if !scope.has_default_resource() { + for resource in &mut self.resources { + match resource { + ResourceItem::Resource(_) => (), + ResourceItem::Scope(scope) => { + if !scope.has_default_resource() { + if let Some(ref default) = self.default { scope.default_resource(default.clone()); } - scope.finish() } - ResourceItem::Handler(hnd) => { - if !hnd.has_default_resource() { + *scope.router().rmap.parent.borrow_mut() = Some(self.rmap.clone()); + scope.finish(); + } + ResourceItem::Handler(hnd) => { + if !hnd.has_default_resource() { + if let Some(ref default) = self.default { hnd.default_resource(default.clone()); } - hnd.finish() } + hnd.finish() } } } @@ -459,35 +551,38 @@ pub struct ResourceDef { } impl ResourceDef { - /// Parse path pattern and create new `Resource` instance. + /// Parse path pattern and create new `ResourceDef` instance. /// /// Panics if path pattern is wrong. pub fn new(path: &str) -> Self { - ResourceDef::with_prefix(path, if path.is_empty() { "" } else { "/" }, false) + ResourceDef::with_prefix(path, false, !path.is_empty()) } - /// Parse path pattern and create new `Resource` instance. + /// Parse path pattern and create new `ResourceDef` instance. /// /// Use `prefix` type instead of `static`. /// /// Panics if path regex pattern is wrong. pub fn prefix(path: &str) -> Self { - ResourceDef::with_prefix(path, "/", true) + ResourceDef::with_prefix(path, true, !path.is_empty()) } - /// Construct external resource + /// Construct external resource def /// /// Panics if path pattern is wrong. pub fn external(path: &str) -> Self { - let mut resource = ResourceDef::with_prefix(path, "/", false); + let mut resource = ResourceDef::with_prefix(path, false, false); resource.rtp = ResourceType::External; resource } - /// Parse path pattern and create new `Resource` instance with custom prefix - pub fn with_prefix(path: &str, prefix: &str, for_prefix: bool) -> Self { - let (pattern, elements, is_dynamic, len) = - ResourceDef::parse(path, prefix, for_prefix); + /// Parse path pattern and create new `ResourceDef` instance with custom prefix + pub fn with_prefix(path: &str, for_prefix: bool, slash: bool) -> Self { + let mut path = path.to_owned(); + if slash && !path.starts_with('/') { + path.insert(0, '/'); + } + let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix); let tp = if is_dynamic { let re = match Regex::new(&pattern) { @@ -705,23 +800,21 @@ impl ResourceDef { /// Build resource path. pub fn resource_path( - &self, elements: U, prefix: &str, - ) -> Result + &self, path: &mut String, elements: &mut U, + ) -> Result<(), UrlGenerationError> where - U: IntoIterator, + U: Iterator, I: AsRef, { - let mut path = match self.tp { - PatternType::Prefix(ref p) => p.to_owned(), - PatternType::Static(ref p) => p.to_owned(), + match self.tp { + PatternType::Prefix(ref p) => path.push_str(p), + PatternType::Static(ref p) => path.push_str(p), PatternType::Dynamic(..) => { - let mut path = String::new(); - let mut iter = elements.into_iter(); for el in &self.elements { match *el { PatternElement::Str(ref s) => path.push_str(s), PatternElement::Var(_) => { - if let Some(val) = iter.next() { + if let Some(val) = elements.next() { path.push_str(val.as_ref()) } else { return Err(UrlGenerationError::NotEnoughElements); @@ -729,34 +822,18 @@ impl ResourceDef { } } } - path } }; - - if self.rtp != ResourceType::External { - if prefix.ends_with('/') { - if path.starts_with('/') { - path.insert_str(0, &prefix[..prefix.len() - 1]); - } else { - path.insert_str(0, prefix); - } - } else { - if !path.starts_with('/') { - path.insert(0, '/'); - } - path.insert_str(0, prefix); - } - } - Ok(path) + Ok(()) } fn parse( - pattern: &str, prefix: &str, for_prefix: bool, + pattern: &str, for_prefix: bool, ) -> (String, Vec, bool, usize) { const DEFAULT_PATTERN: &str = "[^/]+"; - let mut re1 = String::from("^") + prefix; - let mut re2 = String::from(prefix); + let mut re1 = String::from("^"); + let mut re2 = String::new(); let mut el = String::new(); let mut in_param = false; let mut in_param_pattern = false; @@ -766,12 +843,7 @@ impl ResourceDef { let mut elems = Vec::new(); let mut len = 0; - 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; - } - + for ch in pattern.chars() { if in_param { // In parameter segment: `{....}` if ch == '}' { @@ -846,7 +918,7 @@ mod tests { #[test] fn test_recognizer10() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/name"))); router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); router.register_resource(Resource::new(ResourceDef::new( @@ -858,7 +930,7 @@ mod tests { ))); router.register_resource(Resource::new(ResourceDef::new("/v/{tail:.*}"))); router.register_resource(Resource::new(ResourceDef::new("/test2/{test}.html"))); - router.register_resource(Resource::new(ResourceDef::new("{test}/index.html"))); + router.register_resource(Resource::new(ResourceDef::new("/{test}/index.html"))); let req = TestRequest::with_uri("/name").finish(); let info = router.recognize(&req, &(), 0); @@ -909,7 +981,7 @@ mod tests { #[test] fn test_recognizer_2() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/index.json"))); router.register_resource(Resource::new(ResourceDef::new("/{source}.json"))); @@ -924,7 +996,7 @@ mod tests { #[test] fn test_recognizer_with_prefix() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/name"))); router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); @@ -943,7 +1015,7 @@ mod tests { assert_eq!(&info.match_info()["val"], "value"); // same patterns - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); router.register_resource(Resource::new(ResourceDef::new("/name"))); router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); @@ -1049,7 +1121,7 @@ mod tests { #[test] fn test_request_resource() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); let mut resource = Resource::new(ResourceDef::new("/index.json")); resource.name("r1"); router.register_resource(resource); @@ -1071,7 +1143,7 @@ mod tests { #[test] fn test_has_resource() { - let mut router = Router::<()>::new(); + let mut router = Router::<()>::default(); let scope = Scope::new("/test").resource("/name", |_| "done"); router.register_scope(scope); @@ -1088,4 +1160,93 @@ mod tests { let info = router.default_route_info(); assert!(info.has_resource("/test2/test10/name")); } + + #[test] + fn test_url_for() { + let mut router = Router::<()>::new(ResourceDef::prefix("")); + + let mut resource = Resource::new(ResourceDef::new("/tttt")); + resource.name("r0"); + router.register_resource(resource); + + let scope = Scope::new("/test").resource("/name", |r| { + r.name("r1"); + }); + router.register_scope(scope); + + let scope = Scope::new("/test2") + .nested("/test10", |s| s.resource("/name", |r| r.name("r2"))); + router.register_scope(scope); + router.finish(); + + let req = TestRequest::with_uri("/test").request(); + { + let info = router.default_route_info(); + + let res = info + .url_for(&req, "r0", Vec::<&'static str>::new()) + .unwrap(); + assert_eq!(res.as_str(), "http://localhost:8080/tttt"); + + let res = info + .url_for(&req, "r1", Vec::<&'static str>::new()) + .unwrap(); + assert_eq!(res.as_str(), "http://localhost:8080/test/name"); + + let res = info + .url_for(&req, "r2", Vec::<&'static str>::new()) + .unwrap(); + assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); + } + + let req = TestRequest::with_uri("/test/name").request(); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, ResourceId::Normal(1)); + + let res = info + .url_for(&req, "r0", Vec::<&'static str>::new()) + .unwrap(); + assert_eq!(res.as_str(), "http://localhost:8080/tttt"); + + let res = info + .url_for(&req, "r1", Vec::<&'static str>::new()) + .unwrap(); + assert_eq!(res.as_str(), "http://localhost:8080/test/name"); + + let res = info + .url_for(&req, "r2", Vec::<&'static str>::new()) + .unwrap(); + assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); + } + + #[test] + fn test_url_for_dynamic() { + let mut router = Router::<()>::new(ResourceDef::prefix("")); + + let mut resource = Resource::new(ResourceDef::new("/{name}/test/index.{ext}")); + resource.name("r0"); + router.register_resource(resource); + + let scope = Scope::new("/{name1}").nested("/{name2}", |s| { + s.resource("/{name3}/test/index.{ext}", |r| r.name("r2")) + }); + router.register_scope(scope); + router.finish(); + + let req = TestRequest::with_uri("/test").request(); + { + let info = router.default_route_info(); + + let res = info.url_for(&req, "r0", vec!["sec1", "html"]).unwrap(); + assert_eq!(res.as_str(), "http://localhost:8080/sec1/test/index.html"); + + let res = info + .url_for(&req, "r2", vec!["sec1", "sec2", "sec3", "html"]) + .unwrap(); + assert_eq!( + res.as_str(), + "http://localhost:8080/sec1/sec2/sec3/test/index.html" + ); + } + } } diff --git a/src/scope.rs b/src/scope.rs index 43d07852..d8a0a81a 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -58,11 +58,11 @@ pub struct Scope { #[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] impl Scope { /// Create a new scope - // TODO: Why is this not exactly the default impl? pub fn new(path: &str) -> Scope { + let rdef = ResourceDef::prefix(path); Scope { - rdef: ResourceDef::prefix(path), - router: Rc::new(Router::new()), + rdef: rdef.clone(), + router: Rc::new(Router::new(rdef)), filters: Vec::new(), middlewares: Rc::new(Vec::new()), } @@ -132,10 +132,11 @@ impl Scope { where F: FnOnce(Scope) -> Scope, { + let rdef = ResourceDef::prefix(path); let scope = Scope { - rdef: ResourceDef::prefix(path), + rdef: rdef.clone(), filters: Vec::new(), - router: Rc::new(Router::new()), + router: Rc::new(Router::new(rdef)), middlewares: Rc::new(Vec::new()), }; let mut scope = f(scope); @@ -178,10 +179,11 @@ impl Scope { where F: FnOnce(Scope) -> Scope, { + let rdef = ResourceDef::prefix(&path); let scope = Scope { - rdef: ResourceDef::prefix(&path), + rdef: rdef.clone(), filters: Vec::new(), - router: Rc::new(Router::new()), + router: Rc::new(Router::new(rdef)), middlewares: Rc::new(Vec::new()), }; Rc::get_mut(&mut self.router) @@ -258,12 +260,7 @@ impl Scope { F: FnOnce(&mut Resource) -> R + 'static, { // add resource - let pattern = ResourceDef::with_prefix( - path, - if path.is_empty() { "" } else { "/" }, - false, - ); - let mut resource = Resource::new(pattern); + let mut resource = Resource::new(ResourceDef::new(path)); f(&mut resource); Rc::get_mut(&mut self.router) diff --git a/src/test.rs b/src/test.rs index f466db2d..f94732dd 100644 --- a/src/test.rs +++ b/src/test.rs @@ -147,13 +147,11 @@ impl TestServer { #[cfg(feature = "rust-tls")] { use rustls::ClientConfig; - use std::io::BufReader; use std::fs::File; + use std::io::BufReader; let mut config = ClientConfig::new(); let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - config - .root_store - .add_pem_file(pem_file).unwrap(); + config.root_store.add_pem_file(pem_file).unwrap(); ClientConnector::with_connector(Arc::new(config)).start() } #[cfg(not(any(feature = "alpn", feature = "rust-tls")))] @@ -574,7 +572,7 @@ impl TestRequest { payload, prefix, } = self; - let router = Router::<()>::new(); + let router = Router::<()>::default(); let pool = RequestPool::pool(ServerSettings::default()); let mut req = RequestPool::get(pool); From aa1e75f071e0c729b217e42a93b742ace0ba6b39 Mon Sep 17 00:00:00 2001 From: jrconlin Date: Tue, 31 Jul 2018 16:21:18 -0700 Subject: [PATCH 0554/1635] feature: allow TestServer to open a websocket on any URL * added `TestServer::ws_at(uri_str)` * modified `TestServer::ws()` to call `self.ws_at("/")` to preserve behavior Closes #432 --- src/test.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/test.rs b/src/test.rs index f94732dd..2ec7a98d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -207,15 +207,23 @@ impl TestServer { self.rt.block_on(fut) } - /// Connect to websocket server - pub fn ws( + /// Connect to websocket server at a given path + pub fn ws_at( &mut self, + path: &str, ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - let url = self.url("/"); + let url = self.url(path); self.rt .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) } + /// Connect to a websocket server + pub fn ws( + &mut self, + ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { + self.ws_at("/") + } + /// Create `GET` request pub fn get(&self) -> ClientRequestBuilder { ClientRequest::get(self.url("/").as_str()) From 58230b15b9cd67a98e65a074652bd384e24757f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 31 Jul 2018 19:51:26 -0700 Subject: [PATCH 0555/1635] use one thread for accept loop; refactor rust-tls support --- .travis.yml | 6 +- src/server/accept.rs | 439 +++++++++++++++++++++++++++---------------- src/server/mod.rs | 6 +- src/server/srv.rs | 57 +++--- src/test.rs | 65 ++++--- tests/test_server.rs | 56 ++++++ tests/test_ws.rs | 9 +- 7 files changed, 406 insertions(+), 232 deletions(-) diff --git a/.travis.yml b/.travis.yml index 54a86aa7..f03c9523 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,12 +32,12 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then cargo clean - cargo test --features="alpn,tls" -- --nocapture + cargo test --features="alpn,tls,rust-tls" -- --nocapture fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - cargo tarpaulin --features="alpn,tls" --out Xml --no-count + cargo tarpaulin --features="alpn,tls,rust-tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi @@ -46,7 +46,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then - cargo doc --features "alpn, tls, session" --no-deps && + cargo doc --features "alpn, tls, rust-tls, session" --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/src/server/accept.rs b/src/server/accept.rs index a91ca814..75280560 100644 --- a/src/server/accept.rs +++ b/src/server/accept.rs @@ -1,22 +1,16 @@ use std::sync::mpsc as sync_mpsc; -use std::time::Duration; +use std::time::{Duration, Instant}; use std::{io, net, thread}; -use futures::sync::mpsc; +use futures::{sync::mpsc, Future}; use mio; use slab::Slab; +use tokio_timer::Delay; -#[cfg(feature = "tls")] -use native_tls::TlsAcceptor; - -#[cfg(feature = "alpn")] -use openssl::ssl::{AlpnError, SslAcceptorBuilder}; - -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; +use actix::{msgs::Execute, Arbiter, System}; use super::srv::{ServerCommand, Socket}; -use super::worker::{Conn, SocketInfo}; +use super::worker::Conn; pub(crate) enum Command { Pause, @@ -25,169 +19,43 @@ pub(crate) enum Command { Worker(usize, mpsc::UnboundedSender>), } +struct ServerSocketInfo { + addr: net::SocketAddr, + token: usize, + sock: mio::net::TcpListener, + timeout: Option, +} + +struct Accept { + poll: mio::Poll, + rx: sync_mpsc::Receiver, + sockets: Slab, + workers: Vec<(usize, mpsc::UnboundedSender>)>, + _reg: mio::Registration, + next: usize, + srv: mpsc::UnboundedSender, + timer: (mio::Registration, mio::SetReadiness), +} + +const CMD: mio::Token = mio::Token(0); +const TIMER: mio::Token = mio::Token(1); + pub(crate) fn start_accept_thread( - token: usize, sock: Socket, srv: mpsc::UnboundedSender, - socks: Slab, - mut workers: Vec<(usize, mpsc::UnboundedSender>)>, + socks: Vec<(usize, Socket)>, srv: mpsc::UnboundedSender, + workers: Vec<(usize, mpsc::UnboundedSender>)>, ) -> (mio::SetReadiness, sync_mpsc::Sender) { let (tx, rx) = sync_mpsc::channel(); let (reg, readiness) = mio::Registration::new2(); + let sys = System::current(); + // start accept thread #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] let _ = thread::Builder::new() - .name(format!("Accept on {}", sock.addr)) + .name("actix-web accept loop".to_owned()) .spawn(move || { - const SRV: mio::Token = mio::Token(0); - const CMD: mio::Token = mio::Token(1); - - let addr = sock.addr; - let mut server = Some( - mio::net::TcpListener::from_std(sock.lst) - .expect("Can not create mio::net::TcpListener"), - ); - - // 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 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 incoming commands - if let Err(err) = - poll.register(®, CMD, mio::Ready::readable(), mio::PollOpt::edge()) - { - panic!("Can not register Registration: {}", err); - } - - // Create storage for events - let mut events = mio::Events::with_capacity(128); - - // Sleep on error - let sleep = Duration::from_millis(100); - - let mut next = 0; - loop { - if let Err(err) = poll.poll(&mut events, None) { - panic!("Poll error: {}", err); - } - - for event in events.iter() { - match event.token() { - SRV => if let Some(ref server) = server { - loop { - match server.accept_std() { - Ok((io, addr)) => { - let mut msg = Conn { - io, - token, - peer: Some(addr), - http2: false, - }; - while !workers.is_empty() { - match workers[next].1.unbounded_send(msg) { - Ok(_) => (), - Err(err) => { - let _ = srv.unbounded_send( - ServerCommand::WorkerDied( - workers[next].0, - socks.clone(), - ), - ); - msg = err.into_inner(); - workers.swap_remove(next); - if workers.is_empty() { - error!("No workers"); - thread::sleep(sleep); - break; - } else if workers.len() <= next { - next = 0; - } - continue; - } - } - next = (next + 1) % workers.len(); - break; - } - } - Err(ref e) - if e.kind() == io::ErrorKind::WouldBlock => - { - break - } - Err(ref e) if connection_error(e) => continue, - Err(e) => { - error!("Error accepting connection: {}", e); - // sleep after error - thread::sleep(sleep); - break; - } - } - } - }, - CMD => match rx.try_recv() { - Ok(cmd) => match cmd { - Command::Pause => if let Some(ref server) = server { - if let Err(err) = poll.deregister(server) { - error!( - "Can not deregister server socket {}", - err - ); - } else { - info!( - "Paused accepting connections on {}", - addr - ); - } - }, - Command::Resume => { - 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 => { - if let Some(server) = server.take() { - let _ = poll.deregister(&server); - } - return; - } - Command::Worker(idx, addr) => { - workers.push((idx, addr)); - } - }, - Err(err) => match err { - sync_mpsc::TryRecvError::Empty => (), - sync_mpsc::TryRecvError::Disconnected => { - if let Some(server) = server.take() { - let _ = poll.deregister(&server); - } - return; - } - }, - }, - _ => unreachable!(), - } - } - } + System::set_current(sys); + Accept::new(reg, rx, socks, workers, srv).poll(); }); (readiness, tx) @@ -205,3 +73,244 @@ fn connection_error(e: &io::Error) -> bool { || e.kind() == io::ErrorKind::ConnectionAborted || e.kind() == io::ErrorKind::ConnectionReset } + +impl Accept { + fn new( + _reg: mio::Registration, rx: sync_mpsc::Receiver, + socks: Vec<(usize, Socket)>, + workers: Vec<(usize, mpsc::UnboundedSender>)>, + srv: mpsc::UnboundedSender, + ) -> Accept { + // 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 commands + if let Err(err) = + poll.register(&_reg, CMD, mio::Ready::readable(), mio::PollOpt::edge()) + { + panic!("Can not register Registration: {}", err); + } + + // Start accept + let mut sockets = Slab::new(); + for (stoken, sock) in socks { + let server = mio::net::TcpListener::from_std(sock.lst) + .expect("Can not create mio::net::TcpListener"); + + let entry = sockets.vacant_entry(); + let token = entry.key(); + + // Start listening for incoming connections + if let Err(err) = poll.register( + &server, + mio::Token(token + 1000), + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { + panic!("Can not register io: {}", err); + } + + entry.insert(ServerSocketInfo { + token: stoken, + addr: sock.addr, + sock: server, + timeout: None, + }); + } + + // Timer + let (tm, tmr) = mio::Registration::new2(); + if let Err(err) = + poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge()) + { + panic!("Can not register Registration: {}", err); + } + + Accept { + poll, + rx, + _reg, + sockets, + workers, + srv, + next: 0, + timer: (tm, tmr), + } + } + + fn poll(&mut self) { + // Create storage for events + let mut events = mio::Events::with_capacity(128); + + loop { + if let Err(err) = self.poll.poll(&mut events, None) { + panic!("Poll error: {}", err); + } + + for event in events.iter() { + let token = event.token(); + match token { + CMD => if !self.process_cmd() { + return; + }, + TIMER => self.process_timer(), + _ => self.accept(token), + } + } + } + } + + fn process_timer(&mut self) { + let now = Instant::now(); + for (token, info) in self.sockets.iter_mut() { + if let Some(inst) = info.timeout.take() { + if now > inst { + if let Err(err) = self.poll.register( + &info.sock, + mio::Token(token + 1000), + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { + error!("Can not register server socket {}", err); + } else { + info!("Resume accepting connections on {}", info.addr); + } + } else { + info.timeout = Some(inst); + } + } + } + } + + fn process_cmd(&mut self) -> bool { + loop { + match self.rx.try_recv() { + Ok(cmd) => match cmd { + Command::Pause => { + for (_, info) in self.sockets.iter_mut() { + if let Err(err) = self.poll.deregister(&info.sock) { + error!("Can not deregister server socket {}", err); + } else { + info!("Paused accepting connections on {}", info.addr); + } + } + } + Command::Resume => { + for (token, info) in self.sockets.iter() { + if let Err(err) = self.poll.register( + &info.sock, + mio::Token(token + 1000), + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { + error!("Can not resume socket accept process: {}", err); + } else { + info!( + "Accepting connections on {} has been resumed", + info.addr + ); + } + } + } + Command::Stop => { + for (_, info) in self.sockets.iter() { + let _ = self.poll.deregister(&info.sock); + } + return false; + } + Command::Worker(idx, addr) => { + self.workers.push((idx, addr)); + } + }, + Err(err) => match err { + sync_mpsc::TryRecvError::Empty => break, + sync_mpsc::TryRecvError::Disconnected => { + for (_, info) in self.sockets.iter() { + let _ = self.poll.deregister(&info.sock); + } + return false; + } + }, + } + } + true + } + + fn accept(&mut self, token: mio::Token) { + let token = usize::from(token); + if token < 1000 { + return; + } + + if let Some(info) = self.sockets.get_mut(token - 1000) { + loop { + match info.sock.accept_std() { + Ok((io, addr)) => { + let mut msg = Conn { + io, + token: info.token, + peer: Some(addr), + http2: false, + }; + while !self.workers.is_empty() { + match self.workers[self.next].1.unbounded_send(msg) { + Ok(_) => (), + Err(err) => { + let _ = self.srv.unbounded_send( + ServerCommand::WorkerDied( + self.workers[self.next].0, + ), + ); + msg = err.into_inner(); + self.workers.swap_remove(self.next); + if self.workers.is_empty() { + error!("No workers"); + thread::sleep(Duration::from_millis(100)); + break; + } else if self.workers.len() <= self.next { + self.next = 0; + } + continue; + } + } + self.next = (self.next + 1) % self.workers.len(); + break; + } + } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, + Err(ref e) if connection_error(e) => continue, + Err(e) => { + error!("Error accepting connection: {}", e); + if let Err(err) = self.poll.deregister(&info.sock) { + error!("Can not deregister server socket {}", err); + } + + // sleep after error + info.timeout = Some(Instant::now() + Duration::from_millis(500)); + + let r = self.timer.1.clone(); + System::current().arbiter().do_send(Execute::new( + move || -> Result<(), ()> { + Arbiter::spawn( + Delay::new( + Instant::now() + Duration::from_millis(510), + ).map_err(|_| ()) + .and_then(move |_| { + let _ = + r.set_readiness(mio::Ready::readable()); + Ok(()) + }), + ); + Ok(()) + }, + )); + break; + } + } + } + } + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index a4f5e87d..429e293f 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -315,10 +315,10 @@ impl IoStream for TlsStream { #[cfg(feature = "rust-tls")] use rustls::{ClientSession, ServerSession}; #[cfg(feature = "rust-tls")] -use tokio_rustls::TlsStream; +use tokio_rustls::TlsStream as RustlsStream; #[cfg(feature = "rust-tls")] -impl IoStream for TlsStream { +impl IoStream for RustlsStream { #[inline] fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { let _ = ::shutdown(self); @@ -337,7 +337,7 @@ impl IoStream for TlsStream { } #[cfg(feature = "rust-tls")] -impl IoStream for TlsStream { +impl IoStream for RustlsStream { #[inline] fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { let _ = ::shutdown(self); diff --git a/src/server/srv.rs b/src/server/srv.rs index a054d5a7..e776f742 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -46,14 +46,6 @@ fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> { Ok(()) } -#[cfg(all(feature = "rust-tls", not(feature = "alpn")))] -fn configure_alpn(builder: &mut Arc) -> io::Result<()> { - Arc::::get_mut(builder) - .unwrap() - .set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]); - Ok(()) -} - /// An HTTP Server pub struct HttpServer where @@ -68,7 +60,11 @@ where #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] workers: Vec<(usize, Addr>)>, sockets: Vec, - accept: Vec<(mio::SetReadiness, sync_mpsc::Sender)>, + accept: Option<( + mio::SetReadiness, + sync_mpsc::Sender, + Slab, + )>, exit: bool, shutdown_timeout: u16, signals: Option>, @@ -77,7 +73,7 @@ where } pub(crate) enum ServerCommand { - WorkerDied(usize, Slab), + WorkerDied(usize), } impl Actor for HttpServer @@ -114,7 +110,7 @@ where factory: Arc::new(f), workers: Vec::new(), sockets: Vec::new(), - accept: Vec::new(), + accept: None, exit: false, shutdown_timeout: 30, signals: None, @@ -280,22 +276,22 @@ where Ok(self) } - #[cfg(all(feature = "rust-tls", not(feature = "alpn")))] + #[cfg(feature = "rust-tls")] /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_ssl( - mut self, lst: net::TcpListener, mut builder: Arc, + pub fn listen_rustls( + mut self, lst: net::TcpListener, mut builder: ServerConfig, ) -> io::Result { // alpn support if !self.no_http2 { - configure_alpn(&mut builder)?; + builder.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]); } let addr = lst.local_addr().unwrap(); self.sockets.push(Socket { addr, lst, - tp: StreamHandlerType::Rustls(builder.clone()), + tp: StreamHandlerType::Rustls(Arc::new(builder)), }); Ok(self) } @@ -378,20 +374,21 @@ where Ok(self) } - #[cfg(all(feature = "rust-tls", not(feature = "alpn")))] + #[cfg(feature = "rust-tls")] /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_ssl( - mut self, addr: S, mut builder: Arc, + pub fn bind_rustls( + mut self, addr: S, mut builder: ServerConfig, ) -> io::Result { // alpn support if !self.no_http2 { - configure_alpn(&mut builder)?; + builder.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]); } + let builder = Arc::new(builder); let sockets = self.bind2(addr)?; - self.sockets.extend(sockets.into_iter().map(|mut s| { + self.sockets.extend(sockets.into_iter().map(move |mut s| { s.tp = StreamHandlerType::Rustls(builder.clone()); s })); @@ -487,17 +484,12 @@ impl HttpServer { let settings = ServerSettings::new(Some(addrs[0].1.addr), &self.host, false); let workers = self.start_workers(&settings, &socks); - // start acceptors threads - for (token, sock) in addrs { + // start accept thread + for (_, sock) in &addrs { info!("Starting server on http://{}", sock.addr); - self.accept.push(start_accept_thread( - token, - sock, - tx.clone(), - socks.clone(), - workers.clone(), - )); } + let (r, cmd) = start_accept_thread(addrs, tx.clone(), workers.clone()); + self.accept = Some((r, cmd, socks)); // start http server actor let signals = self.subscribe_to_signals(); @@ -672,7 +664,7 @@ impl StreamHandler for HttpServer { fn handle(&mut self, msg: ServerCommand, _: &mut Context) { match msg { - ServerCommand::WorkerDied(idx, socks) => { + ServerCommand::WorkerDied(idx) => { let mut found = false; for i in 0..self.workers.len() { if self.workers[i].0 == idx { @@ -700,6 +692,7 @@ impl StreamHandler for HttpServer { let ka = self.keep_alive; let factory = Arc::clone(&self.factory); let host = self.host.clone(); + let socks = self.accept.as_ref().unwrap().2.clone(); let addr = socks[0].addr; let addr = Arbiter::start(move |ctx: &mut Context<_>| { @@ -709,7 +702,7 @@ impl StreamHandler for HttpServer { ctx.add_message_stream(rx); Worker::new(apps, socks, ka, settings) }); - for item in &self.accept { + if let Some(ref item) = &self.accept { let _ = item.1.send(Command::Worker(new_idx, tx.clone())); let _ = item.0.set_readiness(mio::Ready::readable()); } diff --git a/src/test.rs b/src/test.rs index f94732dd..5c520a75 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,10 +15,10 @@ use tokio::runtime::current_thread::Runtime; #[cfg(feature = "alpn")] use openssl::ssl::SslAcceptorBuilder; -#[cfg(feature = "rust-tls")] +#[cfg(all(feature = "rust-tls"))] use rustls::ServerConfig; -#[cfg(feature = "rust-tls")] -use std::sync::Arc; +//#[cfg(all(feature = "rust-tls"))] +//use std::sync::Arc; use application::{App, HttpApplication}; use body::Binary; @@ -144,7 +144,7 @@ impl TestServer { builder.set_verify(SslVerifyMode::NONE); ClientConnector::with_connector(builder.build()).start() } - #[cfg(feature = "rust-tls")] + #[cfg(all(feature = "rust-tls", not(feature = "alpn")))] { use rustls::ClientConfig; use std::fs::File; @@ -256,7 +256,7 @@ pub struct TestServerBuilder { #[cfg(feature = "alpn")] ssl: Option, #[cfg(feature = "rust-tls")] - ssl: Option>, + rust_ssl: Option, } impl TestServerBuilder { @@ -267,8 +267,10 @@ impl TestServerBuilder { { TestServerBuilder { state: Box::new(state), - #[cfg(any(feature = "alpn", feature = "rust-tls"))] + #[cfg(feature = "alpn")] ssl: None, + #[cfg(feature = "rust-tls")] + rust_ssl: None, } } @@ -280,9 +282,9 @@ impl TestServerBuilder { } #[cfg(feature = "rust-tls")] - /// Create ssl server - pub fn ssl(mut self, ssl: Arc) -> Self { - self.ssl = Some(ssl); + /// Create rust tls server + pub fn rustls(mut self, ssl: ServerConfig) -> Self { + self.rust_ssl = Some(ssl); self } @@ -294,41 +296,56 @@ impl TestServerBuilder { { let (tx, rx) = mpsc::channel(); - #[cfg(any(feature = "alpn", feature = "rust-tls"))] - let ssl = self.ssl.is_some(); - #[cfg(not(any(feature = "alpn", feature = "rust-tls")))] - let ssl = false; + let mut has_ssl = false; + + #[cfg(feature = "alpn")] + { + has_ssl = has_ssl || self.ssl.is_some(); + } + + #[cfg(feature = "rust-tls")] + { + has_ssl = has_ssl || self.rust_ssl.is_some(); + } // run server in separate thread thread::spawn(move || { - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); + let addr = TestServer::unused_addr(); let sys = System::new("actix-test-server"); let state = self.state; - let srv = HttpServer::new(move || { + let mut srv = HttpServer::new(move || { let mut app = TestApp::new(state()); config(&mut app); vec![app] }).workers(1) .disable_signals(); - tx.send((System::current(), local_addr, TestServer::get_conn())) + tx.send((System::current(), addr, TestServer::get_conn())) .unwrap(); - #[cfg(any(feature = "alpn", feature = "rust-tls"))] + #[cfg(feature = "alpn")] { let ssl = self.ssl.take(); if let Some(ssl) = ssl { - srv.listen_ssl(tcp, ssl).unwrap().start(); - } else { - srv.listen(tcp).start(); + let tcp = net::TcpListener::bind(addr).unwrap(); + srv = srv.listen_ssl(tcp, ssl).unwrap(); } } - #[cfg(not(any(feature = "alpn", feature = "rust-tls")))] + #[cfg(feature = "rust-tls")] { - srv.listen(tcp).start(); + let ssl = self.rust_ssl.take(); + if let Some(ssl) = ssl { + let tcp = net::TcpListener::bind(addr).unwrap(); + srv = srv.listen_rustls(tcp, ssl).unwrap(); + } } + if !has_ssl { + let tcp = net::TcpListener::bind(addr).unwrap(); + srv = srv.listen(tcp); + } + srv.start(); + sys.run(); }); @@ -336,8 +353,8 @@ impl TestServerBuilder { System::set_current(system); TestServer { addr, - ssl, conn, + ssl: has_ssl, rt: Runtime::new().unwrap(), } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 82a318e5..3a825928 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -153,6 +153,62 @@ fn test_shutdown() { let _ = sys.stop(); } +#[test] +#[cfg(unix)] +fn test_panic() { + let _ = test::TestServer::unused_addr(); + let (tx, rx) = mpsc::channel(); + + thread::spawn(|| { + System::run(move || { + let srv = server::new(|| { + App::new() + .resource("/panic", |r| { + r.method(http::Method::GET).f(|_| -> &'static str { + panic!("error"); + }); + }) + .resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + }) + }).workers(1); + + let srv = srv.bind("127.0.0.1:0").unwrap(); + let addr = srv.addrs()[0]; + srv.start(); + let _ = tx.send((addr, System::current())); + }); + }); + let (addr, sys) = rx.recv().unwrap(); + System::set_current(sys.clone()); + + let mut rt = Runtime::new().unwrap(); + { + let req = client::ClientRequest::get(format!("http://{}/panic", addr).as_str()) + .finish() + .unwrap(); + let response = rt.block_on(req.send()); + assert!(response.is_err()); + } + + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = rt.block_on(req.send()); + assert!(response.is_err()); + } + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = rt.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + } + + let _ = sys.stop(); +} + #[test] fn test_simple() { let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok())); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 1ed80bf7..94f38978 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -277,13 +277,12 @@ fn test_ws_server_ssl() { #[test] #[cfg(feature = "rust-tls")] -fn test_ws_server_ssl() { +fn test_ws_server_rust_tls() { extern crate rustls; - use rustls::{ServerConfig, NoClientAuth}; use rustls::internal::pemfile::{certs, rsa_private_keys}; - use std::io::BufReader; - use std::sync::Arc; + use rustls::{NoClientAuth, ServerConfig}; use std::fs::File; + use std::io::BufReader; // load ssl keys let mut config = ServerConfig::new(NoClientAuth::new()); @@ -293,7 +292,7 @@ fn test_ws_server_ssl() { let mut keys = rsa_private_keys(key_file).unwrap(); config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - let mut srv = test::TestServer::build().ssl(Arc::new(config)).start(|app| { + let mut srv = test::TestServer::build().rustls(config).start(|app| { app.handler(|req| { ws::start( req, From dca4c110dd0634bd864e624b475f8e7f6e3a5b36 Mon Sep 17 00:00:00 2001 From: jrconlin Date: Tue, 31 Jul 2018 16:21:18 -0700 Subject: [PATCH 0556/1635] feature: allow TestServer to open a websocket on any URL * added `TestServer::ws_at(uri_str)` * modified `TestServer::ws()` to call `self.ws_at("/")` to preserve behavior Closes #432 --- CHANGES.md | 4 +++- src/test.rs | 14 +++++++++++--- tests/test_ws.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 237b4bfb..9cb883a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Support HTTP/2 with rustls #36 +* Allow TestServer to open a websocket on any URL # 433 + ### Fixed * Do not override HOST header for client request #428 @@ -22,7 +24,7 @@ * Add implementation of `FromRequest` for `Option` and `Result` * Allow to handle application prefix, i.e. allow to handle `/app` path - for application with `/app` prefix. + for application with `/app` prefix. Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix) api doc. diff --git a/src/test.rs b/src/test.rs index f94732dd..2ec7a98d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -207,15 +207,23 @@ impl TestServer { self.rt.block_on(fut) } - /// Connect to websocket server - pub fn ws( + /// Connect to websocket server at a given path + pub fn ws_at( &mut self, + path: &str, ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - let url = self.url("/"); + let url = self.url(path); self.rt .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) } + /// Connect to a websocket server + pub fn ws( + &mut self, + ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { + self.ws_at("/") + } + /// Create `GET` request pub fn get(&self) -> ClientRequestBuilder { ClientRequest::get(self.url("/").as_str()) diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 1ed80bf7..86717272 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -64,6 +64,46 @@ fn test_simple() { ); } +// websocket resource helper function +fn start_ws_resource(req: &HttpRequest) -> Result { + ws::start(req, Ws) +} + +#[test] +fn test_simple_path() { + const PATH:&str = "/v1/ws/"; + + // Create a websocket at a specific path. + let mut srv = test::TestServer::new(|app| { + app.resource(PATH, |r| r.route().f(start_ws_resource)); + }); + // fetch the sockets for the resource at a given path. + let (reader, mut writer) = srv.ws_at(PATH).unwrap(); + + writer.text("text"); + let (item, reader) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); + + writer.binary(b"text".as_ref()); + let (item, reader) = srv.execute(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(ws::Message::Binary(Bytes::from_static(b"text").into())) + ); + + writer.ping("ping"); + let (item, reader) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); + + writer.close(Some(ws::CloseCode::Normal.into())); + let (item, _) = srv.execute(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + ); +} + + #[test] fn test_empty_close_code() { let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); From 972b008a6e15defd9d7d8dfb9073091b341b716a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 1 Aug 2018 09:42:12 -0700 Subject: [PATCH 0557/1635] remove unsafe error transmute, upgrade failure to 0.1.2 #434 --- Cargo.toml | 2 +- src/error.rs | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 695b2e31..31440eb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ cookie = { version="0.11", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } flate2 = { version="^1.0.2", optional = true, default-features = false } -failure = "=0.1.1" +failure = "^0.1.2" # io mio = "^0.6.13" diff --git a/src/error.rs b/src/error.rs index 461b23e2..76c8e79e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -52,7 +52,8 @@ pub struct Error { impl Error { /// Deprecated way to reference the underlying response error. #[deprecated( - since = "0.6.0", note = "please use `Error::as_response_error()` instead" + since = "0.6.0", + note = "please use `Error::as_response_error()` instead" )] pub fn cause(&self) -> &ResponseError { self.cause.as_ref() @@ -97,21 +98,9 @@ impl Error { // // So we first downcast into that compat, to then further downcast through // the failure's Error downcasting system into the original failure. - // - // This currently requires a transmute. This could be avoided if failure - // provides a deref: https://github.com/rust-lang-nursery/failure/pull/213 let compat: Option<&failure::Compat> = Fail::downcast_ref(self.cause.as_fail()); - if let Some(compat) = compat { - pub struct CompatWrappedError { - error: failure::Error, - } - let compat: &CompatWrappedError = - unsafe { &*(compat as *const _ as *const CompatWrappedError) }; - compat.error.downcast_ref() - } else { - None - } + compat.and_then(|e| e.get_ref().downcast_ref()) } } From a5f80a25ffa057fd2ec78c0cd32d4dc39af1c417 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 1 Aug 2018 10:51:47 -0700 Subject: [PATCH 0558/1635] update changes --- CHANGES.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9cb883a3..d86de70f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,15 +1,18 @@ # Changes -## [0.7.3] - 2018-07-xx +## [0.7.3] - 2018-08-01 ### Added * Support HTTP/2 with rustls #36 -* Allow TestServer to open a websocket on any URL # 433 +* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433 + ### Fixed +* Fixed failure 0.1.2 compatibility + * Do not override HOST header for client request #428 * Gz streaming, use `flate2::write::GzDecoder` #228 From 0da3fdcb09973954bb155ee8b3d8c265d37d5de4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 1 Aug 2018 10:59:00 -0700 Subject: [PATCH 0559/1635] do not use Arc for rustls config --- src/client/connector.rs | 88 +++++++++++++++++++++++++++++++++-------- src/test.rs | 9 ++--- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index a0054671..ef66cd73 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -26,21 +26,51 @@ use native_tls::{Error as TlsError, TlsConnector, TlsStream}; #[cfg(all(feature = "tls", not(feature = "alpn")))] use tokio_tls::TlsConnectorExt; -#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +#[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) +)] use rustls::ClientConfig; -#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +#[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) +)] use std::io::Error as TLSError; -#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +#[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) +)] use std::sync::Arc; -#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +#[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) +)] use tokio_rustls::ClientConfigExt; -#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +#[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) +)] use webpki::DNSNameRef; -#[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] +#[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) +)] use webpki_roots; use server::IoStream; -use {HAS_OPENSSL, HAS_TLS, HAS_RUSTLS}; +use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS}; /// Client connector usage stats #[derive(Default, Message)] @@ -153,7 +183,12 @@ pub enum ClientConnectorError { SslError(#[cause] TlsError), /// SSL error - #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + #[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) + )] #[fail(display = "{}", _0)] SslError(#[cause] TLSError), @@ -211,7 +246,12 @@ pub struct ClientConnector { connector: SslConnector, #[cfg(all(feature = "tls", not(feature = "alpn")))] connector: TlsConnector, - #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + #[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) + )] connector: Arc, stats: ClientConnectorStats, @@ -282,13 +322,18 @@ impl Default for ClientConnector { paused: Paused::No, } } - #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + #[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) + )] { let mut config = ClientConfig::new(); config .root_store .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - ClientConnector::with_connector(Arc::new(config)) + ClientConnector::with_connector(config) } #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] @@ -380,7 +425,12 @@ impl ClientConnector { } } - #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + #[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) + )] /// Create `ClientConnector` actor with custom `SslConnector` instance. /// /// By default `ClientConnector` uses very a simple SSL configuration. @@ -425,11 +475,11 @@ impl ClientConnector { /// }); /// } /// ``` - pub fn with_connector(connector: Arc) -> ClientConnector { + pub fn with_connector(connector: ClientConfig) -> ClientConnector { let (tx, rx) = mpsc::unbounded(); ClientConnector { - connector, + connector: Arc::new(connector), stats: ClientConnectorStats::default(), subscriber: None, acq_tx: tx, @@ -806,7 +856,12 @@ impl ClientConnector { } } - #[cfg(all(feature = "rust-tls", not(any(feature = "alpn", feature = "tls"))))] + #[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) + )] match res { Err(err) => { let _ = waiter.tx.send(Err(err.into())); @@ -815,7 +870,8 @@ impl ClientConnector { Ok(stream) => { act.stats.opened += 1; if conn.0.ssl { - let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap(); + let host = + DNSNameRef::try_from_ascii_str(&key.host).unwrap(); fut::Either::A( act.connector .connect_async(host, stream) diff --git a/src/test.rs b/src/test.rs index 4e23e64a..244c079a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -17,8 +17,6 @@ use tokio::runtime::current_thread::Runtime; use openssl::ssl::SslAcceptorBuilder; #[cfg(all(feature = "rust-tls"))] use rustls::ServerConfig; -//#[cfg(all(feature = "rust-tls"))] -//use std::sync::Arc; use application::{App, HttpApplication}; use body::Binary; @@ -152,7 +150,7 @@ impl TestServer { let mut config = ClientConfig::new(); let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); config.root_store.add_pem_file(pem_file).unwrap(); - ClientConnector::with_connector(Arc::new(config)).start() + ClientConnector::with_connector(config).start() } #[cfg(not(any(feature = "alpn", feature = "rust-tls")))] { @@ -209,8 +207,7 @@ impl TestServer { /// Connect to websocket server at a given path pub fn ws_at( - &mut self, - path: &str, + &mut self, path: &str, ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { let url = self.url(path); self.rt @@ -223,7 +220,7 @@ impl TestServer { ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { self.ws_at("/") } - + /// Create `GET` request pub fn get(&self) -> ClientRequestBuilder { ClientRequest::get(self.url("/").as_str()) From e9c1889df46394c4c6e8fdda2e956a1077b628cf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 1 Aug 2018 16:41:24 -0700 Subject: [PATCH 0560/1635] test timing --- tests/test_server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_server.rs b/tests/test_server.rs index 3a825928..842d685f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -887,6 +887,7 @@ fn test_brotli_encoding_large() { fn test_h2() { let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); let addr = srv.addr(); + thread::sleep(time::Duration::from_millis(500)); let mut core = Runtime::new().unwrap(); let tcp = TcpStream::connect(&addr); From 8c89c90c50f64bb411db1a95aeec6b2a1cc9d9e1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 2 Aug 2018 23:17:10 -0700 Subject: [PATCH 0561/1635] add accept backpressure #250 --- CHANGES.md | 7 + Cargo.toml | 2 +- src/server/accept.rs | 358 +++++++++++++++++++++++++++++++---------- src/server/h1.rs | 110 ++++--------- src/server/settings.rs | 53 +++--- src/server/srv.rs | 150 +++++++---------- src/server/worker.rs | 132 +++++++++++++-- 7 files changed, 516 insertions(+), 296 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d86de70f..f7e663d6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.7.4] - 2018-08-xx + +### Added + +* Added `HttpServer::max_connections()` and `HttpServer::max_sslrate()`, accept backpressure #250 + + ## [0.7.3] - 2018-08-01 ### Added diff --git a/Cargo.toml b/Cargo.toml index 31440eb3..86cb53d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.3" +version = "0.7.4" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/server/accept.rs b/src/server/accept.rs index 75280560..f846e4a4 100644 --- a/src/server/accept.rs +++ b/src/server/accept.rs @@ -10,13 +10,13 @@ use tokio_timer::Delay; use actix::{msgs::Execute, Arbiter, System}; use super::srv::{ServerCommand, Socket}; -use super::worker::Conn; +use super::worker::{Conn, WorkerClient}; pub(crate) enum Command { Pause, Resume, Stop, - Worker(usize, mpsc::UnboundedSender>), + Worker(WorkerClient), } struct ServerSocketInfo { @@ -26,40 +26,133 @@ struct ServerSocketInfo { timeout: Option, } +#[derive(Clone)] +pub(crate) struct AcceptNotify { + ready: mio::SetReadiness, + maxconn: usize, + maxconn_low: usize, + maxsslrate: usize, + maxsslrate_low: usize, +} + +impl AcceptNotify { + pub fn new(ready: mio::SetReadiness, maxconn: usize, maxsslrate: usize) -> Self { + let maxconn_low = if maxconn > 10 { maxconn - 10 } else { 0 }; + let maxsslrate_low = if maxsslrate > 10 { maxsslrate - 10 } else { 0 }; + AcceptNotify { + ready, + maxconn, + maxconn_low, + maxsslrate, + maxsslrate_low, + } + } + + pub fn notify_maxconn(&self, maxconn: usize) { + if maxconn > self.maxconn_low && maxconn <= self.maxconn { + let _ = self.ready.set_readiness(mio::Ready::readable()); + } + } + pub fn notify_maxsslrate(&self, sslrate: usize) { + if sslrate > self.maxsslrate_low && sslrate <= self.maxsslrate { + let _ = self.ready.set_readiness(mio::Ready::readable()); + } + } +} + +impl Default for AcceptNotify { + fn default() -> Self { + AcceptNotify::new(mio::Registration::new2().1, 0, 0) + } +} + +pub(crate) struct AcceptLoop { + cmd_reg: Option, + cmd_ready: mio::SetReadiness, + notify_reg: Option, + notify_ready: mio::SetReadiness, + tx: sync_mpsc::Sender, + rx: Option>, + srv: Option<( + mpsc::UnboundedSender, + mpsc::UnboundedReceiver, + )>, + maxconn: usize, + maxsslrate: usize, +} + +impl AcceptLoop { + pub fn new() -> AcceptLoop { + let (tx, rx) = sync_mpsc::channel(); + let (cmd_reg, cmd_ready) = mio::Registration::new2(); + let (notify_reg, notify_ready) = mio::Registration::new2(); + + AcceptLoop { + tx, + cmd_ready, + cmd_reg: Some(cmd_reg), + notify_ready, + notify_reg: Some(notify_reg), + maxconn: 102_400, + maxsslrate: 256, + rx: Some(rx), + srv: Some(mpsc::unbounded()), + } + } + + pub fn send(&self, msg: Command) { + let _ = self.tx.send(msg); + let _ = self.cmd_ready.set_readiness(mio::Ready::readable()); + } + + pub fn get_notify(&self) -> AcceptNotify { + AcceptNotify::new(self.notify_ready.clone(), self.maxconn, self.maxsslrate) + } + + pub fn max_connections(&mut self, num: usize) { + self.maxconn = num; + } + + pub fn max_sslrate(&mut self, num: usize) { + self.maxsslrate = num; + } + + pub(crate) fn start( + &mut self, socks: Vec<(usize, Socket)>, workers: Vec, + ) -> mpsc::UnboundedReceiver { + let (tx, rx) = self.srv.take().expect("Can not re-use AcceptInfo"); + + Accept::start( + self.rx.take().expect("Can not re-use AcceptInfo"), + self.cmd_reg.take().expect("Can not re-use AcceptInfo"), + self.notify_reg.take().expect("Can not re-use AcceptInfo"), + self.maxconn, + self.maxsslrate, + socks, + tx, + workers, + ); + rx + } +} + struct Accept { poll: mio::Poll, rx: sync_mpsc::Receiver, sockets: Slab, - workers: Vec<(usize, mpsc::UnboundedSender>)>, - _reg: mio::Registration, - next: usize, + workers: Vec, srv: mpsc::UnboundedSender, timer: (mio::Registration, mio::SetReadiness), + next: usize, + maxconn: usize, + maxsslrate: usize, + backpressure: bool, } +const DELTA: usize = 100; const CMD: mio::Token = mio::Token(0); const TIMER: mio::Token = mio::Token(1); - -pub(crate) fn start_accept_thread( - socks: Vec<(usize, Socket)>, srv: mpsc::UnboundedSender, - workers: Vec<(usize, mpsc::UnboundedSender>)>, -) -> (mio::SetReadiness, sync_mpsc::Sender) { - let (tx, rx) = sync_mpsc::channel(); - let (reg, readiness) = mio::Registration::new2(); - - let sys = System::current(); - - // start accept thread - #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - let _ = thread::Builder::new() - .name("actix-web accept loop".to_owned()) - .spawn(move || { - System::set_current(sys); - Accept::new(reg, rx, socks, workers, srv).poll(); - }); - - (readiness, tx) -} +const NOTIFY: mio::Token = mio::Token(2); /// This function defines errors that are per-connection. Which basically /// means that if we get this error from `accept()` system call it means @@ -75,11 +168,51 @@ fn connection_error(e: &io::Error) -> bool { } impl Accept { + #![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] + pub(crate) fn start( + rx: sync_mpsc::Receiver, cmd_reg: mio::Registration, + notify_reg: mio::Registration, maxconn: usize, maxsslrate: usize, + socks: Vec<(usize, Socket)>, srv: mpsc::UnboundedSender, + workers: Vec, + ) { + let sys = System::current(); + + // start accept thread + let _ = thread::Builder::new() + .name("actix-web accept loop".to_owned()) + .spawn(move || { + System::set_current(sys); + let mut accept = Accept::new(rx, socks, workers, srv); + accept.maxconn = maxconn; + accept.maxsslrate = maxsslrate; + + // Start listening for incoming commands + if let Err(err) = accept.poll.register( + &cmd_reg, + CMD, + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { + panic!("Can not register Registration: {}", err); + } + + // Start listening for notify updates + if let Err(err) = accept.poll.register( + ¬ify_reg, + NOTIFY, + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { + panic!("Can not register Registration: {}", err); + } + + accept.poll(); + }); + } + fn new( - _reg: mio::Registration, rx: sync_mpsc::Receiver, - socks: Vec<(usize, Socket)>, - workers: Vec<(usize, mpsc::UnboundedSender>)>, - srv: mpsc::UnboundedSender, + rx: sync_mpsc::Receiver, socks: Vec<(usize, Socket)>, + workers: Vec, srv: mpsc::UnboundedSender, ) -> Accept { // Create a poll instance let poll = match mio::Poll::new() { @@ -87,13 +220,6 @@ impl Accept { Err(err) => panic!("Can not create mio::Poll: {}", err), }; - // Start listening for incoming commands - if let Err(err) = - poll.register(&_reg, CMD, mio::Ready::readable(), mio::PollOpt::edge()) - { - panic!("Can not register Registration: {}", err); - } - // Start accept let mut sockets = Slab::new(); for (stoken, sock) in socks { @@ -106,7 +232,7 @@ impl Accept { // Start listening for incoming connections if let Err(err) = poll.register( &server, - mio::Token(token + 1000), + mio::Token(token + DELTA), mio::Ready::readable(), mio::PollOpt::edge(), ) { @@ -132,12 +258,14 @@ impl Accept { Accept { poll, rx, - _reg, sockets, workers, srv, next: 0, timer: (tm, tmr), + maxconn: 102_400, + maxsslrate: 256, + backpressure: false, } } @@ -157,7 +285,14 @@ impl Accept { return; }, TIMER => self.process_timer(), - _ => self.accept(token), + NOTIFY => self.backpressure(false), + _ => { + let token = usize::from(token); + if token < DELTA { + continue; + } + self.accept(token - DELTA); + } } } } @@ -170,7 +305,7 @@ impl Accept { if now > inst { if let Err(err) = self.poll.register( &info.sock, - mio::Token(token + 1000), + mio::Token(token + DELTA), mio::Ready::readable(), mio::PollOpt::edge(), ) { @@ -202,7 +337,7 @@ impl Accept { for (token, info) in self.sockets.iter() { if let Err(err) = self.poll.register( &info.sock, - mio::Token(token + 1000), + mio::Token(token + DELTA), mio::Ready::readable(), mio::PollOpt::edge(), ) { @@ -221,8 +356,9 @@ impl Accept { } return false; } - Command::Worker(idx, addr) => { - self.workers.push((idx, addr)); + Command::Worker(worker) => { + self.backpressure(false); + self.workers.push(worker); } }, Err(err) => match err { @@ -239,48 +375,100 @@ impl Accept { true } - fn accept(&mut self, token: mio::Token) { - let token = usize::from(token); - if token < 1000 { - return; + fn backpressure(&mut self, on: bool) { + if self.backpressure { + if !on { + self.backpressure = false; + for (token, info) in self.sockets.iter() { + if let Err(err) = self.poll.register( + &info.sock, + mio::Token(token + DELTA), + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { + error!("Can not resume socket accept process: {}", err); + } else { + info!("Accepting connections on {} has been resumed", info.addr); + } + } + } + } else if on { + self.backpressure = true; + for (_, info) in self.sockets.iter() { + let _ = self.poll.deregister(&info.sock); + } } + } - if let Some(info) = self.sockets.get_mut(token - 1000) { - loop { - match info.sock.accept_std() { - Ok((io, addr)) => { - let mut msg = Conn { - io, - token: info.token, - peer: Some(addr), - http2: false, - }; - while !self.workers.is_empty() { - match self.workers[self.next].1.unbounded_send(msg) { - Ok(_) => (), - Err(err) => { - let _ = self.srv.unbounded_send( - ServerCommand::WorkerDied( - self.workers[self.next].0, - ), - ); - msg = err.into_inner(); - self.workers.swap_remove(self.next); - if self.workers.is_empty() { - error!("No workers"); - thread::sleep(Duration::from_millis(100)); - break; - } else if self.workers.len() <= self.next { - self.next = 0; - } - continue; - } - } + fn accept_one(&mut self, mut msg: Conn) { + if self.backpressure { + while !self.workers.is_empty() { + match self.workers[self.next].send(msg) { + Ok(_) => (), + Err(err) => { + let _ = self.srv.unbounded_send(ServerCommand::WorkerDied( + self.workers[self.next].idx, + )); + msg = err.into_inner(); + self.workers.swap_remove(self.next); + if self.workers.is_empty() { + error!("No workers"); + return; + } else if self.workers.len() <= self.next { + self.next = 0; + } + continue; + } + } + self.next = (self.next + 1) % self.workers.len(); + break; + } + } else { + let mut idx = 0; + while idx < self.workers.len() { + idx += 1; + if self.workers[self.next].available(self.maxconn, self.maxsslrate) { + match self.workers[self.next].send(msg) { + Ok(_) => { self.next = (self.next + 1) % self.workers.len(); - break; + return; + } + Err(err) => { + let _ = self.srv.unbounded_send(ServerCommand::WorkerDied( + self.workers[self.next].idx, + )); + msg = err.into_inner(); + self.workers.swap_remove(self.next); + if self.workers.is_empty() { + error!("No workers"); + self.backpressure(true); + return; + } else if self.workers.len() <= self.next { + self.next = 0; + } + continue; } } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break, + } + self.next = (self.next + 1) % self.workers.len(); + } + // enable backpressure + self.backpressure(true); + self.accept_one(msg); + } + } + + fn accept(&mut self, token: usize) { + loop { + let msg = if let Some(info) = self.sockets.get_mut(token) { + match info.sock.accept_std() { + Ok((io, addr)) => Conn { + io, + token: info.token, + peer: Some(addr), + http2: false, + }, + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return, Err(ref e) if connection_error(e) => continue, Err(e) => { error!("Error accepting connection: {}", e); @@ -307,10 +495,14 @@ impl Accept { Ok(()) }, )); - break; + return; } } - } + } else { + return; + }; + + self.accept_one(msg); } } } diff --git a/src/server/h1.rs b/src/server/h1.rs index 511b32bc..9f3bda28 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -464,6 +464,7 @@ where #[cfg(test)] mod tests { use std::net::Shutdown; + use std::sync::{atomic::AtomicUsize, Arc}; use std::{cmp, io, time}; use bytes::{Buf, Bytes, BytesMut}; @@ -473,10 +474,22 @@ mod tests { use super::*; use application::HttpApplication; use httpmessage::HttpMessage; + use server::accept::AcceptNotify; use server::h1decoder::Message; use server::settings::{ServerSettings, WorkerSettings}; use server::{KeepAlive, Request}; + fn wrk_settings() -> WorkerSettings { + WorkerSettings::::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + AcceptNotify::default(), + Arc::new(AtomicUsize::new(0)), + Arc::new(AtomicUsize::new(0)), + ) + } + impl Message { fn message(self) -> Request { match self { @@ -506,8 +519,7 @@ mod tests { macro_rules! parse_ready { ($e:expr) => {{ - let settings: WorkerSettings = - WorkerSettings::new(Vec::new(), KeepAlive::Os, ServerSettings::default()); + let settings = wrk_settings(); match H1Decoder::new().decode($e, &settings) { Ok(Some(msg)) => msg.message(), Ok(_) => unreachable!("Eof during parsing http request"), @@ -518,8 +530,7 @@ mod tests { macro_rules! expect_parse_err { ($e:expr) => {{ - let settings: WorkerSettings = - WorkerSettings::new(Vec::new(), KeepAlive::Os, ServerSettings::default()); + let settings = wrk_settings(); match H1Decoder::new().decode($e, &settings) { Err(err) => match err { @@ -595,11 +606,7 @@ mod tests { fn test_req_parse() { let buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n"); let readbuf = BytesMut::new(); - let settings = Rc::new(WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - )); + let settings = Rc::new(wrk_settings()); let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); h1.poll_io(); @@ -611,11 +618,7 @@ mod tests { fn test_req_parse_err() { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); let readbuf = BytesMut::new(); - let settings = Rc::new(WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - )); + let settings = Rc::new(wrk_settings()); let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); h1.poll_io(); @@ -626,11 +629,7 @@ mod tests { #[test] fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { @@ -647,11 +646,7 @@ mod tests { #[test] fn test_parse_partial() { let mut buf = BytesMut::from("PUT /test HTTP/1"); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { @@ -674,11 +669,7 @@ mod tests { #[test] fn test_parse_post() { let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { @@ -696,11 +687,7 @@ mod tests { fn test_parse_body() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { @@ -727,11 +714,7 @@ mod tests { fn test_parse_body_crlf() { let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); match reader.decode(&mut buf, &settings) { @@ -757,11 +740,7 @@ mod tests { #[test] fn test_parse_partial_eof() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); @@ -780,11 +759,7 @@ mod tests { #[test] fn test_headers_split_field() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } @@ -815,11 +790,7 @@ mod tests { Set-Cookie: c1=cookie1\r\n\ Set-Cookie: c2=cookie2\r\n\r\n", ); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); let req = msg.message(); @@ -1015,11 +986,7 @@ mod tests { #[test] fn test_http_request_upgrade() { - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ connection: upgrade\r\n\ @@ -1085,12 +1052,7 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); - + let settings = wrk_settings(); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.is_payload()); @@ -1125,11 +1087,7 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); assert!(msg.is_payload()); @@ -1163,11 +1121,7 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); @@ -1214,11 +1168,7 @@ mod tests { &"GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n"[..], ); - let settings = WorkerSettings::::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = wrk_settings(); let mut reader = H1Decoder::new(); let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); diff --git a/src/server/settings.rs b/src/server/settings.rs index cc2e1c06..8e30646d 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -1,7 +1,8 @@ -use std::cell::{Cell, RefCell, RefMut, UnsafeCell}; +use std::cell::{RefCell, RefMut, UnsafeCell}; use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; +use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc}; use std::{env, fmt, net}; use bytes::BytesMut; @@ -11,6 +12,7 @@ use lazycell::LazyCell; use parking_lot::Mutex; use time; +use super::accept::AcceptNotify; use super::channel::Node; use super::message::{Request, RequestPool}; use super::KeepAlive; @@ -93,21 +95,6 @@ impl ServerSettings { } } - pub(crate) fn parts(&self) -> (Option, String, bool) { - (self.addr, self.host.clone(), self.secure) - } - - pub(crate) fn from_parts(parts: (Option, String, bool)) -> Self { - let (addr, host, secure) = parts; - ServerSettings { - addr, - host, - secure, - cpu_pool: LazyCell::new(), - responses: HttpResponsePool::get_pool(), - } - } - /// Returns the socket address of the local half of this TCP connection pub fn local_addr(&self) -> Option { self.addr @@ -150,14 +137,17 @@ pub(crate) struct WorkerSettings { ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, - channels: Cell, + channels: Arc, node: RefCell>, date: UnsafeCell, + sslrate: Arc, + notify: AcceptNotify, } impl WorkerSettings { pub(crate) fn new( h: Vec, keep_alive: KeepAlive, settings: ServerSettings, + notify: AcceptNotify, channels: Arc, sslrate: Arc, ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -169,16 +159,18 @@ impl WorkerSettings { h: RefCell::new(h), bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), - channels: Cell::new(0), node: RefCell::new(Node::head()), date: UnsafeCell::new(Date::new()), keep_alive, ka_enabled, + channels, + sslrate, + notify, } } pub fn num_channels(&self) -> usize { - self.channels.get() + self.channels.load(Ordering::Relaxed) } pub fn head(&self) -> RefMut> { @@ -210,16 +202,12 @@ impl WorkerSettings { } pub fn add_channel(&self) { - self.channels.set(self.channels.get() + 1); + self.channels.fetch_add(1, Ordering::Relaxed); } 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"); - } + let val = self.channels.fetch_sub(1, Ordering::Relaxed); + self.notify.notify_maxconn(val); } pub fn update_date(&self) { @@ -240,6 +228,16 @@ impl WorkerSettings { dst.extend_from_slice(date_bytes); } } + + #[allow(dead_code)] + pub(crate) fn ssl_conn_add(&self) { + self.sslrate.fetch_add(1, Ordering::Relaxed); + } + #[allow(dead_code)] + pub(crate) fn ssl_conn_del(&self) { + let val = self.sslrate.fetch_sub(1, Ordering::Relaxed); + self.notify.notify_maxsslrate(val); + } } struct Date { @@ -311,6 +309,9 @@ mod tests { Vec::new(), KeepAlive::Os, ServerSettings::default(), + AcceptNotify::default(), + Arc::new(AtomicUsize::new(0)), + Arc::new(AtomicUsize::new(0)), ); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf1, true); diff --git a/src/server/srv.rs b/src/server/srv.rs index e776f742..b6bd2196 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -1,5 +1,5 @@ use std::rc::Rc; -use std::sync::{mpsc as sync_mpsc, Arc}; +use std::sync::{atomic::AtomicUsize, Arc}; use std::time::Duration; use std::{io, net}; @@ -10,10 +10,8 @@ use actix::{ use futures::sync::mpsc; use futures::{Future, Sink, Stream}; -use mio; use net2::TcpBuilder; use num_cpus; -use slab::Slab; use tokio_io::{AsyncRead, AsyncWrite}; #[cfg(feature = "tls")] @@ -25,10 +23,12 @@ use openssl::ssl::{AlpnError, SslAcceptorBuilder}; #[cfg(feature = "rust-tls")] use rustls::ServerConfig; -use super::accept::{start_accept_thread, Command}; +use super::accept::{AcceptLoop, AcceptNotify, Command}; use super::channel::{HttpChannel, WrapperStream}; use super::settings::{ServerSettings, WorkerSettings}; -use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker}; +use super::worker::{ + Conn, StopWorker, StreamHandlerType, Worker, WorkerClient, WorkersPool, +}; use super::{IntoHttpHandler, IoStream, KeepAlive}; use super::{PauseServer, ResumeServer, StopServer}; @@ -54,17 +54,10 @@ where h: Option>>, threads: usize, backlog: i32, - host: Option, - keep_alive: KeepAlive, - factory: Arc Vec + Send + Sync>, - #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] - workers: Vec<(usize, Addr>)>, sockets: Vec, - accept: Option<( - mio::SetReadiness, - sync_mpsc::Sender, - Slab, - )>, + pool: WorkersPool, + workers: Vec<(usize, Addr>)>, + accept: AcceptLoop, exit: bool, shutdown_timeout: u16, signals: Option>, @@ -105,12 +98,10 @@ where h: None, threads: num_cpus::get(), backlog: 2048, - host: None, - keep_alive: KeepAlive::Os, - factory: Arc::new(f), + pool: WorkersPool::new(f), workers: Vec::new(), sockets: Vec::new(), - accept: None, + accept: AcceptLoop::new(), exit: false, shutdown_timeout: 30, signals: None, @@ -128,15 +119,6 @@ where self } - #[doc(hidden)] - #[deprecated( - since = "0.6.0", - note = "please use `HttpServer::workers()` instead" - )] - pub fn threads(self, num: usize) -> Self { - self.workers(num) - } - /// Set the maximum number of pending connections. /// /// This refers to the number of clients that can be waiting to be served. @@ -152,11 +134,34 @@ where self } + /// Sets the maximum per-worker number of concurrent connections. + /// + /// All socket listeners will stop accepting connections when this limit is reached + /// for each worker. + /// + /// By default max connections is set to a 100k. + pub fn max_connections(mut self, num: usize) -> Self { + self.accept.max_connections(num); + self + } + + /// Sets the maximum concurrent per-worker number of SSL handshakes. + /// + /// All listeners will stop accepting connections when this limit is reached. It + /// can be used to limit the global SSL CPU usage regardless of each worker + /// capacity. + /// + /// By default max connections is set to a 256. + pub fn max_sslrate(mut self, num: usize) -> Self { + self.accept.max_sslrate(num); + self + } + /// Set server keep-alive setting. /// /// By default keep alive is set to a `Os`. pub fn keep_alive>(mut self, val: T) -> Self { - self.keep_alive = val.into(); + self.pool.keep_alive = val.into(); self } @@ -166,7 +171,7 @@ where /// 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.pool.host = Some(val); self } @@ -395,27 +400,12 @@ where Ok(self) } - fn start_workers( - &mut self, settings: &ServerSettings, sockets: &Slab, - ) -> Vec<(usize, mpsc::UnboundedSender>)> { + fn start_workers(&mut self, notify: &AcceptNotify) -> Vec { // start workers let mut workers = Vec::new(); for idx in 0..self.threads { - let (tx, rx) = mpsc::unbounded::>(); - - let ka = self.keep_alive; - let socks = sockets.clone(); - let factory = Arc::clone(&self.factory); - let parts = settings.parts(); - - let addr = Arbiter::start(move |ctx: &mut Context<_>| { - let s = ServerSettings::from_parts(parts); - let apps: Vec<_> = - (*factory)().into_iter().map(|h| h.into_handler()).collect(); - ctx.add_message_stream(rx); - Worker::new(apps, socks, ka, s) - }); - workers.push((idx, tx)); + let (worker, addr) = self.pool.start(idx, notify.clone()); + workers.push(worker); self.workers.push((idx, addr)); } info!("Starting {} http workers", self.threads); @@ -466,30 +456,20 @@ impl HttpServer { if self.sockets.is_empty() { panic!("HttpServer::bind() has to be called before start()"); } else { - let (tx, rx) = mpsc::unbounded(); - - let mut socks = Slab::new(); let mut addrs: Vec<(usize, Socket)> = Vec::new(); for socket in self.sockets.drain(..) { - let entry = socks.vacant_entry(); - let token = entry.key(); - entry.insert(SocketInfo { - addr: socket.addr, - htype: socket.tp.clone(), - }); + let token = self.pool.insert(socket.addr, socket.tp.clone()); addrs.push((token, socket)); } - - let settings = ServerSettings::new(Some(addrs[0].1.addr), &self.host, false); - let workers = self.start_workers(&settings, &socks); + let notify = self.accept.get_notify(); + let workers = self.start_workers(¬ify); // start accept thread for (_, sock) in &addrs { info!("Starting server on http://{}", sock.addr); } - let (r, cmd) = start_accept_thread(addrs, tx.clone(), workers.clone()); - self.accept = Some((r, cmd, socks)); + let rx = self.accept.start(addrs, workers.clone()); // start http server actor let signals = self.subscribe_to_signals(); @@ -600,15 +580,18 @@ 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 apps: Vec<_> = (*self.factory)() + let settings = ServerSettings::new(Some(addr), &self.pool.host, secure); + let apps: Vec<_> = (*self.pool.factory)() .into_iter() .map(|h| h.into_handler()) .collect(); self.h = Some(Rc::new(WorkerSettings::new( apps, - self.keep_alive, + self.pool.keep_alive, settings, + AcceptNotify::default(), + Arc::new(AtomicUsize::new(0)), + Arc::new(AtomicUsize::new(0)), ))); // start server @@ -676,7 +659,6 @@ impl StreamHandler for HttpServer { if found { error!("Worker has died {:?}, restarting", idx); - let (tx, rx) = mpsc::unbounded::>(); let mut new_idx = self.workers.len(); 'found: loop { @@ -689,25 +671,10 @@ impl StreamHandler for HttpServer { break; } - let ka = self.keep_alive; - let factory = Arc::clone(&self.factory); - let host = self.host.clone(); - let socks = self.accept.as_ref().unwrap().2.clone(); - let addr = socks[0].addr; - - let addr = Arbiter::start(move |ctx: &mut Context<_>| { - let settings = ServerSettings::new(Some(addr), &host, false); - let apps: Vec<_> = - (*factory)().into_iter().map(|h| h.into_handler()).collect(); - ctx.add_message_stream(rx); - Worker::new(apps, socks, ka, settings) - }); - if let Some(ref item) = &self.accept { - let _ = item.1.send(Command::Worker(new_idx, tx.clone())); - let _ = item.0.set_readiness(mio::Ready::readable()); - } - + let (worker, addr) = + self.pool.start(new_idx, self.accept.get_notify()); self.workers.push((new_idx, addr)); + self.accept.send(Command::Worker(worker)); } } } @@ -735,10 +702,7 @@ impl Handler for HttpServer { 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.accept.send(Command::Pause); } } @@ -746,10 +710,7 @@ impl Handler for HttpServer { 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.accept.send(Command::Resume); } } @@ -758,10 +719,7 @@ impl Handler for HttpServer { 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); - let _ = item.0.set_readiness(mio::Ready::readable()); - } + self.accept.send(Command::Stop); // stop workers let (tx, rx) = mpsc::channel(1); diff --git a/src/server/worker.rs b/src/server/worker.rs index 5e753ce5..ed079956 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,9 +1,12 @@ +use std::rc::Rc; +use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc}; +use std::{net, time}; + +use futures::sync::mpsc::{unbounded, SendError, UnboundedSender}; use futures::sync::oneshot; use futures::Future; use net2::TcpStreamExt; use slab::Slab; -use std::rc::Rc; -use std::{net, time}; use tokio::executor::current_thread; use tokio_reactor::Handle; use tokio_tcp::TcpStream; @@ -24,16 +27,15 @@ use tokio_openssl::SslAcceptorExt; #[cfg(feature = "rust-tls")] use rustls::{ServerConfig, Session}; #[cfg(feature = "rust-tls")] -use std::sync::Arc; -#[cfg(feature = "rust-tls")] use tokio_rustls::ServerConfigExt; use actix::msgs::StopArbiter; -use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response}; +use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, Message, Response}; -use server::channel::HttpChannel; -use server::settings::{ServerSettings, WorkerSettings}; -use server::{HttpHandler, KeepAlive}; +use super::accept::AcceptNotify; +use super::channel::HttpChannel; +use super::settings::{ServerSettings, WorkerSettings}; +use super::{HttpHandler, IntoHttpHandler, KeepAlive}; #[derive(Message)] pub(crate) struct Conn { @@ -49,6 +51,95 @@ pub(crate) struct SocketInfo { pub htype: StreamHandlerType, } +pub(crate) struct WorkersPool { + sockets: Slab, + pub factory: Arc Vec + Send + Sync>, + pub host: Option, + pub keep_alive: KeepAlive, +} + +impl WorkersPool { + pub fn new(factory: F) -> Self + where + F: Fn() -> Vec + Send + Sync + 'static, + { + WorkersPool { + factory: Arc::new(factory), + host: None, + keep_alive: KeepAlive::Os, + sockets: Slab::new(), + } + } + + pub fn insert(&mut self, addr: net::SocketAddr, htype: StreamHandlerType) -> usize { + let entry = self.sockets.vacant_entry(); + let token = entry.key(); + entry.insert(SocketInfo { addr, htype }); + token + } + + pub fn start( + &mut self, idx: usize, notify: AcceptNotify, + ) -> (WorkerClient, Addr>) { + let host = self.host.clone(); + let addr = self.sockets[0].addr; + let factory = Arc::clone(&self.factory); + let socks = self.sockets.clone(); + let ka = self.keep_alive; + let (tx, rx) = unbounded::>(); + let client = WorkerClient::new(idx, tx, self.sockets.clone()); + let conn = client.conn.clone(); + let sslrate = client.sslrate.clone(); + + let addr = Arbiter::start(move |ctx: &mut Context<_>| { + let s = ServerSettings::new(Some(addr), &host, false); + let apps: Vec<_> = + (*factory)().into_iter().map(|h| h.into_handler()).collect(); + ctx.add_message_stream(rx); + Worker::new(apps, socks, ka, s, conn, sslrate, notify) + }); + + (client, addr) + } +} + +#[derive(Clone)] +pub(crate) struct WorkerClient { + pub idx: usize, + tx: UnboundedSender>, + info: Slab, + pub conn: Arc, + pub sslrate: Arc, +} + +impl WorkerClient { + fn new( + idx: usize, tx: UnboundedSender>, info: Slab, + ) -> Self { + WorkerClient { + idx, + tx, + info, + conn: Arc::new(AtomicUsize::new(0)), + sslrate: Arc::new(AtomicUsize::new(0)), + } + } + + pub fn send( + &self, msg: Conn, + ) -> Result<(), SendError>> { + self.tx.unbounded_send(msg) + } + + pub fn available(&self, maxconn: usize, maxsslrate: usize) -> bool { + if maxsslrate <= self.sslrate.load(Ordering::Relaxed) { + false + } else { + maxconn > self.conn.load(Ordering::Relaxed) + } + } +} + /// Stop worker message. Returns `true` on successful shutdown /// and `false` if some connections still alive. pub(crate) struct StopWorker { @@ -75,7 +166,8 @@ where impl Worker { pub(crate) fn new( h: Vec, socks: Slab, keep_alive: KeepAlive, - settings: ServerSettings, + settings: ServerSettings, conn: Arc, sslrate: Arc, + notify: AcceptNotify, ) -> Worker { let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { Some(time::Duration::new(val as u64, 0)) @@ -84,7 +176,9 @@ impl Worker { }; Worker { - settings: Rc::new(WorkerSettings::new(h, keep_alive, settings)), + settings: Rc::new(WorkerSettings::new( + h, keep_alive, settings, notify, conn, sslrate, + )), socks, tcp_ka, } @@ -182,6 +276,18 @@ pub(crate) enum StreamHandlerType { } impl StreamHandlerType { + pub fn is_ssl(&self) -> bool { + match *self { + StreamHandlerType::Normal => false, + #[cfg(feature = "tls")] + StreamHandlerType::Tls(_) => true, + #[cfg(feature = "alpn")] + StreamHandlerType::Alpn(_) => true, + #[cfg(feature = "rust-tls")] + StreamHandlerType::Rustls(_) => true, + } + } + fn handle( &mut self, h: Rc>, msg: Conn, ) { @@ -201,9 +307,11 @@ impl StreamHandlerType { let _ = io.set_nodelay(true); let io = TcpStream::from_std(io, &Handle::default()) .expect("failed to associate TCP stream"); + self.settings.ssl_conn_add(); current_thread::spawn(TlsAcceptorExt::accept_async(acceptor, io).then( move |res| { + self.settings.ssl_conn_del(); match res { Ok(io) => current_thread::spawn(HttpChannel::new( h, io, peer, http2, @@ -222,9 +330,11 @@ impl StreamHandlerType { let _ = io.set_nodelay(true); let io = TcpStream::from_std(io, &Handle::default()) .expect("failed to associate TCP stream"); + self.settings.ssl_conn_add(); current_thread::spawn(SslAcceptorExt::accept_async(acceptor, io).then( move |res| { + self.settings.ssl_conn_del(); match res { Ok(io) => { let http2 = if let Some(p) = @@ -252,9 +362,11 @@ impl StreamHandlerType { let _ = io.set_nodelay(true); let io = TcpStream::from_std(io, &Handle::default()) .expect("failed to associate TCP stream"); + self.settings.ssl_conn_add(); current_thread::spawn(ServerConfigExt::accept_async(acceptor, io).then( move |res| { + self.settings.ssl_conn_del(); match res { Ok(io) => { let http2 = if let Some(p) = From f8e5d7c6c1a1a5c30da7c904fbc4b5d1276ec6fd Mon Sep 17 00:00:00 2001 From: Mathieu Amiot <1262712+OtaK@users.noreply.github.com> Date: Fri, 3 Aug 2018 11:11:51 +0000 Subject: [PATCH 0562/1635] Fixed broken build on wrong variable usage (#440) --- src/server/worker.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/server/worker.rs b/src/server/worker.rs index ed079956..e9bf4225 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -307,11 +307,11 @@ impl StreamHandlerType { let _ = io.set_nodelay(true); let io = TcpStream::from_std(io, &Handle::default()) .expect("failed to associate TCP stream"); - self.settings.ssl_conn_add(); + h.ssl_conn_add(); current_thread::spawn(TlsAcceptorExt::accept_async(acceptor, io).then( move |res| { - self.settings.ssl_conn_del(); + h.ssl_conn_del(); match res { Ok(io) => current_thread::spawn(HttpChannel::new( h, io, peer, http2, @@ -330,11 +330,11 @@ impl StreamHandlerType { let _ = io.set_nodelay(true); let io = TcpStream::from_std(io, &Handle::default()) .expect("failed to associate TCP stream"); - self.settings.ssl_conn_add(); + h.ssl_conn_add(); current_thread::spawn(SslAcceptorExt::accept_async(acceptor, io).then( move |res| { - self.settings.ssl_conn_del(); + h.ssl_conn_del(); match res { Ok(io) => { let http2 = if let Some(p) = @@ -362,11 +362,11 @@ impl StreamHandlerType { let _ = io.set_nodelay(true); let io = TcpStream::from_std(io, &Handle::default()) .expect("failed to associate TCP stream"); - self.settings.ssl_conn_add(); + h.ssl_conn_add(); current_thread::spawn(ServerConfigExt::accept_async(acceptor, io).then( move |res| { - self.settings.ssl_conn_del(); + h.ssl_conn_del(); match res { Ok(io) => { let http2 = if let Some(p) = From 9a10d8aa7a8c48be693382fb8864a2381925bd73 Mon Sep 17 00:00:00 2001 From: Mathieu Amiot <1262712+OtaK@users.noreply.github.com> Date: Fri, 3 Aug 2018 12:03:11 +0000 Subject: [PATCH 0563/1635] Fixed headers' formating for CORS Middleware Access-Control-Expose-Headers header value to HTTP/1.1 & HTTP/2 spec-compliant format (#436) --- CHANGES.md | 3 ++- src/middleware/cors.rs | 26 +++++++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f7e663d6..d0488c55 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,7 +15,6 @@ * Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433 - ### Fixed * Fixed failure 0.1.2 compatibility @@ -26,6 +25,8 @@ * HttpRequest::url_for is not working with scopes #429 +* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436 + ## [0.7.2] - 2018-07-26 diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 052e4da2..a6172740 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -838,10 +838,9 @@ impl CorsBuilder { if !self.expose_hdrs.is_empty() { cors.expose_hdrs = Some( - self.expose_hdrs - .iter() - .fold(String::new(), |s, v| s + v.as_str())[1..] - .to_owned(), + self.expose_hdrs.iter() + .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..] + .to_owned() ); } Cors { @@ -1073,12 +1072,14 @@ mod tests { #[test] fn test_response() { + let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; let cors = Cors::build() .send_wildcard() .disable_preflight() .max_age(3600) .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) + .allowed_headers(exposed_headers.clone()) + .expose_headers(exposed_headers.clone()) .allowed_header(header::CONTENT_TYPE) .finish(); @@ -1100,6 +1101,21 @@ mod tests { resp.headers().get(header::VARY).unwrap().as_bytes() ); + { + let headers = resp.headers() + .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) + .unwrap() + .to_str() + .unwrap() + .split(',') + .map(|s| s.trim()) + .collect::>(); + + for h in exposed_headers { + assert!(headers.contains(&h.as_str())); + } + } + let resp: HttpResponse = HttpResponse::Ok().header(header::VARY, "Accept").finish(); let resp = cors.response(&req, resp).unwrap().response(); From e61ef7dee4a4e1017372783594b6f6bda6dc6f5c Mon Sep 17 00:00:00 2001 From: Jan Michael Auer Date: Fri, 3 Aug 2018 14:56:26 +0200 Subject: [PATCH 0564/1635] Use zlib instead of deflate for content encoding (#442) --- CHANGES.md | 3 +++ src/client/writer.rs | 6 +++--- src/server/input.rs | 6 +++--- src/server/output.rs | 8 ++++---- tests/test_server.rs | 12 ++++++------ 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d0488c55..478b8e0e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,9 @@ * Added `HttpServer::max_connections()` and `HttpServer::max_sslrate()`, accept backpressure #250 +* Fix: Use zlib instead of raw deflate for decoding and encoding payloads with + `Content-Encoding: deflate`. + ## [0.7.3] - 2018-08-01 diff --git a/src/client/writer.rs b/src/client/writer.rs index b691407d..81ad9651 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -8,7 +8,7 @@ use std::io::{self, Write}; use brotli2::write::BrotliEncoder; use bytes::{BufMut, BytesMut}; #[cfg(feature = "flate2")] -use flate2::write::{DeflateEncoder, GzEncoder}; +use flate2::write::{GzEncoder, ZlibEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; use futures::{Async, Poll}; @@ -232,7 +232,7 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { let mut enc = match encoding { #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::default()), + ZlibEncoder::new(transfer, Compression::default()), ), #[cfg(feature = "flate2")] ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( @@ -302,7 +302,7 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { req.replace_body(body); let enc = match encoding { #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( + ContentEncoding::Deflate => ContentEncoder::Deflate(ZlibEncoder::new( transfer, Compression::default(), )), diff --git a/src/server/input.rs b/src/server/input.rs index fe62e760..d23d1e99 100644 --- a/src/server/input.rs +++ b/src/server/input.rs @@ -5,7 +5,7 @@ use brotli2::write::BrotliDecoder; use bytes::{Bytes, BytesMut}; use error::PayloadError; #[cfg(feature = "flate2")] -use flate2::write::{DeflateDecoder, GzDecoder}; +use flate2::write::{GzDecoder, ZlibDecoder}; use header::ContentEncoding; use http::header::{HeaderMap, CONTENT_ENCODING}; use payload::{PayloadSender, PayloadStatus, PayloadWriter}; @@ -139,7 +139,7 @@ impl PayloadWriter for EncodedPayload { pub(crate) enum Decoder { #[cfg(feature = "flate2")] - Deflate(Box>), + Deflate(Box>), #[cfg(feature = "flate2")] Gzip(Box>), #[cfg(feature = "brotli")] @@ -186,7 +186,7 @@ impl PayloadStream { } #[cfg(feature = "flate2")] ContentEncoding::Deflate => { - Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new()))) + Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new()))) } #[cfg(feature = "flate2")] ContentEncoding::Gzip => { diff --git a/src/server/output.rs b/src/server/output.rs index 597faf34..970e03d8 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -7,7 +7,7 @@ use std::{cmp, fmt, io, mem}; use brotli2::write::BrotliEncoder; use bytes::BytesMut; #[cfg(feature = "flate2")] -use flate2::write::{DeflateEncoder, GzEncoder}; +use flate2::write::{GzEncoder, ZlibEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH}; @@ -210,7 +210,7 @@ impl Output { let mut enc = match encoding { #[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate( - DeflateEncoder::new(transfer, Compression::fast()), + ZlibEncoder::new(transfer, Compression::fast()), ), #[cfg(feature = "flate2")] ContentEncoding::Gzip => ContentEncoder::Gzip( @@ -273,7 +273,7 @@ impl Output { let enc = match encoding { #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new( + ContentEncoding::Deflate => ContentEncoder::Deflate(ZlibEncoder::new( transfer, Compression::fast(), )), @@ -354,7 +354,7 @@ impl Output { pub(crate) enum ContentEncoder { #[cfg(feature = "flate2")] - Deflate(DeflateEncoder), + Deflate(ZlibEncoder), #[cfg(feature = "flate2")] Gzip(GzEncoder), #[cfg(feature = "brotli")] diff --git a/tests/test_server.rs b/tests/test_server.rs index 842d685f..4db73a3b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -20,7 +20,7 @@ use std::{net, thread, time}; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{Bytes, BytesMut}; use flate2::read::GzDecoder; -use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder}; +use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; use flate2::Compression; use futures::stream::once; use futures::{Future, Stream}; @@ -528,7 +528,7 @@ fn test_body_chunked_explicit() { #[test] fn test_body_identity() { - let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); e.write_all(STR.as_ref()).unwrap(); let enc = e.finish().unwrap(); let enc2 = enc.clone(); @@ -578,7 +578,7 @@ fn test_body_deflate() { let bytes = srv.execute(response.body()).unwrap(); // decode deflate - let mut e = DeflateDecoder::new(Vec::new()); + let mut e = ZlibDecoder::new(Vec::new()); e.write_all(bytes.as_ref()).unwrap(); let dec = e.finish().unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); @@ -727,7 +727,7 @@ fn test_reading_deflate_encoding() { }) }); - let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); e.write_all(STR.as_ref()).unwrap(); let enc = e.finish().unwrap(); @@ -760,7 +760,7 @@ fn test_reading_deflate_encoding_large() { }) }); - let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); e.write_all(data.as_ref()).unwrap(); let enc = e.finish().unwrap(); @@ -797,7 +797,7 @@ fn test_reading_deflate_encoding_large_random() { }) }); - let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); e.write_all(data.as_ref()).unwrap(); let enc = e.finish().unwrap(); From 036cf5e867a997f059784837712c1d1d05b84fbe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 3 Aug 2018 08:20:59 -0700 Subject: [PATCH 0565/1635] update changes --- CHANGES.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 478b8e0e..c6e4a943 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,11 +4,16 @@ ### Added -* Added `HttpServer::max_connections()` and `HttpServer::max_sslrate()`, accept backpressure #250 +* Added `HttpServer::max_connections()` and `HttpServer::max_sslrate()`, + accept backpressure #250 -* Fix: Use zlib instead of raw deflate for decoding and encoding payloads with +### Fixed + +* Use zlib instead of raw deflate for decoding and encoding payloads with `Content-Encoding: deflate`. +* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436 + ## [0.7.3] - 2018-08-01 From f3f1e04853dbaf1ff7f014ff50319fc822e20240 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 3 Aug 2018 16:09:46 -0700 Subject: [PATCH 0566/1635] refactor ssl support --- CHANGES.md | 5 +- src/server/accept.rs | 60 ++-- src/server/h1.rs | 2 +- src/server/h2.rs | 2 +- src/server/mod.rs | 129 +++----- src/server/settings.rs | 24 +- src/server/srv.rs | 379 ++++++++-------------- src/server/ssl/mod.rs | 14 + src/server/ssl/nativetls.rs | 67 ++++ src/server/ssl/openssl.rs | 96 ++++++ src/server/ssl/rustls.rs | 92 ++++++ src/server/worker.rs | 622 ++++++++++++++++++++++-------------- 12 files changed, 879 insertions(+), 613 deletions(-) create mode 100644 src/server/ssl/mod.rs create mode 100644 src/server/ssl/nativetls.rs create mode 100644 src/server/ssl/openssl.rs create mode 100644 src/server/ssl/rustls.rs diff --git a/CHANGES.md b/CHANGES.md index c6e4a943..4d1610c0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,9 +4,12 @@ ### Added -* Added `HttpServer::max_connections()` and `HttpServer::max_sslrate()`, +* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`, accept backpressure #250 +* Allow to customize connection handshake process via `HttpServer::listen_with()` + and `HttpServer::bind_with()` methods + ### Fixed * Use zlib instead of raw deflate for decoding and encoding payloads with diff --git a/src/server/accept.rs b/src/server/accept.rs index f846e4a4..e837852d 100644 --- a/src/server/accept.rs +++ b/src/server/accept.rs @@ -9,8 +9,8 @@ use tokio_timer::Delay; use actix::{msgs::Execute, Arbiter, System}; -use super::srv::{ServerCommand, Socket}; -use super::worker::{Conn, WorkerClient}; +use super::srv::ServerCommand; +use super::worker::{Conn, Socket, Token, WorkerClient}; pub(crate) enum Command { Pause, @@ -21,7 +21,7 @@ pub(crate) enum Command { struct ServerSocketInfo { addr: net::SocketAddr, - token: usize, + token: Token, sock: mio::net::TcpListener, timeout: Option, } @@ -31,20 +31,24 @@ pub(crate) struct AcceptNotify { ready: mio::SetReadiness, maxconn: usize, maxconn_low: usize, - maxsslrate: usize, - maxsslrate_low: usize, + maxconnrate: usize, + maxconnrate_low: usize, } impl AcceptNotify { - pub fn new(ready: mio::SetReadiness, maxconn: usize, maxsslrate: usize) -> Self { + pub fn new(ready: mio::SetReadiness, maxconn: usize, maxconnrate: usize) -> Self { let maxconn_low = if maxconn > 10 { maxconn - 10 } else { 0 }; - let maxsslrate_low = if maxsslrate > 10 { maxsslrate - 10 } else { 0 }; + let maxconnrate_low = if maxconnrate > 10 { + maxconnrate - 10 + } else { + 0 + }; AcceptNotify { ready, maxconn, maxconn_low, - maxsslrate, - maxsslrate_low, + maxconnrate, + maxconnrate_low, } } @@ -53,8 +57,8 @@ impl AcceptNotify { let _ = self.ready.set_readiness(mio::Ready::readable()); } } - pub fn notify_maxsslrate(&self, sslrate: usize) { - if sslrate > self.maxsslrate_low && sslrate <= self.maxsslrate { + pub fn notify_maxconnrate(&self, connrate: usize) { + if connrate > self.maxconnrate_low && connrate <= self.maxconnrate { let _ = self.ready.set_readiness(mio::Ready::readable()); } } @@ -78,7 +82,7 @@ pub(crate) struct AcceptLoop { mpsc::UnboundedReceiver, )>, maxconn: usize, - maxsslrate: usize, + maxconnrate: usize, } impl AcceptLoop { @@ -94,7 +98,7 @@ impl AcceptLoop { notify_ready, notify_reg: Some(notify_reg), maxconn: 102_400, - maxsslrate: 256, + maxconnrate: 256, rx: Some(rx), srv: Some(mpsc::unbounded()), } @@ -106,19 +110,19 @@ impl AcceptLoop { } pub fn get_notify(&self) -> AcceptNotify { - AcceptNotify::new(self.notify_ready.clone(), self.maxconn, self.maxsslrate) + AcceptNotify::new(self.notify_ready.clone(), self.maxconn, self.maxconnrate) } - pub fn max_connections(&mut self, num: usize) { + pub fn maxconn(&mut self, num: usize) { self.maxconn = num; } - pub fn max_sslrate(&mut self, num: usize) { - self.maxsslrate = num; + pub fn maxconnrate(&mut self, num: usize) { + self.maxconnrate = num; } pub(crate) fn start( - &mut self, socks: Vec<(usize, Socket)>, workers: Vec, + &mut self, socks: Vec, workers: Vec, ) -> mpsc::UnboundedReceiver { let (tx, rx) = self.srv.take().expect("Can not re-use AcceptInfo"); @@ -127,7 +131,7 @@ impl AcceptLoop { self.cmd_reg.take().expect("Can not re-use AcceptInfo"), self.notify_reg.take().expect("Can not re-use AcceptInfo"), self.maxconn, - self.maxsslrate, + self.maxconnrate, socks, tx, workers, @@ -145,7 +149,7 @@ struct Accept { timer: (mio::Registration, mio::SetReadiness), next: usize, maxconn: usize, - maxsslrate: usize, + maxconnrate: usize, backpressure: bool, } @@ -171,8 +175,8 @@ impl Accept { #![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] pub(crate) fn start( rx: sync_mpsc::Receiver, cmd_reg: mio::Registration, - notify_reg: mio::Registration, maxconn: usize, maxsslrate: usize, - socks: Vec<(usize, Socket)>, srv: mpsc::UnboundedSender, + notify_reg: mio::Registration, maxconn: usize, maxconnrate: usize, + socks: Vec, srv: mpsc::UnboundedSender, workers: Vec, ) { let sys = System::current(); @@ -184,7 +188,7 @@ impl Accept { System::set_current(sys); let mut accept = Accept::new(rx, socks, workers, srv); accept.maxconn = maxconn; - accept.maxsslrate = maxsslrate; + accept.maxconnrate = maxconnrate; // Start listening for incoming commands if let Err(err) = accept.poll.register( @@ -211,7 +215,7 @@ impl Accept { } fn new( - rx: sync_mpsc::Receiver, socks: Vec<(usize, Socket)>, + rx: sync_mpsc::Receiver, socks: Vec, workers: Vec, srv: mpsc::UnboundedSender, ) -> Accept { // Create a poll instance @@ -222,7 +226,7 @@ impl Accept { // Start accept let mut sockets = Slab::new(); - for (stoken, sock) in socks { + for sock in socks { let server = mio::net::TcpListener::from_std(sock.lst) .expect("Can not create mio::net::TcpListener"); @@ -240,7 +244,7 @@ impl Accept { } entry.insert(ServerSocketInfo { - token: stoken, + token: sock.token, addr: sock.addr, sock: server, timeout: None, @@ -264,7 +268,7 @@ impl Accept { next: 0, timer: (tm, tmr), maxconn: 102_400, - maxsslrate: 256, + maxconnrate: 256, backpressure: false, } } @@ -427,7 +431,7 @@ impl Accept { let mut idx = 0; while idx < self.workers.len() { idx += 1; - if self.workers[self.next].available(self.maxconn, self.maxsslrate) { + if self.workers[self.next].available(self.maxconn, self.maxconnrate) { match self.workers[self.next].send(msg) { Ok(_) => { self.next = (self.next + 1) % self.workers.len(); diff --git a/src/server/h1.rs b/src/server/h1.rs index 9f3bda28..085cea00 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -375,7 +375,7 @@ where self.keepalive_timer.take(); // search handler for request - for h in self.settings.handlers().iter_mut() { + for h in self.settings.handlers().iter() { msg = match h.handle(msg) { Ok(mut pipe) => { if self.tasks.is_empty() { diff --git a/src/server/h2.rs b/src/server/h2.rs index e5355a1f..cb5367c5 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -347,7 +347,7 @@ impl Entry { // start request processing let mut task = None; - for h in settings.handlers().iter_mut() { + for h in settings.handlers().iter() { msg = match h.handle(msg) { Ok(t) => { task = Some(t); diff --git a/src/server/mod.rs b/src/server/mod.rs index 429e293f..55de25db 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,10 +1,11 @@ //! Http server use std::net::Shutdown; -use std::{io, time}; +use std::{io, net, time}; use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; +use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_reactor::Handle; use tokio_tcp::TcpStream; pub(crate) mod accept; @@ -21,11 +22,13 @@ pub(crate) mod message; pub(crate) mod output; pub(crate) mod settings; mod srv; +mod ssl; mod worker; pub use self::message::Request; pub use self::settings::ServerSettings; pub use self::srv::HttpServer; +pub use self::ssl::*; #[doc(hidden)] pub use self::helpers::write_content_length; @@ -72,6 +75,13 @@ where HttpServer::new(factory) } +bitflags! { + pub struct ServerFlags: u8 { + const HTTP1 = 0b0000_0001; + const HTTP2 = 0b0000_0010; + } +} + #[derive(Debug, PartialEq, Clone, Copy)] /// Server keep-alive setting pub enum KeepAlive { @@ -179,6 +189,34 @@ impl IntoHttpHandler for T { } } +pub(crate) trait IntoAsyncIo { + type Io: AsyncRead + AsyncWrite; + + fn into_async_io(self) -> Result; +} + +impl IntoAsyncIo for net::TcpStream { + type Io = TcpStream; + + fn into_async_io(self) -> Result { + TcpStream::from_std(self, &Handle::default()) + } +} + +/// Trait implemented by types that could accept incomming socket connections. +pub trait AcceptorService: Clone { + /// Established connection type + type Accepted: IoStream; + /// Future describes async accept process. + type Future: Future + 'static; + + /// Establish new connection + fn accept(&self, io: Io) -> Self::Future; + + /// Scheme + fn scheme(&self) -> &'static str; +} + #[doc(hidden)] #[derive(Debug)] pub enum WriterState { @@ -267,90 +305,3 @@ impl IoStream for TcpStream { TcpStream::set_linger(self, dur) } } - -#[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) - } -} - -#[cfg(feature = "rust-tls")] -use rustls::{ClientSession, ServerSession}; -#[cfg(feature = "rust-tls")] -use tokio_rustls::TlsStream as RustlsStream; - -#[cfg(feature = "rust-tls")] -impl IoStream for RustlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } -} - -#[cfg(feature = "rust-tls")] -impl IoStream for RustlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } -} diff --git a/src/server/settings.rs b/src/server/settings.rs index 8e30646d..508be67d 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -132,7 +132,7 @@ impl ServerSettings { const DATE_VALUE_LENGTH: usize = 29; pub(crate) struct WorkerSettings { - h: RefCell>, + h: Vec, keep_alive: u64, ka_enabled: bool, bytes: Rc, @@ -140,14 +140,14 @@ pub(crate) struct WorkerSettings { channels: Arc, node: RefCell>, date: UnsafeCell, - sslrate: Arc, + connrate: Arc, notify: AcceptNotify, } impl WorkerSettings { pub(crate) fn new( h: Vec, keep_alive: KeepAlive, settings: ServerSettings, - notify: AcceptNotify, channels: Arc, sslrate: Arc, + notify: AcceptNotify, channels: Arc, connrate: Arc, ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -156,7 +156,7 @@ impl WorkerSettings { }; WorkerSettings { - h: RefCell::new(h), + h, bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), node: RefCell::new(Node::head()), @@ -164,7 +164,7 @@ impl WorkerSettings { keep_alive, ka_enabled, channels, - sslrate, + connrate, notify, } } @@ -177,8 +177,8 @@ impl WorkerSettings { self.node.borrow_mut() } - pub fn handlers(&self) -> RefMut> { - self.h.borrow_mut() + pub fn handlers(&self) -> &Vec { + &self.h } pub fn keep_alive(&self) -> u64 { @@ -230,13 +230,13 @@ impl WorkerSettings { } #[allow(dead_code)] - pub(crate) fn ssl_conn_add(&self) { - self.sslrate.fetch_add(1, Ordering::Relaxed); + pub(crate) fn conn_rate_add(&self) { + self.connrate.fetch_add(1, Ordering::Relaxed); } #[allow(dead_code)] - pub(crate) fn ssl_conn_del(&self) { - let val = self.sslrate.fetch_sub(1, Ordering::Relaxed); - self.notify.notify_maxsslrate(val); + pub(crate) fn conn_rate_del(&self) { + let val = self.connrate.fetch_sub(1, Ordering::Relaxed); + self.notify.notify_maxconnrate(val); } } diff --git a/src/server/srv.rs b/src/server/srv.rs index b6bd2196..33c820aa 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -10,15 +10,15 @@ use actix::{ use futures::sync::mpsc; use futures::{Future, Sink, Stream}; -use net2::TcpBuilder; use num_cpus; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_tcp::TcpStream; #[cfg(feature = "tls")] use native_tls::TlsAcceptor; #[cfg(feature = "alpn")] -use openssl::ssl::{AlpnError, SslAcceptorBuilder}; +use openssl::ssl::SslAcceptorBuilder; #[cfg(feature = "rust-tls")] use rustls::ServerConfig; @@ -26,43 +26,25 @@ use rustls::ServerConfig; use super::accept::{AcceptLoop, AcceptNotify, Command}; use super::channel::{HttpChannel, WrapperStream}; use super::settings::{ServerSettings, WorkerSettings}; -use super::worker::{ - Conn, StopWorker, StreamHandlerType, Worker, WorkerClient, WorkersPool, -}; -use super::{IntoHttpHandler, IoStream, KeepAlive}; +use super::worker::{Conn, StopWorker, Token, Worker, WorkerClient, WorkerFactory}; +use super::{AcceptorService, IntoHttpHandler, IoStream, KeepAlive}; use super::{PauseServer, ResumeServer, StopServer}; -#[cfg(feature = "alpn")] -fn configure_alpn(builder: &mut SslAcceptorBuilder) -> io::Result<()> { - builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - Ok(()) -} - /// An HTTP Server pub struct HttpServer where H: IntoHttpHandler + 'static, { - h: Option>>, threads: usize, - backlog: i32, - sockets: Vec, - pool: WorkersPool, - workers: Vec<(usize, Addr>)>, + factory: WorkerFactory, + workers: Vec<(usize, Addr)>, accept: AcceptLoop, exit: bool, shutdown_timeout: u16, signals: Option>, no_http2: bool, no_signals: bool, + settings: Option>>, } pub(crate) enum ServerCommand { @@ -76,12 +58,6 @@ where type Context = Context; } -pub(crate) struct Socket { - pub lst: net::TcpListener, - pub addr: net::SocketAddr, - pub tp: StreamHandlerType, -} - impl HttpServer where H: IntoHttpHandler + 'static, @@ -95,18 +71,16 @@ where let f = move || (factory)().into_iter().collect(); HttpServer { - h: None, threads: num_cpus::get(), - backlog: 2048, - pool: WorkersPool::new(f), + factory: WorkerFactory::new(f), workers: Vec::new(), - sockets: Vec::new(), accept: AcceptLoop::new(), exit: false, shutdown_timeout: 30, signals: None, no_http2: false, no_signals: false, + settings: None, } } @@ -130,7 +104,7 @@ where /// /// This method should be called before `bind()` method call. pub fn backlog(mut self, num: i32) -> Self { - self.backlog = num; + self.factory.backlog = num; self } @@ -140,20 +114,19 @@ where /// for each worker. /// /// By default max connections is set to a 100k. - pub fn max_connections(mut self, num: usize) -> Self { - self.accept.max_connections(num); + pub fn maxconn(mut self, num: usize) -> Self { + self.accept.maxconn(num); self } - /// Sets the maximum concurrent per-worker number of SSL handshakes. + /// Sets the maximum per-worker concurrent connection establish process. /// /// All listeners will stop accepting connections when this limit is reached. It - /// can be used to limit the global SSL CPU usage regardless of each worker - /// capacity. + /// can be used to limit the global SSL CPU usage. /// /// By default max connections is set to a 256. - pub fn max_sslrate(mut self, num: usize) -> Self { - self.accept.max_sslrate(num); + pub fn maxconnrate(mut self, num: usize) -> Self { + self.accept.maxconnrate(num); self } @@ -161,7 +134,7 @@ where /// /// By default keep alive is set to a `Os`. pub fn keep_alive>(mut self, val: T) -> Self { - self.pool.keep_alive = val.into(); + self.factory.keep_alive = val.into(); self } @@ -171,7 +144,7 @@ where /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. /// html#method.host) documentation for more information. pub fn server_hostname(mut self, val: String) -> Self { - self.pool.host = Some(val); + self.factory.host = Some(val); self } @@ -215,7 +188,7 @@ where /// Get addresses of bound sockets. pub fn addrs(&self) -> Vec { - self.sockets.iter().map(|s| s.addr).collect() + self.factory.addrs() } /// Get addresses of bound sockets and the scheme for it. @@ -225,10 +198,7 @@ where /// and the user should be presented with an enumeration of which /// socket requires which protocol. pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.sockets - .iter() - .map(|s| (s.addr, s.tp.scheme())) - .collect() + self.factory.addrs_with_scheme() } /// Use listener for accepting incoming connection requests @@ -236,175 +206,177 @@ where /// HttpServer does not change any configuration for TcpListener, /// it needs to be configured before passing it to listen() method. pub fn listen(mut self, lst: net::TcpListener) -> Self { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - lst, - tp: StreamHandlerType::Normal, - }); + self.factory.listen(lst); self } + /// Use listener for accepting incoming connection requests + pub fn listen_with( + mut self, lst: net::TcpListener, acceptor: A, + ) -> io::Result + where + A: AcceptorService + Send + 'static, + { + self.factory.listen_with(lst, acceptor); + Ok(self) + } + #[cfg(feature = "tls")] + #[doc(hidden)] + #[deprecated( + since = "0.7.4", + note = "please use `actix_web::HttpServer::listen_with()` and `actix_web::server::NativeTlsAcceptor` instead" + )] /// Use listener for accepting incoming tls connection requests /// /// HttpServer does not change any configuration for TcpListener, /// it needs to be configured before passing it to listen() method. - pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - lst, - tp: StreamHandlerType::Tls(acceptor.clone()), - }); - self + pub fn listen_tls( + self, lst: net::TcpListener, acceptor: TlsAcceptor, + ) -> io::Result { + use super::NativeTlsAcceptor; + + self.listen_with(lst, NativeTlsAcceptor::new(acceptor)) } #[cfg(feature = "alpn")] + #[doc(hidden)] + #[deprecated( + since = "0.7.4", + note = "please use `actix_web::HttpServer::listen_with()` and `actix_web::server::OpensslAcceptor` instead" + )] /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn listen_ssl( - mut self, lst: net::TcpListener, mut builder: SslAcceptorBuilder, + self, lst: net::TcpListener, builder: SslAcceptorBuilder, ) -> io::Result { + use super::{OpensslAcceptor, ServerFlags}; + // alpn support - if !self.no_http2 { - configure_alpn(&mut builder)?; - } - let acceptor = builder.build(); - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - lst, - tp: StreamHandlerType::Alpn(acceptor.clone()), - }); - Ok(self) + let flags = if !self.no_http2 { + ServerFlags::HTTP1 + } else { + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; + + self.listen_with(lst, OpensslAcceptor::with_flags(builder, flags)?) } #[cfg(feature = "rust-tls")] + #[doc(hidden)] + #[deprecated( + since = "0.7.4", + note = "please use `actix_web::HttpServer::listen_with()` and `actix_web::server::RustlsAcceptor` instead" + )] /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn listen_rustls( - mut self, lst: net::TcpListener, mut builder: ServerConfig, + self, lst: net::TcpListener, builder: ServerConfig, ) -> io::Result { + use super::{RustlsAcceptor, ServerFlags}; + // alpn support - if !self.no_http2 { - builder.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]); - } - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - lst, - tp: StreamHandlerType::Rustls(Arc::new(builder)), - }); - Ok(self) - } - - fn bind2(&mut self, addr: S) -> io::Result> { - let mut err = None; - let mut succ = false; - let mut sockets = Vec::new(); - for addr in addr.to_socket_addrs()? { - match create_tcp_listener(addr, self.backlog) { - Ok(lst) => { - succ = true; - let addr = lst.local_addr().unwrap(); - sockets.push(Socket { - lst, - addr, - tp: StreamHandlerType::Normal, - }); - } - Err(e) => err = Some(e), - } - } - - if !succ { - if let Some(e) = err.take() { - Err(e) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Can not bind to address.", - )) - } + let flags = if !self.no_http2 { + ServerFlags::HTTP1 } else { - Ok(sockets) - } + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; + + self.listen_with(lst, RustlsAcceptor::with_flags(builder, flags)) } /// The socket address to bind /// /// To bind multiple addresses this method can be called multiple times. pub fn bind(mut self, addr: S) -> io::Result { - let sockets = self.bind2(addr)?; - self.sockets.extend(sockets); + self.factory.bind(addr)?; + Ok(self) + } + + /// Start listening for incoming connections with supplied acceptor. + #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] + pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result + where + S: net::ToSocketAddrs, + A: AcceptorService + Send + 'static, + { + self.factory.bind_with(addr, &acceptor)?; Ok(self) } #[cfg(feature = "tls")] + #[doc(hidden)] + #[deprecated( + since = "0.7.4", + note = "please use `actix_web::HttpServer::bind_with()` and `actix_web::server::NativeTlsAcceptor` instead" + )] /// The ssl socket address to bind /// /// To bind multiple addresses this method can be called multiple times. pub fn bind_tls( - mut self, addr: S, acceptor: TlsAcceptor, + self, addr: S, acceptor: TlsAcceptor, ) -> io::Result { - let sockets = self.bind2(addr)?; - self.sockets.extend(sockets.into_iter().map(|mut s| { - s.tp = StreamHandlerType::Tls(acceptor.clone()); - s - })); - Ok(self) + use super::NativeTlsAcceptor; + + self.bind_with(addr, NativeTlsAcceptor::new(acceptor)) } #[cfg(feature = "alpn")] + #[doc(hidden)] + #[deprecated( + since = "0.7.4", + note = "please use `actix_web::HttpServer::bind_with()` and `actix_web::server::OpensslAcceptor` instead" + )] /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_ssl( - mut self, addr: S, mut builder: SslAcceptorBuilder, - ) -> io::Result { - // alpn support - if !self.no_http2 { - configure_alpn(&mut builder)?; - } + pub fn bind_ssl(self, addr: S, builder: SslAcceptorBuilder) -> io::Result + where + S: net::ToSocketAddrs, + { + use super::{OpensslAcceptor, ServerFlags}; - let acceptor = builder.build(); - let sockets = self.bind2(addr)?; - self.sockets.extend(sockets.into_iter().map(|mut s| { - s.tp = StreamHandlerType::Alpn(acceptor.clone()); - s - })); - Ok(self) + // alpn support + let flags = if !self.no_http2 { + ServerFlags::HTTP1 + } else { + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; + + self.bind_with(addr, OpensslAcceptor::with_flags(builder, flags)?) } #[cfg(feature = "rust-tls")] + #[doc(hidden)] + #[deprecated( + since = "0.7.4", + note = "please use `actix_web::HttpServer::bind_with()` and `actix_web::server::RustlsAcceptor` instead" + )] /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn bind_rustls( - mut self, addr: S, mut builder: ServerConfig, + self, addr: S, builder: ServerConfig, ) -> io::Result { - // alpn support - if !self.no_http2 { - builder.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]); - } + use super::{RustlsAcceptor, ServerFlags}; - let builder = Arc::new(builder); - let sockets = self.bind2(addr)?; - self.sockets.extend(sockets.into_iter().map(move |mut s| { - s.tp = StreamHandlerType::Rustls(builder.clone()); - s - })); - Ok(self) + // alpn support + let flags = if !self.no_http2 { + ServerFlags::HTTP1 + } else { + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; + + self.bind_with(addr, RustlsAcceptor::with_flags(builder, flags)) } fn start_workers(&mut self, notify: &AcceptNotify) -> Vec { // start workers let mut workers = Vec::new(); for idx in 0..self.threads { - let (worker, addr) = self.pool.start(idx, notify.clone()); + let (worker, addr) = self.factory.start(idx, notify.clone()); workers.push(worker); self.workers.push((idx, addr)); } @@ -453,23 +425,18 @@ impl HttpServer { /// } /// ``` pub fn start(mut self) -> Addr { - if self.sockets.is_empty() { + let sockets = self.factory.take_sockets(); + if sockets.is_empty() { panic!("HttpServer::bind() has to be called before start()"); } else { - let mut addrs: Vec<(usize, Socket)> = Vec::new(); - - for socket in self.sockets.drain(..) { - let token = self.pool.insert(socket.addr, socket.tp.clone()); - addrs.push((token, socket)); - } let notify = self.accept.get_notify(); let workers = self.start_workers(¬ify); // start accept thread - for (_, sock) in &addrs { + for sock in &sockets { info!("Starting server on http://{}", sock.addr); } - let rx = self.accept.start(addrs, workers.clone()); + let rx = self.accept.start(sockets, workers.clone()); // start http server actor let signals = self.subscribe_to_signals(); @@ -511,64 +478,6 @@ impl HttpServer { } } -#[doc(hidden)] -#[cfg(feature = "tls")] -#[deprecated( - since = "0.6.0", - note = "please use `actix_web::HttpServer::bind_tls` instead" -)] -impl HttpServer { - /// Start listening for incoming tls connections. - pub fn start_tls(mut self, acceptor: TlsAcceptor) -> io::Result> { - for sock in &mut self.sockets { - match sock.tp { - StreamHandlerType::Normal => (), - _ => continue, - } - sock.tp = StreamHandlerType::Tls(acceptor.clone()); - } - Ok(self.start()) - } -} - -#[doc(hidden)] -#[cfg(feature = "alpn")] -#[deprecated( - since = "0.6.0", - note = "please use `actix_web::HttpServer::bind_ssl` instead" -)] -impl HttpServer { - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn start_ssl( - mut self, mut builder: SslAcceptorBuilder, - ) -> io::Result> { - // alpn support - if !self.no_http2 { - builder.set_alpn_protos(b"\x02h2\x08http/1.1")?; - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - } - - let acceptor = builder.build(); - for sock in &mut self.sockets { - match sock.tp { - StreamHandlerType::Normal => (), - _ => continue, - } - sock.tp = StreamHandlerType::Alpn(acceptor.clone()); - } - Ok(self.start()) - } -} - impl HttpServer { /// Start listening for incoming connections from a stream. /// @@ -580,14 +489,14 @@ impl HttpServer { { // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let settings = ServerSettings::new(Some(addr), &self.pool.host, secure); - let apps: Vec<_> = (*self.pool.factory)() + let settings = ServerSettings::new(Some(addr), &self.factory.host, secure); + let apps: Vec<_> = (*self.factory.factory)() .into_iter() .map(|h| h.into_handler()) .collect(); - self.h = Some(Rc::new(WorkerSettings::new( + self.settings = Some(Rc::new(WorkerSettings::new( apps, - self.pool.keep_alive, + self.factory.keep_alive, settings, AcceptNotify::default(), Arc::new(AtomicUsize::new(0)), @@ -599,7 +508,7 @@ impl HttpServer { let addr = HttpServer::create(move |ctx| { ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn { io: WrapperStream::new(t), - token: 0, + token: Token::new(0), peer: None, http2: false, })); @@ -672,7 +581,7 @@ impl StreamHandler for HttpServer { } let (worker, addr) = - self.pool.start(new_idx, self.accept.get_notify()); + self.factory.start(new_idx, self.accept.get_notify()); self.workers.push((new_idx, addr)); self.accept.send(Command::Worker(worker)); } @@ -690,7 +599,7 @@ where fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { Arbiter::spawn(HttpChannel::new( - Rc::clone(self.h.as_ref().unwrap()), + Rc::clone(self.settings.as_ref().unwrap()), msg.io, msg.peer, msg.http2, @@ -766,15 +675,3 @@ impl Handler for HttpServer { } } } - -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.reuse_address(true)?; - builder.bind(addr)?; - Ok(builder.listen(backlog)?) -} diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs new file mode 100644 index 00000000..d99c4a58 --- /dev/null +++ b/src/server/ssl/mod.rs @@ -0,0 +1,14 @@ +#[cfg(feature = "alpn")] +mod openssl; +#[cfg(feature = "alpn")] +pub use self::openssl::OpensslAcceptor; + +#[cfg(feature = "tls")] +mod nativetls; +#[cfg(feature = "tls")] +pub use self::nativetls::NativeTlsAcceptor; + +#[cfg(feature = "rust-tls")] +mod rustls; +#[cfg(feature = "rust-tls")] +pub use self::rustls::RustlsAcceptor; diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs new file mode 100644 index 00000000..8749599e --- /dev/null +++ b/src/server/ssl/nativetls.rs @@ -0,0 +1,67 @@ +use std::net::Shutdown; +use std::{io, time}; + +use futures::{Future, Poll}; +use native_tls::TlsAcceptor; +use tokio_tls::{AcceptAsync, TlsAcceptorExt, TlsStream}; + +use server::{AcceptorService, IoStream}; + +#[derive(Clone)] +/// Support `SSL` connections via native-tls package +/// +/// `tls` feature enables `NativeTlsAcceptor` type +pub struct NativeTlsAcceptor { + acceptor: TlsAcceptor, +} + +impl NativeTlsAcceptor { + /// Create `NativeTlsAcceptor` instance + pub fn new(acceptor: TlsAcceptor) -> Self { + NativeTlsAcceptor { acceptor } + } +} + +pub struct AcceptorFut(AcceptAsync); + +impl Future for AcceptorFut { + type Item = TlsStream; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + self.0 + .poll() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } +} + +impl AcceptorService for NativeTlsAcceptor { + type Accepted = TlsStream; + type Future = AcceptorFut; + + fn scheme(&self) -> &'static str { + "https" + } + + fn accept(&self, io: Io) -> Self::Future { + AcceptorFut(TlsAcceptorExt::accept_async(&self.acceptor, io)) + } +} + +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/server/ssl/openssl.rs b/src/server/ssl/openssl.rs new file mode 100644 index 00000000..996c510d --- /dev/null +++ b/src/server/ssl/openssl.rs @@ -0,0 +1,96 @@ +use std::net::Shutdown; +use std::{io, time}; + +use futures::{Future, Poll}; +use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; +use tokio_openssl::{AcceptAsync, SslAcceptorExt, SslStream}; + +use server::{AcceptorService, IoStream, ServerFlags}; + +#[derive(Clone)] +/// Support `SSL` connections via openssl package +/// +/// `alpn` feature enables `OpensslAcceptor` type +pub struct OpensslAcceptor { + acceptor: SslAcceptor, +} + +impl OpensslAcceptor { + /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. + pub fn new(builder: SslAcceptorBuilder) -> io::Result { + OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) + } + + /// Create `OpensslAcceptor` with custom server flags. + pub fn with_flags( + mut builder: SslAcceptorBuilder, flags: ServerFlags, + ) -> io::Result { + let mut protos = Vec::new(); + if flags.contains(ServerFlags::HTTP1) { + protos.extend(b"\x08http/1.1"); + } + if flags.contains(ServerFlags::HTTP2) { + protos.extend(b"\x02h2"); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } + }); + } + + if !protos.is_empty() { + builder.set_alpn_protos(&protos)?; + } + + Ok(OpensslAcceptor { + acceptor: builder.build(), + }) + } +} + +pub struct AcceptorFut(AcceptAsync); + +impl Future for AcceptorFut { + type Item = SslStream; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + self.0 + .poll() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } +} + +impl AcceptorService for OpensslAcceptor { + type Accepted = SslStream; + type Future = AcceptorFut; + + fn scheme(&self) -> &'static str { + "https" + } + + fn accept(&self, io: Io) -> Self::Future { + AcceptorFut(SslAcceptorExt::accept_async(&self.acceptor, io)) + } +} + +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) + } +} diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs new file mode 100644 index 00000000..45cb61be --- /dev/null +++ b/src/server/ssl/rustls.rs @@ -0,0 +1,92 @@ +use std::net::Shutdown; +use std::sync::Arc; +use std::{io, time}; + +use rustls::{ClientSession, ServerConfig, ServerSession}; +use tokio_io::AsyncWrite; +use tokio_rustls::{AcceptAsync, ServerConfigExt, TlsStream}; + +use server::{AcceptorService, IoStream, ServerFlags}; + +#[derive(Clone)] +/// Support `SSL` connections via rustls package +/// +/// `rust-tls` feature enables `RustlsAcceptor` type +pub struct RustlsAcceptor { + config: Arc, +} + +impl RustlsAcceptor { + /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. + pub fn new(config: ServerConfig) -> Self { + RustlsAcceptor::with_flags(config, ServerFlags::HTTP1 | ServerFlags::HTTP2) + } + + /// Create `OpensslAcceptor` with custom server flags. + pub fn with_flags(mut config: ServerConfig, flags: ServerFlags) -> Self { + let mut protos = Vec::new(); + if flags.contains(ServerFlags::HTTP1) { + protos.push("http/1.1".to_string()); + } + if flags.contains(ServerFlags::HTTP2) { + protos.push("h2".to_string()); + } + + if !protos.is_empty() { + config.set_protocols(&protos); + } + + RustlsAcceptor { + config: Arc::new(config), + } + } +} + +impl AcceptorService for RustlsAcceptor { + type Accepted = TlsStream; + type Future = AcceptAsync; + + fn scheme(&self) -> &'static str { + "https" + } + + fn accept(&self, io: Io) -> Self::Future { + ServerConfigExt::accept_async(&self.config, io) + } +} + +impl IoStream for TlsStream { + #[inline] + fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { + let _ = ::shutdown(self); + Ok(()) + } + + #[inline] + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { + self.get_mut().0.set_nodelay(nodelay) + } + + #[inline] + fn set_linger(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().0.set_linger(dur) + } +} + +impl IoStream for TlsStream { + #[inline] + fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { + let _ = ::shutdown(self); + Ok(()) + } + + #[inline] + fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { + self.get_mut().0.set_nodelay(nodelay) + } + + #[inline] + fn set_linger(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().0.set_linger(dur) + } +} diff --git a/src/server/worker.rs b/src/server/worker.rs index e9bf4225..3b8f426d 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,102 +1,195 @@ +use std::marker::PhantomData; use std::rc::Rc; use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc}; -use std::{net, time}; +use std::{io, mem, net, time}; use futures::sync::mpsc::{unbounded, SendError, UnboundedSender}; use futures::sync::oneshot; use futures::Future; -use net2::TcpStreamExt; -use slab::Slab; +use net2::{TcpBuilder, TcpStreamExt}; use tokio::executor::current_thread; -use tokio_reactor::Handle; use tokio_tcp::TcpStream; -#[cfg(any(feature = "tls", feature = "alpn", feature = "rust-tls"))] -use futures::future; - -#[cfg(feature = "tls")] -use native_tls::TlsAcceptor; -#[cfg(feature = "tls")] -use tokio_tls::TlsAcceptorExt; - -#[cfg(feature = "alpn")] -use openssl::ssl::SslAcceptor; -#[cfg(feature = "alpn")] -use tokio_openssl::SslAcceptorExt; - -#[cfg(feature = "rust-tls")] -use rustls::{ServerConfig, Session}; -#[cfg(feature = "rust-tls")] -use tokio_rustls::ServerConfigExt; - use actix::msgs::StopArbiter; use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, Message, Response}; use super::accept::AcceptNotify; use super::channel::HttpChannel; use super::settings::{ServerSettings, WorkerSettings}; -use super::{HttpHandler, IntoHttpHandler, KeepAlive}; +use super::{ + AcceptorService, HttpHandler, IntoAsyncIo, IntoHttpHandler, IoStream, KeepAlive, +}; #[derive(Message)] pub(crate) struct Conn { pub io: T, - pub token: usize, + pub token: Token, pub peer: Option, pub http2: bool, } -#[derive(Clone)] -pub(crate) struct SocketInfo { - pub addr: net::SocketAddr, - pub htype: StreamHandlerType, +#[derive(Clone, Copy)] +pub struct Token(usize); + +impl Token { + pub(crate) fn new(val: usize) -> Token { + Token(val) + } } -pub(crate) struct WorkersPool { - sockets: Slab, +pub(crate) struct Socket { + pub lst: net::TcpListener, + pub addr: net::SocketAddr, + pub token: Token, +} + +pub(crate) struct WorkerFactory { pub factory: Arc Vec + Send + Sync>, pub host: Option, pub keep_alive: KeepAlive, + pub backlog: i32, + sockets: Vec, + handlers: Vec>>, } -impl WorkersPool { +impl WorkerFactory { pub fn new(factory: F) -> Self where F: Fn() -> Vec + Send + Sync + 'static, { - WorkersPool { + WorkerFactory { factory: Arc::new(factory), host: None, + backlog: 2048, keep_alive: KeepAlive::Os, - sockets: Slab::new(), + sockets: Vec::new(), + handlers: Vec::new(), } } - pub fn insert(&mut self, addr: net::SocketAddr, htype: StreamHandlerType) -> usize { - let entry = self.sockets.vacant_entry(); - let token = entry.key(); - entry.insert(SocketInfo { addr, htype }); - token + pub fn addrs(&self) -> Vec { + self.sockets.iter().map(|s| s.addr).collect() + } + + pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { + self.handlers + .iter() + .map(|s| (s.addr(), s.scheme())) + .collect() + } + + pub fn take_sockets(&mut self) -> Vec { + mem::replace(&mut self.sockets, Vec::new()) + } + + pub fn listen(&mut self, lst: net::TcpListener) { + let token = Token(self.handlers.len()); + let addr = lst.local_addr().unwrap(); + self.handlers + .push(Box::new(SimpleHandler::new(lst.local_addr().unwrap()))); + self.sockets.push(Socket { lst, addr, token }) + } + + pub fn listen_with(&mut self, lst: net::TcpListener, acceptor: A) + where + A: AcceptorService + Send + 'static, + { + let token = Token(self.handlers.len()); + let addr = lst.local_addr().unwrap(); + self.handlers.push(Box::new(StreamHandler::new( + lst.local_addr().unwrap(), + acceptor, + ))); + self.sockets.push(Socket { lst, addr, token }) + } + + pub fn bind(&mut self, addr: S) -> io::Result<()> + where + S: net::ToSocketAddrs, + { + let sockets = self.bind2(addr)?; + + for lst in sockets { + let token = Token(self.handlers.len()); + let addr = lst.local_addr().unwrap(); + self.handlers + .push(Box::new(SimpleHandler::new(lst.local_addr().unwrap()))); + self.sockets.push(Socket { lst, addr, token }) + } + Ok(()) + } + + pub fn bind_with(&mut self, addr: S, acceptor: &A) -> io::Result<()> + where + S: net::ToSocketAddrs, + A: AcceptorService + Send + 'static, + { + let sockets = self.bind2(addr)?; + + for lst in sockets { + let token = Token(self.handlers.len()); + let addr = lst.local_addr().unwrap(); + self.handlers.push(Box::new(StreamHandler::new( + lst.local_addr().unwrap(), + acceptor.clone(), + ))); + self.sockets.push(Socket { lst, addr, token }) + } + Ok(()) + } + + fn bind2( + &self, addr: S, + ) -> io::Result> { + let mut err = None; + let mut succ = false; + let mut sockets = Vec::new(); + for addr in addr.to_socket_addrs()? { + match create_tcp_listener(addr, self.backlog) { + Ok(lst) => { + succ = true; + sockets.push(lst); + } + Err(e) => err = Some(e), + } + } + + 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) + } } pub fn start( &mut self, idx: usize, notify: AcceptNotify, - ) -> (WorkerClient, Addr>) { + ) -> (WorkerClient, Addr) { let host = self.host.clone(); - let addr = self.sockets[0].addr; + let addr = self.handlers[0].addr(); let factory = Arc::clone(&self.factory); - let socks = self.sockets.clone(); let ka = self.keep_alive; let (tx, rx) = unbounded::>(); - let client = WorkerClient::new(idx, tx, self.sockets.clone()); + let client = WorkerClient::new(idx, tx); let conn = client.conn.clone(); let sslrate = client.sslrate.clone(); + let handlers: Vec<_> = self.handlers.iter().map(|v| v.clone()).collect(); let addr = Arbiter::start(move |ctx: &mut Context<_>| { let s = ServerSettings::new(Some(addr), &host, false); let apps: Vec<_> = (*factory)().into_iter().map(|h| h.into_handler()).collect(); ctx.add_message_stream(rx); - Worker::new(apps, socks, ka, s, conn, sslrate, notify) + let inner = WorkerInner::new(apps, handlers, ka, s, conn, sslrate, notify); + Worker { + inner: Box::new(inner), + } }); (client, addr) @@ -107,19 +200,15 @@ impl WorkersPool { pub(crate) struct WorkerClient { pub idx: usize, tx: UnboundedSender>, - info: Slab, pub conn: Arc, pub sslrate: Arc, } impl WorkerClient { - fn new( - idx: usize, tx: UnboundedSender>, info: Slab, - ) -> Self { + fn new(idx: usize, tx: UnboundedSender>) -> Self { WorkerClient { idx, tx, - info, conn: Arc::new(AtomicUsize::new(0)), sslrate: Arc::new(AtomicUsize::new(0)), } @@ -154,47 +243,30 @@ impl Message for StopWorker { /// /// Worker accepts Socket objects via unbounded channel and start requests /// processing. -pub(crate) struct Worker -where - H: HttpHandler + 'static, -{ - settings: Rc>, - socks: Slab, - tcp_ka: Option, +pub(crate) struct Worker { + inner: Box, } -impl Worker { - pub(crate) fn new( - h: Vec, socks: Slab, keep_alive: KeepAlive, - settings: ServerSettings, conn: Arc, sslrate: Arc, - notify: AcceptNotify, - ) -> Worker { - let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { - Some(time::Duration::new(val as u64, 0)) - } else { - None - }; +impl Actor for Worker { + type Context = Context; - Worker { - settings: Rc::new(WorkerSettings::new( - h, keep_alive, settings, notify, conn, sslrate, - )), - socks, - tcp_ka, - } + fn started(&mut self, ctx: &mut Self::Context) { + self.update_date(ctx); } +} - fn update_time(&self, ctx: &mut Context) { - self.settings.update_date(); - ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx)); +impl Worker { + fn update_date(&self, ctx: &mut Context) { + self.inner.update_date(); + ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_date(ctx)); } fn shutdown_timeout( - &self, ctx: &mut Context, tx: oneshot::Sender, dur: time::Duration, + &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.settings.num_channels(); + let num = slf.inner.num_channels(); if num == 0 { let _ = tx.send(true); Arbiter::current().do_send(StopArbiter(0)); @@ -202,7 +274,7 @@ impl Worker { slf.shutdown_timeout(ctx, tx, d); } else { info!("Force shutdown http worker, {} connections", num); - slf.settings.head().traverse::(); + slf.inner.force_shutdown(); let _ = tx.send(false); Arbiter::current().do_send(StopArbiter(0)); } @@ -210,44 +282,20 @@ impl Worker { } } -impl Actor for Worker -where - H: HttpHandler + 'static, -{ - type Context = Context; - - fn started(&mut self, ctx: &mut Self::Context) { - self.update_time(ctx); - } -} - -impl Handler> for Worker -where - H: HttpHandler + 'static, -{ +impl Handler> for Worker { type Result = (); fn handle(&mut self, msg: Conn, _: &mut Context) { - if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { - error!("Can not set socket keep-alive option"); - } - self.socks - .get_mut(msg.token) - .unwrap() - .htype - .handle(Rc::clone(&self.settings), msg); + self.inner.handle_connect(msg) } } /// `StopWorker` message handler -impl Handler for Worker -where - H: HttpHandler + 'static, -{ +impl Handler for Worker { type Result = Response; fn handle(&mut self, msg: StopWorker, ctx: &mut Context) -> Self::Result { - let num = self.settings.num_channels(); + let num = self.inner.num_channels(); if num == 0 { info!("Shutting down http worker, 0 connections"); Response::reply(Ok(true)) @@ -258,148 +306,242 @@ where Response::async(rx.map_err(|_| ())) } else { info!("Force shutdown http worker, {} connections", num); - self.settings.head().traverse::(); + self.inner.force_shutdown(); Response::reply(Ok(false)) } } } -#[derive(Clone)] -pub(crate) enum StreamHandlerType { - Normal, - #[cfg(feature = "tls")] - Tls(TlsAcceptor), - #[cfg(feature = "alpn")] - Alpn(SslAcceptor), - #[cfg(feature = "rust-tls")] - Rustls(Arc), +trait WorkerHandler { + fn update_date(&self); + + fn handle_connect(&mut self, Conn); + + fn force_shutdown(&self); + + fn num_channels(&self) -> usize; } -impl StreamHandlerType { - pub fn is_ssl(&self) -> bool { - match *self { - StreamHandlerType::Normal => false, - #[cfg(feature = "tls")] - StreamHandlerType::Tls(_) => true, - #[cfg(feature = "alpn")] - StreamHandlerType::Alpn(_) => true, - #[cfg(feature = "rust-tls")] - StreamHandlerType::Rustls(_) => true, - } - } +struct WorkerInner +where + H: HttpHandler + 'static, +{ + settings: Rc>, + socks: Vec>>, + tcp_ka: Option, +} - fn handle( - &mut self, h: Rc>, msg: Conn, - ) { - match *self { - StreamHandlerType::Normal => { - let _ = msg.io.set_nodelay(true); - let io = TcpStream::from_std(msg.io, &Handle::default()) - .expect("failed to associate TCP stream"); +impl WorkerInner { + pub(crate) fn new( + h: Vec, socks: Vec>>, + keep_alive: KeepAlive, settings: ServerSettings, conn: Arc, + sslrate: Arc, notify: AcceptNotify, + ) -> WorkerInner { + let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { + Some(time::Duration::new(val as u64, 0)) + } else { + None + }; - current_thread::spawn(HttpChannel::new(h, io, msg.peer, msg.http2)); - } - #[cfg(feature = "tls")] - StreamHandlerType::Tls(ref acceptor) => { - let Conn { - io, peer, http2, .. - } = msg; - let _ = io.set_nodelay(true); - let io = TcpStream::from_std(io, &Handle::default()) - .expect("failed to associate TCP stream"); - h.ssl_conn_add(); - - current_thread::spawn(TlsAcceptorExt::accept_async(acceptor, io).then( - move |res| { - h.ssl_conn_del(); - match res { - Ok(io) => current_thread::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.set_nodelay(true); - let io = TcpStream::from_std(io, &Handle::default()) - .expect("failed to associate TCP stream"); - h.ssl_conn_add(); - - current_thread::spawn(SslAcceptorExt::accept_async(acceptor, io).then( - move |res| { - h.ssl_conn_del(); - 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 - }; - current_thread::spawn(HttpChannel::new( - h, io, peer, http2, - )); - } - Err(err) => { - trace!("Error during handling tls connection: {}", err) - } - }; - future::result(Ok(())) - }, - )); - } - #[cfg(feature = "rust-tls")] - StreamHandlerType::Rustls(ref acceptor) => { - let Conn { io, peer, .. } = msg; - let _ = io.set_nodelay(true); - let io = TcpStream::from_std(io, &Handle::default()) - .expect("failed to associate TCP stream"); - h.ssl_conn_add(); - - current_thread::spawn(ServerConfigExt::accept_async(acceptor, io).then( - move |res| { - h.ssl_conn_del(); - match res { - Ok(io) => { - let http2 = if let Some(p) = - io.get_ref().1.get_alpn_protocol() - { - p.len() == 2 && &p == &"h2" - } else { - false - }; - current_thread::spawn(HttpChannel::new( - h, io, peer, http2, - )); - } - Err(err) => { - trace!("Error during handling tls connection: {}", err) - } - }; - future::result(Ok(())) - }, - )); - } - } - } - - pub(crate) fn scheme(&self) -> &'static str { - match *self { - StreamHandlerType::Normal => "http", - #[cfg(feature = "tls")] - StreamHandlerType::Tls(_) => "https", - #[cfg(feature = "alpn")] - StreamHandlerType::Alpn(_) => "https", - #[cfg(feature = "rust-tls")] - StreamHandlerType::Rustls(_) => "https", + WorkerInner { + settings: Rc::new(WorkerSettings::new( + h, keep_alive, settings, notify, conn, sslrate, + )), + socks, + tcp_ka, } } } + +impl WorkerHandler for WorkerInner +where + H: HttpHandler + 'static, +{ + fn update_date(&self) { + self.settings.update_date(); + } + + fn handle_connect(&mut self, msg: Conn) { + if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { + error!("Can not set socket keep-alive option"); + } + self.socks[msg.token.0].handle(Rc::clone(&self.settings), msg.io, msg.peer); + } + + fn num_channels(&self) -> usize { + self.settings.num_channels() + } + + fn force_shutdown(&self) { + self.settings.head().traverse::(); + } +} + +struct SimpleHandler { + addr: net::SocketAddr, + io: PhantomData, +} + +impl Clone for SimpleHandler { + fn clone(&self) -> Self { + SimpleHandler { + addr: self.addr, + io: PhantomData, + } + } +} + +impl SimpleHandler { + fn new(addr: net::SocketAddr) -> Self { + SimpleHandler { + addr, + io: PhantomData, + } + } +} + +impl IoStreamHandler for SimpleHandler +where + H: HttpHandler, + Io: IntoAsyncIo + Send + 'static, + Io::Io: IoStream, +{ + fn addr(&self) -> net::SocketAddr { + self.addr + } + + fn clone(&self) -> Box> { + Box::new(Clone::clone(self)) + } + + fn scheme(&self) -> &'static str { + "http" + } + + fn handle(&self, h: Rc>, io: Io, peer: Option) { + let mut io = match io.into_async_io() { + Ok(io) => io, + Err(err) => { + trace!("Failed to create async io: {}", err); + return; + } + }; + let _ = io.set_nodelay(true); + + current_thread::spawn(HttpChannel::new(h, io, peer, false)); + } +} + +struct StreamHandler { + acceptor: A, + addr: net::SocketAddr, + io: PhantomData, +} + +impl> StreamHandler { + fn new(addr: net::SocketAddr, acceptor: A) -> Self { + StreamHandler { + addr, + acceptor, + io: PhantomData, + } + } +} + +impl> Clone for StreamHandler { + fn clone(&self) -> Self { + StreamHandler { + addr: self.addr, + acceptor: self.acceptor.clone(), + io: PhantomData, + } + } +} + +impl IoStreamHandler for StreamHandler +where + H: HttpHandler, + Io: IntoAsyncIo + Send + 'static, + Io::Io: IoStream, + A: AcceptorService + Send + 'static, +{ + fn addr(&self) -> net::SocketAddr { + self.addr + } + + fn clone(&self) -> Box> { + Box::new(Clone::clone(self)) + } + + fn scheme(&self) -> &'static str { + self.acceptor.scheme() + } + + fn handle(&self, h: Rc>, io: Io, peer: Option) { + let mut io = match io.into_async_io() { + Ok(io) => io, + Err(err) => { + trace!("Failed to create async io: {}", err); + return; + } + }; + let _ = io.set_nodelay(true); + + h.conn_rate_add(); + current_thread::spawn(self.acceptor.accept(io).then(move |res| { + h.conn_rate_del(); + match res { + Ok(io) => current_thread::spawn(HttpChannel::new(h, io, peer, false)), + Err(err) => trace!("Can not establish connection: {}", err), + } + Ok(()) + })) + } +} + +impl IoStreamHandler for Box> +where + H: HttpHandler, + Io: IntoAsyncIo, +{ + fn addr(&self) -> net::SocketAddr { + self.as_ref().addr() + } + + fn clone(&self) -> Box> { + self.as_ref().clone() + } + + fn scheme(&self) -> &'static str { + self.as_ref().scheme() + } + + fn handle(&self, h: Rc>, io: Io, peer: Option) { + self.as_ref().handle(h, io, peer) + } +} + +pub(crate) trait IoStreamHandler: Send +where + H: HttpHandler, +{ + fn clone(&self) -> Box>; + + fn addr(&self) -> net::SocketAddr; + + fn scheme(&self) -> &'static str; + + fn handle(&self, h: Rc>, io: Io, peer: Option); +} + +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.reuse_address(true)?; + builder.bind(addr)?; + Ok(builder.listen(backlog)?) +} From e34b5c08ba280f2a8318b2ed607309e41cb9f4d6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 3 Aug 2018 19:24:53 -0700 Subject: [PATCH 0567/1635] allow to pass extra information from acceptor to application level --- src/server/h1.rs | 4 ++++ src/server/h2.rs | 11 +++++++++-- src/server/message.rs | 8 ++++++++ src/server/mod.rs | 7 +++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 085cea00..2c07f0cf 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -368,6 +368,10 @@ where self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); } + // stream extensions + msg.inner_mut().stream_extensions = + self.stream.get_mut().extensions(); + // set remote addr msg.inner_mut().addr = self.addr; diff --git a/src/server/h2.rs b/src/server/h2.rs index cb5367c5..9f072502 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -14,6 +14,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use error::{Error, PayloadError}; +use extensions::Extensions; use http::{StatusCode, Version}; use payload::{Payload, PayloadStatus, PayloadWriter}; use uri::Url; @@ -22,7 +23,7 @@ use super::error::ServerError; use super::h2writer::H2Writer; use super::input::PayloadType; use super::settings::WorkerSettings; -use super::{HttpHandler, HttpHandlerTask, Writer}; +use super::{HttpHandler, HttpHandlerTask, IoStream, Writer}; bitflags! { struct Flags: u8 { @@ -42,6 +43,7 @@ where state: State>, tasks: VecDeque>, keepalive_timer: Option, + extensions: Option>, } enum State { @@ -52,12 +54,13 @@ enum State { impl Http2 where - T: AsyncRead + AsyncWrite + 'static, + T: IoStream + 'static, H: HttpHandler + 'static, { pub fn new( settings: Rc>, io: T, addr: Option, buf: Bytes, ) -> Self { + let extensions = io.extensions(); Http2 { flags: Flags::empty(), tasks: VecDeque::new(), @@ -68,6 +71,7 @@ where keepalive_timer: None, addr, settings, + extensions, } } @@ -206,6 +210,7 @@ where resp, self.addr, &self.settings, + self.extensions.clone(), )); } Ok(Async::NotReady) => { @@ -324,6 +329,7 @@ impl Entry { fn new( parts: Parts, recv: RecvStream, resp: SendResponse, addr: Option, settings: &Rc>, + extensions: Option>, ) -> Entry where H: HttpHandler + 'static, @@ -338,6 +344,7 @@ impl Entry { inner.method = parts.method; inner.version = parts.version; inner.headers = parts.headers; + inner.stream_extensions = extensions; *inner.payload.borrow_mut() = Some(payload); inner.addr = addr; } diff --git a/src/server/message.rs b/src/server/message.rs index 395d7b7c..43f7e142 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -35,6 +35,7 @@ pub(crate) struct InnerRequest { pub(crate) info: RefCell, pub(crate) payload: RefCell>, pub(crate) settings: ServerSettings, + pub(crate) stream_extensions: Option>, pool: &'static RequestPool, } @@ -82,6 +83,7 @@ impl Request { info: RefCell::new(ConnectionInfo::default()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), + stream_extensions: None, }), } } @@ -189,6 +191,12 @@ impl Request { } } + /// Io stream extensions + #[inline] + pub fn stream_extensions(&self) -> Option<&Extensions> { + self.inner().stream_extensions.as_ref().map(|e| e.as_ref()) + } + /// Server settings #[inline] pub fn server_settings(&self) -> &ServerSettings { diff --git a/src/server/mod.rs b/src/server/mod.rs index 55de25db..baf00492 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,6 @@ //! Http server use std::net::Shutdown; +use std::rc::Rc; use std::{io, net, time}; use bytes::{BufMut, BytesMut}; @@ -36,6 +37,7 @@ pub use self::helpers::write_content_length; use actix::Message; use body::Binary; use error::Error; +use extensions::Extensions; use header::ContentEncoding; use httpresponse::HttpResponse; @@ -287,6 +289,11 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { } } } + + /// Extra io stream extensions + fn extensions(&self) -> Option> { + None + } } impl IoStream for TcpStream { From ac9180ac465443370b6893841c1ce84497d936e3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 3 Aug 2018 19:32:46 -0700 Subject: [PATCH 0568/1635] simplify channel impl --- src/server/accept.rs | 1 - src/server/channel.rs | 34 ++++++++++------------------------ src/server/srv.rs | 2 -- src/server/worker.rs | 5 ++--- 4 files changed, 12 insertions(+), 30 deletions(-) diff --git a/src/server/accept.rs b/src/server/accept.rs index e837852d..61bc72fb 100644 --- a/src/server/accept.rs +++ b/src/server/accept.rs @@ -470,7 +470,6 @@ impl Accept { io, token: info.token, peer: Some(addr), - http2: false, }, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return, Err(ref e) if connection_error(e) => continue, diff --git a/src/server/channel.rs b/src/server/channel.rs index b817b416..c158f66b 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -2,7 +2,7 @@ use std::net::{Shutdown, SocketAddr}; use std::rc::Rc; use std::{io, ptr, time}; -use bytes::{Buf, BufMut, Bytes, BytesMut}; +use bytes::{Buf, BufMut, BytesMut}; use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -38,32 +38,18 @@ where H: HttpHandler + 'static, { pub(crate) fn new( - settings: Rc>, mut io: T, peer: Option, - http2: bool, + settings: Rc>, io: T, peer: Option, ) -> HttpChannel { settings.add_channel(); - let _ = io.set_nodelay(true); - if http2 { - HttpChannel { - node: None, - proto: Some(HttpProtocol::H2(h2::Http2::new( - settings, - io, - peer, - Bytes::new(), - ))), - } - } else { - HttpChannel { - node: None, - proto: Some(HttpProtocol::Unknown( - settings, - peer, - io, - BytesMut::with_capacity(8192), - )), - } + HttpChannel { + node: None, + proto: Some(HttpProtocol::Unknown( + settings, + peer, + io, + BytesMut::with_capacity(8192), + )), } } diff --git a/src/server/srv.rs b/src/server/srv.rs index 33c820aa..7e50e12b 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -510,7 +510,6 @@ impl HttpServer { io: WrapperStream::new(t), token: Token::new(0), peer: None, - http2: false, })); self }); @@ -602,7 +601,6 @@ where Rc::clone(self.settings.as_ref().unwrap()), msg.io, msg.peer, - msg.http2, )); } } diff --git a/src/server/worker.rs b/src/server/worker.rs index 3b8f426d..168382e6 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -25,7 +25,6 @@ pub(crate) struct Conn { pub io: T, pub token: Token, pub peer: Option, - pub http2: bool, } #[derive(Clone, Copy)] @@ -428,7 +427,7 @@ where }; let _ = io.set_nodelay(true); - current_thread::spawn(HttpChannel::new(h, io, peer, false)); + current_thread::spawn(HttpChannel::new(h, io, peer)); } } @@ -491,7 +490,7 @@ where current_thread::spawn(self.acceptor.accept(io).then(move |res| { h.conn_rate_del(); match res { - Ok(io) => current_thread::spawn(HttpChannel::new(h, io, peer, false)), + Ok(io) => current_thread::spawn(HttpChannel::new(h, io, peer)), Err(err) => trace!("Can not establish connection: {}", err), } Ok(()) From 84b27db218549df6fdee47b89b975eaaac6a4584 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 3 Aug 2018 19:40:43 -0700 Subject: [PATCH 0569/1635] fix no_http2 flag --- src/server/srv.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/srv.rs b/src/server/srv.rs index 7e50e12b..17d84998 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -254,7 +254,7 @@ where use super::{OpensslAcceptor, ServerFlags}; // alpn support - let flags = if !self.no_http2 { + let flags = if self.no_http2 { ServerFlags::HTTP1 } else { ServerFlags::HTTP1 | ServerFlags::HTTP2 @@ -278,7 +278,7 @@ where use super::{RustlsAcceptor, ServerFlags}; // alpn support - let flags = if !self.no_http2 { + let flags = if self.no_http2 { ServerFlags::HTTP1 } else { ServerFlags::HTTP1 | ServerFlags::HTTP2 From 900fd5a98e7bd1988dd7d8a504ccc31cc0fd4354 Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 4 Aug 2018 01:34:23 +0300 Subject: [PATCH 0570/1635] Correct settings headers for HTTP2 Add test to verify number of Set-Cookies --- src/server/h2writer.rs | 6 +++--- tests/test_server.rs | 44 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index ff87b693..511929fa 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -112,7 +112,7 @@ impl Writer for H2Writer { DATE => has_date = true, _ => (), } - resp.headers_mut().insert(key, value.clone()); + resp.headers_mut().append(key, value.clone()); } // set date header @@ -151,6 +151,8 @@ impl Writer for H2Writer { .insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap()); } + trace!("Response: {:?}", resp); + match self .respond .send_response(resp, self.flags.contains(Flags::EOF)) @@ -159,8 +161,6 @@ impl Writer for H2Writer { Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), } - trace!("Response: {:?}", msg); - let body = msg.replace_body(Body::Empty); if let Body::Binary(bytes) = body { if bytes.is_empty() { diff --git a/tests/test_server.rs b/tests/test_server.rs index 4db73a3b..5c438568 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -931,3 +931,47 @@ fn test_application() { let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); } + +#[test] +fn test_server_cookies() { + use actix_web::http; + + let mut srv = test::TestServer::with_factory(|| { + App::new().resource("/", |r| r.f(|_| HttpResponse::Ok().cookie(http::CookieBuilder::new("first", "first_value").http_only(true).finish()) + .cookie(http::Cookie::new("second", "first_value")) + .cookie(http::Cookie::new("second", "second_value")) + .finish()) + ) + }); + + let first_cookie = http::CookieBuilder::new("first", "first_value").http_only(true).finish(); + let second_cookie = http::Cookie::new("second", "second_value"); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + let cookies = response.cookies().expect("To have cookies"); + assert_eq!(cookies.len(), 2); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } + + let first_cookie = first_cookie.to_string(); + let second_cookie = second_cookie.to_string(); + //Check that we have exactly two instances of raw cookie headers + let cookies = response.headers().get_all(http::header::SET_COOKIE) + .iter() + .map(|header| header.to_str().expect("To str").to_string()) + .collect::>(); + assert_eq!(cookies.len(), 2); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } +} From 85e7548088b9cc6b7782b38ceef63b35500fbf32 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 4 Aug 2018 08:56:33 -0700 Subject: [PATCH 0571/1635] fix adding multiple response headers for http/2 #446 --- CHANGES.md | 2 ++ src/server/h2writer.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4d1610c0..714e6b67 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,8 @@ * Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436 +* Fix adding multiple response headers #446 + ## [0.7.3] - 2018-08-01 diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index ff87b693..05bf4519 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -112,7 +112,7 @@ impl Writer for H2Writer { DATE => has_date = true, _ => (), } - resp.headers_mut().insert(key, value.clone()); + resp.headers_mut().append(key, value.clone()); } // set date header @@ -159,7 +159,7 @@ impl Writer for H2Writer { Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), } - trace!("Response: {:?}", msg); + trace!("HttpResponse: {:?}", msg); let body = msg.replace_body(Body::Empty); if let Body::Binary(bytes) = body { From 954f1a0b0fba127b1a68131cea22cbdb22a6ec0c Mon Sep 17 00:00:00 2001 From: Erik Desjardins Date: Mon, 6 Aug 2018 03:44:08 -0400 Subject: [PATCH 0572/1635] impl FromRequest for () (#449) --- src/extractor.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/extractor.rs b/src/extractor.rs index 5c2c7f60..312287e0 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -690,6 +690,12 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { } }); +impl FromRequest for () { + type Config = (); + type Result = Self; + fn from_request(_req: &HttpRequest, _cfg: &Self::Config) -> Self::Result {} +} + tuple_from_req!(TupleFromRequest1, (0, A)); tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); @@ -1006,5 +1012,7 @@ mod tests { assert_eq!((res.0).1, "user1"); assert_eq!((res.1).0, "name"); assert_eq!((res.1).1, "user1"); + + let () = <()>::extract(&req); } } From 9c80d3aa77a036f2b4b8ec6332d19940120f633b Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 7 Aug 2018 10:01:29 +0300 Subject: [PATCH 0573/1635] Write non-80 port in HOST of client's request (#451) --- CHANGES.md | 3 ++- src/client/request.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 714e6b67..eff8d4cf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,7 @@ * Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`, accept backpressure #250 -* Allow to customize connection handshake process via `HttpServer::listen_with()` +* Allow to customize connection handshake process via `HttpServer::listen_with()` and `HttpServer::bind_with()` methods ### Fixed @@ -19,6 +19,7 @@ * Fix adding multiple response headers #446 +* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448 ## [0.7.3] - 2018-08-01 diff --git a/src/client/request.rs b/src/client/request.rs index 4d506c3f..aff4ab48 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -629,7 +629,14 @@ impl ClientRequestBuilder { if let Some(parts) = parts(&mut self.request, &self.err) { if let Some(host) = parts.uri.host() { if !parts.headers.contains_key(header::HOST) { - match host.try_into() { + let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); + + let _ = match parts.uri.port() { + None | Some(80) | Some(443) => write!(wrt, "{}", host), + Some(port) => write!(wrt, "{}:{}", host, port), + }; + + match wrt.get_mut().take().freeze().try_into() { Ok(value) => { parts.headers.insert(header::HOST, value); } From 86a5afb5ca6eb0ad41b105e30e604a4c1ea169f7 Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 7 Aug 2018 17:33:49 +0300 Subject: [PATCH 0574/1635] Reserve enough space for ServerError task to write status line --- src/server/error.rs | 3 +++ src/server/helpers.rs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/server/error.rs b/src/server/error.rs index b3c79a06..4c264bc1 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -16,6 +16,9 @@ impl HttpHandlerTask for ServerError { fn poll_io(&mut self, io: &mut Writer) -> Poll { { let bytes = io.buffer(); + //Buffer should have sufficient capacity for status line + //and extra space + bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1); helpers::write_status_line(self.0, self.1.as_u16(), bytes); } io.set_date(); diff --git a/src/server/helpers.rs b/src/server/helpers.rs index 03bbc831..f7e030f2 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -8,8 +8,10 @@ const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 6061626364656667686970717273747576777879\ 8081828384858687888990919293949596979899"; +pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13; + pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; 13] = [ + let mut buf: [u8; STATUS_LINE_BUF_SIZE] = [ b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ', ]; match version { From 58a079bd10808d8d5be183b12a8c0fe74cd73bf1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 7 Aug 2018 11:56:39 -0700 Subject: [PATCH 0575/1635] include content-length to error response --- src/server/error.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/server/error.rs b/src/server/error.rs index 4c264bc1..5bd0bf83 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -16,11 +16,12 @@ impl HttpHandlerTask for ServerError { fn poll_io(&mut self, io: &mut Writer) -> Poll { { let bytes = io.buffer(); - //Buffer should have sufficient capacity for status line - //and extra space + // Buffer should have sufficient capacity for status line + // and extra space bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1); helpers::write_status_line(self.0, self.1.as_u16(), bytes); } + io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n"); io.set_date(); Ok(Async::Ready(true)) } From 5bd82d4f03696701eafa2abd3f2eb454d2dad62c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 7 Aug 2018 12:00:51 -0700 Subject: [PATCH 0576/1635] update changes --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index eff8d4cf..7c69161d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,9 @@ * Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448 +* Panic during access without routing being set #452 + + ## [0.7.3] - 2018-08-01 ### Added From 85acc3f8df0eefb430dda366c63421f88a2cb6eb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 7 Aug 2018 12:49:40 -0700 Subject: [PATCH 0577/1635] deprecate HttpServer::no_http2(), update changes --- CHANGES.md | 11 +++++++++++ src/server/srv.rs | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 7c69161d..b9ee0470 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,17 @@ * Panic during access without routing being set #452 +### Deprecated + +* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or + `RustlsAcceptor::with_flags()` instead + +* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been + deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`. + +* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been + deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`. + ## [0.7.3] - 2018-08-01 diff --git a/src/server/srv.rs b/src/server/srv.rs index 17d84998..c2bb6c81 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -181,6 +181,8 @@ where } /// Disable `HTTP/2` support + #[doc(hidden)] + #[deprecated(since = "0.7.4", note = "please use acceptor service with proper ServerFlags parama")] pub fn no_http2(mut self) -> Self { self.no_http2 = true; self @@ -655,6 +657,7 @@ impl Handler for HttpServer { }); } } + fut::ok(()) }), ); @@ -672,4 +675,4 @@ impl Handler for HttpServer { Response::reply(Ok(())) } } -} +} \ No newline at end of file From 57f991280cf1ee0ae4d7d78b9e1072f9d3d1eee9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 7 Aug 2018 13:53:24 -0700 Subject: [PATCH 0578/1635] fix protocol order for rustls acceptor --- src/server/ssl/rustls.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs index 45cb61be..6ad0a7b2 100644 --- a/src/server/ssl/rustls.rs +++ b/src/server/ssl/rustls.rs @@ -25,13 +25,12 @@ impl RustlsAcceptor { /// Create `OpensslAcceptor` with custom server flags. pub fn with_flags(mut config: ServerConfig, flags: ServerFlags) -> Self { let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP1) { - protos.push("http/1.1".to_string()); - } if flags.contains(ServerFlags::HTTP2) { protos.push("h2".to_string()); } - + if flags.contains(ServerFlags::HTTP1) { + protos.push("http/1.1".to_string()); + } if !protos.is_empty() { config.set_protocols(&protos); } From 30769e3072e96902abdfd457e71ab3aa8b06f56a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 7 Aug 2018 20:48:25 -0700 Subject: [PATCH 0579/1635] fix http/2 error handling --- CHANGES.md | 2 + src/pipeline.rs | 127 ++++++++++++++++++++++++++++------------- src/server/h2.rs | 22 +++++-- src/server/h2writer.rs | 7 +-- src/test.rs | 6 +- tests/test_ws.rs | 10 ++-- 6 files changed, 116 insertions(+), 58 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b9ee0470..bfd86a1a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,8 @@ * Panic during access without routing being set #452 +* Fixed http/2 error handling + ### Deprecated * `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or diff --git a/src/pipeline.rs b/src/pipeline.rs index 7c277a58..ca6e974d 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -42,13 +42,6 @@ enum PipelineState { } impl> PipelineState { - fn is_response(&self) -> bool { - match *self { - PipelineState::Response(_) => true, - _ => false, - } - } - fn poll( &mut self, info: &mut PipelineInfo, mws: &[Box>], ) -> Option> { @@ -58,7 +51,8 @@ impl> PipelineState { PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws), PipelineState::Finishing(ref mut state) => state.poll(info, mws), PipelineState::Completed(ref mut state) => state.poll(info), - PipelineState::Response(_) | PipelineState::None | PipelineState::Error => { + PipelineState::Response(ref mut state) => state.poll(info, mws), + PipelineState::None | PipelineState::Error => { None } } @@ -130,22 +124,20 @@ impl> HttpHandlerTask for Pipeline { let mut state = mem::replace(&mut self.1, PipelineState::None); loop { - if state.is_response() { - if let PipelineState::Response(st) = state { - match st.poll_io(io, &mut self.0, &self.2) { - Ok(state) => { - self.1 = state; - if let Some(error) = self.0.error.take() { - return Err(error); - } else { - return Ok(Async::Ready(self.is_done())); - } - } - Err(state) => { - self.1 = state; - return Ok(Async::NotReady); + if let PipelineState::Response(st) = state { + match st.poll_io(io, &mut self.0, &self.2) { + Ok(state) => { + self.1 = state; + if let Some(error) = self.0.error.take() { + return Err(error); + } else { + return Ok(Async::Ready(self.is_done())); } } + Err(state) => { + self.1 = state; + return Ok(Async::NotReady); + } } } match state { @@ -401,7 +393,7 @@ impl RunMiddlewares { } struct ProcessResponse { - resp: HttpResponse, + resp: Option, iostate: IOState, running: RunningState, drain: Option>, @@ -442,7 +434,7 @@ impl ProcessResponse { #[inline] fn init(resp: HttpResponse) -> PipelineState { PipelineState::Response(ProcessResponse { - resp, + resp: Some(resp), iostate: IOState::Response, running: RunningState::Running, drain: None, @@ -451,6 +443,59 @@ impl ProcessResponse { }) } + fn poll( + &mut self, info: &mut PipelineInfo, mws: &[Box>], + ) -> Option> { + println!("POLL"); + // connection is dead at this point + match mem::replace(&mut self.iostate, IOState::Done) { + IOState::Response => + Some(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())), + IOState::Payload(_) => + Some(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())), + IOState::Actor(mut ctx) => { + if info.disconnected.take().is_some() { + ctx.disconnected(); + } + loop { + match ctx.poll() { + Ok(Async::Ready(Some(vec))) => { + if vec.is_empty() { + continue; + } + for frame in vec { + match frame { + Frame::Chunk(None) => { + info.context = Some(ctx); + return Some(FinishingMiddlewares::init( + info, mws, self.resp.take().unwrap(), + )) + } + Frame::Chunk(Some(_)) => (), + Frame::Drain(fut) => {let _ = fut.send(());}, + } + } + } + Ok(Async::Ready(None)) => + return Some(FinishingMiddlewares::init( + info, mws, self.resp.take().unwrap(), + )), + Ok(Async::NotReady) => { + self.iostate = IOState::Actor(ctx); + return None; + } + Err(err) => { + info.context = Some(ctx); + info.error = Some(err); + return Some(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())); + } + } + } + } + IOState::Done => Some(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())) + } + } + fn poll_io( mut self, io: &mut Writer, info: &mut PipelineInfo, mws: &[Box>], @@ -462,24 +507,24 @@ impl ProcessResponse { let result = match mem::replace(&mut self.iostate, IOState::Done) { IOState::Response => { let encoding = - self.resp.content_encoding().unwrap_or(info.encoding); + self.resp.as_ref().unwrap().content_encoding().unwrap_or(info.encoding); let result = - match io.start(&info.req, &mut self.resp, encoding) { + match io.start(&info.req, self.resp.as_mut().unwrap(), encoding) { Ok(res) => res, Err(err) => { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, mws, self.resp, + info, mws, self.resp.take().unwrap(), )); } }; - if let Some(err) = self.resp.error() { - if self.resp.status().is_server_error() { + if let Some(err) = self.resp.as_ref().unwrap().error() { + if self.resp.as_ref().unwrap().status().is_server_error() { error!( "Error occured during request handling, status: {} {}", - self.resp.status(), err + self.resp.as_ref().unwrap().status(), err ); } else { warn!( @@ -493,7 +538,7 @@ impl ProcessResponse { } // always poll stream or actor for the first time - match self.resp.replace_body(Body::Empty) { + match self.resp.as_mut().unwrap().replace_body(Body::Empty) { Body::Streaming(stream) => { self.iostate = IOState::Payload(stream); continue 'inner; @@ -512,7 +557,7 @@ impl ProcessResponse { if let Err(err) = io.write_eof() { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, mws, self.resp, + info, mws, self.resp.take().unwrap(), )); } break; @@ -523,7 +568,7 @@ impl ProcessResponse { Err(err) => { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, mws, self.resp, + info, mws, self.resp.take().unwrap(), )); } Ok(result) => result, @@ -536,7 +581,7 @@ impl ProcessResponse { Err(err) => { info.error = Some(err); return Ok(FinishingMiddlewares::init( - info, mws, self.resp, + info, mws, self.resp.take().unwrap(), )); } }, @@ -559,7 +604,7 @@ impl ProcessResponse { info.error = Some(err.into()); return Ok( FinishingMiddlewares::init( - info, mws, self.resp, + info, mws, self.resp.take().unwrap(), ), ); } @@ -572,7 +617,7 @@ impl ProcessResponse { info.error = Some(err.into()); return Ok( FinishingMiddlewares::init( - info, mws, self.resp, + info, mws, self.resp.take().unwrap(), ), ); } @@ -598,7 +643,7 @@ impl ProcessResponse { info.context = Some(ctx); info.error = Some(err); return Ok(FinishingMiddlewares::init( - info, mws, self.resp, + info, mws, self.resp.take().unwrap(), )); } } @@ -638,7 +683,7 @@ impl ProcessResponse { info.context = Some(ctx); } info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, mws, self.resp)); + return Ok(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())); } } } @@ -652,11 +697,11 @@ impl ProcessResponse { Ok(_) => (), Err(err) => { info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, mws, self.resp)); + return Ok(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())); } } - self.resp.set_response_size(io.written()); - Ok(FinishingMiddlewares::init(info, mws, self.resp)) + self.resp.as_mut().unwrap().set_response_size(io.written()); + Ok(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())) } _ => Err(PipelineState::Response(self)), } diff --git a/src/server/h2.rs b/src/server/h2.rs index 9f072502..d52dc74f 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -102,13 +102,19 @@ where loop { let mut not_ready = true; + let disconnected = self.flags.contains(Flags::DISCONNECTED); // check in-flight connections for item in &mut self.tasks { // read payload - item.poll_payload(); + if !disconnected { + item.poll_payload(); + } if !item.flags.contains(EntryFlags::EOF) { + if disconnected { + item.flags.insert(EntryFlags::EOF); + } else { let retry = item.payload.need_read() == PayloadStatus::Read; loop { match item.task.poll_io(&mut item.stream) { @@ -141,12 +147,14 @@ where } break; } - } else if !item.flags.contains(EntryFlags::FINISHED) { + } + } + + if item.flags.contains(EntryFlags::EOF) && !item.flags.contains(EntryFlags::FINISHED) { match item.task.poll_completed() { Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => { - not_ready = false; - item.flags.insert(EntryFlags::FINISHED); + item.flags.insert(EntryFlags::FINISHED | EntryFlags::WRITE_DONE); } Err(err) => { item.flags.insert( @@ -161,6 +169,7 @@ where if item.flags.contains(EntryFlags::FINISHED) && !item.flags.contains(EntryFlags::WRITE_DONE) + && !disconnected { match item.stream.poll_completed(false) { Ok(Async::NotReady) => (), @@ -168,7 +177,7 @@ where not_ready = false; item.flags.insert(EntryFlags::WRITE_DONE); } - Err(_err) => { + Err(_) => { item.flags.insert(EntryFlags::ERROR); } } @@ -177,7 +186,7 @@ where // cleanup finished tasks while !self.tasks.is_empty() { - if self.tasks[0].flags.contains(EntryFlags::EOF) + if self.tasks[0].flags.contains(EntryFlags::FINISHED) && self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) || self.tasks[0].flags.contains(EntryFlags::ERROR) { @@ -397,6 +406,7 @@ impl Entry { } Ok(Async::NotReady) => break, Err(err) => { + println!("POLL-PAYLOAD error: {:?}", err); self.payload.set_error(PayloadError::Http2(err)); break; } diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 511929fa..ce61b3ed 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -167,7 +167,6 @@ impl Writer for H2Writer { Ok(WriterState::Done) } else { self.flags.insert(Flags::EOF); - self.written = bytes.len() as u64; self.buffer.write(bytes.as_ref())?; if let Some(ref mut stream) = self.stream { self.flags.insert(Flags::RESERVED); @@ -183,8 +182,6 @@ impl Writer for H2Writer { } fn write(&mut self, payload: &Binary) -> io::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 @@ -253,7 +250,9 @@ impl Writer for H2Writer { return Ok(Async::Ready(())); } } - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), + Err(e) => { + return Err(io::Error::new(io::ErrorKind::Other, e)) + } } } } diff --git a/src/test.rs b/src/test.rs index 244c079a..70de5a16 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,8 +15,10 @@ use tokio::runtime::current_thread::Runtime; #[cfg(feature = "alpn")] use openssl::ssl::SslAcceptorBuilder; -#[cfg(all(feature = "rust-tls"))] +#[cfg(feature = "rust-tls")] use rustls::ServerConfig; +#[cfg(feature = "rust-tls")] +use server::RustlsAcceptor; use application::{App, HttpApplication}; use body::Binary; @@ -342,7 +344,7 @@ impl TestServerBuilder { let ssl = self.rust_ssl.take(); if let Some(ssl) = ssl { let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_rustls(tcp, ssl).unwrap(); + srv = srv.listen_with(tcp, RustlsAcceptor::new(ssl)).unwrap(); } } if !has_ssl { diff --git a/tests/test_ws.rs b/tests/test_ws.rs index aa57faf6..752e88b5 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -210,7 +210,7 @@ impl Ws2 { ctx.drain() .and_then(|_, act, ctx| { act.count += 1; - if act.count != 10_000 { + if act.count != 1_000 { act.send(ctx); } actix::fut::ok(()) @@ -248,7 +248,7 @@ fn test_server_send_text() { }); let (mut reader, _writer) = srv.ws().unwrap(); - for _ in 0..10_000 { + for _ in 0..1_000 { let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; assert_eq!(item, data); @@ -272,7 +272,7 @@ fn test_server_send_bin() { }); let (mut reader, _writer) = srv.ws().unwrap(); - for _ in 0..10_000 { + for _ in 0..1_000 { let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; assert_eq!(item, data); @@ -308,7 +308,7 @@ fn test_ws_server_ssl() { let (mut reader, _writer) = srv.ws().unwrap(); let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { + for _ in 0..1_000 { let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; assert_eq!(item, data); @@ -347,7 +347,7 @@ fn test_ws_server_rust_tls() { let (mut reader, _writer) = srv.ws().unwrap(); let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { + for _ in 0..1_000 { let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; assert_eq!(item, data); From 992f7a11b37d6dbc6a0a3634b5b1fadba573b1c9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 7 Aug 2018 22:40:09 -0700 Subject: [PATCH 0580/1635] remove debug println --- src/pipeline.rs | 1 - src/server/h2.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index ca6e974d..09c5e49d 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -446,7 +446,6 @@ impl ProcessResponse { fn poll( &mut self, info: &mut PipelineInfo, mws: &[Box>], ) -> Option> { - println!("POLL"); // connection is dead at this point match mem::replace(&mut self.iostate, IOState::Done) { IOState::Response => diff --git a/src/server/h2.rs b/src/server/h2.rs index d52dc74f..0835f592 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -406,7 +406,6 @@ impl Entry { } Ok(Async::NotReady) => break, Err(err) => { - println!("POLL-PAYLOAD error: {:?}", err); self.payload.set_error(PayloadError::Http2(err)); break; } From 8eb9eb42479a6812fa1c2f519d8dd18013f7a690 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 8 Aug 2018 09:12:32 -0700 Subject: [PATCH 0581/1635] flush io on complete --- src/server/h1writer.rs | 3 ++- tests/test_ws.rs | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index e8f172f4..8c948471 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -337,9 +337,10 @@ impl Writer for H1Writer { } } if shutdown { + self.stream.poll_flush()?; self.stream.shutdown() } else { - Ok(Async::Ready(())) + self.stream.poll_flush() } } } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 752e88b5..aa57faf6 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -210,7 +210,7 @@ impl Ws2 { ctx.drain() .and_then(|_, act, ctx| { act.count += 1; - if act.count != 1_000 { + if act.count != 10_000 { act.send(ctx); } actix::fut::ok(()) @@ -248,7 +248,7 @@ fn test_server_send_text() { }); let (mut reader, _writer) = srv.ws().unwrap(); - for _ in 0..1_000 { + for _ in 0..10_000 { let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; assert_eq!(item, data); @@ -272,7 +272,7 @@ fn test_server_send_bin() { }); let (mut reader, _writer) = srv.ws().unwrap(); - for _ in 0..1_000 { + for _ in 0..10_000 { let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; assert_eq!(item, data); @@ -308,7 +308,7 @@ fn test_ws_server_ssl() { let (mut reader, _writer) = srv.ws().unwrap(); let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..1_000 { + for _ in 0..10_000 { let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; assert_eq!(item, data); @@ -347,7 +347,7 @@ fn test_ws_server_rust_tls() { let (mut reader, _writer) = srv.ws().unwrap(); let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..1_000 { + for _ in 0..10_000 { let (item, r) = srv.execute(reader.into_future()).unwrap(); reader = r; assert_eq!(item, data); From 7a11c2eac11458293544f9e79daa1771b437131d Mon Sep 17 00:00:00 2001 From: David McNeil Date: Wed, 8 Aug 2018 11:11:15 -0600 Subject: [PATCH 0582/1635] Add json2 HttpResponseBuilder method --- src/httpresponse.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 2673da2a..87bd8c8b 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -650,7 +650,14 @@ impl HttpResponseBuilder { /// /// `HttpResponseBuilder` can not be used after this call. pub fn json(&mut self, value: T) -> HttpResponse { - match serde_json::to_string(&value) { + self.json2(&value) + } + + /// Set a json body and generate `HttpResponse` + /// + /// `HttpResponseBuilder` can not be used after this call. + pub fn json2(&mut self, value: &T) -> HttpResponse { + match serde_json::to_string(value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) { From 7c8dc4c201c88eb480dc0428a2f3430feb404f3a Mon Sep 17 00:00:00 2001 From: David McNeil Date: Wed, 8 Aug 2018 11:58:56 -0600 Subject: [PATCH 0583/1635] Add json2 tests --- src/httpresponse.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 87bd8c8b..7700d352 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -1193,6 +1193,30 @@ mod tests { ); } + #[test] + fn test_json2() { + let resp = HttpResponse::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("application/json")); + assert_eq!( + *resp.body(), + Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) + ); + } + + #[test] + fn test_json2_ct() { + let resp = HttpResponse::build(StatusCode::OK) + .header(CONTENT_TYPE, "text/json") + .json2(&vec!["v1", "v2", "v3"]); + let ct = resp.headers().get(CONTENT_TYPE).unwrap(); + assert_eq!(ct, HeaderValue::from_static("text/json")); + assert_eq!( + *resp.body(), + Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) + ); + } + impl Body { pub(crate) fn bin_ref(&self) -> &Binary { match *self { From 542782f28a6a2c9215f63be27bd13d68a17b5523 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 8 Aug 2018 13:57:13 -0700 Subject: [PATCH 0584/1635] add HttpRequest::drop_state() --- src/httprequest.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/httprequest.rs b/src/httprequest.rs index 6f3bfe13..a21d772e 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -81,6 +81,15 @@ impl HttpRequest { } } + /// Construct new http request with empty state. + pub fn drop_state(&self) -> HttpRequest { + HttpRequest { + Rc::new(()), + req: self.req.as_ref().map(|r| r.clone()), + resource: self.resource.clone(), + } + } + #[inline] /// Construct new http request with new RouteInfo. pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest { From b69774db61cef70ee08a024e1a7383dd93b6eb19 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 8 Aug 2018 14:23:16 -0700 Subject: [PATCH 0585/1635] fix attr name --- src/httprequest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index a21d772e..128dcbf1 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -84,7 +84,7 @@ impl HttpRequest { /// Construct new http request with empty state. pub fn drop_state(&self) -> HttpRequest { HttpRequest { - Rc::new(()), + state: Rc::new(()), req: self.req.as_ref().map(|r| r.clone()), resource: self.resource.clone(), } From cfe4829a56bc523afc7df8ca6314a52c35d01bc2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 8 Aug 2018 16:13:45 -0700 Subject: [PATCH 0586/1635] add TestRequest::execute() helper method --- src/extractor.rs | 6 ++++++ src/router.rs | 13 ------------- src/test.rs | 32 +++++++++++++++++++++++++++----- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 312287e0..233ad6ce 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -101,6 +101,12 @@ impl Path { } } +impl From for Path { + fn from(inner: T) -> Path { + Path{inner} + } +} + impl FromRequest for Path where T: DeserializeOwned, diff --git a/src/router.rs b/src/router.rs index 3d112bf6..ff52eac5 100644 --- a/src/router.rs +++ b/src/router.rs @@ -290,19 +290,6 @@ impl Router { } } - #[cfg(test)] - pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> ResourceInfo { - let mut params = Params::with_url(req.url()); - params.set_tail(prefix); - - ResourceInfo { - params, - prefix: 0, - rmap: self.rmap.clone(), - resource: ResourceId::Default, - } - } - #[cfg(test)] pub(crate) fn default_route_info(&self) -> ResourceInfo { ResourceInfo { diff --git a/src/test.rs b/src/test.rs index 70de5a16..42f51174 100644 --- a/src/test.rs +++ b/src/test.rs @@ -676,8 +676,6 @@ impl TestRequest { /// 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, h: &H) -> Result { let req = self.finish(); let resp = h.handle(&req); @@ -686,7 +684,10 @@ impl TestRequest { Ok(resp) => match resp.into().into() { AsyncResultItem::Ok(resp) => Ok(resp), AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(_) => panic!("Async handler is not supported."), + AsyncResultItem::Future(fut) => { + let mut sys = System::new("test"); + sys.block_on(fut) + } }, Err(err) => Err(err.into()), } @@ -706,8 +707,8 @@ impl TestRequest { let req = self.finish(); let fut = h(req.clone()); - let mut core = Runtime::new().unwrap(); - match core.block_on(fut) { + let mut sys = System::new("test"); + match sys.block_on(fut) { Ok(r) => match r.respond_to(&req) { Ok(reply) => match reply.into().into() { AsyncResultItem::Ok(resp) => Ok(resp), @@ -718,4 +719,25 @@ impl TestRequest { Err(err) => Err(err), } } + + /// This method generates `HttpRequest` instance and executes handler + pub fn execute(self, f: F) -> Result + where F: FnOnce(&HttpRequest) -> R, + R: Responder + 'static, + { + let req = self.finish(); + let resp = f(&req); + + match resp.respond_to(&req) { + Ok(resp) => match resp.into().into() { + AsyncResultItem::Ok(resp) => Ok(resp), + AsyncResultItem::Err(err) => Err(err), + AsyncResultItem::Future(fut) => { + let mut sys = System::new("test"); + sys.block_on(fut) + } + }, + Err(err) => Err(err.into()), + } + } } From e4ce6dfbdf0c6cd25ec5c1723b6611cf456ed8f5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 9 Aug 2018 11:52:32 -0700 Subject: [PATCH 0587/1635] refactor workers management --- src/server/accept.rs | 131 +++----- src/server/channel.rs | 16 +- src/server/h1.rs | 14 +- src/server/mod.rs | 14 + src/server/server.rs | 504 +++++++++++++++++++++++++++++++ src/server/settings.rs | 68 +++-- src/server/srv.rs | 664 +++++++++++++++++++++++++---------------- src/server/worker.rs | 477 +++-------------------------- src/test.rs | 13 +- 9 files changed, 1061 insertions(+), 840 deletions(-) create mode 100644 src/server/server.rs diff --git a/src/server/accept.rs b/src/server/accept.rs index 61bc72fb..d642c40f 100644 --- a/src/server/accept.rs +++ b/src/server/accept.rs @@ -9,8 +9,9 @@ use tokio_timer::Delay; use actix::{msgs::Execute, Arbiter, System}; -use super::srv::ServerCommand; -use super::worker::{Conn, Socket, Token, WorkerClient}; +use super::server::ServerCommand; +use super::worker::{Conn, WorkerClient}; +use super::Token; pub(crate) enum Command { Pause, @@ -22,51 +23,27 @@ pub(crate) enum Command { struct ServerSocketInfo { addr: net::SocketAddr, token: Token, + handler: Token, sock: mio::net::TcpListener, timeout: Option, } #[derive(Clone)] -pub(crate) struct AcceptNotify { - ready: mio::SetReadiness, - maxconn: usize, - maxconn_low: usize, - maxconnrate: usize, - maxconnrate_low: usize, -} +pub(crate) struct AcceptNotify(mio::SetReadiness); impl AcceptNotify { - pub fn new(ready: mio::SetReadiness, maxconn: usize, maxconnrate: usize) -> Self { - let maxconn_low = if maxconn > 10 { maxconn - 10 } else { 0 }; - let maxconnrate_low = if maxconnrate > 10 { - maxconnrate - 10 - } else { - 0 - }; - AcceptNotify { - ready, - maxconn, - maxconn_low, - maxconnrate, - maxconnrate_low, - } + pub(crate) fn new(ready: mio::SetReadiness) -> Self { + AcceptNotify(ready) } - pub fn notify_maxconn(&self, maxconn: usize) { - if maxconn > self.maxconn_low && maxconn <= self.maxconn { - let _ = self.ready.set_readiness(mio::Ready::readable()); - } - } - pub fn notify_maxconnrate(&self, connrate: usize) { - if connrate > self.maxconnrate_low && connrate <= self.maxconnrate { - let _ = self.ready.set_readiness(mio::Ready::readable()); - } + pub(crate) fn notify(&self) { + let _ = self.0.set_readiness(mio::Ready::readable()); } } impl Default for AcceptNotify { fn default() -> Self { - AcceptNotify::new(mio::Registration::new2().1, 0, 0) + AcceptNotify::new(mio::Registration::new2().1) } } @@ -81,8 +58,6 @@ pub(crate) struct AcceptLoop { mpsc::UnboundedSender, mpsc::UnboundedReceiver, )>, - maxconn: usize, - maxconnrate: usize, } impl AcceptLoop { @@ -97,8 +72,6 @@ impl AcceptLoop { cmd_reg: Some(cmd_reg), notify_ready, notify_reg: Some(notify_reg), - maxconn: 102_400, - maxconnrate: 256, rx: Some(rx), srv: Some(mpsc::unbounded()), } @@ -110,19 +83,12 @@ impl AcceptLoop { } pub fn get_notify(&self) -> AcceptNotify { - AcceptNotify::new(self.notify_ready.clone(), self.maxconn, self.maxconnrate) - } - - pub fn maxconn(&mut self, num: usize) { - self.maxconn = num; - } - - pub fn maxconnrate(&mut self, num: usize) { - self.maxconnrate = num; + AcceptNotify::new(self.notify_ready.clone()) } pub(crate) fn start( - &mut self, socks: Vec, workers: Vec, + &mut self, socks: Vec>, + workers: Vec, ) -> mpsc::UnboundedReceiver { let (tx, rx) = self.srv.take().expect("Can not re-use AcceptInfo"); @@ -130,8 +96,6 @@ impl AcceptLoop { self.rx.take().expect("Can not re-use AcceptInfo"), self.cmd_reg.take().expect("Can not re-use AcceptInfo"), self.notify_reg.take().expect("Can not re-use AcceptInfo"), - self.maxconn, - self.maxconnrate, socks, tx, workers, @@ -148,8 +112,6 @@ struct Accept { srv: mpsc::UnboundedSender, timer: (mio::Registration, mio::SetReadiness), next: usize, - maxconn: usize, - maxconnrate: usize, backpressure: bool, } @@ -175,9 +137,8 @@ impl Accept { #![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] pub(crate) fn start( rx: sync_mpsc::Receiver, cmd_reg: mio::Registration, - notify_reg: mio::Registration, maxconn: usize, maxconnrate: usize, - socks: Vec, srv: mpsc::UnboundedSender, - workers: Vec, + notify_reg: mio::Registration, socks: Vec>, + srv: mpsc::UnboundedSender, workers: Vec, ) { let sys = System::current(); @@ -187,8 +148,6 @@ impl Accept { .spawn(move || { System::set_current(sys); let mut accept = Accept::new(rx, socks, workers, srv); - accept.maxconn = maxconn; - accept.maxconnrate = maxconnrate; // Start listening for incoming commands if let Err(err) = accept.poll.register( @@ -215,7 +174,7 @@ impl Accept { } fn new( - rx: sync_mpsc::Receiver, socks: Vec, + rx: sync_mpsc::Receiver, socks: Vec>, workers: Vec, srv: mpsc::UnboundedSender, ) -> Accept { // Create a poll instance @@ -226,29 +185,33 @@ impl Accept { // Start accept let mut sockets = Slab::new(); - for sock in socks { - let server = mio::net::TcpListener::from_std(sock.lst) - .expect("Can not create mio::net::TcpListener"); + for (idx, srv_socks) in socks.into_iter().enumerate() { + for (hnd_token, lst) in srv_socks { + let addr = lst.local_addr().unwrap(); + let server = mio::net::TcpListener::from_std(lst) + .expect("Can not create mio::net::TcpListener"); - let entry = sockets.vacant_entry(); - let token = entry.key(); + let entry = sockets.vacant_entry(); + let token = entry.key(); - // Start listening for incoming connections - if let Err(err) = poll.register( - &server, - mio::Token(token + DELTA), - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { - panic!("Can not register io: {}", err); + // Start listening for incoming connections + if let Err(err) = poll.register( + &server, + mio::Token(token + DELTA), + mio::Ready::readable(), + mio::PollOpt::edge(), + ) { + panic!("Can not register io: {}", err); + } + + entry.insert(ServerSocketInfo { + addr, + token: hnd_token, + handler: Token(idx), + sock: server, + timeout: None, + }); } - - entry.insert(ServerSocketInfo { - token: sock.token, - addr: sock.addr, - sock: server, - timeout: None, - }); } // Timer @@ -267,8 +230,6 @@ impl Accept { srv, next: 0, timer: (tm, tmr), - maxconn: 102_400, - maxconnrate: 256, backpressure: false, } } @@ -431,7 +392,7 @@ impl Accept { let mut idx = 0; while idx < self.workers.len() { idx += 1; - if self.workers[self.next].available(self.maxconn, self.maxconnrate) { + if self.workers[self.next].available() { match self.workers[self.next].send(msg) { Ok(_) => { self.next = (self.next + 1) % self.workers.len(); @@ -469,6 +430,7 @@ impl Accept { Ok((io, addr)) => Conn { io, token: info.token, + handler: info.handler, peer: Some(addr), }, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return, @@ -489,11 +451,10 @@ impl Accept { Delay::new( Instant::now() + Duration::from_millis(510), ).map_err(|_| ()) - .and_then(move |_| { - let _ = - r.set_readiness(mio::Ready::readable()); - Ok(()) - }), + .and_then(move |_| { + let _ = r.set_readiness(mio::Ready::readable()); + Ok(()) + }), ); Ok(()) }, diff --git a/src/server/channel.rs b/src/server/channel.rs index c158f66b..7de561c6 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -7,7 +7,7 @@ use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use super::settings::WorkerSettings; -use super::{h1, h2, HttpHandler, IoStream}; +use super::{h1, h2, ConnectionTag, HttpHandler, IoStream}; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; @@ -30,6 +30,7 @@ where { proto: Option>, node: Option>>, + _tag: ConnectionTag, } impl HttpChannel @@ -40,9 +41,10 @@ where pub(crate) fn new( settings: Rc>, io: T, peer: Option, ) -> HttpChannel { - settings.add_channel(); + let _tag = settings.connection(); HttpChannel { + _tag, node: None, proto: Some(HttpProtocol::Unknown( settings, @@ -97,7 +99,6 @@ where let result = h1.poll(); match result { Ok(Async::Ready(())) | Err(_) => { - h1.settings().remove_channel(); if let Some(n) = self.node.as_mut() { n.remove() }; @@ -110,7 +111,6 @@ where let result = h2.poll(); match result { Ok(Async::Ready(())) | Err(_) => { - h2.settings().remove_channel(); if let Some(n) = self.node.as_mut() { n.remove() }; @@ -119,16 +119,10 @@ where } return result; } - Some(HttpProtocol::Unknown( - ref mut settings, - _, - ref mut io, - ref mut buf, - )) => { + Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => { match io.read_available(buf) { Ok(Async::Ready(true)) | Err(_) => { debug!("Ignored premature client disconnection"); - settings.remove_channel(); if let Some(n) = self.node.as_mut() { n.remove() }; diff --git a/src/server/h1.rs b/src/server/h1.rs index 2c07f0cf..808dc11a 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -468,7 +468,6 @@ where #[cfg(test)] mod tests { use std::net::Shutdown; - use std::sync::{atomic::AtomicUsize, Arc}; use std::{cmp, io, time}; use bytes::{Buf, Bytes, BytesMut}; @@ -478,20 +477,17 @@ mod tests { use super::*; use application::HttpApplication; use httpmessage::HttpMessage; - use server::accept::AcceptNotify; use server::h1decoder::Message; use server::settings::{ServerSettings, WorkerSettings}; - use server::{KeepAlive, Request}; + use server::{Connections, KeepAlive, Request}; - fn wrk_settings() -> WorkerSettings { - WorkerSettings::::new( + fn wrk_settings() -> Rc> { + Rc::new(WorkerSettings::::new( Vec::new(), KeepAlive::Os, ServerSettings::default(), - AcceptNotify::default(), - Arc::new(AtomicUsize::new(0)), - Arc::new(AtomicUsize::new(0)), - ) + Connections::default(), + )) } impl Message { diff --git a/src/server/mod.rs b/src/server/mod.rs index baf00492..f3449793 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -21,12 +21,16 @@ pub(crate) mod helpers; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; +mod server; pub(crate) mod settings; mod srv; mod ssl; mod worker; pub use self::message::Request; +pub use self::server::{ + ConnectionRateTag, ConnectionTag, Connections, Server, Service, ServiceHandler, +}; pub use self::settings::ServerSettings; pub use self::srv::HttpServer; pub use self::ssl::*; @@ -136,6 +140,16 @@ impl Message for StopServer { type Result = Result<(), ()>; } +/// Socket id token +#[derive(Clone, Copy)] +pub struct Token(usize); + +impl Token { + pub(crate) fn new(val: usize) -> Token { + Token(val) + } +} + /// Low level http request handler #[allow(unused_variables)] pub trait HttpHandler: 'static { diff --git a/src/server/server.rs b/src/server/server.rs new file mode 100644 index 00000000..ff88040f --- /dev/null +++ b/src/server/server.rs @@ -0,0 +1,504 @@ +use std::{mem, net}; +use std::time::Duration; +use std::sync::{Arc, atomic::{AtomicUsize, Ordering}}; + +use futures::{Future, Stream, Sink}; +use futures::sync::{mpsc, mpsc::unbounded}; + +use actix::{fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, + Context, Handler, Response, System, StreamHandler, WrapFuture}; + +use super::accept::{AcceptLoop, AcceptNotify, Command}; +use super::worker::{StopWorker, Worker, WorkerClient, Conn}; +use super::{PauseServer, ResumeServer, StopServer, Token}; + +pub trait Service: Send + 'static { + /// Clone service + fn clone(&self) -> Box; + + /// Create service handler for this service + fn create(&self, conn: Connections) -> Box; +} + +impl Service for Box { + fn clone(&self) -> Box { + self.as_ref().clone() + } + + fn create(&self, conn: Connections) -> Box { + self.as_ref().create(conn) + } +} + +pub trait ServiceHandler { + /// Handle incoming stream + fn handle(&mut self, token: Token, io: net::TcpStream, peer: Option); + + /// Shutdown open handlers + fn shutdown(&self, _: bool) {} +} + +pub(crate) enum ServerCommand { + WorkerDied(usize), +} + +pub struct Server { + threads: usize, + workers: Vec<(usize, Addr)>, + services: Vec>, + sockets: Vec>, + accept: AcceptLoop, + exit: bool, + shutdown_timeout: u16, + signals: Option>, + no_signals: bool, + maxconn: usize, + maxconnrate: usize, +} + +impl Default for Server { + fn default() -> Self { + Self::new() + } +} + +impl Server { + /// Create new Server instance + pub fn new() -> Server { + Server { + threads: num_cpus::get(), + workers: Vec::new(), + services: Vec::new(), + sockets: Vec::new(), + accept: AcceptLoop::new(), + exit: false, + shutdown_timeout: 30, + signals: None, + no_signals: false, + maxconn: 102_400, + maxconnrate: 256, + } + } + + /// Set number of workers to start. + /// + /// By default http server uses number of available logical cpu as threads + /// count. + pub fn workers(mut self, num: usize) -> Self { + self.threads = num; + self + } + + /// Sets the maximum per-worker number of concurrent connections. + /// + /// All socket listeners will stop accepting connections when this limit is reached + /// for each worker. + /// + /// By default max connections is set to a 100k. + pub fn maxconn(mut self, num: usize) -> Self { + self.maxconn = num; + self + } + + /// Sets the maximum per-worker concurrent connection establish process. + /// + /// All listeners will stop accepting connections when this limit is reached. It + /// can be used to limit the global SSL CPU usage. + /// + /// By default max connections is set to a 256. + pub fn maxconnrate(mut self, num: usize) -> Self { + self.maxconnrate= num; + self + } + + /// Stop actix system. + /// + /// `SystemExit` message stops currently running system. + pub fn system_exit(mut self) -> Self { + self.exit = true; + self + } + + #[doc(hidden)] + /// Set alternative address for `ProcessSignals` actor. + pub fn signals(mut self, addr: Addr) -> 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. 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 + } + + /// Add new service to server + pub fn service(mut self, srv: T, sockets: Vec<(Token, net::TcpListener)>) -> Self + where + T: Into> + { + self.services.push(srv.into()); + self.sockets.push(sockets); + self + } + + /// Spawn new thread and start listening for incoming connections. + /// + /// This method spawns new thread and starts new actix system. Other than + /// that it is similar to `start()` method. This method blocks. + /// + /// This methods panics if no socket addresses get bound. + /// + /// ```rust,ignore + /// # extern crate futures; + /// # extern crate actix_web; + /// # use futures::Future; + /// use actix_web::*; + /// + /// fn main() { + /// Server::new(). + /// .service( + /// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0") + /// .expect("Can not bind to 127.0.0.1:0")) + /// .run(); + /// } + /// ``` + pub fn run(self) { + let sys = System::new("http-server"); + self.start(); + sys.run(); + } + + /// Start + pub fn start(mut self) -> Addr { + if self.sockets.is_empty() { + panic!("Service should have at least one bound socket"); + } else { + info!("Starting {} http workers", self.threads); + + // start workers + let mut workers = Vec::new(); + for idx in 0..self.threads { + let (addr, worker) = self.start_worker(idx, self.accept.get_notify()); + workers.push(worker); + self.workers.push((idx, addr)); + } + + // start accept thread + for sock in &self.sockets { + for s in sock.iter() { + info!("Starting server on http://{:?}", s.1.local_addr().ok()); + } + } + let rx = self.accept.start( + mem::replace(&mut self.sockets, Vec::new()), workers); + + // start http server actor + let signals = self.subscribe_to_signals(); + let addr = Actor::create(move |ctx| { + ctx.add_stream(rx); + self + }); + if let Some(signals) = signals { + signals.do_send(signal::Subscribe(addr.clone().recipient())) + } + addr + } + } + + // subscribe to os signals + fn subscribe_to_signals(&self) -> Option> { + if !self.no_signals { + if let Some(ref signals) = self.signals { + Some(signals.clone()) + } else { + Some(System::current().registry().get::()) + } + } else { + None + } + } + + fn start_worker(&self, idx: usize, notify: AcceptNotify) -> (Addr, WorkerClient) { + let (tx, rx) = unbounded::>(); + let conns = Connections::new(notify, self.maxconn, self.maxconnrate); + let worker = WorkerClient::new(idx, tx, conns.clone()); + let services: Vec<_> = self.services.iter().map(|v| v.clone()).collect(); + + let addr = Arbiter::start(move |ctx: &mut Context<_>| { + ctx.add_message_stream(rx); + let handlers: Vec<_> = services.into_iter().map(|s| s.create(conns.clone())).collect(); + Worker::new(conns, handlers) + }); + + (addr, worker) + } +} + +impl Actor for Server +{ + type Context = Context; +} + +/// Signals support +/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system +/// message to `System` actor. +impl Handler for Server { + type Result = (); + + fn handle(&mut self, msg: signal::Signal, ctx: &mut Context) { + 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); + } + _ => (), + } + } +} + +impl Handler for Server { + type Result = (); + + fn handle(&mut self, _: PauseServer, _: &mut Context) { + self.accept.send(Command::Pause); + } +} + +impl Handler for Server { + type Result = (); + + fn handle(&mut self, _: ResumeServer, _: &mut Context) { + self.accept.send(Command::Resume); + } +} + +impl Handler for Server { + type Result = Response<(), ()>; + + fn handle(&mut self, msg: StopServer, ctx: &mut Context) -> Self::Result { + // stop accept thread + self.accept.send(Command::Stop); + + // stop workers + 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 { + let tx2 = tx.clone(); + ctx.spawn( + worker + .1 + .send(StopWorker { graceful: dur }) + .into_actor(self) + .then(move |_, slf, ctx| { + slf.workers.pop(); + if slf.workers.is_empty() { + let _ = tx2.send(()); + + // we need to stop system if server was spawned + if slf.exit { + ctx.run_later(Duration::from_millis(300), |_, _| { + System::current().stop(); + }); + } + } + + fut::ok(()) + }), + ); + } + + if !self.workers.is_empty() { + Response::async(rx.into_future().map(|_| ()).map_err(|_| ())) + } else { + // we need to stop system if server was spawned + if self.exit { + ctx.run_later(Duration::from_millis(300), |_, _| { + System::current().stop(); + }); + } + Response::reply(Ok(())) + } + } +} + +/// Commands from accept threads +impl StreamHandler for Server { + fn finished(&mut self, _: &mut Context) {} + + fn handle(&mut self, msg: ServerCommand, _: &mut Context) { + match msg { + ServerCommand::WorkerDied(idx) => { + let mut found = false; + for i in 0..self.workers.len() { + if self.workers[i].0 == idx { + self.workers.swap_remove(i); + found = true; + break; + } + } + + if found { + error!("Worker has died {:?}, restarting", idx); + + let mut new_idx = self.workers.len(); + 'found: loop { + for i in 0..self.workers.len() { + if self.workers[i].0 == new_idx { + new_idx += 1; + continue 'found; + } + } + break; + } + + let (addr, worker) = self.start_worker(new_idx, self.accept.get_notify()); + self.workers.push((new_idx, addr)); + self.accept.send(Command::Worker(worker)); + } + } + } + } +} + +#[derive(Clone, Default)] +pub struct Connections (Arc); + +impl Connections { + fn new(notify: AcceptNotify, maxconn: usize, maxconnrate: usize) -> Self { + let maxconn_low = if maxconn > 10 { maxconn - 10 } else { 0 }; + let maxconnrate_low = if maxconnrate > 10 { + maxconnrate - 10 + } else { + 0 + }; + + Connections ( + Arc::new(ConnectionsInner { + notify, + maxconn, maxconnrate, + maxconn_low, maxconnrate_low, + conn: AtomicUsize::new(0), + connrate: AtomicUsize::new(0), + })) + } + + pub(crate) fn available(&self) -> bool { + self.0.available() + } + + pub(crate) fn num_connections(&self) -> usize { + self.0.conn.load(Ordering::Relaxed) + } + + /// Report opened connection + pub fn connection(&self) -> ConnectionTag { + ConnectionTag::new(self.0.clone()) + } + + /// Report rate connection, rate is usually ssl handshake + pub fn connection_rate(&self) -> ConnectionRateTag { + ConnectionRateTag::new(self.0.clone()) + } +} + +#[derive(Default)] +struct ConnectionsInner { + notify: AcceptNotify, + conn: AtomicUsize, + connrate: AtomicUsize, + maxconn: usize, + maxconnrate: usize, + maxconn_low: usize, + maxconnrate_low: usize, +} + +impl ConnectionsInner { + fn available(&self) -> bool { + if self.maxconnrate <= self.connrate.load(Ordering::Relaxed) { + false + } else { + self.maxconn > self.conn.load(Ordering::Relaxed) + } + } + + fn notify_maxconn(&self, maxconn: usize) { + if maxconn > self.maxconn_low && maxconn <= self.maxconn { + self.notify.notify(); + } + } + + fn notify_maxconnrate(&self, connrate: usize) { + if connrate > self.maxconnrate_low && connrate <= self.maxconnrate { + self.notify.notify(); + } + } + +} + +/// Type responsible for max connection stat. +/// +/// Max connections stat get updated on drop. +pub struct ConnectionTag(Arc); + +impl ConnectionTag { + fn new(inner: Arc) -> Self { + inner.conn.fetch_add(1, Ordering::Relaxed); + ConnectionTag(inner) + } +} + +impl Drop for ConnectionTag { + fn drop(&mut self) { + let conn = self.0.conn.fetch_sub(1, Ordering::Relaxed); + self.0.notify_maxconn(conn); + } +} + +/// Type responsible for max connection rate stat. +/// +/// Max connections rate stat get updated on drop. +pub struct ConnectionRateTag (Arc); + +impl ConnectionRateTag { + fn new(inner: Arc) -> Self { + inner.connrate.fetch_add(1, Ordering::Relaxed); + ConnectionRateTag(inner) + } +} + +impl Drop for ConnectionRateTag { + fn drop(&mut self) { + let connrate = self.0.connrate.fetch_sub(1, Ordering::Relaxed); + self.0.notify_maxconnrate(connrate); + } +} diff --git a/src/server/settings.rs b/src/server/settings.rs index 508be67d..e9ca0f85 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -2,19 +2,22 @@ use std::cell::{RefCell, RefMut, UnsafeCell}; use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; -use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc}; +use std::time::{Duration, Instant}; use std::{env, fmt, net}; +use actix::Arbiter; use bytes::BytesMut; +use futures::Stream; use futures_cpupool::CpuPool; use http::StatusCode; use lazycell::LazyCell; use parking_lot::Mutex; use time; +use tokio_timer::Interval; -use super::accept::AcceptNotify; use super::channel::Node; use super::message::{Request, RequestPool}; +use super::server::{ConnectionRateTag, ConnectionTag, Connections}; use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; @@ -137,17 +140,36 @@ pub(crate) struct WorkerSettings { ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, - channels: Arc, + conns: Connections, node: RefCell>, date: UnsafeCell, - connrate: Arc, - notify: AcceptNotify, +} + +impl WorkerSettings { + pub(crate) fn create( + apps: Vec, keep_alive: KeepAlive, settings: ServerSettings, + conns: Connections, + ) -> Rc> { + let settings = Rc::new(Self::new(apps, keep_alive, settings, conns)); + + // periodic date update + let s = settings.clone(); + Arbiter::spawn( + Interval::new(Instant::now(), Duration::from_secs(1)) + .map_err(|_| ()) + .and_then(move |_| { + s.update_date(); + Ok(()) + }).fold((), |(), _| Ok(())), + ); + + settings + } } impl WorkerSettings { pub(crate) fn new( - h: Vec, keep_alive: KeepAlive, settings: ServerSettings, - notify: AcceptNotify, channels: Arc, connrate: Arc, + h: Vec, keep_alive: KeepAlive, settings: ServerSettings, conns: Connections, ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -163,16 +185,10 @@ impl WorkerSettings { date: UnsafeCell::new(Date::new()), keep_alive, ka_enabled, - channels, - connrate, - notify, + conns, } } - pub fn num_channels(&self) -> usize { - self.channels.load(Ordering::Relaxed) - } - pub fn head(&self) -> RefMut> { self.node.borrow_mut() } @@ -201,16 +217,11 @@ impl WorkerSettings { RequestPool::get(self.messages) } - pub fn add_channel(&self) { - self.channels.fetch_add(1, Ordering::Relaxed); + pub fn connection(&self) -> ConnectionTag { + self.conns.connection() } - pub fn remove_channel(&self) { - let val = self.channels.fetch_sub(1, Ordering::Relaxed); - self.notify.notify_maxconn(val); - } - - pub fn update_date(&self) { + fn update_date(&self) { // Unsafe: WorkerSetting is !Sync and !Send unsafe { &mut *self.date.get() }.update(); } @@ -230,13 +241,8 @@ impl WorkerSettings { } #[allow(dead_code)] - pub(crate) fn conn_rate_add(&self) { - self.connrate.fetch_add(1, Ordering::Relaxed); - } - #[allow(dead_code)] - pub(crate) fn conn_rate_del(&self) { - let val = self.connrate.fetch_sub(1, Ordering::Relaxed); - self.notify.notify_maxconnrate(val); + pub(crate) fn connection_rate(&self) -> ConnectionRateTag { + self.conns.connection_rate() } } @@ -309,9 +315,7 @@ mod tests { Vec::new(), KeepAlive::Os, ServerSettings::default(), - AcceptNotify::default(), - Arc::new(AtomicUsize::new(0)), - Arc::new(AtomicUsize::new(0)), + Connections::default(), ); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf1, true); diff --git a/src/server/srv.rs b/src/server/srv.rs index c2bb6c81..eaf7802c 100644 --- a/src/server/srv.rs +++ b/src/server/srv.rs @@ -1,16 +1,14 @@ +use std::marker::PhantomData; use std::rc::Rc; -use std::sync::{atomic::AtomicUsize, Arc}; -use std::time::Duration; -use std::{io, net}; +use std::sync::Arc; +use std::{io, mem, net, time}; -use actix::{ - fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler, - Response, StreamHandler, System, WrapFuture, -}; +use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System}; -use futures::sync::mpsc; -use futures::{Future, Sink, Stream}; +use futures::{Future, Stream}; +use net2::{TcpBuilder, TcpStreamExt}; use num_cpus; +use tokio::executor::current_thread; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; @@ -23,39 +21,33 @@ use openssl::ssl::SslAcceptorBuilder; #[cfg(feature = "rust-tls")] use rustls::ServerConfig; -use super::accept::{AcceptLoop, AcceptNotify, Command}; use super::channel::{HttpChannel, WrapperStream}; +use super::server::{Connections, Server, Service, ServiceHandler}; use super::settings::{ServerSettings, WorkerSettings}; -use super::worker::{Conn, StopWorker, Token, Worker, WorkerClient, WorkerFactory}; -use super::{AcceptorService, IntoHttpHandler, IoStream, KeepAlive}; -use super::{PauseServer, ResumeServer, StopServer}; +use super::worker::{Conn, Socket}; +use super::{ + AcceptorService, HttpHandler, IntoAsyncIo, IntoHttpHandler, IoStream, KeepAlive, + Token, +}; /// An HTTP Server pub struct HttpServer where H: IntoHttpHandler + 'static, { + factory: Arc Vec + Send + Sync>, + host: Option, + keep_alive: KeepAlive, + backlog: i32, threads: usize, - factory: WorkerFactory, - workers: Vec<(usize, Addr)>, - accept: AcceptLoop, exit: bool, shutdown_timeout: u16, - signals: Option>, no_http2: bool, no_signals: bool, - settings: Option>>, -} - -pub(crate) enum ServerCommand { - WorkerDied(usize), -} - -impl Actor for HttpServer -where - H: IntoHttpHandler, -{ - type Context = Context; + maxconn: usize, + maxconnrate: usize, + sockets: Vec, + handlers: Vec>>, } impl HttpServer @@ -72,15 +64,19 @@ where HttpServer { threads: num_cpus::get(), - factory: WorkerFactory::new(f), - workers: Vec::new(), - accept: AcceptLoop::new(), - exit: false, + factory: Arc::new(f), + host: None, + backlog: 2048, + keep_alive: KeepAlive::Os, shutdown_timeout: 30, - signals: None, + exit: true, no_http2: false, no_signals: false, - settings: None, + maxconn: 102_400, + maxconnrate: 256, + // settings: None, + sockets: Vec::new(), + handlers: Vec::new(), } } @@ -104,7 +100,7 @@ where /// /// This method should be called before `bind()` method call. pub fn backlog(mut self, num: i32) -> Self { - self.factory.backlog = num; + self.backlog = num; self } @@ -115,7 +111,7 @@ where /// /// By default max connections is set to a 100k. pub fn maxconn(mut self, num: usize) -> Self { - self.accept.maxconn(num); + self.maxconn = num; self } @@ -126,7 +122,7 @@ where /// /// By default max connections is set to a 256. pub fn maxconnrate(mut self, num: usize) -> Self { - self.accept.maxconnrate(num); + self.maxconnrate = num; self } @@ -134,7 +130,7 @@ where /// /// By default keep alive is set to a `Os`. pub fn keep_alive>(mut self, val: T) -> Self { - self.factory.keep_alive = val.into(); + self.keep_alive = val.into(); self } @@ -144,7 +140,7 @@ where /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. /// html#method.host) documentation for more information. pub fn server_hostname(mut self, val: String) -> Self { - self.factory.host = Some(val); + self.host = Some(val); self } @@ -156,12 +152,6 @@ where self } - /// Set alternative address for `ProcessSignals` actor. - pub fn signals(mut self, addr: Addr) -> Self { - self.signals = Some(addr); - self - } - /// Disable signal handling pub fn disable_signals(mut self) -> Self { self.no_signals = true; @@ -182,7 +172,10 @@ where /// Disable `HTTP/2` support #[doc(hidden)] - #[deprecated(since = "0.7.4", note = "please use acceptor service with proper ServerFlags parama")] + #[deprecated( + since = "0.7.4", + note = "please use acceptor service with proper ServerFlags parama" + )] pub fn no_http2(mut self) -> Self { self.no_http2 = true; self @@ -190,7 +183,7 @@ where /// Get addresses of bound sockets. pub fn addrs(&self) -> Vec { - self.factory.addrs() + self.sockets.iter().map(|s| s.addr).collect() } /// Get addresses of bound sockets and the scheme for it. @@ -200,7 +193,10 @@ where /// and the user should be presented with an enumeration of which /// socket requires which protocol. pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.factory.addrs_with_scheme() + self.handlers + .iter() + .map(|s| (s.addr(), s.scheme())) + .collect() } /// Use listener for accepting incoming connection requests @@ -208,19 +204,29 @@ where /// HttpServer does not change any configuration for TcpListener, /// it needs to be configured before passing it to listen() method. pub fn listen(mut self, lst: net::TcpListener) -> Self { - self.factory.listen(lst); + let token = Token(self.handlers.len()); + let addr = lst.local_addr().unwrap(); + self.handlers + .push(Box::new(SimpleHandler::new(lst.local_addr().unwrap()))); + self.sockets.push(Socket { lst, addr, token }); + self } /// Use listener for accepting incoming connection requests - pub fn listen_with( - mut self, lst: net::TcpListener, acceptor: A, - ) -> io::Result + pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self where A: AcceptorService + Send + 'static, { - self.factory.listen_with(lst, acceptor); - Ok(self) + let token = Token(self.handlers.len()); + let addr = lst.local_addr().unwrap(); + self.handlers.push(Box::new(StreamHandler::new( + lst.local_addr().unwrap(), + acceptor, + ))); + self.sockets.push(Socket { lst, addr, token }); + + self } #[cfg(feature = "tls")] @@ -233,12 +239,10 @@ where /// /// HttpServer does not change any configuration for TcpListener, /// it needs to be configured before passing it to listen() method. - pub fn listen_tls( - self, lst: net::TcpListener, acceptor: TlsAcceptor, - ) -> io::Result { + pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { use super::NativeTlsAcceptor; - self.listen_with(lst, NativeTlsAcceptor::new(acceptor)) + Ok(self.listen_with(lst, NativeTlsAcceptor::new(acceptor))) } #[cfg(feature = "alpn")] @@ -262,7 +266,7 @@ where ServerFlags::HTTP1 | ServerFlags::HTTP2 }; - self.listen_with(lst, OpensslAcceptor::with_flags(builder, flags)?) + Ok(self.listen_with(lst, OpensslAcceptor::with_flags(builder, flags)?)) } #[cfg(feature = "rust-tls")] @@ -274,9 +278,7 @@ where /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_rustls( - self, lst: net::TcpListener, builder: ServerConfig, - ) -> io::Result { + pub fn listen_rustls(self, lst: net::TcpListener, builder: ServerConfig) -> Self { use super::{RustlsAcceptor, ServerFlags}; // alpn support @@ -293,7 +295,16 @@ where /// /// To bind multiple addresses this method can be called multiple times. pub fn bind(mut self, addr: S) -> io::Result { - self.factory.bind(addr)?; + let sockets = self.bind2(addr)?; + + for lst in sockets { + let token = Token(self.handlers.len()); + let addr = lst.local_addr().unwrap(); + self.handlers + .push(Box::new(SimpleHandler::new(lst.local_addr().unwrap()))); + self.sockets.push(Socket { lst, addr, token }) + } + Ok(self) } @@ -304,10 +315,51 @@ where S: net::ToSocketAddrs, A: AcceptorService + Send + 'static, { - self.factory.bind_with(addr, &acceptor)?; + let sockets = self.bind2(addr)?; + + for lst in sockets { + let token = Token(self.handlers.len()); + let addr = lst.local_addr().unwrap(); + self.handlers.push(Box::new(StreamHandler::new( + lst.local_addr().unwrap(), + acceptor.clone(), + ))); + self.sockets.push(Socket { lst, addr, token }) + } + Ok(self) } + fn bind2( + &self, addr: S, + ) -> io::Result> { + let mut err = None; + let mut succ = false; + let mut sockets = Vec::new(); + for addr in addr.to_socket_addrs()? { + match create_tcp_listener(addr, self.backlog) { + Ok(lst) => { + succ = true; + sockets.push(lst); + } + Err(e) => err = Some(e), + } + } + + 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) + } + } + #[cfg(feature = "tls")] #[doc(hidden)] #[deprecated( @@ -373,37 +425,59 @@ where self.bind_with(addr, RustlsAcceptor::with_flags(builder, flags)) } +} - fn start_workers(&mut self, notify: &AcceptNotify) -> Vec { - // start workers - let mut workers = Vec::new(); - for idx in 0..self.threads { - let (worker, addr) = self.factory.start(idx, notify.clone()); - workers.push(worker); - self.workers.push((idx, addr)); - } - info!("Starting {} http workers", self.threads); - workers +impl Into> for HttpServer { + fn into(self) -> Box { + Box::new(HttpService { + factory: self.factory, + host: self.host, + keep_alive: self.keep_alive, + handlers: self.handlers, + }) + } +} + +struct HttpService { + factory: Arc Vec + Send + Sync>, + host: Option, + keep_alive: KeepAlive, + handlers: Vec>>, +} + +impl Service for HttpService { + fn clone(&self) -> Box { + Box::new(HttpService { + factory: self.factory.clone(), + host: self.host.clone(), + keep_alive: self.keep_alive, + handlers: self.handlers.iter().map(|v| v.clone()).collect(), + }) } - // subscribe to os signals - fn subscribe_to_signals(&self) -> Option> { - if !self.no_signals { - if let Some(ref signals) = self.signals { - Some(signals.clone()) - } else { - Some(System::current().registry().get::()) - } - } else { - None - } + fn create(&self, conns: Connections) -> Box { + let addr = self.handlers[0].addr(); + let s = ServerSettings::new(Some(addr), &self.host, false); + let apps: Vec<_> = (*self.factory)() + .into_iter() + .map(|h| h.into_handler()) + .collect(); + let handlers = self.handlers.iter().map(|h| h.clone()).collect(); + + Box::new(HttpServiceHandler::new( + apps, + handlers, + self.keep_alive, + s, + conns, + )) } } impl HttpServer { /// Start listening for incoming connections. /// - /// This method starts number of http handler workers in separate threads. + /// This method starts number of http workers in separate threads. /// For each address this method starts separate thread which does /// `accept()` in a loop. /// @@ -426,31 +500,25 @@ impl HttpServer { /// sys.run(); // <- Run actix system, this method starts all async processes /// } /// ``` - pub fn start(mut self) -> Addr { - let sockets = self.factory.take_sockets(); - if sockets.is_empty() { - panic!("HttpServer::bind() has to be called before start()"); + pub fn start(mut self) -> Addr { + let mut srv = Server::new() + .workers(self.threads) + .maxconn(self.maxconn) + .maxconnrate(self.maxconnrate) + .shutdown_timeout(self.shutdown_timeout); + + srv = if self.exit { srv.system_exit() } else { srv }; + srv = if self.no_signals { + srv.disable_signals() } else { - let notify = self.accept.get_notify(); - let workers = self.start_workers(¬ify); + srv + }; - // start accept thread - for sock in &sockets { - info!("Starting server on http://{}", sock.addr); - } - let rx = self.accept.start(sockets, workers.clone()); - - // start http server actor - let signals = self.subscribe_to_signals(); - let addr = Actor::create(move |ctx| { - ctx.add_stream(rx); - self - }); - if let Some(signals) = signals { - signals.do_send(signal::Subscribe(addr.clone().recipient())) - } - addr - } + let sockets: Vec<_> = mem::replace(&mut self.sockets, Vec::new()) + .into_iter() + .map(|item| (item.token, item.lst)) + .collect(); + srv.service(self, sockets).start() } /// Spawn new thread and start listening for incoming connections. @@ -484,195 +552,279 @@ impl HttpServer { /// Start listening for incoming connections from a stream. /// /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(mut self, stream: S, secure: bool) -> Addr + pub fn start_incoming(self, stream: S, secure: bool) where S: Stream + Send + 'static, T: AsyncRead + AsyncWrite + Send + 'static, { // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let settings = ServerSettings::new(Some(addr), &self.factory.host, secure); - let apps: Vec<_> = (*self.factory.factory)() + let srv_settings = ServerSettings::new(Some(addr), &self.host, secure); + let apps: Vec<_> = (*self.factory)() .into_iter() .map(|h| h.into_handler()) .collect(); - self.settings = Some(Rc::new(WorkerSettings::new( + let settings = WorkerSettings::create( apps, - self.factory.keep_alive, - settings, - AcceptNotify::default(), - Arc::new(AtomicUsize::new(0)), - Arc::new(AtomicUsize::new(0)), - ))); + self.keep_alive, + srv_settings, + Connections::default(), + ); // start server - let signals = self.subscribe_to_signals(); - let addr = HttpServer::create(move |ctx| { + HttpIncoming::create(move |ctx| { ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn { io: WrapperStream::new(t), + handler: Token::new(0), token: Token::new(0), peer: None, })); - self + HttpIncoming { settings } }); - - if let Some(signals) = signals { - signals.do_send(signal::Subscribe(addr.clone().recipient())) - } - addr } } -/// Signals support -/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system -/// message to `System` actor. -impl Handler for HttpServer { - type Result = (); - - fn handle(&mut self, msg: signal::Signal, ctx: &mut Context) { - 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); - } - _ => (), - } - } +struct HttpIncoming { + settings: Rc>, } -/// Commands from accept threads -impl StreamHandler for HttpServer { - fn finished(&mut self, _: &mut Context) {} - - fn handle(&mut self, msg: ServerCommand, _: &mut Context) { - match msg { - ServerCommand::WorkerDied(idx) => { - let mut found = false; - for i in 0..self.workers.len() { - if self.workers[i].0 == idx { - self.workers.swap_remove(i); - found = true; - break; - } - } - - if found { - error!("Worker has died {:?}, restarting", idx); - - let mut new_idx = self.workers.len(); - 'found: loop { - for i in 0..self.workers.len() { - if self.workers[i].0 == new_idx { - new_idx += 1; - continue 'found; - } - } - break; - } - - let (worker, addr) = - self.factory.start(new_idx, self.accept.get_notify()); - self.workers.push((new_idx, addr)); - self.accept.send(Command::Worker(worker)); - } - } - } - } +impl Actor for HttpIncoming +where + H: HttpHandler, +{ + type Context = Context; } -impl Handler> for HttpServer +impl Handler> for HttpIncoming where T: IoStream, - H: IntoHttpHandler, + H: HttpHandler, { type Result = (); fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { Arbiter::spawn(HttpChannel::new( - Rc::clone(self.settings.as_ref().unwrap()), + Rc::clone(&self.settings), msg.io, msg.peer, )); } } -impl Handler for HttpServer { - type Result = (); - - fn handle(&mut self, _: PauseServer, _: &mut Context) { - self.accept.send(Command::Pause); - } +struct HttpServiceHandler +where + H: HttpHandler + 'static, +{ + settings: Rc>, + handlers: Vec>>, + tcp_ka: Option, } -impl Handler for HttpServer { - type Result = (); - - fn handle(&mut self, _: ResumeServer, _: &mut Context) { - self.accept.send(Command::Resume); - } -} - -impl Handler for HttpServer { - type Result = Response<(), ()>; - - fn handle(&mut self, msg: StopServer, ctx: &mut Context) -> Self::Result { - // stop accept threads - self.accept.send(Command::Stop); - - // stop workers - let (tx, rx) = mpsc::channel(1); - - let dur = if msg.graceful { - Some(Duration::new(u64::from(self.shutdown_timeout), 0)) +impl HttpServiceHandler { + fn new( + apps: Vec, handlers: Vec>>, + keep_alive: KeepAlive, settings: ServerSettings, conns: Connections, + ) -> HttpServiceHandler { + let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { + Some(time::Duration::new(val as u64, 0)) } else { None }; - for worker in &self.workers { - let tx2 = tx.clone(); - ctx.spawn( - worker - .1 - .send(StopWorker { graceful: dur }) - .into_actor(self) - .then(move |_, slf, ctx| { - slf.workers.pop(); - if slf.workers.is_empty() { - let _ = tx2.send(()); + let settings = WorkerSettings::create(apps, keep_alive, settings, conns); - // we need to stop system if server was spawned - if slf.exit { - ctx.run_later(Duration::from_millis(300), |_, _| { - System::current().stop(); - }); - } - } - - fut::ok(()) - }), - ); - } - - if !self.workers.is_empty() { - Response::async(rx.into_future().map(|_| ()).map_err(|_| ())) - } else { - // we need to stop system if server was spawned - if self.exit { - ctx.run_later(Duration::from_millis(300), |_, _| { - System::current().stop(); - }); - } - Response::reply(Ok(())) + HttpServiceHandler { + handlers, + tcp_ka, + settings, } } -} \ No newline at end of file +} + +impl ServiceHandler for HttpServiceHandler +where + H: HttpHandler + 'static, +{ + fn handle( + &mut self, token: Token, io: net::TcpStream, peer: Option, + ) { + if self.tcp_ka.is_some() && io.set_keepalive(self.tcp_ka).is_err() { + error!("Can not set socket keep-alive option"); + } + self.handlers[token.0].handle(Rc::clone(&self.settings), io, peer); + } + + fn shutdown(&self, force: bool) { + if force { + self.settings.head().traverse::(); + } + } +} + +struct SimpleHandler { + addr: net::SocketAddr, + io: PhantomData, +} + +impl Clone for SimpleHandler { + fn clone(&self) -> Self { + SimpleHandler { + addr: self.addr, + io: PhantomData, + } + } +} + +impl SimpleHandler { + fn new(addr: net::SocketAddr) -> Self { + SimpleHandler { + addr, + io: PhantomData, + } + } +} + +impl IoStreamHandler for SimpleHandler +where + H: HttpHandler, + Io: IntoAsyncIo + Send + 'static, + Io::Io: IoStream, +{ + fn addr(&self) -> net::SocketAddr { + self.addr + } + + fn clone(&self) -> Box> { + Box::new(Clone::clone(self)) + } + + fn scheme(&self) -> &'static str { + "http" + } + + fn handle(&self, h: Rc>, io: Io, peer: Option) { + let mut io = match io.into_async_io() { + Ok(io) => io, + Err(err) => { + trace!("Failed to create async io: {}", err); + return; + } + }; + let _ = io.set_nodelay(true); + + current_thread::spawn(HttpChannel::new(h, io, peer)); + } +} + +struct StreamHandler { + acceptor: A, + addr: net::SocketAddr, + io: PhantomData, +} + +impl> StreamHandler { + fn new(addr: net::SocketAddr, acceptor: A) -> Self { + StreamHandler { + addr, + acceptor, + io: PhantomData, + } + } +} + +impl> Clone for StreamHandler { + fn clone(&self) -> Self { + StreamHandler { + addr: self.addr, + acceptor: self.acceptor.clone(), + io: PhantomData, + } + } +} + +impl IoStreamHandler for StreamHandler +where + H: HttpHandler, + Io: IntoAsyncIo + Send + 'static, + Io::Io: IoStream, + A: AcceptorService + Send + 'static, +{ + fn addr(&self) -> net::SocketAddr { + self.addr + } + + fn clone(&self) -> Box> { + Box::new(Clone::clone(self)) + } + + fn scheme(&self) -> &'static str { + self.acceptor.scheme() + } + + fn handle(&self, h: Rc>, io: Io, peer: Option) { + let mut io = match io.into_async_io() { + Ok(io) => io, + Err(err) => { + trace!("Failed to create async io: {}", err); + return; + } + }; + let _ = io.set_nodelay(true); + + let rate = h.connection_rate(); + current_thread::spawn(self.acceptor.accept(io).then(move |res| { + drop(rate); + match res { + Ok(io) => current_thread::spawn(HttpChannel::new(h, io, peer)), + Err(err) => trace!("Can not establish connection: {}", err), + } + Ok(()) + })) + } +} + +impl IoStreamHandler for Box> +where + H: HttpHandler, + Io: IntoAsyncIo, +{ + fn addr(&self) -> net::SocketAddr { + self.as_ref().addr() + } + + fn clone(&self) -> Box> { + self.as_ref().clone() + } + + fn scheme(&self) -> &'static str { + self.as_ref().scheme() + } + + fn handle(&self, h: Rc>, io: Io, peer: Option) { + self.as_ref().handle(h, io, peer) + } +} + +trait IoStreamHandler: Send +where + H: HttpHandler, +{ + fn clone(&self) -> Box>; + + fn addr(&self) -> net::SocketAddr; + + fn scheme(&self) -> &'static str; + + fn handle(&self, h: Rc>, io: Io, peer: Option); +} + +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.reuse_address(true)?; + builder.bind(addr)?; + Ok(builder.listen(backlog)?) +} diff --git a/src/server/worker.rs b/src/server/worker.rs index 168382e6..77128adc 100644 --- a/src/server/worker.rs +++ b/src/server/worker.rs @@ -1,216 +1,41 @@ -use std::marker::PhantomData; -use std::rc::Rc; -use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc}; -use std::{io, mem, net, time}; +use std::{net, time}; -use futures::sync::mpsc::{unbounded, SendError, UnboundedSender}; +use futures::sync::mpsc::{SendError, UnboundedSender}; use futures::sync::oneshot; use futures::Future; -use net2::{TcpBuilder, TcpStreamExt}; -use tokio::executor::current_thread; -use tokio_tcp::TcpStream; use actix::msgs::StopArbiter; -use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, Message, Response}; +use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response}; -use super::accept::AcceptNotify; -use super::channel::HttpChannel; -use super::settings::{ServerSettings, WorkerSettings}; -use super::{ - AcceptorService, HttpHandler, IntoAsyncIo, IntoHttpHandler, IoStream, KeepAlive, -}; +use super::server::{Connections, ServiceHandler}; +use super::Token; #[derive(Message)] pub(crate) struct Conn { pub io: T, + pub handler: Token, pub token: Token, pub peer: Option, } -#[derive(Clone, Copy)] -pub struct Token(usize); - -impl Token { - pub(crate) fn new(val: usize) -> Token { - Token(val) - } -} - pub(crate) struct Socket { pub lst: net::TcpListener, pub addr: net::SocketAddr, pub token: Token, } -pub(crate) struct WorkerFactory { - pub factory: Arc Vec + Send + Sync>, - pub host: Option, - pub keep_alive: KeepAlive, - pub backlog: i32, - sockets: Vec, - handlers: Vec>>, -} - -impl WorkerFactory { - pub fn new(factory: F) -> Self - where - F: Fn() -> Vec + Send + Sync + 'static, - { - WorkerFactory { - factory: Arc::new(factory), - host: None, - backlog: 2048, - keep_alive: KeepAlive::Os, - sockets: Vec::new(), - handlers: Vec::new(), - } - } - - pub fn addrs(&self) -> Vec { - self.sockets.iter().map(|s| s.addr).collect() - } - - pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.handlers - .iter() - .map(|s| (s.addr(), s.scheme())) - .collect() - } - - pub fn take_sockets(&mut self) -> Vec { - mem::replace(&mut self.sockets, Vec::new()) - } - - pub fn listen(&mut self, lst: net::TcpListener) { - let token = Token(self.handlers.len()); - let addr = lst.local_addr().unwrap(); - self.handlers - .push(Box::new(SimpleHandler::new(lst.local_addr().unwrap()))); - self.sockets.push(Socket { lst, addr, token }) - } - - pub fn listen_with(&mut self, lst: net::TcpListener, acceptor: A) - where - A: AcceptorService + Send + 'static, - { - let token = Token(self.handlers.len()); - let addr = lst.local_addr().unwrap(); - self.handlers.push(Box::new(StreamHandler::new( - lst.local_addr().unwrap(), - acceptor, - ))); - self.sockets.push(Socket { lst, addr, token }) - } - - pub fn bind(&mut self, addr: S) -> io::Result<()> - where - S: net::ToSocketAddrs, - { - let sockets = self.bind2(addr)?; - - for lst in sockets { - let token = Token(self.handlers.len()); - let addr = lst.local_addr().unwrap(); - self.handlers - .push(Box::new(SimpleHandler::new(lst.local_addr().unwrap()))); - self.sockets.push(Socket { lst, addr, token }) - } - Ok(()) - } - - pub fn bind_with(&mut self, addr: S, acceptor: &A) -> io::Result<()> - where - S: net::ToSocketAddrs, - A: AcceptorService + Send + 'static, - { - let sockets = self.bind2(addr)?; - - for lst in sockets { - let token = Token(self.handlers.len()); - let addr = lst.local_addr().unwrap(); - self.handlers.push(Box::new(StreamHandler::new( - lst.local_addr().unwrap(), - acceptor.clone(), - ))); - self.sockets.push(Socket { lst, addr, token }) - } - Ok(()) - } - - fn bind2( - &self, addr: S, - ) -> io::Result> { - let mut err = None; - let mut succ = false; - let mut sockets = Vec::new(); - for addr in addr.to_socket_addrs()? { - match create_tcp_listener(addr, self.backlog) { - Ok(lst) => { - succ = true; - sockets.push(lst); - } - Err(e) => err = Some(e), - } - } - - 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) - } - } - - pub fn start( - &mut self, idx: usize, notify: AcceptNotify, - ) -> (WorkerClient, Addr) { - let host = self.host.clone(); - let addr = self.handlers[0].addr(); - let factory = Arc::clone(&self.factory); - let ka = self.keep_alive; - let (tx, rx) = unbounded::>(); - let client = WorkerClient::new(idx, tx); - let conn = client.conn.clone(); - let sslrate = client.sslrate.clone(); - let handlers: Vec<_> = self.handlers.iter().map(|v| v.clone()).collect(); - - let addr = Arbiter::start(move |ctx: &mut Context<_>| { - let s = ServerSettings::new(Some(addr), &host, false); - let apps: Vec<_> = - (*factory)().into_iter().map(|h| h.into_handler()).collect(); - ctx.add_message_stream(rx); - let inner = WorkerInner::new(apps, handlers, ka, s, conn, sslrate, notify); - Worker { - inner: Box::new(inner), - } - }); - - (client, addr) - } -} - #[derive(Clone)] pub(crate) struct WorkerClient { pub idx: usize, tx: UnboundedSender>, - pub conn: Arc, - pub sslrate: Arc, + conns: Connections, } impl WorkerClient { - fn new(idx: usize, tx: UnboundedSender>) -> Self { - WorkerClient { - idx, - tx, - conn: Arc::new(AtomicUsize::new(0)), - sslrate: Arc::new(AtomicUsize::new(0)), - } + pub fn new( + idx: usize, tx: UnboundedSender>, conns: Connections, + ) -> Self { + WorkerClient { idx, tx, conns } } pub fn send( @@ -219,12 +44,8 @@ impl WorkerClient { self.tx.unbounded_send(msg) } - pub fn available(&self, maxconn: usize, maxsslrate: usize) -> bool { - if maxsslrate <= self.sslrate.load(Ordering::Relaxed) { - false - } else { - maxconn > self.conn.load(Ordering::Relaxed) - } + pub fn available(&self) -> bool { + self.conns.available() } } @@ -243,21 +64,21 @@ impl Message for StopWorker { /// Worker accepts Socket objects via unbounded channel and start requests /// processing. pub(crate) struct Worker { - inner: Box, + conns: Connections, + handlers: Vec>, } impl Actor for Worker { type Context = Context; - - fn started(&mut self, ctx: &mut Self::Context) { - self.update_date(ctx); - } } impl Worker { - fn update_date(&self, ctx: &mut Context) { - self.inner.update_date(); - ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_date(ctx)); + pub(crate) fn new(conns: Connections, handlers: Vec>) -> Self { + Worker { conns, handlers } + } + + fn shutdown(&self, force: bool) { + self.handlers.iter().for_each(|h| h.shutdown(force)); } fn shutdown_timeout( @@ -265,7 +86,7 @@ impl Worker { ) { // sleep for 1 second and then check again ctx.run_later(time::Duration::new(1, 0), move |slf, ctx| { - let num = slf.inner.num_channels(); + let num = slf.conns.num_connections(); if num == 0 { let _ = tx.send(true); Arbiter::current().do_send(StopArbiter(0)); @@ -273,7 +94,7 @@ impl Worker { slf.shutdown_timeout(ctx, tx, d); } else { info!("Force shutdown http worker, {} connections", num); - slf.inner.force_shutdown(); + slf.shutdown(true); let _ = tx.send(false); Arbiter::current().do_send(StopArbiter(0)); } @@ -285,7 +106,7 @@ impl Handler> for Worker { type Result = (); fn handle(&mut self, msg: Conn, _: &mut Context) { - self.inner.handle_connect(msg) + self.handlers[msg.handler.0].handle(msg.token, msg.io, msg.peer) } } @@ -294,253 +115,25 @@ impl Handler for Worker { type Result = Response; fn handle(&mut self, msg: StopWorker, ctx: &mut Context) -> Self::Result { - let num = self.inner.num_channels(); + let num = self.conns.num_connections(); if num == 0 { info!("Shutting down http worker, 0 connections"); Response::reply(Ok(true)) } else if let Some(dur) = msg.graceful { - info!("Graceful http worker shutdown, {} connections", num); + self.shutdown(false); let (tx, rx) = oneshot::channel(); - self.shutdown_timeout(ctx, tx, dur); - Response::async(rx.map_err(|_| ())) + let num = self.conns.num_connections(); + if num != 0 { + info!("Graceful http worker shutdown, {} connections", num); + self.shutdown_timeout(ctx, tx, dur); + Response::reply(Ok(true)) + } else { + Response::async(rx.map_err(|_| ())) + } } else { info!("Force shutdown http worker, {} connections", num); - self.inner.force_shutdown(); + self.shutdown(true); Response::reply(Ok(false)) } } } - -trait WorkerHandler { - fn update_date(&self); - - fn handle_connect(&mut self, Conn); - - fn force_shutdown(&self); - - fn num_channels(&self) -> usize; -} - -struct WorkerInner -where - H: HttpHandler + 'static, -{ - settings: Rc>, - socks: Vec>>, - tcp_ka: Option, -} - -impl WorkerInner { - pub(crate) fn new( - h: Vec, socks: Vec>>, - keep_alive: KeepAlive, settings: ServerSettings, conn: Arc, - sslrate: Arc, notify: AcceptNotify, - ) -> WorkerInner { - let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { - Some(time::Duration::new(val as u64, 0)) - } else { - None - }; - - WorkerInner { - settings: Rc::new(WorkerSettings::new( - h, keep_alive, settings, notify, conn, sslrate, - )), - socks, - tcp_ka, - } - } -} - -impl WorkerHandler for WorkerInner -where - H: HttpHandler + 'static, -{ - fn update_date(&self) { - self.settings.update_date(); - } - - fn handle_connect(&mut self, msg: Conn) { - if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() { - error!("Can not set socket keep-alive option"); - } - self.socks[msg.token.0].handle(Rc::clone(&self.settings), msg.io, msg.peer); - } - - fn num_channels(&self) -> usize { - self.settings.num_channels() - } - - fn force_shutdown(&self) { - self.settings.head().traverse::(); - } -} - -struct SimpleHandler { - addr: net::SocketAddr, - io: PhantomData, -} - -impl Clone for SimpleHandler { - fn clone(&self) -> Self { - SimpleHandler { - addr: self.addr, - io: PhantomData, - } - } -} - -impl SimpleHandler { - fn new(addr: net::SocketAddr) -> Self { - SimpleHandler { - addr, - io: PhantomData, - } - } -} - -impl IoStreamHandler for SimpleHandler -where - H: HttpHandler, - Io: IntoAsyncIo + Send + 'static, - Io::Io: IoStream, -{ - fn addr(&self) -> net::SocketAddr { - self.addr - } - - fn clone(&self) -> Box> { - Box::new(Clone::clone(self)) - } - - fn scheme(&self) -> &'static str { - "http" - } - - fn handle(&self, h: Rc>, io: Io, peer: Option) { - let mut io = match io.into_async_io() { - Ok(io) => io, - Err(err) => { - trace!("Failed to create async io: {}", err); - return; - } - }; - let _ = io.set_nodelay(true); - - current_thread::spawn(HttpChannel::new(h, io, peer)); - } -} - -struct StreamHandler { - acceptor: A, - addr: net::SocketAddr, - io: PhantomData, -} - -impl> StreamHandler { - fn new(addr: net::SocketAddr, acceptor: A) -> Self { - StreamHandler { - addr, - acceptor, - io: PhantomData, - } - } -} - -impl> Clone for StreamHandler { - fn clone(&self) -> Self { - StreamHandler { - addr: self.addr, - acceptor: self.acceptor.clone(), - io: PhantomData, - } - } -} - -impl IoStreamHandler for StreamHandler -where - H: HttpHandler, - Io: IntoAsyncIo + Send + 'static, - Io::Io: IoStream, - A: AcceptorService + Send + 'static, -{ - fn addr(&self) -> net::SocketAddr { - self.addr - } - - fn clone(&self) -> Box> { - Box::new(Clone::clone(self)) - } - - fn scheme(&self) -> &'static str { - self.acceptor.scheme() - } - - fn handle(&self, h: Rc>, io: Io, peer: Option) { - let mut io = match io.into_async_io() { - Ok(io) => io, - Err(err) => { - trace!("Failed to create async io: {}", err); - return; - } - }; - let _ = io.set_nodelay(true); - - h.conn_rate_add(); - current_thread::spawn(self.acceptor.accept(io).then(move |res| { - h.conn_rate_del(); - match res { - Ok(io) => current_thread::spawn(HttpChannel::new(h, io, peer)), - Err(err) => trace!("Can not establish connection: {}", err), - } - Ok(()) - })) - } -} - -impl IoStreamHandler for Box> -where - H: HttpHandler, - Io: IntoAsyncIo, -{ - fn addr(&self) -> net::SocketAddr { - self.as_ref().addr() - } - - fn clone(&self) -> Box> { - self.as_ref().clone() - } - - fn scheme(&self) -> &'static str { - self.as_ref().scheme() - } - - fn handle(&self, h: Rc>, io: Io, peer: Option) { - self.as_ref().handle(h, io, peer) - } -} - -pub(crate) trait IoStreamHandler: Send -where - H: HttpHandler, -{ - fn clone(&self) -> Box>; - - fn addr(&self) -> net::SocketAddr; - - fn scheme(&self) -> &'static str; - - fn handle(&self, h: Rc>, io: Io, peer: Option); -} - -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.reuse_address(true)?; - builder.bind(addr)?; - Ok(builder.listen(backlog)?) -} diff --git a/src/test.rs b/src/test.rs index 42f51174..92aa6c8d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -17,6 +17,8 @@ use tokio::runtime::current_thread::Runtime; use openssl::ssl::SslAcceptorBuilder; #[cfg(feature = "rust-tls")] use rustls::ServerConfig; +#[cfg(feature = "alpn")] +use server::OpensslAcceptor; #[cfg(feature = "rust-tls")] use server::RustlsAcceptor; @@ -326,7 +328,7 @@ impl TestServerBuilder { config(&mut app); vec![app] }).workers(1) - .disable_signals(); + .disable_signals(); tx.send((System::current(), addr, TestServer::get_conn())) .unwrap(); @@ -336,7 +338,7 @@ impl TestServerBuilder { let ssl = self.ssl.take(); if let Some(ssl) = ssl { let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_ssl(tcp, ssl).unwrap(); + srv = srv.listen_with(tcp, OpensslAcceptor::new(ssl).unwrap()); } } #[cfg(feature = "rust-tls")] @@ -344,7 +346,7 @@ impl TestServerBuilder { let ssl = self.rust_ssl.take(); if let Some(ssl) = ssl { let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_with(tcp, RustlsAcceptor::new(ssl)).unwrap(); + srv = srv.listen_with(tcp, RustlsAcceptor::new(ssl)); } } if !has_ssl { @@ -722,8 +724,9 @@ impl TestRequest { /// This method generates `HttpRequest` instance and executes handler pub fn execute(self, f: F) -> Result - where F: FnOnce(&HttpRequest) -> R, - R: Responder + 'static, + where + F: FnOnce(&HttpRequest) -> R, + R: Responder + 'static, { let req = self.finish(); let resp = f(&req); From 2e8d67e2aecfb850c341146bce5ccf60ff04f73b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 9 Aug 2018 13:08:59 -0700 Subject: [PATCH 0588/1635] upgrade native-tls package --- CHANGES.md | 4 ++ Cargo.toml | 5 +- src/lib.rs | 2 - src/server/{srv.rs => http.rs} | 2 +- src/server/mod.rs | 7 ++- src/server/ssl/mod.rs | 2 +- src/server/ssl/nativetls.rs | 111 +++++++++++++++++++++++++++------ 7 files changed, 104 insertions(+), 29 deletions(-) rename src/server/{srv.rs => http.rs} (99%) diff --git a/CHANGES.md b/CHANGES.md index bfd86a1a..3dbb3795 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,10 @@ * Allow to customize connection handshake process via `HttpServer::listen_with()` and `HttpServer::bind_with()` methods +### Changed + +* native-tls - 0.2 + ### Fixed * Use zlib instead of raw deflate for decoding and encoding payloads with diff --git a/Cargo.toml b/Cargo.toml index 86cb53d1..3bfac16c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ path = "src/lib.rs" default = ["session", "brotli", "flate2-c"] # tls -tls = ["native-tls", "tokio-tls"] +tls = ["native-tls"] # openssl alpn = ["openssl", "tokio-openssl"] @@ -100,8 +100,7 @@ tokio-timer = "0.2" tokio-reactor = "0.1" # native-tls -native-tls = { version="0.1", optional = true } -tokio-tls = { version="0.1", optional = true } +native-tls = { version="0.2", optional = true } # openssl openssl = { version="0.10", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 626bb95f..ed02b1b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,8 +143,6 @@ extern crate serde_derive; #[cfg(feature = "tls")] extern crate native_tls; -#[cfg(feature = "tls")] -extern crate tokio_tls; #[cfg(feature = "openssl")] extern crate openssl; diff --git a/src/server/srv.rs b/src/server/http.rs similarity index 99% rename from src/server/srv.rs rename to src/server/http.rs index eaf7802c..5deaf029 100644 --- a/src/server/srv.rs +++ b/src/server/http.rs @@ -242,7 +242,7 @@ where pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { use super::NativeTlsAcceptor; - Ok(self.listen_with(lst, NativeTlsAcceptor::new(acceptor))) + self.listen_with(lst, NativeTlsAcceptor::new(acceptor)) } #[cfg(feature = "alpn")] diff --git a/src/server/mod.rs b/src/server/mod.rs index f3449793..67952e43 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -23,22 +23,23 @@ pub(crate) mod message; pub(crate) mod output; mod server; pub(crate) mod settings; -mod srv; +mod http; mod ssl; mod worker; +use actix::Message; + pub use self::message::Request; pub use self::server::{ ConnectionRateTag, ConnectionTag, Connections, Server, Service, ServiceHandler, }; pub use self::settings::ServerSettings; -pub use self::srv::HttpServer; +pub use self::http::HttpServer; pub use self::ssl::*; #[doc(hidden)] pub use self::helpers::write_content_length; -use actix::Message; use body::Binary; use error::Error; use extensions::Extensions; diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs index d99c4a58..b29a7d4a 100644 --- a/src/server/ssl/mod.rs +++ b/src/server/ssl/mod.rs @@ -6,7 +6,7 @@ pub use self::openssl::OpensslAcceptor; #[cfg(feature = "tls")] mod nativetls; #[cfg(feature = "tls")] -pub use self::nativetls::NativeTlsAcceptor; +pub use self::nativetls::{TlsStream, NativeTlsAcceptor}; #[cfg(feature = "rust-tls")] mod rustls; diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs index 8749599e..c3f2c38d 100644 --- a/src/server/ssl/nativetls.rs +++ b/src/server/ssl/nativetls.rs @@ -1,9 +1,9 @@ use std::net::Shutdown; use std::{io, time}; -use futures::{Future, Poll}; -use native_tls::TlsAcceptor; -use tokio_tls::{AcceptAsync, TlsAcceptorExt, TlsStream}; +use futures::{Async, Future, Poll}; +use native_tls::{self, TlsAcceptor, HandshakeError}; +use tokio_io::{AsyncRead, AsyncWrite}; use server::{AcceptorService, IoStream}; @@ -15,36 +15,41 @@ pub struct NativeTlsAcceptor { acceptor: TlsAcceptor, } +/// A wrapper around an underlying raw stream which implements the TLS or SSL +/// protocol. +/// +/// A `TlsStream` represents a handshake that has been completed successfully +/// and both the server and the client are ready for receiving and sending +/// data. Bytes read from a `TlsStream` are decrypted from `S` and bytes written +/// to a `TlsStream` are encrypted when passing through to `S`. +#[derive(Debug)] +pub struct TlsStream { + inner: native_tls::TlsStream, +} + +/// Future returned from `NativeTlsAcceptor::accept` which will resolve +/// once the accept handshake has finished. +pub struct Accept{ + inner: Option, HandshakeError>>, +} + impl NativeTlsAcceptor { /// Create `NativeTlsAcceptor` instance pub fn new(acceptor: TlsAcceptor) -> Self { - NativeTlsAcceptor { acceptor } - } -} - -pub struct AcceptorFut(AcceptAsync); - -impl Future for AcceptorFut { - type Item = TlsStream; - type Error = io::Error; - - fn poll(&mut self) -> Poll { - self.0 - .poll() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + NativeTlsAcceptor { acceptor: acceptor.into() } } } impl AcceptorService for NativeTlsAcceptor { type Accepted = TlsStream; - type Future = AcceptorFut; + type Future = Accept; fn scheme(&self) -> &'static str { "https" } fn accept(&self, io: Io) -> Self::Future { - AcceptorFut(TlsAcceptorExt::accept_async(&self.acceptor, io)) + Accept { inner: Some(self.acceptor.accept(io)) } } } @@ -65,3 +70,71 @@ impl IoStream for TlsStream { self.get_mut().get_mut().set_linger(dur) } } + +impl Future for Accept { + type Item = TlsStream; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + match self.inner.take().expect("cannot poll MidHandshake twice") { + Ok(stream) => Ok(TlsStream { inner: stream }.into()), + Err(HandshakeError::Failure(e)) => Err(io::Error::new(io::ErrorKind::Other, e)), + Err(HandshakeError::WouldBlock(s)) => { + match s.handshake() { + Ok(stream) => Ok(TlsStream { inner: stream }.into()), + Err(HandshakeError::Failure(e)) => + Err(io::Error::new(io::ErrorKind::Other, e)), + Err(HandshakeError::WouldBlock(s)) => { + self.inner = Some(Err(HandshakeError::WouldBlock(s))); + Ok(Async::NotReady) + } + } + } + } + } +} + +impl TlsStream { + /// Get access to the internal `native_tls::TlsStream` stream which also + /// transitively allows access to `S`. + pub fn get_ref(&self) -> &native_tls::TlsStream { + &self.inner + } + + /// Get mutable access to the internal `native_tls::TlsStream` stream which + /// also transitively allows mutable access to `S`. + pub fn get_mut(&mut self) -> &mut native_tls::TlsStream { + &mut self.inner + } +} + +impl io::Read for TlsStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +impl io::Write for TlsStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + + +impl AsyncRead for TlsStream { +} + +impl AsyncWrite for TlsStream { + fn shutdown(&mut self) -> Poll<(), io::Error> { + match self.inner.shutdown() { + Ok(_) => (), + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), + Err(e) => return Err(e), + } + self.inner.get_mut().shutdown() + } +} \ No newline at end of file From 2ab7dbadce151bf9b3b7e24b6694ae8f52021b12 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 9 Aug 2018 13:38:10 -0700 Subject: [PATCH 0589/1635] better ergonomics for Server::service() method --- src/server/http.rs | 21 +++++++++++---------- src/server/server.rs | 9 +++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/server/http.rs b/src/server/http.rs index 5deaf029..edf8aef6 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -427,14 +427,19 @@ where } } -impl Into> for HttpServer { - fn into(self) -> Box { - Box::new(HttpService { +impl Into<(Box, Vec<(Token, net::TcpListener)>)> for HttpServer { + fn into(mut self) -> (Box, Vec<(Token, net::TcpListener)>) { + let sockets: Vec<_> = mem::replace(&mut self.sockets, Vec::new()) + .into_iter() + .map(|item| (item.token, item.lst)) + .collect(); + + (Box::new(HttpService { factory: self.factory, host: self.host, keep_alive: self.keep_alive, handlers: self.handlers, - }) + }), sockets) } } @@ -500,7 +505,7 @@ impl HttpServer { /// sys.run(); // <- Run actix system, this method starts all async processes /// } /// ``` - pub fn start(mut self) -> Addr { + pub fn start(self) -> Addr { let mut srv = Server::new() .workers(self.threads) .maxconn(self.maxconn) @@ -514,11 +519,7 @@ impl HttpServer { srv }; - let sockets: Vec<_> = mem::replace(&mut self.sockets, Vec::new()) - .into_iter() - .map(|item| (item.token, item.lst)) - .collect(); - srv.service(self, sockets).start() + srv.service(self).start() } /// Spawn new thread and start listening for incoming connections. diff --git a/src/server/server.rs b/src/server/server.rs index ff88040f..bef1ed16 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -145,11 +145,12 @@ impl Server { } /// Add new service to server - pub fn service(mut self, srv: T, sockets: Vec<(Token, net::TcpListener)>) -> Self - where - T: Into> + pub fn service(mut self, srv: T) -> Self + where + T: Into<(Box, Vec<(Token, net::TcpListener)>)> { - self.services.push(srv.into()); + let (srv, sockets) = srv.into(); + self.services.push(srv); self.sockets.push(sockets); self } From 26629aafa589f8fc6a85a9bad1cf561664c36421 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 9 Aug 2018 13:41:13 -0700 Subject: [PATCH 0590/1635] explicit use --- src/server/server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/server.rs b/src/server/server.rs index bef1ed16..9e25efc5 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -2,6 +2,7 @@ use std::{mem, net}; use std::time::Duration; use std::sync::{Arc, atomic::{AtomicUsize, Ordering}}; +use num_cpus; use futures::{Future, Stream, Sink}; use futures::sync::{mpsc, mpsc::unbounded}; From cc3fbd27e05723c5004ae302263ad78fc2a53d39 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 9 Aug 2018 17:25:23 -0700 Subject: [PATCH 0591/1635] better ergonomics --- src/handler.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 3ac0c2ab..661cd028 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -353,13 +353,16 @@ impl> From> for AsyncResult { } } -impl> From>, E>> - for AsyncResult +impl From>, E>> for AsyncResult +where T: 'static, + E: Into + 'static { #[inline] - fn from(res: Result>, E>) -> Self { + fn from(res: Result>, E>) -> Self { match res { - Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(fut))), + Ok(fut) => AsyncResult( + Some(AsyncResultItem::Future( + Box::new(fut.map_err(|e| e.into()))))), Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), } } From bf7779a9a35c5b49f56904b644a6d033c2e59928 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 9 Aug 2018 18:58:14 -0700 Subject: [PATCH 0592/1635] add TestRequest::run_async_result helper method --- src/test.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 92aa6c8d..64aef663 100644 --- a/src/test.rs +++ b/src/test.rs @@ -26,7 +26,7 @@ use application::{App, HttpApplication}; use body::Binary; use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; use error::Error; -use handler::{AsyncResultItem, Handler, Responder}; +use handler::{AsyncResult, AsyncResultItem, Handler, Responder}; use header::{Header, IntoHeaderValue}; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -722,6 +722,25 @@ impl TestRequest { } } + /// This method generates `HttpRequest` instance and executes handler + pub fn run_async_result(self, f: F) -> Result + where + F: FnOnce(&HttpRequest) -> R, + R: Into>, + { + let req = self.finish(); + let res = f(&req); + + match res.into().into() { + AsyncResultItem::Ok(resp) => Ok(resp), + AsyncResultItem::Err(err) => Err(err), + AsyncResultItem::Future(fut) => { + let mut sys = System::new("test"); + sys.block_on(fut) + } + } + } + /// This method generates `HttpRequest` instance and executes handler pub fn execute(self, f: F) -> Result where From d9c7cd96a6d9e1fef0f38b7410cf954cb3e6b38c Mon Sep 17 00:00:00 2001 From: Gowee Date: Mon, 13 Aug 2018 22:34:05 +0800 Subject: [PATCH 0593/1635] Rework Content-Disposition parsing totally (#461) --- src/fs.rs | 10 +- src/header/common/content_disposition.rs | 927 +++++++++++++++++++---- src/header/mod.rs | 6 +- src/multipart.rs | 4 +- 4 files changed, 791 insertions(+), 156 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index f23ba12c..4c819212 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -164,11 +164,7 @@ impl NamedFile { let disposition_type = C::content_disposition_map(ct.type_()); let cd = ContentDisposition { disposition: disposition_type, - parameters: vec![DispositionParam::Filename( - header::Charset::Ext("UTF-8".to_owned()), - None, - filename.as_bytes().to_vec(), - )], + parameters: vec![DispositionParam::Filename(filename.into_owned())], }; (ct, cd) }; @@ -991,9 +987,7 @@ mod tests { let cd = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![DispositionParam::Filename( - header::Charset::Ext("UTF-8".to_owned()), - None, - "test.png".as_bytes().to_vec(), + String::from("test.png") )], }; let mut file = NamedFile::open("tests/test.png") diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index ff04ef56..686cf9c6 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -2,17 +2,35 @@ // // "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt // "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc2388.txt +// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt // Browser conformance tests at: http://greenbytes.de/tech/tc2231/ // IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml -use language_tags::LanguageTag; use header; +use header::ExtendedValue; use header::{Header, IntoHeaderValue, Writer}; -use header::shared::Charset; +use regex::Regex; use std::fmt::{self, Write}; +/// Split at the index of the first `needle` if it exists or at the end. +fn split_once<'a>(haystack: &'a str, needle: char) -> (&'a str, &'a str) { + haystack.find(needle).map_or_else( + || (haystack, ""), + |sc| { + let (first, last) = haystack.split_at(sc); + (first, last.split_at(1).1) + }, + ) +} + +/// Split at the index of the first `needle` if it exists or at the end, trim the right of the +/// first part and the left of the last part. +fn split_once_and_trim<'a>(haystack: &'a str, needle: char) -> (&'a str, &'a str) { + let (first, last) = split_once(haystack, needle); + (first.trim_right(), last.trim_left()) +} + /// The implied disposition of the content of the HTTP body. #[derive(Clone, Debug, PartialEq)] pub enum DispositionType { @@ -21,27 +39,166 @@ pub enum DispositionType { /// Attachment implies that the recipient should prompt the user to save the response locally, /// rather than process it normally (as per its media type). Attachment, - /// Extension type. Should be handled by recipients the same way as Attachment - Ext(String) + /// Used in *multipart/form-data* as defined in + /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. + FormData, + /// Extension type. Should be handled by recipients the same way as Attachment + Ext(String), } -/// A parameter to the disposition type. +impl<'a> From<&'a str> for DispositionType { + fn from(origin: &'a str) -> DispositionType { + if origin.eq_ignore_ascii_case("inline") { + DispositionType::Inline + } else if origin.eq_ignore_ascii_case("attachment") { + DispositionType::Attachment + } else if origin.eq_ignore_ascii_case("form-data") { + DispositionType::FormData + } else { + DispositionType::Ext(origin.to_owned()) + } + } +} + +/// Parameter in [`ContentDisposition`]. +/// +/// # Examples +/// ``` +/// use actix_web::http::header::DispositionParam; +/// +/// let param = DispositionParam::Filename(String::from("sample.txt")); +/// assert!(param.is_filename()); +/// assert_eq!(param.as_filename().unwrap(), "sample.txt"); +/// ``` #[derive(Clone, Debug, PartialEq)] pub enum DispositionParam { - /// A Filename consisting of a Charset, an optional LanguageTag, and finally a sequence of - /// bytes representing the filename - Filename(Charset, Option, Vec), - /// Extension type consisting of token and value. Recipients should ignore unrecognized - /// parameters. - Ext(String, String) + /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from + /// the form. + Name(String), + /// A plain file name. + Filename(String), + /// An extended file name. It must not exist for `ContentType::Formdata` according to + /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). + FilenameExt(ExtendedValue), + /// An unrecognized regular parameter as defined in + /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in + /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should + /// ignore unrecognizable parameters. + Unknown(String, String), + /// An unrecognized extended paramater as defined in + /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in + /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single + /// trailling asterisk is not included. Recipients should ignore unrecognizable parameters. + UnknownExt(String, ExtendedValue), } -/// A `Content-Disposition` header, (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266). +impl DispositionParam { + /// Returns `true` if the paramater is [`Name`](DispositionParam::Name). + #[inline] + pub fn is_name(&self) -> bool { + self.as_name().is_some() + } + + /// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename). + #[inline] + pub fn is_filename(&self) -> bool { + self.as_filename().is_some() + } + + /// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt). + #[inline] + pub fn is_filename_ext(&self) -> bool { + self.as_filename_ext().is_some() + } + + /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` + #[inline] + /// matches. + pub fn is_unknown<'a, T: AsRef>(&self, name: T) -> bool { + self.as_unknown(name).is_some() + } + + /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the + /// `name` matches. + #[inline] + pub fn is_unknown_ext<'a, T: AsRef>(&self, name: T) -> bool { + self.as_unknown_ext(name).is_some() + } + + /// Returns the name if applicable. + #[inline] + pub fn as_name<'a>(&'a self) -> Option<&'a str> { + match self { + DispositionParam::Name(ref name) => Some(name.as_str()), + _ => None, + } + } + + /// Returns the filename if applicable. + #[inline] + pub fn as_filename<'a>(&'a self) -> Option<&'a str> { + match self { + &DispositionParam::Filename(ref filename) => Some(filename.as_str()), + _ => None, + } + } + + /// Returns the filename* if applicable. + #[inline] + pub fn as_filename_ext<'a>(&'a self) -> Option<&'a ExtendedValue> { + match self { + &DispositionParam::FilenameExt(ref value) => Some(value), + _ => None, + } + } + + /// Returns the value of the unrecognized regular parameter if it is + /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. + #[inline] + pub fn as_unknown<'a, T: AsRef>(&'a self, name: T) -> Option<&'a str> { + match self { + &DispositionParam::Unknown(ref ext_name, ref value) + if ext_name.eq_ignore_ascii_case(name.as_ref()) => + { + Some(value.as_str()) + } + _ => None, + } + } + + /// Returns the value of the unrecognized extended parameter if it is + /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. + #[inline] + pub fn as_unknown_ext<'a, T: AsRef>( + &'a self, name: T, + ) -> Option<&'a ExtendedValue> { + match self { + &DispositionParam::UnknownExt(ref ext_name, ref value) + if ext_name.eq_ignore_ascii_case(name.as_ref()) => + { + Some(value) + } + _ => None, + } + } +} + +/// A *Content-Disposition* header. It is compatible to be used either as +/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) +/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as +/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) +/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). /// -/// The Content-Disposition response header field is used to convey -/// additional information about how to process the response payload, and -/// also can be used to attach additional metadata, such as the filename -/// to use when saving the response payload locally. +/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if +/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as +/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be +/// used to attach additional metadata, such as the filename to use when saving the response payload +/// locally. +/// +/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that +/// can be used on the subpart of a multipart body to give information about the field it applies to. +/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body +/// itself, *Content-Disposition* has no effect. /// /// # ABNF @@ -65,88 +222,219 @@ pub enum DispositionParam { /// ext-token = /// ``` /// +/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within +/// *multipart/form-data*. +/// /// # Example /// /// ``` -/// use actix_web::http::header::{ContentDisposition, DispositionType, DispositionParam, Charset}; +/// use actix_web::http::header::{ +/// Charset, ContentDisposition, DispositionParam, DispositionType, +/// ExtendedValue, +/// }; /// /// let cd1 = ContentDisposition { /// disposition: DispositionType::Attachment, -/// parameters: vec![DispositionParam::Filename( -/// Charset::Iso_8859_1, // The character set for the bytes of the filename -/// None, // The optional language tag (see `language-tag` crate) -/// b"\xa9 Copyright 1989.txt".to_vec() // the actual bytes of the filename -/// )] +/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue { +/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename +/// language_tag: None, // The optional language tag (see `language-tag` crate) +/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename +/// })], /// }; +/// assert!(cd1.is_attachment()); +/// assert!(cd1.get_filename_ext().is_some()); /// /// let cd2 = ContentDisposition { -/// disposition: DispositionType::Inline, -/// parameters: vec![DispositionParam::Filename( -/// Charset::Ext("UTF-8".to_owned()), -/// None, -/// "\u{2764}".as_bytes().to_vec() -/// )] +/// disposition: DispositionType::FormData, +/// parameters: vec![ +/// DispositionParam::Name(String::from("file")), +/// DispositionParam::Filename(String::from("bill.odt")), +/// ], /// }; +/// assert_eq!(cd2.get_name(), Some("file")); // field name +/// assert_eq!(cd2.get_filename(), Some("bill.odt")); /// ``` +/// +/// # WARN +/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly +/// change to match local file system conventions if applicable, and do not use directory path +/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3) +/// . #[derive(Clone, Debug, PartialEq)] pub struct ContentDisposition { - /// The disposition + /// The disposition type pub disposition: DispositionType, /// Disposition parameters pub parameters: Vec, } + impl ContentDisposition { - /// Parse a raw Content-Disposition header value + /// Parse a raw Content-Disposition header value. pub fn from_raw(hv: &header::HeaderValue) -> Result { - header::from_one_raw_str(Some(hv)).and_then(|s: String| { - let mut sections = s.split(';'); - let disposition = match sections.next() { - Some(s) => s.trim(), - None => return Err(::error::ParseError::Header), - }; + // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible + // ASCII characters. So `hv.as_bytes` is necessary here. + let hv = String::from_utf8(hv.as_bytes().to_vec()) + .map_err(|_| ::error::ParseError::Header)?; + let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); + if disp_type.len() == 0 { + return Err(::error::ParseError::Header); + } + let mut cd = ContentDisposition { + disposition: disp_type.into(), + parameters: Vec::new(), + }; - let mut cd = ContentDisposition { - disposition: if disposition.eq_ignore_ascii_case("inline") { - DispositionType::Inline - } else if disposition.eq_ignore_ascii_case("attachment") { - DispositionType::Attachment - } else { - DispositionType::Ext(disposition.to_owned()) - }, - parameters: Vec::new(), - }; - - for section in sections { - let mut parts = section.splitn(2, '='); - - let key = if let Some(key) = parts.next() { - key.trim() - } else { - return Err(::error::ParseError::Header); - }; - - let val = if let Some(val) = parts.next() { - val.trim() - } else { - return Err(::error::ParseError::Header); - }; - - cd.parameters.push( - if key.eq_ignore_ascii_case("filename") { - DispositionParam::Filename( - Charset::Ext("UTF-8".to_owned()), None, - val.trim_matches('"').as_bytes().to_owned()) - } else if key.eq_ignore_ascii_case("filename*") { - let extended_value = try!(header::parse_extended_value(val)); - DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value) - } else { - DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned()) - } - ); + while left.len() > 0 { + let (param_name, new_left) = split_once_and_trim(left, '='); + if param_name.len() == 0 || param_name == "*" || new_left.len() == 0 { + return Err(::error::ParseError::Header); } + left = new_left; + if param_name.ends_with('*') { + // extended parameters + let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk + let (ext_value, new_left) = split_once_and_trim(left, ';'); + left = new_left; + let ext_value = header::parse_extended_value(ext_value)?; - Ok(cd) - }) + let param = if param_name.eq_ignore_ascii_case("filename") { + DispositionParam::FilenameExt(ext_value) + } else { + DispositionParam::UnknownExt(param_name.to_owned(), ext_value) + }; + cd.parameters.push(param); + } else { + // regular parameters + let value = if left.starts_with('\"') { + // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 + let mut escaping = false; + let mut quoted_string = vec![]; + let mut end = None; + // search for closing quote + for (i, &c) in left.as_bytes().iter().skip(1).enumerate() { + if escaping { + escaping = false; + quoted_string.push(c); + } else { + if c == 0x5c + // backslash + { + escaping = true; + } else if c == 0x22 + // double quote + { + end = Some(i + 1); // cuz skipped 1 for the leading quote + break; + } else { + quoted_string.push(c); + } + } + } + left = &left[end.ok_or(::error::ParseError::Header)? + 1..]; + left = split_once(left, ';').1.trim_left(); + // In fact, it should not be Err if the above code is correct. + let quoted_string = String::from_utf8(quoted_string) + .map_err(|_| ::error::ParseError::Header)?; + quoted_string + } else { + // token: won't contains semicolon according to RFC 2616 Section 2.2 + let (token, new_left) = split_once_and_trim(left, ';'); + left = new_left; + token.to_owned() + }; + if value.len() == 0 { + return Err(::error::ParseError::Header); + } + + let param = if param_name.eq_ignore_ascii_case("name") { + DispositionParam::Name(value) + } else if param_name.eq_ignore_ascii_case("filename") { + DispositionParam::Filename(value) + } else { + DispositionParam::Unknown(param_name.to_owned(), value) + }; + cd.parameters.push(param); + } + } + + Ok(cd) + } + + /// Returns `true` if it is [`Inline`](DispositionType::Inline). + pub fn is_inline(&self) -> bool { + match self.disposition { + DispositionType::Inline => true, + _ => false, + } + } + + /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). + pub fn is_attachment(&self) -> bool { + match self.disposition { + DispositionType::Attachment => true, + _ => false, + } + } + + /// Returns `true` if it is [`FormData`](DispositionType::FormData). + pub fn is_form_data(&self) -> bool { + match self.disposition { + DispositionType::FormData => true, + _ => false, + } + } + + /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. + pub fn is_ext>(&self, disp_type: T) -> bool { + match self.disposition { + DispositionType::Ext(ref t) + if t.eq_ignore_ascii_case(disp_type.as_ref()) => + { + true + } + _ => false, + } + } + + /// Return the value of *name* if exists. + pub fn get_name<'a>(&'a self) -> Option<&'a str> { + self.parameters.iter().filter_map(|p| p.as_name()).nth(0) + } + + /// Return the value of *filename* if exists. + pub fn get_filename<'a>(&'a self) -> Option<&'a str> { + self.parameters + .iter() + .filter_map(|p| p.as_filename()) + .nth(0) + } + + /// Return the value of *filename\** if exists. + pub fn get_filename_ext<'a>(&'a self) -> Option<&'a ExtendedValue> { + self.parameters + .iter() + .filter_map(|p| p.as_filename_ext()) + .nth(0) + } + + /// Return the value of the parameter which the `name` matches. + pub fn get_unknown<'a, T: AsRef>(&'a self, name: T) -> Option<&'a str> { + let name = name.as_ref(); + self.parameters + .iter() + .filter_map(|p| p.as_unknown(name)) + .nth(0) + } + + /// Return the value of the extended parameter which the `name` matches. + pub fn get_unknown_ext<'a, T: AsRef>( + &'a self, name: T, + ) -> Option<&'a ExtendedValue> { + let name = name.as_ref(); + self.parameters + .iter() + .filter_map(|p| p.as_unknown_ext(name)) + .nth(0) } } @@ -174,67 +462,76 @@ impl Header for ContentDisposition { } } -impl fmt::Display for ContentDisposition { +impl fmt::Display for DispositionType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.disposition { - DispositionType::Inline => try!(write!(f, "inline")), - DispositionType::Attachment => try!(write!(f, "attachment")), - DispositionType::Ext(ref s) => try!(write!(f, "{}", s)), + match self { + DispositionType::Inline => write!(f, "inline"), + DispositionType::Attachment => write!(f, "attachment"), + DispositionType::FormData => write!(f, "form-data"), + DispositionType::Ext(ref s) => write!(f, "{}", s), } - for param in &self.parameters { - match *param { - DispositionParam::Filename(ref charset, ref opt_lang, ref bytes) => { - let mut use_simple_format: bool = false; - if opt_lang.is_none() { - if let Charset::Ext(ref ext) = *charset { - if ext.eq_ignore_ascii_case("utf-8") { - use_simple_format = true; - } - } - } - if use_simple_format { - use std::str; - try!(write!(f, "; filename=\"{}\"", - match str::from_utf8(bytes) { - Ok(s) => s, - Err(_) => return Err(fmt::Error), - })); - } else { - try!(write!(f, "; filename*={}'", charset)); - if let Some(ref lang) = *opt_lang { - try!(write!(f, "{}", lang)); - }; - try!(write!(f, "'")); - try!(header::http_percent_encode(f, bytes)) - } - }, - DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)), + } +} + +impl fmt::Display for DispositionParam { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and + // backslash should be escaped in quoted-string (i.e. "foobar"). + // Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . + lazy_static! { + static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); + } + match self { + DispositionParam::Name(ref value) => write!(f, "name={}", value), + DispositionParam::Filename(ref value) => { + write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) + } + DispositionParam::Unknown(ref name, ref value) => write!( + f, + "{}=\"{}\"", + name, + &RE.replace_all(value, "\\$0").as_ref() + ), + DispositionParam::FilenameExt(ref ext_value) => { + write!(f, "filename*={}", ext_value) + } + DispositionParam::UnknownExt(ref name, ref ext_value) => { + write!(f, "{}*={}", name, ext_value) } } - Ok(()) + } +} + +impl fmt::Display for ContentDisposition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.disposition)?; + self.parameters + .iter() + .map(|param| write!(f, "; {}", param)) + .collect() } } #[cfg(test)] mod tests { - use super::{ContentDisposition,DispositionType,DispositionParam}; - use header::HeaderValue; + use super::{ContentDisposition, DispositionParam, DispositionType}; use header::shared::Charset; + use header::{ExtendedValue, HeaderValue}; #[test] - fn test_from_raw() { + fn test_from_raw_basic() { assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); - let a = HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\""); + let a = HeaderValue::from_static( + "form-data; dummy=3; name=upload; filename=\"sample.png\"", + ); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { - disposition: DispositionType::Ext("form-data".to_owned()), + disposition: DispositionType::FormData, parameters: vec![ - DispositionParam::Ext("dummy".to_owned(), "3".to_owned()), - DispositionParam::Ext("name".to_owned(), "upload".to_owned()), - DispositionParam::Filename( - Charset::Ext("UTF-8".to_owned()), - None, - "sample.png".bytes().collect()) ] + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], }; assert_eq!(a, b); @@ -242,44 +539,386 @@ mod tests { let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::Filename( - Charset::Ext("UTF-8".to_owned()), - None, - "image.jpg".bytes().collect()) ] + parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], }; assert_eq!(a, b); - let a = HeaderValue::from_static("attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); + let a = HeaderValue::from_static("inline; filename=image.jpg"); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"", + ); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::Filename( - Charset::Ext("UTF-8".to_owned()), - None, - vec![0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, - 0xe2, 0x82, 0xac, 0x20, b'r', b'a', b't', b'e', b's']) ] + parameters: vec![DispositionParam::Unknown( + String::from("creation-date"), + "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(), + )], }; assert_eq!(a, b); } #[test] - fn test_display() { - let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; + fn test_from_raw_extended() { + let a = HeaderValue::from_static( + "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Ext(String::from("UTF-8")), + language_tag: None, + value: vec![ + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, + b'r', b'a', b't', b'e', b's', + ], + })], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Ext(String::from("UTF-8")), + language_tag: None, + value: vec![ + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, + b'r', b'a', b't', b'e', b's', + ], + })], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_extra_whitespace() { + let a = HeaderValue::from_static( + "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_unordered() { + let a = HeaderValue::from_static( + "form-data; dummy=3; filename=\"sample.png\" ; name=upload;", + // Actually, a trailling semolocon is not compliant. But it is fine to accept. + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + DispositionParam::Name("upload".to_owned()), + ], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_str( + "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"", + ).unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![ + DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: None, + value: b"foo-\xe4.html".to_vec(), + }), + DispositionParam::Filename("foo-ä.html".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_only_disp() { + let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) + .unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![], + }; + assert_eq!(a, b); + + let a = + ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![], + }; + assert_eq!(a, b); + + let a = ContentDisposition::from_raw(&HeaderValue::from_static( + "unknown-disp-param", + )).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Ext(String::from("unknown-disp-param")), + parameters: vec![], + }; + assert_eq!(a, b); + } + + #[test] + fn from_raw_with_mixed_case() { + let a = HeaderValue::from_str( + "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"", + ).unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![ + DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: None, + value: b"foo-\xe4.html".to_vec(), + }), + DispositionParam::Filename("foo-ä.html".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn from_raw_with_unicode() { + /* RFC7578 Section 4.2: + Some commonly deployed systems use multipart/form-data with file names directly encoded + including octets outside the US-ASCII range. The encoding used for the file names is + typically UTF-8, although HTML forms will use the charset associated with the form. + + Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. + (And now, only UTF-8 is handled by this implementation.) + */ + let a = + HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") + .unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name(String::from("upload")), + DispositionParam::Filename(String::from("文件.webp")), + ], + }; + assert_eq!(a, b); + + let a = + HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx\"").unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name(String::from("upload")), + DispositionParam::Filename(String::from( + "余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx", + )), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_escape() { + let a = HeaderValue::from_static( + "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename( + ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g'] + .iter() + .collect(), + ), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_semicolon() { + let a = + HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![DispositionParam::Filename(String::from( + "A semicolon here;.pdf", + ))], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_uncessary_percent_decode() { + let a = HeaderValue::from_static( + "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name("photo".to_owned()), + DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")), + ], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "form-data; name=photo; filename=\"%74%65%73%74.png\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name("photo".to_owned()), + DispositionParam::Filename(String::from("%74%65%73%74.png")), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_param_value_missing() { + let a = HeaderValue::from_static("form-data; name=upload ; filename="); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf"); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; filename= "); + assert!(ContentDisposition::from_raw(&a).is_err()); + } + + #[test] + fn test_from_raw_param_name_missing() { + let a = HeaderValue::from_static("inline; =\"test.txt\""); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; =diary.odt"); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; ="); + assert!(ContentDisposition::from_raw(&a).is_err()); + } + + #[test] + fn test_display_extended() { + let as_string = + "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; let a = HeaderValue::from_static(as_string); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}",a); + let display_rendered = format!("{}", a); assert_eq!(as_string, display_rendered); - let a = HeaderValue::from_static("attachment; filename*=UTF-8''black%20and%20white.csv"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}",a); - assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered); - let a = HeaderValue::from_static("attachment; filename=colourful.csv"); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}",a); - assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered); + let display_rendered = format!("{}", a); + assert_eq!( + "attachment; filename=\"colourful.csv\"".to_owned(), + display_rendered + ); + } + + #[test] + fn test_display_quote() { + let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\""; + as_string + .find(['\\', '\"'].iter().collect::().as_str()) + .unwrap(); // ensure `\"` is there + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!(as_string, display_rendered); + } + + #[test] + fn test_display_space_tab() { + let as_string = "form-data; name=upload; filename=\"Space here.png\""; + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!(as_string, display_rendered); + + let a: ContentDisposition = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))], + }; + let display_rendered = format!("{}", a); + assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered); + } + + #[test] + fn test_display_control_characters() { + /* let a = "attachment; filename=\"carriage\rreturn.png\""; + let a = HeaderValue::from_static(a); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!( + "attachment; filename=\"carriage\\\rreturn.png\"", + display_rendered + );*/ + // No way to create a HeaderValue containing a carriage return. + + let a: ContentDisposition = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))], + }; + let display_rendered = format!("{}", a); + assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered); + } + + #[test] + fn test_param_methods() { + let param = DispositionParam::Filename(String::from("sample.txt")); + assert!(param.is_filename()); + assert_eq!(param.as_filename().unwrap(), "sample.txt"); + + let param = DispositionParam::Unknown(String::from("foo"), String::from("bar")); + assert!(param.is_unknown("foo")); + assert_eq!(param.as_unknown("fOo"), Some("bar")); + } + + #[test] + fn test_disposition_methods() { + let cd = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], + }; + assert_eq!(cd.get_name(), Some("upload")); + assert_eq!(cd.get_unknown("dummy"), Some("3")); + assert_eq!(cd.get_filename(), Some("sample.png")); + assert_eq!(cd.get_unknown_ext("dummy"), None); + assert_eq!(cd.get_unknown("duMMy"), Some("3")); } } diff --git a/src/header/mod.rs b/src/header/mod.rs index 291bc6ea..cdd2ad20 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -263,8 +263,10 @@ where // From hyper v0.11.27 src/header/parsing.rs -/// An extended header parameter value (i.e., tagged with a character set and optionally, -/// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +/// The value part of an extended parameter consisting of three parts: +/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), +/// and a character sequence representing the actual value (`value`), separated by single quote +/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). #[derive(Clone, Debug, PartialEq)] pub struct ExtendedValue { /// The character set that is used to encode the `value` to a string. diff --git a/src/multipart.rs b/src/multipart.rs index d4b6059f..dbf3d179 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -758,11 +758,11 @@ mod tests { let cd = field.content_disposition().unwrap(); assert_eq!( cd.disposition, - DispositionType::Ext("form-data".into()) + DispositionType::FormData ); assert_eq!( cd.parameters[0], - DispositionParam::Ext("name".into(), "file".into()) + DispositionParam::Name("file".into()) ); } assert_eq!(field.content_type().type_(), mime::TEXT); From 9f5641c85b5c44eec0b4b9ab3e5c29c8e65c8682 Mon Sep 17 00:00:00 2001 From: Douman Date: Mon, 13 Aug 2018 17:37:00 +0300 Subject: [PATCH 0594/1635] Add mention of reworked Content-Disposition --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3dbb3795..ac302ed0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ * native-tls - 0.2 +* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461 + ### Fixed * Use zlib instead of raw deflate for decoding and encoding payloads with From 248bd388cadf30003e77b9167a8751653cde08ad Mon Sep 17 00:00:00 2001 From: Douman Date: Thu, 16 Aug 2018 16:11:15 +0300 Subject: [PATCH 0595/1635] Improve HTTP server docs (#470) --- src/lib.rs | 3 +- src/pipeline.rs | 4 +- src/server/http.rs | 4 ++ src/server/mod.rs | 112 ++++++++++++++++++++++++++++++++++++++++++- src/server/server.rs | 28 ++++++----- 5 files changed, 136 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ed02b1b6..c6d3453a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -251,7 +251,8 @@ pub mod dev { pub use context::Drain; pub use extractor::{FormConfig, PayloadConfig}; pub use handler::{AsyncResult, Handler}; - pub use httpmessage::{MessageBody, UrlEncoded}; + pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; + pub use pipeline::Pipeline; pub use httpresponse::HttpResponseBuilder; pub use info::ConnectionInfo; pub use json::{JsonBody, JsonConfig}; diff --git a/src/pipeline.rs b/src/pipeline.rs index 09c5e49d..7f206a9f 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -83,7 +83,7 @@ impl PipelineInfo { } impl> Pipeline { - pub fn new( + pub(crate) fn new( req: HttpRequest, mws: Rc>>>, handler: Rc, ) -> Pipeline { let mut info = PipelineInfo { @@ -475,7 +475,7 @@ impl ProcessResponse { } } } - Ok(Async::Ready(None)) => + Ok(Async::Ready(None)) => return Some(FinishingMiddlewares::init( info, mws, self.resp.take().unwrap(), )), diff --git a/src/server/http.rs b/src/server/http.rs index edf8aef6..e3740d95 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -31,6 +31,10 @@ use super::{ }; /// An HTTP Server +/// +/// By default it serves HTTP2 when HTTPs is enabled, +/// in order to change it, use `ServerFlags` that can be provided +/// to acceptor service. pub struct HttpServer where H: IntoHttpHandler + 'static, diff --git a/src/server/mod.rs b/src/server/mod.rs index 67952e43..f33a345e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,4 +1,111 @@ -//! Http server +//! Http server module +//! +//! The module contains everything necessary to setup +//! HTTP server. +//! +//! In order to start HTTP server, first you need to create and configure it +//! using factory that can be supplied to [new](fn.new.html). +//! +//! ## Factory +//! +//! Factory is a function that returns Application, describing how +//! to serve incoming HTTP requests. +//! +//! As the server uses worker pool, the factory function is restricted to trait bounds +//! `Sync + Send + 'static` so that each worker would be able to accept Application +//! without a need for synchronization. +//! +//! If you wish to share part of state among all workers you should +//! wrap it in `Arc` and potentially synchronization primitive like +//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html) +//! If the wrapped type is not thread safe. +//! +//! Note though that locking is not advisable for asynchronous programming +//! and you should minimize all locks in your request handlers +//! +//! ## HTTPS Support +//! +//! Actix-web provides support for major crates that provides TLS. +//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html) +//! that describes how HTTP Server accepts connections. +//! +//! For `bind` and `listen` there are corresponding `bind_with` and `listen_with` that accepts +//! these services. +//! +//! By default, acceptor would work with both HTTP2 and HTTP1 protocols. +//! But it can be controlled using [ServerFlags](struct.ServerFlags.html) which +//! can be supplied when creating `AcceptorService`. +//! +//! **NOTE:** `native-tls` doesn't support `HTTP2` yet +//! +//! ## Signal handling and shutdown +//! +//! By default HTTP Server listens for system signals +//! and, gracefully shuts down at most after 30 seconds. +//! +//! Both signal handling and shutdown timeout can be controlled +//! using corresponding methods. +//! +//! If worker, for some reason, unable to shut down within timeout +//! it is forcibly dropped. +//! +//! ## Example +//! +//! ```rust,ignore +//!extern crate actix; +//!extern crate actix_web; +//!extern crate rustls; +//! +//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder}; +//!use std::io::BufReader; +//!use rustls::internal::pemfile::{certs, rsa_private_keys}; +//!use rustls::{NoClientAuth, ServerConfig}; +//! +//!fn index(req: &HttpRequest) -> Result { +//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!")) +//!} +//! +//!fn load_ssl() -> ServerConfig { +//! use std::io::BufReader; +//! +//! const CERT: &'static [u8] = include_bytes!("../cert.pem"); +//! const KEY: &'static [u8] = include_bytes!("../key.pem"); +//! +//! let mut cert = BufReader::new(CERT); +//! let mut key = BufReader::new(KEY); +//! +//! let mut config = ServerConfig::new(NoClientAuth::new()); +//! let cert_chain = certs(&mut cert).unwrap(); +//! let mut keys = rsa_private_keys(&mut key).unwrap(); +//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); +//! +//! config +//!} +//! +//!fn main() { +//! let sys = actix::System::new("http-server"); +//! // load ssl keys +//! let config = load_ssl(); +//! +//! // Create acceptor service for only HTTP1 protocol +//! // You can use ::new(config) to leave defaults +//! let acceptor = server::RustlsAcceptor::with_flags(config, actix_web::server::ServerFlags::HTTP1); +//! +//! // create and start server at once +//! server::new(|| { +//! App::new() +//! // register simple handler, handle all methods +//! .resource("/index.html", |r| r.f(index)) +//! })) +//! }).bind_with("127.0.0.1:8080", acceptor) +//! .unwrap() +//! .start(); +//! +//! println!("Started http server: 127.0.0.1:8080"); +//! //Run system so that server would start accepting connections +//! let _ = sys.run(); +//!} +//! ``` use std::net::Shutdown; use std::rc::Rc; use std::{io, net, time}; @@ -83,8 +190,11 @@ where } bitflags! { + ///Flags that can be used to configure HTTP Server. pub struct ServerFlags: u8 { + ///Use HTTP1 protocol const HTTP1 = 0b0000_0001; + ///Use HTTP2 protocol const HTTP2 = 0b0000_0010; } } diff --git a/src/server/server.rs b/src/server/server.rs index 9e25efc5..552ba8ee 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -13,6 +13,8 @@ use super::accept::{AcceptLoop, AcceptNotify, Command}; use super::worker::{StopWorker, Worker, WorkerClient, Conn}; use super::{PauseServer, ResumeServer, StopServer, Token}; +///Describes service that could be used +///with [Server](struct.Server.html) pub trait Service: Send + 'static { /// Clone service fn clone(&self) -> Box; @@ -31,6 +33,8 @@ impl Service for Box { } } +///Describes the way serivce handles incoming +///TCP connections. pub trait ServiceHandler { /// Handle incoming stream fn handle(&mut self, token: Token, io: net::TcpStream, peer: Option); @@ -43,6 +47,7 @@ pub(crate) enum ServerCommand { WorkerDied(usize), } +///Server pub struct Server { threads: usize, workers: Vec<(usize, Addr)>, @@ -80,7 +85,7 @@ impl Server { maxconnrate: 256, } } - + /// Set number of workers to start. /// /// By default http server uses number of available logical cpu as threads @@ -108,7 +113,7 @@ impl Server { /// /// By default max connections is set to a 256. pub fn maxconnrate(mut self, num: usize) -> Self { - self.maxconnrate= num; + self.maxconnrate = num; self } @@ -146,7 +151,7 @@ impl Server { } /// Add new service to server - pub fn service(mut self, srv: T) -> Self + pub fn service(mut self, srv: T) -> Self where T: Into<(Box, Vec<(Token, net::TcpListener)>)> { @@ -171,7 +176,7 @@ impl Server { /// /// fn main() { /// Server::new(). - /// .service( + /// .service( /// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) /// .bind("127.0.0.1:0") /// .expect("Can not bind to 127.0.0.1:0")) @@ -184,7 +189,7 @@ impl Server { sys.run(); } - /// Start + /// Starts Server Actor and returns its address pub fn start(mut self) -> Addr { if self.sockets.is_empty() { panic!("Service should have at least one bound socket"); @@ -393,7 +398,8 @@ impl StreamHandler for Server { } #[derive(Clone, Default)] -pub struct Connections (Arc); +///Contains information about connection. +pub struct Connections(Arc); impl Connections { fn new(notify: AcceptNotify, maxconn: usize, maxconnrate: usize) -> Self { @@ -458,7 +464,7 @@ impl ConnectionsInner { self.notify.notify(); } } - + fn notify_maxconnrate(&self, connrate: usize) { if connrate > self.maxconnrate_low && connrate <= self.maxconnrate { self.notify.notify(); @@ -468,8 +474,8 @@ impl ConnectionsInner { } /// Type responsible for max connection stat. -/// -/// Max connections stat get updated on drop. +/// +/// Max connections stat get updated on drop. pub struct ConnectionTag(Arc); impl ConnectionTag { @@ -487,8 +493,8 @@ impl Drop for ConnectionTag { } /// Type responsible for max connection rate stat. -/// -/// Max connections rate stat get updated on drop. +/// +/// Max connections rate stat get updated on drop. pub struct ConnectionRateTag (Arc); impl ConnectionRateTag { From eb1e9a785f72e9702a773395dfff8e437ab74635 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 16 Aug 2018 20:29:06 -0700 Subject: [PATCH 0596/1635] allow to use fn with multiple arguments with .with()/.with_async() --- CHANGES.md | 4 +- MIGRATION.md | 6 ++ src/application.rs | 3 +- src/extractor.rs | 4 +- src/json.rs | 2 +- src/resource.rs | 3 +- src/route.rs | 27 +++---- src/router.rs | 3 +- src/scope.rs | 3 +- src/with.rs | 156 +++++++++++++++++++++++++++++++++-------- tests/test_handlers.rs | 4 +- tests/test_server.rs | 9 +-- 12 files changed, 169 insertions(+), 55 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ac302ed0..e73b929a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.4] - 2018-08-xx +## [0.8.0] - 2018-08-xx ### Added @@ -12,6 +12,8 @@ ### Changed +* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`. + * native-tls - 0.2 * `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461 diff --git a/MIGRATION.md b/MIGRATION.md index 29bf0c34..910e99a4 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,9 @@ +## 0.8 + +* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple + even for handler with one parameter. + + ## 0.7 * `HttpRequest` does not implement `Stream` anymore. If you need to read request payload diff --git a/src/application.rs b/src/application.rs index 6885185f..4c8946c4 100644 --- a/src/application.rs +++ b/src/application.rs @@ -12,6 +12,7 @@ use resource::Resource; use router::{ResourceDef, Router}; use scope::Scope; use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; +use with::WithFactory; /// Application pub struct HttpApplication { @@ -249,7 +250,7 @@ where /// ``` pub fn route(mut self, path: &str, method: Method, f: F) -> App where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { diff --git a/src/extractor.rs b/src/extractor.rs index 233ad6ce..6d156d47 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -332,7 +332,7 @@ impl fmt::Display for Form { /// |r| { /// r.method(http::Method::GET) /// // register form handler and change form extractor configuration -/// .with_config(index, |cfg| {cfg.limit(4096);}) +/// .with_config(index, |cfg| {cfg.0.limit(4096);}) /// }, /// ); /// } @@ -427,7 +427,7 @@ impl FromRequest for Bytes { /// let app = App::new().resource("/index.html", |r| { /// r.method(http::Method::GET) /// .with_config(index, |cfg| { // <- register handler with extractor params -/// cfg.limit(4096); // <- limit size of the payload +/// cfg.0.limit(4096); // <- limit size of the payload /// }) /// }); /// } diff --git a/src/json.rs b/src/json.rs index c76aeaa7..86eefca9 100644 --- a/src/json.rs +++ b/src/json.rs @@ -172,7 +172,7 @@ where /// let app = App::new().resource("/index.html", |r| { /// r.method(http::Method::POST) /// .with_config(index, |cfg| { -/// cfg.limit(4096) // <- change json extractor configuration +/// cfg.0.limit(4096) // <- change json extractor configuration /// .error_handler(|err, req| { // <- create custom error response /// error::InternalError::from_response( /// err, HttpResponse::Conflict().finish()).into() diff --git a/src/resource.rs b/src/resource.rs index 1bf8d88f..d884dd44 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -13,6 +13,7 @@ use middleware::Middleware; use pred; use route::Route; use router::ResourceDef; +use with::WithFactory; #[derive(Copy, Clone)] pub(crate) struct RouteId(usize); @@ -217,7 +218,7 @@ impl Resource { /// ``` pub fn with(&mut self, handler: F) where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { diff --git a/src/route.rs b/src/route.rs index d383d90b..e2635aa6 100644 --- a/src/route.rs +++ b/src/route.rs @@ -16,7 +16,7 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; -use with::{With, WithAsync}; +use with::{WithAsyncFactory, WithFactory}; /// Resource route definition /// @@ -166,15 +166,15 @@ impl Route { /// ``` pub fn with(&mut self, handler: F) where - F: Fn(T) -> R + 'static, + F: WithFactory + 'static, R: Responder + 'static, T: FromRequest + 'static, { - self.h(With::new(handler, ::default())); + self.h(handler.create()); } /// Set handler function. Same as `.with()` but it allows to configure - /// extractor. + /// extractor. Configuration closure accepts config objects as tuple. /// /// ```rust /// # extern crate bytes; @@ -192,21 +192,21 @@ impl Route { /// let app = App::new().resource("/index.html", |r| { /// r.method(http::Method::GET) /// .with_config(index, |cfg| { // <- register handler - /// cfg.limit(4096); // <- limit size of the payload + /// cfg.0.limit(4096); // <- limit size of the payload /// }) /// }); /// } /// ``` pub fn with_config(&mut self, handler: F, cfg_f: C) where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, C: FnOnce(&mut T::Config), { let mut cfg = ::default(); cfg_f(&mut cfg); - self.h(With::new(handler, cfg)); + self.h(handler.create_with_config(cfg)); } /// Set async handler function, use request extractor for parameters. @@ -240,17 +240,18 @@ impl Route { /// ``` pub fn with_async(&mut self, handler: F) where - F: Fn(T) -> R + 'static, + F: WithAsyncFactory, R: Future + 'static, I: Responder + 'static, E: Into + 'static, T: FromRequest + 'static, { - self.h(WithAsync::new(handler, ::default())); + self.h(handler.create()); } /// Set async handler function, use request extractor for parameters. - /// This method allows to configure extractor. + /// This method allows to configure extractor. Configuration closure + /// accepts config objects as tuple. /// /// ```rust /// # extern crate bytes; @@ -275,14 +276,14 @@ impl Route { /// "/{username}/index.html", // <- define path parameters /// |r| r.method(http::Method::GET) /// .with_async_config(index, |cfg| { - /// cfg.limit(4096); + /// cfg.0.limit(4096); /// }), /// ); // <- use `with` extractor /// } /// ``` pub fn with_async_config(&mut self, handler: F, cfg: C) where - F: Fn(T) -> R + 'static, + F: WithAsyncFactory, R: Future + 'static, I: Responder + 'static, E: Into + 'static, @@ -291,7 +292,7 @@ impl Route { { let mut extractor_cfg = ::default(); cfg(&mut extractor_cfg); - self.h(WithAsync::new(handler, extractor_cfg)); + self.h(handler.create_with_config(extractor_cfg)); } } diff --git a/src/router.rs b/src/router.rs index ff52eac5..6dc6224a 100644 --- a/src/router.rs +++ b/src/router.rs @@ -17,6 +17,7 @@ use pred::Predicate; use resource::{DefaultResource, Resource}; use scope::Scope; use server::Request; +use with::WithFactory; #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) enum ResourceId { @@ -398,7 +399,7 @@ impl Router { pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { diff --git a/src/scope.rs b/src/scope.rs index d8a0a81a..baf891c3 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -17,6 +17,7 @@ use pred::Predicate; use resource::{DefaultResource, Resource}; use router::{ResourceDef, Router}; use server::Request; +use with::WithFactory; /// Resources scope /// @@ -222,7 +223,7 @@ impl Scope { /// ``` pub fn route(mut self, path: &str, method: Method, f: F) -> Scope where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { diff --git a/src/with.rs b/src/with.rs index 0af626c8..caffe0ac 100644 --- a/src/with.rs +++ b/src/with.rs @@ -7,24 +7,74 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -pub(crate) struct With +trait FnWith: 'static { + fn call_with(self: &Self, T) -> R; +} + +impl R + 'static> FnWith for F { + #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))] + fn call_with(self: &Self, arg: T) -> R { + (*self)(arg) + } +} + +#[doc(hidden)] +pub trait WithFactory: 'static +where T: FromRequest, + R: Responder, +{ + fn create(self) -> With; + + fn create_with_config(self, T::Config) -> With; +} + +#[doc(hidden)] +pub trait WithAsyncFactory: 'static +where T: FromRequest, + R: Future, + I: Responder, + E: Into, +{ + fn create(self) -> WithAsync; + + fn create_with_config(self, T::Config) -> WithAsync; +} + +// impl WithFactory<(T1, T2, T3), S, R> for F +// where F: Fn(T1, T2, T3) -> R + 'static, +// T1: FromRequest + 'static, +// T2: FromRequest + 'static, +// T3: FromRequest + 'static, +// R: Responder + 'static, +// S: 'static, +// { +// fn create(self) -> With<(T1, T2, T3), S, R> { +// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), ( +// T1::Config::default(), T2::Config::default(), T3::Config::default())) +// } + +// fn create_with_config(self, cfg: (T1::Config, T2::Config, T3::Config,)) -> With<(T1, T2, T3), S, R> { +// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), cfg) +// } +// } + +#[doc(hidden)] +pub struct With where - F: Fn(T) -> R, T: FromRequest, S: 'static, { - hnd: Rc, + hnd: Rc>, cfg: Rc, _s: PhantomData, } -impl With +impl With where - F: Fn(T) -> R, T: FromRequest, S: 'static, { - pub fn new(f: F, cfg: T::Config) -> Self { + pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { With { cfg: Rc::new(cfg), hnd: Rc::new(f), @@ -33,9 +83,8 @@ where } } -impl Handler for With +impl Handler for With where - F: Fn(T) -> R + 'static, R: Responder + 'static, T: FromRequest + 'static, S: 'static, @@ -60,24 +109,22 @@ where } } -struct WithHandlerFut +struct WithHandlerFut where - F: Fn(T) -> R, R: Responder, T: FromRequest + 'static, S: 'static, { started: bool, - hnd: Rc, + hnd: Rc>, cfg: Rc, req: HttpRequest, fut1: Option>>, fut2: Option>>, } -impl Future for WithHandlerFut +impl Future for WithHandlerFut where - F: Fn(T) -> R, R: Responder + 'static, T: FromRequest + 'static, S: 'static, @@ -108,7 +155,7 @@ where } }; - let item = match (*self.hnd)(item).respond_to(&self.req) { + let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) { Ok(item) => item.into(), Err(e) => return Err(e.into()), }; @@ -124,30 +171,29 @@ where } } -pub(crate) struct WithAsync +#[doc(hidden)] +pub struct WithAsync where - F: Fn(T) -> R, R: Future, I: Responder, E: Into, T: FromRequest, S: 'static, { - hnd: Rc, + hnd: Rc>, cfg: Rc, _s: PhantomData, } -impl WithAsync +impl WithAsync where - F: Fn(T) -> R, R: Future, I: Responder, E: Into, T: FromRequest, S: 'static, { - pub fn new(f: F, cfg: T::Config) -> Self { + pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { WithAsync { cfg: Rc::new(cfg), hnd: Rc::new(f), @@ -156,9 +202,8 @@ where } } -impl Handler for WithAsync +impl Handler for WithAsync where - F: Fn(T) -> R + 'static, R: Future + 'static, I: Responder + 'static, E: Into + 'static, @@ -186,9 +231,8 @@ where } } -struct WithAsyncHandlerFut +struct WithAsyncHandlerFut where - F: Fn(T) -> R, R: Future + 'static, I: Responder + 'static, E: Into + 'static, @@ -196,7 +240,7 @@ where S: 'static, { started: bool, - hnd: Rc, + hnd: Rc>, cfg: Rc, req: HttpRequest, fut1: Option>>, @@ -204,9 +248,8 @@ where fut3: Option>>, } -impl Future for WithAsyncHandlerFut +impl Future for WithAsyncHandlerFut where - F: Fn(T) -> R, R: Future + 'static, I: Responder + 'static, E: Into + 'static, @@ -257,7 +300,64 @@ where } }; - self.fut2 = Some((*self.hnd)(item)); + self.fut2 = Some(self.hnd.as_ref().call_with(item)); self.poll() } } + + +macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => { + impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func + where Func: Fn($($T,)+) -> Res + 'static, + $($T: FromRequest + 'static,)+ + Res: Responder + 'static, + State: 'static, + { + fn create(self) -> With<($($T,)+), State, Res> { + With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) + } + + fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> { + With::new(move |($($n,)+)| (self)($($n,)+), cfg) + } + } +}); + +macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => { + impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func + where Func: Fn($($T,)+) -> Res + 'static, + $($T: FromRequest + 'static,)+ + Res: Future, + Item: Responder + 'static, + Err: Into, + State: 'static, + { + fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> { + WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) + } + + fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> { + WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg) + } + } +}); + +with_factory_tuple!((a, A)); +with_factory_tuple!((a, A), (b, B)); +with_factory_tuple!((a, A), (b, B), (c, C)); +with_factory_tuple!((a, A), (b, B), (c, C), (d, D)); +with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); +with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); +with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); +with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H)); +with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I)); + +with_async_factory_tuple!((a, A)); +with_async_factory_tuple!((a, A), (b, B)); +with_async_factory_tuple!((a, A), (b, B), (c, C)); +with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D)); +with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); +with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); +with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); +with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H)); +with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I)); diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index c86a3e9c..4243cd3a 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -208,7 +208,7 @@ fn test_form_extractor2() { r.route().with_config( |form: Form| format!("{}", form.username), |cfg| { - cfg.error_handler(|err, _| { + cfg.0.error_handler(|err, _| { error::InternalError::from_response( err, HttpResponse::Conflict().finish(), @@ -423,7 +423,7 @@ fn test_path_and_query_extractor2_async3() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { r.route().with( - |(data, p, _q): (Json, Path, Query)| { + |data: Json, p: Path, _: Query| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) diff --git a/tests/test_server.rs b/tests/test_server.rs index 5c438568..8739b4f7 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -13,8 +13,8 @@ extern crate tokio_reactor; extern crate tokio_tcp; use std::io::{Read, Write}; -use std::sync::{mpsc, Arc}; -use std::{net, thread, time}; +use std::sync::Arc; +use std::{thread, time}; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; @@ -32,7 +32,6 @@ use tokio::executor::current_thread; use tokio::runtime::current_thread::Runtime; use tokio_tcp::TcpStream; -use actix::System; use actix_web::*; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -60,11 +59,13 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] #[cfg(unix)] fn test_start() { + use std::{mpsc, net}; + let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(|| { - System::run(move || { + actix::System::run(move || { let srv = server::new(|| { vec![App::new().resource("/", |r| { r.method(http::Method::GET).f(|_| HttpResponse::Ok()) From a8405d0686c2e4fd85c173e61d3ac8617a8a2cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kornel=20Lesin=CC=81ski?= Date: Fri, 17 Aug 2018 13:12:47 +0100 Subject: [PATCH 0597/1635] Fix tests on Unix --- tests/test_server.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index 8739b4f7..36c1b6e6 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -59,13 +59,14 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] #[cfg(unix)] fn test_start() { - use std::{mpsc, net}; + use std::sync::mpsc; + use actix::System; let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); thread::spawn(|| { - actix::System::run(move || { + System::run(move || { let srv = server::new(|| { vec![App::new().resource("/", |r| { r.method(http::Method::GET).f(|_| HttpResponse::Ok()) @@ -118,6 +119,10 @@ fn test_start() { #[test] #[cfg(unix)] fn test_shutdown() { + use std::sync::mpsc; + use std::net; + use actix::System; + let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); @@ -157,6 +162,9 @@ fn test_shutdown() { #[test] #[cfg(unix)] fn test_panic() { + use std::sync::mpsc; + use actix::System; + let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); From bdc9a8bb07afb10df5cef54f4a5f8ab36c2e253a Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 17 Aug 2018 17:04:16 +0100 Subject: [PATCH 0598/1635] Optionally support tokio-uds's UnixStream as IoStream (#472) --- CHANGES.md | 2 ++ Cargo.toml | 6 ++++++ src/client/connector.rs | 4 ++++ src/lib.rs | 4 ++++ src/server/mod.rs | 18 ++++++++++++++++++ tests/test_client.rs | 10 ++++++++++ 6 files changed, 44 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index e73b929a..9dd908ae 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ * Allow to customize connection handshake process via `HttpServer::listen_with()` and `HttpServer::bind_with()` methods +* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472 + ### Changed * It is allowed to use function with up to 10 parameters for handler with `extractor parameters`. diff --git a/Cargo.toml b/Cargo.toml index 3bfac16c..3d72f41c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,9 @@ alpn = ["openssl", "tokio-openssl"] # rustls rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"] +# unix sockets +uds = ["tokio-uds"] + # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] @@ -112,6 +115,9 @@ tokio-rustls = { version = "0.7", optional = true } webpki = { version = "0.18", optional = true } webpki-roots = { version = "0.15", optional = true } +# unix sockets +tokio-uds = { version="0.2", optional = true } + # forked url_encoded itoa = "0.4" dtoa = "0.4" diff --git a/src/client/connector.rs b/src/client/connector.rs index ef66cd73..75b2e149 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1287,6 +1287,10 @@ impl Connection { } /// Create a new connection from an IO Stream + /// + /// The stream can be a `UnixStream` if the Unix-only "uds" feature is enabled. + /// + /// See also `ClientRequestBuilder::with_connection()`. pub fn from_stream(io: T) -> Connection { Connection::new(Key::empty(), None, Box::new(io)) } diff --git a/src/lib.rs b/src/lib.rs index c6d3453a..3f1dafc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,8 @@ //! * `tls` - enables ssl support via `native-tls` crate //! * `alpn` - enables ssl support via `openssl` crate, require for `http/2` //! support +//! * `uds` - enables support for making client requests via Unix Domain Sockets. +//! Unix only. Not necessary for *serving* requests. //! * `session` - enables session support, includes `ring` crate as //! dependency //! * `brotli` - enables `brotli` compression support, requires `c` @@ -120,6 +122,8 @@ extern crate tokio_io; extern crate tokio_reactor; extern crate tokio_tcp; extern crate tokio_timer; +#[cfg(all(unix, feature = "uds"))] +extern crate tokio_uds; extern crate url; #[macro_use] extern crate serde; diff --git a/src/server/mod.rs b/src/server/mod.rs index f33a345e..cccdf826 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -421,6 +421,24 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { } } +#[cfg(all(unix, feature = "uds"))] +impl IoStream for ::tokio_uds::UnixStream { + #[inline] + fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { + ::tokio_uds::UnixStream::shutdown(self, how) + } + + #[inline] + fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn set_linger(&mut self, _dur: Option) -> io::Result<()> { + Ok(()) + } +} + impl IoStream for TcpStream { #[inline] fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { diff --git a/tests/test_client.rs b/tests/test_client.rs index 5e685699..16d95bf2 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -5,6 +5,8 @@ extern crate bytes; extern crate flate2; extern crate futures; extern crate rand; +#[cfg(all(unix, feature = "uds"))] +extern crate tokio_uds; use std::io::Read; @@ -198,6 +200,14 @@ fn test_client_gzip_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } + +#[cfg(all(unix, feature = "uds"))] +#[test] +fn test_compatible_with_unix_socket_stream() { + let (stream, _) = tokio_uds::UnixStream::pair().unwrap(); + let _ = client::Connection::from_stream(stream); +} + #[cfg(feature = "brotli")] #[test] fn test_client_brotli_encoding() { From 56bc900a82e955fd58e2039695b4887d30386982 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 17 Aug 2018 19:53:16 +0300 Subject: [PATCH 0599/1635] Set minimum rustls version that fixes corruption (#474) --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d72f41c..ff8571ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,8 +110,8 @@ openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } #rustls -rustls = { version = "0.13", optional = true } -tokio-rustls = { version = "0.7", optional = true } +rustls = { version = "^0.13.1", optional = true } +tokio-rustls = { version = "^0.7.2", optional = true } webpki = { version = "0.18", optional = true } webpki-roots = { version = "0.15", optional = true } From e680541e10aff1fc6a4d271ab308516e835a73a0 Mon Sep 17 00:00:00 2001 From: Franz Gregor Date: Sat, 18 Aug 2018 19:32:28 +0200 Subject: [PATCH 0600/1635] Made extensions constructor public --- src/extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensions.rs b/src/extensions.rs index da7b5ba2..3e3f24a2 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -39,7 +39,7 @@ pub struct Extensions { impl Extensions { /// Create an empty `Extensions`. #[inline] - pub(crate) fn new() -> Extensions { + pub fn new() -> Extensions { Extensions { map: HashMap::default(), } From 986f19af8655b95ebbba3a2b8fe2829eca1c85c5 Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 21 Aug 2018 22:23:17 +0300 Subject: [PATCH 0601/1635] Revert back to serde_urlencoded dependecy (#479) --- Cargo.toml | 4 +- src/lib.rs | 2 +- src/serde_urlencoded/de.rs | 305 ------------------- src/serde_urlencoded/mod.rs | 121 -------- src/serde_urlencoded/ser/key.rs | 74 ----- src/serde_urlencoded/ser/mod.rs | 490 ------------------------------ src/serde_urlencoded/ser/pair.rs | 239 --------------- src/serde_urlencoded/ser/part.rs | 201 ------------ src/serde_urlencoded/ser/value.rs | 59 ---- 9 files changed, 2 insertions(+), 1493 deletions(-) delete mode 100644 src/serde_urlencoded/de.rs delete mode 100644 src/serde_urlencoded/mod.rs delete mode 100644 src/serde_urlencoded/ser/key.rs delete mode 100644 src/serde_urlencoded/ser/mod.rs delete mode 100644 src/serde_urlencoded/ser/pair.rs delete mode 100644 src/serde_urlencoded/ser/part.rs delete mode 100644 src/serde_urlencoded/ser/value.rs diff --git a/Cargo.toml b/Cargo.toml index ff8571ba..6437ec26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,9 +118,7 @@ webpki-roots = { version = "0.15", optional = true } # unix sockets tokio-uds = { version="0.2", optional = true } -# forked url_encoded -itoa = "0.4" -dtoa = "0.4" +serde_urlencoded = "^0.5.3" [dev-dependencies] env_logger = "0.5" diff --git a/src/lib.rs b/src/lib.rs index 3f1dafc1..72fe26c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,6 +127,7 @@ extern crate tokio_uds; extern crate url; #[macro_use] extern crate serde; +extern crate serde_urlencoded; #[cfg(feature = "brotli")] extern crate brotli2; extern crate encoding; @@ -184,7 +185,6 @@ mod resource; mod route; mod router; mod scope; -mod serde_urlencoded; mod uri; mod with; diff --git a/src/serde_urlencoded/de.rs b/src/serde_urlencoded/de.rs deleted file mode 100644 index ae14afbf..00000000 --- a/src/serde_urlencoded/de.rs +++ /dev/null @@ -1,305 +0,0 @@ -//! Deserialization support for the `application/x-www-form-urlencoded` format. - -use serde::de::Error as de_Error; -use serde::de::{ - self, DeserializeSeed, EnumAccess, IntoDeserializer, VariantAccess, Visitor, -}; - -use serde::de::value::MapDeserializer; -use std::borrow::Cow; -use std::io::Read; -use url::form_urlencoded::parse; -use url::form_urlencoded::Parse as UrlEncodedParse; - -#[doc(inline)] -pub use serde::de::value::Error; - -/// Deserializes a `application/x-wwww-url-encoded` value from a `&[u8]`. -/// -/// ```ignore -/// let meal = vec![ -/// ("bread".to_owned(), "baguette".to_owned()), -/// ("cheese".to_owned(), "comté".to_owned()), -/// ("meat".to_owned(), "ham".to_owned()), -/// ("fat".to_owned(), "butter".to_owned()), -/// ]; -/// -/// assert_eq!( -/// serde_urlencoded::from_bytes::>( -/// b"bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter"), -/// Ok(meal)); -/// ``` -pub fn from_bytes<'de, T>(input: &'de [u8]) -> Result -where - T: de::Deserialize<'de>, -{ - T::deserialize(Deserializer::new(parse(input))) -} - -/// Deserializes a `application/x-wwww-url-encoded` value from a `&str`. -/// -/// ```ignore -/// let meal = vec![ -/// ("bread".to_owned(), "baguette".to_owned()), -/// ("cheese".to_owned(), "comté".to_owned()), -/// ("meat".to_owned(), "ham".to_owned()), -/// ("fat".to_owned(), "butter".to_owned()), -/// ]; -/// -/// assert_eq!( -/// serde_urlencoded::from_str::>( -/// "bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter"), -/// Ok(meal)); -/// ``` -pub fn from_str<'de, T>(input: &'de str) -> Result -where - T: de::Deserialize<'de>, -{ - from_bytes(input.as_bytes()) -} - -#[allow(dead_code)] -/// Convenience function that reads all bytes from `reader` and deserializes -/// them with `from_bytes`. -pub fn from_reader(mut reader: R) -> Result -where - T: de::DeserializeOwned, - R: Read, -{ - let mut buf = vec![]; - reader - .read_to_end(&mut buf) - .map_err(|e| de::Error::custom(format_args!("could not read input: {}", e)))?; - from_bytes(&buf) -} - -/// A deserializer for the `application/x-www-form-urlencoded` format. -/// -/// * Supported top-level outputs are structs, maps and sequences of pairs, -/// with or without a given length. -/// -/// * Main `deserialize` methods defers to `deserialize_map`. -/// -/// * Everything else but `deserialize_seq` and `deserialize_seq_fixed_size` -/// defers to `deserialize`. -pub struct Deserializer<'de> { - inner: MapDeserializer<'de, PartIterator<'de>, Error>, -} - -impl<'de> Deserializer<'de> { - /// Returns a new `Deserializer`. - pub fn new(parser: UrlEncodedParse<'de>) -> Self { - Deserializer { - inner: MapDeserializer::new(PartIterator(parser)), - } - } -} - -impl<'de> de::Deserializer<'de> for Deserializer<'de> { - type Error = Error; - - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - self.deserialize_map(visitor) - } - - fn deserialize_map(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_map(self.inner) - } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_seq(self.inner) - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - self.inner.end()?; - visitor.visit_unit() - } - - forward_to_deserialize_any! { - bool - u8 - u16 - u32 - u64 - i8 - i16 - i32 - i64 - f32 - f64 - char - str - string - option - bytes - byte_buf - unit_struct - newtype_struct - tuple_struct - struct - identifier - tuple - enum - ignored_any - } -} - -struct PartIterator<'de>(UrlEncodedParse<'de>); - -impl<'de> Iterator for PartIterator<'de> { - type Item = (Part<'de>, Part<'de>); - - fn next(&mut self) -> Option { - self.0.next().map(|(k, v)| (Part(k), Part(v))) - } -} - -struct Part<'de>(Cow<'de, str>); - -impl<'de> IntoDeserializer<'de> for Part<'de> { - type Deserializer = Self; - - fn into_deserializer(self) -> Self::Deserializer { - self - } -} - -macro_rules! forward_parsed_value { - ($($ty:ident => $method:ident,)*) => { - $( - fn $method(self, visitor: V) -> Result - where V: de::Visitor<'de> - { - match self.0.parse::<$ty>() { - Ok(val) => val.into_deserializer().$method(visitor), - Err(e) => Err(de::Error::custom(e)) - } - } - )* - } -} - -impl<'de> de::Deserializer<'de> for Part<'de> { - type Error = Error; - - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - self.0.into_deserializer().deserialize_any(visitor) - } - - fn deserialize_option(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_some(self) - } - - fn deserialize_enum( - self, _name: &'static str, _variants: &'static [&'static str], visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_enum(ValueEnumAccess { value: self.0 }) - } - - forward_to_deserialize_any! { - char - str - string - unit - bytes - byte_buf - unit_struct - newtype_struct - tuple_struct - struct - identifier - tuple - ignored_any - seq - map - } - - forward_parsed_value! { - bool => deserialize_bool, - u8 => deserialize_u8, - u16 => deserialize_u16, - u32 => deserialize_u32, - u64 => deserialize_u64, - i8 => deserialize_i8, - i16 => deserialize_i16, - i32 => deserialize_i32, - i64 => deserialize_i64, - f32 => deserialize_f32, - f64 => deserialize_f64, - } -} - -/// Provides access to a keyword which can be deserialized into an enum variant. The enum variant -/// must be a unit variant, otherwise deserialization will fail. -struct ValueEnumAccess<'de> { - value: Cow<'de, str>, -} - -impl<'de> EnumAccess<'de> for ValueEnumAccess<'de> { - type Error = Error; - type Variant = UnitOnlyVariantAccess; - - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> - where - V: DeserializeSeed<'de>, - { - let variant = seed.deserialize(self.value.into_deserializer())?; - Ok((variant, UnitOnlyVariantAccess)) - } -} - -/// A visitor for deserializing the contents of the enum variant. As we only support -/// `unit_variant`, all other variant types will return an error. -struct UnitOnlyVariantAccess; - -impl<'de> VariantAccess<'de> for UnitOnlyVariantAccess { - type Error = Error; - - fn unit_variant(self) -> Result<(), Self::Error> { - Ok(()) - } - - fn newtype_variant_seed(self, _seed: T) -> Result - where - T: DeserializeSeed<'de>, - { - Err(Error::custom("expected unit variant")) - } - - fn tuple_variant(self, _len: usize, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(Error::custom("expected unit variant")) - } - - fn struct_variant( - self, _fields: &'static [&'static str], _visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(Error::custom("expected unit variant")) - } -} diff --git a/src/serde_urlencoded/mod.rs b/src/serde_urlencoded/mod.rs deleted file mode 100644 index 7e2cf33a..00000000 --- a/src/serde_urlencoded/mod.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! `x-www-form-urlencoded` meets Serde - -extern crate dtoa; -extern crate itoa; - -pub mod de; -pub mod ser; - -#[doc(inline)] -pub use self::de::{from_bytes, from_reader, from_str, Deserializer}; -#[doc(inline)] -pub use self::ser::{to_string, Serializer}; - -#[cfg(test)] -mod tests { - #[test] - fn deserialize_bytes() { - let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; - - assert_eq!(super::from_bytes(b"first=23&last=42"), Ok(result)); - } - - #[test] - fn deserialize_str() { - let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; - - assert_eq!(super::from_str("first=23&last=42"), Ok(result)); - } - - #[test] - fn deserialize_reader() { - let result = vec![("first".to_owned(), 23), ("last".to_owned(), 42)]; - - assert_eq!(super::from_reader(b"first=23&last=42" as &[_]), Ok(result)); - } - - #[test] - fn deserialize_option() { - let result = vec![ - ("first".to_owned(), Some(23)), - ("last".to_owned(), Some(42)), - ]; - assert_eq!(super::from_str("first=23&last=42"), Ok(result)); - } - - #[test] - fn deserialize_unit() { - assert_eq!(super::from_str(""), Ok(())); - assert_eq!(super::from_str("&"), Ok(())); - assert_eq!(super::from_str("&&"), Ok(())); - assert!(super::from_str::<()>("first=23").is_err()); - } - - #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] - enum X { - A, - B, - C, - } - - #[test] - fn deserialize_unit_enum() { - let result = vec![ - ("one".to_owned(), X::A), - ("two".to_owned(), X::B), - ("three".to_owned(), X::C), - ]; - - assert_eq!(super::from_str("one=A&two=B&three=C"), Ok(result)); - } - - #[test] - fn serialize_option_map_int() { - let params = &[("first", Some(23)), ("middle", None), ("last", Some(42))]; - - assert_eq!(super::to_string(params), Ok("first=23&last=42".to_owned())); - } - - #[test] - fn serialize_option_map_string() { - let params = &[ - ("first", Some("hello")), - ("middle", None), - ("last", Some("world")), - ]; - - assert_eq!( - super::to_string(params), - Ok("first=hello&last=world".to_owned()) - ); - } - - #[test] - fn serialize_option_map_bool() { - let params = &[("one", Some(true)), ("two", Some(false))]; - - assert_eq!( - super::to_string(params), - Ok("one=true&two=false".to_owned()) - ); - } - - #[test] - fn serialize_map_bool() { - let params = &[("one", true), ("two", false)]; - - assert_eq!( - super::to_string(params), - Ok("one=true&two=false".to_owned()) - ); - } - - #[test] - fn serialize_unit_enum() { - let params = &[("one", X::A), ("two", X::B), ("three", X::C)]; - assert_eq!( - super::to_string(params), - Ok("one=A&two=B&three=C".to_owned()) - ); - } -} diff --git a/src/serde_urlencoded/ser/key.rs b/src/serde_urlencoded/ser/key.rs deleted file mode 100644 index 48497a55..00000000 --- a/src/serde_urlencoded/ser/key.rs +++ /dev/null @@ -1,74 +0,0 @@ -use super::super::ser::part::Sink; -use super::super::ser::Error; -use serde::Serialize; -use std::borrow::Cow; -use std::ops::Deref; - -pub enum Key<'key> { - Static(&'static str), - Dynamic(Cow<'key, str>), -} - -impl<'key> Deref for Key<'key> { - type Target = str; - - fn deref(&self) -> &str { - match *self { - Key::Static(key) => key, - Key::Dynamic(ref key) => key, - } - } -} - -impl<'key> From> for Cow<'static, str> { - fn from(key: Key<'key>) -> Self { - match key { - Key::Static(key) => key.into(), - Key::Dynamic(key) => key.into_owned().into(), - } - } -} - -pub struct KeySink { - end: End, -} - -impl KeySink -where - End: for<'key> FnOnce(Key<'key>) -> Result, -{ - pub fn new(end: End) -> Self { - KeySink { end } - } -} - -impl Sink for KeySink -where - End: for<'key> FnOnce(Key<'key>) -> Result, -{ - type Ok = Ok; - - fn serialize_static_str(self, value: &'static str) -> Result { - (self.end)(Key::Static(value)) - } - - fn serialize_str(self, value: &str) -> Result { - (self.end)(Key::Dynamic(value.into())) - } - - fn serialize_string(self, value: String) -> Result { - (self.end)(Key::Dynamic(value.into())) - } - - fn serialize_none(self) -> Result { - Err(self.unsupported()) - } - - fn serialize_some(self, _value: &T) -> Result { - Err(self.unsupported()) - } - - fn unsupported(self) -> Error { - Error::Custom("unsupported key".into()) - } -} diff --git a/src/serde_urlencoded/ser/mod.rs b/src/serde_urlencoded/ser/mod.rs deleted file mode 100644 index b4022d56..00000000 --- a/src/serde_urlencoded/ser/mod.rs +++ /dev/null @@ -1,490 +0,0 @@ -//! Serialization support for the `application/x-www-form-urlencoded` format. - -mod key; -mod pair; -mod part; -mod value; - -use serde::ser; -use std::borrow::Cow; -use std::error; -use std::fmt; -use std::str; -use url::form_urlencoded::Serializer as UrlEncodedSerializer; -use url::form_urlencoded::Target as UrlEncodedTarget; - -/// Serializes a value into a `application/x-wwww-url-encoded` `String` buffer. -/// -/// ```ignore -/// let meal = &[ -/// ("bread", "baguette"), -/// ("cheese", "comté"), -/// ("meat", "ham"), -/// ("fat", "butter"), -/// ]; -/// -/// assert_eq!( -/// serde_urlencoded::to_string(meal), -/// Ok("bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter".to_owned())); -/// ``` -pub fn to_string(input: T) -> Result { - let mut urlencoder = UrlEncodedSerializer::new("".to_owned()); - input.serialize(Serializer::new(&mut urlencoder))?; - Ok(urlencoder.finish()) -} - -/// A serializer for the `application/x-www-form-urlencoded` format. -/// -/// * Supported top-level inputs are structs, maps and sequences of pairs, -/// with or without a given length. -/// -/// * Supported keys and values are integers, bytes (if convertible to strings), -/// unit structs and unit variants. -/// -/// * Newtype structs defer to their inner values. -pub struct Serializer<'output, Target: 'output + UrlEncodedTarget> { - urlencoder: &'output mut UrlEncodedSerializer, -} - -impl<'output, Target: 'output + UrlEncodedTarget> Serializer<'output, Target> { - /// Returns a new `Serializer`. - pub fn new(urlencoder: &'output mut UrlEncodedSerializer) -> Self { - Serializer { urlencoder } - } -} - -/// Errors returned during serializing to `application/x-www-form-urlencoded`. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Error { - Custom(Cow<'static, str>), - Utf8(str::Utf8Error), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::Custom(ref msg) => msg.fmt(f), - Error::Utf8(ref err) => write!(f, "invalid UTF-8: {}", err), - } - } -} - -impl error::Error for Error { - fn description(&self) -> &str { - match *self { - Error::Custom(ref msg) => msg, - Error::Utf8(ref err) => error::Error::description(err), - } - } - - /// The lower-level cause of this error, in the case of a `Utf8` error. - fn cause(&self) -> Option<&error::Error> { - match *self { - Error::Custom(_) => None, - Error::Utf8(ref err) => Some(err), - } - } -} - -impl ser::Error for Error { - fn custom(msg: T) -> Self { - Error::Custom(format!("{}", msg).into()) - } -} - -/// Sequence serializer. -pub struct SeqSerializer<'output, Target: 'output + UrlEncodedTarget> { - urlencoder: &'output mut UrlEncodedSerializer, -} - -/// Tuple serializer. -/// -/// Mostly used for arrays. -pub struct TupleSerializer<'output, Target: 'output + UrlEncodedTarget> { - urlencoder: &'output mut UrlEncodedSerializer, -} - -/// Tuple struct serializer. -/// -/// Never instantiated, tuple structs are not supported. -pub struct TupleStructSerializer<'output, T: 'output + UrlEncodedTarget> { - inner: ser::Impossible<&'output mut UrlEncodedSerializer, Error>, -} - -/// Tuple variant serializer. -/// -/// Never instantiated, tuple variants are not supported. -pub struct TupleVariantSerializer<'output, T: 'output + UrlEncodedTarget> { - inner: ser::Impossible<&'output mut UrlEncodedSerializer, Error>, -} - -/// Map serializer. -pub struct MapSerializer<'output, Target: 'output + UrlEncodedTarget> { - urlencoder: &'output mut UrlEncodedSerializer, - key: Option>, -} - -/// Struct serializer. -pub struct StructSerializer<'output, Target: 'output + UrlEncodedTarget> { - urlencoder: &'output mut UrlEncodedSerializer, -} - -/// Struct variant serializer. -/// -/// Never instantiated, struct variants are not supported. -pub struct StructVariantSerializer<'output, T: 'output + UrlEncodedTarget> { - inner: ser::Impossible<&'output mut UrlEncodedSerializer, Error>, -} - -impl<'output, Target> ser::Serializer for Serializer<'output, Target> -where - Target: 'output + UrlEncodedTarget, -{ - type Ok = &'output mut UrlEncodedSerializer; - type Error = Error; - type SerializeSeq = SeqSerializer<'output, Target>; - type SerializeTuple = TupleSerializer<'output, Target>; - type SerializeTupleStruct = TupleStructSerializer<'output, Target>; - type SerializeTupleVariant = TupleVariantSerializer<'output, Target>; - type SerializeMap = MapSerializer<'output, Target>; - type SerializeStruct = StructSerializer<'output, Target>; - type SerializeStructVariant = StructVariantSerializer<'output, Target>; - - /// Returns an error. - fn serialize_bool(self, _v: bool) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_i8(self, _v: i8) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_i16(self, _v: i16) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_i32(self, _v: i32) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_i64(self, _v: i64) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_u8(self, _v: u8) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_u16(self, _v: u16) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_u32(self, _v: u32) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_u64(self, _v: u64) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_f32(self, _v: f32) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_f64(self, _v: f64) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_char(self, _v: char) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_str(self, _value: &str) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_bytes(self, _value: &[u8]) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_unit(self) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_unit_struct(self, _name: &'static str) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_unit_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - ) -> Result { - Err(Error::top_level()) - } - - /// Serializes the inner value, ignoring the newtype name. - fn serialize_newtype_struct( - self, _name: &'static str, value: &T, - ) -> Result { - value.serialize(self) - } - - /// Returns an error. - fn serialize_newtype_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _value: &T, - ) -> Result { - Err(Error::top_level()) - } - - /// Returns `Ok`. - fn serialize_none(self) -> Result { - Ok(self.urlencoder) - } - - /// Serializes the given value. - fn serialize_some( - self, value: &T, - ) -> Result { - value.serialize(self) - } - - /// Serialize a sequence, given length (if any) is ignored. - fn serialize_seq(self, _len: Option) -> Result { - Ok(SeqSerializer { - urlencoder: self.urlencoder, - }) - } - - /// Returns an error. - fn serialize_tuple(self, _len: usize) -> Result { - Ok(TupleSerializer { - urlencoder: self.urlencoder, - }) - } - - /// Returns an error. - fn serialize_tuple_struct( - self, _name: &'static str, _len: usize, - ) -> Result { - Err(Error::top_level()) - } - - /// Returns an error. - fn serialize_tuple_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _len: usize, - ) -> Result { - Err(Error::top_level()) - } - - /// Serializes a map, given length is ignored. - fn serialize_map(self, _len: Option) -> Result { - Ok(MapSerializer { - urlencoder: self.urlencoder, - key: None, - }) - } - - /// Serializes a struct, given length is ignored. - fn serialize_struct( - self, _name: &'static str, _len: usize, - ) -> Result { - Ok(StructSerializer { - urlencoder: self.urlencoder, - }) - } - - /// Returns an error. - fn serialize_struct_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _len: usize, - ) -> Result { - Err(Error::top_level()) - } -} - -impl<'output, Target> ser::SerializeSeq for SeqSerializer<'output, Target> -where - Target: 'output + UrlEncodedTarget, -{ - type Ok = &'output mut UrlEncodedSerializer; - type Error = Error; - - fn serialize_element( - &mut self, value: &T, - ) -> Result<(), Error> { - value.serialize(pair::PairSerializer::new(self.urlencoder)) - } - - fn end(self) -> Result { - Ok(self.urlencoder) - } -} - -impl<'output, Target> ser::SerializeTuple for TupleSerializer<'output, Target> -where - Target: 'output + UrlEncodedTarget, -{ - type Ok = &'output mut UrlEncodedSerializer; - type Error = Error; - - fn serialize_element( - &mut self, value: &T, - ) -> Result<(), Error> { - value.serialize(pair::PairSerializer::new(self.urlencoder)) - } - - fn end(self) -> Result { - Ok(self.urlencoder) - } -} - -impl<'output, Target> ser::SerializeTupleStruct - for TupleStructSerializer<'output, Target> -where - Target: 'output + UrlEncodedTarget, -{ - type Ok = &'output mut UrlEncodedSerializer; - type Error = Error; - - fn serialize_field( - &mut self, value: &T, - ) -> Result<(), Error> { - self.inner.serialize_field(value) - } - - fn end(self) -> Result { - self.inner.end() - } -} - -impl<'output, Target> ser::SerializeTupleVariant - for TupleVariantSerializer<'output, Target> -where - Target: 'output + UrlEncodedTarget, -{ - type Ok = &'output mut UrlEncodedSerializer; - type Error = Error; - - fn serialize_field( - &mut self, value: &T, - ) -> Result<(), Error> { - self.inner.serialize_field(value) - } - - fn end(self) -> Result { - self.inner.end() - } -} - -impl<'output, Target> ser::SerializeMap for MapSerializer<'output, Target> -where - Target: 'output + UrlEncodedTarget, -{ - type Ok = &'output mut UrlEncodedSerializer; - type Error = Error; - - fn serialize_entry( - &mut self, key: &K, value: &V, - ) -> Result<(), Error> { - let key_sink = key::KeySink::new(|key| { - let value_sink = value::ValueSink::new(self.urlencoder, &key); - value.serialize(part::PartSerializer::new(value_sink))?; - self.key = None; - Ok(()) - }); - let entry_serializer = part::PartSerializer::new(key_sink); - key.serialize(entry_serializer) - } - - fn serialize_key( - &mut self, key: &T, - ) -> Result<(), Error> { - let key_sink = key::KeySink::new(|key| Ok(key.into())); - let key_serializer = part::PartSerializer::new(key_sink); - self.key = Some(key.serialize(key_serializer)?); - Ok(()) - } - - fn serialize_value( - &mut self, value: &T, - ) -> Result<(), Error> { - { - let key = self.key.as_ref().ok_or_else(Error::no_key)?; - let value_sink = value::ValueSink::new(self.urlencoder, &key); - value.serialize(part::PartSerializer::new(value_sink))?; - } - self.key = None; - Ok(()) - } - - fn end(self) -> Result { - Ok(self.urlencoder) - } -} - -impl<'output, Target> ser::SerializeStruct for StructSerializer<'output, Target> -where - Target: 'output + UrlEncodedTarget, -{ - type Ok = &'output mut UrlEncodedSerializer; - type Error = Error; - - fn serialize_field( - &mut self, key: &'static str, value: &T, - ) -> Result<(), Error> { - let value_sink = value::ValueSink::new(self.urlencoder, key); - value.serialize(part::PartSerializer::new(value_sink)) - } - - fn end(self) -> Result { - Ok(self.urlencoder) - } -} - -impl<'output, Target> ser::SerializeStructVariant - for StructVariantSerializer<'output, Target> -where - Target: 'output + UrlEncodedTarget, -{ - type Ok = &'output mut UrlEncodedSerializer; - type Error = Error; - - fn serialize_field( - &mut self, key: &'static str, value: &T, - ) -> Result<(), Error> { - self.inner.serialize_field(key, value) - } - - fn end(self) -> Result { - self.inner.end() - } -} - -impl Error { - fn top_level() -> Self { - let msg = "top-level serializer supports only maps and structs"; - Error::Custom(msg.into()) - } - - fn no_key() -> Self { - let msg = "tried to serialize a value before serializing key"; - Error::Custom(msg.into()) - } -} diff --git a/src/serde_urlencoded/ser/pair.rs b/src/serde_urlencoded/ser/pair.rs deleted file mode 100644 index 68db144f..00000000 --- a/src/serde_urlencoded/ser/pair.rs +++ /dev/null @@ -1,239 +0,0 @@ -use super::super::ser::key::KeySink; -use super::super::ser::part::PartSerializer; -use super::super::ser::value::ValueSink; -use super::super::ser::Error; -use serde::ser; -use std::borrow::Cow; -use std::mem; -use url::form_urlencoded::Serializer as UrlEncodedSerializer; -use url::form_urlencoded::Target as UrlEncodedTarget; - -pub struct PairSerializer<'target, Target: 'target + UrlEncodedTarget> { - urlencoder: &'target mut UrlEncodedSerializer, - state: PairState, -} - -impl<'target, Target> PairSerializer<'target, Target> -where - Target: 'target + UrlEncodedTarget, -{ - pub fn new(urlencoder: &'target mut UrlEncodedSerializer) -> Self { - PairSerializer { - urlencoder, - state: PairState::WaitingForKey, - } - } -} - -impl<'target, Target> ser::Serializer for PairSerializer<'target, Target> -where - Target: 'target + UrlEncodedTarget, -{ - type Ok = (); - type Error = Error; - type SerializeSeq = ser::Impossible<(), Error>; - type SerializeTuple = Self; - type SerializeTupleStruct = ser::Impossible<(), Error>; - type SerializeTupleVariant = ser::Impossible<(), Error>; - type SerializeMap = ser::Impossible<(), Error>; - type SerializeStruct = ser::Impossible<(), Error>; - type SerializeStructVariant = ser::Impossible<(), Error>; - - fn serialize_bool(self, _v: bool) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_i8(self, _v: i8) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_i16(self, _v: i16) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_i32(self, _v: i32) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_i64(self, _v: i64) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_u8(self, _v: u8) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_u16(self, _v: u16) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_u32(self, _v: u32) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_u64(self, _v: u64) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_f32(self, _v: f32) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_f64(self, _v: f64) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_char(self, _v: char) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_str(self, _value: &str) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_bytes(self, _value: &[u8]) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_unit(self) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_unit_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - ) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_newtype_struct( - self, _name: &'static str, value: &T, - ) -> Result<(), Error> { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _value: &T, - ) -> Result<(), Error> { - Err(Error::unsupported_pair()) - } - - fn serialize_none(self) -> Result<(), Error> { - Ok(()) - } - - fn serialize_some(self, value: &T) -> Result<(), Error> { - value.serialize(self) - } - - fn serialize_seq(self, _len: Option) -> Result { - Err(Error::unsupported_pair()) - } - - fn serialize_tuple(self, len: usize) -> Result { - if len == 2 { - Ok(self) - } else { - Err(Error::unsupported_pair()) - } - } - - fn serialize_tuple_struct( - self, _name: &'static str, _len: usize, - ) -> Result { - Err(Error::unsupported_pair()) - } - - fn serialize_tuple_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _len: usize, - ) -> Result { - Err(Error::unsupported_pair()) - } - - fn serialize_map(self, _len: Option) -> Result { - Err(Error::unsupported_pair()) - } - - fn serialize_struct( - self, _name: &'static str, _len: usize, - ) -> Result { - Err(Error::unsupported_pair()) - } - - fn serialize_struct_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _len: usize, - ) -> Result { - Err(Error::unsupported_pair()) - } -} - -impl<'target, Target> ser::SerializeTuple for PairSerializer<'target, Target> -where - Target: 'target + UrlEncodedTarget, -{ - type Ok = (); - type Error = Error; - - fn serialize_element( - &mut self, value: &T, - ) -> Result<(), Error> { - match mem::replace(&mut self.state, PairState::Done) { - PairState::WaitingForKey => { - let key_sink = KeySink::new(|key| Ok(key.into())); - let key_serializer = PartSerializer::new(key_sink); - self.state = PairState::WaitingForValue { - key: value.serialize(key_serializer)?, - }; - Ok(()) - } - PairState::WaitingForValue { key } => { - let result = { - let value_sink = ValueSink::new(self.urlencoder, &key); - let value_serializer = PartSerializer::new(value_sink); - value.serialize(value_serializer) - }; - if result.is_ok() { - self.state = PairState::Done; - } else { - self.state = PairState::WaitingForValue { key }; - } - result - } - PairState::Done => Err(Error::done()), - } - } - - fn end(self) -> Result<(), Error> { - if let PairState::Done = self.state { - Ok(()) - } else { - Err(Error::not_done()) - } - } -} - -enum PairState { - WaitingForKey, - WaitingForValue { key: Cow<'static, str> }, - Done, -} - -impl Error { - fn done() -> Self { - Error::Custom("this pair has already been serialized".into()) - } - - fn not_done() -> Self { - Error::Custom("this pair has not yet been serialized".into()) - } - - fn unsupported_pair() -> Self { - Error::Custom("unsupported pair".into()) - } -} diff --git a/src/serde_urlencoded/ser/part.rs b/src/serde_urlencoded/ser/part.rs deleted file mode 100644 index 4874dd34..00000000 --- a/src/serde_urlencoded/ser/part.rs +++ /dev/null @@ -1,201 +0,0 @@ -use serde; - -use super::super::dtoa; -use super::super::itoa; -use super::super::ser::Error; -use std::str; - -pub struct PartSerializer { - sink: S, -} - -impl PartSerializer { - pub fn new(sink: S) -> Self { - PartSerializer { sink } - } -} - -pub trait Sink: Sized { - type Ok; - - fn serialize_static_str(self, value: &'static str) -> Result; - - fn serialize_str(self, value: &str) -> Result; - fn serialize_string(self, value: String) -> Result; - fn serialize_none(self) -> Result; - - fn serialize_some( - self, value: &T, - ) -> Result; - - fn unsupported(self) -> Error; -} - -impl serde::ser::Serializer for PartSerializer { - type Ok = S::Ok; - type Error = Error; - type SerializeSeq = serde::ser::Impossible; - type SerializeTuple = serde::ser::Impossible; - type SerializeTupleStruct = serde::ser::Impossible; - type SerializeTupleVariant = serde::ser::Impossible; - type SerializeMap = serde::ser::Impossible; - type SerializeStruct = serde::ser::Impossible; - type SerializeStructVariant = serde::ser::Impossible; - - fn serialize_bool(self, v: bool) -> Result { - self.sink - .serialize_static_str(if v { "true" } else { "false" }) - } - - fn serialize_i8(self, v: i8) -> Result { - self.serialize_integer(v) - } - - fn serialize_i16(self, v: i16) -> Result { - self.serialize_integer(v) - } - - fn serialize_i32(self, v: i32) -> Result { - self.serialize_integer(v) - } - - fn serialize_i64(self, v: i64) -> Result { - self.serialize_integer(v) - } - - fn serialize_u8(self, v: u8) -> Result { - self.serialize_integer(v) - } - - fn serialize_u16(self, v: u16) -> Result { - self.serialize_integer(v) - } - - fn serialize_u32(self, v: u32) -> Result { - self.serialize_integer(v) - } - - fn serialize_u64(self, v: u64) -> Result { - self.serialize_integer(v) - } - - fn serialize_f32(self, v: f32) -> Result { - self.serialize_floating(v) - } - - fn serialize_f64(self, v: f64) -> Result { - self.serialize_floating(v) - } - - fn serialize_char(self, v: char) -> Result { - self.sink.serialize_string(v.to_string()) - } - - fn serialize_str(self, value: &str) -> Result { - self.sink.serialize_str(value) - } - - fn serialize_bytes(self, value: &[u8]) -> Result { - match str::from_utf8(value) { - Ok(value) => self.sink.serialize_str(value), - Err(err) => Err(Error::Utf8(err)), - } - } - - fn serialize_unit(self) -> Result { - Err(self.sink.unsupported()) - } - - fn serialize_unit_struct(self, name: &'static str) -> Result { - self.sink.serialize_static_str(name) - } - - fn serialize_unit_variant( - self, _name: &'static str, _variant_index: u32, variant: &'static str, - ) -> Result { - self.sink.serialize_static_str(variant) - } - - fn serialize_newtype_struct( - self, _name: &'static str, value: &T, - ) -> Result { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _value: &T, - ) -> Result { - Err(self.sink.unsupported()) - } - - fn serialize_none(self) -> Result { - self.sink.serialize_none() - } - - fn serialize_some( - self, value: &T, - ) -> Result { - self.sink.serialize_some(value) - } - - fn serialize_seq(self, _len: Option) -> Result { - Err(self.sink.unsupported()) - } - - fn serialize_tuple(self, _len: usize) -> Result { - Err(self.sink.unsupported()) - } - - fn serialize_tuple_struct( - self, _name: &'static str, _len: usize, - ) -> Result { - Err(self.sink.unsupported()) - } - - fn serialize_tuple_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _len: usize, - ) -> Result { - Err(self.sink.unsupported()) - } - - fn serialize_map(self, _len: Option) -> Result { - Err(self.sink.unsupported()) - } - - fn serialize_struct( - self, _name: &'static str, _len: usize, - ) -> Result { - Err(self.sink.unsupported()) - } - - fn serialize_struct_variant( - self, _name: &'static str, _variant_index: u32, _variant: &'static str, - _len: usize, - ) -> Result { - Err(self.sink.unsupported()) - } -} - -impl PartSerializer { - fn serialize_integer(self, value: I) -> Result - where - I: itoa::Integer, - { - let mut buf = [b'\0'; 20]; - let len = itoa::write(&mut buf[..], value).unwrap(); - let part = unsafe { str::from_utf8_unchecked(&buf[0..len]) }; - serde::ser::Serializer::serialize_str(self, part) - } - - fn serialize_floating(self, value: F) -> Result - where - F: dtoa::Floating, - { - let mut buf = [b'\0'; 24]; - let len = dtoa::write(&mut buf[..], value).unwrap(); - let part = unsafe { str::from_utf8_unchecked(&buf[0..len]) }; - serde::ser::Serializer::serialize_str(self, part) - } -} diff --git a/src/serde_urlencoded/ser/value.rs b/src/serde_urlencoded/ser/value.rs deleted file mode 100644 index 3c47739f..00000000 --- a/src/serde_urlencoded/ser/value.rs +++ /dev/null @@ -1,59 +0,0 @@ -use super::super::ser::part::{PartSerializer, Sink}; -use super::super::ser::Error; -use serde::ser::Serialize; -use std::str; -use url::form_urlencoded::Serializer as UrlEncodedSerializer; -use url::form_urlencoded::Target as UrlEncodedTarget; - -pub struct ValueSink<'key, 'target, Target> -where - Target: 'target + UrlEncodedTarget, -{ - urlencoder: &'target mut UrlEncodedSerializer, - key: &'key str, -} - -impl<'key, 'target, Target> ValueSink<'key, 'target, Target> -where - Target: 'target + UrlEncodedTarget, -{ - pub fn new( - urlencoder: &'target mut UrlEncodedSerializer, key: &'key str, - ) -> Self { - ValueSink { urlencoder, key } - } -} - -impl<'key, 'target, Target> Sink for ValueSink<'key, 'target, Target> -where - Target: 'target + UrlEncodedTarget, -{ - type Ok = (); - - fn serialize_str(self, value: &str) -> Result<(), Error> { - self.urlencoder.append_pair(self.key, value); - Ok(()) - } - - fn serialize_static_str(self, value: &'static str) -> Result<(), Error> { - self.serialize_str(value) - } - - fn serialize_string(self, value: String) -> Result<(), Error> { - self.serialize_str(&value) - } - - fn serialize_none(self) -> Result { - Ok(()) - } - - fn serialize_some( - self, value: &T, - ) -> Result { - value.serialize(PartSerializer::new(self)) - } - - fn unsupported(self) -> Error { - Error::Custom("unsupported value".into()) - } -} From cf54be2f1792593434021322fcacedf18c635106 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 23 Aug 2018 09:39:11 -0700 Subject: [PATCH 0602/1635] hide new server api --- CHANGES.md | 4 +++- MIGRATION.md | 2 +- src/server/http.rs | 42 +++++++----------------------------------- src/server/mod.rs | 4 ++++ src/server/server.rs | 13 ++++++++----- 5 files changed, 23 insertions(+), 42 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9dd908ae..fcaf2554 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.8.0] - 2018-08-xx +## [0.7.4] - 2018-08-xx ### Added @@ -15,6 +15,8 @@ ### Changed * It is allowed to use function with up to 10 parameters for handler with `extractor parameters`. + `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple + even for handler with one parameter. * native-tls - 0.2 diff --git a/MIGRATION.md b/MIGRATION.md index 910e99a4..3c0bdd94 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,4 +1,4 @@ -## 0.8 +## 0.7.4 * `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple even for handler with one parameter. diff --git a/src/server/http.rs b/src/server/http.rs index e3740d95..f0cbacdb 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -175,11 +175,11 @@ where } /// Disable `HTTP/2` support - #[doc(hidden)] - #[deprecated( - since = "0.7.4", - note = "please use acceptor service with proper ServerFlags parama" - )] + // #[doc(hidden)] + // #[deprecated( + // since = "0.7.4", + // note = "please use acceptor service with proper ServerFlags parama" + // )] pub fn no_http2(mut self) -> Self { self.no_http2 = true; self @@ -217,6 +217,7 @@ where self } + #[doc(hidden)] /// Use listener for accepting incoming connection requests pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self where @@ -234,11 +235,6 @@ where } #[cfg(feature = "tls")] - #[doc(hidden)] - #[deprecated( - since = "0.7.4", - note = "please use `actix_web::HttpServer::listen_with()` and `actix_web::server::NativeTlsAcceptor` instead" - )] /// Use listener for accepting incoming tls connection requests /// /// HttpServer does not change any configuration for TcpListener, @@ -250,11 +246,6 @@ where } #[cfg(feature = "alpn")] - #[doc(hidden)] - #[deprecated( - since = "0.7.4", - note = "please use `actix_web::HttpServer::listen_with()` and `actix_web::server::OpensslAcceptor` instead" - )] /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" @@ -274,11 +265,6 @@ where } #[cfg(feature = "rust-tls")] - #[doc(hidden)] - #[deprecated( - since = "0.7.4", - note = "please use `actix_web::HttpServer::listen_with()` and `actix_web::server::RustlsAcceptor` instead" - )] /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" @@ -313,6 +299,7 @@ where } /// Start listening for incoming connections with supplied acceptor. + #[doc(hidden)] #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result where @@ -365,11 +352,6 @@ where } #[cfg(feature = "tls")] - #[doc(hidden)] - #[deprecated( - since = "0.7.4", - note = "please use `actix_web::HttpServer::bind_with()` and `actix_web::server::NativeTlsAcceptor` instead" - )] /// The ssl socket address to bind /// /// To bind multiple addresses this method can be called multiple times. @@ -382,11 +364,6 @@ where } #[cfg(feature = "alpn")] - #[doc(hidden)] - #[deprecated( - since = "0.7.4", - note = "please use `actix_web::HttpServer::bind_with()` and `actix_web::server::OpensslAcceptor` instead" - )] /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" @@ -407,11 +384,6 @@ where } #[cfg(feature = "rust-tls")] - #[doc(hidden)] - #[deprecated( - since = "0.7.4", - note = "please use `actix_web::HttpServer::bind_with()` and `actix_web::server::RustlsAcceptor` instead" - )] /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" diff --git a/src/server/mod.rs b/src/server/mod.rs index cccdf826..901260be 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -137,11 +137,15 @@ mod worker; use actix::Message; pub use self::message::Request; + +#[doc(hidden)] pub use self::server::{ ConnectionRateTag, ConnectionTag, Connections, Server, Service, ServiceHandler, }; pub use self::settings::ServerSettings; pub use self::http::HttpServer; + +#[doc(hidden)] pub use self::ssl::*; #[doc(hidden)] diff --git a/src/server/server.rs b/src/server/server.rs index 552ba8ee..0646c100 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -13,8 +13,9 @@ use super::accept::{AcceptLoop, AcceptNotify, Command}; use super::worker::{StopWorker, Worker, WorkerClient, Conn}; use super::{PauseServer, ResumeServer, StopServer, Token}; -///Describes service that could be used -///with [Server](struct.Server.html) +#[doc(hidden)] +/// Describes service that could be used +/// with [Server](struct.Server.html) pub trait Service: Send + 'static { /// Clone service fn clone(&self) -> Box; @@ -33,8 +34,9 @@ impl Service for Box { } } -///Describes the way serivce handles incoming -///TCP connections. +#[doc(hidden)] +/// Describes the way serivce handles incoming +/// TCP connections. pub trait ServiceHandler { /// Handle incoming stream fn handle(&mut self, token: Token, io: net::TcpStream, peer: Option); @@ -47,7 +49,8 @@ pub(crate) enum ServerCommand { WorkerDied(usize), } -///Server +/// Generic server +#[doc(hidden)] pub struct Server { threads: usize, workers: Vec<(usize, Addr)>, From e9c139bdea7519625c491407e86cedb2938ab90f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 23 Aug 2018 09:47:32 -0700 Subject: [PATCH 0603/1635] clippy warnings --- src/header/common/content_disposition.rs | 70 ++++++++++-------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index 686cf9c6..5e8cbd67 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -14,7 +14,7 @@ use regex::Regex; use std::fmt::{self, Write}; /// Split at the index of the first `needle` if it exists or at the end. -fn split_once<'a>(haystack: &'a str, needle: char) -> (&'a str, &'a str) { +fn split_once(haystack: &str, needle: char) -> (&str, &str) { haystack.find(needle).map_or_else( || (haystack, ""), |sc| { @@ -26,7 +26,7 @@ fn split_once<'a>(haystack: &'a str, needle: char) -> (&'a str, &'a str) { /// Split at the index of the first `needle` if it exists or at the end, trim the right of the /// first part and the left of the last part. -fn split_once_and_trim<'a>(haystack: &'a str, needle: char) -> (&'a str, &'a str) { +fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { let (first, last) = split_once(haystack, needle); (first.trim_right(), last.trim_left()) } @@ -114,20 +114,20 @@ impl DispositionParam { /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` #[inline] /// matches. - pub fn is_unknown<'a, T: AsRef>(&self, name: T) -> bool { + pub fn is_unknown>(&self, name: T) -> bool { self.as_unknown(name).is_some() } /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the /// `name` matches. #[inline] - pub fn is_unknown_ext<'a, T: AsRef>(&self, name: T) -> bool { + pub fn is_unknown_ext>(&self, name: T) -> bool { self.as_unknown_ext(name).is_some() } /// Returns the name if applicable. #[inline] - pub fn as_name<'a>(&'a self) -> Option<&'a str> { + pub fn as_name(&self) -> Option<&str> { match self { DispositionParam::Name(ref name) => Some(name.as_str()), _ => None, @@ -136,18 +136,18 @@ impl DispositionParam { /// Returns the filename if applicable. #[inline] - pub fn as_filename<'a>(&'a self) -> Option<&'a str> { + pub fn as_filename(&self) -> Option<&str> { match self { - &DispositionParam::Filename(ref filename) => Some(filename.as_str()), + DispositionParam::Filename(ref filename) => Some(filename.as_str()), _ => None, } } /// Returns the filename* if applicable. #[inline] - pub fn as_filename_ext<'a>(&'a self) -> Option<&'a ExtendedValue> { + pub fn as_filename_ext(&self) -> Option<&ExtendedValue> { match self { - &DispositionParam::FilenameExt(ref value) => Some(value), + DispositionParam::FilenameExt(ref value) => Some(value), _ => None, } } @@ -155,9 +155,9 @@ impl DispositionParam { /// Returns the value of the unrecognized regular parameter if it is /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. #[inline] - pub fn as_unknown<'a, T: AsRef>(&'a self, name: T) -> Option<&'a str> { + pub fn as_unknown>(&self, name: T) -> Option<&str> { match self { - &DispositionParam::Unknown(ref ext_name, ref value) + DispositionParam::Unknown(ref ext_name, ref value) if ext_name.eq_ignore_ascii_case(name.as_ref()) => { Some(value.as_str()) @@ -169,11 +169,9 @@ impl DispositionParam { /// Returns the value of the unrecognized extended parameter if it is /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. #[inline] - pub fn as_unknown_ext<'a, T: AsRef>( - &'a self, name: T, - ) -> Option<&'a ExtendedValue> { + pub fn as_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { match self { - &DispositionParam::UnknownExt(ref ext_name, ref value) + DispositionParam::UnknownExt(ref ext_name, ref value) if ext_name.eq_ignore_ascii_case(name.as_ref()) => { Some(value) @@ -276,7 +274,7 @@ impl ContentDisposition { let hv = String::from_utf8(hv.as_bytes().to_vec()) .map_err(|_| ::error::ParseError::Header)?; let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); - if disp_type.len() == 0 { + if disp_type.is_empty() { return Err(::error::ParseError::Header); } let mut cd = ContentDisposition { @@ -284,9 +282,9 @@ impl ContentDisposition { parameters: Vec::new(), }; - while left.len() > 0 { + while !left.is_empty() { let (param_name, new_left) = split_once_and_trim(left, '='); - if param_name.len() == 0 || param_name == "*" || new_left.len() == 0 { + if param_name.is_empty() || param_name == "*" || new_left.is_empty() { return Err(::error::ParseError::Header); } left = new_left; @@ -315,34 +313,28 @@ impl ContentDisposition { if escaping { escaping = false; quoted_string.push(c); - } else { - if c == 0x5c + } else if c == 0x5c { // backslash - { - escaping = true; - } else if c == 0x22 + escaping = true; + } else if c == 0x22 { // double quote - { - end = Some(i + 1); // cuz skipped 1 for the leading quote - break; - } else { - quoted_string.push(c); - } + end = Some(i + 1); // cuz skipped 1 for the leading quote + break; + } else { + quoted_string.push(c); } } left = &left[end.ok_or(::error::ParseError::Header)? + 1..]; left = split_once(left, ';').1.trim_left(); // In fact, it should not be Err if the above code is correct. - let quoted_string = String::from_utf8(quoted_string) - .map_err(|_| ::error::ParseError::Header)?; - quoted_string + String::from_utf8(quoted_string).map_err(|_| ::error::ParseError::Header)? } else { // token: won't contains semicolon according to RFC 2616 Section 2.2 let (token, new_left) = split_once_and_trim(left, ';'); left = new_left; token.to_owned() }; - if value.len() == 0 { + if value.is_empty() { return Err(::error::ParseError::Header); } @@ -397,12 +389,12 @@ impl ContentDisposition { } /// Return the value of *name* if exists. - pub fn get_name<'a>(&'a self) -> Option<&'a str> { + pub fn get_name(&self) -> Option<&str> { self.parameters.iter().filter_map(|p| p.as_name()).nth(0) } /// Return the value of *filename* if exists. - pub fn get_filename<'a>(&'a self) -> Option<&'a str> { + pub fn get_filename(&self) -> Option<&str> { self.parameters .iter() .filter_map(|p| p.as_filename()) @@ -410,7 +402,7 @@ impl ContentDisposition { } /// Return the value of *filename\** if exists. - pub fn get_filename_ext<'a>(&'a self) -> Option<&'a ExtendedValue> { + pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { self.parameters .iter() .filter_map(|p| p.as_filename_ext()) @@ -418,7 +410,7 @@ impl ContentDisposition { } /// Return the value of the parameter which the `name` matches. - pub fn get_unknown<'a, T: AsRef>(&'a self, name: T) -> Option<&'a str> { + pub fn get_unknown>(&self, name: T) -> Option<&str> { let name = name.as_ref(); self.parameters .iter() @@ -427,9 +419,7 @@ impl ContentDisposition { } /// Return the value of the extended parameter which the `name` matches. - pub fn get_unknown_ext<'a, T: AsRef>( - &'a self, name: T, - ) -> Option<&'a ExtendedValue> { + pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { let name = name.as_ref(); self.parameters .iter() From 1716380f0890a1e936d84181effeb63906c1e609 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 23 Aug 2018 09:48:01 -0700 Subject: [PATCH 0604/1635] clippy fmt --- src/application.rs | 3 +- src/client/connector.rs | 295 +++++++++++++++++----------------- src/client/writer.rs | 7 +- src/extractor.rs | 22 +-- src/fs.rs | 16 +- src/handler.rs | 11 +- src/header/mod.rs | 3 +- src/helpers.rs | 3 +- src/httpmessage.rs | 24 ++- src/httprequest.rs | 3 +- src/httpresponse.rs | 6 +- src/info.rs | 3 +- src/json.rs | 23 +-- src/lib.rs | 8 +- src/middleware/cors.rs | 29 ++-- src/middleware/csrf.rs | 5 +- src/middleware/errhandlers.rs | 2 +- src/middleware/session.rs | 6 +- src/multipart.rs | 8 +- src/param.rs | 2 +- src/payload.rs | 24 +-- src/pipeline.rs | 151 +++++++++++------ src/pred.rs | 3 +- src/scope.rs | 66 +++----- src/server/h1decoder.rs | 6 +- src/server/h1writer.rs | 3 +- src/server/h2.rs | 65 ++++---- src/server/h2writer.rs | 4 +- src/server/http.rs | 19 ++- src/server/mod.rs | 4 +- src/server/output.rs | 7 +- src/server/server.rs | 65 +++++--- src/server/ssl/mod.rs | 2 +- src/server/ssl/nativetls.rs | 41 ++--- src/with.rs | 61 +++++-- src/ws/mod.rs | 39 ++--- tests/test_client.rs | 25 +-- tests/test_handlers.rs | 28 ++-- tests/test_middleware.rs | 69 +++----- tests/test_server.rs | 66 ++++---- tests/test_ws.rs | 6 +- 41 files changed, 616 insertions(+), 617 deletions(-) diff --git a/src/application.rs b/src/application.rs index 4c8946c4..3ef753f5 100644 --- a/src/application.rs +++ b/src/application.rs @@ -776,8 +776,7 @@ mod tests { .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) .route("/test", Method::POST, |_: HttpRequest| { HttpResponse::Created() - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/test").method(Method::GET).request(); let resp = app.run(req); diff --git a/src/client/connector.rs b/src/client/connector.rs index 75b2e149..61347682 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -768,168 +768,161 @@ impl ClientConnector { ).map_err(move |_, act, _| { act.release_key(&key2); () - }) - .and_then(move |res, act, _| { - #[cfg(feature = "alpn")] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&key.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = - waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } + }).and_then(move |res, act, _| { + #[cfg(feature = "alpn")] + match res { + Err(err) => { + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + fut::Either::A( + act.connector + .connect_async(&key.host, stream) + .into_actor(act) + .then(move |res, _, _| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e), + )); } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all(feature = "tls", not(feature = "alpn")))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&conn.0.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = - waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } + Ok(stream) => { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } + } + fut::ok(()) + }), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) } } + } - #[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) - )] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let host = - DNSNameRef::try_from_ascii_str(&key.host).unwrap(); - fut::Either::A( - act.connector - .connect_async(host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = - waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } + #[cfg(all(feature = "tls", not(feature = "alpn")))] + match res { + Err(err) => { + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + fut::Either::A( + act.connector + .connect_async(&conn.0.host, stream) + .into_actor(act) + .then(move |res, _, _| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e), + )); } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } + Ok(stream) => { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + } + } + fut::ok(()) + }), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) } } + } - #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::err(()) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let _ = waiter - .tx - .send(Err(ClientConnectorError::SslIsNotSupported)); - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - }; - fut::ok(()) + #[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) + )] + match res { + Err(err) => { + let _ = waiter.tx.send(Err(err.into())); + fut::Either::B(fut::err(())) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap(); + fut::Either::A( + act.connector + .connect_async(host, stream) + .into_actor(act) + .then(move |res, _, _| { + match res { + Err(e) => { + let _ = waiter.tx.send(Err( + ClientConnectorError::SslError(e), + )); + } + Ok(stream) => { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + } + } + fut::ok(()) + }), + ) + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + fut::Either::B(fut::ok(())) } } - }) - .spawn(ctx); + } + + #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] + match res { + Err(err) => { + let _ = waiter.tx.send(Err(err.into())); + fut::err(()) + } + Ok(stream) => { + act.stats.opened += 1; + if conn.0.ssl { + let _ = + waiter.tx.send(Err(ClientConnectorError::SslIsNotSupported)); + } else { + let _ = waiter.tx.send(Ok(Connection::new( + conn.0.clone(), + Some(conn), + Box::new(stream), + ))); + }; + fut::ok(()) + } + } + }).spawn(ctx); } } diff --git a/src/client/writer.rs b/src/client/writer.rs index 81ad9651..45abfb77 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -302,10 +302,9 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { req.replace_body(body); let enc = match encoding { #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate(ZlibEncoder::new( - transfer, - Compression::default(), - )), + ContentEncoding::Deflate => { + ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default())) + } #[cfg(feature = "flate2")] ContentEncoding::Gzip => { ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default())) diff --git a/src/extractor.rs b/src/extractor.rs index 6d156d47..7b0b4b00 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -103,7 +103,7 @@ impl Path { impl From for Path { fn from(inner: T) -> Path { - Path{inner} + Path { inner } } } @@ -802,8 +802,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); let mut cfg = FormConfig::default(); cfg.limit(4096); @@ -837,8 +837,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); match Option::>::from_request(&req, &cfg) .poll() @@ -857,8 +857,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); + .set_payload(Bytes::from_static(b"bye=world")) + .finish(); match Option::>::from_request(&req, &cfg) .poll() @@ -875,8 +875,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); match Result::, Error>::from_request(&req, &FormConfig::default()) .poll() @@ -895,8 +895,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); + .set_payload(Bytes::from_static(b"bye=world")) + .finish(); match Result::, Error>::from_request(&req, &FormConfig::default()) .poll() diff --git a/src/fs.rs b/src/fs.rs index 4c819212..10cdaff7 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -369,11 +369,7 @@ impl Responder for NamedFile { .body("This resource only supports GET and HEAD.")); } - let etag = if C::is_use_etag() { - self.etag() - } else { - None - }; + let etag = if C::is_use_etag() { self.etag() } else { None }; let last_modified = if C::is_use_last_modifier() { self.last_modified() } else { @@ -518,7 +514,8 @@ impl Stream for ChunkedReadFile { max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; let mut buf = Vec::with_capacity(max_bytes); file.seek(io::SeekFrom::Start(offset))?; - let nbytes = file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; + let nbytes = + file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; if nbytes == 0 { return Err(io::ErrorKind::UnexpectedEof.into()); } @@ -869,8 +866,7 @@ impl HttpRange { length: length as u64, })) } - }) - .collect::>()?; + }).collect::>()?; let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); @@ -986,9 +982,7 @@ mod tests { use header::{ContentDisposition, DispositionParam, DispositionType}; let cd = ContentDisposition { disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename( - String::from("test.png") - )], + parameters: vec![DispositionParam::Filename(String::from("test.png"))], }; let mut file = NamedFile::open("tests/test.png") .unwrap() diff --git a/src/handler.rs b/src/handler.rs index 661cd028..2b6cc660 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -354,15 +354,16 @@ impl> From> for AsyncResult { } impl From>, E>> for AsyncResult -where T: 'static, - E: Into + 'static +where + T: 'static, + E: Into + 'static, { #[inline] fn from(res: Result>, E>) -> Self { match res { - Ok(fut) => AsyncResult( - Some(AsyncResultItem::Future( - Box::new(fut.map_err(|e| e.into()))))), + Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new( + fut.map_err(|e| e.into()), + )))), Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), } } diff --git a/src/header/mod.rs b/src/header/mod.rs index cdd2ad20..74e4b03e 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -223,8 +223,7 @@ pub fn from_comma_delimited( .filter_map(|x| match x.trim() { "" => None, y => Some(y), - }) - .filter_map(|x| x.trim().parse().ok()), + }).filter_map(|x| x.trim().parse().ok()), ) } Ok(result) diff --git a/src/helpers.rs b/src/helpers.rs index 400b1225..e82d6161 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -279,8 +279,7 @@ mod tests { true, StatusCode::MOVED_PERMANENTLY, )) - }) - .finish(); + }).finish(); // trailing slashes let params = vec![ diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 5db2f075..60f77b07 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -479,8 +479,7 @@ where body.extend_from_slice(&chunk); Ok(body) } - }) - .map(|body| body.freeze()), + }).map(|body| body.freeze()), )); self.poll() } @@ -588,8 +587,7 @@ where body.extend_from_slice(&chunk); Ok(body) } - }) - .and_then(move |body| { + }).and_then(move |body| { if (encoding as *const Encoding) == UTF_8 { serde_urlencoded::from_bytes::(&body) .map_err(|_| UrlencodedError::Parse) @@ -694,8 +692,7 @@ mod tests { .header( header::TRANSFER_ENCODING, Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), - ) - .finish(); + ).finish(); assert!(req.chunked().is_err()); } @@ -734,7 +731,7 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "xxxx") - .finish(); + .finish(); assert_eq!( req.urlencoded::().poll().err().unwrap(), UrlencodedError::UnknownLength @@ -744,7 +741,7 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "1000000") - .finish(); + .finish(); assert_eq!( req.urlencoded::().poll().err().unwrap(), UrlencodedError::Overflow @@ -765,8 +762,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); let result = req.urlencoded::().poll().ok().unwrap(); assert_eq!( @@ -780,8 +777,8 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); let result = req.urlencoded().poll().ok().unwrap(); assert_eq!( @@ -830,8 +827,7 @@ mod tests { b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", - )) - .finish(); + )).finish(); let mut r = Readlines::new(&req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( diff --git a/src/httprequest.rs b/src/httprequest.rs index 128dcbf1..f4de8152 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -264,7 +264,8 @@ impl HttpRequest { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); for hdr in self.request().inner.headers.get_all(header::COOKIE) { - let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; + let s = + str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; for cookie_str in s.split(';').map(|s| s.trim()) { if !cookie_str.is_empty() { cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 7700d352..f0257018 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -142,8 +142,7 @@ impl HttpResponse { HeaderValue::from_str(&cookie.to_string()) .map(|c| { h.append(header::SET_COOKIE, c); - }) - .map_err(|e| e.into()) + }).map_err(|e| e.into()) } /// Remove all cookies with the given name from this response. Returns @@ -1079,8 +1078,7 @@ mod tests { .http_only(true) .max_age(Duration::days(1)) .finish(), - ) - .del_cookie(&cookies[0]) + ).del_cookie(&cookies[0]) .finish(); let mut val: Vec<_> = resp diff --git a/src/info.rs b/src/info.rs index b15ba988..aeffc5ba 100644 --- a/src/info.rs +++ b/src/info.rs @@ -174,8 +174,7 @@ mod tests { .header( header::FORWARDED, "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", - ) - .request(); + ).request(); let mut info = ConnectionInfo::default(); info.update(&req); diff --git a/src/json.rs b/src/json.rs index 86eefca9..178143f1 100644 --- a/src/json.rs +++ b/src/json.rs @@ -327,8 +327,7 @@ impl Future for JsonBod body.extend_from_slice(&chunk); Ok(body) } - }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); + }).and_then(|body| Ok(serde_json::from_slice::(&body)?)); self.fut = Some(Box::new(fut)); self.poll() } @@ -388,8 +387,7 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), - ) - .finish(); + ).finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); @@ -397,12 +395,10 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + ).header( header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), - ) - .finish(); + ).finish(); let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); @@ -410,12 +406,10 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + ).header( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .finish(); let mut json = req.json::(); @@ -442,9 +436,8 @@ mod tests { ).header( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); + ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); assert!(handler.handle(&req).as_err().is_none()) } } diff --git a/src/lib.rs b/src/lib.rs index 72fe26c1..4eeb5ada 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,7 +127,6 @@ extern crate tokio_uds; extern crate url; #[macro_use] extern crate serde; -extern crate serde_urlencoded; #[cfg(feature = "brotli")] extern crate brotli2; extern crate encoding; @@ -135,6 +134,7 @@ extern crate encoding; extern crate flate2; extern crate h2 as http2; extern crate num_cpus; +extern crate serde_urlencoded; #[macro_use] extern crate percent_encoding; extern crate serde_json; @@ -256,12 +256,12 @@ pub mod dev { pub use extractor::{FormConfig, PayloadConfig}; pub use handler::{AsyncResult, Handler}; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use pipeline::Pipeline; pub use httpresponse::HttpResponseBuilder; pub use info::ConnectionInfo; pub use json::{JsonBody, JsonConfig}; pub use param::{FromParam, Params}; pub use payload::{Payload, PayloadBuffer}; + pub use pipeline::Pipeline; pub use resource::Resource; pub use route::Route; pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; @@ -283,7 +283,9 @@ pub mod http { /// Various http headers pub mod header { pub use header::*; - pub use header::{ContentDisposition, DispositionType, DispositionParam, Charset, LanguageTag}; + pub use header::{ + Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag, + }; } pub use header::ContentEncoding; pub use httpresponse::ConnectionType; diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index a6172740..e75dc73e 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -387,12 +387,10 @@ impl Middleware for Cors { header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str(), ); - }) - .if_some(headers, |headers, resp| { + }).if_some(headers, |headers, resp| { let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); - }) - .if_true(self.inner.origins.is_all(), |resp| { + }).if_true(self.inner.origins.is_all(), |resp| { if self.inner.send_wildcard { resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); } else { @@ -402,17 +400,14 @@ impl Middleware for Cors { origin.clone(), ); } - }) - .if_true(self.inner.origins.is_some(), |resp| { + }).if_true(self.inner.origins.is_some(), |resp| { resp.header( header::ACCESS_CONTROL_ALLOW_ORIGIN, self.inner.origins_str.as_ref().unwrap().clone(), ); - }) - .if_true(self.inner.supports_credentials, |resp| { + }).if_true(self.inner.supports_credentials, |resp| { resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); - }) - .header( + }).header( header::ACCESS_CONTROL_ALLOW_METHODS, &self .inner @@ -420,8 +415,7 @@ impl Middleware for Cors { .iter() .fold(String::new(), |s, v| s + "," + v.as_str()) .as_str()[1..], - ) - .finish(), + ).finish(), )) } else { // Only check requests with a origin header. @@ -838,9 +832,10 @@ impl CorsBuilder { if !self.expose_hdrs.is_empty() { cors.expose_hdrs = Some( - self.expose_hdrs.iter() + self.expose_hdrs + .iter() .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..] - .to_owned() + .to_owned(), ); } Cors { @@ -977,8 +972,7 @@ mod tests { .header( header::ACCESS_CONTROL_REQUEST_HEADERS, "AUTHORIZATION,ACCEPT", - ) - .method(Method::OPTIONS) + ).method(Method::OPTIONS) .finish(); let resp = cors.start(&req).unwrap().response(); @@ -1102,7 +1096,8 @@ mod tests { ); { - let headers = resp.headers() + let headers = resp + .headers() .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) .unwrap() .to_str() diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index cda1d324..02cd150d 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -93,8 +93,7 @@ fn origin(headers: &HeaderMap) -> Option, CsrfError>> { .to_str() .map_err(|_| CsrfError::BadOrigin) .map(|o| o.into()) - }) - .or_else(|| { + }).or_else(|| { headers.get(header::REFERER).map(|referer| { Uri::try_from(Bytes::from(referer.as_bytes())) .ok() @@ -251,7 +250,7 @@ mod tests { "Referer", "https://www.example.com/some/path?query=param", ).method(Method::POST) - .finish(); + .finish(); assert!(csrf.start(&req).is_ok()); } diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 83c66aae..c7d19d33 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -131,7 +131,7 @@ mod tests { ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), ).middleware(MiddlewareOne) - .handler(|_| HttpResponse::Ok()) + .handler(|_| HttpResponse::Ok()) }); let request = srv.get().finish().unwrap(); diff --git a/src/middleware/session.rs b/src/middleware/session.rs index cc7aab6b..7bf5c0e9 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -579,8 +579,7 @@ mod tests { App::new() .middleware(SessionStorage::new( CookieSessionBackend::signed(&[0; 32]).secure(false), - )) - .resource("/", |r| { + )).resource("/", |r| { r.f(|req| { let _ = req.session().set("counter", 100); "test" @@ -599,8 +598,7 @@ mod tests { App::new() .middleware(SessionStorage::new( CookieSessionBackend::signed(&[0; 32]).secure(false), - )) - .resource("/", |r| { + )).resource("/", |r| { r.with(|ses: Session| { let _ = ses.set("counter", 100); "test" diff --git a/src/multipart.rs b/src/multipart.rs index dbf3d179..fe809294 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -756,10 +756,7 @@ mod tests { { use http::header::{DispositionParam, DispositionType}; let cd = field.content_disposition().unwrap(); - assert_eq!( - cd.disposition, - DispositionType::FormData - ); + assert_eq!(cd.disposition, DispositionType::FormData); assert_eq!( cd.parameters[0], DispositionParam::Name("file".into()) @@ -813,7 +810,6 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })) - .unwrap(); + })).unwrap(); } } diff --git a/src/param.rs b/src/param.rs index 2704b60d..063159d7 100644 --- a/src/param.rs +++ b/src/param.rs @@ -236,7 +236,7 @@ macro_rules! FROM_STR { ($type:ty) => { impl FromParam for $type { type Err = InternalError<<$type as FromStr>::Err>; - + fn from_param(val: &str) -> Result { <$type as FromStr>::from_str(val) .map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST)) diff --git a/src/payload.rs b/src/payload.rs index b20bec65..1d9281f5 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -513,8 +513,7 @@ where .fold(BytesMut::new(), |mut b, c| { b.extend_from_slice(c); b - }) - .freeze() + }).freeze() } } @@ -553,8 +552,7 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })) - .unwrap(); + })).unwrap(); } #[test] @@ -578,8 +576,7 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })) - .unwrap(); + })).unwrap(); } #[test] @@ -596,8 +593,7 @@ mod tests { payload.readany().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) - })) - .unwrap(); + })).unwrap(); } #[test] @@ -625,8 +621,7 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })) - .unwrap(); + })).unwrap(); } #[test] @@ -659,8 +654,7 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })) - .unwrap(); + })).unwrap(); } #[test] @@ -693,8 +687,7 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })) - .unwrap(); + })).unwrap(); } #[test] @@ -715,7 +708,6 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })) - .unwrap(); + })).unwrap(); } } diff --git a/src/pipeline.rs b/src/pipeline.rs index 7f206a9f..1940f930 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -52,9 +52,7 @@ impl> PipelineState { PipelineState::Finishing(ref mut state) => state.poll(info, mws), PipelineState::Completed(ref mut state) => state.poll(info), PipelineState::Response(ref mut state) => state.poll(info, mws), - PipelineState::None | PipelineState::Error => { - None - } + PipelineState::None | PipelineState::Error => None, } } } @@ -448,10 +446,16 @@ impl ProcessResponse { ) -> Option> { // connection is dead at this point match mem::replace(&mut self.iostate, IOState::Done) { - IOState::Response => - Some(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())), - IOState::Payload(_) => - Some(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())), + IOState::Response => Some(FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + )), + IOState::Payload(_) => Some(FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + )), IOState::Actor(mut ctx) => { if info.disconnected.take().is_some() { ctx.disconnected(); @@ -467,18 +471,25 @@ impl ProcessResponse { Frame::Chunk(None) => { info.context = Some(ctx); return Some(FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), - )) + info, + mws, + self.resp.take().unwrap(), + )); } Frame::Chunk(Some(_)) => (), - Frame::Drain(fut) => {let _ = fut.send(());}, + Frame::Drain(fut) => { + let _ = fut.send(()); + } } } } - Ok(Async::Ready(None)) => + Ok(Async::Ready(None)) => { return Some(FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), - )), + info, + mws, + self.resp.take().unwrap(), + )) + } Ok(Async::NotReady) => { self.iostate = IOState::Actor(ctx); return None; @@ -486,12 +497,20 @@ impl ProcessResponse { Err(err) => { info.context = Some(ctx); info.error = Some(err); - return Some(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())); + return Some(FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + )); } } } } - IOState::Done => Some(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())) + IOState::Done => Some(FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + )), } } @@ -505,22 +524,32 @@ impl ProcessResponse { 'inner: loop { let result = match mem::replace(&mut self.iostate, IOState::Done) { IOState::Response => { - let encoding = - self.resp.as_ref().unwrap().content_encoding().unwrap_or(info.encoding); + let encoding = self + .resp + .as_ref() + .unwrap() + .content_encoding() + .unwrap_or(info.encoding); - let result = - match io.start(&info.req, self.resp.as_mut().unwrap(), encoding) { - Ok(res) => res, - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), - )); - } - }; + let result = match io.start( + &info.req, + self.resp.as_mut().unwrap(), + encoding, + ) { + Ok(res) => res, + Err(err) => { + info.error = Some(err.into()); + return Ok(FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + )); + } + }; if let Some(err) = self.resp.as_ref().unwrap().error() { - if self.resp.as_ref().unwrap().status().is_server_error() { + if self.resp.as_ref().unwrap().status().is_server_error() + { error!( "Error occured during request handling, status: {} {}", self.resp.as_ref().unwrap().status(), err @@ -556,7 +585,9 @@ impl ProcessResponse { if let Err(err) = io.write_eof() { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), + info, + mws, + self.resp.take().unwrap(), )); } break; @@ -567,7 +598,9 @@ impl ProcessResponse { Err(err) => { info.error = Some(err.into()); return Ok(FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), + info, + mws, + self.resp.take().unwrap(), )); } Ok(result) => result, @@ -580,7 +613,9 @@ impl ProcessResponse { Err(err) => { info.error = Some(err); return Ok(FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), + info, + mws, + self.resp.take().unwrap(), )); } }, @@ -603,26 +638,30 @@ impl ProcessResponse { info.error = Some(err.into()); return Ok( FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), + info, + mws, + self.resp.take().unwrap(), ), ); } break 'inner; } - Frame::Chunk(Some(chunk)) => { - match io.write(&chunk) { - Err(err) => { - info.context = Some(ctx); - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), - ), - ); - } - Ok(result) => res = Some(result), + Frame::Chunk(Some(chunk)) => match io + .write(&chunk) + { + Err(err) => { + info.context = Some(ctx); + info.error = Some(err.into()); + return Ok( + FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + ), + ); } - } + Ok(result) => res = Some(result), + }, Frame::Drain(fut) => self.drain = Some(fut), } } @@ -642,7 +681,9 @@ impl ProcessResponse { info.context = Some(ctx); info.error = Some(err); return Ok(FinishingMiddlewares::init( - info, mws, self.resp.take().unwrap(), + info, + mws, + self.resp.take().unwrap(), )); } } @@ -682,7 +723,11 @@ impl ProcessResponse { info.context = Some(ctx); } info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())); + return Ok(FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + )); } } } @@ -696,11 +741,19 @@ impl ProcessResponse { Ok(_) => (), Err(err) => { info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())); + return Ok(FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + )); } } self.resp.as_mut().unwrap().set_response_size(io.written()); - Ok(FinishingMiddlewares::init(info, mws, self.resp.take().unwrap())) + Ok(FinishingMiddlewares::init( + info, + mws, + self.resp.take().unwrap(), + )) } _ => Err(PipelineState::Response(self)), } diff --git a/src/pred.rs b/src/pred.rs index 22f12ac2..99d6e608 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -264,8 +264,7 @@ mod tests { .header( header::HOST, header::HeaderValue::from_static("www.rust-lang.org"), - ) - .finish(); + ).finish(); let pred = Host("www.rust-lang.org"); assert!(pred.check(&req, req.state())); diff --git a/src/scope.rs b/src/scope.rs index baf891c3..8298f534 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -715,8 +715,7 @@ mod tests { let app = App::new() .scope("/app", |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/path1").request(); let resp = app.run(req); @@ -730,8 +729,7 @@ mod tests { scope .resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); @@ -747,8 +745,7 @@ mod tests { let app = App::new() .scope("/app/", |scope| { scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); @@ -764,8 +761,7 @@ mod tests { let app = App::new() .scope("/app/", |scope| { scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app").request(); let resp = app.run(req); @@ -783,12 +779,12 @@ mod tests { scope .route("/path1", Method::GET, |_: HttpRequest<_>| { HttpResponse::Ok() - }) - .route("/path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }) - .finish(); + }).route( + "/path1", + Method::DELETE, + |_: HttpRequest<_>| HttpResponse::Ok(), + ) + }).finish(); let req = TestRequest::with_uri("/app/path1").request(); let resp = app.run(req); @@ -814,8 +810,7 @@ mod tests { scope .filter(pred::Get()) .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) @@ -840,8 +835,7 @@ mod tests { .body(format!("project: {}", &r.match_info()["project"])) }) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/ab-project1/path1").request(); let resp = app.run(req); @@ -869,8 +863,7 @@ mod tests { scope.with_state("/t1", State, |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/t1/path1").request(); let resp = app.run(req); @@ -888,8 +881,7 @@ mod tests { .resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("/", |r| r.f(|_| HttpResponse::Created())) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); @@ -909,8 +901,7 @@ mod tests { scope.with_state("/t1/", State, |scope| { scope.resource("", |r| r.f(|_| HttpResponse::Ok())) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); @@ -930,8 +921,7 @@ mod tests { scope.with_state("/t1/", State, |scope| { scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); @@ -953,8 +943,7 @@ mod tests { .filter(pred::Get()) .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) @@ -976,8 +965,7 @@ mod tests { scope.nested("/t1", |scope| { scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/t1/path1").request(); let resp = app.run(req); @@ -993,8 +981,7 @@ mod tests { .resource("", |r| r.f(|_| HttpResponse::Ok())) .resource("/", |r| r.f(|_| HttpResponse::Created())) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/t1").request(); let resp = app.run(req); @@ -1014,8 +1001,7 @@ mod tests { .filter(pred::Get()) .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) @@ -1044,8 +1030,7 @@ mod tests { }) }) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/project_1/path1").request(); let resp = app.run(req); @@ -1077,8 +1062,7 @@ mod tests { }) }) }) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/test/1/path1").request(); let resp = app.run(req); @@ -1104,8 +1088,7 @@ mod tests { scope .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) .default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }) - .finish(); + }).finish(); let req = TestRequest::with_uri("/app/path2").request(); let resp = app.run(req); @@ -1121,8 +1104,7 @@ mod tests { let app = App::new() .scope("/app1", |scope| { scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }) - .scope("/app2", |scope| scope) + }).scope("/app2", |scope| scope) .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) .finish(); diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index d1948a0d..084ae8b2 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -166,9 +166,9 @@ impl H1Decoder { { true } else { - version == Version::HTTP_11 - && !(conn.contains("close") - || conn.contains("upgrade")) + version == Version::HTTP_11 && !(conn + .contains("close") + || conn.contains("upgrade")) } } else { false diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 8c948471..8981f9df 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -152,8 +152,7 @@ impl Writer for H1Writer { let reason = msg.reason().as_bytes(); if let Body::Binary(ref bytes) = body { buffer.reserve( - 256 - + msg.headers().len() * AVERAGE_HEADER_SIZE + 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len() + reason.len(), ); diff --git a/src/server/h2.rs b/src/server/h2.rs index 0835f592..986888ff 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -115,46 +115,51 @@ where if disconnected { item.flags.insert(EntryFlags::EOF); } else { - let retry = item.payload.need_read() == PayloadStatus::Read; - loop { - match item.task.poll_io(&mut item.stream) { - Ok(Async::Ready(ready)) => { - if ready { + let retry = item.payload.need_read() == PayloadStatus::Read; + loop { + match item.task.poll_io(&mut item.stream) { + Ok(Async::Ready(ready)) => { + if ready { + item.flags.insert( + EntryFlags::EOF | EntryFlags::FINISHED, + ); + } else { + item.flags.insert(EntryFlags::EOF); + } + not_ready = false; + } + Ok(Async::NotReady) => { + if item.payload.need_read() + == PayloadStatus::Read + && !retry + { + continue; + } + } + Err(err) => { + error!("Unhandled error: {}", err); item.flags.insert( - EntryFlags::EOF | EntryFlags::FINISHED, + EntryFlags::EOF + | EntryFlags::ERROR + | EntryFlags::WRITE_DONE, ); - } else { - item.flags.insert(EntryFlags::EOF); - } - not_ready = false; - } - Ok(Async::NotReady) => { - if item.payload.need_read() == PayloadStatus::Read - && !retry - { - continue; + item.stream.reset(Reason::INTERNAL_ERROR); } } - Err(err) => { - error!("Unhandled error: {}", err); - item.flags.insert( - EntryFlags::EOF - | EntryFlags::ERROR - | EntryFlags::WRITE_DONE, - ); - item.stream.reset(Reason::INTERNAL_ERROR); - } + break; } - break; - } } } - - if item.flags.contains(EntryFlags::EOF) && !item.flags.contains(EntryFlags::FINISHED) { + + if item.flags.contains(EntryFlags::EOF) + && !item.flags.contains(EntryFlags::FINISHED) + { match item.task.poll_completed() { Ok(Async::NotReady) => (), Ok(Async::Ready(_)) => { - item.flags.insert(EntryFlags::FINISHED | EntryFlags::WRITE_DONE); + item.flags.insert( + EntryFlags::FINISHED | EntryFlags::WRITE_DONE, + ); } Err(err) => { item.flags.insert( diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index ce61b3ed..398e9817 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -250,9 +250,7 @@ impl Writer for H2Writer { return Ok(Async::Ready(())); } } - Err(e) => { - return Err(io::Error::new(io::ErrorKind::Other, e)) - } + Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), } } } diff --git a/src/server/http.rs b/src/server/http.rs index f0cbacdb..05f0b244 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -403,19 +403,24 @@ where } } -impl Into<(Box, Vec<(Token, net::TcpListener)>)> for HttpServer { +impl Into<(Box, Vec<(Token, net::TcpListener)>)> + for HttpServer +{ fn into(mut self) -> (Box, Vec<(Token, net::TcpListener)>) { let sockets: Vec<_> = mem::replace(&mut self.sockets, Vec::new()) .into_iter() .map(|item| (item.token, item.lst)) .collect(); - (Box::new(HttpService { - factory: self.factory, - host: self.host, - keep_alive: self.keep_alive, - handlers: self.handlers, - }), sockets) + ( + Box::new(HttpService { + factory: self.factory, + host: self.host, + keep_alive: self.keep_alive, + handlers: self.handlers, + }), + sockets, + ) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 901260be..2ac933a7 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -125,12 +125,12 @@ mod h1writer; mod h2; mod h2writer; pub(crate) mod helpers; +mod http; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; mod server; pub(crate) mod settings; -mod http; mod ssl; mod worker; @@ -138,12 +138,12 @@ use actix::Message; pub use self::message::Request; +pub use self::http::HttpServer; #[doc(hidden)] pub use self::server::{ ConnectionRateTag, ConnectionTag, Connections, Server, Service, ServiceHandler, }; pub use self::settings::ServerSettings; -pub use self::http::HttpServer; #[doc(hidden)] pub use self::ssl::*; diff --git a/src/server/output.rs b/src/server/output.rs index 970e03d8..74b08338 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -273,10 +273,9 @@ impl Output { let enc = match encoding { #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate(ZlibEncoder::new( - transfer, - Compression::fast(), - )), + ContentEncoding::Deflate => { + ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::fast())) + } #[cfg(feature = "flate2")] ContentEncoding::Gzip => { ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast())) diff --git a/src/server/server.rs b/src/server/server.rs index 0646c100..7bab70f0 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,16 +1,21 @@ -use std::{mem, net}; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; use std::time::Duration; -use std::sync::{Arc, atomic::{AtomicUsize, Ordering}}; +use std::{mem, net}; -use num_cpus; -use futures::{Future, Stream, Sink}; use futures::sync::{mpsc, mpsc::unbounded}; +use futures::{Future, Sink, Stream}; +use num_cpus; -use actix::{fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, - Context, Handler, Response, System, StreamHandler, WrapFuture}; +use actix::{ + fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler, + Response, StreamHandler, System, WrapFuture, +}; use super::accept::{AcceptLoop, AcceptNotify, Command}; -use super::worker::{StopWorker, Worker, WorkerClient, Conn}; +use super::worker::{Conn, StopWorker, Worker, WorkerClient}; use super::{PauseServer, ResumeServer, StopServer, Token}; #[doc(hidden)] @@ -39,7 +44,9 @@ impl Service for Box { /// TCP connections. pub trait ServiceHandler { /// Handle incoming stream - fn handle(&mut self, token: Token, io: net::TcpStream, peer: Option); + fn handle( + &mut self, token: Token, io: net::TcpStream, peer: Option, + ); /// Shutdown open handlers fn shutdown(&self, _: bool) {} @@ -156,7 +163,7 @@ impl Server { /// Add new service to server pub fn service(mut self, srv: T) -> Self where - T: Into<(Box, Vec<(Token, net::TcpListener)>)> + T: Into<(Box, Vec<(Token, net::TcpListener)>)>, { let (srv, sockets) = srv.into(); self.services.push(srv); @@ -213,8 +220,9 @@ impl Server { info!("Starting server on http://{:?}", s.1.local_addr().ok()); } } - let rx = self.accept.start( - mem::replace(&mut self.sockets, Vec::new()), workers); + let rx = self + .accept + .start(mem::replace(&mut self.sockets, Vec::new()), workers); // start http server actor let signals = self.subscribe_to_signals(); @@ -242,7 +250,9 @@ impl Server { } } - fn start_worker(&self, idx: usize, notify: AcceptNotify) -> (Addr, WorkerClient) { + fn start_worker( + &self, idx: usize, notify: AcceptNotify, + ) -> (Addr, WorkerClient) { let (tx, rx) = unbounded::>(); let conns = Connections::new(notify, self.maxconn, self.maxconnrate); let worker = WorkerClient::new(idx, tx, conns.clone()); @@ -250,7 +260,10 @@ impl Server { let addr = Arbiter::start(move |ctx: &mut Context<_>| { ctx.add_message_stream(rx); - let handlers: Vec<_> = services.into_iter().map(|s| s.create(conns.clone())).collect(); + let handlers: Vec<_> = services + .into_iter() + .map(|s| s.create(conns.clone())) + .collect(); Worker::new(conns, handlers) }); @@ -258,8 +271,7 @@ impl Server { } } -impl Actor for Server -{ +impl Actor for Server { type Context = Context; } @@ -391,7 +403,8 @@ impl StreamHandler for Server { break; } - let (addr, worker) = self.start_worker(new_idx, self.accept.get_notify()); + let (addr, worker) = + self.start_worker(new_idx, self.accept.get_notify()); self.workers.push((new_idx, addr)); self.accept.send(Command::Worker(worker)); } @@ -413,14 +426,15 @@ impl Connections { 0 }; - Connections ( - Arc::new(ConnectionsInner { - notify, - maxconn, maxconnrate, - maxconn_low, maxconnrate_low, - conn: AtomicUsize::new(0), - connrate: AtomicUsize::new(0), - })) + Connections(Arc::new(ConnectionsInner { + notify, + maxconn, + maxconnrate, + maxconn_low, + maxconnrate_low, + conn: AtomicUsize::new(0), + connrate: AtomicUsize::new(0), + })) } pub(crate) fn available(&self) -> bool { @@ -473,7 +487,6 @@ impl ConnectionsInner { self.notify.notify(); } } - } /// Type responsible for max connection stat. @@ -498,7 +511,7 @@ impl Drop for ConnectionTag { /// Type responsible for max connection rate stat. /// /// Max connections rate stat get updated on drop. -pub struct ConnectionRateTag (Arc); +pub struct ConnectionRateTag(Arc); impl ConnectionRateTag { fn new(inner: Arc) -> Self { diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs index b29a7d4a..bd931fb8 100644 --- a/src/server/ssl/mod.rs +++ b/src/server/ssl/mod.rs @@ -6,7 +6,7 @@ pub use self::openssl::OpensslAcceptor; #[cfg(feature = "tls")] mod nativetls; #[cfg(feature = "tls")] -pub use self::nativetls::{TlsStream, NativeTlsAcceptor}; +pub use self::nativetls::{NativeTlsAcceptor, TlsStream}; #[cfg(feature = "rust-tls")] mod rustls; diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs index c3f2c38d..e35f12d2 100644 --- a/src/server/ssl/nativetls.rs +++ b/src/server/ssl/nativetls.rs @@ -2,7 +2,7 @@ use std::net::Shutdown; use std::{io, time}; use futures::{Async, Future, Poll}; -use native_tls::{self, TlsAcceptor, HandshakeError}; +use native_tls::{self, HandshakeError, TlsAcceptor}; use tokio_io::{AsyncRead, AsyncWrite}; use server::{AcceptorService, IoStream}; @@ -29,14 +29,16 @@ pub struct TlsStream { /// Future returned from `NativeTlsAcceptor::accept` which will resolve /// once the accept handshake has finished. -pub struct Accept{ +pub struct Accept { inner: Option, HandshakeError>>, } impl NativeTlsAcceptor { /// Create `NativeTlsAcceptor` instance pub fn new(acceptor: TlsAcceptor) -> Self { - NativeTlsAcceptor { acceptor: acceptor.into() } + NativeTlsAcceptor { + acceptor: acceptor.into(), + } } } @@ -49,7 +51,9 @@ impl AcceptorService for NativeTlsAcceptor { } fn accept(&self, io: Io) -> Self::Future { - Accept { inner: Some(self.acceptor.accept(io)) } + Accept { + inner: Some(self.acceptor.accept(io)), + } } } @@ -78,18 +82,19 @@ impl Future for Accept { fn poll(&mut self) -> Poll { match self.inner.take().expect("cannot poll MidHandshake twice") { Ok(stream) => Ok(TlsStream { inner: stream }.into()), - Err(HandshakeError::Failure(e)) => Err(io::Error::new(io::ErrorKind::Other, e)), - Err(HandshakeError::WouldBlock(s)) => { - match s.handshake() { - Ok(stream) => Ok(TlsStream { inner: stream }.into()), - Err(HandshakeError::Failure(e)) => - Err(io::Error::new(io::ErrorKind::Other, e)), - Err(HandshakeError::WouldBlock(s)) => { - self.inner = Some(Err(HandshakeError::WouldBlock(s))); - Ok(Async::NotReady) - } - } + Err(HandshakeError::Failure(e)) => { + Err(io::Error::new(io::ErrorKind::Other, e)) } + Err(HandshakeError::WouldBlock(s)) => match s.handshake() { + Ok(stream) => Ok(TlsStream { inner: stream }.into()), + Err(HandshakeError::Failure(e)) => { + Err(io::Error::new(io::ErrorKind::Other, e)) + } + Err(HandshakeError::WouldBlock(s)) => { + self.inner = Some(Err(HandshakeError::WouldBlock(s))); + Ok(Async::NotReady) + } + }, } } } @@ -124,9 +129,7 @@ impl io::Write for TlsStream { } } - -impl AsyncRead for TlsStream { -} +impl AsyncRead for TlsStream {} impl AsyncWrite for TlsStream { fn shutdown(&mut self) -> Poll<(), io::Error> { @@ -137,4 +140,4 @@ impl AsyncWrite for TlsStream { } self.inner.get_mut().shutdown() } -} \ No newline at end of file +} diff --git a/src/with.rs b/src/with.rs index caffe0ac..5e2c0141 100644 --- a/src/with.rs +++ b/src/with.rs @@ -20,8 +20,9 @@ impl R + 'static> FnWith for F { #[doc(hidden)] pub trait WithFactory: 'static -where T: FromRequest, - R: Responder, +where + T: FromRequest, + R: Responder, { fn create(self) -> With; @@ -30,10 +31,11 @@ where T: FromRequest, #[doc(hidden)] pub trait WithAsyncFactory: 'static -where T: FromRequest, - R: Future, - I: Responder, - E: Into, +where + T: FromRequest, + R: Future, + I: Responder, + E: Into, { fn create(self) -> WithAsync; @@ -305,7 +307,6 @@ where } } - macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => { impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func where Func: Fn($($T,)+) -> Res + 'static, @@ -349,8 +350,27 @@ with_factory_tuple!((a, A), (b, B), (c, C), (d, D)); with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I)); +with_factory_tuple!( + (a, A), + (b, B), + (c, C), + (d, D), + (e, E), + (f, F), + (g, G), + (h, H) +); +with_factory_tuple!( + (a, A), + (b, B), + (c, C), + (d, D), + (e, E), + (f, F), + (g, G), + (h, H), + (i, I) +); with_async_factory_tuple!((a, A)); with_async_factory_tuple!((a, A), (b, B)); @@ -359,5 +379,24 @@ with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D)); with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I)); +with_async_factory_tuple!( + (a, A), + (b, B), + (c, C), + (d, D), + (e, E), + (f, F), + (g, G), + (h, H) +); +with_async_factory_tuple!( + (a, A), + (b, B), + (c, C), + (d, D), + (e, E), + (f, F), + (g, G), + (h, H), + (i, I) +); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 6b37bc7e..c16f8d6d 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -387,8 +387,7 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .finish(); + ).finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap() @@ -398,12 +397,10 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + ).header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .finish(); + ).finish(); assert_eq!( HandshakeError::NoVersionHeader, handshake(&req).err().unwrap() @@ -413,16 +410,13 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + ).header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + ).header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), - ) - .finish(); + ).finish(); assert_eq!( HandshakeError::UnsupportedVersion, handshake(&req).err().unwrap() @@ -432,16 +426,13 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + ).header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + ).header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .finish(); + ).finish(); assert_eq!( HandshakeError::BadWebsocketKey, handshake(&req).err().unwrap() @@ -451,20 +442,16 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + ).header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + ).header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + ).header( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) - .finish(); + ).finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, handshake(&req).unwrap().finish().status() diff --git a/tests/test_client.rs b/tests/test_client.rs index 16d95bf2..d7341ce1 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -118,8 +118,7 @@ fn test_client_gzip_encoding() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Deflate) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -148,8 +147,7 @@ fn test_client_gzip_encoding_large() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Deflate) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -181,8 +179,7 @@ fn test_client_gzip_encoding_large_random() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Deflate) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -200,7 +197,6 @@ fn test_client_gzip_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } - #[cfg(all(unix, feature = "uds"))] #[test] fn test_compatible_with_unix_socket_stream() { @@ -218,8 +214,7 @@ fn test_client_brotli_encoding() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Gzip) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -252,8 +247,7 @@ fn test_client_brotli_encoding_large_random() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Gzip) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -282,8 +276,7 @@ fn test_client_deflate_encoding() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Br) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -316,8 +309,7 @@ fn test_client_deflate_encoding_large_random() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Br) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -346,8 +338,7 @@ fn test_client_streaming_explicit() { .chunked() .content_encoding(http::ContentEncoding::Identity) .body(body)) - }) - .responder() + }).responder() }) }); diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 4243cd3a..3ea709c9 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -191,8 +191,7 @@ fn test_form_extractor() { .uri(srv.url("/test1/index.html")) .form(FormData { username: "test".to_string(), - }) - .unwrap(); + }).unwrap(); let response = srv.execute(request.send()).unwrap(); assert!(response.status().is_success()); @@ -306,8 +305,7 @@ fn test_path_and_query_extractor2_async() { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) - }) - .responder() + }).responder() }, ) }); @@ -336,8 +334,7 @@ fn test_path_and_query_extractor3_async() { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) - }) - .responder() + }).responder() }) }); }); @@ -361,8 +358,7 @@ fn test_path_and_query_extractor4_async() { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) - }) - .responder() + }).responder() }) }); }); @@ -387,8 +383,7 @@ fn test_path_and_query_extractor2_async2() { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) - }) - .responder() + }).responder() }, ) }); @@ -422,15 +417,13 @@ fn test_path_and_query_extractor2_async2() { fn test_path_and_query_extractor2_async3() { let mut srv = test::TestServer::new(|app| { app.resource("/{username}/index.html", |r| { - r.route().with( - |data: Json, p: Path, _: Query| { + r.route() + .with(|data: Json, p: Path, _: Query| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) - }) - .responder() - }, - ) + }).responder() + }) }); }); @@ -467,8 +460,7 @@ fn test_path_and_query_extractor2_async4() { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)) - }) - .responder() + }).responder() }) }); }); diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs index 4fa1c81d..6cb6ee36 100644 --- a/tests/test_middleware.rs +++ b/tests/test_middleware.rs @@ -84,11 +84,10 @@ fn test_middleware_multiple() { response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }) - .handler(|_| HttpResponse::Ok()) + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }).handler(|_| HttpResponse::Ok()) }); let request = srv.get().finish().unwrap(); @@ -143,11 +142,10 @@ fn test_resource_middleware_multiple() { response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }) - .handler(|_| HttpResponse::Ok()) + start: Arc::clone(&act_num1), + response: Arc::clone(&act_num2), + finish: Arc::clone(&act_num3), + }).handler(|_| HttpResponse::Ok()) }); let request = srv.get().finish().unwrap(); @@ -176,8 +174,7 @@ fn test_scope_middleware() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) }) }); @@ -207,13 +204,11 @@ fn test_scope_middleware_multiple() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .middleware(MiddlewareTest { + }).middleware(MiddlewareTest { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) }) }); @@ -242,8 +237,7 @@ fn test_middleware_async_handler() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/", |r| { + }).resource("/", |r| { r.route().a(|_| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(|_| Ok(HttpResponse::Ok())) @@ -312,8 +306,7 @@ fn test_scope_middleware_async_handler() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/test", |r| { + }).resource("/test", |r| { r.route().a(|_| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(|_| Ok(HttpResponse::Ok())) @@ -379,8 +372,7 @@ fn test_scope_middleware_async_error() { start: Arc::clone(&act_req), response: Arc::clone(&act_resp), finish: Arc::clone(&act_fin), - }) - .resource("/test", |r| r.f(index_test_middleware_async_error)) + }).resource("/test", |r| r.f(index_test_middleware_async_error)) }) }); @@ -514,13 +506,11 @@ fn test_async_middleware_multiple() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .middleware(MiddlewareAsyncTest { + }).middleware(MiddlewareAsyncTest { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); @@ -550,13 +540,11 @@ fn test_async_sync_middleware_multiple() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .middleware(MiddlewareTest { + }).middleware(MiddlewareTest { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) }); let request = srv.get().uri(srv.url("/test")).finish().unwrap(); @@ -587,8 +575,7 @@ fn test_async_scope_middleware() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) }) }); @@ -620,13 +607,11 @@ fn test_async_scope_middleware_multiple() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .middleware(MiddlewareAsyncTest { + }).middleware(MiddlewareAsyncTest { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) }) }); @@ -658,13 +643,11 @@ fn test_async_async_scope_middleware_multiple() { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .middleware(MiddlewareTest { + }).middleware(MiddlewareTest { start: Arc::clone(&act_num1), response: Arc::clone(&act_num2), finish: Arc::clone(&act_num3), - }) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) + }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) }) }); @@ -1012,8 +995,7 @@ fn test_session_storage_middleware() { App::new() .middleware(SessionStorage::new( CookieSessionBackend::signed(&[0; 32]).secure(false), - )) - .resource("/index", move |r| { + )).resource("/index", move |r| { r.f(|req| { let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD); assert!(res.is_ok()); @@ -1033,8 +1015,7 @@ fn test_session_storage_middleware() { HttpResponse::Ok() }) - }) - .resource("/expect_cookie", move |r| { + }).resource("/expect_cookie", move |r| { r.f(|req| { let _cookies = req.cookies().expect("To get cookies"); diff --git a/tests/test_server.rs b/tests/test_server.rs index 36c1b6e6..c573c4e1 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -59,8 +59,8 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] #[cfg(unix)] fn test_start() { - use std::sync::mpsc; use actix::System; + use std::sync::mpsc; let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); @@ -119,9 +119,9 @@ fn test_start() { #[test] #[cfg(unix)] fn test_shutdown() { - use std::sync::mpsc; - use std::net; use actix::System; + use std::net; + use std::sync::mpsc; let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); @@ -162,8 +162,8 @@ fn test_shutdown() { #[test] #[cfg(unix)] fn test_panic() { - use std::sync::mpsc; use actix::System; + use std::sync::mpsc; let _ = test::TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); @@ -176,8 +176,7 @@ fn test_panic() { r.method(http::Method::GET).f(|_| -> &'static str { panic!("error"); }); - }) - .resource("/", |r| { + }).resource("/", |r| { r.method(http::Method::GET).f(|_| HttpResponse::Ok()) }) }).workers(1); @@ -628,8 +627,7 @@ fn test_gzip_encoding() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -661,8 +659,7 @@ fn test_gzip_encoding_large() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -698,8 +695,7 @@ fn test_reading_gzip_encoding_large_random() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -731,8 +727,7 @@ fn test_reading_deflate_encoding() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -764,8 +759,7 @@ fn test_reading_deflate_encoding_large() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -801,8 +795,7 @@ fn test_reading_deflate_encoding_large_random() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -835,8 +828,7 @@ fn test_brotli_encoding() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -869,8 +861,7 @@ fn test_brotli_encoding_large() { Ok(HttpResponse::Ok() .content_encoding(http::ContentEncoding::Identity) .body(bytes)) - }) - .responder() + }).responder() }) }); @@ -946,14 +937,23 @@ fn test_server_cookies() { use actix_web::http; let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| r.f(|_| HttpResponse::Ok().cookie(http::CookieBuilder::new("first", "first_value").http_only(true).finish()) - .cookie(http::Cookie::new("second", "first_value")) - .cookie(http::Cookie::new("second", "second_value")) - .finish()) - ) + App::new().resource("/", |r| { + r.f(|_| { + HttpResponse::Ok() + .cookie( + http::CookieBuilder::new("first", "first_value") + .http_only(true) + .finish(), + ).cookie(http::Cookie::new("second", "first_value")) + .cookie(http::Cookie::new("second", "second_value")) + .finish() + }) + }) }); - let first_cookie = http::CookieBuilder::new("first", "first_value").http_only(true).finish(); + let first_cookie = http::CookieBuilder::new("first", "first_value") + .http_only(true) + .finish(); let second_cookie = http::Cookie::new("second", "second_value"); let request = srv.get().finish().unwrap(); @@ -972,10 +972,12 @@ fn test_server_cookies() { let first_cookie = first_cookie.to_string(); let second_cookie = second_cookie.to_string(); //Check that we have exactly two instances of raw cookie headers - let cookies = response.headers().get_all(http::header::SET_COOKIE) - .iter() - .map(|header| header.to_str().expect("To str").to_string()) - .collect::>(); + let cookies = response + .headers() + .get_all(http::header::SET_COOKIE) + .iter() + .map(|header| header.to_str().expect("To str").to_string()) + .collect::>(); assert_eq!(cookies.len(), 2); if cookies[0] == first_cookie { assert_eq!(cookies[1], second_cookie); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index aa57faf6..49118fc7 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -71,7 +71,7 @@ fn start_ws_resource(req: &HttpRequest) -> Result { #[test] fn test_simple_path() { - const PATH:&str = "/v1/ws/"; + const PATH: &str = "/v1/ws/"; // Create a websocket at a specific path. let mut srv = test::TestServer::new(|app| { @@ -103,7 +103,6 @@ fn test_simple_path() { ); } - #[test] fn test_empty_close_code() { let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); @@ -214,8 +213,7 @@ impl Ws2 { act.send(ctx); } actix::fut::ok(()) - }) - .wait(ctx); + }).wait(ctx); } } From 810995ade026935cf0de10356138aded3d8db7a6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 23 Aug 2018 10:10:13 -0700 Subject: [PATCH 0605/1635] fix tokio-tls dependency #480 --- Cargo.toml | 3 ++- src/client/connector.rs | 30 +++++++++++++++++++++++++----- src/lib.rs | 2 ++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6437ec26..bc182b16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ path = "src/lib.rs" default = ["session", "brotli", "flate2-c"] # tls -tls = ["native-tls"] +tls = ["native-tls", "tokio-tls"] # openssl alpn = ["openssl", "tokio-openssl"] @@ -104,6 +104,7 @@ tokio-reactor = "0.1" # native-tls native-tls = { version="0.2", optional = true } +tokio-tls = { version="0.2", optional = true } # openssl openssl = { version="0.10", optional = true } diff --git a/src/client/connector.rs b/src/client/connector.rs index 61347682..c0dbf85f 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -22,9 +22,9 @@ use openssl::ssl::{Error as OpensslError, SslConnector, SslMethod}; use tokio_openssl::SslConnectorExt; #[cfg(all(feature = "tls", not(feature = "alpn")))] -use native_tls::{Error as TlsError, TlsConnector, TlsStream}; +use native_tls::{Error as TlsError, TlsConnector as NativeTlsConnector}; #[cfg(all(feature = "tls", not(feature = "alpn")))] -use tokio_tls::TlsConnectorExt; +use tokio_tls::{TlsConnector, TlsStream}; #[cfg( all( @@ -301,14 +301,14 @@ impl Default for ClientConnector { #[cfg(all(feature = "tls", not(feature = "alpn")))] { let (tx, rx) = mpsc::unbounded(); - let builder = TlsConnector::builder().unwrap(); + let builder = NativeTlsConnector::builder(); ClientConnector { stats: ClientConnectorStats::default(), subscriber: None, acq_tx: tx, acq_rx: Some(rx), resolver: None, - connector: builder.build().unwrap(), + connector: builder.build().unwrap().into(), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), limit: 100, @@ -822,7 +822,7 @@ impl ClientConnector { if conn.0.ssl { fut::Either::A( act.connector - .connect_async(&conn.0.host, stream) + .connect(&conn.0.host, stream) .into_actor(act) .then(move |res, _, _| { match res { @@ -1342,3 +1342,23 @@ impl AsyncWrite for Connection { self.stream.shutdown() } } + +#[cfg(feature = "tls")] +/// This is temp solution untile actix-net migration +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) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4eeb5ada..f57ab937 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,6 +148,8 @@ extern crate serde_derive; #[cfg(feature = "tls")] extern crate native_tls; +#[cfg(feature = "tls")] +extern crate tokio_tls; #[cfg(feature = "openssl")] extern crate openssl; From 8dfc34e7851a205cb457f067b3232dab03a1abfe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 23 Aug 2018 10:27:32 -0700 Subject: [PATCH 0606/1635] fix tokio-tls IoStream impl --- src/client/connector.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index c0dbf85f..1217b5bc 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -24,7 +24,7 @@ use tokio_openssl::SslConnectorExt; #[cfg(all(feature = "tls", not(feature = "alpn")))] use native_tls::{Error as TlsError, TlsConnector as NativeTlsConnector}; #[cfg(all(feature = "tls", not(feature = "alpn")))] -use tokio_tls::{TlsConnector, TlsStream}; +use tokio_tls::{TlsConnector}; #[cfg( all( @@ -1343,6 +1343,9 @@ impl AsyncWrite for Connection { } } +#[cfg(feature = "tls")] +use tokio_tls::{TlsStream}; + #[cfg(feature = "tls")] /// This is temp solution untile actix-net migration impl IoStream for TlsStream { From 3dafe6c251187d9813e592e3f7b5d4ed4af37620 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 23 Aug 2018 11:30:07 -0700 Subject: [PATCH 0607/1635] hide token and server flags --- src/server/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/mod.rs b/src/server/mod.rs index 2ac933a7..0d10521a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -193,6 +193,7 @@ where HttpServer::new(factory) } +#[doc(hidden)] bitflags! { ///Flags that can be used to configure HTTP Server. pub struct ServerFlags: u8 { @@ -256,6 +257,7 @@ impl Message for StopServer { } /// Socket id token +#[doc(hidden)] #[derive(Clone, Copy)] pub struct Token(usize); From 9ef7a9c182cd38d791ce2ba32463827fa78a6f4a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 23 Aug 2018 11:30:49 -0700 Subject: [PATCH 0608/1635] hide AcceptorService --- src/server/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/mod.rs b/src/server/mod.rs index 0d10521a..36d85a78 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -336,6 +336,7 @@ impl IntoAsyncIo for net::TcpStream { } } +#[doc(hidden)] /// Trait implemented by types that could accept incomming socket connections. pub trait AcceptorService: Clone { /// Established connection type From 48ef18ffa9436260e6c5285d21c9982622d877d8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 23 Aug 2018 12:54:59 -0700 Subject: [PATCH 0609/1635] update changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fcaf2554..eaf7b42b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.4] - 2018-08-xx +## [0.7.4] - 2018-08-23 ### Added From 471a3e98064fd511466077984953368f08efd772 Mon Sep 17 00:00:00 2001 From: 0x1793d1 <2362128+0x1793d1@users.noreply.github.com> Date: Fri, 24 Aug 2018 23:21:32 +0200 Subject: [PATCH 0610/1635] Fix server startup log message --- src/server/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/server.rs b/src/server/server.rs index 7bab70f0..122571fd 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -217,7 +217,7 @@ impl Server { // start accept thread for sock in &self.sockets { for s in sock.iter() { - info!("Starting server on http://{:?}", s.1.local_addr().ok()); + info!("Starting server on http://{}", s.1.local_addr().unwrap()); } } let rx = self From c3ae9997fc126988e7aec80453886b824b9d7b36 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Sun, 26 Aug 2018 22:21:05 +0800 Subject: [PATCH 0611/1635] Fix bug with http1 client disconnects. --- src/client/parser.rs | 12 ++++----- src/server/channel.rs | 26 +++++++++++++----- src/server/h1.rs | 63 ++++++++++++++++++++++--------------------- src/server/mod.rs | 6 ++--- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index f5390cc3..dd4e60bc 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -41,10 +41,10 @@ impl HttpResponseParser { // if buf is empty parse_message will always return NotReady, let's avoid that if buf.is_empty() { match io.read_available(buf) { - Ok(Async::Ready(true)) => { + Ok(Async::Ready((_, true))) => { return Err(HttpResponseParserError::Disconnect) } - Ok(Async::Ready(false)) => (), + Ok(Async::Ready((_, false))) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => return Err(HttpResponseParserError::Error(err.into())), } @@ -63,10 +63,10 @@ impl HttpResponseParser { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } match io.read_available(buf) { - Ok(Async::Ready(true)) => { + Ok(Async::Ready((_, true))) => { return Err(HttpResponseParserError::Disconnect) } - Ok(Async::Ready(false)) => (), + Ok(Async::Ready((_, false))) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { return Err(HttpResponseParserError::Error(err.into())) @@ -87,8 +87,8 @@ impl HttpResponseParser { loop { // read payload let (not_ready, stream_finished) = match io.read_available(buf) { - Ok(Async::Ready(true)) => (false, true), - Ok(Async::Ready(false)) => (false, false), + Ok(Async::Ready((_, true))) => (false, true), + Ok(Async::Ready((_, false))) => (false, false), Ok(Async::NotReady) => (true, false), Err(err) => return Err(err.into()), }; diff --git a/src/server/channel.rs b/src/server/channel.rs index 7de561c6..84f30151 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -94,6 +94,7 @@ where }; } + let mut is_eof = false; let kind = match self.proto { Some(HttpProtocol::H1(ref mut h1)) => { let result = h1.poll(); @@ -120,16 +121,27 @@ where return result; } Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => { + let mut disconnect = false; match io.read_available(buf) { - Ok(Async::Ready(true)) | Err(_) => { - debug!("Ignored premature client disconnection"); - if let Some(n) = self.node.as_mut() { - n.remove() - }; - return Err(()); + Ok(Async::Ready((read_some, stream_closed))) => { + is_eof = stream_closed; + // Only disconnect if no data was read. + if is_eof && !read_some { + disconnect = true; + } + } + Err(_) => { + disconnect = true; } _ => (), } + if disconnect { + debug!("Ignored premature client disconnection"); + if let Some(n) = self.node.as_mut() { + n.remove() + }; + return Err(()); + } if buf.len() >= 14 { if buf[..14] == HTTP2_PREFACE[..] { @@ -149,7 +161,7 @@ where match kind { ProtocolKind::Http1 => { self.proto = - Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf))); + Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf, is_eof))); return self.poll(); } ProtocolKind::Http2 => { diff --git a/src/server/h1.rs b/src/server/h1.rs index 808dc11a..f9cfb622 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -90,10 +90,10 @@ where { pub fn new( settings: Rc>, stream: T, addr: Option, - buf: BytesMut, + buf: BytesMut, is_eof: bool, ) -> Self { Http1 { - flags: Flags::KEEPALIVE, + flags: Flags::KEEPALIVE | if is_eof { Flags::DISCONNECTED } else { Flags::empty() }, stream: H1Writer::new(stream, Rc::clone(&settings)), decoder: H1Decoder::new(), payload: None, @@ -132,6 +132,21 @@ where } } + fn client_disconnect(&mut self) { + // notify all tasks + self.notify_disconnect(); + // kill keepalive + self.keepalive_timer.take(); + + // on parse error, stop reading stream but tasks need to be + // completed + self.flags.insert(Flags::ERROR); + + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } + } + #[inline] pub fn poll(&mut self) -> Poll<(), ()> { // keep-alive timer @@ -188,38 +203,21 @@ where && self.can_read() { match self.stream.get_mut().read_available(&mut self.buf) { - Ok(Async::Ready(disconnected)) => { - if disconnected { - // notify all tasks - self.notify_disconnect(); - // kill keepalive - self.keepalive_timer.take(); - - // on parse error, stop reading stream but tasks need to be - // completed - self.flags.insert(Flags::ERROR); - - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); - } - } else { + Ok(Async::Ready((read_some, disconnected))) => { + if read_some { self.parse(); } + if disconnected { + // delay disconnect until all tasks have finished. + self.flags.insert(Flags::DISCONNECTED); + if self.tasks.is_empty() { + self.client_disconnect(); + } + } } Ok(Async::NotReady) => (), Err(_) => { - // notify all tasks - self.notify_disconnect(); - // kill keepalive - self.keepalive_timer.take(); - - // on parse error, stop reading stream but tasks need to be - // completed - self.flags.insert(Flags::ERROR); - - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); - } + self.client_disconnect(); } } } @@ -331,8 +329,13 @@ where } } - // deal with keep-alive + // deal with keep-alive and steam eof (client-side write shutdown) if self.tasks.is_empty() { + // handle stream eof + if self.flags.contains(Flags::DISCONNECTED) { + self.client_disconnect(); + return Ok(Async::Ready(false)); + } // no keep-alive if self.flags.contains(Flags::ERROR) || (!self.flags.contains(Flags::KEEPALIVE) diff --git a/src/server/mod.rs b/src/server/mod.rs index 36d85a78..009e06cc 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -390,7 +390,7 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn set_linger(&mut self, dur: Option) -> io::Result<()>; - fn read_available(&mut self, buf: &mut BytesMut) -> Poll { + fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> { let mut read_some = false; loop { if buf.remaining_mut() < LW_BUFFER_SIZE { @@ -400,7 +400,7 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { match self.read(buf.bytes_mut()) { Ok(n) => { if n == 0 { - return Ok(Async::Ready(!read_some)); + return Ok(Async::Ready((read_some, true))); } else { read_some = true; buf.advance_mut(n); @@ -409,7 +409,7 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { Err(e) => { return if e.kind() == io::ErrorKind::WouldBlock { if read_some { - Ok(Async::Ready(false)) + Ok(Async::Ready((read_some, false))) } else { Ok(Async::NotReady) } From 8393d09a0fea7a303362bbae676386998d960f52 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Mon, 27 Aug 2018 00:31:31 +0800 Subject: [PATCH 0612/1635] Fix tests. --- src/server/h1.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index f9cfb622..ae5dd465 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -611,7 +611,7 @@ mod tests { let readbuf = BytesMut::new(); let settings = Rc::new(wrk_settings()); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true); h1.poll_io(); h1.poll_io(); assert_eq!(h1.tasks.len(), 1); @@ -623,7 +623,7 @@ mod tests { let readbuf = BytesMut::new(); let settings = Rc::new(wrk_settings()); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf); + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true); h1.poll_io(); h1.poll_io(); assert!(h1.flags.contains(Flags::ERROR)); From 4bab50c8611683e9e51c6f49130838e934155fff Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 29 Aug 2018 20:53:31 +0200 Subject: [PATCH 0613/1635] Add ability to pass a custom TlsConnector (#491) --- src/client/connector.rs | 217 +++++++++++++--------------------------- 1 file changed, 68 insertions(+), 149 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 1217b5bc..430a0f75 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -17,14 +17,16 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; #[cfg(feature = "alpn")] -use openssl::ssl::{Error as OpensslError, SslConnector, SslMethod}; -#[cfg(feature = "alpn")] -use tokio_openssl::SslConnectorExt; +use { + openssl::ssl::{Error as SslError, SslConnector, SslMethod}, + tokio_openssl::SslConnectorExt +}; #[cfg(all(feature = "tls", not(feature = "alpn")))] -use native_tls::{Error as TlsError, TlsConnector as NativeTlsConnector}; -#[cfg(all(feature = "tls", not(feature = "alpn")))] -use tokio_tls::{TlsConnector}; +use { + native_tls::{Error as SslError, TlsConnector as NativeTlsConnector}, + tokio_tls::TlsConnector as SslConnector +}; #[cfg( all( @@ -32,42 +34,25 @@ use tokio_tls::{TlsConnector}; not(any(feature = "alpn", feature = "tls")) ) )] -use rustls::ClientConfig; +use { + rustls::ClientConfig, + std::io::Error as SslError, + std::sync::Arc, + tokio_rustls::ClientConfigExt, + webpki::DNSNameRef, + webpki_roots, +}; + #[cfg( all( feature = "rust-tls", not(any(feature = "alpn", feature = "tls")) ) )] -use std::io::Error as TLSError; -#[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) -)] -use std::sync::Arc; -#[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) -)] -use tokio_rustls::ClientConfigExt; -#[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) -)] -use webpki::DNSNameRef; -#[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) -)] -use webpki_roots; +type SslConnector = Arc; + +#[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] +type SslConnector = (); use server::IoStream; use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS}; @@ -173,24 +158,9 @@ pub enum ClientConnectorError { SslIsNotSupported, /// SSL error - #[cfg(feature = "alpn")] + #[cfg(any(feature = "tls", feature = "alpn", feature = "rust-tls"))] #[fail(display = "{}", _0)] - SslError(#[cause] OpensslError), - - /// SSL error - #[cfg(all(feature = "tls", not(feature = "alpn")))] - #[fail(display = "{}", _0)] - SslError(#[cause] TlsError), - - /// SSL error - #[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) - )] - #[fail(display = "{}", _0)] - SslError(#[cause] TLSError), + SslError(#[cause] SslError), /// Resolver error #[fail(display = "{}", _0)] @@ -242,17 +212,7 @@ impl Paused { /// `ClientConnector` type is responsible for transport layer of a /// client connection. pub struct ClientConnector { - #[cfg(all(feature = "alpn"))] connector: SslConnector, - #[cfg(all(feature = "tls", not(feature = "alpn")))] - connector: TlsConnector, - #[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) - )] - connector: Arc, stats: ClientConnectorStats, subscriber: Option>, @@ -293,71 +253,32 @@ impl SystemService for ClientConnector {} impl Default for ClientConnector { fn default() -> ClientConnector { - #[cfg(all(feature = "alpn"))] - { - let builder = SslConnector::builder(SslMethod::tls()).unwrap(); - ClientConnector::with_connector(builder.build()) - } - #[cfg(all(feature = "tls", not(feature = "alpn")))] - { - let (tx, rx) = mpsc::unbounded(); - let builder = NativeTlsConnector::builder(); - ClientConnector { - stats: ClientConnectorStats::default(), - subscriber: None, - acq_tx: tx, - acq_rx: Some(rx), - resolver: None, - connector: builder.build().unwrap().into(), - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: Some(HashMap::new()), - wait_timeout: None, - paused: Paused::No, - } - } - #[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) - )] - { - let mut config = ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - ClientConnector::with_connector(config) - } + let connector = { + #[cfg(all(feature = "alpn"))] + { SslConnector::builder(SslMethod::tls()).unwrap().build() } - #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] - { - let (tx, rx) = mpsc::unbounded(); - ClientConnector { - stats: ClientConnectorStats::default(), - subscriber: None, - acq_tx: tx, - acq_rx: Some(rx), - resolver: None, - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: Some(HashMap::new()), - wait_timeout: None, - paused: Paused::No, + #[cfg(all(feature = "tls", not(feature = "alpn")))] + { NativeTlsConnector::builder().build().unwrap().into() } + + #[cfg( + all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ) + )] + { + let mut config = ClientConfig::new(); + config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + Arc::new(config) } - } + + #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] + { () } + }; + + ClientConnector::with_connector_impl(connector) } } @@ -402,27 +323,8 @@ impl ClientConnector { /// } /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { - let (tx, rx) = mpsc::unbounded(); - - ClientConnector { - connector, - stats: ClientConnectorStats::default(), - subscriber: None, - acq_tx: tx, - acq_rx: Some(rx), - resolver: None, - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: Some(HashMap::new()), - wait_timeout: None, - paused: Paused::No, - } + // keep level of indirection for docstrings matching featureflags + Self::with_connector_impl(connector) } #[cfg( @@ -476,10 +378,27 @@ impl ClientConnector { /// } /// ``` pub fn with_connector(connector: ClientConfig) -> ClientConnector { + // keep level of indirection for docstrings matching featureflags + Self::with_connector_impl(Arc::new(connector)) + } + + #[cfg( + all( + feature = "tls", + not(any(feature = "alpn", feature = "rust-tls")) + ) + )] + pub fn with_connector(connector: SslConnector) -> ClientConnector { + // keep level of indirection for docstrings matching featureflags + Self::with_connector_impl(connector) + } + + #[inline] + fn with_connector_impl(connector: SslConnector) -> ClientConnector { let (tx, rx) = mpsc::unbounded(); ClientConnector { - connector: Arc::new(connector), + connector, stats: ClientConnectorStats::default(), subscriber: None, acq_tx: tx, @@ -1364,4 +1283,4 @@ impl IoStream for TlsStream { fn set_linger(&mut self, dur: Option) -> io::Result<()> { self.get_mut().get_mut().set_linger(dur) } -} \ No newline at end of file +} From 797b52ecbf21bfd5cfec2306653af6741279b595 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 29 Aug 2018 20:58:23 +0200 Subject: [PATCH 0614/1635] Update CHANGES.md --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index eaf7b42b..34b0a962 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.7.5] - 2018-09-xx + +### Added + +* Added the ability to pass a custom `TlsConnector`. + ## [0.7.4] - 2018-08-23 ### Added From 3ccbce6bc833959c61f9fd2eb440b2cc7370d0cd Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Sat, 1 Sep 2018 00:08:53 +0800 Subject: [PATCH 0615/1635] Fix issue with 'Connection: close' in ClientRequest --- src/client/parser.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index dd4e60bc..5dd16339 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -41,7 +41,8 @@ impl HttpResponseParser { // if buf is empty parse_message will always return NotReady, let's avoid that if buf.is_empty() { match io.read_available(buf) { - Ok(Async::Ready((_, true))) => { + Ok(Async::Ready((true, true))) => (), + Ok(Async::Ready((false, true))) => { return Err(HttpResponseParserError::Disconnect) } Ok(Async::Ready((_, false))) => (), @@ -63,7 +64,8 @@ impl HttpResponseParser { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } match io.read_available(buf) { - Ok(Async::Ready((_, true))) => { + Ok(Async::Ready((true, true))) => (), + Ok(Async::Ready((false, true))) => { return Err(HttpResponseParserError::Disconnect) } Ok(Async::Ready((_, false))) => (), From 487519acec5d419146a3493f03bd1fba44b56b5b Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Sat, 1 Sep 2018 00:34:19 +0800 Subject: [PATCH 0616/1635] Add client test for 'Connection: close' as reported in issue #495 --- tests/test_client.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_client.rs b/tests/test_client.rs index d7341ce1..d4a2ce1f 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -66,6 +66,16 @@ fn test_simple() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[test] +fn test_connection_close() { + let mut srv = + test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + + let request = srv.get().header("Connection", "close").finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); +} + #[test] fn test_with_query_parameter() { let mut srv = test::TestServer::new(|app| { From 23416561734c925ba678284d02b4e4c56b11a699 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Sat, 1 Sep 2018 01:41:38 +0800 Subject: [PATCH 0617/1635] Simplify buffer reading logic. Remove duplicate code. --- src/client/parser.rs | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index 5dd16339..7348de32 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -38,20 +38,17 @@ impl HttpResponseParser { where T: IoStream, { - // if buf is empty parse_message will always return NotReady, let's avoid that - if buf.is_empty() { + loop { match io.read_available(buf) { - Ok(Async::Ready((true, true))) => (), Ok(Async::Ready((false, true))) => { return Err(HttpResponseParserError::Disconnect) } - Ok(Async::Ready((_, false))) => (), + Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(HttpResponseParserError::Error(err.into())), + Err(err) => { + return Err(HttpResponseParserError::Error(err.into())) + } } - } - - loop { match HttpResponseParser::parse_message(buf) .map_err(HttpResponseParserError::Error)? { @@ -63,17 +60,6 @@ impl HttpResponseParser { if buf.capacity() >= MAX_BUFFER_SIZE { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } - match io.read_available(buf) { - Ok(Async::Ready((true, true))) => (), - Ok(Async::Ready((false, true))) => { - return Err(HttpResponseParserError::Disconnect) - } - Ok(Async::Ready((_, false))) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { - return Err(HttpResponseParserError::Error(err.into())) - } - } } } } From a42a8a2321bfb1d32599206f70105b085d08387e Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Sat, 1 Sep 2018 02:15:36 +0800 Subject: [PATCH 0618/1635] Add some comments to clarify logic. --- src/client/parser.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/parser.rs b/src/client/parser.rs index 7348de32..b6f4ea3f 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -39,6 +39,7 @@ impl HttpResponseParser { T: IoStream, { loop { + // Read some more data into the buffer for the parser. match io.read_available(buf) { Ok(Async::Ready((false, true))) => { return Err(HttpResponseParserError::Disconnect) @@ -49,6 +50,8 @@ impl HttpResponseParser { return Err(HttpResponseParserError::Error(err.into())) } } + + // Call HTTP response parser. match HttpResponseParser::parse_message(buf) .map_err(HttpResponseParserError::Error)? { @@ -60,6 +63,7 @@ impl HttpResponseParser { if buf.capacity() >= MAX_BUFFER_SIZE { return Err(HttpResponseParserError::Error(ParseError::TooLarge)); } + // Parser needs more data. Loop and read more data. } } } From 66881d7dd196eb7a588b576e6e4654362c326cf4 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Sat, 1 Sep 2018 02:25:05 +0800 Subject: [PATCH 0619/1635] If buffer is empty, read more data before calling parser. --- src/client/parser.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/client/parser.rs b/src/client/parser.rs index b6f4ea3f..5fd81da2 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -39,6 +39,23 @@ impl HttpResponseParser { T: IoStream, { loop { + // Don't call parser until we have data to parse. + if !buf.is_empty() { + match HttpResponseParser::parse_message(buf) + .map_err(HttpResponseParserError::Error)? + { + Async::Ready((msg, decoder)) => { + self.decoder = decoder; + return Ok(Async::Ready(msg)); + } + Async::NotReady => { + if buf.capacity() >= MAX_BUFFER_SIZE { + return Err(HttpResponseParserError::Error(ParseError::TooLarge)); + } + // Parser needs more data. + } + } + } // Read some more data into the buffer for the parser. match io.read_available(buf) { Ok(Async::Ready((false, true))) => { @@ -50,22 +67,6 @@ impl HttpResponseParser { return Err(HttpResponseParserError::Error(err.into())) } } - - // Call HTTP response parser. - match HttpResponseParser::parse_message(buf) - .map_err(HttpResponseParserError::Error)? - { - Async::Ready((msg, decoder)) => { - self.decoder = decoder; - return Ok(Async::Ready(msg)); - } - Async::NotReady => { - if buf.capacity() >= MAX_BUFFER_SIZE { - return Err(HttpResponseParserError::Error(ParseError::TooLarge)); - } - // Parser needs more data. Loop and read more data. - } - } } } From 2d518318993bd1c5393136371acc0a74887c4e97 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 31 Aug 2018 17:24:13 -0700 Subject: [PATCH 0620/1635] handle socket read disconnect --- CHANGES.md | 5 ++++ src/server/h1.rs | 60 +++++++++++++++++++++++++----------------- src/server/h1writer.rs | 20 +++++++------- 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 34b0a962..d99aa8ba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,11 @@ * Added the ability to pass a custom `TlsConnector`. +### Fixed + +* Handle socket read disconnect + + ## [0.7.4] - 2018-08-23 ### Added diff --git a/src/server/h1.rs b/src/server/h1.rs index ae5dd465..1acae26e 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -22,13 +22,14 @@ use super::{HttpHandler, HttpHandlerTask, IoStream}; const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const ERROR = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const DISCONNECTED = 0b0001_0000; - const POLLED = 0b0010_0000; + pub struct Flags: u8 { + const STARTED = 0b0000_0001; + const ERROR = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const SHUTDOWN = 0b0000_1000; + const READ_DISCONNECTED = 0b0001_0000; + const WRITE_DISCONNECTED = 0b0010_0000; + const POLLED = 0b0100_0000; } } @@ -93,7 +94,7 @@ where buf: BytesMut, is_eof: bool, ) -> Self { Http1 { - flags: Flags::KEEPALIVE | if is_eof { Flags::DISCONNECTED } else { Flags::empty() }, + flags: if is_eof { Flags::READ_DISCONNECTED } else { Flags::KEEPALIVE }, stream: H1Writer::new(stream, Rc::clone(&settings)), decoder: H1Decoder::new(), payload: None, @@ -117,6 +118,10 @@ where #[inline] fn can_read(&self) -> bool { + if self.flags.intersects(Flags::ERROR | Flags::READ_DISCONNECTED) { + return false + } + if let Some(ref info) = self.payload { info.need_read() == PayloadStatus::Read } else { @@ -125,6 +130,8 @@ where } fn notify_disconnect(&mut self) { + self.flags.insert(Flags::WRITE_DISCONNECTED); + // notify all tasks self.stream.disconnected(); for task in &mut self.tasks { @@ -163,11 +170,15 @@ where // shutdown if self.flags.contains(Flags::SHUTDOWN) { + if self.flags.intersects( + Flags::ERROR | Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) { + return Ok(Async::Ready(())) + } match self.stream.poll_completed(true) { Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::Ready(_)) => return Ok(Async::Ready(())), Err(err) => { - debug!("Error sending data: {}", err); + debug!("Error sendips ng data: {}", err); return Err(()); } } @@ -197,11 +208,9 @@ where self.flags.insert(Flags::POLLED); return; } + // read io from socket - if !self.flags.intersects(Flags::ERROR) - && self.tasks.len() < MAX_PIPELINED_MESSAGES - && self.can_read() - { + if self.can_read() && self.tasks.len() < MAX_PIPELINED_MESSAGES { match self.stream.get_mut().read_available(&mut self.buf) { Ok(Async::Ready((read_some, disconnected))) => { if read_some { @@ -209,7 +218,7 @@ where } if disconnected { // delay disconnect until all tasks have finished. - self.flags.insert(Flags::DISCONNECTED); + self.flags.insert(Flags::READ_DISCONNECTED); if self.tasks.is_empty() { self.client_disconnect(); } @@ -231,7 +240,9 @@ where let mut idx = 0; while idx < self.tasks.len() { // only one task can do io operation in http/1 - if !io && !self.tasks[idx].flags.contains(EntryFlags::EOF) { + if !io && !self.tasks[idx].flags.contains(EntryFlags::EOF) + && !self.flags.contains(Flags::WRITE_DISCONNECTED) + { // io is corrupted, send buffer if self.tasks[idx].flags.contains(EntryFlags::ERROR) { if let Ok(Async::NotReady) = self.stream.poll_completed(true) { @@ -295,7 +306,6 @@ where } // cleanup finished tasks - let max = self.tasks.len() >= MAX_PIPELINED_MESSAGES; while !self.tasks.is_empty() { if self.tasks[0] .flags @@ -306,15 +316,13 @@ where break; } } - // read more message - if max && self.tasks.len() >= MAX_PIPELINED_MESSAGES { - return Ok(Async::Ready(true)); - } // check stream state if self.flags.contains(Flags::STARTED) { match self.stream.poll_completed(false) { - Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::NotReady) => { + return Ok(Async::NotReady) + }, Err(err) => { debug!("Error sending data: {}", err); self.notify_disconnect(); @@ -332,8 +340,7 @@ where // deal with keep-alive and steam eof (client-side write shutdown) if self.tasks.is_empty() { // handle stream eof - if self.flags.contains(Flags::DISCONNECTED) { - self.client_disconnect(); + if self.flags.contains(Flags::READ_DISCONNECTED) { return Ok(Async::Ready(false)); } // no keep-alive @@ -451,7 +458,12 @@ where break; } } - Ok(None) => break, + Ok(None) => { + if self.flags.contains(Flags::READ_DISCONNECTED) && self.tasks.is_empty() { + self.client_disconnect(); + } + break + }, Err(e) => { self.flags.insert(Flags::ERROR); if let Some(mut payload) = self.payload.take() { diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 8981f9df..422f0ebc 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -63,7 +63,9 @@ impl H1Writer { self.flags = Flags::KEEPALIVE; } - pub fn disconnected(&mut self) {} + pub fn disconnected(&mut self) { + self.flags.insert(Flags::DISCONNECTED); + } pub fn keepalive(&self) -> bool { self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) @@ -268,10 +270,7 @@ impl Writer for H1Writer { let pl: &[u8] = payload.as_ref(); let n = match Self::write_data(&mut self.stream, pl) { Err(err) => { - if err.kind() == io::ErrorKind::WriteZero { - self.disconnected(); - } - + self.disconnected(); return Err(err); } Ok(val) => val, @@ -315,14 +314,15 @@ impl Writer for H1Writer { #[inline] fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { + if self.flags.contains(Flags::DISCONNECTED) { + return Err(io::Error::new(io::ErrorKind::Other, "disconnected")); + } + if !self.buffer.is_empty() { let written = { match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) { Err(err) => { - if err.kind() == io::ErrorKind::WriteZero { - self.disconnected(); - } - + self.disconnected(); return Err(err); } Ok(val) => val, @@ -339,7 +339,7 @@ impl Writer for H1Writer { self.stream.poll_flush()?; self.stream.shutdown() } else { - self.stream.poll_flush() + Ok(self.stream.poll_flush()?) } } } From 3fa23f5e10ce89ee7f06bddf0a8b3ac35062cd39 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 31 Aug 2018 17:25:15 -0700 Subject: [PATCH 0621/1635] update version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bc182b16..631b48dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.4" +version = "0.7.5" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From c313c003a4b8b3526b33f782996116263cba7140 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 31 Aug 2018 17:45:29 -0700 Subject: [PATCH 0622/1635] Fix typo --- src/server/h1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 1acae26e..dd849710 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -178,7 +178,7 @@ where Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::Ready(_)) => return Ok(Async::Ready(())), Err(err) => { - debug!("Error sendips ng data: {}", err); + debug!("Error sending data: {}", err); return Err(()); } } From 0b42cae08254768d7b16ab95ffce1d2269ff0b05 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 31 Aug 2018 18:54:19 -0700 Subject: [PATCH 0623/1635] update tests --- src/server/h1.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 1acae26e..65292297 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -291,9 +291,8 @@ where } else if !self.tasks[idx].flags.contains(EntryFlags::FINISHED) { match self.tasks[idx].pipe.poll_completed() { Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - self.tasks[idx].flags.insert(EntryFlags::FINISHED) - } + Ok(Async::Ready(_)) => + self.tasks[idx].flags.insert(EntryFlags::FINISHED), Err(err) => { self.notify_disconnect(); self.tasks[idx].flags.insert(EntryFlags::ERROR); @@ -618,24 +617,35 @@ mod tests { } #[test] - fn test_req_parse() { + fn test_req_parse1() { let buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n"); let readbuf = BytesMut::new(); let settings = Rc::new(wrk_settings()); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true); + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false); h1.poll_io(); h1.poll_io(); assert_eq!(h1.tasks.len(), 1); } + #[test] + fn test_req_parse2() { + let buf = Buffer::new(""); + let readbuf = BytesMut::from(Vec::::from(&b"GET /test HTTP/1.1\r\n\r\n"[..])); + let settings = Rc::new(wrk_settings()); + + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true); + h1.poll_io(); + assert_eq!(h1.tasks.len(), 1); + } + #[test] fn test_req_parse_err() { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); let readbuf = BytesMut::new(); let settings = Rc::new(wrk_settings()); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true); + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false); h1.poll_io(); h1.poll_io(); assert!(h1.flags.contains(Flags::ERROR)); From a2b170fec96d0d101dcd7e1abd31c5595d88e453 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 31 Aug 2018 18:56:21 -0700 Subject: [PATCH 0624/1635] fmt --- src/client/connector.rs | 86 ++++++++++++++++++----------------------- src/client/parser.rs | 8 ++-- src/param.rs | 1 - src/server/channel.rs | 5 ++- src/server/h1.rs | 41 +++++++++++++------- 5 files changed, 71 insertions(+), 70 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 430a0f75..694e03bc 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -19,36 +19,28 @@ use tokio_timer::Delay; #[cfg(feature = "alpn")] use { openssl::ssl::{Error as SslError, SslConnector, SslMethod}, - tokio_openssl::SslConnectorExt + tokio_openssl::SslConnectorExt, }; #[cfg(all(feature = "tls", not(feature = "alpn")))] use { native_tls::{Error as SslError, TlsConnector as NativeTlsConnector}, - tokio_tls::TlsConnector as SslConnector + tokio_tls::TlsConnector as SslConnector, }; -#[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) -)] +#[cfg(all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) +))] use { - rustls::ClientConfig, - std::io::Error as SslError, - std::sync::Arc, - tokio_rustls::ClientConfigExt, - webpki::DNSNameRef, - webpki_roots, + rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc, + tokio_rustls::ClientConfigExt, webpki::DNSNameRef, webpki_roots, }; -#[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) -)] +#[cfg(all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) +))] type SslConnector = Arc; #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] @@ -255,17 +247,19 @@ impl Default for ClientConnector { fn default() -> ClientConnector { let connector = { #[cfg(all(feature = "alpn"))] - { SslConnector::builder(SslMethod::tls()).unwrap().build() } + { + SslConnector::builder(SslMethod::tls()).unwrap().build() + } #[cfg(all(feature = "tls", not(feature = "alpn")))] - { NativeTlsConnector::builder().build().unwrap().into() } + { + NativeTlsConnector::builder().build().unwrap().into() + } - #[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) - )] + #[cfg(all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ))] { let mut config = ClientConfig::new(); config @@ -275,7 +269,9 @@ impl Default for ClientConnector { } #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] - { () } + { + () + } }; ClientConnector::with_connector_impl(connector) @@ -327,12 +323,10 @@ impl ClientConnector { Self::with_connector_impl(connector) } - #[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) - )] + #[cfg(all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ))] /// Create `ClientConnector` actor with custom `SslConnector` instance. /// /// By default `ClientConnector` uses very a simple SSL configuration. @@ -382,12 +376,10 @@ impl ClientConnector { Self::with_connector_impl(Arc::new(connector)) } - #[cfg( - all( - feature = "tls", - not(any(feature = "alpn", feature = "rust-tls")) - ) - )] + #[cfg(all( + feature = "tls", + not(any(feature = "alpn", feature = "rust-tls")) + ))] pub fn with_connector(connector: SslConnector) -> ClientConnector { // keep level of indirection for docstrings matching featureflags Self::with_connector_impl(connector) @@ -772,12 +764,10 @@ impl ClientConnector { } } - #[cfg( - all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) - ) - )] + #[cfg(all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "tls")) + ))] match res { Err(err) => { let _ = waiter.tx.send(Err(err.into())); @@ -1263,7 +1253,7 @@ impl AsyncWrite for Connection { } #[cfg(feature = "tls")] -use tokio_tls::{TlsStream}; +use tokio_tls::TlsStream; #[cfg(feature = "tls")] /// This is temp solution untile actix-net migration diff --git a/src/client/parser.rs b/src/client/parser.rs index 5fd81da2..0ee4598d 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -50,7 +50,9 @@ impl HttpResponseParser { } Async::NotReady => { if buf.capacity() >= MAX_BUFFER_SIZE { - return Err(HttpResponseParserError::Error(ParseError::TooLarge)); + return Err(HttpResponseParserError::Error( + ParseError::TooLarge, + )); } // Parser needs more data. } @@ -63,9 +65,7 @@ impl HttpResponseParser { } Ok(Async::Ready(_)) => (), Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { - return Err(HttpResponseParserError::Error(err.into())) - } + Err(err) => return Err(HttpResponseParserError::Error(err.into())), } } } diff --git a/src/param.rs b/src/param.rs index 063159d7..d0664df9 100644 --- a/src/param.rs +++ b/src/param.rs @@ -236,7 +236,6 @@ macro_rules! FROM_STR { ($type:ty) => { impl FromParam for $type { type Err = InternalError<<$type as FromStr>::Err>; - fn from_param(val: &str) -> Result { <$type as FromStr>::from_str(val) .map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST)) diff --git a/src/server/channel.rs b/src/server/channel.rs index 84f30151..bec1c4c8 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -160,8 +160,9 @@ where if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { match kind { ProtocolKind::Http1 => { - self.proto = - Some(HttpProtocol::H1(h1::Http1::new(settings, io, addr, buf, is_eof))); + self.proto = Some(HttpProtocol::H1(h1::Http1::new( + settings, io, addr, buf, is_eof, + ))); return self.poll(); } ProtocolKind::Http2 => { diff --git a/src/server/h1.rs b/src/server/h1.rs index 65292297..f4875519 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -94,7 +94,11 @@ where buf: BytesMut, is_eof: bool, ) -> Self { Http1 { - flags: if is_eof { Flags::READ_DISCONNECTED } else { Flags::KEEPALIVE }, + flags: if is_eof { + Flags::READ_DISCONNECTED + } else { + Flags::KEEPALIVE + }, stream: H1Writer::new(stream, Rc::clone(&settings)), decoder: H1Decoder::new(), payload: None, @@ -118,8 +122,11 @@ where #[inline] fn can_read(&self) -> bool { - if self.flags.intersects(Flags::ERROR | Flags::READ_DISCONNECTED) { - return false + if self + .flags + .intersects(Flags::ERROR | Flags::READ_DISCONNECTED) + { + return false; } if let Some(ref info) = self.payload { @@ -171,8 +178,9 @@ where // shutdown if self.flags.contains(Flags::SHUTDOWN) { if self.flags.intersects( - Flags::ERROR | Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) { - return Ok(Async::Ready(())) + Flags::ERROR | Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED, + ) { + return Ok(Async::Ready(())); } match self.stream.poll_completed(true) { Ok(Async::NotReady) => return Ok(Async::NotReady), @@ -240,7 +248,8 @@ where let mut idx = 0; while idx < self.tasks.len() { // only one task can do io operation in http/1 - if !io && !self.tasks[idx].flags.contains(EntryFlags::EOF) + if !io + && !self.tasks[idx].flags.contains(EntryFlags::EOF) && !self.flags.contains(Flags::WRITE_DISCONNECTED) { // io is corrupted, send buffer @@ -291,8 +300,9 @@ where } else if !self.tasks[idx].flags.contains(EntryFlags::FINISHED) { match self.tasks[idx].pipe.poll_completed() { Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => - self.tasks[idx].flags.insert(EntryFlags::FINISHED), + Ok(Async::Ready(_)) => { + self.tasks[idx].flags.insert(EntryFlags::FINISHED) + } Err(err) => { self.notify_disconnect(); self.tasks[idx].flags.insert(EntryFlags::ERROR); @@ -319,9 +329,7 @@ where // check stream state if self.flags.contains(Flags::STARTED) { match self.stream.poll_completed(false) { - Ok(Async::NotReady) => { - return Ok(Async::NotReady) - }, + Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); self.notify_disconnect(); @@ -458,11 +466,13 @@ where } } Ok(None) => { - if self.flags.contains(Flags::READ_DISCONNECTED) && self.tasks.is_empty() { + if self.flags.contains(Flags::READ_DISCONNECTED) + && self.tasks.is_empty() + { self.client_disconnect(); } - break - }, + break; + } Err(e) => { self.flags.insert(Flags::ERROR); if let Some(mut payload) = self.payload.take() { @@ -631,7 +641,8 @@ mod tests { #[test] fn test_req_parse2() { let buf = Buffer::new(""); - let readbuf = BytesMut::from(Vec::::from(&b"GET /test HTTP/1.1\r\n\r\n"[..])); + let readbuf = + BytesMut::from(Vec::::from(&b"GET /test HTTP/1.1\r\n\r\n"[..])); let settings = Rc::new(wrk_settings()); let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true); From 0425e2776f5cbcaca3489a5dd565b12e63bc688c Mon Sep 17 00:00:00 2001 From: Robert Gabriel Jakabosky Date: Sat, 1 Sep 2018 17:00:32 +0800 Subject: [PATCH 0625/1635] Fix Issue #490 (#498) * Add failing testcase for HTTP 404 response with no reason text. * Include canonical reason test for HTTP error responses. * Don't send a reason for unknown status codes. --- src/server/error.rs | 5 +++++ tests/test_server.rs | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/server/error.rs b/src/server/error.rs index 5bd0bf83..d08ccf87 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -21,7 +21,12 @@ impl HttpHandlerTask for ServerError { bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1); helpers::write_status_line(self.0, self.1.as_u16(), bytes); } + // Convert Status Code to Reason. + let reason = self.1.canonical_reason().unwrap_or(""); + io.buffer().extend_from_slice(reason.as_bytes()); + // No response body. io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n"); + // date header io.set_date(); Ok(Async::Ready(true)) } diff --git a/tests/test_server.rs b/tests/test_server.rs index c573c4e1..8235be6b 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -932,6 +932,29 @@ fn test_application() { assert!(response.status().is_success()); } +#[test] +fn test_default_404_handler_response() { + let mut srv = test::TestServer::with_factory(|| { + App::new() + .prefix("/app") + .resource("", |r| r.f(|_| HttpResponse::Ok())) + .resource("/", |r| r.f(|_| HttpResponse::Ok())) + }); + let addr = srv.addr(); + + let mut buf = [0; 24]; + let request = TcpStream::connect(&addr) + .and_then(|sock| { + tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n") + .and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf)) + .and_then(|(_, buf)| Ok(buf)) + }) + .map_err(|e| panic!("{:?}", e)); + let response = srv.execute(request).unwrap(); + let rep = String::from_utf8_lossy(&response[..]); + assert!(rep.contains("HTTP/1.1 404 Not Found")); +} + #[test] fn test_server_cookies() { use actix_web::http; From 3439f552886e650ce5293575f9808e40f76909b6 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 1 Sep 2018 17:13:52 +0200 Subject: [PATCH 0626/1635] doc: Add example for using custom nativetls connector (#497) --- src/client/connector.rs | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 694e03bc..239a00c5 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -292,7 +292,6 @@ impl ClientConnector { /// # extern crate futures; /// # use futures::{future, Future}; /// # use std::io::Write; - /// # use std::process; /// # use actix_web::actix::Actor; /// extern crate openssl; /// use actix_web::{actix, client::ClientConnector, client::Connect}; @@ -337,10 +336,8 @@ impl ClientConnector { /// # #![cfg(feature = "rust-tls")] /// # extern crate actix_web; /// # extern crate futures; - /// # extern crate tokio; /// # use futures::{future, Future}; /// # use std::io::Write; - /// # use std::process; /// # use actix_web::actix::Actor; /// extern crate rustls; /// extern crate webpki_roots; @@ -380,6 +377,42 @@ impl ClientConnector { feature = "tls", not(any(feature = "alpn", feature = "rust-tls")) ))] + /// Create `ClientConnector` actor with custom `SslConnector` instance. + /// + /// By default `ClientConnector` uses very a simple SSL configuration. + /// With `with_connector` method it is possible to use a custom + /// `SslConnector` object. + /// + /// ```rust + /// # #![cfg(feature = "tls")] + /// # extern crate actix_web; + /// # extern crate futures; + /// # use futures::{future, Future}; + /// # use std::io::Write; + /// # use actix_web::actix::Actor; + /// extern crate native_tls; + /// extern crate webpki_roots; + /// use native_tls::TlsConnector; + /// use actix_web::{actix, client::ClientConnector, client::Connect}; + /// + /// fn main() { + /// actix::run(|| { + /// let connector = TlsConnector::new().unwrap(); + /// let conn = ClientConnector::with_connector(connector.into()).start(); + /// + /// conn.send( + /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host + /// .map_err(|_| ()) + /// .and_then(|res| { + /// if let Ok(mut stream) = res { + /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); + /// } + /// # actix::System::current().stop(); + /// Ok(()) + /// }) + /// }); + /// } + /// ``` pub fn with_connector(connector: SslConnector) -> ClientConnector { // keep level of indirection for docstrings matching featureflags Self::with_connector_impl(connector) From f2f05e77155ba08348906ed31491a9fa9ae3cc5e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 2 Sep 2018 07:47:19 -0700 Subject: [PATCH 0627/1635] allow to register handlers on scope level #465 --- CHANGES.md | 3 +++ src/scope.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d99aa8ba..b8ab0f87 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,9 @@ * Added the ability to pass a custom `TlsConnector`. +* Allow to register handlers on scope level #465 + + ### Fixed * Handle socket read disconnect diff --git a/src/scope.rs b/src/scope.rs index 8298f534..83e43f43 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -5,7 +5,10 @@ use std::rc::Rc; use futures::{Async, Future, Poll}; use error::Error; -use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler}; +use handler::{ + AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, RouteHandler, + WrapHandler, +}; use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -286,6 +289,44 @@ impl Scope { self } + /// Configure handler for specific path prefix. + /// + /// A path prefix consists of valid path segments, i.e for the + /// prefix `/app` any request with the paths `/app`, `/app/` or + /// `/app/test` would match, but the path `/application` would + /// not. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpRequest, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().scope("/scope-prefix", |scope| { + /// handler("/app", |req: &HttpRequest| match *req.method() { + /// http::Method::GET => HttpResponse::Ok(), + /// http::Method::POST => HttpResponse::MethodNotAllowed(), + /// _ => HttpResponse::NotFound(), + /// }) + /// }); + /// } + /// ``` + pub fn handler>(mut self, path: &str, handler: H) -> Scope { + { + let mut path = path.trim().trim_right_matches('/').to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/') + } + if path.len() > 1 && path.ends_with('/') { + path.pop(); + } + + Rc::get_mut(&mut self.router) + .expect("Multiple copies of scope router") + .register_handler(&path, Box::new(WrapHandler::new(handler)), None); + } + self + } + /// Register a scope middleware /// /// This is similar to `App's` middlewares, but @@ -1120,4 +1161,32 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); } + + #[test] + fn test_handler() { + let app = App::new() + .scope("/scope", |scope| { + scope.handler("/test", |_: &_| HttpResponse::Ok()) + }).finish(); + + let req = TestRequest::with_uri("/scope/test").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/scope/test/").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/scope/test/app").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/scope/testapp").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/scope/blah").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + } } From 968c81e2678ee301b5f685181bac5edec7d312b2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 2 Sep 2018 08:14:54 -0700 Subject: [PATCH 0628/1635] Handling scoped paths without leading slashes #460 --- CHANGES.md | 4 ++- src/scope.rs | 84 +++++++++++++++++++++++++++++++++++--------- tests/test_server.rs | 3 +- 3 files changed, 71 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b8ab0f87..dd6cdcd2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.5] - 2018-09-xx +## [0.7.5] - 2018-09-02 ### Added @@ -13,6 +13,8 @@ * Handle socket read disconnect +* Handling scoped paths without leading slashes #460 + ## [0.7.4] - 2018-08-23 diff --git a/src/scope.rs b/src/scope.rs index 83e43f43..a1fd907a 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -183,7 +183,7 @@ impl Scope { where F: FnOnce(Scope) -> Scope, { - let rdef = ResourceDef::prefix(&path); + let rdef = ResourceDef::prefix(&insert_slash(path)); let scope = Scope { rdef: rdef.clone(), filters: Vec::new(), @@ -230,9 +230,11 @@ impl Scope { R: Responder + 'static, T: FromRequest + 'static, { - Rc::get_mut(&mut self.router) - .unwrap() - .register_route(path, method, f); + Rc::get_mut(&mut self.router).unwrap().register_route( + &insert_slash(path), + method, + f, + ); self } @@ -264,7 +266,7 @@ impl Scope { F: FnOnce(&mut Resource) -> R + 'static, { // add resource - let mut resource = Resource::new(ResourceDef::new(path)); + let mut resource = Resource::new(ResourceDef::new(&insert_slash(path))); f(&mut resource); Rc::get_mut(&mut self.router) @@ -311,19 +313,17 @@ impl Scope { /// } /// ``` pub fn handler>(mut self, path: &str, handler: H) -> Scope { - { - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/') - } - if path.len() > 1 && path.ends_with('/') { - path.pop(); - } - - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); + let mut path = path.trim().trim_right_matches('/').to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/') } + if path.len() > 1 && path.ends_with('/') { + path.pop(); + } + + Rc::get_mut(&mut self.router) + .expect("Multiple copies of scope router") + .register_handler(&path, Box::new(WrapHandler::new(handler)), None); self } @@ -342,6 +342,14 @@ impl Scope { } } +fn insert_slash(path: &str) -> String { + let mut path = path.to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/'); + }; + path +} + impl RouteHandler for Scope { fn handle(&self, req: &HttpRequest) -> AsyncResult { let tail = req.match_info().tail as usize; @@ -844,6 +852,34 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); } + #[test] + fn test_scope_route_without_leading_slash() { + let app = App::new() + .scope("app", |scope| { + scope + .route("path1", Method::GET, |_: HttpRequest<_>| HttpResponse::Ok()) + .route("path1", Method::DELETE, |_: HttpRequest<_>| { + HttpResponse::Ok() + }) + }).finish(); + + let req = TestRequest::with_uri("/app/path1").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + } + #[test] fn test_scope_filter() { let app = App::new() @@ -1013,6 +1049,20 @@ mod tests { assert_eq!(resp.as_msg().status(), StatusCode::CREATED); } + #[test] + fn test_nested_scope_no_slash() { + let app = App::new() + .scope("/app", |scope| { + scope.nested("t1", |scope| { + scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) + }) + }).finish(); + + let req = TestRequest::with_uri("/app/t1/path1").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::CREATED); + } + #[test] fn test_nested_scope_root() { let app = App::new() diff --git a/tests/test_server.rs b/tests/test_server.rs index 8235be6b..97161a30 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -948,8 +948,7 @@ fn test_default_404_handler_response() { tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n") .and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf)) .and_then(|(_, buf)| Ok(buf)) - }) - .map_err(|e| panic!("{:?}", e)); + }).map_err(|e| panic!("{:?}", e)); let response = srv.execute(request).unwrap(); let rep = String::from_utf8_lossy(&response[..]); assert!(rep.contains("HTTP/1.1 404 Not Found")); From b7a73e0a4fd62a53da7fa0ee638f7e019ade390e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 2 Sep 2018 08:51:26 -0700 Subject: [PATCH 0629/1635] fix Scope::handler doc test --- src/scope.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scope.rs b/src/scope.rs index a1fd907a..6e7f2898 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -304,7 +304,7 @@ impl Scope { /// /// fn main() { /// let app = App::new().scope("/scope-prefix", |scope| { - /// handler("/app", |req: &HttpRequest| match *req.method() { + /// scope.handler("/app", |req: &HttpRequest| match *req.method() { /// http::Method::GET => HttpResponse::Ok(), /// http::Method::POST => HttpResponse::MethodNotAllowed(), /// _ => HttpResponse::NotFound(), From 24d12289435db12517741e818a23cb811cc5301b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 3 Sep 2018 11:28:47 -0700 Subject: [PATCH 0630/1635] simplify handler path processing --- src/application.rs | 7 ++----- src/scope.rs | 9 +-------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/application.rs b/src/application.rs index 3ef753f5..40726832 100644 --- a/src/application.rs +++ b/src/application.rs @@ -447,11 +447,8 @@ where { let mut path = path.trim().trim_right_matches('/').to_owned(); if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/') - } - if path.len() > 1 && path.ends_with('/') { - path.pop(); - } + path.insert(0, '/'); + }; self.parts .as_mut() .expect("Use after finish") diff --git a/src/scope.rs b/src/scope.rs index 6e7f2898..4ce4901a 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -313,14 +313,7 @@ impl Scope { /// } /// ``` pub fn handler>(mut self, path: &str, handler: H) -> Scope { - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/') - } - if path.len() > 1 && path.ends_with('/') { - path.pop(); - } - + let path = insert_slash(path.trim().trim_right_matches('/')); Rc::get_mut(&mut self.router) .expect("Multiple copies of scope router") .register_handler(&path, Box::new(WrapHandler::new(handler)), None); From f0f67072aece8f7cacb6be8fcf24d147ecfe1ee7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 3 Sep 2018 21:35:11 -0700 Subject: [PATCH 0631/1635] Read client response until eof if connection header set to close #464 --- CHANGES.md | 4 ++++ src/client/parser.rs | 37 ++++++++++++++++++++++++++++++------- tests/test_client.rs | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dd6cdcd2..b48c743c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,10 @@ * Handling scoped paths without leading slashes #460 +### Changed + +* Read client response until eof if connection header set to close #464 + ## [0.7.4] - 2018-08-23 diff --git a/src/client/parser.rs b/src/client/parser.rs index 0ee4598d..11252fa5 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -20,6 +20,7 @@ const MAX_HEADERS: usize = 96; #[derive(Default)] pub struct HttpResponseParser { decoder: Option, + eof: bool, // indicate that we read payload until stream eof } #[derive(Debug, Fail)] @@ -44,8 +45,14 @@ impl HttpResponseParser { match HttpResponseParser::parse_message(buf) .map_err(HttpResponseParserError::Error)? { - Async::Ready((msg, decoder)) => { - self.decoder = decoder; + Async::Ready((msg, info)) => { + if let Some((decoder, eof)) = info { + self.eof = eof; + self.decoder = Some(decoder); + } else { + self.eof = false; + self.decoder = None; + } return Ok(Async::Ready(msg)); } Async::NotReady => { @@ -97,7 +104,12 @@ impl HttpResponseParser { return Ok(Async::NotReady); } if stream_finished { - return Err(PayloadError::Incomplete); + // read untile eof? + if self.eof { + return Ok(Async::Ready(None)); + } else { + return Err(PayloadError::Incomplete); + } } } Err(err) => return Err(err.into()), @@ -110,7 +122,7 @@ impl HttpResponseParser { fn parse_message( buf: &mut BytesMut, - ) -> Poll<(ClientResponse, Option), ParseError> { + ) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), ParseError> { // Unsafe: we read only this data only after httparse parses headers into. // performance bump for pipeline benchmarks. let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; @@ -156,12 +168,12 @@ impl HttpResponseParser { } let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { - Some(EncodingDecoder::eof()) + Some((EncodingDecoder::eof(), true)) } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { // Content-Length if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { - Some(EncodingDecoder::length(len)) + Some((EncodingDecoder::length(len), false)) } else { debug!("illegal Content-Length: {:?}", len); return Err(ParseError::Header); @@ -172,7 +184,18 @@ impl HttpResponseParser { } } else if chunked(&hdrs)? { // Chunked encoding - Some(EncodingDecoder::chunked()) + Some((EncodingDecoder::chunked(), false)) + } else if let Some(value) = hdrs.get(header::CONNECTION) { + let close = if let Ok(s) = value.to_str() { + s == "close" + } else { + false + }; + if close { + Some((EncodingDecoder::eof(), true)) + } else { + None + } } else { None }; diff --git a/tests/test_client.rs b/tests/test_client.rs index d4a2ce1f..8707114f 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -8,7 +8,8 @@ extern crate rand; #[cfg(all(unix, feature = "uds"))] extern crate tokio_uds; -use std::io::Read; +use std::io::{Read, Write}; +use std::{net, thread}; use bytes::Bytes; use flate2::read::GzDecoder; @@ -470,3 +471,34 @@ fn test_default_headers() { "\"" ))); } + +#[test] +fn client_read_until_eof() { + let addr = test::TestServer::unused_addr(); + + thread::spawn(move || { + let lst = net::TcpListener::bind(addr).unwrap(); + + for stream in lst.incoming() { + let mut stream = stream.unwrap(); + let mut b = [0; 1000]; + let _ = stream.read(&mut b).unwrap(); + let _ = stream + .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); + } + }); + + let mut sys = actix::System::new("test"); + + // client request + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + println!("TEST: {:?}", req); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"welcome!")); +} From 4ca9fd2ad165118d79be478fac0a6bd5750c1cc7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 3 Sep 2018 22:09:12 -0700 Subject: [PATCH 0632/1635] remove debug print --- CHANGES.md | 3 ++- tests/test_client.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b48c743c..954a6c31 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.5] - 2018-09-02 +## [0.7.5] - 2018-09-04 ### Added @@ -15,6 +15,7 @@ * Handling scoped paths without leading slashes #460 + ### Changed * Read client response until eof if connection header set to close #464 diff --git a/tests/test_client.rs b/tests/test_client.rs index 8707114f..28d60faf 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -494,7 +494,6 @@ fn client_read_until_eof() { let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) .finish() .unwrap(); - println!("TEST: {:?}", req); let response = sys.block_on(req.send()).unwrap(); assert!(response.status().is_success()); From 86fdbb47a59f7b963ed2d03720420d22a2732c50 Mon Sep 17 00:00:00 2001 From: Jan Michael Auer Date: Wed, 5 Sep 2018 10:41:23 +0200 Subject: [PATCH 0633/1635] Fix system_exit in HttpServer (#501) --- src/server/http.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/http.rs b/src/server/http.rs index 05f0b244..ed463f75 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -73,7 +73,7 @@ where backlog: 2048, keep_alive: KeepAlive::Os, shutdown_timeout: 30, - exit: true, + exit: false, no_http2: false, no_signals: false, maxconn: 102_400, From 42f3773becb285ef4caff000540914b2f3282f6a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 5 Sep 2018 09:03:58 -0700 Subject: [PATCH 0634/1635] update changes --- CHANGES.md | 7 +++++++ Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 954a6c31..2f236d84 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.7.6] - 2018-09-xx + +### Fixed + +* Fix system_exit in HttpServer #501 + + ## [0.7.5] - 2018-09-04 ### Added diff --git a/Cargo.toml b/Cargo.toml index 631b48dc..704eac47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.5" +version = "0.7.6" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 4251b0bc10cd4f650d7470d297c563e0d02e3678 Mon Sep 17 00:00:00 2001 From: Maciej Piechotka Date: Wed, 5 Sep 2018 16:14:54 +0200 Subject: [PATCH 0635/1635] Refactor resource route parsing to allow repetition in the regexes --- CHANGES.md | 2 +- src/router.rs | 121 ++++++++++++++++++++++++-------------------------- 2 files changed, 58 insertions(+), 65 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2f236d84..3a5a68de 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ ### Fixed * Fix system_exit in HttpServer #501 - +* Fix parsing of route param containin regexes with repetition #500 ## [0.7.5] - 2018-09-04 diff --git a/src/router.rs b/src/router.rs index 6dc6224a..4a0f672c 100644 --- a/src/router.rs +++ b/src/router.rs @@ -815,73 +815,56 @@ impl ResourceDef { Ok(()) } - fn parse( - pattern: &str, for_prefix: bool, - ) -> (String, Vec, bool, usize) { + fn parse_param( + pattern: &str, + ) -> (PatternElement, String, &str) { const DEFAULT_PATTERN: &str = "[^/]+"; - - let mut re1 = String::from("^"); - let mut re2 = String::new(); - 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 is_dynamic = false; - let mut elems = Vec::new(); - let mut len = 0; - - for ch in pattern.chars() { - if in_param { - // In parameter segment: `{....}` - if ch == '}' { - elems.push(PatternElement::Var(param_name.clone())); - re1.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); - - param_name.clear(); - param_pattern = String::from(DEFAULT_PATTERN); - - len = 0; - 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; - is_dynamic = true; - elems.push(PatternElement::Str(el.clone())); - el.clear(); - } else { - re1.push_str(escape(&ch.to_string()).as_str()); - re2.push(ch); - el.push(ch); - len += 1; + let mut params_nesting = 0usize; + let close_idx = pattern.find(|c| match c { + '{' => {params_nesting += 1; false}, + '}' => {params_nesting -= 1; params_nesting == 0}, + _ => false + }).expect("malformed param"); + let (mut param, rem) = pattern.split_at(close_idx + 1); + param = ¶m[1..param.len() - 1]; // Remove outer brackets + let (name, pattern) = match param.find(":") { + Some(idx) => { + let (name, pattern) = param.split_at(idx); + (name, &pattern[1..]) } - } - - if !el.is_empty() { - elems.push(PatternElement::Str(el.clone())); - } - - let re = if is_dynamic { - if !for_prefix { - re1.push('$'); - } - re1 - } else { - re2 + None => (param, DEFAULT_PATTERN) }; - (re, elems, is_dynamic, len) + (PatternElement::Var(name.to_string()), format!(r"(?P<{}>{})", &name, &pattern), rem) + } + + fn parse( + mut pattern: &str, for_prefix: bool, + ) -> (String, Vec, bool, usize) { + if pattern.find("{").is_none() { + return (String::from(pattern), vec![PatternElement::Str(String::from(pattern))], false, pattern.chars().count()) + }; + + let mut elems = Vec::new(); + let mut re = String::from("^"); + + while let Some(idx) = pattern.find("{") { + let (prefix, rem) = pattern.split_at(idx); + elems.push(PatternElement::Str(String::from(prefix))); + re.push_str(&escape(prefix)); + let (param_pattern, re_part, rem) = Self::parse_param(rem); + elems.push(param_pattern); + re.push_str(&re_part); + pattern = rem; + } + + elems.push(PatternElement::Str(String::from(pattern))); + re.push_str(&escape(pattern)); + + if !for_prefix { + re.push_str("$"); + } + + (re, elems, true, pattern.chars().count()) } } @@ -1072,6 +1055,16 @@ mod tests { let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(info.get("version").unwrap(), "151"); assert_eq!(info.get("id").unwrap(), "adahg32"); + + let re = ResourceDef::new("/{id:[[:digit:]]{6}}"); + assert!(re.is_match("/012345")); + assert!(!re.is_match("/012")); + assert!(!re.is_match("/01234567")); + assert!(!re.is_match("/XXXXXX")); + + let req = TestRequest::with_uri("/012345").finish(); + let info = re.match_with_params(&req, 0).unwrap(); + assert_eq!(info.get("id").unwrap(), "012345"); } #[test] From 002bb24b26fbdfa6a664a10a109d763ca2f3f989 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 7 Sep 2018 20:46:43 -0700 Subject: [PATCH 0636/1635] unhide SessionBackend and SessionImpl traits and cleanup warnings --- CHANGES.md | 6 ++++++ Cargo.toml | 1 + src/client/connector.rs | 1 + src/lib.rs | 1 + src/middleware/session.rs | 8 ++++++-- src/router.rs | 36 +++++++++++++++++++++++++----------- src/server/http.rs | 12 ++++++------ 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3a5a68de..e5de591f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,8 +5,14 @@ ### Fixed * Fix system_exit in HttpServer #501 + * Fix parsing of route param containin regexes with repetition #500 +### Changes + +* Unhide `SessionBackend` and `SessionImpl` traits #455 + + ## [0.7.5] - 2018-09-04 ### Added diff --git a/Cargo.toml b/Cargo.toml index 704eac47..6855c0ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" tokio-reactor = "0.1" +tokio-current-thread = "0.1" # native-tls native-tls = { version="0.2", optional = true } diff --git a/src/client/connector.rs b/src/client/connector.rs index 239a00c5..896f98a4 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -204,6 +204,7 @@ impl Paused { /// `ClientConnector` type is responsible for transport layer of a /// client connection. pub struct ClientConnector { + #[allow(dead_code)] connector: SslConnector, stats: ClientConnectorStats, diff --git a/src/lib.rs b/src/lib.rs index f57ab937..2559f646 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,7 @@ extern crate parking_lot; extern crate rand; extern crate slab; extern crate tokio; +extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_reactor; extern crate tokio_tcp; diff --git a/src/middleware/session.rs b/src/middleware/session.rs index 7bf5c0e9..e8b0e555 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -270,14 +270,17 @@ impl> Middleware for SessionStorage { } /// A simple key-value storage interface that is internally used by `Session`. -#[doc(hidden)] pub trait SessionImpl: 'static { + /// Get session value by key fn get(&self, key: &str) -> Option<&str>; + /// Set session value fn set(&mut self, key: &str, value: String); + /// Remove specific key from session fn remove(&mut self, key: &str); + /// Remove all values from session fn clear(&mut self); /// Write session to storage backend. @@ -285,9 +288,10 @@ pub trait SessionImpl: 'static { } /// Session's storage backend trait definition. -#[doc(hidden)] pub trait SessionBackend: Sized + 'static { + /// Session item type Session: SessionImpl; + /// Future that reads session type ReadFuture: Future; /// Parse the session from request and load data from a storage backend. diff --git a/src/router.rs b/src/router.rs index 4a0f672c..ab84838f 100644 --- a/src/router.rs +++ b/src/router.rs @@ -815,16 +815,21 @@ impl ResourceDef { Ok(()) } - fn parse_param( - pattern: &str, - ) -> (PatternElement, String, &str) { + fn parse_param(pattern: &str) -> (PatternElement, String, &str) { const DEFAULT_PATTERN: &str = "[^/]+"; let mut params_nesting = 0usize; - let close_idx = pattern.find(|c| match c { - '{' => {params_nesting += 1; false}, - '}' => {params_nesting -= 1; params_nesting == 0}, - _ => false - }).expect("malformed param"); + let close_idx = pattern + .find(|c| match c { + '{' => { + params_nesting += 1; + false + } + '}' => { + params_nesting -= 1; + params_nesting == 0 + } + _ => false, + }).expect("malformed param"); let (mut param, rem) = pattern.split_at(close_idx + 1); param = ¶m[1..param.len() - 1]; // Remove outer brackets let (name, pattern) = match param.find(":") { @@ -832,16 +837,25 @@ impl ResourceDef { let (name, pattern) = param.split_at(idx); (name, &pattern[1..]) } - None => (param, DEFAULT_PATTERN) + None => (param, DEFAULT_PATTERN), }; - (PatternElement::Var(name.to_string()), format!(r"(?P<{}>{})", &name, &pattern), rem) + ( + PatternElement::Var(name.to_string()), + format!(r"(?P<{}>{})", &name, &pattern), + rem, + ) } fn parse( mut pattern: &str, for_prefix: bool, ) -> (String, Vec, bool, usize) { if pattern.find("{").is_none() { - return (String::from(pattern), vec![PatternElement::Str(String::from(pattern))], false, pattern.chars().count()) + return ( + String::from(pattern), + vec![PatternElement::Str(String::from(pattern))], + false, + pattern.chars().count(), + ); }; let mut elems = Vec::new(); diff --git a/src/server/http.rs b/src/server/http.rs index ed463f75..eafd45a3 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -3,12 +3,12 @@ use std::rc::Rc; use std::sync::Arc; use std::{io, mem, net, time}; -use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System}; +use actix::{Actor, Addr, AsyncContext, Context, Handler, System}; use futures::{Future, Stream}; use net2::{TcpBuilder, TcpStreamExt}; use num_cpus; -use tokio::executor::current_thread; +use tokio_current_thread::spawn; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; @@ -585,7 +585,7 @@ where type Result = (); fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new( + spawn(HttpChannel::new( Rc::clone(&self.settings), msg.io, msg.peer, @@ -693,7 +693,7 @@ where }; let _ = io.set_nodelay(true); - current_thread::spawn(HttpChannel::new(h, io, peer)); + spawn(HttpChannel::new(h, io, peer)); } } @@ -753,10 +753,10 @@ where let _ = io.set_nodelay(true); let rate = h.connection_rate(); - current_thread::spawn(self.acceptor.accept(io).then(move |res| { + spawn(self.acceptor.accept(io).then(move |res| { drop(rate); match res { - Ok(io) => current_thread::spawn(HttpChannel::new(h, io, peer)), + Ok(io) => spawn(HttpChannel::new(h, io, peer)), Err(err) => trace!("Can not establish connection: {}", err), } Ok(()) From cdb57b840e138a60b6b733648008ca877a916e2b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 7 Sep 2018 20:47:54 -0700 Subject: [PATCH 0637/1635] prepare release --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e5de591f..0eb92dad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.6] - 2018-09-xx +## [0.7.6] - 2018-09-07 ### Fixed From 003b05b095e9b6f63b03341df5ba4dcae7554215 Mon Sep 17 00:00:00 2001 From: Maciej Piechotka Date: Tue, 11 Sep 2018 13:57:55 +0200 Subject: [PATCH 0638/1635] Don't ignore errors in std::fmt::Debug implementations (#506) --- src/client/request.rs | 20 ++++++++++---------- src/client/response.rs | 8 ++++---- src/httprequest.rs | 14 +++++++------- src/multipart.rs | 10 +++++----- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index aff4ab48..76fb1be5 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -254,16 +254,16 @@ impl ClientRequest { impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!( + writeln!( f, "\nClientRequest {:?} {}:{}", self.version, self.method, self.uri - ); - let _ = writeln!(f, " headers:"); + )?; + writeln!(f, " headers:")?; for (key, val) in self.headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); + writeln!(f, " {:?}: {:?}", key, val)?; } - res + Ok(()) } } @@ -750,16 +750,16 @@ fn parts<'a>( impl fmt::Debug for ClientRequestBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(ref parts) = self.request { - let res = writeln!( + writeln!( f, "\nClientRequestBuilder {:?} {}:{}", parts.version, parts.method, parts.uri - ); - let _ = writeln!(f, " headers:"); + )?; + writeln!(f, " headers:")?; for (key, val) in parts.headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); + writeln!(f, " {:?}: {:?}", key, val)?; } - res + Ok(()) } else { write!(f, "ClientRequestBuilder(Consumed)") } diff --git a/src/client/response.rs b/src/client/response.rs index 0c094a2a..5f1f4264 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -95,12 +95,12 @@ impl ClientResponse { impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status()); - let _ = writeln!(f, " headers:"); + writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?; + writeln!(f, " headers:")?; for (key, val) in self.headers().iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); + writeln!(f, " {:?}: {:?}", key, val)?; } - res + Ok(()) } } diff --git a/src/httprequest.rs b/src/httprequest.rs index f4de8152..d8c49496 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -354,24 +354,24 @@ impl FromRequest for HttpRequest { impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!( + writeln!( f, "\nHttpRequest {:?} {}:{}", self.version(), self.method(), self.path() - ); + )?; if !self.query_string().is_empty() { - let _ = writeln!(f, " query: ?{:?}", self.query_string()); + writeln!(f, " query: ?{:?}", self.query_string())?; } if !self.match_info().is_empty() { - let _ = writeln!(f, " params: {:?}", self.match_info()); + writeln!(f, " params: {:?}", self.match_info())?; } - let _ = writeln!(f, " headers:"); + writeln!(f, " headers:")?; for (key, val) in self.headers().iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); + writeln!(f, " {:?}: {:?}", key, val)?; } - res + Ok(()) } } diff --git a/src/multipart.rs b/src/multipart.rs index fe809294..862f60ec 100644 --- a/src/multipart.rs +++ b/src/multipart.rs @@ -441,13 +441,13 @@ where impl fmt::Debug for Field { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!(f, "\nMultipartField: {}", self.ct); - let _ = writeln!(f, " boundary: {}", self.inner.borrow().boundary); - let _ = writeln!(f, " headers:"); + writeln!(f, "\nMultipartField: {}", self.ct)?; + writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; + writeln!(f, " headers:")?; for (key, val) in self.headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); + writeln!(f, " {:?}: {:?}", key, val)?; } - res + Ok(()) } } From e0ae6b10cdefe597870496bb81a2d68078d8f92c Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Mon, 10 Sep 2018 01:49:12 +0800 Subject: [PATCH 0639/1635] Fix bug with HttpChannel linked list. --- src/server/channel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index bec1c4c8..79f9da40 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -77,7 +77,7 @@ where type Error = (); fn poll(&mut self) -> Poll { - if self.node.is_some() { + if self.node.is_none() { let el = self as *mut _; self.node = Some(Node::new(el)); let _ = match self.proto { From 70b45659e235c53d81d2eb0814761ed14002e151 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Mon, 10 Sep 2018 01:51:03 +0800 Subject: [PATCH 0640/1635] Make Node's `traverse` method take a closure instead of calling `shutdown` on each HttpChannel. --- src/server/channel.rs | 6 +++--- src/server/http.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index 79f9da40..7b63125e 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -55,7 +55,7 @@ where } } - fn shutdown(&mut self) { + pub(crate) fn shutdown(&mut self) { match self.proto { Some(HttpProtocol::H1(ref mut h1)) => { let io = h1.io(); @@ -232,7 +232,7 @@ impl Node<()> { } } - pub(crate) fn traverse(&self) + pub(crate) fn traverse)>(&self, f: F) where T: IoStream, H: HttpHandler + 'static, @@ -247,7 +247,7 @@ impl Node<()> { if !n.element.is_null() { let ch: &mut HttpChannel = &mut *(&mut *(n.element as *mut _) as *mut () as *mut _); - ch.shutdown(); + f(ch); } } } else { diff --git a/src/server/http.rs b/src/server/http.rs index eafd45a3..f83b74f3 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -637,7 +637,7 @@ where fn shutdown(&self, force: bool) { if force { - self.settings.head().traverse::(); + self.settings.head().traverse(|ch: &mut HttpChannel| ch.shutdown()); } } } From 04608b2ea6bb925a09b979cce473068e5658a327 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Wed, 12 Sep 2018 00:24:10 +0800 Subject: [PATCH 0641/1635] Update changes. --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0eb92dad..ccb2f132 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.7.7] - 2018-09-xx + +### Fixed + +* Fix linked list of HttpChannels #504 + ## [0.7.6] - 2018-09-07 ### Fixed From 70a3f317d35b81089faf7dc0095cb336206e7a98 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 11 Sep 2018 11:24:05 -0700 Subject: [PATCH 0642/1635] fix failing requests to test server #508 --- CHANGES.md | 5 ++++- Cargo.toml | 2 +- src/server/http.rs | 11 +++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ccb2f132..77cac1fe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,14 @@ # Changes -## [0.7.7] - 2018-09-xx +## [0.7.7] - 2018-09-11 ### Fixed * Fix linked list of HttpChannels #504 +* Fix requests to TestServer fail #508 + + ## [0.7.6] - 2018-09-07 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 6855c0ea..12a1ecf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.6" +version = "0.7.7" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/server/http.rs b/src/server/http.rs index f83b74f3..f9b2689e 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -3,12 +3,11 @@ use std::rc::Rc; use std::sync::Arc; use std::{io, mem, net, time}; -use actix::{Actor, Addr, AsyncContext, Context, Handler, System}; +use actix::{Arbiter, Actor, Addr, AsyncContext, Context, Handler, System}; use futures::{Future, Stream}; use net2::{TcpBuilder, TcpStreamExt}; use num_cpus; -use tokio_current_thread::spawn; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; @@ -585,7 +584,7 @@ where type Result = (); fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { - spawn(HttpChannel::new( + Arbiter::spawn(HttpChannel::new( Rc::clone(&self.settings), msg.io, msg.peer, @@ -693,7 +692,7 @@ where }; let _ = io.set_nodelay(true); - spawn(HttpChannel::new(h, io, peer)); + Arbiter::spawn(HttpChannel::new(h, io, peer)); } } @@ -753,10 +752,10 @@ where let _ = io.set_nodelay(true); let rate = h.connection_rate(); - spawn(self.acceptor.accept(io).then(move |res| { + Arbiter::spawn(self.acceptor.accept(io).then(move |res| { drop(rate); match res { - Ok(io) => spawn(HttpChannel::new(h, io, peer)), + Ok(io) => Arbiter::spawn(HttpChannel::new(h, io, peer)), Err(err) => trace!("Can not establish connection: {}", err), } Ok(()) From c3f8b5cf22c7d4b6c903a2a930d1cdd7c155c449 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 11 Sep 2018 11:25:32 -0700 Subject: [PATCH 0643/1635] clippy warnings --- src/router.rs | 6 +++--- src/server/http.rs | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/router.rs b/src/router.rs index ab84838f..aa15e46d 100644 --- a/src/router.rs +++ b/src/router.rs @@ -832,7 +832,7 @@ impl ResourceDef { }).expect("malformed param"); let (mut param, rem) = pattern.split_at(close_idx + 1); param = ¶m[1..param.len() - 1]; // Remove outer brackets - let (name, pattern) = match param.find(":") { + let (name, pattern) = match param.find(':') { Some(idx) => { let (name, pattern) = param.split_at(idx); (name, &pattern[1..]) @@ -849,7 +849,7 @@ impl ResourceDef { fn parse( mut pattern: &str, for_prefix: bool, ) -> (String, Vec, bool, usize) { - if pattern.find("{").is_none() { + if pattern.find('{').is_none() { return ( String::from(pattern), vec![PatternElement::Str(String::from(pattern))], @@ -861,7 +861,7 @@ impl ResourceDef { let mut elems = Vec::new(); let mut re = String::from("^"); - while let Some(idx) = pattern.find("{") { + while let Some(idx) = pattern.find('{') { let (prefix, rem) = pattern.split_at(idx); elems.push(PatternElement::Str(String::from(prefix))); re.push_str(&escape(prefix)); diff --git a/src/server/http.rs b/src/server/http.rs index f9b2689e..948889f4 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use std::sync::Arc; use std::{io, mem, net, time}; -use actix::{Arbiter, Actor, Addr, AsyncContext, Context, Handler, System}; +use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System}; use futures::{Future, Stream}; use net2::{TcpBuilder, TcpStreamExt}; @@ -636,7 +636,9 @@ where fn shutdown(&self, force: bool) { if force { - self.settings.head().traverse(|ch: &mut HttpChannel| ch.shutdown()); + self.settings + .head() + .traverse(|ch: &mut HttpChannel| ch.shutdown()); } } } From d65c72b44d24cb098031284eaedbd8a8e8c50c0b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Sep 2018 09:55:38 -0700 Subject: [PATCH 0644/1635] use server keep-alive timer as slow request timer --- CHANGES.md | 7 +++++++ src/scope.rs | 8 +++----- src/server/accept.rs | 11 +++++++---- src/server/channel.rs | 27 ++++++++++++++++++++++++++- src/server/h1.rs | 12 ++++++------ src/server/h2.rs | 3 ++- src/server/settings.rs | 12 +++++++++++- src/test.rs | 2 ++ tests/test_client.rs | 29 +++++++++++++++++------------ 9 files changed, 81 insertions(+), 30 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 77cac1fe..c764a592 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.7.8] - 2018-09-xx + +### Added + +* Use server `Keep-Alive` setting as slow request timeout. + + ## [0.7.7] - 2018-09-11 ### Fixed diff --git a/src/scope.rs b/src/scope.rs index 4ce4901a..bd3daf16 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -821,11 +821,9 @@ mod tests { scope .route("/path1", Method::GET, |_: HttpRequest<_>| { HttpResponse::Ok() - }).route( - "/path1", - Method::DELETE, - |_: HttpRequest<_>| HttpResponse::Ok(), - ) + }).route("/path1", Method::DELETE, |_: HttpRequest<_>| { + HttpResponse::Ok() + }) }).finish(); let req = TestRequest::with_uri("/app/path1").request(); diff --git a/src/server/accept.rs b/src/server/accept.rs index d642c40f..307a2a2f 100644 --- a/src/server/accept.rs +++ b/src/server/accept.rs @@ -451,10 +451,13 @@ impl Accept { Delay::new( Instant::now() + Duration::from_millis(510), ).map_err(|_| ()) - .and_then(move |_| { - let _ = r.set_readiness(mio::Ready::readable()); - Ok(()) - }), + .and_then( + move |_| { + let _ = + r.set_readiness(mio::Ready::readable()); + Ok(()) + }, + ), ); Ok(()) }, diff --git a/src/server/channel.rs b/src/server/channel.rs index 7b63125e..5119eb5f 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -5,6 +5,7 @@ use std::{io, ptr, time}; use bytes::{Buf, BufMut, BytesMut}; use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::Delay; use super::settings::WorkerSettings; use super::{h1, h2, ConnectionTag, HttpHandler, IoStream}; @@ -30,6 +31,7 @@ where { proto: Option>, node: Option>>, + ka_timeout: Option, _tag: ConnectionTag, } @@ -42,9 +44,11 @@ where settings: Rc>, io: T, peer: Option, ) -> HttpChannel { let _tag = settings.connection(); + let ka_timeout = settings.keep_alive_timer(); HttpChannel { _tag, + ka_timeout, node: None, proto: Some(HttpProtocol::Unknown( settings, @@ -77,6 +81,21 @@ where type Error = (); fn poll(&mut self) -> Poll { + // keep-alive timer + if let Some(ref mut timer) = self.ka_timeout { + match timer.poll() { + Ok(Async::Ready(_)) => { + trace!("Slow request timed out, close connection"); + if let Some(n) = self.node.as_mut() { + n.remove() + }; + return Ok(Async::Ready(())); + } + Ok(Async::NotReady) => (), + Err(_) => panic!("Something is really wrong"), + } + } + if self.node.is_none() { let el = self as *mut _; self.node = Some(Node::new(el)); @@ -161,7 +180,12 @@ where match kind { ProtocolKind::Http1 => { self.proto = Some(HttpProtocol::H1(h1::Http1::new( - settings, io, addr, buf, is_eof, + settings, + io, + addr, + buf, + is_eof, + self.ka_timeout.take(), ))); return self.poll(); } @@ -171,6 +195,7 @@ where io, addr, buf.freeze(), + self.ka_timeout.take(), ))); return self.poll(); } diff --git a/src/server/h1.rs b/src/server/h1.rs index dc88cac9..d6e13e22 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -91,7 +91,7 @@ where { pub fn new( settings: Rc>, stream: T, addr: Option, - buf: BytesMut, is_eof: bool, + buf: BytesMut, is_eof: bool, keepalive_timer: Option, ) -> Self { Http1 { flags: if is_eof { @@ -103,10 +103,10 @@ where decoder: H1Decoder::new(), payload: None, tasks: VecDeque::new(), - keepalive_timer: None, addr, buf, settings, + keepalive_timer, } } @@ -364,7 +364,7 @@ where if self.keepalive_timer.is_none() && keep_alive > 0 { trace!("Start keep-alive timer"); let mut timer = - Delay::new(Instant::now() + Duration::new(keep_alive, 0)); + Delay::new(Instant::now() + Duration::from_secs(keep_alive)); // register timer let _ = timer.poll(); self.keepalive_timer = Some(timer); @@ -632,7 +632,7 @@ mod tests { let readbuf = BytesMut::new(); let settings = Rc::new(wrk_settings()); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false); + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false, None); h1.poll_io(); h1.poll_io(); assert_eq!(h1.tasks.len(), 1); @@ -645,7 +645,7 @@ mod tests { BytesMut::from(Vec::::from(&b"GET /test HTTP/1.1\r\n\r\n"[..])); let settings = Rc::new(wrk_settings()); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true); + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true, None); h1.poll_io(); assert_eq!(h1.tasks.len(), 1); } @@ -656,7 +656,7 @@ mod tests { let readbuf = BytesMut::new(); let settings = Rc::new(wrk_settings()); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false); + let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false, None); h1.poll_io(); h1.poll_io(); assert!(h1.flags.contains(Flags::ERROR)); diff --git a/src/server/h2.rs b/src/server/h2.rs index 986888ff..913e2cd7 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -59,6 +59,7 @@ where { pub fn new( settings: Rc>, io: T, addr: Option, buf: Bytes, + keepalive_timer: Option, ) -> Self { let extensions = io.extensions(); Http2 { @@ -68,10 +69,10 @@ where unread: if buf.is_empty() { None } else { Some(buf) }, inner: io, })), - keepalive_timer: None, addr, settings, extensions, + keepalive_timer, } } diff --git a/src/server/settings.rs b/src/server/settings.rs index e9ca0f85..fc0d931f 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -13,7 +13,7 @@ use http::StatusCode; use lazycell::LazyCell; use parking_lot::Mutex; use time; -use tokio_timer::Interval; +use tokio_timer::{Delay, Interval}; use super::channel::Node; use super::message::{Request, RequestPool}; @@ -197,6 +197,16 @@ impl WorkerSettings { &self.h } + pub fn keep_alive_timer(&self) -> Option { + if self.keep_alive != 0 { + Some(Delay::new( + Instant::now() + Duration::from_secs(self.keep_alive), + )) + } else { + None + } + } + pub fn keep_alive(&self) -> u64 { self.keep_alive } diff --git a/src/test.rs b/src/test.rs index 64aef663..c068086d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -120,6 +120,7 @@ impl TestServer { HttpServer::new(factory) .disable_signals() .listen(tcp) + .keep_alive(5) .start(); tx.send((System::current(), local_addr, TestServer::get_conn())) @@ -328,6 +329,7 @@ impl TestServerBuilder { config(&mut app); vec![app] }).workers(1) + .keep_alive(5) .disable_signals(); tx.send((System::current(), addr, TestServer::get_conn())) diff --git a/tests/test_client.rs b/tests/test_client.rs index 28d60faf..8c5d5819 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -407,24 +407,29 @@ fn test_client_cookie_handling() { let cookie2 = cookie2b.clone(); app.handler(move |req: &HttpRequest| { // Check cookies were sent correctly - req.cookie("cookie1").ok_or_else(err) - .and_then(|c1| if c1.value() == "value1" { + req.cookie("cookie1") + .ok_or_else(err) + .and_then(|c1| { + if c1.value() == "value1" { Ok(()) } else { Err(err()) - }) - .and_then(|()| req.cookie("cookie2").ok_or_else(err)) - .and_then(|c2| if c2.value() == "value2" { + } + }).and_then(|()| req.cookie("cookie2").ok_or_else(err)) + .and_then(|c2| { + if c2.value() == "value2" { Ok(()) } else { Err(err()) - }) - // Send some cookies back - .map(|_| HttpResponse::Ok() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - ) + } + }) + // Send some cookies back + .map(|_| { + HttpResponse::Ok() + .cookie(cookie1.clone()) + .cookie(cookie2.clone()) + .finish() + }) }) }); From 9d1eefc38ff3bd1fa79ad33518c701b8e320f88b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Sep 2018 09:57:54 -0700 Subject: [PATCH 0645/1635] use 5 seconds keep-alive timer by default --- CHANGES.md | 4 ++++ src/server/http.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c764a592..91e34ae3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ * Use server `Keep-Alive` setting as slow request timeout. +### Changed + +* Use 5 seconds keep-alive timer by default. + ## [0.7.7] - 2018-09-11 diff --git a/src/server/http.rs b/src/server/http.rs index 948889f4..b6f577b0 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -70,7 +70,7 @@ where factory: Arc::new(f), host: None, backlog: 2048, - keep_alive: KeepAlive::Os, + keep_alive: KeepAlive::Timeout(5), shutdown_timeout: 30, exit: false, no_http2: false, @@ -131,7 +131,7 @@ where /// Set server keep-alive setting. /// - /// By default keep alive is set to a `Os`. + /// By default keep alive is set to a 5 seconds. pub fn keep_alive>(mut self, val: T) -> Self { self.keep_alive = val.into(); self From bbe69e5b8d914f6cc638db8fdeab6c1edbba3cfe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Sep 2018 10:00:54 -0700 Subject: [PATCH 0646/1635] update version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 12a1ecf9..4a985016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.7" +version = "0.7.8" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 7449884ce3b7bb6a741b481bdb903622e90fb0aa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Sep 2018 17:09:07 -0700 Subject: [PATCH 0647/1635] fix wrong error message for path deserialize for i32 #510 --- CHANGES.md | 4 ++++ src/de.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 91e34ae3..6fdecc24 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,10 @@ * Use 5 seconds keep-alive timer by default. +### Fixed + +* Fixed wrong error message for i16 type #510 + ## [0.7.7] - 2018-09-11 diff --git a/src/de.rs b/src/de.rs index ecb2fa9a..59ab79ba 100644 --- a/src/de.rs +++ b/src/de.rs @@ -175,7 +175,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { parse_single_value!(deserialize_bool, visit_bool, "bool"); parse_single_value!(deserialize_i8, visit_i8, "i8"); parse_single_value!(deserialize_i16, visit_i16, "i16"); - parse_single_value!(deserialize_i32, visit_i32, "i16"); + parse_single_value!(deserialize_i32, visit_i32, "i32"); parse_single_value!(deserialize_i64, visit_i64, "i64"); parse_single_value!(deserialize_u8, visit_u8, "u8"); parse_single_value!(deserialize_u16, visit_u16, "u16"); From 03e318f44649aed6c00de19bdb93cc6b0377b1fd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Sep 2018 17:10:53 -0700 Subject: [PATCH 0648/1635] update changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6fdecc24..03fe9fbc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,7 @@ ### Added -* Use server `Keep-Alive` setting as slow request timeout. +* Use server `Keep-Alive` setting as slow request timeout #439 ### Changed From 599e6b3385e5d433779937d21229935c7d90220e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 17 Sep 2018 05:29:07 -0700 Subject: [PATCH 0649/1635] refactor channel node remove operation --- src/server/channel.rs | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index 5119eb5f..193c8e6e 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -72,6 +72,18 @@ where } } +impl Drop for HttpChannel +where + T: IoStream, + H: HttpHandler + 'static, +{ + fn drop(&mut self) { + if let Some(mut node) = self.node.take() { + node.remove() + } + } +} + impl Future for HttpChannel where T: IoStream, @@ -86,9 +98,6 @@ where match timer.poll() { Ok(Async::Ready(_)) => { trace!("Slow request timed out, close connection"); - if let Some(n) = self.node.as_mut() { - n.remove() - }; return Ok(Async::Ready(())); } Ok(Async::NotReady) => (), @@ -116,28 +125,10 @@ where let mut is_eof = false; let kind = match self.proto { Some(HttpProtocol::H1(ref mut h1)) => { - let result = h1.poll(); - match result { - Ok(Async::Ready(())) | Err(_) => { - if let Some(n) = self.node.as_mut() { - n.remove() - }; - } - _ => (), - } - return result; + return h1.poll(); } Some(HttpProtocol::H2(ref mut h2)) => { - let result = h2.poll(); - match result { - Ok(Async::Ready(())) | Err(_) => { - if let Some(n) = self.node.as_mut() { - n.remove() - }; - } - _ => (), - } - return result; + return h2.poll(); } Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => { let mut disconnect = false; @@ -156,9 +147,6 @@ where } if disconnect { debug!("Ignored premature client disconnection"); - if let Some(n) = self.node.as_mut() { - n.remove() - }; return Err(()); } From bfb2f2e9e1ad254098c712eb7951273b9c997dce Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 17 Sep 2018 10:25:45 -0700 Subject: [PATCH 0650/1635] fix node.remove(), update next node pointer --- src/server/channel.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index 193c8e6e..89fd55b4 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -226,12 +226,15 @@ impl Node { fn remove(&mut self) { unsafe { self.element = ptr::null_mut(); - let next = self.next.take(); + let mut next = self.next.take(); let mut prev = self.prev.take(); if let Some(ref mut prev) = prev { prev.as_mut().unwrap().next = next; } + if let Some(ref mut next) = next { + next.as_mut().unwrap().prev = prev; + } } } } From 764103566d7e8eeb23304702179304ef1a9a1d89 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 17 Sep 2018 10:48:37 -0700 Subject: [PATCH 0651/1635] update changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 03fe9fbc..3a28a82e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.8] - 2018-09-xx +## [0.7.8] - 2018-09-17 ### Added From f40153fca4374d30ee285b857f332664c96a765c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 17 Sep 2018 11:39:03 -0700 Subject: [PATCH 0652/1635] fix node::insert() method, missing next element --- src/server/channel.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index 89fd55b4..1795f8c2 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -208,13 +208,14 @@ impl Node { } } - fn insert(&mut self, next: &mut Node) { + fn insert(&mut self, next_el: &mut Node) { unsafe { - let next: *mut Node = next as *const _ as *mut _; + let next: *mut Node = next_el as *const _ as *mut _; - if let Some(ref mut next2) = self.next { + if let Some(next2) = self.next { let n = next2.as_mut().unwrap(); n.prev = Some(next); + next_el.next = Some(next2 as *mut _); } self.next = Some(next); @@ -226,13 +227,13 @@ impl Node { fn remove(&mut self) { unsafe { self.element = ptr::null_mut(); - let mut next = self.next.take(); - let mut prev = self.prev.take(); + let next = self.next.take(); + let prev = self.prev.take(); - if let Some(ref mut prev) = prev { + if let Some(prev) = prev { prev.as_mut().unwrap().next = next; } - if let Some(ref mut next) = next { + if let Some(next) = next { next.as_mut().unwrap().prev = prev; } } From 0dc96658f24f3e61842e1b5461a8492ef1c90649 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 21 Sep 2018 07:24:10 +0300 Subject: [PATCH 0653/1635] Send response to inform client of error (#515) --- CHANGES.md | 6 ++++++ src/payload.rs | 4 +++- src/server/h1.rs | 24 +++++++++++++++++------- tests/test_server.rs | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3a28a82e..36b6dc76 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.7.9] - 2018-09-x + +### Fixed + +* HTTP1 decoding errors are reported to the client. #512 + ## [0.7.8] - 2018-09-17 ### Added diff --git a/src/payload.rs b/src/payload.rs index 1d9281f5..382c0b0f 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -1,6 +1,8 @@ //! Payload stream use bytes::{Bytes, BytesMut}; -use futures::task::{current as current_task, Task}; +use futures::task::Task; +#[cfg(not(test))] +use futures::task::current as current_task; use futures::{Async, Poll, Stream}; use std::cell::RefCell; use std::cmp; diff --git a/src/server/h1.rs b/src/server/h1.rs index d6e13e22..b715dfb6 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -373,6 +373,16 @@ where Ok(Async::NotReady) } + fn push_response_entry(&mut self, status: StatusCode) { + self.tasks.push_back(Entry { + pipe: EntryPipe::Error(ServerError::err( + Version::HTTP_11, + status, + )), + flags: EntryFlags::empty(), + }); + } + pub fn parse(&mut self) { 'outer: loop { match self.decoder.decode(&mut self.buf, &self.settings) { @@ -439,13 +449,7 @@ where } // handler is not found - self.tasks.push_back(Entry { - pipe: EntryPipe::Error(ServerError::err( - Version::HTTP_11, - StatusCode::NOT_FOUND, - )), - flags: EntryFlags::empty(), - }); + self.push_response_entry(StatusCode::NOT_FOUND); } Ok(Some(Message::Chunk(chunk))) => { if let Some(ref mut payload) = self.payload { @@ -453,6 +457,7 @@ where } else { error!("Internal server error: unexpected payload chunk"); self.flags.insert(Flags::ERROR); + self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); break; } } @@ -462,6 +467,7 @@ where } else { error!("Internal server error: unexpected eof"); self.flags.insert(Flags::ERROR); + self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); break; } } @@ -482,6 +488,9 @@ where }; payload.set_error(e); } + + //Malformed requests should be responded with 400 + self.push_response_entry(StatusCode::BAD_REQUEST); break; } } @@ -660,6 +669,7 @@ mod tests { h1.poll_io(); h1.poll_io(); assert!(h1.flags.contains(Flags::ERROR)); + assert_eq!(h1.tasks.len(), 1); } #[test] diff --git a/tests/test_server.rs b/tests/test_server.rs index 97161a30..52c47dd2 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,6 +11,7 @@ extern crate rand; extern crate tokio; extern crate tokio_reactor; extern crate tokio_tcp; +extern crate tokio_current_thread as current_thread; use std::io::{Read, Write}; use std::sync::Arc; @@ -28,7 +29,6 @@ use h2::client as h2client; use modhttp::Request; use rand::distributions::Alphanumeric; use rand::Rng; -use tokio::executor::current_thread; use tokio::runtime::current_thread::Runtime; use tokio_tcp::TcpStream; From 1b298142e3b954003b419db015796bbcc702adcd Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 21 Sep 2018 08:45:22 +0300 Subject: [PATCH 0654/1635] Correct composing of multiple origins in cors (#518) --- CHANGES.md | 1 + src/middleware/cors.rs | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 36b6dc76..ecdb65ef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### Fixed * HTTP1 decoding errors are reported to the client. #512 +* Correctly compose multiple allowed origins in CORS. #517 ## [0.7.8] - 2018-09-17 diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index e75dc73e..f1adf0c4 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -826,8 +826,8 @@ impl CorsBuilder { if let AllOrSome::Some(ref origins) = cors.origins { let s = origins .iter() - .fold(String::new(), |s, v| s + &v.to_string()); - cors.origins_str = Some(HeaderValue::try_from(s.as_str()).unwrap()); + .fold(String::new(), |s, v| format!("{}, {}", s, v)); + cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap()); } if !self.expose_hdrs.is_empty() { @@ -1122,16 +1122,18 @@ mod tests { let cors = Cors::build() .disable_vary_header() .allowed_origin("https://www.example.com") + .allowed_origin("https://www.google.com") .finish(); let resp: HttpResponse = HttpResponse::Ok().into(); let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"https://www.example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); + + let origins_str = resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().to_str().unwrap(); + + if origins_str.starts_with("https://www.example.com") { + assert_eq!("https://www.example.com, https://www.google.com", origins_str); + } else { + assert_eq!("https://www.google.com, https://www.example.com", origins_str); + } } #[test] From 782eeb5ded9bda6f41ad957315ce1e413873f83c Mon Sep 17 00:00:00 2001 From: Ashley Date: Wed, 26 Sep 2018 20:56:34 +1200 Subject: [PATCH 0655/1635] Reduced unsafe converage (#520) --- src/server/channel.rs | 26 +++++++------ src/server/h1writer.rs | 70 ++++++++++++++++++---------------- src/server/helpers.rs | 86 ++++++++++++++++++++++++------------------ src/server/mod.rs | 36 +++++++++--------- src/uri.rs | 2 +- 5 files changed, 121 insertions(+), 99 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index 1795f8c2..3d753f65 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -209,31 +209,35 @@ impl Node { } fn insert(&mut self, next_el: &mut Node) { - unsafe { - let next: *mut Node = next_el as *const _ as *mut _; + let next: *mut Node = next_el as *const _ as *mut _; - if let Some(next2) = self.next { + if let Some(next2) = self.next { + unsafe { let n = next2.as_mut().unwrap(); n.prev = Some(next); - next_el.next = Some(next2 as *mut _); } - self.next = Some(next); + next_el.next = Some(next2 as *mut _); + } + self.next = Some(next); + unsafe { let next: &mut Node = &mut *next; next.prev = Some(self as *mut _); } } fn remove(&mut self) { - unsafe { - self.element = ptr::null_mut(); - let next = self.next.take(); - let prev = self.prev.take(); + self.element = ptr::null_mut(); + let next = self.next.take(); + let prev = self.prev.take(); - if let Some(prev) = prev { + if let Some(prev) = prev { + unsafe { prev.as_mut().unwrap().next = next; } - if let Some(next) = next { + } + if let Some(next) = next { + unsafe { next.as_mut().unwrap().prev = prev; } } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 422f0ebc..72a68aeb 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -196,45 +196,49 @@ impl Writer for H1Writer { let mut pos = 0; let mut has_date = false; let mut remaining = buffer.remaining_mut(); - unsafe { - let mut buf = &mut *(buffer.bytes_mut() as *mut [u8]); - for (key, value) in msg.headers() { - match *key { - TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - _ => continue, - }, - DATE => { - has_date = true; - } - _ => (), + let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; + for (key, value) in msg.headers() { + match *key { + TRANSFER_ENCODING => continue, + CONTENT_ENCODING => if encoding != ContentEncoding::Identity { + continue; + }, + CONTENT_LENGTH => match info.length { + ResponseLength::None => (), + _ => continue, + }, + DATE => { + has_date = true; } + _ => (), + } - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { buffer.advance_mut(pos); - pos = 0; - buffer.reserve(len); - remaining = buffer.remaining_mut(); + } + pos = 0; + buffer.reserve(len); + remaining = buffer.remaining_mut(); + unsafe { buf = &mut *(buffer.bytes_mut() as *mut _); } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; } + + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; + } + unsafe { buffer.advance_mut(pos); } diff --git a/src/server/helpers.rs b/src/server/helpers.rs index f7e030f2..9c0b7f40 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -29,20 +29,24 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM let lut_ptr = DEC_DIGITS_LUT.as_ptr(); let four = n > 999; + // decode 2 more chars, if > 2 chars + let d1 = (n % 100) << 1; + n /= 100; + curr -= 2; unsafe { - // decode 2 more chars, if > 2 chars - 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 { - curr -= 1; + // decode last 1 or 2 chars + if n < 10 { + curr -= 1; + unsafe { *buf_ptr.offset(curr) = (n as u8) + b'0'; - } else { - let d1 = n << 1; - curr -= 2; + } + } else { + let d1 = n << 1; + curr -= 2; + unsafe { ptr::copy_nonoverlapping( lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), @@ -107,47 +111,55 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) { } pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { - unsafe { - let mut curr: isize = 39; - let mut buf: [u8; 41] = 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(); + let mut curr: isize = 39; + 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(); - // 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; + let d1 = (rem / 100) << 1; + let d2 = (rem % 100) << 1; + curr -= 4; + unsafe { 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 + // 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; + // decode 2 more chars, if > 2 chars + if n >= 100 { + let d1 = (n % 100) << 1; + n /= 100; + curr -= 2; + unsafe { ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); } + } - // decode last 1 or 2 chars - if n < 10 { - curr -= 1; + // decode last 1 or 2 chars + if n < 10 { + curr -= 1; + unsafe { *buf_ptr.offset(curr) = (n as u8) + b'0'; - } else { - let d1 = n << 1; - curr -= 2; + } + } else { + let d1 = n << 1; + curr -= 2; + unsafe { 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), 41 - curr as usize, diff --git a/src/server/mod.rs b/src/server/mod.rs index 009e06cc..96ec570a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -396,27 +396,29 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { if buf.remaining_mut() < LW_BUFFER_SIZE { buf.reserve(HW_BUFFER_SIZE); } - unsafe { - match self.read(buf.bytes_mut()) { - Ok(n) => { - if n == 0 { - return Ok(Async::Ready((read_some, true))); - } else { - read_some = true; + + let read = unsafe { self.read(buf.bytes_mut()) }; + match read { + Ok(n) => { + if n == 0 { + return Ok(Async::Ready((read_some, true))); + } else { + read_some = true; + unsafe { buf.advance_mut(n); } } - Err(e) => { - return if e.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Async::Ready((read_some, false))) - } else { - Ok(Async::NotReady) - } + } + Err(e) => { + return if e.kind() == io::ErrorKind::WouldBlock { + if read_some { + Ok(Async::Ready((read_some, false))) } else { - Err(e) - }; - } + Ok(Async::NotReady) + } + } else { + Err(e) + }; } } } diff --git a/src/uri.rs b/src/uri.rs index 752ddad8..881cf20a 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -148,7 +148,7 @@ impl Quoter { if let Some(data) = cloned { // Unsafe: we get data from http::Uri, which does utf-8 checks already // this code only decodes valid pct encoded values - Some(unsafe { Rc::new(String::from_utf8_unchecked(data)) }) + Some(Rc::new(unsafe { String::from_utf8_unchecked(data) })) } else { None } From 59deb4b40d770d53690e354cde8de071d94d86a8 Mon Sep 17 00:00:00 2001 From: sapir Date: Fri, 28 Sep 2018 04:15:02 +0300 Subject: [PATCH 0656/1635] Try to separate HTTP/1 read & write disconnect handling, to fix #511. (#514) --- src/server/h1.rs | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index b715dfb6..afe143b4 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -136,7 +136,7 @@ where } } - fn notify_disconnect(&mut self) { + fn write_disconnected(&mut self) { self.flags.insert(Flags::WRITE_DISCONNECTED); // notify all tasks @@ -144,17 +144,18 @@ where for task in &mut self.tasks { task.pipe.disconnected(); } - } - fn client_disconnect(&mut self) { - // notify all tasks - self.notify_disconnect(); // kill keepalive self.keepalive_timer.take(); + } - // on parse error, stop reading stream but tasks need to be - // completed - self.flags.insert(Flags::ERROR); + fn read_disconnected(&mut self) { + self.flags.insert( + Flags::READ_DISCONNECTED + // on parse error, stop reading stream but tasks need to be + // completed + | Flags::ERROR, + ); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); @@ -225,16 +226,17 @@ where self.parse(); } if disconnected { + self.read_disconnected(); // delay disconnect until all tasks have finished. - self.flags.insert(Flags::READ_DISCONNECTED); if self.tasks.is_empty() { - self.client_disconnect(); + self.write_disconnected(); } } } Ok(Async::NotReady) => (), Err(_) => { - self.client_disconnect(); + self.read_disconnected(); + self.write_disconnected(); } } } @@ -291,7 +293,8 @@ where Err(err) => { // it is not possible to recover from error // during pipe handling, so just drop connection - self.notify_disconnect(); + self.read_disconnected(); + self.write_disconnected(); self.tasks[idx].flags.insert(EntryFlags::ERROR); error!("Unhandled error1: {}", err); continue; @@ -304,7 +307,8 @@ where self.tasks[idx].flags.insert(EntryFlags::FINISHED) } Err(err) => { - self.notify_disconnect(); + self.read_disconnected(); + self.write_disconnected(); self.tasks[idx].flags.insert(EntryFlags::ERROR); error!("Unhandled error: {}", err); continue; @@ -332,7 +336,8 @@ where Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); - self.notify_disconnect(); + self.read_disconnected(); + self.write_disconnected(); return Err(()); } Ok(Async::Ready(_)) => { @@ -472,10 +477,11 @@ where } } Ok(None) => { - if self.flags.contains(Flags::READ_DISCONNECTED) - && self.tasks.is_empty() - { - self.client_disconnect(); + if self.flags.contains(Flags::READ_DISCONNECTED) { + self.read_disconnected(); + if self.tasks.is_empty() { + self.write_disconnected(); + } } break; } From 52195bbf167618039ef5b39c9e83c06643052e0b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 18:17:58 -0700 Subject: [PATCH 0657/1635] update version --- CHANGES.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ecdb65ef..517f8cbe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,8 +5,12 @@ ### Fixed * HTTP1 decoding errors are reported to the client. #512 + * Correctly compose multiple allowed origins in CORS. #517 +* Websocket server finished() isn't called if client disconnects #511 + + ## [0.7.8] - 2018-09-17 ### Added diff --git a/Cargo.toml b/Cargo.toml index 4a985016..59a48a0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.8" +version = "0.7.9" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 1907102685a7a1b09a4689b304038f69b8f4b7ef Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 7 Sep 2018 23:34:27 -0700 Subject: [PATCH 0658/1635] switch to actix-net server --- Cargo.toml | 3 +- src/lib.rs | 2 + src/server/accept.rs | 475 -------------------------- src/server/channel.rs | 5 +- src/server/http.rs | 743 ++++++++++++++++++----------------------- src/server/mod.rs | 47 +-- src/server/server.rs | 528 ----------------------------- src/server/settings.rs | 18 +- src/server/worker.rs | 139 -------- 9 files changed, 341 insertions(+), 1619 deletions(-) delete mode 100644 src/server/accept.rs delete mode 100644 src/server/server.rs delete mode 100644 src/server/worker.rs diff --git a/Cargo.toml b/Cargo.toml index 59a48a0e..d4ea4fc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ default = ["session", "brotli", "flate2-c"] tls = ["native-tls", "tokio-tls"] # openssl -alpn = ["openssl", "tokio-openssl"] +alpn = ["openssl", "tokio-openssl", "actix-net/ssl"] # rustls rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"] @@ -57,6 +57,7 @@ flate2-rust = ["flate2/rust_backend"] [dependencies] actix = "0.7.0" +actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 2559f646..1dfe143e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,6 +140,8 @@ extern crate serde_urlencoded; extern crate percent_encoding; extern crate serde_json; extern crate smallvec; + +extern crate actix_net; #[macro_use] extern crate actix as actix_inner; diff --git a/src/server/accept.rs b/src/server/accept.rs deleted file mode 100644 index 307a2a2f..00000000 --- a/src/server/accept.rs +++ /dev/null @@ -1,475 +0,0 @@ -use std::sync::mpsc as sync_mpsc; -use std::time::{Duration, Instant}; -use std::{io, net, thread}; - -use futures::{sync::mpsc, Future}; -use mio; -use slab::Slab; -use tokio_timer::Delay; - -use actix::{msgs::Execute, Arbiter, System}; - -use super::server::ServerCommand; -use super::worker::{Conn, WorkerClient}; -use super::Token; - -pub(crate) enum Command { - Pause, - Resume, - Stop, - Worker(WorkerClient), -} - -struct ServerSocketInfo { - addr: net::SocketAddr, - token: Token, - handler: Token, - sock: mio::net::TcpListener, - timeout: Option, -} - -#[derive(Clone)] -pub(crate) struct AcceptNotify(mio::SetReadiness); - -impl AcceptNotify { - pub(crate) fn new(ready: mio::SetReadiness) -> Self { - AcceptNotify(ready) - } - - pub(crate) fn notify(&self) { - let _ = self.0.set_readiness(mio::Ready::readable()); - } -} - -impl Default for AcceptNotify { - fn default() -> Self { - AcceptNotify::new(mio::Registration::new2().1) - } -} - -pub(crate) struct AcceptLoop { - cmd_reg: Option, - cmd_ready: mio::SetReadiness, - notify_reg: Option, - notify_ready: mio::SetReadiness, - tx: sync_mpsc::Sender, - rx: Option>, - srv: Option<( - mpsc::UnboundedSender, - mpsc::UnboundedReceiver, - )>, -} - -impl AcceptLoop { - pub fn new() -> AcceptLoop { - let (tx, rx) = sync_mpsc::channel(); - let (cmd_reg, cmd_ready) = mio::Registration::new2(); - let (notify_reg, notify_ready) = mio::Registration::new2(); - - AcceptLoop { - tx, - cmd_ready, - cmd_reg: Some(cmd_reg), - notify_ready, - notify_reg: Some(notify_reg), - rx: Some(rx), - srv: Some(mpsc::unbounded()), - } - } - - pub fn send(&self, msg: Command) { - let _ = self.tx.send(msg); - let _ = self.cmd_ready.set_readiness(mio::Ready::readable()); - } - - pub fn get_notify(&self) -> AcceptNotify { - AcceptNotify::new(self.notify_ready.clone()) - } - - pub(crate) fn start( - &mut self, socks: Vec>, - workers: Vec, - ) -> mpsc::UnboundedReceiver { - let (tx, rx) = self.srv.take().expect("Can not re-use AcceptInfo"); - - Accept::start( - self.rx.take().expect("Can not re-use AcceptInfo"), - self.cmd_reg.take().expect("Can not re-use AcceptInfo"), - self.notify_reg.take().expect("Can not re-use AcceptInfo"), - socks, - tx, - workers, - ); - rx - } -} - -struct Accept { - poll: mio::Poll, - rx: sync_mpsc::Receiver, - sockets: Slab, - workers: Vec, - srv: mpsc::UnboundedSender, - timer: (mio::Registration, mio::SetReadiness), - next: usize, - backpressure: bool, -} - -const DELTA: usize = 100; -const CMD: mio::Token = mio::Token(0); -const TIMER: mio::Token = mio::Token(1); -const NOTIFY: mio::Token = mio::Token(2); - -/// This function defines errors that are per-connection. Which basically -/// means that if we get this error from `accept()` system call it means -/// next connection might be ready to be accepted. -/// -/// All other errors will incur a timeout before next `accept()` is performed. -/// The timeout is useful to handle resource exhaustion errors like ENFILE -/// and EMFILE. Otherwise, could enter into tight loop. -fn connection_error(e: &io::Error) -> bool { - e.kind() == io::ErrorKind::ConnectionRefused - || e.kind() == io::ErrorKind::ConnectionAborted - || e.kind() == io::ErrorKind::ConnectionReset -} - -impl Accept { - #![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] - pub(crate) fn start( - rx: sync_mpsc::Receiver, cmd_reg: mio::Registration, - notify_reg: mio::Registration, socks: Vec>, - srv: mpsc::UnboundedSender, workers: Vec, - ) { - let sys = System::current(); - - // start accept thread - let _ = thread::Builder::new() - .name("actix-web accept loop".to_owned()) - .spawn(move || { - System::set_current(sys); - let mut accept = Accept::new(rx, socks, workers, srv); - - // Start listening for incoming commands - if let Err(err) = accept.poll.register( - &cmd_reg, - CMD, - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { - panic!("Can not register Registration: {}", err); - } - - // Start listening for notify updates - if let Err(err) = accept.poll.register( - ¬ify_reg, - NOTIFY, - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { - panic!("Can not register Registration: {}", err); - } - - accept.poll(); - }); - } - - fn new( - rx: sync_mpsc::Receiver, socks: Vec>, - workers: Vec, srv: mpsc::UnboundedSender, - ) -> Accept { - // Create a poll instance - let poll = match mio::Poll::new() { - Ok(poll) => poll, - Err(err) => panic!("Can not create mio::Poll: {}", err), - }; - - // Start accept - let mut sockets = Slab::new(); - for (idx, srv_socks) in socks.into_iter().enumerate() { - for (hnd_token, lst) in srv_socks { - let addr = lst.local_addr().unwrap(); - let server = mio::net::TcpListener::from_std(lst) - .expect("Can not create mio::net::TcpListener"); - - let entry = sockets.vacant_entry(); - let token = entry.key(); - - // Start listening for incoming connections - if let Err(err) = poll.register( - &server, - mio::Token(token + DELTA), - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { - panic!("Can not register io: {}", err); - } - - entry.insert(ServerSocketInfo { - addr, - token: hnd_token, - handler: Token(idx), - sock: server, - timeout: None, - }); - } - } - - // Timer - let (tm, tmr) = mio::Registration::new2(); - if let Err(err) = - poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge()) - { - panic!("Can not register Registration: {}", err); - } - - Accept { - poll, - rx, - sockets, - workers, - srv, - next: 0, - timer: (tm, tmr), - backpressure: false, - } - } - - fn poll(&mut self) { - // Create storage for events - let mut events = mio::Events::with_capacity(128); - - loop { - if let Err(err) = self.poll.poll(&mut events, None) { - panic!("Poll error: {}", err); - } - - for event in events.iter() { - let token = event.token(); - match token { - CMD => if !self.process_cmd() { - return; - }, - TIMER => self.process_timer(), - NOTIFY => self.backpressure(false), - _ => { - let token = usize::from(token); - if token < DELTA { - continue; - } - self.accept(token - DELTA); - } - } - } - } - } - - fn process_timer(&mut self) { - let now = Instant::now(); - for (token, info) in self.sockets.iter_mut() { - if let Some(inst) = info.timeout.take() { - if now > inst { - if let Err(err) = self.poll.register( - &info.sock, - mio::Token(token + DELTA), - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { - error!("Can not register server socket {}", err); - } else { - info!("Resume accepting connections on {}", info.addr); - } - } else { - info.timeout = Some(inst); - } - } - } - } - - fn process_cmd(&mut self) -> bool { - loop { - match self.rx.try_recv() { - Ok(cmd) => match cmd { - Command::Pause => { - for (_, info) in self.sockets.iter_mut() { - if let Err(err) = self.poll.deregister(&info.sock) { - error!("Can not deregister server socket {}", err); - } else { - info!("Paused accepting connections on {}", info.addr); - } - } - } - Command::Resume => { - for (token, info) in self.sockets.iter() { - if let Err(err) = self.poll.register( - &info.sock, - mio::Token(token + DELTA), - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { - error!("Can not resume socket accept process: {}", err); - } else { - info!( - "Accepting connections on {} has been resumed", - info.addr - ); - } - } - } - Command::Stop => { - for (_, info) in self.sockets.iter() { - let _ = self.poll.deregister(&info.sock); - } - return false; - } - Command::Worker(worker) => { - self.backpressure(false); - self.workers.push(worker); - } - }, - Err(err) => match err { - sync_mpsc::TryRecvError::Empty => break, - sync_mpsc::TryRecvError::Disconnected => { - for (_, info) in self.sockets.iter() { - let _ = self.poll.deregister(&info.sock); - } - return false; - } - }, - } - } - true - } - - fn backpressure(&mut self, on: bool) { - if self.backpressure { - if !on { - self.backpressure = false; - for (token, info) in self.sockets.iter() { - if let Err(err) = self.poll.register( - &info.sock, - mio::Token(token + DELTA), - mio::Ready::readable(), - mio::PollOpt::edge(), - ) { - error!("Can not resume socket accept process: {}", err); - } else { - info!("Accepting connections on {} has been resumed", info.addr); - } - } - } - } else if on { - self.backpressure = true; - for (_, info) in self.sockets.iter() { - let _ = self.poll.deregister(&info.sock); - } - } - } - - fn accept_one(&mut self, mut msg: Conn) { - if self.backpressure { - while !self.workers.is_empty() { - match self.workers[self.next].send(msg) { - Ok(_) => (), - Err(err) => { - let _ = self.srv.unbounded_send(ServerCommand::WorkerDied( - self.workers[self.next].idx, - )); - msg = err.into_inner(); - self.workers.swap_remove(self.next); - if self.workers.is_empty() { - error!("No workers"); - return; - } else if self.workers.len() <= self.next { - self.next = 0; - } - continue; - } - } - self.next = (self.next + 1) % self.workers.len(); - break; - } - } else { - let mut idx = 0; - while idx < self.workers.len() { - idx += 1; - if self.workers[self.next].available() { - match self.workers[self.next].send(msg) { - Ok(_) => { - self.next = (self.next + 1) % self.workers.len(); - return; - } - Err(err) => { - let _ = self.srv.unbounded_send(ServerCommand::WorkerDied( - self.workers[self.next].idx, - )); - msg = err.into_inner(); - self.workers.swap_remove(self.next); - if self.workers.is_empty() { - error!("No workers"); - self.backpressure(true); - return; - } else if self.workers.len() <= self.next { - self.next = 0; - } - continue; - } - } - } - self.next = (self.next + 1) % self.workers.len(); - } - // enable backpressure - self.backpressure(true); - self.accept_one(msg); - } - } - - fn accept(&mut self, token: usize) { - loop { - let msg = if let Some(info) = self.sockets.get_mut(token) { - match info.sock.accept_std() { - Ok((io, addr)) => Conn { - io, - token: info.token, - handler: info.handler, - peer: Some(addr), - }, - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return, - Err(ref e) if connection_error(e) => continue, - Err(e) => { - error!("Error accepting connection: {}", e); - if let Err(err) = self.poll.deregister(&info.sock) { - error!("Can not deregister server socket {}", err); - } - - // sleep after error - info.timeout = Some(Instant::now() + Duration::from_millis(500)); - - let r = self.timer.1.clone(); - System::current().arbiter().do_send(Execute::new( - move || -> Result<(), ()> { - Arbiter::spawn( - Delay::new( - Instant::now() + Duration::from_millis(510), - ).map_err(|_| ()) - .and_then( - move |_| { - let _ = - r.set_readiness(mio::Ready::readable()); - Ok(()) - }, - ), - ); - Ok(()) - }, - )); - return; - } - } - } else { - return; - }; - - self.accept_one(msg); - } - } -} diff --git a/src/server/channel.rs b/src/server/channel.rs index 3d753f65..d83e9a38 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -8,7 +8,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use super::settings::WorkerSettings; -use super::{h1, h2, ConnectionTag, HttpHandler, IoStream}; +use super::{h1, h2, HttpHandler, IoStream}; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; @@ -32,7 +32,6 @@ where proto: Option>, node: Option>>, ka_timeout: Option, - _tag: ConnectionTag, } impl HttpChannel @@ -43,11 +42,9 @@ where pub(crate) fn new( settings: Rc>, io: T, peer: Option, ) -> HttpChannel { - let _tag = settings.connection(); let ka_timeout = settings.keep_alive_timer(); HttpChannel { - _tag, ka_timeout, node: None, proto: Some(HttpProtocol::Unknown( diff --git a/src/server/http.rs b/src/server/http.rs index b6f577b0..5059b132 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -5,29 +5,31 @@ use std::{io, mem, net, time}; use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System}; -use futures::{Future, Stream}; -use net2::{TcpBuilder, TcpStreamExt}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Poll, Stream}; +use net2::TcpBuilder; use num_cpus; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_tcp::TcpStream; -#[cfg(feature = "tls")] -use native_tls::TlsAcceptor; +use actix_net::{ssl, NewService, Service, Server}; + +//#[cfg(feature = "tls")] +//use native_tls::TlsAcceptor; #[cfg(feature = "alpn")] use openssl::ssl::SslAcceptorBuilder; -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; +//#[cfg(feature = "rust-tls")] +//use rustls::ServerConfig; -use super::channel::{HttpChannel, WrapperStream}; -use super::server::{Connections, Server, Service, ServiceHandler}; +use super::channel::HttpChannel; use super::settings::{ServerSettings, WorkerSettings}; -use super::worker::{Conn, Socket}; -use super::{ - AcceptorService, HttpHandler, IntoAsyncIo, IntoHttpHandler, IoStream, KeepAlive, - Token, -}; +use super::{HttpHandler, IntoHttpHandler, IoStream, KeepAlive}; + +struct Socket { + lst: net::TcpListener, + addr: net::SocketAddr, + handler: Box>, +} /// An HTTP Server /// @@ -49,8 +51,7 @@ where no_signals: bool, maxconn: usize, maxconnrate: usize, - sockets: Vec, - handlers: Vec>>, + sockets: Vec>, } impl HttpServer @@ -75,11 +76,9 @@ where exit: false, no_http2: false, no_signals: false, - maxconn: 102_400, + maxconn: 25_600, maxconnrate: 256, - // settings: None, sockets: Vec::new(), - handlers: Vec::new(), } } @@ -112,7 +111,7 @@ where /// All socket listeners will stop accepting connections when this limit is reached /// for each worker. /// - /// By default max connections is set to a 100k. + /// By default max connections is set to a 25k. pub fn maxconn(mut self, num: usize) -> Self { self.maxconn = num; self @@ -196,9 +195,9 @@ where /// and the user should be presented with an enumeration of which /// socket requires which protocol. pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.handlers + self.sockets .iter() - .map(|s| (s.addr(), s.scheme())) + .map(|s| (s.addr, s.handler.scheme())) .collect() } @@ -207,78 +206,82 @@ where /// HttpServer does not change any configuration for TcpListener, /// it needs to be configured before passing it to listen() method. pub fn listen(mut self, lst: net::TcpListener) -> Self { - let token = Token(self.handlers.len()); let addr = lst.local_addr().unwrap(); - self.handlers - .push(Box::new(SimpleHandler::new(lst.local_addr().unwrap()))); - self.sockets.push(Socket { lst, addr, token }); + self.sockets.push(Socket { + lst, + addr, + handler: Box::new(SimpleHandler { + addr, + factory: self.factory.clone(), + }), + }); self } - #[doc(hidden)] - /// Use listener for accepting incoming connection requests - pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self - where - A: AcceptorService + Send + 'static, - { - let token = Token(self.handlers.len()); - let addr = lst.local_addr().unwrap(); - self.handlers.push(Box::new(StreamHandler::new( - lst.local_addr().unwrap(), - acceptor, - ))); - self.sockets.push(Socket { lst, addr, token }); + // #[doc(hidden)] + // /// Use listener for accepting incoming connection requests + // pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self + // where + // A: AcceptorService + Send + 'static, + // { + // let token = Token(self.handlers.len()); + // let addr = lst.local_addr().unwrap(); + // self.handlers.push(Box::new(StreamHandler::new( + // lst.local_addr().unwrap(), + // acceptor, + // ))); + // self.sockets.push(Socket { lst, addr, token }); - self - } + // self + // } - #[cfg(feature = "tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { - use super::NativeTlsAcceptor; + // #[cfg(feature = "tls")] + // /// Use listener for accepting incoming tls connection requests + // /// + // /// HttpServer does not change any configuration for TcpListener, + // /// it needs to be configured before passing it to listen() method. + // pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { + // use super::NativeTlsAcceptor; + // + // self.listen_with(lst, NativeTlsAcceptor::new(acceptor)) + // } - self.listen_with(lst, NativeTlsAcceptor::new(acceptor)) - } + // #[cfg(feature = "alpn")] + // /// Use listener for accepting incoming tls connection requests + // /// + // /// This method sets alpn protocols to "h2" and "http/1.1" + // pub fn listen_ssl( + // self, lst: net::TcpListener, builder: SslAcceptorBuilder, + // ) -> io::Result { + // use super::{OpensslAcceptor, ServerFlags}; - #[cfg(feature = "alpn")] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_ssl( - self, lst: net::TcpListener, builder: SslAcceptorBuilder, - ) -> io::Result { - use super::{OpensslAcceptor, ServerFlags}; + // alpn support + // let flags = if self.no_http2 { + // ServerFlags::HTTP1 + // } else { + // ServerFlags::HTTP1 | ServerFlags::HTTP2 + // }; - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; + // Ok(self.listen_with(lst, OpensslAcceptor::with_flags(builder, flags)?)) + // } - Ok(self.listen_with(lst, OpensslAcceptor::with_flags(builder, flags)?)) - } + // #[cfg(feature = "rust-tls")] + // /// Use listener for accepting incoming tls connection requests + // /// + // /// This method sets alpn protocols to "h2" and "http/1.1" + // pub fn listen_rustls(self, lst: net::TcpListener, builder: ServerConfig) -> Self { + // use super::{RustlsAcceptor, ServerFlags}; - #[cfg(feature = "rust-tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_rustls(self, lst: net::TcpListener, builder: ServerConfig) -> Self { - use super::{RustlsAcceptor, ServerFlags}; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - self.listen_with(lst, RustlsAcceptor::with_flags(builder, flags)) - } + // // alpn support + // let flags = if self.no_http2 { + // ServerFlags::HTTP1 + // } else { + // ServerFlags::HTTP1 | ServerFlags::HTTP2 + // }; + // + // self.listen_with(lst, RustlsAcceptor::with_flags(builder, flags)) + // } /// The socket address to bind /// @@ -287,38 +290,34 @@ where let sockets = self.bind2(addr)?; for lst in sockets { - let token = Token(self.handlers.len()); - let addr = lst.local_addr().unwrap(); - self.handlers - .push(Box::new(SimpleHandler::new(lst.local_addr().unwrap()))); - self.sockets.push(Socket { lst, addr, token }) + self = self.listen(lst); } Ok(self) } - /// Start listening for incoming connections with supplied acceptor. - #[doc(hidden)] - #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] - pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result - where - S: net::ToSocketAddrs, - A: AcceptorService + Send + 'static, - { - let sockets = self.bind2(addr)?; + // /// Start listening for incoming connections with supplied acceptor. + // #[doc(hidden)] + // #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] + // pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result + // where + // S: net::ToSocketAddrs, + // A: AcceptorService + Send + 'static, + // { + // let sockets = self.bind2(addr)?; - for lst in sockets { - let token = Token(self.handlers.len()); - let addr = lst.local_addr().unwrap(); - self.handlers.push(Box::new(StreamHandler::new( - lst.local_addr().unwrap(), - acceptor.clone(), - ))); - self.sockets.push(Socket { lst, addr, token }) - } + // for lst in sockets { + // let token = Token(self.handlers.len()); + // let addr = lst.local_addr().unwrap(); + // self.handlers.push(Box::new(StreamHandler::new( + // lst.local_addr().unwrap(), + // acceptor.clone(), + // ))); + // self.sockets.push(Socket { lst, addr, token }) + // } - Ok(self) - } + // Ok(self) + // } fn bind2( &self, addr: S, @@ -350,112 +349,109 @@ where } } - #[cfg(feature = "tls")] - /// The ssl socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind_tls( - self, addr: S, acceptor: TlsAcceptor, - ) -> io::Result { - use super::NativeTlsAcceptor; + // #[cfg(feature = "tls")] + // /// The ssl socket address to bind + // /// + // /// To bind multiple addresses this method can be called multiple times. + // pub fn bind_tls( + // self, addr: S, acceptor: TlsAcceptor, + // ) -> io::Result { + // use super::NativeTlsAcceptor; - self.bind_with(addr, NativeTlsAcceptor::new(acceptor)) - } + // self.bind_with(addr, NativeTlsAcceptor::new(acceptor)) + // } - #[cfg(feature = "alpn")] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_ssl(self, addr: S, builder: SslAcceptorBuilder) -> io::Result - where - S: net::ToSocketAddrs, - { - use super::{OpensslAcceptor, ServerFlags}; + // #[cfg(feature = "alpn")] + // /// Start listening for incoming tls connections. + // /// + // /// This method sets alpn protocols to "h2" and "http/1.1" + // pub fn bind_ssl(self, addr: S, builder: SslAcceptorBuilder) -> io::Result + // where + // S: net::ToSocketAddrs, + // { + // use super::{OpensslAcceptor, ServerFlags}; - // alpn support - let flags = if !self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; + // // alpn support + // let flags = if !self.no_http2 { + // ServerFlags::HTTP1 + // } else { + // ServerFlags::HTTP1 | ServerFlags::HTTP2 + // }; - self.bind_with(addr, OpensslAcceptor::with_flags(builder, flags)?) - } + // self.bind_with(addr, OpensslAcceptor::with_flags(builder, flags)?) + // } - #[cfg(feature = "rust-tls")] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_rustls( - self, addr: S, builder: ServerConfig, - ) -> io::Result { - use super::{RustlsAcceptor, ServerFlags}; + // #[cfg(feature = "rust-tls")] + // /// Start listening for incoming tls connections. + // /// + // /// This method sets alpn protocols to "h2" and "http/1.1" + // pub fn bind_rustls( + // self, addr: S, builder: ServerConfig, + // ) -> io::Result { + // use super::{RustlsAcceptor, ServerFlags}; - // alpn support - let flags = if !self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; + // // alpn support + // let flags = if !self.no_http2 { + // ServerFlags::HTTP1 + // } else { + // ServerFlags::HTTP1 | ServerFlags::HTTP2 + // }; - self.bind_with(addr, RustlsAcceptor::with_flags(builder, flags)) - } + // self.bind_with(addr, RustlsAcceptor::with_flags(builder, flags)) + // } } -impl Into<(Box, Vec<(Token, net::TcpListener)>)> - for HttpServer +struct HttpService +where + H: HttpHandler, + F: IntoHttpHandler, + Io: IoStream, { - fn into(mut self) -> (Box, Vec<(Token, net::TcpListener)>) { - let sockets: Vec<_> = mem::replace(&mut self.sockets, Vec::new()) - .into_iter() - .map(|item| (item.token, item.lst)) - .collect(); - - ( - Box::new(HttpService { - factory: self.factory, - host: self.host, - keep_alive: self.keep_alive, - handlers: self.handlers, - }), - sockets, - ) - } -} - -struct HttpService { - factory: Arc Vec + Send + Sync>, + factory: Arc Vec + Send + Sync>, + addr: net::SocketAddr, host: Option, keep_alive: KeepAlive, - handlers: Vec>>, + _t: PhantomData<(H, Io)>, } -impl Service for HttpService { - fn clone(&self) -> Box { - Box::new(HttpService { - factory: self.factory.clone(), - host: self.host.clone(), - keep_alive: self.keep_alive, - handlers: self.handlers.iter().map(|v| v.clone()).collect(), - }) - } +impl NewService for HttpService +where + H: HttpHandler, + F: IntoHttpHandler, + Io: IoStream, +{ + type Request = Io; + type Response = (); + type Error = (); + type InitError = (); + type Service = HttpServiceHandler; + type Future = FutureResult; - fn create(&self, conns: Connections) -> Box { - let addr = self.handlers[0].addr(); - let s = ServerSettings::new(Some(addr), &self.host, false); + fn new_service(&self) -> Self::Future { + let s = ServerSettings::new(Some(self.addr), &self.host, false); let apps: Vec<_> = (*self.factory)() .into_iter() .map(|h| h.into_handler()) .collect(); - let handlers = self.handlers.iter().map(|h| h.clone()).collect(); - Box::new(HttpServiceHandler::new( - apps, - handlers, - self.keep_alive, - s, - conns, - )) + ok(HttpServiceHandler::new(apps, self.keep_alive, s)) + } +} + +impl Clone for HttpService +where + H: HttpHandler, + F: IntoHttpHandler, + Io: IoStream, +{ + fn clone(&self) -> HttpService { + HttpService { + addr: self.addr, + factory: self.factory.clone(), + host: self.host.clone(), + keep_alive: self.keep_alive, + _t: PhantomData, + } } } @@ -485,11 +481,12 @@ impl HttpServer { /// sys.run(); // <- Run actix system, this method starts all async processes /// } /// ``` - pub fn start(self) -> Addr { + pub fn start(mut self) -> Addr { + ssl::max_concurrent_ssl_connect(self.maxconnrate); + let mut srv = Server::new() .workers(self.threads) .maxconn(self.maxconn) - .maxconnrate(self.maxconnrate) .shutdown_timeout(self.shutdown_timeout); srv = if self.exit { srv.system_exit() } else { srv }; @@ -499,7 +496,17 @@ impl HttpServer { srv }; - srv.service(self).start() + let sockets = mem::replace(&mut self.sockets, Vec::new()); + + for socket in sockets { + let Socket { + lst, + addr: _, + handler, + } = socket; + srv = handler.register(srv, lst, self.host.clone(), self.keep_alive); + } + srv.start() } /// Spawn new thread and start listening for incoming connections. @@ -529,277 +536,187 @@ impl HttpServer { } } -impl HttpServer { - /// Start listening for incoming connections from a stream. - /// - /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(self, stream: S, secure: bool) - where - S: Stream + Send + 'static, - T: AsyncRead + AsyncWrite + Send + 'static, - { - // set server settings - let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let srv_settings = ServerSettings::new(Some(addr), &self.host, secure); - let apps: Vec<_> = (*self.factory)() - .into_iter() - .map(|h| h.into_handler()) - .collect(); - let settings = WorkerSettings::create( - apps, - self.keep_alive, - srv_settings, - Connections::default(), - ); +// impl HttpServer { +// /// Start listening for incoming connections from a stream. +// /// +// /// This method uses only one thread for handling incoming connections. +// pub fn start_incoming(self, stream: S, secure: bool) +// where +// S: Stream + Send + 'static, +// T: AsyncRead + AsyncWrite + Send + 'static, +// { +// // set server settings +// let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); +// let srv_settings = ServerSettings::new(Some(addr), &self.host, secure); +// let apps: Vec<_> = (*self.factory)() +// .into_iter() +// .map(|h| h.into_handler()) +// .collect(); +// let settings = WorkerSettings::create( +// apps, +// self.keep_alive, +// srv_settings, +// ); - // start server - HttpIncoming::create(move |ctx| { - ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn { - io: WrapperStream::new(t), - handler: Token::new(0), - token: Token::new(0), - peer: None, - })); - HttpIncoming { settings } - }); - } -} +// // start server +// HttpIncoming::create(move |ctx| { +// ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn { +// io: WrapperStream::new(t), +// handler: Token::new(0), +// token: Token::new(0), +// peer: None, +// })); +// HttpIncoming { settings } +// }); +// } +// } -struct HttpIncoming { - settings: Rc>, -} +// struct HttpIncoming { +// settings: Rc>, +// } -impl Actor for HttpIncoming +// impl Actor for HttpIncoming +// where +// H: HttpHandler, +// { +// type Context = Context; +// } + +// impl Handler> for HttpIncoming +// where +// T: IoStream, +// H: HttpHandler, +// { +// type Result = (); + +// fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { +// spawn(HttpChannel::new( +// Rc::clone(&self.settings), +// msg.io, +// msg.peer, +// )); +// } +// } + +struct HttpServiceHandler where H: HttpHandler, -{ - type Context = Context; -} - -impl Handler> for HttpIncoming -where - T: IoStream, - H: HttpHandler, -{ - type Result = (); - - fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new( - Rc::clone(&self.settings), - msg.io, - msg.peer, - )); - } -} - -struct HttpServiceHandler -where - H: HttpHandler + 'static, + Io: IoStream, { settings: Rc>, - handlers: Vec>>, tcp_ka: Option, + _t: PhantomData, } -impl HttpServiceHandler { +impl HttpServiceHandler +where + H: HttpHandler, + Io: IoStream, +{ fn new( - apps: Vec, handlers: Vec>>, - keep_alive: KeepAlive, settings: ServerSettings, conns: Connections, - ) -> HttpServiceHandler { + apps: Vec, keep_alive: KeepAlive, settings: ServerSettings, + ) -> HttpServiceHandler { let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { Some(time::Duration::new(val as u64, 0)) } else { None }; - let settings = WorkerSettings::create(apps, keep_alive, settings, conns); + let settings = WorkerSettings::create(apps, keep_alive, settings); HttpServiceHandler { - handlers, tcp_ka, settings, + _t: PhantomData, } } } -impl ServiceHandler for HttpServiceHandler +impl Service for HttpServiceHandler where - H: HttpHandler + 'static, + H: HttpHandler, + Io: IoStream, { - fn handle( - &mut self, token: Token, io: net::TcpStream, peer: Option, - ) { - if self.tcp_ka.is_some() && io.set_keepalive(self.tcp_ka).is_err() { - error!("Can not set socket keep-alive option"); - } - self.handlers[token.0].handle(Rc::clone(&self.settings), io, peer); + type Request = Io; + type Response = (); + type Error = (); + type Future = HttpChannel; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) } - fn shutdown(&self, force: bool) { - if force { - self.settings - .head() - .traverse(|ch: &mut HttpChannel| ch.shutdown()); - } + fn call(&mut self, mut req: Self::Request) -> Self::Future { + let _ = req.set_nodelay(true); + HttpChannel::new(Rc::clone(&self.settings), req, None) } + + // fn shutdown(&self, force: bool) { + // if force { + // self.settings.head().traverse::(); + // } + // } } -struct SimpleHandler { - addr: net::SocketAddr, - io: PhantomData, +trait IoStreamHandler: Send +where + H: IntoHttpHandler, +{ + fn addr(&self) -> net::SocketAddr; + + fn scheme(&self) -> &'static str; + + fn register( + &self, server: Server, lst: net::TcpListener, host: Option, + keep_alive: KeepAlive, + ) -> Server; } -impl Clone for SimpleHandler { +struct SimpleHandler +where + H: IntoHttpHandler, +{ + pub addr: net::SocketAddr, + pub factory: Arc Vec + Send + Sync>, +} + +impl Clone for SimpleHandler { fn clone(&self) -> Self { SimpleHandler { addr: self.addr, - io: PhantomData, + factory: self.factory.clone(), } } } -impl SimpleHandler { - fn new(addr: net::SocketAddr) -> Self { - SimpleHandler { - addr, - io: PhantomData, - } - } -} - -impl IoStreamHandler for SimpleHandler +impl IoStreamHandler for SimpleHandler where - H: HttpHandler, - Io: IntoAsyncIo + Send + 'static, - Io::Io: IoStream, + H: IntoHttpHandler + 'static, { fn addr(&self) -> net::SocketAddr { self.addr } - fn clone(&self) -> Box> { - Box::new(Clone::clone(self)) - } - fn scheme(&self) -> &'static str { "http" } - fn handle(&self, h: Rc>, io: Io, peer: Option) { - let mut io = match io.into_async_io() { - Ok(io) => io, - Err(err) => { - trace!("Failed to create async io: {}", err); - return; - } - }; - let _ = io.set_nodelay(true); + fn register( + &self, server: Server, lst: net::TcpListener, host: Option, + keep_alive: KeepAlive, + ) -> Server { + let addr = self.addr; + let factory = self.factory.clone(); - Arbiter::spawn(HttpChannel::new(h, io, peer)); - } -} - -struct StreamHandler { - acceptor: A, - addr: net::SocketAddr, - io: PhantomData, -} - -impl> StreamHandler { - fn new(addr: net::SocketAddr, acceptor: A) -> Self { - StreamHandler { + server.listen(lst, move || HttpService { + keep_alive, addr, - acceptor, - io: PhantomData, - } + host: host.clone(), + factory: factory.clone(), + _t: PhantomData, + }) } } -impl> Clone for StreamHandler { - fn clone(&self) -> Self { - StreamHandler { - addr: self.addr, - acceptor: self.acceptor.clone(), - io: PhantomData, - } - } -} - -impl IoStreamHandler for StreamHandler -where - H: HttpHandler, - Io: IntoAsyncIo + Send + 'static, - Io::Io: IoStream, - A: AcceptorService + Send + 'static, -{ - fn addr(&self) -> net::SocketAddr { - self.addr - } - - fn clone(&self) -> Box> { - Box::new(Clone::clone(self)) - } - - fn scheme(&self) -> &'static str { - self.acceptor.scheme() - } - - fn handle(&self, h: Rc>, io: Io, peer: Option) { - let mut io = match io.into_async_io() { - Ok(io) => io, - Err(err) => { - trace!("Failed to create async io: {}", err); - return; - } - }; - let _ = io.set_nodelay(true); - - let rate = h.connection_rate(); - Arbiter::spawn(self.acceptor.accept(io).then(move |res| { - drop(rate); - match res { - Ok(io) => Arbiter::spawn(HttpChannel::new(h, io, peer)), - Err(err) => trace!("Can not establish connection: {}", err), - } - Ok(()) - })) - } -} - -impl IoStreamHandler for Box> -where - H: HttpHandler, - Io: IntoAsyncIo, -{ - fn addr(&self) -> net::SocketAddr { - self.as_ref().addr() - } - - fn clone(&self) -> Box> { - self.as_ref().clone() - } - - fn scheme(&self) -> &'static str { - self.as_ref().scheme() - } - - fn handle(&self, h: Rc>, io: Io, peer: Option) { - self.as_ref().handle(h, io, peer) - } -} - -trait IoStreamHandler: Send -where - H: HttpHandler, -{ - fn clone(&self) -> Box>; - - fn addr(&self) -> net::SocketAddr; - - fn scheme(&self) -> &'static str; - - fn handle(&self, h: Rc>, io: Io, peer: Option); -} - fn create_tcp_listener( addr: net::SocketAddr, backlog: i32, ) -> io::Result { diff --git a/src/server/mod.rs b/src/server/mod.rs index 96ec570a..25eca3a7 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -108,15 +108,13 @@ //! ``` use std::net::Shutdown; use std::rc::Rc; -use std::{io, net, time}; +use std::{io, time}; use bytes::{BufMut, BytesMut}; -use futures::{Async, Future, Poll}; +use futures::{Async, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_reactor::Handle; use tokio_tcp::TcpStream; -pub(crate) mod accept; mod channel; mod error; pub(crate) mod h1; @@ -129,25 +127,15 @@ mod http; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; -mod server; pub(crate) mod settings; mod ssl; -mod worker; use actix::Message; -pub use self::message::Request; - pub use self::http::HttpServer; -#[doc(hidden)] -pub use self::server::{ - ConnectionRateTag, ConnectionTag, Connections, Server, Service, ServiceHandler, -}; +pub use self::message::Request; pub use self::settings::ServerSettings; -#[doc(hidden)] -pub use self::ssl::*; - #[doc(hidden)] pub use self::helpers::write_content_length; @@ -322,35 +310,6 @@ impl IntoHttpHandler for T { } } -pub(crate) trait IntoAsyncIo { - type Io: AsyncRead + AsyncWrite; - - fn into_async_io(self) -> Result; -} - -impl IntoAsyncIo for net::TcpStream { - type Io = TcpStream; - - fn into_async_io(self) -> Result { - TcpStream::from_std(self, &Handle::default()) - } -} - -#[doc(hidden)] -/// Trait implemented by types that could accept incomming socket connections. -pub trait AcceptorService: Clone { - /// Established connection type - type Accepted: IoStream; - /// Future describes async accept process. - type Future: Future + 'static; - - /// Establish new connection - fn accept(&self, io: Io) -> Self::Future; - - /// Scheme - fn scheme(&self) -> &'static str; -} - #[doc(hidden)] #[derive(Debug)] pub enum WriterState { diff --git a/src/server/server.rs b/src/server/server.rs deleted file mode 100644 index 122571fd..00000000 --- a/src/server/server.rs +++ /dev/null @@ -1,528 +0,0 @@ -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, -}; -use std::time::Duration; -use std::{mem, net}; - -use futures::sync::{mpsc, mpsc::unbounded}; -use futures::{Future, Sink, Stream}; -use num_cpus; - -use actix::{ - fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler, - Response, StreamHandler, System, WrapFuture, -}; - -use super::accept::{AcceptLoop, AcceptNotify, Command}; -use super::worker::{Conn, StopWorker, Worker, WorkerClient}; -use super::{PauseServer, ResumeServer, StopServer, Token}; - -#[doc(hidden)] -/// Describes service that could be used -/// with [Server](struct.Server.html) -pub trait Service: Send + 'static { - /// Clone service - fn clone(&self) -> Box; - - /// Create service handler for this service - fn create(&self, conn: Connections) -> Box; -} - -impl Service for Box { - fn clone(&self) -> Box { - self.as_ref().clone() - } - - fn create(&self, conn: Connections) -> Box { - self.as_ref().create(conn) - } -} - -#[doc(hidden)] -/// Describes the way serivce handles incoming -/// TCP connections. -pub trait ServiceHandler { - /// Handle incoming stream - fn handle( - &mut self, token: Token, io: net::TcpStream, peer: Option, - ); - - /// Shutdown open handlers - fn shutdown(&self, _: bool) {} -} - -pub(crate) enum ServerCommand { - WorkerDied(usize), -} - -/// Generic server -#[doc(hidden)] -pub struct Server { - threads: usize, - workers: Vec<(usize, Addr)>, - services: Vec>, - sockets: Vec>, - accept: AcceptLoop, - exit: bool, - shutdown_timeout: u16, - signals: Option>, - no_signals: bool, - maxconn: usize, - maxconnrate: usize, -} - -impl Default for Server { - fn default() -> Self { - Self::new() - } -} - -impl Server { - /// Create new Server instance - pub fn new() -> Server { - Server { - threads: num_cpus::get(), - workers: Vec::new(), - services: Vec::new(), - sockets: Vec::new(), - accept: AcceptLoop::new(), - exit: false, - shutdown_timeout: 30, - signals: None, - no_signals: false, - maxconn: 102_400, - maxconnrate: 256, - } - } - - /// Set number of workers to start. - /// - /// By default http server uses number of available logical cpu as threads - /// count. - pub fn workers(mut self, num: usize) -> Self { - self.threads = num; - self - } - - /// Sets the maximum per-worker number of concurrent connections. - /// - /// All socket listeners will stop accepting connections when this limit is reached - /// for each worker. - /// - /// By default max connections is set to a 100k. - pub fn maxconn(mut self, num: usize) -> Self { - self.maxconn = num; - self - } - - /// Sets the maximum per-worker concurrent connection establish process. - /// - /// All listeners will stop accepting connections when this limit is reached. It - /// can be used to limit the global SSL CPU usage. - /// - /// By default max connections is set to a 256. - pub fn maxconnrate(mut self, num: usize) -> Self { - self.maxconnrate = num; - self - } - - /// Stop actix system. - /// - /// `SystemExit` message stops currently running system. - pub fn system_exit(mut self) -> Self { - self.exit = true; - self - } - - #[doc(hidden)] - /// Set alternative address for `ProcessSignals` actor. - pub fn signals(mut self, addr: Addr) -> 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. 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 - } - - /// Add new service to server - pub fn service(mut self, srv: T) -> Self - where - T: Into<(Box, Vec<(Token, net::TcpListener)>)>, - { - let (srv, sockets) = srv.into(); - self.services.push(srv); - self.sockets.push(sockets); - self - } - - /// Spawn new thread and start listening for incoming connections. - /// - /// This method spawns new thread and starts new actix system. Other than - /// that it is similar to `start()` method. This method blocks. - /// - /// This methods panics if no socket addresses get bound. - /// - /// ```rust,ignore - /// # extern crate futures; - /// # extern crate actix_web; - /// # use futures::Future; - /// use actix_web::*; - /// - /// fn main() { - /// Server::new(). - /// .service( - /// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0")) - /// .run(); - /// } - /// ``` - pub fn run(self) { - let sys = System::new("http-server"); - self.start(); - sys.run(); - } - - /// Starts Server Actor and returns its address - pub fn start(mut self) -> Addr { - if self.sockets.is_empty() { - panic!("Service should have at least one bound socket"); - } else { - info!("Starting {} http workers", self.threads); - - // start workers - let mut workers = Vec::new(); - for idx in 0..self.threads { - let (addr, worker) = self.start_worker(idx, self.accept.get_notify()); - workers.push(worker); - self.workers.push((idx, addr)); - } - - // start accept thread - for sock in &self.sockets { - for s in sock.iter() { - info!("Starting server on http://{}", s.1.local_addr().unwrap()); - } - } - let rx = self - .accept - .start(mem::replace(&mut self.sockets, Vec::new()), workers); - - // start http server actor - let signals = self.subscribe_to_signals(); - let addr = Actor::create(move |ctx| { - ctx.add_stream(rx); - self - }); - if let Some(signals) = signals { - signals.do_send(signal::Subscribe(addr.clone().recipient())) - } - addr - } - } - - // subscribe to os signals - fn subscribe_to_signals(&self) -> Option> { - if !self.no_signals { - if let Some(ref signals) = self.signals { - Some(signals.clone()) - } else { - Some(System::current().registry().get::()) - } - } else { - None - } - } - - fn start_worker( - &self, idx: usize, notify: AcceptNotify, - ) -> (Addr, WorkerClient) { - let (tx, rx) = unbounded::>(); - let conns = Connections::new(notify, self.maxconn, self.maxconnrate); - let worker = WorkerClient::new(idx, tx, conns.clone()); - let services: Vec<_> = self.services.iter().map(|v| v.clone()).collect(); - - let addr = Arbiter::start(move |ctx: &mut Context<_>| { - ctx.add_message_stream(rx); - let handlers: Vec<_> = services - .into_iter() - .map(|s| s.create(conns.clone())) - .collect(); - Worker::new(conns, handlers) - }); - - (addr, worker) - } -} - -impl Actor for Server { - type Context = Context; -} - -/// Signals support -/// Handle `SIGINT`, `SIGTERM`, `SIGQUIT` signals and stop actix system -/// message to `System` actor. -impl Handler for Server { - type Result = (); - - fn handle(&mut self, msg: signal::Signal, ctx: &mut Context) { - 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); - } - _ => (), - } - } -} - -impl Handler for Server { - type Result = (); - - fn handle(&mut self, _: PauseServer, _: &mut Context) { - self.accept.send(Command::Pause); - } -} - -impl Handler for Server { - type Result = (); - - fn handle(&mut self, _: ResumeServer, _: &mut Context) { - self.accept.send(Command::Resume); - } -} - -impl Handler for Server { - type Result = Response<(), ()>; - - fn handle(&mut self, msg: StopServer, ctx: &mut Context) -> Self::Result { - // stop accept thread - self.accept.send(Command::Stop); - - // stop workers - 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 { - let tx2 = tx.clone(); - ctx.spawn( - worker - .1 - .send(StopWorker { graceful: dur }) - .into_actor(self) - .then(move |_, slf, ctx| { - slf.workers.pop(); - if slf.workers.is_empty() { - let _ = tx2.send(()); - - // we need to stop system if server was spawned - if slf.exit { - ctx.run_later(Duration::from_millis(300), |_, _| { - System::current().stop(); - }); - } - } - - fut::ok(()) - }), - ); - } - - if !self.workers.is_empty() { - Response::async(rx.into_future().map(|_| ()).map_err(|_| ())) - } else { - // we need to stop system if server was spawned - if self.exit { - ctx.run_later(Duration::from_millis(300), |_, _| { - System::current().stop(); - }); - } - Response::reply(Ok(())) - } - } -} - -/// Commands from accept threads -impl StreamHandler for Server { - fn finished(&mut self, _: &mut Context) {} - - fn handle(&mut self, msg: ServerCommand, _: &mut Context) { - match msg { - ServerCommand::WorkerDied(idx) => { - let mut found = false; - for i in 0..self.workers.len() { - if self.workers[i].0 == idx { - self.workers.swap_remove(i); - found = true; - break; - } - } - - if found { - error!("Worker has died {:?}, restarting", idx); - - let mut new_idx = self.workers.len(); - 'found: loop { - for i in 0..self.workers.len() { - if self.workers[i].0 == new_idx { - new_idx += 1; - continue 'found; - } - } - break; - } - - let (addr, worker) = - self.start_worker(new_idx, self.accept.get_notify()); - self.workers.push((new_idx, addr)); - self.accept.send(Command::Worker(worker)); - } - } - } - } -} - -#[derive(Clone, Default)] -///Contains information about connection. -pub struct Connections(Arc); - -impl Connections { - fn new(notify: AcceptNotify, maxconn: usize, maxconnrate: usize) -> Self { - let maxconn_low = if maxconn > 10 { maxconn - 10 } else { 0 }; - let maxconnrate_low = if maxconnrate > 10 { - maxconnrate - 10 - } else { - 0 - }; - - Connections(Arc::new(ConnectionsInner { - notify, - maxconn, - maxconnrate, - maxconn_low, - maxconnrate_low, - conn: AtomicUsize::new(0), - connrate: AtomicUsize::new(0), - })) - } - - pub(crate) fn available(&self) -> bool { - self.0.available() - } - - pub(crate) fn num_connections(&self) -> usize { - self.0.conn.load(Ordering::Relaxed) - } - - /// Report opened connection - pub fn connection(&self) -> ConnectionTag { - ConnectionTag::new(self.0.clone()) - } - - /// Report rate connection, rate is usually ssl handshake - pub fn connection_rate(&self) -> ConnectionRateTag { - ConnectionRateTag::new(self.0.clone()) - } -} - -#[derive(Default)] -struct ConnectionsInner { - notify: AcceptNotify, - conn: AtomicUsize, - connrate: AtomicUsize, - maxconn: usize, - maxconnrate: usize, - maxconn_low: usize, - maxconnrate_low: usize, -} - -impl ConnectionsInner { - fn available(&self) -> bool { - if self.maxconnrate <= self.connrate.load(Ordering::Relaxed) { - false - } else { - self.maxconn > self.conn.load(Ordering::Relaxed) - } - } - - fn notify_maxconn(&self, maxconn: usize) { - if maxconn > self.maxconn_low && maxconn <= self.maxconn { - self.notify.notify(); - } - } - - fn notify_maxconnrate(&self, connrate: usize) { - if connrate > self.maxconnrate_low && connrate <= self.maxconnrate { - self.notify.notify(); - } - } -} - -/// Type responsible for max connection stat. -/// -/// Max connections stat get updated on drop. -pub struct ConnectionTag(Arc); - -impl ConnectionTag { - fn new(inner: Arc) -> Self { - inner.conn.fetch_add(1, Ordering::Relaxed); - ConnectionTag(inner) - } -} - -impl Drop for ConnectionTag { - fn drop(&mut self) { - let conn = self.0.conn.fetch_sub(1, Ordering::Relaxed); - self.0.notify_maxconn(conn); - } -} - -/// Type responsible for max connection rate stat. -/// -/// Max connections rate stat get updated on drop. -pub struct ConnectionRateTag(Arc); - -impl ConnectionRateTag { - fn new(inner: Arc) -> Self { - inner.connrate.fetch_add(1, Ordering::Relaxed); - ConnectionRateTag(inner) - } -} - -impl Drop for ConnectionRateTag { - fn drop(&mut self) { - let connrate = self.0.connrate.fetch_sub(1, Ordering::Relaxed); - self.0.notify_maxconnrate(connrate); - } -} diff --git a/src/server/settings.rs b/src/server/settings.rs index fc0d931f..2ca0b9b9 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -17,7 +17,7 @@ use tokio_timer::{Delay, Interval}; use super::channel::Node; use super::message::{Request, RequestPool}; -use super::server::{ConnectionRateTag, ConnectionTag, Connections}; +// use super::server::{ConnectionRateTag, ConnectionTag, Connections}; use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; @@ -140,7 +140,6 @@ pub(crate) struct WorkerSettings { ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, - conns: Connections, node: RefCell>, date: UnsafeCell, } @@ -148,9 +147,8 @@ pub(crate) struct WorkerSettings { impl WorkerSettings { pub(crate) fn create( apps: Vec, keep_alive: KeepAlive, settings: ServerSettings, - conns: Connections, ) -> Rc> { - let settings = Rc::new(Self::new(apps, keep_alive, settings, conns)); + let settings = Rc::new(Self::new(apps, keep_alive, settings)); // periodic date update let s = settings.clone(); @@ -169,7 +167,7 @@ impl WorkerSettings { impl WorkerSettings { pub(crate) fn new( - h: Vec, keep_alive: KeepAlive, settings: ServerSettings, conns: Connections, + h: Vec, keep_alive: KeepAlive, settings: ServerSettings, ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -185,7 +183,6 @@ impl WorkerSettings { date: UnsafeCell::new(Date::new()), keep_alive, ka_enabled, - conns, } } @@ -227,10 +224,6 @@ impl WorkerSettings { RequestPool::get(self.messages) } - pub fn connection(&self) -> ConnectionTag { - self.conns.connection() - } - fn update_date(&self) { // Unsafe: WorkerSetting is !Sync and !Send unsafe { &mut *self.date.get() }.update(); @@ -249,11 +242,6 @@ impl WorkerSettings { dst.extend_from_slice(date_bytes); } } - - #[allow(dead_code)] - pub(crate) fn connection_rate(&self) -> ConnectionRateTag { - self.conns.connection_rate() - } } struct Date { diff --git a/src/server/worker.rs b/src/server/worker.rs deleted file mode 100644 index 77128adc..00000000 --- a/src/server/worker.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::{net, time}; - -use futures::sync::mpsc::{SendError, UnboundedSender}; -use futures::sync::oneshot; -use futures::Future; - -use actix::msgs::StopArbiter; -use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response}; - -use super::server::{Connections, ServiceHandler}; -use super::Token; - -#[derive(Message)] -pub(crate) struct Conn { - pub io: T, - pub handler: Token, - pub token: Token, - pub peer: Option, -} - -pub(crate) struct Socket { - pub lst: net::TcpListener, - pub addr: net::SocketAddr, - pub token: Token, -} - -#[derive(Clone)] -pub(crate) struct WorkerClient { - pub idx: usize, - tx: UnboundedSender>, - conns: Connections, -} - -impl WorkerClient { - pub fn new( - idx: usize, tx: UnboundedSender>, conns: Connections, - ) -> Self { - WorkerClient { idx, tx, conns } - } - - pub fn send( - &self, msg: Conn, - ) -> Result<(), SendError>> { - self.tx.unbounded_send(msg) - } - - pub fn available(&self) -> bool { - self.conns.available() - } -} - -/// Stop worker message. Returns `true` on successful shutdown -/// and `false` if some connections still alive. -pub(crate) struct StopWorker { - pub graceful: Option, -} - -impl Message for StopWorker { - type Result = Result; -} - -/// Http worker -/// -/// Worker accepts Socket objects via unbounded channel and start requests -/// processing. -pub(crate) struct Worker { - conns: Connections, - handlers: Vec>, -} - -impl Actor for Worker { - type Context = Context; -} - -impl Worker { - pub(crate) fn new(conns: Connections, handlers: Vec>) -> Self { - Worker { conns, handlers } - } - - fn shutdown(&self, force: bool) { - self.handlers.iter().for_each(|h| h.shutdown(force)); - } - - 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.conns.num_connections(); - if num == 0 { - let _ = tx.send(true); - Arbiter::current().do_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); - slf.shutdown(true); - let _ = tx.send(false); - Arbiter::current().do_send(StopArbiter(0)); - } - }); - } -} - -impl Handler> for Worker { - type Result = (); - - fn handle(&mut self, msg: Conn, _: &mut Context) { - self.handlers[msg.handler.0].handle(msg.token, msg.io, msg.peer) - } -} - -/// `StopWorker` message handler -impl Handler for Worker { - type Result = Response; - - fn handle(&mut self, msg: StopWorker, ctx: &mut Context) -> Self::Result { - let num = self.conns.num_connections(); - if num == 0 { - info!("Shutting down http worker, 0 connections"); - Response::reply(Ok(true)) - } else if let Some(dur) = msg.graceful { - self.shutdown(false); - let (tx, rx) = oneshot::channel(); - let num = self.conns.num_connections(); - if num != 0 { - info!("Graceful http worker shutdown, {} connections", num); - self.shutdown_timeout(ctx, tx, dur); - Response::reply(Ok(true)) - } else { - Response::async(rx.map_err(|_| ())) - } - } else { - info!("Force shutdown http worker, {} connections", num); - self.shutdown(true); - Response::reply(Ok(false)) - } - } -} From c9a52e3197d3d34e41732f54cb99983b8d1bd8e7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 8 Sep 2018 09:20:18 -0700 Subject: [PATCH 0659/1635] refactor date generatioin --- src/server/channel.rs | 5 ++- src/server/h1.rs | 9 +++-- src/server/h1writer.rs | 5 ++- src/server/h2.rs | 12 +++---- src/server/h2writer.rs | 11 +++--- src/server/http.rs | 9 +++-- src/server/settings.rs | 80 ++++++++++++++++++++++-------------------- 7 files changed, 64 insertions(+), 67 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index d83e9a38..6d0992bc 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -1,5 +1,4 @@ use std::net::{Shutdown, SocketAddr}; -use std::rc::Rc; use std::{io, ptr, time}; use bytes::{Buf, BufMut, BytesMut}; @@ -15,7 +14,7 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; enum HttpProtocol { H1(h1::Http1), H2(h2::Http2), - Unknown(Rc>, Option, T, BytesMut), + Unknown(WorkerSettings, Option, T, BytesMut), } enum ProtocolKind { @@ -40,7 +39,7 @@ where H: HttpHandler + 'static, { pub(crate) fn new( - settings: Rc>, io: T, peer: Option, + settings: WorkerSettings, io: T, peer: Option, ) -> HttpChannel { let ka_timeout = settings.keep_alive_timer(); diff --git a/src/server/h1.rs b/src/server/h1.rs index afe143b4..82ab914a 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -1,6 +1,5 @@ use std::collections::VecDeque; use std::net::SocketAddr; -use std::rc::Rc; use std::time::{Duration, Instant}; use bytes::BytesMut; @@ -43,7 +42,7 @@ bitflags! { pub(crate) struct Http1 { flags: Flags, - settings: Rc>, + settings: WorkerSettings, addr: Option, stream: H1Writer, decoder: H1Decoder, @@ -90,7 +89,7 @@ where H: HttpHandler + 'static, { pub fn new( - settings: Rc>, stream: T, addr: Option, + settings: WorkerSettings, stream: T, addr: Option, buf: BytesMut, is_eof: bool, keepalive_timer: Option, ) -> Self { Http1 { @@ -99,7 +98,7 @@ where } else { Flags::KEEPALIVE }, - stream: H1Writer::new(stream, Rc::clone(&settings)), + stream: H1Writer::new(stream, settings.clone()), decoder: H1Decoder::new(), payload: None, tasks: VecDeque::new(), @@ -112,7 +111,7 @@ where #[inline] pub fn settings(&self) -> &WorkerSettings { - self.settings.as_ref() + &self.settings } #[inline] diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 72a68aeb..15451659 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -1,7 +1,6 @@ // #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] use std::io::{self, Write}; -use std::rc::Rc; use bytes::{BufMut, BytesMut}; use futures::{Async, Poll}; @@ -38,11 +37,11 @@ pub(crate) struct H1Writer { headers_size: u32, buffer: Output, buffer_capacity: usize, - settings: Rc>, + settings: WorkerSettings, } impl H1Writer { - pub fn new(stream: T, settings: Rc>) -> H1Writer { + pub fn new(stream: T, settings: WorkerSettings) -> H1Writer { H1Writer { flags: Flags::KEEPALIVE, written: 0, diff --git a/src/server/h2.rs b/src/server/h2.rs index 913e2cd7..ba52a884 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -38,7 +38,7 @@ where H: HttpHandler + 'static, { flags: Flags, - settings: Rc>, + settings: WorkerSettings, addr: Option, state: State>, tasks: VecDeque>, @@ -58,7 +58,7 @@ where H: HttpHandler + 'static, { pub fn new( - settings: Rc>, io: T, addr: Option, buf: Bytes, + settings: WorkerSettings, io: T, addr: Option, buf: Bytes, keepalive_timer: Option, ) -> Self { let extensions = io.extensions(); @@ -83,7 +83,7 @@ where } pub fn settings(&self) -> &WorkerSettings { - self.settings.as_ref() + &self.settings } pub fn poll(&mut self) -> Poll<(), ()> { @@ -224,7 +224,7 @@ where body, resp, self.addr, - &self.settings, + self.settings.clone(), self.extensions.clone(), )); } @@ -343,7 +343,7 @@ struct Entry { impl Entry { fn new( parts: Parts, recv: RecvStream, resp: SendResponse, - addr: Option, settings: &Rc>, + addr: Option, settings: WorkerSettings, extensions: Option>, ) -> Entry where @@ -387,7 +387,7 @@ impl Entry { )) }), payload: psender, - stream: H2Writer::new(resp, Rc::clone(settings)), + stream: H2Writer::new(resp, settings), flags: EntryFlags::empty(), recv, } diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 398e9817..4bfc1b7c 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -1,14 +1,12 @@ #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] +use std::{cmp, io}; + use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http2::server::SendResponse; use http2::{Reason, SendStream}; use modhttp::Response; -use std::rc::Rc; -use std::{cmp, io}; - -use http::{HttpTryFrom, Method, Version}; use super::helpers; use super::message::Request; @@ -20,6 +18,7 @@ use header::ContentEncoding; use http::header::{ HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, }; +use http::{HttpTryFrom, Method, Version}; use httpresponse::HttpResponse; const CHUNK_SIZE: usize = 16_384; @@ -40,12 +39,12 @@ pub(crate) struct H2Writer { written: u64, buffer: Output, buffer_capacity: usize, - settings: Rc>, + settings: WorkerSettings, } impl H2Writer { pub fn new( - respond: SendResponse, settings: Rc>, + respond: SendResponse, settings: WorkerSettings, ) -> H2Writer { H2Writer { stream: None, diff --git a/src/server/http.rs b/src/server/http.rs index 5059b132..b55842fa 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -1,5 +1,4 @@ use std::marker::PhantomData; -use std::rc::Rc; use std::sync::Arc; use std::{io, mem, net, time}; @@ -10,7 +9,7 @@ use futures::{Async, Poll, Stream}; use net2::TcpBuilder; use num_cpus; -use actix_net::{ssl, NewService, Service, Server}; +use actix_net::{ssl, NewService, Server, Service}; //#[cfg(feature = "tls")] //use native_tls::TlsAcceptor; @@ -603,7 +602,7 @@ where H: HttpHandler, Io: IoStream, { - settings: Rc>, + settings: WorkerSettings, tcp_ka: Option, _t: PhantomData, } @@ -621,7 +620,7 @@ where } else { None }; - let settings = WorkerSettings::create(apps, keep_alive, settings); + let settings = WorkerSettings::new(apps, keep_alive, settings); HttpServiceHandler { tcp_ka, @@ -647,7 +646,7 @@ where fn call(&mut self, mut req: Self::Request) -> Self::Future { let _ = req.set_nodelay(true); - HttpChannel::new(Rc::clone(&self.settings), req, None) + HttpChannel::new(self.settings.clone(), req, None) } // fn shutdown(&self, force: bool) { diff --git a/src/server/settings.rs b/src/server/settings.rs index 2ca0b9b9..439d0e75 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -2,22 +2,21 @@ use std::cell::{RefCell, RefMut, UnsafeCell}; use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; -use std::time::{Duration, Instant}; +use std::time::Duration; use std::{env, fmt, net}; -use actix::Arbiter; use bytes::BytesMut; -use futures::Stream; +use futures::{future, Future}; use futures_cpupool::CpuPool; use http::StatusCode; use lazycell::LazyCell; use parking_lot::Mutex; use time; -use tokio_timer::{Delay, Interval}; +use tokio_timer::{sleep, Delay, Interval}; +use tokio_current_thread::spawn; use super::channel::Node; use super::message::{Request, RequestPool}; -// use super::server::{ConnectionRateTag, ConnectionTag, Connections}; use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; @@ -134,34 +133,21 @@ impl ServerSettings { // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; -pub(crate) struct WorkerSettings { +pub(crate) struct WorkerSettings(Rc>); + +struct Inner { h: Vec, keep_alive: u64, ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, node: RefCell>, - date: UnsafeCell, + date: UnsafeCell<(bool, Date)>, } -impl WorkerSettings { - pub(crate) fn create( - apps: Vec, keep_alive: KeepAlive, settings: ServerSettings, - ) -> Rc> { - let settings = Rc::new(Self::new(apps, keep_alive, settings)); - - // periodic date update - let s = settings.clone(); - Arbiter::spawn( - Interval::new(Instant::now(), Duration::from_secs(1)) - .map_err(|_| ()) - .and_then(move |_| { - s.update_date(); - Ok(()) - }).fold((), |(), _| Ok(())), - ); - - settings +impl Clone for WorkerSettings { + fn clone(&self) -> Self { + WorkerSettings(self.0.clone()) } } @@ -175,23 +161,23 @@ impl WorkerSettings { KeepAlive::Disabled => (0, false), }; - WorkerSettings { + WorkerSettings(Rc::new(Inner { h, + keep_alive, + ka_enabled, bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), node: RefCell::new(Node::head()), - date: UnsafeCell::new(Date::new()), - keep_alive, - ka_enabled, - } + date: UnsafeCell::new((false, Date::new())), + })) } pub fn head(&self) -> RefMut> { - self.node.borrow_mut() + self.0.node.borrow_mut() } pub fn handlers(&self) -> &Vec { - &self.h + &self.0.h } pub fn keep_alive_timer(&self) -> Option { @@ -205,33 +191,49 @@ impl WorkerSettings { } pub fn keep_alive(&self) -> u64 { - self.keep_alive + self.0.keep_alive } pub fn keep_alive_enabled(&self) -> bool { - self.ka_enabled + self.0.ka_enabled } pub fn get_bytes(&self) -> BytesMut { - self.bytes.get_bytes() + self.0.bytes.get_bytes() } pub fn release_bytes(&self, bytes: BytesMut) { - self.bytes.release_bytes(bytes) + self.0.bytes.release_bytes(bytes) } pub fn get_request(&self) -> Request { - RequestPool::get(self.messages) + RequestPool::get(self.0.messages) } fn update_date(&self) { // Unsafe: WorkerSetting is !Sync and !Send - unsafe { &mut *self.date.get() }.update(); + unsafe { (&mut *self.0.date.get()).0 = false }; } +} +impl WorkerSettings { pub fn set_date(&self, dst: &mut BytesMut, full: bool) { // Unsafe: WorkerSetting is !Sync and !Send - let date_bytes = unsafe { &(*self.date.get()).bytes }; + let date_bytes = unsafe { + let date = &mut (*self.0.date.get()); + if !date.0 { + date.1.update(); + date.0 = true; + + // periodic date update + let s = self.clone(); + spawn(sleep(Duration::from_secs(1)).then(move |_| { + s.update_date(); + future::ok(()) + })); + } + &date.1.bytes + }; if full { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(b"date: "); From 7cf9af9b555e9360eab4c5dee4be5965d9e4e6c2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 8 Sep 2018 09:21:24 -0700 Subject: [PATCH 0660/1635] disable ssl for travis --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f03c9523..494a6a30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,12 +32,12 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then cargo clean - cargo test --features="alpn,tls,rust-tls" -- --nocapture + cargo test --features="" -- --nocapture fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - cargo tarpaulin --features="alpn,tls,rust-tls" --out Xml --no-count + cargo tarpaulin --features="" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi @@ -46,7 +46,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then - cargo doc --features "alpn, tls, rust-tls, session" --no-deps && + cargo doc --features "session" --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && From 6a61138bf80205342feb4140dfcb574a1e1cdf04 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 8 Sep 2018 14:55:39 -0700 Subject: [PATCH 0661/1635] enable ssl feature --- .travis.yml | 6 +- Cargo.toml | 3 + src/lib.rs | 4 +- src/server/h1.rs | 21 ++-- src/server/http.rs | 259 +++++++++++++++++++++----------------- src/server/mod.rs | 40 +----- src/server/settings.rs | 28 +++-- src/server/ssl/mod.rs | 22 ++-- src/server/ssl/openssl.rs | 91 ++++---------- tests/test_server.rs | 3 +- 10 files changed, 224 insertions(+), 253 deletions(-) diff --git a/.travis.yml b/.travis.yml index 494a6a30..e2d70678 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,12 +32,12 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then cargo clean - cargo test --features="" -- --nocapture + cargo test --features="ssl" -- --nocapture fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - cargo tarpaulin --features="" --out Xml --no-count + cargo tarpaulin --features="ssl" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi @@ -46,7 +46,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then - cargo doc --features "session" --no-deps && + cargo doc --features "ssl,session" --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/Cargo.toml b/Cargo.toml index d4ea4fc1..53680631 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,9 @@ default = ["session", "brotli", "flate2-c"] tls = ["native-tls", "tokio-tls"] # openssl +ssl = ["openssl", "tokio-openssl", "actix-net/ssl"] + +# deprecated, use "ssl" alpn = ["openssl", "tokio-openssl", "actix-net/ssl"] # rustls diff --git a/src/lib.rs b/src/lib.rs index 1dfe143e..099b0b16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,8 +64,8 @@ //! ## Package feature //! //! * `tls` - enables ssl support via `native-tls` crate -//! * `alpn` - enables ssl support via `openssl` crate, require for `http/2` -//! support +//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` +//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` //! * `uds` - enables support for making client requests via Unix Domain Sockets. //! Unix only. Not necessary for *serving* requests. //! * `session` - enables session support, includes `ring` crate as diff --git a/src/server/h1.rs b/src/server/h1.rs index 82ab914a..1d2ddbe2 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -517,15 +517,14 @@ mod tests { use httpmessage::HttpMessage; use server::h1decoder::Message; use server::settings::{ServerSettings, WorkerSettings}; - use server::{Connections, KeepAlive, Request}; + use server::{KeepAlive, Request}; - fn wrk_settings() -> Rc> { - Rc::new(WorkerSettings::::new( + fn wrk_settings() -> WorkerSettings { + WorkerSettings::::new( Vec::new(), KeepAlive::Os, ServerSettings::default(), - Connections::default(), - )) + ) } impl Message { @@ -644,9 +643,9 @@ mod tests { fn test_req_parse1() { let buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n"); let readbuf = BytesMut::new(); - let settings = Rc::new(wrk_settings()); + let settings = wrk_settings(); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false, None); + let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, false, None); h1.poll_io(); h1.poll_io(); assert_eq!(h1.tasks.len(), 1); @@ -657,9 +656,9 @@ mod tests { let buf = Buffer::new(""); let readbuf = BytesMut::from(Vec::::from(&b"GET /test HTTP/1.1\r\n\r\n"[..])); - let settings = Rc::new(wrk_settings()); + let settings = wrk_settings(); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, true, None); + let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, true, None); h1.poll_io(); assert_eq!(h1.tasks.len(), 1); } @@ -668,9 +667,9 @@ mod tests { fn test_req_parse_err() { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); let readbuf = BytesMut::new(); - let settings = Rc::new(wrk_settings()); + let settings = wrk_settings(); - let mut h1 = Http1::new(Rc::clone(&settings), buf, None, readbuf, false, None); + let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, false, None); h1.poll_io(); h1.poll_io(); assert!(h1.flags.contains(Flags::ERROR)); diff --git a/src/server/http.rs b/src/server/http.rs index b55842fa..725cfbac 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -2,19 +2,19 @@ use std::marker::PhantomData; use std::sync::Arc; use std::{io, mem, net, time}; -use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System}; +use actix::{Actor, Addr, AsyncContext, Context, Handler, System}; +use actix_net::{ssl, NewService, NewServiceExt, Server, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, Poll, Stream}; use net2::TcpBuilder; use num_cpus; - -use actix_net::{ssl, NewService, Server, Service}; +use tokio_tcp::TcpStream; //#[cfg(feature = "tls")] //use native_tls::TlsAcceptor; -#[cfg(feature = "alpn")] +#[cfg(any(feature = "alpn", feature = "ssl"))] use openssl::ssl::SslAcceptorBuilder; //#[cfg(feature = "rust-tls")] @@ -25,9 +25,10 @@ use super::settings::{ServerSettings, WorkerSettings}; use super::{HttpHandler, IntoHttpHandler, IoStream, KeepAlive}; struct Socket { + scheme: &'static str, lst: net::TcpListener, addr: net::SocketAddr, - handler: Box>, + handler: Box>, } /// An HTTP Server @@ -194,10 +195,7 @@ where /// and the user should be presented with an enumeration of which /// socket requires which protocol. pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.sockets - .iter() - .map(|s| (s.addr, s.handler.scheme())) - .collect() + self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() } /// Use listener for accepting incoming connection requests @@ -209,7 +207,8 @@ where self.sockets.push(Socket { lst, addr, - handler: Box::new(SimpleHandler { + scheme: "http", + handler: Box::new(SimpleFactory { addr, factory: self.factory.clone(), }), @@ -218,22 +217,28 @@ where self } - // #[doc(hidden)] - // /// Use listener for accepting incoming connection requests - // pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self - // where - // A: AcceptorService + Send + 'static, - // { - // let token = Token(self.handlers.len()); - // let addr = lst.local_addr().unwrap(); - // self.handlers.push(Box::new(StreamHandler::new( - // lst.local_addr().unwrap(), - // acceptor, - // ))); - // self.sockets.push(Socket { lst, addr, token }); + #[doc(hidden)] + /// Use listener for accepting incoming connection requests + pub(crate) fn listen_with(mut self, lst: net::TcpListener, acceptor: F) -> Self + where + F: Fn() -> T + Send + Clone + 'static, + T: NewService + Clone + 'static, + T::Response: IoStream, + { + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + lst, + addr, + scheme: "https", + handler: Box::new(AcceptorFactory { + addr, + acceptor, + factory: self.factory.clone(), + }), + }); - // self - // } + self + } // #[cfg(feature = "tls")] // /// Use listener for accepting incoming tls connection requests @@ -246,24 +251,27 @@ where // self.listen_with(lst, NativeTlsAcceptor::new(acceptor)) // } - // #[cfg(feature = "alpn")] - // /// Use listener for accepting incoming tls connection requests - // /// - // /// This method sets alpn protocols to "h2" and "http/1.1" - // pub fn listen_ssl( - // self, lst: net::TcpListener, builder: SslAcceptorBuilder, - // ) -> io::Result { - // use super::{OpensslAcceptor, ServerFlags}; + #[cfg(any(feature = "alpn", feature = "ssl"))] + /// Use listener for accepting incoming tls connection requests + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn listen_ssl( + self, lst: net::TcpListener, builder: SslAcceptorBuilder, + ) -> io::Result { + use super::{openssl_acceptor_with_flags, ServerFlags}; - // alpn support - // let flags = if self.no_http2 { - // ServerFlags::HTTP1 - // } else { - // ServerFlags::HTTP1 | ServerFlags::HTTP2 - // }; + let flags = if self.no_http2 { + ServerFlags::HTTP1 + } else { + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; - // Ok(self.listen_with(lst, OpensslAcceptor::with_flags(builder, flags)?)) - // } + let acceptor = openssl_acceptor_with_flags(builder, flags)?; + + Ok(self.listen_with(lst, move || { + ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) + })) + } // #[cfg(feature = "rust-tls")] // /// Use listener for accepting incoming tls connection requests @@ -400,60 +408,6 @@ where // } } -struct HttpService -where - H: HttpHandler, - F: IntoHttpHandler, - Io: IoStream, -{ - factory: Arc Vec + Send + Sync>, - addr: net::SocketAddr, - host: Option, - keep_alive: KeepAlive, - _t: PhantomData<(H, Io)>, -} - -impl NewService for HttpService -where - H: HttpHandler, - F: IntoHttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = (); - type InitError = (); - type Service = HttpServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - let s = ServerSettings::new(Some(self.addr), &self.host, false); - let apps: Vec<_> = (*self.factory)() - .into_iter() - .map(|h| h.into_handler()) - .collect(); - - ok(HttpServiceHandler::new(apps, self.keep_alive, s)) - } -} - -impl Clone for HttpService -where - H: HttpHandler, - F: IntoHttpHandler, - Io: IoStream, -{ - fn clone(&self) -> HttpService { - HttpService { - addr: self.addr, - factory: self.factory.clone(), - host: self.host.clone(), - keep_alive: self.keep_alive, - _t: PhantomData, - } - } -} - impl HttpServer { /// Start listening for incoming connections. /// @@ -500,8 +454,9 @@ impl HttpServer { for socket in sockets { let Socket { lst, - addr: _, handler, + addr: _, + scheme: _, } = socket; srv = handler.register(srv, lst, self.host.clone(), self.keep_alive); } @@ -597,6 +552,43 @@ impl HttpServer { // } // } +struct HttpService +where + H: HttpHandler, + F: IntoHttpHandler, + Io: IoStream, +{ + factory: Arc Vec + Send + Sync>, + addr: net::SocketAddr, + host: Option, + keep_alive: KeepAlive, + _t: PhantomData<(H, Io)>, +} + +impl NewService for HttpService +where + H: HttpHandler, + F: IntoHttpHandler, + Io: IoStream, +{ + type Request = Io; + type Response = (); + type Error = (); + type InitError = (); + type Service = HttpServiceHandler; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + let s = ServerSettings::new(Some(self.addr), &self.host, false); + let apps: Vec<_> = (*self.factory)() + .into_iter() + .map(|h| h.into_handler()) + .collect(); + + ok(HttpServiceHandler::new(apps, self.keep_alive, s)) + } +} + struct HttpServiceHandler where H: HttpHandler, @@ -656,21 +648,17 @@ where // } } -trait IoStreamHandler: Send +trait ServiceFactory where H: IntoHttpHandler, { - fn addr(&self) -> net::SocketAddr; - - fn scheme(&self) -> &'static str; - fn register( &self, server: Server, lst: net::TcpListener, host: Option, keep_alive: KeepAlive, ) -> Server; } -struct SimpleHandler +struct SimpleFactory where H: IntoHttpHandler, { @@ -678,27 +666,19 @@ where pub factory: Arc Vec + Send + Sync>, } -impl Clone for SimpleHandler { +impl Clone for SimpleFactory { fn clone(&self) -> Self { - SimpleHandler { + SimpleFactory { addr: self.addr, factory: self.factory.clone(), } } } -impl IoStreamHandler for SimpleHandler +impl ServiceFactory for SimpleFactory where H: IntoHttpHandler + 'static, { - fn addr(&self) -> net::SocketAddr { - self.addr - } - - fn scheme(&self) -> &'static str { - "http" - } - fn register( &self, server: Server, lst: net::TcpListener, host: Option, keep_alive: KeepAlive, @@ -716,6 +696,59 @@ where } } +struct AcceptorFactory +where + F: Fn() -> T + Send + Clone + 'static, + T: NewService, + H: IntoHttpHandler, +{ + pub addr: net::SocketAddr, + pub acceptor: F, + pub factory: Arc Vec + Send + Sync>, +} + +impl Clone for AcceptorFactory +where + F: Fn() -> T + Send + Clone + 'static, + T: NewService, + H: IntoHttpHandler, +{ + fn clone(&self) -> Self { + AcceptorFactory { + addr: self.addr, + acceptor: self.acceptor.clone(), + factory: self.factory.clone(), + } + } +} + +impl ServiceFactory for AcceptorFactory +where + F: Fn() -> T + Send + Clone + 'static, + H: IntoHttpHandler + 'static, + T: NewService + Clone + 'static, + T::Response: IoStream, +{ + fn register( + &self, server: Server, lst: net::TcpListener, host: Option, + keep_alive: KeepAlive, + ) -> Server { + let addr = self.addr; + let factory = self.factory.clone(); + let acceptor = self.acceptor.clone(); + + server.listen(lst, move || { + (acceptor)().and_then(HttpService { + keep_alive, + addr, + host: host.clone(), + factory: factory.clone(), + _t: PhantomData, + }) + }) + } +} + fn create_tcp_listener( addr: net::SocketAddr, backlog: i32, ) -> io::Result { diff --git a/src/server/mod.rs b/src/server/mod.rs index 25eca3a7..111cc87a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -115,6 +115,8 @@ use futures::{Async, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; +pub use actix_net::{PauseServer, ResumeServer, StopServer}; + mod channel; mod error; pub(crate) mod h1; @@ -128,9 +130,9 @@ pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; pub(crate) mod settings; -mod ssl; -use actix::Message; +mod ssl; +pub use self::ssl::*; pub use self::http::HttpServer; pub use self::message::Request; @@ -221,40 +223,6 @@ impl From> for KeepAlive { } } -/// 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 accepting incoming connections -#[derive(Message)] -pub struct ResumeServer; - -/// Stop incoming connection processing, stop all workers and exit. -/// -/// If server starts with `spawn()` method, then spawned thread get terminated. -pub struct StopServer { - /// Whether to try and shut down gracefully - pub graceful: bool, -} - -impl Message for StopServer { - type Result = Result<(), ()>; -} - -/// Socket id token -#[doc(hidden)] -#[derive(Clone, Copy)] -pub struct Token(usize); - -impl Token { - pub(crate) fn new(val: usize) -> Token { - Token(val) - } -} - /// Low level http request handler #[allow(unused_variables)] pub trait HttpHandler: 'static { diff --git a/src/server/settings.rs b/src/server/settings.rs index 439d0e75..47da515a 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -303,6 +303,8 @@ impl SharedBytesPool { #[cfg(test)] mod tests { use super::*; + use futures::future; + use tokio::runtime::current_thread; #[test] fn test_date_len() { @@ -311,16 +313,20 @@ mod tests { #[test] fn test_date() { - let settings = WorkerSettings::<()>::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - Connections::default(), - ); - let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, true); - let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, true); - assert_eq!(buf1, buf2); + let mut rt = current_thread::Runtime::new().unwrap(); + + let _ = rt.block_on(future::lazy(|| { + let settings = WorkerSettings::<()>::new( + Vec::new(), + KeepAlive::Os, + ServerSettings::default(), + ); + let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf1, true); + let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf2, true); + assert_eq!(buf1, buf2); + future::ok::<_, ()>(()) + })); } } diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs index bd931fb8..7101de78 100644 --- a/src/server/ssl/mod.rs +++ b/src/server/ssl/mod.rs @@ -1,14 +1,14 @@ -#[cfg(feature = "alpn")] +#[cfg(any(feature = "alpn", feature = "ssl"))] mod openssl; -#[cfg(feature = "alpn")] -pub use self::openssl::OpensslAcceptor; +#[cfg(any(feature = "alpn", feature = "ssl"))] +pub use self::openssl::*; -#[cfg(feature = "tls")] -mod nativetls; -#[cfg(feature = "tls")] -pub use self::nativetls::{NativeTlsAcceptor, TlsStream}; +//#[cfg(feature = "tls")] +//mod nativetls; +//#[cfg(feature = "tls")] +//pub use self::nativetls::{NativeTlsAcceptor, TlsStream}; -#[cfg(feature = "rust-tls")] -mod rustls; -#[cfg(feature = "rust-tls")] -pub use self::rustls::RustlsAcceptor; +//#[cfg(feature = "rust-tls")] +//mod rustls; +//#[cfg(feature = "rust-tls")] +//pub use self::rustls::RustlsAcceptor; diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs index 996c510d..34315523 100644 --- a/src/server/ssl/openssl.rs +++ b/src/server/ssl/openssl.rs @@ -1,80 +1,41 @@ use std::net::Shutdown; use std::{io, time}; -use futures::{Future, Poll}; use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; -use tokio_openssl::{AcceptAsync, SslAcceptorExt, SslStream}; +use tokio_openssl::SslStream; -use server::{AcceptorService, IoStream, ServerFlags}; +use server::{IoStream, ServerFlags}; -#[derive(Clone)] -/// Support `SSL` connections via openssl package -/// -/// `alpn` feature enables `OpensslAcceptor` type -pub struct OpensslAcceptor { - acceptor: SslAcceptor, +/// Configure `SslAcceptorBuilder` with enabled `HTTP/2` and `HTTP1.1` support. +pub fn openssl_acceptor(builder: SslAcceptorBuilder) -> io::Result { + openssl_acceptor_with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) } -impl OpensslAcceptor { - /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. - pub fn new(builder: SslAcceptorBuilder) -> io::Result { - OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) +/// Configure `SslAcceptorBuilder` with custom server flags. +pub fn openssl_acceptor_with_flags( + mut builder: SslAcceptorBuilder, flags: ServerFlags, +) -> io::Result { + let mut protos = Vec::new(); + if flags.contains(ServerFlags::HTTP1) { + protos.extend(b"\x08http/1.1"); + } + if flags.contains(ServerFlags::HTTP2) { + protos.extend(b"\x02h2"); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } + }); } - /// Create `OpensslAcceptor` with custom server flags. - pub fn with_flags( - mut builder: SslAcceptorBuilder, flags: ServerFlags, - ) -> io::Result { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP1) { - protos.extend(b"\x08http/1.1"); - } - if flags.contains(ServerFlags::HTTP2) { - protos.extend(b"\x02h2"); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - } - - if !protos.is_empty() { - builder.set_alpn_protos(&protos)?; - } - - Ok(OpensslAcceptor { - acceptor: builder.build(), - }) - } -} - -pub struct AcceptorFut(AcceptAsync); - -impl Future for AcceptorFut { - type Item = SslStream; - type Error = io::Error; - - fn poll(&mut self) -> Poll { - self.0 - .poll() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) - } -} - -impl AcceptorService for OpensslAcceptor { - type Accepted = SslStream; - type Future = AcceptorFut; - - fn scheme(&self) -> &'static str { - "https" + if !protos.is_empty() { + builder.set_alpn_protos(&protos)?; } - fn accept(&self, io: Io) -> Self::Future { - AcceptorFut(SslAcceptorExt::accept_async(&self.acceptor, io)) - } + Ok(builder.build()) } impl IoStream for SslStream { diff --git a/tests/test_server.rs b/tests/test_server.rs index 52c47dd2..30ee13fb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -30,6 +30,7 @@ use modhttp::Request; use rand::distributions::Alphanumeric; use rand::Rng; use tokio::runtime::current_thread::Runtime; +use tokio_current_thread::spawn; use tokio_tcp::TcpStream; use actix_web::*; @@ -904,7 +905,7 @@ fn test_h2() { let (response, _) = client.send_request(request, false).unwrap(); // Spawn a task to run the conn... - current_thread::spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); + spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); response.and_then(|response| { assert_eq!(response.status(), http::StatusCode::OK); From a3cfc242328c4e501c22728f73db8f94c27cc413 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 9 Sep 2018 10:51:30 -0700 Subject: [PATCH 0662/1635] refactor acceptor service --- src/server/http.rs | 382 +++++++++++++++++++++++++++++++++------------ src/server/mod.rs | 8 +- src/test.rs | 40 ++--- 3 files changed, 307 insertions(+), 123 deletions(-) diff --git a/src/server/http.rs b/src/server/http.rs index 725cfbac..41161ed3 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -1,9 +1,9 @@ use std::marker::PhantomData; -use std::sync::Arc; use std::{io, mem, net, time}; use actix::{Actor, Addr, AsyncContext, Context, Handler, System}; -use actix_net::{ssl, NewService, NewServiceExt, Server, Service}; +use actix_net::server::{Server, ServerServiceFactory}; +use actix_net::{ssl, NewService, NewServiceExt, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, Poll, Stream}; @@ -36,11 +36,12 @@ struct Socket { /// By default it serves HTTP2 when HTTPs is enabled, /// in order to change it, use `ServerFlags` that can be provided /// to acceptor service. -pub struct HttpServer +pub struct HttpServer where H: IntoHttpHandler + 'static, + F: Fn() -> Vec + Send + Clone, { - factory: Arc Vec + Send + Sync>, + factory: F, host: Option, keep_alive: KeepAlive, backlog: i32, @@ -54,21 +55,39 @@ where sockets: Vec>, } -impl HttpServer +impl HttpServer where H: IntoHttpHandler + 'static, + F: Fn() -> Vec + Send + Clone + 'static, { /// Create new http server with application factory - pub fn new(factory: F) -> Self + pub fn new(factory: F1) -> HttpServer Vec + Send + Clone> where - F: Fn() -> U + Sync + Send + 'static, + F1: Fn() -> U + Send + Clone, U: IntoIterator + 'static, { - let f = move || (factory)().into_iter().collect(); + let f = move || (factory.clone())().into_iter().collect(); HttpServer { threads: num_cpus::get(), - factory: Arc::new(f), + factory: f, + host: None, + backlog: 2048, + keep_alive: KeepAlive::Os, + shutdown_timeout: 30, + exit: false, + no_http2: false, + no_signals: false, + maxconn: 25_600, + maxconnrate: 256, + sockets: Vec::new(), + } + } + + pub(crate) fn with_factory(factory: F) -> HttpServer { + HttpServer { + factory, + threads: num_cpus::get(), host: None, backlog: 2048, keep_alive: KeepAlive::Timeout(5), @@ -211,6 +230,13 @@ where handler: Box::new(SimpleFactory { addr, factory: self.factory.clone(), + pipeline: DefaultPipelineFactory { + addr, + factory: self.factory.clone(), + host: self.host.clone(), + keep_alive: self.keep_alive, + _t: PhantomData, + }, }), }); @@ -219,22 +245,30 @@ where #[doc(hidden)] /// Use listener for accepting incoming connection requests - pub(crate) fn listen_with(mut self, lst: net::TcpListener, acceptor: F) -> Self + pub(crate) fn listen_with( + mut self, lst: net::TcpListener, acceptor: A, + ) -> Self where - F: Fn() -> T + Send + Clone + 'static, - T: NewService + Clone + 'static, - T::Response: IoStream, + A: AcceptorServiceFactory, + T: NewService + + Clone + + 'static, + Io: IoStream + Send, { let addr = lst.local_addr().unwrap(); self.sockets.push(Socket { lst, addr, scheme: "https", - handler: Box::new(AcceptorFactory { - addr, + handler: Box::new(HttpServiceBuilder::new( acceptor, - factory: self.factory.clone(), - }), + DefaultPipelineFactory::new( + self.factory.clone(), + self.host.clone(), + addr, + self.keep_alive, + ), + )), }); self @@ -256,7 +290,7 @@ where /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn listen_ssl( - self, lst: net::TcpListener, builder: SslAcceptorBuilder, + mut self, lst: net::TcpListener, builder: SslAcceptorBuilder, ) -> io::Result { use super::{openssl_acceptor_with_flags, ServerFlags}; @@ -268,9 +302,23 @@ where let acceptor = openssl_acceptor_with_flags(builder, flags)?; - Ok(self.listen_with(lst, move || { - ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) - })) + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + lst, + addr, + scheme: "https", + handler: Box::new(HttpServiceBuilder::new( + move || ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()), + DefaultPipelineFactory::new( + self.factory.clone(), + self.host.clone(), + addr, + self.keep_alive, + ), + )), + }); + + Ok(self) } // #[cfg(feature = "rust-tls")] @@ -408,7 +456,7 @@ where // } } -impl HttpServer { +impl Vec + Send + Clone> HttpServer { /// Start listening for incoming connections. /// /// This method starts number of http workers in separate threads. @@ -552,35 +600,35 @@ impl HttpServer { // } // } -struct HttpService +struct HttpService where - H: HttpHandler, - F: IntoHttpHandler, + F: Fn() -> Vec, + H: IntoHttpHandler, Io: IoStream, { - factory: Arc Vec + Send + Sync>, + factory: F, addr: net::SocketAddr, host: Option, keep_alive: KeepAlive, - _t: PhantomData<(H, Io)>, + _t: PhantomData, } -impl NewService for HttpService +impl NewService for HttpService where - H: HttpHandler, - F: IntoHttpHandler, + F: Fn() -> Vec, + H: IntoHttpHandler, Io: IoStream, { type Request = Io; type Response = (); type Error = (); type InitError = (); - type Service = HttpServiceHandler; + type Service = HttpServiceHandler; type Future = FutureResult; fn new_service(&self) -> Self::Future { let s = ServerSettings::new(Some(self.addr), &self.host, false); - let apps: Vec<_> = (*self.factory)() + let apps: Vec<_> = (self.factory)() .into_iter() .map(|h| h.into_handler()) .collect(); @@ -658,94 +706,43 @@ where ) -> Server; } -struct SimpleFactory +struct SimpleFactory where H: IntoHttpHandler, + F: Fn() -> Vec + Send + Clone, + P: HttpPipelineFactory, { pub addr: net::SocketAddr, - pub factory: Arc Vec + Send + Sync>, + pub factory: F, + pub pipeline: P, } -impl Clone for SimpleFactory { +impl Clone for SimpleFactory +where + P: HttpPipelineFactory, + F: Fn() -> Vec + Send + Clone, +{ fn clone(&self) -> Self { SimpleFactory { addr: self.addr, factory: self.factory.clone(), + pipeline: self.pipeline.clone(), } } } -impl ServiceFactory for SimpleFactory +impl ServiceFactory for SimpleFactory where H: IntoHttpHandler + 'static, + F: Fn() -> Vec + Send + Clone + 'static, + P: HttpPipelineFactory, { fn register( - &self, server: Server, lst: net::TcpListener, host: Option, - keep_alive: KeepAlive, + &self, server: Server, lst: net::TcpListener, _host: Option, + _keep_alive: KeepAlive, ) -> Server { - let addr = self.addr; - let factory = self.factory.clone(); - - server.listen(lst, move || HttpService { - keep_alive, - addr, - host: host.clone(), - factory: factory.clone(), - _t: PhantomData, - }) - } -} - -struct AcceptorFactory -where - F: Fn() -> T + Send + Clone + 'static, - T: NewService, - H: IntoHttpHandler, -{ - pub addr: net::SocketAddr, - pub acceptor: F, - pub factory: Arc Vec + Send + Sync>, -} - -impl Clone for AcceptorFactory -where - F: Fn() -> T + Send + Clone + 'static, - T: NewService, - H: IntoHttpHandler, -{ - fn clone(&self) -> Self { - AcceptorFactory { - addr: self.addr, - acceptor: self.acceptor.clone(), - factory: self.factory.clone(), - } - } -} - -impl ServiceFactory for AcceptorFactory -where - F: Fn() -> T + Send + Clone + 'static, - H: IntoHttpHandler + 'static, - T: NewService + Clone + 'static, - T::Response: IoStream, -{ - fn register( - &self, server: Server, lst: net::TcpListener, host: Option, - keep_alive: KeepAlive, - ) -> Server { - let addr = self.addr; - let factory = self.factory.clone(); - let acceptor = self.acceptor.clone(); - - server.listen(lst, move || { - (acceptor)().and_then(HttpService { - keep_alive, - addr, - host: host.clone(), - factory: factory.clone(), - _t: PhantomData, - }) - }) + let pipeline = self.pipeline.clone(); + server.listen(lst, move || pipeline.create()) } } @@ -760,3 +757,186 @@ fn create_tcp_listener( builder.bind(addr)?; Ok(builder.listen(backlog)?) } + +pub struct HttpServiceBuilder { + acceptor: A, + pipeline: P, + t: PhantomData, +} + +impl HttpServiceBuilder +where + A: AcceptorServiceFactory, + P: HttpPipelineFactory, + H: IntoHttpHandler, +{ + pub fn new(acceptor: A, pipeline: P) -> Self { + Self { + acceptor, + pipeline, + t: PhantomData, + } + } + + pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder + where + A1: AcceptorServiceFactory, + { + HttpServiceBuilder { + acceptor, + pipeline: self.pipeline, + t: PhantomData, + } + } + + pub fn pipeline(self, pipeline: P1) -> HttpServiceBuilder + where + P1: HttpPipelineFactory, + { + HttpServiceBuilder { + pipeline, + acceptor: self.acceptor, + t: PhantomData, + } + } + + fn finish(&self) -> impl ServerServiceFactory { + let acceptor = self.acceptor.clone(); + let pipeline = self.pipeline.clone(); + + move || acceptor.create().and_then(pipeline.create()) + } +} + +impl ServiceFactory for HttpServiceBuilder +where + A: AcceptorServiceFactory, + P: HttpPipelineFactory, + H: IntoHttpHandler, +{ + fn register( + &self, server: Server, lst: net::TcpListener, _host: Option, + _keep_alive: KeepAlive, + ) -> Server { + server.listen(lst, self.finish()) + } +} + +pub trait AcceptorServiceFactory: Send + Clone + 'static { + type Io: IoStream + Send; + type NewService: NewService< + Request = TcpStream, + Response = Self::Io, + Error = (), + InitError = (), + >; + + fn create(&self) -> Self::NewService; +} + +impl AcceptorServiceFactory for F +where + F: Fn() -> T + Send + Clone + 'static, + T::Response: IoStream + Send, + T: NewService, +{ + type Io = T::Response; + type NewService = T; + + fn create(&self) -> T { + (self)() + } +} + +pub trait HttpPipelineFactory: Send + Clone + 'static { + type Io: IoStream; + type NewService: NewService< + Request = Self::Io, + Response = (), + Error = (), + InitError = (), + >; + + fn create(&self) -> Self::NewService; +} + +impl HttpPipelineFactory for F +where + F: Fn() -> T + Send + Clone + 'static, + T: NewService, + T::Request: IoStream, +{ + type Io = T::Request; + type NewService = T; + + fn create(&self) -> T { + (self)() + } +} + +struct DefaultPipelineFactory +where + F: Fn() -> Vec + Send + Clone, +{ + factory: F, + host: Option, + addr: net::SocketAddr, + keep_alive: KeepAlive, + _t: PhantomData, +} + +impl DefaultPipelineFactory +where + Io: IoStream + Send, + F: Fn() -> Vec + Send + Clone + 'static, + H: IntoHttpHandler + 'static, +{ + fn new( + factory: F, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, + ) -> Self { + Self { + factory, + addr, + keep_alive, + host, + _t: PhantomData, + } + } +} + +impl Clone for DefaultPipelineFactory +where + Io: IoStream, + F: Fn() -> Vec + Send + Clone, + H: IntoHttpHandler, +{ + fn clone(&self) -> Self { + Self { + factory: self.factory.clone(), + addr: self.addr, + keep_alive: self.keep_alive, + host: self.host.clone(), + _t: PhantomData, + } + } +} + +impl HttpPipelineFactory for DefaultPipelineFactory +where + Io: IoStream + Send, + F: Fn() -> Vec + Send + Clone + 'static, + H: IntoHttpHandler + 'static, +{ + type Io = Io; + type NewService = HttpService; + + fn create(&self) -> Self::NewService { + HttpService { + addr: self.addr, + keep_alive: self.keep_alive, + host: self.host.clone(), + factory: self.factory.clone(), + _t: PhantomData, + } + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 111cc87a..6ba03376 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -174,13 +174,13 @@ const HW_BUFFER_SIZE: usize = 32_768; /// sys.run(); /// } /// ``` -pub fn new(factory: F) -> HttpServer +pub fn new(factory: F) -> HttpServer Vec + Send + Clone> where - F: Fn() -> U + Sync + Send + 'static, - U: IntoIterator + 'static, + F: Fn() -> U + Send + Clone + 'static, + U: IntoIterator, H: IntoHttpHandler + 'static, { - HttpServer::new(factory) + HttpServer::with_factory(move || (factory.clone())().into_iter().collect()) } #[doc(hidden)] diff --git a/src/test.rs b/src/test.rs index c068086d..c589ea4b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -79,13 +79,13 @@ impl TestServer { /// middlewares or set handlers for test application. pub fn new(config: F) -> Self where - F: Sync + Send + 'static + Fn(&mut TestApp<()>), + F: Clone + Send + 'static + Fn(&mut TestApp<()>), { TestServerBuilder::new(|| ()).start(config) } /// Create test server builder - pub fn build() -> TestServerBuilder<()> { + pub fn build() -> TestServerBuilder<(), impl Fn() -> () + Clone + Send + 'static> { TestServerBuilder::new(|| ()) } @@ -94,9 +94,9 @@ impl TestServer { /// This method can be used for constructing application state. /// Also it can be used for external dependency initialization, /// like creating sync actors for diesel integration. - pub fn build_with_state(state: F) -> TestServerBuilder + pub fn build_with_state(state: F) -> TestServerBuilder where - F: Fn() -> S + Sync + Send + 'static, + F: Fn() -> S + Clone + Send + 'static, S: 'static, { TestServerBuilder::new(state) @@ -105,11 +105,12 @@ impl TestServer { /// Start new test server with application factory pub fn with_factory(factory: F) -> Self where - F: Fn() -> U + Sync + Send + 'static, - U: IntoIterator + 'static, + F: Fn() -> U + Send + Clone + 'static, + U: IntoIterator, H: IntoHttpHandler + 'static, { let (tx, rx) = mpsc::channel(); + let factory = move || (factory.clone())().into_iter().collect(); // run server in separate thread thread::spawn(move || { @@ -117,7 +118,7 @@ impl TestServer { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - HttpServer::new(factory) + let _ = HttpServer::with_factory(factory) .disable_signals() .listen(tcp) .keep_alive(5) @@ -261,22 +262,25 @@ impl Drop for TestServer { /// /// This type can be used to construct an instance of `TestServer` through a /// builder-like pattern. -pub struct TestServerBuilder { - state: Box S + Sync + Send + 'static>, +pub struct TestServerBuilder +where + F: Fn() -> S + Send + Clone + 'static, +{ + state: F, #[cfg(feature = "alpn")] ssl: Option, #[cfg(feature = "rust-tls")] rust_ssl: Option, } -impl TestServerBuilder { +impl TestServerBuilder +where + F: Fn() -> S + Send + Clone + 'static, +{ /// Create a new test server - pub fn new(state: F) -> TestServerBuilder - where - F: Fn() -> S + Sync + Send + 'static, - { + pub fn new(state: F) -> TestServerBuilder { TestServerBuilder { - state: Box::new(state), + state, #[cfg(feature = "alpn")] ssl: None, #[cfg(feature = "rust-tls")] @@ -300,9 +304,9 @@ impl TestServerBuilder { #[allow(unused_mut)] /// Configure test application and run test server - pub fn start(mut self, config: F) -> TestServer + pub fn start(mut self, config: C) -> TestServer where - F: Sync + Send + 'static + Fn(&mut TestApp), + C: Fn(&mut TestApp) + Clone + Send + 'static, { let (tx, rx) = mpsc::channel(); @@ -324,7 +328,7 @@ impl TestServerBuilder { let sys = System::new("actix-test-server"); let state = self.state; - let mut srv = HttpServer::new(move || { + let mut srv = HttpServer::with_factory(move || { let mut app = TestApp::new(state()); config(&mut app); vec![app] From a63d3f9a7a0e5f4982404b66802c73eb9e6c65fd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 9 Sep 2018 14:14:53 -0700 Subject: [PATCH 0663/1635] cleanup ServerFactory trait --- src/server/http.rs | 130 ++++++++++++++++++++++++------------------- tests/test_server.rs | 1 + 2 files changed, 75 insertions(+), 56 deletions(-) diff --git a/src/server/http.rs b/src/server/http.rs index 41161ed3..5cdeb564 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -351,28 +351,36 @@ where Ok(self) } - // /// Start listening for incoming connections with supplied acceptor. - // #[doc(hidden)] - // #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] - // pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result - // where - // S: net::ToSocketAddrs, - // A: AcceptorService + Send + 'static, - // { - // let sockets = self.bind2(addr)?; + /// Start listening for incoming connections with supplied acceptor. + #[doc(hidden)] + #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] + pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result + where + S: net::ToSocketAddrs, + A: AcceptorServiceFactory, + { + let sockets = self.bind2(addr)?; - // for lst in sockets { - // let token = Token(self.handlers.len()); - // let addr = lst.local_addr().unwrap(); - // self.handlers.push(Box::new(StreamHandler::new( - // lst.local_addr().unwrap(), - // acceptor.clone(), - // ))); - // self.sockets.push(Socket { lst, addr, token }) - // } + for lst in sockets { + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + lst, + addr, + scheme: "https", + handler: Box::new(HttpServiceBuilder::new( + acceptor.clone(), + DefaultPipelineFactory::new( + self.factory.clone(), + self.host.clone(), + addr, + self.keep_alive, + ), + )), + }); + } - // Ok(self) - // } + Ok(self) + } fn bind2( &self, addr: S, @@ -416,25 +424,50 @@ where // self.bind_with(addr, NativeTlsAcceptor::new(acceptor)) // } - // #[cfg(feature = "alpn")] - // /// Start listening for incoming tls connections. - // /// - // /// This method sets alpn protocols to "h2" and "http/1.1" - // pub fn bind_ssl(self, addr: S, builder: SslAcceptorBuilder) -> io::Result - // where - // S: net::ToSocketAddrs, - // { - // use super::{OpensslAcceptor, ServerFlags}; + #[cfg(any(feature = "alpn", feature = "ssl"))] + /// Start listening for incoming tls connections. + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn bind_ssl( + mut self, addr: S, builder: SslAcceptorBuilder, + ) -> io::Result + where + S: net::ToSocketAddrs, + { + use super::{openssl_acceptor_with_flags, ServerFlags}; - // // alpn support - // let flags = if !self.no_http2 { - // ServerFlags::HTTP1 - // } else { - // ServerFlags::HTTP1 | ServerFlags::HTTP2 - // }; + let sockets = self.bind2(addr)?; - // self.bind_with(addr, OpensslAcceptor::with_flags(builder, flags)?) - // } + // alpn support + let flags = if !self.no_http2 { + ServerFlags::HTTP1 + } else { + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; + + let acceptor = openssl_acceptor_with_flags(builder, flags)?; + + for lst in sockets { + let addr = lst.local_addr().unwrap(); + let accpt = acceptor.clone(); + self.sockets.push(Socket { + lst, + addr, + scheme: "https", + handler: Box::new(HttpServiceBuilder::new( + move || ssl::OpensslAcceptor::new(accpt.clone()).map_err(|_| ()), + DefaultPipelineFactory::new( + self.factory.clone(), + self.host.clone(), + addr, + self.keep_alive, + ), + )), + }); + } + + Ok(self) + } // #[cfg(feature = "rust-tls")] // /// Start listening for incoming tls connections. @@ -500,13 +533,7 @@ impl Vec + Send + Clone> HttpServer { let sockets = mem::replace(&mut self.sockets, Vec::new()); for socket in sockets { - let Socket { - lst, - handler, - addr: _, - scheme: _, - } = socket; - srv = handler.register(srv, lst, self.host.clone(), self.keep_alive); + srv = socket.handler.register(srv, socket.lst); } srv.start() } @@ -700,10 +727,7 @@ trait ServiceFactory where H: IntoHttpHandler, { - fn register( - &self, server: Server, lst: net::TcpListener, host: Option, - keep_alive: KeepAlive, - ) -> Server; + fn register(&self, server: Server, lst: net::TcpListener) -> Server; } struct SimpleFactory @@ -737,10 +761,7 @@ where F: Fn() -> Vec + Send + Clone + 'static, P: HttpPipelineFactory, { - fn register( - &self, server: Server, lst: net::TcpListener, _host: Option, - _keep_alive: KeepAlive, - ) -> Server { + fn register(&self, server: Server, lst: net::TcpListener) -> Server { let pipeline = self.pipeline.clone(); server.listen(lst, move || pipeline.create()) } @@ -814,10 +835,7 @@ where P: HttpPipelineFactory, H: IntoHttpHandler, { - fn register( - &self, server: Server, lst: net::TcpListener, _host: Option, - _keep_alive: KeepAlive, - ) -> Server { + fn register(&self, server: Server, lst: net::TcpListener) -> Server { server.listen(lst, self.finish()) } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 30ee13fb..41f4bcf3 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -9,6 +9,7 @@ extern crate h2; extern crate http as modhttp; extern crate rand; extern crate tokio; +extern crate tokio_current_thread; extern crate tokio_reactor; extern crate tokio_tcp; extern crate tokio_current_thread as current_thread; From 6f3e70a92a39501c8655c9c8e45e4004e424efa6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 9 Sep 2018 14:33:45 -0700 Subject: [PATCH 0664/1635] simplify application factory --- src/server/h1.rs | 75 +++++++++++++++++++++++------------------- src/server/h2.rs | 26 +++++---------- src/server/http.rs | 60 ++++++++++----------------------- src/server/mod.rs | 7 ++-- src/server/settings.rs | 10 +++--- src/test.rs | 12 +++---- 6 files changed, 80 insertions(+), 110 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 1d2ddbe2..739c6651 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -410,45 +410,52 @@ where self.keepalive_timer.take(); // search handler for request - for h in self.settings.handlers().iter() { - msg = match h.handle(msg) { - Ok(mut pipe) => { - if self.tasks.is_empty() { - match pipe.poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); + match self.settings.handler().handle(msg) { + Ok(mut pipe) => { + if self.tasks.is_empty() { + match pipe.poll_io(&mut self.stream) { + Ok(Async::Ready(ready)) => { + // override keep-alive state + if self.stream.keepalive() { + self.flags.insert(Flags::KEEPALIVE); + } else { + self.flags.remove(Flags::KEEPALIVE); + } + // prepare stream for next response + self.stream.reset(); - if !ready { - let item = Entry { - pipe: EntryPipe::Task(pipe), - flags: EntryFlags::EOF, - }; - self.tasks.push_back(item); - } - continue 'outer; - } - Ok(Async::NotReady) => {} - Err(err) => { - error!("Unhandled error: {}", err); - self.flags.insert(Flags::ERROR); - return; + if !ready { + let item = Entry { + pipe: EntryPipe::Task(pipe), + flags: EntryFlags::EOF, + }; + self.tasks.push_back(item); } + continue 'outer; + } + Ok(Async::NotReady) => {} + Err(err) => { + error!("Unhandled error: {}", err); + self.flags.insert(Flags::ERROR); + return; } } - self.tasks.push_back(Entry { - pipe: EntryPipe::Task(pipe), - flags: EntryFlags::empty(), - }); - continue 'outer; } - Err(msg) => msg, + self.tasks.push_back(Entry { + pipe: EntryPipe::Task(pipe), + flags: EntryFlags::empty(), + }); + continue 'outer; + } + Err(msg) => { + // handler is not found + self.tasks.push_back(Entry { + pipe: EntryPipe::Error(ServerError::err( + Version::HTTP_11, + StatusCode::NOT_FOUND, + )), + flags: EntryFlags::empty(), + }); } } diff --git a/src/server/h2.rs b/src/server/h2.rs index ba52a884..a7cf8aec 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -368,28 +368,20 @@ impl Entry { let psender = PayloadType::new(msg.headers(), psender); // start request processing - let mut task = None; - for h in settings.handlers().iter() { - msg = match h.handle(msg) { - Ok(t) => { - task = Some(t); - break; - } - Err(msg) => msg, - } - } + let task = match settings.handler().handle(msg) { + Ok(task) => EntryPipe::Task(task), + Err(msg) => EntryPipe::Error(ServerError::err( + Version::HTTP_2, + StatusCode::NOT_FOUND, + )), + }; Entry { - task: task.map(EntryPipe::Task).unwrap_or_else(|| { - EntryPipe::Error(ServerError::err( - Version::HTTP_2, - StatusCode::NOT_FOUND, - )) - }), + task, + recv, payload: psender, stream: H2Writer::new(resp, settings), flags: EntryFlags::empty(), - recv, } } diff --git a/src/server/http.rs b/src/server/http.rs index 5cdeb564..faee041c 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -39,7 +39,7 @@ struct Socket { pub struct HttpServer where H: IntoHttpHandler + 'static, - F: Fn() -> Vec + Send + Clone, + F: Fn() -> H + Send + Clone, { factory: F, host: Option, @@ -58,33 +58,10 @@ where impl HttpServer where H: IntoHttpHandler + 'static, - F: Fn() -> Vec + Send + Clone + 'static, + F: Fn() -> H + Send + Clone + 'static, { /// Create new http server with application factory - pub fn new(factory: F1) -> HttpServer Vec + Send + Clone> - where - F1: Fn() -> U + Send + Clone, - U: IntoIterator + 'static, - { - let f = move || (factory.clone())().into_iter().collect(); - - HttpServer { - threads: num_cpus::get(), - factory: f, - host: None, - backlog: 2048, - keep_alive: KeepAlive::Os, - shutdown_timeout: 30, - exit: false, - no_http2: false, - no_signals: false, - maxconn: 25_600, - maxconnrate: 256, - sockets: Vec::new(), - } - } - - pub(crate) fn with_factory(factory: F) -> HttpServer { + pub fn new(factory: F) -> HttpServer { HttpServer { factory, threads: num_cpus::get(), @@ -489,7 +466,7 @@ where // } } -impl Vec + Send + Clone> HttpServer { +impl H + Send + Clone> HttpServer { /// Start listening for incoming connections. /// /// This method starts number of http workers in separate threads. @@ -629,7 +606,7 @@ impl Vec + Send + Clone> HttpServer { struct HttpService where - F: Fn() -> Vec, + F: Fn() -> H, H: IntoHttpHandler, Io: IoStream, { @@ -642,7 +619,7 @@ where impl NewService for HttpService where - F: Fn() -> Vec, + F: Fn() -> H, H: IntoHttpHandler, Io: IoStream, { @@ -655,12 +632,9 @@ where fn new_service(&self) -> Self::Future { let s = ServerSettings::new(Some(self.addr), &self.host, false); - let apps: Vec<_> = (self.factory)() - .into_iter() - .map(|h| h.into_handler()) - .collect(); + let app = (self.factory)().into_handler(); - ok(HttpServiceHandler::new(apps, self.keep_alive, s)) + ok(HttpServiceHandler::new(app, self.keep_alive, s)) } } @@ -680,14 +654,14 @@ where Io: IoStream, { fn new( - apps: Vec, keep_alive: KeepAlive, settings: ServerSettings, + app: H, keep_alive: KeepAlive, settings: ServerSettings, ) -> HttpServiceHandler { let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { Some(time::Duration::new(val as u64, 0)) } else { None }; - let settings = WorkerSettings::new(apps, keep_alive, settings); + let settings = WorkerSettings::new(app, keep_alive, settings); HttpServiceHandler { tcp_ka, @@ -733,7 +707,7 @@ where struct SimpleFactory where H: IntoHttpHandler, - F: Fn() -> Vec + Send + Clone, + F: Fn() -> H + Send + Clone, P: HttpPipelineFactory, { pub addr: net::SocketAddr, @@ -744,7 +718,7 @@ where impl Clone for SimpleFactory where P: HttpPipelineFactory, - F: Fn() -> Vec + Send + Clone, + F: Fn() -> H + Send + Clone, { fn clone(&self) -> Self { SimpleFactory { @@ -758,7 +732,7 @@ where impl ServiceFactory for SimpleFactory where H: IntoHttpHandler + 'static, - F: Fn() -> Vec + Send + Clone + 'static, + F: Fn() -> H + Send + Clone + 'static, P: HttpPipelineFactory, { fn register(&self, server: Server, lst: net::TcpListener) -> Server { @@ -894,7 +868,7 @@ where struct DefaultPipelineFactory where - F: Fn() -> Vec + Send + Clone, + F: Fn() -> H + Send + Clone, { factory: F, host: Option, @@ -906,7 +880,7 @@ where impl DefaultPipelineFactory where Io: IoStream + Send, - F: Fn() -> Vec + Send + Clone + 'static, + F: Fn() -> H + Send + Clone + 'static, H: IntoHttpHandler + 'static, { fn new( @@ -925,7 +899,7 @@ where impl Clone for DefaultPipelineFactory where Io: IoStream, - F: Fn() -> Vec + Send + Clone, + F: Fn() -> H + Send + Clone, H: IntoHttpHandler, { fn clone(&self) -> Self { @@ -942,7 +916,7 @@ where impl HttpPipelineFactory for DefaultPipelineFactory where Io: IoStream + Send, - F: Fn() -> Vec + Send + Clone + 'static, + F: Fn() -> H + Send + Clone + 'static, H: IntoHttpHandler + 'static, { type Io = Io; diff --git a/src/server/mod.rs b/src/server/mod.rs index 6ba03376..ec7e8e4e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -174,13 +174,12 @@ const HW_BUFFER_SIZE: usize = 32_768; /// sys.run(); /// } /// ``` -pub fn new(factory: F) -> HttpServer Vec + Send + Clone> +pub fn new(factory: F) -> HttpServer where - F: Fn() -> U + Send + Clone + 'static, - U: IntoIterator, + F: Fn() -> H + Send + Clone + 'static, H: IntoHttpHandler + 'static, { - HttpServer::with_factory(move || (factory.clone())().into_iter().collect()) + HttpServer::new(factory) } #[doc(hidden)] diff --git a/src/server/settings.rs b/src/server/settings.rs index 47da515a..18a8c095 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -136,7 +136,7 @@ const DATE_VALUE_LENGTH: usize = 29; pub(crate) struct WorkerSettings(Rc>); struct Inner { - h: Vec, + handler: H, keep_alive: u64, ka_enabled: bool, bytes: Rc, @@ -153,7 +153,7 @@ impl Clone for WorkerSettings { impl WorkerSettings { pub(crate) fn new( - h: Vec, keep_alive: KeepAlive, settings: ServerSettings, + handler: H, keep_alive: KeepAlive, settings: ServerSettings, ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -162,7 +162,7 @@ impl WorkerSettings { }; WorkerSettings(Rc::new(Inner { - h, + handler, keep_alive, ka_enabled, bytes: Rc::new(SharedBytesPool::new()), @@ -176,8 +176,8 @@ impl WorkerSettings { self.0.node.borrow_mut() } - pub fn handlers(&self) -> &Vec { - &self.0.h + pub fn handler(&self) -> &H { + &self.0.handler } pub fn keep_alive_timer(&self) -> Option { diff --git a/src/test.rs b/src/test.rs index c589ea4b..b9d64f27 100644 --- a/src/test.rs +++ b/src/test.rs @@ -103,14 +103,12 @@ impl TestServer { } /// Start new test server with application factory - pub fn with_factory(factory: F) -> Self + pub fn with_factory(factory: F) -> Self where - F: Fn() -> U + Send + Clone + 'static, - U: IntoIterator, + F: Fn() -> H + Send + Clone + 'static, H: IntoHttpHandler + 'static, { let (tx, rx) = mpsc::channel(); - let factory = move || (factory.clone())().into_iter().collect(); // run server in separate thread thread::spawn(move || { @@ -118,7 +116,7 @@ impl TestServer { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - let _ = HttpServer::with_factory(factory) + let _ = HttpServer::new(factory) .disable_signals() .listen(tcp) .keep_alive(5) @@ -328,10 +326,10 @@ where let sys = System::new("actix-test-server"); let state = self.state; - let mut srv = HttpServer::with_factory(move || { + let mut srv = HttpServer::new(move || { let mut app = TestApp::new(state()); config(&mut app); - vec![app] + app }).workers(1) .keep_alive(5) .disable_signals(); From dbb4fab4f7a91cb69d5356d5027193ba2c436dc4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 9 Sep 2018 18:06:00 -0700 Subject: [PATCH 0665/1635] separate mod for HttpHandler; add HttpHandler impl for Vec --- src/server/h1.rs | 33 ++----- src/server/h2.rs | 2 +- src/server/handler.rs | 189 +++++++++++++++++++++++++++++++++++++++++ src/server/http.rs | 3 +- src/server/mod.rs | 63 +------------- src/server/settings.rs | 7 +- 6 files changed, 204 insertions(+), 93 deletions(-) create mode 100644 src/server/handler.rs diff --git a/src/server/h1.rs b/src/server/h1.rs index 739c6651..5ae841bd 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -447,7 +447,7 @@ where }); continue 'outer; } - Err(msg) => { + Err(_) => { // handler is not found self.tasks.push_back(Entry { pipe: EntryPipe::Error(ServerError::err( @@ -516,19 +516,22 @@ mod tests { use std::{cmp, io, time}; use bytes::{Buf, Bytes, BytesMut}; + use futures::future; use http::{Method, Version}; + use tokio::runtime::current_thread; use tokio_io::{AsyncRead, AsyncWrite}; use super::*; - use application::HttpApplication; + use application::{App, HttpApplication}; use httpmessage::HttpMessage; use server::h1decoder::Message; + use server::handler::IntoHttpHandler; use server::settings::{ServerSettings, WorkerSettings}; use server::{KeepAlive, Request}; fn wrk_settings() -> WorkerSettings { WorkerSettings::::new( - Vec::new(), + App::new().into_handler(), KeepAlive::Os, ServerSettings::default(), ) @@ -646,30 +649,6 @@ mod tests { } } - #[test] - fn test_req_parse1() { - let buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n"); - let readbuf = BytesMut::new(); - let settings = wrk_settings(); - - let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, false, None); - h1.poll_io(); - h1.poll_io(); - assert_eq!(h1.tasks.len(), 1); - } - - #[test] - fn test_req_parse2() { - let buf = Buffer::new(""); - let readbuf = - BytesMut::from(Vec::::from(&b"GET /test HTTP/1.1\r\n\r\n"[..])); - let settings = wrk_settings(); - - let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, true, None); - h1.poll_io(); - assert_eq!(h1.tasks.len(), 1); - } - #[test] fn test_req_parse_err() { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); diff --git a/src/server/h2.rs b/src/server/h2.rs index a7cf8aec..f31c2db3 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -370,7 +370,7 @@ impl Entry { // start request processing let task = match settings.handler().handle(msg) { Ok(task) => EntryPipe::Task(task), - Err(msg) => EntryPipe::Error(ServerError::err( + Err(_) => EntryPipe::Error(ServerError::err( Version::HTTP_2, StatusCode::NOT_FOUND, )), diff --git a/src/server/handler.rs b/src/server/handler.rs new file mode 100644 index 00000000..0700e196 --- /dev/null +++ b/src/server/handler.rs @@ -0,0 +1,189 @@ +use futures::{Async, Poll}; + +use super::message::Request; +use super::Writer; +use error::Error; + +/// Low level http request handler +#[allow(unused_variables)] +pub trait HttpHandler: 'static { + /// Request handling task + type Task: HttpHandlerTask; + + /// Handle request + fn handle(&self, req: Request) -> Result; +} + +impl HttpHandler for Box>> { + type Task = Box; + + fn handle(&self, req: Request) -> Result, Request> { + self.as_ref().handle(req) + } +} + +/// Low level http request handler +pub trait HttpHandlerTask { + /// Poll task, this method is used before or after *io* object is available + fn poll_completed(&mut self) -> Poll<(), Error> { + Ok(Async::Ready(())) + } + + /// Poll task when *io* object is available + fn poll_io(&mut self, io: &mut Writer) -> Poll; + + /// Connection is disconnected + fn disconnected(&mut self) {} +} + +impl HttpHandlerTask for Box { + fn poll_io(&mut self, io: &mut Writer) -> Poll { + self.as_mut().poll_io(io) + } +} + +/// 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 + } +} + +impl IntoHttpHandler for Vec { + type Handler = VecHttpHandler; + + fn into_handler(self) -> Self::Handler { + VecHttpHandler(self.into_iter().map(|item| item.into_handler()).collect()) + } +} + +#[doc(hidden)] +pub struct VecHttpHandler(Vec); + +impl HttpHandler for VecHttpHandler { + type Task = H::Task; + + fn handle(&self, mut req: Request) -> Result { + for h in &self.0 { + req = match h.handle(req) { + Ok(task) => return Ok(task), + Err(e) => e, + }; + } + Err(req) + } +} + +macro_rules! http_handler ({$EN:ident, $(($n:tt, $T:ident)),+} => { + impl<$($T: HttpHandler,)+> HttpHandler for ($($T,)+) { + type Task = $EN<$($T,)+>; + + fn handle(&self, mut req: Request) -> Result { + $( + req = match self.$n.handle(req) { + Ok(task) => return Ok($EN::$T(task)), + Err(e) => e, + }; + )+ + Err(req) + } + } + + #[doc(hidden)] + pub enum $EN<$($T: HttpHandler,)+> { + $($T ($T::Task),)+ + } + + impl<$($T: HttpHandler,)+> HttpHandlerTask for $EN<$($T,)+> + { + fn poll_completed(&mut self) -> Poll<(), Error> { + match self { + $($EN :: $T(ref mut task) => task.poll_completed(),)+ + } + } + + fn poll_io(&mut self, io: &mut Writer) -> Poll { + match self { + $($EN::$T(ref mut task) => task.poll_io(io),)+ + } + } + + /// Connection is disconnected + fn disconnected(&mut self) { + match self { + $($EN::$T(ref mut task) => task.disconnected(),)+ + } + } + } +}); + +http_handler!(HttpHandlerTask1, (0, A)); +http_handler!(HttpHandlerTask2, (0, A), (1, B)); +http_handler!(HttpHandlerTask3, (0, A), (1, B), (2, C)); +http_handler!(HttpHandlerTask4, (0, A), (1, B), (2, C), (3, D)); +http_handler!(HttpHandlerTask5, (0, A), (1, B), (2, C), (3, D), (4, E)); +http_handler!( + HttpHandlerTask6, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F) +); +http_handler!( + HttpHandlerTask7, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F), + (6, G) +); +http_handler!( + HttpHandlerTask8, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F), + (6, G), + (7, H) +); +http_handler!( + HttpHandlerTask9, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F), + (6, G), + (7, H), + (8, I) +); +http_handler!( + HttpHandlerTask10, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F), + (6, G), + (7, H), + (8, I), + (9, J) +); diff --git a/src/server/http.rs b/src/server/http.rs index faee041c..f67ebe95 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -3,7 +3,8 @@ use std::{io, mem, net, time}; use actix::{Actor, Addr, AsyncContext, Context, Handler, System}; use actix_net::server::{Server, ServerServiceFactory}; -use actix_net::{ssl, NewService, NewServiceExt, Service}; +use actix_net::service::{NewService, NewServiceExt, Service}; +use actix_net::ssl; use futures::future::{ok, FutureResult}; use futures::{Async, Poll, Stream}; diff --git a/src/server/mod.rs b/src/server/mod.rs index ec7e8e4e..75f75fcd 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -115,7 +115,7 @@ use futures::{Async, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; -pub use actix_net::{PauseServer, ResumeServer, StopServer}; +pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; mod channel; mod error; @@ -124,25 +124,25 @@ pub(crate) mod h1decoder; mod h1writer; mod h2; mod h2writer; +mod handler; pub(crate) mod helpers; mod http; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; pub(crate) mod settings; - mod ssl; -pub use self::ssl::*; +pub use self::handler::*; pub use self::http::HttpServer; pub use self::message::Request; pub use self::settings::ServerSettings; +pub use self::ssl::*; #[doc(hidden)] pub use self::helpers::write_content_length; use body::Binary; -use error::Error; use extensions::Extensions; use header::ContentEncoding; use httpresponse::HttpResponse; @@ -222,61 +222,6 @@ impl From> for KeepAlive { } } -/// Low level http request handler -#[allow(unused_variables)] -pub trait HttpHandler: 'static { - /// Request handling task - type Task: HttpHandlerTask; - - /// Handle request - fn handle(&self, req: Request) -> Result; -} - -impl HttpHandler for Box>> { - type Task = Box; - - fn handle(&self, req: Request) -> Result, Request> { - self.as_ref().handle(req) - } -} - -/// Low level http request handler -pub trait HttpHandlerTask { - /// Poll task, this method is used before or after *io* object is available - fn poll_completed(&mut self) -> Poll<(), Error> { - Ok(Async::Ready(())) - } - - /// Poll task when *io* object is available - fn poll_io(&mut self, io: &mut Writer) -> Poll; - - /// Connection is disconnected - fn disconnected(&mut self) {} -} - -impl HttpHandlerTask for Box { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - self.as_mut().poll_io(io) - } -} - -/// 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 - } -} - #[doc(hidden)] #[derive(Debug)] pub enum WriterState { diff --git a/src/server/settings.rs b/src/server/settings.rs index 18a8c095..fe36c331 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -316,11 +316,8 @@ mod tests { let mut rt = current_thread::Runtime::new().unwrap(); let _ = rt.block_on(future::lazy(|| { - let settings = WorkerSettings::<()>::new( - Vec::new(), - KeepAlive::Os, - ServerSettings::default(), - ); + let settings = + WorkerSettings::<()>::new((), KeepAlive::Os, ServerSettings::default()); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf1, true); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); From 0aa0f326f72ecdfe51d1494ef1ec0b9a0fc1c379 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Sep 2018 10:27:58 -0700 Subject: [PATCH 0666/1635] fix changes from master --- src/server/settings.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/server/settings.rs b/src/server/settings.rs index fe36c331..6b2fc727 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -2,7 +2,7 @@ use std::cell::{RefCell, RefMut, UnsafeCell}; use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; -use std::time::Duration; +use std::time::{Instant, Duration}; use std::{env, fmt, net}; use bytes::BytesMut; @@ -12,7 +12,7 @@ use http::StatusCode; use lazycell::LazyCell; use parking_lot::Mutex; use time; -use tokio_timer::{sleep, Delay, Interval}; +use tokio_timer::{sleep, Delay}; use tokio_current_thread::spawn; use super::channel::Node; @@ -181,9 +181,10 @@ impl WorkerSettings { } pub fn keep_alive_timer(&self) -> Option { - if self.keep_alive != 0 { + let ka = self.0.keep_alive; + if ka != 0 { Some(Delay::new( - Instant::now() + Duration::from_secs(self.keep_alive), + Instant::now() + Duration::from_secs(ka), )) } else { None From 9f1417af301024f07c964a0c28f56265676bd9af Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 26 Sep 2018 20:43:54 -0700 Subject: [PATCH 0667/1635] refactor http service builder --- Cargo.toml | 1 + src/middleware/cors.rs | 17 +- src/payload.rs | 2 +- src/server/builder.rs | 257 +++++++++++++++++++++++++++++ src/server/h1.rs | 9 +- src/server/http.rs | 359 +++-------------------------------------- src/server/mod.rs | 2 + src/server/service.rs | 133 +++++++++++++++ src/server/settings.rs | 8 +- tests/test_server.rs | 2 +- 10 files changed, 435 insertions(+), 355 deletions(-) create mode 100644 src/server/builder.rs create mode 100644 src/server/service.rs diff --git a/Cargo.toml b/Cargo.toml index 53680631..e17b7283 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ flate2-rust = ["flate2/rust_backend"] [dependencies] actix = "0.7.0" actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = { path = "../actix-net" } base64 = "0.9" bitflags = "1.0" diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index f1adf0c4..953f2911 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -1127,12 +1127,23 @@ mod tests { let resp: HttpResponse = HttpResponse::Ok().into(); let resp = cors.response(&req, resp).unwrap().response(); - let origins_str = resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().to_str().unwrap(); + let origins_str = resp + .headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .to_str() + .unwrap(); if origins_str.starts_with("https://www.example.com") { - assert_eq!("https://www.example.com, https://www.google.com", origins_str); + assert_eq!( + "https://www.example.com, https://www.google.com", + origins_str + ); } else { - assert_eq!("https://www.google.com, https://www.example.com", origins_str); + assert_eq!( + "https://www.google.com, https://www.example.com", + origins_str + ); } } diff --git a/src/payload.rs b/src/payload.rs index 382c0b0f..2131e3c3 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -1,8 +1,8 @@ //! Payload stream use bytes::{Bytes, BytesMut}; -use futures::task::Task; #[cfg(not(test))] use futures::task::current as current_task; +use futures::task::Task; use futures::{Async, Poll, Stream}; use std::cell::RefCell; use std::cmp; diff --git a/src/server/builder.rs b/src/server/builder.rs new file mode 100644 index 00000000..4a77bcd5 --- /dev/null +++ b/src/server/builder.rs @@ -0,0 +1,257 @@ +use std::marker::PhantomData; +use std::net; + +use actix_net::server; +use actix_net::service::{NewService, NewServiceExt, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Poll}; +use tokio_tcp::TcpStream; + +use super::handler::IntoHttpHandler; +use super::service::HttpService; +use super::{IoStream, KeepAlive}; + +pub(crate) trait ServiceFactory +where + H: IntoHttpHandler, +{ + fn register(&self, server: server::Server, lst: net::TcpListener) -> server::Server; +} + +pub struct HttpServiceBuilder +where + F: Fn() -> H + Send + Clone, +{ + factory: F, + acceptor: A, + pipeline: P, +} + +impl HttpServiceBuilder +where + F: Fn() -> H + Send + Clone, + H: IntoHttpHandler, + A: AcceptorServiceFactory, + P: HttpPipelineFactory, +{ + pub fn new(factory: F, acceptor: A, pipeline: P) -> Self { + Self { + factory, + pipeline, + acceptor, + } + } + + pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder + where + A1: AcceptorServiceFactory, + { + HttpServiceBuilder { + acceptor, + pipeline: self.pipeline, + factory: self.factory.clone(), + } + } + + pub fn pipeline(self, pipeline: P1) -> HttpServiceBuilder + where + P1: HttpPipelineFactory, + { + HttpServiceBuilder { + pipeline, + acceptor: self.acceptor, + factory: self.factory.clone(), + } + } + + fn finish(&self) -> impl server::StreamServiceFactory { + let pipeline = self.pipeline.clone(); + let acceptor = self.acceptor.clone(); + move || acceptor.create().and_then(pipeline.create()) + } +} + +impl Clone for HttpServiceBuilder +where + F: Fn() -> H + Send + Clone, + A: AcceptorServiceFactory, + P: HttpPipelineFactory, +{ + fn clone(&self) -> Self { + HttpServiceBuilder { + factory: self.factory.clone(), + acceptor: self.acceptor.clone(), + pipeline: self.pipeline.clone(), + } + } +} + +impl ServiceFactory for HttpServiceBuilder +where + F: Fn() -> H + Send + Clone, + A: AcceptorServiceFactory, + P: HttpPipelineFactory, + H: IntoHttpHandler, +{ + fn register(&self, server: server::Server, lst: net::TcpListener) -> server::Server { + server.listen("actix-web", lst, self.finish()) + } +} + +pub trait AcceptorServiceFactory: Send + Clone + 'static { + type Io: IoStream + Send; + type NewService: NewService< + Request = TcpStream, + Response = Self::Io, + Error = (), + InitError = (), + >; + + fn create(&self) -> Self::NewService; +} + +impl AcceptorServiceFactory for F +where + F: Fn() -> T + Send + Clone + 'static, + T::Response: IoStream + Send, + T: NewService, +{ + type Io = T::Response; + type NewService = T; + + fn create(&self) -> T { + (self)() + } +} + +pub trait HttpPipelineFactory: Send + Clone + 'static { + type Io: IoStream; + type NewService: NewService< + Request = Self::Io, + Response = (), + Error = (), + InitError = (), + >; + + fn create(&self) -> Self::NewService; +} + +impl HttpPipelineFactory for F +where + F: Fn() -> T + Send + Clone + 'static, + T: NewService, + T::Request: IoStream, +{ + type Io = T::Request; + type NewService = T; + + fn create(&self) -> T { + (self)() + } +} + +pub(crate) struct DefaultPipelineFactory +where + F: Fn() -> H + Send + Clone, +{ + factory: F, + host: Option, + addr: net::SocketAddr, + keep_alive: KeepAlive, + _t: PhantomData, +} + +impl DefaultPipelineFactory +where + Io: IoStream + Send, + F: Fn() -> H + Send + Clone + 'static, + H: IntoHttpHandler + 'static, +{ + pub fn new( + factory: F, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, + ) -> Self { + Self { + factory, + addr, + keep_alive, + host, + _t: PhantomData, + } + } +} + +impl Clone for DefaultPipelineFactory +where + Io: IoStream, + F: Fn() -> H + Send + Clone, + H: IntoHttpHandler, +{ + fn clone(&self) -> Self { + Self { + factory: self.factory.clone(), + addr: self.addr, + keep_alive: self.keep_alive, + host: self.host.clone(), + _t: PhantomData, + } + } +} + +impl HttpPipelineFactory for DefaultPipelineFactory +where + Io: IoStream + Send, + F: Fn() -> H + Send + Clone + 'static, + H: IntoHttpHandler + 'static, +{ + type Io = Io; + type NewService = HttpService; + + fn create(&self) -> Self::NewService { + HttpService::new( + self.factory.clone(), + self.addr, + self.host.clone(), + self.keep_alive, + ) + } +} + +#[derive(Clone)] +pub(crate) struct DefaultAcceptor; + +impl AcceptorServiceFactory for DefaultAcceptor { + type Io = TcpStream; + type NewService = DefaultAcceptor; + + fn create(&self) -> Self::NewService { + DefaultAcceptor + } +} + +impl NewService for DefaultAcceptor { + type Request = TcpStream; + type Response = TcpStream; + type Error = (); + type InitError = (); + type Service = DefaultAcceptor; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(DefaultAcceptor) + } +} + +impl Service for DefaultAcceptor { + type Request = TcpStream; + type Response = TcpStream; + type Error = (); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + ok(req) + } +} diff --git a/src/server/h1.rs b/src/server/h1.rs index 5ae841bd..36d40e8d 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -89,8 +89,8 @@ where H: HttpHandler + 'static, { pub fn new( - settings: WorkerSettings, stream: T, addr: Option, - buf: BytesMut, is_eof: bool, keepalive_timer: Option, + settings: WorkerSettings, stream: T, addr: Option, buf: BytesMut, + is_eof: bool, keepalive_timer: Option, ) -> Self { Http1 { flags: if is_eof { @@ -379,10 +379,7 @@ where fn push_response_entry(&mut self, status: StatusCode) { self.tasks.push_back(Entry { - pipe: EntryPipe::Error(ServerError::err( - Version::HTTP_11, - status, - )), + pipe: EntryPipe::Error(ServerError::err(Version::HTTP_11, status)), flags: EntryFlags::empty(), }); } diff --git a/src/server/http.rs b/src/server/http.rs index f67ebe95..f54900fc 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -1,13 +1,10 @@ -use std::marker::PhantomData; -use std::{io, mem, net, time}; +use std::{io, mem, net}; -use actix::{Actor, Addr, AsyncContext, Context, Handler, System}; -use actix_net::server::{Server, ServerServiceFactory}; -use actix_net::service::{NewService, NewServiceExt, Service}; +use actix::{Addr, System}; +use actix_net::server; +use actix_net::service::NewService; use actix_net::ssl; -use futures::future::{ok, FutureResult}; -use futures::{Async, Poll, Stream}; use net2::TcpBuilder; use num_cpus; use tokio_tcp::TcpStream; @@ -21,9 +18,9 @@ use openssl::ssl::SslAcceptorBuilder; //#[cfg(feature = "rust-tls")] //use rustls::ServerConfig; -use super::channel::HttpChannel; -use super::settings::{ServerSettings, WorkerSettings}; -use super::{HttpHandler, IntoHttpHandler, IoStream, KeepAlive}; +use super::builder::{AcceptorServiceFactory, HttpServiceBuilder, ServiceFactory}; +use super::builder::{DefaultAcceptor, DefaultPipelineFactory}; +use super::{IntoHttpHandler, IoStream, KeepAlive}; struct Socket { scheme: &'static str, @@ -205,17 +202,16 @@ where lst, addr, scheme: "http", - handler: Box::new(SimpleFactory { - addr, - factory: self.factory.clone(), - pipeline: DefaultPipelineFactory { + handler: Box::new(HttpServiceBuilder::new( + self.factory.clone(), + DefaultAcceptor, + DefaultPipelineFactory::new( + self.factory.clone(), + self.host.clone(), addr, - factory: self.factory.clone(), - host: self.host.clone(), - keep_alive: self.keep_alive, - _t: PhantomData, - }, - }), + self.keep_alive, + ), + )), }); self @@ -239,6 +235,7 @@ where addr, scheme: "https", handler: Box::new(HttpServiceBuilder::new( + self.factory.clone(), acceptor, DefaultPipelineFactory::new( self.factory.clone(), @@ -346,6 +343,7 @@ where addr, scheme: "https", handler: Box::new(HttpServiceBuilder::new( + self.factory.clone(), acceptor.clone(), DefaultPipelineFactory::new( self.factory.clone(), @@ -493,10 +491,10 @@ impl H + Send + Clone> HttpServer { /// sys.run(); // <- Run actix system, this method starts all async processes /// } /// ``` - pub fn start(mut self) -> Addr { + pub fn start(mut self) -> Addr { ssl::max_concurrent_ssl_connect(self.maxconnrate); - let mut srv = Server::new() + let mut srv = server::Server::new() .workers(self.threads) .maxconn(self.maxconn) .shutdown_timeout(self.shutdown_timeout); @@ -605,143 +603,6 @@ impl H + Send + Clone> HttpServer { // } // } -struct HttpService -where - F: Fn() -> H, - H: IntoHttpHandler, - Io: IoStream, -{ - factory: F, - addr: net::SocketAddr, - host: Option, - keep_alive: KeepAlive, - _t: PhantomData, -} - -impl NewService for HttpService -where - F: Fn() -> H, - H: IntoHttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = (); - type InitError = (); - type Service = HttpServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - let s = ServerSettings::new(Some(self.addr), &self.host, false); - let app = (self.factory)().into_handler(); - - ok(HttpServiceHandler::new(app, self.keep_alive, s)) - } -} - -struct HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: WorkerSettings, - tcp_ka: Option, - _t: PhantomData, -} - -impl HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new( - app: H, keep_alive: KeepAlive, settings: ServerSettings, - ) -> HttpServiceHandler { - let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { - Some(time::Duration::new(val as u64, 0)) - } else { - None - }; - let settings = WorkerSettings::new(app, keep_alive, settings); - - HttpServiceHandler { - tcp_ka, - settings, - _t: PhantomData, - } - } -} - -impl Service for HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = (); - type Future = HttpChannel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, mut req: Self::Request) -> Self::Future { - let _ = req.set_nodelay(true); - HttpChannel::new(self.settings.clone(), req, None) - } - - // fn shutdown(&self, force: bool) { - // if force { - // self.settings.head().traverse::(); - // } - // } -} - -trait ServiceFactory -where - H: IntoHttpHandler, -{ - fn register(&self, server: Server, lst: net::TcpListener) -> Server; -} - -struct SimpleFactory -where - H: IntoHttpHandler, - F: Fn() -> H + Send + Clone, - P: HttpPipelineFactory, -{ - pub addr: net::SocketAddr, - pub factory: F, - pub pipeline: P, -} - -impl Clone for SimpleFactory -where - P: HttpPipelineFactory, - F: Fn() -> H + Send + Clone, -{ - fn clone(&self) -> Self { - SimpleFactory { - addr: self.addr, - factory: self.factory.clone(), - pipeline: self.pipeline.clone(), - } - } -} - -impl ServiceFactory for SimpleFactory -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone + 'static, - P: HttpPipelineFactory, -{ - fn register(&self, server: Server, lst: net::TcpListener) -> Server { - let pipeline = self.pipeline.clone(); - server.listen(lst, move || pipeline.create()) - } -} - fn create_tcp_listener( addr: net::SocketAddr, backlog: i32, ) -> io::Result { @@ -753,183 +614,3 @@ fn create_tcp_listener( builder.bind(addr)?; Ok(builder.listen(backlog)?) } - -pub struct HttpServiceBuilder { - acceptor: A, - pipeline: P, - t: PhantomData, -} - -impl HttpServiceBuilder -where - A: AcceptorServiceFactory, - P: HttpPipelineFactory, - H: IntoHttpHandler, -{ - pub fn new(acceptor: A, pipeline: P) -> Self { - Self { - acceptor, - pipeline, - t: PhantomData, - } - } - - pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder - where - A1: AcceptorServiceFactory, - { - HttpServiceBuilder { - acceptor, - pipeline: self.pipeline, - t: PhantomData, - } - } - - pub fn pipeline(self, pipeline: P1) -> HttpServiceBuilder - where - P1: HttpPipelineFactory, - { - HttpServiceBuilder { - pipeline, - acceptor: self.acceptor, - t: PhantomData, - } - } - - fn finish(&self) -> impl ServerServiceFactory { - let acceptor = self.acceptor.clone(); - let pipeline = self.pipeline.clone(); - - move || acceptor.create().and_then(pipeline.create()) - } -} - -impl ServiceFactory for HttpServiceBuilder -where - A: AcceptorServiceFactory, - P: HttpPipelineFactory, - H: IntoHttpHandler, -{ - fn register(&self, server: Server, lst: net::TcpListener) -> Server { - server.listen(lst, self.finish()) - } -} - -pub trait AcceptorServiceFactory: Send + Clone + 'static { - type Io: IoStream + Send; - type NewService: NewService< - Request = TcpStream, - Response = Self::Io, - Error = (), - InitError = (), - >; - - fn create(&self) -> Self::NewService; -} - -impl AcceptorServiceFactory for F -where - F: Fn() -> T + Send + Clone + 'static, - T::Response: IoStream + Send, - T: NewService, -{ - type Io = T::Response; - type NewService = T; - - fn create(&self) -> T { - (self)() - } -} - -pub trait HttpPipelineFactory: Send + Clone + 'static { - type Io: IoStream; - type NewService: NewService< - Request = Self::Io, - Response = (), - Error = (), - InitError = (), - >; - - fn create(&self) -> Self::NewService; -} - -impl HttpPipelineFactory for F -where - F: Fn() -> T + Send + Clone + 'static, - T: NewService, - T::Request: IoStream, -{ - type Io = T::Request; - type NewService = T; - - fn create(&self) -> T { - (self)() - } -} - -struct DefaultPipelineFactory -where - F: Fn() -> H + Send + Clone, -{ - factory: F, - host: Option, - addr: net::SocketAddr, - keep_alive: KeepAlive, - _t: PhantomData, -} - -impl DefaultPipelineFactory -where - Io: IoStream + Send, - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, -{ - fn new( - factory: F, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, - ) -> Self { - Self { - factory, - addr, - keep_alive, - host, - _t: PhantomData, - } - } -} - -impl Clone for DefaultPipelineFactory -where - Io: IoStream, - F: Fn() -> H + Send + Clone, - H: IntoHttpHandler, -{ - fn clone(&self) -> Self { - Self { - factory: self.factory.clone(), - addr: self.addr, - keep_alive: self.keep_alive, - host: self.host.clone(), - _t: PhantomData, - } - } -} - -impl HttpPipelineFactory for DefaultPipelineFactory -where - Io: IoStream + Send, - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, -{ - type Io = Io; - type NewService = HttpService; - - fn create(&self) -> Self::NewService { - HttpService { - addr: self.addr, - keep_alive: self.keep_alive, - host: self.host.clone(), - factory: self.factory.clone(), - _t: PhantomData, - } - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index 75f75fcd..ac4ffc9a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -117,6 +117,7 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; +pub(crate) mod builder; mod channel; mod error; pub(crate) mod h1; @@ -130,6 +131,7 @@ mod http; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; +pub(crate) mod service; pub(crate) mod settings; mod ssl; diff --git a/src/server/service.rs b/src/server/service.rs new file mode 100644 index 00000000..6f80cd6d --- /dev/null +++ b/src/server/service.rs @@ -0,0 +1,133 @@ +use std::marker::PhantomData; +use std::net; +use std::time::Duration; + +use actix_net::service::{NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Poll}; + +use super::channel::HttpChannel; +use super::handler::{HttpHandler, IntoHttpHandler}; +use super::settings::{ServerSettings, WorkerSettings}; +use super::{IoStream, KeepAlive}; + +pub enum HttpServiceMessage { + /// New stream + Connect(T), + /// Gracefull shutdown + Shutdown(Duration), + /// Force shutdown + ForceShutdown, +} + +pub(crate) struct HttpService +where + F: Fn() -> H, + H: IntoHttpHandler, + Io: IoStream, +{ + factory: F, + addr: net::SocketAddr, + host: Option, + keep_alive: KeepAlive, + _t: PhantomData, +} + +impl HttpService +where + F: Fn() -> H, + H: IntoHttpHandler, + Io: IoStream, +{ + pub fn new( + factory: F, addr: net::SocketAddr, host: Option, keep_alive: KeepAlive, + ) -> Self { + HttpService { + factory, + addr, + host, + keep_alive, + _t: PhantomData, + } + } +} + +impl NewService for HttpService +where + F: Fn() -> H, + H: IntoHttpHandler, + Io: IoStream, +{ + type Request = Io; + type Response = (); + type Error = (); + type InitError = (); + type Service = HttpServiceHandler; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + let s = ServerSettings::new(Some(self.addr), &self.host, false); + let app = (self.factory)().into_handler(); + + ok(HttpServiceHandler::new(app, self.keep_alive, s)) + } +} + +pub(crate) struct HttpServiceHandler +where + H: HttpHandler, + Io: IoStream, +{ + settings: WorkerSettings, + tcp_ka: Option, + _t: PhantomData, +} + +impl HttpServiceHandler +where + H: HttpHandler, + Io: IoStream, +{ + fn new( + app: H, keep_alive: KeepAlive, settings: ServerSettings, + ) -> HttpServiceHandler { + let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { + Some(Duration::new(val as u64, 0)) + } else { + None + }; + let settings = WorkerSettings::new(app, keep_alive, settings); + + HttpServiceHandler { + tcp_ka, + settings, + _t: PhantomData, + } + } +} + +impl Service for HttpServiceHandler +where + H: HttpHandler, + Io: IoStream, +{ + type Request = Io; + type Response = (); + type Error = (); + type Future = HttpChannel; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, mut req: Self::Request) -> Self::Future { + let _ = req.set_nodelay(true); + HttpChannel::new(self.settings.clone(), req, None) + } + + // fn shutdown(&self, force: bool) { + // if force { + // self.settings.head().traverse::(); + // } + // } +} diff --git a/src/server/settings.rs b/src/server/settings.rs index 6b2fc727..21ce2719 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -2,7 +2,7 @@ use std::cell::{RefCell, RefMut, UnsafeCell}; use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; -use std::time::{Instant, Duration}; +use std::time::{Duration, Instant}; use std::{env, fmt, net}; use bytes::BytesMut; @@ -12,8 +12,8 @@ use http::StatusCode; use lazycell::LazyCell; use parking_lot::Mutex; use time; -use tokio_timer::{sleep, Delay}; use tokio_current_thread::spawn; +use tokio_timer::{sleep, Delay}; use super::channel::Node; use super::message::{Request, RequestPool}; @@ -183,9 +183,7 @@ impl WorkerSettings { pub fn keep_alive_timer(&self) -> Option { let ka = self.0.keep_alive; if ka != 0 { - Some(Delay::new( - Instant::now() + Duration::from_secs(ka), - )) + Some(Delay::new(Instant::now() + Duration::from_secs(ka))) } else { None } diff --git a/tests/test_server.rs b/tests/test_server.rs index 41f4bcf3..c1dbf531 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -10,9 +10,9 @@ extern crate http as modhttp; extern crate rand; extern crate tokio; extern crate tokio_current_thread; +extern crate tokio_current_thread as current_thread; extern crate tokio_reactor; extern crate tokio_tcp; -extern crate tokio_current_thread as current_thread; use std::io::{Read, Write}; use std::sync::Arc; From b6a1cfa6ad4534c61da1646b7059785703ff234c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 26 Sep 2018 22:14:14 -0700 Subject: [PATCH 0668/1635] update openssl support --- src/server/builder.rs | 2 ++ src/server/h1.rs | 2 -- src/server/http.rs | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/server/builder.rs b/src/server/builder.rs index 4a77bcd5..ad412444 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -98,6 +98,7 @@ where } } +/// This trait indicates types that can create acceptor service for http server. pub trait AcceptorServiceFactory: Send + Clone + 'static { type Io: IoStream + Send; type NewService: NewService< @@ -217,6 +218,7 @@ where } #[derive(Clone)] +/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream` pub(crate) struct DefaultAcceptor; impl AcceptorServiceFactory for DefaultAcceptor { diff --git a/src/server/h1.rs b/src/server/h1.rs index 36d40e8d..b6b576ed 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -513,9 +513,7 @@ mod tests { use std::{cmp, io, time}; use bytes::{Buf, Bytes, BytesMut}; - use futures::future; use http::{Method, Version}; - use tokio::runtime::current_thread; use tokio_io::{AsyncRead, AsyncWrite}; use super::*; diff --git a/src/server/http.rs b/src/server/http.rs index f54900fc..3baf8a23 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -268,6 +268,7 @@ where mut self, lst: net::TcpListener, builder: SslAcceptorBuilder, ) -> io::Result { use super::{openssl_acceptor_with_flags, ServerFlags}; + use actix_net::service::NewServiceExt; let flags = if self.no_http2 { ServerFlags::HTTP1 @@ -283,6 +284,7 @@ where addr, scheme: "https", handler: Box::new(HttpServiceBuilder::new( + self.factory.clone(), move || ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()), DefaultPipelineFactory::new( self.factory.clone(), @@ -411,6 +413,7 @@ where S: net::ToSocketAddrs, { use super::{openssl_acceptor_with_flags, ServerFlags}; + use actix_net::service::NewServiceExt; let sockets = self.bind2(addr)?; @@ -431,6 +434,7 @@ where addr, scheme: "https", handler: Box::new(HttpServiceBuilder::new( + self.factory.clone(), move || ssl::OpensslAcceptor::new(accpt.clone()).map_err(|_| ()), DefaultPipelineFactory::new( self.factory.clone(), From d57579d70067e675ba47c09d52ac3bab4aa18edf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 17:15:38 -0700 Subject: [PATCH 0669/1635] refactor acceptor pipeline add client timeout --- src/server/acceptor.rs | 315 +++++++++++++++++++++++++++++++++++++++++ src/server/builder.rs | 214 +++++++++++----------------- src/server/channel.rs | 2 +- src/server/http.rs | 51 ++++--- src/server/mod.rs | 4 + src/server/service.rs | 77 +++------- src/server/settings.rs | 31 ++-- 7 files changed, 474 insertions(+), 220 deletions(-) create mode 100644 src/server/acceptor.rs diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs new file mode 100644 index 00000000..d7847416 --- /dev/null +++ b/src/server/acceptor.rs @@ -0,0 +1,315 @@ +use std::time::Duration; + +use actix_net::server::ServerMessage; +use actix_net::service::{NewService, Service}; +use futures::future::{err, ok, Either, FutureResult}; +use futures::{Async, Future, Poll}; +use tokio_reactor::Handle; +use tokio_tcp::TcpStream; +use tokio_timer::{sleep, Delay}; + +use super::handler::HttpHandler; +use super::settings::WorkerSettings; +use super::IoStream; + +/// This trait indicates types that can create acceptor service for http server. +pub trait AcceptorServiceFactory: Send + Clone + 'static { + type Io: IoStream + Send; + type NewService: NewService< + Request = TcpStream, + Response = Self::Io, + Error = (), + InitError = (), + >; + + fn create(&self) -> Self::NewService; +} + +impl AcceptorServiceFactory for F +where + F: Fn() -> T + Send + Clone + 'static, + T::Response: IoStream + Send, + T: NewService, +{ + type Io = T::Response; + type NewService = T; + + fn create(&self) -> T { + (self)() + } +} + +#[derive(Clone)] +/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream` +pub(crate) struct DefaultAcceptor; + +impl AcceptorServiceFactory for DefaultAcceptor { + type Io = TcpStream; + type NewService = DefaultAcceptor; + + fn create(&self) -> Self::NewService { + DefaultAcceptor + } +} + +impl NewService for DefaultAcceptor { + type Request = TcpStream; + type Response = TcpStream; + type Error = (); + type InitError = (); + type Service = DefaultAcceptor; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(DefaultAcceptor) + } +} + +impl Service for DefaultAcceptor { + type Request = TcpStream; + type Response = TcpStream; + type Error = (); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + ok(req) + } +} + +pub(crate) struct TcpAcceptor { + inner: T, + settings: WorkerSettings, +} + +impl TcpAcceptor +where + H: HttpHandler, + T: NewService, +{ + pub(crate) fn new(settings: WorkerSettings, inner: T) -> Self { + TcpAcceptor { inner, settings } + } +} + +impl NewService for TcpAcceptor +where + H: HttpHandler, + T: NewService, +{ + type Request = ServerMessage; + type Response = (); + type Error = (); + type InitError = (); + type Service = TcpAcceptorService; + type Future = TcpAcceptorResponse; + + fn new_service(&self) -> Self::Future { + TcpAcceptorResponse { + fut: self.inner.new_service(), + settings: self.settings.clone(), + } + } +} + +pub(crate) struct TcpAcceptorResponse +where + H: HttpHandler, + T: NewService, +{ + fut: T::Future, + settings: WorkerSettings, +} + +impl Future for TcpAcceptorResponse +where + H: HttpHandler, + T: NewService, +{ + type Item = TcpAcceptorService; + type Error = (); + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Err(_) => Err(()), + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(service)) => Ok(Async::Ready(TcpAcceptorService { + inner: service, + settings: self.settings.clone(), + })), + } + } +} + +pub(crate) struct TcpAcceptorService { + inner: T, + settings: WorkerSettings, +} + +impl Service for TcpAcceptorService +where + H: HttpHandler, + T: Service, +{ + type Request = ServerMessage; + type Response = (); + type Error = (); + type Future = Either, FutureResult<(), ()>>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.inner.poll_ready().map_err(|_| ()) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + match req { + ServerMessage::Connect(stream) => { + let stream = + TcpStream::from_std(stream, &Handle::default()).map_err(|e| { + error!("Can not convert to an async tcp stream: {}", e); + }); + + if let Ok(stream) = stream { + Either::A(TcpAcceptorServiceFut { + fut: self.inner.call(stream), + }) + } else { + Either::B(err(())) + } + } + ServerMessage::Shutdown(timeout) => Either::B(ok(())), + ServerMessage::ForceShutdown => { + // self.settings.head().traverse::(); + Either::B(ok(())) + } + } + } +} + +pub(crate) struct TcpAcceptorServiceFut { + fut: T, +} + +impl Future for TcpAcceptorServiceFut +where + T: Future, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Err(_) => Err(()), + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(_)) => Ok(Async::Ready(())), + } + } +} + +/// Errors produced by `AcceptorTimeout` service. +#[derive(Debug)] +pub enum TimeoutError { + /// The inner service error + Service(T), + + /// The request did not complete within the specified timeout. + Timeout, +} + +/// Acceptor timeout middleware +/// +/// Applies timeout to request prcoessing. +pub(crate) struct AcceptorTimeout { + inner: T, + timeout: usize, +} + +impl AcceptorTimeout { + pub(crate) fn new(timeout: usize, inner: T) -> Self { + Self { inner, timeout } + } +} + +impl NewService for AcceptorTimeout { + type Request = T::Request; + type Response = T::Response; + type Error = TimeoutError; + type InitError = T::InitError; + type Service = AcceptorTimeoutService; + type Future = AcceptorTimeoutFut; + + fn new_service(&self) -> Self::Future { + AcceptorTimeoutFut { + fut: self.inner.new_service(), + timeout: self.timeout, + } + } +} + +#[doc(hidden)] +pub(crate) struct AcceptorTimeoutFut { + fut: T::Future, + timeout: usize, +} + +impl Future for AcceptorTimeoutFut { + type Item = AcceptorTimeoutService; + type Error = T::InitError; + + fn poll(&mut self) -> Poll { + let inner = try_ready!(self.fut.poll()); + Ok(Async::Ready(AcceptorTimeoutService { + inner, + timeout: self.timeout as u64, + })) + } +} + +/// Acceptor timeout service +/// +/// Applies timeout to request prcoessing. +pub(crate) struct AcceptorTimeoutService { + inner: T, + timeout: u64, +} + +impl Service for AcceptorTimeoutService { + type Request = T::Request; + type Response = T::Response; + type Error = TimeoutError; + type Future = AcceptorTimeoutResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.inner.poll_ready().map_err(TimeoutError::Service) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + AcceptorTimeoutResponse { + fut: self.inner.call(req), + sleep: sleep(Duration::from_millis(self.timeout)), + } + } +} + +pub(crate) struct AcceptorTimeoutResponse { + fut: T::Future, + sleep: Delay, +} +impl Future for AcceptorTimeoutResponse { + type Item = T::Response; + type Error = TimeoutError; + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Ok(Async::NotReady) => match self.sleep.poll() { + Err(_) => Err(TimeoutError::Timeout), + Ok(Async::Ready(_)) => Err(TimeoutError::Timeout), + Ok(Async::NotReady) => Ok(Async::NotReady), + }, + Ok(Async::Ready(resp)) => Ok(Async::Ready(resp)), + Err(err) => Err(TimeoutError::Service(err)), + } + } +} diff --git a/src/server/builder.rs b/src/server/builder.rs index ad412444..98a2d502 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -1,21 +1,24 @@ use std::marker::PhantomData; use std::net; +use actix_net::either::Either; use actix_net::server; -use actix_net::service::{NewService, NewServiceExt, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Poll}; -use tokio_tcp::TcpStream; +use actix_net::service::{NewService, NewServiceExt}; -use super::handler::IntoHttpHandler; +use super::acceptor::{AcceptorServiceFactory, AcceptorTimeout, TcpAcceptor}; +use super::handler::{HttpHandler, IntoHttpHandler}; use super::service::HttpService; +use super::settings::{ServerSettings, WorkerSettings}; use super::{IoStream, KeepAlive}; pub(crate) trait ServiceFactory where H: IntoHttpHandler, { - fn register(&self, server: server::Server, lst: net::TcpListener) -> server::Server; + fn register( + &self, server: server::Server, lst: net::TcpListener, host: Option, + addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, + ) -> server::Server; } pub struct HttpServiceBuilder @@ -29,11 +32,12 @@ where impl HttpServiceBuilder where - F: Fn() -> H + Send + Clone, + F: Fn() -> H + Send + Clone + 'static, H: IntoHttpHandler, A: AcceptorServiceFactory, - P: HttpPipelineFactory, + P: HttpPipelineFactory, { + /// Create http service builder pub fn new(factory: F, acceptor: A, pipeline: P) -> Self { Self { factory, @@ -42,6 +46,7 @@ where } } + /// Use different acceptor factory pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder where A1: AcceptorServiceFactory, @@ -53,9 +58,10 @@ where } } + /// Use different pipeline factory pub fn pipeline(self, pipeline: P1) -> HttpServiceBuilder where - P1: HttpPipelineFactory, + P1: HttpPipelineFactory, { HttpServiceBuilder { pipeline, @@ -64,18 +70,45 @@ where } } - fn finish(&self) -> impl server::StreamServiceFactory { + fn finish( + &self, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, + client_timeout: usize, + ) -> impl server::ServiceFactory { + let factory = self.factory.clone(); let pipeline = self.pipeline.clone(); let acceptor = self.acceptor.clone(); - move || acceptor.create().and_then(pipeline.create()) + move || { + let app = (factory)().into_handler(); + let settings = WorkerSettings::new( + app, + keep_alive, + client_timeout as u64, + ServerSettings::new(Some(addr), &host, false), + ); + + if client_timeout == 0 { + Either::A(TcpAcceptor::new( + settings.clone(), + acceptor.create().and_then(pipeline.create(settings)), + )) + } else { + Either::B(TcpAcceptor::new( + settings.clone(), + AcceptorTimeout::new(client_timeout, acceptor.create()) + .map_err(|_| ()) + .and_then(pipeline.create(settings)), + )) + } + } } } impl Clone for HttpServiceBuilder where F: Fn() -> H + Send + Clone, + H: IntoHttpHandler, A: AcceptorServiceFactory, - P: HttpPipelineFactory, + P: HttpPipelineFactory, { fn clone(&self) -> Self { HttpServiceBuilder { @@ -88,44 +121,24 @@ where impl ServiceFactory for HttpServiceBuilder where - F: Fn() -> H + Send + Clone, + F: Fn() -> H + Send + Clone + 'static, A: AcceptorServiceFactory, - P: HttpPipelineFactory, + P: HttpPipelineFactory, H: IntoHttpHandler, { - fn register(&self, server: server::Server, lst: net::TcpListener) -> server::Server { - server.listen("actix-web", lst, self.finish()) + fn register( + &self, server: server::Server, lst: net::TcpListener, host: Option, + addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, + ) -> server::Server { + server.listen2( + "actix-web", + lst, + self.finish(host, addr, keep_alive, client_timeout), + ) } } -/// This trait indicates types that can create acceptor service for http server. -pub trait AcceptorServiceFactory: Send + Clone + 'static { - type Io: IoStream + Send; - type NewService: NewService< - Request = TcpStream, - Response = Self::Io, - Error = (), - InitError = (), - >; - - fn create(&self) -> Self::NewService; -} - -impl AcceptorServiceFactory for F -where - F: Fn() -> T + Send + Clone + 'static, - T::Response: IoStream + Send, - T: NewService, -{ - type Io = T::Response; - type NewService = T; - - fn create(&self) -> T { - (self)() - } -} - -pub trait HttpPipelineFactory: Send + Clone + 'static { +pub trait HttpPipelineFactory: Send + Clone + 'static { type Io: IoStream; type NewService: NewService< Request = Self::Io, @@ -134,126 +147,59 @@ pub trait HttpPipelineFactory: Send + Clone + 'static { InitError = (), >; - fn create(&self) -> Self::NewService; + fn create(&self, settings: WorkerSettings) -> Self::NewService; } -impl HttpPipelineFactory for F +impl HttpPipelineFactory for F where - F: Fn() -> T + Send + Clone + 'static, + F: Fn(WorkerSettings) -> T + Send + Clone + 'static, T: NewService, T::Request: IoStream, + H: HttpHandler, { type Io = T::Request; type NewService = T; - fn create(&self) -> T { - (self)() + fn create(&self, settings: WorkerSettings) -> T { + (self)(settings) } } -pub(crate) struct DefaultPipelineFactory -where - F: Fn() -> H + Send + Clone, -{ - factory: F, - host: Option, - addr: net::SocketAddr, - keep_alive: KeepAlive, - _t: PhantomData, +pub(crate) struct DefaultPipelineFactory { + _t: PhantomData<(H, Io)>, } -impl DefaultPipelineFactory +unsafe impl Send for DefaultPipelineFactory {} + +impl DefaultPipelineFactory where Io: IoStream + Send, - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, + H: HttpHandler + 'static, { - pub fn new( - factory: F, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, - ) -> Self { - Self { - factory, - addr, - keep_alive, - host, - _t: PhantomData, - } + pub fn new() -> Self { + Self { _t: PhantomData } } } -impl Clone for DefaultPipelineFactory +impl Clone for DefaultPipelineFactory where Io: IoStream, - F: Fn() -> H + Send + Clone, - H: IntoHttpHandler, + H: HttpHandler, { fn clone(&self) -> Self { - Self { - factory: self.factory.clone(), - addr: self.addr, - keep_alive: self.keep_alive, - host: self.host.clone(), - _t: PhantomData, - } + Self { _t: PhantomData } } } -impl HttpPipelineFactory for DefaultPipelineFactory +impl HttpPipelineFactory for DefaultPipelineFactory where - Io: IoStream + Send, - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, + Io: IoStream, + H: HttpHandler + 'static, { type Io = Io; - type NewService = HttpService; + type NewService = HttpService; - fn create(&self) -> Self::NewService { - HttpService::new( - self.factory.clone(), - self.addr, - self.host.clone(), - self.keep_alive, - ) - } -} - -#[derive(Clone)] -/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream` -pub(crate) struct DefaultAcceptor; - -impl AcceptorServiceFactory for DefaultAcceptor { - type Io = TcpStream; - type NewService = DefaultAcceptor; - - fn create(&self) -> Self::NewService { - DefaultAcceptor - } -} - -impl NewService for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type InitError = (); - type Service = DefaultAcceptor; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(DefaultAcceptor) - } -} - -impl Service for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - ok(req) + fn create(&self, settings: WorkerSettings) -> Self::NewService { + HttpService::new(settings) } } diff --git a/src/server/channel.rs b/src/server/channel.rs index 6d0992bc..c1e6b6b2 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -41,7 +41,7 @@ where pub(crate) fn new( settings: WorkerSettings, io: T, peer: Option, ) -> HttpChannel { - let ka_timeout = settings.keep_alive_timer(); + let ka_timeout = settings.client_timer(); HttpChannel { ka_timeout, diff --git a/src/server/http.rs b/src/server/http.rs index 3baf8a23..0fe14221 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -18,8 +18,9 @@ use openssl::ssl::SslAcceptorBuilder; //#[cfg(feature = "rust-tls")] //use rustls::ServerConfig; -use super::builder::{AcceptorServiceFactory, HttpServiceBuilder, ServiceFactory}; -use super::builder::{DefaultAcceptor, DefaultPipelineFactory}; +use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; +use super::builder::DefaultPipelineFactory; +use super::builder::{HttpServiceBuilder, ServiceFactory}; use super::{IntoHttpHandler, IoStream, KeepAlive}; struct Socket { @@ -50,6 +51,7 @@ where no_signals: bool, maxconn: usize, maxconnrate: usize, + client_timeout: usize, sockets: Vec>, } @@ -72,6 +74,7 @@ where no_signals: false, maxconn: 25_600, maxconnrate: 256, + client_timeout: 5000, sockets: Vec::new(), } } @@ -130,6 +133,20 @@ where self } + /// Set server client timneout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: usize) -> Self { + self.client_timeout = val; + self + } + /// Set server host name. /// /// Host name is used by application router aa a hostname for url @@ -205,12 +222,7 @@ where handler: Box::new(HttpServiceBuilder::new( self.factory.clone(), DefaultAcceptor, - DefaultPipelineFactory::new( - self.factory.clone(), - self.host.clone(), - addr, - self.keep_alive, - ), + DefaultPipelineFactory::new(), )), }); @@ -237,12 +249,7 @@ where handler: Box::new(HttpServiceBuilder::new( self.factory.clone(), acceptor, - DefaultPipelineFactory::new( - self.factory.clone(), - self.host.clone(), - addr, - self.keep_alive, - ), + DefaultPipelineFactory::new(), )), }); @@ -347,12 +354,7 @@ where handler: Box::new(HttpServiceBuilder::new( self.factory.clone(), acceptor.clone(), - DefaultPipelineFactory::new( - self.factory.clone(), - self.host.clone(), - addr, - self.keep_alive, - ), + DefaultPipelineFactory::new(), )), }); } @@ -513,7 +515,14 @@ impl H + Send + Clone> HttpServer { let sockets = mem::replace(&mut self.sockets, Vec::new()); for socket in sockets { - srv = socket.handler.register(srv, socket.lst); + srv = socket.handler.register( + srv, + socket.lst, + self.host.clone(), + socket.addr, + self.keep_alive.clone(), + self.client_timeout, + ); } srv.start() } diff --git a/src/server/mod.rs b/src/server/mod.rs index ac4ffc9a..9e91eda0 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -117,6 +117,7 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; +pub(crate) mod acceptor; pub(crate) mod builder; mod channel; mod error; @@ -144,6 +145,9 @@ pub use self::ssl::*; #[doc(hidden)] pub use self::helpers::write_content_length; +#[doc(hidden)] +pub use self::builder::HttpServiceBuilder; + use body::Binary; use extensions::Extensions; use header::ContentEncoding; diff --git a/src/server/service.rs b/src/server/service.rs index 6f80cd6d..042c86ed 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -1,75 +1,50 @@ use std::marker::PhantomData; -use std::net; -use std::time::Duration; use actix_net::service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, Poll}; use super::channel::HttpChannel; -use super::handler::{HttpHandler, IntoHttpHandler}; -use super::settings::{ServerSettings, WorkerSettings}; -use super::{IoStream, KeepAlive}; +use super::handler::HttpHandler; +use super::settings::WorkerSettings; +use super::IoStream; -pub enum HttpServiceMessage { - /// New stream - Connect(T), - /// Gracefull shutdown - Shutdown(Duration), - /// Force shutdown - ForceShutdown, -} - -pub(crate) struct HttpService +pub(crate) struct HttpService where - F: Fn() -> H, - H: IntoHttpHandler, + H: HttpHandler, Io: IoStream, { - factory: F, - addr: net::SocketAddr, - host: Option, - keep_alive: KeepAlive, + settings: WorkerSettings, _t: PhantomData, } -impl HttpService +impl HttpService where - F: Fn() -> H, - H: IntoHttpHandler, + H: HttpHandler, Io: IoStream, { - pub fn new( - factory: F, addr: net::SocketAddr, host: Option, keep_alive: KeepAlive, - ) -> Self { + pub fn new(settings: WorkerSettings) -> Self { HttpService { - factory, - addr, - host, - keep_alive, + settings, _t: PhantomData, } } } -impl NewService for HttpService +impl NewService for HttpService where - F: Fn() -> H, - H: IntoHttpHandler, + H: HttpHandler, Io: IoStream, { type Request = Io; type Response = (); type Error = (); type InitError = (); - type Service = HttpServiceHandler; + type Service = HttpServiceHandler; type Future = FutureResult; fn new_service(&self) -> Self::Future { - let s = ServerSettings::new(Some(self.addr), &self.host, false); - let app = (self.factory)().into_handler(); - - ok(HttpServiceHandler::new(app, self.keep_alive, s)) + ok(HttpServiceHandler::new(self.settings.clone())) } } @@ -79,7 +54,7 @@ where Io: IoStream, { settings: WorkerSettings, - tcp_ka: Option, + // tcp_ka: Option, _t: PhantomData, } @@ -88,18 +63,14 @@ where H: HttpHandler, Io: IoStream, { - fn new( - app: H, keep_alive: KeepAlive, settings: ServerSettings, - ) -> HttpServiceHandler { - let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { - Some(Duration::new(val as u64, 0)) - } else { - None - }; - let settings = WorkerSettings::new(app, keep_alive, settings); + fn new(settings: WorkerSettings) -> HttpServiceHandler { + // let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { + // Some(Duration::new(val as u64, 0)) + // } else { + // None + // }; HttpServiceHandler { - tcp_ka, settings, _t: PhantomData, } @@ -124,10 +95,4 @@ where let _ = req.set_nodelay(true); HttpChannel::new(self.settings.clone(), req, None) } - - // fn shutdown(&self, force: bool) { - // if force { - // self.settings.head().traverse::(); - // } - // } } diff --git a/src/server/settings.rs b/src/server/settings.rs index 21ce2719..fe564c5b 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -133,11 +133,12 @@ impl ServerSettings { // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; -pub(crate) struct WorkerSettings(Rc>); +pub struct WorkerSettings(Rc>); struct Inner { handler: H, keep_alive: u64, + client_timeout: u64, ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, @@ -153,7 +154,7 @@ impl Clone for WorkerSettings { impl WorkerSettings { pub(crate) fn new( - handler: H, keep_alive: KeepAlive, settings: ServerSettings, + handler: H, keep_alive: KeepAlive, client_timeout: u64, settings: ServerSettings, ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -165,6 +166,7 @@ impl WorkerSettings { handler, keep_alive, ka_enabled, + client_timeout, bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), node: RefCell::new(Node::head()), @@ -172,14 +174,15 @@ impl WorkerSettings { })) } - pub fn head(&self) -> RefMut> { + pub(crate) fn head(&self) -> RefMut> { self.0.node.borrow_mut() } - pub fn handler(&self) -> &H { + pub(crate) fn handler(&self) -> &H { &self.0.handler } + #[inline] pub fn keep_alive_timer(&self) -> Option { let ka = self.0.keep_alive; if ka != 0 { @@ -189,23 +192,35 @@ impl WorkerSettings { } } + #[inline] pub fn keep_alive(&self) -> u64 { self.0.keep_alive } + #[inline] pub fn keep_alive_enabled(&self) -> bool { self.0.ka_enabled } - pub fn get_bytes(&self) -> BytesMut { + #[inline] + pub fn client_timer(&self) -> Option { + let delay = self.0.client_timeout; + if delay != 0 { + Some(Delay::new(Instant::now() + Duration::from_millis(delay))) + } else { + None + } + } + + pub(crate) fn get_bytes(&self) -> BytesMut { self.0.bytes.get_bytes() } - pub fn release_bytes(&self, bytes: BytesMut) { + pub(crate) fn release_bytes(&self, bytes: BytesMut) { self.0.bytes.release_bytes(bytes) } - pub fn get_request(&self) -> Request { + pub(crate) fn get_request(&self) -> Request { RequestPool::get(self.0.messages) } @@ -216,7 +231,7 @@ impl WorkerSettings { } impl WorkerSettings { - pub fn set_date(&self, dst: &mut BytesMut, full: bool) { + pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) { // Unsafe: WorkerSetting is !Sync and !Send let date_bytes = unsafe { let date = &mut (*self.0.date.get()); From 85445ea8096e8e1edf018241cfb300c51ef19628 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 17:21:28 -0700 Subject: [PATCH 0670/1635] rename and simplify ServiceFactory trait --- src/server/builder.rs | 19 ++++++++----------- src/server/h1.rs | 1 + src/server/http.rs | 8 ++++---- src/server/settings.rs | 8 ++++++-- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/server/builder.rs b/src/server/builder.rs index 98a2d502..5af9d0c8 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use std::net; use actix_net::either::Either; -use actix_net::server; +use actix_net::server::{Server, ServiceFactory}; use actix_net::service::{NewService, NewServiceExt}; use super::acceptor::{AcceptorServiceFactory, AcceptorTimeout, TcpAcceptor}; @@ -11,14 +11,11 @@ use super::service::HttpService; use super::settings::{ServerSettings, WorkerSettings}; use super::{IoStream, KeepAlive}; -pub(crate) trait ServiceFactory -where - H: IntoHttpHandler, -{ +pub(crate) trait ServiceProvider { fn register( - &self, server: server::Server, lst: net::TcpListener, host: Option, + &self, server: Server, lst: net::TcpListener, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, - ) -> server::Server; + ) -> Server; } pub struct HttpServiceBuilder @@ -73,7 +70,7 @@ where fn finish( &self, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, - ) -> impl server::ServiceFactory { + ) -> impl ServiceFactory { let factory = self.factory.clone(); let pipeline = self.pipeline.clone(); let acceptor = self.acceptor.clone(); @@ -119,7 +116,7 @@ where } } -impl ServiceFactory for HttpServiceBuilder +impl ServiceProvider for HttpServiceBuilder where F: Fn() -> H + Send + Clone + 'static, A: AcceptorServiceFactory, @@ -127,9 +124,9 @@ where H: IntoHttpHandler, { fn register( - &self, server: server::Server, lst: net::TcpListener, host: Option, + &self, server: Server, lst: net::TcpListener, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, - ) -> server::Server { + ) -> Server { server.listen2( "actix-web", lst, diff --git a/src/server/h1.rs b/src/server/h1.rs index b6b576ed..b5ee93e6 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -528,6 +528,7 @@ mod tests { WorkerSettings::::new( App::new().into_handler(), KeepAlive::Os, + 5000, ServerSettings::default(), ) } diff --git a/src/server/http.rs b/src/server/http.rs index 0fe14221..49ae4f28 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -20,14 +20,14 @@ use openssl::ssl::SslAcceptorBuilder; use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; use super::builder::DefaultPipelineFactory; -use super::builder::{HttpServiceBuilder, ServiceFactory}; +use super::builder::{HttpServiceBuilder, ServiceProvider}; use super::{IntoHttpHandler, IoStream, KeepAlive}; -struct Socket { +struct Socket { scheme: &'static str, lst: net::TcpListener, addr: net::SocketAddr, - handler: Box>, + handler: Box, } /// An HTTP Server @@ -52,7 +52,7 @@ where maxconn: usize, maxconnrate: usize, client_timeout: usize, - sockets: Vec>, + sockets: Vec, } impl HttpServer diff --git a/src/server/settings.rs b/src/server/settings.rs index fe564c5b..db5f6c57 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -330,8 +330,12 @@ mod tests { let mut rt = current_thread::Runtime::new().unwrap(); let _ = rt.block_on(future::lazy(|| { - let settings = - WorkerSettings::<()>::new((), KeepAlive::Os, ServerSettings::default()); + let settings = WorkerSettings::<()>::new( + (), + KeepAlive::Os, + 0, + ServerSettings::default(), + ); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf1, true); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); From 3173c9fa830b71999424028bed4ccd4e19680cb4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 19:34:07 -0700 Subject: [PATCH 0671/1635] diesable client timeout for tcp stream acceptor --- src/server/builder.rs | 22 +++++++++++++++++++--- src/server/http.rs | 12 +++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/server/builder.rs b/src/server/builder.rs index 5af9d0c8..28541820 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -18,6 +18,7 @@ pub(crate) trait ServiceProvider { ) -> Server; } +/// Utility type that builds complete http pipeline pub struct HttpServiceBuilder where F: Fn() -> H + Send + Clone, @@ -25,6 +26,7 @@ where factory: F, acceptor: A, pipeline: P, + no_client_timer: bool, } impl HttpServiceBuilder @@ -40,9 +42,15 @@ where factory, pipeline, acceptor, + no_client_timer: false, } } + pub(crate) fn no_client_timer(mut self) -> Self { + self.no_client_timer = true; + self + } + /// Use different acceptor factory pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder where @@ -52,6 +60,7 @@ where acceptor, pipeline: self.pipeline, factory: self.factory.clone(), + no_client_timer: self.no_client_timer, } } @@ -64,6 +73,7 @@ where pipeline, acceptor: self.acceptor, factory: self.factory.clone(), + no_client_timer: self.no_client_timer, } } @@ -71,6 +81,11 @@ where &self, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, ) -> impl ServiceFactory { + let timeout = if self.no_client_timer { + 0 + } else { + client_timeout + }; let factory = self.factory.clone(); let pipeline = self.pipeline.clone(); let acceptor = self.acceptor.clone(); @@ -79,11 +94,11 @@ where let settings = WorkerSettings::new( app, keep_alive, - client_timeout as u64, + timeout as u64, ServerSettings::new(Some(addr), &host, false), ); - if client_timeout == 0 { + if timeout == 0 { Either::A(TcpAcceptor::new( settings.clone(), acceptor.create().and_then(pipeline.create(settings)), @@ -91,7 +106,7 @@ where } else { Either::B(TcpAcceptor::new( settings.clone(), - AcceptorTimeout::new(client_timeout, acceptor.create()) + AcceptorTimeout::new(timeout, acceptor.create()) .map_err(|_| ()) .and_then(pipeline.create(settings)), )) @@ -112,6 +127,7 @@ where factory: self.factory.clone(), acceptor: self.acceptor.clone(), pipeline: self.pipeline.clone(), + no_client_timer: self.no_client_timer, } } } diff --git a/src/server/http.rs b/src/server/http.rs index 49ae4f28..6d37473c 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -219,11 +219,13 @@ where lst, addr, scheme: "http", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - DefaultAcceptor, - DefaultPipelineFactory::new(), - )), + handler: Box::new( + HttpServiceBuilder::new( + self.factory.clone(), + DefaultAcceptor, + DefaultPipelineFactory::new(), + ).no_client_timer(), + ), }); self From 0bca21ec6dd4205e5476b7eaf2c282a22f063300 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 19:57:40 -0700 Subject: [PATCH 0672/1635] fix ssl tests --- src/server/http.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/server/http.rs b/src/server/http.rs index 6d37473c..263fd40a 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -295,12 +295,7 @@ where handler: Box::new(HttpServiceBuilder::new( self.factory.clone(), move || ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()), - DefaultPipelineFactory::new( - self.factory.clone(), - self.host.clone(), - addr, - self.keep_alive, - ), + DefaultPipelineFactory::new(), )), }); @@ -440,12 +435,7 @@ where handler: Box::new(HttpServiceBuilder::new( self.factory.clone(), move || ssl::OpensslAcceptor::new(accpt.clone()).map_err(|_| ()), - DefaultPipelineFactory::new( - self.factory.clone(), - self.host.clone(), - addr, - self.keep_alive, - ), + DefaultPipelineFactory::new(), )), }); } From ecfda64f6d5b433e8ba11c918c579bac755b6927 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 20:39:37 -0700 Subject: [PATCH 0673/1635] add native-tls support --- .travis.yml | 4 +- Cargo.toml | 4 +- src/server/http.rs | 35 ++++++---- src/server/ssl/mod.rs | 6 +- src/server/ssl/nativetls.rs | 123 +----------------------------------- 5 files changed, 31 insertions(+), 141 deletions(-) diff --git a/.travis.yml b/.travis.yml index e2d70678..497f7bbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,12 +32,12 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then cargo clean - cargo test --features="ssl" -- --nocapture + cargo test --features="ssl,tls" -- --nocapture fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - cargo tarpaulin --features="ssl" --out Xml --no-count + cargo tarpaulin --features="ssl,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi diff --git a/Cargo.toml b/Cargo.toml index e17b7283..205e178b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ path = "src/lib.rs" default = ["session", "brotli", "flate2-c"] # tls -tls = ["native-tls", "tokio-tls"] +tls = ["native-tls", "tokio-tls", "actix-net/tls"] # openssl ssl = ["openssl", "tokio-openssl", "actix-net/ssl"] @@ -41,7 +41,7 @@ ssl = ["openssl", "tokio-openssl", "actix-net/ssl"] alpn = ["openssl", "tokio-openssl", "actix-net/ssl"] # rustls -rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"] +rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"] # unix sockets uds = ["tokio-uds"] diff --git a/src/server/http.rs b/src/server/http.rs index 263fd40a..1cc89981 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -9,8 +9,8 @@ use net2::TcpBuilder; use num_cpus; use tokio_tcp::TcpStream; -//#[cfg(feature = "tls")] -//use native_tls::TlsAcceptor; +#[cfg(feature = "tls")] +use native_tls::TlsAcceptor; #[cfg(any(feature = "alpn", feature = "ssl"))] use openssl::ssl::SslAcceptorBuilder; @@ -258,16 +258,27 @@ where self } - // #[cfg(feature = "tls")] - // /// Use listener for accepting incoming tls connection requests - // /// - // /// HttpServer does not change any configuration for TcpListener, - // /// it needs to be configured before passing it to listen() method. - // pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { - // use super::NativeTlsAcceptor; - // - // self.listen_with(lst, NativeTlsAcceptor::new(acceptor)) - // } + #[cfg(feature = "tls")] + /// Use listener for accepting incoming tls connection requests + /// + /// HttpServer does not change any configuration for TcpListener, + /// it needs to be configured before passing it to listen() method. + pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { + use actix_net::service::NewServiceExt; + + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + lst, + addr, + scheme: "https", + handler: Box::new(HttpServiceBuilder::new( + self.factory.clone(), + move || ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()), + DefaultPipelineFactory::new(), + )), + }); + self + } #[cfg(any(feature = "alpn", feature = "ssl"))] /// Use listener for accepting incoming tls connection requests diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs index 7101de78..7302cf0b 100644 --- a/src/server/ssl/mod.rs +++ b/src/server/ssl/mod.rs @@ -3,10 +3,8 @@ mod openssl; #[cfg(any(feature = "alpn", feature = "ssl"))] pub use self::openssl::*; -//#[cfg(feature = "tls")] -//mod nativetls; -//#[cfg(feature = "tls")] -//pub use self::nativetls::{NativeTlsAcceptor, TlsStream}; +#[cfg(feature = "tls")] +mod nativetls; //#[cfg(feature = "rust-tls")] //mod rustls; diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs index e35f12d2..d59948c7 100644 --- a/src/server/ssl/nativetls.rs +++ b/src/server/ssl/nativetls.rs @@ -1,61 +1,9 @@ use std::net::Shutdown; use std::{io, time}; -use futures::{Async, Future, Poll}; -use native_tls::{self, HandshakeError, TlsAcceptor}; -use tokio_io::{AsyncRead, AsyncWrite}; +use actix_net::ssl::TlsStream; -use server::{AcceptorService, IoStream}; - -#[derive(Clone)] -/// Support `SSL` connections via native-tls package -/// -/// `tls` feature enables `NativeTlsAcceptor` type -pub struct NativeTlsAcceptor { - acceptor: TlsAcceptor, -} - -/// A wrapper around an underlying raw stream which implements the TLS or SSL -/// protocol. -/// -/// A `TlsStream` represents a handshake that has been completed successfully -/// and both the server and the client are ready for receiving and sending -/// data. Bytes read from a `TlsStream` are decrypted from `S` and bytes written -/// to a `TlsStream` are encrypted when passing through to `S`. -#[derive(Debug)] -pub struct TlsStream { - inner: native_tls::TlsStream, -} - -/// Future returned from `NativeTlsAcceptor::accept` which will resolve -/// once the accept handshake has finished. -pub struct Accept { - inner: Option, HandshakeError>>, -} - -impl NativeTlsAcceptor { - /// Create `NativeTlsAcceptor` instance - pub fn new(acceptor: TlsAcceptor) -> Self { - NativeTlsAcceptor { - acceptor: acceptor.into(), - } - } -} - -impl AcceptorService for NativeTlsAcceptor { - type Accepted = TlsStream; - type Future = Accept; - - fn scheme(&self) -> &'static str { - "https" - } - - fn accept(&self, io: Io) -> Self::Future { - Accept { - inner: Some(self.acceptor.accept(io)), - } - } -} +use server::IoStream; impl IoStream for TlsStream { #[inline] @@ -74,70 +22,3 @@ impl IoStream for TlsStream { self.get_mut().get_mut().set_linger(dur) } } - -impl Future for Accept { - type Item = TlsStream; - type Error = io::Error; - - fn poll(&mut self) -> Poll { - match self.inner.take().expect("cannot poll MidHandshake twice") { - Ok(stream) => Ok(TlsStream { inner: stream }.into()), - Err(HandshakeError::Failure(e)) => { - Err(io::Error::new(io::ErrorKind::Other, e)) - } - Err(HandshakeError::WouldBlock(s)) => match s.handshake() { - Ok(stream) => Ok(TlsStream { inner: stream }.into()), - Err(HandshakeError::Failure(e)) => { - Err(io::Error::new(io::ErrorKind::Other, e)) - } - Err(HandshakeError::WouldBlock(s)) => { - self.inner = Some(Err(HandshakeError::WouldBlock(s))); - Ok(Async::NotReady) - } - }, - } - } -} - -impl TlsStream { - /// Get access to the internal `native_tls::TlsStream` stream which also - /// transitively allows access to `S`. - pub fn get_ref(&self) -> &native_tls::TlsStream { - &self.inner - } - - /// Get mutable access to the internal `native_tls::TlsStream` stream which - /// also transitively allows mutable access to `S`. - pub fn get_mut(&mut self) -> &mut native_tls::TlsStream { - &mut self.inner - } -} - -impl io::Read for TlsStream { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.inner.read(buf) - } -} - -impl io::Write for TlsStream { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } -} - -impl AsyncRead for TlsStream {} - -impl AsyncWrite for TlsStream { - fn shutdown(&mut self) -> Poll<(), io::Error> { - match self.inner.shutdown() { - Ok(_) => (), - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Err(e) => return Err(e), - } - self.inner.get_mut().shutdown() - } -} From 1ff86e5ac4f2378295b1d1880c3ec759b1d4b8cc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 21:24:21 -0700 Subject: [PATCH 0674/1635] restore rust-tls support --- .travis.yml | 6 ++--- src/server/http.rs | 50 ++++++++++++++++++++++++++-------------- src/server/ssl/mod.rs | 8 +++---- src/server/ssl/rustls.rs | 43 ++++++++++------------------------ src/test.rs | 4 +--- 5 files changed, 53 insertions(+), 58 deletions(-) diff --git a/.travis.yml b/.travis.yml index 497f7bbc..0023965d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,12 +32,12 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then cargo clean - cargo test --features="ssl,tls" -- --nocapture + cargo test --features="ssl,tls,rust-tls" -- --nocapture fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - cargo tarpaulin --features="ssl,tls" --out Xml --no-count + cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi @@ -46,7 +46,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then - cargo doc --features "ssl,session" --no-deps && + cargo doc --features "ssl,tls,rust-tls,session" --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/src/server/http.rs b/src/server/http.rs index 1cc89981..6432f18f 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -15,8 +15,8 @@ use native_tls::TlsAcceptor; #[cfg(any(feature = "alpn", feature = "ssl"))] use openssl::ssl::SslAcceptorBuilder; -//#[cfg(feature = "rust-tls")] -//use rustls::ServerConfig; +#[cfg(feature = "rust-tls")] +use rustls::ServerConfig; use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; use super::builder::DefaultPipelineFactory; @@ -313,22 +313,38 @@ where Ok(self) } - // #[cfg(feature = "rust-tls")] - // /// Use listener for accepting incoming tls connection requests - // /// - // /// This method sets alpn protocols to "h2" and "http/1.1" - // pub fn listen_rustls(self, lst: net::TcpListener, builder: ServerConfig) -> Self { - // use super::{RustlsAcceptor, ServerFlags}; + #[cfg(feature = "rust-tls")] + /// Use listener for accepting incoming tls connection requests + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn listen_rustls(mut self, lst: net::TcpListener, config: ServerConfig) -> Self { + use super::{RustlsAcceptor, ServerFlags}; + use actix_net::service::NewServiceExt; - // // alpn support - // let flags = if self.no_http2 { - // ServerFlags::HTTP1 - // } else { - // ServerFlags::HTTP1 | ServerFlags::HTTP2 - // }; - // - // self.listen_with(lst, RustlsAcceptor::with_flags(builder, flags)) - // } + // alpn support + let flags = if self.no_http2 { + ServerFlags::HTTP1 + } else { + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; + + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + lst, + addr, + scheme: "https", + handler: Box::new(HttpServiceBuilder::new( + self.factory.clone(), + move || { + RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) + }, + DefaultPipelineFactory::new(), + )), + }); + + //Ok(self) + self + } /// The socket address to bind /// diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs index 7302cf0b..1d6b55b1 100644 --- a/src/server/ssl/mod.rs +++ b/src/server/ssl/mod.rs @@ -6,7 +6,7 @@ pub use self::openssl::*; #[cfg(feature = "tls")] mod nativetls; -//#[cfg(feature = "rust-tls")] -//mod rustls; -//#[cfg(feature = "rust-tls")] -//pub use self::rustls::RustlsAcceptor; +#[cfg(feature = "rust-tls")] +mod rustls; +#[cfg(feature = "rust-tls")] +pub use self::rustls::RustlsAcceptor; diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs index 6ad0a7b2..c74b62ea 100644 --- a/src/server/ssl/rustls.rs +++ b/src/server/ssl/rustls.rs @@ -1,29 +1,25 @@ use std::net::Shutdown; -use std::sync::Arc; use std::{io, time}; +use actix_net::ssl; //::RustlsAcceptor; use rustls::{ClientSession, ServerConfig, ServerSession}; -use tokio_io::AsyncWrite; -use tokio_rustls::{AcceptAsync, ServerConfigExt, TlsStream}; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_rustls::TlsStream; -use server::{AcceptorService, IoStream, ServerFlags}; +use server::{IoStream, ServerFlags}; -#[derive(Clone)] /// Support `SSL` connections via rustls package /// /// `rust-tls` feature enables `RustlsAcceptor` type -pub struct RustlsAcceptor { - config: Arc, +pub struct RustlsAcceptor { + _t: ssl::RustlsAcceptor, } -impl RustlsAcceptor { - /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. - pub fn new(config: ServerConfig) -> Self { - RustlsAcceptor::with_flags(config, ServerFlags::HTTP1 | ServerFlags::HTTP2) - } - - /// Create `OpensslAcceptor` with custom server flags. - pub fn with_flags(mut config: ServerConfig, flags: ServerFlags) -> Self { +impl RustlsAcceptor { + /// Create `RustlsAcceptor` with custom server flags. + pub fn with_flags( + mut config: ServerConfig, flags: ServerFlags, + ) -> ssl::RustlsAcceptor { let mut protos = Vec::new(); if flags.contains(ServerFlags::HTTP2) { protos.push("h2".to_string()); @@ -35,22 +31,7 @@ impl RustlsAcceptor { config.set_protocols(&protos); } - RustlsAcceptor { - config: Arc::new(config), - } - } -} - -impl AcceptorService for RustlsAcceptor { - type Accepted = TlsStream; - type Future = AcceptAsync; - - fn scheme(&self) -> &'static str { - "https" - } - - fn accept(&self, io: Io) -> Self::Future { - ServerConfigExt::accept_async(&self.config, io) + ssl::RustlsAcceptor::new(config) } } diff --git a/src/test.rs b/src/test.rs index b9d64f27..83b0b83b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -19,8 +19,6 @@ use openssl::ssl::SslAcceptorBuilder; use rustls::ServerConfig; #[cfg(feature = "alpn")] use server::OpensslAcceptor; -#[cfg(feature = "rust-tls")] -use server::RustlsAcceptor; use application::{App, HttpApplication}; use body::Binary; @@ -350,7 +348,7 @@ where let ssl = self.rust_ssl.take(); if let Some(ssl) = ssl { let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_with(tcp, RustlsAcceptor::new(ssl)); + srv = srv.listen_rustls(tcp, ssl); } } if !has_ssl { From d0fc9d7b99961cbbcd8dc389292abdaf46337fcb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 21:55:44 -0700 Subject: [PATCH 0675/1635] simplify listen_ and bind_ methods --- src/server/http.rs | 157 +++++++++++++++------------------------------ 1 file changed, 52 insertions(+), 105 deletions(-) diff --git a/src/server/http.rs b/src/server/http.rs index 6432f18f..22537cb8 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -1,13 +1,11 @@ use std::{io, mem, net}; use actix::{Addr, System}; -use actix_net::server; -use actix_net::service::NewService; +use actix_net::server::Server; use actix_net::ssl; use net2::TcpBuilder; use num_cpus; -use tokio_tcp::TcpStream; #[cfg(feature = "tls")] use native_tls::TlsAcceptor; @@ -21,7 +19,7 @@ use rustls::ServerConfig; use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; use super::builder::DefaultPipelineFactory; use super::builder::{HttpServiceBuilder, ServiceProvider}; -use super::{IntoHttpHandler, IoStream, KeepAlive}; +use super::{IntoHttpHandler, KeepAlive}; struct Socket { scheme: &'static str, @@ -233,15 +231,9 @@ where #[doc(hidden)] /// Use listener for accepting incoming connection requests - pub(crate) fn listen_with( - mut self, lst: net::TcpListener, acceptor: A, - ) -> Self + pub(crate) fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self where - A: AcceptorServiceFactory, - T: NewService - + Clone - + 'static, - Io: IoStream + Send, + A: AcceptorServiceFactory, { let addr = lst.local_addr().unwrap(); self.sockets.push(Socket { @@ -266,18 +258,9 @@ where pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { use actix_net::service::NewServiceExt; - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - move || ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()), - DefaultPipelineFactory::new(), - )), - }); - self + self.listen_with(lst, move || { + ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) + }) } #[cfg(any(feature = "alpn", feature = "ssl"))] @@ -285,7 +268,7 @@ where /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn listen_ssl( - mut self, lst: net::TcpListener, builder: SslAcceptorBuilder, + self, lst: net::TcpListener, builder: SslAcceptorBuilder, ) -> io::Result { use super::{openssl_acceptor_with_flags, ServerFlags}; use actix_net::service::NewServiceExt; @@ -297,20 +280,9 @@ where }; let acceptor = openssl_acceptor_with_flags(builder, flags)?; - - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - move || ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()), - DefaultPipelineFactory::new(), - )), - }); - - Ok(self) + Ok(self.listen_with(lst, move || { + ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) + })) } #[cfg(feature = "rust-tls")] @@ -328,22 +300,9 @@ where ServerFlags::HTTP1 | ServerFlags::HTTP2 }; - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - move || { - RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) - }, - DefaultPipelineFactory::new(), - )), - }); - - //Ok(self) - self + self.listen_with(lst, move || { + RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) + }) } /// The socket address to bind @@ -416,33 +375,32 @@ where } } - // #[cfg(feature = "tls")] - // /// The ssl socket address to bind - // /// - // /// To bind multiple addresses this method can be called multiple times. - // pub fn bind_tls( - // self, addr: S, acceptor: TlsAcceptor, - // ) -> io::Result { - // use super::NativeTlsAcceptor; + #[cfg(feature = "tls")] + /// The ssl socket address to bind + /// + /// To bind multiple addresses this method can be called multiple times. + pub fn bind_tls( + self, addr: S, acceptor: TlsAcceptor, + ) -> io::Result { + use actix_net::service::NewServiceExt; + use actix_net::ssl::NativeTlsAcceptor; - // self.bind_with(addr, NativeTlsAcceptor::new(acceptor)) - // } + self.bind_with(addr, move || { + NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) + }) + } #[cfg(any(feature = "alpn", feature = "ssl"))] /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_ssl( - mut self, addr: S, builder: SslAcceptorBuilder, - ) -> io::Result + pub fn bind_ssl(self, addr: S, builder: SslAcceptorBuilder) -> io::Result where S: net::ToSocketAddrs, { use super::{openssl_acceptor_with_flags, ServerFlags}; use actix_net::service::NewServiceExt; - let sockets = self.bind2(addr)?; - // alpn support let flags = if !self.no_http2 { ServerFlags::HTTP1 @@ -451,43 +409,32 @@ where }; let acceptor = openssl_acceptor_with_flags(builder, flags)?; - - for lst in sockets { - let addr = lst.local_addr().unwrap(); - let accpt = acceptor.clone(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - move || ssl::OpensslAcceptor::new(accpt.clone()).map_err(|_| ()), - DefaultPipelineFactory::new(), - )), - }); - } - - Ok(self) + self.bind_with(addr, move || { + ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) + }) } - // #[cfg(feature = "rust-tls")] - // /// Start listening for incoming tls connections. - // /// - // /// This method sets alpn protocols to "h2" and "http/1.1" - // pub fn bind_rustls( - // self, addr: S, builder: ServerConfig, - // ) -> io::Result { - // use super::{RustlsAcceptor, ServerFlags}; + #[cfg(feature = "rust-tls")] + /// Start listening for incoming tls connections. + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn bind_rustls( + self, addr: S, builder: ServerConfig, + ) -> io::Result { + use super::{RustlsAcceptor, ServerFlags}; + use actix_net::service::NewServiceExt; - // // alpn support - // let flags = if !self.no_http2 { - // ServerFlags::HTTP1 - // } else { - // ServerFlags::HTTP1 | ServerFlags::HTTP2 - // }; + // alpn support + let flags = if !self.no_http2 { + ServerFlags::HTTP1 + } else { + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; - // self.bind_with(addr, RustlsAcceptor::with_flags(builder, flags)) - // } + self.bind_with(addr, move || { + RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ()) + }) + } } impl H + Send + Clone> HttpServer { @@ -516,10 +463,10 @@ impl H + Send + Clone> HttpServer { /// sys.run(); // <- Run actix system, this method starts all async processes /// } /// ``` - pub fn start(mut self) -> Addr { + pub fn start(mut self) -> Addr { ssl::max_concurrent_ssl_connect(self.maxconnrate); - let mut srv = server::Server::new() + let mut srv = Server::new() .workers(self.threads) .maxconn(self.maxconn) .shutdown_timeout(self.shutdown_timeout); From 4b59ae24760b361c85b04967611c5ddeae16c912 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 22:15:38 -0700 Subject: [PATCH 0676/1635] fix ssl config for client connector --- src/client/connector.rs | 60 +++++++++++++++++++++++++++++------------ src/test.rs | 25 ++++++++--------- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 896f98a4..6e82e3fd 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -16,13 +16,16 @@ use http::{Error as HttpError, HttpTryFrom, Uri}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -#[cfg(feature = "alpn")] +#[cfg(any(feature = "alpn", feature = "ssl"))] use { openssl::ssl::{Error as SslError, SslConnector, SslMethod}, tokio_openssl::SslConnectorExt, }; -#[cfg(all(feature = "tls", not(feature = "alpn")))] +#[cfg(all( + feature = "tls", + not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) +))] use { native_tls::{Error as SslError, TlsConnector as NativeTlsConnector}, tokio_tls::TlsConnector as SslConnector, @@ -30,7 +33,7 @@ use { #[cfg(all( feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) + not(any(feature = "alpn", feature = "tls", feature = "ssl")) ))] use { rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc, @@ -39,11 +42,16 @@ use { #[cfg(all( feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) + not(any(feature = "alpn", feature = "tls", feature = "ssl")) ))] type SslConnector = Arc; -#[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] +#[cfg(not(any( + feature = "alpn", + feature = "ssl", + feature = "tls", + feature = "rust-tls", +)))] type SslConnector = (); use server::IoStream; @@ -150,7 +158,12 @@ pub enum ClientConnectorError { SslIsNotSupported, /// SSL error - #[cfg(any(feature = "tls", feature = "alpn", feature = "rust-tls"))] + #[cfg(any( + feature = "tls", + feature = "alpn", + feature = "ssl", + feature = "rust-tls", + ))] #[fail(display = "{}", _0)] SslError(#[cause] SslError), @@ -247,19 +260,22 @@ impl SystemService for ClientConnector {} impl Default for ClientConnector { fn default() -> ClientConnector { let connector = { - #[cfg(all(feature = "alpn"))] + #[cfg(all(any(feature = "alpn", feature = "ssl")))] { SslConnector::builder(SslMethod::tls()).unwrap().build() } - #[cfg(all(feature = "tls", not(feature = "alpn")))] + #[cfg(all( + feature = "tls", + not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) + ))] { NativeTlsConnector::builder().build().unwrap().into() } #[cfg(all( feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) + not(any(feature = "alpn", feature = "tls", feature = "ssl")) ))] { let mut config = ClientConfig::new(); @@ -269,7 +285,12 @@ impl Default for ClientConnector { Arc::new(config) } - #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] + #[cfg(not(any( + feature = "alpn", + feature = "ssl", + feature = "tls", + feature = "rust-tls", + )))] { () } @@ -280,7 +301,7 @@ impl Default for ClientConnector { } impl ClientConnector { - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "ssl"))] /// Create `ClientConnector` actor with custom `SslConnector` instance. /// /// By default `ClientConnector` uses very a simple SSL configuration. @@ -325,7 +346,7 @@ impl ClientConnector { #[cfg(all( feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) + not(any(feature = "alpn", feature = "ssl", feature = "tls")) ))] /// Create `ClientConnector` actor with custom `SslConnector` instance. /// @@ -376,7 +397,7 @@ impl ClientConnector { #[cfg(all( feature = "tls", - not(any(feature = "alpn", feature = "rust-tls")) + not(any(feature = "ssl", feature = "alpn", feature = "rust-tls")) ))] /// Create `ClientConnector` actor with custom `SslConnector` instance. /// @@ -714,7 +735,7 @@ impl ClientConnector { act.release_key(&key2); () }).and_then(move |res, act, _| { - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "ssl"))] match res { Err(err) => { let _ = waiter.tx.send(Err(err.into())); @@ -756,7 +777,7 @@ impl ClientConnector { } } - #[cfg(all(feature = "tls", not(feature = "alpn")))] + #[cfg(all(feature = "tls", not(any(feature = "alpn", feature = "ssl"))))] match res { Err(err) => { let _ = waiter.tx.send(Err(err.into())); @@ -800,7 +821,7 @@ impl ClientConnector { #[cfg(all( feature = "rust-tls", - not(any(feature = "alpn", feature = "tls")) + not(any(feature = "alpn", feature = "ssl", feature = "tls")) ))] match res { Err(err) => { @@ -844,7 +865,12 @@ impl ClientConnector { } } - #[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))] + #[cfg(not(any( + feature = "alpn", + feature = "ssl", + feature = "tls", + feature = "rust-tls" + )))] match res { Err(err) => { let _ = waiter.tx.send(Err(err.into())); diff --git a/src/test.rs b/src/test.rs index 83b0b83b..d0cfb255 100644 --- a/src/test.rs +++ b/src/test.rs @@ -13,12 +13,10 @@ use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; -#[cfg(feature = "alpn")] +#[cfg(any(feature = "alpn", feature = "ssl"))] use openssl::ssl::SslAcceptorBuilder; #[cfg(feature = "rust-tls")] use rustls::ServerConfig; -#[cfg(feature = "alpn")] -use server::OpensslAcceptor; use application::{App, HttpApplication}; use body::Binary; @@ -136,7 +134,7 @@ impl TestServer { } fn get_conn() -> Addr { - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "ssl"))] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -144,7 +142,10 @@ impl TestServer { builder.set_verify(SslVerifyMode::NONE); ClientConnector::with_connector(builder.build()).start() } - #[cfg(all(feature = "rust-tls", not(feature = "alpn")))] + #[cfg(all( + feature = "rust-tls", + not(any(feature = "alpn", feature = "ssl")) + ))] { use rustls::ClientConfig; use std::fs::File; @@ -154,7 +155,7 @@ impl TestServer { config.root_store.add_pem_file(pem_file).unwrap(); ClientConnector::with_connector(config).start() } - #[cfg(not(any(feature = "alpn", feature = "rust-tls")))] + #[cfg(not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")))] { ClientConnector::default().start() } @@ -263,7 +264,7 @@ where F: Fn() -> S + Send + Clone + 'static, { state: F, - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "ssl"))] ssl: Option, #[cfg(feature = "rust-tls")] rust_ssl: Option, @@ -277,14 +278,14 @@ where pub fn new(state: F) -> TestServerBuilder { TestServerBuilder { state, - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "ssl"))] ssl: None, #[cfg(feature = "rust-tls")] rust_ssl: None, } } - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "ssl"))] /// Create ssl server pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self { self.ssl = Some(ssl); @@ -308,7 +309,7 @@ where let mut has_ssl = false; - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "ssl"))] { has_ssl = has_ssl || self.ssl.is_some(); } @@ -335,12 +336,12 @@ where tx.send((System::current(), addr, TestServer::get_conn())) .unwrap(); - #[cfg(feature = "alpn")] + #[cfg(any(feature = "alpn", feature = "ssl"))] { let ssl = self.ssl.take(); if let Some(ssl) = ssl { let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_with(tcp, OpensslAcceptor::new(ssl).unwrap()); + srv = srv.listen_ssl(tcp, ssl).unwrap(); } } #[cfg(feature = "rust-tls")] From bec37fdbd53f91e96ba161568ed6e191729c1411 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 27 Sep 2018 22:23:29 -0700 Subject: [PATCH 0677/1635] update travis config --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0023965d..dbdcb923 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,12 +30,12 @@ before_script: script: - | - if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then + if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then cargo clean cargo test --features="ssl,tls,rust-tls" -- --nocapture fi - | - if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) @@ -45,7 +45,7 @@ script: # Upload docs after_success: - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then cargo doc --features "ssl,tls,rust-tls,session" --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && From fc5088b55ee4285d7f17dd58fd81982156d9b977 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Sep 2018 00:08:23 -0700 Subject: [PATCH 0678/1635] fix tarpaulin args --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dbdcb923..59f6a854 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml --no-count + cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 0f1c80ccc63840b9da646b268f2e07dd520c6837 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Sep 2018 08:45:49 -0700 Subject: [PATCH 0679/1635] deprecate start_incoming --- src/client/connector.rs | 4 +- src/server/channel.rs | 5 ++ src/server/http.rs | 115 +++++++++++++++++++--------------------- 3 files changed, 63 insertions(+), 61 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 6e82e3fd..8d71913f 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -51,7 +51,7 @@ type SslConnector = Arc; feature = "ssl", feature = "tls", feature = "rust-tls", -)))] +),))] type SslConnector = (); use server::IoStream; @@ -290,7 +290,7 @@ impl Default for ClientConnector { feature = "ssl", feature = "tls", feature = "rust-tls", - )))] + ),))] { () } diff --git a/src/server/channel.rs b/src/server/channel.rs index c1e6b6b2..0d92c23a 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -1,6 +1,7 @@ use std::net::{Shutdown, SocketAddr}; use std::{io, ptr, time}; +use actix::Message; use bytes::{Buf, BufMut, BytesMut}; use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -282,6 +283,10 @@ where io: T, } +impl Message for WrapperStream { + type Result = (); +} + impl WrapperStream where T: AsyncRead + AsyncWrite + 'static, diff --git a/src/server/http.rs b/src/server/http.rs index 22537cb8..81c4d3ad 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -1,11 +1,13 @@ use std::{io, mem, net}; -use actix::{Addr, System}; +use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System}; use actix_net::server::Server; use actix_net::ssl; +use futures::Stream; use net2::TcpBuilder; use num_cpus; +use tokio_io::{AsyncRead, AsyncWrite}; #[cfg(feature = "tls")] use native_tls::TlsAcceptor; @@ -17,8 +19,10 @@ use openssl::ssl::SslAcceptorBuilder; use rustls::ServerConfig; use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; -use super::builder::DefaultPipelineFactory; -use super::builder::{HttpServiceBuilder, ServiceProvider}; +use super::builder::{DefaultPipelineFactory, HttpServiceBuilder, ServiceProvider}; +use super::channel::{HttpChannel, WrapperStream}; +use super::handler::HttpHandler; +use super::settings::{ServerSettings, WorkerSettings}; use super::{IntoHttpHandler, KeepAlive}; struct Socket { @@ -520,67 +524,60 @@ impl H + Send + Clone> HttpServer { } } -// impl HttpServer { -// /// Start listening for incoming connections from a stream. -// /// -// /// This method uses only one thread for handling incoming connections. -// pub fn start_incoming(self, stream: S, secure: bool) -// where -// S: Stream + Send + 'static, -// T: AsyncRead + AsyncWrite + Send + 'static, -// { -// // set server settings -// let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); -// let srv_settings = ServerSettings::new(Some(addr), &self.host, secure); -// let apps: Vec<_> = (*self.factory)() -// .into_iter() -// .map(|h| h.into_handler()) -// .collect(); -// let settings = WorkerSettings::create( -// apps, -// self.keep_alive, -// srv_settings, -// ); +impl HttpServer +where + H: IntoHttpHandler, + F: Fn() -> H + Send + Clone, +{ + #[doc(hidden)] + #[deprecated(since = "0.7.8")] + /// Start listening for incoming connections from a stream. + /// + /// This method uses only one thread for handling incoming connections. + pub fn start_incoming(self, stream: S, secure: bool) + where + S: Stream + 'static, + T: AsyncRead + AsyncWrite + 'static, + { + // set server settings + let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let apps = (self.factory)().into_handler(); + let settings = WorkerSettings::new( + apps, + self.keep_alive, + self.client_timeout as u64, + ServerSettings::new(Some(addr), &self.host, secure), + ); -// // start server -// HttpIncoming::create(move |ctx| { -// ctx.add_message_stream(stream.map_err(|_| ()).map(move |t| Conn { -// io: WrapperStream::new(t), -// handler: Token::new(0), -// token: Token::new(0), -// peer: None, -// })); -// HttpIncoming { settings } -// }); -// } -// } + // start server + HttpIncoming::create(move |ctx| { + ctx.add_message_stream( + stream.map_err(|_| ()).map(move |t| WrapperStream::new(t)), + ); + HttpIncoming { settings } + }); + } +} -// struct HttpIncoming { -// settings: Rc>, -// } +struct HttpIncoming { + settings: WorkerSettings, +} -// impl Actor for HttpIncoming -// where -// H: HttpHandler, -// { -// type Context = Context; -// } +impl Actor for HttpIncoming { + type Context = Context; +} -// impl Handler> for HttpIncoming -// where -// T: IoStream, -// H: HttpHandler, -// { -// type Result = (); +impl Handler> for HttpIncoming +where + T: AsyncRead + AsyncWrite, + H: HttpHandler, +{ + type Result = (); -// fn handle(&mut self, msg: Conn, _: &mut Context) -> Self::Result { -// spawn(HttpChannel::new( -// Rc::clone(&self.settings), -// msg.io, -// msg.peer, -// )); -// } -// } + fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { + Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg, None)); + } +} fn create_tcp_listener( addr: net::SocketAddr, backlog: i32, From f2d42e5e7719383fccdf97315437da27a4991dfb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Sep 2018 11:50:47 -0700 Subject: [PATCH 0680/1635] refactor acceptor error handling --- Cargo.toml | 4 +- src/client/connector.rs | 4 +- src/server/acceptor.rs | 275 ++++++++++++++++++++++++---------------- src/server/builder.rs | 38 ++++-- src/server/channel.rs | 5 - src/server/error.rs | 15 +++ src/server/http.rs | 70 +--------- src/server/incoming.rs | 70 ++++++++++ src/server/mod.rs | 1 + 9 files changed, 288 insertions(+), 194 deletions(-) create mode 100644 src/server/incoming.rs diff --git a/Cargo.toml b/Cargo.toml index 205e178b..0e95c327 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,8 +60,8 @@ flate2-rust = ["flate2/rust_backend"] [dependencies] actix = "0.7.0" -actix-net = { git="https://github.com/actix/actix-net.git" } -#actix-net = { path = "../actix-net" } +#actix-net = { git="https://github.com/actix/actix-net.git" } +actix-net = { path = "../actix-net" } base64 = "0.9" bitflags = "1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index 8d71913f..6e82e3fd 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -51,7 +51,7 @@ type SslConnector = Arc; feature = "ssl", feature = "tls", feature = "rust-tls", -),))] +)))] type SslConnector = (); use server::IoStream; @@ -290,7 +290,7 @@ impl Default for ClientConnector { feature = "ssl", feature = "tls", feature = "rust-tls", - ),))] + )))] { () } diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index d7847416..caad0e2e 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -1,3 +1,4 @@ +use std::net; use std::time::Duration; use actix_net::server::ServerMessage; @@ -8,6 +9,7 @@ use tokio_reactor::Handle; use tokio_tcp::TcpStream; use tokio_timer::{sleep, Delay}; +use super::error::AcceptorError; use super::handler::HttpHandler; use super::settings::WorkerSettings; use super::IoStream; @@ -15,12 +17,7 @@ use super::IoStream; /// This trait indicates types that can create acceptor service for http server. pub trait AcceptorServiceFactory: Send + Clone + 'static { type Io: IoStream + Send; - type NewService: NewService< - Request = TcpStream, - Response = Self::Io, - Error = (), - InitError = (), - >; + type NewService: NewService; fn create(&self) -> Self::NewService; } @@ -29,7 +26,7 @@ impl AcceptorServiceFactory for F where F: Fn() -> T + Send + Clone + 'static, T::Response: IoStream + Send, - T: NewService, + T: NewService, { type Io = T::Response; type NewService = T; @@ -80,144 +77,91 @@ impl Service for DefaultAcceptor { } } -pub(crate) struct TcpAcceptor { +pub(crate) struct TcpAcceptor { inner: T, - settings: WorkerSettings, } -impl TcpAcceptor +impl TcpAcceptor where - H: HttpHandler, - T: NewService, + T: NewService>, { - pub(crate) fn new(settings: WorkerSettings, inner: T) -> Self { - TcpAcceptor { inner, settings } + pub(crate) fn new(inner: T) -> Self { + TcpAcceptor { inner } } } -impl NewService for TcpAcceptor +impl NewService for TcpAcceptor where - H: HttpHandler, - T: NewService, + T: NewService>, { - type Request = ServerMessage; - type Response = (); - type Error = (); - type InitError = (); - type Service = TcpAcceptorService; - type Future = TcpAcceptorResponse; + type Request = net::TcpStream; + type Response = T::Response; + type Error = AcceptorError; + type InitError = T::InitError; + type Service = TcpAcceptorService; + type Future = TcpAcceptorResponse; fn new_service(&self) -> Self::Future { TcpAcceptorResponse { fut: self.inner.new_service(), - settings: self.settings.clone(), } } } -pub(crate) struct TcpAcceptorResponse +pub(crate) struct TcpAcceptorResponse where - H: HttpHandler, T: NewService, { fut: T::Future, - settings: WorkerSettings, } -impl Future for TcpAcceptorResponse +impl Future for TcpAcceptorResponse where - H: HttpHandler, T: NewService, { - type Item = TcpAcceptorService; - type Error = (); + type Item = TcpAcceptorService; + type Error = T::InitError; fn poll(&mut self) -> Poll { - match self.fut.poll() { - Err(_) => Err(()), - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(service)) => Ok(Async::Ready(TcpAcceptorService { - inner: service, - settings: self.settings.clone(), - })), + match self.fut.poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(service) => { + Ok(Async::Ready(TcpAcceptorService { inner: service })) + } } } } -pub(crate) struct TcpAcceptorService { +pub(crate) struct TcpAcceptorService { inner: T, - settings: WorkerSettings, } -impl Service for TcpAcceptorService +impl Service for TcpAcceptorService where - H: HttpHandler, - T: Service, + T: Service>, { - type Request = ServerMessage; - type Response = (); - type Error = (); - type Future = Either, FutureResult<(), ()>>; + type Request = net::TcpStream; + type Response = T::Response; + type Error = AcceptorError; + type Future = Either>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready().map_err(|_| ()) + self.inner.poll_ready() } fn call(&mut self, req: Self::Request) -> Self::Future { - match req { - ServerMessage::Connect(stream) => { - let stream = - TcpStream::from_std(stream, &Handle::default()).map_err(|e| { - error!("Can not convert to an async tcp stream: {}", e); - }); + let stream = TcpStream::from_std(req, &Handle::default()).map_err(|e| { + error!("Can not convert to an async tcp stream: {}", e); + AcceptorError::Io(e) + }); - if let Ok(stream) = stream { - Either::A(TcpAcceptorServiceFut { - fut: self.inner.call(stream), - }) - } else { - Either::B(err(())) - } - } - ServerMessage::Shutdown(timeout) => Either::B(ok(())), - ServerMessage::ForceShutdown => { - // self.settings.head().traverse::(); - Either::B(ok(())) - } + match stream { + Ok(stream) => Either::A(self.inner.call(stream)), + Err(e) => Either::B(err(e)), } } } -pub(crate) struct TcpAcceptorServiceFut { - fut: T, -} - -impl Future for TcpAcceptorServiceFut -where - T: Future, -{ - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Err(_) => Err(()), - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(_)) => Ok(Async::Ready(())), - } - } -} - -/// Errors produced by `AcceptorTimeout` service. -#[derive(Debug)] -pub enum TimeoutError { - /// The inner service error - Service(T), - - /// The request did not complete within the specified timeout. - Timeout, -} - /// Acceptor timeout middleware /// /// Applies timeout to request prcoessing. @@ -235,7 +179,7 @@ impl AcceptorTimeout { impl NewService for AcceptorTimeout { type Request = T::Request; type Response = T::Response; - type Error = TimeoutError; + type Error = AcceptorError; type InitError = T::InitError; type Service = AcceptorTimeoutService; type Future = AcceptorTimeoutFut; @@ -278,11 +222,11 @@ pub(crate) struct AcceptorTimeoutService { impl Service for AcceptorTimeoutService { type Request = T::Request; type Response = T::Response; - type Error = TimeoutError; + type Error = AcceptorError; type Future = AcceptorTimeoutResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready().map_err(TimeoutError::Service) + self.inner.poll_ready().map_err(AcceptorError::Service) } fn call(&mut self, req: Self::Request) -> Self::Future { @@ -299,17 +243,134 @@ pub(crate) struct AcceptorTimeoutResponse { } impl Future for AcceptorTimeoutResponse { type Item = T::Response; - type Error = TimeoutError; + type Error = AcceptorError; fn poll(&mut self) -> Poll { - match self.fut.poll() { - Ok(Async::NotReady) => match self.sleep.poll() { - Err(_) => Err(TimeoutError::Timeout), - Ok(Async::Ready(_)) => Err(TimeoutError::Timeout), + match self.fut.poll().map_err(AcceptorError::Service)? { + Async::NotReady => match self.sleep.poll() { + Err(_) => Err(AcceptorError::Timeout), + Ok(Async::Ready(_)) => Err(AcceptorError::Timeout), Ok(Async::NotReady) => Ok(Async::NotReady), }, - Ok(Async::Ready(resp)) => Ok(Async::Ready(resp)), - Err(err) => Err(TimeoutError::Service(err)), + Async::Ready(resp) => Ok(Async::Ready(resp)), + } + } +} + +pub(crate) struct ServerMessageAcceptor { + inner: T, + settings: WorkerSettings, +} + +impl ServerMessageAcceptor +where + H: HttpHandler, + T: NewService, +{ + pub(crate) fn new(settings: WorkerSettings, inner: T) -> Self { + ServerMessageAcceptor { inner, settings } + } +} + +impl NewService for ServerMessageAcceptor +where + H: HttpHandler, + T: NewService, +{ + type Request = ServerMessage; + type Response = (); + type Error = T::Error; + type InitError = T::InitError; + type Service = ServerMessageAcceptorService; + type Future = ServerMessageAcceptorResponse; + + fn new_service(&self) -> Self::Future { + ServerMessageAcceptorResponse { + fut: self.inner.new_service(), + settings: self.settings.clone(), + } + } +} + +pub(crate) struct ServerMessageAcceptorResponse +where + H: HttpHandler, + T: NewService, +{ + fut: T::Future, + settings: WorkerSettings, +} + +impl Future for ServerMessageAcceptorResponse +where + H: HttpHandler, + T: NewService, +{ + type Item = ServerMessageAcceptorService; + type Error = T::InitError; + + fn poll(&mut self) -> Poll { + match self.fut.poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService { + inner: service, + settings: self.settings.clone(), + })), + } + } +} + +pub(crate) struct ServerMessageAcceptorService { + inner: T, + settings: WorkerSettings, +} + +impl Service for ServerMessageAcceptorService +where + H: HttpHandler, + T: Service, +{ + type Request = ServerMessage; + type Response = (); + type Error = T::Error; + type Future = + Either, FutureResult<(), Self::Error>>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.inner.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + match req { + ServerMessage::Connect(stream) => { + Either::A(ServerMessageAcceptorServiceFut { + fut: self.inner.call(stream), + }) + } + ServerMessage::Shutdown(timeout) => Either::B(ok(())), + ServerMessage::ForceShutdown => { + // self.settings.head().traverse::(); + Either::B(ok(())) + } + } + } +} + +pub(crate) struct ServerMessageAcceptorServiceFut { + fut: T::Future, +} + +impl Future for ServerMessageAcceptorServiceFut +where + T: Service, +{ + type Item = (); + type Error = T::Error; + + fn poll(&mut self) -> Poll { + match self.fut.poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(_) => Ok(Async::Ready(())), } } } diff --git a/src/server/builder.rs b/src/server/builder.rs index 28541820..46ab9f46 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -5,7 +5,10 @@ use actix_net::either::Either; use actix_net::server::{Server, ServiceFactory}; use actix_net::service::{NewService, NewServiceExt}; -use super::acceptor::{AcceptorServiceFactory, AcceptorTimeout, TcpAcceptor}; +use super::acceptor::{ + AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor, +}; +use super::error::AcceptorError; use super::handler::{HttpHandler, IntoHttpHandler}; use super::service::HttpService; use super::settings::{ServerSettings, WorkerSettings}; @@ -99,16 +102,30 @@ where ); if timeout == 0 { - Either::A(TcpAcceptor::new( + Either::A(ServerMessageAcceptor::new( settings.clone(), - acceptor.create().and_then(pipeline.create(settings)), + TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) + .map_err(|_| ()) + .map_init_err(|_| ()) + .and_then( + pipeline + .create(settings) + .map_init_err(|_| ()) + .map_err(|_| ()), + ), )) } else { - Either::B(TcpAcceptor::new( + Either::B(ServerMessageAcceptor::new( settings.clone(), - AcceptorTimeout::new(timeout, acceptor.create()) + TcpAcceptor::new(AcceptorTimeout::new(timeout, acceptor.create())) .map_err(|_| ()) - .and_then(pipeline.create(settings)), + .map_init_err(|_| ()) + .and_then( + pipeline + .create(settings) + .map_init_err(|_| ()) + .map_err(|_| ()), + ), )) } } @@ -153,12 +170,7 @@ where pub trait HttpPipelineFactory: Send + Clone + 'static { type Io: IoStream; - type NewService: NewService< - Request = Self::Io, - Response = (), - Error = (), - InitError = (), - >; + type NewService: NewService; fn create(&self, settings: WorkerSettings) -> Self::NewService; } @@ -166,7 +178,7 @@ pub trait HttpPipelineFactory: Send + Clone + 'static { impl HttpPipelineFactory for F where F: Fn(WorkerSettings) -> T + Send + Clone + 'static, - T: NewService, + T: NewService, T::Request: IoStream, H: HttpHandler, { diff --git a/src/server/channel.rs b/src/server/channel.rs index 0d92c23a..c1e6b6b2 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -1,7 +1,6 @@ use std::net::{Shutdown, SocketAddr}; use std::{io, ptr, time}; -use actix::Message; use bytes::{Buf, BufMut, BytesMut}; use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -283,10 +282,6 @@ where io: T, } -impl Message for WrapperStream { - type Result = (); -} - impl WrapperStream where T: AsyncRead + AsyncWrite + 'static, diff --git a/src/server/error.rs b/src/server/error.rs index d08ccf87..ff8b831a 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -1,9 +1,24 @@ +use std::io; + use futures::{Async, Poll}; use super::{helpers, HttpHandlerTask, Writer}; use http::{StatusCode, Version}; use Error; +/// Errors produced by `AcceptorError` service. +#[derive(Debug)] +pub enum AcceptorError { + /// The inner service error + Service(T), + + /// Io specific error + Io(io::Error), + + /// The request did not complete within the specified timeout. + Timeout, +} + pub(crate) struct ServerError(Version, StatusCode); impl ServerError { diff --git a/src/server/http.rs b/src/server/http.rs index 81c4d3ad..846f7f01 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -1,13 +1,11 @@ use std::{io, mem, net}; -use actix::{Actor, Addr, Arbiter, AsyncContext, Context, Handler, System}; +use actix::{Addr, System}; use actix_net::server::Server; use actix_net::ssl; -use futures::Stream; use net2::TcpBuilder; use num_cpus; -use tokio_io::{AsyncRead, AsyncWrite}; #[cfg(feature = "tls")] use native_tls::TlsAcceptor; @@ -20,9 +18,6 @@ use rustls::ServerConfig; use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; use super::builder::{DefaultPipelineFactory, HttpServiceBuilder, ServiceProvider}; -use super::channel::{HttpChannel, WrapperStream}; -use super::handler::HttpHandler; -use super::settings::{ServerSettings, WorkerSettings}; use super::{IntoHttpHandler, KeepAlive}; struct Socket { @@ -42,9 +37,10 @@ where H: IntoHttpHandler + 'static, F: Fn() -> H + Send + Clone, { - factory: F, - host: Option, - keep_alive: KeepAlive, + pub(super) factory: F, + pub(super) host: Option, + pub(super) keep_alive: KeepAlive, + pub(super) client_timeout: usize, backlog: i32, threads: usize, exit: bool, @@ -53,7 +49,6 @@ where no_signals: bool, maxconn: usize, maxconnrate: usize, - client_timeout: usize, sockets: Vec, } @@ -524,61 +519,6 @@ impl H + Send + Clone> HttpServer { } } -impl HttpServer -where - H: IntoHttpHandler, - F: Fn() -> H + Send + Clone, -{ - #[doc(hidden)] - #[deprecated(since = "0.7.8")] - /// Start listening for incoming connections from a stream. - /// - /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(self, stream: S, secure: bool) - where - S: Stream + 'static, - T: AsyncRead + AsyncWrite + 'static, - { - // set server settings - let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let apps = (self.factory)().into_handler(); - let settings = WorkerSettings::new( - apps, - self.keep_alive, - self.client_timeout as u64, - ServerSettings::new(Some(addr), &self.host, secure), - ); - - // start server - HttpIncoming::create(move |ctx| { - ctx.add_message_stream( - stream.map_err(|_| ()).map(move |t| WrapperStream::new(t)), - ); - HttpIncoming { settings } - }); - } -} - -struct HttpIncoming { - settings: WorkerSettings, -} - -impl Actor for HttpIncoming { - type Context = Context; -} - -impl Handler> for HttpIncoming -where - T: AsyncRead + AsyncWrite, - H: HttpHandler, -{ - type Result = (); - - fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg, None)); - } -} - fn create_tcp_listener( addr: net::SocketAddr, backlog: i32, ) -> io::Result { diff --git a/src/server/incoming.rs b/src/server/incoming.rs new file mode 100644 index 00000000..7ab289d0 --- /dev/null +++ b/src/server/incoming.rs @@ -0,0 +1,70 @@ +//! Support for `Stream`, deprecated! +use std::{io, net}; + +use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message}; +use futures::Stream; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::channel::{HttpChannel, WrapperStream}; +use super::handler::{HttpHandler, IntoHttpHandler}; +use super::http::HttpServer; +use super::settings::{ServerSettings, WorkerSettings}; + +impl Message for WrapperStream { + type Result = (); +} + +impl HttpServer +where + H: IntoHttpHandler, + F: Fn() -> H + Send + Clone, +{ + #[doc(hidden)] + #[deprecated(since = "0.7.8")] + /// Start listening for incoming connections from a stream. + /// + /// This method uses only one thread for handling incoming connections. + pub fn start_incoming(self, stream: S, secure: bool) + where + S: Stream + 'static, + T: AsyncRead + AsyncWrite + 'static, + { + // set server settings + let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let apps = (self.factory)().into_handler(); + let settings = WorkerSettings::new( + apps, + self.keep_alive, + self.client_timeout as u64, + ServerSettings::new(Some(addr), &self.host, secure), + ); + + // start server + HttpIncoming::create(move |ctx| { + ctx.add_message_stream( + stream.map_err(|_| ()).map(move |t| WrapperStream::new(t)), + ); + HttpIncoming { settings } + }); + } +} + +struct HttpIncoming { + settings: WorkerSettings, +} + +impl Actor for HttpIncoming { + type Context = Context; +} + +impl Handler> for HttpIncoming +where + T: AsyncRead + AsyncWrite, + H: HttpHandler, +{ + type Result = (); + + fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { + Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg, None)); + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 9e91eda0..1e145571 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -129,6 +129,7 @@ mod h2writer; mod handler; pub(crate) mod helpers; mod http; +pub(crate) mod incoming; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; From e95babf8d3559b947b4a06c331a1cb505571f834 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Sep 2018 12:35:09 -0700 Subject: [PATCH 0681/1635] log acctor init errors --- Cargo.toml | 4 ++-- src/client/connector.rs | 4 ++-- src/server/acceptor.rs | 17 +++++++++++++---- src/server/builder.rs | 5 ++++- src/server/http.rs | 9 ++++++--- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e95c327..205e178b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,8 +60,8 @@ flate2-rust = ["flate2/rust_backend"] [dependencies] actix = "0.7.0" -#actix-net = { git="https://github.com/actix/actix-net.git" } -actix-net = { path = "../actix-net" } +actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = { path = "../actix-net" } base64 = "0.9" bitflags = "1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index 6e82e3fd..8d71913f 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -51,7 +51,7 @@ type SslConnector = Arc; feature = "ssl", feature = "tls", feature = "rust-tls", -)))] +),))] type SslConnector = (); use server::IoStream; @@ -290,7 +290,7 @@ impl Default for ClientConnector { feature = "ssl", feature = "tls", feature = "rust-tls", - )))] + ),))] { () } diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index caad0e2e..bad8847d 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -1,5 +1,5 @@ -use std::net; use std::time::Duration; +use std::{fmt, net}; use actix_net::server::ServerMessage; use actix_net::service::{NewService, Service}; @@ -27,6 +27,7 @@ where F: Fn() -> T + Send + Clone + 'static, T::Response: IoStream + Send, T: NewService, + T::InitError: fmt::Debug, { type Io = T::Response; type NewService = T; @@ -84,6 +85,7 @@ pub(crate) struct TcpAcceptor { impl TcpAcceptor where T: NewService>, + T::InitError: fmt::Debug, { pub(crate) fn new(inner: T) -> Self { TcpAcceptor { inner } @@ -93,6 +95,7 @@ where impl NewService for TcpAcceptor where T: NewService>, + T::InitError: fmt::Debug, { type Request = net::TcpStream; type Response = T::Response; @@ -111,6 +114,7 @@ where pub(crate) struct TcpAcceptorResponse where T: NewService, + T::InitError: fmt::Debug, { fut: T::Future, } @@ -118,16 +122,21 @@ where impl Future for TcpAcceptorResponse where T: NewService, + T::InitError: fmt::Debug, { type Item = TcpAcceptorService; type Error = T::InitError; fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(service) => { + match self.fut.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(service)) => { Ok(Async::Ready(TcpAcceptorService { inner: service })) } + Err(e) => { + error!("Can not create accetor service: {:?}", e); + Err(e) + } } } } diff --git a/src/server/builder.rs b/src/server/builder.rs index 46ab9f46..8c0a0f62 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -1,5 +1,5 @@ use std::marker::PhantomData; -use std::net; +use std::{fmt, net}; use actix_net::either::Either; use actix_net::server::{Server, ServiceFactory}; @@ -37,6 +37,7 @@ where F: Fn() -> H + Send + Clone + 'static, H: IntoHttpHandler, A: AcceptorServiceFactory, + ::InitError: fmt::Debug, P: HttpPipelineFactory, { /// Create http service builder @@ -58,6 +59,7 @@ where pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder where A1: AcceptorServiceFactory, + ::InitError: fmt::Debug, { HttpServiceBuilder { acceptor, @@ -153,6 +155,7 @@ impl ServiceProvider for HttpServiceBuilder where F: Fn() -> H + Send + Clone + 'static, A: AcceptorServiceFactory, + ::InitError: fmt::Debug, P: HttpPipelineFactory, H: IntoHttpHandler, { diff --git a/src/server/http.rs b/src/server/http.rs index 846f7f01..034f903e 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -1,7 +1,8 @@ -use std::{io, mem, net}; +use std::{fmt, io, mem, net}; use actix::{Addr, System}; use actix_net::server::Server; +use actix_net::service::NewService; use actix_net::ssl; use net2::TcpBuilder; @@ -233,6 +234,7 @@ where pub(crate) fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self where A: AcceptorServiceFactory, + ::InitError: fmt::Debug, { let addr = lst.local_addr().unwrap(); self.sockets.push(Socket { @@ -254,7 +256,7 @@ where /// /// HttpServer does not change any configuration for TcpListener, /// it needs to be configured before passing it to listen() method. - pub fn listen_tls(mut self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { + pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { use actix_net::service::NewServiceExt; self.listen_with(lst, move || { @@ -288,7 +290,7 @@ where /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_rustls(mut self, lst: net::TcpListener, config: ServerConfig) -> Self { + pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self { use super::{RustlsAcceptor, ServerFlags}; use actix_net::service::NewServiceExt; @@ -324,6 +326,7 @@ where where S: net::ToSocketAddrs, A: AcceptorServiceFactory, + ::InitError: fmt::Debug, { let sockets = self.bind2(addr)?; From 4aac3d6a92ccdffc9eaf324a207c98ce6df8d4b4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Sep 2018 15:04:59 -0700 Subject: [PATCH 0682/1635] refactor keep-alive timer --- src/client/connector.rs | 4 +- src/server/h1.rs | 176 ++++++++++++++++++++++++++-------------- src/server/h1writer.rs | 4 + src/server/h2.rs | 21 +++-- src/server/settings.rs | 79 +++++++++++++----- 5 files changed, 189 insertions(+), 95 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 8d71913f..32426e0a 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -50,8 +50,8 @@ type SslConnector = Arc; feature = "alpn", feature = "ssl", feature = "tls", - feature = "rust-tls", -),))] + feature = "rust-tls" +)))] type SslConnector = (); use server::IoStream; diff --git a/src/server/h1.rs b/src/server/h1.rs index b5ee93e6..76c0d4b6 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -1,6 +1,6 @@ use std::collections::VecDeque; use std::net::SocketAddr; -use std::time::{Duration, Instant}; +use std::time::Instant; use bytes::BytesMut; use futures::{Async, Future, Poll}; @@ -49,7 +49,14 @@ pub(crate) struct Http1 { payload: Option, buf: BytesMut, tasks: VecDeque>, - keepalive_timer: Option, + ka_enabled: bool, + ka_expire: Instant, + ka_timer: Option, +} + +struct Entry { + pipe: EntryPipe, + flags: EntryFlags, } enum EntryPipe { @@ -78,11 +85,6 @@ impl EntryPipe { } } -struct Entry { - pipe: EntryPipe, - flags: EntryFlags, -} - impl Http1 where T: IoStream, @@ -92,6 +94,15 @@ where settings: WorkerSettings, stream: T, addr: Option, buf: BytesMut, is_eof: bool, keepalive_timer: Option, ) -> Self { + let ka_enabled = settings.keep_alive_enabled(); + let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { + (delay.deadline(), Some(delay)) + } else if let Some(delay) = settings.keep_alive_timer() { + (delay.deadline(), Some(delay)) + } else { + (settings.now(), None) + }; + Http1 { flags: if is_eof { Flags::READ_DISCONNECTED @@ -105,7 +116,9 @@ where addr, buf, settings, - keepalive_timer, + ka_timer, + ka_expire, + ka_enabled, } } @@ -143,9 +156,6 @@ where for task in &mut self.tasks { task.pipe.disconnected(); } - - // kill keepalive - self.keepalive_timer.take(); } fn read_disconnected(&mut self) { @@ -163,16 +173,9 @@ where #[inline] pub fn poll(&mut self) -> Poll<(), ()> { - // keep-alive timer - if let Some(ref mut timer) = self.keepalive_timer { - match timer.poll() { - Ok(Async::Ready(_)) => { - trace!("Keep-alive timeout, close connection"); - self.flags.insert(Flags::SHUTDOWN); - } - Ok(Async::NotReady) => (), - Err(_) => unreachable!(), - } + // check connection keep-alive + if !self.poll_keep_alive() { + return Ok(Async::Ready(())); } // shutdown @@ -203,11 +206,70 @@ where self.flags.insert(Flags::SHUTDOWN); return self.poll(); } - Async::NotReady => return Ok(Async::NotReady), + Async::NotReady => { + // deal with keep-alive and steam eof (client-side write shutdown) + if self.tasks.is_empty() { + // handle stream eof + if self.flags.contains(Flags::READ_DISCONNECTED) { + self.flags.insert(Flags::SHUTDOWN); + return self.poll(); + } + // no keep-alive + if self.flags.contains(Flags::ERROR) + || (!self.flags.contains(Flags::KEEPALIVE) + || !self.ka_enabled) + && self.flags.contains(Flags::STARTED) + { + self.flags.insert(Flags::SHUTDOWN); + return self.poll(); + } + } + return Ok(Async::NotReady); + } } } } + /// keep-alive timer. returns `true` is keep-alive, otherwise drop + fn poll_keep_alive(&mut self) -> bool { + let timer = if let Some(ref mut timer) = self.ka_timer { + match timer.poll() { + Ok(Async::Ready(_)) => { + if timer.deadline() >= self.ka_expire { + // check for any outstanding request handling + if self.tasks.is_empty() { + // if we get timer during shutdown, just drop connection + if self.flags.contains(Flags::SHUTDOWN) { + return false; + } else { + trace!("Keep-alive timeout, close connection"); + self.flags.insert(Flags::SHUTDOWN); + None + } + } else { + self.settings.keep_alive_timer() + } + } else { + Some(Delay::new(self.ka_expire)) + } + } + Ok(Async::NotReady) => None, + Err(e) => { + error!("Timer error {:?}", e); + return false; + } + } + } else { + None + }; + + if let Some(mut timer) = timer { + let _ = timer.poll(); + self.ka_timer = Some(timer); + } + true + } + #[inline] /// read data from stream pub fn poll_io(&mut self) { @@ -283,6 +345,11 @@ where } // no more IO for this iteration Ok(Async::NotReady) => { + // check if we need timer + if self.ka_timer.is_some() && self.stream.upgrade() { + self.ka_timer.take(); + } + // check if previously read backpressure was enabled if self.can_read() && !retry { return Ok(Async::Ready(true)); @@ -348,32 +415,6 @@ where } } - // deal with keep-alive and steam eof (client-side write shutdown) - if self.tasks.is_empty() { - // handle stream eof - if self.flags.contains(Flags::READ_DISCONNECTED) { - return Ok(Async::Ready(false)); - } - // no keep-alive - if self.flags.contains(Flags::ERROR) - || (!self.flags.contains(Flags::KEEPALIVE) - || !self.settings.keep_alive_enabled()) - && self.flags.contains(Flags::STARTED) - { - return Ok(Async::Ready(false)); - } - - // start keep-alive timer - let keep_alive = self.settings.keep_alive(); - if self.keepalive_timer.is_none() && keep_alive > 0 { - trace!("Start keep-alive timer"); - let mut timer = - Delay::new(Instant::now() + Duration::from_secs(keep_alive)); - // register timer - let _ = timer.poll(); - self.keepalive_timer = Some(timer); - } - } Ok(Async::NotReady) } @@ -385,9 +426,12 @@ where } pub fn parse(&mut self) { + let mut updated = false; + 'outer: loop { match self.decoder.decode(&mut self.buf, &self.settings) { Ok(Some(Message::Message { mut msg, payload })) => { + updated = true; self.flags.insert(Flags::STARTED); if payload { @@ -403,9 +447,6 @@ where // set remote addr msg.inner_mut().addr = self.addr; - // stop keepalive timer - self.keepalive_timer.take(); - // search handler for request match self.settings.handler().handle(msg) { Ok(mut pipe) => { @@ -430,7 +471,7 @@ where } continue 'outer; } - Ok(Async::NotReady) => {} + Ok(Async::NotReady) => (), Err(err) => { error!("Unhandled error: {}", err); self.flags.insert(Flags::ERROR); @@ -460,6 +501,7 @@ where self.push_response_entry(StatusCode::NOT_FOUND); } Ok(Some(Message::Chunk(chunk))) => { + updated = true; if let Some(ref mut payload) = self.payload { payload.feed_data(chunk); } else { @@ -470,6 +512,7 @@ where } } Ok(Some(Message::Eof)) => { + updated = true; if let Some(mut payload) = self.payload.take() { payload.feed_eof(); } else { @@ -489,6 +532,7 @@ where break; } Err(e) => { + updated = false; self.flags.insert(Flags::ERROR); if let Some(mut payload) = self.payload.take() { let e = match e { @@ -504,6 +548,12 @@ where } } } + + if self.ka_timer.is_some() && updated { + if let Some(expire) = self.settings.keep_alive_expire() { + self.ka_expire = expire; + } + } } } @@ -512,7 +562,9 @@ mod tests { use std::net::Shutdown; use std::{cmp, io, time}; + use actix::System; use bytes::{Buf, Bytes, BytesMut}; + use futures::future; use http::{Method, Version}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -647,15 +699,19 @@ mod tests { #[test] fn test_req_parse_err() { - let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - let readbuf = BytesMut::new(); - let settings = wrk_settings(); + let mut sys = System::new("test"); + sys.block_on(future::lazy(|| { + let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); + let readbuf = BytesMut::new(); + let settings = wrk_settings(); - let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, false, None); - h1.poll_io(); - h1.poll_io(); - assert!(h1.flags.contains(Flags::ERROR)); - assert_eq!(h1.tasks.len(), 1); + let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, false, None); + h1.poll_io(); + h1.poll_io(); + assert!(h1.flags.contains(Flags::ERROR)); + assert_eq!(h1.tasks.len(), 1); + future::ok::<_, ()>(()) + })); } #[test] diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 15451659..3036aa08 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -66,6 +66,10 @@ impl H1Writer { self.flags.insert(Flags::DISCONNECTED); } + pub fn upgrade(&self) -> bool { + self.flags.contains(Flags::UPGRADE) + } + pub fn keepalive(&self) -> bool { self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) } diff --git a/src/server/h2.rs b/src/server/h2.rs index f31c2db3..d9ca2f64 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -2,7 +2,7 @@ use std::collections::VecDeque; use std::io::{Read, Write}; use std::net::SocketAddr; use std::rc::Rc; -use std::time::{Duration, Instant}; +use std::time::Instant; use std::{cmp, io, mem}; use bytes::{Buf, Bytes}; @@ -232,16 +232,15 @@ where // start keep-alive timer if self.tasks.is_empty() { 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 = Delay::new( - Instant::now() - + Duration::new(keep_alive, 0), - ); - // register timeout - let _ = timeout.poll(); - self.keepalive_timer = Some(timeout); + if self.keepalive_timer.is_none() { + if let Some(ka) = self.settings.keep_alive() { + trace!("Start keep-alive timer"); + let mut timeout = + Delay::new(Instant::now() + ka); + // register timeout + let _ = timeout.poll(); + self.keepalive_timer = Some(timeout); + } } } else { // keep-alive disable, drop connection diff --git a/src/server/settings.rs b/src/server/settings.rs index db5f6c57..5ca77729 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -137,7 +137,7 @@ pub struct WorkerSettings(Rc>); struct Inner { handler: H, - keep_alive: u64, + keep_alive: Option, client_timeout: u64, ka_enabled: bool, bytes: Rc, @@ -161,6 +161,11 @@ impl WorkerSettings { KeepAlive::Os | KeepAlive::Tcp(_) => (0, true), KeepAlive::Disabled => (0, false), }; + let keep_alive = if ka_enabled && keep_alive > 0 { + Some(Duration::from_secs(keep_alive)) + } else { + None + }; WorkerSettings(Rc::new(Inner { handler, @@ -183,17 +188,7 @@ impl WorkerSettings { } #[inline] - pub fn keep_alive_timer(&self) -> Option { - let ka = self.0.keep_alive; - if ka != 0 { - Some(Delay::new(Instant::now() + Duration::from_secs(ka))) - } else { - None - } - } - - #[inline] - pub fn keep_alive(&self) -> u64 { + pub fn keep_alive(&self) -> Option { self.0.keep_alive } @@ -202,16 +197,6 @@ impl WorkerSettings { self.0.ka_enabled } - #[inline] - pub fn client_timer(&self) -> Option { - let delay = self.0.client_timeout; - if delay != 0 { - Some(Delay::new(Instant::now() + Duration::from_millis(delay))) - } else { - None - } - } - pub(crate) fn get_bytes(&self) -> BytesMut { self.0.bytes.get_bytes() } @@ -231,6 +216,34 @@ impl WorkerSettings { } impl WorkerSettings { + #[inline] + pub fn client_timer(&self) -> Option { + let delay = self.0.client_timeout; + if delay != 0 { + Some(Delay::new(self.now() + Duration::from_millis(delay))) + } else { + None + } + } + + #[inline] + pub fn keep_alive_timer(&self) -> Option { + if let Some(ka) = self.0.keep_alive { + Some(Delay::new(self.now() + ka)) + } else { + None + } + } + + /// Keep-alive expire time + pub fn keep_alive_expire(&self) -> Option { + if let Some(ka) = self.0.keep_alive { + Some(self.now() + ka) + } else { + None + } + } + pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) { // Unsafe: WorkerSetting is !Sync and !Send let date_bytes = unsafe { @@ -258,9 +271,29 @@ impl WorkerSettings { dst.extend_from_slice(date_bytes); } } + + #[inline] + pub(crate) fn now(&self) -> Instant { + unsafe { + let date = &mut (*self.0.date.get()); + if !date.0 { + date.1.update(); + date.0 = true; + + // periodic date update + let s = self.clone(); + spawn(sleep(Duration::from_secs(1)).then(move |_| { + s.update_date(); + future::ok(()) + })); + } + date.1.current + } + } } struct Date { + current: Instant, bytes: [u8; DATE_VALUE_LENGTH], pos: usize, } @@ -268,6 +301,7 @@ struct Date { impl Date { fn new() -> Date { let mut date = Date { + current: Instant::now(), bytes: [0; DATE_VALUE_LENGTH], pos: 0, }; @@ -276,6 +310,7 @@ impl Date { } fn update(&mut self) { self.pos = 0; + self.current = Instant::now(); write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); } } From 5966ee6192bcd12580637f9f388244def7a80752 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Sep 2018 16:03:53 -0700 Subject: [PATCH 0683/1635] add HttpServer::register() function, allows to register services in actix net server --- src/client/connector.rs | 2 +- src/server/builder.rs | 19 +++++++++++++++++++ src/server/http.rs | 15 +++++++++++++++ src/server/ssl/mod.rs | 2 +- src/server/ssl/openssl.rs | 26 +++++++++++++++++++++++--- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 32426e0a..3f4916af 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -290,7 +290,7 @@ impl Default for ClientConnector { feature = "ssl", feature = "tls", feature = "rust-tls", - ),))] + )))] { () } diff --git a/src/server/builder.rs b/src/server/builder.rs index 8c0a0f62..c9a97af3 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -32,6 +32,25 @@ where no_client_timer: bool, } +impl HttpServiceBuilder> +where + Io: IoStream + Send, + F: Fn() -> H + Send + Clone + 'static, + H: IntoHttpHandler, + A: AcceptorServiceFactory, + ::InitError: fmt::Debug, +{ + /// Create http service builder with default pipeline factory + pub fn with_default_pipeline(factory: F, acceptor: A) -> Self { + Self { + factory, + acceptor, + pipeline: DefaultPipelineFactory::new(), + no_client_timer: false, + } + } +} + impl HttpServiceBuilder where F: Fn() -> H + Send + Clone + 'static, diff --git a/src/server/http.rs b/src/server/http.rs index 034f903e..6344771b 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -520,6 +520,21 @@ impl H + Send + Clone> HttpServer { self.start(); sys.run(); } + + /// Register current http server as actix-net's server service + pub fn register(self, mut srv: Server) -> Server { + for socket in self.sockets { + srv = socket.handler.register( + srv, + socket.lst, + self.host.clone(), + socket.addr, + self.keep_alive.clone(), + self.client_timeout, + ); + } + srv + } } fn create_tcp_listener( diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs index 1d6b55b1..c09573fe 100644 --- a/src/server/ssl/mod.rs +++ b/src/server/ssl/mod.rs @@ -1,7 +1,7 @@ #[cfg(any(feature = "alpn", feature = "ssl"))] mod openssl; #[cfg(any(feature = "alpn", feature = "ssl"))] -pub use self::openssl::*; +pub use self::openssl::{openssl_acceptor_with_flags, OpensslAcceptor}; #[cfg(feature = "tls")] mod nativetls; diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs index 34315523..590dc0bb 100644 --- a/src/server/ssl/openssl.rs +++ b/src/server/ssl/openssl.rs @@ -1,14 +1,34 @@ use std::net::Shutdown; use std::{io, time}; +use actix_net::ssl; use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; +use tokio_io::{AsyncRead, AsyncWrite}; use tokio_openssl::SslStream; use server::{IoStream, ServerFlags}; -/// Configure `SslAcceptorBuilder` with enabled `HTTP/2` and `HTTP1.1` support. -pub fn openssl_acceptor(builder: SslAcceptorBuilder) -> io::Result { - openssl_acceptor_with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) +/// Support `SSL` connections via openssl package +/// +/// `ssl` feature enables `OpensslAcceptor` type +pub struct OpensslAcceptor { + _t: ssl::OpensslAcceptor, +} + +impl OpensslAcceptor { + /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. + pub fn new(builder: SslAcceptorBuilder) -> io::Result> { + OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) + } + + /// Create `OpensslAcceptor` with custom server flags. + pub fn with_flags( + mut builder: SslAcceptorBuilder, flags: ServerFlags, + ) -> io::Result> { + let acceptor = openssl_acceptor_with_flags(builder, flags)?; + + Ok(ssl::OpensslAcceptor::new(acceptor)) + } } /// Configure `SslAcceptorBuilder` with custom server flags. From c1e0b4f32275b212992c9f9991e3f4797e66c152 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 14:43:06 -0700 Subject: [PATCH 0684/1635] expose internal http server types and allow to create custom http pipelines --- src/server/builder.rs | 132 +++++------------------------------------ src/server/channel.rs | 12 ++-- src/server/error.rs | 34 +++++++++++ src/server/h1.rs | 12 ++-- src/server/h2.rs | 14 ++--- src/server/http.rs | 34 ++++++----- src/server/incoming.rs | 8 ++- src/server/mod.rs | 8 +-- src/server/service.rs | 12 ++-- src/server/settings.rs | 24 ++++---- tests/test_server.rs | 37 ++++++++++++ 11 files changed, 148 insertions(+), 179 deletions(-) diff --git a/src/server/builder.rs b/src/server/builder.rs index c9a97af3..8e7f82f8 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -1,4 +1,3 @@ -use std::marker::PhantomData; use std::{fmt, net}; use actix_net::either::Either; @@ -9,61 +8,39 @@ use super::acceptor::{ AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor, }; use super::error::AcceptorError; -use super::handler::{HttpHandler, IntoHttpHandler}; +use super::handler::IntoHttpHandler; use super::service::HttpService; use super::settings::{ServerSettings, WorkerSettings}; -use super::{IoStream, KeepAlive}; +use super::KeepAlive; pub(crate) trait ServiceProvider { fn register( - &self, server: Server, lst: net::TcpListener, host: Option, + &self, server: Server, lst: net::TcpListener, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, ) -> Server; } /// Utility type that builds complete http pipeline -pub struct HttpServiceBuilder +pub struct HttpServiceBuilder where F: Fn() -> H + Send + Clone, { factory: F, acceptor: A, - pipeline: P, no_client_timer: bool, } -impl HttpServiceBuilder> -where - Io: IoStream + Send, - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, -{ - /// Create http service builder with default pipeline factory - pub fn with_default_pipeline(factory: F, acceptor: A) -> Self { - Self { - factory, - acceptor, - pipeline: DefaultPipelineFactory::new(), - no_client_timer: false, - } - } -} - -impl HttpServiceBuilder +impl HttpServiceBuilder where F: Fn() -> H + Send + Clone + 'static, H: IntoHttpHandler, A: AcceptorServiceFactory, ::InitError: fmt::Debug, - P: HttpPipelineFactory, { /// Create http service builder - pub fn new(factory: F, acceptor: A, pipeline: P) -> Self { + pub fn new(factory: F, acceptor: A) -> Self { Self { factory, - pipeline, acceptor, no_client_timer: false, } @@ -75,34 +52,20 @@ where } /// Use different acceptor factory - pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder + pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder where A1: AcceptorServiceFactory, ::InitError: fmt::Debug, { HttpServiceBuilder { acceptor, - pipeline: self.pipeline, - factory: self.factory.clone(), - no_client_timer: self.no_client_timer, - } - } - - /// Use different pipeline factory - pub fn pipeline(self, pipeline: P1) -> HttpServiceBuilder - where - P1: HttpPipelineFactory, - { - HttpServiceBuilder { - pipeline, - acceptor: self.acceptor, factory: self.factory.clone(), no_client_timer: self.no_client_timer, } } fn finish( - &self, host: Option, addr: net::SocketAddr, keep_alive: KeepAlive, + &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, ) -> impl ServiceFactory { let timeout = if self.no_client_timer { @@ -111,7 +74,6 @@ where client_timeout }; let factory = self.factory.clone(); - let pipeline = self.pipeline.clone(); let acceptor = self.acceptor.clone(); move || { let app = (factory)().into_handler(); @@ -119,7 +81,7 @@ where app, keep_alive, timeout as u64, - ServerSettings::new(Some(addr), &host, false), + ServerSettings::new(addr, &host, false), ); if timeout == 0 { @@ -129,8 +91,7 @@ where .map_err(|_| ()) .map_init_err(|_| ()) .and_then( - pipeline - .create(settings) + HttpService::new(settings) .map_init_err(|_| ()) .map_err(|_| ()), ), @@ -142,8 +103,7 @@ where .map_err(|_| ()) .map_init_err(|_| ()) .and_then( - pipeline - .create(settings) + HttpService::new(settings) .map_init_err(|_| ()) .map_err(|_| ()), ), @@ -153,33 +113,30 @@ where } } -impl Clone for HttpServiceBuilder +impl Clone for HttpServiceBuilder where F: Fn() -> H + Send + Clone, H: IntoHttpHandler, A: AcceptorServiceFactory, - P: HttpPipelineFactory, { fn clone(&self) -> Self { HttpServiceBuilder { factory: self.factory.clone(), acceptor: self.acceptor.clone(), - pipeline: self.pipeline.clone(), no_client_timer: self.no_client_timer, } } } -impl ServiceProvider for HttpServiceBuilder +impl ServiceProvider for HttpServiceBuilder where F: Fn() -> H + Send + Clone + 'static, A: AcceptorServiceFactory, ::InitError: fmt::Debug, - P: HttpPipelineFactory, H: IntoHttpHandler, { fn register( - &self, server: Server, lst: net::TcpListener, host: Option, + &self, server: Server, lst: net::TcpListener, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, ) -> Server { server.listen2( @@ -189,64 +146,3 @@ where ) } } - -pub trait HttpPipelineFactory: Send + Clone + 'static { - type Io: IoStream; - type NewService: NewService; - - fn create(&self, settings: WorkerSettings) -> Self::NewService; -} - -impl HttpPipelineFactory for F -where - F: Fn(WorkerSettings) -> T + Send + Clone + 'static, - T: NewService, - T::Request: IoStream, - H: HttpHandler, -{ - type Io = T::Request; - type NewService = T; - - fn create(&self, settings: WorkerSettings) -> T { - (self)(settings) - } -} - -pub(crate) struct DefaultPipelineFactory { - _t: PhantomData<(H, Io)>, -} - -unsafe impl Send for DefaultPipelineFactory {} - -impl DefaultPipelineFactory -where - Io: IoStream + Send, - H: HttpHandler + 'static, -{ - pub fn new() -> Self { - Self { _t: PhantomData } - } -} - -impl Clone for DefaultPipelineFactory -where - Io: IoStream, - H: HttpHandler, -{ - fn clone(&self) -> Self { - Self { _t: PhantomData } - } -} - -impl HttpPipelineFactory for DefaultPipelineFactory -where - Io: IoStream, - H: HttpHandler + 'static, -{ - type Io = Io; - type NewService = HttpService; - - fn create(&self, settings: WorkerSettings) -> Self::NewService { - HttpService::new(settings) - } -} diff --git a/src/server/channel.rs b/src/server/channel.rs index c1e6b6b2..3cea291f 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -6,6 +6,7 @@ use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; +use super::error::HttpDispatchError; use super::settings::WorkerSettings; use super::{h1, h2, HttpHandler, IoStream}; @@ -86,7 +87,7 @@ where H: HttpHandler + 'static, { type Item = (); - type Error = (); + type Error = HttpDispatchError; fn poll(&mut self) -> Poll { // keep-alive timer @@ -127,6 +128,7 @@ where return h2.poll(); } Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => { + let mut err = None; let mut disconnect = false; match io.read_available(buf) { Ok(Async::Ready((read_some, stream_closed))) => { @@ -136,14 +138,16 @@ where disconnect = true; } } - Err(_) => { - disconnect = true; + Err(e) => { + err = Some(e.into()); } _ => (), } if disconnect { debug!("Ignored premature client disconnection"); - return Err(()); + return Ok(Async::Ready(())); + } else if let Some(e) = err { + return Err(e); } if buf.len() >= 14 { diff --git a/src/server/error.rs b/src/server/error.rs index ff8b831a..b8b60226 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -1,6 +1,7 @@ use std::io; use futures::{Async, Poll}; +use http2; use super::{helpers, HttpHandlerTask, Writer}; use http::{StatusCode, Version}; @@ -19,6 +20,39 @@ pub enum AcceptorError { Timeout, } +#[derive(Fail, Debug)] +/// A set of errors that can occur during dispatching http requests +pub enum HttpDispatchError { + /// Application error + #[fail(display = "Application specific error")] + AppError, + + /// An `io::Error` that occurred while trying to read or write to a network + /// stream. + #[fail(display = "IO error: {}", _0)] + Io(io::Error), + + /// The first request did not complete within the specified timeout. + #[fail(display = "The first request did not complete within the specified timeout")] + SlowRequestTimeout, + + /// HTTP2 error + #[fail(display = "HTTP2 error: {}", _0)] + Http2(http2::Error), +} + +impl From for HttpDispatchError { + fn from(err: io::Error) -> Self { + HttpDispatchError::Io(err) + } +} + +impl From for HttpDispatchError { + fn from(err: http2::Error) -> Self { + HttpDispatchError::Http2(err) + } +} + pub(crate) struct ServerError(Version, StatusCode); impl ServerError { diff --git a/src/server/h1.rs b/src/server/h1.rs index 76c0d4b6..b1798122 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -10,7 +10,7 @@ use error::{Error, PayloadError}; use http::{StatusCode, Version}; use payload::{Payload, PayloadStatus, PayloadWriter}; -use super::error::ServerError; +use super::error::{HttpDispatchError, ServerError}; use super::h1decoder::{DecoderError, H1Decoder, Message}; use super::h1writer::H1Writer; use super::input::PayloadType; @@ -172,7 +172,7 @@ where } #[inline] - pub fn poll(&mut self) -> Poll<(), ()> { + pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { // check connection keep-alive if !self.poll_keep_alive() { return Ok(Async::Ready(())); @@ -190,7 +190,7 @@ where Ok(Async::Ready(_)) => return Ok(Async::Ready(())), Err(err) => { debug!("Error sending data: {}", err); - return Err(()); + return Err(err.into()); } } } @@ -303,7 +303,7 @@ where } } - pub fn poll_handler(&mut self) -> Poll { + pub fn poll_handler(&mut self) -> Poll { let retry = self.can_read(); // check in-flight messages @@ -321,7 +321,7 @@ where return Ok(Async::NotReady); } self.flags.insert(Flags::ERROR); - return Err(()); + return Err(HttpDispatchError::AppError); } match self.tasks[idx].pipe.poll_io(&mut self.stream) { @@ -404,7 +404,7 @@ where debug!("Error sending data: {}", err); self.read_disconnected(); self.write_disconnected(); - return Err(()); + return Err(err.into()); } Ok(Async::Ready(_)) => { // non consumed payload in that case close connection diff --git a/src/server/h2.rs b/src/server/h2.rs index d9ca2f64..589e77c2 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -19,7 +19,7 @@ use http::{StatusCode, Version}; use payload::{Payload, PayloadStatus, PayloadWriter}; use uri::Url; -use super::error::ServerError; +use super::error::{HttpDispatchError, ServerError}; use super::h2writer::H2Writer; use super::input::PayloadType; use super::settings::WorkerSettings; @@ -86,7 +86,7 @@ where &self.settings } - pub fn poll(&mut self) -> Poll<(), ()> { + pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { // server if let State::Connection(ref mut conn) = self.state { // keep-alive timer @@ -244,9 +244,7 @@ where } } else { // keep-alive disable, drop connection - return conn.poll_close().map_err(|e| { - error!("Error during connection close: {}", e) - }); + return conn.poll_close().map_err(|e| e.into()); } } else { // keep-alive unset, rely on operating system @@ -267,9 +265,7 @@ where if not_ready { if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) { - return conn - .poll_close() - .map_err(|e| error!("Error during connection close: {}", e)); + return conn.poll_close().map_err(|e| e.into()); } else { return Ok(Async::NotReady); } @@ -284,7 +280,7 @@ where Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { trace!("Error handling connection: {}", err); - return Err(()); + return Err(err.into()); } } } else { diff --git a/src/server/http.rs b/src/server/http.rs index 6344771b..311c53cb 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -18,7 +18,7 @@ use openssl::ssl::SslAcceptorBuilder; use rustls::ServerConfig; use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; -use super::builder::{DefaultPipelineFactory, HttpServiceBuilder, ServiceProvider}; +use super::builder::{HttpServiceBuilder, ServiceProvider}; use super::{IntoHttpHandler, KeepAlive}; struct Socket { @@ -131,7 +131,7 @@ where self } - /// Set server client timneout in milliseconds for first request. + /// Set server client timeout in milliseconds for first request. /// /// Defines a timeout for reading client request header. If a client does not transmit /// the entire set headers within this time, the request is terminated with @@ -218,11 +218,8 @@ where addr, scheme: "http", handler: Box::new( - HttpServiceBuilder::new( - self.factory.clone(), - DefaultAcceptor, - DefaultPipelineFactory::new(), - ).no_client_timer(), + HttpServiceBuilder::new(self.factory.clone(), DefaultAcceptor) + .no_client_timer(), ), }); @@ -231,7 +228,7 @@ where #[doc(hidden)] /// Use listener for accepting incoming connection requests - pub(crate) fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self + pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self where A: AcceptorServiceFactory, ::InitError: fmt::Debug, @@ -241,11 +238,7 @@ where lst, addr, scheme: "https", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - acceptor, - DefaultPipelineFactory::new(), - )), + handler: Box::new(HttpServiceBuilder::new(self.factory.clone(), acceptor)), }); self @@ -339,7 +332,6 @@ where handler: Box::new(HttpServiceBuilder::new( self.factory.clone(), acceptor.clone(), - DefaultPipelineFactory::new(), )), }); } @@ -483,10 +475,15 @@ impl H + Send + Clone> HttpServer { let sockets = mem::replace(&mut self.sockets, Vec::new()); for socket in sockets { + let host = self + .host + .as_ref() + .map(|h| h.to_owned()) + .unwrap_or_else(|| format!("{}", socket.addr)); srv = socket.handler.register( srv, socket.lst, - self.host.clone(), + host, socket.addr, self.keep_alive.clone(), self.client_timeout, @@ -524,10 +521,15 @@ impl H + Send + Clone> HttpServer { /// Register current http server as actix-net's server service pub fn register(self, mut srv: Server) -> Server { for socket in self.sockets { + let host = self + .host + .as_ref() + .map(|h| h.to_owned()) + .unwrap_or_else(|| format!("{}", socket.addr)); srv = socket.handler.register( srv, socket.lst, - self.host.clone(), + host, socket.addr, self.keep_alive.clone(), self.client_timeout, diff --git a/src/server/incoming.rs b/src/server/incoming.rs index 7ab289d0..c7728008 100644 --- a/src/server/incoming.rs +++ b/src/server/incoming.rs @@ -2,7 +2,7 @@ use std::{io, net}; use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message}; -use futures::Stream; +use futures::{Future, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use super::channel::{HttpChannel, WrapperStream}; @@ -36,7 +36,7 @@ where apps, self.keep_alive, self.client_timeout as u64, - ServerSettings::new(Some(addr), &self.host, secure), + ServerSettings::new(addr, "127.0.0.1:8080", secure), ); // start server @@ -65,6 +65,8 @@ where type Result = (); fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg, None)); + Arbiter::spawn( + HttpChannel::new(self.settings.clone(), msg, None).map_err(|_| ()), + ); } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 1e145571..f9d2b585 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -140,15 +140,15 @@ mod ssl; pub use self::handler::*; pub use self::http::HttpServer; pub use self::message::Request; -pub use self::settings::ServerSettings; pub use self::ssl::*; +pub use self::error::{AcceptorError, HttpDispatchError}; +pub use self::service::HttpService; +pub use self::settings::{ServerSettings, WorkerSettings}; + #[doc(hidden)] pub use self::helpers::write_content_length; -#[doc(hidden)] -pub use self::builder::HttpServiceBuilder; - use body::Binary; use extensions::Extensions; use header::ContentEncoding; diff --git a/src/server/service.rs b/src/server/service.rs index 042c86ed..2988bc66 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -5,11 +5,12 @@ use futures::future::{ok, FutureResult}; use futures::{Async, Poll}; use super::channel::HttpChannel; +use super::error::HttpDispatchError; use super::handler::HttpHandler; use super::settings::WorkerSettings; use super::IoStream; -pub(crate) struct HttpService +pub struct HttpService where H: HttpHandler, Io: IoStream, @@ -23,6 +24,7 @@ where H: HttpHandler, Io: IoStream, { + /// Create new `HttpService` instance. pub fn new(settings: WorkerSettings) -> Self { HttpService { settings, @@ -38,17 +40,17 @@ where { type Request = Io; type Response = (); - type Error = (); + type Error = HttpDispatchError; type InitError = (); type Service = HttpServiceHandler; - type Future = FutureResult; + type Future = FutureResult; fn new_service(&self) -> Self::Future { ok(HttpServiceHandler::new(self.settings.clone())) } } -pub(crate) struct HttpServiceHandler +pub struct HttpServiceHandler where H: HttpHandler, Io: IoStream, @@ -84,7 +86,7 @@ where { type Request = Io; type Response = (); - type Error = (); + type Error = HttpDispatchError; type Future = HttpChannel; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/server/settings.rs b/src/server/settings.rs index 5ca77729..fbe515f9 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -43,7 +43,7 @@ lazy_static! { /// Various server settings pub struct ServerSettings { - addr: Option, + addr: net::SocketAddr, secure: bool, host: String, cpu_pool: LazyCell, @@ -65,7 +65,7 @@ impl Clone for ServerSettings { impl Default for ServerSettings { fn default() -> Self { ServerSettings { - addr: None, + addr: "127.0.0.1:8080".parse().unwrap(), secure: false, host: "localhost:8080".to_owned(), responses: HttpResponsePool::get_pool(), @@ -76,16 +76,8 @@ impl Default for ServerSettings { impl ServerSettings { /// Crate server settings instance - pub(crate) fn new( - addr: Option, host: &Option, secure: bool, - ) -> ServerSettings { - let host = if let Some(ref host) = *host { - host.clone() - } else if let Some(ref addr) = addr { - format!("{}", addr) - } else { - "localhost".to_owned() - }; + pub fn new(addr: net::SocketAddr, host: &str, secure: bool) -> ServerSettings { + let host = host.to_owned(); let cpu_pool = LazyCell::new(); let responses = HttpResponsePool::get_pool(); ServerSettings { @@ -98,7 +90,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) -> net::SocketAddr { self.addr } @@ -153,7 +145,7 @@ impl Clone for WorkerSettings { } impl WorkerSettings { - pub(crate) fn new( + pub fn new( handler: H, keep_alive: KeepAlive, client_timeout: u64, settings: ServerSettings, ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { @@ -188,11 +180,13 @@ impl WorkerSettings { } #[inline] + /// Keep alive duration if configured. pub fn keep_alive(&self) -> Option { self.0.keep_alive } #[inline] + /// Return state of connection keep-alive funcitonality pub fn keep_alive_enabled(&self) -> bool { self.0.ka_enabled } @@ -217,6 +211,7 @@ impl WorkerSettings { impl WorkerSettings { #[inline] + /// Client timeout for first request. pub fn client_timer(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { @@ -227,6 +222,7 @@ impl WorkerSettings { } #[inline] + /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { if let Some(ka) = self.0.keep_alive { Some(Delay::new(self.now() + ka)) diff --git a/tests/test_server.rs b/tests/test_server.rs index c1dbf531..66b96ecc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,4 +1,5 @@ extern crate actix; +extern crate actix_net; extern crate actix_web; #[cfg(feature = "brotli")] extern crate brotli2; @@ -18,6 +19,7 @@ use std::io::{Read, Write}; use std::sync::Arc; use std::{thread, time}; +use actix_net::server::Server; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{Bytes, BytesMut}; @@ -1010,3 +1012,38 @@ fn test_server_cookies() { assert_eq!(cookies[1], first_cookie); } } + +#[test] +fn test_custom_pipeline() { + use actix::System; + use actix_web::server::{HttpService, KeepAlive, ServerSettings, WorkerSettings}; + + let addr = test::TestServer::unused_addr(); + + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let app = App::new() + .route("/", http::Method::GET, |_: HttpRequest| "OK") + .finish(); + let settings = WorkerSettings::new( + app, + KeepAlive::Disabled, + 10, + ServerSettings::new(addr, "localhost", false), + ); + + HttpService::new(settings) + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + } +} From 2217a152cb0fcbbc5a5485936ceeb684bb532e41 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 15:19:49 -0700 Subject: [PATCH 0685/1635] expose app error by http service --- src/client/connector.rs | 7 ++----- src/server/error.rs | 14 ++++++++++++-- src/server/h1.rs | 14 +++++++++++--- src/server/settings.rs | 1 + 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 3f4916af..88d6dfd6 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -285,12 +285,9 @@ impl Default for ClientConnector { Arc::new(config) } + #[cfg_attr(rustfmt, rustfmt_skip)] #[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls", - )))] + feature = "alpn", feature = "ssl", feature = "tls", feature = "rust-tls")))] { () } diff --git a/src/server/error.rs b/src/server/error.rs index b8b60226..4396e6a2 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -24,8 +24,8 @@ pub enum AcceptorError { /// A set of errors that can occur during dispatching http requests pub enum HttpDispatchError { /// Application error - #[fail(display = "Application specific error")] - AppError, + #[fail(display = "Application specific error: {}", _0)] + App(Error), /// An `io::Error` that occurred while trying to read or write to a network /// stream. @@ -39,6 +39,16 @@ pub enum HttpDispatchError { /// HTTP2 error #[fail(display = "HTTP2 error: {}", _0)] Http2(http2::Error), + + /// Unknown error + #[fail(display = "Unknown error")] + Unknown, +} + +impl From for HttpDispatchError { + fn from(err: Error) -> Self { + HttpDispatchError::App(err) + } } impl From for HttpDispatchError { diff --git a/src/server/h1.rs b/src/server/h1.rs index b1798122..a1a6c0af 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -49,6 +49,7 @@ pub(crate) struct Http1 { payload: Option, buf: BytesMut, tasks: VecDeque>, + error: Option, ka_enabled: bool, ka_expire: Instant, ka_timer: Option, @@ -113,6 +114,7 @@ where decoder: H1Decoder::new(), payload: None, tasks: VecDeque::new(), + error: None, addr, buf, settings, @@ -321,7 +323,11 @@ where return Ok(Async::NotReady); } self.flags.insert(Flags::ERROR); - return Err(HttpDispatchError::AppError); + return Err(self + .error + .take() + .map(|e| e.into()) + .unwrap_or(HttpDispatchError::Unknown)); } match self.tasks[idx].pipe.poll_io(&mut self.stream) { @@ -357,12 +363,13 @@ where io = true; } Err(err) => { + error!("Unhandled error1: {}", err); // it is not possible to recover from error // during pipe handling, so just drop connection self.read_disconnected(); self.write_disconnected(); self.tasks[idx].flags.insert(EntryFlags::ERROR); - error!("Unhandled error1: {}", err); + self.error = Some(err); continue; } } @@ -373,10 +380,11 @@ where self.tasks[idx].flags.insert(EntryFlags::FINISHED) } Err(err) => { + error!("Unhandled error: {}", err); self.read_disconnected(); self.write_disconnected(); self.tasks[idx].flags.insert(EntryFlags::ERROR); - error!("Unhandled error: {}", err); + self.error = Some(err); continue; } } diff --git a/src/server/settings.rs b/src/server/settings.rs index fbe515f9..fe9cd82a 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -145,6 +145,7 @@ impl Clone for WorkerSettings { } impl WorkerSettings { + /// Create instance of `WorkerSettings` pub fn new( handler: H, keep_alive: KeepAlive, client_timeout: u64, settings: ServerSettings, ) -> WorkerSettings { From 91af3ca148e7be9b48cd1d9bcaa316b442e2457c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 19:18:24 -0700 Subject: [PATCH 0686/1635] simplify h1 dispatcher --- src/lib.rs | 4 - src/server/error.rs | 12 ++ src/server/h1.rs | 425 ++++++++++++++++++---------------------- src/server/h1decoder.rs | 1 + src/server/handler.rs | 21 +- src/server/http.rs | 4 +- src/server/incoming.rs | 4 +- src/server/message.rs | 21 ++ 8 files changed, 249 insertions(+), 243 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 099b0b16..df3c3817 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,10 +81,6 @@ specialization, // for impl ErrorResponse for std::error::Error extern_prelude, ))] -#![cfg_attr( - feature = "cargo-clippy", - allow(decimal_literal_representation, suspicious_arithmetic_impl) -)] #![warn(missing_docs)] #[macro_use] diff --git a/src/server/error.rs b/src/server/error.rs index 4396e6a2..eb3e8847 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -36,10 +36,22 @@ pub enum HttpDispatchError { #[fail(display = "The first request did not complete within the specified timeout")] SlowRequestTimeout, + /// Shutdown timeout + #[fail(display = "Connection shutdown timeout")] + ShutdownTimeout, + /// HTTP2 error #[fail(display = "HTTP2 error: {}", _0)] Http2(http2::Error), + /// Malformed request + #[fail(display = "Malformed request")] + MalformedRequest, + + /// Internal error + #[fail(display = "Internal error")] + InternalError, + /// Unknown error #[fail(display = "Unknown error")] Unknown, diff --git a/src/server/h1.rs b/src/server/h1.rs index a1a6c0af..f3c71e3c 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -4,6 +4,7 @@ use std::time::Instant; use bytes::BytesMut; use futures::{Async, Future, Poll}; +use tokio_current_thread::spawn; use tokio_timer::Delay; use error::{Error, PayloadError}; @@ -13,17 +14,16 @@ use payload::{Payload, PayloadStatus, PayloadWriter}; use super::error::{HttpDispatchError, ServerError}; use super::h1decoder::{DecoderError, H1Decoder, Message}; use super::h1writer::H1Writer; +use super::handler::{HttpHandler, HttpHandlerTask, HttpHandlerTaskFut}; use super::input::PayloadType; use super::settings::WorkerSettings; -use super::Writer; -use super::{HttpHandler, HttpHandlerTask, IoStream}; +use super::{IoStream, Writer}; const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { pub struct Flags: u8 { const STARTED = 0b0000_0001; - const ERROR = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const SHUTDOWN = 0b0000_1000; const READ_DISCONNECTED = 0b0001_0000; @@ -32,14 +32,6 @@ bitflags! { } } -bitflags! { - struct EntryFlags: u8 { - const EOF = 0b0000_0001; - const ERROR = 0b0000_0010; - const FINISHED = 0b0000_0100; - } -} - pub(crate) struct Http1 { flags: Flags, settings: WorkerSettings, @@ -49,39 +41,40 @@ pub(crate) struct Http1 { payload: Option, buf: BytesMut, tasks: VecDeque>, - error: Option, + error: Option, ka_enabled: bool, ka_expire: Instant, ka_timer: Option, } -struct Entry { - pipe: EntryPipe, - flags: EntryFlags, -} - -enum EntryPipe { +enum Entry { Task(H::Task), Error(Box), } -impl EntryPipe { +impl Entry { + fn into_task(self) -> H::Task { + match self { + Entry::Task(task) => task, + Entry::Error(_) => panic!(), + } + } fn disconnected(&mut self) { match *self { - EntryPipe::Task(ref mut task) => task.disconnected(), - EntryPipe::Error(ref mut task) => task.disconnected(), + Entry::Task(ref mut task) => task.disconnected(), + Entry::Error(ref mut task) => task.disconnected(), } } fn poll_io(&mut self, io: &mut Writer) -> Poll { match *self { - EntryPipe::Task(ref mut task) => task.poll_io(io), - EntryPipe::Error(ref mut task) => task.poll_io(io), + Entry::Task(ref mut task) => task.poll_io(io), + Entry::Error(ref mut task) => task.poll_io(io), } } fn poll_completed(&mut self) -> Poll<(), Error> { match *self { - EntryPipe::Task(ref mut task) => task.poll_completed(), - EntryPipe::Error(ref mut task) => task.poll_completed(), + Entry::Task(ref mut task) => task.poll_completed(), + Entry::Error(ref mut task) => task.poll_completed(), } } } @@ -136,10 +129,7 @@ where #[inline] fn can_read(&self) -> bool { - if self - .flags - .intersects(Flags::ERROR | Flags::READ_DISCONNECTED) - { + if self.flags.intersects(Flags::READ_DISCONNECTED) { return false; } @@ -150,41 +140,46 @@ where } } - fn write_disconnected(&mut self) { - self.flags.insert(Flags::WRITE_DISCONNECTED); - - // notify all tasks - self.stream.disconnected(); - for task in &mut self.tasks { - task.pipe.disconnected(); - } - } - - fn read_disconnected(&mut self) { - self.flags.insert( - Flags::READ_DISCONNECTED - // on parse error, stop reading stream but tasks need to be - // completed - | Flags::ERROR, - ); - + // if checked is set to true, delay disconnect until all tasks have finished. + fn client_disconnected(&mut self, checked: bool) { + self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } + + if !checked || self.tasks.is_empty() { + self.flags.insert(Flags::WRITE_DISCONNECTED); + self.stream.disconnected(); + + // notify all tasks + for mut task in self.tasks.drain(..) { + task.disconnected(); + match task.poll_completed() { + Ok(Async::NotReady) => { + // spawn not completed task, it does not require access to io + // at this point + spawn(HttpHandlerTaskFut::new(task.into_task())); + } + Ok(Async::Ready(_)) => (), + Err(err) => { + error!("Unhandled application error: {}", err); + } + } + } + } } #[inline] pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { // check connection keep-alive - if !self.poll_keep_alive() { - return Ok(Async::Ready(())); - } + self.poll_keep_alive()?; // shutdown if self.flags.contains(Flags::SHUTDOWN) { - if self.flags.intersects( - Flags::ERROR | Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED, - ) { + if self + .flags + .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) + { return Ok(Async::Ready(())); } match self.stream.poll_completed(true) { @@ -197,44 +192,46 @@ where } } - self.poll_io(); + self.poll_io()?; - loop { + if !self.flags.contains(Flags::WRITE_DISCONNECTED) { match self.poll_handler()? { - Async::Ready(true) => { - self.poll_io(); - } + Async::Ready(true) => self.poll(), Async::Ready(false) => { self.flags.insert(Flags::SHUTDOWN); - return self.poll(); + self.poll() } Async::NotReady => { // deal with keep-alive and steam eof (client-side write shutdown) if self.tasks.is_empty() { // handle stream eof - if self.flags.contains(Flags::READ_DISCONNECTED) { - self.flags.insert(Flags::SHUTDOWN); - return self.poll(); + if self.flags.intersects( + Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED, + ) { + return Ok(Async::Ready(())); } // no keep-alive - if self.flags.contains(Flags::ERROR) - || (!self.flags.contains(Flags::KEEPALIVE) - || !self.ka_enabled) - && self.flags.contains(Flags::STARTED) + if self.flags.contains(Flags::STARTED) + && (!self.ka_enabled + || !self.flags.contains(Flags::KEEPALIVE)) { self.flags.insert(Flags::SHUTDOWN); return self.poll(); } } - return Ok(Async::NotReady); + Ok(Async::NotReady) } } + } else if let Some(err) = self.error.take() { + Err(err) + } else { + Ok(Async::Ready(())) } } /// keep-alive timer. returns `true` is keep-alive, otherwise drop - fn poll_keep_alive(&mut self) -> bool { - let timer = if let Some(ref mut timer) = self.ka_timer { + fn poll_keep_alive(&mut self) -> Result<(), HttpDispatchError> { + if let Some(ref mut timer) = self.ka_timer { match timer.poll() { Ok(Async::Ready(_)) => { if timer.deadline() >= self.ka_expire { @@ -242,43 +239,39 @@ where if self.tasks.is_empty() { // if we get timer during shutdown, just drop connection if self.flags.contains(Flags::SHUTDOWN) { - return false; + return Err(HttpDispatchError::ShutdownTimeout); } else { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); - None + // TODO: start shutdown timer + return Ok(()); } - } else { - self.settings.keep_alive_timer() + } else if let Some(deadline) = self.settings.keep_alive_expire() + { + timer.reset(deadline) } } else { - Some(Delay::new(self.ka_expire)) + timer.reset(self.ka_expire) } } - Ok(Async::NotReady) => None, + Ok(Async::NotReady) => (), Err(e) => { error!("Timer error {:?}", e); - return false; + return Err(HttpDispatchError::Unknown); } } - } else { - None - }; - - if let Some(mut timer) = timer { - let _ = timer.poll(); - self.ka_timer = Some(timer); } - true + + Ok(()) } #[inline] /// read data from stream - pub fn poll_io(&mut self) { + pub fn poll_io(&mut self) -> Result<(), HttpDispatchError> { if !self.flags.contains(Flags::POLLED) { - self.parse(); + self.parse()?; self.flags.insert(Flags::POLLED); - return; + return Ok(()); } // read io from socket @@ -286,136 +279,118 @@ where match self.stream.get_mut().read_available(&mut self.buf) { Ok(Async::Ready((read_some, disconnected))) => { if read_some { - self.parse(); + self.parse()?; } if disconnected { - self.read_disconnected(); - // delay disconnect until all tasks have finished. - if self.tasks.is_empty() { - self.write_disconnected(); - } + self.client_disconnected(true); } } Ok(Async::NotReady) => (), - Err(_) => { - self.read_disconnected(); - self.write_disconnected(); + Err(err) => { + self.client_disconnected(false); + return Err(err.into()); } } } + Ok(()) } pub fn poll_handler(&mut self) -> Poll { let retry = self.can_read(); - // check in-flight messages - let mut io = false; - let mut idx = 0; - while idx < self.tasks.len() { - // only one task can do io operation in http/1 - if !io - && !self.tasks[idx].flags.contains(EntryFlags::EOF) - && !self.flags.contains(Flags::WRITE_DISCONNECTED) - { - // io is corrupted, send buffer - if self.tasks[idx].flags.contains(EntryFlags::ERROR) { - if let Ok(Async::NotReady) = self.stream.poll_completed(true) { - return Ok(Async::NotReady); - } - self.flags.insert(Flags::ERROR); - return Err(self - .error - .take() - .map(|e| e.into()) - .unwrap_or(HttpDispatchError::Unknown)); - } - - match self.tasks[idx].pipe.poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - if ready { - self.tasks[idx] - .flags - .insert(EntryFlags::EOF | EntryFlags::FINISHED); - } else { - self.tasks[idx].flags.insert(EntryFlags::EOF); - } - } - // no more IO for this iteration - Ok(Async::NotReady) => { - // check if we need timer - if self.ka_timer.is_some() && self.stream.upgrade() { - self.ka_timer.take(); - } - - // check if previously read backpressure was enabled - if self.can_read() && !retry { - return Ok(Async::Ready(true)); - } - io = true; - } - Err(err) => { - error!("Unhandled error1: {}", err); - // it is not possible to recover from error - // during pipe handling, so just drop connection - self.read_disconnected(); - self.write_disconnected(); - self.tasks[idx].flags.insert(EntryFlags::ERROR); - self.error = Some(err); - continue; - } - } - } else if !self.tasks[idx].flags.contains(EntryFlags::FINISHED) { - match self.tasks[idx].pipe.poll_completed() { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - self.tasks[idx].flags.insert(EntryFlags::FINISHED) - } - Err(err) => { - error!("Unhandled error: {}", err); - self.read_disconnected(); - self.write_disconnected(); - self.tasks[idx].flags.insert(EntryFlags::ERROR); - self.error = Some(err); - continue; - } - } - } - idx += 1; - } - - // cleanup finished tasks + // process first pipelined response, only one task can do io operation in http/1 while !self.tasks.is_empty() { - if self.tasks[0] - .flags - .contains(EntryFlags::EOF | EntryFlags::FINISHED) - { - self.tasks.pop_front(); - } else { - break; + match self.tasks[0].poll_io(&mut self.stream) { + Ok(Async::Ready(ready)) => { + // override keep-alive state + if self.stream.keepalive() { + self.flags.insert(Flags::KEEPALIVE); + } else { + self.flags.remove(Flags::KEEPALIVE); + } + // prepare stream for next response + self.stream.reset(); + + let task = self.tasks.pop_front().unwrap(); + if !ready { + // task is done with io operations but still needs to do more work + spawn(HttpHandlerTaskFut::new(task.into_task())); + } + } + Ok(Async::NotReady) => { + // check if we need timer + if self.ka_timer.is_some() && self.stream.upgrade() { + self.ka_timer.take(); + } + + // if read-backpressure is enabled and we consumed some data. + // we may read more data + if !retry && self.can_read() { + return Ok(Async::Ready(true)); + } + break; + } + Err(err) => { + error!("Unhandled error1: {}", err); + // it is not possible to recover from error + // during pipe handling, so just drop connection + self.client_disconnected(false); + return Err(err.into()); + } } } - // check stream state + // check in-flight messages. all tasks must be alive, + // they need to produce response. if app returned error + // and we can not continue processing incoming requests. + let mut idx = 1; + while idx < self.tasks.len() { + let stop = match self.tasks[idx].poll_completed() { + Ok(Async::NotReady) => false, + Ok(Async::Ready(_)) => true, + Err(err) => { + self.error = Some(err.into()); + true + } + }; + if stop { + // error in task handling or task is completed, + // so no response for this task which means we can not read more requests + // because pipeline sequence is broken. + // but we can safely complete existing tasks + self.flags.insert(Flags::READ_DISCONNECTED); + + for mut task in self.tasks.drain(idx..) { + task.disconnected(); + match task.poll_completed() { + Ok(Async::NotReady) => { + // spawn not completed task, it does not require access to io + // at this point + spawn(HttpHandlerTaskFut::new(task.into_task())); + } + Ok(Async::Ready(_)) => (), + Err(err) => { + error!("Unhandled application error: {}", err); + } + } + } + break; + } else { + idx += 1; + } + } + + // flush stream if self.flags.contains(Flags::STARTED) { match self.stream.poll_completed(false) { Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); - self.read_disconnected(); - self.write_disconnected(); + self.client_disconnected(false); return Err(err.into()); } Ok(Async::Ready(_)) => { - // non consumed payload in that case close connection + // if payload is not consumed we can not use connection if self.payload.is_some() && self.tasks.is_empty() { return Ok(Async::Ready(false)); } @@ -427,13 +402,11 @@ where } fn push_response_entry(&mut self, status: StatusCode) { - self.tasks.push_back(Entry { - pipe: EntryPipe::Error(ServerError::err(Version::HTTP_11, status)), - flags: EntryFlags::empty(), - }); + self.tasks + .push_back(Entry::Error(ServerError::err(Version::HTTP_11, status))); } - pub fn parse(&mut self) { + pub fn parse(&mut self) -> Result<(), HttpDispatchError> { let mut updated = false; 'outer: loop { @@ -457,9 +430,9 @@ where // search handler for request match self.settings.handler().handle(msg) { - Ok(mut pipe) => { + Ok(mut task) => { if self.tasks.is_empty() { - match pipe.poll_io(&mut self.stream) { + match task.poll_io(&mut self.stream) { Ok(Async::Ready(ready)) => { // override keep-alive state if self.stream.keepalive() { @@ -471,42 +444,28 @@ where self.stream.reset(); if !ready { - let item = Entry { - pipe: EntryPipe::Task(pipe), - flags: EntryFlags::EOF, - }; - self.tasks.push_back(item); + // task is done with io operations + // but still needs to do more work + spawn(HttpHandlerTaskFut::new(task)); } continue 'outer; } Ok(Async::NotReady) => (), Err(err) => { error!("Unhandled error: {}", err); - self.flags.insert(Flags::ERROR); - return; + self.client_disconnected(false); + return Err(err.into()); } } } - self.tasks.push_back(Entry { - pipe: EntryPipe::Task(pipe), - flags: EntryFlags::empty(), - }); + self.tasks.push_back(Entry::Task(task)); continue 'outer; } Err(_) => { // handler is not found - self.tasks.push_back(Entry { - pipe: EntryPipe::Error(ServerError::err( - Version::HTTP_11, - StatusCode::NOT_FOUND, - )), - flags: EntryFlags::empty(), - }); + self.push_response_entry(StatusCode::NOT_FOUND); } } - - // handler is not found - self.push_response_entry(StatusCode::NOT_FOUND); } Ok(Some(Message::Chunk(chunk))) => { updated = true; @@ -514,8 +473,9 @@ where payload.feed_data(chunk); } else { error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::ERROR); + self.flags.insert(Flags::READ_DISCONNECTED); self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.error = Some(HttpDispatchError::InternalError); break; } } @@ -525,23 +485,19 @@ where payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::ERROR); + self.flags.insert(Flags::READ_DISCONNECTED); self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.error = Some(HttpDispatchError::InternalError); break; } } Ok(None) => { if self.flags.contains(Flags::READ_DISCONNECTED) { - self.read_disconnected(); - if self.tasks.is_empty() { - self.write_disconnected(); - } + self.client_disconnected(true); } break; } Err(e) => { - updated = false; - self.flags.insert(Flags::ERROR); if let Some(mut payload) = self.payload.take() { let e = match e { DecoderError::Io(e) => PayloadError::Io(e), @@ -550,8 +506,10 @@ where payload.set_error(e); } - //Malformed requests should be responded with 400 + // Malformed requests should be responded with 400 self.push_response_entry(StatusCode::BAD_REQUEST); + self.flags.insert(Flags::READ_DISCONNECTED); + self.error = Some(HttpDispatchError::MalformedRequest); break; } } @@ -562,6 +520,7 @@ where self.ka_expire = expire; } } + Ok(()) } } @@ -708,15 +667,15 @@ mod tests { #[test] fn test_req_parse_err() { let mut sys = System::new("test"); - sys.block_on(future::lazy(|| { + let _ = sys.block_on(future::lazy(|| { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); let readbuf = BytesMut::new(); let settings = wrk_settings(); let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, false, None); - h1.poll_io(); - h1.poll_io(); - assert!(h1.flags.contains(Flags::ERROR)); + assert!(h1.poll_io().is_ok()); + assert!(h1.poll_io().is_ok()); + assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); assert_eq!(h1.tasks.len(), 1); future::ok::<_, ()>(()) })); diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 084ae8b2..a7531bbb 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -18,6 +18,7 @@ pub(crate) struct H1Decoder { decoder: Option, } +#[derive(Debug)] pub(crate) enum Message { Message { msg: Request, payload: bool }, Chunk(Bytes), diff --git a/src/server/handler.rs b/src/server/handler.rs index 0700e196..33e50ac3 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -1,4 +1,4 @@ -use futures::{Async, Poll}; +use futures::{Async, Future, Poll}; use super::message::Request; use super::Writer; @@ -42,6 +42,25 @@ impl HttpHandlerTask for Box { } } +pub(super) struct HttpHandlerTaskFut { + task: T, +} + +impl HttpHandlerTaskFut { + pub(crate) fn new(task: T) -> Self { + Self { task } + } +} + +impl Future for HttpHandlerTaskFut { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll<(), ()> { + self.task.poll_completed().map_err(|_| ()) + } +} + /// Conversion helper trait pub trait IntoHttpHandler { /// The associated type which is result of conversion. diff --git a/src/server/http.rs b/src/server/http.rs index 311c53cb..511b1832 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -485,7 +485,7 @@ impl H + Send + Clone> HttpServer { socket.lst, host, socket.addr, - self.keep_alive.clone(), + self.keep_alive, self.client_timeout, ); } @@ -531,7 +531,7 @@ impl H + Send + Clone> HttpServer { socket.lst, host, socket.addr, - self.keep_alive.clone(), + self.keep_alive, self.client_timeout, ); } diff --git a/src/server/incoming.rs b/src/server/incoming.rs index c7728008..a56ccb80 100644 --- a/src/server/incoming.rs +++ b/src/server/incoming.rs @@ -41,9 +41,7 @@ where // start server HttpIncoming::create(move |ctx| { - ctx.add_message_stream( - stream.map_err(|_| ()).map(move |t| WrapperStream::new(t)), - ); + ctx.add_message_stream(stream.map_err(|_| ()).map(WrapperStream::new)); HttpIncoming { settings } }); } diff --git a/src/server/message.rs b/src/server/message.rs index 43f7e142..9c4bc1ec 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -1,5 +1,6 @@ use std::cell::{Cell, Ref, RefCell, RefMut}; use std::collections::VecDeque; +use std::fmt; use std::net::SocketAddr; use std::rc::Rc; @@ -220,6 +221,26 @@ impl Request { } } +impl fmt::Debug for Request { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "\nRequest {:?} {}:{}", + self.version(), + self.method(), + self.path() + )?; + if let Some(q) = self.uri().query().as_ref() { + writeln!(f, " query: ?{:?}", q)?; + } + writeln!(f, " headers:")?; + for (key, val) in self.headers().iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} + pub(crate) struct RequestPool( RefCell>>, RefCell, From 16945a554abd5ddc9b3aaec4f102f9eeaae5e1a8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 20:04:16 -0700 Subject: [PATCH 0687/1635] add client shutdown timeout --- CHANGES.md | 7 ++ src/server/acceptor.rs | 8 +-- src/server/builder.rs | 26 +++----- src/server/h1.rs | 12 +++- src/server/http.rs | 31 ++++++++- src/server/incoming.rs | 3 +- src/server/mod.rs | 2 +- src/server/settings.rs | 142 ++++++++++++++++++++++++++++++++++++++++- tests/test_server.rs | 15 +++-- 9 files changed, 208 insertions(+), 38 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 517f8cbe..32d2bea7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,13 @@ ## [0.7.9] - 2018-09-x +### Added + +* Added client shutdown timeout setting + +* Added slow request timeout setting + + ### Fixed * HTTP1 decoding errors are reported to the client. #512 diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index bad8847d..15d66112 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -176,11 +176,11 @@ where /// Applies timeout to request prcoessing. pub(crate) struct AcceptorTimeout { inner: T, - timeout: usize, + timeout: u64, } impl AcceptorTimeout { - pub(crate) fn new(timeout: usize, inner: T) -> Self { + pub(crate) fn new(timeout: u64, inner: T) -> Self { Self { inner, timeout } } } @@ -204,7 +204,7 @@ impl NewService for AcceptorTimeout { #[doc(hidden)] pub(crate) struct AcceptorTimeoutFut { fut: T::Future, - timeout: usize, + timeout: u64, } impl Future for AcceptorTimeoutFut { @@ -215,7 +215,7 @@ impl Future for AcceptorTimeoutFut { let inner = try_ready!(self.fut.poll()); Ok(Async::Ready(AcceptorTimeoutService { inner, - timeout: self.timeout as u64, + timeout: self.timeout, })) } } diff --git a/src/server/builder.rs b/src/server/builder.rs index 8e7f82f8..9e932353 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -16,12 +16,13 @@ use super::KeepAlive; pub(crate) trait ServiceProvider { fn register( &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, + addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: u64, + client_shutdown: u64, ) -> Server; } /// Utility type that builds complete http pipeline -pub struct HttpServiceBuilder +pub(crate) struct HttpServiceBuilder where F: Fn() -> H + Send + Clone, { @@ -51,22 +52,9 @@ where self } - /// Use different acceptor factory - pub fn acceptor(self, acceptor: A1) -> HttpServiceBuilder - where - A1: AcceptorServiceFactory, - ::InitError: fmt::Debug, - { - HttpServiceBuilder { - acceptor, - factory: self.factory.clone(), - no_client_timer: self.no_client_timer, - } - } - fn finish( &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, - client_timeout: usize, + client_timeout: u64, client_shutdown: u64, ) -> impl ServiceFactory { let timeout = if self.no_client_timer { 0 @@ -81,6 +69,7 @@ where app, keep_alive, timeout as u64, + client_shutdown, ServerSettings::new(addr, &host, false), ); @@ -137,12 +126,13 @@ where { fn register( &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: usize, + addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: u64, + client_shutdown: u64, ) -> Server { server.listen2( "actix-web", lst, - self.finish(host, addr, keep_alive, client_timeout), + self.finish(host, addr, keep_alive, client_timeout, client_shutdown), ) } } diff --git a/src/server/h1.rs b/src/server/h1.rs index f3c71e3c..f5e2bf2f 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -243,8 +243,15 @@ where } else { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); - // TODO: start shutdown timer - return Ok(()); + + // start shutdown timer + if let Some(deadline) = + self.settings.client_shutdown_timer() + { + timer.reset(deadline) + } else { + return Ok(()); + } } } else if let Some(deadline) = self.settings.keep_alive_expire() { @@ -548,6 +555,7 @@ mod tests { App::new().into_handler(), KeepAlive::Os, 5000, + 2000, ServerSettings::default(), ) } diff --git a/src/server/http.rs b/src/server/http.rs index 511b1832..5e1d3351 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -41,7 +41,8 @@ where pub(super) factory: F, pub(super) host: Option, pub(super) keep_alive: KeepAlive, - pub(super) client_timeout: usize, + pub(super) client_timeout: u64, + pub(super) client_shutdown: u64, backlog: i32, threads: usize, exit: bool, @@ -73,6 +74,7 @@ where maxconn: 25_600, maxconnrate: 256, client_timeout: 5000, + client_shutdown: 5000, sockets: Vec::new(), } } @@ -140,11 +142,24 @@ where /// To disable timeout set value to 0. /// /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: usize) -> Self { + pub fn client_timeout(mut self, val: u64) -> Self { self.client_timeout = val; self } + /// Set server connection shutdown timeout in milliseconds. + /// + /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete + /// within this time, the request is dropped. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_shutdown(mut self, val: u64) -> Self { + self.client_shutdown = val; + self + } + /// Set server host name. /// /// Host name is used by application router aa a hostname for url @@ -480,6 +495,11 @@ impl H + Send + Clone> HttpServer { .as_ref() .map(|h| h.to_owned()) .unwrap_or_else(|| format!("{}", socket.addr)); + let client_shutdown = if socket.scheme == "https" { + self.client_shutdown + } else { + 0 + }; srv = socket.handler.register( srv, socket.lst, @@ -487,6 +507,7 @@ impl H + Send + Clone> HttpServer { socket.addr, self.keep_alive, self.client_timeout, + client_shutdown, ); } srv.start() @@ -526,6 +547,11 @@ impl H + Send + Clone> HttpServer { .as_ref() .map(|h| h.to_owned()) .unwrap_or_else(|| format!("{}", socket.addr)); + let client_shutdown = if socket.scheme == "https" { + self.client_shutdown + } else { + 0 + }; srv = socket.handler.register( srv, socket.lst, @@ -533,6 +559,7 @@ impl H + Send + Clone> HttpServer { socket.addr, self.keep_alive, self.client_timeout, + client_shutdown, ); } srv diff --git a/src/server/incoming.rs b/src/server/incoming.rs index a56ccb80..c4e984b9 100644 --- a/src/server/incoming.rs +++ b/src/server/incoming.rs @@ -35,7 +35,8 @@ where let settings = WorkerSettings::new( apps, self.keep_alive, - self.client_timeout as u64, + self.client_timeout, + self.client_shutdown, ServerSettings::new(addr, "127.0.0.1:8080", secure), ); diff --git a/src/server/mod.rs b/src/server/mod.rs index f9d2b585..b7241051 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -144,7 +144,7 @@ pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; pub use self::service::HttpService; -pub use self::settings::{ServerSettings, WorkerSettings}; +pub use self::settings::{ServerSettings, WorkerSettings, WorkerSettingsBuilder}; #[doc(hidden)] pub use self::helpers::write_content_length; diff --git a/src/server/settings.rs b/src/server/settings.rs index fe9cd82a..ac79e4a4 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -76,7 +76,9 @@ impl Default for ServerSettings { impl ServerSettings { /// Crate server settings instance - pub fn new(addr: net::SocketAddr, host: &str, secure: bool) -> ServerSettings { + pub(crate) fn new( + addr: net::SocketAddr, host: &str, secure: bool, + ) -> ServerSettings { let host = host.to_owned(); let cpu_pool = LazyCell::new(); let responses = HttpResponsePool::get_pool(); @@ -131,6 +133,7 @@ struct Inner { handler: H, keep_alive: Option, client_timeout: u64, + client_shutdown: u64, ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, @@ -146,8 +149,9 @@ impl Clone for WorkerSettings { impl WorkerSettings { /// Create instance of `WorkerSettings` - pub fn new( - handler: H, keep_alive: KeepAlive, client_timeout: u64, settings: ServerSettings, + pub(crate) fn new( + handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, + settings: ServerSettings, ) -> WorkerSettings { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -165,6 +169,7 @@ impl WorkerSettings { keep_alive, ka_enabled, client_timeout, + client_shutdown, bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), node: RefCell::new(Node::head()), @@ -172,6 +177,11 @@ impl WorkerSettings { })) } + /// Create worker settings builder. + pub fn build(handler: H) -> WorkerSettingsBuilder { + WorkerSettingsBuilder::new(handler) + } + pub(crate) fn head(&self) -> RefMut> { self.0.node.borrow_mut() } @@ -222,6 +232,16 @@ impl WorkerSettings { } } + /// Client shutdown timer + pub fn client_shutdown_timer(&self) -> Option { + let delay = self.0.client_shutdown; + if delay != 0 { + Some(self.now() + Duration::from_millis(delay)) + } else { + None + } + } + #[inline] /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { @@ -289,6 +309,121 @@ impl WorkerSettings { } } +/// An worker settings builder +/// +/// This type can be used to construct an instance of `WorkerSettings` through a +/// builder-like pattern. +pub struct WorkerSettingsBuilder { + handler: H, + keep_alive: KeepAlive, + client_timeout: u64, + client_shutdown: u64, + host: String, + addr: net::SocketAddr, + secure: bool, +} + +impl WorkerSettingsBuilder { + /// Create instance of `WorkerSettingsBuilder` + pub fn new(handler: H) -> WorkerSettingsBuilder { + WorkerSettingsBuilder { + handler, + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_shutdown: 5000, + secure: false, + host: "localhost".to_owned(), + addr: "127.0.0.1:8080".parse().unwrap(), + } + } + + /// Enable secure flag for current server. + /// + /// By default this flag is set to false. + pub fn secure(mut self) -> Self { + self.secure = true; + self + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: T) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection shutdown timeout in milliseconds. + /// + /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete + /// within this time, the request is dropped. This timeout affects only secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_shutdown(mut self, val: u64) -> Self { + self.client_shutdown = val; + 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. + /// + /// By default host name is set to a "localhost" value. + pub fn server_hostname(mut self, val: &str) -> Self { + self.host = val.to_owned(); + self + } + + /// Set server ip address. + /// + /// 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. + /// + /// By default server address is set to a "127.0.0.1:8080" + pub fn server_address(mut self, addr: S) -> Self { + match addr.to_socket_addrs() { + Err(err) => error!("Can not convert to SocketAddr: {}", err), + Ok(mut addrs) => if let Some(addr) = addrs.next() { + self.addr = addr; + }, + } + self + } + + /// Finish worker settings configuration and create `WorkerSettings` object. + pub fn finish(self) -> WorkerSettings { + let settings = ServerSettings::new(self.addr, &self.host, self.secure); + let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; + + WorkerSettings::new( + self.handler, + self.keep_alive, + self.client_timeout, + client_shutdown, + settings, + ) + } +} + struct Date { current: Instant, bytes: [u8; DATE_VALUE_LENGTH], @@ -366,6 +501,7 @@ mod tests { (), KeepAlive::Os, 0, + 0, ServerSettings::default(), ); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); diff --git a/tests/test_server.rs b/tests/test_server.rs index 66b96ecc..f8fabef6 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1016,7 +1016,7 @@ fn test_server_cookies() { #[test] fn test_custom_pipeline() { use actix::System; - use actix_web::server::{HttpService, KeepAlive, ServerSettings, WorkerSettings}; + use actix_web::server::{HttpService, KeepAlive, WorkerSettings}; let addr = test::TestServer::unused_addr(); @@ -1026,12 +1026,13 @@ fn test_custom_pipeline() { let app = App::new() .route("/", http::Method::GET, |_: HttpRequest| "OK") .finish(); - let settings = WorkerSettings::new( - app, - KeepAlive::Disabled, - 10, - ServerSettings::new(addr, "localhost", false), - ); + let settings = WorkerSettings::build(app) + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_shutdown(1000) + .server_hostname("localhost") + .server_address(addr) + .finish(); HttpService::new(settings) }).unwrap() From 1bac65de4c11409ba09ff8b5b040ca1d07f72d30 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 20:15:26 -0700 Subject: [PATCH 0688/1635] add websocket stopped test --- tests/test_ws.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 49118fc7..cf928349 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -5,6 +5,10 @@ extern crate futures; extern crate http; extern crate rand; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::{thread, time}; + use bytes::Bytes; use futures::Stream; use rand::distributions::Alphanumeric; @@ -351,3 +355,44 @@ fn test_ws_server_rust_tls() { assert_eq!(item, data); } } + +struct WsStopped(Arc); + +impl Actor for WsStopped { + type Context = ws::WebsocketContext; + + fn stopped(&mut self, ctx: &mut Self::Context) { + self.0.fetch_add(1, Ordering::Relaxed); + } +} + +impl StreamHandler for WsStopped { + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + match msg { + ws::Message::Text(text) => ctx.text(text), + _ => (), + } + } +} + +#[test] +fn test_ws_stopped() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let _ = thread::spawn(move || { + let num3 = num2.clone(); + let mut srv = test::TestServer::new(move |app| { + let num4 = num3.clone(); + app.handler(move |req| ws::start(req, WsStopped(num4.clone()))) + }); + let (reader, mut writer) = srv.ws().unwrap(); + + writer.text("text"); + let (item, reader) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); + }); + + thread::sleep(time::Duration::from_secs(1)); + assert_eq!(num.load(Ordering::Relaxed), 1); +} From e4686f6c8d9519186061f4944cb6f0e3be0eb8e7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 20:53:22 -0700 Subject: [PATCH 0689/1635] set socket linger to 0 on timeout --- src/server/h1.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index f5e2bf2f..433a916b 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -1,6 +1,6 @@ use std::collections::VecDeque; -use std::net::SocketAddr; -use std::time::Instant; +use std::net::{Shutdown, SocketAddr}; +use std::time::{Duration, Instant}; use bytes::BytesMut; use futures::{Async, Future, Poll}; @@ -239,6 +239,12 @@ where if self.tasks.is_empty() { // if we get timer during shutdown, just drop connection if self.flags.contains(Flags::SHUTDOWN) { + let io = self.stream.get_mut(); + let _ = IoStream::set_linger( + io, + Some(Duration::from_secs(0)), + ); + let _ = IoStream::shutdown(io, Shutdown::Both); return Err(HttpDispatchError::ShutdownTimeout); } else { trace!("Keep-alive timeout, close connection"); From 127af925411604c57a42b96318ca83d7ca07db99 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 21:16:56 -0700 Subject: [PATCH 0690/1635] clippy warnings --- src/application.rs | 2 +- src/client/writer.rs | 5 ++++- src/extensions.rs | 1 + src/handler.rs | 3 +-- src/httpresponse.rs | 2 +- src/info.rs | 5 ++++- src/lib.rs | 1 + src/middleware/defaultheaders.rs | 2 +- src/route.rs | 7 +++---- src/scope.rs | 5 ++++- src/server/h2writer.rs | 5 ++++- src/server/helpers.rs | 4 ++-- src/server/http.rs | 5 ++++- src/server/output.rs | 16 +++++++--------- src/server/settings.rs | 2 +- src/with.rs | 19 ------------------- src/ws/frame.rs | 2 +- src/ws/mask.rs | 9 ++++++--- 18 files changed, 46 insertions(+), 49 deletions(-) diff --git a/src/application.rs b/src/application.rs index 40726832..d8a6cbe7 100644 --- a/src/application.rs +++ b/src/application.rs @@ -135,7 +135,7 @@ where /// instance for each thread, thus application state must be constructed /// multiple times. If you want to share state between different /// threads, a shared object should be used, e.g. `Arc`. Application - /// state does not need to be `Send` and `Sync`. + /// state does not need to be `Send` or `Sync`. pub fn with_state(state: S) -> App { App { parts: Some(ApplicationParts { diff --git a/src/client/writer.rs b/src/client/writer.rs index 45abfb77..e74f2233 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -1,4 +1,7 @@ -#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] +#![cfg_attr( + feature = "cargo-clippy", + allow(clippy::redundant_field_names) +)] use std::cell::RefCell; use std::fmt::Write as FmtWrite; diff --git a/src/extensions.rs b/src/extensions.rs index 3e3f24a2..430b87bd 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -31,6 +31,7 @@ impl Hasher for IdHasher { type AnyMap = HashMap, BuildHasherDefault>; +#[derive(Default)] /// A type map of request extensions. pub struct Extensions { map: AnyMap, diff --git a/src/handler.rs b/src/handler.rs index 2b6cc660..399fd6ba 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -530,8 +530,7 @@ where /// } /// /// /// extract path info using serde -/// fn index(data: (State, Path)) -> String { -/// let (state, path) = data; +/// fn index(state: State, path: Path)) -> String { /// format!("{} {}!", state.msg, path.username) /// } /// diff --git a/src/httpresponse.rs b/src/httpresponse.rs index f0257018..59815c58 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -694,7 +694,7 @@ impl HttpResponseBuilder { } #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] +#[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))] fn parts<'a>( parts: &'a mut Option>, err: &Option, ) -> Option<&'a mut Box> { diff --git a/src/info.rs b/src/info.rs index aeffc5ba..5a2f2180 100644 --- a/src/info.rs +++ b/src/info.rs @@ -16,7 +16,10 @@ pub struct ConnectionInfo { impl ConnectionInfo { /// Create *ConnectionInfo* instance for a request. - #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] + #[cfg_attr( + feature = "cargo-clippy", + allow(clippy::cyclomatic_complexity) + )] pub fn update(&mut self, req: &Request) { let mut host = None; let mut scheme = None; diff --git a/src/lib.rs b/src/lib.rs index df3c3817..1ed40809 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,7 @@ #![cfg_attr(actix_nightly, feature( specialization, // for impl ErrorResponse for std::error::Error extern_prelude, + tool_lints, ))] #![warn(missing_docs)] diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index a33fa6a3..d980a250 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -48,7 +48,7 @@ impl DefaultHeaders { /// Set a header. #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] + #[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))] pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, diff --git a/src/route.rs b/src/route.rs index e2635aa6..e4a7a957 100644 --- a/src/route.rs +++ b/src/route.rs @@ -134,8 +134,7 @@ impl Route { /// } /// ``` /// - /// It is possible to use tuples for specifing multiple extractors for one - /// handler function. + /// It is possible to use multiple extractors for one handler function. /// /// ```rust /// # extern crate bytes; @@ -152,9 +151,9 @@ impl Route { /// /// /// extract path info using serde /// fn index( - /// info: (Path, Query>, Json), + /// path: Path, query: Query>, body: Json, /// ) -> Result { - /// Ok(format!("Welcome {}!", info.0.username)) + /// Ok(format!("Welcome {}!", path.username)) /// } /// /// fn main() { diff --git a/src/scope.rs b/src/scope.rs index bd3daf16..43789d42 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -59,7 +59,10 @@ pub struct Scope { middlewares: Rc>>>, } -#[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] +#[cfg_attr( + feature = "cargo-clippy", + allow(clippy::new_without_default_derive) +)] impl Scope { /// Create a new scope pub fn new(path: &str) -> Scope { diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 4bfc1b7c..0893b5b6 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -1,4 +1,7 @@ -#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] +#![cfg_attr( + feature = "cargo-clippy", + allow(clippy::redundant_field_names) +)] use std::{cmp, io}; diff --git a/src/server/helpers.rs b/src/server/helpers.rs index 9c0b7f40..e4ccd8ae 100644 --- a/src/server/helpers.rs +++ b/src/server/helpers.rs @@ -78,7 +78,7 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) { let d1 = n << 1; unsafe { ptr::copy_nonoverlapping( - DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), + DEC_DIGITS_LUT.as_ptr().add(d1), buf.as_mut_ptr().offset(18), 2, ); @@ -94,7 +94,7 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) { n /= 100; unsafe { ptr::copy_nonoverlapping( - DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), + DEC_DIGITS_LUT.as_ptr().add(d1), buf.as_mut_ptr().offset(19), 2, ) diff --git a/src/server/http.rs b/src/server/http.rs index 5e1d3351..5a720086 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -329,7 +329,10 @@ where /// Start listening for incoming connections with supplied acceptor. #[doc(hidden)] - #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] + #[cfg_attr( + feature = "cargo-clippy", + allow(clippy::needless_pass_by_value) + )] pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result where S: net::ToSocketAddrs, diff --git a/src/server/output.rs b/src/server/output.rs index 74b08338..46b03c9d 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -151,10 +151,9 @@ impl Output { let version = resp.version().unwrap_or_else(|| req.version); let mut len = 0; - #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] let has_body = match resp.body() { - &Body::Empty => false, - &Body::Binary(ref bin) => { + Body::Empty => false, + Body::Binary(ref bin) => { len = bin.len(); !(response_encoding == ContentEncoding::Auto && len < 96) } @@ -190,16 +189,15 @@ impl Output { #[cfg(not(any(feature = "brotli", feature = "flate2")))] let mut encoding = ContentEncoding::Identity; - #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] let transfer = match resp.body() { - &Body::Empty => { + Body::Empty => { if !info.head { info.length = ResponseLength::Zero; } *self = Output::Empty(buf); return; } - &Body::Binary(_) => { + Body::Binary(_) => { #[cfg(any(feature = "brotli", feature = "flate2"))] { if !(encoding == ContentEncoding::Identity @@ -244,7 +242,7 @@ impl Output { } return; } - &Body::Streaming(_) | &Body::Actor(_) => { + Body::Streaming(_) | Body::Actor(_) => { if resp.upgrade() { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); @@ -441,7 +439,7 @@ impl ContentEncoder { } } - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] + #[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] #[inline(always)] pub fn write_eof(&mut self) -> Result { let encoder = @@ -483,7 +481,7 @@ impl ContentEncoder { } } - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] + #[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] #[inline(always)] pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { diff --git a/src/server/settings.rs b/src/server/settings.rs index ac79e4a4..a50a0706 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -216,7 +216,7 @@ impl WorkerSettings { fn update_date(&self) { // Unsafe: WorkerSetting is !Sync and !Send - unsafe { (&mut *self.0.date.get()).0 = false }; + unsafe { (*self.0.date.get()).0 = false }; } } diff --git a/src/with.rs b/src/with.rs index 5e2c0141..c6d54dee 100644 --- a/src/with.rs +++ b/src/with.rs @@ -12,7 +12,6 @@ trait FnWith: 'static { } impl R + 'static> FnWith for F { - #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))] fn call_with(self: &Self, arg: T) -> R { (*self)(arg) } @@ -42,24 +41,6 @@ where fn create_with_config(self, T::Config) -> WithAsync; } -// impl WithFactory<(T1, T2, T3), S, R> for F -// where F: Fn(T1, T2, T3) -> R + 'static, -// T1: FromRequest + 'static, -// T2: FromRequest + 'static, -// T3: FromRequest + 'static, -// R: Responder + 'static, -// S: 'static, -// { -// fn create(self) -> With<(T1, T2, T3), S, R> { -// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), ( -// T1::Config::default(), T2::Config::default(), T3::Config::default())) -// } - -// fn create_with_config(self, cfg: (T1::Config, T2::Config, T3::Config,)) -> With<(T1, T2, T3), S, R> { -// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), cfg) -// } -// } - #[doc(hidden)] pub struct With where diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 5e4fd829..d5fa9827 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -46,7 +46,7 @@ impl Frame { Frame::message(payload, OpCode::Close, true, genmask) } - #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] + #[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))] fn read_copy_md( pl: &mut PayloadBuffer, server: bool, max_size: usize, ) -> Poll)>, ProtocolError> diff --git a/src/ws/mask.rs b/src/ws/mask.rs index e9bfb3d5..a88c21af 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,5 +1,5 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] use std::ptr::copy_nonoverlapping; use std::slice; @@ -19,7 +19,7 @@ impl<'a> ShortSlice<'a> { /// Faster version of `apply_mask()` which operates on 8-byte blocks. #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] +#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // Extend the mask to 64 bits let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); @@ -50,7 +50,10 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so // inefficient, it could be done better. The compiler does not understand that // a `ShortSlice` must be smaller than a u64. -#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] +#[cfg_attr( + feature = "cargo-clippy", + allow(clippy::needless_pass_by_value) +)] fn xor_short(buf: ShortSlice, mask: u64) { // Unsafe: we know that a `ShortSlice` fits in a u64 unsafe { From 84edc57fd9d9a2075e5d3aaff5257eecc0c206b8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 21:19:27 -0700 Subject: [PATCH 0691/1635] increase sleep time --- tests/test_ws.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ws.rs b/tests/test_ws.rs index cf928349..67c4c591 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -393,6 +393,6 @@ fn test_ws_stopped() { assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); }); - thread::sleep(time::Duration::from_secs(1)); + thread::sleep(time::Duration::from_secs(3)); assert_eq!(num.load(Ordering::Relaxed), 1); } From 7c78797d9b9acc1653d6cc8338ef4ef71a756422 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 21:30:00 -0700 Subject: [PATCH 0692/1635] proper stop for test_ws_stopped test --- tests/test_ws.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 67c4c591..f67314e8 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -361,7 +361,7 @@ struct WsStopped(Arc); impl Actor for WsStopped { type Context = ws::WebsocketContext; - fn stopped(&mut self, ctx: &mut Self::Context) { + fn stopped(&mut self, _: &mut Self::Context) { self.0.fetch_add(1, Ordering::Relaxed); } } @@ -387,12 +387,10 @@ fn test_ws_stopped() { app.handler(move |req| ws::start(req, WsStopped(num4.clone()))) }); let (reader, mut writer) = srv.ws().unwrap(); - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); + let (item, _) = srv.execute(reader.into_future()).unwrap(); assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - }); + }).join(); - thread::sleep(time::Duration::from_secs(3)); assert_eq!(num.load(Ordering::Relaxed), 1); } From c674ea912691d86379d610a4794a50cef4b2feac Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 22:23:02 -0700 Subject: [PATCH 0693/1635] add StreamConfiguration service --- src/client/connector.rs | 5 ++ src/server/channel.rs | 8 +++- src/server/h1.rs | 37 ++++++++------- src/server/mod.rs | 16 ++++++- src/server/service.rs | 93 +++++++++++++++++++++++++++++++++---- src/server/ssl/nativetls.rs | 5 ++ src/server/ssl/openssl.rs | 5 ++ src/server/ssl/rustls.rs | 10 ++++ tests/test_server.rs | 9 +++- tests/test_ws.rs | 2 +- 10 files changed, 160 insertions(+), 30 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 88d6dfd6..88be77f9 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1283,6 +1283,11 @@ impl IoStream for Connection { fn set_linger(&mut self, dur: Option) -> io::Result<()> { IoStream::set_linger(&mut *self.stream, dur) } + + #[inline] + fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { + IoStream::set_keepalive(&mut *self.stream, dur) + } } impl io::Read for Connection { diff --git a/src/server/channel.rs b/src/server/channel.rs index 3cea291f..d8cad970 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -13,7 +13,7 @@ use super::{h1, h2, HttpHandler, IoStream}; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; enum HttpProtocol { - H1(h1::Http1), + H1(h1::Http1Dispatcher), H2(h2::Http2), Unknown(WorkerSettings, Option, T, BytesMut), } @@ -167,7 +167,7 @@ where if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { match kind { ProtocolKind::Http1 => { - self.proto = Some(HttpProtocol::H1(h1::Http1::new( + self.proto = Some(HttpProtocol::H1(h1::Http1Dispatcher::new( settings, io, addr, @@ -311,6 +311,10 @@ where fn set_linger(&mut self, _: Option) -> io::Result<()> { Ok(()) } + #[inline] + fn set_keepalive(&mut self, _: Option) -> io::Result<()> { + Ok(()) + } } impl io::Read for WrapperStream diff --git a/src/server/h1.rs b/src/server/h1.rs index 433a916b..6875972e 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -24,15 +24,18 @@ const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { pub struct Flags: u8 { const STARTED = 0b0000_0001; + const KEEPALIVE_ENABLED = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const SHUTDOWN = 0b0000_1000; const READ_DISCONNECTED = 0b0001_0000; const WRITE_DISCONNECTED = 0b0010_0000; const POLLED = 0b0100_0000; + } } -pub(crate) struct Http1 { +/// Dispatcher for HTTP/1.1 protocol +pub struct Http1Dispatcher { flags: Flags, settings: WorkerSettings, addr: Option, @@ -42,7 +45,6 @@ pub(crate) struct Http1 { buf: BytesMut, tasks: VecDeque>, error: Option, - ka_enabled: bool, ka_expire: Instant, ka_timer: Option, } @@ -79,7 +81,7 @@ impl Entry { } } -impl Http1 +impl Http1Dispatcher where T: IoStream, H: HttpHandler + 'static, @@ -88,7 +90,6 @@ where settings: WorkerSettings, stream: T, addr: Option, buf: BytesMut, is_eof: bool, keepalive_timer: Option, ) -> Self { - let ka_enabled = settings.keep_alive_enabled(); let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { (delay.deadline(), Some(delay)) } else if let Some(delay) = settings.keep_alive_timer() { @@ -97,12 +98,16 @@ where (settings.now(), None) }; - Http1 { - flags: if is_eof { - Flags::READ_DISCONNECTED - } else { - Flags::KEEPALIVE - }, + let mut flags = if is_eof { + Flags::READ_DISCONNECTED + } else if settings.keep_alive_enabled() { + Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED + } else { + Flags::empty() + }; + + Http1Dispatcher { + flags, stream: H1Writer::new(stream, settings.clone()), decoder: H1Decoder::new(), payload: None, @@ -113,7 +118,6 @@ where settings, ka_timer, ka_expire, - ka_enabled, } } @@ -212,7 +216,7 @@ where } // no keep-alive if self.flags.contains(Flags::STARTED) - && (!self.ka_enabled + && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) || !self.flags.contains(Flags::KEEPALIVE)) { self.flags.insert(Flags::SHUTDOWN); @@ -280,7 +284,7 @@ where #[inline] /// read data from stream - pub fn poll_io(&mut self) -> Result<(), HttpDispatchError> { + pub(self) fn poll_io(&mut self) -> Result<(), HttpDispatchError> { if !self.flags.contains(Flags::POLLED) { self.parse()?; self.flags.insert(Flags::POLLED); @@ -308,7 +312,7 @@ where Ok(()) } - pub fn poll_handler(&mut self) -> Poll { + pub(self) fn poll_handler(&mut self) -> Poll { let retry = self.can_read(); // process first pipelined response, only one task can do io operation in http/1 @@ -419,7 +423,7 @@ where .push_back(Entry::Error(ServerError::err(Version::HTTP_11, status))); } - pub fn parse(&mut self) -> Result<(), HttpDispatchError> { + pub(self) fn parse(&mut self) -> Result<(), HttpDispatchError> { let mut updated = false; 'outer: loop { @@ -686,7 +690,8 @@ mod tests { let readbuf = BytesMut::new(); let settings = wrk_settings(); - let mut h1 = Http1::new(settings.clone(), buf, None, readbuf, false, None); + let mut h1 = + Http1Dispatcher::new(settings.clone(), buf, None, readbuf, false, None); assert!(h1.poll_io().is_ok()); assert!(h1.poll_io().is_ok()); assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); diff --git a/src/server/mod.rs b/src/server/mod.rs index b7241051..456b4618 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -143,9 +143,11 @@ pub use self::message::Request; pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; -pub use self::service::HttpService; pub use self::settings::{ServerSettings, WorkerSettings, WorkerSettingsBuilder}; +#[doc(hidden)] +pub use self::service::{HttpService, StreamConfiguration}; + #[doc(hidden)] pub use self::helpers::write_content_length; @@ -268,6 +270,8 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn set_linger(&mut self, dur: Option) -> io::Result<()>; + fn set_keepalive(&mut self, dur: Option) -> io::Result<()>; + fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> { let mut read_some = false; loop { @@ -324,6 +328,11 @@ impl IoStream for ::tokio_uds::UnixStream { fn set_linger(&mut self, _dur: Option) -> io::Result<()> { Ok(()) } + + #[inline] + fn set_keepalive(&mut self, _nodelay: bool) -> io::Result<()> { + Ok(()) + } } impl IoStream for TcpStream { @@ -341,4 +350,9 @@ impl IoStream for TcpStream { fn set_linger(&mut self, dur: Option) -> io::Result<()> { TcpStream::set_linger(self, dur) } + + #[inline] + fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { + TcpStream::set_keepalive(self, dur) + } } diff --git a/src/server/service.rs b/src/server/service.rs index 2988bc66..89a58af7 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -1,4 +1,5 @@ use std::marker::PhantomData; +use std::time::Duration; use actix_net::service::{NewService, Service}; use futures::future::{ok, FutureResult}; @@ -10,6 +11,7 @@ use super::handler::HttpHandler; use super::settings::WorkerSettings; use super::IoStream; +/// `NewService` implementation for HTTP1/HTTP2 transports pub struct HttpService where H: HttpHandler, @@ -56,7 +58,6 @@ where Io: IoStream, { settings: WorkerSettings, - // tcp_ka: Option, _t: PhantomData, } @@ -66,12 +67,6 @@ where Io: IoStream, { fn new(settings: WorkerSettings) -> HttpServiceHandler { - // let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive { - // Some(Duration::new(val as u64, 0)) - // } else { - // None - // }; - HttpServiceHandler { settings, _t: PhantomData, @@ -94,7 +89,89 @@ where } fn call(&mut self, mut req: Self::Request) -> Self::Future { - let _ = req.set_nodelay(true); HttpChannel::new(self.settings.clone(), req, None) } } + +/// `NewService` implementation for stream configuration service +pub struct StreamConfiguration { + no_delay: Option, + tcp_ka: Option>, + _t: PhantomData<(T, E)>, +} + +impl StreamConfiguration { + /// Create new `StreamConfigurationService` instance. + pub fn new() -> Self { + Self { + no_delay: None, + tcp_ka: None, + _t: PhantomData, + } + } + + /// Sets the value of the `TCP_NODELAY` option on this socket. + pub fn nodelay(mut self, nodelay: bool) -> Self { + self.no_delay = Some(nodelay); + self + } + + /// Sets whether keepalive messages are enabled to be sent on this socket. + pub fn tcp_keepalive(mut self, keepalive: Option) -> Self { + self.tcp_ka = Some(keepalive); + self + } +} + +impl NewService for StreamConfiguration { + type Request = T; + type Response = T; + type Error = E; + type InitError = (); + type Service = StreamConfigurationService; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(StreamConfigurationService { + no_delay: self.no_delay.clone(), + tcp_ka: self.tcp_ka.clone(), + _t: PhantomData, + }) + } +} + +/// Stream configuration service +pub struct StreamConfigurationService { + no_delay: Option, + tcp_ka: Option>, + _t: PhantomData<(T, E)>, +} + +impl Service for StreamConfigurationService +where + T: IoStream, +{ + type Request = T; + type Response = T; + type Error = E; + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, mut req: Self::Request) -> Self::Future { + if let Some(no_delay) = self.no_delay { + if req.set_nodelay(no_delay).is_err() { + error!("Can not set socket no-delay option"); + } + } + if let Some(keepalive) = self.tcp_ka { + if req.set_keepalive(keepalive).is_err() { + error!("Can not set socket keep-alive option"); + } + } + + ok(req) + } +} diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs index d59948c7..e56b4521 100644 --- a/src/server/ssl/nativetls.rs +++ b/src/server/ssl/nativetls.rs @@ -21,4 +21,9 @@ impl IoStream for TlsStream { fn set_linger(&mut self, dur: Option) -> io::Result<()> { self.get_mut().get_mut().set_linger(dur) } + + #[inline] + fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().get_mut().set_keepalive(dur) + } } diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs index 590dc0bb..99ca40e0 100644 --- a/src/server/ssl/openssl.rs +++ b/src/server/ssl/openssl.rs @@ -74,4 +74,9 @@ impl IoStream for SslStream { fn set_linger(&mut self, dur: Option) -> io::Result<()> { self.get_mut().get_mut().set_linger(dur) } + + #[inline] + fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().get_mut().set_keepalive(dur) + } } diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs index c74b62ea..df78d1dc 100644 --- a/src/server/ssl/rustls.rs +++ b/src/server/ssl/rustls.rs @@ -51,6 +51,11 @@ impl IoStream for TlsStream { fn set_linger(&mut self, dur: Option) -> io::Result<()> { self.get_mut().0.set_linger(dur) } + + #[inline] + fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().0.set_keepalive(dur) + } } impl IoStream for TlsStream { @@ -69,4 +74,9 @@ impl IoStream for TlsStream { fn set_linger(&mut self, dur: Option) -> io::Result<()> { self.get_mut().0.set_linger(dur) } + + #[inline] + fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().0.set_keepalive(dur) + } } diff --git a/tests/test_server.rs b/tests/test_server.rs index f8fabef6..a74cb809 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1016,7 +1016,10 @@ fn test_server_cookies() { #[test] fn test_custom_pipeline() { use actix::System; - use actix_web::server::{HttpService, KeepAlive, WorkerSettings}; + use actix_net::service::NewServiceExt; + use actix_web::server::{ + HttpService, KeepAlive, StreamConfiguration, WorkerSettings, + }; let addr = test::TestServer::unused_addr(); @@ -1034,7 +1037,9 @@ fn test_custom_pipeline() { .server_address(addr) .finish(); - HttpService::new(settings) + StreamConfiguration::new() + .nodelay(true) + .and_then(HttpService::new(settings)) }).unwrap() .run(); }); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index f67314e8..3baa48eb 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -7,7 +7,7 @@ extern crate rand; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use std::{thread, time}; +use std::thread; use bytes::Bytes; use futures::Stream; From 368f73513a733f360c4edc63f6191510989ed8ac Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 22:25:53 -0700 Subject: [PATCH 0694/1635] set tcp-keepalive for test as well --- tests/test_server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_server.rs b/tests/test_server.rs index a74cb809..a85c5c32 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1039,6 +1039,7 @@ fn test_custom_pipeline() { StreamConfiguration::new() .nodelay(true) + .tcp_keepalive(Some(time::Duration::from_secs(10))) .and_then(HttpService::new(settings)) }).unwrap() .run(); From fdfadb52e1846e6dec09f205ddbbe830927ae949 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 22:29:30 -0700 Subject: [PATCH 0695/1635] fix doc test for State --- src/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler.rs b/src/handler.rs index 399fd6ba..88210fbc 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -530,7 +530,7 @@ where /// } /// /// /// extract path info using serde -/// fn index(state: State, path: Path)) -> String { +/// fn index(state: State, path: Path) -> String { /// format!("{} {}!", state.msg, path.username) /// } /// From f007860a1650e89deae7aae9c5835632a15db16b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Oct 2018 22:48:11 -0700 Subject: [PATCH 0696/1635] cleanup warnings --- src/client/connector.rs | 5 +++++ src/server/h1.rs | 5 ++++- src/server/service.rs | 8 +++++++- src/server/ssl/openssl.rs | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 88be77f9..90a2e1c8 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1335,4 +1335,9 @@ impl IoStream for TlsStream { fn set_linger(&mut self, dur: Option) -> io::Result<()> { self.get_mut().get_mut().set_linger(dur) } + + #[inline] + fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { + self.get_mut().get_mut().set_keepalive(dur) + } } diff --git a/src/server/h1.rs b/src/server/h1.rs index 6875972e..fe8f976b 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -98,7 +98,7 @@ where (settings.now(), None) }; - let mut flags = if is_eof { + let flags = if is_eof { Flags::READ_DISCONNECTED } else if settings.keep_alive_enabled() { Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED @@ -664,6 +664,9 @@ mod tests { fn set_linger(&mut self, _: Option) -> io::Result<()> { Ok(()) } + fn set_keepalive(&mut self, _: Option) -> io::Result<()> { + Ok(()) + } } impl io::Write for Buffer { fn write(&mut self, buf: &[u8]) -> io::Result { diff --git a/src/server/service.rs b/src/server/service.rs index 89a58af7..231ac599 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -88,12 +88,15 @@ where Ok(Async::Ready(())) } - fn call(&mut self, mut req: Self::Request) -> Self::Future { + fn call(&mut self, req: Self::Request) -> Self::Future { HttpChannel::new(self.settings.clone(), req, None) } } /// `NewService` implementation for stream configuration service +/// +/// Stream configuration service allows to change some socket level +/// parameters. for example `tcp nodelay` or `tcp keep-alive`. pub struct StreamConfiguration { no_delay: Option, tcp_ka: Option>, @@ -141,6 +144,9 @@ impl NewService for StreamConfiguration { } /// Stream configuration service +/// +/// Stream configuration service allows to change some socket level +/// parameters. for example `tcp nodelay` or `tcp keep-alive`. pub struct StreamConfigurationService { no_delay: Option, tcp_ka: Option>, diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs index 99ca40e0..f9e0e177 100644 --- a/src/server/ssl/openssl.rs +++ b/src/server/ssl/openssl.rs @@ -23,7 +23,7 @@ impl OpensslAcceptor { /// Create `OpensslAcceptor` with custom server flags. pub fn with_flags( - mut builder: SslAcceptorBuilder, flags: ServerFlags, + builder: SslAcceptorBuilder, flags: ServerFlags, ) -> io::Result> { let acceptor = openssl_acceptor_with_flags(builder, flags)?; From f3ce6574e4d7e6ec2308bbd2a0235a7b25b8caf4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 00:19:28 -0700 Subject: [PATCH 0697/1635] fix client timer and add slow request tests --- CHANGES.md | 2 ++ Cargo.toml | 3 ++- src/server/builder.rs | 55 ++++++++++++++++++------------------------ src/server/channel.rs | 30 ++++++++++++++--------- src/server/h1.rs | 36 ++++++++++++++++++++++++++- src/server/http.rs | 22 +++++++++-------- src/server/settings.rs | 10 ++++++++ tests/test_server.rs | 40 ++++++++++++++++++++++++++++++ 8 files changed, 144 insertions(+), 54 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 32d2bea7..145caec1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Added slow request timeout setting +* Respond with 408 response on slow request timeout #523 + ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 205e178b..8997fa5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ language-tags = "0.2" lazy_static = "1.0" lazycell = "1.0.0" parking_lot = "0.6" +serde_urlencoded = "^0.5.3" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } @@ -125,7 +126,7 @@ webpki-roots = { version = "0.15", optional = true } # unix sockets tokio-uds = { version="0.2", optional = true } -serde_urlencoded = "^0.5.3" +backtrace="*" [dev-dependencies] env_logger = "0.5" diff --git a/src/server/builder.rs b/src/server/builder.rs index 9e932353..6bafb460 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -16,7 +16,7 @@ use super::KeepAlive; pub(crate) trait ServiceProvider { fn register( &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: u64, + addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, client_shutdown: u64, ) -> Server; } @@ -28,7 +28,6 @@ where { factory: F, acceptor: A, - no_client_timer: bool, } impl HttpServiceBuilder @@ -40,27 +39,13 @@ where { /// Create http service builder pub fn new(factory: F, acceptor: A) -> Self { - Self { - factory, - acceptor, - no_client_timer: false, - } - } - - pub(crate) fn no_client_timer(mut self) -> Self { - self.no_client_timer = true; - self + Self { factory, acceptor } } fn finish( - &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, + &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, client_shutdown: u64, ) -> impl ServiceFactory { - let timeout = if self.no_client_timer { - 0 - } else { - client_timeout - }; let factory = self.factory.clone(); let acceptor = self.acceptor.clone(); move || { @@ -68,12 +53,12 @@ where let settings = WorkerSettings::new( app, keep_alive, - timeout as u64, + client_timeout, client_shutdown, ServerSettings::new(addr, &host, false), ); - if timeout == 0 { + if secure { Either::A(ServerMessageAcceptor::new( settings.clone(), TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) @@ -88,14 +73,16 @@ where } else { Either::B(ServerMessageAcceptor::new( settings.clone(), - TcpAcceptor::new(AcceptorTimeout::new(timeout, acceptor.create())) - .map_err(|_| ()) - .map_init_err(|_| ()) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), + TcpAcceptor::new(AcceptorTimeout::new( + client_timeout, + acceptor.create(), + )).map_err(|_| ()) + .map_init_err(|_| ()) + .and_then( + HttpService::new(settings) + .map_init_err(|_| ()) + .map_err(|_| ()), + ), )) } } @@ -112,7 +99,6 @@ where HttpServiceBuilder { factory: self.factory.clone(), acceptor: self.acceptor.clone(), - no_client_timer: self.no_client_timer, } } } @@ -126,13 +112,20 @@ where { fn register( &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, client_timeout: u64, + addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, client_shutdown: u64, ) -> Server { server.listen2( "actix-web", lst, - self.finish(host, addr, keep_alive, client_timeout, client_shutdown), + self.finish( + host, + addr, + keep_alive, + secure, + client_timeout, + client_shutdown, + ), ) } } diff --git a/src/server/channel.rs b/src/server/channel.rs index d8cad970..f5780620 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -9,6 +9,7 @@ use tokio_timer::Delay; use super::error::HttpDispatchError; use super::settings::WorkerSettings; use super::{h1, h2, HttpHandler, IoStream}; +use http::StatusCode; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; @@ -42,11 +43,9 @@ where pub(crate) fn new( settings: WorkerSettings, io: T, peer: Option, ) -> HttpChannel { - let ka_timeout = settings.client_timer(); - HttpChannel { - ka_timeout, node: None, + ka_timeout: settings.client_timer(), proto: Some(HttpProtocol::Unknown( settings, peer, @@ -91,10 +90,23 @@ where fn poll(&mut self) -> Poll { // keep-alive timer - if let Some(ref mut timer) = self.ka_timeout { - match timer.poll() { + if self.ka_timeout.is_some() { + match self.ka_timeout.as_mut().unwrap().poll() { Ok(Async::Ready(_)) => { trace!("Slow request timed out, close connection"); + if let Some(HttpProtocol::Unknown(settings, _, io, buf)) = + self.proto.take() + { + self.proto = + Some(HttpProtocol::H1(h1::Http1Dispatcher::for_error( + settings, + io, + StatusCode::REQUEST_TIMEOUT, + self.ka_timeout.take(), + buf, + ))); + return self.poll(); + } return Ok(Async::Ready(())); } Ok(Async::NotReady) => (), @@ -121,12 +133,8 @@ where let mut is_eof = false; let kind = match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => { - return h1.poll(); - } - Some(HttpProtocol::H2(ref mut h2)) => { - return h2.poll(); - } + Some(HttpProtocol::H1(ref mut h1)) => return h1.poll(), + Some(HttpProtocol::H2(ref mut h2)) => return h2.poll(), Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => { let mut err = None; let mut disconnect = false; diff --git a/src/server/h1.rs b/src/server/h1.rs index fe8f976b..205be949 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -121,6 +121,31 @@ where } } + pub(crate) fn for_error( + settings: WorkerSettings, stream: T, status: StatusCode, + mut keepalive_timer: Option, buf: BytesMut, + ) -> Self { + if let Some(deadline) = settings.client_timer_expire() { + let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline)); + } + + let mut disp = Http1Dispatcher { + flags: Flags::STARTED | Flags::READ_DISCONNECTED, + stream: H1Writer::new(stream, settings.clone()), + decoder: H1Decoder::new(), + payload: None, + tasks: VecDeque::new(), + error: None, + addr: None, + ka_timer: keepalive_timer, + ka_expire: settings.now(), + buf, + settings, + }; + disp.push_response_entry(status); + disp + } + #[inline] pub fn settings(&self) -> &WorkerSettings { &self.settings @@ -133,7 +158,7 @@ where #[inline] fn can_read(&self) -> bool { - if self.flags.intersects(Flags::READ_DISCONNECTED) { + if self.flags.contains(Flags::READ_DISCONNECTED) { return false; } @@ -250,6 +275,15 @@ where ); let _ = IoStream::shutdown(io, Shutdown::Both); return Err(HttpDispatchError::ShutdownTimeout); + } else if !self.flags.contains(Flags::STARTED) { + // timeout on first request (slow request) return 408 + trace!("Slow request timeout"); + self.flags + .insert(Flags::STARTED | Flags::READ_DISCONNECTED); + self.tasks.push_back(Entry::Error(ServerError::err( + Version::HTTP_11, + StatusCode::REQUEST_TIMEOUT, + ))); } else { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); diff --git a/src/server/http.rs b/src/server/http.rs index 5a720086..91f5d73e 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -232,10 +232,10 @@ where lst, addr, scheme: "http", - handler: Box::new( - HttpServiceBuilder::new(self.factory.clone(), DefaultAcceptor) - .no_client_timer(), - ), + handler: Box::new(HttpServiceBuilder::new( + self.factory.clone(), + DefaultAcceptor, + )), }); self @@ -498,10 +498,10 @@ impl H + Send + Clone> HttpServer { .as_ref() .map(|h| h.to_owned()) .unwrap_or_else(|| format!("{}", socket.addr)); - let client_shutdown = if socket.scheme == "https" { - self.client_shutdown + let (secure, client_shutdown) = if socket.scheme == "https" { + (true, self.client_shutdown) } else { - 0 + (false, 0) }; srv = socket.handler.register( srv, @@ -509,6 +509,7 @@ impl H + Send + Clone> HttpServer { host, socket.addr, self.keep_alive, + secure, self.client_timeout, client_shutdown, ); @@ -550,10 +551,10 @@ impl H + Send + Clone> HttpServer { .as_ref() .map(|h| h.to_owned()) .unwrap_or_else(|| format!("{}", socket.addr)); - let client_shutdown = if socket.scheme == "https" { - self.client_shutdown + let (secure, client_shutdown) = if socket.scheme == "https" { + (true, self.client_shutdown) } else { - 0 + (false, 0) }; srv = socket.handler.register( srv, @@ -561,6 +562,7 @@ impl H + Send + Clone> HttpServer { host, socket.addr, self.keep_alive, + secure, self.client_timeout, client_shutdown, ); diff --git a/src/server/settings.rs b/src/server/settings.rs index a50a0706..2f306073 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -232,6 +232,16 @@ impl WorkerSettings { } } + /// Client timeout for first request. + pub fn client_timer_expire(&self) -> Option { + let delay = self.0.client_timeout; + if delay != 0 { + Some(self.now() + Duration::from_millis(delay)) + } else { + None + } + } + /// Client shutdown timer pub fn client_shutdown_timer(&self) -> Option { let delay = self.0.client_shutdown; diff --git a/tests/test_server.rs b/tests/test_server.rs index a85c5c32..269a1cd7 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1054,3 +1054,43 @@ fn test_custom_pipeline() { assert!(response.status().is_success()); } } + +#[test] +fn test_slow_request() { + use actix::System; + use std::net; + use std::sync::mpsc; + let (tx, rx) = mpsc::channel(); + + let addr = test::TestServer::unused_addr(); + + thread::spawn(move || { + System::run(move || { + let srv = server::new(|| { + vec![App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + })] + }); + + let srv = srv.bind(addr).unwrap(); + srv.client_timeout(200).start(); + let _ = tx.send(System::current()); + }); + }); + let sys = rx.recv().unwrap(); + + thread::sleep(time::Duration::from_millis(200)); + + let mut stream = net::TcpStream::connect(addr).unwrap(); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 408 Request Timeou")); + + let mut stream = net::TcpStream::connect(addr).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 408 Request Timeou")); + + sys.stop(); +} From eed377e77356f2c89b4cf9cda9ab4e76c0dbe146 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 00:20:27 -0700 Subject: [PATCH 0698/1635] uneeded dep --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8997fa5e..cedb38da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,8 +126,6 @@ webpki-roots = { version = "0.15", optional = true } # unix sockets tokio-uds = { version="0.2", optional = true } -backtrace="*" - [dev-dependencies] env_logger = "0.5" serde_derive = "1.0" From c8505bb53f6d93d4f4091c4a491e4077a5df370d Mon Sep 17 00:00:00 2001 From: Danil Berestov Date: Wed, 3 Oct 2018 00:15:48 +0800 Subject: [PATCH 0699/1635] content-length bug fix (#525) * content-length bug fix * changes.md is updated * typo --- CHANGES.md | 2 ++ src/server/output.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 145caec1..375f2882 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,8 @@ * Websocket server finished() isn't called if client disconnects #511 +* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521 + ## [0.7.8] - 2018-09-17 diff --git a/src/server/output.rs b/src/server/output.rs index 46b03c9d..70c24fac 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -11,7 +11,7 @@ use flate2::write::{GzEncoder, ZlibEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH}; -use http::Version; +use http::{StatusCode, Version}; use super::message::InnerRequest; use body::{Binary, Body}; @@ -192,7 +192,13 @@ impl Output { let transfer = match resp.body() { Body::Empty => { if !info.head { - info.length = ResponseLength::Zero; + info.length = match resp.status() { + StatusCode::NO_CONTENT + | StatusCode::CONTINUE + | StatusCode::SWITCHING_PROTOCOLS + | StatusCode::PROCESSING => ResponseLength::None, + _ => ResponseLength::Zero, + }; } *self = Output::Empty(buf); return; From f8b176de9ec17bd338229f96a1adbdaaadda0abb Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 2 Oct 2018 20:09:31 +0300 Subject: [PATCH 0700/1635] Fix no_http2 flag in HttpServer (#526) --- CHANGES.md | 1 + src/server/http.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 375f2882..a55ef7ec 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,7 @@ * Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521 +* Correct usage of `no_http2` flag in `bind_*` methods. #519 ## [0.7.8] - 2018-09-17 diff --git a/src/server/http.rs b/src/server/http.rs index 91f5d73e..6a7790c1 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -414,7 +414,7 @@ where use actix_net::service::NewServiceExt; // alpn support - let flags = if !self.no_http2 { + let flags = if self.no_http2 { ServerFlags::HTTP1 } else { ServerFlags::HTTP1 | ServerFlags::HTTP2 @@ -437,7 +437,7 @@ where use actix_net::service::NewServiceExt; // alpn support - let flags = if !self.no_http2 { + let flags = if self.no_http2 { ServerFlags::HTTP1 } else { ServerFlags::HTTP1 | ServerFlags::HTTP2 From 61c7534e0362953159f302416611ad9fa020ac80 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 10:43:23 -0700 Subject: [PATCH 0701/1635] fix stream flushing --- src/server/error.rs | 4 + src/server/h1.rs | 162 +++++++++++++++++++++-------------------- src/server/h1writer.rs | 4 + tests/test_server.rs | 32 ++++++++ tests/test_ws.rs | 14 ++-- 5 files changed, 131 insertions(+), 85 deletions(-) diff --git a/src/server/error.rs b/src/server/error.rs index eb3e8847..70f10099 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -44,6 +44,10 @@ pub enum HttpDispatchError { #[fail(display = "HTTP2 error: {}", _0)] Http2(http2::Error), + /// Payload is not consumed + #[fail(display = "Task is completed but request's payload is not consumed")] + PayloadIsNotConsumed, + /// Malformed request #[fail(display = "Malformed request")] MalformedRequest, diff --git a/src/server/h1.rs b/src/server/h1.rs index 205be949..cd913427 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -30,7 +30,7 @@ bitflags! { const READ_DISCONNECTED = 0b0001_0000; const WRITE_DISCONNECTED = 0b0010_0000; const POLLED = 0b0100_0000; - + const FLUSHED = 0b1000_0000; } } @@ -99,9 +99,9 @@ where }; let flags = if is_eof { - Flags::READ_DISCONNECTED + Flags::READ_DISCONNECTED | Flags::FLUSHED } else if settings.keep_alive_enabled() { - Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED + Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED } else { Flags::empty() }; @@ -130,7 +130,7 @@ where } let mut disp = Http1Dispatcher { - flags: Flags::STARTED | Flags::READ_DISCONNECTED, + flags: Flags::STARTED | Flags::READ_DISCONNECTED | Flags::FLUSHED, stream: H1Writer::new(stream, settings.clone()), decoder: H1Decoder::new(), payload: None, @@ -177,7 +177,8 @@ where } if !checked || self.tasks.is_empty() { - self.flags.insert(Flags::WRITE_DISCONNECTED); + self.flags + .insert(Flags::WRITE_DISCONNECTED | Flags::FLUSHED); self.stream.disconnected(); // notify all tasks @@ -205,54 +206,70 @@ where // shutdown if self.flags.contains(Flags::SHUTDOWN) { - if self - .flags - .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) - { + if self.flags.intersects(Flags::WRITE_DISCONNECTED) { return Ok(Async::Ready(())); } - match self.stream.poll_completed(true) { - Ok(Async::NotReady) => return Ok(Async::NotReady), - Ok(Async::Ready(_)) => return Ok(Async::Ready(())), - Err(err) => { - debug!("Error sending data: {}", err); - return Err(err.into()); - } - } + return self.poll_flush(true); } - self.poll_io()?; - + // process incoming requests if !self.flags.contains(Flags::WRITE_DISCONNECTED) { - match self.poll_handler()? { - Async::Ready(true) => self.poll(), - Async::Ready(false) => { - self.flags.insert(Flags::SHUTDOWN); - self.poll() + self.poll_handler()?; + + // flush stream + self.poll_flush(false)?; + + // deal with keep-alive and stream eof (client-side write shutdown) + if self.tasks.is_empty() && self.flags.intersects(Flags::FLUSHED) { + // handle stream eof + if self + .flags + .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) + { + return Ok(Async::Ready(())); } - Async::NotReady => { - // deal with keep-alive and steam eof (client-side write shutdown) - if self.tasks.is_empty() { - // handle stream eof - if self.flags.intersects( - Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED, - ) { - return Ok(Async::Ready(())); - } - // no keep-alive - if self.flags.contains(Flags::STARTED) - && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) - || !self.flags.contains(Flags::KEEPALIVE)) - { - self.flags.insert(Flags::SHUTDOWN); - return self.poll(); - } + // no keep-alive + if self.flags.contains(Flags::STARTED) + && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) + || !self.flags.contains(Flags::KEEPALIVE)) + { + self.flags.insert(Flags::SHUTDOWN); + return self.poll(); + } + } + Ok(Async::NotReady) + } else if let Some(err) = self.error.take() { + Err(err) + } else { + Ok(Async::Ready(())) + } + } + + /// Flush stream + fn poll_flush(&mut self, shutdown: bool) -> Poll<(), HttpDispatchError> { + if shutdown || self.flags.contains(Flags::STARTED) { + match self.stream.poll_completed(shutdown) { + Ok(Async::NotReady) => { + // mark stream + if !self.stream.flushed() { + self.flags.remove(Flags::FLUSHED); } Ok(Async::NotReady) } + Err(err) => { + debug!("Error sending data: {}", err); + self.client_disconnected(false); + return Err(err.into()); + } + Ok(Async::Ready(_)) => { + // if payload is not consumed we can not use connection + if self.payload.is_some() && self.tasks.is_empty() { + return Err(HttpDispatchError::PayloadIsNotConsumed); + } + self.flags.insert(Flags::FLUSHED); + Ok(Async::Ready(())) + } } - } else if let Some(err) = self.error.take() { - Err(err) } else { Ok(Async::Ready(())) } @@ -317,20 +334,23 @@ where } #[inline] - /// read data from stream - pub(self) fn poll_io(&mut self) -> Result<(), HttpDispatchError> { + /// read data from the stream + pub(self) fn poll_io(&mut self) -> Result { if !self.flags.contains(Flags::POLLED) { - self.parse()?; + let updated = self.parse()?; self.flags.insert(Flags::POLLED); - return Ok(()); + return Ok(updated); } // read io from socket + let mut updated = false; if self.can_read() && self.tasks.len() < MAX_PIPELINED_MESSAGES { match self.stream.get_mut().read_available(&mut self.buf) { Ok(Async::Ready((read_some, disconnected))) => { if read_some { - self.parse()?; + if self.parse()? { + updated = true; + } } if disconnected { self.client_disconnected(true); @@ -343,13 +363,14 @@ where } } } - Ok(()) + Ok(updated) } - pub(self) fn poll_handler(&mut self) -> Poll { - let retry = self.can_read(); + pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { + self.poll_io()?; + let mut retry = self.can_read(); - // process first pipelined response, only one task can do io operation in http/1 + // process first pipelined response, only first task can do io operation in http/1 while !self.tasks.is_empty() { match self.tasks[0].poll_io(&mut self.stream) { Ok(Async::Ready(ready)) => { @@ -375,9 +396,12 @@ where } // if read-backpressure is enabled and we consumed some data. - // we may read more data + // we may read more dataand retry if !retry && self.can_read() { - return Ok(Async::Ready(true)); + if self.poll_io()? { + retry = self.can_read(); + continue; + } } break; } @@ -431,25 +455,7 @@ where } } - // flush stream - if self.flags.contains(Flags::STARTED) { - match self.stream.poll_completed(false) { - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { - debug!("Error sending data: {}", err); - self.client_disconnected(false); - return Err(err.into()); - } - Ok(Async::Ready(_)) => { - // if payload is not consumed we can not use connection - if self.payload.is_some() && self.tasks.is_empty() { - return Ok(Async::Ready(false)); - } - } - } - } - - Ok(Async::NotReady) + Ok(()) } fn push_response_entry(&mut self, status: StatusCode) { @@ -457,7 +463,7 @@ where .push_back(Entry::Error(ServerError::err(Version::HTTP_11, status))); } - pub(self) fn parse(&mut self) -> Result<(), HttpDispatchError> { + pub(self) fn parse(&mut self) -> Result { let mut updated = false; 'outer: loop { @@ -524,7 +530,7 @@ where payload.feed_data(chunk); } else { error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::READ_DISCONNECTED); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); self.error = Some(HttpDispatchError::InternalError); break; @@ -536,7 +542,7 @@ where payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::READ_DISCONNECTED); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); self.error = Some(HttpDispatchError::InternalError); break; @@ -559,7 +565,7 @@ where // Malformed requests should be responded with 400 self.push_response_entry(StatusCode::BAD_REQUEST); - self.flags.insert(Flags::READ_DISCONNECTED); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); self.error = Some(HttpDispatchError::MalformedRequest); break; } @@ -571,7 +577,7 @@ where self.ka_expire = expire; } } - Ok(()) + Ok(updated) } } diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 3036aa08..5c32de3a 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -62,6 +62,10 @@ impl H1Writer { self.flags = Flags::KEEPALIVE; } + pub fn flushed(&mut self) -> bool { + self.buffer.is_empty() + } + pub fn disconnected(&mut self) { self.flags.insert(Flags::DISCONNECTED); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 269a1cd7..03a89642 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1094,3 +1094,35 @@ fn test_slow_request() { sys.stop(); } + +#[test] +fn test_malformed_request() { + use actix::System; + use std::net; + use std::sync::mpsc; + let (tx, rx) = mpsc::channel(); + + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + System::run(move || { + let srv = server::new(|| { + vec![App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + })] + }); + + let _ = srv.bind(addr).unwrap().start(); + let _ = tx.send(System::current()); + }); + }); + let sys = rx.recv().unwrap(); + thread::sleep(time::Duration::from_millis(200)); + + let mut stream = net::TcpStream::connect(addr).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 400 Bad Request")); + + sys.stop(); +} diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 3baa48eb..522832e0 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -7,7 +7,7 @@ extern crate rand; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use std::thread; +use std::{thread, time}; use bytes::Bytes; use futures::Stream; @@ -380,17 +380,17 @@ fn test_ws_stopped() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); - let _ = thread::spawn(move || { + let mut srv = test::TestServer::new(move |app| { let num3 = num2.clone(); - let mut srv = test::TestServer::new(move |app| { - let num4 = num3.clone(); - app.handler(move |req| ws::start(req, WsStopped(num4.clone()))) - }); + app.handler(move |req| ws::start(req, WsStopped(num3.clone()))) + }); + { let (reader, mut writer) = srv.ws().unwrap(); writer.text("text"); let (item, _) = srv.execute(reader.into_future()).unwrap(); assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - }).join(); + } + thread::sleep(time::Duration::from_millis(1000)); assert_eq!(num.load(Ordering::Relaxed), 1); } From 724668910b5817e6e9a5f9efab92da871d2b6941 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 11:18:59 -0700 Subject: [PATCH 0702/1635] fix ssh handshake timeout --- src/server/acceptor.rs | 13 +++++--- src/server/builder.rs | 38 +++++++--------------- tests/test_server.rs | 72 +++++++++++++++++++++++++++++++++++++++--- tests/test_ws.rs | 4 +-- 4 files changed, 90 insertions(+), 37 deletions(-) diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index 15d66112..3dcd8ac8 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -176,12 +176,15 @@ where /// Applies timeout to request prcoessing. pub(crate) struct AcceptorTimeout { inner: T, - timeout: u64, + timeout: Duration, } impl AcceptorTimeout { pub(crate) fn new(timeout: u64, inner: T) -> Self { - Self { inner, timeout } + Self { + inner, + timeout: Duration::from_millis(timeout), + } } } @@ -204,7 +207,7 @@ impl NewService for AcceptorTimeout { #[doc(hidden)] pub(crate) struct AcceptorTimeoutFut { fut: T::Future, - timeout: u64, + timeout: Duration, } impl Future for AcceptorTimeoutFut { @@ -225,7 +228,7 @@ impl Future for AcceptorTimeoutFut { /// Applies timeout to request prcoessing. pub(crate) struct AcceptorTimeoutService { inner: T, - timeout: u64, + timeout: Duration, } impl Service for AcceptorTimeoutService { @@ -241,7 +244,7 @@ impl Service for AcceptorTimeoutService { fn call(&mut self, req: Self::Request) -> Self::Future { AcceptorTimeoutResponse { fut: self.inner.call(req), - sleep: sleep(Duration::from_millis(self.timeout)), + sleep: sleep(self.timeout), } } } diff --git a/src/server/builder.rs b/src/server/builder.rs index 6bafb460..8a979752 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -59,18 +59,6 @@ where ); if secure { - Either::A(ServerMessageAcceptor::new( - settings.clone(), - TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) - .map_err(|_| ()) - .map_init_err(|_| ()) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } else { Either::B(ServerMessageAcceptor::new( settings.clone(), TcpAcceptor::new(AcceptorTimeout::new( @@ -84,25 +72,23 @@ where .map_err(|_| ()), ), )) + } else { + Either::A(ServerMessageAcceptor::new( + settings.clone(), + TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) + .map_err(|_| ()) + .map_init_err(|_| ()) + .and_then( + HttpService::new(settings) + .map_init_err(|_| ()) + .map_err(|_| ()), + ), + )) } } } } -impl Clone for HttpServiceBuilder -where - F: Fn() -> H + Send + Clone, - H: IntoHttpHandler, - A: AcceptorServiceFactory, -{ - fn clone(&self) -> Self { - HttpServiceBuilder { - factory: self.factory.clone(), - acceptor: self.acceptor.clone(), - } - } -} - impl ServiceProvider for HttpServiceBuilder where F: Fn() -> H + Send + Clone + 'static, diff --git a/tests/test_server.rs b/tests/test_server.rs index 03a89642..4f33e313 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -15,6 +15,9 @@ extern crate tokio_current_thread as current_thread; extern crate tokio_reactor; extern crate tokio_tcp; +#[cfg(feature = "ssl")] +extern crate openssl; + use std::io::{Read, Write}; use std::sync::Arc; use std::{thread, time}; @@ -1084,13 +1087,13 @@ fn test_slow_request() { let mut stream = net::TcpStream::connect(addr).unwrap(); let mut data = String::new(); let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeou")); + assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); let mut stream = net::TcpStream::connect(addr).unwrap(); let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeou")); + assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); sys.stop(); } @@ -1106,9 +1109,9 @@ fn test_malformed_request() { thread::spawn(move || { System::run(move || { let srv = server::new(|| { - vec![App::new().resource("/", |r| { + App::new().resource("/", |r| { r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] + }) }); let _ = srv.bind(addr).unwrap().start(); @@ -1126,3 +1129,64 @@ fn test_malformed_request() { sys.stop(); } + +#[test] +fn test_app_404() { + let mut srv = test::TestServer::with_factory(|| { + App::new().prefix("/prefix").resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + }) + }); + + let request = srv.client(http::Method::GET, "/prefix/").finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + let request = srv.client(http::Method::GET, "/").finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::NOT_FOUND); +} + +#[test] +#[cfg(feature = "ssl")] +fn test_ssl_handshake_timeout() { + use actix::System; + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + use std::net; + use std::sync::mpsc; + + let (tx, rx) = mpsc::channel(); + let addr = test::TestServer::unused_addr(); + + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); + + thread::spawn(move || { + System::run(move || { + let srv = server::new(|| { + App::new().resource("/", |r| { + r.method(http::Method::GET).f(|_| HttpResponse::Ok()) + }) + }); + + srv.bind_ssl(addr, builder) + .unwrap() + .workers(1) + .client_timeout(200) + .start(); + let _ = tx.send(System::current()); + }); + }); + let sys = rx.recv().unwrap(); + + let mut stream = net::TcpStream::connect(addr).unwrap(); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.is_empty()) +} diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 522832e0..ebb5ff29 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -14,7 +14,7 @@ use futures::Stream; use rand::distributions::Alphanumeric; use rand::Rng; -#[cfg(feature = "alpn")] +#[cfg(feature = "ssl")] extern crate openssl; #[cfg(feature = "rust-tls")] extern crate rustls; @@ -282,7 +282,7 @@ fn test_server_send_bin() { } #[test] -#[cfg(feature = "alpn")] +#[cfg(feature = "ssl")] fn test_ws_server_ssl() { extern crate openssl; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; From b59712c439c438ddc73efaf0df17c72b2fd5e9d9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 11:32:43 -0700 Subject: [PATCH 0703/1635] add ssl handshake timeout tests --- src/server/h1.rs | 4 +- tests/test_server.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_ws.rs | 2 - 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index cd913427..af7e6529 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -206,7 +206,7 @@ where // shutdown if self.flags.contains(Flags::SHUTDOWN) { - if self.flags.intersects(Flags::WRITE_DISCONNECTED) { + if self.flags.contains(Flags::WRITE_DISCONNECTED) { return Ok(Async::Ready(())); } return self.poll_flush(true); @@ -220,7 +220,7 @@ where self.poll_flush(false)?; // deal with keep-alive and stream eof (client-side write shutdown) - if self.tasks.is_empty() && self.flags.intersects(Flags::FLUSHED) { + if self.tasks.is_empty() && self.flags.contains(Flags::FLUSHED) { // handle stream eof if self .flags diff --git a/tests/test_server.rs b/tests/test_server.rs index 4f33e313..9c17fd66 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -890,6 +890,100 @@ fn test_brotli_encoding_large() { assert_eq!(bytes, Bytes::from(data)); } +#[cfg(all(feature = "brotli", future = "ssl"))] +#[test] +fn test_ssl_brotli_encoding_large() { + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); + + let data = STR.repeat(10); + let mut srv = test::TestServer::build().ssl(builder).start(|app| { + app.handler(|req: &HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }).responder() + }) + }); + + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let request = srv + .post() + .header(http::header::CONTENT_ENCODING, "br") + .body(enc) + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} + +#[cfg(future = "rust-ssl")] +#[test] +fn test_reading_deflate_encoding_large_random_ssl() { + use rustls::internal::pemfile::{certs, rsa_private_keys}; + use rustls::{NoClientAuth, ServerConfig}; + use std::fs::File; + use std::io::BufReader; + + // load ssl keys + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = rsa_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); + + let mut srv = test::TestServer::build().rustls(config).start(|app| { + app.handler(|req: &HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }).responder() + }) + }); + + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let request = srv + .post() + .header(http::header::CONTENT_ENCODING, "deflate") + .body(enc) + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); +} + #[test] fn test_h2() { let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index ebb5ff29..5a0ce204 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -284,7 +284,6 @@ fn test_server_send_bin() { #[test] #[cfg(feature = "ssl")] fn test_ws_server_ssl() { - extern crate openssl; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys @@ -320,7 +319,6 @@ fn test_ws_server_ssl() { #[test] #[cfg(feature = "rust-tls")] fn test_ws_server_rust_tls() { - extern crate rustls; use rustls::internal::pemfile::{certs, rsa_private_keys}; use rustls::{NoClientAuth, ServerConfig}; use std::fs::File; From d7379bd10b19ac0aa8778b89c9d41a2538d5f5d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 13:41:33 -0700 Subject: [PATCH 0704/1635] update server ssl tests; upgrade rustls --- CHANGES.md | 1 + Cargo.toml | 4 +- tests/identity.pfx | Bin 0 -> 5549 bytes tests/test_server.rs | 146 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 tests/identity.pfx diff --git a/CHANGES.md b/CHANGES.md index a55ef7ec..3c55c3f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ * Correct usage of `no_http2` flag in `bind_*` methods. #519 + ## [0.7.8] - 2018-09-17 ### Added diff --git a/Cargo.toml b/Cargo.toml index cedb38da..46719d70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,8 +118,8 @@ openssl = { version="0.10", optional = true } tokio-openssl = { version="0.2", optional = true } #rustls -rustls = { version = "^0.13.1", optional = true } -tokio-rustls = { version = "^0.7.2", optional = true } +rustls = { version = "0.14", optional = true } +tokio-rustls = { version = "0.8", optional = true } webpki = { version = "0.18", optional = true } webpki-roots = { version = "0.15", optional = true } diff --git a/tests/identity.pfx b/tests/identity.pfx new file mode 100644 index 0000000000000000000000000000000000000000..946e3b8b8ae10e19a11e7ac6eead66b12fff0014 GIT binary patch literal 5549 zcmY+GRZtv&vTbn*Fa-DD5S(Fv;O=h0gF_fx!{8nycyM=jcXvr}cXtMNxH(n#z4P8j zS68jQyT2EE0A2|kEIfMvo;?yO<4>8N_ZYCqu-O54MhF3T`v0&tdjMMWe%lL7KUFAFV5{u;owkU`~uKqm}O*fBSo5RVYIG` zPAKF6ePO1*(FhW)-QpFAvtO?cVWGD0hs0WO4>qy>9T48E19Q`LuD0r#V<$Vj_c2yp z)4}gHs(AF<^Bo{R_`YId+=%k!c;why`_I0GXxv)FEmD)RG7Vs?g`z@xC^k*0+`Ps8 zZQH$QkvVVeOE(P8=g*0kvj|2!A! zCaUuJ^XO0NPlE$=oQRK9ti`Ic7JJnq;osnZ4OA`G7m{`HKP{cH&`YLS z&7cXaq1TKaOG&uTJqqY25!-%6M82yrFSuJv{`JhJUOE4ZMT1J%h8u3pSuQ$qle0}C z2}0Lk)3OpsFm1yeJ1|XW9gt0oX1PIiJ+HG_ccQbIH}C6hb|aPpRO(@Rt|u~DJIX%l zC+^x;>&_>s!^v1hwDp-biu2rspT|oc!3;MUJ>ui2i(Lamzq&B9OCcN`j&N`^wTcd1 z`DFH$T83h&1Ws&{N(khR##{-N3RSG@_ZPyM3SQ_TAXRRqyV#LUkE0&kEvE8Y61{fKJ+Y= z$eQ*1oOaeF^Z9A#-r@E7LmI)arBxsyT>V+(Ruq)&tBqzOi1sjBwNjPe_jraq(=2r^ zB|%@2bxrAnZr&d!&MNXZn&!iHPAs)aINufHy*7>&8Ap}7vY+Ji590{9Gq+KBpjkN0 zeCWDYHbCrNc%062ljm(+!6eM>ku}@T8$$TYjIT%~Y3z>YH2FxhEJ6lUL_#W3!=?ks zFo969*bgk}a!GRqXMSjjgd&?6Y%hBVuhL~74jyLlXNnJzvi?lWEo_HD7ZkXXB+vYf z**da2rqVV>3tDz^j`grt{G5Ye8`Q@KgQelPxGF%zLkZ=m7N>eor9pQmK6B$F&s9TaFeP0w?b5Q2p`A;be~m;x!mwj;I4ye%g!Op2Z^tb4S}i z#o}zOP0;F#mw3YEF_5DmxuBJQ{MZN!`^i6PwkgfA(n$bs9PQx6aZB5p$2&d$+`VjO zsH)tO=SExUR825&T#?}~JqRDb1{y6YsTMn zYINH#Ru0n1kxJc|=rVjd@`uh*nzIv3n@lt8^$P6GC5xIW1?7XLu5&GtX$Ho%rp5@a z<;*{8D{Rd@ocNX}STxWU(2moCw*4dI^{&HccPIwaXF^%V^P@gTFF*ow(`z_5$lNR7B+?<1E> zULlb}pDx~mAAb5{pg@(IH|PW09Pz6r7!}<9>(fAn8=3nRL|EV^f5o-!1D@`uY_E0= zXWciaPcKF60&?K+MAnU0gz}SuZb>wVAeRY>Lz9(&Zu_rB_JiKSV8HOqO3;vSD41G~ zIYpO4JcJ5K0+;O6x0VlNvNY%;v*XCt2V>-|%pxixP0jfH+xE^8^D3ZXs zS4AQ;$|oz)t4F{7(j7F7l{R}lJhwq)ekCh$|7`g4CXbIghAm6OSZ1L&K5-Y1w$mQa z_MBdP-vu45dbf@y=ls^2$D{4YS(f+0N}W49&$hLSRWl_WZB;uN`i7GQSU2{> z%tc5FX16D4Hc~M^2Q-}r|Th>@2!HT<`AbzLpW6XWl;-{}i)bscjNo9~Z zt>K*BFR$SlC#sBVi?$v)Y?HUyl)E2s`8W6ZXD9n+OhlM3@csT_+o|7G5+{-kChNBE zGoihxT`ld2`0D_n8Kx;B37>D}D7${tb4|NGV4*rXJY< zrP!d-L?ATkL~#^D#VB&5mZjsJsQ(E>4kqY74x%)7DdB0tEho2OB+t;4_6jhwgA|cO znS?z?-;5Y{h$UXY8kALQOM~}sKX{KB4C#6j4aK&Qk9w{6xr6GoWSrQBZ-V^FUe|IuEu|!Ho zP~_-_Q3K`aH^E3h7qd#rjmRUmA*ZPMaLSD!4zIf>se1cE%=z*bSCvx#B3|QUR$0#m9GAZ3L&y-$F1DIC& zT8gc<4SckFy#eY%&9U^Kz5>cD`L{z)t6x+lxMS@K!T}DzX*QtVExm3-I%(wjkMP#> z1(Cx)JgRslsAW|p`13(`X}#T<>eipuNW4PA4R2-@N+Vc&(y87pk_BSeS+d6)P$^TP z`?$x%WPFPWh9eb*vW#gb#gty{p~gAkXo)>T5Uf}c#r*~bw?jGhA9M7i8I5Xoby}f^ z^p4Qzi_ghn7*)HR6CmO5t+f(DYnCA3GX_!BFzYWFViu9Jn~Rh{W4lS>Ien~yruGbu z>(;O4jayyFSMEP1yQ9A{eQ^Hh(X@0+auu!ON=}-efn0d?U5LMh$zI#vo9nqvQ$058 z0%`dSn#OpIe|FctVo1fv^b^C(=MqC(usOYfc9zoBNAJEY_F!S;aKK8MEaXZ2?J0*q z%9PBkwW53|md4e|UjFDs!BwW$KTztoZy9^)QA!(U)itkV!h$GWP*eYJRt+x@x5V%i z1J7`#SEWSE_@>C3i32lz2g3U@e}8X+mAz+L`pS5%*YB7Dxr&GE4t*>KB6I zE-@2=x9^2U&Ux`Z5<+-#U+{a@#CzMU?Leqv9AhmP6iaWp>Oq!NsxHbS=EW1!zJLz{ zMUGYEB7n6q3Er#o<|Z`u0MwrUN4&EGP-_taP%Ho8(tlHkg!X?l`~xi9ztHXDeK*m&V~nDC|pLVujbH(b=fk1EfnQhXPjX1C48B*(7^BO>cFO~ zAH*SPpf#PD###N!E9oD!h87(U5h5>lHJLd?LymUDW$S z^HqD#EbaAcgn)aq3MVH(f*KmTMjHO5dW|_+katLspc=S!v^H3wli*n6ojf^qf3K=y zKr%Yay%^*Xvh0;}J@-TW@bcRzax<}Q{(@FjI+hfy@)|L*hscS(TttTvz_%Nv+(z8GGr?Ic>si}%oD zjS`B@EE+;f!(VuH>B7DBvpXijW%<{YjhQlo9fhwvuO{nH%@)BNiq<1`YY&2FCcm1v z)V&H!Kcj^3Bt0y%<I%;IH=P zJ41oky-lS{I9?XOY~Id^5UX<7RGwt!+GLyLENxHkj-snrrD9wg(faIi6I{jBh1go{ zM*ViN9ui7wip-=5;2)#r3!%>8z?BacE<1_@ALhkFNVIX%-ABNFWSr=jiLQ6%-{KRS zWwq<$wd1t!5~>t;g)EsTUt{vq;Fq-VE-~ml6ZXxgTNfUz_@@T7 zjsLv05k4~6I(7sOoX;Me9Mx*!2n`*&G62*4lL#eRXMj)}# zKr96@p5g@T(q@%oV#NRfPt?05WUo1wQk&aO{Pmwj7rdpbgcv6`Z78Sujv^Cw!}t9r`^Lc8t$WvLza7iBfkmc zDDY9Tit~6VzqS0L=Ayqpm%buj8Ag(=8<}4__o+jk*7sRDS~t>ecLxvu9W`08b;!5* zVykt!s+BvMXq)=q=y6Z4d=}r*-a;}j-*Qyy?4M4cOLy-!>N4}f4H%^$+ju=$d;iJ7 zPw8!Js)`{-TTglxv%Sm^K45gm#!5u2ni5W%dv9_+kEJG*<)*>LnTIO3 zP6HOD&&FrUjtF+byLCKHD-ACb%_vi{l0xPj|E3=MNnc)wn90{jb#I)vL$jsxtHlPu zCN(WOd)y*If;(Pc2D_T*H8TGC6#FjyA&L=1eB$U3^IK?5o$tR7-J`4e7hlpb7w~C+Agyessj{qzudSzQHf)p zE3bG;_o5JE@|R;^XOzYSMRqL`ey& z7cxd^&=J@~sxSdNJ~4`WKG3Pf5(-q%1&@Bp@h^T2p}7IVJL}{Ir#}ZYrY(`qOdx+V z!4-keXG9fPwZ72Z>0InsQ!U51)YwT!#5(o7Q73y)O7Vj^xant+Mw#45BH$9?B1vV- zK-S5Bmx_T9S^3JTc)fx@Q&R72RSU{u74roB{&3hlzknbj{dy%m?DxD5GH*40Tk$q; zDxcEj{-~XXePdPnzs!=S2lkV$IweaW8lBM@z7iZlAu5N%pYus=+D*DQyFs=GYfXl#{yYa^Ur z^wdzws}mB6Y~FeN1FIrbx)Pe=LH~}x^wgbl7Mnn)HlS|~MKWejJ*=#vF+_%p7!)FC z*>GliQit=X=~)<^UO-kl>$hKAhy@PbN%T6E8AFDRIPqP?3nKbu9SC1*x-_q%oa>un z)jigLjSg;Ie=Sf4&{X0(O6asc*}f}KP)&dmbcEmjIu<#h|51e3-$TXpM$ACpy4wx^ z7~&b%^6mSNlP@DXnZzWuf4O5+N;qfkl$8#UJw?Z-lL=nl=k*Qj5K!X_jNe5*ceN|1 zJO(G;HL??jza#IvW7CL&O%M}9wD`Q<7M?twO`H?+^WCB~JG8Y5`NHaesh58pW(2{G0QEPc8si#*pBZg zbmb#%Lx#-6A2aE}_`5AxI8xhZ#FL@NXPYMwK!#-*abrgDL>W=V)NBx_S5!i=ATh~B zoT!rujGkse$2dK9!A$E8ILE})=TNqjus$Y5VZgwNlvHx9Q@B&7X378MKMHcNc9XmD z{1dA1N8T7+{i%3V@mO!?q$Lgg=zTVwG9vk)dN9s4cwe literal 0 HcmV?d00001 diff --git a/tests/test_server.rs b/tests/test_server.rs index 9c17fd66..240a5ddc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -15,8 +15,12 @@ extern crate tokio_current_thread as current_thread; extern crate tokio_reactor; extern crate tokio_tcp; +#[cfg(feature = "tls")] +extern crate native_tls; #[cfg(feature = "ssl")] extern crate openssl; +#[cfg(feature = "rust-tls")] +extern crate rustls; use std::io::{Read, Write}; use std::sync::Arc; @@ -890,10 +894,13 @@ fn test_brotli_encoding_large() { assert_eq!(bytes, Bytes::from(data)); } -#[cfg(all(feature = "brotli", future = "ssl"))] +#[cfg(all(feature = "brotli", feature = "ssl"))] #[test] -fn test_ssl_brotli_encoding_large() { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; +fn test_brotli_encoding_large_ssl() { + use actix::{Actor, System}; + use openssl::ssl::{ + SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, + }; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); builder @@ -904,7 +911,7 @@ fn test_ssl_brotli_encoding_large() { .unwrap(); let data = STR.repeat(10); - let mut srv = test::TestServer::build().ssl(builder).start(|app| { + let srv = test::TestServer::build().ssl(builder).start(|app| { app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { @@ -914,28 +921,39 @@ fn test_ssl_brotli_encoding_large() { }).responder() }) }); + let mut rt = System::new("test"); + // client connector + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let conn = client::ClientConnector::with_connector(builder.build()).start(); + + // body let mut e = BrotliEncoder::new(Vec::new(), 5); e.write_all(data.as_ref()).unwrap(); let enc = e.finish().unwrap(); // client request - let request = srv - .post() + let request = client::ClientRequest::build() + .uri(srv.url("/")) + .method(http::Method::POST) .header(http::header::CONTENT_ENCODING, "br") + .with_connector(conn) .body(enc) .unwrap(); - let response = srv.execute(request.send()).unwrap(); + let response = rt.block_on(request.send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = rt.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from(data)); } -#[cfg(future = "rust-ssl")] +#[cfg(all(feature = "rust-tls", feature = "ssl"))] #[test] fn test_reading_deflate_encoding_large_random_ssl() { + use actix::{Actor, System}; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; use rustls::internal::pemfile::{certs, rsa_private_keys}; use rustls::{NoClientAuth, ServerConfig}; use std::fs::File; @@ -954,7 +972,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { .take(160_000) .collect::(); - let mut srv = test::TestServer::build().rustls(config).start(|app| { + let srv = test::TestServer::build().rustls(config).start(|app| { app.handler(|req: &HttpRequest| { req.body() .and_then(|bytes: Bytes| { @@ -965,25 +983,120 @@ fn test_reading_deflate_encoding_large_random_ssl() { }) }); + let mut rt = System::new("test"); + + // client connector + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let conn = client::ClientConnector::with_connector(builder.build()).start(); + + // encode data let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); e.write_all(data.as_ref()).unwrap(); let enc = e.finish().unwrap(); // client request - let request = srv - .post() + let request = client::ClientRequest::build() + .uri(srv.url("/")) + .method(http::Method::POST) .header(http::header::CONTENT_ENCODING, "deflate") + .with_connector(conn) .body(enc) .unwrap(); - let response = srv.execute(request.send()).unwrap(); + let response = rt.block_on(request.send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = rt.block_on(response.body()).unwrap(); assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, Bytes::from(data)); } +#[cfg(all(feature = "tls", feature = "ssl"))] +#[test] +fn test_reading_deflate_encoding_large_random_tls() { + use native_tls::{Identity, TlsAcceptor}; + use openssl::ssl::{ + SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, + }; + use std::fs::File; + use std::sync::mpsc; + + use actix::{Actor, System}; + let (tx, rx) = mpsc::channel(); + + // load ssl keys + let mut file = File::open("tests/identity.pfx").unwrap(); + let mut identity = vec![]; + file.read_to_end(&mut identity).unwrap(); + let identity = Identity::from_pkcs12(&identity, "1").unwrap(); + let acceptor = TlsAcceptor::new(identity).unwrap(); + + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); + + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); + + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + System::run(move || { + server::new(|| { + App::new().handler("/", |req: &HttpRequest| { + req.body() + .and_then(|bytes: Bytes| { + Ok(HttpResponse::Ok() + .content_encoding(http::ContentEncoding::Identity) + .body(bytes)) + }).responder() + }) + }).bind_tls(addr, acceptor) + .unwrap() + .start(); + let _ = tx.send(System::current()); + }); + }); + let sys = rx.recv().unwrap(); + + let mut rt = System::new("test"); + + // client connector + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let conn = client::ClientConnector::with_connector(builder.build()).start(); + + // encode data + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let request = client::ClientRequest::build() + .uri(format!("https://{}/", addr)) + .method(http::Method::POST) + .header(http::header::CONTENT_ENCODING, "deflate") + .with_connector(conn) + .body(enc) + .unwrap(); + let response = rt.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = rt.block_on(response.body()).unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); + + let _ = sys.stop(); +} + #[test] fn test_h2() { let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); @@ -1160,7 +1273,6 @@ fn test_slow_request() { let (tx, rx) = mpsc::channel(); let addr = test::TestServer::unused_addr(); - thread::spawn(move || { System::run(move || { let srv = server::new(|| { @@ -1282,5 +1394,7 @@ fn test_ssl_handshake_timeout() { let mut stream = net::TcpStream::connect(addr).unwrap(); let mut data = String::new(); let _ = stream.read_to_string(&mut data); - assert!(data.is_empty()) + assert!(data.is_empty()); + + let _ = sys.stop(); } From ae5c4dfb7812caaa95b550f379fa3312dd6fcd01 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 15:25:32 -0700 Subject: [PATCH 0705/1635] refactor http channels list; rename WorkerSettings --- src/client/connector.rs | 1 + src/server/acceptor.rs | 17 +++-- src/server/builder.rs | 4 +- src/server/channel.rs | 148 ++++++++++++++++++++-------------------- src/server/h1.rs | 32 ++++----- src/server/h1decoder.rs | 6 +- src/server/h1writer.rs | 6 +- src/server/h2.rs | 10 +-- src/server/h2writer.rs | 8 +-- src/server/incoming.rs | 6 +- src/server/mod.rs | 2 +- src/server/service.rs | 20 ++++-- src/server/settings.rs | 43 ++++++------ tests/test_server.rs | 4 +- 14 files changed, 157 insertions(+), 150 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 90a2e1c8..07c7b646 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -293,6 +293,7 @@ impl Default for ClientConnector { } }; + #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_unit_value))] ClientConnector::with_connector_impl(connector) } } diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index 3dcd8ac8..79d133d2 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -9,9 +9,10 @@ use tokio_reactor::Handle; use tokio_tcp::TcpStream; use tokio_timer::{sleep, Delay}; +use super::channel::HttpProtocol; use super::error::AcceptorError; use super::handler::HttpHandler; -use super::settings::WorkerSettings; +use super::settings::ServiceConfig; use super::IoStream; /// This trait indicates types that can create acceptor service for http server. @@ -271,7 +272,7 @@ impl Future for AcceptorTimeoutResponse { pub(crate) struct ServerMessageAcceptor { inner: T, - settings: WorkerSettings, + settings: ServiceConfig, } impl ServerMessageAcceptor @@ -279,7 +280,7 @@ where H: HttpHandler, T: NewService, { - pub(crate) fn new(settings: WorkerSettings, inner: T) -> Self { + pub(crate) fn new(settings: ServiceConfig, inner: T) -> Self { ServerMessageAcceptor { inner, settings } } } @@ -310,7 +311,7 @@ where T: NewService, { fut: T::Future, - settings: WorkerSettings, + settings: ServiceConfig, } impl Future for ServerMessageAcceptorResponse @@ -334,7 +335,7 @@ where pub(crate) struct ServerMessageAcceptorService { inner: T, - settings: WorkerSettings, + settings: ServiceConfig, } impl Service for ServerMessageAcceptorService @@ -359,9 +360,11 @@ where fut: self.inner.call(stream), }) } - ServerMessage::Shutdown(timeout) => Either::B(ok(())), + ServerMessage::Shutdown(_) => Either::B(ok(())), ServerMessage::ForceShutdown => { - // self.settings.head().traverse::(); + self.settings + .head() + .traverse(|proto: &mut HttpProtocol| proto.shutdown()); Either::B(ok(())) } } diff --git a/src/server/builder.rs b/src/server/builder.rs index 8a979752..ec6ce992 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -10,7 +10,7 @@ use super::acceptor::{ use super::error::AcceptorError; use super::handler::IntoHttpHandler; use super::service::HttpService; -use super::settings::{ServerSettings, WorkerSettings}; +use super::settings::{ServerSettings, ServiceConfig}; use super::KeepAlive; pub(crate) trait ServiceProvider { @@ -50,7 +50,7 @@ where let acceptor = self.acceptor.clone(); move || { let app = (factory)().into_handler(); - let settings = WorkerSettings::new( + let settings = ServiceConfig::new( app, keep_alive, client_timeout, diff --git a/src/server/channel.rs b/src/server/channel.rs index f5780620..513601ac 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -1,5 +1,5 @@ use std::net::{Shutdown, SocketAddr}; -use std::{io, ptr, time}; +use std::{io, mem, time}; use bytes::{Buf, BufMut, BytesMut}; use futures::{Async, Future, Poll}; @@ -7,16 +7,35 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use super::error::HttpDispatchError; -use super::settings::WorkerSettings; +use super::settings::ServiceConfig; use super::{h1, h2, HttpHandler, IoStream}; use http::StatusCode; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; -enum HttpProtocol { +pub(crate) enum HttpProtocol { H1(h1::Http1Dispatcher), H2(h2::Http2), - Unknown(WorkerSettings, Option, T, BytesMut), + Unknown(ServiceConfig, Option, T, BytesMut), + None, +} + +impl HttpProtocol { + pub(crate) fn shutdown(&mut self) { + match self { + HttpProtocol::H1(ref mut h1) => { + let io = h1.io(); + let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); + let _ = IoStream::shutdown(io, Shutdown::Both); + } + HttpProtocol::H2(ref mut h2) => h2.shutdown(), + HttpProtocol::Unknown(_, _, io, _) => { + let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); + let _ = IoStream::shutdown(io, Shutdown::Both); + } + HttpProtocol::None => (), + } + } } enum ProtocolKind { @@ -30,8 +49,8 @@ where T: IoStream, H: HttpHandler + 'static, { - proto: Option>, - node: Option>>, + node: Node>, + node_reg: bool, ka_timeout: Option, } @@ -41,12 +60,14 @@ where H: HttpHandler + 'static, { pub(crate) fn new( - settings: WorkerSettings, io: T, peer: Option, + settings: ServiceConfig, io: T, peer: Option, ) -> HttpChannel { + let ka_timeout = settings.client_timer(); + HttpChannel { - node: None, - ka_timeout: settings.client_timer(), - proto: Some(HttpProtocol::Unknown( + ka_timeout, + node_reg: false, + node: Node::new(HttpProtocol::Unknown( settings, peer, io, @@ -54,18 +75,6 @@ where )), } } - - pub(crate) fn shutdown(&mut self) { - match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => { - 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(), - _ => (), - } - } } impl Drop for HttpChannel @@ -74,9 +83,7 @@ where H: HttpHandler + 'static, { fn drop(&mut self) { - if let Some(mut node) = self.node.take() { - node.remove() - } + self.node.remove(); } } @@ -94,17 +101,16 @@ where match self.ka_timeout.as_mut().unwrap().poll() { Ok(Async::Ready(_)) => { trace!("Slow request timed out, close connection"); - if let Some(HttpProtocol::Unknown(settings, _, io, buf)) = - self.proto.take() - { - self.proto = - Some(HttpProtocol::H1(h1::Http1Dispatcher::for_error( + let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); + if let HttpProtocol::Unknown(settings, _, io, buf) = proto { + *self.node.get_mut() = + HttpProtocol::H1(h1::Http1Dispatcher::for_error( settings, io, StatusCode::REQUEST_TIMEOUT, self.ka_timeout.take(), buf, - ))); + )); return self.poll(); } return Ok(Async::Ready(())); @@ -114,28 +120,22 @@ where } } - if self.node.is_none() { - let el = self as *mut _; - self.node = Some(Node::new(el)); - let _ = match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => { - self.node.as_mut().map(|n| h1.settings().head().insert(n)) - } - Some(HttpProtocol::H2(ref mut h2)) => { - self.node.as_mut().map(|n| h2.settings().head().insert(n)) - } - Some(HttpProtocol::Unknown(ref mut settings, _, _, _)) => { - self.node.as_mut().map(|n| settings.head().insert(n)) - } - None => unreachable!(), + if !self.node_reg { + self.node_reg = true; + let settings = match self.node.get_mut() { + HttpProtocol::H1(ref mut h1) => h1.settings().clone(), + HttpProtocol::H2(ref mut h2) => h2.settings().clone(), + HttpProtocol::Unknown(ref mut settings, _, _, _) => settings.clone(), + HttpProtocol::None => unreachable!(), }; + settings.head().insert(&mut self.node); } let mut is_eof = false; - let kind = match self.proto { - Some(HttpProtocol::H1(ref mut h1)) => return h1.poll(), - Some(HttpProtocol::H2(ref mut h2)) => return h2.poll(), - Some(HttpProtocol::Unknown(_, _, ref mut io, ref mut buf)) => { + let kind = match self.node.get_mut() { + HttpProtocol::H1(ref mut h1) => return h1.poll(), + HttpProtocol::H2(ref mut h2) => return h2.poll(), + HttpProtocol::Unknown(_, _, ref mut io, ref mut buf) => { let mut err = None; let mut disconnect = false; match io.read_available(buf) { @@ -168,31 +168,32 @@ where return Ok(Async::NotReady); } } - None => unreachable!(), + HttpProtocol::None => unreachable!(), }; // upgrade to specific http protocol - if let Some(HttpProtocol::Unknown(settings, addr, io, buf)) = self.proto.take() { + let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); + if let HttpProtocol::Unknown(settings, addr, io, buf) = proto { match kind { ProtocolKind::Http1 => { - self.proto = Some(HttpProtocol::H1(h1::Http1Dispatcher::new( + *self.node.get_mut() = HttpProtocol::H1(h1::Http1Dispatcher::new( settings, io, addr, buf, is_eof, self.ka_timeout.take(), - ))); + )); return self.poll(); } ProtocolKind::Http2 => { - self.proto = Some(HttpProtocol::H2(h2::Http2::new( + *self.node.get_mut() = HttpProtocol::H2(h2::Http2::new( settings, io, addr, buf.freeze(), self.ka_timeout.take(), - ))); + )); return self.poll(); } } @@ -204,18 +205,22 @@ where pub(crate) struct Node { next: Option<*mut Node>, prev: Option<*mut Node>, - element: *mut T, + element: T, } impl Node { - fn new(el: *mut T) -> Self { + fn new(element: T) -> Self { Node { + element, next: None, prev: None, - element: el, } } + fn get_mut(&mut self) -> &mut T { + &mut self.element + } + fn insert(&mut self, next_el: &mut Node) { let next: *mut Node = next_el as *const _ as *mut _; @@ -235,7 +240,6 @@ impl Node { } fn remove(&mut self) { - self.element = ptr::null_mut(); let next = self.next.take(); let prev = self.prev.take(); @@ -257,30 +261,28 @@ impl Node<()> { Node { next: None, prev: None, - element: ptr::null_mut(), + element: (), } } - pub(crate) fn traverse)>(&self, f: F) + pub(crate) fn traverse)>(&self, f: F) where T: IoStream, H: HttpHandler + 'static, { - let mut next = self.next.as_ref(); - loop { - if let Some(n) = next { - unsafe { - let n: &Node<()> = &*(n.as_ref().unwrap() as *const _); - next = n.next.as_ref(); + if let Some(n) = self.next.as_ref() { + unsafe { + let mut next: &mut Node> = + &mut *(n.as_ref().unwrap() as *const _ as *mut _); + loop { + f(&mut next.element); - if !n.element.is_null() { - let ch: &mut HttpChannel = - &mut *(&mut *(n.element as *mut _) as *mut () as *mut _); - f(ch); + next = if let Some(n) = next.next.as_ref() { + &mut **n + } else { + return; } } - } else { - return; } } } diff --git a/src/server/h1.rs b/src/server/h1.rs index af7e6529..53c4e2cf 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -16,7 +16,7 @@ use super::h1decoder::{DecoderError, H1Decoder, Message}; use super::h1writer::H1Writer; use super::handler::{HttpHandler, HttpHandlerTask, HttpHandlerTaskFut}; use super::input::PayloadType; -use super::settings::WorkerSettings; +use super::settings::ServiceConfig; use super::{IoStream, Writer}; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -37,7 +37,7 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Http1Dispatcher { flags: Flags, - settings: WorkerSettings, + settings: ServiceConfig, addr: Option, stream: H1Writer, decoder: H1Decoder, @@ -87,7 +87,7 @@ where H: HttpHandler + 'static, { pub fn new( - settings: WorkerSettings, stream: T, addr: Option, buf: BytesMut, + settings: ServiceConfig, stream: T, addr: Option, buf: BytesMut, is_eof: bool, keepalive_timer: Option, ) -> Self { let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { @@ -122,7 +122,7 @@ where } pub(crate) fn for_error( - settings: WorkerSettings, stream: T, status: StatusCode, + settings: ServiceConfig, stream: T, status: StatusCode, mut keepalive_timer: Option, buf: BytesMut, ) -> Self { if let Some(deadline) = settings.client_timer_expire() { @@ -147,7 +147,7 @@ where } #[inline] - pub fn settings(&self) -> &WorkerSettings { + pub fn settings(&self) -> &ServiceConfig { &self.settings } @@ -259,7 +259,7 @@ where Err(err) => { debug!("Error sending data: {}", err); self.client_disconnected(false); - return Err(err.into()); + Err(err.into()) } Ok(Async::Ready(_)) => { // if payload is not consumed we can not use connection @@ -347,10 +347,8 @@ where if self.can_read() && self.tasks.len() < MAX_PIPELINED_MESSAGES { match self.stream.get_mut().read_available(&mut self.buf) { Ok(Async::Ready((read_some, disconnected))) => { - if read_some { - if self.parse()? { - updated = true; - } + if read_some && self.parse()? { + updated = true; } if disconnected { self.client_disconnected(true); @@ -397,11 +395,9 @@ where // if read-backpressure is enabled and we consumed some data. // we may read more dataand retry - if !retry && self.can_read() { - if self.poll_io()? { - retry = self.can_read(); - continue; - } + if !retry && self.can_read() && self.poll_io()? { + retry = self.can_read(); + continue; } break; } @@ -597,11 +593,11 @@ mod tests { use httpmessage::HttpMessage; use server::h1decoder::Message; use server::handler::IntoHttpHandler; - use server::settings::{ServerSettings, WorkerSettings}; + use server::settings::{ServerSettings, ServiceConfig}; use server::{KeepAlive, Request}; - fn wrk_settings() -> WorkerSettings { - WorkerSettings::::new( + fn wrk_settings() -> ServiceConfig { + ServiceConfig::::new( App::new().into_handler(), KeepAlive::Os, 5000, diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index a7531bbb..434dc42d 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -5,7 +5,7 @@ use futures::{Async, Poll}; use httparse; use super::message::{MessageFlags, Request}; -use super::settings::WorkerSettings; +use super::settings::ServiceConfig; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HttpTryFrom, Method, Uri, Version}; @@ -43,7 +43,7 @@ impl H1Decoder { } pub fn decode( - &mut self, src: &mut BytesMut, settings: &WorkerSettings, + &mut self, src: &mut BytesMut, settings: &ServiceConfig, ) -> Result, DecoderError> { // read payload if self.decoder.is_some() { @@ -80,7 +80,7 @@ impl H1Decoder { } fn parse_message( - &self, buf: &mut BytesMut, settings: &WorkerSettings, + &self, buf: &mut BytesMut, settings: &ServiceConfig, ) -> Poll<(Request, Option), ParseError> { // Parse http message let mut has_upgrade = false; diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index 5c32de3a..c27a4c44 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -8,7 +8,7 @@ use tokio_io::AsyncWrite; use super::helpers; use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::WorkerSettings; +use super::settings::ServiceConfig; use super::Request; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; @@ -37,11 +37,11 @@ pub(crate) struct H1Writer { headers_size: u32, buffer: Output, buffer_capacity: usize, - settings: WorkerSettings, + settings: ServiceConfig, } impl H1Writer { - pub fn new(stream: T, settings: WorkerSettings) -> H1Writer { + pub fn new(stream: T, settings: ServiceConfig) -> H1Writer { H1Writer { flags: Flags::KEEPALIVE, written: 0, diff --git a/src/server/h2.rs b/src/server/h2.rs index 589e77c2..312b51df 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -22,7 +22,7 @@ use uri::Url; use super::error::{HttpDispatchError, ServerError}; use super::h2writer::H2Writer; use super::input::PayloadType; -use super::settings::WorkerSettings; +use super::settings::ServiceConfig; use super::{HttpHandler, HttpHandlerTask, IoStream, Writer}; bitflags! { @@ -38,7 +38,7 @@ where H: HttpHandler + 'static, { flags: Flags, - settings: WorkerSettings, + settings: ServiceConfig, addr: Option, state: State>, tasks: VecDeque>, @@ -58,7 +58,7 @@ where H: HttpHandler + 'static, { pub fn new( - settings: WorkerSettings, io: T, addr: Option, buf: Bytes, + settings: ServiceConfig, io: T, addr: Option, buf: Bytes, keepalive_timer: Option, ) -> Self { let extensions = io.extensions(); @@ -82,7 +82,7 @@ where self.keepalive_timer.take(); } - pub fn settings(&self) -> &WorkerSettings { + pub fn settings(&self) -> &ServiceConfig { &self.settings } @@ -338,7 +338,7 @@ struct Entry { impl Entry { fn new( parts: Parts, recv: RecvStream, resp: SendResponse, - addr: Option, settings: WorkerSettings, + addr: Option, settings: ServiceConfig, extensions: Option>, ) -> Entry where diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 0893b5b6..51d4dce6 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -14,7 +14,7 @@ use modhttp::Response; use super::helpers; use super::message::Request; use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::WorkerSettings; +use super::settings::ServiceConfig; use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; use body::{Binary, Body}; use header::ContentEncoding; @@ -42,13 +42,11 @@ pub(crate) struct H2Writer { written: u64, buffer: Output, buffer_capacity: usize, - settings: WorkerSettings, + settings: ServiceConfig, } impl H2Writer { - pub fn new( - respond: SendResponse, settings: WorkerSettings, - ) -> H2Writer { + pub fn new(respond: SendResponse, settings: ServiceConfig) -> H2Writer { H2Writer { stream: None, flags: Flags::empty(), diff --git a/src/server/incoming.rs b/src/server/incoming.rs index c4e984b9..f2bc1d8f 100644 --- a/src/server/incoming.rs +++ b/src/server/incoming.rs @@ -8,7 +8,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::channel::{HttpChannel, WrapperStream}; use super::handler::{HttpHandler, IntoHttpHandler}; use super::http::HttpServer; -use super::settings::{ServerSettings, WorkerSettings}; +use super::settings::{ServerSettings, ServiceConfig}; impl Message for WrapperStream { type Result = (); @@ -32,7 +32,7 @@ where // set server settings let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); let apps = (self.factory)().into_handler(); - let settings = WorkerSettings::new( + let settings = ServiceConfig::new( apps, self.keep_alive, self.client_timeout, @@ -49,7 +49,7 @@ where } struct HttpIncoming { - settings: WorkerSettings, + settings: ServiceConfig, } impl Actor for HttpIncoming { diff --git a/src/server/mod.rs b/src/server/mod.rs index 456b4618..d6e9f26b 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -143,7 +143,7 @@ pub use self::message::Request; pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; -pub use self::settings::{ServerSettings, WorkerSettings, WorkerSettingsBuilder}; +pub use self::settings::{ServerSettings, ServiceConfig, ServiceConfigBuilder}; #[doc(hidden)] pub use self::service::{HttpService, StreamConfiguration}; diff --git a/src/server/service.rs b/src/server/service.rs index 231ac599..ec71a1f1 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -8,7 +8,7 @@ use futures::{Async, Poll}; use super::channel::HttpChannel; use super::error::HttpDispatchError; use super::handler::HttpHandler; -use super::settings::WorkerSettings; +use super::settings::ServiceConfig; use super::IoStream; /// `NewService` implementation for HTTP1/HTTP2 transports @@ -17,7 +17,7 @@ where H: HttpHandler, Io: IoStream, { - settings: WorkerSettings, + settings: ServiceConfig, _t: PhantomData, } @@ -27,7 +27,7 @@ where Io: IoStream, { /// Create new `HttpService` instance. - pub fn new(settings: WorkerSettings) -> Self { + pub fn new(settings: ServiceConfig) -> Self { HttpService { settings, _t: PhantomData, @@ -57,7 +57,7 @@ where H: HttpHandler, Io: IoStream, { - settings: WorkerSettings, + settings: ServiceConfig, _t: PhantomData, } @@ -66,7 +66,7 @@ where H: HttpHandler, Io: IoStream, { - fn new(settings: WorkerSettings) -> HttpServiceHandler { + fn new(settings: ServiceConfig) -> HttpServiceHandler { HttpServiceHandler { settings, _t: PhantomData, @@ -103,6 +103,12 @@ pub struct StreamConfiguration { _t: PhantomData<(T, E)>, } +impl Default for StreamConfiguration { + fn default() -> Self { + Self::new() + } +} + impl StreamConfiguration { /// Create new `StreamConfigurationService` instance. pub fn new() -> Self { @@ -136,8 +142,8 @@ impl NewService for StreamConfiguration { fn new_service(&self) -> Self::Future { ok(StreamConfigurationService { - no_delay: self.no_delay.clone(), - tcp_ka: self.tcp_ka.clone(), + no_delay: self.no_delay, + tcp_ka: self.tcp_ka, _t: PhantomData, }) } diff --git a/src/server/settings.rs b/src/server/settings.rs index 2f306073..3798fae5 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -127,7 +127,8 @@ impl ServerSettings { // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; -pub struct WorkerSettings(Rc>); +/// Http service configuration +pub struct ServiceConfig(Rc>); struct Inner { handler: H, @@ -141,18 +142,18 @@ struct Inner { date: UnsafeCell<(bool, Date)>, } -impl Clone for WorkerSettings { +impl Clone for ServiceConfig { fn clone(&self) -> Self { - WorkerSettings(self.0.clone()) + ServiceConfig(self.0.clone()) } } -impl WorkerSettings { - /// Create instance of `WorkerSettings` +impl ServiceConfig { + /// Create instance of `ServiceConfig` pub(crate) fn new( handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, settings: ServerSettings, - ) -> WorkerSettings { + ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), KeepAlive::Os | KeepAlive::Tcp(_) => (0, true), @@ -164,7 +165,7 @@ impl WorkerSettings { None }; - WorkerSettings(Rc::new(Inner { + ServiceConfig(Rc::new(Inner { handler, keep_alive, ka_enabled, @@ -178,8 +179,8 @@ impl WorkerSettings { } /// Create worker settings builder. - pub fn build(handler: H) -> WorkerSettingsBuilder { - WorkerSettingsBuilder::new(handler) + pub fn build(handler: H) -> ServiceConfigBuilder { + ServiceConfigBuilder::new(handler) } pub(crate) fn head(&self) -> RefMut> { @@ -220,7 +221,7 @@ impl WorkerSettings { } } -impl WorkerSettings { +impl ServiceConfig { #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { @@ -319,11 +320,11 @@ impl WorkerSettings { } } -/// An worker settings builder +/// A service config builder /// -/// This type can be used to construct an instance of `WorkerSettings` through a +/// This type can be used to construct an instance of `ServiceConfig` through a /// builder-like pattern. -pub struct WorkerSettingsBuilder { +pub struct ServiceConfigBuilder { handler: H, keep_alive: KeepAlive, client_timeout: u64, @@ -333,10 +334,10 @@ pub struct WorkerSettingsBuilder { secure: bool, } -impl WorkerSettingsBuilder { - /// Create instance of `WorkerSettingsBuilder` - pub fn new(handler: H) -> WorkerSettingsBuilder { - WorkerSettingsBuilder { +impl ServiceConfigBuilder { + /// Create instance of `ServiceConfigBuilder` + pub fn new(handler: H) -> ServiceConfigBuilder { + ServiceConfigBuilder { handler, keep_alive: KeepAlive::Timeout(5), client_timeout: 5000, @@ -419,12 +420,12 @@ impl WorkerSettingsBuilder { self } - /// Finish worker settings configuration and create `WorkerSettings` object. - pub fn finish(self) -> WorkerSettings { + /// Finish service configuration and create `ServiceConfig` object. + pub fn finish(self) -> ServiceConfig { let settings = ServerSettings::new(self.addr, &self.host, self.secure); let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; - WorkerSettings::new( + ServiceConfig::new( self.handler, self.keep_alive, self.client_timeout, @@ -507,7 +508,7 @@ mod tests { let mut rt = current_thread::Runtime::new().unwrap(); let _ = rt.block_on(future::lazy(|| { - let settings = WorkerSettings::<()>::new( + let settings = ServiceConfig::<()>::new( (), KeepAlive::Os, 0, diff --git a/tests/test_server.rs b/tests/test_server.rs index 240a5ddc..8d9a400d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1228,7 +1228,7 @@ fn test_custom_pipeline() { use actix::System; use actix_net::service::NewServiceExt; use actix_web::server::{ - HttpService, KeepAlive, StreamConfiguration, WorkerSettings, + HttpService, KeepAlive, ServiceConfig, StreamConfiguration, }; let addr = test::TestServer::unused_addr(); @@ -1239,7 +1239,7 @@ fn test_custom_pipeline() { let app = App::new() .route("/", http::Method::GET, |_: HttpRequest| "OK") .finish(); - let settings = WorkerSettings::build(app) + let settings = ServiceConfig::build(app) .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_shutdown(1000) From 2710f70e394700c58dbf1951d19bd0b249fbf279 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 17:30:29 -0700 Subject: [PATCH 0706/1635] add H1 transport --- src/server/channel.rs | 85 ++++++++++++++++++++++++++++------ src/server/h1.rs | 13 ++++-- src/server/h2.rs | 4 +- src/server/incoming.rs | 4 +- src/server/mod.rs | 20 ++++++-- src/server/service.rs | 87 ++++++++++++++++++++++++++++++++++- src/server/ssl/nativetls.rs | 7 ++- src/server/ssl/openssl.rs | 7 ++- src/server/ssl/rustls.rs | 7 ++- tests/test_custom_pipeline.rs | 81 ++++++++++++++++++++++++++++++++ tests/test_server.rs | 43 ----------------- 11 files changed, 284 insertions(+), 74 deletions(-) create mode 100644 tests/test_custom_pipeline.rs diff --git a/src/server/channel.rs b/src/server/channel.rs index 513601ac..cbbe1a95 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -1,4 +1,4 @@ -use std::net::{Shutdown, SocketAddr}; +use std::net::Shutdown; use std::{io, mem, time}; use bytes::{Buf, BufMut, BytesMut}; @@ -16,7 +16,7 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; pub(crate) enum HttpProtocol { H1(h1::Http1Dispatcher), H2(h2::Http2), - Unknown(ServiceConfig, Option, T, BytesMut), + Unknown(ServiceConfig, T, BytesMut), None, } @@ -29,7 +29,7 @@ impl HttpProtocol { let _ = IoStream::shutdown(io, Shutdown::Both); } HttpProtocol::H2(ref mut h2) => h2.shutdown(), - HttpProtocol::Unknown(_, _, io, _) => { + HttpProtocol::Unknown(_, io, _) => { let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); let _ = IoStream::shutdown(io, Shutdown::Both); } @@ -59,9 +59,7 @@ where T: IoStream, H: HttpHandler + 'static, { - pub(crate) fn new( - settings: ServiceConfig, io: T, peer: Option, - ) -> HttpChannel { + pub(crate) fn new(settings: ServiceConfig, io: T) -> HttpChannel { let ka_timeout = settings.client_timer(); HttpChannel { @@ -69,7 +67,6 @@ where node_reg: false, node: Node::new(HttpProtocol::Unknown( settings, - peer, io, BytesMut::with_capacity(8192), )), @@ -102,7 +99,7 @@ where Ok(Async::Ready(_)) => { trace!("Slow request timed out, close connection"); let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); - if let HttpProtocol::Unknown(settings, _, io, buf) = proto { + if let HttpProtocol::Unknown(settings, io, buf) = proto { *self.node.get_mut() = HttpProtocol::H1(h1::Http1Dispatcher::for_error( settings, @@ -125,7 +122,7 @@ where let settings = match self.node.get_mut() { HttpProtocol::H1(ref mut h1) => h1.settings().clone(), HttpProtocol::H2(ref mut h2) => h2.settings().clone(), - HttpProtocol::Unknown(ref mut settings, _, _, _) => settings.clone(), + HttpProtocol::Unknown(ref mut settings, _, _) => settings.clone(), HttpProtocol::None => unreachable!(), }; settings.head().insert(&mut self.node); @@ -135,7 +132,7 @@ where let kind = match self.node.get_mut() { HttpProtocol::H1(ref mut h1) => return h1.poll(), HttpProtocol::H2(ref mut h2) => return h2.poll(), - HttpProtocol::Unknown(_, _, ref mut io, ref mut buf) => { + HttpProtocol::Unknown(_, ref mut io, ref mut buf) => { let mut err = None; let mut disconnect = false; match io.read_available(buf) { @@ -173,13 +170,12 @@ where // upgrade to specific http protocol let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); - if let HttpProtocol::Unknown(settings, addr, io, buf) = proto { + if let HttpProtocol::Unknown(settings, io, buf) = proto { match kind { ProtocolKind::Http1 => { *self.node.get_mut() = HttpProtocol::H1(h1::Http1Dispatcher::new( settings, io, - addr, buf, is_eof, self.ka_timeout.take(), @@ -190,7 +186,6 @@ where *self.node.get_mut() = HttpProtocol::H2(h2::Http2::new( settings, io, - addr, buf.freeze(), self.ka_timeout.take(), )); @@ -202,6 +197,70 @@ where } } +#[doc(hidden)] +pub struct H1Channel +where + T: IoStream, + H: HttpHandler + 'static, +{ + node: Node>, + node_reg: bool, +} + +impl H1Channel +where + T: IoStream, + H: HttpHandler + 'static, +{ + pub(crate) fn new(settings: ServiceConfig, io: T) -> H1Channel { + H1Channel { + node_reg: false, + node: Node::new(HttpProtocol::H1(h1::Http1Dispatcher::new( + settings, + io, + BytesMut::with_capacity(8192), + false, + None, + ))), + } + } +} + +impl Drop for H1Channel +where + T: IoStream, + H: HttpHandler + 'static, +{ + fn drop(&mut self) { + self.node.remove(); + } +} + +impl Future for H1Channel +where + T: IoStream, + H: HttpHandler + 'static, +{ + type Item = (); + type Error = HttpDispatchError; + + fn poll(&mut self) -> Poll { + if !self.node_reg { + self.node_reg = true; + let settings = match self.node.get_mut() { + HttpProtocol::H1(ref mut h1) => h1.settings().clone(), + _ => unreachable!(), + }; + settings.head().insert(&mut self.node); + } + + match self.node.get_mut() { + HttpProtocol::H1(ref mut h1) => h1.poll(), + _ => unreachable!(), + } + } +} + pub(crate) struct Node { next: Option<*mut Node>, prev: Option<*mut Node>, diff --git a/src/server/h1.rs b/src/server/h1.rs index 53c4e2cf..7a59b649 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -87,9 +87,10 @@ where H: HttpHandler + 'static, { pub fn new( - settings: ServiceConfig, stream: T, addr: Option, buf: BytesMut, - is_eof: bool, keepalive_timer: Option, + settings: ServiceConfig, stream: T, buf: BytesMut, is_eof: bool, + keepalive_timer: Option, ) -> Self { + let addr = stream.peer_addr(); let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { (delay.deadline(), Some(delay)) } else if let Some(delay) = settings.keep_alive_timer() { @@ -107,12 +108,12 @@ where }; Http1Dispatcher { - flags, stream: H1Writer::new(stream, settings.clone()), decoder: H1Decoder::new(), payload: None, tasks: VecDeque::new(), error: None, + flags, addr, buf, settings, @@ -337,9 +338,11 @@ where /// read data from the stream pub(self) fn poll_io(&mut self) -> Result { if !self.flags.contains(Flags::POLLED) { - let updated = self.parse()?; self.flags.insert(Flags::POLLED); - return Ok(updated); + if !self.buf.is_empty() { + let updated = self.parse()?; + return Ok(updated); + } } // read io from socket diff --git a/src/server/h2.rs b/src/server/h2.rs index 312b51df..2fe2fa07 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -58,9 +58,9 @@ where H: HttpHandler + 'static, { pub fn new( - settings: ServiceConfig, io: T, addr: Option, buf: Bytes, - keepalive_timer: Option, + settings: ServiceConfig, io: T, buf: Bytes, keepalive_timer: Option, ) -> Self { + let addr = io.peer_addr(); let extensions = io.extensions(); Http2 { flags: Flags::empty(), diff --git a/src/server/incoming.rs b/src/server/incoming.rs index f2bc1d8f..b13bba2a 100644 --- a/src/server/incoming.rs +++ b/src/server/incoming.rs @@ -64,8 +64,6 @@ where type Result = (); fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { - Arbiter::spawn( - HttpChannel::new(self.settings.clone(), msg, None).map_err(|_| ()), - ); + Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg).map_err(|_| ())); } } diff --git a/src/server/mod.rs b/src/server/mod.rs index d6e9f26b..c942ff91 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -106,7 +106,7 @@ //! let _ = sys.run(); //!} //! ``` -use std::net::Shutdown; +use std::net::{Shutdown, SocketAddr}; use std::rc::Rc; use std::{io, time}; @@ -143,10 +143,13 @@ pub use self::message::Request; pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; -pub use self::settings::{ServerSettings, ServiceConfig, ServiceConfigBuilder}; +pub use self::settings::ServerSettings; #[doc(hidden)] -pub use self::service::{HttpService, StreamConfiguration}; +pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; + +#[doc(hidden)] +pub use self::service::{H1Service, HttpService, StreamConfiguration}; #[doc(hidden)] pub use self::helpers::write_content_length; @@ -266,6 +269,12 @@ pub trait Writer { pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn shutdown(&mut self, how: Shutdown) -> io::Result<()>; + /// Returns the socket address of the remote peer of this TCP connection. + fn peer_addr(&self) -> Option { + None + } + + /// Sets the value of the TCP_NODELAY option on this socket. fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>; fn set_linger(&mut self, dur: Option) -> io::Result<()>; @@ -341,6 +350,11 @@ impl IoStream for TcpStream { TcpStream::shutdown(self, how) } + #[inline] + fn peer_addr(&self) -> Option { + TcpStream::peer_addr(self).ok() + } + #[inline] fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { TcpStream::set_nodelay(self, nodelay) diff --git a/src/server/service.rs b/src/server/service.rs index ec71a1f1..e3402e30 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -5,7 +5,7 @@ use actix_net::service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, Poll}; -use super::channel::HttpChannel; +use super::channel::{H1Channel, HttpChannel}; use super::error::HttpDispatchError; use super::handler::HttpHandler; use super::settings::ServiceConfig; @@ -89,7 +89,90 @@ where } fn call(&mut self, req: Self::Request) -> Self::Future { - HttpChannel::new(self.settings.clone(), req, None) + HttpChannel::new(self.settings.clone(), req) + } +} + +/// `NewService` implementation for HTTP1 transport +pub struct H1Service +where + H: HttpHandler, + Io: IoStream, +{ + settings: ServiceConfig, + _t: PhantomData, +} + +impl H1Service +where + H: HttpHandler, + Io: IoStream, +{ + /// Create new `HttpService` instance. + pub fn new(settings: ServiceConfig) -> Self { + H1Service { + settings, + _t: PhantomData, + } + } +} + +impl NewService for H1Service +where + H: HttpHandler, + Io: IoStream, +{ + type Request = Io; + type Response = (); + type Error = HttpDispatchError; + type InitError = (); + type Service = H1ServiceHandler; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(H1ServiceHandler::new(self.settings.clone())) + } +} + +/// `Service` implementation for HTTP1 transport +pub struct H1ServiceHandler +where + H: HttpHandler, + Io: IoStream, +{ + settings: ServiceConfig, + _t: PhantomData, +} + +impl H1ServiceHandler +where + H: HttpHandler, + Io: IoStream, +{ + fn new(settings: ServiceConfig) -> H1ServiceHandler { + H1ServiceHandler { + settings, + _t: PhantomData, + } + } +} + +impl Service for H1ServiceHandler +where + H: HttpHandler, + Io: IoStream, +{ + type Request = Io; + type Response = (); + type Error = HttpDispatchError; + type Future = H1Channel; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + H1Channel::new(self.settings.clone(), req) } } diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs index e56b4521..a9797ffb 100644 --- a/src/server/ssl/nativetls.rs +++ b/src/server/ssl/nativetls.rs @@ -1,4 +1,4 @@ -use std::net::Shutdown; +use std::net::{Shutdown, SocketAddr}; use std::{io, time}; use actix_net::ssl::TlsStream; @@ -12,6 +12,11 @@ impl IoStream for TlsStream { Ok(()) } + #[inline] + fn peer_addr(&self) -> Option { + self.get_ref().get_ref().peer_addr() + } + #[inline] fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { self.get_mut().get_mut().set_nodelay(nodelay) diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs index f9e0e177..9d370f8b 100644 --- a/src/server/ssl/openssl.rs +++ b/src/server/ssl/openssl.rs @@ -1,4 +1,4 @@ -use std::net::Shutdown; +use std::net::{Shutdown, SocketAddr}; use std::{io, time}; use actix_net::ssl; @@ -65,6 +65,11 @@ impl IoStream for SslStream { Ok(()) } + #[inline] + fn peer_addr(&self) -> Option { + self.get_ref().get_ref().peer_addr() + } + #[inline] fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { self.get_mut().get_mut().set_nodelay(nodelay) diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs index df78d1dc..a53a53a9 100644 --- a/src/server/ssl/rustls.rs +++ b/src/server/ssl/rustls.rs @@ -1,4 +1,4 @@ -use std::net::Shutdown; +use std::net::{Shutdown, SocketAddr}; use std::{io, time}; use actix_net::ssl; //::RustlsAcceptor; @@ -65,6 +65,11 @@ impl IoStream for TlsStream { Ok(()) } + #[inline] + fn peer_addr(&self) -> Option { + self.get_ref().0.peer_addr() + } + #[inline] fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { self.get_mut().0.set_nodelay(nodelay) diff --git a/tests/test_custom_pipeline.rs b/tests/test_custom_pipeline.rs new file mode 100644 index 00000000..cf1eeb5b --- /dev/null +++ b/tests/test_custom_pipeline.rs @@ -0,0 +1,81 @@ +extern crate actix; +extern crate actix_net; +extern crate actix_web; + +use std::{thread, time}; + +use actix::System; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration}; +use actix_web::{client, test, App, HttpRequest}; + +#[test] +fn test_custom_pipeline() { + let addr = test::TestServer::unused_addr(); + + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let app = App::new() + .route("/", http::Method::GET, |_: HttpRequest| "OK") + .finish(); + let settings = ServiceConfig::build(app) + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_shutdown(1000) + .server_hostname("localhost") + .server_address(addr) + .finish(); + + StreamConfiguration::new() + .nodelay(true) + .tcp_keepalive(Some(time::Duration::from_secs(10))) + .and_then(HttpService::new(settings)) + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + } +} + +#[test] +fn test_h1() { + use actix_web::server::H1Service; + + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let app = App::new() + .route("/", http::Method::GET, |_: HttpRequest| "OK") + .finish(); + let settings = ServiceConfig::build(app) + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_shutdown(1000) + .server_hostname("localhost") + .server_address(addr) + .finish(); + + H1Service::new(settings) + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + } +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 8d9a400d..477d3e64 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -26,7 +26,6 @@ use std::io::{Read, Write}; use std::sync::Arc; use std::{thread, time}; -use actix_net::server::Server; #[cfg(feature = "brotli")] use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::{Bytes, BytesMut}; @@ -1223,48 +1222,6 @@ fn test_server_cookies() { } } -#[test] -fn test_custom_pipeline() { - use actix::System; - use actix_net::service::NewServiceExt; - use actix_web::server::{ - HttpService, KeepAlive, ServiceConfig, StreamConfiguration, - }; - - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - StreamConfiguration::new() - .nodelay(true) - .tcp_keepalive(Some(time::Duration::from_secs(10))) - .and_then(HttpService::new(settings)) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} - #[test] fn test_slow_request() { use actix::System; From 1f68ce85410a57d323297502559a46e912eaf4d5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 19:05:58 -0700 Subject: [PATCH 0707/1635] fix tests --- src/server/h1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 7a59b649..4fb730f7 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -733,7 +733,7 @@ mod tests { let settings = wrk_settings(); let mut h1 = - Http1Dispatcher::new(settings.clone(), buf, None, readbuf, false, None); + Http1Dispatcher::new(settings.clone(), buf, readbuf, false, None); assert!(h1.poll_io().is_ok()); assert!(h1.poll_io().is_ok()); assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); From bbcd618304e7bee84413fbb74df70910e21b41ca Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 19:12:08 -0700 Subject: [PATCH 0708/1635] export AcceptorTimeout --- src/server/acceptor.rs | 12 ++++++++---- src/server/mod.rs | 3 +++ src/server/settings.rs | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index 79d133d2..f66e51db 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -172,10 +172,11 @@ where } } +#[doc(hidden)] /// Acceptor timeout middleware /// /// Applies timeout to request prcoessing. -pub(crate) struct AcceptorTimeout { +pub struct AcceptorTimeout { inner: T, timeout: Duration, } @@ -206,7 +207,7 @@ impl NewService for AcceptorTimeout { } #[doc(hidden)] -pub(crate) struct AcceptorTimeoutFut { +pub struct AcceptorTimeoutFut { fut: T::Future, timeout: Duration, } @@ -224,10 +225,11 @@ impl Future for AcceptorTimeoutFut { } } +#[doc(hidden)] /// Acceptor timeout service /// /// Applies timeout to request prcoessing. -pub(crate) struct AcceptorTimeoutService { +pub struct AcceptorTimeoutService { inner: T, timeout: Duration, } @@ -250,10 +252,12 @@ impl Service for AcceptorTimeoutService { } } -pub(crate) struct AcceptorTimeoutResponse { +#[doc(hidden)] +pub struct AcceptorTimeoutResponse { fut: T::Future, sleep: Delay, } + impl Future for AcceptorTimeoutResponse { type Item = T::Response; type Error = AcceptorError; diff --git a/src/server/mod.rs b/src/server/mod.rs index c942ff91..3277dba5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -145,6 +145,9 @@ pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; pub use self::settings::ServerSettings; +#[doc(hidden)] +pub use self::acceptor::AcceptorTimeout; + #[doc(hidden)] pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; diff --git a/src/server/settings.rs b/src/server/settings.rs index 3798fae5..9b27ed5e 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -282,7 +282,7 @@ impl ServiceConfig { // periodic date update let s = self.clone(); - spawn(sleep(Duration::from_secs(1)).then(move |_| { + spawn(sleep(Duration::from_millis(500)).then(move |_| { s.update_date(); future::ok(()) })); @@ -310,7 +310,7 @@ impl ServiceConfig { // periodic date update let s = self.clone(); - spawn(sleep(Duration::from_secs(1)).then(move |_| { + spawn(sleep(Duration::from_millis(500)).then(move |_| { s.update_date(); future::ok(()) })); From 401ea574c03161ea0c9d1d935915381272c4d9aa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 19:31:30 -0700 Subject: [PATCH 0709/1635] make AcceptorTimeout::new public --- src/server/acceptor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index f66e51db..2e1b1f28 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -182,7 +182,8 @@ pub struct AcceptorTimeout { } impl AcceptorTimeout { - pub(crate) fn new(timeout: u64, inner: T) -> Self { + /// Create new `AcceptorTimeout` instance. timeout is in milliseconds. + pub fn new(timeout: u64, inner: T) -> Self { Self { inner, timeout: Duration::from_millis(timeout), From b0677aa0290adc94dbcf5da7ee4ae2ac35c08548 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 19:42:24 -0700 Subject: [PATCH 0710/1635] fix stable compatibility --- tests/test_custom_pipeline.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_custom_pipeline.rs b/tests/test_custom_pipeline.rs index cf1eeb5b..6b5df00e 100644 --- a/tests/test_custom_pipeline.rs +++ b/tests/test_custom_pipeline.rs @@ -8,7 +8,7 @@ use actix::System; use actix_net::server::Server; use actix_net::service::NewServiceExt; use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration}; -use actix_web::{client, test, App, HttpRequest}; +use actix_web::{client, http, test, App, HttpRequest}; #[test] fn test_custom_pipeline() { From 49eea3bf76d82aa1b4f31a6efb6dcf803f6623de Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 20:22:51 -0700 Subject: [PATCH 0711/1635] travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 59f6a854..62867e03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml + RUST_BACKTRACE=1 cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 1e1a4f846e0f3a109b168bfb660ae781697688eb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Oct 2018 22:23:51 -0700 Subject: [PATCH 0712/1635] use actix-net cell features --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 46719d70..12f98ac3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ name = "actix_web" path = "src/lib.rs" [features] -default = ["session", "brotli", "flate2-c"] +default = ["session", "brotli", "flate2-c", "cell"] # tls tls = ["native-tls", "tokio-tls", "actix-net/tls"] @@ -58,6 +58,8 @@ flate2-c = ["flate2/miniz-sys"] # rust backend for flate2 crate flate2-rust = ["flate2/rust_backend"] +cell = ["actix-net/cell"] + [dependencies] actix = "0.7.0" actix-net = { git="https://github.com/actix/actix-net.git" } From 13b0ee735519795e29efe62ea3e021610b9232b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 16:22:00 -0700 Subject: [PATCH 0713/1635] stopping point --- Cargo.toml | 1 + src/lib.rs | 1 + src/server/channel.rs | 5 +- src/server/error.rs | 47 +++-- src/server/h1.rs | 295 +++++++++++++--------------- src/server/h1codec.rs | 251 ++++++++++++++++++++++++ src/server/h1decoder.rs | 54 ++--- src/server/h1disp.rs | 425 ++++++++++++++++++++++++++++++++++++++++ src/server/h2.rs | 2 +- src/server/message.rs | 9 +- src/server/mod.rs | 8 +- src/server/output.rs | 8 +- src/server/service.rs | 9 +- src/server/settings.rs | 5 + tests/test_h1v2.rs | 58 ++++++ 15 files changed, 958 insertions(+), 220 deletions(-) create mode 100644 src/server/h1codec.rs create mode 100644 src/server/h1disp.rs create mode 100644 tests/test_h1v2.rs diff --git a/Cargo.toml b/Cargo.toml index 12f98ac3..d70a65cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ futures = "0.1" futures-cpupool = "0.1" slab = "0.4" tokio = "0.1" +tokio-codec = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 1ed40809..f494c05d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,7 @@ extern crate parking_lot; extern crate rand; extern crate slab; extern crate tokio; +extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_reactor; diff --git a/src/server/channel.rs b/src/server/channel.rs index cbbe1a95..b1fef964 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -9,6 +9,7 @@ use tokio_timer::Delay; use super::error::HttpDispatchError; use super::settings::ServiceConfig; use super::{h1, h2, HttpHandler, IoStream}; +use error::Error; use http::StatusCode; const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; @@ -90,7 +91,7 @@ where H: HttpHandler + 'static, { type Item = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; fn poll(&mut self) -> Poll { // keep-alive timer @@ -242,7 +243,7 @@ where H: HttpHandler + 'static, { type Item = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; fn poll(&mut self) -> Poll { if !self.node_reg { diff --git a/src/server/error.rs b/src/server/error.rs index 70f10099..3ae9a107 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -1,11 +1,12 @@ +use std::fmt::{Debug, Display}; use std::io; use futures::{Async, Poll}; use http2; use super::{helpers, HttpHandlerTask, Writer}; +use error::{Error, ParseError}; use http::{StatusCode, Version}; -use Error; /// Errors produced by `AcceptorError` service. #[derive(Debug)] @@ -20,60 +21,70 @@ pub enum AcceptorError { Timeout, } -#[derive(Fail, Debug)] +#[derive(Debug)] /// A set of errors that can occur during dispatching http requests -pub enum HttpDispatchError { +pub enum HttpDispatchError { /// Application error - #[fail(display = "Application specific error: {}", _0)] - App(Error), + // #[fail(display = "Application specific error: {}", _0)] + App(E), /// An `io::Error` that occurred while trying to read or write to a network /// stream. - #[fail(display = "IO error: {}", _0)] + // #[fail(display = "IO error: {}", _0)] Io(io::Error), + /// Http request parse error. + // #[fail(display = "Parse error: {}", _0)] + Parse(ParseError), + /// The first request did not complete within the specified timeout. - #[fail(display = "The first request did not complete within the specified timeout")] + // #[fail(display = "The first request did not complete within the specified timeout")] SlowRequestTimeout, /// Shutdown timeout - #[fail(display = "Connection shutdown timeout")] + // #[fail(display = "Connection shutdown timeout")] ShutdownTimeout, /// HTTP2 error - #[fail(display = "HTTP2 error: {}", _0)] + // #[fail(display = "HTTP2 error: {}", _0)] Http2(http2::Error), /// Payload is not consumed - #[fail(display = "Task is completed but request's payload is not consumed")] + // #[fail(display = "Task is completed but request's payload is not consumed")] PayloadIsNotConsumed, /// Malformed request - #[fail(display = "Malformed request")] + // #[fail(display = "Malformed request")] MalformedRequest, /// Internal error - #[fail(display = "Internal error")] + // #[fail(display = "Internal error")] InternalError, /// Unknown error - #[fail(display = "Unknown error")] + // #[fail(display = "Unknown error")] Unknown, } -impl From for HttpDispatchError { - fn from(err: Error) -> Self { - HttpDispatchError::App(err) +// impl From for HttpDispatchError { +// fn from(err: E) -> Self { +// HttpDispatchError::App(err) +// } +// } + +impl From for HttpDispatchError { + fn from(err: ParseError) -> Self { + HttpDispatchError::Parse(err) } } -impl From for HttpDispatchError { +impl From for HttpDispatchError { fn from(err: io::Error) -> Self { HttpDispatchError::Io(err) } } -impl From for HttpDispatchError { +impl From for HttpDispatchError { fn from(err: http2::Error) -> Self { HttpDispatchError::Http2(err) } diff --git a/src/server/h1.rs b/src/server/h1.rs index 4fb730f7..e2b4bf45 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -7,15 +7,16 @@ use futures::{Async, Future, Poll}; use tokio_current_thread::spawn; use tokio_timer::Delay; -use error::{Error, PayloadError}; +use error::{Error, ParseError, PayloadError}; use http::{StatusCode, Version}; use payload::{Payload, PayloadStatus, PayloadWriter}; use super::error::{HttpDispatchError, ServerError}; -use super::h1decoder::{DecoderError, H1Decoder, Message}; +use super::h1decoder::{H1Decoder, Message}; use super::h1writer::H1Writer; use super::handler::{HttpHandler, HttpHandlerTask, HttpHandlerTaskFut}; use super::input::PayloadType; +use super::message::Request; use super::settings::ServiceConfig; use super::{IoStream, Writer}; @@ -44,7 +45,7 @@ pub struct Http1Dispatcher { payload: Option, buf: BytesMut, tasks: VecDeque>, - error: Option, + error: Option>, ka_expire: Instant, ka_timer: Option, } @@ -109,7 +110,7 @@ where Http1Dispatcher { stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(), + decoder: H1Decoder::new(settings.request_pool()), payload: None, tasks: VecDeque::new(), error: None, @@ -133,7 +134,7 @@ where let mut disp = Http1Dispatcher { flags: Flags::STARTED | Flags::READ_DISCONNECTED | Flags::FLUSHED, stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(), + decoder: H1Decoder::new(settings.request_pool()), payload: None, tasks: VecDeque::new(), error: None, @@ -201,7 +202,7 @@ where } #[inline] - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { + pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { // check connection keep-alive self.poll_keep_alive()?; @@ -247,7 +248,7 @@ where } /// Flush stream - fn poll_flush(&mut self, shutdown: bool) -> Poll<(), HttpDispatchError> { + fn poll_flush(&mut self, shutdown: bool) -> Poll<(), HttpDispatchError> { if shutdown || self.flags.contains(Flags::STARTED) { match self.stream.poll_completed(shutdown) { Ok(Async::NotReady) => { @@ -277,7 +278,7 @@ where } /// keep-alive timer. returns `true` is keep-alive, otherwise drop - fn poll_keep_alive(&mut self) -> Result<(), HttpDispatchError> { + fn poll_keep_alive(&mut self) -> Result<(), HttpDispatchError> { if let Some(ref mut timer) = self.ka_timer { match timer.poll() { Ok(Async::Ready(_)) => { @@ -336,7 +337,7 @@ where #[inline] /// read data from the stream - pub(self) fn poll_io(&mut self) -> Result { + pub(self) fn poll_io(&mut self) -> Result> { if !self.flags.contains(Flags::POLLED) { self.flags.insert(Flags::POLLED); if !self.buf.is_empty() { @@ -367,7 +368,7 @@ where Ok(updated) } - pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { + pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { self.poll_io()?; let mut retry = self.can_read(); @@ -409,7 +410,7 @@ where // it is not possible to recover from error // during pipe handling, so just drop connection self.client_disconnected(false); - return Err(err.into()); + return Err(HttpDispatchError::App(err)); } } } @@ -423,7 +424,7 @@ where Ok(Async::NotReady) => false, Ok(Async::Ready(_)) => true, Err(err) => { - self.error = Some(err.into()); + self.error = Some(HttpDispatchError::App(err)); true } }; @@ -462,66 +463,75 @@ where .push_back(Entry::Error(ServerError::err(Version::HTTP_11, status))); } - pub(self) fn parse(&mut self) -> Result { + fn handle_message( + &mut self, mut msg: Request, payload: bool, + ) -> Result<(), HttpDispatchError> { + self.flags.insert(Flags::STARTED); + + if payload { + let (ps, pl) = Payload::new(false); + *msg.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); + } + + // stream extensions + msg.inner_mut().stream_extensions = self.stream.get_mut().extensions(); + + // set remote addr + msg.inner_mut().addr = self.addr; + + // search handler for request + match self.settings.handler().handle(msg) { + Ok(mut task) => { + if self.tasks.is_empty() { + match task.poll_io(&mut self.stream) { + Ok(Async::Ready(ready)) => { + // override keep-alive state + if self.stream.keepalive() { + self.flags.insert(Flags::KEEPALIVE); + } else { + self.flags.remove(Flags::KEEPALIVE); + } + // prepare stream for next response + self.stream.reset(); + + if !ready { + // task is done with io operations + // but still needs to do more work + spawn(HttpHandlerTaskFut::new(task)); + } + } + Ok(Async::NotReady) => (), + Err(err) => { + error!("Unhandled error: {}", err); + self.client_disconnected(false); + return Err(HttpDispatchError::App(err)); + } + } + } else { + self.tasks.push_back(Entry::Task(task)); + } + } + Err(_) => { + // handler is not found + self.push_response_entry(StatusCode::NOT_FOUND); + } + } + Ok(()) + } + + pub(self) fn parse(&mut self) -> Result> { let mut updated = false; 'outer: loop { - match self.decoder.decode(&mut self.buf, &self.settings) { - Ok(Some(Message::Message { mut msg, payload })) => { + match self.decoder.decode(&mut self.buf) { + Ok(Some(Message::Message(msg))) => { updated = true; - self.flags.insert(Flags::STARTED); - - if payload { - let (ps, pl) = Payload::new(false); - *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); - } - - // stream extensions - msg.inner_mut().stream_extensions = - self.stream.get_mut().extensions(); - - // set remote addr - msg.inner_mut().addr = self.addr; - - // search handler for request - match self.settings.handler().handle(msg) { - Ok(mut task) => { - if self.tasks.is_empty() { - match task.poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - if !ready { - // task is done with io operations - // but still needs to do more work - spawn(HttpHandlerTaskFut::new(task)); - } - continue 'outer; - } - Ok(Async::NotReady) => (), - Err(err) => { - error!("Unhandled error: {}", err); - self.client_disconnected(false); - return Err(err.into()); - } - } - } - self.tasks.push_back(Entry::Task(task)); - continue 'outer; - } - Err(_) => { - // handler is not found - self.push_response_entry(StatusCode::NOT_FOUND); - } - } + self.handle_message(msg, false)?; + } + Ok(Some(Message::MessageWithPayload(msg))) => { + updated = true; + self.handle_message(msg, true)?; } Ok(Some(Message::Chunk(chunk))) => { updated = true; @@ -556,8 +566,8 @@ where Err(e) => { if let Some(mut payload) = self.payload.take() { let e = match e { - DecoderError::Io(e) => PayloadError::Io(e), - DecoderError::Error(_) => PayloadError::EncodingCorrupted, + ParseError::Io(e) => PayloadError::Io(e), + _ => PayloadError::EncodingCorrupted, }; payload.set_error(e); } @@ -593,6 +603,7 @@ mod tests { use super::*; use application::{App, HttpApplication}; + use error::ParseError; use httpmessage::HttpMessage; use server::h1decoder::Message; use server::handler::IntoHttpHandler; @@ -612,13 +623,14 @@ mod tests { impl Message { fn message(self) -> Request { match self { - Message::Message { msg, payload: _ } => msg, + Message::Message(msg) => msg, + Message::MessageWithPayload(msg) => msg, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - Message::Message { msg: _, payload } => payload, + Message::MessageWithPayload(_) => true, _ => panic!("error"), } } @@ -639,7 +651,7 @@ mod tests { macro_rules! parse_ready { ($e:expr) => {{ let settings = wrk_settings(); - match H1Decoder::new().decode($e, &settings) { + match H1Decoder::new(settings.request_pool()).decode($e) { Ok(Some(msg)) => msg.message(), Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), @@ -651,10 +663,10 @@ mod tests { ($e:expr) => {{ let settings = wrk_settings(); - match H1Decoder::new().decode($e, &settings) { + match H1Decoder::new(settings.request_pool()).decode($e) { Err(err) => match err { - DecoderError::Error(_) => (), - _ => unreachable!("Parse error expected"), + ParseError::Io(_) => unreachable!("Parse error expected"), + _ => (), }, _ => unreachable!("Error expected"), } @@ -747,8 +759,8 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(Some(msg)) => { let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); @@ -764,14 +776,14 @@ mod tests { let mut buf = BytesMut::from("PUT /test HTTP/1"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(None) => (), _ => unreachable!("Error"), } buf.extend(b".1\r\n\r\n"); - match reader.decode(&mut buf, &settings) { + match reader.decode(&mut buf) { Ok(Some(msg)) => { let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); @@ -787,8 +799,8 @@ mod tests { let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(Some(msg)) => { let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_10); @@ -805,20 +817,15 @@ mod tests { BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(Some(msg)) => { let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"body" ); } @@ -832,20 +839,15 @@ mod tests { BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { + let mut reader = H1Decoder::new(settings.request_pool()); + match reader.decode(&mut buf) { Ok(Some(msg)) => { let mut req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"body" ); } @@ -857,11 +859,11 @@ mod tests { fn test_parse_partial_eof() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + let mut reader = H1Decoder::new(settings.request_pool()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); - match reader.decode(&mut buf, &settings) { + match reader.decode(&mut buf) { Ok(Some(msg)) => { let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); @@ -877,17 +879,17 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + let mut reader = H1Decoder::new(settings.request_pool()); + assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t"); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"es"); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t: value\r\n\r\n"); - match reader.decode(&mut buf, &settings) { + match reader.decode(&mut buf) { Ok(Some(msg)) => { let req = msg.message(); assert_eq!(req.version(), Version::HTTP_11); @@ -907,8 +909,8 @@ mod tests { Set-Cookie: c2=cookie2\r\n\r\n", ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); let req = msg.message(); let val: Vec<_> = req @@ -1109,19 +1111,14 @@ mod tests { upgrade: websocket\r\n\r\n\ some raw data", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req = msg.message(); assert!(!req.keep_alive()); assert!(req.upgrade()); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"some raw data" ); } @@ -1169,32 +1166,22 @@ mod tests { transfer-encoding: chunked\r\n\r\n", ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req = msg.message(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"data" ); assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"line" ); - assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); + assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); } #[test] @@ -1204,8 +1191,8 @@ mod tests { transfer-encoding: chunked\r\n\r\n", ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req = msg.message(); assert!(req.chunked().unwrap()); @@ -1216,14 +1203,14 @@ mod tests { transfer-encoding: chunked\r\n\r\n" .iter(), ); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req2 = msg.message(); assert!(req2.chunked().unwrap()); @@ -1239,30 +1226,30 @@ mod tests { ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); let req = msg.message(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\n1111\r\n"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"1111"); buf.extend(b"4\r\ndata\r"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"data"); buf.extend(b"\n4"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\n"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"li"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"li"); //trailers @@ -1270,12 +1257,12 @@ mod tests { //not_ready!(reader.parse(&mut buf, &mut readbuf)); buf.extend(b"ne\r\n0\r\n"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); - assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); + assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); } #[test] @@ -1286,17 +1273,17 @@ mod tests { ); let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let mut reader = H1Decoder::new(settings.request_pool()); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.is_payload()); assert!(msg.message().chunked().unwrap()); buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); + let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); + let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); } } diff --git a/src/server/h1codec.rs b/src/server/h1codec.rs new file mode 100644 index 00000000..ea56110d --- /dev/null +++ b/src/server/h1codec.rs @@ -0,0 +1,251 @@ +#![allow(unused_imports, unused_variables, dead_code)] +use std::io::{self, Write}; + +use bytes::{BufMut, Bytes, BytesMut}; +use tokio_codec::{Decoder, Encoder}; + +use super::h1decoder::{H1Decoder, Message}; +use super::helpers; +use super::message::RequestPool; +use super::output::{ResponseInfo, ResponseLength}; +use body::Body; +use error::ParseError; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::Version; +use httpresponse::HttpResponse; + +pub(crate) enum OutMessage { + Response(HttpResponse), + Payload(Bytes), +} + +pub(crate) struct H1Codec { + decoder: H1Decoder, + encoder: H1Writer, +} + +impl H1Codec { + pub fn new(pool: &'static RequestPool) -> Self { + H1Codec { + decoder: H1Decoder::new(pool), + encoder: H1Writer::new(), + } + } +} + +impl Decoder for H1Codec { + type Item = Message; + type Error = ParseError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + self.decoder.decode(src) + } +} + +impl Encoder for H1Codec { + type Item = OutMessage; + type Error = io::Error; + + fn encode( + &mut self, item: Self::Item, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + match item { + OutMessage::Response(res) => { + self.encoder.encode(res, dst)?; + } + OutMessage::Payload(bytes) => { + dst.extend_from_slice(&bytes); + } + } + Ok(()) + } +} + +bitflags! { + struct Flags: u8 { + const STARTED = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const DISCONNECTED = 0b0000_1000; + } +} + +const AVERAGE_HEADER_SIZE: usize = 30; + +struct H1Writer { + flags: Flags, + written: u64, + headers_size: u32, +} + +impl H1Writer { + fn new() -> H1Writer { + H1Writer { + flags: Flags::empty(), + written: 0, + headers_size: 0, + } + } + + fn written(&self) -> u64 { + self.written + } + + pub fn reset(&mut self) { + self.written = 0; + self.flags = Flags::KEEPALIVE; + } + + pub fn upgrade(&self) -> bool { + self.flags.contains(Flags::UPGRADE) + } + + pub fn keepalive(&self) -> bool { + self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) + } + + fn encode( + &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, + ) -> io::Result<()> { + // prepare task + let info = ResponseInfo::new(false); // req.inner.method == Method::HEAD); + + //if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { + //self.flags = Flags::STARTED | Flags::KEEPALIVE; + //} else { + self.flags = Flags::STARTED; + //} + + // Connection upgrade + let version = msg.version().unwrap_or_else(|| Version::HTTP_11); //req.inner.version); + if msg.upgrade() { + self.flags.insert(Flags::UPGRADE); + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("upgrade")); + } + // keep-alive + else if self.flags.contains(Flags::KEEPALIVE) { + if version < Version::HTTP_11 { + msg.headers_mut() + .insert(CONNECTION, HeaderValue::from_static("keep-alive")); + } + } 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 reason = msg.reason().as_bytes(); + if let Body::Binary(ref bytes) = body { + buffer.reserve( + 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + + bytes.len() + + reason.len(), + ); + } else { + buffer.reserve( + 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), + ); + } + + // status line + helpers::write_status_line(version, msg.status().as_u16(), buffer); + buffer.extend_from_slice(reason); + + // content length + match info.length { + ResponseLength::Chunked => { + buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } + ResponseLength::Zero => { + buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") + } + ResponseLength::Length(len) => { + helpers::write_content_length(len, buffer) + } + ResponseLength::Length64(len) => { + buffer.extend_from_slice(b"\r\ncontent-length: "); + write!(buffer.writer(), "{}", len)?; + buffer.extend_from_slice(b"\r\n"); + } + ResponseLength::None => buffer.extend_from_slice(b"\r\n"), + } + if let Some(ce) = info.content_encoding { + buffer.extend_from_slice(b"content-encoding: "); + buffer.extend_from_slice(ce.as_ref()); + buffer.extend_from_slice(b"\r\n"); + } + + // write headers + let mut pos = 0; + let mut has_date = false; + let mut remaining = buffer.remaining_mut(); + let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; + for (key, value) in msg.headers() { + match *key { + TRANSFER_ENCODING => continue, + CONTENT_LENGTH => match info.length { + ResponseLength::None => (), + _ => continue, + }, + DATE => { + has_date = true; + } + _ => (), + } + + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { + buffer.advance_mut(pos); + } + pos = 0; + buffer.reserve(len); + remaining = buffer.remaining_mut(); + unsafe { + buf = &mut *(buffer.bytes_mut() as *mut _); + } + } + + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; + } + unsafe { + buffer.advance_mut(pos); + } + + // optimized date header, set_date writes \r\n + if !has_date { + // self.settings.set_date(&mut buffer, true); + buffer.extend_from_slice(b"\r\n"); + } else { + // msg eof + buffer.extend_from_slice(b"\r\n"); + } + self.headers_size = buffer.len() as u32; + } + + if let Body::Binary(bytes) = body { + self.written = bytes.len() as u64; + // buffer.write(bytes.as_ref())?; + buffer.extend_from_slice(bytes.as_ref()); + } else { + // capacity, makes sense only for streaming or actor + // self.buffer_capacity = msg.write_buffer_capacity(); + + msg.replace_body(body); + } + Ok(()) + } +} diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 434dc42d..c6f0974a 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -4,8 +4,7 @@ use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use httparse; -use super::message::{MessageFlags, Request}; -use super::settings::ServiceConfig; +use super::message::{MessageFlags, Request, RequestPool}; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HttpTryFrom, Method, Uri, Version}; @@ -16,35 +15,26 @@ const MAX_HEADERS: usize = 96; pub(crate) struct H1Decoder { decoder: Option, + pool: &'static RequestPool, } #[derive(Debug)] -pub(crate) enum Message { - Message { msg: Request, payload: bool }, +pub enum Message { + Message(Request), + MessageWithPayload(Request), Chunk(Bytes), Eof, } -#[derive(Debug)] -pub(crate) enum DecoderError { - Io(io::Error), - Error(ParseError), -} - -impl From for DecoderError { - fn from(err: io::Error) -> DecoderError { - DecoderError::Io(err) - } -} - impl H1Decoder { - pub fn new() -> H1Decoder { - H1Decoder { decoder: None } + pub fn new(pool: &'static RequestPool) -> H1Decoder { + H1Decoder { + pool, + decoder: None, + } } - pub fn decode( - &mut self, src: &mut BytesMut, settings: &ServiceConfig, - ) -> Result, DecoderError> { + pub fn decode(&mut self, src: &mut BytesMut) -> Result, ParseError> { // read payload if self.decoder.is_some() { match self.decoder.as_mut().unwrap().decode(src)? { @@ -57,21 +47,19 @@ impl H1Decoder { } } - match self - .parse_message(src, settings) - .map_err(DecoderError::Error)? - { + match self.parse_message(src)? { Async::Ready((msg, decoder)) => { self.decoder = decoder; - Ok(Some(Message::Message { - msg, - payload: self.decoder.is_some(), - })) + if self.decoder.is_some() { + Ok(Some(Message::MessageWithPayload(msg))) + } else { + Ok(Some(Message::Message(msg))) + } } Async::NotReady => { if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - Err(DecoderError::Error(ParseError::TooLarge)) + Err(ParseError::TooLarge) } else { Ok(None) } @@ -79,8 +67,8 @@ impl H1Decoder { } } - fn parse_message( - &self, buf: &mut BytesMut, settings: &ServiceConfig, + fn parse_message( + &self, buf: &mut BytesMut, ) -> Poll<(Request, Option), ParseError> { // Parse http message let mut has_upgrade = false; @@ -119,7 +107,7 @@ impl H1Decoder { let slice = buf.split_to(len).freeze(); // convert headers - let mut msg = settings.get_request(); + let mut msg = RequestPool::get(self.pool); { let inner = msg.inner_mut(); inner diff --git a/src/server/h1disp.rs b/src/server/h1disp.rs new file mode 100644 index 00000000..b1c2c8a2 --- /dev/null +++ b/src/server/h1disp.rs @@ -0,0 +1,425 @@ +// #![allow(unused_imports, unused_variables, dead_code)] +use std::collections::VecDeque; +use std::fmt::{Debug, Display}; +use std::net::SocketAddr; +// use std::time::{Duration, Instant}; + +use actix_net::service::Service; + +use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; +use tokio_codec::Framed; +// use tokio_current_thread::spawn; +use tokio_io::AsyncWrite; +// use tokio_timer::Delay; + +use error::{ParseError, PayloadError}; +use payload::{Payload, PayloadStatus, PayloadWriter}; + +use body::Body; +use httpresponse::HttpResponse; + +use super::error::HttpDispatchError; +use super::h1codec::{H1Codec, OutMessage}; +use super::h1decoder::Message; +use super::input::PayloadType; +use super::message::{Request, RequestPool}; +use super::IoStream; + +const MAX_PIPELINED_MESSAGES: usize = 16; + +bitflags! { + pub struct Flags: u8 { + const STARTED = 0b0000_0001; + const KEEPALIVE_ENABLED = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const SHUTDOWN = 0b0000_1000; + const READ_DISCONNECTED = 0b0001_0000; + const WRITE_DISCONNECTED = 0b0010_0000; + const POLLED = 0b0100_0000; + const FLUSHED = 0b1000_0000; + } +} + +/// Dispatcher for HTTP/1.1 protocol +pub struct Http1Dispatcher +where + S::Error: Debug + Display, +{ + service: S, + flags: Flags, + addr: Option, + framed: Framed, + error: Option>, + + state: State, + payload: Option, + messages: VecDeque, +} + +enum State { + None, + Response(S::Future), + SendResponse(Option), + SendResponseWithPayload(Option<(OutMessage, Body)>), + Payload(Body), +} + +impl State { + fn is_empty(&self) -> bool { + if let State::None = self { + true + } else { + false + } + } +} + +impl Http1Dispatcher +where + T: IoStream, + S: Service, + S::Error: Debug + Display, +{ + pub fn new(stream: T, pool: &'static RequestPool, service: S) -> Self { + let addr = stream.peer_addr(); + let flags = Flags::FLUSHED; + let codec = H1Codec::new(pool); + let framed = Framed::new(stream, codec); + + Http1Dispatcher { + payload: None, + state: State::None, + error: None, + messages: VecDeque::new(), + service, + flags, + addr, + framed, + } + } + + #[inline] + fn can_read(&self) -> bool { + if self.flags.contains(Flags::READ_DISCONNECTED) { + return false; + } + + if let Some(ref info) = self.payload { + info.need_read() == PayloadStatus::Read + } else { + true + } + } + + // if checked is set to true, delay disconnect until all tasks have finished. + fn client_disconnected(&mut self, checked: bool) { + self.flags.insert(Flags::READ_DISCONNECTED); + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } + + // if !checked || self.tasks.is_empty() { + // self.flags + // .insert(Flags::WRITE_DISCONNECTED | Flags::FLUSHED); + + // // notify tasks + // for mut task in self.tasks.drain(..) { + // task.disconnected(); + // match task.poll_completed() { + // Ok(Async::NotReady) => { + // // spawn not completed task, it does not require access to io + // // at this point + // spawn(HttpHandlerTaskFut::new(task.into_task())); + // } + // Ok(Async::Ready(_)) => (), + // Err(err) => { + // error!("Unhandled application error: {}", err); + // } + // } + // } + // } + } + + /// Flush stream + fn poll_flush(&mut self) -> Poll<(), HttpDispatchError> { + if self.flags.contains(Flags::STARTED) && !self.flags.contains(Flags::FLUSHED) { + match self.framed.poll_complete() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => { + debug!("Error sending data: {}", err); + self.client_disconnected(false); + Err(err.into()) + } + Ok(Async::Ready(_)) => { + // if payload is not consumed we can not use connection + if self.payload.is_some() && self.state.is_empty() { + return Err(HttpDispatchError::PayloadIsNotConsumed); + } + self.flags.insert(Flags::FLUSHED); + Ok(Async::Ready(())) + } + } + } else { + Ok(Async::Ready(())) + } + } + + pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { + self.poll_io()?; + let mut retry = self.can_read(); + + // process + loop { + let state = match self.state { + State::None => loop { + break if let Some(msg) = self.messages.pop_front() { + let mut task = self.service.call(msg); + match task.poll() { + Ok(Async::Ready(res)) => { + if res.body().is_streaming() { + unimplemented!() + } else { + Some(Ok(State::SendResponse(Some( + OutMessage::Response(res), + )))) + } + } + Ok(Async::NotReady) => Some(Ok(State::Response(task))), + Err(err) => Some(Err(HttpDispatchError::App(err))), + } + } else { + None + }; + }, + State::Payload(ref mut body) => unimplemented!(), + State::Response(ref mut fut) => { + match fut.poll() { + Ok(Async::Ready(res)) => { + if res.body().is_streaming() { + unimplemented!() + } else { + Some(Ok(State::SendResponse(Some( + OutMessage::Response(res), + )))) + } + } + Ok(Async::NotReady) => None, + Err(err) => { + // it is not possible to recover from error + // during pipe handling, so just drop connection + Some(Err(HttpDispatchError::App(err))) + } + } + } + State::SendResponse(ref mut item) => { + let msg = item.take().expect("SendResponse is empty"); + match self.framed.start_send(msg) { + Ok(AsyncSink::Ready) => { + self.flags.remove(Flags::FLUSHED); + Some(Ok(State::None)) + } + Ok(AsyncSink::NotReady(msg)) => { + *item = Some(msg); + return Ok(()); + } + Err(err) => Some(Err(HttpDispatchError::Io(err))), + } + } + State::SendResponseWithPayload(ref mut item) => { + let (msg, body) = item.take().expect("SendResponse is empty"); + match self.framed.start_send(msg) { + Ok(AsyncSink::Ready) => { + self.flags.remove(Flags::FLUSHED); + Some(Ok(State::Payload(body))) + } + Ok(AsyncSink::NotReady(msg)) => { + *item = Some((msg, body)); + return Ok(()); + } + Err(err) => Some(Err(HttpDispatchError::Io(err))), + } + } + }; + + match state { + Some(Ok(state)) => self.state = state, + Some(Err(err)) => { + // error!("Unhandled error1: {}", err); + self.client_disconnected(false); + return Err(err); + } + None => { + // if read-backpressure is enabled and we consumed some data. + // we may read more dataand retry + if !retry && self.can_read() && self.poll_io()? { + retry = self.can_read(); + continue; + } + break; + } + } + } + + Ok(()) + } + + fn one_message(&mut self, msg: Message) -> Result<(), HttpDispatchError> { + self.flags.insert(Flags::STARTED); + + match msg { + Message::Message(mut msg) => { + // set remote addr + msg.inner_mut().addr = self.addr; + + // handle request early + if self.state.is_empty() { + let mut task = self.service.call(msg); + match task.poll() { + Ok(Async::Ready(res)) => { + if res.body().is_streaming() { + unimplemented!() + } else { + self.state = + State::SendResponse(Some(OutMessage::Response(res))); + } + } + Ok(Async::NotReady) => self.state = State::Response(task), + Err(err) => { + error!("Unhandled application error: {}", err); + self.client_disconnected(false); + return Err(HttpDispatchError::App(err)); + } + } + } else { + self.messages.push_back(msg); + } + } + Message::MessageWithPayload(mut msg) => { + // set remote addr + msg.inner_mut().addr = self.addr; + + // payload + let (ps, pl) = Payload::new(false); + *msg.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); + + self.messages.push_back(msg); + } + Message::Chunk(chunk) => { + if let Some(ref mut payload) = self.payload { + payload.feed_data(chunk); + } else { + error!("Internal server error: unexpected payload chunk"); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.error = Some(HttpDispatchError::InternalError); + } + } + Message::Eof => { + if let Some(mut payload) = self.payload.take() { + payload.feed_eof(); + } else { + error!("Internal server error: unexpected eof"); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.error = Some(HttpDispatchError::InternalError); + } + } + } + + Ok(()) + } + + pub(self) fn poll_io(&mut self) -> Result> { + let mut updated = false; + + if self.messages.len() < MAX_PIPELINED_MESSAGES { + 'outer: loop { + match self.framed.poll() { + Ok(Async::Ready(Some(msg))) => { + updated = true; + self.one_message(msg)?; + } + Ok(Async::Ready(None)) => { + if self.flags.contains(Flags::READ_DISCONNECTED) { + self.client_disconnected(true); + } + break; + } + Ok(Async::NotReady) => break, + Err(e) => { + if let Some(mut payload) = self.payload.take() { + let e = match e { + ParseError::Io(e) => PayloadError::Io(e), + _ => PayloadError::EncodingCorrupted, + }; + payload.set_error(e); + } + + // Malformed requests should be responded with 400 + // self.push_response_entry(StatusCode::BAD_REQUEST); + self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + self.error = Some(HttpDispatchError::MalformedRequest); + break; + } + } + } + } + + Ok(updated) + } +} + +impl Future for Http1Dispatcher +where + T: IoStream, + S: Service, + S::Error: Debug + Display, +{ + type Item = (); + type Error = HttpDispatchError; + + #[inline] + fn poll(&mut self) -> Poll<(), Self::Error> { + // shutdown + if self.flags.contains(Flags::SHUTDOWN) { + if self.flags.contains(Flags::WRITE_DISCONNECTED) { + return Ok(Async::Ready(())); + } + try_ready!(self.poll_flush()); + return Ok(AsyncWrite::shutdown(self.framed.get_mut())?); + } + + // process incoming requests + if !self.flags.contains(Flags::WRITE_DISCONNECTED) { + self.poll_handler()?; + + // flush stream + self.poll_flush()?; + + // deal with keep-alive and stream eof (client-side write shutdown) + if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { + // handle stream eof + if self + .flags + .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) + { + return Ok(Async::Ready(())); + } + // no keep-alive + if self.flags.contains(Flags::STARTED) + && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) + || !self.flags.contains(Flags::KEEPALIVE)) + { + self.flags.insert(Flags::SHUTDOWN); + return self.poll(); + } + } + Ok(Async::NotReady) + } else if let Some(err) = self.error.take() { + Err(err) + } else { + Ok(Async::Ready(())) + } + } +} diff --git a/src/server/h2.rs b/src/server/h2.rs index 2fe2fa07..27ae4785 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -86,7 +86,7 @@ where &self.settings } - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { + pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { // server if let State::Connection(ref mut conn) = self.state { // keep-alive timer diff --git a/src/server/message.rs b/src/server/message.rs index 9c4bc1ec..74ec5f17 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -241,10 +241,7 @@ impl fmt::Debug for Request { } } -pub(crate) struct RequestPool( - RefCell>>, - RefCell, -); +pub struct RequestPool(RefCell>>, RefCell); thread_local!(static POOL: &'static RequestPool = RequestPool::create()); @@ -257,7 +254,7 @@ impl RequestPool { Box::leak(Box::new(pool)) } - pub fn pool(settings: ServerSettings) -> &'static RequestPool { + pub(crate) fn pool(settings: ServerSettings) -> &'static RequestPool { POOL.with(|p| { *p.1.borrow_mut() = settings; *p @@ -275,7 +272,7 @@ impl RequestPool { #[inline] /// Release request instance - pub fn release(&self, msg: Rc) { + pub(crate) fn release(&self, msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { v.push_front(msg); diff --git a/src/server/mod.rs b/src/server/mod.rs index 3277dba5..ce3f2fbf 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -122,7 +122,10 @@ pub(crate) mod builder; mod channel; mod error; pub(crate) mod h1; -pub(crate) mod h1decoder; +#[doc(hidden)] +pub mod h1codec; +#[doc(hidden)] +pub mod h1decoder; mod h1writer; mod h2; mod h2writer; @@ -145,6 +148,9 @@ pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; pub use self::settings::ServerSettings; +#[doc(hidden)] +pub mod h1disp; + #[doc(hidden)] pub use self::acceptor::AcceptorTimeout; diff --git a/src/server/output.rs b/src/server/output.rs index 70c24fac..1da7e902 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -10,7 +10,7 @@ use bytes::BytesMut; use flate2::write::{GzEncoder, ZlibEncoder}; #[cfg(feature = "flate2")] use flate2::Compression; -use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH}; +use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use super::message::InnerRequest; @@ -18,6 +18,12 @@ use body::{Binary, Body}; use header::ContentEncoding; use httpresponse::HttpResponse; +// #[derive(Debug)] +// pub(crate) struct RequestInfo { +// pub version: Version, +// pub accept_encoding: Option, +// } + #[derive(Debug)] pub(crate) enum ResponseLength { Chunked, diff --git a/src/server/service.rs b/src/server/service.rs index e3402e30..a55c33f7 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -10,6 +10,7 @@ use super::error::HttpDispatchError; use super::handler::HttpHandler; use super::settings::ServiceConfig; use super::IoStream; +use error::Error; /// `NewService` implementation for HTTP1/HTTP2 transports pub struct HttpService @@ -42,7 +43,7 @@ where { type Request = Io; type Response = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; type InitError = (); type Service = HttpServiceHandler; type Future = FutureResult; @@ -81,7 +82,7 @@ where { type Request = Io; type Response = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; type Future = HttpChannel; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -124,7 +125,7 @@ where { type Request = Io; type Response = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; type InitError = (); type Service = H1ServiceHandler; type Future = FutureResult; @@ -164,7 +165,7 @@ where { type Request = Io; type Response = (); - type Error = HttpDispatchError; + type Error = HttpDispatchError; type Future = H1Channel; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/server/settings.rs b/src/server/settings.rs index 9b27ed5e..9df1e457 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -215,6 +215,11 @@ impl ServiceConfig { RequestPool::get(self.0.messages) } + #[doc(hidden)] + pub fn request_pool(&self) -> &'static RequestPool { + self.0.messages + } + fn update_date(&self) { // Unsafe: WorkerSetting is !Sync and !Send unsafe { (*self.0.date.get()).0 = false }; diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs new file mode 100644 index 00000000..7e8e9a42 --- /dev/null +++ b/tests/test_h1v2.rs @@ -0,0 +1,58 @@ +extern crate actix; +extern crate actix_net; +extern crate actix_web; +extern crate futures; + +use std::thread; + +use actix::System; +use actix_net::server::Server; +use actix_net::service::{IntoNewService, IntoService}; +use futures::future; + +use actix_web::server::h1disp::Http1Dispatcher; +use actix_web::server::KeepAlive; +use actix_web::server::ServiceConfig; +use actix_web::{client, test, App, Error, HttpRequest, HttpResponse}; + +#[test] +fn test_h1_v2() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let app = App::new() + .route("/", http::Method::GET, |_: HttpRequest| "OK") + .finish(); + let settings = ServiceConfig::build(app) + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_shutdown(1000) + .server_hostname("localhost") + .server_address(addr) + .finish(); + + (move |io| { + let pool = settings.request_pool(); + Http1Dispatcher::new( + io, + pool, + (|req| { + println!("REQ: {:?}", req); + future::ok::<_, Error>(HttpResponse::Ok().finish()) + }).into_service(), + ) + }).into_new_service() + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + } +} From 6aa2de7b8d655f9fd0dc25d67c90d32a580a101c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 17:00:27 -0700 Subject: [PATCH 0714/1635] remove actix-web artifacts --- CHANGES.md | 712 +----------- Cargo.toml | 15 +- MIGRATION.md | 176 --- Makefile | 14 - README.md | 44 +- build.rs | 16 - src/application.rs | 879 --------------- src/body.rs | 29 +- src/client/connector.rs | 1344 ---------------------- src/client/mod.rs | 118 -- src/client/parser.rs | 238 ---- src/client/pipeline.rs | 552 --------- src/client/request.rs | 782 ------------- src/client/response.rs | 124 --- src/client/writer.rs | 412 ------- src/context.rs | 294 ----- src/de.rs | 443 -------- src/error.rs | 15 +- src/extractor.rs | 1024 ----------------- src/fs.rs | 1786 ------------------------------ src/handler.rs | 562 ---------- src/helpers.rs | 571 ---------- src/httpmessage.rs | 41 - src/httpresponse.rs | 110 +- src/json.rs | 99 +- src/lib.rs | 101 +- src/middleware/cors.rs | 1183 -------------------- src/middleware/csrf.rs | 275 ----- src/middleware/defaultheaders.rs | 120 -- src/middleware/errhandlers.rs | 141 --- src/middleware/identity.rs | 387 ------- src/middleware/logger.rs | 384 ------- src/middleware/mod.rs | 68 -- src/middleware/session.rs | 617 ----------- src/multipart.rs | 815 -------------- src/param.rs | 303 ----- src/pipeline.rs | 869 --------------- src/pred.rs | 328 ------ src/resource.rs | 324 ------ src/route.rs | 666 ----------- src/router.rs | 1247 --------------------- src/scope.rs | 1236 --------------------- src/server/acceptor.rs | 396 ------- src/server/builder.rs | 117 -- src/server/channel.rs | 436 -------- src/server/error.rs | 29 - src/server/h1writer.rs | 356 ------ src/server/h2.rs | 453 -------- src/server/h2writer.rs | 259 ----- src/server/handler.rs | 208 ---- src/server/http.rs | 584 ---------- src/server/incoming.rs | 69 -- src/server/mod.rs | 147 +-- src/server/output.rs | 2 +- src/server/settings.rs | 44 +- src/server/ssl/mod.rs | 12 - src/server/ssl/nativetls.rs | 34 - src/server/ssl/openssl.rs | 87 -- src/server/ssl/rustls.rs | 87 -- src/with.rs | 383 ------- tests/test_client.rs | 508 --------- tests/test_custom_pipeline.rs | 81 -- tests/test_h1v2.rs | 14 +- tests/test_handlers.rs | 677 ----------- tests/test_middleware.rs | 1055 ------------------ tests/test_server.rs | 1357 ----------------------- tests/test_ws.rs | 394 ------- 67 files changed, 51 insertions(+), 27202 deletions(-) delete mode 100644 MIGRATION.md delete mode 100644 Makefile delete mode 100644 build.rs delete mode 100644 src/application.rs delete mode 100644 src/client/connector.rs delete mode 100644 src/client/mod.rs delete mode 100644 src/client/parser.rs delete mode 100644 src/client/pipeline.rs delete mode 100644 src/client/request.rs delete mode 100644 src/client/response.rs delete mode 100644 src/client/writer.rs delete mode 100644 src/context.rs delete mode 100644 src/de.rs delete mode 100644 src/extractor.rs delete mode 100644 src/fs.rs delete mode 100644 src/handler.rs delete mode 100644 src/helpers.rs delete mode 100644 src/middleware/cors.rs delete mode 100644 src/middleware/csrf.rs delete mode 100644 src/middleware/defaultheaders.rs delete mode 100644 src/middleware/errhandlers.rs delete mode 100644 src/middleware/identity.rs delete mode 100644 src/middleware/logger.rs delete mode 100644 src/middleware/mod.rs delete mode 100644 src/middleware/session.rs delete mode 100644 src/multipart.rs delete mode 100644 src/param.rs delete mode 100644 src/pipeline.rs delete mode 100644 src/pred.rs delete mode 100644 src/resource.rs delete mode 100644 src/route.rs delete mode 100644 src/router.rs delete mode 100644 src/scope.rs delete mode 100644 src/server/acceptor.rs delete mode 100644 src/server/builder.rs delete mode 100644 src/server/channel.rs delete mode 100644 src/server/h1writer.rs delete mode 100644 src/server/h2.rs delete mode 100644 src/server/h2writer.rs delete mode 100644 src/server/handler.rs delete mode 100644 src/server/http.rs delete mode 100644 src/server/incoming.rs delete mode 100644 src/server/ssl/mod.rs delete mode 100644 src/server/ssl/nativetls.rs delete mode 100644 src/server/ssl/openssl.rs delete mode 100644 src/server/ssl/rustls.rs delete mode 100644 src/with.rs delete mode 100644 tests/test_client.rs delete mode 100644 tests/test_custom_pipeline.rs delete mode 100644 tests/test_handlers.rs delete mode 100644 tests/test_middleware.rs delete mode 100644 tests/test_server.rs delete mode 100644 tests/test_ws.rs diff --git a/CHANGES.md b/CHANGES.md index 3c55c3f6..c3c38011 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,713 +1,5 @@ # Changes -## [0.7.9] - 2018-09-x +## [0.1.0] - 2018-09-x -### Added - -* Added client shutdown timeout setting - -* Added slow request timeout setting - -* Respond with 408 response on slow request timeout #523 - - -### Fixed - -* HTTP1 decoding errors are reported to the client. #512 - -* Correctly compose multiple allowed origins in CORS. #517 - -* Websocket server finished() isn't called if client disconnects #511 - -* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521 - -* Correct usage of `no_http2` flag in `bind_*` methods. #519 - - -## [0.7.8] - 2018-09-17 - -### Added - -* Use server `Keep-Alive` setting as slow request timeout #439 - -### Changed - -* Use 5 seconds keep-alive timer by default. - -### Fixed - -* Fixed wrong error message for i16 type #510 - - -## [0.7.7] - 2018-09-11 - -### Fixed - -* Fix linked list of HttpChannels #504 - -* Fix requests to TestServer fail #508 - - -## [0.7.6] - 2018-09-07 - -### Fixed - -* Fix system_exit in HttpServer #501 - -* Fix parsing of route param containin regexes with repetition #500 - -### Changes - -* Unhide `SessionBackend` and `SessionImpl` traits #455 - - -## [0.7.5] - 2018-09-04 - -### Added - -* Added the ability to pass a custom `TlsConnector`. - -* Allow to register handlers on scope level #465 - - -### Fixed - -* Handle socket read disconnect - -* Handling scoped paths without leading slashes #460 - - -### Changed - -* Read client response until eof if connection header set to close #464 - - -## [0.7.4] - 2018-08-23 - -### Added - -* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`, - accept backpressure #250 - -* Allow to customize connection handshake process via `HttpServer::listen_with()` - and `HttpServer::bind_with()` methods - -* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472 - -### Changed - -* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`. - `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - -* native-tls - 0.2 - -* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461 - -### Fixed - -* Use zlib instead of raw deflate for decoding and encoding payloads with - `Content-Encoding: deflate`. - -* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436 - -* Fix adding multiple response headers #446 - -* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448 - -* Panic during access without routing being set #452 - -* Fixed http/2 error handling - -### Deprecated - -* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or - `RustlsAcceptor::with_flags()` instead - -* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been - deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`. - -* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been - deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`. - - -## [0.7.3] - 2018-08-01 - -### Added - -* Support HTTP/2 with rustls #36 - -* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433 - -### Fixed - -* Fixed failure 0.1.2 compatibility - -* Do not override HOST header for client request #428 - -* Gz streaming, use `flate2::write::GzDecoder` #228 - -* HttpRequest::url_for is not working with scopes #429 - -* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436 - - -## [0.7.2] - 2018-07-26 - -### Added - -* Add implementation of `FromRequest` for `Option` and `Result` - -* Allow to handle application prefix, i.e. allow to handle `/app` path - for application with `/app` prefix. - Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix) - api doc. - -* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies - -### Changed - -* Upgrade to cookie 0.11 - -* Removed the timestamp from the default logger middleware - -### Fixed - -* Missing response header "content-encoding" #421 - -* Fix stream draining for http/2 connections #290 - - -## [0.7.1] - 2018-07-21 - -### Fixed - -* Fixed default_resource 'not yet implemented' panic #410 - - -## [0.7.0] - 2018-07-21 - -### Added - -* Add `fs::StaticFileConfig` to provide means of customizing static - file services. It allows to map `mime` to `Content-Disposition`, - specify whether to use `ETag` and `Last-Modified` and allowed methods. - -* Add `.has_prefixed_resource()` method to `router::ResourceInfo` - for route matching with prefix awareness - -* Add `HttpMessage::readlines()` for reading line by line. - -* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. - -* Add method to configure custom error handler to `Form` extractor. - -* Add methods to `HttpResponse` to retrieve, add, and delete cookies - -* Add `.set_content_type()` and `.set_content_disposition()` methods - to `fs::NamedFile` to allow overriding the values inferred by default - -* Add `fs::file_extension_to_mime()` helper function to get the MIME - type for a file extension - -* Add `.content_disposition()` method to parse Content-Disposition of - multipart fields - -* Re-export `actix::prelude::*` as `actix_web::actix` module. - -* `HttpRequest::url_for_static()` for a named route with no variables segments - -* Propagation of the application's default resource to scopes that haven't set a default resource. - - -### Changed - -* Min rustc version is 1.26 - -* Use tokio instead of tokio-core - -* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages. - -* Became possible to use enums with query extractor. - Issue [#371](https://github.com/actix/actix-web/issues/371). - [Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134) - -* `HttpResponse::into_builder()` now moves cookies into the builder - instead of dropping them - -* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self` - -* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest` - -* Added header `User-Agent: Actix-web/` to default headers when building a request - -* port `Extensions` type from http create, we don't need `Send + Sync` - -* `HttpRequest::query()` returns `Ref>` - -* `HttpRequest::cookies()` returns `Ref>>` - -* `StaticFiles::new()` returns `Result, Error>` instead of `StaticFiles` - -* `StaticFiles` uses the default handler if the file does not exist - - -### Removed - -* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. - -* Remove `HttpMessage::range()` - - -## [0.6.15] - 2018-07-11 - -### Fixed - -* Fix h2 compatibility #352 - -* Fix duplicate tail of StaticFiles with index_file. #344 - - -## [0.6.14] - 2018-06-21 - -### Added - -* Allow to disable masking for websockets client - -### Fixed - -* SendRequest execution fails with the "internal error: entered unreachable code" #329 - - -## [0.6.13] - 2018-06-11 - -* http/2 end-of-frame is not set if body is empty bytes #307 - -* InternalError can trigger memory unsafety #301 - - -## [0.6.12] - 2018-06-08 - -### Added - -* Add `Host` filter #287 - -* Allow to filter applications - -* Improved failure interoperability with downcasting #285 - -* Allow to use custom resolver for `ClientConnector` - - -## [0.6.11] - 2018-06-05 - -* Support chunked encoding for UrlEncoded body #262 - -* `HttpRequest::url_for()` for a named route with no variables segments #265 - -* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255 - -* CORS: Do not validate Origin header on non-OPTION requests #271 - -* Fix multipart upload "Incomplete" error #282 - - -## [0.6.10] - 2018-05-24 - -### Added - -* Allow to use path without trailing slashes for scope registration #241 - -* Allow to set encoding for exact NamedFile #239 - -### Fixed - -* `TestServer::post()` actually sends `GET` request #240 - - -## 0.6.9 (2018-05-22) - -* Drop connection if request's payload is not fully consumed #236 - -* Fix streaming response with body compression - - -## 0.6.8 (2018-05-20) - -* Fix scope resource path extractor #234 - -* Re-use tcp listener on pause/resume - - -## 0.6.7 (2018-05-17) - -* Fix compilation with --no-default-features - - -## 0.6.6 (2018-05-17) - -* Panic during middleware execution #226 - -* Add support for listen_tls/listen_ssl #224 - -* Implement extractor for `Session` - -* Ranges header support for NamedFile #60 - - -## 0.6.5 (2018-05-15) - -* Fix error handling during request decoding #222 - - -## 0.6.4 (2018-05-11) - -* Fix segfault in ServerSettings::get_response_builder() - - -## 0.6.3 (2018-05-10) - -* Add `Router::with_async()` method for async handler registration. - -* Added error response functions for 501,502,503,504 - -* Fix client request timeout handling - - -## 0.6.2 (2018-05-09) - -* WsWriter trait is optional. - - -## 0.6.1 (2018-05-08) - -* Fix http/2 payload streaming #215 - -* Fix connector's default `keep-alive` and `lifetime` settings #212 - -* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214 - -* Allow to exclude certain endpoints from logging #211 - - -## 0.6.0 (2018-05-08) - -* Add route scopes #202 - -* Allow to use ssl and non-ssl connections at the same time #206 - -* Websocket CloseCode Empty/Status is ambiguous #193 - -* Add Content-Disposition to NamedFile #204 - -* Allow to access Error's backtrace object - -* Allow to override files listing renderer for `StaticFiles` #203 - -* Various extractor usability improvements #207 - - -## 0.5.6 (2018-04-24) - -* Make flate2 crate optional #200 - - -## 0.5.5 (2018-04-24) - -* Fix panic when Websocket is closed with no error code #191 - -* Allow to use rust backend for flate2 crate #199 - -## 0.5.4 (2018-04-19) - -* Add identity service middleware - -* Middleware response() is not invoked if there was an error in async handler #187 - -* Use Display formatting for InternalError Display implementation #188 - - -## 0.5.3 (2018-04-18) - -* Impossible to quote slashes in path parameters #182 - - -## 0.5.2 (2018-04-16) - -* Allow to configure StaticFiles's CpuPool, via static method or env variable - -* Add support for custom handling of Json extractor errors #181 - -* Fix StaticFiles does not support percent encoded paths #177 - -* Fix Client Request with custom Body Stream halting on certain size requests #176 - - -## 0.5.1 (2018-04-12) - -* Client connector provides stats, `ClientConnector::stats()` - -* Fix end-of-stream handling in parse_payload #173 - -* Fix StaticFiles generate a lot of threads #174 - - -## 0.5.0 (2018-04-10) - -* Type-safe path/query/form parameter handling, using serde #70 - -* HttpResponse builder's methods `.body()`, `.finish()`, `.json()` - return `HttpResponse` instead of `Result` - -* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()` - -* Added `signed` and `private` `CookieSessionBackend`s - -* Added `HttpRequest::resource()`, returns current matched resource - -* Added `ErrorHandlers` middleware - -* Fix router cannot parse Non-ASCII characters in URL #137 - -* Fix client connection pooling - -* Fix long client urls #129 - -* Fix panic on invalid URL characters #130 - -* Fix logger request duration calculation #152 - -* Fix prefix and static file serving #168 - - -## 0.4.10 (2018-03-20) - -* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods - -* Allow to set client request timeout - -* Allow to set client websocket handshake timeout - -* Refactor `TestServer` configuration - -* Fix server websockets big payloads support - -* Fix http/2 date header generation - - -## 0.4.9 (2018-03-16) - -* Allow to disable http/2 support - -* Wake payload reading task when data is available - -* Fix server keep-alive handling - -* Send Query Parameters in client requests #120 - -* Move brotli encoding to a feature - -* Add option of default handler for `StaticFiles` handler #57 - -* Add basic client connection pooling - - -## 0.4.8 (2018-03-12) - -* Allow to set read buffer capacity for server request - -* Handle WouldBlock error for socket accept call - - -## 0.4.7 (2018-03-11) - -* Fix panic on unknown content encoding - -* Fix connection get closed too early - -* Fix streaming response handling for http/2 - -* Better sleep on error support - - -## 0.4.6 (2018-03-10) - -* Fix client cookie handling - -* Fix json content type detection - -* Fix CORS middleware #117 - -* Optimize websockets stream support - - -## 0.4.5 (2018-03-07) - -* Fix compression #103 and #104 - -* Fix client cookie handling #111 - -* Non-blocking processing of a `NamedFile` - -* Enable compression support for `NamedFile` - -* Better support for `NamedFile` type - -* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client. - -* Add native-tls support for client - -* Allow client connection timeout to be set #108 - -* Allow to use std::net::TcpListener for HttpServer - -* Handle panics in worker threads - - -## 0.4.4 (2018-03-04) - -* Allow to use Arc> as response/request body - -* Fix handling of requests with an encoded body with a length > 8192 #93 - -## 0.4.3 (2018-03-03) - -* Fix request body read bug - -* Fix segmentation fault #79 - -* Set reuse address before bind #90 - - -## 0.4.2 (2018-03-02) - -* Better naming for websockets implementation - -* Add `Pattern::with_prefix()`, make it more usable outside of actix - -* Add csrf middleware for filter for cross-site request forgery #89 - -* Fix disconnect on idle connections - - -## 0.4.1 (2018-03-01) - -* Rename `Route::p()` to `Route::filter()` - -* Better naming for http codes - -* Fix payload parse in situation when socket data is not ready. - -* Fix Session mutable borrow lifetime #87 - - -## 0.4.0 (2018-02-28) - -* Actix 0.5 compatibility - -* Fix request json/urlencoded loaders - -* Simplify HttpServer type definition - -* Added HttpRequest::encoding() method - -* Added HttpRequest::mime_type() method - -* Added HttpRequest::uri_mut(), allows to modify request uri - -* Added StaticFiles::index_file() - -* Added http client - -* Added websocket client - -* Added TestServer::ws(), test websockets client - -* Added TestServer http client support - -* Allow to override content encoding on application level - - -## 0.3.3 (2018-01-25) - -* Stop processing any events after context stop - -* Re-enable write back-pressure for h1 connections - -* Refactor HttpServer::start_ssl() method - -* Upgrade openssl to 0.10 - - -## 0.3.2 (2018-01-21) - -* Fix HEAD requests handling - -* Log request processing errors - -* Always enable content encoding if encoding explicitly selected - -* Allow multiple Applications on a single server with different state #49 - -* CORS middleware: allowed_headers is defaulting to None #50 - - -## 0.3.1 (2018-01-13) - -* Fix directory entry path #47 - -* Do not enable chunked encoding for HTTP/1.0 - -* Allow explicitly disable chunked encoding - - -## 0.3.0 (2018-01-12) - -* HTTP/2 Support - -* Refactor streaming responses - -* Refactor error handling - -* Asynchronous middlewares - -* Refactor logger middleware - -* Content compression/decompression (br, gzip, deflate) - -* Server multi-threading - -* Graceful shutdown support - - -## 0.2.1 (2017-11-03) - -* Allow to start tls server with `HttpServer::serve_tls` - -* Export `Frame` enum - -* Add conversion impl from `HttpResponse` and `BinaryBody` to a `Frame` - - -## 0.2.0 (2017-10-30) - -* Do not use `http::Uri` as it can not parse some valid paths - -* Refactor response `Body` - -* Refactor `RouteRecognizer` usability - -* Refactor `HttpContext::write` - -* Refactor `Payload` stream - -* Re-use `BinaryBody` for `Frame::Payload` - -* Stop http actor on `write_eof` - -* Fix disconnection handling. - - -## 0.1.0 (2017-10-23) - -* First release +* Initial impl diff --git a/Cargo.toml b/Cargo.toml index d70a65cf..258301da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "actix-web" -version = "0.7.9" +name = "actix-http" +version = "0.1.0" authors = ["Nikolay Kim "] -description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." +description = "Actix http" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" @@ -14,7 +14,6 @@ categories = ["network-programming", "asynchronous", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] -build = "build.rs" [package.metadata.docs.rs] features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] @@ -25,7 +24,7 @@ appveyor = { repository = "fafhrd91/actix-web-hdy9d" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" } [lib] -name = "actix_web" +name = "actix_http" path = "src/lib.rs" [features] @@ -130,6 +129,7 @@ webpki-roots = { version = "0.15", optional = true } tokio-uds = { version="0.2", optional = true } [dev-dependencies] +actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" @@ -140,8 +140,3 @@ version_check = "0.1" lto = true opt-level = 3 codegen-units = 1 - -[workspace] -members = [ - "./", -] diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index 3c0bdd94..00000000 --- a/MIGRATION.md +++ /dev/null @@ -1,176 +0,0 @@ -## 0.7.4 - -* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - - -## 0.7 - -* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload - use `HttpMessage::payload()` method. - - instead of - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .from_err() - .fold(...) - .... - } - ``` - - use `.payload()` - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .payload() // <- get request payload stream - .from_err() - .fold(...) - .... - } - ``` - -* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) - trait uses `&HttpRequest` instead of `&mut HttpRequest`. - -* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. - - instead of - - ```rust - fn index(query: Query<..>, info: Json impl Responder {} - ``` - - use tuple of extractors and use `.with()` for registration: - - ```rust - fn index((query, json): (Query<..>, Json impl Responder {} - ``` - -* `Handler::handle()` uses `&self` instead of `&mut self` - -* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value - -* Removed deprecated `HttpServer::threads()`, use - [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. - -* Renamed `client::ClientConnectorError::Connector` to - `client::ClientConnectorError::Resolver` - -* `Route::with()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_config()` - - instead of - - ```rust - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with(index) - .limit(4096); // <- limit size of the payload - }); - } - ``` - - use - - ```rust - - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with_config(index, |cfg| { // <- register handler - cfg.limit(4096); // <- limit size of the payload - }) - }); - } - ``` - -* `Route::with_async()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_async_config()` - - -## 0.6 - -* `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` - -* `ws::Message::Close` now includes optional close reason. - `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. - -* `HttpServer::threads()` renamed to `HttpServer::workers()`. - -* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. - Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. - -* `HttpRequest::extensions()` returns read only reference to the request's Extension - `HttpRequest::extensions_mut()` returns mutable reference. - -* Instead of - - `use actix_web::middleware::{ - CookieSessionBackend, CookieSessionError, RequestSession, - Session, SessionBackend, SessionImpl, SessionStorage};` - - use `actix_web::middleware::session` - - `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, - RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` - -* `FromRequest::from_request()` accepts mutable reference to a request - -* `FromRequest::Result` has to implement `Into>` - -* [`Responder::respond_to()`]( - https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) - is generic over `S` - -* Use `Query` extractor instead of HttpRequest::query()`. - - ```rust - fn index(q: Query>) -> Result<..> { - ... - } - ``` - - or - - ```rust - let q = Query::>::extract(req); - ``` - -* Websocket operations are implemented as `WsWriter` trait. - you need to use `use actix_web::ws::WsWriter` - - -## 0.5 - -* `HttpResponseBuilder::body()`, `.finish()`, `.json()` - methods return `HttpResponse` instead of `Result` - -* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` - moved to `actix_web::http` module - -* `actix_web::header` moved to `actix_web::http::header` - -* `NormalizePath` moved to `actix_web::http` module - -* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, - shortcut for `actix_web::server::HttpServer::new()` - -* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself - -* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. - -* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type - -* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` - functions should be used instead - -* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` - instead of `Result<_, http::Error>` - -* `Application` renamed to a `App` - -* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` diff --git a/Makefile b/Makefile deleted file mode 100644 index e3b8b2cf..00000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.PHONY: default build test doc book clean - -CARGO_FLAGS := --features "$(FEATURES) alpn tls" - -default: test - -build: - cargo build $(CARGO_FLAGS) - -test: build clippy - cargo test $(CARGO_FLAGS) - -doc: build - cargo doc --no-deps $(CARGO_FLAGS) diff --git a/README.md b/README.md index 4e396cb9..b092a172 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,6 @@ -# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Actix web is a simple, pragmatic and extremely fast web framework for Rust. - -* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols -* Streaming and pipelining -* Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/docs/websockets/) support -* Transparent content compression/decompression (br, gzip, deflate) -* Configurable [request routing](https://actix.rs/docs/url-dispatch/) -* Graceful server shutdown -* Multipart streams -* Static assets -* SSL support with OpenSSL or `native-tls` -* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) -* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -* Built on top of [Actix actor framework](https://github.com/actix/actix) +Actix http ## Documentation & community resources @@ -44,30 +30,6 @@ fn main() { } ``` -### More examples - -* [Basics](https://github.com/actix/examples/tree/master/basics/) -* [Stateful](https://github.com/actix/examples/tree/master/state/) -* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) -* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) -* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) -* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / - [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates -* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) -* [r2d2](https://github.com/actix/examples/tree/master/r2d2/) -* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) -* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) -* [Json](https://github.com/actix/examples/tree/master/json/) - -You may consider checking out -[this directory](https://github.com/actix/examples/tree/master/) for more examples. - -## Benchmarks - -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) - -* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). - ## License This project is licensed under either of @@ -80,5 +42,5 @@ at your option. ## Code of Conduct Contribution to the actix-web crate is organized under the terms of the -Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to +Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to intervene to uphold that code of conduct. diff --git a/build.rs b/build.rs deleted file mode 100644 index c8457944..00000000 --- a/build.rs +++ /dev/null @@ -1,16 +0,0 @@ -extern crate version_check; - -fn main() { - match version_check::is_min_version("1.26.0") { - Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"), - _ => (), - }; - match version_check::is_nightly() { - Some(true) => { - println!("cargo:rustc-cfg=actix_nightly"); - println!("cargo:rustc-cfg=actix_impl_trait"); - } - Some(false) => (), - None => (), - }; -} diff --git a/src/application.rs b/src/application.rs deleted file mode 100644 index d8a6cbe7..00000000 --- a/src/application.rs +++ /dev/null @@ -1,879 +0,0 @@ -use std::rc::Rc; - -use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler}; -use header::ContentEncoding; -use http::Method; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use pipeline::{Pipeline, PipelineHandler}; -use pred::Predicate; -use resource::Resource; -use router::{ResourceDef, Router}; -use scope::Scope; -use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; -use with::WithFactory; - -/// Application -pub struct HttpApplication { - state: Rc, - prefix: String, - prefix_len: usize, - inner: Rc>, - filters: Option>>>, - middlewares: Rc>>>, -} - -#[doc(hidden)] -pub struct Inner { - router: Router, - encoding: ContentEncoding, -} - -impl PipelineHandler for Inner { - #[inline] - fn encoding(&self) -> ContentEncoding { - self.encoding - } - - fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.router.handle(req) - } -} - -impl HttpApplication { - #[cfg(test)] - pub(crate) fn run(&self, req: Request) -> AsyncResult { - let info = self - .inner - .router - .recognize(&req, &self.state, self.prefix_len); - let req = HttpRequest::new(req, Rc::clone(&self.state), info); - - self.inner.handle(&req) - } -} - -impl HttpHandler for HttpApplication { - type Task = Pipeline>; - - fn handle(&self, msg: Request) -> Result>, Request> { - let m = { - if self.prefix_len == 0 { - true - } else { - let path = msg.path(); - path.starts_with(&self.prefix) - && (path.len() == self.prefix_len - || path.split_at(self.prefix_len).1.starts_with('/')) - } - }; - if m { - if let Some(ref filters) = self.filters { - for filter in filters { - if !filter.check(&msg, &self.state) { - return Err(msg); - } - } - } - - let info = self - .inner - .router - .recognize(&msg, &self.state, self.prefix_len); - - let inner = Rc::clone(&self.inner); - let req = HttpRequest::new(msg, Rc::clone(&self.state), info); - Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner)) - } else { - Err(msg) - } - } -} - -struct ApplicationParts { - state: S, - prefix: String, - router: Router, - encoding: ContentEncoding, - middlewares: Vec>>, - filters: Vec>>, -} - -/// Structure that follows the builder pattern for building application -/// instances. -pub struct App { - parts: Option>, -} - -impl App<()> { - /// Create application with empty state. Application can - /// be configured with a builder-like pattern. - pub fn new() -> App<()> { - App::with_state(()) - } -} - -impl Default for App<()> { - fn default() -> Self { - App::new() - } -} - -impl App -where - S: 'static, -{ - /// Create application with specified state. Application can be - /// configured with a builder-like pattern. - /// - /// State is shared with all resources within same application and - /// could be accessed with `HttpRequest::state()` method. - /// - /// **Note**: http server accepts an application factory rather than - /// an application instance. Http server constructs an application - /// instance for each thread, thus application state must be constructed - /// multiple times. If you want to share state between different - /// threads, a shared object should be used, e.g. `Arc`. Application - /// state does not need to be `Send` or `Sync`. - pub fn with_state(state: S) -> App { - App { - parts: Some(ApplicationParts { - state, - prefix: "".to_owned(), - router: Router::new(ResourceDef::prefix("")), - middlewares: Vec::new(), - filters: Vec::new(), - encoding: ContentEncoding::Auto, - }), - } - } - - /// Get reference to the application state - pub fn state(&self) -> &S { - let parts = self.parts.as_ref().expect("Use after finish"); - &parts.state - } - - /// Set application prefix. - /// - /// Only requests that match the application's prefix get - /// processed by this application. - /// - /// The application prefix always contains a leading slash (`/`). - /// If the supplied prefix does not contain leading slash, it is - /// inserted. - /// - /// Prefix should consist of valid path segments. i.e for an - /// application with the prefix `/app` any request with the paths - /// `/app`, `/app/` or `/app/test` would match, but the path - /// `/application` would not. - /// - /// In the following example only requests with an `/app/` path - /// prefix get handled. Requests with path `/app/test/` would be - /// handled, while requests with the paths `/application` or - /// `/other/...` would return `NOT FOUND`. It is also possible to - /// handle `/app` path, to do this you can register resource for - /// empty string `""` - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .prefix("/app") - /// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path - /// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path - /// .resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .finish(); - /// } - /// ``` - pub fn prefix>(mut self, prefix: P) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - let mut prefix = prefix.into(); - if !prefix.starts_with('/') { - prefix.insert(0, '/') - } - parts.router.set_prefix(&prefix); - parts.prefix = prefix; - } - self - } - - /// Add match predicate to application. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn main() { - /// App::new() - /// .filter(pred::Host("www.rust-lang.org")) - /// .resource("/path", |r| r.f(|_| HttpResponse::Ok())) - /// # .finish(); - /// # } - /// ``` - pub fn filter + 'static>(mut self, p: T) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.filters.push(Box::new(p)); - } - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `App::resource()` method. - /// Handler functions need to accept one request extractor - /// argument. - /// - /// This method could be called multiple times, in that case - /// multiple routes would be registered for same resource path. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .route("/test", http::Method::GET, |_: HttpRequest| { - /// HttpResponse::Ok() - /// }) - /// .route("/test", http::Method::POST, |_: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// }); - /// } - /// ``` - pub fn route(mut self, path: &str, method: Method, f: F) -> App - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_route(path, method, f); - - self - } - - /// Configure scope for common root path. - /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/{project_id}", |scope| { - /// scope - /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) - /// }); - /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(mut self, path: &str, f: F) -> App - where - F: FnOnce(Scope) -> Scope, - { - let scope = f(Scope::new(path)); - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_scope(scope); - self - } - - /// Configure resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> App - where - F: FnOnce(&mut Resource) -> R + 'static, - { - { - let parts = self.parts.as_mut().expect("Use after finish"); - - // create resource - let mut resource = Resource::new(ResourceDef::new(path)); - - // configure - f(&mut resource); - - parts.router.register_resource(resource); - } - self - } - - /// Configure resource for a specific path. - #[doc(hidden)] - pub fn register_resource(&mut self, resource: Resource) { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_resource(resource); - } - - /// Default resource to be used if no matching route could be found. - pub fn default_resource(mut self, f: F) -> App - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // create and configure default resource - let mut resource = Resource::new(ResourceDef::new("")); - f(&mut resource); - - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_default_resource(resource.into()); - - self - } - - /// Set default content encoding. `ContentEncoding::Auto` is set by default. - pub fn default_encoding(mut self, encoding: ContentEncoding) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.encoding = encoding; - } - self - } - - /// Register an external resource. - /// - /// External resources are useful for URL generation purposes only - /// and are never considered for matching at request time. Calls to - /// `HttpRequest::url_for()` will work as expected. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest, HttpResponse, Result}; - /// - /// fn index(req: &HttpRequest) -> Result { - /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; - /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); - /// Ok(HttpResponse::Ok().into()) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .resource("/index.html", |r| r.get().f(index)) - /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") - /// .finish(); - /// } - /// ``` - pub fn external_resource(mut self, name: T, url: U) -> App - where - T: AsRef, - U: AsRef, - { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_external(name.as_ref(), ResourceDef::external(url.as_ref())); - self - } - - /// Configure handler for specific path prefix. - /// - /// A path prefix consists of valid path segments, i.e for the - /// prefix `/app` any request with the paths `/app`, `/app/` or - /// `/app/test` would match, but the path `/application` would - /// not. - /// - /// Path tail is available as `tail` parameter in request's match_dict. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().handler("/app", |req: &HttpRequest| match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }); - /// } - /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> App { - { - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); - } - self - } - - /// Register a middleware. - pub fn middleware>(mut self, mw: M) -> App { - self.parts - .as_mut() - .expect("Use after finish") - .middlewares - .push(Box::new(mw)); - self - } - - /// Run external configuration as part of the application building - /// process - /// - /// This function is useful for moving parts of configuration to a - /// different module or event library. For example we can move - /// some of the resources' configuration to different module. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{fs, middleware, App, HttpResponse}; - /// - /// // this function could be located in different module - /// fn config(app: App) -> App { - /// app.resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .middleware(middleware::Logger::default()) - /// .configure(config) // <- register resources - /// .handler("/static", fs::StaticFiles::new(".").unwrap()); - /// } - /// ``` - pub fn configure(self, cfg: F) -> App - where - F: Fn(App) -> App, - { - cfg(self) - } - - /// Finish application configuration and create `HttpHandler` object. - pub fn finish(&mut self) -> HttpApplication { - let mut parts = self.parts.take().expect("Use after finish"); - let prefix = parts.prefix.trim().trim_right_matches('/'); - parts.router.finish(); - - let inner = Rc::new(Inner { - router: parts.router, - encoding: parts.encoding, - }); - let filters = if parts.filters.is_empty() { - None - } else { - Some(parts.filters) - }; - - HttpApplication { - inner, - filters, - state: Rc::new(parts.state), - middlewares: Rc::new(parts.middlewares), - prefix: prefix.to_owned(), - prefix_len: prefix.len(), - } - } - - /// Convenience method for creating `Box` instances. - /// - /// This method is useful if you need to register multiple - /// application instances with different state. - /// - /// ```rust - /// # use std::thread; - /// # extern crate actix_web; - /// use actix_web::{server, App, HttpResponse}; - /// - /// struct State1; - /// - /// struct State2; - /// - /// fn main() { - /// # thread::spawn(|| { - /// server::new(|| { - /// vec![ - /// App::with_state(State1) - /// .prefix("/app1") - /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - /// .boxed(), - /// App::with_state(State2) - /// .prefix("/app2") - /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - /// .boxed(), - /// ] - /// }).bind("127.0.0.1:8080") - /// .unwrap() - /// .run() - /// # }); - /// } - /// ``` - pub fn boxed(mut self) -> Box>> { - Box::new(BoxedApplication { app: self.finish() }) - } -} - -struct BoxedApplication { - app: HttpApplication, -} - -impl HttpHandler for BoxedApplication { - type Task = Box; - - fn handle(&self, req: Request) -> Result { - self.app.handle(req).map(|t| { - let task: Self::Task = Box::new(t); - task - }) - } -} - -impl IntoHttpHandler for App { - type Handler = HttpApplication; - - fn into_handler(mut self) -> HttpApplication { - self.finish() - } -} - -impl<'a, S: 'static> IntoHttpHandler for &'a mut App { - type Handler = HttpApplication; - - fn into_handler(self) -> HttpApplication { - self.finish() - } -} - -#[doc(hidden)] -impl Iterator for App { - type Item = HttpApplication; - - fn next(&mut self) -> Option { - if self.parts.is_some() { - Some(self.finish()) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use body::{Binary, Body}; - use http::StatusCode; - use httprequest::HttpRequest; - use httpresponse::HttpResponse; - use pred; - use test::{TestRequest, TestServer}; - - #[test] - fn test_default_resource() { - let app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) - .finish(); - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_unhandled_prefix() { - let app = App::new() - .prefix("/test") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let ctx = TestRequest::default().request(); - assert!(app.handle(ctx).is_err()); - } - - #[test] - fn test_state() { - let app = App::with_state(10) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let req = TestRequest::with_state(10).request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_prefix() { - let app = App::new() - .prefix("/test") - .resource("/blah", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let req = TestRequest::with_uri("/test").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/test/blah").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/testing").request(); - let resp = app.handle(req); - assert!(resp.is_err()); - } - - #[test] - fn test_handler() { - let app = App::new() - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler2() { - let app = App::new() - .handler("test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler_with_prefix() { - let app = App::new() - .prefix("prefix") - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/prefix/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/prefix/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_route() { - let app = App::new() - .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) - .route("/test", Method::POST, |_: HttpRequest| { - HttpResponse::Created() - }).finish(); - - let req = TestRequest::with_uri("/test").method(Method::GET).request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler_prefix() { - let app = App::new() - .prefix("/app") - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_option_responder() { - let app = App::new() - .resource("/none", |r| r.f(|_| -> Option<&'static str> { None })) - .resource("/some", |r| r.f(|_| Some("some"))) - .finish(); - - let req = TestRequest::with_uri("/none").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/some").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); - } - - #[test] - fn test_filter() { - let mut srv = TestServer::with_factory(|| { - App::new() - .filter(pred::Get()) - .handler("/test", |_: &_| HttpResponse::Ok()) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv.post().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_prefix_root() { - let mut srv = TestServer::with_factory(|| { - App::new() - .prefix("/test") - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .resource("", |r| r.f(|_| HttpResponse::Created())) - }); - - let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::CREATED); - } - -} diff --git a/src/body.rs b/src/body.rs index a93db1e9..e689b704 100644 --- a/src/body.rs +++ b/src/body.rs @@ -3,10 +3,7 @@ use futures::Stream; use std::sync::Arc; use std::{fmt, mem}; -use context::ActorHttpContext; use error::Error; -use handler::Responder; -use httprequest::HttpRequest; use httpresponse::HttpResponse; /// Type represent streaming body @@ -21,8 +18,8 @@ pub enum Body { /// Unspecified streaming response. Developer is responsible for setting /// right `Content-Length` or `Transfer-Encoding` headers. Streaming(BodyStream), - /// Special body type for actor response. - Actor(Box), + // /// Special body type for actor response. + // Actor(Box), } /// Represents various types of binary body. @@ -45,7 +42,7 @@ impl Body { #[inline] pub fn is_streaming(&self) -> bool { match *self { - Body::Streaming(_) | Body::Actor(_) => true, + Body::Streaming(_) => true, _ => false, } } @@ -94,7 +91,7 @@ impl PartialEq for Body { Body::Binary(ref b2) => b == b2, _ => false, }, - Body::Streaming(_) | Body::Actor(_) => false, + Body::Streaming(_) => false, } } } @@ -105,7 +102,6 @@ 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::Actor(_) => write!(f, "Body::Actor(_)"), } } } @@ -119,12 +115,6 @@ where } } -impl From> for Body { - fn from(ctx: Box) -> Body { - Body::Actor(ctx) - } -} - impl Binary { #[inline] /// Returns `true` if body is empty @@ -254,17 +244,6 @@ impl AsRef<[u8]> for Binary { } } -impl Responder for Binary { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(HttpResponse::build_from(req) - .content_type("application/octet-stream") - .body(self)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/client/connector.rs b/src/client/connector.rs deleted file mode 100644 index 07c7b646..00000000 --- a/src/client/connector.rs +++ /dev/null @@ -1,1344 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use std::net::Shutdown; -use std::time::{Duration, Instant}; -use std::{fmt, io, mem, time}; - -use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; -use actix::{ - fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context, - ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, - SystemService, WrapFuture, -}; - -use futures::sync::{mpsc, oneshot}; -use futures::{Async, Future, Poll}; -use http::{Error as HttpError, HttpTryFrom, Uri}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -#[cfg(any(feature = "alpn", feature = "ssl"))] -use { - openssl::ssl::{Error as SslError, SslConnector, SslMethod}, - tokio_openssl::SslConnectorExt, -}; - -#[cfg(all( - feature = "tls", - not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) -))] -use { - native_tls::{Error as SslError, TlsConnector as NativeTlsConnector}, - tokio_tls::TlsConnector as SslConnector, -}; - -#[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) -))] -use { - rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc, - tokio_rustls::ClientConfigExt, webpki::DNSNameRef, webpki_roots, -}; - -#[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) -))] -type SslConnector = Arc; - -#[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls" -)))] -type SslConnector = (); - -use server::IoStream; -use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS}; - -/// Client connector usage stats -#[derive(Default, Message)] -pub struct ClientConnectorStats { - /// Number of waited-on connections - pub waits: usize, - /// Size of the wait queue - pub wait_queue: usize, - /// Number of reused connections - pub reused: usize, - /// Number of opened connections - pub opened: usize, - /// Number of closed connections - pub closed: usize, - /// Number of connections with errors - pub errors: usize, - /// Number of connection timeouts - pub timeouts: usize, -} - -#[derive(Debug)] -/// `Connect` type represents a message that can be sent to -/// `ClientConnector` with a connection request. -pub struct Connect { - pub(crate) uri: Uri, - pub(crate) wait_timeout: Duration, - pub(crate) conn_timeout: Duration, -} - -impl Connect { - /// Create `Connect` message for specified `Uri` - pub fn new(uri: U) -> Result - where - Uri: HttpTryFrom, - { - Ok(Connect { - uri: Uri::try_from(uri).map_err(|e| e.into())?, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - }) - } - - /// Connection timeout, i.e. max time to connect to remote host. - /// Set to 1 second by default. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - self.conn_timeout = timeout; - self - } - - /// If connection pool limits are enabled, wait time indicates - /// max time to wait for a connection to become available. - /// Set to 5 seconds by default. - pub fn wait_timeout(mut self, timeout: Duration) -> Self { - self.wait_timeout = timeout; - self - } -} - -impl Message for Connect { - type Result = Result; -} - -/// Pause connection process for `ClientConnector` -/// -/// All connect requests enter wait state during connector pause. -pub struct Pause { - time: Option, -} - -impl Pause { - /// Create message with pause duration parameter - pub fn new(time: Duration) -> Pause { - Pause { time: Some(time) } - } -} - -impl Default for Pause { - fn default() -> Pause { - Pause { time: None } - } -} - -impl Message for Pause { - type Result = (); -} - -/// Resume connection process for `ClientConnector` -#[derive(Message)] -pub struct Resume; - -/// A set of errors that can occur while connecting to an HTTP host -#[derive(Fail, Debug)] -pub enum ClientConnectorError { - /// Invalid URL - #[fail(display = "Invalid URL")] - InvalidUrl, - - /// SSL feature is not enabled - #[fail(display = "SSL is not supported")] - SslIsNotSupported, - - /// SSL error - #[cfg(any( - feature = "tls", - feature = "alpn", - feature = "ssl", - feature = "rust-tls", - ))] - #[fail(display = "{}", _0)] - SslError(#[cause] SslError), - - /// Resolver error - #[fail(display = "{}", _0)] - Resolver(#[cause] ResolverError), - - /// Connection took too long - #[fail(display = "Timeout while establishing connection")] - Timeout, - - /// Connector has been disconnected - #[fail(display = "Internal error: connector has been disconnected")] - Disconnected, - - /// Connection IO error - #[fail(display = "{}", _0)] - IoError(#[cause] io::Error), -} - -impl From for ClientConnectorError { - fn from(err: ResolverError) -> ClientConnectorError { - match err { - ResolverError::Timeout => ClientConnectorError::Timeout, - _ => ClientConnectorError::Resolver(err), - } - } -} - -struct Waiter { - tx: oneshot::Sender>, - wait: Instant, - conn_timeout: Duration, -} - -enum Paused { - No, - Yes, - Timeout(Instant, Delay), -} - -impl Paused { - fn is_paused(&self) -> bool { - match *self { - Paused::No => false, - _ => true, - } - } -} - -/// `ClientConnector` type is responsible for transport layer of a -/// client connection. -pub struct ClientConnector { - #[allow(dead_code)] - connector: SslConnector, - - stats: ClientConnectorStats, - subscriber: Option>, - - acq_tx: mpsc::UnboundedSender, - acq_rx: Option>, - - resolver: Option>, - conn_lifetime: Duration, - conn_keep_alive: Duration, - limit: usize, - limit_per_host: usize, - acquired: usize, - acquired_per_host: HashMap, - available: HashMap>, - to_close: Vec, - waiters: Option>>, - wait_timeout: Option<(Instant, Delay)>, - paused: Paused, -} - -impl Actor for ClientConnector { - type Context = Context; - - fn started(&mut self, ctx: &mut Self::Context) { - if self.resolver.is_none() { - self.resolver = Some(Resolver::from_registry()) - } - self.collect_periodic(ctx); - ctx.add_stream(self.acq_rx.take().unwrap()); - ctx.spawn(Maintenance); - } -} - -impl Supervised for ClientConnector {} - -impl SystemService for ClientConnector {} - -impl Default for ClientConnector { - fn default() -> ClientConnector { - let connector = { - #[cfg(all(any(feature = "alpn", feature = "ssl")))] - { - SslConnector::builder(SslMethod::tls()).unwrap().build() - } - - #[cfg(all( - feature = "tls", - not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) - ))] - { - NativeTlsConnector::builder().build().unwrap().into() - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) - ))] - { - let mut config = ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - Arc::new(config) - } - - #[cfg_attr(rustfmt, rustfmt_skip)] - #[cfg(not(any( - feature = "alpn", feature = "ssl", feature = "tls", feature = "rust-tls")))] - { - () - } - }; - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_unit_value))] - ClientConnector::with_connector_impl(connector) - } -} - -impl ClientConnector { - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust,ignore - /// # #![cfg(feature="alpn")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate openssl; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// use openssl::ssl::{SslConnector, SslMethod}; - /// - /// fn main() { - /// actix::run(|| { - /// // Start `ClientConnector` with custom `SslConnector` - /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn = ClientConnector::with_connector(ssl_conn).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: SslConnector) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(connector) - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl", feature = "tls")) - ))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust - /// # #![cfg(feature = "rust-tls")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate rustls; - /// extern crate webpki_roots; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// use rustls::ClientConfig; - /// use std::sync::Arc; - /// - /// fn main() { - /// actix::run(|| { - /// // Start `ClientConnector` with custom `ClientConfig` - /// let mut config = ClientConfig::new(); - /// config - /// .root_store - /// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - /// let conn = ClientConnector::with_connector(Arc::new(config)).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: ClientConfig) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(Arc::new(connector)) - } - - #[cfg(all( - feature = "tls", - not(any(feature = "ssl", feature = "alpn", feature = "rust-tls")) - ))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust - /// # #![cfg(feature = "tls")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate native_tls; - /// extern crate webpki_roots; - /// use native_tls::TlsConnector; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// fn main() { - /// actix::run(|| { - /// let connector = TlsConnector::new().unwrap(); - /// let conn = ClientConnector::with_connector(connector.into()).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: SslConnector) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(connector) - } - - #[inline] - fn with_connector_impl(connector: SslConnector) -> ClientConnector { - let (tx, rx) = mpsc::unbounded(); - - ClientConnector { - connector, - stats: ClientConnectorStats::default(), - subscriber: None, - acq_tx: tx, - acq_rx: Some(rx), - resolver: None, - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: Some(HashMap::new()), - wait_timeout: None, - paused: Paused::No, - } - } - - /// Set total number of simultaneous connections. - /// - /// If limit is 0, the connector has no limit. - /// The default limit size is 100. - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set total number of simultaneous connections to the same endpoint. - /// - /// Endpoints are the same if they have equal (host, port, ssl) triplets. - /// If limit is 0, the connector has no limit. The default limit size is 0. - pub fn limit_per_host(mut self, limit: usize) -> Self { - self.limit_per_host = limit; - self - } - - /// Set keep-alive period for opened connection. - /// - /// Keep-alive period is the period between connection usage. If - /// the delay between repeated usages of the same connection - /// exceeds this period, the connection is closed. - /// Default keep-alive period is 15 seconds. - pub fn conn_keep_alive(mut self, dur: Duration) -> Self { - self.conn_keep_alive = dur; - self - } - - /// Set max lifetime period for connection. - /// - /// Connection lifetime is max lifetime of any opened connection - /// until it is closed regardless of keep-alive period. - /// Default lifetime period is 75 seconds. - pub fn conn_lifetime(mut self, dur: Duration) -> Self { - self.conn_lifetime = dur; - self - } - - /// Subscribe for connector stats. Only one subscriber is supported. - pub fn stats(mut self, subs: Recipient) -> Self { - self.subscriber = Some(subs); - self - } - - /// Use custom resolver actor - pub fn resolver(mut self, addr: Addr) -> Self { - self.resolver = Some(addr); - self - } - - fn acquire(&mut self, key: &Key) -> Acquire { - // check limits - if self.limit > 0 { - if self.acquired >= self.limit { - return Acquire::NotAvailable; - } - if self.limit_per_host > 0 { - if let Some(per_host) = self.acquired_per_host.get(key) { - if *per_host >= self.limit_per_host { - return Acquire::NotAvailable; - } - } - } - } else if self.limit_per_host > 0 { - if let Some(per_host) = self.acquired_per_host.get(key) { - if *per_host >= self.limit_per_host { - return Acquire::NotAvailable; - } - } - } - - self.reserve(key); - - // check if open connection is available - // cleanup stale connections at the same time - if let Some(ref mut connections) = self.available.get_mut(key) { - let now = Instant::now(); - while let Some(conn) = connections.pop_back() { - // check if it still usable - if (now - conn.0) > self.conn_keep_alive - || (now - conn.1.ts) > self.conn_lifetime - { - self.stats.closed += 1; - self.to_close.push(conn.1); - } else { - let mut conn = conn.1; - let mut buf = [0; 2]; - match conn.stream().read(&mut buf) { - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Ok(n) if n > 0 => { - self.stats.closed += 1; - self.to_close.push(conn); - continue; - } - Ok(_) | Err(_) => continue, - } - return Acquire::Acquired(conn); - } - } - } - Acquire::Available - } - - fn reserve(&mut self, key: &Key) { - self.acquired += 1; - let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - 0 - }; - self.acquired_per_host.insert(key.clone(), per_host + 1); - } - - fn release_key(&mut self, key: &Key) { - if self.acquired > 0 { - self.acquired -= 1; - } - let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - return; - }; - if per_host > 1 { - self.acquired_per_host.insert(key.clone(), per_host - 1); - } else { - self.acquired_per_host.remove(key); - } - } - - fn collect_periodic(&mut self, ctx: &mut Context) { - // check connections for shutdown - let mut idx = 0; - while idx < self.to_close.len() { - match AsyncWrite::shutdown(&mut self.to_close[idx]) { - Ok(Async::NotReady) => idx += 1, - _ => { - self.to_close.swap_remove(idx); - } - } - } - - // re-schedule next collect period - ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); - - // send stats - let mut stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); - if let Some(ref mut subscr) = self.subscriber { - if let Some(ref waiters) = self.waiters { - for w in waiters.values() { - stats.wait_queue += w.len(); - } - } - let _ = subscr.do_send(stats); - } - } - - // TODO: waiters should be sorted by deadline. maybe timewheel? - fn collect_waiters(&mut self) { - let now = Instant::now(); - let mut next = None; - - for waiters in self.waiters.as_mut().unwrap().values_mut() { - let mut idx = 0; - while idx < waiters.len() { - let wait = waiters[idx].wait; - if wait <= now { - self.stats.timeouts += 1; - let waiter = waiters.swap_remove_back(idx).unwrap(); - let _ = waiter.tx.send(Err(ClientConnectorError::Timeout)); - } else { - if let Some(n) = next { - if wait < n { - next = Some(wait); - } - } else { - next = Some(wait); - } - idx += 1; - } - } - } - - if next.is_some() { - self.install_wait_timeout(next.unwrap()); - } - } - - fn install_wait_timeout(&mut self, time: Instant) { - if let Some(ref mut wait) = self.wait_timeout { - if wait.0 < time { - return; - } - } - - let mut timeout = Delay::new(time); - let _ = timeout.poll(); - self.wait_timeout = Some((time, timeout)); - } - - fn wait_for( - &mut self, key: Key, wait: Duration, conn_timeout: Duration, - ) -> oneshot::Receiver> { - // connection is not available, wait - let (tx, rx) = oneshot::channel(); - - let wait = Instant::now() + wait; - self.install_wait_timeout(wait); - - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.waiters - .as_mut() - .unwrap() - .entry(key) - .or_insert_with(VecDeque::new) - .push_back(waiter); - rx - } - - fn check_availibility(&mut self, ctx: &mut Context) { - // check waiters - let mut act_waiters = self.waiters.take().unwrap(); - - for (key, ref mut waiters) in &mut act_waiters { - while let Some(waiter) = waiters.pop_front() { - if waiter.tx.is_canceled() { - continue; - } - - match self.acquire(key) { - Acquire::Acquired(mut conn) => { - // use existing connection - self.stats.reused += 1; - conn.pool = - Some(AcquiredConn(key.clone(), Some(self.acq_tx.clone()))); - let _ = waiter.tx.send(Ok(conn)); - } - Acquire::NotAvailable => { - waiters.push_front(waiter); - break; - } - Acquire::Available => { - // create new connection - self.connect_waiter(&key, waiter, ctx); - } - } - } - } - - self.waiters = Some(act_waiters); - } - - fn connect_waiter(&mut self, key: &Key, waiter: Waiter, ctx: &mut Context) { - let key = key.clone(); - let conn = AcquiredConn(key.clone(), Some(self.acq_tx.clone())); - - let key2 = key.clone(); - fut::WrapFuture::::actfuture( - self.resolver.as_ref().unwrap().send( - ResolveConnect::host_and_port(&conn.0.host, conn.0.port) - .timeout(waiter.conn_timeout), - ), - ).map_err(move |_, act, _| { - act.release_key(&key2); - () - }).and_then(move |res, act, _| { - #[cfg(any(feature = "alpn", feature = "ssl"))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&key.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all(feature = "tls", not(any(feature = "alpn", feature = "ssl"))))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect(&conn.0.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl", feature = "tls")) - ))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap(); - fut::Either::A( - act.connector - .connect_async(host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls" - )))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::err(()) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let _ = - waiter.tx.send(Err(ClientConnectorError::SslIsNotSupported)); - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - }; - fut::ok(()) - } - } - }).spawn(ctx); - } -} - -impl Handler for ClientConnector { - type Result = (); - - fn handle(&mut self, msg: Pause, _: &mut Self::Context) { - if let Some(time) = msg.time { - let when = Instant::now() + time; - let mut timeout = Delay::new(when); - let _ = timeout.poll(); - self.paused = Paused::Timeout(when, timeout); - } else { - self.paused = Paused::Yes; - } - } -} - -impl Handler for ClientConnector { - type Result = (); - - fn handle(&mut self, _: Resume, _: &mut Self::Context) { - self.paused = Paused::No; - } -} - -impl Handler for ClientConnector { - type Result = ActorResponse; - - fn handle(&mut self, msg: Connect, ctx: &mut Self::Context) -> Self::Result { - let uri = &msg.uri; - let wait_timeout = msg.wait_timeout; - let conn_timeout = msg.conn_timeout; - - // host name is required - if uri.host().is_none() { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)); - } - - // supported protocols - let proto = match uri.scheme_part() { - Some(scheme) => match Protocol::from(scheme.as_str()) { - Some(proto) => proto, - None => { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)) - } - }, - None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)), - }; - - // check ssl availability - if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS && !HAS_RUSTLS { - return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)); - } - - let host = uri.host().unwrap().to_owned(); - let port = uri.port().unwrap_or_else(|| proto.port()); - let key = Key { - host, - port, - ssl: proto.is_secure(), - }; - - // check pause state - if self.paused.is_paused() { - let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); - self.stats.waits += 1; - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - match err { - ClientConnectorError::Timeout => (), - _ => { - act.release_key(&key); - } - } - act.stats.errors += 1; - act.check_availibility(ctx); - fut::err(err) - } - }), - ); - } - - // do not re-use websockets connection - if !proto.is_http() { - let (tx, rx) = oneshot::channel(); - let wait = Instant::now() + wait_timeout; - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.connect_waiter(&key, waiter, ctx); - - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - act.stats.errors += 1; - act.release_key(&key); - act.check_availibility(ctx); - fut::err(err) - } - }), - ); - } - - // acquire connection - match self.acquire(&key) { - Acquire::Acquired(mut conn) => { - // use existing connection - conn.pool = Some(AcquiredConn(key, Some(self.acq_tx.clone()))); - self.stats.reused += 1; - ActorResponse::async(fut::ok(conn)) - } - Acquire::NotAvailable => { - // connection is not available, wait - let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); - self.stats.waits += 1; - - ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - match err { - ClientConnectorError::Timeout => (), - _ => { - act.release_key(&key); - } - } - act.stats.errors += 1; - act.check_availibility(ctx); - fut::err(err) - } - }), - ) - } - Acquire::Available => { - let (tx, rx) = oneshot::channel(); - let wait = Instant::now() + wait_timeout; - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.connect_waiter(&key, waiter, ctx); - - ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - act.stats.errors += 1; - act.release_key(&key); - act.check_availibility(ctx); - fut::err(err) - } - }), - ) - } - } - } -} - -impl StreamHandler for ClientConnector { - fn handle(&mut self, msg: AcquiredConnOperation, ctx: &mut Context) { - match msg { - AcquiredConnOperation::Close(conn) => { - self.release_key(&conn.key); - self.to_close.push(conn); - self.stats.closed += 1; - } - AcquiredConnOperation::Release(conn) => { - self.release_key(&conn.key); - if (Instant::now() - conn.ts) < self.conn_lifetime { - self.available - .entry(conn.key.clone()) - .or_insert_with(VecDeque::new) - .push_back(Conn(Instant::now(), conn)); - } else { - self.to_close.push(conn); - self.stats.closed += 1; - } - } - AcquiredConnOperation::ReleaseKey(key) => { - // closed - self.stats.closed += 1; - self.release_key(&key); - } - } - - self.check_availibility(ctx); - } -} - -struct Maintenance; - -impl fut::ActorFuture for Maintenance { - type Item = (); - type Error = (); - type Actor = ClientConnector; - - fn poll( - &mut self, act: &mut ClientConnector, ctx: &mut Context, - ) -> Poll { - // check pause duration - if let Paused::Timeout(inst, _) = act.paused { - if inst <= Instant::now() { - act.paused = Paused::No; - } - } - - // collect wait timers - act.collect_waiters(); - - // check waiters - act.check_availibility(ctx); - - Ok(Async::NotReady) - } -} - -#[derive(PartialEq, Hash, Debug, Clone, Copy)] -enum Protocol { - Http, - Https, - Ws, - Wss, -} - -impl Protocol { - fn from(s: &str) -> Option { - match s { - "http" => Some(Protocol::Http), - "https" => Some(Protocol::Https), - "ws" => Some(Protocol::Ws), - "wss" => Some(Protocol::Wss), - _ => None, - } - } - - fn is_http(self) -> bool { - match self { - Protocol::Https | Protocol::Http => true, - _ => false, - } - } - - fn is_secure(self) -> bool { - match self { - Protocol::Https | Protocol::Wss => true, - _ => false, - } - } - - fn port(self) -> u16 { - match self { - Protocol::Http | Protocol::Ws => 80, - Protocol::Https | Protocol::Wss => 443, - } - } -} - -#[derive(Hash, Eq, PartialEq, Clone, Debug)] -struct Key { - host: String, - port: u16, - ssl: bool, -} - -impl Key { - fn empty() -> Key { - Key { - host: String::new(), - port: 0, - ssl: false, - } - } -} - -#[derive(Debug)] -struct Conn(Instant, Connection); - -enum Acquire { - Acquired(Connection), - Available, - NotAvailable, -} - -enum AcquiredConnOperation { - Close(Connection), - Release(Connection), - ReleaseKey(Key), -} - -struct AcquiredConn(Key, Option>); - -impl AcquiredConn { - fn close(&mut self, conn: Connection) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::Close(conn)); - } - } - fn release(&mut self, conn: Connection) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::Release(conn)); - } - } -} - -impl Drop for AcquiredConn { - fn drop(&mut self) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::ReleaseKey(self.0.clone())); - } - } -} - -/// HTTP client connection -pub struct Connection { - key: Key, - stream: Box, - pool: Option, - ts: Instant, -} - -impl fmt::Debug for Connection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Connection {}:{}", self.key.host, self.key.port) - } -} - -impl Connection { - fn new(key: Key, pool: Option, stream: Box) -> Self { - Connection { - key, - stream, - pool, - ts: Instant::now(), - } - } - - /// Raw IO stream - pub fn stream(&mut self) -> &mut IoStream { - &mut *self.stream - } - - /// Create a new connection from an IO Stream - /// - /// The stream can be a `UnixStream` if the Unix-only "uds" feature is enabled. - /// - /// See also `ClientRequestBuilder::with_connection()`. - pub fn from_stream(io: T) -> Connection { - Connection::new(Key::empty(), None, Box::new(io)) - } - - /// Close connection - pub fn close(mut self) { - if let Some(mut pool) = self.pool.take() { - pool.close(self) - } - } - - /// Release this connection to the connection pool - pub fn release(mut self) { - if let Some(mut pool) = self.pool.take() { - pool.release(self) - } - } -} - -impl IoStream for Connection { - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - IoStream::shutdown(&mut *self.stream, how) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - IoStream::set_nodelay(&mut *self.stream, nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - IoStream::set_linger(&mut *self.stream, dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - IoStream::set_keepalive(&mut *self.stream, dur) - } -} - -impl io::Read for Connection { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.stream.read(buf) - } -} - -impl AsyncRead for Connection {} - -impl io::Write for Connection { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.stream.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.stream.flush() - } -} - -impl AsyncWrite for Connection { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.stream.shutdown() - } -} - -#[cfg(feature = "tls")] -use tokio_tls::TlsStream; - -#[cfg(feature = "tls")] -/// This is temp solution untile actix-net migration -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) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index a0713fe3..00000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! Http client api -//! -//! ```rust -//! # extern crate actix_web; -//! # extern crate futures; -//! # extern crate tokio; -//! # use futures::Future; -//! # use std::process; -//! use actix_web::{actix, client}; -//! -//! fn main() { -//! actix::run( -//! || client::get("http://www.rust-lang.org") // <- Create request builder -//! .header("User-Agent", "Actix-web") -//! .finish().unwrap() -//! .send() // <- Send http request -//! .map_err(|_| ()) -//! .and_then(|response| { // <- server http response -//! println!("Response: {:?}", response); -//! # actix::System::current().stop(); -//! Ok(()) -//! }) -//! ); -//! } -//! ``` -mod connector; -mod parser; -mod pipeline; -mod request; -mod response; -mod writer; - -pub use self::connector::{ - ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection, - Pause, Resume, -}; -pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; -pub(crate) use self::pipeline::Pipeline; -pub use self::pipeline::{SendRequest, SendRequestError}; -pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; -pub(crate) use self::writer::HttpClientWriter; - -use error::ResponseError; -use http::Method; -use httpresponse::HttpResponse; - -/// Convert `SendRequestError` to a `HttpResponse` -impl ResponseError for SendRequestError { - fn error_response(&self) -> HttpResponse { - match *self { - SendRequestError::Timeout => HttpResponse::GatewayTimeout(), - SendRequestError::Connector(_) => HttpResponse::BadGateway(), - _ => HttpResponse::InternalServerError(), - }.into() - } -} - -/// Create request builder for `GET` requests -/// -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # extern crate tokio; -/// # extern crate env_logger; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::{actix, client}; -/// -/// fn main() { -/// actix::run( -/// || client::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); -/// } -/// ``` -pub fn get>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder -} - -/// Create request builder for `HEAD` requests -pub fn head>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder -} - -/// Create request builder for `POST` requests -pub fn post>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder -} - -/// Create request builder for `PUT` requests -pub fn put>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder -} - -/// Create request builder for `DELETE` requests -pub fn delete>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder -} diff --git a/src/client/parser.rs b/src/client/parser.rs deleted file mode 100644 index 11252fa5..00000000 --- a/src/client/parser.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::mem; - -use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{HeaderMap, StatusCode, Version}; -use httparse; - -use error::{ParseError, PayloadError}; - -use server::h1decoder::{EncodingDecoder, HeaderIndex}; -use server::IoStream; - -use super::response::ClientMessage; -use super::ClientResponse; - -const MAX_BUFFER_SIZE: usize = 131_072; -const MAX_HEADERS: usize = 96; - -#[derive(Default)] -pub struct HttpResponseParser { - decoder: Option, - eof: bool, // indicate that we read payload until stream eof -} - -#[derive(Debug, Fail)] -pub enum HttpResponseParserError { - /// Server disconnected - #[fail(display = "Server disconnected")] - Disconnect, - #[fail(display = "{}", _0)] - Error(#[cause] ParseError), -} - -impl HttpResponseParser { - pub fn parse( - &mut self, io: &mut T, buf: &mut BytesMut, - ) -> Poll - where - T: IoStream, - { - loop { - // Don't call parser until we have data to parse. - if !buf.is_empty() { - match HttpResponseParser::parse_message(buf) - .map_err(HttpResponseParserError::Error)? - { - Async::Ready((msg, info)) => { - if let Some((decoder, eof)) = info { - self.eof = eof; - self.decoder = Some(decoder); - } else { - self.eof = false; - self.decoder = None; - } - return Ok(Async::Ready(msg)); - } - Async::NotReady => { - if buf.capacity() >= MAX_BUFFER_SIZE { - return Err(HttpResponseParserError::Error( - ParseError::TooLarge, - )); - } - // Parser needs more data. - } - } - } - // Read some more data into the buffer for the parser. - match io.read_available(buf) { - Ok(Async::Ready((false, true))) => { - return Err(HttpResponseParserError::Disconnect) - } - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(HttpResponseParserError::Error(err.into())), - } - } - } - - pub fn parse_payload( - &mut self, io: &mut T, buf: &mut BytesMut, - ) -> Poll, PayloadError> - where - T: IoStream, - { - if self.decoder.is_some() { - loop { - // read payload - let (not_ready, stream_finished) = match io.read_available(buf) { - Ok(Async::Ready((_, true))) => (false, true), - Ok(Async::Ready((_, false))) => (false, false), - Ok(Async::NotReady) => (true, false), - Err(err) => return Err(err.into()), - }; - - match self.decoder.as_mut().unwrap().decode(buf) { - Ok(Async::Ready(Some(b))) => return Ok(Async::Ready(Some(b))), - Ok(Async::Ready(None)) => { - self.decoder.take(); - return Ok(Async::Ready(None)); - } - Ok(Async::NotReady) => { - if not_ready { - return Ok(Async::NotReady); - } - if stream_finished { - // read untile eof? - if self.eof { - return Ok(Async::Ready(None)); - } else { - return Err(PayloadError::Incomplete); - } - } - } - Err(err) => return Err(err.into()), - } - } - } else { - Ok(Async::Ready(None)) - } - } - - fn parse_message( - buf: &mut BytesMut, - ) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), ParseError> { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - - let (len, version, status, headers_len) = { - let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let mut resp = httparse::Response::new(&mut parsed); - match resp.parse(buf)? { - httparse::Status::Complete(len) => { - let version = if resp.version.unwrap_or(1) == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - HeaderIndex::record(buf, resp.headers, &mut headers); - let status = StatusCode::from_u16(resp.code.unwrap()) - .map_err(|_| ParseError::Status)?; - - (len, version, status, resp.headers.len()) - } - httparse::Status::Partial => return Ok(Async::NotReady), - } - }; - - let slice = buf.split_to(len).freeze(); - - // convert headers - let mut hdrs = HeaderMap::new(); - for idx in headers[..headers_len].iter() { - if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - hdrs.append(name, value); - } else { - return Err(ParseError::Header); - } - } - - let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { - Some((EncodingDecoder::eof(), true)) - } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Some((EncodingDecoder::length(len), false)) - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else if chunked(&hdrs)? { - // Chunked encoding - Some((EncodingDecoder::chunked(), false)) - } else if let Some(value) = hdrs.get(header::CONNECTION) { - let close = if let Ok(s) = value.to_str() { - s == "close" - } else { - false - }; - if close { - Some((EncodingDecoder::eof(), true)) - } else { - None - } - } else { - None - }; - - if let Some(decoder) = decoder { - Ok(Async::Ready(( - ClientResponse::new(ClientMessage { - status, - version, - headers: hdrs, - cookies: None, - }), - Some(decoder), - ))) - } else { - Ok(Async::Ready(( - ClientResponse::new(ClientMessage { - status, - version, - headers: hdrs, - cookies: None, - }), - None, - ))) - } - } -} - -/// Check if request has chunked transfer encoding -pub 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) - } -} diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs deleted file mode 100644 index 394b7a6c..00000000 --- a/src/client/pipeline.rs +++ /dev/null @@ -1,552 +0,0 @@ -use bytes::{Bytes, BytesMut}; -use futures::sync::oneshot; -use futures::{Async, Future, Poll, Stream}; -use http::header::CONTENT_ENCODING; -use std::time::{Duration, Instant}; -use std::{io, mem}; -use tokio_timer::Delay; - -use actix::{Addr, Request, SystemService}; - -use super::{ - ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect, - Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError, -}; -use body::{Body, BodyStream}; -use context::{ActorHttpContext, Frame}; -use error::Error; -use error::PayloadError; -use header::ContentEncoding; -use http::{Method, Uri}; -use httpmessage::HttpMessage; -use server::input::PayloadStream; -use server::WriterState; - -/// A set of errors that can occur during request sending and response reading -#[derive(Fail, Debug)] -pub enum SendRequestError { - /// Response took too long - #[fail(display = "Timeout while waiting for response")] - Timeout, - /// Failed to connect to host - #[fail(display = "Failed to connect to host: {}", _0)] - Connector(#[cause] ClientConnectorError), - /// Error parsing response - #[fail(display = "{}", _0)] - ParseError(#[cause] HttpResponseParserError), - /// Error reading response payload - #[fail(display = "Error reading response payload: {}", _0)] - Io(#[cause] io::Error), -} - -impl From for SendRequestError { - fn from(err: io::Error) -> SendRequestError { - SendRequestError::Io(err) - } -} - -impl From for SendRequestError { - fn from(err: ClientConnectorError) -> SendRequestError { - match err { - ClientConnectorError::Timeout => SendRequestError::Timeout, - _ => SendRequestError::Connector(err), - } - } -} - -enum State { - New, - Connect(Request), - Connection(Connection), - Send(Box), - None, -} - -/// `SendRequest` is a `Future` which represents an asynchronous -/// request sending process. -#[must_use = "SendRequest does nothing unless polled"] -pub struct SendRequest { - req: ClientRequest, - state: State, - conn: Option>, - conn_timeout: Duration, - wait_timeout: Duration, - timeout: Option, -} - -impl SendRequest { - pub(crate) fn new(req: ClientRequest) -> SendRequest { - SendRequest { - req, - conn: None, - state: State::New, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - pub(crate) fn with_connector( - req: ClientRequest, conn: Addr, - ) -> SendRequest { - SendRequest { - req, - conn: Some(conn), - state: State::New, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest { - SendRequest { - req, - state: State::Connection(conn), - conn: None, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - /// Set request timeout - /// - /// Request timeout is the total time before a response must be received. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(timeout); - self - } - - /// Set connection timeout - /// - /// Connection timeout includes resolving hostname and actual connection to - /// the host. - /// Default value is 1 second. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - self.conn_timeout = timeout; - self - } - - /// Set wait timeout - /// - /// If connections pool limits are enabled, wait time indicates max time - /// to wait for available connection. Default value is 5 seconds. - pub fn wait_timeout(mut self, timeout: Duration) -> Self { - self.wait_timeout = timeout; - self - } -} - -impl Future for SendRequest { - type Item = ClientResponse; - type Error = SendRequestError; - - fn poll(&mut self) -> Poll { - loop { - let state = mem::replace(&mut self.state, State::None); - - match state { - State::New => { - let conn = if let Some(conn) = self.conn.take() { - conn - } else { - ClientConnector::from_registry() - }; - self.state = State::Connect(conn.send(Connect { - uri: self.req.uri().clone(), - wait_timeout: self.wait_timeout, - conn_timeout: self.conn_timeout, - })) - } - State::Connect(mut conn) => match conn.poll() { - Ok(Async::NotReady) => { - self.state = State::Connect(conn); - return Ok(Async::NotReady); - } - Ok(Async::Ready(result)) => match result { - Ok(stream) => self.state = State::Connection(stream), - Err(err) => return Err(err.into()), - }, - Err(_) => { - return Err(SendRequestError::Connector( - ClientConnectorError::Disconnected, - )); - } - }, - State::Connection(conn) => { - let mut writer = HttpClientWriter::new(); - writer.start(&mut self.req)?; - - let body = match self.req.replace_body(Body::Empty) { - Body::Streaming(stream) => IoBody::Payload(stream), - Body::Actor(ctx) => IoBody::Actor(ctx), - _ => IoBody::Done, - }; - - let timeout = self - .timeout - .take() - .unwrap_or_else(|| Duration::from_secs(5)); - - let pl = Box::new(Pipeline { - body, - writer, - conn: Some(conn), - parser: Some(HttpResponseParser::default()), - parser_buf: BytesMut::new(), - disconnected: false, - body_completed: false, - drain: None, - decompress: None, - should_decompress: self.req.response_decompress(), - write_state: RunningState::Running, - timeout: Some(Delay::new(Instant::now() + timeout)), - meth: self.req.method().clone(), - path: self.req.uri().clone(), - }); - self.state = State::Send(pl); - } - State::Send(mut pl) => { - pl.poll_timeout()?; - pl.poll_write().map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()) - })?; - - match pl.parse() { - Ok(Async::Ready(mut resp)) => { - if self.req.method() == Method::HEAD { - pl.parser.take(); - } - resp.set_pipeline(pl); - return Ok(Async::Ready(resp)); - } - Ok(Async::NotReady) => { - self.state = State::Send(pl); - return Ok(Async::NotReady); - } - Err(err) => { - return Err(SendRequestError::ParseError(err)); - } - } - } - State::None => unreachable!(), - } - } - } -} - -pub struct Pipeline { - body: IoBody, - body_completed: bool, - conn: Option, - writer: HttpClientWriter, - parser: Option, - parser_buf: BytesMut, - disconnected: bool, - drain: Option>, - decompress: Option, - should_decompress: bool, - write_state: RunningState, - timeout: Option, - meth: Method, - path: Uri, -} - -enum IoBody { - Payload(BodyStream), - Actor(Box), - Done, -} - -#[derive(Debug, PartialEq)] -enum RunningState { - Running, - Paused, - Done, -} - -impl RunningState { - #[inline] - fn pause(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Paused - } - } - #[inline] - fn resume(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Running - } - } -} - -impl Pipeline { - fn release_conn(&mut self) { - if let Some(conn) = self.conn.take() { - if self.meth == Method::HEAD { - conn.close() - } else { - conn.release() - } - } - } - - #[inline] - fn parse(&mut self) -> Poll { - if let Some(ref mut conn) = self.conn { - match self - .parser - .as_mut() - .unwrap() - .parse(conn, &mut self.parser_buf) - { - Ok(Async::Ready(resp)) => { - // check content-encoding - if self.should_decompress { - if let Some(enc) = resp.headers().get(CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - match ContentEncoding::from(enc) { - ContentEncoding::Auto - | ContentEncoding::Identity => (), - enc => { - self.decompress = Some(PayloadStream::new(enc)) - } - } - } - } - } - - Ok(Async::Ready(resp)) - } - val => val, - } - } else { - Ok(Async::NotReady) - } - } - - #[inline] - pub(crate) fn poll(&mut self) -> Poll, PayloadError> { - if self.conn.is_none() { - return Ok(Async::Ready(None)); - } - let mut need_run = false; - - // need write? - match self - .poll_write() - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? - { - Async::NotReady => need_run = true, - Async::Ready(_) => { - self.poll_timeout().map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("{}", e)) - })?; - } - } - - // need read? - if self.parser.is_some() { - let conn: &mut Connection = self.conn.as_mut().unwrap(); - - loop { - match self - .parser - .as_mut() - .unwrap() - .parse_payload(conn, &mut self.parser_buf)? - { - Async::Ready(Some(b)) => { - if let Some(ref mut decompress) = self.decompress { - match decompress.feed_data(b) { - Ok(Some(b)) => return Ok(Async::Ready(Some(b))), - Ok(None) => return Ok(Async::NotReady), - Err(ref err) - if err.kind() == io::ErrorKind::WouldBlock => - { - continue - } - Err(err) => return Err(err.into()), - } - } else { - return Ok(Async::Ready(Some(b))); - } - } - Async::Ready(None) => { - let _ = self.parser.take(); - break; - } - Async::NotReady => return Ok(Async::NotReady), - } - } - } - - // eof - if let Some(mut decompress) = self.decompress.take() { - let res = decompress.feed_eof(); - if let Some(b) = res? { - self.release_conn(); - return Ok(Async::Ready(Some(b))); - } - } - - if need_run { - Ok(Async::NotReady) - } else { - self.release_conn(); - Ok(Async::Ready(None)) - } - } - - fn poll_timeout(&mut self) -> Result<(), SendRequestError> { - if self.timeout.is_some() { - match self.timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(())) => return Err(SendRequestError::Timeout), - Ok(Async::NotReady) => (), - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()), - } - } - Ok(()) - } - - #[inline] - fn poll_write(&mut self) -> Poll<(), Error> { - if self.write_state == RunningState::Done || self.conn.is_none() { - return Ok(Async::Ready(())); - } - - let mut done = false; - if self.drain.is_none() && self.write_state != RunningState::Paused { - 'outter: loop { - let result = match mem::replace(&mut self.body, IoBody::Done) { - IoBody::Payload(mut body) => match body.poll()? { - Async::Ready(None) => { - self.writer.write_eof()?; - self.body_completed = true; - break; - } - Async::Ready(Some(chunk)) => { - self.body = IoBody::Payload(body); - self.writer.write(chunk.as_ref())? - } - Async::NotReady => { - done = true; - self.body = IoBody::Payload(body); - break; - } - }, - IoBody::Actor(mut ctx) => { - if self.disconnected { - ctx.disconnected(); - } - match ctx.poll()? { - Async::Ready(Some(vec)) => { - if vec.is_empty() { - self.body = IoBody::Actor(ctx); - break; - } - let mut res = None; - for frame in vec { - match frame { - Frame::Chunk(None) => { - self.body_completed = true; - self.writer.write_eof()?; - break 'outter; - } - Frame::Chunk(Some(chunk)) => { - res = - Some(self.writer.write(chunk.as_ref())?) - } - Frame::Drain(fut) => self.drain = Some(fut), - } - } - self.body = IoBody::Actor(ctx); - if self.drain.is_some() { - self.write_state.resume(); - break; - } - res.unwrap() - } - Async::Ready(None) => { - done = true; - break; - } - Async::NotReady => { - done = true; - self.body = IoBody::Actor(ctx); - break; - } - } - } - IoBody::Done => { - self.body_completed = true; - done = true; - break; - } - }; - - match result { - WriterState::Pause => { - self.write_state.pause(); - break; - } - WriterState::Done => self.write_state.resume(), - } - } - } - - // flush io but only if we need to - match self - .writer - .poll_completed(self.conn.as_mut().unwrap(), false) - { - Ok(Async::Ready(_)) => { - if self.disconnected - || (self.body_completed && self.writer.is_completed()) - { - self.write_state = RunningState::Done; - } else { - self.write_state.resume(); - } - - // resolve drain futures - if let Some(tx) = self.drain.take() { - let _ = tx.send(()); - } - // restart io processing - if !done || self.write_state == RunningState::Done { - self.poll_write() - } else { - Ok(Async::NotReady) - } - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => Err(err.into()), - } - } -} - -impl Drop for Pipeline { - fn drop(&mut self) { - if let Some(conn) = self.conn.take() { - debug!( - "Client http transaction is not completed, dropping connection: {:?} {:?}", - self.meth, - self.path, - ); - conn.close() - } - } -} - -/// Future that resolves to a complete request body. -impl Stream for Box { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - Pipeline::poll(self) - } -} diff --git a/src/client/request.rs b/src/client/request.rs deleted file mode 100644 index 76fb1be5..00000000 --- a/src/client/request.rs +++ /dev/null @@ -1,782 +0,0 @@ -use std::fmt::Write as FmtWrite; -use std::io::Write; -use std::time::Duration; -use std::{fmt, mem}; - -use actix::Addr; -use bytes::{BufMut, Bytes, BytesMut}; -use cookie::{Cookie, CookieJar}; -use futures::Stream; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use serde::Serialize; -use serde_json; -use serde_urlencoded; -use url::Url; - -use super::connector::{ClientConnector, Connection}; -use super::pipeline::SendRequest; -use body::Body; -use error::Error; -use header::{ContentEncoding, Header, IntoHeaderValue}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{uri, Error as HttpError, HeaderMap, HttpTryFrom, Method, Uri, Version}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; - -/// An HTTP Client Request -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # extern crate tokio; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::{actix, client}; -/// -/// fn main() { -/// actix::run( -/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); -/// } -/// ``` -pub struct ClientRequest { - uri: Uri, - method: Method, - version: Version, - headers: HeaderMap, - body: Body, - chunked: bool, - upgrade: bool, - timeout: Option, - encoding: ContentEncoding, - response_decompress: bool, - buffer_capacity: usize, - conn: ConnectionType, -} - -enum ConnectionType { - Default, - Connector(Addr), - Connection(Connection), -} - -impl Default for ClientRequest { - fn default() -> ClientRequest { - ClientRequest { - uri: Uri::default(), - method: Method::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - body: Body::Empty, - chunked: false, - upgrade: false, - timeout: None, - encoding: ContentEncoding::Auto, - response_decompress: true, - buffer_capacity: 32_768, - conn: ConnectionType::Default, - } - } -} - -impl ClientRequest { - /// Create request builder for `GET` request - pub fn get>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder - } - - /// Create request builder for `HEAD` request - pub fn head>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder - } - - /// Create request builder for `POST` request - pub fn post>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder - } - - /// Create request builder for `PUT` request - pub fn put>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder - } - - /// Create request builder for `DELETE` request - pub fn delete>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder - } -} - -impl ClientRequest { - /// Create client request builder - pub fn build() -> ClientRequestBuilder { - ClientRequestBuilder { - request: Some(ClientRequest::default()), - err: None, - cookies: None, - default_headers: true, - } - } - - /// Create client request builder - pub fn build_from>(source: T) -> ClientRequestBuilder { - source.into() - } - - /// Get the request URI - #[inline] - pub fn uri(&self) -> &Uri { - &self.uri - } - - /// Set client request URI - #[inline] - pub fn set_uri(&mut self, uri: Uri) { - self.uri = uri - } - - /// Get the request method - #[inline] - pub fn method(&self) -> &Method { - &self.method - } - - /// Set HTTP `Method` for the request - #[inline] - pub fn set_method(&mut self, method: Method) { - self.method = method - } - - /// Get HTTP version for the request - #[inline] - pub fn version(&self) -> Version { - self.version - } - - /// Set http `Version` for the request - #[inline] - pub fn set_version(&mut self, version: Version) { - self.version = version - } - - /// Get the headers from the request - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - /// is chunked encoding enabled - #[inline] - pub fn chunked(&self) -> bool { - self.chunked - } - - /// is upgrade request - #[inline] - pub fn upgrade(&self) -> bool { - self.upgrade - } - - /// Content encoding - #[inline] - pub fn content_encoding(&self) -> ContentEncoding { - self.encoding - } - - /// Decompress response payload - #[inline] - pub fn response_decompress(&self) -> bool { - self.response_decompress - } - - /// Requested write buffer capacity - pub fn write_buffer_capacity(&self) -> usize { - self.buffer_capacity - } - - /// Get body of this response - #[inline] - pub fn body(&self) -> &Body { - &self.body - } - - /// Set a body - pub fn set_body>(&mut self, body: B) { - self.body = body.into(); - } - - /// Extract body, replace it with `Empty` - pub(crate) fn replace_body(&mut self, body: Body) -> Body { - mem::replace(&mut self.body, body) - } - - /// Send request - /// - /// This method returns a future that resolves to a ClientResponse - pub fn send(mut self) -> SendRequest { - let timeout = self.timeout.take(); - let send = match mem::replace(&mut self.conn, ConnectionType::Default) { - ConnectionType::Default => SendRequest::new(self), - ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn), - ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn), - }; - if let Some(timeout) = timeout { - send.timeout(timeout) - } else { - send - } - } -} - -impl fmt::Debug for ClientRequest { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nClientRequest {:?} {}:{}", - self.version, self.method, self.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -/// An HTTP Client request builder -/// -/// This type can be used to construct an instance of `ClientRequest` through a -/// builder-like pattern. -pub struct ClientRequestBuilder { - request: Option, - err: Option, - cookies: Option, - default_headers: bool, -} - -impl ClientRequestBuilder { - /// Set HTTP URI of request. - #[inline] - pub fn uri>(&mut self, uri: U) -> &mut Self { - match Url::parse(uri.as_ref()) { - Ok(url) => self._uri(url.as_str()), - Err(_) => self._uri(uri.as_ref()), - } - } - - fn _uri(&mut self, url: &str) -> &mut Self { - match Uri::try_from(url) { - Ok(uri) => { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.uri = uri; - } - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.method = method; - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn get_method(&mut self) -> &Method { - let parts = self.request.as_ref().expect("cannot reuse request builder"); - &parts.method - } - - /// Set HTTP version of this request. - /// - /// By default requests's HTTP version depends on network stream - #[inline] - pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.version = version; - } - self - } - - /// Set a header. - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_web; - /// # use actix_web::client::*; - /// # - /// use actix_web::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .set(http::header::Date::now()) - /// .set(http::header::ContentType(mime::TEXT_HTML)) - /// .finish() - /// .unwrap(); - /// } - /// ``` - #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// # use actix_web::client::*; - /// # - /// use http::header; - /// - /// fn main() { - /// let req = ClientRequest::build() - /// .header("X-TEST", "value") - /// .header(header::CONTENT_TYPE, "application/json") - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header. - pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header only if it is not yet set. - pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => if !parts.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - } - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content encoding. - /// - /// By default `ContentEncoding::Identity` is used. - #[inline] - pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.encoding = enc; - } - self - } - - /// Enables automatic chunked transfer encoding - #[inline] - pub fn chunked(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.chunked = true; - } - self - } - - /// Enable connection upgrade - #[inline] - pub fn upgrade(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.upgrade = true; - } - self - } - - /// Set request's content type - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - HeaderValue: HttpTryFrom, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderValue::try_from(value) { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content length - #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { - let mut wrt = BytesMut::new().writer(); - let _ = write!(wrt, "{}", len); - self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) - } - - /// Set a cookie - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - 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 - } - - /// Do not add default request headers. - /// By default `Accept-Encoding` and `User-Agent` headers are set. - pub fn no_default_headers(&mut self) -> &mut Self { - self.default_headers = false; - self - } - - /// Disable automatic decompress response body - pub fn disable_decompress(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.response_decompress = false; - } - self - } - - /// Set write buffer capacity - /// - /// Default buffer capacity is 32kb - pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.buffer_capacity = cap; - } - self - } - - /// Set request timeout - /// - /// Request timeout is a total time before response should be received. - /// Default value is 5 seconds. - pub fn timeout(&mut self, timeout: Duration) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.timeout = Some(timeout); - } - self - } - - /// Send request using custom connector - pub fn with_connector(&mut self, conn: Addr) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.conn = ConnectionType::Connector(conn); - } - self - } - - /// Send request using existing `Connection` - pub fn with_connection(&mut self, conn: Connection) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.conn = ConnectionType::Connection(conn); - } - self - } - - /// This method calls provided closure with builder reference if - /// value is `true`. - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut ClientRequestBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if - /// value is `Some`. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut ClientRequestBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - - /// Set a body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> Result { - if let Some(e) = self.err.take() { - return Err(e.into()); - } - - if self.default_headers { - // enable br only for https - let https = if let Some(parts) = parts(&mut self.request, &self.err) { - parts - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true) - } else { - true - }; - - if https { - self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); - } else { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); - } - - // set request host header - if let Some(parts) = parts(&mut self.request, &self.err) { - if let Some(host) = parts.uri.host() { - if !parts.headers.contains_key(header::HOST) { - let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - - let _ = match parts.uri.port() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), - }; - - match wrt.get_mut().take().freeze().try_into() { - Ok(value) => { - parts.headers.insert(header::HOST, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - } - - // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("actix-web/", env!("CARGO_PKG_VERSION")), - ); - } - - let mut request = self.request.take().expect("cannot reuse request builder"); - - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - request.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - request.body = body.into(); - Ok(request) - } - - /// Set a JSON body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Result { - let body = serde_json::to_string(&value)?; - - let contains = if let Some(parts) = parts(&mut self.request, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/json"); - } - - self.body(body) - } - - /// Set a urlencoded body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn form(&mut self, value: T) -> Result { - let body = serde_urlencoded::to_string(&value)?; - - let contains = if let Some(parts) = parts(&mut self.request, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); - } - - self.body(body) - } - - /// Set a streaming body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> Result - where - S: Stream + 'static, - E: Into, - { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) - } - - /// Set an empty body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn finish(&mut self) -> Result { - self.body(Body::Empty) - } - - /// This method construct new `ClientRequestBuilder` - pub fn take(&mut self) -> ClientRequestBuilder { - ClientRequestBuilder { - request: self.request.take(), - err: self.err.take(), - cookies: self.cookies.take(), - default_headers: self.default_headers, - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, err: &Option, -) -> Option<&'a mut ClientRequest> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl fmt::Debug for ClientRequestBuilder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref parts) = self.request { - writeln!( - f, - "\nClientRequestBuilder {:?} {}:{}", - parts.version, parts.method, parts.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in parts.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } else { - write!(f, "ClientRequestBuilder(Consumed)") - } - } -} - -/// Create `ClientRequestBuilder` from `HttpRequest` -/// -/// It is useful for proxy requests. This implementation -/// copies all request headers and the method. -impl<'a, S: 'static> From<&'a HttpRequest> for ClientRequestBuilder { - fn from(req: &'a HttpRequest) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - for (key, value) in req.headers() { - builder.header(key.clone(), value.clone()); - } - builder.method(req.method().clone()); - builder - } -} diff --git a/src/client/response.rs b/src/client/response.rs deleted file mode 100644 index 5f1f4264..00000000 --- a/src/client/response.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::cell::RefCell; -use std::{fmt, str}; - -use cookie::Cookie; -use http::header::{self, HeaderValue}; -use http::{HeaderMap, StatusCode, Version}; - -use error::CookieParseError; -use httpmessage::HttpMessage; - -use super::pipeline::Pipeline; - -pub(crate) struct ClientMessage { - pub status: StatusCode, - pub version: Version, - pub headers: HeaderMap, - pub cookies: Option>>, -} - -impl Default for ClientMessage { - fn default() -> ClientMessage { - ClientMessage { - status: StatusCode::OK, - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - cookies: None, - } - } -} - -/// An HTTP Client response -pub struct ClientResponse(ClientMessage, RefCell>>); - -impl HttpMessage for ClientResponse { - type Stream = Box; - - /// Get the headers from the response. - #[inline] - fn headers(&self) -> &HeaderMap { - &self.0.headers - } - - #[inline] - fn payload(&self) -> Box { - self.1 - .borrow_mut() - .take() - .expect("Payload is already consumed.") - } -} - -impl ClientResponse { - pub(crate) fn new(msg: ClientMessage) -> ClientResponse { - ClientResponse(msg, RefCell::new(None)) - } - - pub(crate) fn set_pipeline(&mut self, pl: Box) { - *self.1.borrow_mut() = Some(pl); - } - - /// Get the HTTP version of this response. - #[inline] - pub fn version(&self) -> Version { - self.0.version - } - - /// Get the status from the server. - #[inline] - pub fn status(&self) -> StatusCode { - self.0.status - } - - /// Load response cookies. - pub fn cookies(&self) -> Result>, CookieParseError> { - let mut cookies = Vec::new(); - for val in self.0.headers.get_all(header::SET_COOKIE).iter() { - let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?; - cookies.push(Cookie::parse_encoded(s)?.into_owned()); - } - Ok(cookies) - } - - /// Return request cookie. - pub fn cookie(&self, name: &str) -> Option { - if let Ok(cookies) = self.cookies() { - for cookie in cookies { - if cookie.name() == name { - return Some(cookie); - } - } - } - None - } -} - -impl fmt::Debug for ClientResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?; - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_debug() { - let mut resp = ClientResponse::new(ClientMessage::default()); - resp.0 - .headers - .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1")); - resp.0 - .headers - .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2")); - - let dbg = format!("{:?}", resp); - assert!(dbg.contains("ClientResponse")); - } -} diff --git a/src/client/writer.rs b/src/client/writer.rs deleted file mode 100644 index e74f2233..00000000 --- a/src/client/writer.rs +++ /dev/null @@ -1,412 +0,0 @@ -#![cfg_attr( - feature = "cargo-clippy", - allow(clippy::redundant_field_names) -)] - -use std::cell::RefCell; -use std::fmt::Write as FmtWrite; -use std::io::{self, Write}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliEncoder; -use bytes::{BufMut, BytesMut}; -#[cfg(feature = "flate2")] -use flate2::write::{GzEncoder, ZlibEncoder}; -#[cfg(feature = "flate2")] -use flate2::Compression; -use futures::{Async, Poll}; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Version}; -use time::{self, Duration}; -use tokio_io::AsyncWrite; - -use body::{Binary, Body}; -use header::ContentEncoding; -use server::output::{ContentEncoder, Output, TransferEncoding}; -use server::WriterState; - -use client::ClientRequest; - -const AVERAGE_HEADER_SIZE: usize = 30; - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const DISCONNECTED = 0b0000_1000; - } -} - -pub(crate) struct HttpClientWriter { - flags: Flags, - written: u64, - headers_size: u32, - buffer: Output, - buffer_capacity: usize, -} - -impl HttpClientWriter { - pub fn new() -> HttpClientWriter { - HttpClientWriter { - flags: Flags::empty(), - written: 0, - headers_size: 0, - buffer_capacity: 0, - buffer: Output::Buffer(BytesMut::new()), - } - } - - pub fn disconnected(&mut self) { - self.buffer.take(); - } - - pub fn is_completed(&self) -> bool { - self.buffer.is_empty() - } - - // pub fn keepalive(&self) -> bool { - // self.flags.contains(Flags::KEEPALIVE) && - // !self.flags.contains(Flags::UPGRADE) } - - fn write_to_stream( - &mut self, stream: &mut T, - ) -> io::Result { - while !self.buffer.is_empty() { - match stream.write(self.buffer.as_ref().as_ref()) { - Ok(0) => { - self.disconnected(); - return Ok(WriterState::Done); - } - Ok(n) => { - let _ = self.buffer.split_to(n); - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - if self.buffer.len() > self.buffer_capacity { - return Ok(WriterState::Pause); - } else { - return Ok(WriterState::Done); - } - } - Err(err) => return Err(err), - } - } - Ok(WriterState::Done) - } -} - -pub struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl HttpClientWriter { - pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> { - // prepare task - self.buffer = content_encoder(self.buffer.take(), msg); - self.flags.insert(Flags::STARTED); - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - } - - // render message - { - // output buffer - let buffer = self.buffer.as_mut(); - - // status line - writeln!( - Writer(buffer), - "{} {} {:?}\r", - msg.method(), - msg.uri() - .path_and_query() - .map(|u| u.as_str()) - .unwrap_or("/"), - msg.version() - ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - // write headers - if let Body::Binary(ref bytes) = *msg.body() { - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); - } else { - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); - } - - 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"); - } - - // set date header - if !msg.headers().contains_key(DATE) { - buffer.extend_from_slice(b"date: "); - set_date(buffer); - buffer.extend_from_slice(b"\r\n\r\n"); - } else { - buffer.extend_from_slice(b"\r\n"); - } - } - self.headers_size = self.buffer.len() as u32; - - if msg.body().is_binary() { - if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { - self.written += bytes.len() as u64; - self.buffer.write(bytes.as_ref())?; - } - } else { - self.buffer_capacity = msg.write_buffer_capacity(); - } - Ok(()) - } - - pub fn write(&mut self, payload: &[u8]) -> io::Result { - self.written += payload.len() as u64; - if !self.flags.contains(Flags::DISCONNECTED) { - self.buffer.write(payload)?; - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - pub fn write_eof(&mut self) -> io::Result<()> { - if self.buffer.write_eof()? { - Ok(()) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } - } - - #[inline] - pub fn poll_completed( - &mut self, stream: &mut T, shutdown: bool, - ) -> Poll<(), io::Error> { - match self.write_to_stream(stream) { - Ok(WriterState::Done) => { - if shutdown { - stream.shutdown() - } else { - Ok(Async::Ready(())) - } - } - Ok(WriterState::Pause) => Ok(Async::NotReady), - Err(err) => Err(err), - } - } -} - -fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { - let version = req.version(); - let mut body = req.replace_body(Body::Empty); - let mut encoding = req.content_encoding(); - - let transfer = match body { - Body::Empty => { - req.headers_mut().remove(CONTENT_LENGTH); - return Output::Empty(buf); - } - Body::Binary(ref mut bytes) => { - #[cfg(any(feature = "flate2", feature = "brotli"))] - { - if encoding.is_compression() { - let mut tmp = BytesMut::new(); - let mut transfer = TransferEncoding::eof(tmp); - let mut enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate( - ZlibEncoder::new(transfer, Compression::default()), - ), - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( - transfer, - Compression::default(), - )), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - ContentEncoder::Br(BrotliEncoder::new(transfer, 5)) - } - ContentEncoding::Auto | ContentEncoding::Identity => { - unreachable!() - } - }; - // TODO return error! - let _ = enc.write(bytes.as_ref()); - let _ = enc.write_eof(); - *bytes = Binary::from(enc.buf_mut().take()); - - req.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); - encoding = ContentEncoding::Identity; - } - let mut b = BytesMut::new(); - let _ = write!(b, "{}", bytes.len()); - req.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof(buf) - } - #[cfg(not(any(feature = "flate2", feature = "brotli")))] - { - let mut b = BytesMut::new(); - let _ = write!(b, "{}", bytes.len()); - req.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof(buf) - } - } - Body::Streaming(_) | Body::Actor(_) => { - if req.upgrade() { - if version == Version::HTTP_2 { - error!("Connection upgrade is forbidden for HTTP/2"); - } else { - req.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - if encoding != ContentEncoding::Identity { - encoding = ContentEncoding::Identity; - req.headers_mut().remove(CONTENT_ENCODING); - } - TransferEncoding::eof(buf) - } else { - streaming_encoding(buf, version, req) - } - } - }; - - if encoding.is_compression() { - req.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); - } - - req.replace_body(body); - let enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default())) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default())) - } - #[cfg(feature = "brotli")] - ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)), - ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer), - }; - Output::Encoder(enc) -} - -fn streaming_encoding( - buf: BytesMut, version: Version, req: &mut ClientRequest, -) -> TransferEncoding { - if req.chunked() { - // Enable transfer encoding - req.headers_mut().remove(CONTENT_LENGTH); - if version == Version::HTTP_2 { - req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) - } else { - req.headers_mut() - .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) - } - } else { - // if Content-Length is specified, then use it as length hint - let (len, chunked) = if let Some(len) = req.headers().get(CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - (Some(len), false) - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - (None, true) - }; - - if !chunked { - if let Some(len) = len { - TransferEncoding::length(len, buf) - } else { - TransferEncoding::eof(buf) - } - } else { - // Enable transfer encoding - match version { - Version::HTTP_11 => { - req.headers_mut() - .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) - } - _ => { - req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) - } - } - } - } -} - -// "Sun, 06 Nov 1994 08:49:37 GMT".len() -pub const DATE_VALUE_LENGTH: usize = 29; - -fn set_date(dst: &mut BytesMut) { - CACHED.with(|cache| { - let mut cache = cache.borrow_mut(); - let now = time::get_time(); - if now > cache.next_update { - cache.update(now); - } - dst.extend_from_slice(cache.buffer()); - }) -} - -struct CachedDate { - bytes: [u8; DATE_VALUE_LENGTH], - next_update: time::Timespec, -} - -thread_local!(static CACHED: RefCell = RefCell::new(CachedDate { - bytes: [0; DATE_VALUE_LENGTH], - next_update: time::Timespec::new(0, 0), -})); - -impl CachedDate { - fn buffer(&self) -> &[u8] { - &self.bytes[..] - } - - fn update(&mut self, now: time::Timespec) { - write!(&mut self.bytes[..], "{}", time::at_utc(now).rfc822()).unwrap(); - self.next_update = now + Duration::seconds(1); - self.next_update.nsec = 0; - } -} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 71a5af2d..00000000 --- a/src/context.rs +++ /dev/null @@ -1,294 +0,0 @@ -extern crate actix; - -use futures::sync::oneshot; -use futures::sync::oneshot::Sender; -use futures::{Async, Future, Poll}; -use smallvec::SmallVec; -use std::marker::PhantomData; - -use self::actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, -}; -use self::actix::fut::ActorFuture; -use self::actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, -}; - -use body::{Binary, Body}; -use error::{Error, ErrorInternalServerError}; -use httprequest::HttpRequest; - -pub trait ActorHttpContext: 'static { - fn disconnected(&mut self); - fn poll(&mut self) -> Poll>, Error>; -} - -#[derive(Debug)] -pub enum Frame { - Chunk(Option), - Drain(oneshot::Sender<()>), -} - -impl Frame { - pub fn len(&self) -> usize { - match *self { - Frame::Chunk(Some(ref bin)) => bin.len(), - _ => 0, - } - } -} - -/// Execution context for http actors -pub struct HttpContext -where - A: Actor>, -{ - inner: ContextParts, - stream: Option>, - request: HttpRequest, - disconnected: bool, -} - -impl ActorContext for HttpContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for HttpContext -where - A: Actor, -{ - #[inline] - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - #[inline] - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - #[inline] - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl HttpContext -where - A: Actor, -{ - #[inline] - /// Create a new HTTP Context from a request and an actor - pub fn create(req: HttpRequest, actor: A) -> Body { - let mb = Mailbox::default(); - let ctx = HttpContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb))) - } - - /// Create a new HTTP Context - pub fn with_factory(req: HttpRequest, f: F) -> Body - where - F: FnOnce(&mut Self) -> A + 'static, - { - let mb = Mailbox::default(); - let mut ctx = HttpContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - - let act = f(&mut ctx); - Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb))) - } -} - -impl HttpContext -where - A: Actor, -{ - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - self.request.state() - } - - /// Incoming request - #[inline] - pub fn request(&mut self) -> &mut HttpRequest { - &mut self.request - } - - /// Write payload - #[inline] - pub fn write>(&mut self, data: B) { - if !self.disconnected { - self.add_frame(Frame::Chunk(Some(data.into()))); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Indicate end of streaming payload. Also this method calls `Self::close`. - #[inline] - pub fn write_eof(&mut self) { - self.add_frame(Frame::Chunk(None)); - } - - /// Returns drain future - pub fn drain(&mut self) -> Drain { - let (tx, rx) = oneshot::channel(); - self.add_frame(Frame::Drain(tx)); - Drain::new(rx) - } - - /// Check if connection still open - #[inline] - pub fn connected(&self) -> bool { - !self.disconnected - } - - #[inline] - fn add_frame(&mut self, frame: Frame) { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - if let Some(s) = self.stream.as_mut() { - s.push(frame) - } - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } -} - -impl AsyncContextParts for HttpContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct HttpContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl HttpContextFut -where - A: Actor>, -{ - fn new(ctx: HttpContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - HttpContextFut { fut } - } -} - -impl ActorHttpContext for HttpContextFut -where - A: Actor>, - S: 'static, -{ - #[inline] - fn disconnected(&mut self) { - self.fut.ctx().disconnected = true; - self.fut.ctx().stop(); - } - - fn poll(&mut self) -> Poll>, Error> { - if self.fut.alive() { - match self.fut.poll() { - Ok(Async::NotReady) | Ok(Async::Ready(())) => (), - Err(_) => return Err(ErrorInternalServerError("error")), - } - } - - // frames - if let Some(data) = self.fut.ctx().stream.take() { - Ok(Async::Ready(Some(data))) - } else if self.fut.alive() { - Ok(Async::NotReady) - } else { - Ok(Async::Ready(None)) - } - } -} - -impl ToEnvelope for HttpContext -where - A: Actor> + Handler, - M: Message + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} - -/// Consume a future -pub struct Drain { - fut: oneshot::Receiver<()>, - _a: PhantomData, -} - -impl Drain { - /// Create a drain from a future - pub fn new(fut: oneshot::Receiver<()>) -> Self { - Drain { - fut, - _a: PhantomData, - } - } -} - -impl ActorFuture for Drain { - type Item = (); - type Error = (); - type Actor = A; - - #[inline] - fn poll( - &mut self, _: &mut A, _: &mut ::Context, - ) -> Poll { - self.fut.poll().map_err(|_| ()) - } -} diff --git a/src/de.rs b/src/de.rs deleted file mode 100644 index 59ab79ba..00000000 --- a/src/de.rs +++ /dev/null @@ -1,443 +0,0 @@ -use serde::de::{self, Deserializer, Error as DeError, Visitor}; - -use httprequest::HttpRequest; -use param::ParamsIter; - -macro_rules! unsupported_type { - ($trait_fn:ident, $name:expr) => { - fn $trait_fn(self, _: V) -> Result - where V: Visitor<'de> - { - Err(de::value::Error::custom(concat!("unsupported type: ", $name))) - } - }; -} - -macro_rules! parse_single_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { - fn $trait_fn(self, visitor: V) -> Result - where V: Visitor<'de> - { - if self.req.match_info().len() != 1 { - Err(de::value::Error::custom( - format!("wrong number of parameters: {} expected 1", - self.req.match_info().len()).as_str())) - } else { - let v = self.req.match_info()[0].parse().map_err( - |_| de::value::Error::custom( - format!("can not parse {:?} to a {}", - &self.req.match_info()[0], $tp)))?; - visitor.$visit_fn(v) - } - } - } -} - -pub struct PathDeserializer<'de, S: 'de> { - req: &'de HttpRequest, -} - -impl<'de, S: 'de> PathDeserializer<'de, S> { - pub fn new(req: &'de HttpRequest) -> Self { - PathDeserializer { req } - } -} - -impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { - type Error = de::value::Error; - - fn deserialize_map(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_map(ParamsDeserializer { - params: self.req.match_info().iter(), - current: None, - }) - } - - fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_map(visitor) - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_unit(visitor) - } - - fn deserialize_newtype_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_tuple( - self, len: usize, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() < len { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected {}", - self.req.match_info().len(), - len - ).as_str(), - )) - } else { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - } - - fn deserialize_tuple_struct( - self, _: &'static str, len: usize, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() < len { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected {}", - self.req.match_info().len(), - len - ).as_str(), - )) - } else { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - } - - fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: enum")) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() != 1 { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected 1", - self.req.match_info().len() - ).as_str(), - )) - } else { - visitor.visit_str(&self.req.match_info()[0]) - } - } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - }) - } - - unsupported_type!(deserialize_any, "'any'"); - unsupported_type!(deserialize_bytes, "bytes"); - unsupported_type!(deserialize_option, "Option"); - unsupported_type!(deserialize_identifier, "identifier"); - unsupported_type!(deserialize_ignored_any, "ignored_any"); - - parse_single_value!(deserialize_bool, visit_bool, "bool"); - parse_single_value!(deserialize_i8, visit_i8, "i8"); - parse_single_value!(deserialize_i16, visit_i16, "i16"); - parse_single_value!(deserialize_i32, visit_i32, "i32"); - parse_single_value!(deserialize_i64, visit_i64, "i64"); - parse_single_value!(deserialize_u8, visit_u8, "u8"); - parse_single_value!(deserialize_u16, visit_u16, "u16"); - parse_single_value!(deserialize_u32, visit_u32, "u32"); - parse_single_value!(deserialize_u64, visit_u64, "u64"); - parse_single_value!(deserialize_f32, visit_f32, "f32"); - parse_single_value!(deserialize_f64, visit_f64, "f64"); - parse_single_value!(deserialize_string, visit_string, "String"); - parse_single_value!(deserialize_byte_buf, visit_string, "String"); - parse_single_value!(deserialize_char, visit_char, "char"); -} - -struct ParamsDeserializer<'de> { - params: ParamsIter<'de>, - current: Option<(&'de str, &'de str)>, -} - -impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { - type Error = de::value::Error; - - fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> - where - K: de::DeserializeSeed<'de>, - { - self.current = self.params.next().map(|ref item| (item.0, item.1)); - match self.current { - Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), - None => Ok(None), - } - } - - fn next_value_seed(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - if let Some((_, value)) = self.current.take() { - seed.deserialize(Value { value }) - } else { - Err(de::value::Error::custom("unexpected item")) - } - } -} - -struct Key<'de> { - key: &'de str, -} - -impl<'de> Deserializer<'de> for Key<'de> { - type Error = de::value::Error; - - fn deserialize_identifier(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_str(self.key) - } - - fn deserialize_any(self, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("Unexpected")) - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes - byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum ignored_any - } -} - -macro_rules! parse_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { - fn $trait_fn(self, visitor: V) -> Result - where V: Visitor<'de> - { - let v = self.value.parse().map_err( - |_| de::value::Error::custom( - format!("can not parse {:?} to a {}", self.value, $tp)))?; - visitor.$visit_fn(v) - } - } -} - -struct Value<'de> { - value: &'de str, -} - -impl<'de> Deserializer<'de> for Value<'de> { - type Error = de::value::Error; - - parse_value!(deserialize_bool, visit_bool, "bool"); - parse_value!(deserialize_i8, visit_i8, "i8"); - parse_value!(deserialize_i16, visit_i16, "i16"); - parse_value!(deserialize_i32, visit_i32, "i16"); - parse_value!(deserialize_i64, visit_i64, "i64"); - parse_value!(deserialize_u8, visit_u8, "u8"); - parse_value!(deserialize_u16, visit_u16, "u16"); - parse_value!(deserialize_u32, visit_u32, "u32"); - parse_value!(deserialize_u64, visit_u64, "u64"); - parse_value!(deserialize_f32, visit_f32, "f32"); - parse_value!(deserialize_f64, visit_f64, "f64"); - parse_value!(deserialize_string, visit_string, "String"); - parse_value!(deserialize_byte_buf, visit_string, "String"); - parse_value!(deserialize_char, visit_char, "char"); - - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_bytes(self.value.as_bytes()) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_str(self.value) - } - - fn deserialize_option(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_some(self) - } - - fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_enum(ValueEnum { value: self.value }) - } - - fn deserialize_newtype_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_tuple(self, _: usize, _: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: tuple")) - } - - fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: struct")) - } - - fn deserialize_tuple_struct( - self, _: &'static str, _: usize, _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: tuple struct")) - } - - unsupported_type!(deserialize_any, "any"); - unsupported_type!(deserialize_seq, "seq"); - unsupported_type!(deserialize_map, "map"); - unsupported_type!(deserialize_identifier, "identifier"); -} - -struct ParamsSeq<'de> { - params: ParamsIter<'de>, -} - -impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { - type Error = de::value::Error; - - fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> - where - T: de::DeserializeSeed<'de>, - { - match self.params.next() { - Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)), - None => Ok(None), - } - } -} - -struct ValueEnum<'de> { - value: &'de str, -} - -impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { - type Error = de::value::Error; - type Variant = UnitVariant; - - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> - where - V: de::DeserializeSeed<'de>, - { - Ok((seed.deserialize(Key { key: self.value })?, UnitVariant)) - } -} - -struct UnitVariant; - -impl<'de> de::VariantAccess<'de> for UnitVariant { - type Error = de::value::Error; - - fn unit_variant(self) -> Result<(), Self::Error> { - Ok(()) - } - - fn newtype_variant_seed(self, _seed: T) -> Result - where - T: de::DeserializeSeed<'de>, - { - Err(de::value::Error::custom("not supported")) - } - - fn tuple_variant(self, _len: usize, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("not supported")) - } - - fn struct_variant( - self, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("not supported")) - } -} diff --git a/src/error.rs b/src/error.rs index 76c8e79e..72480380 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,8 +22,7 @@ pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; -use handler::Responder; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; use httpresponse::{HttpResponse, HttpResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) @@ -727,18 +726,6 @@ where } } -impl Responder for InternalError -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: &HttpRequest) -> Result { - Err(self.into()) - } -} - /// Helper function that creates wrapper of any error and generate *BAD /// REQUEST* response. #[allow(non_snake_case)] diff --git a/src/extractor.rs b/src/extractor.rs deleted file mode 100644 index 7b0b4b00..00000000 --- a/src/extractor.rs +++ /dev/null @@ -1,1024 +0,0 @@ -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; -use std::{fmt, str}; - -use bytes::Bytes; -use encoding::all::UTF_8; -use encoding::types::{DecoderTrap, Encoding}; -use futures::{future, Async, Future, Poll}; -use mime::Mime; -use serde::de::{self, DeserializeOwned}; -use serde_urlencoded; - -use de::PathDeserializer; -use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError}; -use handler::{AsyncResult, FromRequest}; -use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; -use httprequest::HttpRequest; - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's path. -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// use actix_web::{http, App, Path, Result}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// fn index(info: Path<(String, u32)>) -> Result { -/// Ok(format!("Welcome {}! {}", info.0, info.1)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Path, Result}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract path info using serde -/// fn index(info: Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -pub struct Path { - inner: T, -} - -impl AsRef for Path { - fn as_ref(&self) -> &T { - &self.inner - } -} - -impl Deref for Path { - type Target = T; - - fn deref(&self) -> &T { - &self.inner - } -} - -impl DerefMut for Path { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Path { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.inner - } -} - -impl From for Path { - fn from(inner: T) -> Path { - Path { inner } - } -} - -impl FromRequest for Path -where - T: DeserializeOwned, -{ - type Config = (); - type Result = Result; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - let req = req.clone(); - de::Deserialize::deserialize(PathDeserializer::new(&req)) - .map_err(ErrorNotFound) - .map(|inner| Path { inner }) - } -} - -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -impl fmt::Display for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from from the request's query. -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Query, http}; -/// -/// -///#[derive(Debug, Deserialize)] -///pub enum ResponseType { -/// Token, -/// Code -///} -/// -///#[derive(Deserialize)] -///pub struct AuthRequest { -/// id: u64, -/// response_type: ResponseType, -///} -/// -/// // use `with` extractor for query info -/// // this handler get called only if request's query contains `username` field -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -/// } -/// ``` -pub struct Query(T); - -impl Deref for Query { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Query { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl Query { - /// Deconstruct to a inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl FromRequest for Query -where - T: de::DeserializeOwned, -{ - type Config = (); - type Result = Result; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - serde_urlencoded::from_str::(req.query_string()) - .map_err(|e| e.into()) - .map(Query) - } -} - -impl fmt::Debug for Query { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Query { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's body. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**FormConfig**](dev/struct.FormConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Form, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// extract form data using serde -/// /// this handler get called only if content type is *x-www-form-urlencoded* -/// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// # fn main() {} -/// ``` -pub struct Form(pub T); - -impl Form { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Form { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Form { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl FromRequest for Form -where - T: DeserializeOwned + 'static, - S: 'static, -{ - type Config = FormConfig; - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - Box::new( - UrlEncoded::new(req) - .limit(cfg.limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Form), - ) - } -} - -impl fmt::Debug for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Form extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Form, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// extract form data using serde. -/// /// custom configuration is used for this handler, max payload size is 4k -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| { -/// r.method(http::Method::GET) -/// // register form handler and change form extractor configuration -/// .with_config(index, |cfg| {cfg.0.limit(4096);}) -/// }, -/// ); -/// } -/// ``` -pub struct FormConfig { - limit: usize, - ehandler: Rc) -> Error>, -} - -impl FormConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self - where - F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl Default for FormConfig { - fn default() -> Self { - FormConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - } - } -} - -/// Request payload extractor. -/// -/// Loads request's payload and construct Bytes instance. -/// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// extern crate bytes; -/// # extern crate actix_web; -/// use actix_web::{http, App, Result}; -/// -/// /// extract text data from request -/// fn index(body: bytes::Bytes) -> Result { -/// Ok(format!("Body {:?}!", body)) -/// } -/// -/// fn main() { -/// let app = App::new() -/// .resource("/index.html", |r| r.method(http::Method::GET).with(index)); -/// } -/// ``` -impl FromRequest for Bytes { - type Config = PayloadConfig; - type Result = Result>, Error>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - // check content-type - cfg.check_mimetype(req)?; - - Ok(Box::new(MessageBody::new(req).limit(cfg.limit).from_err())) - } -} - -/// Extract text information from the request's body. -/// -/// Text extractor automatically decode body according to the request's charset. -/// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{http, App, Result}; -/// -/// /// extract text data from request -/// fn index(body: String) -> Result { -/// Ok(format!("Body {}!", body)) -/// } -/// -/// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::GET) -/// .with_config(index, |cfg| { // <- register handler with extractor params -/// cfg.0.limit(4096); // <- limit size of the payload -/// }) -/// }); -/// } -/// ``` -impl FromRequest for String { - type Config = PayloadConfig; - type Result = Result>, Error>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - // check content-type - cfg.check_mimetype(req)?; - - // check charset - let encoding = req.encoding()?; - - Ok(Box::new( - MessageBody::new(req) - .limit(cfg.limit) - .from_err() - .and_then(move |body| { - let enc: *const Encoding = encoding as *const Encoding; - if enc == UTF_8 { - Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) - } else { - Ok(encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))?) - } - }), - )) - } -} - -/// Optionally extract a field from the request -/// -/// If the FromRequest for T fails, return None rather than returning an error response -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } -/// -/// impl FromRequest for Thing { -/// type Config = (); -/// type Result = Result; -/// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { -/// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) -/// } else { -/// Err(ErrorBadRequest("no luck")) -/// } -/// -/// } -/// } -/// -/// /// extract text data from request -/// fn index(supplied_thing: Option) -> Result { -/// match supplied_thing { -/// // Puns not intended -/// Some(thing) => Ok(format!("Got something: {:?}", thing)), -/// None => Ok(format!("No thing!")) -/// } -/// } -/// -/// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) -/// }); -/// } -/// ``` -impl FromRequest for Option -where - T: FromRequest, -{ - type Config = T::Config; - type Result = Box, Error = Error>>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then(|r| match r { - Ok(v) => future::ok(Some(v)), - Err(_) => future::ok(None), - })) - } -} - -/// Optionally extract a field from the request or extract the Error if unsuccessful -/// -/// If the FromRequest for T fails, inject Err into handler rather than returning an error response -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } -/// -/// impl FromRequest for Thing { -/// type Config = (); -/// type Result = Result; -/// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { -/// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) -/// } else { -/// Err(ErrorBadRequest("no luck")) -/// } -/// -/// } -/// } -/// -/// /// extract text data from request -/// fn index(supplied_thing: Result) -> Result { -/// match supplied_thing { -/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)), -/// Err(e) => Ok(format!("Error extracting thing: {}", e)) -/// } -/// } -/// -/// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) -/// }); -/// } -/// ``` -impl FromRequest for Result -where - T: FromRequest, -{ - type Config = T::Config; - type Result = Box, Error = Error>>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then(future::ok)) - } -} - -/// Payload configuration for request's payload. -pub struct PayloadConfig { - limit: usize, - mimetype: Option, -} - -impl PayloadConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { - self.limit = limit; - self - } - - /// Set required mime-type of the request. By default mime type is not - /// enforced. - pub fn mimetype(&mut self, mt: Mime) -> &mut Self { - self.mimetype = Some(mt); - self - } - - fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> { - // check content-type - if let Some(ref mt) = self.mimetype { - match req.mime_type() { - Ok(Some(ref req_mt)) => { - if mt != req_mt { - return Err(ErrorBadRequest("Unexpected Content-Type")); - } - } - Ok(None) => { - return Err(ErrorBadRequest("Content-Type is expected")); - } - Err(err) => { - return Err(err.into()); - } - } - } - Ok(()) - } -} - -impl Default for PayloadConfig { - fn default() -> Self { - PayloadConfig { - limit: 262_144, - mimetype: None, - } - } -} - -macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { - - /// FromRequest implementation for tuple - impl + 'static),+> FromRequest for ($($T,)+) - where - S: 'static, - { - type Config = ($($T::Config,)+); - type Result = Box>; - - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new($fut_type { - s: PhantomData, - items: <($(Option<$T>,)+)>::default(), - futs: ($(Some($T::from_request(req, &cfg.$n).into()),)+), - }) - } - } - - struct $fut_type),+> - where - S: 'static, - { - s: PhantomData, - items: ($(Option<$T>,)+), - futs: ($(Option>,)+), - } - - impl),+> Future for $fut_type - where - S: 'static, - { - type Item = ($($T,)+); - type Error = Error; - - fn poll(&mut self) -> Poll { - let mut ready = true; - - $( - if self.futs.$n.is_some() { - match self.futs.$n.as_mut().unwrap().poll() { - Ok(Async::Ready(item)) => { - self.items.$n = Some(item); - self.futs.$n.take(); - } - Ok(Async::NotReady) => ready = false, - Err(e) => return Err(e), - } - } - )+ - - if ready { - Ok(Async::Ready( - ($(self.items.$n.take().unwrap(),)+) - )) - } else { - Ok(Async::NotReady) - } - } - } -}); - -impl FromRequest for () { - type Config = (); - type Result = Self; - fn from_request(_req: &HttpRequest, _cfg: &Self::Config) -> Self::Result {} -} - -tuple_from_req!(TupleFromRequest1, (0, A)); -tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); -tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); -tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); -tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); -tuple_from_req!( - TupleFromRequest6, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F) -); -tuple_from_req!( - TupleFromRequest7, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G) -); -tuple_from_req!( - TupleFromRequest8, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H) -); -tuple_from_req!( - TupleFromRequest9, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I) -); - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - use futures::{Async, Future}; - use http::header; - use mime; - use resource::Resource; - use router::{ResourceDef, Router}; - use test::TestRequest; - - #[derive(Deserialize, Debug, PartialEq)] - struct Info { - hello: String, - } - - #[test] - fn test_bytes() { - let cfg = PayloadConfig::default(); - let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s, Bytes::from_static(b"hello=world")); - } - _ => unreachable!(), - } - } - - #[test] - fn test_string() { - let cfg = PayloadConfig::default(); - let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match String::from_request(&req, &cfg).unwrap().poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s, "hello=world"); - } - _ => unreachable!(), - } - } - - #[test] - fn test_form() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - let mut cfg = FormConfig::default(); - cfg.limit(4096); - match Form::::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.hello, "world"); - } - _ => unreachable!(), - } - } - - #[test] - fn test_option() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).finish(); - - let mut cfg = FormConfig::default(); - cfg.limit(4096); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!(r, None), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!( - r, - Some(Form(Info { - hello: "world".into() - })) - ), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); - - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!(r, None), - _ => unreachable!(), - } - } - - #[test] - fn test_result() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - match Result::, Error>::from_request(&req, &FormConfig::default()) - .poll() - .unwrap() - { - Async::Ready(Ok(r)) => assert_eq!( - r, - Form(Info { - hello: "world".into() - }) - ), - _ => unreachable!(), - } - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); - - match Result::, Error>::from_request(&req, &FormConfig::default()) - .poll() - .unwrap() - { - Async::Ready(r) => assert!(r.is_err()), - _ => unreachable!(), - } - } - - #[test] - fn test_payload_config() { - let req = TestRequest::default().finish(); - let mut cfg = PayloadConfig::default(); - cfg.mimetype(mime::APPLICATION_JSON); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).finish(); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = - TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); - assert!(cfg.check_mimetype(&req).is_ok()); - } - - #[derive(Deserialize)] - struct MyStruct { - key: String, - value: String, - } - - #[derive(Deserialize)] - struct Id { - id: String, - } - - #[derive(Deserialize)] - struct Test2 { - key: String, - value: u32, - } - - #[test] - fn test_request_extract() { - let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let s = Path::::from_request(&req, &()).unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - - let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); - - let s = Query::::from_request(&req, &()).unwrap(); - assert_eq!(s.id, "test"); - - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let req = TestRequest::with_uri("/name/32/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let s = Path::::from_request(&req, &()).unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - - let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - - let res = Path::>::extract(&req).unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); - } - - #[test] - fn test_extract_path_single() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - - let req = TestRequest::with_uri("/32/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); - } - - #[test] - fn test_tuple_extract() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - - let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let res = match <(Path<(String, String)>,)>::extract(&req).poll() { - Ok(Async::Ready(res)) => res, - _ => panic!("error"), - }; - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - - let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req) - .poll() - { - Ok(Async::Ready(res)) => res, - _ => panic!("error"), - }; - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - assert_eq!((res.1).0, "name"); - assert_eq!((res.1).1, "user1"); - - let () = <()>::extract(&req); - } -} diff --git a/src/fs.rs b/src/fs.rs deleted file mode 100644 index 10cdaff7..00000000 --- a/src/fs.rs +++ /dev/null @@ -1,1786 +0,0 @@ -//! Static files support -use std::fmt::Write; -use std::fs::{DirEntry, File, Metadata}; -use std::io::{Read, Seek}; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::{cmp, io}; - -#[cfg(unix)] -use std::os::unix::fs::MetadataExt; - -use bytes::Bytes; -use futures::{Async, Future, Poll, Stream}; -use futures_cpupool::{CpuFuture, CpuPool}; -use htmlescape::encode_minimal as escape_html_entity; -use mime; -use mime_guess::{get_mime_type, guess_mime_type}; -use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; - -use error::{Error, StaticFileError}; -use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; -use header; -use header::{ContentDisposition, DispositionParam, DispositionType}; -use http::{ContentEncoding, Method, StatusCode}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use param::FromParam; -use server::settings::DEFAULT_CPUPOOL; - -///Describes `StaticFiles` configiration -/// -///To configure actix's static resources you need -///to define own configiration type and implement any method -///you wish to customize. -///As trait implements reasonable defaults for Actix. -/// -///## Example -/// -///```rust -/// extern crate mime; -/// extern crate actix_web; -/// use actix_web::http::header::DispositionType; -/// use actix_web::fs::{StaticFileConfig, NamedFile}; -/// -/// #[derive(Default)] -/// struct MyConfig; -/// -/// impl StaticFileConfig for MyConfig { -/// fn content_disposition_map(typ: mime::Name) -> DispositionType { -/// DispositionType::Attachment -/// } -/// } -/// -/// let file = NamedFile::open_with_config("foo.txt", MyConfig); -///``` -pub trait StaticFileConfig: Default { - ///Describes mapping for mime type to content disposition header - /// - ///By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. - ///Others are mapped to Attachment - fn content_disposition_map(typ: mime::Name) -> DispositionType { - match typ { - mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, - _ => DispositionType::Attachment, - } - } - - ///Describes whether Actix should attempt to calculate `ETag` - /// - ///Defaults to `true` - fn is_use_etag() -> bool { - true - } - - ///Describes whether Actix should use last modified date of file. - /// - ///Defaults to `true` - fn is_use_last_modifier() -> bool { - true - } - - ///Describes allowed methods to access static resources. - /// - ///By default all methods are allowed - fn is_method_allowed(_method: &Method) -> bool { - true - } -} - -///Default content disposition as described in -///[StaticFileConfig](trait.StaticFileConfig.html) -#[derive(Default)] -pub struct DefaultConfig; -impl StaticFileConfig for DefaultConfig {} - -/// Return the MIME type associated with a filename extension (case-insensitive). -/// If `ext` is empty or no associated type for the extension was found, returns -/// the type `application/octet-stream`. -#[inline] -pub fn file_extension_to_mime(ext: &str) -> mime::Mime { - get_mime_type(ext) -} - -/// A file with an associated name. -#[derive(Debug)] -pub struct NamedFile { - path: PathBuf, - file: File, - content_type: mime::Mime, - content_disposition: header::ContentDisposition, - md: Metadata, - modified: Option, - cpu_pool: Option, - encoding: Option, - status_code: StatusCode, - _cd_map: PhantomData, -} - -impl NamedFile { - /// Attempts to open a file in read-only mode. - /// - /// # Examples - /// - /// ```rust - /// use actix_web::fs::NamedFile; - /// - /// let file = NamedFile::open("foo.txt"); - /// ``` - pub fn open>(path: P) -> io::Result { - Self::open_with_config(path, DefaultConfig) - } -} - -impl NamedFile { - /// Attempts to open a file in read-only mode using provided configiration. - /// - /// # Examples - /// - /// ```rust - /// use actix_web::fs::{DefaultConfig, NamedFile}; - /// - /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); - /// ``` - pub fn open_with_config>(path: P, _: C) -> io::Result> { - let path = path.as_ref().to_path_buf(); - - // Get the name of the file and use it to construct default Content-Type - // and Content-Disposition values - let (content_type, content_disposition) = { - let filename = match path.file_name() { - Some(name) => name.to_string_lossy(), - None => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Provided path has no filename", - )) - } - }; - - let ct = guess_mime_type(&path); - let disposition_type = C::content_disposition_map(ct.type_()); - let cd = ContentDisposition { - disposition: disposition_type, - parameters: vec![DispositionParam::Filename(filename.into_owned())], - }; - (ct, cd) - }; - - let file = File::open(&path)?; - let md = file.metadata()?; - let modified = md.modified().ok(); - let cpu_pool = None; - let encoding = None; - Ok(NamedFile { - path, - file, - content_type, - content_disposition, - md, - modified, - cpu_pool, - encoding, - status_code: StatusCode::OK, - _cd_map: PhantomData, - }) - } - - /// Returns reference to the underlying `File` object. - #[inline] - pub fn file(&self) -> &File { - &self.file - } - - /// Retrieve the path of this file. - /// - /// # Examples - /// - /// ```rust - /// # use std::io; - /// use actix_web::fs::NamedFile; - /// - /// # 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.path.as_path() - } - - /// Set `CpuPool` to use - #[inline] - pub fn set_cpu_pool(mut self, cpu_pool: CpuPool) -> Self { - self.cpu_pool = Some(cpu_pool); - self - } - - /// Set response **Status Code** - pub fn set_status_code(mut self, status: StatusCode) -> Self { - self.status_code = status; - self - } - - /// Set the MIME Content-Type for serving this file. By default - /// the Content-Type is inferred from the filename extension. - #[inline] - pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { - self.content_type = mime_type; - self - } - - /// Set the Content-Disposition for serving this file. This allows - /// changing the inline/attachment disposition as well as the filename - /// sent to the peer. By default the disposition is `inline` for text, - /// image, and video content types, and `attachment` otherwise, and - /// the filename is taken from the path provided in the `open` method - /// after converting it to UTF-8 using - /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). - #[inline] - pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { - self.content_disposition = cd; - self - } - - /// Set content encoding for serving this file - #[inline] - pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { - self.encoding = Some(enc); - self - } - - fn etag(&self) -> Option { - // This etag format is similar to Apache's. - self.modified.as_ref().map(|mtime| { - let ino = { - #[cfg(unix)] - { - self.md.ino() - } - #[cfg(not(unix))] - { - 0 - } - }; - - let dur = mtime - .duration_since(UNIX_EPOCH) - .expect("modification time must be after epoch"); - header::EntityTag::strong(format!( - "{:x}:{:x}:{:x}:{:x}", - ino, - self.md.len(), - dur.as_secs(), - dur.subsec_nanos() - )) - }) - } - - fn last_modified(&self) -> Option { - self.modified.map(|mtime| mtime.into()) - } -} - -impl Deref for NamedFile { - type Target = File; - - fn deref(&self) -> &File { - &self.file - } -} - -impl DerefMut for NamedFile { - fn deref_mut(&mut self) -> &mut File { - &mut self.file - } -} - -/// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - None | Some(header::IfMatch::Any) => true, - Some(header::IfMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.strong_eq(some_etag) { - return true; - } - } - } - false - } - } -} - -/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - Some(header::IfNoneMatch::Any) => false, - Some(header::IfNoneMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.weak_eq(some_etag) { - return false; - } - } - } - true - } - None => true, - } -} - -impl Responder for NamedFile { - type Item = HttpResponse; - type Error = io::Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - if self.status_code != StatusCode::OK { - let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - - if let Some(current_encoding) = self.encoding { - resp.content_encoding(current_encoding); - } - let reader = ChunkedReadFile { - size: self.md.len(), - offset: 0, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), - file: Some(self.file), - fut: None, - counter: 0, - }; - return Ok(resp.streaming(reader)); - } - - if !C::is_method_allowed(req.method()) { - return Ok(HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")); - } - - let etag = if C::is_use_etag() { self.etag() } else { None }; - let last_modified = if C::is_use_last_modifier() { - self.last_modified() - } else { - None - }; - - // check preconditions - let precondition_failed = if !any_match(etag.as_ref(), req) { - true - } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = - (last_modified, req.get_header()) - { - m > since - } else { - false - }; - - // check last modified - let not_modified = if !none_match(etag.as_ref(), req) { - true - } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = - (last_modified, req.get_header()) - { - m <= since - } else { - false - }; - - let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - - if let Some(current_encoding) = self.encoding { - resp.content_encoding(current_encoding); - } - - resp.if_some(last_modified, |lm, resp| { - resp.set(header::LastModified(lm)); - }).if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); - - resp.header(header::ACCEPT_RANGES, "bytes"); - - let mut length = self.md.len(); - let mut offset = 0; - - // check for range header - if let Some(ranges) = req.headers().get(header::RANGE) { - if let Ok(rangesheader) = ranges.to_str() { - if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { - length = rangesvec[0].length; - offset = rangesvec[0].start; - resp.content_encoding(ContentEncoding::Identity); - resp.header( - header::CONTENT_RANGE, - format!( - "bytes {}-{}/{}", - offset, - offset + length - 1, - self.md.len() - ), - ); - } else { - resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); - return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); - }; - } else { - return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); - }; - }; - - resp.header(header::CONTENT_LENGTH, format!("{}", length)); - - if precondition_failed { - return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); - } else if not_modified { - return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); - } - - if *req.method() == Method::HEAD { - Ok(resp.finish()) - } else { - let reader = ChunkedReadFile { - offset, - size: length, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), - file: Some(self.file), - fut: None, - counter: 0, - }; - if offset != 0 || length != self.md.len() { - return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); - }; - Ok(resp.streaming(reader)) - } - } -} - -/// A helper created from a `std::fs::File` which reads the file -/// chunk-by-chunk on a `CpuPool`. -pub struct ChunkedReadFile { - size: u64, - offset: u64, - cpu_pool: CpuPool, - file: Option, - fut: Option>, - counter: u64, -} - -impl Stream for ChunkedReadFile { - type Item = Bytes; - type Error = Error; - - fn poll(&mut self) -> Poll, Error> { - if self.fut.is_some() { - return match self.fut.as_mut().unwrap().poll()? { - Async::Ready((file, bytes)) => { - self.fut.take(); - self.file = Some(file); - self.offset += bytes.len() as u64; - self.counter += bytes.len() as u64; - Ok(Async::Ready(Some(bytes))) - } - Async::NotReady => Ok(Async::NotReady), - }; - } - - let size = self.size; - let offset = self.offset; - let counter = self.counter; - - if size == counter { - Ok(Async::Ready(None)) - } else { - let mut file = self.file.take().expect("Use after completion"); - self.fut = Some(self.cpu_pool.spawn_fn(move || { - let max_bytes: usize; - max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; - let nbytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - if nbytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - Ok((file, Bytes::from(buf))) - })); - self.poll() - } - } -} - -type DirectoryRenderer = - Fn(&Directory, &HttpRequest) -> Result; - -/// A directory; responds with the generated directory listing. -#[derive(Debug)] -pub struct Directory { - /// Base directory - pub base: PathBuf, - /// Path of subdirectory to generate listing for - pub path: PathBuf, -} - -impl Directory { - /// Create a new directory - pub fn new(base: PathBuf, path: PathBuf) -> Directory { - Directory { base, path } - } - - /// Is this entry visible from this directory? - pub fn is_visible(&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 - } -} - -fn directory_listing( - dir: &Directory, 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 dir.path.read_dir()? { - if dir.is_visible(&entry) { - let entry = entry.unwrap(); - let p = match entry.path().strip_prefix(&dir.path) { - Ok(p) => base.join(p), - Err(_) => continue, - }; - // show file url as relative to static path - let file_url = utf8_percent_encode(&p.to_string_lossy(), DEFAULT_ENCODE_SET) - .to_string(); - // " -- " & -- & ' -- ' < -- < > -- > - let file_name = escape_html_entity(&entry.file_name().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, file_name - ); - } else { - let _ = write!( - body, - "
  • {}
  • ", - file_url, file_name - ); - } - } else { - continue; - } - } - } - - let html = format!( - "\ - {}\ -

    {}

    \ -
      \ - {}\ -
    \n", - index_of, index_of, body - ); - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) -} - -/// Static files handling -/// -/// `StaticFile` handler must be registered with `App::handler()` method, -/// because `StaticFile` handler requires access sub-path information. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{fs, App}; -/// -/// fn main() { -/// let app = App::new() -/// .handler("/static", fs::StaticFiles::new(".").unwrap()) -/// .finish(); -/// } -/// ``` -pub struct StaticFiles { - directory: PathBuf, - index: Option, - show_index: bool, - cpu_pool: CpuPool, - default: Box>, - renderer: Box>, - _chunk_size: usize, - _follow_symlinks: bool, - _cd_map: PhantomData, -} - -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. - /// - /// `StaticFile` uses `CpuPool` for blocking filesystem operations. - /// By default pool with 20 threads is used. - /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. - pub fn new>(dir: T) -> Result, Error> { - Self::with_config(dir, DefaultConfig) - } - - /// Create new `StaticFiles` instance for specified base directory and - /// `CpuPool`. - pub fn with_pool>( - dir: T, pool: CpuPool, - ) -> Result, Error> { - Self::with_config_pool(dir, pool, DefaultConfig) - } -} - -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. - /// - /// Identical with `new` but allows to specify configiration to use. - pub fn with_config>( - dir: T, config: C, - ) -> Result, Error> { - // use default CpuPool - let pool = { DEFAULT_CPUPOOL.lock().clone() }; - - StaticFiles::with_config_pool(dir, pool, config) - } - - /// Create new `StaticFiles` instance for specified base directory with config and - /// `CpuPool`. - pub fn with_config_pool>( - dir: T, pool: CpuPool, _: C, - ) -> Result, Error> { - let dir = dir.into().canonicalize()?; - - if !dir.is_dir() { - return Err(StaticFileError::IsNotDirectory.into()); - } - - Ok(StaticFiles { - directory: dir, - index: None, - show_index: false, - cpu_pool: pool, - default: Box::new(WrapHandler::new(|_: &_| { - HttpResponse::new(StatusCode::NOT_FOUND) - })), - renderer: Box::new(directory_listing), - _chunk_size: 0, - _follow_symlinks: false, - _cd_map: PhantomData, - }) - } - - /// Show files listing for directories. - /// - /// By default show files listing is disabled. - pub fn show_files_listing(mut self) -> Self { - self.show_index = true; - self - } - - /// Set custom directory renderer - pub fn files_listing_renderer(mut self, f: F) -> Self - where - for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) - -> Result - + 'static, - { - self.renderer = Box::new(f); - self - } - - /// Set index file - /// - /// Redirects to specific index file for directory "/" instead of - /// showing files listing. - pub fn index_file>(mut self, index: T) -> StaticFiles { - self.index = Some(index.into()); - self - } - - /// Sets default handler which is used when no matched file could be found. - pub fn default_handler>(mut self, handler: H) -> StaticFiles { - self.default = Box::new(WrapHandler::new(handler)); - self - } - - fn try_handle( - &self, req: &HttpRequest, - ) -> Result, Error> { - let tail: String = req.match_info().query("tail")?; - let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; - - // full filepath - let path = self.directory.join(&relpath).canonicalize()?; - - if path.is_dir() { - if let Some(ref redir_index) = self.index { - // TODO: Don't redirect, just return the index content. - // TODO: It'd be nice if there were a good usable URL manipulation - // library - let mut new_path: String = req.path().to_owned(); - if !new_path.ends_with('/') { - new_path.push('/'); - } - new_path.push_str(redir_index); - HttpResponse::Found() - .header(header::LOCATION, new_path.as_str()) - .finish() - .respond_to(&req) - } else if self.show_index { - let dir = Directory::new(self.directory.clone(), path); - Ok((*self.renderer)(&dir, &req)?.into()) - } else { - Err(StaticFileError::IsDirectory.into()) - } - } else { - NamedFile::open_with_config(path, C::default())? - .set_cpu_pool(self.cpu_pool.clone()) - .respond_to(&req)? - .respond_to(&req) - } - } -} - -impl Handler for StaticFiles { - type Result = Result, Error>; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - self.try_handle(req).or_else(|e| { - debug!("StaticFiles: Failed to handle {}: {}", req.path(), e); - Ok(self.default.handle(req)) - }) - } -} - -/// HTTP Range header representation. -#[derive(Debug, Clone, Copy)] -struct HttpRange { - pub start: u64, - pub length: u64, -} - -static PREFIX: &'static str = "bytes="; -const PREFIX_LEN: usize = 6; - -impl HttpRange { - /// Parses Range HTTP header string as per RFC 2616. - /// - /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). - /// `size` is full size of response (file). - fn parse(header: &str, size: u64) -> Result, ()> { - if header.is_empty() { - return Ok(Vec::new()); - } - if !header.starts_with(PREFIX) { - return Err(()); - } - - let size_sig = size as i64; - let mut no_overlap = false; - - let all_ranges: Vec> = header[PREFIX_LEN..] - .split(',') - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(|ra| { - let mut start_end_iter = ra.split('-'); - - let start_str = start_end_iter.next().ok_or(())?.trim(); - let end_str = start_end_iter.next().ok_or(())?.trim(); - - if start_str.is_empty() { - // If no start is specified, end specifies the - // range start relative to the end of the file. - let mut length: i64 = try!(end_str.parse().map_err(|_| ())); - - if length > size_sig { - length = size_sig; - } - - Ok(Some(HttpRange { - start: (size_sig - length) as u64, - length: length as u64, - })) - } else { - let start: i64 = start_str.parse().map_err(|_| ())?; - - if start < 0 { - return Err(()); - } - if start >= size_sig { - no_overlap = true; - return Ok(None); - } - - let length = if end_str.is_empty() { - // If no end is specified, range extends to end of the file. - size_sig - start - } else { - let mut end: i64 = end_str.parse().map_err(|_| ())?; - - if start > end { - return Err(()); - } - - if end >= size_sig { - end = size_sig - 1; - } - - end - start + 1 - }; - - Ok(Some(HttpRange { - start: start as u64, - length: length as u64, - })) - } - }).collect::>()?; - - let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); - - if no_overlap && ranges.is_empty() { - return Err(()); - } - - Ok(ranges) - } -} - -#[cfg(test)] -mod tests { - use std::fs; - - use super::*; - use application::App; - use body::{Binary, Body}; - use http::{header, Method, StatusCode}; - use test::{self, TestRequest}; - - #[test] - fn test_file_extension_to_mime() { - let m = file_extension_to_mime("jpg"); - assert_eq!(m, mime::IMAGE_JPEG); - - let m = file_extension_to_mime("invalid extension!!"); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - - let m = file_extension_to_mime(""); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - } - - #[test] - fn test_named_file_text() { - assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[test] - fn test_named_file_set_content_type() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_content_type(mime::TEXT_XML) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/xml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[test] - fn test_named_file_image() { - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[test] - fn test_named_file_image_attachment() { - use header::{ContentDisposition, DispositionParam, DispositionType}; - let cd = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename(String::from("test.png"))], - }; - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_content_disposition(cd) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - } - - #[derive(Default)] - pub struct AllAttachmentConfig; - impl StaticFileConfig for AllAttachmentConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Attachment - } - } - - #[derive(Default)] - pub struct AllInlineConfig; - impl StaticFileConfig for AllInlineConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Inline - } - } - - #[test] - fn test_named_file_image_attachment_and_custom_config() { - let file = NamedFile::open_with_config("tests/test.png", AllAttachmentConfig) - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - - let file = NamedFile::open_with_config("tests/test.png", AllInlineConfig) - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[test] - fn test_named_file_binary() { - let mut file = NamedFile::open("tests/test.binary") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.binary\"" - ); - } - - #[test] - fn test_named_file_status_code_text() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_status_code(StatusCode::NOT_FOUND) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_named_file_ranges_status_code() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/Cargo.toml")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/Cargo.toml")) - .header(header::RANGE, "bytes=1-0") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); - } - - #[test] - fn test_named_file_content_range_headers() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".") - .unwrap() - .index_file("tests/test.binary"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes 10-20/100"); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-5") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes */100"); - } - - #[test] - fn test_named_file_content_length_headers() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".") - .unwrap() - .index_file("tests/test.binary"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "11"); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-8") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "0"); - - // Without range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .no_default_headers() - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "100"); - - // chunked - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - { - let te = response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(te, "chunked"); - } - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("tests/test.binary").unwrap()); - assert_eq!(bytes, data); - } - - #[derive(Default)] - pub struct OnlyMethodHeadConfig; - impl StaticFileConfig for OnlyMethodHeadConfig { - fn is_method_allowed(method: &Method) -> bool { - match *method { - Method::HEAD => true, - _ => false, - } - } - } - - #[test] - fn test_named_file_not_allowed() { - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::POST).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::PUT).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::GET).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_named_file_content_encoding() { - let req = TestRequest::default().method(Method::GET).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); - - assert!(file.encoding.is_none()); - let resp = file - .set_content_encoding(ContentEncoding::Identity) - .respond_to(&req) - .unwrap(); - - assert!(resp.content_encoding().is_some()); - assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); - } - - #[test] - fn test_named_file_any_method() { - let req = TestRequest::default().method(Method::POST).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_static_files() { - let mut st = StaticFiles::new(".").unwrap().show_files_listing(); - let req = TestRequest::with_uri("/missing") - .param("tail", "missing") - .finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - st.show_index = false; - let req = TestRequest::default().finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::default().param("tail", "").finish(); - - st.show_index = true; - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - 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")); - } - - #[test] - fn test_static_files_bad_directory() { - let st: Result, Error> = StaticFiles::new("missing"); - assert!(st.is_err()); - - let st: Result, Error> = StaticFiles::new("Cargo.toml"); - assert!(st.is_err()); - } - - #[test] - fn test_default_handler_file_missing() { - let st = StaticFiles::new(".") - .unwrap() - .default_handler(|_: &_| "default content"); - let req = TestRequest::with_uri("/missing") - .param("tail", "missing") - .finish(); - - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body(), - &Body::Binary(Binary::Slice(b"default content")) - ); - } - - #[test] - fn test_redirect_to_index() { - let st = StaticFiles::new(".").unwrap().index_file("index.html"); - let req = TestRequest::default().uri("/tests").finish(); - - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" - ); - - let req = TestRequest::default().uri("/tests/").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" - ); - } - - #[test] - fn test_redirect_to_index_nested() { - let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); - let req = TestRequest::default().uri("/src/client").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); - assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/src/client/mod.rs" - ); - } - - #[test] - fn integration_redirect_to_index_with_prefix() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .prefix("public") - .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) - }); - - let request = srv.get().uri(srv.url("/public")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/public/Cargo.toml"); - - let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/public/Cargo.toml"); - } - - #[test] - fn integration_redirect_to_index() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/test/Cargo.toml"); - - let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/test/Cargo.toml"); - } - - #[test] - fn integration_percent_encoded() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - let request = srv - .get() - .uri(srv.url("/test/%43argo.toml")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - } - - struct T(&'static str, u64, Vec); - - #[test] - fn test_parse() { - let tests = vec![ - T("", 0, vec![]), - T("", 1000, vec![]), - T("foo", 0, vec![]), - T("bytes=", 0, vec![]), - T("bytes=7", 10, vec![]), - T("bytes= 7 ", 10, vec![]), - T("bytes=1-", 0, vec![]), - T("bytes=5-4", 10, vec![]), - T("bytes=0-2,5-4", 10, vec![]), - T("bytes=2-5,4-3", 10, vec![]), - T("bytes=--5,4--3", 10, vec![]), - T("bytes=A-", 10, vec![]), - T("bytes=A- ", 10, vec![]), - T("bytes=A-Z", 10, vec![]), - T("bytes= -Z", 10, vec![]), - T("bytes=5-Z", 10, vec![]), - T("bytes=Ran-dom, garbage", 10, vec![]), - T("bytes=0x01-0x02", 10, vec![]), - T("bytes= ", 10, vec![]), - T("bytes= , , , ", 10, vec![]), - T( - "bytes=0-9", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=5-", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=0-20", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=15-,0-5", - 10, - vec![HttpRange { - start: 0, - length: 6, - }], - ), - T( - "bytes=1-2,5-", - 10, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 5, - length: 5, - }, - ], - ), - T( - "bytes=-2 , 7-", - 11, - vec![ - HttpRange { - start: 9, - length: 2, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=0-0 ,2-2, 7-", - 11, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 2, - length: 1, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=-5", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=-15", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-499", - 10000, - vec![HttpRange { - start: 0, - length: 500, - }], - ), - T( - "bytes=500-999", - 10000, - vec![HttpRange { - start: 500, - length: 500, - }], - ), - T( - "bytes=-500", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=9500-", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=0-0,-1", - 10000, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 9999, - length: 1, - }, - ], - ), - T( - "bytes=500-600,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 101, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - T( - "bytes=500-700,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 201, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - // Match Apache laxity: - T( - "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", - 11, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 4, - length: 2, - }, - HttpRange { - start: 7, - length: 2, - }, - ], - ), - ]; - - for t in tests { - let header = t.0; - let size = t.1; - let expected = t.2; - - let res = HttpRange::parse(header, size); - - if res.is_err() { - if expected.is_empty() { - continue; - } else { - assert!( - false, - "parse({}, {}) returned error {:?}", - header, - size, - res.unwrap_err() - ); - } - } - - let got = res.unwrap(); - - if got.len() != expected.len() { - assert!( - false, - "len(parseRange({}, {})) = {}, want {}", - header, - size, - got.len(), - expected.len() - ); - continue; - } - - for i in 0..expected.len() { - if got[i].start != expected[i].start { - assert!( - false, - "parseRange({}, {})[{}].start = {}, want {}", - header, size, i, got[i].start, expected[i].start - ) - } - if got[i].length != expected[i].length { - assert!( - false, - "parseRange({}, {})[{}].length = {}, want {}", - header, size, i, got[i].length, expected[i].length - ) - } - } - } - } -} diff --git a/src/handler.rs b/src/handler.rs deleted file mode 100644 index 88210fbc..00000000 --- a/src/handler.rs +++ /dev/null @@ -1,562 +0,0 @@ -use std::marker::PhantomData; -use std::ops::Deref; - -use futures::future::{err, ok, Future}; -use futures::{Async, Poll}; - -use error::Error; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use resource::DefaultResource; - -/// Trait defines object that could be registered as route handler -#[allow(unused_variables)] -pub trait Handler: 'static { - /// The type of value that handler will return. - type Result: Responder; - - /// Handle request - fn handle(&self, req: &HttpRequest) -> Self::Result; -} - -/// 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; - - /// Convert itself to `AsyncResult` or `Error`. - fn respond_to( - self, req: &HttpRequest, - ) -> Result; -} - -/// Trait implemented by types that can be extracted from request. -/// -/// Types that implement this trait can be used with `Route::with()` method. -pub trait FromRequest: Sized { - /// Configuration for conversion process - type Config: Default; - - /// Future that resolves to a Self - type Result: Into>; - - /// Convert request to a Self - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; - - /// Convert request to a Self - /// - /// This method uses default extractor configuration - fn extract(req: &HttpRequest) -> Self::Result { - Self::from_request(req, &Self::Config::default()) - } -} - -/// Combines two different responder types into a single type -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # use futures::future::Future; -/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse}; -/// use futures::future::result; -/// -/// type RegisterResult = -/// Either>>; -/// -/// fn index(req: HttpRequest) -> RegisterResult { -/// if is_a_variant() { -/// // <- choose variant A -/// Either::A(HttpResponse::BadRequest().body("Bad data")) -/// } else { -/// Either::B( -/// // <- variant B -/// result(Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!"))) -/// .responder(), -/// ) -/// } -/// } -/// # fn is_a_variant() -> bool { true } -/// # fn main() {} -/// ``` -#[derive(Debug)] -pub enum Either { - /// First branch of the type - A(A), - /// Second branch of the type - B(B), -} - -impl Responder for Either -where - A: Responder, - B: Responder, -{ - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - match self { - Either::A(a) => match a.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - Either::B(b) => match b.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - } - } -} - -impl Future for Either -where - A: Future, - B: Future, -{ - type Item = I; - type Error = E; - - fn poll(&mut self) -> Poll { - match *self { - Either::A(ref mut fut) => fut.poll(), - Either::B(ref mut fut) => fut.poll(), - } - } -} - -impl Responder for Option -where - T: Responder, -{ - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - match self { - Some(t) => match t.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()), - } - } -} - -/// Convenience trait that converts `Future` object to a `Boxed` future -/// -/// For example loading json from request's body is async operation. -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # #[macro_use] extern crate serde_derive; -/// use actix_web::{ -/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse, -/// }; -/// 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 -/// Ok(HttpResponse::Ok().into()) -/// }) -/// // Construct boxed future by using `AsyncResponder::responder()` method -/// .responder() -/// } -/// # fn main() {} -/// ``` -pub trait AsyncResponder: Sized { - /// Convert to a boxed future - 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, - R: Responder + 'static, -{ - type Result = R; - - fn handle(&self, req: &HttpRequest) -> R { - (self)(req) - } -} - -/// Represents async result -/// -/// Result could be in tree different forms. -/// * Ok(T) - ready item -/// * Err(E) - error happen during reply process -/// * Future - reply process completes in the future -pub struct AsyncResult(Option>); - -impl Future for AsyncResult { - type Item = I; - type Error = E; - - fn poll(&mut self) -> Poll { - let res = self.0.take().expect("use after resolve"); - match res { - AsyncResultItem::Ok(msg) => Ok(Async::Ready(msg)), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(mut fut) => match fut.poll() { - Ok(Async::NotReady) => { - self.0 = Some(AsyncResultItem::Future(fut)); - Ok(Async::NotReady) - } - Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)), - Err(err) => Err(err), - }, - } - } -} - -pub(crate) enum AsyncResultItem { - Ok(I), - Err(E), - Future(Box>), -} - -impl AsyncResult { - /// Create async response - #[inline] - pub fn async(fut: Box>) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Future(fut))) - } - - /// Send response - #[inline] - pub fn ok>(ok: R) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Ok(ok.into()))) - } - - /// Send error - #[inline] - pub fn err>(err: R) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Err(err.into()))) - } - - #[inline] - pub(crate) fn into(self) -> AsyncResultItem { - self.0.expect("use after resolve") - } - - #[cfg(test)] - pub(crate) fn as_msg(&self) -> &I { - match self.0.as_ref().unwrap() { - &AsyncResultItem::Ok(ref resp) => resp, - _ => panic!(), - } - } - - #[cfg(test)] - pub(crate) fn as_err(&self) -> Option<&E> { - match self.0.as_ref().unwrap() { - &AsyncResultItem::Err(ref err) => Some(err), - _ => None, - } - } -} - -impl Responder for AsyncResult { - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, _: &HttpRequest, - ) -> Result, Error> { - Ok(self) - } -} - -impl Responder for HttpResponse { - type Item = AsyncResult; - type Error = Error; - - #[inline] - fn respond_to( - self, _: &HttpRequest, - ) -> Result, Error> { - Ok(AsyncResult(Some(AsyncResultItem::Ok(self)))) - } -} - -impl From for AsyncResult { - #[inline] - fn from(resp: T) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Ok(resp))) - } -} - -impl> Responder for Result { - type Item = ::Item; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - match self { - Ok(val) => match val.respond_to(req) { - Ok(val) => Ok(val), - Err(err) => Err(err.into()), - }, - Err(err) => Err(err.into()), - } - } -} - -impl> From, E>> for AsyncResult { - #[inline] - fn from(res: Result, E>) -> Self { - match res { - Ok(val) => val, - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl> From> for AsyncResult { - #[inline] - fn from(res: Result) -> Self { - match res { - Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))), - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl From>, E>> for AsyncResult -where - T: 'static, - E: Into + 'static, -{ - #[inline] - fn from(res: Result>, E>) -> Self { - match res { - Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new( - fut.map_err(|e| e.into()), - )))), - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl From>> for AsyncResult { - #[inline] - fn from(fut: Box>) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Future(fut))) - } -} - -/// Convenience type alias -pub type FutureResponse = Box>; - -impl Responder for Box> -where - I: Responder + 'static, - E: Into + 'static, -{ - type Item = AsyncResult; - type Error = Error; - - #[inline] - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - let req = req.clone(); - let fut = self - .map_err(|e| e.into()) - .then(move |r| match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => ok(resp), - _ => panic!("Nested async replies are not supported"), - }, - Err(e) => err(e), - }); - Ok(AsyncResult::async(Box::new(fut))) - } -} - -pub(crate) trait RouteHandler: 'static { - fn handle(&self, &HttpRequest) -> AsyncResult; - - fn has_default_resource(&self) -> bool { - false - } - - fn default_resource(&mut self, _: DefaultResource) {} - - fn finish(&mut self) {} -} - -/// Route handler wrapper for Handler -pub(crate) struct WrapHandler -where - H: Handler, - R: Responder, - S: 'static, -{ - h: H, - s: PhantomData, -} - -impl WrapHandler -where - H: Handler, - R: Responder, - S: 'static, -{ - pub fn new(h: H) -> Self { - WrapHandler { h, s: PhantomData } - } -} - -impl RouteHandler for WrapHandler -where - H: Handler, - R: Responder + 'static, - S: 'static, -{ - fn handle(&self, req: &HttpRequest) -> AsyncResult { - match self.h.handle(req).respond_to(req) { - Ok(reply) => reply.into(), - Err(err) => AsyncResult::err(err.into()), - } - } -} - -/// Async route handler -pub(crate) struct AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - h: Box, - s: PhantomData, -} - -impl AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - pub fn new(h: H) -> Self { - AsyncHandler { - h: Box::new(h), - s: PhantomData, - } - } -} - -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) -> AsyncResult { - let req = req.clone(); - let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| { - match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => Either::A(ok(resp)), - AsyncResultItem::Err(e) => Either::A(err(e)), - AsyncResultItem::Future(fut) => Either::B(fut), - }, - Err(e) => Either::A(err(e)), - } - }); - AsyncResult::async(Box::new(fut)) - } -} - -/// Access an application state -/// -/// `S` - application state type -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Path, State}; -/// -/// /// Application state -/// struct MyApp { -/// msg: &'static str, -/// } -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract path info using serde -/// fn index(state: State, path: Path) -> String { -/// format!("{} {}!", state.msg, path.username) -/// } -/// -/// fn main() { -/// let app = App::with_state(MyApp { msg: "Welcome" }).resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -pub struct State(HttpRequest); - -impl Deref for State { - type Target = S; - - fn deref(&self) -> &S { - self.0.state() - } -} - -impl FromRequest for State { - type Config = (); - type Result = State; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - State(req.clone()) - } -} diff --git a/src/helpers.rs b/src/helpers.rs deleted file mode 100644 index e82d6161..00000000 --- a/src/helpers.rs +++ /dev/null @@ -1,571 +0,0 @@ -//! Various helpers - -use http::{header, StatusCode}; -use regex::Regex; - -use handler::Handler; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -/// Path normalization helper -/// -/// By normalizing it means: -/// -/// - Add a trailing slash to the path. -/// - Remove a trailing slash from 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::*; -/// use actix_web::http::NormalizePath; -/// -/// # fn index(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() -/// # } -/// fn main() { -/// let app = App::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, - re_merge: Regex, - redirect: StatusCode, - not_found: StatusCode, -} - -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, - merge: true, - re_merge: Regex::new("//+").unwrap(), - redirect: StatusCode::MOVED_PERMANENTLY, - not_found: StatusCode::NOT_FOUND, - } - } -} - -impl NormalizePath { - /// Create new `NormalizePath` instance - pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { - NormalizePath { - append, - merge, - redirect, - re_merge: Regex::new("//+").unwrap(), - not_found: StatusCode::NOT_FOUND, - } - } -} - -impl Handler for NormalizePath { - type Result = HttpResponse; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - 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 req.resource().has_prefixed_resource(p.as_ref()) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_ref()) - .finish(); - } - // merge slashes and append trailing slash - if self.append && !p.ends_with('/') { - let p = p.as_ref().to_owned() + "/"; - if req.resource().has_prefixed_resource(&p) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); - } - } - - // try to remove trailing slash - if p.ends_with('/') { - let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_resource(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } else if p.ends_with('/') { - // try to remove trailing slash - let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_resource(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } - // append trailing slash - if self.append && !req.path().ends_with('/') { - let p = req.path().to_owned() + "/"; - if req.resource().has_prefixed_resource(&p) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); - } - } - HttpResponse::new(self.not_found) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use application::App; - use http::{header, Method}; - use test::TestRequest; - - fn index(_req: &HttpRequest) -> HttpResponse { - HttpResponse::new(StatusCode::OK) - } - - #[test] - fn test_normalize_path_trailing_slashes() { - let app = App::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/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), - ("/resource2/", "", StatusCode::OK), - ("/resource1?p1=1&p2=2", "", StatusCode::OK), - ( - "/resource1/?p1=1&p2=2", - "/resource1?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/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 = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } - - #[test] - fn test_prefixed_normalize_path_trailing_slashes() { - let app = App::new() - .prefix("/test") - .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![ - ("/test/resource1", "", StatusCode::OK), - ( - "/test/resource1/", - "/test/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/test/resource2", - "/test/resource2/", - StatusCode::MOVED_PERMANENTLY, - ), - ("/test/resource2/", "", StatusCode::OK), - ("/test/resource1?p1=1&p2=2", "", StatusCode::OK), - ( - "/test/resource1/?p1=1&p2=2", - "/test/resource1?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/test/resource2?p1=1&p2=2", - "/test/resource2/?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ("/test/resource2/?p1=1&p2=2", "", StatusCode::OK), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - 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 = App::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::MOVED_PERMANENTLY), - ("/resource2", StatusCode::NOT_FOUND), - ("/resource2/", StatusCode::OK), - ("/resource1?p1=1&p2=2", StatusCode::OK), - ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), - ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), - ("/resource2/?p1=1&p2=2", StatusCode::OK), - ]; - for (path, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - } - } - - #[test] - fn test_normalize_path_merge_slashes() { - let app = App::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/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), - ( - "//resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b//", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ("/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", - "/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", - "/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", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - 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 = App::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/", - "/resource1/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", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/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", - "/resource1/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, - ), - ( - "///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", - "/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", - "/resource1/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, - ), - ( - "/////resource2/a///b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - 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/httpmessage.rs b/src/httpmessage.rs index 60f77b07..8c972bd1 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -15,7 +15,6 @@ use error::{ }; use header::Header; use json::JsonBody; -use multipart::Multipart; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { @@ -203,46 +202,6 @@ pub trait HttpMessage: Sized { JsonBody::new(self) } - /// Return stream to http payload processes as multipart. - /// - /// Content-type: multipart/form-data; - /// - /// ## Server example - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate env_logger; - /// # extern crate futures; - /// # use std::str; - /// # use actix_web::*; - /// # use actix_web::actix::fut::FinishStream; - /// # 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(|_| HttpResponse::Ok().into()) - /// .responder() - /// } - /// # fn main() {} - /// ``` - fn multipart(&self) -> Multipart { - let boundary = Multipart::boundary(self.headers()); - Multipart::new(boundary, self.payload()) - } - /// Return stream of lines. fn readlines(&self) -> Readlines { Readlines::new(self) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 59815c58..73de380a 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -13,12 +13,10 @@ use serde::Serialize; use serde_json; use body::Body; -use client::ClientResponse; use error::Error; -use handler::Responder; use header::{ContentEncoding, Header, IntoHeaderValue}; use httpmessage::HttpMessage; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -720,16 +718,6 @@ impl From for HttpResponse { } } -impl Responder for HttpResponseBuilder { - type Item = HttpResponse; - type Error = Error; - - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> Result { - Ok(self.finish()) - } -} - impl From<&'static str> for HttpResponse { fn from(val: &'static str) -> Self { HttpResponse::Ok() @@ -738,18 +726,6 @@ impl From<&'static str> for HttpResponse { } } -impl Responder for &'static str { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl From<&'static [u8]> for HttpResponse { fn from(val: &'static [u8]) -> Self { HttpResponse::Ok() @@ -758,18 +734,6 @@ impl From<&'static [u8]> for HttpResponse { } } -impl Responder for &'static [u8] { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: String) -> Self { HttpResponse::Ok() @@ -778,18 +742,6 @@ impl From for HttpResponse { } } -impl Responder for String { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl<'a> From<&'a String> for HttpResponse { fn from(val: &'a String) -> Self { HttpResponse::build(StatusCode::OK) @@ -798,18 +750,6 @@ impl<'a> From<&'a String> for HttpResponse { } } -impl<'a> Responder for &'a String { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: Bytes) -> Self { HttpResponse::Ok() @@ -818,18 +758,6 @@ impl From for HttpResponse { } } -impl Responder for Bytes { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - impl From for HttpResponse { fn from(val: BytesMut) -> Self { HttpResponse::Ok() @@ -838,40 +766,6 @@ impl From for HttpResponse { } } -impl Responder for BytesMut { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -/// Create `HttpResponseBuilder` from `ClientResponse` -/// -/// It is useful for proxy response. This implementation -/// copies all responses's headers and status. -impl<'a> From<&'a ClientResponse> for HttpResponseBuilder { - fn from(resp: &'a ClientResponse) -> HttpResponseBuilder { - let mut builder = HttpResponse::build(resp.status()); - for (key, value) in resp.headers() { - builder.header(key.clone(), value.clone()); - } - builder - } -} - -impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { - fn from(req: &'a HttpRequest) -> HttpResponseBuilder { - req.request() - .server_settings() - .get_response_builder(StatusCode::OK) - } -} - #[derive(Debug)] struct InnerHttpResponse { version: Option, @@ -921,7 +815,7 @@ impl InnerHttpResponse { let body = match mem::replace(&mut self.body, Body::Empty) { Body::Empty => None, Body::Binary(mut bin) => Some(bin.take()), - Body::Streaming(_) | Body::Actor(_) => { + Body::Streaming(_) => { error!("Streaming or Actor body is not support by error response"); None } diff --git a/src/json.rs b/src/json.rs index 178143f1..04dd369e 100644 --- a/src/json.rs +++ b/src/json.rs @@ -11,10 +11,9 @@ use serde::Serialize; use serde_json; use error::{Error, JsonPayloadError}; -use handler::{FromRequest, Responder}; use http::StatusCode; use httpmessage::HttpMessage; -use httprequest::HttpRequest; +// use httprequest::HttpRequest; use httpresponse::HttpResponse; /// Json helper @@ -116,102 +115,6 @@ where } } -impl Responder for Json { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - let body = serde_json::to_string(&self.0)?; - - Ok(req - .build_response(StatusCode::OK) - .content_type("application/json") - .body(body)) - } -} - -impl FromRequest for Json -where - T: DeserializeOwned + 'static, - S: 'static, -{ - type Config = JsonConfig; - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - Box::new( - JsonBody::new(req) - .limit(cfg.limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Json), - ) - } -} - -/// Json extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, http, App, HttpResponse, Json, Result}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::POST) -/// .with_config(index, |cfg| { -/// cfg.0.limit(4096) // <- change json extractor configuration -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }); -/// }) -/// }); -/// } -/// ``` -pub struct JsonConfig { - limit: usize, - ehandler: Rc) -> Error>, -} - -impl JsonConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self - where - F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl Default for JsonConfig { - fn default() -> Self { - JsonConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - } - } -} - /// Request payload json parser that resolves to a deserialized `T` value. /// /// Returns error: diff --git a/src/lib.rs b/src/lib.rs index f494c05d..6df1a770 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,13 +77,11 @@ //! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. //! -#![cfg_attr(actix_nightly, feature( - specialization, // for impl ErrorResponse for std::error::Error - extern_prelude, - tool_lints, -))] +#![cfg_attr(actix_nightly, feature(tool_lints))] #![warn(missing_docs)] +#![allow(unused_imports, unused_variables, dead_code)] +extern crate actix; #[macro_use] extern crate log; extern crate base64; @@ -140,109 +138,36 @@ extern crate serde_json; extern crate smallvec; extern crate actix_net; -#[macro_use] -extern crate actix as actix_inner; #[cfg(test)] #[macro_use] extern crate serde_derive; -#[cfg(feature = "tls")] -extern crate native_tls; -#[cfg(feature = "tls")] -extern crate tokio_tls; - -#[cfg(feature = "openssl")] -extern crate openssl; -#[cfg(feature = "openssl")] -extern crate tokio_openssl; - -#[cfg(feature = "rust-tls")] -extern crate rustls; -#[cfg(feature = "rust-tls")] -extern crate tokio_rustls; -#[cfg(feature = "rust-tls")] -extern crate webpki; -#[cfg(feature = "rust-tls")] -extern crate webpki_roots; - -mod application; mod body; -mod context; -mod de; mod extensions; -mod extractor; -mod handler; mod header; -mod helpers; mod httpcodes; mod httpmessage; -mod httprequest; +//mod httprequest; mod httpresponse; mod info; mod json; -mod param; mod payload; -mod pipeline; -mod resource; -mod route; -mod router; -mod scope; mod uri; -mod with; -pub mod client; pub mod error; -pub mod fs; -pub mod middleware; -pub mod multipart; -pub mod pred; pub mod server; -pub mod test; -pub mod ws; -pub use application::App; +//pub mod test; +//pub mod ws; pub use body::{Binary, Body}; -pub use context::HttpContext; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; -pub use extractor::{Form, Path, Query}; -pub use handler::{ - AsyncResponder, Either, FromRequest, FutureResponse, Responder, State, -}; pub use httpmessage::HttpMessage; -pub use httprequest::HttpRequest; +//pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; -pub use scope::Scope; pub use server::Request; -pub mod actix { - //! Re-exports [actix's](https://docs.rs/actix/) prelude - - extern crate actix; - pub use self::actix::actors::resolver; - pub use self::actix::actors::signal; - pub use self::actix::fut; - pub use self::actix::msgs; - pub use self::actix::prelude::*; - pub use self::actix::{run, spawn}; -} - -#[cfg(feature = "openssl")] -pub(crate) const HAS_OPENSSL: bool = true; -#[cfg(not(feature = "openssl"))] -pub(crate) const HAS_OPENSSL: bool = false; - -#[cfg(feature = "tls")] -pub(crate) const HAS_TLS: bool = true; -#[cfg(not(feature = "tls"))] -pub(crate) const HAS_TLS: bool = false; - -#[cfg(feature = "rust-tls")] -pub(crate) const HAS_RUSTLS: bool = true; -#[cfg(not(feature = "rust-tls"))] -pub(crate) const HAS_RUSTLS: bool = false; - pub mod dev { //! The `actix-web` prelude for library developers //! @@ -255,19 +180,11 @@ pub mod dev { //! ``` pub use body::BodyStream; - pub use context::Drain; - pub use extractor::{FormConfig, PayloadConfig}; - pub use handler::{AsyncResult, Handler}; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; pub use info::ConnectionInfo; - pub use json::{JsonBody, JsonConfig}; - pub use param::{FromParam, Params}; + pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; - pub use pipeline::Pipeline; - pub use resource::Resource; - pub use route::Route; - pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; } pub mod http { @@ -281,8 +198,6 @@ pub mod http { pub use cookie::{Cookie, CookieBuilder}; - pub use helpers::NormalizePath; - /// Various http headers pub mod header { pub use header::*; diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs deleted file mode 100644 index 953f2911..00000000 --- a/src/middleware/cors.rs +++ /dev/null @@ -1,1183 +0,0 @@ -//! Cross-origin resource sharing (CORS) for Actix applications -//! -//! CORS middleware could be used with application and with resource. -//! First you need to construct CORS middleware instance. -//! -//! To construct a cors: -//! -//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -//! 2. Use any of the builder methods to set fields in the backend. -//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -//! constructed backend. -//! -//! Cors middleware could be used as parameter for `App::middleware()` or -//! `Resource::middleware()` methods. But you have to use -//! `Cors::for_app()` method to support *preflight* OPTIONS request. -//! -//! -//! # Example -//! -//! ```rust -//! # extern crate actix_web; -//! use actix_web::middleware::cors::Cors; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; -//! -//! fn index(mut req: HttpRequest) -> &'static str { -//! "Hello world" -//! } -//! -//! fn main() { -//! let app = App::new().configure(|app| { -//! Cors::for_app(app) // <- Construct CORS middleware builder -//! .allowed_origin("https://www.rust-lang.org/") -//! .allowed_methods(vec!["GET", "POST"]) -//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) -//! .allowed_header(http::header::CONTENT_TYPE) -//! .max_age(3600) -//! .resource("/index.html", |r| { -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); -//! }) -//! .register() -//! }); -//! } -//! ``` -//! In this example custom *CORS* middleware get registered for "/index.html" -//! endpoint. -//! -//! Cors middleware automatically handle *OPTIONS* preflight request. -use std::collections::HashSet; -use std::iter::FromIterator; -use std::rc::Rc; - -use http::header::{self, HeaderName, HeaderValue}; -use http::{self, HttpTryFrom, Method, StatusCode, Uri}; - -use application::App; -use error::{ResponseError, Result}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; -use resource::Resource; -use router::ResourceDef; -use server::Request; - -/// A set of errors that can occur during processing CORS -#[derive(Debug, Fail)] -pub enum CorsError { - /// The HTTP request header `Origin` is required but was not provided - #[fail( - display = "The HTTP request header `Origin` is required but was not provided" - )] - MissingOrigin, - /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")] - BadOrigin, - /// The request header `Access-Control-Request-Method` is required but is - /// missing - #[fail( - display = "The request header `Access-Control-Request-Method` is required but is missing" - )] - MissingRequestMethod, - /// The request header `Access-Control-Request-Method` has an invalid value - #[fail( - display = "The request header `Access-Control-Request-Method` has an invalid value" - )] - BadRequestMethod, - /// The request header `Access-Control-Request-Headers` has an invalid - /// value - #[fail( - display = "The request header `Access-Control-Request-Headers` has an invalid value" - )] - BadRequestHeaders, - /// The request header `Access-Control-Request-Headers` is required but is - /// missing. - #[fail( - display = "The request header `Access-Control-Request-Headers` is required but is - missing" - )] - MissingRequestHeaders, - /// Origin is not allowed to make this request - #[fail(display = "Origin is not allowed to make this request")] - OriginNotAllowed, - /// Requested method is not allowed - #[fail(display = "Requested method is not allowed")] - MethodNotAllowed, - /// One or more headers requested are not allowed - #[fail(display = "One or more headers requested are not allowed")] - HeadersNotAllowed, -} - -impl ResponseError for CorsError { - fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self)) - } -} - -/// An enum signifying that some of type T is allowed, or `All` (everything is -/// allowed). -/// -/// `Default` is implemented for this enum and is `All`. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum AllOrSome { - /// Everything is allowed. Usually equivalent to the "*" value. - All, - /// Only some of `T` is allowed - Some(T), -} - -impl Default for AllOrSome { - fn default() -> Self { - AllOrSome::All - } -} - -impl AllOrSome { - /// Returns whether this is an `All` variant - pub fn is_all(&self) -> bool { - match *self { - AllOrSome::All => true, - AllOrSome::Some(_) => false, - } - } - - /// Returns whether this is a `Some` variant - pub fn is_some(&self) -> bool { - !self.is_all() - } - - /// Returns &T - pub fn as_ref(&self) -> Option<&T> { - match *self { - AllOrSome::All => None, - AllOrSome::Some(ref t) => Some(t), - } - } -} - -/// `Middleware` for Cross-origin resource sharing support -/// -/// The Cors struct contains the settings for CORS requests to be validated and -/// for responses to be generated. -#[derive(Clone)] -pub struct Cors { - inner: Rc, -} - -struct Inner { - methods: HashSet, - origins: AllOrSome>, - origins_str: Option, - headers: AllOrSome>, - expose_hdrs: Option, - max_age: Option, - preflight: bool, - send_wildcard: bool, - supports_credentials: bool, - vary_header: bool, -} - -impl Default for Cors { - fn default() -> Cors { - let inner = Inner { - origins: AllOrSome::default(), - origins_str: None, - methods: HashSet::from_iter( - vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ].into_iter(), - ), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }; - Cors { - inner: Rc::new(inner), - } - } -} - -impl Cors { - /// Build a new CORS middleware instance - pub fn build() -> CorsBuilder<()> { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: None, - } - } - - /// Create CorsBuilder for a specified application. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .resource("/resource", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn for_app(app: App) -> CorsBuilder { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: Some(app), - } - } - - /// This method register cors middleware with resource and - /// adds route for *OPTIONS* preflight requests. - /// - /// It is possible to register *Cors* middleware with - /// `Resource::middleware()` method, but in that case *Cors* - /// middleware wont be able to handle *OPTIONS* requests. - pub fn register(self, resource: &mut Resource) { - resource - .method(Method::OPTIONS) - .h(|_: &_| HttpResponse::Ok()); - resource.middleware(self); - } - - fn validate_origin(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ORIGIN) { - if let Ok(origin) = hdr.to_str() { - return match self.inner.origins { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_origins) => allowed_origins - .get(origin) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::OriginNotAllowed), - }; - } - Err(CorsError::BadOrigin) - } else { - return match self.inner.origins { - AllOrSome::All => Ok(()), - _ => Err(CorsError::MissingOrigin), - }; - } - } - - fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { - if let Ok(meth) = hdr.to_str() { - if let Ok(method) = Method::try_from(meth) { - return self - .inner - .methods - .get(&method) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::MethodNotAllowed); - } - } - Err(CorsError::BadRequestMethod) - } else { - Err(CorsError::MissingRequestMethod) - } - } - - fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> { - match self.inner.headers { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - if let Ok(headers) = hdr.to_str() { - let mut hdrs = HashSet::new(); - for hdr in headers.split(',') { - match HeaderName::try_from(hdr.trim()) { - Ok(hdr) => hdrs.insert(hdr), - Err(_) => return Err(CorsError::BadRequestHeaders), - }; - } - - if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { - return Err(CorsError::HeadersNotAllowed); - } - return Ok(()); - } - Err(CorsError::BadRequestHeaders) - } else { - Err(CorsError::MissingRequestHeaders) - } - } - } - } -} - -impl Middleware for Cors { - fn start(&self, req: &HttpRequest) -> Result { - if self.inner.preflight && Method::OPTIONS == *req.method() { - self.validate_origin(req)?; - self.validate_allowed_method(&req)?; - self.validate_allowed_headers(&req)?; - - // allowed headers - let headers = if let Some(headers) = self.inner.headers.as_ref() { - Some( - HeaderValue::try_from( - &headers - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).unwrap(), - ) - } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - Some(hdr.clone()) - } else { - None - }; - - Ok(Started::Response( - HttpResponse::Ok() - .if_some(self.inner.max_age.as_ref(), |max_age, resp| { - let _ = resp.header( - header::ACCESS_CONTROL_MAX_AGE, - format!("{}", max_age).as_str(), - ); - }).if_some(headers, |headers, resp| { - let _ = - resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); - }).if_true(self.inner.origins.is_all(), |resp| { - if self.inner.send_wildcard { - resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - } else { - let origin = req.headers().get(header::ORIGIN).unwrap(); - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - origin.clone(), - ); - } - }).if_true(self.inner.origins.is_some(), |resp| { - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); - }).if_true(self.inner.supports_credentials, |resp| { - resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); - }).header( - header::ACCESS_CONTROL_ALLOW_METHODS, - &self - .inner - .methods - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).finish(), - )) - } else { - // Only check requests with a origin header. - if req.headers().contains_key(header::ORIGIN) { - self.validate_origin(req)?; - } - - Ok(Started::Done) - } - } - - fn response( - &self, req: &HttpRequest, mut resp: HttpResponse, - ) -> Result { - match self.inner.origins { - AllOrSome::All => { - if self.inner.send_wildcard { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - HeaderValue::from_static("*"), - ); - } else if let Some(origin) = req.headers().get(header::ORIGIN) { - resp.headers_mut() - .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); - } - } - AllOrSome::Some(_) => { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); - } - } - - if let Some(ref expose) = self.inner.expose_hdrs { - resp.headers_mut().insert( - header::ACCESS_CONTROL_EXPOSE_HEADERS, - HeaderValue::try_from(expose.as_str()).unwrap(), - ); - } - if self.inner.supports_credentials { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_CREDENTIALS, - HeaderValue::from_static("true"), - ); - } - if self.inner.vary_header { - let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) { - let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); - val.extend(hdr.as_bytes()); - val.extend(b", Origin"); - HeaderValue::try_from(&val[..]).unwrap() - } else { - HeaderValue::from_static("Origin") - }; - resp.headers_mut().insert(header::VARY, value); - } - Ok(Response::Done(resp)) - } -} - -/// Structure that follows the builder pattern for building `Cors` middleware -/// structs. -/// -/// To construct a cors: -/// -/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -/// 2. Use any of the builder methods to set fields in the backend. -/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -/// constructed backend. -/// -/// # Example -/// -/// ```rust -/// # extern crate http; -/// # extern crate actix_web; -/// use actix_web::middleware::cors; -/// use http::header; -/// -/// # fn main() { -/// let cors = cors::Cors::build() -/// .allowed_origin("https://www.rust-lang.org/") -/// .allowed_methods(vec!["GET", "POST"]) -/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) -/// .allowed_header(header::CONTENT_TYPE) -/// .max_age(3600) -/// .finish(); -/// # } -/// ``` -pub struct CorsBuilder { - cors: Option, - methods: bool, - error: Option, - expose_hdrs: HashSet, - resources: Vec>, - app: Option>, -} - -fn cors<'a>( - parts: &'a mut Option, err: &Option, -) -> Option<&'a mut Inner> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl CorsBuilder { - /// Add an origin that are allowed to make requests. - /// Will be verified against the `Origin` request header. - /// - /// When `All` is set, and `send_wildcard` is set, "*" will be sent in - /// the `Access-Control-Allow-Origin` response header. Otherwise, the - /// client's `Origin` request header will be echoed back in the - /// `Access-Control-Allow-Origin` response header. - /// - /// When `Some` is set, the client's `Origin` request header will be - /// checked in a case-sensitive manner. - /// - /// This is the `list of origins` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `All`. - /// - /// Builder panics if supplied origin is not valid uri. - pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - match Uri::try_from(origin) { - Ok(_) => { - if cors.origins.is_all() { - cors.origins = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut origins) = cors.origins { - origins.insert(origin.to_owned()); - } - } - Err(e) => { - self.error = Some(e.into()); - } - } - } - self - } - - /// Set a list of methods which the allowed origins are allowed to access - /// for requests. - /// - /// This is the `list of methods` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]` - pub fn allowed_methods(&mut self, methods: U) -> &mut CorsBuilder - where - U: IntoIterator, - Method: HttpTryFrom, - { - self.methods = true; - if let Some(cors) = cors(&mut self.cors, &self.error) { - for m in methods { - match Method::try_from(m) { - Ok(method) => { - cors.methods.insert(method); - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - } - self - } - - /// Set an allowed header - pub fn allowed_header(&mut self, header: H) -> &mut CorsBuilder - where - HeaderName: HttpTryFrom, - { - if let Some(cors) = cors(&mut self.cors, &self.error) { - match HeaderName::try_from(header) { - Ok(method) => { - if cors.headers.is_all() { - cors.headers = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut headers) = cors.headers { - headers.insert(method); - } - } - Err(e) => self.error = Some(e.into()), - } - } - self - } - - /// Set a list of header field names which can be used when - /// this resource is accessed by allowed origins. - /// - /// If `All` is set, whatever is requested by the client in - /// `Access-Control-Request-Headers` will be echoed back in the - /// `Access-Control-Allow-Headers` header. - /// - /// This is the `list of headers` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `All`. - pub fn allowed_headers(&mut self, headers: U) -> &mut CorsBuilder - where - U: IntoIterator, - HeaderName: HttpTryFrom, - { - if let Some(cors) = cors(&mut self.cors, &self.error) { - for h in headers { - match HeaderName::try_from(h) { - Ok(method) => { - if cors.headers.is_all() { - cors.headers = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut headers) = cors.headers { - headers.insert(method); - } - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - } - self - } - - /// Set a list of headers which are safe to expose to the API of a CORS API - /// specification. This corresponds to the - /// `Access-Control-Expose-Headers` response header. - /// - /// This is the `list of exposed headers` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// This defaults to an empty set. - pub fn expose_headers(&mut self, headers: U) -> &mut CorsBuilder - where - U: IntoIterator, - HeaderName: HttpTryFrom, - { - for h in headers { - match HeaderName::try_from(h) { - Ok(method) => { - self.expose_hdrs.insert(method); - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - self - } - - /// Set a maximum time for which this CORS request maybe cached. - /// This value is set as the `Access-Control-Max-Age` header. - /// - /// This defaults to `None` (unset). - pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.max_age = Some(max_age) - } - self - } - - /// Set a wildcard origins - /// - /// If send wildcard is set and the `allowed_origins` parameter is `All`, a - /// wildcard `Access-Control-Allow-Origin` response header is sent, - /// rather than the request’s `Origin` header. - /// - /// This is the `supports credentials flag` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// This **CANNOT** be used in conjunction with `allowed_origins` set to - /// `All` and `allow_credentials` set to `true`. Depending on the mode - /// of usage, this will either result in an `Error:: - /// CredentialsWithWildcardOrigin` error during actix launch or runtime. - /// - /// Defaults to `false`. - pub fn send_wildcard(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.send_wildcard = true - } - self - } - - /// Allows users to make authenticated requests - /// - /// If true, injects the `Access-Control-Allow-Credentials` header in - /// responses. This allows cookies and credentials to be submitted - /// across domains. - /// - /// This option cannot be used in conjunction with an `allowed_origin` set - /// to `All` and `send_wildcards` set to `true`. - /// - /// Defaults to `false`. - /// - /// Builder panics if credentials are allowed, but the Origin is set to "*". - /// This is not allowed by W3C - pub fn supports_credentials(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.supports_credentials = true - } - self - } - - /// Disable `Vary` header support. - /// - /// When enabled the header `Vary: Origin` will be returned as per the W3 - /// implementation guidelines. - /// - /// Setting this header when the `Access-Control-Allow-Origin` is - /// dynamically generated (e.g. when there is more than one allowed - /// origin, and an Origin than '*' is returned) informs CDNs and other - /// caches that the CORS headers are dynamic, and cannot be cached. - /// - /// By default `vary` header support is enabled. - pub fn disable_vary_header(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.vary_header = false - } - self - } - - /// Disable *preflight* request support. - /// - /// When enabled cors middleware automatically handles *OPTIONS* request. - /// This is useful application level middleware. - /// - /// By default *preflight* support is enabled. - pub fn disable_preflight(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.preflight = false - } - self - } - - /// Configure resource for a specific path. - /// - /// This is similar to a `App::resource()` method. Except, cors middleware - /// get registered for the resource. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .allowed_methods(vec!["GET", "POST"]) - /// .allowed_header(http::header::CONTENT_TYPE) - /// .max_age(3600) - /// .resource("/resource1", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .resource("/resource2", |r| { // register another resource - /// r.method(http::Method::HEAD) - /// .f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource handler - let mut resource = Resource::new(ResourceDef::new(path)); - f(&mut resource); - - self.resources.push(resource); - self - } - - fn construct(&mut self) -> Cors { - if !self.methods { - self.allowed_methods(vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ]); - } - - if let Some(e) = self.error.take() { - panic!("{}", e); - } - - let mut cors = self.cors.take().expect("cannot reuse CorsBuilder"); - - if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() { - panic!("Credentials are allowed, but the Origin is set to \"*\""); - } - - if let AllOrSome::Some(ref origins) = cors.origins { - let s = origins - .iter() - .fold(String::new(), |s, v| format!("{}, {}", s, v)); - cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap()); - } - - if !self.expose_hdrs.is_empty() { - cors.expose_hdrs = Some( - self.expose_hdrs - .iter() - .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..] - .to_owned(), - ); - } - Cors { - inner: Rc::new(cors), - } - } - - /// Finishes building and returns the built `Cors` instance. - /// - /// This method panics in case of any configuration error. - pub fn finish(&mut self) -> Cors { - if !self.resources.is_empty() { - panic!( - "CorsBuilder::resource() was used, - to construct CORS `.register(app)` method should be used" - ); - } - self.construct() - } - - /// Finishes building Cors middleware and register middleware for - /// application - /// - /// This method panics in case of any configuration error or if non of - /// resources are registered. - pub fn register(&mut self) -> App { - if self.resources.is_empty() { - panic!("No resources are registered."); - } - - let cors = self.construct(); - let mut app = self - .app - .take() - .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); - - // register resources - for mut resource in self.resources.drain(..) { - cors.clone().register(&mut resource); - app.register_resource(resource); - } - - app - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::{self, TestRequest}; - - impl Started { - fn is_done(&self) -> bool { - match *self { - Started::Done => true, - _ => false, - } - } - fn response(self) -> HttpResponse { - match self { - Started::Response(resp) => resp, - _ => panic!(), - } - } - } - impl Response { - fn response(self) -> HttpResponse { - match self { - Response::Done(resp) => resp, - _ => panic!(), - } - } - } - - #[test] - #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] - fn cors_validates_illegal_allow_credentials() { - Cors::build() - .supports_credentials() - .send_wildcard() - .finish(); - } - - #[test] - #[should_panic(expected = "No resources are registered")] - fn no_resource() { - Cors::build() - .supports_credentials() - .send_wildcard() - .register(); - } - - #[test] - #[should_panic(expected = "Cors::for_app(app)")] - fn no_resource2() { - Cors::build() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register(); - } - - #[test] - fn validate_origin_allows_all_origins() { - let cors = Cors::default(); - let req = TestRequest::with_header("Origin", "https://www.example.com").finish(); - - assert!(cors.start(&req).ok().unwrap().is_done()) - } - - #[test] - fn test_preflight() { - let mut cors = Cors::build() - .send_wildcard() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) - .allowed_header(header::CONTENT_TYPE) - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - - assert!(cors.start(&req).is_err()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") - .method(Method::OPTIONS) - .finish(); - - assert!(cors.start(&req).is_err()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ).method(Method::OPTIONS) - .finish(); - - let resp = cors.start(&req).unwrap().response(); - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"3600"[..], - resp.headers() - .get(header::ACCESS_CONTROL_MAX_AGE) - .unwrap() - .as_bytes() - ); - //assert_eq!( - // &b"authorization,accept,content-type"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap(). - // as_bytes()); assert_eq!( - // &b"POST,GET,OPTIONS"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap(). - // as_bytes()); - - Rc::get_mut(&mut cors.inner).unwrap().preflight = false; - assert!(cors.start(&req).unwrap().is_done()); - } - - // #[test] - // #[should_panic(expected = "MissingOrigin")] - // fn test_validate_missing_origin() { - // let cors = Cors::build() - // .allowed_origin("https://www.example.com") - // .finish(); - // let mut req = HttpRequest::default(); - // cors.start(&req).unwrap(); - // } - - #[test] - #[should_panic(expected = "OriginNotAllowed")] - fn test_validate_not_allowed_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.unknown.com") - .method(Method::GET) - .finish(); - cors.start(&req).unwrap(); - } - - #[test] - fn test_validate_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::GET) - .finish(); - - assert!(cors.start(&req).unwrap().is_done()); - } - - #[test] - fn test_no_origin_response() { - let cors = Cors::build().finish(); - - let req = TestRequest::default().method(Method::GET).finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert!( - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_none() - ); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"https://www.example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - } - - #[test] - fn test_response() { - let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let cors = Cors::build() - .send_wildcard() - .disable_preflight() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(exposed_headers.clone()) - .expose_headers(exposed_headers.clone()) - .allowed_header(header::CONTENT_TYPE) - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - { - let headers = resp - .headers() - .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) - .unwrap() - .to_str() - .unwrap() - .split(',') - .map(|s| s.trim()) - .collect::>(); - - for h in exposed_headers { - assert!(headers.contains(&h.as_str())); - } - } - - let resp: HttpResponse = - HttpResponse::Ok().header(header::VARY, "Accept").finish(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"Accept, Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - let cors = Cors::build() - .disable_vary_header() - .allowed_origin("https://www.example.com") - .allowed_origin("https://www.google.com") - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - - let origins_str = resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .to_str() - .unwrap(); - - if origins_str.starts_with("https://www.example.com") { - assert_eq!( - "https://www.example.com, https://www.google.com", - origins_str - ); - } else { - assert_eq!( - "https://www.google.com, https://www.example.com", - origins_str - ); - } - } - - #[test] - fn cors_resource() { - let mut srv = test::TestServer::with_factory(|| { - App::new().configure(|app| { - Cors::for_app(app) - .allowed_origin("https://www.example.com") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register() - }) - }); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example2.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - } -} diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs deleted file mode 100644 index 02cd150d..00000000 --- a/src/middleware/csrf.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! A filter for cross-site request forgery (CSRF). -//! -//! This middleware is stateless and [based on request -//! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers). -//! -//! By default requests are allowed only if one of these is true: -//! -//! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the -//! applications responsibility to ensure these methods cannot be used to -//! execute unwanted actions. Note that upgrade requests for websockets are -//! also considered safe. -//! * The `Origin` header (added automatically by the browser) matches one -//! of the allowed origins. -//! * There is no `Origin` header but the `Referer` header matches one of -//! the allowed origins. -//! -//! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr) -//! if you want to allow requests with unprotected methods via -//! [CORS](../cors/struct.Cors.html). -//! -//! # Example -//! -//! ``` -//! # extern crate actix_web; -//! use actix_web::middleware::csrf; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; -//! -//! fn handle_post(_: &HttpRequest) -> &'static str { -//! "This action should only be triggered with requests from the same site" -//! } -//! -//! fn main() { -//! let app = App::new() -//! .middleware( -//! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"), -//! ) -//! .resource("/", |r| { -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::POST).f(handle_post); -//! }) -//! .finish(); -//! } -//! ``` -//! -//! In this example the entire application is protected from CSRF. - -use std::borrow::Cow; -use std::collections::HashSet; - -use bytes::Bytes; -use error::{ResponseError, Result}; -use http::{header, HeaderMap, HttpTryFrom, Uri}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Started}; -use server::Request; - -/// Potential cross-site request forgery detected. -#[derive(Debug, Fail)] -pub enum CsrfError { - /// The HTTP request header `Origin` was required but not provided. - #[fail(display = "Origin header required")] - MissingOrigin, - /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display = "Could not parse Origin header")] - BadOrigin, - /// The cross-site request was denied. - #[fail(display = "Cross-site request denied")] - CsrDenied, -} - -impl ResponseError for CsrfError { - fn error_response(&self) -> HttpResponse { - HttpResponse::Forbidden().body(self.to_string()) - } -} - -fn uri_origin(uri: &Uri) -> Option { - match (uri.scheme_part(), uri.host(), uri.port()) { - (Some(scheme), Some(host), Some(port)) => { - Some(format!("{}://{}:{}", scheme, host, port)) - } - (Some(scheme), Some(host), None) => Some(format!("{}://{}", scheme, host)), - _ => None, - } -} - -fn origin(headers: &HeaderMap) -> Option, CsrfError>> { - headers - .get(header::ORIGIN) - .map(|origin| { - origin - .to_str() - .map_err(|_| CsrfError::BadOrigin) - .map(|o| o.into()) - }).or_else(|| { - headers.get(header::REFERER).map(|referer| { - Uri::try_from(Bytes::from(referer.as_bytes())) - .ok() - .as_ref() - .and_then(uri_origin) - .ok_or(CsrfError::BadOrigin) - .map(|o| o.into()) - }) - }) -} - -/// A middleware that filters cross-site requests. -/// -/// To construct a CSRF filter: -/// -/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to -/// start building. -/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed -/// origins. -/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve -/// the constructed filter. -/// -/// # Example -/// -/// ``` -/// use actix_web::middleware::csrf; -/// use actix_web::App; -/// -/// # fn main() { -/// let app = App::new() -/// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com")); -/// # } -/// ``` -#[derive(Default)] -pub struct CsrfFilter { - origins: HashSet, - allow_xhr: bool, - allow_missing_origin: bool, - allow_upgrade: bool, -} - -impl CsrfFilter { - /// Start building a `CsrfFilter`. - pub fn new() -> CsrfFilter { - CsrfFilter { - origins: HashSet::new(), - allow_xhr: false, - allow_missing_origin: false, - allow_upgrade: false, - } - } - - /// Add an origin that is allowed to make requests. Will be verified - /// against the `Origin` request header. - pub fn allowed_origin>(mut self, origin: T) -> CsrfFilter { - self.origins.insert(origin.into()); - self - } - - /// Allow all requests with an `X-Requested-With` header. - /// - /// A cross-site attacker should not be able to send requests with custom - /// headers unless a CORS policy whitelists them. Therefore it should be - /// safe to allow requests with an `X-Requested-With` header (added - /// automatically by many JavaScript libraries). - /// - /// This is disabled by default, because in Safari it is possible to - /// circumvent this using redirects and Flash. - /// - /// Use this method to enable more lax filtering. - pub fn allow_xhr(mut self) -> CsrfFilter { - self.allow_xhr = true; - self - } - - /// Allow requests if the expected `Origin` header is missing (and - /// there is no `Referer` to fall back on). - /// - /// The filter is conservative by default, but it should be safe to allow - /// missing `Origin` headers because a cross-site attacker cannot prevent - /// the browser from sending `Origin` on unprotected requests. - pub fn allow_missing_origin(mut self) -> CsrfFilter { - self.allow_missing_origin = true; - self - } - - /// Allow cross-site upgrade requests (for example to open a WebSocket). - pub fn allow_upgrade(mut self) -> CsrfFilter { - self.allow_upgrade = true; - self - } - - fn validate(&self, req: &Request) -> Result<(), CsrfError> { - let is_upgrade = req.headers().contains_key(header::UPGRADE); - let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade); - - if is_safe || (self.allow_xhr && req.headers().contains_key("x-requested-with")) - { - Ok(()) - } else if let Some(header) = origin(req.headers()) { - match header { - Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()), - Ok(_) => Err(CsrfError::CsrDenied), - Err(err) => Err(err), - } - } else if self.allow_missing_origin { - Ok(()) - } else { - Err(CsrfError::MissingOrigin) - } - } -} - -impl Middleware for CsrfFilter { - fn start(&self, req: &HttpRequest) -> Result { - self.validate(req)?; - Ok(Started::Done) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::Method; - use test::TestRequest; - - #[test] - fn test_safe() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header("Origin", "https://www.w3.org") - .method(Method::HEAD) - .finish(); - - assert!(csrf.start(&req).is_ok()); - } - - #[test] - fn test_csrf() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header("Origin", "https://www.w3.org") - .method(Method::POST) - .finish(); - - assert!(csrf.start(&req).is_err()); - } - - #[test] - fn test_referer() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header( - "Referer", - "https://www.example.com/some/path?query=param", - ).method(Method::POST) - .finish(); - - assert!(csrf.start(&req).is_ok()); - } - - #[test] - fn test_upgrade() { - let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let lax_csrf = CsrfFilter::new() - .allowed_origin("https://www.example.com") - .allow_upgrade(); - - let req = TestRequest::with_header("Origin", "https://cswsh.com") - .header("Connection", "Upgrade") - .header("Upgrade", "websocket") - .method(Method::GET) - .finish(); - - assert!(strict_csrf.start(&req).is_err()); - assert!(lax_csrf.start(&req).is_ok()); - } -} diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs deleted file mode 100644 index d980a250..00000000 --- a/src/middleware/defaultheaders.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Default response headers -use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; -use http::{HeaderMap, HttpTryFrom}; - -use error::Result; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response}; - -/// `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::{http, middleware, App, HttpResponse}; -/// -/// fn main() { -/// let app = App::new() -/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) -/// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD) -/// .f(|_| HttpResponse::MethodNotAllowed()); -/// }) -/// .finish(); -/// } -/// ``` -pub struct DefaultHeaders { - ct: bool, - headers: HeaderMap, -} - -impl Default for DefaultHeaders { - fn default() -> Self { - DefaultHeaders { - ct: false, - headers: HeaderMap::new(), - } - } -} - -impl DefaultHeaders { - /// Construct `DefaultHeaders` middleware. - pub fn new() -> DefaultHeaders { - DefaultHeaders::default() - } - - /// Set a header. - #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))] - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: HttpTryFrom, - HeaderValue: HttpTryFrom, - { - match HeaderName::try_from(key) { - Ok(key) => match HeaderValue::try_from(value) { - Ok(value) => { - self.headers.append(key, value); - } - Err(_) => panic!("Can not create header value"), - }, - Err(_) => panic!("Can not create header name"), - } - self - } - - /// Set *CONTENT-TYPE* header if response does not contain this header. - pub fn content_type(mut self) -> Self { - self.ct = true; - self - } -} - -impl Middleware for DefaultHeaders { - fn response(&self, _: &HttpRequest, mut resp: HttpResponse) -> Result { - 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"), - ); - } - Ok(Response::Done(resp)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::header::CONTENT_TYPE; - use test::TestRequest; - - #[test] - fn test_default_headers() { - let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); - - let req = TestRequest::default().finish(); - - let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - - let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); - let resp = match mw.response(&req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); - } -} diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs deleted file mode 100644 index c7d19d33..00000000 --- a/src/middleware/errhandlers.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::collections::HashMap; - -use error::Result; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response}; - -type ErrorHandler = Fn(&HttpRequest, HttpResponse) -> Result; - -/// `Middleware` for allowing custom handlers for responses. -/// -/// You can use `ErrorHandlers::handler()` method to register a custom error -/// handler for specific status code. You can modify existing response or -/// create completely new one. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::{ErrorHandlers, Response}; -/// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; -/// -/// fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { -/// let mut builder = resp.into_builder(); -/// builder.header(http::header::CONTENT_TYPE, "application/json"); -/// Ok(Response::Done(builder.into())) -/// } -/// -/// fn main() { -/// let app = App::new() -/// .middleware( -/// ErrorHandlers::new() -/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), -/// ) -/// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD) -/// .f(|_| HttpResponse::MethodNotAllowed()); -/// }) -/// .finish(); -/// } -/// ``` -pub struct ErrorHandlers { - handlers: HashMap>>, -} - -impl Default for ErrorHandlers { - fn default() -> Self { - ErrorHandlers { - handlers: HashMap::new(), - } - } -} - -impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance - pub fn new() -> Self { - ErrorHandlers::default() - } - - /// Register error handler for specified status code - pub fn handler(mut self, status: StatusCode, handler: F) -> Self - where - F: Fn(&HttpRequest, HttpResponse) -> Result + 'static, - { - self.handlers.insert(status, Box::new(handler)); - self - } -} - -impl Middleware for ErrorHandlers { - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(handler) = self.handlers.get(&resp.status()) { - handler(req, resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use error::{Error, ErrorInternalServerError}; - use http::header::CONTENT_TYPE; - use http::StatusCode; - use httpmessage::HttpMessage; - use middleware::Started; - use test::{self, TestRequest}; - - fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { - let mut builder = resp.into_builder(); - builder.header(CONTENT_TYPE, "0001"); - Ok(Response::Done(builder.into())) - } - - #[test] - fn test_handler() { - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - - let mut req = TestRequest::default().finish(); - let resp = HttpResponse::InternalServerError().finish(); - let resp = match mw.response(&mut req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - - let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&mut req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert!(!resp.headers().contains_key(CONTENT_TYPE)); - } - - struct MiddlewareOne; - - impl Middleware for MiddlewareOne { - fn start(&self, _: &HttpRequest) -> Result { - Err(ErrorInternalServerError("middleware error")) - } - } - - #[test] - fn test_middleware_start_error() { - let mut srv = test::TestServer::new(move |app| { - app.middleware( - ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), - ).middleware(MiddlewareOne) - .handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001"); - } -} diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs deleted file mode 100644 index d890bebe..00000000 --- a/src/middleware/identity.rs +++ /dev/null @@ -1,387 +0,0 @@ -//! Request identity service for Actix applications. -//! -//! [**IdentityService**](struct.IdentityService.html) middleware can be -//! used with different policies types to store identity information. -//! -//! By default, only cookie identity policy is implemented. Other backend -//! implementations can be added separately. -//! -//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) -//! uses cookies as identity storage. -//! -//! To access current request identity -//! [**RequestIdentity**](trait.RequestIdentity.html) should be used. -//! *HttpRequest* implements *RequestIdentity* trait. -//! -//! ```rust -//! use actix_web::middleware::identity::RequestIdentity; -//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -//! use actix_web::*; -//! -//! fn index(req: HttpRequest) -> Result { -//! // access request identity -//! if let Some(id) = req.identity() { -//! Ok(format!("Welcome! {}", id)) -//! } else { -//! Ok("Welcome Anonymous!".to_owned()) -//! } -//! } -//! -//! fn login(mut req: HttpRequest) -> HttpResponse { -//! req.remember("User1".to_owned()); // <- remember identity -//! HttpResponse::Ok().finish() -//! } -//! -//! fn logout(mut req: HttpRequest) -> HttpResponse { -//! req.forget(); // <- remove identity -//! HttpResponse::Ok().finish() -//! } -//! -//! fn main() { -//! let app = App::new().middleware(IdentityService::new( -//! // <- create identity middleware -//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend -//! .name("auth-cookie") -//! .secure(false), -//! )); -//! } -//! ``` -use std::rc::Rc; - -use cookie::{Cookie, CookieJar, Key}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use time::Duration; - -use error::{Error, Result}; -use http::header::{self, HeaderValue}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; - -/// The helper trait to obtain your identity from a request. -/// -/// ```rust -/// use actix_web::middleware::identity::RequestIdentity; -/// use actix_web::*; -/// -/// fn index(req: HttpRequest) -> Result { -/// // access request identity -/// if let Some(id) = req.identity() { -/// Ok(format!("Welcome! {}", id)) -/// } else { -/// Ok("Welcome Anonymous!".to_owned()) -/// } -/// } -/// -/// fn login(mut req: HttpRequest) -> HttpResponse { -/// req.remember("User1".to_owned()); // <- remember identity -/// HttpResponse::Ok().finish() -/// } -/// -/// fn logout(mut req: HttpRequest) -> HttpResponse { -/// req.forget(); // <- remove identity -/// HttpResponse::Ok().finish() -/// } -/// # fn main() {} -/// ``` -pub trait RequestIdentity { - /// Return the claimed identity of the user associated request or - /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option; - - /// Remember identity. - fn remember(&self, identity: String); - - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&self); -} - -impl RequestIdentity for HttpRequest { - fn identity(&self) -> Option { - if let Some(id) = self.extensions().get::() { - return id.0.identity().map(|s| s.to_owned()); - } - None - } - - fn remember(&self, identity: String) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.as_mut().remember(identity); - } - } - - fn forget(&self) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.forget(); - } - } -} - -/// An identity -pub trait Identity: 'static { - /// Return the claimed identity of the user associated request or - /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option<&str>; - - /// Remember identity. - fn remember(&mut self, key: String); - - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&mut self); - - /// Write session to storage backend. - fn write(&mut self, resp: HttpResponse) -> Result; -} - -/// Identity policy definition. -pub trait IdentityPolicy: Sized + 'static { - /// The associated identity - type Identity: Identity; - - /// The return type of the middleware - type Future: Future; - - /// Parse the session from request and load data from a service identity. - fn from_request(&self, request: &HttpRequest) -> Self::Future; -} - -/// Request identity middleware -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(IdentityService::new( -/// // <- create identity middleware -/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend -/// .name("auth-cookie") -/// .secure(false), -/// )); -/// } -/// ``` -pub struct IdentityService { - backend: T, -} - -impl IdentityService { - /// Create new identity service with specified backend. - pub fn new(backend: T) -> Self { - IdentityService { backend } - } -} - -struct IdentityBox(Box); - -impl> Middleware for IdentityService { - fn start(&self, req: &HttpRequest) -> Result { - let req = req.clone(); - let fut = self.backend.from_request(&req).then(move |res| match res { - Ok(id) => { - req.extensions_mut().insert(IdentityBox(Box::new(id))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(ref mut id) = req.extensions_mut().get_mut::() { - id.0.as_mut().write(resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -#[doc(hidden)] -/// Identity that uses private cookies as identity storage. -pub struct CookieIdentity { - changed: bool, - identity: Option, - inner: Rc, -} - -impl Identity for CookieIdentity { - fn identity(&self) -> Option<&str> { - self.identity.as_ref().map(|s| s.as_ref()) - } - - fn remember(&mut self, value: String) { - self.changed = true; - self.identity = Some(value); - } - - fn forget(&mut self) { - self.changed = true; - self.identity = None; - } - - fn write(&mut self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, self.identity.take()); - } - Ok(Response::Done(resp)) - } -} - -struct CookieIdentityInner { - key: Key, - name: String, - path: String, - domain: Option, - secure: bool, - max_age: Option, -} - -impl CookieIdentityInner { - fn new(key: &[u8]) -> CookieIdentityInner { - CookieIdentityInner { - key: Key::from_master(key), - name: "actix-identity".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - max_age: None, - } - } - - fn set_cookie(&self, resp: &mut HttpResponse, id: Option) -> Result<()> { - let some = id.is_some(); - { - let id = id.unwrap_or_else(String::new); - let mut cookie = Cookie::new(self.name.clone(), id); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(true); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - let mut jar = CookieJar::new(); - if some { - jar.private(&self.key).add(cookie); - } else { - jar.add_original(cookie.clone()); - jar.private(&self.key).remove(cookie); - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - } - - Ok(()) - } - - fn load(&self, req: &HttpRequest) -> Option { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); - - let cookie_opt = jar.private(&self.key).get(&self.name); - if let Some(cookie) = cookie_opt { - return Some(cookie.value().into()); - } - } - } - } - None - } -} - -/// Use cookies for request identity storage. -/// -/// The constructors take a key as an argument. -/// This is the private key for cookie - when this value is changed, -/// all identities are lost. The constructors will panic if the key is less -/// than 32 bytes in length. -/// -/// # Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(IdentityService::new( -/// // <- create identity middleware -/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy -/// .domain("www.rust-lang.org") -/// .name("actix_auth") -/// .path("/") -/// .secure(true), -/// )); -/// } -/// ``` -pub struct CookieIdentityPolicy(Rc); - -impl CookieIdentityPolicy { - /// Construct new `CookieIdentityPolicy` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn new(key: &[u8]) -> CookieIdentityPolicy { - CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key))) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().path = value.into(); - self - } - - /// Sets the `name` field in the session cookie being built. - pub fn name>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().name = value.into(); - self - } - - /// Sets the `domain` field in the session cookie being built. - pub fn domain>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } -} - -impl IdentityPolicy for CookieIdentityPolicy { - type Identity = CookieIdentity; - type Future = FutureResult; - - fn from_request(&self, req: &HttpRequest) -> Self::Future { - let identity = self.0.load(req); - FutOk(CookieIdentity { - identity, - changed: false, - inner: Rc::clone(&self.0), - }) - } -} diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs deleted file mode 100644 index b7bb1bb8..00000000 --- a/src/middleware/logger.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Request logging middleware -use std::collections::HashSet; -use std::env; -use std::fmt::{self, Display, Formatter}; - -use regex::Regex; -use time; - -use error::Result; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Finished, Middleware, Started}; - -/// `Middleware` for logging request and response info to the terminal. -/// -/// `Logger` middleware uses standard log crate to log information. You should -/// enable logger for `actix_web` package to see access log. -/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar) -/// -/// ## Usage -/// -/// Create `Logger` middleware with the specified `format`. -/// Default `Logger` could be created with `default` method, it uses the -/// default format: -/// -/// ```ignore -/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T -/// ``` -/// ```rust -/// # extern crate actix_web; -/// extern crate env_logger; -/// use actix_web::middleware::Logger; -/// use actix_web::App; -/// -/// fn main() { -/// std::env::set_var("RUST_LOG", "actix_web=info"); -/// env_logger::init(); -/// -/// let app = App::new() -/// .middleware(Logger::default()) -/// .middleware(Logger::new("%a %{User-Agent}i")) -/// .finish(); -/// } -/// ``` -/// -/// ## 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 -/// -/// `%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'] -/// -pub struct Logger { - format: Format, - exclude: HashSet, -} - -impl Logger { - /// Create `Logger` middleware with the specified `format`. - pub fn new(format: &str) -> Logger { - Logger { - format: Format::new(format), - exclude: HashSet::new(), - } - } - - /// Ignore and do not log access info for specified path. - pub fn exclude>(mut self, path: T) -> Self { - self.exclude.insert(path.into()); - self - } -} - -impl Default for Logger { - /// Create `Logger` middleware with format: - /// - /// ```ignore - /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T - /// ``` - fn default() -> Logger { - Logger { - format: Format::default(), - exclude: HashSet::new(), - } - } -} - -struct StartTime(time::Tm); - -impl Logger { - fn log(&self, req: &HttpRequest, resp: &HttpResponse) { - if let Some(entry_time) = req.extensions().get::() { - let render = |fmt: &mut Formatter| { - for unit in &self.format.0 { - unit.render(fmt, req, resp, entry_time.0)?; - } - Ok(()) - }; - info!("{}", FormatDisplay(&render)); - } - } -} - -impl Middleware for Logger { - fn start(&self, req: &HttpRequest) -> Result { - if !self.exclude.contains(req.path()) { - req.extensions_mut().insert(StartTime(time::now())); - } - Ok(Started::Done) - } - - fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { - self.log(req, resp); - Finished::Done - } -} - -/// A formatting style for the `Logger`, consisting of multiple -/// `FormatText`s concatenated into one line. -#[derive(Clone)] -#[doc(hidden)] -struct Format(Vec); - -impl Default for Format { - /// Return the default formatting style for the `Logger`: - fn default() -> Format { - Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#) - } -} - -impl Format { - /// Create a `Format` from a format string. - /// - /// Returns `None` if the format string syntax is incorrect. - pub fn new(s: &str) -> Format { - trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap(); - - let mut idx = 0; - let mut results = Vec::new(); - for cap in fmt.captures_iter(s) { - let m = cap.get(0).unwrap(); - let pos = m.start(); - if idx != pos { - results.push(FormatText::Str(s[idx..pos].to_owned())); - } - idx = m.end(); - - if let Some(key) = cap.get(2) { - results.push(match cap.get(3).unwrap().as_str() { - "i" => FormatText::RequestHeader(key.as_str().to_owned()), - "o" => FormatText::ResponseHeader(key.as_str().to_owned()), - "e" => FormatText::EnvironHeader(key.as_str().to_owned()), - _ => unreachable!(), - }) - } else { - let m = cap.get(1).unwrap(); - results.push(match m.as_str() { - "%" => FormatText::Percent, - "a" => FormatText::RemoteAddr, - "t" => FormatText::RequestTime, - "r" => FormatText::RequestLine, - "s" => FormatText::ResponseStatus, - "b" => FormatText::ResponseSize, - "T" => FormatText::Time, - "D" => FormatText::TimeMillis, - _ => FormatText::Str(m.as_str().to_owned()), - }); - } - } - if idx != s.len() { - results.push(FormatText::Str(s[idx..].to_owned())); - } - - Format(results) - } -} - -/// A string of text to be logged. This is either one of the data -/// fields supported by the `Logger`, or a custom `String`. -#[doc(hidden)] -#[derive(Debug, Clone)] -pub enum FormatText { - Str(String), - Percent, - RequestLine, - RequestTime, - ResponseStatus, - ResponseSize, - Time, - TimeMillis, - RemoteAddr, - RequestHeader(String), - ResponseHeader(String), - EnvironHeader(String), -} - -impl FormatText { - 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), - FormatText::Percent => "%".fmt(fmt), - FormatText::RequestLine => { - if req.query_string().is_empty() { - fmt.write_fmt(format_args!( - "{} {} {:?}", - req.method(), - req.path(), - req.version() - )) - } else { - fmt.write_fmt(format_args!( - "{} {}?{} {:?}", - req.method(), - req.path(), - req.query_string(), - req.version() - )) - } - } - FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt), - FormatText::ResponseSize => resp.response_size().fmt(fmt), - FormatText::Time => { - let rt = time::now() - entry_time; - let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0; - fmt.write_fmt(format_args!("{:.6}", rt)) - } - FormatText::TimeMillis => { - let rt = time::now() - entry_time; - let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0; - fmt.write_fmt(format_args!("{:.6}", rt)) - } - FormatText::RemoteAddr => { - if let Some(remote) = req.connection_info().remote() { - return remote.fmt(fmt); - } else { - "-".fmt(fmt) - } - } - FormatText::RequestTime => entry_time - .strftime("[%d/%b/%Y:%H:%M:%S %z]") - .unwrap() - .fmt(fmt), - FormatText::RequestHeader(ref name) => { - let s = if let Some(val) = req.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - fmt.write_fmt(format_args!("{}", s)) - } - FormatText::ResponseHeader(ref name) => { - let s = if let Some(val) = resp.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - fmt.write_fmt(format_args!("{}", s)) - } - FormatText::EnvironHeader(ref name) => { - if let Ok(val) = env::var(name) { - fmt.write_fmt(format_args!("{}", val)) - } else { - "-".fmt(fmt) - } - } - } - } -} - -pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>); - -impl<'a> fmt::Display for FormatDisplay<'a> { - fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { - (self.0)(fmt) - } -} - -#[cfg(test)] -mod tests { - use time; - - use super::*; - use http::{header, StatusCode}; - use test::TestRequest; - - #[test] - fn test_logger() { - let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ).finish(); - let resp = HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") - .force_close() - .finish(); - - match logger.start(&req) { - Ok(Started::Done) => (), - _ => panic!(), - }; - match logger.finish(&req, &resp) { - Finished::Done => (), - _ => panic!(), - } - let entry_time = time::now(); - let render = |fmt: &mut Formatter| { - for unit in &logger.format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("ACTIX-WEB ttt")); - } - - #[test] - fn test_default_format() { - let format = Format::default(); - - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ).finish(); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - let entry_time = time::now(); - - let render = |fmt: &mut Formatter| { - for unit in &format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET / HTTP/1.1")); - assert!(s.contains("200 0")); - assert!(s.contains("ACTIX-WEB")); - - let req = TestRequest::with_uri("/?test").finish(); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - let entry_time = time::now(); - - let render = |fmt: &mut Formatter| { - for unit in &format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET /?test HTTP/1.1")); - } -} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs deleted file mode 100644 index c69dbb3e..00000000 --- a/src/middleware/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Middlewares -use futures::Future; - -use error::{Error, Result}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -mod logger; - -pub mod cors; -pub mod csrf; -mod defaultheaders; -mod errhandlers; -#[cfg(feature = "session")] -pub mod identity; -#[cfg(feature = "session")] -pub mod session; -pub use self::defaultheaders::DefaultHeaders; -pub use self::errhandlers::ErrorHandlers; -pub use self::logger::Logger; - -/// Middleware start result -pub enum Started { - /// Middleware is completed, continue to next middleware - Done, - /// New http response got generated. If middleware generates response - /// handler execution halts. - Response(HttpResponse), - /// Execution completed, runs future to completion. - Future(Box, Error = Error>>), -} - -/// Middleware execution result -pub enum Response { - /// New http response got generated - Done(HttpResponse), - /// Result is a future that resolves to a new http response - Future(Box>), -} - -/// Middleware finish result -pub enum Finished { - /// Execution completed - Done, - /// Execution completed, but run future to completion - Future(Box>), -} - -/// Middleware definition -#[allow(unused_variables)] -pub trait Middleware: 'static { - /// Method is called when request is ready. It may return - /// future, which should resolve before next middleware get called. - fn start(&self, req: &HttpRequest) -> Result { - Ok(Started::Done) - } - - /// Method is called when handler returns response, - /// but before sending http message to peer. - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - Ok(Response::Done(resp)) - } - - /// Method is called after body stream get sent to peer. - fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { - Finished::Done - } -} diff --git a/src/middleware/session.rs b/src/middleware/session.rs deleted file mode 100644 index e8b0e555..00000000 --- a/src/middleware/session.rs +++ /dev/null @@ -1,617 +0,0 @@ -//! User sessions. -//! -//! Actix provides a general solution for session management. The -//! [**SessionStorage**](struct.SessionStorage.html) -//! middleware can be used with different backend types to store session -//! data in different backends. -//! -//! By default, only cookie session backend is implemented. Other -//! backend implementations can be added. -//! -//! [**CookieSessionBackend**](struct.CookieSessionBackend.html) -//! uses cookies as session storage. `CookieSessionBackend` creates sessions -//! which are limited to storing fewer than 4000 bytes of data, as the payload -//! must fit into a single cookie. An internal server error is generated if a -//! session contains more than 4000 bytes. -//! -//! A cookie may have a security policy of *signed* or *private*. Each has -//! a respective `CookieSessionBackend` constructor. -//! -//! A *signed* cookie may be viewed but not modified by the client. A *private* -//! cookie may neither be viewed nor modified by the client. -//! -//! The constructors take a key as an argument. This is the private key -//! for cookie session - when this value is changed, all session data is lost. -//! -//! In general, you create a `SessionStorage` middleware and initialize it -//! with specific backend implementation, such as a `CookieSessionBackend`. -//! To access session data, -//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session) -//! must be used. This method returns a -//! [*Session*](struct.Session.html) object, which allows us to get or set -//! session data. -//! -//! ```rust -//! # extern crate actix_web; -//! use actix_web::{actix, server, App, HttpRequest, Result}; -//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; -//! -//! fn index(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() { -//! actix::System::run(|| { -//! server::new( -//! || App::new().middleware( -//! SessionStorage::new( // <- create session middleware -//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend -//! .secure(false) -//! ))) -//! .bind("127.0.0.1:59880").unwrap() -//! .start(); -//! # actix::System::current().stop(); -//! }); -//! } -//! ``` -use std::cell::RefCell; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::rc::Rc; -use std::sync::Arc; - -use cookie::{Cookie, CookieJar, Key, SameSite}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use http::header::{self, HeaderValue}; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json; -use serde_json::error::Error as JsonError; -use time::Duration; - -use error::{Error, ResponseError, Result}; -use handler::FromRequest; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; - -/// The helper trait to obtain your session data from a request. -/// -/// ```rust -/// use actix_web::middleware::session::RequestSession; -/// use actix_web::*; -/// -/// 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 { - /// Get the session from the request - fn session(&self) -> Session; -} - -impl RequestSession for HttpRequest { - fn session(&self) -> Session { - if let Some(s_impl) = self.extensions().get::>() { - return Session(SessionInner::Session(Arc::clone(&s_impl))); - } - Session(SessionInner::None) - } -} - -/// The high-level interface you use to modify session data. -/// -/// Session object could be obtained with -/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session) -/// method. `RequestSession` trait is implemented for `HttpRequest`. -/// -/// ```rust -/// use actix_web::middleware::session::RequestSession; -/// use actix_web::*; -/// -/// 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(SessionInner); - -enum SessionInner { - Session(Arc), - None, -} - -impl Session { - /// Get a `value` from the session. - pub fn get(&self, key: &str) -> Result> { - match self.0 { - SessionInner::Session(ref sess) => { - if let Some(s) = sess.as_ref().0.borrow().get(key) { - Ok(Some(serde_json::from_str(s)?)) - } else { - Ok(None) - } - } - SessionInner::None => Ok(None), - } - } - - /// Set a `value` from the session. - pub fn set(&self, key: &str, value: T) -> Result<()> { - match self.0 { - SessionInner::Session(ref sess) => { - sess.as_ref() - .0 - .borrow_mut() - .set(key, serde_json::to_string(&value)?); - Ok(()) - } - SessionInner::None => Ok(()), - } - } - - /// Remove value from the session. - pub fn remove(&self, key: &str) { - match self.0 { - SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key), - SessionInner::None => (), - } - } - - /// Clear the session. - pub fn clear(&self) { - match self.0 { - SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(), - SessionInner::None => (), - } - } -} - -/// Extractor implementation for Session type. -/// -/// ```rust -/// # use actix_web::*; -/// use actix_web::middleware::session::Session; -/// -/// fn index(session: Session) -> Result<&'static str> { -/// // access session data -/// if let Some(count) = session.get::("counter")? { -/// session.set("counter", count + 1)?; -/// } else { -/// session.set("counter", 1)?; -/// } -/// -/// Ok("Welcome!") -/// } -/// # fn main() {} -/// ``` -impl FromRequest for Session { - type Config = (); - type Result = Session; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - req.session() - } -} - -struct SessionImplCell(RefCell>); - -/// Session storage middleware -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(SessionStorage::new( -/// // <- create session middleware -/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend -/// .secure(false), -/// )); -/// } -/// ``` -pub struct SessionStorage(T, PhantomData); - -impl> SessionStorage { - /// Create session storage - pub fn new(backend: T) -> SessionStorage { - SessionStorage(backend, PhantomData) - } -} - -impl> Middleware for SessionStorage { - fn start(&self, req: &HttpRequest) -> Result { - let mut req = req.clone(); - - let fut = self.0.from_request(&mut req).then(move |res| match res { - Ok(sess) => { - req.extensions_mut() - .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess))))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(s_box) = req.extensions().get::>() { - s_box.0.borrow_mut().write(resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -/// A simple key-value storage interface that is internally used by `Session`. -pub trait SessionImpl: 'static { - /// Get session value by key - fn get(&self, key: &str) -> Option<&str>; - - /// Set session value - fn set(&mut self, key: &str, value: String); - - /// Remove specific key from session - fn remove(&mut self, key: &str); - - /// Remove all values from session - fn clear(&mut self); - - /// Write session to storage backend. - fn write(&self, resp: HttpResponse) -> Result; -} - -/// Session's storage backend trait definition. -pub trait SessionBackend: Sized + 'static { - /// Session item - type Session: SessionImpl; - /// Future that reads session - type ReadFuture: Future; - - /// Parse the session from request and load data from a storage backend. - fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; -} - -/// Session that uses signed cookies as session storage -pub struct CookieSession { - changed: bool, - state: HashMap, - inner: Rc, -} - -/// Errors that can occur during handling cookie session -#[derive(Fail, Debug)] -pub enum CookieSessionError { - /// Size of the serialized session is greater than 4000 bytes. - #[fail(display = "Size of the serialized session is greater than 4000 bytes.")] - Overflow, - /// Fail to serialize session. - #[fail(display = "Fail to serialize session")] - Serialize(JsonError), -} - -impl ResponseError for CookieSessionError {} - -impl SessionImpl for CookieSession { - fn get(&self, key: &str) -> Option<&str> { - if let Some(s) = self.state.get(key) { - Some(s) - } else { - None - } - } - - fn set(&mut self, key: &str, value: String) { - self.changed = true; - self.state.insert(key.to_owned(), value); - } - - fn remove(&mut self, key: &str) { - self.changed = true; - self.state.remove(key); - } - - fn clear(&mut self) { - self.changed = true; - self.state.clear() - } - - fn write(&self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, &self.state); - } - Ok(Response::Done(resp)) - } -} - -enum CookieSecurity { - Signed, - Private, -} - -struct CookieSessionInner { - key: Key, - security: CookieSecurity, - name: String, - path: String, - domain: Option, - secure: bool, - http_only: bool, - max_age: Option, - same_site: Option, -} - -impl CookieSessionInner { - fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { - CookieSessionInner { - security, - key: Key::from_master(key), - name: "actix-session".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - http_only: true, - max_age: None, - same_site: None, - } - } - - fn set_cookie( - &self, resp: &mut HttpResponse, state: &HashMap, - ) -> Result<()> { - let value = - serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; - if value.len() > 4064 { - return Err(CookieSessionError::Overflow.into()); - } - - let mut cookie = Cookie::new(self.name.clone(), value); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(self.http_only); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - if let Some(same_site) = self.same_site { - cookie.set_same_site(same_site); - } - - let mut jar = CookieJar::new(); - - match self.security { - CookieSecurity::Signed => jar.signed(&self.key).add(cookie), - CookieSecurity::Private => jar.private(&self.key).add(cookie), - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.encoded().to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - - Ok(()) - } - - fn load(&self, req: &mut HttpRequest) -> HashMap { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); - - let cookie_opt = match self.security { - CookieSecurity::Signed => jar.signed(&self.key).get(&self.name), - CookieSecurity::Private => { - jar.private(&self.key).get(&self.name) - } - }; - if let Some(cookie) = cookie_opt { - if let Ok(val) = serde_json::from_str(cookie.value()) { - return val; - } - } - } - } - } - HashMap::new() - } -} - -/// Use cookies for session storage. -/// -/// `CookieSessionBackend` creates sessions which are limited to storing -/// fewer than 4000 bytes of data (as the payload must fit into a single -/// cookie). An Internal Server Error is generated if the session contains more -/// than 4000 bytes. -/// -/// A cookie may have a security policy of *signed* or *private*. Each has a -/// respective `CookieSessionBackend` constructor. -/// -/// A *signed* cookie is stored on the client as plaintext alongside -/// a signature such that the cookie may be viewed but not modified by the -/// client. -/// -/// A *private* cookie is stored on the client as encrypted text -/// such that it may neither be viewed nor modified by the client. -/// -/// The constructors take a key as an argument. -/// This is the private key for cookie session - when this value is changed, -/// all session data is lost. The constructors will panic if the key is less -/// than 32 bytes in length. -/// -/// The backend relies on `cookie` crate to create and read cookies. -/// By default all cookies are percent encoded, but certain symbols may -/// cause troubles when reading cookie, if they are not properly percent encoded. -/// -/// # Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::session::CookieSessionBackend; -/// -/// # fn main() { -/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32]) -/// .domain("www.rust-lang.org") -/// .name("actix_session") -/// .path("/") -/// .secure(true); -/// # } -/// ``` -pub struct CookieSessionBackend(Rc); - -impl CookieSessionBackend { - /// Construct new *signed* `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn signed(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(CookieSessionInner::new( - key, - CookieSecurity::Signed, - ))) - } - - /// Construct new *private* `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn private(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(CookieSessionInner::new( - key, - CookieSecurity::Private, - ))) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().path = value.into(); - self - } - - /// Sets the `name` field in the session cookie being built. - pub fn name>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().name = value.into(); - self - } - - /// Sets the `domain` field in the session cookie being built. - pub fn domain>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `http_only` field in the session cookie being built. - pub fn http_only(mut self, value: bool) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().http_only = value; - self - } - - /// Sets the `same_site` field in the session cookie being built. - pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().same_site = Some(value); - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } -} - -impl SessionBackend for CookieSessionBackend { - type Session = CookieSession; - type ReadFuture = FutureResult; - - fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { - let state = self.0.load(req); - FutOk(CookieSession { - changed: false, - inner: Rc::clone(&self.0), - state, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use application::App; - use test; - - #[test] - fn cookie_session() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/", |r| { - r.f(|req| { - let _ = req.session().set("counter", 100); - "test" - }) - }) - }); - - let request = srv.get().uri(srv.url("/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.cookie("actix-session").is_some()); - } - - #[test] - fn cookie_session_extractor() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/", |r| { - r.with(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - }) - }) - }); - - let request = srv.get().uri(srv.url("/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.cookie("actix-session").is_some()); - } -} diff --git a/src/multipart.rs b/src/multipart.rs deleted file mode 100644 index 862f60ec..00000000 --- a/src/multipart.rs +++ /dev/null @@ -1,815 +0,0 @@ -//! Multipart requests support -use std::cell::{RefCell, UnsafeCell}; -use std::marker::PhantomData; -use std::rc::Rc; -use std::{cmp, fmt}; - -use bytes::Bytes; -use futures::task::{current as current_task, Task}; -use futures::{Async, Poll, Stream}; -use http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; -use http::HttpTryFrom; -use httparse; -use mime; - -use error::{MultipartError, ParseError, PayloadError}; -use payload::PayloadBuffer; - -const MAX_HEADERS: usize = 32; - -/// The server-side implementation of `multipart/form-data` requests. -/// -/// This will parse the incoming stream into `MultipartItem` instances via its -/// Stream implementation. -/// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` -/// is used for nested multipart streams. -pub struct Multipart { - safety: Safety, - error: Option, - inner: Option>>>, -} - -/// -pub enum MultipartItem { - /// Multipart field - Field(Field), - /// Nested multipart stream - Nested(Multipart), -} - -enum InnerMultipartItem { - None, - Field(Rc>>), - Multipart(Rc>>), -} - -#[derive(PartialEq, Debug)] -enum InnerState { - /// Stream eof - Eof, - /// Skip data until first boundary - FirstBoundary, - /// Reading boundary - Boundary, - /// Reading Headers, - Headers, -} - -struct InnerMultipart { - payload: PayloadRef, - boundary: String, - state: InnerState, - item: InnerMultipartItem, -} - -impl Multipart<()> { - /// Extract boundary info from headers. - pub fn boundary(headers: &HeaderMap) -> Result { - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - if let Some(boundary) = ct.get_param(mime::BOUNDARY) { - Ok(boundary.as_str().to_owned()) - } else { - Err(MultipartError::Boundary) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::NoContentType) - } - } -} - -impl Multipart -where - S: Stream, -{ - /// Create multipart instance for boundary. - pub fn new(boundary: Result, stream: S) -> Multipart { - match boundary { - Ok(boundary) => Multipart { - error: None, - safety: Safety::new(), - inner: Some(Rc::new(RefCell::new(InnerMultipart { - boundary, - payload: PayloadRef::new(PayloadBuffer::new(stream)), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - }))), - }, - Err(err) => Multipart { - error: Some(err), - safety: Safety::new(), - inner: None, - }, - } - } -} - -impl Stream for Multipart -where - S: Stream, -{ - type Item = MultipartItem; - type Error = MultipartError; - - fn poll(&mut self) -> Poll, Self::Error> { - 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) - } - } -} - -impl InnerMultipart -where - S: Stream, -{ - fn read_headers(payload: &mut PayloadBuffer) -> Poll { - match payload.read_until(b"\r\n\r\n")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(bytes)) => { - let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; - match httparse::parse_headers(&bytes, &mut hdrs) { - Ok(httparse::Status::Complete((_, hdrs))) => { - // convert headers - let mut headers = HeaderMap::with_capacity(hdrs.len()); - for h in hdrs { - if let Ok(name) = HeaderName::try_from(h.name) { - if let Ok(value) = HeaderValue::try_from(h.value) { - headers.append(name, value); - } else { - return Err(ParseError::Header.into()); - } - } else { - return Err(ParseError::Header.into()); - } - } - Ok(Async::Ready(headers)) - } - Ok(httparse::Status::Partial) => Err(ParseError::Header.into()), - Err(err) => Err(ParseError::from(err).into()), - } - } - } - } - - fn read_boundary( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { - // TODO: need to read epilogue - match payload.readline()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(chunk)) => { - if chunk.len() == boundary.len() + 4 - && &chunk[..2] == b"--" - && &chunk[2..boundary.len() + 2] == boundary.as_bytes() - { - Ok(Async::Ready(false)) - } else if chunk.len() == boundary.len() + 6 - && &chunk[..2] == b"--" - && &chunk[2..boundary.len() + 2] == boundary.as_bytes() - && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" - { - Ok(Async::Ready(true)) - } else { - Err(MultipartError::Boundary) - } - } - } - } - - fn skip_until_boundary( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { - let mut eof = false; - loop { - match payload.readline()? { - Async::Ready(Some(chunk)) => { - if chunk.is_empty() { - //ValueError("Could not find starting boundary %r" - //% (self._boundary)) - } - if chunk.len() < boundary.len() { - continue; - } - if &chunk[..2] == b"--" - && &chunk[2..chunk.len() - 2] == boundary.as_bytes() - { - break; - } else { - if chunk.len() < boundary.len() + 2 { - continue; - } - let b: &[u8] = boundary.as_ref(); - if &chunk[..boundary.len()] == b - && &chunk[boundary.len()..boundary.len() + 2] == b"--" - { - eof = true; - break; - } - } - } - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Err(MultipartError::Incomplete), - } - } - Ok(Async::Ready(eof)) - } - - fn poll( - &mut self, safety: &Safety, - ) -> Poll>, MultipartError> { - if self.state == InnerState::Eof { - Ok(Async::Ready(None)) - } else { - // release field - loop { - // Nested multipart streams of fields has to be consumed - // before switching to next - if safety.current() { - let stop = match self.item { - InnerMultipartItem::Field(ref mut field) => { - match field.borrow_mut().poll(safety)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(_)) => continue, - Async::Ready(None) => true, - } - } - InnerMultipartItem::Multipart(ref mut multipart) => { - match multipart.borrow_mut().poll(safety)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(_)) => continue, - Async::Ready(None) => true, - } - } - _ => false, - }; - if stop { - self.item = InnerMultipartItem::None; - } - if let InnerMultipartItem::None = self.item { - break; - } - } - } - - let headers = if let Some(payload) = self.payload.get_mut(safety) { - match self.state { - // read until first boundary - InnerState::FirstBoundary => { - match InnerMultipart::skip_until_boundary( - payload, - &self.boundary, - )? { - Async::Ready(eof) => { - if eof { - self.state = InnerState::Eof; - return Ok(Async::Ready(None)); - } else { - self.state = InnerState::Headers; - } - } - Async::NotReady => return Ok(Async::NotReady), - } - } - // read boundary - InnerState::Boundary => { - match InnerMultipart::read_boundary(payload, &self.boundary)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(eof) => { - if eof { - self.state = InnerState::Eof; - return Ok(Async::Ready(None)); - } else { - self.state = InnerState::Headers; - } - } - } - } - _ => (), - } - - // read field headers for next field - if self.state == InnerState::Headers { - if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? - { - self.state = InnerState::Boundary; - headers - } else { - return Ok(Async::NotReady); - } - } else { - unreachable!() - } - } else { - debug!("NotReady: field is in flight"); - return Ok(Async::NotReady); - }; - - // content type - let mut mt = mime::APPLICATION_OCTET_STREAM; - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - mt = ct; - } - } - } - - self.state = InnerState::Boundary; - - // nested multipart stream - if mt.type_() == mime::MULTIPART { - let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) { - Rc::new(RefCell::new(InnerMultipart { - payload: self.payload.clone(), - boundary: boundary.as_str().to_owned(), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - })) - } else { - return Err(MultipartError::Boundary); - }; - - self.item = InnerMultipartItem::Multipart(Rc::clone(&inner)); - - Ok(Async::Ready(Some(MultipartItem::Nested(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, - )?)); - self.item = InnerMultipartItem::Field(Rc::clone(&field)); - - Ok(Async::Ready(Some(MultipartItem::Field(Field::new( - safety.clone(), - headers, - mt, - field, - ))))) - } - } - } -} - -impl Drop for InnerMultipart { - fn drop(&mut self) { - // InnerMultipartItem::Field has to be dropped first because of Safety. - self.item = InnerMultipartItem::None; - } -} - -/// A single field in a multipart stream -pub struct Field { - ct: mime::Mime, - headers: HeaderMap, - inner: Rc>>, - safety: Safety, -} - -impl Field -where - S: Stream, -{ - fn new( - safety: Safety, headers: HeaderMap, ct: mime::Mime, - inner: Rc>>, - ) -> Self { - Field { - ct, - headers, - inner, - safety, - } - } - - /// Get a map of headers - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Get the content type of the field - pub fn content_type(&self) -> &mime::Mime { - &self.ct - } - - /// Get the content disposition of the field, if it exists - pub fn content_disposition(&self) -> Option { - // RFC 7578: 'Each part MUST contain a Content-Disposition header field - // where the disposition type is "form-data".' - if let Some(content_disposition) = - self.headers.get(::http::header::CONTENT_DISPOSITION) - { - ContentDisposition::from_raw(content_disposition).ok() - } else { - None - } - } -} - -impl Stream for Field -where - S: Stream, -{ - type Item = Bytes; - type Error = MultipartError; - - fn poll(&mut self) -> Poll, Self::Error> { - if self.safety.current() { - self.inner.borrow_mut().poll(&self.safety) - } else { - Ok(Async::NotReady) - } - } -} - -impl fmt::Debug for Field { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nMultipartField: {}", self.ct)?; - writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; - writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -struct InnerField { - payload: Option>, - boundary: String, - eof: bool, - length: Option, -} - -impl InnerField -where - S: Stream, -{ - fn new( - payload: PayloadRef, boundary: String, headers: &HeaderMap, - ) -> Result, PayloadError> { - let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Some(len) - } else { - return Err(PayloadError::Incomplete); - } - } else { - return Err(PayloadError::Incomplete); - } - } else { - None - }; - - Ok(InnerField { - boundary, - payload: Some(payload), - eof: false, - length: len, - }) - } - - /// Reads body part content chunk of the specified size. - /// The body part must has `Content-Length` header with proper value. - fn read_len( - payload: &mut PayloadBuffer, size: &mut u64, - ) -> Poll, MultipartError> { - if *size == 0 { - Ok(Async::Ready(None)) - } else { - match payload.readany() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => Err(MultipartError::Incomplete), - Ok(Async::Ready(Some(mut chunk))) => { - let len = cmp::min(chunk.len() as u64, *size); - *size -= len; - let ch = chunk.split_to(len as usize); - if !chunk.is_empty() { - payload.unprocessed(chunk); - } - Ok(Async::Ready(Some(ch))) - } - Err(err) => Err(err.into()), - } - } - } - - /// Reads content chunk of body part with unknown length. - /// The `Content-Length` header for body part is not necessary. - fn read_stream( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll, MultipartError> { - match payload.read_until(b"\r")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { - if chunk.len() == 1 { - payload.unprocessed(chunk); - match payload.read_exact(boundary.len() + 4)? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { - if &chunk[..2] == b"\r\n" - && &chunk[2..4] == b"--" - && &chunk[4..] == boundary.as_bytes() - { - payload.unprocessed(chunk); - Ok(Async::Ready(None)) - } else { - // \r might be part of data stream - let ch = chunk.split_to(1); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) - } - } - } - } else { - let to = chunk.len() - 1; - let ch = chunk.split_to(to); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) - } - } - } - } - - fn poll(&mut self, s: &Safety) -> Poll, MultipartError> { - if self.payload.is_none() { - return Ok(Async::Ready(None)); - } - - let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { - let res = if let Some(ref mut len) = self.length { - InnerField::read_len(payload, len)? - } else { - InnerField::read_stream(payload, &self.boundary)? - }; - - match res { - Async::NotReady => Async::NotReady, - Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), - Async::Ready(None) => { - self.eof = true; - match payload.readline()? { - Async::NotReady => Async::NotReady, - Async::Ready(None) => Async::Ready(None), - Async::Ready(Some(line)) => { - if line.as_ref() != b"\r\n" { - warn!("multipart field did not read all the data or it is malformed"); - } - Async::Ready(None) - } - } - } - } - } else { - Async::NotReady - }; - - if Async::Ready(None) == result { - self.payload.take(); - } - Ok(result) - } -} - -struct PayloadRef { - payload: Rc>>, -} - -impl PayloadRef -where - S: Stream, -{ - fn new(payload: PayloadBuffer) -> PayloadRef { - PayloadRef { - payload: Rc::new(payload.into()), - } - } - - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer> - where - 'a: 'b, - { - // Unsafe: Invariant is inforced by Safety Safety is used as ref counter, - // only top most ref can have mutable access to payload. - if s.current() { - let payload: &mut PayloadBuffer = unsafe { &mut *self.payload.get() }; - Some(payload) - } else { - None - } - } -} - -impl Clone for PayloadRef { - fn clone(&self) -> PayloadRef { - PayloadRef { - payload: Rc::clone(&self.payload), - } - } -} - -/// Counter. It tracks of number of clones of payloads and give access to -/// payload only to top most task panics if Safety get destroyed and it not top -/// most task. -#[derive(Debug)] -struct Safety { - task: Option, - level: usize, - payload: Rc>, -} - -impl Safety { - fn new() -> Safety { - let payload = Rc::new(PhantomData); - Safety { - task: None, - level: Rc::strong_count(&payload), - payload, - } - } - - fn current(&self) -> bool { - Rc::strong_count(&self.payload) == self.level - } -} - -impl Clone for Safety { - fn clone(&self) -> Safety { - let payload = Rc::clone(&self.payload); - Safety { - task: Some(current_task()), - level: Rc::strong_count(&payload), - payload, - } - } -} - -impl Drop for Safety { - fn drop(&mut self) { - // parent task is dead - if Rc::strong_count(&self.payload) != self.level { - panic!("Safety get dropped but it is not from top-most task"); - } - if let Some(task) = self.task.take() { - task.notify() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - use futures::future::{lazy, result}; - use payload::{Payload, PayloadWriter}; - use tokio::runtime::current_thread::Runtime; - - #[test] - fn test_boundary() { - let headers = HeaderMap::new(); - match Multipart::boundary(&headers) { - Err(MultipartError::NoContentType) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("test"), - ); - - match Multipart::boundary(&headers) { - Err(MultipartError::ParseContentType) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("multipart/mixed"), - ); - match Multipart::boundary(&headers) { - Err(MultipartError::Boundary) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static( - "multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"", - ), - ); - - assert_eq!( - Multipart::boundary(&headers).unwrap(), - "5c02368e880e436dab70ed54e1c58209" - ); - } - - #[test] - fn test_multipart() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); - - let bytes = Bytes::from( - "testasdadsad\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - test\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - data\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n"); - sender.feed_data(bytes); - - let mut multipart = Multipart::new( - Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()), - payload, - ); - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { - { - use http::header::{DispositionParam, DispositionType}; - let cd = field.content_disposition().unwrap(); - assert_eq!(cd.disposition, DispositionType::FormData); - assert_eq!( - cd.parameters[0], - DispositionParam::Name("file".into()) - ); - } - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.poll() { - Ok(Async::Ready(Some(chunk))) => { - assert_eq!(chunk, "test") - } - _ => unreachable!(), - } - match field.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - } - _ => unreachable!(), - }, - _ => unreachable!(), - } - - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.poll() { - Ok(Async::Ready(Some(chunk))) => { - assert_eq!(chunk, "data") - } - _ => unreachable!(), - } - match field.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - } - _ => unreachable!(), - }, - _ => unreachable!(), - } - - match multipart.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } -} diff --git a/src/param.rs b/src/param.rs deleted file mode 100644 index d0664df9..00000000 --- a/src/param.rs +++ /dev/null @@ -1,303 +0,0 @@ -use std; -use std::ops::Index; -use std::path::PathBuf; -use std::rc::Rc; -use std::str::FromStr; - -use http::StatusCode; -use smallvec::SmallVec; - -use error::{InternalError, ResponseError, UriSegmentError}; -use uri::Url; - -/// 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; -} - -#[derive(Debug, Clone)] -pub(crate) enum ParamItem { - Static(&'static str), - UrlSegment(u16, u16), -} - -/// Route match information -/// -/// If resource path contains variable patterns, `Params` stores this variables. -#[derive(Debug, Clone)] -pub struct Params { - url: Url, - pub(crate) tail: u16, - pub(crate) segments: SmallVec<[(Rc, ParamItem); 3]>, -} - -impl Params { - pub(crate) fn new() -> Params { - Params { - url: Url::default(), - tail: 0, - segments: SmallVec::new(), - } - } - - pub(crate) fn with_url(url: &Url) -> Params { - Params { - url: url.clone(), - tail: 0, - segments: SmallVec::new(), - } - } - - pub(crate) fn clear(&mut self) { - self.segments.clear(); - } - - pub(crate) fn set_tail(&mut self, tail: u16) { - self.tail = tail; - } - - pub(crate) fn set_url(&mut self, url: Url) { - self.url = url; - } - - pub(crate) fn add(&mut self, name: Rc, value: ParamItem) { - self.segments.push((name, value)); - } - - pub(crate) fn add_static(&mut self, name: &str, value: &'static str) { - self.segments - .push((Rc::new(name.to_string()), ParamItem::Static(value))); - } - - /// Check if there are any matched patterns - pub fn is_empty(&self) -> bool { - self.segments.is_empty() - } - - /// Check number of extracted parameters - pub fn len(&self) -> usize { - self.segments.len() - } - - /// Get matched parameter by name without type conversion - pub fn get(&self, key: &str) -> Option<&str> { - for item in self.segments.iter() { - if key == item.0.as_str() { - return match item.1 { - ParamItem::Static(ref s) => Some(&s), - ParamItem::UrlSegment(s, e) => { - Some(&self.url.path()[(s as usize)..(e as usize)]) - } - }; - } - } - if key == "tail" { - Some(&self.url.path()[(self.tail as usize)..]) - } else { - None - } - } - - /// Get unprocessed part of path - pub fn unprocessed(&self) -> &str { - &self.url.path()[(self.tail as usize)..] - } - - /// 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("") - } - } - - /// Return iterator to items in parameter container - pub fn iter(&self) -> ParamsIter { - ParamsIter { - idx: 0, - params: self, - } - } -} - -#[derive(Debug)] -pub struct ParamsIter<'a> { - idx: usize, - params: &'a Params, -} - -impl<'a> Iterator for ParamsIter<'a> { - type Item = (&'a str, &'a str); - - #[inline] - fn next(&mut self) -> Option<(&'a str, &'a str)> { - if self.idx < self.params.len() { - let idx = self.idx; - let res = match self.params.segments[idx].1 { - ParamItem::Static(ref s) => &s, - ParamItem::UrlSegment(s, e) => { - &self.params.url.path()[(s as usize)..(e as usize)] - } - }; - self.idx += 1; - return Some((&self.params.segments[idx].0, res)); - } - None - } -} - -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") - } -} - -impl Index for Params { - type Output = str; - - fn index(&self, idx: usize) -> &str { - match self.segments[idx].1 { - ParamItem::Static(ref s) => &s, - ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)], - } - } -} - -/// 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) - } -} - -macro_rules! FROM_STR { - ($type:ty) => { - impl FromParam for $type { - type Err = InternalError<<$type as FromStr>::Err>; - fn from_param(val: &str) -> Result { - <$type as FromStr>::from_str(val) - .map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST)) - } - } - }; -} - -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/pipeline.rs b/src/pipeline.rs deleted file mode 100644 index 1940f930..00000000 --- a/src/pipeline.rs +++ /dev/null @@ -1,869 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; -use std::{io, mem}; - -use futures::sync::oneshot; -use futures::{Async, Future, Poll, Stream}; -use log::Level::Debug; - -use body::{Body, BodyStream}; -use context::{ActorHttpContext, Frame}; -use error::Error; -use handler::{AsyncResult, AsyncResultItem}; -use header::ContentEncoding; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Finished, Middleware, Response, Started}; -use server::{HttpHandlerTask, Writer, WriterState}; - -#[doc(hidden)] -pub trait PipelineHandler { - fn encoding(&self) -> ContentEncoding; - - fn handle(&self, &HttpRequest) -> AsyncResult; -} - -#[doc(hidden)] -pub struct Pipeline( - PipelineInfo, - PipelineState, - Rc>>>, -); - -enum PipelineState { - None, - Error, - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Response(ProcessResponse), - Finishing(FinishingMiddlewares), - Completed(Completed), -} - -impl> PipelineState { - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - match *self { - PipelineState::Starting(ref mut state) => state.poll(info, mws), - PipelineState::Handler(ref mut state) => state.poll(info, mws), - PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws), - PipelineState::Finishing(ref mut state) => state.poll(info, mws), - PipelineState::Completed(ref mut state) => state.poll(info), - PipelineState::Response(ref mut state) => state.poll(info, mws), - PipelineState::None | PipelineState::Error => None, - } - } -} - -struct PipelineInfo { - req: HttpRequest, - count: u16, - context: Option>, - error: Option, - disconnected: Option, - encoding: ContentEncoding, -} - -impl PipelineInfo { - fn poll_context(&mut self) -> Poll<(), Error> { - if let Some(ref mut context) = self.context { - match context.poll() { - Err(err) => Err(err), - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(_)) => Ok(Async::Ready(())), - } - } else { - Ok(Async::Ready(())) - } - } -} - -impl> Pipeline { - pub(crate) fn new( - req: HttpRequest, mws: Rc>>>, handler: Rc, - ) -> Pipeline { - let mut info = PipelineInfo { - req, - count: 0, - error: None, - context: None, - disconnected: None, - encoding: handler.encoding(), - }; - let state = StartMiddlewares::init(&mut info, &mws, handler); - - Pipeline(info, state, mws) - } -} - -impl Pipeline { - #[inline] - fn is_done(&self) -> bool { - match self.1 { - PipelineState::None - | PipelineState::Error - | PipelineState::Starting(_) - | PipelineState::Handler(_) - | PipelineState::RunMiddlewares(_) - | PipelineState::Response(_) => true, - PipelineState::Finishing(_) | PipelineState::Completed(_) => false, - } - } -} - -impl> HttpHandlerTask for Pipeline { - fn disconnected(&mut self) { - self.0.disconnected = Some(true); - } - - fn poll_io(&mut self, io: &mut Writer) -> Poll { - let mut state = mem::replace(&mut self.1, PipelineState::None); - - loop { - if let PipelineState::Response(st) = state { - match st.poll_io(io, &mut self.0, &self.2) { - Ok(state) => { - self.1 = state; - if let Some(error) = self.0.error.take() { - return Err(error); - } else { - return Ok(Async::Ready(self.is_done())); - } - } - Err(state) => { - self.1 = state; - return Ok(Async::NotReady); - } - } - } - match state { - PipelineState::None => return Ok(Async::Ready(true)), - PipelineState::Error => { - return Err( - io::Error::new(io::ErrorKind::Other, "Internal error").into() - ) - } - _ => (), - } - - match state.poll(&mut self.0, &self.2) { - Some(st) => state = st, - None => { - return { - self.1 = state; - Ok(Async::NotReady) - } - } - } - } - } - - fn poll_completed(&mut self) -> Poll<(), Error> { - let mut state = mem::replace(&mut self.1, PipelineState::None); - loop { - match state { - PipelineState::None | PipelineState::Error => { - return Ok(Async::Ready(())) - } - _ => (), - } - - if let Some(st) = state.poll(&mut self.0, &self.2) { - state = st; - } else { - self.1 = state; - return Ok(Async::NotReady); - } - } - } -} - -type Fut = Box, Error = Error>>; - -/// Middlewares start executor -struct StartMiddlewares { - hnd: Rc, - fut: Option, - _s: PhantomData, -} - -impl> StartMiddlewares { - fn init( - info: &mut PipelineInfo, mws: &[Box>], hnd: Rc, - ) -> PipelineState { - // execute middlewares, we need this stage because middlewares could be - // non-async and we can move to next state immediately - let len = mws.len() as u16; - - loop { - if info.count == len { - let reply = hnd.handle(&info.req); - return WaitingResponse::init(info, mws, reply); - } else { - match mws[info.count as usize].start(&info.req) { - Ok(Started::Done) => info.count += 1, - Ok(Started::Response(resp)) => { - return RunMiddlewares::init(info, mws, resp); - } - Ok(Started::Future(fut)) => { - return PipelineState::Starting(StartMiddlewares { - hnd, - fut: Some(fut), - _s: PhantomData, - }) - } - Err(err) => { - return RunMiddlewares::init(info, mws, err.into()); - } - } - } - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - let len = mws.len() as u16; - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, mws, resp)); - } - loop { - if info.count == len { - let reply = self.hnd.handle(&info.req); - return Some(WaitingResponse::init(info, mws, reply)); - } else { - let res = mws[info.count as usize].start(&info.req); - match res { - Ok(Started::Done) => info.count += 1, - Ok(Started::Response(resp)) => { - return Some(RunMiddlewares::init(info, mws, resp)); - } - Ok(Started::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init( - info, - mws, - err.into(), - )); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, mws, err.into())); - } - } - } - } -} - -// waiting for response -struct WaitingResponse { - fut: Box>, - _s: PhantomData, - _h: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], - reply: AsyncResult, - ) -> PipelineState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()), - AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse { - fut, - _s: PhantomData, - _h: PhantomData, - }), - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, mws, resp)), - Err(err) => Some(RunMiddlewares::init(info, mws, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, - _h: PhantomData, -} - -impl RunMiddlewares { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], mut resp: HttpResponse, - ) -> PipelineState { - if info.count == 0 { - return ProcessResponse::init(resp); - } - let mut curr = 0; - let len = mws.len(); - - loop { - let state = mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = (curr + 1) as u16; - return ProcessResponse::init(err.into()); - } - Ok(Response::Done(r)) => { - curr += 1; - if curr == len { - return ProcessResponse::init(r); - } else { - r - } - } - Ok(Response::Future(fut)) => { - return PipelineState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - _h: PhantomData, - }); - } - }; - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - let len = mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(ProcessResponse::init(err.into())), - }; - - loop { - if self.curr == len { - return Some(ProcessResponse::init(resp)); - } else { - let state = mws[self.curr].response(&info.req, resp); - match state { - Err(err) => return Some(ProcessResponse::init(err.into())), - Ok(Response::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(Response::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -struct ProcessResponse { - resp: Option, - iostate: IOState, - running: RunningState, - drain: Option>, - _s: PhantomData, - _h: PhantomData, -} - -#[derive(PartialEq, Debug)] -enum RunningState { - Running, - Paused, - Done, -} - -impl RunningState { - #[inline] - fn pause(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Paused - } - } - #[inline] - fn resume(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Running - } - } -} - -enum IOState { - Response, - Payload(BodyStream), - Actor(Box), - Done, -} - -impl ProcessResponse { - #[inline] - fn init(resp: HttpResponse) -> PipelineState { - PipelineState::Response(ProcessResponse { - resp: Some(resp), - iostate: IOState::Response, - running: RunningState::Running, - drain: None, - _s: PhantomData, - _h: PhantomData, - }) - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - // connection is dead at this point - match mem::replace(&mut self.iostate, IOState::Done) { - IOState::Response => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - IOState::Payload(_) => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - IOState::Actor(mut ctx) => { - if info.disconnected.take().is_some() { - ctx.disconnected(); - } - loop { - match ctx.poll() { - Ok(Async::Ready(Some(vec))) => { - if vec.is_empty() { - continue; - } - for frame in vec { - match frame { - Frame::Chunk(None) => { - info.context = Some(ctx); - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - Frame::Chunk(Some(_)) => (), - Frame::Drain(fut) => { - let _ = fut.send(()); - } - } - } - } - Ok(Async::Ready(None)) => { - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )) - } - Ok(Async::NotReady) => { - self.iostate = IOState::Actor(ctx); - return None; - } - Err(err) => { - info.context = Some(ctx); - info.error = Some(err); - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - } - IOState::Done => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - } - } - - fn poll_io( - mut self, io: &mut Writer, info: &mut PipelineInfo, - mws: &[Box>], - ) -> Result, PipelineState> { - loop { - if self.drain.is_none() && self.running != RunningState::Paused { - // if task is paused, write buffer is probably full - 'inner: loop { - let result = match mem::replace(&mut self.iostate, IOState::Done) { - IOState::Response => { - let encoding = self - .resp - .as_ref() - .unwrap() - .content_encoding() - .unwrap_or(info.encoding); - - let result = match io.start( - &info.req, - self.resp.as_mut().unwrap(), - encoding, - ) { - Ok(res) => res, - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - }; - - if let Some(err) = self.resp.as_ref().unwrap().error() { - if self.resp.as_ref().unwrap().status().is_server_error() - { - error!( - "Error occured during request handling, status: {} {}", - self.resp.as_ref().unwrap().status(), err - ); - } else { - warn!( - "Error occured during request handling: {}", - err - ); - } - if log_enabled!(Debug) { - debug!("{:?}", err); - } - } - - // always poll stream or actor for the first time - match self.resp.as_mut().unwrap().replace_body(Body::Empty) { - Body::Streaming(stream) => { - self.iostate = IOState::Payload(stream); - continue 'inner; - } - Body::Actor(ctx) => { - self.iostate = IOState::Actor(ctx); - continue 'inner; - } - _ => (), - } - - result - } - IOState::Payload(mut body) => match body.poll() { - Ok(Async::Ready(None)) => { - if let Err(err) = io.write_eof() { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - break; - } - Ok(Async::Ready(Some(chunk))) => { - self.iostate = IOState::Payload(body); - match io.write(&chunk.into()) { - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - Ok(result) => result, - } - } - Ok(Async::NotReady) => { - self.iostate = IOState::Payload(body); - break; - } - Err(err) => { - info.error = Some(err); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - }, - IOState::Actor(mut ctx) => { - if info.disconnected.take().is_some() { - ctx.disconnected(); - } - match ctx.poll() { - Ok(Async::Ready(Some(vec))) => { - if vec.is_empty() { - self.iostate = IOState::Actor(ctx); - break; - } - let mut res = None; - for frame in vec { - match frame { - Frame::Chunk(None) => { - info.context = Some(ctx); - if let Err(err) = io.write_eof() { - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - ), - ); - } - break 'inner; - } - Frame::Chunk(Some(chunk)) => match io - .write(&chunk) - { - Err(err) => { - info.context = Some(ctx); - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - ), - ); - } - Ok(result) => res = Some(result), - }, - Frame::Drain(fut) => self.drain = Some(fut), - } - } - self.iostate = IOState::Actor(ctx); - if self.drain.is_some() { - self.running.resume(); - break 'inner; - } - res.unwrap() - } - Ok(Async::Ready(None)) => break, - Ok(Async::NotReady) => { - self.iostate = IOState::Actor(ctx); - break; - } - Err(err) => { - info.context = Some(ctx); - info.error = Some(err); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - IOState::Done => break, - }; - - match result { - WriterState::Pause => { - self.running.pause(); - break; - } - WriterState::Done => self.running.resume(), - } - } - } - - // flush io but only if we need to - if self.running == RunningState::Paused || self.drain.is_some() { - match io.poll_completed(false) { - Ok(Async::Ready(_)) => { - self.running.resume(); - - // resolve drain futures - if let Some(tx) = self.drain.take() { - let _ = tx.send(()); - } - // restart io processing - continue; - } - Ok(Async::NotReady) => return Err(PipelineState::Response(self)), - Err(err) => { - if let IOState::Actor(mut ctx) = - mem::replace(&mut self.iostate, IOState::Done) - { - ctx.disconnected(); - info.context = Some(ctx); - } - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - break; - } - - // response is completed - match self.iostate { - IOState::Done => { - match io.write_eof() { - Ok(_) => (), - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - self.resp.as_mut().unwrap().set_response_size(io.written()); - Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )) - } - _ => Err(PipelineState::Response(self)), - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, - _h: PhantomData, -} - -impl FinishingMiddlewares { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], resp: HttpResponse, - ) -> PipelineState { - if info.count == 0 { - resp.release(); - Completed::init(info) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - _h: PhantomData, - }; - if let Some(st) = state.poll(info, mws) { - st - } else { - PipelineState::Finishing(state) - } - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - self.resp.take().unwrap().release(); - return Some(Completed::init(info)); - } - - info.count -= 1; - let state = - mws[info.count as usize].finish(&info.req, self.resp.as_ref().unwrap()); - match state { - Finished::Done => { - if info.count == 0 { - self.resp.take().unwrap().release(); - return Some(Completed::init(info)); - } - } - Finished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -#[derive(Debug)] -struct Completed(PhantomData, PhantomData); - -impl Completed { - #[inline] - fn init(info: &mut PipelineInfo) -> PipelineState { - if let Some(ref err) = info.error { - error!("Error occurred during request handling: {}", err); - } - - if info.context.is_none() { - PipelineState::None - } else { - match info.poll_context() { - Ok(Async::NotReady) => { - PipelineState::Completed(Completed(PhantomData, PhantomData)) - } - Ok(Async::Ready(())) => PipelineState::None, - Err(_) => PipelineState::Error, - } - } - } - - #[inline] - fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - match info.poll_context() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(())) => Some(PipelineState::None), - Err(_) => Some(PipelineState::Error), - } - } -} diff --git a/src/pred.rs b/src/pred.rs deleted file mode 100644 index 99d6e608..00000000 --- a/src/pred.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! Route match predicates -#![allow(non_snake_case)] -use std::marker::PhantomData; - -use http; -use http::{header, HttpTryFrom}; -use server::message::Request; - -/// Trait defines resource route predicate. -/// Predicate can modify request object. It is also possible to -/// to store extra attributes on request by using `Extensions` container, -/// Extensions container available via `HttpRequest::extensions()` method. -pub trait Predicate { - /// Check if request matches predicate - fn check(&self, &Request, &S) -> bool; -} - -/// Return predicate that matches if any of supplied predicate matches. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Any(pred::Get()).or(pred::Post())) -/// .f(|r| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn Any + 'static>(pred: P) -> AnyPredicate { - AnyPredicate(vec![Box::new(pred)]) -} - -/// 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: &Request, state: &S) -> bool { - for p in &self.0 { - if p.check(req, state) { - return true; - } - } - false - } -} - -/// Return predicate that matches if all of supplied predicate matches. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter( -/// pred::All(pred::Get()) -/// .and(pred::Header("content-type", "text/plain")), -/// ) -/// .f(|_| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn All + 'static>(pred: P) -> AllPredicate { - AllPredicate(vec![Box::new(pred)]) -} - -/// 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: &Request, state: &S) -> bool { - for p in &self.0 { - if !p.check(req, state) { - return false; - } - } - true - } -} - -/// Return predicate that matches if supplied predicate does not match. -pub fn Not + 'static>(pred: P) -> NotPredicate { - NotPredicate(Box::new(pred)) -} - -#[doc(hidden)] -pub struct NotPredicate(Box>); - -impl Predicate for NotPredicate { - fn check(&self, req: &Request, state: &S) -> bool { - !self.0.check(req, state) - } -} - -/// Http method predicate -#[doc(hidden)] -pub struct MethodPredicate(http::Method, PhantomData); - -impl Predicate for MethodPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - *req.method() == self.0 - } -} - -/// Predicate to match *GET* http method -pub fn Get() -> MethodPredicate { - MethodPredicate(http::Method::GET, PhantomData) -} - -/// Predicate to match *POST* http method -pub fn Post() -> MethodPredicate { - MethodPredicate(http::Method::POST, PhantomData) -} - -/// Predicate to match *PUT* http method -pub fn Put() -> MethodPredicate { - MethodPredicate(http::Method::PUT, PhantomData) -} - -/// Predicate to match *DELETE* http method -pub fn Delete() -> MethodPredicate { - MethodPredicate(http::Method::DELETE, PhantomData) -} - -/// Predicate to match *HEAD* http method -pub fn Head() -> MethodPredicate { - MethodPredicate(http::Method::HEAD, PhantomData) -} - -/// Predicate to match *OPTIONS* http method -pub fn Options() -> MethodPredicate { - MethodPredicate(http::Method::OPTIONS, PhantomData) -} - -/// Predicate to match *CONNECT* http method -pub fn Connect() -> MethodPredicate { - MethodPredicate(http::Method::CONNECT, PhantomData) -} - -/// Predicate to match *PATCH* http method -pub fn Patch() -> MethodPredicate { - MethodPredicate(http::Method::PATCH, PhantomData) -} - -/// Predicate to match *TRACE* http method -pub fn Trace() -> MethodPredicate { - MethodPredicate(http::Method::TRACE, PhantomData) -} - -/// Predicate to match specified http method -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, -) -> HeaderPredicate { - HeaderPredicate( - header::HeaderName::try_from(name).unwrap(), - header::HeaderValue::from_static(value), - PhantomData, - ) -} - -#[doc(hidden)] -pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); - -impl Predicate for HeaderPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - if let Some(val) = req.headers().get(&self.0) { - return val == self.1; - } - false - } -} - -/// Return predicate that matches if request contains specified Host name. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Host("www.rust-lang.org")) -/// .f(|_| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn Host>(host: H) -> HostPredicate { - HostPredicate(host.as_ref().to_string(), None, PhantomData) -} - -#[doc(hidden)] -pub struct HostPredicate(String, Option, PhantomData); - -impl HostPredicate { - /// Set reuest scheme to match - pub fn scheme>(&mut self, scheme: H) { - self.1 = Some(scheme.as_ref().to_string()) - } -} - -impl Predicate for HostPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - let info = req.connection_info(); - if let Some(ref scheme) = self.1 { - self.0 == info.host() && scheme == info.scheme() - } else { - self.0 == info.host() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::{header, Method}; - use test::TestRequest; - - #[test] - fn test_header() { - let req = TestRequest::with_header( - header::TRANSFER_ENCODING, - header::HeaderValue::from_static("chunked"), - ).finish(); - - let pred = Header("transfer-encoding", "chunked"); - assert!(pred.check(&req, req.state())); - - let pred = Header("transfer-encoding", "other"); - assert!(!pred.check(&req, req.state())); - - let pred = Header("content-type", "other"); - assert!(!pred.check(&req, req.state())); - } - - #[test] - fn test_host() { - let req = TestRequest::default() - .header( - header::HOST, - header::HeaderValue::from_static("www.rust-lang.org"), - ).finish(); - - let pred = Host("www.rust-lang.org"); - assert!(pred.check(&req, req.state())); - - let pred = Host("localhost"); - assert!(!pred.check(&req, req.state())); - } - - #[test] - fn test_methods() { - let req = TestRequest::default().finish(); - let req2 = TestRequest::default().method(Method::POST).finish(); - - assert!(Get().check(&req, req.state())); - assert!(!Get().check(&req2, req2.state())); - assert!(Post().check(&req2, req2.state())); - assert!(!Post().check(&req, req.state())); - - let r = TestRequest::default().method(Method::PUT).finish(); - assert!(Put().check(&r, r.state())); - assert!(!Put().check(&req, req.state())); - - let r = TestRequest::default().method(Method::DELETE).finish(); - assert!(Delete().check(&r, r.state())); - assert!(!Delete().check(&req, req.state())); - - let r = TestRequest::default().method(Method::HEAD).finish(); - assert!(Head().check(&r, r.state())); - assert!(!Head().check(&req, req.state())); - - let r = TestRequest::default().method(Method::OPTIONS).finish(); - assert!(Options().check(&r, r.state())); - assert!(!Options().check(&req, req.state())); - - let r = TestRequest::default().method(Method::CONNECT).finish(); - assert!(Connect().check(&r, r.state())); - assert!(!Connect().check(&req, req.state())); - - let r = TestRequest::default().method(Method::PATCH).finish(); - assert!(Patch().check(&r, r.state())); - assert!(!Patch().check(&req, req.state())); - - let r = TestRequest::default().method(Method::TRACE).finish(); - assert!(Trace().check(&r, r.state())); - assert!(!Trace().check(&req, req.state())); - } - - #[test] - fn test_preds() { - let r = TestRequest::default().method(Method::TRACE).finish(); - - assert!(Not(Get()).check(&r, r.state())); - assert!(!Not(Trace()).check(&r, r.state())); - - assert!(All(Trace()).and(Trace()).check(&r, r.state())); - assert!(!All(Get()).and(Trace()).check(&r, r.state())); - - assert!(Any(Get()).or(Trace()).check(&r, r.state())); - assert!(!Any(Get()).or(Get()).check(&r, r.state())); - } -} diff --git a/src/resource.rs b/src/resource.rs deleted file mode 100644 index d884dd44..00000000 --- a/src/resource.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::ops::Deref; -use std::rc::Rc; - -use futures::Future; -use http::Method; -use smallvec::SmallVec; - -use error::Error; -use handler::{AsyncResult, FromRequest, Handler, Responder}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use pred; -use route::Route; -use router::ResourceDef; -use with::WithFactory; - -#[derive(Copy, Clone)] -pub(crate) struct RouteId(usize); - -/// *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) -/// 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 considered matched and route handler get called. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{App, HttpResponse, http}; -/// -/// fn main() { -/// let app = App::new() -/// .resource( -/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok())) -/// .finish(); -/// } -pub struct Resource { - rdef: ResourceDef, - routes: SmallVec<[Route; 3]>, - middlewares: Rc>>>, -} - -impl Resource { - /// Create new resource with specified resource definition - pub fn new(rdef: ResourceDef) -> Self { - Resource { - rdef, - routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()), - } - } - - /// Name of the resource - pub(crate) fn get_name(&self) -> &str { - self.rdef.name() - } - - /// Set resource name - pub fn name(&mut self, name: &str) { - self.rdef.set_name(name); - } - - /// Resource definition - pub fn rdef(&self) -> &ResourceDef { - &self.rdef - } -} - -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. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new() - /// .resource("/", |r| { - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .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 `GET` route. - pub fn get(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Get()) - } - - /// Register a new `POST` route. - pub fn post(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Post()) - } - - /// Register a new `PUT` route. - pub fn put(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Put()) - } - - /// Register a new `DELETE` route. - pub fn delete(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Delete()) - } - - /// Register a new `HEAD` route. - pub fn head(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Head()) - } - - /// Register a new route and add method check to route. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.method(http::Method::GET).f(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index)); - /// ``` - pub fn method(&mut self, method: Method) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Method(method)) - } - - /// Register a new route and add handler object. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.h(handler)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().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. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.f(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().f(index)); - /// ``` - pub fn f(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().f(handler) - } - - /// Register a new route and add handler. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.with(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().with(index)); - /// ``` - pub fn with(&mut self, handler: F) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with(handler); - } - - /// Register a new route and add async handler. - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// use actix_web::*; - /// use futures::future::Future; - /// - /// fn index(req: HttpRequest) -> Box> { - /// unimplemented!() - /// } - /// - /// App::new().resource("/", |r| r.with_async(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// # use actix_web::*; - /// # use futures::future::Future; - /// # fn index(req: HttpRequest) -> Box> { - /// # unimplemented!() - /// # } - /// App::new().resource("/", |r| r.route().with_async(index)); - /// ``` - pub fn with_async(&mut self, handler: F) - where - F: Fn(T) -> R + 'static, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with_async(handler); - } - - /// Register a resource middleware - /// - /// This is similar to `App's` middlewares, but - /// middlewares get invoked on resource level. - /// - /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to peer. - pub fn middleware>(&mut self, mw: M) { - Rc::get_mut(&mut self.middlewares) - .unwrap() - .push(Box::new(mw)); - } - - #[inline] - pub(crate) fn get_route_id(&self, req: &HttpRequest) -> Option { - for idx in 0..self.routes.len() { - if (&self.routes[idx]).check(req) { - return Some(RouteId(idx)); - } - } - None - } - - #[inline] - pub(crate) fn handle( - &self, id: RouteId, req: &HttpRequest, - ) -> AsyncResult { - if self.middlewares.is_empty() { - (&self.routes[id.0]).handle(req) - } else { - (&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares)) - } - } -} - -/// Default resource -pub struct DefaultResource(Rc>); - -impl Deref for DefaultResource { - type Target = Resource; - - fn deref(&self) -> &Resource { - self.0.as_ref() - } -} - -impl Clone for DefaultResource { - fn clone(&self) -> Self { - DefaultResource(self.0.clone()) - } -} - -impl From> for DefaultResource { - fn from(res: Resource) -> Self { - DefaultResource(Rc::new(res)) - } -} diff --git a/src/route.rs b/src/route.rs deleted file mode 100644 index e4a7a957..00000000 --- a/src/route.rs +++ /dev/null @@ -1,666 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; - -use futures::{Async, Future, Poll}; - -use error::Error; -use handler::{ - AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, - RouteHandler, WrapHandler, -}; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{ - Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted, -}; -use pred::Predicate; -use with::{WithAsyncFactory, WithFactory}; - -/// 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: InnerHandler, -} - -impl Default for Route { - fn default() -> Route { - Route { - preds: Vec::new(), - handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)), - } - } -} - -impl Route { - #[inline] - pub(crate) fn check(&self, req: &HttpRequest) -> bool { - let state = req.state(); - for pred in &self.preds { - if !pred.check(req, state) { - return false; - } - } - true - } - - #[inline] - pub(crate) fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.handler.handle(req) - } - - #[inline] - pub(crate) fn compose( - &self, req: HttpRequest, mws: Rc>>>, - ) -> AsyncResult { - AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) - } - - /// Add match predicate to route. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn main() { - /// App::new().resource("/path", |r| { - /// r.route() - /// .filter(pred::Get()) - /// .filter(pred::Header("content-type", "text/plain")) - /// .f(|req| HttpResponse::Ok()) - /// }) - /// # .finish(); - /// # } - /// ``` - pub fn filter + 'static>(&mut self, p: T) -> &mut Self { - self.preds.push(Box::new(p)); - self - } - - /// Set handler object. Usually call to this method is last call - /// during route configuration, so it does not return reference to self. - pub fn h>(&mut self, handler: H) { - self.handler = InnerHandler::new(handler); - } - - /// Set handler function. Usually call to this method is last call - /// during route configuration, so it does not return reference to self. - pub fn f(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.handler = InnerHandler::new(handler); - } - - /// Set async handler function. - pub fn a(&mut self, handler: H) - where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - self.handler = InnerHandler::async(handler); - } - - /// Set handler function, use request extractor for parameters. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Path, Result}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Path) -> Result { - /// Ok(format!("Welcome {}!", info.username)) - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index), - /// ); // <- use `with` extractor - /// } - /// ``` - /// - /// It is possible to use multiple extractors for one handler function. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// # use std::collections::HashMap; - /// use actix_web::{http, App, Json, Path, Query, Result}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index( - /// path: Path, query: Query>, body: Json, - /// ) -> Result { - /// Ok(format!("Welcome {}!", path.username)) - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with(&mut self, handler: F) - where - F: WithFactory + 'static, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.h(handler.create()); - } - - /// Set handler function. Same as `.with()` but it allows to configure - /// extractor. Configuration closure accepts config objects as tuple. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Path, Result}; - /// - /// /// extract text data from request - /// fn index(body: String) -> Result { - /// Ok(format!("Body {}!", body)) - /// } - /// - /// fn main() { - /// let app = App::new().resource("/index.html", |r| { - /// r.method(http::Method::GET) - /// .with_config(index, |cfg| { // <- register handler - /// cfg.0.limit(4096); // <- limit size of the payload - /// }) - /// }); - /// } - /// ``` - pub fn with_config(&mut self, handler: F, cfg_f: C) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - C: FnOnce(&mut T::Config), - { - let mut cfg = ::default(); - cfg_f(&mut cfg); - self.h(handler.create_with_config(cfg)); - } - - /// Set async handler function, use request extractor for parameters. - /// Also this method needs to be used if your handler function returns - /// `impl Future<>` - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Path}; - /// use futures::Future; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Path) -> Box> { - /// unimplemented!() - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with_async(index), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with_async(&mut self, handler: F) - where - F: WithAsyncFactory, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - { - self.h(handler.create()); - } - - /// Set async handler function, use request extractor for parameters. - /// This method allows to configure extractor. Configuration closure - /// accepts config objects as tuple. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Form}; - /// use futures::Future; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Form) -> Box> { - /// unimplemented!() - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET) - /// .with_async_config(index, |cfg| { - /// cfg.0.limit(4096); - /// }), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with_async_config(&mut self, handler: F, cfg: C) - where - F: WithAsyncFactory, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - C: FnOnce(&mut T::Config), - { - let mut extractor_cfg = ::default(); - cfg(&mut extractor_cfg); - self.h(handler.create_with_config(extractor_cfg)); - } -} - -/// `RouteHandler` wrapper. This struct is required because it needs to be -/// shared for resource level middlewares. -struct InnerHandler(Rc>>); - -impl InnerHandler { - #[inline] - fn new>(h: H) -> Self { - InnerHandler(Rc::new(Box::new(WrapHandler::new(h)))) - } - - #[inline] - fn async(h: H) -> Self - where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - InnerHandler(Rc::new(Box::new(AsyncHandler::new(h)))) - } - - #[inline] - pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.0.handle(req) - } -} - -impl Clone for InnerHandler { - #[inline] - fn clone(&self) -> Self { - InnerHandler(Rc::clone(&self.0)) - } -} - -/// Compose resource level middlewares with route handler. -struct Compose { - info: ComposeInfo, - state: ComposeState, -} - -struct ComposeInfo { - count: usize, - req: HttpRequest, - mws: Rc>>>, - handler: InnerHandler, -} - -enum ComposeState { - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Finishing(FinishingMiddlewares), - Completed(Response), -} - -impl ComposeState { - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match *self { - ComposeState::Starting(ref mut state) => state.poll(info), - ComposeState::Handler(ref mut state) => state.poll(info), - ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Finishing(ref mut state) => state.poll(info), - ComposeState::Completed(_) => None, - } - } -} - -impl Compose { - fn new( - req: HttpRequest, mws: Rc>>>, handler: InnerHandler, - ) -> Self { - let mut info = ComposeInfo { - count: 0, - req, - mws, - handler, - }; - let state = StartMiddlewares::init(&mut info); - - Compose { state, info } - } -} - -impl Future for Compose { - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - if let ComposeState::Completed(ref mut resp) = self.state { - let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)); - } - if let Some(state) = self.state.poll(&mut self.info) { - self.state = state; - } else { - return Ok(Async::NotReady); - } - } - } -} - -/// Middlewares start executor -struct StartMiddlewares { - fut: Option, - _s: PhantomData, -} - -type Fut = Box, Error = Error>>; - -impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); - - loop { - if info.count == len { - let reply = info.handler.handle(&info.req); - return WaitingResponse::init(info, reply); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp); - } - Ok(MiddlewareStarted::Future(fut)) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }); - } - Err(err) => { - return RunMiddlewares::init(info, err.into()); - } - } - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, resp)); - } - loop { - if info.count == len { - let reply = info.handler.handle(&info.req); - return Some(WaitingResponse::init(info, reply)); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return Some(RunMiddlewares::init(info, resp)); - } - Ok(MiddlewareStarted::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } -} - -type HandlerFuture = Future; - -// waiting for response -struct WaitingResponse { - fut: Box, - _s: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut ComposeInfo, reply: AsyncResult, - ) -> ComposeState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), - AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { - fut, - _s: PhantomData, - }), - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), - Err(err) => Some(RunMiddlewares::init(info, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, -} - -impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { - let mut curr = 0; - let len = info.mws.len(); - - loop { - let state = info.mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = curr + 1; - return FinishingMiddlewares::init(info, err.into()); - } - Ok(MiddlewareResponse::Done(r)) => { - curr += 1; - if curr == len { - return FinishingMiddlewares::init(info, r); - } else { - r - } - } - Ok(MiddlewareResponse::Future(fut)) => { - return ComposeState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - }); - } - }; - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), - }; - - loop { - if self.curr == len { - return Some(FinishingMiddlewares::init(info, resp)); - } else { - let state = info.mws[self.curr].response(&info.req, resp); - match state { - Err(err) => { - return Some(FinishingMiddlewares::init(info, err.into())) - } - Ok(MiddlewareResponse::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(MiddlewareResponse::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, -} - -impl FinishingMiddlewares { - fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { - if info.count == 0 { - Response::init(resp) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - }; - if let Some(st) = state.poll(info) { - st - } else { - ComposeState::Finishing(state) - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - - info.count -= 1; - - let state = info.mws[info.count as usize] - .finish(&info.req, self.resp.as_ref().unwrap()); - match state { - MiddlewareFinished::Done => { - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - } - MiddlewareFinished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -struct Response { - resp: Option, - _s: PhantomData, -} - -impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Completed(Response { - resp: Some(resp), - _s: PhantomData, - }) - } -} diff --git a/src/router.rs b/src/router.rs deleted file mode 100644 index aa15e46d..00000000 --- a/src/router.rs +++ /dev/null @@ -1,1247 +0,0 @@ -use std::cell::RefCell; -use std::cmp::min; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; -use std::rc::Rc; - -use regex::{escape, Regex}; -use url::Url; - -use error::UrlGenerationError; -use handler::{AsyncResult, FromRequest, Responder, RouteHandler}; -use http::{Method, StatusCode}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use param::{ParamItem, Params}; -use pred::Predicate; -use resource::{DefaultResource, Resource}; -use scope::Scope; -use server::Request; -use with::WithFactory; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum ResourceId { - Default, - Normal(u16), -} - -enum ResourcePattern { - Resource(ResourceDef), - Handler(ResourceDef, Option>>>), - Scope(ResourceDef, Vec>>), -} - -enum ResourceItem { - Resource(Resource), - Handler(Box>), - Scope(Scope), -} - -/// Interface for application router. -pub struct Router { - rmap: Rc, - patterns: Vec>, - resources: Vec>, - default: Option>, -} - -/// Information about current resource -#[derive(Clone)] -pub struct ResourceInfo { - rmap: Rc, - resource: ResourceId, - params: Params, - prefix: u16, -} - -impl ResourceInfo { - /// Name os the resource - #[inline] - pub fn name(&self) -> &str { - if let ResourceId::Normal(idx) = self.resource { - self.rmap.patterns[idx as usize].0.name() - } else { - "" - } - } - - /// This method returns reference to matched `ResourceDef` object. - #[inline] - pub fn rdef(&self) -> Option<&ResourceDef> { - if let ResourceId::Normal(idx) = self.resource { - Some(&self.rmap.patterns[idx as usize].0) - } else { - None - } - } - - pub(crate) fn set_prefix(&mut self, prefix: u16) { - self.prefix = prefix; - } - - /// Get a reference to the Params object. - /// - /// Params is a container for url parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. - #[inline] - pub fn match_info(&self) -> &Params { - &self.params - } - - #[inline] - pub(crate) fn merge(&mut self, info: &ResourceInfo) { - let mut p = info.params.clone(); - p.set_tail(self.params.tail); - for item in &self.params.segments { - p.add(item.0.clone(), item.1.clone()); - } - - self.prefix = info.params.tail; - self.params = p; - } - - /// Generate url for named resource - /// - /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. - /// url_for) for detailed information. - pub fn url_for( - &self, req: &Request, name: &str, elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - let mut path = String::new(); - let mut elements = elements.into_iter(); - - if self - .rmap - .patterns_for(name, &mut path, &mut elements)? - .is_some() - { - if path.starts_with('/') { - let conn = req.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) - } else { - Ok(Url::parse(&path)?) - } - } else { - Err(UrlGenerationError::ResourceNotFound) - } - } - - /// Check if application contains matching resource. - /// - /// 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_resource()` call - /// would return `false`. - pub fn has_resource(&self, path: &str) -> bool { - self.rmap.has_resource(path) - } - - /// Check if application contains matching resource. - /// - /// This method does take `prefix` into account - /// but behaves like `has_route` in case `prefix` is not set in the router. - /// - /// For example if prefix is `/test` and router contains route `/name`, the - /// following path would be recognizable `/test/name` and `has_prefixed_route()` call - /// would return `true`. - /// It will not match against prefix in case it's not given. For example for `/name` - /// with a `/test` prefix would return `false` - pub fn has_prefixed_resource(&self, path: &str) -> bool { - let prefix = self.prefix as usize; - if prefix >= path.len() { - return false; - } - self.rmap.has_resource(&path[prefix..]) - } -} - -pub(crate) struct ResourceMap { - root: ResourceDef, - parent: RefCell>>, - named: HashMap, - patterns: Vec<(ResourceDef, Option>)>, - nested: Vec>, -} - -impl ResourceMap { - fn has_resource(&self, path: &str) -> bool { - let path = if path.is_empty() { "/" } else { path }; - - for (pattern, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if let Some(plen) = pattern.is_prefix_match(path) { - return rmap.has_resource(&path[plen..]); - } - } else if pattern.is_match(path) { - return true; - } - } - false - } - - fn patterns_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if self.pattern_for(name, path, elements)?.is_some() { - Ok(Some(())) - } else { - self.parent_pattern_for(name, path, elements) - } - } - - fn pattern_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(pattern) = self.named.get(name) { - self.fill_root(path, elements)?; - pattern.resource_path(path, elements)?; - Ok(Some(())) - } else { - for rmap in &self.nested { - if rmap.pattern_for(name, path, elements)?.is_some() { - return Ok(Some(())); - } - } - Ok(None) - } - } - - fn fill_root( - &self, path: &mut String, elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - parent.fill_root(path, elements)?; - } - self.root.resource_path(path, elements) - } - - fn parent_pattern_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - if let Some(pattern) = parent.named.get(name) { - self.fill_root(path, elements)?; - pattern.resource_path(path, elements)?; - Ok(Some(())) - } else { - parent.parent_pattern_for(name, path, elements) - } - } else { - Ok(None) - } - } -} - -impl Default for Router { - fn default() -> Self { - Router::new(ResourceDef::new("")) - } -} - -impl Router { - pub(crate) fn new(root: ResourceDef) -> Self { - Router { - rmap: Rc::new(ResourceMap { - root, - parent: RefCell::new(None), - named: HashMap::new(), - patterns: Vec::new(), - nested: Vec::new(), - }), - resources: Vec::new(), - patterns: Vec::new(), - default: None, - } - } - - #[inline] - pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> ResourceInfo { - ResourceInfo { - params, - prefix: 0, - rmap: self.rmap.clone(), - resource: ResourceId::Normal(idx), - } - } - - #[cfg(test)] - pub(crate) fn default_route_info(&self) -> ResourceInfo { - ResourceInfo { - params: Params::new(), - rmap: self.rmap.clone(), - resource: ResourceId::Default, - prefix: 0, - } - } - - pub(crate) fn set_prefix(&mut self, path: &str) { - Rc::get_mut(&mut self.rmap).unwrap().root = ResourceDef::new(path); - } - - pub(crate) fn register_resource(&mut self, resource: Resource) { - { - let rmap = Rc::get_mut(&mut self.rmap).unwrap(); - - let name = resource.get_name(); - if !name.is_empty() { - assert!( - !rmap.named.contains_key(name), - "Named resource {:?} is registered.", - name - ); - rmap.named.insert(name.to_owned(), resource.rdef().clone()); - } - rmap.patterns.push((resource.rdef().clone(), None)); - } - self.patterns - .push(ResourcePattern::Resource(resource.rdef().clone())); - self.resources.push(ResourceItem::Resource(resource)); - } - - pub(crate) fn register_scope(&mut self, mut scope: Scope) { - Rc::get_mut(&mut self.rmap) - .unwrap() - .patterns - .push((scope.rdef().clone(), Some(scope.router().rmap.clone()))); - Rc::get_mut(&mut self.rmap) - .unwrap() - .nested - .push(scope.router().rmap.clone()); - - let filters = scope.take_filters(); - self.patterns - .push(ResourcePattern::Scope(scope.rdef().clone(), filters)); - self.resources.push(ResourceItem::Scope(scope)); - } - - pub(crate) fn register_handler( - &mut self, path: &str, hnd: Box>, - filters: Option>>>, - ) { - let rdef = ResourceDef::prefix(path); - Rc::get_mut(&mut self.rmap) - .unwrap() - .patterns - .push((rdef.clone(), None)); - self.resources.push(ResourceItem::Handler(hnd)); - self.patterns.push(ResourcePattern::Handler(rdef, filters)); - } - - pub(crate) fn has_default_resource(&self) -> bool { - self.default.is_some() - } - - pub(crate) fn register_default_resource(&mut self, resource: DefaultResource) { - self.default = Some(resource); - } - - pub(crate) fn finish(&mut self) { - for resource in &mut self.resources { - match resource { - ResourceItem::Resource(_) => (), - ResourceItem::Scope(scope) => { - if !scope.has_default_resource() { - if let Some(ref default) = self.default { - scope.default_resource(default.clone()); - } - } - *scope.router().rmap.parent.borrow_mut() = Some(self.rmap.clone()); - scope.finish(); - } - ResourceItem::Handler(hnd) => { - if !hnd.has_default_resource() { - if let Some(ref default) = self.default { - hnd.default_resource(default.clone()); - } - } - hnd.finish() - } - } - } - } - - pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { - let rmap = Rc::get_mut(&mut self.rmap).unwrap(); - assert!( - !rmap.named.contains_key(name), - "Named resource {:?} is registered.", - name - ); - rmap.named.insert(name.to_owned(), rdef); - } - - pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - let out = { - // get resource handler - let mut iterator = self.resources.iter_mut(); - - loop { - if let Some(ref mut resource) = iterator.next() { - if let ResourceItem::Resource(ref mut resource) = resource { - if resource.rdef().pattern() == path { - resource.method(method).with(f); - break None; - } - } - } else { - let mut resource = Resource::new(ResourceDef::new(path)); - resource.method(method).with(f); - break Some(resource); - } - } - }; - if let Some(out) = out { - self.register_resource(out); - } - } - - /// Handle request - pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - let resource = match req.resource().resource { - ResourceId::Normal(idx) => &self.resources[idx as usize], - ResourceId::Default => { - if let Some(ref default) = self.default { - if let Some(id) = default.get_route_id(req) { - return default.handle(id, req); - } - } - return AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)); - } - }; - match resource { - ResourceItem::Resource(ref resource) => { - if let Some(id) = resource.get_route_id(req) { - return resource.handle(id, req); - } - - if let Some(ref default) = self.default { - if let Some(id) = default.get_route_id(req) { - return default.handle(id, req); - } - } - } - ResourceItem::Handler(hnd) => return hnd.handle(req), - ResourceItem::Scope(hnd) => return hnd.handle(req), - } - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) - } - - /// Query for matched resource - pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> ResourceInfo { - if tail <= req.path().len() { - 'outer: for (idx, resource) in self.patterns.iter().enumerate() { - match resource { - ResourcePattern::Resource(rdef) => { - if let Some(params) = rdef.match_with_params(req, tail) { - return self.route_info_params(idx as u16, params); - } - } - ResourcePattern::Handler(rdef, filters) => { - if let Some(params) = rdef.match_prefix_with_params(req, tail) { - if let Some(ref filters) = filters { - for filter in filters { - if !filter.check(req, state) { - continue 'outer; - } - } - } - return self.route_info_params(idx as u16, params); - } - } - ResourcePattern::Scope(rdef, filters) => { - if let Some(params) = rdef.match_prefix_with_params(req, tail) { - for filter in filters { - if !filter.check(req, state) { - continue 'outer; - } - } - return self.route_info_params(idx as u16, params); - } - } - } - } - } - ResourceInfo { - prefix: tail as u16, - params: Params::new(), - rmap: self.rmap.clone(), - resource: ResourceId::Default, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -enum PatternElement { - Str(String), - Var(String), -} - -#[derive(Clone, Debug)] -enum PatternType { - Static(String), - Prefix(String), - Dynamic(Regex, Vec>, usize), -} - -#[derive(Debug, Copy, Clone, PartialEq)] -/// Resource type -pub enum ResourceType { - /// Normal resource - Normal, - /// Resource for application default handler - Default, - /// External resource - External, - /// Unknown resource type - Unset, -} - -/// Resource type describes an entry in resources table -#[derive(Clone, Debug)] -pub struct ResourceDef { - tp: PatternType, - rtp: ResourceType, - name: String, - pattern: String, - elements: Vec, -} - -impl ResourceDef { - /// Parse path pattern and create new `ResourceDef` instance. - /// - /// Panics if path pattern is wrong. - pub fn new(path: &str) -> Self { - ResourceDef::with_prefix(path, false, !path.is_empty()) - } - - /// Parse path pattern and create new `ResourceDef` instance. - /// - /// Use `prefix` type instead of `static`. - /// - /// Panics if path regex pattern is wrong. - pub fn prefix(path: &str) -> Self { - ResourceDef::with_prefix(path, true, !path.is_empty()) - } - - /// Construct external resource def - /// - /// Panics if path pattern is wrong. - pub fn external(path: &str) -> Self { - let mut resource = ResourceDef::with_prefix(path, false, false); - resource.rtp = ResourceType::External; - resource - } - - /// Parse path pattern and create new `ResourceDef` instance with custom prefix - pub fn with_prefix(path: &str, for_prefix: bool, slash: bool) -> Self { - let mut path = path.to_owned(); - if slash && !path.starts_with('/') { - path.insert(0, '/'); - } - let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix); - - let tp = if is_dynamic { - let re = match Regex::new(&pattern) { - Ok(re) => re, - Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), - }; - // actix creates one router per thread - let names = re - .capture_names() - .filter_map(|name| name.map(|name| Rc::new(name.to_owned()))) - .collect(); - PatternType::Dynamic(re, names, len) - } else if for_prefix { - PatternType::Prefix(pattern.clone()) - } else { - PatternType::Static(pattern.clone()) - }; - - ResourceDef { - tp, - elements, - name: "".to_string(), - rtp: ResourceType::Normal, - pattern: path.to_owned(), - } - } - - /// Resource type - pub fn rtype(&self) -> ResourceType { - self.rtp - } - - /// Resource name - pub fn name(&self) -> &str { - &self.name - } - - /// Resource name - pub(crate) fn set_name(&mut self, name: &str) { - self.name = name.to_owned(); - } - - /// Path pattern of the resource - pub fn pattern(&self) -> &str { - &self.pattern - } - - /// Is this path a match against this resource? - pub fn is_match(&self, path: &str) -> bool { - match self.tp { - PatternType::Static(ref s) => s == path, - PatternType::Dynamic(ref re, _, _) => re.is_match(path), - PatternType::Prefix(ref s) => path.starts_with(s), - } - } - - fn is_prefix_match(&self, path: &str) -> Option { - let plen = path.len(); - let path = if path.is_empty() { "/" } else { path }; - - match self.tp { - PatternType::Static(ref s) => if s == path { - Some(plen) - } else { - None - }, - PatternType::Dynamic(ref re, _, len) => { - if let Some(captures) = re.captures(path) { - let mut pos = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - pos = m.end(); - } - } - Some(plen + pos + len) - } else { - None - } - } - PatternType::Prefix(ref s) => { - let len = if path == s { - s.len() - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return None; - }; - Some(min(plen, len)) - } - } - } - - /// Are the given path and parameters a match against this resource? - pub fn match_with_params(&self, req: &Request, plen: usize) -> Option { - let path = &req.path()[plen..]; - - match self.tp { - PatternType::Static(ref s) => if s != path { - None - } else { - Some(Params::with_url(req.url())) - }, - PatternType::Dynamic(ref re, ref names, _) => { - if let Some(captures) = re.captures(path) { - let mut params = Params::with_url(req.url()); - let mut idx = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - params.add( - names[idx].clone(), - ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - ), - ); - idx += 1; - } - } - params.set_tail(req.path().len() as u16); - Some(params) - } else { - None - } - } - PatternType::Prefix(ref s) => if !path.starts_with(s) { - None - } else { - Some(Params::with_url(req.url())) - }, - } - } - - /// Is the given path a prefix match and do the parameters match against this resource? - pub fn match_prefix_with_params( - &self, req: &Request, plen: usize, - ) -> Option { - let path = &req.path()[plen..]; - let path = if path.is_empty() { "/" } else { path }; - - match self.tp { - PatternType::Static(ref s) => if s == path { - let mut params = Params::with_url(req.url()); - params.set_tail(req.path().len() as u16); - Some(params) - } else { - None - }, - PatternType::Dynamic(ref re, ref names, len) => { - if let Some(captures) = re.captures(path) { - let mut params = Params::with_url(req.url()); - let mut pos = 0; - let mut passed = false; - let mut idx = 0; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - params.add( - names[idx].clone(), - ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - ), - ); - idx += 1; - pos = m.end(); - } - } - params.set_tail((plen + pos + len) as u16); - Some(params) - } else { - None - } - } - PatternType::Prefix(ref s) => { - let len = if path == s { - s.len() - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return None; - }; - let mut params = Params::with_url(req.url()); - params.set_tail(min(req.path().len(), plen + len) as u16); - Some(params) - } - } - } - - /// Build resource path. - pub fn resource_path( - &self, path: &mut String, elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - match self.tp { - PatternType::Prefix(ref p) => path.push_str(p), - PatternType::Static(ref p) => path.push_str(p), - PatternType::Dynamic(..) => { - for el in &self.elements { - match *el { - PatternElement::Str(ref s) => path.push_str(s), - PatternElement::Var(_) => { - if let Some(val) = elements.next() { - path.push_str(val.as_ref()) - } else { - return Err(UrlGenerationError::NotEnoughElements); - } - } - } - } - } - }; - Ok(()) - } - - fn parse_param(pattern: &str) -> (PatternElement, String, &str) { - const DEFAULT_PATTERN: &str = "[^/]+"; - let mut params_nesting = 0usize; - let close_idx = pattern - .find(|c| match c { - '{' => { - params_nesting += 1; - false - } - '}' => { - params_nesting -= 1; - params_nesting == 0 - } - _ => false, - }).expect("malformed param"); - let (mut param, rem) = pattern.split_at(close_idx + 1); - param = ¶m[1..param.len() - 1]; // Remove outer brackets - let (name, pattern) = match param.find(':') { - Some(idx) => { - let (name, pattern) = param.split_at(idx); - (name, &pattern[1..]) - } - None => (param, DEFAULT_PATTERN), - }; - ( - PatternElement::Var(name.to_string()), - format!(r"(?P<{}>{})", &name, &pattern), - rem, - ) - } - - fn parse( - mut pattern: &str, for_prefix: bool, - ) -> (String, Vec, bool, usize) { - if pattern.find('{').is_none() { - return ( - String::from(pattern), - vec![PatternElement::Str(String::from(pattern))], - false, - pattern.chars().count(), - ); - }; - - let mut elems = Vec::new(); - let mut re = String::from("^"); - - while let Some(idx) = pattern.find('{') { - let (prefix, rem) = pattern.split_at(idx); - elems.push(PatternElement::Str(String::from(prefix))); - re.push_str(&escape(prefix)); - let (param_pattern, re_part, rem) = Self::parse_param(rem); - elems.push(param_pattern); - re.push_str(&re_part); - pattern = rem; - } - - elems.push(PatternElement::Str(String::from(pattern))); - re.push_str(&escape(pattern)); - - if !for_prefix { - re.push_str("$"); - } - - (re, elems, true, pattern.chars().count()) - } -} - -impl PartialEq for ResourceDef { - fn eq(&self, other: &ResourceDef) -> bool { - self.pattern == other.pattern - } -} - -impl Eq for ResourceDef {} - -impl Hash for ResourceDef { - fn hash(&self, state: &mut H) { - self.pattern.hash(state); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::TestRequest; - - #[test] - fn test_recognizer10() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - router.register_resource(Resource::new(ResourceDef::new( - "/name/{val}/index.html", - ))); - router.register_resource(Resource::new(ResourceDef::new("/file/{file}.{ext}"))); - router.register_resource(Resource::new(ResourceDef::new( - "/v{val}/{val2}/index.html", - ))); - router.register_resource(Resource::new(ResourceDef::new("/v/{tail:.*}"))); - router.register_resource(Resource::new(ResourceDef::new("/test2/{test}.html"))); - router.register_resource(Resource::new(ResourceDef::new("/{test}/index.html"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - assert!(info.match_info().is_empty()); - - let req = TestRequest::with_uri("/name/value").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.match_info().get("val").unwrap(), "value"); - assert_eq!(&info.match_info()["val"], "value"); - - let req = TestRequest::with_uri("/name/value2/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(2)); - assert_eq!(info.match_info().get("val").unwrap(), "value2"); - - let req = TestRequest::with_uri("/file/file.gz").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(3)); - assert_eq!(info.match_info().get("file").unwrap(), "file"); - assert_eq!(info.match_info().get("ext").unwrap(), "gz"); - - let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(4)); - assert_eq!(info.match_info().get("val").unwrap(), "test"); - assert_eq!(info.match_info().get("val2").unwrap(), "ttt"); - - let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(5)); - assert_eq!( - info.match_info().get("tail").unwrap(), - "blah-blah/index.html" - ); - - let req = TestRequest::with_uri("/test2/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(6)); - assert_eq!(info.match_info().get("test").unwrap(), "index"); - - let req = TestRequest::with_uri("/bbb/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(7)); - assert_eq!(info.match_info().get("test").unwrap(), "bbb"); - } - - #[test] - fn test_recognizer_2() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/index.json"))); - router.register_resource(Resource::new(ResourceDef::new("/{source}.json"))); - - let req = TestRequest::with_uri("/index.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - } - - #[test] - fn test_recognizer_with_prefix() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test/name").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test/name/value").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.match_info().get("val").unwrap(), "value"); - assert_eq!(&info.match_info()["val"], "value"); - - // same patterns - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test2/name").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test2/name-test").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test2/name/ttt").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(&info.match_info()["val"], "ttt"); - } - - #[test] - fn test_parse_static() { - let re = ResourceDef::new("/"); - assert!(re.is_match("/")); - assert!(!re.is_match("/a")); - - let re = ResourceDef::new("/name"); - assert!(re.is_match("/name")); - assert!(!re.is_match("/name1")); - assert!(!re.is_match("/name/")); - assert!(!re.is_match("/name~")); - - let re = ResourceDef::new("/name/"); - assert!(re.is_match("/name/")); - assert!(!re.is_match("/name")); - assert!(!re.is_match("/name/gs")); - - let re = ResourceDef::new("/user/profile"); - assert!(re.is_match("/user/profile")); - assert!(!re.is_match("/user/profile/profile")); - } - - #[test] - fn test_parse_param() { - let re = ResourceDef::new("/user/{id}"); - 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 req = TestRequest::with_uri("/user/profile").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "profile"); - - let req = TestRequest::with_uri("/user/1245125").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "1245125"); - - let re = ResourceDef::new("/v{version}/resource/{id}"); - assert!(re.is_match("/v1/resource/320120")); - assert!(!re.is_match("/v/resource/1")); - assert!(!re.is_match("/resource")); - - let req = TestRequest::with_uri("/v151/resource/adahg32").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("version").unwrap(), "151"); - assert_eq!(info.get("id").unwrap(), "adahg32"); - - let re = ResourceDef::new("/{id:[[:digit:]]{6}}"); - assert!(re.is_match("/012345")); - assert!(!re.is_match("/012")); - assert!(!re.is_match("/01234567")); - assert!(!re.is_match("/XXXXXX")); - - let req = TestRequest::with_uri("/012345").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "012345"); - } - - #[test] - fn test_resource_prefix() { - let re = ResourceDef::prefix("/name"); - assert!(re.is_match("/name")); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/test/test")); - assert!(re.is_match("/name1")); - assert!(re.is_match("/name~")); - - let re = ResourceDef::prefix("/name/"); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); - } - - #[test] - fn test_reousrce_prefix_dynamic() { - let re = ResourceDef::prefix("/{name}/"); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); - - let req = TestRequest::with_uri("/test2/").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(&info["name"], "test2"); - assert_eq!(&info[0], "test2"); - - let req = TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(&info["name"], "test2"); - assert_eq!(&info[0], "test2"); - } - - #[test] - fn test_request_resource() { - let mut router = Router::<()>::default(); - let mut resource = Resource::new(ResourceDef::new("/index.json")); - resource.name("r1"); - router.register_resource(resource); - let mut resource = Resource::new(ResourceDef::new("/test.json")); - resource.name("r2"); - router.register_resource(resource); - - let req = TestRequest::with_uri("/index.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - - assert_eq!(info.name(), "r1"); - - let req = TestRequest::with_uri("/test.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.name(), "r2"); - } - - #[test] - fn test_has_resource() { - let mut router = Router::<()>::default(); - let scope = Scope::new("/test").resource("/name", |_| "done"); - router.register_scope(scope); - - { - let info = router.default_route_info(); - assert!(!info.has_resource("/test")); - assert!(info.has_resource("/test/name")); - } - - let scope = - Scope::new("/test2").nested("/test10", |s| s.resource("/name", |_| "done")); - router.register_scope(scope); - - let info = router.default_route_info(); - assert!(info.has_resource("/test2/test10/name")); - } - - #[test] - fn test_url_for() { - let mut router = Router::<()>::new(ResourceDef::prefix("")); - - let mut resource = Resource::new(ResourceDef::new("/tttt")); - resource.name("r0"); - router.register_resource(resource); - - let scope = Scope::new("/test").resource("/name", |r| { - r.name("r1"); - }); - router.register_scope(scope); - - let scope = Scope::new("/test2") - .nested("/test10", |s| s.resource("/name", |r| r.name("r2"))); - router.register_scope(scope); - router.finish(); - - let req = TestRequest::with_uri("/test").request(); - { - let info = router.default_route_info(); - - let res = info - .url_for(&req, "r0", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/tttt"); - - let res = info - .url_for(&req, "r1", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test/name"); - - let res = info - .url_for(&req, "r2", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); - } - - let req = TestRequest::with_uri("/test/name").request(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - - let res = info - .url_for(&req, "r0", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/tttt"); - - let res = info - .url_for(&req, "r1", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test/name"); - - let res = info - .url_for(&req, "r2", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); - } - - #[test] - fn test_url_for_dynamic() { - let mut router = Router::<()>::new(ResourceDef::prefix("")); - - let mut resource = Resource::new(ResourceDef::new("/{name}/test/index.{ext}")); - resource.name("r0"); - router.register_resource(resource); - - let scope = Scope::new("/{name1}").nested("/{name2}", |s| { - s.resource("/{name3}/test/index.{ext}", |r| r.name("r2")) - }); - router.register_scope(scope); - router.finish(); - - let req = TestRequest::with_uri("/test").request(); - { - let info = router.default_route_info(); - - let res = info.url_for(&req, "r0", vec!["sec1", "html"]).unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/sec1/test/index.html"); - - let res = info - .url_for(&req, "r2", vec!["sec1", "sec2", "sec3", "html"]) - .unwrap(); - assert_eq!( - res.as_str(), - "http://localhost:8080/sec1/sec2/sec3/test/index.html" - ); - } - } -} diff --git a/src/scope.rs b/src/scope.rs deleted file mode 100644 index 43789d42..00000000 --- a/src/scope.rs +++ /dev/null @@ -1,1236 +0,0 @@ -use std::marker::PhantomData; -use std::mem; -use std::rc::Rc; - -use futures::{Async, Future, Poll}; - -use error::Error; -use handler::{ - AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, RouteHandler, - WrapHandler, -}; -use http::Method; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{ - Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted, -}; -use pred::Predicate; -use resource::{DefaultResource, Resource}; -use router::{ResourceDef, Router}; -use server::Request; -use with::WithFactory; - -/// Resources scope -/// -/// Scope is a set of resources with common root path. -/// Scopes collect multiple paths under a common path prefix. -/// Scope path can contain variable path segments as resources. -/// Scope prefix is always complete path segment, i.e `/app` would -/// be converted to a `/app/` and it would not match `/app` path. -/// -/// You can get variable path segments from `HttpRequest::match_info()`. -/// `Path` extractor also is able to extract scope level variable segments. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{http, App, HttpRequest, HttpResponse}; -/// -/// fn main() { -/// let app = App::new().scope("/{project_id}/", |scope| { -/// scope -/// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) -/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) -/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) -/// }); -/// } -/// ``` -/// -/// In the above example three routes get registered: -/// * /{project_id}/path1 - reponds to all http method -/// * /{project_id}/path2 - `GET` requests -/// * /{project_id}/path3 - `HEAD` requests -/// -pub struct Scope { - rdef: ResourceDef, - router: Rc>, - filters: Vec>>, - middlewares: Rc>>>, -} - -#[cfg_attr( - feature = "cargo-clippy", - allow(clippy::new_without_default_derive) -)] -impl Scope { - /// Create a new scope - pub fn new(path: &str) -> Scope { - let rdef = ResourceDef::prefix(path); - Scope { - rdef: rdef.clone(), - router: Rc::new(Router::new(rdef)), - filters: Vec::new(), - middlewares: Rc::new(Vec::new()), - } - } - - #[inline] - pub(crate) fn rdef(&self) -> &ResourceDef { - &self.rdef - } - - pub(crate) fn router(&self) -> &Router { - self.router.as_ref() - } - - #[inline] - pub(crate) fn take_filters(&mut self) -> Vec>> { - mem::replace(&mut self.filters, Vec::new()) - } - - /// Add match predicate to scope. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, pred, App, HttpRequest, HttpResponse, Path}; - /// - /// fn index(data: Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope - /// .filter(pred::Header("content-type", "text/plain")) - /// .route("/test1", http::Method::GET, index) - /// .route("/test2", http::Method::POST, |_: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// }) - /// }); - /// } - /// ``` - pub fn filter + 'static>(mut self, p: T) -> Self { - self.filters.push(Box::new(p)); - self - } - - /// Create nested scope with new state. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest}; - /// - /// struct AppState; - /// - /// fn index(req: &HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.with_state("/state2", AppState, |scope| { - /// scope.resource("/test1", |r| r.f(index)) - /// }) - /// }); - /// } - /// ``` - pub fn with_state(mut self, path: &str, state: T, f: F) -> Scope - where - F: FnOnce(Scope) -> Scope, - { - let rdef = ResourceDef::prefix(path); - let scope = Scope { - rdef: rdef.clone(), - filters: Vec::new(), - router: Rc::new(Router::new(rdef)), - middlewares: Rc::new(Vec::new()), - }; - let mut scope = f(scope); - - let state = Rc::new(state); - let filters: Vec>> = vec![Box::new(FiltersWrapper { - state: Rc::clone(&state), - filters: scope.take_filters(), - })]; - let handler = Box::new(Wrapper { scope, state }); - - Rc::get_mut(&mut self.router).unwrap().register_handler( - path, - handler, - Some(filters), - ); - - self - } - - /// Create nested scope. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest}; - /// - /// struct AppState; - /// - /// fn index(req: &HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::with_state(AppState).scope("/app", |scope| { - /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index))) - /// }); - /// } - /// ``` - pub fn nested(mut self, path: &str, f: F) -> Scope - where - F: FnOnce(Scope) -> Scope, - { - let rdef = ResourceDef::prefix(&insert_slash(path)); - let scope = Scope { - rdef: rdef.clone(), - filters: Vec::new(), - router: Rc::new(Router::new(rdef)), - middlewares: Rc::new(Vec::new()), - }; - Rc::get_mut(&mut self.router) - .unwrap() - .register_scope(f(scope)); - - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `Scope::resource()` method. - /// Handler functions need to accept one request extractor - /// argument. - /// - /// This method could be called multiple times, in that case - /// multiple routes would be registered for same resource path. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; - /// - /// fn index(data: Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.route("/test1", http::Method::GET, index).route( - /// "/test2", - /// http::Method::POST, - /// |_: HttpRequest| HttpResponse::MethodNotAllowed(), - /// ) - /// }); - /// } - /// ``` - pub fn route(mut self, path: &str, method: Method, f: F) -> Scope - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - Rc::get_mut(&mut self.router).unwrap().register_route( - &insert_slash(path), - method, - f, - ); - self - } - - /// Configure resource for a specific path. - /// - /// This method is similar to an `App::resource()` method. - /// Resources may have variable path segments. Resource path uses scope - /// path as a path prefix. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new().scope("/api", |scope| { - /// scope.resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|_| HttpResponse::Ok()) - /// }) - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> Scope - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource - let mut resource = Resource::new(ResourceDef::new(&insert_slash(path))); - f(&mut resource); - - Rc::get_mut(&mut self.router) - .unwrap() - .register_resource(resource); - self - } - - /// Default resource to be used if no matching route could be found. - pub fn default_resource(mut self, f: F) -> Scope - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // create and configure default resource - let mut resource = Resource::new(ResourceDef::new("")); - f(&mut resource); - - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_default_resource(resource.into()); - - self - } - - /// Configure handler for specific path prefix. - /// - /// A path prefix consists of valid path segments, i.e for the - /// prefix `/app` any request with the paths `/app`, `/app/` or - /// `/app/test` would match, but the path `/application` would - /// not. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/scope-prefix", |scope| { - /// scope.handler("/app", |req: &HttpRequest| match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }) - /// }); - /// } - /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> Scope { - let path = insert_slash(path.trim().trim_right_matches('/')); - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); - self - } - - /// Register a scope middleware - /// - /// This is similar to `App's` middlewares, but - /// middlewares get invoked on scope level. - /// - /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to the peer. - pub fn middleware>(mut self, mw: M) -> Scope { - Rc::get_mut(&mut self.middlewares) - .expect("Can not use after configuration") - .push(Box::new(mw)); - self - } -} - -fn insert_slash(path: &str) -> String { - let mut path = path.to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - path -} - -impl RouteHandler for Scope { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let tail = req.match_info().tail as usize; - - // recognize resources - let info = self.router.recognize(req, req.state(), tail); - let req2 = req.with_route_info(info); - if self.middlewares.is_empty() { - self.router.handle(&req2) - } else { - AsyncResult::async(Box::new(Compose::new( - req2, - Rc::clone(&self.router), - Rc::clone(&self.middlewares), - ))) - } - } - - fn has_default_resource(&self) -> bool { - self.router.has_default_resource() - } - - fn default_resource(&mut self, default: DefaultResource) { - Rc::get_mut(&mut self.router) - .expect("Can not use after configuration") - .register_default_resource(default); - } - - fn finish(&mut self) { - Rc::get_mut(&mut self.router) - .expect("Can not use after configuration") - .finish(); - } -} - -struct Wrapper { - state: Rc, - scope: Scope, -} - -impl RouteHandler for Wrapper { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let req = req.with_state(Rc::clone(&self.state)); - self.scope.handle(&req) - } -} - -struct FiltersWrapper { - state: Rc, - filters: Vec>>, -} - -impl Predicate for FiltersWrapper { - fn check(&self, req: &Request, _: &S2) -> bool { - for filter in &self.filters { - if !filter.check(&req, &self.state) { - return false; - } - } - true - } -} - -/// Compose resource level middlewares with route handler. -struct Compose { - info: ComposeInfo, - state: ComposeState, -} - -struct ComposeInfo { - count: usize, - req: HttpRequest, - router: Rc>, - mws: Rc>>>, -} - -enum ComposeState { - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Finishing(FinishingMiddlewares), - Completed(Response), -} - -impl ComposeState { - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match *self { - ComposeState::Starting(ref mut state) => state.poll(info), - ComposeState::Handler(ref mut state) => state.poll(info), - ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Finishing(ref mut state) => state.poll(info), - ComposeState::Completed(_) => None, - } - } -} - -impl Compose { - fn new( - req: HttpRequest, router: Rc>, mws: Rc>>>, - ) -> Self { - let mut info = ComposeInfo { - mws, - req, - router, - count: 0, - }; - let state = StartMiddlewares::init(&mut info); - - Compose { state, info } - } -} - -impl Future for Compose { - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - if let ComposeState::Completed(ref mut resp) = self.state { - let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)); - } - if let Some(state) = self.state.poll(&mut self.info) { - self.state = state; - } else { - return Ok(Async::NotReady); - } - } - } -} - -/// Middlewares start executor -struct StartMiddlewares { - fut: Option, - _s: PhantomData, -} - -type Fut = Box, Error = Error>>; - -impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); - - loop { - if info.count == len { - let reply = info.router.handle(&info.req); - return WaitingResponse::init(info, reply); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp); - } - Ok(MiddlewareStarted::Future(fut)) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }); - } - Err(err) => { - return RunMiddlewares::init(info, err.into()); - } - } - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, resp)); - } - loop { - if info.count == len { - let reply = info.router.handle(&info.req); - return Some(WaitingResponse::init(info, reply)); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return Some(RunMiddlewares::init(info, resp)); - } - Ok(MiddlewareStarted::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } -} - -// waiting for response -struct WaitingResponse { - fut: Box>, - _s: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut ComposeInfo, reply: AsyncResult, - ) -> ComposeState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), - AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { - fut, - _s: PhantomData, - }), - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), - Err(err) => Some(RunMiddlewares::init(info, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, -} - -impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { - let mut curr = 0; - let len = info.mws.len(); - - loop { - let state = info.mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = curr + 1; - return FinishingMiddlewares::init(info, err.into()); - } - Ok(MiddlewareResponse::Done(r)) => { - curr += 1; - if curr == len { - return FinishingMiddlewares::init(info, r); - } else { - r - } - } - Ok(MiddlewareResponse::Future(fut)) => { - return ComposeState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - }); - } - }; - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), - }; - - loop { - if self.curr == len { - return Some(FinishingMiddlewares::init(info, resp)); - } else { - let state = info.mws[self.curr].response(&info.req, resp); - match state { - Err(err) => { - return Some(FinishingMiddlewares::init(info, err.into())) - } - Ok(MiddlewareResponse::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(MiddlewareResponse::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, -} - -impl FinishingMiddlewares { - fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { - if info.count == 0 { - Response::init(resp) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - }; - if let Some(st) = state.poll(info) { - st - } else { - ComposeState::Finishing(state) - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - - info.count -= 1; - let state = info.mws[info.count as usize] - .finish(&info.req, self.resp.as_ref().unwrap()); - match state { - MiddlewareFinished::Done => { - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - } - MiddlewareFinished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -struct Response { - resp: Option, - _s: PhantomData, -} - -impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Completed(Response { - resp: Some(resp), - _s: PhantomData, - }) - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - - use application::App; - use body::Body; - use http::{Method, StatusCode}; - use httprequest::HttpRequest; - use httpresponse::HttpResponse; - use pred; - use test::TestRequest; - - #[test] - fn test_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_root2() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_root3() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_route() { - let app = App::new() - .scope("app", |scope| { - scope - .route("/path1", Method::GET, |_: HttpRequest<_>| { - HttpResponse::Ok() - }).route("/path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_route_without_leading_slash() { - let app = App::new() - .scope("app", |scope| { - scope - .route("path1", Method::GET, |_: HttpRequest<_>| HttpResponse::Ok()) - .route("path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_variable_segment() { - let app = App::new() - .scope("/ab-{project}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/ab-project1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/aa-project1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_with_state() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_with_state_root() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_with_state_root2() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1/", State, |scope| { - scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_with_state_root3() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1/", State, |scope| { - scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_with_state_filter() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_nested_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_no_slash() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("t1", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_nested_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project_id}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Created().body(format!( - "project: {}", - &r.match_info()["project_id"] - )) - }) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/project_1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); - } - _ => panic!(), - } - } - - #[test] - fn test_nested2_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project}", |scope| { - scope.nested("/{id}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - }) - }) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/test/1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/app/test/1/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_default_resource() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - .default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }).finish(); - - let req = TestRequest::with_uri("/app/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_default_resource_propagation() { - let app = App::new() - .scope("/app1", |scope| { - scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }).scope("/app2", |scope| scope) - .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) - .finish(); - - let req = TestRequest::with_uri("/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/app1/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/app2/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_handler() { - let app = App::new() - .scope("/scope", |scope| { - scope.handler("/test", |_: &_| HttpResponse::Ok()) - }).finish(); - - let req = TestRequest::with_uri("/scope/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/scope/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } -} diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs deleted file mode 100644 index 2e1b1f28..00000000 --- a/src/server/acceptor.rs +++ /dev/null @@ -1,396 +0,0 @@ -use std::time::Duration; -use std::{fmt, net}; - -use actix_net::server::ServerMessage; -use actix_net::service::{NewService, Service}; -use futures::future::{err, ok, Either, FutureResult}; -use futures::{Async, Future, Poll}; -use tokio_reactor::Handle; -use tokio_tcp::TcpStream; -use tokio_timer::{sleep, Delay}; - -use super::channel::HttpProtocol; -use super::error::AcceptorError; -use super::handler::HttpHandler; -use super::settings::ServiceConfig; -use super::IoStream; - -/// This trait indicates types that can create acceptor service for http server. -pub trait AcceptorServiceFactory: Send + Clone + 'static { - type Io: IoStream + Send; - type NewService: NewService; - - fn create(&self) -> Self::NewService; -} - -impl AcceptorServiceFactory for F -where - F: Fn() -> T + Send + Clone + 'static, - T::Response: IoStream + Send, - T: NewService, - T::InitError: fmt::Debug, -{ - type Io = T::Response; - type NewService = T; - - fn create(&self) -> T { - (self)() - } -} - -#[derive(Clone)] -/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream` -pub(crate) struct DefaultAcceptor; - -impl AcceptorServiceFactory for DefaultAcceptor { - type Io = TcpStream; - type NewService = DefaultAcceptor; - - fn create(&self) -> Self::NewService { - DefaultAcceptor - } -} - -impl NewService for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type InitError = (); - type Service = DefaultAcceptor; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(DefaultAcceptor) - } -} - -impl Service for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - ok(req) - } -} - -pub(crate) struct TcpAcceptor { - inner: T, -} - -impl TcpAcceptor -where - T: NewService>, - T::InitError: fmt::Debug, -{ - pub(crate) fn new(inner: T) -> Self { - TcpAcceptor { inner } - } -} - -impl NewService for TcpAcceptor -where - T: NewService>, - T::InitError: fmt::Debug, -{ - type Request = net::TcpStream; - type Response = T::Response; - type Error = AcceptorError; - type InitError = T::InitError; - type Service = TcpAcceptorService; - type Future = TcpAcceptorResponse; - - fn new_service(&self) -> Self::Future { - TcpAcceptorResponse { - fut: self.inner.new_service(), - } - } -} - -pub(crate) struct TcpAcceptorResponse -where - T: NewService, - T::InitError: fmt::Debug, -{ - fut: T::Future, -} - -impl Future for TcpAcceptorResponse -where - T: NewService, - T::InitError: fmt::Debug, -{ - type Item = TcpAcceptorService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(service)) => { - Ok(Async::Ready(TcpAcceptorService { inner: service })) - } - Err(e) => { - error!("Can not create accetor service: {:?}", e); - Err(e) - } - } - } -} - -pub(crate) struct TcpAcceptorService { - inner: T, -} - -impl Service for TcpAcceptorService -where - T: Service>, -{ - type Request = net::TcpStream; - type Response = T::Response; - type Error = AcceptorError; - type Future = Either>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready() - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - let stream = TcpStream::from_std(req, &Handle::default()).map_err(|e| { - error!("Can not convert to an async tcp stream: {}", e); - AcceptorError::Io(e) - }); - - match stream { - Ok(stream) => Either::A(self.inner.call(stream)), - Err(e) => Either::B(err(e)), - } - } -} - -#[doc(hidden)] -/// Acceptor timeout middleware -/// -/// Applies timeout to request prcoessing. -pub struct AcceptorTimeout { - inner: T, - timeout: Duration, -} - -impl AcceptorTimeout { - /// Create new `AcceptorTimeout` instance. timeout is in milliseconds. - pub fn new(timeout: u64, inner: T) -> Self { - Self { - inner, - timeout: Duration::from_millis(timeout), - } - } -} - -impl NewService for AcceptorTimeout { - type Request = T::Request; - type Response = T::Response; - type Error = AcceptorError; - type InitError = T::InitError; - type Service = AcceptorTimeoutService; - type Future = AcceptorTimeoutFut; - - fn new_service(&self) -> Self::Future { - AcceptorTimeoutFut { - fut: self.inner.new_service(), - timeout: self.timeout, - } - } -} - -#[doc(hidden)] -pub struct AcceptorTimeoutFut { - fut: T::Future, - timeout: Duration, -} - -impl Future for AcceptorTimeoutFut { - type Item = AcceptorTimeoutService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - let inner = try_ready!(self.fut.poll()); - Ok(Async::Ready(AcceptorTimeoutService { - inner, - timeout: self.timeout, - })) - } -} - -#[doc(hidden)] -/// Acceptor timeout service -/// -/// Applies timeout to request prcoessing. -pub struct AcceptorTimeoutService { - inner: T, - timeout: Duration, -} - -impl Service for AcceptorTimeoutService { - type Request = T::Request; - type Response = T::Response; - type Error = AcceptorError; - type Future = AcceptorTimeoutResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready().map_err(AcceptorError::Service) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - AcceptorTimeoutResponse { - fut: self.inner.call(req), - sleep: sleep(self.timeout), - } - } -} - -#[doc(hidden)] -pub struct AcceptorTimeoutResponse { - fut: T::Future, - sleep: Delay, -} - -impl Future for AcceptorTimeoutResponse { - type Item = T::Response; - type Error = AcceptorError; - - fn poll(&mut self) -> Poll { - match self.fut.poll().map_err(AcceptorError::Service)? { - Async::NotReady => match self.sleep.poll() { - Err(_) => Err(AcceptorError::Timeout), - Ok(Async::Ready(_)) => Err(AcceptorError::Timeout), - Ok(Async::NotReady) => Ok(Async::NotReady), - }, - Async::Ready(resp) => Ok(Async::Ready(resp)), - } - } -} - -pub(crate) struct ServerMessageAcceptor { - inner: T, - settings: ServiceConfig, -} - -impl ServerMessageAcceptor -where - H: HttpHandler, - T: NewService, -{ - pub(crate) fn new(settings: ServiceConfig, inner: T) -> Self { - ServerMessageAcceptor { inner, settings } - } -} - -impl NewService for ServerMessageAcceptor -where - H: HttpHandler, - T: NewService, -{ - type Request = ServerMessage; - type Response = (); - type Error = T::Error; - type InitError = T::InitError; - type Service = ServerMessageAcceptorService; - type Future = ServerMessageAcceptorResponse; - - fn new_service(&self) -> Self::Future { - ServerMessageAcceptorResponse { - fut: self.inner.new_service(), - settings: self.settings.clone(), - } - } -} - -pub(crate) struct ServerMessageAcceptorResponse -where - H: HttpHandler, - T: NewService, -{ - fut: T::Future, - settings: ServiceConfig, -} - -impl Future for ServerMessageAcceptorResponse -where - H: HttpHandler, - T: NewService, -{ - type Item = ServerMessageAcceptorService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService { - inner: service, - settings: self.settings.clone(), - })), - } - } -} - -pub(crate) struct ServerMessageAcceptorService { - inner: T, - settings: ServiceConfig, -} - -impl Service for ServerMessageAcceptorService -where - H: HttpHandler, - T: Service, -{ - type Request = ServerMessage; - type Response = (); - type Error = T::Error; - type Future = - Either, FutureResult<(), Self::Error>>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready() - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - match req { - ServerMessage::Connect(stream) => { - Either::A(ServerMessageAcceptorServiceFut { - fut: self.inner.call(stream), - }) - } - ServerMessage::Shutdown(_) => Either::B(ok(())), - ServerMessage::ForceShutdown => { - self.settings - .head() - .traverse(|proto: &mut HttpProtocol| proto.shutdown()); - Either::B(ok(())) - } - } - } -} - -pub(crate) struct ServerMessageAcceptorServiceFut { - fut: T::Future, -} - -impl Future for ServerMessageAcceptorServiceFut -where - T: Service, -{ - type Item = (); - type Error = T::Error; - - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(_) => Ok(Async::Ready(())), - } - } -} diff --git a/src/server/builder.rs b/src/server/builder.rs deleted file mode 100644 index ec6ce992..00000000 --- a/src/server/builder.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::{fmt, net}; - -use actix_net::either::Either; -use actix_net::server::{Server, ServiceFactory}; -use actix_net::service::{NewService, NewServiceExt}; - -use super::acceptor::{ - AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor, -}; -use super::error::AcceptorError; -use super::handler::IntoHttpHandler; -use super::service::HttpService; -use super::settings::{ServerSettings, ServiceConfig}; -use super::KeepAlive; - -pub(crate) trait ServiceProvider { - fn register( - &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, - client_shutdown: u64, - ) -> Server; -} - -/// Utility type that builds complete http pipeline -pub(crate) struct HttpServiceBuilder -where - F: Fn() -> H + Send + Clone, -{ - factory: F, - acceptor: A, -} - -impl HttpServiceBuilder -where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, -{ - /// Create http service builder - pub fn new(factory: F, acceptor: A) -> Self { - Self { factory, acceptor } - } - - fn finish( - &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, - client_timeout: u64, client_shutdown: u64, - ) -> impl ServiceFactory { - let factory = self.factory.clone(); - let acceptor = self.acceptor.clone(); - move || { - let app = (factory)().into_handler(); - let settings = ServiceConfig::new( - app, - keep_alive, - client_timeout, - client_shutdown, - ServerSettings::new(addr, &host, false), - ); - - if secure { - Either::B(ServerMessageAcceptor::new( - settings.clone(), - TcpAcceptor::new(AcceptorTimeout::new( - client_timeout, - acceptor.create(), - )).map_err(|_| ()) - .map_init_err(|_| ()) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } else { - Either::A(ServerMessageAcceptor::new( - settings.clone(), - TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) - .map_err(|_| ()) - .map_init_err(|_| ()) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } - } - } -} - -impl ServiceProvider for HttpServiceBuilder -where - F: Fn() -> H + Send + Clone + 'static, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - H: IntoHttpHandler, -{ - fn register( - &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, - client_shutdown: u64, - ) -> Server { - server.listen2( - "actix-web", - lst, - self.finish( - host, - addr, - keep_alive, - secure, - client_timeout, - client_shutdown, - ), - ) - } -} diff --git a/src/server/channel.rs b/src/server/channel.rs deleted file mode 100644 index b1fef964..00000000 --- a/src/server/channel.rs +++ /dev/null @@ -1,436 +0,0 @@ -use std::net::Shutdown; -use std::{io, mem, time}; - -use bytes::{Buf, BufMut, BytesMut}; -use futures::{Async, Future, Poll}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -use super::error::HttpDispatchError; -use super::settings::ServiceConfig; -use super::{h1, h2, HttpHandler, IoStream}; -use error::Error; -use http::StatusCode; - -const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; - -pub(crate) enum HttpProtocol { - H1(h1::Http1Dispatcher), - H2(h2::Http2), - Unknown(ServiceConfig, T, BytesMut), - None, -} - -impl HttpProtocol { - pub(crate) fn shutdown(&mut self) { - match self { - HttpProtocol::H1(ref mut h1) => { - let io = h1.io(); - let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - } - HttpProtocol::H2(ref mut h2) => h2.shutdown(), - HttpProtocol::Unknown(_, io, _) => { - let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - } - HttpProtocol::None => (), - } - } -} - -enum ProtocolKind { - Http1, - Http2, -} - -#[doc(hidden)] -pub struct HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - node: Node>, - node_reg: bool, - ka_timeout: Option, -} - -impl HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub(crate) fn new(settings: ServiceConfig, io: T) -> HttpChannel { - let ka_timeout = settings.client_timer(); - - HttpChannel { - ka_timeout, - node_reg: false, - node: Node::new(HttpProtocol::Unknown( - settings, - io, - BytesMut::with_capacity(8192), - )), - } - } -} - -impl Drop for HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - fn drop(&mut self) { - self.node.remove(); - } -} - -impl Future for HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - type Item = (); - type Error = HttpDispatchError; - - fn poll(&mut self) -> Poll { - // keep-alive timer - if self.ka_timeout.is_some() { - match self.ka_timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(_)) => { - trace!("Slow request timed out, close connection"); - let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); - if let HttpProtocol::Unknown(settings, io, buf) = proto { - *self.node.get_mut() = - HttpProtocol::H1(h1::Http1Dispatcher::for_error( - settings, - io, - StatusCode::REQUEST_TIMEOUT, - self.ka_timeout.take(), - buf, - )); - return self.poll(); - } - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => (), - Err(_) => panic!("Something is really wrong"), - } - } - - if !self.node_reg { - self.node_reg = true; - let settings = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.settings().clone(), - HttpProtocol::H2(ref mut h2) => h2.settings().clone(), - HttpProtocol::Unknown(ref mut settings, _, _) => settings.clone(), - HttpProtocol::None => unreachable!(), - }; - settings.head().insert(&mut self.node); - } - - let mut is_eof = false; - let kind = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => return h1.poll(), - HttpProtocol::H2(ref mut h2) => return h2.poll(), - HttpProtocol::Unknown(_, ref mut io, ref mut buf) => { - let mut err = None; - let mut disconnect = false; - match io.read_available(buf) { - Ok(Async::Ready((read_some, stream_closed))) => { - is_eof = stream_closed; - // Only disconnect if no data was read. - if is_eof && !read_some { - disconnect = true; - } - } - Err(e) => { - err = Some(e.into()); - } - _ => (), - } - if disconnect { - debug!("Ignored premature client disconnection"); - return Ok(Async::Ready(())); - } else if let Some(e) = err { - return Err(e); - } - - if buf.len() >= 14 { - if buf[..14] == HTTP2_PREFACE[..] { - ProtocolKind::Http2 - } else { - ProtocolKind::Http1 - } - } else { - return Ok(Async::NotReady); - } - } - HttpProtocol::None => unreachable!(), - }; - - // upgrade to specific http protocol - let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); - if let HttpProtocol::Unknown(settings, io, buf) = proto { - match kind { - ProtocolKind::Http1 => { - *self.node.get_mut() = HttpProtocol::H1(h1::Http1Dispatcher::new( - settings, - io, - buf, - is_eof, - self.ka_timeout.take(), - )); - return self.poll(); - } - ProtocolKind::Http2 => { - *self.node.get_mut() = HttpProtocol::H2(h2::Http2::new( - settings, - io, - buf.freeze(), - self.ka_timeout.take(), - )); - return self.poll(); - } - } - } - unreachable!() - } -} - -#[doc(hidden)] -pub struct H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - node: Node>, - node_reg: bool, -} - -impl H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub(crate) fn new(settings: ServiceConfig, io: T) -> H1Channel { - H1Channel { - node_reg: false, - node: Node::new(HttpProtocol::H1(h1::Http1Dispatcher::new( - settings, - io, - BytesMut::with_capacity(8192), - false, - None, - ))), - } - } -} - -impl Drop for H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - fn drop(&mut self) { - self.node.remove(); - } -} - -impl Future for H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - type Item = (); - type Error = HttpDispatchError; - - fn poll(&mut self) -> Poll { - if !self.node_reg { - self.node_reg = true; - let settings = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.settings().clone(), - _ => unreachable!(), - }; - settings.head().insert(&mut self.node); - } - - match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.poll(), - _ => unreachable!(), - } - } -} - -pub(crate) struct Node { - next: Option<*mut Node>, - prev: Option<*mut Node>, - element: T, -} - -impl Node { - fn new(element: T) -> Self { - Node { - element, - next: None, - prev: None, - } - } - - fn get_mut(&mut self) -> &mut T { - &mut self.element - } - - fn insert(&mut self, next_el: &mut Node) { - let next: *mut Node = next_el as *const _ as *mut _; - - if let Some(next2) = self.next { - unsafe { - let n = next2.as_mut().unwrap(); - n.prev = Some(next); - } - next_el.next = Some(next2 as *mut _); - } - self.next = Some(next); - - unsafe { - let next: &mut Node = &mut *next; - next.prev = Some(self as *mut _); - } - } - - fn remove(&mut self) { - let next = self.next.take(); - let prev = self.prev.take(); - - if let Some(prev) = prev { - unsafe { - prev.as_mut().unwrap().next = next; - } - } - if let Some(next) = next { - unsafe { - next.as_mut().unwrap().prev = prev; - } - } - } -} - -impl Node<()> { - pub(crate) fn head() -> Self { - Node { - next: None, - prev: None, - element: (), - } - } - - pub(crate) fn traverse)>(&self, f: F) - where - T: IoStream, - H: HttpHandler + 'static, - { - if let Some(n) = self.next.as_ref() { - unsafe { - let mut next: &mut Node> = - &mut *(n.as_ref().unwrap() as *const _ as *mut _); - loop { - f(&mut next.element); - - next = if let Some(n) = next.next.as_ref() { - &mut **n - } else { - return; - } - } - } - } - } -} - -/// Wrapper for `AsyncRead + AsyncWrite` types -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 } - } -} - -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(()) - } - #[inline] - fn set_keepalive(&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, -{ - #[inline] - fn read_buf(&mut self, buf: &mut B) -> Poll { - self.io.read_buf(buf) - } -} - -impl AsyncWrite for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.shutdown() - } - #[inline] - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.io.write_buf(buf) - } -} diff --git a/src/server/error.rs b/src/server/error.rs index 3ae9a107..d9e1239e 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -4,7 +4,6 @@ use std::io; use futures::{Async, Poll}; use http2; -use super::{helpers, HttpHandlerTask, Writer}; use error::{Error, ParseError}; use http::{StatusCode, Version}; @@ -89,31 +88,3 @@ impl From for HttpDispatchError { HttpDispatchError::Http2(err) } } - -pub(crate) struct ServerError(Version, StatusCode); - -impl ServerError { - pub fn err(ver: Version, status: StatusCode) -> Box { - Box::new(ServerError(ver, status)) - } -} - -impl HttpHandlerTask for ServerError { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - { - let bytes = io.buffer(); - // Buffer should have sufficient capacity for status line - // and extra space - bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1); - helpers::write_status_line(self.0, self.1.as_u16(), bytes); - } - // Convert Status Code to Reason. - let reason = self.1.canonical_reason().unwrap_or(""); - io.buffer().extend_from_slice(reason.as_bytes()); - // No response body. - io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n"); - // date header - io.set_date(); - Ok(Async::Ready(true)) - } -} diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs deleted file mode 100644 index c27a4c44..00000000 --- a/src/server/h1writer.rs +++ /dev/null @@ -1,356 +0,0 @@ -// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] - -use std::io::{self, Write}; - -use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; -use tokio_io::AsyncWrite; - -use super::helpers; -use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::ServiceConfig; -use super::Request; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use body::{Binary, Body}; -use header::ContentEncoding; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{Method, Version}; -use httpresponse::HttpResponse; - -const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific - -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: T, - written: u64, - headers_size: u32, - buffer: Output, - buffer_capacity: usize, - settings: ServiceConfig, -} - -impl H1Writer { - pub fn new(stream: T, settings: ServiceConfig) -> H1Writer { - H1Writer { - flags: Flags::KEEPALIVE, - written: 0, - headers_size: 0, - buffer: Output::Buffer(settings.get_bytes()), - buffer_capacity: 0, - stream, - settings, - } - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.stream - } - - pub fn reset(&mut self) { - self.written = 0; - self.flags = Flags::KEEPALIVE; - } - - pub fn flushed(&mut self) -> bool { - self.buffer.is_empty() - } - - pub fn disconnected(&mut self) { - self.flags.insert(Flags::DISCONNECTED); - } - - pub fn upgrade(&self) -> bool { - self.flags.contains(Flags::UPGRADE) - } - - pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) - } - - fn write_data(stream: &mut T, data: &[u8]) -> io::Result { - let mut written = 0; - while written < data.len() { - match stream.write(&data[written..]) { - Ok(0) => { - return Err(io::Error::new(io::ErrorKind::WriteZero, "")); - } - Ok(n) => { - written += n; - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - return Ok(written) - } - Err(err) => return Err(err), - } - } - Ok(written) - } -} - -impl Drop for H1Writer { - fn drop(&mut self) { - if let Some(bytes) = self.buffer.take_option() { - self.settings.release_bytes(bytes); - } - } -} - -impl Writer for H1Writer { - #[inline] - fn written(&self) -> u64 { - self.written - } - - #[inline] - fn set_date(&mut self) { - self.settings.set_date(self.buffer.as_mut(), true) - } - - #[inline] - fn buffer(&mut self) -> &mut BytesMut { - self.buffer.as_mut() - } - - fn start( - &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result { - // prepare task - let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); - self.buffer.for_server(&mut info, &req.inner, msg, encoding); - if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { - self.flags = Flags::STARTED | Flags::KEEPALIVE; - } else { - self.flags = Flags::STARTED; - } - - // Connection upgrade - let version = msg.version().unwrap_or_else(|| req.inner.version); - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - // keep-alive - else if self.flags.contains(Flags::KEEPALIVE) { - if version < Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("keep-alive")); - } - } else if version >= Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("close")); - } - let body = msg.replace_body(Body::Empty); - - // render message - { - // output buffer - let mut buffer = self.buffer.as_mut(); - - let reason = msg.reason().as_bytes(); - if let Body::Binary(ref bytes) = body { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE - + bytes.len() - + reason.len(), - ); - } else { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), - ); - } - - // status line - helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); - buffer.extend_from_slice(reason); - - // content length - match info.length { - ResponseLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - ResponseLength::Zero => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") - } - ResponseLength::Length(len) => { - helpers::write_content_length(len, &mut buffer) - } - ResponseLength::Length64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - ResponseLength::None => buffer.extend_from_slice(b"\r\n"), - } - if let Some(ce) = info.content_encoding { - buffer.extend_from_slice(b"content-encoding: "); - buffer.extend_from_slice(ce.as_ref()); - buffer.extend_from_slice(b"\r\n"); - } - - // write headers - let mut pos = 0; - let mut has_date = false; - let mut remaining = buffer.remaining_mut(); - let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in msg.headers() { - match *key { - TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - _ => continue, - }, - DATE => { - has_date = true; - } - _ => (), - } - - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { - buffer.advance_mut(pos); - } - pos = 0; - buffer.reserve(len); - remaining = buffer.remaining_mut(); - unsafe { - buf = &mut *(buffer.bytes_mut() as *mut _); - } - } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; - } - unsafe { - buffer.advance_mut(pos); - } - - // optimized date header, set_date writes \r\n - if !has_date { - self.settings.set_date(&mut buffer, true); - } else { - // msg eof - buffer.extend_from_slice(b"\r\n"); - } - self.headers_size = buffer.len() as u32; - } - - if let Body::Binary(bytes) = body { - self.written = bytes.len() as u64; - self.buffer.write(bytes.as_ref())?; - } else { - // capacity, makes sense only for streaming or actor - self.buffer_capacity = msg.write_buffer_capacity(); - - msg.replace_body(body); - } - Ok(WriterState::Done) - } - - fn write(&mut self, payload: &Binary) -> io::Result { - self.written += payload.len() as u64; - if !self.flags.contains(Flags::DISCONNECTED) { - if self.flags.contains(Flags::STARTED) { - // shortcut for upgraded connection - if self.flags.contains(Flags::UPGRADE) { - if self.buffer.is_empty() { - let pl: &[u8] = payload.as_ref(); - let n = match Self::write_data(&mut self.stream, pl) { - Err(err) => { - self.disconnected(); - return Err(err); - } - Ok(val) => val, - }; - if n < pl.len() { - self.buffer.write(&pl[n..])?; - return Ok(WriterState::Done); - } - } else { - self.buffer.write(payload.as_ref())?; - } - } else { - // TODO: add warning, write after EOF - self.buffer.write(payload.as_ref())?; - } - } else { - // could be response to EXCEPT header - self.buffer.write(payload.as_ref())?; - } - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn write_eof(&mut self) -> io::Result { - if !self.buffer.write_eof()? { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - #[inline] - fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { - if self.flags.contains(Flags::DISCONNECTED) { - return Err(io::Error::new(io::ErrorKind::Other, "disconnected")); - } - - if !self.buffer.is_empty() { - let written = { - match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) { - Err(err) => { - self.disconnected(); - return Err(err); - } - Ok(val) => val, - } - }; - let _ = self.buffer.split_to(written); - if shutdown && !self.buffer.is_empty() - || (self.buffer.len() > self.buffer_capacity) - { - return Ok(Async::NotReady); - } - } - if shutdown { - self.stream.poll_flush()?; - self.stream.shutdown() - } else { - Ok(self.stream.poll_flush()?) - } - } -} diff --git a/src/server/h2.rs b/src/server/h2.rs deleted file mode 100644 index 27ae4785..00000000 --- a/src/server/h2.rs +++ /dev/null @@ -1,453 +0,0 @@ -use std::collections::VecDeque; -use std::io::{Read, Write}; -use std::net::SocketAddr; -use std::rc::Rc; -use std::time::Instant; -use std::{cmp, io, mem}; - -use bytes::{Buf, Bytes}; -use futures::{Async, Future, Poll, Stream}; -use http2::server::{self, Connection, Handshake, SendResponse}; -use http2::{Reason, RecvStream}; -use modhttp::request::Parts; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -use error::{Error, PayloadError}; -use extensions::Extensions; -use http::{StatusCode, Version}; -use payload::{Payload, PayloadStatus, PayloadWriter}; -use uri::Url; - -use super::error::{HttpDispatchError, ServerError}; -use super::h2writer::H2Writer; -use super::input::PayloadType; -use super::settings::ServiceConfig; -use super::{HttpHandler, HttpHandlerTask, IoStream, Writer}; - -bitflags! { - struct Flags: u8 { - const DISCONNECTED = 0b0000_0010; - } -} - -/// HTTP/2 Transport -pub(crate) struct Http2 -where - T: AsyncRead + AsyncWrite + 'static, - H: HttpHandler + 'static, -{ - flags: Flags, - settings: ServiceConfig, - addr: Option, - state: State>, - tasks: VecDeque>, - keepalive_timer: Option, - extensions: Option>, -} - -enum State { - Handshake(Handshake), - Connection(Connection), - Empty, -} - -impl Http2 -where - T: IoStream + 'static, - H: HttpHandler + 'static, -{ - pub fn new( - settings: ServiceConfig, io: T, buf: Bytes, keepalive_timer: Option, - ) -> Self { - let addr = io.peer_addr(); - let extensions = io.extensions(); - Http2 { - flags: Flags::empty(), - tasks: VecDeque::new(), - state: State::Handshake(server::handshake(IoWrapper { - unread: if buf.is_empty() { None } else { Some(buf) }, - inner: io, - })), - addr, - settings, - extensions, - keepalive_timer, - } - } - - pub(crate) fn shutdown(&mut self) { - self.state = State::Empty; - self.tasks.clear(); - self.keepalive_timer.take(); - } - - pub fn settings(&self) -> &ServiceConfig { - &self.settings - } - - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { - // server - if let State::Connection(ref mut conn) = self.state { - // keep-alive timer - if let Some(ref mut timeout) = self.keepalive_timer { - match timeout.poll() { - Ok(Async::Ready(_)) => { - trace!("Keep-alive timeout, close connection"); - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => (), - Err(_) => unreachable!(), - } - } - - loop { - let mut not_ready = true; - let disconnected = self.flags.contains(Flags::DISCONNECTED); - - // check in-flight connections - for item in &mut self.tasks { - // read payload - if !disconnected { - item.poll_payload(); - } - - if !item.flags.contains(EntryFlags::EOF) { - if disconnected { - item.flags.insert(EntryFlags::EOF); - } else { - let retry = item.payload.need_read() == PayloadStatus::Read; - loop { - match item.task.poll_io(&mut item.stream) { - Ok(Async::Ready(ready)) => { - if ready { - item.flags.insert( - EntryFlags::EOF | EntryFlags::FINISHED, - ); - } else { - item.flags.insert(EntryFlags::EOF); - } - not_ready = false; - } - Ok(Async::NotReady) => { - if item.payload.need_read() - == PayloadStatus::Read - && !retry - { - continue; - } - } - Err(err) => { - error!("Unhandled error: {}", err); - item.flags.insert( - EntryFlags::EOF - | EntryFlags::ERROR - | EntryFlags::WRITE_DONE, - ); - item.stream.reset(Reason::INTERNAL_ERROR); - } - } - break; - } - } - } - - if item.flags.contains(EntryFlags::EOF) - && !item.flags.contains(EntryFlags::FINISHED) - { - match item.task.poll_completed() { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - item.flags.insert( - EntryFlags::FINISHED | EntryFlags::WRITE_DONE, - ); - } - Err(err) => { - item.flags.insert( - EntryFlags::ERROR - | EntryFlags::WRITE_DONE - | EntryFlags::FINISHED, - ); - error!("Unhandled error: {}", err); - } - } - } - - if item.flags.contains(EntryFlags::FINISHED) - && !item.flags.contains(EntryFlags::WRITE_DONE) - && !disconnected - { - match item.stream.poll_completed(false) { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - not_ready = false; - item.flags.insert(EntryFlags::WRITE_DONE); - } - Err(_) => { - item.flags.insert(EntryFlags::ERROR); - } - } - } - } - - // cleanup finished tasks - while !self.tasks.is_empty() { - if self.tasks[0].flags.contains(EntryFlags::FINISHED) - && self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) - || self.tasks[0].flags.contains(EntryFlags::ERROR) - { - self.tasks.pop_front(); - } else { - break; - } - } - - // get request - if !self.flags.contains(Flags::DISCONNECTED) { - match conn.poll() { - Ok(Async::Ready(None)) => { - not_ready = false; - self.flags.insert(Flags::DISCONNECTED); - for entry in &mut self.tasks { - entry.task.disconnected() - } - } - Ok(Async::Ready(Some((req, resp)))) => { - not_ready = false; - let (parts, body) = req.into_parts(); - - // stop keepalive timer - self.keepalive_timer.take(); - - self.tasks.push_back(Entry::new( - parts, - body, - resp, - self.addr, - self.settings.clone(), - self.extensions.clone(), - )); - } - Ok(Async::NotReady) => { - // start keep-alive timer - if self.tasks.is_empty() { - if self.settings.keep_alive_enabled() { - if self.keepalive_timer.is_none() { - if let Some(ka) = self.settings.keep_alive() { - trace!("Start keep-alive timer"); - let mut timeout = - Delay::new(Instant::now() + ka); - // register timeout - let _ = timeout.poll(); - self.keepalive_timer = Some(timeout); - } - } - } else { - // keep-alive disable, drop connection - return conn.poll_close().map_err(|e| e.into()); - } - } else { - // keep-alive unset, rely on operating system - return Ok(Async::NotReady); - } - } - Err(err) => { - trace!("Connection error: {}", err); - self.flags.insert(Flags::DISCONNECTED); - for entry in &mut self.tasks { - entry.task.disconnected() - } - self.keepalive_timer.take(); - } - } - } - - if not_ready { - if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) - { - return conn.poll_close().map_err(|e| e.into()); - } else { - return Ok(Async::NotReady); - } - } - } - } - - // handshake - self.state = if let State::Handshake(ref mut handshake) = self.state { - match handshake.poll() { - Ok(Async::Ready(conn)) => State::Connection(conn), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { - trace!("Error handling connection: {}", err); - return Err(err.into()); - } - } - } else { - mem::replace(&mut self.state, State::Empty) - }; - - self.poll() - } -} - -bitflags! { - struct EntryFlags: u8 { - const EOF = 0b0000_0001; - const REOF = 0b0000_0010; - const ERROR = 0b0000_0100; - const FINISHED = 0b0000_1000; - const WRITE_DONE = 0b0001_0000; - } -} - -enum EntryPipe { - Task(H::Task), - Error(Box), -} - -impl EntryPipe { - fn disconnected(&mut self) { - match *self { - EntryPipe::Task(ref mut task) => task.disconnected(), - EntryPipe::Error(ref mut task) => task.disconnected(), - } - } - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match *self { - EntryPipe::Task(ref mut task) => task.poll_io(io), - EntryPipe::Error(ref mut task) => task.poll_io(io), - } - } - fn poll_completed(&mut self) -> Poll<(), Error> { - match *self { - EntryPipe::Task(ref mut task) => task.poll_completed(), - EntryPipe::Error(ref mut task) => task.poll_completed(), - } - } -} - -struct Entry { - task: EntryPipe, - payload: PayloadType, - recv: RecvStream, - stream: H2Writer, - flags: EntryFlags, -} - -impl Entry { - fn new( - parts: Parts, recv: RecvStream, resp: SendResponse, - addr: Option, settings: ServiceConfig, - extensions: Option>, - ) -> Entry - where - H: HttpHandler + 'static, - { - // Payload and Content-Encoding - let (psender, payload) = Payload::new(false); - - let mut msg = settings.get_request(); - { - let inner = msg.inner_mut(); - inner.url = Url::new(parts.uri); - inner.method = parts.method; - inner.version = parts.version; - inner.headers = parts.headers; - inner.stream_extensions = extensions; - *inner.payload.borrow_mut() = Some(payload); - inner.addr = addr; - } - - // Payload sender - let psender = PayloadType::new(msg.headers(), psender); - - // start request processing - let task = match settings.handler().handle(msg) { - Ok(task) => EntryPipe::Task(task), - Err(_) => EntryPipe::Error(ServerError::err( - Version::HTTP_2, - StatusCode::NOT_FOUND, - )), - }; - - Entry { - task, - recv, - payload: psender, - stream: H2Writer::new(resp, settings), - flags: EntryFlags::empty(), - } - } - - fn poll_payload(&mut self) { - while !self.flags.contains(EntryFlags::REOF) - && self.payload.need_read() == PayloadStatus::Read - { - match self.recv.poll() { - Ok(Async::Ready(Some(chunk))) => { - let l = chunk.len(); - self.payload.feed_data(chunk); - if let Err(err) = self.recv.release_capacity().release_capacity(l) { - self.payload.set_error(PayloadError::Http2(err)); - break; - } - } - Ok(Async::Ready(None)) => { - self.flags.insert(EntryFlags::REOF); - self.payload.feed_eof(); - } - Ok(Async::NotReady) => break, - Err(err) => { - self.payload.set_error(PayloadError::Http2(err)); - break; - } - } - } - } -} - -struct IoWrapper { - unread: Option, - inner: T, -} - -impl Read for IoWrapper { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - if let Some(mut bytes) = self.unread.take() { - let size = cmp::min(buf.len(), bytes.len()); - buf[..size].copy_from_slice(&bytes[..size]); - if bytes.len() > size { - bytes.split_to(size); - self.unread = Some(bytes); - } - Ok(size) - } else { - self.inner.read(buf) - } - } -} - -impl Write for IoWrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) - } - fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } -} - -impl AsyncRead for IoWrapper { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.inner.prepare_uninitialized_buffer(buf) - } -} - -impl AsyncWrite for IoWrapper { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.inner.shutdown() - } - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.inner.write_buf(buf) - } -} diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs deleted file mode 100644 index 51d4dce6..00000000 --- a/src/server/h2writer.rs +++ /dev/null @@ -1,259 +0,0 @@ -#![cfg_attr( - feature = "cargo-clippy", - allow(clippy::redundant_field_names) -)] - -use std::{cmp, io}; - -use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; -use http2::server::SendResponse; -use http2::{Reason, SendStream}; -use modhttp::Response; - -use super::helpers; -use super::message::Request; -use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::ServiceConfig; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use body::{Binary, Body}; -use header::ContentEncoding; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Method, Version}; -use httpresponse::HttpResponse; - -const CHUNK_SIZE: usize = 16_384; - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const DISCONNECTED = 0b0000_0010; - const EOF = 0b0000_0100; - const RESERVED = 0b0000_1000; - } -} - -pub(crate) struct H2Writer { - respond: SendResponse, - stream: Option>, - flags: Flags, - written: u64, - buffer: Output, - buffer_capacity: usize, - settings: ServiceConfig, -} - -impl H2Writer { - pub fn new(respond: SendResponse, settings: ServiceConfig) -> H2Writer { - H2Writer { - stream: None, - flags: Flags::empty(), - written: 0, - buffer: Output::Buffer(settings.get_bytes()), - buffer_capacity: 0, - respond, - settings, - } - } - - pub fn reset(&mut self, reason: Reason) { - if let Some(mut stream) = self.stream.take() { - stream.send_reset(reason) - } - } -} - -impl Drop for H2Writer { - fn drop(&mut self) { - self.settings.release_bytes(self.buffer.take()); - } -} - -impl Writer for H2Writer { - fn written(&self) -> u64 { - self.written - } - - #[inline] - fn set_date(&mut self) { - self.settings.set_date(self.buffer.as_mut(), true) - } - - #[inline] - fn buffer(&mut self) -> &mut BytesMut { - self.buffer.as_mut() - } - - fn start( - &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result { - // prepare response - self.flags.insert(Flags::STARTED); - let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); - self.buffer.for_server(&mut info, &req.inner, msg, encoding); - - let mut has_date = false; - let mut resp = Response::new(()); - *resp.status_mut() = msg.status(); - *resp.version_mut() = Version::HTTP_2; - for (key, value) in msg.headers().iter() { - match *key { - // http2 specific - CONNECTION | TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - _ => continue, - }, - DATE => has_date = true, - _ => (), - } - resp.headers_mut().append(key, value.clone()); - } - - // set date header - if !has_date { - let mut bytes = BytesMut::with_capacity(29); - self.settings.set_date(&mut bytes, false); - resp.headers_mut() - .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); - } - - // content length - match info.length { - ResponseLength::Zero => { - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - self.flags.insert(Flags::EOF); - } - ResponseLength::Length(len) => { - let mut val = BytesMut::new(); - helpers::convert_usize(len, &mut val); - let l = val.len(); - resp.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), - ); - } - ResponseLength::Length64(len) => { - let l = format!("{}", len); - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap()); - } - _ => (), - } - if let Some(ce) = info.content_encoding { - resp.headers_mut() - .insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap()); - } - - trace!("Response: {:?}", resp); - - match self - .respond - .send_response(resp, self.flags.contains(Flags::EOF)) - { - Ok(stream) => self.stream = Some(stream), - Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), - } - - let body = msg.replace_body(Body::Empty); - if let Body::Binary(bytes) = body { - if bytes.is_empty() { - Ok(WriterState::Done) - } else { - self.flags.insert(Flags::EOF); - self.buffer.write(bytes.as_ref())?; - if let Some(ref mut stream) = self.stream { - self.flags.insert(Flags::RESERVED); - stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); - } - Ok(WriterState::Pause) - } - } else { - msg.replace_body(body); - self.buffer_capacity = msg.write_buffer_capacity(); - Ok(WriterState::Done) - } - } - - fn write(&mut self, payload: &Binary) -> io::Result { - if !self.flags.contains(Flags::DISCONNECTED) { - if self.flags.contains(Flags::STARTED) { - // TODO: add warning, write after EOF - self.buffer.write(payload.as_ref())?; - } else { - // might be response for EXCEPT - error!("Not supported"); - } - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn write_eof(&mut self) -> io::Result { - self.flags.insert(Flags::EOF); - if !self.buffer.write_eof()? { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn poll_completed(&mut self, _shutdown: bool) -> Poll<(), io::Error> { - if !self.flags.contains(Flags::STARTED) { - return Ok(Async::NotReady); - } - - if let Some(ref mut stream) = self.stream { - // reserve capacity - if !self.flags.contains(Flags::RESERVED) && !self.buffer.is_empty() { - self.flags.insert(Flags::RESERVED); - stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); - } - - loop { - match stream.poll_capacity() { - Ok(Async::NotReady) => return Ok(Async::NotReady), - Ok(Async::Ready(None)) => return Ok(Async::Ready(())), - Ok(Async::Ready(Some(cap))) => { - let len = self.buffer.len(); - let bytes = self.buffer.split_to(cmp::min(cap, len)); - let eof = - self.buffer.is_empty() && self.flags.contains(Flags::EOF); - self.written += bytes.len() as u64; - - if let Err(e) = stream.send_data(bytes.freeze(), eof) { - return Err(io::Error::new(io::ErrorKind::Other, e)); - } else if !self.buffer.is_empty() { - let cap = cmp::min(self.buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - if eof { - stream.reserve_capacity(0); - continue; - } - self.flags.remove(Flags::RESERVED); - return Ok(Async::Ready(())); - } - } - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), - } - } - } - Ok(Async::Ready(())) - } -} diff --git a/src/server/handler.rs b/src/server/handler.rs deleted file mode 100644 index 33e50ac3..00000000 --- a/src/server/handler.rs +++ /dev/null @@ -1,208 +0,0 @@ -use futures::{Async, Future, Poll}; - -use super::message::Request; -use super::Writer; -use error::Error; - -/// Low level http request handler -#[allow(unused_variables)] -pub trait HttpHandler: 'static { - /// Request handling task - type Task: HttpHandlerTask; - - /// Handle request - fn handle(&self, req: Request) -> Result; -} - -impl HttpHandler for Box>> { - type Task = Box; - - fn handle(&self, req: Request) -> Result, Request> { - self.as_ref().handle(req) - } -} - -/// Low level http request handler -pub trait HttpHandlerTask { - /// Poll task, this method is used before or after *io* object is available - fn poll_completed(&mut self) -> Poll<(), Error> { - Ok(Async::Ready(())) - } - - /// Poll task when *io* object is available - fn poll_io(&mut self, io: &mut Writer) -> Poll; - - /// Connection is disconnected - fn disconnected(&mut self) {} -} - -impl HttpHandlerTask for Box { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - self.as_mut().poll_io(io) - } -} - -pub(super) struct HttpHandlerTaskFut { - task: T, -} - -impl HttpHandlerTaskFut { - pub(crate) fn new(task: T) -> Self { - Self { task } - } -} - -impl Future for HttpHandlerTaskFut { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll<(), ()> { - self.task.poll_completed().map_err(|_| ()) - } -} - -/// 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 - } -} - -impl IntoHttpHandler for Vec { - type Handler = VecHttpHandler; - - fn into_handler(self) -> Self::Handler { - VecHttpHandler(self.into_iter().map(|item| item.into_handler()).collect()) - } -} - -#[doc(hidden)] -pub struct VecHttpHandler(Vec); - -impl HttpHandler for VecHttpHandler { - type Task = H::Task; - - fn handle(&self, mut req: Request) -> Result { - for h in &self.0 { - req = match h.handle(req) { - Ok(task) => return Ok(task), - Err(e) => e, - }; - } - Err(req) - } -} - -macro_rules! http_handler ({$EN:ident, $(($n:tt, $T:ident)),+} => { - impl<$($T: HttpHandler,)+> HttpHandler for ($($T,)+) { - type Task = $EN<$($T,)+>; - - fn handle(&self, mut req: Request) -> Result { - $( - req = match self.$n.handle(req) { - Ok(task) => return Ok($EN::$T(task)), - Err(e) => e, - }; - )+ - Err(req) - } - } - - #[doc(hidden)] - pub enum $EN<$($T: HttpHandler,)+> { - $($T ($T::Task),)+ - } - - impl<$($T: HttpHandler,)+> HttpHandlerTask for $EN<$($T,)+> - { - fn poll_completed(&mut self) -> Poll<(), Error> { - match self { - $($EN :: $T(ref mut task) => task.poll_completed(),)+ - } - } - - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match self { - $($EN::$T(ref mut task) => task.poll_io(io),)+ - } - } - - /// Connection is disconnected - fn disconnected(&mut self) { - match self { - $($EN::$T(ref mut task) => task.disconnected(),)+ - } - } - } -}); - -http_handler!(HttpHandlerTask1, (0, A)); -http_handler!(HttpHandlerTask2, (0, A), (1, B)); -http_handler!(HttpHandlerTask3, (0, A), (1, B), (2, C)); -http_handler!(HttpHandlerTask4, (0, A), (1, B), (2, C), (3, D)); -http_handler!(HttpHandlerTask5, (0, A), (1, B), (2, C), (3, D), (4, E)); -http_handler!( - HttpHandlerTask6, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F) -); -http_handler!( - HttpHandlerTask7, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G) -); -http_handler!( - HttpHandlerTask8, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H) -); -http_handler!( - HttpHandlerTask9, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I) -); -http_handler!( - HttpHandlerTask10, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I), - (9, J) -); diff --git a/src/server/http.rs b/src/server/http.rs deleted file mode 100644 index 6a7790c1..00000000 --- a/src/server/http.rs +++ /dev/null @@ -1,584 +0,0 @@ -use std::{fmt, io, mem, net}; - -use actix::{Addr, System}; -use actix_net::server::Server; -use actix_net::service::NewService; -use actix_net::ssl; - -use net2::TcpBuilder; -use num_cpus; - -#[cfg(feature = "tls")] -use native_tls::TlsAcceptor; - -#[cfg(any(feature = "alpn", feature = "ssl"))] -use openssl::ssl::SslAcceptorBuilder; - -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; - -use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; -use super::builder::{HttpServiceBuilder, ServiceProvider}; -use super::{IntoHttpHandler, KeepAlive}; - -struct Socket { - scheme: &'static str, - lst: net::TcpListener, - addr: net::SocketAddr, - handler: Box, -} - -/// An HTTP Server -/// -/// By default it serves HTTP2 when HTTPs is enabled, -/// in order to change it, use `ServerFlags` that can be provided -/// to acceptor service. -pub struct HttpServer -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone, -{ - pub(super) factory: F, - pub(super) host: Option, - pub(super) keep_alive: KeepAlive, - pub(super) client_timeout: u64, - pub(super) client_shutdown: u64, - backlog: i32, - threads: usize, - exit: bool, - shutdown_timeout: u16, - no_http2: bool, - no_signals: bool, - maxconn: usize, - maxconnrate: usize, - sockets: Vec, -} - -impl HttpServer -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone + 'static, -{ - /// Create new http server with application factory - pub fn new(factory: F) -> HttpServer { - HttpServer { - factory, - threads: num_cpus::get(), - host: None, - backlog: 2048, - keep_alive: KeepAlive::Timeout(5), - shutdown_timeout: 30, - exit: false, - no_http2: false, - no_signals: false, - maxconn: 25_600, - maxconnrate: 256, - client_timeout: 5000, - client_shutdown: 5000, - sockets: Vec::new(), - } - } - - /// Set number of workers to start. - /// - /// By default http server uses number of available logical cpu as threads - /// count. - pub fn workers(mut self, num: usize) -> Self { - self.threads = num; - 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. - /// - /// This method should be called before `bind()` method call. - pub fn backlog(mut self, num: i32) -> Self { - self.backlog = num; - self - } - - /// Sets the maximum per-worker number of concurrent connections. - /// - /// All socket listeners will stop accepting connections when this limit is reached - /// for each worker. - /// - /// By default max connections is set to a 25k. - pub fn maxconn(mut self, num: usize) -> Self { - self.maxconn = num; - self - } - - /// Sets the maximum per-worker concurrent connection establish process. - /// - /// All listeners will stop accepting connections when this limit is reached. It - /// can be used to limit the global SSL CPU usage. - /// - /// By default max connections is set to a 256. - pub fn maxconnrate(mut self, num: usize) -> Self { - self.maxconnrate = num; - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: T) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection shutdown timeout in milliseconds. - /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(mut self, val: u64) -> Self { - self.client_shutdown = val; - 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 - } - - /// Stop actix system. - /// - /// `SystemExit` message stops currently running system. - pub fn system_exit(mut self) -> Self { - self.exit = true; - 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. 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 - } - - /// Disable `HTTP/2` support - // #[doc(hidden)] - // #[deprecated( - // since = "0.7.4", - // note = "please use acceptor service with proper ServerFlags parama" - // )] - pub fn no_http2(mut self) -> Self { - self.no_http2 = true; - self - } - - /// Get addresses of bound sockets. - pub fn addrs(&self) -> Vec { - self.sockets.iter().map(|s| s.addr).collect() - } - - /// Get addresses of bound sockets and the scheme for it. - /// - /// This is useful when the server is bound from different sources - /// with some sockets listening on http and some listening on https - /// and the user should be presented with an enumeration of which - /// socket requires which protocol. - pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() - } - - /// Use listener for accepting incoming connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen(mut self, lst: net::TcpListener) -> Self { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "http", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - DefaultAcceptor, - )), - }); - - self - } - - #[doc(hidden)] - /// Use listener for accepting incoming connection requests - pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self - where - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new(self.factory.clone(), acceptor)), - }); - - self - } - - #[cfg(feature = "tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { - use actix_net::service::NewServiceExt; - - self.listen_with(lst, move || { - ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_ssl( - self, lst: net::TcpListener, builder: SslAcceptorBuilder, - ) -> io::Result { - use super::{openssl_acceptor_with_flags, ServerFlags}; - use actix_net::service::NewServiceExt; - - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - Ok(self.listen_with(lst, move || { - ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) - })) - } - - #[cfg(feature = "rust-tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self { - use super::{RustlsAcceptor, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - self.listen_with(lst, move || { - RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) - }) - } - - /// The socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind(mut self, addr: S) -> io::Result { - let sockets = self.bind2(addr)?; - - for lst in sockets { - self = self.listen(lst); - } - - Ok(self) - } - - /// Start listening for incoming connections with supplied acceptor. - #[doc(hidden)] - #[cfg_attr( - feature = "cargo-clippy", - allow(clippy::needless_pass_by_value) - )] - pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result - where - S: net::ToSocketAddrs, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - { - let sockets = self.bind2(addr)?; - - for lst in sockets { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - acceptor.clone(), - )), - }); - } - - Ok(self) - } - - fn bind2( - &self, addr: S, - ) -> io::Result> { - let mut err = None; - let mut succ = false; - let mut sockets = Vec::new(); - for addr in addr.to_socket_addrs()? { - match create_tcp_listener(addr, self.backlog) { - Ok(lst) => { - succ = true; - sockets.push(lst); - } - Err(e) => err = Some(e), - } - } - - 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) - } - } - - #[cfg(feature = "tls")] - /// The ssl socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind_tls( - self, addr: S, acceptor: TlsAcceptor, - ) -> io::Result { - use actix_net::service::NewServiceExt; - use actix_net::ssl::NativeTlsAcceptor; - - self.bind_with(addr, move || { - NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_ssl(self, addr: S, builder: SslAcceptorBuilder) -> io::Result - where - S: net::ToSocketAddrs, - { - use super::{openssl_acceptor_with_flags, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - self.bind_with(addr, move || { - ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(feature = "rust-tls")] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_rustls( - self, addr: S, builder: ServerConfig, - ) -> io::Result { - use super::{RustlsAcceptor, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - self.bind_with(addr, move || { - RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ()) - }) - } -} - -impl H + Send + Clone> HttpServer { - /// Start listening for incoming connections. - /// - /// This method starts number of http workers in separate threads. - /// 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_web; - /// use actix_web::{actix, server, App, HttpResponse}; - /// - /// fn main() { - /// let sys = actix::System::new("example"); // <- create Actix system - /// - /// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .start(); - /// # actix::System::current().stop(); - /// sys.run(); // <- Run actix system, this method starts all async processes - /// } - /// ``` - pub fn start(mut self) -> Addr { - ssl::max_concurrent_ssl_connect(self.maxconnrate); - - let mut srv = Server::new() - .workers(self.threads) - .maxconn(self.maxconn) - .shutdown_timeout(self.shutdown_timeout); - - srv = if self.exit { srv.system_exit() } else { srv }; - srv = if self.no_signals { - srv.disable_signals() - } else { - srv - }; - - let sockets = mem::replace(&mut self.sockets, Vec::new()); - - for socket in sockets { - let host = self - .host - .as_ref() - .map(|h| h.to_owned()) - .unwrap_or_else(|| format!("{}", socket.addr)); - let (secure, client_shutdown) = if socket.scheme == "https" { - (true, self.client_shutdown) - } else { - (false, 0) - }; - srv = socket.handler.register( - srv, - socket.lst, - host, - socket.addr, - self.keep_alive, - secure, - self.client_timeout, - client_shutdown, - ); - } - srv.start() - } - - /// Spawn new thread and start listening for incoming connections. - /// - /// This method spawns new thread and starts new actix system. Other than - /// that it is similar to `start()` method. This method blocks. - /// - /// This methods panics if no socket addresses get bound. - /// - /// ```rust,ignore - /// # extern crate futures; - /// # extern crate actix_web; - /// # use futures::Future; - /// use actix_web::*; - /// - /// fn main() { - /// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .run(); - /// } - /// ``` - pub fn run(self) { - let sys = System::new("http-server"); - self.start(); - sys.run(); - } - - /// Register current http server as actix-net's server service - pub fn register(self, mut srv: Server) -> Server { - for socket in self.sockets { - let host = self - .host - .as_ref() - .map(|h| h.to_owned()) - .unwrap_or_else(|| format!("{}", socket.addr)); - let (secure, client_shutdown) = if socket.scheme == "https" { - (true, self.client_shutdown) - } else { - (false, 0) - }; - srv = socket.handler.register( - srv, - socket.lst, - host, - socket.addr, - self.keep_alive, - secure, - self.client_timeout, - client_shutdown, - ); - } - srv - } -} - -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.reuse_address(true)?; - builder.bind(addr)?; - Ok(builder.listen(backlog)?) -} diff --git a/src/server/incoming.rs b/src/server/incoming.rs deleted file mode 100644 index b13bba2a..00000000 --- a/src/server/incoming.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Support for `Stream`, deprecated! -use std::{io, net}; - -use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message}; -use futures::{Future, Stream}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use super::channel::{HttpChannel, WrapperStream}; -use super::handler::{HttpHandler, IntoHttpHandler}; -use super::http::HttpServer; -use super::settings::{ServerSettings, ServiceConfig}; - -impl Message for WrapperStream { - type Result = (); -} - -impl HttpServer -where - H: IntoHttpHandler, - F: Fn() -> H + Send + Clone, -{ - #[doc(hidden)] - #[deprecated(since = "0.7.8")] - /// Start listening for incoming connections from a stream. - /// - /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(self, stream: S, secure: bool) - where - S: Stream + 'static, - T: AsyncRead + AsyncWrite + 'static, - { - // set server settings - let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let apps = (self.factory)().into_handler(); - let settings = ServiceConfig::new( - apps, - self.keep_alive, - self.client_timeout, - self.client_shutdown, - ServerSettings::new(addr, "127.0.0.1:8080", secure), - ); - - // start server - HttpIncoming::create(move |ctx| { - ctx.add_message_stream(stream.map_err(|_| ()).map(WrapperStream::new)); - HttpIncoming { settings } - }); - } -} - -struct HttpIncoming { - settings: ServiceConfig, -} - -impl Actor for HttpIncoming { - type Context = Context; -} - -impl Handler> for HttpIncoming -where - T: AsyncRead + AsyncWrite, - H: HttpHandler, -{ - type Result = (); - - fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg).map_err(|_| ())); - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index ce3f2fbf..7d64a6e2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -117,33 +117,20 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; -pub(crate) mod acceptor; -pub(crate) mod builder; -mod channel; mod error; -pub(crate) mod h1; +// pub(crate) mod h1; #[doc(hidden)] pub mod h1codec; #[doc(hidden)] pub mod h1decoder; -mod h1writer; -mod h2; -mod h2writer; -mod handler; pub(crate) mod helpers; -mod http; -pub(crate) mod incoming; pub(crate) mod input; pub(crate) mod message; pub(crate) mod output; -pub(crate) mod service; +// pub(crate) mod service; pub(crate) mod settings; -mod ssl; -pub use self::handler::*; -pub use self::http::HttpServer; pub use self::message::Request; -pub use self::ssl::*; pub use self::error::{AcceptorError, HttpDispatchError}; pub use self::settings::ServerSettings; @@ -151,14 +138,11 @@ pub use self::settings::ServerSettings; #[doc(hidden)] pub mod h1disp; -#[doc(hidden)] -pub use self::acceptor::AcceptorTimeout; - #[doc(hidden)] pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; -#[doc(hidden)] -pub use self::service::{H1Service, HttpService, StreamConfiguration}; +//#[doc(hidden)] +//pub use self::service::{H1Service, HttpService, StreamConfiguration}; #[doc(hidden)] pub use self::helpers::write_content_length; @@ -174,53 +158,11 @@ pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; const LW_BUFFER_SIZE: usize = 4096; const HW_BUFFER_SIZE: usize = 32_768; -/// Create new http server with application factory. -/// -/// This is shortcut for `server::HttpServer::new()` method. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{actix, server, App, HttpResponse}; -/// -/// fn main() { -/// let sys = actix::System::new("example"); // <- create Actix system -/// -/// server::new( -/// || App::new() -/// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090").unwrap() -/// .start(); -/// -/// # actix::System::current().stop(); -/// sys.run(); -/// } -/// ``` -pub fn new(factory: F) -> HttpServer -where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, -{ - HttpServer::new(factory) -} - -#[doc(hidden)] -bitflags! { - ///Flags that can be used to configure HTTP Server. - pub struct ServerFlags: u8 { - ///Use HTTP1 protocol - const HTTP1 = 0b0000_0001; - ///Use HTTP2 protocol - const HTTP2 = 0b0000_0010; - } -} - #[derive(Debug, PartialEq, Clone, Copy)] /// Server keep-alive setting pub enum KeepAlive { /// Keep alive in seconds Timeout(usize), - /// Use `SO_KEEPALIVE` socket option, value in seconds - Tcp(usize), /// Relay on OS to shutdown tcp connection Os, /// Disabled @@ -243,41 +185,9 @@ impl From> for KeepAlive { } } -#[doc(hidden)] -#[derive(Debug)] -pub enum WriterState { - Done, - Pause, -} - -#[doc(hidden)] -/// Stream writer -pub trait Writer { - /// number of bytes written to the stream - fn written(&self) -> u64; - - #[doc(hidden)] - fn set_date(&mut self); - - #[doc(hidden)] - fn buffer(&mut self) -> &mut BytesMut; - - fn start( - &mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result; - - fn write(&mut self, payload: &Binary) -> io::Result; - - fn write_eof(&mut self) -> io::Result; - - fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error>; -} - #[doc(hidden)] /// Low-level io stream operations pub trait IoStream: AsyncRead + AsyncWrite + 'static { - fn shutdown(&mut self, how: Shutdown) -> io::Result<()>; - /// Returns the socket address of the remote peer of this TCP connection. fn peer_addr(&self) -> Option { None @@ -289,54 +199,10 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { fn set_linger(&mut self, dur: Option) -> io::Result<()>; fn set_keepalive(&mut self, dur: Option) -> io::Result<()>; - - fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> { - let mut read_some = false; - loop { - if buf.remaining_mut() < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE); - } - - let read = unsafe { self.read(buf.bytes_mut()) }; - match read { - Ok(n) => { - if n == 0 { - return Ok(Async::Ready((read_some, true))); - } else { - read_some = true; - unsafe { - buf.advance_mut(n); - } - } - } - Err(e) => { - return if e.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Async::Ready((read_some, false))) - } else { - Ok(Async::NotReady) - } - } else { - Err(e) - }; - } - } - } - } - - /// Extra io stream extensions - fn extensions(&self) -> Option> { - None - } } #[cfg(all(unix, feature = "uds"))] impl IoStream for ::tokio_uds::UnixStream { - #[inline] - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - ::tokio_uds::UnixStream::shutdown(self, how) - } - #[inline] fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { Ok(()) @@ -354,11 +220,6 @@ impl IoStream for ::tokio_uds::UnixStream { } impl IoStream for TcpStream { - #[inline] - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - TcpStream::shutdown(self, how) - } - #[inline] fn peer_addr(&self) -> Option { TcpStream::peer_addr(self).ok() diff --git a/src/server/output.rs b/src/server/output.rs index 1da7e902..143ba402 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -254,7 +254,7 @@ impl Output { } return; } - Body::Streaming(_) | Body::Actor(_) => { + Body::Streaming(_) => { if resp.upgrade() { if version == Version::HTTP_2 { error!("Connection upgrade is forbidden for HTTP/2"); diff --git a/src/server/settings.rs b/src/server/settings.rs index 9df1e457..f2128359 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -15,7 +15,6 @@ use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use super::channel::Node; use super::message::{Request, RequestPool}; use super::KeepAlive; use body::Body; @@ -128,35 +127,33 @@ impl ServerSettings { const DATE_VALUE_LENGTH: usize = 29; /// Http service configuration -pub struct ServiceConfig(Rc>); +pub struct ServiceConfig(Rc); -struct Inner { - handler: H, +struct Inner { keep_alive: Option, client_timeout: u64, client_shutdown: u64, ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, - node: RefCell>, date: UnsafeCell<(bool, Date)>, } -impl Clone for ServiceConfig { +impl Clone for ServiceConfig { fn clone(&self) -> Self { ServiceConfig(self.0.clone()) } } -impl ServiceConfig { +impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( - handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, + keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, settings: ServerSettings, - ) -> ServiceConfig { + ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), - KeepAlive::Os | KeepAlive::Tcp(_) => (0, true), + KeepAlive::Os => (0, true), KeepAlive::Disabled => (0, false), }; let keep_alive = if ka_enabled && keep_alive > 0 { @@ -166,29 +163,19 @@ impl ServiceConfig { }; ServiceConfig(Rc::new(Inner { - handler, keep_alive, ka_enabled, client_timeout, client_shutdown, bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), - node: RefCell::new(Node::head()), date: UnsafeCell::new((false, Date::new())), })) } /// Create worker settings builder. - pub fn build(handler: H) -> ServiceConfigBuilder { - ServiceConfigBuilder::new(handler) - } - - pub(crate) fn head(&self) -> RefMut> { - self.0.node.borrow_mut() - } - - pub(crate) fn handler(&self) -> &H { - &self.0.handler + pub fn build() -> ServiceConfigBuilder { + ServiceConfigBuilder::new() } #[inline] @@ -226,7 +213,7 @@ impl ServiceConfig { } } -impl ServiceConfig { +impl ServiceConfig { #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { @@ -329,8 +316,7 @@ impl ServiceConfig { /// /// This type can be used to construct an instance of `ServiceConfig` through a /// builder-like pattern. -pub struct ServiceConfigBuilder { - handler: H, +pub struct ServiceConfigBuilder { keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, @@ -339,11 +325,10 @@ pub struct ServiceConfigBuilder { secure: bool, } -impl ServiceConfigBuilder { +impl ServiceConfigBuilder { /// Create instance of `ServiceConfigBuilder` - pub fn new(handler: H) -> ServiceConfigBuilder { + pub fn new() -> ServiceConfigBuilder { ServiceConfigBuilder { - handler, keep_alive: KeepAlive::Timeout(5), client_timeout: 5000, client_shutdown: 5000, @@ -426,12 +411,11 @@ impl ServiceConfigBuilder { } /// Finish service configuration and create `ServiceConfig` object. - pub fn finish(self) -> ServiceConfig { + pub fn finish(self) -> ServiceConfig { let settings = ServerSettings::new(self.addr, &self.host, self.secure); let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; ServiceConfig::new( - self.handler, self.keep_alive, self.client_timeout, client_shutdown, diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs deleted file mode 100644 index c09573fe..00000000 --- a/src/server/ssl/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[cfg(any(feature = "alpn", feature = "ssl"))] -mod openssl; -#[cfg(any(feature = "alpn", feature = "ssl"))] -pub use self::openssl::{openssl_acceptor_with_flags, OpensslAcceptor}; - -#[cfg(feature = "tls")] -mod nativetls; - -#[cfg(feature = "rust-tls")] -mod rustls; -#[cfg(feature = "rust-tls")] -pub use self::rustls::RustlsAcceptor; diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs deleted file mode 100644 index a9797ffb..00000000 --- a/src/server/ssl/nativetls.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl::TlsStream; - -use server::IoStream; - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().get_ref().peer_addr() - } - - #[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) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs deleted file mode 100644 index 9d370f8b..00000000 --- a/src/server/ssl/openssl.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl; -use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_openssl::SslStream; - -use server::{IoStream, ServerFlags}; - -/// Support `SSL` connections via openssl package -/// -/// `ssl` feature enables `OpensslAcceptor` type -pub struct OpensslAcceptor { - _t: ssl::OpensslAcceptor, -} - -impl OpensslAcceptor { - /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. - pub fn new(builder: SslAcceptorBuilder) -> io::Result> { - OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) - } - - /// Create `OpensslAcceptor` with custom server flags. - pub fn with_flags( - builder: SslAcceptorBuilder, flags: ServerFlags, - ) -> io::Result> { - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - - Ok(ssl::OpensslAcceptor::new(acceptor)) - } -} - -/// Configure `SslAcceptorBuilder` with custom server flags. -pub fn openssl_acceptor_with_flags( - mut builder: SslAcceptorBuilder, flags: ServerFlags, -) -> io::Result { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP1) { - protos.extend(b"\x08http/1.1"); - } - if flags.contains(ServerFlags::HTTP2) { - protos.extend(b"\x02h2"); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - } - - if !protos.is_empty() { - builder.set_alpn_protos(&protos)?; - } - - Ok(builder.build()) -} - -impl IoStream for SslStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().get_ref().peer_addr() - } - - #[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) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs deleted file mode 100644 index a53a53a9..00000000 --- a/src/server/ssl/rustls.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl; //::RustlsAcceptor; -use rustls::{ClientSession, ServerConfig, ServerSession}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_rustls::TlsStream; - -use server::{IoStream, ServerFlags}; - -/// Support `SSL` connections via rustls package -/// -/// `rust-tls` feature enables `RustlsAcceptor` type -pub struct RustlsAcceptor { - _t: ssl::RustlsAcceptor, -} - -impl RustlsAcceptor { - /// Create `RustlsAcceptor` with custom server flags. - pub fn with_flags( - mut config: ServerConfig, flags: ServerFlags, - ) -> ssl::RustlsAcceptor { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP2) { - protos.push("h2".to_string()); - } - if flags.contains(ServerFlags::HTTP1) { - protos.push("http/1.1".to_string()); - } - if !protos.is_empty() { - config.set_protocols(&protos); - } - - ssl::RustlsAcceptor::new(config) - } -} - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_keepalive(dur) - } -} - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().0.peer_addr() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_keepalive(dur) - } -} diff --git a/src/with.rs b/src/with.rs deleted file mode 100644 index c6d54dee..00000000 --- a/src/with.rs +++ /dev/null @@ -1,383 +0,0 @@ -use futures::{Async, Future, Poll}; -use std::marker::PhantomData; -use std::rc::Rc; - -use error::Error; -use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -trait FnWith: 'static { - fn call_with(self: &Self, T) -> R; -} - -impl R + 'static> FnWith for F { - fn call_with(self: &Self, arg: T) -> R { - (*self)(arg) - } -} - -#[doc(hidden)] -pub trait WithFactory: 'static -where - T: FromRequest, - R: Responder, -{ - fn create(self) -> With; - - fn create_with_config(self, T::Config) -> With; -} - -#[doc(hidden)] -pub trait WithAsyncFactory: 'static -where - T: FromRequest, - R: Future, - I: Responder, - E: Into, -{ - fn create(self) -> WithAsync; - - fn create_with_config(self, T::Config) -> WithAsync; -} - -#[doc(hidden)] -pub struct With -where - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - cfg: Rc, - _s: PhantomData, -} - -impl With -where - T: FromRequest, - S: 'static, -{ - pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { - With { - cfg: Rc::new(cfg), - hnd: Rc::new(f), - _s: PhantomData, - } - } -} - -impl Handler for With -where - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut { - req: req.clone(), - started: false, - hnd: Rc::clone(&self.hnd), - cfg: self.cfg.clone(), - fut1: None, - fut2: None, - }; - - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithHandlerFut -where - R: Responder, - T: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg: Rc, - req: HttpRequest, - fut1: Option>>, - fut2: Option>>, -} - -impl Future for WithHandlerFut -where - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut2 { - return fut.poll(); - } - - let item = if !self.started { - self.started = true; - let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); - match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - } - } else { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - } - }; - - let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) { - Ok(item) => item.into(), - Err(e) => return Err(e.into()), - }; - - match item.into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut2 = Some(fut); - self.poll() - } - } - } -} - -#[doc(hidden)] -pub struct WithAsync -where - R: Future, - I: Responder, - E: Into, - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - cfg: Rc, - _s: PhantomData, -} - -impl WithAsync -where - R: Future, - I: Responder, - E: Into, - T: FromRequest, - S: 'static, -{ - pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { - WithAsync { - cfg: Rc::new(cfg), - hnd: Rc::new(f), - _s: PhantomData, - } - } -} - -impl Handler for WithAsync -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let mut fut = WithAsyncHandlerFut { - req: req.clone(), - started: false, - hnd: Rc::clone(&self.hnd), - cfg: Rc::clone(&self.cfg), - fut1: None, - fut2: None, - fut3: None, - }; - - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithAsyncHandlerFut -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg: Rc, - req: HttpRequest, - fut1: Option>>, - fut2: Option, - fut3: Option>>, -} - -impl Future for WithAsyncHandlerFut -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut3 { - return fut.poll(); - } - - if self.fut2.is_some() { - return match self.fut2.as_mut().unwrap().poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(r)) => match r.respond_to(&self.req) { - Ok(r) => match r.into().into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut3 = Some(fut); - self.poll() - } - }, - Err(e) => Err(e.into()), - }, - Err(e) => Err(e.into()), - }; - } - - let item = if !self.started { - self.started = true; - let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); - match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - } - } else { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - } - }; - - self.fut2 = Some(self.hnd.as_ref().call_with(item)); - self.poll() - } -} - -macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => { - impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func - where Func: Fn($($T,)+) -> Res + 'static, - $($T: FromRequest + 'static,)+ - Res: Responder + 'static, - State: 'static, - { - fn create(self) -> With<($($T,)+), State, Res> { - With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) - } - - fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> { - With::new(move |($($n,)+)| (self)($($n,)+), cfg) - } - } -}); - -macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => { - impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func - where Func: Fn($($T,)+) -> Res + 'static, - $($T: FromRequest + 'static,)+ - Res: Future, - Item: Responder + 'static, - Err: Into, - State: 'static, - { - fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> { - WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) - } - - fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> { - WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg) - } - } -}); - -with_factory_tuple!((a, A)); -with_factory_tuple!((a, A), (b, B)); -with_factory_tuple!((a, A), (b, B), (c, C)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H) -); -with_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H), - (i, I) -); - -with_async_factory_tuple!((a, A)); -with_async_factory_tuple!((a, A), (b, B)); -with_async_factory_tuple!((a, A), (b, B), (c, C)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_async_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H) -); -with_async_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H), - (i, I) -); diff --git a/tests/test_client.rs b/tests/test_client.rs deleted file mode 100644 index 8c5d5819..00000000 --- a/tests/test_client.rs +++ /dev/null @@ -1,508 +0,0 @@ -#![allow(deprecated)] -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate flate2; -extern crate futures; -extern crate rand; -#[cfg(all(unix, feature = "uds"))] -extern crate tokio_uds; - -use std::io::{Read, Write}; -use std::{net, thread}; - -use bytes::Bytes; -use flate2::read::GzDecoder; -use futures::stream::once; -use futures::Future; -use rand::Rng; - -use actix_web::*; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[test] -fn test_simple() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().header("x-test", "111").finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("ClientRequest")); - assert!(repr.contains("x-test")); - - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - - let request = srv.post().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_connection_close() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().header("Connection", "close").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_with_query_parameter() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| match req.query().get("qp") { - Some(_) => HttpResponse::Ok().finish(), - None => HttpResponse::BadRequest().finish(), - }) - }); - - let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); - - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_no_decompress() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - - // POST - let request = srv.post().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - let bytes = srv.execute(response.body()).unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_gzip_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_gzip_encoding_large() { - let data = STR.repeat(10); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(100_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(unix, feature = "uds"))] -#[test] -fn test_compatible_with_unix_socket_stream() { - let (stream, _) = tokio_uds::UnixStream::pair().unwrap(); - let _ = client::Connection::from_stream(stream); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_brotli_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .client(http::Method::POST, "/") - .content_encoding(http::ContentEncoding::Br) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_brotli_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(move |bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .client(http::Method::POST, "/") - .content_encoding(http::ContentEncoding::Br) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_deflate_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_deflate_encoding_large_random() { - let data = rand::thread_rng() - .gen_ascii_chars() - .take(70_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Deflate) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_client_streaming_explicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .map_err(Error::from) - .and_then(|body| { - Ok(HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Identity) - .body(body)) - }).responder() - }) - }); - - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - - let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_streaming_implicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_cookie_handling() { - use actix_web::http::Cookie; - fn err() -> Error { - use std::io::{Error as IoError, ErrorKind}; - // stub some generic error - Error::from(IoError::from(ErrorKind::NotFound)) - } - let cookie1 = Cookie::build("cookie1", "value1").finish(); - let cookie2 = Cookie::build("cookie2", "value2") - .domain("www.example.org") - .path("/") - .secure(true) - .http_only(true) - .finish(); - // Q: are all these clones really necessary? A: Yes, possibly - let cookie1b = cookie1.clone(); - let cookie2b = cookie2.clone(); - let mut srv = test::TestServer::new(move |app| { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); - app.handler(move |req: &HttpRequest| { - // Check cookies were sent correctly - req.cookie("cookie1") - .ok_or_else(err) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(err()) - } - }).and_then(|()| req.cookie("cookie2").ok_or_else(err)) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(err()) - } - }) - // Send some cookies back - .map(|_| { - HttpResponse::Ok() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - }) - }) - }); - - let request = srv - .get() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, cookie1); - let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, cookie2); -} - -#[test] -fn test_default_headers() { - let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(repr.contains(concat!( - "\"user-agent\": \"actix-web/", - env!("CARGO_PKG_VERSION"), - "\"" - ))); - - let request_override = srv - .get() - .header("User-Agent", "test") - .header("Accept-Encoding", "over_test") - .finish() - .unwrap(); - let repr_override = format!("{:?}", request_override); - assert!(repr_override.contains("\"user-agent\": \"test\"")); - assert!(repr_override.contains("\"accept-encoding\": \"over_test\"")); - assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(!repr_override.contains(concat!( - "\"user-agent\": \"Actix-web/", - env!("CARGO_PKG_VERSION"), - "\"" - ))); -} - -#[test] -fn client_read_until_eof() { - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - let lst = net::TcpListener::bind(addr).unwrap(); - - for stream in lst.incoming() { - let mut stream = stream.unwrap(); - let mut b = [0; 1000]; - let _ = stream.read(&mut b).unwrap(); - let _ = stream - .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); - } - }); - - let mut sys = actix::System::new("test"); - - // client request - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = sys.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"welcome!")); -} diff --git a/tests/test_custom_pipeline.rs b/tests/test_custom_pipeline.rs deleted file mode 100644 index 6b5df00e..00000000 --- a/tests/test_custom_pipeline.rs +++ /dev/null @@ -1,81 +0,0 @@ -extern crate actix; -extern crate actix_net; -extern crate actix_web; - -use std::{thread, time}; - -use actix::System; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; -use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration}; -use actix_web::{client, http, test, App, HttpRequest}; - -#[test] -fn test_custom_pipeline() { - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - StreamConfiguration::new() - .nodelay(true) - .tcp_keepalive(Some(time::Duration::from_secs(10))) - .and_then(HttpService::new(settings)) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} - -#[test] -fn test_h1() { - use actix_web::server::H1Service; - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - H1Service::new(settings) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index 7e8e9a42..77b6d202 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -1,4 +1,5 @@ extern crate actix; +extern crate actix_http; extern crate actix_net; extern crate actix_web; extern crate futures; @@ -8,12 +9,12 @@ use std::thread; use actix::System; use actix_net::server::Server; use actix_net::service::{IntoNewService, IntoService}; +use actix_web::{client, test}; use futures::future; -use actix_web::server::h1disp::Http1Dispatcher; -use actix_web::server::KeepAlive; -use actix_web::server::ServiceConfig; -use actix_web::{client, test, App, Error, HttpRequest, HttpResponse}; +use actix_http::server::h1disp::Http1Dispatcher; +use actix_http::server::{KeepAlive, ServiceConfig}; +use actix_http::{Error, HttpResponse}; #[test] fn test_h1_v2() { @@ -21,10 +22,7 @@ fn test_h1_v2() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) + let settings = ServiceConfig::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_shutdown(1000) diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs deleted file mode 100644 index 3ea709c9..00000000 --- a/tests/test_handlers.rs +++ /dev/null @@ -1,677 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -extern crate h2; -extern crate http; -extern crate tokio_timer; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; - -use std::io; -use std::time::{Duration, Instant}; - -use actix_web::*; -use bytes::Bytes; -use futures::Future; -use http::StatusCode; -use serde_json::Value; -use tokio_timer::Delay; - -#[derive(Deserialize)] -struct PParam { - username: String, -} - -#[test] -fn test_path_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.with(|p: Path| format!("Welcome {}!", p.username)) - }); - }); - - // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); -} - -#[test] -fn test_async_handler() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|p: Path| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| Ok(format!("Welcome {}!", p.username))) - .responder() - }) - }); - }); - - // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); -} - -#[test] -fn test_query_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/index.html", |r| { - r.with(|p: Query| format!("Welcome {}!", p.username)) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/index.html?username=test")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); - - // client request - let request = srv.get().uri(srv.url("/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[derive(Deserialize, Debug)] -pub enum ResponseType { - Token, - Code, -} - -#[derive(Debug, Deserialize)] -pub struct AuthRequest { - id: u64, - response_type: ResponseType, -} - -#[test] -fn test_query_enum_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/index.html", |r| { - r.with(|p: Query| format!("{:?}", p.into_inner())) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/index.html?id=64&response_type=Code")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }") - ); - - let request = srv - .get() - .uri(srv.url("/index.html?id=64&response_type=Co")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv - .get() - .uri(srv.url("/index.html?response_type=Code")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_async_extractor_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|data: Json| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| Ok(format!("{}", data.0))) - .responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}")); -} - -#[derive(Deserialize, Serialize)] -struct FormData { - username: String, -} - -#[test] -fn test_form_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|form: Form| format!("{}", form.username)) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .form(FormData { - username: "test".to_string(), - }).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"test")); -} - -#[test] -fn test_form_extractor2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_config( - |form: Form| format!("{}", form.username), - |cfg| { - cfg.0.error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ).into() - }); - }, - ); - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/x-www-form-urlencoded") - .body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_client_error()); -} - -#[test] -fn test_path_and_query_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(p, q): (Path, Query)| { - format!("Welcome {} - {}!", p.username, q.username) - }) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|(_r, p, q): (HttpRequest, Path, Query)| { - format!("Welcome {} - {}!", p.username, q.username) - }) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with( - |(p, _q, data): (Path, Query, Json)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }, - ) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); -} - -#[test] -fn test_path_and_query_extractor3_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(p, data): (Path, Json)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_path_and_query_extractor4_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(data, p): (Json, Path)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_path_and_query_extractor2_async2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with( - |(p, data, _q): (Path, Json, Query)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }, - ) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async3() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|data: Json, p: Path, _: Query| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async4() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|data: (Json, Path, Query)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_scope_and_path_extractor() { - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/sc", |scope| { - scope.resource("/{num}/index.html", |r| { - r.route() - .with(|p: Path<(usize,)>| format!("Welcome {}!", p.0)) - }) - }) - }); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome 10!")); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[test] -fn test_nested_scope_and_path_extractor() { - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/sc", |scope| { - scope.nested("/{num}", |scope| { - scope.resource("/{num}/index.html", |r| { - r.route().with(|p: Path<(usize, usize)>| { - format!("Welcome {} {}!", p.0, p.1) - }) - }) - }) - }) - }); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/12/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!")); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[cfg(actix_impl_trait)] -fn test_impl_trait( - data: (Json, Path, Query), -) -> impl Future { - Delay::new(Instant::now() + Duration::from_millis(10)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) - .and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))) -} - -#[cfg(actix_impl_trait)] -fn test_impl_trait_err( - _data: (Json, Path, Query), -) -> impl Future { - Delay::new(Instant::now() + Duration::from_millis(10)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) - .and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other"))) -} - -#[cfg(actix_impl_trait)] -#[test] -fn test_path_and_query_extractor2_async4_impl_trait() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_async(test_impl_trait) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[cfg(actix_impl_trait)] -#[test] -fn test_path_and_query_extractor2_async4_impl_trait_err() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_async(test_impl_trait_err) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[test] -fn test_non_ascii_route() { - let mut srv = test::TestServer::new(|app| { - app.resource("/中文/index.html", |r| r.f(|_| "success")); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/中文/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"success")); -} - -#[test] -fn test_unsafe_path_route() { - let mut srv = test::TestServer::new(|app| { - app.resource("/test/{url}", |r| { - r.f(|r| format!("success: {}", &r.match_info()["url"])) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test/http%3A%2F%2Fexample.com")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"success: http:%2F%2Fexample.com") - ); -} diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs deleted file mode 100644 index 6cb6ee36..00000000 --- a/tests/test_middleware.rs +++ /dev/null @@ -1,1055 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate futures; -extern crate tokio_timer; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::thread; -use std::time::{Duration, Instant}; - -use actix_web::error::{Error, ErrorInternalServerError}; -use actix_web::*; -use futures::{future, Future}; -use tokio_timer::Delay; - -struct MiddlewareTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareTest { - fn start(&self, _: &HttpRequest) -> Result { - self.start - .store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - Ok(middleware::Started::Done) - } - - fn response( - &self, _: &HttpRequest, resp: HttpResponse, - ) -> Result { - self.response - .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - Ok(middleware::Response::Done(resp)) - } - - fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish - .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - middleware::Finished::Done - } -} - -#[test] -fn test_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut 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(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_resource_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut 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(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_scope_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/", |r| { - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", |r| { - r.middleware(mw); - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| { - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -fn index_test_middleware_async_error(_: &HttpRequest) -> FutureResponse { - future::result(Err(error::ErrorBadRequest("TEST"))).responder() -} - -#[test] -fn test_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).handler(index_test_middleware_async_error) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).resource("/test", |r| r.f(index_test_middleware_async_error)) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }; - - App::new().resource("/test", move |r| { - r.middleware(mw); - r.f(index_test_middleware_async_error); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -struct MiddlewareAsyncTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareAsyncTest { - fn start(&self, _: &HttpRequest) -> Result { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let start = Arc::clone(&self.start); - Ok(middleware::Started::Future(Box::new( - to.from_err().and_then(move |_| { - start.fetch_add(1, Ordering::Relaxed); - Ok(None) - }), - ))) - } - - fn response( - &self, _: &HttpRequest, resp: HttpResponse, - ) -> Result { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let response = Arc::clone(&self.response); - Ok(middleware::Response::Future(Box::new( - to.from_err().and_then(move |_| { - response.fetch_add(1, Ordering::Relaxed); - Ok(resp) - }), - ))) - } - - fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let finish = Arc::clone(&self.finish); - middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| { - finish.fetch_add(1, Ordering::Relaxed); - Ok(()) - }))) - } -} - -#[test] -fn test_async_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(50)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_sync_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(50)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_scope_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_async_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_resource_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - let mw2 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(mw2); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_sync_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - let mw2 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(mw2); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -struct MiddlewareWithErr; - -impl middleware::Middleware for MiddlewareWithErr { - fn start(&self, _: &HttpRequest) -> Result { - Err(ErrorInternalServerError("middleware error")) - } -} - -struct MiddlewareAsyncWithErr; - -impl middleware::Middleware for MiddlewareAsyncWithErr { - fn start(&self, _: &HttpRequest) -> Result { - Ok(middleware::Started::Future(Box::new(future::err( - ErrorInternalServerError("middleware error"), - )))) - } -} - -#[test] -fn test_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new() - .middleware(mw1) - .middleware(MiddlewareWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new() - .middleware(mw1) - .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().scope("/scope", |scope| { - scope - .middleware(mw1) - .middleware(MiddlewareWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().scope("/scope", |scope| { - scope - .middleware(mw1) - .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(MiddlewareWithErr); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(MiddlewareAsyncWithErr); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[cfg(feature = "session")] -#[test] -fn test_session_storage_middleware() { - use actix_web::middleware::session::{ - CookieSessionBackend, RequestSession, SessionStorage, - }; - - const SIMPLE_NAME: &'static str = "simple"; - const SIMPLE_PAYLOAD: &'static str = "kantan"; - const COMPLEX_NAME: &'static str = "test"; - const COMPLEX_PAYLOAD: &'static str = "url=https://test.com&generate_204"; - //TODO: investigate how to handle below input - //const COMPLEX_PAYLOAD: &'static str = "FJc%26continue_url%3Dhttp%253A%252F%252Fconnectivitycheck.gstatic.com%252Fgenerate_204"; - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/index", move |r| { - r.f(|req| { - let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD); - assert!(res.is_ok()); - let value = req.session().get::(COMPLEX_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); - - let res = req.session().set(SIMPLE_NAME, SIMPLE_PAYLOAD); - assert!(res.is_ok()); - let value = req.session().get::(SIMPLE_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); - - HttpResponse::Ok() - }) - }).resource("/expect_cookie", move |r| { - r.f(|req| { - let _cookies = req.cookies().expect("To get cookies"); - - let value = req.session().get::(SIMPLE_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); - - let value = req.session().get::(COMPLEX_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); - - HttpResponse::Ok() - }) - }) - }); - - let request = srv.get().uri(srv.url("/index")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert!(response.headers().contains_key("set-cookie")); - let set_cookie = response.headers().get("set-cookie"); - assert!(set_cookie.is_some()); - let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str"); - - let request = srv - .get() - .uri(srv.url("/expect_cookie")) - .header("cookie", set_cookie.split(';').next().unwrap()) - .finish() - .unwrap(); - - srv.execute(request.send()).unwrap(); -} diff --git a/tests/test_server.rs b/tests/test_server.rs deleted file mode 100644 index 477d3e64..00000000 --- a/tests/test_server.rs +++ /dev/null @@ -1,1357 +0,0 @@ -extern crate actix; -extern crate actix_net; -extern crate actix_web; -#[cfg(feature = "brotli")] -extern crate brotli2; -extern crate bytes; -extern crate flate2; -extern crate futures; -extern crate h2; -extern crate http as modhttp; -extern crate rand; -extern crate tokio; -extern crate tokio_current_thread; -extern crate tokio_current_thread as current_thread; -extern crate tokio_reactor; -extern crate tokio_tcp; - -#[cfg(feature = "tls")] -extern crate native_tls; -#[cfg(feature = "ssl")] -extern crate openssl; -#[cfg(feature = "rust-tls")] -extern crate rustls; - -use std::io::{Read, Write}; -use std::sync::Arc; -use std::{thread, time}; - -#[cfg(feature = "brotli")] -use brotli2::write::{BrotliDecoder, BrotliEncoder}; -use bytes::{Bytes, BytesMut}; -use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; -use flate2::Compression; -use futures::stream::once; -use futures::{Future, Stream}; -use h2::client as h2client; -use modhttp::Request; -use rand::distributions::Alphanumeric; -use rand::Rng; -use tokio::runtime::current_thread::Runtime; -use tokio_current_thread::spawn; -use tokio_tcp::TcpStream; - -use actix_web::*; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[test] -#[cfg(unix)] -fn test_start() { - use actix::System; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.start(); - let _ = tx.send((addr, srv_addr, System::current())); - }); - }); - let (addr, srv_addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - // pause - let _ = srv_addr.send(server::PauseServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .timeout(time::Duration::from_millis(200)) - .finish() - .unwrap(); - assert!(rt.block_on(req.send()).is_err()); - } - - // resume - let _ = srv_addr.send(server::ResumeServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - let _ = sys.stop(); -} - -#[test] -#[cfg(unix)] -fn test_shutdown() { - use actix::System; - use std::net; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.shutdown_timeout(1).start(); - let _ = tx.send((addr, srv_addr, System::current())); - }); - }); - let (addr, srv_addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - srv_addr.do_send(server::StopServer { graceful: true }); - assert!(response.status().is_success()); - } - - thread::sleep(time::Duration::from_millis(1000)); - assert!(net::TcpStream::connect(addr).is_err()); - - let _ = sys.stop(); -} - -#[test] -#[cfg(unix)] -fn test_panic() { - use actix::System; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - App::new() - .resource("/panic", |r| { - r.method(http::Method::GET).f(|_| -> &'static str { - panic!("error"); - }); - }).resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }).workers(1); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - srv.start(); - let _ = tx.send((addr, System::current())); - }); - }); - let (addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/panic", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()); - assert!(response.is_err()); - } - - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()); - assert!(response.is_err()); - } - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - let _ = sys.stop(); -} - -#[test] -fn test_simple() { - let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok())); - let req = srv.get().finish().unwrap(); - let response = srv.execute(req.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_headers() { - let data = STR.repeat(10); - let srv_data = Arc::new(data.clone()); - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - let mut builder = HttpResponse::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - builder.body(data.as_ref()) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_body() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_gzip() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_gzip_large() { - let data = STR.repeat(10); - let srv_data = Arc::new(data.clone()); - - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from(data)); -} - -#[test] -fn test_body_gzip_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(70_000) - .collect::(); - let srv_data = Arc::new(data.clone()); - - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(dec.len(), data.len()); - assert_eq!(Bytes::from(dec), Bytes::from(data)); -} - -#[test] -fn test_body_chunked_implicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_body_br_streaming() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode br - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_head_empty() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish()) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_head_binary() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .content_length(100) - .body(STR) - }) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_head_binary2() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(STR) - }) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[test] -fn test_body_length() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_length(STR.len() as u64) - .content_encoding(http::ContentEncoding::Identity) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_chunked_explicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_identity() { - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - let enc2 = enc.clone(); - - let mut srv = test::TestServer::new(move |app| { - let enc3 = enc2.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc3.clone()) - }) - }); - - // client request - let request = srv - .get() - .header("accept-encoding", "deflate") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode deflate - assert_eq!(bytes, Bytes::from(STR)); -} - -#[test] -fn test_body_deflate() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - }) - }); - - // client request - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode deflate - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_body_brotli() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(STR) - }) - }); - - // client request - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode brotli - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_gzip_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_gzip_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(60_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_deflate_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_reading_deflate_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "brotli", feature = "ssl"))] -#[test] -fn test_brotli_encoding_large_ssl() { - use actix::{Actor, System}; - use openssl::ssl::{ - SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, - }; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = STR.repeat(10); - let srv = test::TestServer::build().ssl(builder).start(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // body - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(srv.url("/")) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "br") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "rust-tls", feature = "ssl"))] -#[test] -fn test_reading_deflate_encoding_large_random_ssl() { - use actix::{Actor, System}; - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, rsa_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let srv = test::TestServer::build().rustls(config).start(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(srv.url("/")) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "deflate") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "tls", feature = "ssl"))] -#[test] -fn test_reading_deflate_encoding_large_random_tls() { - use native_tls::{Identity, TlsAcceptor}; - use openssl::ssl::{ - SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, - }; - use std::fs::File; - use std::sync::mpsc; - - use actix::{Actor, System}; - let (tx, rx) = mpsc::channel(); - - // load ssl keys - let mut file = File::open("tests/identity.pfx").unwrap(); - let mut identity = vec![]; - file.read_to_end(&mut identity).unwrap(); - let identity = Identity::from_pkcs12(&identity, "1").unwrap(); - let acceptor = TlsAcceptor::new(identity).unwrap(); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - server::new(|| { - App::new().handler("/", |req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }).bind_tls(addr, acceptor) - .unwrap() - .start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(format!("https://{}/", addr)) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "deflate") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); - - let _ = sys.stop(); -} - -#[test] -fn test_h2() { - let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - let addr = srv.addr(); - thread::sleep(time::Duration::from_millis(500)); - - let mut core = Runtime::new().unwrap(); - let tcp = TcpStream::connect(&addr); - - let tcp = tcp - .then(|res| h2client::handshake(res.unwrap())) - .then(move |res| { - let (mut client, h2) = res.unwrap(); - - let request = Request::builder() - .uri(format!("https://{}/", addr).as_str()) - .body(()) - .unwrap(); - let (response, _) = client.send_request(request, false).unwrap(); - - // Spawn a task to run the conn... - spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); - - response.and_then(|response| { - assert_eq!(response.status(), http::StatusCode::OK); - - let (_, body) = response.into_parts(); - - body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { - b.extend(c); - Ok(b) - }) - }) - }); - let _res = core.block_on(tcp); - // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_application() { - let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_default_404_handler_response() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .prefix("/app") - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - }); - let addr = srv.addr(); - - let mut buf = [0; 24]; - let request = TcpStream::connect(&addr) - .and_then(|sock| { - tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n") - .and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf)) - .and_then(|(_, buf)| Ok(buf)) - }).map_err(|e| panic!("{:?}", e)); - let response = srv.execute(request).unwrap(); - let rep = String::from_utf8_lossy(&response[..]); - assert!(rep.contains("HTTP/1.1 404 Not Found")); -} - -#[test] -fn test_server_cookies() { - use actix_web::http; - - let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| { - r.f(|_| { - HttpResponse::Ok() - .cookie( - http::CookieBuilder::new("first", "first_value") - .http_only(true) - .finish(), - ).cookie(http::Cookie::new("second", "first_value")) - .cookie(http::Cookie::new("second", "second_value")) - .finish() - }) - }) - }); - - let first_cookie = http::CookieBuilder::new("first", "first_value") - .http_only(true) - .finish(); - let second_cookie = http::Cookie::new("second", "second_value"); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let cookies = response.cookies().expect("To have cookies"); - assert_eq!(cookies.len(), 2); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } - - let first_cookie = first_cookie.to_string(); - let second_cookie = second_cookie.to_string(); - //Check that we have exactly two instances of raw cookie headers - let cookies = response - .headers() - .get_all(http::header::SET_COOKIE) - .iter() - .map(|header| header.to_str().expect("To str").to_string()) - .collect::>(); - assert_eq!(cookies.len(), 2); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } -} - -#[test] -fn test_slow_request() { - use actix::System; - use std::net; - use std::sync::mpsc; - let (tx, rx) = mpsc::channel(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind(addr).unwrap(); - srv.client_timeout(200).start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - thread::sleep(time::Duration::from_millis(200)); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - sys.stop(); -} - -#[test] -fn test_malformed_request() { - use actix::System; - use std::net; - use std::sync::mpsc; - let (tx, rx) = mpsc::channel(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - let _ = srv.bind(addr).unwrap().start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - thread::sleep(time::Duration::from_millis(200)); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 400 Bad Request")); - - sys.stop(); -} - -#[test] -fn test_app_404() { - let mut srv = test::TestServer::with_factory(|| { - App::new().prefix("/prefix").resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - let request = srv.client(http::Method::GET, "/prefix/").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let request = srv.client(http::Method::GET, "/").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::NOT_FOUND); -} - -#[test] -#[cfg(feature = "ssl")] -fn test_ssl_handshake_timeout() { - use actix::System; - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - use std::net; - use std::sync::mpsc; - - let (tx, rx) = mpsc::channel(); - let addr = test::TestServer::unused_addr(); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - srv.bind_ssl(addr, builder) - .unwrap() - .workers(1) - .client_timeout(200) - .start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.is_empty()); - - let _ = sys.stop(); -} diff --git a/tests/test_ws.rs b/tests/test_ws.rs deleted file mode 100644 index 5a0ce204..00000000 --- a/tests/test_ws.rs +++ /dev/null @@ -1,394 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -extern crate http; -extern crate rand; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::{thread, time}; - -use bytes::Bytes; -use futures::Stream; -use rand::distributions::Alphanumeric; -use rand::Rng; - -#[cfg(feature = "ssl")] -extern crate openssl; -#[cfg(feature = "rust-tls")] -extern crate rustls; - -use actix::prelude::*; -use actix_web::*; - -struct Ws; - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[test] -fn test_simple() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(ws::CloseCode::Normal.into())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - ); -} - -// websocket resource helper function -fn start_ws_resource(req: &HttpRequest) -> Result { - ws::start(req, Ws) -} - -#[test] -fn test_simple_path() { - const PATH: &str = "/v1/ws/"; - - // Create a websocket at a specific path. - let mut srv = test::TestServer::new(|app| { - app.resource(PATH, |r| r.route().f(start_ws_resource)); - }); - // fetch the sockets for the resource at a given path. - let (reader, mut writer) = srv.ws_at(PATH).unwrap(); - - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(ws::CloseCode::Normal.into())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - ); -} - -#[test] -fn test_empty_close_code() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.close(None); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(None))); -} - -#[test] -fn test_close_description() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - let close_reason: ws::CloseReason = - (ws::CloseCode::Normal, "close description").into(); - writer.close(Some(close_reason.clone())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(Some(close_reason)))); -} - -#[test] -fn test_large_text() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(65_536) - .collect::(); - - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (mut reader, mut writer) = srv.ws().unwrap(); - - for _ in 0..100 { - writer.text(data.clone()); - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, Some(ws::Message::Text(data.clone()))); - } -} - -#[test] -fn test_large_bin() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(65_536) - .collect::(); - - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (mut reader, mut writer) = srv.ws().unwrap(); - - for _ in 0..100 { - writer.binary(data.clone()); - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone())))); - } -} - -#[test] -fn test_client_frame_size() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(131_072) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| -> Result { - let mut resp = ws::handshake(req)?; - let stream = ws::WsStream::new(req.payload()).max_size(131_072); - - let body = ws::WebsocketContext::create(req.clone(), Ws, stream); - Ok(resp.body(body)) - }) - }); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.binary(data.clone()); - match srv.execute(reader.into_future()).err().unwrap().0 { - ws::ProtocolError::Overflow => (), - _ => panic!(), - } -} - -struct Ws2 { - count: usize, - bin: bool, -} - -impl Actor for Ws2 { - type Context = ws::WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - self.send(ctx); - } -} - -impl Ws2 { - fn send(&mut self, ctx: &mut ws::WebsocketContext) { - if self.bin { - ctx.binary(Vec::from("0".repeat(65_536))); - } else { - ctx.text("0".repeat(65_536)); - } - ctx.drain() - .and_then(|_, act, ctx| { - act.count += 1; - if act.count != 10_000 { - act.send(ctx); - } - actix::fut::ok(()) - }).wait(ctx); - } -} - -impl StreamHandler for Ws2 { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[test] -fn test_server_send_text() { - let data = Some(ws::Message::Text("0".repeat(65_536))); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -fn test_server_send_bin() { - let data = Some(ws::Message::Binary(Binary::from("0".repeat(65_536)))); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: true, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -#[cfg(feature = "ssl")] -fn test_ws_server_ssl() { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let mut srv = test::TestServer::build().ssl(builder).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -#[cfg(feature = "rust-tls")] -fn test_ws_server_rust_tls() { - use rustls::internal::pemfile::{certs, rsa_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let mut srv = test::TestServer::build().rustls(config).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - - let (mut reader, _writer) = srv.ws().unwrap(); - - let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -struct WsStopped(Arc); - -impl Actor for WsStopped { - type Context = ws::WebsocketContext; - - fn stopped(&mut self, _: &mut Self::Context) { - self.0.fetch_add(1, Ordering::Relaxed); - } -} - -impl StreamHandler for WsStopped { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Text(text) => ctx.text(text), - _ => (), - } - } -} - -#[test] -fn test_ws_stopped() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let mut srv = test::TestServer::new(move |app| { - let num3 = num2.clone(); - app.handler(move |req| ws::start(req, WsStopped(num3.clone()))) - }); - { - let (reader, mut writer) = srv.ws().unwrap(); - writer.text("text"); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - } - thread::sleep(time::Duration::from_millis(1000)); - - assert_eq!(num.load(Ordering::Relaxed), 1); -} From b15b2dda22dda5d13c58c457ec3ada773c03d5b8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 17:34:57 -0700 Subject: [PATCH 0715/1635] remove ServerSettings --- Cargo.toml | 10 +- src/error.rs | 4 - src/info.rs | 220 ----------------------------------------- src/lib.rs | 12 --- src/server/error.rs | 17 ---- src/server/message.rs | 46 ++------- src/server/mod.rs | 7 +- src/server/settings.rs | 119 +--------------------- 8 files changed, 10 insertions(+), 425 deletions(-) delete mode 100644 src/info.rs diff --git a/Cargo.toml b/Cargo.toml index 258301da..60f14485 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,14 +66,11 @@ actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" -h2 = "0.1" -htmlescape = "0.3" http = "^0.1.8" httparse = "1.3" log = "0.4" mime = "0.3" mime_guess = "2.0.0-alpha" -num_cpus = "1.0" percent-encoding = "1.0" rand = "0.5" regex = "1.0" @@ -85,8 +82,6 @@ time = "0.1" encoding = "0.2" language-tags = "0.2" lazy_static = "1.0" -lazycell = "1.0.0" -parking_lot = "0.6" serde_urlencoded = "^0.5.3" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } @@ -96,14 +91,10 @@ flate2 = { version="^1.0.2", optional = true, default-features = false } failure = "^0.1.2" # io -mio = "^0.6.13" net2 = "0.2" bytes = "0.4" byteorder = "1.2" futures = "0.1" -futures-cpupool = "0.1" -slab = "0.4" -tokio = "0.1" tokio-codec = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" @@ -132,6 +123,7 @@ tokio-uds = { version="0.2", optional = true } actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" +tokio = "0.1" [build-dependencies] version_check = "0.1" diff --git a/src/error.rs b/src/error.rs index 72480380..ff2388de 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,7 +11,6 @@ use failure::{self, Backtrace, Fail}; use futures::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; -use http2::Error as Http2Error; use httparse; use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; @@ -350,9 +349,6 @@ pub enum PayloadError { /// Io error #[fail(display = "{}", _0)] Io(#[cause] IoError), - /// Http2 error - #[fail(display = "{}", _0)] - Http2(#[cause] Http2Error), } impl From for PayloadError { diff --git a/src/info.rs b/src/info.rs deleted file mode 100644 index 5a2f2180..00000000 --- a/src/info.rs +++ /dev/null @@ -1,220 +0,0 @@ -use http::header::{self, HeaderName}; -use server::Request; - -const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; -const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; -const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; - -/// `HttpRequest` connection information -#[derive(Clone, Default)] -pub struct ConnectionInfo { - scheme: String, - host: String, - remote: Option, - peer: Option, -} - -impl ConnectionInfo { - /// Create *ConnectionInfo* instance for a request. - #[cfg_attr( - feature = "cargo-clippy", - allow(clippy::cyclomatic_complexity) - )] - pub fn update(&mut self, req: &Request) { - let mut host = None; - let mut scheme = None; - 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.trim().splitn(2, '='); - if let Some(name) = items.next() { - if let Some(val) = items.next() { - match &name.to_lowercase() as &str { - "for" => if remote.is_none() { - remote = Some(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_lowercase(X_FORWARDED_PROTO).unwrap()) - { - if let Ok(h) = h.to_str() { - scheme = h.split(',').next().map(|v| v.trim()); - } - } - if scheme.is_none() { - scheme = req.uri().scheme_part().map(|a| a.as_str()); - if scheme.is_none() && req.server_settings().secure() { - scheme = Some("https") - } - } - } - - // host - if host.is_none() { - if let Some(h) = req - .headers() - .get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) - { - if let Ok(h) = h.to_str() { - host = h.split(',').next().map(|v| v.trim()); - } - } - if host.is_none() { - if let Some(h) = req.headers().get(header::HOST) { - host = h.to_str().ok(); - } - if host.is_none() { - host = req.uri().authority_part().map(|a| a.as_str()); - if host.is_none() { - host = Some(req.server_settings().host()); - } - } - } - } - - // remote addr - if remote.is_none() { - if let Some(h) = req - .headers() - .get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) - { - if let Ok(h) = h.to_str() { - remote = h.split(',').next().map(|v| v.trim()); - } - } - if remote.is_none() { - // get peeraddr from socketaddr - peer = req.peer_addr().map(|addr| format!("{}", addr)); - } - } - - self.scheme = scheme.unwrap_or("http").to_owned(); - self.host = host.unwrap_or("localhost").to_owned(); - self.remote = remote.map(|s| s.to_owned()); - self.peer = peer; - } - - /// 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 - /// - Server hostname - 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 - /// - peer name of opened socket - #[inline] - pub fn remote(&self) -> Option<&str> { - if let Some(ref r) = self.remote { - Some(r) - } else if let Some(ref peer) = self.peer { - Some(peer) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::TestRequest; - - #[test] - fn test_forwarded() { - let req = TestRequest::default().request(); - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.scheme(), "http"); - assert_eq!(info.host(), "localhost:8080"); - - let req = TestRequest::default() - .header( - header::FORWARDED, - "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", - ).request(); - - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.scheme(), "https"); - assert_eq!(info.host(), "rust-lang.org"); - assert_eq!(info.remote(), Some("192.0.2.60")); - - let req = TestRequest::default() - .header(header::HOST, "rust-lang.org") - .request(); - - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.scheme(), "http"); - assert_eq!(info.host(), "rust-lang.org"); - assert_eq!(info.remote(), None); - - let req = TestRequest::default() - .header(X_FORWARDED_FOR, "192.0.2.60") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.remote(), Some("192.0.2.60")); - - let req = TestRequest::default() - .header(X_FORWARDED_HOST, "192.0.2.60") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.host(), "192.0.2.60"); - assert_eq!(info.remote(), None); - - let req = TestRequest::default() - .header(X_FORWARDED_PROTO, "https") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); - assert_eq!(info.scheme(), "https"); - } -} diff --git a/src/lib.rs b/src/lib.rs index 6df1a770..efd56618 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,20 +99,13 @@ extern crate lazy_static; #[macro_use] extern crate futures; extern crate cookie; -extern crate futures_cpupool; -extern crate htmlescape; extern crate http as modhttp; extern crate httparse; extern crate language_tags; -extern crate lazycell; extern crate mime; extern crate mime_guess; -extern crate mio; extern crate net2; -extern crate parking_lot; extern crate rand; -extern crate slab; -extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; @@ -129,8 +122,6 @@ extern crate brotli2; extern crate encoding; #[cfg(feature = "flate2")] extern crate flate2; -extern crate h2 as http2; -extern crate num_cpus; extern crate serde_urlencoded; #[macro_use] extern crate percent_encoding; @@ -148,9 +139,7 @@ mod extensions; mod header; mod httpcodes; mod httpmessage; -//mod httprequest; mod httpresponse; -mod info; mod json; mod payload; mod uri; @@ -182,7 +171,6 @@ pub mod dev { pub use body::BodyStream; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; - pub use info::ConnectionInfo; pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; } diff --git a/src/server/error.rs b/src/server/error.rs index d9e1239e..7d5c67d1 100644 --- a/src/server/error.rs +++ b/src/server/error.rs @@ -2,7 +2,6 @@ use std::fmt::{Debug, Display}; use std::io; use futures::{Async, Poll}; -use http2; use error::{Error, ParseError}; use http::{StatusCode, Version}; @@ -44,10 +43,6 @@ pub enum HttpDispatchError { // #[fail(display = "Connection shutdown timeout")] ShutdownTimeout, - /// HTTP2 error - // #[fail(display = "HTTP2 error: {}", _0)] - Http2(http2::Error), - /// Payload is not consumed // #[fail(display = "Task is completed but request's payload is not consumed")] PayloadIsNotConsumed, @@ -65,12 +60,6 @@ pub enum HttpDispatchError { Unknown, } -// impl From for HttpDispatchError { -// fn from(err: E) -> Self { -// HttpDispatchError::App(err) -// } -// } - impl From for HttpDispatchError { fn from(err: ParseError) -> Self { HttpDispatchError::Parse(err) @@ -82,9 +71,3 @@ impl From for HttpDispatchError { HttpDispatchError::Io(err) } } - -impl From for HttpDispatchError { - fn from(err: http2::Error) -> Self { - HttpDispatchError::Http2(err) - } -} diff --git a/src/server/message.rs b/src/server/message.rs index 74ec5f17..c39302ba 100644 --- a/src/server/message.rs +++ b/src/server/message.rs @@ -8,9 +8,7 @@ use http::{header, HeaderMap, Method, Uri, Version}; use extensions::Extensions; use httpmessage::HttpMessage; -use info::ConnectionInfo; use payload::Payload; -use server::ServerSettings; use uri::Url as InnerUrl; bitflags! { @@ -33,9 +31,7 @@ pub(crate) struct InnerRequest { pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, pub(crate) addr: Option, - pub(crate) info: RefCell, pub(crate) payload: RefCell>, - pub(crate) settings: ServerSettings, pub(crate) stream_extensions: Option>, pool: &'static RequestPool, } @@ -70,18 +66,16 @@ impl HttpMessage for Request { impl Request { /// Create new RequestContext instance - pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request { + pub(crate) fn new(pool: &'static RequestPool) -> Request { Request { inner: Rc::new(InnerRequest { pool, - settings, method: Method::GET, url: InnerUrl::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), flags: Cell::new(MessageFlags::empty()), addr: None, - info: RefCell::new(ConnectionInfo::default()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), stream_extensions: None, @@ -144,9 +138,6 @@ impl Request { /// /// Peer address is actual socket address, if proxy is used in front of /// actix http server, then peer address would be address of this proxy. - /// - /// To get client connection information `connection_info()` method should - /// be used. pub fn peer_addr(&self) -> Option { self.inner().addr } @@ -179,31 +170,12 @@ impl Request { self.inner().method == Method::CONNECT } - /// Get *ConnectionInfo* for the correct request. - pub fn connection_info(&self) -> Ref { - if self.inner().flags.get().contains(MessageFlags::CONN_INFO) { - self.inner().info.borrow() - } else { - let mut flags = self.inner().flags.get(); - flags.insert(MessageFlags::CONN_INFO); - self.inner().flags.set(flags); - self.inner().info.borrow_mut().update(self); - self.inner().info.borrow() - } - } - /// Io stream extensions #[inline] pub fn stream_extensions(&self) -> Option<&Extensions> { self.inner().stream_extensions.as_ref().map(|e| e.as_ref()) } - /// Server settings - #[inline] - pub fn server_settings(&self) -> &ServerSettings { - &self.inner().settings - } - pub(crate) fn clone(&self) -> Self { Request { inner: self.inner.clone(), @@ -241,24 +213,18 @@ impl fmt::Debug for Request { } } -pub struct RequestPool(RefCell>>, RefCell); +pub struct RequestPool(RefCell>>); thread_local!(static POOL: &'static RequestPool = RequestPool::create()); impl RequestPool { fn create() -> &'static RequestPool { - let pool = RequestPool( - RefCell::new(VecDeque::with_capacity(128)), - RefCell::new(ServerSettings::default()), - ); + let pool = RequestPool(RefCell::new(VecDeque::with_capacity(128))); Box::leak(Box::new(pool)) } - pub(crate) fn pool(settings: ServerSettings) -> &'static RequestPool { - POOL.with(|p| { - *p.1.borrow_mut() = settings; - *p - }) + pub(crate) fn pool() -> &'static RequestPool { + POOL.with(|p| *p) } #[inline] @@ -266,7 +232,7 @@ impl RequestPool { if let Some(msg) = pool.0.borrow_mut().pop_front() { Request { inner: msg } } else { - Request::new(pool, pool.1.borrow().clone()) + Request::new(pool) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 7d64a6e2..be172e64 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -130,10 +130,8 @@ pub(crate) mod output; // pub(crate) mod service; pub(crate) mod settings; -pub use self::message::Request; - pub use self::error::{AcceptorError, HttpDispatchError}; -pub use self::settings::ServerSettings; +pub use self::message::Request; #[doc(hidden)] pub mod h1disp; @@ -141,9 +139,6 @@ pub mod h1disp; #[doc(hidden)] pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; -//#[doc(hidden)] -//pub use self::service::{H1Service, HttpService, StreamConfiguration}; - #[doc(hidden)] pub use self::helpers::write_content_length; diff --git a/src/server/settings.rs b/src/server/settings.rs index f2128359..b8b7e51f 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -7,10 +7,7 @@ use std::{env, fmt, net}; use bytes::BytesMut; use futures::{future, Future}; -use futures_cpupool::CpuPool; use http::StatusCode; -use lazycell::LazyCell; -use parking_lot::Mutex; use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; @@ -20,109 +17,6 @@ use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; -/// Env variable for default cpu pool size -const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL"; - -lazy_static! { - pub(crate) static ref DEFAULT_CPUPOOL: Mutex = { - let default = match env::var(ENV_CPU_POOL_VAR) { - Ok(val) => { - if let Ok(val) = val.parse() { - val - } else { - error!("Can not parse ACTIX_CPU_POOL value"); - 20 - } - } - Err(_) => 20, - }; - Mutex::new(CpuPool::new(default)) - }; -} - -/// Various server settings -pub struct ServerSettings { - addr: net::SocketAddr, - secure: bool, - host: String, - cpu_pool: LazyCell, - responses: &'static HttpResponsePool, -} - -impl Clone for ServerSettings { - fn clone(&self) -> Self { - ServerSettings { - addr: self.addr, - secure: self.secure, - host: self.host.clone(), - cpu_pool: LazyCell::new(), - responses: HttpResponsePool::get_pool(), - } - } -} - -impl Default for ServerSettings { - fn default() -> Self { - ServerSettings { - addr: "127.0.0.1:8080".parse().unwrap(), - secure: false, - host: "localhost:8080".to_owned(), - responses: HttpResponsePool::get_pool(), - cpu_pool: LazyCell::new(), - } - } -} - -impl ServerSettings { - /// Crate server settings instance - pub(crate) fn new( - addr: net::SocketAddr, host: &str, secure: bool, - ) -> ServerSettings { - let host = host.to_owned(); - let cpu_pool = LazyCell::new(); - let responses = HttpResponsePool::get_pool(); - ServerSettings { - addr, - secure, - host, - cpu_pool, - responses, - } - } - - /// Returns the socket address of the local half of this TCP connection - pub fn local_addr(&self) -> net::SocketAddr { - self.addr - } - - /// Returns true if connection is secure(https) - pub fn secure(&self) -> bool { - self.secure - } - - /// Returns host header value - pub fn host(&self) -> &str { - &self.host - } - - /// Returns default `CpuPool` for server - pub fn cpu_pool(&self) -> &CpuPool { - self.cpu_pool.borrow_with(|| DEFAULT_CPUPOOL.lock().clone()) - } - - #[inline] - pub(crate) fn get_response(&self, status: StatusCode, body: Body) -> HttpResponse { - HttpResponsePool::get_response(&self.responses, status, body) - } - - #[inline] - pub(crate) fn get_response_builder( - &self, status: StatusCode, - ) -> HttpResponseBuilder { - HttpResponsePool::get_builder(&self.responses, status) - } -} - // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; @@ -149,7 +43,6 @@ impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, - settings: ServerSettings, ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -168,7 +61,7 @@ impl ServiceConfig { client_timeout, client_shutdown, bytes: Rc::new(SharedBytesPool::new()), - messages: RequestPool::pool(settings), + messages: RequestPool::pool(), date: UnsafeCell::new((false, Date::new())), })) } @@ -211,9 +104,7 @@ impl ServiceConfig { // Unsafe: WorkerSetting is !Sync and !Send unsafe { (*self.0.date.get()).0 = false }; } -} -impl ServiceConfig { #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { @@ -412,15 +303,9 @@ impl ServiceConfigBuilder { /// Finish service configuration and create `ServiceConfig` object. pub fn finish(self) -> ServiceConfig { - let settings = ServerSettings::new(self.addr, &self.host, self.secure); let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; - ServiceConfig::new( - self.keep_alive, - self.client_timeout, - client_shutdown, - settings, - ) + ServiceConfig::new(self.keep_alive, self.client_timeout, client_shutdown) } } From 4ca711909b07b882f57fc76c395da37378bee649 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 20:02:10 -0700 Subject: [PATCH 0716/1635] refactor types --- src/{server/settings.rs => config.rs} | 50 +- src/error.rs | 136 +-- src/{server/h1codec.rs => h1/codec.rs} | 32 +- src/{server/h1decoder.rs => h1/decoder.rs} | 16 +- src/{server/h1disp.rs => h1/dispatcher.rs} | 80 +- src/h1/mod.rs | 9 + src/h1/service.rs | 125 ++ src/{server => }/helpers.rs | 0 src/lib.rs | 9 +- src/{server/message.rs => request.rs} | 34 +- src/server/error.rs | 73 -- src/server/h1.rs | 1289 -------------------- src/server/mod.rs | 21 +- src/server/output.rs | 2 +- tests/test_h1v2.rs | 20 +- 15 files changed, 273 insertions(+), 1623 deletions(-) rename src/{server/settings.rs => config.rs} (89%) rename src/{server/h1codec.rs => h1/codec.rs} (93%) rename src/{server/h1decoder.rs => h1/decoder.rs} (97%) rename src/{server/h1disp.rs => h1/dispatcher.rs} (86%) create mode 100644 src/h1/mod.rs create mode 100644 src/h1/service.rs rename src/{server => }/helpers.rs (100%) rename src/{server/message.rs => request.rs} (87%) delete mode 100644 src/server/error.rs delete mode 100644 src/server/h1.rs diff --git a/src/server/settings.rs b/src/config.rs similarity index 89% rename from src/server/settings.rs rename to src/config.rs index b8b7e51f..508cd5dd 100644 --- a/src/server/settings.rs +++ b/src/config.rs @@ -12,10 +12,10 @@ use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use super::message::{Request, RequestPool}; -use super::KeepAlive; use body::Body; use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; +use request::{Request, RequestPool}; +use server::KeepAlive; // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; @@ -28,8 +28,6 @@ struct Inner { client_timeout: u64, client_shutdown: u64, ka_enabled: bool, - bytes: Rc, - messages: &'static RequestPool, date: UnsafeCell<(bool, Date)>, } @@ -60,8 +58,6 @@ impl ServiceConfig { ka_enabled, client_timeout, client_shutdown, - bytes: Rc::new(SharedBytesPool::new()), - messages: RequestPool::pool(), date: UnsafeCell::new((false, Date::new())), })) } @@ -83,23 +79,6 @@ impl ServiceConfig { self.0.ka_enabled } - pub(crate) fn get_bytes(&self) -> BytesMut { - self.0.bytes.get_bytes() - } - - pub(crate) fn release_bytes(&self, bytes: BytesMut) { - self.0.bytes.release_bytes(bytes) - } - - pub(crate) fn get_request(&self) -> Request { - RequestPool::get(self.0.messages) - } - - #[doc(hidden)] - pub fn request_pool(&self) -> &'static RequestPool { - self.0.messages - } - fn update_date(&self) { // Unsafe: WorkerSetting is !Sync and !Send unsafe { (*self.0.date.get()).0 = false }; @@ -341,31 +320,6 @@ impl fmt::Write for Date { } } -#[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) -> BytesMut { - if let Some(bytes) = self.0.borrow_mut().pop_front() { - bytes - } else { - BytesMut::new() - } - } - - pub fn release_bytes(&self, mut bytes: BytesMut) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - bytes.clear(); - v.push_front(bytes); - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/error.rs b/src/error.rs index ff2388de..e39dea9b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -377,62 +377,56 @@ impl ResponseError for cookie::ParseError { } } -/// A set of errors that can occur during parsing multipart streams -#[derive(Fail, Debug)] -pub enum MultipartError { - /// Content-Type header is not found - #[fail(display = "No Content-type header found")] - NoContentType, - /// Can not parse Content-Type header - #[fail(display = "Can not parse Content-Type header")] - ParseContentType, - /// Multipart boundary is not found - #[fail(display = "Multipart boundary is not found")] - Boundary, - /// Multipart stream is incomplete - #[fail(display = "Multipart stream is incomplete")] - Incomplete, - /// Error during field parsing - #[fail(display = "{}", _0)] - Parse(#[cause] ParseError), - /// Payload error - #[fail(display = "{}", _0)] - Payload(#[cause] PayloadError), +#[derive(Debug)] +/// A set of errors that can occur during dispatching http requests +pub enum DispatchError { + /// Service error + // #[fail(display = "Application specific error: {}", _0)] + Service(E), + + /// An `io::Error` that occurred while trying to read or write to a network + /// stream. + // #[fail(display = "IO error: {}", _0)] + Io(io::Error), + + /// Http request parse error. + // #[fail(display = "Parse error: {}", _0)] + Parse(ParseError), + + /// The first request did not complete within the specified timeout. + // #[fail(display = "The first request did not complete within the specified timeout")] + SlowRequestTimeout, + + /// Shutdown timeout + // #[fail(display = "Connection shutdown timeout")] + ShutdownTimeout, + + /// Payload is not consumed + // #[fail(display = "Task is completed but request's payload is not consumed")] + PayloadIsNotConsumed, + + /// Malformed request + // #[fail(display = "Malformed request")] + MalformedRequest, + + /// Internal error + // #[fail(display = "Internal error")] + InternalError, + + /// Unknown error + // #[fail(display = "Unknown error")] + Unknown, } -impl From for MultipartError { - fn from(err: ParseError) -> MultipartError { - MultipartError::Parse(err) +impl From for DispatchError { + fn from(err: ParseError) -> Self { + DispatchError::Parse(err) } } -impl From for MultipartError { - fn from(err: PayloadError) -> MultipartError { - MultipartError::Payload(err) - } -} - -/// Return `BadRequest` for `MultipartError` -impl ResponseError for MultipartError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// Error during handling `Expect` header -#[derive(Fail, PartialEq, Debug)] -pub enum ExpectError { - /// Expect header value can not be converted to utf8 - #[fail(display = "Expect header value can not be converted to utf8")] - Encoding, - /// Unknown expect value - #[fail(display = "Unknown expect value")] - UnknownExpect, -} - -impl ResponseError for ExpectError { - fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::EXPECTATION_FAILED, "Unknown Expect") +impl From for DispatchError { + fn from(err: io::Error) -> Self { + DispatchError::Io(err) } } @@ -565,28 +559,6 @@ impl From for ReadlinesError { } } -/// 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) - } -} - /// Errors which can occur when attempting to generate resource uri. #[derive(Fail, Debug, PartialEq)] pub enum UrlGenerationError { @@ -610,24 +582,6 @@ impl From for UrlGenerationError { } } -/// Errors which can occur when serving static files. -#[derive(Fail, Debug, PartialEq)] -pub enum StaticFileError { - /// Path is not a directory - #[fail(display = "Path is not a directory. Unable to serve static files")] - IsNotDirectory, - /// Cannot render directory - #[fail(display = "Unable to render directory without index file")] - IsDirectory, -} - -/// Return `NotFound` for `StaticFileError` -impl ResponseError for StaticFileError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::NOT_FOUND) - } -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/src/server/h1codec.rs b/src/h1/codec.rs similarity index 93% rename from src/server/h1codec.rs rename to src/h1/codec.rs index ea56110d..01188357 100644 --- a/src/server/h1codec.rs +++ b/src/h1/codec.rs @@ -4,37 +4,45 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::h1decoder::{H1Decoder, Message}; -use super::helpers; -use super::message::RequestPool; -use super::output::{ResponseInfo, ResponseLength}; +use super::decoder::H1Decoder; +pub use super::decoder::InMessage; use body::Body; use error::ParseError; +use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::Version; use httpresponse::HttpResponse; +use request::RequestPool; +use server::output::{ResponseInfo, ResponseLength}; -pub(crate) enum OutMessage { +pub enum OutMessage { Response(HttpResponse), Payload(Bytes), } -pub(crate) struct H1Codec { +/// HTTP/1 Codec +pub struct Codec { decoder: H1Decoder, encoder: H1Writer, } -impl H1Codec { - pub fn new(pool: &'static RequestPool) -> Self { - H1Codec { +impl Codec { + /// Create HTTP/1 codec + pub fn new() -> Self { + Codec::with_pool(RequestPool::pool()) + } + + /// Create HTTP/1 codec with request's pool + pub(crate) fn with_pool(pool: &'static RequestPool) -> Self { + Codec { decoder: H1Decoder::new(pool), encoder: H1Writer::new(), } } } -impl Decoder for H1Codec { - type Item = Message; +impl Decoder for Codec { + type Item = InMessage; type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -42,7 +50,7 @@ impl Decoder for H1Codec { } } -impl Encoder for H1Codec { +impl Encoder for Codec { type Item = OutMessage; type Error = io::Error; diff --git a/src/server/h1decoder.rs b/src/h1/decoder.rs similarity index 97% rename from src/server/h1decoder.rs rename to src/h1/decoder.rs index c6f0974a..47cc5fdf 100644 --- a/src/server/h1decoder.rs +++ b/src/h1/decoder.rs @@ -4,10 +4,10 @@ use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use httparse; -use super::message::{MessageFlags, Request, RequestPool}; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HttpTryFrom, Method, Uri, Version}; +use request::{MessageFlags, Request, RequestPool}; use uri::Url; const MAX_BUFFER_SIZE: usize = 131_072; @@ -19,7 +19,7 @@ pub(crate) struct H1Decoder { } #[derive(Debug)] -pub enum Message { +pub enum InMessage { Message(Request), MessageWithPayload(Request), Chunk(Bytes), @@ -34,14 +34,16 @@ impl H1Decoder { } } - pub fn decode(&mut self, src: &mut BytesMut) -> Result, ParseError> { + pub fn decode( + &mut self, src: &mut BytesMut, + ) -> Result, ParseError> { // read payload if self.decoder.is_some() { match self.decoder.as_mut().unwrap().decode(src)? { - Async::Ready(Some(bytes)) => return Ok(Some(Message::Chunk(bytes))), + Async::Ready(Some(bytes)) => return Ok(Some(InMessage::Chunk(bytes))), Async::Ready(None) => { self.decoder.take(); - return Ok(Some(Message::Eof)); + return Ok(Some(InMessage::Eof)); } Async::NotReady => return Ok(None), } @@ -51,9 +53,9 @@ impl H1Decoder { Async::Ready((msg, decoder)) => { self.decoder = decoder; if self.decoder.is_some() { - Ok(Some(Message::MessageWithPayload(msg))) + Ok(Some(InMessage::MessageWithPayload(msg))) } else { - Ok(Some(Message::Message(msg))) + Ok(Some(InMessage::Message(msg))) } } Async::NotReady => { diff --git a/src/server/h1disp.rs b/src/h1/dispatcher.rs similarity index 86% rename from src/server/h1disp.rs rename to src/h1/dispatcher.rs index b1c2c8a2..6e5672d3 100644 --- a/src/server/h1disp.rs +++ b/src/h1/dispatcher.rs @@ -9,21 +9,20 @@ use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; use tokio_codec::Framed; // use tokio_current_thread::spawn; -use tokio_io::AsyncWrite; +use tokio_io::{AsyncRead, AsyncWrite}; // use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadStatus, PayloadWriter}; use body::Body; +use error::DispatchError; use httpresponse::HttpResponse; -use super::error::HttpDispatchError; -use super::h1codec::{H1Codec, OutMessage}; -use super::h1decoder::Message; -use super::input::PayloadType; -use super::message::{Request, RequestPool}; -use super::IoStream; +use request::{Request, RequestPool}; +use server::input::PayloadType; + +use super::codec::{Codec, InMessage, OutMessage}; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -41,15 +40,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Http1Dispatcher +pub struct Dispatcher where S::Error: Debug + Display, { service: S, flags: Flags, - addr: Option, - framed: Framed, - error: Option>, + framed: Framed, + error: Option>, state: State, payload: Option, @@ -74,26 +72,24 @@ impl State { } } -impl Http1Dispatcher +impl Dispatcher where - T: IoStream, + T: AsyncRead + AsyncWrite, S: Service, S::Error: Debug + Display, { - pub fn new(stream: T, pool: &'static RequestPool, service: S) -> Self { - let addr = stream.peer_addr(); + /// Create http/1 dispatcher. + pub fn new(stream: T, service: S) -> Self { let flags = Flags::FLUSHED; - let codec = H1Codec::new(pool); - let framed = Framed::new(stream, codec); + let framed = Framed::new(stream, Codec::new()); - Http1Dispatcher { + Dispatcher { payload: None, state: State::None, error: None, messages: VecDeque::new(), service, flags, - addr, framed, } } @@ -141,7 +137,7 @@ where } /// Flush stream - fn poll_flush(&mut self) -> Poll<(), HttpDispatchError> { + fn poll_flush(&mut self) -> Poll<(), DispatchError> { if self.flags.contains(Flags::STARTED) && !self.flags.contains(Flags::FLUSHED) { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), @@ -153,7 +149,7 @@ where Ok(Async::Ready(_)) => { // if payload is not consumed we can not use connection if self.payload.is_some() && self.state.is_empty() { - return Err(HttpDispatchError::PayloadIsNotConsumed); + return Err(DispatchError::PayloadIsNotConsumed); } self.flags.insert(Flags::FLUSHED); Ok(Async::Ready(())) @@ -164,7 +160,7 @@ where } } - pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { + pub(self) fn poll_handler(&mut self) -> Result<(), DispatchError> { self.poll_io()?; let mut retry = self.can_read(); @@ -185,7 +181,7 @@ where } } Ok(Async::NotReady) => Some(Ok(State::Response(task))), - Err(err) => Some(Err(HttpDispatchError::App(err))), + Err(err) => Some(Err(DispatchError::Service(err))), } } else { None @@ -207,7 +203,7 @@ where Err(err) => { // it is not possible to recover from error // during pipe handling, so just drop connection - Some(Err(HttpDispatchError::App(err))) + Some(Err(DispatchError::Service(err))) } } } @@ -222,7 +218,7 @@ where *item = Some(msg); return Ok(()); } - Err(err) => Some(Err(HttpDispatchError::Io(err))), + Err(err) => Some(Err(DispatchError::Io(err))), } } State::SendResponseWithPayload(ref mut item) => { @@ -236,7 +232,7 @@ where *item = Some((msg, body)); return Ok(()); } - Err(err) => Some(Err(HttpDispatchError::Io(err))), + Err(err) => Some(Err(DispatchError::Io(err))), } } }; @@ -263,14 +259,11 @@ where Ok(()) } - fn one_message(&mut self, msg: Message) -> Result<(), HttpDispatchError> { + fn one_message(&mut self, msg: InMessage) -> Result<(), DispatchError> { self.flags.insert(Flags::STARTED); match msg { - Message::Message(mut msg) => { - // set remote addr - msg.inner_mut().addr = self.addr; - + InMessage::Message(msg) => { // handle request early if self.state.is_empty() { let mut task = self.service.call(msg); @@ -287,17 +280,14 @@ where Err(err) => { error!("Unhandled application error: {}", err); self.client_disconnected(false); - return Err(HttpDispatchError::App(err)); + return Err(DispatchError::Service(err)); } } } else { self.messages.push_back(msg); } } - Message::MessageWithPayload(mut msg) => { - // set remote addr - msg.inner_mut().addr = self.addr; - + InMessage::MessageWithPayload(msg) => { // payload let (ps, pl) = Payload::new(false); *msg.inner.payload.borrow_mut() = Some(pl); @@ -305,24 +295,24 @@ where self.messages.push_back(msg); } - Message::Chunk(chunk) => { + InMessage::Chunk(chunk) => { if let Some(ref mut payload) = self.payload { payload.feed_data(chunk); } else { error!("Internal server error: unexpected payload chunk"); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); + self.error = Some(DispatchError::InternalError); } } - Message::Eof => { + InMessage::Eof => { if let Some(mut payload) = self.payload.take() { payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); + self.error = Some(DispatchError::InternalError); } } } @@ -330,7 +320,7 @@ where Ok(()) } - pub(self) fn poll_io(&mut self) -> Result> { + pub(self) fn poll_io(&mut self) -> Result> { let mut updated = false; if self.messages.len() < MAX_PIPELINED_MESSAGES { @@ -359,7 +349,7 @@ where // Malformed requests should be responded with 400 // self.push_response_entry(StatusCode::BAD_REQUEST); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.error = Some(HttpDispatchError::MalformedRequest); + self.error = Some(DispatchError::MalformedRequest); break; } } @@ -370,14 +360,14 @@ where } } -impl Future for Http1Dispatcher +impl Future for Dispatcher where - T: IoStream, + T: AsyncRead + AsyncWrite, S: Service, S::Error: Debug + Display, { type Item = (); - type Error = HttpDispatchError; + type Error = DispatchError; #[inline] fn poll(&mut self) -> Poll<(), Self::Error> { diff --git a/src/h1/mod.rs b/src/h1/mod.rs new file mode 100644 index 00000000..245f2fc2 --- /dev/null +++ b/src/h1/mod.rs @@ -0,0 +1,9 @@ +//! HTTP/1 implementation +mod codec; +mod decoder; +mod dispatcher; +mod service; + +pub use self::codec::Codec; +pub use self::dispatcher::Dispatcher; +pub use self::service::{H1Service, H1ServiceHandler}; diff --git a/src/h1/service.rs b/src/h1/service.rs new file mode 100644 index 00000000..3017a3ef --- /dev/null +++ b/src/h1/service.rs @@ -0,0 +1,125 @@ +use std::fmt::{Debug, Display}; +use std::marker::PhantomData; +use std::time::Duration; + +use actix_net::service::{IntoNewService, NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Future, Poll}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use config::ServiceConfig; +use error::DispatchError; +use httpresponse::HttpResponse; +use request::Request; + +use super::dispatcher::Dispatcher; + +/// `NewService` implementation for HTTP1 transport +pub struct H1Service { + srv: S, + cfg: ServiceConfig, + _t: PhantomData, +} + +impl H1Service +where + S: NewService, +{ + /// Create new `HttpService` instance. + pub fn new>(cfg: ServiceConfig, service: F) -> Self { + H1Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } +} + +impl NewService for H1Service +where + T: AsyncRead + AsyncWrite, + S: NewService + Clone, + S::Service: Clone, + S::Error: Debug + Display, +{ + type Request = T; + type Response = (); + type Error = DispatchError; + type InitError = S::InitError; + type Service = H1ServiceHandler; + type Future = H1ServiceResponse; + + fn new_service(&self) -> Self::Future { + H1ServiceResponse { + fut: self.srv.new_service(), + cfg: Some(self.cfg.clone()), + _t: PhantomData, + } + } +} + +pub struct H1ServiceResponse { + fut: S::Future, + cfg: Option, + _t: PhantomData, +} + +impl Future for H1ServiceResponse +where + T: AsyncRead + AsyncWrite, + S: NewService, + S::Service: Clone, + S::Error: Debug + Display, +{ + type Item = H1ServiceHandler; + type Error = S::InitError; + + fn poll(&mut self) -> Poll { + let service = try_ready!(self.fut.poll()); + Ok(Async::Ready(H1ServiceHandler::new( + self.cfg.take().unwrap(), + service, + ))) + } +} + +/// `Service` implementation for HTTP1 transport +pub struct H1ServiceHandler { + srv: S, + cfg: ServiceConfig, + _t: PhantomData, +} + +impl H1ServiceHandler +where + S: Service + Clone, + S::Error: Debug + Display, +{ + fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { + H1ServiceHandler { + srv, + cfg, + _t: PhantomData, + } + } +} + +impl Service for H1ServiceHandler +where + T: AsyncRead + AsyncWrite, + S: Service + Clone, + S::Error: Debug + Display, +{ + type Request = T; + type Response = (); + type Error = DispatchError; + type Future = Dispatcher; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.srv.poll_ready().map_err(|e| DispatchError::Service(e)) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + Dispatcher::new(req, self.srv.clone()) + } +} diff --git a/src/server/helpers.rs b/src/helpers.rs similarity index 100% rename from src/server/helpers.rs rename to src/helpers.rs diff --git a/src/lib.rs b/src/lib.rs index efd56618..ec86f032 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,7 @@ extern crate actix_net; extern crate serde_derive; mod body; +mod config; mod extensions; mod header; mod httpcodes; @@ -142,9 +143,12 @@ mod httpmessage; mod httpresponse; mod json; mod payload; +mod request; mod uri; pub mod error; +pub mod h1; +pub(crate) mod helpers; pub mod server; //pub mod test; //pub mod ws; @@ -152,10 +156,11 @@ pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; -//pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use json::Json; -pub use server::Request; +pub use request::Request; + +pub use self::config::{ServiceConfig, ServiceConfigBuilder}; pub mod dev { //! The `actix-web` prelude for library developers diff --git a/src/server/message.rs b/src/request.rs similarity index 87% rename from src/server/message.rs rename to src/request.rs index c39302ba..a75fda3a 100644 --- a/src/server/message.rs +++ b/src/request.rs @@ -30,7 +30,6 @@ pub(crate) struct InnerRequest { pub(crate) flags: Cell, pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, - pub(crate) addr: Option, pub(crate) payload: RefCell>, pub(crate) stream_extensions: Option>, pool: &'static RequestPool, @@ -65,8 +64,13 @@ impl HttpMessage for Request { } impl Request { - /// Create new RequestContext instance - pub(crate) fn new(pool: &'static RequestPool) -> Request { + /// Create new Request instance + pub fn new() -> Request { + Request::with_pool(RequestPool::pool()) + } + + /// Create new Request instance with pool + pub(crate) fn with_pool(pool: &'static RequestPool) -> Request { Request { inner: Rc::new(InnerRequest { pool, @@ -75,7 +79,6 @@ impl Request { version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), flags: Cell::new(MessageFlags::empty()), - addr: None, payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), stream_extensions: None, @@ -134,14 +137,6 @@ impl Request { &mut self.inner_mut().headers } - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - pub fn peer_addr(&self) -> Option { - self.inner().addr - } - /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { @@ -170,12 +165,6 @@ impl Request { self.inner().method == Method::CONNECT } - /// Io stream extensions - #[inline] - pub fn stream_extensions(&self) -> Option<&Extensions> { - self.inner().stream_extensions.as_ref().map(|e| e.as_ref()) - } - pub(crate) fn clone(&self) -> Self { Request { inner: self.inner.clone(), @@ -213,7 +202,8 @@ impl fmt::Debug for Request { } } -pub struct RequestPool(RefCell>>); +/// Request's objects pool +pub(crate) struct RequestPool(RefCell>>); thread_local!(static POOL: &'static RequestPool = RequestPool::create()); @@ -223,16 +213,18 @@ impl RequestPool { Box::leak(Box::new(pool)) } - pub(crate) fn pool() -> &'static RequestPool { + /// Get default request's pool + pub fn pool() -> &'static RequestPool { POOL.with(|p| *p) } + /// Get Request object #[inline] pub fn get(pool: &'static RequestPool) -> Request { if let Some(msg) = pool.0.borrow_mut().pop_front() { Request { inner: msg } } else { - Request::new(pool) + Request::with_pool(pool) } } diff --git a/src/server/error.rs b/src/server/error.rs deleted file mode 100644 index 7d5c67d1..00000000 --- a/src/server/error.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::fmt::{Debug, Display}; -use std::io; - -use futures::{Async, Poll}; - -use error::{Error, ParseError}; -use http::{StatusCode, Version}; - -/// Errors produced by `AcceptorError` service. -#[derive(Debug)] -pub enum AcceptorError { - /// The inner service error - Service(T), - - /// Io specific error - Io(io::Error), - - /// The request did not complete within the specified timeout. - Timeout, -} - -#[derive(Debug)] -/// A set of errors that can occur during dispatching http requests -pub enum HttpDispatchError { - /// Application error - // #[fail(display = "Application specific error: {}", _0)] - App(E), - - /// An `io::Error` that occurred while trying to read or write to a network - /// stream. - // #[fail(display = "IO error: {}", _0)] - Io(io::Error), - - /// Http request parse error. - // #[fail(display = "Parse error: {}", _0)] - Parse(ParseError), - - /// The first request did not complete within the specified timeout. - // #[fail(display = "The first request did not complete within the specified timeout")] - SlowRequestTimeout, - - /// Shutdown timeout - // #[fail(display = "Connection shutdown timeout")] - ShutdownTimeout, - - /// Payload is not consumed - // #[fail(display = "Task is completed but request's payload is not consumed")] - PayloadIsNotConsumed, - - /// Malformed request - // #[fail(display = "Malformed request")] - MalformedRequest, - - /// Internal error - // #[fail(display = "Internal error")] - InternalError, - - /// Unknown error - // #[fail(display = "Unknown error")] - Unknown, -} - -impl From for HttpDispatchError { - fn from(err: ParseError) -> Self { - HttpDispatchError::Parse(err) - } -} - -impl From for HttpDispatchError { - fn from(err: io::Error) -> Self { - HttpDispatchError::Io(err) - } -} diff --git a/src/server/h1.rs b/src/server/h1.rs deleted file mode 100644 index e2b4bf45..00000000 --- a/src/server/h1.rs +++ /dev/null @@ -1,1289 +0,0 @@ -use std::collections::VecDeque; -use std::net::{Shutdown, SocketAddr}; -use std::time::{Duration, Instant}; - -use bytes::BytesMut; -use futures::{Async, Future, Poll}; -use tokio_current_thread::spawn; -use tokio_timer::Delay; - -use error::{Error, ParseError, PayloadError}; -use http::{StatusCode, Version}; -use payload::{Payload, PayloadStatus, PayloadWriter}; - -use super::error::{HttpDispatchError, ServerError}; -use super::h1decoder::{H1Decoder, Message}; -use super::h1writer::H1Writer; -use super::handler::{HttpHandler, HttpHandlerTask, HttpHandlerTaskFut}; -use super::input::PayloadType; -use super::message::Request; -use super::settings::ServiceConfig; -use super::{IoStream, Writer}; - -const MAX_PIPELINED_MESSAGES: usize = 16; - -bitflags! { - pub struct Flags: u8 { - const STARTED = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const READ_DISCONNECTED = 0b0001_0000; - const WRITE_DISCONNECTED = 0b0010_0000; - const POLLED = 0b0100_0000; - const FLUSHED = 0b1000_0000; - } -} - -/// Dispatcher for HTTP/1.1 protocol -pub struct Http1Dispatcher { - flags: Flags, - settings: ServiceConfig, - addr: Option, - stream: H1Writer, - decoder: H1Decoder, - payload: Option, - buf: BytesMut, - tasks: VecDeque>, - error: Option>, - ka_expire: Instant, - ka_timer: Option, -} - -enum Entry { - Task(H::Task), - Error(Box), -} - -impl Entry { - fn into_task(self) -> H::Task { - match self { - Entry::Task(task) => task, - Entry::Error(_) => panic!(), - } - } - fn disconnected(&mut self) { - match *self { - Entry::Task(ref mut task) => task.disconnected(), - Entry::Error(ref mut task) => task.disconnected(), - } - } - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match *self { - Entry::Task(ref mut task) => task.poll_io(io), - Entry::Error(ref mut task) => task.poll_io(io), - } - } - fn poll_completed(&mut self) -> Poll<(), Error> { - match *self { - Entry::Task(ref mut task) => task.poll_completed(), - Entry::Error(ref mut task) => task.poll_completed(), - } - } -} - -impl Http1Dispatcher -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub fn new( - settings: ServiceConfig, stream: T, buf: BytesMut, is_eof: bool, - keepalive_timer: Option, - ) -> Self { - let addr = stream.peer_addr(); - let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = settings.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (settings.now(), None) - }; - - let flags = if is_eof { - Flags::READ_DISCONNECTED | Flags::FLUSHED - } else if settings.keep_alive_enabled() { - Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED - } else { - Flags::empty() - }; - - Http1Dispatcher { - stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(settings.request_pool()), - payload: None, - tasks: VecDeque::new(), - error: None, - flags, - addr, - buf, - settings, - ka_timer, - ka_expire, - } - } - - pub(crate) fn for_error( - settings: ServiceConfig, stream: T, status: StatusCode, - mut keepalive_timer: Option, buf: BytesMut, - ) -> Self { - if let Some(deadline) = settings.client_timer_expire() { - let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline)); - } - - let mut disp = Http1Dispatcher { - flags: Flags::STARTED | Flags::READ_DISCONNECTED | Flags::FLUSHED, - stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(settings.request_pool()), - payload: None, - tasks: VecDeque::new(), - error: None, - addr: None, - ka_timer: keepalive_timer, - ka_expire: settings.now(), - buf, - settings, - }; - disp.push_response_entry(status); - disp - } - - #[inline] - pub fn settings(&self) -> &ServiceConfig { - &self.settings - } - - #[inline] - pub(crate) fn io(&mut self) -> &mut T { - self.stream.get_mut() - } - - #[inline] - fn can_read(&self) -> bool { - if self.flags.contains(Flags::READ_DISCONNECTED) { - return false; - } - - if let Some(ref info) = self.payload { - info.need_read() == PayloadStatus::Read - } else { - true - } - } - - // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(&mut self, checked: bool) { - self.flags.insert(Flags::READ_DISCONNECTED); - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); - } - - if !checked || self.tasks.is_empty() { - self.flags - .insert(Flags::WRITE_DISCONNECTED | Flags::FLUSHED); - self.stream.disconnected(); - - // notify all tasks - for mut task in self.tasks.drain(..) { - task.disconnected(); - match task.poll_completed() { - Ok(Async::NotReady) => { - // spawn not completed task, it does not require access to io - // at this point - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - Ok(Async::Ready(_)) => (), - Err(err) => { - error!("Unhandled application error: {}", err); - } - } - } - } - } - - #[inline] - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { - // check connection keep-alive - self.poll_keep_alive()?; - - // shutdown - if self.flags.contains(Flags::SHUTDOWN) { - if self.flags.contains(Flags::WRITE_DISCONNECTED) { - return Ok(Async::Ready(())); - } - return self.poll_flush(true); - } - - // process incoming requests - if !self.flags.contains(Flags::WRITE_DISCONNECTED) { - self.poll_handler()?; - - // flush stream - self.poll_flush(false)?; - - // deal with keep-alive and stream eof (client-side write shutdown) - if self.tasks.is_empty() && self.flags.contains(Flags::FLUSHED) { - // handle stream eof - if self - .flags - .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) - { - return Ok(Async::Ready(())); - } - // no keep-alive - if self.flags.contains(Flags::STARTED) - && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) - || !self.flags.contains(Flags::KEEPALIVE)) - { - self.flags.insert(Flags::SHUTDOWN); - return self.poll(); - } - } - Ok(Async::NotReady) - } else if let Some(err) = self.error.take() { - Err(err) - } else { - Ok(Async::Ready(())) - } - } - - /// Flush stream - fn poll_flush(&mut self, shutdown: bool) -> Poll<(), HttpDispatchError> { - if shutdown || self.flags.contains(Flags::STARTED) { - match self.stream.poll_completed(shutdown) { - Ok(Async::NotReady) => { - // mark stream - if !self.stream.flushed() { - self.flags.remove(Flags::FLUSHED); - } - Ok(Async::NotReady) - } - Err(err) => { - debug!("Error sending data: {}", err); - self.client_disconnected(false); - Err(err.into()) - } - Ok(Async::Ready(_)) => { - // if payload is not consumed we can not use connection - if self.payload.is_some() && self.tasks.is_empty() { - return Err(HttpDispatchError::PayloadIsNotConsumed); - } - self.flags.insert(Flags::FLUSHED); - Ok(Async::Ready(())) - } - } - } else { - Ok(Async::Ready(())) - } - } - - /// keep-alive timer. returns `true` is keep-alive, otherwise drop - fn poll_keep_alive(&mut self) -> Result<(), HttpDispatchError> { - if let Some(ref mut timer) = self.ka_timer { - match timer.poll() { - Ok(Async::Ready(_)) => { - if timer.deadline() >= self.ka_expire { - // check for any outstanding request handling - if self.tasks.is_empty() { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - let io = self.stream.get_mut(); - let _ = IoStream::set_linger( - io, - Some(Duration::from_secs(0)), - ); - let _ = IoStream::shutdown(io, Shutdown::Both); - return Err(HttpDispatchError::ShutdownTimeout); - } else if !self.flags.contains(Flags::STARTED) { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags - .insert(Flags::STARTED | Flags::READ_DISCONNECTED); - self.tasks.push_back(Entry::Error(ServerError::err( - Version::HTTP_11, - StatusCode::REQUEST_TIMEOUT, - ))); - } else { - trace!("Keep-alive timeout, close connection"); - self.flags.insert(Flags::SHUTDOWN); - - // start shutdown timer - if let Some(deadline) = - self.settings.client_shutdown_timer() - { - timer.reset(deadline) - } else { - return Ok(()); - } - } - } else if let Some(deadline) = self.settings.keep_alive_expire() - { - timer.reset(deadline) - } - } else { - timer.reset(self.ka_expire) - } - } - Ok(Async::NotReady) => (), - Err(e) => { - error!("Timer error {:?}", e); - return Err(HttpDispatchError::Unknown); - } - } - } - - Ok(()) - } - - #[inline] - /// read data from the stream - pub(self) fn poll_io(&mut self) -> Result> { - if !self.flags.contains(Flags::POLLED) { - self.flags.insert(Flags::POLLED); - if !self.buf.is_empty() { - let updated = self.parse()?; - return Ok(updated); - } - } - - // read io from socket - let mut updated = false; - if self.can_read() && self.tasks.len() < MAX_PIPELINED_MESSAGES { - match self.stream.get_mut().read_available(&mut self.buf) { - Ok(Async::Ready((read_some, disconnected))) => { - if read_some && self.parse()? { - updated = true; - } - if disconnected { - self.client_disconnected(true); - } - } - Ok(Async::NotReady) => (), - Err(err) => { - self.client_disconnected(false); - return Err(err.into()); - } - } - } - Ok(updated) - } - - pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { - self.poll_io()?; - let mut retry = self.can_read(); - - // process first pipelined response, only first task can do io operation in http/1 - while !self.tasks.is_empty() { - match self.tasks[0].poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - let task = self.tasks.pop_front().unwrap(); - if !ready { - // task is done with io operations but still needs to do more work - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - } - Ok(Async::NotReady) => { - // check if we need timer - if self.ka_timer.is_some() && self.stream.upgrade() { - self.ka_timer.take(); - } - - // if read-backpressure is enabled and we consumed some data. - // we may read more dataand retry - if !retry && self.can_read() && self.poll_io()? { - retry = self.can_read(); - continue; - } - break; - } - Err(err) => { - error!("Unhandled error1: {}", err); - // it is not possible to recover from error - // during pipe handling, so just drop connection - self.client_disconnected(false); - return Err(HttpDispatchError::App(err)); - } - } - } - - // check in-flight messages. all tasks must be alive, - // they need to produce response. if app returned error - // and we can not continue processing incoming requests. - let mut idx = 1; - while idx < self.tasks.len() { - let stop = match self.tasks[idx].poll_completed() { - Ok(Async::NotReady) => false, - Ok(Async::Ready(_)) => true, - Err(err) => { - self.error = Some(HttpDispatchError::App(err)); - true - } - }; - if stop { - // error in task handling or task is completed, - // so no response for this task which means we can not read more requests - // because pipeline sequence is broken. - // but we can safely complete existing tasks - self.flags.insert(Flags::READ_DISCONNECTED); - - for mut task in self.tasks.drain(idx..) { - task.disconnected(); - match task.poll_completed() { - Ok(Async::NotReady) => { - // spawn not completed task, it does not require access to io - // at this point - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - Ok(Async::Ready(_)) => (), - Err(err) => { - error!("Unhandled application error: {}", err); - } - } - } - break; - } else { - idx += 1; - } - } - - Ok(()) - } - - fn push_response_entry(&mut self, status: StatusCode) { - self.tasks - .push_back(Entry::Error(ServerError::err(Version::HTTP_11, status))); - } - - fn handle_message( - &mut self, mut msg: Request, payload: bool, - ) -> Result<(), HttpDispatchError> { - self.flags.insert(Flags::STARTED); - - if payload { - let (ps, pl) = Payload::new(false); - *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); - } - - // stream extensions - msg.inner_mut().stream_extensions = self.stream.get_mut().extensions(); - - // set remote addr - msg.inner_mut().addr = self.addr; - - // search handler for request - match self.settings.handler().handle(msg) { - Ok(mut task) => { - if self.tasks.is_empty() { - match task.poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - if !ready { - // task is done with io operations - // but still needs to do more work - spawn(HttpHandlerTaskFut::new(task)); - } - } - Ok(Async::NotReady) => (), - Err(err) => { - error!("Unhandled error: {}", err); - self.client_disconnected(false); - return Err(HttpDispatchError::App(err)); - } - } - } else { - self.tasks.push_back(Entry::Task(task)); - } - } - Err(_) => { - // handler is not found - self.push_response_entry(StatusCode::NOT_FOUND); - } - } - Ok(()) - } - - pub(self) fn parse(&mut self) -> Result> { - let mut updated = false; - - 'outer: loop { - match self.decoder.decode(&mut self.buf) { - Ok(Some(Message::Message(msg))) => { - updated = true; - self.handle_message(msg, false)?; - } - Ok(Some(Message::MessageWithPayload(msg))) => { - updated = true; - self.handle_message(msg, true)?; - } - Ok(Some(Message::Chunk(chunk))) => { - updated = true; - if let Some(ref mut payload) = self.payload { - payload.feed_data(chunk); - } else { - error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); - break; - } - } - Ok(Some(Message::Eof)) => { - updated = true; - if let Some(mut payload) = self.payload.take() { - payload.feed_eof(); - } else { - error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); - break; - } - } - Ok(None) => { - if self.flags.contains(Flags::READ_DISCONNECTED) { - self.client_disconnected(true); - } - break; - } - Err(e) => { - if let Some(mut payload) = self.payload.take() { - let e = match e { - ParseError::Io(e) => PayloadError::Io(e), - _ => PayloadError::EncodingCorrupted, - }; - payload.set_error(e); - } - - // Malformed requests should be responded with 400 - self.push_response_entry(StatusCode::BAD_REQUEST); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.error = Some(HttpDispatchError::MalformedRequest); - break; - } - } - } - - if self.ka_timer.is_some() && updated { - if let Some(expire) = self.settings.keep_alive_expire() { - self.ka_expire = expire; - } - } - Ok(updated) - } -} - -#[cfg(test)] -mod tests { - use std::net::Shutdown; - use std::{cmp, io, time}; - - use actix::System; - use bytes::{Buf, Bytes, BytesMut}; - use futures::future; - use http::{Method, Version}; - use tokio_io::{AsyncRead, AsyncWrite}; - - use super::*; - use application::{App, HttpApplication}; - use error::ParseError; - use httpmessage::HttpMessage; - use server::h1decoder::Message; - use server::handler::IntoHttpHandler; - use server::settings::{ServerSettings, ServiceConfig}; - use server::{KeepAlive, Request}; - - fn wrk_settings() -> ServiceConfig { - ServiceConfig::::new( - App::new().into_handler(), - KeepAlive::Os, - 5000, - 2000, - ServerSettings::default(), - ) - } - - impl Message { - fn message(self) -> Request { - match self { - Message::Message(msg) => msg, - Message::MessageWithPayload(msg) => msg, - _ => panic!("error"), - } - } - fn is_payload(&self) -> bool { - match *self { - Message::MessageWithPayload(_) => true, - _ => panic!("error"), - } - } - fn chunk(self) -> Bytes { - match self { - Message::Chunk(chunk) => chunk, - _ => panic!("error"), - } - } - fn eof(&self) -> bool { - match *self { - Message::Eof => true, - _ => false, - } - } - } - - macro_rules! parse_ready { - ($e:expr) => {{ - let settings = wrk_settings(); - match H1Decoder::new(settings.request_pool()).decode($e) { - Ok(Some(msg)) => msg.message(), - Ok(_) => unreachable!("Eof during parsing http request"), - Err(err) => unreachable!("Error during parsing http request: {:?}", err), - } - }}; - } - - macro_rules! expect_parse_err { - ($e:expr) => {{ - let settings = wrk_settings(); - - match H1Decoder::new(settings.request_pool()).decode($e) { - Err(err) => match err { - ParseError::Io(_) => unreachable!("Parse error expected"), - _ => (), - }, - _ => unreachable!("Error expected"), - } - }}; - } - - struct Buffer { - buf: Bytes, - err: Option, - } - - impl Buffer { - fn new(data: &'static str) -> Buffer { - Buffer { - buf: Bytes::from(data), - err: None, - } - } - } - - impl AsyncRead for Buffer {} - impl io::Read for Buffer { - fn read(&mut self, dst: &mut [u8]) -> Result { - if self.buf.is_empty() { - if self.err.is_some() { - Err(self.err.take().unwrap()) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - let size = cmp::min(self.buf.len(), dst.len()); - let b = self.buf.split_to(size); - dst[..size].copy_from_slice(&b); - Ok(size) - } - } - } - - impl IoStream for Buffer { - fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { - Ok(()) - } - fn set_nodelay(&mut self, _: bool) -> io::Result<()> { - Ok(()) - } - fn set_linger(&mut self, _: Option) -> io::Result<()> { - Ok(()) - } - fn set_keepalive(&mut 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) - } - } - - #[test] - fn test_req_parse_err() { - let mut sys = System::new("test"); - let _ = sys.block_on(future::lazy(|| { - let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - let readbuf = BytesMut::new(); - let settings = wrk_settings(); - - let mut h1 = - Http1Dispatcher::new(settings.clone(), buf, readbuf, false, None); - assert!(h1.poll_io().is_ok()); - assert!(h1.poll_io().is_ok()); - assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); - assert_eq!(h1.tasks.len(), 1); - future::ok::<_, ()>(()) - })); - } - - #[test] - fn test_parse() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_partial() { - let mut buf = BytesMut::from("PUT /test HTTP/1"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(None) => (), - _ => unreachable!("Error"), - } - - buf.extend(b".1\r\n\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::PUT); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_post() { - let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_10); - assert_eq!(*req.method(), Method::POST); - assert_eq!(req.path(), "/test2"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_body() { - let mut buf = - BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_body_crlf() { - let mut buf = - BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_partial_eof() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(settings.request_pool()); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_headers_split_field() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - assert!{ reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"t"); - assert!{ reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"es"); - assert!{ reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"t: value\r\n\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_headers_multi_value() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - Set-Cookie: c1=cookie1\r\n\ - Set-Cookie: c2=cookie2\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - let req = msg.message(); - - let val: Vec<_> = req - .headers() - .get_all("Set-Cookie") - .iter() - .map(|v| v.to_str().unwrap().to_owned()) - .collect(); - assert_eq!(val[0], "c1=cookie1"); - assert_eq!(val[1], "c2=cookie2"); - } - - #[test] - fn test_conn_default_1_0() { - let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_default_1_1() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_close() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_close_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_keep_alive_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_keep_alive_1_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_other_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_other_1_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_upgrade() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - upgrade: websockets\r\n\ - connection: upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - } - - #[test] - fn test_conn_upgrade_connect_method() { - let mut buf = BytesMut::from( - "CONNECT /test HTTP/1.1\r\n\ - content-type: text/plain\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - } - - #[test] - fn test_request_chunked() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(val); - } else { - unreachable!("Error"); - } - - // type in chunked - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chnked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(!val); - } else { - unreachable!("Error"); - } - } - - #[test] - fn test_headers_content_length_err_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - content-length: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf) - } - - #[test] - fn test_headers_content_length_err_2() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - content-length: -1\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_invalid_header() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - test line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_invalid_name() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - test[]: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_bad_status_line() { - let mut buf = BytesMut::from("getpath \r\n\r\n"); - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_upgrade() { - let settings = wrk_settings(); - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: upgrade\r\n\ - upgrade: websocket\r\n\r\n\ - some raw data", - ); - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(!req.keep_alive()); - assert!(req.upgrade()); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"some raw data" - ); - } - - #[test] - fn test_http_request_parser_utf8() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - x-test: теÑÑ‚\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!( - req.headers().get("x-test").unwrap().as_bytes(), - "теÑÑ‚".as_bytes() - ); - } - - #[test] - fn test_http_request_parser_two_slashes() { - let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert_eq!(req.path(), "//path"); - } - - #[test] - fn test_http_request_parser_bad_method() { - let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_parser_bad_version() { - let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_chunked_payload() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"data" - ); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"line" - ); - assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); - } - - #[test] - fn test_http_request_chunked_payload_and_next_message() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend( - b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ - POST /test2 HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n" - .iter(), - ); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req2 = msg.message(); - assert!(req2.chunked().unwrap()); - assert_eq!(*req2.method(), Method::POST); - assert!(req2.chunked().unwrap()); - } - - #[test] - fn test_http_request_chunked_payload_chunks() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\n1111\r\n"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"1111"); - - buf.extend(b"4\r\ndata\r"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - - buf.extend(b"\n4"); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r"); - assert!(reader.decode(&mut buf).unwrap().is_none()); - buf.extend(b"\n"); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"li"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"li"); - - //trailers - //buf.feed_data("test: test\r\n"); - //not_ready!(reader.parse(&mut buf, &mut readbuf)); - - buf.extend(b"ne\r\n0\r\n"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r\n"); - assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); - } - - #[test] - fn test_parse_chunked_payload_chunk_extension() { - let mut buf = BytesMut::from( - &"GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"[..], - ); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(settings.request_pool()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - assert!(msg.message().chunked().unwrap()); - - buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index be172e64..068094c2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -117,30 +117,11 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; -mod error; -// pub(crate) mod h1; -#[doc(hidden)] -pub mod h1codec; -#[doc(hidden)] -pub mod h1decoder; -pub(crate) mod helpers; pub(crate) mod input; -pub(crate) mod message; pub(crate) mod output; -// pub(crate) mod service; -pub(crate) mod settings; - -pub use self::error::{AcceptorError, HttpDispatchError}; -pub use self::message::Request; #[doc(hidden)] -pub mod h1disp; - -#[doc(hidden)] -pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; - -#[doc(hidden)] -pub use self::helpers::write_content_length; +pub use super::helpers::write_content_length; use body::Binary; use extensions::Extensions; diff --git a/src/server/output.rs b/src/server/output.rs index 143ba402..f20bd326 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -13,10 +13,10 @@ use flate2::Compression; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; -use super::message::InnerRequest; use body::{Binary, Body}; use header::ContentEncoding; use httpresponse::HttpResponse; +use request::InnerRequest; // #[derive(Debug)] // pub(crate) struct RequestInfo { diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index 77b6d202..e32481bc 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -12,9 +12,8 @@ use actix_net::service::{IntoNewService, IntoService}; use actix_web::{client, test}; use futures::future; -use actix_http::server::h1disp::Http1Dispatcher; -use actix_http::server::{KeepAlive, ServiceConfig}; -use actix_http::{Error, HttpResponse}; +use actix_http::server::KeepAlive; +use actix_http::{h1, Error, HttpResponse, ServiceConfig}; #[test] fn test_h1_v2() { @@ -30,17 +29,10 @@ fn test_h1_v2() { .server_address(addr) .finish(); - (move |io| { - let pool = settings.request_pool(); - Http1Dispatcher::new( - io, - pool, - (|req| { - println!("REQ: {:?}", req); - future::ok::<_, Error>(HttpResponse::Ok().finish()) - }).into_service(), - ) - }).into_new_service() + h1::H1Service::new(settings, |req| { + println!("REQ: {:?}", req); + future::ok::<_, Error>(HttpResponse::Ok().finish()) + }) }).unwrap() .run(); }); From 829dbae609205d9245c927c6234d346957a1f94f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 21:14:18 -0700 Subject: [PATCH 0717/1635] cleanups and tests --- Cargo.toml | 2 +- src/body.rs | 1 - src/config.rs | 17 +- src/error.rs | 38 +- src/h1/codec.rs | 5 +- src/h1/decoder.rs | 666 ++++++++++++++++++++- src/h1/dispatcher.rs | 14 +- src/h1/mod.rs | 2 +- src/h1/service.rs | 4 +- src/header/common/accept.rs | 18 +- src/header/common/accept_charset.rs | 18 +- src/header/common/accept_language.rs | 12 +- src/header/common/allow.rs | 19 +- src/header/common/cache_control.rs | 10 +- src/header/common/content_disposition.rs | 9 +- src/header/common/content_language.rs | 12 +- src/header/common/content_type.rs | 10 +- src/header/common/date.rs | 4 +- src/header/common/etag.rs | 8 +- src/header/common/expires.rs | 4 +- src/header/common/if_match.rs | 8 +- src/header/common/if_modified_since.rs | 4 +- src/header/common/if_none_match.rs | 8 +- src/header/common/if_range.rs | 14 +- src/header/common/if_unmodified_since.rs | 4 +- src/header/common/last_modified.rs | 4 +- src/httpmessage.rs | 6 +- src/httpresponse.rs | 121 ++-- src/json.rs | 47 +- src/lib.rs | 29 +- src/request.rs | 25 +- src/server/mod.rs | 17 +- src/server/output.rs | 1 + src/test.rs | 712 +++++++---------------- tests/test_h1v2.rs | 1 - 35 files changed, 1063 insertions(+), 811 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 60f14485..86012175 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,7 @@ bytes = "0.4" byteorder = "1.2" futures = "0.1" tokio-codec = "0.1" +tokio = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" @@ -123,7 +124,6 @@ tokio-uds = { version="0.2", optional = true } actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" -tokio = "0.1" [build-dependencies] version_check = "0.1" diff --git a/src/body.rs b/src/body.rs index e689b704..db06bef2 100644 --- a/src/body.rs +++ b/src/body.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use std::{fmt, mem}; use error::Error; -use httpresponse::HttpResponse; /// Type represent streaming body pub type BodyStream = Box>; diff --git a/src/config.rs b/src/config.rs index 508cd5dd..543e78ac 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,20 +1,15 @@ -use std::cell::{RefCell, RefMut, UnsafeCell}; -use std::collections::VecDeque; +use std::cell::UnsafeCell; use std::fmt::Write; use std::rc::Rc; use std::time::{Duration, Instant}; -use std::{env, fmt, net}; +use std::{fmt, net}; use bytes::BytesMut; use futures::{future, Future}; -use http::StatusCode; use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use body::Body; -use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; -use request::{Request, RequestPool}; use server::KeepAlive; // "Sun, 06 Nov 1994 08:49:37 GMT".len() @@ -336,13 +331,7 @@ mod tests { let mut rt = current_thread::Runtime::new().unwrap(); let _ = rt.block_on(future::lazy(|| { - let settings = ServiceConfig::<()>::new( - (), - KeepAlive::Os, - 0, - 0, - ServerSettings::default(), - ); + let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf1, true); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); diff --git a/src/error.rs b/src/error.rs index e39dea9b..21aabac4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,7 +28,7 @@ use httpresponse::{HttpResponse, HttpResponseParts}; /// for actix web operations /// /// This typedef is generally used to avoid writing out -/// `actix_web::error::Error` directly and is otherwise a direct mapping to +/// `actix_http::error::Error` directly and is otherwise a direct mapping to /// `Result`. pub type Result = result::Result; @@ -589,13 +589,12 @@ impl From for UrlGenerationError { /// default. /// /// ```rust -/// # extern crate actix_web; -/// # use actix_web::*; -/// use actix_web::fs::NamedFile; +/// # extern crate actix_http; +/// # use std::io; +/// # use actix_http::*; /// -/// fn index(req: HttpRequest) -> Result { -/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; -/// Ok(f) +/// fn index(req: Request) -> Result<&'static str> { +/// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error"))) /// } /// # fn main() {} /// ``` @@ -837,14 +836,6 @@ mod tests { use std::error::Error as StdError; use std::io; - #[test] - #[cfg(actix_nightly)] - fn test_nightly() { - let resp: HttpResponse = - IoError::new(io::ErrorKind::Other, "test").error_response(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - #[test] fn test_into_response() { let resp: HttpResponse = ParseError::Incomplete.error_response(); @@ -853,9 +844,6 @@ mod tests { let resp: HttpResponse = CookieParseError::EmptyName.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = MultipartError::Boundary.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); let resp: HttpResponse = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -899,14 +887,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[test] - fn test_expect_error() { - let resp: HttpResponse = ExpectError::Encoding.error_response(); - assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); - let resp: HttpResponse = ExpectError::UnknownExpect.error_response(); - assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); - } - macro_rules! from { ($from:expr => $error:pat) => { match ParseError::from($from) { @@ -963,10 +943,8 @@ mod tests { #[test] fn test_internal_error() { - let err = InternalError::from_response( - ExpectError::Encoding, - HttpResponse::Ok().into(), - ); + let err = + InternalError::from_response(ParseError::Method, HttpResponse::Ok().into()); let resp: HttpResponse = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 01188357..f1b526d5 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -15,8 +15,11 @@ use httpresponse::HttpResponse; use request::RequestPool; use server::output::{ResponseInfo, ResponseLength}; +/// Http response pub enum OutMessage { + /// Http response message Response(HttpResponse), + /// Payload chunk Payload(Bytes), } @@ -35,7 +38,7 @@ impl Codec { /// Create HTTP/1 codec with request's pool pub(crate) fn with_pool(pool: &'static RequestPool) -> Self { Codec { - decoder: H1Decoder::new(pool), + decoder: H1Decoder::with_pool(pool), encoder: H1Writer::new(), } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 47cc5fdf..66f24628 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -18,16 +18,26 @@ pub(crate) struct H1Decoder { pool: &'static RequestPool, } +/// Incoming http/1 request #[derive(Debug)] pub enum InMessage { + /// Request Message(Request), + /// Request with payload MessageWithPayload(Request), + /// Payload chunk Chunk(Bytes), + /// End of payload Eof, } impl H1Decoder { - pub fn new(pool: &'static RequestPool) -> H1Decoder { + #[cfg(test)] + pub fn new() -> H1Decoder { + H1Decoder::with_pool(RequestPool::pool()) + } + + pub fn with_pool(pool: &'static RequestPool) -> H1Decoder { H1Decoder { pool, decoder: None, @@ -497,3 +507,657 @@ impl ChunkedState { } } } + +#[cfg(test)] +mod tests { + use std::net::Shutdown; + use std::{cmp, io, time}; + + use actix::System; + use bytes::{Buf, Bytes, BytesMut}; + use futures::{future, future::ok}; + use http::{Method, Version}; + use tokio_io::{AsyncRead, AsyncWrite}; + + use super::*; + use error::ParseError; + use h1::{Dispatcher, InMessage}; + use httpmessage::HttpMessage; + use request::Request; + use server::KeepAlive; + + impl InMessage { + fn message(self) -> Request { + match self { + InMessage::Message(msg) => msg, + InMessage::MessageWithPayload(msg) => msg, + _ => panic!("error"), + } + } + fn is_payload(&self) -> bool { + match *self { + InMessage::MessageWithPayload(_) => true, + _ => panic!("error"), + } + } + fn chunk(self) -> Bytes { + match self { + InMessage::Chunk(chunk) => chunk, + _ => panic!("error"), + } + } + fn eof(&self) -> bool { + match *self { + InMessage::Eof => true, + _ => false, + } + } + } + + macro_rules! parse_ready { + ($e:expr) => {{ + match H1Decoder::new().decode($e) { + Ok(Some(msg)) => msg.message(), + Ok(_) => unreachable!("Eof during parsing http request"), + Err(err) => unreachable!("Error during parsing http request: {:?}", err), + } + }}; + } + + macro_rules! expect_parse_err { + ($e:expr) => {{ + match H1Decoder::new().decode($e) { + Err(err) => match err { + ParseError::Io(_) => unreachable!("Parse error expected"), + _ => (), + }, + _ => unreachable!("Error expected"), + } + }}; + } + + struct Buffer { + buf: Bytes, + err: Option, + } + + impl Buffer { + fn new(data: &'static str) -> Buffer { + Buffer { + buf: Bytes::from(data), + err: None, + } + } + } + + impl AsyncRead for Buffer {} + impl io::Read for Buffer { + fn read(&mut self, dst: &mut [u8]) -> Result { + if self.buf.is_empty() { + if self.err.is_some() { + Err(self.err.take().unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + let size = cmp::min(self.buf.len(), dst.len()); + let b = self.buf.split_to(size); + dst[..size].copy_from_slice(&b); + Ok(size) + } + } + } + + impl io::Write for Buffer { + fn write(&mut self, buf: &[u8]) -> io::Result { + 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) + } + } + + // #[test] + // fn test_req_parse_err() { + // let mut sys = System::new("test"); + // let _ = sys.block_on(future::lazy(|| { + // let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); + // let readbuf = BytesMut::new(); + + // let mut h1 = Dispatcher::new(buf, |req| ok(HttpResponse::Ok().finish())); + // assert!(h1.poll_io().is_ok()); + // assert!(h1.poll_io().is_ok()); + // assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); + // assert_eq!(h1.tasks.len(), 1); + // future::ok::<_, ()>(()) + // })); + // } + + #[test] + fn test_parse() { + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_partial() { + let mut buf = BytesMut::from("PUT /test HTTP/1"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(None) => (), + _ => unreachable!("Error"), + } + + buf.extend(b".1\r\n\r\n"); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let mut req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::PUT); + assert_eq!(req.path(), "/test"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_post() { + let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let mut req = msg.message(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::POST); + assert_eq!(req.path(), "/test2"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_body() { + let mut buf = + BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let mut req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"body" + ); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_body_crlf() { + let mut buf = + BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); + + let mut reader = H1Decoder::new(); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let mut req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"body" + ); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_parse_partial_eof() { + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); + let mut reader = H1Decoder::new(); + assert!(reader.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"\r\n"); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_headers_split_field() { + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); + + let mut reader = H1Decoder::new(); + assert!{ reader.decode(&mut buf).unwrap().is_none() } + + buf.extend(b"t"); + assert!{ reader.decode(&mut buf).unwrap().is_none() } + + buf.extend(b"es"); + assert!{ reader.decode(&mut buf).unwrap().is_none() } + + buf.extend(b"t: value\r\n\r\n"); + match reader.decode(&mut buf) { + Ok(Some(msg)) => { + let req = msg.message(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); + } + Ok(_) | Err(_) => unreachable!("Error during parsing http request"), + } + } + + #[test] + fn test_headers_multi_value() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + Set-Cookie: c1=cookie1\r\n\ + Set-Cookie: c2=cookie2\r\n\r\n", + ); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + let req = msg.message(); + + let val: Vec<_> = req + .headers() + .get_all("Set-Cookie") + .iter() + .map(|v| v.to_str().unwrap().to_owned()) + .collect(); + assert_eq!(val[0], "c1=cookie1"); + assert_eq!(val[1], "c2=cookie2"); + } + + #[test] + fn test_conn_default_1_0() { + let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); + } + + #[test] + fn test_conn_default_1_1() { + let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + } + + #[test] + fn test_conn_close() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: close\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); + } + + #[test] + fn test_conn_close_1_0() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: close\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); + } + + #[test] + fn test_conn_keep_alive_1_0() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: keep-alive\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + } + + #[test] + fn test_conn_keep_alive_1_1() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: keep-alive\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + } + + #[test] + fn test_conn_other_1_0() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: other\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); + } + + #[test] + fn test_conn_other_1_1() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: other\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + } + + #[test] + fn test_conn_upgrade() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + upgrade: websockets\r\n\ + connection: upgrade\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.upgrade()); + } + + #[test] + fn test_conn_upgrade_connect_method() { + let mut buf = BytesMut::from( + "CONNECT /test HTTP/1.1\r\n\ + content-type: text/plain\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.upgrade()); + } + + #[test] + fn test_request_chunked() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + if let Ok(val) = req.chunked() { + assert!(val); + } else { + unreachable!("Error"); + } + + // type in chunked + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chnked\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + if let Ok(val) = req.chunked() { + assert!(!val); + } else { + unreachable!("Error"); + } + } + + #[test] + fn test_headers_content_length_err_1() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + content-length: line\r\n\r\n", + ); + + expect_parse_err!(&mut buf) + } + + #[test] + fn test_headers_content_length_err_2() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + content-length: -1\r\n\r\n", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_invalid_header() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + test line\r\n\r\n", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_invalid_name() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + test[]: line\r\n\r\n", + ); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_bad_status_line() { + let mut buf = BytesMut::from("getpath \r\n\r\n"); + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_upgrade() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: upgrade\r\n\ + upgrade: websocket\r\n\r\n\ + some raw data", + ); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = msg.message(); + assert!(!req.keep_alive()); + assert!(req.upgrade()); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"some raw data" + ); + } + + #[test] + fn test_http_request_parser_utf8() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + x-test: теÑÑ‚\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert_eq!( + req.headers().get("x-test").unwrap().as_bytes(), + "теÑÑ‚".as_bytes() + ); + } + + #[test] + fn test_http_request_parser_two_slashes() { + let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); + let req = parse_ready!(&mut buf); + + assert_eq!(req.path(), "//path"); + } + + #[test] + fn test_http_request_parser_bad_method() { + let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_parser_bad_version() { + let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); + + expect_parse_err!(&mut buf); + } + + #[test] + fn test_http_request_chunked_payload() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = msg.message(); + assert!(req.chunked().unwrap()); + + buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"data" + ); + assert_eq!( + reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"line" + ); + assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); + } + + #[test] + fn test_http_request_chunked_payload_and_next_message() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = msg.message(); + assert!(req.chunked().unwrap()); + + buf.extend( + b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ + POST /test2 HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n" + .iter(), + ); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"line"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.eof()); + + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req2 = msg.message(); + assert!(req2.chunked().unwrap()); + assert_eq!(*req2.method(), Method::POST); + assert!(req2.chunked().unwrap()); + } + + #[test] + fn test_http_request_chunked_payload_chunks() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + let req = msg.message(); + assert!(req.chunked().unwrap()); + + buf.extend(b"4\r\n1111\r\n"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"1111"); + + buf.extend(b"4\r\ndata\r"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + + buf.extend(b"\n4"); + assert!(reader.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"\r"); + assert!(reader.decode(&mut buf).unwrap().is_none()); + buf.extend(b"\n"); + assert!(reader.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"li"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"li"); + + //trailers + //buf.feed_data("test: test\r\n"); + //not_ready!(reader.parse(&mut buf, &mut readbuf)); + + buf.extend(b"ne\r\n0\r\n"); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"ne"); + assert!(reader.decode(&mut buf).unwrap().is_none()); + + buf.extend(b"\r\n"); + assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); + } + + #[test] + fn test_parse_chunked_payload_chunk_extension() { + let mut buf = BytesMut::from( + &"GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n"[..], + ); + + let mut reader = H1Decoder::new(); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.is_payload()); + assert!(msg.message().chunked().unwrap()); + + buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") + let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); + assert_eq!(chunk, Bytes::from_static(b"data")); + let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); + assert_eq!(chunk, Bytes::from_static(b"line")); + let msg = reader.decode(&mut buf).unwrap().unwrap(); + assert!(msg.eof()); + } +} diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 6e5672d3..eda8ebf0 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,7 +1,6 @@ // #![allow(unused_imports, unused_variables, dead_code)] use std::collections::VecDeque; use std::fmt::{Debug, Display}; -use std::net::SocketAddr; // use std::time::{Duration, Instant}; use actix_net::service::Service; @@ -16,10 +15,10 @@ use error::{ParseError, PayloadError}; use payload::{Payload, PayloadStatus, PayloadWriter}; use body::Body; +use config::ServiceConfig; use error::DispatchError; use httpresponse::HttpResponse; - -use request::{Request, RequestPool}; +use request::Request; use server::input::PayloadType; use super::codec::{Codec, InMessage, OutMessage}; @@ -52,6 +51,8 @@ where state: State, payload: Option, messages: VecDeque, + + config: ServiceConfig, } enum State { @@ -79,7 +80,7 @@ where S::Error: Debug + Display, { /// Create http/1 dispatcher. - pub fn new(stream: T, service: S) -> Self { + pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { let flags = Flags::FLUSHED; let framed = Framed::new(stream, Codec::new()); @@ -91,6 +92,7 @@ where service, flags, framed, + config, } } @@ -108,7 +110,7 @@ where } // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(&mut self, checked: bool) { + fn client_disconnected(&mut self, _checked: bool) { self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); @@ -187,7 +189,7 @@ where None }; }, - State::Payload(ref mut body) => unimplemented!(), + State::Payload(ref mut _body) => unimplemented!(), State::Response(ref mut fut) => { match fut.poll() { Ok(Async::Ready(res)) => { diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 245f2fc2..1a2bb018 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -4,6 +4,6 @@ mod decoder; mod dispatcher; mod service; -pub use self::codec::Codec; +pub use self::codec::{Codec, InMessage, OutMessage}; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler}; diff --git a/src/h1/service.rs b/src/h1/service.rs index 3017a3ef..436e77a5 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,9 +1,7 @@ use std::fmt::{Debug, Display}; use std::marker::PhantomData; -use std::time::Duration; use actix_net::service::{IntoNewService, NewService, Service}; -use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -120,6 +118,6 @@ where } fn call(&mut self, req: Self::Request) -> Self::Future { - Dispatcher::new(req, self.srv.clone()) + Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs index d736e53a..1ba321ce 100644 --- a/src/header/common/accept.rs +++ b/src/header/common/accept.rs @@ -30,10 +30,10 @@ header! { /// /// # Examples /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{Accept, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -47,10 +47,10 @@ header! { /// ``` /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{Accept, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -64,10 +64,10 @@ header! { /// ``` /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs index 674415fb..49a7237a 100644 --- a/src/header/common/accept_charset.rs +++ b/src/header/common/accept_charset.rs @@ -22,9 +22,9 @@ header! { /// /// # Examples /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -34,9 +34,9 @@ header! { /// # } /// ``` /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem}; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -49,9 +49,9 @@ header! { /// # } /// ``` /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs index 12593e1a..25fd97df 100644 --- a/src/header/common/accept_language.rs +++ b/src/header/common/accept_language.rs @@ -23,10 +23,10 @@ header! { /// # Examples /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// # extern crate language_tags; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -42,10 +42,10 @@ header! { /// ``` /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; /// # /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs index 5046290d..089c823d 100644 --- a/src/header/common/allow.rs +++ b/src/header/common/allow.rs @@ -1,5 +1,5 @@ -use http::Method; use http::header; +use http::Method; header! { /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) @@ -23,11 +23,10 @@ header! { /// # Examples /// /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Allow; - /// use http::Method; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::Allow; + /// use actix_http::http::Method; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -38,11 +37,9 @@ header! { /// ``` /// /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Allow; - /// use http::Method; + /// # extern crate actix_http; + /// use actix_http::HttpResponse; + /// use actix_http::http::{Method, header::Allow}; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs index adc60e4a..4379b6f7 100644 --- a/src/header/common/cache_control.rs +++ b/src/header/common/cache_control.rs @@ -1,5 +1,5 @@ -use header::{Header, IntoHeaderValue, Writer}; use header::{fmt_comma_delimited, from_comma_delimited}; +use header::{Header, IntoHeaderValue, Writer}; use http::header; use std::fmt::{self, Write}; use std::str::FromStr; @@ -26,16 +26,16 @@ use std::str::FromStr; /// /// # Examples /// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{CacheControl, CacheDirective}; +/// use actix_http::HttpResponse; +/// use actix_http::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// /// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{CacheControl, CacheDirective}; +/// use actix_http::HttpResponse; +/// use actix_http::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(CacheControl(vec![ diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index 5e8cbd67..0efc4fb0 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -64,7 +64,7 @@ impl<'a> From<&'a str> for DispositionType { /// /// # Examples /// ``` -/// use actix_web::http::header::DispositionParam; +/// use actix_http::http::header::DispositionParam; /// /// let param = DispositionParam::Filename(String::from("sample.txt")); /// assert!(param.is_filename()); @@ -226,7 +226,7 @@ impl DispositionParam { /// # Example /// /// ``` -/// use actix_web::http::header::{ +/// use actix_http::http::header::{ /// Charset, ContentDisposition, DispositionParam, DispositionType, /// ExtendedValue, /// }; @@ -327,7 +327,8 @@ impl ContentDisposition { left = &left[end.ok_or(::error::ParseError::Header)? + 1..]; left = split_once(left, ';').1.trim_left(); // In fact, it should not be Err if the above code is correct. - String::from_utf8(quoted_string).map_err(|_| ::error::ParseError::Header)? + String::from_utf8(quoted_string) + .map_err(|_| ::error::ParseError::Header)? } else { // token: won't contains semicolon according to RFC 2616 Section 2.2 let (token, new_left) = split_once_and_trim(left, ';'); @@ -874,7 +875,7 @@ mod tests { "attachment; filename=\"carriage\\\rreturn.png\"", display_rendered );*/ - // No way to create a HeaderValue containing a carriage return. + // No way to create a HeaderValue containing a carriage return. let a: ContentDisposition = ContentDisposition { disposition: DispositionType::Inline, diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs index e12d34d0..c1f87d51 100644 --- a/src/header/common/content_language.rs +++ b/src/header/common/content_language.rs @@ -24,10 +24,10 @@ header! { /// # Examples /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// # use actix_web::http::header::{ContentLanguage, qitem}; + /// use actix_http::HttpResponse; + /// # use actix_http::http::header::{ContentLanguage, qitem}; /// # /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -40,10 +40,10 @@ header! { /// ``` /// /// ```rust - /// # extern crate actix_web; + /// # extern crate actix_http; /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// # use actix_web::http::header::{ContentLanguage, qitem}; + /// use actix_http::HttpResponse; + /// # use actix_http::http::header::{ContentLanguage, qitem}; /// # /// # fn main() { /// diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs index 08900e1c..3286d4ca 100644 --- a/src/header/common/content_type.rs +++ b/src/header/common/content_type.rs @@ -31,8 +31,8 @@ header! { /// # Examples /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::ContentType; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::ContentType; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); @@ -44,10 +44,10 @@ header! { /// /// ```rust /// # extern crate mime; - /// # extern crate actix_web; + /// # extern crate actix_http; /// use mime::TEXT_HTML; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::ContentType; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::ContentType; /// /// # fn main() { /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/date.rs b/src/header/common/date.rs index 88a47bc3..9ce2bd65 100644 --- a/src/header/common/date.rs +++ b/src/header/common/date.rs @@ -20,8 +20,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Date; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::Date; /// use std::time::SystemTime; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs index 39dd908c..ea4be2a7 100644 --- a/src/header/common/etag.rs +++ b/src/header/common/etag.rs @@ -28,16 +28,16 @@ header! { /// # Examples /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ETag, EntityTag}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{ETag, EntityTag}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); /// ``` /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ETag, EntityTag}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{ETag, EntityTag}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs index 4ec66b88..bdd25fdb 100644 --- a/src/header/common/expires.rs +++ b/src/header/common/expires.rs @@ -22,8 +22,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Expires; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::Expires; /// use std::time::{SystemTime, Duration}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs index 20a2b1e6..5f7976a4 100644 --- a/src/header/common/if_match.rs +++ b/src/header/common/if_match.rs @@ -30,16 +30,16 @@ header! { /// # Examples /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfMatch; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::IfMatch; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(IfMatch::Any); /// ``` /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{IfMatch, EntityTag}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{IfMatch, EntityTag}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set( diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs index 1914d34d..41d6fba2 100644 --- a/src/header/common/if_modified_since.rs +++ b/src/header/common/if_modified_since.rs @@ -22,8 +22,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfModifiedSince; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::IfModifiedSince; /// use std::time::{SystemTime, Duration}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs index 124f4b8e..8b3905ba 100644 --- a/src/header/common/if_none_match.rs +++ b/src/header/common/if_none_match.rs @@ -32,16 +32,16 @@ header! { /// # Examples /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfNoneMatch; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::IfNoneMatch; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(IfNoneMatch::Any); /// ``` /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{IfNoneMatch, EntityTag}; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::{IfNoneMatch, EntityTag}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set( diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs index dd95b7ba..8cbb8c89 100644 --- a/src/header/common/if_range.rs +++ b/src/header/common/if_range.rs @@ -1,7 +1,9 @@ use error::ParseError; use header::from_one_raw_str; -use header::{EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, - InvalidHeaderValueBytes, Writer}; +use header::{ + EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, + InvalidHeaderValueBytes, Writer, +}; use http::header; use httpmessage::HttpMessage; use std::fmt::{self, Display, Write}; @@ -35,8 +37,8 @@ use std::fmt::{self, Display, Write}; /// # Examples /// /// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{EntityTag, IfRange}; +/// use actix_http::HttpResponse; +/// use actix_http::http::header::{EntityTag, IfRange}; /// /// let mut builder = HttpResponse::Ok(); /// builder.set(IfRange::EntityTag(EntityTag::new( @@ -46,8 +48,8 @@ use std::fmt::{self, Display, Write}; /// ``` /// /// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::IfRange; +/// use actix_http::HttpResponse; +/// use actix_http::http::header::IfRange; /// use std::time::{Duration, SystemTime}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs index f87e760c..02f9252e 100644 --- a/src/header/common/if_unmodified_since.rs +++ b/src/header/common/if_unmodified_since.rs @@ -23,8 +23,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfUnmodifiedSince; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::IfUnmodifiedSince; /// use std::time::{SystemTime, Duration}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs index aba82888..608f4313 100644 --- a/src/header/common/last_modified.rs +++ b/src/header/common/last_modified.rs @@ -22,8 +22,8 @@ header! { /// # Example /// /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::LastModified; + /// use actix_http::HttpResponse; + /// use actix_http::http::header::LastModified; /// use std::time::{SystemTime, Duration}; /// /// let mut builder = HttpResponse::Ok(); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 8c972bd1..531aa1a7 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -106,7 +106,7 @@ pub trait HttpMessage: Sized { /// /// ## Server example /// - /// ```rust + /// ```rust,ignore /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; @@ -143,7 +143,7 @@ pub trait HttpMessage: Sized { /// /// ## Server example /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # extern crate futures; /// # use futures::Future; @@ -176,7 +176,7 @@ pub trait HttpMessage: Sized { /// /// ## Server example /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 73de380a..cdcdea94 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -15,7 +15,7 @@ use serde_json; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; -use httpmessage::HttpMessage; +// use httpmessage::HttpMessage; // use httprequest::HttpRequest; /// max write buffer size 64k @@ -366,7 +366,7 @@ impl HttpResponseBuilder { /// Set a header. /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse, Result}; /// @@ -394,7 +394,7 @@ impl HttpResponseBuilder { /// Set a header. /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse}; /// @@ -516,7 +516,7 @@ impl HttpResponseBuilder { /// Set a cookie /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse, Result}; /// @@ -546,7 +546,7 @@ impl HttpResponseBuilder { /// Remove cookie /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, HttpRequest, HttpResponse, Result}; /// @@ -956,38 +956,38 @@ mod tests { assert!(dbg.contains("HttpResponse")); } - #[test] - fn test_response_cookies() { - let req = TestRequest::default() - .header(COOKIE, "cookie1=value1") - .header(COOKIE, "cookie2=value2") - .finish(); - let cookies = req.cookies().unwrap(); + // #[test] + // fn test_response_cookies() { + // let req = TestRequest::default() + // .header(COOKIE, "cookie1=value1") + // .header(COOKIE, "cookie2=value2") + // .finish(); + // let cookies = req.cookies().unwrap(); - let resp = HttpResponse::Ok() - .cookie( - http::Cookie::build("name", "value") - .domain("www.rust-lang.org") - .path("/test") - .http_only(true) - .max_age(Duration::days(1)) - .finish(), - ).del_cookie(&cookies[0]) - .finish(); + // let resp = HttpResponse::Ok() + // .cookie( + // http::Cookie::build("name", "value") + // .domain("www.rust-lang.org") + // .path("/test") + // .http_only(true) + // .max_age(Duration::days(1)) + // .finish(), + // ).del_cookie(&cookies[0]) + // .finish(); - 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" - ); - } + // 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_update_response_cookies() { @@ -1131,15 +1131,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from("test")); - let resp: HttpResponse = "test".respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test")); - let resp: HttpResponse = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -1149,15 +1140,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); - let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); - let resp: HttpResponse = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -1167,15 +1149,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); - let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); - let resp: HttpResponse = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -1185,15 +1158,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); - let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); - let b = Bytes::from_static(b"test"); let resp: HttpResponse = b.into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1208,19 +1172,6 @@ mod tests { ); let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().bin_ref(), - &Binary::from(Bytes::from_static(b"test")) - ); - - let b = BytesMut::from("test"); let resp: HttpResponse = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( @@ -1231,7 +1182,7 @@ mod tests { assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); let b = BytesMut::from("test"); - let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); + let resp: HttpResponse = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), diff --git a/src/json.rs b/src/json.rs index 04dd369e..5c64b9bd 100644 --- a/src/json.rs +++ b/src/json.rs @@ -3,18 +3,13 @@ use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; use std::fmt; use std::ops::{Deref, DerefMut}; -use std::rc::Rc; use mime; use serde::de::DeserializeOwned; -use serde::Serialize; use serde_json; -use error::{Error, JsonPayloadError}; -use http::StatusCode; +use error::JsonPayloadError; use httpmessage::HttpMessage; -// use httprequest::HttpRequest; -use httpresponse::HttpResponse; /// Json helper /// @@ -30,7 +25,7 @@ use httpresponse::HttpResponse; /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; /// use actix_web::{App, Json, Result, http}; @@ -57,7 +52,7 @@ use httpresponse::HttpResponse; /// to serialize into *JSON*. The type `T` must implement the `Serialize` /// trait from *serde*. /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// # #[macro_use] extern crate serde_derive; /// # use actix_web::*; @@ -124,7 +119,7 @@ where /// /// # Server example /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; @@ -243,9 +238,7 @@ mod tests { use futures::Async; use http::header; - use handler::Handler; use test::TestRequest; - use with::With; impl PartialEq for JsonPayloadError { fn eq(&self, other: &JsonPayloadError) -> bool { @@ -268,18 +261,6 @@ mod tests { name: String, } - #[test] - fn test_json() { - let json = Json(MyObject { - name: "test".to_owned(), - }); - let resp = json.respond_to(&TestRequest::default().finish()).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/json" - ); - } - #[test] fn test_json_body() { let req = TestRequest::default().finish(); @@ -323,24 +304,4 @@ mod tests { }) ); } - - #[test] - fn test_with_json() { - let mut cfg = JsonConfig::default(); - cfg.limit(4096); - let handler = With::new(|data: Json| data, cfg); - - let req = TestRequest::default().finish(); - assert!(handler.handle(&req).as_err().is_some()); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ).header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - assert!(handler.handle(&req).as_err().is_none()) - } } diff --git a/src/lib.rs b/src/lib.rs index ec86f032..b9f90c7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Actix web is a small, pragmatic, and extremely fast web framework //! for Rust. //! -//! ```rust +//! ```rust,ignore //! use actix_web::{server, App, Path, Responder}; //! # use std::thread; //! @@ -78,10 +78,11 @@ //! `gzip`, `deflate` compression. //! #![cfg_attr(actix_nightly, feature(tool_lints))] -#![warn(missing_docs)] -#![allow(unused_imports, unused_variables, dead_code)] +// #![warn(missing_docs)] +// #![allow(unused_imports, unused_variables, dead_code)] extern crate actix; +extern crate actix_net; #[macro_use] extern crate log; extern crate base64; @@ -98,7 +99,12 @@ extern crate failure; extern crate lazy_static; #[macro_use] extern crate futures; +#[cfg(feature = "brotli")] +extern crate brotli2; extern crate cookie; +extern crate encoding; +#[cfg(feature = "flate2")] +extern crate flate2; extern crate http as modhttp; extern crate httparse; extern crate language_tags; @@ -106,6 +112,8 @@ extern crate mime; extern crate mime_guess; extern crate net2; extern crate rand; +extern crate serde; +extern crate serde_urlencoded; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; @@ -116,19 +124,10 @@ extern crate tokio_timer; extern crate tokio_uds; extern crate url; #[macro_use] -extern crate serde; -#[cfg(feature = "brotli")] -extern crate brotli2; -extern crate encoding; -#[cfg(feature = "flate2")] -extern crate flate2; -extern crate serde_urlencoded; -#[macro_use] extern crate percent_encoding; extern crate serde_json; extern crate smallvec; - -extern crate actix_net; +extern crate tokio; #[cfg(test)] #[macro_use] @@ -150,7 +149,7 @@ pub mod error; pub mod h1; pub(crate) mod helpers; pub mod server; -//pub mod test; +pub mod test; //pub mod ws; pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; @@ -170,7 +169,7 @@ pub mod dev { //! //! ``` //! # #![allow(unused_imports)] - //! use actix_web::dev::*; + //! use actix_http::dev::*; //! ``` pub use body::BodyStream; diff --git a/src/request.rs b/src/request.rs index a75fda3a..82d8c22f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,7 +1,6 @@ use std::cell::{Cell, Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::fmt; -use std::net::SocketAddr; use std::rc::Rc; use http::{header, HeaderMap, Method, Uri, Version}; @@ -31,7 +30,6 @@ pub(crate) struct InnerRequest { pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, pub(crate) payload: RefCell>, - pub(crate) stream_extensions: Option>, pool: &'static RequestPool, } @@ -81,7 +79,6 @@ impl Request { flags: Cell::new(MessageFlags::empty()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), - stream_extensions: None, }), } } @@ -170,15 +167,13 @@ impl Request { inner: self.inner.clone(), } } +} - pub(crate) fn release(self) { - let mut inner = self.inner; - if let Some(r) = Rc::get_mut(&mut inner) { - r.reset(); - } else { - return; +impl Drop for Request { + fn drop(&mut self) { + if Rc::strong_count(&self.inner) == 1 { + self.inner.pool.release(self.inner.clone()); } - inner.pool.release(inner); } } @@ -221,11 +216,13 @@ impl RequestPool { /// Get Request object #[inline] pub fn get(pool: &'static RequestPool) -> Request { - if let Some(msg) = pool.0.borrow_mut().pop_front() { - Request { inner: msg } - } else { - Request::with_pool(pool) + if let Some(mut msg) = pool.0.borrow_mut().pop_front() { + if let Some(r) = Rc::get_mut(&mut msg) { + r.reset(); + } + return Request { inner: msg }; } + Request::with_pool(pool) } #[inline] diff --git a/src/server/mod.rs b/src/server/mod.rs index 068094c2..0abd7c21 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -106,12 +106,9 @@ //! let _ = sys.run(); //!} //! ``` -use std::net::{Shutdown, SocketAddr}; -use std::rc::Rc; +use std::net::SocketAddr; use std::{io, time}; -use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tcp::TcpStream; @@ -123,16 +120,8 @@ pub(crate) mod output; #[doc(hidden)] pub use super::helpers::write_content_length; -use body::Binary; -use extensions::Extensions; -use header::ContentEncoding; -use httpresponse::HttpResponse; - -/// max buffer size 64k -pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; - -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 32_768; +// /// max buffer size 64k +// pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; #[derive(Debug, PartialEq, Clone, Copy)] /// Server keep-alive setting diff --git a/src/server/output.rs b/src/server/output.rs index f20bd326..cfc85e4b 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -1,3 +1,4 @@ +#![allow(unused_imports, unused_variables, dead_code)] use std::fmt::Write as FmtWrite; use std::io::Write; use std::str::FromStr; diff --git a/src/test.rs b/src/test.rs index d0cfb255..3c48df64 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,10 +1,8 @@ //! Various helpers for Actix applications to use during testing. -use std::rc::Rc; +use std::net; use std::str::FromStr; -use std::sync::mpsc; -use std::{net, thread}; -use actix_inner::{Actor, Addr, System}; +use actix::System; use cookie::Cookie; use futures::Future; @@ -13,28 +11,12 @@ use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; -#[cfg(any(feature = "alpn", feature = "ssl"))] -use openssl::ssl::SslAcceptorBuilder; -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; - -use application::{App, HttpApplication}; use body::Binary; -use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; -use error::Error; -use handler::{AsyncResult, AsyncResultItem, Handler, Responder}; use header::{Header, IntoHeaderValue}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use param::Params; use payload::Payload; -use resource::Resource; -use router::Router; -use server::message::{Request, RequestPool}; -use server::{HttpServer, IntoHttpHandler, ServerSettings}; +use request::Request; use uri::Url as InnerUrl; -use ws; +// use ws; /// The `TestServer` type. /// @@ -63,9 +45,8 @@ use ws; /// ``` pub struct TestServer { addr: net::SocketAddr, - ssl: bool, - conn: Addr, rt: Runtime, + ssl: bool, } impl TestServer { @@ -73,92 +54,11 @@ impl TestServer { /// /// This method accepts configuration method. You can add /// middlewares or set handlers for test application. - pub fn new(config: F) -> Self + pub fn new(_config: F) -> Self where - F: Clone + Send + 'static + Fn(&mut TestApp<()>), + F: Fn() + Clone + Send + 'static, { - TestServerBuilder::new(|| ()).start(config) - } - - /// Create test server builder - pub fn build() -> TestServerBuilder<(), impl Fn() -> () + Clone + Send + 'static> { - TestServerBuilder::new(|| ()) - } - - /// Create test server builder with specific state factory - /// - /// This method can be used for constructing application state. - /// Also it can be used for external dependency initialization, - /// like creating sync actors for diesel integration. - pub fn build_with_state(state: F) -> TestServerBuilder - where - F: Fn() -> S + Clone + Send + 'static, - S: 'static, - { - TestServerBuilder::new(state) - } - - /// Start new test server with application factory - pub fn with_factory(factory: F) -> Self - where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, - { - let (tx, rx) = mpsc::channel(); - - // run server in separate thread - thread::spawn(move || { - let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - - let _ = HttpServer::new(factory) - .disable_signals() - .listen(tcp) - .keep_alive(5) - .start(); - - tx.send((System::current(), local_addr, TestServer::get_conn())) - .unwrap(); - sys.run(); - }); - - let (system, addr, conn) = rx.recv().unwrap(); - System::set_current(system); - TestServer { - addr, - conn, - ssl: false, - rt: Runtime::new().unwrap(), - } - } - - fn get_conn() -> Addr { - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - ClientConnector::with_connector(builder.build()).start() - } - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl")) - ))] - { - use rustls::ClientConfig; - use std::fs::File; - use std::io::BufReader; - let mut config = ClientConfig::new(); - let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - config.root_store.add_pem_file(pem_file).unwrap(); - ClientConnector::with_connector(config).start() - } - #[cfg(not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")))] - { - ClientConnector::default().start() - } + unimplemented!() } /// Get firat available unused address @@ -208,45 +108,45 @@ impl TestServer { self.rt.block_on(fut) } - /// Connect to websocket server at a given path - pub fn ws_at( - &mut self, path: &str, - ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - let url = self.url(path); - self.rt - .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) - } + // /// Connect to websocket server at a given path + // pub fn ws_at( + // &mut self, path: &str, + // ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { + // let url = self.url(path); + // self.rt + // .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) + // } - /// Connect to a websocket server - pub fn ws( - &mut self, - ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - self.ws_at("/") - } + // /// Connect to a websocket server + // pub fn ws( + // &mut self, + // ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { + // self.ws_at("/") + // } - /// Create `GET` request - pub fn get(&self) -> ClientRequestBuilder { - ClientRequest::get(self.url("/").as_str()) - } + // /// Create `GET` request + // pub fn get(&self) -> ClientRequestBuilder { + // ClientRequest::get(self.url("/").as_str()) + // } - /// Create `POST` request - pub fn post(&self) -> ClientRequestBuilder { - ClientRequest::post(self.url("/").as_str()) - } + // /// Create `POST` request + // pub fn post(&self) -> ClientRequestBuilder { + // ClientRequest::post(self.url("/").as_str()) + // } - /// Create `HEAD` request - pub fn head(&self) -> ClientRequestBuilder { - ClientRequest::head(self.url("/").as_str()) - } + // /// Create `HEAD` request + // pub fn head(&self) -> ClientRequestBuilder { + // ClientRequest::head(self.url("/").as_str()) + // } - /// Connect to test http server - pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - ClientRequest::build() - .method(meth) - .uri(self.url(path).as_str()) - .with_connector(self.conn.clone()) - .take() - } + // /// Connect to test http server + // pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { + // ClientRequest::build() + // .method(meth) + // .uri(self.url(path).as_str()) + // .with_connector(self.conn.clone()) + // .take() + // } } impl Drop for TestServer { @@ -255,183 +155,98 @@ impl Drop for TestServer { } } -/// An `TestServer` builder -/// -/// This type can be used to construct an instance of `TestServer` through a -/// builder-like pattern. -pub struct TestServerBuilder -where - F: Fn() -> S + Send + Clone + 'static, -{ - state: F, - #[cfg(any(feature = "alpn", feature = "ssl"))] - ssl: Option, - #[cfg(feature = "rust-tls")] - rust_ssl: Option, -} +// /// An `TestServer` builder +// /// +// /// This type can be used to construct an instance of `TestServer` through a +// /// builder-like pattern. +// pub struct TestServerBuilder +// where +// F: Fn() -> S + Send + Clone + 'static, +// { +// state: F, +// } -impl TestServerBuilder -where - F: Fn() -> S + Send + Clone + 'static, -{ - /// Create a new test server - pub fn new(state: F) -> TestServerBuilder { - TestServerBuilder { - state, - #[cfg(any(feature = "alpn", feature = "ssl"))] - ssl: None, - #[cfg(feature = "rust-tls")] - rust_ssl: None, - } - } +// impl TestServerBuilder +// where +// F: Fn() -> S + Send + Clone + 'static, +// { +// /// Create a new test server +// pub fn new(state: F) -> TestServerBuilder { +// TestServerBuilder { state } +// } - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Create ssl server - pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self { - self.ssl = Some(ssl); - self - } +// #[allow(unused_mut)] +// /// Configure test application and run test server +// pub fn start(mut self, config: C) -> TestServer +// where +// C: Fn(&mut TestApp) + Clone + Send + 'static, +// { +// let (tx, rx) = mpsc::channel(); - #[cfg(feature = "rust-tls")] - /// Create rust tls server - pub fn rustls(mut self, ssl: ServerConfig) -> Self { - self.rust_ssl = Some(ssl); - self - } +// let mut has_ssl = false; - #[allow(unused_mut)] - /// Configure test application and run test server - pub fn start(mut self, config: C) -> TestServer - where - C: Fn(&mut TestApp) + Clone + Send + 'static, - { - let (tx, rx) = mpsc::channel(); +// #[cfg(any(feature = "alpn", feature = "ssl"))] +// { +// has_ssl = has_ssl || self.ssl.is_some(); +// } - let mut has_ssl = false; +// #[cfg(feature = "rust-tls")] +// { +// has_ssl = has_ssl || self.rust_ssl.is_some(); +// } - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - has_ssl = has_ssl || self.ssl.is_some(); - } +// // run server in separate thread +// thread::spawn(move || { +// let addr = TestServer::unused_addr(); - #[cfg(feature = "rust-tls")] - { - has_ssl = has_ssl || self.rust_ssl.is_some(); - } +// let sys = System::new("actix-test-server"); +// let state = self.state; +// let mut srv = HttpServer::new(move || { +// let mut app = TestApp::new(state()); +// config(&mut app); +// app +// }).workers(1) +// .keep_alive(5) +// .disable_signals(); - // run server in separate thread - thread::spawn(move || { - let addr = TestServer::unused_addr(); +// tx.send((System::current(), addr, TestServer::get_conn())) +// .unwrap(); - let sys = System::new("actix-test-server"); - let state = self.state; - let mut srv = HttpServer::new(move || { - let mut app = TestApp::new(state()); - config(&mut app); - app - }).workers(1) - .keep_alive(5) - .disable_signals(); +// #[cfg(any(feature = "alpn", feature = "ssl"))] +// { +// let ssl = self.ssl.take(); +// if let Some(ssl) = ssl { +// let tcp = net::TcpListener::bind(addr).unwrap(); +// srv = srv.listen_ssl(tcp, ssl).unwrap(); +// } +// } +// #[cfg(feature = "rust-tls")] +// { +// let ssl = self.rust_ssl.take(); +// if let Some(ssl) = ssl { +// let tcp = net::TcpListener::bind(addr).unwrap(); +// srv = srv.listen_rustls(tcp, ssl); +// } +// } +// if !has_ssl { +// let tcp = net::TcpListener::bind(addr).unwrap(); +// srv = srv.listen(tcp); +// } +// srv.start(); - tx.send((System::current(), addr, TestServer::get_conn())) - .unwrap(); +// sys.run(); +// }); - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - let ssl = self.ssl.take(); - if let Some(ssl) = ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_ssl(tcp, ssl).unwrap(); - } - } - #[cfg(feature = "rust-tls")] - { - let ssl = self.rust_ssl.take(); - if let Some(ssl) = ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_rustls(tcp, ssl); - } - } - if !has_ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen(tcp); - } - srv.start(); - - sys.run(); - }); - - let (system, addr, conn) = rx.recv().unwrap(); - System::set_current(system); - TestServer { - addr, - conn, - ssl: has_ssl, - rt: Runtime::new().unwrap(), - } - } -} - -/// Test application helper for testing request handlers. -pub struct TestApp { - app: Option>, -} - -impl TestApp { - fn new(state: S) -> TestApp { - let app = App::with_state(state); - TestApp { app: Some(app) } - } - - /// Register handler for "/" - pub fn handler(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.app = Some(self.app.take().unwrap().resource("/", |r| r.f(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 - } - - /// Register resource. This method is similar - /// to `App::resource()` method. - pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp - where - F: FnOnce(&mut Resource) -> R + 'static, - { - self.app = Some(self.app.take().unwrap().resource(path, f)); - self - } -} - -impl IntoHttpHandler for TestApp { - type Handler = HttpApplication; - - fn into_handler(mut self) -> HttpApplication { - self.app.take().unwrap().into_handler() - } -} - -#[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 - } - } -} +// let (system, addr, conn) = rx.recv().unwrap(); +// System::set_current(system); +// TestServer { +// addr, +// conn, +// ssl: has_ssl, +// rt: Runtime::new().unwrap(), +// } +// } +// } /// Test `HttpRequest` builder /// @@ -460,70 +275,49 @@ impl Iterator for TestApp { /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` -pub struct TestRequest { - state: S, +pub struct TestRequest { version: Version, method: Method, uri: Uri, headers: HeaderMap, - params: Params, - cookies: Option>>, + _cookies: Option>>, payload: Option, prefix: u16, } -impl Default for TestRequest<()> { - fn default() -> TestRequest<()> { +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::new(), - cookies: None, + _cookies: None, payload: None, prefix: 0, } } } -impl TestRequest<()> { +impl TestRequest { /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestRequest<()> { + pub fn with_uri(path: &str) -> TestRequest { TestRequest::default().uri(path) } /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest<()> { + pub fn with_hdr(hdr: H) -> TestRequest { TestRequest::default().set(hdr) } /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest<()> + pub fn with_header(key: K, value: V) -> TestRequest where HeaderName: HttpTryFrom, V: IntoHeaderValue, { TestRequest::default().header(key, value) } -} - -impl TestRequest { - /// Start HttpRequest build process with application state - pub fn with_state(state: S) -> TestRequest { - TestRequest { - state, - method: Method::GET, - uri: Uri::from_str("/").unwrap(), - version: Version::HTTP_11, - headers: HeaderMap::new(), - params: Params::new(), - cookies: None, - payload: None, - prefix: 0, - } - } /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { @@ -567,12 +361,6 @@ impl TestRequest { 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_static(name, value); - self - } - /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { let mut data = data.into(); @@ -588,23 +376,19 @@ impl TestRequest { self } - /// Complete request creation and generate `HttpRequest` instance - pub fn finish(self) -> HttpRequest { + /// Complete request creation and generate `Request` instance + pub fn finish(self) -> Request { let TestRequest { - state, method, uri, version, headers, - mut params, - cookies, + _cookies: _, payload, - prefix, + prefix: _, } = self; - let router = Router::<()>::default(); - let pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); + let mut req = Request::new(); { let inner = req.inner_mut(); inner.method = method; @@ -613,156 +397,94 @@ impl TestRequest { inner.headers = headers; *inner.payload.borrow_mut() = payload; } - params.set_url(req.url().clone()); - let mut info = router.route_info_params(0, params); - info.set_prefix(prefix); - - let mut req = HttpRequest::new(req, Rc::new(state), info); - req.set_cookies(cookies); + // req.set_cookies(cookies); req } - #[cfg(test)] - /// Complete request creation and generate `HttpRequest` instance - pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest { - let TestRequest { - state, - method, - uri, - version, - headers, - mut params, - cookies, - payload, - prefix, - } = self; + // /// This method generates `HttpRequest` instance and runs handler + // /// with generated request. + // pub fn run>(self, h: &H) -> Result { + // let req = self.finish(); + // let resp = h.handle(&req); - let pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); - { - let inner = req.inner_mut(); - inner.method = method; - inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; - *inner.payload.borrow_mut() = payload; - } - params.set_url(req.url().clone()); - let mut info = router.route_info_params(0, params); - info.set_prefix(prefix); - let mut req = HttpRequest::new(req, Rc::new(state), info); - req.set_cookies(cookies); - req - } + // match resp.respond_to(&req) { + // Ok(resp) => match resp.into().into() { + // AsyncResultItem::Ok(resp) => Ok(resp), + // AsyncResultItem::Err(err) => Err(err), + // AsyncResultItem::Future(fut) => { + // let mut sys = System::new("test"); + // sys.block_on(fut) + // } + // }, + // Err(err) => Err(err.into()), + // } + // } - /// Complete request creation and generate server `Request` instance - pub fn request(self) -> Request { - let TestRequest { - method, - uri, - version, - headers, - payload, - .. - } = self; + // /// 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 pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); - { - let inner = req.inner_mut(); - inner.method = method; - inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; - *inner.payload.borrow_mut() = payload; - } - req - } + // let mut sys = System::new("test"); + // match sys.block_on(fut) { + // Ok(r) => match r.respond_to(&req) { + // Ok(reply) => match reply.into().into() { + // AsyncResultItem::Ok(resp) => Ok(resp), + // _ => panic!("Nested async replies are not supported"), + // }, + // Err(e) => Err(e), + // }, + // Err(err) => Err(err), + // } + // } - /// This method generates `HttpRequest` instance and runs handler - /// with generated request. - pub fn run>(self, h: &H) -> Result { - let req = self.finish(); - let resp = h.handle(&req); + // /// This method generates `HttpRequest` instance and executes handler + // pub fn run_async_result(self, f: F) -> Result + // where + // F: FnOnce(&HttpRequest) -> R, + // R: Into>, + // { + // let req = self.finish(); + // let res = f(&req); - match resp.respond_to(&req) { - Ok(resp) => match resp.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - }, - Err(err) => Err(err.into()), - } - } + // match res.into().into() { + // AsyncResultItem::Ok(resp) => Ok(resp), + // AsyncResultItem::Err(err) => Err(err), + // AsyncResultItem::Future(fut) => { + // let mut sys = System::new("test"); + // sys.block_on(fut) + // } + // } + // } - /// 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()); + // /// This method generates `HttpRequest` instance and executes handler + // pub fn execute(self, f: F) -> Result + // where + // F: FnOnce(&HttpRequest) -> R, + // R: Responder + 'static, + // { + // let req = self.finish(); + // let resp = f(&req); - let mut sys = System::new("test"); - match sys.block_on(fut) { - Ok(r) => match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - _ => panic!("Nested async replies are not supported"), - }, - Err(e) => Err(e), - }, - Err(err) => Err(err), - } - } - - /// This method generates `HttpRequest` instance and executes handler - pub fn run_async_result(self, f: F) -> Result - where - F: FnOnce(&HttpRequest) -> R, - R: Into>, - { - let req = self.finish(); - let res = f(&req); - - match res.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - } - } - - /// This method generates `HttpRequest` instance and executes handler - pub fn execute(self, f: F) -> Result - where - F: FnOnce(&HttpRequest) -> R, - R: Responder + 'static, - { - let req = self.finish(); - let resp = f(&req); - - match resp.respond_to(&req) { - Ok(resp) => match resp.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - }, - Err(err) => Err(err.into()), - } - } + // match resp.respond_to(&req) { + // Ok(resp) => match resp.into().into() { + // AsyncResultItem::Ok(resp) => Ok(resp), + // AsyncResultItem::Err(err) => Err(err), + // AsyncResultItem::Future(fut) => { + // let mut sys = System::new("test"); + // sys.block_on(fut) + // } + // }, + // Err(err) => Err(err.into()), + // } + // } } diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index e32481bc..d06777b7 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -8,7 +8,6 @@ use std::thread; use actix::System; use actix_net::server::Server; -use actix_net::service::{IntoNewService, IntoService}; use actix_web::{client, test}; use futures::future; From 99a915e66870bc144aaab16eaf404c1f250f9288 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 21:15:24 -0700 Subject: [PATCH 0718/1635] disable gh-pages update --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62867e03..aa8a44f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,12 +43,12 @@ script: fi # Upload docs -after_success: - - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --features "ssl,tls,rust-tls,session" --no-deps && - echo "" > target/doc/index.html && - git clone https://github.com/davisp/ghp-import.git && - ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && - echo "Uploaded documentation" - fi +#after_success: +# - | +# if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then +# cargo doc --features "ssl,tls,rust-tls,session" --no-deps && +# echo "" > target/doc/index.html && +# git clone https://github.com/davisp/ghp-import.git && +# ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && +# echo "Uploaded documentation" +# fi From df50e636f19ee864e15ba38e404845f8fb88b9e2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 21:18:36 -0700 Subject: [PATCH 0719/1635] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b092a172..74024eb5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http From e78014c65a0971c87e7913c8bf6b8aa46edf1ebd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 21:19:43 -0700 Subject: [PATCH 0720/1635] fix travis link in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74024eb5..7ddd532e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http From 7fdc18f9b9e278e6b64c14ddf3bff1a1f96e3c94 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 23:39:11 -0700 Subject: [PATCH 0721/1635] calculate response parameters --- src/config.rs | 36 +-- src/error.rs | 4 +- src/h1/codec.rs | 36 ++- src/h1/dispatcher.rs | 89 +++++++- src/lib.rs | 2 +- src/server/output.rs | 530 +++++-------------------------------------- 6 files changed, 194 insertions(+), 503 deletions(-) diff --git a/src/config.rs b/src/config.rs index 543e78ac..36b949c3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,7 +21,7 @@ pub struct ServiceConfig(Rc); struct Inner { keep_alive: Option, client_timeout: u64, - client_shutdown: u64, + client_disconnect: u64, ka_enabled: bool, date: UnsafeCell<(bool, Date)>, } @@ -35,7 +35,7 @@ impl Clone for ServiceConfig { impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( - keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, + keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), @@ -52,7 +52,7 @@ impl ServiceConfig { keep_alive, ka_enabled, client_timeout, - client_shutdown, + client_disconnect, date: UnsafeCell::new((false, Date::new())), })) } @@ -100,9 +100,9 @@ impl ServiceConfig { } } - /// Client shutdown timer - pub fn client_shutdown_timer(&self) -> Option { - let delay = self.0.client_shutdown; + /// Client disconnect timer + pub fn client_disconnect_timer(&self) -> Option { + let delay = self.0.client_disconnect; if delay != 0 { Some(self.now() + Duration::from_millis(delay)) } else { @@ -184,7 +184,7 @@ impl ServiceConfig { pub struct ServiceConfigBuilder { keep_alive: KeepAlive, client_timeout: u64, - client_shutdown: u64, + client_disconnect: u64, host: String, addr: net::SocketAddr, secure: bool, @@ -196,7 +196,7 @@ impl ServiceConfigBuilder { ServiceConfigBuilder { keep_alive: KeepAlive::Timeout(5), client_timeout: 5000, - client_shutdown: 5000, + client_disconnect: 0, secure: false, host: "localhost".to_owned(), addr: "127.0.0.1:8080".parse().unwrap(), @@ -204,10 +204,14 @@ impl ServiceConfigBuilder { } /// Enable secure flag for current server. + /// This flags also enables `client disconnect timeout`. /// /// By default this flag is set to false. pub fn secure(mut self) -> Self { self.secure = true; + if self.client_disconnect == 0 { + self.client_disconnect = 3000; + } self } @@ -233,16 +237,16 @@ impl ServiceConfigBuilder { self } - /// Set server connection shutdown timeout in milliseconds. + /// Set server connection disconnect timeout in milliseconds. /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. This timeout affects only secure connections. + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. /// /// To disable timeout set value to 0. /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(mut self, val: u64) -> Self { - self.client_shutdown = val; + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; self } @@ -277,9 +281,7 @@ impl ServiceConfigBuilder { /// Finish service configuration and create `ServiceConfig` object. pub fn finish(self) -> ServiceConfig { - let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; - - ServiceConfig::new(self.keep_alive, self.client_timeout, client_shutdown) + ServiceConfig::new(self.keep_alive, self.client_timeout, self.client_disconnect) } } diff --git a/src/error.rs b/src/error.rs index 21aabac4..fb5df232 100644 --- a/src/error.rs +++ b/src/error.rs @@ -397,9 +397,9 @@ pub enum DispatchError { // #[fail(display = "The first request did not complete within the specified timeout")] SlowRequestTimeout, - /// Shutdown timeout + /// Disconnect timeout. Makes sense for ssl streams. // #[fail(display = "Connection shutdown timeout")] - ShutdownTimeout, + DisconnectTimeout, /// Payload is not consumed // #[fail(display = "Task is completed but request's payload is not consumed")] diff --git a/src/h1/codec.rs b/src/h1/codec.rs index f1b526d5..ac54194a 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -10,7 +10,7 @@ use body::Body; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::Version; +use http::{Method, Version}; use httpresponse::HttpResponse; use request::RequestPool; use server::output::{ResponseInfo, ResponseLength}; @@ -27,6 +27,8 @@ pub enum OutMessage { pub struct Codec { decoder: H1Decoder, encoder: H1Writer, + head: bool, + version: Version, } impl Codec { @@ -40,6 +42,8 @@ impl Codec { Codec { decoder: H1Decoder::with_pool(pool), encoder: H1Writer::new(), + head: false, + version: Version::HTTP_11, } } } @@ -49,7 +53,17 @@ impl Decoder for Codec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - self.decoder.decode(src) + let res = self.decoder.decode(src); + + match res { + Ok(Some(InMessage::Message(ref req))) + | Ok(Some(InMessage::MessageWithPayload(ref req))) => { + self.head = req.inner.method == Method::HEAD; + self.version = req.inner.version; + } + _ => (), + } + res } } @@ -62,7 +76,7 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { OutMessage::Response(res) => { - self.encoder.encode(res, dst)?; + self.encoder.encode(res, dst, self.head, self.version)?; } OutMessage::Payload(bytes) => { dst.extend_from_slice(&bytes); @@ -87,6 +101,7 @@ struct H1Writer { flags: Flags, written: u64, headers_size: u32, + info: ResponseInfo, } impl H1Writer { @@ -95,6 +110,7 @@ impl H1Writer { flags: Flags::empty(), written: 0, headers_size: 0, + info: ResponseInfo::default(), } } @@ -116,10 +132,11 @@ impl H1Writer { } fn encode( - &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, + &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, head: bool, + version: Version, ) -> io::Result<()> { // prepare task - let info = ResponseInfo::new(false); // req.inner.method == Method::HEAD); + self.info.update(&mut msg, head, version); //if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { //self.flags = Flags::STARTED | Flags::KEEPALIVE; @@ -166,7 +183,7 @@ impl H1Writer { buffer.extend_from_slice(reason); // content length - match info.length { + match self.info.length { ResponseLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } @@ -183,11 +200,6 @@ impl H1Writer { } ResponseLength::None => buffer.extend_from_slice(b"\r\n"), } - if let Some(ce) = info.content_encoding { - buffer.extend_from_slice(b"content-encoding: "); - buffer.extend_from_slice(ce.as_ref()); - buffer.extend_from_slice(b"\r\n"); - } // write headers let mut pos = 0; @@ -197,7 +209,7 @@ impl H1Writer { for (key, value) in msg.headers() { match *key { TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match info.length { + CONTENT_LENGTH => match self.info.length { ResponseLength::None => (), _ => continue, }, diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index eda8ebf0..f777648e 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,7 +1,7 @@ // #![allow(unused_imports, unused_variables, dead_code)] use std::collections::VecDeque; use std::fmt::{Debug, Display}; -// use std::time::{Duration, Instant}; +use std::time::Instant; use actix_net::service::Service; @@ -9,7 +9,7 @@ use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; use tokio_codec::Framed; // use tokio_current_thread::spawn; use tokio_io::{AsyncRead, AsyncWrite}; -// use tokio_timer::Delay; +use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadStatus, PayloadWriter}; @@ -47,12 +47,14 @@ where flags: Flags, framed: Framed, error: Option>, + config: ServiceConfig, state: State, payload: Option, messages: VecDeque, - config: ServiceConfig, + ka_expire: Instant, + ka_timer: Option, } enum State { @@ -81,9 +83,28 @@ where { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { - let flags = Flags::FLUSHED; + Dispatcher::with_timeout(stream, config, None, service) + } + + /// Create http/1 dispatcher with slow request timeout. + pub fn with_timeout( + stream: T, config: ServiceConfig, timeout: Option, service: S, + ) -> Self { + let flags = if config.keep_alive_enabled() { + Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED + } else { + Flags::FLUSHED + }; let framed = Framed::new(stream, Codec::new()); + let (ka_expire, ka_timer) = if let Some(delay) = timeout { + (delay.deadline(), Some(delay)) + } else if let Some(delay) = config.keep_alive_timer() { + (delay.deadline(), Some(delay)) + } else { + (config.now(), None) + }; + Dispatcher { payload: None, state: State::None, @@ -93,6 +114,8 @@ where flags, framed, config, + ka_expire, + ka_timer, } } @@ -358,8 +381,64 @@ where } } + if self.ka_timer.is_some() && updated { + if let Some(expire) = self.config.keep_alive_expire() { + self.ka_expire = expire; + } + } Ok(updated) } + + /// keep-alive timer + fn poll_keepalive(&mut self) -> Result<(), DispatchError> { + if let Some(ref mut timer) = self.ka_timer { + match timer.poll() { + Ok(Async::Ready(_)) => { + if timer.deadline() >= self.ka_expire { + // check for any outstanding request handling + if self.state.is_empty() && self.messages.is_empty() { + // if we get timer during shutdown, just drop connection + if self.flags.contains(Flags::SHUTDOWN) { + return Err(DispatchError::DisconnectTimeout); + } else if !self.flags.contains(Flags::STARTED) { + // timeout on first request (slow request) return 408 + trace!("Slow request timeout"); + self.flags + .insert(Flags::STARTED | Flags::READ_DISCONNECTED); + self.state = + State::SendResponse(Some(OutMessage::Response( + HttpResponse::RequestTimeout().finish(), + ))); + } else { + trace!("Keep-alive timeout, close connection"); + self.flags.insert(Flags::SHUTDOWN); + + // start shutdown timer + if let Some(deadline) = + self.config.client_disconnect_timer() + { + timer.reset(deadline) + } else { + return Ok(()); + } + } + } else if let Some(deadline) = self.config.keep_alive_expire() { + timer.reset(deadline) + } + } else { + timer.reset(self.ka_expire) + } + } + Ok(Async::NotReady) => (), + Err(e) => { + error!("Timer error {:?}", e); + return Err(DispatchError::Unknown); + } + } + } + + Ok(()) + } } impl Future for Dispatcher @@ -373,6 +452,8 @@ where #[inline] fn poll(&mut self) -> Poll<(), Self::Error> { + self.poll_keepalive()?; + // shutdown if self.flags.contains(Flags::SHUTDOWN) { if self.flags.contains(Flags::WRITE_DISCONNECTED) { diff --git a/src/lib.rs b/src/lib.rs index b9f90c7a..6215bc4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,7 +79,7 @@ //! #![cfg_attr(actix_nightly, feature(tool_lints))] // #![warn(missing_docs)] -// #![allow(unused_imports, unused_variables, dead_code)] +#![allow(dead_code)] extern crate actix; extern crate actix_net; diff --git a/src/server/output.rs b/src/server/output.rs index cfc85e4b..5fc6fc83 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -4,26 +4,15 @@ use std::io::Write; use std::str::FromStr; use std::{cmp, fmt, io, mem}; -#[cfg(feature = "brotli")] -use brotli2::write::BrotliEncoder; -use bytes::BytesMut; -#[cfg(feature = "flate2")] -use flate2::write::{GzEncoder, ZlibEncoder}; -#[cfg(feature = "flate2")] -use flate2::Compression; +use bytes::{Bytes, BytesMut}; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use body::{Binary, Body}; use header::ContentEncoding; +use http::Method; use httpresponse::HttpResponse; -use request::InnerRequest; - -// #[derive(Debug)] -// pub(crate) struct RequestInfo { -// pub version: Version, -// pub accept_encoding: Option, -// } +use request::Request; #[derive(Debug)] pub(crate) enum ResponseLength { @@ -38,285 +27,91 @@ pub(crate) enum ResponseLength { pub(crate) struct ResponseInfo { head: bool, pub length: ResponseLength, - pub content_encoding: Option<&'static str>, + pub te: TransferEncoding, +} + +impl Default for ResponseInfo { + fn default() -> Self { + ResponseInfo { + head: false, + length: ResponseLength::None, + te: TransferEncoding::empty(), + } + } } impl ResponseInfo { - pub fn new(head: bool) -> Self { - ResponseInfo { - head, - length: ResponseLength::None, - content_encoding: None, - } - } -} + pub fn update(&mut self, resp: &mut HttpResponse, head: bool, version: Version) { + self.head = head; -#[derive(Debug)] -pub(crate) enum Output { - Empty(BytesMut), - Buffer(BytesMut), - Encoder(ContentEncoder), - TE(TransferEncoding), - Done, -} - -impl Output { - pub fn take(&mut self) -> BytesMut { - match mem::replace(self, Output::Done) { - Output::Empty(bytes) => bytes, - Output::Buffer(bytes) => bytes, - Output::Encoder(mut enc) => enc.take_buf(), - Output::TE(mut te) => te.take(), - Output::Done => panic!(), - } - } - - pub fn take_option(&mut self) -> Option { - match mem::replace(self, Output::Done) { - Output::Empty(bytes) => Some(bytes), - Output::Buffer(bytes) => Some(bytes), - Output::Encoder(mut enc) => Some(enc.take_buf()), - Output::TE(mut te) => Some(te.take()), - Output::Done => None, - } - } - - pub fn as_ref(&mut self) -> &BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes, - Output::Buffer(ref mut bytes) => bytes, - Output::Encoder(ref mut enc) => enc.buf_ref(), - Output::TE(ref mut te) => te.buf_ref(), - Output::Done => panic!(), - } - } - pub fn as_mut(&mut self) -> &mut BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes, - Output::Buffer(ref mut bytes) => bytes, - Output::Encoder(ref mut enc) => enc.buf_mut(), - Output::TE(ref mut te) => te.buf_mut(), - Output::Done => panic!(), - } - } - pub fn split_to(&mut self, cap: usize) -> BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes.split_to(cap), - Output::Buffer(ref mut bytes) => bytes.split_to(cap), - Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap), - Output::TE(ref mut te) => te.buf_mut().split_to(cap), - Output::Done => BytesMut::new(), - } - } - - pub fn len(&self) -> usize { - match self { - Output::Empty(ref bytes) => bytes.len(), - Output::Buffer(ref bytes) => bytes.len(), - Output::Encoder(ref enc) => enc.len(), - Output::TE(ref te) => te.len(), - Output::Done => 0, - } - } - - pub fn is_empty(&self) -> bool { - match self { - Output::Empty(ref bytes) => bytes.is_empty(), - Output::Buffer(ref bytes) => bytes.is_empty(), - Output::Encoder(ref enc) => enc.is_empty(), - Output::TE(ref te) => te.is_empty(), - Output::Done => true, - } - } - - pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { - match self { - Output::Buffer(ref mut bytes) => { - bytes.extend_from_slice(data); - Ok(()) - } - Output::Encoder(ref mut enc) => enc.write(data), - Output::TE(ref mut te) => te.encode(data).map(|_| ()), - Output::Empty(_) | Output::Done => Ok(()), - } - } - - pub fn write_eof(&mut self) -> Result { - match self { - Output::Buffer(_) => Ok(true), - Output::Encoder(ref mut enc) => enc.write_eof(), - Output::TE(ref mut te) => Ok(te.encode_eof()), - Output::Empty(_) | Output::Done => Ok(true), - } - } - - pub(crate) fn for_server( - &mut self, info: &mut ResponseInfo, req: &InnerRequest, resp: &mut HttpResponse, - response_encoding: ContentEncoding, - ) { - let buf = self.take(); - let version = resp.version().unwrap_or_else(|| req.version); + let version = resp.version().unwrap_or_else(|| version); let mut len = 0; let has_body = match resp.body() { Body::Empty => false, Body::Binary(ref bin) => { len = bin.len(); - !(response_encoding == ContentEncoding::Auto && len < 96) + true } _ => true, }; - // Enable content encoding only if response does not contain Content-Encoding - // header - #[cfg(any(feature = "brotli", feature = "flate2"))] - let mut encoding = if has_body { - let encoding = match response_encoding { - ContentEncoding::Auto => { - // negotiate content-encoding - if let Some(val) = req.headers.get(ACCEPT_ENCODING) { - if let Ok(enc) = val.to_str() { - AcceptEncoding::parse(enc) - } else { - ContentEncoding::Identity - } - } else { - ContentEncoding::Identity - } - } - encoding => encoding, - }; - if encoding.is_compression() { - info.content_encoding = Some(encoding.as_str()); - } - encoding - } else { - ContentEncoding::Identity + let has_body = match resp.body() { + Body::Empty => false, + _ => true, }; - #[cfg(not(any(feature = "brotli", feature = "flate2")))] - let mut encoding = ContentEncoding::Identity; let transfer = match resp.body() { Body::Empty => { - if !info.head { - info.length = match resp.status() { + if !self.head { + self.length = match resp.status() { StatusCode::NO_CONTENT | StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS | StatusCode::PROCESSING => ResponseLength::None, _ => ResponseLength::Zero, }; + } else { + self.length = ResponseLength::Zero; } - *self = Output::Empty(buf); - return; + TransferEncoding::empty() } Body::Binary(_) => { - #[cfg(any(feature = "brotli", feature = "flate2"))] - { - if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) - { - let mut tmp = BytesMut::new(); - let mut transfer = TransferEncoding::eof(tmp); - let mut enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate( - ZlibEncoder::new(transfer, Compression::fast()), - ), - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => ContentEncoder::Gzip( - GzEncoder::new(transfer, Compression::fast()), - ), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) - } - ContentEncoding::Identity | ContentEncoding::Auto => { - unreachable!() - } - }; - - let bin = resp.replace_body(Body::Empty).binary(); - - // TODO return error! - let _ = enc.write(bin.as_ref()); - let _ = enc.write_eof(); - let body = enc.buf_mut().take(); - len = body.len(); - resp.replace_body(Binary::from(body)); - } - } - - info.length = ResponseLength::Length(len); - if info.head { - *self = Output::Empty(buf); - } else { - *self = Output::Buffer(buf); - } - return; + self.length = ResponseLength::Length(len); + TransferEncoding::length(len as u64) } Body::Streaming(_) => { if resp.upgrade() { - if version == Version::HTTP_2 { - error!("Connection upgrade is forbidden for HTTP/2"); - } - if encoding != ContentEncoding::Identity { - encoding = ContentEncoding::Identity; - info.content_encoding.take(); - } - TransferEncoding::eof(buf) + self.length = ResponseLength::None; + TransferEncoding::eof() } else { - if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) - { - resp.headers_mut().remove(CONTENT_LENGTH); - } - Output::streaming_encoding(info, buf, version, resp) + self.streaming_encoding(version, resp) } } }; // check for head response - if info.head { + if self.head { resp.set_body(Body::Empty); - *self = Output::Empty(transfer.buf.unwrap()); - return; + } else { + self.te = transfer; } - - let enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::fast())) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast())) - } - #[cfg(feature = "brotli")] - ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)), - ContentEncoding::Identity | ContentEncoding::Auto => { - *self = Output::TE(transfer); - return; - } - }; - *self = Output::Encoder(enc); } fn streaming_encoding( - info: &mut ResponseInfo, buf: BytesMut, version: Version, - resp: &mut HttpResponse, + &mut self, version: Version, resp: &mut HttpResponse, ) -> TransferEncoding { match resp.chunked() { Some(true) => { // Enable transfer encoding if version == Version::HTTP_2 { - info.length = ResponseLength::None; - TransferEncoding::eof(buf) + self.length = ResponseLength::None; + TransferEncoding::eof() } else { - info.length = ResponseLength::Chunked; - TransferEncoding::chunked(buf) + self.length = ResponseLength::Chunked; + TransferEncoding::chunked() } } - Some(false) => TransferEncoding::eof(buf), + Some(false) => TransferEncoding::eof(), None => { // if Content-Length is specified, then use it as length hint let (len, chunked) = @@ -339,21 +134,21 @@ impl Output { if !chunked { if let Some(len) = len { - info.length = ResponseLength::Length64(len); - TransferEncoding::length(len, buf) + self.length = ResponseLength::Length64(len); + TransferEncoding::length(len) } else { - TransferEncoding::eof(buf) + TransferEncoding::eof() } } else { // Enable transfer encoding match version { Version::HTTP_11 => { - info.length = ResponseLength::Chunked; - TransferEncoding::chunked(buf) + self.length = ResponseLength::Chunked; + TransferEncoding::chunked() } _ => { - info.length = ResponseLength::None; - TransferEncoding::eof(buf) + self.length = ResponseLength::None; + TransferEncoding::eof() } } } @@ -362,178 +157,9 @@ impl Output { } } -pub(crate) enum ContentEncoder { - #[cfg(feature = "flate2")] - Deflate(ZlibEncoder), - #[cfg(feature = "flate2")] - Gzip(GzEncoder), - #[cfg(feature = "brotli")] - Br(BrotliEncoder), - Identity(TransferEncoding), -} - -impl fmt::Debug for ContentEncoder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), - ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"), - } - } -} - -impl ContentEncoder { - #[inline] - pub fn len(&self) -> usize { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref encoder) => encoder.get_ref().len(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref encoder) => encoder.get_ref().len(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref encoder) => encoder.get_ref().len(), - ContentEncoder::Identity(ref encoder) => encoder.len(), - } - } - - #[inline] - pub fn is_empty(&self) -> bool { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref encoder) => encoder.get_ref().is_empty(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_empty(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_empty(), - ContentEncoder::Identity(ref encoder) => encoder.is_empty(), - } - } - - #[inline] - pub(crate) fn take_buf(&mut self) -> BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), - ContentEncoder::Identity(ref mut encoder) => encoder.take(), - } - } - - #[inline] - pub(crate) fn buf_mut(&mut self) -> &mut BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_mut(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_mut(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_mut(), - ContentEncoder::Identity(ref mut encoder) => encoder.buf_mut(), - } - } - - #[inline] - pub(crate) fn buf_ref(&mut self) -> &BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_ref(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_ref(), - ContentEncoder::Identity(ref mut encoder) => encoder.buf_ref(), - } - } - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] - #[inline(always)] - pub fn write_eof(&mut self) -> Result { - let encoder = - mem::replace(self, ContentEncoder::Identity(TransferEncoding::empty())); - - match encoder { - #[cfg(feature = "brotli")] - ContentEncoder::Br(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - ContentEncoder::Identity(mut writer) => { - let res = writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(res) - } - } - } - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] - #[inline(always)] - pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding br encoding: {}", err); - Err(err) - } - }, - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding gzip encoding: {}", err); - Err(err) - } - }, - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding deflate encoding: {}", err); - Err(err) - } - }, - ContentEncoder::Identity(ref mut encoder) => { - encoder.encode(data)?; - Ok(()) - } - } - } -} - /// Encoders to handle different Transfer-Encodings. #[derive(Debug)] pub(crate) struct TransferEncoding { - buf: Option, kind: TransferEncodingKind, } @@ -552,65 +178,41 @@ enum TransferEncodingKind { } impl TransferEncoding { - fn take(&mut self) -> BytesMut { - self.buf.take().unwrap() - } - - fn buf_ref(&mut self) -> &BytesMut { - self.buf.as_ref().unwrap() - } - - fn len(&self) -> usize { - self.buf.as_ref().unwrap().len() - } - - fn is_empty(&self) -> bool { - self.buf.as_ref().unwrap().is_empty() - } - - fn buf_mut(&mut self) -> &mut BytesMut { - self.buf.as_mut().unwrap() - } - #[inline] pub fn empty() -> TransferEncoding { TransferEncoding { - buf: None, kind: TransferEncodingKind::Eof, } } #[inline] - pub fn eof(buf: BytesMut) -> TransferEncoding { + pub fn eof() -> TransferEncoding { TransferEncoding { - buf: Some(buf), kind: TransferEncodingKind::Eof, } } #[inline] - pub fn chunked(buf: BytesMut) -> TransferEncoding { + pub fn chunked() -> TransferEncoding { TransferEncoding { - buf: Some(buf), kind: TransferEncodingKind::Chunked(false), } } #[inline] - pub fn length(len: u64, buf: BytesMut) -> TransferEncoding { + pub fn length(len: u64) -> TransferEncoding { TransferEncoding { - buf: Some(buf), kind: TransferEncodingKind::Length(len), } } /// Encode message. Return `EOF` state of encoder #[inline] - pub fn encode(&mut self, msg: &[u8]) -> io::Result { + pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { match self.kind { TransferEncodingKind::Eof => { let eof = msg.is_empty(); - self.buf.as_mut().unwrap().extend_from_slice(msg); + buf.extend_from_slice(msg); Ok(eof) } TransferEncodingKind::Chunked(ref mut eof) => { @@ -620,17 +222,14 @@ impl TransferEncoding { if msg.is_empty() { *eof = true; - self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n"); + buf.extend_from_slice(b"0\r\n\r\n"); } else { - let mut buf = BytesMut::new(); - writeln!(&mut buf, "{:X}\r", msg.len()) + writeln!(buf.as_mut(), "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let b = self.buf.as_mut().unwrap(); - b.reserve(buf.len() + msg.len() + 2); - b.extend_from_slice(buf.as_ref()); - b.extend_from_slice(msg); - b.extend_from_slice(b"\r\n"); + buf.reserve(msg.len() + 2); + buf.extend_from_slice(msg); + buf.extend_from_slice(b"\r\n"); } Ok(*eof) } @@ -641,10 +240,7 @@ impl TransferEncoding { } let len = cmp::min(*remaining, msg.len() as u64); - self.buf - .as_mut() - .unwrap() - .extend_from_slice(&msg[..len as usize]); + buf.extend_from_slice(&msg[..len as usize]); *remaining -= len as u64; Ok(*remaining == 0) @@ -657,14 +253,14 @@ impl TransferEncoding { /// Encode eof. Return `EOF` state of encoder #[inline] - pub fn encode_eof(&mut self) -> bool { + pub fn encode_eof(&mut self, buf: &mut BytesMut) -> bool { match self.kind { TransferEncodingKind::Eof => true, TransferEncodingKind::Length(rem) => rem == 0, TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; - self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n"); + buf.extend_from_slice(b"0\r\n\r\n"); } true } @@ -675,9 +271,9 @@ impl TransferEncoding { impl io::Write for TransferEncoding { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { - if self.buf.is_some() { - self.encode(buf)?; - } + // if self.buf.is_some() { + // self.encode(buf)?; + // } Ok(buf.len()) } From caa5a54b8f965484d2fb1fb5584f9a6047dc57b8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 4 Oct 2018 23:46:43 -0700 Subject: [PATCH 0722/1635] fix test and remove unused code --- src/h1/decoder.rs | 8 +- src/httprequest.rs | 545 ------------------------------------------- src/server/output.rs | 89 +------ 3 files changed, 12 insertions(+), 630 deletions(-) delete mode 100644 src/httprequest.rs diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 66f24628..90946b45 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -510,21 +510,17 @@ impl ChunkedState { #[cfg(test)] mod tests { - use std::net::Shutdown; - use std::{cmp, io, time}; + use std::{cmp, io}; - use actix::System; use bytes::{Buf, Bytes, BytesMut}; - use futures::{future, future::ok}; use http::{Method, Version}; use tokio_io::{AsyncRead, AsyncWrite}; use super::*; use error::ParseError; - use h1::{Dispatcher, InMessage}; + use h1::InMessage; use httpmessage::HttpMessage; use request::Request; - use server::KeepAlive; impl InMessage { fn message(self) -> Request { diff --git a/src/httprequest.rs b/src/httprequest.rs deleted file mode 100644 index d8c49496..00000000 --- a/src/httprequest.rs +++ /dev/null @@ -1,545 +0,0 @@ -//! HTTP Request message related code. -use std::cell::{Ref, RefMut}; -use std::collections::HashMap; -use std::net::SocketAddr; -use std::ops::Deref; -use std::rc::Rc; -use std::{fmt, str}; - -use cookie::Cookie; -use futures_cpupool::CpuPool; -use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; -use url::{form_urlencoded, Url}; - -use body::Body; -use error::{CookieParseError, UrlGenerationError}; -use extensions::Extensions; -use handler::FromRequest; -use httpmessage::HttpMessage; -use httpresponse::{HttpResponse, HttpResponseBuilder}; -use info::ConnectionInfo; -use param::Params; -use payload::Payload; -use router::ResourceInfo; -use server::Request; - -struct Query(HashMap); -struct Cookies(Vec>); - -/// An HTTP Request -pub struct HttpRequest { - req: Option, - state: Rc, - resource: ResourceInfo, -} - -impl HttpMessage for HttpRequest { - type Stream = Payload; - - #[inline] - fn headers(&self) -> &HeaderMap { - self.request().headers() - } - - #[inline] - fn payload(&self) -> Payload { - if let Some(payload) = self.request().inner.payload.borrow_mut().take() { - payload - } else { - Payload::empty() - } - } -} - -impl Deref for HttpRequest { - type Target = Request; - - fn deref(&self) -> &Request { - self.request() - } -} - -impl HttpRequest { - #[inline] - pub(crate) fn new( - req: Request, state: Rc, resource: ResourceInfo, - ) -> HttpRequest { - HttpRequest { - state, - resource, - req: Some(req), - } - } - - #[inline] - /// Construct new http request with state. - pub(crate) fn with_state(&self, state: Rc) -> HttpRequest { - HttpRequest { - state, - req: self.req.as_ref().map(|r| r.clone()), - resource: self.resource.clone(), - } - } - - /// Construct new http request with empty state. - pub fn drop_state(&self) -> HttpRequest { - HttpRequest { - state: Rc::new(()), - req: self.req.as_ref().map(|r| r.clone()), - resource: self.resource.clone(), - } - } - - #[inline] - /// Construct new http request with new RouteInfo. - pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest { - resource.merge(&self.resource); - - HttpRequest { - resource, - req: self.req.as_ref().map(|r| r.clone()), - state: self.state.clone(), - } - } - - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - &self.state - } - - #[inline] - /// Server request - pub fn request(&self) -> &Request { - self.req.as_ref().unwrap() - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.request().extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.request().extensions_mut() - } - - /// Default `CpuPool` - #[inline] - #[doc(hidden)] - pub fn cpu_pool(&self) -> &CpuPool { - self.request().server_settings().cpu_pool() - } - - #[inline] - /// Create http response - pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse { - self.request().server_settings().get_response(status, body) - } - - #[inline] - /// Create http response builder - pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder { - self.request() - .server_settings() - .get_response_builder(status) - } - - /// Read the Request Uri. - #[inline] - pub fn uri(&self) -> &Uri { - self.request().inner.url.uri() - } - - /// Read the Request method. - #[inline] - pub fn method(&self) -> &Method { - &self.request().inner.method - } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> Version { - self.request().inner.version - } - - /// The target path of this Request. - #[inline] - pub fn path(&self) -> &str { - self.request().inner.url.path() - } - - /// Get *ConnectionInfo* for the correct request. - #[inline] - pub fn connection_info(&self) -> Ref { - self.request().connection_info() - } - - /// Generate url for named resource - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::{App, HttpRequest, HttpResponse, http}; - /// # - /// fn index(req: HttpRequest) -> HttpResponse { - /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource - /// HttpResponse::Ok().into() - /// } - /// - /// fn main() { - /// let app = App::new() - /// .resource("/test/{one}/{two}/{three}", |r| { - /// r.name("foo"); // <- set resource name, then it could be used in `url_for` - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .finish(); - /// } - /// ``` - pub fn url_for( - &self, name: &str, elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - self.resource.url_for(&self, name, elements) - } - - /// Generate url for named resource - /// - /// This method is similar to `HttpRequest::url_for()` but it can be used - /// for urls that do not contain variable parts. - pub fn url_for_static(&self, name: &str) -> Result { - const NO_PARAMS: [&str; 0] = []; - self.url_for(name, &NO_PARAMS) - } - - /// This method returns reference to current `RouteInfo` object. - #[inline] - pub fn resource(&self) -> &ResourceInfo { - &self.resource - } - - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - /// - /// To get client connection information `connection_info()` method should - /// be used. - #[inline] - pub fn peer_addr(&self) -> Option { - self.request().inner.addr - } - - /// url query parameters. - pub fn query(&self) -> Ref> { - if self.extensions().get::().is_none() { - let mut query = HashMap::new(); - for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { - query.insert(key.as_ref().to_string(), val.to_string()); - } - self.extensions_mut().insert(Query(query)); - } - Ref::map(self.extensions(), |ext| &ext.get::().unwrap().0) - } - - /// The query string in the URL. - /// - /// E.g., id=10 - #[inline] - pub fn query_string(&self) -> &str { - if let Some(query) = self.uri().query().as_ref() { - query - } else { - "" - } - } - - /// Load request cookies. - #[inline] - pub fn cookies(&self) -> Result>>, CookieParseError> { - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.request().inner.headers.get_all(header::COOKIE) { - let s = - str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; - for cookie_str in s.split(';').map(|s| s.trim()) { - if !cookie_str.is_empty() { - cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); - } - } - } - self.extensions_mut().insert(Cookies(cookies)); - } - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } - - /// Return request cookie. - #[inline] - pub fn cookie(&self, name: &str) -> Option> { - if let Ok(cookies) = self.cookies() { - for cookie in cookies.iter() { - if cookie.name() == name { - return Some(cookie.to_owned()); - } - } - } - None - } - - pub(crate) fn set_cookies(&mut self, cookies: Option>>) { - if let Some(cookies) = cookies { - self.extensions_mut().insert(Cookies(cookies)); - } - } - - /// Get a reference to the Params object. - /// - /// Params is a container for url parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. - #[inline] - pub fn match_info(&self) -> &Params { - &self.resource.match_info() - } - - /// Check if request requires connection upgrade - pub(crate) fn upgrade(&self) -> bool { - self.request().upgrade() - } - - /// Set read buffer capacity - /// - /// Default buffer capacity is 32Kb. - pub fn set_read_buffer_capacity(&mut self, cap: usize) { - if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() { - payload.set_read_buffer_capacity(cap) - } - } -} - -impl Drop for HttpRequest { - fn drop(&mut self) { - if let Some(req) = self.req.take() { - req.release(); - } - } -} - -impl Clone for HttpRequest { - fn clone(&self) -> HttpRequest { - HttpRequest { - req: self.req.as_ref().map(|r| r.clone()), - state: self.state.clone(), - resource: self.resource.clone(), - } - } -} - -impl FromRequest for HttpRequest { - type Config = (); - type Result = Self; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - req.clone() - } -} - -impl fmt::Debug for HttpRequest { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nHttpRequest {:?} {}:{}", - self.version(), - self.method(), - self.path() - )?; - if !self.query_string().is_empty() { - writeln!(f, " query: ?{:?}", self.query_string())?; - } - if !self.match_info().is_empty() { - writeln!(f, " params: {:?}", self.match_info())?; - } - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use resource::Resource; - use router::{ResourceDef, Router}; - use test::TestRequest; - - #[test] - fn test_debug() { - 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 = TestRequest::default().finish(); - assert!(req.cookies().unwrap().is_empty()); - } - - #[test] - fn test_request_cookies() { - let req = TestRequest::default() - .header(header::COOKIE, "cookie1=value1") - .header(header::COOKIE, "cookie2=value2") - .finish(); - { - 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_request_query() { - let req = TestRequest::with_uri("/?id=test").finish(); - assert_eq!(req.query_string(), "id=test"); - let query = req.query(); - assert_eq!(&query["id"], "test"); - } - - #[test] - fn test_request_match_info() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/"))); - - let req = TestRequest::with_uri("/value/?id=test").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.match_info().get("key"), Some("value")); - } - - #[test] - fn test_url_for() { - let mut router = Router::<()>::default(); - let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}")); - resource.name("index"); - router.register_resource(resource); - - let info = router.default_route_info(); - assert!(!info.has_prefixed_resource("/use/")); - assert!(info.has_resource("/user/test.html")); - assert!(info.has_prefixed_resource("/user/test.html")); - assert!(!info.has_resource("/test/unknown")); - assert!(!info.has_prefixed_resource("/test/unknown")); - - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") - .finish_with_router(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" - ); - } - - #[test] - fn test_url_for_with_prefix() { - let mut resource = Resource::new(ResourceDef::new("/user/{name}.html")); - resource.name("index"); - let mut router = Router::<()>::default(); - router.set_prefix("/prefix"); - router.register_resource(resource); - - let mut info = router.default_route_info(); - info.set_prefix(7); - assert!(!info.has_prefixed_resource("/use/")); - assert!(info.has_resource("/user/test.html")); - assert!(!info.has_prefixed_resource("/user/test.html")); - assert!(!info.has_resource("/prefix/user/test.html")); - assert!(info.has_prefixed_resource("/prefix/user/test.html")); - - let req = TestRequest::with_uri("/prefix/test") - .prefix(7) - .header(header::HOST, "www.rust-lang.org") - .finish_with_router(router); - let url = req.url_for("index", &["test"]); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/prefix/user/test.html" - ); - } - - #[test] - fn test_url_for_static() { - let mut resource = Resource::new(ResourceDef::new("/index.html")); - resource.name("index"); - let mut router = Router::<()>::default(); - router.set_prefix("/prefix"); - router.register_resource(resource); - - let mut info = router.default_route_info(); - info.set_prefix(7); - assert!(info.has_resource("/index.html")); - assert!(!info.has_prefixed_resource("/index.html")); - assert!(!info.has_resource("/prefix/index.html")); - assert!(info.has_prefixed_resource("/prefix/index.html")); - - let req = TestRequest::with_uri("/prefix/test") - .prefix(7) - .header(header::HOST, "www.rust-lang.org") - .finish_with_router(router); - let url = req.url_for_static("index"); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/prefix/index.html" - ); - } - - #[test] - fn test_url_for_external() { - let mut router = Router::<()>::default(); - router.register_external( - "youtube", - ResourceDef::external("https://youtube.com/watch/{video_id}"), - ); - - let info = router.default_route_info(); - assert!(!info.has_resource("https://youtube.com/watch/unknown")); - assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown")); - - let req = TestRequest::default().finish_with_router(router); - let url = req.url_for("youtube", &["oHg5SJYRHA0"]); - assert_eq!( - url.ok().unwrap().as_str(), - "https://youtube.com/watch/oHg5SJYRHA0" - ); - } -} diff --git a/src/server/output.rs b/src/server/output.rs index 5fc6fc83..fc688684 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -224,7 +224,7 @@ impl TransferEncoding { *eof = true; buf.extend_from_slice(b"0\r\n\r\n"); } else { - writeln!(buf.as_mut(), "{:X}\r", msg.len()) + writeln!(Writer(buf), "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; buf.reserve(msg.len() + 2); @@ -268,87 +268,18 @@ impl TransferEncoding { } } -impl io::Write for TransferEncoding { - #[inline] +struct Writer<'a>(pub &'a mut BytesMut); + +impl<'a> io::Write for Writer<'a> { fn write(&mut self, buf: &[u8]) -> io::Result { - // if self.buf.is_some() { - // self.encode(buf)?; - // } + self.0.extend_from_slice(buf); Ok(buf.len()) } - - #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } } -struct AcceptEncoding { - encoding: ContentEncoding, - quality: f64, -} - -impl Eq for AcceptEncoding {} - -impl Ord for AcceptEncoding { - fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering { - if self.quality > other.quality { - cmp::Ordering::Less - } else if self.quality < other.quality { - cmp::Ordering::Greater - } else { - cmp::Ordering::Equal - } - } -} - -impl PartialOrd for AcceptEncoding { - fn partial_cmp(&self, other: &AcceptEncoding) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for AcceptEncoding { - fn eq(&self, other: &AcceptEncoding) -> bool { - self.quality == other.quality - } -} - -impl AcceptEncoding { - fn new(tag: &str) -> Option { - let parts: Vec<&str> = tag.split(';').collect(); - let encoding = match parts.len() { - 0 => return None, - _ => ContentEncoding::from(parts[0]), - }; - let quality = match parts.len() { - 1 => encoding.quality(), - _ => match f64::from_str(parts[1]) { - Ok(q) => q, - Err(_) => 0.0, - }, - }; - Some(AcceptEncoding { encoding, quality }) - } - - /// Parse a raw Accept-Encoding header value into an ordered list. - pub fn parse(raw: &str) -> ContentEncoding { - let mut encodings: Vec<_> = raw - .replace(' ', "") - .split(',') - .map(|l| AcceptEncoding::new(l)) - .collect(); - encodings.sort(); - - for enc in encodings { - if let Some(enc) = enc { - return enc.encoding; - } - } - ContentEncoding::Identity - } -} - #[cfg(test)] mod tests { use super::*; @@ -356,14 +287,14 @@ mod tests { #[test] fn test_chunked_te() { - let bytes = BytesMut::new(); - let mut enc = TransferEncoding::chunked(bytes); + let mut bytes = BytesMut::new(); + let mut enc = TransferEncoding::chunked(); { - assert!(!enc.encode(b"test").ok().unwrap()); - assert!(enc.encode(b"").ok().unwrap()); + assert!(!enc.encode(b"test", &mut bytes).ok().unwrap()); + assert!(enc.encode(b"", &mut bytes).ok().unwrap()); } assert_eq!( - enc.buf_mut().take().freeze(), + bytes.take().freeze(), Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") ); } From c99f9eaa63b87670fcf6cb5f15f859ed761bfdb8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 05:59:02 -0700 Subject: [PATCH 0723/1635] Update test_h1v2.rs --- tests/test_h1v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index d06777b7..77a6ecae 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -23,7 +23,7 @@ fn test_h1_v2() { let settings = ServiceConfig::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) - .client_shutdown(1000) + .client_disconnect(1000) .server_hostname("localhost") .server_address(addr) .finish(); From c24a8f4c2d121a02e828be52712b9d4b43004f29 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 07:02:09 -0700 Subject: [PATCH 0724/1635] remove high level apis --- Cargo.toml | 17 +- src/h1/codec.rs | 2 +- src/h1/dispatcher.rs | 7 +- src/h1/mod.rs | 1 + src/{server/output.rs => h1/response.rs} | 0 src/header.rs | 265 +++++++ src/header/common/accept.rs | 159 ---- src/header/common/accept_charset.rs | 69 -- src/header/common/accept_encoding.rs | 72 -- src/header/common/accept_language.rs | 75 -- src/header/common/allow.rs | 82 -- src/header/common/cache_control.rs | 254 ------- src/header/common/content_disposition.rs | 915 ----------------------- src/header/common/content_language.rs | 65 -- src/header/common/content_range.rs | 210 ------ src/header/common/content_type.rs | 122 --- src/header/common/date.rs | 42 -- src/header/common/etag.rs | 96 --- src/header/common/expires.rs | 39 - src/header/common/if_match.rs | 70 -- src/header/common/if_modified_since.rs | 39 - src/header/common/if_none_match.rs | 92 --- src/header/common/if_range.rs | 117 --- src/header/common/if_unmodified_since.rs | 40 - src/header/common/last_modified.rs | 38 - src/header/common/mod.rs | 350 --------- src/header/common/range.rs | 434 ----------- src/header/mod.rs | 471 ------------ src/header/shared/charset.rs | 152 ---- src/header/shared/encoding.rs | 59 -- src/header/shared/entity.rs | 266 ------- src/header/shared/httpdate.rs | 119 --- src/header/shared/mod.rs | 14 - src/header/shared/quality_item.rs | 294 -------- src/httpresponse.rs | 4 +- src/lib.rs | 13 +- src/server/input.rs | 288 ------- src/server/mod.rs | 3 - src/server/service.rs | 273 ------- src/ws/context.rs | 334 --------- 40 files changed, 273 insertions(+), 5689 deletions(-) rename src/{server/output.rs => h1/response.rs} (100%) create mode 100644 src/header.rs delete mode 100644 src/header/common/accept.rs delete mode 100644 src/header/common/accept_charset.rs delete mode 100644 src/header/common/accept_encoding.rs delete mode 100644 src/header/common/accept_language.rs delete mode 100644 src/header/common/allow.rs delete mode 100644 src/header/common/cache_control.rs delete mode 100644 src/header/common/content_disposition.rs delete mode 100644 src/header/common/content_language.rs delete mode 100644 src/header/common/content_range.rs delete mode 100644 src/header/common/content_type.rs delete mode 100644 src/header/common/date.rs delete mode 100644 src/header/common/etag.rs delete mode 100644 src/header/common/expires.rs delete mode 100644 src/header/common/if_match.rs delete mode 100644 src/header/common/if_modified_since.rs delete mode 100644 src/header/common/if_none_match.rs delete mode 100644 src/header/common/if_range.rs delete mode 100644 src/header/common/if_unmodified_since.rs delete mode 100644 src/header/common/last_modified.rs delete mode 100644 src/header/common/mod.rs delete mode 100644 src/header/common/range.rs delete mode 100644 src/header/mod.rs delete mode 100644 src/header/shared/charset.rs delete mode 100644 src/header/shared/encoding.rs delete mode 100644 src/header/shared/entity.rs delete mode 100644 src/header/shared/httpdate.rs delete mode 100644 src/header/shared/mod.rs delete mode 100644 src/header/shared/quality_item.rs delete mode 100644 src/server/input.rs delete mode 100644 src/server/service.rs delete mode 100644 src/ws/context.rs diff --git a/Cargo.toml b/Cargo.toml index 86012175..870f772e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["session", "brotli", "flate2-c", "cell"] +default = ["session", "cell"] # tls tls = ["native-tls", "tokio-tls", "actix-net/tls"] @@ -48,15 +48,6 @@ uds = ["tokio-uds"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] -# brotli encoding, requires c compiler -brotli = ["brotli2"] - -# miniz-sys backend for flate2 crate -flate2-c = ["flate2/miniz-sys"] - -# rust backend for flate2 crate -flate2-rust = ["flate2/rust_backend"] - cell = ["actix-net/cell"] [dependencies] @@ -70,23 +61,17 @@ http = "^0.1.8" httparse = "1.3" log = "0.4" mime = "0.3" -mime_guess = "2.0.0-alpha" percent-encoding = "1.0" rand = "0.5" -regex = "1.0" serde = "1.0" serde_json = "1.0" sha1 = "0.6" -smallvec = "0.6" time = "0.1" encoding = "0.2" -language-tags = "0.2" lazy_static = "1.0" serde_urlencoded = "^0.5.3" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } -brotli2 = { version="^0.3.2", optional = true } -flate2 = { version="^1.0.2", optional = true, default-features = false } failure = "^0.1.2" diff --git a/src/h1/codec.rs b/src/h1/codec.rs index ac54194a..dd6b26ba 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,6 +6,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::H1Decoder; pub use super::decoder::InMessage; +use super::response::{ResponseInfo, ResponseLength}; use body::Body; use error::ParseError; use helpers; @@ -13,7 +14,6 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{Method, Version}; use httpresponse::HttpResponse; use request::RequestPool; -use server::output::{ResponseInfo, ResponseLength}; /// Http response pub enum OutMessage { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f777648e..2b524556 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -12,14 +12,13 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use error::{ParseError, PayloadError}; -use payload::{Payload, PayloadStatus, PayloadWriter}; +use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; use config::ServiceConfig; use error::DispatchError; use httpresponse::HttpResponse; use request::Request; -use server::input::PayloadType; use super::codec::{Codec, InMessage, OutMessage}; @@ -50,7 +49,7 @@ where config: ServiceConfig, state: State, - payload: Option, + payload: Option, messages: VecDeque, ka_expire: Instant, @@ -316,7 +315,7 @@ where // payload let (ps, pl) = Payload::new(false); *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); + self.payload = Some(ps); self.messages.push_back(msg); } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 1a2bb018..1653227b 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -2,6 +2,7 @@ mod codec; mod decoder; mod dispatcher; +mod response; mod service; pub use self::codec::{Codec, InMessage, OutMessage}; diff --git a/src/server/output.rs b/src/h1/response.rs similarity index 100% rename from src/server/output.rs rename to src/h1/response.rs diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 00000000..7b32d6c2 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,265 @@ +//! Various http headers + +use std::fmt; +use std::str::FromStr; + +use bytes::{Bytes, BytesMut}; +use mime::Mime; +use modhttp::header::GetAll; +use modhttp::Error as HttpError; +use percent_encoding; + +pub use modhttp::header::*; + +use error::ParseError; +use httpmessage::HttpMessage; + +#[doc(hidden)] +/// A trait for any object that will represent a header field and value. +pub trait Header +where + Self: IntoHeaderValue, +{ + /// Returns the name of the header field + fn name() -> HeaderName; + + /// Parse a header + fn parse(msg: &T) -> Result; +} + +#[doc(hidden)] +/// A trait for any object that can be Converted to a `HeaderValue` +pub trait IntoHeaderValue: Sized { + /// The type returned in the event of a conversion error. + type Error: Into; + + /// Try to convert value to a Header value. + fn try_into(self) -> Result; +} + +impl IntoHeaderValue for HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + Ok(self) + } +} + +impl<'a> IntoHeaderValue for &'a str { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + self.parse() + } +} + +impl<'a> IntoHeaderValue for &'a [u8] { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_bytes(self) + } +} + +impl IntoHeaderValue for Bytes { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(self) + } +} + +impl IntoHeaderValue for Vec { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for String { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for Mime { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(format!("{}", self))) + } +} + +/// Represents supported types of content encodings +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ContentEncoding { + /// Automatically select encoding based on encoding negotiation + Auto, + /// A format using the Brotli algorithm + Br, + /// A format using the zlib structure with deflate algorithm + Deflate, + /// Gzip algorithm + Gzip, + /// Indicates the identity function (i.e. no compression, nor modification) + Identity, +} + +impl ContentEncoding { + #[inline] + /// Is the content compressed? + pub fn is_compression(self) -> bool { + match self { + ContentEncoding::Identity | ContentEncoding::Auto => false, + _ => true, + } + } + + #[inline] + /// Convert content encoding to string + pub fn as_str(self) -> &'static str { + match self { + ContentEncoding::Br => "br", + ContentEncoding::Gzip => "gzip", + ContentEncoding::Deflate => "deflate", + ContentEncoding::Identity | ContentEncoding::Auto => "identity", + } + } + + #[inline] + /// default quality value + pub fn quality(self) -> f64 { + match self { + ContentEncoding::Br => 1.1, + ContentEncoding::Gzip => 1.0, + ContentEncoding::Deflate => 0.9, + ContentEncoding::Identity | ContentEncoding::Auto => 0.1, + } + } +} + +// TODO: remove memory allocation +impl<'a> From<&'a str> for ContentEncoding { + fn from(s: &'a str) -> ContentEncoding { + match AsRef::::as_ref(&s.trim().to_lowercase()) { + "br" => ContentEncoding::Br, + "gzip" => ContentEncoding::Gzip, + "deflate" => ContentEncoding::Deflate, + _ => ContentEncoding::Identity, + } + } +} + +#[doc(hidden)] +pub(crate) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer { + buf: BytesMut::new(), + } + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl fmt::Write for Writer { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.buf.extend_from_slice(s.as_bytes()); + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { + fmt::write(self, args) + } +} + +#[inline] +#[doc(hidden)] +/// Reads a comma-delimited raw header into a Vec. +pub fn from_comma_delimited( + all: GetAll, +) -> Result, ParseError> { + let mut result = Vec::new(); + for h in all { + let s = h.to_str().map_err(|_| ParseError::Header)?; + result.extend( + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }).filter_map(|x| x.trim().parse().ok()), + ) + } + Ok(result) +} + +#[inline] +#[doc(hidden)] +/// Reads a single string when parsing a header. +pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { + if let Some(line) = val { + let line = line.to_str().map_err(|_| ParseError::Header)?; + if !line.is_empty() { + return T::from_str(line).or(Err(ParseError::Header)); + } + } + Err(ParseError::Header) +} + +#[inline] +#[doc(hidden)] +/// Format an array into a comma-delimited string. +pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result +where + T: fmt::Display, +{ + let mut iter = parts.iter(); + if let Some(part) = iter.next() { + fmt::Display::fmt(part, f)?; + } + for part in iter { + f.write_str(", ")?; + fmt::Display::fmt(part, f)?; + } + Ok(()) +} + +/// Percent encode a sequence of bytes with a character set defined in +/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] +/// +/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 +pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { + let encoded = + percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} +mod percent_encoding_http { + use percent_encoding; + + // internal module because macro is hard-coded to make a public item + // but we don't want to public export this item + define_encode_set! { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } +} diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs deleted file mode 100644 index 1ba321ce..00000000 --- a/src/header/common/accept.rs +++ /dev/null @@ -1,159 +0,0 @@ -use header::{qitem, QualityItem}; -use http::header as http; -use mime::{self, Mime}; - -header! { - /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) - /// - /// The `Accept` header field can be used by user agents to specify - /// response media types that are acceptable. Accept header fields can - /// be used to indicate that the request is specifically limited to a - /// small set of desired types, as in the case of a request for an - /// in-line image - /// - /// # ABNF - /// - /// ```text - /// Accept = #( media-range [ accept-params ] ) - /// - /// media-range = ( "*/*" - /// / ( type "/" "*" ) - /// / ( type "/" subtype ) - /// ) *( OWS ";" OWS parameter ) - /// accept-params = weight *( accept-ext ) - /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] - /// ``` - /// - /// # Example values - /// * `audio/*; q=0.2, audio/basic` - /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` - /// - /// # Examples - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), - /// ]) - /// ); - /// # } - /// ``` - (Accept, http::ACCEPT) => (QualityItem)+ - - test_accept { - // Tests from the RFC - test_header!( - test1, - vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), - ]))); - test_header!( - test2, - vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], - Some(HeaderField(vec![ - QualityItem::new(TEXT_PLAIN, q(500)), - qitem(TEXT_HTML), - QualityItem::new( - "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), - ]))); - // Custom tests - test_header!( - test3, - vec![b"text/plain; charset=utf-8"], - Some(Accept(vec![ - qitem(TEXT_PLAIN_UTF_8), - ]))); - test_header!( - test4, - vec![b"text/plain; charset=utf-8; q=0.5"], - Some(Accept(vec![ - QualityItem::new(TEXT_PLAIN_UTF_8, - q(500)), - ]))); - - #[test] - fn test_fuzzing1() { - use test::TestRequest; - let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish(); - let header = Accept::parse(&req); - assert!(header.is_ok()); - } - } -} - -impl Accept { - /// A constructor to easily create `Accept: */*`. - pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) - } - - /// A constructor to easily create `Accept: application/json`. - pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) - } - - /// A constructor to easily create `Accept: text/*`. - pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) - } - - /// A constructor to easily create `Accept: image/*`. - pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) - } -} diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs deleted file mode 100644 index 49a7237a..00000000 --- a/src/header/common/accept_charset.rs +++ /dev/null @@ -1,69 +0,0 @@ -use header::{Charset, QualityItem, ACCEPT_CHARSET}; - -header! { - /// `Accept-Charset` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) - /// - /// The `Accept-Charset` header field can be sent by a user agent to - /// indicate what charsets are acceptable in textual response content. - /// This field allows user agents capable of understanding more - /// comprehensive or special-purpose charsets to signal that capability - /// to an origin server that is capable of representing information in - /// those charsets. - /// - /// # ABNF - /// - /// ```text - /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) - /// ``` - /// - /// # Example values - /// * `iso-8859-5, unicode-1-1;q=0.8` - /// - /// # Examples - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![ - /// QualityItem::new(Charset::Us_Ascii, q(900)), - /// QualityItem::new(Charset::Iso_8859_10, q(200)), - /// ]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) - /// ); - /// # } - /// ``` - (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ - - test_accept_charset { - /// Test case from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); - } -} diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs deleted file mode 100644 index c90f529b..00000000 --- a/src/header/common/accept_encoding.rs +++ /dev/null @@ -1,72 +0,0 @@ -use header::{Encoding, QualityItem}; - -header! { - /// `Accept-Encoding` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) - /// - /// The `Accept-Encoding` header field can be used by user agents to - /// indicate what response content-codings are - /// acceptable in the response. An `identity` token is used as a synonym - /// for "no encoding" in order to communicate when no encoding is - /// preferred. - /// - /// # ABNF - /// - /// ```text - /// Accept-Encoding = #( codings [ weight ] ) - /// codings = content-coding / "identity" / "*" - /// ``` - /// - /// # Example values - /// * `compress, gzip` - /// * `` - /// * `*` - /// * `compress;q=0.5, gzip;q=1` - /// * `gzip;q=1.0, identity; q=0.5, *;q=0` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), - /// ]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), - /// ]) - /// ); - /// ``` - (AcceptEncoding, "Accept-Encoding") => (QualityItem)* - - test_accept_encoding { - // From the RFC - test_header!(test1, vec![b"compress, gzip"]); - test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - test_header!(test3, vec![b"*"]); - // Note: Removed quality 1 from gzip - test_header!(test4, vec![b"compress;q=0.5, gzip"]); - // Note: Removed quality 1 from gzip - test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); - } -} diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs deleted file mode 100644 index 25fd97df..00000000 --- a/src/header/common/accept_language.rs +++ /dev/null @@ -1,75 +0,0 @@ -use header::{QualityItem, ACCEPT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Accept-Language` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) - /// - /// The `Accept-Language` header field can be used by user agents to - /// indicate the set of natural languages that are preferred in the - /// response. - /// - /// # ABNF - /// - /// ```text - /// Accept-Language = 1#( language-range [ weight ] ) - /// language-range = - /// ``` - /// - /// # Example values - /// * `da, en-gb;q=0.8, en;q=0.7` - /// * `en-us;q=1.0, en;q=0.5, fr` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// # extern crate language_tags; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// let mut langtag: LanguageTag = Default::default(); - /// langtag.language = Some("en".to_owned()); - /// langtag.region = Some("US".to_owned()); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; - /// # - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag!(da)), - /// QualityItem::new(langtag!(en;;;GB), q(800)), - /// QualityItem::new(langtag!(en), q(700)), - /// ]) - /// ); - /// # } - /// ``` - (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ - - test_accept_language { - // From the RFC - test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); - // Own test - test_header!( - test2, vec![b"en-US, en; q=0.5, fr"], - Some(AcceptLanguage(vec![ - qitem("en-US".parse().unwrap()), - QualityItem::new("en".parse().unwrap(), q(500)), - qitem("fr".parse().unwrap()), - ]))); - } -} diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs deleted file mode 100644 index 089c823d..00000000 --- a/src/header/common/allow.rs +++ /dev/null @@ -1,82 +0,0 @@ -use http::header; -use http::Method; - -header! { - /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) - /// - /// The `Allow` header field lists the set of methods advertised as - /// supported by the target resource. The purpose of this field is - /// strictly to inform the recipient of valid request methods associated - /// with the resource. - /// - /// # ABNF - /// - /// ```text - /// Allow = #method - /// ``` - /// - /// # Example values - /// * `GET, HEAD, PUT` - /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` - /// * `` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Allow; - /// use actix_http::http::Method; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// Allow(vec![Method::GET]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::{Method, header::Allow}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// Allow(vec![ - /// Method::GET, - /// Method::POST, - /// Method::PATCH, - /// ]) - /// ); - /// # } - /// ``` - (Allow, header::ALLOW) => (Method)* - - test_allow { - // From the RFC - test_header!( - test1, - vec![b"GET, HEAD, PUT"], - Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); - // Own tests - test_header!( - test2, - vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], - Some(HeaderField(vec![ - Method::OPTIONS, - Method::GET, - Method::PUT, - Method::POST, - Method::DELETE, - Method::HEAD, - Method::TRACE, - Method::CONNECT, - Method::PATCH]))); - test_header!( - test3, - vec![b""], - Some(HeaderField(Vec::::new()))); - } -} diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs deleted file mode 100644 index 4379b6f7..00000000 --- a/src/header/common/cache_control.rs +++ /dev/null @@ -1,254 +0,0 @@ -use header::{fmt_comma_delimited, from_comma_delimited}; -use header::{Header, IntoHeaderValue, Writer}; -use http::header; -use std::fmt::{self, Write}; -use std::str::FromStr; - -/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) -/// -/// The `Cache-Control` header field is used to specify directives for -/// caches along the request/response chain. Such cache directives are -/// unidirectional in that the presence of a directive in a request does -/// not imply that the same directive is to be given in the response. -/// -/// # ABNF -/// -/// ```text -/// Cache-Control = 1#cache-directive -/// cache-directive = token [ "=" ( token / quoted-string ) ] -/// ``` -/// -/// # Example values -/// -/// * `no-cache` -/// * `private, community="UCI"` -/// * `max-age=30` -/// -/// # Examples -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); -/// ``` -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(CacheControl(vec![ -/// CacheDirective::NoCache, -/// CacheDirective::Private, -/// CacheDirective::MaxAge(360u32), -/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), -/// ])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct CacheControl(pub Vec); - -__hyper__deref!(CacheControl => Vec); - -//TODO: this could just be the header! macro -impl Header for CacheControl { - fn name() -> header::HeaderName { - header::CACHE_CONTROL - } - - #[inline] - fn parse(msg: &T) -> Result - where - T: ::HttpMessage, - { - let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; - if !directives.is_empty() { - Ok(CacheControl(directives)) - } else { - Err(::error::ParseError::Header) - } - } -} - -impl fmt::Display for CacheControl { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) - } -} - -impl IntoHeaderValue for CacheControl { - type Error = header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_shared(writer.take()) - } -} - -/// `CacheControl` contains a list of these directives. -#[derive(PartialEq, Clone, Debug)] -pub enum CacheDirective { - /// "no-cache" - NoCache, - /// "no-store" - NoStore, - /// "no-transform" - NoTransform, - /// "only-if-cached" - OnlyIfCached, - - // request directives - /// "max-age=delta" - MaxAge(u32), - /// "max-stale=delta" - MaxStale(u32), - /// "min-fresh=delta" - MinFresh(u32), - - // response directives - /// "must-revalidate" - MustRevalidate, - /// "public" - Public, - /// "private" - Private, - /// "proxy-revalidate" - ProxyRevalidate, - /// "s-maxage=delta" - SMaxAge(u32), - - /// Extension directives. Optionally include an argument. - Extension(String, Option), -} - -impl fmt::Display for CacheDirective { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::CacheDirective::*; - fmt::Display::fmt( - match *self { - NoCache => "no-cache", - NoStore => "no-store", - NoTransform => "no-transform", - OnlyIfCached => "only-if-cached", - - MaxAge(secs) => return write!(f, "max-age={}", secs), - MaxStale(secs) => return write!(f, "max-stale={}", secs), - MinFresh(secs) => return write!(f, "min-fresh={}", secs), - - MustRevalidate => "must-revalidate", - Public => "public", - Private => "private", - ProxyRevalidate => "proxy-revalidate", - SMaxAge(secs) => return write!(f, "s-maxage={}", secs), - - Extension(ref name, None) => &name[..], - Extension(ref name, Some(ref arg)) => { - return write!(f, "{}={}", name, arg) - } - }, - f, - ) - } -} - -impl FromStr for CacheDirective { - type Err = Option<::Err>; - fn from_str(s: &str) -> Result::Err>> { - use self::CacheDirective::*; - match s { - "no-cache" => Ok(NoCache), - "no-store" => Ok(NoStore), - "no-transform" => Ok(NoTransform), - "only-if-cached" => Ok(OnlyIfCached), - "must-revalidate" => Ok(MustRevalidate), - "public" => Ok(Public), - "private" => Ok(Private), - "proxy-revalidate" => Ok(ProxyRevalidate), - "" => Err(None), - _ => match s.find('=') { - Some(idx) if idx + 1 < s.len() => { - match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { - ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), - ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), - ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), - ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => { - Ok(Extension(left.to_owned(), Some(right.to_owned()))) - } - } - } - Some(_) => Err(None), - None => Ok(Extension(s.to_owned(), None)), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use header::Header; - use test::TestRequest; - - #[test] - fn test_parse_multiple_headers() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::NoCache, - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_argument() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::MaxAge(100), - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_quote_form() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![CacheDirective::MaxAge(200)])) - ) - } - - #[test] - fn test_parse_extension() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::Extension("foo".to_owned(), None), - CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), - ])) - ) - } - - #[test] - fn test_parse_bad_syntax() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); - let cache: Result = Header::parse(&req); - assert_eq!(cache.ok(), None) - } -} diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs deleted file mode 100644 index 0efc4fb0..00000000 --- a/src/header/common/content_disposition.rs +++ /dev/null @@ -1,915 +0,0 @@ -// # References -// -// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt -// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt -// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ -// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml - -use header; -use header::ExtendedValue; -use header::{Header, IntoHeaderValue, Writer}; -use regex::Regex; - -use std::fmt::{self, Write}; - -/// Split at the index of the first `needle` if it exists or at the end. -fn split_once(haystack: &str, needle: char) -> (&str, &str) { - haystack.find(needle).map_or_else( - || (haystack, ""), - |sc| { - let (first, last) = haystack.split_at(sc); - (first, last.split_at(1).1) - }, - ) -} - -/// Split at the index of the first `needle` if it exists or at the end, trim the right of the -/// first part and the left of the last part. -fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { - let (first, last) = split_once(haystack, needle); - (first.trim_right(), last.trim_left()) -} - -/// The implied disposition of the content of the HTTP body. -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionType { - /// Inline implies default processing - Inline, - /// Attachment implies that the recipient should prompt the user to save the response locally, - /// rather than process it normally (as per its media type). - Attachment, - /// Used in *multipart/form-data* as defined in - /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. - FormData, - /// Extension type. Should be handled by recipients the same way as Attachment - Ext(String), -} - -impl<'a> From<&'a str> for DispositionType { - fn from(origin: &'a str) -> DispositionType { - if origin.eq_ignore_ascii_case("inline") { - DispositionType::Inline - } else if origin.eq_ignore_ascii_case("attachment") { - DispositionType::Attachment - } else if origin.eq_ignore_ascii_case("form-data") { - DispositionType::FormData - } else { - DispositionType::Ext(origin.to_owned()) - } - } -} - -/// Parameter in [`ContentDisposition`]. -/// -/// # Examples -/// ``` -/// use actix_http::http::header::DispositionParam; -/// -/// let param = DispositionParam::Filename(String::from("sample.txt")); -/// assert!(param.is_filename()); -/// assert_eq!(param.as_filename().unwrap(), "sample.txt"); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionParam { - /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from - /// the form. - Name(String), - /// A plain file name. - Filename(String), - /// An extended file name. It must not exist for `ContentType::Formdata` according to - /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). - FilenameExt(ExtendedValue), - /// An unrecognized regular parameter as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should - /// ignore unrecognizable parameters. - Unknown(String, String), - /// An unrecognized extended paramater as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single - /// trailling asterisk is not included. Recipients should ignore unrecognizable parameters. - UnknownExt(String, ExtendedValue), -} - -impl DispositionParam { - /// Returns `true` if the paramater is [`Name`](DispositionParam::Name). - #[inline] - pub fn is_name(&self) -> bool { - self.as_name().is_some() - } - - /// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename). - #[inline] - pub fn is_filename(&self) -> bool { - self.as_filename().is_some() - } - - /// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt). - #[inline] - pub fn is_filename_ext(&self) -> bool { - self.as_filename_ext().is_some() - } - - /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` - #[inline] - /// matches. - pub fn is_unknown>(&self, name: T) -> bool { - self.as_unknown(name).is_some() - } - - /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the - /// `name` matches. - #[inline] - pub fn is_unknown_ext>(&self, name: T) -> bool { - self.as_unknown_ext(name).is_some() - } - - /// Returns the name if applicable. - #[inline] - pub fn as_name(&self) -> Option<&str> { - match self { - DispositionParam::Name(ref name) => Some(name.as_str()), - _ => None, - } - } - - /// Returns the filename if applicable. - #[inline] - pub fn as_filename(&self) -> Option<&str> { - match self { - DispositionParam::Filename(ref filename) => Some(filename.as_str()), - _ => None, - } - } - - /// Returns the filename* if applicable. - #[inline] - pub fn as_filename_ext(&self) -> Option<&ExtendedValue> { - match self { - DispositionParam::FilenameExt(ref value) => Some(value), - _ => None, - } - } - - /// Returns the value of the unrecognized regular parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown>(&self, name: T) -> Option<&str> { - match self { - DispositionParam::Unknown(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value.as_str()) - } - _ => None, - } - } - - /// Returns the value of the unrecognized extended parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - match self { - DispositionParam::UnknownExt(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value) - } - _ => None, - } - } -} - -/// A *Content-Disposition* header. It is compatible to be used either as -/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) -/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as -/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) -/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). -/// -/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if -/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as -/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be -/// used to attach additional metadata, such as the filename to use when saving the response payload -/// locally. -/// -/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that -/// can be used on the subpart of a multipart body to give information about the field it applies to. -/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body -/// itself, *Content-Disposition* has no effect. -/// -/// # ABNF - -/// ```text -/// content-disposition = "Content-Disposition" ":" -/// disposition-type *( ";" disposition-parm ) -/// -/// disposition-type = "inline" | "attachment" | disp-ext-type -/// ; case-insensitive -/// -/// disp-ext-type = token -/// -/// disposition-parm = filename-parm | disp-ext-parm -/// -/// filename-parm = "filename" "=" value -/// | "filename*" "=" ext-value -/// -/// disp-ext-parm = token "=" value -/// | ext-token "=" ext-value -/// -/// ext-token = -/// ``` -/// -/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within -/// *multipart/form-data*. -/// -/// # Example -/// -/// ``` -/// use actix_http::http::header::{ -/// Charset, ContentDisposition, DispositionParam, DispositionType, -/// ExtendedValue, -/// }; -/// -/// let cd1 = ContentDisposition { -/// disposition: DispositionType::Attachment, -/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue { -/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename -/// language_tag: None, // The optional language tag (see `language-tag` crate) -/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename -/// })], -/// }; -/// assert!(cd1.is_attachment()); -/// assert!(cd1.get_filename_ext().is_some()); -/// -/// let cd2 = ContentDisposition { -/// disposition: DispositionType::FormData, -/// parameters: vec![ -/// DispositionParam::Name(String::from("file")), -/// DispositionParam::Filename(String::from("bill.odt")), -/// ], -/// }; -/// assert_eq!(cd2.get_name(), Some("file")); // field name -/// assert_eq!(cd2.get_filename(), Some("bill.odt")); -/// ``` -/// -/// # WARN -/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly -/// change to match local file system conventions if applicable, and do not use directory path -/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3) -/// . -#[derive(Clone, Debug, PartialEq)] -pub struct ContentDisposition { - /// The disposition type - pub disposition: DispositionType, - /// Disposition parameters - pub parameters: Vec, -} - -impl ContentDisposition { - /// Parse a raw Content-Disposition header value. - pub fn from_raw(hv: &header::HeaderValue) -> Result { - // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible - // ASCII characters. So `hv.as_bytes` is necessary here. - let hv = String::from_utf8(hv.as_bytes().to_vec()) - .map_err(|_| ::error::ParseError::Header)?; - let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); - if disp_type.is_empty() { - return Err(::error::ParseError::Header); - } - let mut cd = ContentDisposition { - disposition: disp_type.into(), - parameters: Vec::new(), - }; - - while !left.is_empty() { - let (param_name, new_left) = split_once_and_trim(left, '='); - if param_name.is_empty() || param_name == "*" || new_left.is_empty() { - return Err(::error::ParseError::Header); - } - left = new_left; - if param_name.ends_with('*') { - // extended parameters - let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk - let (ext_value, new_left) = split_once_and_trim(left, ';'); - left = new_left; - let ext_value = header::parse_extended_value(ext_value)?; - - let param = if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::FilenameExt(ext_value) - } else { - DispositionParam::UnknownExt(param_name.to_owned(), ext_value) - }; - cd.parameters.push(param); - } else { - // regular parameters - let value = if left.starts_with('\"') { - // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 - let mut escaping = false; - let mut quoted_string = vec![]; - let mut end = None; - // search for closing quote - for (i, &c) in left.as_bytes().iter().skip(1).enumerate() { - if escaping { - escaping = false; - quoted_string.push(c); - } else if c == 0x5c { - // backslash - escaping = true; - } else if c == 0x22 { - // double quote - end = Some(i + 1); // cuz skipped 1 for the leading quote - break; - } else { - quoted_string.push(c); - } - } - left = &left[end.ok_or(::error::ParseError::Header)? + 1..]; - left = split_once(left, ';').1.trim_left(); - // In fact, it should not be Err if the above code is correct. - String::from_utf8(quoted_string) - .map_err(|_| ::error::ParseError::Header)? - } else { - // token: won't contains semicolon according to RFC 2616 Section 2.2 - let (token, new_left) = split_once_and_trim(left, ';'); - left = new_left; - token.to_owned() - }; - if value.is_empty() { - return Err(::error::ParseError::Header); - } - - let param = if param_name.eq_ignore_ascii_case("name") { - DispositionParam::Name(value) - } else if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::Filename(value) - } else { - DispositionParam::Unknown(param_name.to_owned(), value) - }; - cd.parameters.push(param); - } - } - - Ok(cd) - } - - /// Returns `true` if it is [`Inline`](DispositionType::Inline). - pub fn is_inline(&self) -> bool { - match self.disposition { - DispositionType::Inline => true, - _ => false, - } - } - - /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). - pub fn is_attachment(&self) -> bool { - match self.disposition { - DispositionType::Attachment => true, - _ => false, - } - } - - /// Returns `true` if it is [`FormData`](DispositionType::FormData). - pub fn is_form_data(&self) -> bool { - match self.disposition { - DispositionType::FormData => true, - _ => false, - } - } - - /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. - pub fn is_ext>(&self, disp_type: T) -> bool { - match self.disposition { - DispositionType::Ext(ref t) - if t.eq_ignore_ascii_case(disp_type.as_ref()) => - { - true - } - _ => false, - } - } - - /// Return the value of *name* if exists. - pub fn get_name(&self) -> Option<&str> { - self.parameters.iter().filter_map(|p| p.as_name()).nth(0) - } - - /// Return the value of *filename* if exists. - pub fn get_filename(&self) -> Option<&str> { - self.parameters - .iter() - .filter_map(|p| p.as_filename()) - .nth(0) - } - - /// Return the value of *filename\** if exists. - pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { - self.parameters - .iter() - .filter_map(|p| p.as_filename_ext()) - .nth(0) - } - - /// Return the value of the parameter which the `name` matches. - pub fn get_unknown>(&self, name: T) -> Option<&str> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown(name)) - .nth(0) - } - - /// Return the value of the extended parameter which the `name` matches. - pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown_ext(name)) - .nth(0) - } -} - -impl IntoHeaderValue for ContentDisposition { - type Error = header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_shared(writer.take()) - } -} - -impl Header for ContentDisposition { - fn name() -> header::HeaderName { - header::CONTENT_DISPOSITION - } - - fn parse(msg: &T) -> Result { - if let Some(h) = msg.headers().get(Self::name()) { - Self::from_raw(&h) - } else { - Err(::error::ParseError::Header) - } - } -} - -impl fmt::Display for DispositionType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DispositionType::Inline => write!(f, "inline"), - DispositionType::Attachment => write!(f, "attachment"), - DispositionType::FormData => write!(f, "form-data"), - DispositionType::Ext(ref s) => write!(f, "{}", s), - } - } -} - -impl fmt::Display for DispositionParam { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and - // backslash should be escaped in quoted-string (i.e. "foobar"). - // Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . - lazy_static! { - static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); - } - match self { - DispositionParam::Name(ref value) => write!(f, "name={}", value), - DispositionParam::Filename(ref value) => { - write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) - } - DispositionParam::Unknown(ref name, ref value) => write!( - f, - "{}=\"{}\"", - name, - &RE.replace_all(value, "\\$0").as_ref() - ), - DispositionParam::FilenameExt(ref ext_value) => { - write!(f, "filename*={}", ext_value) - } - DispositionParam::UnknownExt(ref name, ref ext_value) => { - write!(f, "{}*={}", name, ext_value) - } - } - } -} - -impl fmt::Display for ContentDisposition { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.disposition)?; - self.parameters - .iter() - .map(|param| write!(f, "; {}", param)) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::{ContentDisposition, DispositionParam, DispositionType}; - use header::shared::Charset; - use header::{ExtendedValue, HeaderValue}; - #[test] - fn test_from_raw_basic() { - assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); - - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"sample.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("inline; filename=image.jpg"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Unknown( - String::from("creation-date"), - "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(), - )], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extended() { - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extra_whitespace() { - let a = HeaderValue::from_static( - "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_unordered() { - let a = HeaderValue::from_static( - "form-data; dummy=3; filename=\"sample.png\" ; name=upload;", - // Actually, a trailling semolocon is not compliant. But it is fine to accept. - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - DispositionParam::Name("upload".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_str( - "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"", - ).unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_only_disp() { - let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) - .unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = - ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = ContentDisposition::from_raw(&HeaderValue::from_static( - "unknown-disp-param", - )).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Ext(String::from("unknown-disp-param")), - parameters: vec![], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_mixed_case() { - let a = HeaderValue::from_str( - "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"", - ).unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_unicode() { - /* RFC7578 Section 4.2: - Some commonly deployed systems use multipart/form-data with file names directly encoded - including octets outside the US-ASCII range. The encoding used for the file names is - typically UTF-8, although HTML forms will use the charset associated with the form. - - Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. - (And now, only UTF-8 is handled by this implementation.) - */ - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") - .unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from("文件.webp")), - ], - }; - assert_eq!(a, b); - - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx\"").unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from( - "余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx", - )), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_escape() { - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename( - ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g'] - .iter() - .collect(), - ), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_semicolon() { - let a = - HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![DispositionParam::Filename(String::from( - "A semicolon here;.pdf", - ))], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_uncessary_percent_decode() { - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74.png")), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_param_value_missing() { - let a = HeaderValue::from_static("form-data; name=upload ; filename="); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; filename= "); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_from_raw_param_name_missing() { - let a = HeaderValue::from_static("inline; =\"test.txt\""); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; =diary.odt"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; ="); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_display_extended() { - let as_string = - "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a = HeaderValue::from_static("attachment; filename=colourful.csv"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"colourful.csv\"".to_owned(), - display_rendered - ); - } - - #[test] - fn test_display_quote() { - let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\""; - as_string - .find(['\\', '\"'].iter().collect::().as_str()) - .unwrap(); // ensure `\"` is there - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - } - - #[test] - fn test_display_space_tab() { - let as_string = "form-data; name=upload; filename=\"Space here.png\""; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered); - } - - #[test] - fn test_display_control_characters() { - /* let a = "attachment; filename=\"carriage\rreturn.png\""; - let a = HeaderValue::from_static(a); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"carriage\\\rreturn.png\"", - display_rendered - );*/ - // No way to create a HeaderValue containing a carriage return. - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered); - } - - #[test] - fn test_param_methods() { - let param = DispositionParam::Filename(String::from("sample.txt")); - assert!(param.is_filename()); - assert_eq!(param.as_filename().unwrap(), "sample.txt"); - - let param = DispositionParam::Unknown(String::from("foo"), String::from("bar")); - assert!(param.is_unknown("foo")); - assert_eq!(param.as_unknown("fOo"), Some("bar")); - } - - #[test] - fn test_disposition_methods() { - let cd = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(cd.get_name(), Some("upload")); - assert_eq!(cd.get_unknown("dummy"), Some("3")); - assert_eq!(cd.get_filename(), Some("sample.png")); - assert_eq!(cd.get_unknown_ext("dummy"), None); - assert_eq!(cd.get_unknown("duMMy"), Some("3")); - } -} diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs deleted file mode 100644 index c1f87d51..00000000 --- a/src/header/common/content_language.rs +++ /dev/null @@ -1,65 +0,0 @@ -use header::{QualityItem, CONTENT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Content-Language` header, defined in - /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) - /// - /// The `Content-Language` header field describes the natural language(s) - /// of the intended audience for the representation. Note that this - /// might not be equivalent to all the languages used within the - /// representation. - /// - /// # ABNF - /// - /// ```text - /// Content-Language = 1#language-tag - /// ``` - /// - /// # Example values - /// - /// * `da` - /// * `mi, en` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(en)), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(da)), - /// qitem(langtag!(en;;;GB)), - /// ]) - /// ); - /// # } - /// ``` - (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ - - test_content_language { - test_header!(test1, vec![b"da"]); - test_header!(test2, vec![b"mi, en"]); - } -} diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs deleted file mode 100644 index 999307e2..00000000 --- a/src/header/common/content_range.rs +++ /dev/null @@ -1,210 +0,0 @@ -use error::ParseError; -use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, - CONTENT_RANGE}; -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -header! { - /// `Content-Range` header, defined in - /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) - (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] - - test_content_range { - test_header!(test_bytes, - vec![b"bytes 0-499/500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: Some(500) - }))); - - test_header!(test_bytes_unknown_len, - vec![b"bytes 0-499/*"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: None - }))); - - test_header!(test_bytes_unknown_range, - vec![b"bytes */500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: None, - instance_length: Some(500) - }))); - - test_header!(test_unregistered, - vec![b"seconds 1-2"], - Some(ContentRange(ContentRangeSpec::Unregistered { - unit: "seconds".to_owned(), - resp: "1-2".to_owned() - }))); - - test_header!(test_no_len, - vec![b"bytes 0-499"], - None::); - - test_header!(test_only_unit, - vec![b"bytes"], - None::); - - test_header!(test_end_less_than_start, - vec![b"bytes 499-0/500"], - None::); - - test_header!(test_blank, - vec![b""], - None::); - - test_header!(test_bytes_many_spaces, - vec![b"bytes 1-2/500 3"], - None::); - - test_header!(test_bytes_many_slashes, - vec![b"bytes 1-2/500/600"], - None::); - - test_header!(test_bytes_many_dashes, - vec![b"bytes 1-2-3/500"], - None::); - - } -} - -/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) -/// -/// # ABNF -/// -/// ```text -/// Content-Range = byte-content-range -/// / other-content-range -/// -/// byte-content-range = bytes-unit SP -/// ( byte-range-resp / unsatisfied-range ) -/// -/// byte-range-resp = byte-range "/" ( complete-length / "*" ) -/// byte-range = first-byte-pos "-" last-byte-pos -/// unsatisfied-range = "*/" complete-length -/// -/// complete-length = 1*DIGIT -/// -/// other-content-range = other-range-unit SP other-range-resp -/// other-range-resp = *CHAR -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum ContentRangeSpec { - /// Byte range - Bytes { - /// First and last bytes of the range, omitted if request could not be - /// satisfied - range: Option<(u64, u64)>, - - /// Total length of the instance, can be omitted if unknown - instance_length: Option, - }, - - /// Custom range, with unit not registered at IANA - Unregistered { - /// other-range-unit - unit: String, - - /// other-range-resp - resp: String, - }, -} - -fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { - let mut iter = s.splitn(2, separator); - match (iter.next(), iter.next()) { - (Some(a), Some(b)) => Some((a, b)), - _ => None, - } -} - -impl FromStr for ContentRangeSpec { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - let res = match split_in_two(s, ' ') { - Some(("bytes", resp)) => { - let (range, instance_length) = - split_in_two(resp, '/').ok_or(ParseError::Header)?; - - let instance_length = if instance_length == "*" { - None - } else { - Some(instance_length - .parse() - .map_err(|_| ParseError::Header)?) - }; - - let range = if range == "*" { - None - } else { - let (first_byte, last_byte) = - split_in_two(range, '-').ok_or(ParseError::Header)?; - let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?; - let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; - if last_byte < first_byte { - return Err(ParseError::Header); - } - Some((first_byte, last_byte)) - }; - - ContentRangeSpec::Bytes { - range, - instance_length, - } - } - Some((unit, resp)) => ContentRangeSpec::Unregistered { - unit: unit.to_owned(), - resp: resp.to_owned(), - }, - _ => return Err(ParseError::Header), - }; - Ok(res) - } -} - -impl Display for ContentRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ContentRangeSpec::Bytes { - range, - instance_length, - } => { - f.write_str("bytes ")?; - match range { - Some((first_byte, last_byte)) => { - write!(f, "{}-{}", first_byte, last_byte)?; - } - None => { - f.write_str("*")?; - } - }; - f.write_str("/")?; - if let Some(v) = instance_length { - write!(f, "{}", v) - } else { - f.write_str("*") - } - } - ContentRangeSpec::Unregistered { - ref unit, - ref resp, - } => { - f.write_str(unit)?; - f.write_str(" ")?; - f.write_str(resp) - } - } - } -} - -impl IntoHeaderValue for ContentRangeSpec { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_shared(writer.take()) - } -} diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs deleted file mode 100644 index 3286d4ca..00000000 --- a/src/header/common/content_type.rs +++ /dev/null @@ -1,122 +0,0 @@ -use header::CONTENT_TYPE; -use mime::{self, Mime}; - -header! { - /// `Content-Type` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) - /// - /// The `Content-Type` header field indicates the media type of the - /// associated representation: either the representation enclosed in the - /// message payload or the selected representation, as determined by the - /// message semantics. The indicated media type defines both the data - /// format and how that data is intended to be processed by a recipient, - /// within the scope of the received message semantics, after any content - /// codings indicated by Content-Encoding are decoded. - /// - /// Although the `mime` crate allows the mime options to be any slice, this crate - /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If - /// this is an issue, it's possible to implement `Header` on a custom struct. - /// - /// # ABNF - /// - /// ```text - /// Content-Type = media-type - /// ``` - /// - /// # Example values - /// - /// * `text/html; charset=utf-8` - /// * `application/json` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentType::json() - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_http; - /// use mime::TEXT_HTML; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentType(TEXT_HTML) - /// ); - /// # } - /// ``` - (ContentType, CONTENT_TYPE) => [Mime] - - test_content_type { - test_header!( - test1, - vec![b"text/html"], - Some(HeaderField(TEXT_HTML))); - } -} - -impl ContentType { - /// A constructor to easily create a `Content-Type: application/json` - /// header. - #[inline] - pub fn json() -> ContentType { - ContentType(mime::APPLICATION_JSON) - } - - /// A constructor to easily create a `Content-Type: text/plain; - /// charset=utf-8` header. - #[inline] - pub fn plaintext() -> ContentType { - ContentType(mime::TEXT_PLAIN_UTF_8) - } - - /// A constructor to easily create a `Content-Type: text/html` header. - #[inline] - pub fn html() -> ContentType { - ContentType(mime::TEXT_HTML) - } - - /// A constructor to easily create a `Content-Type: text/xml` header. - #[inline] - pub fn xml() -> ContentType { - ContentType(mime::TEXT_XML) - } - - /// A constructor to easily create a `Content-Type: - /// application/www-form-url-encoded` header. - #[inline] - pub fn form_url_encoded() -> ContentType { - ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) - } - /// A constructor to easily create a `Content-Type: image/jpeg` header. - #[inline] - pub fn jpeg() -> ContentType { - ContentType(mime::IMAGE_JPEG) - } - - /// A constructor to easily create a `Content-Type: image/png` header. - #[inline] - pub fn png() -> ContentType { - ContentType(mime::IMAGE_PNG) - } - - /// A constructor to easily create a `Content-Type: - /// application/octet-stream` header. - #[inline] - pub fn octet_stream() -> ContentType { - ContentType(mime::APPLICATION_OCTET_STREAM) - } -} - -impl Eq for ContentType {} diff --git a/src/header/common/date.rs b/src/header/common/date.rs deleted file mode 100644 index 9ce2bd65..00000000 --- a/src/header/common/date.rs +++ /dev/null @@ -1,42 +0,0 @@ -use header::{HttpDate, DATE}; -use std::time::SystemTime; - -header! { - /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) - /// - /// The `Date` header field represents the date and time at which the - /// message was originated. - /// - /// # ABNF - /// - /// ```text - /// Date = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Tue, 15 Nov 1994 08:12:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Date; - /// use std::time::SystemTime; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(Date(SystemTime::now().into())); - /// ``` - (Date, DATE) => [HttpDate] - - test_date { - test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); - } -} - -impl Date { - /// Create a date instance set to the current system time - pub fn now() -> Date { - Date(SystemTime::now().into()) - } -} diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs deleted file mode 100644 index ea4be2a7..00000000 --- a/src/header/common/etag.rs +++ /dev/null @@ -1,96 +0,0 @@ -use header::{EntityTag, ETAG}; - -header! { - /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) - /// - /// The `ETag` header field in a response provides the current entity-tag - /// for the selected representation, as determined at the conclusion of - /// handling the request. An entity-tag is an opaque validator for - /// differentiating between multiple representations of the same - /// resource, regardless of whether those multiple representations are - /// due to resource state changes over time, content negotiation - /// resulting in multiple representations being valid at the same time, - /// or both. An entity-tag consists of an opaque quoted string, possibly - /// prefixed by a weakness indicator. - /// - /// # ABNF - /// - /// ```text - /// ETag = entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `""` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{ETag, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{ETag, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); - /// ``` - (ETag, ETAG) => [EntityTag] - - test_etag { - // From the RFC - test_header!(test1, - vec![b"\"xyzzy\""], - Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - test_header!(test2, - vec![b"W/\"xyzzy\""], - Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - test_header!(test3, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - // Own tests - test_header!(test4, - vec![b"\"foobar\""], - Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - test_header!(test5, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - test_header!(test6, - vec![b"W/\"weak-etag\""], - Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - test_header!(test7, - vec![b"W/\"\x65\x62\""], - Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - test_header!(test8, - vec![b"W/\"\""], - Some(ETag(EntityTag::new(true, "".to_owned())))); - test_header!(test9, - vec![b"no-dquotes"], - None::); - test_header!(test10, - vec![b"w/\"the-first-w-is-case-sensitive\""], - None::); - test_header!(test11, - vec![b""], - None::); - test_header!(test12, - vec![b"\"unmatched-dquotes1"], - None::); - test_header!(test13, - vec![b"unmatched-dquotes2\""], - None::); - test_header!(test14, - vec![b"matched-\"dquotes\""], - None::); - test_header!(test15, - vec![b"\""], - None::); - } -} diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs deleted file mode 100644 index bdd25fdb..00000000 --- a/src/header/common/expires.rs +++ /dev/null @@ -1,39 +0,0 @@ -use header::{HttpDate, EXPIRES}; - -header! { - /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) - /// - /// The `Expires` header field gives the date/time after which the - /// response is considered stale. - /// - /// The presence of an Expires field does not imply that the original - /// resource will change or cease to exist at, before, or after that - /// time. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// * `Thu, 01 Dec 1994 16:00:00 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Expires; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - /// builder.set(Expires(expiration.into())); - /// ``` - (Expires, EXPIRES) => [HttpDate] - - test_expires { - // Test case from RFC - test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); - } -} diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs deleted file mode 100644 index 5f7976a4..00000000 --- a/src/header/common/if_match.rs +++ /dev/null @@ -1,70 +0,0 @@ -use header::{EntityTag, IF_MATCH}; - -header! { - /// `If-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) - /// - /// The `If-Match` header field makes the request method conditional on - /// the recipient origin server either having at least one current - /// representation of the target resource, when the field-value is "*", - /// or having a current representation of the target resource that has an - /// entity-tag matching a member of the list of entity-tags provided in - /// the field-value. - /// - /// An origin server MUST use the strong comparison function when - /// comparing entity-tags for `If-Match`, since the client - /// intends this precondition to prevent the method from being applied if - /// there have been any changes to the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * "xyzzy", "r2d2xxxx", "c3piozzzz" - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfMatch; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(IfMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{IfMatch, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// IfMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfMatch, IF_MATCH) => {Any / (EntityTag)+} - - test_if_match { - test_header!( - test1, - vec![b"\"xyzzy\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned())]))); - test_header!( - test2, - vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned()), - EntityTag::new(false, "r2d2xxxx".to_owned()), - EntityTag::new(false, "c3piozzzz".to_owned())]))); - test_header!(test3, vec![b"*"], Some(IfMatch::Any)); - } -} diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs deleted file mode 100644 index 41d6fba2..00000000 --- a/src/header/common/if_modified_since.rs +++ /dev/null @@ -1,39 +0,0 @@ -use header::{HttpDate, IF_MODIFIED_SINCE}; - -header! { - /// `If-Modified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) - /// - /// The `If-Modified-Since` header field makes a GET or HEAD request - /// method conditional on the selected representation's modification date - /// being more recent than the date provided in the field-value. - /// Transfer of the selected representation's data is avoided if that - /// data has not changed. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfModifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfModifiedSince(modified.into())); - /// ``` - (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] - - test_if_modified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs deleted file mode 100644 index 8b3905ba..00000000 --- a/src/header/common/if_none_match.rs +++ /dev/null @@ -1,92 +0,0 @@ -use header::{EntityTag, IF_NONE_MATCH}; - -header! { - /// `If-None-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) - /// - /// The `If-None-Match` header field makes the request method conditional - /// on a recipient cache or origin server either not having any current - /// representation of the target resource, when the field-value is "*", - /// or having a selected representation with an entity-tag that does not - /// match any of those listed in the field-value. - /// - /// A recipient MUST use the weak comparison function when comparing - /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags - /// can be used for cache validation even if there have been changes to - /// the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-None-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` - /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` - /// * `*` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfNoneMatch; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(IfNoneMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{IfNoneMatch, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// IfNoneMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} - - test_if_none_match { - test_header!(test1, vec![b"\"xyzzy\""]); - test_header!(test2, vec![b"W/\"xyzzy\""]); - test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - test_header!(test5, vec![b"*"]); - } -} - -#[cfg(test)] -mod tests { - use super::IfNoneMatch; - use header::{EntityTag, Header, IF_NONE_MATCH}; - use test::TestRequest; - - #[test] - fn test_if_none_match() { - let mut if_none_match: Result; - - let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); - if_none_match = Header::parse(&req); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - - let req = - TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) - .finish(); - - if_none_match = Header::parse(&req); - let mut entities: Vec = Vec::new(); - let foobar_etag = EntityTag::new(false, "foobar".to_owned()); - let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); - entities.push(foobar_etag); - entities.push(weak_etag); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); - } -} diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs deleted file mode 100644 index 8cbb8c89..00000000 --- a/src/header/common/if_range.rs +++ /dev/null @@ -1,117 +0,0 @@ -use error::ParseError; -use header::from_one_raw_str; -use header::{ - EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, - InvalidHeaderValueBytes, Writer, -}; -use http::header; -use httpmessage::HttpMessage; -use std::fmt::{self, Display, Write}; - -/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) -/// -/// If a client has a partial copy of a representation and wishes to have -/// an up-to-date copy of the entire representation, it could use the -/// Range header field with a conditional GET (using either or both of -/// If-Unmodified-Since and If-Match.) However, if the precondition -/// fails because the representation has been modified, the client would -/// then have to make a second request to obtain the entire current -/// representation. -/// -/// The `If-Range` header field allows a client to \"short-circuit\" the -/// second request. Informally, its meaning is as follows: if the -/// representation is unchanged, send me the part(s) that I am requesting -/// in Range; otherwise, send me the entire representation. -/// -/// # ABNF -/// -/// ```text -/// If-Range = entity-tag / HTTP-date -/// ``` -/// -/// # Example values -/// -/// * `Sat, 29 Oct 1994 19:43:31 GMT` -/// * `\"xyzzy\"` -/// -/// # Examples -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{EntityTag, IfRange}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(IfRange::EntityTag(EntityTag::new( -/// false, -/// "xyzzy".to_owned(), -/// ))); -/// ``` -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::IfRange; -/// use std::time::{Duration, SystemTime}; -/// -/// let mut builder = HttpResponse::Ok(); -/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); -/// builder.set(IfRange::Date(fetched.into())); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum IfRange { - /// The entity-tag the client has of the resource - EntityTag(EntityTag), - /// The date when the client retrieved the resource - Date(HttpDate), -} - -impl Header for IfRange { - fn name() -> HeaderName { - header::IF_RANGE - } - #[inline] - fn parse(msg: &T) -> Result - where - T: HttpMessage, - { - let etag: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); - if let Ok(etag) = etag { - return Ok(IfRange::EntityTag(etag)); - } - let date: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); - if let Ok(date) = date { - return Ok(IfRange::Date(date)); - } - Err(ParseError::Header) - } -} - -impl Display for IfRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - IfRange::EntityTag(ref x) => Display::fmt(x, f), - IfRange::Date(ref x) => Display::fmt(x, f), - } - } -} - -impl IntoHeaderValue for IfRange { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_shared(writer.take()) - } -} - -#[cfg(test)] -mod test_if_range { - use super::IfRange as HeaderField; - use header::*; - use std::str; - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"xyzzy\""]); - test_header!(test3, vec![b"this-is-invalid"], None::); -} diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs deleted file mode 100644 index 02f9252e..00000000 --- a/src/header/common/if_unmodified_since.rs +++ /dev/null @@ -1,40 +0,0 @@ -use header::{HttpDate, IF_UNMODIFIED_SINCE}; - -header! { - /// `If-Unmodified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) - /// - /// The `If-Unmodified-Since` header field makes the request method - /// conditional on the selected representation's last modification date - /// being earlier than or equal to the date provided in the field-value. - /// This field accomplishes the same purpose as If-Match for cases where - /// the user agent does not have an entity-tag for the representation. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfUnmodifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfUnmodifiedSince(modified.into())); - /// ``` - (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] - - test_if_unmodified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs deleted file mode 100644 index 608f4313..00000000 --- a/src/header/common/last_modified.rs +++ /dev/null @@ -1,38 +0,0 @@ -use header::{HttpDate, LAST_MODIFIED}; - -header! { - /// `Last-Modified` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) - /// - /// The `Last-Modified` header field in a response provides a timestamp - /// indicating the date and time at which the origin server believes the - /// selected representation was last modified, as determined at the - /// conclusion of handling the request. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::LastModified; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(LastModified(modified.into())); - /// ``` - (LastModified, LAST_MODIFIED) => [HttpDate] - - test_last_modified { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} -} diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs deleted file mode 100644 index e6185b5a..00000000 --- a/src/header/common/mod.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! A Collection of Header implementations for common HTTP Headers. -//! -//! ## Mime -//! -//! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime](https://docs.rs/mime) crate -//! is used, such as `ContentType(pub Mime)`. -#![cfg_attr(rustfmt, rustfmt_skip)] - -pub use self::accept_charset::AcceptCharset; -//pub use self::accept_encoding::AcceptEncoding; -pub use self::accept_language::AcceptLanguage; -pub use self::accept::Accept; -pub use self::allow::Allow; -pub use self::cache_control::{CacheControl, CacheDirective}; -pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; -pub use self::content_language::ContentLanguage; -pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_type::ContentType; -pub use self::date::Date; -pub use self::etag::ETag; -pub use self::expires::Expires; -pub use self::if_match::IfMatch; -pub use self::if_modified_since::IfModifiedSince; -pub use self::if_none_match::IfNoneMatch; -pub use self::if_range::IfRange; -pub use self::if_unmodified_since::IfUnmodifiedSince; -pub use self::last_modified::LastModified; -//pub use self::range::{Range, ByteRangeSpec}; - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__deref { - ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &$to { - &self.0 - } - } - - impl ::std::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut $to { - &mut self.0 - } - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__tm { - ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] - #[cfg(test)] - mod $tm{ - use std::str; - use http::Method; - use $crate::header::*; - use $crate::mime::*; - use super::$id as HeaderField; - $($tf)* - } - - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! test_header { - ($id:ident, $raw:expr) => { - #[test] - fn $id() { - use test; - let raw = $raw; - let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item); - } - let req = req.finish(); - let value = HeaderField::parse(&req); - let result = format!("{}", value.unwrap()); - let expected = String::from_utf8(raw[0].to_vec()).unwrap(); - let result_cmp: Vec = result - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - let expected_cmp: Vec = expected - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - assert_eq!(result_cmp.concat(), expected_cmp.concat()); - } - }; - ($id:ident, $raw:expr, $typed:expr) => { - #[test] - fn $id() { - use $crate::test; - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item); - } - let req = req.finish(); - let val = HeaderField::parse(&req); - let typed: Option = $typed; - // Test parsing - assert_eq!(val.ok(), typed); - // Test formatting - if typed.is_some() { - let raw = &($raw)[..]; - let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap()); - let mut joined = String::new(); - joined.push_str(iter.next().unwrap()); - for s in iter { - joined.push_str(", "); - joined.push_str(s); - } - assert_eq!(format!("{}", typed.unwrap()), joined); - } - } - } -} - -#[macro_export] -macro_rules! header { - // $a:meta: Attributes associated with the header item (usually docs) - // $id:ident: Identifier of the header - // $n:expr: Lowercase name of the header - // $nn:expr: Nice name of the header - - // List header, zero or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - // List header, one or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - // Single value header - ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub $value); - __hyper__deref!($id => $value); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_one_raw_str( - msg.headers().get(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::std::fmt::Display::fmt(&self.0, f) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - self.0.try_into() - } - } - }; - // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub enum $id { - /// Any value is a match - Any, - /// Only the listed items are a match - Items(Vec<$item>), - } - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - let any = msg.headers().get(Self::name()).and_then(|hdr| { - hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); - - if let Some(true) = any { - Ok($id::Any) - } else { - Ok($id::Items( - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name()))?)) - } - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match *self { - $id::Any => f.write_str("*"), - $id::Items(ref fields) => $crate::header::fmt_comma_delimited( - f, &fields[..]) - } - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - - // optional test module - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => ($item)* - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)+ - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* ($id, $name) => [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => {Any / ($item)+} - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; -} - - -mod accept_charset; -//mod accept_encoding; -mod accept_language; -mod accept; -mod allow; -mod cache_control; -mod content_disposition; -mod content_language; -mod content_range; -mod content_type; -mod date; -mod etag; -mod expires; -mod if_match; -mod if_modified_since; -mod if_none_match; -mod if_range; -mod if_unmodified_since; -mod last_modified; -//mod range; diff --git a/src/header/common/range.rs b/src/header/common/range.rs deleted file mode 100644 index 71718fc7..00000000 --- a/src/header/common/range.rs +++ /dev/null @@ -1,434 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use header::parsing::from_one_raw_str; -use header::{Header, Raw}; - -/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) -/// -/// The "Range" header field on a GET request modifies the method -/// semantics to request transfer of only one or more subranges of the -/// selected representation data, rather than the entire selected -/// representation data. -/// -/// # ABNF -/// -/// ```text -/// Range = byte-ranges-specifier / other-ranges-specifier -/// other-ranges-specifier = other-range-unit "=" other-range-set -/// other-range-set = 1*VCHAR -/// -/// bytes-unit = "bytes" -/// -/// byte-ranges-specifier = bytes-unit "=" byte-range-set -/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) -/// byte-range-spec = first-byte-pos "-" [last-byte-pos] -/// first-byte-pos = 1*DIGIT -/// last-byte-pos = 1*DIGIT -/// ``` -/// -/// # Example values -/// -/// * `bytes=1000-` -/// * `bytes=-2000` -/// * `bytes=0-1,30-40` -/// * `bytes=0-10,20-90,-100` -/// * `custom_unit=0-123` -/// * `custom_unit=xxx-yyy` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] -/// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum Range { - /// Byte range - Bytes(Vec), - /// Custom range, with unit not registered at IANA - /// (`other-range-unit`: String , `other-range-set`: String) - Unregistered(String, String), -} - -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] -pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") - FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), - /// Get last x bytes ("-x") - Last(u64), -} - -impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. - /// - /// The resulting range is guaranteed to be a satisfiable range within the - /// bounds of `0 <= from <= to < full_length`. - /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 - /// OK` status code. - /// - /// This function closely follows [RFC 7233][1] section 2.1. - /// As such, it considers ranges to be satisfiable if they meet the - /// following conditions: - /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. - /// - /// The function also computes remainder ranges based on the RFC: - /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). - /// - /// [1]: https://tools.ietf.org/html/rfc7233 - pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { - // If the full length is zero, there is no satisfiable end-inclusive range. - if full_length == 0 { - return None; - } - match self { - &ByteRangeSpec::FromTo(from, to) => { - if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) - } else { - None - } - } - &ByteRangeSpec::AllFrom(from) => { - if from < full_length { - Some((from, full_length - 1)) - } else { - None - } - } - &ByteRangeSpec::Last(last) => { - if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. - if last > full_length { - Some((0, full_length - 1)) - } else { - Some((full_length - last, full_length - 1)) - } - } else { - None - } - } - } - } -} - -impl Range { - /// Get the most common byte range header ("bytes=from-to") - pub fn bytes(from: u64, to: u64) -> Range { - Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) - } - - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") - pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { - Range::Bytes( - ranges - .iter() - .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) - .collect(), - ) - } -} - -impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), - ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), - } - } -} - -impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { - try!(write!(f, "bytes=")); - - for (i, range) in ranges.iter().enumerate() { - if i != 0 { - try!(f.write_str(",")); - } - try!(Display::fmt(range, f)); - } - Ok(()) - } - Range::Unregistered(ref unit, ref range_str) => { - write!(f, "{}={}", unit, range_str) - } - } - } -} - -impl FromStr for Range { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); - - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { - let ranges = from_comma_delimited(ranges); - if ranges.is_empty() { - return Err(::Error::Header); - } - Ok(Range::Bytes(ranges)) - } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok( - Range::Unregistered(unit.to_owned(), range_str.to_owned()), - ), - _ => Err(::Error::Header), - } - } -} - -impl FromStr for ByteRangeSpec { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); - - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => end.parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::Last), - (Some(start), Some("")) => start - .parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::AllFrom), - (Some(start), Some(end)) => match (start.parse(), end.parse()) { - (Ok(start), Ok(end)) if start <= end => { - Ok(ByteRangeSpec::FromTo(start, end)) - } - _ => Err(::Error::Header), - }, - _ => Err(::Error::Header), - } - } -} - -fn from_comma_delimited(s: &str) -> Vec { - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.parse().ok()) - .collect() -} - -impl Header for Range { - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -#[test] -fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); - let r3 = Range::bytes(1, 100); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = - Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::AllFrom(200), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::Last(100), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"abc".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"custom=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"=1-100".into()); - assert_eq!(r.ok(), None); -} - -#[test] -fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set(Range::Bytes(vec![ - ByteRangeSpec::FromTo(0, 1000), - ByteRangeSpec::AllFrom(2000), - ])); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); - - headers.clear(); - headers.set(Range::Bytes(vec![])); - - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); - - headers.clear(); - headers.set(Range::Unregistered( - "custom".to_owned(), - "1-xxx".to_owned(), - )); - - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); -} - -#[test] -fn test_byte_range_spec_to_satisfiable_range() { - assert_eq!( - Some((0, 0)), - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((0, 2)), - ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((1, 2)), - ByteRangeSpec::Last(2).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::Last(1).to_satisfiable_range(3) - ); - assert_eq!( - Some((0, 2)), - ByteRangeSpec::Last(5).to_satisfiable_range(3) - ); - assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); -} diff --git a/src/header/mod.rs b/src/header/mod.rs deleted file mode 100644 index 74e4b03e..00000000 --- a/src/header/mod.rs +++ /dev/null @@ -1,471 +0,0 @@ -//! Various http headers -// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) - -use std::fmt; -use std::str::FromStr; - -use bytes::{Bytes, BytesMut}; -use mime::Mime; -use modhttp::header::GetAll; -use modhttp::Error as HttpError; -use percent_encoding; - -pub use modhttp::header::*; - -use error::ParseError; -use httpmessage::HttpMessage; - -mod common; -mod shared; -#[doc(hidden)] -pub use self::common::*; -#[doc(hidden)] -pub use self::shared::*; - -#[doc(hidden)] -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ - /// Returns the name of the header field - fn name() -> HeaderName; - - /// Parse a header - fn parse(msg: &T) -> Result; -} - -#[doc(hidden)] -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(format!("{}", self))) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - #[cfg(feature = "brotli")] - Br, - /// A format using the zlib structure with deflate algorithm - #[cfg(feature = "flate2")] - Deflate, - /// Gzip algorithm - #[cfg(feature = "flate2")] - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - match self { - ContentEncoding::Identity | ContentEncoding::Auto => false, - _ => true, - } - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - #[cfg(feature = "brotli")] - ContentEncoding::Br => "br", - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => "gzip", - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - #[cfg(feature = "brotli")] - ContentEncoding::Br => 1.1, - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => 1.0, - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -// TODO: remove memory allocation -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - match AsRef::::as_ref(&s.trim().to_lowercase()) { - #[cfg(feature = "brotli")] - "br" => ContentEncoding::Br, - #[cfg(feature = "flate2")] - "gzip" => ContentEncoding::Gzip, - #[cfg(feature = "flate2")] - "deflate" => ContentEncoding::Deflate, - _ => ContentEncoding::Identity, - } - } -} - -#[doc(hidden)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { - fmt::write(self, args) - } -} - -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited( - all: GetAll, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }).filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -// From hyper v0.11.27 src/header/parsing.rs - -/// The value part of an extended parameter consisting of three parts: -/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), -/// and a character sequence representing the actual value (`value`), separated by single quote -/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] -pub struct ExtendedValue { - /// The character set that is used to encode the `value` to a string. - pub charset: Charset, - /// The human language details of the `value`, if available. - pub language_tag: Option, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -/// -/// Extended values are denoted by parameter names that end with `*`. -/// -/// ## ABNF -/// -/// ```text -/// ext-value = charset "'" [ language ] "'" value-chars -/// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// value-chars = *( pct-encoded / attr-char ) -/// -/// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 -/// -/// attr-char = ALPHA / DIGIT -/// / "!" / "#" / "$" / "&" / "+" / "-" / "." -/// / "^" / "_" / "`" / "|" / "~" -/// ; token except ( "*" / "'" / "%" ) -/// ``` -pub fn parse_extended_value(val: &str) -> Result { - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3, '\''); - - // Interpret the first piece as a Charset - let charset: Charset = match parts.next() { - None => return Err(::error::ParseError::Header), - Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?, - }; - - // Interpret the second piece as a language tag - let language_tag: Option = match parts.next() { - None => return Err(::error::ParseError::Header), - Some("") => None, - Some(s) => match s.parse() { - Ok(lt) => Some(lt), - Err(_) => return Err(::error::ParseError::Header), - }, - }; - - // Interpret the third piece as a sequence of value characters - let value: Vec = match parts.next() { - None => return Err(::error::ParseError::Header), - Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), - }; - - Ok(ExtendedValue { - value, - charset, - language_tag, - }) -} - -impl fmt::Display for ExtendedValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let encoded_value = percent_encoding::percent_encode( - &self.value[..], - self::percent_encoding_http::HTTP_VALUE, - ); - if let Some(ref lang) = self.language_tag { - write!(f, "{}'{}'{}", self.charset, lang, encoded_value) - } else { - write!(f, "{}''{}", self.charset, encoded_value) - } - } -} - -/// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 -pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = - percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} -mod percent_encoding_http { - use percent_encoding; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} - -#[cfg(test)] -mod tests { - use super::{parse_extended_value, ExtendedValue}; - use header::shared::Charset; - use language_tags::LanguageTag; - - #[test] - fn test_parse_extended_value_with_encoding_and_language_tag() { - let expected_language_tag = "en".parse::().unwrap(); - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode character U+00A3 (POUND SIGN) - let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Iso_8859_1, extended_value.charset); - assert!(extended_value.language_tag.is_some()); - assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!( - vec![163, b' ', b'r', b'a', b't', b'e', b's'], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_with_encoding() { - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) - // and U+20AC (EURO SIGN) - let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); - assert!(extended_value.language_tag.is_none()); - assert_eq!( - vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_missing_language_tag_and_encoding() { - // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 - let result = parse_extended_value("foo%20bar.html"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted() { - let result = parse_extended_value("UTF-8'missing third part"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted_blank() { - let result = parse_extended_value("blank second part'"); - assert!(result.is_err()); - } - - #[test] - fn test_fmt_extended_value_with_encoding_and_language_tag() { - let extended_value = ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: Some("en".parse().expect("Could not parse language tag")), - value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], - }; - assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); - } - - #[test] - fn test_fmt_extended_value_with_encoding() { - let extended_value = ExtendedValue { - charset: Charset::Ext("UTF-8".to_string()), - language_tag: None, - value: vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - }; - assert_eq!( - "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value) - ); - } -} diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs deleted file mode 100644 index b679971b..00000000 --- a/src/header/shared/charset.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use self::Charset::*; - -/// A Mime charset. -/// -/// The string representation is normalized to upper case. -/// -/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. -/// -/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml -#[derive(Clone, Debug, PartialEq)] -#[allow(non_camel_case_types)] -pub enum Charset { - /// US ASCII - Us_Ascii, - /// ISO-8859-1 - Iso_8859_1, - /// ISO-8859-2 - Iso_8859_2, - /// ISO-8859-3 - Iso_8859_3, - /// ISO-8859-4 - Iso_8859_4, - /// ISO-8859-5 - Iso_8859_5, - /// ISO-8859-6 - Iso_8859_6, - /// ISO-8859-7 - Iso_8859_7, - /// ISO-8859-8 - Iso_8859_8, - /// ISO-8859-9 - Iso_8859_9, - /// ISO-8859-10 - Iso_8859_10, - /// Shift_JIS - Shift_Jis, - /// EUC-JP - Euc_Jp, - /// ISO-2022-KR - Iso_2022_Kr, - /// EUC-KR - Euc_Kr, - /// ISO-2022-JP - Iso_2022_Jp, - /// ISO-2022-JP-2 - Iso_2022_Jp_2, - /// ISO-8859-6-E - Iso_8859_6_E, - /// ISO-8859-6-I - Iso_8859_6_I, - /// ISO-8859-8-E - Iso_8859_8_E, - /// ISO-8859-8-I - Iso_8859_8_I, - /// GB2312 - Gb2312, - /// Big5 - Big5, - /// KOI8-R - Koi8_R, - /// An arbitrary charset specified as a string - Ext(String), -} - -impl Charset { - fn label(&self) -> &str { - match *self { - Us_Ascii => "US-ASCII", - Iso_8859_1 => "ISO-8859-1", - Iso_8859_2 => "ISO-8859-2", - Iso_8859_3 => "ISO-8859-3", - Iso_8859_4 => "ISO-8859-4", - Iso_8859_5 => "ISO-8859-5", - Iso_8859_6 => "ISO-8859-6", - Iso_8859_7 => "ISO-8859-7", - Iso_8859_8 => "ISO-8859-8", - Iso_8859_9 => "ISO-8859-9", - Iso_8859_10 => "ISO-8859-10", - Shift_Jis => "Shift-JIS", - Euc_Jp => "EUC-JP", - Iso_2022_Kr => "ISO-2022-KR", - Euc_Kr => "EUC-KR", - Iso_2022_Jp => "ISO-2022-JP", - Iso_2022_Jp_2 => "ISO-2022-JP-2", - Iso_8859_6_E => "ISO-8859-6-E", - Iso_8859_6_I => "ISO-8859-6-I", - Iso_8859_8_E => "ISO-8859-8-E", - Iso_8859_8_I => "ISO-8859-8-I", - Gb2312 => "GB2312", - Big5 => "big5", - Koi8_R => "KOI8-R", - Ext(ref s) => s, - } - } -} - -impl Display for Charset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.label()) - } -} - -impl FromStr for Charset { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - Ok(match s.to_ascii_uppercase().as_ref() { - "US-ASCII" => Us_Ascii, - "ISO-8859-1" => Iso_8859_1, - "ISO-8859-2" => Iso_8859_2, - "ISO-8859-3" => Iso_8859_3, - "ISO-8859-4" => Iso_8859_4, - "ISO-8859-5" => Iso_8859_5, - "ISO-8859-6" => Iso_8859_6, - "ISO-8859-7" => Iso_8859_7, - "ISO-8859-8" => Iso_8859_8, - "ISO-8859-9" => Iso_8859_9, - "ISO-8859-10" => Iso_8859_10, - "SHIFT-JIS" => Shift_Jis, - "EUC-JP" => Euc_Jp, - "ISO-2022-KR" => Iso_2022_Kr, - "EUC-KR" => Euc_Kr, - "ISO-2022-JP" => Iso_2022_Jp, - "ISO-2022-JP-2" => Iso_2022_Jp_2, - "ISO-8859-6-E" => Iso_8859_6_E, - "ISO-8859-6-I" => Iso_8859_6_I, - "ISO-8859-8-E" => Iso_8859_8_E, - "ISO-8859-8-I" => Iso_8859_8_I, - "GB2312" => Gb2312, - "big5" => Big5, - "KOI8-R" => Koi8_R, - s => Ext(s.to_owned()), - }) - } -} - -#[test] -fn test_parse() { - assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); - assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); - assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); -} - -#[test] -fn test_display() { - assert_eq!("US-ASCII", format!("{}", Us_Ascii)); - assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); -} diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs deleted file mode 100644 index 64027d8a..00000000 --- a/src/header/shared/encoding.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::fmt; -use std::str; - -pub use self::Encoding::{ - Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, -}; - -/// A value to represent an encoding used in `Transfer-Encoding` -/// or `Accept-Encoding` header. -#[derive(Clone, PartialEq, Debug)] -pub enum Encoding { - /// The `chunked` encoding. - Chunked, - /// The `br` encoding. - Brotli, - /// The `gzip` encoding. - Gzip, - /// The `deflate` encoding. - Deflate, - /// The `compress` encoding. - Compress, - /// The `identity` encoding. - Identity, - /// The `trailers` encoding. - Trailers, - /// Some other encoding that is less common, can be any String. - EncodingExt(String), -} - -impl fmt::Display for Encoding { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Chunked => "chunked", - Brotli => "br", - Gzip => "gzip", - Deflate => "deflate", - Compress => "compress", - Identity => "identity", - Trailers => "trailers", - EncodingExt(ref s) => s.as_ref(), - }) - } -} - -impl str::FromStr for Encoding { - type Err = ::error::ParseError; - fn from_str(s: &str) -> Result { - match s { - "chunked" => Ok(Chunked), - "br" => Ok(Brotli), - "deflate" => Ok(Deflate), - "gzip" => Ok(Gzip), - "compress" => Ok(Compress), - "identity" => Ok(Identity), - "trailers" => Ok(Trailers), - _ => Ok(EncodingExt(s.to_owned())), - } - } -} diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs deleted file mode 100644 index 0d3b0a4e..00000000 --- a/src/header/shared/entity.rs +++ /dev/null @@ -1,266 +0,0 @@ -use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -/// check that each char in the slice is either: -/// 1. `%x21`, or -/// 2. in the range `%x23` to `%x7E`, or -/// 3. above `%x80` -fn check_slice_validity(slice: &str) -> bool { - slice - .bytes() - .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) -} - -/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) -/// -/// An entity tag consists of a string enclosed by two literal double quotes. -/// Preceding the first double quote is an optional weakness indicator, -/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and -/// `W/"xyzzy"`. -/// -/// # ABNF -/// -/// ```text -/// entity-tag = [ weak ] opaque-tag -/// weak = %x57.2F ; "W/", case-sensitive -/// opaque-tag = DQUOTE *etagc DQUOTE -/// etagc = %x21 / %x23-7E / obs-text -/// ; VCHAR except double quotes, plus obs-text -/// ``` -/// -/// # Comparison -/// To check if two entity tags are equivalent in an application always use the -/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use -/// `==` to check if two tags are identical. -/// -/// The example below shows the results for a set of entity-tag pairs and -/// both the weak and strong comparison function results: -/// -/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison | -/// |---------|---------|-------------------|-----------------| -/// | `W/"1"` | `W/"1"` | no match | match | -/// | `W/"1"` | `W/"2"` | no match | no match | -/// | `W/"1"` | `"1"` | no match | match | -/// | `"1"` | `"1"` | match | match | -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct EntityTag { - /// Weakness indicator for the tag - pub weak: bool, - /// The opaque string in between the DQUOTEs - tag: String, -} - -impl EntityTag { - /// Constructs a new EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn new(weak: bool, tag: String) -> EntityTag { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - EntityTag { weak, tag } - } - - /// Constructs a new weak EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn weak(tag: String) -> EntityTag { - EntityTag::new(true, tag) - } - - /// Constructs a new strong EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn strong(tag: String) -> EntityTag { - EntityTag::new(false, tag) - } - - /// Get the tag. - pub fn tag(&self) -> &str { - self.tag.as_ref() - } - - /// Set the tag. - /// # Panics - /// If the tag contains invalid characters. - pub fn set_tag(&mut self, tag: String) { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - self.tag = tag - } - - /// For strong comparison two entity-tags are equivalent if both are not - /// weak and their opaque-tags match character-by-character. - pub fn strong_eq(&self, other: &EntityTag) -> bool { - !self.weak && !other.weak && self.tag == other.tag - } - - /// For weak comparison two entity-tags are equivalent if their - /// opaque-tags match character-by-character, regardless of either or - /// both being tagged as "weak". - pub fn weak_eq(&self, other: &EntityTag) -> bool { - self.tag == other.tag - } - - /// The inverse of `EntityTag.strong_eq()`. - pub fn strong_ne(&self, other: &EntityTag) -> bool { - !self.strong_eq(other) - } - - /// The inverse of `EntityTag.weak_eq()`. - pub fn weak_ne(&self, other: &EntityTag) -> bool { - !self.weak_eq(other) - } -} - -impl Display for EntityTag { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.weak { - write!(f, "W/\"{}\"", self.tag) - } else { - write!(f, "\"{}\"", self.tag) - } - } -} - -impl FromStr for EntityTag { - type Err = ::error::ParseError; - - fn from_str(s: &str) -> Result { - let length: usize = s.len(); - let slice = &s[..]; - // Early exits if it doesn't terminate in a DQUOTE. - if !slice.ends_with('"') || slice.len() < 2 { - return Err(::error::ParseError::Header); - } - // The etag is weak if its first char is not a DQUOTE. - if slice.len() >= 2 - && slice.starts_with('"') - && check_slice_validity(&slice[1..length - 1]) - { - // No need to check if the last char is a DQUOTE, - // we already did that above. - return Ok(EntityTag { - weak: false, - tag: slice[1..length - 1].to_owned(), - }); - } else if slice.len() >= 4 - && slice.starts_with("W/\"") - && check_slice_validity(&slice[3..length - 1]) - { - return Ok(EntityTag { - weak: true, - tag: slice[3..length - 1].to_owned(), - }); - } - Err(::error::ParseError::Header) - } -} - -impl IntoHeaderValue for EntityTag { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut wrt = Writer::new(); - write!(wrt, "{}", self).unwrap(); - HeaderValue::from_shared(wrt.take()) - } -} - -#[cfg(test)] -mod tests { - use super::EntityTag; - - #[test] - fn test_etag_parse_success() { - // Expected success - assert_eq!( - "\"foobar\"".parse::().unwrap(), - EntityTag::strong("foobar".to_owned()) - ); - assert_eq!( - "\"\"".parse::().unwrap(), - EntityTag::strong("".to_owned()) - ); - assert_eq!( - "W/\"weaktag\"".parse::().unwrap(), - EntityTag::weak("weaktag".to_owned()) - ); - assert_eq!( - "W/\"\x65\x62\"".parse::().unwrap(), - EntityTag::weak("\x65\x62".to_owned()) - ); - assert_eq!( - "W/\"\"".parse::().unwrap(), - EntityTag::weak("".to_owned()) - ); - } - - #[test] - fn test_etag_parse_failures() { - // Expected failures - assert!("no-dquotes".parse::().is_err()); - assert!( - "w/\"the-first-w-is-case-sensitive\"" - .parse::() - .is_err() - ); - assert!("".parse::().is_err()); - assert!("\"unmatched-dquotes1".parse::().is_err()); - assert!("unmatched-dquotes2\"".parse::().is_err()); - assert!("matched-\"dquotes\"".parse::().is_err()); - } - - #[test] - fn test_etag_fmt() { - assert_eq!( - format!("{}", EntityTag::strong("foobar".to_owned())), - "\"foobar\"" - ); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); - assert_eq!( - format!("{}", EntityTag::weak("weak-etag".to_owned())), - "W/\"weak-etag\"" - ); - assert_eq!( - format!("{}", EntityTag::weak("\u{0065}".to_owned())), - "W/\"\x65\"" - ); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); - } - - #[test] - fn test_cmp() { - // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | - // |---------|---------|-------------------|-----------------| - // | `W/"1"` | `W/"1"` | no match | match | - // | `W/"1"` | `W/"2"` | no match | no match | - // | `W/"1"` | `"1"` | no match | match | - // | `"1"` | `"1"` | match | match | - let mut etag1 = EntityTag::weak("1".to_owned()); - let mut etag2 = EntityTag::weak("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::weak("2".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(!etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::strong("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(!etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - } -} diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs deleted file mode 100644 index 7fd26b12..00000000 --- a/src/header/shared/httpdate.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::fmt::{self, Display}; -use std::io::Write; -use std::str::FromStr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use bytes::{BufMut, BytesMut}; -use http::header::{HeaderValue, InvalidHeaderValueBytes}; -use time; - -use error::ParseError; -use header::IntoHeaderValue; - -/// A timestamp with HTTP formatting and parsing -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct HttpDate(time::Tm); - -impl FromStr for HttpDate { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match time::strptime(s, "%a, %d %b %Y %T %Z") - .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) - .or_else(|_| time::strptime(s, "%c")) - { - Ok(t) => Ok(HttpDate(t)), - Err(_) => Err(ParseError::Header), - } - } -} - -impl Display for HttpDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0.to_utc().rfc822(), f) - } -} - -impl From for HttpDate { - fn from(tm: time::Tm) -> HttpDate { - HttpDate(tm) - } -} - -impl From for HttpDate { - fn from(sys: SystemTime) -> HttpDate { - let tmspec = match sys.duration_since(UNIX_EPOCH) { - Ok(dur) => { - time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) - } - Err(err) => { - let neg = err.duration(); - time::Timespec::new( - -(neg.as_secs() as i64), - -(neg.subsec_nanos() as i32), - ) - } - }; - HttpDate(time::at_utc(tmspec)) - } -} - -impl IntoHeaderValue for HttpDate { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut wrt = BytesMut::with_capacity(29).writer(); - write!(wrt, "{}", self.0.rfc822()).unwrap(); - HeaderValue::from_shared(wrt.get_mut().take().freeze()) - } -} - -impl From for SystemTime { - fn from(date: HttpDate) -> SystemTime { - let spec = date.0.to_timespec(); - if spec.sec >= 0 { - UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) - } else { - UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) - } - } -} - -#[cfg(test)] -mod tests { - use super::HttpDate; - use time::Tm; - - const NOV_07: HttpDate = HttpDate(Tm { - tm_nsec: 0, - tm_sec: 37, - tm_min: 48, - tm_hour: 8, - tm_mday: 7, - tm_mon: 10, - tm_year: 94, - tm_wday: 0, - tm_isdst: 0, - tm_yday: 0, - tm_utcoff: 0, - }); - - #[test] - fn test_date() { - assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), - NOV_07 - ); - assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" - .parse::() - .unwrap(), - NOV_07 - ); - assert_eq!( - "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - NOV_07 - ); - assert!("this-is-no-date".parse::().is_err()); - } -} diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs deleted file mode 100644 index f2bc9163..00000000 --- a/src/header/shared/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Copied for `hyper::header::shared`; - -pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; -pub use self::httpdate::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; -pub use language_tags::LanguageTag; - -mod charset; -mod encoding; -mod entity; -mod httpdate; -mod quality_item; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs deleted file mode 100644 index 80bd7e1c..00000000 --- a/src/header/shared/quality_item.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::cmp; -use std::default::Default; -use std::fmt; -use std::str; - -use self::internal::IntoQuality; - -/// Represents a quality used in quality values. -/// -/// Can be created with the `q` function. -/// -/// # Implementation notes -/// -/// The quality value is defined as a number between 0 and 1 with three decimal -/// places. This means there are 1001 possible values. Since floating point -/// numbers are not exact and the smallest floating point data type (`f32`) -/// consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to -/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality -/// `q=0.532`. -/// -/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) -/// gives more information on quality values in HTTP header fields. -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Quality(u16); - -impl Default for Quality { - fn default() -> Quality { - Quality(1000) - } -} - -/// Represents an item with a quality value as defined in -/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). -#[derive(Clone, PartialEq, Debug)] -pub struct QualityItem { - /// The actual contents of the field. - pub item: T, - /// The quality (client or server preference) for the value. - pub quality: Quality, -} - -impl QualityItem { - /// Creates a new `QualityItem` from an item and a quality. - /// The item can be of any type. - /// The quality should be a value in the range [0, 1]. - pub fn new(item: T, quality: Quality) -> QualityItem { - QualityItem { item, quality } - } -} - -impl cmp::PartialOrd for QualityItem { - fn partial_cmp(&self, other: &QualityItem) -> Option { - self.quality.partial_cmp(&other.quality) - } -} - -impl fmt::Display for QualityItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.item, f)?; - match self.quality.0 { - 1000 => Ok(()), - 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), - } - } -} - -impl str::FromStr for QualityItem { - type Err = ::error::ParseError; - - fn from_str(s: &str) -> Result, ::error::ParseError> { - if !s.is_ascii() { - return Err(::error::ParseError::Header); - } - // Set defaults used if parsing fails. - let mut raw_item = s; - let mut quality = 1f32; - - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); - if parts.len() == 2 { - if parts[0].len() < 2 { - return Err(::error::ParseError::Header); - } - let start = &parts[0][0..2]; - if start == "q=" || start == "Q=" { - let q_part = &parts[0][2..parts[0].len()]; - if q_part.len() > 5 { - return Err(::error::ParseError::Header); - } - match q_part.parse::() { - Ok(q_value) => { - if 0f32 <= q_value && q_value <= 1f32 { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(::error::ParseError::Header); - } - } - Err(_) => return Err(::error::ParseError::Header), - } - } - } - match raw_item.parse::() { - // we already checked above that the quality is within range - Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), - Err(_) => Err(::error::ParseError::Header), - } - } -} - -#[inline] -fn from_f32(f: f32) -> Quality { - // this function is only used internally. A check that `f` is within range - // should be done before calling this method. Just in case, this - // debug_assert should catch if we were forgetful - debug_assert!( - f >= 0f32 && f <= 1f32, - "q value must be between 0.0 and 1.0" - ); - Quality((f * 1000f32) as u16) -} - -/// Convenience function to wrap a value in a `QualityItem` -/// Sets `q` to the default 1.0 -pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Default::default()) -} - -/// Convenience function to create a `Quality` from a float or integer. -/// -/// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality { - val.into_quality() -} - -mod internal { - use super::Quality; - - // TryFrom is probably better, but it's not stable. For now, we want to - // keep the functionality of the `q` function, while allowing it to be - // generic over `f32` and `u16`. - // - // `q` would panic before, so keep that behavior. `TryFrom` can be - // introduced later for a non-panicking conversion. - - pub trait IntoQuality: Sealed + Sized { - fn into_quality(self) -> Quality; - } - - impl IntoQuality for f32 { - fn into_quality(self) -> Quality { - assert!( - self >= 0f32 && self <= 1f32, - "float must be between 0.0 and 1.0" - ); - super::from_f32(self) - } - } - - impl IntoQuality for u16 { - fn into_quality(self) -> Quality { - assert!(self <= 1000, "u16 must be between 0 and 1000"); - Quality(self) - } - } - - pub trait Sealed {} - impl Sealed for u16 {} - impl Sealed for f32 {} -} - -#[cfg(test)] -mod tests { - use super::super::encoding::*; - use super::*; - - #[test] - fn test_quality_item_fmt_q_1() { - let x = qitem(Chunked); - assert_eq!(format!("{}", x), "chunked"); - } - #[test] - fn test_quality_item_fmt_q_0001() { - let x = QualityItem::new(Chunked, Quality(1)); - assert_eq!(format!("{}", x), "chunked; q=0.001"); - } - #[test] - fn test_quality_item_fmt_q_05() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(500), - }; - assert_eq!(format!("{}", x), "identity; q=0.5"); - } - - #[test] - fn test_quality_item_fmt_q_0() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(0), - }; - assert_eq!(x.to_string(), "identity; q=0"); - } - - #[test] - fn test_quality_item_from_str1() { - let x: Result, _> = "chunked".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str2() { - let x: Result, _> = "chunked; q=1".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str3() { - let x: Result, _> = "gzip; q=0.5".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(500), - } - ); - } - #[test] - fn test_quality_item_from_str4() { - let x: Result, _> = "gzip; q=0.273".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(273), - } - ); - } - #[test] - fn test_quality_item_from_str5() { - let x: Result, _> = "gzip; q=0.2739999".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_from_str6() { - let x: Result, _> = "gzip; q=2".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_ordering() { - let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); - let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); - let comparision_result: bool = x.gt(&y); - assert!(comparision_result) - } - - #[test] - fn test_quality() { - assert_eq!(q(0.5), Quality(500)); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid() { - q(-1.0); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid2() { - q(2.0); - } - - #[test] - fn test_fuzzing_bugs() { - assert!("99999;".parse::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) - } -} diff --git a/src/httpresponse.rs b/src/httpresponse.rs index cdcdea94..3c034fae 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -15,8 +15,6 @@ use serde_json; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; -// use httpmessage::HttpMessage; -// use httprequest::HttpRequest; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -942,8 +940,8 @@ mod tests { use body::Binary; use http; use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use time::Duration; + use header::ContentEncoding; use test::TestRequest; #[test] diff --git a/src/lib.rs b/src/lib.rs index 6215bc4f..7efb4dea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,6 @@ extern crate log; extern crate base64; extern crate byteorder; extern crate bytes; -extern crate regex; extern crate sha1; extern crate time; #[macro_use] @@ -99,21 +98,16 @@ extern crate failure; extern crate lazy_static; #[macro_use] extern crate futures; -#[cfg(feature = "brotli")] -extern crate brotli2; extern crate cookie; extern crate encoding; -#[cfg(feature = "flate2")] -extern crate flate2; extern crate http as modhttp; extern crate httparse; -extern crate language_tags; extern crate mime; -extern crate mime_guess; extern crate net2; extern crate rand; extern crate serde; extern crate serde_urlencoded; +extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; @@ -126,8 +120,6 @@ extern crate url; #[macro_use] extern crate percent_encoding; extern crate serde_json; -extern crate smallvec; -extern crate tokio; #[cfg(test)] #[macro_use] @@ -193,9 +185,6 @@ pub mod http { /// Various http headers pub mod header { pub use header::*; - pub use header::{ - Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag, - }; } pub use header::ContentEncoding; pub use httpresponse::ConnectionType; diff --git a/src/server/input.rs b/src/server/input.rs deleted file mode 100644 index d23d1e99..00000000 --- a/src/server/input.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::io::{self, Write}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliDecoder; -use bytes::{Bytes, BytesMut}; -use error::PayloadError; -#[cfg(feature = "flate2")] -use flate2::write::{GzDecoder, ZlibDecoder}; -use header::ContentEncoding; -use http::header::{HeaderMap, CONTENT_ENCODING}; -use payload::{PayloadSender, PayloadStatus, PayloadWriter}; - -pub(crate) enum PayloadType { - Sender(PayloadSender), - Encoding(Box), -} - -impl PayloadType { - #[cfg(any(feature = "brotli", feature = "flate2"))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - // check content-encoding - let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - ContentEncoding::from(enc) - } else { - ContentEncoding::Auto - } - } else { - ContentEncoding::Auto - }; - - match enc { - ContentEncoding::Auto | ContentEncoding::Identity => { - PayloadType::Sender(sender) - } - _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), - } - } - - #[cfg(not(any(feature = "brotli", feature = "flate2")))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - PayloadType::Sender(sender) - } -} - -impl PayloadWriter for PayloadType { - #[inline] - fn set_error(&mut self, err: PayloadError) { - match *self { - PayloadType::Sender(ref mut sender) => sender.set_error(err), - PayloadType::Encoding(ref mut enc) => enc.set_error(err), - } - } - - #[inline] - fn feed_eof(&mut self) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_eof(), - PayloadType::Encoding(ref mut enc) => enc.feed_eof(), - } - } - - #[inline] - fn feed_data(&mut self, data: Bytes) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_data(data), - PayloadType::Encoding(ref mut enc) => enc.feed_data(data), - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - match *self { - PayloadType::Sender(ref sender) => sender.need_read(), - PayloadType::Encoding(ref enc) => enc.need_read(), - } - } -} - -/// Payload wrapper with content decompression support -pub(crate) struct EncodedPayload { - inner: PayloadSender, - error: bool, - payload: PayloadStream, -} - -impl EncodedPayload { - pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { - EncodedPayload { - inner, - error: false, - payload: PayloadStream::new(enc), - } - } -} - -impl PayloadWriter for EncodedPayload { - fn set_error(&mut self, err: PayloadError) { - self.inner.set_error(err) - } - - fn feed_eof(&mut self) { - if !self.error { - match self.payload.feed_eof() { - Err(err) => { - self.error = true; - self.set_error(PayloadError::Io(err)); - } - Ok(value) => { - if let Some(b) = value { - self.inner.feed_data(b); - } - self.inner.feed_eof(); - } - } - } - } - - fn feed_data(&mut self, data: Bytes) { - if self.error { - return; - } - - match self.payload.feed_data(data) { - Ok(Some(b)) => self.inner.feed_data(b), - Ok(None) => (), - Err(e) => { - self.error = true; - self.set_error(e.into()); - } - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - self.inner.need_read() - } -} - -pub(crate) enum Decoder { - #[cfg(feature = "flate2")] - Deflate(Box>), - #[cfg(feature = "flate2")] - Gzip(Box>), - #[cfg(feature = "brotli")] - Br(Box>), - Identity, -} - -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::with_capacity(8192), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl io::Write for Writer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -/// Payload stream with decompression support -pub(crate) struct PayloadStream { - decoder: Decoder, -} - -impl PayloadStream { - pub fn new(enc: ContentEncoding) -> PayloadStream { - let decoder = match enc { - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - Decoder::Gzip(Box::new(GzDecoder::new(Writer::new()))) - } - _ => Decoder::Identity, - }; - PayloadStream { decoder } - } -} - -impl PayloadStream { - pub fn feed_eof(&mut self) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.finish() { - Ok(mut writer) => { - let b = writer.take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(None), - } - } - - pub fn feed_data(&mut self, data: Bytes) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(Some(data)), - } - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index 0abd7c21..972fbf3f 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -114,9 +114,6 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; -pub(crate) mod input; -pub(crate) mod output; - #[doc(hidden)] pub use super::helpers::write_content_length; diff --git a/src/server/service.rs b/src/server/service.rs deleted file mode 100644 index a55c33f7..00000000 --- a/src/server/service.rs +++ /dev/null @@ -1,273 +0,0 @@ -use std::marker::PhantomData; -use std::time::Duration; - -use actix_net::service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Poll}; - -use super::channel::{H1Channel, HttpChannel}; -use super::error::HttpDispatchError; -use super::handler::HttpHandler; -use super::settings::ServiceConfig; -use super::IoStream; -use error::Error; - -/// `NewService` implementation for HTTP1/HTTP2 transports -pub struct HttpService -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl HttpService -where - H: HttpHandler, - Io: IoStream, -{ - /// Create new `HttpService` instance. - pub fn new(settings: ServiceConfig) -> Self { - HttpService { - settings, - _t: PhantomData, - } - } -} - -impl NewService for HttpService -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type InitError = (); - type Service = HttpServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(HttpServiceHandler::new(self.settings.clone())) - } -} - -pub struct HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new(settings: ServiceConfig) -> HttpServiceHandler { - HttpServiceHandler { - settings, - _t: PhantomData, - } - } -} - -impl Service for HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type Future = HttpChannel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - HttpChannel::new(self.settings.clone(), req) - } -} - -/// `NewService` implementation for HTTP1 transport -pub struct H1Service -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl H1Service -where - H: HttpHandler, - Io: IoStream, -{ - /// Create new `HttpService` instance. - pub fn new(settings: ServiceConfig) -> Self { - H1Service { - settings, - _t: PhantomData, - } - } -} - -impl NewService for H1Service -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type InitError = (); - type Service = H1ServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(H1ServiceHandler::new(self.settings.clone())) - } -} - -/// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new(settings: ServiceConfig) -> H1ServiceHandler { - H1ServiceHandler { - settings, - _t: PhantomData, - } - } -} - -impl Service for H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type Future = H1Channel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - H1Channel::new(self.settings.clone(), req) - } -} - -/// `NewService` implementation for stream configuration service -/// -/// Stream configuration service allows to change some socket level -/// parameters. for example `tcp nodelay` or `tcp keep-alive`. -pub struct StreamConfiguration { - no_delay: Option, - tcp_ka: Option>, - _t: PhantomData<(T, E)>, -} - -impl Default for StreamConfiguration { - fn default() -> Self { - Self::new() - } -} - -impl StreamConfiguration { - /// Create new `StreamConfigurationService` instance. - pub fn new() -> Self { - Self { - no_delay: None, - tcp_ka: None, - _t: PhantomData, - } - } - - /// Sets the value of the `TCP_NODELAY` option on this socket. - pub fn nodelay(mut self, nodelay: bool) -> Self { - self.no_delay = Some(nodelay); - self - } - - /// Sets whether keepalive messages are enabled to be sent on this socket. - pub fn tcp_keepalive(mut self, keepalive: Option) -> Self { - self.tcp_ka = Some(keepalive); - self - } -} - -impl NewService for StreamConfiguration { - type Request = T; - type Response = T; - type Error = E; - type InitError = (); - type Service = StreamConfigurationService; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(StreamConfigurationService { - no_delay: self.no_delay, - tcp_ka: self.tcp_ka, - _t: PhantomData, - }) - } -} - -/// Stream configuration service -/// -/// Stream configuration service allows to change some socket level -/// parameters. for example `tcp nodelay` or `tcp keep-alive`. -pub struct StreamConfigurationService { - no_delay: Option, - tcp_ka: Option>, - _t: PhantomData<(T, E)>, -} - -impl Service for StreamConfigurationService -where - T: IoStream, -{ - type Request = T; - type Response = T; - type Error = E; - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, mut req: Self::Request) -> Self::Future { - if let Some(no_delay) = self.no_delay { - if req.set_nodelay(no_delay).is_err() { - error!("Can not set socket no-delay option"); - } - } - if let Some(keepalive) = self.tcp_ka { - if req.set_keepalive(keepalive).is_err() { - error!("Can not set socket keep-alive option"); - } - } - - ok(req) - } -} diff --git a/src/ws/context.rs b/src/ws/context.rs deleted file mode 100644 index 4db83df5..00000000 --- a/src/ws/context.rs +++ /dev/null @@ -1,334 +0,0 @@ -extern crate actix; - -use bytes::Bytes; -use futures::sync::oneshot::{self, Sender}; -use futures::{Async, Future, Poll, Stream}; -use smallvec::SmallVec; - -use self::actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, - ToEnvelope, -}; -use self::actix::fut::ActorFuture; -use self::actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, - Message as ActixMessage, SpawnHandle, -}; - -use body::{Binary, Body}; -use context::{ActorHttpContext, Drain, Frame as ContextFrame}; -use error::{Error, ErrorInternalServerError, PayloadError}; -use httprequest::HttpRequest; - -use ws::frame::{Frame, FramedMessage}; -use ws::proto::{CloseReason, OpCode}; -use ws::{Message, ProtocolError, WsStream, WsWriter}; - -/// Execution context for `WebSockets` actors -pub struct WebsocketContext -where - A: Actor>, -{ - inner: ContextParts, - stream: Option>, - request: HttpRequest, - disconnected: bool, -} - -impl ActorContext for WebsocketContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for WebsocketContext -where - A: Actor, -{ - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl WebsocketContext -where - A: Actor, -{ - #[inline] - /// Create a new Websocket context from a request and an actor - pub fn create

    (req: HttpRequest, actor: A, stream: WsStream

    ) -> Body - where - A: StreamHandler, - P: Stream + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - ctx.add_stream(stream); - - Body::Actor(Box::new(WebsocketContextFut::new(ctx, actor, mb))) - } - - /// Create a new Websocket context - pub fn with_factory(req: HttpRequest, f: F) -> Body - where - F: FnOnce(&mut Self) -> A + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - - let act = f(&mut ctx); - Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb))) - } -} - -impl WebsocketContext -where - A: Actor, -{ - /// Write payload - /// - /// This is a low-level function that accepts framed messages that should - /// be created using `Frame::message()`. If you want to send text or binary - /// data you should prefer the `text()` or `binary()` convenience functions - /// that handle the framing for you. - #[inline] - pub fn write_raw(&mut self, data: FramedMessage) { - if !self.disconnected { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - let stream = self.stream.as_mut().unwrap(); - stream.push(ContextFrame::Chunk(Some(data.0))); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - self.request.state() - } - - /// Incoming request - #[inline] - pub fn request(&mut self) -> &mut HttpRequest { - &mut self.request - } - - /// Returns drain future - pub fn drain(&mut self) -> Drain { - let (tx, rx) = oneshot::channel(); - self.add_frame(ContextFrame::Drain(tx)); - Drain::new(rx) - } - - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write_raw(Frame::message(text.into(), OpCode::Text, true, false)); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write_raw(Frame::message(data, OpCode::Binary, true, false)); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &str) { - self.write_raw(Frame::message( - Vec::from(message), - OpCode::Ping, - true, - false, - )); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &str) { - self.write_raw(Frame::message( - Vec::from(message), - OpCode::Pong, - true, - false, - )); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write_raw(Frame::close(reason, false)); - } - - /// Check if connection still open - #[inline] - pub fn connected(&self) -> bool { - !self.disconnected - } - - #[inline] - fn add_frame(&mut self, frame: ContextFrame) { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - if let Some(s) = self.stream.as_mut() { - s.push(frame) - } - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } -} - -impl WsWriter for WebsocketContext -where - A: Actor, - S: 'static, -{ - /// Send text frame - #[inline] - fn send_text>(&mut self, text: T) { - self.text(text) - } - - /// Send binary frame - #[inline] - fn send_binary>(&mut self, data: B) { - self.binary(data) - } - - /// Send ping frame - #[inline] - fn send_ping(&mut self, message: &str) { - self.ping(message) - } - - /// Send pong frame - #[inline] - fn send_pong(&mut self, message: &str) { - self.pong(message) - } - - /// Send close frame - #[inline] - fn send_close(&mut self, reason: Option) { - self.close(reason) - } -} - -impl AsyncContextParts for WebsocketContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct WebsocketContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl WebsocketContextFut -where - A: Actor>, -{ - fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - WebsocketContextFut { fut } - } -} - -impl ActorHttpContext for WebsocketContextFut -where - A: Actor>, - S: 'static, -{ - #[inline] - fn disconnected(&mut self) { - self.fut.ctx().disconnected = true; - self.fut.ctx().stop(); - } - - fn poll(&mut self) -> Poll>, Error> { - if self.fut.alive() && self.fut.poll().is_err() { - return Err(ErrorInternalServerError("error")); - } - - // frames - if let Some(data) = self.fut.ctx().stream.take() { - Ok(Async::Ready(Some(data))) - } else if self.fut.alive() { - Ok(Async::NotReady) - } else { - Ok(Async::Ready(None)) - } - } -} - -impl ToEnvelope for WebsocketContext -where - A: Actor> + Handler, - M: ActixMessage + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} From fbf67544e5e4a4dcb49bff695b2282a76b94221d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 08:00:36 -0700 Subject: [PATCH 0725/1635] remove unused code --- Cargo.toml | 6 +- src/config.rs | 29 ++++++- src/header.rs | 111 +----------------------- src/lib.rs | 7 +- src/server/mod.rs | 204 --------------------------------------------- tests/test_h1v2.rs | 3 +- 6 files changed, 32 insertions(+), 328 deletions(-) delete mode 100644 src/server/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 870f772e..455b6148 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,15 +30,12 @@ path = "src/lib.rs" [features] default = ["session", "cell"] -# tls +# native-tls tls = ["native-tls", "tokio-tls", "actix-net/tls"] # openssl ssl = ["openssl", "tokio-openssl", "actix-net/ssl"] -# deprecated, use "ssl" -alpn = ["openssl", "tokio-openssl", "actix-net/ssl"] - # rustls rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"] @@ -61,7 +58,6 @@ http = "^0.1.8" httparse = "1.3" log = "0.4" mime = "0.3" -percent-encoding = "1.0" rand = "0.5" serde = "1.0" serde_json = "1.0" diff --git a/src/config.rs b/src/config.rs index 36b949c3..4e85044f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,11 +10,36 @@ use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use server::KeepAlive; - // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; +#[derive(Debug, PartialEq, Clone, Copy)] +/// Server keep-alive setting +pub enum KeepAlive { + /// Keep alive in seconds + Timeout(usize), + /// Relay on OS to shutdown tcp connection + Os, + /// Disabled + Disabled, +} + +impl From for KeepAlive { + fn from(keepalive: usize) -> Self { + KeepAlive::Timeout(keepalive) + } +} + +impl From> for KeepAlive { + fn from(keepalive: Option) -> Self { + if let Some(keepalive) = keepalive { + KeepAlive::Timeout(keepalive) + } else { + KeepAlive::Disabled + } + } +} + /// Http service configuration pub struct ServiceConfig(Rc); diff --git a/src/header.rs b/src/header.rs index 7b32d6c2..b1ba6524 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,13 +1,8 @@ //! Various http headers -use std::fmt; -use std::str::FromStr; - -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use mime::Mime; -use modhttp::header::GetAll; use modhttp::Error as HttpError; -use percent_encoding; pub use modhttp::header::*; @@ -159,107 +154,3 @@ impl<'a> From<&'a str> for ContentEncoding { } } } - -#[doc(hidden)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { - fmt::write(self, args) - } -} - -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited( - all: GetAll, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }).filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -/// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 -pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = - percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} -mod percent_encoding_http { - use percent_encoding; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 7efb4dea..accad2fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,7 @@ extern crate mime; extern crate net2; extern crate rand; extern crate serde; +extern crate serde_json; extern crate serde_urlencoded; extern crate tokio; extern crate tokio_codec; @@ -117,9 +118,6 @@ extern crate tokio_timer; #[cfg(all(unix, feature = "uds"))] extern crate tokio_uds; extern crate url; -#[macro_use] -extern crate percent_encoding; -extern crate serde_json; #[cfg(test)] #[macro_use] @@ -140,7 +138,6 @@ mod uri; pub mod error; pub mod h1; pub(crate) mod helpers; -pub mod server; pub mod test; //pub mod ws; pub use body::{Binary, Body}; @@ -151,7 +148,7 @@ pub use httpresponse::HttpResponse; pub use json::Json; pub use request::Request; -pub use self::config::{ServiceConfig, ServiceConfigBuilder}; +pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; pub mod dev { //! The `actix-web` prelude for library developers diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 972fbf3f..00000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,204 +0,0 @@ -//! Http server module -//! -//! The module contains everything necessary to setup -//! HTTP server. -//! -//! In order to start HTTP server, first you need to create and configure it -//! using factory that can be supplied to [new](fn.new.html). -//! -//! ## Factory -//! -//! Factory is a function that returns Application, describing how -//! to serve incoming HTTP requests. -//! -//! As the server uses worker pool, the factory function is restricted to trait bounds -//! `Sync + Send + 'static` so that each worker would be able to accept Application -//! without a need for synchronization. -//! -//! If you wish to share part of state among all workers you should -//! wrap it in `Arc` and potentially synchronization primitive like -//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html) -//! If the wrapped type is not thread safe. -//! -//! Note though that locking is not advisable for asynchronous programming -//! and you should minimize all locks in your request handlers -//! -//! ## HTTPS Support -//! -//! Actix-web provides support for major crates that provides TLS. -//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html) -//! that describes how HTTP Server accepts connections. -//! -//! For `bind` and `listen` there are corresponding `bind_with` and `listen_with` that accepts -//! these services. -//! -//! By default, acceptor would work with both HTTP2 and HTTP1 protocols. -//! But it can be controlled using [ServerFlags](struct.ServerFlags.html) which -//! can be supplied when creating `AcceptorService`. -//! -//! **NOTE:** `native-tls` doesn't support `HTTP2` yet -//! -//! ## Signal handling and shutdown -//! -//! By default HTTP Server listens for system signals -//! and, gracefully shuts down at most after 30 seconds. -//! -//! Both signal handling and shutdown timeout can be controlled -//! using corresponding methods. -//! -//! If worker, for some reason, unable to shut down within timeout -//! it is forcibly dropped. -//! -//! ## Example -//! -//! ```rust,ignore -//!extern crate actix; -//!extern crate actix_web; -//!extern crate rustls; -//! -//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder}; -//!use std::io::BufReader; -//!use rustls::internal::pemfile::{certs, rsa_private_keys}; -//!use rustls::{NoClientAuth, ServerConfig}; -//! -//!fn index(req: &HttpRequest) -> Result { -//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!")) -//!} -//! -//!fn load_ssl() -> ServerConfig { -//! use std::io::BufReader; -//! -//! const CERT: &'static [u8] = include_bytes!("../cert.pem"); -//! const KEY: &'static [u8] = include_bytes!("../key.pem"); -//! -//! let mut cert = BufReader::new(CERT); -//! let mut key = BufReader::new(KEY); -//! -//! let mut config = ServerConfig::new(NoClientAuth::new()); -//! let cert_chain = certs(&mut cert).unwrap(); -//! let mut keys = rsa_private_keys(&mut key).unwrap(); -//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); -//! -//! config -//!} -//! -//!fn main() { -//! let sys = actix::System::new("http-server"); -//! // load ssl keys -//! let config = load_ssl(); -//! -//! // Create acceptor service for only HTTP1 protocol -//! // You can use ::new(config) to leave defaults -//! let acceptor = server::RustlsAcceptor::with_flags(config, actix_web::server::ServerFlags::HTTP1); -//! -//! // create and start server at once -//! server::new(|| { -//! App::new() -//! // register simple handler, handle all methods -//! .resource("/index.html", |r| r.f(index)) -//! })) -//! }).bind_with("127.0.0.1:8080", acceptor) -//! .unwrap() -//! .start(); -//! -//! println!("Started http server: 127.0.0.1:8080"); -//! //Run system so that server would start accepting connections -//! let _ = sys.run(); -//!} -//! ``` -use std::net::SocketAddr; -use std::{io, time}; - -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_tcp::TcpStream; - -pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; - -#[doc(hidden)] -pub use super::helpers::write_content_length; - -// /// max buffer size 64k -// pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; - -#[derive(Debug, PartialEq, Clone, Copy)] -/// Server keep-alive setting -pub enum KeepAlive { - /// Keep alive in seconds - Timeout(usize), - /// Relay on OS to shutdown tcp connection - Os, - /// Disabled - Disabled, -} - -impl From for KeepAlive { - fn from(keepalive: usize) -> Self { - KeepAlive::Timeout(keepalive) - } -} - -impl From> for KeepAlive { - fn from(keepalive: Option) -> Self { - if let Some(keepalive) = keepalive { - KeepAlive::Timeout(keepalive) - } else { - KeepAlive::Disabled - } - } -} - -#[doc(hidden)] -/// Low-level io stream operations -pub trait IoStream: AsyncRead + AsyncWrite + 'static { - /// Returns the socket address of the remote peer of this TCP connection. - fn peer_addr(&self) -> Option { - None - } - - /// Sets the value of the TCP_NODELAY option on this socket. - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>; - - fn set_linger(&mut self, dur: Option) -> io::Result<()>; - - fn set_keepalive(&mut self, dur: Option) -> io::Result<()>; -} - -#[cfg(all(unix, feature = "uds"))] -impl IoStream for ::tokio_uds::UnixStream { - #[inline] - fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn set_linger(&mut self, _dur: Option) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn set_keepalive(&mut self, _nodelay: bool) -> io::Result<()> { - Ok(()) - } -} - -impl IoStream for TcpStream { - #[inline] - fn peer_addr(&self) -> Option { - TcpStream::peer_addr(self).ok() - } - - #[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) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - TcpStream::set_keepalive(self, dur) - } -} diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index 77a6ecae..bb943065 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -11,8 +11,7 @@ use actix_net::server::Server; use actix_web::{client, test}; use futures::future; -use actix_http::server::KeepAlive; -use actix_http::{h1, Error, HttpResponse, ServiceConfig}; +use actix_http::{h1, Error, HttpResponse, KeepAlive, ServiceConfig}; #[test] fn test_h1_v2() { From 2e27d7774089e21aecaea41f59657bc5c73882e4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 10:03:10 -0700 Subject: [PATCH 0726/1635] fix connection keepalive support --- src/framed/framed.rs | 283 +++++++++++++++++++++++++++++ src/framed/framed_read.rs | 216 ++++++++++++++++++++++ src/framed/framed_write.rs | 243 +++++++++++++++++++++++++ src/framed/mod.rs | 32 ++++ src/h1/codec.rs | 193 ++++++++++---------- src/h1/dispatcher.rs | 15 +- src/h1/{response.rs => encoder.rs} | 8 +- src/h1/mod.rs | 2 +- src/lib.rs | 3 + 9 files changed, 890 insertions(+), 105 deletions(-) create mode 100644 src/framed/framed.rs create mode 100644 src/framed/framed_read.rs create mode 100644 src/framed/framed_write.rs create mode 100644 src/framed/mod.rs rename src/h1/{response.rs => encoder.rs} (98%) diff --git a/src/framed/framed.rs b/src/framed/framed.rs new file mode 100644 index 00000000..f6295d98 --- /dev/null +++ b/src/framed/framed.rs @@ -0,0 +1,283 @@ +#![allow(deprecated)] + +use std::fmt; +use std::io::{self, Read, Write}; + +use bytes::BytesMut; +use futures::{Poll, Sink, StartSend, Stream}; +use tokio_codec::{Decoder, Encoder}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2}; +use super::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2}; + +/// A unified `Stream` and `Sink` interface to an underlying I/O object, using +/// the `Encoder` and `Decoder` traits to encode and decode frames. +/// +/// You can create a `Framed` instance by using the `AsyncRead::framed` adapter. +pub struct Framed { + inner: FramedRead2>>, +} + +pub struct Fuse(pub T, pub U); + +impl Framed +where + T: AsyncRead + AsyncWrite, + U: Decoder + Encoder, +{ + /// Provides a `Stream` and `Sink` interface for reading and writing to this + /// `Io` object, using `Decode` and `Encode` to read and write the raw data. + /// + /// Raw I/O objects work with byte sequences, but higher-level code usually + /// wants to batch these into meaningful chunks, called "frames". This + /// method layers framing on top of an I/O object, by using the `Codec` + /// traits to handle encoding and decoding of messages frames. Note that + /// the incoming and outgoing frame types may be distinct. + /// + /// This function returns a *single* object that is both `Stream` and + /// `Sink`; grouping this into a single object is often useful for layering + /// things like gzip or TLS, which require both read and write access to the + /// underlying object. + /// + /// If you want to work more directly with the streams and sink, consider + /// calling `split` on the `Framed` returned by this method, which will + /// break them into separate objects, allowing them to interact more easily. + pub fn new(inner: T, codec: U) -> Framed { + Framed { + inner: framed_read2(framed_write2(Fuse(inner, codec))), + } + } +} + +impl Framed { + /// Provides a `Stream` and `Sink` interface for reading and writing to this + /// `Io` object, using `Decode` and `Encode` to read and write the raw data. + /// + /// Raw I/O objects work with byte sequences, but higher-level code usually + /// wants to batch these into meaningful chunks, called "frames". This + /// method layers framing on top of an I/O object, by using the `Codec` + /// traits to handle encoding and decoding of messages frames. Note that + /// the incoming and outgoing frame types may be distinct. + /// + /// This function returns a *single* object that is both `Stream` and + /// `Sink`; grouping this into a single object is often useful for layering + /// things like gzip or TLS, which require both read and write access to the + /// underlying object. + /// + /// This objects takes a stream and a readbuffer and a writebuffer. These field + /// can be obtained from an existing `Framed` with the `into_parts` method. + /// + /// If you want to work more directly with the streams and sink, consider + /// calling `split` on the `Framed` returned by this method, which will + /// break them into separate objects, allowing them to interact more easily. + pub fn from_parts(parts: FramedParts) -> Framed { + Framed { + inner: framed_read2_with_buffer( + framed_write2_with_buffer(Fuse(parts.io, parts.codec), parts.write_buf), + parts.read_buf, + ), + } + } + + /// Returns a reference to the underlying codec. + pub fn get_codec(&self) -> &U { + &self.inner.get_ref().get_ref().1 + } + + /// Returns a mutable reference to the underlying codec. + pub fn get_codec_mut(&mut self) -> &mut U { + &mut self.inner.get_mut().get_mut().1 + } + + /// Returns a reference to the underlying I/O stream wrapped by + /// `Frame`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_ref(&self) -> &T { + &self.inner.get_ref().get_ref().0 + } + + /// Returns a mutable reference to the underlying I/O stream wrapped by + /// `Frame`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner.get_mut().get_mut().0 + } + + /// Consumes the `Frame`, returning its underlying I/O stream. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn into_inner(self) -> T { + self.inner.into_inner().into_inner().0 + } + + /// Consumes the `Frame`, returning its underlying I/O stream, the buffer + /// with unprocessed data, and the codec. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn into_parts(self) -> FramedParts { + let (inner, read_buf) = self.inner.into_parts(); + let (inner, write_buf) = inner.into_parts(); + + FramedParts { + io: inner.0, + codec: inner.1, + read_buf: read_buf, + write_buf: write_buf, + _priv: (), + } + } +} + +impl Stream for Framed +where + T: AsyncRead, + U: Decoder, +{ + type Item = U::Item; + type Error = U::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.inner.poll() + } +} + +impl Sink for Framed +where + T: AsyncWrite, + U: Encoder, + U::Error: From, +{ + type SinkItem = U::Item; + type SinkError = U::Error; + + fn start_send( + &mut self, item: Self::SinkItem, + ) -> StartSend { + self.inner.get_mut().start_send(item) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + self.inner.get_mut().poll_complete() + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + self.inner.get_mut().close() + } +} + +impl fmt::Debug for Framed +where + T: fmt::Debug, + U: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Framed") + .field("io", &self.inner.get_ref().get_ref().0) + .field("codec", &self.inner.get_ref().get_ref().1) + .finish() + } +} + +// ===== impl Fuse ===== + +impl Read for Fuse { + fn read(&mut self, dst: &mut [u8]) -> io::Result { + self.0.read(dst) + } +} + +impl AsyncRead for Fuse { + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.0.prepare_uninitialized_buffer(buf) + } +} + +impl Write for Fuse { + fn write(&mut self, src: &[u8]) -> io::Result { + self.0.write(src) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +impl AsyncWrite for Fuse { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.0.shutdown() + } +} + +impl Decoder for Fuse { + type Item = U::Item; + type Error = U::Error; + + fn decode( + &mut self, buffer: &mut BytesMut, + ) -> Result, Self::Error> { + self.1.decode(buffer) + } + + fn decode_eof( + &mut self, buffer: &mut BytesMut, + ) -> Result, Self::Error> { + self.1.decode_eof(buffer) + } +} + +impl Encoder for Fuse { + type Item = U::Item; + type Error = U::Error; + + fn encode( + &mut self, item: Self::Item, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + self.1.encode(item, dst) + } +} + +/// `FramedParts` contains an export of the data of a Framed transport. +/// It can be used to construct a new `Framed` with a different codec. +/// It contains all current buffers and the inner transport. +#[derive(Debug)] +pub struct FramedParts { + /// The inner transport used to read bytes to and write bytes to + pub io: T, + + /// The codec + pub codec: U, + + /// The buffer with read but unprocessed data. + pub read_buf: BytesMut, + + /// A buffer with unprocessed data which are not written yet. + pub write_buf: BytesMut, + + /// This private field allows us to add additional fields in the future in a + /// backwards compatible way. + _priv: (), +} + +impl FramedParts { + /// Create a new, default, `FramedParts` + pub fn new(io: T, codec: U) -> FramedParts { + FramedParts { + io, + codec, + read_buf: BytesMut::new(), + write_buf: BytesMut::new(), + _priv: (), + } + } +} diff --git a/src/framed/framed_read.rs b/src/framed/framed_read.rs new file mode 100644 index 00000000..065e2920 --- /dev/null +++ b/src/framed/framed_read.rs @@ -0,0 +1,216 @@ +use std::fmt; + +use bytes::BytesMut; +use futures::{Async, Poll, Sink, StartSend, Stream}; +use tokio_codec::Decoder; +use tokio_io::AsyncRead; + +use super::framed::Fuse; + +/// A `Stream` of messages decoded from an `AsyncRead`. +pub struct FramedRead { + inner: FramedRead2>, +} + +pub struct FramedRead2 { + inner: T, + eof: bool, + is_readable: bool, + buffer: BytesMut, +} + +const INITIAL_CAPACITY: usize = 8 * 1024; + +// ===== impl FramedRead ===== + +impl FramedRead +where + T: AsyncRead, + D: Decoder, +{ + /// Creates a new `FramedRead` with the given `decoder`. + pub fn new(inner: T, decoder: D) -> FramedRead { + FramedRead { + inner: framed_read2(Fuse(inner, decoder)), + } + } +} + +impl FramedRead { + /// Returns a reference to the underlying I/O stream wrapped by + /// `FramedRead`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_ref(&self) -> &T { + &self.inner.inner.0 + } + + /// Returns a mutable reference to the underlying I/O stream wrapped by + /// `FramedRead`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner.inner.0 + } + + /// Consumes the `FramedRead`, returning its underlying I/O stream. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn into_inner(self) -> T { + self.inner.inner.0 + } + + /// Returns a reference to the underlying decoder. + pub fn decoder(&self) -> &D { + &self.inner.inner.1 + } + + /// Returns a mutable reference to the underlying decoder. + pub fn decoder_mut(&mut self) -> &mut D { + &mut self.inner.inner.1 + } +} + +impl Stream for FramedRead +where + T: AsyncRead, + D: Decoder, +{ + type Item = D::Item; + type Error = D::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.inner.poll() + } +} + +impl Sink for FramedRead +where + T: Sink, +{ + type SinkItem = T::SinkItem; + type SinkError = T::SinkError; + + fn start_send( + &mut self, item: Self::SinkItem, + ) -> StartSend { + self.inner.inner.0.start_send(item) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + self.inner.inner.0.poll_complete() + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + self.inner.inner.0.close() + } +} + +impl fmt::Debug for FramedRead +where + T: fmt::Debug, + D: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FramedRead") + .field("inner", &self.inner.inner.0) + .field("decoder", &self.inner.inner.1) + .field("eof", &self.inner.eof) + .field("is_readable", &self.inner.is_readable) + .field("buffer", &self.inner.buffer) + .finish() + } +} + +// ===== impl FramedRead2 ===== + +pub fn framed_read2(inner: T) -> FramedRead2 { + FramedRead2 { + inner: inner, + eof: false, + is_readable: false, + buffer: BytesMut::with_capacity(INITIAL_CAPACITY), + } +} + +pub fn framed_read2_with_buffer(inner: T, mut buf: BytesMut) -> FramedRead2 { + if buf.capacity() < INITIAL_CAPACITY { + let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity(); + buf.reserve(bytes_to_reserve); + } + FramedRead2 { + inner: inner, + eof: false, + is_readable: buf.len() > 0, + buffer: buf, + } +} + +impl FramedRead2 { + pub fn get_ref(&self) -> &T { + &self.inner + } + + pub fn into_inner(self) -> T { + self.inner + } + + pub fn into_parts(self) -> (T, BytesMut) { + (self.inner, self.buffer) + } + + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl Stream for FramedRead2 +where + T: AsyncRead + Decoder, +{ + type Item = T::Item; + type Error = T::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + loop { + // Repeatedly call `decode` or `decode_eof` as long as it is + // "readable". Readable is defined as not having returned `None`. If + // the upstream has returned EOF, and the decoder is no longer + // readable, it can be assumed that the decoder will never become + // readable again, at which point the stream is terminated. + if self.is_readable { + if self.eof { + let frame = try!(self.inner.decode_eof(&mut self.buffer)); + return Ok(Async::Ready(frame)); + } + + trace!("attempting to decode a frame"); + + if let Some(frame) = try!(self.inner.decode(&mut self.buffer)) { + trace!("frame decoded from buffer"); + return Ok(Async::Ready(Some(frame))); + } + + self.is_readable = false; + } + + assert!(!self.eof); + + // Otherwise, try to read more data and try again. Make sure we've + // got room for at least one byte to read to ensure that we don't + // get a spurious 0 that looks like EOF + self.buffer.reserve(1); + if 0 == try_ready!(self.inner.read_buf(&mut self.buffer)) { + self.eof = true; + } + + self.is_readable = true; + } + } +} diff --git a/src/framed/framed_write.rs b/src/framed/framed_write.rs new file mode 100644 index 00000000..310c7630 --- /dev/null +++ b/src/framed/framed_write.rs @@ -0,0 +1,243 @@ +use std::fmt; +use std::io::{self, Read}; + +use bytes::BytesMut; +use futures::{Async, AsyncSink, Poll, Sink, StartSend, Stream}; +use tokio_codec::{Decoder, Encoder}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::framed::Fuse; + +/// A `Sink` of frames encoded to an `AsyncWrite`. +pub struct FramedWrite { + inner: FramedWrite2>, +} + +pub struct FramedWrite2 { + inner: T, + buffer: BytesMut, +} + +const INITIAL_CAPACITY: usize = 8 * 1024; +const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY; + +impl FramedWrite +where + T: AsyncWrite, + E: Encoder, +{ + /// Creates a new `FramedWrite` with the given `encoder`. + pub fn new(inner: T, encoder: E) -> FramedWrite { + FramedWrite { + inner: framed_write2(Fuse(inner, encoder)), + } + } +} + +impl FramedWrite { + /// Returns a reference to the underlying I/O stream wrapped by + /// `FramedWrite`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_ref(&self) -> &T { + &self.inner.inner.0 + } + + /// Returns a mutable reference to the underlying I/O stream wrapped by + /// `FramedWrite`. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner.inner.0 + } + + /// Consumes the `FramedWrite`, returning its underlying I/O stream. + /// + /// Note that care should be taken to not tamper with the underlying stream + /// of data coming in as it may corrupt the stream of frames otherwise + /// being worked with. + pub fn into_inner(self) -> T { + self.inner.inner.0 + } + + /// Returns a reference to the underlying decoder. + pub fn encoder(&self) -> &E { + &self.inner.inner.1 + } + + /// Returns a mutable reference to the underlying decoder. + pub fn encoder_mut(&mut self) -> &mut E { + &mut self.inner.inner.1 + } +} + +impl Sink for FramedWrite +where + T: AsyncWrite, + E: Encoder, +{ + type SinkItem = E::Item; + type SinkError = E::Error; + + fn start_send(&mut self, item: E::Item) -> StartSend { + self.inner.start_send(item) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + self.inner.poll_complete() + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + Ok(try!(self.inner.close())) + } +} + +impl Stream for FramedWrite +where + T: Stream, +{ + type Item = T::Item; + type Error = T::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.inner.inner.0.poll() + } +} + +impl fmt::Debug for FramedWrite +where + T: fmt::Debug, + U: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FramedWrite") + .field("inner", &self.inner.get_ref().0) + .field("encoder", &self.inner.get_ref().1) + .field("buffer", &self.inner.buffer) + .finish() + } +} + +// ===== impl FramedWrite2 ===== + +pub fn framed_write2(inner: T) -> FramedWrite2 { + FramedWrite2 { + inner: inner, + buffer: BytesMut::with_capacity(INITIAL_CAPACITY), + } +} + +pub fn framed_write2_with_buffer(inner: T, mut buf: BytesMut) -> FramedWrite2 { + if buf.capacity() < INITIAL_CAPACITY { + let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity(); + buf.reserve(bytes_to_reserve); + } + FramedWrite2 { + inner: inner, + buffer: buf, + } +} + +impl FramedWrite2 { + pub fn get_ref(&self) -> &T { + &self.inner + } + + pub fn into_inner(self) -> T { + self.inner + } + + pub fn into_parts(self) -> (T, BytesMut) { + (self.inner, self.buffer) + } + + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl Sink for FramedWrite2 +where + T: AsyncWrite + Encoder, +{ + type SinkItem = T::Item; + type SinkError = T::Error; + + fn start_send(&mut self, item: T::Item) -> StartSend { + // If the buffer is already over 8KiB, then attempt to flush it. If after flushing it's + // *still* over 8KiB, then apply backpressure (reject the send). + if self.buffer.len() >= BACKPRESSURE_BOUNDARY { + try!(self.poll_complete()); + + if self.buffer.len() >= BACKPRESSURE_BOUNDARY { + return Ok(AsyncSink::NotReady(item)); + } + } + + try!(self.inner.encode(item, &mut self.buffer)); + + Ok(AsyncSink::Ready) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + trace!("flushing framed transport"); + + while !self.buffer.is_empty() { + trace!("writing; remaining={}", self.buffer.len()); + + let n = try_ready!(self.inner.poll_write(&self.buffer)); + + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to \ + write frame to transport", + ).into()); + } + + // TODO: Add a way to `bytes` to do this w/o returning the drained + // data. + let _ = self.buffer.split_to(n); + } + + // Try flushing the underlying IO + try_ready!(self.inner.poll_flush()); + + trace!("framed transport flushed"); + return Ok(Async::Ready(())); + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + try_ready!(self.poll_complete()); + Ok(try!(self.inner.shutdown())) + } +} + +impl Decoder for FramedWrite2 { + type Item = T::Item; + type Error = T::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, T::Error> { + self.inner.decode(src) + } + + fn decode_eof(&mut self, src: &mut BytesMut) -> Result, T::Error> { + self.inner.decode_eof(src) + } +} + +impl Read for FramedWrite2 { + fn read(&mut self, dst: &mut [u8]) -> io::Result { + self.inner.read(dst) + } +} + +impl AsyncRead for FramedWrite2 { + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.inner.prepare_uninitialized_buffer(buf) + } +} diff --git a/src/framed/mod.rs b/src/framed/mod.rs new file mode 100644 index 00000000..cb0308fa --- /dev/null +++ b/src/framed/mod.rs @@ -0,0 +1,32 @@ +//! Utilities for encoding and decoding frames. +//! +//! Contains adapters to go from streams of bytes, [`AsyncRead`] and +//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`]. +//! Framed streams are also known as [transports]. +//! +//! [`AsyncRead`]: # +//! [`AsyncWrite`]: # +//! [`Sink`]: # +//! [`Stream`]: # +//! [transports]: # + +#![deny(missing_docs, missing_debug_implementations, warnings)] +#![doc(hidden, html_root_url = "https://docs.rs/tokio-codec/0.1.0")] + +// _tokio_codec are the items that belong in the `tokio_codec` crate. However, because we need to +// maintain backward compatibility until the next major breaking change, they are defined here. +// When the next breaking change comes, they should be moved to the `tokio_codec` crate and become +// independent. +// +// The primary reason we can't move these to `tokio-codec` now is because, again for backward +// compatibility reasons, we need to keep `Decoder` and `Encoder` in tokio_io::codec. And `Decoder` +// and `Encoder` needs to reference `Framed`. So they all still need to still be in the same +// module. + +mod framed; +mod framed_read; +mod framed_write; + +pub use self::framed::{Framed, FramedParts}; +pub use self::framed_read::FramedRead; +pub use self::framed_write::FramedWrite; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index dd6b26ba..40d0a240 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,7 +6,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::H1Decoder; pub use super::decoder::InMessage; -use super::response::{ResponseInfo, ResponseLength}; +use super::encoder::{ResponseEncoder, ResponseLength}; use body::Body; use error::ParseError; use helpers; @@ -15,6 +15,17 @@ use http::{Method, Version}; use httpresponse::HttpResponse; use request::RequestPool; +bitflags! { + struct Flags: u8 { + const HEAD = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const KEEPALIVE_ENABLED = 0b0001_0000; + } +} + +const AVERAGE_HEADER_SIZE: usize = 30; + /// Http response pub enum OutMessage { /// Http response message @@ -26,91 +37,40 @@ pub enum OutMessage { /// HTTP/1 Codec pub struct Codec { decoder: H1Decoder, - encoder: H1Writer, - head: bool, version: Version, -} -impl Codec { - /// Create HTTP/1 codec - pub fn new() -> Self { - Codec::with_pool(RequestPool::pool()) - } - - /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool(pool: &'static RequestPool) -> Self { - Codec { - decoder: H1Decoder::with_pool(pool), - encoder: H1Writer::new(), - head: false, - version: Version::HTTP_11, - } - } -} - -impl Decoder for Codec { - type Item = InMessage; - type Error = ParseError; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let res = self.decoder.decode(src); - - match res { - Ok(Some(InMessage::Message(ref req))) - | Ok(Some(InMessage::MessageWithPayload(ref req))) => { - self.head = req.inner.method == Method::HEAD; - self.version = req.inner.version; - } - _ => (), - } - res - } -} - -impl Encoder for Codec { - type Item = OutMessage; - type Error = io::Error; - - fn encode( - &mut self, item: Self::Item, dst: &mut BytesMut, - ) -> Result<(), Self::Error> { - match item { - OutMessage::Response(res) => { - self.encoder.encode(res, dst, self.head, self.version)?; - } - OutMessage::Payload(bytes) => { - dst.extend_from_slice(&bytes); - } - } - Ok(()) - } -} - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const DISCONNECTED = 0b0000_1000; - } -} - -const AVERAGE_HEADER_SIZE: usize = 30; - -struct H1Writer { + // encoder part flags: Flags, written: u64, headers_size: u32, - info: ResponseInfo, + te: ResponseEncoder, } -impl H1Writer { - fn new() -> H1Writer { - H1Writer { - flags: Flags::empty(), +impl Codec { + /// Create HTTP/1 codec. + /// + /// `keepalive_enabled` how response `connection` header get generated. + pub fn new(keepalive_enabled: bool) -> Self { + Codec::with_pool(RequestPool::pool(), keepalive_enabled) + } + + /// Create HTTP/1 codec with request's pool + pub(crate) fn with_pool( + pool: &'static RequestPool, keepalive_enabled: bool, + ) -> Self { + let flags = if keepalive_enabled { + Flags::KEEPALIVE_ENABLED + } else { + Flags::empty() + }; + Codec { + decoder: H1Decoder::with_pool(pool), + version: Version::HTTP_11, + + flags, written: 0, headers_size: 0, - info: ResponseInfo::default(), + te: ResponseEncoder::default(), } } @@ -118,46 +78,42 @@ impl H1Writer { self.written } - pub fn reset(&mut self) { - self.written = 0; - self.flags = Flags::KEEPALIVE; - } - pub fn upgrade(&self) -> bool { self.flags.contains(Flags::UPGRADE) } pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) + self.flags.contains(Flags::KEEPALIVE) } - fn encode( - &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, head: bool, - version: Version, + fn encode_response( + &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, ) -> io::Result<()> { - // prepare task - self.info.update(&mut msg, head, version); + // prepare transfer encoding + self.te + .update(&mut msg, self.flags.contains(Flags::HEAD), self.version); - //if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { - //self.flags = Flags::STARTED | Flags::KEEPALIVE; - //} else { - self.flags = Flags::STARTED; - //} + let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg + .keep_alive() + .unwrap_or_else(|| self.flags.contains(Flags::KEEPALIVE)); // Connection upgrade - let version = msg.version().unwrap_or_else(|| Version::HTTP_11); //req.inner.version); + let version = msg.version().unwrap_or_else(|| self.version); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); + self.flags.remove(Flags::KEEPALIVE); msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive - else if self.flags.contains(Flags::KEEPALIVE) { + else if ka { + self.flags.insert(Flags::KEEPALIVE); if version < Version::HTTP_11 { msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("keep-alive")); } } else if version >= Version::HTTP_11 { + self.flags.remove(Flags::KEEPALIVE); msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("close")); } @@ -183,7 +139,7 @@ impl H1Writer { buffer.extend_from_slice(reason); // content length - match self.info.length { + match self.te.length { ResponseLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } @@ -209,7 +165,7 @@ impl H1Writer { for (key, value) in msg.headers() { match *key { TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match self.info.length { + CONTENT_LENGTH => match self.te.length { ResponseLength::None => (), _ => continue, }, @@ -272,3 +228,46 @@ impl H1Writer { Ok(()) } } + +impl Decoder for Codec { + type Item = InMessage; + type Error = ParseError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + let res = self.decoder.decode(src); + + match res { + Ok(Some(InMessage::Message(ref req))) + | Ok(Some(InMessage::MessageWithPayload(ref req))) => { + self.flags + .set(Flags::HEAD, req.inner.method == Method::HEAD); + self.version = req.inner.version; + if self.flags.contains(Flags::KEEPALIVE_ENABLED) { + self.flags.set(Flags::KEEPALIVE, req.keep_alive()); + } + } + _ => (), + } + res + } +} + +impl Encoder for Codec { + type Item = OutMessage; + type Error = io::Error; + + fn encode( + &mut self, item: Self::Item, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + match item { + OutMessage::Response(res) => { + self.written = 0; + self.encode_response(res, dst)?; + } + OutMessage::Payload(bytes) => { + dst.extend_from_slice(&bytes); + } + } + Ok(()) + } +} diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 2b524556..18309402 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -6,7 +6,6 @@ use std::time::Instant; use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; -use tokio_codec::Framed; // use tokio_current_thread::spawn; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; @@ -17,6 +16,7 @@ use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; use config::ServiceConfig; use error::DispatchError; +use framed::Framed; use httpresponse::HttpResponse; use request::Request; @@ -89,12 +89,13 @@ where pub fn with_timeout( stream: T, config: ServiceConfig, timeout: Option, service: S, ) -> Self { - let flags = if config.keep_alive_enabled() { + let keepalive = config.keep_alive_enabled(); + let flags = if keepalive { Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED } else { Flags::FLUSHED }; - let framed = Framed::new(stream, Codec::new()); + let framed = Framed::new(stream, Codec::new(keepalive)); let (ka_expire, ka_timer) = if let Some(delay) = timeout { (delay.deadline(), Some(delay)) @@ -235,6 +236,10 @@ where let msg = item.take().expect("SendResponse is empty"); match self.framed.start_send(msg) { Ok(AsyncSink::Ready) => { + self.flags.set( + Flags::KEEPALIVE, + self.framed.get_codec().keepalive(), + ); self.flags.remove(Flags::FLUSHED); Some(Ok(State::None)) } @@ -249,6 +254,10 @@ where let (msg, body) = item.take().expect("SendResponse is empty"); match self.framed.start_send(msg) { Ok(AsyncSink::Ready) => { + self.flags.set( + Flags::KEEPALIVE, + self.framed.get_codec().keepalive(), + ); self.flags.remove(Flags::FLUSHED); Some(Ok(State::Payload(body))) } diff --git a/src/h1/response.rs b/src/h1/encoder.rs similarity index 98% rename from src/h1/response.rs rename to src/h1/encoder.rs index fc688684..d1758735 100644 --- a/src/h1/response.rs +++ b/src/h1/encoder.rs @@ -24,15 +24,15 @@ pub(crate) enum ResponseLength { } #[derive(Debug)] -pub(crate) struct ResponseInfo { +pub(crate) struct ResponseEncoder { head: bool, pub length: ResponseLength, pub te: TransferEncoding, } -impl Default for ResponseInfo { +impl Default for ResponseEncoder { fn default() -> Self { - ResponseInfo { + ResponseEncoder { head: false, length: ResponseLength::None, te: TransferEncoding::empty(), @@ -40,7 +40,7 @@ impl Default for ResponseInfo { } } -impl ResponseInfo { +impl ResponseEncoder { pub fn update(&mut self, resp: &mut HttpResponse, head: bool, version: Version) { self.head = head; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 1653227b..f9abfea5 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -2,7 +2,7 @@ mod codec; mod decoder; mod dispatcher; -mod response; +mod encoder; mod service; pub use self::codec::{Codec, InMessage, OutMessage}; diff --git a/src/lib.rs b/src/lib.rs index accad2fb..0544569a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,9 @@ mod payload; mod request; mod uri; +#[doc(hidden)] +pub mod framed; + pub mod error; pub mod h1; pub(crate) mod helpers; From d53f3d718793aa9f7aed0feca1f6de74b970a4f7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 10:19:15 -0700 Subject: [PATCH 0727/1635] re-enable websockets --- src/lib.rs | 2 +- src/ws/client.rs | 602 ----------------------------------------------- src/ws/mod.rs | 71 +----- 3 files changed, 6 insertions(+), 669 deletions(-) delete mode 100644 src/ws/client.rs diff --git a/src/lib.rs b/src/lib.rs index 0544569a..ae5a9f95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,7 @@ pub mod error; pub mod h1; pub(crate) mod helpers; pub mod test; -//pub mod ws; +pub mod ws; pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; diff --git a/src/ws/client.rs b/src/ws/client.rs deleted file mode 100644 index 18789fef..00000000 --- a/src/ws/client.rs +++ /dev/null @@ -1,602 +0,0 @@ -//! Http client request -use std::cell::RefCell; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, io, str}; - -use base64; -use bytes::Bytes; -use cookie::Cookie; -use futures::sync::mpsc::{unbounded, UnboundedSender}; -use futures::{Async, Future, Poll, Stream}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HttpTryFrom, StatusCode}; -use rand; -use sha1::Sha1; - -use actix::{Addr, SystemService}; - -use body::{Binary, Body}; -use error::{Error, UrlParseError}; -use header::IntoHeaderValue; -use httpmessage::HttpMessage; -use payload::PayloadBuffer; - -use client::{ - ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError, - Pipeline, SendRequest, SendRequestError, -}; - -use super::frame::{Frame, FramedMessage}; -use super::proto::{CloseReason, OpCode}; -use super::{Message, ProtocolError, WsWriter}; - -/// Websocket client error -#[derive(Fail, Debug)] -pub enum ClientError { - /// Invalid url - #[fail(display = "Invalid url")] - InvalidUrl, - /// Invalid response status - #[fail(display = "Invalid response status")] - InvalidResponseStatus(StatusCode), - /// Invalid upgrade header - #[fail(display = "Invalid upgrade header")] - InvalidUpgradeHeader, - /// Invalid connection header - #[fail(display = "Invalid connection header")] - InvalidConnectionHeader(HeaderValue), - /// Missing CONNECTION header - #[fail(display = "Missing CONNECTION header")] - MissingConnectionHeader, - /// Missing SEC-WEBSOCKET-ACCEPT header - #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] - MissingWebSocketAcceptHeader, - /// Invalid challenge response - #[fail(display = "Invalid challenge response")] - InvalidChallengeResponse(String, HeaderValue), - /// Http parsing error - #[fail(display = "Http parsing error")] - Http(Error), - /// Url parsing error - #[fail(display = "Url parsing error")] - Url(UrlParseError), - /// Response parsing error - #[fail(display = "Response parsing error")] - ResponseParseError(HttpResponseParserError), - /// Send request error - #[fail(display = "{}", _0)] - SendRequest(SendRequestError), - /// Protocol error - #[fail(display = "{}", _0)] - Protocol(#[cause] ProtocolError), - /// IO Error - #[fail(display = "{}", _0)] - Io(io::Error), - /// "Disconnected" - #[fail(display = "Disconnected")] - Disconnected, -} - -impl From for ClientError { - fn from(err: Error) -> ClientError { - ClientError::Http(err) - } -} - -impl From for ClientError { - fn from(err: UrlParseError) -> ClientError { - ClientError::Url(err) - } -} - -impl From for ClientError { - fn from(err: SendRequestError) -> ClientError { - ClientError::SendRequest(err) - } -} - -impl From for ClientError { - fn from(err: ProtocolError) -> ClientError { - ClientError::Protocol(err) - } -} - -impl From for ClientError { - fn from(err: io::Error) -> ClientError { - ClientError::Io(err) - } -} - -impl From for ClientError { - fn from(err: HttpResponseParserError) -> ClientError { - ClientError::ResponseParseError(err) - } -} - -/// `WebSocket` client -/// -/// Example of `WebSocket` client usage is available in -/// [websocket example]( -/// https://github.com/actix/examples/blob/master/websocket/src/client.rs#L24) -pub struct Client { - request: ClientRequestBuilder, - err: Option, - http_err: Option, - origin: Option, - protocols: Option, - conn: Addr, - max_size: usize, - no_masking: bool, -} - -impl Client { - /// Create new websocket connection - pub fn new>(uri: S) -> Client { - Client::with_connector(uri, ClientConnector::from_registry()) - } - - /// Create new websocket connection with custom `ClientConnector` - pub fn with_connector>(uri: S, conn: Addr) -> Client { - let mut cl = Client { - request: ClientRequest::build(), - err: None, - http_err: None, - origin: None, - protocols: None, - max_size: 65_536, - no_masking: false, - conn, - }; - cl.request.uri(uri.as_ref()); - cl - } - - /// Set supported websocket protocols - pub fn protocols(mut self, protos: U) -> Self - where - U: IntoIterator + 'static, - V: AsRef, - { - let mut protos = protos - .into_iter() - .fold(String::new(), |acc, s| acc + s.as_ref() + ","); - protos.pop(); - self.protocols = Some(protos); - self - } - - /// Set cookie for handshake request - pub fn cookie(mut self, cookie: Cookie) -> Self { - self.request.cookie(cookie); - self - } - - /// Set request Origin - pub fn origin(mut self, origin: V) -> Self - where - HeaderValue: HttpTryFrom, - { - match HeaderValue::try_from(origin) { - Ok(value) => self.origin = Some(value), - Err(e) => self.http_err = Some(e.into()), - } - self - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_frame_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } - - /// Set write buffer capacity - /// - /// Default buffer capacity is 32kb - pub fn write_buffer_capacity(mut self, cap: usize) -> Self { - self.request.write_buffer_capacity(cap); - self - } - - /// Disable payload masking. By default ws client masks frame payload. - pub fn no_masking(mut self) -> Self { - self.no_masking = true; - self - } - - /// Set request header - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - self.request.header(key, value); - self - } - - /// Set websocket handshake timeout - /// - /// Handshake timeout is a total time for successful handshake. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.request.timeout(timeout); - self - } - - /// Connect to websocket server and do ws handshake - pub fn connect(&mut self) -> ClientHandshake { - if let Some(e) = self.err.take() { - ClientHandshake::error(e) - } else if let Some(e) = self.http_err.take() { - ClientHandshake::error(Error::from(e).into()) - } else { - // origin - if let Some(origin) = self.origin.take() { - self.request.set_header(header::ORIGIN, origin); - } - - self.request.upgrade(); - self.request.set_header(header::UPGRADE, "websocket"); - self.request.set_header(header::CONNECTION, "upgrade"); - self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); - self.request.with_connector(self.conn.clone()); - - if let Some(protocols) = self.protocols.take() { - self.request - .set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); - } - let request = match self.request.finish() { - Ok(req) => req, - Err(err) => return ClientHandshake::error(err.into()), - }; - - if request.uri().host().is_none() { - return ClientHandshake::error(ClientError::InvalidUrl); - } - if let Some(scheme) = request.uri().scheme_part() { - if scheme != "http" - && scheme != "https" - && scheme != "ws" - && scheme != "wss" - { - return ClientHandshake::error(ClientError::InvalidUrl); - } - } else { - return ClientHandshake::error(ClientError::InvalidUrl); - } - - // start handshake - ClientHandshake::new(request, self.max_size, self.no_masking) - } - } -} - -struct Inner { - tx: UnboundedSender, - rx: PayloadBuffer>, - closed: bool, -} - -/// Future that implementes client websocket handshake process. -/// -/// It resolves to a pair of `ClientReader` and `ClientWriter` that -/// can be used for reading and writing websocket frames. -pub struct ClientHandshake { - request: Option, - tx: Option>, - key: String, - error: Option, - max_size: usize, - no_masking: bool, -} - -impl ClientHandshake { - fn new( - mut request: ClientRequest, max_size: usize, no_masking: bool, - ) -> ClientHandshake { - // Generate a random key for the `Sec-WebSocket-Key` header. - // a base64-encoded (see Section 4 of [RFC4648]) value that, - // when decoded, is 16 bytes in length (RFC 6455) - let sec_key: [u8; 16] = rand::random(); - let key = base64::encode(&sec_key); - - request.headers_mut().insert( - header::SEC_WEBSOCKET_KEY, - HeaderValue::try_from(key.as_str()).unwrap(), - ); - - let (tx, rx) = unbounded(); - request.set_body(Body::Streaming(Box::new(rx.map_err(|_| { - io::Error::new(io::ErrorKind::Other, "disconnected").into() - })))); - - ClientHandshake { - key, - max_size, - no_masking, - request: Some(request.send()), - tx: Some(tx), - error: None, - } - } - - fn error(err: ClientError) -> ClientHandshake { - ClientHandshake { - key: String::new(), - request: None, - tx: None, - error: Some(err), - max_size: 0, - no_masking: false, - } - } - - /// Set handshake timeout - /// - /// Handshake timeout is a total time before handshake should be completed. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - if let Some(request) = self.request.take() { - self.request = Some(request.timeout(timeout)); - } - self - } - - /// Set connection timeout - /// - /// Connection timeout includes resolving hostname and actual connection to - /// the host. - /// Default value is 1 second. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - if let Some(request) = self.request.take() { - self.request = Some(request.conn_timeout(timeout)); - } - self - } -} - -impl Future for ClientHandshake { - type Item = (ClientReader, ClientWriter); - type Error = ClientError; - - fn poll(&mut self) -> Poll { - if let Some(err) = self.error.take() { - return Err(err); - } - - let resp = match self.request.as_mut().unwrap().poll()? { - Async::Ready(response) => { - self.request.take(); - response - } - Async::NotReady => return Ok(Async::NotReady), - }; - - // verify response - if resp.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(ClientError::InvalidResponseStatus(resp.status())); - } - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - trace!("Invalid upgrade header"); - return Err(ClientError::InvalidUpgradeHeader); - } - // Check for "CONNECTION" header - if let Some(conn) = resp.headers().get(header::CONNECTION) { - if let Ok(s) = conn.to_str() { - if !s.to_lowercase().contains("upgrade") { - trace!("Invalid connection header: {}", s); - return Err(ClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - trace!("Invalid connection header: {:?}", conn); - return Err(ClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - trace!("Missing connection header"); - return Err(ClientError::MissingConnectionHeader); - } - - if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT) { - // field is constructed by concatenating /key/ - // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) - const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(self.key.as_ref()); - sha1.update(WS_GUID); - let encoded = base64::encode(&sha1.digest().bytes()); - if key.as_bytes() != encoded.as_bytes() { - trace!( - "Invalid challenge response: expected: {} received: {:?}", - encoded, - key - ); - return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); - } - } else { - trace!("Missing SEC-WEBSOCKET-ACCEPT header"); - return Err(ClientError::MissingWebSocketAcceptHeader); - }; - - let inner = Inner { - tx: self.tx.take().unwrap(), - rx: PayloadBuffer::new(resp.payload()), - closed: false, - }; - - let inner = Rc::new(RefCell::new(inner)); - Ok(Async::Ready(( - ClientReader { - inner: Rc::clone(&inner), - max_size: self.max_size, - no_masking: self.no_masking, - }, - ClientWriter { inner }, - ))) - } -} - -/// Websocket reader client -pub struct ClientReader { - inner: Rc>, - max_size: usize, - no_masking: bool, -} - -impl fmt::Debug for ClientReader { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ws::ClientReader()") - } -} - -impl Stream for ClientReader { - type Item = Message; - type Error = ProtocolError; - - fn poll(&mut self) -> Poll, Self::Error> { - let max_size = self.max_size; - let no_masking = self.no_masking; - let mut inner = self.inner.borrow_mut(); - if inner.closed { - return Ok(Async::Ready(None)); - } - - // read - match Frame::parse(&mut inner.rx, no_masking, max_size) { - Ok(Async::Ready(Some(frame))) => { - let (_finished, opcode, payload) = frame.unpack(); - - match opcode { - // continuation is not supported - OpCode::Continue => { - inner.closed = true; - Err(ProtocolError::NoContinuation) - } - OpCode::Bad => { - inner.closed = true; - Err(ProtocolError::BadOpCode) - } - OpCode::Close => { - inner.closed = true; - let close_reason = Frame::parse_close_payload(&payload); - Ok(Async::Ready(Some(Message::Close(close_reason)))) - } - OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Pong => Ok(Async::Ready(Some(Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => { - inner.closed = true; - Err(ProtocolError::BadEncoding) - } - } - } - } - } - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { - inner.closed = true; - Err(e) - } - } - } -} - -/// Websocket writer client -pub struct ClientWriter { - inner: Rc>, -} - -impl ClientWriter { - /// Write payload - #[inline] - fn write(&mut self, mut data: FramedMessage) { - let inner = self.inner.borrow_mut(); - if !inner.closed { - let _ = inner.tx.unbounded_send(data.0.take()); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write(Frame::message(text.into(), OpCode::Text, true, true)); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write(Frame::message(data, OpCode::Binary, true, true)); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true)); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true)); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write(Frame::close(reason, true)); - } -} - -impl WsWriter for ClientWriter { - /// Send text frame - #[inline] - fn send_text>(&mut self, text: T) { - self.text(text) - } - - /// Send binary frame - #[inline] - fn send_binary>(&mut self, data: B) { - self.binary(data) - } - - /// Send ping frame - #[inline] - fn send_ping(&mut self, message: &str) { - self.ping(message) - } - - /// Send pong frame - #[inline] - fn send_pong(&mut self, message: &str) { - self.pong(message) - } - - /// Send close frame - #[inline] - fn send_close(&mut self, reason: Option) { - self.close(reason); - } -} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index c16f8d6d..6bb84c18 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -1,69 +1,23 @@ -//! `WebSocket` support for Actix +//! `WebSocket` support. //! //! To setup a `WebSocket`, first do web socket handshake then on success //! convert `Payload` into a `WsStream` stream and then use `WsWriter` to //! communicate with the peer. -//! -//! ## Example -//! -//! ```rust -//! # extern crate actix_web; -//! # use actix_web::actix::*; -//! # use actix_web::*; -//! use actix_web::{ws, HttpRequest, HttpResponse}; -//! -//! // do websocket handshake and start actor -//! fn ws_index(req: &HttpRequest) -> Result { -//! ws::start(req, Ws) -//! } -//! -//! struct Ws; -//! -//! impl Actor for Ws { -//! type Context = ws::WebsocketContext; -//! } -//! -//! // Handler for ws::Message messages -//! impl StreamHandler for Ws { -//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { -//! match msg { -//! ws::Message::Ping(msg) => ctx.pong(&msg), -//! ws::Message::Text(text) => ctx.text(text), -//! ws::Message::Binary(bin) => ctx.binary(bin), -//! _ => (), -//! } -//! } -//! } -//! # -//! # fn main() { -//! # App::new() -//! # .resource("/ws/", |r| r.f(ws_index)) // <- register websocket route -//! # .finish(); -//! # } //! ``` use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{header, Method, StatusCode}; -use super::actix::{Actor, StreamHandler}; - use body::Binary; -use error::{Error, PayloadError, ResponseError}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; +use error::{PayloadError, ResponseError}; use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use payload::PayloadBuffer; +use request::Request; -mod client; -mod context; mod frame; mod mask; mod proto; -pub use self::client::{ - Client, ClientError, ClientHandshake, ClientReader, ClientWriter, -}; -pub use self::context::WebsocketContext; pub use self::frame::{Frame, FramedMessage}; pub use self::proto::{CloseCode, CloseReason, OpCode}; @@ -156,7 +110,7 @@ impl ResponseError for HandshakeError { } /// `WebSocket` Message -#[derive(Debug, PartialEq, Message)] +#[derive(Debug, PartialEq)] pub enum Message { /// Text message Text(String), @@ -170,19 +124,6 @@ pub enum Message { Close(Option), } -/// Do websocket handshake and start actor -pub fn start(req: &HttpRequest, actor: A) -> Result -where - A: Actor> + StreamHandler, - S: 'static, -{ - let mut resp = handshake(req)?; - let stream = WsStream::new(req.payload()); - - let body = WebsocketContext::create(req.clone(), actor, stream); - Ok(resp.body(body)) -} - /// Prepare `WebSocket` handshake response. /// /// This function returns handshake `HttpResponse`, ready to send to peer. @@ -191,9 +132,7 @@ where // /// `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: &Request) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); From 8c2244dd889ce4a1f2e216b379661aa3a22806e5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 11:04:59 -0700 Subject: [PATCH 0728/1635] rename HttpResponse --- README.md | 2 +- src/error.rs | 133 +++++++------- src/h1/codec.rs | 6 +- src/h1/decoder.rs | 2 +- src/h1/dispatcher.rs | 8 +- src/h1/encoder.rs | 6 +- src/h1/service.rs | 10 +- src/httpcodes.rs | 12 +- src/httpmessage.rs | 16 +- src/json.rs | 6 +- src/lib.rs | 12 +- src/payload.rs | 2 +- src/{httpresponse.rs => response.rs} | 260 +++++++++++++-------------- src/test.rs | 22 +-- src/ws/mod.rs | 52 ++---- tests/test_h1v2.rs | 4 +- 16 files changed, 266 insertions(+), 287 deletions(-) rename src/{httpresponse.rs => response.rs} (81%) diff --git a/README.md b/README.md index 7ddd532e..b273ea8c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![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/fafhrd91/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/fafhrd91/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http diff --git a/src/error.rs b/src/error.rs index fb5df232..dc2d45b8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,8 +21,7 @@ pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; -// use httprequest::HttpRequest; -use httpresponse::{HttpResponse, HttpResponseParts}; +use response::{Response, ResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -124,13 +123,13 @@ impl InternalResponseErrorAsFail for T { } } -/// Error that can be converted to `HttpResponse` +/// Error that can be converted to `Response` pub trait ResponseError: Fail + InternalResponseErrorAsFail { /// Create response for error /// /// Internal server error is generated by default. - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) + fn error_response(&self) -> Response { + Response::new(StatusCode::INTERNAL_SERVER_ERROR) } } @@ -155,10 +154,10 @@ impl fmt::Debug for Error { } } -/// Convert `Error` to a `HttpResponse` instance -impl From for HttpResponse { +/// Convert `Error` to a `Response` instance +impl From for Response { fn from(err: Error) -> Self { - HttpResponse::from_error(err) + Response::from_error(err) } } @@ -202,15 +201,15 @@ impl ResponseError for UrlParseError {} /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } /// Return `BAD_REQUEST` for `Utf8Error` impl ResponseError for Utf8Error { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -220,26 +219,26 @@ impl ResponseError for HttpError {} /// Return `InternalServerError` for `io::Error` impl ResponseError for io::Error { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match self.kind() { - io::ErrorKind::NotFound => HttpResponse::new(StatusCode::NOT_FOUND), - io::ErrorKind::PermissionDenied => HttpResponse::new(StatusCode::FORBIDDEN), - _ => HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR), + io::ErrorKind::NotFound => Response::new(StatusCode::NOT_FOUND), + io::ErrorKind::PermissionDenied => Response::new(StatusCode::FORBIDDEN), + _ => Response::new(StatusCode::INTERNAL_SERVER_ERROR), } } } /// `BadRequest` for `InvalidHeaderValue` impl ResponseError for header::InvalidHeaderValue { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } /// `BadRequest` for `InvalidHeaderValue` impl ResponseError for header::InvalidHeaderValueBytes { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -288,8 +287,8 @@ pub enum ParseError { /// Return `BadRequest` for `ParseError` impl ResponseError for ParseError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -362,18 +361,18 @@ impl From for PayloadError { /// - `Overflow` returns `PayloadTooLarge` /// - Other errors returns `BadRequest` impl ResponseError for PayloadError { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match *self { - PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + PayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), + _ => Response::new(StatusCode::BAD_REQUEST), } } } /// Return `BadRequest` for `cookie::ParseError` impl ResponseError for cookie::ParseError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -443,8 +442,8 @@ pub enum ContentTypeError { /// Return `BadRequest` for `ContentTypeError` impl ResponseError for ContentTypeError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -475,15 +474,11 @@ pub enum UrlencodedError { /// Return `BadRequest` for `UrlencodedError` impl ResponseError for UrlencodedError { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match *self { - UrlencodedError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - UrlencodedError::UnknownLength => { - HttpResponse::new(StatusCode::LENGTH_REQUIRED) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + UrlencodedError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), + UrlencodedError::UnknownLength => Response::new(StatusCode::LENGTH_REQUIRED), + _ => Response::new(StatusCode::BAD_REQUEST), } } } @@ -513,12 +508,10 @@ pub enum JsonPayloadError { /// Return `BadRequest` for `UrlencodedError` impl ResponseError for JsonPayloadError { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match *self { - JsonPayloadError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + JsonPayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), + _ => Response::new(StatusCode::BAD_REQUEST), } } } @@ -606,7 +599,7 @@ pub struct InternalError { enum InternalErrorType { Status(StatusCode), - Response(Box>>), + Response(Box>>), } impl InternalError { @@ -619,8 +612,8 @@ impl InternalError { } } - /// Create `InternalError` with predefined `HttpResponse`. - pub fn from_response(cause: T, response: HttpResponse) -> Self { + /// Create `InternalError` with predefined `Response`. + pub fn from_response(cause: T, response: Response) -> Self { let resp = response.into_parts(); InternalError { cause, @@ -661,14 +654,14 @@ impl ResponseError for InternalError where T: Send + Sync + fmt::Debug + fmt::Display + 'static, { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match self.status { - InternalErrorType::Status(st) => HttpResponse::new(st), + InternalErrorType::Status(st) => Response::new(st), InternalErrorType::Response(ref resp) => { if let Some(resp) = resp.lock().unwrap().take() { - HttpResponse::from_parts(resp) + Response::from_parts(resp) } else { - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) + Response::new(StatusCode::INTERNAL_SERVER_ERROR) } } } @@ -838,14 +831,14 @@ mod tests { #[test] fn test_into_response() { - let resp: HttpResponse = ParseError::Incomplete.error_response(); + let resp: Response = ParseError::Incomplete.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = CookieParseError::EmptyName.error_response(); + let resp: Response = CookieParseError::EmptyName.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); - let resp: HttpResponse = err.error_response(); + let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -883,7 +876,7 @@ mod tests { fn test_error_http_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); let e = Error::from(orig); - let resp: HttpResponse = e.into(); + let resp: Response = e.into(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } @@ -944,8 +937,8 @@ mod tests { #[test] fn test_internal_error() { let err = - InternalError::from_response(ParseError::Method, HttpResponse::Ok().into()); - let resp: HttpResponse = err.error_response(); + InternalError::from_response(ParseError::Method, Response::Ok().into()); + let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::OK); } @@ -977,49 +970,49 @@ mod tests { #[test] fn test_error_helpers() { - let r: HttpResponse = ErrorBadRequest("err").into(); + let r: Response = ErrorBadRequest("err").into(); assert_eq!(r.status(), StatusCode::BAD_REQUEST); - let r: HttpResponse = ErrorUnauthorized("err").into(); + let r: Response = ErrorUnauthorized("err").into(); assert_eq!(r.status(), StatusCode::UNAUTHORIZED); - let r: HttpResponse = ErrorForbidden("err").into(); + let r: Response = ErrorForbidden("err").into(); assert_eq!(r.status(), StatusCode::FORBIDDEN); - let r: HttpResponse = ErrorNotFound("err").into(); + let r: Response = ErrorNotFound("err").into(); assert_eq!(r.status(), StatusCode::NOT_FOUND); - let r: HttpResponse = ErrorMethodNotAllowed("err").into(); + let r: Response = ErrorMethodNotAllowed("err").into(); assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); - let r: HttpResponse = ErrorRequestTimeout("err").into(); + let r: Response = ErrorRequestTimeout("err").into(); assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); - let r: HttpResponse = ErrorConflict("err").into(); + let r: Response = ErrorConflict("err").into(); assert_eq!(r.status(), StatusCode::CONFLICT); - let r: HttpResponse = ErrorGone("err").into(); + let r: Response = ErrorGone("err").into(); assert_eq!(r.status(), StatusCode::GONE); - let r: HttpResponse = ErrorPreconditionFailed("err").into(); + let r: Response = ErrorPreconditionFailed("err").into(); assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); - let r: HttpResponse = ErrorExpectationFailed("err").into(); + let r: Response = ErrorExpectationFailed("err").into(); assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); - let r: HttpResponse = ErrorInternalServerError("err").into(); + let r: Response = ErrorInternalServerError("err").into(); assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); - let r: HttpResponse = ErrorNotImplemented("err").into(); + let r: Response = ErrorNotImplemented("err").into(); assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED); - let r: HttpResponse = ErrorBadGateway("err").into(); + let r: Response = ErrorBadGateway("err").into(); assert_eq!(r.status(), StatusCode::BAD_GATEWAY); - let r: HttpResponse = ErrorServiceUnavailable("err").into(); + let r: Response = ErrorServiceUnavailable("err").into(); assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE); - let r: HttpResponse = ErrorGatewayTimeout("err").into(); + let r: Response = ErrorGatewayTimeout("err").into(); assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); } } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 40d0a240..a27e6472 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -12,8 +12,8 @@ use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; -use httpresponse::HttpResponse; use request::RequestPool; +use response::Response; bitflags! { struct Flags: u8 { @@ -29,7 +29,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// Http response pub enum OutMessage { /// Http response message - Response(HttpResponse), + Response(Response), /// Payload chunk Payload(Bytes), } @@ -87,7 +87,7 @@ impl Codec { } fn encode_response( - &mut self, mut msg: HttpResponse, buffer: &mut BytesMut, + &mut self, mut msg: Response, buffer: &mut BytesMut, ) -> io::Result<()> { // prepare transfer encoding self.te diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 90946b45..48776226 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -628,7 +628,7 @@ mod tests { // let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); // let readbuf = BytesMut::new(); - // let mut h1 = Dispatcher::new(buf, |req| ok(HttpResponse::Ok().finish())); + // let mut h1 = Dispatcher::new(buf, |req| ok(Response::Ok().finish())); // assert!(h1.poll_io().is_ok()); // assert!(h1.poll_io().is_ok()); // assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 18309402..728a78b9 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -17,8 +17,8 @@ use body::Body; use config::ServiceConfig; use error::DispatchError; use framed::Framed; -use httpresponse::HttpResponse; use request::Request; +use response::Response; use super::codec::{Codec, InMessage, OutMessage}; @@ -77,7 +77,7 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug + Display, { /// Create http/1 dispatcher. @@ -415,7 +415,7 @@ where .insert(Flags::STARTED | Flags::READ_DISCONNECTED); self.state = State::SendResponse(Some(OutMessage::Response( - HttpResponse::RequestTimeout().finish(), + Response::RequestTimeout().finish(), ))); } else { trace!("Keep-alive timeout, close connection"); @@ -452,7 +452,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug + Display, { type Item = (); diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index d1758735..1544b240 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -11,8 +11,8 @@ use http::{StatusCode, Version}; use body::{Binary, Body}; use header::ContentEncoding; use http::Method; -use httpresponse::HttpResponse; use request::Request; +use response::Response; #[derive(Debug)] pub(crate) enum ResponseLength { @@ -41,7 +41,7 @@ impl Default for ResponseEncoder { } impl ResponseEncoder { - pub fn update(&mut self, resp: &mut HttpResponse, head: bool, version: Version) { + pub fn update(&mut self, resp: &mut Response, head: bool, version: Version) { self.head = head; let version = resp.version().unwrap_or_else(|| version); @@ -98,7 +98,7 @@ impl ResponseEncoder { } fn streaming_encoding( - &mut self, version: Version, resp: &mut HttpResponse, + &mut self, version: Version, resp: &mut Response, ) -> TransferEncoding { match resp.chunked() { Some(true) => { diff --git a/src/h1/service.rs b/src/h1/service.rs index 436e77a5..8038c9fb 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -7,8 +7,8 @@ use tokio_io::{AsyncRead, AsyncWrite}; use config::ServiceConfig; use error::DispatchError; -use httpresponse::HttpResponse; use request::Request; +use response::Response; use super::dispatcher::Dispatcher; @@ -36,7 +36,7 @@ where impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService + Clone, + S: NewService + Clone, S::Service: Clone, S::Error: Debug + Display, { @@ -65,7 +65,7 @@ pub struct H1ServiceResponse { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: Clone, S::Error: Debug + Display, { @@ -90,7 +90,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service + Clone, + S: Service + Clone, S::Error: Debug + Display, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { @@ -105,7 +105,7 @@ where impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + Clone, + S: Service + Clone, S::Error: Debug + Display, { type Request = T; diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 41e57d1e..7d42a1cc 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -1,18 +1,18 @@ //! Basic http responses #![allow(non_upper_case_globals)] use http::StatusCode; -use httpresponse::{HttpResponse, HttpResponseBuilder}; +use response::{Response, ResponseBuilder}; macro_rules! STATIC_RESP { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] - pub fn $name() -> HttpResponseBuilder { - HttpResponse::build($status) + pub fn $name() -> ResponseBuilder { + Response::build($status) } }; } -impl HttpResponse { +impl Response { STATIC_RESP!(Ok, StatusCode::OK); STATIC_RESP!(Created, StatusCode::CREATED); STATIC_RESP!(Accepted, StatusCode::ACCEPTED); @@ -74,11 +74,11 @@ impl HttpResponse { mod tests { use body::Body; use http::StatusCode; - use httpresponse::HttpResponse; + use response::Response; #[test] fn test_build() { - let resp = HttpResponse::Ok().body(Body::Empty); + let resp = Response::Ok().body(Body::Empty); assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 531aa1a7..f68f3650 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -112,18 +112,18 @@ pub trait HttpMessage: Sized { /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; /// use actix_web::{ - /// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse, + /// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, Response, /// }; /// use bytes::Bytes; /// use futures::future::Future; /// - /// fn index(mut req: HttpRequest) -> FutureResponse { + /// fn index(mut req: HttpRequest) -> FutureResponse { /// 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(HttpResponse::Ok().into()) + /// Ok(Response::Ok().into()) /// }).responder() /// } /// # fn main() {} @@ -148,15 +148,15 @@ pub trait HttpMessage: Sized { /// # extern crate futures; /// # use futures::Future; /// # use std::collections::HashMap; - /// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse}; + /// use actix_web::{FutureResponse, HttpMessage, HttpRequest, Response}; /// - /// fn index(mut req: HttpRequest) -> FutureResponse { + /// fn index(mut req: HttpRequest) -> FutureResponse { /// Box::new( /// req.urlencoded::>() // <- get UrlEncoded future /// .from_err() /// .and_then(|params| { // <- url encoded parameters /// println!("==== BODY ==== {:?}", params); - /// Ok(HttpResponse::Ok().into()) + /// Ok(Response::Ok().into()) /// }), /// ) /// } @@ -188,12 +188,12 @@ pub trait HttpMessage: Sized { /// name: String, /// } /// - /// fn index(mut req: HttpRequest) -> Box> { + /// fn index(mut req: HttpRequest) -> Box> { /// req.json() // <- get JsonBody future /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value /// println!("==== BODY ==== {:?}", val); - /// Ok(HttpResponse::Ok().into()) + /// Ok(Response::Ok().into()) /// }).responder() /// } /// # fn main() {} diff --git a/src/json.rs b/src/json.rs index 5c64b9bd..a5288489 100644 --- a/src/json.rs +++ b/src/json.rs @@ -123,7 +123,7 @@ where /// # extern crate actix_web; /// # extern crate futures; /// # #[macro_use] extern crate serde_derive; -/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse}; +/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, Response}; /// use futures::future::Future; /// /// #[derive(Deserialize, Debug)] @@ -131,12 +131,12 @@ where /// name: String, /// } /// -/// fn index(mut req: HttpRequest) -> Box> { +/// fn index(mut req: HttpRequest) -> Box> { /// req.json() // <- get JsonBody future /// .from_err() /// .and_then(|val: MyObj| { // <- deserialized value /// println!("==== BODY ==== {:?}", val); -/// Ok(HttpResponse::Ok().into()) +/// Ok(Response::Ok().into()) /// }).responder() /// } /// # fn main() {} diff --git a/src/lib.rs b/src/lib.rs index ae5a9f95..74e7ced7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,8 +41,8 @@ //! represents an HTTP server instance and is used to instantiate and //! configure servers. //! -//! * [HttpRequest](struct.HttpRequest.html) and -//! [HttpResponse](struct.HttpResponse.html): These structs +//! * [Request](struct.Request.html) and +//! [Response](struct.Response.html): These structs //! represent HTTP requests and responses and expose various methods //! for inspecting, creating and otherwise utilizing them. //! @@ -129,10 +129,10 @@ mod extensions; mod header; mod httpcodes; mod httpmessage; -mod httpresponse; mod json; mod payload; mod request; +mod response; mod uri; #[doc(hidden)] @@ -147,9 +147,9 @@ pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; -pub use httpresponse::HttpResponse; pub use json::Json; pub use request::Request; +pub use response::Response; pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; @@ -166,9 +166,9 @@ pub mod dev { pub use body::BodyStream; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use httpresponse::HttpResponseBuilder; pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; + pub use response::ResponseBuilder; } pub mod http { @@ -187,5 +187,5 @@ pub mod http { pub use header::*; } pub use header::ContentEncoding; - pub use httpresponse::ConnectionType; + pub use response::ConnectionType; } diff --git a/src/payload.rs b/src/payload.rs index 2131e3c3..3f51f6ec 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -27,7 +27,7 @@ pub(crate) enum PayloadStatus { /// `.readany()` method. Payload stream is not thread safe. Payload does not /// notify current task when new data is available. /// -/// Payload stream can be used as `HttpResponse` body stream. +/// Payload stream can be used as `Response` body stream. #[derive(Debug)] pub struct Payload { inner: Rc>, diff --git a/src/httpresponse.rs b/src/response.rs similarity index 81% rename from src/httpresponse.rs rename to src/response.rs index 3c034fae..d0136b40 100644 --- a/src/httpresponse.rs +++ b/src/response.rs @@ -31,54 +31,54 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct HttpResponse(Box, &'static HttpResponsePool); +pub struct Response(Box, &'static ResponsePool); -impl HttpResponse { +impl Response { #[inline] - fn get_ref(&self) -> &InnerHttpResponse { + fn get_ref(&self) -> &InnerResponse { self.0.as_ref() } #[inline] - fn get_mut(&mut self) -> &mut InnerHttpResponse { + fn get_mut(&mut self) -> &mut InnerResponse { self.0.as_mut() } /// Create http response builder with specific status. #[inline] - pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponsePool::get(status) + pub fn build(status: StatusCode) -> ResponseBuilder { + ResponsePool::get(status) } /// Create http response builder #[inline] - pub fn build_from>(source: T) -> HttpResponseBuilder { + pub fn build_from>(source: T) -> ResponseBuilder { source.into() } /// Constructs a response #[inline] - pub fn new(status: StatusCode) -> HttpResponse { - HttpResponsePool::with_body(status, Body::Empty) + pub fn new(status: StatusCode) -> Response { + ResponsePool::with_body(status, Body::Empty) } /// Constructs a response with body #[inline] - pub fn with_body>(status: StatusCode, body: B) -> HttpResponse { - HttpResponsePool::with_body(status, body.into()) + pub fn with_body>(status: StatusCode, body: B) -> Response { + ResponsePool::with_body(status, body.into()) } /// Constructs an error response #[inline] - pub fn from_error(error: Error) -> HttpResponse { + pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().error_response(); resp.get_mut().error = Some(error); resp } - /// Convert `HttpResponse` to a `HttpResponseBuilder` + /// Convert `Response` to a `ResponseBuilder` #[inline] - pub fn into_builder(self) -> HttpResponseBuilder { + pub fn into_builder(self) -> ResponseBuilder { // If this response has cookies, load them into a jar let mut jar: Option = None; for c in self.cookies() { @@ -91,7 +91,7 @@ impl HttpResponse { } } - HttpResponseBuilder { + ResponseBuilder { pool: self.1, response: Some(self.0), err: None, @@ -282,23 +282,23 @@ impl HttpResponse { self.1.release(self.0); } - pub(crate) fn into_parts(self) -> HttpResponseParts { + pub(crate) fn into_parts(self) -> ResponseParts { self.0.into_parts() } - pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse { - HttpResponse( - Box::new(InnerHttpResponse::from_parts(parts)), - HttpResponsePool::get_pool(), + pub(crate) fn from_parts(parts: ResponseParts) -> Response { + Response( + Box::new(InnerResponse::from_parts(parts)), + ResponsePool::get_pool(), ) } } -impl fmt::Debug for HttpResponse { +impl fmt::Debug for Response { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = writeln!( f, - "\nHttpResponse {:?} {}{}", + "\nResponse {:?} {}{}", self.get_ref().version, self.get_ref().status, self.get_ref().reason.unwrap_or("") @@ -332,16 +332,16 @@ impl<'a> Iterator for CookieIter<'a> { /// An HTTP response builder /// -/// This type can be used to construct an instance of `HttpResponse` through a +/// This type can be used to construct an instance of `Response` through a /// builder-like pattern. -pub struct HttpResponseBuilder { - pool: &'static HttpResponsePool, - response: Option>, +pub struct ResponseBuilder { + pool: &'static ResponsePool, + response: Option>, err: Option, cookies: Option, } -impl HttpResponseBuilder { +impl ResponseBuilder { /// Set HTTP status code of this response. #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { @@ -366,10 +366,10 @@ impl HttpResponseBuilder { /// /// ```rust,ignore /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; + /// use actix_web::{http, Request, Response, Result}; /// - /// fn index(req: HttpRequest) -> Result { - /// Ok(HttpResponse::Ok() + /// fn index(req: HttpRequest) -> Result { + /// Ok(Response::Ok() /// .set(http::header::IfModifiedSince( /// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?, /// )) @@ -394,10 +394,10 @@ impl HttpResponseBuilder { /// /// ```rust,ignore /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse}; + /// use actix_web::{http, Request, Response}; /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// HttpResponse::Ok() + /// fn index(req: HttpRequest) -> Response { + /// Response::Ok() /// .header("X-TEST", "value") /// .header(http::header::CONTENT_TYPE, "application/json") /// .finish() @@ -516,10 +516,10 @@ impl HttpResponseBuilder { /// /// ```rust,ignore /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; + /// use actix_web::{http, HttpRequest, Response, Result}; /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// HttpResponse::Ok() + /// fn index(req: HttpRequest) -> Response { + /// Response::Ok() /// .cookie( /// http::Cookie::build("name", "value") /// .domain("www.rust-lang.org") @@ -546,10 +546,10 @@ impl HttpResponseBuilder { /// /// ```rust,ignore /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; + /// use actix_web::{http, HttpRequest, Response, Result}; /// - /// fn index(req: &HttpRequest) -> HttpResponse { - /// let mut builder = HttpResponse::Ok(); + /// fn index(req: &HttpRequest) -> Response { + /// let mut builder = Response::Ok(); /// /// if let Some(ref cookie) = req.cookie("name") { /// builder.del_cookie(cookie); @@ -575,7 +575,7 @@ impl HttpResponseBuilder { /// true. pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where - F: FnOnce(&mut HttpResponseBuilder), + F: FnOnce(&mut ResponseBuilder), { if value { f(self); @@ -587,7 +587,7 @@ impl HttpResponseBuilder { /// Some. pub fn if_some(&mut self, value: Option, f: F) -> &mut Self where - F: FnOnce(T, &mut HttpResponseBuilder), + F: FnOnce(T, &mut ResponseBuilder), { if let Some(val) = value { f(val, self); @@ -609,10 +609,10 @@ impl HttpResponseBuilder { self } - /// Set a body and generate `HttpResponse`. + /// Set a body and generate `Response`. /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> HttpResponse { + /// `ResponseBuilder` can not be used after this call. + pub fn body>(&mut self, body: B) -> Response { if let Some(e) = self.err.take() { return Error::from(e).into(); } @@ -626,14 +626,14 @@ impl HttpResponseBuilder { } } response.body = body.into(); - HttpResponse(response, self.pool) + Response(response, self.pool) } #[inline] - /// Set a streaming body and generate `HttpResponse`. + /// Set a streaming body and generate `Response`. /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> HttpResponse + /// `ResponseBuilder` can not be used after this call. + pub fn streaming(&mut self, stream: S) -> Response where S: Stream + 'static, E: Into, @@ -641,17 +641,17 @@ impl HttpResponseBuilder { self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) } - /// Set a json body and generate `HttpResponse` + /// Set a json body and generate `Response` /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> HttpResponse { + /// `ResponseBuilder` can not be used after this call. + pub fn json(&mut self, value: T) -> Response { self.json2(&value) } - /// Set a json body and generate `HttpResponse` + /// Set a json body and generate `Response` /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> HttpResponse { + /// `ResponseBuilder` can not be used after this call. + pub fn json2(&mut self, value: &T) -> Response { match serde_json::to_string(value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) @@ -671,16 +671,16 @@ impl HttpResponseBuilder { } #[inline] - /// Set an empty body and generate `HttpResponse` + /// Set an empty body and generate `Response` /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn finish(&mut self) -> HttpResponse { + /// `ResponseBuilder` can not be used after this call. + pub fn finish(&mut self) -> Response { self.body(Body::Empty) } - /// This method construct new `HttpResponseBuilder` - pub fn take(&mut self) -> HttpResponseBuilder { - HttpResponseBuilder { + /// This method construct new `ResponseBuilder` + pub fn take(&mut self) -> ResponseBuilder { + ResponseBuilder { pool: self.pool, response: self.response.take(), err: self.err.take(), @@ -692,8 +692,8 @@ impl HttpResponseBuilder { #[inline] #[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))] fn parts<'a>( - parts: &'a mut Option>, err: &Option, -) -> Option<&'a mut Box> { + parts: &'a mut Option>, err: &Option, +) -> Option<&'a mut Box> { if err.is_some() { return None; } @@ -701,7 +701,7 @@ fn parts<'a>( } /// Helper converters -impl, E: Into> From> for HttpResponse { +impl, E: Into> From> for Response { fn from(res: Result) -> Self { match res { Ok(val) => val.into(), @@ -710,62 +710,62 @@ impl, E: Into> From> for HttpResponse } } -impl From for HttpResponse { - fn from(mut builder: HttpResponseBuilder) -> Self { +impl From for Response { + fn from(mut builder: ResponseBuilder) -> Self { builder.finish() } } -impl From<&'static str> for HttpResponse { +impl From<&'static str> for Response { fn from(val: &'static str) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("text/plain; charset=utf-8") .body(val) } } -impl From<&'static [u8]> for HttpResponse { +impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("application/octet-stream") .body(val) } } -impl From for HttpResponse { +impl From for Response { fn from(val: String) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("text/plain; charset=utf-8") .body(val) } } -impl<'a> From<&'a String> for HttpResponse { +impl<'a> From<&'a String> for Response { fn from(val: &'a String) -> Self { - HttpResponse::build(StatusCode::OK) + Response::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") .body(val) } } -impl From for HttpResponse { +impl From for Response { fn from(val: Bytes) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("application/octet-stream") .body(val) } } -impl From for HttpResponse { +impl From for Response { fn from(val: BytesMut) -> Self { - HttpResponse::Ok() + Response::Ok() .content_type("application/octet-stream") .body(val) } } #[derive(Debug)] -struct InnerHttpResponse { +struct InnerResponse { version: Option, headers: HeaderMap, status: StatusCode, @@ -779,7 +779,7 @@ struct InnerHttpResponse { error: Option, } -pub(crate) struct HttpResponseParts { +pub(crate) struct ResponseParts { version: Option, headers: HeaderMap, status: StatusCode, @@ -790,10 +790,10 @@ pub(crate) struct HttpResponseParts { error: Option, } -impl InnerHttpResponse { +impl InnerResponse { #[inline] - fn new(status: StatusCode, body: Body) -> InnerHttpResponse { - InnerHttpResponse { + fn new(status: StatusCode, body: Body) -> InnerResponse { + InnerResponse { status, body, version: None, @@ -809,7 +809,7 @@ impl InnerHttpResponse { } /// This is for failure, we can not have Send + Sync on Streaming and Actor response - fn into_parts(mut self) -> HttpResponseParts { + fn into_parts(mut self) -> ResponseParts { let body = match mem::replace(&mut self.body, Body::Empty) { Body::Empty => None, Body::Binary(mut bin) => Some(bin.take()), @@ -819,7 +819,7 @@ impl InnerHttpResponse { } }; - HttpResponseParts { + ResponseParts { body, version: self.version, headers: self.headers, @@ -831,14 +831,14 @@ impl InnerHttpResponse { } } - fn from_parts(parts: HttpResponseParts) -> InnerHttpResponse { + fn from_parts(parts: ResponseParts) -> InnerResponse { let body = if let Some(ref body) = parts.body { Body::Binary(body.clone().into()) } else { Body::Empty }; - InnerHttpResponse { + InnerResponse { body, status: parts.status, version: parts.version, @@ -855,35 +855,35 @@ impl InnerHttpResponse { } /// Internal use only! -pub(crate) struct HttpResponsePool(RefCell>>); +pub(crate) struct ResponsePool(RefCell>>); -thread_local!(static POOL: &'static HttpResponsePool = HttpResponsePool::pool()); +thread_local!(static POOL: &'static ResponsePool = ResponsePool::pool()); -impl HttpResponsePool { - fn pool() -> &'static HttpResponsePool { - let pool = HttpResponsePool(RefCell::new(VecDeque::with_capacity(128))); +impl ResponsePool { + fn pool() -> &'static ResponsePool { + let pool = ResponsePool(RefCell::new(VecDeque::with_capacity(128))); Box::leak(Box::new(pool)) } - pub fn get_pool() -> &'static HttpResponsePool { + pub fn get_pool() -> &'static ResponsePool { POOL.with(|p| *p) } #[inline] pub fn get_builder( - pool: &'static HttpResponsePool, status: StatusCode, - ) -> HttpResponseBuilder { + pool: &'static ResponsePool, status: StatusCode, + ) -> ResponseBuilder { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; - HttpResponseBuilder { + ResponseBuilder { pool, response: Some(msg), err: None, cookies: None, } } else { - let msg = Box::new(InnerHttpResponse::new(status, Body::Empty)); - HttpResponseBuilder { + let msg = Box::new(InnerResponse::new(status, Body::Empty)); + ResponseBuilder { pool, response: Some(msg), err: None, @@ -894,30 +894,30 @@ impl HttpResponsePool { #[inline] pub fn get_response( - pool: &'static HttpResponsePool, status: StatusCode, body: Body, - ) -> HttpResponse { + pool: &'static ResponsePool, status: StatusCode, body: Body, + ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; msg.body = body; - HttpResponse(msg, pool) + Response(msg, pool) } else { - let msg = Box::new(InnerHttpResponse::new(status, body)); - HttpResponse(msg, pool) + let msg = Box::new(InnerResponse::new(status, body)); + Response(msg, pool) } } #[inline] - fn get(status: StatusCode) -> HttpResponseBuilder { - POOL.with(|pool| HttpResponsePool::get_builder(pool, status)) + fn get(status: StatusCode) -> ResponseBuilder { + POOL.with(|pool| ResponsePool::get_builder(pool, status)) } #[inline] - fn with_body(status: StatusCode, body: Body) -> HttpResponse { - POOL.with(|pool| HttpResponsePool::get_response(pool, status, body)) + fn with_body(status: StatusCode, body: Body) -> Response { + POOL.with(|pool| ResponsePool::get_response(pool, status, body)) } #[inline] - fn release(&self, mut inner: Box) { + fn release(&self, mut inner: Box) { let mut p = self.0.borrow_mut(); if p.len() < 128 { inner.headers.clear(); @@ -946,12 +946,12 @@ mod tests { #[test] fn test_debug() { - let resp = HttpResponse::Ok() + let resp = Response::Ok() .header(COOKIE, HeaderValue::from_static("cookie1=value1; ")) .header(COOKIE, HeaderValue::from_static("cookie2=value2; ")) .finish(); let dbg = format!("{:?}", resp); - assert!(dbg.contains("HttpResponse")); + assert!(dbg.contains("Response")); } // #[test] @@ -962,7 +962,7 @@ mod tests { // .finish(); // let cookies = req.cookies().unwrap(); - // let resp = HttpResponse::Ok() + // let resp = Response::Ok() // .cookie( // http::Cookie::build("name", "value") // .domain("www.rust-lang.org") @@ -989,7 +989,7 @@ mod tests { #[test] fn test_update_response_cookies() { - let mut r = HttpResponse::Ok() + let mut r = Response::Ok() .cookie(http::Cookie::new("original", "val100")) .finish(); @@ -1012,7 +1012,7 @@ mod tests { #[test] fn test_basic_builder() { - let resp = HttpResponse::Ok() + let resp = Response::Ok() .header("X-TEST", "value") .version(Version::HTTP_10) .finish(); @@ -1022,19 +1022,19 @@ mod tests { #[test] fn test_upgrade() { - let resp = HttpResponse::build(StatusCode::OK).upgrade().finish(); + let resp = Response::build(StatusCode::OK).upgrade().finish(); assert!(resp.upgrade()) } #[test] fn test_force_close() { - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let resp = Response::build(StatusCode::OK).force_close().finish(); assert!(!resp.keep_alive().unwrap()) } #[test] fn test_content_type() { - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .content_type("text/plain") .body(Body::Empty); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") @@ -1042,18 +1042,18 @@ mod tests { #[test] fn test_content_encoding() { - let resp = HttpResponse::build(StatusCode::OK).finish(); + let resp = Response::build(StatusCode::OK).finish(); assert_eq!(resp.content_encoding(), None); #[cfg(feature = "brotli")] { - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .content_encoding(ContentEncoding::Br) .finish(); assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); } - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .content_encoding(ContentEncoding::Gzip) .finish(); assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); @@ -1061,7 +1061,7 @@ mod tests { #[test] fn test_json() { - let resp = HttpResponse::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); + let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( @@ -1072,7 +1072,7 @@ mod tests { #[test] fn test_json_ct() { - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .header(CONTENT_TYPE, "text/json") .json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); @@ -1085,7 +1085,7 @@ mod tests { #[test] fn test_json2() { - let resp = HttpResponse::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); + let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); assert_eq!( @@ -1096,7 +1096,7 @@ mod tests { #[test] fn test_json2_ct() { - let resp = HttpResponse::build(StatusCode::OK) + let resp = Response::build(StatusCode::OK) .header(CONTENT_TYPE, "text/json") .json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); @@ -1120,7 +1120,7 @@ mod tests { fn test_into_response() { let req = TestRequest::default().finish(); - let resp: HttpResponse = "test".into(); + let resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1129,7 +1129,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from("test")); - let resp: HttpResponse = b"test".as_ref().into(); + let resp: Response = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1138,7 +1138,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); - let resp: HttpResponse = "test".to_owned().into(); + let resp: Response = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1147,7 +1147,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); - let resp: HttpResponse = (&"test".to_owned()).into(); + let resp: Response = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1157,7 +1157,7 @@ mod tests { assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1170,7 +1170,7 @@ mod tests { ); let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1180,7 +1180,7 @@ mod tests { assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); let b = BytesMut::from("test"); - let resp: HttpResponse = b.into(); + let resp: Response = b.into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), @@ -1192,7 +1192,7 @@ mod tests { #[test] fn test_into_builder() { - let mut resp: HttpResponse = "test".into(); + let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); resp.add_cookie(&http::Cookie::new("cookie1", "val100")) diff --git a/src/test.rs b/src/test.rs index 3c48df64..71145cee 100644 --- a/src/test.rs +++ b/src/test.rs @@ -25,12 +25,12 @@ use uri::Url as InnerUrl; /// /// # Examples /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// # use actix_web::*; /// # -/// # fn my_handler(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() +/// # fn my_handler(req: &HttpRequest) -> Response { +/// # Response::Ok().into() /// # } /// # /// # fn main() { @@ -248,20 +248,20 @@ impl Drop for TestServer { // } // } -/// Test `HttpRequest` builder +/// Test `Request` builder /// -/// ```rust +/// ```rust,ignore /// # extern crate http; /// # extern crate actix_web; /// # use http::{header, StatusCode}; /// # use actix_web::*; /// use actix_web::test::TestRequest; /// -/// fn index(req: &HttpRequest) -> HttpResponse { +/// fn index(req: &HttpRequest) -> Response { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// HttpResponse::Ok().into() +/// Response::Ok().into() /// } else { -/// HttpResponse::BadRequest().into() +/// Response::BadRequest().into() /// } /// } /// @@ -403,7 +403,7 @@ impl TestRequest { // /// This method generates `HttpRequest` instance and runs handler // /// with generated request. - // pub fn run>(self, h: &H) -> Result { + // pub fn run>(self, h: &H) -> Result { // let req = self.finish(); // let resp = h.handle(&req); @@ -424,7 +424,7 @@ impl TestRequest { // /// with generated request. // /// // /// This method panics is handler returns actor. - // pub fn run_async(self, h: H) -> Result + // pub fn run_async(self, h: H) -> Result // where // H: Fn(HttpRequest) -> F + 'static, // F: Future + 'static, @@ -467,7 +467,7 @@ impl TestRequest { // } // /// This method generates `HttpRequest` instance and executes handler - // pub fn execute(self, f: F) -> Result + // pub fn execute(self, f: F) -> Result // where // F: FnOnce(&HttpRequest) -> R, // R: Responder + 'static, diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 6bb84c18..61fad543 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -10,9 +10,9 @@ use http::{header, Method, StatusCode}; use body::Binary; use error::{PayloadError, ResponseError}; -use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; use payload::PayloadBuffer; use request::Request; +use response::{ConnectionType, Response, ResponseBuilder}; mod frame; mod mask; @@ -85,26 +85,26 @@ pub enum HandshakeError { } impl ResponseError for HandshakeError { - fn error_response(&self) -> HttpResponse { + fn error_response(&self) -> Response { match *self { - HandshakeError::GetMethodRequired => HttpResponse::MethodNotAllowed() + HandshakeError::GetMethodRequired => Response::MethodNotAllowed() .header(header::ALLOW, "GET") .finish(), - HandshakeError::NoWebsocketUpgrade => HttpResponse::BadRequest() + HandshakeError::NoWebsocketUpgrade => Response::BadRequest() .reason("No WebSocket UPGRADE header found") .finish(), - HandshakeError::NoConnectionUpgrade => HttpResponse::BadRequest() + HandshakeError::NoConnectionUpgrade => Response::BadRequest() .reason("No CONNECTION upgrade") .finish(), - HandshakeError::NoVersionHeader => HttpResponse::BadRequest() + HandshakeError::NoVersionHeader => Response::BadRequest() .reason("Websocket version header is required") .finish(), - HandshakeError::UnsupportedVersion => HttpResponse::BadRequest() + HandshakeError::UnsupportedVersion => Response::BadRequest() .reason("Unsupported version") .finish(), - HandshakeError::BadWebsocketKey => HttpResponse::BadRequest() - .reason("Handshake error") - .finish(), + HandshakeError::BadWebsocketKey => { + Response::BadRequest().reason("Handshake error").finish() + } } } } @@ -126,13 +126,13 @@ pub enum Message { /// Prepare `WebSocket` handshake response. /// -/// This function returns handshake `HttpResponse`, ready to send to peer. +/// This function returns handshake `Response`, ready to send to peer. /// It does not perform any IO. /// // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. -pub fn handshake(req: &Request) -> Result { +pub fn handshake(req: &Request) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); @@ -181,7 +181,7 @@ pub fn handshake(req: &Request) -> Result { proto::hash_key(key.as_ref()) }; - Ok(HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) + Ok(Response::build(StatusCode::SWITCHING_PROTOCOLS) .connection_type(ConnectionType::Upgrade) .header(header::UPGRADE, "websocket") .header(header::TRANSFER_ENCODING, "chunked") @@ -280,20 +280,6 @@ where } } -/// Common writing methods for a websocket. -pub trait WsWriter { - /// Send a text - fn send_text>(&mut self, text: T); - /// Send a binary - fn send_binary>(&mut self, data: B); - /// Send a ping message - fn send_ping(&mut self, message: &str); - /// Send a pong message - fn send_pong(&mut self, message: &str); - /// Close the connection - fn send_close(&mut self, reason: Option); -} - #[cfg(test)] mod tests { use super::*; @@ -399,17 +385,17 @@ mod tests { #[test] fn test_wserror_http_response() { - let resp: HttpResponse = HandshakeError::GetMethodRequired.error_response(); + let resp: Response = HandshakeError::GetMethodRequired.error_response(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: HttpResponse = HandshakeError::NoWebsocketUpgrade.error_response(); + let resp: Response = HandshakeError::NoWebsocketUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::NoConnectionUpgrade.error_response(); + let resp: Response = HandshakeError::NoConnectionUpgrade.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::NoVersionHeader.error_response(); + let resp: Response = HandshakeError::NoVersionHeader.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::UnsupportedVersion.error_response(); + let resp: Response = HandshakeError::UnsupportedVersion.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::BadWebsocketKey.error_response(); + let resp: Response = HandshakeError::BadWebsocketKey.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } } diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs index bb943065..1866f29b 100644 --- a/tests/test_h1v2.rs +++ b/tests/test_h1v2.rs @@ -11,7 +11,7 @@ use actix_net::server::Server; use actix_web::{client, test}; use futures::future; -use actix_http::{h1, Error, HttpResponse, KeepAlive, ServiceConfig}; +use actix_http::{h1, Error, KeepAlive, Response, ServiceConfig}; #[test] fn test_h1_v2() { @@ -29,7 +29,7 @@ fn test_h1_v2() { h1::H1Service::new(settings, |req| { println!("REQ: {:?}", req); - future::ok::<_, Error>(HttpResponse::Ok().finish()) + future::ok::<_, Error>(Response::Ok().finish()) }) }).unwrap() .run(); From 5c0a2066cc901c136062121345684332a2e98ac3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 12:47:22 -0700 Subject: [PATCH 0729/1635] refactor ws to a websocket codec --- src/body.rs | 2 - src/ws/codec.rs | 119 +++++++++++++++ src/ws/frame.rs | 382 ++++++++++++++---------------------------------- src/ws/mod.rs | 133 ++--------------- 4 files changed, 238 insertions(+), 398 deletions(-) create mode 100644 src/ws/codec.rs diff --git a/src/body.rs b/src/body.rs index db06bef2..c10b067a 100644 --- a/src/body.rs +++ b/src/body.rs @@ -17,8 +17,6 @@ pub enum Body { /// Unspecified streaming response. Developer is responsible for setting /// right `Content-Length` or `Transfer-Encoding` headers. Streaming(BodyStream), - // /// Special body type for actor response. - // Actor(Box), } /// Represents various types of binary body. diff --git a/src/ws/codec.rs b/src/ws/codec.rs new file mode 100644 index 00000000..6e2b1209 --- /dev/null +++ b/src/ws/codec.rs @@ -0,0 +1,119 @@ +use bytes::BytesMut; +use tokio_codec::{Decoder, Encoder}; + +use super::frame::Frame; +use super::proto::{CloseReason, OpCode}; +use super::ProtocolError; +use body::Binary; + +/// `WebSocket` Message +#[derive(Debug, PartialEq)] +pub enum Message { + /// Text message + Text(String), + /// Binary message + Binary(Binary), + /// Ping message + Ping(String), + /// Pong message + Pong(String), + /// Close message with optional reason + Close(Option), +} + +/// WebSockets protocol codec +pub struct Codec { + max_size: usize, + server: bool, +} + +impl Codec { + /// Create new websocket frames decoder + pub fn new() -> Codec { + Codec { + max_size: 65_536, + server: true, + } + } + + /// Set max frame size + /// + /// By default max size is set to 64kb + pub fn max_size(mut self, size: usize) -> Self { + self.max_size = size; + self + } + + /// Set decoder to client mode. + /// + /// By default decoder works in server mode. + pub fn client_mode(mut self) -> Self { + self.server = false; + self + } +} + +impl Encoder for Codec { + type Item = Message; + type Error = ProtocolError; + + fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> { + match item { + Message::Text(txt) => { + Frame::write_message(dst, txt, OpCode::Text, true, !self.server) + } + Message::Binary(bin) => { + Frame::write_message(dst, bin, OpCode::Binary, true, !self.server) + } + Message::Ping(txt) => { + Frame::write_message(dst, txt, OpCode::Ping, true, !self.server) + } + Message::Pong(txt) => { + Frame::write_message(dst, txt, OpCode::Pong, true, !self.server) + } + Message::Close(reason) => Frame::write_close(dst, reason, !self.server), + } + Ok(()) + } +} + +impl Decoder for Codec { + type Item = Message; + type Error = ProtocolError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + match Frame::parse(src, self.server, self.max_size) { + Ok(Some((finished, opcode, payload))) => { + // continuation is not supported + if !finished { + return Err(ProtocolError::NoContinuation); + } + + match opcode { + OpCode::Continue => Err(ProtocolError::NoContinuation), + OpCode::Bad => Err(ProtocolError::BadOpCode), + OpCode::Close => { + let close_reason = Frame::parse_close_payload(&payload); + Ok(Some(Message::Close(close_reason))) + } + OpCode::Ping => Ok(Some(Message::Ping( + String::from_utf8_lossy(payload.as_ref()).into(), + ))), + OpCode::Pong => Ok(Some(Message::Pong( + String::from_utf8_lossy(payload.as_ref()).into(), + ))), + OpCode::Binary => Ok(Some(Message::Binary(payload))), + OpCode::Text => { + let tmp = Vec::from(payload.as_ref()); + match String::from_utf8(tmp) { + Ok(s) => Ok(Some(Message::Text(s))), + Err(_) => Err(ProtocolError::BadEncoding), + } + } + } + } + Ok(None) => Ok(None), + Err(e) => Err(e), + } + } +} diff --git a/src/ws/frame.rs b/src/ws/frame.rs index d5fa9827..38bebc28 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,144 +1,29 @@ use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; -use bytes::{BufMut, Bytes, BytesMut}; -use futures::{Async, Poll, Stream}; +use bytes::{BufMut, BytesMut}; use rand; -use std::fmt; use body::Binary; -use error::PayloadError; -use payload::PayloadBuffer; - use ws::mask::apply_mask; use ws::proto::{CloseCode, CloseReason, OpCode}; use ws::ProtocolError; /// A struct representing a `WebSocket` frame. #[derive(Debug)] -pub struct Frame { - finished: bool, - opcode: OpCode, - payload: Binary, -} +pub struct Frame; impl Frame { - /// Destruct frame - pub fn unpack(self) -> (bool, OpCode, Binary) { - (self.finished, self.opcode, self.payload) - } - - /// Create a new Close control frame. - #[inline] - pub fn close(reason: Option, genmask: bool) -> FramedMessage { - let payload = match reason { - None => Vec::new(), - Some(reason) => { - let mut code_bytes = [0; 2]; - NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); - - let mut payload = Vec::from(&code_bytes[..]); - if let Some(description) = reason.description { - payload.extend(description.as_bytes()); - } - payload - } - }; - - Frame::message(payload, OpCode::Close, true, genmask) - } - - #[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))] - fn read_copy_md( - pl: &mut PayloadBuffer, server: bool, max_size: usize, - ) -> Poll)>, ProtocolError> - where - S: Stream, - { - let mut idx = 2; - let buf = match pl.copy(2)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let first = buf[0]; - let second = buf[1]; - let finished = first & 0x80 != 0; - - // check masking - let masked = second & 0x80 != 0; - if !masked && server { - return Err(ProtocolError::UnmaskedFrame); - } else if masked && !server { - return Err(ProtocolError::MaskedFrame); - } - - // Op code - let opcode = OpCode::from(first & 0x0F); - - if let OpCode::Bad = opcode { - return Err(ProtocolError::InvalidOpcode(first & 0x0F)); - } - - let len = second & 0x7F; - let length = if len == 126 { - let buf = match pl.copy(4)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let len = NetworkEndian::read_uint(&buf[idx..], 2) as usize; - idx += 2; - len - } else if len == 127 { - let buf = match pl.copy(10)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let len = NetworkEndian::read_uint(&buf[idx..], 8); - if len > max_size as u64 { - return Err(ProtocolError::Overflow); - } - idx += 8; - len as usize - } else { - len as usize - }; - - // check for max allowed size - if length > max_size { - return Err(ProtocolError::Overflow); - } - - let mask = if server { - let buf = match pl.copy(idx + 4)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - - let mask: &[u8] = &buf[idx..idx + 4]; - let mask_u32 = LittleEndian::read_u32(mask); - idx += 4; - Some(mask_u32) - } else { - None - }; - - Ok(Async::Ready(Some((idx, finished, opcode, length, mask)))) - } - - fn read_chunk_md( - chunk: &[u8], server: bool, max_size: usize, - ) -> Poll<(usize, bool, OpCode, usize, Option), ProtocolError> { - let chunk_len = chunk.len(); + fn parse_metadata( + src: &[u8], server: bool, max_size: usize, + ) -> Result)>, ProtocolError> { + let chunk_len = src.len(); let mut idx = 2; if chunk_len < 2 { - return Ok(Async::NotReady); + return Ok(None); } - let first = chunk[0]; - let second = chunk[1]; + let first = src[0]; + let second = src[1]; let finished = first & 0x80 != 0; // check masking @@ -159,16 +44,16 @@ impl Frame { let len = second & 0x7F; let length = if len == 126 { if chunk_len < 4 { - return Ok(Async::NotReady); + return Ok(None); } - let len = NetworkEndian::read_uint(&chunk[idx..], 2) as usize; + let len = NetworkEndian::read_uint(&src[idx..], 2) as usize; idx += 2; len } else if len == 127 { if chunk_len < 10 { - return Ok(Async::NotReady); + return Ok(None); } - let len = NetworkEndian::read_uint(&chunk[idx..], 8); + let len = NetworkEndian::read_uint(&src[idx..], 8); if len > max_size as u64 { return Err(ProtocolError::Overflow); } @@ -185,10 +70,10 @@ impl Frame { let mask = if server { if chunk_len < idx + 4 { - return Ok(Async::NotReady); + return Ok(None); } - let mask: &[u8] = &chunk[idx..idx + 4]; + let mask: &[u8] = &src[idx..idx + 4]; let mask_u32 = LittleEndian::read_u32(mask); idx += 4; Some(mask_u32) @@ -196,56 +81,34 @@ impl Frame { None }; - Ok(Async::Ready((idx, finished, opcode, length, mask))) + Ok(Some((idx, finished, opcode, length, mask))) } /// Parse the input stream into a frame. - pub fn parse( - pl: &mut PayloadBuffer, server: bool, max_size: usize, - ) -> Poll, ProtocolError> - where - S: Stream, - { - // try to parse ws frame md from one chunk - let result = match pl.get_chunk()? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::Ready(Some(chunk)) => Frame::read_chunk_md(chunk, server, max_size)?, - }; + pub fn parse( + src: &mut BytesMut, server: bool, max_size: usize, + ) -> Result, ProtocolError> { + // try to parse ws frame metadata + let (idx, finished, opcode, length, mask) = + match Frame::parse_metadata(src, server, max_size)? { + None => return Ok(None), + Some(res) => res, + }; - let (idx, finished, opcode, length, mask) = match result { - // we may need to join several chunks - Async::NotReady => match Frame::read_copy_md(pl, server, max_size)? { - Async::Ready(Some(item)) => item, - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Ok(Async::Ready(None)), - }, - Async::Ready(item) => item, - }; - - match pl.can_read(idx + length)? { - Async::Ready(Some(true)) => (), - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::Ready(Some(false)) | Async::NotReady => return Ok(Async::NotReady), + // not enough data + if src.len() < idx + length { + return Ok(None); } // remove prefix - pl.drop_bytes(idx); + src.split_to(idx); // no need for body if length == 0 { - return Ok(Async::Ready(Some(Frame { - finished, - opcode, - payload: Binary::from(""), - }))); + return Ok(Some((finished, opcode, Binary::from("")))); } - let data = match pl.read_exact(length)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => panic!(), - }; + let mut data = src.split_to(length); // control frames must have length <= 125 match opcode { @@ -254,26 +117,17 @@ impl Frame { } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Async::Ready(Some(Frame::default()))); + return Ok(Some((true, OpCode::Close, Binary::from("")))); } _ => (), } // unmask - let data = if let Some(mask) = mask { - let mut buf = BytesMut::new(); - buf.extend_from_slice(&data); - apply_mask(&mut buf, mask); - buf.freeze() - } else { - data - }; + if let Some(mask) = mask { + apply_mask(&mut data, mask); + } - Ok(Async::Ready(Some(Frame { - finished, - opcode, - payload: data.into(), - }))) + Ok(Some((finished, opcode, data.into()))) } /// Parse the payload of a close frame. @@ -293,120 +147,101 @@ impl Frame { } /// Generate binary representation - pub fn message>( - data: B, code: OpCode, finished: bool, genmask: bool, - ) -> FramedMessage { - let payload = data.into(); - let one: u8 = if finished { - 0x80 | Into::::into(code) + pub fn write_message>( + dst: &mut BytesMut, pl: B, op: OpCode, fin: bool, mask: bool, + ) { + let payload = pl.into(); + let one: u8 = if fin { + 0x80 | Into::::into(op) } else { - code.into() + op.into() }; let payload_len = payload.len(); - let (two, p_len) = if genmask { + let (two, p_len) = if mask { (0x80, payload_len + 4) } else { (0, payload_len) }; - let mut buf = if payload_len < 126 { - let mut buf = BytesMut::with_capacity(p_len + 2); - buf.put_slice(&[one, two | payload_len as u8]); - buf + if payload_len < 126 { + dst.put_slice(&[one, two | payload_len as u8]); } else if payload_len <= 65_535 { - let mut buf = BytesMut::with_capacity(p_len + 4); - buf.put_slice(&[one, two | 126]); - buf.put_u16_be(payload_len as u16); - buf + dst.reserve(p_len + 4); + dst.put_slice(&[one, two | 126]); + dst.put_u16_be(payload_len as u16); } else { - let mut buf = BytesMut::with_capacity(p_len + 10); - buf.put_slice(&[one, two | 127]); - buf.put_u64_be(payload_len as u64); - buf + dst.reserve(p_len + 10); + dst.put_slice(&[one, two | 127]); + dst.put_u64_be(payload_len as u64); }; - let binary = if genmask { + if mask { let mask = rand::random::(); - buf.put_u32_le(mask); - buf.extend_from_slice(payload.as_ref()); - let pos = buf.len() - payload_len; - apply_mask(&mut buf[pos..], mask); - buf.into() + dst.put_u32_le(mask); + dst.extend_from_slice(payload.as_ref()); + let pos = dst.len() - payload_len; + apply_mask(&mut dst[pos..], mask); } else { - buf.put_slice(payload.as_ref()); - buf.into() - }; - - FramedMessage(binary) - } -} - -impl Default for Frame { - fn default() -> Frame { - Frame { - finished: true, - opcode: OpCode::Close, - payload: Binary::from(&b""[..]), + dst.put_slice(payload.as_ref()); } } -} -impl fmt::Display for Frame { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - " - - final: {} - opcode: {} - payload length: {} - payload: 0x{} -", - self.finished, - self.opcode, - self.payload.len(), - self.payload - .as_ref() - .iter() - .map(|byte| format!("{:x}", byte)) - .collect::() - ) + /// Create a new Close control frame. + #[inline] + pub fn write_close(dst: &mut BytesMut, reason: Option, mask: bool) { + let payload = match reason { + None => Vec::new(), + Some(reason) => { + let mut code_bytes = [0; 2]; + NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); + + let mut payload = Vec::from(&code_bytes[..]); + if let Some(description) = reason.description { + payload.extend(description.as_bytes()); + } + payload + } + }; + + Frame::write_message(dst, payload, OpCode::Close, true, mask) } } -/// `WebSocket` message with framing. -#[derive(Debug)] -pub struct FramedMessage(pub(crate) Binary); - #[cfg(test)] mod tests { use super::*; - use futures::stream::once; - fn is_none(frm: &Poll, ProtocolError>) -> bool { + struct F { + finished: bool, + opcode: OpCode, + payload: Binary, + } + + fn is_none(frm: &Result, ProtocolError>) -> bool { match *frm { - Ok(Async::Ready(None)) => true, + Ok(None) => true, _ => false, } } - fn extract(frm: Poll, ProtocolError>) -> Frame { + fn extract(frm: Result, ProtocolError>) -> F { match frm { - Ok(Async::Ready(Some(frame))) => frame, + Ok(Some((finished, opcode, payload))) => F { + finished, + opcode, + payload, + }, _ => unreachable!("error"), } } #[test] fn test_parse() { - let mut buf = PayloadBuffer::new(once(Ok(BytesMut::from( - &[0b0000_0001u8, 0b0000_0001u8][..], - ).freeze()))); + let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(b"1"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -416,9 +251,7 @@ mod tests { #[test] fn test_parse_length0() { - let buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - + let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); @@ -427,14 +260,12 @@ mod tests { #[test] fn test_parse_length2() { - let buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); + let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); buf.extend(&[0u8, 4u8][..]); buf.extend(b"1234"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -444,14 +275,12 @@ mod tests { #[test] fn test_parse_length4() { - let buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); + let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); assert!(is_none(&Frame::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(b"1234"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); let frame = extract(Frame::parse(&mut buf, false, 1024)); assert!(!frame.finished); @@ -464,7 +293,6 @@ mod tests { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]); buf.extend(b"0001"); buf.extend(b"1"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, false, 1024).is_err()); @@ -478,7 +306,6 @@ mod tests { fn test_parse_frame_no_mask() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(&[1u8]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, true, 1024).is_err()); @@ -492,7 +319,6 @@ mod tests { fn test_parse_frame_max_size() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); buf.extend(&[1u8, 1u8]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); assert!(Frame::parse(&mut buf, true, 1).is_err()); @@ -504,35 +330,39 @@ mod tests { #[test] fn test_ping_frame() { - let frame = Frame::message(Vec::from("data"), OpCode::Ping, true, false); + let mut buf = BytesMut::new(); + Frame::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false); let mut v = vec![137u8, 4u8]; v.extend(b"data"); - assert_eq!(frame.0, v.into()); + assert_eq!(&buf[..], &v[..]); } #[test] fn test_pong_frame() { - let frame = Frame::message(Vec::from("data"), OpCode::Pong, true, false); + let mut buf = BytesMut::new(); + Frame::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false); let mut v = vec![138u8, 4u8]; v.extend(b"data"); - assert_eq!(frame.0, v.into()); + assert_eq!(&buf[..], &v[..]); } #[test] fn test_close_frame() { + let mut buf = BytesMut::new(); let reason = (CloseCode::Normal, "data"); - let frame = Frame::close(Some(reason.into()), false); + Frame::write_close(&mut buf, Some(reason.into()), false); let mut v = vec![136u8, 6u8, 3u8, 232u8]; v.extend(b"data"); - assert_eq!(frame.0, v.into()); + assert_eq!(&buf[..], &v[..]); } #[test] fn test_empty_close_frame() { - let frame = Frame::close(None, false); - assert_eq!(frame.0, vec![0x88, 0x00].into()); + let mut buf = BytesMut::new(); + Frame::write_close(&mut buf, None, false); + assert_eq!(&buf[..], &vec![0x88, 0x00][..]); } } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 61fad543..e8bf3870 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -1,24 +1,23 @@ -//! `WebSocket` support. +//! WebSocket protocol support. //! //! To setup a `WebSocket`, first do web socket handshake then on success //! convert `Payload` into a `WsStream` stream and then use `WsWriter` to //! communicate with the peer. //! ``` -use bytes::Bytes; -use futures::{Async, Poll, Stream}; -use http::{header, Method, StatusCode}; +use std::io; -use body::Binary; -use error::{PayloadError, ResponseError}; -use payload::PayloadBuffer; +use error::ResponseError; +use http::{header, Method, StatusCode}; use request::Request; use response::{ConnectionType, Response, ResponseBuilder}; +mod codec; mod frame; mod mask; mod proto; -pub use self::frame::{Frame, FramedMessage}; +pub use self::codec::Message; +pub use self::frame::Frame; pub use self::proto::{CloseCode, CloseReason, OpCode}; /// Websocket protocol errors @@ -48,16 +47,16 @@ pub enum ProtocolError { /// Bad utf-8 encoding #[fail(display = "Bad utf-8 encoding.")] BadEncoding, - /// Payload error - #[fail(display = "Payload error: {}", _0)] - Payload(#[cause] PayloadError), + /// Io error + #[fail(display = "io error: {}", _0)] + Io(#[cause] io::Error), } impl ResponseError for ProtocolError {} -impl From for ProtocolError { - fn from(err: PayloadError) -> ProtocolError { - ProtocolError::Payload(err) +impl From for ProtocolError { + fn from(err: io::Error) -> ProtocolError { + ProtocolError::Io(err) } } @@ -109,21 +108,6 @@ impl ResponseError for HandshakeError { } } -/// `WebSocket` Message -#[derive(Debug, PartialEq)] -pub enum Message { - /// Text message - Text(String), - /// Binary message - Binary(Binary), - /// Ping message - Ping(String), - /// Pong message - Pong(String), - /// Close message with optional reason - Close(Option), -} - /// Prepare `WebSocket` handshake response. /// /// This function returns handshake `Response`, ready to send to peer. @@ -189,97 +173,6 @@ pub fn handshake(req: &Request) -> Result { .take()) } -/// Maps `Payload` stream into stream of `ws::Message` items -pub struct WsStream { - rx: PayloadBuffer, - closed: bool, - max_size: usize, -} - -impl WsStream -where - S: Stream, -{ - /// Create new websocket frames stream - pub fn new(stream: S) -> WsStream { - WsStream { - rx: PayloadBuffer::new(stream), - closed: false, - max_size: 65_536, - } - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } -} - -impl Stream for WsStream -where - S: Stream, -{ - type Item = Message; - type Error = ProtocolError; - - fn poll(&mut self) -> Poll, Self::Error> { - if self.closed { - return Ok(Async::Ready(None)); - } - - match Frame::parse(&mut self.rx, true, self.max_size) { - Ok(Async::Ready(Some(frame))) => { - let (finished, opcode, payload) = frame.unpack(); - - // continuation is not supported - if !finished { - self.closed = true; - return Err(ProtocolError::NoContinuation); - } - - match opcode { - OpCode::Continue => Err(ProtocolError::NoContinuation), - OpCode::Bad => { - self.closed = true; - Err(ProtocolError::BadOpCode) - } - OpCode::Close => { - self.closed = true; - let close_reason = Frame::parse_close_payload(&payload); - Ok(Async::Ready(Some(Message::Close(close_reason)))) - } - OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Pong => Ok(Async::Ready(Some(Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => { - self.closed = true; - Err(ProtocolError::BadEncoding) - } - } - } - } - } - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { - self.closed = true; - Err(e) - } - } - } -} - #[cfg(test)] mod tests { use super::*; From 7e135b798b1f20368b62a9d2f7f03dbbc361bffe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 14:30:40 -0700 Subject: [PATCH 0730/1635] add websocket transport and test --- src/framed/framed.rs | 283 ------------------------------------- src/framed/framed_read.rs | 216 ---------------------------- src/framed/framed_write.rs | 243 ------------------------------- src/framed/mod.rs | 32 ----- src/h1/dispatcher.rs | 4 +- src/lib.rs | 3 - src/ws/mod.rs | 4 +- src/ws/transport.rs | 50 +++++++ tests/test_ws.rs | 111 +++++++++++++++ 9 files changed, 165 insertions(+), 781 deletions(-) delete mode 100644 src/framed/framed.rs delete mode 100644 src/framed/framed_read.rs delete mode 100644 src/framed/framed_write.rs delete mode 100644 src/framed/mod.rs create mode 100644 src/ws/transport.rs create mode 100644 tests/test_ws.rs diff --git a/src/framed/framed.rs b/src/framed/framed.rs deleted file mode 100644 index f6295d98..00000000 --- a/src/framed/framed.rs +++ /dev/null @@ -1,283 +0,0 @@ -#![allow(deprecated)] - -use std::fmt; -use std::io::{self, Read, Write}; - -use bytes::BytesMut; -use futures::{Poll, Sink, StartSend, Stream}; -use tokio_codec::{Decoder, Encoder}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use super::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2}; -use super::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2}; - -/// A unified `Stream` and `Sink` interface to an underlying I/O object, using -/// the `Encoder` and `Decoder` traits to encode and decode frames. -/// -/// You can create a `Framed` instance by using the `AsyncRead::framed` adapter. -pub struct Framed { - inner: FramedRead2>>, -} - -pub struct Fuse(pub T, pub U); - -impl Framed -where - T: AsyncRead + AsyncWrite, - U: Decoder + Encoder, -{ - /// Provides a `Stream` and `Sink` interface for reading and writing to this - /// `Io` object, using `Decode` and `Encode` to read and write the raw data. - /// - /// Raw I/O objects work with byte sequences, but higher-level code usually - /// wants to batch these into meaningful chunks, called "frames". This - /// method layers framing on top of an I/O object, by using the `Codec` - /// traits to handle encoding and decoding of messages frames. Note that - /// the incoming and outgoing frame types may be distinct. - /// - /// This function returns a *single* object that is both `Stream` and - /// `Sink`; grouping this into a single object is often useful for layering - /// things like gzip or TLS, which require both read and write access to the - /// underlying object. - /// - /// If you want to work more directly with the streams and sink, consider - /// calling `split` on the `Framed` returned by this method, which will - /// break them into separate objects, allowing them to interact more easily. - pub fn new(inner: T, codec: U) -> Framed { - Framed { - inner: framed_read2(framed_write2(Fuse(inner, codec))), - } - } -} - -impl Framed { - /// Provides a `Stream` and `Sink` interface for reading and writing to this - /// `Io` object, using `Decode` and `Encode` to read and write the raw data. - /// - /// Raw I/O objects work with byte sequences, but higher-level code usually - /// wants to batch these into meaningful chunks, called "frames". This - /// method layers framing on top of an I/O object, by using the `Codec` - /// traits to handle encoding and decoding of messages frames. Note that - /// the incoming and outgoing frame types may be distinct. - /// - /// This function returns a *single* object that is both `Stream` and - /// `Sink`; grouping this into a single object is often useful for layering - /// things like gzip or TLS, which require both read and write access to the - /// underlying object. - /// - /// This objects takes a stream and a readbuffer and a writebuffer. These field - /// can be obtained from an existing `Framed` with the `into_parts` method. - /// - /// If you want to work more directly with the streams and sink, consider - /// calling `split` on the `Framed` returned by this method, which will - /// break them into separate objects, allowing them to interact more easily. - pub fn from_parts(parts: FramedParts) -> Framed { - Framed { - inner: framed_read2_with_buffer( - framed_write2_with_buffer(Fuse(parts.io, parts.codec), parts.write_buf), - parts.read_buf, - ), - } - } - - /// Returns a reference to the underlying codec. - pub fn get_codec(&self) -> &U { - &self.inner.get_ref().get_ref().1 - } - - /// Returns a mutable reference to the underlying codec. - pub fn get_codec_mut(&mut self) -> &mut U { - &mut self.inner.get_mut().get_mut().1 - } - - /// Returns a reference to the underlying I/O stream wrapped by - /// `Frame`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_ref(&self) -> &T { - &self.inner.get_ref().get_ref().0 - } - - /// Returns a mutable reference to the underlying I/O stream wrapped by - /// `Frame`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner.get_mut().get_mut().0 - } - - /// Consumes the `Frame`, returning its underlying I/O stream. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn into_inner(self) -> T { - self.inner.into_inner().into_inner().0 - } - - /// Consumes the `Frame`, returning its underlying I/O stream, the buffer - /// with unprocessed data, and the codec. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn into_parts(self) -> FramedParts { - let (inner, read_buf) = self.inner.into_parts(); - let (inner, write_buf) = inner.into_parts(); - - FramedParts { - io: inner.0, - codec: inner.1, - read_buf: read_buf, - write_buf: write_buf, - _priv: (), - } - } -} - -impl Stream for Framed -where - T: AsyncRead, - U: Decoder, -{ - type Item = U::Item; - type Error = U::Error; - - fn poll(&mut self) -> Poll, Self::Error> { - self.inner.poll() - } -} - -impl Sink for Framed -where - T: AsyncWrite, - U: Encoder, - U::Error: From, -{ - type SinkItem = U::Item; - type SinkError = U::Error; - - fn start_send( - &mut self, item: Self::SinkItem, - ) -> StartSend { - self.inner.get_mut().start_send(item) - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - self.inner.get_mut().poll_complete() - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - self.inner.get_mut().close() - } -} - -impl fmt::Debug for Framed -where - T: fmt::Debug, - U: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Framed") - .field("io", &self.inner.get_ref().get_ref().0) - .field("codec", &self.inner.get_ref().get_ref().1) - .finish() - } -} - -// ===== impl Fuse ===== - -impl Read for Fuse { - fn read(&mut self, dst: &mut [u8]) -> io::Result { - self.0.read(dst) - } -} - -impl AsyncRead for Fuse { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.0.prepare_uninitialized_buffer(buf) - } -} - -impl Write for Fuse { - fn write(&mut self, src: &[u8]) -> io::Result { - self.0.write(src) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } -} - -impl AsyncWrite for Fuse { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.0.shutdown() - } -} - -impl Decoder for Fuse { - type Item = U::Item; - type Error = U::Error; - - fn decode( - &mut self, buffer: &mut BytesMut, - ) -> Result, Self::Error> { - self.1.decode(buffer) - } - - fn decode_eof( - &mut self, buffer: &mut BytesMut, - ) -> Result, Self::Error> { - self.1.decode_eof(buffer) - } -} - -impl Encoder for Fuse { - type Item = U::Item; - type Error = U::Error; - - fn encode( - &mut self, item: Self::Item, dst: &mut BytesMut, - ) -> Result<(), Self::Error> { - self.1.encode(item, dst) - } -} - -/// `FramedParts` contains an export of the data of a Framed transport. -/// It can be used to construct a new `Framed` with a different codec. -/// It contains all current buffers and the inner transport. -#[derive(Debug)] -pub struct FramedParts { - /// The inner transport used to read bytes to and write bytes to - pub io: T, - - /// The codec - pub codec: U, - - /// The buffer with read but unprocessed data. - pub read_buf: BytesMut, - - /// A buffer with unprocessed data which are not written yet. - pub write_buf: BytesMut, - - /// This private field allows us to add additional fields in the future in a - /// backwards compatible way. - _priv: (), -} - -impl FramedParts { - /// Create a new, default, `FramedParts` - pub fn new(io: T, codec: U) -> FramedParts { - FramedParts { - io, - codec, - read_buf: BytesMut::new(), - write_buf: BytesMut::new(), - _priv: (), - } - } -} diff --git a/src/framed/framed_read.rs b/src/framed/framed_read.rs deleted file mode 100644 index 065e2920..00000000 --- a/src/framed/framed_read.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::fmt; - -use bytes::BytesMut; -use futures::{Async, Poll, Sink, StartSend, Stream}; -use tokio_codec::Decoder; -use tokio_io::AsyncRead; - -use super::framed::Fuse; - -/// A `Stream` of messages decoded from an `AsyncRead`. -pub struct FramedRead { - inner: FramedRead2>, -} - -pub struct FramedRead2 { - inner: T, - eof: bool, - is_readable: bool, - buffer: BytesMut, -} - -const INITIAL_CAPACITY: usize = 8 * 1024; - -// ===== impl FramedRead ===== - -impl FramedRead -where - T: AsyncRead, - D: Decoder, -{ - /// Creates a new `FramedRead` with the given `decoder`. - pub fn new(inner: T, decoder: D) -> FramedRead { - FramedRead { - inner: framed_read2(Fuse(inner, decoder)), - } - } -} - -impl FramedRead { - /// Returns a reference to the underlying I/O stream wrapped by - /// `FramedRead`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_ref(&self) -> &T { - &self.inner.inner.0 - } - - /// Returns a mutable reference to the underlying I/O stream wrapped by - /// `FramedRead`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner.inner.0 - } - - /// Consumes the `FramedRead`, returning its underlying I/O stream. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn into_inner(self) -> T { - self.inner.inner.0 - } - - /// Returns a reference to the underlying decoder. - pub fn decoder(&self) -> &D { - &self.inner.inner.1 - } - - /// Returns a mutable reference to the underlying decoder. - pub fn decoder_mut(&mut self) -> &mut D { - &mut self.inner.inner.1 - } -} - -impl Stream for FramedRead -where - T: AsyncRead, - D: Decoder, -{ - type Item = D::Item; - type Error = D::Error; - - fn poll(&mut self) -> Poll, Self::Error> { - self.inner.poll() - } -} - -impl Sink for FramedRead -where - T: Sink, -{ - type SinkItem = T::SinkItem; - type SinkError = T::SinkError; - - fn start_send( - &mut self, item: Self::SinkItem, - ) -> StartSend { - self.inner.inner.0.start_send(item) - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - self.inner.inner.0.poll_complete() - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - self.inner.inner.0.close() - } -} - -impl fmt::Debug for FramedRead -where - T: fmt::Debug, - D: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("FramedRead") - .field("inner", &self.inner.inner.0) - .field("decoder", &self.inner.inner.1) - .field("eof", &self.inner.eof) - .field("is_readable", &self.inner.is_readable) - .field("buffer", &self.inner.buffer) - .finish() - } -} - -// ===== impl FramedRead2 ===== - -pub fn framed_read2(inner: T) -> FramedRead2 { - FramedRead2 { - inner: inner, - eof: false, - is_readable: false, - buffer: BytesMut::with_capacity(INITIAL_CAPACITY), - } -} - -pub fn framed_read2_with_buffer(inner: T, mut buf: BytesMut) -> FramedRead2 { - if buf.capacity() < INITIAL_CAPACITY { - let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity(); - buf.reserve(bytes_to_reserve); - } - FramedRead2 { - inner: inner, - eof: false, - is_readable: buf.len() > 0, - buffer: buf, - } -} - -impl FramedRead2 { - pub fn get_ref(&self) -> &T { - &self.inner - } - - pub fn into_inner(self) -> T { - self.inner - } - - pub fn into_parts(self) -> (T, BytesMut) { - (self.inner, self.buffer) - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Stream for FramedRead2 -where - T: AsyncRead + Decoder, -{ - type Item = T::Item; - type Error = T::Error; - - fn poll(&mut self) -> Poll, Self::Error> { - loop { - // Repeatedly call `decode` or `decode_eof` as long as it is - // "readable". Readable is defined as not having returned `None`. If - // the upstream has returned EOF, and the decoder is no longer - // readable, it can be assumed that the decoder will never become - // readable again, at which point the stream is terminated. - if self.is_readable { - if self.eof { - let frame = try!(self.inner.decode_eof(&mut self.buffer)); - return Ok(Async::Ready(frame)); - } - - trace!("attempting to decode a frame"); - - if let Some(frame) = try!(self.inner.decode(&mut self.buffer)) { - trace!("frame decoded from buffer"); - return Ok(Async::Ready(Some(frame))); - } - - self.is_readable = false; - } - - assert!(!self.eof); - - // Otherwise, try to read more data and try again. Make sure we've - // got room for at least one byte to read to ensure that we don't - // get a spurious 0 that looks like EOF - self.buffer.reserve(1); - if 0 == try_ready!(self.inner.read_buf(&mut self.buffer)) { - self.eof = true; - } - - self.is_readable = true; - } - } -} diff --git a/src/framed/framed_write.rs b/src/framed/framed_write.rs deleted file mode 100644 index 310c7630..00000000 --- a/src/framed/framed_write.rs +++ /dev/null @@ -1,243 +0,0 @@ -use std::fmt; -use std::io::{self, Read}; - -use bytes::BytesMut; -use futures::{Async, AsyncSink, Poll, Sink, StartSend, Stream}; -use tokio_codec::{Decoder, Encoder}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use super::framed::Fuse; - -/// A `Sink` of frames encoded to an `AsyncWrite`. -pub struct FramedWrite { - inner: FramedWrite2>, -} - -pub struct FramedWrite2 { - inner: T, - buffer: BytesMut, -} - -const INITIAL_CAPACITY: usize = 8 * 1024; -const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY; - -impl FramedWrite -where - T: AsyncWrite, - E: Encoder, -{ - /// Creates a new `FramedWrite` with the given `encoder`. - pub fn new(inner: T, encoder: E) -> FramedWrite { - FramedWrite { - inner: framed_write2(Fuse(inner, encoder)), - } - } -} - -impl FramedWrite { - /// Returns a reference to the underlying I/O stream wrapped by - /// `FramedWrite`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_ref(&self) -> &T { - &self.inner.inner.0 - } - - /// Returns a mutable reference to the underlying I/O stream wrapped by - /// `FramedWrite`. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner.inner.0 - } - - /// Consumes the `FramedWrite`, returning its underlying I/O stream. - /// - /// Note that care should be taken to not tamper with the underlying stream - /// of data coming in as it may corrupt the stream of frames otherwise - /// being worked with. - pub fn into_inner(self) -> T { - self.inner.inner.0 - } - - /// Returns a reference to the underlying decoder. - pub fn encoder(&self) -> &E { - &self.inner.inner.1 - } - - /// Returns a mutable reference to the underlying decoder. - pub fn encoder_mut(&mut self) -> &mut E { - &mut self.inner.inner.1 - } -} - -impl Sink for FramedWrite -where - T: AsyncWrite, - E: Encoder, -{ - type SinkItem = E::Item; - type SinkError = E::Error; - - fn start_send(&mut self, item: E::Item) -> StartSend { - self.inner.start_send(item) - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - self.inner.poll_complete() - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - Ok(try!(self.inner.close())) - } -} - -impl Stream for FramedWrite -where - T: Stream, -{ - type Item = T::Item; - type Error = T::Error; - - fn poll(&mut self) -> Poll, Self::Error> { - self.inner.inner.0.poll() - } -} - -impl fmt::Debug for FramedWrite -where - T: fmt::Debug, - U: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("FramedWrite") - .field("inner", &self.inner.get_ref().0) - .field("encoder", &self.inner.get_ref().1) - .field("buffer", &self.inner.buffer) - .finish() - } -} - -// ===== impl FramedWrite2 ===== - -pub fn framed_write2(inner: T) -> FramedWrite2 { - FramedWrite2 { - inner: inner, - buffer: BytesMut::with_capacity(INITIAL_CAPACITY), - } -} - -pub fn framed_write2_with_buffer(inner: T, mut buf: BytesMut) -> FramedWrite2 { - if buf.capacity() < INITIAL_CAPACITY { - let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity(); - buf.reserve(bytes_to_reserve); - } - FramedWrite2 { - inner: inner, - buffer: buf, - } -} - -impl FramedWrite2 { - pub fn get_ref(&self) -> &T { - &self.inner - } - - pub fn into_inner(self) -> T { - self.inner - } - - pub fn into_parts(self) -> (T, BytesMut) { - (self.inner, self.buffer) - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Sink for FramedWrite2 -where - T: AsyncWrite + Encoder, -{ - type SinkItem = T::Item; - type SinkError = T::Error; - - fn start_send(&mut self, item: T::Item) -> StartSend { - // If the buffer is already over 8KiB, then attempt to flush it. If after flushing it's - // *still* over 8KiB, then apply backpressure (reject the send). - if self.buffer.len() >= BACKPRESSURE_BOUNDARY { - try!(self.poll_complete()); - - if self.buffer.len() >= BACKPRESSURE_BOUNDARY { - return Ok(AsyncSink::NotReady(item)); - } - } - - try!(self.inner.encode(item, &mut self.buffer)); - - Ok(AsyncSink::Ready) - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - trace!("flushing framed transport"); - - while !self.buffer.is_empty() { - trace!("writing; remaining={}", self.buffer.len()); - - let n = try_ready!(self.inner.poll_write(&self.buffer)); - - if n == 0 { - return Err(io::Error::new( - io::ErrorKind::WriteZero, - "failed to \ - write frame to transport", - ).into()); - } - - // TODO: Add a way to `bytes` to do this w/o returning the drained - // data. - let _ = self.buffer.split_to(n); - } - - // Try flushing the underlying IO - try_ready!(self.inner.poll_flush()); - - trace!("framed transport flushed"); - return Ok(Async::Ready(())); - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - try_ready!(self.poll_complete()); - Ok(try!(self.inner.shutdown())) - } -} - -impl Decoder for FramedWrite2 { - type Item = T::Item; - type Error = T::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, T::Error> { - self.inner.decode(src) - } - - fn decode_eof(&mut self, src: &mut BytesMut) -> Result, T::Error> { - self.inner.decode_eof(src) - } -} - -impl Read for FramedWrite2 { - fn read(&mut self, dst: &mut [u8]) -> io::Result { - self.inner.read(dst) - } -} - -impl AsyncRead for FramedWrite2 { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.inner.prepare_uninitialized_buffer(buf) - } -} diff --git a/src/framed/mod.rs b/src/framed/mod.rs deleted file mode 100644 index cb0308fa..00000000 --- a/src/framed/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Utilities for encoding and decoding frames. -//! -//! Contains adapters to go from streams of bytes, [`AsyncRead`] and -//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`]. -//! Framed streams are also known as [transports]. -//! -//! [`AsyncRead`]: # -//! [`AsyncWrite`]: # -//! [`Sink`]: # -//! [`Stream`]: # -//! [transports]: # - -#![deny(missing_docs, missing_debug_implementations, warnings)] -#![doc(hidden, html_root_url = "https://docs.rs/tokio-codec/0.1.0")] - -// _tokio_codec are the items that belong in the `tokio_codec` crate. However, because we need to -// maintain backward compatibility until the next major breaking change, they are defined here. -// When the next breaking change comes, they should be moved to the `tokio_codec` crate and become -// independent. -// -// The primary reason we can't move these to `tokio-codec` now is because, again for backward -// compatibility reasons, we need to keep `Decoder` and `Encoder` in tokio_io::codec. And `Decoder` -// and `Encoder` needs to reference `Framed`. So they all still need to still be in the same -// module. - -mod framed; -mod framed_read; -mod framed_write; - -pub use self::framed::{Framed, FramedParts}; -pub use self::framed_read::FramedRead; -pub use self::framed_write::FramedWrite; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 728a78b9..f2001302 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,12 +1,11 @@ -// #![allow(unused_imports, unused_variables, dead_code)] use std::collections::VecDeque; use std::fmt::{Debug, Display}; use std::time::Instant; +use actix_net::codec::Framed; use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; -// use tokio_current_thread::spawn; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; @@ -16,7 +15,6 @@ use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; use config::ServiceConfig; use error::DispatchError; -use framed::Framed; use request::Request; use response::Response; diff --git a/src/lib.rs b/src/lib.rs index 74e7ced7..9acdf3cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,9 +135,6 @@ mod request; mod response; mod uri; -#[doc(hidden)] -pub mod framed; - pub mod error; pub mod h1; pub(crate) mod helpers; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index e8bf3870..bd657d94 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -15,10 +15,12 @@ mod codec; mod frame; mod mask; mod proto; +mod transport; -pub use self::codec::Message; +pub use self::codec::{Codec, Message}; pub use self::frame::Frame; pub use self::proto::{CloseCode, CloseReason, OpCode}; +pub use self::transport::Transport; /// Websocket protocol errors #[derive(Fail, Debug)] diff --git a/src/ws/transport.rs b/src/ws/transport.rs new file mode 100644 index 00000000..aabeb5d5 --- /dev/null +++ b/src/ws/transport.rs @@ -0,0 +1,50 @@ +use actix_net::codec::Framed; +use actix_net::framed::{FramedTransport, FramedTransportError}; +use actix_net::service::{IntoService, Service}; +use futures::{Future, Poll}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::{Codec, Message}; + +pub struct Transport +where + S: Service, + T: AsyncRead + AsyncWrite, +{ + inner: FramedTransport, +} + +impl Transport +where + T: AsyncRead + AsyncWrite, + S: Service, + S::Future: 'static, + S::Error: 'static, +{ + pub fn new>(io: T, service: F) -> Self { + Transport { + inner: FramedTransport::new(Framed::new(io, Codec::new()), service), + } + } + + pub fn with>(framed: Framed, service: F) -> Self { + Transport { + inner: FramedTransport::new(framed, service), + } + } +} + +impl Future for Transport +where + T: AsyncRead + AsyncWrite, + S: Service, + S::Future: 'static, + S::Error: 'static, +{ + type Item = (); + type Error = FramedTransportError; + + fn poll(&mut self) -> Poll { + self.inner.poll() + } +} diff --git a/tests/test_ws.rs b/tests/test_ws.rs new file mode 100644 index 00000000..86a30919 --- /dev/null +++ b/tests/test_ws.rs @@ -0,0 +1,111 @@ +extern crate actix; +extern crate actix_http; +extern crate actix_net; +extern crate actix_web; +extern crate bytes; +extern crate futures; + +use std::{io, thread}; + +use actix::System; +use actix_net::codec::Framed; +use actix_net::server::Server; +use actix_net::service::IntoNewService; +use actix_web::{test, ws as web_ws}; +use bytes::Bytes; +use futures::future::{ok, Either}; +use futures::{Future, Sink, Stream}; + +use actix_http::{h1, ws, ResponseError}; + +fn ws_handler(req: ws::Message) -> impl Future { + match req { + ws::Message::Ping(msg) => ok(ws::Message::Pong(msg)), + ws::Message::Text(text) => ok(ws::Message::Text(text)), + ws::Message::Binary(bin) => ok(ws::Message::Binary(bin)), + ws::Message::Close(reason) => ok(ws::Message::Close(reason)), + _ => ok(ws::Message::Close(None)), + } +} + +#[test] +fn test_simple() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + (|io| { + // read http request + let framed = Framed::new(io, h1::Codec::new(false)); + framed + .into_future() + .map_err(|_| ()) + .and_then(|(req, framed)| { + // validate request + if let Some(h1::InMessage::MessageWithPayload(req)) = req { + match ws::handshake(&req) { + Err(e) => { + // validation failed + let resp = e.error_response(); + Either::A( + framed + .send(h1::OutMessage::Response(resp)) + .map_err(|_| ()) + .map(|_| ()), + ) + } + Ok(mut resp) => Either::B( + // send response + framed + .send(h1::OutMessage::Response( + resp.finish(), + )).map_err(|_| ()) + .and_then(|framed| { + // start websocket service + let framed = + framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_handler) + .map_err(|_| ()) + }), + ), + } + } else { + panic!() + } + }) + }).into_new_service() + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let (reader, mut writer) = sys + .block_on(web_ws::Client::new(format!("http://{}/", addr)).connect()) + .unwrap(); + + writer.text("text"); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); + + writer.binary(b"text".as_ref()); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) + ); + + writer.ping("ping"); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); + + writer.close(Some(web_ws::CloseCode::Normal.into())); + let (item, _) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(web_ws::Message::Close(Some( + web_ws::CloseCode::Normal.into() + ))) + ); + } +} From c0699a070efc7b72f120cb380a03cf5abaaa3bd8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 15:39:35 -0700 Subject: [PATCH 0731/1635] add TakeRequest service; update ws test case --- src/h1/mod.rs | 2 +- src/h1/service.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++-- src/ws/mod.rs | 1 - tests/test_ws.rs | 73 ++++++++++++++++++++---------------------- 4 files changed, 114 insertions(+), 43 deletions(-) diff --git a/src/h1/mod.rs b/src/h1/mod.rs index f9abfea5..266ebf39 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -7,4 +7,4 @@ mod service; pub use self::codec::{Codec, InMessage, OutMessage}; pub use self::dispatcher::Dispatcher; -pub use self::service::{H1Service, H1ServiceHandler}; +pub use self::service::{H1Service, H1ServiceHandler, TakeRequest}; diff --git a/src/h1/service.rs b/src/h1/service.rs index 8038c9fb..02535fc8 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,15 +1,17 @@ use std::fmt::{Debug, Display}; use std::marker::PhantomData; +use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; -use futures::{Async, Future, Poll}; +use futures::{future, Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use config::ServiceConfig; -use error::DispatchError; +use error::{DispatchError, ParseError}; use request::Request; use response::Response; +use super::codec::{Codec, InMessage}; use super::dispatcher::Dispatcher; /// `NewService` implementation for HTTP1 transport @@ -121,3 +123,78 @@ where Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } + +/// `NewService` that implements, read one request from framed object feature. +pub struct TakeRequest { + _t: PhantomData, +} + +impl TakeRequest { + /// Create new `TakeRequest` instance. + pub fn new() -> Self { + TakeRequest { _t: PhantomData } + } +} + +impl NewService for TakeRequest +where + T: AsyncRead + AsyncWrite, +{ + type Request = Framed; + type Response = (Option, Framed); + type Error = ParseError; + type InitError = (); + type Service = TakeRequestService; + type Future = future::FutureResult; + + fn new_service(&self) -> Self::Future { + future::ok(TakeRequestService { _t: PhantomData }) + } +} + +/// `NewService` that implements, read one request from framed object feature. +pub struct TakeRequestService { + _t: PhantomData, +} + +impl Service for TakeRequestService +where + T: AsyncRead + AsyncWrite, +{ + type Request = Framed; + type Response = (Option, Framed); + type Error = ParseError; + type Future = TakeRequestServiceResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, framed: Self::Request) -> Self::Future { + TakeRequestServiceResponse { + framed: Some(framed), + } + } +} + +pub struct TakeRequestServiceResponse +where + T: AsyncRead + AsyncWrite, +{ + framed: Option>, +} + +impl Future for TakeRequestServiceResponse +where + T: AsyncRead + AsyncWrite, +{ + type Item = (Option, Framed); + type Error = ParseError; + + fn poll(&mut self) -> Poll { + match self.framed.as_mut().unwrap().poll()? { + Async::Ready(item) => Ok(Async::Ready((item, self.framed.take().unwrap()))), + Async::NotReady => Ok(Async::NotReady), + } + } +} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index bd657d94..5ebb502b 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -3,7 +3,6 @@ //! To setup a `WebSocket`, first do web socket handshake then on success //! convert `Payload` into a `WsStream` stream and then use `WsWriter` to //! communicate with the peer. -//! ``` use std::io; use error::ResponseError; diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 86a30919..a2a18ff2 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -9,8 +9,9 @@ use std::{io, thread}; use actix::System; use actix_net::codec::Framed; +use actix_net::framed::IntoFramed; use actix_net::server::Server; -use actix_net::service::IntoNewService; +use actix_net::service::NewServiceExt; use actix_web::{test, ws as web_ws}; use bytes::Bytes; use futures::future::{ok, Either}; @@ -18,7 +19,7 @@ use futures::{Future, Sink, Stream}; use actix_http::{h1, ws, ResponseError}; -fn ws_handler(req: ws::Message) -> impl Future { +fn ws_service(req: ws::Message) -> impl Future { match req { ws::Message::Ping(msg) => ok(ws::Message::Pong(msg)), ws::Message::Text(text) => ok(ws::Message::Text(text)), @@ -34,46 +35,40 @@ fn test_simple() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - (|io| { - // read http request - let framed = Framed::new(io, h1::Codec::new(false)); - framed - .into_future() - .map_err(|_| ()) - .and_then(|(req, framed)| { - // validate request - if let Some(h1::InMessage::MessageWithPayload(req)) = req { - match ws::handshake(&req) { - Err(e) => { - // validation failed - let resp = e.error_response(); - Either::A( - framed - .send(h1::OutMessage::Response(resp)) - .map_err(|_| ()) - .map(|_| ()), - ) - } - Ok(mut resp) => Either::B( - // send response + IntoFramed::new(|| h1::Codec::new(false)) + .and_then(h1::TakeRequest::new().map_err(|_| ())) + .and_then(|(req, framed): (_, Framed<_, _>)| { + // validate request + if let Some(h1::InMessage::MessageWithPayload(req)) = req { + match ws::handshake(&req) { + Err(e) => { + // validation failed + let resp = e.error_response(); + Either::A( framed - .send(h1::OutMessage::Response( - resp.finish(), - )).map_err(|_| ()) - .and_then(|framed| { - // start websocket service - let framed = - framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_handler) - .map_err(|_| ()) - }), - ), + .send(h1::OutMessage::Response(resp)) + .map_err(|_| ()) + .map(|_| ()), + ) } - } else { - panic!() + Ok(mut resp) => Either::B( + // send response + framed + .send(h1::OutMessage::Response(resp.finish())) + .map_err(|_| ()) + .and_then(|framed| { + // start websocket service + let framed = + framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_service) + .map_err(|_| ()) + }), + ), } - }) - }).into_new_service() + } else { + panic!() + } + }) }).unwrap() .run(); }); From 7ae5a43877966cb335a1a03b128f7cc6caefac51 Mon Sep 17 00:00:00 2001 From: lzx <40080252+lzxZz@users.noreply.github.com> Date: Sat, 6 Oct 2018 13:16:12 +0800 Subject: [PATCH 0732/1635] httpresponse.rs doc fix (#534) --- src/httpresponse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 59815c58..8b091d42 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -272,7 +272,7 @@ impl HttpResponse { self.get_mut().response_size = size; } - /// Set write buffer capacity + /// Get write buffer capacity pub fn write_buffer_capacity(&self) -> usize { self.get_ref().write_capacity } From 10678a22af6a138a6106ffc3dccd281625bf7c4b Mon Sep 17 00:00:00 2001 From: Danil Berestov Date: Sat, 6 Oct 2018 13:17:20 +0800 Subject: [PATCH 0733/1635] test content length (#532) --- tests/test_server.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/test_server.rs b/tests/test_server.rs index 477d3e64..cb19cfed 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1355,3 +1355,47 @@ fn test_ssl_handshake_timeout() { let _ = sys.stop(); } + +#[test] +fn test_content_length() { + use http::StatusCode; + use actix_web::http::header::{HeaderName, HeaderValue}; + + let mut srv = test::TestServer::new(move |app| { + app.resource("/{status}", |r| { + r.f(|req: &HttpRequest| { + let indx: usize = + req.match_info().get("status").unwrap().parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + HttpResponse::new(statuses[indx]) + }) + }); + }); + + let addr = srv.addr(); + let mut get_resp = |i| { + let url = format!("http://{}/{}", addr, i); + let req = srv.get().uri(url).finish().unwrap(); + srv.execute(req.send()).unwrap() + }; + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + for i in 0..4 { + let response = get_resp(i); + assert_eq!(response.headers().get(&header), None); + } + for i in 4..6 { + let response = get_resp(i); + assert_eq!(response.headers().get(&header), Some(&value)); + } +} + From ee62814216fb67697f2451803f31f006085c05fc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 20:31:22 -0700 Subject: [PATCH 0734/1635] split request decoder and payload decoder --- src/h1/codec.rs | 57 ++++--- src/h1/decoder.rs | 389 +++++++++++++++++++--------------------------- 2 files changed, 199 insertions(+), 247 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index a27e6472..8ab8f252 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -4,15 +4,14 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::H1Decoder; -pub use super::decoder::InMessage; +use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; use super::encoder::{ResponseEncoder, ResponseLength}; use body::Body; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; -use request::RequestPool; +use request::{Request, RequestPool}; use response::Response; bitflags! { @@ -34,9 +33,23 @@ pub enum OutMessage { Payload(Bytes), } +/// Incoming http/1 request +#[derive(Debug)] +pub enum InMessage { + /// Request + Message(Request), + /// Request with payload + MessageWithPayload(Request), + /// Payload chunk + Chunk(Bytes), + /// End of payload + Eof, +} + /// HTTP/1 Codec pub struct Codec { - decoder: H1Decoder, + decoder: RequestDecoder, + payload: Option, version: Version, // encoder part @@ -64,7 +77,8 @@ impl Codec { Flags::empty() }; Codec { - decoder: H1Decoder::with_pool(pool), + decoder: RequestDecoder::with_pool(pool), + payload: None, version: Version::HTTP_11, flags, @@ -234,21 +248,28 @@ impl Decoder for Codec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let res = self.decoder.decode(src); - - match res { - Ok(Some(InMessage::Message(ref req))) - | Ok(Some(InMessage::MessageWithPayload(ref req))) => { - self.flags - .set(Flags::HEAD, req.inner.method == Method::HEAD); - self.version = req.inner.version; - if self.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.flags.set(Flags::KEEPALIVE, req.keep_alive()); - } + if self.payload.is_some() { + Ok(match self.payload.as_mut().unwrap().decode(src)? { + Some(PayloadItem::Chunk(chunk)) => Some(InMessage::Chunk(chunk)), + Some(PayloadItem::Eof) => Some(InMessage::Eof), + None => None, + }) + } else if let Some((req, payload)) = self.decoder.decode(src)? { + self.flags + .set(Flags::HEAD, req.inner.method == Method::HEAD); + self.version = req.inner.version; + if self.flags.contains(Flags::KEEPALIVE_ENABLED) { + self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } - _ => (), + self.payload = payload; + if self.payload.is_some() { + Ok(Some(InMessage::MessageWithPayload(req))) + } else { + Ok(Some(InMessage::Message(req))) + } + } else { + Ok(None) } - res } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 48776226..fb29f033 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -3,6 +3,7 @@ use std::{io, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use httparse; +use tokio_codec::Decoder; use error::ParseError; use http::header::{HeaderName, HeaderValue}; @@ -13,75 +14,25 @@ use uri::Url; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; -pub(crate) struct H1Decoder { - decoder: Option, - pool: &'static RequestPool, +pub struct RequestDecoder(&'static RequestPool); + +impl RequestDecoder { + pub(crate) fn with_pool(pool: &'static RequestPool) -> RequestDecoder { + RequestDecoder(pool) + } } -/// Incoming http/1 request -#[derive(Debug)] -pub enum InMessage { - /// Request - Message(Request), - /// Request with payload - MessageWithPayload(Request), - /// Payload chunk - Chunk(Bytes), - /// End of payload - Eof, +impl Default for RequestDecoder { + fn default() -> RequestDecoder { + RequestDecoder::with_pool(RequestPool::pool()) + } } -impl H1Decoder { - #[cfg(test)] - pub fn new() -> H1Decoder { - H1Decoder::with_pool(RequestPool::pool()) - } +impl Decoder for RequestDecoder { + type Item = (Request, Option); + type Error = ParseError; - pub fn with_pool(pool: &'static RequestPool) -> H1Decoder { - H1Decoder { - pool, - decoder: None, - } - } - - pub fn decode( - &mut self, src: &mut BytesMut, - ) -> Result, ParseError> { - // read payload - if self.decoder.is_some() { - match self.decoder.as_mut().unwrap().decode(src)? { - Async::Ready(Some(bytes)) => return Ok(Some(InMessage::Chunk(bytes))), - Async::Ready(None) => { - self.decoder.take(); - return Ok(Some(InMessage::Eof)); - } - Async::NotReady => return Ok(None), - } - } - - match self.parse_message(src)? { - Async::Ready((msg, decoder)) => { - self.decoder = decoder; - if self.decoder.is_some() { - Ok(Some(InMessage::MessageWithPayload(msg))) - } else { - Ok(Some(InMessage::Message(msg))) - } - } - Async::NotReady => { - if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - Err(ParseError::TooLarge) - } else { - Ok(None) - } - } - } - } - - fn parse_message( - &self, buf: &mut BytesMut, - ) -> Poll<(Request, Option), ParseError> { + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { // Parse http message let mut has_upgrade = false; let mut chunked = false; @@ -98,7 +49,7 @@ impl H1Decoder { unsafe { mem::uninitialized() }; let mut req = httparse::Request::new(&mut parsed); - match req.parse(buf)? { + match req.parse(src)? { httparse::Status::Complete(len) => { let method = Method::from_bytes(req.method.unwrap().as_bytes()) .map_err(|_| ParseError::Method)?; @@ -108,18 +59,18 @@ impl H1Decoder { } else { Version::HTTP_10 }; - HeaderIndex::record(buf, req.headers, &mut headers); + HeaderIndex::record(src, req.headers, &mut headers); (len, method, path, version, req.headers.len()) } - httparse::Status::Partial => return Ok(Async::NotReady), + httparse::Status::Partial => return Ok(None), } }; - let slice = buf.split_to(len).freeze(); + let slice = src.split_to(len).freeze(); // convert headers - let mut msg = RequestPool::get(self.pool); + let mut msg = RequestPool::get(self.0); { let inner = msg.inner_mut(); inner @@ -198,18 +149,21 @@ impl H1Decoder { // https://tools.ietf.org/html/rfc7230#section-3.3.3 let decoder = if chunked { // Chunked encoding - Some(EncodingDecoder::chunked()) + Some(PayloadDecoder::chunked()) } else if let Some(len) = content_length { // Content-Length - Some(EncodingDecoder::length(len)) + Some(PayloadDecoder::length(len)) } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect - Some(EncodingDecoder::eof()) + Some(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); } else { None }; - Ok(Async::Ready((msg, decoder))) + Ok(Some((msg, decoder))) } } @@ -235,30 +189,37 @@ impl HeaderIndex { } } +#[derive(Debug, Clone)] +/// Http payload item +pub enum PayloadItem { + Chunk(Bytes), + Eof, +} + /// Decoders to handle different Transfer-Encodings. /// /// If a message body does not include a Transfer-Encoding, it *should* /// include a Content-Length header. #[derive(Debug, Clone, PartialEq)] -pub struct EncodingDecoder { +pub struct PayloadDecoder { kind: Kind, } -impl EncodingDecoder { - pub fn length(x: u64) -> EncodingDecoder { - EncodingDecoder { +impl PayloadDecoder { + pub fn length(x: u64) -> PayloadDecoder { + PayloadDecoder { kind: Kind::Length(x), } } - pub fn chunked() -> EncodingDecoder { - EncodingDecoder { + pub fn chunked() -> PayloadDecoder { + PayloadDecoder { kind: Kind::Chunked(ChunkedState::Size, 0), } } - pub fn eof() -> EncodingDecoder { - EncodingDecoder { + pub fn eof() -> PayloadDecoder { + PayloadDecoder { kind: Kind::Eof(false), } } @@ -302,53 +263,59 @@ enum ChunkedState { End, } -impl EncodingDecoder { - pub fn decode(&mut self, body: &mut BytesMut) -> Poll, io::Error> { +impl Decoder for PayloadDecoder { + type Item = PayloadItem; + type Error = io::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { match self.kind { Kind::Length(ref mut remaining) => { if *remaining == 0 { - Ok(Async::Ready(None)) + Ok(Some(PayloadItem::Eof)) } else { - if body.is_empty() { - return Ok(Async::NotReady); + if src.is_empty() { + return Ok(None); } - let len = body.len() as u64; + let len = src.len() as u64; let buf; if *remaining > len { - buf = body.take().freeze(); + buf = src.take().freeze(); *remaining -= len; } else { - buf = body.split_to(*remaining as usize).freeze(); + buf = src.split_to(*remaining as usize).freeze(); *remaining = 0; - } + }; trace!("Length read: {}", buf.len()); - Ok(Async::Ready(Some(buf))) + Ok(Some(PayloadItem::Chunk(buf))) } } Kind::Chunked(ref mut state, ref mut size) => { loop { let mut buf = None; // advances the chunked state - *state = try_ready!(state.step(body, size, &mut buf)); + *state = match state.step(src, size, &mut buf)? { + Async::NotReady => return Ok(None), + Async::Ready(state) => state, + }; if *state == ChunkedState::End { trace!("End of chunked stream"); - return Ok(Async::Ready(None)); + return Ok(Some(PayloadItem::Eof)); } if let Some(buf) = buf { - return Ok(Async::Ready(Some(buf))); + return Ok(Some(PayloadItem::Chunk(buf))); } - if body.is_empty() { - return Ok(Async::NotReady); + if src.is_empty() { + return Ok(None); } } } Kind::Eof(ref mut is_eof) => { if *is_eof { - Ok(Async::Ready(None)) - } else if !body.is_empty() { - Ok(Async::Ready(Some(body.take().freeze()))) + Ok(Some(PayloadItem::Eof)) + } else if !src.is_empty() { + Ok(Some(PayloadItem::Chunk(src.take().freeze()))) } else { - Ok(Async::NotReady) + Ok(None) } } } @@ -536,15 +503,18 @@ mod tests { _ => panic!("error"), } } + } + + impl PayloadItem { fn chunk(self) -> Bytes { match self { - InMessage::Chunk(chunk) => chunk, + PayloadItem::Chunk(chunk) => chunk, _ => panic!("error"), } } fn eof(&self) -> bool { match *self { - InMessage::Eof => true, + PayloadItem::Eof => true, _ => false, } } @@ -552,8 +522,8 @@ mod tests { macro_rules! parse_ready { ($e:expr) => {{ - match H1Decoder::new().decode($e) { - Ok(Some(msg)) => msg.message(), + match RequestDecoder::default().decode($e) { + Ok(Some((msg, _))) => msg, Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), } @@ -562,7 +532,7 @@ mod tests { macro_rules! expect_parse_err { ($e:expr) => {{ - match H1Decoder::new().decode($e) { + match RequestDecoder::default().decode($e) { Err(err) => match err { ParseError::Io(_) => unreachable!("Parse error expected"), _ => (), @@ -641,10 +611,9 @@ mod tests { fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let mut reader = H1Decoder::new(); + let mut reader = RequestDecoder::default(); match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); + Ok(Some((req, _))) => { assert_eq!(req.version(), Version::HTTP_11); assert_eq!(*req.method(), Method::GET); assert_eq!(req.path(), "/test"); @@ -657,38 +626,25 @@ mod tests { fn test_parse_partial() { let mut buf = BytesMut::from("PUT /test HTTP/1"); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf) { - Ok(None) => (), - _ => unreachable!("Error"), - } + let mut reader = RequestDecoder::default(); + assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b".1\r\n\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::PUT); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::PUT); + assert_eq!(req.path(), "/test"); } #[test] fn test_parse_post() { let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_10); - assert_eq!(*req.method(), Method::POST); - assert_eq!(req.path(), "/test2"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let mut reader = RequestDecoder::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_10); + assert_eq!(*req.method(), Method::POST); + assert_eq!(req.path(), "/test2"); } #[test] @@ -696,20 +652,16 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!( + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"body" + ); } #[test] @@ -717,45 +669,36 @@ mod tests { let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!( + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + b"body" + ); } #[test] fn test_parse_partial_eof() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = H1Decoder::new(); + let mut reader = RequestDecoder::default(); assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); } #[test] fn test_headers_split_field() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = H1Decoder::new(); + let mut reader = RequestDecoder::default(); assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t"); @@ -765,16 +708,11 @@ mod tests { assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t: value\r\n\r\n"); - match reader.decode(&mut buf) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert_eq!(req.version(), Version::HTTP_11); + assert_eq!(*req.method(), Method::GET); + assert_eq!(req.path(), "/test"); + assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); } #[test] @@ -784,9 +722,8 @@ mod tests { Set-Cookie: c1=cookie1\r\n\ Set-Cookie: c2=cookie2\r\n\r\n", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); let val: Vec<_> = req .headers() @@ -985,14 +922,13 @@ mod tests { upgrade: websocket\r\n\r\n\ some raw data", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); assert!(!req.keep_alive()); assert!(req.upgrade()); assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"some raw data" ); } @@ -1039,22 +975,21 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"data" ); assert_eq!( - reader.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), + pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), b"line" ); - assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); + assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); } #[test] @@ -1063,10 +998,9 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); buf.extend( @@ -1075,19 +1009,17 @@ mod tests { transfer-encoding: chunked\r\n\r\n" .iter(), ); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req2 = msg.message(); - assert!(req2.chunked().unwrap()); - assert_eq!(*req2.method(), Method::POST); - assert!(req2.chunked().unwrap()); + let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); + assert!(req.chunked().unwrap()); + assert_eq!(*req.method(), Method::POST); + assert!(req.chunked().unwrap()); } #[test] @@ -1097,30 +1029,29 @@ mod tests { transfer-encoding: chunked\r\n\r\n", ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); + let mut reader = RequestDecoder::default(); + let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); buf.extend(b"4\r\n1111\r\n"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"1111"); buf.extend(b"4\r\ndata\r"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"data"); buf.extend(b"\n4"); - assert!(reader.decode(&mut buf).unwrap().is_none()); + assert!(pl.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r"); - assert!(reader.decode(&mut buf).unwrap().is_none()); + assert!(pl.decode(&mut buf).unwrap().is_none()); buf.extend(b"\n"); - assert!(reader.decode(&mut buf).unwrap().is_none()); + assert!(pl.decode(&mut buf).unwrap().is_none()); buf.extend(b"li"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"li"); //trailers @@ -1128,12 +1059,12 @@ mod tests { //not_ready!(reader.parse(&mut buf, &mut readbuf)); buf.extend(b"ne\r\n0\r\n"); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(reader.decode(&mut buf).unwrap().is_none()); + assert!(pl.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); - assert!(reader.decode(&mut buf).unwrap().unwrap().eof()); + assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); } #[test] @@ -1143,17 +1074,17 @@ mod tests { transfer-encoding: chunked\r\n\r\n"[..], ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf).unwrap().unwrap(); - assert!(msg.is_payload()); - assert!(msg.message().chunked().unwrap()); + let mut reader = RequestDecoder::default(); + let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + assert!(msg.chunked().unwrap()); buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); + let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader.decode(&mut buf).unwrap().unwrap().chunk(); + let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = reader.decode(&mut buf).unwrap().unwrap(); + let msg = pl.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); } } From c368abdf5fcce199b7b2ff3560a7476e63da90ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 20:34:19 -0700 Subject: [PATCH 0735/1635] remove Json type --- src/json.rs | 101 ---------------------------------------------------- src/lib.rs | 1 - 2 files changed, 102 deletions(-) diff --git a/src/json.rs b/src/json.rs index a5288489..e2c99ba3 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,8 +1,6 @@ use bytes::BytesMut; use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; -use std::fmt; -use std::ops::{Deref, DerefMut}; use mime; use serde::de::DeserializeOwned; @@ -11,105 +9,6 @@ use serde_json; use error::JsonPayloadError; use httpmessage::HttpMessage; -/// Json helper -/// -/// Json can be used for two different purpose. First is for json response -/// generation and second is for extracting typed information from request's -/// payload. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Json, Result, http}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor -/// } -/// ``` -/// -/// 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,ignore -/// # 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 Json { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Json { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Json { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl fmt::Debug for Json -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Json: {:?}", self.0) - } -} - -impl fmt::Display for Json -where - T: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - /// Request payload json parser that resolves to a deserialized `T` value. /// /// Returns error: diff --git a/src/lib.rs b/src/lib.rs index 9acdf3cd..32f78517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,7 +144,6 @@ pub use body::{Binary, Body}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; -pub use json::Json; pub use request::Request; pub use response::Response; From 87b83a34034416dcaed9ded3fea442a21c48f211 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 21:07:32 -0700 Subject: [PATCH 0736/1635] update tests, remove unused deps --- Cargo.toml | 2 - src/error.rs | 27 ------------- src/h1/dispatcher.rs | 88 +++++++++++++++++++++---------------------- src/lib.rs | 2 - tests/test_h1v2.rs | 46 ---------------------- tests/test_server.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 122 deletions(-) delete mode 100644 tests/test_h1v2.rs create mode 100644 tests/test_server.rs diff --git a/Cargo.toml b/Cargo.toml index 455b6148..48f199ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,6 @@ time = "0.1" encoding = "0.2" lazy_static = "1.0" serde_urlencoded = "^0.5.3" -url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } failure = "^0.1.2" @@ -81,7 +80,6 @@ tokio = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" -tokio-reactor = "0.1" tokio-current-thread = "0.1" # native-tls diff --git a/src/error.rs b/src/error.rs index dc2d45b8..26b3ca56 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,7 +16,6 @@ use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; use tokio_timer::Error as TimerError; -pub use url::ParseError as UrlParseError; // re-exports pub use cookie::ParseError as CookieParseError; @@ -196,9 +195,6 @@ impl ResponseError for FormError {} /// `InternalServerError` for `TimerError` impl ResponseError for TimerError {} -/// `InternalServerError` for `UrlParseError` -impl ResponseError for UrlParseError {} - /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { fn error_response(&self) -> Response { @@ -552,29 +548,6 @@ impl From for ReadlinesError { } } -/// Errors which can occur when attempting to generate resource uri. -#[derive(Fail, Debug, PartialEq)] -pub enum UrlGenerationError { - /// Resource not found - #[fail(display = "Resource not found")] - ResourceNotFound, - /// Not all path pattern covered - #[fail(display = "Not all path pattern covered")] - NotEnoughElements, - /// URL parse error - #[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) - } -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f2001302..a39967a2 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -48,12 +48,17 @@ where state: State, payload: Option, - messages: VecDeque, + messages: VecDeque, ka_expire: Instant, ka_timer: Option, } +enum Message { + Item(Request), + Error(Response), +} + enum State { None, Response(S::Future), @@ -131,32 +136,11 @@ where } // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(&mut self, _checked: bool) { + fn client_disconnected(&mut self) { self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } - - // if !checked || self.tasks.is_empty() { - // self.flags - // .insert(Flags::WRITE_DISCONNECTED | Flags::FLUSHED); - - // // notify tasks - // for mut task in self.tasks.drain(..) { - // task.disconnected(); - // match task.poll_completed() { - // Ok(Async::NotReady) => { - // // spawn not completed task, it does not require access to io - // // at this point - // spawn(HttpHandlerTaskFut::new(task.into_task())); - // } - // Ok(Async::Ready(_)) => (), - // Err(err) => { - // error!("Unhandled application error: {}", err); - // } - // } - // } - // } } /// Flush stream @@ -166,7 +150,7 @@ where Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); - self.client_disconnected(false); + self.client_disconnected(); Err(err.into()) } Ok(Async::Ready(_)) => { @@ -192,19 +176,28 @@ where let state = match self.state { State::None => loop { break if let Some(msg) = self.messages.pop_front() { - let mut task = self.service.call(msg); - match task.poll() { - Ok(Async::Ready(res)) => { - if res.body().is_streaming() { - unimplemented!() - } else { - Some(Ok(State::SendResponse(Some( - OutMessage::Response(res), - )))) + match msg { + Message::Item(msg) => { + let mut task = self.service.call(msg); + match task.poll() { + Ok(Async::Ready(res)) => { + if res.body().is_streaming() { + unimplemented!() + } else { + Some(Ok(State::SendResponse(Some( + OutMessage::Response(res), + )))) + } + } + Ok(Async::NotReady) => { + Some(Ok(State::Response(task))) + } + Err(err) => Some(Err(DispatchError::Service(err))), } } - Ok(Async::NotReady) => Some(Ok(State::Response(task))), - Err(err) => Some(Err(DispatchError::Service(err))), + Message::Error(res) => Some(Ok(State::SendResponse(Some( + OutMessage::Response(res), + )))), } } else { None @@ -249,7 +242,8 @@ where } } State::SendResponseWithPayload(ref mut item) => { - let (msg, body) = item.take().expect("SendResponse is empty"); + let (msg, body) = + item.take().expect("SendResponseWithPayload is empty"); match self.framed.start_send(msg) { Ok(AsyncSink::Ready) => { self.flags.set( @@ -271,8 +265,7 @@ where match state { Some(Ok(state)) => self.state = state, Some(Err(err)) => { - // error!("Unhandled error1: {}", err); - self.client_disconnected(false); + self.client_disconnected(); return Err(err); } None => { @@ -310,12 +303,12 @@ where Ok(Async::NotReady) => self.state = State::Response(task), Err(err) => { error!("Unhandled application error: {}", err); - self.client_disconnected(false); + self.client_disconnected(); return Err(DispatchError::Service(err)); } } } else { - self.messages.push_back(msg); + self.messages.push_back(Message::Item(msg)); } } InMessage::MessageWithPayload(msg) => { @@ -324,7 +317,7 @@ where *msg.inner.payload.borrow_mut() = Some(pl); self.payload = Some(ps); - self.messages.push_back(msg); + self.messages.push_back(Message::Item(msg)); } InMessage::Chunk(chunk) => { if let Some(ref mut payload) = self.payload { @@ -332,7 +325,9 @@ where } else { error!("Internal server error: unexpected payload chunk"); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.messages.push_back(Message::Error( + Response::InternalServerError().finish(), + )); self.error = Some(DispatchError::InternalError); } } @@ -342,7 +337,9 @@ where } else { error!("Internal server error: unexpected eof"); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - // self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); + self.messages.push_back(Message::Error( + Response::InternalServerError().finish(), + )); self.error = Some(DispatchError::InternalError); } } @@ -363,7 +360,7 @@ where } Ok(Async::Ready(None)) => { if self.flags.contains(Flags::READ_DISCONNECTED) { - self.client_disconnected(true); + self.client_disconnected(); } break; } @@ -378,7 +375,8 @@ where } // Malformed requests should be responded with 400 - // self.push_response_entry(StatusCode::BAD_REQUEST); + self.messages + .push_back(Message::Error(Response::BadRequest().finish())); self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); self.error = Some(DispatchError::MalformedRequest); break; diff --git a/src/lib.rs b/src/lib.rs index 32f78517..4ec09726 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,12 +112,10 @@ extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; -extern crate tokio_reactor; extern crate tokio_tcp; extern crate tokio_timer; #[cfg(all(unix, feature = "uds"))] extern crate tokio_uds; -extern crate url; #[cfg(test)] #[macro_use] diff --git a/tests/test_h1v2.rs b/tests/test_h1v2.rs deleted file mode 100644 index 1866f29b..00000000 --- a/tests/test_h1v2.rs +++ /dev/null @@ -1,46 +0,0 @@ -extern crate actix; -extern crate actix_http; -extern crate actix_net; -extern crate actix_web; -extern crate futures; - -use std::thread; - -use actix::System; -use actix_net::server::Server; -use actix_web::{client, test}; -use futures::future; - -use actix_http::{h1, Error, KeepAlive, Response, ServiceConfig}; - -#[test] -fn test_h1_v2() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let settings = ServiceConfig::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - h1::H1Service::new(settings, |req| { - println!("REQ: {:?}", req); - future::ok::<_, Error>(Response::Ok().finish()) - }) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} diff --git a/tests/test_server.rs b/tests/test_server.rs new file mode 100644 index 00000000..cc21416c --- /dev/null +++ b/tests/test_server.rs @@ -0,0 +1,90 @@ +extern crate actix; +extern crate actix_http; +extern crate actix_net; +extern crate actix_web; +extern crate futures; + +use std::{io::Read, io::Write, net, thread, time}; + +use actix::System; +use actix_net::server::Server; +use actix_web::{client, test}; +use futures::future; + +use actix_http::{h1, Error, KeepAlive, Response, ServiceConfig}; + +#[test] +fn test_h1_v2() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let settings = ServiceConfig::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .server_address(addr) + .finish(); + + h1::H1Service::new(settings, |_| { + future::ok::<_, Error>(Response::Ok().finish()) + }) + }).unwrap() + .run(); + }); + + let mut sys = System::new("test"); + { + let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + } +} + +#[test] +fn test_slow_request() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let settings = ServiceConfig::build().client_timeout(100).finish(); + + h1::H1Service::new(settings, |_| { + future::ok::<_, Error>(Response::Ok().finish()) + }) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut stream = net::TcpStream::connect(addr).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); +} + +#[test] +fn test_malformed_request() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let settings = ServiceConfig::build().client_timeout(100).finish(); + h1::H1Service::new(settings, |_| { + future::ok::<_, Error>(Response::Ok().finish()) + }) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut stream = net::TcpStream::connect(addr).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 400 Bad Request")); +} From 25af82c45a01d97ae65011665fa819c0edaa31df Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 21:14:02 -0700 Subject: [PATCH 0737/1635] cleanup dependencies --- .travis.yml | 17 +++-------------- Cargo.toml | 35 +---------------------------------- src/lib.rs | 14 -------------- src/response.rs | 4 +--- 4 files changed, 5 insertions(+), 65 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa8a44f2..feae3059 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,17 +14,6 @@ matrix: allow_failures: - rust: nightly -env: - global: - # - RUSTFLAGS="-C link-dead-code" - - OPENSSL_VERSION=openssl-1.0.2 - -before_install: - - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl - - sudo apt-get update -qq - - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev - -# Add clippy before_script: - export PATH=$PATH:~/.cargo/bin @@ -32,12 +21,12 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then cargo clean - cargo test --features="ssl,tls,rust-tls" -- --nocapture + cargo test fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - RUST_BACKTRACE=1 cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml + RUST_BACKTRACE=1 cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi @@ -46,7 +35,7 @@ script: #after_success: # - | # if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then -# cargo doc --features "ssl,tls,rust-tls,session" --no-deps && +# cargo doc --features "session" --no-deps && # echo "" > target/doc/index.html && # git clone https://github.com/davisp/ghp-import.git && # ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/Cargo.toml b/Cargo.toml index 48f199ed..4e66ff0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,12 @@ repository = "https://github.com/actix/actix-web.git" documentation = "https://actix.rs/api/actix-web/stable/actix_web/" categories = ["network-programming", "asynchronous", "web-programming::http-server", - "web-programming::http-client", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] [package.metadata.docs.rs] -features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] +features = ["session"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } @@ -30,18 +29,6 @@ path = "src/lib.rs" [features] default = ["session", "cell"] -# native-tls -tls = ["native-tls", "tokio-tls", "actix-net/tls"] - -# openssl -ssl = ["openssl", "tokio-openssl", "actix-net/ssl"] - -# rustls -rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"] - -# unix sockets -uds = ["tokio-uds"] - # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] @@ -82,31 +69,11 @@ tokio-tcp = "0.1" tokio-timer = "0.2" tokio-current-thread = "0.1" -# native-tls -native-tls = { version="0.2", optional = true } -tokio-tls = { version="0.2", optional = true } - -# openssl -openssl = { version="0.10", optional = true } -tokio-openssl = { version="0.2", optional = true } - -#rustls -rustls = { version = "0.14", optional = true } -tokio-rustls = { version = "0.8", optional = true } -webpki = { version = "0.18", optional = true } -webpki-roots = { version = "0.15", optional = true } - -# unix sockets -tokio-uds = { version="0.2", optional = true } - [dev-dependencies] actix-web = "0.7" env_logger = "0.5" serde_derive = "1.0" -[build-dependencies] -version_check = "0.1" - [profile.release] lto = true opt-level = 3 diff --git a/src/lib.rs b/src/lib.rs index 4ec09726..8a7bcfa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,21 +63,9 @@ //! //! ## Package feature //! -//! * `tls` - enables ssl support via `native-tls` crate -//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` -//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` -//! * `uds` - enables support for making client requests via Unix Domain Sockets. -//! Unix only. Not necessary for *serving* requests. //! * `session` - enables session support, includes `ring` crate as //! dependency -//! * `brotli` - enables `brotli` compression support, requires `c` -//! compiler -//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires -//! `c` compiler -//! * `flate2-rust` - experimental rust based implementation for -//! `gzip`, `deflate` compression. //! -#![cfg_attr(actix_nightly, feature(tool_lints))] // #![warn(missing_docs)] #![allow(dead_code)] @@ -114,8 +102,6 @@ extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_tcp; extern crate tokio_timer; -#[cfg(all(unix, feature = "uds"))] -extern crate tokio_uds; #[cfg(test)] #[macro_use] diff --git a/src/response.rs b/src/response.rs index d0136b40..e834748e 100644 --- a/src/response.rs +++ b/src/response.rs @@ -942,7 +942,7 @@ mod tests { use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; use header::ContentEncoding; - use test::TestRequest; + // use test::TestRequest; #[test] fn test_debug() { @@ -1118,8 +1118,6 @@ mod tests { #[test] fn test_into_response() { - let req = TestRequest::default().finish(); - let resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( From dda5b399ca56e966c34908756879f9489ac70481 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 21:32:01 -0700 Subject: [PATCH 0738/1635] add content-length test --- tests/test_server.rs | 58 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/tests/test_server.rs b/tests/test_server.rs index cc21416c..d0f364b6 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -8,10 +8,10 @@ use std::{io::Read, io::Write, net, thread, time}; use actix::System; use actix_net::server::Server; -use actix_web::{client, test}; +use actix_web::{client, test, HttpMessage}; use futures::future; -use actix_http::{h1, Error, KeepAlive, Response, ServiceConfig}; +use actix_http::{h1, Error, KeepAlive, Request, Response, ServiceConfig}; #[test] fn test_h1_v2() { @@ -88,3 +88,57 @@ fn test_malformed_request() { let _ = stream.read_to_string(&mut data); assert!(data.starts_with("HTTP/1.1 400 Bad Request")); } + +#[test] +fn test_content_length() { + use actix_http::http::{ + header::{HeaderName, HeaderValue}, + StatusCode, + }; + + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let settings = ServiceConfig::build().client_timeout(100).finish(); + h1::H1Service::new(settings, |req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, Error>(Response::new(statuses[indx])) + }) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + let mut sys = System::new("test"); + { + for i in 0..4 { + let req = + client::ClientRequest::get(format!("http://{}/{}", addr, i).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = + client::ClientRequest::get(format!("http://{}/{}", addr, i).as_str()) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} From b0ca6220f07fafd9862542cb703cefc1753c16e9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 6 Oct 2018 22:36:57 -0700 Subject: [PATCH 0739/1635] refactor te encoding --- src/error.rs | 6 +- src/h1/codec.rs | 32 +++-------- src/h1/dispatcher.rs | 132 +++++++++++++++++++++---------------------- src/h1/service.rs | 13 +++-- 4 files changed, 83 insertions(+), 100 deletions(-) diff --git a/src/error.rs b/src/error.rs index 26b3ca56..277814d2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -374,7 +374,7 @@ impl ResponseError for cookie::ParseError { #[derive(Debug)] /// A set of errors that can occur during dispatching http requests -pub enum DispatchError { +pub enum DispatchError { /// Service error // #[fail(display = "Application specific error: {}", _0)] Service(E), @@ -413,13 +413,13 @@ pub enum DispatchError { Unknown, } -impl From for DispatchError { +impl From for DispatchError { fn from(err: ParseError) -> Self { DispatchError::Parse(err) } } -impl From for DispatchError { +impl From for DispatchError { fn from(err: io::Error) -> Self { DispatchError::Io(err) } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 8ab8f252..247b0f01 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -54,7 +54,6 @@ pub struct Codec { // encoder part flags: Flags, - written: u64, headers_size: u32, te: ResponseEncoder, } @@ -82,31 +81,30 @@ impl Codec { version: Version::HTTP_11, flags, - written: 0, headers_size: 0, te: ResponseEncoder::default(), } } - fn written(&self) -> u64 { - self.written - } - + /// Check if request is upgrade pub fn upgrade(&self) -> bool { self.flags.contains(Flags::UPGRADE) } + /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { self.flags.contains(Flags::KEEPALIVE) } + /// prepare transfer encoding + pub fn prepare_te(&mut self, res: &mut Response) { + self.te + .update(res, self.flags.contains(Flags::HEAD), self.version); + } + fn encode_response( &mut self, mut msg: Response, buffer: &mut BytesMut, ) -> io::Result<()> { - // prepare transfer encoding - self.te - .update(&mut msg, self.flags.contains(Flags::HEAD), self.version); - let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg .keep_alive() .unwrap_or_else(|| self.flags.contains(Flags::KEEPALIVE)); @@ -131,12 +129,11 @@ impl Codec { msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("close")); } - let body = msg.replace_body(Body::Empty); // render message { let reason = msg.reason().as_bytes(); - if let Body::Binary(ref bytes) = body { + if let Body::Binary(ref bytes) = msg.body() { buffer.reserve( 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len() @@ -229,16 +226,6 @@ impl Codec { self.headers_size = buffer.len() as u32; } - if let Body::Binary(bytes) = body { - self.written = bytes.len() as u64; - // buffer.write(bytes.as_ref())?; - buffer.extend_from_slice(bytes.as_ref()); - } else { - // capacity, makes sense only for streaming or actor - // self.buffer_capacity = msg.write_buffer_capacity(); - - msg.replace_body(body); - } Ok(()) } } @@ -282,7 +269,6 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { OutMessage::Response(res) => { - self.written = 0; self.encode_response(res, dst)?; } OutMessage::Payload(bytes) => { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index a39967a2..7b6d31fe 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,15 +1,15 @@ use std::collections::VecDeque; -use std::fmt::{Debug, Display}; use std::time::Instant; use actix_net::codec::Framed; use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; +use log::Level::Debug; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -use error::{ParseError, PayloadError}; +use error::{Error, ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; @@ -38,7 +38,7 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where - S::Error: Debug + Display, + S::Error: Into, { service: S, flags: Flags, @@ -81,7 +81,7 @@ impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug + Display, + S::Error: Into, { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { @@ -177,52 +177,34 @@ where State::None => loop { break if let Some(msg) = self.messages.pop_front() { match msg { - Message::Item(msg) => { - let mut task = self.service.call(msg); - match task.poll() { - Ok(Async::Ready(res)) => { - if res.body().is_streaming() { - unimplemented!() - } else { - Some(Ok(State::SendResponse(Some( - OutMessage::Response(res), - )))) - } - } - Ok(Async::NotReady) => { - Some(Ok(State::Response(task))) - } - Err(err) => Some(Err(DispatchError::Service(err))), - } - } - Message::Error(res) => Some(Ok(State::SendResponse(Some( + Message::Item(req) => Some(self.handle_request(req)), + Message::Error(res) => Some(State::SendResponse(Some( OutMessage::Response(res), - )))), + ))), } } else { None }; }, State::Payload(ref mut _body) => unimplemented!(), - State::Response(ref mut fut) => { - match fut.poll() { - Ok(Async::Ready(res)) => { - if res.body().is_streaming() { - unimplemented!() - } else { - Some(Ok(State::SendResponse(Some( - OutMessage::Response(res), - )))) - } - } - Ok(Async::NotReady) => None, - Err(err) => { - // it is not possible to recover from error - // during pipe handling, so just drop connection - Some(Err(DispatchError::Service(err))) + State::Response(ref mut fut) => match fut.poll() { + Ok(Async::Ready(mut res)) => { + self.framed.get_codec_mut().prepare_te(&mut res); + if res.body().is_streaming() { + unimplemented!() + } else { + Some(State::SendResponse(Some(OutMessage::Response(res)))) } } - } + Ok(Async::NotReady) => None, + Err(err) => { + let err = err.into(); + if log_enabled!(Debug) { + debug!("{:?}", err); + } + Some(State::SendResponse(Some(OutMessage::Response(err.into())))) + } + }, State::SendResponse(ref mut item) => { let msg = item.take().expect("SendResponse is empty"); match self.framed.start_send(msg) { @@ -232,13 +214,19 @@ where self.framed.get_codec().keepalive(), ); self.flags.remove(Flags::FLUSHED); - Some(Ok(State::None)) + Some(State::None) } Ok(AsyncSink::NotReady(msg)) => { *item = Some(msg); return Ok(()); } - Err(err) => Some(Err(DispatchError::Io(err))), + Err(err) => { + self.flags.insert(Flags::READ_DISCONNECTED); + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } + return Err(DispatchError::Io(err)); + } } } State::SendResponseWithPayload(ref mut item) => { @@ -251,23 +239,25 @@ where self.framed.get_codec().keepalive(), ); self.flags.remove(Flags::FLUSHED); - Some(Ok(State::Payload(body))) + Some(State::Payload(body)) } Ok(AsyncSink::NotReady(msg)) => { *item = Some((msg, body)); return Ok(()); } - Err(err) => Some(Err(DispatchError::Io(err))), + Err(err) => { + self.flags.insert(Flags::READ_DISCONNECTED); + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete); + } + return Err(DispatchError::Io(err)); + } } } }; match state { - Some(Ok(state)) => self.state = state, - Some(Err(err)) => { - self.client_disconnected(); - return Err(err); - } + Some(state) => self.state = state, None => { // if read-backpressure is enabled and we consumed some data. // we may read more dataand retry @@ -283,6 +273,28 @@ where Ok(()) } + fn handle_request(&mut self, req: Request) -> State { + let mut task = self.service.call(req); + match task.poll() { + Ok(Async::Ready(mut res)) => { + self.framed.get_codec_mut().prepare_te(&mut res); + if res.body().is_streaming() { + unimplemented!() + } else { + State::SendResponse(Some(OutMessage::Response(res))) + } + } + Ok(Async::NotReady) => State::Response(task), + Err(err) => { + let err = err.into(); + if log_enabled!(Debug) { + debug!("{:?}", err); + } + State::SendResponse(Some(OutMessage::Response(err.into()))) + } + } + } + fn one_message(&mut self, msg: InMessage) -> Result<(), DispatchError> { self.flags.insert(Flags::STARTED); @@ -290,23 +302,7 @@ where InMessage::Message(msg) => { // handle request early if self.state.is_empty() { - let mut task = self.service.call(msg); - match task.poll() { - Ok(Async::Ready(res)) => { - if res.body().is_streaming() { - unimplemented!() - } else { - self.state = - State::SendResponse(Some(OutMessage::Response(res))); - } - } - Ok(Async::NotReady) => self.state = State::Response(task), - Err(err) => { - error!("Unhandled application error: {}", err); - self.client_disconnected(); - return Err(DispatchError::Service(err)); - } - } + self.state = self.handle_request(msg); } else { self.messages.push_back(Message::Item(msg)); } @@ -449,7 +445,7 @@ impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug + Display, + S::Error: Into, { type Item = (); type Error = DispatchError; diff --git a/src/h1/service.rs b/src/h1/service.rs index 02535fc8..3ac073ad 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,4 +1,3 @@ -use std::fmt::{Debug, Display}; use std::marker::PhantomData; use actix_net::codec::Framed; @@ -7,7 +6,7 @@ use futures::{future, Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use config::ServiceConfig; -use error::{DispatchError, ParseError}; +use error::{DispatchError, Error, ParseError}; use request::Request; use response::Response; @@ -24,6 +23,8 @@ pub struct H1Service { impl H1Service where S: NewService, + S::Service: Clone, + S::Error: Into, { /// Create new `HttpService` instance. pub fn new>(cfg: ServiceConfig, service: F) -> Self { @@ -40,7 +41,7 @@ where T: AsyncRead + AsyncWrite, S: NewService + Clone, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Into, { type Request = T; type Response = (); @@ -69,7 +70,7 @@ where T: AsyncRead + AsyncWrite, S: NewService, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Into, { type Item = H1ServiceHandler; type Error = S::InitError; @@ -93,7 +94,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where S: Service + Clone, - S::Error: Debug + Display, + S::Error: Into, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { @@ -108,7 +109,7 @@ impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service + Clone, - S::Error: Debug + Display, + S::Error: Into, { type Request = T; type Response = (); From 8d85c45c1d1369f7e357e9f3f19c4beb75284740 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Oct 2018 00:04:38 -0700 Subject: [PATCH 0740/1635] simplify error handling --- src/body.rs | 6 ++-- src/error.rs | 6 ++-- src/h1/dispatcher.rs | 69 ++++++++++++++++++++++---------------------- src/h1/service.rs | 13 +++++---- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/body.rs b/src/body.rs index c10b067a..c78ea817 100644 --- a/src/body.rs +++ b/src/body.rs @@ -69,10 +69,10 @@ impl Body { /// Is this binary body. #[inline] - pub(crate) fn binary(self) -> Binary { + pub(crate) fn into_binary(self) -> Option { match self { - Body::Binary(b) => b, - _ => panic!(), + Body::Binary(b) => Some(b), + _ => None, } } } diff --git a/src/error.rs b/src/error.rs index 277814d2..1e60c348 100644 --- a/src/error.rs +++ b/src/error.rs @@ -374,7 +374,7 @@ impl ResponseError for cookie::ParseError { #[derive(Debug)] /// A set of errors that can occur during dispatching http requests -pub enum DispatchError { +pub enum DispatchError { /// Service error // #[fail(display = "Application specific error: {}", _0)] Service(E), @@ -413,13 +413,13 @@ pub enum DispatchError { Unknown, } -impl From for DispatchError { +impl From for DispatchError { fn from(err: ParseError) -> Self { DispatchError::Parse(err) } } -impl From for DispatchError { +impl From for DispatchError { fn from(err: io::Error) -> Self { DispatchError::Io(err) } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 7b6d31fe..3bf17a8b 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,15 +1,15 @@ use std::collections::VecDeque; +use std::fmt::{Debug, Display}; use std::time::Instant; use actix_net::codec::Framed; use actix_net::service::Service; use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; -use log::Level::Debug; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -use error::{Error, ParseError, PayloadError}; +use error::{ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; @@ -38,7 +38,7 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where - S::Error: Into, + S::Error: Debug + Display, { service: S, flags: Flags, @@ -61,7 +61,7 @@ enum Message { enum State { None, - Response(S::Future), + ServiceCall(S::Future), SendResponse(Option), SendResponseWithPayload(Option<(OutMessage, Body)>), Payload(Body), @@ -81,7 +81,7 @@ impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Into, + S::Error: Debug + Display, { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { @@ -177,7 +177,7 @@ where State::None => loop { break if let Some(msg) = self.messages.pop_front() { match msg { - Message::Item(req) => Some(self.handle_request(req)), + Message::Item(req) => Some(self.handle_request(req)?), Message::Error(res) => Some(State::SendResponse(Some( OutMessage::Response(res), ))), @@ -187,23 +187,23 @@ where }; }, State::Payload(ref mut _body) => unimplemented!(), - State::Response(ref mut fut) => match fut.poll() { - Ok(Async::Ready(mut res)) => { + State::ServiceCall(ref mut fut) => match fut + .poll() + .map_err(DispatchError::Service)? + { + Async::Ready(mut res) => { self.framed.get_codec_mut().prepare_te(&mut res); - if res.body().is_streaming() { - unimplemented!() - } else { + let body = res.replace_body(Body::Empty); + if body.is_empty() { Some(State::SendResponse(Some(OutMessage::Response(res)))) + } else { + Some(State::SendResponseWithPayload(Some(( + OutMessage::Response(res), + body, + )))) } } - Ok(Async::NotReady) => None, - Err(err) => { - let err = err.into(); - if log_enabled!(Debug) { - debug!("{:?}", err); - } - Some(State::SendResponse(Some(OutMessage::Response(err.into())))) - } + Async::NotReady => None, }, State::SendResponse(ref mut item) => { let msg = item.take().expect("SendResponse is empty"); @@ -273,25 +273,24 @@ where Ok(()) } - fn handle_request(&mut self, req: Request) -> State { + fn handle_request( + &mut self, req: Request, + ) -> Result, DispatchError> { let mut task = self.service.call(req); - match task.poll() { - Ok(Async::Ready(mut res)) => { + match task.poll().map_err(DispatchError::Service)? { + Async::Ready(mut res) => { self.framed.get_codec_mut().prepare_te(&mut res); - if res.body().is_streaming() { - unimplemented!() + let body = res.replace_body(Body::Empty); + if body.is_empty() { + Ok(State::SendResponse(Some(OutMessage::Response(res)))) } else { - State::SendResponse(Some(OutMessage::Response(res))) + Ok(State::SendResponseWithPayload(Some(( + OutMessage::Response(res), + body, + )))) } } - Ok(Async::NotReady) => State::Response(task), - Err(err) => { - let err = err.into(); - if log_enabled!(Debug) { - debug!("{:?}", err); - } - State::SendResponse(Some(OutMessage::Response(err.into()))) - } + Async::NotReady => Ok(State::ServiceCall(task)), } } @@ -302,7 +301,7 @@ where InMessage::Message(msg) => { // handle request early if self.state.is_empty() { - self.state = self.handle_request(msg); + self.state = self.handle_request(msg)?; } else { self.messages.push_back(Message::Item(msg)); } @@ -445,7 +444,7 @@ impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Into, + S::Error: Debug + Display, { type Item = (); type Error = DispatchError; diff --git a/src/h1/service.rs b/src/h1/service.rs index 3ac073ad..aa59614d 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,3 +1,4 @@ +use std::fmt::{Debug, Display}; use std::marker::PhantomData; use actix_net::codec::Framed; @@ -6,7 +7,7 @@ use futures::{future, Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use config::ServiceConfig; -use error::{DispatchError, Error, ParseError}; +use error::{DispatchError, ParseError}; use request::Request; use response::Response; @@ -24,7 +25,7 @@ impl H1Service where S: NewService, S::Service: Clone, - S::Error: Into, + S::Error: Debug + Display, { /// Create new `HttpService` instance. pub fn new>(cfg: ServiceConfig, service: F) -> Self { @@ -41,7 +42,7 @@ where T: AsyncRead + AsyncWrite, S: NewService + Clone, S::Service: Clone, - S::Error: Into, + S::Error: Debug + Display, { type Request = T; type Response = (); @@ -70,7 +71,7 @@ where T: AsyncRead + AsyncWrite, S: NewService, S::Service: Clone, - S::Error: Into, + S::Error: Debug + Display, { type Item = H1ServiceHandler; type Error = S::InitError; @@ -94,7 +95,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where S: Service + Clone, - S::Error: Into, + S::Error: Debug + Display, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { @@ -109,7 +110,7 @@ impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service + Clone, - S::Error: Into, + S::Error: Debug + Display, { type Request = T; type Response = (); From 9c4a55c95c106abf8edebad1a5db8188d0bea57a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Oct 2018 08:28:38 -0700 Subject: [PATCH 0741/1635] simplify H1Service configuration --- src/h1/service.rs | 136 ++++++++++++++++++++++++++++++++++++++++++- tests/test_server.rs | 28 ++++----- 2 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/h1/service.rs b/src/h1/service.rs index aa59614d..eea0e6d9 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,12 +1,13 @@ use std::fmt::{Debug, Display}; use std::marker::PhantomData; +use std::net; use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; use futures::{future, Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; -use config::ServiceConfig; +use config::{KeepAlive, ServiceConfig}; use error::{DispatchError, ParseError}; use request::Request; use response::Response; @@ -28,13 +29,20 @@ where S::Error: Debug + Display, { /// Create new `HttpService` instance. - pub fn new>(cfg: ServiceConfig, service: F) -> Self { + pub fn new>(service: F) -> Self { + let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); + H1Service { cfg, srv: service.into_new_service(), _t: PhantomData, } } + + /// Create builder for `HttpService` instance. + pub fn build() -> H1ServiceBuilder { + H1ServiceBuilder::new() + } } impl NewService for H1Service @@ -60,6 +68,130 @@ where } } +/// A http/1 new service builder +/// +/// This type can be used to construct an instance of `ServiceConfig` through a +/// builder-like pattern. +pub struct H1ServiceBuilder { + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, + host: String, + addr: net::SocketAddr, + secure: bool, + _t: PhantomData<(T, S)>, +} + +impl H1ServiceBuilder +where + S: NewService, + S::Service: Clone, + S::Error: Debug + Display, +{ + /// Create instance of `ServiceConfigBuilder` + pub fn new() -> H1ServiceBuilder { + H1ServiceBuilder { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_disconnect: 0, + secure: false, + host: "localhost".to_owned(), + addr: "127.0.0.1:8080".parse().unwrap(), + _t: PhantomData, + } + } + + /// Enable secure flag for current server. + /// This flags also enables `client disconnect timeout`. + /// + /// By default this flag is set to false. + pub fn secure(mut self) -> Self { + self.secure = true; + if self.client_disconnect == 0 { + self.client_disconnect = 3000; + } + self + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: U) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; + 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. + /// + /// By default host name is set to a "localhost" value. + pub fn server_hostname(mut self, val: &str) -> Self { + self.host = val.to_owned(); + self + } + + /// Set server ip address. + /// + /// 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. + /// + /// By default server address is set to a "127.0.0.1:8080" + pub fn server_address(mut self, addr: U) -> Self { + match addr.to_socket_addrs() { + Err(err) => error!("Can not convert to SocketAddr: {}", err), + Ok(mut addrs) => if let Some(addr) = addrs.next() { + self.addr = addr; + }, + } + self + } + + /// Finish service configuration and create `H1Service` instance. + pub fn finish>(self, service: F) -> H1Service { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + H1Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } +} + pub struct H1ServiceResponse { fut: S::Future, cfg: Option, diff --git a/tests/test_server.rs b/tests/test_server.rs index d0f364b6..8d682e12 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,7 +11,7 @@ use actix_net::server::Server; use actix_web::{client, test, HttpMessage}; use futures::future; -use actix_http::{h1, Error, KeepAlive, Request, Response, ServiceConfig}; +use actix_http::{h1, Error, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { @@ -19,17 +19,13 @@ fn test_h1_v2() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let settings = ServiceConfig::build() + h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_disconnect(1000) .server_hostname("localhost") .server_address(addr) - .finish(); - - h1::H1Service::new(settings, |_| { - future::ok::<_, Error>(Response::Ok().finish()) - }) + .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -50,11 +46,9 @@ fn test_slow_request() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let settings = ServiceConfig::build().client_timeout(100).finish(); - - h1::H1Service::new(settings, |_| { - future::ok::<_, Error>(Response::Ok().finish()) - }) + h1::H1Service::build() + .client_timeout(100) + .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -73,10 +67,9 @@ fn test_malformed_request() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let settings = ServiceConfig::build().client_timeout(100).finish(); - h1::H1Service::new(settings, |_| { - future::ok::<_, Error>(Response::Ok().finish()) - }) + h1::H1Service::build() + .client_timeout(100) + .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -100,8 +93,7 @@ fn test_content_length() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - let settings = ServiceConfig::build().client_timeout(100).finish(); - h1::H1Service::new(settings, |req: Request| { + h1::H1Service::new(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ StatusCode::NO_CONTENT, From 13193a0721110f0ad0dd046af76480322ae46430 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Oct 2018 09:48:53 -0700 Subject: [PATCH 0742/1635] refactor http/1 dispatcher --- src/error.rs | 15 ++---- src/h1/decoder.rs | 16 +++---- src/h1/dispatcher.rs | 111 ++++++++++++++++++------------------------- src/h1/service.rs | 14 +++--- src/payload.rs | 7 --- tests/test_server.rs | 11 +++-- 6 files changed, 68 insertions(+), 106 deletions(-) diff --git a/src/error.rs b/src/error.rs index 1e60c348..465b8ae0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -341,15 +341,6 @@ pub enum PayloadError { /// A payload length is unknown. #[fail(display = "A payload length is unknown.")] UnknownLength, - /// Io error - #[fail(display = "{}", _0)] - Io(#[cause] IoError), -} - -impl From for PayloadError { - fn from(err: IoError) -> PayloadError { - PayloadError::Io(err) - } } /// `PayloadError` returns two possible results: @@ -374,7 +365,7 @@ impl ResponseError for cookie::ParseError { #[derive(Debug)] /// A set of errors that can occur during dispatching http requests -pub enum DispatchError { +pub enum DispatchError { /// Service error // #[fail(display = "Application specific error: {}", _0)] Service(E), @@ -413,13 +404,13 @@ pub enum DispatchError { Unknown, } -impl From for DispatchError { +impl From for DispatchError { fn from(err: ParseError) -> Self { DispatchError::Parse(err) } } -impl From for DispatchError { +impl From for DispatchError { fn from(err: io::Error) -> Self { DispatchError::Io(err) } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index fb29f033..d0c3fa04 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -219,9 +219,7 @@ impl PayloadDecoder { } pub fn eof() -> PayloadDecoder { - PayloadDecoder { - kind: Kind::Eof(false), - } + PayloadDecoder { kind: Kind::Eof } } } @@ -246,7 +244,7 @@ enum Kind { /// > the final encoding, the message body length cannot be determined /// > reliably; the server MUST respond with the 400 (Bad Request) /// > status code and then close the connection. - Eof(bool), + Eof, } #[derive(Debug, PartialEq, Clone)] @@ -309,13 +307,11 @@ impl Decoder for PayloadDecoder { } } } - Kind::Eof(ref mut is_eof) => { - if *is_eof { - Ok(Some(PayloadItem::Eof)) - } else if !src.is_empty() { - Ok(Some(PayloadItem::Chunk(src.take().freeze()))) - } else { + Kind::Eof => { + if src.is_empty() { Ok(None) + } else { + Ok(Some(PayloadItem::Chunk(src.take().freeze()))) } } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 3bf17a8b..92bec354 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,5 +1,5 @@ use std::collections::VecDeque; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::time::Instant; use actix_net::codec::Framed; @@ -27,18 +27,17 @@ bitflags! { const STARTED = 0b0000_0001; const KEEPALIVE_ENABLED = 0b0000_0010; const KEEPALIVE = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const READ_DISCONNECTED = 0b0001_0000; - const WRITE_DISCONNECTED = 0b0010_0000; - const POLLED = 0b0100_0000; - const FLUSHED = 0b1000_0000; + const POLLED = 0b0000_1000; + const FLUSHED = 0b0001_0000; + const SHUTDOWN = 0b0010_0000; + const DISCONNECTED = 0b0100_0000; } } /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where - S::Error: Debug + Display, + S::Error: Debug, { service: S, flags: Flags, @@ -81,7 +80,7 @@ impl Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug + Display, + S::Error: Debug, { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { @@ -122,9 +121,8 @@ where } } - #[inline] fn can_read(&self) -> bool { - if self.flags.contains(Flags::READ_DISCONNECTED) { + if self.flags.contains(Flags::DISCONNECTED) { return false; } @@ -137,7 +135,7 @@ where // if checked is set to true, delay disconnect until all tasks have finished. fn client_disconnected(&mut self) { - self.flags.insert(Flags::READ_DISCONNECTED); + self.flags.insert(Flags::DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } @@ -145,12 +143,11 @@ where /// Flush stream fn poll_flush(&mut self) -> Poll<(), DispatchError> { - if self.flags.contains(Flags::STARTED) && !self.flags.contains(Flags::FLUSHED) { + if !self.flags.contains(Flags::FLUSHED) { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); - self.client_disconnected(); Err(err.into()) } Ok(Async::Ready(_)) => { @@ -167,8 +164,7 @@ where } } - pub(self) fn poll_handler(&mut self) -> Result<(), DispatchError> { - self.poll_io()?; + fn poll_response(&mut self) -> Result<(), DispatchError> { let mut retry = self.can_read(); // process @@ -221,7 +217,6 @@ where return Ok(()); } Err(err) => { - self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } @@ -246,7 +241,6 @@ where return Ok(()); } Err(err) => { - self.flags.insert(Flags::READ_DISCONNECTED); if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete); } @@ -261,7 +255,7 @@ where None => { // if read-backpressure is enabled and we consumed some data. // we may read more dataand retry - if !retry && self.can_read() && self.poll_io()? { + if !retry && self.can_read() && self.poll_request()? { retry = self.can_read(); continue; } @@ -319,7 +313,7 @@ where payload.feed_data(chunk); } else { error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + self.flags.insert(Flags::DISCONNECTED); self.messages.push_back(Message::Error( Response::InternalServerError().finish(), )); @@ -331,7 +325,7 @@ where payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); + self.flags.insert(Flags::DISCONNECTED); self.messages.push_back(Message::Error( Response::InternalServerError().finish(), )); @@ -343,7 +337,7 @@ where Ok(()) } - pub(self) fn poll_io(&mut self) -> Result> { + pub(self) fn poll_request(&mut self) -> Result> { let mut updated = false; if self.messages.len() < MAX_PIPELINED_MESSAGES { @@ -354,26 +348,25 @@ where self.one_message(msg)?; } Ok(Async::Ready(None)) => { - if self.flags.contains(Flags::READ_DISCONNECTED) { - self.client_disconnected(); - } + self.client_disconnected(); break; } Ok(Async::NotReady) => break, + Err(ParseError::Io(e)) => { + self.client_disconnected(); + self.error = Some(DispatchError::Io(e)); + break; + } Err(e) => { if let Some(mut payload) = self.payload.take() { - let e = match e { - ParseError::Io(e) => PayloadError::Io(e), - _ => PayloadError::EncodingCorrupted, - }; - payload.set_error(e); + payload.set_error(PayloadError::EncodingCorrupted); } // Malformed requests should be responded with 400 self.messages .push_back(Message::Error(Response::BadRequest().finish())); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.error = Some(DispatchError::MalformedRequest); + self.flags.insert(Flags::DISCONNECTED); + self.error = Some(e.into()); break; } } @@ -402,8 +395,7 @@ where } else if !self.flags.contains(Flags::STARTED) { // timeout on first request (slow request) return 408 trace!("Slow request timeout"); - self.flags - .insert(Flags::STARTED | Flags::READ_DISCONNECTED); + self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); self.state = State::SendResponse(Some(OutMessage::Response( Response::RequestTimeout().finish(), @@ -444,54 +436,43 @@ impl Future for Dispatcher where T: AsyncRead + AsyncWrite, S: Service, - S::Error: Debug + Display, + S::Error: Debug, { type Item = (); type Error = DispatchError; #[inline] fn poll(&mut self) -> Poll<(), Self::Error> { - self.poll_keepalive()?; - - // shutdown if self.flags.contains(Flags::SHUTDOWN) { - if self.flags.contains(Flags::WRITE_DISCONNECTED) { - return Ok(Async::Ready(())); - } + self.poll_keepalive()?; try_ready!(self.poll_flush()); - return Ok(AsyncWrite::shutdown(self.framed.get_mut())?); - } - - // process incoming requests - if !self.flags.contains(Flags::WRITE_DISCONNECTED) { - self.poll_handler()?; - - // flush stream + Ok(AsyncWrite::shutdown(self.framed.get_mut())?) + } else { + self.poll_keepalive()?; + self.poll_request()?; + self.poll_response()?; self.poll_flush()?; - // deal with keep-alive and stream eof (client-side write shutdown) + // keep-alive and stream errors if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { - // handle stream eof - if self - .flags - .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) - { - return Ok(Async::Ready(())); + if let Some(err) = self.error.take() { + Err(err) + } else if self.flags.contains(Flags::DISCONNECTED) { + Ok(Async::Ready(())) } - // no keep-alive - if self.flags.contains(Flags::STARTED) - && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) - || !self.flags.contains(Flags::KEEPALIVE)) + // disconnect if keep-alive is not enabled + else if self.flags.contains(Flags::STARTED) && !self + .flags + .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) { self.flags.insert(Flags::SHUTDOWN); - return self.poll(); + self.poll() + } else { + Ok(Async::NotReady) } + } else { + Ok(Async::NotReady) } - Ok(Async::NotReady) - } else if let Some(err) = self.error.take() { - Err(err) - } else { - Ok(Async::Ready(())) } } } diff --git a/src/h1/service.rs b/src/h1/service.rs index eea0e6d9..de24f52c 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,4 +1,4 @@ -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::marker::PhantomData; use std::net; @@ -26,7 +26,7 @@ impl H1Service where S: NewService, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Debug, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -50,7 +50,7 @@ where T: AsyncRead + AsyncWrite, S: NewService + Clone, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Debug, { type Request = T; type Response = (); @@ -86,7 +86,7 @@ impl H1ServiceBuilder where S: NewService, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` pub fn new() -> H1ServiceBuilder { @@ -203,7 +203,7 @@ where T: AsyncRead + AsyncWrite, S: NewService, S::Service: Clone, - S::Error: Debug + Display, + S::Error: Debug, { type Item = H1ServiceHandler; type Error = S::InitError; @@ -227,7 +227,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where S: Service + Clone, - S::Error: Debug + Display, + S::Error: Debug, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { @@ -242,7 +242,7 @@ impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service + Clone, - S::Error: Debug + Display, + S::Error: Debug, { type Request = T; type Response = (); diff --git a/src/payload.rs b/src/payload.rs index 3f51f6ec..54539c40 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -522,18 +522,11 @@ where #[cfg(test)] mod tests { use super::*; - use failure::Fail; use futures::future::{lazy, result}; - use std::io; use tokio::runtime::current_thread::Runtime; #[test] fn test_error() { - let err: PayloadError = - io::Error::new(io::ErrorKind::Other, "ParseError").into(); - assert_eq!(format!("{}", err), "ParseError"); - assert_eq!(format!("{}", err.cause().unwrap()), "ParseError"); - let err = PayloadError::Incomplete; assert_eq!( format!("{}", err), diff --git a/tests/test_server.rs b/tests/test_server.rs index 8d682e12..43e3966d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,7 +11,7 @@ use actix_net::server::Server; use actix_web::{client, test, HttpMessage}; use futures::future; -use actix_http::{h1, Error, KeepAlive, Request, Response}; +use actix_http::{h1, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { @@ -25,10 +25,11 @@ fn test_h1_v2() { .client_disconnect(1000) .server_hostname("localhost") .server_address(addr) - .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) }).unwrap() .run(); }); + thread::sleep(time::Duration::from_millis(100)); let mut sys = System::new("test"); { @@ -48,7 +49,7 @@ fn test_slow_request() { .bind("test", addr, move || { h1::H1Service::build() .client_timeout(100) - .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -69,7 +70,7 @@ fn test_malformed_request() { .bind("test", addr, move || { h1::H1Service::build() .client_timeout(100) - .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -103,7 +104,7 @@ fn test_content_length() { StatusCode::OK, StatusCode::NOT_FOUND, ]; - future::ok::<_, Error>(Response::new(statuses[indx])) + future::ok::<_, ()>(Response::new(statuses[indx])) }) }).unwrap() .run(); From 8acf9eb98a505eb5f16d72af5424763989599c51 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Oct 2018 10:09:48 -0700 Subject: [PATCH 0743/1635] better keep-alive handling --- src/h1/dispatcher.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 92bec354..d44e687d 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -386,21 +386,13 @@ where if let Some(ref mut timer) = self.ka_timer { match timer.poll() { Ok(Async::Ready(_)) => { - if timer.deadline() >= self.ka_expire { - // check for any outstanding request handling - if self.state.is_empty() && self.messages.is_empty() { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - return Err(DispatchError::DisconnectTimeout); - } else if !self.flags.contains(Flags::STARTED) { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - self.state = - State::SendResponse(Some(OutMessage::Response( - Response::RequestTimeout().finish(), - ))); - } else { + // if we get timer during shutdown, just drop connection + if self.flags.contains(Flags::SHUTDOWN) { + return Err(DispatchError::DisconnectTimeout); + } else if timer.deadline() >= self.ka_expire { + // check for any outstanding response processing + if self.state.is_empty() { + if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); @@ -412,6 +404,14 @@ where } else { return Ok(()); } + } else { + // timeout on first request (slow request) return 408 + trace!("Slow request timeout"); + self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); + self.state = + State::SendResponse(Some(OutMessage::Response( + Response::RequestTimeout().finish(), + ))); } } else if let Some(deadline) = self.config.keep_alive_expire() { timer.reset(deadline) From cfad5bf1f343a472f8dbaf9c6dba525c59cb19b9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 07:47:42 -0700 Subject: [PATCH 0744/1635] enable slow request timeout for h2 dispatcher --- src/server/h1.rs | 27 +++++------- src/server/h2.rs | 106 +++++++++++++++++++++++++++------------------ src/server/http.rs | 5 --- src/server/mod.rs | 14 ++---- 4 files changed, 78 insertions(+), 74 deletions(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 4fb730f7..0fb72ef7 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -203,7 +203,7 @@ where #[inline] pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { // check connection keep-alive - self.poll_keep_alive()?; + self.poll_keepalive()?; // shutdown if self.flags.contains(Flags::SHUTDOWN) { @@ -277,23 +277,21 @@ where } /// keep-alive timer. returns `true` is keep-alive, otherwise drop - fn poll_keep_alive(&mut self) -> Result<(), HttpDispatchError> { + fn poll_keepalive(&mut self) -> Result<(), HttpDispatchError> { if let Some(ref mut timer) = self.ka_timer { match timer.poll() { Ok(Async::Ready(_)) => { + // if we get timer during shutdown, just drop connection + if self.flags.contains(Flags::SHUTDOWN) { + let io = self.stream.get_mut(); + let _ = IoStream::set_linger(io, Some(Duration::from_secs(0))); + let _ = IoStream::shutdown(io, Shutdown::Both); + return Err(HttpDispatchError::ShutdownTimeout); + } if timer.deadline() >= self.ka_expire { // check for any outstanding request handling if self.tasks.is_empty() { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - let io = self.stream.get_mut(); - let _ = IoStream::set_linger( - io, - Some(Duration::from_secs(0)), - ); - let _ = IoStream::shutdown(io, Shutdown::Both); - return Err(HttpDispatchError::ShutdownTimeout); - } else if !self.flags.contains(Flags::STARTED) { + if !self.flags.contains(Flags::STARTED) { // timeout on first request (slow request) return 408 trace!("Slow request timeout"); self.flags @@ -315,9 +313,8 @@ where return Ok(()); } } - } else if let Some(deadline) = self.settings.keep_alive_expire() - { - timer.reset(deadline) + } else if let Some(dl) = self.settings.keep_alive_expire() { + timer.reset(dl) } } else { timer.reset(self.ka_expire) diff --git a/src/server/h2.rs b/src/server/h2.rs index 2fe2fa07..6ad9af70 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -27,7 +27,8 @@ use super::{HttpHandler, HttpHandlerTask, IoStream, Writer}; bitflags! { struct Flags: u8 { - const DISCONNECTED = 0b0000_0010; + const DISCONNECTED = 0b0000_0001; + const SHUTDOWN = 0b0000_0010; } } @@ -42,8 +43,9 @@ where addr: Option, state: State>, tasks: VecDeque>, - keepalive_timer: Option, extensions: Option>, + ka_expire: Instant, + ka_timer: Option, } enum State { @@ -62,6 +64,16 @@ where ) -> Self { let addr = io.peer_addr(); let extensions = io.extensions(); + + // keep-alive timeout + let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { + (delay.deadline(), Some(delay)) + } else if let Some(delay) = settings.keep_alive_timer() { + (delay.deadline(), Some(delay)) + } else { + (settings.now(), None) + }; + Http2 { flags: Flags::empty(), tasks: VecDeque::new(), @@ -72,14 +84,14 @@ where addr, settings, extensions, - keepalive_timer, + ka_expire, + ka_timer, } } pub(crate) fn shutdown(&mut self) { self.state = State::Empty; self.tasks.clear(); - self.keepalive_timer.take(); } pub fn settings(&self) -> &ServiceConfig { @@ -87,21 +99,16 @@ where } pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { + self.poll_keepalive()?; + // server if let State::Connection(ref mut conn) = self.state { - // keep-alive timer - if let Some(ref mut timeout) = self.keepalive_timer { - match timeout.poll() { - Ok(Async::Ready(_)) => { - trace!("Keep-alive timeout, close connection"); - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => (), - Err(_) => unreachable!(), - } - } - loop { + // shutdown connection + if self.flags.contains(Flags::SHUTDOWN) { + return conn.poll_close().map_err(|e| e.into()); + } + let mut not_ready = true; let disconnected = self.flags.contains(Flags::DISCONNECTED); @@ -216,8 +223,12 @@ where not_ready = false; let (parts, body) = req.into_parts(); - // stop keepalive timer - self.keepalive_timer.take(); + // update keep-alive expire + if self.ka_timer.is_some() { + if let Some(expire) = self.settings.keep_alive_expire() { + self.ka_expire = expire; + } + } self.tasks.push_back(Entry::new( parts, @@ -228,36 +239,14 @@ where self.extensions.clone(), )); } - Ok(Async::NotReady) => { - // start keep-alive timer - if self.tasks.is_empty() { - if self.settings.keep_alive_enabled() { - if self.keepalive_timer.is_none() { - if let Some(ka) = self.settings.keep_alive() { - trace!("Start keep-alive timer"); - let mut timeout = - Delay::new(Instant::now() + ka); - // register timeout - let _ = timeout.poll(); - self.keepalive_timer = Some(timeout); - } - } - } else { - // keep-alive disable, drop connection - return conn.poll_close().map_err(|e| e.into()); - } - } else { - // keep-alive unset, rely on operating system - return Ok(Async::NotReady); - } - } + Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { trace!("Connection error: {}", err); - self.flags.insert(Flags::DISCONNECTED); + self.flags.insert(Flags::SHUTDOWN); for entry in &mut self.tasks { entry.task.disconnected() } - self.keepalive_timer.take(); + continue; } } } @@ -289,6 +278,37 @@ where self.poll() } + + /// keep-alive timer. returns `true` is keep-alive, otherwise drop + fn poll_keepalive(&mut self) -> Result<(), HttpDispatchError> { + if let Some(ref mut timer) = self.ka_timer { + match timer.poll() { + Ok(Async::Ready(_)) => { + // if we get timer during shutdown, just drop connection + if self.flags.contains(Flags::SHUTDOWN) { + return Err(HttpDispatchError::ShutdownTimeout); + } + if timer.deadline() >= self.ka_expire { + // check for any outstanding request handling + if self.tasks.is_empty() { + return Err(HttpDispatchError::ShutdownTimeout); + } else if let Some(dl) = self.settings.keep_alive_expire() { + timer.reset(dl) + } + } else { + timer.reset(self.ka_expire) + } + } + Ok(Async::NotReady) => (), + Err(e) => { + error!("Timer error {:?}", e); + return Err(HttpDispatchError::Unknown); + } + } + } + + Ok(()) + } } bitflags! { diff --git a/src/server/http.rs b/src/server/http.rs index 6a7790c1..9ecd4a5d 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -197,11 +197,6 @@ where } /// Disable `HTTP/2` support - // #[doc(hidden)] - // #[deprecated( - // since = "0.7.4", - // note = "please use acceptor service with proper ServerFlags parama" - // )] pub fn no_http2(mut self) -> Self { self.no_http2 = true; self diff --git a/src/server/mod.rs b/src/server/mod.rs index 3277dba5..8d719516 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -12,7 +12,7 @@ //! to serve incoming HTTP requests. //! //! As the server uses worker pool, the factory function is restricted to trait bounds -//! `Sync + Send + 'static` so that each worker would be able to accept Application +//! `Send + Clone + 'static` so that each worker would be able to accept Application //! without a need for synchronization. //! //! If you wish to share part of state among all workers you should @@ -29,13 +29,9 @@ //! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html) //! that describes how HTTP Server accepts connections. //! -//! For `bind` and `listen` there are corresponding `bind_with` and `listen_with` that accepts +//! For `bind` and `listen` there are corresponding `bind_ssl|tls|rustls` and `listen_ssl|tls|rustls` that accepts //! these services. //! -//! By default, acceptor would work with both HTTP2 and HTTP1 protocols. -//! But it can be controlled using [ServerFlags](struct.ServerFlags.html) which -//! can be supplied when creating `AcceptorService`. -//! //! **NOTE:** `native-tls` doesn't support `HTTP2` yet //! //! ## Signal handling and shutdown @@ -87,17 +83,13 @@ //! // load ssl keys //! let config = load_ssl(); //! -//! // Create acceptor service for only HTTP1 protocol -//! // You can use ::new(config) to leave defaults -//! let acceptor = server::RustlsAcceptor::with_flags(config, actix_web::server::ServerFlags::HTTP1); -//! //! // create and start server at once //! server::new(|| { //! App::new() //! // register simple handler, handle all methods //! .resource("/index.html", |r| r.f(index)) //! })) -//! }).bind_with("127.0.0.1:8080", acceptor) +//! }).bind_rustls("127.0.0.1:8443", config) //! .unwrap() //! .start(); //! From 30db78c19cf8c3e65a4cfefdeb6f8ccf15cec921 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 07:55:01 -0700 Subject: [PATCH 0745/1635] use TakeItem instead of TakeRequest --- src/h1/mod.rs | 3 +- src/h1/service.rs | 81 ++--------------------------------------------- tests/test_ws.rs | 3 +- 3 files changed, 6 insertions(+), 81 deletions(-) diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 266ebf39..634136a4 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -6,5 +6,6 @@ mod encoder; mod service; pub use self::codec::{Codec, InMessage, OutMessage}; +pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; -pub use self::service::{H1Service, H1ServiceHandler, TakeRequest}; +pub use self::service::{H1Service, H1ServiceHandler}; diff --git a/src/h1/service.rs b/src/h1/service.rs index de24f52c..a7261df1 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -2,17 +2,15 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::net; -use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; -use futures::{future, Async, Future, Poll, Stream}; +use futures::{Async, Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; use config::{KeepAlive, ServiceConfig}; -use error::{DispatchError, ParseError}; +use error::DispatchError; use request::Request; use response::Response; -use super::codec::{Codec, InMessage}; use super::dispatcher::Dispatcher; /// `NewService` implementation for HTTP1 transport @@ -257,78 +255,3 @@ where Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } - -/// `NewService` that implements, read one request from framed object feature. -pub struct TakeRequest { - _t: PhantomData, -} - -impl TakeRequest { - /// Create new `TakeRequest` instance. - pub fn new() -> Self { - TakeRequest { _t: PhantomData } - } -} - -impl NewService for TakeRequest -where - T: AsyncRead + AsyncWrite, -{ - type Request = Framed; - type Response = (Option, Framed); - type Error = ParseError; - type InitError = (); - type Service = TakeRequestService; - type Future = future::FutureResult; - - fn new_service(&self) -> Self::Future { - future::ok(TakeRequestService { _t: PhantomData }) - } -} - -/// `NewService` that implements, read one request from framed object feature. -pub struct TakeRequestService { - _t: PhantomData, -} - -impl Service for TakeRequestService -where - T: AsyncRead + AsyncWrite, -{ - type Request = Framed; - type Response = (Option, Framed); - type Error = ParseError; - type Future = TakeRequestServiceResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, framed: Self::Request) -> Self::Future { - TakeRequestServiceResponse { - framed: Some(framed), - } - } -} - -pub struct TakeRequestServiceResponse -where - T: AsyncRead + AsyncWrite, -{ - framed: Option>, -} - -impl Future for TakeRequestServiceResponse -where - T: AsyncRead + AsyncWrite, -{ - type Item = (Option, Framed); - type Error = ParseError; - - fn poll(&mut self) -> Poll { - match self.framed.as_mut().unwrap().poll()? { - Async::Ready(item) => Ok(Async::Ready((item, self.framed.take().unwrap()))), - Async::NotReady => Ok(Async::NotReady), - } - } -} diff --git a/tests/test_ws.rs b/tests/test_ws.rs index a2a18ff2..8a109874 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -12,6 +12,7 @@ use actix_net::codec::Framed; use actix_net::framed::IntoFramed; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use actix_net::stream::TakeItem; use actix_web::{test, ws as web_ws}; use bytes::Bytes; use futures::future::{ok, Either}; @@ -36,7 +37,7 @@ fn test_simple() { Server::new() .bind("test", addr, move || { IntoFramed::new(|| h1::Codec::new(false)) - .and_then(h1::TakeRequest::new().map_err(|_| ())) + .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request if let Some(h1::InMessage::MessageWithPayload(req)) = req { From 431e33acb2a73245dd7d99e9876d5ee37028c951 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 10:14:29 -0700 Subject: [PATCH 0746/1635] add Date header to response --- src/config.rs | 140 +++++++++++++++++++++++++------------------ src/h1/codec.rs | 15 ++--- src/h1/dispatcher.rs | 2 +- src/lib.rs | 9 +-- 4 files changed, 91 insertions(+), 75 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4e85044f..2e14a33e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,7 +48,7 @@ struct Inner { client_timeout: u64, client_disconnect: u64, ka_enabled: bool, - date: UnsafeCell<(bool, Date)>, + timer: DateService, } impl Clone for ServiceConfig { @@ -78,7 +78,7 @@ impl ServiceConfig { ka_enabled, client_timeout, client_disconnect, - date: UnsafeCell::new((false, Date::new())), + timer: DateService::with(Duration::from_millis(500)), })) } @@ -99,17 +99,14 @@ impl ServiceConfig { self.0.ka_enabled } - fn update_date(&self) { - // Unsafe: WorkerSetting is !Sync and !Send - unsafe { (*self.0.date.get()).0 = false }; - } - #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { - Some(Delay::new(self.now() + Duration::from_millis(delay))) + Some(Delay::new( + self.0.timer.now() + Duration::from_millis(delay), + )) } else { None } @@ -119,7 +116,7 @@ impl ServiceConfig { pub fn client_timer_expire(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) + Some(self.0.timer.now() + Duration::from_millis(delay)) } else { None } @@ -129,7 +126,7 @@ impl ServiceConfig { pub fn client_disconnect_timer(&self) -> Option { let delay = self.0.client_disconnect; if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) + Some(self.0.timer.now() + Duration::from_millis(delay)) } else { None } @@ -139,7 +136,7 @@ impl ServiceConfig { /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(Delay::new(self.now() + ka)) + Some(Delay::new(self.0.timer.now() + ka)) } else { None } @@ -148,57 +145,23 @@ impl ServiceConfig { /// Keep-alive expire time pub fn keep_alive_expire(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(self.now() + ka) + Some(self.0.timer.now() + ka) } else { None } } - pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) { - // Unsafe: WorkerSetting is !Sync and !Send - let date_bytes = unsafe { - let date = &mut (*self.0.date.get()); - if !date.0 { - date.1.update(); - date.0 = true; - - // periodic date update - let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.update_date(); - future::ok(()) - })); - } - &date.1.bytes - }; - if full { - let mut buf: [u8; 39] = [0; 39]; - buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(date_bytes); - buf[35..].copy_from_slice(b"\r\n\r\n"); - dst.extend_from_slice(&buf); - } else { - dst.extend_from_slice(date_bytes); - } - } - #[inline] pub(crate) fn now(&self) -> Instant { - unsafe { - let date = &mut (*self.0.date.get()); - if !date.0 { - date.1.update(); - date.0 = true; + self.0.timer.now() + } - // periodic date update - let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.update_date(); - future::ok(()) - })); - } - date.1.current - } + pub(crate) fn set_date(&self, dst: &mut BytesMut) { + let mut buf: [u8; 39] = [0; 39]; + buf[..6].copy_from_slice(b"date: "); + buf[6..35].copy_from_slice(&self.0.timer.date().bytes); + buf[35..].copy_from_slice(b"\r\n\r\n"); + dst.extend_from_slice(&buf); } } @@ -311,7 +274,6 @@ impl ServiceConfigBuilder { } struct Date { - current: Instant, bytes: [u8; DATE_VALUE_LENGTH], pos: usize, } @@ -319,7 +281,6 @@ struct Date { impl Date { fn new() -> Date { let mut date = Date { - current: Instant::now(), bytes: [0; DATE_VALUE_LENGTH], pos: 0, }; @@ -328,7 +289,6 @@ impl Date { } fn update(&mut self) { self.pos = 0; - self.current = Instant::now(); write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); } } @@ -342,6 +302,68 @@ impl fmt::Write for Date { } } +#[derive(Clone)] +struct DateService(Rc); + +struct DateServiceInner { + interval: Duration, + current: UnsafeCell>, +} + +impl DateServiceInner { + fn new(interval: Duration) -> Self { + DateServiceInner { + interval, + current: UnsafeCell::new(None), + } + } + + fn get_ref(&self) -> &Option<(Date, Instant)> { + unsafe { &*self.current.get() } + } + + fn reset(&self) { + unsafe { (&mut *self.current.get()).take() }; + } + + fn update(&self) { + let now = Instant::now(); + let date = Date::new(); + *(unsafe { &mut *self.current.get() }) = Some((date, now)); + } +} + +impl DateService { + fn with(resolution: Duration) -> Self { + DateService(Rc::new(DateServiceInner::new(resolution))) + } + + fn check_date(&self) { + if self.0.get_ref().is_none() { + self.0.update(); + + // periodic date update + let s = self.clone(); + spawn(sleep(Duration::from_millis(500)).then(move |_| { + s.0.reset(); + future::ok(()) + })); + } + } + + fn now(&self) -> Instant { + self.check_date(); + self.0.get_ref().as_ref().unwrap().1 + } + + fn date(&self) -> &Date { + self.check_date(); + + let item = self.0.get_ref().as_ref().unwrap(); + &item.0 + } +} + #[cfg(test)] mod tests { use super::*; @@ -360,9 +382,9 @@ mod tests { let _ = rt.block_on(future::lazy(|| { let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, true); + settings.set_date(&mut buf1); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, true); + settings.set_date(&mut buf2); assert_eq!(buf1, buf2); future::ok::<_, ()>(()) })); diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 247b0f01..d0faad43 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -7,6 +7,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; use super::encoder::{ResponseEncoder, ResponseLength}; use body::Body; +use config::ServiceConfig; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; @@ -48,6 +49,7 @@ pub enum InMessage { /// HTTP/1 Codec pub struct Codec { + config: ServiceConfig, decoder: RequestDecoder, payload: Option, version: Version, @@ -62,20 +64,19 @@ impl Codec { /// Create HTTP/1 codec. /// /// `keepalive_enabled` how response `connection` header get generated. - pub fn new(keepalive_enabled: bool) -> Self { - Codec::with_pool(RequestPool::pool(), keepalive_enabled) + pub fn new(config: ServiceConfig) -> Self { + Codec::with_pool(RequestPool::pool(), config) } /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool( - pool: &'static RequestPool, keepalive_enabled: bool, - ) -> Self { - let flags = if keepalive_enabled { + pub(crate) fn with_pool(pool: &'static RequestPool, config: ServiceConfig) -> Self { + let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE_ENABLED } else { Flags::empty() }; Codec { + config, decoder: RequestDecoder::with_pool(pool), payload: None, version: Version::HTTP_11, @@ -217,7 +218,7 @@ impl Codec { // optimized date header, set_date writes \r\n if !has_date { - // self.settings.set_date(&mut buffer, true); + self.config.set_date(buffer); buffer.extend_from_slice(b"\r\n"); } else { // msg eof diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index d44e687d..c8ce7d65 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -97,7 +97,7 @@ where } else { Flags::FLUSHED }; - let framed = Framed::new(stream, Codec::new(keepalive)); + let framed = Framed::new(stream, Codec::new(config.clone())); let (ka_expire, ka_timer) = if let Some(delay) = timeout { (delay.deadline(), Some(delay)) diff --git a/src/lib.rs b/src/lib.rs index 8a7bcfa4..85bf9c2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,17 +48,10 @@ //! //! ## Features //! -//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols +//! * Supported *HTTP/1.x* protocol //! * Streaming and pipelining //! * Keep-alive and slow requests handling //! * `WebSockets` server/client -//! * Transparent content compression/decompression (br, gzip, deflate) -//! * Configurable request routing -//! * Graceful server shutdown -//! * Multipart streams -//! * SSL support with OpenSSL or `native-tls` -//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) -//! * Built on top of [Actix actor framework](https://github.com/actix/actix) //! * Supported Rust version: 1.26 or later //! //! ## Package feature From 03d988b898ab976bdf04658209c35239b6c4b1e7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 10:16:19 -0700 Subject: [PATCH 0747/1635] refactor date rendering --- src/server/settings.rs | 66 ++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/src/server/settings.rs b/src/server/settings.rs index 9b27ed5e..bafffb5f 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -1,4 +1,4 @@ -use std::cell::{RefCell, RefMut, UnsafeCell}; +use std::cell::{Cell, RefCell, RefMut}; use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; @@ -139,7 +139,7 @@ struct Inner { bytes: Rc, messages: &'static RequestPool, node: RefCell>, - date: UnsafeCell<(bool, Date)>, + date: Cell>, } impl Clone for ServiceConfig { @@ -174,7 +174,7 @@ impl ServiceConfig { bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), node: RefCell::new(Node::head()), - date: UnsafeCell::new((false, Date::new())), + date: Cell::new(None), })) } @@ -214,11 +214,6 @@ impl ServiceConfig { pub(crate) fn get_request(&self) -> Request { RequestPool::get(self.0.messages) } - - fn update_date(&self) { - // Unsafe: WorkerSetting is !Sync and !Send - unsafe { (*self.0.date.get()).0 = false }; - } } impl ServiceConfig { @@ -272,51 +267,39 @@ impl ServiceConfig { } } - pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) { - // Unsafe: WorkerSetting is !Sync and !Send - let date_bytes = unsafe { - let date = &mut (*self.0.date.get()); - if !date.0 { - date.1.update(); - date.0 = true; + fn check_date(&self) { + if unsafe { &*self.0.date.as_ptr() }.is_none() { + self.0.date.set(Some(Date::new())); + + // periodic date update + let s = self.clone(); + spawn(sleep(Duration::from_millis(500)).then(move |_| { + s.0.date.set(None); + future::ok(()) + })); + } + } + + pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) { + self.check_date(); + + let date = &unsafe { &*self.0.date.as_ptr() }.as_ref().unwrap().bytes; - // periodic date update - let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.update_date(); - future::ok(()) - })); - } - &date.1.bytes - }; if full { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(date_bytes); + buf[6..35].copy_from_slice(date); buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } else { - dst.extend_from_slice(date_bytes); + dst.extend_from_slice(date); } } #[inline] pub(crate) fn now(&self) -> Instant { - unsafe { - let date = &mut (*self.0.date.get()); - if !date.0 { - date.1.update(); - date.0 = true; - - // periodic date update - let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.update_date(); - future::ok(()) - })); - } - date.1.current - } + self.check_date(); + unsafe { &*self.0.date.as_ptr() }.as_ref().unwrap().current } } @@ -435,6 +418,7 @@ impl ServiceConfigBuilder { } } +#[derive(Copy, Clone)] struct Date { current: Instant, bytes: [u8; DATE_VALUE_LENGTH], From 805e7a4cd042f305175a8436ed7c0b6a8802ff99 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 15:24:51 -0700 Subject: [PATCH 0748/1635] impl response body support --- src/h1/codec.rs | 24 +++- src/h1/dispatcher.rs | 134 ++++++++++-------- src/h1/encoder.rs | 46 ++++--- tests/test_server.rs | 314 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 427 insertions(+), 91 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index d0faad43..16965768 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,7 +6,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; use super::encoder::{ResponseEncoder, ResponseLength}; -use body::Body; +use body::{Binary, Body}; use config::ServiceConfig; use error::ParseError; use helpers; @@ -26,12 +26,13 @@ bitflags! { const AVERAGE_HEADER_SIZE: usize = 30; +#[derive(Debug)] /// Http response pub enum OutMessage { /// Http response message Response(Response), /// Payload chunk - Payload(Bytes), + Payload(Option), } /// Incoming http/1 request @@ -151,6 +152,7 @@ impl Codec { buffer.extend_from_slice(reason); // content length + let mut len_is_set = true; match self.te.length { ResponseLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") @@ -167,6 +169,10 @@ impl Codec { buffer.extend_from_slice(b"\r\n"); } ResponseLength::None => buffer.extend_from_slice(b"\r\n"), + ResponseLength::HeaderOrZero => { + len_is_set = false; + buffer.extend_from_slice(b"\r\n") + } } // write headers @@ -179,6 +185,9 @@ impl Codec { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match self.te.length { ResponseLength::None => (), + ResponseLength::HeaderOrZero => { + len_is_set = true; + } _ => continue, }, DATE => { @@ -215,11 +224,13 @@ impl Codec { unsafe { buffer.advance_mut(pos); } + if !len_is_set { + buffer.extend_from_slice(b"content-length: 0\r\n") + } // optimized date header, set_date writes \r\n if !has_date { self.config.set_date(buffer); - buffer.extend_from_slice(b"\r\n"); } else { // msg eof buffer.extend_from_slice(b"\r\n"); @@ -272,8 +283,11 @@ impl Encoder for Codec { OutMessage::Response(res) => { self.encode_response(res, dst)?; } - OutMessage::Payload(bytes) => { - dst.extend_from_slice(&bytes); + OutMessage::Payload(Some(bytes)) => { + self.te.encode(bytes.as_ref(), dst)?; + } + OutMessage::Payload(None) => { + self.te.encode_eof(dst)?; } } Ok(()) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index c8ce7d65..c2ce1203 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -12,7 +12,7 @@ use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; -use body::Body; +use body::{Body, BodyStream}; use config::ServiceConfig; use error::DispatchError; use request::Request; @@ -61,9 +61,8 @@ enum Message { enum State { None, ServiceCall(S::Future), - SendResponse(Option), - SendResponseWithPayload(Option<(OutMessage, Body)>), - Payload(Body), + SendResponse(Option<(OutMessage, Body)>), + SendPayload(Option, Option), } impl State { @@ -99,6 +98,7 @@ where }; let framed = Framed::new(stream, Codec::new(config.clone())); + // keep-alive timer let (ka_expire, ka_timer) = if let Some(delay) = timeout { (delay.deadline(), Some(delay)) } else if let Some(delay) = config.keep_alive_timer() { @@ -174,59 +174,32 @@ where break if let Some(msg) = self.messages.pop_front() { match msg { Message::Item(req) => Some(self.handle_request(req)?), - Message::Error(res) => Some(State::SendResponse(Some( + Message::Error(res) => Some(State::SendResponse(Some(( OutMessage::Response(res), - ))), + Body::Empty, + )))), } } else { None }; }, - State::Payload(ref mut _body) => unimplemented!(), - State::ServiceCall(ref mut fut) => match fut - .poll() - .map_err(DispatchError::Service)? - { - Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); - let body = res.replace_body(Body::Empty); - if body.is_empty() { - Some(State::SendResponse(Some(OutMessage::Response(res)))) - } else { - Some(State::SendResponseWithPayload(Some(( + // call inner service + State::ServiceCall(ref mut fut) => { + match fut.poll().map_err(DispatchError::Service)? { + Async::Ready(mut res) => { + self.framed.get_codec_mut().prepare_te(&mut res); + let body = res.replace_body(Body::Empty); + Some(State::SendResponse(Some(( OutMessage::Response(res), body, )))) } - } - Async::NotReady => None, - }, - State::SendResponse(ref mut item) => { - let msg = item.take().expect("SendResponse is empty"); - match self.framed.start_send(msg) { - Ok(AsyncSink::Ready) => { - self.flags.set( - Flags::KEEPALIVE, - self.framed.get_codec().keepalive(), - ); - self.flags.remove(Flags::FLUSHED); - Some(State::None) - } - Ok(AsyncSink::NotReady(msg)) => { - *item = Some(msg); - return Ok(()); - } - Err(err) => { - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); - } - return Err(DispatchError::Io(err)); - } + Async::NotReady => None, } } - State::SendResponseWithPayload(ref mut item) => { - let (msg, body) = - item.take().expect("SendResponseWithPayload is empty"); + // send respons + State::SendResponse(ref mut item) => { + let (msg, body) = item.take().expect("SendResponse is empty"); match self.framed.start_send(msg) { Ok(AsyncSink::Ready) => { self.flags.set( @@ -234,7 +207,16 @@ where self.framed.get_codec().keepalive(), ); self.flags.remove(Flags::FLUSHED); - Some(State::Payload(body)) + match body { + Body::Empty => Some(State::None), + Body::Binary(bin) => Some(State::SendPayload( + None, + Some(OutMessage::Payload(bin.into())), + )), + Body::Streaming(stream) => { + Some(State::SendPayload(Some(stream), None)) + } + } } Ok(AsyncSink::NotReady(msg)) => { *item = Some((msg, body)); @@ -248,6 +230,48 @@ where } } } + // Send payload + State::SendPayload(ref mut stream, ref mut bin) => { + if let Some(item) = bin.take() { + match self.framed.start_send(item) { + Ok(AsyncSink::Ready) => { + self.flags.remove(Flags::FLUSHED); + } + Ok(AsyncSink::NotReady(item)) => { + *bin = Some(item); + return Ok(()); + } + Err(err) => return Err(DispatchError::Io(err)), + } + } + if let Some(ref mut stream) = stream { + match stream.poll() { + Ok(Async::Ready(Some(item))) => match self + .framed + .start_send(OutMessage::Payload(Some(item.into()))) + { + Ok(AsyncSink::Ready) => { + self.flags.remove(Flags::FLUSHED); + continue; + } + Ok(AsyncSink::NotReady(msg)) => { + *bin = Some(msg); + return Ok(()); + } + Err(err) => return Err(DispatchError::Io(err)), + }, + Ok(Async::Ready(None)) => Some(State::SendPayload( + None, + Some(OutMessage::Payload(None)), + )), + Ok(Async::NotReady) => return Ok(()), + // Err(err) => return Err(DispatchError::Io(err)), + Err(_) => return Err(DispatchError::Unknown), + } + } else { + Some(State::None) + } + } }; match state { @@ -275,19 +299,13 @@ where Async::Ready(mut res) => { self.framed.get_codec_mut().prepare_te(&mut res); let body = res.replace_body(Body::Empty); - if body.is_empty() { - Ok(State::SendResponse(Some(OutMessage::Response(res)))) - } else { - Ok(State::SendResponseWithPayload(Some(( - OutMessage::Response(res), - body, - )))) - } + Ok(State::SendResponse(Some((OutMessage::Response(res), body)))) } Async::NotReady => Ok(State::ServiceCall(task)), } } + /// Process one incoming message fn one_message(&mut self, msg: InMessage) -> Result<(), DispatchError> { self.flags.insert(Flags::STARTED); @@ -408,10 +426,12 @@ where // timeout on first request (slow request) return 408 trace!("Slow request timeout"); self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - self.state = - State::SendResponse(Some(OutMessage::Response( + self.state = State::SendResponse(Some(( + OutMessage::Response( Response::RequestTimeout().finish(), - ))); + ), + Body::Empty, + ))); } } else if let Some(deadline) = self.config.keep_alive_expire() { timer.reset(deadline) diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 1544b240..6e8d44ce 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -17,9 +17,13 @@ use response::Response; #[derive(Debug)] pub(crate) enum ResponseLength { Chunked, + /// Content length is 0 Zero, + /// Check if headers contains length or write 0 + HeaderOrZero, Length(usize), Length64(u64), + /// Do no set content-length None, } @@ -41,6 +45,16 @@ impl Default for ResponseEncoder { } impl ResponseEncoder { + /// Encode message + pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { + self.te.encode(msg, buf) + } + + /// Encode eof + pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { + self.te.encode_eof(buf) + } + pub fn update(&mut self, resp: &mut Response, head: bool, version: Version) { self.head = head; @@ -63,17 +77,13 @@ impl ResponseEncoder { let transfer = match resp.body() { Body::Empty => { - if !self.head { - self.length = match resp.status() { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => ResponseLength::None, - _ => ResponseLength::Zero, - }; - } else { - self.length = ResponseLength::Zero; - } + self.length = match resp.status() { + StatusCode::NO_CONTENT + | StatusCode::CONTINUE + | StatusCode::SWITCHING_PROTOCOLS + | StatusCode::PROCESSING => ResponseLength::None, + _ => ResponseLength::HeaderOrZero, + }; TransferEncoding::empty() } Body::Binary(_) => { @@ -253,16 +263,22 @@ impl TransferEncoding { /// Encode eof. Return `EOF` state of encoder #[inline] - pub fn encode_eof(&mut self, buf: &mut BytesMut) -> bool { + pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { match self.kind { - TransferEncodingKind::Eof => true, - TransferEncodingKind::Length(rem) => rem == 0, + TransferEncodingKind::Eof => Ok(()), + TransferEncodingKind::Length(rem) => { + if rem != 0 { + Err(io::Error::new(io::ErrorKind::UnexpectedEof, "")) + } else { + Ok(()) + } + } TransferEncodingKind::Chunked(ref mut eof) => { if !*eof { *eof = true; buf.extend_from_slice(b"0\r\n\r\n"); } - true + Ok(()) } } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 43e3966d..e8617609 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -2,6 +2,7 @@ extern crate actix; extern crate actix_http; extern crate actix_net; extern crate actix_web; +extern crate bytes; extern crate futures; use std::{io::Read, io::Write, net, thread, time}; @@ -9,9 +10,11 @@ use std::{io::Read, io::Write, net, thread, time}; use actix::System; use actix_net::server::Server; use actix_web::{client, test, HttpMessage}; -use futures::future; +use bytes::Bytes; +use futures::future::{self, ok}; +use futures::stream::once; -use actix_http::{h1, KeepAlive, Request, Response}; +use actix_http::{h1, Body, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { @@ -33,7 +36,7 @@ fn test_h1_v2() { let mut sys = System::new("test"); { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) + let req = client::ClientRequest::get(format!("http://{}/", addr)) .finish() .unwrap(); let response = sys.block_on(req.send()).unwrap(); @@ -68,9 +71,7 @@ fn test_malformed_request() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - h1::H1Service::build() - .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())) }).unwrap() .run(); }); @@ -117,21 +118,306 @@ fn test_content_length() { let mut sys = System::new("test"); { for i in 0..4 { - let req = - client::ClientRequest::get(format!("http://{}/{}", addr, i).as_str()) - .finish() - .unwrap(); + let req = client::ClientRequest::get(format!("http://{}/{}", addr, i)) + .finish() + .unwrap(); let response = sys.block_on(req.send()).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = - client::ClientRequest::get(format!("http://{}/{}", addr, i).as_str()) - .finish() - .unwrap(); + let req = client::ClientRequest::get(format!("http://{}/{}", addr, i)) + .finish() + .unwrap(); let response = sys.block_on(req.send()).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } } + +#[test] +fn test_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + let data = data.clone(); + h1::H1Service::new(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(builder.body(data.clone())) + }) + }) + .unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(400)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data2)); +} + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_body() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_head_empty() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).finish()) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::head(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + { + println!("RESP: {:?}", response); + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_head_binary() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::head(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_head_binary2() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::head(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[test] +fn test_body_length() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .content_length(STR.len() as u64) + .body(Body::Streaming(Box::new(body))), + ) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_body_chunked_explicit() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .chunked() + .body(Body::Streaming(Box::new(body))), + ) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_body_chunked_implicit() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::new(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().body(Body::Streaming(Box::new(body)))) + }) + }).unwrap() + .run() + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} From 4e7fac08b9675dad2965611e1c33cc734603de4f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 15:30:59 -0700 Subject: [PATCH 0749/1635] do not override content-length header --- src/server/h1writer.rs | 14 +++++++++++--- src/server/output.rs | 16 +++++++--------- tests/test_server.rs | 3 +-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs index c27a4c44..97ce6dff 100644 --- a/src/server/h1writer.rs +++ b/src/server/h1writer.rs @@ -176,13 +176,11 @@ impl Writer for H1Writer { buffer.extend_from_slice(reason); // content length + let mut len_is_set = true; match info.length { ResponseLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - ResponseLength::Zero => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") - } ResponseLength::Length(len) => { helpers::write_content_length(len, &mut buffer) } @@ -191,6 +189,10 @@ impl Writer for H1Writer { write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } + ResponseLength::Zero => { + len_is_set = false; + buffer.extend_from_slice(b"\r\n"); + } ResponseLength::None => buffer.extend_from_slice(b"\r\n"), } if let Some(ce) = info.content_encoding { @@ -212,6 +214,9 @@ impl Writer for H1Writer { }, CONTENT_LENGTH => match info.length { ResponseLength::None => (), + ResponseLength::Zero => { + len_is_set = true; + } _ => continue, }, DATE => { @@ -248,6 +253,9 @@ impl Writer for H1Writer { unsafe { buffer.advance_mut(pos); } + if !len_is_set { + buffer.extend_from_slice(b"content-length: 0\r\n") + } // optimized date header, set_date writes \r\n if !has_date { diff --git a/src/server/output.rs b/src/server/output.rs index 70c24fac..35f3c7a4 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -191,15 +191,13 @@ impl Output { let transfer = match resp.body() { Body::Empty => { - if !info.head { - info.length = match resp.status() { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => ResponseLength::None, - _ => ResponseLength::Zero, - }; - } + info.length = match resp.status() { + StatusCode::NO_CONTENT + | StatusCode::CONTINUE + | StatusCode::SWITCHING_PROTOCOLS + | StatusCode::PROCESSING => ResponseLength::None, + _ => ResponseLength::Zero, + }; *self = Output::Empty(buf); return; } diff --git a/tests/test_server.rs b/tests/test_server.rs index cb19cfed..f3c9bf9d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1358,8 +1358,8 @@ fn test_ssl_handshake_timeout() { #[test] fn test_content_length() { - use http::StatusCode; use actix_web::http::header::{HeaderName, HeaderValue}; + use http::StatusCode; let mut srv = test::TestServer::new(move |app| { app.resource("/{status}", |r| { @@ -1398,4 +1398,3 @@ fn test_content_length() { assert_eq!(response.headers().get(&header), Some(&value)); } } - From 3984ad45dfb5f13a9e645b0f48265c5ec5b6834f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 15:33:38 -0700 Subject: [PATCH 0750/1635] separate ResponseLength::Zero is not needed --- src/h1/codec.rs | 9 +++------ src/h1/encoder.rs | 6 ++---- tests/test_server.rs | 7 ++++++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 16965768..8f97d677 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -158,7 +158,8 @@ impl Codec { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } ResponseLength::Zero => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n") + len_is_set = false; + buffer.extend_from_slice(b"\r\n") } ResponseLength::Length(len) => { helpers::write_content_length(len, buffer) @@ -169,10 +170,6 @@ impl Codec { buffer.extend_from_slice(b"\r\n"); } ResponseLength::None => buffer.extend_from_slice(b"\r\n"), - ResponseLength::HeaderOrZero => { - len_is_set = false; - buffer.extend_from_slice(b"\r\n") - } } // write headers @@ -185,7 +182,7 @@ impl Codec { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match self.te.length { ResponseLength::None => (), - ResponseLength::HeaderOrZero => { + ResponseLength::Zero => { len_is_set = true; } _ => continue, diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 6e8d44ce..ea11f11f 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -17,10 +17,8 @@ use response::Response; #[derive(Debug)] pub(crate) enum ResponseLength { Chunked, - /// Content length is 0 - Zero, /// Check if headers contains length or write 0 - HeaderOrZero, + Zero, Length(usize), Length64(u64), /// Do no set content-length @@ -82,7 +80,7 @@ impl ResponseEncoder { | StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS | StatusCode::PROCESSING => ResponseLength::None, - _ => ResponseLength::HeaderOrZero, + _ => ResponseLength::Zero, }; TransferEncoding::empty() } diff --git a/tests/test_server.rs b/tests/test_server.rs index e8617609..3b13b98e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -123,6 +123,12 @@ fn test_content_length() { .unwrap(); let response = sys.block_on(req.send()).unwrap(); assert_eq!(response.headers().get(&header), None); + + let req = client::ClientRequest::head(format!("http://{}/{}", addr, i)) + .finish() + .unwrap(); + let response = sys.block_on(req.send()).unwrap(); + assert_eq!(response.headers().get(&header), None); } for i in 4..6 { @@ -254,7 +260,6 @@ fn test_head_empty() { assert!(response.status().is_success()); { - println!("RESP: {:?}", response); let len = response .headers() .get(http::header::CONTENT_LENGTH) From f99a723643d3e0618068acb2631331e3c25f6dba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 15:52:12 -0700 Subject: [PATCH 0751/1635] add Default impl for ServiceConfig --- src/config.rs | 6 ++++++ tests/test_ws.rs | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 2e14a33e..ff22ea48 100644 --- a/src/config.rs +++ b/src/config.rs @@ -57,6 +57,12 @@ impl Clone for ServiceConfig { } } +impl Default for ServiceConfig { + fn default() -> Self { + Self::new(KeepAlive::Timeout(5), 0, 0) + } +} + impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 8a109874..00ccd155 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -18,7 +18,7 @@ use bytes::Bytes; use futures::future::{ok, Either}; use futures::{Future, Sink, Stream}; -use actix_http::{h1, ws, ResponseError}; +use actix_http::{h1, ws, ResponseError, ServiceConfig}; fn ws_service(req: ws::Message) -> impl Future { match req { @@ -36,7 +36,7 @@ fn test_simple() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - IntoFramed::new(|| h1::Codec::new(false)) + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request From 2b4870e65b70bfd1600eecc3a61f82d437599482 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 16:10:07 -0700 Subject: [PATCH 0752/1635] fix tests on stable --- 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 3b13b98e..f382eafb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -14,7 +14,7 @@ use bytes::Bytes; use futures::future::{self, ok}; use futures::stream::once; -use actix_http::{h1, Body, KeepAlive, Request, Response}; +use actix_http::{h1, http, Body, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { From fd5da5945efe16ef6e7f08027312286cc4c2829b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 21:23:52 -0700 Subject: [PATCH 0753/1635] update appveyor config --- .appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 7addc8c0..4af6cdbf 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -37,4 +37,5 @@ build: false # Equivalent to Travis' `script` phase test_script: - - cargo test --no-default-features --features="flate2-rust" + - cargo clean + - cargo test From 93b1c5fd46465f2e08b661811a28b968fb77de70 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 21:58:37 -0700 Subject: [PATCH 0754/1635] update deps --- .appveyor.yml | 3 ++- CHANGES.md | 2 +- Cargo.toml | 10 ++-------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 7addc8c0..2f0a4a7d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,6 +1,6 @@ environment: global: - PROJECT_NAME: actix + PROJECT_NAME: actix-web matrix: # Stable channel - TARGET: i686-pc-windows-msvc @@ -37,4 +37,5 @@ build: false # Equivalent to Travis' `script` phase test_script: + - cargo clean - cargo test --no-default-features --features="flate2-rust" diff --git a/CHANGES.md b/CHANGES.md index 3c55c3f6..f4f665a8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.9] - 2018-09-x +## [0.7.9] - 2018-10-09 ### Added diff --git a/Cargo.toml b/Cargo.toml index 12f98ac3..2d606cc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] build = "build.rs" [package.metadata.docs.rs] -features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"] +features = ["tls", "ssl", "rust-tls", "session", "brotli", "flate2-c"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } @@ -62,8 +62,7 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.0" -actix-net = { git="https://github.com/actix/actix-net.git" } -#actix-net = { path = "../actix-net" } +actix-net = "0.1.0" base64 = "0.9" bitflags = "1.0" @@ -139,8 +138,3 @@ version_check = "0.1" lto = true opt-level = 3 codegen-units = 1 - -[workspace] -members = [ - "./", -] From cb78d9d41a4063b81b18d1218819740916693821 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Oct 2018 22:04:53 -0700 Subject: [PATCH 0755/1635] use actix-net release --- .appveyor.yml | 2 +- Cargo.toml | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 4af6cdbf..780fdd6b 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,6 +1,6 @@ environment: global: - PROJECT_NAME: actix + PROJECT_NAME: actix-http matrix: # Stable channel - TARGET: i686-pc-windows-msvc diff --git a/Cargo.toml b/Cargo.toml index 4e66ff0e..7bfb82fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,13 +36,14 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.0" -actix-net = { git="https://github.com/actix/actix-net.git" } -#actix-net = { path = "../actix-net" } +actix-net = "0.1.0" +#actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" http = "^0.1.8" httparse = "1.3" +failure = "^0.1.2" log = "0.4" mime = "0.3" rand = "0.5" @@ -55,8 +56,6 @@ lazy_static = "1.0" serde_urlencoded = "^0.5.3" cookie = { version="0.11", features=["percent-encode"] } -failure = "^0.1.2" - # io net2 = "0.2" bytes = "0.4" From c3ad516f56cfa59accd91b7d589a2c8e62969844 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 09:45:24 -0700 Subject: [PATCH 0756/1635] disable shutdown atm --- src/server/channel.rs | 91 ++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/src/server/channel.rs b/src/server/channel.rs index cbbe1a95..1f4ec5b1 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -49,8 +49,8 @@ where T: IoStream, H: HttpHandler + 'static, { - node: Node>, - node_reg: bool, + proto: HttpProtocol, + node: Option>, ka_timeout: Option, } @@ -64,12 +64,8 @@ where HttpChannel { ka_timeout, - node_reg: false, - node: Node::new(HttpProtocol::Unknown( - settings, - io, - BytesMut::with_capacity(8192), - )), + node: None, + proto: HttpProtocol::Unknown(settings, io, BytesMut::with_capacity(8192)), } } } @@ -80,7 +76,9 @@ where H: HttpHandler + 'static, { fn drop(&mut self) { - self.node.remove(); + if let Some(mut node) = self.node.take() { + node.remove() + } } } @@ -98,16 +96,15 @@ where match self.ka_timeout.as_mut().unwrap().poll() { Ok(Async::Ready(_)) => { trace!("Slow request timed out, close connection"); - let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); + let proto = mem::replace(&mut self.proto, HttpProtocol::None); if let HttpProtocol::Unknown(settings, io, buf) = proto { - *self.node.get_mut() = - HttpProtocol::H1(h1::Http1Dispatcher::for_error( - settings, - io, - StatusCode::REQUEST_TIMEOUT, - self.ka_timeout.take(), - buf, - )); + self.proto = HttpProtocol::H1(h1::Http1Dispatcher::for_error( + settings, + io, + StatusCode::REQUEST_TIMEOUT, + self.ka_timeout.take(), + buf, + )); return self.poll(); } return Ok(Async::Ready(())); @@ -117,19 +114,24 @@ where } } - if !self.node_reg { - self.node_reg = true; - let settings = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.settings().clone(), - HttpProtocol::H2(ref mut h2) => h2.settings().clone(), - HttpProtocol::Unknown(ref mut settings, _, _) => settings.clone(), + if self.node.is_none() { + self.node = Some(Node::new(())); + let _ = match self.proto { + HttpProtocol::H1(ref mut h1) => { + self.node.as_mut().map(|n| h1.settings().head().insert(n)) + } + HttpProtocol::H2(ref mut h2) => { + self.node.as_mut().map(|n| h2.settings().head().insert(n)) + } + HttpProtocol::Unknown(ref mut settings, _, _) => { + self.node.as_mut().map(|n| settings.head().insert(n)) + } HttpProtocol::None => unreachable!(), }; - settings.head().insert(&mut self.node); } let mut is_eof = false; - let kind = match self.node.get_mut() { + let kind = match self.proto { HttpProtocol::H1(ref mut h1) => return h1.poll(), HttpProtocol::H2(ref mut h2) => return h2.poll(), HttpProtocol::Unknown(_, ref mut io, ref mut buf) => { @@ -169,11 +171,11 @@ where }; // upgrade to specific http protocol - let proto = mem::replace(self.node.get_mut(), HttpProtocol::None); + let proto = mem::replace(&mut self.proto, HttpProtocol::None); if let HttpProtocol::Unknown(settings, io, buf) = proto { match kind { ProtocolKind::Http1 => { - *self.node.get_mut() = HttpProtocol::H1(h1::Http1Dispatcher::new( + self.proto = HttpProtocol::H1(h1::Http1Dispatcher::new( settings, io, buf, @@ -183,7 +185,7 @@ where return self.poll(); } ProtocolKind::Http2 => { - *self.node.get_mut() = HttpProtocol::H2(h2::Http2::new( + self.proto = HttpProtocol::H2(h2::Http2::new( settings, io, buf.freeze(), @@ -203,8 +205,8 @@ where T: IoStream, H: HttpHandler + 'static, { - node: Node>, - node_reg: bool, + proto: HttpProtocol, + node: Option>, } impl H1Channel @@ -214,14 +216,14 @@ where { pub(crate) fn new(settings: ServiceConfig, io: T) -> H1Channel { H1Channel { - node_reg: false, - node: Node::new(HttpProtocol::H1(h1::Http1Dispatcher::new( + node: None, + proto: HttpProtocol::H1(h1::Http1Dispatcher::new( settings, io, BytesMut::with_capacity(8192), false, None, - ))), + )), } } } @@ -232,7 +234,9 @@ where H: HttpHandler + 'static, { fn drop(&mut self) { - self.node.remove(); + if let Some(mut node) = self.node.take() { + node.remove(); + } } } @@ -245,16 +249,17 @@ where type Error = HttpDispatchError; fn poll(&mut self) -> Poll { - if !self.node_reg { - self.node_reg = true; - let settings = match self.node.get_mut() { - HttpProtocol::H1(ref mut h1) => h1.settings().clone(), + if self.node.is_none() { + self.node = Some(Node::new(())); + match self.proto { + HttpProtocol::H1(ref mut h1) => { + self.node.as_mut().map(|n| h1.settings().head().insert(n)); + } _ => unreachable!(), }; - settings.head().insert(&mut self.node); } - match self.node.get_mut() { + match self.proto { HttpProtocol::H1(ref mut h1) => h1.poll(), _ => unreachable!(), } @@ -276,10 +281,6 @@ impl Node { } } - fn get_mut(&mut self) -> &mut T { - &mut self.element - } - fn insert(&mut self, next_el: &mut Node) { let next: *mut Node = next_el as *const _ as *mut _; From 1407bf4f7f2a0e0ec6302b792eef45acbc4656bf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 10:36:40 -0700 Subject: [PATCH 0757/1635] simplify h1 codec messages --- README.md | 2 +- src/h1/codec.rs | 27 ++++---- src/h1/decoder.rs | 5 +- src/h1/dispatcher.rs | 154 +++++++++++++++++++++---------------------- tests/test_ws.rs | 2 +- 5 files changed, 90 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index b273ea8c..59e6fc37 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![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/fafhrd91/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/fafhrd91/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/bwq6923pblqg55gk/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-http/branch/master) [![codecov](https://codecov.io/gh/fafhrd91/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/fafhrd91/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 8f97d677..04cf395b 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -32,20 +32,16 @@ pub enum OutMessage { /// Http response message Response(Response), /// Payload chunk - Payload(Option), + Chunk(Option), } /// Incoming http/1 request #[derive(Debug)] pub enum InMessage { /// Request - Message(Request), - /// Request with payload - MessageWithPayload(Request), + Message { req: Request, payload: bool }, /// Payload chunk - Chunk(Bytes), - /// End of payload - Eof, + Chunk(Option), } /// HTTP/1 Codec @@ -246,8 +242,8 @@ impl Decoder for Codec { fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(InMessage::Chunk(chunk)), - Some(PayloadItem::Eof) => Some(InMessage::Eof), + Some(PayloadItem::Chunk(chunk)) => Some(InMessage::Chunk(Some(chunk))), + Some(PayloadItem::Eof) => Some(InMessage::Chunk(None)), None => None, }) } else if let Some((req, payload)) = self.decoder.decode(src)? { @@ -258,11 +254,10 @@ impl Decoder for Codec { self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } self.payload = payload; - if self.payload.is_some() { - Ok(Some(InMessage::MessageWithPayload(req))) - } else { - Ok(Some(InMessage::Message(req))) - } + Ok(Some(InMessage::Message { + req, + payload: self.payload.is_some(), + })) } else { Ok(None) } @@ -280,10 +275,10 @@ impl Encoder for Codec { OutMessage::Response(res) => { self.encode_response(res, dst)?; } - OutMessage::Payload(Some(bytes)) => { + OutMessage::Chunk(Some(bytes)) => { self.te.encode(bytes.as_ref(), dst)?; } - OutMessage::Payload(None) => { + OutMessage::Chunk(None) => { self.te.encode_eof(dst)?; } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index d0c3fa04..5fe8b19c 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -488,14 +488,13 @@ mod tests { impl InMessage { fn message(self) -> Request { match self { - InMessage::Message(msg) => msg, - InMessage::MessageWithPayload(msg) => msg, + InMessage::Message { req, payload: _ } => req, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - InMessage::MessageWithPayload(_) => true, + InMessage::Message { req: _, payload } => payload, _ => panic!("error"), } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index c2ce1203..8b7c2933 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -211,7 +211,7 @@ where Body::Empty => Some(State::None), Body::Binary(bin) => Some(State::SendPayload( None, - Some(OutMessage::Payload(bin.into())), + Some(OutMessage::Chunk(bin.into())), )), Body::Streaming(stream) => { Some(State::SendPayload(Some(stream), None)) @@ -248,7 +248,7 @@ where match stream.poll() { Ok(Async::Ready(Some(item))) => match self .framed - .start_send(OutMessage::Payload(Some(item.into()))) + .start_send(OutMessage::Chunk(Some(item.into()))) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); @@ -262,7 +262,7 @@ where }, Ok(Async::Ready(None)) => Some(State::SendPayload( None, - Some(OutMessage::Payload(None)), + Some(OutMessage::Chunk(None)), )), Ok(Async::NotReady) => return Ok(()), // Err(err) => return Err(DispatchError::Io(err)), @@ -305,89 +305,85 @@ where } } - /// Process one incoming message - fn one_message(&mut self, msg: InMessage) -> Result<(), DispatchError> { - self.flags.insert(Flags::STARTED); - - match msg { - InMessage::Message(msg) => { - // handle request early - if self.state.is_empty() { - self.state = self.handle_request(msg)?; - } else { - self.messages.push_back(Message::Item(msg)); - } - } - InMessage::MessageWithPayload(msg) => { - // payload - let (ps, pl) = Payload::new(false); - *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(ps); - - self.messages.push_back(Message::Item(msg)); - } - InMessage::Chunk(chunk) => { - if let Some(ref mut payload) = self.payload { - payload.feed_data(chunk); - } else { - error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::DISCONNECTED); - self.messages.push_back(Message::Error( - Response::InternalServerError().finish(), - )); - self.error = Some(DispatchError::InternalError); - } - } - InMessage::Eof => { - if let Some(mut payload) = self.payload.take() { - payload.feed_eof(); - } else { - error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::DISCONNECTED); - self.messages.push_back(Message::Error( - Response::InternalServerError().finish(), - )); - self.error = Some(DispatchError::InternalError); - } - } + /// Process one incoming requests + pub(self) fn poll_request(&mut self) -> Result> { + // limit a mount of non processed requests + if self.messages.len() >= MAX_PIPELINED_MESSAGES { + return Ok(false); } - Ok(()) - } - - pub(self) fn poll_request(&mut self) -> Result> { let mut updated = false; + 'outer: loop { + match self.framed.poll() { + Ok(Async::Ready(Some(msg))) => { + updated = true; + self.flags.insert(Flags::STARTED); - if self.messages.len() < MAX_PIPELINED_MESSAGES { - 'outer: loop { - match self.framed.poll() { - Ok(Async::Ready(Some(msg))) => { - updated = true; - self.one_message(msg)?; - } - Ok(Async::Ready(None)) => { - self.client_disconnected(); - break; - } - Ok(Async::NotReady) => break, - Err(ParseError::Io(e)) => { - self.client_disconnected(); - self.error = Some(DispatchError::Io(e)); - break; - } - Err(e) => { - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::EncodingCorrupted); + match msg { + InMessage::Message { req, payload } => { + if payload { + let (ps, pl) = Payload::new(false); + *req.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(ps); + } + + // handle request early + if self.state.is_empty() { + self.state = self.handle_request(req)?; + } else { + self.messages.push_back(Message::Item(req)); + } + } + InMessage::Chunk(Some(chunk)) => { + if let Some(ref mut payload) = self.payload { + payload.feed_data(chunk); + } else { + error!( + "Internal server error: unexpected payload chunk" + ); + self.flags.insert(Flags::DISCONNECTED); + self.messages.push_back(Message::Error( + Response::InternalServerError().finish(), + )); + self.error = Some(DispatchError::InternalError); + } + } + InMessage::Chunk(None) => { + if let Some(mut payload) = self.payload.take() { + payload.feed_eof(); + } else { + error!("Internal server error: unexpected eof"); + self.flags.insert(Flags::DISCONNECTED); + self.messages.push_back(Message::Error( + Response::InternalServerError().finish(), + )); + self.error = Some(DispatchError::InternalError); + } } - - // Malformed requests should be responded with 400 - self.messages - .push_back(Message::Error(Response::BadRequest().finish())); - self.flags.insert(Flags::DISCONNECTED); - self.error = Some(e.into()); - break; } } + Ok(Async::Ready(None)) => { + self.client_disconnected(); + break; + } + Ok(Async::NotReady) => break, + Err(ParseError::Io(e)) => { + self.client_disconnected(); + self.error = Some(DispatchError::Io(e)); + break; + } + Err(e) => { + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::EncodingCorrupted); + } + + // Malformed requests should be responded with 400 + self.messages + .push_back(Message::Error(Response::BadRequest().finish())); + self.flags.insert(Flags::DISCONNECTED); + self.error = Some(e.into()); + break; + } } } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 00ccd155..73590990 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -40,7 +40,7 @@ fn test_simple() { .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request - if let Some(h1::InMessage::MessageWithPayload(req)) = req { + if let Some(h1::InMessage::Message { req, payload: _ }) = req { match ws::handshake(&req) { Err(e) => { // validation failed From 4a167dc89e7ce9b0c9fcd239bf7c4f5e17e0e7bc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 10:46:00 -0700 Subject: [PATCH 0758/1635] update readme example --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 59e6fc37..3cb6f230 100644 --- a/README.md +++ b/README.md @@ -5,27 +5,28 @@ Actix http ## Documentation & community resources * [User Guide](https://actix.rs/docs/) -* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/) +* [API Documentation (Development)](https://actix.rs/actix-http/actix_http/) +* [API Documentation (Releases)](https://actix.rs/api/actix-http/stable/actix_http/) * [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-web](https://crates.io/crates/actix-web) +* Cargo package: [actix-http](https://crates.io/crates/actix-web) * Minimum supported Rust version: 1.26 or later ## Example ```rust -extern crate actix_web; -use actix_web::{http, server, App, Path, Responder}; - -fn index(info: Path<(u32, String)>) -> impl Responder { - format!("Hello {}! id:{}", info.1, info.0) -} +extern crate actix_http; +use actix_http::{h1, Response, ServiceConfig}; fn main() { - server::new( - || App::new() - .route("/{id}/{name}/index.html", http::Method::GET, index)) - .bind("127.0.0.1:8080").unwrap() + Server::new() + .bind("app", addr, move || { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec + .and_then(TakeItem::new().map_err(|_| ())) // <- read one request + .and_then(|(req, framed): (_, Framed<_, _>)| { // <- send response and close conn + framed + .send(h1::OutMessage::Response(Response::Ok().finish())) + }) + }) .run(); } ``` @@ -41,6 +42,6 @@ at your option. ## Code of Conduct -Contribution to the actix-web crate is organized under the terms of the -Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to +Contribution to the actix-http crate is organized under the terms of the +Contributor Covenant, the maintainer of actix-http, @fafhrd91, promises to intervene to uphold that code of conduct. From 65e9201b4d586df303b7d0870cdb55a107d0327b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 11:35:57 -0700 Subject: [PATCH 0759/1635] Fixed panic during graceful shutdown --- CHANGES.md | 7 +++++++ src/server/acceptor.rs | 8 ++++---- src/server/channel.rs | 34 +++++++++++++++++----------------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f4f665a8..46f1fb36 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.7.10] - 2018-10-09 + +### Fixed + +* Fixed panic during graceful shutdown + + ## [0.7.9] - 2018-10-09 ### Added diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index 2e1b1f28..a18dded9 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -9,7 +9,7 @@ use tokio_reactor::Handle; use tokio_tcp::TcpStream; use tokio_timer::{sleep, Delay}; -use super::channel::HttpProtocol; +// use super::channel::HttpProtocol; use super::error::AcceptorError; use super::handler::HttpHandler; use super::settings::ServiceConfig; @@ -367,9 +367,9 @@ where } ServerMessage::Shutdown(_) => Either::B(ok(())), ServerMessage::ForceShutdown => { - self.settings - .head() - .traverse(|proto: &mut HttpProtocol| proto.shutdown()); + // self.settings + // .head() + // .traverse(|proto: &mut HttpProtocol| proto.shutdown()); Either::B(ok(())) } } diff --git a/src/server/channel.rs b/src/server/channel.rs index 1f4ec5b1..af90d934 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -20,23 +20,23 @@ pub(crate) enum HttpProtocol { None, } -impl HttpProtocol { - pub(crate) fn shutdown(&mut self) { - match self { - HttpProtocol::H1(ref mut h1) => { - let io = h1.io(); - let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - } - HttpProtocol::H2(ref mut h2) => h2.shutdown(), - HttpProtocol::Unknown(_, io, _) => { - let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - } - HttpProtocol::None => (), - } - } -} +// impl HttpProtocol { +// fn shutdown_(&mut self) { +// match self { +// HttpProtocol::H1(ref mut h1) => { +// let io = h1.io(); +// let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); +// let _ = IoStream::shutdown(io, Shutdown::Both); +// } +// HttpProtocol::H2(ref mut h2) => h2.shutdown(), +// HttpProtocol::Unknown(_, io, _) => { +// let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); +// let _ = IoStream::shutdown(io, Shutdown::Both); +// } +// HttpProtocol::None => (), +// } +// } +// } enum ProtocolKind { Http1, From 4d17a9afcca0d8818f41feb6d5c24b58b960b45e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 11:42:52 -0700 Subject: [PATCH 0760/1635] update version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2d606cc0..51474c26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.9" +version = "0.7.10" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From c63838bb71bfa96c46341982afd17cb3608aaf0b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 13:12:49 -0700 Subject: [PATCH 0761/1635] fix 204 support for http/2 --- CHANGES.md | 7 +++++++ src/server/h2writer.rs | 13 +++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 46f1fb36..260b6df7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.7.11] - 2018-10-09 + +### Fixed + +* Fixed 204 responses for http/2 + + ## [0.7.10] - 2018-10-09 ### Fixed diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 51d4dce6..66f2923c 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -96,6 +96,7 @@ impl Writer for H2Writer { let mut has_date = false; let mut resp = Response::new(()); + let mut len_is_set = false; *resp.status_mut() = msg.status(); *resp.version_mut() = Version::HTTP_2; for (key, value) in msg.headers().iter() { @@ -107,6 +108,9 @@ impl Writer for H2Writer { }, CONTENT_LENGTH => match info.length { ResponseLength::None => (), + ResponseLength::Zero => { + len_is_set = true; + } _ => continue, }, DATE => has_date = true, @@ -126,8 +130,10 @@ impl Writer for H2Writer { // content length match info.length { ResponseLength::Zero => { - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + if !len_is_set { + resp.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); + } self.flags.insert(Flags::EOF); } ResponseLength::Length(len) => { @@ -144,6 +150,9 @@ impl Writer for H2Writer { resp.headers_mut() .insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap()); } + ResponseLength::None => { + self.flags.insert(Flags::EOF); + } _ => (), } if let Some(ce) = info.content_encoding { From f45038bbfe338661f3b958b10c37dd64d3d70650 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 9 Oct 2018 13:23:37 -0700 Subject: [PATCH 0762/1635] remove unused code --- Cargo.toml | 2 +- README.md | 5 +- src/server/acceptor.rs | 37 ++++------- src/server/builder.rs | 2 - src/server/channel.rs | 136 ----------------------------------------- src/server/h1.rs | 10 --- src/server/h2.rs | 9 --- src/server/settings.rs | 9 +-- 8 files changed, 15 insertions(+), 195 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 51474c26..14102881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.10" +version = "0.7.11" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/README.md b/README.md index 4e396cb9..321f82ab 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Client/server [WebSockets](https://actix.rs/docs/websockets/) support * Transparent content compression/decompression (br, gzip, deflate) * Configurable [request routing](https://actix.rs/docs/url-dispatch/) -* Graceful server shutdown * Multipart streams * Static assets * SSL support with OpenSSL or `native-tls` @@ -51,7 +50,7 @@ fn main() { * [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) * [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) * [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) -* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / +* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates * [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) * [r2d2](https://github.com/actix/examples/tree/master/r2d2/) @@ -66,8 +65,6 @@ You may consider checking out * [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) -* Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). - ## License This project is licensed under either of diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs index a18dded9..994b4b7b 100644 --- a/src/server/acceptor.rs +++ b/src/server/acceptor.rs @@ -9,10 +9,7 @@ use tokio_reactor::Handle; use tokio_tcp::TcpStream; use tokio_timer::{sleep, Delay}; -// use super::channel::HttpProtocol; use super::error::AcceptorError; -use super::handler::HttpHandler; -use super::settings::ServiceConfig; use super::IoStream; /// This trait indicates types that can create acceptor service for http server. @@ -275,56 +272,49 @@ impl Future for AcceptorTimeoutResponse { } } -pub(crate) struct ServerMessageAcceptor { +pub(crate) struct ServerMessageAcceptor { inner: T, - settings: ServiceConfig, } -impl ServerMessageAcceptor +impl ServerMessageAcceptor where - H: HttpHandler, T: NewService, { - pub(crate) fn new(settings: ServiceConfig, inner: T) -> Self { - ServerMessageAcceptor { inner, settings } + pub(crate) fn new(inner: T) -> Self { + ServerMessageAcceptor { inner } } } -impl NewService for ServerMessageAcceptor +impl NewService for ServerMessageAcceptor where - H: HttpHandler, T: NewService, { type Request = ServerMessage; type Response = (); type Error = T::Error; type InitError = T::InitError; - type Service = ServerMessageAcceptorService; - type Future = ServerMessageAcceptorResponse; + type Service = ServerMessageAcceptorService; + type Future = ServerMessageAcceptorResponse; fn new_service(&self) -> Self::Future { ServerMessageAcceptorResponse { fut: self.inner.new_service(), - settings: self.settings.clone(), } } } -pub(crate) struct ServerMessageAcceptorResponse +pub(crate) struct ServerMessageAcceptorResponse where - H: HttpHandler, T: NewService, { fut: T::Future, - settings: ServiceConfig, } -impl Future for ServerMessageAcceptorResponse +impl Future for ServerMessageAcceptorResponse where - H: HttpHandler, T: NewService, { - type Item = ServerMessageAcceptorService; + type Item = ServerMessageAcceptorService; type Error = T::InitError; fn poll(&mut self) -> Poll { @@ -332,20 +322,17 @@ where Async::NotReady => Ok(Async::NotReady), Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService { inner: service, - settings: self.settings.clone(), })), } } } -pub(crate) struct ServerMessageAcceptorService { +pub(crate) struct ServerMessageAcceptorService { inner: T, - settings: ServiceConfig, } -impl Service for ServerMessageAcceptorService +impl Service for ServerMessageAcceptorService where - H: HttpHandler, T: Service, { type Request = ServerMessage; diff --git a/src/server/builder.rs b/src/server/builder.rs index ec6ce992..4f159af1 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -60,7 +60,6 @@ where if secure { Either::B(ServerMessageAcceptor::new( - settings.clone(), TcpAcceptor::new(AcceptorTimeout::new( client_timeout, acceptor.create(), @@ -74,7 +73,6 @@ where )) } else { Either::A(ServerMessageAcceptor::new( - settings.clone(), TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) .map_err(|_| ()) .map_init_err(|_| ()) diff --git a/src/server/channel.rs b/src/server/channel.rs index af90d934..d65b05e8 100644 --- a/src/server/channel.rs +++ b/src/server/channel.rs @@ -50,7 +50,6 @@ where H: HttpHandler + 'static, { proto: HttpProtocol, - node: Option>, ka_timeout: Option, } @@ -64,24 +63,11 @@ where HttpChannel { ka_timeout, - node: None, proto: HttpProtocol::Unknown(settings, io, BytesMut::with_capacity(8192)), } } } -impl Drop for HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - fn drop(&mut self) { - if let Some(mut node) = self.node.take() { - node.remove() - } - } -} - impl Future for HttpChannel where T: IoStream, @@ -114,22 +100,6 @@ where } } - if self.node.is_none() { - self.node = Some(Node::new(())); - let _ = match self.proto { - HttpProtocol::H1(ref mut h1) => { - self.node.as_mut().map(|n| h1.settings().head().insert(n)) - } - HttpProtocol::H2(ref mut h2) => { - self.node.as_mut().map(|n| h2.settings().head().insert(n)) - } - HttpProtocol::Unknown(ref mut settings, _, _) => { - self.node.as_mut().map(|n| settings.head().insert(n)) - } - HttpProtocol::None => unreachable!(), - }; - } - let mut is_eof = false; let kind = match self.proto { HttpProtocol::H1(ref mut h1) => return h1.poll(), @@ -206,7 +176,6 @@ where H: HttpHandler + 'static, { proto: HttpProtocol, - node: Option>, } impl H1Channel @@ -216,7 +185,6 @@ where { pub(crate) fn new(settings: ServiceConfig, io: T) -> H1Channel { H1Channel { - node: None, proto: HttpProtocol::H1(h1::Http1Dispatcher::new( settings, io, @@ -228,18 +196,6 @@ where } } -impl Drop for H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - fn drop(&mut self) { - if let Some(mut node) = self.node.take() { - node.remove(); - } - } -} - impl Future for H1Channel where T: IoStream, @@ -249,16 +205,6 @@ where type Error = HttpDispatchError; fn poll(&mut self) -> Poll { - if self.node.is_none() { - self.node = Some(Node::new(())); - match self.proto { - HttpProtocol::H1(ref mut h1) => { - self.node.as_mut().map(|n| h1.settings().head().insert(n)); - } - _ => unreachable!(), - }; - } - match self.proto { HttpProtocol::H1(ref mut h1) => h1.poll(), _ => unreachable!(), @@ -266,88 +212,6 @@ where } } -pub(crate) struct Node { - next: Option<*mut Node>, - prev: Option<*mut Node>, - element: T, -} - -impl Node { - fn new(element: T) -> Self { - Node { - element, - next: None, - prev: None, - } - } - - fn insert(&mut self, next_el: &mut Node) { - let next: *mut Node = next_el as *const _ as *mut _; - - if let Some(next2) = self.next { - unsafe { - let n = next2.as_mut().unwrap(); - n.prev = Some(next); - } - next_el.next = Some(next2 as *mut _); - } - self.next = Some(next); - - unsafe { - let next: &mut Node = &mut *next; - next.prev = Some(self as *mut _); - } - } - - fn remove(&mut self) { - let next = self.next.take(); - let prev = self.prev.take(); - - if let Some(prev) = prev { - unsafe { - prev.as_mut().unwrap().next = next; - } - } - if let Some(next) = next { - unsafe { - next.as_mut().unwrap().prev = prev; - } - } - } -} - -impl Node<()> { - pub(crate) fn head() -> Self { - Node { - next: None, - prev: None, - element: (), - } - } - - pub(crate) fn traverse)>(&self, f: F) - where - T: IoStream, - H: HttpHandler + 'static, - { - if let Some(n) = self.next.as_ref() { - unsafe { - let mut next: &mut Node> = - &mut *(n.as_ref().unwrap() as *const _ as *mut _); - loop { - f(&mut next.element); - - next = if let Some(n) = next.next.as_ref() { - &mut **n - } else { - return; - } - } - } - } - } -} - /// Wrapper for `AsyncRead + AsyncWrite` types pub(crate) struct WrapperStream where diff --git a/src/server/h1.rs b/src/server/h1.rs index 0fb72ef7..a2ffc055 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -147,16 +147,6 @@ where disp } - #[inline] - pub fn settings(&self) -> &ServiceConfig { - &self.settings - } - - #[inline] - pub(crate) fn io(&mut self) -> &mut T { - self.stream.get_mut() - } - #[inline] fn can_read(&self) -> bool { if self.flags.contains(Flags::READ_DISCONNECTED) { diff --git a/src/server/h2.rs b/src/server/h2.rs index 6ad9af70..35afa339 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -89,15 +89,6 @@ where } } - pub(crate) fn shutdown(&mut self) { - self.state = State::Empty; - self.tasks.clear(); - } - - pub fn settings(&self) -> &ServiceConfig { - &self.settings - } - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { self.poll_keepalive()?; diff --git a/src/server/settings.rs b/src/server/settings.rs index bafffb5f..66a4eed8 100644 --- a/src/server/settings.rs +++ b/src/server/settings.rs @@ -1,4 +1,4 @@ -use std::cell::{Cell, RefCell, RefMut}; +use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::fmt::Write; use std::rc::Rc; @@ -15,7 +15,6 @@ use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; -use super::channel::Node; use super::message::{Request, RequestPool}; use super::KeepAlive; use body::Body; @@ -138,7 +137,6 @@ struct Inner { ka_enabled: bool, bytes: Rc, messages: &'static RequestPool, - node: RefCell>, date: Cell>, } @@ -173,7 +171,6 @@ impl ServiceConfig { client_shutdown, bytes: Rc::new(SharedBytesPool::new()), messages: RequestPool::pool(settings), - node: RefCell::new(Node::head()), date: Cell::new(None), })) } @@ -183,10 +180,6 @@ impl ServiceConfig { ServiceConfigBuilder::new(handler) } - pub(crate) fn head(&self) -> RefMut> { - self.0.node.borrow_mut() - } - pub(crate) fn handler(&self) -> &H { &self.0.handler } From ec8aef6b433832d5ab384d7bf66f847356900189 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Oct 2018 08:36:16 -0700 Subject: [PATCH 0763/1635] update dep versions --- CHANGES.md | 9 +++++++++ Cargo.toml | 9 ++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 260b6df7..39b97cc0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,14 @@ # Changes +## [0.7.12] - 2018-10-10 + +### Changed + +* Set min version for actix + +* Set min version for actix-net + + ## [0.7.11] - 2018-10-09 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 14102881..ea400dc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.11" +version = "0.7.12" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -61,11 +61,12 @@ flate2-rust = ["flate2/rust_backend"] cell = ["actix-net/cell"] [dependencies] -actix = "0.7.0" -actix-net = "0.1.0" +actix = "^0.7.5" +actix-net = "^0.1.1" base64 = "0.9" bitflags = "1.0" +failure = "^0.1.2" h2 = "0.1" htmlescape = "0.3" http = "^0.1.8" @@ -93,8 +94,6 @@ cookie = { version="0.11", features=["percent-encode"] } brotli2 = { version="^0.3.2", optional = true } flate2 = { version="^1.0.2", optional = true, default-features = false } -failure = "^0.1.2" - # io mio = "^0.6.13" net2 = "0.2" From 47b47af01a6baf8258c1286d3c82f6f28524e2ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Oct 2018 13:20:00 -0700 Subject: [PATCH 0764/1635] refactor ws codec --- src/ws/codec.rs | 74 ++++++++++++++++++++++++++++++-------------- src/ws/frame.rs | 75 +++++++++++++++++++++++++-------------------- src/ws/mod.rs | 4 +-- src/ws/transport.rs | 6 ++-- tests/test_ws.rs | 21 ++++++++++--- 5 files changed, 113 insertions(+), 67 deletions(-) diff --git a/src/ws/codec.rs b/src/ws/codec.rs index 6e2b1209..7ba10672 100644 --- a/src/ws/codec.rs +++ b/src/ws/codec.rs @@ -1,7 +1,7 @@ use bytes::BytesMut; use tokio_codec::{Decoder, Encoder}; -use super::frame::Frame; +use super::frame::Parser; use super::proto::{CloseReason, OpCode}; use super::ProtocolError; use body::Binary; @@ -21,6 +21,21 @@ pub enum Message { Close(Option), } +/// `WebSocket` frame +#[derive(Debug, PartialEq)] +pub enum Frame { + /// Text frame, codec does not verify utf8 encoding + Text(Option), + /// Binary frame + Binary(Option), + /// Ping message + Ping(String), + /// Pong message + Pong(String), + /// Close message with optional reason + Close(Option), +} + /// WebSockets protocol codec pub struct Codec { max_size: usize, @@ -60,29 +75,29 @@ impl Encoder for Codec { fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> { match item { Message::Text(txt) => { - Frame::write_message(dst, txt, OpCode::Text, true, !self.server) + Parser::write_message(dst, txt, OpCode::Text, true, !self.server) } Message::Binary(bin) => { - Frame::write_message(dst, bin, OpCode::Binary, true, !self.server) + Parser::write_message(dst, bin, OpCode::Binary, true, !self.server) } Message::Ping(txt) => { - Frame::write_message(dst, txt, OpCode::Ping, true, !self.server) + Parser::write_message(dst, txt, OpCode::Ping, true, !self.server) } Message::Pong(txt) => { - Frame::write_message(dst, txt, OpCode::Pong, true, !self.server) + Parser::write_message(dst, txt, OpCode::Pong, true, !self.server) } - Message::Close(reason) => Frame::write_close(dst, reason, !self.server), + Message::Close(reason) => Parser::write_close(dst, reason, !self.server), } Ok(()) } } impl Decoder for Codec { - type Item = Message; + type Item = Frame; type Error = ProtocolError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - match Frame::parse(src, self.server, self.max_size) { + match Parser::parse(src, self.server, self.max_size) { Ok(Some((finished, opcode, payload))) => { // continuation is not supported if !finished { @@ -93,23 +108,36 @@ impl Decoder for Codec { OpCode::Continue => Err(ProtocolError::NoContinuation), OpCode::Bad => Err(ProtocolError::BadOpCode), OpCode::Close => { - let close_reason = Frame::parse_close_payload(&payload); - Ok(Some(Message::Close(close_reason))) - } - OpCode::Ping => Ok(Some(Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into(), - ))), - OpCode::Pong => Ok(Some(Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into(), - ))), - OpCode::Binary => Ok(Some(Message::Binary(payload))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => Ok(Some(Message::Text(s))), - Err(_) => Err(ProtocolError::BadEncoding), + if let Some(ref pl) = payload { + let close_reason = Parser::parse_close_payload(pl); + Ok(Some(Frame::Close(close_reason))) + } else { + Ok(Some(Frame::Close(None))) } } + OpCode::Ping => { + if let Some(ref pl) = payload { + Ok(Some(Frame::Ping(String::from_utf8_lossy(pl).into()))) + } else { + Ok(Some(Frame::Ping(String::new()))) + } + } + OpCode::Pong => { + if let Some(ref pl) = payload { + Ok(Some(Frame::Pong(String::from_utf8_lossy(pl).into()))) + } else { + Ok(Some(Frame::Pong(String::new()))) + } + } + OpCode::Binary => Ok(Some(Frame::Binary(payload))), + OpCode::Text => { + Ok(Some(Frame::Text(payload))) + //let tmp = Vec::from(payload.as_ref()); + //match String::from_utf8(tmp) { + // Ok(s) => Ok(Some(Message::Text(s))), + // Err(_) => Err(ProtocolError::BadEncoding), + //} + } } } Ok(None) => Ok(None), diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 38bebc28..de1b9239 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -9,9 +9,9 @@ use ws::ProtocolError; /// A struct representing a `WebSocket` frame. #[derive(Debug)] -pub struct Frame; +pub struct Parser; -impl Frame { +impl Parser { fn parse_metadata( src: &[u8], server: bool, max_size: usize, ) -> Result)>, ProtocolError> { @@ -87,10 +87,10 @@ impl Frame { /// Parse the input stream into a frame. pub fn parse( src: &mut BytesMut, server: bool, max_size: usize, - ) -> Result, ProtocolError> { + ) -> Result)>, ProtocolError> { // try to parse ws frame metadata let (idx, finished, opcode, length, mask) = - match Frame::parse_metadata(src, server, max_size)? { + match Parser::parse_metadata(src, server, max_size)? { None => return Ok(None), Some(res) => res, }; @@ -105,7 +105,7 @@ impl Frame { // no need for body if length == 0 { - return Ok(Some((finished, opcode, Binary::from("")))); + return Ok(Some((finished, opcode, None))); } let mut data = src.split_to(length); @@ -117,7 +117,7 @@ impl Frame { } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Some((true, OpCode::Close, Binary::from("")))); + return Ok(Some((true, OpCode::Close, None))); } _ => (), } @@ -127,16 +127,16 @@ impl Frame { apply_mask(&mut data, mask); } - Ok(Some((finished, opcode, data.into()))) + Ok(Some((finished, opcode, Some(data)))) } /// Parse the payload of a close frame. - pub fn parse_close_payload(payload: &Binary) -> Option { + pub fn parse_close_payload(payload: &[u8]) -> Option { if payload.len() >= 2 { - let raw_code = NetworkEndian::read_u16(payload.as_ref()); + let raw_code = NetworkEndian::read_u16(payload); let code = CloseCode::from(raw_code); let description = if payload.len() > 2 { - Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into()) + Some(String::from_utf8_lossy(&payload[2..]).into()) } else { None }; @@ -203,33 +203,40 @@ impl Frame { } }; - Frame::write_message(dst, payload, OpCode::Close, true, mask) + Parser::write_message(dst, payload, OpCode::Close, true, mask) } } #[cfg(test)] mod tests { use super::*; + use bytes::Bytes; struct F { finished: bool, opcode: OpCode, - payload: Binary, + payload: Bytes, } - fn is_none(frm: &Result, ProtocolError>) -> bool { + fn is_none( + frm: &Result)>, ProtocolError>, + ) -> bool { match *frm { Ok(None) => true, _ => false, } } - fn extract(frm: Result, ProtocolError>) -> F { + fn extract( + frm: Result)>, ProtocolError>, + ) -> F { match frm { Ok(Some((finished, opcode, payload))) => F { finished, opcode, - payload, + payload: payload + .map(|b| b.freeze()) + .unwrap_or_else(|| Bytes::from("")), }, _ => unreachable!("error"), } @@ -238,12 +245,12 @@ mod tests { #[test] fn test_parse() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); + assert!(is_none(&Parser::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(b"1"); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1"[..]); @@ -252,7 +259,7 @@ mod tests { #[test] fn test_parse_length0() { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert!(frame.payload.is_empty()); @@ -261,13 +268,13 @@ mod tests { #[test] fn test_parse_length2() { let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); + assert!(is_none(&Parser::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); buf.extend(&[0u8, 4u8][..]); buf.extend(b"1234"); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1234"[..]); @@ -276,13 +283,13 @@ mod tests { #[test] fn test_parse_length4() { let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); + assert!(is_none(&Parser::parse(&mut buf, false, 1024))); let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); buf.extend(b"1234"); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); assert_eq!(frame.payload.as_ref(), &b"1234"[..]); @@ -294,12 +301,12 @@ mod tests { buf.extend(b"0001"); buf.extend(b"1"); - assert!(Frame::parse(&mut buf, false, 1024).is_err()); + assert!(Parser::parse(&mut buf, false, 1024).is_err()); - let frame = extract(Frame::parse(&mut buf, true, 1024)); + let frame = extract(Parser::parse(&mut buf, true, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload, vec![1u8].into()); + assert_eq!(frame.payload, Bytes::from(vec![1u8])); } #[test] @@ -307,12 +314,12 @@ mod tests { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); buf.extend(&[1u8]); - assert!(Frame::parse(&mut buf, true, 1024).is_err()); + assert!(Parser::parse(&mut buf, true, 1024).is_err()); - let frame = extract(Frame::parse(&mut buf, false, 1024)); + let frame = extract(Parser::parse(&mut buf, false, 1024)); assert!(!frame.finished); assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload, vec![1u8].into()); + assert_eq!(frame.payload, Bytes::from(vec![1u8])); } #[test] @@ -320,9 +327,9 @@ mod tests { let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); buf.extend(&[1u8, 1u8]); - assert!(Frame::parse(&mut buf, true, 1).is_err()); + assert!(Parser::parse(&mut buf, true, 1).is_err()); - if let Err(ProtocolError::Overflow) = Frame::parse(&mut buf, false, 0) { + if let Err(ProtocolError::Overflow) = Parser::parse(&mut buf, false, 0) { } else { unreachable!("error"); } @@ -331,7 +338,7 @@ mod tests { #[test] fn test_ping_frame() { let mut buf = BytesMut::new(); - Frame::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false); + Parser::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false); let mut v = vec![137u8, 4u8]; v.extend(b"data"); @@ -341,7 +348,7 @@ mod tests { #[test] fn test_pong_frame() { let mut buf = BytesMut::new(); - Frame::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false); + Parser::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false); let mut v = vec![138u8, 4u8]; v.extend(b"data"); @@ -352,7 +359,7 @@ mod tests { fn test_close_frame() { let mut buf = BytesMut::new(); let reason = (CloseCode::Normal, "data"); - Frame::write_close(&mut buf, Some(reason.into()), false); + Parser::write_close(&mut buf, Some(reason.into()), false); let mut v = vec![136u8, 6u8, 3u8, 232u8]; v.extend(b"data"); @@ -362,7 +369,7 @@ mod tests { #[test] fn test_empty_close_frame() { let mut buf = BytesMut::new(); - Frame::write_close(&mut buf, None, false); + Parser::write_close(&mut buf, None, false); assert_eq!(&buf[..], &vec![0x88, 0x00][..]); } } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 5ebb502b..7df1f4b4 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -16,8 +16,8 @@ mod mask; mod proto; mod transport; -pub use self::codec::{Codec, Message}; -pub use self::frame::Frame; +pub use self::codec::{Codec, Frame, Message}; +pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; pub use self::transport::Transport; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index aabeb5d5..102d02b4 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -4,7 +4,7 @@ use actix_net::service::{IntoService, Service}; use futures::{Future, Poll}; use tokio_io::{AsyncRead, AsyncWrite}; -use super::{Codec, Message}; +use super::{Codec, Frame, Message}; pub struct Transport where @@ -17,7 +17,7 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { @@ -37,7 +37,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 73590990..91e212ef 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -20,12 +20,23 @@ use futures::{Future, Sink, Stream}; use actix_http::{h1, ws, ResponseError, ServiceConfig}; -fn ws_service(req: ws::Message) -> impl Future { +fn ws_service(req: ws::Frame) -> impl Future { match req { - ws::Message::Ping(msg) => ok(ws::Message::Pong(msg)), - ws::Message::Text(text) => ok(ws::Message::Text(text)), - ws::Message::Binary(bin) => ok(ws::Message::Binary(bin)), - ws::Message::Close(reason) => ok(ws::Message::Close(reason)), + ws::Frame::Ping(msg) => ok(ws::Message::Pong(msg)), + ws::Frame::Text(text) => { + let text = if let Some(pl) = text { + String::from_utf8(Vec::from(pl.as_ref())).unwrap() + } else { + String::new() + }; + ok(ws::Message::Text(text)) + } + ws::Frame::Binary(bin) => ok(ws::Message::Binary( + bin.map(|e| e.freeze()) + .unwrap_or_else(|| Bytes::from("")) + .into(), + )), + ws::Frame::Close(reason) => ok(ws::Message::Close(reason)), _ => ok(ws::Message::Close(None)), } } From 06addd55232a866dd195149c3622c9b37f5b9ae5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Oct 2018 13:23:25 -0700 Subject: [PATCH 0765/1635] update deps --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7bfb82fb..9193aeea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,15 +35,15 @@ session = ["cookie/secure"] cell = ["actix-net/cell"] [dependencies] -actix = "0.7.0" -actix-net = "0.1.0" +actix = "0.7.5" +actix-net = "0.1.1" #actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" -http = "^0.1.8" +http = "0.1.8" httparse = "1.3" -failure = "^0.1.2" +failure = "0.1.2" log = "0.4" mime = "0.3" rand = "0.5" @@ -53,7 +53,7 @@ sha1 = "0.6" time = "0.1" encoding = "0.2" lazy_static = "1.0" -serde_urlencoded = "^0.5.3" +serde_urlencoded = "0.5.3" cookie = { version="0.11", features=["percent-encode"] } # io From 32145cf6c31a9d149041b0894029190e3c4086ba Mon Sep 17 00:00:00 2001 From: jeizsm Date: Thu, 11 Oct 2018 11:05:07 +0300 Subject: [PATCH 0766/1635] fix after update tokio-rustls (#542) --- CHANGES.md | 6 ++++++ src/client/connector.rs | 16 +++++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 39b97cc0..ad5ae9e1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.7.13] - 2018-10-* + +### Fixed + +* Fixed rustls build + ## [0.7.12] - 2018-10-10 ### Changed diff --git a/src/client/connector.rs b/src/client/connector.rs index 07c7b646..3f4ac27c 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -37,15 +37,9 @@ use { ))] use { rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc, - tokio_rustls::ClientConfigExt, webpki::DNSNameRef, webpki_roots, + tokio_rustls::TlsConnector as SslConnector, webpki::DNSNameRef, webpki_roots, }; -#[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) -))] -type SslConnector = Arc; - #[cfg(not(any( feature = "alpn", feature = "ssl", @@ -282,7 +276,7 @@ impl Default for ClientConnector { config .root_store .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - Arc::new(config) + SslConnector::from(Arc::new(config)) } #[cfg_attr(rustfmt, rustfmt_skip)] @@ -373,7 +367,7 @@ impl ClientConnector { /// config /// .root_store /// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - /// let conn = ClientConnector::with_connector(Arc::new(config)).start(); + /// let conn = ClientConnector::with_connector(config).start(); /// /// conn.send( /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host @@ -390,7 +384,7 @@ impl ClientConnector { /// ``` pub fn with_connector(connector: ClientConfig) -> ClientConnector { // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(Arc::new(connector)) + Self::with_connector_impl(SslConnector::from(Arc::new(connector))) } #[cfg(all( @@ -832,7 +826,7 @@ impl ClientConnector { let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap(); fut::Either::A( act.connector - .connect_async(host, stream) + .connect(host, stream) .into_actor(act) .then(move |res, _, _| { match res { From b960b5827c844d32bb85ea560d0d9e0783c2aaf7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 11 Oct 2018 20:15:10 -0700 Subject: [PATCH 0767/1635] export Uri --- src/h1/service.rs | 2 +- src/lib.rs | 5 +++-- src/request.rs | 13 ++++++++----- src/uri.rs | 6 +++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/h1/service.rs b/src/h1/service.rs index a7261df1..aa7a5173 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -22,7 +22,7 @@ pub struct H1Service { impl H1Service where - S: NewService, + S: NewService + Clone, S::Service: Clone, S::Error: Debug, { diff --git a/src/lib.rs b/src/lib.rs index 85bf9c2f..5ce3ec39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ mod json; mod payload; mod request; mod response; -mod uri; +pub mod uri; pub mod error; pub mod h1; @@ -148,10 +148,11 @@ pub mod http { //! Various HTTP related types // re-exports + pub use modhttp::header::{HeaderName, HeaderValue}; pub use modhttp::{Method, StatusCode, Version}; #[doc(hidden)] - pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri}; + pub use modhttp::{uri, Error, HeaderMap, HttpTryFrom, Uri}; pub use cookie::{Cookie, CookieBuilder}; diff --git a/src/request.rs b/src/request.rs index 82d8c22f..ef28e369 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,7 +8,7 @@ use http::{header, HeaderMap, Method, Uri, Version}; use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; -use uri::Url as InnerUrl; +use uri::Url; bitflags! { pub(crate) struct MessageFlags: u8 { @@ -25,7 +25,7 @@ pub struct Request { pub(crate) struct InnerRequest { pub(crate) version: Version, pub(crate) method: Method, - pub(crate) url: InnerUrl, + pub(crate) url: Url, pub(crate) flags: Cell, pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, @@ -73,7 +73,7 @@ impl Request { inner: Rc::new(InnerRequest { pool, method: Method::GET, - url: InnerUrl::default(), + url: Url::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), flags: Cell::new(MessageFlags::empty()), @@ -94,7 +94,7 @@ impl Request { } #[inline] - pub(crate) fn url(&self) -> &InnerUrl { + pub fn url(&self) -> &Url { &self.inner().url } @@ -162,7 +162,10 @@ impl Request { self.inner().method == Method::CONNECT } - pub(crate) fn clone(&self) -> Self { + #[doc(hidden)] + /// Note: this method should be called only as part of clone operation + /// of wrapper type. + pub fn clone_request(&self) -> Self { Request { inner: self.inner.clone(), } diff --git a/src/uri.rs b/src/uri.rs index 881cf20a..6edd220c 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -32,11 +32,11 @@ fn set_bit(array: &mut [u8], ch: u8) { } lazy_static! { - static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; + pub static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; } #[derive(Default, Clone, Debug)] -pub(crate) struct Url { +pub struct Url { uri: Uri, path: Option>, } @@ -61,7 +61,7 @@ impl Url { } } -pub(crate) struct Quoter { +pub struct Quoter { safe_table: [u8; 16], protected_table: [u8; 16], } From d145136e569b49caec0fe87735ab0c736b2eb5de Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 13 Oct 2018 09:54:03 +0300 Subject: [PATCH 0768/1635] Add individual check for TLS features --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 62867e03..6793745f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,9 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then cargo clean + cargo check --feature rust-tls + cargo check --feature ssl + cargo check --feature tls cargo test --features="ssl,tls,rust-tls" -- --nocapture fi - | From 63a443fce0560f2d4275032cd12c3fb2d22dd931 Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 13 Oct 2018 10:05:21 +0300 Subject: [PATCH 0769/1635] Correct build script --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6793745f..c5dfcd81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,9 +32,9 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then cargo clean - cargo check --feature rust-tls - cargo check --feature ssl - cargo check --feature tls + cargo check --features rust-tls + cargo check --features ssl + cargo check --features tls cargo test --features="ssl,tls,rust-tls" -- --nocapture fi - | From d39c018c9384963030d0adda0e29a0735d7d5179 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 13 Oct 2018 23:57:31 -0700 Subject: [PATCH 0770/1635] do not handle upgrade and connect requests --- src/h1/codec.rs | 43 ++++++++++++++++++++------- src/h1/decoder.rs | 45 ++++++++++++++++++++--------- src/h1/dispatcher.rs | 69 ++++++++++++++++++++++++++++++-------------- src/h1/mod.rs | 13 ++++++++- src/h1/service.rs | 5 ++-- tests/test_server.rs | 21 +++++++++----- tests/test_ws.rs | 2 +- 7 files changed, 141 insertions(+), 57 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 04cf395b..a91f5cb3 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -4,7 +4,7 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; +use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder, RequestPayloadType}; use super::encoder::{ResponseEncoder, ResponseLength}; use body::{Binary, Body}; use config::ServiceConfig; @@ -17,10 +17,11 @@ use response::Response; bitflags! { struct Flags: u8 { - const HEAD = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const KEEPALIVE_ENABLED = 0b0001_0000; + const HEAD = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const KEEPALIVE_ENABLED = 0b0000_1000; + const UNHANDLED = 0b0001_0000; } } @@ -39,11 +40,19 @@ pub enum OutMessage { #[derive(Debug)] pub enum InMessage { /// Request - Message { req: Request, payload: bool }, + Message(Request, InMessageType), /// Payload chunk Chunk(Option), } +/// Incoming request type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InMessageType { + None, + Payload, + Unhandled, +} + /// HTTP/1 Codec pub struct Codec { config: ServiceConfig, @@ -246,6 +255,8 @@ impl Decoder for Codec { Some(PayloadItem::Eof) => Some(InMessage::Chunk(None)), None => None, }) + } else if self.flags.contains(Flags::UNHANDLED) { + Ok(None) } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags .set(Flags::HEAD, req.inner.method == Method::HEAD); @@ -253,11 +264,21 @@ impl Decoder for Codec { if self.flags.contains(Flags::KEEPALIVE_ENABLED) { self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } - self.payload = payload; - Ok(Some(InMessage::Message { - req, - payload: self.payload.is_some(), - })) + let payload = match payload { + RequestPayloadType::None => { + self.payload = None; + InMessageType::None + } + RequestPayloadType::Payload(pl) => { + self.payload = Some(pl); + InMessageType::Payload + } + RequestPayloadType::Unhandled => { + self.payload = None; + InMessageType::Unhandled + } + }; + Ok(Some(InMessage::Message(req, payload))) } else { Ok(None) } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 5fe8b19c..c2a8d0e9 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -16,6 +16,13 @@ const MAX_HEADERS: usize = 96; pub struct RequestDecoder(&'static RequestPool); +/// Incoming request type +pub enum RequestPayloadType { + None, + Payload(PayloadDecoder), + Unhandled, +} + impl RequestDecoder { pub(crate) fn with_pool(pool: &'static RequestPool) -> RequestDecoder { RequestDecoder(pool) @@ -29,7 +36,7 @@ impl Default for RequestDecoder { } impl Decoder for RequestDecoder { - type Item = (Request, Option); + type Item = (Request, RequestPayloadType); type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -149,18 +156,18 @@ impl Decoder for RequestDecoder { // https://tools.ietf.org/html/rfc7230#section-3.3.3 let decoder = if chunked { // Chunked encoding - Some(PayloadDecoder::chunked()) + RequestPayloadType::Payload(PayloadDecoder::chunked()) } else if let Some(len) = content_length { // Content-Length - Some(PayloadDecoder::length(len)) + RequestPayloadType::Payload(PayloadDecoder::length(len)) } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect - Some(PayloadDecoder::eof()) + RequestPayloadType::Unhandled } else if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); } else { - None + RequestPayloadType::None }; Ok(Some((msg, decoder))) @@ -481,20 +488,36 @@ mod tests { use super::*; use error::ParseError; - use h1::InMessage; + use h1::{InMessage, InMessageType}; use httpmessage::HttpMessage; use request::Request; + impl RequestPayloadType { + fn unwrap(self) -> PayloadDecoder { + match self { + RequestPayloadType::Payload(pl) => pl, + _ => panic!(), + } + } + + fn is_unhandled(&self) -> bool { + match self { + RequestPayloadType::Unhandled => true, + _ => false, + } + } + } + impl InMessage { fn message(self) -> Request { match self { - InMessage::Message { req, payload: _ } => req, + InMessage::Message(req, _) => req, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - InMessage::Message { req: _, payload } => payload, + InMessage::Message(_, payload) => payload == InMessageType::Payload, _ => panic!("error"), } } @@ -919,13 +942,9 @@ mod tests { ); let mut reader = RequestDecoder::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); assert!(!req.keep_alive()); assert!(req.upgrade()); - assert_eq!( - pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"some raw data" - ); + assert!(pl.is_unhandled()); } #[test] diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8b7c2933..8ae2ae8c 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -18,7 +18,8 @@ use error::DispatchError; use request::Request; use response::Response; -use super::codec::{Codec, InMessage, OutMessage}; +use super::codec::{Codec, InMessage, InMessageType, OutMessage}; +use super::H1ServiceResult; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -41,13 +42,14 @@ where { service: S, flags: Flags, - framed: Framed, + framed: Option>, error: Option>, config: ServiceConfig, state: State, payload: Option, messages: VecDeque, + unhandled: Option, ka_expire: Instant, ka_timer: Option, @@ -112,9 +114,10 @@ where state: State::None, error: None, messages: VecDeque::new(), + framed: Some(framed), + unhandled: None, service, flags, - framed, config, ka_expire, ka_timer, @@ -144,7 +147,7 @@ where /// Flush stream fn poll_flush(&mut self) -> Poll<(), DispatchError> { if !self.flags.contains(Flags::FLUSHED) { - match self.framed.poll_complete() { + match self.framed.as_mut().unwrap().poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); @@ -187,7 +190,11 @@ where State::ServiceCall(ref mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); + self.framed + .as_mut() + .unwrap() + .get_codec_mut() + .prepare_te(&mut res); let body = res.replace_body(Body::Empty); Some(State::SendResponse(Some(( OutMessage::Response(res), @@ -200,11 +207,11 @@ where // send respons State::SendResponse(ref mut item) => { let (msg, body) = item.take().expect("SendResponse is empty"); - match self.framed.start_send(msg) { + match self.framed.as_mut().unwrap().start_send(msg) { Ok(AsyncSink::Ready) => { self.flags.set( Flags::KEEPALIVE, - self.framed.get_codec().keepalive(), + self.framed.as_mut().unwrap().get_codec().keepalive(), ); self.flags.remove(Flags::FLUSHED); match body { @@ -233,7 +240,7 @@ where // Send payload State::SendPayload(ref mut stream, ref mut bin) => { if let Some(item) = bin.take() { - match self.framed.start_send(item) { + match self.framed.as_mut().unwrap().start_send(item) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); } @@ -248,6 +255,8 @@ where match stream.poll() { Ok(Async::Ready(Some(item))) => match self .framed + .as_mut() + .unwrap() .start_send(OutMessage::Chunk(Some(item.into()))) { Ok(AsyncSink::Ready) => { @@ -297,7 +306,11 @@ where let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); + self.framed + .as_mut() + .unwrap() + .get_codec_mut() + .prepare_te(&mut res); let body = res.replace_body(Body::Empty); Ok(State::SendResponse(Some((OutMessage::Response(res), body)))) } @@ -314,17 +327,24 @@ where let mut updated = false; 'outer: loop { - match self.framed.poll() { + match self.framed.as_mut().unwrap().poll() { Ok(Async::Ready(Some(msg))) => { updated = true; self.flags.insert(Flags::STARTED); match msg { - InMessage::Message { req, payload } => { - if payload { - let (ps, pl) = Payload::new(false); - *req.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(ps); + InMessage::Message(req, payload) => { + match payload { + InMessageType::Payload => { + let (ps, pl) = Payload::new(false); + *req.inner.payload.borrow_mut() = Some(pl); + self.payload = Some(ps); + } + InMessageType::Unhandled => { + self.unhandled = Some(req); + return Ok(updated); + } + _ => (), } // handle request early @@ -454,15 +474,16 @@ where S: Service, S::Error: Debug, { - type Item = (); + type Item = H1ServiceResult; type Error = DispatchError; #[inline] - fn poll(&mut self) -> Poll<(), Self::Error> { + fn poll(&mut self) -> Poll { if self.flags.contains(Flags::SHUTDOWN) { self.poll_keepalive()?; try_ready!(self.poll_flush()); - Ok(AsyncWrite::shutdown(self.framed.get_mut())?) + let io = self.framed.take().unwrap().into_inner(); + Ok(Async::Ready(H1ServiceResult::Shutdown(io))) } else { self.poll_keepalive()?; self.poll_request()?; @@ -474,15 +495,21 @@ where if let Some(err) = self.error.take() { Err(err) } else if self.flags.contains(Flags::DISCONNECTED) { - Ok(Async::Ready(())) + Ok(Async::Ready(H1ServiceResult::Disconnected)) + } + // unhandled request (upgrade or connect) + else if self.unhandled.is_some() { + let req = self.unhandled.take().unwrap(); + let framed = self.framed.take().unwrap(); + Ok(Async::Ready(H1ServiceResult::Unhandled(req, framed))) } // disconnect if keep-alive is not enabled else if self.flags.contains(Flags::STARTED) && !self .flags .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) { - self.flags.insert(Flags::SHUTDOWN); - self.poll() + let io = self.framed.take().unwrap().into_inner(); + Ok(Async::Ready(H1ServiceResult::Shutdown(io))) } else { Ok(Async::NotReady) } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 634136a4..4e196ad5 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,11 +1,22 @@ //! HTTP/1 implementation +use actix_net::codec::Framed; + mod codec; mod decoder; mod dispatcher; mod encoder; mod service; -pub use self::codec::{Codec, InMessage, OutMessage}; +pub use self::codec::{Codec, InMessage, InMessageType, OutMessage}; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler}; + +use request::Request; + +/// H1 service response type +pub enum H1ServiceResult { + Disconnected, + Shutdown(T), + Unhandled(Request, Framed), +} diff --git a/src/h1/service.rs b/src/h1/service.rs index aa7a5173..6e9d7d65 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -12,6 +12,7 @@ use request::Request; use response::Response; use super::dispatcher::Dispatcher; +use super::H1ServiceResult; /// `NewService` implementation for HTTP1 transport pub struct H1Service { @@ -51,7 +52,7 @@ where S::Error: Debug, { type Request = T; - type Response = (); + type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; type Service = H1ServiceHandler; @@ -243,7 +244,7 @@ where S::Error: Debug, { type Request = T; - type Response = (); + type Response = H1ServiceResult; type Error = DispatchError; type Future = Dispatcher; diff --git a/tests/test_server.rs b/tests/test_server.rs index f382eafb..c8de0290 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -9,6 +9,7 @@ use std::{io::Read, io::Write, net, thread, time}; use actix::System; use actix_net::server::Server; +use actix_net::service::NewServiceExt; use actix_web::{client, test, HttpMessage}; use bytes::Bytes; use futures::future::{self, ok}; @@ -29,6 +30,7 @@ fn test_h1_v2() { .server_hostname("localhost") .server_address(addr) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }).unwrap() .run(); }); @@ -53,6 +55,7 @@ fn test_slow_request() { h1::H1Service::build() .client_timeout(100) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }).unwrap() .run(); }); @@ -72,6 +75,7 @@ fn test_malformed_request() { Server::new() .bind("test", addr, move || { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }).unwrap() .run(); }); @@ -106,7 +110,7 @@ fn test_content_length() { StatusCode::NOT_FOUND, ]; future::ok::<_, ()>(Response::new(statuses[indx])) - }) + }).map(|_| ()) }).unwrap() .run(); }); @@ -172,7 +176,7 @@ fn test_headers() { ); } future::ok::<_, ()>(builder.body(data.clone())) - }) + }).map(|_| ()) }) .unwrap() .run() @@ -221,6 +225,7 @@ fn test_body() { Server::new() .bind("test", addr, move || { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) }).unwrap() .run(); }); @@ -246,7 +251,7 @@ fn test_head_empty() { .bind("test", addr, move || { h1::H1Service::new(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).finish()) - }) + }).map(|_| ()) }).unwrap() .run() }); @@ -282,7 +287,7 @@ fn test_head_binary() { ok::<_, ()>( Response::Ok().content_length(STR.len() as u64).body(STR), ) - }) + }).map(|_| ()) }).unwrap() .run() }); @@ -314,7 +319,7 @@ fn test_head_binary2() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))) + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }).unwrap() .run() }); @@ -349,7 +354,7 @@ fn test_body_length() { .content_length(STR.len() as u64) .body(Body::Streaming(Box::new(body))), ) - }) + }).map(|_| ()) }).unwrap() .run() }); @@ -380,7 +385,7 @@ fn test_body_chunked_explicit() { .chunked() .body(Body::Streaming(Box::new(body))), ) - }) + }).map(|_| ()) }).unwrap() .run() }); @@ -409,7 +414,7 @@ fn test_body_chunked_implicit() { h1::H1Service::new(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().body(Body::Streaming(Box::new(body)))) - }) + }).map(|_| ()) }).unwrap() .run() }); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 91e212ef..f475cd22 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -51,7 +51,7 @@ fn test_simple() { .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request - if let Some(h1::InMessage::Message { req, payload: _ }) = req { + if let Some(h1::InMessage::Message(req, _)) = req { match ws::handshake(&req) { Err(e) => { // validation failed From dd948f836e75b4edecb912203fe9f6fe89365115 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Oct 2018 08:08:12 -0700 Subject: [PATCH 0771/1635] HttpServer not sending streamed request body on HTTP/2 requests #544 --- CHANGES.md | 7 +++++-- src/httprequest.rs | 2 +- src/server/output.rs | 23 +++++++++-------------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ad5ae9e1..62d2e915 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,13 @@ # Changes -## [0.7.13] - 2018-10-* +## [0.7.13] - 2018-10-14 ### Fixed -* Fixed rustls build +* Fixed rustls support + +* HttpServer not sending streamed request body on HTTP/2 requests #544 + ## [0.7.12] - 2018-10-10 diff --git a/src/httprequest.rs b/src/httprequest.rs index d8c49496..0e4f74e5 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -216,7 +216,7 @@ impl HttpRequest { self.url_for(name, &NO_PARAMS) } - /// This method returns reference to current `RouteInfo` object. + /// This method returns reference to current `ResourceInfo` object. #[inline] pub fn resource(&self) -> &ResourceInfo { &self.resource diff --git a/src/server/output.rs b/src/server/output.rs index 35f3c7a4..104700d4 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -299,12 +299,11 @@ impl Output { match resp.chunked() { Some(true) => { // Enable transfer encoding - if version == Version::HTTP_2 { - info.length = ResponseLength::None; - TransferEncoding::eof(buf) - } else { - info.length = ResponseLength::Chunked; + info.length = ResponseLength::Chunked; + if version == Version::HTTP_11 { TransferEncoding::chunked(buf) + } else { + TransferEncoding::eof(buf) } } Some(false) => TransferEncoding::eof(buf), @@ -337,15 +336,11 @@ impl Output { } } else { // Enable transfer encoding - match version { - Version::HTTP_11 => { - info.length = ResponseLength::Chunked; - TransferEncoding::chunked(buf) - } - _ => { - info.length = ResponseLength::None; - TransferEncoding::eof(buf) - } + info.length = ResponseLength::Chunked; + if version == Version::HTTP_11 { + TransferEncoding::chunked(buf) + } else { + TransferEncoding::eof(buf) } } } From c04b4678f136b118fef40b979c0df90402a8e0e4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Oct 2018 08:10:41 -0700 Subject: [PATCH 0772/1635] bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ea400dc6..d98ce5ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.12" +version = "0.7.13" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 3c402a55da704ac6605d26500877bedab8565bf7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 15 Oct 2018 15:56:47 -0700 Subject: [PATCH 0773/1635] added H1SimpleService --- src/h1/codec.rs | 12 ++++++ src/h1/mod.rs | 2 +- src/h1/service.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index a91f5cb3..02046648 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -103,6 +103,17 @@ impl Codec { self.flags.contains(Flags::KEEPALIVE) } + /// Check last request's message type + pub fn message_type(&self) -> InMessageType { + if self.flags.contains(Flags::UNHANDLED) { + InMessageType::Unhandled + } else if self.payload.is_none() { + InMessageType::None + } else { + InMessageType::Payload + } + } + /// prepare transfer encoding pub fn prepare_te(&mut self, res: &mut Response) { self.te @@ -275,6 +286,7 @@ impl Decoder for Codec { } RequestPayloadType::Unhandled => { self.payload = None; + self.flags.insert(Flags::UNHANDLED); InMessageType::Unhandled } }; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 4e196ad5..2a276b7a 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -10,7 +10,7 @@ mod service; pub use self::codec::{Codec, InMessage, InMessageType, OutMessage}; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; -pub use self::service::{H1Service, H1ServiceHandler}; +pub use self::service::{H1Service, H1ServiceHandler, H1SimpleService}; use request::Request; diff --git a/src/h1/service.rs b/src/h1/service.rs index 6e9d7d65..bf92e8d2 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -2,15 +2,18 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::net; +use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; -use futures::{Async, Future, Poll}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use config::{KeepAlive, ServiceConfig}; -use error::DispatchError; +use error::{DispatchError, ParseError}; use request::Request; use response::Response; +use super::codec::{Codec, InMessage}; use super::dispatcher::Dispatcher; use super::H1ServiceResult; @@ -191,6 +194,7 @@ where } } +#[doc(hidden)] pub struct H1ServiceResponse { fut: S::Future, cfg: Option, @@ -256,3 +260,94 @@ where Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } + +/// `NewService` implementation for `H1SimpleServiceHandler` service +pub struct H1SimpleService { + config: ServiceConfig, + _t: PhantomData, +} + +impl H1SimpleService { + /// Create new `H1SimpleService` instance. + pub fn new() -> Self { + H1SimpleService { + config: ServiceConfig::default(), + _t: PhantomData, + } + } +} + +impl NewService for H1SimpleService +where + T: AsyncRead + AsyncWrite, +{ + type Request = T; + type Response = (Request, Framed); + type Error = ParseError; + type InitError = (); + type Service = H1SimpleServiceHandler; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(H1SimpleServiceHandler { + config: self.config.clone(), + _t: PhantomData, + }) + } +} + +/// `Service` implementation for HTTP1 transport. Reads one request and returns +/// request and framed object. +pub struct H1SimpleServiceHandler { + config: ServiceConfig, + _t: PhantomData, +} + +impl Service for H1SimpleServiceHandler +where + T: AsyncRead + AsyncWrite, +{ + type Request = T; + type Response = (Request, Framed); + type Error = ParseError; + type Future = H1SimpleServiceHandlerResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + H1SimpleServiceHandlerResponse { + framed: Some(Framed::new(req, Codec::new(self.config.clone()))), + } + } +} + +#[doc(hidden)] +pub struct H1SimpleServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, +{ + framed: Option>, +} + +impl Future for H1SimpleServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, +{ + type Item = (Request, Framed); + type Error = ParseError; + + fn poll(&mut self) -> Poll { + match self.framed.as_mut().unwrap().poll()? { + Async::Ready(Some(req)) => match req { + InMessage::Message(req, _) => { + Ok(Async::Ready((req, self.framed.take().unwrap()))) + } + InMessage::Chunk(_) => unreachable!("Something is wrong"), + }, + Async::Ready(None) => Err(ParseError::Incomplete), + Async::NotReady => Ok(Async::NotReady), + } + } +} From 20c693b39c0e80436cc6c1d0e3641a226f8c8fe1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 15 Oct 2018 16:46:13 -0700 Subject: [PATCH 0774/1635] rename service --- src/h1/mod.rs | 2 +- src/h1/service.rs | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 2a276b7a..1ac65912 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -10,7 +10,7 @@ mod service; pub use self::codec::{Codec, InMessage, InMessageType, OutMessage}; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; -pub use self::service::{H1Service, H1ServiceHandler, H1SimpleService}; +pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; use request::Request; diff --git a/src/h1/service.rs b/src/h1/service.rs index bf92e8d2..404ded6b 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -261,23 +261,23 @@ where } } -/// `NewService` implementation for `H1SimpleServiceHandler` service -pub struct H1SimpleService { +/// `NewService` implementation for `OneRequestService` service +pub struct OneRequest { config: ServiceConfig, _t: PhantomData, } -impl H1SimpleService { +impl OneRequest { /// Create new `H1SimpleService` instance. pub fn new() -> Self { - H1SimpleService { + OneRequest { config: ServiceConfig::default(), _t: PhantomData, } } } -impl NewService for H1SimpleService +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { @@ -285,11 +285,11 @@ where type Response = (Request, Framed); type Error = ParseError; type InitError = (); - type Service = H1SimpleServiceHandler; + type Service = OneRequestService; type Future = FutureResult; fn new_service(&self) -> Self::Future { - ok(H1SimpleServiceHandler { + ok(OneRequestService { config: self.config.clone(), _t: PhantomData, }) @@ -298,40 +298,40 @@ where /// `Service` implementation for HTTP1 transport. Reads one request and returns /// request and framed object. -pub struct H1SimpleServiceHandler { +pub struct OneRequestService { config: ServiceConfig, _t: PhantomData, } -impl Service for H1SimpleServiceHandler +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { type Request = T; type Response = (Request, Framed); type Error = ParseError; - type Future = H1SimpleServiceHandlerResponse; + type Future = OneRequestServiceResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } fn call(&mut self, req: Self::Request) -> Self::Future { - H1SimpleServiceHandlerResponse { + OneRequestServiceResponse { framed: Some(Framed::new(req, Codec::new(self.config.clone()))), } } } #[doc(hidden)] -pub struct H1SimpleServiceHandlerResponse +pub struct OneRequestServiceResponse where T: AsyncRead + AsyncWrite, { framed: Option>, } -impl Future for H1SimpleServiceHandlerResponse +impl Future for OneRequestServiceResponse where T: AsyncRead + AsyncWrite, { From f383f618b537d6912ba9e2e7e27402b7bba964e1 Mon Sep 17 00:00:00 2001 From: ivan-ochc Date: Thu, 18 Oct 2018 21:27:31 +0300 Subject: [PATCH 0775/1635] Fix typo in error message (#554) --- src/pipeline.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index 1940f930..a938f2eb 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -551,12 +551,12 @@ impl ProcessResponse { if self.resp.as_ref().unwrap().status().is_server_error() { error!( - "Error occured during request handling, status: {} {}", + "Error occurred during request handling, status: {} {}", self.resp.as_ref().unwrap().status(), err ); } else { warn!( - "Error occured during request handling: {}", + "Error occurred during request handling: {}", err ); } From 960274ada8540064364579cb5a7caeb289ae7340 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 19 Oct 2018 07:52:10 +0300 Subject: [PATCH 0776/1635] Refactoring of server output to not exclude HTTP_10 (#552) --- CHANGES.md | 6 ++++++ src/server/output.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 62d2e915..8ac1724a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.7.14] - 2018-10-x + +### Fixed + +* HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549 + ## [0.7.13] - 2018-10-14 ### Fixed diff --git a/src/server/output.rs b/src/server/output.rs index 104700d4..ac89d644 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -300,10 +300,10 @@ impl Output { Some(true) => { // Enable transfer encoding info.length = ResponseLength::Chunked; - if version == Version::HTTP_11 { - TransferEncoding::chunked(buf) - } else { + if version == Version::HTTP_2 { TransferEncoding::eof(buf) + } else { + TransferEncoding::chunked(buf) } } Some(false) => TransferEncoding::eof(buf), @@ -337,10 +337,10 @@ impl Output { } else { // Enable transfer encoding info.length = ResponseLength::Chunked; - if version == Version::HTTP_11 { - TransferEncoding::chunked(buf) - } else { + if version == Version::HTTP_2 { TransferEncoding::eof(buf) + } else { + TransferEncoding::chunked(buf) } } } From 42d5d48e7105270e89eb8af6b111c305c5536e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 20 Oct 2018 05:43:43 +0200 Subject: [PATCH 0777/1635] add a way to configure error treatment for Query and Path extractors (#550) * add a way to configure error treatment for Query extractor * allow error handler to be customized for Path extractor --- CHANGES.md | 4 ++ src/extractor.rs | 122 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8ac1724a..f5adb82c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ * HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549 +### Added + +* Add method to configure custom error handler to `Query` and `Path` extractors. + ## [0.7.13] - 2018-10-14 ### Fixed diff --git a/src/extractor.rs b/src/extractor.rs index 7b0b4b00..45e29ace 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -111,18 +111,64 @@ impl FromRequest for Path where T: DeserializeOwned, { - type Config = (); + type Config = PathConfig; type Result = Result; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { let req = req.clone(); + let req2 = req.clone(); + let err = Rc::clone(&cfg.ehandler); de::Deserialize::deserialize(PathDeserializer::new(&req)) - .map_err(ErrorNotFound) + .map_err(move |e| (*err)(e, &req2)) .map(|inner| Path { inner }) } } +/// Path extractor configuration +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::{error, http, App, HttpResponse, Path, Result}; +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: Path<(u32, String)>) -> Result { +/// Ok(format!("Welcome {}!", info.1)) +/// } +/// +/// fn main() { +/// let app = App::new().resource("/index.html/{id}/{name}", |r| { +/// r.method(http::Method::GET).with_config(index, |cfg| { +/// cfg.0.error_handler(|err, req| { +/// // <- create custom error response +/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// }); +/// }) +/// }); +/// } +/// ``` +pub struct PathConfig { + ehandler: Rc) -> Error>, +} +impl PathConfig { + /// Set custom error handler + pub fn error_handler(&mut self, f: F) -> &mut Self + where + F: Fn(serde_urlencoded::de::Error, &HttpRequest) -> Error + 'static, + { + self.ehandler = Rc::new(f); + self + } +} + +impl Default for PathConfig { + fn default() -> Self { + PathConfig { + ehandler: Rc::new(|e, _| ErrorNotFound(e)), + } + } +} + impl fmt::Debug for Path { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) @@ -200,17 +246,69 @@ impl FromRequest for Query where T: de::DeserializeOwned, { - type Config = (); + type Config = QueryConfig; type Result = Result; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + let req2 = req.clone(); + let err = Rc::clone(&cfg.ehandler); serde_urlencoded::from_str::(req.query_string()) - .map_err(|e| e.into()) + .map_err(move |e| (*err)(e, &req2)) .map(Query) } } +/// Query extractor configuration +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{error, http, App, HttpResponse, Query, Result}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: Query) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource("/index.html", |r| { +/// r.method(http::Method::GET).with_config(index, |cfg| { +/// cfg.0.error_handler(|err, req| { +/// // <- create custom error response +/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// }); +/// }) +/// }); +/// } +/// ``` +pub struct QueryConfig { + ehandler: Rc) -> Error>, +} +impl QueryConfig { + /// Set custom error handler + pub fn error_handler(&mut self, f: F) -> &mut Self + where + F: Fn(serde_urlencoded::de::Error, &HttpRequest) -> Error + 'static, + { + self.ehandler = Rc::new(f); + self + } +} + +impl Default for QueryConfig { + fn default() -> Self { + QueryConfig { + ehandler: Rc::new(|e, _| e.into()), + } + } +} + impl fmt::Debug for Query { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) @@ -951,15 +1049,15 @@ mod tests { let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); - let s = Path::::from_request(&req, &()).unwrap(); + let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); - let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); + let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap(); assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); - let s = Query::::from_request(&req, &()).unwrap(); + let s = Query::::from_request(&req, &QueryConfig::default()).unwrap(); assert_eq!(s.id, "test"); let mut router = Router::<()>::default(); @@ -968,11 +1066,11 @@ mod tests { let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); - let s = Path::::from_request(&req, &()).unwrap(); + let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); assert_eq!(s.as_ref().key, "name"); assert_eq!(s.value, 32); - let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); + let s = Path::<(String, u8)>::from_request(&req, &PathConfig::default()).unwrap(); assert_eq!(s.0, "name"); assert_eq!(s.1, 32); @@ -989,7 +1087,7 @@ mod tests { let req = TestRequest::with_uri("/32/").finish(); let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); - assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); + assert_eq!(*Path::::from_request(&req, &&PathConfig::default()).unwrap(), 32); } #[test] From 9b94eaa6a8017cf203fef999c75366b56554216f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Oct 2018 09:59:20 -0700 Subject: [PATCH 0778/1635] ws services --- src/error.rs | 7 ++ src/h1/service.rs | 5 +- src/lib.rs | 2 + src/service.rs | 185 ++++++++++++++++++++++++++++++++++++++++++++++ src/ws/mod.rs | 44 +++++++---- src/ws/service.rs | 52 +++++++++++++ tests/test_ws.rs | 9 ++- 7 files changed, 284 insertions(+), 20 deletions(-) create mode 100644 src/service.rs create mode 100644 src/ws/service.rs diff --git a/src/error.rs b/src/error.rs index 465b8ae0..3c090203 100644 --- a/src/error.rs +++ b/src/error.rs @@ -632,6 +632,13 @@ where } } +/// Convert Response to a Error +impl From for Error { + fn from(res: Response) -> Error { + InternalError::from_response("", res).into() + } +} + /// Helper function that creates wrapper of any error and generate *BAD /// REQUEST* response. #[allow(non_snake_case)] diff --git a/src/h1/service.rs b/src/h1/service.rs index 404ded6b..096cb301 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -267,7 +267,10 @@ pub struct OneRequest { _t: PhantomData, } -impl OneRequest { +impl OneRequest +where + T: AsyncRead + AsyncWrite, +{ /// Create new `H1SimpleService` instance. pub fn new() -> Self { OneRequest { diff --git a/src/lib.rs b/src/lib.rs index 5ce3ec39..c9cebb47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,6 +110,7 @@ mod json; mod payload; mod request; mod response; +mod service; pub mod uri; pub mod error; @@ -123,6 +124,7 @@ pub use extensions::Extensions; pub use httpmessage::HttpMessage; pub use request::Request; pub use response::Response; +pub use service::{SendError, SendResponse}; pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 00000000..4467087b --- /dev/null +++ b/src/service.rs @@ -0,0 +1,185 @@ +use std::io; +use std::marker::PhantomData; + +use actix_net::codec::Framed; +use actix_net::service::{NewService, Service}; +use futures::future::{ok, Either, FutureResult}; +use futures::{Async, AsyncSink, Future, Poll, Sink}; +use tokio_io::AsyncWrite; + +use error::ResponseError; +use h1::{Codec, OutMessage}; +use response::Response; + +pub struct SendError(PhantomData<(T, R, E)>); + +impl Default for SendError +where + T: AsyncWrite, + E: ResponseError, +{ + fn default() -> Self { + SendError(PhantomData) + } +} + +impl NewService for SendError +where + T: AsyncWrite, + E: ResponseError, +{ + type Request = Result)>; + type Response = R; + type Error = (E, Framed); + type InitError = (); + type Service = SendError; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(SendError(PhantomData)) + } +} + +impl Service for SendError +where + T: AsyncWrite, + E: ResponseError, +{ + type Request = Result)>; + type Response = R; + type Error = (E, Framed); + type Future = Either)>, SendErrorFut>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + match req { + Ok(r) => Either::A(ok(r)), + Err((e, framed)) => Either::B(SendErrorFut { + framed: Some(framed), + res: Some(OutMessage::Response(e.error_response())), + err: Some(e), + _t: PhantomData, + }), + } + } +} + +pub struct SendErrorFut { + res: Option, + framed: Option>, + err: Option, + _t: PhantomData, +} + +impl Future for SendErrorFut +where + E: ResponseError, + T: AsyncWrite, +{ + type Item = R; + type Error = (E, Framed); + + fn poll(&mut self) -> Poll { + if let Some(res) = self.res.take() { + match self.framed.as_mut().unwrap().start_send(res) { + Ok(AsyncSink::Ready) => (), + Ok(AsyncSink::NotReady(res)) => { + self.res = Some(res); + return Ok(Async::NotReady); + } + Err(_) => { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())) + } + } + } + match self.framed.as_mut().unwrap().poll_complete() { + Ok(Async::Ready(_)) => { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(_) => { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())) + } + } + } +} + +pub struct SendResponse(PhantomData<(T,)>); + +impl Default for SendResponse +where + T: AsyncWrite, +{ + fn default() -> Self { + SendResponse(PhantomData) + } +} + +impl NewService for SendResponse +where + T: AsyncWrite, +{ + type Request = (Response, Framed); + type Response = Framed; + type Error = io::Error; + type InitError = (); + type Service = SendResponse; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(SendResponse(PhantomData)) + } +} + +impl Service for SendResponse +where + T: AsyncWrite, +{ + type Request = (Response, Framed); + type Response = Framed; + type Error = io::Error; + type Future = SendResponseFut; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (res, framed): Self::Request) -> Self::Future { + SendResponseFut { + res: Some(OutMessage::Response(res)), + framed: Some(framed), + } + } +} + +pub struct SendResponseFut { + res: Option, + framed: Option>, +} + +impl Future for SendResponseFut +where + T: AsyncWrite, +{ + type Item = Framed; + type Error = io::Error; + + fn poll(&mut self) -> Poll { + if let Some(res) = self.res.take() { + match self.framed.as_mut().unwrap().start_send(res)? { + AsyncSink::Ready => (), + AsyncSink::NotReady(res) => { + self.res = Some(res); + return Ok(Async::NotReady); + } + } + } + match self.framed.as_mut().unwrap().poll_complete()? { + Async::Ready(_) => Ok(Async::Ready(self.framed.take().unwrap())), + Async::NotReady => Ok(Async::NotReady), + } + } +} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 7df1f4b4..690c56fe 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -14,11 +14,13 @@ mod codec; mod frame; mod mask; mod proto; +mod service; mod transport; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; +pub use self::service::VerifyWebSockets; pub use self::transport::Transport; /// Websocket protocol errors @@ -109,15 +111,20 @@ impl ResponseError for HandshakeError { } } -/// Prepare `WebSocket` handshake response. -/// -/// This function returns handshake `Response`, ready to send to peer. -/// It does not perform any IO. -/// +/// Verify `WebSocket` handshake request and create handshake reponse. // /// `protocols` is a sequence of known protocols. On successful handshake, // /// the returned response headers contain the first protocol in this list // /// which the server also knows. pub fn handshake(req: &Request) -> Result { + verify_handshake(req)?; + Ok(handshake_response(req)) +} + +/// Verify `WebSocket` handshake request. +// /// `protocols` is a sequence of known protocols. On successful handshake, +// /// the returned response headers contain the first protocol in this list +// /// which the server also knows. +pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); @@ -161,17 +168,24 @@ pub fn handshake(req: &Request) -> Result { if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) { return Err(HandshakeError::BadWebsocketKey); } + Ok(()) +} + +/// Create websocket's handshake response +/// +/// This function returns handshake `Response`, ready to send to peer. +pub fn handshake_response(req: &Request) -> ResponseBuilder { let key = { let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap(); proto::hash_key(key.as_ref()) }; - Ok(Response::build(StatusCode::SWITCHING_PROTOCOLS) + Response::build(StatusCode::SWITCHING_PROTOCOLS) .connection_type(ConnectionType::Upgrade) .header(header::UPGRADE, "websocket") .header(header::TRANSFER_ENCODING, "chunked") .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) - .take()) + .take() } #[cfg(test)] @@ -185,13 +199,13 @@ mod tests { let req = TestRequest::default().method(Method::POST).finish(); assert_eq!( HandshakeError::GetMethodRequired, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default().finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -199,7 +213,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -209,7 +223,7 @@ mod tests { ).finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -222,7 +236,7 @@ mod tests { ).finish(); assert_eq!( HandshakeError::NoVersionHeader, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -238,7 +252,7 @@ mod tests { ).finish(); assert_eq!( HandshakeError::UnsupportedVersion, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -254,7 +268,7 @@ mod tests { ).finish(); assert_eq!( HandshakeError::BadWebsocketKey, - handshake(&req).err().unwrap() + verify_handshake(&req).err().unwrap() ); let req = TestRequest::default() @@ -273,7 +287,7 @@ mod tests { ).finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, - handshake(&req).unwrap().finish().status() + handshake_response(&req).finish().status() ); } diff --git a/src/ws/service.rs b/src/ws/service.rs new file mode 100644 index 00000000..9cce4d63 --- /dev/null +++ b/src/ws/service.rs @@ -0,0 +1,52 @@ +use std::marker::PhantomData; + +use actix_net::codec::Framed; +use actix_net::service::{NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, IntoFuture, Poll}; + +use h1::Codec; +use request::Request; + +use super::{verify_handshake, HandshakeError}; + +pub struct VerifyWebSockets { + _t: PhantomData, +} + +impl Default for VerifyWebSockets { + fn default() -> Self { + VerifyWebSockets { _t: PhantomData } + } +} + +impl NewService for VerifyWebSockets { + type Request = (Request, Framed); + type Response = (Request, Framed); + type Error = (HandshakeError, Framed); + type InitError = (); + type Service = VerifyWebSockets; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(VerifyWebSockets { _t: PhantomData }) + } +} + +impl Service for VerifyWebSockets { + type Request = (Request, Framed); + type Response = (Request, Framed); + type Error = (HandshakeError, Framed); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (req, framed): Self::Request) -> Self::Future { + match verify_handshake(&req) { + Err(e) => Err((e, framed)).into_future(), + Ok(_) => Ok((req, framed)).into_future(), + } + } +} diff --git a/tests/test_ws.rs b/tests/test_ws.rs index f475cd22..a5503c20 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -52,7 +52,7 @@ fn test_simple() { .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request if let Some(h1::InMessage::Message(req, _)) = req { - match ws::handshake(&req) { + match ws::verify_handshake(&req) { Err(e) => { // validation failed let resp = e.error_response(); @@ -63,11 +63,12 @@ fn test_simple() { .map(|_| ()), ) } - Ok(mut resp) => Either::B( + Ok(_) => Either::B( // send response framed - .send(h1::OutMessage::Response(resp.finish())) - .map_err(|_| ()) + .send(h1::OutMessage::Response( + ws::handshake_response(&req).finish(), + )).map_err(|_| ()) .and_then(|framed| { // start websocket service let framed = From 09c94cb06be623d21aead3173707268b185ab78b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Oct 2018 18:18:05 -0700 Subject: [PATCH 0779/1635] add client http codec; websockets client --- Cargo.toml | 3 + src/client/mod.rs | 6 + src/client/request.rs | 564 +++++++++++++++++++++++++++++++++++++++ src/client/response.rs | 128 +++++++++ src/h1/client.rs | 217 +++++++++++++++ src/h1/codec.rs | 76 ++---- src/h1/decoder.rs | 194 ++++++++++++-- src/h1/dispatcher.rs | 71 ++--- src/h1/encoder.rs | 34 +++ src/h1/mod.rs | 28 +- src/h1/service.rs | 8 +- src/lib.rs | 3 + src/request.rs | 53 ++-- src/service.rs | 10 +- src/ws/client/connect.rs | 95 +++++++ src/ws/client/error.rs | 87 ++++++ src/ws/client/mod.rs | 48 ++++ src/ws/client/service.rs | 270 +++++++++++++++++++ src/ws/mod.rs | 2 + tests/test_ws.rs | 53 +++- 20 files changed, 1802 insertions(+), 148 deletions(-) create mode 100644 src/client/mod.rs create mode 100644 src/client/request.rs create mode 100644 src/client/response.rs create mode 100644 src/h1/client.rs create mode 100644 src/ws/client/connect.rs create mode 100644 src/ws/client/error.rs create mode 100644 src/ws/client/mod.rs create mode 100644 src/ws/client/service.rs diff --git a/Cargo.toml b/Cargo.toml index 9193aeea..87bd05b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,10 @@ time = "0.1" encoding = "0.2" lazy_static = "1.0" serde_urlencoded = "0.5.3" + cookie = { version="0.11", features=["percent-encode"] } +percent-encoding = "1.0" +url = { version="1.7", features=["query_encoding"] } # io net2 = "0.2" diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 00000000..a5582fea --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1,6 @@ +//! Http client api +mod request; +mod response; + +pub use self::request::{ClientRequest, ClientRequestBuilder}; +pub use self::response::ClientResponse; diff --git a/src/client/request.rs b/src/client/request.rs new file mode 100644 index 00000000..bf82927e --- /dev/null +++ b/src/client/request.rs @@ -0,0 +1,564 @@ +use std::fmt; +use std::fmt::Write as FmtWrite; +use std::io::Write; + +use bytes::{BufMut, BytesMut}; +use cookie::{Cookie, CookieJar}; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use urlcrate::Url; + +use header::{self, Header, IntoHeaderValue}; +use http::{ + uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, + Uri, Version, +}; + +/// An HTTP Client Request +/// +/// ```rust +/// # extern crate actix_web; +/// # extern crate futures; +/// # extern crate tokio; +/// # use futures::Future; +/// # use std::process; +/// use actix_web::{actix, client}; +/// +/// fn main() { +/// actix::run( +/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .finish().unwrap() +/// .send() // <- Send http request +/// .map_err(|_| ()) +/// .and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// # actix::System::current().stop(); +/// Ok(()) +/// }), +/// ); +/// } +/// ``` +pub struct ClientRequest { + uri: Uri, + method: Method, + version: Version, + headers: HeaderMap, + chunked: bool, + upgrade: bool, +} + +impl Default for ClientRequest { + fn default() -> ClientRequest { + ClientRequest { + uri: Uri::default(), + method: Method::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + chunked: false, + upgrade: false, + } + } +} + +impl ClientRequest { + /// Create request builder for `GET` request + pub fn get>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::GET).uri(uri); + builder + } + + /// Create request builder for `HEAD` request + pub fn head>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::HEAD).uri(uri); + builder + } + + /// Create request builder for `POST` request + pub fn post>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::POST).uri(uri); + builder + } + + /// Create request builder for `PUT` request + pub fn put>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::PUT).uri(uri); + builder + } + + /// Create request builder for `DELETE` request + pub fn delete>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::DELETE).uri(uri); + builder + } +} + +impl ClientRequest { + /// Create client request builder + pub fn build() -> ClientRequestBuilder { + ClientRequestBuilder { + request: Some(ClientRequest::default()), + err: None, + cookies: None, + default_headers: true, + } + } + + /// Get the request URI + #[inline] + pub fn uri(&self) -> &Uri { + &self.uri + } + + /// Set client request URI + #[inline] + pub fn set_uri(&mut self, uri: Uri) { + self.uri = uri + } + + /// Get the request method + #[inline] + pub fn method(&self) -> &Method { + &self.method + } + + /// Set HTTP `Method` for the request + #[inline] + pub fn set_method(&mut self, method: Method) { + self.method = method + } + + /// Get HTTP version for the request + #[inline] + pub fn version(&self) -> Version { + self.version + } + + /// Set http `Version` for the request + #[inline] + pub fn set_version(&mut self, version: Version) { + self.version = version + } + + /// Get the headers from the request + #[inline] + pub fn headers(&self) -> &HeaderMap { + &self.headers + } + + /// Get a mutable reference to the headers + #[inline] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + + /// is chunked encoding enabled + #[inline] + pub fn chunked(&self) -> bool { + self.chunked + } + + /// is upgrade request + #[inline] + pub fn upgrade(&self) -> bool { + self.upgrade + } +} + +impl fmt::Debug for ClientRequest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "\nClientRequest {:?} {}:{}", + self.version, self.method, self.uri + )?; + writeln!(f, " headers:")?; + for (key, val) in self.headers.iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} + +/// An HTTP Client request builder +/// +/// This type can be used to construct an instance of `ClientRequest` through a +/// builder-like pattern. +pub struct ClientRequestBuilder { + request: Option, + err: Option, + cookies: Option, + default_headers: bool, +} + +impl ClientRequestBuilder { + /// Set HTTP URI of request. + #[inline] + pub fn uri>(&mut self, uri: U) -> &mut Self { + match Url::parse(uri.as_ref()) { + Ok(url) => self._uri(url.as_str()), + Err(_) => self._uri(uri.as_ref()), + } + } + + fn _uri(&mut self, url: &str) -> &mut Self { + match Uri::try_from(url) { + Ok(uri) => { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.uri = uri; + } + } + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Set HTTP method of this request. + #[inline] + pub fn method(&mut self, method: Method) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.method = method; + } + self + } + + /// Set HTTP method of this request. + #[inline] + pub fn get_method(&mut self) -> &Method { + let parts = self.request.as_ref().expect("cannot reuse request builder"); + &parts.method + } + + /// Set HTTP version of this request. + /// + /// By default requests's HTTP version depends on network stream + #[inline] + pub fn version(&mut self, version: Version) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.version = version; + } + self + } + + /// Set a header. + /// + /// ```rust + /// # extern crate mime; + /// # extern crate actix_web; + /// # use actix_web::client::*; + /// # + /// use actix_web::{client, http}; + /// + /// fn main() { + /// let req = client::ClientRequest::build() + /// .set(http::header::Date::now()) + /// .set(http::header::ContentType(mime::TEXT_HTML)) + /// .finish() + /// .unwrap(); + /// } + /// ``` + #[doc(hidden)] + pub fn set(&mut self, hdr: H) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + match hdr.try_into() { + Ok(value) => { + parts.headers.insert(H::name(), value); + } + Err(e) => self.err = Some(e.into()), + } + } + self + } + + /// Append a header. + /// + /// Header gets appended to existing header. + /// To override header use `set_header()` method. + /// + /// ```rust + /// # extern crate http; + /// # extern crate actix_web; + /// # use actix_web::client::*; + /// # + /// use http::header; + /// + /// fn main() { + /// let req = ClientRequest::build() + /// .header("X-TEST", "value") + /// .header(header::CONTENT_TYPE, "application/json") + /// .finish() + /// .unwrap(); + /// } + /// ``` + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + parts.headers.append(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Set a header. + pub fn set_header(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Set a header only if it is not yet set. + pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => if !parts.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + } + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Enable connection upgrade + #[inline] + pub fn upgrade(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.request, &self.err) { + parts.upgrade = true; + } + self + } + + /// Set request's content type + #[inline] + pub fn content_type(&mut self, value: V) -> &mut Self + where + HeaderValue: HttpTryFrom, + { + if let Some(parts) = parts(&mut self.request, &self.err) { + match HeaderValue::try_from(value) { + Ok(value) => { + parts.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Set content length + #[inline] + pub fn content_length(&mut self, len: u64) -> &mut Self { + let mut wrt = BytesMut::new().writer(); + let _ = write!(wrt, "{}", len); + self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) + } + + /// Set a cookie + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{client, http}; + /// + /// fn main() { + /// let req = client::ClientRequest::build() + /// .cookie( + /// http::Cookie::build("name", "value") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .secure(true) + /// .http_only(true) + /// .finish(), + /// ) + /// .finish() + /// .unwrap(); + /// } + /// ``` + pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { + 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 + } + + /// Do not add default request headers. + /// By default `Accept-Encoding` and `User-Agent` headers are set. + pub fn no_default_headers(&mut self) -> &mut Self { + self.default_headers = false; + self + } + + /// This method calls provided closure with builder reference if + /// value is `true`. + pub fn if_true(&mut self, value: bool, f: F) -> &mut Self + where + F: FnOnce(&mut ClientRequestBuilder), + { + if value { + f(self); + } + self + } + + /// This method calls provided closure with builder reference if + /// value is `Some`. + pub fn if_some(&mut self, value: Option, f: F) -> &mut Self + where + F: FnOnce(T, &mut ClientRequestBuilder), + { + if let Some(val) = value { + f(val, self); + } + self + } + + /// Set a body and generate `ClientRequest`. + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn finish(&mut self) -> Result { + if let Some(e) = self.err.take() { + return Err(e); + } + + if self.default_headers { + // enable br only for https + let https = if let Some(parts) = parts(&mut self.request, &self.err) { + parts + .uri + .scheme_part() + .map(|s| s == &uri::Scheme::HTTPS) + .unwrap_or(true) + } else { + true + }; + + if https { + self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); + } else { + self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); + } + + // set request host header + if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(host) = parts.uri.host() { + if !parts.headers.contains_key(header::HOST) { + let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); + + let _ = match parts.uri.port() { + None | Some(80) | Some(443) => write!(wrt, "{}", host), + Some(port) => write!(wrt, "{}:{}", host, port), + }; + + match wrt.get_mut().take().freeze().try_into() { + Ok(value) => { + parts.headers.insert(header::HOST, value); + } + Err(e) => self.err = Some(e.into()), + } + } + } + } + + // user agent + self.set_header_if_none( + header::USER_AGENT, + concat!("actix-http/", env!("CARGO_PKG_VERSION")), + ); + } + + let mut request = self.request.take().expect("cannot reuse request builder"); + + // set cookies + if let Some(ref mut jar) = self.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + request.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } + Ok(request) + } + + /// This method construct new `ClientRequestBuilder` + pub fn take(&mut self) -> ClientRequestBuilder { + ClientRequestBuilder { + request: self.request.take(), + err: self.err.take(), + cookies: self.cookies.take(), + default_headers: self.default_headers, + } + } +} + +#[inline] +fn parts<'a>( + parts: &'a mut Option, err: &Option, +) -> Option<&'a mut ClientRequest> { + if err.is_some() { + return None; + } + parts.as_mut() +} + +impl fmt::Debug for ClientRequestBuilder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(ref parts) = self.request { + writeln!( + f, + "\nClientRequestBuilder {:?} {}:{}", + parts.version, parts.method, parts.uri + )?; + writeln!(f, " headers:")?; + for (key, val) in parts.headers.iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } else { + write!(f, "ClientRequestBuilder(Consumed)") + } + } +} diff --git a/src/client/response.rs b/src/client/response.rs new file mode 100644 index 00000000..627a1c78 --- /dev/null +++ b/src/client/response.rs @@ -0,0 +1,128 @@ +use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::fmt; +use std::rc::Rc; + +use http::{HeaderMap, Method, StatusCode, Version}; + +use extensions::Extensions; +use httpmessage::HttpMessage; +use payload::Payload; +use request::{Message, MessageFlags, MessagePool}; +use uri::Url; + +/// Client Response +pub struct ClientResponse { + pub(crate) inner: Rc, +} + +impl HttpMessage for ClientResponse { + type Stream = Payload; + + fn headers(&self) -> &HeaderMap { + &self.inner.headers + } + + #[inline] + fn payload(&self) -> Payload { + if let Some(payload) = self.inner.payload.borrow_mut().take() { + payload + } else { + Payload::empty() + } + } +} + +impl ClientResponse { + /// Create new Request instance + pub fn new() -> ClientResponse { + ClientResponse::with_pool(MessagePool::pool()) + } + + /// Create new Request instance with pool + pub(crate) fn with_pool(pool: &'static MessagePool) -> ClientResponse { + ClientResponse { + inner: Rc::new(Message { + pool, + method: Method::GET, + status: StatusCode::OK, + url: Url::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + flags: Cell::new(MessageFlags::empty()), + payload: RefCell::new(None), + extensions: RefCell::new(Extensions::new()), + }), + } + } + + #[inline] + pub(crate) fn inner(&self) -> &Message { + self.inner.as_ref() + } + + #[inline] + pub(crate) fn inner_mut(&mut self) -> &mut Message { + Rc::get_mut(&mut self.inner).expect("Multiple copies exist") + } + + /// Read the Request Version. + #[inline] + pub fn version(&self) -> Version { + self.inner().version + } + + /// Get the status from the server. + #[inline] + pub fn status(&self) -> StatusCode { + self.inner().status + } + + #[inline] + /// Returns Request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.inner().headers + } + + #[inline] + /// Returns mutable Request's headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.inner_mut().headers + } + + /// Checks if a connection should be kept alive. + #[inline] + pub fn keep_alive(&self) -> bool { + self.inner().flags.get().contains(MessageFlags::KEEPALIVE) + } + + /// Request extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.inner().extensions.borrow() + } + + /// Mutable reference to a the request's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.inner().extensions.borrow_mut() + } +} + +impl Drop for ClientResponse { + fn drop(&mut self) { + if Rc::strong_count(&self.inner) == 1 { + self.inner.pool.release(self.inner.clone()); + } + } +} + +impl fmt::Debug for ClientResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; + writeln!(f, " headers:")?; + for (key, val) in self.headers().iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} diff --git a/src/h1/client.rs b/src/h1/client.rs new file mode 100644 index 00000000..8ec1f0ae --- /dev/null +++ b/src/h1/client.rs @@ -0,0 +1,217 @@ +#![allow(unused_imports, unused_variables, dead_code)] +use std::io::{self, Write}; + +use bytes::{BufMut, Bytes, BytesMut}; +use tokio_codec::{Decoder, Encoder}; + +use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder}; +use super::encoder::{RequestEncoder, ResponseLength}; +use super::{Message, MessageType}; +use body::{Binary, Body}; +use client::{ClientRequest, ClientResponse}; +use config::ServiceConfig; +use error::ParseError; +use helpers; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::{Method, Version}; +use request::MessagePool; + +bitflags! { + struct Flags: u8 { + const HEAD = 0b0000_0001; + const UPGRADE = 0b0000_0010; + const KEEPALIVE = 0b0000_0100; + const KEEPALIVE_ENABLED = 0b0000_1000; + const UNHANDLED = 0b0001_0000; + } +} + +const AVERAGE_HEADER_SIZE: usize = 30; + +/// HTTP/1 Codec +pub struct ClientCodec { + config: ServiceConfig, + decoder: ResponseDecoder, + payload: Option, + version: Version, + + // encoder part + flags: Flags, + headers_size: u32, + te: RequestEncoder, +} + +impl Default for ClientCodec { + fn default() -> Self { + ClientCodec::new(ServiceConfig::default()) + } +} + +impl ClientCodec { + /// Create HTTP/1 codec. + /// + /// `keepalive_enabled` how response `connection` header get generated. + pub fn new(config: ServiceConfig) -> Self { + ClientCodec::with_pool(MessagePool::pool(), config) + } + + /// Create HTTP/1 codec with request's pool + pub(crate) fn with_pool(pool: &'static MessagePool, config: ServiceConfig) -> Self { + let flags = if config.keep_alive_enabled() { + Flags::KEEPALIVE_ENABLED + } else { + Flags::empty() + }; + ClientCodec { + config, + decoder: ResponseDecoder::with_pool(pool), + payload: None, + version: Version::HTTP_11, + + flags, + headers_size: 0, + te: RequestEncoder::default(), + } + } + + /// Check if request is upgrade + pub fn upgrade(&self) -> bool { + self.flags.contains(Flags::UPGRADE) + } + + /// Check if last response is keep-alive + pub fn keepalive(&self) -> bool { + self.flags.contains(Flags::KEEPALIVE) + } + + /// Check last request's message type + pub fn message_type(&self) -> MessageType { + if self.flags.contains(Flags::UNHANDLED) { + MessageType::Unhandled + } else if self.payload.is_none() { + MessageType::None + } else { + MessageType::Payload + } + } + + /// prepare transfer encoding + pub fn prepare_te(&mut self, res: &mut ClientRequest) { + self.te + .update(res, self.flags.contains(Flags::HEAD), self.version); + } + + fn encode_response( + &mut self, msg: ClientRequest, buffer: &mut BytesMut, + ) -> io::Result<()> { + // Connection upgrade + if msg.upgrade() { + self.flags.insert(Flags::UPGRADE); + } + + // render message + { + // status line + writeln!( + Writer(buffer), + "{} {} {:?}\r", + msg.method(), + msg.uri() + .path_and_query() + .map(|u| u.as_str()) + .unwrap_or("/"), + msg.version() + ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + // write headers + buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); + 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"); + } + + // set date header + if !msg.headers().contains_key(DATE) { + self.config.set_date(buffer); + } else { + buffer.extend_from_slice(b"\r\n"); + } + } + + Ok(()) + } +} + +impl Decoder for ClientCodec { + type Item = Message; + type Error = ParseError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if self.payload.is_some() { + Ok(match self.payload.as_mut().unwrap().decode(src)? { + Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), + Some(PayloadItem::Eof) => Some(Message::Chunk(None)), + None => None, + }) + } else if self.flags.contains(Flags::UNHANDLED) { + Ok(None) + } else if let Some((req, payload)) = self.decoder.decode(src)? { + self.flags + .set(Flags::HEAD, req.inner.method == Method::HEAD); + self.version = req.inner.version; + if self.flags.contains(Flags::KEEPALIVE_ENABLED) { + self.flags.set(Flags::KEEPALIVE, req.keep_alive()); + } + match payload { + PayloadType::None => self.payload = None, + PayloadType::Payload(pl) => self.payload = Some(pl), + PayloadType::Unhandled => { + self.payload = None; + self.flags.insert(Flags::UNHANDLED); + } + }; + Ok(Some(Message::Item(req))) + } else { + Ok(None) + } + } +} + +impl Encoder for ClientCodec { + type Item = Message; + type Error = io::Error; + + fn encode( + &mut self, item: Self::Item, dst: &mut BytesMut, + ) -> Result<(), Self::Error> { + match item { + Message::Item(res) => { + self.encode_response(res, dst)?; + } + Message::Chunk(Some(bytes)) => { + self.te.encode(bytes.as_ref(), dst)?; + } + Message::Chunk(None) => { + self.te.encode_eof(dst)?; + } + } + Ok(()) + } +} + +pub struct Writer<'a>(pub &'a mut BytesMut); + +impl<'a> io::Write for Writer<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 02046648..8f7e77e0 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -4,15 +4,16 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder, RequestPayloadType}; +use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, RequestDecoder}; use super::encoder::{ResponseEncoder, ResponseLength}; +use super::{Message, MessageType}; use body::{Binary, Body}; use config::ServiceConfig; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; -use request::{Request, RequestPool}; +use request::{MessagePool, Request}; use response::Response; bitflags! { @@ -27,32 +28,6 @@ bitflags! { const AVERAGE_HEADER_SIZE: usize = 30; -#[derive(Debug)] -/// Http response -pub enum OutMessage { - /// Http response message - Response(Response), - /// Payload chunk - Chunk(Option), -} - -/// Incoming http/1 request -#[derive(Debug)] -pub enum InMessage { - /// Request - Message(Request, InMessageType), - /// Payload chunk - Chunk(Option), -} - -/// Incoming request type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum InMessageType { - None, - Payload, - Unhandled, -} - /// HTTP/1 Codec pub struct Codec { config: ServiceConfig, @@ -71,11 +46,11 @@ impl Codec { /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - Codec::with_pool(RequestPool::pool(), config) + Codec::with_pool(MessagePool::pool(), config) } /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool(pool: &'static RequestPool, config: ServiceConfig) -> Self { + pub(crate) fn with_pool(pool: &'static MessagePool, config: ServiceConfig) -> Self { let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE_ENABLED } else { @@ -104,13 +79,13 @@ impl Codec { } /// Check last request's message type - pub fn message_type(&self) -> InMessageType { + pub fn message_type(&self) -> MessageType { if self.flags.contains(Flags::UNHANDLED) { - InMessageType::Unhandled + MessageType::Unhandled } else if self.payload.is_none() { - InMessageType::None + MessageType::None } else { - InMessageType::Payload + MessageType::Payload } } @@ -256,14 +231,14 @@ impl Codec { } impl Decoder for Codec { - type Item = InMessage; + type Item = Message; type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(InMessage::Chunk(Some(chunk))), - Some(PayloadItem::Eof) => Some(InMessage::Chunk(None)), + Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), + Some(PayloadItem::Eof) => Some(Message::Chunk(None)), None => None, }) } else if self.flags.contains(Flags::UNHANDLED) { @@ -275,22 +250,15 @@ impl Decoder for Codec { if self.flags.contains(Flags::KEEPALIVE_ENABLED) { self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } - let payload = match payload { - RequestPayloadType::None => { - self.payload = None; - InMessageType::None - } - RequestPayloadType::Payload(pl) => { - self.payload = Some(pl); - InMessageType::Payload - } - RequestPayloadType::Unhandled => { + match payload { + PayloadType::None => self.payload = None, + PayloadType::Payload(pl) => self.payload = Some(pl), + PayloadType::Unhandled => { self.payload = None; self.flags.insert(Flags::UNHANDLED); - InMessageType::Unhandled } - }; - Ok(Some(InMessage::Message(req, payload))) + } + Ok(Some(Message::Item(req))) } else { Ok(None) } @@ -298,20 +266,20 @@ impl Decoder for Codec { } impl Encoder for Codec { - type Item = OutMessage; + type Item = Message; type Error = io::Error; fn encode( &mut self, item: Self::Item, dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - OutMessage::Response(res) => { + Message::Item(res) => { self.encode_response(res, dst)?; } - OutMessage::Chunk(Some(bytes)) => { + Message::Chunk(Some(bytes)) => { self.te.encode(bytes.as_ref(), dst)?; } - OutMessage::Chunk(None) => { + Message::Chunk(None) => { self.te.encode_eof(dst)?; } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index c2a8d0e9..2c1e6c1f 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -5,38 +5,43 @@ use futures::{Async, Poll}; use httparse; use tokio_codec::Decoder; +use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; -use http::{header, HttpTryFrom, Method, Uri, Version}; -use request::{MessageFlags, Request, RequestPool}; +use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; +use request::{MessageFlags, MessagePool, Request}; use uri::Url; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; -pub struct RequestDecoder(&'static RequestPool); +/// Client request decoder +pub struct RequestDecoder(&'static MessagePool); + +/// Server response decoder +pub struct ResponseDecoder(&'static MessagePool); /// Incoming request type -pub enum RequestPayloadType { +pub enum PayloadType { None, Payload(PayloadDecoder), Unhandled, } impl RequestDecoder { - pub(crate) fn with_pool(pool: &'static RequestPool) -> RequestDecoder { + pub(crate) fn with_pool(pool: &'static MessagePool) -> RequestDecoder { RequestDecoder(pool) } } impl Default for RequestDecoder { fn default() -> RequestDecoder { - RequestDecoder::with_pool(RequestPool::pool()) + RequestDecoder::with_pool(MessagePool::pool()) } } impl Decoder for RequestDecoder { - type Item = (Request, RequestPayloadType); + type Item = (Request, PayloadType); type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { @@ -77,7 +82,7 @@ impl Decoder for RequestDecoder { let slice = src.split_to(len).freeze(); // convert headers - let mut msg = RequestPool::get(self.0); + let mut msg = MessagePool::get_request(self.0); { let inner = msg.inner_mut(); inner @@ -156,18 +161,165 @@ impl Decoder for RequestDecoder { // https://tools.ietf.org/html/rfc7230#section-3.3.3 let decoder = if chunked { // Chunked encoding - RequestPayloadType::Payload(PayloadDecoder::chunked()) + PayloadType::Payload(PayloadDecoder::chunked()) } else if let Some(len) = content_length { // Content-Length - RequestPayloadType::Payload(PayloadDecoder::length(len)) + PayloadType::Payload(PayloadDecoder::length(len)) } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect - RequestPayloadType::Unhandled + PayloadType::Unhandled } else if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); } else { - RequestPayloadType::None + PayloadType::None + }; + + Ok(Some((msg, decoder))) + } +} + +impl ResponseDecoder { + pub(crate) fn with_pool(pool: &'static MessagePool) -> ResponseDecoder { + ResponseDecoder(pool) + } +} + +impl Default for ResponseDecoder { + fn default() -> ResponseDecoder { + ResponseDecoder::with_pool(MessagePool::pool()) + } +} + +impl Decoder for ResponseDecoder { + type Item = (ClientResponse, PayloadType); + type Error = ParseError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + // Parse http message + let mut chunked = false; + let mut content_length = None; + + let msg = { + // Unsafe: we read only this data only after httparse parses headers into. + // performance bump for pipeline benchmarks. + let mut headers: [HeaderIndex; MAX_HEADERS] = + unsafe { mem::uninitialized() }; + + let (len, version, status, headers_len) = { + let mut parsed: [httparse::Header; MAX_HEADERS] = + unsafe { mem::uninitialized() }; + + let mut res = httparse::Response::new(&mut parsed); + match res.parse(src)? { + httparse::Status::Complete(len) => { + let version = if res.version.unwrap() == 1 { + Version::HTTP_11 + } else { + Version::HTTP_10 + }; + let status = StatusCode::from_u16(res.code.unwrap()) + .map_err(|_| ParseError::Status)?; + HeaderIndex::record(src, res.headers, &mut headers); + + (len, version, status, res.headers.len()) + } + httparse::Status::Partial => return Ok(None), + } + }; + + let slice = src.split_to(len).freeze(); + + // convert headers + let mut msg = MessagePool::get_response(self.0); + { + let inner = msg.inner_mut(); + inner + .flags + .get_mut() + .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); + + for idx in headers[..headers_len].iter() { + if let Ok(name) = + HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) + { + // Unsafe: httparse check header value for valid utf-8 + let value = unsafe { + HeaderValue::from_shared_unchecked( + slice.slice(idx.value.0, idx.value.1), + ) + }; + match name { + header::CONTENT_LENGTH => { + if let Ok(s) = value.to_str() { + if let Ok(len) = s.parse::() { + content_length = Some(len); + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header); + } + } else { + debug!("illegal Content-Length: {:?}", len); + return Err(ParseError::Header); + } + } + // transfer-encoding + header::TRANSFER_ENCODING => { + if let Ok(s) = value.to_str() { + chunked = s.to_lowercase().contains("chunked"); + } else { + return Err(ParseError::Header); + } + } + // connection keep-alive state + header::CONNECTION => { + let ka = if let Ok(conn) = value.to_str() { + if version == Version::HTTP_10 + && conn.contains("keep-alive") + { + true + } else { + version == Version::HTTP_11 && !(conn + .contains("close") + || conn.contains("upgrade")) + } + } else { + false + }; + inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); + } + _ => (), + } + + inner.headers.append(name, value); + } else { + return Err(ParseError::Header); + } + } + + inner.status = status; + inner.version = version; + } + msg + }; + + // https://tools.ietf.org/html/rfc7230#section-3.3.3 + let decoder = if chunked { + // Chunked encoding + PayloadType::Payload(PayloadDecoder::chunked()) + } else if let Some(len) = content_length { + // Content-Length + PayloadType::Payload(PayloadDecoder::length(len)) + } else if msg.inner.status == StatusCode::SWITCHING_PROTOCOLS + || msg.inner.method == Method::CONNECT + { + // switching protocol or connect + PayloadType::Unhandled + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); + } else { + PayloadType::None }; Ok(Some((msg, decoder))) @@ -488,36 +640,30 @@ mod tests { use super::*; use error::ParseError; - use h1::{InMessage, InMessageType}; + use h1::Message; use httpmessage::HttpMessage; use request::Request; - impl RequestPayloadType { + impl PayloadType { fn unwrap(self) -> PayloadDecoder { match self { - RequestPayloadType::Payload(pl) => pl, + PayloadType::Payload(pl) => pl, _ => panic!(), } } fn is_unhandled(&self) -> bool { match self { - RequestPayloadType::Unhandled => true, + PayloadType::Unhandled => true, _ => false, } } } - impl InMessage { + impl Message { fn message(self) -> Request { match self { - InMessage::Message(req, _) => req, - _ => panic!("error"), - } - } - fn is_payload(&self) -> bool { - match *self { - InMessage::Message(_, payload) => payload == InMessageType::Payload, + Message::Item(req) => req, _ => panic!("error"), } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8ae2ae8c..a7f97e8c 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -18,8 +18,8 @@ use error::DispatchError; use request::Request; use response::Response; -use super::codec::{Codec, InMessage, InMessageType, OutMessage}; -use super::H1ServiceResult; +use super::codec::Codec; +use super::{H1ServiceResult, Message, MessageType}; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -48,14 +48,14 @@ where state: State, payload: Option, - messages: VecDeque, + messages: VecDeque, unhandled: Option, ka_expire: Instant, ka_timer: Option, } -enum Message { +enum DispatcherMessage { Item(Request), Error(Response), } @@ -63,8 +63,8 @@ enum Message { enum State { None, ServiceCall(S::Future), - SendResponse(Option<(OutMessage, Body)>), - SendPayload(Option, Option), + SendResponse(Option<(Message, Body)>), + SendPayload(Option, Option>), } impl State { @@ -176,11 +176,12 @@ where State::None => loop { break if let Some(msg) = self.messages.pop_front() { match msg { - Message::Item(req) => Some(self.handle_request(req)?), - Message::Error(res) => Some(State::SendResponse(Some(( - OutMessage::Response(res), - Body::Empty, - )))), + DispatcherMessage::Item(req) => { + Some(self.handle_request(req)?) + } + DispatcherMessage::Error(res) => Some(State::SendResponse( + Some((Message::Item(res), Body::Empty)), + )), } } else { None @@ -196,10 +197,7 @@ where .get_codec_mut() .prepare_te(&mut res); let body = res.replace_body(Body::Empty); - Some(State::SendResponse(Some(( - OutMessage::Response(res), - body, - )))) + Some(State::SendResponse(Some((Message::Item(res), body)))) } Async::NotReady => None, } @@ -216,9 +214,9 @@ where self.flags.remove(Flags::FLUSHED); match body { Body::Empty => Some(State::None), - Body::Binary(bin) => Some(State::SendPayload( + Body::Binary(mut bin) => Some(State::SendPayload( None, - Some(OutMessage::Chunk(bin.into())), + Some(Message::Chunk(Some(bin.take()))), )), Body::Streaming(stream) => { Some(State::SendPayload(Some(stream), None)) @@ -257,7 +255,7 @@ where .framed .as_mut() .unwrap() - .start_send(OutMessage::Chunk(Some(item.into()))) + .start_send(Message::Chunk(Some(item.into()))) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); @@ -271,7 +269,7 @@ where }, Ok(Async::Ready(None)) => Some(State::SendPayload( None, - Some(OutMessage::Chunk(None)), + Some(Message::Chunk(None)), )), Ok(Async::NotReady) => return Ok(()), // Err(err) => return Err(DispatchError::Io(err)), @@ -312,7 +310,7 @@ where .get_codec_mut() .prepare_te(&mut res); let body = res.replace_body(Body::Empty); - Ok(State::SendResponse(Some((OutMessage::Response(res), body)))) + Ok(State::SendResponse(Some((Message::Item(res), body)))) } Async::NotReady => Ok(State::ServiceCall(task)), } @@ -333,14 +331,20 @@ where self.flags.insert(Flags::STARTED); match msg { - InMessage::Message(req, payload) => { - match payload { - InMessageType::Payload => { + Message::Item(req) => { + match self + .framed + .as_ref() + .unwrap() + .get_codec() + .message_type() + { + MessageType::Payload => { let (ps, pl) = Payload::new(false); *req.inner.payload.borrow_mut() = Some(pl); self.payload = Some(ps); } - InMessageType::Unhandled => { + MessageType::Unhandled => { self.unhandled = Some(req); return Ok(updated); } @@ -351,10 +355,10 @@ where if self.state.is_empty() { self.state = self.handle_request(req)?; } else { - self.messages.push_back(Message::Item(req)); + self.messages.push_back(DispatcherMessage::Item(req)); } } - InMessage::Chunk(Some(chunk)) => { + Message::Chunk(Some(chunk)) => { if let Some(ref mut payload) = self.payload { payload.feed_data(chunk); } else { @@ -362,19 +366,19 @@ where "Internal server error: unexpected payload chunk" ); self.flags.insert(Flags::DISCONNECTED); - self.messages.push_back(Message::Error( + self.messages.push_back(DispatcherMessage::Error( Response::InternalServerError().finish(), )); self.error = Some(DispatchError::InternalError); } } - InMessage::Chunk(None) => { + Message::Chunk(None) => { if let Some(mut payload) = self.payload.take() { payload.feed_eof(); } else { error!("Internal server error: unexpected eof"); self.flags.insert(Flags::DISCONNECTED); - self.messages.push_back(Message::Error( + self.messages.push_back(DispatcherMessage::Error( Response::InternalServerError().finish(), )); self.error = Some(DispatchError::InternalError); @@ -398,8 +402,9 @@ where } // Malformed requests should be responded with 400 - self.messages - .push_back(Message::Error(Response::BadRequest().finish())); + self.messages.push_back(DispatcherMessage::Error( + Response::BadRequest().finish(), + )); self.flags.insert(Flags::DISCONNECTED); self.error = Some(e.into()); break; @@ -443,9 +448,7 @@ where trace!("Slow request timeout"); self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); self.state = State::SendResponse(Some(( - OutMessage::Response( - Response::RequestTimeout().finish(), - ), + Message::Item(Response::RequestTimeout().finish()), Body::Empty, ))); } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index ea11f11f..63ba05d0 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -9,6 +9,7 @@ use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use body::{Binary, Body}; +use client::ClientRequest; use header::ContentEncoding; use http::Method; use request::Request; @@ -165,6 +166,39 @@ impl ResponseEncoder { } } +#[derive(Debug)] +pub(crate) struct RequestEncoder { + head: bool, + pub length: ResponseLength, + pub te: TransferEncoding, +} + +impl Default for RequestEncoder { + fn default() -> Self { + RequestEncoder { + head: false, + length: ResponseLength::None, + te: TransferEncoding::empty(), + } + } +} + +impl RequestEncoder { + /// Encode message + pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { + self.te.encode(msg, buf) + } + + /// Encode eof + pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { + self.te.encode_eof(buf) + } + + pub fn update(&mut self, resp: &mut ClientRequest, head: bool, version: Version) { + self.head = head; + } +} + /// Encoders to handle different Transfer-Encodings. #[derive(Debug)] pub(crate) struct TransferEncoding { diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 1ac65912..c33b193f 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,13 +1,16 @@ //! HTTP/1 implementation use actix_net::codec::Framed; +use bytes::Bytes; +mod client; mod codec; mod decoder; mod dispatcher; mod encoder; mod service; -pub use self::codec::{Codec, InMessage, InMessageType, OutMessage}; +pub use self::client::ClientCodec; +pub use self::codec::Codec; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; @@ -20,3 +23,26 @@ pub enum H1ServiceResult { Shutdown(T), Unhandled(Request, Framed), } + +#[derive(Debug)] +/// Codec message +pub enum Message { + /// Http message + Item(T), + /// Payload chunk + Chunk(Option), +} + +impl From for Message { + fn from(item: T) -> Self { + Message::Item(item) + } +} + +/// Incoming request type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MessageType { + None, + Payload, + Unhandled, +} diff --git a/src/h1/service.rs b/src/h1/service.rs index 096cb301..d691ae75 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -13,9 +13,9 @@ use error::{DispatchError, ParseError}; use request::Request; use response::Response; -use super::codec::{Codec, InMessage}; +use super::codec::Codec; use super::dispatcher::Dispatcher; -use super::H1ServiceResult; +use super::{H1ServiceResult, Message}; /// `NewService` implementation for HTTP1 transport pub struct H1Service { @@ -344,10 +344,10 @@ where fn poll(&mut self) -> Poll { match self.framed.as_mut().unwrap().poll()? { Async::Ready(Some(req)) => match req { - InMessage::Message(req, _) => { + Message::Item(req) => { Ok(Async::Ready((req, self.framed.take().unwrap()))) } - InMessage::Chunk(_) => unreachable!("Something is wrong"), + Message::Chunk(_) => unreachable!("Something is wrong"), }, Async::Ready(None) => Err(ParseError::Incomplete), Async::NotReady => Ok(Async::NotReady), diff --git a/src/lib.rs b/src/lib.rs index c9cebb47..572c23ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ extern crate http as modhttp; extern crate httparse; extern crate mime; extern crate net2; +extern crate percent_encoding; extern crate rand; extern crate serde; extern crate serde_json; @@ -95,12 +96,14 @@ extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_tcp; extern crate tokio_timer; +extern crate url as urlcrate; #[cfg(test)] #[macro_use] extern crate serde_derive; mod body; +pub mod client; mod config; mod extensions; mod header; diff --git a/src/request.rs b/src/request.rs index ef28e369..ad048639 100644 --- a/src/request.rs +++ b/src/request.rs @@ -3,8 +3,9 @@ use std::collections::VecDeque; use std::fmt; use std::rc::Rc; -use http::{header, HeaderMap, Method, Uri, Version}; +use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; +use client::ClientResponse; use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; @@ -17,23 +18,24 @@ bitflags! { } } -/// Request's context +/// Request pub struct Request { - pub(crate) inner: Rc, + pub(crate) inner: Rc, } -pub(crate) struct InnerRequest { +pub(crate) struct Message { pub(crate) version: Version, + pub(crate) status: StatusCode, pub(crate) method: Method, pub(crate) url: Url, pub(crate) flags: Cell, pub(crate) headers: HeaderMap, pub(crate) extensions: RefCell, pub(crate) payload: RefCell>, - pool: &'static RequestPool, + pub(crate) pool: &'static MessagePool, } -impl InnerRequest { +impl Message { #[inline] /// Reset request instance pub fn reset(&mut self) { @@ -64,15 +66,16 @@ impl HttpMessage for Request { impl Request { /// Create new Request instance pub fn new() -> Request { - Request::with_pool(RequestPool::pool()) + Request::with_pool(MessagePool::pool()) } /// Create new Request instance with pool - pub(crate) fn with_pool(pool: &'static RequestPool) -> Request { + pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { Request { - inner: Rc::new(InnerRequest { + inner: Rc::new(Message { pool, method: Method::GET, + status: StatusCode::OK, url: Url::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), @@ -84,12 +87,12 @@ impl Request { } #[inline] - pub(crate) fn inner(&self) -> &InnerRequest { + pub(crate) fn inner(&self) -> &Message { self.inner.as_ref() } #[inline] - pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest { + pub(crate) fn inner_mut(&mut self) -> &mut Message { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } @@ -201,24 +204,24 @@ impl fmt::Debug for Request { } /// Request's objects pool -pub(crate) struct RequestPool(RefCell>>); +pub(crate) struct MessagePool(RefCell>>); -thread_local!(static POOL: &'static RequestPool = RequestPool::create()); +thread_local!(static POOL: &'static MessagePool = MessagePool::create()); -impl RequestPool { - fn create() -> &'static RequestPool { - let pool = RequestPool(RefCell::new(VecDeque::with_capacity(128))); +impl MessagePool { + fn create() -> &'static MessagePool { + let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); Box::leak(Box::new(pool)) } /// Get default request's pool - pub fn pool() -> &'static RequestPool { + pub fn pool() -> &'static MessagePool { POOL.with(|p| *p) } /// Get Request object #[inline] - pub fn get(pool: &'static RequestPool) -> Request { + pub fn get_request(pool: &'static MessagePool) -> Request { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { if let Some(r) = Rc::get_mut(&mut msg) { r.reset(); @@ -228,9 +231,21 @@ impl RequestPool { Request::with_pool(pool) } + /// Get Client Response object + #[inline] + pub fn get_response(pool: &'static MessagePool) -> ClientResponse { + if let Some(mut msg) = pool.0.borrow_mut().pop_front() { + if let Some(r) = Rc::get_mut(&mut msg) { + r.reset(); + } + return ClientResponse { inner: msg }; + } + ClientResponse::with_pool(pool) + } + #[inline] /// Release request instance - pub(crate) fn release(&self, msg: Rc) { + pub(crate) fn release(&self, msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { v.push_front(msg); diff --git a/src/service.rs b/src/service.rs index 4467087b..a2852914 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,7 +8,7 @@ use futures::{Async, AsyncSink, Future, Poll, Sink}; use tokio_io::AsyncWrite; use error::ResponseError; -use h1::{Codec, OutMessage}; +use h1::{Codec, Message}; use response::Response; pub struct SendError(PhantomData<(T, R, E)>); @@ -59,7 +59,7 @@ where Ok(r) => Either::A(ok(r)), Err((e, framed)) => Either::B(SendErrorFut { framed: Some(framed), - res: Some(OutMessage::Response(e.error_response())), + res: Some(Message::Item(e.error_response())), err: Some(e), _t: PhantomData, }), @@ -68,7 +68,7 @@ where } pub struct SendErrorFut { - res: Option, + res: Option>, framed: Option>, err: Option, _t: PhantomData, @@ -149,14 +149,14 @@ where fn call(&mut self, (res, framed): Self::Request) -> Self::Future { SendResponseFut { - res: Some(OutMessage::Response(res)), + res: Some(Message::Item(res)), framed: Some(framed), } } } pub struct SendResponseFut { - res: Option, + res: Option>, framed: Option>, } diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs new file mode 100644 index 00000000..575b4e4d --- /dev/null +++ b/src/ws/client/connect.rs @@ -0,0 +1,95 @@ +//! Http client request +use std::str; + +use cookie::Cookie; +use http::header::{HeaderName, HeaderValue}; +use http::{Error as HttpError, HttpTryFrom}; + +use client::{ClientRequest, ClientRequestBuilder}; +use header::IntoHeaderValue; + +use super::ClientError; + +/// `WebSocket` connection +pub struct Connect { + pub(super) request: ClientRequestBuilder, + pub(super) err: Option, + pub(super) http_err: Option, + pub(super) origin: Option, + pub(super) protocols: Option, + pub(super) max_size: usize, + pub(super) server_mode: bool, +} + +impl Connect { + /// Create new websocket connection + pub fn new>(uri: S) -> Connect { + let mut cl = Connect { + request: ClientRequest::build(), + err: None, + http_err: None, + origin: None, + protocols: None, + max_size: 65_536, + server_mode: false, + }; + cl.request.uri(uri.as_ref()); + cl + } + + /// Set supported websocket protocols + pub fn protocols(mut self, protos: U) -> Self + where + U: IntoIterator + 'static, + V: AsRef, + { + let mut protos = protos + .into_iter() + .fold(String::new(), |acc, s| acc + s.as_ref() + ","); + protos.pop(); + self.protocols = Some(protos); + self + } + + /// Set cookie for handshake request + pub fn cookie(mut self, cookie: Cookie) -> Self { + self.request.cookie(cookie); + self + } + + /// Set request Origin + pub fn origin(mut self, origin: V) -> Self + where + HeaderValue: HttpTryFrom, + { + match HeaderValue::try_from(origin) { + Ok(value) => self.origin = Some(value), + Err(e) => self.http_err = Some(e.into()), + } + self + } + + /// Set max frame size + /// + /// By default max size is set to 64kb + pub fn max_frame_size(mut self, size: usize) -> Self { + self.max_size = size; + self + } + + /// Disable payload masking. By default ws client masks frame payload. + pub fn server_mode(mut self) -> Self { + self.server_mode = true; + self + } + + /// Set request header + pub fn header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + self.request.header(key, value); + self + } +} diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs new file mode 100644 index 00000000..62a8800a --- /dev/null +++ b/src/ws/client/error.rs @@ -0,0 +1,87 @@ +//! Http client request +use std::io; + +use actix_net::connector::ConnectorError; +use http::header::HeaderValue; +use http::StatusCode; + +use error::ParseError; +use http::Error as HttpError; +use ws::ProtocolError; + +/// Websocket client error +#[derive(Fail, Debug)] +pub enum ClientError { + /// Invalid url + #[fail(display = "Invalid url")] + InvalidUrl, + /// Invalid response status + #[fail(display = "Invalid response status")] + InvalidResponseStatus(StatusCode), + /// Invalid upgrade header + #[fail(display = "Invalid upgrade header")] + InvalidUpgradeHeader, + /// Invalid connection header + #[fail(display = "Invalid connection header")] + InvalidConnectionHeader(HeaderValue), + /// Missing CONNECTION header + #[fail(display = "Missing CONNECTION header")] + MissingConnectionHeader, + /// Missing SEC-WEBSOCKET-ACCEPT header + #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] + MissingWebSocketAcceptHeader, + /// Invalid challenge response + #[fail(display = "Invalid challenge response")] + InvalidChallengeResponse(String, HeaderValue), + /// Http parsing error + #[fail(display = "Http parsing error")] + Http(HttpError), + // /// Url parsing error + // #[fail(display = "Url parsing error")] + // Url(UrlParseError), + /// Response parsing error + #[fail(display = "Response parsing error")] + ParseError(ParseError), + /// Protocol error + #[fail(display = "{}", _0)] + Protocol(#[cause] ProtocolError), + /// Connect error + #[fail(display = "{:?}", _0)] + Connect(ConnectorError), + /// IO Error + #[fail(display = "{}", _0)] + Io(io::Error), + /// "Disconnected" + #[fail(display = "Disconnected")] + Disconnected, +} + +impl From for ClientError { + fn from(err: HttpError) -> ClientError { + ClientError::Http(err) + } +} + +impl From for ClientError { + fn from(err: ConnectorError) -> ClientError { + ClientError::Connect(err) + } +} + +impl From for ClientError { + fn from(err: ProtocolError) -> ClientError { + ClientError::Protocol(err) + } +} + +impl From for ClientError { + fn from(err: io::Error) -> ClientError { + ClientError::Io(err) + } +} + +impl From for ClientError { + fn from(err: ParseError) -> ClientError { + ClientError::ParseError(err) + } +} diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs new file mode 100644 index 00000000..0dbf081c --- /dev/null +++ b/src/ws/client/mod.rs @@ -0,0 +1,48 @@ +mod connect; +mod error; +mod service; + +pub use self::connect::Connect; +pub use self::error::ClientError; +pub use self::service::Client; + +#[derive(PartialEq, Hash, Debug, Clone, Copy)] +pub(crate) enum Protocol { + Http, + Https, + Ws, + Wss, +} + +impl Protocol { + fn from(s: &str) -> Option { + match s { + "http" => Some(Protocol::Http), + "https" => Some(Protocol::Https), + "ws" => Some(Protocol::Ws), + "wss" => Some(Protocol::Wss), + _ => None, + } + } + + fn is_http(self) -> bool { + match self { + Protocol::Https | Protocol::Http => true, + _ => false, + } + } + + fn is_secure(self) -> bool { + match self { + Protocol::Https | Protocol::Wss => true, + _ => false, + } + } + + fn port(self) -> u16 { + match self { + Protocol::Http | Protocol::Ws => 80, + Protocol::Https | Protocol::Wss => 443, + } + } +} diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs new file mode 100644 index 00000000..ab0e4803 --- /dev/null +++ b/src/ws/client/service.rs @@ -0,0 +1,270 @@ +//! websockets client +use std::marker::PhantomData; + +use actix_net::codec::Framed; +use actix_net::connector::{ConnectorError, DefaultConnector}; +use actix_net::service::Service; +use base64; +use futures::future::{err, Either, FutureResult}; +use futures::{Async, Future, Poll, Sink, Stream}; +use http::header::{self, HeaderValue}; +use http::{HttpTryFrom, StatusCode}; +use rand; +use sha1::Sha1; +use tokio_io::{AsyncRead, AsyncWrite}; + +use client::ClientResponse; +use h1; +use ws::Codec; + +use super::{ClientError, Connect, Protocol}; + +/// WebSocket's client +pub struct Client +where + T: Service, + T::Response: AsyncRead + AsyncWrite, +{ + connector: T, +} + +impl Client +where + T: Service, + T::Response: AsyncRead + AsyncWrite, +{ + /// Create new websocket's client factory + pub fn new(connector: T) -> Self { + Client { connector } + } +} + +impl Default for Client> { + fn default() -> Self { + Client::new(DefaultConnector::default()) + } +} + +impl Clone for Client +where + T: Service + Clone, + T::Response: AsyncRead + AsyncWrite, +{ + fn clone(&self) -> Self { + Client { + connector: self.connector.clone(), + } + } +} + +impl Service for Client +where + T: Service, + T::Response: AsyncRead + AsyncWrite + 'static, + T::Future: 'static, +{ + type Request = Connect; + type Response = Framed; + type Error = ClientError; + type Future = Either< + FutureResult, + ClientResponseFut, + >; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.connector.poll_ready().map_err(ClientError::from) + } + + fn call(&mut self, mut req: Self::Request) -> Self::Future { + if let Some(e) = req.err.take() { + Either::A(err(e)) + } else if let Some(e) = req.http_err.take() { + Either::A(err(e.into())) + } else { + // origin + if let Some(origin) = req.origin.take() { + req.request.set_header(header::ORIGIN, origin); + } + + req.request.upgrade(); + req.request.set_header(header::UPGRADE, "websocket"); + req.request.set_header(header::CONNECTION, "upgrade"); + req.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); + + if let Some(protocols) = req.protocols.take() { + req.request + .set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); + } + let mut request = match req.request.finish() { + Ok(req) => req, + Err(e) => return Either::A(err(e.into())), + }; + + if request.uri().host().is_none() { + return Either::A(err(ClientError::InvalidUrl)); + } + + // supported protocols + let proto = if let Some(scheme) = request.uri().scheme_part() { + match Protocol::from(scheme.as_str()) { + Some(proto) => proto, + None => return Either::A(err(ClientError::InvalidUrl)), + } + } else { + return Either::A(err(ClientError::InvalidUrl)); + }; + + // Generate a random key for the `Sec-WebSocket-Key` header. + // a base64-encoded (see Section 4 of [RFC4648]) value that, + // when decoded, is 16 bytes in length (RFC 6455) + let sec_key: [u8; 16] = rand::random(); + let key = base64::encode(&sec_key); + + request.headers_mut().insert( + header::SEC_WEBSOCKET_KEY, + HeaderValue::try_from(key.as_str()).unwrap(), + ); + + // prep connection + let host = { + let uri = request.uri(); + format!( + "{}:{}", + uri.host().unwrap(), + uri.port().unwrap_or_else(|| proto.port()) + ) + }; + + let fut = Box::new( + self.connector + .call(host) + .map_err(|e| ClientError::from(e)) + .and_then(move |io| { + // h1 protocol + let framed = Framed::new(io, h1::ClientCodec::default()); + framed + .send(request.into()) + .map_err(|e| ClientError::from(e)) + .and_then(|framed| { + framed + .into_future() + .map_err(|(e, _)| ClientError::from(e)) + }) + }), + ); + + // start handshake + Either::B(ClientResponseFut { + key, + fut, + max_size: req.max_size, + server_mode: req.server_mode, + _t: PhantomData, + }) + } + } +} + +/// Future that implementes client websocket handshake process. +/// +/// It resolves to a `Framed` instance. +pub struct ClientResponseFut +where + T: AsyncRead + AsyncWrite, +{ + fut: Box< + Future< + Item = ( + Option>, + Framed, + ), + Error = ClientError, + >, + >, + key: String, + max_size: usize, + server_mode: bool, + _t: PhantomData, +} + +impl Future for ClientResponseFut +where + T: AsyncRead + AsyncWrite, +{ + type Item = Framed; + type Error = ClientError; + + fn poll(&mut self) -> Poll { + let (item, framed) = try_ready!(self.fut.poll()); + + let res = match item { + Some(h1::Message::Item(res)) => res, + Some(h1::Message::Chunk(_)) => unreachable!(), + None => return Err(ClientError::Disconnected), + }; + + // verify response + if res.status() != StatusCode::SWITCHING_PROTOCOLS { + return Err(ClientError::InvalidResponseStatus(res.status())); + } + // Check for "UPGRADE" to websocket header + let has_hdr = if let Some(hdr) = res.headers().get(header::UPGRADE) { + if let Ok(s) = hdr.to_str() { + s.to_lowercase().contains("websocket") + } else { + false + } + } else { + false + }; + if !has_hdr { + trace!("Invalid upgrade header"); + return Err(ClientError::InvalidUpgradeHeader); + } + // Check for "CONNECTION" header + if let Some(conn) = res.headers().get(header::CONNECTION) { + if let Ok(s) = conn.to_str() { + if !s.to_lowercase().contains("upgrade") { + trace!("Invalid connection header: {}", s); + return Err(ClientError::InvalidConnectionHeader(conn.clone())); + } + } else { + trace!("Invalid connection header: {:?}", conn); + return Err(ClientError::InvalidConnectionHeader(conn.clone())); + } + } else { + trace!("Missing connection header"); + return Err(ClientError::MissingConnectionHeader); + } + + if let Some(key) = res.headers().get(header::SEC_WEBSOCKET_ACCEPT) { + // field is constructed by concatenating /key/ + // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) + const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + let mut sha1 = Sha1::new(); + sha1.update(self.key.as_ref()); + sha1.update(WS_GUID); + let encoded = base64::encode(&sha1.digest().bytes()); + if key.as_bytes() != encoded.as_bytes() { + trace!( + "Invalid challenge response: expected: {} received: {:?}", + encoded, + key + ); + return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); + } + } else { + trace!("Missing SEC-WEBSOCKET-ACCEPT header"); + return Err(ClientError::MissingWebSocketAcceptHeader); + }; + + // websockets codec + let codec = if self.server_mode { + Codec::new().max_size(self.max_size) + } else { + Codec::new().max_size(self.max_size).client_mode() + }; + + Ok(Async::Ready(framed.into_framed(codec))) + } +} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 690c56fe..b5a66708 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -10,6 +10,7 @@ use http::{header, Method, StatusCode}; use request::Request; use response::{ConnectionType, Response, ResponseBuilder}; +mod client; mod codec; mod frame; mod mask; @@ -17,6 +18,7 @@ mod proto; mod service; mod transport; +pub use self::client::{Client, ClientError, Connect}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; diff --git a/tests/test_ws.rs b/tests/test_ws.rs index a5503c20..85770fa8 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -11,12 +11,12 @@ use actix::System; use actix_net::codec::Framed; use actix_net::framed::IntoFramed; use actix_net::server::Server; -use actix_net::service::NewServiceExt; +use actix_net::service::{NewServiceExt, Service}; use actix_net::stream::TakeItem; use actix_web::{test, ws as web_ws}; -use bytes::Bytes; -use futures::future::{ok, Either}; -use futures::{Future, Sink, Stream}; +use bytes::{Bytes, BytesMut}; +use futures::future::{lazy, ok, Either}; +use futures::{Future, IntoFuture, Sink, Stream}; use actix_http::{h1, ws, ResponseError, ServiceConfig}; @@ -51,14 +51,14 @@ fn test_simple() { .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request - if let Some(h1::InMessage::Message(req, _)) = req { + if let Some(h1::Message::Item(req)) = req { match ws::verify_handshake(&req) { Err(e) => { // validation failed let resp = e.error_response(); Either::A( framed - .send(h1::OutMessage::Response(resp)) + .send(h1::Message::Item(resp)) .map_err(|_| ()) .map(|_| ()), ) @@ -66,7 +66,7 @@ fn test_simple() { Ok(_) => Either::B( // send response framed - .send(h1::OutMessage::Response( + .send(h1::Message::Item( ws::handshake_response(&req).finish(), )).map_err(|_| ()) .and_then(|framed| { @@ -116,4 +116,43 @@ fn test_simple() { ))) ); } + + // client service + let mut client = sys + .block_on(lazy(|| Ok::<_, ()>(ws::Client::default()).into_future())) + .unwrap(); + let framed = sys + .block_on(client.call(ws::Connect::new(format!("http://{}/", addr)))) + .unwrap(); + + let framed = sys + .block_on(framed.send(ws::Message::Text("text".to_string()))) + .unwrap(); + let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + + let framed = sys + .block_on(framed.send(ws::Message::Binary("text".into()))) + .unwrap(); + let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) + ); + + let framed = sys + .block_on(framed.send(ws::Message::Ping("text".into()))) + .unwrap(); + let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + + let framed = sys + .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) + .unwrap(); + + let (item, _framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) + ) } From 4260692034d9f78f2e99ea4cd00c12dbacf42929 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Oct 2018 18:52:40 -0700 Subject: [PATCH 0780/1635] add DefaultClient type alias --- src/ws/client/mod.rs | 2 +- src/ws/client/service.rs | 3 +++ src/ws/mod.rs | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 0dbf081c..12c7229b 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -4,7 +4,7 @@ mod service; pub use self::connect::Connect; pub use self::error::ClientError; -pub use self::service::Client; +pub use self::service::{Client, DefaultClient}; #[derive(PartialEq, Hash, Debug, Clone, Copy)] pub(crate) enum Protocol { diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index ab0e4803..8e97b743 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -19,6 +19,9 @@ use ws::Codec; use super::{ClientError, Connect, Protocol}; +/// Default client, uses default connector. +pub type DefaultClient = Client>; + /// WebSocket's client pub struct Client where diff --git a/src/ws/mod.rs b/src/ws/mod.rs index b5a66708..1fbbd03d 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -18,7 +18,7 @@ mod proto; mod service; mod transport; -pub use self::client::{Client, ClientError, Connect}; +pub use self::client::{Client, ClientError, Connect, DefaultClient}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; From bc6e62349c907f633377881f95fef69399fe8416 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Oct 2018 21:44:20 -0700 Subject: [PATCH 0781/1635] update deps; export api --- Cargo.toml | 4 ++-- src/request.rs | 24 +++++++++++++----------- src/ws/client/error.rs | 3 --- src/ws/codec.rs | 1 + 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87bd05b8..87e705f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,8 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.5" -actix-net = "0.1.1" -#actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = "0.1.1" +actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" diff --git a/src/request.rs b/src/request.rs index ad048639..07632bf0 100644 --- a/src/request.rs +++ b/src/request.rs @@ -23,16 +23,16 @@ pub struct Request { pub(crate) inner: Rc, } -pub(crate) struct Message { - pub(crate) version: Version, - pub(crate) status: StatusCode, - pub(crate) method: Method, - pub(crate) url: Url, - pub(crate) flags: Cell, - pub(crate) headers: HeaderMap, - pub(crate) extensions: RefCell, - pub(crate) payload: RefCell>, +pub struct Message { + pub version: Version, + pub status: StatusCode, + pub method: Method, + pub url: Url, + pub headers: HeaderMap, + pub extensions: RefCell, + pub payload: RefCell>, pub(crate) pool: &'static MessagePool, + pub(crate) flags: Cell, } impl Message { @@ -87,12 +87,14 @@ impl Request { } #[inline] - pub(crate) fn inner(&self) -> &Message { + #[doc(hidden)] + pub fn inner(&self) -> &Message { self.inner.as_ref() } #[inline] - pub(crate) fn inner_mut(&mut self) -> &mut Message { + #[doc(hidden)] + pub fn inner_mut(&mut self) -> &mut Message { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 62a8800a..951cd6ac 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -36,9 +36,6 @@ pub enum ClientError { /// Http parsing error #[fail(display = "Http parsing error")] Http(HttpError), - // /// Url parsing error - // #[fail(display = "Url parsing error")] - // Url(UrlParseError), /// Response parsing error #[fail(display = "Response parsing error")] ParseError(ParseError), diff --git a/src/ws/codec.rs b/src/ws/codec.rs index 7ba10672..5bdc5819 100644 --- a/src/ws/codec.rs +++ b/src/ws/codec.rs @@ -36,6 +36,7 @@ pub enum Frame { Close(Option), } +#[derive(Debug)] /// WebSockets protocol codec pub struct Codec { max_size: usize, From cd0223e8b7387d57bbd32dacc97b8a6d4ef580d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Oct 2018 22:41:30 -0700 Subject: [PATCH 0782/1635] update Connector usage --- src/ws/client/service.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 8e97b743..b494564b 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use actix_net::codec::Framed; -use actix_net::connector::{ConnectorError, DefaultConnector}; +use actix_net::connector::{Connect as TcpConnect, ConnectorError, DefaultConnector}; use actix_net::service::Service; use base64; use futures::future::{err, Either, FutureResult}; @@ -20,7 +20,7 @@ use ws::Codec; use super::{ClientError, Connect, Protocol}; /// Default client, uses default connector. -pub type DefaultClient = Client>; +pub type DefaultClient = Client; /// WebSocket's client pub struct Client @@ -33,7 +33,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -42,7 +42,7 @@ where } } -impl Default for Client> { +impl Default for Client { fn default() -> Self { Client::new(DefaultConnector::default()) } @@ -50,7 +50,7 @@ impl Default for Client> { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -62,7 +62,7 @@ where impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { @@ -129,18 +129,14 @@ where ); // prep connection - let host = { - let uri = request.uri(); - format!( - "{}:{}", - uri.host().unwrap(), - uri.port().unwrap_or_else(|| proto.port()) - ) - }; + let connect = TcpConnect::new( + request.uri().host().unwrap(), + request.uri().port().unwrap_or_else(|| proto.port()), + ); let fut = Box::new( self.connector - .call(host) + .call(connect) .map_err(|e| ClientError::from(e)) .and_then(move |io| { // h1 protocol From 540ad18432ed7be876fe7deae9f00415c116dbf4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Oct 2018 16:48:45 -0700 Subject: [PATCH 0783/1635] add Debug impl --- src/h1/codec.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 8f7e77e0..1484d56b 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -1,4 +1,5 @@ #![allow(unused_imports, unused_variables, dead_code)] +use std::fmt; use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; @@ -41,6 +42,12 @@ pub struct Codec { te: ResponseEncoder, } +impl fmt::Debug for Codec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "h1::Codec({:?})", self.flags) + } +} + impl Codec { /// Create HTTP/1 codec. /// From 5f91f5eda6f3b61d91a63b5ef651ef9f2617d7b7 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 26 Oct 2018 10:59:06 +0300 Subject: [PATCH 0784/1635] Correct IoStream::set_keepalive for UDS (#564) Enable uds feature in tests --- .travis.yml | 2 +- src/server/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c5dfcd81..9b1bcff5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ script: cargo check --features rust-tls cargo check --features ssl cargo check --features tls - cargo test --features="ssl,tls,rust-tls" -- --nocapture + cargo test --features="ssl,tls,rust-tls,uds" -- --nocapture fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then diff --git a/src/server/mod.rs b/src/server/mod.rs index 8d719516..0a16f26b 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -334,7 +334,7 @@ impl IoStream for ::tokio_uds::UnixStream { } #[inline] - fn set_keepalive(&mut self, _nodelay: bool) -> io::Result<()> { + fn set_keepalive(&mut self, _dur: Option) -> io::Result<()> { Ok(()) } } From cfd9a56ff74dd6cb6ad38b6a67b40ed1763d6071 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 28 Oct 2018 09:24:19 -0700 Subject: [PATCH 0785/1635] Add async/await ref --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 321f82ab..db3cc68c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Built on top of [Actix actor framework](https://github.com/actix/actix) +* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support. ## Documentation & community resources From c2540cc59b8967291cf8166bcfb256a5091420a0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 29 Oct 2018 16:39:46 -0700 Subject: [PATCH 0786/1635] clippy warnings --- src/client/request.rs | 3 ++- src/config.rs | 4 +++- src/h1/client.rs | 8 ++++++-- src/h1/codec.rs | 8 ++++++-- src/h1/decoder.rs | 16 ++++++++++++---- src/h1/dispatcher.rs | 34 +++++++++++++++++----------------- src/h1/encoder.rs | 4 +++- src/h1/service.rs | 2 +- src/response.rs | 12 ++++++++---- src/service.rs | 6 ++---- src/ws/client/service.rs | 4 ++-- src/ws/frame.rs | 14 +++++++++++--- src/ws/mask.rs | 9 +++------ 13 files changed, 76 insertions(+), 48 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index bf82927e..8c794933 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -536,7 +536,8 @@ impl ClientRequestBuilder { #[inline] fn parts<'a>( - parts: &'a mut Option, err: &Option, + parts: &'a mut Option, + err: &Option, ) -> Option<&'a mut ClientRequest> { if err.is_some() { return None; diff --git a/src/config.rs b/src/config.rs index ff22ea48..833bca7f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -66,7 +66,9 @@ impl Default for ServiceConfig { impl ServiceConfig { /// Create instance of `ServiceConfig` pub(crate) fn new( - keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, ) -> ServiceConfig { let (keep_alive, ka_enabled) = match keep_alive { KeepAlive::Timeout(val) => (val as u64, true), diff --git a/src/h1/client.rs b/src/h1/client.rs index 8ec1f0ae..b55af185 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -102,7 +102,9 @@ impl ClientCodec { } fn encode_response( - &mut self, msg: ClientRequest, buffer: &mut BytesMut, + &mut self, + msg: ClientRequest, + buffer: &mut BytesMut, ) -> io::Result<()> { // Connection upgrade if msg.upgrade() { @@ -187,7 +189,9 @@ impl Encoder for ClientCodec { type Error = io::Error; fn encode( - &mut self, item: Self::Item, dst: &mut BytesMut, + &mut self, + item: Self::Item, + dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { Message::Item(res) => { diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 1484d56b..7cc516d6 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -103,7 +103,9 @@ impl Codec { } fn encode_response( - &mut self, mut msg: Response, buffer: &mut BytesMut, + &mut self, + mut msg: Response, + buffer: &mut BytesMut, ) -> io::Result<()> { let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg .keep_alive() @@ -277,7 +279,9 @@ impl Encoder for Codec { type Error = io::Error; fn encode( - &mut self, item: Self::Item, dst: &mut BytesMut, + &mut self, + item: Self::Item, + dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { Message::Item(res) => { diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 2c1e6c1f..e8d07af1 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -334,7 +334,9 @@ pub(crate) struct HeaderIndex { impl HeaderIndex { pub(crate) fn record( - bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndex], + bytes: &[u8], + headers: &[httparse::Header], + indices: &mut [HeaderIndex], ) { let bytes_ptr = bytes.as_ptr() as usize; for (header, indices) in headers.iter().zip(indices.iter_mut()) { @@ -491,7 +493,10 @@ macro_rules! byte ( impl ChunkedState { fn step( - &self, body: &mut BytesMut, size: &mut u64, buf: &mut Option, + &self, + body: &mut BytesMut, + size: &mut u64, + buf: &mut Option, ) -> Poll { use self::ChunkedState::*; match *self { @@ -554,7 +559,8 @@ impl ChunkedState { } } fn read_size_lf( - rdr: &mut BytesMut, size: &mut u64, + rdr: &mut BytesMut, + size: &mut u64, ) -> Poll { match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), @@ -567,7 +573,9 @@ impl ChunkedState { } fn read_body( - rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option, + rdr: &mut BytesMut, + rem: &mut u64, + buf: &mut Option, ) -> Poll { trace!("Chunked read, remaining={:?}", rem); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index a7f97e8c..513c1961 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -90,7 +90,10 @@ where /// Create http/1 dispatcher with slow request timeout. pub fn with_timeout( - stream: T, config: ServiceConfig, timeout: Option, service: S, + stream: T, + config: ServiceConfig, + timeout: Option, + service: S, ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { @@ -173,19 +176,15 @@ where // process loop { let state = match self.state { - State::None => loop { - break if let Some(msg) = self.messages.pop_front() { - match msg { - DispatcherMessage::Item(req) => { - Some(self.handle_request(req)?) - } - DispatcherMessage::Error(res) => Some(State::SendResponse( - Some((Message::Item(res), Body::Empty)), - )), - } - } else { - None - }; + State::None => if let Some(msg) = self.messages.pop_front() { + match msg { + DispatcherMessage::Item(req) => Some(self.handle_request(req)?), + DispatcherMessage::Error(res) => Some(State::SendResponse( + Some((Message::Item(res), Body::Empty)), + )), + } + } else { + None }, // call inner service State::ServiceCall(ref mut fut) => { @@ -255,7 +254,7 @@ where .framed .as_mut() .unwrap() - .start_send(Message::Chunk(Some(item.into()))) + .start_send(Message::Chunk(Some(item))) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); @@ -299,7 +298,8 @@ where } fn handle_request( - &mut self, req: Request, + &mut self, + req: Request, ) -> Result, DispatchError> { let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { @@ -324,7 +324,7 @@ where } let mut updated = false; - 'outer: loop { + loop { match self.framed.as_mut().unwrap().poll() { Ok(Async::Ready(Some(msg))) => { updated = true; diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 63ba05d0..fc0a3a1b 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -107,7 +107,9 @@ impl ResponseEncoder { } fn streaming_encoding( - &mut self, version: Version, resp: &mut Response, + &mut self, + version: Version, + resp: &mut Response, ) -> TransferEncoding { match resp.chunked() { Some(true) => { diff --git a/src/h1/service.rs b/src/h1/service.rs index d691ae75..7e5e8c5f 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -253,7 +253,7 @@ where type Future = Dispatcher; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(|e| DispatchError::Service(e)) + self.srv.poll_ready().map_err(DispatchError::Service) } fn call(&mut self, req: Self::Request) -> Self::Future { diff --git a/src/response.rs b/src/response.rs index e834748e..1901443f 100644 --- a/src/response.rs +++ b/src/response.rs @@ -690,9 +690,10 @@ impl ResponseBuilder { } #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))] +#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] fn parts<'a>( - parts: &'a mut Option>, err: &Option, + parts: &'a mut Option>, + err: &Option, ) -> Option<&'a mut Box> { if err.is_some() { return None; @@ -871,7 +872,8 @@ impl ResponsePool { #[inline] pub fn get_builder( - pool: &'static ResponsePool, status: StatusCode, + pool: &'static ResponsePool, + status: StatusCode, ) -> ResponseBuilder { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; @@ -894,7 +896,9 @@ impl ResponsePool { #[inline] pub fn get_response( - pool: &'static ResponsePool, status: StatusCode, body: Body, + pool: &'static ResponsePool, + status: StatusCode, + body: Body, ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.status = status; diff --git a/src/service.rs b/src/service.rs index a2852914..16dcf8c6 100644 --- a/src/service.rs +++ b/src/service.rs @@ -97,12 +97,10 @@ where } match self.framed.as_mut().unwrap().poll_complete() { Ok(Async::Ready(_)) => { - return Err((self.err.take().unwrap(), self.framed.take().unwrap())) + Err((self.err.take().unwrap(), self.framed.take().unwrap())) } Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => { - return Err((self.err.take().unwrap(), self.framed.take().unwrap())) - } + Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())), } } } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index b494564b..485ce562 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -137,13 +137,13 @@ where let fut = Box::new( self.connector .call(connect) - .map_err(|e| ClientError::from(e)) + .map_err(ClientError::from) .and_then(move |io| { // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed .send(request.into()) - .map_err(|e| ClientError::from(e)) + .map_err(ClientError::from) .and_then(|framed| { framed .into_future() diff --git a/src/ws/frame.rs b/src/ws/frame.rs index de1b9239..ca5f0e89 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -13,7 +13,9 @@ pub struct Parser; impl Parser { fn parse_metadata( - src: &[u8], server: bool, max_size: usize, + src: &[u8], + server: bool, + max_size: usize, ) -> Result)>, ProtocolError> { let chunk_len = src.len(); @@ -86,7 +88,9 @@ impl Parser { /// Parse the input stream into a frame. pub fn parse( - src: &mut BytesMut, server: bool, max_size: usize, + src: &mut BytesMut, + server: bool, + max_size: usize, ) -> Result)>, ProtocolError> { // try to parse ws frame metadata let (idx, finished, opcode, length, mask) = @@ -148,7 +152,11 @@ impl Parser { /// Generate binary representation pub fn write_message>( - dst: &mut BytesMut, pl: B, op: OpCode, fin: bool, mask: bool, + dst: &mut BytesMut, + pl: B, + op: OpCode, + fin: bool, + mask: bool, ) { let payload = pl.into(); let one: u8 = if fin { diff --git a/src/ws/mask.rs b/src/ws/mask.rs index a88c21af..e9bfb3d5 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,5 +1,5 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] +#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] use std::ptr::copy_nonoverlapping; use std::slice; @@ -19,7 +19,7 @@ impl<'a> ShortSlice<'a> { /// Faster version of `apply_mask()` which operates on 8-byte blocks. #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] +#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // Extend the mask to 64 bits let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); @@ -50,10 +50,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so // inefficient, it could be done better. The compiler does not understand that // a `ShortSlice` must be smaller than a u64. -#[cfg_attr( - feature = "cargo-clippy", - allow(clippy::needless_pass_by_value) -)] +#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] fn xor_short(buf: ShortSlice, mask: u64) { // Unsafe: we know that a `ShortSlice` fits in a u64 unsafe { From 148cf73003c10728b773d3d0fd6256c4ff6016f1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 30 Oct 2018 11:46:44 -0700 Subject: [PATCH 0787/1635] allow to create response with error message --- src/error.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/error.rs b/src/error.rs index 3c090203..3064cda4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -98,6 +98,14 @@ impl Error { Fail::downcast_ref(self.cause.as_fail()); compat.and_then(|e| e.get_ref().downcast_ref()) } + + /// Converts error to a response instance and set error message as response body + pub fn response_with_message(self) -> Response { + let message = format!("{}", self); + let mut resp: Response = self.into(); + resp.set_body(message); + resp + } } /// Helper trait to downcast a response error into a fail. From 79bcbb8a101cf448ebb8bc7e7c11f3249f0b0e19 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 30 Oct 2018 11:50:30 -0700 Subject: [PATCH 0788/1635] use error message --- src/service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service.rs b/src/service.rs index 16dcf8c6..5879b388 100644 --- a/src/service.rs +++ b/src/service.rs @@ -59,7 +59,7 @@ where Ok(r) => Either::A(ok(r)), Err((e, framed)) => Either::B(SendErrorFut { framed: Some(framed), - res: Some(Message::Item(e.error_response())), + res: Some(Message::Item(e.response_with_message())), err: Some(e), _t: PhantomData, }), From da82e249547530a809fe5b3c3c4252d4abbbfef5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 30 Oct 2018 11:53:48 -0700 Subject: [PATCH 0789/1635] render error message as body --- src/service.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/service.rs b/src/service.rs index 5879b388..c7653032 100644 --- a/src/service.rs +++ b/src/service.rs @@ -57,12 +57,16 @@ where fn call(&mut self, req: Self::Request) -> Self::Future { match req { Ok(r) => Either::A(ok(r)), - Err((e, framed)) => Either::B(SendErrorFut { - framed: Some(framed), - res: Some(Message::Item(e.response_with_message())), - err: Some(e), - _t: PhantomData, - }), + Err((e, framed)) => { + let mut resp = e.error_response(); + resp.set_body(format!("{}", e)); + Either::B(SendErrorFut { + framed: Some(framed), + res: Some(resp.into()), + err: Some(e), + _t: PhantomData, + }) + } } } } From 3b536ee96c0118e8cf452b28f6acab6085db22a6 Mon Sep 17 00:00:00 2001 From: Stanislav Tkach Date: Thu, 1 Nov 2018 10:14:48 +0200 Subject: [PATCH 0790/1635] Use old clippy attributes syntax (#562) --- src/client/connector.rs | 2 +- src/client/writer.rs | 2 +- src/httpresponse.rs | 2 +- src/info.rs | 2 +- src/middleware/defaultheaders.rs | 2 +- src/scope.rs | 2 +- src/server/h2writer.rs | 2 +- src/server/http.rs | 2 +- src/server/output.rs | 4 ++-- src/ws/frame.rs | 2 +- src/ws/mask.rs | 6 +++--- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 3f4ac27c..3990c955 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -287,7 +287,7 @@ impl Default for ClientConnector { } }; - #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_unit_value))] + #[cfg_attr(feature = "cargo-clippy", allow(let_unit_value))] ClientConnector::with_connector_impl(connector) } } diff --git a/src/client/writer.rs b/src/client/writer.rs index e74f2233..321753bb 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -1,6 +1,6 @@ #![cfg_attr( feature = "cargo-clippy", - allow(clippy::redundant_field_names) + allow(redundant_field_names) )] use std::cell::RefCell; diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 8b091d42..52dd8046 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -694,7 +694,7 @@ impl HttpResponseBuilder { } #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(clippy::borrowed_box))] +#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] fn parts<'a>( parts: &'a mut Option>, err: &Option, ) -> Option<&'a mut Box> { diff --git a/src/info.rs b/src/info.rs index 5a2f2180..43c22123 100644 --- a/src/info.rs +++ b/src/info.rs @@ -18,7 +18,7 @@ impl ConnectionInfo { /// Create *ConnectionInfo* instance for a request. #[cfg_attr( feature = "cargo-clippy", - allow(clippy::cyclomatic_complexity) + allow(cyclomatic_complexity) )] pub fn update(&mut self, req: &Request) { let mut host = None; diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index d980a250..a33fa6a3 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -48,7 +48,7 @@ impl DefaultHeaders { /// Set a header. #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))] + #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, diff --git a/src/scope.rs b/src/scope.rs index 43789d42..1bddc0e0 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -61,7 +61,7 @@ pub struct Scope { #[cfg_attr( feature = "cargo-clippy", - allow(clippy::new_without_default_derive) + allow(new_without_default_derive) )] impl Scope { /// Create a new scope diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs index 66f2923c..fef6f889 100644 --- a/src/server/h2writer.rs +++ b/src/server/h2writer.rs @@ -1,6 +1,6 @@ #![cfg_attr( feature = "cargo-clippy", - allow(clippy::redundant_field_names) + allow(redundant_field_names) )] use std::{cmp, io}; diff --git a/src/server/http.rs b/src/server/http.rs index 9ecd4a5d..0bec8be3 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -326,7 +326,7 @@ where #[doc(hidden)] #[cfg_attr( feature = "cargo-clippy", - allow(clippy::needless_pass_by_value) + allow(needless_pass_by_value) )] pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result where diff --git a/src/server/output.rs b/src/server/output.rs index ac89d644..4a86ffbb 100644 --- a/src/server/output.rs +++ b/src/server/output.rs @@ -438,7 +438,7 @@ impl ContentEncoder { } } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[inline(always)] pub fn write_eof(&mut self) -> Result { let encoder = @@ -480,7 +480,7 @@ impl ContentEncoder { } } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] + #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] #[inline(always)] pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { match *self { diff --git a/src/ws/frame.rs b/src/ws/frame.rs index d5fa9827..5e4fd829 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -46,7 +46,7 @@ impl Frame { Frame::message(payload, OpCode::Close, true, genmask) } - #[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))] + #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] fn read_copy_md( pl: &mut PayloadBuffer, server: bool, max_size: usize, ) -> Poll)>, ProtocolError> diff --git a/src/ws/mask.rs b/src/ws/mask.rs index a88c21af..18ce57bb 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,5 +1,5 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] +#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] use std::ptr::copy_nonoverlapping; use std::slice; @@ -19,7 +19,7 @@ impl<'a> ShortSlice<'a> { /// Faster version of `apply_mask()` which operates on 8-byte blocks. #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] +#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // Extend the mask to 64 bits let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); @@ -52,7 +52,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // a `ShortSlice` must be smaller than a u64. #[cfg_attr( feature = "cargo-clippy", - allow(clippy::needless_pass_by_value) + allow(needless_pass_by_value) )] fn xor_short(buf: ShortSlice, mask: u64) { // Unsafe: we know that a `ShortSlice` fits in a u64 From f1587243c264af76aa731162687c3ba752b8a42e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 5 Nov 2018 19:32:03 -0800 Subject: [PATCH 0791/1635] fix body decoding --- Cargo.toml | 1 + src/h1/codec.rs | 63 +++++++++++++++++++++++++++++++++++++++++++- src/h1/decoder.rs | 11 -------- src/h1/dispatcher.rs | 2 ++ src/h1/mod.rs | 29 ++++++++++++++++++++ 5 files changed, 94 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87e705f7..23499bbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.5" #actix-net = "0.1.1" +#actix-net = { path="../actix-net/" } actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 7cc516d6..dd1e27e0 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -42,6 +42,12 @@ pub struct Codec { te: ResponseEncoder, } +impl Default for Codec { + fn default() -> Self { + Codec::new(ServiceConfig::default()) + } +} + impl fmt::Debug for Codec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "h1::Codec({:?})", self.flags) @@ -247,7 +253,10 @@ impl Decoder for Codec { if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), - Some(PayloadItem::Eof) => Some(Message::Chunk(None)), + Some(PayloadItem::Eof) => { + self.payload.take(); + Some(Message::Chunk(None)) + } None => None, }) } else if self.flags.contains(Flags::UNHANDLED) { @@ -297,3 +306,55 @@ impl Encoder for Codec { Ok(()) } } + +#[cfg(test)] +mod tests { + use std::{cmp, io}; + + use bytes::{Buf, Bytes, BytesMut}; + use http::{Method, Version}; + use tokio_io::{AsyncRead, AsyncWrite}; + + use super::*; + use error::ParseError; + use h1::Message; + use httpmessage::HttpMessage; + use request::Request; + + #[test] + fn test_http_request_chunked_payload_and_next_message() { + let mut codec = Codec::default(); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n", + ); + let item = codec.decode(&mut buf).unwrap().unwrap(); + let req = item.message(); + + assert_eq!(req.method(), Method::GET); + assert!(req.chunked().unwrap()); + + buf.extend( + b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ + POST /test2 HTTP/1.1\r\n\ + transfer-encoding: chunked\r\n\r\n" + .iter(), + ); + + let msg = codec.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"data"); + + let msg = codec.decode(&mut buf).unwrap().unwrap(); + assert_eq!(msg.chunk().as_ref(), b"line"); + + let msg = codec.decode(&mut buf).unwrap().unwrap(); + assert!(msg.eof()); + + // decode next message + let item = codec.decode(&mut buf).unwrap().unwrap(); + let req = item.message(); + assert_eq!(*req.method(), Method::POST); + assert!(req.chunked().unwrap()); + } +} diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index e8d07af1..cfa0879d 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -648,9 +648,7 @@ mod tests { use super::*; use error::ParseError; - use h1::Message; use httpmessage::HttpMessage; - use request::Request; impl PayloadType { fn unwrap(self) -> PayloadDecoder { @@ -668,15 +666,6 @@ mod tests { } } - impl Message { - fn message(self) -> Request { - match self { - Message::Item(req) => req, - _ => panic!("error"), - } - } - } - impl PayloadItem { fn chunk(self) -> Bytes { match self { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 513c1961..fb30d881 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -370,6 +370,7 @@ where Response::InternalServerError().finish(), )); self.error = Some(DispatchError::InternalError); + break; } } Message::Chunk(None) => { @@ -382,6 +383,7 @@ where Response::InternalServerError().finish(), )); self.error = Some(DispatchError::InternalError); + break; } } } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index c33b193f..e7377177 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -46,3 +46,32 @@ pub enum MessageType { Payload, Unhandled, } + +#[cfg(test)] +mod tests { + use super::*; + + impl Message { + pub fn message(self) -> Request { + match self { + Message::Item(req) => req, + _ => panic!("error"), + } + } + + pub fn chunk(self) -> Bytes { + match self { + Message::Chunk(Some(data)) => data, + _ => panic!("error"), + } + } + + pub fn eof(self) -> bool { + match self { + Message::Chunk(None) => true, + Message::Chunk(Some(_)) => false, + _ => panic!("error"), + } + } + } +} From 8e354021d47b2131180de1a312d308ca96e7eb9a Mon Sep 17 00:00:00 2001 From: Julian Tescher Date: Wed, 7 Nov 2018 12:24:06 -0800 Subject: [PATCH 0792/1635] Add SameSite option to identity middleware cookie (#581) --- CHANGES.md | 1 + src/middleware/identity.rs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f5adb82c..2aa9cbfd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ ### Added * Add method to configure custom error handler to `Query` and `Path` extractors. +* Add method to configure `SameSite` option in `CookieIdentityPolicy`. ## [0.7.13] - 2018-10-14 diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index d890bebe..a664ba1f 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -48,7 +48,7 @@ //! ``` use std::rc::Rc; -use cookie::{Cookie, CookieJar, Key}; +use cookie::{Cookie, CookieJar, Key, SameSite}; use futures::future::{err as FutErr, ok as FutOk, FutureResult}; use futures::Future; use time::Duration; @@ -237,6 +237,7 @@ struct CookieIdentityInner { domain: Option, secure: bool, max_age: Option, + same_site: Option, } impl CookieIdentityInner { @@ -248,6 +249,7 @@ impl CookieIdentityInner { domain: None, secure: true, max_age: None, + same_site: None, } } @@ -268,6 +270,10 @@ impl CookieIdentityInner { cookie.set_max_age(max_age); } + if let Some(same_site) = self.same_site { + cookie.set_same_site(same_site); + } + let mut jar = CookieJar::new(); if some { jar.private(&self.key).add(cookie); @@ -370,6 +376,12 @@ impl CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); self } + + /// Sets the `same_site` field in the session cookie being built. + pub fn same_site(mut self, same_site: SameSite) -> Self { + Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site); + self + } } impl IdentityPolicy for CookieIdentityPolicy { From 2677d325a727508e0d2b17ac412173b06528eb7a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 7 Nov 2018 21:09:33 -0800 Subject: [PATCH 0793/1635] fix keep-alive timer reset --- CHANGES.md | 7 ++++++- Cargo.toml | 2 +- src/server/h1.rs | 21 +++++++++++++++------ src/server/h2.rs | 18 +++++++++++++----- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2aa9cbfd..1e66cff8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,16 +1,21 @@ # Changes -## [0.7.14] - 2018-10-x +## [0.7.14] - 2018-11-x ### Fixed +* Fix keep-alive timer reset + * HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549 + ### Added * Add method to configure custom error handler to `Query` and `Path` extractors. + * Add method to configure `SameSite` option in `CookieIdentityPolicy`. + ## [0.7.13] - 2018-10-14 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index d98ce5ea..4a6e2317 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.13" +version = "0.7.14" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/server/h1.rs b/src/server/h1.rs index a2ffc055..07f773eb 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -87,7 +87,10 @@ where H: HttpHandler + 'static, { pub fn new( - settings: ServiceConfig, stream: T, buf: BytesMut, is_eof: bool, + settings: ServiceConfig, + stream: T, + buf: BytesMut, + is_eof: bool, keepalive_timer: Option, ) -> Self { let addr = stream.peer_addr(); @@ -123,8 +126,11 @@ where } pub(crate) fn for_error( - settings: ServiceConfig, stream: T, status: StatusCode, - mut keepalive_timer: Option, buf: BytesMut, + settings: ServiceConfig, + stream: T, + status: StatusCode, + mut keepalive_timer: Option, + buf: BytesMut, ) -> Self { if let Some(deadline) = settings.client_timer_expire() { let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline)); @@ -298,16 +304,19 @@ where if let Some(deadline) = self.settings.client_shutdown_timer() { - timer.reset(deadline) + timer.reset(deadline); + let _ = timer.poll(); } else { return Ok(()); } } } else if let Some(dl) = self.settings.keep_alive_expire() { - timer.reset(dl) + timer.reset(dl); + let _ = timer.poll(); } } else { - timer.reset(self.ka_expire) + timer.reset(self.ka_expire); + let _ = timer.poll(); } } Ok(Async::NotReady) => (), diff --git a/src/server/h2.rs b/src/server/h2.rs index 35afa339..c9e968a3 100644 --- a/src/server/h2.rs +++ b/src/server/h2.rs @@ -60,7 +60,10 @@ where H: HttpHandler + 'static, { pub fn new( - settings: ServiceConfig, io: T, buf: Bytes, keepalive_timer: Option, + settings: ServiceConfig, + io: T, + buf: Bytes, + keepalive_timer: Option, ) -> Self { let addr = io.peer_addr(); let extensions = io.extensions(); @@ -284,10 +287,12 @@ where if self.tasks.is_empty() { return Err(HttpDispatchError::ShutdownTimeout); } else if let Some(dl) = self.settings.keep_alive_expire() { - timer.reset(dl) + timer.reset(dl); + let _ = timer.poll(); } } else { - timer.reset(self.ka_expire) + timer.reset(self.ka_expire); + let _ = timer.poll(); } } Ok(Async::NotReady) => (), @@ -348,8 +353,11 @@ struct Entry { impl Entry { fn new( - parts: Parts, recv: RecvStream, resp: SendResponse, - addr: Option, settings: ServiceConfig, + parts: Parts, + recv: RecvStream, + resp: SendResponse, + addr: Option, + settings: ServiceConfig, extensions: Option>, ) -> Entry where From 62f1c90c8d245d3854e1ff2d7228c0c705aa1eb7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 7 Nov 2018 21:18:40 -0800 Subject: [PATCH 0794/1635] update base64 dep --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4a6e2317..8041c783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ cell = ["actix-net/cell"] actix = "^0.7.5" actix-net = "^0.1.1" -base64 = "0.9" +base64 = "0.10" bitflags = "1.0" failure = "^0.1.2" h2 = "0.1" From 6a1d560f227294b0edfb7a6e11e4acbc75cd8015 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 09:30:53 -0800 Subject: [PATCH 0795/1635] fix keep-alive timer reset --- Cargo.toml | 5 ++--- src/h1/dispatcher.rs | 9 ++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23499bbf..fcc169b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,8 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.5" -#actix-net = "0.1.1" -#actix-net = { path="../actix-net/" } -actix-net = { git="https://github.com/actix/actix-net.git" } +actix-net = "0.2.0" +#actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index fb30d881..cc9ec721 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -441,7 +441,8 @@ where if let Some(deadline) = self.config.client_disconnect_timer() { - timer.reset(deadline) + timer.reset(deadline); + let _ = timer.poll(); } else { return Ok(()); } @@ -455,10 +456,12 @@ where ))); } } else if let Some(deadline) = self.config.keep_alive_expire() { - timer.reset(deadline) + timer.reset(deadline); + let _ = timer.poll(); } } else { - timer.reset(self.ka_expire) + timer.reset(self.ka_expire); + let _ = timer.poll(); } } Ok(Async::NotReady) => (), From 9ab586e24e1ea4be98c31e6b0eed09f962fad1f9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 16:06:23 -0800 Subject: [PATCH 0796/1635] update actix-net dep --- Cargo.toml | 2 +- tests/test_ws.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8041c783..16be8cd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ cell = ["actix-net/cell"] [dependencies] actix = "^0.7.5" -actix-net = "^0.1.1" +actix-net = "0.2.0" base64 = "0.10" bitflags = "1.0" diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 5a0ce204..cb46bc7e 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -385,10 +385,11 @@ fn test_ws_stopped() { { let (reader, mut writer) = srv.ws().unwrap(); writer.text("text"); + writer.close(None); let (item, _) = srv.execute(reader.into_future()).unwrap(); assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); } - thread::sleep(time::Duration::from_millis(1000)); + thread::sleep(time::Duration::from_millis(100)); assert_eq!(num.load(Ordering::Relaxed), 1); } From 1a0bf32ec76411e6ae017ea680b4dad7db3f0c69 Mon Sep 17 00:00:00 2001 From: imaperson Date: Fri, 9 Nov 2018 01:08:06 +0100 Subject: [PATCH 0797/1635] Fix unnecessary owned string and change htmlescape in favor of askama_escape (#584) --- Cargo.toml | 2 +- src/fs.rs | 27 +++++++++++++++++++-------- src/lib.rs | 2 +- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16be8cd4..0dcce54b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,11 +64,11 @@ cell = ["actix-net/cell"] actix = "^0.7.5" actix-net = "0.2.0" +askama_escape = "0.1.0" base64 = "0.10" bitflags = "1.0" failure = "^0.1.2" h2 = "0.1" -htmlescape = "0.3" http = "^0.1.8" httparse = "1.3" log = "0.4" diff --git a/src/fs.rs b/src/fs.rs index 10cdaff7..51470846 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -11,10 +11,10 @@ use std::{cmp, io}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; +use askama_escape::{escape as escape_html_entity}; use bytes::Bytes; use futures::{Async, Future, Poll, Stream}; use futures_cpupool::{CpuFuture, CpuPool}; -use htmlescape::encode_minimal as escape_html_entity; use mime; use mime_guess::{get_mime_type, guess_mime_type}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; @@ -561,6 +561,20 @@ impl Directory { } } +// show file url as relative to static path +macro_rules! encode_file_url { + ($path:ident) => { + utf8_percent_encode(&$path.to_string_lossy(), DEFAULT_ENCODE_SET) + }; +} + +// " -- " & -- & ' -- ' < -- < > -- > / -- / +macro_rules! encode_file_name { + ($entry:ident) => { + escape_html_entity(&$entry.file_name().to_string_lossy()) + }; +} + fn directory_listing( dir: &Directory, req: &HttpRequest, ) -> Result { @@ -575,11 +589,6 @@ fn directory_listing( Ok(p) => base.join(p), Err(_) => continue, }; - // show file url as relative to static path - let file_url = utf8_percent_encode(&p.to_string_lossy(), DEFAULT_ENCODE_SET) - .to_string(); - // " -- " & -- & ' -- ' < -- < > -- > - let file_name = escape_html_entity(&entry.file_name().to_string_lossy()); // if file is a directory, add '/' to the end of the name if let Ok(metadata) = entry.metadata() { @@ -587,13 +596,15 @@ fn directory_listing( let _ = write!( body, "

  • {}/
  • ", - file_url, file_name + encode_file_url!(p), + encode_file_name!(entry), ); } else { let _ = write!( body, "
  • {}
  • ", - file_url, file_name + encode_file_url!(p), + encode_file_name!(entry), ); } } else { diff --git a/src/lib.rs b/src/lib.rs index 1ed40809..738153fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,9 +100,9 @@ extern crate failure; extern crate lazy_static; #[macro_use] extern crate futures; +extern crate askama_escape; extern crate cookie; extern crate futures_cpupool; -extern crate htmlescape; extern crate http as modhttp; extern crate httparse; extern crate language_tags; From 5b7740dee3f0ddd5ff953755b62f1371c95e7489 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 16:12:16 -0800 Subject: [PATCH 0798/1635] hide ChunkedReadFile --- src/fs.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 10cdaff7..4fa11287 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -472,6 +472,7 @@ impl Responder for NamedFile { } } +#[doc(hidden)] /// A helper created from a `std::fs::File` which reads the file /// chunk-by-chunk on a `CpuPool`. pub struct ChunkedReadFile { @@ -562,7 +563,8 @@ impl Directory { } fn directory_listing( - dir: &Directory, req: &HttpRequest, + dir: &Directory, + req: &HttpRequest, ) -> Result { let index_of = format!("Index of {}", req.path()); let mut body = String::new(); @@ -656,7 +658,8 @@ impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory and /// `CpuPool`. pub fn with_pool>( - dir: T, pool: CpuPool, + dir: T, + pool: CpuPool, ) -> Result, Error> { Self::with_config_pool(dir, pool, DefaultConfig) } @@ -667,7 +670,8 @@ impl StaticFiles { /// /// Identical with `new` but allows to specify configiration to use. pub fn with_config>( - dir: T, config: C, + dir: T, + config: C, ) -> Result, Error> { // use default CpuPool let pool = { DEFAULT_CPUPOOL.lock().clone() }; @@ -678,7 +682,9 @@ impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory with config and /// `CpuPool`. pub fn with_config_pool>( - dir: T, pool: CpuPool, _: C, + dir: T, + pool: CpuPool, + _: C, ) -> Result, Error> { let dir = dir.into().canonicalize()?; @@ -736,7 +742,8 @@ impl StaticFiles { } fn try_handle( - &self, req: &HttpRequest, + &self, + req: &HttpRequest, ) -> Result, Error> { let tail: String = req.match_info().query("tail")?; let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; From 7065c540e1822fb15d4d040703c314c15ce81e95 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 16:29:43 -0800 Subject: [PATCH 0799/1635] set nodelay on socket #560 --- CHANGES.md | 14 ++++++++------ src/server/builder.rs | 33 ++++++++++++++++++++++++++------- src/server/service.rs | 2 +- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1e66cff8..61723741 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,18 +2,20 @@ ## [0.7.14] - 2018-11-x +### Added + +* Add method to configure custom error handler to `Query` and `Path` extractors. + +* Add method to configure `SameSite` option in `CookieIdentityPolicy`. + + ### Fixed * Fix keep-alive timer reset * HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549 - -### Added - -* Add method to configure custom error handler to `Query` and `Path` extractors. - -* Add method to configure `SameSite` option in `CookieIdentityPolicy`. +* Set nodelay for socket #560 ## [0.7.13] - 2018-10-14 diff --git a/src/server/builder.rs b/src/server/builder.rs index 4f159af1..ea3638f1 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -9,14 +9,20 @@ use super::acceptor::{ }; use super::error::AcceptorError; use super::handler::IntoHttpHandler; -use super::service::HttpService; +use super::service::{HttpService, StreamConfiguration}; use super::settings::{ServerSettings, ServiceConfig}; use super::KeepAlive; pub(crate) trait ServiceProvider { fn register( - &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, + &self, + server: Server, + lst: net::TcpListener, + host: String, + addr: net::SocketAddr, + keep_alive: KeepAlive, + secure: bool, + client_timeout: u64, client_shutdown: u64, ) -> Server; } @@ -43,8 +49,13 @@ where } fn finish( - &self, host: String, addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, - client_timeout: u64, client_shutdown: u64, + &self, + host: String, + addr: net::SocketAddr, + keep_alive: KeepAlive, + secure: bool, + client_timeout: u64, + client_shutdown: u64, ) -> impl ServiceFactory { let factory = self.factory.clone(); let acceptor = self.acceptor.clone(); @@ -65,6 +76,7 @@ where acceptor.create(), )).map_err(|_| ()) .map_init_err(|_| ()) + .and_then(StreamConfiguration::new().nodelay(true)) .and_then( HttpService::new(settings) .map_init_err(|_| ()) @@ -76,6 +88,7 @@ where TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) .map_err(|_| ()) .map_init_err(|_| ()) + .and_then(StreamConfiguration::new().nodelay(true)) .and_then( HttpService::new(settings) .map_init_err(|_| ()) @@ -95,8 +108,14 @@ where H: IntoHttpHandler, { fn register( - &self, server: Server, lst: net::TcpListener, host: String, - addr: net::SocketAddr, keep_alive: KeepAlive, secure: bool, client_timeout: u64, + &self, + server: Server, + lst: net::TcpListener, + host: String, + addr: net::SocketAddr, + keep_alive: KeepAlive, + secure: bool, + client_timeout: u64, client_shutdown: u64, ) -> Server { server.listen2( diff --git a/src/server/service.rs b/src/server/service.rs index e3402e30..cd4b3d3f 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -88,7 +88,7 @@ where Ok(Async::Ready(())) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, mut req: Self::Request) -> Self::Future { HttpChannel::new(self.settings.clone(), req) } } From 61b1030882781f93c0228b5605041a197e5eb8f5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 20:35:47 -0800 Subject: [PATCH 0800/1635] Fix websockets connection drop if request contains content-length header #567 --- CHANGES.md | 2 ++ Cargo.toml | 4 ++-- src/server/h1decoder.rs | 31 +++++++++++++++++++++++++------ src/server/service.rs | 2 +- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 61723741..b1717ea9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,8 @@ ### Fixed +* Fix websockets connection drop if request contains "content-length" header #567 + * Fix keep-alive timer reset * HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549 diff --git a/Cargo.toml b/Cargo.toml index 0dcce54b..4abb64e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,8 +61,8 @@ flate2-rust = ["flate2/rust_backend"] cell = ["actix-net/cell"] [dependencies] -actix = "^0.7.5" -actix-net = "0.2.0" +actix = "0.7.6" +actix-net = "0.2.1" askama_escape = "0.1.0" base64 = "0.10" diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 434dc42d..10f7e68a 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -43,7 +43,9 @@ impl H1Decoder { } pub fn decode( - &mut self, src: &mut BytesMut, settings: &ServiceConfig, + &mut self, + src: &mut BytesMut, + settings: &ServiceConfig, ) -> Result, DecoderError> { // read payload if self.decoder.is_some() { @@ -80,7 +82,9 @@ impl H1Decoder { } fn parse_message( - &self, buf: &mut BytesMut, settings: &ServiceConfig, + &self, + buf: &mut BytesMut, + settings: &ServiceConfig, ) -> Poll<(Request, Option), ParseError> { // Parse http message let mut has_upgrade = false; @@ -178,6 +182,13 @@ impl H1Decoder { } header::UPGRADE => { has_upgrade = true; + // check content-length, some clients (dart) + // sends "content-length: 0" with websocket upgrade + if let Ok(val) = value.to_str() { + if val == "websocket" { + content_length = None; + } + } } _ => (), } @@ -221,7 +232,9 @@ pub(crate) struct HeaderIndex { impl HeaderIndex { pub(crate) fn record( - bytes: &[u8], headers: &[httparse::Header], indices: &mut [HeaderIndex], + bytes: &[u8], + headers: &[httparse::Header], + indices: &mut [HeaderIndex], ) { let bytes_ptr = bytes.as_ptr() as usize; for (header, indices) in headers.iter().zip(indices.iter_mut()) { @@ -369,7 +382,10 @@ macro_rules! byte ( impl ChunkedState { fn step( - &self, body: &mut BytesMut, size: &mut u64, buf: &mut Option, + &self, + body: &mut BytesMut, + size: &mut u64, + buf: &mut Option, ) -> Poll { use self::ChunkedState::*; match *self { @@ -432,7 +448,8 @@ impl ChunkedState { } } fn read_size_lf( - rdr: &mut BytesMut, size: &mut u64, + rdr: &mut BytesMut, + size: &mut u64, ) -> Poll { match byte!(rdr) { b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), @@ -445,7 +462,9 @@ impl ChunkedState { } fn read_body( - rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option, + rdr: &mut BytesMut, + rem: &mut u64, + buf: &mut Option, ) -> Poll { trace!("Chunked read, remaining={:?}", rem); diff --git a/src/server/service.rs b/src/server/service.rs index cd4b3d3f..e3402e30 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -88,7 +88,7 @@ where Ok(Async::Ready(())) } - fn call(&mut self, mut req: Self::Request) -> Self::Future { + fn call(&mut self, req: Self::Request) -> Self::Future { HttpChannel::new(self.settings.clone(), req) } } From dea39030bcf882c879c724d6b4d803735424e1dd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 20:38:40 -0800 Subject: [PATCH 0801/1635] properly handle upgrade header if content-length header is set --- src/h1/decoder.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index cfa0879d..472e2993 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -141,6 +141,13 @@ impl Decoder for RequestDecoder { } header::UPGRADE => { has_upgrade = true; + // check content-length, some clients (dart) + // sends "content-length: 0" with websocket upgrade + if let Ok(val) = value.to_str() { + if val == "websocket" { + content_length = None; + } + } } _ => (), } From b25b083866be7369af890e74a401b33767c856f2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 20:45:48 -0800 Subject: [PATCH 0802/1635] do not stop on keep-alive timer if sink is not completly flushed --- src/h1/dispatcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index cc9ec721..f4dfdb21 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -432,7 +432,7 @@ where return Err(DispatchError::DisconnectTimeout); } else if timer.deadline() >= self.ka_expire { // check for any outstanding response processing - if self.state.is_empty() { + if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); From 1ef0eed0bde2d66ea38762e7a8d1ec65b68e2cf4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Nov 2018 20:46:13 -0800 Subject: [PATCH 0803/1635] do not stop on keep-alive timer if sink is not completly flushed --- src/server/h1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/h1.rs b/src/server/h1.rs index 07f773eb..f491ba59 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -286,7 +286,7 @@ where } if timer.deadline() >= self.ka_expire { // check for any outstanding request handling - if self.tasks.is_empty() { + if self.tasks.is_empty() && self.flags.contains(Flags::FLUSHED) { if !self.flags.contains(Flags::STARTED) { // timeout on first request (slow request) return 408 trace!("Slow request timeout"); From 537144f0b9ef865935c3e542c40dab52da1bba96 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 11 Nov 2018 23:12:54 -0800 Subject: [PATCH 0804/1635] add http client connector service --- Cargo.toml | 26 +- src/client/connect.rs | 80 ++++++ src/client/connection.rs | 79 ++++++ src/client/connector.rs | 500 +++++++++++++++++++++++++++++++++ src/client/error.rs | 77 ++++++ src/client/mod.rs | 8 + src/client/pool.rs | 579 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 7 + 8 files changed, 1354 insertions(+), 2 deletions(-) create mode 100644 src/client/connect.rs create mode 100644 src/client/connection.rs create mode 100644 src/client/connector.rs create mode 100644 src/client/error.rs create mode 100644 src/client/pool.rs diff --git a/Cargo.toml b/Cargo.toml index fcc169b7..e586f34e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,16 +34,26 @@ session = ["cookie/secure"] cell = ["actix-net/cell"] +# tls +tls = ["native-tls", "actix-net/tls"] + +# openssl +ssl = ["openssl", "actix-net/ssl"] + +# rustls +rust-tls = ["rustls", "actix-net/rust-tls"] + [dependencies] actix = "0.7.5" -actix-net = "0.2.0" -#actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = "0.2.0" +actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" http = "0.1.8" httparse = "1.3" failure = "0.1.2" +indexmap = "1.0" log = "0.4" mime = "0.3" rand = "0.5" @@ -61,6 +71,7 @@ url = { version="1.7", features=["query_encoding"] } # io net2 = "0.2" +slab = "0.4" bytes = "0.4" byteorder = "1.2" futures = "0.1" @@ -70,6 +81,17 @@ tokio-io = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" tokio-current-thread = "0.1" +trust-dns-proto = "0.5.0" +trust-dns-resolver = "0.10.0" + +# native-tls +native-tls = { version="0.2", optional = true } + +# openssl +openssl = { version="0.10", optional = true } + +#rustls +rustls = { version = "^0.14", optional = true } [dev-dependencies] actix-web = "0.7" diff --git a/src/client/connect.rs b/src/client/connect.rs new file mode 100644 index 00000000..40c3e8ec --- /dev/null +++ b/src/client/connect.rs @@ -0,0 +1,80 @@ +use actix_net::connector::RequestPort; +use actix_net::resolver::RequestHost; +use http::uri::Uri; +use http::{Error as HttpError, HttpTryFrom}; + +use super::error::{ConnectorError, InvalidUrlKind}; +use super::pool::Key; + +#[derive(Debug)] +/// `Connect` type represents a message that can be sent to +/// `Connector` with a connection request. +pub struct Connect { + pub(crate) uri: Uri, +} + +impl Connect { + /// Construct `Uri` instance and create `Connect` message. + pub fn new(uri: U) -> Result + where + Uri: HttpTryFrom, + { + Ok(Connect { + uri: Uri::try_from(uri).map_err(|e| e.into())?, + }) + } + + /// Create `Connect` message for specified `Uri` + pub fn with(uri: Uri) -> Connect { + Connect { uri } + } + + pub(crate) fn is_secure(&self) -> bool { + if let Some(scheme) = self.uri.scheme_part() { + scheme.as_str() == "https" + } else { + false + } + } + + pub(crate) fn key(&self) -> Key { + self.uri.authority_part().unwrap().clone().into() + } + + pub(crate) fn validate(&self) -> Result<(), ConnectorError> { + if self.uri.host().is_none() { + Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingHost)) + } else if self.uri.scheme_part().is_none() { + Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingScheme)) + } else if let Some(scheme) = self.uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" | "https" | "wss" => Ok(()), + _ => Err(ConnectorError::InvalidUrl(InvalidUrlKind::UnknownScheme)), + } + } else { + Ok(()) + } + } +} + +impl RequestHost for Connect { + fn host(&self) -> &str { + &self.uri.host().unwrap() + } +} + +impl RequestPort for Connect { + fn port(&self) -> u16 { + if let Some(port) = self.uri.port() { + port + } else if let Some(scheme) = self.uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" => 80, + "https" | "wss" => 443, + _ => 80, + } + } else { + 80 + } + } +} diff --git a/src/client/connection.rs b/src/client/connection.rs new file mode 100644 index 00000000..294e100c --- /dev/null +++ b/src/client/connection.rs @@ -0,0 +1,79 @@ +use std::{fmt, io, time}; + +use futures::Poll; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::pool::Acquired; + +/// HTTP client connection +pub struct Connection { + io: T, + created: time::Instant, + pool: Option>, +} + +impl fmt::Debug for Connection +where + T: AsyncRead + AsyncWrite + fmt::Debug + 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Connection {:?}", self.io) + } +} + +impl Connection { + pub(crate) fn new(io: T, created: time::Instant, pool: Acquired) -> Self { + Connection { + io, + created, + pool: Some(pool), + } + } + + /// Raw IO stream + pub fn get_mut(&mut self) -> &mut T { + &mut self.io + } + + /// Close connection + pub fn close(mut self) { + if let Some(mut pool) = self.pool.take() { + pool.close(self) + } + } + + /// Release this connection to the connection pool + pub fn release(mut self) { + if let Some(mut pool) = self.pool.take() { + pool.release(self) + } + } + + pub(crate) fn into_inner(self) -> (T, time::Instant) { + (self.io, self.created) + } +} + +impl io::Read for Connection { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.io.read(buf) + } +} + +impl AsyncRead for Connection {} + +impl io::Write for Connection { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.io.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.io.flush() + } +} + +impl AsyncWrite for Connection { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.io.shutdown() + } +} diff --git a/src/client/connector.rs b/src/client/connector.rs new file mode 100644 index 00000000..97da074d --- /dev/null +++ b/src/client/connector.rs @@ -0,0 +1,500 @@ +use std::time::Duration; +use std::{fmt, io}; + +use actix_net::connector::TcpConnector; +use actix_net::resolver::Resolver; +use actix_net::service::{Service, ServiceExt}; +use actix_net::timeout::{TimeoutError, TimeoutService}; +use futures::future::Either; +use futures::Poll; +use tokio_io::{AsyncRead, AsyncWrite}; +use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; + +use super::connect::Connect; +use super::connection::Connection; +use super::error::ConnectorError; +use super::pool::ConnectionPool; + +#[cfg(feature = "ssl")] +use actix_net::ssl::OpensslConnector; +#[cfg(feature = "ssl")] +use openssl::ssl::{SslConnector, SslMethod}; + +#[cfg(not(feature = "ssl"))] +type SslConnector = (); + +/// Http client connector builde instance. +/// `Connector` type uses builder-like pattern for connector service construction. +pub struct Connector { + resolver: Resolver, + timeout: Duration, + conn_lifetime: Duration, + conn_keep_alive: Duration, + disconnect_timeout: Duration, + limit: usize, + #[allow(dead_code)] + connector: SslConnector, +} + +impl Default for Connector { + fn default() -> Connector { + let connector = { + #[cfg(feature = "ssl")] + { + SslConnector::builder(SslMethod::tls()).unwrap().build() + } + #[cfg(not(feature = "ssl"))] + { + () + } + }; + + Connector { + connector, + resolver: Resolver::default(), + timeout: Duration::from_secs(1), + conn_lifetime: Duration::from_secs(75), + conn_keep_alive: Duration::from_secs(15), + disconnect_timeout: Duration::from_millis(3000), + limit: 100, + } + } +} + +impl Connector { + /// Use custom resolver configuration. + pub fn resolver_config(mut self, cfg: ResolverConfig, opts: ResolverOpts) -> Self { + self.resolver = Resolver::new(cfg, opts); + self + } + + /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. + /// Set to 1 second by default. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + #[cfg(feature = "ssl")] + /// Use custom `SslConnector` instance. + pub fn ssl(mut self, connector: SslConnector) -> Self { + self.connector = connector; + self + } + + /// Set total number of simultaneous connections per type of scheme. + /// + /// If limit is 0, the connector has no limit. + /// The default limit size is 100. + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set keep-alive period for opened connection. + /// + /// Keep-alive period is the period between connection usage. If + /// the delay between repeated usages of the same connection + /// exceeds this period, the connection is closed. + /// Default keep-alive period is 15 seconds. + pub fn conn_keep_alive(mut self, dur: Duration) -> Self { + self.conn_keep_alive = dur; + self + } + + /// Set max lifetime period for connection. + /// + /// Connection lifetime is max lifetime of any opened connection + /// until it is closed regardless of keep-alive period. + /// Default lifetime period is 75 seconds. + pub fn conn_lifetime(mut self, dur: Duration) -> Self { + self.conn_lifetime = dur; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the socket get dropped. This timeout affects only secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn disconnect_timeout(mut self, dur: Duration) -> Self { + self.disconnect_timeout = dur; + self + } + + /// Finish configuration process and create connector service. + pub fn service( + self, + ) -> impl Service< + Request = Connect, + Response = impl AsyncRead + AsyncWrite + fmt::Debug, + Error = ConnectorError, + > + Clone { + #[cfg(not(feature = "ssl"))] + { + let connector = TimeoutService::new( + self.timeout, + self.resolver + .map_err(ConnectorError::from) + .and_then(TcpConnector::default().from_err()), + ).map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectorError::Timeout, + }); + + connect_impl::InnerConnector { + tcp_pool: ConnectionPool::new( + connector, + self.conn_lifetime, + self.conn_keep_alive, + None, + self.limit, + ), + } + } + #[cfg(feature = "ssl")] + { + let ssl_service = TimeoutService::new( + self.timeout, + self.resolver + .clone() + .map_err(ConnectorError::from) + .and_then(TcpConnector::default().from_err()) + .and_then( + OpensslConnector::service(self.connector) + .map_err(ConnectorError::SslError), + ), + ).map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectorError::Timeout, + }); + + let tcp_service = TimeoutService::new( + self.timeout, + self.resolver + .map_err(ConnectorError::from) + .and_then(TcpConnector::default().from_err()), + ).map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectorError::Timeout, + }); + + connect_impl::InnerConnector { + tcp_pool: ConnectionPool::new( + tcp_service, + self.conn_lifetime, + self.conn_keep_alive, + None, + self.limit, + ), + ssl_pool: ConnectionPool::new( + ssl_service, + self.conn_lifetime, + self.conn_keep_alive, + Some(self.disconnect_timeout), + self.limit, + ), + } + } + } +} + +#[cfg(not(feature = "ssl"))] +mod connect_impl { + use super::*; + use futures::future::{err, FutureResult}; + + pub(crate) struct InnerConnector + where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, + { + pub(crate) tcp_pool: ConnectionPool, + } + + impl Clone for InnerConnector + where + Io: AsyncRead + AsyncWrite + 'static, + T: Service + + Clone, + { + fn clone(&self) -> Self { + InnerConnector { + tcp_pool: self.tcp_pool.clone(), + } + } + } + + impl Service for InnerConnector + where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, + { + type Request = Connect; + type Response = Connection; + type Error = ConnectorError; + type Future = Either< + as Service>::Future, + FutureResult, ConnectorError>, + >; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.tcp_pool.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + if req.is_secure() { + Either::B(err(ConnectorError::SslIsNotSupported)) + } else if let Err(e) = req.validate() { + Either::B(err(e)) + } else { + Either::A(self.tcp_pool.call(req)) + } + } + } +} + +#[cfg(feature = "ssl")] +mod connect_impl { + use std::marker::PhantomData; + + use futures::future::{err, FutureResult}; + use futures::{Async, Future, Poll}; + + use super::*; + + pub(crate) struct InnerConnector + where + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + T1: Service< + Request = Connect, + Response = (Connect, Io1), + Error = ConnectorError, + >, + T2: Service< + Request = Connect, + Response = (Connect, Io2), + Error = ConnectorError, + >, + { + pub(crate) tcp_pool: ConnectionPool, + pub(crate) ssl_pool: ConnectionPool, + } + + impl Clone for InnerConnector + where + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + T1: Service< + Request = Connect, + Response = (Connect, Io1), + Error = ConnectorError, + > + Clone, + T2: Service< + Request = Connect, + Response = (Connect, Io2), + Error = ConnectorError, + > + Clone, + { + fn clone(&self) -> Self { + InnerConnector { + tcp_pool: self.tcp_pool.clone(), + ssl_pool: self.ssl_pool.clone(), + } + } + } + + impl Service for InnerConnector + where + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + T1: Service< + Request = Connect, + Response = (Connect, Io1), + Error = ConnectorError, + >, + T2: Service< + Request = Connect, + Response = (Connect, Io2), + Error = ConnectorError, + >, + { + type Request = Connect; + type Response = IoEither, Connection>; + type Error = ConnectorError; + type Future = Either< + FutureResult, + Either< + InnerConnectorResponseA, + InnerConnectorResponseB, + >, + >; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.tcp_pool.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + if let Err(e) = req.validate() { + Either::A(err(e)) + } else if req.is_secure() { + Either::B(Either::A(InnerConnectorResponseA { + fut: self.tcp_pool.call(req), + _t: PhantomData, + })) + } else { + Either::B(Either::B(InnerConnectorResponseB { + fut: self.ssl_pool.call(req), + _t: PhantomData, + })) + } + } + } + + pub(crate) struct InnerConnectorResponseA + where + Io1: AsyncRead + AsyncWrite + 'static, + T: Service, + { + fut: as Service>::Future, + _t: PhantomData, + } + + impl Future for InnerConnectorResponseA + where + T: Service, + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + { + type Item = IoEither, Connection>; + type Error = ConnectorError; + + fn poll(&mut self) -> Poll { + match self.fut.poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(res) => Ok(Async::Ready(IoEither::A(res))), + } + } + } + + pub(crate) struct InnerConnectorResponseB + where + Io2: AsyncRead + AsyncWrite + 'static, + T: Service, + { + fut: as Service>::Future, + _t: PhantomData, + } + + impl Future for InnerConnectorResponseB + where + T: Service, + Io1: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + 'static, + { + type Item = IoEither, Connection>; + type Error = ConnectorError; + + fn poll(&mut self) -> Poll { + match self.fut.poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(res) => Ok(Async::Ready(IoEither::B(res))), + } + } + } +} + +pub(crate) enum IoEither { + A(Io1), + B(Io2), +} + +impl io::Read for IoEither +where + Io1: io::Read, + Io2: io::Read, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + IoEither::A(ref mut io) => io.read(buf), + IoEither::B(ref mut io) => io.read(buf), + } + } +} + +impl AsyncRead for IoEither +where + Io1: AsyncRead, + Io2: AsyncRead, +{ + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + match self { + IoEither::A(ref io) => io.prepare_uninitialized_buffer(buf), + IoEither::B(ref io) => io.prepare_uninitialized_buffer(buf), + } + } +} + +impl AsyncWrite for IoEither +where + Io1: AsyncWrite, + Io2: AsyncWrite, +{ + fn shutdown(&mut self) -> Poll<(), io::Error> { + match self { + IoEither::A(ref mut io) => io.shutdown(), + IoEither::B(ref mut io) => io.shutdown(), + } + } + + fn poll_write(&mut self, buf: &[u8]) -> Poll { + match self { + IoEither::A(ref mut io) => io.poll_write(buf), + IoEither::B(ref mut io) => io.poll_write(buf), + } + } + + fn poll_flush(&mut self) -> Poll<(), io::Error> { + match self { + IoEither::A(ref mut io) => io.poll_flush(), + IoEither::B(ref mut io) => io.poll_flush(), + } + } +} + +impl io::Write for IoEither +where + Io1: io::Write, + Io2: io::Write, +{ + fn flush(&mut self) -> io::Result<()> { + match self { + IoEither::A(ref mut io) => io.flush(), + IoEither::B(ref mut io) => io.flush(), + } + } + + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + IoEither::A(ref mut io) => io.write(buf), + IoEither::B(ref mut io) => io.write(buf), + } + } +} + +impl fmt::Debug for IoEither +where + Io1: fmt::Debug, + Io2: fmt::Debug, +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + IoEither::A(ref io) => io.fmt(fmt), + IoEither::B(ref io) => io.fmt(fmt), + } + } +} diff --git a/src/client/error.rs b/src/client/error.rs new file mode 100644 index 00000000..ba640723 --- /dev/null +++ b/src/client/error.rs @@ -0,0 +1,77 @@ +use std::io; + +use trust_dns_resolver::error::ResolveError; + +#[cfg(feature = "ssl")] +use openssl::ssl::Error as SslError; + +#[cfg(all( + feature = "tls", + not(any(feature = "ssl", feature = "rust-tls")) +))] +use native_tls::Error as SslError; + +#[cfg(all( + feature = "rust-tls", + not(any(feature = "tls", feature = "ssl")) +))] +use std::io::Error as SslError; + +/// A set of errors that can occur while connecting to an HTTP host +#[derive(Fail, Debug)] +pub enum ConnectorError { + /// Invalid URL + #[fail(display = "Invalid URL")] + InvalidUrl(InvalidUrlKind), + + /// SSL feature is not enabled + #[fail(display = "SSL is not supported")] + SslIsNotSupported, + + /// SSL error + #[cfg(any(feature = "tls", feature = "ssl", feature = "rust-tls"))] + #[fail(display = "{}", _0)] + SslError(#[cause] SslError), + + /// Failed to resolve the hostname + #[fail(display = "Failed resolving hostname: {}", _0)] + Resolver(ResolveError), + + /// No dns records + #[fail(display = "No dns records found for the input")] + NoRecords, + + /// Connecting took too long + #[fail(display = "Timeout out while establishing connection")] + Timeout, + + /// Connector has been disconnected + #[fail(display = "Internal error: connector has been disconnected")] + Disconnected, + + /// Connection io error + #[fail(display = "{}", _0)] + IoError(io::Error), +} + +#[derive(Fail, Debug)] +pub enum InvalidUrlKind { + #[fail(display = "Missing url scheme")] + MissingScheme, + #[fail(display = "Unknown url scheme")] + UnknownScheme, + #[fail(display = "Missing host name")] + MissingHost, +} + +impl From for ConnectorError { + fn from(err: io::Error) -> ConnectorError { + ConnectorError::IoError(err) + } +} + +impl From for ConnectorError { + fn from(err: ResolveError) -> ConnectorError { + ConnectorError::Resolver(err) + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index a5582fea..714e6c69 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,6 +1,14 @@ //! Http client api +mod connect; +mod connection; +mod connector; +mod error; +mod pool; mod request; mod response; +pub use self::connect::Connect; +pub use self::connector::Connector; +pub use self::error::{ConnectorError, InvalidUrlKind}; pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; diff --git a/src/client/pool.rs b/src/client/pool.rs new file mode 100644 index 00000000..6ff8c96c --- /dev/null +++ b/src/client/pool.rs @@ -0,0 +1,579 @@ +use std::cell::RefCell; +use std::collections::{HashMap, VecDeque}; +use std::io; +use std::rc::Rc; +use std::time::{Duration, Instant}; + +use actix_net::service::Service; +use futures::future::{ok, Either, FutureResult}; +use futures::sync::oneshot; +use futures::task::AtomicTask; +use futures::{Async, Future, Poll}; +use http::uri::Authority; +use indexmap::IndexSet; +use slab::Slab; +use tokio_current_thread::spawn; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::{sleep, Delay}; + +use super::connect::Connect; +use super::connection::Connection; +use super::error::ConnectorError; + +#[derive(Hash, Eq, PartialEq, Clone, Debug)] +pub(crate) struct Key { + authority: Authority, +} + +impl From for Key { + fn from(authority: Authority) -> Key { + Key { authority } + } +} + +#[derive(Debug)] +struct AvailableConnection { + io: T, + used: Instant, + created: Instant, +} + +/// Connections pool +pub(crate) struct ConnectionPool( + T, + Rc>>, +); + +impl ConnectionPool +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, +{ + pub(crate) fn new( + connector: T, + conn_lifetime: Duration, + conn_keep_alive: Duration, + disconnect_timeout: Option, + limit: usize, + ) -> Self { + ConnectionPool( + connector, + Rc::new(RefCell::new(Inner { + conn_lifetime, + conn_keep_alive, + disconnect_timeout, + limit, + acquired: 0, + waiters: Slab::new(), + waiters_queue: IndexSet::new(), + available: HashMap::new(), + task: AtomicTask::new(), + })), + ) + } +} + +impl Clone for ConnectionPool +where + T: Clone, + Io: AsyncRead + AsyncWrite + 'static, +{ + fn clone(&self) -> Self { + ConnectionPool(self.0.clone(), self.1.clone()) + } +} + +impl Service for ConnectionPool +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, +{ + type Request = Connect; + type Response = Connection; + type Error = ConnectorError; + type Future = Either< + FutureResult, ConnectorError>, + Either, OpenConnection>, + >; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.0.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + let key = req.key(); + + // acquire connection + match self.1.as_ref().borrow_mut().acquire(&key) { + Acquire::Acquired(io, created) => { + // use existing connection + Either::A(ok(Connection::new( + io, + created, + Acquired(key, Some(self.1.clone())), + ))) + } + Acquire::NotAvailable => { + // connection is not available, wait + let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req); + Either::B(Either::A(WaitForConnection { + rx, + key, + token, + inner: Some(self.1.clone()), + })) + } + Acquire::Available => { + // open new connection + Either::B(Either::B(OpenConnection::new( + key, + self.1.clone(), + self.0.call(req), + ))) + } + } + } +} + +#[doc(hidden)] +pub struct WaitForConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + key: Key, + token: usize, + rx: oneshot::Receiver, ConnectorError>>, + inner: Option>>>, +} + +impl Drop for WaitForConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(i) = self.inner.take() { + let mut inner = i.as_ref().borrow_mut(); + inner.release_waiter(&self.key, self.token); + inner.check_availibility(); + } + } +} + +impl Future for WaitForConnection +where + Io: AsyncRead + AsyncWrite, +{ + type Item = Connection; + type Error = ConnectorError; + + fn poll(&mut self) -> Poll { + match self.rx.poll() { + Ok(Async::Ready(item)) => match item { + Err(err) => Err(err), + Ok(conn) => { + let _ = self.inner.take(); + Ok(Async::Ready(conn)) + } + }, + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(_) => { + let _ = self.inner.take(); + Err(ConnectorError::Disconnected) + } + } + } +} + +#[doc(hidden)] +pub struct OpenConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fut: F, + key: Key, + inner: Option>>>, +} + +impl OpenConnection +where + F: Future, + Io: AsyncRead + AsyncWrite + 'static, +{ + fn new(key: Key, inner: Rc>>, fut: F) -> Self { + OpenConnection { + key, + fut, + inner: Some(inner), + } + } +} + +impl Drop for OpenConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(inner) = self.inner.take() { + let mut inner = inner.as_ref().borrow_mut(); + inner.release(); + inner.check_availibility(); + } + } +} + +impl Future for OpenConnection +where + F: Future, + Io: AsyncRead + AsyncWrite, +{ + type Item = Connection; + type Error = ConnectorError; + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Err(err) => Err(err.into()), + Ok(Async::Ready((_, io))) => { + let _ = self.inner.take(); + Ok(Async::Ready(Connection::new( + io, + Instant::now(), + Acquired(self.key.clone(), self.inner.clone()), + ))) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + } + } +} + +struct OpenWaitingConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fut: F, + key: Key, + rx: Option, ConnectorError>>>, + inner: Option>>>, +} + +impl OpenWaitingConnection +where + F: Future + 'static, + Io: AsyncRead + AsyncWrite + 'static, +{ + fn spawn( + key: Key, + rx: oneshot::Sender, ConnectorError>>, + inner: Rc>>, + fut: F, + ) { + spawn(OpenWaitingConnection { + key, + fut, + rx: Some(rx), + inner: Some(inner), + }) + } +} + +impl Drop for OpenWaitingConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(inner) = self.inner.take() { + let mut inner = inner.as_ref().borrow_mut(); + inner.release(); + inner.check_availibility(); + } + } +} + +impl Future for OpenWaitingConnection +where + F: Future, + Io: AsyncRead + AsyncWrite, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Err(err) => { + let _ = self.inner.take(); + if let Some(rx) = self.rx.take() { + let _ = rx.send(Err(err)); + } + Err(()) + } + Ok(Async::Ready((_, io))) => { + let _ = self.inner.take(); + if let Some(rx) = self.rx.take() { + let _ = rx.send(Ok(Connection::new( + io, + Instant::now(), + Acquired(self.key.clone(), self.inner.clone()), + ))); + } + Ok(Async::Ready(())) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + } + } +} + +enum Acquire { + Acquired(T, Instant), + Available, + NotAvailable, +} + +pub(crate) struct Inner +where + Io: AsyncRead + AsyncWrite + 'static, +{ + conn_lifetime: Duration, + conn_keep_alive: Duration, + disconnect_timeout: Option, + limit: usize, + acquired: usize, + available: HashMap>>, + waiters: Slab<( + Connect, + oneshot::Sender, ConnectorError>>, + )>, + waiters_queue: IndexSet<(Key, usize)>, + task: AtomicTask, +} + +impl Inner +where + Io: AsyncRead + AsyncWrite + 'static, +{ + /// connection is not available, wait + fn wait_for( + &mut self, + connect: Connect, + ) -> ( + oneshot::Receiver, ConnectorError>>, + usize, + ) { + let (tx, rx) = oneshot::channel(); + + let key = connect.key(); + let entry = self.waiters.vacant_entry(); + let token = entry.key(); + entry.insert((connect, tx)); + assert!(!self.waiters_queue.insert((key, token))); + (rx, token) + } + + fn release_waiter(&mut self, key: &Key, token: usize) { + self.waiters.remove(token); + self.waiters_queue.remove(&(key.clone(), token)); + } + + fn acquire(&mut self, key: &Key) -> Acquire { + // check limits + if self.limit > 0 && self.acquired >= self.limit { + return Acquire::NotAvailable; + } + + self.reserve(); + + // check if open connection is available + // cleanup stale connections at the same time + if let Some(ref mut connections) = self.available.get_mut(key) { + let now = Instant::now(); + while let Some(conn) = connections.pop_back() { + // check if it still usable + if (now - conn.used) > self.conn_keep_alive + || (now - conn.created) > self.conn_lifetime + { + if let Some(timeout) = self.disconnect_timeout { + spawn(CloseConnection::new(conn.io, timeout)) + } + } else { + let mut io = conn.io; + let mut buf = [0; 2]; + match io.read(&mut buf) { + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), + Ok(n) if n > 0 => { + if let Some(timeout) = self.disconnect_timeout { + spawn(CloseConnection::new(io, timeout)) + } + continue; + } + Ok(_) | Err(_) => continue, + } + return Acquire::Acquired(io, conn.created); + } + } + } + Acquire::Available + } + + fn reserve(&mut self) { + self.acquired += 1; + } + + fn release(&mut self) { + self.acquired -= 1; + } + + fn release_conn(&mut self, key: &Key, io: Io, created: Instant) { + self.acquired -= 1; + self.available + .entry(key.clone()) + .or_insert_with(VecDeque::new) + .push_back(AvailableConnection { + io, + created, + used: Instant::now(), + }); + } + + fn release_close(&mut self, io: Io) { + self.acquired -= 1; + if let Some(timeout) = self.disconnect_timeout { + spawn(CloseConnection::new(io, timeout)) + } + } + + fn check_availibility(&self) { + if !self.waiters_queue.is_empty() && self.acquired < self.limit { + self.task.notify() + } + } +} + +struct ConnectorPoolSupport +where + Io: AsyncRead + AsyncWrite + 'static, +{ + connector: T, + inner: Rc>>, +} + +impl Future for ConnectorPoolSupport +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, + T::Future: 'static, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let mut inner = self.inner.as_ref().borrow_mut(); + inner.task.register(); + + // check waiters + loop { + let (key, token) = { + if let Some((key, token)) = inner.waiters_queue.get_index(0) { + (key.clone(), *token) + } else { + break; + } + }; + match inner.acquire(&key) { + Acquire::NotAvailable => break, + Acquire::Acquired(io, created) => { + let (_, tx) = inner.waiters.remove(token); + if let Err(conn) = tx.send(Ok(Connection::new( + io, + created, + Acquired(key.clone(), Some(self.inner.clone())), + ))) { + let (io, created) = conn.unwrap().into_inner(); + inner.release_conn(&key, io, created); + } + } + Acquire::Available => { + let (connect, tx) = inner.waiters.remove(token); + OpenWaitingConnection::spawn( + key.clone(), + tx, + self.inner.clone(), + self.connector.call(connect), + ); + } + } + let _ = inner.waiters_queue.swap_remove_index(0); + } + + Ok(Async::NotReady) + } +} + +struct CloseConnection { + io: T, + timeout: Delay, +} + +impl CloseConnection +where + T: AsyncWrite, +{ + fn new(io: T, timeout: Duration) -> Self { + CloseConnection { + io, + timeout: sleep(timeout), + } + } +} + +impl Future for CloseConnection +where + T: AsyncWrite, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll<(), ()> { + match self.timeout.poll() { + Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), + Ok(Async::NotReady) => match self.io.shutdown() { + Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), + Ok(Async::NotReady) => Ok(Async::NotReady), + }, + } + } +} + +pub(crate) struct Acquired( + Key, + Option>>>, +); + +impl Acquired +where + T: AsyncRead + AsyncWrite + 'static, +{ + pub(crate) fn close(&mut self, conn: Connection) { + if let Some(inner) = self.1.take() { + let (io, _) = conn.into_inner(); + inner.as_ref().borrow_mut().release_close(io); + } + } + pub(crate) fn release(&mut self, conn: Connection) { + if let Some(inner) = self.1.take() { + let (io, created) = conn.into_inner(); + inner + .as_ref() + .borrow_mut() + .release_conn(&self.0, io, created); + } + } +} + +impl Drop for Acquired +where + T: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(inner) = self.1.take() { + inner.as_ref().borrow_mut().release(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 572c23ae..32369d16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,6 +83,7 @@ extern crate cookie; extern crate encoding; extern crate http as modhttp; extern crate httparse; +extern crate indexmap; extern crate mime; extern crate net2; extern crate percent_encoding; @@ -90,18 +91,24 @@ extern crate rand; extern crate serde; extern crate serde_json; extern crate serde_urlencoded; +extern crate slab; extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; extern crate tokio_tcp; extern crate tokio_timer; +extern crate trust_dns_proto; +extern crate trust_dns_resolver; extern crate url as urlcrate; #[cfg(test)] #[macro_use] extern crate serde_derive; +#[cfg(feature = "ssl")] +extern crate openssl; + mod body; pub mod client; mod config; From 550c5f55b68a1eb00793c71f0bdd41c7ac12e3dd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 13 Nov 2018 22:53:30 -0800 Subject: [PATCH 0805/1635] add simple http client --- src/body.rs | 136 ++++++++++++++++++++++++++++- src/client/connect.rs | 12 +-- src/client/connection.rs | 4 +- src/client/connector.rs | 2 +- src/client/error.rs | 40 +++++++++ src/client/mod.rs | 6 +- src/client/pipeline.rs | 174 ++++++++++++++++++++++++++++++++++++ src/client/pool.rs | 67 +++++++------- src/client/request.rs | 184 ++++++++++++++++++++++++--------------- src/client/response.rs | 49 +++++++---- src/h1/client.rs | 60 ++++++------- src/h1/codec.rs | 14 ++- src/h1/decoder.rs | 8 +- src/h1/dispatcher.rs | 2 +- src/h1/encoder.rs | 4 +- src/h1/mod.rs | 11 ++- src/request.rs | 5 +- src/ws/client/service.rs | 7 +- tests/test_client.rs | 147 +++++++++++++++++++++++++++++++ 19 files changed, 745 insertions(+), 187 deletions(-) create mode 100644 src/client/pipeline.rs create mode 100644 tests/test_client.rs diff --git a/src/body.rs b/src/body.rs index c78ea817..e001273c 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,13 +1,39 @@ -use bytes::{Bytes, BytesMut}; -use futures::Stream; use std::sync::Arc; use std::{fmt, mem}; +use bytes::{Bytes, BytesMut}; +use futures::{Async, Poll, Stream}; + use error::Error; /// Type represent streaming body pub type BodyStream = Box>; +/// Different type of bory +pub enum BodyType { + None, + Zero, + Sized(usize), + Unsized, +} + +/// Type that provides this trait can be streamed to a peer. +pub trait MessageBody { + fn tp(&self) -> BodyType; + + fn poll_next(&mut self) -> Poll, Error>; +} + +impl MessageBody for () { + fn tp(&self) -> BodyType { + BodyType::Zero + } + + fn poll_next(&mut self) -> Poll, Error> { + Ok(Async::Ready(None)) + } +} + /// Represents various types of http message body. pub enum Body { /// Empty response. `Content-Length` header is set to `0` @@ -241,6 +267,112 @@ impl AsRef<[u8]> for Binary { } } +impl MessageBody for Bytes { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(mem::replace(self, Bytes::new())))) + } + } +} + +impl MessageBody for &'static str { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(Bytes::from_static( + mem::replace(self, "").as_ref(), + )))) + } + } +} + +impl MessageBody for &'static [u8] { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(Bytes::from_static(mem::replace( + self, b"", + ))))) + } + } +} + +impl MessageBody for Vec { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(Bytes::from(mem::replace( + self, + Vec::new(), + ))))) + } + } +} + +impl MessageBody for String { + fn tp(&self) -> BodyType { + BodyType::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(Bytes::from( + mem::replace(self, String::new()).into_bytes(), + )))) + } + } +} + +#[doc(hidden)] +pub struct MessageBodyStream { + stream: S, +} + +impl MessageBodyStream +where + S: Stream, +{ + pub fn new(stream: S) -> Self { + MessageBodyStream { stream } + } +} + +impl MessageBody for MessageBodyStream +where + S: Stream, +{ + fn tp(&self) -> BodyType { + BodyType::Unsized + } + + fn poll_next(&mut self) -> Poll, Error> { + self.stream.poll() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/client/connect.rs b/src/client/connect.rs index 40c3e8ec..a445228e 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -14,8 +14,13 @@ pub struct Connect { } impl Connect { + /// Create `Connect` message for specified `Uri` + pub fn new(uri: Uri) -> Connect { + Connect { uri } + } + /// Construct `Uri` instance and create `Connect` message. - pub fn new(uri: U) -> Result + pub fn try_from(uri: U) -> Result where Uri: HttpTryFrom, { @@ -24,11 +29,6 @@ impl Connect { }) } - /// Create `Connect` message for specified `Uri` - pub fn with(uri: Uri) -> Connect { - Connect { uri } - } - pub(crate) fn is_secure(&self) -> bool { if let Some(scheme) = self.uri.scheme_part() { scheme.as_str() == "https" diff --git a/src/client/connection.rs b/src/client/connection.rs index 294e100c..eec64267 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -6,7 +6,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::pool::Acquired; /// HTTP client connection -pub struct Connection { +pub struct Connection { io: T, created: time::Instant, pool: Option>, @@ -14,7 +14,7 @@ pub struct Connection { impl fmt::Debug for Connection where - T: AsyncRead + AsyncWrite + fmt::Debug + 'static, + T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Connection {:?}", self.io) diff --git a/src/client/connector.rs b/src/client/connector.rs index 97da074d..1eae135f 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -130,7 +130,7 @@ impl Connector { self, ) -> impl Service< Request = Connect, - Response = impl AsyncRead + AsyncWrite + fmt::Debug, + Response = Connection, Error = ConnectorError, > + Clone { #[cfg(not(feature = "ssl"))] diff --git a/src/client/error.rs b/src/client/error.rs index ba640723..2c475364 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -17,6 +17,8 @@ use native_tls::Error as SslError; ))] use std::io::Error as SslError; +use error::{Error, ParseError}; + /// A set of errors that can occur while connecting to an HTTP host #[derive(Fail, Debug)] pub enum ConnectorError { @@ -75,3 +77,41 @@ impl From for ConnectorError { ConnectorError::Resolver(err) } } + +/// A set of errors that can occur during request sending and response reading +#[derive(Debug)] +pub enum SendRequestError { + /// Failed to connect to host + // #[fail(display = "Failed to connect to host: {}", _0)] + Connector(ConnectorError), + /// Error sending request + Send(io::Error), + /// Error parsing response + Response(ParseError), + /// Error sending request body + Body(Error), +} + +impl From for SendRequestError { + fn from(err: io::Error) -> SendRequestError { + SendRequestError::Send(err) + } +} + +impl From for SendRequestError { + fn from(err: ConnectorError) -> SendRequestError { + SendRequestError::Connector(err) + } +} + +impl From for SendRequestError { + fn from(err: ParseError) -> SendRequestError { + SendRequestError::Response(err) + } +} + +impl From for SendRequestError { + fn from(err: Error) -> SendRequestError { + SendRequestError::Body(err) + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index 714e6c69..da0cbc67 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -3,12 +3,14 @@ mod connect; mod connection; mod connector; mod error; +mod pipeline; mod pool; mod request; mod response; pub use self::connect::Connect; +pub use self::connection::Connection; pub use self::connector::Connector; -pub use self::error::{ConnectorError, InvalidUrlKind}; -pub use self::request::{ClientRequest, ClientRequestBuilder}; +pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; +pub use self::request::{ClientRequest, ClientRequestBuilder, RequestHead}; pub use self::response::ClientResponse; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs new file mode 100644 index 00000000..fff90013 --- /dev/null +++ b/src/client/pipeline.rs @@ -0,0 +1,174 @@ +use std::collections::VecDeque; + +use actix_net::codec::Framed; +use actix_net::service::Service; +use bytes::Bytes; +use futures::future::{err, ok, Either}; +use futures::{Async, Future, Poll, Sink, Stream}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use super::error::{ConnectorError, SendRequestError}; +use super::request::RequestHead; +use super::response::ClientResponse; +use super::{Connect, Connection}; +use body::{BodyStream, BodyType, MessageBody}; +use error::Error; +use h1; + +pub fn send_request( + head: RequestHead, + body: B, + connector: &mut T, +) -> impl Future +where + T: Service, Error = ConnectorError>, + B: MessageBody, + Io: AsyncRead + AsyncWrite + 'static, +{ + let tp = body.tp(); + + connector + .call(Connect::new(head.uri.clone())) + .from_err() + .map(|io| Framed::new(io, h1::ClientCodec::default())) + .and_then(|framed| framed.send((head, tp).into()).from_err()) + .and_then(move |framed| match body.tp() { + BodyType::None | BodyType::Zero => Either::A(ok(framed)), + _ => Either::B(SendBody::new(body, framed)), + }).and_then(|framed| { + framed + .into_future() + .map_err(|(e, _)| SendRequestError::from(e)) + .and_then(|(item, framed)| { + if let Some(item) = item { + let mut res = item.into_item().unwrap(); + match framed.get_codec().message_type() { + h1::MessageType::None => release_connection(framed), + _ => res.payload = Some(Payload::stream(framed)), + } + ok(res) + } else { + err(ConnectorError::Disconnected.into()) + } + }) + }) +} + +struct SendBody { + body: Option, + framed: Option, h1::ClientCodec>>, + write_buf: VecDeque>, + flushed: bool, +} + +impl SendBody +where + Io: AsyncRead + AsyncWrite + 'static, + B: MessageBody, +{ + fn new(body: B, framed: Framed, h1::ClientCodec>) -> Self { + SendBody { + body: Some(body), + framed: Some(framed), + write_buf: VecDeque::new(), + flushed: true, + } + } +} + +impl Future for SendBody +where + Io: AsyncRead + AsyncWrite + 'static, + B: MessageBody, +{ + type Item = Framed, h1::ClientCodec>; + type Error = SendRequestError; + + fn poll(&mut self) -> Poll { + let mut body_ready = true; + loop { + while body_ready + && self.body.is_some() + && !self.framed.as_ref().unwrap().is_write_buf_full() + { + match self.body.as_mut().unwrap().poll_next()? { + Async::Ready(None) => { + self.flushed = false; + self.framed + .as_mut() + .unwrap() + .start_send(h1::Message::Chunk(None))?; + break; + } + Async::Ready(Some(chunk)) => { + self.flushed = false; + self.framed + .as_mut() + .unwrap() + .start_send(h1::Message::Chunk(Some(chunk)))?; + } + Async::NotReady => body_ready = false, + } + } + + if !self.flushed { + match self.framed.as_mut().unwrap().poll_complete()? { + Async::Ready(_) => { + self.flushed = true; + continue; + } + Async::NotReady => return Ok(Async::NotReady), + } + } + + if self.body.is_none() { + return Ok(Async::Ready(self.framed.take().unwrap())); + } + return Ok(Async::NotReady); + } + } +} + +struct Payload { + framed: Option, h1::ClientCodec>>, +} + +impl Payload { + fn stream(framed: Framed, h1::ClientCodec>) -> BodyStream { + Box::new(Payload { + framed: Some(framed), + }) + } +} + +impl Stream for Payload { + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + match self.framed.as_mut().unwrap().poll()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(Some(chunk)) => match chunk { + h1::Message::Chunk(Some(chunk)) => Ok(Async::Ready(Some(chunk))), + h1::Message::Chunk(None) => { + release_connection(self.framed.take().unwrap()); + Ok(Async::Ready(None)) + } + h1::Message::Item(_) => unreachable!(), + }, + Async::Ready(None) => Ok(Async::Ready(None)), + } + } +} + +fn release_connection(framed: Framed, h1::ClientCodec>) +where + Io: AsyncRead + AsyncWrite + 'static, +{ + let parts = framed.into_parts(); + if parts.read_buf.is_empty() && parts.write_buf.is_empty() { + parts.io.release() + } else { + parts.io.close() + } +} diff --git a/src/client/pool.rs b/src/client/pool.rs index 6ff8c96c..25296a6d 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -327,10 +327,7 @@ enum Acquire { NotAvailable, } -pub(crate) struct Inner -where - Io: AsyncRead + AsyncWrite + 'static, -{ +pub(crate) struct Inner { conn_lifetime: Duration, conn_keep_alive: Duration, disconnect_timeout: Option, @@ -345,6 +342,33 @@ where task: AtomicTask, } +impl Inner { + fn reserve(&mut self) { + self.acquired += 1; + } + + fn release(&mut self) { + self.acquired -= 1; + } + + fn release_waiter(&mut self, key: &Key, token: usize) { + self.waiters.remove(token); + self.waiters_queue.remove(&(key.clone(), token)); + } + + fn release_conn(&mut self, key: &Key, io: Io, created: Instant) { + self.acquired -= 1; + self.available + .entry(key.clone()) + .or_insert_with(VecDeque::new) + .push_back(AvailableConnection { + io, + created, + used: Instant::now(), + }); + } +} + impl Inner where Io: AsyncRead + AsyncWrite + 'static, @@ -367,11 +391,6 @@ where (rx, token) } - fn release_waiter(&mut self, key: &Key, token: usize) { - self.waiters.remove(token); - self.waiters_queue.remove(&(key.clone(), token)); - } - fn acquire(&mut self, key: &Key) -> Acquire { // check limits if self.limit > 0 && self.acquired >= self.limit { @@ -412,26 +431,6 @@ where Acquire::Available } - fn reserve(&mut self) { - self.acquired += 1; - } - - fn release(&mut self) { - self.acquired -= 1; - } - - fn release_conn(&mut self, key: &Key, io: Io, created: Instant) { - self.acquired -= 1; - self.available - .entry(key.clone()) - .or_insert_with(VecDeque::new) - .push_back(AvailableConnection { - io, - created, - used: Instant::now(), - }); - } - fn release_close(&mut self, io: Io) { self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { @@ -541,10 +540,7 @@ where } } -pub(crate) struct Acquired( - Key, - Option>>>, -); +pub(crate) struct Acquired(Key, Option>>>); impl Acquired where @@ -567,10 +563,7 @@ where } } -impl Drop for Acquired -where - T: AsyncRead + AsyncWrite + 'static, -{ +impl Drop for Acquired { fn drop(&mut self) { if let Some(inner) = self.1.take() { inner.as_ref().borrow_mut().release(); diff --git a/src/client/request.rs b/src/client/request.rs index 8c794933..60337413 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -2,17 +2,25 @@ use std::fmt; use std::fmt::Write as FmtWrite; use std::io::Write; -use bytes::{BufMut, BytesMut}; +use actix_net::service::Service; +use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; +use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use tokio_io::{AsyncRead, AsyncWrite}; use urlcrate::Url; +use body::{MessageBody, MessageBodyStream}; +use error::Error; use header::{self, Header, IntoHeaderValue}; use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; +use super::response::ClientResponse; +use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; + /// An HTTP Client Request /// /// ```rust @@ -38,29 +46,40 @@ use http::{ /// ); /// } /// ``` -pub struct ClientRequest { - uri: Uri, - method: Method, - version: Version, - headers: HeaderMap, - chunked: bool, - upgrade: bool, +pub struct ClientRequest { + head: RequestHead, + body: B, } -impl Default for ClientRequest { - fn default() -> ClientRequest { - ClientRequest { +pub struct RequestHead { + pub uri: Uri, + pub method: Method, + pub version: Version, + pub headers: HeaderMap, +} + +impl Default for RequestHead { + fn default() -> RequestHead { + RequestHead { uri: Uri::default(), method: Method::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - chunked: false, - upgrade: false, } } } -impl ClientRequest { +impl ClientRequest<()> { + /// Create client request builder + pub fn build() -> ClientRequestBuilder { + ClientRequestBuilder { + head: Some(RequestHead::default()), + err: None, + cookies: None, + default_headers: true, + } + } + /// Create request builder for `GET` request pub fn get>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); @@ -97,87 +116,90 @@ impl ClientRequest { } } -impl ClientRequest { - /// Create client request builder - pub fn build() -> ClientRequestBuilder { - ClientRequestBuilder { - request: Some(ClientRequest::default()), - err: None, - cookies: None, - default_headers: true, - } - } - +impl ClientRequest +where + B: MessageBody, +{ /// Get the request URI #[inline] pub fn uri(&self) -> &Uri { - &self.uri + &self.head.uri } /// Set client request URI #[inline] pub fn set_uri(&mut self, uri: Uri) { - self.uri = uri + self.head.uri = uri } /// Get the request method #[inline] pub fn method(&self) -> &Method { - &self.method + &self.head.method } /// Set HTTP `Method` for the request #[inline] pub fn set_method(&mut self, method: Method) { - self.method = method + self.head.method = method } /// Get HTTP version for the request #[inline] pub fn version(&self) -> Version { - self.version + self.head.version } /// Set http `Version` for the request #[inline] pub fn set_version(&mut self, version: Version) { - self.version = version + self.head.version = version } /// Get the headers from the request #[inline] pub fn headers(&self) -> &HeaderMap { - &self.headers + &self.head.headers } /// Get a mutable reference to the headers #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers + &mut self.head.headers } - /// is chunked encoding enabled - #[inline] - pub fn chunked(&self) -> bool { - self.chunked + /// Deconstruct ClientRequest to a RequestHead and body tuple + pub fn into_parts(self) -> (RequestHead, B) { + (self.head, self.body) } - /// is upgrade request - #[inline] - pub fn upgrade(&self) -> bool { - self.upgrade + // Send request + /// + /// This method returns a future that resolves to a ClientResponse + pub fn send( + self, + connector: &mut T, + ) -> impl Future + where + T: Service, Error = ConnectorError>, + Io: AsyncRead + AsyncWrite + 'static, + { + pipeline::send_request(self.head, self.body, connector) } } -impl fmt::Debug for ClientRequest { +impl fmt::Debug for ClientRequest +where + B: MessageBody, +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, "\nClientRequest {:?} {}:{}", - self.version, self.method, self.uri + self.head.version, self.head.method, self.head.uri )?; writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { + for (key, val) in self.head.headers.iter() { writeln!(f, " {:?}: {:?}", key, val)?; } Ok(()) @@ -189,7 +211,7 @@ impl fmt::Debug for ClientRequest { /// This type can be used to construct an instance of `ClientRequest` through a /// builder-like pattern. pub struct ClientRequestBuilder { - request: Option, + head: Option, err: Option, cookies: Option, default_headers: bool, @@ -208,7 +230,7 @@ impl ClientRequestBuilder { fn _uri(&mut self, url: &str) -> &mut Self { match Uri::try_from(url) { Ok(uri) => { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { parts.uri = uri; } } @@ -220,7 +242,7 @@ impl ClientRequestBuilder { /// Set HTTP method of this request. #[inline] pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { parts.method = method; } self @@ -229,7 +251,7 @@ impl ClientRequestBuilder { /// Set HTTP method of this request. #[inline] pub fn get_method(&mut self) -> &Method { - let parts = self.request.as_ref().expect("cannot reuse request builder"); + let parts = self.head.as_ref().expect("cannot reuse request builder"); &parts.method } @@ -238,7 +260,7 @@ impl ClientRequestBuilder { /// By default requests's HTTP version depends on network stream #[inline] pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { parts.version = version; } self @@ -263,7 +285,7 @@ impl ClientRequestBuilder { /// ``` #[doc(hidden)] pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match hdr.try_into() { Ok(value) => { parts.headers.insert(H::name(), value); @@ -299,7 +321,7 @@ impl ClientRequestBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { @@ -319,7 +341,7 @@ impl ClientRequestBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { @@ -339,7 +361,7 @@ impl ClientRequestBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => if !parts.headers.contains_key(&key) { match value.try_into() { @@ -357,11 +379,12 @@ impl ClientRequestBuilder { /// Enable connection upgrade #[inline] - pub fn upgrade(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.upgrade = true; - } - self + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { + self.set_header(header::UPGRADE, value) + .set_header(header::CONNECTION, "upgrade") } /// Set request's content type @@ -370,7 +393,7 @@ impl ClientRequestBuilder { where HeaderValue: HttpTryFrom, { - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderValue::try_from(value) { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); @@ -454,14 +477,17 @@ impl ClientRequestBuilder { /// Set a body and generate `ClientRequest`. /// /// `ClientRequestBuilder` can not be used after this call. - pub fn finish(&mut self) -> Result { + pub fn body( + &mut self, + body: B, + ) -> Result, HttpError> { if let Some(e) = self.err.take() { return Err(e); } if self.default_headers { // enable br only for https - let https = if let Some(parts) = parts(&mut self.request, &self.err) { + let https = if let Some(parts) = parts(&mut self.head, &self.err) { parts .uri .scheme_part() @@ -478,7 +504,7 @@ impl ClientRequestBuilder { } // set request host header - if let Some(parts) = parts(&mut self.request, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(host) = parts.uri.host() { if !parts.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); @@ -505,7 +531,7 @@ impl ClientRequestBuilder { ); } - let mut request = self.request.take().expect("cannot reuse request builder"); + let mut head = self.head.take().expect("cannot reuse request builder"); // set cookies if let Some(ref mut jar) = self.cookies { @@ -515,18 +541,38 @@ impl ClientRequestBuilder { let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let _ = write!(&mut cookie, "; {}={}", name, value); } - request.headers.insert( + head.headers.insert( header::COOKIE, HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), ); } - Ok(request) + Ok(ClientRequest { head, body }) + } + + /// Set an streaming body and generate `ClientRequest`. + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn stream( + &mut self, + stream: S, + ) -> Result, HttpError> + where + S: Stream, + { + self.body(MessageBodyStream::new(stream)) + } + + /// Set an empty body and generate `ClientRequest`. + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn finish(&mut self) -> Result, HttpError> { + self.body(()) } /// This method construct new `ClientRequestBuilder` pub fn take(&mut self) -> ClientRequestBuilder { ClientRequestBuilder { - request: self.request.take(), + head: self.head.take(), err: self.err.take(), cookies: self.cookies.take(), default_headers: self.default_headers, @@ -536,9 +582,9 @@ impl ClientRequestBuilder { #[inline] fn parts<'a>( - parts: &'a mut Option, + parts: &'a mut Option, err: &Option, -) -> Option<&'a mut ClientRequest> { +) -> Option<&'a mut RequestHead> { if err.is_some() { return None; } @@ -547,7 +593,7 @@ fn parts<'a>( impl fmt::Debug for ClientRequestBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref parts) = self.request { + if let Some(ref parts) = self.head { writeln!( f, "\nClientRequestBuilder {:?} {}:{}", diff --git a/src/client/response.rs b/src/client/response.rs index 627a1c78..0d5a87a0 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -2,35 +2,38 @@ use std::cell::{Cell, Ref, RefCell, RefMut}; use std::fmt; use std::rc::Rc; +use bytes::Bytes; +use futures::{Async, Poll, Stream}; use http::{HeaderMap, Method, StatusCode, Version}; +use body::BodyStream; +use error::Error; use extensions::Extensions; -use httpmessage::HttpMessage; -use payload::Payload; use request::{Message, MessageFlags, MessagePool}; use uri::Url; /// Client Response pub struct ClientResponse { pub(crate) inner: Rc, + pub(crate) payload: Option, } -impl HttpMessage for ClientResponse { - type Stream = Payload; +// impl HttpMessage for ClientResponse { +// type Stream = Payload; - fn headers(&self) -> &HeaderMap { - &self.inner.headers - } +// fn headers(&self) -> &HeaderMap { +// &self.inner.headers +// } - #[inline] - fn payload(&self) -> Payload { - if let Some(payload) = self.inner.payload.borrow_mut().take() { - payload - } else { - Payload::empty() - } - } -} +// #[inline] +// fn payload(&self) -> Payload { +// if let Some(payload) = self.inner.payload.borrow_mut().take() { +// payload +// } else { +// Payload::empty() +// } +// } +// } impl ClientResponse { /// Create new Request instance @@ -52,6 +55,7 @@ impl ClientResponse { payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), }), + payload: None, } } @@ -108,6 +112,19 @@ impl ClientResponse { } } +impl Stream for ClientResponse { + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + if let Some(ref mut payload) = self.payload { + payload.poll() + } else { + Ok(Async::Ready(None)) + } + } +} + impl Drop for ClientResponse { fn drop(&mut self) { if Rc::strong_count(&self.inner) == 1 { diff --git a/src/h1/client.rs b/src/h1/client.rs index b55af185..9ace98e0 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -7,12 +7,14 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder}; use super::encoder::{RequestEncoder, ResponseLength}; use super::{Message, MessageType}; -use body::{Binary, Body}; -use client::{ClientRequest, ClientResponse}; +use body::{Binary, Body, BodyType}; +use client::{ClientResponse, RequestHead}; use config::ServiceConfig; use error::ParseError; use helpers; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::header::{ + HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, +}; use http::{Method, Version}; use request::MessagePool; @@ -22,7 +24,7 @@ bitflags! { const UPGRADE = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const KEEPALIVE_ENABLED = 0b0000_1000; - const UNHANDLED = 0b0001_0000; + const STREAM = 0b0001_0000; } } @@ -86,8 +88,8 @@ impl ClientCodec { /// Check last request's message type pub fn message_type(&self) -> MessageType { - if self.flags.contains(Flags::UNHANDLED) { - MessageType::Unhandled + if self.flags.contains(Flags::STREAM) { + MessageType::Stream } else if self.payload.is_none() { MessageType::None } else { @@ -96,38 +98,31 @@ impl ClientCodec { } /// prepare transfer encoding - pub fn prepare_te(&mut self, res: &mut ClientRequest) { + pub fn prepare_te(&mut self, head: &mut RequestHead, btype: BodyType) { self.te - .update(res, self.flags.contains(Flags::HEAD), self.version); + .update(head, self.flags.contains(Flags::HEAD), self.version); } fn encode_response( &mut self, - msg: ClientRequest, + msg: RequestHead, + btype: BodyType, buffer: &mut BytesMut, ) -> io::Result<()> { - // Connection upgrade - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - } - // render message { // status line writeln!( Writer(buffer), "{} {} {:?}\r", - msg.method(), - msg.uri() - .path_and_query() - .map(|u| u.as_str()) - .unwrap_or("/"), - msg.version() + msg.method, + msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), + msg.version ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; // write headers - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); - for (key, value) in msg.headers() { + buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE); + 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); @@ -135,10 +130,15 @@ impl ClientCodec { buffer.put_slice(b": "); buffer.put_slice(v); buffer.put_slice(b"\r\n"); + + // Connection upgrade + if key == UPGRADE { + self.flags.insert(Flags::UPGRADE); + } } // set date header - if !msg.headers().contains_key(DATE) { + if !msg.headers.contains_key(DATE) { self.config.set_date(buffer); } else { buffer.extend_from_slice(b"\r\n"); @@ -160,8 +160,6 @@ impl Decoder for ClientCodec { Some(PayloadItem::Eof) => Some(Message::Chunk(None)), None => None, }) - } else if self.flags.contains(Flags::UNHANDLED) { - Ok(None) } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags .set(Flags::HEAD, req.inner.method == Method::HEAD); @@ -172,9 +170,9 @@ impl Decoder for ClientCodec { match payload { PayloadType::None => self.payload = None, PayloadType::Payload(pl) => self.payload = Some(pl), - PayloadType::Unhandled => { - self.payload = None; - self.flags.insert(Flags::UNHANDLED); + PayloadType::Stream(pl) => { + self.payload = Some(pl); + self.flags.insert(Flags::STREAM); } }; Ok(Some(Message::Item(req))) @@ -185,7 +183,7 @@ impl Decoder for ClientCodec { } impl Encoder for ClientCodec { - type Item = Message; + type Item = Message<(RequestHead, BodyType)>; type Error = io::Error; fn encode( @@ -194,8 +192,8 @@ impl Encoder for ClientCodec { dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - Message::Item(res) => { - self.encode_response(res, dst)?; + Message::Item((msg, btype)) => { + self.encode_response(msg, btype, dst)?; } Message::Chunk(Some(bytes)) => { self.te.encode(bytes.as_ref(), dst)?; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index dd1e27e0..44a1b81f 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -23,7 +23,7 @@ bitflags! { const UPGRADE = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const KEEPALIVE_ENABLED = 0b0000_1000; - const UNHANDLED = 0b0001_0000; + const STREAM = 0b0001_0000; } } @@ -93,8 +93,8 @@ impl Codec { /// Check last request's message type pub fn message_type(&self) -> MessageType { - if self.flags.contains(Flags::UNHANDLED) { - MessageType::Unhandled + if self.flags.contains(Flags::STREAM) { + MessageType::Stream } else if self.payload.is_none() { MessageType::None } else { @@ -259,8 +259,6 @@ impl Decoder for Codec { } None => None, }) - } else if self.flags.contains(Flags::UNHANDLED) { - Ok(None) } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags .set(Flags::HEAD, req.inner.method == Method::HEAD); @@ -271,9 +269,9 @@ impl Decoder for Codec { match payload { PayloadType::None => self.payload = None, PayloadType::Payload(pl) => self.payload = Some(pl), - PayloadType::Unhandled => { - self.payload = None; - self.flags.insert(Flags::UNHANDLED); + PayloadType::Stream(pl) => { + self.payload = Some(pl); + self.flags.insert(Flags::STREAM); } } Ok(Some(Message::Item(req))) diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 472e2993..f2a3ee3f 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -25,7 +25,7 @@ pub struct ResponseDecoder(&'static MessagePool); pub enum PayloadType { None, Payload(PayloadDecoder), - Unhandled, + Stream(PayloadDecoder), } impl RequestDecoder { @@ -174,7 +174,7 @@ impl Decoder for RequestDecoder { PayloadType::Payload(PayloadDecoder::length(len)) } else if has_upgrade || msg.inner.method == Method::CONNECT { // upgrade(websocket) or connect - PayloadType::Unhandled + PayloadType::Stream(PayloadDecoder::eof()) } else if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); @@ -321,7 +321,7 @@ impl Decoder for ResponseDecoder { || msg.inner.method == Method::CONNECT { // switching protocol or connect - PayloadType::Unhandled + PayloadType::Stream(PayloadDecoder::eof()) } else if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); @@ -667,7 +667,7 @@ mod tests { fn is_unhandled(&self) -> bool { match self { - PayloadType::Unhandled => true, + PayloadType::Stream(_) => true, _ => false, } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f4dfdb21..b23af964 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -344,7 +344,7 @@ where *req.inner.payload.borrow_mut() = Some(pl); self.payload = Some(ps); } - MessageType::Unhandled => { + MessageType::Stream => { self.unhandled = Some(req); return Ok(updated); } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index fc0a3a1b..caad113d 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -9,7 +9,7 @@ use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use body::{Binary, Body}; -use client::ClientRequest; +use client::RequestHead; use header::ContentEncoding; use http::Method; use request::Request; @@ -196,7 +196,7 @@ impl RequestEncoder { self.te.encode_eof(buf) } - pub fn update(&mut self, resp: &mut ClientRequest, head: bool, version: Version) { + pub fn update(&mut self, resp: &mut RequestHead, head: bool, version: Version) { self.head = head; } } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index e7377177..e7e0759b 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -33,6 +33,15 @@ pub enum Message { Chunk(Option), } +impl Message { + pub fn into_item(self) -> Option { + match self { + Message::Item(item) => Some(item), + _ => None, + } + } +} + impl From for Message { fn from(item: T) -> Self { Message::Item(item) @@ -44,7 +53,7 @@ impl From for Message { pub enum MessageType { None, Payload, - Unhandled, + Stream, } #[cfg(test)] diff --git a/src/request.rs b/src/request.rs index 07632bf0..593080fa 100644 --- a/src/request.rs +++ b/src/request.rs @@ -240,7 +240,10 @@ impl MessagePool { if let Some(r) = Rc::get_mut(&mut msg) { r.reset(); } - return ClientResponse { inner: msg }; + return ClientResponse { + inner: msg, + payload: None, + }; } ClientResponse::with_pool(pool) } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 485ce562..80cf9868 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -13,6 +13,7 @@ use rand; use sha1::Sha1; use tokio_io::{AsyncRead, AsyncWrite}; +use body::BodyType; use client::ClientResponse; use h1; use ws::Codec; @@ -89,9 +90,7 @@ where req.request.set_header(header::ORIGIN, origin); } - req.request.upgrade(); - req.request.set_header(header::UPGRADE, "websocket"); - req.request.set_header(header::CONNECTION, "upgrade"); + req.request.upgrade("websocket"); req.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); if let Some(protocols) = req.protocols.take() { @@ -142,7 +141,7 @@ where // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed - .send(request.into()) + .send((request.into_parts().0, BodyType::None).into()) .map_err(ClientError::from) .and_then(|framed| { framed diff --git a/tests/test_client.rs b/tests/test_client.rs new file mode 100644 index 00000000..6741a2c6 --- /dev/null +++ b/tests/test_client.rs @@ -0,0 +1,147 @@ +extern crate actix; +extern crate actix_http; +extern crate actix_net; +extern crate bytes; +extern crate futures; + +use std::{thread, time}; + +use actix::System; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::future::{self, lazy, ok}; + +use actix_http::{client, h1, test, Request, Response}; + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_h1_v2() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let mut connector = sys + .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) + .unwrap(); + + let req = client::ClientRequest::get(format!("http://{}/", addr)) + .finish() + .unwrap(); + + let response = sys.block_on(req.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); + + let request = client::ClientRequest::get(format!("http://{}/", addr)) + .header("x-test", "111") + .finish() + .unwrap(); + let repr = format!("{:?}", request); + assert!(repr.contains("ClientRequest")); + assert!(repr.contains("x-test")); + + let response = sys.block_on(request.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); + + // read response + // let bytes = srv.execute(response.body()).unwrap(); + // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + let request = client::ClientRequest::post(format!("http://{}/", addr)) + .finish() + .unwrap(); + let response = sys.block_on(request.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); + + // read response + // let bytes = srv.execute(response.body()).unwrap(); + // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_connection_close() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let mut connector = sys + .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) + .unwrap(); + + let request = client::ClientRequest::get(format!("http://{}/", addr)) + .header("Connection", "close") + .finish() + .unwrap(); + let response = sys.block_on(request.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); +} + +#[test] +fn test_with_query_parameter() { + let addr = test::TestServer::unused_addr(); + thread::spawn(move || { + Server::new() + .bind("test", addr, move || { + h1::H1Service::build() + .finish(|req: Request| { + if req.uri().query().unwrap().contains("qp=") { + ok::<_, ()>(Response::Ok().finish()) + } else { + ok::<_, ()>(Response::BadRequest().finish()) + } + }).map(|_| ()) + }).unwrap() + .run(); + }); + thread::sleep(time::Duration::from_millis(100)); + + let mut sys = System::new("test"); + let mut connector = sys + .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) + .unwrap(); + + let request = client::ClientRequest::get(format!("http://{}/?qp=5", addr)) + .finish() + .unwrap(); + + let response = sys.block_on(request.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); +} From 6297fe0d4117bd93a4e215b36fee91e7bcb8d750 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 09:38:16 -0800 Subject: [PATCH 0806/1635] refactor client response payload handling --- Cargo.toml | 3 +- src/body.rs | 7 ++- src/client/pipeline.rs | 51 +++++++++++++------- src/client/response.rs | 45 +++++++++-------- src/error.rs | 8 +++- src/h1/client.rs | 106 ++++++++++++++++++++++++++++++----------- src/h1/dispatcher.rs | 7 ++- src/h1/mod.rs | 2 +- src/payload.rs | 8 ++-- src/request.rs | 2 +- tests/test_client.rs | 10 ++-- 11 files changed, 166 insertions(+), 83 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e586f34e..074e5363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,8 @@ rust-tls = ["rustls", "actix-net/rust-tls"] [dependencies] actix = "0.7.5" #actix-net = "0.2.0" -actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = { git="https://github.com/actix/actix-net.git" } +actix-net = { path="../actix-net" } base64 = "0.9" bitflags = "1.0" diff --git a/src/body.rs b/src/body.rs index e001273c..1165909e 100644 --- a/src/body.rs +++ b/src/body.rs @@ -4,10 +4,13 @@ use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; -use error::Error; +use error::{Error, PayloadError}; /// Type represent streaming body -pub type BodyStream = Box>; +pub type BodyStream = Box>; + +/// Type represent streaming payload +pub type PayloadStream = Box>; /// Different type of bory pub enum BodyType { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index fff90013..4081b635 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -11,8 +11,8 @@ use super::error::{ConnectorError, SendRequestError}; use super::request::RequestHead; use super::response::ClientResponse; use super::{Connect, Connection}; -use body::{BodyStream, BodyType, MessageBody}; -use error::Error; +use body::{BodyType, MessageBody, PayloadStream}; +use error::PayloadError; use h1; pub fn send_request( @@ -44,7 +44,7 @@ where let mut res = item.into_item().unwrap(); match framed.get_codec().message_type() { h1::MessageType::None => release_connection(framed), - _ => res.payload = Some(Payload::stream(framed)), + _ => *res.payload.borrow_mut() = Some(Payload::stream(framed)), } ok(res) } else { @@ -129,41 +129,56 @@ where } } -struct Payload { - framed: Option, h1::ClientCodec>>, +struct EmptyPayload; + +impl Stream for EmptyPayload { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + Ok(Async::Ready(None)) + } +} + +pub(crate) struct Payload { + framed: Option, h1::ClientPayloadCodec>>, +} + +impl Payload<()> { + pub fn empty() -> PayloadStream { + Box::new(EmptyPayload) + } } impl Payload { - fn stream(framed: Framed, h1::ClientCodec>) -> BodyStream { + fn stream(framed: Framed, h1::ClientCodec>) -> PayloadStream { Box::new(Payload { - framed: Some(framed), + framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), }) } } impl Stream for Payload { type Item = Bytes; - type Error = Error; + type Error = PayloadError; - fn poll(&mut self) -> Poll, Error> { + fn poll(&mut self) -> Poll, Self::Error> { match self.framed.as_mut().unwrap().poll()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(Some(chunk)) => match chunk { - h1::Message::Chunk(Some(chunk)) => Ok(Async::Ready(Some(chunk))), - h1::Message::Chunk(None) => { - release_connection(self.framed.take().unwrap()); - Ok(Async::Ready(None)) - } - h1::Message::Item(_) => unreachable!(), + Async::Ready(Some(chunk)) => if let Some(chunk) = chunk { + Ok(Async::Ready(Some(chunk))) + } else { + release_connection(self.framed.take().unwrap()); + Ok(Async::Ready(None)) }, Async::Ready(None) => Ok(Async::Ready(None)), } } } -fn release_connection(framed: Framed, h1::ClientCodec>) +fn release_connection(framed: Framed, U>) where - Io: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + 'static, { let parts = framed.into_parts(); if parts.read_buf.is_empty() && parts.write_buf.is_empty() { diff --git a/src/client/response.rs b/src/client/response.rs index 0d5a87a0..e8e63e4f 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -6,34 +6,37 @@ use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{HeaderMap, Method, StatusCode, Version}; -use body::BodyStream; -use error::Error; +use body::PayloadStream; +use error::PayloadError; use extensions::Extensions; +use httpmessage::HttpMessage; use request::{Message, MessageFlags, MessagePool}; use uri::Url; +use super::pipeline::Payload; + /// Client Response pub struct ClientResponse { pub(crate) inner: Rc, - pub(crate) payload: Option, + pub(crate) payload: RefCell>, } -// impl HttpMessage for ClientResponse { -// type Stream = Payload; +impl HttpMessage for ClientResponse { + type Stream = PayloadStream; -// fn headers(&self) -> &HeaderMap { -// &self.inner.headers -// } + fn headers(&self) -> &HeaderMap { + &self.inner.headers + } -// #[inline] -// fn payload(&self) -> Payload { -// if let Some(payload) = self.inner.payload.borrow_mut().take() { -// payload -// } else { -// Payload::empty() -// } -// } -// } + #[inline] + fn payload(&self) -> Self::Stream { + if let Some(payload) = self.payload.borrow_mut().take() { + payload + } else { + Payload::empty() + } + } +} impl ClientResponse { /// Create new Request instance @@ -55,7 +58,7 @@ impl ClientResponse { payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), }), - payload: None, + payload: RefCell::new(None), } } @@ -114,10 +117,10 @@ impl ClientResponse { impl Stream for ClientResponse { type Item = Bytes; - type Error = Error; + type Error = PayloadError; - fn poll(&mut self) -> Poll, Error> { - if let Some(ref mut payload) = self.payload { + fn poll(&mut self) -> Poll, Self::Error> { + if let Some(ref mut payload) = &mut *self.payload.borrow_mut() { payload.poll() } else { Ok(Async::Ready(None)) diff --git a/src/error.rs b/src/error.rs index 3064cda4..956ec4eb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -339,7 +339,7 @@ impl From for ParseError { pub enum PayloadError { /// A payload reached EOF, but is not complete. #[fail(display = "A payload reached EOF, but is not complete.")] - Incomplete, + Incomplete(Option), /// Content encoding stream corruption #[fail(display = "Can not decode content-encoding.")] EncodingCorrupted, @@ -351,6 +351,12 @@ pub enum PayloadError { UnknownLength, } +impl From for PayloadError { + fn from(err: io::Error) -> Self { + PayloadError::Incomplete(Some(err)) + } +} + /// `PayloadError` returns two possible results: /// /// - `Overflow` returns `PayloadTooLarge` diff --git a/src/h1/client.rs b/src/h1/client.rs index 9ace98e0..eb7bdc23 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -10,7 +10,7 @@ use super::{Message, MessageType}; use body::{Binary, Body, BodyType}; use client::{ClientResponse, RequestHead}; use config::ServiceConfig; -use error::ParseError; +use error::{ParseError, PayloadError}; use helpers; use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, @@ -32,6 +32,15 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// HTTP/1 Codec pub struct ClientCodec { + inner: ClientCodecInner, +} + +/// HTTP/1 Payload Codec +pub struct ClientPayloadCodec { + inner: ClientCodecInner, +} + +struct ClientCodecInner { config: ServiceConfig, decoder: ResponseDecoder, payload: Option, @@ -65,32 +74,34 @@ impl ClientCodec { Flags::empty() }; ClientCodec { - config, - decoder: ResponseDecoder::with_pool(pool), - payload: None, - version: Version::HTTP_11, + inner: ClientCodecInner { + config, + decoder: ResponseDecoder::with_pool(pool), + payload: None, + version: Version::HTTP_11, - flags, - headers_size: 0, - te: RequestEncoder::default(), + flags, + headers_size: 0, + te: RequestEncoder::default(), + }, } } /// Check if request is upgrade pub fn upgrade(&self) -> bool { - self.flags.contains(Flags::UPGRADE) + self.inner.flags.contains(Flags::UPGRADE) } /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) + self.inner.flags.contains(Flags::KEEPALIVE) } /// Check last request's message type pub fn message_type(&self) -> MessageType { - if self.flags.contains(Flags::STREAM) { + if self.inner.flags.contains(Flags::STREAM) { MessageType::Stream - } else if self.payload.is_none() { + } else if self.inner.payload.is_none() { MessageType::None } else { MessageType::Payload @@ -99,10 +110,27 @@ impl ClientCodec { /// prepare transfer encoding pub fn prepare_te(&mut self, head: &mut RequestHead, btype: BodyType) { - self.te - .update(head, self.flags.contains(Flags::HEAD), self.version); + self.inner.te.update( + head, + self.inner.flags.contains(Flags::HEAD), + self.inner.version, + ); } + /// Convert message codec to a payload codec + pub fn into_payload_codec(self) -> ClientPayloadCodec { + ClientPayloadCodec { inner: self.inner } + } +} + +impl ClientPayloadCodec { + /// Transform payload codec to a message codec + pub fn into_message_codec(self) -> ClientCodec { + ClientCodec { inner: self.inner } + } +} + +impl ClientCodecInner { fn encode_response( &mut self, msg: RequestHead, @@ -154,25 +182,26 @@ impl Decoder for ClientCodec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if self.payload.is_some() { - Ok(match self.payload.as_mut().unwrap().decode(src)? { + if self.inner.payload.is_some() { + Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), Some(PayloadItem::Eof) => Some(Message::Chunk(None)), None => None, }) - } else if let Some((req, payload)) = self.decoder.decode(src)? { - self.flags + } else if let Some((req, payload)) = self.inner.decoder.decode(src)? { + self.inner + .flags .set(Flags::HEAD, req.inner.method == Method::HEAD); - self.version = req.inner.version; - if self.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.flags.set(Flags::KEEPALIVE, req.keep_alive()); + self.inner.version = req.inner.version; + if self.inner.flags.contains(Flags::KEEPALIVE_ENABLED) { + self.inner.flags.set(Flags::KEEPALIVE, req.keep_alive()); } match payload { - PayloadType::None => self.payload = None, - PayloadType::Payload(pl) => self.payload = Some(pl), + PayloadType::None => self.inner.payload = None, + PayloadType::Payload(pl) => self.inner.payload = Some(pl), PayloadType::Stream(pl) => { - self.payload = Some(pl); - self.flags.insert(Flags::STREAM); + self.inner.payload = Some(pl); + self.inner.flags.insert(Flags::STREAM); } }; Ok(Some(Message::Item(req))) @@ -182,6 +211,27 @@ impl Decoder for ClientCodec { } } +impl Decoder for ClientPayloadCodec { + type Item = Option; + type Error = PayloadError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + assert!( + self.inner.payload.is_some(), + "Payload decoder is not specified" + ); + + Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { + Some(PayloadItem::Chunk(chunk)) => Some(Some(chunk)), + Some(PayloadItem::Eof) => { + self.inner.payload.take(); + Some(None) + } + None => None, + }) + } +} + impl Encoder for ClientCodec { type Item = Message<(RequestHead, BodyType)>; type Error = io::Error; @@ -193,13 +243,13 @@ impl Encoder for ClientCodec { ) -> Result<(), Self::Error> { match item { Message::Item((msg, btype)) => { - self.encode_response(msg, btype, dst)?; + self.inner.encode_response(msg, btype, dst)?; } Message::Chunk(Some(bytes)) => { - self.te.encode(bytes.as_ref(), dst)?; + self.inner.te.encode(bytes.as_ref(), dst)?; } Message::Chunk(None) => { - self.te.encode_eof(dst)?; + self.inner.te.encode_eof(dst)?; } } Ok(()) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index b23af964..71d6435c 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -143,7 +143,7 @@ where fn client_disconnected(&mut self) { self.flags.insert(Flags::DISCONNECTED); if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); + payload.set_error(PayloadError::Incomplete(None)); } } @@ -228,7 +228,7 @@ where } Err(err) => { if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); + payload.set_error(PayloadError::Incomplete(None)); } return Err(DispatchError::Io(err)); } @@ -236,7 +236,10 @@ where } // Send payload State::SendPayload(ref mut stream, ref mut bin) => { + println!("SEND payload"); if let Some(item) = bin.take() { + let mut framed = self.framed.as_mut().unwrap(); + if framed.is_ match self.framed.as_mut().unwrap().start_send(item) { Ok(AsyncSink::Ready) => { self.flags.remove(Flags::FLUSHED); diff --git a/src/h1/mod.rs b/src/h1/mod.rs index e7e0759b..21261e99 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -9,7 +9,7 @@ mod dispatcher; mod encoder; mod service; -pub use self::client::ClientCodec; +pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; diff --git a/src/payload.rs b/src/payload.rs index 54539c40..b0592496 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -527,7 +527,7 @@ mod tests { #[test] fn test_error() { - let err = PayloadError::Incomplete; + let err = PayloadError::Incomplete(None); assert_eq!( format!("{}", err), "A payload reached EOF, but is not complete." @@ -584,7 +584,7 @@ mod tests { assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - sender.set_error(PayloadError::Incomplete); + sender.set_error(PayloadError::Incomplete(None)); payload.readany().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) @@ -644,7 +644,7 @@ mod tests { ); assert_eq!(payload.len, 4); - sender.set_error(PayloadError::Incomplete); + sender.set_error(PayloadError::Incomplete(None)); payload.read_exact(10).err().unwrap(); let res: Result<(), ()> = Ok(()); @@ -677,7 +677,7 @@ mod tests { ); assert_eq!(payload.len, 0); - sender.set_error(PayloadError::Incomplete); + sender.set_error(PayloadError::Incomplete(None)); payload.read_until(b"b").err().unwrap(); let res: Result<(), ()> = Ok(()); diff --git a/src/request.rs b/src/request.rs index 593080fa..fb5cb183 100644 --- a/src/request.rs +++ b/src/request.rs @@ -242,7 +242,7 @@ impl MessagePool { } return ClientResponse { inner: msg, - payload: None, + payload: RefCell::new(None), }; } ClientResponse::with_pool(pool) diff --git a/tests/test_client.rs b/tests/test_client.rs index 6741a2c6..40920d1b 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -9,8 +9,10 @@ use std::{thread, time}; use actix::System; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use bytes::Bytes; use futures::future::{self, lazy, ok}; +use actix_http::HttpMessage; use actix_http::{client, h1, test, Request, Response}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -73,8 +75,8 @@ fn test_h1_v2() { assert!(response.status().is_success()); // read response - // let bytes = srv.execute(response.body()).unwrap(); - // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = client::ClientRequest::post(format!("http://{}/", addr)) .finish() @@ -83,8 +85,8 @@ fn test_h1_v2() { assert!(response.status().is_success()); // read response - // let bytes = srv.execute(response.body()).unwrap(); - // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + let bytes = sys.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] From 03ad9a3105d95aaf5c8f3bb2a58cd8d600380344 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 10:52:40 -0800 Subject: [PATCH 0807/1635] simplify client decoder --- Cargo.toml | 6 +-- src/client/pipeline.rs | 18 +++------ src/h1/client.rs | 16 +++----- src/h1/dispatcher.rs | 79 +++++++++++++++------------------------- src/h1/mod.rs | 9 ----- src/ws/client/service.rs | 8 +--- 6 files changed, 46 insertions(+), 90 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 074e5363..80d24595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,9 @@ rust-tls = ["rustls", "actix-net/rust-tls"] [dependencies] actix = "0.7.5" -#actix-net = "0.2.0" -#actix-net = { git="https://github.com/actix/actix-net.git" } -actix-net = { path="../actix-net" } +#actix-net = "0.2.2" +actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = { path="../actix-net" } base64 = "0.9" bitflags = "1.0" diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 4081b635..24ec8366 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -40,11 +40,12 @@ where .into_future() .map_err(|(e, _)| SendRequestError::from(e)) .and_then(|(item, framed)| { - if let Some(item) = item { - let mut res = item.into_item().unwrap(); + if let Some(res) = item { match framed.get_codec().message_type() { h1::MessageType::None => release_connection(framed), - _ => *res.payload.borrow_mut() = Some(Payload::stream(framed)), + _ => { + *res.payload.borrow_mut() = Some(Payload::stream(framed)) + } } ok(res) } else { @@ -92,21 +93,14 @@ where && !self.framed.as_ref().unwrap().is_write_buf_full() { match self.body.as_mut().unwrap().poll_next()? { - Async::Ready(None) => { + Async::Ready(item) => { self.flushed = false; self.framed .as_mut() .unwrap() - .start_send(h1::Message::Chunk(None))?; + .force_send(h1::Message::Chunk(item))?; break; } - Async::Ready(Some(chunk)) => { - self.flushed = false; - self.framed - .as_mut() - .unwrap() - .start_send(h1::Message::Chunk(Some(chunk)))?; - } Async::NotReady => body_ready = false, } } diff --git a/src/h1/client.rs b/src/h1/client.rs index eb7bdc23..d2ac2034 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -178,17 +178,13 @@ impl ClientCodecInner { } impl Decoder for ClientCodec { - type Item = Message; + type Item = ClientResponse; type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if self.inner.payload.is_some() { - Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), - Some(PayloadItem::Eof) => Some(Message::Chunk(None)), - None => None, - }) - } else if let Some((req, payload)) = self.inner.decoder.decode(src)? { + debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); + + if let Some((req, payload)) = self.inner.decoder.decode(src)? { self.inner .flags .set(Flags::HEAD, req.inner.method == Method::HEAD); @@ -204,7 +200,7 @@ impl Decoder for ClientCodec { self.inner.flags.insert(Flags::STREAM); } }; - Ok(Some(Message::Item(req))) + Ok(Some(req)) } else { Ok(None) } @@ -216,7 +212,7 @@ impl Decoder for ClientPayloadCodec { type Error = PayloadError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - assert!( + debug_assert!( self.inner.payload.is_some(), "Payload decoder is not specified" ); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 71d6435c..470d3df9 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -64,7 +64,7 @@ enum State { None, ServiceCall(S::Future), SendResponse(Option<(Message, Body)>), - SendPayload(Option, Option>), + SendPayload(BodyStream), } impl State { @@ -204,21 +204,23 @@ where // send respons State::SendResponse(ref mut item) => { let (msg, body) = item.take().expect("SendResponse is empty"); - match self.framed.as_mut().unwrap().start_send(msg) { + let framed = self.framed.as_mut().unwrap(); + match framed.start_send(msg) { Ok(AsyncSink::Ready) => { - self.flags.set( - Flags::KEEPALIVE, - self.framed.as_mut().unwrap().get_codec().keepalive(), - ); + self.flags + .set(Flags::KEEPALIVE, framed.get_codec().keepalive()); self.flags.remove(Flags::FLUSHED); match body { Body::Empty => Some(State::None), - Body::Binary(mut bin) => Some(State::SendPayload( - None, - Some(Message::Chunk(Some(bin.take()))), - )), Body::Streaming(stream) => { - Some(State::SendPayload(Some(stream), None)) + Some(State::SendPayload(stream)) + } + Body::Binary(mut bin) => { + self.flags.remove(Flags::FLUSHED); + framed + .force_send(Message::Chunk(Some(bin.take())))?; + framed.force_send(Message::Chunk(None))?; + Some(State::None) } } } @@ -235,51 +237,28 @@ where } } // Send payload - State::SendPayload(ref mut stream, ref mut bin) => { - println!("SEND payload"); - if let Some(item) = bin.take() { - let mut framed = self.framed.as_mut().unwrap(); - if framed.is_ - match self.framed.as_mut().unwrap().start_send(item) { - Ok(AsyncSink::Ready) => { - self.flags.remove(Flags::FLUSHED); - } - Ok(AsyncSink::NotReady(item)) => { - *bin = Some(item); - return Ok(()); - } - Err(err) => return Err(DispatchError::Io(err)), - } - } - if let Some(ref mut stream) = stream { - match stream.poll() { - Ok(Async::Ready(Some(item))) => match self - .framed - .as_mut() - .unwrap() - .start_send(Message::Chunk(Some(item))) - { - Ok(AsyncSink::Ready) => { + State::SendPayload(ref mut stream) => { + let mut framed = self.framed.as_mut().unwrap(); + loop { + if !framed.is_write_buf_full() { + match stream.poll().map_err(|_| DispatchError::Unknown)? { + Async::Ready(Some(item)) => { self.flags.remove(Flags::FLUSHED); + framed.force_send(Message::Chunk(Some(item)))?; continue; } - Ok(AsyncSink::NotReady(msg)) => { - *bin = Some(msg); - return Ok(()); + Async::Ready(None) => { + self.flags.remove(Flags::FLUSHED); + framed.force_send(Message::Chunk(None))?; } - Err(err) => return Err(DispatchError::Io(err)), - }, - Ok(Async::Ready(None)) => Some(State::SendPayload( - None, - Some(Message::Chunk(None)), - )), - Ok(Async::NotReady) => return Ok(()), - // Err(err) => return Err(DispatchError::Io(err)), - Err(_) => return Err(DispatchError::Unknown), + Async::NotReady => return Ok(()), + } + } else { + return Ok(()); } - } else { - Some(State::None) + break; } + None } }; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 21261e99..81838700 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -33,15 +33,6 @@ pub enum Message { Chunk(Option), } -impl Message { - pub fn into_item(self) -> Option { - match self { - Message::Item(item) => Some(item), - _ => None, - } - } -} - impl From for Message { fn from(item: T) -> Self { Message::Item(item) diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 80cf9868..34a15144 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -172,10 +172,7 @@ where { fut: Box< Future< - Item = ( - Option>, - Framed, - ), + Item = (Option, Framed), Error = ClientError, >, >, @@ -196,8 +193,7 @@ where let (item, framed) = try_ready!(self.fut.poll()); let res = match item { - Some(h1::Message::Item(res)) => res, - Some(h1::Message::Chunk(_)) => unreachable!(), + Some(res) => res, None => return Err(ClientError::Disconnected), }; From cd9901c928bfb7b016484f8c0c81c3629eca3e9f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 16:24:01 -0800 Subject: [PATCH 0808/1635] prepare release --- CHANGES.md | 2 +- Cargo.toml | 2 +- src/client/parser.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b1717ea9..efeaadf0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.14] - 2018-11-x +## [0.7.14] - 2018-11-14 ### Added diff --git a/Cargo.toml b/Cargo.toml index 4abb64e2..41f2e667 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.6" -actix-net = "0.2.1" +actix-net = "0.2.2" askama_escape = "0.1.0" base64 = "0.10" diff --git a/src/client/parser.rs b/src/client/parser.rs index 11252fa5..92a7abe1 100644 --- a/src/client/parser.rs +++ b/src/client/parser.rs @@ -56,7 +56,7 @@ impl HttpResponseParser { return Ok(Async::Ready(msg)); } Async::NotReady => { - if buf.capacity() >= MAX_BUFFER_SIZE { + if buf.len() >= MAX_BUFFER_SIZE { return Err(HttpResponseParserError::Error( ParseError::TooLarge, )); From 6e7560e28781c71318af9fc28b29bd54a50fa83f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 18:57:58 -0800 Subject: [PATCH 0809/1635] SendResponse service sends body as well --- src/service.rs | 109 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 20 deletions(-) diff --git a/src/service.rs b/src/service.rs index c7653032..774efb74 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,13 +1,13 @@ -use std::io; use std::marker::PhantomData; use actix_net::codec::Framed; use actix_net::service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, AsyncSink, Future, Poll, Sink}; -use tokio_io::AsyncWrite; +use tokio_io::{AsyncRead, AsyncWrite}; -use error::ResponseError; +use body::Body; +use error::{Error, ResponseError}; use h1::{Codec, Message}; use response::Response; @@ -113,20 +113,43 @@ pub struct SendResponse(PhantomData<(T,)>); impl Default for SendResponse where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { fn default() -> Self { SendResponse(PhantomData) } } +impl SendResponse +where + T: AsyncRead + AsyncWrite, +{ + pub fn send( + mut framed: Framed, + mut res: Response, + ) -> impl Future, Error = Error> { + // init codec + framed.get_codec_mut().prepare_te(&mut res); + + // extract body from response + let body = res.replace_body(Body::Empty); + + // write response + SendResponseFut { + res: Some(Message::Item(res)), + body: Some(body), + framed: Some(framed), + } + } +} + impl NewService for SendResponse where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { type Request = (Response, Framed); type Response = Framed; - type Error = io::Error; + type Error = Error; type InitError = (); type Service = SendResponse; type Future = FutureResult; @@ -138,20 +161,23 @@ where impl Service for SendResponse where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { type Request = (Response, Framed); type Response = Framed; - type Error = io::Error; + type Error = Error; type Future = SendResponseFut; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } - fn call(&mut self, (res, framed): Self::Request) -> Self::Future { + fn call(&mut self, (mut res, mut framed): Self::Request) -> Self::Future { + framed.get_codec_mut().prepare_te(&mut res); + let body = res.replace_body(Body::Empty); SendResponseFut { res: Some(Message::Item(res)), + body: Some(body), framed: Some(framed), } } @@ -159,29 +185,72 @@ where pub struct SendResponseFut { res: Option>, + body: Option, framed: Option>, } impl Future for SendResponseFut where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { type Item = Framed; - type Error = io::Error; + type Error = Error; fn poll(&mut self) -> Poll { - if let Some(res) = self.res.take() { - match self.framed.as_mut().unwrap().start_send(res)? { - AsyncSink::Ready => (), - AsyncSink::NotReady(res) => { - self.res = Some(res); - return Ok(Async::NotReady); + // send response + if self.res.is_some() { + let framed = self.framed.as_mut().unwrap(); + if !framed.is_write_buf_full() { + if let Some(res) = self.res.take() { + println!("SEND RESP: {:?}", res); + framed.force_send(res)?; } } } - match self.framed.as_mut().unwrap().poll_complete()? { - Async::Ready(_) => Ok(Async::Ready(self.framed.take().unwrap())), - Async::NotReady => Ok(Async::NotReady), + + // send body + if self.res.is_none() && self.body.is_some() { + let framed = self.framed.as_mut().unwrap(); + if !framed.is_write_buf_full() { + let body = self.body.take().unwrap(); + match body { + Body::Empty => (), + Body::Streaming(mut stream) => loop { + match stream.poll()? { + Async::Ready(item) => { + let done = item.is_none(); + framed.force_send(Message::Chunk(item.into()))?; + if !done { + if !framed.is_write_buf_full() { + continue; + } else { + self.body = Some(Body::Streaming(stream)); + break; + } + } + } + Async::NotReady => { + self.body = Some(Body::Streaming(stream)); + break; + } + } + }, + Body::Binary(mut bin) => { + framed.force_send(Message::Chunk(Some(bin.take())))?; + framed.force_send(Message::Chunk(None))?; + } + } + } } + + // flush + match self.framed.as_mut().unwrap().poll_complete()? { + Async::Ready(_) => if self.res.is_some() || self.body.is_some() { + return self.poll(); + }, + Async::NotReady => return Ok(Async::NotReady), + } + + Ok(Async::Ready(self.framed.take().unwrap())) } } From acd42f92d8f507358c5884dd387b48c69e4eeca4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 14 Nov 2018 19:08:52 -0800 Subject: [PATCH 0810/1635] remove debug print --- src/service.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/service.rs b/src/service.rs index 774efb74..3aa5d1e4 100644 --- a/src/service.rs +++ b/src/service.rs @@ -202,7 +202,6 @@ where let framed = self.framed.as_mut().unwrap(); if !framed.is_write_buf_full() { if let Some(res) = self.res.take() { - println!("SEND RESP: {:?}", res); framed.force_send(res)?; } } From 6d9733cdf79ae196015624e13be16515c96d9479 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 15 Nov 2018 11:10:23 -0800 Subject: [PATCH 0811/1635] define generic client Connection trait --- src/client/connection.rs | 69 ++++++++++++++++++++++++++-------------- src/client/connector.rs | 58 ++++++++++++++++++++++----------- src/client/pipeline.rs | 44 ++++++++++++++----------- src/client/pool.rs | 32 +++++++++---------- src/client/request.rs | 7 ++-- 5 files changed, 129 insertions(+), 81 deletions(-) diff --git a/src/client/connection.rs b/src/client/connection.rs index eec64267..363a4ece 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -5,14 +5,23 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::pool::Acquired; +pub trait Connection: AsyncRead + AsyncWrite + 'static { + /// Close connection + fn close(&mut self); + + /// Release connection to the connection pool + fn release(&mut self); +} + +#[doc(hidden)] /// HTTP client connection -pub struct Connection { - io: T, +pub struct IoConnection { + io: Option, created: time::Instant, pool: Option>, } -impl fmt::Debug for Connection +impl fmt::Debug for IoConnection where T: fmt::Debug, { @@ -21,59 +30,73 @@ where } } -impl Connection { +impl IoConnection { pub(crate) fn new(io: T, created: time::Instant, pool: Acquired) -> Self { - Connection { - io, + IoConnection { created, + io: Some(io), pool: Some(pool), } } /// Raw IO stream pub fn get_mut(&mut self) -> &mut T { - &mut self.io + self.io.as_mut().unwrap() } + pub(crate) fn into_inner(self) -> (T, time::Instant) { + (self.io.unwrap(), self.created) + } +} + +impl Connection for IoConnection { /// Close connection - pub fn close(mut self) { + fn close(&mut self) { if let Some(mut pool) = self.pool.take() { - pool.close(self) + if let Some(io) = self.io.take() { + pool.close(IoConnection { + io: Some(io), + created: self.created, + pool: None, + }) + } } } /// Release this connection to the connection pool - pub fn release(mut self) { + fn release(&mut self) { if let Some(mut pool) = self.pool.take() { - pool.release(self) + if let Some(io) = self.io.take() { + pool.release(IoConnection { + io: Some(io), + created: self.created, + pool: None, + }) + } } } - - pub(crate) fn into_inner(self) -> (T, time::Instant) { - (self.io, self.created) - } } -impl io::Read for Connection { +impl io::Read for IoConnection { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.read(buf) + self.io.as_mut().unwrap().read(buf) } } -impl AsyncRead for Connection {} +impl AsyncRead for IoConnection {} -impl io::Write for Connection { +impl io::Write for IoConnection { fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.write(buf) + self.io.as_mut().unwrap().write(buf) } fn flush(&mut self) -> io::Result<()> { - self.io.flush() + self.io.as_mut().unwrap().flush() } } -impl AsyncWrite for Connection { +impl AsyncWrite for IoConnection { fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.shutdown() + self.io.as_mut().unwrap().shutdown() } } diff --git a/src/client/connector.rs b/src/client/connector.rs index 1eae135f..81808521 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -11,7 +11,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use super::connect::Connect; -use super::connection::Connection; +use super::connection::{Connection, IoConnection}; use super::error::ConnectorError; use super::pool::ConnectionPool; @@ -130,7 +130,7 @@ impl Connector { self, ) -> impl Service< Request = Connect, - Response = Connection, + Response = impl Connection, Error = ConnectorError, > + Clone { #[cfg(not(feature = "ssl"))] @@ -234,11 +234,11 @@ mod connect_impl { T: Service, { type Request = Connect; - type Response = Connection; + type Response = IoConnection; type Error = ConnectorError; type Future = Either< as Service>::Future, - FutureResult, ConnectorError>, + FutureResult, ConnectorError>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -324,7 +324,7 @@ mod connect_impl { >, { type Request = Connect; - type Response = IoEither, Connection>; + type Response = IoEither, IoConnection>; type Error = ConnectorError; type Future = Either< FutureResult, @@ -342,13 +342,13 @@ mod connect_impl { if let Err(e) = req.validate() { Either::A(err(e)) } else if req.is_secure() { - Either::B(Either::A(InnerConnectorResponseA { - fut: self.tcp_pool.call(req), + Either::B(Either::B(InnerConnectorResponseB { + fut: self.ssl_pool.call(req), _t: PhantomData, })) } else { - Either::B(Either::B(InnerConnectorResponseB { - fut: self.ssl_pool.call(req), + Either::B(Either::A(InnerConnectorResponseA { + fut: self.tcp_pool.call(req), _t: PhantomData, })) } @@ -370,7 +370,7 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { - type Item = IoEither, Connection>; + type Item = IoEither, IoConnection>; type Error = ConnectorError; fn poll(&mut self) -> Poll { @@ -396,7 +396,7 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { - type Item = IoEither, Connection>; + type Item = IoEither, IoConnection>; type Error = ConnectorError; fn poll(&mut self) -> Poll { @@ -413,10 +413,30 @@ pub(crate) enum IoEither { B(Io2), } +impl Connection for IoEither +where + Io1: Connection, + Io2: Connection, +{ + fn close(&mut self) { + match self { + IoEither::A(ref mut io) => io.close(), + IoEither::B(ref mut io) => io.close(), + } + } + + fn release(&mut self) { + match self { + IoEither::A(ref mut io) => io.release(), + IoEither::B(ref mut io) => io.release(), + } + } +} + impl io::Read for IoEither where - Io1: io::Read, - Io2: io::Read, + Io1: Connection, + Io2: Connection, { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { @@ -428,8 +448,8 @@ where impl AsyncRead for IoEither where - Io1: AsyncRead, - Io2: AsyncRead, + Io1: Connection, + Io2: Connection, { unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { match self { @@ -441,8 +461,8 @@ where impl AsyncWrite for IoEither where - Io1: AsyncWrite, - Io2: AsyncWrite, + Io1: Connection, + Io2: Connection, { fn shutdown(&mut self) -> Poll<(), io::Error> { match self { @@ -468,8 +488,8 @@ where impl io::Write for IoEither where - Io1: io::Write, - Io2: io::Write, + Io1: Connection, + Io2: Connection, { fn flush(&mut self) -> io::Result<()> { match self { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 24ec8366..dc6a644d 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -15,27 +15,32 @@ use body::{BodyType, MessageBody, PayloadStream}; use error::PayloadError; use h1; -pub fn send_request( +pub(crate) fn send_request( head: RequestHead, body: B, connector: &mut T, ) -> impl Future where - T: Service, Error = ConnectorError>, + T: Service, B: MessageBody, - Io: AsyncRead + AsyncWrite + 'static, + I: Connection, { let tp = body.tp(); connector + // connect to the host .call(Connect::new(head.uri.clone())) .from_err() + // create Framed and send reqest .map(|io| Framed::new(io, h1::ClientCodec::default())) .and_then(|framed| framed.send((head, tp).into()).from_err()) + // send request body .and_then(move |framed| match body.tp() { BodyType::None | BodyType::Zero => Either::A(ok(framed)), _ => Either::B(SendBody::new(body, framed)), - }).and_then(|framed| { + }) + // read response and init read body + .and_then(|framed| { framed .into_future() .map_err(|(e, _)| SendRequestError::from(e)) @@ -55,19 +60,20 @@ where }) } -struct SendBody { +/// Future responsible for sending request body to the peer +struct SendBody { body: Option, - framed: Option, h1::ClientCodec>>, + framed: Option>, write_buf: VecDeque>, flushed: bool, } -impl SendBody +impl SendBody where - Io: AsyncRead + AsyncWrite + 'static, + I: AsyncRead + AsyncWrite + 'static, B: MessageBody, { - fn new(body: B, framed: Framed, h1::ClientCodec>) -> Self { + fn new(body: B, framed: Framed) -> Self { SendBody { body: Some(body), framed: Some(framed), @@ -77,12 +83,12 @@ where } } -impl Future for SendBody +impl Future for SendBody where - Io: AsyncRead + AsyncWrite + 'static, + I: Connection, B: MessageBody, { - type Item = Framed, h1::ClientCodec>; + type Item = Framed; type Error = SendRequestError; fn poll(&mut self) -> Poll { @@ -135,7 +141,7 @@ impl Stream for EmptyPayload { } pub(crate) struct Payload { - framed: Option, h1::ClientPayloadCodec>>, + framed: Option>, } impl Payload<()> { @@ -144,15 +150,15 @@ impl Payload<()> { } } -impl Payload { - fn stream(framed: Framed, h1::ClientCodec>) -> PayloadStream { +impl Payload { + fn stream(framed: Framed) -> PayloadStream { Box::new(Payload { framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), }) } } -impl Stream for Payload { +impl Stream for Payload { type Item = Bytes; type Error = PayloadError; @@ -170,11 +176,11 @@ impl Stream for Payload { } } -fn release_connection(framed: Framed, U>) +fn release_connection(framed: Framed) where - T: AsyncRead + AsyncWrite + 'static, + T: Connection, { - let parts = framed.into_parts(); + let mut parts = framed.into_parts(); if parts.read_buf.is_empty() && parts.write_buf.is_empty() { parts.io.release() } else { diff --git a/src/client/pool.rs b/src/client/pool.rs index 25296a6d..44008f34 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -17,7 +17,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::{sleep, Delay}; use super::connect::Connect; -use super::connection::Connection; +use super::connection::IoConnection; use super::error::ConnectorError; #[derive(Hash, Eq, PartialEq, Clone, Debug)] @@ -89,10 +89,10 @@ where T: Service, { type Request = Connect; - type Response = Connection; + type Response = IoConnection; type Error = ConnectorError; type Future = Either< - FutureResult, ConnectorError>, + FutureResult, ConnectorError>, Either, OpenConnection>, >; @@ -107,7 +107,7 @@ where match self.1.as_ref().borrow_mut().acquire(&key) { Acquire::Acquired(io, created) => { // use existing connection - Either::A(ok(Connection::new( + Either::A(ok(IoConnection::new( io, created, Acquired(key, Some(self.1.clone())), @@ -142,7 +142,7 @@ where { key: Key, token: usize, - rx: oneshot::Receiver, ConnectorError>>, + rx: oneshot::Receiver, ConnectorError>>, inner: Option>>>, } @@ -163,7 +163,7 @@ impl Future for WaitForConnection where Io: AsyncRead + AsyncWrite, { - type Item = Connection; + type Item = IoConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { @@ -226,7 +226,7 @@ where F: Future, Io: AsyncRead + AsyncWrite, { - type Item = Connection; + type Item = IoConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { @@ -234,7 +234,7 @@ where Err(err) => Err(err.into()), Ok(Async::Ready((_, io))) => { let _ = self.inner.take(); - Ok(Async::Ready(Connection::new( + Ok(Async::Ready(IoConnection::new( io, Instant::now(), Acquired(self.key.clone(), self.inner.clone()), @@ -251,7 +251,7 @@ where { fut: F, key: Key, - rx: Option, ConnectorError>>>, + rx: Option, ConnectorError>>>, inner: Option>>>, } @@ -262,7 +262,7 @@ where { fn spawn( key: Key, - rx: oneshot::Sender, ConnectorError>>, + rx: oneshot::Sender, ConnectorError>>, inner: Rc>>, fut: F, ) { @@ -308,7 +308,7 @@ where Ok(Async::Ready((_, io))) => { let _ = self.inner.take(); if let Some(rx) = self.rx.take() { - let _ = rx.send(Ok(Connection::new( + let _ = rx.send(Ok(IoConnection::new( io, Instant::now(), Acquired(self.key.clone(), self.inner.clone()), @@ -336,7 +336,7 @@ pub(crate) struct Inner { available: HashMap>>, waiters: Slab<( Connect, - oneshot::Sender, ConnectorError>>, + oneshot::Sender, ConnectorError>>, )>, waiters_queue: IndexSet<(Key, usize)>, task: AtomicTask, @@ -378,7 +378,7 @@ where &mut self, connect: Connect, ) -> ( - oneshot::Receiver, ConnectorError>>, + oneshot::Receiver, ConnectorError>>, usize, ) { let (tx, rx) = oneshot::channel(); @@ -479,7 +479,7 @@ where Acquire::NotAvailable => break, Acquire::Acquired(io, created) => { let (_, tx) = inner.waiters.remove(token); - if let Err(conn) = tx.send(Ok(Connection::new( + if let Err(conn) = tx.send(Ok(IoConnection::new( io, created, Acquired(key.clone(), Some(self.inner.clone())), @@ -546,13 +546,13 @@ impl Acquired where T: AsyncRead + AsyncWrite + 'static, { - pub(crate) fn close(&mut self, conn: Connection) { + pub(crate) fn close(&mut self, conn: IoConnection) { if let Some(inner) = self.1.take() { let (io, _) = conn.into_inner(); inner.as_ref().borrow_mut().release_close(io); } } - pub(crate) fn release(&mut self, conn: Connection) { + pub(crate) fn release(&mut self, conn: IoConnection) { if let Some(inner) = self.1.take() { let (io, created) = conn.into_inner(); inner diff --git a/src/client/request.rs b/src/client/request.rs index 60337413..602abed5 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -7,7 +7,6 @@ use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use tokio_io::{AsyncRead, AsyncWrite}; use urlcrate::Url; use body::{MessageBody, MessageBodyStream}; @@ -176,13 +175,13 @@ where // Send request /// /// This method returns a future that resolves to a ClientResponse - pub fn send( + pub fn send( self, connector: &mut T, ) -> impl Future where - T: Service, Error = ConnectorError>, - Io: AsyncRead + AsyncWrite + 'static, + T: Service, + I: Connection, { pipeline::send_request(self.head, self.body, connector) } From 3b7bc41418ad51a8e2acea2b7dbbc84a68914a29 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 15 Nov 2018 22:34:29 -0800 Subject: [PATCH 0812/1635] use RequestHead for Request --- src/client/mod.rs | 2 +- src/client/pipeline.rs | 2 +- src/client/request.rs | 19 +++---------- src/client/response.rs | 16 +++++------ src/h1/client.rs | 8 +++--- src/h1/codec.rs | 4 +-- src/h1/decoder.rs | 14 +++++----- src/h1/encoder.rs | 3 +-- src/request.rs | 61 ++++++++++++++++++++++++++---------------- src/test.rs | 6 ++--- 10 files changed, 68 insertions(+), 67 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index da0cbc67..76c3f8b8 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -12,5 +12,5 @@ pub use self::connect::Connect; pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; -pub use self::request::{ClientRequest, ClientRequestBuilder, RequestHead}; +pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index dc6a644d..26bd1c62 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -8,12 +8,12 @@ use futures::{Async, Future, Poll, Sink, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use super::error::{ConnectorError, SendRequestError}; -use super::request::RequestHead; use super::response::ClientResponse; use super::{Connect, Connection}; use body::{BodyType, MessageBody, PayloadStream}; use error::PayloadError; use h1; +use request::RequestHead; pub(crate) fn send_request( head: RequestHead, diff --git a/src/client/request.rs b/src/client/request.rs index 602abed5..c4c7f2f6 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -16,6 +16,7 @@ use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; +use request::RequestHead; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; @@ -50,21 +51,9 @@ pub struct ClientRequest { body: B, } -pub struct RequestHead { - pub uri: Uri, - pub method: Method, - pub version: Version, - pub headers: HeaderMap, -} - -impl Default for RequestHead { - fn default() -> RequestHead { - RequestHead { - uri: Uri::default(), - method: Method::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - } +impl RequestHead { + pub fn clear(&mut self) { + self.headers.clear() } } diff --git a/src/client/response.rs b/src/client/response.rs index e8e63e4f..56e13fa4 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -4,13 +4,13 @@ use std::rc::Rc; use bytes::Bytes; use futures::{Async, Poll, Stream}; -use http::{HeaderMap, Method, StatusCode, Version}; +use http::{HeaderMap, StatusCode, Version}; use body::PayloadStream; use error::PayloadError; use extensions::Extensions; use httpmessage::HttpMessage; -use request::{Message, MessageFlags, MessagePool}; +use request::{Message, MessageFlags, MessagePool, RequestHead}; use uri::Url; use super::pipeline::Payload; @@ -25,7 +25,7 @@ impl HttpMessage for ClientResponse { type Stream = PayloadStream; fn headers(&self) -> &HeaderMap { - &self.inner.headers + &self.inner.head.headers } #[inline] @@ -49,11 +49,9 @@ impl ClientResponse { ClientResponse { inner: Rc::new(Message { pool, - method: Method::GET, + head: RequestHead::default(), status: StatusCode::OK, url: Url::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), flags: Cell::new(MessageFlags::empty()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), @@ -75,7 +73,7 @@ impl ClientResponse { /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner().version + self.inner().head.version } /// Get the status from the server. @@ -87,13 +85,13 @@ impl ClientResponse { #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner().headers + &self.inner().head.headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().headers + &mut self.inner_mut().head.headers } /// Checks if a connection should be kept alive. diff --git a/src/h1/client.rs b/src/h1/client.rs index d2ac2034..81a6f568 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -8,7 +8,7 @@ use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder}; use super::encoder::{RequestEncoder, ResponseLength}; use super::{Message, MessageType}; use body::{Binary, Body, BodyType}; -use client::{ClientResponse, RequestHead}; +use client::ClientResponse; use config::ServiceConfig; use error::{ParseError, PayloadError}; use helpers; @@ -16,7 +16,7 @@ use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use request::MessagePool; +use request::{MessagePool, RequestHead}; bitflags! { struct Flags: u8 { @@ -187,8 +187,8 @@ impl Decoder for ClientCodec { if let Some((req, payload)) = self.inner.decoder.decode(src)? { self.inner .flags - .set(Flags::HEAD, req.inner.method == Method::HEAD); - self.inner.version = req.inner.version; + .set(Flags::HEAD, req.inner.head.method == Method::HEAD); + self.inner.version = req.inner.head.version; if self.inner.flags.contains(Flags::KEEPALIVE_ENABLED) { self.inner.flags.set(Flags::KEEPALIVE, req.keep_alive()); } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 44a1b81f..57e84898 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -261,8 +261,8 @@ impl Decoder for Codec { }) } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags - .set(Flags::HEAD, req.inner.method == Method::HEAD); - self.version = req.inner.version; + .set(Flags::HEAD, req.inner.head.method == Method::HEAD); + self.version = req.inner.head.version; if self.flags.contains(Flags::KEEPALIVE_ENABLED) { self.flags.set(Flags::KEEPALIVE, req.keep_alive()); } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index f2a3ee3f..75bd0f7c 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -152,15 +152,15 @@ impl Decoder for RequestDecoder { _ => (), } - inner.headers.append(name, value); + inner.head.headers.append(name, value); } else { return Err(ParseError::Header); } } inner.url = path; - inner.method = method; - inner.version = version; + inner.head.method = method; + inner.head.version = version; } msg }; @@ -172,7 +172,7 @@ impl Decoder for RequestDecoder { } else if let Some(len) = content_length { // Content-Length PayloadType::Payload(PayloadDecoder::length(len)) - } else if has_upgrade || msg.inner.method == Method::CONNECT { + } else if has_upgrade || msg.inner.head.method == Method::CONNECT { // upgrade(websocket) or connect PayloadType::Stream(PayloadDecoder::eof()) } else if src.len() >= MAX_BUFFER_SIZE { @@ -298,14 +298,14 @@ impl Decoder for ResponseDecoder { _ => (), } - inner.headers.append(name, value); + inner.head.headers.append(name, value); } else { return Err(ParseError::Header); } } inner.status = status; - inner.version = version; + inner.head.version = version; } msg }; @@ -318,7 +318,7 @@ impl Decoder for ResponseDecoder { // Content-Length PayloadType::Payload(PayloadDecoder::length(len)) } else if msg.inner.status == StatusCode::SWITCHING_PROTOCOLS - || msg.inner.method == Method::CONNECT + || msg.inner.head.method == Method::CONNECT { // switching protocol or connect PayloadType::Stream(PayloadDecoder::eof()) diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index caad113d..7527eeea 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -9,10 +9,9 @@ use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; use body::{Binary, Body}; -use client::RequestHead; use header::ContentEncoding; use http::Method; -use request::Request; +use request::{Request, RequestHead}; use response::Response; #[derive(Debug)] diff --git a/src/request.rs b/src/request.rs index fb5cb183..edad4d50 100644 --- a/src/request.rs +++ b/src/request.rs @@ -23,12 +23,28 @@ pub struct Request { pub(crate) inner: Rc, } -pub struct Message { - pub version: Version, - pub status: StatusCode, +pub struct RequestHead { + pub uri: Uri, pub method: Method, - pub url: Url, + pub version: Version, pub headers: HeaderMap, +} + +impl Default for RequestHead { + fn default() -> RequestHead { + RequestHead { + uri: Uri::default(), + method: Method::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + } + } +} + +pub struct Message { + pub head: RequestHead, + pub url: Url, + pub status: StatusCode, pub extensions: RefCell, pub payload: RefCell>, pub(crate) pool: &'static MessagePool, @@ -39,7 +55,7 @@ impl Message { #[inline] /// Reset request instance pub fn reset(&mut self) { - self.headers.clear(); + self.head.clear(); self.extensions.borrow_mut().clear(); self.flags.set(MessageFlags::empty()); *self.payload.borrow_mut() = None; @@ -50,7 +66,7 @@ impl HttpMessage for Request { type Stream = Payload; fn headers(&self) -> &HeaderMap { - &self.inner.headers + &self.inner.head.headers } #[inline] @@ -74,11 +90,9 @@ impl Request { Request { inner: Rc::new(Message { pool, - method: Method::GET, - status: StatusCode::OK, url: Url::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), + head: RequestHead::default(), + status: StatusCode::OK, flags: Cell::new(MessageFlags::empty()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), @@ -98,27 +112,28 @@ impl Request { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } - #[inline] - pub fn url(&self) -> &Url { - &self.inner().url - } - - /// Read the Request Uri. + /// Request's uri. #[inline] pub fn uri(&self) -> &Uri { - self.inner().url.uri() + &self.inner().head.uri + } + + /// Mutable reference to the request's uri. + #[inline] + pub fn uri_mut(&mut self) -> &mut Uri { + &mut self.inner_mut().head.uri } /// Read the Request method. #[inline] pub fn method(&self) -> &Method { - &self.inner().method + &self.inner().head.method } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner().version + self.inner().head.version } /// The target path of this Request. @@ -130,13 +145,13 @@ impl Request { #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner().headers + &self.inner().head.headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().headers + &mut self.inner_mut().head.headers } /// Checks if a connection should be kept alive. @@ -159,12 +174,12 @@ impl Request { /// Check if request requires connection upgrade pub fn upgrade(&self) -> bool { - if let Some(conn) = self.inner().headers.get(header::CONNECTION) { + if let Some(conn) = self.inner().head.headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { return s.to_lowercase().contains("upgrade"); } } - self.inner().method == Method::CONNECT + self.inner().head.method == Method::CONNECT } #[doc(hidden)] diff --git a/src/test.rs b/src/test.rs index 71145cee..b0b8dc83 100644 --- a/src/test.rs +++ b/src/test.rs @@ -391,10 +391,10 @@ impl TestRequest { let mut req = Request::new(); { let inner = req.inner_mut(); - inner.method = method; + inner.head.method = method; inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; + inner.head.version = version; + inner.head.headers = headers; *inner.payload.borrow_mut() = payload; } // req.set_cookies(cookies); From 625469f0f421fef4ee7266fd459c6efed31cbdb0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 16 Nov 2018 19:28:07 -0800 Subject: [PATCH 0813/1635] refactor decoder --- src/client/pipeline.rs | 2 +- src/client/request.rs | 2 +- src/client/response.rs | 64 ++--- src/h1/client.rs | 21 +- src/h1/codec.rs | 13 +- src/h1/decoder.rs | 551 ++++++++++++++++++++--------------------- src/h1/encoder.rs | 3 +- src/h1/mod.rs | 1 - src/lib.rs | 1 + src/message.rs | 163 ++++++++++++ src/request.rs | 148 ++--------- src/test.rs | 2 +- src/uri.rs | 17 +- 13 files changed, 493 insertions(+), 495 deletions(-) create mode 100644 src/message.rs diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 26bd1c62..17ba93e7 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -13,7 +13,7 @@ use super::{Connect, Connection}; use body::{BodyType, MessageBody, PayloadStream}; use error::PayloadError; use h1; -use request::RequestHead; +use message::RequestHead; pub(crate) fn send_request( head: RequestHead, diff --git a/src/client/request.rs b/src/client/request.rs index c4c7f2f6..d3d1544c 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -16,7 +16,7 @@ use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use request::RequestHead; +use message::RequestHead; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; diff --git a/src/client/response.rs b/src/client/response.rs index 56e13fa4..797c88df 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,6 +1,5 @@ -use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::cell::RefCell; use std::fmt; -use std::rc::Rc; use bytes::Bytes; use futures::{Async, Poll, Stream}; @@ -8,16 +7,14 @@ use http::{HeaderMap, StatusCode, Version}; use body::PayloadStream; use error::PayloadError; -use extensions::Extensions; use httpmessage::HttpMessage; -use request::{Message, MessageFlags, MessagePool, RequestHead}; -use uri::Url; +use message::{MessageFlags, ResponseHead}; use super::pipeline::Payload; /// Client Response pub struct ClientResponse { - pub(crate) inner: Rc, + pub(crate) head: ResponseHead, pub(crate) payload: RefCell>, } @@ -25,7 +22,7 @@ impl HttpMessage for ClientResponse { type Stream = PayloadStream; fn headers(&self) -> &HeaderMap { - &self.inner.head.headers + &self.head.headers } #[inline] @@ -41,75 +38,50 @@ impl HttpMessage for ClientResponse { impl ClientResponse { /// Create new Request instance pub fn new() -> ClientResponse { - ClientResponse::with_pool(MessagePool::pool()) - } - - /// Create new Request instance with pool - pub(crate) fn with_pool(pool: &'static MessagePool) -> ClientResponse { ClientResponse { - inner: Rc::new(Message { - pool, - head: RequestHead::default(), - status: StatusCode::OK, - url: Url::default(), - flags: Cell::new(MessageFlags::empty()), - payload: RefCell::new(None), - extensions: RefCell::new(Extensions::new()), - }), + head: ResponseHead::default(), payload: RefCell::new(None), } } #[inline] - pub(crate) fn inner(&self) -> &Message { - self.inner.as_ref() + pub(crate) fn head(&self) -> &ResponseHead { + &self.head } #[inline] - pub(crate) fn inner_mut(&mut self) -> &mut Message { - Rc::get_mut(&mut self.inner).expect("Multiple copies exist") + pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { + &mut self.head } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner().head.version + self.head().version.clone().unwrap() } /// Get the status from the server. #[inline] pub fn status(&self) -> StatusCode { - self.inner().status + self.head().status } #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner().head.headers + &self.head().headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().head.headers + &mut self.head_mut().headers } /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { - self.inner().flags.get().contains(MessageFlags::KEEPALIVE) - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.inner().extensions.borrow() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.inner().extensions.borrow_mut() + self.head().flags.contains(MessageFlags::KEEPALIVE) } } @@ -126,14 +98,6 @@ impl Stream for ClientResponse { } } -impl Drop for ClientResponse { - fn drop(&mut self) { - if Rc::strong_count(&self.inner) == 1 { - self.inner.pool.release(self.inner.clone()); - } - } -} - impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; diff --git a/src/h1/client.rs b/src/h1/client.rs index 81a6f568..8d4051d3 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -4,7 +4,7 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, ResponseDecoder}; +use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::{RequestEncoder, ResponseLength}; use super::{Message, MessageType}; use body::{Binary, Body, BodyType}; @@ -16,7 +16,7 @@ use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use request::{MessagePool, RequestHead}; +use message::{MessagePool, RequestHead}; bitflags! { struct Flags: u8 { @@ -42,7 +42,7 @@ pub struct ClientPayloadCodec { struct ClientCodecInner { config: ServiceConfig, - decoder: ResponseDecoder, + decoder: MessageDecoder, payload: Option, version: Version, @@ -63,11 +63,6 @@ impl ClientCodec { /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - ClientCodec::with_pool(MessagePool::pool(), config) - } - - /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool(pool: &'static MessagePool, config: ServiceConfig) -> Self { let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE_ENABLED } else { @@ -76,7 +71,7 @@ impl ClientCodec { ClientCodec { inner: ClientCodecInner { config, - decoder: ResponseDecoder::with_pool(pool), + decoder: MessageDecoder::default(), payload: None, version: Version::HTTP_11, @@ -185,10 +180,10 @@ impl Decoder for ClientCodec { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - self.inner - .flags - .set(Flags::HEAD, req.inner.head.method == Method::HEAD); - self.inner.version = req.inner.head.version; + // self.inner + // .flags + // .set(Flags::HEAD, req.head.method == Method::HEAD); + // self.inner.version = req.head.version; if self.inner.flags.contains(Flags::KEEPALIVE_ENABLED) { self.inner.flags.set(Flags::KEEPALIVE, req.keep_alive()); } diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 57e84898..c1c6091d 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -5,7 +5,7 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{PayloadDecoder, PayloadItem, PayloadType, RequestDecoder}; +use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::{ResponseEncoder, ResponseLength}; use super::{Message, MessageType}; use body::{Binary, Body}; @@ -14,7 +14,7 @@ use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; -use request::{MessagePool, Request}; +use request::Request; use response::Response; bitflags! { @@ -32,7 +32,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// HTTP/1 Codec pub struct Codec { config: ServiceConfig, - decoder: RequestDecoder, + decoder: MessageDecoder, payload: Option, version: Version, @@ -59,11 +59,6 @@ impl Codec { /// /// `keepalive_enabled` how response `connection` header get generated. pub fn new(config: ServiceConfig) -> Self { - Codec::with_pool(MessagePool::pool(), config) - } - - /// Create HTTP/1 codec with request's pool - pub(crate) fn with_pool(pool: &'static MessagePool, config: ServiceConfig) -> Self { let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE_ENABLED } else { @@ -71,7 +66,7 @@ impl Codec { }; Codec { config, - decoder: RequestDecoder::with_pool(pool), + decoder: MessageDecoder::default(), payload: None, version: Version::HTTP_11, diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 75bd0f7c..fe2aa707 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -1,3 +1,4 @@ +use std::marker::PhantomData; use std::{io, mem}; use bytes::{Bytes, BytesMut}; @@ -8,327 +9,305 @@ use tokio_codec::Decoder; use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; -use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; -use request::{MessageFlags, MessagePool, Request}; -use uri::Url; +use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; +use message::MessageFlags; +use request::Request; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; -/// Client request decoder -pub struct RequestDecoder(&'static MessagePool); - -/// Server response decoder -pub struct ResponseDecoder(&'static MessagePool); +/// Incoming messagd decoder +pub(crate) struct MessageDecoder(PhantomData); /// Incoming request type -pub enum PayloadType { +pub(crate) enum PayloadType { None, Payload(PayloadDecoder), Stream(PayloadDecoder), } -impl RequestDecoder { - pub(crate) fn with_pool(pool: &'static MessagePool) -> RequestDecoder { - RequestDecoder(pool) +impl Default for MessageDecoder { + fn default() -> Self { + MessageDecoder(PhantomData) } } -impl Default for RequestDecoder { - fn default() -> RequestDecoder { - RequestDecoder::with_pool(MessagePool::pool()) - } -} - -impl Decoder for RequestDecoder { - type Item = (Request, PayloadType); +impl Decoder for MessageDecoder { + type Item = (T, PayloadType); type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - // Parse http message + T::decode(src) + } +} + +pub(crate) enum PayloadLength { + None, + Chunked, + Upgrade, + Length(u64), +} + +pub(crate) trait MessageTypeDecoder: Sized { + fn keep_alive(&mut self); + + fn headers_mut(&mut self) -> &mut HeaderMap; + + fn decode(src: &mut BytesMut) -> Result, ParseError>; + + fn process_headers( + &mut self, + slice: &Bytes, + version: Version, + raw_headers: &[HeaderIndex], + ) -> Result { + let mut ka = version != Version::HTTP_10; let mut has_upgrade = false; let mut chunked = false; let mut content_length = None; - let msg = { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = + { + let headers = self.headers_mut(); + + for idx in raw_headers.iter() { + if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) + { + // Unsafe: httparse check header value for valid utf-8 + let value = unsafe { + HeaderValue::from_shared_unchecked( + slice.slice(idx.value.0, idx.value.1), + ) + }; + match name { + header::CONTENT_LENGTH => { + if let Ok(s) = value.to_str() { + if let Ok(len) = s.parse::() { + content_length = Some(len); + } else { + debug!("illegal Content-Length: {:?}", s); + return Err(ParseError::Header); + } + } else { + debug!("illegal Content-Length: {:?}", value); + return Err(ParseError::Header); + } + } + // transfer-encoding + header::TRANSFER_ENCODING => { + if let Ok(s) = value.to_str() { + chunked = s.to_lowercase().contains("chunked"); + } else { + return Err(ParseError::Header); + } + } + // connection keep-alive state + header::CONNECTION => { + ka = if let Ok(conn) = value.to_str() { + if version == Version::HTTP_10 + && conn.contains("keep-alive") + { + true + } else { + version == Version::HTTP_11 && !(conn + .contains("close") + || conn.contains("upgrade")) + } + } else { + false + } + } + header::UPGRADE => { + has_upgrade = true; + // check content-length, some clients (dart) + // sends "content-length: 0" with websocket upgrade + if let Ok(val) = value.to_str() { + if val == "websocket" { + content_length = None; + } + } + } + _ => (), + } + + headers.append(name, value); + } else { + return Err(ParseError::Header); + } + } + } + + if ka { + self.keep_alive(); + } + + if chunked { + Ok(PayloadLength::Chunked) + } else if let Some(len) = content_length { + Ok(PayloadLength::Length(len)) + } else if has_upgrade { + Ok(PayloadLength::Upgrade) + } else { + Ok(PayloadLength::None) + } + } +} + +impl MessageTypeDecoder for Request { + fn keep_alive(&mut self) { + self.inner_mut().flags.set(MessageFlags::KEEPALIVE); + } + + fn headers_mut(&mut self) -> &mut HeaderMap { + self.headers_mut() + } + + fn decode(src: &mut BytesMut) -> Result, ParseError> { + // Unsafe: we read only this data only after httparse parses headers into. + // performance bump for pipeline benchmarks. + let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; + + let (len, method, uri, version, headers_len) = { + let mut parsed: [httparse::Header; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, method, path, version, headers_len) = { - let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let mut req = httparse::Request::new(&mut parsed); - match req.parse(src)? { - httparse::Status::Complete(len) => { - let method = Method::from_bytes(req.method.unwrap().as_bytes()) - .map_err(|_| ParseError::Method)?; - let path = Url::new(Uri::try_from(req.path.unwrap())?); - let version = if req.version.unwrap() == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - HeaderIndex::record(src, req.headers, &mut headers); - - (len, method, path, version, req.headers.len()) - } - httparse::Status::Partial => return Ok(None), - } - }; - - let slice = src.split_to(len).freeze(); - - // convert headers - let mut msg = MessagePool::get_request(self.0); - { - let inner = msg.inner_mut(); - inner - .flags - .get_mut() - .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); - - for idx in headers[..headers_len].iter() { - if let Ok(name) = - HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) - { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { - if let Ok(len) = s.parse::() { - content_length = Some(len); - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } - // transfer-encoding - header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str() { - chunked = s.to_lowercase().contains("chunked"); - } else { - return Err(ParseError::Header); - } - } - // connection keep-alive state - header::CONNECTION => { - let ka = if let Ok(conn) = value.to_str() { - if version == Version::HTTP_10 - && conn.contains("keep-alive") - { - true - } else { - version == Version::HTTP_11 && !(conn - .contains("close") - || conn.contains("upgrade")) - } - } else { - false - }; - inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); - } - header::UPGRADE => { - has_upgrade = true; - // check content-length, some clients (dart) - // sends "content-length: 0" with websocket upgrade - if let Ok(val) = value.to_str() { - if val == "websocket" { - content_length = None; - } - } - } - _ => (), - } - - inner.head.headers.append(name, value); + let mut req = httparse::Request::new(&mut parsed); + match req.parse(src)? { + httparse::Status::Complete(len) => { + let method = Method::from_bytes(req.method.unwrap().as_bytes()) + .map_err(|_| ParseError::Method)?; + let uri = Uri::try_from(req.path.unwrap())?; + let version = if req.version.unwrap() == 1 { + Version::HTTP_11 } else { - return Err(ParseError::Header); - } - } + Version::HTTP_10 + }; + HeaderIndex::record(src, req.headers, &mut headers); - inner.url = path; - inner.head.method = method; - inner.head.version = version; + (len, method, uri, version, req.headers.len()) + } + httparse::Status::Partial => return Ok(None), } - msg }; + // convert headers + let mut msg = Request::new(); + + let len = msg.process_headers( + &src.split_to(len).freeze(), + version, + &headers[..headers_len], + )?; + // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = if chunked { - // Chunked encoding - PayloadType::Payload(PayloadDecoder::chunked()) - } else if let Some(len) = content_length { - // Content-Length - PayloadType::Payload(PayloadDecoder::length(len)) - } else if has_upgrade || msg.inner.head.method == Method::CONNECT { - // upgrade(websocket) or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None + let decoder = match len { + PayloadLength::Chunked => { + // Chunked encoding + PayloadType::Payload(PayloadDecoder::chunked()) + } + PayloadLength::Length(len) => { + // Content-Length + PayloadType::Payload(PayloadDecoder::length(len)) + } + PayloadLength::Upgrade => { + // upgrade(websocket) or connect + PayloadType::Stream(PayloadDecoder::eof()) + } + PayloadLength::None => { + if method == Method::CONNECT { + // upgrade(websocket) or connect + PayloadType::Stream(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); + } else { + PayloadType::None + } + } }; + { + let inner = msg.inner_mut(); + inner.url.update(&uri); + inner.head.uri = uri; + inner.head.method = method; + inner.head.version = version; + } + Ok(Some((msg, decoder))) } } -impl ResponseDecoder { - pub(crate) fn with_pool(pool: &'static MessagePool) -> ResponseDecoder { - ResponseDecoder(pool) +impl MessageTypeDecoder for ClientResponse { + fn keep_alive(&mut self) { + self.head.flags.insert(MessageFlags::KEEPALIVE); } -} -impl Default for ResponseDecoder { - fn default() -> ResponseDecoder { - ResponseDecoder::with_pool(MessagePool::pool()) + fn headers_mut(&mut self) -> &mut HeaderMap { + self.headers_mut() } -} -impl Decoder for ResponseDecoder { - type Item = (ClientResponse, PayloadType); - type Error = ParseError; + fn decode(src: &mut BytesMut) -> Result, ParseError> { + // Unsafe: we read only this data only after httparse parses headers into. + // performance bump for pipeline benchmarks. + let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - // Parse http message - let mut chunked = false; - let mut content_length = None; - - let msg = { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = + let (len, version, status, headers_len) = { + let mut parsed: [httparse::Header; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, version, status, headers_len) = { - let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let mut res = httparse::Response::new(&mut parsed); - match res.parse(src)? { - httparse::Status::Complete(len) => { - let version = if res.version.unwrap() == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - let status = StatusCode::from_u16(res.code.unwrap()) - .map_err(|_| ParseError::Status)?; - HeaderIndex::record(src, res.headers, &mut headers); - - (len, version, status, res.headers.len()) - } - httparse::Status::Partial => return Ok(None), - } - }; - - let slice = src.split_to(len).freeze(); - - // convert headers - let mut msg = MessagePool::get_response(self.0); - { - let inner = msg.inner_mut(); - inner - .flags - .get_mut() - .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); - - for idx in headers[..headers_len].iter() { - if let Ok(name) = - HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) - { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { - if let Ok(len) = s.parse::() { - content_length = Some(len); - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } - // transfer-encoding - header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str() { - chunked = s.to_lowercase().contains("chunked"); - } else { - return Err(ParseError::Header); - } - } - // connection keep-alive state - header::CONNECTION => { - let ka = if let Ok(conn) = value.to_str() { - if version == Version::HTTP_10 - && conn.contains("keep-alive") - { - true - } else { - version == Version::HTTP_11 && !(conn - .contains("close") - || conn.contains("upgrade")) - } - } else { - false - }; - inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); - } - _ => (), - } - - inner.head.headers.append(name, value); + let mut res = httparse::Response::new(&mut parsed); + match res.parse(src)? { + httparse::Status::Complete(len) => { + let version = if res.version.unwrap() == 1 { + Version::HTTP_11 } else { - return Err(ParseError::Header); - } - } + Version::HTTP_10 + }; + let status = StatusCode::from_u16(res.code.unwrap()) + .map_err(|_| ParseError::Status)?; + HeaderIndex::record(src, res.headers, &mut headers); - inner.status = status; - inner.head.version = version; + (len, version, status, res.headers.len()) + } + httparse::Status::Partial => return Ok(None), } - msg }; + let mut msg = ClientResponse::new(); + + // convert headers + let len = msg.process_headers( + &src.split_to(len).freeze(), + version, + &headers[..headers_len], + )?; + // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = if chunked { - // Chunked encoding - PayloadType::Payload(PayloadDecoder::chunked()) - } else if let Some(len) = content_length { - // Content-Length - PayloadType::Payload(PayloadDecoder::length(len)) - } else if msg.inner.status == StatusCode::SWITCHING_PROTOCOLS - || msg.inner.head.method == Method::CONNECT - { - // switching protocol or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None + let decoder = match len { + PayloadLength::Chunked => { + // Chunked encoding + PayloadType::Payload(PayloadDecoder::chunked()) + } + PayloadLength::Length(len) => { + // Content-Length + PayloadType::Payload(PayloadDecoder::length(len)) + } + _ => { + if status == StatusCode::SWITCHING_PROTOCOLS { + // switching protocol or connect + PayloadType::Stream(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); + } else { + PayloadType::None + } + } }; + msg.head.status = status; + msg.head.version = Some(version); + Ok(Some((msg, decoder))) } } @@ -690,7 +669,7 @@ mod tests { macro_rules! parse_ready { ($e:expr) => {{ - match RequestDecoder::default().decode($e) { + match MessageDecoder::::default().decode($e) { Ok(Some((msg, _))) => msg, Ok(_) => unreachable!("Eof during parsing http request"), Err(err) => unreachable!("Error during parsing http request: {:?}", err), @@ -700,7 +679,7 @@ mod tests { macro_rules! expect_parse_err { ($e:expr) => {{ - match RequestDecoder::default().decode($e) { + match MessageDecoder::::default().decode($e) { Err(err) => match err { ParseError::Io(_) => unreachable!("Parse error expected"), _ => (), @@ -779,7 +758,7 @@ mod tests { fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); match reader.decode(&mut buf) { Ok(Some((req, _))) => { assert_eq!(req.version(), Version::HTTP_11); @@ -794,7 +773,7 @@ mod tests { fn test_parse_partial() { let mut buf = BytesMut::from("PUT /test HTTP/1"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b".1\r\n\r\n"); @@ -808,7 +787,7 @@ mod tests { fn test_parse_post() { let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); assert_eq!(req.version(), Version::HTTP_10); assert_eq!(*req.method(), Method::POST); @@ -820,7 +799,7 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert_eq!(req.version(), Version::HTTP_11); @@ -837,7 +816,7 @@ mod tests { let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert_eq!(req.version(), Version::HTTP_11); @@ -852,7 +831,7 @@ mod tests { #[test] fn test_parse_partial_eof() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); assert!(reader.decode(&mut buf).unwrap().is_none()); buf.extend(b"\r\n"); @@ -866,7 +845,7 @@ mod tests { fn test_headers_split_field() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); assert!{ reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t"); @@ -890,7 +869,7 @@ mod tests { Set-Cookie: c1=cookie1\r\n\ Set-Cookie: c2=cookie2\r\n\r\n", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); let val: Vec<_> = req @@ -1090,7 +1069,7 @@ mod tests { upgrade: websocket\r\n\r\n\ some raw data", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); assert!(!req.keep_alive()); assert!(req.upgrade()); @@ -1139,7 +1118,7 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); @@ -1162,7 +1141,7 @@ mod tests { "GET /test HTTP/1.1\r\n\ transfer-encoding: chunked\r\n\r\n", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); @@ -1193,7 +1172,7 @@ mod tests { transfer-encoding: chunked\r\n\r\n", ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert!(req.chunked().unwrap()); @@ -1238,7 +1217,7 @@ mod tests { transfer-encoding: chunked\r\n\r\n"[..], ); - let mut reader = RequestDecoder::default(); + let mut reader = MessageDecoder::::default(); let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); let mut pl = pl.unwrap(); assert!(msg.chunked().unwrap()); diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 7527eeea..de45351d 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -11,7 +11,8 @@ use http::{StatusCode, Version}; use body::{Binary, Body}; use header::ContentEncoding; use http::Method; -use request::{Request, RequestHead}; +use message::RequestHead; +use request::Request; use response::Response; #[derive(Debug)] diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 81838700..395d6619 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -11,7 +11,6 @@ mod service; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; -pub use self::decoder::{PayloadDecoder, RequestDecoder}; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; diff --git a/src/lib.rs b/src/lib.rs index 32369d16..f64876e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,7 @@ mod header; mod httpcodes; mod httpmessage; mod json; +mod message; mod payload; mod request; mod response; diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 00000000..b38c2f80 --- /dev/null +++ b/src/message.rs @@ -0,0 +1,163 @@ +use std::cell::{Cell, RefCell}; +use std::collections::VecDeque; +use std::rc::Rc; + +use http::{HeaderMap, Method, StatusCode, Uri, Version}; + +use extensions::Extensions; +use payload::Payload; +use uri::Url; + +#[doc(hidden)] +pub trait Head: Default + 'static { + fn clear(&mut self); + + fn pool() -> &'static MessagePool; +} + +bitflags! { + pub(crate) struct MessageFlags: u8 { + const KEEPALIVE = 0b0000_0001; + } +} + +pub struct RequestHead { + pub uri: Uri, + pub method: Method, + pub version: Version, + pub headers: HeaderMap, + pub(crate) flags: MessageFlags, +} + +impl Default for RequestHead { + fn default() -> RequestHead { + RequestHead { + uri: Uri::default(), + method: Method::default(), + version: Version::HTTP_11, + headers: HeaderMap::with_capacity(16), + flags: MessageFlags::empty(), + } + } +} + +impl Head for RequestHead { + fn clear(&mut self) { + self.headers.clear(); + self.flags = MessageFlags::empty(); + } + + fn pool() -> &'static MessagePool { + REQUEST_POOL.with(|p| *p) + } +} + +pub struct ResponseHead { + pub version: Option, + pub status: StatusCode, + pub headers: HeaderMap, + pub reason: Option<&'static str>, + pub(crate) flags: MessageFlags, +} + +impl Default for ResponseHead { + fn default() -> ResponseHead { + ResponseHead { + version: None, + status: StatusCode::OK, + headers: HeaderMap::with_capacity(16), + reason: None, + flags: MessageFlags::empty(), + } + } +} + +impl Head for ResponseHead { + fn clear(&mut self) { + self.headers.clear(); + self.flags = MessageFlags::empty(); + } + + fn pool() -> &'static MessagePool { + RESPONSE_POOL.with(|p| *p) + } +} + +pub struct Message { + pub head: T, + pub url: Url, + pub status: StatusCode, + pub extensions: RefCell, + pub payload: RefCell>, + pub(crate) pool: &'static MessagePool, + pub(crate) flags: Cell, +} + +impl Message { + #[inline] + /// Reset request instance + pub fn reset(&mut self) { + self.head.clear(); + self.extensions.borrow_mut().clear(); + self.flags.set(MessageFlags::empty()); + *self.payload.borrow_mut() = None; + } +} + +impl Default for Message { + fn default() -> Self { + Message { + pool: T::pool(), + url: Url::default(), + head: T::default(), + status: StatusCode::OK, + flags: Cell::new(MessageFlags::empty()), + payload: RefCell::new(None), + extensions: RefCell::new(Extensions::new()), + } + } +} + +#[doc(hidden)] +/// Request's objects pool +pub struct MessagePool(RefCell>>>); + +thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); +thread_local!(static RESPONSE_POOL: &'static MessagePool = MessagePool::::create()); + +impl MessagePool { + /// Get default request's pool + pub fn pool() -> &'static MessagePool { + REQUEST_POOL.with(|p| *p) + } + + /// Get Request object + #[inline] + pub fn get_message() -> Rc> { + REQUEST_POOL.with(|pool| { + if let Some(mut msg) = pool.0.borrow_mut().pop_front() { + if let Some(r) = Rc::get_mut(&mut msg) { + r.reset(); + } + return msg; + } + Rc::new(Message::default()) + }) + } +} + +impl MessagePool { + fn create() -> &'static MessagePool { + let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); + Box::leak(Box::new(pool)) + } + + #[inline] + /// Release request instance + pub(crate) fn release(&self, msg: Rc>) { + let v = &mut self.0.borrow_mut(); + if v.len() < 128 { + v.push_front(msg); + } + } +} diff --git a/src/request.rs b/src/request.rs index edad4d50..1e191047 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,65 +1,18 @@ -use std::cell::{Cell, Ref, RefCell, RefMut}; -use std::collections::VecDeque; +use std::cell::{Ref, RefMut}; use std::fmt; use std::rc::Rc; -use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; +use http::{header, HeaderMap, Method, Uri, Version}; -use client::ClientResponse; use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; -use uri::Url; -bitflags! { - pub(crate) struct MessageFlags: u8 { - const KEEPALIVE = 0b0000_0001; - const CONN_INFO = 0b0000_0010; - } -} +use message::{Message, MessageFlags, MessagePool, RequestHead}; /// Request pub struct Request { - pub(crate) inner: Rc, -} - -pub struct RequestHead { - pub uri: Uri, - pub method: Method, - pub version: Version, - pub headers: HeaderMap, -} - -impl Default for RequestHead { - fn default() -> RequestHead { - RequestHead { - uri: Uri::default(), - method: Method::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - } - } -} - -pub struct Message { - pub head: RequestHead, - pub url: Url, - pub status: StatusCode, - pub extensions: RefCell, - pub payload: RefCell>, - pub(crate) pool: &'static MessagePool, - pub(crate) flags: Cell, -} - -impl Message { - #[inline] - /// Reset request instance - pub fn reset(&mut self) { - self.head.clear(); - self.extensions.borrow_mut().clear(); - self.flags.set(MessageFlags::empty()); - *self.payload.borrow_mut() = None; - } + pub(crate) inner: Rc>, } impl HttpMessage for Request { @@ -82,33 +35,35 @@ impl HttpMessage for Request { impl Request { /// Create new Request instance pub fn new() -> Request { - Request::with_pool(MessagePool::pool()) - } - - /// Create new Request instance with pool - pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { Request { - inner: Rc::new(Message { - pool, - url: Url::default(), - head: RequestHead::default(), - status: StatusCode::OK, - flags: Cell::new(MessageFlags::empty()), - payload: RefCell::new(None), - extensions: RefCell::new(Extensions::new()), - }), + inner: MessagePool::get_message(), } } + // /// Create new Request instance with pool + // pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { + // Request { + // inner: Rc::new(Message { + // pool, + // url: Url::default(), + // head: RequestHead::default(), + // status: StatusCode::OK, + // flags: Cell::new(MessageFlags::empty()), + // payload: RefCell::new(None), + // extensions: RefCell::new(Extensions::new()), + // }), + // } + // } + #[inline] #[doc(hidden)] - pub fn inner(&self) -> &Message { + pub fn inner(&self) -> &Message { self.inner.as_ref() } #[inline] #[doc(hidden)] - pub fn inner_mut(&mut self) -> &mut Message { + pub fn inner_mut(&mut self) -> &mut Message { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } @@ -139,7 +94,11 @@ impl Request { /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.inner().url.path() + if let Some(path) = self.inner().url.path() { + path + } else { + self.inner().head.uri.path() + } } #[inline] @@ -219,56 +178,3 @@ impl fmt::Debug for Request { Ok(()) } } - -/// Request's objects pool -pub(crate) struct MessagePool(RefCell>>); - -thread_local!(static POOL: &'static MessagePool = MessagePool::create()); - -impl MessagePool { - fn create() -> &'static MessagePool { - let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); - Box::leak(Box::new(pool)) - } - - /// Get default request's pool - pub fn pool() -> &'static MessagePool { - POOL.with(|p| *p) - } - - /// Get Request object - #[inline] - pub fn get_request(pool: &'static MessagePool) -> Request { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.reset(); - } - return Request { inner: msg }; - } - Request::with_pool(pool) - } - - /// Get Client Response object - #[inline] - pub fn get_response(pool: &'static MessagePool) -> ClientResponse { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.reset(); - } - return ClientResponse { - inner: msg, - payload: RefCell::new(None), - }; - } - ClientResponse::with_pool(pool) - } - - #[inline] - /// Release request instance - pub(crate) fn release(&self, msg: Rc) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - v.push_front(msg); - } - } -} diff --git a/src/test.rs b/src/test.rs index b0b8dc83..43974934 100644 --- a/src/test.rs +++ b/src/test.rs @@ -392,7 +392,7 @@ impl TestRequest { { let inner = req.inner_mut(); inner.head.method = method; - inner.url = InnerUrl::new(uri); + inner.url = InnerUrl::new(&uri); inner.head.version = version; inner.head.headers = headers; *inner.payload.borrow_mut() = payload; diff --git a/src/uri.rs b/src/uri.rs index 6edd220c..89f6d3b1 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -37,27 +37,22 @@ lazy_static! { #[derive(Default, Clone, Debug)] pub struct Url { - uri: Uri, path: Option>, } impl Url { - pub fn new(uri: Uri) -> Url { + pub fn new(uri: &Uri) -> Url { let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); - Url { uri, path } + Url { path } } - pub fn uri(&self) -> &Uri { - &self.uri + pub(crate) fn update(&mut self, uri: &Uri) { + self.path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); } - pub fn path(&self) -> &str { - if let Some(ref s) = self.path { - s - } else { - self.uri.path() - } + pub fn path(&self) -> Option<&str> { + self.path.as_ref().map(|s| s.as_str()) } } From aa20e2670d44b893ae47ad01c61fc1fd3b65dcce Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 16 Nov 2018 21:09:33 -0800 Subject: [PATCH 0814/1635] refactor h1 dispatcher --- src/h1/client.rs | 69 +++++++-- src/h1/decoder.rs | 90 +++++------- src/h1/dispatcher.rs | 338 ++++++++++++++++++++++--------------------- 3 files changed, 267 insertions(+), 230 deletions(-) diff --git a/src/h1/client.rs b/src/h1/client.rs index 8d4051d3..f871cb33 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -125,6 +125,15 @@ impl ClientPayloadCodec { } } +fn prn_version(ver: Version) -> &'static str { + match ver { + Version::HTTP_09 => "HTTP/0.9", + Version::HTTP_10 => "HTTP/1.0", + Version::HTTP_11 => "HTTP/1.1", + Version::HTTP_2 => "HTTP/2.0", + } +} + impl ClientCodecInner { fn encode_response( &mut self, @@ -135,33 +144,63 @@ impl ClientCodecInner { // render message { // status line - writeln!( + write!( Writer(buffer), - "{} {} {:?}\r", + "{} {} {}", msg.method, msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), - msg.version + prn_version(msg.version) ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; // write headers buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE); - 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"); - // Connection upgrade - if key == UPGRADE { - self.flags.insert(Flags::UPGRADE); + // content length + let mut len_is_set = true; + match btype { + BodyType::Sized(len) => { + buffer.extend_from_slice(b"\r\ncontent-length: "); + write!(buffer.writer(), "{}", len)?; + buffer.extend_from_slice(b"\r\n"); } + BodyType::Unsized => { + buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } + BodyType::Zero => { + len_is_set = false; + buffer.extend_from_slice(b"\r\n") + } + BodyType::None => buffer.extend_from_slice(b"\r\n"), + } + + let mut has_date = false; + + for (key, value) in &msg.headers { + match *key { + TRANSFER_ENCODING => continue, + CONTENT_LENGTH => match btype { + BodyType::None => (), + BodyType::Zero => len_is_set = true, + _ => continue, + }, + DATE => has_date = true, + UPGRADE => self.flags.insert(Flags::UPGRADE), + _ => (), + } + + buffer.put_slice(key.as_ref()); + buffer.put_slice(b": "); + buffer.put_slice(value.as_ref()); + buffer.put_slice(b"\r\n"); + } + + // set content length + if !len_is_set { + buffer.extend_from_slice(b"content-length: 0\r\n") } // set date header - if !msg.headers.contains_key(DATE) { + if !has_date { self.config.set_date(buffer); } else { buffer.extend_from_slice(b"\r\n"); diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index fe2aa707..61cab7ad 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -42,10 +42,9 @@ impl Decoder for MessageDecoder { } pub(crate) enum PayloadLength { - None, - Chunked, + Payload(PayloadType), Upgrade, - Length(u64), + None, } pub(crate) trait MessageTypeDecoder: Sized { @@ -55,7 +54,7 @@ pub(crate) trait MessageTypeDecoder: Sized { fn decode(src: &mut BytesMut) -> Result, ParseError>; - fn process_headers( + fn set_headers( &mut self, slice: &Bytes, version: Version, @@ -140,10 +139,17 @@ pub(crate) trait MessageTypeDecoder: Sized { self.keep_alive(); } + // https://tools.ietf.org/html/rfc7230#section-3.3.3 if chunked { - Ok(PayloadLength::Chunked) + // Chunked encoding + Ok(PayloadLength::Payload(PayloadType::Payload( + PayloadDecoder::chunked(), + ))) } else if let Some(len) = content_length { - Ok(PayloadLength::Length(len)) + // Content-Length + Ok(PayloadLength::Payload(PayloadType::Payload( + PayloadDecoder::length(len), + ))) } else if has_upgrade { Ok(PayloadLength::Upgrade) } else { @@ -166,7 +172,7 @@ impl MessageTypeDecoder for Request { // performance bump for pipeline benchmarks. let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, method, uri, version, headers_len) = { + let (len, method, uri, ver, h_len) = { let mut parsed: [httparse::Header; MAX_HEADERS] = unsafe { mem::uninitialized() }; @@ -189,35 +195,24 @@ impl MessageTypeDecoder for Request { } }; - // convert headers let mut msg = Request::new(); - let len = msg.process_headers( - &src.split_to(len).freeze(), - version, - &headers[..headers_len], - )?; + // convert headers + let len = + msg.set_headers(&src.split_to(len).freeze(), ver, &headers[..h_len])?; - // https://tools.ietf.org/html/rfc7230#section-3.3.3 + // payload decoder let decoder = match len { - PayloadLength::Chunked => { - // Chunked encoding - PayloadType::Payload(PayloadDecoder::chunked()) - } - PayloadLength::Length(len) => { - // Content-Length - PayloadType::Payload(PayloadDecoder::length(len)) - } + PayloadLength::Payload(pl) => pl, PayloadLength::Upgrade => { - // upgrade(websocket) or connect + // upgrade(websocket) PayloadType::Stream(PayloadDecoder::eof()) } PayloadLength::None => { if method == Method::CONNECT { - // upgrade(websocket) or connect PayloadType::Stream(PayloadDecoder::eof()) } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); } else { PayloadType::None @@ -230,7 +225,7 @@ impl MessageTypeDecoder for Request { inner.url.update(&uri); inner.head.uri = uri; inner.head.method = method; - inner.head.version = version; + inner.head.version = ver; } Ok(Some((msg, decoder))) @@ -251,7 +246,7 @@ impl MessageTypeDecoder for ClientResponse { // performance bump for pipeline benchmarks. let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, version, status, headers_len) = { + let (len, ver, status, h_len) = { let mut parsed: [httparse::Header; MAX_HEADERS] = unsafe { mem::uninitialized() }; @@ -276,37 +271,26 @@ impl MessageTypeDecoder for ClientResponse { let mut msg = ClientResponse::new(); // convert headers - let len = msg.process_headers( - &src.split_to(len).freeze(), - version, - &headers[..headers_len], - )?; + let len = + msg.set_headers(&src.split_to(len).freeze(), ver, &headers[..h_len])?; - // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = match len { - PayloadLength::Chunked => { - // Chunked encoding - PayloadType::Payload(PayloadDecoder::chunked()) - } - PayloadLength::Length(len) => { - // Content-Length - PayloadType::Payload(PayloadDecoder::length(len)) - } - _ => { - if status == StatusCode::SWITCHING_PROTOCOLS { - // switching protocol or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None - } + // message payload + let decoder = if let PayloadLength::Payload(pl) = len { + pl + } else { + if status == StatusCode::SWITCHING_PROTOCOLS { + // switching protocol or connect + PayloadType::Stream(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); + } else { + PayloadType::None } }; msg.head.status = status; - msg.head.version = Some(version); + msg.head.version = Some(ver); Ok(Some((msg, decoder))) } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 470d3df9..4a0bce72 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -1,11 +1,12 @@ use std::collections::VecDeque; use std::fmt::Debug; +use std::mem; use std::time::Instant; use actix_net::codec::Framed; use actix_net::service::Service; -use futures::{Async, AsyncSink, Future, Poll, Sink, Stream}; +use futures::{Async, Future, Poll, Sink, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; @@ -37,12 +38,19 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher +where + S::Error: Debug, +{ + inner: Option>, +} + +struct InnerDispatcher where S::Error: Debug, { service: S, flags: Flags, - framed: Option>, + framed: Framed, error: Option>, config: ServiceConfig, @@ -63,7 +71,6 @@ enum DispatcherMessage { enum State { None, ServiceCall(S::Future), - SendResponse(Option<(Message, Body)>), SendPayload(BodyStream), } @@ -113,20 +120,29 @@ where }; Dispatcher { - payload: None, - state: State::None, - error: None, - messages: VecDeque::new(), - framed: Some(framed), - unhandled: None, - service, - flags, - config, - ka_expire, - ka_timer, + inner: Some(InnerDispatcher { + framed, + payload: None, + state: State::None, + error: None, + messages: VecDeque::new(), + unhandled: None, + service, + flags, + config, + ka_expire, + ka_timer, + }), } } +} +impl InnerDispatcher +where + T: AsyncRead + AsyncWrite, + S: Service, + S::Error: Debug, +{ fn can_read(&self) -> bool { if self.flags.contains(Flags::DISCONNECTED) { return false; @@ -150,7 +166,7 @@ where /// Flush stream fn poll_flush(&mut self) -> Poll<(), DispatchError> { if !self.flags.contains(Flags::FLUSHED) { - match self.framed.as_mut().unwrap().poll_complete() { + match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); @@ -170,90 +186,82 @@ where } } + fn send_response( + &mut self, + message: Response, + body: Body, + ) -> Result, DispatchError> { + self.framed + .force_send(Message::Item(message)) + .map_err(|err| { + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete(None)); + } + DispatchError::Io(err) + })?; + + self.flags + .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); + self.flags.remove(Flags::FLUSHED); + match body { + Body::Empty => Ok(State::None), + Body::Streaming(stream) => Ok(State::SendPayload(stream)), + Body::Binary(mut bin) => { + self.flags.remove(Flags::FLUSHED); + self.framed.force_send(Message::Chunk(Some(bin.take())))?; + self.framed.force_send(Message::Chunk(None))?; + Ok(State::None) + } + } + } + fn poll_response(&mut self) -> Result<(), DispatchError> { let mut retry = self.can_read(); - - // process loop { - let state = match self.state { - State::None => if let Some(msg) = self.messages.pop_front() { - match msg { - DispatcherMessage::Item(req) => Some(self.handle_request(req)?), - DispatcherMessage::Error(res) => Some(State::SendResponse( - Some((Message::Item(res), Body::Empty)), - )), + let state = match mem::replace(&mut self.state, State::None) { + State::None => match self.messages.pop_front() { + Some(DispatcherMessage::Item(req)) => { + Some(self.handle_request(req)?) } - } else { - None + Some(DispatcherMessage::Error(res)) => { + Some(self.send_response(res, Body::Empty)?) + } + None => None, }, - // call inner service - State::ServiceCall(ref mut fut) => { + State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed - .as_mut() - .unwrap() - .get_codec_mut() - .prepare_te(&mut res); + self.framed.get_codec_mut().prepare_te(&mut res); let body = res.replace_body(Body::Empty); - Some(State::SendResponse(Some((Message::Item(res), body)))) + Some(self.send_response(res, body)?) } - Async::NotReady => None, - } - } - // send respons - State::SendResponse(ref mut item) => { - let (msg, body) = item.take().expect("SendResponse is empty"); - let framed = self.framed.as_mut().unwrap(); - match framed.start_send(msg) { - Ok(AsyncSink::Ready) => { - self.flags - .set(Flags::KEEPALIVE, framed.get_codec().keepalive()); - self.flags.remove(Flags::FLUSHED); - match body { - Body::Empty => Some(State::None), - Body::Streaming(stream) => { - Some(State::SendPayload(stream)) - } - Body::Binary(mut bin) => { - self.flags.remove(Flags::FLUSHED); - framed - .force_send(Message::Chunk(Some(bin.take())))?; - framed.force_send(Message::Chunk(None))?; - Some(State::None) - } - } - } - Ok(AsyncSink::NotReady(msg)) => { - *item = Some((msg, body)); - return Ok(()); - } - Err(err) => { - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } - return Err(DispatchError::Io(err)); + Async::NotReady => { + self.state = State::ServiceCall(fut); + None } } } - // Send payload - State::SendPayload(ref mut stream) => { - let mut framed = self.framed.as_mut().unwrap(); + State::SendPayload(mut stream) => { loop { - if !framed.is_write_buf_full() { + if !self.framed.is_write_buf_full() { match stream.poll().map_err(|_| DispatchError::Unknown)? { Async::Ready(Some(item)) => { self.flags.remove(Flags::FLUSHED); - framed.force_send(Message::Chunk(Some(item)))?; + self.framed + .force_send(Message::Chunk(Some(item)))?; continue; } Async::Ready(None) => { self.flags.remove(Flags::FLUSHED); - framed.force_send(Message::Chunk(None))?; + self.framed.force_send(Message::Chunk(None))?; + } + Async::NotReady => { + self.state = State::SendPayload(stream); + return Ok(()); } - Async::NotReady => return Ok(()), } } else { + self.state = State::SendPayload(stream); return Ok(()); } break; @@ -266,7 +274,7 @@ where Some(state) => self.state = state, None => { // if read-backpressure is enabled and we consumed some data. - // we may read more dataand retry + // we may read more data and retry if !retry && self.can_read() && self.poll_request()? { retry = self.can_read(); continue; @@ -286,13 +294,9 @@ where let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed - .as_mut() - .unwrap() - .get_codec_mut() - .prepare_te(&mut res); + self.framed.get_codec_mut().prepare_te(&mut res); let body = res.replace_body(Body::Empty); - Ok(State::SendResponse(Some((Message::Item(res), body)))) + self.send_response(res, body) } Async::NotReady => Ok(State::ServiceCall(task)), } @@ -307,20 +311,14 @@ where let mut updated = false; loop { - match self.framed.as_mut().unwrap().poll() { + match self.framed.poll() { Ok(Async::Ready(Some(msg))) => { updated = true; self.flags.insert(Flags::STARTED); match msg { Message::Item(req) => { - match self - .framed - .as_ref() - .unwrap() - .get_codec() - .message_type() - { + match self.framed.get_codec().message_type() { MessageType::Payload => { let (ps, pl) = Payload::new(false); *req.inner.payload.borrow_mut() = Some(pl); @@ -406,52 +404,58 @@ where /// keep-alive timer fn poll_keepalive(&mut self) -> Result<(), DispatchError> { - if let Some(ref mut timer) = self.ka_timer { - match timer.poll() { - Ok(Async::Ready(_)) => { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - return Err(DispatchError::DisconnectTimeout); - } else if timer.deadline() >= self.ka_expire { - // check for any outstanding response processing - if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { - if self.flags.contains(Flags::STARTED) { - trace!("Keep-alive timeout, close connection"); - self.flags.insert(Flags::SHUTDOWN); + if self.ka_timer.is_some() { + return Ok(()); + } + match self.ka_timer.as_mut().unwrap().poll().map_err(|e| { + error!("Timer error {:?}", e); + DispatchError::Unknown + })? { + Async::Ready(_) => { + // if we get timeout during shutdown, drop connection + if self.flags.contains(Flags::SHUTDOWN) { + return Err(DispatchError::DisconnectTimeout); + } else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire { + // check for any outstanding response processing + if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { + if self.flags.contains(Flags::STARTED) { + trace!("Keep-alive timeout, close connection"); + self.flags.insert(Flags::SHUTDOWN); - // start shutdown timer - if let Some(deadline) = - self.config.client_disconnect_timer() - { + // start shutdown timer + if let Some(deadline) = self.config.client_disconnect_timer() + { + self.ka_timer.as_mut().map(|timer| { timer.reset(deadline); let _ = timer.poll(); - } else { - return Ok(()); - } + }); } else { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - self.state = State::SendResponse(Some(( - Message::Item(Response::RequestTimeout().finish()), - Body::Empty, - ))); + return Ok(()); } - } else if let Some(deadline) = self.config.keep_alive_expire() { + } else { + // timeout on first request (slow request) return 408 + trace!("Slow request timeout"); + self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); + self.state = self.send_response( + Response::RequestTimeout().finish(), + Body::Empty, + )?; + } + } else if let Some(deadline) = self.config.keep_alive_expire() { + self.ka_timer.as_mut().map(|timer| { timer.reset(deadline); let _ = timer.poll(); - } - } else { - timer.reset(self.ka_expire); - let _ = timer.poll(); + }); } - } - Ok(Async::NotReady) => (), - Err(e) => { - error!("Timer error {:?}", e); - return Err(DispatchError::Unknown); + } else { + let expire = self.ka_expire; + self.ka_timer.as_mut().map(|timer| { + timer.reset(expire); + let _ = timer.poll(); + }); } } + Async::NotReady => (), } Ok(()) @@ -469,43 +473,53 @@ where #[inline] fn poll(&mut self) -> Poll { - if self.flags.contains(Flags::SHUTDOWN) { - self.poll_keepalive()?; - try_ready!(self.poll_flush()); - let io = self.framed.take().unwrap().into_inner(); - Ok(Async::Ready(H1ServiceResult::Shutdown(io))) - } else { - self.poll_keepalive()?; - self.poll_request()?; - self.poll_response()?; - self.poll_flush()?; - - // keep-alive and stream errors - if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { - if let Some(err) = self.error.take() { - Err(err) - } else if self.flags.contains(Flags::DISCONNECTED) { - Ok(Async::Ready(H1ServiceResult::Disconnected)) - } - // unhandled request (upgrade or connect) - else if self.unhandled.is_some() { - let req = self.unhandled.take().unwrap(); - let framed = self.framed.take().unwrap(); - Ok(Async::Ready(H1ServiceResult::Unhandled(req, framed))) - } - // disconnect if keep-alive is not enabled - else if self.flags.contains(Flags::STARTED) && !self - .flags - .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) - { - let io = self.framed.take().unwrap().into_inner(); - Ok(Async::Ready(H1ServiceResult::Shutdown(io))) - } else { - Ok(Async::NotReady) - } + let shutdown = if let Some(ref mut inner) = self.inner { + if inner.flags.contains(Flags::SHUTDOWN) { + inner.poll_keepalive()?; + try_ready!(inner.poll_flush()); + true } else { - Ok(Async::NotReady) + inner.poll_keepalive()?; + inner.poll_request()?; + inner.poll_response()?; + inner.poll_flush()?; + + // keep-alive and stream errors + if inner.state.is_empty() && inner.flags.contains(Flags::FLUSHED) { + if let Some(err) = inner.error.take() { + return Err(err); + } else if inner.flags.contains(Flags::DISCONNECTED) { + return Ok(Async::Ready(H1ServiceResult::Disconnected)); + } + // unhandled request (upgrade or connect) + else if inner.unhandled.is_some() { + false + } + // disconnect if keep-alive is not enabled + else if inner.flags.contains(Flags::STARTED) && !inner + .flags + .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) + { + true + } else { + return Ok(Async::NotReady); + } + } else { + return Ok(Async::NotReady); + } } + } else { + unreachable!() + }; + + let mut inner = self.inner.take().unwrap(); + if shutdown { + Ok(Async::Ready(H1ServiceResult::Shutdown( + inner.framed.into_inner(), + ))) + } else { + let req = inner.unhandled.take().unwrap(); + Ok(Async::Ready(H1ServiceResult::Unhandled(req, inner.framed))) } } } From 3a4b16a6d57458f9071cb22732d07ab7f2d864e2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 16 Nov 2018 21:30:37 -0800 Subject: [PATCH 0815/1635] use BodyLength for request and response body --- src/body.rs | 36 +++++++++++++++++++----------------- src/client/pipeline.rs | 12 ++++++------ src/h1/client.rs | 27 ++++++++++++++------------- src/h1/codec.rs | 20 +++++++++----------- src/h1/encoder.rs | 39 ++++++++++++++------------------------- src/ws/client/service.rs | 4 ++-- 6 files changed, 64 insertions(+), 74 deletions(-) diff --git a/src/body.rs b/src/body.rs index 1165909e..6e6239c3 100644 --- a/src/body.rs +++ b/src/body.rs @@ -12,24 +12,26 @@ pub type BodyStream = Box>; /// Type represent streaming payload pub type PayloadStream = Box>; -/// Different type of bory -pub enum BodyType { +#[derive(Debug)] +/// Different type of body +pub enum BodyLength { None, Zero, Sized(usize), + Sized64(u64), Unsized, } /// Type that provides this trait can be streamed to a peer. pub trait MessageBody { - fn tp(&self) -> BodyType; + fn length(&self) -> BodyLength; fn poll_next(&mut self) -> Poll, Error>; } impl MessageBody for () { - fn tp(&self) -> BodyType { - BodyType::Zero + fn length(&self) -> BodyLength { + BodyLength::Zero } fn poll_next(&mut self) -> Poll, Error> { @@ -271,8 +273,8 @@ impl AsRef<[u8]> for Binary { } impl MessageBody for Bytes { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -285,8 +287,8 @@ impl MessageBody for Bytes { } impl MessageBody for &'static str { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -301,8 +303,8 @@ impl MessageBody for &'static str { } impl MessageBody for &'static [u8] { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -317,8 +319,8 @@ impl MessageBody for &'static [u8] { } impl MessageBody for Vec { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -334,8 +336,8 @@ impl MessageBody for Vec { } impl MessageBody for String { - fn tp(&self) -> BodyType { - BodyType::Sized(self.len()) + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -367,8 +369,8 @@ impl MessageBody for MessageBodyStream where S: Stream, { - fn tp(&self) -> BodyType { - BodyType::Unsized + fn length(&self) -> BodyLength { + BodyLength::Unsized } fn poll_next(&mut self) -> Poll, Error> { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 17ba93e7..93b349e9 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -10,7 +10,7 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::error::{ConnectorError, SendRequestError}; use super::response::ClientResponse; use super::{Connect, Connection}; -use body::{BodyType, MessageBody, PayloadStream}; +use body::{BodyLength, MessageBody, PayloadStream}; use error::PayloadError; use h1; use message::RequestHead; @@ -25,7 +25,7 @@ where B: MessageBody, I: Connection, { - let tp = body.tp(); + let len = body.length(); connector // connect to the host @@ -33,10 +33,10 @@ where .from_err() // create Framed and send reqest .map(|io| Framed::new(io, h1::ClientCodec::default())) - .and_then(|framed| framed.send((head, tp).into()).from_err()) + .and_then(|framed| framed.send((head, len).into()).from_err()) // send request body - .and_then(move |framed| match body.tp() { - BodyType::None | BodyType::Zero => Either::A(ok(framed)), + .and_then(move |framed| match body.length() { + BodyLength::None | BodyLength::Zero => Either::A(ok(framed)), _ => Either::B(SendBody::new(body, framed)), }) // read response and init read body @@ -64,7 +64,7 @@ where struct SendBody { body: Option, framed: Option>, - write_buf: VecDeque>, + write_buf: VecDeque>, flushed: bool, } diff --git a/src/h1/client.rs b/src/h1/client.rs index f871cb33..8b367acc 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -5,9 +5,9 @@ use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; -use super::encoder::{RequestEncoder, ResponseLength}; +use super::encoder::RequestEncoder; use super::{Message, MessageType}; -use body::{Binary, Body, BodyType}; +use body::{Binary, Body, BodyLength}; use client::ClientResponse; use config::ServiceConfig; use error::{ParseError, PayloadError}; @@ -104,7 +104,7 @@ impl ClientCodec { } /// prepare transfer encoding - pub fn prepare_te(&mut self, head: &mut RequestHead, btype: BodyType) { + pub fn prepare_te(&mut self, head: &mut RequestHead, length: BodyLength) { self.inner.te.update( head, self.inner.flags.contains(Flags::HEAD), @@ -138,7 +138,7 @@ impl ClientCodecInner { fn encode_response( &mut self, msg: RequestHead, - btype: BodyType, + length: BodyLength, buffer: &mut BytesMut, ) -> io::Result<()> { // render message @@ -157,20 +157,21 @@ impl ClientCodecInner { // content length let mut len_is_set = true; - match btype { - BodyType::Sized(len) => { + match length { + BodyLength::Sized(len) => helpers::write_content_length(len, buffer), + BodyLength::Sized64(len) => { buffer.extend_from_slice(b"\r\ncontent-length: "); write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } - BodyType::Unsized => { + BodyLength::Unsized => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - BodyType::Zero => { + BodyLength::Zero => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } - BodyType::None => buffer.extend_from_slice(b"\r\n"), + BodyLength::None => buffer.extend_from_slice(b"\r\n"), } let mut has_date = false; @@ -178,9 +179,9 @@ impl ClientCodecInner { for (key, value) in &msg.headers { match *key { TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match btype { - BodyType::None => (), - BodyType::Zero => len_is_set = true, + CONTENT_LENGTH => match length { + BodyLength::None => (), + BodyLength::Zero => len_is_set = true, _ => continue, }, DATE => has_date = true, @@ -263,7 +264,7 @@ impl Decoder for ClientPayloadCodec { } impl Encoder for ClientCodec { - type Item = Message<(RequestHead, BodyType)>; + type Item = Message<(RequestHead, BodyLength)>; type Error = io::Error; fn encode( diff --git a/src/h1/codec.rs b/src/h1/codec.rs index c1c6091d..a6f39242 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,9 +6,9 @@ use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; -use super::encoder::{ResponseEncoder, ResponseLength}; +use super::encoder::ResponseEncoder; use super::{Message, MessageType}; -use body::{Binary, Body}; +use body::{Binary, Body, BodyLength}; use config::ServiceConfig; use error::ParseError; use helpers; @@ -155,22 +155,20 @@ impl Codec { // content length let mut len_is_set = true; match self.te.length { - ResponseLength::Chunked => { + BodyLength::Unsized => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - ResponseLength::Zero => { + BodyLength::Zero => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } - ResponseLength::Length(len) => { - helpers::write_content_length(len, buffer) - } - ResponseLength::Length64(len) => { + BodyLength::Sized(len) => helpers::write_content_length(len, buffer), + BodyLength::Sized64(len) => { buffer.extend_from_slice(b"\r\ncontent-length: "); write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } - ResponseLength::None => buffer.extend_from_slice(b"\r\n"), + BodyLength::None => buffer.extend_from_slice(b"\r\n"), } // write headers @@ -182,8 +180,8 @@ impl Codec { match *key { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match self.te.length { - ResponseLength::None => (), - ResponseLength::Zero => { + BodyLength::None => (), + BodyLength::Zero => { len_is_set = true; } _ => continue, diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index de45351d..5cfdd01b 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -8,28 +8,17 @@ use bytes::{Bytes, BytesMut}; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; -use body::{Binary, Body}; +use body::{Binary, Body, BodyLength}; use header::ContentEncoding; use http::Method; use message::RequestHead; use request::Request; use response::Response; -#[derive(Debug)] -pub(crate) enum ResponseLength { - Chunked, - /// Check if headers contains length or write 0 - Zero, - Length(usize), - Length64(u64), - /// Do no set content-length - None, -} - #[derive(Debug)] pub(crate) struct ResponseEncoder { head: bool, - pub length: ResponseLength, + pub length: BodyLength, pub te: TransferEncoding, } @@ -37,7 +26,7 @@ impl Default for ResponseEncoder { fn default() -> Self { ResponseEncoder { head: false, - length: ResponseLength::None, + length: BodyLength::None, te: TransferEncoding::empty(), } } @@ -80,18 +69,18 @@ impl ResponseEncoder { StatusCode::NO_CONTENT | StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => ResponseLength::None, - _ => ResponseLength::Zero, + | StatusCode::PROCESSING => BodyLength::None, + _ => BodyLength::Zero, }; TransferEncoding::empty() } Body::Binary(_) => { - self.length = ResponseLength::Length(len); + self.length = BodyLength::Sized(len); TransferEncoding::length(len as u64) } Body::Streaming(_) => { if resp.upgrade() { - self.length = ResponseLength::None; + self.length = BodyLength::None; TransferEncoding::eof() } else { self.streaming_encoding(version, resp) @@ -115,10 +104,10 @@ impl ResponseEncoder { Some(true) => { // Enable transfer encoding if version == Version::HTTP_2 { - self.length = ResponseLength::None; + self.length = BodyLength::None; TransferEncoding::eof() } else { - self.length = ResponseLength::Chunked; + self.length = BodyLength::Unsized; TransferEncoding::chunked() } } @@ -145,7 +134,7 @@ impl ResponseEncoder { if !chunked { if let Some(len) = len { - self.length = ResponseLength::Length64(len); + self.length = BodyLength::Sized64(len); TransferEncoding::length(len) } else { TransferEncoding::eof() @@ -154,11 +143,11 @@ impl ResponseEncoder { // Enable transfer encoding match version { Version::HTTP_11 => { - self.length = ResponseLength::Chunked; + self.length = BodyLength::Unsized; TransferEncoding::chunked() } _ => { - self.length = ResponseLength::None; + self.length = BodyLength::None; TransferEncoding::eof() } } @@ -171,7 +160,7 @@ impl ResponseEncoder { #[derive(Debug)] pub(crate) struct RequestEncoder { head: bool, - pub length: ResponseLength, + pub length: BodyLength, pub te: TransferEncoding, } @@ -179,7 +168,7 @@ impl Default for RequestEncoder { fn default() -> Self { RequestEncoder { head: false, - length: ResponseLength::None, + length: BodyLength::None, te: TransferEncoding::empty(), } } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 34a15144..94be59f6 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -13,7 +13,7 @@ use rand; use sha1::Sha1; use tokio_io::{AsyncRead, AsyncWrite}; -use body::BodyType; +use body::BodyLength; use client::ClientResponse; use h1; use ws::Codec; @@ -141,7 +141,7 @@ where // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed - .send((request.into_parts().0, BodyType::None).into()) + .send((request.into_parts().0, BodyLength::None).into()) .map_err(ClientError::from) .and_then(|framed| { framed From f0bd4d868e12f2f1b227c681e9cadd007937d94a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 17 Nov 2018 08:56:40 -0800 Subject: [PATCH 0816/1635] simplify server response type --- src/message.rs | 2 + src/response.rs | 119 +++++++++++++++++++++--------------------------- 2 files changed, 53 insertions(+), 68 deletions(-) diff --git a/src/message.rs b/src/message.rs index b38c2f80..f0275093 100644 --- a/src/message.rs +++ b/src/message.rs @@ -74,6 +74,8 @@ impl Default for ResponseHead { impl Head for ResponseHead { fn clear(&mut self) { + self.reason = None; + self.version = None; self.headers.clear(); self.flags = MessageFlags::empty(); } diff --git a/src/response.rs b/src/response.rs index 1901443f..0b20f41b 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,6 +12,7 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; +use message::{Head, ResponseHead, MessageFlags}; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; @@ -31,7 +32,7 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct Response(Box, &'static ResponsePool); +pub struct Response(Box); impl Response { #[inline] @@ -92,7 +93,6 @@ impl Response { } ResponseBuilder { - pool: self.1, response: Some(self.0), err: None, cookies: jar, @@ -108,33 +108,33 @@ impl Response { /// Get the HTTP version of this response #[inline] pub fn version(&self) -> Option { - self.get_ref().version + self.get_ref().head.version } /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { - &self.get_ref().headers + &self.get_ref().head.headers } /// Get a mutable reference to the headers #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.get_mut().headers + &mut self.get_mut().head.headers } /// Get an iterator for the cookies set by this response #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter(), + iter: self.get_ref().head.headers.get_all(header::SET_COOKIE).iter(), } } /// Add a cookie to this response #[inline] pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { - let h = &mut self.get_mut().headers; + let h = &mut self.get_mut().head.headers; HeaderValue::from_str(&cookie.to_string()) .map(|c| { h.append(header::SET_COOKIE, c); @@ -145,7 +145,7 @@ impl Response { /// the number of cookies removed. #[inline] pub fn del_cookie(&mut self, name: &str) -> usize { - let h = &mut self.get_mut().headers; + let h = &mut self.get_mut().head.headers; let vals: Vec = h .get_all(header::SET_COOKIE) .iter() @@ -171,23 +171,23 @@ impl Response { /// Get the response status code #[inline] pub fn status(&self) -> StatusCode { - self.get_ref().status + self.get_ref().head.status } /// Set the `StatusCode` for this response #[inline] pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.get_mut().status + &mut self.get_mut().head.status } /// Get custom reason for the response #[inline] pub fn reason(&self) -> &str { - if let Some(reason) = self.get_ref().reason { + if let Some(reason) = self.get_ref().head.reason { reason } else { self.get_ref() - .status + .head.status .canonical_reason() .unwrap_or("") } @@ -196,7 +196,7 @@ impl Response { /// Set the custom reason for the response #[inline] pub fn set_reason(&mut self, reason: &'static str) -> &mut Self { - self.get_mut().reason = Some(reason); + self.get_mut().head.reason = Some(reason); self } @@ -279,7 +279,7 @@ impl Response { } pub(crate) fn release(self) { - self.1.release(self.0); + ResponsePool::release(self.0); } pub(crate) fn into_parts(self) -> ResponseParts { @@ -287,10 +287,7 @@ impl Response { } pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response( - Box::new(InnerResponse::from_parts(parts)), - ResponsePool::get_pool(), - ) + Response(Box::new(InnerResponse::from_parts(parts))) } } @@ -299,13 +296,13 @@ impl fmt::Debug for Response { let res = writeln!( f, "\nResponse {:?} {}{}", - self.get_ref().version, - self.get_ref().status, - self.get_ref().reason.unwrap_or("") + self.get_ref().head.version, + self.get_ref().head.status, + self.get_ref().head.reason.unwrap_or("") ); let _ = writeln!(f, " encoding: {:?}", self.get_ref().encoding); let _ = writeln!(f, " headers:"); - for (key, val) in self.get_ref().headers.iter() { + for (key, val) in self.get_ref().head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } res @@ -335,7 +332,6 @@ impl<'a> Iterator for CookieIter<'a> { /// This type can be used to construct an instance of `Response` through a /// builder-like pattern. pub struct ResponseBuilder { - pool: &'static ResponsePool, response: Option>, err: Option, cookies: Option, @@ -346,7 +342,7 @@ impl ResponseBuilder { #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.status = status; + parts.head.status = status; } self } @@ -357,7 +353,7 @@ impl ResponseBuilder { #[inline] pub fn version(&mut self, version: Version) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.version = Some(version); + parts.head.version = Some(version); } self } @@ -382,7 +378,7 @@ impl ResponseBuilder { if let Some(parts) = parts(&mut self.response, &self.err) { match hdr.try_into() { Ok(value) => { - parts.headers.append(H::name(), value); + parts.head.headers.append(H::name(), value); } Err(e) => self.err = Some(e.into()), } @@ -413,7 +409,7 @@ impl ResponseBuilder { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { - parts.headers.append(key, value); + parts.head.headers.append(key, value); } Err(e) => self.err = Some(e.into()), }, @@ -427,7 +423,7 @@ impl ResponseBuilder { #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.reason = Some(reason); + parts.head.reason = Some(reason); } self } @@ -496,7 +492,7 @@ impl ResponseBuilder { if let Some(parts) = parts(&mut self.response, &self.err) { match HeaderValue::try_from(value) { Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); + parts.head.headers.insert(header::CONTENT_TYPE, value); } Err(e) => self.err = Some(e.into()), }; @@ -620,13 +616,13 @@ impl ResponseBuilder { if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => response.headers.append(header::SET_COOKIE, val), + Ok(val) => response.head.headers.append(header::SET_COOKIE, val), Err(e) => return Error::from(e).into(), }; } } response.body = body.into(); - Response(response, self.pool) + Response(response) } #[inline] @@ -656,7 +652,7 @@ impl ResponseBuilder { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) + parts.head.headers.contains_key(header::CONTENT_TYPE) } else { true }; @@ -681,7 +677,6 @@ impl ResponseBuilder { /// This method construct new `ResponseBuilder` pub fn take(&mut self) -> ResponseBuilder { ResponseBuilder { - pool: self.pool, response: self.response.take(), err: self.err.take(), cookies: self.cookies.take(), @@ -765,12 +760,8 @@ impl From for Response { } } -#[derive(Debug)] struct InnerResponse { - version: Option, - headers: HeaderMap, - status: StatusCode, - reason: Option<&'static str>, + head: ResponseHead, body: Body, chunked: Option, encoding: Option, @@ -778,13 +769,11 @@ struct InnerResponse { write_capacity: usize, response_size: u64, error: Option, + pool: &'static ResponsePool, } pub(crate) struct ResponseParts { - version: Option, - headers: HeaderMap, - status: StatusCode, - reason: Option<&'static str>, + head: ResponseHead, body: Option, encoding: Option, connection_type: Option, @@ -793,13 +782,17 @@ pub(crate) struct ResponseParts { impl InnerResponse { #[inline] - fn new(status: StatusCode, body: Body) -> InnerResponse { + fn new(status: StatusCode, body: Body, pool: &'static ResponsePool) -> InnerResponse { InnerResponse { - status, + head: ResponseHead { + status, + version: None, + headers: HeaderMap::with_capacity(16), + reason: None, + flags: MessageFlags::empty(), + }, body, - version: None, - headers: HeaderMap::with_capacity(16), - reason: None, + pool, chunked: None, encoding: None, connection_type: None, @@ -822,10 +815,7 @@ impl InnerResponse { ResponseParts { body, - version: self.version, - headers: self.headers, - status: self.status, - reason: self.reason, + head: self.head, encoding: self.encoding, connection_type: self.connection_type, error: self.error, @@ -841,16 +831,14 @@ impl InnerResponse { InnerResponse { body, - status: parts.status, - version: parts.version, - headers: parts.headers, - reason: parts.reason, + head: parts.head, chunked: None, encoding: parts.encoding, connection_type: parts.connection_type, response_size: 0, write_capacity: MAX_WRITE_BUFFER_SIZE, error: parts.error, + pool: ResponsePool::pool(), } } } @@ -876,17 +864,15 @@ impl ResponsePool { status: StatusCode, ) -> ResponseBuilder { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.status = status; + msg.head.status = status; ResponseBuilder { - pool, response: Some(msg), err: None, cookies: None, } } else { - let msg = Box::new(InnerResponse::new(status, Body::Empty)); + let msg = Box::new(InnerResponse::new(status, Body::Empty, pool)); ResponseBuilder { - pool, response: Some(msg), err: None, cookies: None, @@ -901,12 +887,11 @@ impl ResponsePool { body: Body, ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.status = status; + msg.head.status = status; msg.body = body; - Response(msg, pool) + Response(msg) } else { - let msg = Box::new(InnerResponse::new(status, body)); - Response(msg, pool) + Response(Box::new(InnerResponse::new(status, body, pool))) } } @@ -921,13 +906,11 @@ impl ResponsePool { } #[inline] - fn release(&self, mut inner: Box) { - let mut p = self.0.borrow_mut(); + fn release(mut inner: Box) { + let mut p = inner.pool.0.borrow_mut(); if p.len() < 128 { - inner.headers.clear(); - inner.version = None; + inner.head.clear(); inner.chunked = None; - inner.reason = None; inner.encoding = None; inner.connection_type = None; inner.response_size = 0; From e73a97884af8d0c738aaf32cb6142b703620d46f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 17 Nov 2018 09:03:35 -0800 Subject: [PATCH 0817/1635] do not allow to set server response version --- src/client/response.rs | 2 +- src/h1/codec.rs | 7 +++---- src/h1/decoder.rs | 2 +- src/h1/encoder.rs | 2 -- src/message.rs | 5 ++--- src/response.rs | 43 ++++++++++++++++-------------------------- 6 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/client/response.rs b/src/client/response.rs index 797c88df..41c18562 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -57,7 +57,7 @@ impl ClientResponse { /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.head().version.clone().unwrap() + self.head().version } /// Get the status from the server. diff --git a/src/h1/codec.rs b/src/h1/codec.rs index a6f39242..bb8afa71 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -113,7 +113,6 @@ impl Codec { .unwrap_or_else(|| self.flags.contains(Flags::KEEPALIVE)); // Connection upgrade - let version = msg.version().unwrap_or_else(|| self.version); if msg.upgrade() { self.flags.insert(Flags::UPGRADE); self.flags.remove(Flags::KEEPALIVE); @@ -123,11 +122,11 @@ impl Codec { // keep-alive else if ka { self.flags.insert(Flags::KEEPALIVE); - if version < Version::HTTP_11 { + if self.version < Version::HTTP_11 { msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("keep-alive")); } - } else if version >= Version::HTTP_11 { + } else if self.version >= Version::HTTP_11 { self.flags.remove(Flags::KEEPALIVE); msg.headers_mut() .insert(CONNECTION, HeaderValue::from_static("close")); @@ -149,7 +148,7 @@ impl Codec { } // status line - helpers::write_status_line(version, msg.status().as_u16(), buffer); + helpers::write_status_line(self.version, msg.status().as_u16(), buffer); buffer.extend_from_slice(reason); // content length diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 61cab7ad..26154ef1 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -290,7 +290,7 @@ impl MessageTypeDecoder for ClientResponse { }; msg.head.status = status; - msg.head.version = Some(ver); + msg.head.version = ver; Ok(Some((msg, decoder))) } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 5cfdd01b..421d0b96 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -45,8 +45,6 @@ impl ResponseEncoder { pub fn update(&mut self, resp: &mut Response, head: bool, version: Version) { self.head = head; - - let version = resp.version().unwrap_or_else(|| version); let mut len = 0; let has_body = match resp.body() { diff --git a/src/message.rs b/src/message.rs index f0275093..3dcb203d 100644 --- a/src/message.rs +++ b/src/message.rs @@ -53,7 +53,7 @@ impl Head for RequestHead { } pub struct ResponseHead { - pub version: Option, + pub version: Version, pub status: StatusCode, pub headers: HeaderMap, pub reason: Option<&'static str>, @@ -63,7 +63,7 @@ pub struct ResponseHead { impl Default for ResponseHead { fn default() -> ResponseHead { ResponseHead { - version: None, + version: Version::default(), status: StatusCode::OK, headers: HeaderMap::with_capacity(16), reason: None, @@ -75,7 +75,6 @@ impl Default for ResponseHead { impl Head for ResponseHead { fn clear(&mut self) { self.reason = None; - self.version = None; self.headers.clear(); self.flags = MessageFlags::empty(); } diff --git a/src/response.rs b/src/response.rs index 0b20f41b..f96facba 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,10 +12,10 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use message::{Head, ResponseHead, MessageFlags}; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; +use message::{Head, MessageFlags, ResponseHead}; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -105,12 +105,6 @@ impl Response { self.get_ref().error.as_ref() } - /// Get the HTTP version of this response - #[inline] - pub fn version(&self) -> Option { - self.get_ref().head.version - } - /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { @@ -127,7 +121,12 @@ impl Response { #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self.get_ref().head.headers.get_all(header::SET_COOKIE).iter(), + iter: self + .get_ref() + .head + .headers + .get_all(header::SET_COOKIE) + .iter(), } } @@ -187,7 +186,8 @@ impl Response { reason } else { self.get_ref() - .head.status + .head + .status .canonical_reason() .unwrap_or("") } @@ -347,17 +347,6 @@ impl ResponseBuilder { self } - /// 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) { - parts.head.version = Some(version); - } - self - } - /// Set a header. /// /// ```rust,ignore @@ -782,11 +771,15 @@ pub(crate) struct ResponseParts { impl InnerResponse { #[inline] - fn new(status: StatusCode, body: Body, pool: &'static ResponsePool) -> InnerResponse { + fn new( + status: StatusCode, + body: Body, + pool: &'static ResponsePool, + ) -> InnerResponse { InnerResponse { head: ResponseHead { status, - version: None, + version: Version::default(), headers: HeaderMap::with_capacity(16), reason: None, flags: MessageFlags::empty(), @@ -999,11 +992,7 @@ mod tests { #[test] fn test_basic_builder() { - let resp = Response::Ok() - .header("X-TEST", "value") - .version(Version::HTTP_10) - .finish(); - assert_eq!(resp.version(), Some(Version::HTTP_10)); + let resp = Response::Ok().header("X-TEST", "value").finish(); assert_eq!(resp.status(), StatusCode::OK); } From 7fed50bcaefde84c6e9424112e220fe789f99a57 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 17 Nov 2018 20:21:28 -0800 Subject: [PATCH 0818/1635] refactor response body management --- src/body.rs | 118 +++------------- src/client/pipeline.rs | 4 + src/client/request.rs | 6 - src/error.rs | 9 +- src/h1/client.rs | 8 +- src/h1/codec.rs | 26 ++-- src/h1/dispatcher.rs | 80 ++++++----- src/h1/encoder.rs | 122 +++------------- src/h1/service.rs | 54 ++++--- src/lib.rs | 2 +- src/response.rs | 309 ++++++++++++++++------------------------- src/service.rs | 144 +++++++++---------- 12 files changed, 334 insertions(+), 548 deletions(-) diff --git a/src/body.rs b/src/body.rs index 6e6239c3..3b4e0113 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,5 +1,5 @@ +use std::mem; use std::sync::Arc; -use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; @@ -19,7 +19,8 @@ pub enum BodyLength { Zero, Sized(usize), Sized64(u64), - Unsized, + Chunked, + Stream, } /// Type that provides this trait can be streamed to a peer. @@ -39,17 +40,6 @@ impl MessageBody for () { } } -/// Represents various types of http message body. -pub enum Body { - /// Empty response. `Content-Length` header is set to `0` - Empty, - /// Specific response body. - Binary(Binary), - /// Unspecified streaming response. Developer is responsible for setting - /// right `Content-Length` or `Transfer-Encoding` headers. - Streaming(BodyStream), -} - /// Represents various types of binary body. /// `Content-Length` header is set to length of the body. #[derive(Debug, PartialEq)] @@ -65,84 +55,6 @@ pub enum Binary { SharedVec(Arc>), } -impl Body { - /// Does this body streaming. - #[inline] - pub fn is_streaming(&self) -> bool { - match *self { - Body::Streaming(_) => true, - _ => false, - } - } - - /// Is this binary body. - #[inline] - pub fn is_binary(&self) -> bool { - match *self { - Body::Binary(_) => true, - _ => false, - } - } - - /// Is this binary empy. - #[inline] - pub fn is_empty(&self) -> bool { - match *self { - Body::Empty => true, - _ => false, - } - } - - /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> Body { - Body::Binary(Binary::Bytes(Bytes::from(s))) - } - - /// Is this binary body. - #[inline] - pub(crate) fn into_binary(self) -> Option { - match self { - Body::Binary(b) => Some(b), - _ => None, - } - } -} - -impl PartialEq for Body { - fn eq(&self, other: &Body) -> bool { - match *self { - Body::Empty => match *other { - Body::Empty => true, - _ => false, - }, - Body::Binary(ref b) => match *other { - Body::Binary(ref b2) => b == b2, - _ => false, - }, - Body::Streaming(_) => false, - } - } -} - -impl fmt::Debug for Body { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Body::Empty => write!(f, "Body::Empty"), - Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b), - Body::Streaming(_) => write!(f, "Body::Streaming(_)"), - } - } -} - -impl From for Body -where - T: Into, -{ - fn from(b: T) -> Body { - Body::Binary(b.into()) - } -} - impl Binary { #[inline] /// Returns `true` if body is empty @@ -286,6 +198,22 @@ impl MessageBody for Bytes { } } +impl MessageBody for BytesMut { + fn length(&self) -> BodyLength { + BodyLength::Sized(self.len()) + } + + fn poll_next(&mut self) -> Poll, Error> { + if self.is_empty() { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some( + mem::replace(self, BytesMut::new()).freeze(), + ))) + } + } +} + impl MessageBody for &'static str { fn length(&self) -> BodyLength { BodyLength::Sized(self.len()) @@ -370,7 +298,7 @@ where S: Stream, { fn length(&self) -> BodyLength { - BodyLength::Unsized + BodyLength::Chunked } fn poll_next(&mut self) -> Poll, Error> { @@ -382,12 +310,6 @@ where mod tests { use super::*; - #[test] - fn test_body_is_streaming() { - assert_eq!(Body::Empty.is_streaming(), false); - assert_eq!(Body::Binary(Binary::from("")).is_streaming(), false); - } - #[test] fn test_is_empty() { assert_eq!(Binary::from("").is_empty(), true); diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 93b349e9..56c22bd2 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -100,6 +100,10 @@ where { match self.body.as_mut().unwrap().poll_next()? { Async::Ready(item) => { + // check if body is done + if item.is_none() { + let _ = self.body.take(); + } self.flushed = false; self.framed .as_mut() diff --git a/src/client/request.rs b/src/client/request.rs index d3d1544c..f0b76ed0 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -51,12 +51,6 @@ pub struct ClientRequest { body: B, } -impl RequestHead { - pub fn clear(&mut self) { - self.headers.clear() - } -} - impl ClientRequest<()> { /// Create client request builder pub fn build() -> ClientRequestBuilder { diff --git a/src/error.rs b/src/error.rs index 956ec4eb..1f70396c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -100,11 +100,10 @@ impl Error { } /// Converts error to a response instance and set error message as response body - pub fn response_with_message(self) -> Response { + pub fn response_with_message(self) -> Response { let message = format!("{}", self); - let mut resp: Response = self.into(); - resp.set_body(message); - resp + let resp: Response = self.into(); + resp.set_body(message) } } @@ -637,7 +636,7 @@ where InternalErrorType::Status(st) => Response::new(st), InternalErrorType::Response(ref resp) => { if let Some(resp) = resp.lock().unwrap().take() { - Response::from_parts(resp) + Response::<()>::from_parts(resp) } else { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } diff --git a/src/h1/client.rs b/src/h1/client.rs index 8b367acc..ace50466 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -7,7 +7,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::RequestEncoder; use super::{Message, MessageType}; -use body::{Binary, Body, BodyLength}; +use body::{Binary, BodyLength}; use client::ClientResponse; use config::ServiceConfig; use error::{ParseError, PayloadError}; @@ -164,14 +164,16 @@ impl ClientCodecInner { write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } - BodyLength::Unsized => { + BodyLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } BodyLength::Zero => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } - BodyLength::None => buffer.extend_from_slice(b"\r\n"), + BodyLength::None | BodyLength::Stream => { + buffer.extend_from_slice(b"\r\n") + } } let mut has_date = false; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index bb8afa71..6bc20b18 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -8,12 +8,13 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::ResponseEncoder; use super::{Message, MessageType}; -use body::{Binary, Body, BodyLength}; +use body::{Binary, BodyLength}; use config::ServiceConfig; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, Version}; +use message::ResponseHead; use request::Request; use response::Response; @@ -98,9 +99,9 @@ impl Codec { } /// prepare transfer encoding - pub fn prepare_te(&mut self, res: &mut Response) { + pub fn prepare_te(&mut self, head: &mut ResponseHead, length: &mut BodyLength) { self.te - .update(res, self.flags.contains(Flags::HEAD), self.version); + .update(head, self.flags.contains(Flags::HEAD), self.version, length); } fn encode_response( @@ -135,17 +136,8 @@ impl Codec { // render message { let reason = msg.reason().as_bytes(); - if let Body::Binary(ref bytes) = msg.body() { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE - + bytes.len() - + reason.len(), - ); - } else { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), - ); - } + buffer + .reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len()); // status line helpers::write_status_line(self.version, msg.status().as_u16(), buffer); @@ -154,7 +146,7 @@ impl Codec { // content length let mut len_is_set = true; match self.te.length { - BodyLength::Unsized => { + BodyLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } BodyLength::Zero => { @@ -167,7 +159,9 @@ impl Codec { write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } - BodyLength::None => buffer.extend_from_slice(b"\r\n"), + BodyLength::None | BodyLength::Stream => { + buffer.extend_from_slice(b"\r\n") + } } // write headers diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 4a0bce72..508962b4 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -13,7 +13,7 @@ use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; -use body::{Body, BodyStream}; +use body::{BodyLength, MessageBody}; use config::ServiceConfig; use error::DispatchError; use request::Request; @@ -37,14 +37,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher where S::Error: Debug, { - inner: Option>, + inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher where S::Error: Debug, { @@ -54,7 +54,7 @@ where error: Option>, config: ServiceConfig, - state: State, + state: State, payload: Option, messages: VecDeque, unhandled: Option, @@ -68,13 +68,13 @@ enum DispatcherMessage { Error(Response), } -enum State { +enum State { None, ServiceCall(S::Future), - SendPayload(BodyStream), + SendPayload(B), } -impl State { +impl State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -84,11 +84,12 @@ impl State { } } -impl Dispatcher +impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service>, S::Error: Debug, + B: MessageBody, { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { @@ -137,11 +138,12 @@ where } } -impl InnerDispatcher +impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service>, S::Error: Debug, + B: MessageBody, { fn can_read(&self) -> bool { if self.flags.contains(Flags::DISCONNECTED) { @@ -186,11 +188,11 @@ where } } - fn send_response( + fn send_response( &mut self, message: Response, - body: Body, - ) -> Result, DispatchError> { + body: B1, + ) -> Result, DispatchError> { self.framed .force_send(Message::Item(message)) .map_err(|err| { @@ -203,15 +205,9 @@ where self.flags .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); self.flags.remove(Flags::FLUSHED); - match body { - Body::Empty => Ok(State::None), - Body::Streaming(stream) => Ok(State::SendPayload(stream)), - Body::Binary(mut bin) => { - self.flags.remove(Flags::FLUSHED); - self.framed.force_send(Message::Chunk(Some(bin.take())))?; - self.framed.force_send(Message::Chunk(None))?; - Ok(State::None) - } + match body.length() { + BodyLength::None | BodyLength::Zero => Ok(State::None), + _ => Ok(State::SendPayload(body)), } } @@ -224,15 +220,18 @@ where Some(self.handle_request(req)?) } Some(DispatcherMessage::Error(res)) => { - Some(self.send_response(res, Body::Empty)?) + self.send_response(res, ())?; + None } None => None, }, State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); - let body = res.replace_body(Body::Empty); + let (mut res, body) = res.replace_body(()); + self.framed + .get_codec_mut() + .prepare_te(res.head_mut(), &mut body.length()); Some(self.send_response(res, body)?) } Async::NotReady => { @@ -244,7 +243,10 @@ where State::SendPayload(mut stream) => { loop { if !self.framed.is_write_buf_full() { - match stream.poll().map_err(|_| DispatchError::Unknown)? { + match stream + .poll_next() + .map_err(|_| DispatchError::Unknown)? + { Async::Ready(Some(item)) => { self.flags.remove(Flags::FLUSHED); self.framed @@ -290,12 +292,14 @@ where fn handle_request( &mut self, req: Request, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { - Async::Ready(mut res) => { - self.framed.get_codec_mut().prepare_te(&mut res); - let body = res.replace_body(Body::Empty); + Async::Ready(res) => { + let (mut res, body) = res.replace_body(()); + self.framed + .get_codec_mut() + .prepare_te(res.head_mut(), &mut body.length()); self.send_response(res, body) } Async::NotReady => Ok(State::ServiceCall(task)), @@ -436,10 +440,9 @@ where // timeout on first request (slow request) return 408 trace!("Slow request timeout"); self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - self.state = self.send_response( - Response::RequestTimeout().finish(), - Body::Empty, - )?; + let _ = self + .send_response(Response::RequestTimeout().finish(), ()); + self.state = State::None; } } else if let Some(deadline) = self.config.keep_alive_expire() { self.ka_timer.as_mut().map(|timer| { @@ -462,11 +465,12 @@ where } } -impl Future for Dispatcher +impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service>, S::Error: Debug, + B: MessageBody, { type Item = H1ServiceResult; type Error = DispatchError; diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 421d0b96..aee17c1f 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -8,10 +8,10 @@ use bytes::{Bytes, BytesMut}; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; -use body::{Binary, Body, BodyLength}; +use body::{Binary, BodyLength}; use header::ContentEncoding; use http::Method; -use message::RequestHead; +use message::{RequestHead, ResponseHead}; use request::Request; use response::Response; @@ -43,116 +43,36 @@ impl ResponseEncoder { self.te.encode_eof(buf) } - pub fn update(&mut self, resp: &mut Response, head: bool, version: Version) { + pub fn update( + &mut self, + resp: &mut ResponseHead, + head: bool, + version: Version, + length: &mut BodyLength, + ) { self.head = head; - let mut len = 0; - - let has_body = match resp.body() { - Body::Empty => false, - Body::Binary(ref bin) => { - len = bin.len(); - true - } - _ => true, - }; - - let has_body = match resp.body() { - Body::Empty => false, - _ => true, - }; - - let transfer = match resp.body() { - Body::Empty => { - self.length = match resp.status() { + let transfer = match length { + BodyLength::Zero => { + match resp.status { StatusCode::NO_CONTENT | StatusCode::CONTINUE | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => BodyLength::None, - _ => BodyLength::Zero, - }; + | StatusCode::PROCESSING => *length = BodyLength::None, + _ => (), + } TransferEncoding::empty() } - Body::Binary(_) => { - self.length = BodyLength::Sized(len); - TransferEncoding::length(len as u64) - } - Body::Streaming(_) => { - if resp.upgrade() { - self.length = BodyLength::None; - TransferEncoding::eof() - } else { - self.streaming_encoding(version, resp) - } - } + BodyLength::Sized(len) => TransferEncoding::length(*len as u64), + BodyLength::Sized64(len) => TransferEncoding::length(*len), + BodyLength::Chunked => TransferEncoding::chunked(), + BodyLength::Stream => TransferEncoding::eof(), + BodyLength::None => TransferEncoding::length(0), }; // check for head response - if self.head { - resp.set_body(Body::Empty); - } else { + if !self.head { self.te = transfer; } } - - fn streaming_encoding( - &mut self, - version: Version, - resp: &mut Response, - ) -> TransferEncoding { - match resp.chunked() { - Some(true) => { - // Enable transfer encoding - if version == Version::HTTP_2 { - self.length = BodyLength::None; - TransferEncoding::eof() - } else { - self.length = BodyLength::Unsized; - TransferEncoding::chunked() - } - } - Some(false) => TransferEncoding::eof(), - None => { - // if Content-Length is specified, then use it as length hint - let (len, chunked) = - if let Some(len) = resp.headers().get(CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - (Some(len), false) - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - (None, true) - }; - - if !chunked { - if let Some(len) = len { - self.length = BodyLength::Sized64(len); - TransferEncoding::length(len) - } else { - TransferEncoding::eof() - } - } else { - // Enable transfer encoding - match version { - Version::HTTP_11 => { - self.length = BodyLength::Unsized; - TransferEncoding::chunked() - } - _ => { - self.length = BodyLength::None; - TransferEncoding::eof() - } - } - } - } - } - } } #[derive(Debug)] diff --git a/src/h1/service.rs b/src/h1/service.rs index 7e5e8c5f..0f0452ee 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -8,6 +8,7 @@ use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; +use body::MessageBody; use config::{KeepAlive, ServiceConfig}; use error::{DispatchError, ParseError}; use request::Request; @@ -18,17 +19,18 @@ use super::dispatcher::Dispatcher; use super::{H1ServiceResult, Message}; /// `NewService` implementation for HTTP1 transport -pub struct H1Service { +pub struct H1Service { srv: S, cfg: ServiceConfig, - _t: PhantomData, + _t: PhantomData<(T, B)>, } -impl H1Service +impl H1Service where - S: NewService + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, + B: MessageBody, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -47,19 +49,20 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, + B: MessageBody, { type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; - type Service = H1ServiceHandler; - type Future = H1ServiceResponse; + type Service = H1ServiceHandler; + type Future = H1ServiceResponse; fn new_service(&self) -> Self::Future { H1ServiceResponse { @@ -180,7 +183,11 @@ where } /// Finish service configuration and create `H1Service` instance. - pub fn finish>(self, service: F) -> H1Service { + pub fn finish(self, service: F) -> H1Service + where + B: MessageBody, + F: IntoNewService, + { let cfg = ServiceConfig::new( self.keep_alive, self.client_timeout, @@ -195,20 +202,21 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse { +pub struct H1ServiceResponse { fut: S::Future, cfg: Option, - _t: PhantomData, + _t: PhantomData<(T, B)>, } -impl Future for H1ServiceResponse +impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService>, S::Service: Clone, S::Error: Debug, + B: MessageBody, { - type Item = H1ServiceHandler; + type Item = H1ServiceHandler; type Error = S::InitError; fn poll(&mut self) -> Poll { @@ -221,18 +229,19 @@ where } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler { srv: S, cfg: ServiceConfig, - _t: PhantomData, + _t: PhantomData<(T, B)>, } -impl H1ServiceHandler +impl H1ServiceHandler where - S: Service + Clone, + S: Service> + Clone, S::Error: Debug, + B: MessageBody, { - fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { + fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { srv, cfg, @@ -241,16 +250,17 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + Clone, + S: Service> + Clone, S::Error: Debug, + B: MessageBody, { type Request = T; type Response = H1ServiceResult; type Error = DispatchError; - type Future = Dispatcher; + type Future = Dispatcher; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.srv.poll_ready().map_err(DispatchError::Service) diff --git a/src/lib.rs b/src/lib.rs index f64876e3..4b43cae4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,7 +129,7 @@ pub mod h1; pub(crate) mod helpers; pub mod test; pub mod ws; -pub use body::{Binary, Body}; +pub use body::{Binary, MessageBody}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; diff --git a/src/response.rs b/src/response.rs index f96facba..b3e59982 100644 --- a/src/response.rs +++ b/src/response.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::io::Write; -use std::{fmt, mem, str}; +use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; @@ -12,7 +12,7 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use body::Body; +use body::{MessageBody, MessageBodyStream}; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; use message::{Head, MessageFlags, ResponseHead}; @@ -32,19 +32,9 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct Response(Box); - -impl Response { - #[inline] - fn get_ref(&self) -> &InnerResponse { - self.0.as_ref() - } - - #[inline] - fn get_mut(&mut self) -> &mut InnerResponse { - self.0.as_mut() - } +pub struct Response(Box, B); +impl Response<()> { /// Create http response builder with specific status. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { @@ -60,13 +50,7 @@ impl Response { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - ResponsePool::with_body(status, Body::Empty) - } - - /// Constructs a response with body - #[inline] - pub fn with_body>(status: StatusCode, body: B) -> Response { - ResponsePool::with_body(status, body.into()) + ResponsePool::with_body(status, ()) } /// Constructs an error response @@ -98,6 +82,29 @@ impl Response { cookies: jar, } } +} + +impl Response { + #[inline] + fn get_ref(&self) -> &InnerResponse { + self.0.as_ref() + } + + #[inline] + fn get_mut(&mut self) -> &mut InnerResponse { + self.0.as_mut() + } + + #[inline] + pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { + &mut self.0.as_mut().head + } + + /// Constructs a response with body + #[inline] + pub fn with_body(status: StatusCode, body: B) -> Response { + ResponsePool::with_body(status, body.into()) + } /// The source `error` for this response #[inline] @@ -105,6 +112,39 @@ impl Response { self.get_ref().error.as_ref() } + /// Get the response status code + #[inline] + pub fn status(&self) -> StatusCode { + self.get_ref().head.status + } + + /// Set the `StatusCode` for this response + #[inline] + pub fn status_mut(&mut self) -> &mut StatusCode { + &mut self.get_mut().head.status + } + + /// Get custom reason for the response + #[inline] + pub fn reason(&self) -> &str { + if let Some(reason) = self.get_ref().head.reason { + reason + } else { + self.get_ref() + .head + .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.get_mut().head.reason = Some(reason); + self + } + /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { @@ -167,39 +207,6 @@ impl Response { count } - /// Get the response status code - #[inline] - pub fn status(&self) -> StatusCode { - self.get_ref().head.status - } - - /// Set the `StatusCode` for this response - #[inline] - pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.get_mut().head.status - } - - /// Get custom reason for the response - #[inline] - pub fn reason(&self) -> &str { - if let Some(reason) = self.get_ref().head.reason { - reason - } else { - self.get_ref() - .head - .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.get_mut().head.reason = Some(reason); - self - } - /// Set connection type pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self { self.get_mut().connection_type = Some(conn); @@ -224,38 +231,20 @@ impl Response { } } - /// is chunked encoding enabled - #[inline] - pub fn chunked(&self) -> Option { - self.get_ref().chunked - } - - /// Content encoding - #[inline] - pub fn content_encoding(&self) -> Option { - self.get_ref().encoding - } - - /// Set content encoding - pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - self.get_mut().encoding = Some(enc); - self - } - /// Get body os this response #[inline] - pub fn body(&self) -> &Body { - &self.get_ref().body + pub fn body(&self) -> &B { + &self.1 } /// Set a body - pub fn set_body>(&mut self, body: B) { - self.get_mut().body = body.into(); + pub fn set_body(self, body: B2) -> Response { + Response(self.0, body) } /// Set a body and return previous body value - pub fn replace_body>(&mut self, body: B) -> Body { - mem::replace(&mut self.get_mut().body, body.into()) + pub fn replace_body(self, body: B2) -> (Response, B) { + (Response(self.0, body), self.1) } /// Size of response in bytes, excluding HTTP headers @@ -268,16 +257,6 @@ impl Response { self.get_mut().response_size = size; } - /// Set write buffer capacity - pub fn write_buffer_capacity(&self) -> usize { - self.get_ref().write_capacity - } - - /// Set write buffer capacity - pub fn set_write_buffer_capacity(&mut self, cap: usize) { - self.get_mut().write_capacity = cap; - } - pub(crate) fn release(self) { ResponsePool::release(self.0); } @@ -287,7 +266,7 @@ impl Response { } pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response(Box::new(InnerResponse::from_parts(parts))) + Response(Box::new(InnerResponse::from_parts(parts)), ()) } } @@ -454,24 +433,6 @@ impl ResponseBuilder { self.connection_type(ConnectionType::Close) } - /// Enables automatic chunked transfer encoding - #[inline] - pub fn chunked(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.chunked = Some(true); - } - self - } - - /// Force disable chunked encoding - #[inline] - pub fn no_chunking(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.chunked = Some(false); - } - self - } - /// Set response content type #[inline] pub fn content_type(&mut self, value: V) -> &mut Self @@ -580,63 +541,73 @@ impl ResponseBuilder { self } - /// Set write buffer capacity - /// - /// This parameter makes sense only for streaming response - /// or actor. If write buffer reaches specified capacity, stream or actor - /// get paused. - /// - /// Default write buffer capacity is 64kb - pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.write_capacity = cap; - } - self - } + // /// Set write buffer capacity + // /// + // /// This parameter makes sense only for streaming response + // /// or actor. If write buffer reaches specified capacity, stream or actor + // /// get paused. + // /// + // /// Default write buffer capacity is 64kb + // pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { + // if let Some(parts) = parts(&mut self.response, &self.err) { + // parts.write_capacity = cap; + // } + // self + // } /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> Response { - if let Some(e) = self.err.take() { - return Error::from(e).into(); - } + pub fn body(&mut self, body: B) -> Response { + let mut error = if let Some(e) = self.err.take() { + Some(Error::from(e)) + } else { + None + }; + let mut response = self.response.take().expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => response.head.headers.append(header::SET_COOKIE, val), - Err(e) => return Error::from(e).into(), + Ok(val) => { + let _ = response.head.headers.append(header::SET_COOKIE, val); + } + Err(e) => if error.is_none() { + error = Some(Error::from(e)); + }, }; } } - response.body = body.into(); - Response(response) + if let Some(error) = error { + response.error = Some(error); + } + + Response(response, body) } #[inline] /// Set a streaming body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> Response + pub fn streaming(&mut self, stream: S) -> Response where S: Stream + 'static, E: Into, { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) + self.body(MessageBodyStream::new(stream.map_err(|e| e.into()))) } /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response { + pub fn json(&mut self, value: T) -> Response { self.json2(&value) } /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> Response { + pub fn json2(&mut self, value: &T) -> Response { match serde_json::to_string(value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) @@ -651,7 +622,10 @@ impl ResponseBuilder { self.body(body) } - Err(e) => Error::from(e).into(), + Err(e) => { + let mut res: Response = Error::from(e).into(); + res.replace_body(String::new()).0 + } } } @@ -659,8 +633,8 @@ impl ResponseBuilder { /// Set an empty body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn finish(&mut self) -> Response { - self.body(Body::Empty) + pub fn finish(&mut self) -> Response<()> { + self.body(()) } /// This method construct new `ResponseBuilder` @@ -701,7 +675,7 @@ impl From for Response { } } -impl From<&'static str> for Response { +impl From<&'static str> for Response<&'static str> { fn from(val: &'static str) -> Self { Response::Ok() .content_type("text/plain; charset=utf-8") @@ -709,7 +683,7 @@ impl From<&'static str> for Response { } } -impl From<&'static [u8]> for Response { +impl From<&'static [u8]> for Response<&'static [u8]> { fn from(val: &'static [u8]) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -717,7 +691,7 @@ impl From<&'static [u8]> for Response { } } -impl From for Response { +impl From for Response { fn from(val: String) -> Self { Response::Ok() .content_type("text/plain; charset=utf-8") @@ -725,15 +699,7 @@ impl From for Response { } } -impl<'a> From<&'a String> for Response { - fn from(val: &'a String) -> Self { - Response::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(val) - } -} - -impl From for Response { +impl From for Response { fn from(val: Bytes) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -741,7 +707,7 @@ impl From for Response { } } -impl From for Response { +impl From for Response { fn from(val: BytesMut) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -751,8 +717,6 @@ impl From for Response { struct InnerResponse { head: ResponseHead, - body: Body, - chunked: Option, encoding: Option, connection_type: Option, write_capacity: usize, @@ -763,7 +727,6 @@ struct InnerResponse { pub(crate) struct ResponseParts { head: ResponseHead, - body: Option, encoding: Option, connection_type: Option, error: Option, @@ -771,11 +734,7 @@ pub(crate) struct ResponseParts { impl InnerResponse { #[inline] - fn new( - status: StatusCode, - body: Body, - pool: &'static ResponsePool, - ) -> InnerResponse { + fn new(status: StatusCode, pool: &'static ResponsePool) -> InnerResponse { InnerResponse { head: ResponseHead { status, @@ -784,9 +743,7 @@ impl InnerResponse { reason: None, flags: MessageFlags::empty(), }, - body, pool, - chunked: None, encoding: None, connection_type: None, response_size: 0, @@ -796,18 +753,8 @@ impl InnerResponse { } /// This is for failure, we can not have Send + Sync on Streaming and Actor response - fn into_parts(mut self) -> ResponseParts { - let body = match mem::replace(&mut self.body, Body::Empty) { - Body::Empty => None, - Body::Binary(mut bin) => Some(bin.take()), - Body::Streaming(_) => { - error!("Streaming or Actor body is not support by error response"); - None - } - }; - + fn into_parts(self) -> ResponseParts { ResponseParts { - body, head: self.head, encoding: self.encoding, connection_type: self.connection_type, @@ -816,16 +763,8 @@ impl InnerResponse { } fn from_parts(parts: ResponseParts) -> InnerResponse { - let body = if let Some(ref body) = parts.body { - Body::Binary(body.clone().into()) - } else { - Body::Empty - }; - InnerResponse { - body, head: parts.head, - chunked: None, encoding: parts.encoding, connection_type: parts.connection_type, response_size: 0, @@ -864,7 +803,7 @@ impl ResponsePool { cookies: None, } } else { - let msg = Box::new(InnerResponse::new(status, Body::Empty, pool)); + let msg = Box::new(InnerResponse::new(status, pool)); ResponseBuilder { response: Some(msg), err: None, @@ -874,17 +813,16 @@ impl ResponsePool { } #[inline] - pub fn get_response( + pub fn get_response( pool: &'static ResponsePool, status: StatusCode, - body: Body, - ) -> Response { + body: B, + ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.head.status = status; - msg.body = body; - Response(msg) + Response(msg, body) } else { - Response(Box::new(InnerResponse::new(status, body, pool))) + Response(Box::new(InnerResponse::new(status, pool)), body) } } @@ -894,7 +832,7 @@ impl ResponsePool { } #[inline] - fn with_body(status: StatusCode, body: Body) -> Response { + fn with_body(status: StatusCode, body: B) -> Response { POOL.with(|pool| ResponsePool::get_response(pool, status, body)) } @@ -903,7 +841,6 @@ impl ResponsePool { let mut p = inner.pool.0.borrow_mut(); if p.len() < 128 { inner.head.clear(); - inner.chunked = None; inner.encoding = None; inner.connection_type = None; inner.response_size = 0; diff --git a/src/service.rs b/src/service.rs index 3aa5d1e4..ac92a0f7 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,7 +6,7 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, AsyncSink, Future, Poll, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; -use body::Body; +use body::MessageBody; use error::{Error, ResponseError}; use h1::{Codec, Message}; use response::Response; @@ -58,11 +58,11 @@ where match req { Ok(r) => Either::A(ok(r)), Err((e, framed)) => { - let mut resp = e.error_response(); - resp.set_body(format!("{}", e)); + let mut res = e.error_response().set_body(format!("{}", e)); + let (res, _body) = res.replace_body(()); Either::B(SendErrorFut { framed: Some(framed), - res: Some(resp.into()), + res: Some(res.into()), err: Some(e), _t: PhantomData, }) @@ -109,30 +109,30 @@ where } } -pub struct SendResponse(PhantomData<(T,)>); +pub struct SendResponse(PhantomData<(T, B)>); -impl Default for SendResponse -where - T: AsyncRead + AsyncWrite, -{ +impl Default for SendResponse { fn default() -> Self { SendResponse(PhantomData) } } -impl SendResponse +impl SendResponse where T: AsyncRead + AsyncWrite, + B: MessageBody, { pub fn send( mut framed: Framed, - mut res: Response, + res: Response, ) -> impl Future, Error = Error> { - // init codec - framed.get_codec_mut().prepare_te(&mut res); - // extract body from response - let body = res.replace_body(Body::Empty); + let (mut res, body) = res.replace_body(()); + + // init codec + framed + .get_codec_mut() + .prepare_te(&mut res.head_mut(), &mut body.length()); // write response SendResponseFut { @@ -143,15 +143,16 @@ where } } -impl NewService for SendResponse +impl NewService for SendResponse where T: AsyncRead + AsyncWrite, + B: MessageBody, { - type Request = (Response, Framed); + type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); - type Service = SendResponse; + type Service = SendResponse; type Future = FutureResult; fn new_service(&self) -> Self::Future { @@ -159,22 +160,25 @@ where } } -impl Service for SendResponse +impl Service for SendResponse where T: AsyncRead + AsyncWrite, + B: MessageBody, { - type Request = (Response, Framed); + type Request = (Response, Framed); type Response = Framed; type Error = Error; - type Future = SendResponseFut; + type Future = SendResponseFut; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } - fn call(&mut self, (mut res, mut framed): Self::Request) -> Self::Future { - framed.get_codec_mut().prepare_te(&mut res); - let body = res.replace_body(Body::Empty); + fn call(&mut self, (res, mut framed): Self::Request) -> Self::Future { + let (mut res, body) = res.replace_body(()); + framed + .get_codec_mut() + .prepare_te(res.head_mut(), &mut body.length()); SendResponseFut { res: Some(Message::Item(res)), body: Some(body), @@ -183,73 +187,69 @@ where } } -pub struct SendResponseFut { +pub struct SendResponseFut { res: Option>, - body: Option, + body: Option, framed: Option>, } -impl Future for SendResponseFut +impl Future for SendResponseFut where T: AsyncRead + AsyncWrite, + B: MessageBody, { type Item = Framed; type Error = Error; fn poll(&mut self) -> Poll { - // send response - if self.res.is_some() { + loop { + let mut body_ready = self.body.is_some(); let framed = self.framed.as_mut().unwrap(); - if !framed.is_write_buf_full() { - if let Some(res) = self.res.take() { - framed.force_send(res)?; - } - } - } - // send body - if self.res.is_none() && self.body.is_some() { - let framed = self.framed.as_mut().unwrap(); - if !framed.is_write_buf_full() { - let body = self.body.take().unwrap(); - match body { - Body::Empty => (), - Body::Streaming(mut stream) => loop { - match stream.poll()? { - Async::Ready(item) => { - let done = item.is_none(); - framed.force_send(Message::Chunk(item.into()))?; - if !done { - if !framed.is_write_buf_full() { - continue; - } else { - self.body = Some(Body::Streaming(stream)); - break; - } - } - } - Async::NotReady => { - self.body = Some(Body::Streaming(stream)); - break; + // send body + if self.res.is_none() && self.body.is_some() { + while body_ready && self.body.is_some() && !framed.is_write_buf_full() { + match self.body.as_mut().unwrap().poll_next()? { + Async::Ready(item) => { + // body is done + if item.is_none() { + let _ = self.body.take(); } + framed.force_send(Message::Chunk(item))?; } - }, - Body::Binary(mut bin) => { - framed.force_send(Message::Chunk(Some(bin.take())))?; - framed.force_send(Message::Chunk(None))?; + Async::NotReady => body_ready = false, } } } - } - // flush - match self.framed.as_mut().unwrap().poll_complete()? { - Async::Ready(_) => if self.res.is_some() || self.body.is_some() { - return self.poll(); - }, - Async::NotReady => return Ok(Async::NotReady), - } + // flush write buffer + if !framed.is_write_buf_empty() { + match framed.poll_complete()? { + Async::Ready(_) => if body_ready { + continue; + } else { + return Ok(Async::NotReady); + }, + Async::NotReady => return Ok(Async::NotReady), + } + } - Ok(Async::Ready(self.framed.take().unwrap())) + // send response + if let Some(res) = self.res.take() { + framed.force_send(res)?; + continue; + } + + if self.body.is_some() { + if body_ready { + continue; + } else { + return Ok(Async::NotReady); + } + } else { + break; + } + } + return Ok(Async::Ready(self.framed.take().unwrap())); } } From 8fea1367c739224f6365185a6ec6b1c4efdd5a8f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 13:48:42 -0800 Subject: [PATCH 0819/1635] re-introduce Body type, use Body as default body type for Response --- src/body.rs | 371 ++++++++++++++++++++--------------------- src/client/pipeline.rs | 4 +- src/client/request.rs | 9 +- src/error.rs | 5 +- src/h1/client.rs | 6 +- src/h1/codec.rs | 10 +- src/h1/dispatcher.rs | 18 +- src/h1/encoder.rs | 4 +- src/lib.rs | 5 +- src/response.rs | 149 ++++++++--------- src/service.rs | 4 +- src/test.rs | 7 +- src/ws/codec.rs | 5 +- src/ws/frame.rs | 5 +- tests/test_server.rs | 22 +-- tests/test_ws.rs | 20 +-- 16 files changed, 309 insertions(+), 335 deletions(-) diff --git a/src/body.rs b/src/body.rs index 3b4e0113..a44bc603 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,22 +1,19 @@ -use std::mem; -use std::sync::Arc; +use std::marker::PhantomData; +use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; use error::{Error, PayloadError}; -/// Type represent streaming body -pub type BodyStream = Box>; - /// Type represent streaming payload pub type PayloadStream = Box>; -#[derive(Debug)] +#[derive(Debug, PartialEq)] /// Different type of body pub enum BodyLength { None, - Zero, + Empty, Sized(usize), Sized64(u64), Chunked, @@ -32,7 +29,7 @@ pub trait MessageBody { impl MessageBody for () { fn length(&self) -> BodyLength { - BodyLength::Zero + BodyLength::Empty } fn poll_next(&mut self) -> Poll, Error> { @@ -40,150 +37,129 @@ impl MessageBody for () { } } -/// Represents various types of binary body. -/// `Content-Length` header is set to length of the body. -#[derive(Debug, PartialEq)] -pub enum Binary { - /// Bytes body +/// Represents various types of http message body. +pub enum Body { + /// Empty response. `Content-Length` header is not set. + None, + /// Zero sized response body. `Content-Length` header is set to `0`. + Empty, + /// Specific response body. Bytes(Bytes), - /// Static slice - Slice(&'static [u8]), - /// Shared string body - #[doc(hidden)] - SharedString(Arc), - /// Shared vec body - SharedVec(Arc>), + /// Generic message body. + Message(Box), } -impl Binary { - #[inline] - /// Returns `true` if body is empty - pub fn is_empty(&self) -> bool { - self.len() == 0 +impl Body { + /// Create body from slice (copy) + pub fn from_slice(s: &[u8]) -> Body { + Body::Bytes(Bytes::from(s)) } - #[inline] - /// Length of body in bytes - pub fn len(&self) -> usize { - match *self { - Binary::Bytes(ref bytes) => bytes.len(), - Binary::Slice(slice) => slice.len(), - Binary::SharedString(ref s) => s.len(), - Binary::SharedVec(ref s) => s.len(), - } - } - - /// Create binary body from slice - pub fn from_slice(s: &[u8]) -> Binary { - Binary::Bytes(Bytes::from(s)) - } - - /// Convert Binary to a Bytes instance - pub fn take(&mut self) -> Bytes { - mem::replace(self, Binary::Slice(b"")).into() + /// Create body from generic message body. + pub fn from_message(body: B) -> Body { + Body::Message(Box::new(body)) } } -impl Clone for Binary { - fn clone(&self) -> Binary { - match *self { - Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()), - Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)), - Binary::SharedString(ref s) => Binary::SharedString(s.clone()), - Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()), - } - } -} - -impl Into for Binary { - fn into(self) -> Bytes { +impl MessageBody for Body { + fn length(&self) -> BodyLength { match self { - Binary::Bytes(bytes) => bytes, - Binary::Slice(slice) => Bytes::from(slice), - Binary::SharedString(s) => Bytes::from(s.as_str()), - Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())), + Body::None => BodyLength::None, + Body::Empty => BodyLength::Empty, + Body::Bytes(ref bin) => BodyLength::Sized(bin.len()), + Body::Message(ref body) => body.length(), + } + } + + fn poll_next(&mut self) -> Poll, Error> { + match self { + Body::None => Ok(Async::Ready(None)), + Body::Empty => Ok(Async::Ready(None)), + Body::Bytes(ref mut bin) => { + if bin.len() == 0 { + Ok(Async::Ready(None)) + } else { + Ok(Async::Ready(Some(bin.slice_to(bin.len())))) + } + } + Body::Message(ref mut body) => body.poll_next(), } } } -impl From<&'static str> for Binary { - fn from(s: &'static str) -> Binary { - Binary::Slice(s.as_ref()) - } -} - -impl From<&'static [u8]> for Binary { - fn from(s: &'static [u8]) -> Binary { - Binary::Slice(s) - } -} - -impl From> for Binary { - fn from(vec: Vec) -> Binary { - Binary::Bytes(Bytes::from(vec)) - } -} - -impl From for Binary { - fn from(s: String) -> Binary { - Binary::Bytes(Bytes::from(s)) - } -} - -impl<'a> From<&'a String> for Binary { - fn from(s: &'a String) -> Binary { - Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) - } -} - -impl From for Binary { - fn from(s: Bytes) -> Binary { - Binary::Bytes(s) - } -} - -impl From for Binary { - fn from(s: BytesMut) -> Binary { - Binary::Bytes(s.freeze()) - } -} - -impl From> for Binary { - fn from(body: Arc) -> Binary { - Binary::SharedString(body) - } -} - -impl<'a> From<&'a Arc> for Binary { - fn from(body: &'a Arc) -> Binary { - Binary::SharedString(Arc::clone(body)) - } -} - -impl From>> for Binary { - fn from(body: Arc>) -> Binary { - Binary::SharedVec(body) - } -} - -impl<'a> From<&'a Arc>> for Binary { - fn from(body: &'a Arc>) -> Binary { - Binary::SharedVec(Arc::clone(body)) - } -} - -impl AsRef<[u8]> for Binary { - #[inline] - fn as_ref(&self) -> &[u8] { +impl PartialEq for Body { + fn eq(&self, other: &Body) -> bool { match *self { - Binary::Bytes(ref bytes) => bytes.as_ref(), - Binary::Slice(slice) => slice, - Binary::SharedString(ref s) => s.as_bytes(), - Binary::SharedVec(ref s) => s.as_ref().as_ref(), + Body::None => match *other { + Body::None => true, + _ => false, + }, + Body::Empty => match *other { + Body::Empty => true, + _ => false, + }, + Body::Bytes(ref b) => match *other { + Body::Bytes(ref b2) => b == b2, + _ => false, + }, + Body::Message(_) => false, } } } +impl fmt::Debug for Body { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Body::None => write!(f, "Body::None"), + Body::Empty => write!(f, "Body::Zero"), + Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), + Body::Message(_) => write!(f, "Body::Message(_)"), + } + } +} + +impl From<&'static str> for Body { + fn from(s: &'static str) -> Body { + Body::Bytes(Bytes::from_static(s.as_ref())) + } +} + +impl From<&'static [u8]> for Body { + fn from(s: &'static [u8]) -> Body { + Body::Bytes(Bytes::from_static(s.as_ref())) + } +} + +impl From> for Body { + fn from(vec: Vec) -> Body { + Body::Bytes(Bytes::from(vec)) + } +} + +impl From for Body { + fn from(s: String) -> Body { + s.into_bytes().into() + } +} + +impl<'a> From<&'a String> for Body { + fn from(s: &'a String) -> Body { + Body::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) + } +} + +impl From for Body { + fn from(s: Bytes) -> Body { + Body::Bytes(s) + } +} + +impl From for Body { + fn from(s: BytesMut) -> Body { + Body::Bytes(s.freeze()) + } +} + impl MessageBody for Bytes { fn length(&self) -> BodyLength { BodyLength::Sized(self.len()) @@ -279,26 +255,62 @@ impl MessageBody for String { } } -#[doc(hidden)] -pub struct MessageBodyStream { +/// Type represent streaming body. +/// Response does not contain `content-length` header and appropriate transfer encoding is used. +pub struct BodyStream { stream: S, + _t: PhantomData, } -impl MessageBodyStream +impl BodyStream where - S: Stream, + S: Stream, + E: Into, { pub fn new(stream: S) -> Self { - MessageBodyStream { stream } + BodyStream { + stream, + _t: PhantomData, + } } } -impl MessageBody for MessageBodyStream +impl MessageBody for BodyStream +where + S: Stream, + E: Into, +{ + fn length(&self) -> BodyLength { + BodyLength::Chunked + } + + fn poll_next(&mut self) -> Poll, Error> { + self.stream.poll().map_err(|e| e.into()) + } +} + +/// Type represent streaming body. This body implementation should be used +/// if total size of stream is known. Data get sent as is without using transfer encoding. +pub struct SizedStream { + size: usize, + stream: S, +} + +impl SizedStream +where + S: Stream, +{ + pub fn new(size: usize, stream: S) -> Self { + SizedStream { size, stream } + } +} + +impl MessageBody for SizedStream where S: Stream, { fn length(&self) -> BodyLength { - BodyLength::Chunked + BodyLength::Sized(self.size) } fn poll_next(&mut self) -> Poll, Error> { @@ -310,78 +322,61 @@ where mod tests { use super::*; - #[test] - fn test_is_empty() { - assert_eq!(Binary::from("").is_empty(), true); - assert_eq!(Binary::from("test").is_empty(), false); + impl Body { + pub(crate) fn get_ref(&self) -> &[u8] { + match *self { + Body::Bytes(ref bin) => &bin, + _ => panic!(), + } + } } #[test] fn test_static_str() { - assert_eq!(Binary::from("test").len(), 4); - assert_eq!(Binary::from("test").as_ref(), b"test"); + assert_eq!(Body::from("").length(), BodyLength::Sized(0)); + assert_eq!(Body::from("test").length(), BodyLength::Sized(4)); + assert_eq!(Body::from("test").get_ref(), b"test"); } #[test] fn test_static_bytes() { - assert_eq!(Binary::from(b"test".as_ref()).len(), 4); - assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test"); - assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4); - assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test"); + assert_eq!(Body::from(b"test".as_ref()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); + assert_eq!( + Body::from_slice(b"test".as_ref()).length(), + BodyLength::Sized(4) + ); + assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); } #[test] fn test_vec() { - assert_eq!(Binary::from(Vec::from("test")).len(), 4); - assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test"); + assert_eq!(Body::from(Vec::from("test")).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); } #[test] fn test_bytes() { - assert_eq!(Binary::from(Bytes::from("test")).len(), 4); - assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test"); - } - - #[test] - fn test_arc_string() { - let b = Arc::new("test".to_owned()); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), b"test"); + assert_eq!( + Body::from(Bytes::from("test")).length(), + BodyLength::Sized(4) + ); + assert_eq!(Body::from(Bytes::from("test")).get_ref(), b"test"); } #[test] fn test_string() { let b = "test".to_owned(); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), b"test"); - } - - #[test] - fn test_shared_vec() { - let b = Arc::new(Vec::from(&b"test"[..])); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]); + assert_eq!(Body::from(b.clone()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + assert_eq!(Body::from(&b).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(&b).get_ref(), b"test"); } #[test] fn test_bytes_mut() { let b = BytesMut::from("test"); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b).as_ref(), b"test"); - } - - #[test] - fn test_binary_into() { - let bytes = Bytes::from_static(b"test"); - let b: Bytes = Binary::from("test").into(); - assert_eq!(b, bytes); - let b: Bytes = Binary::from(bytes.clone()).into(); - assert_eq!(b, bytes); + assert_eq!(Body::from(b.clone()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b).get_ref(), b"test"); } } diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 56c22bd2..8be860ae 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -36,7 +36,9 @@ where .and_then(|framed| framed.send((head, len).into()).from_err()) // send request body .and_then(move |framed| match body.length() { - BodyLength::None | BodyLength::Zero => Either::A(ok(framed)), + BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => { + Either::A(ok(framed)) + } _ => Either::B(SendBody::new(body, framed)), }) // read response and init read body diff --git a/src/client/request.rs b/src/client/request.rs index f0b76ed0..dd418a6f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -9,7 +9,7 @@ use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use urlcrate::Url; -use body::{MessageBody, MessageBodyStream}; +use body::{BodyStream, MessageBody}; use error::Error; use header::{self, Header, IntoHeaderValue}; use http::{ @@ -534,14 +534,15 @@ impl ClientRequestBuilder { /// Set an streaming body and generate `ClientRequest`. /// /// `ClientRequestBuilder` can not be used after this call. - pub fn stream( + pub fn stream( &mut self, stream: S, ) -> Result, HttpError> where - S: Stream, + S: Stream, + E: Into + 'static, { - self.body(MessageBodyStream::new(stream)) + self.body(BodyStream::new(stream)) } /// Set an empty body and generate `ClientRequest`. diff --git a/src/error.rs b/src/error.rs index 1f70396c..2e0c2382 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,7 @@ use tokio_timer::Error as TimerError; // re-exports pub use cookie::ParseError as CookieParseError; +use body::Body; use response::{Response, ResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) @@ -100,10 +101,10 @@ impl Error { } /// Converts error to a response instance and set error message as response body - pub fn response_with_message(self) -> Response { + pub fn response_with_message(self) -> Response { let message = format!("{}", self); let resp: Response = self.into(); - resp.set_body(message) + resp.set_body(Body::from(message)) } } diff --git a/src/h1/client.rs b/src/h1/client.rs index ace50466..2cb2fb2e 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -7,7 +7,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::RequestEncoder; use super::{Message, MessageType}; -use body::{Binary, BodyLength}; +use body::BodyLength; use client::ClientResponse; use config::ServiceConfig; use error::{ParseError, PayloadError}; @@ -167,7 +167,7 @@ impl ClientCodecInner { BodyLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - BodyLength::Zero => { + BodyLength::Empty => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } @@ -183,7 +183,7 @@ impl ClientCodecInner { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match length { BodyLength::None => (), - BodyLength::Zero => len_is_set = true, + BodyLength::Empty => len_is_set = true, _ => continue, }, DATE => has_date = true, diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 6bc20b18..b4a62a50 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -8,7 +8,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; use super::encoder::ResponseEncoder; use super::{Message, MessageType}; -use body::{Binary, BodyLength}; +use body::BodyLength; use config::ServiceConfig; use error::ParseError; use helpers; @@ -106,7 +106,7 @@ impl Codec { fn encode_response( &mut self, - mut msg: Response, + mut msg: Response<()>, buffer: &mut BytesMut, ) -> io::Result<()> { let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg @@ -149,7 +149,7 @@ impl Codec { BodyLength::Chunked => { buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } - BodyLength::Zero => { + BodyLength::Empty => { len_is_set = false; buffer.extend_from_slice(b"\r\n") } @@ -174,7 +174,7 @@ impl Codec { TRANSFER_ENCODING => continue, CONTENT_LENGTH => match self.te.length { BodyLength::None => (), - BodyLength::Zero => { + BodyLength::Empty => { len_is_set = true; } _ => continue, @@ -268,7 +268,7 @@ impl Decoder for Codec { } impl Encoder for Codec { - type Item = Message; + type Item = Message>; type Error = io::Error; fn encode( diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 508962b4..ff9d54e6 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -65,7 +65,7 @@ where enum DispatcherMessage { Item(Request), - Error(Response), + Error(Response<()>), } enum State { @@ -190,7 +190,7 @@ where fn send_response( &mut self, - message: Response, + message: Response<()>, body: B1, ) -> Result, DispatchError> { self.framed @@ -206,7 +206,7 @@ where .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); self.flags.remove(Flags::FLUSHED); match body.length() { - BodyLength::None | BodyLength::Zero => Ok(State::None), + BodyLength::None | BodyLength::Empty => Ok(State::None), _ => Ok(State::SendPayload(body)), } } @@ -351,7 +351,7 @@ where ); self.flags.insert(Flags::DISCONNECTED); self.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish(), + Response::InternalServerError().finish().drop_body(), )); self.error = Some(DispatchError::InternalError); break; @@ -364,7 +364,7 @@ where error!("Internal server error: unexpected eof"); self.flags.insert(Flags::DISCONNECTED); self.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish(), + Response::InternalServerError().finish().drop_body(), )); self.error = Some(DispatchError::InternalError); break; @@ -389,7 +389,7 @@ where // Malformed requests should be responded with 400 self.messages.push_back(DispatcherMessage::Error( - Response::BadRequest().finish(), + Response::BadRequest().finish().drop_body(), )); self.flags.insert(Flags::DISCONNECTED); self.error = Some(e.into()); @@ -440,8 +440,10 @@ where // timeout on first request (slow request) return 408 trace!("Slow request timeout"); self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - let _ = self - .send_response(Response::RequestTimeout().finish(), ()); + let _ = self.send_response( + Response::RequestTimeout().finish().drop_body(), + (), + ); self.state = State::None; } } else if let Some(deadline) = self.config.keep_alive_expire() { diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index aee17c1f..fd52d731 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -8,7 +8,7 @@ use bytes::{Bytes, BytesMut}; use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; use http::{StatusCode, Version}; -use body::{Binary, BodyLength}; +use body::BodyLength; use header::ContentEncoding; use http::Method; use message::{RequestHead, ResponseHead}; @@ -52,7 +52,7 @@ impl ResponseEncoder { ) { self.head = head; let transfer = match length { - BodyLength::Zero => { + BodyLength::Empty => { match resp.status { StatusCode::NO_CONTENT | StatusCode::CONTINUE diff --git a/src/lib.rs b/src/lib.rs index 4b43cae4..615f0401 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,7 @@ extern crate serde_derive; #[cfg(feature = "ssl")] extern crate openssl; -mod body; +pub mod body; pub mod client; mod config; mod extensions; @@ -129,7 +129,7 @@ pub mod h1; pub(crate) mod helpers; pub mod test; pub mod ws; -pub use body::{Binary, MessageBody}; +pub use body::{Body, MessageBody}; pub use error::{Error, ResponseError, Result}; pub use extensions::Extensions; pub use httpmessage::HttpMessage; @@ -150,7 +150,6 @@ pub mod dev { //! use actix_http::dev::*; //! ``` - pub use body::BodyStream; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use json::JsonBody; pub use payload::{Payload, PayloadBuffer}; diff --git a/src/response.rs b/src/response.rs index b3e59982..a6f7c81c 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,9 +12,9 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use body::{MessageBody, MessageBodyStream}; +use body::{Body, BodyStream, MessageBody}; use error::Error; -use header::{ContentEncoding, Header, IntoHeaderValue}; +use header::{Header, IntoHeaderValue}; use message::{Head, MessageFlags, ResponseHead}; /// max write buffer size 64k @@ -32,9 +32,9 @@ pub enum ConnectionType { } /// An HTTP Response -pub struct Response(Box, B); +pub struct Response(Box, B); -impl Response<()> { +impl Response { /// Create http response builder with specific status. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { @@ -50,7 +50,7 @@ impl Response<()> { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - ResponsePool::with_body(status, ()) + ResponsePool::with_body(status, Body::Empty) } /// Constructs an error response @@ -242,6 +242,11 @@ impl Response { Response(self.0, body) } + /// Drop request's body + pub fn drop_body(self) -> Response<()> { + Response(self.0, ()) + } + /// Set a body and return previous body value pub fn replace_body(self, body: B2) -> (Response, B) { (Response(self.0, body), self.1) @@ -252,7 +257,7 @@ impl Response { self.get_ref().response_size } - /// Set content encoding + /// Set response size pub(crate) fn set_response_size(&mut self, size: u64) { self.get_mut().response_size = size; } @@ -266,7 +271,7 @@ impl Response { } pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response(Box::new(InnerResponse::from_parts(parts)), ()) + Response(Box::new(InnerResponse::from_parts(parts)), Body::Empty) } } @@ -279,7 +284,6 @@ impl fmt::Debug for Response { self.get_ref().head.status, self.get_ref().head.reason.unwrap_or("") ); - let _ = writeln!(f, " encoding: {:?}", self.get_ref().encoding); let _ = writeln!(f, " headers:"); for (key, val) in self.get_ref().head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); @@ -396,20 +400,6 @@ impl ResponseBuilder { self } - /// Set content encoding. - /// - /// 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.response, &self.err) { - parts.encoding = Some(enc); - } - self - } - /// Set connection type #[inline] #[doc(hidden)] @@ -558,7 +548,14 @@ impl ResponseBuilder { /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn body(&mut self, body: B) -> Response { + pub fn body>(&mut self, body: B) -> Response { + self.message_body(body.into()) + } + + /// Set a body and generate `Response`. + /// + /// `ResponseBuilder` can not be used after this call. + pub fn message_body(&mut self, body: B) -> Response { let mut error = if let Some(e) = self.err.take() { Some(Error::from(e)) } else { @@ -589,25 +586,25 @@ impl ResponseBuilder { /// Set a streaming body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> Response + pub fn streaming(&mut self, stream: S) -> Response where S: Stream + 'static, - E: Into, + E: Into + 'static, { - self.body(MessageBodyStream::new(stream.map_err(|e| e.into()))) + self.body(Body::from_message(BodyStream::new(stream))) } /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response { + pub fn json(&mut self, value: T) -> Response { self.json2(&value) } /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> Response { + pub fn json2(&mut self, value: &T) -> Response { match serde_json::to_string(value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.response, &self.err) @@ -620,12 +617,9 @@ impl ResponseBuilder { self.header(header::CONTENT_TYPE, "application/json"); } - self.body(body) - } - Err(e) => { - let mut res: Response = Error::from(e).into(); - res.replace_body(String::new()).0 + self.body(Body::from(body)) } + Err(e) => Error::from(e).into(), } } @@ -633,8 +627,8 @@ impl ResponseBuilder { /// Set an empty body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn finish(&mut self) -> Response<()> { - self.body(()) + pub fn finish(&mut self) -> Response { + self.body(Body::Empty) } /// This method construct new `ResponseBuilder` @@ -675,7 +669,7 @@ impl From for Response { } } -impl From<&'static str> for Response<&'static str> { +impl From<&'static str> for Response { fn from(val: &'static str) -> Self { Response::Ok() .content_type("text/plain; charset=utf-8") @@ -683,7 +677,7 @@ impl From<&'static str> for Response<&'static str> { } } -impl From<&'static [u8]> for Response<&'static [u8]> { +impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -691,7 +685,7 @@ impl From<&'static [u8]> for Response<&'static [u8]> { } } -impl From for Response { +impl From for Response { fn from(val: String) -> Self { Response::Ok() .content_type("text/plain; charset=utf-8") @@ -699,7 +693,15 @@ impl From for Response { } } -impl From for Response { +impl<'a> From<&'a String> for Response { + fn from(val: &'a String) -> Self { + Response::Ok() + .content_type("text/plain; charset=utf-8") + .body(val) + } +} + +impl From for Response { fn from(val: Bytes) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -707,7 +709,7 @@ impl From for Response { } } -impl From for Response { +impl From for Response { fn from(val: BytesMut) -> Self { Response::Ok() .content_type("application/octet-stream") @@ -717,7 +719,6 @@ impl From for Response { struct InnerResponse { head: ResponseHead, - encoding: Option, connection_type: Option, write_capacity: usize, response_size: u64, @@ -727,7 +728,6 @@ struct InnerResponse { pub(crate) struct ResponseParts { head: ResponseHead, - encoding: Option, connection_type: Option, error: Option, } @@ -744,7 +744,6 @@ impl InnerResponse { flags: MessageFlags::empty(), }, pool, - encoding: None, connection_type: None, response_size: 0, write_capacity: MAX_WRITE_BUFFER_SIZE, @@ -756,7 +755,6 @@ impl InnerResponse { fn into_parts(self) -> ResponseParts { ResponseParts { head: self.head, - encoding: self.encoding, connection_type: self.connection_type, error: self.error, } @@ -765,7 +763,6 @@ impl InnerResponse { fn from_parts(parts: ResponseParts) -> InnerResponse { InnerResponse { head: parts.head, - encoding: parts.encoding, connection_type: parts.connection_type, response_size: 0, write_capacity: MAX_WRITE_BUFFER_SIZE, @@ -841,7 +838,6 @@ impl ResponsePool { let mut p = inner.pool.0.borrow_mut(); if p.len() < 128 { inner.head.clear(); - inner.encoding = None; inner.connection_type = None; inner.response_size = 0; inner.error = None; @@ -854,11 +850,10 @@ impl ResponsePool { #[cfg(test)] mod tests { use super::*; - use body::Binary; + use body::Body; use http; use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use header::ContentEncoding; // use test::TestRequest; #[test] @@ -953,24 +948,24 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - #[test] - fn test_content_encoding() { - let resp = Response::build(StatusCode::OK).finish(); - assert_eq!(resp.content_encoding(), None); + // #[test] + // fn test_content_encoding() { + // let resp = Response::build(StatusCode::OK).finish(); + // assert_eq!(resp.content_encoding(), None); - #[cfg(feature = "brotli")] - { - let resp = Response::build(StatusCode::OK) - .content_encoding(ContentEncoding::Br) - .finish(); - assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); - } + // #[cfg(feature = "brotli")] + // { + // let resp = Response::build(StatusCode::OK) + // .content_encoding(ContentEncoding::Br) + // .finish(); + // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); + // } - let resp = Response::build(StatusCode::OK) - .content_encoding(ContentEncoding::Gzip) - .finish(); - assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); - } + // let resp = Response::build(StatusCode::OK) + // .content_encoding(ContentEncoding::Gzip) + // .finish(); + // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); + // } #[test] fn test_json() { @@ -1020,15 +1015,6 @@ mod tests { ); } - impl Body { - pub(crate) fn bin_ref(&self) -> &Binary { - match *self { - Body::Binary(ref bin) => bin, - _ => panic!(), - } - } - } - #[test] fn test_into_response() { let resp: Response = "test".into(); @@ -1038,7 +1024,7 @@ mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test")); + assert_eq!(resp.body().get_ref(), b"test"); let resp: Response = b"test".as_ref().into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1047,7 +1033,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); + assert_eq!(resp.body().get_ref(), b"test"); let resp: Response = "test".to_owned().into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1056,7 +1042,7 @@ mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); + assert_eq!(resp.body().get_ref(), b"test"); let resp: Response = (&"test".to_owned()).into(); assert_eq!(resp.status(), StatusCode::OK); @@ -1065,7 +1051,7 @@ mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); + assert_eq!(resp.body().get_ref(), b"test"); let b = Bytes::from_static(b"test"); let resp: Response = b.into(); @@ -1075,10 +1061,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().bin_ref(), - &Binary::from(Bytes::from_static(b"test")) - ); + assert_eq!(resp.body().get_ref(), b"test"); let b = Bytes::from_static(b"test"); let resp: Response = b.into(); @@ -1088,7 +1071,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); + assert_eq!(resp.body().get_ref(), b"test"); let b = BytesMut::from("test"); let resp: Response = b.into(); @@ -1098,7 +1081,7 @@ mod tests { HeaderValue::from_static("application/octet-stream") ); assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); + assert_eq!(resp.body().get_ref(), b"test"); } #[test] diff --git a/src/service.rs b/src/service.rs index ac92a0f7..51a16b48 100644 --- a/src/service.rs +++ b/src/service.rs @@ -72,7 +72,7 @@ where } pub struct SendErrorFut { - res: Option>, + res: Option>>, framed: Option>, err: Option, _t: PhantomData, @@ -188,7 +188,7 @@ where } pub struct SendResponseFut { - res: Option>, + res: Option>>, body: Option, framed: Option>, } diff --git a/src/test.rs b/src/test.rs index 43974934..c6f258a7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use actix::System; +use bytes::Bytes; use cookie::Cookie; use futures::Future; use http::header::HeaderName; @@ -11,7 +12,6 @@ use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; -use body::Binary; use header::{Header, IntoHeaderValue}; use payload::Payload; use request::Request; @@ -362,10 +362,9 @@ impl TestRequest { } /// Set request payload - pub fn set_payload>(mut self, data: B) -> Self { - let mut data = data.into(); + pub fn set_payload>(mut self, data: B) -> Self { let mut payload = Payload::empty(); - payload.unread_data(data.take()); + payload.unread_data(data.into()); self.payload = Some(payload); self } diff --git a/src/ws/codec.rs b/src/ws/codec.rs index 5bdc5819..10c50528 100644 --- a/src/ws/codec.rs +++ b/src/ws/codec.rs @@ -1,10 +1,9 @@ -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; use super::frame::Parser; use super::proto::{CloseReason, OpCode}; use super::ProtocolError; -use body::Binary; /// `WebSocket` Message #[derive(Debug, PartialEq)] @@ -12,7 +11,7 @@ pub enum Message { /// Text message Text(String), /// Binary message - Binary(Binary), + Binary(Bytes), /// Ping message Ping(String), /// Pong message diff --git a/src/ws/frame.rs b/src/ws/frame.rs index ca5f0e89..56d28296 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,8 +1,7 @@ use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; -use bytes::{BufMut, BytesMut}; +use bytes::{BufMut, Bytes, BytesMut}; use rand; -use body::Binary; use ws::mask::apply_mask; use ws::proto::{CloseCode, CloseReason, OpCode}; use ws::ProtocolError; @@ -151,7 +150,7 @@ impl Parser { } /// Generate binary representation - pub fn write_message>( + pub fn write_message>( dst: &mut BytesMut, pl: B, op: OpCode, diff --git a/tests/test_server.rs b/tests/test_server.rs index c8de0290..c499cf63 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use futures::future::{self, ok}; use futures::stream::once; -use actix_http::{h1, http, Body, KeepAlive, Request, Response}; +use actix_http::{body, h1, http, Body, Error, KeepAlive, Request, Response}; #[test] fn test_h1_v2() { @@ -349,11 +349,9 @@ fn test_body_length() { .bind("test", addr, move || { h1::H1Service::new(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .content_length(STR.len() as u64) - .body(Body::Streaming(Box::new(body))), - ) + ok::<_, ()>(Response::Ok().body(Body::from_message( + body::SizedStream::new(STR.len(), body), + ))) }).map(|_| ()) }).unwrap() .run() @@ -379,12 +377,8 @@ fn test_body_chunked_explicit() { Server::new() .bind("test", addr, move || { h1::H1Service::new(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .chunked() - .body(Body::Streaming(Box::new(body))), - ) + let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) }).map(|_| ()) }).unwrap() .run() @@ -412,8 +406,8 @@ fn test_body_chunked_implicit() { Server::new() .bind("test", addr, move || { h1::H1Service::new(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().body(Body::Streaming(Box::new(body)))) + let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) }).map(|_| ()) }).unwrap() .run() diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 85770fa8..c246d5e4 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -18,7 +18,7 @@ use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Either}; use futures::{Future, IntoFuture, Sink, Stream}; -use actix_http::{h1, ws, ResponseError, ServiceConfig}; +use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -55,20 +55,19 @@ fn test_simple() { match ws::verify_handshake(&req) { Err(e) => { // validation failed - let resp = e.error_response(); Either::A( - framed - .send(h1::Message::Item(resp)) + SendResponse::send(framed, e.error_response()) .map_err(|_| ()) .map(|_| ()), ) } - Ok(_) => Either::B( - // send response - framed - .send(h1::Message::Item( + Ok(_) => { + Either::B( + // send handshake response + SendResponse::send( + framed, ws::handshake_response(&req).finish(), - )).map_err(|_| ()) + ).map_err(|_| ()) .and_then(|framed| { // start websocket service let framed = @@ -76,7 +75,8 @@ fn test_simple() { ws::Transport::with(framed, ws_service) .map_err(|_| ()) }), - ), + ) + } } } else { panic!() From adad2033141499ee071e75cc3a74eba67b2bcd6a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 17:52:56 -0800 Subject: [PATCH 0820/1635] refactor encoder/decoder impl --- Cargo.toml | 4 +- src/body.rs | 7 ++-- src/client/pipeline.rs | 2 +- src/client/request.rs | 17 ++++++++- src/client/response.rs | 4 +- src/h1/client.rs | 45 ++++++++++------------ src/h1/codec.rs | 87 +++++++++++++++++++----------------------- src/h1/decoder.rs | 18 +++++++-- src/h1/dispatcher.rs | 35 +++++++---------- src/h1/encoder.rs | 17 ++------- src/lib.rs | 3 +- src/message.rs | 85 ++++++++++++++++++++++++++++++++++++++++- src/request.rs | 4 +- src/response.rs | 86 +++++++++-------------------------------- src/service.rs | 49 +++++++++--------------- src/ws/mod.rs | 4 +- tests/test_server.rs | 22 +++++++---- 17 files changed, 255 insertions(+), 234 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80d24595..2eef1c50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,9 +91,11 @@ native-tls = { version="0.2", optional = true } # openssl openssl = { version="0.10", optional = true } -#rustls +# rustls rustls = { version = "^0.14", optional = true } +backtrace="*" + [dev-dependencies] actix-web = "0.7" env_logger = "0.5" diff --git a/src/body.rs b/src/body.rs index a44bc603..3449448e 100644 --- a/src/body.rs +++ b/src/body.rs @@ -9,7 +9,7 @@ use error::{Error, PayloadError}; /// Type represent streaming payload pub type PayloadStream = Box>; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Copy, Clone)] /// Different type of body pub enum BodyLength { None, @@ -76,10 +76,11 @@ impl MessageBody for Body { Body::None => Ok(Async::Ready(None)), Body::Empty => Ok(Async::Ready(None)), Body::Bytes(ref mut bin) => { - if bin.len() == 0 { + let len = bin.len(); + if len == 0 { Ok(Async::Ready(None)) } else { - Ok(Async::Ready(Some(bin.slice_to(bin.len())))) + Ok(Async::Ready(Some(bin.split_to(len)))) } } Body::Message(ref mut body) => body.poll_next(), diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 8be860ae..63551cff 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -33,7 +33,7 @@ where .from_err() // create Framed and send reqest .map(|io| Framed::new(io, h1::ClientCodec::default())) - .and_then(|framed| framed.send((head, len).into()).from_err()) + .and_then(move |framed| framed.send((head, len).into()).from_err()) // send request body .and_then(move |framed| match body.length() { BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => { diff --git a/src/client/request.rs b/src/client/request.rs index dd418a6f..cc65b9db 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -16,7 +16,7 @@ use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use message::RequestHead; +use message::{Head, RequestHead}; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; @@ -365,8 +365,21 @@ impl ClientRequestBuilder { where V: IntoHeaderValue, { + { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_upgrade(); + } + } self.set_header(header::UPGRADE, value) - .set_header(header::CONNECTION, "upgrade") + } + + /// Close connection + #[inline] + pub fn close(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.force_close(); + } + self } /// Set request's content type diff --git a/src/client/response.rs b/src/client/response.rs index 41c18562..dc7b13c1 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -8,7 +8,7 @@ use http::{HeaderMap, StatusCode, Version}; use body::PayloadStream; use error::PayloadError; use httpmessage::HttpMessage; -use message::{MessageFlags, ResponseHead}; +use message::{Head, ResponseHead}; use super::pipeline::Payload; @@ -81,7 +81,7 @@ impl ClientResponse { /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { - self.head().flags.contains(MessageFlags::KEEPALIVE) + self.head().keep_alive() } } diff --git a/src/h1/client.rs b/src/h1/client.rs index 2cb2fb2e..e2d1eefe 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -16,7 +16,7 @@ use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use message::{MessagePool, RequestHead}; +use message::{Head, MessagePool, RequestHead}; bitflags! { struct Flags: u8 { @@ -135,7 +135,7 @@ fn prn_version(ver: Version) -> &'static str { } impl ClientCodecInner { - fn encode_response( + fn encode_request( &mut self, msg: RequestHead, length: BodyLength, @@ -146,7 +146,7 @@ impl ClientCodecInner { // status line write!( Writer(buffer), - "{} {} {}", + "{} {} {}\r\n", msg.method, msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), prn_version(msg.version) @@ -156,38 +156,26 @@ impl ClientCodecInner { buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE); // content length - let mut len_is_set = true; match length { BodyLength::Sized(len) => helpers::write_content_length(len, buffer), BodyLength::Sized64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); + buffer.extend_from_slice(b"content-length: "); write!(buffer.writer(), "{}", len)?; buffer.extend_from_slice(b"\r\n"); } BodyLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - BodyLength::Empty => { - len_is_set = false; - buffer.extend_from_slice(b"\r\n") - } - BodyLength::None | BodyLength::Stream => { - buffer.extend_from_slice(b"\r\n") + buffer.extend_from_slice(b"transfer-encoding: chunked\r\n") } + BodyLength::Empty => buffer.extend_from_slice(b"content-length: 0\r\n"), + BodyLength::None | BodyLength::Stream => (), } let mut has_date = false; for (key, value) in &msg.headers { match *key { - TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match length { - BodyLength::None => (), - BodyLength::Empty => len_is_set = true, - _ => continue, - }, + TRANSFER_ENCODING | CONNECTION | CONTENT_LENGTH => continue, DATE => has_date = true, - UPGRADE => self.flags.insert(Flags::UPGRADE), _ => (), } @@ -197,12 +185,19 @@ impl ClientCodecInner { buffer.put_slice(b"\r\n"); } - // set content length - if !len_is_set { - buffer.extend_from_slice(b"content-length: 0\r\n") + // Connection header + if msg.upgrade() { + self.flags.set(Flags::UPGRADE, msg.upgrade()); + buffer.extend_from_slice(b"connection: upgrade\r\n"); + } else if msg.keep_alive() { + if self.version < Version::HTTP_11 { + buffer.extend_from_slice(b"connection: keep-alive\r\n"); + } + } else if self.version >= Version::HTTP_11 { + buffer.extend_from_slice(b"connection: close\r\n"); } - // set date header + // Date header if !has_date { self.config.set_date(buffer); } else { @@ -276,7 +271,7 @@ impl Encoder for ClientCodec { ) -> Result<(), Self::Error> { match item { Message::Item((msg, btype)) => { - self.inner.encode_response(msg, btype, dst)?; + self.inner.encode_request(msg, btype, dst)?; } Message::Chunk(Some(bytes)) => { self.inner.te.encode(bytes.as_ref(), dst)?; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index b4a62a50..117c8cde 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -13,8 +13,8 @@ use config::ServiceConfig; use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::{Method, Version}; -use message::ResponseHead; +use http::{Method, StatusCode, Version}; +use message::{Head, ResponseHead}; use request::Request; use response::Response; @@ -99,69 +99,71 @@ impl Codec { } /// prepare transfer encoding - pub fn prepare_te(&mut self, head: &mut ResponseHead, length: &mut BodyLength) { + fn prepare_te(&mut self, head: &mut ResponseHead, length: BodyLength) { self.te .update(head, self.flags.contains(Flags::HEAD), self.version, length); } fn encode_response( &mut self, - mut msg: Response<()>, + msg: &mut ResponseHead, + length: BodyLength, buffer: &mut BytesMut, ) -> io::Result<()> { - let ka = self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg - .keep_alive() - .unwrap_or_else(|| self.flags.contains(Flags::KEEPALIVE)); + msg.version = self.version; // Connection upgrade if msg.upgrade() { self.flags.insert(Flags::UPGRADE); self.flags.remove(Flags::KEEPALIVE); - msg.headers_mut() + msg.headers .insert(CONNECTION, HeaderValue::from_static("upgrade")); } // keep-alive - else if ka { + else if self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg.keep_alive() { self.flags.insert(Flags::KEEPALIVE); if self.version < Version::HTTP_11 { - msg.headers_mut() + msg.headers .insert(CONNECTION, HeaderValue::from_static("keep-alive")); } } else if self.version >= Version::HTTP_11 { self.flags.remove(Flags::KEEPALIVE); - msg.headers_mut() + msg.headers .insert(CONNECTION, HeaderValue::from_static("close")); } // render message { let reason = msg.reason().as_bytes(); - buffer - .reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len()); + buffer.reserve(256 + msg.headers.len() * AVERAGE_HEADER_SIZE + reason.len()); // status line - helpers::write_status_line(self.version, msg.status().as_u16(), buffer); + helpers::write_status_line(self.version, msg.status.as_u16(), buffer); buffer.extend_from_slice(reason); // content length - let mut len_is_set = true; - match self.te.length { - BodyLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - BodyLength::Empty => { - len_is_set = false; - buffer.extend_from_slice(b"\r\n") - } - BodyLength::Sized(len) => helpers::write_content_length(len, buffer), - BodyLength::Sized64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - BodyLength::None | BodyLength::Stream => { - buffer.extend_from_slice(b"\r\n") - } + match msg.status { + StatusCode::NO_CONTENT + | StatusCode::CONTINUE + | StatusCode::SWITCHING_PROTOCOLS + | StatusCode::PROCESSING => buffer.extend_from_slice(b"\r\n"), + _ => match length { + BodyLength::Chunked => { + buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } + BodyLength::Empty => { + buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"); + } + BodyLength::Sized(len) => helpers::write_content_length(len, buffer), + BodyLength::Sized64(len) => { + buffer.extend_from_slice(b"\r\ncontent-length: "); + write!(buffer.writer(), "{}", len)?; + buffer.extend_from_slice(b"\r\n"); + } + BodyLength::None | BodyLength::Stream => { + buffer.extend_from_slice(b"\r\n") + } + }, } // write headers @@ -169,16 +171,9 @@ impl Codec { let mut has_date = false; let mut remaining = buffer.remaining_mut(); let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in msg.headers() { + for (key, value) in &msg.headers { match *key { - TRANSFER_ENCODING => continue, - CONTENT_LENGTH => match self.te.length { - BodyLength::None => (), - BodyLength::Empty => { - len_is_set = true; - } - _ => continue, - }, + TRANSFER_ENCODING | CONTENT_LENGTH => continue, DATE => { has_date = true; } @@ -213,9 +208,6 @@ impl Codec { unsafe { buffer.advance_mut(pos); } - if !len_is_set { - buffer.extend_from_slice(b"content-length: 0\r\n") - } // optimized date header, set_date writes \r\n if !has_date { @@ -268,7 +260,7 @@ impl Decoder for Codec { } impl Encoder for Codec { - type Item = Message>; + type Item = Message<(Response<()>, BodyLength)>; type Error = io::Error; fn encode( @@ -277,8 +269,9 @@ impl Encoder for Codec { dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - Message::Item(res) => { - self.encode_response(res, dst)?; + Message::Item((mut res, length)) => { + self.prepare_te(res.head_mut(), length); + self.encode_response(res.head_mut(), length, dst)?; } Message::Chunk(Some(bytes)) => { self.te.encode(bytes.as_ref(), dst)?; diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 26154ef1..12008a77 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -10,7 +10,7 @@ use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; -use message::MessageFlags; +use message::Head; use request::Request; const MAX_BUFFER_SIZE: usize = 131_072; @@ -50,6 +50,8 @@ pub(crate) enum PayloadLength { pub(crate) trait MessageTypeDecoder: Sized { fn keep_alive(&mut self); + fn force_close(&mut self); + fn headers_mut(&mut self) -> &mut HeaderMap; fn decode(src: &mut BytesMut) -> Result, ParseError>; @@ -137,6 +139,8 @@ pub(crate) trait MessageTypeDecoder: Sized { if ka { self.keep_alive(); + } else { + self.force_close(); } // https://tools.ietf.org/html/rfc7230#section-3.3.3 @@ -160,7 +164,11 @@ pub(crate) trait MessageTypeDecoder: Sized { impl MessageTypeDecoder for Request { fn keep_alive(&mut self) { - self.inner_mut().flags.set(MessageFlags::KEEPALIVE); + self.inner_mut().head.set_keep_alive() + } + + fn force_close(&mut self) { + self.inner_mut().head.force_close() } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -234,7 +242,11 @@ impl MessageTypeDecoder for Request { impl MessageTypeDecoder for ClientResponse { fn keep_alive(&mut self) { - self.head.flags.insert(MessageFlags::KEEPALIVE); + self.head.set_keep_alive(); + } + + fn force_close(&mut self) { + self.head.force_close(); } fn headers_mut(&mut self) -> &mut HeaderMap { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index ff9d54e6..bf0abb04 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -30,7 +30,6 @@ bitflags! { const KEEPALIVE_ENABLED = 0b0000_0010; const KEEPALIVE = 0b0000_0100; const POLLED = 0b0000_1000; - const FLUSHED = 0b0001_0000; const SHUTDOWN = 0b0010_0000; const DISCONNECTED = 0b0100_0000; } @@ -105,9 +104,9 @@ where ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { - Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED + Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED } else { - Flags::FLUSHED + Flags::empty() }; let framed = Framed::new(stream, Codec::new(config.clone())); @@ -167,7 +166,7 @@ where /// Flush stream fn poll_flush(&mut self) -> Poll<(), DispatchError> { - if !self.flags.contains(Flags::FLUSHED) { + if !self.framed.is_write_buf_empty() { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { @@ -179,7 +178,6 @@ where if self.payload.is_some() && self.state.is_empty() { return Err(DispatchError::PayloadIsNotConsumed); } - self.flags.insert(Flags::FLUSHED); Ok(Async::Ready(())) } } @@ -194,7 +192,7 @@ where body: B1, ) -> Result, DispatchError> { self.framed - .force_send(Message::Item(message)) + .force_send(Message::Item((message, body.length()))) .map_err(|err| { if let Some(mut payload) = self.payload.take() { payload.set_error(PayloadError::Incomplete(None)); @@ -204,7 +202,6 @@ where self.flags .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); - self.flags.remove(Flags::FLUSHED); match body.length() { BodyLength::None | BodyLength::Empty => Ok(State::None), _ => Ok(State::SendPayload(body)), @@ -228,10 +225,7 @@ where State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(mut res) => { - let (mut res, body) = res.replace_body(()); - self.framed - .get_codec_mut() - .prepare_te(res.head_mut(), &mut body.length()); + let (res, body) = res.replace_body(()); Some(self.send_response(res, body)?) } Async::NotReady => { @@ -248,13 +242,11 @@ where .map_err(|_| DispatchError::Unknown)? { Async::Ready(Some(item)) => { - self.flags.remove(Flags::FLUSHED); self.framed .force_send(Message::Chunk(Some(item)))?; continue; } Async::Ready(None) => { - self.flags.remove(Flags::FLUSHED); self.framed.force_send(Message::Chunk(None))?; } Async::NotReady => { @@ -296,10 +288,7 @@ where let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { Async::Ready(res) => { - let (mut res, body) = res.replace_body(()); - self.framed - .get_codec_mut() - .prepare_te(res.head_mut(), &mut body.length()); + let (res, body) = res.replace_body(()); self.send_response(res, body) } Async::NotReady => Ok(State::ServiceCall(task)), @@ -408,7 +397,7 @@ where /// keep-alive timer fn poll_keepalive(&mut self) -> Result<(), DispatchError> { - if self.ka_timer.is_some() { + if self.ka_timer.is_none() { return Ok(()); } match self.ka_timer.as_mut().unwrap().poll().map_err(|e| { @@ -421,7 +410,7 @@ where return Err(DispatchError::DisconnectTimeout); } else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire { // check for any outstanding response processing - if self.state.is_empty() && self.flags.contains(Flags::FLUSHED) { + if self.state.is_empty() && self.framed.is_write_buf_empty() { if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); self.flags.insert(Flags::SHUTDOWN); @@ -490,12 +479,14 @@ where inner.poll_response()?; inner.poll_flush()?; + if inner.flags.contains(Flags::DISCONNECTED) { + return Ok(Async::Ready(H1ServiceResult::Disconnected)); + } + // keep-alive and stream errors - if inner.state.is_empty() && inner.flags.contains(Flags::FLUSHED) { + if inner.state.is_empty() && inner.framed.is_write_buf_empty() { if let Some(err) = inner.error.take() { return Err(err); - } else if inner.flags.contains(Flags::DISCONNECTED) { - return Ok(Async::Ready(H1ServiceResult::Disconnected)); } // unhandled request (upgrade or connect) else if inner.unhandled.is_some() { diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index fd52d731..1664af16 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -48,22 +48,13 @@ impl ResponseEncoder { resp: &mut ResponseHead, head: bool, version: Version, - length: &mut BodyLength, + length: BodyLength, ) { self.head = head; let transfer = match length { - BodyLength::Empty => { - match resp.status { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => *length = BodyLength::None, - _ => (), - } - TransferEncoding::empty() - } - BodyLength::Sized(len) => TransferEncoding::length(*len as u64), - BodyLength::Sized64(len) => TransferEncoding::length(*len), + BodyLength::Empty => TransferEncoding::empty(), + BodyLength::Sized(len) => TransferEncoding::length(len as u64), + BodyLength::Sized64(len) => TransferEncoding::length(len), BodyLength::Chunked => TransferEncoding::chunked(), BodyLength::Stream => TransferEncoding::eof(), BodyLength::None => TransferEncoding::length(0), diff --git a/src/lib.rs b/src/lib.rs index 615f0401..57ff5df7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,6 +109,8 @@ extern crate serde_derive; #[cfg(feature = "ssl")] extern crate openssl; +extern crate backtrace; + pub mod body; pub mod client; mod config; @@ -173,5 +175,4 @@ pub mod http { pub use header::*; } pub use header::ContentEncoding; - pub use response::ConnectionType; } diff --git a/src/message.rs b/src/message.rs index 3dcb203d..e7ce22fc 100644 --- a/src/message.rs +++ b/src/message.rs @@ -12,12 +12,41 @@ use uri::Url; pub trait Head: Default + 'static { fn clear(&mut self); + fn flags(&self) -> MessageFlags; + + fn flags_mut(&mut self) -> &mut MessageFlags; + fn pool() -> &'static MessagePool; + + /// Set upgrade + fn set_upgrade(&mut self) { + *self.flags_mut() = MessageFlags::UPGRADE; + } + + /// Check if request is upgrade request + fn upgrade(&self) -> bool { + self.flags().contains(MessageFlags::UPGRADE) + } + + /// Set keep-alive + fn set_keep_alive(&mut self) { + *self.flags_mut() = MessageFlags::KEEP_ALIVE; + } + + /// Check if request is keep-alive + fn keep_alive(&self) -> bool; + + /// Set force-close connection + fn force_close(&mut self) { + *self.flags_mut() = MessageFlags::FORCE_CLOSE; + } } bitflags! { - pub(crate) struct MessageFlags: u8 { - const KEEPALIVE = 0b0000_0001; + pub struct MessageFlags: u8 { + const KEEP_ALIVE = 0b0000_0001; + const FORCE_CLOSE = 0b0000_0010; + const UPGRADE = 0b0000_0100; } } @@ -47,6 +76,25 @@ impl Head for RequestHead { self.flags = MessageFlags::empty(); } + fn flags(&self) -> MessageFlags { + self.flags + } + + fn flags_mut(&mut self) -> &mut MessageFlags { + &mut self.flags + } + + /// Check if request is keep-alive + fn keep_alive(&self) -> bool { + if self.flags().contains(MessageFlags::FORCE_CLOSE) { + false + } else if self.flags().contains(MessageFlags::KEEP_ALIVE) { + true + } else { + self.version <= Version::HTTP_11 + } + } + fn pool() -> &'static MessagePool { REQUEST_POOL.with(|p| *p) } @@ -79,11 +127,44 @@ impl Head for ResponseHead { self.flags = MessageFlags::empty(); } + fn flags(&self) -> MessageFlags { + self.flags + } + + fn flags_mut(&mut self) -> &mut MessageFlags { + &mut self.flags + } + + /// Check if response is keep-alive + fn keep_alive(&self) -> bool { + if self.flags().contains(MessageFlags::FORCE_CLOSE) { + false + } else if self.flags().contains(MessageFlags::KEEP_ALIVE) { + true + } else { + self.version <= Version::HTTP_11 + } + } + fn pool() -> &'static MessagePool { RESPONSE_POOL.with(|p| *p) } } +impl ResponseHead { + /// Get custom reason for the response + #[inline] + pub fn reason(&self) -> &str { + if let Some(reason) = self.reason { + reason + } else { + self.status + .canonical_reason() + .unwrap_or("") + } + } +} + pub struct Message { pub head: T, pub url: Url, diff --git a/src/request.rs b/src/request.rs index 1e191047..1ee47edb 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,7 +8,7 @@ use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; -use message::{Message, MessageFlags, MessagePool, RequestHead}; +use message::{Head, Message, MessagePool, RequestHead}; /// Request pub struct Request { @@ -116,7 +116,7 @@ impl Request { /// Checks if a connection should be kept alive. #[inline] pub fn keep_alive(&self) -> bool { - self.inner().flags.get().contains(MessageFlags::KEEPALIVE) + self.inner().head.keep_alive() } /// Request extensions diff --git a/src/response.rs b/src/response.rs index a6f7c81c..542d4963 100644 --- a/src/response.rs +++ b/src/response.rs @@ -20,17 +20,6 @@ use message::{Head, MessageFlags, ResponseHead}; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; -/// Represents various types of connection -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ConnectionType { - /// Close connection after response - Close, - /// Keep connection alive after response - KeepAlive, - /// Connection is upgraded to different type - Upgrade, -} - /// An HTTP Response pub struct Response(Box, B); @@ -124,27 +113,6 @@ impl Response { &mut self.get_mut().head.status } - /// Get custom reason for the response - #[inline] - pub fn reason(&self) -> &str { - if let Some(reason) = self.get_ref().head.reason { - reason - } else { - self.get_ref() - .head - .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.get_mut().head.reason = Some(reason); - self - } - /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { @@ -207,28 +175,15 @@ impl Response { count } - /// Set connection type - pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self { - self.get_mut().connection_type = Some(conn); - self - } - /// Connection upgrade status #[inline] pub fn upgrade(&self) -> bool { - self.get_ref().connection_type == Some(ConnectionType::Upgrade) + self.get_ref().head.upgrade() } /// Keep-alive status for this connection - pub fn keep_alive(&self) -> Option { - if let Some(ct) = self.get_ref().connection_type { - match ct { - ConnectionType::KeepAlive => Some(true), - ConnectionType::Close | ConnectionType::Upgrade => Some(false), - } - } else { - None - } + pub fn keep_alive(&self) -> bool { + self.get_ref().head.keep_alive() } /// Get body os this response @@ -275,19 +230,20 @@ impl Response { } } -impl fmt::Debug for Response { +impl fmt::Debug for Response { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = writeln!( f, "\nResponse {:?} {}{}", self.get_ref().head.version, self.get_ref().head.status, - self.get_ref().head.reason.unwrap_or("") + self.get_ref().head.reason.unwrap_or(""), ); let _ = writeln!(f, " headers:"); for (key, val) in self.get_ref().head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } + let _ = writeln!(f, " body: {:?}", self.body().length()); res } } @@ -400,27 +356,31 @@ impl ResponseBuilder { self } - /// Set connection type + /// Set connection type to KeepAlive #[inline] - #[doc(hidden)] - pub fn connection_type(&mut self, conn: ConnectionType) -> &mut Self { + pub fn keep_alive(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.connection_type = Some(conn); + parts.head.set_keep_alive(); } self } /// Set connection type to Upgrade #[inline] - #[doc(hidden)] pub fn upgrade(&mut self) -> &mut Self { - self.connection_type(ConnectionType::Upgrade) + if let Some(parts) = parts(&mut self.response, &self.err) { + parts.head.set_upgrade(); + } + self } /// 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) + if let Some(parts) = parts(&mut self.response, &self.err) { + parts.head.force_close(); + } + self } /// Set response content type @@ -719,8 +679,6 @@ impl From for Response { struct InnerResponse { head: ResponseHead, - connection_type: Option, - write_capacity: usize, response_size: u64, error: Option, pool: &'static ResponsePool, @@ -728,7 +686,6 @@ struct InnerResponse { pub(crate) struct ResponseParts { head: ResponseHead, - connection_type: Option, error: Option, } @@ -744,9 +701,7 @@ impl InnerResponse { flags: MessageFlags::empty(), }, pool, - connection_type: None, response_size: 0, - write_capacity: MAX_WRITE_BUFFER_SIZE, error: None, } } @@ -755,7 +710,6 @@ impl InnerResponse { fn into_parts(self) -> ResponseParts { ResponseParts { head: self.head, - connection_type: self.connection_type, error: self.error, } } @@ -763,9 +717,7 @@ impl InnerResponse { fn from_parts(parts: ResponseParts) -> InnerResponse { InnerResponse { head: parts.head, - connection_type: parts.connection_type, response_size: 0, - write_capacity: MAX_WRITE_BUFFER_SIZE, error: parts.error, pool: ResponsePool::pool(), } @@ -838,10 +790,8 @@ impl ResponsePool { let mut p = inner.pool.0.borrow_mut(); if p.len() < 128 { inner.head.clear(); - inner.connection_type = None; inner.response_size = 0; inner.error = None; - inner.write_capacity = MAX_WRITE_BUFFER_SIZE; p.push_front(inner); } } @@ -937,7 +887,7 @@ mod tests { #[test] fn test_force_close() { let resp = Response::build(StatusCode::OK).force_close().finish(); - assert!(!resp.keep_alive().unwrap()) + assert!(!resp.keep_alive()) } #[test] diff --git a/src/service.rs b/src/service.rs index 51a16b48..f934305c 100644 --- a/src/service.rs +++ b/src/service.rs @@ -3,10 +3,10 @@ use std::marker::PhantomData; use actix_net::codec::Framed; use actix_net::service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; -use futures::{Async, AsyncSink, Future, Poll, Sink}; +use futures::{Async, Future, Poll, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; -use body::MessageBody; +use body::{BodyLength, MessageBody}; use error::{Error, ResponseError}; use h1::{Codec, Message}; use response::Response; @@ -15,7 +15,7 @@ pub struct SendError(PhantomData<(T, R, E)>); impl Default for SendError where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, E: ResponseError, { fn default() -> Self { @@ -25,7 +25,7 @@ where impl NewService for SendError where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, E: ResponseError, { type Request = Result)>; @@ -42,7 +42,7 @@ where impl Service for SendError where - T: AsyncWrite, + T: AsyncRead + AsyncWrite, E: ResponseError, { type Request = Result)>; @@ -62,7 +62,7 @@ where let (res, _body) = res.replace_body(()); Either::B(SendErrorFut { framed: Some(framed), - res: Some(res.into()), + res: Some((res, BodyLength::Empty).into()), err: Some(e), _t: PhantomData, }) @@ -72,7 +72,7 @@ where } pub struct SendErrorFut { - res: Option>>, + res: Option, BodyLength)>>, framed: Option>, err: Option, _t: PhantomData, @@ -81,22 +81,15 @@ pub struct SendErrorFut { impl Future for SendErrorFut where E: ResponseError, - T: AsyncWrite, + T: AsyncRead + AsyncWrite, { type Item = R; type Error = (E, Framed); fn poll(&mut self) -> Poll { if let Some(res) = self.res.take() { - match self.framed.as_mut().unwrap().start_send(res) { - Ok(AsyncSink::Ready) => (), - Ok(AsyncSink::NotReady(res)) => { - self.res = Some(res); - return Ok(Async::NotReady); - } - Err(_) => { - return Err((self.err.take().unwrap(), self.framed.take().unwrap())) - } + if let Err(_) = self.framed.as_mut().unwrap().force_send(res) { + return Err((self.err.take().unwrap(), self.framed.take().unwrap())); } } match self.framed.as_mut().unwrap().poll_complete() { @@ -123,20 +116,15 @@ where B: MessageBody, { pub fn send( - mut framed: Framed, + framed: Framed, res: Response, ) -> impl Future, Error = Error> { // extract body from response - let (mut res, body) = res.replace_body(()); - - // init codec - framed - .get_codec_mut() - .prepare_te(&mut res.head_mut(), &mut body.length()); + let (res, body) = res.replace_body(()); // write response SendResponseFut { - res: Some(Message::Item(res)), + res: Some(Message::Item((res, body.length()))), body: Some(body), framed: Some(framed), } @@ -174,13 +162,10 @@ where Ok(Async::Ready(())) } - fn call(&mut self, (res, mut framed): Self::Request) -> Self::Future { - let (mut res, body) = res.replace_body(()); - framed - .get_codec_mut() - .prepare_te(res.head_mut(), &mut body.length()); + fn call(&mut self, (res, framed): Self::Request) -> Self::Future { + let (res, body) = res.replace_body(()); SendResponseFut { - res: Some(Message::Item(res)), + res: Some(Message::Item((res, body.length()))), body: Some(body), framed: Some(framed), } @@ -188,7 +173,7 @@ where } pub struct SendResponseFut { - res: Option>>, + res: Option, BodyLength)>>, body: Option, framed: Option>, } diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 1fbbd03d..5c86d8c4 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -8,7 +8,7 @@ use std::io; use error::ResponseError; use http::{header, Method, StatusCode}; use request::Request; -use response::{ConnectionType, Response, ResponseBuilder}; +use response::{Response, ResponseBuilder}; mod client; mod codec; @@ -183,7 +183,7 @@ pub fn handshake_response(req: &Request) -> ResponseBuilder { }; Response::build(StatusCode::SWITCHING_PROTOCOLS) - .connection_type(ConnectionType::Upgrade) + .upgrade() .header(header::UPGRADE, "websocket") .header(header::TRANSFER_ENCODING, "chunked") .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) diff --git a/tests/test_server.rs b/tests/test_server.rs index c499cf63..9d16e92e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -12,10 +12,13 @@ use actix_net::server::Server; use actix_net::service::NewServiceExt; use actix_web::{client, test, HttpMessage}; use bytes::Bytes; -use futures::future::{self, ok}; +use futures::future::{self, lazy, ok}; use futures::stream::once; -use actix_http::{body, h1, http, Body, Error, KeepAlive, Request, Response}; +use actix_http::{ + body, client as client2, h1, http, Body, Error, HttpMessage as HttpMessage2, + KeepAlive, Request, Response, +}; #[test] fn test_h1_v2() { @@ -181,14 +184,19 @@ fn test_headers() { .unwrap() .run() }); - thread::sleep(time::Duration::from_millis(400)); + thread::sleep(time::Duration::from_millis(200)); let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) + let mut connector = sys + .block_on(lazy(|| { + Ok::<_, ()>(client2::Connector::default().service()) + })).unwrap(); + + let req = client2::ClientRequest::get(format!("http://{}/", addr)) .finish() .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let response = sys.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -249,9 +257,7 @@ fn test_head_empty() { thread::spawn(move || { Server::new() .bind("test", addr, move || { - h1::H1Service::new(|_| { - ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).finish()) - }).map(|_| ()) + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }).unwrap() .run() }); From 7d3adaa6a8f47ef4101ba1a16e9e6360453237d4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 18:17:38 -0800 Subject: [PATCH 0821/1635] replace message flags with ConnectionType --- src/client/request.rs | 6 +-- src/h1/decoder.rs | 14 +++-- src/lib.rs | 1 + src/message.rs | 115 +++++++++++++++++------------------------- src/response.rs | 10 ++-- 5 files changed, 64 insertions(+), 82 deletions(-) diff --git a/src/client/request.rs b/src/client/request.rs index cc65b9db..735ce493 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -16,7 +16,7 @@ use http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use message::{Head, RequestHead}; +use message::{ConnectionType, Head, RequestHead}; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; @@ -367,7 +367,7 @@ impl ClientRequestBuilder { { { if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_upgrade(); + parts.set_connection_type(ConnectionType::Upgrade); } } self.set_header(header::UPGRADE, value) @@ -377,7 +377,7 @@ impl ClientRequestBuilder { #[inline] pub fn close(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.head, &self.err) { - parts.force_close(); + parts.set_connection_type(ConnectionType::Close); } self } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 12008a77..12dfe165 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -10,7 +10,7 @@ use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; -use message::Head; +use message::{ConnectionType, Head}; use request::Request; const MAX_BUFFER_SIZE: usize = 131_072; @@ -164,11 +164,15 @@ pub(crate) trait MessageTypeDecoder: Sized { impl MessageTypeDecoder for Request { fn keep_alive(&mut self) { - self.inner_mut().head.set_keep_alive() + self.inner_mut() + .head + .set_connection_type(ConnectionType::KeepAlive) } fn force_close(&mut self) { - self.inner_mut().head.force_close() + self.inner_mut() + .head + .set_connection_type(ConnectionType::Close) } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -242,11 +246,11 @@ impl MessageTypeDecoder for Request { impl MessageTypeDecoder for ClientResponse { fn keep_alive(&mut self) { - self.head.set_keep_alive(); + self.head.set_connection_type(ConnectionType::KeepAlive); } fn force_close(&mut self) { - self.head.force_close(); + self.head.set_connection_type(ConnectionType::Close); } fn headers_mut(&mut self) -> &mut HeaderMap { diff --git a/src/lib.rs b/src/lib.rs index 57ff5df7..3024b753 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,4 +175,5 @@ pub mod http { pub use header::*; } pub use header::ContentEncoding; + pub use message::ConnectionType; } diff --git a/src/message.rs b/src/message.rs index e7ce22fc..03e18f08 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,4 +1,4 @@ -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; @@ -8,46 +8,36 @@ use extensions::Extensions; use payload::Payload; use uri::Url; +/// Represents various types of connection +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ConnectionType { + /// Close connection after response + Close, + /// Keep connection alive after response + KeepAlive, + /// Connection is upgraded to different type + Upgrade, +} + #[doc(hidden)] pub trait Head: Default + 'static { fn clear(&mut self); - fn flags(&self) -> MessageFlags; + /// Connection type + fn connection_type(&self) -> ConnectionType; - fn flags_mut(&mut self) -> &mut MessageFlags; + /// Set connection type of the message + fn set_connection_type(&mut self, ctype: ConnectionType); + + fn upgrade(&self) -> bool { + self.connection_type() == ConnectionType::Upgrade + } + + fn keep_alive(&self) -> bool { + self.connection_type() == ConnectionType::KeepAlive + } fn pool() -> &'static MessagePool; - - /// Set upgrade - fn set_upgrade(&mut self) { - *self.flags_mut() = MessageFlags::UPGRADE; - } - - /// Check if request is upgrade request - fn upgrade(&self) -> bool { - self.flags().contains(MessageFlags::UPGRADE) - } - - /// Set keep-alive - fn set_keep_alive(&mut self) { - *self.flags_mut() = MessageFlags::KEEP_ALIVE; - } - - /// Check if request is keep-alive - fn keep_alive(&self) -> bool; - - /// Set force-close connection - fn force_close(&mut self) { - *self.flags_mut() = MessageFlags::FORCE_CLOSE; - } -} - -bitflags! { - pub struct MessageFlags: u8 { - const KEEP_ALIVE = 0b0000_0001; - const FORCE_CLOSE = 0b0000_0010; - const UPGRADE = 0b0000_0100; - } } pub struct RequestHead { @@ -55,7 +45,7 @@ pub struct RequestHead { pub method: Method, pub version: Version, pub headers: HeaderMap, - pub(crate) flags: MessageFlags, + ctype: Option, } impl Default for RequestHead { @@ -65,33 +55,28 @@ impl Default for RequestHead { method: Method::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - flags: MessageFlags::empty(), + ctype: None, } } } impl Head for RequestHead { fn clear(&mut self) { + self.ctype = None; self.headers.clear(); - self.flags = MessageFlags::empty(); } - fn flags(&self) -> MessageFlags { - self.flags + fn set_connection_type(&mut self, ctype: ConnectionType) { + self.ctype = Some(ctype) } - fn flags_mut(&mut self) -> &mut MessageFlags { - &mut self.flags - } - - /// Check if request is keep-alive - fn keep_alive(&self) -> bool { - if self.flags().contains(MessageFlags::FORCE_CLOSE) { - false - } else if self.flags().contains(MessageFlags::KEEP_ALIVE) { - true + fn connection_type(&self) -> ConnectionType { + if let Some(ct) = self.ctype { + ct + } else if self.version <= Version::HTTP_11 { + ConnectionType::Close } else { - self.version <= Version::HTTP_11 + ConnectionType::KeepAlive } } @@ -105,7 +90,7 @@ pub struct ResponseHead { pub status: StatusCode, pub headers: HeaderMap, pub reason: Option<&'static str>, - pub(crate) flags: MessageFlags, + pub(crate) ctype: Option, } impl Default for ResponseHead { @@ -115,34 +100,29 @@ impl Default for ResponseHead { status: StatusCode::OK, headers: HeaderMap::with_capacity(16), reason: None, - flags: MessageFlags::empty(), + ctype: None, } } } impl Head for ResponseHead { fn clear(&mut self) { + self.ctype = None; self.reason = None; self.headers.clear(); - self.flags = MessageFlags::empty(); } - fn flags(&self) -> MessageFlags { - self.flags + fn set_connection_type(&mut self, ctype: ConnectionType) { + self.ctype = Some(ctype) } - fn flags_mut(&mut self) -> &mut MessageFlags { - &mut self.flags - } - - /// Check if response is keep-alive - fn keep_alive(&self) -> bool { - if self.flags().contains(MessageFlags::FORCE_CLOSE) { - false - } else if self.flags().contains(MessageFlags::KEEP_ALIVE) { - true + fn connection_type(&self) -> ConnectionType { + if let Some(ct) = self.ctype { + ct + } else if self.version <= Version::HTTP_11 { + ConnectionType::Close } else { - self.version <= Version::HTTP_11 + ConnectionType::KeepAlive } } @@ -172,7 +152,6 @@ pub struct Message { pub extensions: RefCell, pub payload: RefCell>, pub(crate) pool: &'static MessagePool, - pub(crate) flags: Cell, } impl Message { @@ -181,7 +160,6 @@ impl Message { pub fn reset(&mut self) { self.head.clear(); self.extensions.borrow_mut().clear(); - self.flags.set(MessageFlags::empty()); *self.payload.borrow_mut() = None; } } @@ -193,7 +171,6 @@ impl Default for Message { url: Url::default(), head: T::default(), status: StatusCode::OK, - flags: Cell::new(MessageFlags::empty()), payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), } diff --git a/src/response.rs b/src/response.rs index 542d4963..f2ed7292 100644 --- a/src/response.rs +++ b/src/response.rs @@ -15,7 +15,7 @@ use serde_json; use body::{Body, BodyStream, MessageBody}; use error::Error; use header::{Header, IntoHeaderValue}; -use message::{Head, MessageFlags, ResponseHead}; +use message::{ConnectionType, Head, ResponseHead}; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -360,7 +360,7 @@ impl ResponseBuilder { #[inline] pub fn keep_alive(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_keep_alive(); + parts.head.set_connection_type(ConnectionType::KeepAlive); } self } @@ -369,7 +369,7 @@ impl ResponseBuilder { #[inline] pub fn upgrade(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_upgrade(); + parts.head.set_connection_type(ConnectionType::Upgrade); } self } @@ -378,7 +378,7 @@ impl ResponseBuilder { #[inline] pub fn force_close(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.force_close(); + parts.head.set_connection_type(ConnectionType::Close); } self } @@ -698,7 +698,7 @@ impl InnerResponse { version: Version::default(), headers: HeaderMap::with_capacity(16), reason: None, - flags: MessageFlags::empty(), + ctype: None, }, pool, response_size: 0, From 22d4523c93db4f750ea2866a1e34ad3142773554 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 18:31:44 -0800 Subject: [PATCH 0822/1635] update actix-net --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2eef1c50..50bacddb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,8 @@ rust-tls = ["rustls", "actix-net/rust-tls"] [dependencies] actix = "0.7.5" -#actix-net = "0.2.2" -actix-net = { git="https://github.com/actix/actix-net.git" } -#actix-net = { path="../actix-net" } +actix-net = "0.2.3" +#actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" From 7d6643032408720bbcb4355cf3bc453e8724c272 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 20:08:43 -0800 Subject: [PATCH 0823/1635] move url module to different crate --- Cargo.toml | 5 +- src/h1/decoder.rs | 1 - src/lib.rs | 3 - src/message.rs | 5 -- src/request.rs | 6 +- src/test.rs | 3 +- src/uri.rs | 169 ---------------------------------------------- 7 files changed, 3 insertions(+), 189 deletions(-) delete mode 100644 src/uri.rs diff --git a/Cargo.toml b/Cargo.toml index 50bacddb..b2c7c084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ base64 = "0.9" bitflags = "1.0" http = "0.1.8" httparse = "1.3" -failure = "0.1.2" +failure = "0.1.3" indexmap = "1.0" log = "0.4" mime = "0.3" @@ -62,7 +62,6 @@ serde_json = "1.0" sha1 = "0.6" time = "0.1" encoding = "0.2" -lazy_static = "1.0" serde_urlencoded = "0.5.3" cookie = { version="0.11", features=["percent-encode"] } @@ -93,8 +92,6 @@ openssl = { version="0.10", optional = true } # rustls rustls = { version = "^0.14", optional = true } -backtrace="*" - [dev-dependencies] actix-web = "0.7" env_logger = "0.5" diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 12dfe165..de0df367 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -234,7 +234,6 @@ impl MessageTypeDecoder for Request { { let inner = msg.inner_mut(); - inner.url.update(&uri); inner.head.uri = uri; inner.head.method = method; inner.head.version = ver; diff --git a/src/lib.rs b/src/lib.rs index 3024b753..bee172f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,8 +76,6 @@ extern crate bitflags; #[macro_use] extern crate failure; #[macro_use] -extern crate lazy_static; -#[macro_use] extern crate futures; extern crate cookie; extern crate encoding; @@ -124,7 +122,6 @@ mod payload; mod request; mod response; mod service; -pub mod uri; pub mod error; pub mod h1; diff --git a/src/message.rs b/src/message.rs index 03e18f08..e1059fa4 100644 --- a/src/message.rs +++ b/src/message.rs @@ -6,7 +6,6 @@ use http::{HeaderMap, Method, StatusCode, Uri, Version}; use extensions::Extensions; use payload::Payload; -use uri::Url; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -147,8 +146,6 @@ impl ResponseHead { pub struct Message { pub head: T, - pub url: Url, - pub status: StatusCode, pub extensions: RefCell, pub payload: RefCell>, pub(crate) pool: &'static MessagePool, @@ -168,9 +165,7 @@ impl Default for Message { fn default() -> Self { Message { pool: T::pool(), - url: Url::default(), head: T::default(), - status: StatusCode::OK, payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), } diff --git a/src/request.rs b/src/request.rs index 1ee47edb..d529c09f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -94,11 +94,7 @@ impl Request { /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - if let Some(path) = self.inner().url.path() { - path - } else { - self.inner().head.uri.path() - } + self.inner().head.uri.path() } #[inline] diff --git a/src/test.rs b/src/test.rs index c6f258a7..5442a414 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,7 +15,6 @@ use tokio::runtime::current_thread::Runtime; use header::{Header, IntoHeaderValue}; use payload::Payload; use request::Request; -use uri::Url as InnerUrl; // use ws; /// The `TestServer` type. @@ -390,8 +389,8 @@ impl TestRequest { let mut req = Request::new(); { let inner = req.inner_mut(); + inner.head.uri = uri; inner.head.method = method; - inner.url = InnerUrl::new(&uri); inner.head.version = version; inner.head.headers = headers; *inner.payload.borrow_mut() = payload; diff --git a/src/uri.rs b/src/uri.rs deleted file mode 100644 index 89f6d3b1..00000000 --- a/src/uri.rs +++ /dev/null @@ -1,169 +0,0 @@ -use http::Uri; -use std::rc::Rc; - -#[allow(dead_code)] -const GEN_DELIMS: &[u8] = b":/?#[]@"; -#[allow(dead_code)] -const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; -#[allow(dead_code)] -const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; -#[allow(dead_code)] -const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; -#[allow(dead_code)] -const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~"; -const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~ - !$'()*,"; -const QS: &[u8] = b"+&=;b"; - -#[inline] -fn bit_at(array: &[u8], ch: u8) -> bool { - array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0 -} - -#[inline] -fn set_bit(array: &mut [u8], ch: u8) { - array[(ch >> 3) as usize] |= 1 << (ch & 7) -} - -lazy_static! { - pub static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; -} - -#[derive(Default, Clone, Debug)] -pub struct Url { - path: Option>, -} - -impl Url { - pub fn new(uri: &Uri) -> Url { - let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); - - Url { path } - } - - pub(crate) fn update(&mut self, uri: &Uri) { - self.path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); - } - - pub fn path(&self) -> Option<&str> { - self.path.as_ref().map(|s| s.as_str()) - } -} - -pub struct Quoter { - safe_table: [u8; 16], - protected_table: [u8; 16], -} - -impl Quoter { - pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { - let mut q = Quoter { - safe_table: [0; 16], - protected_table: [0; 16], - }; - - // prepare safe table - for i in 0..128 { - if ALLOWED.contains(&i) { - set_bit(&mut q.safe_table, i); - } - if QS.contains(&i) { - set_bit(&mut q.safe_table, i); - } - } - - for ch in safe { - set_bit(&mut q.safe_table, *ch) - } - - // prepare protected table - for ch in protected { - set_bit(&mut q.safe_table, *ch); - set_bit(&mut q.protected_table, *ch); - } - - q - } - - pub fn requote(&self, val: &[u8]) -> Option> { - let mut has_pct = 0; - let mut pct = [b'%', 0, 0]; - let mut idx = 0; - let mut cloned: Option> = None; - - let len = val.len(); - while idx < len { - let ch = val[idx]; - - if has_pct != 0 { - pct[has_pct] = val[idx]; - has_pct += 1; - if has_pct == 3 { - has_pct = 0; - let buf = cloned.as_mut().unwrap(); - - if let Some(ch) = restore_ch(pct[1], pct[2]) { - if ch < 128 { - if bit_at(&self.protected_table, ch) { - buf.extend_from_slice(&pct); - idx += 1; - continue; - } - - if bit_at(&self.safe_table, ch) { - buf.push(ch); - idx += 1; - continue; - } - } - buf.push(ch); - } else { - buf.extend_from_slice(&pct[..]); - } - } - } else if ch == b'%' { - has_pct = 1; - if cloned.is_none() { - let mut c = Vec::with_capacity(len); - c.extend_from_slice(&val[..idx]); - cloned = Some(c); - } - } else if let Some(ref mut cloned) = cloned { - cloned.push(ch) - } - idx += 1; - } - - if let Some(data) = cloned { - // Unsafe: we get data from http::Uri, which does utf-8 checks already - // this code only decodes valid pct encoded values - Some(Rc::new(unsafe { String::from_utf8_unchecked(data) })) - } else { - None - } - } -} - -#[inline] -fn from_hex(v: u8) -> Option { - if v >= b'0' && v <= b'9' { - Some(v - 0x30) // ord('0') == 0x30 - } else if v >= b'A' && v <= b'F' { - Some(v - 0x41 + 10) // ord('A') == 0x41 - } else if v > b'a' && v <= b'f' { - Some(v - 0x61 + 10) // ord('a') == 0x61 - } else { - None - } -} - -#[inline] -fn restore_ch(d1: u8, d2: u8) -> Option { - from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2))) -} From 18fcddfd637b77f11bea8d17826ff31bd019f277 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 20:25:59 -0800 Subject: [PATCH 0824/1635] remove backtrace dep --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bee172f0..e5f50011 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,8 +107,6 @@ extern crate serde_derive; #[cfg(feature = "ssl")] extern crate openssl; -extern crate backtrace; - pub mod body; pub mod client; mod config; @@ -173,4 +171,4 @@ pub mod http { } pub use header::ContentEncoding; pub use message::ConnectionType; -} +} \ No newline at end of file From 1ca6b44bae381f9042795fea63665aa9d480ccf2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 18 Nov 2018 21:48:20 -0800 Subject: [PATCH 0825/1635] add TestServer --- src/lib.rs | 2 +- src/test.rs | 447 +++++++++++++++++++++---------------------- tests/test_client.rs | 113 ++++------- tests/test_server.rs | 354 ++++++++++++---------------------- tests/test_ws.rs | 128 ++++++------- 5 files changed, 425 insertions(+), 619 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e5f50011..5256dd19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,4 +171,4 @@ pub mod http { } pub use header::ContentEncoding; pub use message::ConnectionType; -} \ No newline at end of file +} diff --git a/src/test.rs b/src/test.rs index 5442a414..9e14129b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,251 +1,31 @@ //! Various helpers for Actix applications to use during testing. -use std::net; use std::str::FromStr; +use std::sync::mpsc; +use std::{net, thread}; use actix::System; +use actix_net::codec::Framed; +use actix_net::server::{Server, StreamServiceFactory}; +use actix_net::service::Service; use bytes::Bytes; use cookie::Cookie; -use futures::Future; +use futures::future::{lazy, Future}; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; +use tokio_io::{AsyncRead, AsyncWrite}; +use body::MessageBody; +use client::{ + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, + ConnectorError, SendRequestError, +}; use header::{Header, IntoHeaderValue}; use payload::Payload; use request::Request; -// use ws; - -/// The `TestServer` type. -/// -/// `TestServer` is very simple test server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// # use actix_web::*; -/// # -/// # fn my_handler(req: &HttpRequest) -> Response { -/// # Response::Ok().into() -/// # } -/// # -/// # fn main() { -/// use actix_web::test::TestServer; -/// -/// let mut srv = TestServer::new(|app| app.handler(my_handler)); -/// -/// let req = srv.get().finish().unwrap(); -/// let response = srv.execute(req.send()).unwrap(); -/// assert!(response.status().is_success()); -/// # } -/// ``` -pub struct TestServer { - addr: net::SocketAddr, - rt: Runtime, - ssl: bool, -} - -impl TestServer { - /// Start new test server - /// - /// This method accepts configuration method. You can add - /// middlewares or set handlers for test application. - pub fn new(_config: F) -> Self - where - F: Fn() + Clone + Send + 'static, - { - unimplemented!() - } - - /// Get firat available unused address - pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = TcpBuilder::new_v4().unwrap(); - socket.bind(&addr).unwrap(); - socket.reuse_address(true).unwrap(); - let tcp = socket.to_tcp_listener().unwrap(); - tcp.local_addr().unwrap() - } - - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - if uri.starts_with('/') { - format!( - "{}://localhost:{}{}", - if self.ssl { "https" } else { "http" }, - self.addr.port(), - uri - ) - } else { - format!( - "{}://localhost:{}/{}", - if self.ssl { "https" } else { "http" }, - self.addr.port(), - uri - ) - } - } - - /// Stop http server - fn stop(&mut self) { - System::current().stop(); - } - - /// Execute future on current core - pub fn execute(&mut self, fut: F) -> Result - where - F: Future, - { - self.rt.block_on(fut) - } - - // /// Connect to websocket server at a given path - // pub fn ws_at( - // &mut self, path: &str, - // ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - // let url = self.url(path); - // self.rt - // .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) - // } - - // /// Connect to a websocket server - // pub fn ws( - // &mut self, - // ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - // self.ws_at("/") - // } - - // /// Create `GET` request - // pub fn get(&self) -> ClientRequestBuilder { - // ClientRequest::get(self.url("/").as_str()) - // } - - // /// Create `POST` request - // pub fn post(&self) -> ClientRequestBuilder { - // ClientRequest::post(self.url("/").as_str()) - // } - - // /// Create `HEAD` request - // pub fn head(&self) -> ClientRequestBuilder { - // ClientRequest::head(self.url("/").as_str()) - // } - - // /// Connect to test http server - // pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - // ClientRequest::build() - // .method(meth) - // .uri(self.url(path).as_str()) - // .with_connector(self.conn.clone()) - // .take() - // } -} - -impl Drop for TestServer { - fn drop(&mut self) { - self.stop() - } -} - -// /// An `TestServer` builder -// /// -// /// This type can be used to construct an instance of `TestServer` through a -// /// builder-like pattern. -// pub struct TestServerBuilder -// where -// F: Fn() -> S + Send + Clone + 'static, -// { -// state: F, -// } - -// impl TestServerBuilder -// where -// F: Fn() -> S + Send + Clone + 'static, -// { -// /// Create a new test server -// pub fn new(state: F) -> TestServerBuilder { -// TestServerBuilder { state } -// } - -// #[allow(unused_mut)] -// /// Configure test application and run test server -// pub fn start(mut self, config: C) -> TestServer -// where -// C: Fn(&mut TestApp) + Clone + Send + 'static, -// { -// let (tx, rx) = mpsc::channel(); - -// let mut has_ssl = false; - -// #[cfg(any(feature = "alpn", feature = "ssl"))] -// { -// has_ssl = has_ssl || self.ssl.is_some(); -// } - -// #[cfg(feature = "rust-tls")] -// { -// has_ssl = has_ssl || self.rust_ssl.is_some(); -// } - -// // run server in separate thread -// thread::spawn(move || { -// let addr = TestServer::unused_addr(); - -// let sys = System::new("actix-test-server"); -// let state = self.state; -// let mut srv = HttpServer::new(move || { -// let mut app = TestApp::new(state()); -// config(&mut app); -// app -// }).workers(1) -// .keep_alive(5) -// .disable_signals(); - -// tx.send((System::current(), addr, TestServer::get_conn())) -// .unwrap(); - -// #[cfg(any(feature = "alpn", feature = "ssl"))] -// { -// let ssl = self.ssl.take(); -// if let Some(ssl) = ssl { -// let tcp = net::TcpListener::bind(addr).unwrap(); -// srv = srv.listen_ssl(tcp, ssl).unwrap(); -// } -// } -// #[cfg(feature = "rust-tls")] -// { -// let ssl = self.rust_ssl.take(); -// if let Some(ssl) = ssl { -// let tcp = net::TcpListener::bind(addr).unwrap(); -// srv = srv.listen_rustls(tcp, ssl); -// } -// } -// if !has_ssl { -// let tcp = net::TcpListener::bind(addr).unwrap(); -// srv = srv.listen(tcp); -// } -// srv.start(); - -// sys.run(); -// }); - -// let (system, addr, conn) = rx.recv().unwrap(); -// System::set_current(system); -// TestServer { -// addr, -// conn, -// ssl: has_ssl, -// rt: Runtime::new().unwrap(), -// } -// } -// } +use ws; /// Test `Request` builder /// @@ -486,3 +266,204 @@ impl TestRequest { // } // } } + +/// The `TestServer` type. +/// +/// `TestServer` is very simple test server that simplify process of writing +/// integration tests cases for actix web applications. +/// +/// # Examples +/// +/// ```rust +/// # extern crate actix_web; +/// # use actix_web::*; +/// # +/// # fn my_handler(req: &HttpRequest) -> HttpResponse { +/// # HttpResponse::Ok().into() +/// # } +/// # +/// # fn main() { +/// use actix_web::test::TestServer; +/// +/// let mut srv = TestServer::new(|app| app.handler(my_handler)); +/// +/// let req = srv.get().finish().unwrap(); +/// let response = srv.execute(req.send()).unwrap(); +/// assert!(response.status().is_success()); +/// # } +/// ``` +pub struct TestServer; + +/// +pub struct TestServerRuntime { + addr: net::SocketAddr, + conn: T, + rt: Runtime, +} + +impl TestServer { + /// Start new test server with application factory + pub fn with_factory( + factory: F, + ) -> TestServerRuntime< + impl Service + + Clone, + > { + let (tx, rx) = mpsc::channel(); + + // run server in separate thread + thread::spawn(move || { + let sys = System::new("actix-test-server"); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + + Server::default() + .listen("test", tcp, factory) + .workers(1) + .disable_signals() + .start(); + + tx.send((System::current(), local_addr)).unwrap(); + sys.run(); + }); + + let (system, addr) = rx.recv().unwrap(); + System::set_current(system); + + let mut rt = Runtime::new().unwrap(); + let conn = rt + .block_on(lazy(|| Ok::<_, ()>(TestServer::new_connector()))) + .unwrap(); + + TestServerRuntime { addr, conn, rt } + } + + fn new_connector( +) -> impl Service + + Clone { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + Connector::default().ssl(builder.build()).service() + } + #[cfg(not(feature = "ssl"))] + { + Connector::default().service() + } + } + + /// Get firat available unused address + pub fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = TcpBuilder::new_v4().unwrap(); + socket.bind(&addr).unwrap(); + socket.reuse_address(true).unwrap(); + let tcp = socket.to_tcp_listener().unwrap(); + tcp.local_addr().unwrap() + } +} + +impl TestServerRuntime { + /// Execute future on current core + pub fn block_on(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + + /// Construct test server url + pub fn addr(&self) -> net::SocketAddr { + self.addr + } + + /// Construct test server url + pub fn url(&self, uri: &str) -> String { + if uri.starts_with('/') { + format!("http://localhost:{}{}", self.addr.port(), uri) + } else { + format!("http://localhost:{}/{}", self.addr.port(), uri) + } + } + + /// Create `GET` request + pub fn get(&self) -> ClientRequestBuilder { + ClientRequest::get(self.url("/").as_str()) + } + + /// Create `POST` request + pub fn post(&self) -> ClientRequestBuilder { + ClientRequest::post(self.url("/").as_str()) + } + + /// Create `HEAD` request + pub fn head(&self) -> ClientRequestBuilder { + ClientRequest::head(self.url("/").as_str()) + } + + /// Connect to test http server + pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { + ClientRequest::build() + .method(meth) + .uri(self.url(path).as_str()) + .take() + } + + /// Http connector + pub fn connector(&mut self) -> &mut T { + &mut self.conn + } + + /// Http connector + pub fn new_connector(&mut self) -> T + where + T: Clone, + { + self.conn.clone() + } + + /// Stop http server + fn stop(&mut self) { + System::current().stop(); + } +} + +impl TestServerRuntime +where + T: Service + Clone, + T::Response: Connection, +{ + /// Connect to websocket server at a given path + pub fn ws_at( + &mut self, + path: &str, + ) -> Result, ws::ClientError> { + let url = self.url(path); + self.rt + .block_on(ws::Client::default().call(ws::Connect::new(url))) + } + + /// Connect to a websocket server + pub fn ws( + &mut self, + ) -> Result, ws::ClientError> { + self.ws_at("/") + } + + /// Send request and read response message + pub fn send_request( + &mut self, + req: ClientRequest, + ) -> Result { + self.rt.block_on(req.send(&mut self.conn)) + } +} + +impl Drop for TestServerRuntime { + fn drop(&mut self) { + self.stop() + } +} diff --git a/tests/test_client.rs b/tests/test_client.rs index 40920d1b..4a4ccb7d 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -4,16 +4,12 @@ extern crate actix_net; extern crate bytes; extern crate futures; -use std::{thread, time}; - -use actix::System; -use actix_net::server::Server; use actix_net::service::NewServiceExt; use bytes::Bytes; -use futures::future::{self, lazy, ok}; +use futures::future::{self, ok}; use actix_http::HttpMessage; -use actix_http::{client, h1, test, Request, Response}; +use actix_http::{client, h1, test::TestServer, Request, Response}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -39,111 +35,70 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_v2() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) - }).unwrap() - .run(); + let mut srv = TestServer::with_factory(move || { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); + let mut connector = srv.new_connector(); - let mut sys = System::new("test"); - let mut connector = sys - .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) - .unwrap(); - - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - - let response = sys.block_on(req.send(&mut connector)).unwrap(); + let request = srv.get().finish().unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); - let request = client::ClientRequest::get(format!("http://{}/", addr)) - .header("x-test", "111") - .finish() - .unwrap(); + let request = srv.get().header("x-test", "111").finish().unwrap(); let repr = format!("{:?}", request); assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); - let response = sys.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let request = client::ClientRequest::post(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(request.send(&mut connector)).unwrap(); + let request = srv.post().finish().unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] fn test_connection_close() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) - }).unwrap() - .run(); + let mut srv = TestServer::with_factory(move || { + h1::H1Service::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); + let mut connector = srv.new_connector(); - let mut sys = System::new("test"); - let mut connector = sys - .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) - .unwrap(); - - let request = client::ClientRequest::get(format!("http://{}/", addr)) - .header("Connection", "close") - .finish() - .unwrap(); - let response = sys.block_on(request.send(&mut connector)).unwrap(); + let request = srv.get().close().finish().unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); } #[test] fn test_with_query_parameter() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .finish(|req: Request| { - if req.uri().query().unwrap().contains("qp=") { - ok::<_, ()>(Response::Ok().finish()) - } else { - ok::<_, ()>(Response::BadRequest().finish()) - } - }).map(|_| ()) - }).unwrap() - .run(); + let mut srv = TestServer::with_factory(move || { + h1::H1Service::build() + .finish(|req: Request| { + if req.uri().query().unwrap().contains("qp=") { + ok::<_, ()>(Response::Ok().finish()) + } else { + ok::<_, ()>(Response::BadRequest().finish()) + } + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); + let mut connector = srv.new_connector(); - let mut sys = System::new("test"); - let mut connector = sys - .block_on(lazy(|| Ok::<_, ()>(client::Connector::default().service()))) - .unwrap(); - - let request = client::ClientRequest::get(format!("http://{}/?qp=5", addr)) + let request = client::ClientRequest::get(srv.url("/?qp=5")) .finish() .unwrap(); - let response = sys.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 9d16e92e..a01af4f0 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,70 +1,48 @@ extern crate actix; extern crate actix_http; extern crate actix_net; -extern crate actix_web; extern crate bytes; extern crate futures; -use std::{io::Read, io::Write, net, thread, time}; +use std::{io::Read, io::Write, net}; -use actix::System; -use actix_net::server::Server; use actix_net::service::NewServiceExt; -use actix_web::{client, test, HttpMessage}; use bytes::Bytes; -use futures::future::{self, lazy, ok}; +use futures::future::{self, ok}; use futures::stream::once; use actix_http::{ - body, client as client2, h1, http, Body, Error, HttpMessage as HttpMessage2, - KeepAlive, Request, Response, + body, client, h1, http, test, Body, Error, HttpMessage as HttpMessage2, KeepAlive, + Request, Response, }; #[test] fn test_h1_v2() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) - }).unwrap() - .run(); + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } + let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); } #[test] fn test_slow_request() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::build() - .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) - }).unwrap() - .run(); + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .client_timeout(100) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut stream = net::TcpStream::connect(addr).unwrap(); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); @@ -73,18 +51,11 @@ fn test_slow_request() { #[test] fn test_malformed_request() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) - }).unwrap() - .run(); + let srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut stream = net::TcpStream::connect(addr).unwrap(); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); let mut data = String::new(); let _ = stream.read_to_string(&mut data); @@ -98,51 +69,42 @@ fn test_content_length() { StatusCode, }; - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }).map(|_| ()) - }).unwrap() - .run(); + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); let header = HeaderName::from_static("content-length"); let value = HeaderValue::from_static("0"); - let mut sys = System::new("test"); { for i in 0..4 { - let req = client::ClientRequest::get(format!("http://{}/{}", addr, i)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert_eq!(response.headers().get(&header), None); - let req = client::ClientRequest::head(format!("http://{}/{}", addr, i)) + let req = client::ClientRequest::head(srv.url(&format!("/{}", i))) .finish() .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let response = srv.send_request(req).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = client::ClientRequest::get(format!("http://{}/{}", addr, i)) + let req = client::ClientRequest::get(srv.url(&format!("/{}", i))) .finish() .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let response = srv.send_request(req).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } @@ -153,54 +115,41 @@ fn test_headers() { let data = STR.repeat(10); let data2 = data.clone(); - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let data = data.clone(); - h1::H1Service::new(move |_| { - let mut builder = Response::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - future::ok::<_, ()>(builder.body(data.clone())) - }).map(|_| ()) - }) - .unwrap() - .run() + let mut srv = test::TestServer::with_factory(move || { + let data = data.clone(); + h1::H1Service::new(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(builder.body(data.clone())) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(200)); - let mut sys = System::new("test"); - let mut connector = sys - .block_on(lazy(|| { - Ok::<_, ()>(client2::Connector::default().service()) - })).unwrap(); + let mut connector = srv.new_connector(); - let req = client2::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); + let req = srv.get().finish().unwrap(); - let response = sys.block_on(req.send(&mut connector)).unwrap(); + let response = srv.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from(data2)); } @@ -228,46 +177,27 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) - }).unwrap() - .run(); + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] fn test_head_empty() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::head(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -279,31 +209,20 @@ fn test_head_empty() { } // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert!(bytes.is_empty()); } #[test] fn test_head_binary() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| { - ok::<_, ()>( - Response::Ok().content_length(STR.len() as u64).body(STR), - ) - }).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::head(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -315,27 +234,18 @@ fn test_head_binary() { } // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert!(bytes.is_empty()); } #[test] fn test_head_binary2() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::head(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -349,57 +259,40 @@ fn test_head_binary2() { #[test] fn test_body_length() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().body(Body::from_message( - body::SizedStream::new(STR.len(), body), - ))) - }).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(Body::from_message(body::SizedStream::new(STR.len(), body))), + ) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] fn test_body_chunked_explicit() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| { - let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) - }).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -407,27 +300,18 @@ fn test_body_chunked_explicit() { #[test] fn test_body_chunked_implicit() { - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - h1::H1Service::new(|_| { - let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) - }).map(|_| ()) - }).unwrap() - .run() + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) + }).map(|_| ()) }); - thread::sleep(time::Duration::from_millis(100)); - let mut sys = System::new("test"); - let req = client::ClientRequest::get(format!("http://{}/", addr)) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response - let bytes = sys.block_on(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } diff --git a/tests/test_ws.rs b/tests/test_ws.rs index c246d5e4..21f63512 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -5,20 +5,18 @@ extern crate actix_web; extern crate bytes; extern crate futures; -use std::{io, thread}; +use std::io; -use actix::System; use actix_net::codec::Framed; use actix_net::framed::IntoFramed; -use actix_net::server::Server; -use actix_net::service::{NewServiceExt, Service}; +use actix_net::service::NewServiceExt; use actix_net::stream::TakeItem; -use actix_web::{test, ws as web_ws}; +use actix_web::ws as web_ws; use bytes::{Bytes, BytesMut}; -use futures::future::{lazy, ok, Either}; -use futures::{Future, IntoFuture, Sink, Stream}; +use futures::future::{ok, Either}; +use futures::{Future, Sink, Stream}; -use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; +use actix_http::{h1, test, ws, ResponseError, SendResponse, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -43,72 +41,66 @@ fn ws_service(req: ws::Frame) -> impl Future)| { - // validate request - if let Some(h1::Message::Item(req)) = req { - match ws::verify_handshake(&req) { - Err(e) => { - // validation failed - Either::A( - SendResponse::send(framed, e.error_response()) - .map_err(|_| ()) - .map(|_| ()), - ) - } - Ok(_) => { - Either::B( - // send handshake response - SendResponse::send( - framed, - ws::handshake_response(&req).finish(), - ).map_err(|_| ()) - .and_then(|framed| { - // start websocket service - let framed = - framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_service) - .map_err(|_| ()) - }), - ) - } - } - } else { - panic!() + let mut srv = test::TestServer::with_factory(|| { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) + .and_then(TakeItem::new().map_err(|_| ())) + .and_then(|(req, framed): (_, Framed<_, _>)| { + // validate request + if let Some(h1::Message::Item(req)) = req { + match ws::verify_handshake(&req) { + Err(e) => { + // validation failed + Either::A( + SendResponse::send(framed, e.error_response()) + .map_err(|_| ()) + .map(|_| ()), + ) } - }) - }).unwrap() - .run(); + Ok(_) => { + Either::B( + // send handshake response + SendResponse::send( + framed, + ws::handshake_response(&req).finish(), + ).map_err(|_| ()) + .and_then(|framed| { + // start websocket service + let framed = framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_service) + .map_err(|_| ()) + }), + ) + } + } + } else { + panic!() + } + }) }); - let mut sys = System::new("test"); { - let (reader, mut writer) = sys - .block_on(web_ws::Client::new(format!("http://{}/", addr)).connect()) - .unwrap(); + let url = srv.url("/"); + + let (reader, mut writer) = + srv.block_on(web_ws::Client::new(url).connect()).unwrap(); writer.text("text"); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + let (item, reader) = srv.block_on(reader.into_future()).unwrap(); assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); writer.binary(b"text".as_ref()); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + let (item, reader) = srv.block_on(reader.into_future()).unwrap(); assert_eq!( item, Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) ); writer.ping("ping"); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + let (item, reader) = srv.block_on(reader.into_future()).unwrap(); assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); writer.close(Some(web_ws::CloseCode::Normal.into())); - let (item, _) = sys.block_on(reader.into_future()).unwrap(); + let (item, _) = srv.block_on(reader.into_future()).unwrap(); assert_eq!( item, Some(web_ws::Message::Close(Some( @@ -118,39 +110,33 @@ fn test_simple() { } // client service - let mut client = sys - .block_on(lazy(|| Ok::<_, ()>(ws::Client::default()).into_future())) - .unwrap(); - let framed = sys - .block_on(client.call(ws::Connect::new(format!("http://{}/", addr)))) - .unwrap(); - - let framed = sys + let framed = srv.ws().unwrap(); + let framed = srv .block_on(framed.send(ws::Message::Text("text".to_string()))) .unwrap(); - let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); - let framed = sys + let framed = srv .block_on(framed.send(ws::Message::Binary("text".into()))) .unwrap(); - let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!( item, Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) ); - let framed = sys + let framed = srv .block_on(framed.send(ws::Message::Ping("text".into()))) .unwrap(); - let (item, framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); - let framed = sys + let framed = srv .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) .unwrap(); - let (item, _framed) = sys.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!( item, Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) From 3901239128ac9d076cdaaeb46220117a8043f7ad Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 19 Nov 2018 14:57:12 -0800 Subject: [PATCH 0826/1635] unify requedt/response encoder --- src/client/pipeline.rs | 13 +- src/h1/client.rs | 185 ++++++++++------------------ src/h1/codec.rs | 184 +++++++--------------------- src/h1/decoder.rs | 97 +++++++-------- src/h1/encoder.rs | 268 ++++++++++++++++++++++++++++++++--------- src/message.rs | 8 +- src/request.rs | 21 ++-- src/response.rs | 61 +++++++++- src/test.rs | 2 +- src/ws/mod.rs | 3 +- tests/test_server.rs | 4 +- tests/test_ws.rs | 7 +- 12 files changed, 448 insertions(+), 405 deletions(-) diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 63551cff..e1d8421e 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -49,7 +49,10 @@ where .and_then(|(item, framed)| { if let Some(res) = item { match framed.get_codec().message_type() { - h1::MessageType::None => release_connection(framed), + h1::MessageType::None => { + let force_close = !framed.get_codec().keepalive(); + release_connection(framed, force_close) + } _ => { *res.payload.borrow_mut() = Some(Payload::stream(framed)) } @@ -174,7 +177,9 @@ impl Stream for Payload { Async::Ready(Some(chunk)) => if let Some(chunk) = chunk { Ok(Async::Ready(Some(chunk))) } else { - release_connection(self.framed.take().unwrap()); + let framed = self.framed.take().unwrap(); + let force_close = framed.get_codec().keepalive(); + release_connection(framed, force_close); Ok(Async::Ready(None)) }, Async::Ready(None) => Ok(Async::Ready(None)), @@ -182,12 +187,12 @@ impl Stream for Payload { } } -fn release_connection(framed: Framed) +fn release_connection(framed: Framed, force_close: bool) where T: Connection, { let mut parts = framed.into_parts(); - if parts.read_buf.is_empty() && parts.write_buf.is_empty() { + if !force_close && parts.read_buf.is_empty() && parts.write_buf.is_empty() { parts.io.release() } else { parts.io.close() diff --git a/src/h1/client.rs b/src/h1/client.rs index e2d1eefe..7704ba97 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -4,8 +4,8 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; -use super::encoder::RequestEncoder; +use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; +use super::{decoder, encoder}; use super::{Message, MessageType}; use body::BodyLength; use client::ClientResponse; @@ -16,13 +16,11 @@ use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use message::{Head, MessagePool, RequestHead}; +use message::{ConnectionType, Head, MessagePool, RequestHead}; bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; const KEEPALIVE_ENABLED = 0b0000_1000; const STREAM = 0b0001_0000; } @@ -42,14 +40,15 @@ pub struct ClientPayloadCodec { struct ClientCodecInner { config: ServiceConfig, - decoder: MessageDecoder, + decoder: decoder::MessageDecoder, payload: Option, version: Version, + ctype: ConnectionType, // encoder part flags: Flags, headers_size: u32, - te: RequestEncoder, + encoder: encoder::MessageEncoder, } impl Default for ClientCodec { @@ -71,25 +70,26 @@ impl ClientCodec { ClientCodec { inner: ClientCodecInner { config, - decoder: MessageDecoder::default(), + decoder: decoder::MessageDecoder::default(), payload: None, version: Version::HTTP_11, + ctype: ConnectionType::Close, flags, headers_size: 0, - te: RequestEncoder::default(), + encoder: encoder::MessageEncoder::default(), }, } } /// Check if request is upgrade pub fn upgrade(&self) -> bool { - self.inner.flags.contains(Flags::UPGRADE) + self.inner.ctype == ConnectionType::Upgrade } /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { - self.inner.flags.contains(Flags::KEEPALIVE) + self.inner.ctype == ConnectionType::KeepAlive } /// Check last request's message type @@ -103,15 +103,6 @@ impl ClientCodec { } } - /// prepare transfer encoding - pub fn prepare_te(&mut self, head: &mut RequestHead, length: BodyLength) { - self.inner.te.update( - head, - self.inner.flags.contains(Flags::HEAD), - self.inner.version, - ); - } - /// Convert message codec to a payload codec pub fn into_payload_codec(self) -> ClientPayloadCodec { ClientPayloadCodec { inner: self.inner } @@ -119,96 +110,17 @@ impl ClientCodec { } impl ClientPayloadCodec { + /// Check if last response is keep-alive + pub fn keepalive(&self) -> bool { + self.inner.ctype == ConnectionType::KeepAlive + } + /// Transform payload codec to a message codec pub fn into_message_codec(self) -> ClientCodec { ClientCodec { inner: self.inner } } } -fn prn_version(ver: Version) -> &'static str { - match ver { - Version::HTTP_09 => "HTTP/0.9", - Version::HTTP_10 => "HTTP/1.0", - Version::HTTP_11 => "HTTP/1.1", - Version::HTTP_2 => "HTTP/2.0", - } -} - -impl ClientCodecInner { - fn encode_request( - &mut self, - msg: RequestHead, - length: BodyLength, - buffer: &mut BytesMut, - ) -> io::Result<()> { - // render message - { - // status line - write!( - Writer(buffer), - "{} {} {}\r\n", - msg.method, - msg.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), - prn_version(msg.version) - ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - // write headers - buffer.reserve(msg.headers.len() * AVERAGE_HEADER_SIZE); - - // content length - match length { - BodyLength::Sized(len) => helpers::write_content_length(len, buffer), - BodyLength::Sized64(len) => { - buffer.extend_from_slice(b"content-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - BodyLength::Chunked => { - buffer.extend_from_slice(b"transfer-encoding: chunked\r\n") - } - BodyLength::Empty => buffer.extend_from_slice(b"content-length: 0\r\n"), - BodyLength::None | BodyLength::Stream => (), - } - - let mut has_date = false; - - for (key, value) in &msg.headers { - match *key { - TRANSFER_ENCODING | CONNECTION | CONTENT_LENGTH => continue, - DATE => has_date = true, - _ => (), - } - - buffer.put_slice(key.as_ref()); - buffer.put_slice(b": "); - buffer.put_slice(value.as_ref()); - buffer.put_slice(b"\r\n"); - } - - // Connection header - if msg.upgrade() { - self.flags.set(Flags::UPGRADE, msg.upgrade()); - buffer.extend_from_slice(b"connection: upgrade\r\n"); - } else if msg.keep_alive() { - if self.version < Version::HTTP_11 { - buffer.extend_from_slice(b"connection: keep-alive\r\n"); - } - } else if self.version >= Version::HTTP_11 { - buffer.extend_from_slice(b"connection: close\r\n"); - } - - // Date header - if !has_date { - self.config.set_date(buffer); - } else { - buffer.extend_from_slice(b"\r\n"); - } - } - - Ok(()) - } -} - impl Decoder for ClientCodec { type Item = ClientResponse; type Error = ParseError; @@ -217,21 +129,27 @@ impl Decoder for ClientCodec { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - // self.inner - // .flags - // .set(Flags::HEAD, req.head.method == Method::HEAD); - // self.inner.version = req.head.version; - if self.inner.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.inner.flags.set(Flags::KEEPALIVE, req.keep_alive()); + if let Some(ctype) = req.head().ctype { + // do not use peer's keep-alive + self.inner.ctype = if ctype == ConnectionType::KeepAlive { + self.inner.ctype + } else { + ctype + }; } - match payload { - PayloadType::None => self.inner.payload = None, - PayloadType::Payload(pl) => self.inner.payload = Some(pl), - PayloadType::Stream(pl) => { - self.inner.payload = Some(pl); - self.inner.flags.insert(Flags::STREAM); + + if !self.inner.flags.contains(Flags::HEAD) { + match payload { + PayloadType::None => self.inner.payload = None, + PayloadType::Payload(pl) => self.inner.payload = Some(pl), + PayloadType::Stream(pl) => { + self.inner.payload = Some(pl); + self.inner.flags.insert(Flags::STREAM); + } } - }; + } else { + self.inner.payload = None; + } Ok(Some(req)) } else { Ok(None) @@ -270,14 +188,39 @@ impl Encoder for ClientCodec { dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - Message::Item((msg, btype)) => { - self.inner.encode_request(msg, btype, dst)?; + Message::Item((mut msg, length)) => { + let inner = &mut self.inner; + inner.version = msg.version; + inner.flags.set(Flags::HEAD, msg.method == Method::HEAD); + + // connection status + inner.ctype = match msg.connection_type() { + ConnectionType::KeepAlive => { + if inner.flags.contains(Flags::KEEPALIVE_ENABLED) { + ConnectionType::KeepAlive + } else { + ConnectionType::Close + } + } + ConnectionType::Upgrade => ConnectionType::Upgrade, + ConnectionType::Close => ConnectionType::Close, + }; + + inner.encoder.encode( + dst, + &mut msg, + false, + inner.version, + length, + inner.ctype, + &inner.config, + )?; } Message::Chunk(Some(bytes)) => { - self.inner.te.encode(bytes.as_ref(), dst)?; + self.inner.encoder.encode_chunk(bytes.as_ref(), dst)?; } Message::Chunk(None) => { - self.inner.te.encode_eof(dst)?; + self.inner.encoder.encode_eof(dst)?; } } Ok(()) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 117c8cde..f9f455e5 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -5,8 +5,8 @@ use std::io::{self, Write}; use bytes::{BufMut, Bytes, BytesMut}; use tokio_codec::{Decoder, Encoder}; -use super::decoder::{MessageDecoder, PayloadDecoder, PayloadItem, PayloadType}; -use super::encoder::ResponseEncoder; +use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; +use super::{decoder, encoder}; use super::{Message, MessageType}; use body::BodyLength; use config::ServiceConfig; @@ -14,15 +14,13 @@ use error::ParseError; use helpers; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, StatusCode, Version}; -use message::{Head, ResponseHead}; +use message::{ConnectionType, Head, ResponseHead}; use request::Request; use response::Response; bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; const KEEPALIVE_ENABLED = 0b0000_1000; const STREAM = 0b0001_0000; } @@ -33,14 +31,15 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// HTTP/1 Codec pub struct Codec { config: ServiceConfig, - decoder: MessageDecoder, + decoder: decoder::MessageDecoder, payload: Option, version: Version, + ctype: ConnectionType, // encoder part flags: Flags, headers_size: u32, - te: ResponseEncoder, + encoder: encoder::MessageEncoder>, } impl Default for Codec { @@ -67,24 +66,25 @@ impl Codec { }; Codec { config, - decoder: MessageDecoder::default(), + decoder: decoder::MessageDecoder::default(), payload: None, version: Version::HTTP_11, + ctype: ConnectionType::Close, flags, headers_size: 0, - te: ResponseEncoder::default(), + encoder: encoder::MessageEncoder::default(), } } /// Check if request is upgrade pub fn upgrade(&self) -> bool { - self.flags.contains(Flags::UPGRADE) + self.ctype == ConnectionType::Upgrade } /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) + self.ctype == ConnectionType::KeepAlive } /// Check last request's message type @@ -97,130 +97,6 @@ impl Codec { MessageType::Payload } } - - /// prepare transfer encoding - fn prepare_te(&mut self, head: &mut ResponseHead, length: BodyLength) { - self.te - .update(head, self.flags.contains(Flags::HEAD), self.version, length); - } - - fn encode_response( - &mut self, - msg: &mut ResponseHead, - length: BodyLength, - buffer: &mut BytesMut, - ) -> io::Result<()> { - msg.version = self.version; - - // Connection upgrade - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - self.flags.remove(Flags::KEEPALIVE); - msg.headers - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - // keep-alive - else if self.flags.contains(Flags::KEEPALIVE_ENABLED) && msg.keep_alive() { - self.flags.insert(Flags::KEEPALIVE); - if self.version < Version::HTTP_11 { - msg.headers - .insert(CONNECTION, HeaderValue::from_static("keep-alive")); - } - } else if self.version >= Version::HTTP_11 { - self.flags.remove(Flags::KEEPALIVE); - msg.headers - .insert(CONNECTION, HeaderValue::from_static("close")); - } - - // render message - { - let reason = msg.reason().as_bytes(); - buffer.reserve(256 + msg.headers.len() * AVERAGE_HEADER_SIZE + reason.len()); - - // status line - helpers::write_status_line(self.version, msg.status.as_u16(), buffer); - buffer.extend_from_slice(reason); - - // content length - match msg.status { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => buffer.extend_from_slice(b"\r\n"), - _ => match length { - BodyLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - BodyLength::Empty => { - buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"); - } - BodyLength::Sized(len) => helpers::write_content_length(len, buffer), - BodyLength::Sized64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - BodyLength::None | BodyLength::Stream => { - buffer.extend_from_slice(b"\r\n") - } - }, - } - - // write headers - let mut pos = 0; - let mut has_date = false; - let mut remaining = buffer.remaining_mut(); - let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in &msg.headers { - match *key { - TRANSFER_ENCODING | CONTENT_LENGTH => continue, - DATE => { - has_date = true; - } - _ => (), - } - - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { - buffer.advance_mut(pos); - } - pos = 0; - buffer.reserve(len); - remaining = buffer.remaining_mut(); - unsafe { - buf = &mut *(buffer.bytes_mut() as *mut _); - } - } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; - } - unsafe { - buffer.advance_mut(pos); - } - - // optimized date header, set_date writes \r\n - if !has_date { - self.config.set_date(buffer); - } else { - // msg eof - buffer.extend_from_slice(b"\r\n"); - } - self.headers_size = buffer.len() as u32; - } - - Ok(()) - } } impl Decoder for Codec { @@ -240,9 +116,12 @@ impl Decoder for Codec { } else if let Some((req, payload)) = self.decoder.decode(src)? { self.flags .set(Flags::HEAD, req.inner.head.method == Method::HEAD); - self.version = req.inner.head.version; - if self.flags.contains(Flags::KEEPALIVE_ENABLED) { - self.flags.set(Flags::KEEPALIVE, req.keep_alive()); + self.version = req.inner().head.version; + self.ctype = req.inner().head.connection_type(); + if self.ctype == ConnectionType::KeepAlive + && !self.flags.contains(Flags::KEEPALIVE_ENABLED) + { + self.ctype = ConnectionType::Close } match payload { PayloadType::None => self.payload = None, @@ -270,14 +149,35 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { Message::Item((mut res, length)) => { - self.prepare_te(res.head_mut(), length); - self.encode_response(res.head_mut(), length, dst)?; + // connection status + self.ctype = if let Some(ct) = res.head().ctype { + if ct == ConnectionType::KeepAlive { + self.ctype + } else { + ct + } + } else { + self.ctype + }; + + // encode message + let len = dst.len(); + self.encoder.encode( + dst, + &mut res, + self.flags.contains(Flags::HEAD), + self.version, + length, + self.ctype, + &self.config, + )?; + self.headers_size = (dst.len() - len) as u32; } Message::Chunk(Some(bytes)) => { - self.te.encode(bytes.as_ref(), dst)?; + self.encoder.encode_chunk(bytes.as_ref(), dst)?; } Message::Chunk(None) => { - self.te.encode_eof(dst)?; + self.encoder.encode_eof(dst)?; } } Ok(()) diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index de0df367..a081a5cf 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -10,15 +10,16 @@ use client::ClientResponse; use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; -use message::{ConnectionType, Head}; +use message::ConnectionType; use request::Request; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; /// Incoming messagd decoder -pub(crate) struct MessageDecoder(PhantomData); +pub(crate) struct MessageDecoder(PhantomData); +#[derive(Debug)] /// Incoming request type pub(crate) enum PayloadType { None, @@ -26,13 +27,13 @@ pub(crate) enum PayloadType { Stream(PayloadDecoder), } -impl Default for MessageDecoder { +impl Default for MessageDecoder { fn default() -> Self { MessageDecoder(PhantomData) } } -impl Decoder for MessageDecoder { +impl Decoder for MessageDecoder { type Item = (T, PayloadType); type Error = ParseError; @@ -47,10 +48,8 @@ pub(crate) enum PayloadLength { None, } -pub(crate) trait MessageTypeDecoder: Sized { - fn keep_alive(&mut self); - - fn force_close(&mut self); +pub(crate) trait MessageType: Sized { + fn set_connection_type(&mut self, ctype: Option); fn headers_mut(&mut self) -> &mut HeaderMap; @@ -59,10 +58,9 @@ pub(crate) trait MessageTypeDecoder: Sized { fn set_headers( &mut self, slice: &Bytes, - version: Version, raw_headers: &[HeaderIndex], ) -> Result { - let mut ka = version != Version::HTTP_10; + let mut ka = None; let mut has_upgrade = false; let mut chunked = false; let mut content_length = None; @@ -104,18 +102,18 @@ pub(crate) trait MessageTypeDecoder: Sized { // connection keep-alive state header::CONNECTION => { ka = if let Ok(conn) = value.to_str() { - if version == Version::HTTP_10 - && conn.contains("keep-alive") - { - true + if conn.contains("keep-alive") { + Some(ConnectionType::KeepAlive) + } else if conn.contains("close") { + Some(ConnectionType::Close) + } else if conn.contains("upgrade") { + Some(ConnectionType::Upgrade) } else { - version == Version::HTTP_11 && !(conn - .contains("close") - || conn.contains("upgrade")) + None } } else { - false - } + None + }; } header::UPGRADE => { has_upgrade = true; @@ -136,12 +134,7 @@ pub(crate) trait MessageTypeDecoder: Sized { } } } - - if ka { - self.keep_alive(); - } else { - self.force_close(); - } + self.set_connection_type(ka); // https://tools.ietf.org/html/rfc7230#section-3.3.3 if chunked { @@ -162,17 +155,9 @@ pub(crate) trait MessageTypeDecoder: Sized { } } -impl MessageTypeDecoder for Request { - fn keep_alive(&mut self) { - self.inner_mut() - .head - .set_connection_type(ConnectionType::KeepAlive) - } - - fn force_close(&mut self) { - self.inner_mut() - .head - .set_connection_type(ConnectionType::Close) +impl MessageType for Request { + fn set_connection_type(&mut self, ctype: Option) { + self.inner_mut().head.ctype = ctype; } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -210,8 +195,7 @@ impl MessageTypeDecoder for Request { let mut msg = Request::new(); // convert headers - let len = - msg.set_headers(&src.split_to(len).freeze(), ver, &headers[..h_len])?; + let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; // payload decoder let decoder = match len { @@ -243,13 +227,9 @@ impl MessageTypeDecoder for Request { } } -impl MessageTypeDecoder for ClientResponse { - fn keep_alive(&mut self) { - self.head.set_connection_type(ConnectionType::KeepAlive); - } - - fn force_close(&mut self) { - self.head.set_connection_type(ConnectionType::Close); +impl MessageType for ClientResponse { + fn set_connection_type(&mut self, ctype: Option) { + self.head.ctype = ctype; } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -286,8 +266,7 @@ impl MessageTypeDecoder for ClientResponse { let mut msg = ClientResponse::new(); // convert headers - let len = - msg.set_headers(&src.split_to(len).freeze(), ver, &headers[..h_len])?; + let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; // message payload let decoder = if let PayloadLength::Payload(pl) = len { @@ -634,6 +613,7 @@ mod tests { use super::*; use error::ParseError; use httpmessage::HttpMessage; + use message::Head; impl PayloadType { fn unwrap(self) -> PayloadDecoder { @@ -886,7 +866,7 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(!req.keep_alive()); + assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] @@ -894,7 +874,7 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); let req = parse_ready!(&mut buf); - assert!(req.keep_alive()); + assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] @@ -905,7 +885,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(!req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); } #[test] @@ -916,7 +896,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(!req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); } #[test] @@ -927,7 +907,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); } #[test] @@ -938,7 +918,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); } #[test] @@ -949,7 +929,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(!req.keep_alive()); + assert_eq!(req.inner().head.connection_type(), ConnectionType::Close); } #[test] @@ -960,7 +940,11 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert!(req.keep_alive()); + assert_eq!(req.inner().head.ctype, None); + assert_eq!( + req.inner().head.connection_type(), + ConnectionType::KeepAlive + ); } #[test] @@ -973,6 +957,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); } #[test] @@ -1070,7 +1055,7 @@ mod tests { ); let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - assert!(!req.keep_alive()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); assert!(req.upgrade()); assert!(pl.is_unhandled()); } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 1664af16..f9b4aab3 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -1,40 +1,217 @@ #![allow(unused_imports, unused_variables, dead_code)] use std::fmt::Write as FmtWrite; use std::io::Write; +use std::marker::PhantomData; use std::str::FromStr; use std::{cmp, fmt, io, mem}; -use bytes::{Bytes, BytesMut}; -use http::header::{HeaderValue, ACCEPT_ENCODING, CONTENT_LENGTH}; -use http::{StatusCode, Version}; +use bytes::{BufMut, Bytes, BytesMut}; +use http::header::{ + HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; +use http::{HeaderMap, StatusCode, Version}; use body::BodyLength; +use config::ServiceConfig; use header::ContentEncoding; +use helpers; use http::Method; -use message::{RequestHead, ResponseHead}; +use message::{ConnectionType, RequestHead, ResponseHead}; use request::Request; use response::Response; +const AVERAGE_HEADER_SIZE: usize = 30; + #[derive(Debug)] -pub(crate) struct ResponseEncoder { - head: bool, +pub(crate) struct MessageEncoder { pub length: BodyLength, pub te: TransferEncoding, + _t: PhantomData, } -impl Default for ResponseEncoder { +impl Default for MessageEncoder { fn default() -> Self { - ResponseEncoder { - head: false, + MessageEncoder { length: BodyLength::None, te: TransferEncoding::empty(), + _t: PhantomData, } } } -impl ResponseEncoder { +pub(crate) trait MessageType: Sized { + fn status(&self) -> Option; + + fn connection_type(&self) -> Option; + + fn headers(&self) -> &HeaderMap; + + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>; + + fn encode_headers( + &mut self, + dst: &mut BytesMut, + version: Version, + mut length: BodyLength, + ctype: ConnectionType, + config: &ServiceConfig, + ) -> io::Result<()> { + let mut skip_len = length != BodyLength::Stream; + + // Content length + if let Some(status) = self.status() { + match status { + StatusCode::NO_CONTENT + | StatusCode::CONTINUE + | StatusCode::PROCESSING => length = BodyLength::None, + StatusCode::SWITCHING_PROTOCOLS => { + skip_len = true; + length = BodyLength::Stream; + } + _ => (), + } + } + match length { + BodyLength::Chunked => { + dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } + BodyLength::Empty => { + dst.extend_from_slice(b"\r\ncontent-length: 0\r\n"); + } + BodyLength::Sized(len) => helpers::write_content_length(len, dst), + BodyLength::Sized64(len) => { + dst.extend_from_slice(b"\r\ncontent-length: "); + write!(dst.writer(), "{}", len)?; + dst.extend_from_slice(b"\r\n"); + } + BodyLength::None | BodyLength::Stream => dst.extend_from_slice(b"\r\n"), + } + + // Connection + match ctype { + ConnectionType::Upgrade => dst.extend_from_slice(b"connection: upgrade\r\n"), + ConnectionType::KeepAlive if version < Version::HTTP_11 => { + dst.extend_from_slice(b"connection: keep-alive\r\n") + } + ConnectionType::Close if version >= Version::HTTP_11 => { + dst.extend_from_slice(b"connection: close\r\n") + } + _ => (), + } + + // write headers + let mut pos = 0; + let mut has_date = false; + let mut remaining = dst.remaining_mut(); + let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; + for (key, value) in self.headers() { + match key { + &CONNECTION => continue, + &TRANSFER_ENCODING | &CONTENT_LENGTH if skip_len => continue, + &DATE => { + has_date = true; + } + _ => (), + } + + let v = value.as_ref(); + let k = key.as_str().as_bytes(); + let len = k.len() + v.len() + 4; + if len > remaining { + unsafe { + dst.advance_mut(pos); + } + pos = 0; + dst.reserve(len); + remaining = dst.remaining_mut(); + unsafe { + buf = &mut *(dst.bytes_mut() as *mut _); + } + } + + buf[pos..pos + k.len()].copy_from_slice(k); + pos += k.len(); + buf[pos..pos + 2].copy_from_slice(b": "); + pos += 2; + buf[pos..pos + v.len()].copy_from_slice(v); + pos += v.len(); + buf[pos..pos + 2].copy_from_slice(b"\r\n"); + pos += 2; + remaining -= len; + } + unsafe { + dst.advance_mut(pos); + } + + // optimized date header, set_date writes \r\n + if !has_date { + config.set_date(dst); + } else { + // msg eof + dst.extend_from_slice(b"\r\n"); + } + + Ok(()) + } +} + +impl MessageType for Response<()> { + fn status(&self) -> Option { + Some(self.head().status) + } + + fn connection_type(&self) -> Option { + self.head().ctype + } + + fn headers(&self) -> &HeaderMap { + &self.head().headers + } + + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { + let head = self.head(); + let reason = head.reason().as_bytes(); + dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE + reason.len()); + + // status line + helpers::write_status_line(head.version, head.status.as_u16(), dst); + dst.extend_from_slice(reason); + Ok(()) + } +} + +impl MessageType for RequestHead { + fn status(&self) -> Option { + None + } + + fn connection_type(&self) -> Option { + self.ctype + } + + fn headers(&self) -> &HeaderMap { + &self.headers + } + + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { + write!( + Writer(dst), + "{} {} {}", + self.method, + self.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), + match self.version { + Version::HTTP_09 => "HTTP/0.9", + Version::HTTP_10 => "HTTP/1.0", + Version::HTTP_11 => "HTTP/1.1", + Version::HTTP_2 => "HTTP/2.0", + } + ).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } +} + +impl MessageEncoder { /// Encode message - pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { + pub fn encode_chunk(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { self.te.encode(msg, buf) } @@ -43,59 +220,32 @@ impl ResponseEncoder { self.te.encode_eof(buf) } - pub fn update( + pub fn encode( &mut self, - resp: &mut ResponseHead, + dst: &mut BytesMut, + message: &mut T, head: bool, version: Version, length: BodyLength, - ) { - self.head = head; - let transfer = match length { - BodyLength::Empty => TransferEncoding::empty(), - BodyLength::Sized(len) => TransferEncoding::length(len as u64), - BodyLength::Sized64(len) => TransferEncoding::length(len), - BodyLength::Chunked => TransferEncoding::chunked(), - BodyLength::Stream => TransferEncoding::eof(), - BodyLength::None => TransferEncoding::length(0), - }; - // check for head response - if !self.head { - self.te = transfer; + ctype: ConnectionType, + config: &ServiceConfig, + ) -> io::Result<()> { + // transfer encoding + if !head { + self.te = match length { + BodyLength::Empty => TransferEncoding::empty(), + BodyLength::Sized(len) => TransferEncoding::length(len as u64), + BodyLength::Sized64(len) => TransferEncoding::length(len), + BodyLength::Chunked => TransferEncoding::chunked(), + BodyLength::Stream => TransferEncoding::eof(), + BodyLength::None => TransferEncoding::empty(), + }; + } else { + self.te = TransferEncoding::empty(); } - } -} -#[derive(Debug)] -pub(crate) struct RequestEncoder { - head: bool, - pub length: BodyLength, - pub te: TransferEncoding, -} - -impl Default for RequestEncoder { - fn default() -> Self { - RequestEncoder { - head: false, - length: BodyLength::None, - te: TransferEncoding::empty(), - } - } -} - -impl RequestEncoder { - /// Encode message - pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { - self.te.encode(msg, buf) - } - - /// Encode eof - pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { - self.te.encode_eof(buf) - } - - pub fn update(&mut self, resp: &mut RequestHead, head: bool, version: Version) { - self.head = head; + message.encode_status(dst)?; + message.encode_headers(dst, version, length, ctype, config) } } @@ -123,7 +273,7 @@ impl TransferEncoding { #[inline] pub fn empty() -> TransferEncoding { TransferEncoding { - kind: TransferEncodingKind::Eof, + kind: TransferEncodingKind::Length(0), } } diff --git a/src/message.rs b/src/message.rs index e1059fa4..d3d52e2f 100644 --- a/src/message.rs +++ b/src/message.rs @@ -39,12 +39,13 @@ pub trait Head: Default + 'static { fn pool() -> &'static MessagePool; } +#[derive(Debug)] pub struct RequestHead { pub uri: Uri, pub method: Method, pub version: Version, pub headers: HeaderMap, - ctype: Option, + pub ctype: Option, } impl Default for RequestHead { @@ -72,7 +73,7 @@ impl Head for RequestHead { fn connection_type(&self) -> ConnectionType { if let Some(ct) = self.ctype { ct - } else if self.version <= Version::HTTP_11 { + } else if self.version < Version::HTTP_11 { ConnectionType::Close } else { ConnectionType::KeepAlive @@ -84,6 +85,7 @@ impl Head for RequestHead { } } +#[derive(Debug)] pub struct ResponseHead { pub version: Version, pub status: StatusCode, @@ -118,7 +120,7 @@ impl Head for ResponseHead { fn connection_type(&self) -> ConnectionType { if let Some(ct) = self.ctype { ct - } else if self.version <= Version::HTTP_11 { + } else if self.version < Version::HTTP_11 { ConnectionType::Close } else { ConnectionType::KeepAlive diff --git a/src/request.rs b/src/request.rs index d529c09f..248555e6 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,7 +8,7 @@ use extensions::Extensions; use httpmessage::HttpMessage; use payload::Payload; -use message::{Head, Message, MessagePool, RequestHead}; +use message::{Message, MessagePool, RequestHead}; /// Request pub struct Request { @@ -67,6 +67,19 @@ impl Request { Rc::get_mut(&mut self.inner).expect("Multiple copies exist") } + #[inline] + /// Http message part of the request + pub fn head(&self) -> &RequestHead { + &self.inner.as_ref().head + } + + #[inline] + #[doc(hidden)] + /// Mutable reference to a http message part of the request + pub fn head_mut(&mut self) -> &mut RequestHead { + &mut self.inner_mut().head + } + /// Request's uri. #[inline] pub fn uri(&self) -> &Uri { @@ -109,12 +122,6 @@ impl Request { &mut self.inner_mut().head.headers } - /// Checks if a connection should be kept alive. - #[inline] - pub fn keep_alive(&self) -> bool { - self.inner().head.keep_alive() - } - /// Request extensions #[inline] pub fn extensions(&self) -> Ref { diff --git a/src/response.rs b/src/response.rs index f2ed7292..bc730718 100644 --- a/src/response.rs +++ b/src/response.rs @@ -85,7 +85,14 @@ impl Response { } #[inline] - pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { + /// Http message part of the response + pub fn head(&self) -> &ResponseHead { + &self.0.as_ref().head + } + + #[inline] + /// Mutable reference to a http message part of the response + pub fn head_mut(&mut self) -> &mut ResponseHead { &mut self.0.as_mut().head } @@ -314,7 +321,7 @@ impl ResponseBuilder { self } - /// Set a header. + /// Append a header to existing headers. /// /// ```rust,ignore /// # extern crate actix_web; @@ -347,6 +354,39 @@ impl ResponseBuilder { self } + /// Set a header. + /// + /// ```rust,ignore + /// # extern crate actix_web; + /// use actix_web::{http, Request, Response}; + /// + /// fn index(req: HttpRequest) -> Response { + /// Response::Ok() + /// .set_header("X-TEST", "value") + /// .set_header(http::header::CONTENT_TYPE, "application/json") + /// .finish() + /// } + /// fn main() {} + /// ``` + pub fn set_header(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.response, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + parts.head.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + /// Set the custom reason for the response. #[inline] pub fn reason(&mut self, reason: &'static str) -> &mut Self { @@ -367,11 +407,14 @@ impl ResponseBuilder { /// Set connection type to Upgrade #[inline] - pub fn upgrade(&mut self) -> &mut Self { + pub fn upgrade(&mut self, value: V) -> &mut Self + where + V: IntoHeaderValue, + { if let Some(parts) = parts(&mut self.response, &self.err) { parts.head.set_connection_type(ConnectionType::Upgrade); } - self + self.set_header(header::UPGRADE, value) } /// Force close connection, even if it is marked as keep-alive @@ -880,8 +923,14 @@ mod tests { #[test] fn test_upgrade() { - let resp = Response::build(StatusCode::OK).upgrade().finish(); - assert!(resp.upgrade()) + let resp = Response::build(StatusCode::OK) + .upgrade("websocket") + .finish(); + assert!(resp.upgrade()); + assert_eq!( + resp.headers().get(header::UPGRADE).unwrap(), + HeaderValue::from_static("websocket") + ); } #[test] diff --git a/src/test.rs b/src/test.rs index 9e14129b..8f90246b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -443,7 +443,7 @@ where ) -> Result, ws::ClientError> { let url = self.url(path); self.rt - .block_on(ws::Client::default().call(ws::Connect::new(url))) + .block_on(lazy(|| ws::Client::default().call(ws::Connect::new(url)))) } /// Connect to a websocket server diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 5c86d8c4..f1c91714 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -183,8 +183,7 @@ pub fn handshake_response(req: &Request) -> ResponseBuilder { }; Response::build(StatusCode::SWITCHING_PROTOCOLS) - .upgrade() - .header(header::UPGRADE, "websocket") + .upgrade("websocket") .header(header::TRANSFER_ENCODING, "chunked") .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) .take() diff --git a/tests/test_server.rs b/tests/test_server.rs index a01af4f0..c6e03e28 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -89,7 +89,9 @@ fn test_content_length() { { for i in 0..4 { - let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); + let req = client::ClientRequest::get(srv.url(&format!("/{}", i))) + .finish() + .unwrap(); let response = srv.send_request(req).unwrap(); assert_eq!(response.headers().get(&header), None); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 21f63512..22ce3ca2 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -13,7 +13,7 @@ use actix_net::service::NewServiceExt; use actix_net::stream::TakeItem; use actix_web::ws as web_ws; use bytes::{Bytes, BytesMut}; -use futures::future::{ok, Either}; +use futures::future::{lazy, ok, Either}; use futures::{Future, Sink, Stream}; use actix_http::{h1, test, ws, ResponseError, SendResponse, ServiceConfig}; @@ -81,8 +81,9 @@ fn test_simple() { { let url = srv.url("/"); - let (reader, mut writer) = - srv.block_on(web_ws::Client::new(url).connect()).unwrap(); + let (reader, mut writer) = srv + .block_on(lazy(|| web_ws::Client::new(url).connect())) + .unwrap(); writer.text("text"); let (item, reader) = srv.block_on(reader.into_future()).unwrap(); From 6b60c9e2302abb1a40d06af271a08c7cf19a1cc8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 19 Nov 2018 16:11:58 -0800 Subject: [PATCH 0827/1635] add debug impl for H1ServiceResult --- src/h1/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 395d6619..da80e55e 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,4 +1,6 @@ //! HTTP/1 implementation +use std::fmt; + use actix_net::codec::Framed; use bytes::Bytes; @@ -23,6 +25,20 @@ pub enum H1ServiceResult { Unhandled(Request, Framed), } +impl fmt::Debug for H1ServiceResult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + H1ServiceResult::Disconnected => write!(f, "H1ServiceResult::Disconnected"), + H1ServiceResult::Shutdown(ref v) => { + write!(f, "H1ServiceResult::Shutdown({:?})", v) + } + H1ServiceResult::Unhandled(ref req, _) => { + write!(f, "H1ServiceResult::Unhandled({:?})", req) + } + } + } +} + #[derive(Debug)] /// Codec message pub enum Message { From e1fc6dea844f0ffe47d12d2b724b93cb6c23479b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 19 Nov 2018 16:39:40 -0800 Subject: [PATCH 0828/1635] restore execute method --- src/test.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test.rs b/src/test.rs index 8f90246b..84a959c4 100644 --- a/src/test.rs +++ b/src/test.rs @@ -375,6 +375,14 @@ impl TestServerRuntime { self.rt.block_on(fut) } + /// Execute future on current core + pub fn execute(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + /// Construct test server url pub fn addr(&self) -> net::SocketAddr { self.addr From 6a9317847979a17fa8d39f05d15188cbb7dde902 Mon Sep 17 00:00:00 2001 From: Huston Bokinsky Date: Sat, 17 Nov 2018 15:25:44 -0800 Subject: [PATCH 0829/1635] Complete error helper functions. --- src/error.rs | 312 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/src/error.rs b/src/error.rs index 76c8e79e..1766c152 100644 --- a/src/error.rs +++ b/src/error.rs @@ -759,6 +759,16 @@ where InternalError::new(err, StatusCode::UNAUTHORIZED).into() } +/// Helper function that creates wrapper of any error and generate +/// *PAYMENT_REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorPaymentRequired(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate *FORBIDDEN* /// response. #[allow(non_snake_case)] @@ -789,6 +799,26 @@ where InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() } +/// Helper function that creates wrapper of any error and generate *NOT +/// ACCEPTABLE* response. +#[allow(non_snake_case)] +pub fn ErrorNotAcceptable(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into() +} + +/// Helper function that creates wrapper of any error and generate *PROXY +/// AUTHENTICATION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorProxyAuthenticationRequired(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate *REQUEST /// TIMEOUT* response. #[allow(non_snake_case)] @@ -819,6 +849,16 @@ where InternalError::new(err, StatusCode::GONE).into() } +/// Helper function that creates wrapper of any error and generate *LENGTH +/// REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorLengthRequired(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LENGTH_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate /// *PRECONDITION FAILED* response. #[allow(non_snake_case)] @@ -829,6 +869,46 @@ where InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() } +/// Helper function that creates wrapper of any error and generate +/// *PAYLOAD TOO LARGE* response. +#[allow(non_snake_case)] +pub fn ErrorPayloadTooLarge(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *URI TOO LONG* response. +#[allow(non_snake_case)] +pub fn ErrorUriTooLong(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::URI_TOO_LONG).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNSUPPORTED MEDIA TYPE* response. +#[allow(non_snake_case)] +pub fn ErrorUnsupportedMediaType(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *RANGE NOT SATISFIABLE* response. +#[allow(non_snake_case)] +pub fn ErrorRangeNotSatisfiable(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into() +} + /// Helper function that creates wrapper of any error and generate /// *EXPECTATION FAILED* response. #[allow(non_snake_case)] @@ -839,6 +919,106 @@ where InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() } +/// Helper function that creates wrapper of any error and generate +/// *IM A TEAPOT* response. +#[allow(non_snake_case)] +pub fn ErrorImATeapot(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::IM_A_TEAPOT).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *MISDIRECTED REQUEST* response. +#[allow(non_snake_case)] +pub fn ErrorMisdirectedRequest(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNPROCESSABLE ENTITY* response. +#[allow(non_snake_case)] +pub fn ErrorUnprocessableEntity(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *LOCKED* response. +#[allow(non_snake_case)] +pub fn ErrorLocked(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LOCKED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *FAILED DEPENDENCY* response. +#[allow(non_snake_case)] +pub fn ErrorFailedDependency(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UPGRADE REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorUpgradeRequired(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *PRECONDITION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorPreconditionRequired(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *TOO MANY REQUESTS* response. +#[allow(non_snake_case)] +pub fn ErrorTooManyRequests(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *REQUEST HEADER FIELDS TOO LARGE* response. +#[allow(non_snake_case)] +pub fn ErrorRequestHeaderFieldsTooLarge(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNAVAILABLE FOR LEGAL REASONS* response. +#[allow(non_snake_case)] +pub fn ErrorUnavailableForLegalReasons(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into() +} + /// Helper function that creates wrapper of any error and /// generate *INTERNAL SERVER ERROR* response. #[allow(non_snake_case)] @@ -889,6 +1069,66 @@ where InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() } +/// Helper function that creates wrapper of any error and +/// generate *HTTP VERSION NOT SUPPORTED* response. +#[allow(non_snake_case)] +pub fn ErrorHttpVersionNotSupported(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *VARIANT ALSO NEGOTIATES* response. +#[allow(non_snake_case)] +pub fn ErrorVariantAlsoNegotiates(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *INSUFFICIENT STORAGE* response. +#[allow(non_snake_case)] +pub fn ErrorInsufficientStorage(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *LOOP DETECTED* response. +#[allow(non_snake_case)] +pub fn ErrorLoopDetected(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LOOP_DETECTED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *NOT EXTENDED* response. +#[allow(non_snake_case)] +pub fn ErrorNotExtended(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NOT_EXTENDED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *NETWORK AUTHENTICATION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorNetworkAuthenticationRequired(err: T) -> Error +where + T: Send + Sync + fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() +} + #[cfg(test)] mod tests { use super::*; @@ -1068,6 +1308,9 @@ mod tests { let r: HttpResponse = ErrorUnauthorized("err").into(); assert_eq!(r.status(), StatusCode::UNAUTHORIZED); + let r: HttpResponse = ErrorPaymentRequired("err").into(); + assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED); + let r: HttpResponse = ErrorForbidden("err").into(); assert_eq!(r.status(), StatusCode::FORBIDDEN); @@ -1077,6 +1320,12 @@ mod tests { let r: HttpResponse = ErrorMethodNotAllowed("err").into(); assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); + let r: HttpResponse = ErrorNotAcceptable("err").into(); + assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE); + + let r: HttpResponse = ErrorProxyAuthenticationRequired("err").into(); + assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); + let r: HttpResponse = ErrorRequestTimeout("err").into(); assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); @@ -1086,12 +1335,57 @@ mod tests { let r: HttpResponse = ErrorGone("err").into(); assert_eq!(r.status(), StatusCode::GONE); + let r: HttpResponse = ErrorLengthRequired("err").into(); + assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED); + let r: HttpResponse = ErrorPreconditionFailed("err").into(); assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); + let r: HttpResponse = ErrorPayloadTooLarge("err").into(); + assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE); + + let r: HttpResponse = ErrorUriTooLong("err").into(); + assert_eq!(r.status(), StatusCode::URI_TOO_LONG); + + let r: HttpResponse = ErrorUnsupportedMediaType("err").into(); + assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); + + let r: HttpResponse = ErrorRangeNotSatisfiable("err").into(); + assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE); + let r: HttpResponse = ErrorExpectationFailed("err").into(); assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); + let r: HttpResponse = ErrorImATeapot("err").into(); + assert_eq!(r.status(), StatusCode::IM_A_TEAPOT); + + let r: HttpResponse = ErrorMisdirectedRequest("err").into(); + assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST); + + let r: HttpResponse = ErrorUnprocessableEntity("err").into(); + assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY); + + let r: HttpResponse = ErrorLocked("err").into(); + assert_eq!(r.status(), StatusCode::LOCKED); + + let r: HttpResponse = ErrorFailedDependency("err").into(); + assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY); + + let r: HttpResponse = ErrorUpgradeRequired("err").into(); + assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED); + + let r: HttpResponse = ErrorPreconditionRequired("err").into(); + assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED); + + let r: HttpResponse = ErrorTooManyRequests("err").into(); + assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS); + + let r: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into(); + assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); + + let r: HttpResponse = ErrorUnavailableForLegalReasons("err").into(); + assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); + let r: HttpResponse = ErrorInternalServerError("err").into(); assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -1106,5 +1400,23 @@ mod tests { let r: HttpResponse = ErrorGatewayTimeout("err").into(); assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); + + let r: HttpResponse = ErrorHttpVersionNotSupported("err").into(); + assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); + + let r: HttpResponse = ErrorVariantAlsoNegotiates("err").into(); + assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); + + let r: HttpResponse = ErrorInsufficientStorage("err").into(); + assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE); + + let r: HttpResponse = ErrorLoopDetected("err").into(); + assert_eq!(r.status(), StatusCode::LOOP_DETECTED); + + let r: HttpResponse = ErrorNotExtended("err").into(); + assert_eq!(r.status(), StatusCode::NOT_EXTENDED); + + let r: HttpResponse = ErrorNetworkAuthenticationRequired("err").into(); + assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); } } From 186d3d727a07bccb6423d36aa9475ece16ef7211 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 20 Nov 2018 10:55:50 -0800 Subject: [PATCH 0830/1635] add kee-alive tests --- src/h1/dispatcher.rs | 27 +++++---- tests/test_server.rs | 130 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 11 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index bf0abb04..550d27c2 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -409,7 +409,7 @@ where if self.flags.contains(Flags::SHUTDOWN) { return Err(DispatchError::DisconnectTimeout); } else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire { - // check for any outstanding response processing + // check for any outstanding tasks if self.state.is_empty() && self.framed.is_write_buf_empty() { if self.flags.contains(Flags::STARTED) { trace!("Keep-alive timeout, close connection"); @@ -427,12 +427,16 @@ where } } else { // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags.insert(Flags::STARTED | Flags::DISCONNECTED); - let _ = self.send_response( - Response::RequestTimeout().finish().drop_body(), - (), - ); + if !self.flags.contains(Flags::STARTED) { + trace!("Slow request timeout"); + let _ = self.send_response( + Response::RequestTimeout().finish().drop_body(), + (), + ); + } else { + trace!("Keep-alive connection timeout"); + } + self.flags.insert(Flags::STARTED | Flags::SHUTDOWN); self.state = State::None; } } else if let Some(deadline) = self.config.keep_alive_expire() { @@ -493,11 +497,14 @@ where false } // disconnect if keep-alive is not enabled - else if inner.flags.contains(Flags::STARTED) && !inner - .flags - .intersects(Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED) + else if inner.flags.contains(Flags::STARTED) + && !inner.flags.intersects(Flags::KEEPALIVE) { true + } + // disconnect if shutdown + else if inner.flags.contains(Flags::SHUTDOWN) { + true } else { return Ok(Async::NotReady); } diff --git a/tests/test_server.rs b/tests/test_server.rs index c6e03e28..16c65aac 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -4,7 +4,9 @@ extern crate actix_net; extern crate bytes; extern crate futures; -use std::{io::Read, io::Write, net}; +use std::io::{Read, Write}; +use std::time::Duration; +use std::{net, thread}; use actix_net::service::NewServiceExt; use bytes::Bytes; @@ -62,6 +64,132 @@ fn test_malformed_request() { assert!(data.starts_with("HTTP/1.1 400 Bad Request")); } +#[test] +fn test_keepalive() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); +} + +#[test] +fn test_keepalive_timeout() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .keep_alive(1) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + thread::sleep(Duration::from_millis(1100)); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[test] +fn test_keepalive_close() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = + stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[test] +fn test_keepalive_http10_default_close() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[test] +fn test_keepalive_http10() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream + .write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[test] +fn test_keepalive_disabled() { + let srv = test::TestServer::with_factory(|| { + h1::H1Service::build() + .keep_alive(KeepAlive::Disabled) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + .map(|_| ()) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + #[test] fn test_content_length() { use actix_http::http::{ From ab3e12f2b4a8a177421120bca0239a9e4cf3d874 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 20 Nov 2018 11:23:05 -0800 Subject: [PATCH 0831/1635] set server response version --- src/h1/codec.rs | 3 +++ tests/test_server.rs | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index f9f455e5..3174e78e 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -149,6 +149,9 @@ impl Encoder for Codec { ) -> Result<(), Self::Error> { match item { Message::Item((mut res, length)) => { + // set response version + res.head_mut().version = self.version; + // connection status self.ctype = if let Some(ct) = res.head().ctype { if ct == ConnectionType::KeepAlive { diff --git a/tests/test_server.rs b/tests/test_server.rs index 16c65aac..a153e584 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -137,7 +137,7 @@ fn test_keepalive_http10_default_close() { let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); @@ -157,13 +157,13 @@ fn test_keepalive_http10() { .write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); let mut data = vec![0; 1024]; let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); let mut data = vec![0; 1024]; let res = stream.read(&mut data).unwrap(); From 389cb13cd63704a024d7e668592c2aca06bcd876 Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 20 Nov 2018 23:06:38 +0300 Subject: [PATCH 0832/1635] Export PathConfig and QueryConfig Closes #597 --- CHANGES.md | 6 ++++++ src/lib.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index efeaadf0..cb448883 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.7.15] - 2018-xx-xx + +## Changed + +* `QueryConfig` and `PathConfig` are made public. + ## [0.7.14] - 2018-11-14 ### Added diff --git a/src/lib.rs b/src/lib.rs index 738153fa..f8326886 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -255,7 +255,7 @@ pub mod dev { pub use body::BodyStream; pub use context::Drain; - pub use extractor::{FormConfig, PayloadConfig}; + pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig}; pub use handler::{AsyncResult, Handler}; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; From 1a322966ff2b25af1213459bd6d62fb9d4b6fcbd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 21 Nov 2018 07:49:24 -0800 Subject: [PATCH 0833/1635] handle response errors --- src/body.rs | 40 ++++++++++++++++++++++ src/h1/dispatcher.rs | 14 ++++---- src/response.rs | 81 ++++++++++++++++++++++---------------------- src/service.rs | 4 +-- tests/test_server.rs | 22 ++++++++++++ 5 files changed, 111 insertions(+), 50 deletions(-) diff --git a/src/body.rs b/src/body.rs index 3449448e..cc4e77af 100644 --- a/src/body.rs +++ b/src/body.rs @@ -37,6 +37,37 @@ impl MessageBody for () { } } +pub enum ResponseBody { + Body(B), + Other(Body), +} + +impl ResponseBody { + pub fn as_ref(&self) -> Option<&B> { + if let ResponseBody::Body(ref b) = self { + Some(b) + } else { + None + } + } +} + +impl MessageBody for ResponseBody { + fn length(&self) -> BodyLength { + match self { + ResponseBody::Body(ref body) => body.length(), + ResponseBody::Other(ref body) => body.length(), + } + } + + fn poll_next(&mut self) -> Poll, Error> { + match self { + ResponseBody::Body(ref mut body) => body.poll_next(), + ResponseBody::Other(ref mut body) => body.poll_next(), + } + } +} + /// Represents various types of http message body. pub enum Body { /// Empty response. `Content-Length` header is not set. @@ -332,6 +363,15 @@ mod tests { } } + impl ResponseBody { + pub(crate) fn get_ref(&self) -> &[u8] { + match *self { + ResponseBody::Body(ref b) => b.get_ref(), + ResponseBody::Other(ref b) => b.get_ref(), + } + } + } + #[test] fn test_static_str() { assert_eq!(Body::from("").length(), BodyLength::Sized(0)); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 550d27c2..48c8e710 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -13,7 +13,7 @@ use tokio_timer::Delay; use error::{ParseError, PayloadError}; use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; -use body::{BodyLength, MessageBody}; +use body::{Body, BodyLength, MessageBody, ResponseBody}; use config::ServiceConfig; use error::DispatchError; use request::Request; @@ -70,7 +70,7 @@ enum DispatcherMessage { enum State { None, ServiceCall(S::Future), - SendPayload(B), + SendPayload(ResponseBody), } impl State { @@ -186,11 +186,11 @@ where } } - fn send_response( + fn send_response( &mut self, message: Response<()>, - body: B1, - ) -> Result, DispatchError> { + body: ResponseBody, + ) -> Result, DispatchError> { self.framed .force_send(Message::Item((message, body.length()))) .map_err(|err| { @@ -217,7 +217,7 @@ where Some(self.handle_request(req)?) } Some(DispatcherMessage::Error(res)) => { - self.send_response(res, ())?; + self.send_response(res, ResponseBody::Other(Body::Empty))?; None } None => None, @@ -431,7 +431,7 @@ where trace!("Slow request timeout"); let _ = self.send_response( Response::RequestTimeout().finish().drop_body(), - (), + ResponseBody::Other(Body::Empty), ); } else { trace!("Keep-alive connection timeout"); diff --git a/src/response.rs b/src/response.rs index bc730718..ae68189d 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,7 +12,7 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use body::{Body, BodyStream, MessageBody}; +use body::{Body, BodyStream, MessageBody, ResponseBody}; use error::Error; use header::{Header, IntoHeaderValue}; use message::{ConnectionType, Head, ResponseHead}; @@ -21,7 +21,7 @@ use message::{ConnectionType, Head, ResponseHead}; pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; /// An HTTP Response -pub struct Response(Box, B); +pub struct Response(Box, ResponseBody); impl Response { /// Create http response builder with specific status. @@ -71,6 +71,15 @@ impl Response { cookies: jar, } } + + /// Convert response to response with body + pub fn into_body(self) -> Response { + let b = match self.1 { + ResponseBody::Body(b) => b, + ResponseBody::Other(b) => b, + }; + Response(self.0, ResponseBody::Other(b)) + } } impl Response { @@ -195,23 +204,26 @@ impl Response { /// Get body os this response #[inline] - pub fn body(&self) -> &B { + pub(crate) fn body(&self) -> &ResponseBody { &self.1 } /// Set a body - pub fn set_body(self, body: B2) -> Response { - Response(self.0, body) + pub(crate) fn set_body(self, body: B2) -> Response { + Response(self.0, ResponseBody::Body(body)) } /// Drop request's body - pub fn drop_body(self) -> Response<()> { - Response(self.0, ()) + pub(crate) fn drop_body(self) -> Response<()> { + Response(self.0, ResponseBody::Body(())) } /// Set a body and return previous body value - pub fn replace_body(self, body: B2) -> (Response, B) { - (Response(self.0, body), self.1) + pub(crate) fn replace_body( + self, + body: B2, + ) -> (Response, ResponseBody) { + (Response(self.0, ResponseBody::Body(body)), self.1) } /// Size of response in bytes, excluding HTTP headers @@ -233,7 +245,10 @@ impl Response { } pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response(Box::new(InnerResponse::from_parts(parts)), Body::Empty) + Response( + Box::new(InnerResponse::from_parts(parts)), + ResponseBody::Body(Body::Empty), + ) } } @@ -250,7 +265,7 @@ impl fmt::Debug for Response { for (key, val) in self.get_ref().head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } - let _ = writeln!(f, " body: {:?}", self.body().length()); + let _ = writeln!(f, " body: {:?}", self.1.length()); res } } @@ -559,11 +574,9 @@ impl ResponseBuilder { /// /// `ResponseBuilder` can not be used after this call. pub fn message_body(&mut self, body: B) -> Response { - let mut error = if let Some(e) = self.err.take() { - Some(Error::from(e)) - } else { - None - }; + if let Some(e) = self.err.take() { + return Response::from(Error::from(e)).into_body(); + } let mut response = self.response.take().expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { @@ -572,17 +585,12 @@ impl ResponseBuilder { Ok(val) => { let _ = response.head.headers.append(header::SET_COOKIE, val); } - Err(e) => if error.is_none() { - error = Some(Error::from(e)); - }, + Err(e) => return Response::from(Error::from(e)).into_body(), }; } } - if let Some(error) = error { - response.error = Some(error); - } - Response(response, body) + Response(response, ResponseBody::Body(body)) } #[inline] @@ -812,9 +820,12 @@ impl ResponsePool { ) -> Response { if let Some(mut msg) = pool.0.borrow_mut().pop_front() { msg.head.status = status; - Response(msg, body) + Response(msg, ResponseBody::Body(body)) } else { - Response(Box::new(InnerResponse::new(status, pool)), body) + Response( + Box::new(InnerResponse::new(status, pool)), + ResponseBody::Body(body), + ) } } @@ -971,10 +982,7 @@ mod tests { let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } #[test] @@ -984,10 +992,7 @@ mod tests { .json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } #[test] @@ -995,10 +1000,7 @@ mod tests { let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } #[test] @@ -1008,10 +1010,7 @@ mod tests { .json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); + assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } #[test] diff --git a/src/service.rs b/src/service.rs index f934305c..6a31b6bb 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,7 +6,7 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; -use body::{BodyLength, MessageBody}; +use body::{BodyLength, MessageBody, ResponseBody}; use error::{Error, ResponseError}; use h1::{Codec, Message}; use response::Response; @@ -174,7 +174,7 @@ where pub struct SendResponseFut { res: Option, BodyLength)>>, - body: Option, + body: Option>, framed: Option>, } diff --git a/tests/test_server.rs b/tests/test_server.rs index a153e584..300b38a8 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -445,3 +445,25 @@ fn test_body_chunked_implicit() { let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } + +#[test] +fn test_response_http_error_handling() { + let mut srv = test::TestServer::with_factory(|| { + h1::H1Service::new(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }).map(|_| ()) + }); + + let req = srv.get().finish().unwrap(); + let response = srv.send_request(req).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} From 41d68c87d926899cdce05ae3bf9c89c23fa1ac50 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Fri, 23 Nov 2018 07:42:40 +0330 Subject: [PATCH 0834/1635] hello-world example added. --- examples/hello-world.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/hello-world.rs diff --git a/examples/hello-world.rs b/examples/hello-world.rs new file mode 100644 index 00000000..44e453df --- /dev/null +++ b/examples/hello-world.rs @@ -0,0 +1,35 @@ +#[macro_use] +extern crate log; +extern crate env_logger; + +extern crate actix_http; +extern crate actix_net; +extern crate futures; +extern crate http; + +use actix_http::{h1, Response}; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::future; +use http::header::{HeaderValue}; +use std::env; + +fn main() { + env::set_var("RUST_LOG", "hello_world=info"); + env_logger::init(); + + Server::new().bind("hello-world", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req| { + info!("{:?}", _req); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + future::ok::<_, ()>(res.body("Hello world!")) + }) + .map(|_| ()) + }).unwrap().run(); +} + From 9aab382ea89395fcc627c5375ddd8721cc47c514 Mon Sep 17 00:00:00 2001 From: Douman Date: Thu, 22 Nov 2018 19:20:07 +0300 Subject: [PATCH 0835/1635] Allow user to provide addr to custom resolver We basically swaps Addr with Recipient to enable user to use custom resolver --- CHANGES.md | 2 ++ Cargo.toml | 2 +- src/client/connector.rs | 12 +++++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cb448883..2e028d6d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ## Changed +* `ClientConnector::resolver` now accepts `Into` instead of `Addr`. It enables user to implement own resolver. + * `QueryConfig` and `PathConfig` are made public. ## [0.7.14] - 2018-11-14 diff --git a/Cargo.toml b/Cargo.toml index 41f2e667..e3fbd4e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ flate2-rust = ["flate2/rust_backend"] cell = ["actix-net/cell"] [dependencies] -actix = "0.7.6" +actix = "0.7.7" actix-net = "0.2.2" askama_escape = "0.1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index 3990c955..72132bc6 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -5,7 +5,7 @@ use std::{fmt, io, mem, time}; use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; use actix::{ - fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context, + fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context, ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, SystemService, WrapFuture, }; @@ -220,7 +220,7 @@ pub struct ClientConnector { acq_tx: mpsc::UnboundedSender, acq_rx: Option>, - resolver: Option>, + resolver: Option>, conn_lifetime: Duration, conn_keep_alive: Duration, limit: usize, @@ -239,7 +239,7 @@ impl Actor for ClientConnector { fn started(&mut self, ctx: &mut Self::Context) { if self.resolver.is_none() { - self.resolver = Some(Resolver::from_registry()) + self.resolver = Some(Resolver::from_registry().recipient()) } self.collect_periodic(ctx); ctx.add_stream(self.acq_rx.take().unwrap()); @@ -503,8 +503,10 @@ impl ClientConnector { } /// Use custom resolver actor - pub fn resolver(mut self, addr: Addr) -> Self { - self.resolver = Some(addr); + /// + /// By default actix's Resolver is used. + pub fn resolver>>(mut self, addr: A) -> Self { + self.resolver = Some(addr.into()); self } From d5ca6e21e2daef2637b0be028990e7264055436c Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sat, 24 Nov 2018 11:29:14 +0330 Subject: [PATCH 0836/1635] simple echo server. --- examples/echo.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/echo.rs diff --git a/examples/echo.rs b/examples/echo.rs new file mode 100644 index 00000000..91a3c76a --- /dev/null +++ b/examples/echo.rs @@ -0,0 +1,42 @@ +#[macro_use] +extern crate log; +extern crate env_logger; + +extern crate actix_http; +extern crate actix_net; +extern crate futures; +extern crate http; +extern crate bytes; + +use actix_http::{h1, Response, Request}; +use bytes::Bytes; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::Future; +use http::header::{HeaderValue}; +use actix_http::HttpMessage; +use std::env; + +fn main() { + env::set_var("RUST_LOG", "echo=info"); + env_logger::init(); + + Server::new().bind("echo", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req: Request| { + _req.body() + .limit(512) + .and_then(|bytes: Bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + Ok(res.body(bytes)) + }) + }) + .map(|_| ()) + }).unwrap().run(); +} + From c3c2286e3ac3c1c9ac6914cc430a9a20dd899721 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sat, 24 Nov 2018 17:07:30 +0330 Subject: [PATCH 0837/1635] An other hello word example and update sample in README.md --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3cb6f230..e7205893 100644 --- a/README.md +++ b/README.md @@ -14,20 +14,23 @@ Actix http ## Example ```rust +// see examples/framed_hello.rs for complete list of used crates. extern crate actix_http; use actix_http::{h1, Response, ServiceConfig}; fn main() { - Server::new() - .bind("app", addr, move || { - IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec - .and_then(TakeItem::new().map_err(|_| ())) // <- read one request - .and_then(|(req, framed): (_, Framed<_, _>)| { // <- send response and close conn - framed - .send(h1::OutMessage::Response(Response::Ok().finish())) - }) - }) - .run(); + env::set_var("RUST_LOG", "framed_hello=info"); + env_logger::init(); + + Server::new().bind("framed_hello", "127.0.0.1:8080", || { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec + .and_then(TakeItem::new().map_err(|_| ())) // <- read one request + .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn + SendResponse::send(_framed, Response::Ok().body("Hello world!")) + .map_err(|_| ()) + .map(|_| ()) + }) + }).unwrap().run(); } ``` From d5b264034299bcaa75f3bd21b4d36dba574c2295 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sat, 24 Nov 2018 17:08:17 +0330 Subject: [PATCH 0838/1635] add framed_hello.rs --- examples/framed_hello.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 examples/framed_hello.rs diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs new file mode 100644 index 00000000..76d23d08 --- /dev/null +++ b/examples/framed_hello.rs @@ -0,0 +1,33 @@ +extern crate log; +extern crate env_logger; + +extern crate actix_http; +extern crate actix_net; +extern crate futures; +extern crate http; +extern crate bytes; + +use actix_http::{h1, ServiceConfig, SendResponse, Response}; +use actix_net::framed::IntoFramed; +use actix_net::codec::Framed; +use actix_net::stream::TakeItem; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::Future; +use std::env; + +fn main() { + env::set_var("RUST_LOG", "framed_hello=info"); + env_logger::init(); + + Server::new().bind("framed_hello", "127.0.0.1:8080", || { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) + .and_then(TakeItem::new().map_err(|_| ())) + .and_then(|(_req, _framed): (_, Framed<_, _>)| { + SendResponse::send(_framed, Response::Ok().body("Hello world!")) + .map_err(|_| ()) + .map(|_| ()) + }) + }).unwrap().run(); +} + From 7a97de3a1e222fa15e8495315dcbcedea11bdfd8 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sat, 24 Nov 2018 17:17:34 +0330 Subject: [PATCH 0839/1635] update readme. --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e7205893..093a0b94 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,10 @@ extern crate actix_http; use actix_http::{h1, Response, ServiceConfig}; fn main() { - env::set_var("RUST_LOG", "framed_hello=info"); - env_logger::init(); - Server::new().bind("framed_hello", "127.0.0.1:8080", || { IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec - .and_then(TakeItem::new().map_err(|_| ())) // <- read one request - .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn + .and_then(TakeItem::new().map_err(|_| ())) // <- read one request + .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn SendResponse::send(_framed, Response::Ok().body("Hello world!")) .map_err(|_| ()) .map(|_| ()) From c386353337cff83626941fca2b58628845b440f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 24 Nov 2018 14:54:11 +0100 Subject: [PATCH 0840/1635] decode reserved characters when extracting path with configuration (#577) * decode reserved characters when extracting path with configuration * remove useless clone * add a method to get decoded parameter by name --- CHANGES.md | 9 ++++ MIGRATION.md | 28 +++++++++++++ src/de.rs | 70 ++++++++++++++++++------------- src/extractor.rs | 76 ++++++++++++++++++++++++++++++++- src/param.rs | 33 ++++++++++++++- src/uri.rs | 95 ++++++++++++++++++++++-------------------- tests/test_handlers.rs | 2 +- 7 files changed, 234 insertions(+), 79 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2e028d6d..902a84f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,12 @@ * `QueryConfig` and `PathConfig` are made public. +### Added + +* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled + with `PathConfig::default().disable_decoding()` + + ## [0.7.14] - 2018-11-14 ### Added @@ -16,6 +22,9 @@ * Add method to configure `SameSite` option in `CookieIdentityPolicy`. +* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled + with `PathConfig::default().disable_decoding()` + ### Fixed diff --git a/MIGRATION.md b/MIGRATION.md index 3c0bdd94..26a31424 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,31 @@ +## 0.7.15 + +* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in + your routes, you should use `%20`. + + instead of + + ```rust + fn main() { + let app = App::new().resource("/my index", |r| { + r.method(http::Method::GET) + .with(index); + }); + } + ``` + + use + + ```rust + fn main() { + let app = App::new().resource("/my%20index", |r| { + r.method(http::Method::GET) + .with(index); + }); + } + ``` + + ## 0.7.4 * `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple diff --git a/src/de.rs b/src/de.rs index 59ab79ba..05f8914f 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,7 +1,10 @@ +use std::rc::Rc; + use serde::de::{self, Deserializer, Error as DeError, Visitor}; use httprequest::HttpRequest; use param::ParamsIter; +use uri::RESERVED_QUOTER; macro_rules! unsupported_type { ($trait_fn:ident, $name:expr) => { @@ -13,6 +16,20 @@ macro_rules! unsupported_type { }; } +macro_rules! percent_decode_if_needed { + ($value:expr, $decode:expr) => { + if $decode { + if let Some(ref mut value) = RESERVED_QUOTER.requote($value.as_bytes()) { + Rc::make_mut(value).parse() + } else { + $value.parse() + } + } else { + $value.parse() + } + } +} + macro_rules! parse_single_value { ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { fn $trait_fn(self, visitor: V) -> Result @@ -23,11 +40,11 @@ macro_rules! parse_single_value { format!("wrong number of parameters: {} expected 1", self.req.match_info().len()).as_str())) } else { - let v = self.req.match_info()[0].parse().map_err( - |_| de::value::Error::custom( - format!("can not parse {:?} to a {}", - &self.req.match_info()[0], $tp)))?; - visitor.$visit_fn(v) + let v_parsed = percent_decode_if_needed!(&self.req.match_info()[0], self.decode) + .map_err(|_| de::value::Error::custom( + format!("can not parse {:?} to a {}", &self.req.match_info()[0], $tp) + ))?; + visitor.$visit_fn(v_parsed) } } } @@ -35,11 +52,12 @@ macro_rules! parse_single_value { pub struct PathDeserializer<'de, S: 'de> { req: &'de HttpRequest, + decode: bool, } impl<'de, S: 'de> PathDeserializer<'de, S> { - pub fn new(req: &'de HttpRequest) -> Self { - PathDeserializer { req } + pub fn new(req: &'de HttpRequest, decode: bool) -> Self { + PathDeserializer { req, decode } } } @@ -53,6 +71,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { visitor.visit_map(ParamsDeserializer { params: self.req.match_info().iter(), current: None, + decode: self.decode, }) } @@ -107,6 +126,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } else { visitor.visit_seq(ParamsSeq { params: self.req.match_info().iter(), + decode: self.decode, }) } } @@ -128,6 +148,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { } else { visitor.visit_seq(ParamsSeq { params: self.req.match_info().iter(), + decode: self.decode, }) } } @@ -141,28 +162,13 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { Err(de::value::Error::custom("unsupported type: enum")) } - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() != 1 { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected 1", - self.req.match_info().len() - ).as_str(), - )) - } else { - visitor.visit_str(&self.req.match_info()[0]) - } - } - fn deserialize_seq(self, visitor: V) -> Result where V: Visitor<'de>, { visitor.visit_seq(ParamsSeq { params: self.req.match_info().iter(), + decode: self.decode, }) } @@ -184,13 +190,16 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { parse_single_value!(deserialize_f32, visit_f32, "f32"); parse_single_value!(deserialize_f64, visit_f64, "f64"); parse_single_value!(deserialize_string, visit_string, "String"); + parse_single_value!(deserialize_str, visit_string, "String"); parse_single_value!(deserialize_byte_buf, visit_string, "String"); parse_single_value!(deserialize_char, visit_char, "char"); + } struct ParamsDeserializer<'de> { params: ParamsIter<'de>, current: Option<(&'de str, &'de str)>, + decode: bool, } impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { @@ -212,7 +221,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { V: de::DeserializeSeed<'de>, { if let Some((_, value)) = self.current.take() { - seed.deserialize(Value { value }) + seed.deserialize(Value { value, decode: self.decode }) } else { Err(de::value::Error::custom("unexpected item")) } @@ -252,16 +261,18 @@ macro_rules! parse_value { fn $trait_fn(self, visitor: V) -> Result where V: Visitor<'de> { - let v = self.value.parse().map_err( - |_| de::value::Error::custom( - format!("can not parse {:?} to a {}", self.value, $tp)))?; - visitor.$visit_fn(v) + let v_parsed = percent_decode_if_needed!(&self.value, self.decode) + .map_err(|_| de::value::Error::custom( + format!("can not parse {:?} to a {}", &self.value, $tp) + ))?; + visitor.$visit_fn(v_parsed) } } } struct Value<'de> { value: &'de str, + decode: bool, } impl<'de> Deserializer<'de> for Value<'de> { @@ -377,6 +388,7 @@ impl<'de> Deserializer<'de> for Value<'de> { struct ParamsSeq<'de> { params: ParamsIter<'de>, + decode: bool, } impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { @@ -387,7 +399,7 @@ impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { T: de::DeserializeSeed<'de>, { match self.params.next() { - Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)), + Some(item) => Ok(Some(seed.deserialize(Value { value: item.1, decode: self.decode })?)), None => Ok(None), } } diff --git a/src/extractor.rs b/src/extractor.rs index 45e29ace..717e0f6c 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -18,7 +18,8 @@ use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's path. +/// Extract typed information from the request's path. Information from the path is +/// URL decoded. Decoding of special characters can be disabled through `PathConfig`. /// /// ## Example /// @@ -119,7 +120,7 @@ where let req = req.clone(); let req2 = req.clone(); let err = Rc::clone(&cfg.ehandler); - de::Deserialize::deserialize(PathDeserializer::new(&req)) + de::Deserialize::deserialize(PathDeserializer::new(&req, cfg.decode)) .map_err(move |e| (*err)(e, &req2)) .map(|inner| Path { inner }) } @@ -149,6 +150,7 @@ where /// ``` pub struct PathConfig { ehandler: Rc) -> Error>, + decode: bool, } impl PathConfig { /// Set custom error handler @@ -159,12 +161,20 @@ impl PathConfig { self.ehandler = Rc::new(f); self } + + /// Disable decoding of URL encoded special charaters from the path + pub fn disable_decoding(&mut self) -> &mut Self + { + self.decode = false; + self + } } impl Default for PathConfig { fn default() -> Self { PathConfig { ehandler: Rc::new(|e, _| ErrorNotFound(e)), + decode: true, } } } @@ -1090,6 +1100,68 @@ mod tests { assert_eq!(*Path::::from_request(&req, &&PathConfig::default()).unwrap(), 32); } + #[test] + fn test_extract_path_decode() { + let mut router = Router::<()>::default(); + router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); + + macro_rules! test_single_value { + ($value:expr, $expected:expr) => { + { + let req = TestRequest::with_uri($value).finish(); + let info = router.recognize(&req, &(), 0); + let req = req.with_route_info(info); + assert_eq!(*Path::::from_request(&req, &PathConfig::default()).unwrap(), $expected); + } + } + } + + test_single_value!("/%25/", "%"); + test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+="); + test_single_value!("/%2B/", "+"); + test_single_value!("/%252B/", "%2B"); + test_single_value!("/%2F/", "/"); + test_single_value!("/%252F/", "%2F"); + test_single_value!("/http%3A%2F%2Flocalhost%3A80%2Ffoo/", "http://localhost:80/foo"); + test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog"); + test_single_value!( + "/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/", + "http://localhost:80/file/%2Fvar%2Flog%2Fsyslog" + ); + + let req = TestRequest::with_uri("/%25/7/?id=test").finish(); + + let mut router = Router::<()>::default(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); + let info = router.recognize(&req, &(), 0); + let req = req.with_route_info(info); + + let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); + assert_eq!(s.key, "%"); + assert_eq!(s.value, 7); + + let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap(); + assert_eq!(s.0, "%"); + assert_eq!(s.1, "7"); + } + + #[test] + fn test_extract_path_no_decode() { + let mut router = Router::<()>::default(); + router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); + + let req = TestRequest::with_uri("/%25/").finish(); + let info = router.recognize(&req, &(), 0); + let req = req.with_route_info(info); + assert_eq!( + *Path::::from_request( + &req, + &&PathConfig::default().disable_decoding() + ).unwrap(), + "%25" + ); + } + #[test] fn test_tuple_extract() { let mut router = Router::<()>::default(); diff --git a/src/param.rs b/src/param.rs index d0664df9..a3f60259 100644 --- a/src/param.rs +++ b/src/param.rs @@ -8,7 +8,7 @@ use http::StatusCode; use smallvec::SmallVec; use error::{InternalError, ResponseError, UriSegmentError}; -use uri::Url; +use uri::{Url, RESERVED_QUOTER}; /// A trait to abstract the idea of creating a new instance of a type from a /// path parameter. @@ -103,6 +103,17 @@ impl Params { } } + /// Get URL-decoded matched parameter by name without type conversion + pub fn get_decoded(&self, key: &str) -> Option { + self.get(key).map(|value| { + if let Some(ref mut value) = RESERVED_QUOTER.requote(value.as_bytes()) { + Rc::make_mut(value).to_string() + } else { + value.to_string() + } + }) + } + /// Get unprocessed part of path pub fn unprocessed(&self) -> &str { &self.url.path()[(self.tail as usize)..] @@ -300,4 +311,24 @@ mod tests { Ok(PathBuf::from_iter(vec!["seg2"])) ); } + + #[test] + fn test_get_param_by_name() { + let mut params = Params::new(); + params.add_static("item1", "path"); + params.add_static("item2", "http%3A%2F%2Flocalhost%3A80%2Ffoo"); + + assert_eq!(params.get("item0"), None); + assert_eq!(params.get_decoded("item0"), None); + assert_eq!(params.get("item1"), Some("path")); + assert_eq!(params.get_decoded("item1"), Some("path".to_string())); + assert_eq!( + params.get("item2"), + Some("http%3A%2F%2Flocalhost%3A80%2Ffoo") + ); + assert_eq!( + params.get_decoded("item2"), + Some("http://localhost:80/foo".to_string()) + ); + } } diff --git a/src/uri.rs b/src/uri.rs index 881cf20a..c87cb3d5 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -1,25 +1,12 @@ use http::Uri; use std::rc::Rc; -#[allow(dead_code)] -const GEN_DELIMS: &[u8] = b":/?#[]@"; -#[allow(dead_code)] -const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; -#[allow(dead_code)] -const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; -#[allow(dead_code)] -const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; -#[allow(dead_code)] -const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~"; -const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~ - !$'()*,"; -const QS: &[u8] = b"+&=;b"; +// https://tools.ietf.org/html/rfc3986#section-2.2 +const RESERVED_PLUS_EXTRA: &[u8] = b":/?#[]@!$&'()*,+?;=%^ <>\"\\`{}|"; + +// https://tools.ietf.org/html/rfc3986#section-2.3 +const UNRESERVED: &[u8] = + b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~"; #[inline] fn bit_at(array: &[u8], ch: u8) -> bool { @@ -32,7 +19,8 @@ fn set_bit(array: &mut [u8], ch: u8) { } lazy_static! { - static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") }; + static ref UNRESERVED_QUOTER: Quoter = { Quoter::new(UNRESERVED) }; + pub(crate) static ref RESERVED_QUOTER: Quoter = { Quoter::new(RESERVED_PLUS_EXTRA) }; } #[derive(Default, Clone, Debug)] @@ -43,7 +31,7 @@ pub(crate) struct Url { impl Url { pub fn new(uri: Uri) -> Url { - let path = DEFAULT_QUOTER.requote(uri.path().as_bytes()); + let path = UNRESERVED_QUOTER.requote(uri.path().as_bytes()); Url { uri, path } } @@ -63,36 +51,19 @@ impl Url { pub(crate) struct Quoter { safe_table: [u8; 16], - protected_table: [u8; 16], } impl Quoter { - pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { + pub fn new(safe: &[u8]) -> Quoter { let mut q = Quoter { safe_table: [0; 16], - protected_table: [0; 16], }; // prepare safe table - for i in 0..128 { - if ALLOWED.contains(&i) { - set_bit(&mut q.safe_table, i); - } - if QS.contains(&i) { - set_bit(&mut q.safe_table, i); - } - } - for ch in safe { set_bit(&mut q.safe_table, *ch) } - // prepare protected table - for ch in protected { - set_bit(&mut q.safe_table, *ch); - set_bit(&mut q.protected_table, *ch); - } - q } @@ -115,19 +86,17 @@ impl Quoter { if let Some(ch) = restore_ch(pct[1], pct[2]) { if ch < 128 { - if bit_at(&self.protected_table, ch) { - buf.extend_from_slice(&pct); - idx += 1; - continue; - } - if bit_at(&self.safe_table, ch) { buf.push(ch); idx += 1; continue; } + + buf.extend_from_slice(&pct); + } else { + // Not ASCII, decode it + buf.push(ch); } - buf.push(ch); } else { buf.extend_from_slice(&pct[..]); } @@ -172,3 +141,37 @@ fn from_hex(v: u8) -> Option { fn restore_ch(d1: u8, d2: u8) -> Option { from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2))) } + + +#[cfg(test)] +mod tests { + use std::rc::Rc; + + use super::*; + + #[test] + fn decode_path() { + assert_eq!(UNRESERVED_QUOTER.requote(b"https://localhost:80/foo"), None); + + assert_eq!( + Rc::try_unwrap(UNRESERVED_QUOTER.requote( + b"https://localhost:80/foo%25" + ).unwrap()).unwrap(), + "https://localhost:80/foo%25".to_string() + ); + + assert_eq!( + Rc::try_unwrap(UNRESERVED_QUOTER.requote( + b"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo" + ).unwrap()).unwrap(), + "http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo".to_string() + ); + + assert_eq!( + Rc::try_unwrap(UNRESERVED_QUOTER.requote( + b"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A" + ).unwrap()).unwrap(), + "http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A".to_string() + ); + } +} \ No newline at end of file diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index 3ea709c9..debc1626 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -672,6 +672,6 @@ fn test_unsafe_path_route() { let bytes = srv.execute(response.body()).unwrap(); assert_eq!( bytes, - Bytes::from_static(b"success: http:%2F%2Fexample.com") + Bytes::from_static(b"success: http%3A%2F%2Fexample.com") ); } From ca1b460924219f0cc2e10bf500094238e953c45e Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Sun, 25 Nov 2018 05:48:33 +0330 Subject: [PATCH 0841/1635] comments aligned. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 093a0b94..be816096 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ use actix_http::{h1, Response, ServiceConfig}; fn main() { Server::new().bind("framed_hello", "127.0.0.1:8080", || { IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec - .and_then(TakeItem::new().map_err(|_| ())) // <- read one request - .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn + .and_then(TakeItem::new().map_err(|_| ())) // <- read one request + .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn SendResponse::send(_framed, Response::Ok().body("Hello world!")) .map_err(|_| ()) .map(|_| ()) From 9c038ee1891afe204b06c2e22dcdc947f6d3469b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 25 Nov 2018 20:14:42 -1000 Subject: [PATCH 0842/1635] allow to use Uri for client request --- src/client/connector.rs | 6 ++++++ src/client/mod.rs | 2 +- src/client/request.rs | 40 +++++++++++++++++++++++++--------------- src/lib.rs | 3 +++ 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 81808521..42cba9de 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -62,6 +62,12 @@ impl Default for Connector { } impl Connector { + /// Use custom resolver. + pub fn resolver(mut self, resolver: Resolver) -> Self { + self.resolver = resolver;; + self + } + /// Use custom resolver configuration. pub fn resolver_config(mut self, cfg: ResolverConfig, opts: ResolverOpts) -> Self { self.resolver = Resolver::new(cfg, opts); diff --git a/src/client/mod.rs b/src/client/mod.rs index 76c3f8b8..dcc4f5d4 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -13,4 +13,4 @@ pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; +pub use self::response::ClientResponse; \ No newline at end of file diff --git a/src/client/request.rs b/src/client/request.rs index 735ce493..dd29d797 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -7,7 +7,6 @@ use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use urlcrate::Url; use body::{BodyStream, MessageBody}; use error::Error; @@ -63,35 +62,50 @@ impl ClientRequest<()> { } /// Create request builder for `GET` request - pub fn get>(uri: U) -> ClientRequestBuilder { + pub fn get(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::GET).uri(uri); builder } /// Create request builder for `HEAD` request - pub fn head>(uri: U) -> ClientRequestBuilder { + pub fn head(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::HEAD).uri(uri); builder } /// Create request builder for `POST` request - pub fn post>(uri: U) -> ClientRequestBuilder { + pub fn post(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::POST).uri(uri); builder } /// Create request builder for `PUT` request - pub fn put>(uri: U) -> ClientRequestBuilder { + pub fn put(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::PUT).uri(uri); builder } /// Create request builder for `DELETE` request - pub fn delete>(uri: U) -> ClientRequestBuilder { + pub fn delete(uri: U) -> ClientRequestBuilder + where + Uri: HttpTryFrom, + { let mut builder = ClientRequest::build(); builder.method(Method::DELETE).uri(uri); builder @@ -202,15 +216,11 @@ pub struct ClientRequestBuilder { impl ClientRequestBuilder { /// Set HTTP URI of request. #[inline] - pub fn uri>(&mut self, uri: U) -> &mut Self { - match Url::parse(uri.as_ref()) { - Ok(url) => self._uri(url.as_str()), - Err(_) => self._uri(uri.as_ref()), - } - } - - fn _uri(&mut self, url: &str) -> &mut Self { - match Uri::try_from(url) { + pub fn uri(&mut self, uri: U) -> &mut Self + where + Uri: HttpTryFrom, + { + match Uri::try_from(uri) { Ok(uri) => { if let Some(parts) = parts(&mut self.head, &self.err) { parts.uri = uri; diff --git a/src/lib.rs b/src/lib.rs index 5256dd19..4870eb64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,6 +163,9 @@ pub mod http { #[doc(hidden)] pub use modhttp::{uri, Error, HeaderMap, HttpTryFrom, Uri}; + #[doc(hidden)] + pub use modhttp::uri::PathAndQuery; + pub use cookie::{Cookie, CookieBuilder}; /// Various http headers From 397804a786d0a4e8167706b46a4bb08808bd17cd Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Wed, 28 Nov 2018 09:15:08 +0330 Subject: [PATCH 0843/1635] echo example with `impl Future` --- examples/echo2.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 examples/echo2.rs diff --git a/examples/echo2.rs b/examples/echo2.rs new file mode 100644 index 00000000..7d8a428f --- /dev/null +++ b/examples/echo2.rs @@ -0,0 +1,47 @@ +#[macro_use] +extern crate log; +extern crate env_logger; + +extern crate actix_http; +extern crate actix_net; +extern crate futures; +extern crate http; +extern crate bytes; + +use actix_http::{h1, Response, Request, Error}; +use bytes::Bytes; +use actix_net::server::Server; +use actix_net::service::NewServiceExt; +use futures::Future; +use http::header::{HeaderValue}; +use actix_http::HttpMessage; +use std::env; + +fn handle_request(_req: Request) -> impl Future{ + _req.body() + .limit(512) + .from_err() + .and_then(|bytes: Bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + Ok(res.body(bytes)) + }) +} + +fn main() { + env::set_var("RUST_LOG", "echo=info"); + env_logger::init(); + + Server::new().bind("echo", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req: Request| { + handle_request(_req) + }) + .map(|_| ()) + }).unwrap().run(); +} + From 4028f6f6fd63cad32636d5f1b461f372302c9af4 Mon Sep 17 00:00:00 2001 From: Ali Shirvani Date: Wed, 28 Nov 2018 09:42:04 +0330 Subject: [PATCH 0844/1635] http crate removed, cargo fmt --- examples/echo.rs | 34 +++++++++++++--------------- examples/echo2.rs | 49 ++++++++++++++++++---------------------- examples/framed_hello.rs | 31 +++++++++++++------------ examples/hello-world.rs | 30 ++++++++++++------------ src/client/mod.rs | 2 +- 5 files changed, 70 insertions(+), 76 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index 91a3c76a..c98863e5 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -4,39 +4,37 @@ extern crate env_logger; extern crate actix_http; extern crate actix_net; +extern crate bytes; extern crate futures; extern crate http; -extern crate bytes; -use actix_http::{h1, Response, Request}; -use bytes::Bytes; +use actix_http::HttpMessage; +use actix_http::{h1, Request, Response}; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use bytes::Bytes; use futures::Future; -use http::header::{HeaderValue}; -use actix_http::HttpMessage; +use http::header::HeaderValue; use std::env; fn main() { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); - Server::new().bind("echo", "127.0.0.1:8080", || { - h1::H1Service::build() - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_req: Request| { - _req.body() - .limit(512) - .and_then(|bytes: Bytes| { + Server::new() + .bind("echo", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req: Request| { + _req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); Ok(res.body(bytes)) }) - }) - .map(|_| ()) - }).unwrap().run(); + }).map(|_| ()) + }).unwrap() + .run(); } - diff --git a/examples/echo2.rs b/examples/echo2.rs index 7d8a428f..4c144b43 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -4,44 +4,39 @@ extern crate env_logger; extern crate actix_http; extern crate actix_net; -extern crate futures; -extern crate http; extern crate bytes; +extern crate futures; -use actix_http::{h1, Response, Request, Error}; -use bytes::Bytes; +use actix_http::http::HeaderValue; +use actix_http::HttpMessage; +use actix_http::{h1, Error, Request, Response}; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use bytes::Bytes; use futures::Future; -use http::header::{HeaderValue}; -use actix_http::HttpMessage; use std::env; -fn handle_request(_req: Request) -> impl Future{ - _req.body() - .limit(512) - .from_err() - .and_then(|bytes: Bytes| { - info!("request body: {:?}", bytes); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - Ok(res.body(bytes)) - }) +fn handle_request(_req: Request) -> impl Future { + _req.body().limit(512).from_err().and_then(|bytes: Bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + Ok(res.body(bytes)) + }) } fn main() { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); - Server::new().bind("echo", "127.0.0.1:8080", || { - h1::H1Service::build() - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_req: Request| { - handle_request(_req) - }) - .map(|_| ()) - }).unwrap().run(); + Server::new() + .bind("echo", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req: Request| handle_request(_req)) + .map(|_| ()) + }).unwrap() + .run(); } - diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 76d23d08..0c9175a9 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -1,18 +1,18 @@ -extern crate log; extern crate env_logger; +extern crate log; extern crate actix_http; extern crate actix_net; +extern crate bytes; extern crate futures; extern crate http; -extern crate bytes; -use actix_http::{h1, ServiceConfig, SendResponse, Response}; -use actix_net::framed::IntoFramed; +use actix_http::{h1, Response, SendResponse, ServiceConfig}; use actix_net::codec::Framed; -use actix_net::stream::TakeItem; +use actix_net::framed::IntoFramed; use actix_net::server::Server; use actix_net::service::NewServiceExt; +use actix_net::stream::TakeItem; use futures::Future; use std::env; @@ -20,14 +20,15 @@ fn main() { env::set_var("RUST_LOG", "framed_hello=info"); env_logger::init(); - Server::new().bind("framed_hello", "127.0.0.1:8080", || { - IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) - .and_then(TakeItem::new().map_err(|_| ())) - .and_then(|(_req, _framed): (_, Framed<_, _>)| { - SendResponse::send(_framed, Response::Ok().body("Hello world!")) - .map_err(|_| ()) - .map(|_| ()) - }) - }).unwrap().run(); + Server::new() + .bind("framed_hello", "127.0.0.1:8080", || { + IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) + .and_then(TakeItem::new().map_err(|_| ())) + .and_then(|(_req, _framed): (_, Framed<_, _>)| { + SendResponse::send(_framed, Response::Ok().body("Hello world!")) + .map_err(|_| ()) + .map(|_| ()) + }) + }).unwrap() + .run(); } - diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 44e453df..74ff509b 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -11,25 +11,25 @@ use actix_http::{h1, Response}; use actix_net::server::Server; use actix_net::service::NewServiceExt; use futures::future; -use http::header::{HeaderValue}; +use http::header::HeaderValue; use std::env; fn main() { env::set_var("RUST_LOG", "hello_world=info"); env_logger::init(); - Server::new().bind("hello-world", "127.0.0.1:8080", || { - h1::H1Service::build() - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_req| { - info!("{:?}", _req); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - future::ok::<_, ()>(res.body("Hello world!")) - }) - .map(|_| ()) - }).unwrap().run(); + Server::new() + .bind("hello-world", "127.0.0.1:8080", || { + h1::H1Service::build() + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|_req| { + info!("{:?}", _req); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + future::ok::<_, ()>(res.body("Hello world!")) + }).map(|_| ()) + }).unwrap() + .run(); } - diff --git a/src/client/mod.rs b/src/client/mod.rs index dcc4f5d4..76c3f8b8 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -13,4 +13,4 @@ pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; \ No newline at end of file +pub use self::response::ClientResponse; From 06387fc778f8d941921127a72f29d9777566b804 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Nov 2018 09:02:31 -1000 Subject: [PATCH 0845/1635] display parse error for ws client errors --- src/ws/client/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 951cd6ac..729a00ce 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -37,7 +37,7 @@ pub enum ClientError { #[fail(display = "Http parsing error")] Http(HttpError), /// Response parsing error - #[fail(display = "Response parsing error")] + #[fail(display = "Response parsing error: {}", _0)] ParseError(ParseError), /// Protocol error #[fail(display = "{}", _0)] From d269904fbffe4b24a35508d44796b7e3373f9e36 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Nov 2018 09:10:13 -1000 Subject: [PATCH 0846/1635] add cause for nested errors --- src/ws/client/error.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 729a00ce..589648ee 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -35,19 +35,19 @@ pub enum ClientError { InvalidChallengeResponse(String, HeaderValue), /// Http parsing error #[fail(display = "Http parsing error")] - Http(HttpError), + Http(#[cause] HttpError), /// Response parsing error #[fail(display = "Response parsing error: {}", _0)] - ParseError(ParseError), + ParseError(#[cause] ParseError), /// Protocol error #[fail(display = "{}", _0)] Protocol(#[cause] ProtocolError), /// Connect error - #[fail(display = "{:?}", _0)] + #[fail(display = "Connector error: {:?}", _0)] Connect(ConnectorError), /// IO Error #[fail(display = "{}", _0)] - Io(io::Error), + Io(#[cause] io::Error), /// "Disconnected" #[fail(display = "Disconnected")] Disconnected, From 5003c00efbdeb8ef76b8137a3d604d7ba4c16d4d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 30 Nov 2018 11:57:57 -0800 Subject: [PATCH 0847/1635] use new Service and NewService traits --- Cargo.toml | 4 +-- rustfmt.toml | 2 +- src/client/connector.rs | 56 ++++++++++++---------------------------- src/client/pipeline.rs | 2 +- src/client/pool.rs | 11 ++++---- src/client/request.rs | 2 +- src/h1/dispatcher.rs | 14 +++++----- src/h1/service.rs | 34 +++++++++++------------- src/service.rs | 16 +++++------- src/test.rs | 9 +++---- src/ws/client/service.rs | 13 +++++----- src/ws/service.rs | 8 +++--- src/ws/transport.rs | 10 +++---- 13 files changed, 73 insertions(+), 108 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2c7c084..b1dd69ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,8 +45,8 @@ rust-tls = ["rustls", "actix-net/rust-tls"] [dependencies] actix = "0.7.5" -actix-net = "0.2.3" -#actix-net = { git="https://github.com/actix/actix-net.git" } +#actix-net = "0.3.0" +actix-net = { git="https://github.com/actix/actix-net.git" } base64 = "0.9" bitflags = "1.0" diff --git a/rustfmt.toml b/rustfmt.toml index 4fff285e..5fcaaca0 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,5 @@ max_width = 89 reorder_imports = true #wrap_comments = true -fn_args_density = "Compressed" +#fn_args_density = "Compressed" #use_small_heuristics = false diff --git a/src/client/connector.rs b/src/client/connector.rs index 42cba9de..3729ce39 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -134,11 +134,8 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { + ) -> impl Service + Clone + { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( @@ -216,7 +213,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -224,8 +221,7 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service - + Clone, + T: Service + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -234,16 +230,15 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< - as Service>::Future, + as Service>::Future, FutureResult, ConnectorError>, >; @@ -251,7 +246,7 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: Connect) -> Self::Future { if req.is_secure() { Either::B(err(ConnectorError::SslIsNotSupported)) } else if let Err(e) = req.validate() { @@ -295,16 +290,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1), - Error = ConnectorError, - > + Clone, - T2: Service< - Request = Connect, - Response = (Connect, Io2), - Error = ConnectorError, - > + Clone, + T1: Service + Clone, + T2: Service + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -318,18 +305,9 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1), - Error = ConnectorError, - >, - T2: Service< - Request = Connect, - Response = (Connect, Io2), - Error = ConnectorError, - >, + T1: Service, + T2: Service, { - type Request = Connect; type Response = IoEither, IoConnection>; type Error = ConnectorError; type Future = Either< @@ -344,7 +322,7 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: Connect) -> Self::Future { if let Err(e) = req.validate() { Either::A(err(e)) } else if req.is_secure() { @@ -364,7 +342,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -372,7 +350,7 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -390,7 +368,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -398,7 +376,7 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index e1d8421e..e7526550 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -21,7 +21,7 @@ pub(crate) fn send_request( connector: &mut T, ) -> impl Future where - T: Service, + T: Service, B: MessageBody, I: Connection, { diff --git a/src/client/pool.rs b/src/client/pool.rs index 44008f34..decf8019 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -47,7 +47,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) fn new( connector: T, @@ -83,12 +83,11 @@ where } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< @@ -100,7 +99,7 @@ where self.0.poll_ready() } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: Connect) -> Self::Future { let key = req.key(); // acquire connection @@ -456,7 +455,7 @@ where impl Future for ConnectorPoolSupport where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, T::Future: 'static, { type Item = (); diff --git a/src/client/request.rs b/src/client/request.rs index dd29d797..e71c3ffd 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -177,7 +177,7 @@ where connector: &mut T, ) -> impl Future where - T: Service, + T: Service, I: Connection, { pipeline::send_request(self.head, self.body, connector) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 48c8e710..5e2742aa 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -36,14 +36,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher, B: MessageBody> where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher, B: MessageBody> where S::Error: Debug, { @@ -67,13 +67,13 @@ enum DispatcherMessage { Error(Response<()>), } -enum State { +enum State, B: MessageBody> { None, ServiceCall(S::Future), SendPayload(ResponseBody), } -impl State { +impl, B: MessageBody> State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -86,7 +86,7 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { @@ -140,7 +140,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { @@ -463,7 +463,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { diff --git a/src/h1/service.rs b/src/h1/service.rs index 0f0452ee..e21d0fb6 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -27,13 +27,13 @@ pub struct H1Service { impl H1Service where - S: NewService> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -49,15 +49,14 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, { - type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; @@ -89,7 +88,7 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where - S: NewService, + S: NewService, S::Service: Clone, S::Error: Debug, { @@ -186,7 +185,7 @@ where pub fn finish(self, service: F) -> H1Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -202,7 +201,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse { +pub struct H1ServiceResponse, B> { fut: S::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -211,7 +210,7 @@ pub struct H1ServiceResponse { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService>, S::Service: Clone, S::Error: Debug, B: MessageBody, @@ -237,7 +236,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service> + Clone, + S: Service> + Clone, S::Error: Debug, B: MessageBody, { @@ -250,14 +249,13 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone, + S: Service> + Clone, S::Error: Debug, B: MessageBody, { - type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type Future = Dispatcher; @@ -266,7 +264,7 @@ where self.srv.poll_ready().map_err(DispatchError::Service) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: T) -> Self::Future { Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) } } @@ -290,11 +288,10 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { - type Request = T; type Response = (Request, Framed); type Error = ParseError; type InitError = (); @@ -316,11 +313,10 @@ pub struct OneRequestService { _t: PhantomData, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { - type Request = T; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; @@ -329,7 +325,7 @@ where Ok(Async::Ready(())) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: T) -> Self::Future { OneRequestServiceResponse { framed: Some(Framed::new(req, Codec::new(self.config.clone()))), } diff --git a/src/service.rs b/src/service.rs index 6a31b6bb..aa507acb 100644 --- a/src/service.rs +++ b/src/service.rs @@ -23,12 +23,11 @@ where } } -impl NewService for SendError +impl NewService)>> for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { - type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); @@ -40,12 +39,11 @@ where } } -impl Service for SendError +impl Service)>> for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { - type Request = Result)>; type Response = R; type Error = (E, Framed); type Future = Either)>, SendErrorFut>; @@ -54,7 +52,7 @@ where Ok(Async::Ready(())) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: Result)>) -> Self::Future { match req { Ok(r) => Either::A(ok(r)), Err((e, framed)) => { @@ -131,12 +129,11 @@ where } } -impl NewService for SendResponse +impl NewService<(Response, Framed)> for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { - type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); @@ -148,12 +145,11 @@ where } } -impl Service for SendResponse +impl Service<(Response, Framed)> for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { - type Request = (Response, Framed); type Response = Framed; type Error = Error; type Future = SendResponseFut; @@ -162,7 +158,7 @@ where Ok(Async::Ready(())) } - fn call(&mut self, (res, framed): Self::Request) -> Self::Future { + fn call(&mut self, (res, framed): (Response, Framed)) -> Self::Future { let (res, body) = res.replace_body(()); SendResponseFut { res: Some(Message::Item((res, body.length()))), diff --git a/src/test.rs b/src/test.rs index 84a959c4..3d12e344 100644 --- a/src/test.rs +++ b/src/test.rs @@ -306,8 +306,7 @@ impl TestServer { pub fn with_factory( factory: F, ) -> TestServerRuntime< - impl Service - + Clone, + impl Service + Clone, > { let (tx, rx) = mpsc::channel(); @@ -339,8 +338,8 @@ impl TestServer { } fn new_connector( -) -> impl Service - + Clone { +) -> impl Service + Clone + { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -441,7 +440,7 @@ impl TestServerRuntime { impl TestServerRuntime where - T: Service + Clone, + T: Service + Clone, T::Response: Connection, { /// Connect to websocket server at a given path diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 94be59f6..68f8032e 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -26,7 +26,7 @@ pub type DefaultClient = Client; /// WebSocket's client pub struct Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { connector: T, @@ -34,7 +34,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -51,7 +51,7 @@ impl Default for Client { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -61,13 +61,12 @@ where } } -impl Service for Client +impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { - type Request = Connect; type Response = Framed; type Error = ClientError; type Future = Either< @@ -79,7 +78,7 @@ where self.connector.poll_ready().map_err(ClientError::from) } - fn call(&mut self, mut req: Self::Request) -> Self::Future { + fn call(&mut self, mut req: Connect) -> Self::Future { if let Some(e) = req.err.take() { Either::A(err(e)) } else if let Some(e) = req.http_err.take() { diff --git a/src/ws/service.rs b/src/ws/service.rs index 9cce4d63..118a2244 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -20,8 +20,7 @@ impl Default for VerifyWebSockets { } } -impl NewService for VerifyWebSockets { - type Request = (Request, Framed); +impl NewService<(Request, Framed)> for VerifyWebSockets { type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); @@ -33,8 +32,7 @@ impl NewService for VerifyWebSockets { } } -impl Service for VerifyWebSockets { - type Request = (Request, Framed); +impl Service<(Request, Framed)> for VerifyWebSockets { type Response = (Request, Framed); type Error = (HandshakeError, Framed); type Future = FutureResult; @@ -43,7 +41,7 @@ impl Service for VerifyWebSockets { Ok(Async::Ready(())) } - fn call(&mut self, (req, framed): Self::Request) -> Self::Future { + fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { match verify_handshake(&req) { Err(e) => Err((e, framed)).into_future(), Ok(_) => Ok((req, framed)).into_future(), diff --git a/src/ws/transport.rs b/src/ws/transport.rs index 102d02b4..8cd79cb0 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -8,7 +8,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service, + S: Service, T: AsyncRead + AsyncWrite, { inner: FramedTransport, @@ -17,17 +17,17 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Transport { inner: FramedTransport::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Transport { inner: FramedTransport::new(framed, service), } @@ -37,7 +37,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { From c0f8bc9e902036ef72b7be105e2fe0a7e8b7c6b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 30 Nov 2018 16:04:33 -0800 Subject: [PATCH 0848/1635] fix ssl support --- src/client/connector.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index 3729ce39..9d084154 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -272,12 +272,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Request = Connect, + Connect, Response = (Connect, Io1), Error = ConnectorError, >, T2: Service< - Request = Connect, + Connect, Response = (Connect, Io2), Error = ConnectorError, >, @@ -301,7 +301,7 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, @@ -344,7 +344,7 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, T: Service, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } @@ -370,7 +370,7 @@ mod connect_impl { Io2: AsyncRead + AsyncWrite + 'static, T: Service, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } From 68c5d6e6d69f14c14c62d03b9e280ebfc320b6e9 Mon Sep 17 00:00:00 2001 From: vemoo Date: Sun, 2 Dec 2018 06:32:55 +0100 Subject: [PATCH 0849/1635] impl `From>` for `Binary` (#611) impl `From` for `Cow<'static, [u8]>` and `From>` for `Binary` --- src/body.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/body.rs b/src/body.rs index a93db1e9..5487dbba 100644 --- a/src/body.rs +++ b/src/body.rs @@ -1,5 +1,6 @@ use bytes::{Bytes, BytesMut}; use futures::Stream; +use std::borrow::Cow; use std::sync::Arc; use std::{fmt, mem}; @@ -194,12 +195,30 @@ impl From> for Binary { } } +impl From> for Binary { + fn from(b: Cow<'static, [u8]>) -> Binary { + match b { + Cow::Borrowed(s) => Binary::Slice(s), + Cow::Owned(vec) => Binary::Bytes(Bytes::from(vec)), + } + } +} + impl From for Binary { fn from(s: String) -> Binary { Binary::Bytes(Bytes::from(s)) } } +impl From> for Binary { + fn from(s: Cow<'static, str>) -> Binary { + match s { + Cow::Borrowed(s) => Binary::Slice(s.as_ref()), + Cow::Owned(s) => Binary::Bytes(Bytes::from(s)), + } + } +} + impl<'a> From<&'a String> for Binary { fn from(s: &'a String) -> Binary { Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) @@ -287,6 +306,16 @@ mod tests { assert_eq!(Binary::from("test").as_ref(), b"test"); } + #[test] + fn test_cow_str() { + let cow: Cow<'static, str> = Cow::Borrowed("test"); + assert_eq!(Binary::from(cow.clone()).len(), 4); + assert_eq!(Binary::from(cow.clone()).as_ref(), b"test"); + let cow: Cow<'static, str> = Cow::Owned("test".to_owned()); + assert_eq!(Binary::from(cow.clone()).len(), 4); + assert_eq!(Binary::from(cow.clone()).as_ref(), b"test"); + } + #[test] fn test_static_bytes() { assert_eq!(Binary::from(b"test".as_ref()).len(), 4); @@ -307,6 +336,16 @@ mod tests { assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test"); } + #[test] + fn test_cow_bytes() { + let cow: Cow<'static, [u8]> = Cow::Borrowed(b"test"); + assert_eq!(Binary::from(cow.clone()).len(), 4); + assert_eq!(Binary::from(cow.clone()).as_ref(), b"test"); + let cow: Cow<'static, [u8]> = Cow::Owned(Vec::from("test")); + assert_eq!(Binary::from(cow.clone()).len(), 4); + assert_eq!(Binary::from(cow.clone()).as_ref(), b"test"); + } + #[test] fn test_arc_string() { let b = Arc::new("test".to_owned()); From 08c7743bb8431033b180a872f39eb006db0933fd Mon Sep 17 00:00:00 2001 From: Kelly Thomas Kline Date: Thu, 15 Nov 2018 18:59:36 -0800 Subject: [PATCH 0850/1635] Add set_mailbox_capacity() function --- src/ws/context.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ws/context.rs b/src/ws/context.rs index 4db83df5..5e207d43 100644 --- a/src/ws/context.rs +++ b/src/ws/context.rs @@ -231,6 +231,13 @@ where pub fn handle(&self) -> SpawnHandle { self.inner.curr_handle() } + + /// Set mailbox capacity + /// + /// By default mailbox capacity is 16 messages. + pub fn set_mailbox_capacity(&mut self, cap: usize) { + self.inner.set_mailbox_capacity(cap) + } } impl WsWriter for WebsocketContext From b1635bc0e6ab116c2ccb684c0440935fe6ac5395 Mon Sep 17 00:00:00 2001 From: silwol Date: Tue, 4 Dec 2018 07:58:22 +0100 Subject: [PATCH 0851/1635] Update some dependencies (#612) * Update rand to 0.6 * Update parking_lot to 0.7 * Update env_logger to 0.6 --- Cargo.toml | 6 +++--- tests/test_client.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e3fbd4e3..37e90051 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ mime = "0.3" mime_guess = "2.0.0-alpha" num_cpus = "1.0" percent-encoding = "1.0" -rand = "0.5" +rand = "0.6" regex = "1.0" serde = "1.0" serde_json = "1.0" @@ -87,7 +87,7 @@ encoding = "0.2" language-tags = "0.2" lazy_static = "1.0" lazycell = "1.0.0" -parking_lot = "0.6" +parking_lot = "0.7" serde_urlencoded = "^0.5.3" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } @@ -127,7 +127,7 @@ webpki-roots = { version = "0.15", optional = true } tokio-uds = { version="0.2", optional = true } [dev-dependencies] -env_logger = "0.5" +env_logger = "0.6" serde_derive = "1.0" [build-dependencies] diff --git a/tests/test_client.rs b/tests/test_client.rs index 8c5d5819..9808f3e6 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -179,7 +179,7 @@ fn test_client_gzip_encoding_large() { #[test] fn test_client_gzip_encoding_large_random() { let data = rand::thread_rng() - .gen_ascii_chars() + .sample_iter(&rand::distributions::Alphanumeric) .take(100_000) .collect::(); @@ -247,7 +247,7 @@ fn test_client_brotli_encoding() { #[test] fn test_client_brotli_encoding_large_random() { let data = rand::thread_rng() - .gen_ascii_chars() + .sample_iter(&rand::distributions::Alphanumeric) .take(70_000) .collect::(); @@ -309,7 +309,7 @@ fn test_client_deflate_encoding() { #[test] fn test_client_deflate_encoding_large_random() { let data = rand::thread_rng() - .gen_ascii_chars() + .sample_iter(&rand::distributions::Alphanumeric) .take(70_000) .collect::(); From 0745a1a9f8d43840454c6aae24df5e2c6f781c36 Mon Sep 17 00:00:00 2001 From: Douman Date: Wed, 5 Dec 2018 03:07:59 -0500 Subject: [PATCH 0852/1635] Remove usage of upcoming keyword async AsyncResult::async is replaced with AsyncResult::future --- CHANGES.md | 2 ++ MIGRATION.md | 2 ++ src/client/connector.rs | 2 +- src/client/request.rs | 2 +- src/handler.rs | 6 +++--- src/middleware/csrf.rs | 2 +- src/route.rs | 2 +- src/scope.rs | 2 +- src/with.rs | 4 ++-- 9 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 902a84f6..4d8fa128 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * `QueryConfig` and `PathConfig` are made public. +* `AsyncResult::async` is changed to `AsyncResult::future` as `async` is reserved keyword in 2018 edition. + ### Added * By default, `Path` extractor now percent decode all characters. This behaviour can be disabled diff --git a/MIGRATION.md b/MIGRATION.md index 26a31424..6b49e3e6 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -25,6 +25,8 @@ } ``` +* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future` + ## 0.7.4 diff --git a/src/client/connector.rs b/src/client/connector.rs index 72132bc6..f5affad3 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -942,7 +942,7 @@ impl Handler for ClientConnector { } let host = uri.host().unwrap().to_owned(); - let port = uri.port().unwrap_or_else(|| proto.port()); + let port = uri.port_part().map(|port| port.as_u16()).unwrap_or_else(|| proto.port()); let key = Key { host, port, diff --git a/src/client/request.rs b/src/client/request.rs index 76fb1be5..71da8f74 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -631,7 +631,7 @@ impl ClientRequestBuilder { if !parts.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match parts.uri.port() { + let _ = match parts.uri.port_part().map(|port| port.as_u16()) { None | Some(80) | Some(443) => write!(wrt, "{}", host), Some(port) => write!(wrt, "{}:{}", host, port), }; diff --git a/src/handler.rs b/src/handler.rs index 88210fbc..6ed93f92 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -250,7 +250,7 @@ pub(crate) enum AsyncResultItem { impl AsyncResult { /// Create async response #[inline] - pub fn async(fut: Box>) -> AsyncResult { + pub fn future(fut: Box>) -> AsyncResult { AsyncResult(Some(AsyncResultItem::Future(fut))) } @@ -401,7 +401,7 @@ where }, Err(e) => err(e), }); - Ok(AsyncResult::async(Box::new(fut))) + Ok(AsyncResult::future(Box::new(fut))) } } @@ -502,7 +502,7 @@ where Err(e) => Either::A(err(e)), } }); - AsyncResult::async(Box::new(fut)) + AsyncResult::future(Box::new(fut)) } } diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs index 02cd150d..cacfc8d5 100644 --- a/src/middleware/csrf.rs +++ b/src/middleware/csrf.rs @@ -76,7 +76,7 @@ impl ResponseError for CsrfError { } fn uri_origin(uri: &Uri) -> Option { - match (uri.scheme_part(), uri.host(), uri.port()) { + match (uri.scheme_part(), uri.host(), uri.port_part().map(|port| port.as_u16())) { (Some(scheme), Some(host), Some(port)) => { Some(format!("{}://{}:{}", scheme, host, port)) } diff --git a/src/route.rs b/src/route.rs index e4a7a957..884a367e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -57,7 +57,7 @@ impl Route { pub(crate) fn compose( &self, req: HttpRequest, mws: Rc>>>, ) -> AsyncResult { - AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone()))) + AsyncResult::future(Box::new(Compose::new(req, mws, self.handler.clone()))) } /// Add match predicate to route. diff --git a/src/scope.rs b/src/scope.rs index 1bddc0e0..fb9e7514 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -356,7 +356,7 @@ impl RouteHandler for Scope { if self.middlewares.is_empty() { self.router.handle(&req2) } else { - AsyncResult::async(Box::new(Compose::new( + AsyncResult::future(Box::new(Compose::new( req2, Rc::clone(&self.router), Rc::clone(&self.middlewares), diff --git a/src/with.rs b/src/with.rs index c6d54dee..140e086e 100644 --- a/src/with.rs +++ b/src/with.rs @@ -86,7 +86,7 @@ where match fut.poll() { Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), + Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)), Err(e) => AsyncResult::err(e), } } @@ -208,7 +208,7 @@ where match fut.poll() { Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)), + Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)), Err(e) => AsyncResult::err(e), } } From ac9fc662c625f5c6273744b98d804019249f887e Mon Sep 17 00:00:00 2001 From: Douman Date: Wed, 5 Dec 2018 18:27:06 +0300 Subject: [PATCH 0853/1635] Bump version to 0.7.15 --- CHANGES.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4d8fa128..6092544e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.15] - 2018-xx-xx +## [0.7.15] - 2018-12-05 ## Changed diff --git a/Cargo.toml b/Cargo.toml index 37e90051..7b8dcec3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.14" +version = "0.7.15" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From e9121025b7abde064124584118c7689a3e5b519e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 6 Dec 2018 14:32:52 -0800 Subject: [PATCH 0854/1635] convert to 2018 edition --- Cargo.toml | 1 + examples/echo.rs | 6 ++- examples/echo2.rs | 3 +- examples/framed_hello.rs | 3 +- examples/hello-world.rs | 6 ++- src/body.rs | 2 +- src/client/connector.rs | 21 ++++------ src/client/error.rs | 13 ++----- src/client/pipeline.rs | 26 +++++++------ src/client/request.rs | 24 ++++++------ src/client/response.rs | 8 ++-- src/config.rs | 9 +++-- src/error.rs | 7 ++-- src/h1/client.rs | 21 +++++----- src/h1/codec.rs | 19 ++++----- src/h1/decoder.rs | 21 +++++----- src/h1/dispatcher.rs | 22 +++++------ src/h1/encoder.rs | 20 +++++----- src/h1/mod.rs | 2 +- src/h1/service.rs | 21 +++++----- src/header.rs | 9 ++--- src/httpcodes.rs | 3 +- src/httpmessage.rs | 39 ++++++++++++------- src/json.rs | 22 +++++++---- src/lib.rs | 83 +++++++++------------------------------- src/message.rs | 4 +- src/payload.rs | 26 ++++++++----- src/request.rs | 9 ++--- src/response.rs | 11 +++--- src/service.rs | 22 ++++++----- src/test.rs | 14 +++---- src/ws/client/connect.rs | 5 +-- src/ws/client/error.rs | 9 ++--- src/ws/client/service.rs | 11 +++--- src/ws/frame.rs | 7 ++-- src/ws/mod.rs | 47 +++++++++++++++-------- src/ws/service.rs | 4 +- tests/test_client.rs | 3 +- tests/test_server.rs | 18 ++++++--- tests/test_ws.rs | 3 +- 40 files changed, 310 insertions(+), 294 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1dd69ac..57ed3f9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ categories = ["network-programming", "asynchronous", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +edition = "2018" [package.metadata.docs.rs] features = ["session"] diff --git a/examples/echo.rs b/examples/echo.rs index c98863e5..0453ad6a 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -34,7 +34,9 @@ fn main() { res.header("x-head", HeaderValue::from_static("dummy value!")); Ok(res.body(bytes)) }) - }).map(|_| ()) - }).unwrap() + }) + .map(|_| ()) + }) + .unwrap() .run(); } diff --git a/examples/echo2.rs b/examples/echo2.rs index 4c144b43..3206ff50 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -37,6 +37,7 @@ fn main() { .server_hostname("localhost") .finish(|_req: Request| handle_request(_req)) .map(|_| ()) - }).unwrap() + }) + .unwrap() .run(); } diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 0c9175a9..6c53d27f 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -29,6 +29,7 @@ fn main() { .map_err(|_| ()) .map(|_| ()) }) - }).unwrap() + }) + .unwrap() .run(); } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 74ff509b..b477f191 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -29,7 +29,9 @@ fn main() { let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); future::ok::<_, ()>(res.body("Hello world!")) - }).map(|_| ()) - }).unwrap() + }) + .map(|_| ()) + }) + .unwrap() .run(); } diff --git a/src/body.rs b/src/body.rs index cc4e77af..4b71e9bb 100644 --- a/src/body.rs +++ b/src/body.rs @@ -4,7 +4,7 @@ use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; -use error::{Error, PayloadError}; +use crate::error::{Error, PayloadError}; /// Type represent streaming payload pub type PayloadStream = Box>; diff --git a/src/client/connector.rs b/src/client/connector.rs index 9d084154..74ee6a7e 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -143,7 +143,8 @@ impl Connector { self.resolver .map_err(ConnectorError::from) .and_then(TcpConnector::default().from_err()), - ).map_err(|e| match e { + ) + .map_err(|e| match e { TimeoutError::Service(e) => e, TimeoutError::Timeout => ConnectorError::Timeout, }); @@ -170,7 +171,8 @@ impl Connector { OpensslConnector::service(self.connector) .map_err(ConnectorError::SslError), ), - ).map_err(|e| match e { + ) + .map_err(|e| match e { TimeoutError::Service(e) => e, TimeoutError::Timeout => ConnectorError::Timeout, }); @@ -180,7 +182,8 @@ impl Connector { self.resolver .map_err(ConnectorError::from) .and_then(TcpConnector::default().from_err()), - ).map_err(|e| match e { + ) + .map_err(|e| match e { TimeoutError::Service(e) => e, TimeoutError::Timeout => ConnectorError::Timeout, }); @@ -271,16 +274,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Connect, - Response = (Connect, Io1), - Error = ConnectorError, - >, - T2: Service< - Connect, - Response = (Connect, Io2), - Error = ConnectorError, - >, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, diff --git a/src/client/error.rs b/src/client/error.rs index 2c475364..d2a0f38e 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -1,23 +1,18 @@ use std::io; +use failure::Fail; use trust_dns_resolver::error::ResolveError; #[cfg(feature = "ssl")] use openssl::ssl::Error as SslError; -#[cfg(all( - feature = "tls", - not(any(feature = "ssl", feature = "rust-tls")) -))] +#[cfg(all(feature = "tls", not(any(feature = "ssl", feature = "rust-tls"))))] use native_tls::Error as SslError; -#[cfg(all( - feature = "rust-tls", - not(any(feature = "tls", feature = "ssl")) -))] +#[cfg(all(feature = "rust-tls", not(any(feature = "tls", feature = "ssl"))))] use std::io::Error as SslError; -use error::{Error, ParseError}; +use crate::error::{Error, ParseError}; /// A set of errors that can occur while connecting to an HTTP host #[derive(Fail, Debug)] diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index e7526550..fc1e53e8 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -10,10 +10,10 @@ use tokio_io::{AsyncRead, AsyncWrite}; use super::error::{ConnectorError, SendRequestError}; use super::response::ClientResponse; use super::{Connect, Connection}; -use body::{BodyLength, MessageBody, PayloadStream}; -use error::PayloadError; -use h1; -use message::RequestHead; +use crate::body::{BodyLength, MessageBody, PayloadStream}; +use crate::error::PayloadError; +use crate::h1; +use crate::message::RequestHead; pub(crate) fn send_request( head: RequestHead, @@ -174,14 +174,16 @@ impl Stream for Payload { fn poll(&mut self) -> Poll, Self::Error> { match self.framed.as_mut().unwrap().poll()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(Some(chunk)) => if let Some(chunk) = chunk { - Ok(Async::Ready(Some(chunk))) - } else { - let framed = self.framed.take().unwrap(); - let force_close = framed.get_codec().keepalive(); - release_connection(framed, force_close); - Ok(Async::Ready(None)) - }, + Async::Ready(Some(chunk)) => { + if let Some(chunk) = chunk { + Ok(Async::Ready(Some(chunk))) + } else { + let framed = self.framed.take().unwrap(); + let force_close = framed.get_codec().keepalive(); + release_connection(framed, force_close); + Ok(Async::Ready(None)) + } + } Async::Ready(None) => Ok(Async::Ready(None)), } } diff --git a/src/client/request.rs b/src/client/request.rs index e71c3ffd..5f294bbd 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -8,14 +8,14 @@ use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use body::{BodyStream, MessageBody}; -use error::Error; -use header::{self, Header, IntoHeaderValue}; -use http::{ +use crate::body::{BodyStream, MessageBody}; +use crate::error::Error; +use crate::header::{self, Header, IntoHeaderValue}; +use crate::http::{ uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use message::{ConnectionType, Head, RequestHead}; +use crate::message::{ConnectionType, Head, RequestHead}; use super::response::ClientResponse; use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; @@ -355,14 +355,16 @@ impl ClientRequestBuilder { { if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { - Ok(key) => if !parts.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); + Ok(key) => { + if !parts.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), } - Err(e) => self.err = Some(e.into()), } - }, + } Err(e) => self.err = Some(e.into()), }; } diff --git a/src/client/response.rs b/src/client/response.rs index dc7b13c1..6bfdfc32 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -5,10 +5,10 @@ use bytes::Bytes; use futures::{Async, Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; -use body::PayloadStream; -use error::PayloadError; -use httpmessage::HttpMessage; -use message::{Head, ResponseHead}; +use crate::body::PayloadStream; +use crate::error::PayloadError; +use crate::httpmessage::HttpMessage; +use crate::message::{Head, ResponseHead}; use super::pipeline::Payload; diff --git a/src/config.rs b/src/config.rs index 833bca7f..661c0901 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ use std::{fmt, net}; use bytes::BytesMut; use futures::{future, Future}; +use log::error; use time; use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; @@ -268,9 +269,11 @@ impl ServiceConfigBuilder { pub fn server_address(mut self, addr: S) -> Self { match addr.to_socket_addrs() { Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => if let Some(addr) = addrs.next() { - self.addr = addr; - }, + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + self.addr = addr; + } + } } self } diff --git a/src/error.rs b/src/error.rs index 2e0c2382..280f9a32 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,8 +20,8 @@ use tokio_timer::Error as TimerError; // re-exports pub use cookie::ParseError as CookieParseError; -use body::Body; -use response::{Response, ResponseParts}; +use crate::body::Body; +use crate::response::{Response, ResponseParts}; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -186,7 +186,8 @@ impl From for Error { /// Compatibility for `failure::Error` impl ResponseError for failure::Compat where T: fmt::Display + fmt::Debug + Sync + Send + 'static -{} +{ +} impl From for Error { fn from(err: failure::Error) -> Error { diff --git a/src/h1/client.rs b/src/h1/client.rs index 7704ba97..f547983f 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -1,22 +1,23 @@ #![allow(unused_imports, unused_variables, dead_code)] use std::io::{self, Write}; +use bitflags::bitflags; use bytes::{BufMut, Bytes, BytesMut}; +use http::header::{ + HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, +}; +use http::{Method, Version}; use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; use super::{Message, MessageType}; -use body::BodyLength; -use client::ClientResponse; -use config::ServiceConfig; -use error::{ParseError, PayloadError}; -use helpers; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, -}; -use http::{Method, Version}; -use message::{ConnectionType, Head, MessagePool, RequestHead}; +use crate::body::BodyLength; +use crate::client::ClientResponse; +use crate::config::ServiceConfig; +use crate::error::{ParseError, PayloadError}; +use crate::helpers; +use crate::message::{ConnectionType, Head, MessagePool, RequestHead}; bitflags! { struct Flags: u8 { diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 3174e78e..54c1ce2e 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -2,21 +2,22 @@ use std::fmt; use std::io::{self, Write}; +use bitflags::bitflags; use bytes::{BufMut, Bytes, BytesMut}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::{Method, StatusCode, Version}; use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; use super::{Message, MessageType}; -use body::BodyLength; -use config::ServiceConfig; -use error::ParseError; -use helpers; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::{Method, StatusCode, Version}; -use message::{ConnectionType, Head, ResponseHead}; -use request::Request; -use response::Response; +use crate::body::BodyLength; +use crate::config::ServiceConfig; +use crate::error::ParseError; +use crate::helpers; +use crate::message::{ConnectionType, Head, ResponseHead}; +use crate::request::Request; +use crate::response::Response; bitflags! { struct Flags: u8 { diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index a081a5cf..26b28440 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -3,15 +3,16 @@ use std::{io, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; -use httparse; -use tokio_codec::Decoder; - -use client::ClientResponse; -use error::ParseError; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; -use message::ConnectionType; -use request::Request; +use httparse; +use log::{debug, error, trace}; +use tokio_codec::Decoder; + +use crate::client::ClientResponse; +use crate::error::ParseError; +use crate::message::ConnectionType; +use crate::request::Request; const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; @@ -825,13 +826,13 @@ mod tests { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); let mut reader = MessageDecoder::::default(); - assert!{ reader.decode(&mut buf).unwrap().is_none() } + assert! { reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t"); - assert!{ reader.decode(&mut buf).unwrap().is_none() } + assert! { reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"es"); - assert!{ reader.decode(&mut buf).unwrap().is_none() } + assert! { reader.decode(&mut buf).unwrap().is_none() } buf.extend(b"t: value\r\n\r\n"); let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 5e2742aa..59b419c3 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -5,19 +5,19 @@ use std::time::Instant; use actix_net::codec::Framed; use actix_net::service::Service; - -use futures::{Async, Future, Poll, Sink, Stream}; +use bitflags::bitflags; +use futures::{try_ready, Async, Future, Poll, Sink, Stream}; +use log::{debug, error, trace}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; -use error::{ParseError, PayloadError}; -use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; - -use body::{Body, BodyLength, MessageBody, ResponseBody}; -use config::ServiceConfig; -use error::DispatchError; -use request::Request; -use response::Response; +use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; +use crate::config::ServiceConfig; +use crate::error::DispatchError; +use crate::error::{ParseError, PayloadError}; +use crate::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; +use crate::request::Request; +use crate::response::Response; use super::codec::Codec; use super::{H1ServiceResult, Message, MessageType}; @@ -224,7 +224,7 @@ where }, State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { - Async::Ready(mut res) => { + Async::Ready(res) => { let (res, body) = res.replace_body(()); Some(self.send_response(res, body)?) } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index f9b4aab3..92456520 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -9,16 +9,15 @@ use bytes::{BufMut, Bytes, BytesMut}; use http::header::{ HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, }; -use http::{HeaderMap, StatusCode, Version}; +use http::{HeaderMap, Method, StatusCode, Version}; -use body::BodyLength; -use config::ServiceConfig; -use header::ContentEncoding; -use helpers; -use http::Method; -use message::{ConnectionType, RequestHead, ResponseHead}; -use request::Request; -use response::Response; +use crate::body::BodyLength; +use crate::config::ServiceConfig; +use crate::header::ContentEncoding; +use crate::helpers; +use crate::message::{ConnectionType, RequestHead, ResponseHead}; +use crate::request::Request; +use crate::response::Response; const AVERAGE_HEADER_SIZE: usize = 30; @@ -205,7 +204,8 @@ impl MessageType for RequestHead { Version::HTTP_11 => "HTTP/1.1", Version::HTTP_2 => "HTTP/2.0", } - ).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + ) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index da80e55e..461ecb95 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -16,7 +16,7 @@ pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; -use request::Request; +use crate::request::Request; /// H1 service response type pub enum H1ServiceResult { diff --git a/src/h1/service.rs b/src/h1/service.rs index e21d0fb6..1a5a587c 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -5,14 +5,15 @@ use std::net; use actix_net::codec::Framed; use actix_net::service::{IntoNewService, NewService, Service}; use futures::future::{ok, FutureResult}; -use futures::{Async, Future, Poll, Stream}; +use futures::{try_ready, Async, Future, Poll, Stream}; +use log::error; use tokio_io::{AsyncRead, AsyncWrite}; -use body::MessageBody; -use config::{KeepAlive, ServiceConfig}; -use error::{DispatchError, ParseError}; -use request::Request; -use response::Response; +use crate::body::MessageBody; +use crate::config::{KeepAlive, ServiceConfig}; +use crate::error::{DispatchError, ParseError}; +use crate::request::Request; +use crate::response::Response; use super::codec::Codec; use super::dispatcher::Dispatcher; @@ -174,9 +175,11 @@ where pub fn server_address(mut self, addr: U) -> Self { match addr.to_socket_addrs() { Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => if let Some(addr) = addrs.next() { - self.addr = addr; - }, + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + self.addr = addr; + } + } } self } diff --git a/src/header.rs b/src/header.rs index b1ba6524..6276dd4f 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,13 +1,12 @@ //! Various http headers use bytes::Bytes; +pub use http::header::*; +use http::Error as HttpError; use mime::Mime; -use modhttp::Error as HttpError; -pub use modhttp::header::*; - -use error::ParseError; -use httpmessage::HttpMessage; +use crate::error::ParseError; +use crate::httpmessage::HttpMessage; #[doc(hidden)] /// A trait for any object that will represent a header field and value. diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 7d42a1cc..80722734 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -1,7 +1,8 @@ //! Basic http responses #![allow(non_upper_case_globals)] use http::StatusCode; -use response::{Response, ResponseBuilder}; + +use crate::response::{Response, ResponseBuilder}; macro_rules! STATIC_RESP { ($name:ident, $status:expr) => { diff --git a/src/httpmessage.rs b/src/httpmessage.rs index f68f3650..e239a733 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -10,11 +10,11 @@ use serde::de::DeserializeOwned; use serde_urlencoded; use std::str; -use error::{ +use crate::error::{ ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, }; -use header::Header; -use json::JsonBody; +use crate::header::Header; +use crate::json::JsonBody; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { @@ -438,7 +438,8 @@ where body.extend_from_slice(&chunk); Ok(body) } - }).map(|body| body.freeze()), + }) + .map(|body| body.freeze()), )); self.poll() } @@ -546,7 +547,8 @@ where body.extend_from_slice(&chunk); Ok(body) } - }).and_then(move |body| { + }) + .and_then(move |body| { if (encoding as *const Encoding) == UTF_8 { serde_urlencoded::from_bytes::(&body) .map_err(|_| UrlencodedError::Parse) @@ -604,7 +606,8 @@ mod tests { let req = TestRequest::with_header( "content-type", "applicationadfadsfasdflknadsfklnadsfjson", - ).finish(); + ) + .finish(); assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); } @@ -619,7 +622,8 @@ mod tests { let req = TestRequest::with_header( "content-type", "application/json; charset=ISO-8859-2", - ).finish(); + ) + .finish(); assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); } @@ -631,7 +635,8 @@ mod tests { let req = TestRequest::with_header( "content-type", "application/json; charset=kkkttktk", - ).finish(); + ) + .finish(); assert_eq!( Some(ContentTypeError::UnknownEncoding), req.encoding().err() @@ -651,7 +656,8 @@ mod tests { .header( header::TRANSFER_ENCODING, Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), - ).finish(); + ) + .finish(); assert!(req.chunked().is_err()); } @@ -689,7 +695,8 @@ mod tests { let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "xxxx") + ) + .header(header::CONTENT_LENGTH, "xxxx") .finish(); assert_eq!( req.urlencoded::().poll().err().unwrap(), @@ -699,7 +706,8 @@ mod tests { let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "1000000") + ) + .header(header::CONTENT_LENGTH, "1000000") .finish(); assert_eq!( req.urlencoded::().poll().err().unwrap(), @@ -720,7 +728,8 @@ mod tests { let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") + ) + .header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) .finish(); @@ -735,7 +744,8 @@ mod tests { let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", - ).header(header::CONTENT_LENGTH, "11") + ) + .header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) .finish(); @@ -786,7 +796,8 @@ mod tests { b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", - )).finish(); + )) + .finish(); let mut r = Readlines::new(&req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( diff --git a/src/json.rs b/src/json.rs index e2c99ba3..0b6ac377 100644 --- a/src/json.rs +++ b/src/json.rs @@ -6,8 +6,8 @@ use mime; use serde::de::DeserializeOwned; use serde_json; -use error::JsonPayloadError; -use httpmessage::HttpMessage; +use crate::error::JsonPayloadError; +use crate::httpmessage::HttpMessage; /// Request payload json parser that resolves to a deserialized `T` value. /// @@ -124,7 +124,8 @@ impl Future for JsonBod body.extend_from_slice(&chunk); Ok(body) } - }).and_then(|body| Ok(serde_json::from_slice::(&body)?)); + }) + .and_then(|body| Ok(serde_json::from_slice::(&body)?)); self.fut = Some(Box::new(fut)); self.poll() } @@ -170,7 +171,8 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), - ).finish(); + ) + .finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); @@ -178,10 +180,12 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ).header( + ) + .header( header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), - ).finish(); + ) + .finish(); let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); @@ -189,10 +193,12 @@ mod tests { .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ).header( + ) + .header( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .finish(); let mut json = req.json::(); diff --git a/src/lib.rs b/src/lib.rs index 4870eb64..76c3a967 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,56 +62,12 @@ // #![warn(missing_docs)] #![allow(dead_code)] -extern crate actix; -extern crate actix_net; -#[macro_use] -extern crate log; -extern crate base64; -extern crate byteorder; -extern crate bytes; -extern crate sha1; -extern crate time; -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate failure; -#[macro_use] -extern crate futures; -extern crate cookie; -extern crate encoding; -extern crate http as modhttp; -extern crate httparse; -extern crate indexmap; -extern crate mime; -extern crate net2; -extern crate percent_encoding; -extern crate rand; -extern crate serde; -extern crate serde_json; -extern crate serde_urlencoded; -extern crate slab; -extern crate tokio; -extern crate tokio_codec; -extern crate tokio_current_thread; -extern crate tokio_io; -extern crate tokio_tcp; -extern crate tokio_timer; -extern crate trust_dns_proto; -extern crate trust_dns_resolver; -extern crate url as urlcrate; - -#[cfg(test)] -#[macro_use] -extern crate serde_derive; - -#[cfg(feature = "ssl")] -extern crate openssl; - pub mod body; pub mod client; mod config; mod extensions; mod header; +mod helpers; mod httpcodes; mod httpmessage; mod json; @@ -123,18 +79,17 @@ mod service; pub mod error; pub mod h1; -pub(crate) mod helpers; pub mod test; pub mod ws; -pub use body::{Body, MessageBody}; -pub use error::{Error, ResponseError, Result}; -pub use extensions::Extensions; -pub use httpmessage::HttpMessage; -pub use request::Request; -pub use response::Response; -pub use service::{SendError, SendResponse}; +pub use self::body::{Body, MessageBody}; pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; +pub use self::error::{Error, ResponseError, Result}; +pub use self::extensions::Extensions; +pub use self::httpmessage::HttpMessage; +pub use self::request::Request; +pub use self::response::Response; +pub use self::service::{SendError, SendResponse}; pub mod dev { //! The `actix-web` prelude for library developers @@ -147,31 +102,31 @@ pub mod dev { //! use actix_http::dev::*; //! ``` - pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use json::JsonBody; - pub use payload::{Payload, PayloadBuffer}; - pub use response::ResponseBuilder; + pub use crate::httpmessage::{MessageBody, Readlines, UrlEncoded}; + pub use crate::json::JsonBody; + pub use crate::payload::{Payload, PayloadBuffer}; + pub use crate::response::ResponseBuilder; } pub mod http { //! Various HTTP related types // re-exports - pub use modhttp::header::{HeaderName, HeaderValue}; - pub use modhttp::{Method, StatusCode, Version}; + pub use http::header::{HeaderName, HeaderValue}; + pub use http::{Method, StatusCode, Version}; #[doc(hidden)] - pub use modhttp::{uri, Error, HeaderMap, HttpTryFrom, Uri}; + pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri}; #[doc(hidden)] - pub use modhttp::uri::PathAndQuery; + pub use http::uri::PathAndQuery; pub use cookie::{Cookie, CookieBuilder}; /// Various http headers pub mod header { - pub use header::*; + pub use crate::header::*; } - pub use header::ContentEncoding; - pub use message::ConnectionType; + pub use crate::header::ContentEncoding; + pub use crate::message::ConnectionType; } diff --git a/src/message.rs b/src/message.rs index d3d52e2f..31d61f63 100644 --- a/src/message.rs +++ b/src/message.rs @@ -4,8 +4,8 @@ use std::rc::Rc; use http::{HeaderMap, Method, StatusCode, Uri, Version}; -use extensions::Extensions; -use payload::Payload; +use crate::extensions::Extensions; +use crate::payload::Payload; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] diff --git a/src/payload.rs b/src/payload.rs index b0592496..37f06d43 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -9,7 +9,7 @@ use std::cmp; use std::collections::VecDeque; use std::rc::{Rc, Weak}; -use error::PayloadError; +use crate::error::PayloadError; /// max buffer size 32k pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; @@ -515,7 +515,8 @@ where .fold(BytesMut::new(), |mut b, c| { b.extend_from_slice(c); b - }).freeze() + }) + .freeze() } } @@ -547,7 +548,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -571,7 +573,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -588,7 +591,8 @@ mod tests { payload.readany().err().unwrap(); let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -616,7 +620,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -649,7 +654,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -682,7 +688,8 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } #[test] @@ -703,6 +710,7 @@ mod tests { let res: Result<(), ()> = Ok(()); result(res) - })).unwrap(); + })) + .unwrap(); } } diff --git a/src/request.rs b/src/request.rs index 248555e6..60ddee19 100644 --- a/src/request.rs +++ b/src/request.rs @@ -4,11 +4,10 @@ use std::rc::Rc; use http::{header, HeaderMap, Method, Uri, Version}; -use extensions::Extensions; -use httpmessage::HttpMessage; -use payload::Payload; - -use message::{Message, MessagePool, RequestHead}; +use crate::extensions::Extensions; +use crate::httpmessage::HttpMessage; +use crate::message::{Message, MessagePool, RequestHead}; +use crate::payload::Payload; /// Request pub struct Request { diff --git a/src/response.rs b/src/response.rs index ae68189d..e506cd16 100644 --- a/src/response.rs +++ b/src/response.rs @@ -12,10 +12,10 @@ use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; use serde::Serialize; use serde_json; -use body::{Body, BodyStream, MessageBody, ResponseBody}; -use error::Error; -use header::{Header, IntoHeaderValue}; -use message::{ConnectionType, Head, ResponseHead}; +use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; +use crate::error::Error; +use crate::header::{Header, IntoHeaderValue}; +use crate::message::{ConnectionType, Head, ResponseHead}; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -161,7 +161,8 @@ impl Response { HeaderValue::from_str(&cookie.to_string()) .map(|c| { h.append(header::SET_COOKIE, c); - }).map_err(|e| e.into()) + }) + .map_err(|e| e.into()) } /// Remove all cookies with the given name from this response. Returns diff --git a/src/service.rs b/src/service.rs index aa507acb..a6a82097 100644 --- a/src/service.rs +++ b/src/service.rs @@ -6,10 +6,10 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; -use body::{BodyLength, MessageBody, ResponseBody}; -use error::{Error, ResponseError}; -use h1::{Codec, Message}; -use response::Response; +use crate::body::{BodyLength, MessageBody, ResponseBody}; +use crate::error::{Error, ResponseError}; +use crate::h1::{Codec, Message}; +use crate::response::Response; pub struct SendError(PhantomData<(T, R, E)>); @@ -56,7 +56,7 @@ where match req { Ok(r) => Either::A(ok(r)), Err((e, framed)) => { - let mut res = e.error_response().set_body(format!("{}", e)); + let res = e.error_response().set_body(format!("{}", e)); let (res, _body) = res.replace_body(()); Either::B(SendErrorFut { framed: Some(framed), @@ -206,11 +206,13 @@ where // flush write buffer if !framed.is_write_buf_empty() { match framed.poll_complete()? { - Async::Ready(_) => if body_ready { - continue; - } else { - return Ok(Async::NotReady); - }, + Async::Ready(_) => { + if body_ready { + continue; + } else { + return Ok(Async::NotReady); + } + } Async::NotReady => return Ok(Async::NotReady), } } diff --git a/src/test.rs b/src/test.rs index 3d12e344..308d2b4d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -17,15 +17,15 @@ use net2::TcpBuilder; use tokio::runtime::current_thread::Runtime; use tokio_io::{AsyncRead, AsyncWrite}; -use body::MessageBody; -use client::{ +use crate::body::MessageBody; +use crate::client::{ ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, ConnectorError, SendRequestError, }; -use header::{Header, IntoHeaderValue}; -use payload::Payload; -use request::Request; -use ws; +use crate::header::{Header, IntoHeaderValue}; +use crate::payload::Payload; +use crate::request::Request; +use crate::ws; /// Test `Request` builder /// @@ -338,7 +338,7 @@ impl TestServer { } fn new_connector( -) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(feature = "ssl")] { diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs index 575b4e4d..09d02563 100644 --- a/src/ws/client/connect.rs +++ b/src/ws/client/connect.rs @@ -5,10 +5,9 @@ use cookie::Cookie; use http::header::{HeaderName, HeaderValue}; use http::{Error as HttpError, HttpTryFrom}; -use client::{ClientRequest, ClientRequestBuilder}; -use header::IntoHeaderValue; - use super::ClientError; +use crate::client::{ClientRequest, ClientRequestBuilder}; +use crate::header::IntoHeaderValue; /// `WebSocket` connection pub struct Connect { diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 589648ee..62a3f47a 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -2,12 +2,11 @@ use std::io; use actix_net::connector::ConnectorError; -use http::header::HeaderValue; -use http::StatusCode; +use failure::Fail; +use http::{header::HeaderValue, Error as HttpError, StatusCode}; -use error::ParseError; -use http::Error as HttpError; -use ws::ProtocolError; +use crate::error::ParseError; +use crate::ws::ProtocolError; /// Websocket client error #[derive(Fail, Debug)] diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 68f8032e..8dd407b0 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -6,17 +6,18 @@ use actix_net::connector::{Connect as TcpConnect, ConnectorError, DefaultConnect use actix_net::service::Service; use base64; use futures::future::{err, Either, FutureResult}; -use futures::{Async, Future, Poll, Sink, Stream}; +use futures::{try_ready, Async, Future, Poll, Sink, Stream}; use http::header::{self, HeaderValue}; use http::{HttpTryFrom, StatusCode}; +use log::trace; use rand; use sha1::Sha1; use tokio_io::{AsyncRead, AsyncWrite}; -use body::BodyLength; -use client::ClientResponse; -use h1; -use ws::Codec; +use crate::body::BodyLength; +use crate::client::ClientResponse; +use crate::h1; +use crate::ws::Codec; use super::{ClientError, Connect, Protocol}; diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 56d28296..32ad4ef4 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -1,10 +1,11 @@ use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; use bytes::{BufMut, Bytes, BytesMut}; +use log::debug; use rand; -use ws::mask::apply_mask; -use ws::proto::{CloseCode, CloseReason, OpCode}; -use ws::ProtocolError; +use crate::ws::mask::apply_mask; +use crate::ws::proto::{CloseCode, CloseReason, OpCode}; +use crate::ws::ProtocolError; /// A struct representing a `WebSocket` frame. #[derive(Debug)] diff --git a/src/ws/mod.rs b/src/ws/mod.rs index f1c91714..ccd9ef4a 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -5,10 +5,12 @@ //! communicate with the peer. use std::io; -use error::ResponseError; +use failure::Fail; use http::{header, Method, StatusCode}; -use request::Request; -use response::{Response, ResponseBuilder}; + +use crate::error::ResponseError; +use crate::request::Request; +use crate::response::{Response, ResponseBuilder}; mod client; mod codec; @@ -221,7 +223,8 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).finish(); + ) + .finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, verify_handshake(&req).err().unwrap() @@ -231,10 +234,12 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).header( + ) + .header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ).finish(); + ) + .finish(); assert_eq!( HandshakeError::NoVersionHeader, verify_handshake(&req).err().unwrap() @@ -244,13 +249,16 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).header( + ) + .header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ).header( + ) + .header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), - ).finish(); + ) + .finish(); assert_eq!( HandshakeError::UnsupportedVersion, verify_handshake(&req).err().unwrap() @@ -260,13 +268,16 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).header( + ) + .header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ).header( + ) + .header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ).finish(); + ) + .finish(); assert_eq!( HandshakeError::BadWebsocketKey, verify_handshake(&req).err().unwrap() @@ -276,16 +287,20 @@ mod tests { .header( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ).header( + ) + .header( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ).header( + ) + .header( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ).header( + ) + .header( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ).finish(); + ) + .finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, handshake_response(&req).finish().status() diff --git a/src/ws/service.rs b/src/ws/service.rs index 118a2244..84627814 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -5,8 +5,8 @@ use actix_net::service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, IntoFuture, Poll}; -use h1::Codec; -use request::Request; +use crate::h1::Codec; +use crate::request::Request; use super::{verify_handshake, HandshakeError}; diff --git a/tests/test_client.rs b/tests/test_client.rs index 4a4ccb7d..3e14cd7b 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -91,7 +91,8 @@ fn test_with_query_parameter() { } else { ok::<_, ()>(Response::BadRequest().finish()) } - }).map(|_| ()) + }) + .map(|_| ()) }); let mut connector = srv.new_connector(); diff --git a/tests/test_server.rs b/tests/test_server.rs index 300b38a8..d169e5a9 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -209,7 +209,8 @@ fn test_content_length() { StatusCode::NOT_FOUND, ]; future::ok::<_, ()>(Response::new(statuses[indx])) - }).map(|_| ()) + }) + .map(|_| ()) }); let header = HeaderName::from_static("content-length"); @@ -348,7 +349,8 @@ fn test_head_binary() { let mut srv = test::TestServer::with_factory(|| { h1::H1Service::new(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); @@ -396,7 +398,8 @@ fn test_body_length() { Response::Ok() .body(Body::from_message(body::SizedStream::new(STR.len(), body))), ) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -414,7 +417,8 @@ fn test_body_chunked_explicit() { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -434,7 +438,8 @@ fn test_body_chunked_implicit() { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -456,7 +461,8 @@ fn test_response_http_error_handling() { .header(http::header::CONTENT_TYPE, broken_header) .body(STR), ) - }).map(|_| ()) + }) + .map(|_| ()) }); let req = srv.get().finish().unwrap(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 22ce3ca2..f890c586 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -62,7 +62,8 @@ fn test_simple() { SendResponse::send( framed, ws::handshake_response(&req).finish(), - ).map_err(|_| ()) + ) + .map_err(|_| ()) .and_then(|framed| { // start websocket service let framed = framed.into_framed(ws::Codec::new()); From 9f4d48f7a1b28f23537b043f17d48bdcb1a2f10b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 6 Dec 2018 15:03:01 -0800 Subject: [PATCH 0855/1635] update tests --- src/h1/codec.rs | 8 ++++---- src/h1/decoder.rs | 6 +++--- src/httpcodes.rs | 4 ++-- src/httpmessage.rs | 3 ++- src/json.rs | 3 ++- src/response.rs | 6 +++--- src/ws/mod.rs | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 54c1ce2e..d67d3608 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -197,10 +197,10 @@ mod tests { use tokio_io::{AsyncRead, AsyncWrite}; use super::*; - use error::ParseError; - use h1::Message; - use httpmessage::HttpMessage; - use request::Request; + use crate::error::ParseError; + use crate::h1::Message; + use crate::httpmessage::HttpMessage; + use crate::request::Request; #[test] fn test_http_request_chunked_payload_and_next_message() { diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 26b28440..e5971f75 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -612,9 +612,9 @@ mod tests { use tokio_io::{AsyncRead, AsyncWrite}; use super::*; - use error::ParseError; - use httpmessage::HttpMessage; - use message::Head; + use crate::error::ParseError; + use crate::httpmessage::HttpMessage; + use crate::message::Head; impl PayloadType { fn unwrap(self) -> PayloadDecoder { diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 80722734..7806bd80 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -73,9 +73,9 @@ impl Response { #[cfg(test)] mod tests { - use body::Body; + use crate::body::Body; + use crate::response::Response; use http::StatusCode; - use response::Response; #[test] fn test_build() { diff --git a/src/httpmessage.rs b/src/httpmessage.rs index e239a733..373b7ed4 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -568,11 +568,12 @@ where #[cfg(test)] mod tests { use super::*; + use crate::test::TestRequest; use encoding::all::ISO_8859_2; use encoding::Encoding; use futures::Async; use mime; - use test::TestRequest; + use serde_derive::Deserialize; #[test] fn test_content_type() { diff --git a/src/json.rs b/src/json.rs index 0b6ac377..bfecf0cc 100644 --- a/src/json.rs +++ b/src/json.rs @@ -137,8 +137,9 @@ mod tests { use bytes::Bytes; use futures::Async; use http::header; + use serde_derive::{Deserialize, Serialize}; - use test::TestRequest; + use crate::test::TestRequest; impl PartialEq for JsonPayloadError { fn eq(&self, other: &JsonPayloadError) -> bool { diff --git a/src/response.rs b/src/response.rs index e506cd16..5cff612f 100644 --- a/src/response.rs +++ b/src/response.rs @@ -855,9 +855,9 @@ impl ResponsePool { #[cfg(test)] mod tests { use super::*; - use body::Body; - use http; - use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::body::Body; + use crate::http; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; // use test::TestRequest; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index ccd9ef4a..02667c96 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -194,8 +194,8 @@ pub fn handshake_response(req: &Request) -> ResponseBuilder { #[cfg(test)] mod tests { use super::*; + use crate::test::TestRequest; use http::{header, Method}; - use test::TestRequest; #[test] fn test_handshake() { From 86af02156bbaee37ebd2c0f6be95cc53e785e910 Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Mon, 10 Dec 2018 17:02:05 +0100 Subject: [PATCH 0856/1635] add impl FromRequest for Either (#618) --- CHANGES.md | 8 +- src/extractor.rs | 195 ++++++++++++++++++++++++++++++++++++++++++++++- src/handler.rs | 2 +- 3 files changed, 202 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6092544e..11e639a8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,14 @@ # Changes +## [0.7.16] - xxxx-xx-xx + +### Added + +* Implement `FromRequest` extractor for `Either` + ## [0.7.15] - 2018-12-05 -## Changed +### Changed * `ClientConnector::resolver` now accepts `Into` instead of `Addr`. It enables user to implement own resolver. diff --git a/src/extractor.rs b/src/extractor.rs index 717e0f6c..6f55487b 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -12,10 +12,11 @@ use serde::de::{self, DeserializeOwned}; use serde_urlencoded; use de::PathDeserializer; -use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError}; +use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError, ErrorConflict}; use handler::{AsyncResult, FromRequest}; use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use httprequest::HttpRequest; +use Either; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's path. Information from the path is @@ -634,6 +635,151 @@ where } } +/// Extract either one of two fields from the request. +/// +/// If both or none of the fields can be extracted, the default behaviour is to prefer the first +/// successful, last that failed. The behaviour can be changed by setting the appropriate +/// ```EitherCollisionStrategy```. +/// +/// CAVEAT: Most of the time both extractors will be run. Make sure that the extractors you specify +/// can be run one after another (or in parallel). This will always fail for extractors that modify +/// the request state (such as the `Form` extractors that read in the body stream). +/// So Either, Form> will not work correctly - it will only succeed if it matches the first +/// option, but will always fail to match the second (since the body stream will be at the end, and +/// appear to be empty). +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// extern crate rand; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; +/// use actix_web::error::ErrorBadRequest; +/// use actix_web::Either; +/// +/// #[derive(Debug, Deserialize)] +/// struct Thing { name: String } +/// +/// #[derive(Debug, Deserialize)] +/// struct OtherThing { id: String } +/// +/// impl FromRequest for Thing { +/// type Config = (); +/// type Result = Result; +/// +/// #[inline] +/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { +/// if rand::random() { +/// Ok(Thing { name: "thingy".into() }) +/// } else { +/// Err(ErrorBadRequest("no luck")) +/// } +/// } +/// } +/// +/// impl FromRequest for OtherThing { +/// type Config = (); +/// type Result = Result; +/// +/// #[inline] +/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { +/// if rand::random() { +/// Ok(OtherThing { id: "otherthingy".into() }) +/// } else { +/// Err(ErrorBadRequest("no luck")) +/// } +/// } +/// } +/// +/// /// extract text data from request +/// fn index(supplied_thing: Either) -> Result { +/// match supplied_thing { +/// Either::A(thing) => Ok(format!("Got something: {:?}", thing)), +/// Either::B(other_thing) => Ok(format!("Got anotherthing: {:?}", other_thing)) +/// } +/// } +/// +/// fn main() { +/// let app = App::new().resource("/users/:first", |r| { +/// r.method(http::Method::POST).with(index) +/// }); +/// } +/// ``` +impl FromRequest for Either where A: FromRequest, B: FromRequest { + type Config = EitherConfig; + type Result = AsyncResult>; + + #[inline] + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + let a = A::from_request(&req.clone(), &cfg.a).into().map(|a| Either::A(a)); + let b = B::from_request(req, &cfg.b).into().map(|b| Either::B(b)); + + match &cfg.collision_strategy { + EitherCollisionStrategy::PreferA => AsyncResult::future(Box::new(a.or_else(|_| b))), + EitherCollisionStrategy::PreferB => AsyncResult::future(Box::new(b.or_else(|_| a))), + EitherCollisionStrategy::FastestSuccessful => AsyncResult::future(Box::new(a.select2(b).then( |r| match r { + Ok(future::Either::A((ares, _b))) => AsyncResult::ok(ares), + Ok(future::Either::B((bres, _a))) => AsyncResult::ok(bres), + Err(future::Either::A((_aerr, b))) => AsyncResult::future(Box::new(b)), + Err(future::Either::B((_berr, a))) => AsyncResult::future(Box::new(a)) + }))), + EitherCollisionStrategy::ErrorA => AsyncResult::future(Box::new(b.then(|r| match r { + Err(_berr) => AsyncResult::future(Box::new(a)), + Ok(b) => AsyncResult::future(Box::new(a.then( |r| match r { + Ok(_a) => Err(ErrorConflict("Both wings of either extractor completed")), + Err(_arr) => Ok(b) + }))) + }))), + EitherCollisionStrategy::ErrorB => AsyncResult::future(Box::new(a.then(|r| match r { + Err(_aerr) => AsyncResult::future(Box::new(b)), + Ok(a) => AsyncResult::future(Box::new(b.then( |r| match r { + Ok(_b) => Err(ErrorConflict("Both wings of either extractor completed")), + Err(_berr) => Ok(a) + }))) + }))), + } + } +} + +/// Defines the result if neither or both of the extractors supplied to an Either extractor succeed. +#[derive(Debug)] +pub enum EitherCollisionStrategy { + /// If both are successful, return A, if both fail, return error of B + PreferA, + /// If both are successful, return B, if both fail, return error of A + PreferB, + /// Return result of the faster, error of the slower if both fail + FastestSuccessful, + + /// Return error if both succeed, return error of A if both fail + ErrorA, + /// Return error if both succeed, return error of B if both fail + ErrorB +} + +impl Default for EitherCollisionStrategy { + fn default() -> Self { + EitherCollisionStrategy::FastestSuccessful + } +} + +pub struct EitherConfig where A: FromRequest, B: FromRequest { + a: A::Config, + b: B::Config, + collision_strategy: EitherCollisionStrategy +} + +impl Default for EitherConfig where A: FromRequest, B: FromRequest { + fn default() -> Self { + EitherConfig { + a: A::Config::default(), + b: B::Config::default(), + collision_strategy: EitherCollisionStrategy::default() + } + } +} + /// Optionally extract a field from the request or extract the Error if unsuccessful /// /// If the FromRequest for T fails, inject Err into handler rather than returning an error response @@ -874,6 +1020,11 @@ mod tests { hello: String, } + #[derive(Deserialize, Debug, PartialEq)] + struct OtherInfo { + bye: String, + } + #[test] fn test_bytes() { let cfg = PayloadConfig::default(); @@ -977,6 +1128,48 @@ mod tests { } } + #[test] + fn test_either() { + let req = TestRequest::default().finish(); + let mut cfg: EitherConfig, Query, _> = EitherConfig::default(); + + assert!(Either::, Query>::from_request(&req, &cfg).poll().is_err()); + + let req = TestRequest::default().uri("/index?hello=world").finish(); + + match Either::, Query>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))), + _ => unreachable!(), + } + + let req = TestRequest::default().uri("/index?bye=world").finish(); + match Either::, Query>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))), + _ => unreachable!(), + } + + let req = TestRequest::default().uri("/index?hello=world&bye=world").finish(); + cfg.collision_strategy = EitherCollisionStrategy::PreferA; + + match Either::, Query>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))), + _ => unreachable!(), + } + + cfg.collision_strategy = EitherCollisionStrategy::PreferB; + + match Either::, Query>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))), + _ => unreachable!(), + } + + cfg.collision_strategy = EitherCollisionStrategy::ErrorA; + assert!(Either::, Query>::from_request(&req, &cfg).poll().is_err()); + + cfg.collision_strategy = EitherCollisionStrategy::FastestSuccessful; + assert!(Either::, Query>::from_request(&req, &cfg).poll().is_ok()); + } + #[test] fn test_result() { let req = TestRequest::with_header( diff --git a/src/handler.rs b/src/handler.rs index 6ed93f92..c6880818 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -86,7 +86,7 @@ pub trait FromRequest: Sized { /// # fn is_a_variant() -> bool { true } /// # fn main() {} /// ``` -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Either { /// First branch of the type A(A), From aaae368ed9e59d41ec03a7025bb95fc419e89566 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 10 Dec 2018 18:08:33 -0800 Subject: [PATCH 0857/1635] use new actix crates --- Cargo.toml | 56 ++++++++++++--------------- examples/echo.rs | 17 ++------- examples/echo2.rs | 16 ++------ examples/framed_hello.rs | 21 +++------- examples/hello-world.rs | 16 ++------ src/client/connect.rs | 3 +- src/client/connection.rs | 2 +- src/client/connector.rs | 13 +++---- src/client/error.rs | 32 ++++++++++++---- src/client/pipeline.rs | 5 +-- src/client/pool.rs | 6 +-- src/client/request.rs | 36 +++++++++--------- src/config.rs | 6 +-- src/error.rs | 6 +-- src/h1/client.rs | 2 +- src/h1/codec.rs | 4 +- src/h1/decoder.rs | 4 +- src/h1/dispatcher.rs | 5 +-- src/h1/mod.rs | 2 +- src/h1/service.rs | 5 +-- src/payload.rs | 2 +- src/service.rs | 5 +-- src/test.rs | 16 ++++---- src/ws/client/error.rs | 2 +- src/ws/client/service.rs | 7 ++-- src/ws/codec.rs | 2 +- src/ws/service.rs | 4 +- src/ws/transport.rs | 7 ++-- tests/test_client.rs | 9 +---- tests/test_server.rs | 8 +--- tests/test_ws.rs | 82 +++++++++++++++++++--------------------- 31 files changed, 174 insertions(+), 227 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57ed3f9b..5afeda94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,61 +28,55 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["session", "cell"] +default = ["session"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] -cell = ["actix-net/cell"] - -# tls -tls = ["native-tls", "actix-net/tls"] - # openssl -ssl = ["openssl", "actix-net/ssl"] - -# rustls -rust-tls = ["rustls", "actix-net/rust-tls"] +ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix = "0.7.5" -#actix-net = "0.3.0" -actix-net = { git="https://github.com/actix/actix-net.git" } +actix-service = "0.1.1" +actix-codec = { git="https://github.com/actix/actix-net.git" } +actix-connector = { git="https://github.com/actix/actix-net.git" } +actix-rt = { git="https://github.com/actix/actix-net.git" } +actix-server = { git="https://github.com/actix/actix-net.git" } +actix-utils = { git="https://github.com/actix/actix-net.git" } + +# actix-codec = { path="../actix-net/actix-codec/" } +# actix-connector = { path="../actix-net/actix-connector/" } +# actix-rt = { path="../actix-net/actix-rt/" } +# actix-server = { path="../actix-net/actix-server/" } +# actix-utils = { path="../actix-net/actix-utils/" } base64 = "0.9" bitflags = "1.0" +bytes = "0.4" +byteorder = "1.2" +cookie = { version="0.11", features=["percent-encode"] } +encoding = "0.2" +failure = "0.1.3" +futures = "0.1" http = "0.1.8" httparse = "1.3" -failure = "0.1.3" indexmap = "1.0" log = "0.4" mime = "0.3" +net2 = "0.2" +percent-encoding = "1.0" rand = "0.5" serde = "1.0" serde_json = "1.0" sha1 = "0.6" -time = "0.1" -encoding = "0.2" -serde_urlencoded = "0.5.3" - -cookie = { version="0.11", features=["percent-encode"] } -percent-encoding = "1.0" -url = { version="1.7", features=["query_encoding"] } - -# io -net2 = "0.2" slab = "0.4" -bytes = "0.4" -byteorder = "1.2" -futures = "0.1" -tokio-codec = "0.1" -tokio = "0.1" -tokio-io = "0.1" +serde_urlencoded = "0.5.3" +time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" -tokio-current-thread = "0.1" trust-dns-proto = "0.5.0" trust-dns-resolver = "0.10.0" +url = { version="1.7", features=["query_encoding"] } # native-tls native-tls = { version="0.2", optional = true } diff --git a/examples/echo.rs b/examples/echo.rs index 0453ad6a..3bfb04d7 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,27 +1,18 @@ -#[macro_use] -extern crate log; -extern crate env_logger; - -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; -extern crate http; - use actix_http::HttpMessage; use actix_http::{h1, Request, Response}; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; +use actix_server::Server; +use actix_service::NewService; use bytes::Bytes; use futures::Future; use http::header::HeaderValue; +use log::info; use std::env; fn main() { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); - Server::new() + Server::build() .bind("echo", "127.0.0.1:8080", || { h1::H1Service::build() .client_timeout(1000) diff --git a/examples/echo2.rs b/examples/echo2.rs index 3206ff50..0e2bc9d5 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -1,19 +1,11 @@ -#[macro_use] -extern crate log; -extern crate env_logger; - -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; - use actix_http::http::HeaderValue; use actix_http::HttpMessage; use actix_http::{h1, Error, Request, Response}; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; +use actix_server::Server; +use actix_service::NewService; use bytes::Bytes; use futures::Future; +use log::info; use std::env; fn handle_request(_req: Request) -> impl Future { @@ -29,7 +21,7 @@ fn main() { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); - Server::new() + Server::build() .bind("echo", "127.0.0.1:8080", || { h1::H1Service::build() .client_timeout(1000) diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 6c53d27f..5bbc3be9 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -1,18 +1,9 @@ -extern crate env_logger; -extern crate log; - -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; -extern crate http; - +use actix_codec::Framed; use actix_http::{h1, Response, SendResponse, ServiceConfig}; -use actix_net::codec::Framed; -use actix_net::framed::IntoFramed; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; -use actix_net::stream::TakeItem; +use actix_server::Server; +use actix_service::NewService; +use actix_utils::framed::IntoFramed; +use actix_utils::stream::TakeItem; use futures::Future; use std::env; @@ -20,7 +11,7 @@ fn main() { env::set_var("RUST_LOG", "framed_hello=info"); env_logger::init(); - Server::new() + Server::build() .bind("framed_hello", "127.0.0.1:8080", || { IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) .and_then(TakeItem::new().map_err(|_| ())) diff --git a/examples/hello-world.rs b/examples/hello-world.rs index b477f191..e0c322a2 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,24 +1,16 @@ -#[macro_use] -extern crate log; -extern crate env_logger; - -extern crate actix_http; -extern crate actix_net; -extern crate futures; -extern crate http; - use actix_http::{h1, Response}; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; +use actix_server::Server; +use actix_service::NewService; use futures::future; use http::header::HeaderValue; +use log::info; use std::env; fn main() { env::set_var("RUST_LOG", "hello_world=info"); env_logger::init(); - Server::new() + Server::build() .bind("hello-world", "127.0.0.1:8080", || { h1::H1Service::build() .client_timeout(1000) diff --git a/src/client/connect.rs b/src/client/connect.rs index a445228e..f4112cfa 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -1,5 +1,4 @@ -use actix_net::connector::RequestPort; -use actix_net::resolver::RequestHost; +use actix_connector::{RequestHost, RequestPort}; use http::uri::Uri; use http::{Error as HttpError, HttpTryFrom}; diff --git a/src/client/connection.rs b/src/client/connection.rs index 363a4ece..ed156bf8 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -1,7 +1,7 @@ use std::{fmt, io, time}; +use actix_codec::{AsyncRead, AsyncWrite}; use futures::Poll; -use tokio_io::{AsyncRead, AsyncWrite}; use super::pool::Acquired; diff --git a/src/client/connector.rs b/src/client/connector.rs index 74ee6a7e..fdc996ff 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,13 +1,12 @@ use std::time::Duration; use std::{fmt, io}; -use actix_net::connector::TcpConnector; -use actix_net::resolver::Resolver; -use actix_net::service::{Service, ServiceExt}; -use actix_net::timeout::{TimeoutError, TimeoutService}; +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_connector::{Resolver, TcpConnector}; +use actix_service::Service; +use actix_utils::timeout::{TimeoutError, TimeoutService}; use futures::future::Either; use futures::Poll; -use tokio_io::{AsyncRead, AsyncWrite}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use super::connect::Connect; @@ -16,7 +15,7 @@ use super::error::ConnectorError; use super::pool::ConnectionPool; #[cfg(feature = "ssl")] -use actix_net::ssl::OpensslConnector; +use actix_connector::ssl::OpensslConnector; #[cfg(feature = "ssl")] use openssl::ssl::{SslConnector, SslMethod}; @@ -169,7 +168,7 @@ impl Connector { .and_then(TcpConnector::default().from_err()) .and_then( OpensslConnector::service(self.connector) - .map_err(ConnectorError::SslError), + .map_err(ConnectorError::from), ), ) .map_err(|e| match e { diff --git a/src/client/error.rs b/src/client/error.rs index d2a0f38e..815bc1ed 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -4,13 +4,7 @@ use failure::Fail; use trust_dns_resolver::error::ResolveError; #[cfg(feature = "ssl")] -use openssl::ssl::Error as SslError; - -#[cfg(all(feature = "tls", not(any(feature = "ssl", feature = "rust-tls"))))] -use native_tls::Error as SslError; - -#[cfg(all(feature = "rust-tls", not(any(feature = "tls", feature = "ssl"))))] -use std::io::Error as SslError; +use openssl::ssl::{Error as SslError, HandshakeError}; use crate::error::{Error, ParseError}; @@ -26,7 +20,7 @@ pub enum ConnectorError { SslIsNotSupported, /// SSL error - #[cfg(any(feature = "tls", feature = "ssl", feature = "rust-tls"))] + #[cfg(feature = "ssl")] #[fail(display = "{}", _0)] SslError(#[cause] SslError), @@ -73,6 +67,28 @@ impl From for ConnectorError { } } +#[cfg(feature = "ssl")] +impl From for ConnectorError { + fn from(err: SslError) -> ConnectorError { + ConnectorError::SslError(err) + } +} + +#[cfg(feature = "ssl")] +impl From> for ConnectorError { + fn from(err: HandshakeError) -> ConnectorError { + match err { + HandshakeError::SetupFailure(stack) => SslError::from(stack).into(), + HandshakeError::Failure(stream) => { + SslError::from(stream.into_error()).into() + } + HandshakeError::WouldBlock(stream) => { + SslError::from(stream.into_error()).into() + } + } + } +} + /// A set of errors that can occur during request sending and response reading #[derive(Debug)] pub enum SendRequestError { diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index fc1e53e8..8d946d64 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -1,11 +1,10 @@ use std::collections::VecDeque; -use actix_net::codec::Framed; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::Service; use bytes::Bytes; use futures::future::{err, ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; -use tokio_io::{AsyncRead, AsyncWrite}; use super::error::{ConnectorError, SendRequestError}; use super::response::ClientResponse; diff --git a/src/client/pool.rs b/src/client/pool.rs index decf8019..94e96899 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -4,7 +4,9 @@ use std::io; use std::rc::Rc; use std::time::{Duration, Instant}; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_rt::spawn; +use actix_service::Service; use futures::future::{ok, Either, FutureResult}; use futures::sync::oneshot; use futures::task::AtomicTask; @@ -12,8 +14,6 @@ use futures::{Async, Future, Poll}; use http::uri::Authority; use indexmap::IndexSet; use slab::Slab; -use tokio_current_thread::spawn; -use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::{sleep, Delay}; use super::connect::Connect; diff --git a/src/client/request.rs b/src/client/request.rs index 5f294bbd..fbb1e840 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -2,7 +2,7 @@ use std::fmt; use std::fmt::Write as FmtWrite; use std::io::Write; -use actix_net::service::Service; +use actix_service::Service; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; @@ -23,26 +23,24 @@ use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; /// An HTTP Client Request /// /// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # extern crate tokio; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::{actix, client}; +/// use futures::future::{Future, lazy}; +/// use actix_rt::System; +/// use actix_http::client; /// /// fn main() { -/// actix::run( -/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); +/// System::new("test").block_on(lazy(|| { +/// let mut connector = client::Connector::default().service(); +/// client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .finish().unwrap() +/// .send(&mut connector) // <- Send http request +/// .map_err(|_| ()) +/// .and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// # actix_rt::System::current().stop(); +/// Ok(()) +/// }) +/// })); /// } /// ``` pub struct ClientRequest { diff --git a/src/config.rs b/src/config.rs index 661c0901..67c928fb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,11 +4,11 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt, net}; +use actix_rt::spawn; use bytes::BytesMut; use futures::{future, Future}; use log::error; use time; -use tokio_current_thread::spawn; use tokio_timer::{sleep, Delay}; // "Sun, 06 Nov 1994 08:49:37 GMT".len() @@ -378,8 +378,8 @@ impl DateService { #[cfg(test)] mod tests { use super::*; + use actix_rt::System; use futures::future; - use tokio::runtime::current_thread; #[test] fn test_date_len() { @@ -388,7 +388,7 @@ mod tests { #[test] fn test_date() { - let mut rt = current_thread::Runtime::new().unwrap(); + let mut rt = System::new("test"); let _ = rt.block_on(future::lazy(|| { let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); diff --git a/src/error.rs b/src/error.rs index 280f9a32..49602bd9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use std::string::FromUtf8Error; use std::sync::Mutex; use std::{fmt, io, result}; -use actix::MailboxError; +// use actix::MailboxError; use cookie; use failure::{self, Backtrace, Fail}; use futures::Canceled; @@ -250,8 +250,8 @@ impl ResponseError for header::InvalidHeaderValueBytes { /// `InternalServerError` for `futures::Canceled` impl ResponseError for Canceled {} -/// `InternalServerError` for `actix::MailboxError` -impl ResponseError for MailboxError {} +// /// `InternalServerError` for `actix::MailboxError` +// impl ResponseError for MailboxError {} /// A set of errors that can occur during parsing HTTP streams #[derive(Fail, Debug)] diff --git a/src/h1/client.rs b/src/h1/client.rs index f547983f..de4d10e1 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -1,13 +1,13 @@ #![allow(unused_imports, unused_variables, dead_code)] use std::io::{self, Write}; +use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{BufMut, Bytes, BytesMut}; use http::header::{ HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, UPGRADE, }; use http::{Method, Version}; -use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; diff --git a/src/h1/codec.rs b/src/h1/codec.rs index d67d3608..fbc8b4a5 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -2,11 +2,11 @@ use std::fmt; use std::io::{self, Write}; +use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{BufMut, Bytes, BytesMut}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::{Method, StatusCode, Version}; -use tokio_codec::{Decoder, Encoder}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; @@ -192,9 +192,9 @@ impl Encoder for Codec { mod tests { use std::{cmp, io}; + use actix_codec::{AsyncRead, AsyncWrite}; use bytes::{Buf, Bytes, BytesMut}; use http::{Method, Version}; - use tokio_io::{AsyncRead, AsyncWrite}; use super::*; use crate::error::ParseError; diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index e5971f75..460d2b3a 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -1,13 +1,13 @@ use std::marker::PhantomData; use std::{io, mem}; +use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll}; use http::header::{HeaderName, HeaderValue}; use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; use httparse; use log::{debug, error, trace}; -use tokio_codec::Decoder; use crate::client::ClientResponse; use crate::error::ParseError; @@ -607,9 +607,9 @@ impl ChunkedState { mod tests { use std::{cmp, io}; + use actix_codec::{AsyncRead, AsyncWrite}; use bytes::{Buf, Bytes, BytesMut}; use http::{Method, Version}; - use tokio_io::{AsyncRead, AsyncWrite}; use super::*; use crate::error::ParseError; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 59b419c3..569b1dee 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -3,12 +3,11 @@ use std::fmt::Debug; use std::mem; use std::time::Instant; -use actix_net::codec::Framed; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::Service; use bitflags::bitflags; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; use log::{debug, error, trace}; -use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 461ecb95..a375b60d 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,7 +1,7 @@ //! HTTP/1 implementation use std::fmt; -use actix_net::codec::Framed; +use actix_codec::Framed; use bytes::Bytes; mod client; diff --git a/src/h1/service.rs b/src/h1/service.rs index 1a5a587c..7c2589cc 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -2,12 +2,11 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::net; -use actix_net::codec::Framed; -use actix_net::service::{IntoNewService, NewService, Service}; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::{IntoNewService, NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, Poll, Stream}; use log::error; -use tokio_io::{AsyncRead, AsyncWrite}; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; diff --git a/src/payload.rs b/src/payload.rs index 37f06d43..ea266f70 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -523,8 +523,8 @@ where #[cfg(test)] mod tests { use super::*; + use actix_rt::Runtime; use futures::future::{lazy, result}; - use tokio::runtime::current_thread::Runtime; #[test] fn test_error() { diff --git a/src/service.rs b/src/service.rs index a6a82097..f98234e7 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,10 +1,9 @@ use std::marker::PhantomData; -use actix_net::codec::Framed; -use actix_net::service::{NewService, Service}; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Sink}; -use tokio_io::{AsyncRead, AsyncWrite}; use crate::body::{BodyLength, MessageBody, ResponseBody}; use crate::error::{Error, ResponseError}; diff --git a/src/test.rs b/src/test.rs index 308d2b4d..c264ac47 100644 --- a/src/test.rs +++ b/src/test.rs @@ -3,10 +3,10 @@ use std::str::FromStr; use std::sync::mpsc; use std::{net, thread}; -use actix::System; -use actix_net::codec::Framed; -use actix_net::server::{Server, StreamServiceFactory}; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_rt::{Runtime, System}; +use actix_server::{Server, StreamServiceFactory}; +use actix_service::Service; use bytes::Bytes; use cookie::Cookie; @@ -14,8 +14,6 @@ use futures::future::{lazy, Future}; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use net2::TcpBuilder; -use tokio::runtime::current_thread::Runtime; -use tokio_io::{AsyncRead, AsyncWrite}; use crate::body::MessageBody; use crate::client::{ @@ -316,7 +314,7 @@ impl TestServer { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - Server::default() + Server::build() .listen("test", tcp, factory) .workers(1) .disable_signals() @@ -390,9 +388,9 @@ impl TestServerRuntime { /// Construct test server url pub fn url(&self, uri: &str) -> String { if uri.starts_with('/') { - format!("http://localhost:{}{}", self.addr.port(), uri) + format!("http://127.0.0.1:{}{}", self.addr.port(), uri) } else { - format!("http://localhost:{}/{}", self.addr.port(), uri) + format!("http://127.0.0.1:{}/{}", self.addr.port(), uri) } } diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 62a3f47a..02c72375 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -1,7 +1,7 @@ //! Http client request use std::io; -use actix_net::connector::ConnectorError; +use actix_connector::ConnectorError; use failure::Fail; use http::{header::HeaderValue, Error as HttpError, StatusCode}; diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 8dd407b0..c48b6e0c 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -1,9 +1,9 @@ //! websockets client use std::marker::PhantomData; -use actix_net::codec::Framed; -use actix_net::connector::{Connect as TcpConnect, ConnectorError, DefaultConnector}; -use actix_net::service::Service; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_connector::{Connect as TcpConnect, ConnectorError, DefaultConnector}; +use actix_service::Service; use base64; use futures::future::{err, Either, FutureResult}; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; @@ -12,7 +12,6 @@ use http::{HttpTryFrom, StatusCode}; use log::trace; use rand; use sha1::Sha1; -use tokio_io::{AsyncRead, AsyncWrite}; use crate::body::BodyLength; use crate::client::ClientResponse; diff --git a/src/ws/codec.rs b/src/ws/codec.rs index 10c50528..286d15f8 100644 --- a/src/ws/codec.rs +++ b/src/ws/codec.rs @@ -1,5 +1,5 @@ +use actix_codec::{Decoder, Encoder}; use bytes::{Bytes, BytesMut}; -use tokio_codec::{Decoder, Encoder}; use super::frame::Parser; use super::proto::{CloseReason, OpCode}; diff --git a/src/ws/service.rs b/src/ws/service.rs index 84627814..8189b195 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; -use actix_net::codec::Framed; -use actix_net::service::{NewService, Service}; +use actix_codec::Framed; +use actix_service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, IntoFuture, Poll}; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index 8cd79cb0..f59ad67a 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -1,8 +1,7 @@ -use actix_net::codec::Framed; -use actix_net::framed::{FramedTransport, FramedTransportError}; -use actix_net::service::{IntoService, Service}; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::{IntoService, Service}; +use actix_utils::framed::{FramedTransport, FramedTransportError}; use futures::{Future, Poll}; -use tokio_io::{AsyncRead, AsyncWrite}; use super::{Codec, Frame, Message}; diff --git a/tests/test_client.rs b/tests/test_client.rs index 3e14cd7b..f19edda1 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -1,10 +1,4 @@ -extern crate actix; -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; - -use actix_net::service::NewServiceExt; +use actix_service::NewService; use bytes::Bytes; use futures::future::{self, ok}; @@ -35,6 +29,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_v2() { + env_logger::init(); let mut srv = TestServer::with_factory(move || { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) diff --git a/tests/test_server.rs b/tests/test_server.rs index d169e5a9..cb9cd3f9 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,14 +1,8 @@ -extern crate actix; -extern crate actix_http; -extern crate actix_net; -extern crate bytes; -extern crate futures; - use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; -use actix_net::service::NewServiceExt; +use actix_service::NewService; use bytes::Bytes; use futures::future::{self, ok}; use futures::stream::once; diff --git a/tests/test_ws.rs b/tests/test_ws.rs index f890c586..11a3f472 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -1,16 +1,9 @@ -extern crate actix; -extern crate actix_http; -extern crate actix_net; -extern crate actix_web; -extern crate bytes; -extern crate futures; - use std::io; -use actix_net::codec::Framed; -use actix_net::framed::IntoFramed; -use actix_net::service::NewServiceExt; -use actix_net::stream::TakeItem; +use actix_codec::Framed; +use actix_service::NewService; +use actix_utils::framed::IntoFramed; +use actix_utils::stream::TakeItem; use actix_web::ws as web_ws; use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Either}; @@ -79,38 +72,6 @@ fn test_simple() { }) }); - { - let url = srv.url("/"); - - let (reader, mut writer) = srv - .block_on(lazy(|| web_ws::Client::new(url).connect())) - .unwrap(); - - writer.text("text"); - let (item, reader) = srv.block_on(reader.into_future()).unwrap(); - assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.block_on(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.block_on(reader.into_future()).unwrap(); - assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(web_ws::CloseCode::Normal.into())); - let (item, _) = srv.block_on(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(web_ws::Message::Close(Some( - web_ws::CloseCode::Normal.into() - ))) - ); - } - // client service let framed = srv.ws().unwrap(); let framed = srv @@ -142,5 +103,38 @@ fn test_simple() { assert_eq!( item, Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) - ) + ); + + { + let mut sys = actix_web::actix::System::new("test"); + let url = srv.url("/"); + + let (reader, mut writer) = sys + .block_on(lazy(|| web_ws::Client::new(url).connect())) + .unwrap(); + + writer.text("text"); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); + + writer.binary(b"text".as_ref()); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) + ); + + writer.ping("ping"); + let (item, reader) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); + + writer.close(Some(web_ws::CloseCode::Normal.into())); + let (item, _) = sys.block_on(reader.into_future()).unwrap(); + assert_eq!( + item, + Some(web_ws::Message::Close(Some( + web_ws::CloseCode::Normal.into() + ))) + ); + } } From 90eef31cc0ee0304bb1d0eaab7e44aaa6181e99e Mon Sep 17 00:00:00 2001 From: ethanpailes Date: Tue, 11 Dec 2018 11:37:52 -0500 Subject: [PATCH 0858/1635] impl ResponseError for SendError when possible (#619) --- CHANGES.md | 3 +++ src/error.rs | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 11e639a8..05f831ae 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,9 @@ * Implement `FromRequest` extractor for `Either` +* Implement `ResponseError` for `SendError` + + ## [0.7.15] - 2018-12-05 ### Changed diff --git a/src/error.rs b/src/error.rs index 1766c152..f4ea981c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use std::string::FromUtf8Error; use std::sync::Mutex; use std::{fmt, io, result}; -use actix::MailboxError; +use actix::{MailboxError, SendError}; use cookie; use failure::{self, Backtrace, Fail}; use futures::Canceled; @@ -136,6 +136,10 @@ pub trait ResponseError: Fail + InternalResponseErrorAsFail { } } +impl ResponseError for SendError +where T: Send + Sync + 'static { +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.cause, f) From 1c60992723eff00f05832b499ed9b0fbaa954c80 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 11 Dec 2018 09:29:12 -0800 Subject: [PATCH 0859/1635] use released crates --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5afeda94..0c6d53f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,11 +38,11 @@ ssl = ["openssl", "actix-connector/ssl"] [dependencies] actix-service = "0.1.1" -actix-codec = { git="https://github.com/actix/actix-net.git" } -actix-connector = { git="https://github.com/actix/actix-net.git" } -actix-rt = { git="https://github.com/actix/actix-net.git" } -actix-server = { git="https://github.com/actix/actix-net.git" } -actix-utils = { git="https://github.com/actix/actix-net.git" } +actix-codec = "0.1.0" +actix-connector = "0.1.0" +actix-rt = "0.1.0" +actix-server = "0.1.0" +actix-utils = "0.1.0" # actix-codec = { path="../actix-net/actix-codec/" } # actix-connector = { path="../actix-net/actix-connector/" } From 46db09428c2a38cab4daa8f272b36fb4112ff235 Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 11 Dec 2018 08:15:07 +0300 Subject: [PATCH 0860/1635] Prepare release 0.7.16 --- CHANGES.md | 2 +- Cargo.toml | 4 ++-- src/client/connector.rs | 4 ++-- src/client/mod.rs | 6 ++++-- src/client/pipeline.rs | 3 ++- src/client/request.rs | 3 ++- src/extractor.rs | 4 +++- src/httpmessage.rs | 3 ++- src/lib.rs | 16 +++++++--------- src/middleware/session.rs | 3 ++- src/server/http.rs | 3 ++- src/server/mod.rs | 3 ++- src/test.rs | 2 +- src/ws/mod.rs | 3 ++- 14 files changed, 34 insertions(+), 25 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 05f831ae..7965bc75 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.16] - xxxx-xx-xx +## [0.7.16] - 2018-12-11 ### Added diff --git a/Cargo.toml b/Cargo.toml index 7b8dcec3..80261110 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.15" +version = "0.7.16" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -61,7 +61,7 @@ flate2-rust = ["flate2/rust_backend"] cell = ["actix-net/cell"] [dependencies] -actix = "0.7.7" +actix = "0.7.9" actix-net = "0.2.2" askama_escape = "0.1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index f5affad3..1d062302 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -3,8 +3,8 @@ use std::net::Shutdown; use std::time::{Duration, Instant}; use std::{fmt, io, mem, time}; -use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; -use actix::{ +use actix_inner::actors::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; +use actix_inner::{ fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context, ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, SystemService, WrapFuture, diff --git a/src/client/mod.rs b/src/client/mod.rs index a0713fe3..7696efa9 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -2,11 +2,12 @@ //! //! ```rust //! # extern crate actix_web; +//! # extern crate actix; //! # extern crate futures; //! # extern crate tokio; //! # use futures::Future; //! # use std::process; -//! use actix_web::{actix, client}; +//! use actix_web::client; //! //! fn main() { //! actix::run( @@ -61,12 +62,13 @@ impl ResponseError for SendRequestError { /// /// ```rust /// # extern crate actix_web; +/// # extern crate actix; /// # extern crate futures; /// # extern crate tokio; /// # extern crate env_logger; /// # use futures::Future; /// # use std::process; -/// use actix_web::{actix, client}; +/// use actix_web::client; /// /// fn main() { /// actix::run( diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs index 394b7a6c..1dbd2e17 100644 --- a/src/client/pipeline.rs +++ b/src/client/pipeline.rs @@ -6,7 +6,8 @@ use std::time::{Duration, Instant}; use std::{io, mem}; use tokio_timer::Delay; -use actix::{Addr, Request, SystemService}; +use actix_inner::dev::Request; +use actix::{Addr, SystemService}; use super::{ ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect, diff --git a/src/client/request.rs b/src/client/request.rs index 71da8f74..ad08ad13 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -27,11 +27,12 @@ use httprequest::HttpRequest; /// /// ```rust /// # extern crate actix_web; +/// # extern crate actix; /// # extern crate futures; /// # extern crate tokio; /// # use futures::Future; /// # use std::process; -/// use actix_web::{actix, client}; +/// use actix_web::client; /// /// fn main() { /// actix::run( diff --git a/src/extractor.rs b/src/extractor.rs index 6f55487b..3c64de9e 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -751,7 +751,6 @@ pub enum EitherCollisionStrategy { PreferB, /// Return result of the faster, error of the slower if both fail FastestSuccessful, - /// Return error if both succeed, return error of A if both fail ErrorA, /// Return error if both succeed, return error of B if both fail @@ -764,6 +763,9 @@ impl Default for EitherCollisionStrategy { } } +///Determines Either extractor configuration +/// +///By default `EitherCollisionStrategy::FastestSuccessful` is used. pub struct EitherConfig where A: FromRequest, B: FromRequest { a: A::Config, b: B::Config, diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 60f77b07..e96dce48 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -213,9 +213,10 @@ pub trait HttpMessage: Sized { /// # extern crate actix_web; /// # extern crate env_logger; /// # extern crate futures; + /// # extern crate actix; /// # use std::str; /// # use actix_web::*; - /// # use actix_web::actix::fut::FinishStream; + /// # use actix::FinishStream; /// # use futures::{Future, Stream}; /// # use futures::future::{ok, result, Either}; /// fn index(mut req: HttpRequest) -> Box> { diff --git a/src/lib.rs b/src/lib.rs index f8326886..21515051 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,14 +217,12 @@ pub use server::Request; pub mod actix { //! Re-exports [actix's](https://docs.rs/actix/) prelude - - extern crate actix; - pub use self::actix::actors::resolver; - pub use self::actix::actors::signal; - pub use self::actix::fut; - pub use self::actix::msgs; - pub use self::actix::prelude::*; - pub use self::actix::{run, spawn}; + pub use super::actix_inner::actors::resolver; + pub use super::actix_inner::actors::signal; + pub use super::actix_inner::fut; + pub use super::actix_inner::msgs; + pub use super::actix_inner::prelude::*; + pub use super::actix_inner::{run, spawn}; } #[cfg(feature = "openssl")] @@ -255,7 +253,7 @@ pub mod dev { pub use body::BodyStream; pub use context::Drain; - pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig}; + pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig, EitherConfig, EitherCollisionStrategy}; pub use handler::{AsyncResult, Handler}; pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use httpresponse::HttpResponseBuilder; diff --git a/src/middleware/session.rs b/src/middleware/session.rs index e8b0e555..0271a13f 100644 --- a/src/middleware/session.rs +++ b/src/middleware/session.rs @@ -33,7 +33,8 @@ //! //! ```rust //! # extern crate actix_web; -//! use actix_web::{actix, server, App, HttpRequest, Result}; +//! # extern crate actix; +//! use actix_web::{server, App, HttpRequest, Result}; //! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; //! //! fn index(req: HttpRequest) -> Result<&'static str> { diff --git a/src/server/http.rs b/src/server/http.rs index 0bec8be3..76049981 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -457,7 +457,8 @@ impl H + Send + Clone> HttpServer { /// /// ```rust /// extern crate actix_web; - /// use actix_web::{actix, server, App, HttpResponse}; + /// extern crate actix; + /// use actix_web::{server, App, HttpResponse}; /// /// fn main() { /// let sys = actix::System::new("example"); // <- create Actix system diff --git a/src/server/mod.rs b/src/server/mod.rs index 0a16f26b..b8498604 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -166,7 +166,8 @@ const HW_BUFFER_SIZE: usize = 32_768; /// /// ```rust /// # extern crate actix_web; -/// use actix_web::{actix, server, App, HttpResponse}; +/// # extern crate actix; +/// use actix_web::{server, App, HttpResponse}; /// /// fn main() { /// let sys = actix::System::new("example"); // <- create Actix system diff --git a/src/test.rs b/src/test.rs index d0cfb255..d543937c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use std::sync::mpsc; use std::{net, thread}; -use actix_inner::{Actor, Addr, System}; +use actix::{Actor, Addr, System}; use cookie::Cookie; use futures::Future; diff --git a/src/ws/mod.rs b/src/ws/mod.rs index c16f8d6d..b0942c0d 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -8,7 +8,8 @@ //! //! ```rust //! # extern crate actix_web; -//! # use actix_web::actix::*; +//! # extern crate actix; +//! # use actix::prelude::*; //! # use actix_web::*; //! use actix_web::{ws, HttpRequest, HttpResponse}; //! From b1001b80b7cf150737e830bb11f144cf09ad61a6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 12 Dec 2018 18:39:01 -0800 Subject: [PATCH 0861/1635] upgrade actix-service dependency --- Cargo.toml | 2 +- src/client/connector.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c6d53f5..d992de64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.1.1" +actix-service = "0.1.3" actix-codec = "0.1.0" actix-connector = "0.1.0" actix-rt = "0.1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index fdc996ff..05caf1ed 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -3,7 +3,7 @@ use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connector::{Resolver, TcpConnector}; -use actix_service::Service; +use actix_service::{Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use futures::future::Either; use futures::Poll; From e8bdcb1c08967431c825751e5c110b37ecde512f Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 15 Dec 2018 09:26:56 +0300 Subject: [PATCH 0862/1635] Update min version of http Closes #630 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 80261110..cd13e341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ base64 = "0.10" bitflags = "1.0" failure = "^0.1.2" h2 = "0.1" -http = "^0.1.8" +http = "^0.1.14" httparse = "1.3" log = "0.4" mime = "0.3" From 1a940d4c18d7ab234800857817f087c9121272b1 Mon Sep 17 00:00:00 2001 From: Douman Date: Sun, 16 Dec 2018 13:46:11 +0300 Subject: [PATCH 0863/1635] H1 decoded should ignore header cases --- CHANGES.md | 6 ++++++ src/server/h1.rs | 33 +++++++++++++++++++++++++++++++++ src/server/h1decoder.rs | 18 +++++++----------- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7965bc75..45faf35b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.7.17] - 2018-xx-xx + +### Fixed + +* HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631 + ## [0.7.16] - 2018-12-11 ### Added diff --git a/src/server/h1.rs b/src/server/h1.rs index f491ba59..f0edefae 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -942,6 +942,14 @@ mod tests { let req = parse_ready!(&mut buf); assert!(!req.keep_alive()); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: Close\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); } #[test] @@ -953,10 +961,26 @@ mod tests { let req = parse_ready!(&mut buf); assert!(!req.keep_alive()); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: Close\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(!req.keep_alive()); } #[test] fn test_conn_keep_alive_1_0() { + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: Keep-Alive\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.keep_alive()); + let mut buf = BytesMut::from( "GET /test HTTP/1.0\r\n\ connection: keep-alive\r\n\r\n", @@ -1009,6 +1033,15 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + upgrade: Websockets\r\n\ + connection: Upgrade\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.upgrade()); } #[test] diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 10f7e68a..80b49983 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -157,23 +157,19 @@ impl H1Decoder { } // transfer-encoding header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str() { - chunked = s.to_lowercase().contains("chunked"); + if let Ok(s) = value.to_str().map(|s| s.trim()) { + chunked = s.eq_ignore_ascii_case("chunked"); } else { return Err(ParseError::Header); } } // connection keep-alive state header::CONNECTION => { - let ka = if let Ok(conn) = value.to_str() { - if version == Version::HTTP_10 - && conn.contains("keep-alive") - { + let ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { + if version == Version::HTTP_10 && conn.eq_ignore_ascii_case("keep-alive") { true } else { - version == Version::HTTP_11 && !(conn - .contains("close") - || conn.contains("upgrade")) + version == Version::HTTP_11 && !(conn.eq_ignore_ascii_case("close") || conn.eq_ignore_ascii_case("upgrade")) } } else { false @@ -184,8 +180,8 @@ impl H1Decoder { has_upgrade = true; // check content-length, some clients (dart) // sends "content-length: 0" with websocket upgrade - if let Ok(val) = value.to_str() { - if val == "websocket" { + if let Ok(val) = value.to_str().map(|val| val.trim()) { + if val.eq_ignore_ascii_case("websocket") { content_length = None; } } From 67df9399df8faa7983e4831d3de2abab37ef49dd Mon Sep 17 00:00:00 2001 From: Douman Date: Sun, 16 Dec 2018 18:43:11 +0300 Subject: [PATCH 0864/1635] H1 decoder should ignore headers case --- src/h1/decoder.rs | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 460d2b3a..b2c410c4 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -94,20 +94,20 @@ pub(crate) trait MessageType: Sized { } // transfer-encoding header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str() { - chunked = s.to_lowercase().contains("chunked"); + if let Ok(s) = value.to_str().map(|s| s.trim()) { + chunked = s.eq_ignore_ascii_case("chunked"); } else { return Err(ParseError::Header); } } // connection keep-alive state header::CONNECTION => { - ka = if let Ok(conn) = value.to_str() { - if conn.contains("keep-alive") { + ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { + if conn.eq_ignore_ascii_case("keep-alive") { Some(ConnectionType::KeepAlive) - } else if conn.contains("close") { + } else if conn.eq_ignore_ascii_case("close") { Some(ConnectionType::Close) - } else if conn.contains("upgrade") { + } else if conn.eq_ignore_ascii_case("upgrade") { Some(ConnectionType::Upgrade) } else { None @@ -120,8 +120,8 @@ pub(crate) trait MessageType: Sized { has_upgrade = true; // check content-length, some clients (dart) // sends "content-length: 0" with websocket upgrade - if let Ok(val) = value.to_str() { - if val == "websocket" { + if let Ok(val) = value.to_str().map(|val| val.trim()) { + if val.eq_ignore_ascii_case("websocket") { content_length = None; } } @@ -887,6 +887,14 @@ mod tests { let req = parse_ready!(&mut buf); assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + connection: Close\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); } #[test] @@ -909,6 +917,15 @@ mod tests { let req = parse_ready!(&mut buf); assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.0\r\n\ + connection: Keep-Alive\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + } #[test] @@ -959,6 +976,17 @@ mod tests { assert!(req.upgrade()); assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + + let mut buf = BytesMut::from( + "GET /test HTTP/1.1\r\n\ + upgrade: Websockets\r\n\ + connection: Upgrade\r\n\r\n", + ); + let req = parse_ready!(&mut buf); + + assert!(req.upgrade()); + assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + } #[test] From cc74435b0155b11c7405e5b9b3288721c9b5edcd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 19 Dec 2018 18:34:56 -0800 Subject: [PATCH 0865/1635] drop failure crate --- Cargo.toml | 13 +- src/client/error.rs | 79 ++------ src/error.rs | 401 ++++++++++++++++++----------------------- src/response.rs | 33 ---- src/ws/client/error.rs | 68 ++----- src/ws/mod.rs | 44 ++--- 6 files changed, 235 insertions(+), 403 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d992de64..a5fc597a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,12 +51,13 @@ actix-utils = "0.1.0" # actix-utils = { path="../actix-net/actix-utils/" } base64 = "0.9" +backtrace = "0.3" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" cookie = { version="0.11", features=["percent-encode"] } +derive_more = "0.13" encoding = "0.2" -failure = "0.1.3" futures = "0.1" http = "0.1.8" httparse = "1.3" @@ -74,19 +75,11 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" -trust-dns-proto = "0.5.0" -trust-dns-resolver = "0.10.0" -url = { version="1.7", features=["query_encoding"] } - -# native-tls -native-tls = { version="0.2", optional = true } +trust-dns-resolver = "0.10.1" # openssl openssl = { version="0.10", optional = true } -# rustls -rustls = { version = "^0.14", optional = true } - [dev-dependencies] actix-web = "0.7" env_logger = "0.5" diff --git a/src/client/error.rs b/src/client/error.rs index 815bc1ed..2a5df9c9 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -1,6 +1,6 @@ use std::io; -use failure::Fail; +use derive_more::{Display, From}; use trust_dns_resolver::error::ResolveError; #[cfg(feature = "ssl")] @@ -9,71 +9,52 @@ use openssl::ssl::{Error as SslError, HandshakeError}; use crate::error::{Error, ParseError}; /// A set of errors that can occur while connecting to an HTTP host -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum ConnectorError { /// Invalid URL - #[fail(display = "Invalid URL")] + #[display(fmt = "Invalid URL")] InvalidUrl(InvalidUrlKind), /// SSL feature is not enabled - #[fail(display = "SSL is not supported")] + #[display(fmt = "SSL is not supported")] SslIsNotSupported, /// SSL error #[cfg(feature = "ssl")] - #[fail(display = "{}", _0)] - SslError(#[cause] SslError), + #[display(fmt = "{}", _0)] + SslError(SslError), /// Failed to resolve the hostname - #[fail(display = "Failed resolving hostname: {}", _0)] + #[display(fmt = "Failed resolving hostname: {}", _0)] Resolver(ResolveError), /// No dns records - #[fail(display = "No dns records found for the input")] + #[display(fmt = "No dns records found for the input")] NoRecords, /// Connecting took too long - #[fail(display = "Timeout out while establishing connection")] + #[display(fmt = "Timeout out while establishing connection")] Timeout, /// Connector has been disconnected - #[fail(display = "Internal error: connector has been disconnected")] + #[display(fmt = "Internal error: connector has been disconnected")] Disconnected, /// Connection io error - #[fail(display = "{}", _0)] - IoError(io::Error), + #[display(fmt = "{}", _0)] + Io(io::Error), } -#[derive(Fail, Debug)] +#[derive(Debug, Display)] pub enum InvalidUrlKind { - #[fail(display = "Missing url scheme")] + #[display(fmt = "Missing url scheme")] MissingScheme, - #[fail(display = "Unknown url scheme")] + #[display(fmt = "Unknown url scheme")] UnknownScheme, - #[fail(display = "Missing host name")] + #[display(fmt = "Missing host name")] MissingHost, } -impl From for ConnectorError { - fn from(err: io::Error) -> ConnectorError { - ConnectorError::IoError(err) - } -} - -impl From for ConnectorError { - fn from(err: ResolveError) -> ConnectorError { - ConnectorError::Resolver(err) - } -} - -#[cfg(feature = "ssl")] -impl From for ConnectorError { - fn from(err: SslError) -> ConnectorError { - ConnectorError::SslError(err) - } -} - #[cfg(feature = "ssl")] impl From> for ConnectorError { fn from(err: HandshakeError) -> ConnectorError { @@ -90,10 +71,10 @@ impl From> for ConnectorError { } /// A set of errors that can occur during request sending and response reading -#[derive(Debug)] +#[derive(Debug, Display, From)] pub enum SendRequestError { /// Failed to connect to host - // #[fail(display = "Failed to connect to host: {}", _0)] + #[display(fmt = "Failed to connect to host: {}", _0)] Connector(ConnectorError), /// Error sending request Send(io::Error), @@ -102,27 +83,3 @@ pub enum SendRequestError { /// Error sending request body Body(Error), } - -impl From for SendRequestError { - fn from(err: io::Error) -> SendRequestError { - SendRequestError::Send(err) - } -} - -impl From for SendRequestError { - fn from(err: ConnectorError) -> SendRequestError { - SendRequestError::Connector(err) - } -} - -impl From for SendRequestError { - fn from(err: ParseError) -> SendRequestError { - SendRequestError::Response(err) - } -} - -impl From for SendRequestError { - fn from(err: Error) -> SendRequestError { - SendRequestError::Body(err) - } -} diff --git a/src/error.rs b/src/error.rs index 49602bd9..8af422fc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,14 @@ //! Error and Result module +use std::cell::RefCell; use std::io::Error as IoError; use std::str::Utf8Error; use std::string::FromUtf8Error; -use std::sync::Mutex; use std::{fmt, io, result}; // use actix::MailboxError; +use backtrace::Backtrace; use cookie; -use failure::{self, Backtrace, Fail}; +use derive_more::{Display, From}; use futures::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; @@ -21,7 +22,7 @@ use tokio_timer::Error as TimerError; pub use cookie::ParseError as CookieParseError; use crate::body::Body; -use crate::response::{Response, ResponseParts}; +use crate::response::Response; /// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) /// for actix web operations @@ -47,20 +48,6 @@ pub struct Error { } impl Error { - /// Deprecated way to reference the underlying response error. - #[deprecated( - since = "0.6.0", - note = "please use `Error::as_response_error()` instead" - )] - pub fn cause(&self) -> &ResponseError { - self.cause.as_ref() - } - - /// Returns a reference to the underlying cause of this `Error` as `Fail` - pub fn as_fail(&self) -> &Fail { - self.cause.as_fail() - } - /// Returns the reference to the underlying `ResponseError`. pub fn as_response_error(&self) -> &ResponseError { self.cause.as_ref() @@ -78,27 +65,27 @@ impl Error { } } - /// Attempts to downcast this `Error` to a particular `Fail` type by - /// reference. - /// - /// If the underlying error is not of type `T`, this will return `None`. - pub fn downcast_ref(&self) -> Option<&T> { - // in the most trivial way the cause is directly of the requested type. - if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) { - return Some(rv); - } + // /// Attempts to downcast this `Error` to a particular `Fail` type by + // /// reference. + // /// + // /// If the underlying error is not of type `T`, this will return `None`. + // pub fn downcast_ref(&self) -> Option<&T> { + // // in the most trivial way the cause is directly of the requested type. + // if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) { + // return Some(rv); + // } - // in the more complex case the error has been constructed from a failure - // error. This happens because we implement From by - // calling compat() and then storing it here. In failure this is - // represented by a failure::Error being wrapped in a failure::Compat. - // - // So we first downcast into that compat, to then further downcast through - // the failure's Error downcasting system into the original failure. - let compat: Option<&failure::Compat> = - Fail::downcast_ref(self.cause.as_fail()); - compat.and_then(|e| e.get_ref().downcast_ref()) - } + // // in the more complex case the error has been constructed from a failure + // // error. This happens because we implement From by + // // calling compat() and then storing it here. In failure this is + // // represented by a failure::Error being wrapped in a failure::Compat. + // // + // // So we first downcast into that compat, to then further downcast through + // // the failure's Error downcasting system into the original failure. + // let compat: Option<&failure::Compat> = + // Fail::downcast_ref(self.cause.as_fail()); + // compat.and_then(|e| e.get_ref().downcast_ref()) + // } /// Converts error to a response instance and set error message as response body pub fn response_with_message(self) -> Response { @@ -108,36 +95,41 @@ impl Error { } } -/// Helper trait to downcast a response error into a fail. -/// -/// This is currently not exposed because it's unclear if this is the best way -/// to achieve the downcasting on `Error` for which this is needed. -#[doc(hidden)] -pub trait InternalResponseErrorAsFail { - #[doc(hidden)] - fn as_fail(&self) -> &Fail; - #[doc(hidden)] - fn as_mut_fail(&mut self) -> &mut Fail; -} +// /// Helper trait to downcast a response error into a fail. +// /// +// /// This is currently not exposed because it's unclear if this is the best way +// /// to achieve the downcasting on `Error` for which this is needed. +// #[doc(hidden)] +// pub trait InternalResponseErrorAsFail { +// #[doc(hidden)] +// fn as_fail(&self) -> &Fail; +// #[doc(hidden)] +// fn as_mut_fail(&mut self) -> &mut Fail; +// } -#[doc(hidden)] -impl InternalResponseErrorAsFail for T { - fn as_fail(&self) -> &Fail { - self - } - fn as_mut_fail(&mut self) -> &mut Fail { - self - } -} +// #[doc(hidden)] +// impl InternalResponseErrorAsFail for T { +// fn as_fail(&self) -> &Fail { +// self +// } +// fn as_mut_fail(&mut self) -> &mut Fail { +// self +// } +// } /// Error that can be converted to `Response` -pub trait ResponseError: Fail + InternalResponseErrorAsFail { +pub trait ResponseError: fmt::Debug + fmt::Display { /// Create response for error /// /// Internal server error is generated by default. fn error_response(&self) -> Response { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } + + /// Response + fn backtrace(&self) -> Option<&Backtrace> { + None + } } impl fmt::Display for Error { @@ -169,7 +161,7 @@ impl From for Response { } /// `Error` for any error that implements `ResponseError` -impl From for Error { +impl From for Error { fn from(err: T) -> Error { let backtrace = if err.backtrace().is_none() { Some(Backtrace::new()) @@ -183,17 +175,17 @@ impl From for Error { } } -/// Compatibility for `failure::Error` -impl ResponseError for failure::Compat where - T: fmt::Display + fmt::Debug + Sync + Send + 'static -{ -} +// /// Compatibility for `failure::Error` +// impl ResponseError for failure::Compat where +// T: fmt::Display + fmt::Debug + Sync + Send + 'static +// { +// } -impl From for Error { - fn from(err: failure::Error) -> Error { - err.compat().into() - } -} +// impl From for Error { +// fn from(err: failure::Error) -> Error { +// err.compat().into() +// } +// } /// `InternalServerError` for `JsonError` impl ResponseError for JsonError {} @@ -254,40 +246,40 @@ impl ResponseError for Canceled {} // impl ResponseError for MailboxError {} /// A set of errors that can occur during parsing HTTP streams -#[derive(Fail, Debug)] +#[derive(Debug, Display)] pub enum ParseError { /// An invalid `Method`, such as `GE.T`. - #[fail(display = "Invalid Method specified")] + #[display(fmt = "Invalid Method specified")] Method, /// An invalid `Uri`, such as `exam ple.domain`. - #[fail(display = "Uri error: {}", _0)] + #[display(fmt = "Uri error: {}", _0)] Uri(InvalidUri), /// An invalid `HttpVersion`, such as `HTP/1.1` - #[fail(display = "Invalid HTTP version specified")] + #[display(fmt = "Invalid HTTP version specified")] Version, /// An invalid `Header`. - #[fail(display = "Invalid Header provided")] + #[display(fmt = "Invalid Header provided")] Header, /// A message head is too large to be reasonable. - #[fail(display = "Message head is too large")] + #[display(fmt = "Message head is too large")] TooLarge, /// A message reached EOF, but is not complete. - #[fail(display = "Message is incomplete")] + #[display(fmt = "Message is incomplete")] Incomplete, /// An invalid `Status`, such as `1337 ELITE`. - #[fail(display = "Invalid Status provided")] + #[display(fmt = "Invalid Status provided")] Status, /// A timeout occurred waiting for an IO event. #[allow(dead_code)] - #[fail(display = "Timeout")] + #[display(fmt = "Timeout")] Timeout, /// An `io::Error` that occurred while trying to read or write to a network /// stream. - #[fail(display = "IO error: {}", _0)] - Io(#[cause] IoError), + #[display(fmt = "IO error: {}", _0)] + Io(IoError), /// Parsing a field as string failed - #[fail(display = "UTF8 error: {}", _0)] - Utf8(#[cause] Utf8Error), + #[display(fmt = "UTF8 error: {}", _0)] + Utf8(Utf8Error), } /// Return `BadRequest` for `ParseError` @@ -335,20 +327,20 @@ impl From for ParseError { } } -#[derive(Fail, Debug)] +#[derive(Display, Debug)] /// A set of errors that can occur during payload parsing pub enum PayloadError { /// A payload reached EOF, but is not complete. - #[fail(display = "A payload reached EOF, but is not complete.")] + #[display(fmt = "A payload reached EOF, but is not complete.")] Incomplete(Option), /// Content encoding stream corruption - #[fail(display = "Can not decode content-encoding.")] + #[display(fmt = "Can not decode content-encoding.")] EncodingCorrupted, /// A payload reached size limit. - #[fail(display = "A payload reached size limit.")] + #[display(fmt = "A payload reached size limit.")] Overflow, /// A payload length is unknown. - #[fail(display = "A payload length is unknown.")] + #[display(fmt = "A payload length is unknown.")] UnknownLength, } @@ -378,44 +370,44 @@ impl ResponseError for cookie::ParseError { } } -#[derive(Debug)] +#[derive(Debug, Display)] /// A set of errors that can occur during dispatching http requests pub enum DispatchError { /// Service error - // #[fail(display = "Application specific error: {}", _0)] + #[display(fmt = "Service specific error: {:?}", _0)] Service(E), /// An `io::Error` that occurred while trying to read or write to a network /// stream. - // #[fail(display = "IO error: {}", _0)] + #[display(fmt = "IO error: {}", _0)] Io(io::Error), /// Http request parse error. - // #[fail(display = "Parse error: {}", _0)] + #[display(fmt = "Parse error: {}", _0)] Parse(ParseError), /// The first request did not complete within the specified timeout. - // #[fail(display = "The first request did not complete within the specified timeout")] + #[display(fmt = "The first request did not complete within the specified timeout")] SlowRequestTimeout, /// Disconnect timeout. Makes sense for ssl streams. - // #[fail(display = "Connection shutdown timeout")] + #[display(fmt = "Connection shutdown timeout")] DisconnectTimeout, /// Payload is not consumed - // #[fail(display = "Task is completed but request's payload is not consumed")] + #[display(fmt = "Task is completed but request's payload is not consumed")] PayloadIsNotConsumed, /// Malformed request - // #[fail(display = "Malformed request")] + #[display(fmt = "Malformed request")] MalformedRequest, /// Internal error - // #[fail(display = "Internal error")] + #[display(fmt = "Internal error")] InternalError, /// Unknown error - // #[fail(display = "Unknown error")] + #[display(fmt = "Unknown error")] Unknown, } @@ -432,13 +424,13 @@ impl From for DispatchError { } /// A set of error that can occure during parsing content type -#[derive(Fail, PartialEq, Debug)] +#[derive(PartialEq, Debug, Display)] pub enum ContentTypeError { /// Can not parse content type - #[fail(display = "Can not parse content type")] + #[display(fmt = "Can not parse content type")] ParseError, /// Unknown content encoding - #[fail(display = "Unknown content encoding")] + #[display(fmt = "Unknown content encoding")] UnknownEncoding, } @@ -450,28 +442,26 @@ impl ResponseError for ContentTypeError { } /// A set of errors that can occur during parsing urlencoded payloads -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum UrlencodedError { /// Can not decode chunked transfer encoding - #[fail(display = "Can not decode chunked transfer encoding")] + #[display(fmt = "Can not decode chunked transfer encoding")] Chunked, /// Payload size is bigger than allowed. (default: 256kB) - #[fail( - display = "Urlencoded payload size is bigger than allowed. (default: 256kB)" - )] + #[display(fmt = "Urlencoded payload size is bigger than allowed. (default: 256kB)")] Overflow, /// Payload size is now known - #[fail(display = "Payload size is now known")] + #[display(fmt = "Payload size is now known")] UnknownLength, /// Content type error - #[fail(display = "Content type error")] + #[display(fmt = "Content type error")] ContentType, /// Parse error - #[fail(display = "Parse error")] + #[display(fmt = "Parse error")] Parse, /// Payload error - #[fail(display = "Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), + #[display(fmt = "Error that occur during reading payload: {}", _0)] + Payload(PayloadError), } /// Return `BadRequest` for `UrlencodedError` @@ -485,27 +475,21 @@ impl ResponseError for UrlencodedError { } } -impl From for UrlencodedError { - fn from(err: PayloadError) -> UrlencodedError { - UrlencodedError::Payload(err) - } -} - /// A set of errors that can occur during parsing json payloads -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum JsonPayloadError { /// Payload size is bigger than allowed. (default: 256kB) - #[fail(display = "Json payload size is bigger than allowed. (default: 256kB)")] + #[display(fmt = "Json payload size is bigger than allowed. (default: 256kB)")] Overflow, /// Content type error - #[fail(display = "Content type error")] + #[display(fmt = "Content type error")] ContentType, /// Deserialize error - #[fail(display = "Json deserialize error: {}", _0)] - Deserialize(#[cause] JsonError), + #[display(fmt = "Json deserialize error: {}", _0)] + Deserialize(JsonError), /// Payload error - #[fail(display = "Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), + #[display(fmt = "Error that occur during reading payload: {}", _0)] + Payload(PayloadError), } /// Return `BadRequest` for `UrlencodedError` @@ -518,19 +502,8 @@ impl ResponseError for JsonPayloadError { } } -impl From for JsonPayloadError { - fn from(err: PayloadError) -> JsonPayloadError { - JsonPayloadError::Payload(err) - } -} - -impl From for JsonPayloadError { - fn from(err: JsonError) -> JsonPayloadError { - JsonPayloadError::Deserialize(err) - } -} - /// Error type returned when reading body as lines. +#[derive(From)] pub enum ReadlinesError { /// Error when decoding a line. EncodingError, @@ -542,18 +515,6 @@ pub enum ReadlinesError { ContentTypeError(ContentTypeError), } -impl From for ReadlinesError { - fn from(err: PayloadError) -> Self { - ReadlinesError::PayloadError(err) - } -} - -impl From for ReadlinesError { - fn from(err: ContentTypeError) -> Self { - ReadlinesError::ContentTypeError(err) - } -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" @@ -578,7 +539,7 @@ pub struct InternalError { enum InternalErrorType { Status(StatusCode), - Response(Box>>), + Response(RefCell>), } impl InternalError { @@ -593,27 +554,17 @@ impl InternalError { /// Create `InternalError` with predefined `Response`. pub fn from_response(cause: T, response: Response) -> Self { - let resp = response.into_parts(); InternalError { cause, - status: InternalErrorType::Response(Box::new(Mutex::new(Some(resp)))), + status: InternalErrorType::Response(RefCell::new(Some(response))), backtrace: Backtrace::new(), } } } -impl Fail for InternalError -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - fn backtrace(&self) -> Option<&Backtrace> { - Some(&self.backtrace) - } -} - impl fmt::Debug for InternalError where - T: Send + Sync + fmt::Debug + 'static, + T: fmt::Debug + 'static, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.cause, f) @@ -622,7 +573,7 @@ where impl fmt::Display for InternalError where - T: Send + Sync + fmt::Display + 'static, + T: fmt::Display + 'static, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.cause, f) @@ -631,14 +582,18 @@ where impl ResponseError for InternalError where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { + fn backtrace(&self) -> Option<&Backtrace> { + Some(&self.backtrace) + } + fn error_response(&self) -> Response { match self.status { InternalErrorType::Status(st) => Response::new(st), InternalErrorType::Response(ref resp) => { - if let Some(resp) = resp.lock().unwrap().take() { - Response::<()>::from_parts(resp) + if let Some(resp) = resp.borrow_mut().take() { + resp } else { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } @@ -659,7 +614,7 @@ impl From for Error { #[allow(non_snake_case)] pub fn ErrorBadRequest(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::BAD_REQUEST).into() } @@ -669,7 +624,7 @@ where #[allow(non_snake_case)] pub fn ErrorUnauthorized(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::UNAUTHORIZED).into() } @@ -679,7 +634,7 @@ where #[allow(non_snake_case)] pub fn ErrorForbidden(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::FORBIDDEN).into() } @@ -689,7 +644,7 @@ where #[allow(non_snake_case)] pub fn ErrorNotFound(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::NOT_FOUND).into() } @@ -699,7 +654,7 @@ where #[allow(non_snake_case)] pub fn ErrorMethodNotAllowed(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() } @@ -709,7 +664,7 @@ where #[allow(non_snake_case)] pub fn ErrorRequestTimeout(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into() } @@ -719,7 +674,7 @@ where #[allow(non_snake_case)] pub fn ErrorConflict(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::CONFLICT).into() } @@ -729,7 +684,7 @@ where #[allow(non_snake_case)] pub fn ErrorGone(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::GONE).into() } @@ -739,7 +694,7 @@ where #[allow(non_snake_case)] pub fn ErrorPreconditionFailed(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() } @@ -749,7 +704,7 @@ where #[allow(non_snake_case)] pub fn ErrorExpectationFailed(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() } @@ -759,7 +714,7 @@ where #[allow(non_snake_case)] pub fn ErrorInternalServerError(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() } @@ -769,7 +724,7 @@ where #[allow(non_snake_case)] pub fn ErrorNotImplemented(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into() } @@ -779,7 +734,7 @@ where #[allow(non_snake_case)] pub fn ErrorBadGateway(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::BAD_GATEWAY).into() } @@ -789,7 +744,7 @@ where #[allow(non_snake_case)] pub fn ErrorServiceUnavailable(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into() } @@ -799,7 +754,7 @@ where #[allow(non_snake_case)] pub fn ErrorGatewayTimeout(err: T) -> Error where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() } @@ -808,10 +763,8 @@ where mod tests { use super::*; use cookie::ParseError as CookieParseError; - use failure; use http::{Error as HttpError, StatusCode}; use httparse; - use std::env; use std::error::Error as StdError; use std::io; @@ -829,11 +782,10 @@ mod tests { } #[test] - fn test_as_fail() { + fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.description().to_owned(); - let e = ParseError::Io(orig); - assert_eq!(format!("{}", e.cause().unwrap()), desc); + let e: Error = ParseError::Io(orig).into(); + assert_eq!(format!("{}", e.as_response_error()), "IO error: other"); } #[test] @@ -847,7 +799,7 @@ mod tests { let orig = io::Error::new(io::ErrorKind::Other, "other"); let desc = orig.description().to_owned(); let e = Error::from(orig); - assert_eq!(format!("{}", e.as_fail()), desc); + assert_eq!(format!("{}", e.as_response_error()), desc); } #[test] @@ -881,7 +833,7 @@ mod tests { ($from:expr => $error:pat) => { match ParseError::from($from) { e @ $error => { - let desc = format!("{}", e.cause().unwrap()); + let desc = format!("{}", e); assert_eq!(desc, $from.description().to_owned()); } _ => unreachable!("{:?}", $from), @@ -891,8 +843,7 @@ mod tests { #[test] fn test_from() { - from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); - + // from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); from!(httparse::Error::HeaderName => ParseError::Header); from!(httparse::Error::HeaderName => ParseError::Header); from!(httparse::Error::HeaderValue => ParseError::Header); @@ -903,22 +854,22 @@ mod tests { from!(httparse::Error::Version => ParseError::Version); } - #[test] - fn failure_error() { - const NAME: &str = "RUST_BACKTRACE"; - let old_tb = env::var(NAME); - env::set_var(NAME, "0"); - let error = failure::err_msg("Hello!"); - let resp: Error = error.into(); - assert_eq!( - format!("{:?}", resp), - "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n" - ); - match old_tb { - Ok(x) => env::set_var(NAME, x), - _ => env::remove_var(NAME), - } - } + // #[test] + // fn failure_error() { + // const NAME: &str = "RUST_BACKTRACE"; + // let old_tb = env::var(NAME); + // env::set_var(NAME, "0"); + // let error = failure::err_msg("Hello!"); + // let resp: Error = error.into(); + // assert_eq!( + // format!("{:?}", resp), + // "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n" + // ); + // match old_tb { + // Ok(x) => env::set_var(NAME, x), + // _ => env::remove_var(NAME), + // } + // } #[test] fn test_internal_error() { @@ -928,31 +879,31 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_error_downcasting_direct() { - #[derive(Debug, Fail)] - #[fail(display = "demo error")] - struct DemoError; + // #[test] + // fn test_error_downcasting_direct() { + // #[derive(Debug, Display)] + // #[display(fmt = "demo error")] + // struct DemoError; - impl ResponseError for DemoError {} + // impl ResponseError for DemoError {} - let err: Error = DemoError.into(); - let err_ref: &DemoError = err.downcast_ref().unwrap(); - assert_eq!(err_ref.to_string(), "demo error"); - } + // let err: Error = DemoError.into(); + // let err_ref: &DemoError = err.downcast_ref().unwrap(); + // assert_eq!(err_ref.to_string(), "demo error"); + // } - #[test] - fn test_error_downcasting_compat() { - #[derive(Debug, Fail)] - #[fail(display = "demo error")] - struct DemoError; + // #[test] + // fn test_error_downcasting_compat() { + // #[derive(Debug, Display)] + // #[display(fmt = "demo error")] + // struct DemoError; - impl ResponseError for DemoError {} + // impl ResponseError for DemoError {} - let err: Error = failure::Error::from(DemoError).into(); - let err_ref: &DemoError = err.downcast_ref().unwrap(); - assert_eq!(err_ref.to_string(), "demo error"); - } + // let err: Error = failure::Error::from(DemoError).into(); + // let err_ref: &DemoError = err.downcast_ref().unwrap(); + // assert_eq!(err_ref.to_string(), "demo error"); + // } #[test] fn test_error_helpers() { diff --git a/src/response.rs b/src/response.rs index 5cff612f..49f2b63f 100644 --- a/src/response.rs +++ b/src/response.rs @@ -240,17 +240,6 @@ impl Response { pub(crate) fn release(self) { ResponsePool::release(self.0); } - - pub(crate) fn into_parts(self) -> ResponseParts { - self.0.into_parts() - } - - pub(crate) fn from_parts(parts: ResponseParts) -> Response { - Response( - Box::new(InnerResponse::from_parts(parts)), - ResponseBody::Body(Body::Empty), - ) - } } impl fmt::Debug for Response { @@ -736,11 +725,6 @@ struct InnerResponse { pool: &'static ResponsePool, } -pub(crate) struct ResponseParts { - head: ResponseHead, - error: Option, -} - impl InnerResponse { #[inline] fn new(status: StatusCode, pool: &'static ResponsePool) -> InnerResponse { @@ -757,23 +741,6 @@ impl InnerResponse { error: None, } } - - /// This is for failure, we can not have Send + Sync on Streaming and Actor response - fn into_parts(self) -> ResponseParts { - ResponseParts { - head: self.head, - error: self.error, - } - } - - fn from_parts(parts: ResponseParts) -> InnerResponse { - InnerResponse { - head: parts.head, - response_size: 0, - error: parts.error, - pool: ResponsePool::pool(), - } - } } /// Internal use only! diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 02c72375..1eccb0b9 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -2,82 +2,52 @@ use std::io; use actix_connector::ConnectorError; -use failure::Fail; +use derive_more::{Display, From}; use http::{header::HeaderValue, Error as HttpError, StatusCode}; use crate::error::ParseError; use crate::ws::ProtocolError; /// Websocket client error -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum ClientError { /// Invalid url - #[fail(display = "Invalid url")] + #[display(fmt = "Invalid url")] InvalidUrl, /// Invalid response status - #[fail(display = "Invalid response status")] + #[display(fmt = "Invalid response status")] InvalidResponseStatus(StatusCode), /// Invalid upgrade header - #[fail(display = "Invalid upgrade header")] + #[display(fmt = "Invalid upgrade header")] InvalidUpgradeHeader, /// Invalid connection header - #[fail(display = "Invalid connection header")] + #[display(fmt = "Invalid connection header")] InvalidConnectionHeader(HeaderValue), /// Missing CONNECTION header - #[fail(display = "Missing CONNECTION header")] + #[display(fmt = "Missing CONNECTION header")] MissingConnectionHeader, /// Missing SEC-WEBSOCKET-ACCEPT header - #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] + #[display(fmt = "Missing SEC-WEBSOCKET-ACCEPT header")] MissingWebSocketAcceptHeader, /// Invalid challenge response - #[fail(display = "Invalid challenge response")] + #[display(fmt = "Invalid challenge response")] InvalidChallengeResponse(String, HeaderValue), /// Http parsing error - #[fail(display = "Http parsing error")] - Http(#[cause] HttpError), + #[display(fmt = "Http parsing error")] + Http(HttpError), /// Response parsing error - #[fail(display = "Response parsing error: {}", _0)] - ParseError(#[cause] ParseError), + #[display(fmt = "Response parsing error: {}", _0)] + ParseError(ParseError), /// Protocol error - #[fail(display = "{}", _0)] - Protocol(#[cause] ProtocolError), + #[display(fmt = "{}", _0)] + Protocol(ProtocolError), /// Connect error - #[fail(display = "Connector error: {:?}", _0)] + #[display(fmt = "Connector error: {:?}", _0)] Connect(ConnectorError), /// IO Error - #[fail(display = "{}", _0)] - Io(#[cause] io::Error), + #[display(fmt = "{}", _0)] + Io(io::Error), /// "Disconnected" - #[fail(display = "Disconnected")] + #[display(fmt = "Disconnected")] Disconnected, } - -impl From for ClientError { - fn from(err: HttpError) -> ClientError { - ClientError::Http(err) - } -} - -impl From for ClientError { - fn from(err: ConnectorError) -> ClientError { - ClientError::Connect(err) - } -} - -impl From for ClientError { - fn from(err: ProtocolError) -> ClientError { - ClientError::Protocol(err) - } -} - -impl From for ClientError { - fn from(err: io::Error) -> ClientError { - ClientError::Io(err) - } -} - -impl From for ClientError { - fn from(err: ParseError) -> ClientError { - ClientError::ParseError(err) - } -} diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 02667c96..2d629c73 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -5,7 +5,7 @@ //! communicate with the peer. use std::io; -use failure::Fail; +use derive_more::{Display, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; @@ -28,65 +28,59 @@ pub use self::service::VerifyWebSockets; pub use self::transport::Transport; /// Websocket protocol errors -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum ProtocolError { /// Received an unmasked frame from client - #[fail(display = "Received an unmasked frame from client")] + #[display(fmt = "Received an unmasked frame from client")] UnmaskedFrame, /// Received a masked frame from server - #[fail(display = "Received a masked frame from server")] + #[display(fmt = "Received a masked frame from server")] MaskedFrame, /// Encountered invalid opcode - #[fail(display = "Invalid opcode: {}", _0)] + #[display(fmt = "Invalid opcode: {}", _0)] InvalidOpcode(u8), /// Invalid control frame length - #[fail(display = "Invalid control frame length: {}", _0)] + #[display(fmt = "Invalid control frame length: {}", _0)] InvalidLength(usize), /// Bad web socket op code - #[fail(display = "Bad web socket op code")] + #[display(fmt = "Bad web socket op code")] BadOpCode, /// A payload reached size limit. - #[fail(display = "A payload reached size limit.")] + #[display(fmt = "A payload reached size limit.")] Overflow, /// Continuation is not supported - #[fail(display = "Continuation is not supported.")] + #[display(fmt = "Continuation is not supported.")] NoContinuation, /// Bad utf-8 encoding - #[fail(display = "Bad utf-8 encoding.")] + #[display(fmt = "Bad utf-8 encoding.")] BadEncoding, /// Io error - #[fail(display = "io error: {}", _0)] - Io(#[cause] io::Error), + #[display(fmt = "io error: {}", _0)] + Io(io::Error), } impl ResponseError for ProtocolError {} -impl From for ProtocolError { - fn from(err: io::Error) -> ProtocolError { - ProtocolError::Io(err) - } -} - /// Websocket handshake errors -#[derive(Fail, PartialEq, Debug)] +#[derive(PartialEq, Debug, Display)] pub enum HandshakeError { /// Only get method is allowed - #[fail(display = "Method not allowed")] + #[display(fmt = "Method not allowed")] GetMethodRequired, /// Upgrade header if not set to websocket - #[fail(display = "Websocket upgrade is expected")] + #[display(fmt = "Websocket upgrade is expected")] NoWebsocketUpgrade, /// Connection header is not set to upgrade - #[fail(display = "Connection upgrade is expected")] + #[display(fmt = "Connection upgrade is expected")] NoConnectionUpgrade, /// Websocket version header is not set - #[fail(display = "Websocket version header is required")] + #[display(fmt = "Websocket version header is required")] NoVersionHeader, /// Unsupported websocket version - #[fail(display = "Unsupported version")] + #[display(fmt = "Unsupported version")] UnsupportedVersion, /// Websocket key is not set or wrong - #[fail(display = "Unknown websocket key")] + #[display(fmt = "Unknown websocket key")] BadWebsocketKey, } From e9fe3879df46fd066ca4bd3abd27919a0eadbfbf Mon Sep 17 00:00:00 2001 From: Phil Booth Date: Tue, 18 Dec 2018 17:53:03 +0000 Subject: [PATCH 0866/1635] Support custom content types in JsonConfig --- CHANGES.md | 4 +++ src/httpmessage.rs | 2 +- src/json.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 45faf35b..232d85b8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## [0.7.17] - 2018-xx-xx +### Added + +* Support for custom content types in `JsonConfig`. #637 + ### Fixed * HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631 diff --git a/src/httpmessage.rs b/src/httpmessage.rs index e96dce48..ea5e4d86 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -200,7 +200,7 @@ pub trait HttpMessage: Sized { /// # fn main() {} /// ``` fn json(&self) -> JsonBody { - JsonBody::new(self) + JsonBody::new::<()>(self, None) } /// Return stream to http payload processes as multipart. diff --git a/src/json.rs b/src/json.rs index 178143f1..b04cad2f 100644 --- a/src/json.rs +++ b/src/json.rs @@ -143,7 +143,7 @@ where let req2 = req.clone(); let err = Rc::clone(&cfg.ehandler); Box::new( - JsonBody::new(req) + JsonBody::new(req, Some(cfg)) .limit(cfg.limit) .map_err(move |e| (*err)(e, &req2)) .map(Json), @@ -155,6 +155,7 @@ where /// /// ```rust /// # extern crate actix_web; +/// extern crate mime; /// #[macro_use] extern crate serde_derive; /// use actix_web::{error, http, App, HttpResponse, Json, Result}; /// @@ -173,6 +174,9 @@ where /// r.method(http::Method::POST) /// .with_config(index, |cfg| { /// cfg.0.limit(4096) // <- change json extractor configuration +/// .content_type(|mime| { // <- accept text/plain content type +/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN +/// }) /// .error_handler(|err, req| { // <- create custom error response /// error::InternalError::from_response( /// err, HttpResponse::Conflict().finish()).into() @@ -184,6 +188,7 @@ where pub struct JsonConfig { limit: usize, ehandler: Rc) -> Error>, + content_type: Option bool>>, } impl JsonConfig { @@ -201,6 +206,15 @@ impl JsonConfig { self.ehandler = Rc::new(f); self } + + /// Set predicate for allowed content types + pub fn content_type(&mut self, predicate: F) -> &mut Self + where + F: Fn(mime::Mime) -> bool + 'static, + { + self.content_type = Some(Box::new(predicate)); + self + } } impl Default for JsonConfig { @@ -208,6 +222,7 @@ impl Default for JsonConfig { JsonConfig { limit: 262_144, ehandler: Rc::new(|e, _| e.into()), + content_type: None, } } } @@ -217,6 +232,7 @@ impl Default for JsonConfig { /// Returns error: /// /// * content type is not `application/json` +/// (unless specified in [`JsonConfig`](struct.JsonConfig.html)) /// * content length is greater than 256k /// /// # Server example @@ -253,10 +269,13 @@ pub struct JsonBody { impl JsonBody { /// Create `JsonBody` for request. - pub fn new(req: &T) -> Self { + pub fn new(req: &T, cfg: Option<&JsonConfig>) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) + mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) || + cfg.map_or(false, |cfg| { + cfg.content_type.as_ref().map_or(false, |predicate| predicate(mime)) + }) } else { false }; @@ -440,4 +459,61 @@ mod tests { .finish(); assert!(handler.handle(&req).as_err().is_none()) } + + #[test] + fn test_with_json_and_bad_content_type() { + let mut cfg = JsonConfig::default(); + cfg.limit(4096); + let handler = With::new(|data: Json| data, cfg); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ).header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); + assert!(handler.handle(&req).as_err().is_some()) + } + + #[test] + fn test_with_json_and_good_custom_content_type() { + let mut cfg = JsonConfig::default(); + cfg.limit(4096); + cfg.content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + }); + let handler = With::new(|data: Json| data, cfg); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ).header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); + assert!(handler.handle(&req).as_err().is_none()) + } + + #[test] + fn test_with_json_and_bad_custom_content_type() { + let mut cfg = JsonConfig::default(); + cfg.limit(4096); + cfg.content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + }); + let handler = With::new(|data: Json| data, cfg); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html"), + ).header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); + assert!(handler.handle(&req).as_err().is_some()) + } } From 477bf0d8ae0b22a4ac2d1f52d0cdcd91599546bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 23 Dec 2018 10:19:12 -0800 Subject: [PATCH 0867/1635] Send HTTP/1.1 100 Continue if request contains expect: continue header #634 --- CHANGES.md | 4 +++- Cargo.toml | 6 +++--- src/server/h1.rs | 46 ++++++++++++++++++++++++++++++----------- src/server/h1decoder.rs | 31 ++++++++++++++++++++------- 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 232d85b8..bdc50fc5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,13 @@ # Changes -## [0.7.17] - 2018-xx-xx +## [0.7.17] - 2018-12-23 ### Added * Support for custom content types in `JsonConfig`. #637 +* Send `HTTP/1.1 100 Continue` if request contains `expect: continue` header #634 + ### Fixed * HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631 diff --git a/Cargo.toml b/Cargo.toml index cd13e341..32ca147c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.16" +version = "0.7.17" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -62,7 +62,7 @@ cell = ["actix-net/cell"] [dependencies] actix = "0.7.9" -actix-net = "0.2.2" +actix-net = "0.2.6" askama_escape = "0.1.0" base64 = "0.10" @@ -105,7 +105,7 @@ slab = "0.4" tokio = "0.1" tokio-io = "0.1" tokio-tcp = "0.1" -tokio-timer = "0.2" +tokio-timer = "0.2.8" tokio-reactor = "0.1" tokio-current-thread = "0.1" diff --git a/src/server/h1.rs b/src/server/h1.rs index f0edefae..fa7d2fda 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -7,6 +7,7 @@ use futures::{Async, Future, Poll}; use tokio_current_thread::spawn; use tokio_timer::Delay; +use body::Binary; use error::{Error, PayloadError}; use http::{StatusCode, Version}; use payload::{Payload, PayloadStatus, PayloadWriter}; @@ -50,32 +51,40 @@ pub struct Http1Dispatcher { } enum Entry { - Task(H::Task), + Task(H::Task, Option<()>), Error(Box), } impl Entry { fn into_task(self) -> H::Task { match self { - Entry::Task(task) => task, + Entry::Task(task, _) => task, Entry::Error(_) => panic!(), } } fn disconnected(&mut self) { match *self { - Entry::Task(ref mut task) => task.disconnected(), + Entry::Task(ref mut task, _) => task.disconnected(), Entry::Error(ref mut task) => task.disconnected(), } } fn poll_io(&mut self, io: &mut Writer) -> Poll { match *self { - Entry::Task(ref mut task) => task.poll_io(io), + Entry::Task(ref mut task, ref mut except) => { + match except { + Some(_) => { + let _ = io.write(&Binary::from("HTTP/1.1 100 Continue\r\n\r\n")); + } + _ => (), + }; + task.poll_io(io) + } Entry::Error(ref mut task) => task.poll_io(io), } } fn poll_completed(&mut self) -> Poll<(), Error> { match *self { - Entry::Task(ref mut task) => task.poll_completed(), + Entry::Task(ref mut task, _) => task.poll_completed(), Entry::Error(ref mut task) => task.poll_completed(), } } @@ -463,7 +472,11 @@ where 'outer: loop { match self.decoder.decode(&mut self.buf, &self.settings) { - Ok(Some(Message::Message { mut msg, payload })) => { + Ok(Some(Message::Message { + mut msg, + mut expect, + payload, + })) => { updated = true; self.flags.insert(Flags::STARTED); @@ -484,6 +497,12 @@ where match self.settings.handler().handle(msg) { Ok(mut task) => { if self.tasks.is_empty() { + if expect { + expect = false; + let _ = self.stream.write(&Binary::from( + "HTTP/1.1 100 Continue\r\n\r\n", + )); + } match task.poll_io(&mut self.stream) { Ok(Async::Ready(ready)) => { // override keep-alive state @@ -510,7 +529,10 @@ where } } } - self.tasks.push_back(Entry::Task(task)); + self.tasks.push_back(Entry::Task( + task, + if expect { Some(()) } else { None }, + )); continue 'outer; } Err(_) => { @@ -608,13 +630,13 @@ mod tests { impl Message { fn message(self) -> Request { match self { - Message::Message { msg, payload: _ } => msg, + Message::Message { msg, .. } => msg, _ => panic!("error"), } } fn is_payload(&self) -> bool { match *self { - Message::Message { msg: _, payload } => payload, + Message::Message { payload, .. } => payload, _ => panic!("error"), } } @@ -874,13 +896,13 @@ mod tests { let settings = wrk_settings(); let mut reader = H1Decoder::new(); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } buf.extend(b"t"); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } buf.extend(b"es"); - assert!{ reader.decode(&mut buf, &settings).unwrap().is_none() } + assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } buf.extend(b"t: value\r\n\r\n"); match reader.decode(&mut buf, &settings) { diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs index 80b49983..ece6b3cc 100644 --- a/src/server/h1decoder.rs +++ b/src/server/h1decoder.rs @@ -20,7 +20,11 @@ pub(crate) struct H1Decoder { #[derive(Debug)] pub(crate) enum Message { - Message { msg: Request, payload: bool }, + Message { + msg: Request, + payload: bool, + expect: bool, + }, Chunk(Bytes), Eof, } @@ -63,10 +67,11 @@ impl H1Decoder { .parse_message(src, settings) .map_err(DecoderError::Error)? { - Async::Ready((msg, decoder)) => { + Async::Ready((msg, expect, decoder)) => { self.decoder = decoder; Ok(Some(Message::Message { msg, + expect, payload: self.decoder.is_some(), })) } @@ -85,11 +90,12 @@ impl H1Decoder { &self, buf: &mut BytesMut, settings: &ServiceConfig, - ) -> Poll<(Request, Option), ParseError> { + ) -> Poll<(Request, bool, Option), ParseError> { // Parse http message let mut has_upgrade = false; let mut chunked = false; let mut content_length = None; + let mut expect_continue = false; let msg = { // Unsafe: we read only this data only after httparse parses headers into. @@ -165,11 +171,17 @@ impl H1Decoder { } // connection keep-alive state header::CONNECTION => { - let ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { - if version == Version::HTTP_10 && conn.eq_ignore_ascii_case("keep-alive") { + let ka = if let Ok(conn) = + value.to_str().map(|conn| conn.trim()) + { + if version == Version::HTTP_10 + && conn.eq_ignore_ascii_case("keep-alive") + { true } else { - version == Version::HTTP_11 && !(conn.eq_ignore_ascii_case("close") || conn.eq_ignore_ascii_case("upgrade")) + version == Version::HTTP_11 + && !(conn.eq_ignore_ascii_case("close") + || conn.eq_ignore_ascii_case("upgrade")) } } else { false @@ -186,6 +198,11 @@ impl H1Decoder { } } } + header::EXPECT => { + if value == "100-continue" { + expect_continue = true + } + } _ => (), } @@ -216,7 +233,7 @@ impl H1Decoder { None }; - Ok(Async::Ready((msg, decoder))) + Ok(Async::Ready((msg, expect_continue, decoder))) } } From bfdf762062e936aabfb068e7a0575adbfb407ecb Mon Sep 17 00:00:00 2001 From: BlueC0re Date: Mon, 24 Dec 2018 19:16:07 +0100 Subject: [PATCH 0868/1635] Only return a single Origin value (#644) Only return a single origin if matched. --- CHANGES.md | 1 + src/middleware/cors.rs | 76 +++++++++++++++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bdc50fc5..89c85b98 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ ### Fixed * HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631 +* Access-Control-Allow-Origin header should only a return a single, matching origin. #603 ## [0.7.16] - 2018-12-11 diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 953f2911..386d0007 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -442,11 +442,23 @@ impl Middleware for Cors { .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); } } - AllOrSome::Some(_) => { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); + AllOrSome::Some(ref origins) => { + if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| { + match o.to_str() { + Ok(os) => origins.contains(os), + _ => false + } + }) { + resp.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + origin.clone(), + ); + } else { + resp.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + self.inner.origins_str.as_ref().unwrap().clone() + ); + }; } } @@ -1134,17 +1146,10 @@ mod tests { .to_str() .unwrap(); - if origins_str.starts_with("https://www.example.com") { - assert_eq!( - "https://www.example.com, https://www.google.com", - origins_str - ); - } else { - assert_eq!( - "https://www.google.com, https://www.example.com", - origins_str - ); - } + assert_eq!( + "https://www.example.com", + origins_str + ); } #[test] @@ -1180,4 +1185,43 @@ mod tests { let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::OK); } + + #[test] + fn test_multiple_origins() { + let cors = Cors::build() + .allowed_origin("https://example.com") + .allowed_origin("https://example.org") + .allowed_methods(vec![Method::GET]) + .finish(); + + + let req = TestRequest::with_header("Origin", "https://example.com") + .method(Method::GET) + .finish(); + let resp: HttpResponse = HttpResponse::Ok().into(); + + let resp = cors.response(&req, resp).unwrap().response(); + print!("{:?}", resp); + assert_eq!( + &b"https://example.com"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + + let req = TestRequest::with_header("Origin", "https://example.org") + .method(Method::GET) + .finish(); + let resp: HttpResponse = HttpResponse::Ok().into(); + + let resp = cors.response(&req, resp).unwrap().response(); + assert_eq!( + &b"https://example.org"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + } } From 037a1c6a24d916ac0f0eb02c40c6b4bb72a5d858 Mon Sep 17 00:00:00 2001 From: Douman Date: Mon, 24 Dec 2018 21:17:09 +0300 Subject: [PATCH 0869/1635] Bump min version of rustc Due to actix & trust-dns requirement --- CHANGES.md | 3 ++- README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 89c85b98..ed15d787 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.17] - 2018-12-23 +## [0.7.17] - 2018-12-xx ### Added @@ -11,6 +11,7 @@ ### Fixed * HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631 + * Access-Control-Allow-Origin header should only a return a single, matching origin. #603 ## [0.7.16] - 2018-12-11 diff --git a/README.md b/README.md index db3cc68c..c7e195de 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.26 or later +* Minimum supported Rust version: 1.31 or later ## Example From 799c6eb71991daef3e08732f33f3f4c98fde3b39 Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 25 Dec 2018 16:28:36 +0300 Subject: [PATCH 0870/1635] 0.7.17 Bump --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ed15d787..1c55f2ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.17] - 2018-12-xx +## [0.7.17] - 2018-12-25 ### Added From 61883042c2ff103b9fcf2aa94e66c0f3a50ef45c Mon Sep 17 00:00:00 2001 From: Ji Qu Date: Wed, 2 Jan 2019 18:24:08 +0800 Subject: [PATCH 0871/1635] Add with-cookie init-method for TestRequest (#647) --- CHANGES.md | 7 +++++++ src/test.rs | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 1c55f2ac..a07388d3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.7.18] - 2019-01-02 + +### Added + +* Add `with_cookie` for `TestRequest` to allow users to customize request cookie. #647 +* Add `cookie` method for `TestRequest` to allow users to add cookie dynamically. + ## [0.7.17] - 2018-12-25 ### Added diff --git a/src/test.rs b/src/test.rs index d543937c..7039c4fc 100644 --- a/src/test.rs +++ b/src/test.rs @@ -507,6 +507,11 @@ impl TestRequest<()> { { TestRequest::default().header(key, value) } + + /// Create TestRequest and set request cookie + pub fn with_cookie(cookie: Cookie<'static>) -> TestRequest<()> { + TestRequest::default().cookie(cookie) + } } impl TestRequest { @@ -543,6 +548,25 @@ impl TestRequest { self } + /// set cookie of this request + pub fn cookie(mut self, cookie: Cookie<'static>) -> Self { + if self.cookies.is_none() { + let mut should_insert = true; + let old_cookies = self.cookies.as_mut().unwrap(); + for old_cookie in old_cookies.iter() { + if old_cookie == &cookie { + should_insert = false + }; + }; + if should_insert { + old_cookies.push(cookie); + }; + } else { + self.cookies = Some(vec![cookie]); + }; + self + } + /// Set a header pub fn set(mut self, hdr: H) -> Self { if let Ok(value) = hdr.try_into() { From 55a2a59906c05f834fe1278fafb8a8dd5c746510 Mon Sep 17 00:00:00 2001 From: Juan Aguilar Date: Thu, 3 Jan 2019 20:34:18 +0100 Subject: [PATCH 0872/1635] Improve change askama_escape in favor of v_htmlescape (#651) --- Cargo.toml | 2 +- src/fs.rs | 7 ++++++- src/lib.rs | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 32ca147c..eb2e1cff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ cell = ["actix-net/cell"] actix = "0.7.9" actix-net = "0.2.6" -askama_escape = "0.1.0" +v_htmlescape = "0.3.2" base64 = "0.10" bitflags = "1.0" failure = "^0.1.2" diff --git a/src/fs.rs b/src/fs.rs index aec058aa..05024da8 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -11,7 +11,7 @@ use std::{cmp, io}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -use askama_escape::{escape as escape_html_entity}; +use v_htmlescape::HTMLEscape; use bytes::Bytes; use futures::{Async, Future, Poll, Stream}; use futures_cpupool::{CpuFuture, CpuPool}; @@ -569,6 +569,11 @@ macro_rules! encode_file_url { }; } +#[inline] +fn escape_html_entity(s: &str) -> HTMLEscape { + HTMLEscape::from(s) +} + // " -- " & -- & ' -- ' < -- < > -- > / -- / macro_rules! encode_file_name { ($entry:ident) => { diff --git a/src/lib.rs b/src/lib.rs index 21515051..3b00cda1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,7 +100,6 @@ extern crate failure; extern crate lazy_static; #[macro_use] extern crate futures; -extern crate askama_escape; extern crate cookie; extern crate futures_cpupool; extern crate http as modhttp; @@ -137,6 +136,7 @@ extern crate serde_urlencoded; extern crate percent_encoding; extern crate serde_json; extern crate smallvec; +extern crate v_htmlescape; extern crate actix_net; #[macro_use] From 4d45313f9d21c5b9835f5effc260c54a4507ce84 Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 8 Jan 2019 10:46:58 +0300 Subject: [PATCH 0873/1635] Decode special characters when handling static files --- src/fs.rs | 23 ++++++++++++++++++++++- tests/test space.binary | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/test space.binary diff --git a/src/fs.rs b/src/fs.rs index 05024da8..47bd81a7 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -761,7 +761,7 @@ impl StaticFiles { &self, req: &HttpRequest, ) -> Result, Error> { - let tail: String = req.match_info().query("tail")?; + let tail: String = req.match_info().get_decoded("tail").unwrap_or_else(|| "".to_string()); let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; // full filepath @@ -1303,6 +1303,27 @@ mod tests { assert_eq!(bytes, data); } + #[test] + fn test_static_files_with_spaces() { + let mut srv = test::TestServer::with_factory(|| { + App::new().handler( + "/", + StaticFiles::new(".").unwrap().index_file("Cargo.toml"), + ) + }); + let request = srv + .get() + .uri(srv.url("/tests/test%20space.binary")) + .finish() + .unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::OK); + + let bytes = srv.execute(response.body()).unwrap(); + let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); + assert_eq!(bytes, data); + } + #[derive(Default)] pub struct OnlyMethodHeadConfig; impl StaticFileConfig for OnlyMethodHeadConfig { diff --git a/tests/test space.binary b/tests/test space.binary new file mode 100644 index 00000000..ef8ff024 --- /dev/null +++ b/tests/test space.binary @@ -0,0 +1 @@ +ÂTÇ‘É‚Vù2þvI ª–\ÇRË™–ˆæeÞvDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.– û®õpA !ûߦÙx j+Uc÷±©X”c%Û;ï"yì­AI \ No newline at end of file From 4f2e9707325d02401bdeeb119b0ca12b91135469 Mon Sep 17 00:00:00 2001 From: Douman Date: Tue, 8 Jan 2019 10:49:03 +0300 Subject: [PATCH 0874/1635] Tidy up CHANGES.md --- CHANGES.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a07388d3..7c02acbf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,16 @@ # Changes -## [0.7.18] - 2019-01-02 +## [0.7.18] - 2019-xx-xx ### Added * Add `with_cookie` for `TestRequest` to allow users to customize request cookie. #647 -* Add `cookie` method for `TestRequest` to allow users to add cookie dynamically. + +* Add `cookie` method for `TestRequest` to allow users to add cookie dynamically. + +### Fixed + +* StaticFiles decode special characters in request's path ## [0.7.17] - 2018-12-25 From e5cdd22720b6273114f153c47b78a08ce0ef9063 Mon Sep 17 00:00:00 2001 From: Julian Tescher Date: Tue, 8 Jan 2019 10:42:22 -0800 Subject: [PATCH 0875/1635] Fix test server listener thread leak (#655) --- CHANGES.md | 2 ++ src/test.rs | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7c02acbf..84ee4b7c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ * StaticFiles decode special characters in request's path +* Fix test server listener leak #654 + ## [0.7.17] - 2018-12-25 ### Added diff --git a/src/test.rs b/src/test.rs index 7039c4fc..f2346115 100644 --- a/src/test.rs +++ b/src/test.rs @@ -5,7 +5,9 @@ use std::sync::mpsc; use std::{net, thread}; use actix::{Actor, Addr, System}; +use actix::actors::signal; +use actix_net::server::Server; use cookie::Cookie; use futures::Future; use http::header::HeaderName; @@ -66,6 +68,7 @@ pub struct TestServer { ssl: bool, conn: Addr, rt: Runtime, + backend: Addr, } impl TestServer { @@ -112,24 +115,25 @@ impl TestServer { let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let local_addr = tcp.local_addr().unwrap(); - let _ = HttpServer::new(factory) + let srv = HttpServer::new(factory) .disable_signals() .listen(tcp) .keep_alive(5) .start(); - tx.send((System::current(), local_addr, TestServer::get_conn())) + tx.send((System::current(), local_addr, TestServer::get_conn(), srv)) .unwrap(); sys.run(); }); - let (system, addr, conn) = rx.recv().unwrap(); + let (system, addr, conn, backend) = rx.recv().unwrap(); System::set_current(system); TestServer { addr, conn, ssl: false, rt: Runtime::new().unwrap(), + backend, } } @@ -197,6 +201,7 @@ impl TestServer { /// Stop http server fn stop(&mut self) { + let _ = self.backend.send(signal::Signal(signal::SignalType::Term)).wait(); System::current().stop(); } @@ -333,8 +338,7 @@ where .keep_alive(5) .disable_signals(); - tx.send((System::current(), addr, TestServer::get_conn())) - .unwrap(); + #[cfg(any(feature = "alpn", feature = "ssl"))] { @@ -356,18 +360,22 @@ where let tcp = net::TcpListener::bind(addr).unwrap(); srv = srv.listen(tcp); } - srv.start(); + let backend = srv.start(); + + tx.send((System::current(), addr, TestServer::get_conn(), backend)) + .unwrap(); sys.run(); }); - let (system, addr, conn) = rx.recv().unwrap(); + let (system, addr, conn, backend) = rx.recv().unwrap(); System::set_current(system); TestServer { addr, conn, ssl: has_ssl, rt: Runtime::new().unwrap(), + backend, } } } From 1fbb52ad3be39add2811f2a79be4dbf2fe63f68b Mon Sep 17 00:00:00 2001 From: Douman Date: Thu, 10 Jan 2019 17:05:18 +0300 Subject: [PATCH 0876/1635] 0.7.18 Bump --- CHANGES.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 84ee4b7c..e40bad5b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.7.18] - 2019-xx-xx +## [0.7.18] - 2019-01-10 ### Added diff --git a/Cargo.toml b/Cargo.toml index eb2e1cff..f927e24e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.17" +version = "0.7.18" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From d6df2e33999c01e5346705a51fc404479b9d4b4b Mon Sep 17 00:00:00 2001 From: Sameer Puri <11097096+sameer@users.noreply.github.com> Date: Thu, 10 Jan 2019 16:26:01 -0600 Subject: [PATCH 0877/1635] Fix HttpResponse doc spelling "os" to "of" --- src/httpresponse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 52dd8046..168e9bf6 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -246,7 +246,7 @@ impl HttpResponse { self } - /// Get body os this response + /// Get body of this response #[inline] pub fn body(&self) -> &Body { &self.get_ref().body From 3431fff4d7f2c39576f8c6070df09f169abf12a8 Mon Sep 17 00:00:00 2001 From: rishflab Date: Mon, 14 Jan 2019 14:13:37 +1100 Subject: [PATCH 0878/1635] Fixed example in client documentation. This closes #665. --- src/client/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 7696efa9..5321e4b0 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -5,9 +5,9 @@ //! # extern crate actix; //! # extern crate futures; //! # extern crate tokio; -//! # use futures::Future; //! # use std::process; //! use actix_web::client; +//! use futures::Future; //! //! fn main() { //! actix::run( @@ -66,9 +66,9 @@ impl ResponseError for SendRequestError { /// # extern crate futures; /// # extern crate tokio; /// # extern crate env_logger; -/// # use futures::Future; /// # use std::process; /// use actix_web::client; +/// use futures::Future; /// /// fn main() { /// actix::run( From a534fdd1257f5b5625fe6afdea356bdaf6ae76f0 Mon Sep 17 00:00:00 2001 From: Neil Jensen Date: Sat, 19 Jan 2019 10:41:48 -0700 Subject: [PATCH 0879/1635] Add io handling for ECONNRESET when data has already been received --- src/server/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/mod.rs b/src/server/mod.rs index b8498604..64129854 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -303,6 +303,8 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static { } else { Ok(Async::NotReady) } + } else if e.kind() == io::ErrorKind::ConnectionReset && read_some { + Ok(Async::Ready((read_some, true))) } else { Err(e) }; From f5bec968c754d7777fa69a1258c49f6f3ca07cbd Mon Sep 17 00:00:00 2001 From: Tomas Izquierdo Garcia-Faria Date: Fri, 25 Jan 2019 02:35:11 +0100 Subject: [PATCH 0880/1635] Bump v_htmlescape version to 0.4 --- Cargo.toml | 2 +- src/fs.rs | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f927e24e..bd3cb306 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ cell = ["actix-net/cell"] actix = "0.7.9" actix-net = "0.2.6" -v_htmlescape = "0.3.2" +v_htmlescape = "0.4" base64 = "0.10" bitflags = "1.0" failure = "^0.1.2" diff --git a/src/fs.rs b/src/fs.rs index 47bd81a7..b7370c64 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -11,7 +11,7 @@ use std::{cmp, io}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -use v_htmlescape::HTMLEscape; +use v_htmlescape::escape as escape_html_entity; use bytes::Bytes; use futures::{Async, Future, Poll, Stream}; use futures_cpupool::{CpuFuture, CpuPool}; @@ -569,11 +569,6 @@ macro_rules! encode_file_url { }; } -#[inline] -fn escape_html_entity(s: &str) -> HTMLEscape { - HTMLEscape::from(s) -} - // " -- " & -- & ' -- ' < -- < > -- > / -- / macro_rules! encode_file_name { ($entry:ident) => { From 42277c5c8f8a3db0fa1e0b445235154e4f87a9fc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 26 Jan 2019 22:09:26 -0800 Subject: [PATCH 0881/1635] update deps --- Cargo.toml | 10 +++++----- src/h1/decoder.rs | 5 ++--- src/ws/transport.rs | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5fc597a..c7622fe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.1.3" +actix-service = "0.1.6" actix-codec = "0.1.0" actix-connector = "0.1.0" actix-rt = "0.1.0" @@ -50,7 +50,7 @@ actix-utils = "0.1.0" # actix-server = { path="../actix-net/actix-server/" } # actix-utils = { path="../actix-net/actix-utils/" } -base64 = "0.9" +base64 = "0.10" backtrace = "0.3" bitflags = "1.0" bytes = "0.4" @@ -66,7 +66,7 @@ log = "0.4" mime = "0.3" net2 = "0.2" percent-encoding = "1.0" -rand = "0.5" +rand = "0.6" serde = "1.0" serde_json = "1.0" sha1 = "0.6" @@ -75,14 +75,14 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" -trust-dns-resolver = "0.10.1" +trust-dns-resolver = { version="0.11.0-alpha.1", default-features = false } # openssl openssl = { version="0.10", optional = true } [dev-dependencies] actix-web = "0.7" -env_logger = "0.5" +env_logger = "0.6" serde_derive = "1.0" [profile.release] diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index b2c410c4..7c17e909 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -102,7 +102,8 @@ pub(crate) trait MessageType: Sized { } // connection keep-alive state header::CONNECTION => { - ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { + ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) + { if conn.eq_ignore_ascii_case("keep-alive") { Some(ConnectionType::KeepAlive) } else if conn.eq_ignore_ascii_case("close") { @@ -925,7 +926,6 @@ mod tests { let req = parse_ready!(&mut buf); assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); - } #[test] @@ -986,7 +986,6 @@ mod tests { assert!(req.upgrade()); assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); - } #[test] diff --git a/src/ws/transport.rs b/src/ws/transport.rs index f59ad67a..6a4f4d22 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -7,7 +7,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service, + S: Service + 'static, T: AsyncRead + AsyncWrite, { inner: FramedTransport, From c3d3e8b465b7406484ee6f5d845426e033a15c60 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 27 Jan 2019 10:59:07 -0800 Subject: [PATCH 0882/1635] move TestServer to separate crate --- Cargo.toml | 21 ++-- src/client/pool.rs | 13 ++- src/config.rs | 11 +- src/httpmessage.rs | 5 +- src/json.rs | 2 +- src/lib.rs | 3 - src/test.rs | 227 +---------------------------------------- test-server/Cargo.toml | 64 ++++++++++++ test-server/src/lib.rs | 227 +++++++++++++++++++++++++++++++++++++++++ tests/test_client.rs | 3 +- tests/test_server.rs | 41 ++++---- tests/test_ws.rs | 5 +- 12 files changed, 345 insertions(+), 277 deletions(-) create mode 100644 test-server/Cargo.toml create mode 100644 test-server/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c7622fe2..42671220 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,15 +39,13 @@ ssl = ["openssl", "actix-connector/ssl"] [dependencies] actix-service = "0.1.6" actix-codec = "0.1.0" -actix-connector = "0.1.0" -actix-rt = "0.1.0" -actix-server = "0.1.0" -actix-utils = "0.1.0" +# actix-connector = "0.1.0" +# actix-utils = "0.1.0" +actix-connector = { git = "https://github.com/actix/actix-net.git" } +actix-utils = { git = "https://github.com/actix/actix-net.git" } # actix-codec = { path="../actix-net/actix-codec/" } # actix-connector = { path="../actix-net/actix-connector/" } -# actix-rt = { path="../actix-net/actix-rt/" } -# actix-server = { path="../actix-net/actix-server/" } # actix-utils = { path="../actix-net/actix-utils/" } base64 = "0.10" @@ -64,7 +62,6 @@ httparse = "1.3" indexmap = "1.0" log = "0.4" mime = "0.3" -net2 = "0.2" percent-encoding = "1.0" rand = "0.6" serde = "1.0" @@ -73,19 +70,17 @@ sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.5.3" time = "0.1" -tokio-tcp = "0.1" tokio-timer = "0.2" +tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.1", default-features = false } # openssl openssl = { version="0.10", optional = true } [dev-dependencies] +actix-rt = "0.1.0" actix-web = "0.7" +actix-server = "0.1" +actix-http-test = { path="test-server" } env_logger = "0.6" serde_derive = "1.0" - -[profile.release] -lto = true -opt-level = 3 -codegen-units = 1 diff --git a/src/client/pool.rs b/src/client/pool.rs index 94e96899..11828dcb 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -5,7 +5,6 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::spawn; use actix_service::Service; use futures::future::{ok, Either, FutureResult}; use futures::sync::oneshot; @@ -265,7 +264,7 @@ where inner: Rc>>, fut: F, ) { - spawn(OpenWaitingConnection { + tokio_current_thread::spawn(OpenWaitingConnection { key, fut, rx: Some(rx), @@ -408,7 +407,9 @@ where || (now - conn.created) > self.conn_lifetime { if let Some(timeout) = self.disconnect_timeout { - spawn(CloseConnection::new(conn.io, timeout)) + tokio_current_thread::spawn(CloseConnection::new( + conn.io, timeout, + )) } } else { let mut io = conn.io; @@ -417,7 +418,9 @@ where Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), Ok(n) if n > 0 => { if let Some(timeout) = self.disconnect_timeout { - spawn(CloseConnection::new(io, timeout)) + tokio_current_thread::spawn(CloseConnection::new( + io, timeout, + )) } continue; } @@ -433,7 +436,7 @@ where fn release_close(&mut self, io: Io) { self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { - spawn(CloseConnection::new(io, timeout)) + tokio_current_thread::spawn(CloseConnection::new(io, timeout)) } } diff --git a/src/config.rs b/src/config.rs index 67c928fb..c37601db 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,6 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt, net}; -use actix_rt::spawn; use bytes::BytesMut; use futures::{future, Future}; use log::error; @@ -355,10 +354,12 @@ impl DateService { // periodic date update let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.0.reset(); - future::ok(()) - })); + tokio_current_thread::spawn(sleep(Duration::from_millis(500)).then( + move |_| { + s.0.reset(); + future::ok(()) + }, + )); } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 373b7ed4..589617fc 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -567,14 +567,15 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::test::TestRequest; use encoding::all::ISO_8859_2; use encoding::Encoding; use futures::Async; use mime; use serde_derive::Deserialize; + use super::*; + use crate::test::TestRequest; + #[test] fn test_content_type() { let req = TestRequest::with_header("content-type", "text/plain").finish(); diff --git a/src/json.rs b/src/json.rs index bfecf0cc..d06449cb 100644 --- a/src/json.rs +++ b/src/json.rs @@ -133,12 +133,12 @@ impl Future for JsonBod #[cfg(test)] mod tests { - use super::*; use bytes::Bytes; use futures::Async; use http::header; use serde_derive::{Deserialize, Serialize}; + use super::*; use crate::test::TestRequest; impl PartialEq for JsonPayloadError { diff --git a/src/lib.rs b/src/lib.rs index 76c3a967..5adc9236 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,9 +59,6 @@ //! * `session` - enables session support, includes `ring` crate as //! dependency //! -// #![warn(missing_docs)] -#![allow(dead_code)] - pub mod body; pub mod client; mod config; diff --git a/src/test.rs b/src/test.rs index c264ac47..4b7e30ac 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,29 +1,14 @@ -//! Various helpers for Actix applications to use during testing. +//! Test Various helpers for Actix applications to use during testing. use std::str::FromStr; -use std::sync::mpsc; -use std::{net, thread}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_rt::{Runtime, System}; -use actix_server::{Server, StreamServiceFactory}; -use actix_service::Service; use bytes::Bytes; use cookie::Cookie; -use futures::future::{lazy, Future}; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use net2::TcpBuilder; -use crate::body::MessageBody; -use crate::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, - ConnectorError, SendRequestError, -}; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; -use crate::request::Request; -use crate::ws; +use crate::Request; /// Test `Request` builder /// @@ -264,211 +249,3 @@ impl TestRequest { // } // } } - -/// The `TestServer` type. -/// -/// `TestServer` is very simple test server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ```rust -/// # extern crate actix_web; -/// # use actix_web::*; -/// # -/// # fn my_handler(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() -/// # } -/// # -/// # fn main() { -/// use actix_web::test::TestServer; -/// -/// let mut srv = TestServer::new(|app| app.handler(my_handler)); -/// -/// let req = srv.get().finish().unwrap(); -/// let response = srv.execute(req.send()).unwrap(); -/// assert!(response.status().is_success()); -/// # } -/// ``` -pub struct TestServer; - -/// -pub struct TestServerRuntime { - addr: net::SocketAddr, - conn: T, - rt: Runtime, -} - -impl TestServer { - /// Start new test server with application factory - pub fn with_factory( - factory: F, - ) -> TestServerRuntime< - impl Service + Clone, - > { - let (tx, rx) = mpsc::channel(); - - // run server in separate thread - thread::spawn(move || { - let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - - Server::build() - .listen("test", tcp, factory) - .workers(1) - .disable_signals() - .start(); - - tx.send((System::current(), local_addr)).unwrap(); - sys.run(); - }); - - let (system, addr) = rx.recv().unwrap(); - System::set_current(system); - - let mut rt = Runtime::new().unwrap(); - let conn = rt - .block_on(lazy(|| Ok::<_, ()>(TestServer::new_connector()))) - .unwrap(); - - TestServerRuntime { addr, conn, rt } - } - - fn new_connector( - ) -> impl Service + Clone - { - #[cfg(feature = "ssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - Connector::default().ssl(builder.build()).service() - } - #[cfg(not(feature = "ssl"))] - { - Connector::default().service() - } - } - - /// Get firat available unused address - pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = TcpBuilder::new_v4().unwrap(); - socket.bind(&addr).unwrap(); - socket.reuse_address(true).unwrap(); - let tcp = socket.to_tcp_listener().unwrap(); - tcp.local_addr().unwrap() - } -} - -impl TestServerRuntime { - /// Execute future on current core - pub fn block_on(&mut self, fut: F) -> Result - where - F: Future, - { - self.rt.block_on(fut) - } - - /// Execute future on current core - pub fn execute(&mut self, fut: F) -> Result - where - F: Future, - { - self.rt.block_on(fut) - } - - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - if uri.starts_with('/') { - format!("http://127.0.0.1:{}{}", self.addr.port(), uri) - } else { - format!("http://127.0.0.1:{}/{}", self.addr.port(), uri) - } - } - - /// Create `GET` request - pub fn get(&self) -> ClientRequestBuilder { - ClientRequest::get(self.url("/").as_str()) - } - - /// Create `POST` request - pub fn post(&self) -> ClientRequestBuilder { - ClientRequest::post(self.url("/").as_str()) - } - - /// Create `HEAD` request - pub fn head(&self) -> ClientRequestBuilder { - ClientRequest::head(self.url("/").as_str()) - } - - /// Connect to test http server - pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - ClientRequest::build() - .method(meth) - .uri(self.url(path).as_str()) - .take() - } - - /// Http connector - pub fn connector(&mut self) -> &mut T { - &mut self.conn - } - - /// Http connector - pub fn new_connector(&mut self) -> T - where - T: Clone, - { - self.conn.clone() - } - - /// Stop http server - fn stop(&mut self) { - System::current().stop(); - } -} - -impl TestServerRuntime -where - T: Service + Clone, - T::Response: Connection, -{ - /// Connect to websocket server at a given path - pub fn ws_at( - &mut self, - path: &str, - ) -> Result, ws::ClientError> { - let url = self.url(path); - self.rt - .block_on(lazy(|| ws::Client::default().call(ws::Connect::new(url)))) - } - - /// Connect to a websocket server - pub fn ws( - &mut self, - ) -> Result, ws::ClientError> { - self.ws_at("/") - } - - /// Send request and read response message - pub fn send_request( - &mut self, - req: ClientRequest, - ) -> Result { - self.rt.block_on(req.send(&mut self.conn)) - } -} - -impl Drop for TestServerRuntime { - fn drop(&mut self) { - self.stop() - } -} diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml new file mode 100644 index 00000000..5cede2dc --- /dev/null +++ b/test-server/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "actix-http-test" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Actix http" +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://actix.rs/api/actix-web/stable/actix_web/" +categories = ["network-programming", "asynchronous", + "web-programming::http-server", + "web-programming::websocket"] +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +edition = "2018" + +[package.metadata.docs.rs] +features = ["session"] + +[lib] +name = "actix_http_test" +path = "src/lib.rs" + +[features] +default = ["session"] + +# sessions feature, session require "ring" crate and c compiler +session = ["cookie/secure"] + +# openssl +ssl = ["openssl", "actix-http/ssl"] + +[dependencies] +actix-codec = "0.1" +actix-service = "0.1.6" +actix-rt = "0.1.0" +actix-server = "0.1.0" +actix-http = { path=".." } +actix-utils = { git = "https://github.com/actix/actix-net.git" } + +# actix-codec = { path="../actix-net/actix-codec/" } +# actix-rt = { path="../actix-net/actix-rt/" } +# actix-server = { path="../actix-net/actix-server/" } + +base64 = "0.10" +bytes = "0.4" +cookie = { version="0.11", features=["percent-encode"] } +futures = "0.1" +http = "0.1.8" +log = "0.4" +env_logger = "0.6" +net2 = "0.2" +serde = "1.0" +serde_json = "1.0" +sha1 = "0.6" +slab = "0.4" +serde_urlencoded = "0.5.3" +time = "0.1" +tokio-tcp = "0.1" +tokio-timer = "0.2" + +# openssl +openssl = { version="0.10", optional = true } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs new file mode 100644 index 00000000..36c0d7d5 --- /dev/null +++ b/test-server/src/lib.rs @@ -0,0 +1,227 @@ +//! Various helpers for Actix applications to use during testing. +use std::sync::mpsc; +use std::{net, thread}; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_rt::{Runtime, System}; +use actix_server::{Server, StreamServiceFactory}; +use actix_service::Service; + +use futures::future::{lazy, Future}; +use http::Method; +use net2::TcpBuilder; + +use actix_http::body::MessageBody; +use actix_http::client::{ + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, + ConnectorError, SendRequestError, +}; +use actix_http::ws; + +/// The `TestServer` type. +/// +/// `TestServer` is very simple test server that simplify process of writing +/// integration tests cases for actix web applications. +/// +/// # Examples +/// +/// ```rust +/// # extern crate actix_web; +/// # use actix_web::*; +/// # +/// # fn my_handler(req: &HttpRequest) -> HttpResponse { +/// # HttpResponse::Ok().into() +/// # } +/// # +/// # fn main() { +/// use actix_web::test::TestServer; +/// +/// let mut srv = TestServer::new(|app| app.handler(my_handler)); +/// +/// let req = srv.get().finish().unwrap(); +/// let response = srv.execute(req.send()).unwrap(); +/// assert!(response.status().is_success()); +/// # } +/// ``` +pub struct TestServer; + +/// +pub struct TestServerRuntime { + addr: net::SocketAddr, + conn: T, + rt: Runtime, +} + +impl TestServer { + /// Start new test server with application factory + pub fn with_factory( + factory: F, + ) -> TestServerRuntime< + impl Service + Clone, + > { + let (tx, rx) = mpsc::channel(); + + // run server in separate thread + thread::spawn(move || { + let sys = System::new("actix-test-server"); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + + Server::build() + .listen("test", tcp, factory) + .workers(1) + .disable_signals() + .start(); + + tx.send((System::current(), local_addr)).unwrap(); + sys.run(); + }); + + let (system, addr) = rx.recv().unwrap(); + System::set_current(system); + + let mut rt = Runtime::new().unwrap(); + let conn = rt + .block_on(lazy(|| Ok::<_, ()>(TestServer::new_connector()))) + .unwrap(); + + TestServerRuntime { addr, conn, rt } + } + + fn new_connector( + ) -> impl Service + Clone + { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + Connector::default().ssl(builder.build()).service() + } + #[cfg(not(feature = "ssl"))] + { + Connector::default().service() + } + } + + /// Get firat available unused address + pub fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = TcpBuilder::new_v4().unwrap(); + socket.bind(&addr).unwrap(); + socket.reuse_address(true).unwrap(); + let tcp = socket.to_tcp_listener().unwrap(); + tcp.local_addr().unwrap() + } +} + +impl TestServerRuntime { + /// Execute future on current core + pub fn block_on(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + + /// Execute future on current core + pub fn execute(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + + /// Construct test server url + pub fn addr(&self) -> net::SocketAddr { + self.addr + } + + /// Construct test server url + pub fn url(&self, uri: &str) -> String { + if uri.starts_with('/') { + format!("http://127.0.0.1:{}{}", self.addr.port(), uri) + } else { + format!("http://127.0.0.1:{}/{}", self.addr.port(), uri) + } + } + + /// Create `GET` request + pub fn get(&self) -> ClientRequestBuilder { + ClientRequest::get(self.url("/").as_str()) + } + + /// Create `POST` request + pub fn post(&self) -> ClientRequestBuilder { + ClientRequest::post(self.url("/").as_str()) + } + + /// Create `HEAD` request + pub fn head(&self) -> ClientRequestBuilder { + ClientRequest::head(self.url("/").as_str()) + } + + /// Connect to test http server + pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { + ClientRequest::build() + .method(meth) + .uri(self.url(path).as_str()) + .take() + } + + /// Http connector + pub fn connector(&mut self) -> &mut T { + &mut self.conn + } + + /// Http connector + pub fn new_connector(&mut self) -> T + where + T: Clone, + { + self.conn.clone() + } + + /// Stop http server + fn stop(&mut self) { + System::current().stop(); + } +} + +impl TestServerRuntime +where + T: Service + Clone, + T::Response: Connection, +{ + /// Connect to websocket server at a given path + pub fn ws_at( + &mut self, + path: &str, + ) -> Result, ws::ClientError> { + let url = self.url(path); + self.rt + .block_on(lazy(|| ws::Client::default().call(ws::Connect::new(url)))) + } + + /// Connect to a websocket server + pub fn ws( + &mut self, + ) -> Result, ws::ClientError> { + self.ws_at("/") + } + + /// Send request and read response message + pub fn send_request( + &mut self, + req: ClientRequest, + ) -> Result { + self.rt.block_on(req.send(&mut self.conn)) + } +} + +impl Drop for TestServerRuntime { + fn drop(&mut self) { + self.stop() + } +} diff --git a/tests/test_client.rs b/tests/test_client.rs index f19edda1..606bac22 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -3,7 +3,8 @@ use bytes::Bytes; use futures::future::{self, ok}; use actix_http::HttpMessage; -use actix_http::{client, h1, test::TestServer, Request, Response}; +use actix_http::{client, h1, Request, Response}; +use actix_http_test::TestServer; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ diff --git a/tests/test_server.rs b/tests/test_server.rs index cb9cd3f9..c23840ea 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -2,19 +2,20 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; +use actix_http_test::TestServer; use actix_service::NewService; use bytes::Bytes; use futures::future::{self, ok}; use futures::stream::once; use actix_http::{ - body, client, h1, http, test, Body, Error, HttpMessage as HttpMessage2, KeepAlive, + body, client, h1, http, Body, Error, HttpMessage as HttpMessage2, KeepAlive, Request, Response, }; #[test] fn test_h1_v2() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) @@ -31,7 +32,7 @@ fn test_h1_v2() { #[test] fn test_slow_request() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .client_timeout(100) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -47,7 +48,7 @@ fn test_slow_request() { #[test] fn test_malformed_request() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())).map(|_| ()) }); @@ -60,7 +61,7 @@ fn test_malformed_request() { #[test] fn test_keepalive() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -80,7 +81,7 @@ fn test_keepalive() { #[test] fn test_keepalive_timeout() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .keep_alive(1) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -101,7 +102,7 @@ fn test_keepalive_timeout() { #[test] fn test_keepalive_close() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -121,7 +122,7 @@ fn test_keepalive_close() { #[test] fn test_keepalive_http10_default_close() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -140,7 +141,7 @@ fn test_keepalive_http10_default_close() { #[test] fn test_keepalive_http10() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -166,7 +167,7 @@ fn test_keepalive_http10() { #[test] fn test_keepalive_disabled() { - let srv = test::TestServer::with_factory(|| { + let srv = TestServer::with_factory(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -191,7 +192,7 @@ fn test_content_length() { StatusCode, }; - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ @@ -240,7 +241,7 @@ fn test_headers() { let data = STR.repeat(10); let data2 = data.clone(); - let mut srv = test::TestServer::with_factory(move || { + let mut srv = TestServer::with_factory(move || { let data = data.clone(); h1::H1Service::new(move |_| { let mut builder = Response::Ok(); @@ -302,7 +303,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -317,7 +318,7 @@ fn test_body() { #[test] fn test_head_empty() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -340,7 +341,7 @@ fn test_head_empty() { #[test] fn test_head_binary() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) }) @@ -366,7 +367,7 @@ fn test_head_binary() { #[test] fn test_head_binary2() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -385,7 +386,7 @@ fn test_head_binary2() { #[test] fn test_body_length() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( @@ -407,7 +408,7 @@ fn test_body_length() { #[test] fn test_body_chunked_explicit() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) @@ -428,7 +429,7 @@ fn test_body_chunked_explicit() { #[test] fn test_body_chunked_implicit() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) @@ -447,7 +448,7 @@ fn test_body_chunked_implicit() { #[test] fn test_response_http_error_handling() { - let mut srv = test::TestServer::with_factory(|| { + let mut srv = TestServer::with_factory(|| { h1::H1Service::new(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 11a3f472..07f857d7 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -1,6 +1,7 @@ use std::io; use actix_codec::Framed; +use actix_http_test::TestServer; use actix_service::NewService; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; @@ -9,7 +10,7 @@ use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Either}; use futures::{Future, Sink, Stream}; -use actix_http::{h1, test, ws, ResponseError, SendResponse, ServiceConfig}; +use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -34,7 +35,7 @@ fn ws_service(req: ws::Frame) -> impl Future)| { From 12fb94204f4eb8923d4830c007923539d1a6b8f5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 27 Jan 2019 11:40:26 -0800 Subject: [PATCH 0883/1635] use hashbrown instead of std HashMap --- Cargo.toml | 6 ++---- src/client/pool.rs | 5 +++-- src/extensions.rs | 31 ++----------------------------- test-server/Cargo.toml | 10 ---------- 4 files changed, 7 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 42671220..b3adfa82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,10 +44,6 @@ actix-codec = "0.1.0" actix-connector = { git = "https://github.com/actix/actix-net.git" } actix-utils = { git = "https://github.com/actix/actix-net.git" } -# actix-codec = { path="../actix-net/actix-codec/" } -# actix-connector = { path="../actix-net/actix-connector/" } -# actix-utils = { path="../actix-net/actix-utils/" } - base64 = "0.10" backtrace = "0.3" bitflags = "1.0" @@ -57,6 +53,8 @@ cookie = { version="0.11", features=["percent-encode"] } derive_more = "0.13" encoding = "0.2" futures = "0.1" +hashbrown = "0.1.8" +h2 = "0.1.16" http = "0.1.8" httparse = "1.3" indexmap = "1.0" diff --git a/src/client/pool.rs b/src/client/pool.rs index 11828dcb..b577587d 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -1,5 +1,5 @@ use std::cell::RefCell; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::io; use std::rc::Rc; use std::time::{Duration, Instant}; @@ -7,9 +7,10 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; use futures::future::{ok, Either, FutureResult}; -use futures::sync::oneshot; use futures::task::AtomicTask; +use futures::unsync::oneshot; use futures::{Async, Future, Poll}; +use hashbrown::HashMap; use http::uri::Authority; use indexmap::IndexSet; use slab::Slab; diff --git a/src/extensions.rs b/src/extensions.rs index 430b87bd..7bb965c9 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,40 +1,13 @@ use std::any::{Any, TypeId}; -use std::collections::HashMap; use std::fmt; use std::hash::{BuildHasherDefault, Hasher}; -struct IdHasher { - id: u64, -} - -impl Default for IdHasher { - fn default() -> IdHasher { - IdHasher { id: 0 } - } -} - -impl Hasher for IdHasher { - fn write(&mut self, bytes: &[u8]) { - for &x in bytes { - self.id.wrapping_add(u64::from(x)); - } - } - - fn write_u64(&mut self, u: u64) { - self.id = u; - } - - fn finish(&self) -> u64 { - self.id - } -} - -type AnyMap = HashMap, BuildHasherDefault>; +use hashbrown::HashMap; #[derive(Default)] /// A type map of request extensions. pub struct Extensions { - map: AnyMap, + map: HashMap>, } impl Extensions { diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 5cede2dc..851b3efe 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -28,9 +28,6 @@ default = ["session"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] -# openssl -ssl = ["openssl", "actix-http/ssl"] - [dependencies] actix-codec = "0.1" actix-service = "0.1.6" @@ -39,10 +36,6 @@ actix-server = "0.1.0" actix-http = { path=".." } actix-utils = { git = "https://github.com/actix/actix-net.git" } -# actix-codec = { path="../actix-net/actix-codec/" } -# actix-rt = { path="../actix-net/actix-rt/" } -# actix-server = { path="../actix-net/actix-server/" } - base64 = "0.10" bytes = "0.4" cookie = { version="0.11", features=["percent-encode"] } @@ -59,6 +52,3 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" - -# openssl -openssl = { version="0.10", optional = true } From 9968afe4a6512611497ca57e5ab2fbd5fdbdfac2 Mon Sep 17 00:00:00 2001 From: wildarch Date: Mon, 28 Jan 2019 06:07:28 +0100 Subject: [PATCH 0884/1635] Use NamedFile with an existing File (#670) --- CHANGES.md | 6 ++++++ src/fs.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e40bad5b..83803abb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [x.x.xx] - xxxx-xx-xx + +### Added + +* Add `from_file` and `from_file_with_config` to `NamedFile` to allow sending files without a known path. #670 + ## [0.7.18] - 2019-01-10 ### Added diff --git a/src/fs.rs b/src/fs.rs index b7370c64..04ababd0 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -120,6 +120,32 @@ pub struct NamedFile { } impl NamedFile { + /// Creates an instance from a previously opened file. + /// + /// The given `path` need not exist and is only used to determine the `ContentType` and + /// `ContentDisposition` headers. + /// + /// # Examples + /// + /// ```no_run + /// extern crate actix_web; + /// + /// use actix_web::fs::NamedFile; + /// use std::io::{self, Write}; + /// use std::env; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::create("foo.txt")?; + /// file.write_all(b"Hello, world!")?; + /// let named_file = NamedFile::from_file(file, "bar.txt")?; + /// Ok(()) + /// } + /// ``` + pub fn from_file>(file: File, path: P) -> io::Result { + Self::from_file_with_config(file, path, DefaultConfig) + } + /// Attempts to open a file in read-only mode. /// /// # Examples @@ -135,16 +161,29 @@ impl NamedFile { } impl NamedFile { - /// Attempts to open a file in read-only mode using provided configiration. + /// Creates an instance from a previously opened file using the provided configuration. + /// + /// The given `path` need not exist and is only used to determine the `ContentType` and + /// `ContentDisposition` headers. /// /// # Examples /// - /// ```rust - /// use actix_web::fs::{DefaultConfig, NamedFile}; + /// ```no_run + /// extern crate actix_web; /// - /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); + /// use actix_web::fs::{DefaultConfig, NamedFile}; + /// use std::io::{self, Write}; + /// use std::env; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::create("foo.txt")?; + /// file.write_all(b"Hello, world!")?; + /// let named_file = NamedFile::from_file_with_config(file, "bar.txt", DefaultConfig)?; + /// Ok(()) + /// } /// ``` - pub fn open_with_config>(path: P, _: C) -> io::Result> { + pub fn from_file_with_config>(file: File, path: P, _: C) -> io::Result> { let path = path.as_ref().to_path_buf(); // Get the name of the file and use it to construct default Content-Type @@ -169,7 +208,6 @@ impl NamedFile { (ct, cd) }; - let file = File::open(&path)?; let md = file.metadata()?; let modified = md.modified().ok(); let cpu_pool = None; @@ -188,6 +226,19 @@ impl NamedFile { }) } + /// Attempts to open a file in read-only mode using provided configuration. + /// + /// # Examples + /// + /// ```rust + /// use actix_web::fs::{DefaultConfig, NamedFile}; + /// + /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); + /// ``` + pub fn open_with_config>(path: P, config: C) -> io::Result> { + Self::from_file_with_config(File::open(&path)?, path, config) + } + /// Returns reference to the underlying `File` object. #[inline] pub fn file(&self) -> &File { From 4a388d7ad97ab85a1e42847acca73d9e8ab9baa4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 28 Jan 2019 20:41:09 -0800 Subject: [PATCH 0885/1635] add client http/2 support --- examples/client.rs | 33 +++ src/client/connection.rs | 138 +++++---- src/client/connector.rs | 229 ++++++-------- src/client/error.rs | 24 +- src/client/{pipeline.rs => h1proto.rs} | 106 +++++-- src/client/h2proto.rs | 151 ++++++++++ src/client/mod.rs | 5 +- src/client/pool.rs | 394 +++++++++++++++---------- src/client/request.rs | 17 +- src/client/response.rs | 2 +- src/error.rs | 5 +- src/extensions.rs | 1 - src/lib.rs | 3 + src/response.rs | 1 + test-server/src/lib.rs | 12 +- 15 files changed, 719 insertions(+), 402 deletions(-) create mode 100644 examples/client.rs rename src/client/{pipeline.rs => h1proto.rs} (67%) create mode 100644 src/client/h2proto.rs diff --git a/examples/client.rs b/examples/client.rs new file mode 100644 index 00000000..06b708e2 --- /dev/null +++ b/examples/client.rs @@ -0,0 +1,33 @@ +use actix_http::{client, Error}; +use actix_rt::System; +use bytes::BytesMut; +use futures::{future::lazy, Future, Stream}; + +fn main() -> Result<(), Error> { + std::env::set_var("RUST_LOG", "actix_http=trace"); + env_logger::init(); + + System::new("test").block_on(lazy(|| { + let mut connector = client::Connector::default().service(); + + client::ClientRequest::get("https://www.rust-lang.org/") // <- Create request builder + .header("User-Agent", "Actix-web") + .finish() + .unwrap() + .send(&mut connector) // <- Send http request + .from_err() + .and_then(|response| { + // <- server http response + println!("Response: {:?}", response); + + // read response body + response + .from_err() + .fold(BytesMut::new(), move |mut acc, chunk| { + acc.extend_from_slice(&chunk); + Ok::<_, Error>(acc) + }) + .map(|body| println!("Downloaded: {:?} bytes", body.len())) + }) + })) +} diff --git a/src/client/connection.rs b/src/client/connection.rs index ed156bf8..b192caae 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -1,11 +1,35 @@ -use std::{fmt, io, time}; +use std::{fmt, time}; use actix_codec::{AsyncRead, AsyncWrite}; -use futures::Poll; +use bytes::Bytes; +use futures::Future; +use h2::client::SendRequest; +use crate::body::MessageBody; +use crate::message::RequestHead; + +use super::error::SendRequestError; use super::pool::Acquired; +use super::response::ClientResponse; +use super::{h1proto, h2proto}; -pub trait Connection: AsyncRead + AsyncWrite + 'static { +pub(crate) enum ConnectionType { + H1(Io), + H2(SendRequest), +} + +pub trait RequestSender { + type Future: Future; + + /// Close connection + fn send_request( + self, + head: RequestHead, + body: B, + ) -> Self::Future; +} + +pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { /// Close connection fn close(&mut self); @@ -16,7 +40,7 @@ pub trait Connection: AsyncRead + AsyncWrite + 'static { #[doc(hidden)] /// HTTP client connection pub struct IoConnection { - io: Option, + io: Option>, created: time::Instant, pool: Option>, } @@ -26,77 +50,83 @@ where T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Connection {:?}", self.io) + match self.io { + Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io), + Some(ConnectionType::H2(_)) => write!(f, "H2Connection"), + None => write!(f, "Connection(Empty)"), + } } } impl IoConnection { - pub(crate) fn new(io: T, created: time::Instant, pool: Acquired) -> Self { + pub(crate) fn new( + io: ConnectionType, + created: time::Instant, + pool: Option>, + ) -> Self { IoConnection { + pool, created, io: Some(io), - pool: Some(pool), } } - /// Raw IO stream - pub fn get_mut(&mut self) -> &mut T { - self.io.as_mut().unwrap() - } - - pub(crate) fn into_inner(self) -> (T, time::Instant) { + pub(crate) fn into_inner(self) -> (ConnectionType, time::Instant) { (self.io.unwrap(), self.created) } } -impl Connection for IoConnection { - /// Close connection - fn close(&mut self) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.close(IoConnection { - io: Some(io), - created: self.created, - pool: None, - }) - } - } - } +impl RequestSender for IoConnection +where + T: AsyncRead + AsyncWrite + 'static, +{ + type Future = Box>; - /// Release this connection to the connection pool - fn release(&mut self) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.release(IoConnection { - io: Some(io), - created: self.created, - pool: None, - }) - } + fn send_request( + mut self, + head: RequestHead, + body: B, + ) -> Self::Future { + match self.io.take().unwrap() { + ConnectionType::H1(io) => Box::new(h1proto::send_request( + io, + head, + body, + self.created, + self.pool, + )), + ConnectionType::H2(io) => Box::new(h2proto::send_request( + io, + head, + body, + self.created, + self.pool, + )), } } } -impl io::Read for IoConnection { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.as_mut().unwrap().read(buf) - } +#[allow(dead_code)] +pub(crate) enum EitherConnection { + A(IoConnection), + B(IoConnection), } -impl AsyncRead for IoConnection {} +impl RequestSender for EitherConnection +where + A: AsyncRead + AsyncWrite + 'static, + B: AsyncRead + AsyncWrite + 'static, +{ + type Future = Box>; -impl io::Write for IoConnection { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.as_mut().unwrap().write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.io.as_mut().unwrap().flush() - } -} - -impl AsyncWrite for IoConnection { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.as_mut().unwrap().shutdown() + fn send_request( + self, + head: RequestHead, + body: RB, + ) -> Self::Future { + match self { + EitherConnection::A(con) => con.send_request(head, body), + EitherConnection::B(con) => con.send_request(head, body), + } } } diff --git a/src/client/connector.rs b/src/client/connector.rs index 05caf1ed..b573181b 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,23 +1,22 @@ use std::time::Duration; -use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connector::{Resolver, TcpConnector}; use actix_service::{Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; -use futures::future::Either; -use futures::Poll; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use super::connect::Connect; -use super::connection::{Connection, IoConnection}; +use super::connection::RequestSender; use super::error::ConnectorError; -use super::pool::ConnectionPool; +use super::pool::{ConnectionPool, Protocol}; #[cfg(feature = "ssl")] use actix_connector::ssl::OpensslConnector; #[cfg(feature = "ssl")] use openssl::ssl::{SslConnector, SslMethod}; +#[cfg(feature = "ssl")] +const H2: &[u8] = b"h2"; #[cfg(not(feature = "ssl"))] type SslConnector = (); @@ -40,7 +39,12 @@ impl Default for Connector { let connector = { #[cfg(feature = "ssl")] { - SslConnector::builder(SslMethod::tls()).unwrap().build() + use log::error; + let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); + let _ = ssl + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| error!("Can not set alpn protocol: {:?}", e)); + ssl.build() } #[cfg(not(feature = "ssl"))] { @@ -133,15 +137,17 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( self.timeout, - self.resolver - .map_err(ConnectorError::from) - .and_then(TcpConnector::default().from_err()), + self.resolver.map_err(ConnectorError::from).and_then( + TcpConnector::default() + .from_err() + .map(|(msg, io)| (msg, io, Protocol::Http1)), + ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -168,7 +174,20 @@ impl Connector { .and_then(TcpConnector::default().from_err()) .and_then( OpensslConnector::service(self.connector) - .map_err(ConnectorError::from), + .map_err(ConnectorError::from) + .map(|(msg, io)| { + let h2 = io + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (msg, io, Protocol::Http2) + } else { + (msg, io, Protocol::Http1) + } + }), ), ) .map_err(|e| match e { @@ -178,9 +197,11 @@ impl Connector { let tcp_service = TimeoutService::new( self.timeout, - self.resolver - .map_err(ConnectorError::from) - .and_then(TcpConnector::default().from_err()), + self.resolver.map_err(ConnectorError::from).and_then( + TcpConnector::default() + .from_err() + .map(|(msg, io)| (msg, io, Protocol::Http1)), + ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -209,13 +230,16 @@ impl Connector { #[cfg(not(feature = "ssl"))] mod connect_impl { + use futures::future::{err, Either, FutureResult}; + use futures::Poll; + use super::*; - use futures::future::{err, FutureResult}; + use crate::client::connection::IoConnection; pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -223,7 +247,8 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service + Clone, + T: Service + + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -235,7 +260,7 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { type Response = IoConnection; type Error = ConnectorError; @@ -264,17 +289,26 @@ mod connect_impl { mod connect_impl { use std::marker::PhantomData; - use futures::future::{err, FutureResult}; + use futures::future::{err, Either, FutureResult}; use futures::{Async, Future, Poll}; use super::*; + use crate::client::connection::EitherConnection; pub(crate) struct InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service< + Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, + T2: Service< + Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -284,8 +318,16 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service + Clone, - T2: Service + Clone, + T1: Service< + Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + > + Clone, + T2: Service< + Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + > + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -299,10 +341,18 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service< + Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, + T2: Service< + Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, { - type Response = IoEither, IoConnection>; + type Response = EitherConnection; type Error = ConnectorError; type Future = Either< FutureResult, @@ -336,7 +386,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -344,17 +394,17 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { - type Item = IoEither, IoConnection>; + type Item = EitherConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { match self.fut.poll()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(res) => Ok(Async::Ready(IoEither::A(res))), + Async::Ready(res) => Ok(Async::Ready(EitherConnection::A(res))), } } } @@ -362,7 +412,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -370,129 +420,18 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { - type Item = IoEither, IoConnection>; + type Item = EitherConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { match self.fut.poll()? { Async::NotReady => Ok(Async::NotReady), - Async::Ready(res) => Ok(Async::Ready(IoEither::B(res))), + Async::Ready(res) => Ok(Async::Ready(EitherConnection::B(res))), } } } } - -pub(crate) enum IoEither { - A(Io1), - B(Io2), -} - -impl Connection for IoEither -where - Io1: Connection, - Io2: Connection, -{ - fn close(&mut self) { - match self { - IoEither::A(ref mut io) => io.close(), - IoEither::B(ref mut io) => io.close(), - } - } - - fn release(&mut self) { - match self { - IoEither::A(ref mut io) => io.release(), - IoEither::B(ref mut io) => io.release(), - } - } -} - -impl io::Read for IoEither -where - Io1: Connection, - Io2: Connection, -{ - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - IoEither::A(ref mut io) => io.read(buf), - IoEither::B(ref mut io) => io.read(buf), - } - } -} - -impl AsyncRead for IoEither -where - Io1: Connection, - Io2: Connection, -{ - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - match self { - IoEither::A(ref io) => io.prepare_uninitialized_buffer(buf), - IoEither::B(ref io) => io.prepare_uninitialized_buffer(buf), - } - } -} - -impl AsyncWrite for IoEither -where - Io1: Connection, - Io2: Connection, -{ - fn shutdown(&mut self) -> Poll<(), io::Error> { - match self { - IoEither::A(ref mut io) => io.shutdown(), - IoEither::B(ref mut io) => io.shutdown(), - } - } - - fn poll_write(&mut self, buf: &[u8]) -> Poll { - match self { - IoEither::A(ref mut io) => io.poll_write(buf), - IoEither::B(ref mut io) => io.poll_write(buf), - } - } - - fn poll_flush(&mut self) -> Poll<(), io::Error> { - match self { - IoEither::A(ref mut io) => io.poll_flush(), - IoEither::B(ref mut io) => io.poll_flush(), - } - } -} - -impl io::Write for IoEither -where - Io1: Connection, - Io2: Connection, -{ - fn flush(&mut self) -> io::Result<()> { - match self { - IoEither::A(ref mut io) => io.flush(), - IoEither::B(ref mut io) => io.flush(), - } - } - - fn write(&mut self, buf: &[u8]) -> io::Result { - match self { - IoEither::A(ref mut io) => io.write(buf), - IoEither::B(ref mut io) => io.write(buf), - } - } -} - -impl fmt::Debug for IoEither -where - Io1: fmt::Debug, - Io2: fmt::Debug, -{ - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self { - IoEither::A(ref io) => io.fmt(fmt), - IoEither::B(ref io) => io.fmt(fmt), - } - } -} diff --git a/src/client/error.rs b/src/client/error.rs index 2a5df9c9..e27a83d8 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -6,7 +6,8 @@ use trust_dns_resolver::error::ResolveError; #[cfg(feature = "ssl")] use openssl::ssl::{Error as SslError, HandshakeError}; -use crate::error::{Error, ParseError}; +use crate::error::{Error, ParseError, ResponseError}; +use crate::response::Response; /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] @@ -32,6 +33,10 @@ pub enum ConnectorError { #[display(fmt = "No dns records found for the input")] NoRecords, + /// Http2 error + #[display(fmt = "{}", _0)] + H2(h2::Error), + /// Connecting took too long #[display(fmt = "Timeout out while establishing connection")] Timeout, @@ -80,6 +85,23 @@ pub enum SendRequestError { Send(io::Error), /// Error parsing response Response(ParseError), + /// Http2 error + #[display(fmt = "{}", _0)] + H2(h2::Error), /// Error sending request body Body(Error), } + +/// Convert `SendRequestError` to a server `Response` +impl ResponseError for SendRequestError { + fn error_response(&self) -> Response { + match *self { + SendRequestError::Connector(ConnectorError::Timeout) => { + Response::GatewayTimeout() + } + SendRequestError::Connector(_) => Response::BadGateway(), + _ => Response::InternalServerError(), + } + .into() + } +} diff --git a/src/client/pipeline.rs b/src/client/h1proto.rs similarity index 67% rename from src/client/pipeline.rs rename to src/client/h1proto.rs index 8d946d64..ed3c66d6 100644 --- a/src/client/pipeline.rs +++ b/src/client/h1proto.rs @@ -1,38 +1,42 @@ -use std::collections::VecDeque; +use std::{io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_service::Service; use bytes::Bytes; use futures::future::{err, ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; +use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectorError, SendRequestError}; +use super::pool::Acquired; use super::response::ClientResponse; -use super::{Connect, Connection}; use crate::body::{BodyLength, MessageBody, PayloadStream}; use crate::error::PayloadError; use crate::h1; use crate::message::RequestHead; -pub(crate) fn send_request( +pub(crate) fn send_request( + io: T, head: RequestHead, body: B, - connector: &mut T, + created: time::Instant, + pool: Option>, ) -> impl Future where - T: Service, + T: AsyncRead + AsyncWrite + 'static, B: MessageBody, - I: Connection, { + let io = H1Connection { + io: Some(io), + created: created, + pool: pool, + }; + let len = body.length(); - connector - // connect to the host - .call(Connect::new(head.uri.clone())) + // create Framed and send reqest + Framed::new(io, h1::ClientCodec::default()) + .send((head, len).into()) .from_err() - // create Framed and send reqest - .map(|io| Framed::new(io, h1::ClientCodec::default())) - .and_then(move |framed| framed.send((head, len).into()).from_err()) // send request body .and_then(move |framed| match body.length() { BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => { @@ -64,11 +68,70 @@ where }) } +#[doc(hidden)] +/// HTTP client connection +pub struct H1Connection { + io: Option, + created: time::Instant, + pool: Option>, +} + +impl ConnectionLifetime for H1Connection { + /// Close connection + fn close(&mut self) { + if let Some(mut pool) = self.pool.take() { + if let Some(io) = self.io.take() { + pool.close(IoConnection::new( + ConnectionType::H1(io), + self.created, + None, + )); + } + } + } + + /// Release this connection to the connection pool + fn release(&mut self) { + if let Some(mut pool) = self.pool.take() { + if let Some(io) = self.io.take() { + pool.release(IoConnection::new( + ConnectionType::H1(io), + self.created, + None, + )); + } + } + } +} + +impl io::Read for H1Connection { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.io.as_mut().unwrap().read(buf) + } +} + +impl AsyncRead for H1Connection {} + +impl io::Write for H1Connection { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.io.as_mut().unwrap().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.io.as_mut().unwrap().flush() + } +} + +impl AsyncWrite for H1Connection { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.io.as_mut().unwrap().shutdown() + } +} + /// Future responsible for sending request body to the peer -struct SendBody { +pub(crate) struct SendBody { body: Option, framed: Option>, - write_buf: VecDeque>, flushed: bool, } @@ -77,11 +140,10 @@ where I: AsyncRead + AsyncWrite + 'static, B: MessageBody, { - fn new(body: B, framed: Framed) -> Self { + pub(crate) fn new(body: B, framed: Framed) -> Self { SendBody { body: Some(body), framed: Some(framed), - write_buf: VecDeque::new(), flushed: true, } } @@ -89,7 +151,7 @@ where impl Future for SendBody where - I: Connection, + I: ConnectionLifetime, B: MessageBody, { type Item = Framed; @@ -158,15 +220,15 @@ impl Payload<()> { } } -impl Payload { - fn stream(framed: Framed) -> PayloadStream { +impl Payload { + pub fn stream(framed: Framed) -> PayloadStream { Box::new(Payload { framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), }) } } -impl Stream for Payload { +impl Stream for Payload { type Item = Bytes; type Error = PayloadError; @@ -190,7 +252,7 @@ impl Stream for Payload { fn release_connection(framed: Framed, force_close: bool) where - T: Connection, + T: ConnectionLifetime, { let mut parts = framed.into_parts(); if !force_close && parts.read_buf.is_empty() && parts.write_buf.is_empty() { diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs new file mode 100644 index 00000000..fe42b909 --- /dev/null +++ b/src/client/h2proto.rs @@ -0,0 +1,151 @@ +use std::cell::RefCell; +use std::time; + +use actix_codec::{AsyncRead, AsyncWrite}; +use bytes::Bytes; +use futures::future::{err, Either}; +use futures::{Async, Future, Poll, Stream}; +use h2::{client::SendRequest, SendStream}; +use http::{request::Request, Version}; + +use super::connection::{ConnectionType, IoConnection}; +use super::error::SendRequestError; +use super::pool::Acquired; +use super::response::ClientResponse; +use crate::body::{BodyLength, MessageBody}; +use crate::message::{RequestHead, ResponseHead}; + +pub(crate) fn send_request( + io: SendRequest, + head: RequestHead, + body: B, + created: time::Instant, + pool: Option>, +) -> impl Future +where + T: AsyncRead + AsyncWrite + 'static, + B: MessageBody, +{ + trace!("Sending client request: {:?} {:?}", head, body.length()); + let eof = match body.length() { + BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => true, + _ => false, + }; + + io.ready() + .map_err(SendRequestError::from) + .and_then(move |mut io| { + let mut req = Request::new(()); + *req.uri_mut() = head.uri; + *req.method_mut() = head.method; + *req.headers_mut() = head.headers; + *req.version_mut() = Version::HTTP_2; + + match io.send_request(req, eof) { + Ok((resp, send)) => { + release(io, pool, created, false); + + if !eof { + Either::A(Either::B( + SendBody { + body, + send, + buf: None, + } + .and_then(move |_| resp.map_err(SendRequestError::from)), + )) + } else { + Either::B(resp.map_err(SendRequestError::from)) + } + } + Err(e) => { + release(io, pool, created, e.is_io()); + Either::A(Either::A(err(e.into()))) + } + } + }) + .and_then(|resp| { + let (parts, body) = resp.into_parts(); + + let mut head = ResponseHead::default(); + head.version = parts.version; + head.status = parts.status; + head.headers = parts.headers; + + Ok(ClientResponse { + head, + payload: RefCell::new(Some(Box::new(body.from_err()))), + }) + }) + .from_err() +} + +struct SendBody { + body: B, + send: SendStream, + buf: Option, +} + +impl Future for SendBody { + type Item = (); + type Error = SendRequestError; + + fn poll(&mut self) -> Poll { + if self.buf.is_none() { + match self.body.poll_next() { + Ok(Async::Ready(Some(buf))) => { + self.send.reserve_capacity(buf.len()); + self.buf = Some(buf); + } + Ok(Async::Ready(None)) => { + if let Err(e) = self.send.send_data(Bytes::new(), true) { + return Err(e.into()); + } + self.send.reserve_capacity(0); + return Ok(Async::Ready(())); + } + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(e.into()), + } + } + + loop { + match self.send.poll_capacity() { + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(None)) => return Ok(Async::Ready(())), + Ok(Async::Ready(Some(cap))) => { + let mut buf = self.buf.take().unwrap(); + let len = buf.len(); + let bytes = buf.split_to(std::cmp::min(cap, len)); + + if let Err(e) = self.send.send_data(bytes, false) { + return Err(e.into()); + } else { + if !buf.is_empty() { + self.send.reserve_capacity(buf.len()); + self.buf = Some(buf); + } + return self.poll(); + } + } + Err(e) => return Err(e.into()), + } + } + } +} + +// release SendRequest object +fn release( + io: SendRequest, + pool: Option>, + created: time::Instant, + close: bool, +) { + if let Some(mut pool) = pool { + if close { + pool.close(IoConnection::new(ConnectionType::H2(io), created, None)); + } else { + pool.release(IoConnection::new(ConnectionType::H2(io), created, None)); + } + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs index 76c3f8b8..c6498f37 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -3,13 +3,14 @@ mod connect; mod connection; mod connector; mod error; -mod pipeline; +mod h1proto; +mod h2proto; mod pool; mod request; mod response; pub use self::connect::Connect; -pub use self::connection::Connection; +pub use self::connection::RequestSender; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; diff --git a/src/client/pool.rs b/src/client/pool.rs index b577587d..089c2627 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -6,10 +6,12 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; +use bytes::Bytes; use futures::future::{ok, Either, FutureResult}; use futures::task::AtomicTask; use futures::unsync::oneshot; use futures::{Async, Future, Poll}; +use h2::client::{handshake, Handshake}; use hashbrown::HashMap; use http::uri::Authority; use indexmap::IndexSet; @@ -17,9 +19,15 @@ use slab::Slab; use tokio_timer::{sleep, Delay}; use super::connect::Connect; -use super::connection::IoConnection; +use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectorError; +#[derive(Clone, Copy, PartialEq)] +pub enum Protocol { + Http1, + Http2, +} + #[derive(Hash, Eq, PartialEq, Clone, Debug)] pub(crate) struct Key { authority: Authority, @@ -31,13 +39,6 @@ impl From for Key { } } -#[derive(Debug)] -struct AvailableConnection { - io: T, - used: Instant, - created: Instant, -} - /// Connections pool pub(crate) struct ConnectionPool( T, @@ -47,7 +48,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) fn new( connector: T, @@ -86,7 +87,7 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { type Response = IoConnection; type Error = ConnectorError; @@ -109,7 +110,7 @@ where Either::A(ok(IoConnection::new( io, created, - Acquired(key, Some(self.1.clone())), + Some(Acquired(key, Some(self.1.clone()))), ))) } Acquire::NotAvailable => { @@ -190,12 +191,13 @@ where { fut: F, key: Key, + h2: Option>, inner: Option>>>, } impl OpenConnection where - F: Future, + F: Future, Io: AsyncRead + AsyncWrite + 'static, { fn new(key: Key, inner: Rc>>, fut: F) -> Self { @@ -203,6 +205,7 @@ where key, fut, inner: Some(inner), + h2: None, } } } @@ -222,110 +225,165 @@ where impl Future for OpenConnection where - F: Future, + F: Future, Io: AsyncRead + AsyncWrite, { type Item = IoConnection; type Error = ConnectorError; fn poll(&mut self) -> Poll { + if let Some(ref mut h2) = self.h2 { + return match h2.poll() { + Ok(Async::Ready((snd, connection))) => { + tokio_current_thread::spawn(connection.map_err(|_| ())); + Ok(Async::Ready(IoConnection::new( + ConnectionType::H2(snd), + Instant::now(), + Some(Acquired(self.key.clone(), self.inner.clone())), + ))) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => Err(e.into()), + }; + } + match self.fut.poll() { Err(err) => Err(err.into()), - Ok(Async::Ready((_, io))) => { + Ok(Async::Ready((_, io, proto))) => { let _ = self.inner.take(); - Ok(Async::Ready(IoConnection::new( - io, - Instant::now(), - Acquired(self.key.clone(), self.inner.clone()), - ))) - } - Ok(Async::NotReady) => Ok(Async::NotReady), - } - } -} - -struct OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + 'static, -{ - fut: F, - key: Key, - rx: Option, ConnectorError>>>, - inner: Option>>>, -} - -impl OpenWaitingConnection -where - F: Future + 'static, - Io: AsyncRead + AsyncWrite + 'static, -{ - fn spawn( - key: Key, - rx: oneshot::Sender, ConnectorError>>, - inner: Rc>>, - fut: F, - ) { - tokio_current_thread::spawn(OpenWaitingConnection { - key, - fut, - rx: Some(rx), - inner: Some(inner), - }) - } -} - -impl Drop for OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + 'static, -{ - fn drop(&mut self) { - if let Some(inner) = self.inner.take() { - let mut inner = inner.as_ref().borrow_mut(); - inner.release(); - inner.check_availibility(); - } - } -} - -impl Future for OpenWaitingConnection -where - F: Future, - Io: AsyncRead + AsyncWrite, -{ - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Err(err) => { - let _ = self.inner.take(); - if let Some(rx) = self.rx.take() { - let _ = rx.send(Err(err)); - } - Err(()) - } - Ok(Async::Ready((_, io))) => { - let _ = self.inner.take(); - if let Some(rx) = self.rx.take() { - let _ = rx.send(Ok(IoConnection::new( - io, + if proto == Protocol::Http1 { + Ok(Async::Ready(IoConnection::new( + ConnectionType::H1(io), Instant::now(), - Acquired(self.key.clone(), self.inner.clone()), - ))); + Some(Acquired(self.key.clone(), self.inner.clone())), + ))) + } else { + self.h2 = Some(handshake(io)); + return self.poll(); } - Ok(Async::Ready(())) } Ok(Async::NotReady) => Ok(Async::NotReady), } } } +// struct OpenWaitingConnection +// where +// Io: AsyncRead + AsyncWrite + 'static, +// { +// fut: F, +// key: Key, +// h2: Option>, +// rx: Option, ConnectorError>>>, +// inner: Option>>>, +// } + +// impl OpenWaitingConnection +// where +// F: Future + 'static, +// Io: AsyncRead + AsyncWrite + 'static, +// { +// fn spawn( +// key: Key, +// rx: oneshot::Sender, ConnectorError>>, +// inner: Rc>>, +// fut: F, +// ) { +// tokio_current_thread::spawn(OpenWaitingConnection { +// key, +// fut, +// h2: None, +// rx: Some(rx), +// inner: Some(inner), +// }) +// } +// } + +// impl Drop for OpenWaitingConnection +// where +// Io: AsyncRead + AsyncWrite + 'static, +// { +// fn drop(&mut self) { +// if let Some(inner) = self.inner.take() { +// let mut inner = inner.as_ref().borrow_mut(); +// inner.release(); +// inner.check_availibility(); +// } +// } +// } + +// impl Future for OpenWaitingConnection +// where +// F: Future, +// Io: AsyncRead + AsyncWrite, +// { +// type Item = (); +// type Error = (); + +// fn poll(&mut self) -> Poll { +// if let Some(ref mut h2) = self.h2 { +// return match h2.poll() { +// Ok(Async::Ready((snd, connection))) => { +// tokio_current_thread::spawn(connection.map_err(|_| ())); +// let _ = self.rx.take().unwrap().send(Ok(IoConnection::new( +// ConnectionType::H2(snd), +// Instant::now(), +// Some(Acquired(self.key.clone(), self.inner.clone())), +// ))); +// Ok(Async::Ready(())) +// } +// Ok(Async::NotReady) => Ok(Async::NotReady), +// Err(e) => { +// let _ = self.inner.take(); +// if let Some(rx) = self.rx.take() { +// let _ = rx.send(Err(e.into())); +// } + +// Err(()) +// } +// }; +// } + +// match self.fut.poll() { +// Err(err) => { +// let _ = self.inner.take(); +// if let Some(rx) = self.rx.take() { +// let _ = rx.send(Err(err)); +// } +// Err(()) +// } +// Ok(Async::Ready((_, io, proto))) => { +// let _ = self.inner.take(); +// if proto == Protocol::Http1 { +// let _ = self.rx.take().unwrap().send(Ok(IoConnection::new( +// ConnectionType::H1(io), +// Instant::now(), +// Some(Acquired(self.key.clone(), self.inner.clone())), +// ))); +// } else { +// self.h2 = Some(handshake(io)); +// return self.poll(); +// } +// Ok(Async::Ready(())) +// } +// Ok(Async::NotReady) => Ok(Async::NotReady), +// } +// } +// } + enum Acquire { - Acquired(T, Instant), + Acquired(ConnectionType, Instant), Available, NotAvailable, } +// #[derive(Debug)] +struct AvailableConnection { + io: ConnectionType, + used: Instant, + created: Instant, +} + pub(crate) struct Inner { conn_lifetime: Duration, conn_keep_alive: Duration, @@ -355,7 +413,7 @@ impl Inner { self.waiters_queue.remove(&(key.clone(), token)); } - fn release_conn(&mut self, key: &Key, io: Io, created: Instant) { + fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { self.acquired -= 1; self.available .entry(key.clone()) @@ -408,24 +466,30 @@ where || (now - conn.created) > self.conn_lifetime { if let Some(timeout) = self.disconnect_timeout { - tokio_current_thread::spawn(CloseConnection::new( - conn.io, timeout, - )) + if let ConnectionType::H1(io) = conn.io { + tokio_current_thread::spawn(CloseConnection::new( + io, timeout, + )) + } } } else { let mut io = conn.io; let mut buf = [0; 2]; - match io.read(&mut buf) { - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Ok(n) if n > 0 => { - if let Some(timeout) = self.disconnect_timeout { - tokio_current_thread::spawn(CloseConnection::new( - io, timeout, - )) + if let ConnectionType::H1(ref mut s) = io { + match s.read(&mut buf) { + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), + Ok(n) if n > 0 => { + if let Some(timeout) = self.disconnect_timeout { + if let ConnectionType::H1(io) = io { + tokio_current_thread::spawn( + CloseConnection::new(io, timeout), + ) + } + } + continue; } - continue; + Ok(_) | Err(_) => continue, } - Ok(_) | Err(_) => continue, } return Acquire::Acquired(io, conn.created); } @@ -434,10 +498,12 @@ where Acquire::Available } - fn release_close(&mut self, io: Io) { + fn release_close(&mut self, io: ConnectionType) { self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { - tokio_current_thread::spawn(CloseConnection::new(io, timeout)) + if let ConnectionType::H1(io) = io { + tokio_current_thread::spawn(CloseConnection::new(io, timeout)) + } } } @@ -448,65 +514,65 @@ where } } -struct ConnectorPoolSupport -where - Io: AsyncRead + AsyncWrite + 'static, -{ - connector: T, - inner: Rc>>, -} +// struct ConnectorPoolSupport +// where +// Io: AsyncRead + AsyncWrite + 'static, +// { +// connector: T, +// inner: Rc>>, +// } -impl Future for ConnectorPoolSupport -where - Io: AsyncRead + AsyncWrite + 'static, - T: Service, - T::Future: 'static, -{ - type Item = (); - type Error = (); +// impl Future for ConnectorPoolSupport +// where +// Io: AsyncRead + AsyncWrite + 'static, +// T: Service, +// T::Future: 'static, +// { +// type Item = (); +// type Error = (); - fn poll(&mut self) -> Poll { - let mut inner = self.inner.as_ref().borrow_mut(); - inner.task.register(); +// fn poll(&mut self) -> Poll { +// let mut inner = self.inner.as_ref().borrow_mut(); +// inner.task.register(); - // check waiters - loop { - let (key, token) = { - if let Some((key, token)) = inner.waiters_queue.get_index(0) { - (key.clone(), *token) - } else { - break; - } - }; - match inner.acquire(&key) { - Acquire::NotAvailable => break, - Acquire::Acquired(io, created) => { - let (_, tx) = inner.waiters.remove(token); - if let Err(conn) = tx.send(Ok(IoConnection::new( - io, - created, - Acquired(key.clone(), Some(self.inner.clone())), - ))) { - let (io, created) = conn.unwrap().into_inner(); - inner.release_conn(&key, io, created); - } - } - Acquire::Available => { - let (connect, tx) = inner.waiters.remove(token); - OpenWaitingConnection::spawn( - key.clone(), - tx, - self.inner.clone(), - self.connector.call(connect), - ); - } - } - let _ = inner.waiters_queue.swap_remove_index(0); - } +// // check waiters +// loop { +// let (key, token) = { +// if let Some((key, token)) = inner.waiters_queue.get_index(0) { +// (key.clone(), *token) +// } else { +// break; +// } +// }; +// match inner.acquire(&key) { +// Acquire::NotAvailable => break, +// Acquire::Acquired(io, created) => { +// let (_, tx) = inner.waiters.remove(token); +// if let Err(conn) = tx.send(Ok(IoConnection::new( +// io, +// created, +// Some(Acquired(key.clone(), Some(self.inner.clone()))), +// ))) { +// let (io, created) = conn.unwrap().into_inner(); +// inner.release_conn(&key, io, created); +// } +// } +// Acquire::Available => { +// let (connect, tx) = inner.waiters.remove(token); +// OpenWaitingConnection::spawn( +// key.clone(), +// tx, +// self.inner.clone(), +// self.connector.call(connect), +// ); +// } +// } +// let _ = inner.waiters_queue.swap_remove_index(0); +// } - Ok(Async::NotReady) - } -} +// Ok(Async::NotReady) +// } +// } struct CloseConnection { io: T, diff --git a/src/client/request.rs b/src/client/request.rs index fbb1e840..a2233a2f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -17,8 +17,9 @@ use crate::http::{ }; use crate::message::{ConnectionType, Head, RequestHead}; +use super::connection::RequestSender; use super::response::ClientResponse; -use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; +use super::{Connect, ConnectorError, SendRequestError}; /// An HTTP Client Request /// @@ -37,7 +38,6 @@ use super::{pipeline, Connect, Connection, ConnectorError, SendRequestError}; /// .map_err(|_| ()) /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); -/// # actix_rt::System::current().stop(); /// Ok(()) /// }) /// })); @@ -175,10 +175,18 @@ where connector: &mut T, ) -> impl Future where + B: 'static, T: Service, - I: Connection, + I: RequestSender, { - pipeline::send_request(self.head, self.body, connector) + let Self { head, body } = self; + + connector + // connect to the host + .call(Connect::new(head.uri.clone())) + .from_err() + // send request + .and_then(move |connection| connection.send_request(head, body)) } } @@ -273,7 +281,6 @@ impl ClientRequestBuilder { /// .unwrap(); /// } /// ``` - #[doc(hidden)] pub fn set(&mut self, hdr: H) -> &mut Self { if let Some(parts) = parts(&mut self.head, &self.err) { match hdr.try_into() { diff --git a/src/client/response.rs b/src/client/response.rs index 6bfdfc32..005c0875 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -10,7 +10,7 @@ use crate::error::PayloadError; use crate::httpmessage::HttpMessage; use crate::message::{Head, ResponseHead}; -use super::pipeline::Payload; +use super::h1proto::Payload; /// Client Response pub struct ClientResponse { diff --git a/src/error.rs b/src/error.rs index 8af422fc..5470534f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -327,7 +327,7 @@ impl From for ParseError { } } -#[derive(Display, Debug)] +#[derive(Display, Debug, From)] /// A set of errors that can occur during payload parsing pub enum PayloadError { /// A payload reached EOF, but is not complete. @@ -342,6 +342,9 @@ pub enum PayloadError { /// A payload length is unknown. #[display(fmt = "A payload length is unknown.")] UnknownLength, + /// Http2 payload error + #[display(fmt = "{}", _0)] + H2Payload(h2::Error), } impl From for PayloadError { diff --git a/src/extensions.rs b/src/extensions.rs index 7bb965c9..f7805641 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,6 +1,5 @@ use std::any::{Any, TypeId}; use std::fmt; -use std::hash::{BuildHasherDefault, Hasher}; use hashbrown::HashMap; diff --git a/src/lib.rs b/src/lib.rs index 5adc9236..0dbaee6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,9 @@ //! * `session` - enables session support, includes `ring` crate as //! dependency //! +#[macro_use] +extern crate log; + pub mod body; pub mod client; mod config; diff --git a/src/response.rs b/src/response.rs index 49f2b63f..5e1c0d07 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] //! Http response use std::cell::RefCell; use std::collections::VecDeque; diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 36c0d7d5..cd74d456 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -13,8 +13,8 @@ use net2::TcpBuilder; use actix_http::body::MessageBody; use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, - ConnectorError, SendRequestError, + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connector, + ConnectorError, RequestSender, SendRequestError, }; use actix_http::ws; @@ -57,7 +57,7 @@ impl TestServer { pub fn with_factory( factory: F, ) -> TestServerRuntime< - impl Service + Clone, + impl Service + Clone, > { let (tx, rx) = mpsc::channel(); @@ -89,7 +89,7 @@ impl TestServer { } fn new_connector( - ) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(feature = "ssl")] { @@ -192,7 +192,7 @@ impl TestServerRuntime { impl TestServerRuntime where T: Service + Clone, - T::Response: Connection, + T::Response: RequestSender, { /// Connect to websocket server at a given path pub fn ws_at( @@ -212,7 +212,7 @@ where } /// Send request and read response message - pub fn send_request( + pub fn send_request( &mut self, req: ClientRequest, ) -> Result { From 4217894d48d6572b24c329e2e9166a39f31b004b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 Jan 2019 10:14:00 -0800 Subject: [PATCH 0886/1635] cleaup warnings --- .travis.yml | 2 +- Cargo.toml | 6 +-- README.md | 7 ++- src/body.rs | 2 +- src/client/connector.rs | 4 +- src/client/error.rs | 8 +-- src/client/h1proto.rs | 4 +- src/client/h2proto.rs | 38 +++++++------- src/client/pool.rs | 4 +- src/client/response.rs | 1 + src/error.rs | 4 +- src/h1/decoder.rs | 81 +++--------------------------- src/h1/dispatcher.rs | 106 +++++++++++++++++++++++++++++++++++----- src/h1/encoder.rs | 8 +-- src/h1/service.rs | 1 + src/lib.rs | 6 +++ src/payload.rs | 16 +++--- src/response.rs | 4 +- src/service.rs | 4 +- src/test.rs | 3 +- src/ws/mask.rs | 6 +-- 21 files changed, 166 insertions(+), 149 deletions(-) diff --git a/.travis.yml b/.travis.yml index feae3059..fd7dfd4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - RUST_BACKTRACE=1 cargo tarpaulin --out Xml + RUST_BACKTRACE=1 cargo tarpaulin --features="ssl" --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi diff --git a/Cargo.toml b/Cargo.toml index b3adfa82..af19caca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ description = "Actix http" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://actix.rs/api/actix-web/stable/actix_web/" +repository = "https://github.com/actix/actix-http.git" +documentation = "https://actix.rs/api/actix-http/stable/actix_http/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] @@ -20,7 +20,7 @@ features = ["session"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } -appveyor = { repository = "fafhrd91/actix-web-hdy9d" } +appveyor = { repository = "actix/actix-http-hdy9d" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" } [lib] diff --git a/README.md b/README.md index be816096..4f9e44b9 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ -# Actix http [![Build Status](https://travis-ci.org/fafhrd91/actix-http.svg?branch=master)](https://travis-ci.org/fafhrd91/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/bwq6923pblqg55gk/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-http/branch/master) [![codecov](https://codecov.io/gh/fafhrd91/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/fafhrd91/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/bwq6923pblqg55gk/branch/master?svg=true)](https://ci.appveyor.com/project/actix/actix-http/branch/master) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http ## Documentation & community resources * [User Guide](https://actix.rs/docs/) -* [API Documentation (Development)](https://actix.rs/actix-http/actix_http/) -* [API Documentation (Releases)](https://actix.rs/api/actix-http/stable/actix_http/) +* [API Documentation](https://docs.rs/actix-http/) * [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http](https://crates.io/crates/actix-web) +* Cargo package: [actix-http](https://crates.io/crates/actix-http) * Minimum supported Rust version: 1.26 or later ## Example diff --git a/src/body.rs b/src/body.rs index 4b71e9bb..12e2d034 100644 --- a/src/body.rs +++ b/src/body.rs @@ -158,7 +158,7 @@ impl From<&'static str> for Body { impl From<&'static [u8]> for Body { fn from(s: &'static [u8]) -> Body { - Body::Bytes(Bytes::from_static(s.as_ref())) + Body::Bytes(Bytes::from_static(s)) } } diff --git a/src/client/connector.rs b/src/client/connector.rs index b573181b..5b5356c7 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -47,9 +47,7 @@ impl Default for Connector { ssl.build() } #[cfg(not(feature = "ssl"))] - { - () - } + {} }; Connector { diff --git a/src/client/error.rs b/src/client/error.rs index e27a83d8..6c91ff97 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -65,12 +65,8 @@ impl From> for ConnectorError { fn from(err: HandshakeError) -> ConnectorError { match err { HandshakeError::SetupFailure(stack) => SslError::from(stack).into(), - HandshakeError::Failure(stream) => { - SslError::from(stream.into_error()).into() - } - HandshakeError::WouldBlock(stream) => { - SslError::from(stream.into_error()).into() - } + HandshakeError::Failure(stream) => stream.into_error().into(), + HandshakeError::WouldBlock(stream) => stream.into_error().into(), } } } diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index ed3c66d6..86fc10b8 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -26,9 +26,9 @@ where B: MessageBody, { let io = H1Connection { + created, + pool, io: Some(io), - created: created, - pool: pool, }; let len = body.length(); diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index fe42b909..e3d5be0b 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -91,25 +91,25 @@ impl Future for SendBody { type Error = SendRequestError; fn poll(&mut self) -> Poll { - if self.buf.is_none() { - match self.body.poll_next() { - Ok(Async::Ready(Some(buf))) => { - self.send.reserve_capacity(buf.len()); - self.buf = Some(buf); - } - Ok(Async::Ready(None)) => { - if let Err(e) = self.send.send_data(Bytes::new(), true) { - return Err(e.into()); - } - self.send.reserve_capacity(0); - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(e) => return Err(e.into()), - } - } - loop { + if self.buf.is_none() { + match self.body.poll_next() { + Ok(Async::Ready(Some(buf))) => { + self.send.reserve_capacity(buf.len()); + self.buf = Some(buf); + } + Ok(Async::Ready(None)) => { + if let Err(e) = self.send.send_data(Bytes::new(), true) { + return Err(e.into()); + } + self.send.reserve_capacity(0); + return Ok(Async::Ready(())); + } + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(e.into()), + } + } + match self.send.poll_capacity() { Ok(Async::NotReady) => return Ok(Async::NotReady), Ok(Async::Ready(None)) => return Ok(Async::Ready(())), @@ -125,7 +125,7 @@ impl Future for SendBody { self.send.reserve_capacity(buf.len()); self.buf = Some(buf); } - return self.poll(); + continue; } } Err(e) => return Err(e.into()), diff --git a/src/client/pool.rs b/src/client/pool.rs index 089c2627..425e8939 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -248,7 +248,7 @@ where } match self.fut.poll() { - Err(err) => Err(err.into()), + Err(err) => Err(err), Ok(Async::Ready((_, io, proto))) => { let _ = self.inner.take(); if proto == Protocol::Http1 { @@ -259,7 +259,7 @@ where ))) } else { self.h2 = Some(handshake(io)); - return self.poll(); + self.poll() } } Ok(Async::NotReady) => Ok(Async::NotReady), diff --git a/src/client/response.rs b/src/client/response.rs index 005c0875..9010a3c5 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -13,6 +13,7 @@ use crate::message::{Head, ResponseHead}; use super::h1proto::Payload; /// Client Response +#[derive(Default)] pub struct ClientResponse { pub(crate) head: ResponseHead, pub(crate) payload: RefCell>, diff --git a/src/error.rs b/src/error.rs index 5470534f..43bf7cfb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -837,7 +837,7 @@ mod tests { match ParseError::from($from) { e @ $error => { let desc = format!("{}", e); - assert_eq!(desc, $from.description().to_owned()); + assert_eq!(desc, format!("IO error: {}", $from.description())); } _ => unreachable!("{:?}", $from), } @@ -846,7 +846,7 @@ mod tests { #[test] fn test_from() { - // from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); + from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); from!(httparse::Error::HeaderName => ParseError::Header); from!(httparse::Error::HeaderName => ParseError::Header); from!(httparse::Error::HeaderValue => ParseError::Header); diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 7c17e909..48086478 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -273,16 +273,14 @@ impl MessageType for ClientResponse { // message payload let decoder = if let PayloadLength::Payload(pl) = len { pl + } else if status == StatusCode::SWITCHING_PROTOCOLS { + // switching protocol or connect + PayloadType::Stream(PayloadDecoder::eof()) + } else if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + return Err(ParseError::TooLarge); } else { - if status == StatusCode::SWITCHING_PROTOCOLS { - // switching protocol or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None - } + PayloadType::None }; msg.head.status = status; @@ -670,71 +668,6 @@ mod tests { }}; } - struct Buffer { - buf: Bytes, - err: Option, - } - - impl Buffer { - fn new(data: &'static str) -> Buffer { - Buffer { - buf: Bytes::from(data), - err: None, - } - } - } - - impl AsyncRead for Buffer {} - impl io::Read for Buffer { - fn read(&mut self, dst: &mut [u8]) -> Result { - if self.buf.is_empty() { - if self.err.is_some() { - Err(self.err.take().unwrap()) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - let size = cmp::min(self.buf.len(), dst.len()); - let b = self.buf.split_to(size); - dst[..size].copy_from_slice(&b); - Ok(size) - } - } - } - - impl io::Write for Buffer { - fn write(&mut self, buf: &[u8]) -> io::Result { - 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) - } - } - - // #[test] - // fn test_req_parse_err() { - // let mut sys = System::new("test"); - // let _ = sys.block_on(future::lazy(|| { - // let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - // let readbuf = BytesMut::new(); - - // let mut h1 = Dispatcher::new(buf, |req| ok(Response::Ok().finish())); - // assert!(h1.poll_io().is_ok()); - // assert!(h1.poll_io().is_ok()); - // assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); - // assert_eq!(h1.tasks.len(), 1); - // future::ok::<_, ()>(()) - // })); - // } - #[test] fn test_parse() { let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 569b1dee..f66955af 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -312,7 +312,7 @@ where Message::Item(req) => { match self.framed.get_codec().message_type() { MessageType::Payload => { - let (ps, pl) = Payload::new(false); + let (ps, pl) = Payload::create(false); *req.inner.payload.borrow_mut() = Some(pl); self.payload = Some(ps); } @@ -417,10 +417,10 @@ where // start shutdown timer if let Some(deadline) = self.config.client_disconnect_timer() { - self.ka_timer.as_mut().map(|timer| { + if let Some(timer) = self.ka_timer.as_mut() { timer.reset(deadline); let _ = timer.poll(); - }); + } } else { return Ok(()); } @@ -439,17 +439,14 @@ where self.state = State::None; } } else if let Some(deadline) = self.config.keep_alive_expire() { - self.ka_timer.as_mut().map(|timer| { + if let Some(timer) = self.ka_timer.as_mut() { timer.reset(deadline); let _ = timer.poll(); - }); + } } - } else { - let expire = self.ka_expire; - self.ka_timer.as_mut().map(|timer| { - timer.reset(expire); - let _ = timer.poll(); - }); + } else if let Some(timer) = self.ka_timer.as_mut() { + timer.reset(self.ka_expire); + let _ = timer.poll(); } } Async::NotReady => (), @@ -526,3 +523,90 @@ where } } } + +#[cfg(test)] +mod tests { + use std::{cmp, io}; + + use actix_codec::{AsyncRead, AsyncWrite}; + use actix_service::IntoService; + use bytes::{Buf, Bytes, BytesMut}; + use futures::future::{lazy, ok}; + + use super::*; + use crate::error::Error; + + struct Buffer { + buf: Bytes, + err: Option, + } + + impl Buffer { + fn new(data: &'static str) -> Buffer { + Buffer { + buf: Bytes::from(data), + err: None, + } + } + } + + impl AsyncRead for Buffer {} + impl io::Read for Buffer { + fn read(&mut self, dst: &mut [u8]) -> Result { + if self.buf.is_empty() { + if self.err.is_some() { + Err(self.err.take().unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + let size = cmp::min(self.buf.len(), dst.len()); + let b = self.buf.split_to(size); + dst[..size].copy_from_slice(&b); + Ok(size) + } + } + } + + impl io::Write for Buffer { + fn write(&mut self, buf: &[u8]) -> io::Result { + 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) + } + } + + #[test] + fn test_req_parse_err() { + let mut sys = actix_rt::System::new("test"); + let _ = sys.block_on(lazy(|| { + let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); + let readbuf = BytesMut::new(); + + let mut h1 = Dispatcher::new( + buf, + ServiceConfig::default(), + (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + ); + assert!(h1.poll().is_ok()); + assert!(h1.poll().is_ok()); + assert!(h1 + .inner + .as_ref() + .unwrap() + .flags + .contains(Flags::DISCONNECTED)); + // assert_eq!(h1.tasks.len(), 1); + ok::<_, ()>(()) + })); + } +} diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 92456520..32c8f9c4 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -104,10 +104,10 @@ pub(crate) trait MessageType: Sized { let mut remaining = dst.remaining_mut(); let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; for (key, value) in self.headers() { - match key { - &CONNECTION => continue, - &TRANSFER_ENCODING | &CONTENT_LENGTH if skip_len => continue, - &DATE => { + match *key { + CONNECTION => continue, + TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, + DATE => { has_date = true; } _ => (), diff --git a/src/h1/service.rs b/src/h1/service.rs index 7c2589cc..d8f63a32 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -272,6 +272,7 @@ where } /// `NewService` implementation for `OneRequestService` service +#[derive(Default)] pub struct OneRequest { config: ServiceConfig, _t: PhantomData, diff --git a/src/lib.rs b/src/lib.rs index 0dbaee6a..44263725 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,12 @@ //! * `session` - enables session support, includes `ring` crate as //! dependency //! +#![allow( + clippy::type_complexity, + clippy::new_without_default, + clippy::new_without_default_derive +)] + #[macro_use] extern crate log; diff --git a/src/payload.rs b/src/payload.rs index ea266f70..6665a0e4 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -42,7 +42,7 @@ impl Payload { /// * `PayloadSender` - *Sender* side of the stream /// /// * `Payload` - *Receiver* side of the stream - pub fn new(eof: bool) -> (PayloadSender, Payload) { + pub fn create(eof: bool) -> (PayloadSender, Payload) { let shared = Rc::new(RefCell::new(Inner::new(eof))); ( @@ -540,7 +540,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (_, payload) = Payload::new(false); + let (_, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(payload.len, 0); @@ -557,7 +557,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); @@ -582,7 +582,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); @@ -600,7 +600,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); sender.feed_data(Bytes::from("line1")); @@ -629,7 +629,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); @@ -663,7 +663,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); + let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); @@ -697,7 +697,7 @@ mod tests { Runtime::new() .unwrap() .block_on(lazy(|| { - let (_, mut payload) = Payload::new(false); + let (_, mut payload) = Payload::create(false); payload.unread_data(Bytes::from("data")); assert!(!payload.is_empty()); diff --git a/src/response.rs b/src/response.rs index 5e1c0d07..a4b65f2b 100644 --- a/src/response.rs +++ b/src/response.rs @@ -109,7 +109,7 @@ impl Response { /// Constructs a response with body #[inline] pub fn with_body(status: StatusCode, body: B) -> Response { - ResponsePool::with_body(status, body.into()) + ResponsePool::with_body(status, body) } /// The source `error` for this response @@ -644,7 +644,7 @@ impl ResponseBuilder { } #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))] +#[allow(clippy::borrowed_box)] fn parts<'a>( parts: &'a mut Option>, err: &Option, diff --git a/src/service.rs b/src/service.rs index f98234e7..57828187 100644 --- a/src/service.rs +++ b/src/service.rs @@ -85,7 +85,7 @@ where fn poll(&mut self) -> Poll { if let Some(res) = self.res.take() { - if let Err(_) = self.framed.as_mut().unwrap().force_send(res) { + if self.framed.as_mut().unwrap().force_send(res).is_err() { return Err((self.err.take().unwrap(), self.framed.take().unwrap())); } } @@ -232,6 +232,6 @@ where break; } } - return Ok(Async::Ready(self.framed.take().unwrap())); + Ok(Async::Ready(self.framed.take().unwrap())) } } diff --git a/src/test.rs b/src/test.rs index 4b7e30ac..a26f31e5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -144,9 +144,8 @@ impl TestRequest { uri, version, headers, - _cookies: _, payload, - prefix: _, + .. } = self; let mut req = Request::new(); diff --git a/src/ws/mask.rs b/src/ws/mask.rs index e9bfb3d5..15737541 100644 --- a/src/ws/mask.rs +++ b/src/ws/mask.rs @@ -1,5 +1,5 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] +#![allow(clippy::cast_ptr_alignment)] use std::ptr::copy_nonoverlapping; use std::slice; @@ -19,7 +19,7 @@ impl<'a> ShortSlice<'a> { /// Faster version of `apply_mask()` which operates on 8-byte blocks. #[inline] -#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] +#[allow(clippy::cast_lossless)] pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // Extend the mask to 64 bits let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); @@ -50,7 +50,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { // TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so // inefficient, it could be done better. The compiler does not understand that // a `ShortSlice` must be smaller than a u64. -#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] +#[allow(clippy::needless_pass_by_value)] fn xor_short(buf: ShortSlice, mask: u64) { // Unsafe: we know that a `ShortSlice` fits in a u64 unsafe { From 9a4eb5a848bce9ede1452cba385e2083abdae7f0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 Jan 2019 10:17:38 -0800 Subject: [PATCH 0887/1635] update readme --- CHANGES.md | 2 +- Cargo.toml | 6 +++--- README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c3c38011..74fa4a22 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,5 @@ # Changes -## [0.1.0] - 2018-09-x +## [0.1.0] - 2019-01-x * Initial impl diff --git a/Cargo.toml b/Cargo.toml index af19caca..01aa1a0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,9 @@ edition = "2018" features = ["session"] [badges] -travis-ci = { repository = "actix/actix-web", branch = "master" } -appveyor = { repository = "actix/actix-http-hdy9d" } -codecov = { repository = "actix/actix-web", branch = "master", service = "github" } +travis-ci = { repository = "actix/actix-http", branch = "master" } +appveyor = { repository = "fafhrd91/actix-http-b1qsn" } +codecov = { repository = "actix/actix-http", branch = "master", service = "github" } [lib] name = "actix_http" diff --git a/README.md b/README.md index 4f9e44b9..a5a25567 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/bwq6923pblqg55gk/branch/master?svg=true)](https://ci.appveyor.com/project/actix/actix-http/branch/master) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/i51p65u2bukstgwg/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-http-b1qsn/branch/master) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http From 3e6bdbd9eee2b965eb97dea30e0e45c71ceffce6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 29 Jan 2019 10:34:27 -0800 Subject: [PATCH 0888/1635] rename trait --- Cargo.toml | 2 +- src/client/connection.rs | 6 +++--- src/client/connector.rs | 4 ++-- src/client/mod.rs | 2 +- src/client/request.rs | 4 ++-- src/h1/decoder.rs | 5 +---- test-server/src/lib.rs | 10 +++++----- 7 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01aa1a0c..88a9ba88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-timer = "0.2" tokio-current-thread = "0.1" -trust-dns-resolver = { version="0.11.0-alpha.1", default-features = false } +trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } # openssl openssl = { version="0.10", optional = true } diff --git a/src/client/connection.rs b/src/client/connection.rs index b192caae..683738e2 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -18,7 +18,7 @@ pub(crate) enum ConnectionType { H2(SendRequest), } -pub trait RequestSender { +pub trait Connection { type Future: Future; /// Close connection @@ -76,7 +76,7 @@ impl IoConnection { } } -impl RequestSender for IoConnection +impl Connection for IoConnection where T: AsyncRead + AsyncWrite + 'static, { @@ -112,7 +112,7 @@ pub(crate) enum EitherConnection { B(IoConnection), } -impl RequestSender for EitherConnection +impl Connection for EitherConnection where A: AsyncRead + AsyncWrite + 'static, B: AsyncRead + AsyncWrite + 'static, diff --git a/src/client/connector.rs b/src/client/connector.rs index 5b5356c7..05e24e51 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -7,7 +7,7 @@ use actix_utils::timeout::{TimeoutError, TimeoutService}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use super::connect::Connect; -use super::connection::RequestSender; +use super::connection::Connection; use super::error::ConnectorError; use super::pool::{ConnectionPool, Protocol}; @@ -135,7 +135,7 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(not(feature = "ssl"))] { diff --git a/src/client/mod.rs b/src/client/mod.rs index c6498f37..8d041827 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -10,7 +10,7 @@ mod request; mod response; pub use self::connect::Connect; -pub use self::connection::RequestSender; +pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; diff --git a/src/client/request.rs b/src/client/request.rs index a2233a2f..b62ebaf3 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -17,7 +17,7 @@ use crate::http::{ }; use crate::message::{ConnectionType, Head, RequestHead}; -use super::connection::RequestSender; +use super::connection::Connection; use super::response::ClientResponse; use super::{Connect, ConnectorError, SendRequestError}; @@ -177,7 +177,7 @@ where where B: 'static, T: Service, - I: RequestSender, + I: Connection, { let Self { head, body } = self; diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 48086478..74e1fb68 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -604,10 +604,7 @@ impl ChunkedState { #[cfg(test)] mod tests { - use std::{cmp, io}; - - use actix_codec::{AsyncRead, AsyncWrite}; - use bytes::{Buf, Bytes, BytesMut}; + use bytes::{Bytes, BytesMut}; use http::{Method, Version}; use super::*; diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index cd74d456..1ef45204 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -13,8 +13,8 @@ use net2::TcpBuilder; use actix_http::body::MessageBody; use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connector, - ConnectorError, RequestSender, SendRequestError, + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, + ConnectorError, SendRequestError, }; use actix_http::ws; @@ -57,7 +57,7 @@ impl TestServer { pub fn with_factory( factory: F, ) -> TestServerRuntime< - impl Service + Clone, + impl Service + Clone, > { let (tx, rx) = mpsc::channel(); @@ -89,7 +89,7 @@ impl TestServer { } fn new_connector( - ) -> impl Service + Clone + ) -> impl Service + Clone { #[cfg(feature = "ssl")] { @@ -192,7 +192,7 @@ impl TestServerRuntime { impl TestServerRuntime where T: Service + Clone, - T::Response: RequestSender, + T::Response: Connection, { /// Connect to websocket server at a given path pub fn ws_at( From 76866f054f16231d9e271dd5bac85abc9698ec1b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 30 Jan 2019 10:29:15 -0800 Subject: [PATCH 0889/1635] move service to submodule; update travis config --- .travis.yml | 10 ++++++++++ src/service/mod.rs | 3 +++ src/{service.rs => service/senderror.rs} | 0 3 files changed, 13 insertions(+) create mode 100644 src/service/mod.rs rename src/{service.rs => service/senderror.rs} (100%) diff --git a/.travis.yml b/.travis.yml index fd7dfd4e..b7b43895 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,16 @@ matrix: allow_failures: - rust: nightly +env: + global: + - RUSTFLAGS="-C link-dead-code" + - OPENSSL_VERSION=openssl-1.0.2 + +before_install: + - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl + - sudo apt-get update -qq + - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev + before_script: - export PATH=$PATH:~/.cargo/bin diff --git a/src/service/mod.rs b/src/service/mod.rs new file mode 100644 index 00000000..83a40bd1 --- /dev/null +++ b/src/service/mod.rs @@ -0,0 +1,3 @@ +mod senderror; + +pub use self::senderror::{SendError, SendResponse}; diff --git a/src/service.rs b/src/service/senderror.rs similarity index 100% rename from src/service.rs rename to src/service/senderror.rs From 3269e3572295496ea5fd9f2115f243088c54b623 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Feb 2019 20:18:44 -0800 Subject: [PATCH 0890/1635] migrate to actix-service 0.2 --- Cargo.toml | 10 +- src/client/connector.rs | 74 +++++++--- src/client/pool.rs | 15 +- src/client/request.rs | 2 +- src/h1/dispatcher.rs | 14 +- src/h1/service.rs | 30 ++-- src/h2/mod.rs | 20 +++ src/h2/service.rs | 310 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/service/senderror.rs | 12 +- src/ws/client/service.rs | 11 +- src/ws/frame.rs | 2 +- src/ws/service.rs | 6 +- src/ws/transport.rs | 10 +- test-server/Cargo.toml | 6 +- test-server/src/lib.rs | 12 +- 16 files changed, 460 insertions(+), 75 deletions(-) create mode 100644 src/h2/mod.rs create mode 100644 src/h2/service.rs diff --git a/Cargo.toml b/Cargo.toml index 88a9ba88..f4114781 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,12 +37,10 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.1.6" +actix-service = "0.2.0" actix-codec = "0.1.0" -# actix-connector = "0.1.0" -# actix-utils = "0.1.0" -actix-connector = { git = "https://github.com/actix/actix-net.git" } -actix-utils = { git = "https://github.com/actix/actix-net.git" } +actix-connector = "0.2.0" +actix-utils = "0.2.0" base64 = "0.10" backtrace = "0.3" @@ -78,7 +76,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" actix-web = "0.7" -actix-server = "0.1" +actix-server = "0.2" actix-http-test = { path="test-server" } env_logger = "0.6" serde_derive = "1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index 05e24e51..fea9b9c0 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -135,8 +135,11 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service + Clone - { + ) -> impl Service< + Request = Connect, + Response = impl Connection, + Error = ConnectorError, + > + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( @@ -237,7 +240,11 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { pub(crate) tcp_pool: ConnectionPool, } @@ -245,8 +252,11 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service - + Clone, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + > + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -255,15 +265,20 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { + type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< - as Service>::Future, + as Service>::Future, FutureResult, ConnectorError>, >; @@ -298,12 +313,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, @@ -317,12 +332,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, > + Clone, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, > + Clone, @@ -335,21 +350,22 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, { + type Request = Connect; type Response = EitherConnection; type Error = ConnectorError; type Future = Either< @@ -384,15 +400,23 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseA where - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -410,15 +434,23 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseB where - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/pool.rs b/src/client/pool.rs index 425e8939..188980cb 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -48,7 +48,11 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { pub(crate) fn new( connector: T, @@ -84,11 +88,16 @@ where } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { + type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< diff --git a/src/client/request.rs b/src/client/request.rs index b62ebaf3..b80f0e6d 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -176,7 +176,7 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f66955af..7780223f 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -35,14 +35,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher, B: MessageBody> +pub struct Dispatcher where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher, B: MessageBody> +struct InnerDispatcher where S::Error: Debug, { @@ -66,13 +66,13 @@ enum DispatcherMessage { Error(Response<()>), } -enum State, B: MessageBody> { +enum State { None, ServiceCall(S::Future), SendPayload(ResponseBody), } -impl, B: MessageBody> State { +impl State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -85,7 +85,7 @@ impl, B: MessageBody> State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { @@ -139,7 +139,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { @@ -459,7 +459,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service>, S::Error: Debug, B: MessageBody, { diff --git a/src/h1/service.rs b/src/h1/service.rs index d8f63a32..c35d1871 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -27,13 +27,13 @@ pub struct H1Service { impl H1Service where - S: NewService> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -49,14 +49,15 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, { + type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; @@ -88,7 +89,7 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where - S: NewService, + S: NewService, S::Service: Clone, S::Error: Debug, { @@ -187,7 +188,7 @@ where pub fn finish(self, service: F) -> H1Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -203,7 +204,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse, B> { +pub struct H1ServiceResponse { fut: S::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -212,7 +213,7 @@ pub struct H1ServiceResponse, B> { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService>, S::Service: Clone, S::Error: Debug, B: MessageBody, @@ -238,7 +239,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service> + Clone, + S: Service> + Clone, S::Error: Debug, B: MessageBody, { @@ -251,13 +252,14 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone, + S: Service> + Clone, S::Error: Debug, B: MessageBody, { + type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type Future = Dispatcher; @@ -291,10 +293,11 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { + type Request = T; type Response = (Request, Framed); type Error = ParseError; type InitError = (); @@ -316,10 +319,11 @@ pub struct OneRequestService { _t: PhantomData, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { + type Request = T; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; diff --git a/src/h2/mod.rs b/src/h2/mod.rs new file mode 100644 index 00000000..4a54ec9f --- /dev/null +++ b/src/h2/mod.rs @@ -0,0 +1,20 @@ +use std::fmt; + +mod service; + +/// H1 service response type +pub enum H2ServiceResult { + Disconnected, + Shutdown(T), +} + +impl fmt::Debug for H2ServiceResult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + H2ServiceResult::Disconnected => write!(f, "H2ServiceResult::Disconnected"), + H2ServiceResult::Shutdown(ref v) => { + write!(f, "H2ServiceResult::Shutdown({:?})", v) + } + } + } +} diff --git a/src/h2/service.rs b/src/h2/service.rs new file mode 100644 index 00000000..827f8448 --- /dev/null +++ b/src/h2/service.rs @@ -0,0 +1,310 @@ +use std::fmt::Debug; +use std::marker::PhantomData; +use std::net; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_service::{IntoNewService, NewService, Service}; +use bytes::Bytes; +use futures::future::{ok, FutureResult}; +use futures::{try_ready, Async, Future, Poll, Stream}; +use h2::server::{self, Connection, Handshake}; +use log::error; + +use crate::body::MessageBody; +use crate::config::{KeepAlive, ServiceConfig}; +use crate::error::{DispatchError, ParseError}; +use crate::request::Request; +use crate::response::Response; + +// use super::dispatcher::Dispatcher; +use super::H2ServiceResult; + +/// `NewService` implementation for HTTP2 transport +pub struct H2Service { + srv: S, + cfg: ServiceConfig, + _t: PhantomData<(T, B)>, +} + +impl H2Service +where + S: NewService> + Clone, + S::Service: Clone, + S::Error: Debug, + B: MessageBody, +{ + /// Create new `HttpService` instance. + pub fn new>(service: F) -> Self { + let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); + + H2Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + + /// Create builder for `HttpService` instance. + pub fn build() -> H2ServiceBuilder { + H2ServiceBuilder::new() + } +} + +impl NewService for H2Service +where + T: AsyncRead + AsyncWrite, + S: NewService> + Clone, + S::Service: Clone, + S::Error: Debug, + B: MessageBody, +{ + type Request = T; + type Response = H2ServiceResult; + type Error = (); //DispatchError; + type InitError = S::InitError; + type Service = H2ServiceHandler; + type Future = H2ServiceResponse; + + fn new_service(&self) -> Self::Future { + H2ServiceResponse { + fut: self.srv.new_service(), + cfg: Some(self.cfg.clone()), + _t: PhantomData, + } + } +} + +/// A http/2 new service builder +/// +/// This type can be used to construct an instance of `ServiceConfig` through a +/// builder-like pattern. +pub struct H2ServiceBuilder { + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, + host: String, + addr: net::SocketAddr, + secure: bool, + _t: PhantomData<(T, S)>, +} + +impl H2ServiceBuilder +where + S: NewService, + S::Service: Clone, + S::Error: Debug, +{ + /// Create instance of `H2ServiceBuilder` + pub fn new() -> H2ServiceBuilder { + H2ServiceBuilder { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_disconnect: 0, + secure: false, + host: "localhost".to_owned(), + addr: "127.0.0.1:8080".parse().unwrap(), + _t: PhantomData, + } + } + + /// Enable secure flag for current server. + /// This flags also enables `client disconnect timeout`. + /// + /// By default this flag is set to false. + pub fn secure(mut self) -> Self { + self.secure = true; + if self.client_disconnect == 0 { + self.client_disconnect = 3000; + } + self + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: U) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; + 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. + /// + /// By default host name is set to a "localhost" value. + pub fn server_hostname(mut self, val: &str) -> Self { + self.host = val.to_owned(); + self + } + + /// Set server ip address. + /// + /// 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. + /// + /// By default server address is set to a "127.0.0.1:8080" + pub fn server_address(mut self, addr: U) -> Self { + match addr.to_socket_addrs() { + Err(err) => error!("Can not convert to SocketAddr: {}", err), + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + self.addr = addr; + } + } + } + self + } + + /// Finish service configuration and create `H1Service` instance. + pub fn finish(self, service: F) -> H2Service + where + B: MessageBody, + F: IntoNewService, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + H2Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } +} + +#[doc(hidden)] +pub struct H2ServiceResponse { + fut: S::Future, + cfg: Option, + _t: PhantomData<(T, B)>, +} + +impl Future for H2ServiceResponse +where + T: AsyncRead + AsyncWrite, + S: NewService>, + S::Service: Clone, + S::Error: Debug, + B: MessageBody, +{ + type Item = H2ServiceHandler; + type Error = S::InitError; + + fn poll(&mut self) -> Poll { + let service = try_ready!(self.fut.poll()); + Ok(Async::Ready(H2ServiceHandler::new( + self.cfg.take().unwrap(), + service, + ))) + } +} + +/// `Service` implementation for http/2 transport +pub struct H2ServiceHandler { + srv: S, + cfg: ServiceConfig, + _t: PhantomData<(T, B)>, +} + +impl H2ServiceHandler +where + S: Service> + Clone, + S::Error: Debug, + B: MessageBody, +{ + fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { + H2ServiceHandler { + srv, + cfg, + _t: PhantomData, + } + } +} + +impl Service for H2ServiceHandler +where + T: AsyncRead + AsyncWrite, + S: Service> + Clone, + S::Error: Debug, + B: MessageBody, +{ + type Request = T; + type Response = H2ServiceResult; + type Error = (); // DispatchError; + type Future = H2ServiceHandlerResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.srv.poll_ready().map_err(|_| ()) + } + + fn call(&mut self, req: T) -> Self::Future { + H2ServiceHandlerResponse { + state: State::Handshake(server::handshake(req)), + _t: PhantomData, + } + } +} + +enum State { + Handshake(Handshake), + Connection(Connection), + Empty, +} + +pub struct H2ServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, + S: Service> + Clone, + S::Error: Debug, + B: MessageBody, +{ + state: State, + _t: PhantomData, +} + +impl Future for H2ServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, + S: Service> + Clone, + S::Error: Debug, + B: MessageBody, +{ + type Item = H2ServiceResult; + type Error = (); + + fn poll(&mut self) -> Poll { + unimplemented!() + } +} diff --git a/src/lib.rs b/src/lib.rs index 44263725..cdb4f038 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ mod service; pub mod error; pub mod h1; +pub mod h2; pub mod test; pub mod ws; diff --git a/src/service/senderror.rs b/src/service/senderror.rs index 57828187..b469a61e 100644 --- a/src/service/senderror.rs +++ b/src/service/senderror.rs @@ -22,11 +22,12 @@ where } } -impl NewService)>> for SendError +impl NewService for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { + type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); @@ -38,11 +39,12 @@ where } } -impl Service)>> for SendError +impl Service for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { + type Request = Result)>; type Response = R; type Error = (E, Framed); type Future = Either)>, SendErrorFut>; @@ -128,11 +130,12 @@ where } } -impl NewService<(Response, Framed)> for SendResponse +impl NewService for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { + type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); @@ -144,11 +147,12 @@ where } } -impl Service<(Response, Framed)> for SendResponse +impl Service for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { + type Request = (Response, Framed); type Response = Framed; type Error = Error; type Future = SendResponseFut; diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index c48b6e0c..586873d1 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -26,7 +26,7 @@ pub type DefaultClient = Client; /// WebSocket's client pub struct Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { connector: T, @@ -34,7 +34,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -51,7 +51,7 @@ impl Default for Client { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -61,12 +61,13 @@ where } } -impl Service for Client +impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { + type Request = Connect; type Response = Framed; type Error = ClientError; type Future = Either< diff --git a/src/ws/frame.rs b/src/ws/frame.rs index 32ad4ef4..d4c15627 100644 --- a/src/ws/frame.rs +++ b/src/ws/frame.rs @@ -117,7 +117,7 @@ impl Parser { // control frames must have length <= 125 match opcode { OpCode::Ping | OpCode::Pong if length > 125 => { - return Err(ProtocolError::InvalidLength(length)) + return Err(ProtocolError::InvalidLength(length)); } OpCode::Close if length > 125 => { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); diff --git a/src/ws/service.rs b/src/ws/service.rs index 8189b195..137d41d4 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -20,7 +20,8 @@ impl Default for VerifyWebSockets { } } -impl NewService<(Request, Framed)> for VerifyWebSockets { +impl NewService for VerifyWebSockets { + type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); @@ -32,7 +33,8 @@ impl NewService<(Request, Framed)> for VerifyWebSockets { } } -impl Service<(Request, Framed)> for VerifyWebSockets { +impl Service for VerifyWebSockets { + type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type Future = FutureResult; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index 6a4f4d22..da7782be 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -7,7 +7,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service + 'static, + S: Service + 'static, T: AsyncRead + AsyncWrite, { inner: FramedTransport, @@ -16,17 +16,17 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Transport { inner: FramedTransport::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Transport { inner: FramedTransport::new(framed, service), } @@ -36,7 +36,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 851b3efe..81a3d909 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -30,11 +30,11 @@ session = ["cookie/secure"] [dependencies] actix-codec = "0.1" -actix-service = "0.1.6" +actix-service = "0.2.0" actix-rt = "0.1.0" -actix-server = "0.1.0" +actix-server = "0.2.0" +actix-utils = "0.2.0" actix-http = { path=".." } -actix-utils = { git = "https://github.com/actix/actix-net.git" } base64 = "0.10" bytes = "0.4" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 1ef45204..8083ebb1 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -57,7 +57,8 @@ impl TestServer { pub fn with_factory( factory: F, ) -> TestServerRuntime< - impl Service + Clone, + impl Service + + Clone, > { let (tx, rx) = mpsc::channel(); @@ -89,8 +90,11 @@ impl TestServer { } fn new_connector( - ) -> impl Service + Clone - { + ) -> impl Service< + Request = Connect, + Response = impl Connection, + Error = ConnectorError, + > + Clone { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -191,7 +195,7 @@ impl TestServerRuntime { impl TestServerRuntime where - T: Service + Clone, + T: Service + Clone, T::Response: Connection, { /// Connect to websocket server at a given path From e70c7f2a5d7d5dac8e66fa7c1e180a58f09bad14 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Feb 2019 20:22:43 -0800 Subject: [PATCH 0891/1635] upgrade derive-more --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f4114781..a039b455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ bitflags = "1.0" bytes = "0.4" byteorder = "1.2" cookie = { version="0.11", features=["percent-encode"] } -derive_more = "0.13" +derive_more = "0.14" encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" From c9bb2116feb8ac6c6c40a7f8f63e03dff8c973d5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Feb 2019 10:50:29 -0800 Subject: [PATCH 0892/1635] update actix-utils --- Cargo.toml | 4 ++++ src/client/connector.rs | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a039b455..ff19a4f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,3 +80,7 @@ actix-server = "0.2" actix-http-test = { path="test-server" } env_logger = "0.6" serde_derive = "1.0" + +[patch.crates-io] +actix-utils = { git = "https://github.com/actix/actix-net.git" } +actix-service = { git = "https://github.com/actix/actix-net.git" } diff --git a/src/client/connector.rs b/src/client/connector.rs index fea9b9c0..8e3c4b5a 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -2,7 +2,7 @@ use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connector::{Resolver, TcpConnector}; -use actix_service::{Service, ServiceExt}; +use actix_service::{Apply, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; @@ -142,8 +142,8 @@ impl Connector { > + Clone { #[cfg(not(feature = "ssl"))] { - let connector = TimeoutService::new( - self.timeout, + let connector = Apply::new( + TimeoutService::new(self.timeout), self.resolver.map_err(ConnectorError::from).and_then( TcpConnector::default() .from_err() @@ -167,8 +167,8 @@ impl Connector { } #[cfg(feature = "ssl")] { - let ssl_service = TimeoutService::new( - self.timeout, + let ssl_service = Apply::new( + TimeoutService::new(self.timeout), self.resolver .clone() .map_err(ConnectorError::from) @@ -196,8 +196,8 @@ impl Connector { TimeoutError::Timeout => ConnectorError::Timeout, }); - let tcp_service = TimeoutService::new( - self.timeout, + let tcp_service = Apply::new( + TimeoutService::new(self.timeout), self.resolver.map_err(ConnectorError::from).and_then( TcpConnector::default() .from_err() From ef5b54a48151f457a8ca47c6683b44feb3dd8525 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Feb 2019 14:05:44 -0800 Subject: [PATCH 0893/1635] use released service crate --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ff19a4f9..37e6a066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.2.0" +actix-service = "0.2.1" actix-codec = "0.1.0" actix-connector = "0.2.0" actix-utils = "0.2.0" @@ -83,4 +83,3 @@ serde_derive = "1.0" [patch.crates-io] actix-utils = { git = "https://github.com/actix/actix-net.git" } -actix-service = { git = "https://github.com/actix/actix-net.git" } From 346d85a8848a771a324dddec056c8c7e370a2356 Mon Sep 17 00:00:00 2001 From: Vladislav Stepanov <8uk.8ak@gmail.com> Date: Mon, 4 Feb 2019 13:20:46 +0300 Subject: [PATCH 0894/1635] Serve static file directly instead of redirecting (#676) --- src/fs.rs | 126 +++++++++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 58 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 04ababd0..dcf6c539 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -790,7 +790,7 @@ impl StaticFiles { /// Set index file /// - /// Redirects to specific index file for directory "/" instead of + /// Shows specific index file for directory "/" instead of /// showing files listing. pub fn index_file>(mut self, index: T) -> StaticFiles { self.index = Some(index.into()); @@ -815,17 +815,11 @@ impl StaticFiles { if path.is_dir() { if let Some(ref redir_index) = self.index { - // TODO: Don't redirect, just return the index content. - // TODO: It'd be nice if there were a good usable URL manipulation - // library - let mut new_path: String = req.path().to_owned(); - if !new_path.ends_with('/') { - new_path.push('/'); - } - new_path.push_str(redir_index); - HttpResponse::Found() - .header(header::LOCATION, new_path.as_str()) - .finish() + let path = path.join(redir_index); + + NamedFile::open_with_config(path, C::default())? + .set_cpu_pool(self.cpu_pool.clone()) + .respond_to(&req)? .respond_to(&req) } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); @@ -1482,43 +1476,66 @@ mod tests { } #[test] - fn test_redirect_to_index() { - let st = StaticFiles::new(".").unwrap().index_file("index.html"); + fn test_serve_index() { + let st = StaticFiles::new(".").unwrap().index_file("test.binary"); let req = TestRequest::default().uri("/tests").finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); + assert_eq!(resp.status(), StatusCode::OK); assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" + resp.headers().get(header::CONTENT_TYPE).expect("content type"), + "application/octet-stream" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).expect("content disposition"), + "attachment; filename=\"test.binary\"" ); let req = TestRequest::default().uri("/tests/").finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); + assert_eq!(resp.status(), StatusCode::OK); assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/tests/index.html" + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "application/octet-stream" ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.binary\"" + ); + + // nonexistent index file + let req = TestRequest::default().uri("/tests/unknown").finish(); + let resp = st.handle(&req).respond_to(&req).unwrap(); + let resp = resp.as_msg(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::default().uri("/tests/unknown/").finish(); + let resp = st.handle(&req).respond_to(&req).unwrap(); + let resp = resp.as_msg(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[test] - fn test_redirect_to_index_nested() { + fn test_serve_index_nested() { let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); let req = TestRequest::default().uri("/src/client").finish(); let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::FOUND); + assert_eq!(resp.status(), StatusCode::OK); assert_eq!( - resp.headers().get(header::LOCATION).unwrap(), - "/src/client/mod.rs" + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-rust" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"mod.rs\"" ); } #[test] - fn integration_redirect_to_index_with_prefix() { + fn integration_serve_index_with_prefix() { let mut srv = test::TestServer::with_factory(|| { App::new() .prefix("public") @@ -1527,29 +1544,21 @@ mod tests { let request = srv.get().uri(srv.url("/public")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/public/Cargo.toml"); + assert_eq!(response.status(), StatusCode::OK); + let bytes = srv.execute(response.body()).unwrap(); + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/public/Cargo.toml"); + assert_eq!(response.status(), StatusCode::OK); + let bytes = srv.execute(response.body()).unwrap(); + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); } #[test] - fn integration_redirect_to_index() { + fn integration_serve_index() { let mut srv = test::TestServer::with_factory(|| { App::new().handler( "test", @@ -1559,25 +1568,26 @@ mod tests { let request = srv.get().uri(srv.url("/test")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/test/Cargo.toml"); + assert_eq!(response.status(), StatusCode::OK); + let bytes = srv.execute(response.body()).unwrap(); + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::FOUND); - let loc = response - .headers() - .get(header::LOCATION) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(loc, "/test/Cargo.toml"); + assert_eq!(response.status(), StatusCode::OK); + let bytes = srv.execute(response.body()).unwrap(); + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); + + // nonexistent index file + let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); + + let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); } #[test] From 55a29d37782443ccf7485caf3c7a7dd270bd60f7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Feb 2019 11:44:15 -0800 Subject: [PATCH 0895/1635] add h2 server support --- .travis.yml | 2 +- Cargo.toml | 7 +- src/body.rs | 21 +++ src/client/connector.rs | 11 +- src/client/h2proto.rs | 57 +++++-- src/client/response.rs | 2 +- src/config.rs | 4 + src/error.rs | 10 ++ src/h1/dispatcher.rs | 4 +- src/h2/dispatcher.rs | 325 ++++++++++++++++++++++++++++++++++++++++ src/h2/mod.rs | 42 ++++++ src/h2/service.rs | 136 +++++++++++------ src/httpmessage.rs | 20 +-- src/json.rs | 2 +- src/message.rs | 4 - src/request.rs | 73 ++++++--- src/test.rs | 21 +-- test-server/Cargo.toml | 5 + test-server/src/lib.rs | 25 +++- tests/cert.pem | 43 ++---- tests/key.pem | 79 ++++------ tests/test_server.rs | 85 ++++++++++- 22 files changed, 774 insertions(+), 204 deletions(-) create mode 100644 src/h2/dispatcher.rs diff --git a/.travis.yml b/.travis.yml index b7b43895..c9c9db14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then cargo clean - cargo test + cargo test --features="ssl" fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then diff --git a/Cargo.toml b/Cargo.toml index 37e6a066..bbb31c16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,13 +34,13 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl", "actix-connector/ssl"] +ssl = ["openssl", "actix-connector/ssl", "actix-server/ssl", "actix-http-test/ssl"] [dependencies] actix-service = "0.2.1" actix-codec = "0.1.0" actix-connector = "0.2.0" -actix-utils = "0.2.0" +actix-utils = "0.2.1" base64 = "0.10" backtrace = "0.3" @@ -80,6 +80,3 @@ actix-server = "0.2" actix-http-test = { path="test-server" } env_logger = "0.6" serde_derive = "1.0" - -[patch.crates-io] -actix-utils = { git = "https://github.com/actix/actix-net.git" } diff --git a/src/body.rs b/src/body.rs index 12e2d034..1c54d4ce 100644 --- a/src/body.rs +++ b/src/body.rs @@ -20,6 +20,18 @@ pub enum BodyLength { Stream, } +impl BodyLength { + pub fn is_eof(&self) -> bool { + match self { + BodyLength::None + | BodyLength::Empty + | BodyLength::Sized(0) + | BodyLength::Sized64(0) => true, + _ => false, + } + } +} + /// Type that provides this trait can be streamed to a peer. pub trait MessageBody { fn length(&self) -> BodyLength; @@ -42,6 +54,15 @@ pub enum ResponseBody { Other(Body), } +impl ResponseBody { + pub fn into_body(self) -> ResponseBody { + match self { + ResponseBody::Body(b) => ResponseBody::Other(b), + ResponseBody::Other(b) => ResponseBody::Other(b), + } + } +} + impl ResponseBody { pub fn as_ref(&self) -> Option<&B> { if let ResponseBody::Body(ref b) = self { diff --git a/src/client/connector.rs b/src/client/connector.rs index 8e3c4b5a..32ba5012 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -12,11 +12,7 @@ use super::error::ConnectorError; use super::pool::{ConnectionPool, Protocol}; #[cfg(feature = "ssl")] -use actix_connector::ssl::OpensslConnector; -#[cfg(feature = "ssl")] -use openssl::ssl::{SslConnector, SslMethod}; -#[cfg(feature = "ssl")] -const H2: &[u8] = b"h2"; +use openssl::ssl::SslConnector; #[cfg(not(feature = "ssl"))] type SslConnector = (); @@ -40,6 +36,8 @@ impl Default for Connector { #[cfg(feature = "ssl")] { use log::error; + use openssl::ssl::{SslConnector, SslMethod}; + let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); let _ = ssl .set_alpn_protos(b"\x02h2\x08http/1.1") @@ -167,6 +165,9 @@ impl Connector { } #[cfg(feature = "ssl")] { + const H2: &[u8] = b"h2"; + use actix_connector::ssl::OpensslConnector; + let ssl_service = Apply::new( TimeoutService::new(self.timeout), self.resolver diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index e3d5be0b..ecd18cf8 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -4,16 +4,19 @@ use std::time; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; use futures::future::{err, Either}; -use futures::{Async, Future, Poll, Stream}; +use futures::{Async, Future, Poll}; use h2::{client::SendRequest, SendStream}; -use http::{request::Request, Version}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::{request::Request, HttpTryFrom, Version}; + +use crate::body::{BodyLength, MessageBody}; +use crate::h2::Payload; +use crate::message::{RequestHead, ResponseHead}; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; use super::pool::Acquired; use super::response::ClientResponse; -use crate::body::{BodyLength, MessageBody}; -use crate::message::{RequestHead, ResponseHead}; pub(crate) fn send_request( io: SendRequest, @@ -27,7 +30,8 @@ where B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.length()); - let eof = match body.length() { + let length = body.length(); + let eof = match length { BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => true, _ => false, }; @@ -38,11 +42,44 @@ where let mut req = Request::new(()); *req.uri_mut() = head.uri; *req.method_mut() = head.method; - *req.headers_mut() = head.headers; *req.version_mut() = Version::HTTP_2; + let mut skip_len = true; + let mut has_date = false; + + // Content length + let _ = match length { + BodyLength::Chunked | BodyLength::None => None, + BodyLength::Stream => { + skip_len = false; + None + } + BodyLength::Empty => req + .headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodyLength::Sized(len) => req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + BodyLength::Sized64(len) => req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + }; + + // copy headers + for (key, value) in head.headers.iter() { + match *key { + CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + CONTENT_LENGTH if skip_len => continue, + DATE => has_date = true, + _ => (), + } + req.headers_mut().append(key, value.clone()); + } + match io.send_request(req, eof) { - Ok((resp, send)) => { + Ok((res, send)) => { release(io, pool, created, false); if !eof { @@ -52,10 +89,10 @@ where send, buf: None, } - .and_then(move |_| resp.map_err(SendRequestError::from)), + .and_then(move |_| res.map_err(SendRequestError::from)), )) } else { - Either::B(resp.map_err(SendRequestError::from)) + Either::B(res.map_err(SendRequestError::from)) } } Err(e) => { @@ -74,7 +111,7 @@ where Ok(ClientResponse { head, - payload: RefCell::new(Some(Box::new(body.from_err()))), + payload: RefCell::new(Some(Box::new(Payload::new(body)))), }) }) .from_err() diff --git a/src/client/response.rs b/src/client/response.rs index 9010a3c5..6224d3cb 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -27,7 +27,7 @@ impl HttpMessage for ClientResponse { } #[inline] - fn payload(&self) -> Self::Stream { + fn payload(self) -> Self::Stream { if let Some(payload) = self.payload.borrow_mut().take() { payload } else { diff --git a/src/config.rs b/src/config.rs index c37601db..960f1370 100644 --- a/src/config.rs +++ b/src/config.rs @@ -171,6 +171,10 @@ impl ServiceConfig { buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } + + pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { + dst.extend_from_slice(&self.0.timer.date().bytes); + } } /// A service config builder diff --git a/src/error.rs b/src/error.rs index 43bf7cfb..03224b55 100644 --- a/src/error.rs +++ b/src/error.rs @@ -389,6 +389,10 @@ pub enum DispatchError { #[display(fmt = "Parse error: {}", _0)] Parse(ParseError), + /// Http/2 error + #[display(fmt = "{}", _0)] + H2(h2::Error), + /// The first request did not complete within the specified timeout. #[display(fmt = "The first request did not complete within the specified timeout")] SlowRequestTimeout, @@ -426,6 +430,12 @@ impl From for DispatchError { } } +impl From for DispatchError { + fn from(err: h2::Error) -> Self { + DispatchError::H2(err) + } +} + /// A set of error that can occure during parsing content type #[derive(PartialEq, Debug, Display)] pub enum ContentTypeError { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 7780223f..1295dfdd 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -309,11 +309,11 @@ where self.flags.insert(Flags::STARTED); match msg { - Message::Item(req) => { + Message::Item(mut req) => { match self.framed.get_codec().message_type() { MessageType::Payload => { let (ps, pl) = Payload::create(false); - *req.inner.payload.borrow_mut() = Some(pl); + req = req.set_payload(pl); self.payload = Some(ps); } MessageType::Stream => { diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs new file mode 100644 index 00000000..2994d0a3 --- /dev/null +++ b/src/h2/dispatcher.rs @@ -0,0 +1,325 @@ +use std::collections::VecDeque; +use std::marker::PhantomData; +use std::time::Instant; +use std::{fmt, mem}; + +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_service::Service; +use bitflags::bitflags; +use bytes::{Bytes, BytesMut}; +use futures::{try_ready, Async, Future, Poll, Sink, Stream}; +use h2::server::{Connection, SendResponse}; +use h2::{RecvStream, SendStream}; +use http::header::{ + HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, +}; +use http::HttpTryFrom; +use log::{debug, error, trace}; +use tokio_timer::Delay; + +use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; +use crate::config::ServiceConfig; +use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; +use crate::message::ResponseHead; +use crate::request::Request; +use crate::response::Response; + +use super::{H2ServiceResult, Payload}; + +const CHUNK_SIZE: usize = 16_384; + +bitflags! { + struct Flags: u8 { + const DISCONNECTED = 0b0000_0001; + const SHUTDOWN = 0b0000_0010; + } +} + +/// Dispatcher for HTTP/2 protocol +pub struct Dispatcher { + flags: Flags, + service: S, + connection: Connection, + config: ServiceConfig, + ka_expire: Instant, + ka_timer: Option, + _t: PhantomData, +} + +impl Dispatcher +where + T: AsyncRead + AsyncWrite, + S: Service, Response = Response> + 'static, + S::Error: Into + fmt::Debug, + B: MessageBody + 'static, +{ + pub fn new( + service: S, + connection: Connection, + config: ServiceConfig, + timeout: Option, + ) -> Self { + let keepalive = config.keep_alive_enabled(); + // let flags = if keepalive { + // Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED + // } else { + // Flags::empty() + // }; + + // keep-alive timer + let (ka_expire, ka_timer) = if let Some(delay) = timeout { + (delay.deadline(), Some(delay)) + } else if let Some(delay) = config.keep_alive_timer() { + (delay.deadline(), Some(delay)) + } else { + (config.now(), None) + }; + + Dispatcher { + service, + config, + ka_expire, + ka_timer, + connection, + flags: Flags::empty(), + _t: PhantomData, + } + } +} + +impl Future for Dispatcher +where + T: AsyncRead + AsyncWrite, + S: Service, Response = Response> + 'static, + S::Error: Into + fmt::Debug, + B: MessageBody + 'static, +{ + type Item = (); + type Error = DispatchError<()>; + + #[inline] + fn poll(&mut self) -> Poll { + loop { + match self.connection.poll()? { + Async::Ready(None) => { + self.flags.insert(Flags::DISCONNECTED); + } + Async::Ready(Some((req, res))) => { + // update keep-alive expire + if self.ka_timer.is_some() { + if let Some(expire) = self.config.keep_alive_expire() { + self.ka_expire = expire; + } + } + + let (parts, body) = req.into_parts(); + let mut req = Request::with_payload(Payload::new(body)); + + let head = &mut req.inner_mut().head; + head.uri = parts.uri; + head.method = parts.method; + head.version = parts.version; + head.headers = parts.headers; + tokio_current_thread::spawn(ServiceResponse:: { + state: ServiceResponseState::ServiceCall( + self.service.call(req), + Some(res), + ), + config: self.config.clone(), + buffer: None, + }) + } + Async::NotReady => return Ok(Async::NotReady), + } + } + } +} + +struct ServiceResponse { + state: ServiceResponseState, + config: ServiceConfig, + buffer: Option, +} + +enum ServiceResponseState { + ServiceCall(S::Future, Option>), + SendPayload(SendStream, ResponseBody), +} + +impl ServiceResponse +where + S: Service, Response = Response> + 'static, + S::Error: Into + fmt::Debug, + B: MessageBody + 'static, +{ + fn prepare_response( + &self, + head: &ResponseHead, + length: &mut BodyLength, + ) -> http::Response<()> { + let mut has_date = false; + let mut skip_len = length != &BodyLength::Stream; + + let mut res = http::Response::new(()); + *res.status_mut() = head.status; + *res.version_mut() = http::Version::HTTP_2; + + // Content length + match head.status { + http::StatusCode::NO_CONTENT + | http::StatusCode::CONTINUE + | http::StatusCode::PROCESSING => *length = BodyLength::None, + http::StatusCode::SWITCHING_PROTOCOLS => { + skip_len = true; + *length = BodyLength::Stream; + } + _ => (), + } + let _ = match length { + BodyLength::Chunked | BodyLength::None | BodyLength::Stream => None, + BodyLength::Empty => res + .headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodyLength::Sized(len) => res.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + BodyLength::Sized64(len) => res.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + }; + + // copy headers + for (key, value) in head.headers.iter() { + match *key { + CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + CONTENT_LENGTH if skip_len => continue, + DATE => has_date = true, + _ => (), + } + res.headers_mut().append(key, value.clone()); + } + + // set date header + if !has_date { + let mut bytes = BytesMut::with_capacity(29); + self.config.set_date_header(&mut bytes); + res.headers_mut() + .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); + } + + res + } +} + +impl Future for ServiceResponse +where + S: Service, Response = Response> + 'static, + S::Error: Into + fmt::Debug, + B: MessageBody + 'static, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + match self.state { + ServiceResponseState::ServiceCall(ref mut call, ref mut send) => { + match call.poll() { + Ok(Async::Ready(res)) => { + let (res, body) = res.replace_body(()); + + let mut send = send.take().unwrap(); + let mut length = body.length(); + let h2_res = self.prepare_response(res.head(), &mut length); + + let stream = send + .send_response(h2_res, length.is_eof()) + .map_err(|e| { + trace!("Error sending h2 response: {:?}", e); + })?; + + if length.is_eof() { + Ok(Async::Ready(())) + } else { + self.state = ServiceResponseState::SendPayload(stream, body); + self.poll() + } + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + + let mut send = send.take().unwrap(); + let mut length = body.length(); + let h2_res = self.prepare_response(res.head(), &mut length); + + let stream = send + .send_response(h2_res, length.is_eof()) + .map_err(|e| { + trace!("Error sending h2 response: {:?}", e); + })?; + + if length.is_eof() { + Ok(Async::Ready(())) + } else { + self.state = ServiceResponseState::SendPayload( + stream, + body.into_body(), + ); + self.poll() + } + } + } + } + ServiceResponseState::SendPayload(ref mut stream, ref mut body) => loop { + loop { + if let Some(ref mut buffer) = self.buffer { + match stream.poll_capacity().map_err(|e| warn!("{:?}", e))? { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(None) => return Ok(Async::Ready(())), + Async::Ready(Some(cap)) => { + let len = buffer.len(); + let bytes = buffer.split_to(std::cmp::min(cap, len)); + + if let Err(e) = stream.send_data(bytes, false) { + warn!("{:?}", e); + return Err(()); + } else if !buffer.is_empty() { + let cap = std::cmp::min(buffer.len(), CHUNK_SIZE); + stream.reserve_capacity(cap); + } else { + self.buffer.take(); + } + } + } + } else { + match body.poll_next() { + Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + if let Err(e) = stream.send_data(Bytes::new(), true) { + warn!("{:?}", e); + return Err(()); + } else { + return Ok(Async::Ready(())); + } + } + Ok(Async::Ready(Some(chunk))) => { + stream.reserve_capacity(std::cmp::min( + chunk.len(), + CHUNK_SIZE, + )); + self.buffer = Some(chunk); + } + Err(e) => { + error!("Response payload stream error: {:?}", e); + return Err(()); + } + } + } + } + }, + } + } +} diff --git a/src/h2/mod.rs b/src/h2/mod.rs index 4a54ec9f..55e05760 100644 --- a/src/h2/mod.rs +++ b/src/h2/mod.rs @@ -1,7 +1,17 @@ +#![allow(dead_code, unused_imports)] + use std::fmt; +use bytes::Bytes; +use futures::{Async, Poll, Stream}; +use h2::RecvStream; + +mod dispatcher; mod service; +pub use self::service::H2Service; +use crate::error::PayloadError; + /// H1 service response type pub enum H2ServiceResult { Disconnected, @@ -18,3 +28,35 @@ impl fmt::Debug for H2ServiceResult { } } } + +/// H2 receive stream +pub struct Payload { + pl: RecvStream, +} + +impl Payload { + pub(crate) fn new(pl: RecvStream) -> Self { + Self { pl } + } +} + +impl Stream for Payload { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + match self.pl.poll() { + Ok(Async::Ready(Some(chunk))) => { + let len = chunk.len(); + if let Err(err) = self.pl.release_capacity().release_capacity(len) { + Err(err.into()) + } else { + Ok(Async::Ready(Some(chunk))) + } + } + Ok(Async::Ready(None)) => Ok(Async::Ready(None)), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => Err(err.into()), + } + } +} diff --git a/src/h2/service.rs b/src/h2/service.rs index 827f8448..b598b0a6 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; use std::marker::PhantomData; -use std::net; +use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoNewService, NewService, Service}; @@ -8,16 +8,17 @@ use bytes::Bytes; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, Poll, Stream}; use h2::server::{self, Connection, Handshake}; +use h2::RecvStream; use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; -use crate::error::{DispatchError, ParseError}; +use crate::error::{DispatchError, Error, ParseError, ResponseError}; use crate::request::Request; use crate::response::Response; -// use super::dispatcher::Dispatcher; -use super::H2ServiceResult; +use super::dispatcher::Dispatcher; +use super::{H2ServiceResult, Payload}; /// `NewService` implementation for HTTP2 transport pub struct H2Service { @@ -28,10 +29,10 @@ pub struct H2Service { impl H2Service where - S: NewService> + Clone, - S::Service: Clone, - S::Error: Debug, - B: MessageBody, + S: NewService, Response = Response> + Clone, + S::Service: Clone + 'static, + S::Error: Into + Debug + 'static, + B: MessageBody + 'static, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -53,14 +54,14 @@ where impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, - S::Service: Clone, - S::Error: Debug, - B: MessageBody, + S: NewService, Response = Response> + Clone, + S::Service: Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { type Request = T; - type Response = H2ServiceResult; - type Error = (); //DispatchError; + type Response = (); + type Error = DispatchError<()>; type InitError = S::InitError; type Service = H2ServiceHandler; type Future = H2ServiceResponse; @@ -90,9 +91,9 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where - S: NewService, - S::Service: Clone, - S::Error: Debug, + S: NewService>, + S::Service: Clone + 'static, + S::Error: Into + Debug + 'static, { /// Create instance of `H2ServiceBuilder` pub fn new() -> H2ServiceBuilder { @@ -185,6 +186,25 @@ where self } + // #[cfg(feature = "ssl")] + // /// Configure alpn protocols for SslAcceptorBuilder. + // pub fn configure_openssl( + // builder: &mut openssl::ssl::SslAcceptorBuilder, + // ) -> io::Result<()> { + // let protos: &[u8] = b"\x02h2"; + // builder.set_alpn_select_callback(|_, protos| { + // const H2: &[u8] = b"\x02h2"; + // if protos.windows(3).any(|window| window == H2) { + // Ok(b"h2") + // } else { + // Err(openssl::ssl::AlpnError::NOACK) + // } + // }); + // builder.set_alpn_protos(&protos)?; + + // Ok(()) + // } + /// Finish service configuration and create `H1Service` instance. pub fn finish(self, service: F) -> H2Service where @@ -214,10 +234,10 @@ pub struct H2ServiceResponse { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, - S::Service: Clone, - S::Error: Debug, - B: MessageBody, + S: NewService, Response = Response>, + S::Service: Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { type Item = H2ServiceHandler; type Error = S::InitError; @@ -240,9 +260,9 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service> + Clone, - S::Error: Debug, - B: MessageBody, + S: Service, Response = Response> + Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { H2ServiceHandler { @@ -256,55 +276,79 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone, - S::Error: Debug, - B: MessageBody, + S: Service, Response = Response> + Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { type Request = T; - type Response = H2ServiceResult; - type Error = (); // DispatchError; + type Response = (); + type Error = DispatchError<()>; type Future = H2ServiceHandlerResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(|_| ()) + self.srv.poll_ready().map_err(|e| { + error!("Service readiness error: {:?}", e); + DispatchError::Service(()) + }) } fn call(&mut self, req: T) -> Self::Future { H2ServiceHandlerResponse { - state: State::Handshake(server::handshake(req)), - _t: PhantomData, + state: State::Handshake( + Some(self.srv.clone()), + Some(self.cfg.clone()), + server::handshake(req), + ), } } } -enum State { - Handshake(Handshake), - Connection(Connection), - Empty, +enum State { + Incoming(Dispatcher), + Handshake(Option, Option, Handshake), } pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + Clone, - S::Error: Debug, - B: MessageBody, + S: Service, Response = Response> + Clone + 'static, + S::Error: Into + Debug, + B: MessageBody + 'static, { - state: State, - _t: PhantomData, + state: State, } impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + Clone, - S::Error: Debug, + S: Service, Response = Response> + Clone, + S::Error: Into + Debug, B: MessageBody, { - type Item = H2ServiceResult; - type Error = (); + type Item = (); + type Error = DispatchError<()>; fn poll(&mut self) -> Poll { - unimplemented!() + match self.state { + State::Incoming(ref mut disp) => disp.poll(), + State::Handshake(ref mut srv, ref mut config, ref mut handshake) => { + match handshake.poll() { + Ok(Async::Ready(conn)) => { + self.state = State::Incoming(Dispatcher::new( + srv.take().unwrap(), + conn, + config.take().unwrap(), + None, + )); + self.poll() + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => { + trace!("H2 handshake error: {}", err); + return Err(err.into()); + } + } + } + } } } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 589617fc..c50de2e9 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -25,7 +25,7 @@ pub trait HttpMessage: Sized { fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(&self) -> Self::Stream; + fn payload(self) -> Self::Stream; #[doc(hidden)] /// Get a header @@ -128,7 +128,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn body(&self) -> MessageBody { + fn body(self) -> MessageBody { MessageBody::new(self) } @@ -162,7 +162,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn urlencoded(&self) -> UrlEncoded { + fn urlencoded(self) -> UrlEncoded { UrlEncoded::new(self) } @@ -198,12 +198,12 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn json(&self) -> JsonBody { + fn json(self) -> JsonBody { JsonBody::new(self) } /// Return stream of lines. - fn readlines(&self) -> Readlines { + fn readlines(self) -> Readlines { Readlines::new(self) } } @@ -220,7 +220,7 @@ pub struct Readlines { impl Readlines { /// Create a new stream to read request line by line. - fn new(req: &T) -> Self { + fn new(req: T) -> Self { let encoding = match req.encoding() { Ok(enc) => enc, Err(err) => return Self::err(req, err.into()), @@ -242,7 +242,7 @@ impl Readlines { self } - fn err(req: &T, err: ReadlinesError) -> Self { + fn err(req: T, err: ReadlinesError) -> Self { Readlines { stream: req.payload(), buff: BytesMut::new(), @@ -362,7 +362,7 @@ pub struct MessageBody { impl MessageBody { /// Create `MessageBody` for request. - pub fn new(req: &T) -> MessageBody { + pub fn new(req: T) -> MessageBody { let mut len = None; if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -457,7 +457,7 @@ pub struct UrlEncoded { impl UrlEncoded { /// Create a new future to URL encode a request - pub fn new(req: &T) -> UrlEncoded { + pub fn new(req: T) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -800,7 +800,7 @@ mod tests { Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .finish(); - let mut r = Readlines::new(&req); + let mut r = Readlines::new(req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/json.rs b/src/json.rs index d06449cb..fc1ab4d2 100644 --- a/src/json.rs +++ b/src/json.rs @@ -50,7 +50,7 @@ pub struct JsonBody { impl JsonBody { /// Create `JsonBody` for request. - pub fn new(req: &T) -> Self { + pub fn new(req: T) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) diff --git a/src/message.rs b/src/message.rs index 31d61f63..a7339222 100644 --- a/src/message.rs +++ b/src/message.rs @@ -5,7 +5,6 @@ use std::rc::Rc; use http::{HeaderMap, Method, StatusCode, Uri, Version}; use crate::extensions::Extensions; -use crate::payload::Payload; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -149,7 +148,6 @@ impl ResponseHead { pub struct Message { pub head: T, pub extensions: RefCell, - pub payload: RefCell>, pub(crate) pool: &'static MessagePool, } @@ -159,7 +157,6 @@ impl Message { pub fn reset(&mut self) { self.head.clear(); self.extensions.borrow_mut().clear(); - *self.payload.borrow_mut() = None; } } @@ -168,7 +165,6 @@ impl Default for Message { Message { pool: T::pool(), head: T::default(), - payload: RefCell::new(None), extensions: RefCell::new(Extensions::new()), } } diff --git a/src/request.rs b/src/request.rs index 60ddee19..b60a772e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -2,43 +2,76 @@ use std::cell::{Ref, RefMut}; use std::fmt; use std::rc::Rc; +use bytes::Bytes; +use futures::Stream; use http::{header, HeaderMap, Method, Uri, Version}; +use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, MessagePool, RequestHead}; use crate::payload::Payload; /// Request -pub struct Request { +pub struct Request

    { + pub(crate) payload: Option

    , pub(crate) inner: Rc>, } -impl HttpMessage for Request { - type Stream = Payload; +impl

    HttpMessage for Request

    +where + P: Stream, +{ + type Stream = P; fn headers(&self) -> &HeaderMap { &self.inner.head.headers } #[inline] - fn payload(&self) -> Payload { - if let Some(payload) = self.inner.payload.borrow_mut().take() { - payload - } else { - Payload::empty() + fn payload(mut self) -> P { + self.payload.take().unwrap() + } +} + +impl Request { + /// Create new Request instance + pub fn new() -> Request { + Request { + payload: Some(Payload::empty()), + inner: MessagePool::get_message(), } } } -impl Request { +impl Request { /// Create new Request instance - pub fn new() -> Request { + pub fn with_payload(payload: Payload) -> Request { Request { + payload: Some(payload), inner: MessagePool::get_message(), } } + /// Create new Request instance + pub fn set_payload

    (self, payload: P) -> Request

    { + Request { + payload: Some(payload), + inner: self.inner.clone(), + } + } + + /// Take request's payload + pub fn take_payload(mut self) -> (Payload, Request<()>) { + ( + self.payload.take().unwrap(), + Request { + payload: Some(()), + inner: self.inner.clone(), + }, + ) + } + // /// Create new Request instance with pool // pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { // Request { @@ -143,17 +176,17 @@ impl Request { self.inner().head.method == Method::CONNECT } - #[doc(hidden)] - /// Note: this method should be called only as part of clone operation - /// of wrapper type. - pub fn clone_request(&self) -> Self { - Request { - inner: self.inner.clone(), - } - } + // #[doc(hidden)] + // /// Note: this method should be called only as part of clone operation + // /// of wrapper type. + // pub fn clone_request(&self) -> Self { + // Request { + // inner: self.inner.clone(), + // } + // } } -impl Drop for Request { +impl Drop for Request { fn drop(&mut self) { if Rc::strong_count(&self.inner) == 1 { self.inner.pool.release(self.inner.clone()); @@ -161,7 +194,7 @@ impl Drop for Request { } } -impl fmt::Debug for Request { +impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, diff --git a/src/test.rs b/src/test.rs index a26f31e5..852dd3c0 100644 --- a/src/test.rs +++ b/src/test.rs @@ -148,15 +148,18 @@ impl TestRequest { .. } = self; - let mut req = Request::new(); - { - let inner = req.inner_mut(); - inner.head.uri = uri; - inner.head.method = method; - inner.head.version = version; - inner.head.headers = headers; - *inner.payload.borrow_mut() = payload; - } + let mut req = if let Some(pl) = payload { + Request::with_payload(pl) + } else { + Request::with_payload(Payload::empty()) + }; + + let inner = req.inner_mut(); + inner.head.uri = uri; + inner.head.method = method; + inner.head.version = version; + inner.head.headers = headers; + // req.set_cookies(cookies); req } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 81a3d909..9c71a25c 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -28,6 +28,9 @@ default = ["session"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] +# openssl +ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] + [dependencies] actix-codec = "0.1" actix-service = "0.2.0" @@ -52,3 +55,5 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" + +openssl = { version="0.10", optional = true } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 8083ebb1..3d6d917e 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -3,6 +3,12 @@ use std::sync::mpsc; use std::{net, thread}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_http::body::MessageBody; +use actix_http::client::{ + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, + ConnectorError, SendRequestError, +}; +use actix_http::ws; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; use actix_service::Service; @@ -11,13 +17,6 @@ use futures::future::{lazy, Future}; use http::Method; use net2::TcpBuilder; -use actix_http::body::MessageBody; -use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, - ConnectorError, SendRequestError, -}; -use actix_http::ws; - /// The `TestServer` type. /// /// `TestServer` is very simple test server that simplify process of writing @@ -101,6 +100,9 @@ impl TestServer { let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); Connector::default().ssl(builder.build()).service() } #[cfg(not(feature = "ssl"))] @@ -151,6 +153,15 @@ impl TestServerRuntime { } } + /// Construct test https server url + pub fn surl(&self, uri: &str) -> String { + if uri.starts_with('/') { + format!("https://127.0.0.1:{}{}", self.addr.port(), uri) + } else { + format!("https://127.0.0.1:{}/{}", self.addr.port(), uri) + } + } + /// Create `GET` request pub fn get(&self) -> ClientRequestBuilder { ClientRequest::get(self.url("/").as_str()) diff --git a/tests/cert.pem b/tests/cert.pem index db04fbfa..5e195d98 100644 --- a/tests/cert.pem +++ b/tests/cert.pem @@ -1,31 +1,16 @@ -----BEGIN CERTIFICATE----- -MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV -BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh -bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4 -MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD -T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF -cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk -L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ -EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU -05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh -4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA -2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng -dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4 -e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT -2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa -TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID -AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB -AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm -ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g -4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1 -hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe -0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq -seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi -7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO -3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5 -XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq -GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr -E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv +MIICljCCAX4CCQDFdWu66640QjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJ1 +czAeFw0xOTAyMDQyMzEyNTBaFw0yMDAyMDQyMzEyNTBaMA0xCzAJBgNVBAYTAnVz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzZUXMnS5X8HWxTvHAc82 +Q2d32fiPQGtD+fp3OV90l6RC9jgMdH4yTVUgX5mYYcW0k89RaP8g61H6b76F9gcd +yZ1idqKI1AU9aeBUPV8wkrouhR/6Omv8fA7yr9tVmNo53jPN7WyKoBoU0r7Yj9Ez +g3qjv/808Jlgby3EhduruyyfdvSt5ZFXnOz2D3SF9DS4yrM2jSw4ZTuoVMfZ8vZe +FVzLo/+sV8qokU6wBTEOAmZQ7e/zZV4qAoH2Z3Vj/uD1Zr/MXYyh81RdXpDqIXwV +Z29LEOa2eTGFEdvfG+tdvvuIvSdF3+WbLrwn2ECfwJ8zmKyTauPRV4pj7ks+wkBI +EQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB6dmuWBOpFfDdu0mdsDb8XnJY1svjH +4kbztXhjQJ/WuhCUIwvXFyz9dqQCq+TbJUbUEzZJEfaq1uaI3iB5wd35ArSoAGJA +k0lonzyeSM+cmNOe/5BPqWhd1qPwbsfgMoCCkZUoTT5Rvw6yt00XIqZzMqrsvRBX +hAcUW3zBtFQNP6aQqsMdn4ClZE0WHf+LzWy2NQh+Sf46tSYBHELfdUawgR789PB4 +/gNjAeklq06JmE/3gELijwaijVIuUsMC9ua//ITk4YIFpqanPtka+7BpfTegPGNs +HCj1g7Jot97oQMuvDOJeso91aiSA+gutepCClZICT8LxNRkY3ZlXYp92 -----END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem index aac387c6..50ded0ce 100644 --- a/tests/key.pem +++ b/tests/key.pem @@ -1,51 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP -n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M -IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5 -4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ -WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk -oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli -JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6 -/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD -YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP -wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA -69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA -AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/ -9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm -YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR -6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM -ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI -7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab -L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+ -vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ -b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz -0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL -OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI -6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC -71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g -9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu -bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb -IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga -/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc -KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2 -iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP -tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD -jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY -l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj -gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh -Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q -1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW -t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI -fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9 -5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt -+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc -3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf -cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T -qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU -DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K -5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc -fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc -Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ -4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6 -I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDNlRcydLlfwdbF +O8cBzzZDZ3fZ+I9Aa0P5+nc5X3SXpEL2OAx0fjJNVSBfmZhhxbSTz1Fo/yDrUfpv +voX2Bx3JnWJ2oojUBT1p4FQ9XzCSui6FH/o6a/x8DvKv21WY2jneM83tbIqgGhTS +vtiP0TODeqO//zTwmWBvLcSF26u7LJ929K3lkVec7PYPdIX0NLjKszaNLDhlO6hU +x9ny9l4VXMuj/6xXyqiRTrAFMQ4CZlDt7/NlXioCgfZndWP+4PVmv8xdjKHzVF1e +kOohfBVnb0sQ5rZ5MYUR298b612++4i9J0Xf5ZsuvCfYQJ/AnzOYrJNq49FXimPu +Sz7CQEgRAgMBAAECggEBALC547EaKmko5wmyM4dYq9sRzTPxuqO0EkGIkIkfh8j8 +ChxDXmGeQnu8HBJSpW4XWP5fkCpkd9YTKOh6rgorX+37f7NgUaOBxaOIlqITfFwF +9Qu3y5IBVpEHAJUwRcsaffiILBRX5GtxQElSijRHsLLr8GySZN4X25B3laNEjcJe +NWJrDaxOn0m0MMGRvBpM8PaZu1Mn9NWxt04b/fteVLdN4TAcuY9TgvVZBq92S2FM +qvZcnJCQckNOuMOptVdP45qPkerKUohpOcqBfIiWFaalC378jE3Dm68p7slt3R6y +I1wVqCI4+MZfM3CtKcYJV0fdqklJCvXORvRiT8OZKakCgYEA5YnhgXOu4CO4DR1T +Lacv716DPyHeKVa6TbHhUhWw4bLwNLUsEL98jeU9SZ6VH8enBlDm5pCsp2i//t9n +8hoykN4L0rS4EyAGENouTRkLhtHfjTAKTKDK8cNvEaS8NOBJWrI0DTiHtFbCRBvI +zRx5VhrB5H4DDbqn7QV9g+GBKvMCgYEA5Ug3bN0RNUi3KDoIRcnWc06HsX307su7 +tB4cGqXJqVOJCrkk5sefGF502+W8m3Ldjaakr+Q9BoOdZX6boZnFtVetT8Hyzk1C +Rkiyz3GcwovOkQK//UmljsuRjgHF+PuQGX5ol4YlJtXU21k5bCsi1Tmyp7IufiGV +AQRMVZVbeesCgYA/QBZGwKTgmJcf7gO8ocRAto999wwr4f0maazIHLgICXHNZFsH +JmzhANk5jxxSjIaG5AYsZJNe8ittxQv0l6l1Z+pkHm5Wvs1NGYIGtq8JcI2kbyd3 +ZBtoMU1K1FUUUPWFq3NSbVBfrkSL1ggoFP+ObYMePmcDAntBgfDLRXl9ZwKBgQCt +/dh5l2UIn27Gawt+EkXX6L8WVTQ6xoZhj/vZyPe4tDip14gGTXQQ5RUfDj7LZCZ2 +6P/OrpAU0mnt7F8kCfI7xBY0EUU1gvGJLn/q5heElt2hs4mIJ4woSZjiP7xBTn2y +qveqDNVCnEBUWGg4Cp/7WTaXBaM8ejV9uQpIY/gwEwKBgQCCYnd9fD8L4nGyOLoD +eUzMV7G8TZfinlxCNMVXfpn4Z8OaYHOk5NiujHK55w4ghx06NQw038qhnX0ogbjU +caWOwCIbrYgx2fwYuOZbJFXdjWlpjIK3RFOcbNCNgCRLT6Lgz4uZYZ9RVftADvMi +zR1QsLWnIvARbTtOPfZqizT2gQ== +-----END PRIVATE KEY----- diff --git a/tests/test_server.rs b/tests/test_server.rs index c23840ea..9fa27e71 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -5,16 +5,16 @@ use std::{net, thread}; use actix_http_test::TestServer; use actix_service::NewService; use bytes::Bytes; -use futures::future::{self, ok}; +use futures::future::{self, ok, Future}; use futures::stream::once; use actix_http::{ - body, client, h1, http, Body, Error, HttpMessage as HttpMessage2, KeepAlive, + body, client, h1, h2, http, Body, Error, HttpMessage as HttpMessage2, KeepAlive, Request, Response, }; #[test] -fn test_h1_v2() { +fn test_h1() { let mut srv = TestServer::with_factory(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) @@ -30,6 +30,85 @@ fn test_h1_v2() { assert!(response.status().is_success()); } +#[cfg(feature = "ssl")] +fn ssl_acceptor() -> std::io::Result> { + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(openssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) +} + +#[cfg(feature = "ssl")] +#[test] +fn test_h2() -> std::io::Result<()> { + let openssl = ssl_acceptor()?; + let mut srv = TestServer::with_factory(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + h2::H2Service::build() + .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); + println!("RES: {:?}", response); + assert!(response.status().is_success()); + Ok(()) +} + +#[cfg(feature = "ssl")] +#[test] +fn test_h2_body() -> std::io::Result<()> { + // std::env::set_var("RUST_LOG", "actix_http=trace"); + // env_logger::init(); + + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let openssl = ssl_acceptor()?; + let mut srv = TestServer::with_factory(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + h2::H2Service::build() + .finish(|req: Request<_>| { + req.body() + .limit(1024 * 1024) + .and_then(|body| Ok(Response::Ok().body(body))) + }) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::get(srv.surl("/")) + .body(data.clone()) + .unwrap(); + let response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) +} + #[test] fn test_slow_request() { let srv = TestServer::with_factory(|| { From fcace161c7bd2fdf81b59b0b8ccefd16219105af Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Feb 2019 12:22:40 -0800 Subject: [PATCH 0896/1635] fix manifest features --- Cargo.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bbb31c16..8cc74446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl", "actix-connector/ssl", "actix-server/ssl", "actix-http-test/ssl"] +ssl = ["openssl"] [dependencies] actix-service = "0.2.1" @@ -76,7 +76,10 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" actix-web = "0.7" -actix-server = "0.2" -actix-http-test = { path="test-server" } +actix-server = { version="0.2", features=["ssl"] } +actix-connector = { version="0.2.0", features=["ssl"] } +actix-utils = "0.2.1" +actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" +openssl = { version="0.10" } From b018e4abafa79e2d00d98b40c51c282c4b90e730 Mon Sep 17 00:00:00 2001 From: Jason Hills Date: Wed, 6 Feb 2019 14:37:43 -0700 Subject: [PATCH 0897/1635] Fixes TestRequest::with_cookie panic --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index f2346115..1d86db9f 100644 --- a/src/test.rs +++ b/src/test.rs @@ -558,7 +558,7 @@ impl TestRequest { /// set cookie of this request pub fn cookie(mut self, cookie: Cookie<'static>) -> Self { - if self.cookies.is_none() { + if self.cookies.is_some() { let mut should_insert = true; let old_cookies = self.cookies.as_mut().unwrap(); for old_cookie in old_cookies.iter() { From cd83553db7a04f60d54f09ddc0e56c7e89a5fcc0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 11:06:05 -0800 Subject: [PATCH 0898/1635] simplify payload api; add missing http error helper functions --- Cargo.toml | 1 - examples/echo.rs | 4 +- examples/echo2.rs | 4 +- src/client/h1proto.rs | 21 +-- src/client/h2proto.rs | 3 +- src/client/request.rs | 19 +-- src/client/response.rs | 22 ++- src/error.rs | 314 +++++++++++++++++++++++++++++++++++++++- src/h1/dispatcher.rs | 2 +- src/h1/mod.rs | 2 + src/{ => h1}/payload.rs | 0 src/httpcodes.rs | 1 + src/httpmessage.rs | 137 ++++++++++-------- src/json.rs | 12 +- src/lib.rs | 2 - src/request.rs | 11 +- src/service/mod.rs | 2 + src/test.rs | 2 +- tests/test_client.rs | 4 +- tests/test_server.rs | 20 +-- tests/test_ws.rs | 34 ----- 21 files changed, 442 insertions(+), 175 deletions(-) rename src/{ => h1}/payload.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 8cc74446..23938f2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,6 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" -actix-web = "0.7" actix-server = { version="0.2", features=["ssl"] } actix-connector = { version="0.2.0", features=["ssl"] } actix-utils = "0.2.1" diff --git a/examples/echo.rs b/examples/echo.rs index 3bfb04d7..03d5b470 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -18,8 +18,8 @@ fn main() { .client_timeout(1000) .client_disconnect(1000) .server_hostname("localhost") - .finish(|_req: Request| { - _req.body().limit(512).and_then(|bytes: Bytes| { + .finish(|mut req: Request| { + req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); diff --git a/examples/echo2.rs b/examples/echo2.rs index 0e2bc9d5..2fd9cbcf 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -8,8 +8,8 @@ use futures::Future; use log::info; use std::env; -fn handle_request(_req: Request) -> impl Future { - _req.body().limit(512).from_err().and_then(|bytes: Bytes| { +fn handle_request(mut req: Request) -> impl Future { + req.body().limit(512).from_err().and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index 86fc10b8..59a03ef4 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -50,14 +50,14 @@ where .into_future() .map_err(|(e, _)| SendRequestError::from(e)) .and_then(|(item, framed)| { - if let Some(res) = item { + if let Some(mut res) = item { match framed.get_codec().message_type() { h1::MessageType::None => { let force_close = !framed.get_codec().keepalive(); release_connection(framed, force_close) } _ => { - *res.payload.borrow_mut() = Some(Payload::stream(framed)) + res.set_payload(Payload::stream(framed)); } } ok(res) @@ -199,27 +199,10 @@ where } } -struct EmptyPayload; - -impl Stream for EmptyPayload { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - Ok(Async::Ready(None)) - } -} - pub(crate) struct Payload { framed: Option>, } -impl Payload<()> { - pub fn empty() -> PayloadStream { - Box::new(EmptyPayload) - } -} - impl Payload { pub fn stream(framed: Framed) -> PayloadStream { Box::new(Payload { diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index ecd18cf8..f2f18d93 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::time; use actix_codec::{AsyncRead, AsyncWrite}; @@ -111,7 +110,7 @@ where Ok(ClientResponse { head, - payload: RefCell::new(Some(Box::new(Payload::new(body)))), + payload: Some(Box::new(Payload::new(body))), }) }) .from_err() diff --git a/src/client/request.rs b/src/client/request.rs index b80f0e6d..7e971756 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -268,10 +268,9 @@ impl ClientRequestBuilder { /// /// ```rust /// # extern crate mime; - /// # extern crate actix_web; - /// # use actix_web::client::*; + /// # extern crate actix_http; /// # - /// use actix_web::{client, http}; + /// use actix_http::{client, http}; /// /// fn main() { /// let req = client::ClientRequest::build() @@ -299,16 +298,14 @@ impl ClientRequestBuilder { /// To override header use `set_header()` method. /// /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// # use actix_web::client::*; + /// # extern crate actix_http; /// # - /// use http::header; + /// use actix_http::{client, http}; /// /// fn main() { - /// let req = ClientRequest::build() + /// let req = client::ClientRequest::build() /// .header("X-TEST", "value") - /// .header(header::CONTENT_TYPE, "application/json") + /// .header(http::header::CONTENT_TYPE, "application/json") /// .finish() /// .unwrap(); /// } @@ -427,8 +424,8 @@ impl ClientRequestBuilder { /// Set a cookie /// /// ```rust - /// # extern crate actix_web; - /// use actix_web::{client, http}; + /// # extern crate actix_http; + /// use actix_http::{client, http}; /// /// fn main() { /// let req = client::ClientRequest::build() diff --git a/src/client/response.rs b/src/client/response.rs index 6224d3cb..65c59f2a 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::fmt; use bytes::Bytes; @@ -10,13 +9,11 @@ use crate::error::PayloadError; use crate::httpmessage::HttpMessage; use crate::message::{Head, ResponseHead}; -use super::h1proto::Payload; - /// Client Response #[derive(Default)] pub struct ClientResponse { pub(crate) head: ResponseHead, - pub(crate) payload: RefCell>, + pub(crate) payload: Option, } impl HttpMessage for ClientResponse { @@ -27,12 +24,8 @@ impl HttpMessage for ClientResponse { } #[inline] - fn payload(self) -> Self::Stream { - if let Some(payload) = self.payload.borrow_mut().take() { - payload - } else { - Payload::empty() - } + fn payload(&mut self) -> Option { + self.payload.take() } } @@ -41,7 +34,7 @@ impl ClientResponse { pub fn new() -> ClientResponse { ClientResponse { head: ResponseHead::default(), - payload: RefCell::new(None), + payload: None, } } @@ -84,6 +77,11 @@ impl ClientResponse { pub fn keep_alive(&self) -> bool { self.head().keep_alive() } + + /// Set response payload + pub fn set_payload(&mut self, payload: PayloadStream) { + self.payload = Some(payload); + } } impl Stream for ClientResponse { @@ -91,7 +89,7 @@ impl Stream for ClientResponse { type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { - if let Some(ref mut payload) = &mut *self.payload.borrow_mut() { + if let Some(ref mut payload) = self.payload { payload.poll() } else { Ok(Async::Ready(None)) diff --git a/src/error.rs b/src/error.rs index 03224b55..f71b429f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -344,7 +344,7 @@ pub enum PayloadError { UnknownLength, /// Http2 payload error #[display(fmt = "{}", _0)] - H2Payload(h2::Error), + Http2Payload(h2::Error), } impl From for PayloadError { @@ -642,6 +642,16 @@ where InternalError::new(err, StatusCode::UNAUTHORIZED).into() } +/// Helper function that creates wrapper of any error and generate +/// *PAYMENT_REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorPaymentRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate *FORBIDDEN* /// response. #[allow(non_snake_case)] @@ -672,6 +682,26 @@ where InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() } +/// Helper function that creates wrapper of any error and generate *NOT +/// ACCEPTABLE* response. +#[allow(non_snake_case)] +pub fn ErrorNotAcceptable(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into() +} + +/// Helper function that creates wrapper of any error and generate *PROXY +/// AUTHENTICATION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorProxyAuthenticationRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate *REQUEST /// TIMEOUT* response. #[allow(non_snake_case)] @@ -702,6 +732,116 @@ where InternalError::new(err, StatusCode::GONE).into() } +/// Helper function that creates wrapper of any error and generate *LENGTH +/// REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorLengthRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LENGTH_REQUIRED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *PAYLOAD TOO LARGE* response. +#[allow(non_snake_case)] +pub fn ErrorPayloadTooLarge(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *URI TOO LONG* response. +#[allow(non_snake_case)] +pub fn ErrorUriTooLong(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::URI_TOO_LONG).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNSUPPORTED MEDIA TYPE* response. +#[allow(non_snake_case)] +pub fn ErrorUnsupportedMediaType(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *RANGE NOT SATISFIABLE* response. +#[allow(non_snake_case)] +pub fn ErrorRangeNotSatisfiable(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *IM A TEAPOT* response. +#[allow(non_snake_case)] +pub fn ErrorImATeapot(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::IM_A_TEAPOT).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *MISDIRECTED REQUEST* response. +#[allow(non_snake_case)] +pub fn ErrorMisdirectedRequest(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNPROCESSABLE ENTITY* response. +#[allow(non_snake_case)] +pub fn ErrorUnprocessableEntity(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *LOCKED* response. +#[allow(non_snake_case)] +pub fn ErrorLocked(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LOCKED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *FAILED DEPENDENCY* response. +#[allow(non_snake_case)] +pub fn ErrorFailedDependency(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UPGRADE REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorUpgradeRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into() +} + /// Helper function that creates wrapper of any error and generate /// *PRECONDITION FAILED* response. #[allow(non_snake_case)] @@ -712,6 +852,46 @@ where InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() } +/// Helper function that creates wrapper of any error and generate +/// *PRECONDITION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorPreconditionRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *TOO MANY REQUESTS* response. +#[allow(non_snake_case)] +pub fn ErrorTooManyRequests(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *REQUEST HEADER FIELDS TOO LARGE* response. +#[allow(non_snake_case)] +pub fn ErrorRequestHeaderFieldsTooLarge(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into() +} + +/// Helper function that creates wrapper of any error and generate +/// *UNAVAILABLE FOR LEGAL REASONS* response. +#[allow(non_snake_case)] +pub fn ErrorUnavailableForLegalReasons(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into() +} + /// Helper function that creates wrapper of any error and generate /// *EXPECTATION FAILED* response. #[allow(non_snake_case)] @@ -772,6 +952,66 @@ where InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() } +/// Helper function that creates wrapper of any error and +/// generate *HTTP VERSION NOT SUPPORTED* response. +#[allow(non_snake_case)] +pub fn ErrorHttpVersionNotSupported(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *VARIANT ALSO NEGOTIATES* response. +#[allow(non_snake_case)] +pub fn ErrorVariantAlsoNegotiates(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *INSUFFICIENT STORAGE* response. +#[allow(non_snake_case)] +pub fn ErrorInsufficientStorage(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *LOOP DETECTED* response. +#[allow(non_snake_case)] +pub fn ErrorLoopDetected(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::LOOP_DETECTED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *NOT EXTENDED* response. +#[allow(non_snake_case)] +pub fn ErrorNotExtended(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NOT_EXTENDED).into() +} + +/// Helper function that creates wrapper of any error and +/// generate *NETWORK AUTHENTICATION REQUIRED* response. +#[allow(non_snake_case)] +pub fn ErrorNetworkAuthenticationRequired(err: T) -> Error +where + T: fmt::Debug + fmt::Display + 'static, +{ + InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() +} + #[cfg(test)] mod tests { use super::*; @@ -926,6 +1166,9 @@ mod tests { let r: Response = ErrorUnauthorized("err").into(); assert_eq!(r.status(), StatusCode::UNAUTHORIZED); + let r: Response = ErrorPaymentRequired("err").into(); + assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED); + let r: Response = ErrorForbidden("err").into(); assert_eq!(r.status(), StatusCode::FORBIDDEN); @@ -935,6 +1178,12 @@ mod tests { let r: Response = ErrorMethodNotAllowed("err").into(); assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); + let r: Response = ErrorNotAcceptable("err").into(); + assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE); + + let r: Response = ErrorProxyAuthenticationRequired("err").into(); + assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); + let r: Response = ErrorRequestTimeout("err").into(); assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); @@ -944,12 +1193,57 @@ mod tests { let r: Response = ErrorGone("err").into(); assert_eq!(r.status(), StatusCode::GONE); + let r: Response = ErrorLengthRequired("err").into(); + assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED); + let r: Response = ErrorPreconditionFailed("err").into(); assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); + let r: Response = ErrorPayloadTooLarge("err").into(); + assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE); + + let r: Response = ErrorUriTooLong("err").into(); + assert_eq!(r.status(), StatusCode::URI_TOO_LONG); + + let r: Response = ErrorUnsupportedMediaType("err").into(); + assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); + + let r: Response = ErrorRangeNotSatisfiable("err").into(); + assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE); + let r: Response = ErrorExpectationFailed("err").into(); assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); + let r: Response = ErrorImATeapot("err").into(); + assert_eq!(r.status(), StatusCode::IM_A_TEAPOT); + + let r: Response = ErrorMisdirectedRequest("err").into(); + assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST); + + let r: Response = ErrorUnprocessableEntity("err").into(); + assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY); + + let r: Response = ErrorLocked("err").into(); + assert_eq!(r.status(), StatusCode::LOCKED); + + let r: Response = ErrorFailedDependency("err").into(); + assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY); + + let r: Response = ErrorUpgradeRequired("err").into(); + assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED); + + let r: Response = ErrorPreconditionRequired("err").into(); + assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED); + + let r: Response = ErrorTooManyRequests("err").into(); + assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS); + + let r: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); + assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); + + let r: Response = ErrorUnavailableForLegalReasons("err").into(); + assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); + let r: Response = ErrorInternalServerError("err").into(); assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -964,5 +1258,23 @@ mod tests { let r: Response = ErrorGatewayTimeout("err").into(); assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); + + let r: Response = ErrorHttpVersionNotSupported("err").into(); + assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); + + let r: Response = ErrorVariantAlsoNegotiates("err").into(); + assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); + + let r: Response = ErrorInsufficientStorage("err").into(); + assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE); + + let r: Response = ErrorLoopDetected("err").into(); + assert_eq!(r.status(), StatusCode::LOOP_DETECTED); + + let r: Response = ErrorNotExtended("err").into(); + assert_eq!(r.status(), StatusCode::NOT_EXTENDED); + + let r: Response = ErrorNetworkAuthenticationRequired("err").into(); + assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 1295dfdd..0a0ed04e 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -14,11 +14,11 @@ use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::DispatchError; use crate::error::{ParseError, PayloadError}; -use crate::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use crate::request::Request; use crate::response::Response; use super::codec::Codec; +use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use super::{H1ServiceResult, Message, MessageType}; const MAX_PIPELINED_MESSAGES: usize = 16; diff --git a/src/h1/mod.rs b/src/h1/mod.rs index a375b60d..9054c266 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -9,11 +9,13 @@ mod codec; mod decoder; mod dispatcher; mod encoder; +mod payload; mod service; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; +pub use self::payload::{Payload, PayloadBuffer}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; use crate::request::Request; diff --git a/src/payload.rs b/src/h1/payload.rs similarity index 100% rename from src/payload.rs rename to src/h1/payload.rs diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 7806bd80..5dfeefa9 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -54,6 +54,7 @@ impl Response { STATIC_RESP!(Gone, StatusCode::GONE); STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED); STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); + STATIC_RESP!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index c50de2e9..39aa1b68 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -25,7 +25,7 @@ pub trait HttpMessage: Sized { fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(self) -> Self::Stream; + fn payload(&mut self) -> Option; #[doc(hidden)] /// Get a header @@ -128,7 +128,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn body(self) -> MessageBody { + fn body(&mut self) -> MessageBody { MessageBody::new(self) } @@ -162,7 +162,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn urlencoded(self) -> UrlEncoded { + fn urlencoded(&mut self) -> UrlEncoded { UrlEncoded::new(self) } @@ -198,19 +198,19 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn json(self) -> JsonBody { + fn json(&mut self) -> JsonBody { JsonBody::new(self) } /// Return stream of lines. - fn readlines(self) -> Readlines { + fn readlines(&mut self) -> Readlines { Readlines::new(self) } } /// Stream to read request line by line. pub struct Readlines { - stream: T::Stream, + stream: Option, buff: BytesMut, limit: usize, checked_buff: bool, @@ -220,7 +220,7 @@ pub struct Readlines { impl Readlines { /// Create a new stream to read request line by line. - fn new(req: T) -> Self { + fn new(req: &mut T) -> Self { let encoding = match req.encoding() { Ok(enc) => enc, Err(err) => return Self::err(req, err.into()), @@ -242,7 +242,7 @@ impl Readlines { self } - fn err(req: T, err: ReadlinesError) -> Self { + fn err(req: &mut T, err: ReadlinesError) -> Self { Readlines { stream: req.payload(), buff: BytesMut::new(), @@ -292,61 +292,65 @@ impl Stream for Readlines { self.checked_buff = true; } // poll req for more bytes - match self.stream.poll() { - Ok(Async::Ready(Some(mut bytes))) => { - // check if there is a newline in bytes - let mut found: Option = None; - for (ind, b) in bytes.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; + if let Some(ref mut stream) = self.stream { + match stream.poll() { + Ok(Async::Ready(Some(mut bytes))) => { + // check if there is a newline in bytes + let mut found: Option = None; + for (ind, b) in bytes.iter().enumerate() { + if *b == b'\n' { + found = Some(ind); + break; + } } + if let Some(ind) = found { + // check if line is longer than limit + if ind + 1 > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = self.encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&bytes.split_to(ind + 1)) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + self.encoding + .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + // extend buffer with rest of the bytes; + self.buff.extend_from_slice(&bytes); + self.checked_buff = false; + return Ok(Async::Ready(Some(line))); + } + self.buff.extend_from_slice(&bytes); + Ok(Async::NotReady) } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + if self.buff.is_empty() { + return Ok(Async::Ready(None)); + } + if self.buff.len() > self.limit { return Err(ReadlinesError::LimitOverflow); } let enc: *const Encoding = self.encoding as *const Encoding; let line = if enc == UTF_8 { - str::from_utf8(&bytes.split_to(ind + 1)) + str::from_utf8(&self.buff) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { self.encoding - .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) + .decode(&self.buff, DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; - // extend buffer with rest of the bytes; - self.buff.extend_from_slice(&bytes); - self.checked_buff = false; - return Ok(Async::Ready(Some(line))); + self.buff.clear(); + Ok(Async::Ready(Some(line))) } - self.buff.extend_from_slice(&bytes); - Ok(Async::NotReady) + Err(e) => Err(ReadlinesError::from(e)), } - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - if self.buff.is_empty() { - return Ok(Async::Ready(None)); - } - if self.buff.len() > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&self.buff, DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - self.buff.clear(); - Ok(Async::Ready(Some(line))) - } - Err(e) => Err(ReadlinesError::from(e)), + } else { + Ok(Async::Ready(None)) } } } @@ -362,7 +366,7 @@ pub struct MessageBody { impl MessageBody { /// Create `MessageBody` for request. - pub fn new(req: T) -> MessageBody { + pub fn new(req: &mut T) -> MessageBody { let mut len = None; if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -379,7 +383,7 @@ impl MessageBody { MessageBody { limit: 262_144, length: len, - stream: Some(req.payload()), + stream: req.payload(), fut: None, err: None, } @@ -424,6 +428,10 @@ where } } + if self.stream.is_none() { + return Ok(Async::Ready(Bytes::new())); + } + // future let limit = self.limit; self.fut = Some(Box::new( @@ -457,7 +465,7 @@ pub struct UrlEncoded { impl UrlEncoded { /// Create a new future to URL encode a request - pub fn new(req: T) -> UrlEncoded { + pub fn new(req: &mut T) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -482,7 +490,7 @@ impl UrlEncoded { UrlEncoded { encoding, - stream: Some(req.payload()), + stream: req.payload(), limit: 262_144, length: len, fut: None, @@ -694,7 +702,7 @@ mod tests { #[test] fn test_urlencoded_error() { - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -705,7 +713,7 @@ mod tests { UrlencodedError::UnknownLength ); - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -716,7 +724,7 @@ mod tests { UrlencodedError::Overflow ); - let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") + let mut req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") .header(header::CONTENT_LENGTH, "10") .finish(); assert_eq!( @@ -727,7 +735,7 @@ mod tests { #[test] fn test_urlencoded() { - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -743,7 +751,7 @@ mod tests { }) ); - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) @@ -762,19 +770,20 @@ mod tests { #[test] fn test_message_body() { - let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().poll().err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), } - let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = + TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), } - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static(b"test")) .finish(); match req.body().poll().ok().unwrap() { @@ -782,7 +791,7 @@ mod tests { _ => unreachable!("error"), } - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).poll().err().unwrap() { @@ -793,14 +802,14 @@ mod tests { #[test] fn test_readlines() { - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .finish(); - let mut r = Readlines::new(req); + let mut r = Readlines::new(&mut req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/json.rs b/src/json.rs index fc1ab4d2..6cd1e87a 100644 --- a/src/json.rs +++ b/src/json.rs @@ -50,7 +50,7 @@ pub struct JsonBody { impl JsonBody { /// Create `JsonBody` for request. - pub fn new(req: T) -> Self { + pub fn new(req: &mut T) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) @@ -79,7 +79,7 @@ impl JsonBody { JsonBody { limit: 262_144, length: len, - stream: Some(req.payload()), + stream: req.payload(), fut: None, err: None, } @@ -164,11 +164,11 @@ mod tests { #[test] fn test_json_body() { - let req = TestRequest::default().finish(); + let mut req = TestRequest::default().finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), @@ -177,7 +177,7 @@ mod tests { let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -190,7 +190,7 @@ mod tests { let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), diff --git a/src/lib.rs b/src/lib.rs index cdb4f038..a3ca52ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,7 +78,6 @@ mod httpcodes; mod httpmessage; mod json; mod message; -mod payload; mod request; mod response; mod service; @@ -111,7 +110,6 @@ pub mod dev { pub use crate::httpmessage::{MessageBody, Readlines, UrlEncoded}; pub use crate::json::JsonBody; - pub use crate::payload::{Payload, PayloadBuffer}; pub use crate::response::ResponseBuilder; } diff --git a/src/request.rs b/src/request.rs index b60a772e..4df95d45 100644 --- a/src/request.rs +++ b/src/request.rs @@ -10,7 +10,8 @@ use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, MessagePool, RequestHead}; -use crate::payload::Payload; + +use crate::h1::Payload; /// Request pub struct Request

    { @@ -29,8 +30,8 @@ where } #[inline] - fn payload(mut self) -> P { - self.payload.take().unwrap() + fn payload(&mut self) -> Option

    { + self.payload.take() } } @@ -62,9 +63,9 @@ impl Request { } /// Take request's payload - pub fn take_payload(mut self) -> (Payload, Request<()>) { + pub fn take_payload(mut self) -> (Option, Request<()>) { ( - self.payload.take().unwrap(), + self.payload.take(), Request { payload: Some(()), inner: self.inner.clone(), diff --git a/src/service/mod.rs b/src/service/mod.rs index 83a40bd1..3939ab99 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,3 +1,5 @@ +use h2::RecvStream; + mod senderror; pub use self::senderror::{SendError, SendResponse}; diff --git a/src/test.rs b/src/test.rs index 852dd3c0..c68bbf8e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,8 +6,8 @@ use cookie::Cookie; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use crate::h1::Payload; use crate::header::{Header, IntoHeaderValue}; -use crate::payload::Payload; use crate::Request; /// Test `Request` builder diff --git a/tests/test_client.rs b/tests/test_client.rs index 606bac22..6f502b0a 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -47,7 +47,7 @@ fn test_h1_v2() { assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -55,7 +55,7 @@ fn test_h1_v2() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = srv.post().finish().unwrap(); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response diff --git a/tests/test_server.rs b/tests/test_server.rs index 9fa27e71..53db3840 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -89,7 +89,7 @@ fn test_h2_body() -> std::io::Result<()> { .map_err(|e| println!("Openssl error: {}", e)) .and_then( h2::H2Service::build() - .finish(|req: Request<_>| { + .finish(|mut req: Request<_>| { req.body() .limit(1024 * 1024) .and_then(|body| Ok(Response::Ok().body(body))) @@ -101,7 +101,7 @@ fn test_h2_body() -> std::io::Result<()> { let req = client::ClientRequest::get(srv.surl("/")) .body(data.clone()) .unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); @@ -350,7 +350,7 @@ fn test_headers() { let req = srv.get().finish().unwrap(); - let response = srv.block_on(req.send(&mut connector)).unwrap(); + let mut response = srv.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -387,7 +387,7 @@ fn test_body() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -402,7 +402,7 @@ fn test_head_empty() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -428,7 +428,7 @@ fn test_head_binary() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -477,7 +477,7 @@ fn test_body_length() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -496,7 +496,7 @@ fn test_body_chunked_explicit() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -517,7 +517,7 @@ fn test_body_chunked_implicit() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -540,7 +540,7 @@ fn test_response_http_error_handling() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 07f857d7..bf5a3c41 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -5,7 +5,6 @@ use actix_http_test::TestServer; use actix_service::NewService; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; -use actix_web::ws as web_ws; use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Either}; use futures::{Future, Sink, Stream}; @@ -105,37 +104,4 @@ fn test_simple() { item, Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) ); - - { - let mut sys = actix_web::actix::System::new("test"); - let url = srv.url("/"); - - let (reader, mut writer) = sys - .block_on(lazy(|| web_ws::Client::new(url).connect())) - .unwrap(); - - writer.text("text"); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); - assert_eq!(item, Some(web_ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(web_ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = sys.block_on(reader.into_future()).unwrap(); - assert_eq!(item, Some(web_ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(web_ws::CloseCode::Normal.into())); - let (item, _) = sys.block_on(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(web_ws::Message::Close(Some( - web_ws::CloseCode::Normal.into() - ))) - ); - } } From c4596b0bd6ce2758a79a93e11f2df77dc1f0f94a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 13:24:24 -0800 Subject: [PATCH 0899/1635] add headers from actix-web --- Cargo.toml | 3 + src/header.rs | 155 ---- src/header/common/accept.rs | 160 ++++ src/header/common/accept_charset.rs | 69 ++ src/header/common/accept_encoding.rs | 72 ++ src/header/common/accept_language.rs | 75 ++ src/header/common/allow.rs | 85 +++ src/header/common/cache_control.rs | 257 +++++++ src/header/common/content_disposition.rs | 918 +++++++++++++++++++++++ src/header/common/content_language.rs | 65 ++ src/header/common/content_range.rs | 208 +++++ src/header/common/content_type.rs | 122 +++ src/header/common/date.rs | 42 ++ src/header/common/etag.rs | 96 +++ src/header/common/expires.rs | 39 + src/header/common/if_match.rs | 70 ++ src/header/common/if_modified_since.rs | 39 + src/header/common/if_none_match.rs | 92 +++ src/header/common/if_range.rs | 116 +++ src/header/common/if_unmodified_since.rs | 40 + src/header/common/last_modified.rs | 38 + src/header/common/mod.rs | 352 +++++++++ src/header/common/range.rs | 434 +++++++++++ src/header/mod.rs | 465 ++++++++++++ src/header/shared/charset.rs | 153 ++++ src/header/shared/encoding.rs | 58 ++ src/header/shared/entity.rs | 265 +++++++ src/header/shared/httpdate.rs | 118 +++ src/header/shared/mod.rs | 14 + src/header/shared/quality_item.rs | 291 +++++++ tests/test_ws.rs | 2 +- 31 files changed, 4757 insertions(+), 156 deletions(-) delete mode 100644 src/header.rs create mode 100644 src/header/common/accept.rs create mode 100644 src/header/common/accept_charset.rs create mode 100644 src/header/common/accept_encoding.rs create mode 100644 src/header/common/accept_language.rs create mode 100644 src/header/common/allow.rs create mode 100644 src/header/common/cache_control.rs create mode 100644 src/header/common/content_disposition.rs create mode 100644 src/header/common/content_language.rs create mode 100644 src/header/common/content_range.rs create mode 100644 src/header/common/content_type.rs create mode 100644 src/header/common/date.rs create mode 100644 src/header/common/etag.rs create mode 100644 src/header/common/expires.rs create mode 100644 src/header/common/if_match.rs create mode 100644 src/header/common/if_modified_since.rs create mode 100644 src/header/common/if_none_match.rs create mode 100644 src/header/common/if_range.rs create mode 100644 src/header/common/if_unmodified_since.rs create mode 100644 src/header/common/last_modified.rs create mode 100644 src/header/common/mod.rs create mode 100644 src/header/common/range.rs create mode 100644 src/header/mod.rs create mode 100644 src/header/shared/charset.rs create mode 100644 src/header/shared/encoding.rs create mode 100644 src/header/shared/entity.rs create mode 100644 src/header/shared/httpdate.rs create mode 100644 src/header/shared/mod.rs create mode 100644 src/header/shared/quality_item.rs diff --git a/Cargo.toml b/Cargo.toml index 23938f2f..ea246762 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,10 +56,13 @@ h2 = "0.1.16" http = "0.1.8" httparse = "1.3" indexmap = "1.0" +lazy_static = "1.0" +language-tags = "0.2" log = "0.4" mime = "0.3" percent-encoding = "1.0" rand = "0.6" +regex = "1.0" serde = "1.0" serde_json = "1.0" sha1 = "0.6" diff --git a/src/header.rs b/src/header.rs deleted file mode 100644 index 6276dd4f..00000000 --- a/src/header.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Various http headers - -use bytes::Bytes; -pub use http::header::*; -use http::Error as HttpError; -use mime::Mime; - -use crate::error::ParseError; -use crate::httpmessage::HttpMessage; - -#[doc(hidden)] -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ - /// Returns the name of the header field - fn name() -> HeaderName; - - /// Parse a header - fn parse(msg: &T) -> Result; -} - -#[doc(hidden)] -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(format!("{}", self))) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - Br, - /// A format using the zlib structure with deflate algorithm - Deflate, - /// Gzip algorithm - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - match self { - ContentEncoding::Identity | ContentEncoding::Auto => false, - _ => true, - } - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - ContentEncoding::Br => "br", - ContentEncoding::Gzip => "gzip", - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - ContentEncoding::Br => 1.1, - ContentEncoding::Gzip => 1.0, - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -// TODO: remove memory allocation -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - match AsRef::::as_ref(&s.trim().to_lowercase()) { - "br" => ContentEncoding::Br, - "gzip" => ContentEncoding::Gzip, - "deflate" => ContentEncoding::Deflate, - _ => ContentEncoding::Identity, - } - } -} diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs new file mode 100644 index 00000000..d52eba24 --- /dev/null +++ b/src/header/common/accept.rs @@ -0,0 +1,160 @@ +use mime::Mime; + +use crate::header::{qitem, QualityItem}; +use crate::http::header; + +header! { + /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) + /// + /// The `Accept` header field can be used by user agents to specify + /// response media types that are acceptable. Accept header fields can + /// be used to indicate that the request is specifically limited to a + /// small set of desired types, as in the case of a request for an + /// in-line image + /// + /// # ABNF + /// + /// ```text + /// Accept = #( media-range [ accept-params ] ) + /// + /// media-range = ( "*/*" + /// / ( type "/" "*" ) + /// / ( type "/" subtype ) + /// ) *( OWS ";" OWS parameter ) + /// accept-params = weight *( accept-ext ) + /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] + /// ``` + /// + /// # Example values + /// * `audio/*; q=0.2, audio/basic` + /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` + /// + /// # Examples + /// ```rust + /// # extern crate actix_http; + /// extern crate mime; + /// use actix_http::Response; + /// use actix_http::http::header::{Accept, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// + /// builder.set( + /// Accept(vec![ + /// qitem(mime::TEXT_HTML), + /// ]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate actix_http; + /// extern crate mime; + /// use actix_http::Response; + /// use actix_http::http::header::{Accept, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// + /// builder.set( + /// Accept(vec![ + /// qitem(mime::APPLICATION_JSON), + /// ]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate actix_http; + /// extern crate mime; + /// use actix_http::Response; + /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// + /// builder.set( + /// Accept(vec![ + /// qitem(mime::TEXT_HTML), + /// qitem("application/xhtml+xml".parse().unwrap()), + /// QualityItem::new( + /// mime::TEXT_XML, + /// q(900) + /// ), + /// qitem("image/webp".parse().unwrap()), + /// QualityItem::new( + /// mime::STAR_STAR, + /// q(800) + /// ), + /// ]) + /// ); + /// # } + /// ``` + (Accept, header::ACCEPT) => (QualityItem)+ + + test_accept { + // Tests from the RFC + test_header!( + test1, + vec![b"audio/*; q=0.2, audio/basic"], + Some(HeaderField(vec![ + QualityItem::new("audio/*".parse().unwrap(), q(200)), + qitem("audio/basic".parse().unwrap()), + ]))); + test_header!( + test2, + vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], + Some(HeaderField(vec![ + QualityItem::new(mime::TEXT_PLAIN, q(500)), + qitem(mime::TEXT_HTML), + QualityItem::new( + "text/x-dvi".parse().unwrap(), + q(800)), + qitem("text/x-c".parse().unwrap()), + ]))); + // Custom tests + test_header!( + test3, + vec![b"text/plain; charset=utf-8"], + Some(Accept(vec![ + qitem(mime::TEXT_PLAIN_UTF_8), + ]))); + test_header!( + test4, + vec![b"text/plain; charset=utf-8; q=0.5"], + Some(Accept(vec![ + QualityItem::new(mime::TEXT_PLAIN_UTF_8, + q(500)), + ]))); + + #[test] + fn test_fuzzing1() { + use crate::test::TestRequest; + let req = TestRequest::with_header(crate::header::ACCEPT, "chunk#;e").finish(); + let header = Accept::parse(&req); + assert!(header.is_ok()); + } + } +} + +impl Accept { + /// A constructor to easily create `Accept: */*`. + pub fn star() -> Accept { + Accept(vec![qitem(mime::STAR_STAR)]) + } + + /// A constructor to easily create `Accept: application/json`. + pub fn json() -> Accept { + Accept(vec![qitem(mime::APPLICATION_JSON)]) + } + + /// A constructor to easily create `Accept: text/*`. + pub fn text() -> Accept { + Accept(vec![qitem(mime::TEXT_STAR)]) + } + + /// A constructor to easily create `Accept: image/*`. + pub fn image() -> Accept { + Accept(vec![qitem(mime::IMAGE_STAR)]) + } +} diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs new file mode 100644 index 00000000..117e2015 --- /dev/null +++ b/src/header/common/accept_charset.rs @@ -0,0 +1,69 @@ +use crate::header::{Charset, QualityItem, ACCEPT_CHARSET}; + +header! { + /// `Accept-Charset` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) + /// + /// The `Accept-Charset` header field can be sent by a user agent to + /// indicate what charsets are acceptable in textual response content. + /// This field allows user agents capable of understanding more + /// comprehensive or special-purpose charsets to signal that capability + /// to an origin server that is capable of representing information in + /// those charsets. + /// + /// # ABNF + /// + /// ```text + /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) + /// ``` + /// + /// # Example values + /// * `iso-8859-5, unicode-1-1;q=0.8` + /// + /// # Examples + /// ```rust + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) + /// ); + /// # } + /// ``` + /// ```rust + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// AcceptCharset(vec![ + /// QualityItem::new(Charset::Us_Ascii, q(900)), + /// QualityItem::new(Charset::Iso_8859_10, q(200)), + /// ]) + /// ); + /// # } + /// ``` + /// ```rust + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) + /// ); + /// # } + /// ``` + (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ + + test_accept_charset { + /// Test case from RFC + test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); + } +} diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs new file mode 100644 index 00000000..c90f529b --- /dev/null +++ b/src/header/common/accept_encoding.rs @@ -0,0 +1,72 @@ +use header::{Encoding, QualityItem}; + +header! { + /// `Accept-Encoding` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) + /// + /// The `Accept-Encoding` header field can be used by user agents to + /// indicate what response content-codings are + /// acceptable in the response. An `identity` token is used as a synonym + /// for "no encoding" in order to communicate when no encoding is + /// preferred. + /// + /// # ABNF + /// + /// ```text + /// Accept-Encoding = #( codings [ weight ] ) + /// codings = content-coding / "identity" / "*" + /// ``` + /// + /// # Example values + /// * `compress, gzip` + /// * `` + /// * `*` + /// * `compress;q=0.5, gzip;q=1` + /// * `gzip;q=1.0, identity; q=0.5, *;q=0` + /// + /// # Examples + /// ``` + /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) + /// ); + /// ``` + /// ``` + /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![ + /// qitem(Encoding::Chunked), + /// qitem(Encoding::Gzip), + /// qitem(Encoding::Deflate), + /// ]) + /// ); + /// ``` + /// ``` + /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![ + /// qitem(Encoding::Chunked), + /// QualityItem::new(Encoding::Gzip, q(600)), + /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), + /// ]) + /// ); + /// ``` + (AcceptEncoding, "Accept-Encoding") => (QualityItem)* + + test_accept_encoding { + // From the RFC + test_header!(test1, vec![b"compress, gzip"]); + test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); + test_header!(test3, vec![b"*"]); + // Note: Removed quality 1 from gzip + test_header!(test4, vec![b"compress;q=0.5, gzip"]); + // Note: Removed quality 1 from gzip + test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + } +} diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs new file mode 100644 index 00000000..55879b57 --- /dev/null +++ b/src/header/common/accept_language.rs @@ -0,0 +1,75 @@ +use crate::header::{QualityItem, ACCEPT_LANGUAGE}; +use language_tags::LanguageTag; + +header! { + /// `Accept-Language` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) + /// + /// The `Accept-Language` header field can be used by user agents to + /// indicate the set of natural languages that are preferred in the + /// response. + /// + /// # ABNF + /// + /// ```text + /// Accept-Language = 1#( language-range [ weight ] ) + /// language-range = + /// ``` + /// + /// # Example values + /// * `da, en-gb;q=0.8, en;q=0.7` + /// * `en-us;q=1.0, en;q=0.5, fr` + /// + /// # Examples + /// + /// ```rust + /// # extern crate actix_http; + /// # extern crate language_tags; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// let mut langtag: LanguageTag = Default::default(); + /// langtag.language = Some("en".to_owned()); + /// langtag.region = Some("US".to_owned()); + /// builder.set( + /// AcceptLanguage(vec![ + /// qitem(langtag), + /// ]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate actix_http; + /// # #[macro_use] extern crate language_tags; + /// use actix_http::Response; + /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; + /// # + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// AcceptLanguage(vec![ + /// qitem(langtag!(da)), + /// QualityItem::new(langtag!(en;;;GB), q(800)), + /// QualityItem::new(langtag!(en), q(700)), + /// ]) + /// ); + /// # } + /// ``` + (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ + + test_accept_language { + // From the RFC + test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); + // Own test + test_header!( + test2, vec![b"en-US, en; q=0.5, fr"], + Some(AcceptLanguage(vec![ + qitem("en-US".parse().unwrap()), + QualityItem::new("en".parse().unwrap(), q(500)), + qitem("fr".parse().unwrap()), + ]))); + } +} diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs new file mode 100644 index 00000000..432cc00d --- /dev/null +++ b/src/header/common/allow.rs @@ -0,0 +1,85 @@ +use http::Method; +use http::header; + +header! { + /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) + /// + /// The `Allow` header field lists the set of methods advertised as + /// supported by the target resource. The purpose of this field is + /// strictly to inform the recipient of valid request methods associated + /// with the resource. + /// + /// # ABNF + /// + /// ```text + /// Allow = #method + /// ``` + /// + /// # Example values + /// * `GET, HEAD, PUT` + /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` + /// * `` + /// + /// # Examples + /// + /// ```rust + /// # extern crate http; + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::Allow; + /// use http::Method; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// Allow(vec![Method::GET]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate http; + /// # extern crate actix_http; + /// use actix_http::Response; + /// use actix_http::http::header::Allow; + /// use http::Method; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// Allow(vec![ + /// Method::GET, + /// Method::POST, + /// Method::PATCH, + /// ]) + /// ); + /// # } + /// ``` + (Allow, header::ALLOW) => (Method)* + + test_allow { + // From the RFC + test_header!( + test1, + vec![b"GET, HEAD, PUT"], + Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); + // Own tests + test_header!( + test2, + vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], + Some(HeaderField(vec![ + Method::OPTIONS, + Method::GET, + Method::PUT, + Method::POST, + Method::DELETE, + Method::HEAD, + Method::TRACE, + Method::CONNECT, + Method::PATCH]))); + test_header!( + test3, + vec![b""], + Some(HeaderField(Vec::::new()))); + } +} diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs new file mode 100644 index 00000000..0b79ea7c --- /dev/null +++ b/src/header/common/cache_control.rs @@ -0,0 +1,257 @@ +use std::fmt::{self, Write}; +use std::str::FromStr; + +use http::header; + +use crate::header::{ + fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, +}; + +/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) +/// +/// The `Cache-Control` header field is used to specify directives for +/// caches along the request/response chain. Such cache directives are +/// unidirectional in that the presence of a directive in a request does +/// not imply that the same directive is to be given in the response. +/// +/// # ABNF +/// +/// ```text +/// Cache-Control = 1#cache-directive +/// cache-directive = token [ "=" ( token / quoted-string ) ] +/// ``` +/// +/// # Example values +/// +/// * `no-cache` +/// * `private, community="UCI"` +/// * `max-age=30` +/// +/// # Examples +/// ```rust +/// use actix_http::Response; +/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// +/// let mut builder = Response::Ok(); +/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); +/// ``` +/// +/// ```rust +/// use actix_http::Response; +/// use actix_http::http::header::{CacheControl, CacheDirective}; +/// +/// let mut builder = Response::Ok(); +/// builder.set(CacheControl(vec![ +/// CacheDirective::NoCache, +/// CacheDirective::Private, +/// CacheDirective::MaxAge(360u32), +/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), +/// ])); +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub struct CacheControl(pub Vec); + +__hyper__deref!(CacheControl => Vec); + +//TODO: this could just be the header! macro +impl Header for CacheControl { + fn name() -> header::HeaderName { + header::CACHE_CONTROL + } + + #[inline] + fn parse(msg: &T) -> Result + where + T: crate::HttpMessage, + { + let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; + if !directives.is_empty() { + Ok(CacheControl(directives)) + } else { + Err(crate::error::ParseError::Header) + } + } +} + +impl fmt::Display for CacheControl { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_comma_delimited(f, &self[..]) + } +} + +impl IntoHeaderValue for CacheControl { + type Error = header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + header::HeaderValue::from_shared(writer.take()) + } +} + +/// `CacheControl` contains a list of these directives. +#[derive(PartialEq, Clone, Debug)] +pub enum CacheDirective { + /// "no-cache" + NoCache, + /// "no-store" + NoStore, + /// "no-transform" + NoTransform, + /// "only-if-cached" + OnlyIfCached, + + // request directives + /// "max-age=delta" + MaxAge(u32), + /// "max-stale=delta" + MaxStale(u32), + /// "min-fresh=delta" + MinFresh(u32), + + // response directives + /// "must-revalidate" + MustRevalidate, + /// "public" + Public, + /// "private" + Private, + /// "proxy-revalidate" + ProxyRevalidate, + /// "s-maxage=delta" + SMaxAge(u32), + + /// Extension directives. Optionally include an argument. + Extension(String, Option), +} + +impl fmt::Display for CacheDirective { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::CacheDirective::*; + fmt::Display::fmt( + match *self { + NoCache => "no-cache", + NoStore => "no-store", + NoTransform => "no-transform", + OnlyIfCached => "only-if-cached", + + MaxAge(secs) => return write!(f, "max-age={}", secs), + MaxStale(secs) => return write!(f, "max-stale={}", secs), + MinFresh(secs) => return write!(f, "min-fresh={}", secs), + + MustRevalidate => "must-revalidate", + Public => "public", + Private => "private", + ProxyRevalidate => "proxy-revalidate", + SMaxAge(secs) => return write!(f, "s-maxage={}", secs), + + Extension(ref name, None) => &name[..], + Extension(ref name, Some(ref arg)) => { + return write!(f, "{}={}", name, arg); + } + }, + f, + ) + } +} + +impl FromStr for CacheDirective { + type Err = Option<::Err>; + fn from_str(s: &str) -> Result::Err>> { + use self::CacheDirective::*; + match s { + "no-cache" => Ok(NoCache), + "no-store" => Ok(NoStore), + "no-transform" => Ok(NoTransform), + "only-if-cached" => Ok(OnlyIfCached), + "must-revalidate" => Ok(MustRevalidate), + "public" => Ok(Public), + "private" => Ok(Private), + "proxy-revalidate" => Ok(ProxyRevalidate), + "" => Err(None), + _ => match s.find('=') { + Some(idx) if idx + 1 < s.len() => { + match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { + ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), + ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), + ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), + ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), + (left, right) => { + Ok(Extension(left.to_owned(), Some(right.to_owned()))) + } + } + } + Some(_) => Err(None), + None => Ok(Extension(s.to_owned(), None)), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::header::Header; + use crate::test::TestRequest; + + #[test] + fn test_parse_multiple_headers() { + let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") + .finish(); + let cache = Header::parse(&req); + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::NoCache, + CacheDirective::Private, + ])) + ) + } + + #[test] + fn test_parse_argument() { + let req = + TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") + .finish(); + let cache = Header::parse(&req); + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::MaxAge(100), + CacheDirective::Private, + ])) + ) + } + + #[test] + fn test_parse_quote_form() { + let req = + TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); + let cache = Header::parse(&req); + assert_eq!( + cache.ok(), + Some(CacheControl(vec![CacheDirective::MaxAge(200)])) + ) + } + + #[test] + fn test_parse_extension() { + let req = + TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); + let cache = Header::parse(&req); + assert_eq!( + cache.ok(), + Some(CacheControl(vec![ + CacheDirective::Extension("foo".to_owned(), None), + CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), + ])) + ) + } + + #[test] + fn test_parse_bad_syntax() { + let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); + let cache: Result = Header::parse(&req); + assert_eq!(cache.ok(), None) + } +} diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs new file mode 100644 index 00000000..e04f9c89 --- /dev/null +++ b/src/header/common/content_disposition.rs @@ -0,0 +1,918 @@ +// # References +// +// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt +// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt +// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt +// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ +// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml + +use lazy_static::lazy_static; +use regex::Regex; +use std::fmt::{self, Write}; + +use crate::header::{self, ExtendedValue, Header, IntoHeaderValue, Writer}; + +/// Split at the index of the first `needle` if it exists or at the end. +fn split_once(haystack: &str, needle: char) -> (&str, &str) { + haystack.find(needle).map_or_else( + || (haystack, ""), + |sc| { + let (first, last) = haystack.split_at(sc); + (first, last.split_at(1).1) + }, + ) +} + +/// Split at the index of the first `needle` if it exists or at the end, trim the right of the +/// first part and the left of the last part. +fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { + let (first, last) = split_once(haystack, needle); + (first.trim_right(), last.trim_left()) +} + +/// The implied disposition of the content of the HTTP body. +#[derive(Clone, Debug, PartialEq)] +pub enum DispositionType { + /// Inline implies default processing + Inline, + /// Attachment implies that the recipient should prompt the user to save the response locally, + /// rather than process it normally (as per its media type). + Attachment, + /// Used in *multipart/form-data* as defined in + /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. + FormData, + /// Extension type. Should be handled by recipients the same way as Attachment + Ext(String), +} + +impl<'a> From<&'a str> for DispositionType { + fn from(origin: &'a str) -> DispositionType { + if origin.eq_ignore_ascii_case("inline") { + DispositionType::Inline + } else if origin.eq_ignore_ascii_case("attachment") { + DispositionType::Attachment + } else if origin.eq_ignore_ascii_case("form-data") { + DispositionType::FormData + } else { + DispositionType::Ext(origin.to_owned()) + } + } +} + +/// Parameter in [`ContentDisposition`]. +/// +/// # Examples +/// ``` +/// use actix_http::http::header::DispositionParam; +/// +/// let param = DispositionParam::Filename(String::from("sample.txt")); +/// assert!(param.is_filename()); +/// assert_eq!(param.as_filename().unwrap(), "sample.txt"); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub enum DispositionParam { + /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from + /// the form. + Name(String), + /// A plain file name. + Filename(String), + /// An extended file name. It must not exist for `ContentType::Formdata` according to + /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). + FilenameExt(ExtendedValue), + /// An unrecognized regular parameter as defined in + /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in + /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should + /// ignore unrecognizable parameters. + Unknown(String, String), + /// An unrecognized extended paramater as defined in + /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in + /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single + /// trailling asterisk is not included. Recipients should ignore unrecognizable parameters. + UnknownExt(String, ExtendedValue), +} + +impl DispositionParam { + /// Returns `true` if the paramater is [`Name`](DispositionParam::Name). + #[inline] + pub fn is_name(&self) -> bool { + self.as_name().is_some() + } + + /// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename). + #[inline] + pub fn is_filename(&self) -> bool { + self.as_filename().is_some() + } + + /// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt). + #[inline] + pub fn is_filename_ext(&self) -> bool { + self.as_filename_ext().is_some() + } + + /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` + #[inline] + /// matches. + pub fn is_unknown>(&self, name: T) -> bool { + self.as_unknown(name).is_some() + } + + /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the + /// `name` matches. + #[inline] + pub fn is_unknown_ext>(&self, name: T) -> bool { + self.as_unknown_ext(name).is_some() + } + + /// Returns the name if applicable. + #[inline] + pub fn as_name(&self) -> Option<&str> { + match self { + DispositionParam::Name(ref name) => Some(name.as_str()), + _ => None, + } + } + + /// Returns the filename if applicable. + #[inline] + pub fn as_filename(&self) -> Option<&str> { + match self { + DispositionParam::Filename(ref filename) => Some(filename.as_str()), + _ => None, + } + } + + /// Returns the filename* if applicable. + #[inline] + pub fn as_filename_ext(&self) -> Option<&ExtendedValue> { + match self { + DispositionParam::FilenameExt(ref value) => Some(value), + _ => None, + } + } + + /// Returns the value of the unrecognized regular parameter if it is + /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. + #[inline] + pub fn as_unknown>(&self, name: T) -> Option<&str> { + match self { + DispositionParam::Unknown(ref ext_name, ref value) + if ext_name.eq_ignore_ascii_case(name.as_ref()) => + { + Some(value.as_str()) + } + _ => None, + } + } + + /// Returns the value of the unrecognized extended parameter if it is + /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. + #[inline] + pub fn as_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { + match self { + DispositionParam::UnknownExt(ref ext_name, ref value) + if ext_name.eq_ignore_ascii_case(name.as_ref()) => + { + Some(value) + } + _ => None, + } + } +} + +/// A *Content-Disposition* header. It is compatible to be used either as +/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) +/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as +/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) +/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). +/// +/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if +/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as +/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be +/// used to attach additional metadata, such as the filename to use when saving the response payload +/// locally. +/// +/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that +/// can be used on the subpart of a multipart body to give information about the field it applies to. +/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body +/// itself, *Content-Disposition* has no effect. +/// +/// # ABNF + +/// ```text +/// content-disposition = "Content-Disposition" ":" +/// disposition-type *( ";" disposition-parm ) +/// +/// disposition-type = "inline" | "attachment" | disp-ext-type +/// ; case-insensitive +/// +/// disp-ext-type = token +/// +/// disposition-parm = filename-parm | disp-ext-parm +/// +/// filename-parm = "filename" "=" value +/// | "filename*" "=" ext-value +/// +/// disp-ext-parm = token "=" value +/// | ext-token "=" ext-value +/// +/// ext-token = +/// ``` +/// +/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within +/// *multipart/form-data*. +/// +/// # Example +/// +/// ``` +/// use actix_http::http::header::{ +/// Charset, ContentDisposition, DispositionParam, DispositionType, +/// ExtendedValue, +/// }; +/// +/// let cd1 = ContentDisposition { +/// disposition: DispositionType::Attachment, +/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue { +/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename +/// language_tag: None, // The optional language tag (see `language-tag` crate) +/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename +/// })], +/// }; +/// assert!(cd1.is_attachment()); +/// assert!(cd1.get_filename_ext().is_some()); +/// +/// let cd2 = ContentDisposition { +/// disposition: DispositionType::FormData, +/// parameters: vec![ +/// DispositionParam::Name(String::from("file")), +/// DispositionParam::Filename(String::from("bill.odt")), +/// ], +/// }; +/// assert_eq!(cd2.get_name(), Some("file")); // field name +/// assert_eq!(cd2.get_filename(), Some("bill.odt")); +/// ``` +/// +/// # WARN +/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly +/// change to match local file system conventions if applicable, and do not use directory path +/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3) +/// . +#[derive(Clone, Debug, PartialEq)] +pub struct ContentDisposition { + /// The disposition type + pub disposition: DispositionType, + /// Disposition parameters + pub parameters: Vec, +} + +impl ContentDisposition { + /// Parse a raw Content-Disposition header value. + pub fn from_raw(hv: &header::HeaderValue) -> Result { + // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible + // ASCII characters. So `hv.as_bytes` is necessary here. + let hv = String::from_utf8(hv.as_bytes().to_vec()) + .map_err(|_| crate::error::ParseError::Header)?; + let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); + if disp_type.is_empty() { + return Err(crate::error::ParseError::Header); + } + let mut cd = ContentDisposition { + disposition: disp_type.into(), + parameters: Vec::new(), + }; + + while !left.is_empty() { + let (param_name, new_left) = split_once_and_trim(left, '='); + if param_name.is_empty() || param_name == "*" || new_left.is_empty() { + return Err(crate::error::ParseError::Header); + } + left = new_left; + if param_name.ends_with('*') { + // extended parameters + let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk + let (ext_value, new_left) = split_once_and_trim(left, ';'); + left = new_left; + let ext_value = header::parse_extended_value(ext_value)?; + + let param = if param_name.eq_ignore_ascii_case("filename") { + DispositionParam::FilenameExt(ext_value) + } else { + DispositionParam::UnknownExt(param_name.to_owned(), ext_value) + }; + cd.parameters.push(param); + } else { + // regular parameters + let value = if left.starts_with('\"') { + // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 + let mut escaping = false; + let mut quoted_string = vec![]; + let mut end = None; + // search for closing quote + for (i, &c) in left.as_bytes().iter().skip(1).enumerate() { + if escaping { + escaping = false; + quoted_string.push(c); + } else if c == 0x5c { + // backslash + escaping = true; + } else if c == 0x22 { + // double quote + end = Some(i + 1); // cuz skipped 1 for the leading quote + break; + } else { + quoted_string.push(c); + } + } + left = &left[end.ok_or(crate::error::ParseError::Header)? + 1..]; + left = split_once(left, ';').1.trim_left(); + // In fact, it should not be Err if the above code is correct. + String::from_utf8(quoted_string) + .map_err(|_| crate::error::ParseError::Header)? + } else { + // token: won't contains semicolon according to RFC 2616 Section 2.2 + let (token, new_left) = split_once_and_trim(left, ';'); + left = new_left; + token.to_owned() + }; + if value.is_empty() { + return Err(crate::error::ParseError::Header); + } + + let param = if param_name.eq_ignore_ascii_case("name") { + DispositionParam::Name(value) + } else if param_name.eq_ignore_ascii_case("filename") { + DispositionParam::Filename(value) + } else { + DispositionParam::Unknown(param_name.to_owned(), value) + }; + cd.parameters.push(param); + } + } + + Ok(cd) + } + + /// Returns `true` if it is [`Inline`](DispositionType::Inline). + pub fn is_inline(&self) -> bool { + match self.disposition { + DispositionType::Inline => true, + _ => false, + } + } + + /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). + pub fn is_attachment(&self) -> bool { + match self.disposition { + DispositionType::Attachment => true, + _ => false, + } + } + + /// Returns `true` if it is [`FormData`](DispositionType::FormData). + pub fn is_form_data(&self) -> bool { + match self.disposition { + DispositionType::FormData => true, + _ => false, + } + } + + /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. + pub fn is_ext>(&self, disp_type: T) -> bool { + match self.disposition { + DispositionType::Ext(ref t) + if t.eq_ignore_ascii_case(disp_type.as_ref()) => + { + true + } + _ => false, + } + } + + /// Return the value of *name* if exists. + pub fn get_name(&self) -> Option<&str> { + self.parameters.iter().filter_map(|p| p.as_name()).nth(0) + } + + /// Return the value of *filename* if exists. + pub fn get_filename(&self) -> Option<&str> { + self.parameters + .iter() + .filter_map(|p| p.as_filename()) + .nth(0) + } + + /// Return the value of *filename\** if exists. + pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { + self.parameters + .iter() + .filter_map(|p| p.as_filename_ext()) + .nth(0) + } + + /// Return the value of the parameter which the `name` matches. + pub fn get_unknown>(&self, name: T) -> Option<&str> { + let name = name.as_ref(); + self.parameters + .iter() + .filter_map(|p| p.as_unknown(name)) + .nth(0) + } + + /// Return the value of the extended parameter which the `name` matches. + pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { + let name = name.as_ref(); + self.parameters + .iter() + .filter_map(|p| p.as_unknown_ext(name)) + .nth(0) + } +} + +impl IntoHeaderValue for ContentDisposition { + type Error = header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + header::HeaderValue::from_shared(writer.take()) + } +} + +impl Header for ContentDisposition { + fn name() -> header::HeaderName { + header::CONTENT_DISPOSITION + } + + fn parse(msg: &T) -> Result { + if let Some(h) = msg.headers().get(Self::name()) { + Self::from_raw(&h) + } else { + Err(crate::error::ParseError::Header) + } + } +} + +impl fmt::Display for DispositionType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DispositionType::Inline => write!(f, "inline"), + DispositionType::Attachment => write!(f, "attachment"), + DispositionType::FormData => write!(f, "form-data"), + DispositionType::Ext(ref s) => write!(f, "{}", s), + } + } +} + +impl fmt::Display for DispositionParam { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and + // backslash should be escaped in quoted-string (i.e. "foobar"). + // Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . + lazy_static! { + static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); + } + match self { + DispositionParam::Name(ref value) => write!(f, "name={}", value), + DispositionParam::Filename(ref value) => { + write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) + } + DispositionParam::Unknown(ref name, ref value) => write!( + f, + "{}=\"{}\"", + name, + &RE.replace_all(value, "\\$0").as_ref() + ), + DispositionParam::FilenameExt(ref ext_value) => { + write!(f, "filename*={}", ext_value) + } + DispositionParam::UnknownExt(ref name, ref ext_value) => { + write!(f, "{}*={}", name, ext_value) + } + } + } +} + +impl fmt::Display for ContentDisposition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.disposition)?; + self.parameters + .iter() + .map(|param| write!(f, "; {}", param)) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::{ContentDisposition, DispositionParam, DispositionType}; + use crate::header::shared::Charset; + use crate::header::{ExtendedValue, HeaderValue}; + + #[test] + fn test_from_raw_basic() { + assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); + + let a = HeaderValue::from_static( + "form-data; dummy=3; name=upload; filename=\"sample.png\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static("inline; filename=image.jpg"); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Unknown( + String::from("creation-date"), + "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(), + )], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_extended() { + let a = HeaderValue::from_static( + "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Ext(String::from("UTF-8")), + language_tag: None, + value: vec![ + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, + b'r', b'a', b't', b'e', b's', + ], + })], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Ext(String::from("UTF-8")), + language_tag: None, + value: vec![ + 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, + b'r', b'a', b't', b'e', b's', + ], + })], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_extra_whitespace() { + let a = HeaderValue::from_static( + "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_unordered() { + let a = HeaderValue::from_static( + "form-data; dummy=3; filename=\"sample.png\" ; name=upload;", + // Actually, a trailling semolocon is not compliant. But it is fine to accept. + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + DispositionParam::Name("upload".to_owned()), + ], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_str( + "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"", + ) + .unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![ + DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: None, + value: b"foo-\xe4.html".to_vec(), + }), + DispositionParam::Filename("foo-ä.html".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_only_disp() { + let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) + .unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![], + }; + assert_eq!(a, b); + + let a = + ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![], + }; + assert_eq!(a, b); + + let a = ContentDisposition::from_raw(&HeaderValue::from_static( + "unknown-disp-param", + )) + .unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Ext(String::from("unknown-disp-param")), + parameters: vec![], + }; + assert_eq!(a, b); + } + + #[test] + fn from_raw_with_mixed_case() { + let a = HeaderValue::from_str( + "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"", + ) + .unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![ + DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: None, + value: b"foo-\xe4.html".to_vec(), + }), + DispositionParam::Filename("foo-ä.html".to_owned()), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn from_raw_with_unicode() { + /* RFC7578 Section 4.2: + Some commonly deployed systems use multipart/form-data with file names directly encoded + including octets outside the US-ASCII range. The encoding used for the file names is + typically UTF-8, although HTML forms will use the charset associated with the form. + + Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. + (And now, only UTF-8 is handled by this implementation.) + */ + let a = + HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") + .unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name(String::from("upload")), + DispositionParam::Filename(String::from("文件.webp")), + ], + }; + assert_eq!(a, b); + + let a = + HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx\"").unwrap(); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name(String::from("upload")), + DispositionParam::Filename(String::from( + "余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx", + )), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_escape() { + let a = HeaderValue::from_static( + "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename( + ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g'] + .iter() + .collect(), + ), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_semicolon() { + let a = + HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![DispositionParam::Filename(String::from( + "A semicolon here;.pdf", + ))], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_uncessary_percent_decode() { + let a = HeaderValue::from_static( + "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name("photo".to_owned()), + DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")), + ], + }; + assert_eq!(a, b); + + let a = HeaderValue::from_static( + "form-data; name=photo; filename=\"%74%65%73%74.png\"", + ); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Name("photo".to_owned()), + DispositionParam::Filename(String::from("%74%65%73%74.png")), + ], + }; + assert_eq!(a, b); + } + + #[test] + fn test_from_raw_param_value_missing() { + let a = HeaderValue::from_static("form-data; name=upload ; filename="); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf"); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; filename= "); + assert!(ContentDisposition::from_raw(&a).is_err()); + } + + #[test] + fn test_from_raw_param_name_missing() { + let a = HeaderValue::from_static("inline; =\"test.txt\""); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; =diary.odt"); + assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; ="); + assert!(ContentDisposition::from_raw(&a).is_err()); + } + + #[test] + fn test_display_extended() { + let as_string = + "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!(as_string, display_rendered); + + let a = HeaderValue::from_static("attachment; filename=colourful.csv"); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!( + "attachment; filename=\"colourful.csv\"".to_owned(), + display_rendered + ); + } + + #[test] + fn test_display_quote() { + let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\""; + as_string + .find(['\\', '\"'].iter().collect::().as_str()) + .unwrap(); // ensure `\"` is there + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!(as_string, display_rendered); + } + + #[test] + fn test_display_space_tab() { + let as_string = "form-data; name=upload; filename=\"Space here.png\""; + let a = HeaderValue::from_static(as_string); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!(as_string, display_rendered); + + let a: ContentDisposition = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))], + }; + let display_rendered = format!("{}", a); + assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered); + } + + #[test] + fn test_display_control_characters() { + /* let a = "attachment; filename=\"carriage\rreturn.png\""; + let a = HeaderValue::from_static(a); + let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); + let display_rendered = format!("{}", a); + assert_eq!( + "attachment; filename=\"carriage\\\rreturn.png\"", + display_rendered + );*/ + // No way to create a HeaderValue containing a carriage return. + + let a: ContentDisposition = ContentDisposition { + disposition: DispositionType::Inline, + parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))], + }; + let display_rendered = format!("{}", a); + assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered); + } + + #[test] + fn test_param_methods() { + let param = DispositionParam::Filename(String::from("sample.txt")); + assert!(param.is_filename()); + assert_eq!(param.as_filename().unwrap(), "sample.txt"); + + let param = DispositionParam::Unknown(String::from("foo"), String::from("bar")); + assert!(param.is_unknown("foo")); + assert_eq!(param.as_unknown("fOo"), Some("bar")); + } + + #[test] + fn test_disposition_methods() { + let cd = ContentDisposition { + disposition: DispositionType::FormData, + parameters: vec![ + DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), + DispositionParam::Name("upload".to_owned()), + DispositionParam::Filename("sample.png".to_owned()), + ], + }; + assert_eq!(cd.get_name(), Some("upload")); + assert_eq!(cd.get_unknown("dummy"), Some("3")); + assert_eq!(cd.get_filename(), Some("sample.png")); + assert_eq!(cd.get_unknown_ext("dummy"), None); + assert_eq!(cd.get_unknown("duMMy"), Some("3")); + } +} diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs new file mode 100644 index 00000000..838981a3 --- /dev/null +++ b/src/header/common/content_language.rs @@ -0,0 +1,65 @@ +use crate::header::{QualityItem, CONTENT_LANGUAGE}; +use language_tags::LanguageTag; + +header! { + /// `Content-Language` header, defined in + /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) + /// + /// The `Content-Language` header field describes the natural language(s) + /// of the intended audience for the representation. Note that this + /// might not be equivalent to all the languages used within the + /// representation. + /// + /// # ABNF + /// + /// ```text + /// Content-Language = 1#language-tag + /// ``` + /// + /// # Example values + /// + /// * `da` + /// * `mi, en` + /// + /// # Examples + /// + /// ```rust + /// # extern crate actix_http; + /// # #[macro_use] extern crate language_tags; + /// use actix_http::Response; + /// # use actix_http::http::header::{ContentLanguage, qitem}; + /// # + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// ContentLanguage(vec![ + /// qitem(langtag!(en)), + /// ]) + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate actix_http; + /// # #[macro_use] extern crate language_tags; + /// use actix_http::Response; + /// # use actix_http::http::header::{ContentLanguage, qitem}; + /// # + /// # fn main() { + /// + /// let mut builder = Response::Ok(); + /// builder.set( + /// ContentLanguage(vec![ + /// qitem(langtag!(da)), + /// qitem(langtag!(en;;;GB)), + /// ]) + /// ); + /// # } + /// ``` + (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ + + test_content_language { + test_header!(test1, vec![b"da"]); + test_header!(test2, vec![b"mi, en"]); + } +} diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs new file mode 100644 index 00000000..cc7f2754 --- /dev/null +++ b/src/header/common/content_range.rs @@ -0,0 +1,208 @@ +use std::fmt::{self, Display, Write}; +use std::str::FromStr; + +use crate::error::ParseError; +use crate::header::{ + HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, CONTENT_RANGE, +}; + +header! { + /// `Content-Range` header, defined in + /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) + (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] + + test_content_range { + test_header!(test_bytes, + vec![b"bytes 0-499/500"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: Some((0, 499)), + instance_length: Some(500) + }))); + + test_header!(test_bytes_unknown_len, + vec![b"bytes 0-499/*"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: Some((0, 499)), + instance_length: None + }))); + + test_header!(test_bytes_unknown_range, + vec![b"bytes */500"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: None, + instance_length: Some(500) + }))); + + test_header!(test_unregistered, + vec![b"seconds 1-2"], + Some(ContentRange(ContentRangeSpec::Unregistered { + unit: "seconds".to_owned(), + resp: "1-2".to_owned() + }))); + + test_header!(test_no_len, + vec![b"bytes 0-499"], + None::); + + test_header!(test_only_unit, + vec![b"bytes"], + None::); + + test_header!(test_end_less_than_start, + vec![b"bytes 499-0/500"], + None::); + + test_header!(test_blank, + vec![b""], + None::); + + test_header!(test_bytes_many_spaces, + vec![b"bytes 1-2/500 3"], + None::); + + test_header!(test_bytes_many_slashes, + vec![b"bytes 1-2/500/600"], + None::); + + test_header!(test_bytes_many_dashes, + vec![b"bytes 1-2-3/500"], + None::); + + } +} + +/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) +/// +/// # ABNF +/// +/// ```text +/// Content-Range = byte-content-range +/// / other-content-range +/// +/// byte-content-range = bytes-unit SP +/// ( byte-range-resp / unsatisfied-range ) +/// +/// byte-range-resp = byte-range "/" ( complete-length / "*" ) +/// byte-range = first-byte-pos "-" last-byte-pos +/// unsatisfied-range = "*/" complete-length +/// +/// complete-length = 1*DIGIT +/// +/// other-content-range = other-range-unit SP other-range-resp +/// other-range-resp = *CHAR +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub enum ContentRangeSpec { + /// Byte range + Bytes { + /// First and last bytes of the range, omitted if request could not be + /// satisfied + range: Option<(u64, u64)>, + + /// Total length of the instance, can be omitted if unknown + instance_length: Option, + }, + + /// Custom range, with unit not registered at IANA + Unregistered { + /// other-range-unit + unit: String, + + /// other-range-resp + resp: String, + }, +} + +fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { + let mut iter = s.splitn(2, separator); + match (iter.next(), iter.next()) { + (Some(a), Some(b)) => Some((a, b)), + _ => None, + } +} + +impl FromStr for ContentRangeSpec { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + let res = match split_in_two(s, ' ') { + Some(("bytes", resp)) => { + let (range, instance_length) = + split_in_two(resp, '/').ok_or(ParseError::Header)?; + + let instance_length = if instance_length == "*" { + None + } else { + Some(instance_length.parse().map_err(|_| ParseError::Header)?) + }; + + let range = if range == "*" { + None + } else { + let (first_byte, last_byte) = + split_in_two(range, '-').ok_or(ParseError::Header)?; + let first_byte = + first_byte.parse().map_err(|_| ParseError::Header)?; + let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; + if last_byte < first_byte { + return Err(ParseError::Header); + } + Some((first_byte, last_byte)) + }; + + ContentRangeSpec::Bytes { + range, + instance_length, + } + } + Some((unit, resp)) => ContentRangeSpec::Unregistered { + unit: unit.to_owned(), + resp: resp.to_owned(), + }, + _ => return Err(ParseError::Header), + }; + Ok(res) + } +} + +impl Display for ContentRangeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ContentRangeSpec::Bytes { + range, + instance_length, + } => { + f.write_str("bytes ")?; + match range { + Some((first_byte, last_byte)) => { + write!(f, "{}-{}", first_byte, last_byte)?; + } + None => { + f.write_str("*")?; + } + }; + f.write_str("/")?; + if let Some(v) = instance_length { + write!(f, "{}", v) + } else { + f.write_str("*") + } + } + ContentRangeSpec::Unregistered { ref unit, ref resp } => { + f.write_str(unit)?; + f.write_str(" ")?; + f.write_str(resp) + } + } + } +} + +impl IntoHeaderValue for ContentRangeSpec { + type Error = InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + HeaderValue::from_shared(writer.take()) + } +} diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs new file mode 100644 index 00000000..a0baa563 --- /dev/null +++ b/src/header/common/content_type.rs @@ -0,0 +1,122 @@ +use crate::header::CONTENT_TYPE; +use mime::Mime; + +header! { + /// `Content-Type` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) + /// + /// The `Content-Type` header field indicates the media type of the + /// associated representation: either the representation enclosed in the + /// message payload or the selected representation, as determined by the + /// message semantics. The indicated media type defines both the data + /// format and how that data is intended to be processed by a recipient, + /// within the scope of the received message semantics, after any content + /// codings indicated by Content-Encoding are decoded. + /// + /// Although the `mime` crate allows the mime options to be any slice, this crate + /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If + /// this is an issue, it's possible to implement `Header` on a custom struct. + /// + /// # ABNF + /// + /// ```text + /// Content-Type = media-type + /// ``` + /// + /// # Example values + /// + /// * `text/html; charset=utf-8` + /// * `application/json` + /// + /// # Examples + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::ContentType; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// ContentType::json() + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # extern crate mime; + /// # extern crate actix_http; + /// use mime::TEXT_HTML; + /// use actix_http::Response; + /// use actix_http::http::header::ContentType; + /// + /// # fn main() { + /// let mut builder = Response::Ok(); + /// builder.set( + /// ContentType(TEXT_HTML) + /// ); + /// # } + /// ``` + (ContentType, CONTENT_TYPE) => [Mime] + + test_content_type { + test_header!( + test1, + vec![b"text/html"], + Some(HeaderField(mime::TEXT_HTML))); + } +} + +impl ContentType { + /// A constructor to easily create a `Content-Type: application/json` + /// header. + #[inline] + pub fn json() -> ContentType { + ContentType(mime::APPLICATION_JSON) + } + + /// A constructor to easily create a `Content-Type: text/plain; + /// charset=utf-8` header. + #[inline] + pub fn plaintext() -> ContentType { + ContentType(mime::TEXT_PLAIN_UTF_8) + } + + /// A constructor to easily create a `Content-Type: text/html` header. + #[inline] + pub fn html() -> ContentType { + ContentType(mime::TEXT_HTML) + } + + /// A constructor to easily create a `Content-Type: text/xml` header. + #[inline] + pub fn xml() -> ContentType { + ContentType(mime::TEXT_XML) + } + + /// A constructor to easily create a `Content-Type: + /// application/www-form-url-encoded` header. + #[inline] + pub fn form_url_encoded() -> ContentType { + ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) + } + /// A constructor to easily create a `Content-Type: image/jpeg` header. + #[inline] + pub fn jpeg() -> ContentType { + ContentType(mime::IMAGE_JPEG) + } + + /// A constructor to easily create a `Content-Type: image/png` header. + #[inline] + pub fn png() -> ContentType { + ContentType(mime::IMAGE_PNG) + } + + /// A constructor to easily create a `Content-Type: + /// application/octet-stream` header. + #[inline] + pub fn octet_stream() -> ContentType { + ContentType(mime::APPLICATION_OCTET_STREAM) + } +} + +impl Eq for ContentType {} diff --git a/src/header/common/date.rs b/src/header/common/date.rs new file mode 100644 index 00000000..784100e8 --- /dev/null +++ b/src/header/common/date.rs @@ -0,0 +1,42 @@ +use crate::header::{HttpDate, DATE}; +use std::time::SystemTime; + +header! { + /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) + /// + /// The `Date` header field represents the date and time at which the + /// message was originated. + /// + /// # ABNF + /// + /// ```text + /// Date = HTTP-date + /// ``` + /// + /// # Example values + /// + /// * `Tue, 15 Nov 1994 08:12:31 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::Date; + /// use std::time::SystemTime; + /// + /// let mut builder = Response::Ok(); + /// builder.set(Date(SystemTime::now().into())); + /// ``` + (Date, DATE) => [HttpDate] + + test_date { + test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); + } +} + +impl Date { + /// Create a date instance set to the current system time + pub fn now() -> Date { + Date(SystemTime::now().into()) + } +} diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs new file mode 100644 index 00000000..325b91cb --- /dev/null +++ b/src/header/common/etag.rs @@ -0,0 +1,96 @@ +use crate::header::{EntityTag, ETAG}; + +header! { + /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) + /// + /// The `ETag` header field in a response provides the current entity-tag + /// for the selected representation, as determined at the conclusion of + /// handling the request. An entity-tag is an opaque validator for + /// differentiating between multiple representations of the same + /// resource, regardless of whether those multiple representations are + /// due to resource state changes over time, content negotiation + /// resulting in multiple representations being valid at the same time, + /// or both. An entity-tag consists of an opaque quoted string, possibly + /// prefixed by a weakness indicator. + /// + /// # ABNF + /// + /// ```text + /// ETag = entity-tag + /// ``` + /// + /// # Example values + /// + /// * `"xyzzy"` + /// * `W/"xyzzy"` + /// * `""` + /// + /// # Examples + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::{ETag, EntityTag}; + /// + /// let mut builder = Response::Ok(); + /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); + /// ``` + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::{ETag, EntityTag}; + /// + /// let mut builder = Response::Ok(); + /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); + /// ``` + (ETag, ETAG) => [EntityTag] + + test_etag { + // From the RFC + test_header!(test1, + vec![b"\"xyzzy\""], + Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); + test_header!(test2, + vec![b"W/\"xyzzy\""], + Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); + test_header!(test3, + vec![b"\"\""], + Some(ETag(EntityTag::new(false, "".to_owned())))); + // Own tests + test_header!(test4, + vec![b"\"foobar\""], + Some(ETag(EntityTag::new(false, "foobar".to_owned())))); + test_header!(test5, + vec![b"\"\""], + Some(ETag(EntityTag::new(false, "".to_owned())))); + test_header!(test6, + vec![b"W/\"weak-etag\""], + Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); + test_header!(test7, + vec![b"W/\"\x65\x62\""], + Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); + test_header!(test8, + vec![b"W/\"\""], + Some(ETag(EntityTag::new(true, "".to_owned())))); + test_header!(test9, + vec![b"no-dquotes"], + None::); + test_header!(test10, + vec![b"w/\"the-first-w-is-case-sensitive\""], + None::); + test_header!(test11, + vec![b""], + None::); + test_header!(test12, + vec![b"\"unmatched-dquotes1"], + None::); + test_header!(test13, + vec![b"unmatched-dquotes2\""], + None::); + test_header!(test14, + vec![b"matched-\"dquotes\""], + None::); + test_header!(test15, + vec![b"\""], + None::); + } +} diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs new file mode 100644 index 00000000..3b9a7873 --- /dev/null +++ b/src/header/common/expires.rs @@ -0,0 +1,39 @@ +use crate::header::{HttpDate, EXPIRES}; + +header! { + /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) + /// + /// The `Expires` header field gives the date/time after which the + /// response is considered stale. + /// + /// The presence of an Expires field does not imply that the original + /// resource will change or cease to exist at, before, or after that + /// time. + /// + /// # ABNF + /// + /// ```text + /// Expires = HTTP-date + /// ``` + /// + /// # Example values + /// * `Thu, 01 Dec 1994 16:00:00 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::Expires; + /// use std::time::{SystemTime, Duration}; + /// + /// let mut builder = Response::Ok(); + /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); + /// builder.set(Expires(expiration.into())); + /// ``` + (Expires, EXPIRES) => [HttpDate] + + test_expires { + // Test case from RFC + test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); + } +} diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs new file mode 100644 index 00000000..7e0e9a7e --- /dev/null +++ b/src/header/common/if_match.rs @@ -0,0 +1,70 @@ +use crate::header::{EntityTag, IF_MATCH}; + +header! { + /// `If-Match` header, defined in + /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) + /// + /// The `If-Match` header field makes the request method conditional on + /// the recipient origin server either having at least one current + /// representation of the target resource, when the field-value is "*", + /// or having a current representation of the target resource that has an + /// entity-tag matching a member of the list of entity-tags provided in + /// the field-value. + /// + /// An origin server MUST use the strong comparison function when + /// comparing entity-tags for `If-Match`, since the client + /// intends this precondition to prevent the method from being applied if + /// there have been any changes to the representation data. + /// + /// # ABNF + /// + /// ```text + /// If-Match = "*" / 1#entity-tag + /// ``` + /// + /// # Example values + /// + /// * `"xyzzy"` + /// * "xyzzy", "r2d2xxxx", "c3piozzzz" + /// + /// # Examples + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::IfMatch; + /// + /// let mut builder = Response::Ok(); + /// builder.set(IfMatch::Any); + /// ``` + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::{IfMatch, EntityTag}; + /// + /// let mut builder = Response::Ok(); + /// builder.set( + /// IfMatch::Items(vec![ + /// EntityTag::new(false, "xyzzy".to_owned()), + /// EntityTag::new(false, "foobar".to_owned()), + /// EntityTag::new(false, "bazquux".to_owned()), + /// ]) + /// ); + /// ``` + (IfMatch, IF_MATCH) => {Any / (EntityTag)+} + + test_if_match { + test_header!( + test1, + vec![b"\"xyzzy\""], + Some(HeaderField::Items( + vec![EntityTag::new(false, "xyzzy".to_owned())]))); + test_header!( + test2, + vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], + Some(HeaderField::Items( + vec![EntityTag::new(false, "xyzzy".to_owned()), + EntityTag::new(false, "r2d2xxxx".to_owned()), + EntityTag::new(false, "c3piozzzz".to_owned())]))); + test_header!(test3, vec![b"*"], Some(IfMatch::Any)); + } +} diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs new file mode 100644 index 00000000..39aca595 --- /dev/null +++ b/src/header/common/if_modified_since.rs @@ -0,0 +1,39 @@ +use crate::header::{HttpDate, IF_MODIFIED_SINCE}; + +header! { + /// `If-Modified-Since` header, defined in + /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) + /// + /// The `If-Modified-Since` header field makes a GET or HEAD request + /// method conditional on the selected representation's modification date + /// being more recent than the date provided in the field-value. + /// Transfer of the selected representation's data is avoided if that + /// data has not changed. + /// + /// # ABNF + /// + /// ```text + /// If-Unmodified-Since = HTTP-date + /// ``` + /// + /// # Example values + /// * `Sat, 29 Oct 1994 19:43:31 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::IfModifiedSince; + /// use std::time::{SystemTime, Duration}; + /// + /// let mut builder = Response::Ok(); + /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); + /// builder.set(IfModifiedSince(modified.into())); + /// ``` + (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] + + test_if_modified_since { + // Test case from RFC + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + } +} diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs new file mode 100644 index 00000000..7f6ccb13 --- /dev/null +++ b/src/header/common/if_none_match.rs @@ -0,0 +1,92 @@ +use crate::header::{EntityTag, IF_NONE_MATCH}; + +header! { + /// `If-None-Match` header, defined in + /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) + /// + /// The `If-None-Match` header field makes the request method conditional + /// on a recipient cache or origin server either not having any current + /// representation of the target resource, when the field-value is "*", + /// or having a selected representation with an entity-tag that does not + /// match any of those listed in the field-value. + /// + /// A recipient MUST use the weak comparison function when comparing + /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags + /// can be used for cache validation even if there have been changes to + /// the representation data. + /// + /// # ABNF + /// + /// ```text + /// If-None-Match = "*" / 1#entity-tag + /// ``` + /// + /// # Example values + /// + /// * `"xyzzy"` + /// * `W/"xyzzy"` + /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` + /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` + /// * `*` + /// + /// # Examples + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::IfNoneMatch; + /// + /// let mut builder = Response::Ok(); + /// builder.set(IfNoneMatch::Any); + /// ``` + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::{IfNoneMatch, EntityTag}; + /// + /// let mut builder = Response::Ok(); + /// builder.set( + /// IfNoneMatch::Items(vec![ + /// EntityTag::new(false, "xyzzy".to_owned()), + /// EntityTag::new(false, "foobar".to_owned()), + /// EntityTag::new(false, "bazquux".to_owned()), + /// ]) + /// ); + /// ``` + (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} + + test_if_none_match { + test_header!(test1, vec![b"\"xyzzy\""]); + test_header!(test2, vec![b"W/\"xyzzy\""]); + test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); + test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); + test_header!(test5, vec![b"*"]); + } +} + +#[cfg(test)] +mod tests { + use super::IfNoneMatch; + use crate::header::{EntityTag, Header, IF_NONE_MATCH}; + use crate::test::TestRequest; + + #[test] + fn test_if_none_match() { + let mut if_none_match: Result; + + let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); + if_none_match = Header::parse(&req); + assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); + + let req = + TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) + .finish(); + + if_none_match = Header::parse(&req); + let mut entities: Vec = Vec::new(); + let foobar_etag = EntityTag::new(false, "foobar".to_owned()); + let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); + entities.push(foobar_etag); + entities.push(weak_etag); + assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); + } +} diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs new file mode 100644 index 00000000..2140ccbb --- /dev/null +++ b/src/header/common/if_range.rs @@ -0,0 +1,116 @@ +use std::fmt::{self, Display, Write}; + +use crate::error::ParseError; +use crate::header::{ + self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, + IntoHeaderValue, InvalidHeaderValueBytes, Writer, +}; +use crate::httpmessage::HttpMessage; + +/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) +/// +/// If a client has a partial copy of a representation and wishes to have +/// an up-to-date copy of the entire representation, it could use the +/// Range header field with a conditional GET (using either or both of +/// If-Unmodified-Since and If-Match.) However, if the precondition +/// fails because the representation has been modified, the client would +/// then have to make a second request to obtain the entire current +/// representation. +/// +/// The `If-Range` header field allows a client to \"short-circuit\" the +/// second request. Informally, its meaning is as follows: if the +/// representation is unchanged, send me the part(s) that I am requesting +/// in Range; otherwise, send me the entire representation. +/// +/// # ABNF +/// +/// ```text +/// If-Range = entity-tag / HTTP-date +/// ``` +/// +/// # Example values +/// +/// * `Sat, 29 Oct 1994 19:43:31 GMT` +/// * `\"xyzzy\"` +/// +/// # Examples +/// +/// ```rust +/// use actix_http::Response; +/// use actix_http::http::header::{EntityTag, IfRange}; +/// +/// let mut builder = Response::Ok(); +/// builder.set(IfRange::EntityTag(EntityTag::new( +/// false, +/// "xyzzy".to_owned(), +/// ))); +/// ``` +/// +/// ```rust +/// use actix_http::Response; +/// use actix_http::http::header::IfRange; +/// use std::time::{Duration, SystemTime}; +/// +/// let mut builder = Response::Ok(); +/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); +/// builder.set(IfRange::Date(fetched.into())); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub enum IfRange { + /// The entity-tag the client has of the resource + EntityTag(EntityTag), + /// The date when the client retrieved the resource + Date(HttpDate), +} + +impl Header for IfRange { + fn name() -> HeaderName { + header::IF_RANGE + } + #[inline] + fn parse(msg: &T) -> Result + where + T: HttpMessage, + { + let etag: Result = + from_one_raw_str(msg.headers().get(header::IF_RANGE)); + if let Ok(etag) = etag { + return Ok(IfRange::EntityTag(etag)); + } + let date: Result = + from_one_raw_str(msg.headers().get(header::IF_RANGE)); + if let Ok(date) = date { + return Ok(IfRange::Date(date)); + } + Err(ParseError::Header) + } +} + +impl Display for IfRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + IfRange::EntityTag(ref x) => Display::fmt(x, f), + IfRange::Date(ref x) => Display::fmt(x, f), + } + } +} + +impl IntoHeaderValue for IfRange { + type Error = InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut writer = Writer::new(); + let _ = write!(&mut writer, "{}", self); + HeaderValue::from_shared(writer.take()) + } +} + +#[cfg(test)] +mod test_if_range { + use super::IfRange as HeaderField; + use crate::header::*; + use std::str; + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + test_header!(test2, vec![b"\"xyzzy\""]); + test_header!(test3, vec![b"this-is-invalid"], None::); +} diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs new file mode 100644 index 00000000..d6c099e6 --- /dev/null +++ b/src/header/common/if_unmodified_since.rs @@ -0,0 +1,40 @@ +use crate::header::{HttpDate, IF_UNMODIFIED_SINCE}; + +header! { + /// `If-Unmodified-Since` header, defined in + /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) + /// + /// The `If-Unmodified-Since` header field makes the request method + /// conditional on the selected representation's last modification date + /// being earlier than or equal to the date provided in the field-value. + /// This field accomplishes the same purpose as If-Match for cases where + /// the user agent does not have an entity-tag for the representation. + /// + /// # ABNF + /// + /// ```text + /// If-Unmodified-Since = HTTP-date + /// ``` + /// + /// # Example values + /// + /// * `Sat, 29 Oct 1994 19:43:31 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::IfUnmodifiedSince; + /// use std::time::{SystemTime, Duration}; + /// + /// let mut builder = Response::Ok(); + /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); + /// builder.set(IfUnmodifiedSince(modified.into())); + /// ``` + (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] + + test_if_unmodified_since { + // Test case from RFC + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + } +} diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs new file mode 100644 index 00000000..cc888ccb --- /dev/null +++ b/src/header/common/last_modified.rs @@ -0,0 +1,38 @@ +use crate::header::{HttpDate, LAST_MODIFIED}; + +header! { + /// `Last-Modified` header, defined in + /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) + /// + /// The `Last-Modified` header field in a response provides a timestamp + /// indicating the date and time at which the origin server believes the + /// selected representation was last modified, as determined at the + /// conclusion of handling the request. + /// + /// # ABNF + /// + /// ```text + /// Expires = HTTP-date + /// ``` + /// + /// # Example values + /// + /// * `Sat, 29 Oct 1994 19:43:31 GMT` + /// + /// # Example + /// + /// ```rust + /// use actix_http::Response; + /// use actix_http::http::header::LastModified; + /// use std::time::{SystemTime, Duration}; + /// + /// let mut builder = Response::Ok(); + /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); + /// builder.set(LastModified(modified.into())); + /// ``` + (LastModified, LAST_MODIFIED) => [HttpDate] + + test_last_modified { + // Test case from RFC + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} +} diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs new file mode 100644 index 00000000..adc7484a --- /dev/null +++ b/src/header/common/mod.rs @@ -0,0 +1,352 @@ +//! A Collection of Header implementations for common HTTP Headers. +//! +//! ## Mime +//! +//! Several header fields use MIME values for their contents. Keeping with the +//! strongly-typed theme, the [mime](https://docs.rs/mime) crate +//! is used, such as `ContentType(pub Mime)`. +#![cfg_attr(rustfmt, rustfmt_skip)] + +pub use self::accept_charset::AcceptCharset; +//pub use self::accept_encoding::AcceptEncoding; +pub use self::accept_language::AcceptLanguage; +pub use self::accept::Accept; +pub use self::allow::Allow; +pub use self::cache_control::{CacheControl, CacheDirective}; +pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; +pub use self::content_language::ContentLanguage; +pub use self::content_range::{ContentRange, ContentRangeSpec}; +pub use self::content_type::ContentType; +pub use self::date::Date; +pub use self::etag::ETag; +pub use self::expires::Expires; +pub use self::if_match::IfMatch; +pub use self::if_modified_since::IfModifiedSince; +pub use self::if_none_match::IfNoneMatch; +pub use self::if_range::IfRange; +pub use self::if_unmodified_since::IfUnmodifiedSince; +pub use self::last_modified::LastModified; +//pub use self::range::{Range, ByteRangeSpec}; + +#[doc(hidden)] +#[macro_export] +macro_rules! __hyper__deref { + ($from:ty => $to:ty) => { + impl ::std::ops::Deref for $from { + type Target = $to; + + #[inline] + fn deref(&self) -> &$to { + &self.0 + } + } + + impl ::std::ops::DerefMut for $from { + #[inline] + fn deref_mut(&mut self) -> &mut $to { + &mut self.0 + } + } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __hyper__tm { + ($id:ident, $tm:ident{$($tf:item)*}) => { + #[allow(unused_imports)] + #[cfg(test)] + mod $tm{ + use std::str; + use http::Method; + use mime::*; + use $crate::header::*; + use super::$id as HeaderField; + $($tf)* + } + + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! test_header { + ($id:ident, $raw:expr) => { + #[test] + fn $id() { + use $crate::test; + use super::*; + + let raw = $raw; + let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); + let mut req = test::TestRequest::default(); + for item in a { + req = req.header(HeaderField::name(), item); + } + let req = req.finish(); + let value = HeaderField::parse(&req); + let result = format!("{}", value.unwrap()); + let expected = String::from_utf8(raw[0].to_vec()).unwrap(); + let result_cmp: Vec = result + .to_ascii_lowercase() + .split(' ') + .map(|x| x.to_owned()) + .collect(); + let expected_cmp: Vec = expected + .to_ascii_lowercase() + .split(' ') + .map(|x| x.to_owned()) + .collect(); + assert_eq!(result_cmp.concat(), expected_cmp.concat()); + } + }; + ($id:ident, $raw:expr, $typed:expr) => { + #[test] + fn $id() { + use $crate::test; + + let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); + let mut req = test::TestRequest::default(); + for item in a { + req = req.header(HeaderField::name(), item); + } + let req = req.finish(); + let val = HeaderField::parse(&req); + let typed: Option = $typed; + // Test parsing + assert_eq!(val.ok(), typed); + // Test formatting + if typed.is_some() { + let raw = &($raw)[..]; + let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap()); + let mut joined = String::new(); + joined.push_str(iter.next().unwrap()); + for s in iter { + joined.push_str(", "); + joined.push_str(s); + } + assert_eq!(format!("{}", typed.unwrap()), joined); + } + } + } +} + +#[macro_export] +macro_rules! header { + // $a:meta: Attributes associated with the header item (usually docs) + // $id:ident: Identifier of the header + // $n:expr: Lowercase name of the header + // $nn:expr: Nice name of the header + + // List header, zero or more items + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub Vec<$item>); + __hyper__deref!($id => Vec<$item>); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { + $crate::http::header::fmt_comma_delimited(f, &self.0[..]) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_shared(writer.take()) + } + } + }; + // List header, one or more items + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub Vec<$item>); + __hyper__deref!($id => Vec<$item>); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + $crate::http::header::fmt_comma_delimited(f, &self.0[..]) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_shared(writer.take()) + } + } + }; + // Single value header + ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub $value); + __hyper__deref!($id => $value); + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + $crate::http::header::from_one_raw_str( + msg.headers().get(Self::name())).map($id) + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + self.0.try_into() + } + } + }; + // List header, one or more items with "*" option + ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub enum $id { + /// Any value is a match + Any, + /// Only the listed items are a match + Items(Vec<$item>), + } + impl $crate::http::header::Header for $id { + #[inline] + fn name() -> $crate::http::header::HeaderName { + $name + } + #[inline] + fn parse(msg: &T) -> Result + where T: $crate::HttpMessage + { + let any = msg.headers().get(Self::name()).and_then(|hdr| { + hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); + + if let Some(true) = any { + Ok($id::Any) + } else { + Ok($id::Items( + $crate::http::header::from_comma_delimited( + msg.headers().get_all(Self::name()))?)) + } + } + } + impl std::fmt::Display for $id { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + $id::Any => f.write_str("*"), + $id::Items(ref fields) => $crate::http::header::fmt_comma_delimited( + f, &fields[..]) + } + } + } + impl $crate::http::header::IntoHeaderValue for $id { + type Error = $crate::http::header::InvalidHeaderValueBytes; + + fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + use std::fmt::Write; + let mut writer = $crate::http::header::Writer::new(); + let _ = write!(&mut writer, "{}", self); + $crate::http::header::HeaderValue::from_shared(writer.take()) + } + } + }; + + // optional test module + ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { + header! { + $(#[$a])* + ($id, $name) => ($item)* + } + + __hyper__tm! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { + header! { + $(#[$a])* + ($id, $n) => ($item)+ + } + + __hyper__tm! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { + header! { + $(#[$a])* ($id, $name) => [$item] + } + + __hyper__tm! { $id, $tm { $($tf)* }} + }; + ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { + header! { + $(#[$a])* + ($id, $name) => {Any / ($item)+} + } + + __hyper__tm! { $id, $tm { $($tf)* }} + }; +} + + +mod accept_charset; +//mod accept_encoding; +mod accept_language; +mod accept; +mod allow; +mod cache_control; +mod content_disposition; +mod content_language; +mod content_range; +mod content_type; +mod date; +mod etag; +mod expires; +mod if_match; +mod if_modified_since; +mod if_none_match; +mod if_range; +mod if_unmodified_since; +mod last_modified; diff --git a/src/header/common/range.rs b/src/header/common/range.rs new file mode 100644 index 00000000..71718fc7 --- /dev/null +++ b/src/header/common/range.rs @@ -0,0 +1,434 @@ +use std::fmt::{self, Display}; +use std::str::FromStr; + +use header::parsing::from_one_raw_str; +use header::{Header, Raw}; + +/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) +/// +/// The "Range" header field on a GET request modifies the method +/// semantics to request transfer of only one or more subranges of the +/// selected representation data, rather than the entire selected +/// representation data. +/// +/// # ABNF +/// +/// ```text +/// Range = byte-ranges-specifier / other-ranges-specifier +/// other-ranges-specifier = other-range-unit "=" other-range-set +/// other-range-set = 1*VCHAR +/// +/// bytes-unit = "bytes" +/// +/// byte-ranges-specifier = bytes-unit "=" byte-range-set +/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) +/// byte-range-spec = first-byte-pos "-" [last-byte-pos] +/// first-byte-pos = 1*DIGIT +/// last-byte-pos = 1*DIGIT +/// ``` +/// +/// # Example values +/// +/// * `bytes=1000-` +/// * `bytes=-2000` +/// * `bytes=0-1,30-40` +/// * `bytes=0-10,20-90,-100` +/// * `custom_unit=0-123` +/// * `custom_unit=xxx-yyy` +/// +/// # Examples +/// +/// ``` +/// use hyper::header::{Headers, Range, ByteRangeSpec}; +/// +/// let mut headers = Headers::new(); +/// headers.set(Range::Bytes( +/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] +/// )); +/// +/// headers.clear(); +/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); +/// ``` +/// +/// ``` +/// use hyper::header::{Headers, Range}; +/// +/// let mut headers = Headers::new(); +/// headers.set(Range::bytes(1, 100)); +/// +/// headers.clear(); +/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub enum Range { + /// Byte range + Bytes(Vec), + /// Custom range, with unit not registered at IANA + /// (`other-range-unit`: String , `other-range-set`: String) + Unregistered(String, String), +} + +/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. +/// Each `ByteRangeSpec` defines a range of bytes to fetch +#[derive(PartialEq, Clone, Debug)] +pub enum ByteRangeSpec { + /// Get all bytes between x and y ("x-y") + FromTo(u64, u64), + /// Get all bytes starting from x ("x-") + AllFrom(u64), + /// Get last x bytes ("-x") + Last(u64), +} + +impl ByteRangeSpec { + /// Given the full length of the entity, attempt to normalize the byte range + /// into an satisfiable end-inclusive (from, to) range. + /// + /// The resulting range is guaranteed to be a satisfiable range within the + /// bounds of `0 <= from <= to < full_length`. + /// + /// If the byte range is deemed unsatisfiable, `None` is returned. + /// An unsatisfiable range is generally cause for a server to either reject + /// the client request with a `416 Range Not Satisfiable` status code, or to + /// simply ignore the range header and serve the full entity using a `200 + /// OK` status code. + /// + /// This function closely follows [RFC 7233][1] section 2.1. + /// As such, it considers ranges to be satisfiable if they meet the + /// following conditions: + /// + /// > If a valid byte-range-set includes at least one byte-range-spec with + /// a first-byte-pos that is less than the current length of the + /// representation, or at least one suffix-byte-range-spec with a + /// non-zero suffix-length, then the byte-range-set is satisfiable. + /// Otherwise, the byte-range-set is unsatisfiable. + /// + /// The function also computes remainder ranges based on the RFC: + /// + /// > If the last-byte-pos value is + /// absent, or if the value is greater than or equal to the current + /// length of the representation data, the byte range is interpreted as + /// the remainder of the representation (i.e., the server replaces the + /// value of last-byte-pos with a value that is one less than the current + /// length of the selected representation). + /// + /// [1]: https://tools.ietf.org/html/rfc7233 + pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { + // If the full length is zero, there is no satisfiable end-inclusive range. + if full_length == 0 { + return None; + } + match self { + &ByteRangeSpec::FromTo(from, to) => { + if from < full_length && from <= to { + Some((from, ::std::cmp::min(to, full_length - 1))) + } else { + None + } + } + &ByteRangeSpec::AllFrom(from) => { + if from < full_length { + Some((from, full_length - 1)) + } else { + None + } + } + &ByteRangeSpec::Last(last) => { + if last > 0 { + // From the RFC: If the selected representation is shorter + // than the specified suffix-length, + // the entire representation is used. + if last > full_length { + Some((0, full_length - 1)) + } else { + Some((full_length - last, full_length - 1)) + } + } else { + None + } + } + } + } +} + +impl Range { + /// Get the most common byte range header ("bytes=from-to") + pub fn bytes(from: u64, to: u64) -> Range { + Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) + } + + /// Get byte range header with multiple subranges + /// ("bytes=from1-to1,from2-to2,fromX-toX") + pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { + Range::Bytes( + ranges + .iter() + .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) + .collect(), + ) + } +} + +impl fmt::Display for ByteRangeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), + ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), + ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), + } + } +} + +impl fmt::Display for Range { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Range::Bytes(ref ranges) => { + try!(write!(f, "bytes=")); + + for (i, range) in ranges.iter().enumerate() { + if i != 0 { + try!(f.write_str(",")); + } + try!(Display::fmt(range, f)); + } + Ok(()) + } + Range::Unregistered(ref unit, ref range_str) => { + write!(f, "{}={}", unit, range_str) + } + } + } +} + +impl FromStr for Range { + type Err = ::Error; + + fn from_str(s: &str) -> ::Result { + let mut iter = s.splitn(2, '='); + + match (iter.next(), iter.next()) { + (Some("bytes"), Some(ranges)) => { + let ranges = from_comma_delimited(ranges); + if ranges.is_empty() { + return Err(::Error::Header); + } + Ok(Range::Bytes(ranges)) + } + (Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok( + Range::Unregistered(unit.to_owned(), range_str.to_owned()), + ), + _ => Err(::Error::Header), + } + } +} + +impl FromStr for ByteRangeSpec { + type Err = ::Error; + + fn from_str(s: &str) -> ::Result { + let mut parts = s.splitn(2, '-'); + + match (parts.next(), parts.next()) { + (Some(""), Some(end)) => end.parse() + .or(Err(::Error::Header)) + .map(ByteRangeSpec::Last), + (Some(start), Some("")) => start + .parse() + .or(Err(::Error::Header)) + .map(ByteRangeSpec::AllFrom), + (Some(start), Some(end)) => match (start.parse(), end.parse()) { + (Ok(start), Ok(end)) if start <= end => { + Ok(ByteRangeSpec::FromTo(start, end)) + } + _ => Err(::Error::Header), + }, + _ => Err(::Error::Header), + } + } +} + +fn from_comma_delimited(s: &str) -> Vec { + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.parse().ok()) + .collect() +} + +impl Header for Range { + fn header_name() -> &'static str { + static NAME: &'static str = "Range"; + NAME + } + + fn parse_header(raw: &Raw) -> ::Result { + from_one_raw_str(raw) + } + + fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { + f.fmt_line(self) + } +} + +#[test] +fn test_parse_bytes_range_valid() { + let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); + let r3 = Range::bytes(1, 100); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); + let r2: Range = + Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); + let r3 = Range::Bytes(vec![ + ByteRangeSpec::FromTo(1, 100), + ByteRangeSpec::AllFrom(200), + ]); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); + let r3 = Range::Bytes(vec![ + ByteRangeSpec::FromTo(1, 100), + ByteRangeSpec::Last(100), + ]); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); + assert_eq!(r, r2); +} + +#[test] +fn test_parse_unregistered_range_valid() { + let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); + assert_eq!(r, r2); + + let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); + assert_eq!(r, r2); + + let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); + assert_eq!(r, r2); +} + +#[test] +fn test_parse_invalid() { + let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"abc".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"bytes=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"custom=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result = Header::parse_header(&"=1-100".into()); + assert_eq!(r.ok(), None); +} + +#[test] +fn test_fmt() { + use header::Headers; + + let mut headers = Headers::new(); + + headers.set(Range::Bytes(vec![ + ByteRangeSpec::FromTo(0, 1000), + ByteRangeSpec::AllFrom(2000), + ])); + assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); + + headers.clear(); + headers.set(Range::Bytes(vec![])); + + assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); + + headers.clear(); + headers.set(Range::Unregistered( + "custom".to_owned(), + "1-xxx".to_owned(), + )); + + assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); +} + +#[test] +fn test_byte_range_spec_to_satisfiable_range() { + assert_eq!( + Some((0, 0)), + ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3) + ); + assert_eq!( + Some((1, 2)), + ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3) + ); + assert_eq!( + Some((1, 2)), + ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0) + ); + + assert_eq!( + Some((0, 2)), + ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) + ); + assert_eq!( + Some((2, 2)), + ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(3).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(5).to_satisfiable_range(3) + ); + assert_eq!( + None, + ByteRangeSpec::AllFrom(0).to_satisfiable_range(0) + ); + + assert_eq!( + Some((1, 2)), + ByteRangeSpec::Last(2).to_satisfiable_range(3) + ); + assert_eq!( + Some((2, 2)), + ByteRangeSpec::Last(1).to_satisfiable_range(3) + ); + assert_eq!( + Some((0, 2)), + ByteRangeSpec::Last(5).to_satisfiable_range(3) + ); + assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); +} diff --git a/src/header/mod.rs b/src/header/mod.rs new file mode 100644 index 00000000..1ef1bd19 --- /dev/null +++ b/src/header/mod.rs @@ -0,0 +1,465 @@ +//! Various http headers +// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) + +use std::{fmt, str::FromStr}; + +use bytes::{Bytes, BytesMut}; +use http::header::GetAll; +use http::Error as HttpError; +use mime::Mime; + +pub use http::header::*; + +use crate::error::ParseError; +use crate::httpmessage::HttpMessage; + +mod common; +mod shared; +#[doc(hidden)] +pub use self::common::*; +#[doc(hidden)] +pub use self::shared::*; + +#[doc(hidden)] +/// A trait for any object that will represent a header field and value. +pub trait Header +where + Self: IntoHeaderValue, +{ + /// Returns the name of the header field + fn name() -> HeaderName; + + /// Parse a header + fn parse(msg: &T) -> Result; +} + +#[doc(hidden)] +/// A trait for any object that can be Converted to a `HeaderValue` +pub trait IntoHeaderValue: Sized { + /// The type returned in the event of a conversion error. + type Error: Into; + + /// Try to convert value to a Header value. + fn try_into(self) -> Result; +} + +impl IntoHeaderValue for HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + Ok(self) + } +} + +impl<'a> IntoHeaderValue for &'a str { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + self.parse() + } +} + +impl<'a> IntoHeaderValue for &'a [u8] { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_bytes(self) + } +} + +impl IntoHeaderValue for Bytes { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(self) + } +} + +impl IntoHeaderValue for Vec { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for String { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for Mime { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(format!("{}", self))) + } +} + +/// Represents supported types of content encodings +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ContentEncoding { + /// Automatically select encoding based on encoding negotiation + Auto, + /// A format using the Brotli algorithm + Br, + /// A format using the zlib structure with deflate algorithm + Deflate, + /// Gzip algorithm + Gzip, + /// Indicates the identity function (i.e. no compression, nor modification) + Identity, +} + +impl ContentEncoding { + #[inline] + /// Is the content compressed? + pub fn is_compression(self) -> bool { + match self { + ContentEncoding::Identity | ContentEncoding::Auto => false, + _ => true, + } + } + + #[inline] + /// Convert content encoding to string + pub fn as_str(self) -> &'static str { + match self { + ContentEncoding::Br => "br", + ContentEncoding::Gzip => "gzip", + ContentEncoding::Deflate => "deflate", + ContentEncoding::Identity | ContentEncoding::Auto => "identity", + } + } + + #[inline] + /// default quality value + pub fn quality(self) -> f64 { + match self { + ContentEncoding::Br => 1.1, + ContentEncoding::Gzip => 1.0, + ContentEncoding::Deflate => 0.9, + ContentEncoding::Identity | ContentEncoding::Auto => 0.1, + } + } +} + +impl<'a> From<&'a str> for ContentEncoding { + fn from(s: &'a str) -> ContentEncoding { + let s = s.trim(); + + if s.eq_ignore_ascii_case("br") { + ContentEncoding::Br + } else if s.eq_ignore_ascii_case("gzip") { + ContentEncoding::Gzip + } else if s.eq_ignore_ascii_case("deflate") { + ContentEncoding::Deflate + } else { + ContentEncoding::Identity + } + } +} + +#[doc(hidden)] +pub(crate) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer { + buf: BytesMut::new(), + } + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl fmt::Write for Writer { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.buf.extend_from_slice(s.as_bytes()); + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { + fmt::write(self, args) + } +} + +#[inline] +#[doc(hidden)] +/// Reads a comma-delimited raw header into a Vec. +pub fn from_comma_delimited( + all: GetAll, +) -> Result, ParseError> { + let mut result = Vec::new(); + for h in all { + let s = h.to_str().map_err(|_| ParseError::Header)?; + result.extend( + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.trim().parse().ok()), + ) + } + Ok(result) +} + +#[inline] +#[doc(hidden)] +/// Reads a single string when parsing a header. +pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { + if let Some(line) = val { + let line = line.to_str().map_err(|_| ParseError::Header)?; + if !line.is_empty() { + return T::from_str(line).or(Err(ParseError::Header)); + } + } + Err(ParseError::Header) +} + +#[inline] +#[doc(hidden)] +/// Format an array into a comma-delimited string. +pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result +where + T: fmt::Display, +{ + let mut iter = parts.iter(); + if let Some(part) = iter.next() { + fmt::Display::fmt(part, f)?; + } + for part in iter { + f.write_str(", ")?; + fmt::Display::fmt(part, f)?; + } + Ok(()) +} + +// From hyper v0.11.27 src/header/parsing.rs + +/// The value part of an extended parameter consisting of three parts: +/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), +/// and a character sequence representing the actual value (`value`), separated by single quote +/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +#[derive(Clone, Debug, PartialEq)] +pub struct ExtendedValue { + /// The character set that is used to encode the `value` to a string. + pub charset: Charset, + /// The human language details of the `value`, if available. + pub language_tag: Option, + /// The parameter value, as expressed in octets. + pub value: Vec, +} + +/// Parses extended header parameter values (`ext-value`), as defined in +/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +/// +/// Extended values are denoted by parameter names that end with `*`. +/// +/// ## ABNF +/// +/// ```text +/// ext-value = charset "'" [ language ] "'" value-chars +/// ; like RFC 2231's +/// ; (see [RFC2231], Section 7) +/// +/// charset = "UTF-8" / "ISO-8859-1" / mime-charset +/// +/// mime-charset = 1*mime-charsetc +/// mime-charsetc = ALPHA / DIGIT +/// / "!" / "#" / "$" / "%" / "&" +/// / "+" / "-" / "^" / "_" / "`" +/// / "{" / "}" / "~" +/// ; as in Section 2.3 of [RFC2978] +/// ; except that the single quote is not included +/// ; SHOULD be registered in the IANA charset registry +/// +/// language = +/// +/// value-chars = *( pct-encoded / attr-char ) +/// +/// pct-encoded = "%" HEXDIG HEXDIG +/// ; see [RFC3986], Section 2.1 +/// +/// attr-char = ALPHA / DIGIT +/// / "!" / "#" / "$" / "&" / "+" / "-" / "." +/// / "^" / "_" / "`" / "|" / "~" +/// ; token except ( "*" / "'" / "%" ) +/// ``` +pub fn parse_extended_value( + val: &str, +) -> Result { + // Break into three pieces separated by the single-quote character + let mut parts = val.splitn(3, '\''); + + // Interpret the first piece as a Charset + let charset: Charset = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?, + }; + + // Interpret the second piece as a language tag + let language_tag: Option = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some("") => None, + Some(s) => match s.parse() { + Ok(lt) => Some(lt), + Err(_) => return Err(crate::error::ParseError::Header), + }, + }; + + // Interpret the third piece as a sequence of value characters + let value: Vec = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), + }; + + Ok(ExtendedValue { + value, + charset, + language_tag, + }) +} + +impl fmt::Display for ExtendedValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let encoded_value = percent_encoding::percent_encode( + &self.value[..], + self::percent_encoding_http::HTTP_VALUE, + ); + if let Some(ref lang) = self.language_tag { + write!(f, "{}'{}'{}", self.charset, lang, encoded_value) + } else { + write!(f, "{}''{}", self.charset, encoded_value) + } + } +} + +/// Percent encode a sequence of bytes with a character set defined in +/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] +/// +/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 +pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { + let encoded = + percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} + +mod percent_encoding_http { + use percent_encoding::{self, define_encode_set}; + + // internal module because macro is hard-coded to make a public item + // but we don't want to public export this item + define_encode_set! { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } +} + +#[cfg(test)] +mod tests { + use super::shared::Charset; + use super::{parse_extended_value, ExtendedValue}; + use language_tags::LanguageTag; + + #[test] + fn test_parse_extended_value_with_encoding_and_language_tag() { + let expected_language_tag = "en".parse::().unwrap(); + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode character U+00A3 (POUND SIGN) + let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Iso_8859_1, extended_value.charset); + assert!(extended_value.language_tag.is_some()); + assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); + assert_eq!( + vec![163, b' ', b'r', b'a', b't', b'e', b's'], + extended_value.value + ); + } + + #[test] + fn test_parse_extended_value_with_encoding() { + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) + // and U+20AC (EURO SIGN) + let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); + assert!(extended_value.language_tag.is_none()); + assert_eq!( + vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + extended_value.value + ); + } + + #[test] + fn test_parse_extended_value_missing_language_tag_and_encoding() { + // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 + let result = parse_extended_value("foo%20bar.html"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted() { + let result = parse_extended_value("UTF-8'missing third part"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted_blank() { + let result = parse_extended_value("blank second part'"); + assert!(result.is_err()); + } + + #[test] + fn test_fmt_extended_value_with_encoding_and_language_tag() { + let extended_value = ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: Some("en".parse().expect("Could not parse language tag")), + value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], + }; + assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); + } + + #[test] + fn test_fmt_extended_value_with_encoding() { + let extended_value = ExtendedValue { + charset: Charset::Ext("UTF-8".to_string()), + language_tag: None, + value: vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + }; + assert_eq!( + "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", + format!("{}", extended_value) + ); + } +} diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs new file mode 100644 index 00000000..ec3fe385 --- /dev/null +++ b/src/header/shared/charset.rs @@ -0,0 +1,153 @@ +use std::fmt::{self, Display}; +use std::str::FromStr; + +use self::Charset::*; + +/// A Mime charset. +/// +/// The string representation is normalized to upper case. +/// +/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. +/// +/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml +#[derive(Clone, Debug, PartialEq)] +#[allow(non_camel_case_types)] +pub enum Charset { + /// US ASCII + Us_Ascii, + /// ISO-8859-1 + Iso_8859_1, + /// ISO-8859-2 + Iso_8859_2, + /// ISO-8859-3 + Iso_8859_3, + /// ISO-8859-4 + Iso_8859_4, + /// ISO-8859-5 + Iso_8859_5, + /// ISO-8859-6 + Iso_8859_6, + /// ISO-8859-7 + Iso_8859_7, + /// ISO-8859-8 + Iso_8859_8, + /// ISO-8859-9 + Iso_8859_9, + /// ISO-8859-10 + Iso_8859_10, + /// Shift_JIS + Shift_Jis, + /// EUC-JP + Euc_Jp, + /// ISO-2022-KR + Iso_2022_Kr, + /// EUC-KR + Euc_Kr, + /// ISO-2022-JP + Iso_2022_Jp, + /// ISO-2022-JP-2 + Iso_2022_Jp_2, + /// ISO-8859-6-E + Iso_8859_6_E, + /// ISO-8859-6-I + Iso_8859_6_I, + /// ISO-8859-8-E + Iso_8859_8_E, + /// ISO-8859-8-I + Iso_8859_8_I, + /// GB2312 + Gb2312, + /// Big5 + Big5, + /// KOI8-R + Koi8_R, + /// An arbitrary charset specified as a string + Ext(String), +} + +impl Charset { + fn label(&self) -> &str { + match *self { + Us_Ascii => "US-ASCII", + Iso_8859_1 => "ISO-8859-1", + Iso_8859_2 => "ISO-8859-2", + Iso_8859_3 => "ISO-8859-3", + Iso_8859_4 => "ISO-8859-4", + Iso_8859_5 => "ISO-8859-5", + Iso_8859_6 => "ISO-8859-6", + Iso_8859_7 => "ISO-8859-7", + Iso_8859_8 => "ISO-8859-8", + Iso_8859_9 => "ISO-8859-9", + Iso_8859_10 => "ISO-8859-10", + Shift_Jis => "Shift-JIS", + Euc_Jp => "EUC-JP", + Iso_2022_Kr => "ISO-2022-KR", + Euc_Kr => "EUC-KR", + Iso_2022_Jp => "ISO-2022-JP", + Iso_2022_Jp_2 => "ISO-2022-JP-2", + Iso_8859_6_E => "ISO-8859-6-E", + Iso_8859_6_I => "ISO-8859-6-I", + Iso_8859_8_E => "ISO-8859-8-E", + Iso_8859_8_I => "ISO-8859-8-I", + Gb2312 => "GB2312", + Big5 => "big5", + Koi8_R => "KOI8-R", + Ext(ref s) => s, + } + } +} + +impl Display for Charset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.label()) + } +} + +impl FromStr for Charset { + type Err = crate::Error; + + fn from_str(s: &str) -> crate::Result { + Ok(match s.to_ascii_uppercase().as_ref() { + "US-ASCII" => Us_Ascii, + "ISO-8859-1" => Iso_8859_1, + "ISO-8859-2" => Iso_8859_2, + "ISO-8859-3" => Iso_8859_3, + "ISO-8859-4" => Iso_8859_4, + "ISO-8859-5" => Iso_8859_5, + "ISO-8859-6" => Iso_8859_6, + "ISO-8859-7" => Iso_8859_7, + "ISO-8859-8" => Iso_8859_8, + "ISO-8859-9" => Iso_8859_9, + "ISO-8859-10" => Iso_8859_10, + "SHIFT-JIS" => Shift_Jis, + "EUC-JP" => Euc_Jp, + "ISO-2022-KR" => Iso_2022_Kr, + "EUC-KR" => Euc_Kr, + "ISO-2022-JP" => Iso_2022_Jp, + "ISO-2022-JP-2" => Iso_2022_Jp_2, + "ISO-8859-6-E" => Iso_8859_6_E, + "ISO-8859-6-I" => Iso_8859_6_I, + "ISO-8859-8-E" => Iso_8859_8_E, + "ISO-8859-8-I" => Iso_8859_8_I, + "GB2312" => Gb2312, + "big5" => Big5, + "KOI8-R" => Koi8_R, + s => Ext(s.to_owned()), + }) + } +} + +#[test] +fn test_parse() { + assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); + assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); + assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); + assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); + assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); +} + +#[test] +fn test_display() { + assert_eq!("US-ASCII", format!("{}", Us_Ascii)); + assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); +} diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs new file mode 100644 index 00000000..af740482 --- /dev/null +++ b/src/header/shared/encoding.rs @@ -0,0 +1,58 @@ +use std::{fmt, str}; + +pub use self::Encoding::{ + Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, +}; + +/// A value to represent an encoding used in `Transfer-Encoding` +/// or `Accept-Encoding` header. +#[derive(Clone, PartialEq, Debug)] +pub enum Encoding { + /// The `chunked` encoding. + Chunked, + /// The `br` encoding. + Brotli, + /// The `gzip` encoding. + Gzip, + /// The `deflate` encoding. + Deflate, + /// The `compress` encoding. + Compress, + /// The `identity` encoding. + Identity, + /// The `trailers` encoding. + Trailers, + /// Some other encoding that is less common, can be any String. + EncodingExt(String), +} + +impl fmt::Display for Encoding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + Chunked => "chunked", + Brotli => "br", + Gzip => "gzip", + Deflate => "deflate", + Compress => "compress", + Identity => "identity", + Trailers => "trailers", + EncodingExt(ref s) => s.as_ref(), + }) + } +} + +impl str::FromStr for Encoding { + type Err = crate::error::ParseError; + fn from_str(s: &str) -> Result { + match s { + "chunked" => Ok(Chunked), + "br" => Ok(Brotli), + "deflate" => Ok(Deflate), + "gzip" => Ok(Gzip), + "compress" => Ok(Compress), + "identity" => Ok(Identity), + "trailers" => Ok(Trailers), + _ => Ok(EncodingExt(s.to_owned())), + } + } +} diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs new file mode 100644 index 00000000..da02dc19 --- /dev/null +++ b/src/header/shared/entity.rs @@ -0,0 +1,265 @@ +use std::fmt::{self, Display, Write}; +use std::str::FromStr; + +use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; + +/// check that each char in the slice is either: +/// 1. `%x21`, or +/// 2. in the range `%x23` to `%x7E`, or +/// 3. above `%x80` +fn check_slice_validity(slice: &str) -> bool { + slice + .bytes() + .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) +} + +/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) +/// +/// An entity tag consists of a string enclosed by two literal double quotes. +/// Preceding the first double quote is an optional weakness indicator, +/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and +/// `W/"xyzzy"`. +/// +/// # ABNF +/// +/// ```text +/// entity-tag = [ weak ] opaque-tag +/// weak = %x57.2F ; "W/", case-sensitive +/// opaque-tag = DQUOTE *etagc DQUOTE +/// etagc = %x21 / %x23-7E / obs-text +/// ; VCHAR except double quotes, plus obs-text +/// ``` +/// +/// # Comparison +/// To check if two entity tags are equivalent in an application always use the +/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use +/// `==` to check if two tags are identical. +/// +/// The example below shows the results for a set of entity-tag pairs and +/// both the weak and strong comparison function results: +/// +/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison | +/// |---------|---------|-------------------|-----------------| +/// | `W/"1"` | `W/"1"` | no match | match | +/// | `W/"1"` | `W/"2"` | no match | no match | +/// | `W/"1"` | `"1"` | no match | match | +/// | `"1"` | `"1"` | match | match | +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EntityTag { + /// Weakness indicator for the tag + pub weak: bool, + /// The opaque string in between the DQUOTEs + tag: String, +} + +impl EntityTag { + /// Constructs a new EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn new(weak: bool, tag: String) -> EntityTag { + assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); + EntityTag { weak, tag } + } + + /// Constructs a new weak EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn weak(tag: String) -> EntityTag { + EntityTag::new(true, tag) + } + + /// Constructs a new strong EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn strong(tag: String) -> EntityTag { + EntityTag::new(false, tag) + } + + /// Get the tag. + pub fn tag(&self) -> &str { + self.tag.as_ref() + } + + /// Set the tag. + /// # Panics + /// If the tag contains invalid characters. + pub fn set_tag(&mut self, tag: String) { + assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); + self.tag = tag + } + + /// For strong comparison two entity-tags are equivalent if both are not + /// weak and their opaque-tags match character-by-character. + pub fn strong_eq(&self, other: &EntityTag) -> bool { + !self.weak && !other.weak && self.tag == other.tag + } + + /// For weak comparison two entity-tags are equivalent if their + /// opaque-tags match character-by-character, regardless of either or + /// both being tagged as "weak". + pub fn weak_eq(&self, other: &EntityTag) -> bool { + self.tag == other.tag + } + + /// The inverse of `EntityTag.strong_eq()`. + pub fn strong_ne(&self, other: &EntityTag) -> bool { + !self.strong_eq(other) + } + + /// The inverse of `EntityTag.weak_eq()`. + pub fn weak_ne(&self, other: &EntityTag) -> bool { + !self.weak_eq(other) + } +} + +impl Display for EntityTag { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.weak { + write!(f, "W/\"{}\"", self.tag) + } else { + write!(f, "\"{}\"", self.tag) + } + } +} + +impl FromStr for EntityTag { + type Err = crate::error::ParseError; + + fn from_str(s: &str) -> Result { + let length: usize = s.len(); + let slice = &s[..]; + // Early exits if it doesn't terminate in a DQUOTE. + if !slice.ends_with('"') || slice.len() < 2 { + return Err(crate::error::ParseError::Header); + } + // The etag is weak if its first char is not a DQUOTE. + if slice.len() >= 2 + && slice.starts_with('"') + && check_slice_validity(&slice[1..length - 1]) + { + // No need to check if the last char is a DQUOTE, + // we already did that above. + return Ok(EntityTag { + weak: false, + tag: slice[1..length - 1].to_owned(), + }); + } else if slice.len() >= 4 + && slice.starts_with("W/\"") + && check_slice_validity(&slice[3..length - 1]) + { + return Ok(EntityTag { + weak: true, + tag: slice[3..length - 1].to_owned(), + }); + } + Err(crate::error::ParseError::Header) + } +} + +impl IntoHeaderValue for EntityTag { + type Error = InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut wrt = Writer::new(); + write!(wrt, "{}", self).unwrap(); + HeaderValue::from_shared(wrt.take()) + } +} + +#[cfg(test)] +mod tests { + use super::EntityTag; + + #[test] + fn test_etag_parse_success() { + // Expected success + assert_eq!( + "\"foobar\"".parse::().unwrap(), + EntityTag::strong("foobar".to_owned()) + ); + assert_eq!( + "\"\"".parse::().unwrap(), + EntityTag::strong("".to_owned()) + ); + assert_eq!( + "W/\"weaktag\"".parse::().unwrap(), + EntityTag::weak("weaktag".to_owned()) + ); + assert_eq!( + "W/\"\x65\x62\"".parse::().unwrap(), + EntityTag::weak("\x65\x62".to_owned()) + ); + assert_eq!( + "W/\"\"".parse::().unwrap(), + EntityTag::weak("".to_owned()) + ); + } + + #[test] + fn test_etag_parse_failures() { + // Expected failures + assert!("no-dquotes".parse::().is_err()); + assert!("w/\"the-first-w-is-case-sensitive\"" + .parse::() + .is_err()); + assert!("".parse::().is_err()); + assert!("\"unmatched-dquotes1".parse::().is_err()); + assert!("unmatched-dquotes2\"".parse::().is_err()); + assert!("matched-\"dquotes\"".parse::().is_err()); + } + + #[test] + fn test_etag_fmt() { + assert_eq!( + format!("{}", EntityTag::strong("foobar".to_owned())), + "\"foobar\"" + ); + assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); + assert_eq!( + format!("{}", EntityTag::weak("weak-etag".to_owned())), + "W/\"weak-etag\"" + ); + assert_eq!( + format!("{}", EntityTag::weak("\u{0065}".to_owned())), + "W/\"\x65\"" + ); + assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); + } + + #[test] + fn test_cmp() { + // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | + // |---------|---------|-------------------|-----------------| + // | `W/"1"` | `W/"1"` | no match | match | + // | `W/"1"` | `W/"2"` | no match | no match | + // | `W/"1"` | `"1"` | no match | match | + // | `"1"` | `"1"` | match | match | + let mut etag1 = EntityTag::weak("1".to_owned()); + let mut etag2 = EntityTag::weak("1".to_owned()); + assert!(!etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + + etag1 = EntityTag::weak("1".to_owned()); + etag2 = EntityTag::weak("2".to_owned()); + assert!(!etag1.strong_eq(&etag2)); + assert!(!etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(etag1.weak_ne(&etag2)); + + etag1 = EntityTag::weak("1".to_owned()); + etag2 = EntityTag::strong("1".to_owned()); + assert!(!etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + + etag1 = EntityTag::strong("1".to_owned()); + etag2 = EntityTag::strong("1".to_owned()); + assert!(etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(!etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + } +} diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs new file mode 100644 index 00000000..350f77bb --- /dev/null +++ b/src/header/shared/httpdate.rs @@ -0,0 +1,118 @@ +use std::fmt::{self, Display}; +use std::io::Write; +use std::str::FromStr; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use bytes::{BufMut, BytesMut}; +use http::header::{HeaderValue, InvalidHeaderValueBytes}; + +use crate::error::ParseError; +use crate::header::IntoHeaderValue; + +/// A timestamp with HTTP formatting and parsing +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct HttpDate(time::Tm); + +impl FromStr for HttpDate { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + match time::strptime(s, "%a, %d %b %Y %T %Z") + .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) + .or_else(|_| time::strptime(s, "%c")) + { + Ok(t) => Ok(HttpDate(t)), + Err(_) => Err(ParseError::Header), + } + } +} + +impl Display for HttpDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0.to_utc().rfc822(), f) + } +} + +impl From for HttpDate { + fn from(tm: time::Tm) -> HttpDate { + HttpDate(tm) + } +} + +impl From for HttpDate { + fn from(sys: SystemTime) -> HttpDate { + let tmspec = match sys.duration_since(UNIX_EPOCH) { + Ok(dur) => { + time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) + } + Err(err) => { + let neg = err.duration(); + time::Timespec::new( + -(neg.as_secs() as i64), + -(neg.subsec_nanos() as i32), + ) + } + }; + HttpDate(time::at_utc(tmspec)) + } +} + +impl IntoHeaderValue for HttpDate { + type Error = InvalidHeaderValueBytes; + + fn try_into(self) -> Result { + let mut wrt = BytesMut::with_capacity(29).writer(); + write!(wrt, "{}", self.0.rfc822()).unwrap(); + HeaderValue::from_shared(wrt.get_mut().take().freeze()) + } +} + +impl From for SystemTime { + fn from(date: HttpDate) -> SystemTime { + let spec = date.0.to_timespec(); + if spec.sec >= 0 { + UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) + } else { + UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) + } + } +} + +#[cfg(test)] +mod tests { + use super::HttpDate; + use time::Tm; + + const NOV_07: HttpDate = HttpDate(Tm { + tm_nsec: 0, + tm_sec: 37, + tm_min: 48, + tm_hour: 8, + tm_mday: 7, + tm_mon: 10, + tm_year: 94, + tm_wday: 0, + tm_isdst: 0, + tm_yday: 0, + tm_utcoff: 0, + }); + + #[test] + fn test_date() { + assert_eq!( + "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), + NOV_07 + ); + assert_eq!( + "Sunday, 07-Nov-94 08:48:37 GMT" + .parse::() + .unwrap(), + NOV_07 + ); + assert_eq!( + "Sun Nov 7 08:48:37 1994".parse::().unwrap(), + NOV_07 + ); + assert!("this-is-no-date".parse::().is_err()); + } +} diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs new file mode 100644 index 00000000..f2bc9163 --- /dev/null +++ b/src/header/shared/mod.rs @@ -0,0 +1,14 @@ +//! Copied for `hyper::header::shared`; + +pub use self::charset::Charset; +pub use self::encoding::Encoding; +pub use self::entity::EntityTag; +pub use self::httpdate::HttpDate; +pub use self::quality_item::{q, qitem, Quality, QualityItem}; +pub use language_tags::LanguageTag; + +mod charset; +mod encoding; +mod entity; +mod httpdate; +mod quality_item; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs new file mode 100644 index 00000000..07c20658 --- /dev/null +++ b/src/header/shared/quality_item.rs @@ -0,0 +1,291 @@ +use std::{cmp, fmt, str}; + +use self::internal::IntoQuality; + +/// Represents a quality used in quality values. +/// +/// Can be created with the `q` function. +/// +/// # Implementation notes +/// +/// The quality value is defined as a number between 0 and 1 with three decimal +/// places. This means there are 1001 possible values. Since floating point +/// numbers are not exact and the smallest floating point data type (`f32`) +/// consumes four bytes, hyper uses an `u16` value to store the +/// quality internally. For performance reasons you may set quality directly to +/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality +/// `q=0.532`. +/// +/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) +/// gives more information on quality values in HTTP header fields. +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Quality(u16); + +impl Default for Quality { + fn default() -> Quality { + Quality(1000) + } +} + +/// Represents an item with a quality value as defined in +/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). +#[derive(Clone, PartialEq, Debug)] +pub struct QualityItem { + /// The actual contents of the field. + pub item: T, + /// The quality (client or server preference) for the value. + pub quality: Quality, +} + +impl QualityItem { + /// Creates a new `QualityItem` from an item and a quality. + /// The item can be of any type. + /// The quality should be a value in the range [0, 1]. + pub fn new(item: T, quality: Quality) -> QualityItem { + QualityItem { item, quality } + } +} + +impl cmp::PartialOrd for QualityItem { + fn partial_cmp(&self, other: &QualityItem) -> Option { + self.quality.partial_cmp(&other.quality) + } +} + +impl fmt::Display for QualityItem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.item, f)?; + match self.quality.0 { + 1000 => Ok(()), + 0 => f.write_str("; q=0"), + x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), + } + } +} + +impl str::FromStr for QualityItem { + type Err = crate::error::ParseError; + + fn from_str(s: &str) -> Result, crate::error::ParseError> { + if !s.is_ascii() { + return Err(crate::error::ParseError::Header); + } + // Set defaults used if parsing fails. + let mut raw_item = s; + let mut quality = 1f32; + + let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); + if parts.len() == 2 { + if parts[0].len() < 2 { + return Err(crate::error::ParseError::Header); + } + let start = &parts[0][0..2]; + if start == "q=" || start == "Q=" { + let q_part = &parts[0][2..parts[0].len()]; + if q_part.len() > 5 { + return Err(crate::error::ParseError::Header); + } + match q_part.parse::() { + Ok(q_value) => { + if 0f32 <= q_value && q_value <= 1f32 { + quality = q_value; + raw_item = parts[1]; + } else { + return Err(crate::error::ParseError::Header); + } + } + Err(_) => return Err(crate::error::ParseError::Header), + } + } + } + match raw_item.parse::() { + // we already checked above that the quality is within range + Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), + Err(_) => Err(crate::error::ParseError::Header), + } + } +} + +#[inline] +fn from_f32(f: f32) -> Quality { + // this function is only used internally. A check that `f` is within range + // should be done before calling this method. Just in case, this + // debug_assert should catch if we were forgetful + debug_assert!( + f >= 0f32 && f <= 1f32, + "q value must be between 0.0 and 1.0" + ); + Quality((f * 1000f32) as u16) +} + +/// Convenience function to wrap a value in a `QualityItem` +/// Sets `q` to the default 1.0 +pub fn qitem(item: T) -> QualityItem { + QualityItem::new(item, Default::default()) +} + +/// Convenience function to create a `Quality` from a float or integer. +/// +/// Implemented for `u16` and `f32`. Panics if value is out of range. +pub fn q(val: T) -> Quality { + val.into_quality() +} + +mod internal { + use super::Quality; + + // TryFrom is probably better, but it's not stable. For now, we want to + // keep the functionality of the `q` function, while allowing it to be + // generic over `f32` and `u16`. + // + // `q` would panic before, so keep that behavior. `TryFrom` can be + // introduced later for a non-panicking conversion. + + pub trait IntoQuality: Sealed + Sized { + fn into_quality(self) -> Quality; + } + + impl IntoQuality for f32 { + fn into_quality(self) -> Quality { + assert!( + self >= 0f32 && self <= 1f32, + "float must be between 0.0 and 1.0" + ); + super::from_f32(self) + } + } + + impl IntoQuality for u16 { + fn into_quality(self) -> Quality { + assert!(self <= 1000, "u16 must be between 0 and 1000"); + Quality(self) + } + } + + pub trait Sealed {} + impl Sealed for u16 {} + impl Sealed for f32 {} +} + +#[cfg(test)] +mod tests { + use super::super::encoding::*; + use super::*; + + #[test] + fn test_quality_item_fmt_q_1() { + let x = qitem(Chunked); + assert_eq!(format!("{}", x), "chunked"); + } + #[test] + fn test_quality_item_fmt_q_0001() { + let x = QualityItem::new(Chunked, Quality(1)); + assert_eq!(format!("{}", x), "chunked; q=0.001"); + } + #[test] + fn test_quality_item_fmt_q_05() { + // Custom value + let x = QualityItem { + item: EncodingExt("identity".to_owned()), + quality: Quality(500), + }; + assert_eq!(format!("{}", x), "identity; q=0.5"); + } + + #[test] + fn test_quality_item_fmt_q_0() { + // Custom value + let x = QualityItem { + item: EncodingExt("identity".to_owned()), + quality: Quality(0), + }; + assert_eq!(x.to_string(), "identity; q=0"); + } + + #[test] + fn test_quality_item_from_str1() { + let x: Result, _> = "chunked".parse(); + assert_eq!( + x.unwrap(), + QualityItem { + item: Chunked, + quality: Quality(1000), + } + ); + } + #[test] + fn test_quality_item_from_str2() { + let x: Result, _> = "chunked; q=1".parse(); + assert_eq!( + x.unwrap(), + QualityItem { + item: Chunked, + quality: Quality(1000), + } + ); + } + #[test] + fn test_quality_item_from_str3() { + let x: Result, _> = "gzip; q=0.5".parse(); + assert_eq!( + x.unwrap(), + QualityItem { + item: Gzip, + quality: Quality(500), + } + ); + } + #[test] + fn test_quality_item_from_str4() { + let x: Result, _> = "gzip; q=0.273".parse(); + assert_eq!( + x.unwrap(), + QualityItem { + item: Gzip, + quality: Quality(273), + } + ); + } + #[test] + fn test_quality_item_from_str5() { + let x: Result, _> = "gzip; q=0.2739999".parse(); + assert!(x.is_err()); + } + #[test] + fn test_quality_item_from_str6() { + let x: Result, _> = "gzip; q=2".parse(); + assert!(x.is_err()); + } + #[test] + fn test_quality_item_ordering() { + let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); + let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); + let comparision_result: bool = x.gt(&y); + assert!(comparision_result) + } + + #[test] + fn test_quality() { + assert_eq!(q(0.5), Quality(500)); + } + + #[test] + #[should_panic] // FIXME - 32-bit msvc unwinding broken + #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] + fn test_quality_invalid() { + q(-1.0); + } + + #[test] + #[should_panic] // FIXME - 32-bit msvc unwinding broken + #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] + fn test_quality_invalid2() { + q(2.0); + } + + #[test] + fn test_fuzzing_bugs() { + assert!("99999;".parse::>().is_err()); + assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) + } +} diff --git a/tests/test_ws.rs b/tests/test_ws.rs index bf5a3c41..e5a54c7c 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -6,7 +6,7 @@ use actix_service::NewService; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use bytes::{Bytes, BytesMut}; -use futures::future::{lazy, ok, Either}; +use futures::future::{ok, Either}; use futures::{Future, Sink, Stream}; use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; From 7d49a07f91156253a371000d24fa9e4291cbce46 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 13:39:15 -0800 Subject: [PATCH 0900/1635] add h1/h2 payload --- src/lib.rs | 1 + src/payload.rs | 32 ++++++++++++++++++++++++++++++++ src/request.rs | 12 +++++++----- src/service/mod.rs | 2 -- src/test.rs | 8 ++++---- 5 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 src/payload.rs diff --git a/src/lib.rs b/src/lib.rs index a3ca52ed..71582393 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ mod service; pub mod error; pub mod h1; pub mod h2; +pub mod payload; pub mod test; pub mod ws; diff --git a/src/payload.rs b/src/payload.rs new file mode 100644 index 00000000..ede1281e --- /dev/null +++ b/src/payload.rs @@ -0,0 +1,32 @@ +use bytes::Bytes; +use derive_more::From; +use futures::{Poll, Stream}; +use h2::RecvStream; + +use crate::error::PayloadError; + +#[derive(From)] +pub enum Payload { + H1(crate::h1::Payload), + H2(crate::h2::Payload), + Dyn(Box>), +} + +impl From for Payload { + fn from(v: RecvStream) -> Self { + Payload::H2(crate::h2::Payload::new(v)) + } +} + +impl Stream for Payload { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + match self { + Payload::H1(ref mut pl) => pl.poll(), + Payload::H2(ref mut pl) => pl.poll(), + Payload::Dyn(ref mut pl) => pl.poll(), + } + } +} diff --git a/src/request.rs b/src/request.rs index 4df95d45..f6be69dd 100644 --- a/src/request.rs +++ b/src/request.rs @@ -10,8 +10,7 @@ use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, MessagePool, RequestHead}; - -use crate::h1::Payload; +use crate::payload::Payload; /// Request pub struct Request

    { @@ -39,7 +38,7 @@ impl Request { /// Create new Request instance pub fn new() -> Request { Request { - payload: Some(Payload::empty()), + payload: None, inner: MessagePool::get_message(), } } @@ -55,9 +54,12 @@ impl Request { } /// Create new Request instance - pub fn set_payload

    (self, payload: P) -> Request

    { + pub fn set_payload(self, payload: I) -> Request

    + where + I: Into

    , + { Request { - payload: Some(payload), + payload: Some(payload.into()), inner: self.inner.clone(), } } diff --git a/src/service/mod.rs b/src/service/mod.rs index 3939ab99..83a40bd1 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,5 +1,3 @@ -use h2::RecvStream; - mod senderror; pub use self::senderror::{SendError, SendResponse}; diff --git a/src/test.rs b/src/test.rs index c68bbf8e..cd160e60 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,8 +6,8 @@ use cookie::Cookie; use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use crate::h1::Payload; use crate::header::{Header, IntoHeaderValue}; +use crate::payload::Payload; use crate::Request; /// Test `Request` builder @@ -125,9 +125,9 @@ impl TestRequest { /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { - let mut payload = Payload::empty(); + let mut payload = crate::h1::Payload::empty(); payload.unread_data(data.into()); - self.payload = Some(payload); + self.payload = Some(payload.into()); self } @@ -151,7 +151,7 @@ impl TestRequest { let mut req = if let Some(pl) = payload { Request::with_payload(pl) } else { - Request::with_payload(Payload::empty()) + Request::with_payload(crate::h1::Payload::empty().into()) }; let inner = req.inner_mut(); From 5575ee7d2d0ce5281c5980a9b6c8769e2d5c9d85 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 13:41:50 -0800 Subject: [PATCH 0901/1635] use same payload type for h1 and h2 --- src/h1/service.rs | 3 ++- src/h2/dispatcher.rs | 5 +++-- src/h2/service.rs | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/h1/service.rs b/src/h1/service.rs index c35d1871..fbc0a2f0 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -11,6 +11,7 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, ParseError}; +use crate::payload::Payload; use crate::request::Request; use crate::response::Response; @@ -27,7 +28,7 @@ pub struct H1Service { impl H1Service where - S: NewService> + Clone, + S: NewService, Response = Response> + Clone, S::Service: Clone, S::Error: Debug, B: MessageBody, diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 2994d0a3..301777a8 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -21,10 +21,11 @@ use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::message::ResponseHead; +use crate::payload::Payload; use crate::request::Request; use crate::response::Response; -use super::{H2ServiceResult, Payload}; +use super::H2ServiceResult; const CHUNK_SIZE: usize = 16_384; @@ -113,7 +114,7 @@ where } let (parts, body) = req.into_parts(); - let mut req = Request::with_payload(Payload::new(body)); + let mut req = Request::with_payload(body.into()); let head = &mut req.inner_mut().head; head.uri = parts.uri; diff --git a/src/h2/service.rs b/src/h2/service.rs index b598b0a6..5759b55e 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -14,11 +14,12 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error, ParseError, ResponseError}; +use crate::payload::Payload; use crate::request::Request; use crate::response::Response; use super::dispatcher::Dispatcher; -use super::{H2ServiceResult, Payload}; +use super::H2ServiceResult; /// `NewService` implementation for HTTP2 transport pub struct H2Service { From 2a6e4dc7ab0ea8fca3df3ebbb7e1905e4a7daa42 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 19:26:12 -0800 Subject: [PATCH 0902/1635] use non mutable self for HttpMessage::payload() for ergonomic reasons --- src/client/h2proto.rs | 3 ++- src/client/response.rs | 13 +++++++------ src/httpmessage.rs | 2 +- src/lib.rs | 2 +- src/request.rs | 18 +++++++++--------- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index f2f18d93..ecd18cf8 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::time; use actix_codec::{AsyncRead, AsyncWrite}; @@ -110,7 +111,7 @@ where Ok(ClientResponse { head, - payload: Some(Box::new(Payload::new(body))), + payload: RefCell::new(Some(Box::new(Payload::new(body)))), }) }) .from_err() diff --git a/src/client/response.rs b/src/client/response.rs index 65c59f2a..7a83d825 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::fmt; use bytes::Bytes; @@ -13,7 +14,7 @@ use crate::message::{Head, ResponseHead}; #[derive(Default)] pub struct ClientResponse { pub(crate) head: ResponseHead, - pub(crate) payload: Option, + pub(crate) payload: RefCell>, } impl HttpMessage for ClientResponse { @@ -24,8 +25,8 @@ impl HttpMessage for ClientResponse { } #[inline] - fn payload(&mut self) -> Option { - self.payload.take() + fn payload(&self) -> Option { + self.payload.borrow_mut().take() } } @@ -34,7 +35,7 @@ impl ClientResponse { pub fn new() -> ClientResponse { ClientResponse { head: ResponseHead::default(), - payload: None, + payload: RefCell::new(None), } } @@ -80,7 +81,7 @@ impl ClientResponse { /// Set response payload pub fn set_payload(&mut self, payload: PayloadStream) { - self.payload = Some(payload); + *self.payload.get_mut() = Some(payload); } } @@ -89,7 +90,7 @@ impl Stream for ClientResponse { type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { - if let Some(ref mut payload) = self.payload { + if let Some(ref mut payload) = self.payload.get_mut() { payload.poll() } else { Ok(Async::Ready(None)) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 39aa1b68..47fc57d6 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -25,7 +25,7 @@ pub trait HttpMessage: Sized { fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(&mut self) -> Option; + fn payload(&self) -> Option; #[doc(hidden)] /// Get a header diff --git a/src/lib.rs b/src/lib.rs index 71582393..f34da84c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ mod extensions; mod header; mod helpers; mod httpcodes; -mod httpmessage; +pub mod httpmessage; mod json; mod message; mod request; diff --git a/src/request.rs b/src/request.rs index f6be69dd..519cc38e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,4 +1,4 @@ -use std::cell::{Ref, RefMut}; +use std::cell::{Ref, RefCell, RefMut}; use std::fmt; use std::rc::Rc; @@ -14,7 +14,7 @@ use crate::payload::Payload; /// Request pub struct Request

    { - pub(crate) payload: Option

    , + pub(crate) payload: RefCell>, pub(crate) inner: Rc>, } @@ -29,8 +29,8 @@ where } #[inline] - fn payload(&mut self) -> Option

    { - self.payload.take() + fn payload(&self) -> Option

    { + self.payload.borrow_mut().take() } } @@ -38,7 +38,7 @@ impl Request { /// Create new Request instance pub fn new() -> Request { Request { - payload: None, + payload: RefCell::new(None), inner: MessagePool::get_message(), } } @@ -48,7 +48,7 @@ impl Request { /// Create new Request instance pub fn with_payload(payload: Payload) -> Request { Request { - payload: Some(payload), + payload: RefCell::new(Some(payload.into())), inner: MessagePool::get_message(), } } @@ -59,7 +59,7 @@ impl Request { I: Into

    , { Request { - payload: Some(payload.into()), + payload: RefCell::new(Some(payload.into())), inner: self.inner.clone(), } } @@ -67,9 +67,9 @@ impl Request { /// Take request's payload pub fn take_payload(mut self) -> (Option, Request<()>) { ( - self.payload.take(), + self.payload.get_mut().take(), Request { - payload: Some(()), + payload: RefCell::new(None), inner: self.inner.clone(), }, ) From a7a2d4cf5c82320fc742e479a6e3a02f86f68090 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 19:53:48 -0800 Subject: [PATCH 0903/1635] fix warns --- examples/echo.rs | 2 +- examples/echo2.rs | 2 +- src/httpmessage.rs | 43 +++++++++++++++++++++---------------------- src/json.rs | 10 +++++----- tests/test_client.rs | 4 ++-- tests/test_server.rs | 20 ++++++++++---------- 6 files changed, 40 insertions(+), 41 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index 03d5b470..5b68024f 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -18,7 +18,7 @@ fn main() { .client_timeout(1000) .client_disconnect(1000) .server_hostname("localhost") - .finish(|mut req: Request| { + .finish(|req: Request| { req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); diff --git a/examples/echo2.rs b/examples/echo2.rs index 2fd9cbcf..daaafa08 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -8,7 +8,7 @@ use futures::Future; use log::info; use std::env; -fn handle_request(mut req: Request) -> impl Future { +fn handle_request(req: Request) -> impl Future { req.body().limit(512).from_err().and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 47fc57d6..f071cd7b 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -128,7 +128,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn body(&mut self) -> MessageBody { + fn body(&self) -> MessageBody { MessageBody::new(self) } @@ -162,7 +162,7 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn urlencoded(&mut self) -> UrlEncoded { + fn urlencoded(&self) -> UrlEncoded { UrlEncoded::new(self) } @@ -198,12 +198,12 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn json(&mut self) -> JsonBody { + fn json(&self) -> JsonBody { JsonBody::new(self) } /// Return stream of lines. - fn readlines(&mut self) -> Readlines { + fn readlines(&self) -> Readlines { Readlines::new(self) } } @@ -220,10 +220,10 @@ pub struct Readlines { impl Readlines { /// Create a new stream to read request line by line. - fn new(req: &mut T) -> Self { + fn new(req: &T) -> Self { let encoding = match req.encoding() { Ok(enc) => enc, - Err(err) => return Self::err(req, err.into()), + Err(err) => return Self::err(err.into()), }; Readlines { @@ -242,9 +242,9 @@ impl Readlines { self } - fn err(req: &mut T, err: ReadlinesError) -> Self { + fn err(err: ReadlinesError) -> Self { Readlines { - stream: req.payload(), + stream: None, buff: BytesMut::new(), limit: 262_144, checked_buff: true, @@ -366,7 +366,7 @@ pub struct MessageBody { impl MessageBody { /// Create `MessageBody` for request. - pub fn new(req: &mut T) -> MessageBody { + pub fn new(req: &T) -> MessageBody { let mut len = None; if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -465,7 +465,7 @@ pub struct UrlEncoded { impl UrlEncoded { /// Create a new future to URL encode a request - pub fn new(req: &mut T) -> UrlEncoded { + pub fn new(req: &T) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -702,7 +702,7 @@ mod tests { #[test] fn test_urlencoded_error() { - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -713,7 +713,7 @@ mod tests { UrlencodedError::UnknownLength ); - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -724,7 +724,7 @@ mod tests { UrlencodedError::Overflow ); - let mut req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") + let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") .header(header::CONTENT_LENGTH, "10") .finish(); assert_eq!( @@ -735,7 +735,7 @@ mod tests { #[test] fn test_urlencoded() { - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -751,7 +751,7 @@ mod tests { }) ); - let mut req = TestRequest::with_header( + let req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) @@ -770,20 +770,19 @@ mod tests { #[test] fn test_message_body() { - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().poll().err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), } - let mut req = - TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), } - let mut req = TestRequest::default() + let req = TestRequest::default() .set_payload(Bytes::from_static(b"test")) .finish(); match req.body().poll().ok().unwrap() { @@ -791,7 +790,7 @@ mod tests { _ => unreachable!("error"), } - let mut req = TestRequest::default() + let req = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).poll().err().unwrap() { @@ -802,14 +801,14 @@ mod tests { #[test] fn test_readlines() { - let mut req = TestRequest::default() + let req = TestRequest::default() .set_payload(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .finish(); - let mut r = Readlines::new(&mut req); + let mut r = Readlines::new(&req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/json.rs b/src/json.rs index 6cd1e87a..f750f545 100644 --- a/src/json.rs +++ b/src/json.rs @@ -50,7 +50,7 @@ pub struct JsonBody { impl JsonBody { /// Create `JsonBody` for request. - pub fn new(req: &mut T) -> Self { + pub fn new(req: &T) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) @@ -164,11 +164,11 @@ mod tests { #[test] fn test_json_body() { - let mut req = TestRequest::default().finish(); + let req = TestRequest::default().finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let mut req = TestRequest::default() + let req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), @@ -177,7 +177,7 @@ mod tests { let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let mut req = TestRequest::default() + let req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -190,7 +190,7 @@ mod tests { let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - let mut req = TestRequest::default() + let req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), diff --git a/tests/test_client.rs b/tests/test_client.rs index 6f502b0a..606bac22 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -47,7 +47,7 @@ fn test_h1_v2() { assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); - let mut response = srv.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -55,7 +55,7 @@ fn test_h1_v2() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = srv.post().finish().unwrap(); - let mut response = srv.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response diff --git a/tests/test_server.rs b/tests/test_server.rs index 53db3840..9fa27e71 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -89,7 +89,7 @@ fn test_h2_body() -> std::io::Result<()> { .map_err(|e| println!("Openssl error: {}", e)) .and_then( h2::H2Service::build() - .finish(|mut req: Request<_>| { + .finish(|req: Request<_>| { req.body() .limit(1024 * 1024) .and_then(|body| Ok(Response::Ok().body(body))) @@ -101,7 +101,7 @@ fn test_h2_body() -> std::io::Result<()> { let req = client::ClientRequest::get(srv.surl("/")) .body(data.clone()) .unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); @@ -350,7 +350,7 @@ fn test_headers() { let req = srv.get().finish().unwrap(); - let mut response = srv.block_on(req.send(&mut connector)).unwrap(); + let response = srv.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -387,7 +387,7 @@ fn test_body() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -402,7 +402,7 @@ fn test_head_empty() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -428,7 +428,7 @@ fn test_head_binary() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -477,7 +477,7 @@ fn test_body_length() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -496,7 +496,7 @@ fn test_body_chunked_explicit() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -517,7 +517,7 @@ fn test_body_chunked_implicit() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -540,7 +540,7 @@ fn test_response_http_error_handling() { }); let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let response = srv.send_request(req).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response From b0e36fdcf9c3eb43606b3980fd12bf53bb8a0cd7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 21:16:46 -0800 Subject: [PATCH 0904/1635] simplify Message api --- src/h1/codec.rs | 8 +- src/h1/decoder.rs | 39 +++--- src/h2/dispatcher.rs | 2 +- src/message.rs | 104 ++++++++++---- src/request.rs | 95 +++++-------- src/response.rs | 321 +++++++++++++++---------------------------- src/test.rs | 10 +- 7 files changed, 243 insertions(+), 336 deletions(-) diff --git a/src/h1/codec.rs b/src/h1/codec.rs index fbc8b4a5..23feda50 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -115,10 +115,10 @@ impl Decoder for Codec { None => None, }) } else if let Some((req, payload)) = self.decoder.decode(src)? { - self.flags - .set(Flags::HEAD, req.inner.head.method == Method::HEAD); - self.version = req.inner().head.version; - self.ctype = req.inner().head.connection_type(); + let head = req.head(); + self.flags.set(Flags::HEAD, head.method == Method::HEAD); + self.version = head.version; + self.ctype = head.connection_type(); if self.ctype == ConnectionType::KeepAlive && !self.flags.contains(Flags::KEEPALIVE_ENABLED) { diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 74e1fb68..80bca94c 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -159,7 +159,7 @@ pub(crate) trait MessageType: Sized { impl MessageType for Request { fn set_connection_type(&mut self, ctype: Option) { - self.inner_mut().head.ctype = ctype; + self.head_mut().ctype = ctype; } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -218,12 +218,10 @@ impl MessageType for Request { } }; - { - let inner = msg.inner_mut(); - inner.head.uri = uri; - inner.head.method = method; - inner.head.version = ver; - } + let head = msg.head_mut(); + head.uri = uri; + head.method = method; + head.version = ver; Ok(Some((msg, decoder))) } @@ -817,7 +815,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().ctype, Some(ConnectionType::Close)); let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ @@ -825,7 +823,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().ctype, Some(ConnectionType::Close)); } #[test] @@ -836,7 +834,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().ctype, Some(ConnectionType::Close)); } #[test] @@ -847,7 +845,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); let mut buf = BytesMut::from( "GET /test HTTP/1.0\r\n\ @@ -855,7 +853,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); } #[test] @@ -866,7 +864,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); } #[test] @@ -877,7 +875,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.connection_type(), ConnectionType::Close); + assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] @@ -888,11 +886,8 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.inner().head.ctype, None); - assert_eq!( - req.inner().head.connection_type(), - ConnectionType::KeepAlive - ); + assert_eq!(req.head().ctype, None); + assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] @@ -905,7 +900,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ @@ -915,7 +910,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); } #[test] @@ -1013,7 +1008,7 @@ mod tests { ); let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(req.inner().head.ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); assert!(req.upgrade()); assert!(pl.is_unhandled()); } diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 301777a8..001acc56 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -116,7 +116,7 @@ where let (parts, body) = req.into_parts(); let mut req = Request::with_payload(body.into()); - let head = &mut req.inner_mut().head; + let head = &mut req.head_mut(); head.uri = parts.uri; head.method = parts.method; head.version = parts.version; diff --git a/src/message.rs b/src/message.rs index a7339222..08edeef3 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::rc::Rc; @@ -146,12 +146,59 @@ impl ResponseHead { } pub struct Message { - pub head: T, - pub extensions: RefCell, - pub(crate) pool: &'static MessagePool, + inner: Rc>, + pool: &'static MessagePool, } impl Message { + /// Get new message from the pool of objects + pub fn new() -> Self { + T::pool().get_message() + } + + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.inner.as_ref().extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.inner.as_ref().extensions.borrow_mut() + } +} + +impl std::ops::Deref for Message { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner.as_ref().head + } +} + +impl std::ops::DerefMut for Message { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut Rc::get_mut(&mut self.inner) + .expect("Multiple copies exist") + .head + } +} + +impl Drop for Message { + fn drop(&mut self) { + if Rc::strong_count(&self.inner) == 1 { + self.pool.release(self.inner.clone()); + } + } +} + +struct MessageInner { + head: T, + extensions: RefCell, +} + +impl MessageInner { #[inline] /// Reset request instance pub fn reset(&mut self) { @@ -160,10 +207,9 @@ impl Message { } } -impl Default for Message { +impl Default for MessageInner { fn default() -> Self { - Message { - pool: T::pool(), + MessageInner { head: T::default(), extensions: RefCell::new(Extensions::new()), } @@ -172,41 +218,39 @@ impl Default for Message { #[doc(hidden)] /// Request's objects pool -pub struct MessagePool(RefCell>>>); +pub struct MessagePool(RefCell>>>); thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); thread_local!(static RESPONSE_POOL: &'static MessagePool = MessagePool::::create()); -impl MessagePool { - /// Get default request's pool - pub fn pool() -> &'static MessagePool { - REQUEST_POOL.with(|p| *p) - } - - /// Get Request object - #[inline] - pub fn get_message() -> Rc> { - REQUEST_POOL.with(|pool| { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.reset(); - } - return msg; - } - Rc::new(Message::default()) - }) - } -} - impl MessagePool { fn create() -> &'static MessagePool { let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128))); Box::leak(Box::new(pool)) } + /// Get message from the pool + #[inline] + fn get_message(&'static self) -> Message { + if let Some(mut msg) = self.0.borrow_mut().pop_front() { + if let Some(r) = Rc::get_mut(&mut msg) { + r.reset(); + } + Message { + inner: msg, + pool: self, + } + } else { + Message { + inner: Rc::new(MessageInner::default()), + pool: self, + } + } + } + #[inline] /// Release request instance - pub(crate) fn release(&self, msg: Rc>) { + fn release(&self, msg: Rc>) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { v.push_front(msg); diff --git a/src/request.rs b/src/request.rs index 519cc38e..0064de4e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,6 +1,5 @@ use std::cell::{Ref, RefCell, RefMut}; use std::fmt; -use std::rc::Rc; use bytes::Bytes; use futures::Stream; @@ -9,13 +8,13 @@ use http::{header, HeaderMap, Method, Uri, Version}; use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; -use crate::message::{Message, MessagePool, RequestHead}; +use crate::message::{Message, RequestHead}; use crate::payload::Payload; /// Request pub struct Request

    { pub(crate) payload: RefCell>, - pub(crate) inner: Rc>, + pub(crate) inner: Message, } impl

    HttpMessage for Request

    @@ -25,7 +24,7 @@ where type Stream = P; fn headers(&self) -> &HeaderMap { - &self.inner.head.headers + &self.head().headers } #[inline] @@ -34,12 +33,21 @@ where } } +impl From> for Request { + fn from(msg: Message) -> Self { + Request { + payload: RefCell::new(None), + inner: msg, + } + } +} + impl Request { /// Create new Request instance pub fn new() -> Request { Request { payload: RefCell::new(None), - inner: MessagePool::get_message(), + inner: Message::new(), } } } @@ -49,7 +57,7 @@ impl Request { pub fn with_payload(payload: Payload) -> Request { Request { payload: RefCell::new(Some(payload.into())), - inner: MessagePool::get_message(), + inner: Message::new(), } } @@ -60,123 +68,90 @@ impl Request { { Request { payload: RefCell::new(Some(payload.into())), - inner: self.inner.clone(), + inner: self.inner, } } - /// Take request's payload - pub fn take_payload(mut self) -> (Option, Request<()>) { - ( - self.payload.get_mut().take(), - Request { - payload: RefCell::new(None), - inner: self.inner.clone(), - }, - ) - } - - // /// Create new Request instance with pool - // pub(crate) fn with_pool(pool: &'static MessagePool) -> Request { - // Request { - // inner: Rc::new(Message { - // pool, - // url: Url::default(), - // head: RequestHead::default(), - // status: StatusCode::OK, - // flags: Cell::new(MessageFlags::empty()), - // payload: RefCell::new(None), - // extensions: RefCell::new(Extensions::new()), - // }), - // } - // } - - #[inline] - #[doc(hidden)] - pub fn inner(&self) -> &Message { - self.inner.as_ref() - } - - #[inline] - #[doc(hidden)] - pub fn inner_mut(&mut self) -> &mut Message { - Rc::get_mut(&mut self.inner).expect("Multiple copies exist") + /// Split request into request head and payload + pub fn into_parts(mut self) -> (Message, Option) { + (self.inner, self.payload.get_mut().take()) } #[inline] /// Http message part of the request pub fn head(&self) -> &RequestHead { - &self.inner.as_ref().head + &*self.inner } #[inline] #[doc(hidden)] /// Mutable reference to a http message part of the request pub fn head_mut(&mut self) -> &mut RequestHead { - &mut self.inner_mut().head + &mut *self.inner } /// Request's uri. #[inline] pub fn uri(&self) -> &Uri { - &self.inner().head.uri + &self.head().uri } /// Mutable reference to the request's uri. #[inline] pub fn uri_mut(&mut self) -> &mut Uri { - &mut self.inner_mut().head.uri + &mut self.head_mut().uri } /// Read the Request method. #[inline] pub fn method(&self) -> &Method { - &self.inner().head.method + &self.head().method } /// Read the Request Version. #[inline] pub fn version(&self) -> Version { - self.inner().head.version + self.head().version } /// The target path of this Request. #[inline] pub fn path(&self) -> &str { - self.inner().head.uri.path() + self.head().uri.path() } #[inline] /// Returns Request's headers. pub fn headers(&self) -> &HeaderMap { - &self.inner().head.headers + &self.head().headers } #[inline] /// Returns mutable Request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().head.headers + &mut self.head_mut().headers } /// Request extensions #[inline] pub fn extensions(&self) -> Ref { - self.inner().extensions.borrow() + self.inner.extensions() } /// Mutable reference to a the request's extensions #[inline] pub fn extensions_mut(&self) -> RefMut { - self.inner().extensions.borrow_mut() + self.inner.extensions_mut() } /// Check if request requires connection upgrade pub fn upgrade(&self) -> bool { - if let Some(conn) = self.inner().head.headers.get(header::CONNECTION) { + if let Some(conn) = self.head().headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { return s.to_lowercase().contains("upgrade"); } } - self.inner().head.method == Method::CONNECT + self.head().method == Method::CONNECT } // #[doc(hidden)] @@ -189,14 +164,6 @@ impl Request { // } } -impl Drop for Request { - fn drop(&mut self) { - if Rc::strong_count(&self.inner) == 1 { - self.inner.pool.release(self.inner.clone()); - } - } -} - impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( diff --git a/src/response.rs b/src/response.rs index a4b65f2b..d84100fa 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,7 +1,4 @@ -#![allow(dead_code)] //! Http response -use std::cell::RefCell; -use std::collections::VecDeque; use std::io::Write; use std::{fmt, str}; @@ -9,26 +6,27 @@ use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::Stream; use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; +use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; use serde::Serialize; use serde_json; use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; use crate::error::Error; use crate::header::{Header, IntoHeaderValue}; -use crate::message::{ConnectionType, Head, ResponseHead}; - -/// max write buffer size 64k -pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; +use crate::message::{ConnectionType, Head, Message, ResponseHead}; /// An HTTP Response -pub struct Response(Box, ResponseBody); +pub struct Response { + head: Message, + body: ResponseBody, + error: Option, +} impl Response { /// Create http response builder with specific status. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { - ResponsePool::get(status) + ResponseBuilder::new(status) } /// Create http response builder @@ -40,14 +38,21 @@ impl Response { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - ResponsePool::with_body(status, Body::Empty) + let mut head: Message = Message::new(); + head.status = status; + + Response { + head, + body: ResponseBody::Body(Body::Empty), + error: None, + } } /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().error_response(); - resp.get_mut().error = Some(error); + resp.error = Some(error); resp } @@ -67,7 +72,7 @@ impl Response { } ResponseBuilder { - response: Some(self.0), + head: Some(self.head), err: None, cookies: jar, } @@ -75,90 +80,85 @@ impl Response { /// Convert response to response with body pub fn into_body(self) -> Response { - let b = match self.1 { + let b = match self.body { ResponseBody::Body(b) => b, ResponseBody::Other(b) => b, }; - Response(self.0, ResponseBody::Other(b)) + Response { + head: self.head, + error: self.error, + body: ResponseBody::Other(b), + } } } impl Response { - #[inline] - fn get_ref(&self) -> &InnerResponse { - self.0.as_ref() - } - - #[inline] - fn get_mut(&mut self) -> &mut InnerResponse { - self.0.as_mut() - } - #[inline] /// Http message part of the response pub fn head(&self) -> &ResponseHead { - &self.0.as_ref().head + &*self.head } #[inline] /// Mutable reference to a http message part of the response pub fn head_mut(&mut self) -> &mut ResponseHead { - &mut self.0.as_mut().head + &mut *self.head } /// Constructs a response with body #[inline] pub fn with_body(status: StatusCode, body: B) -> Response { - ResponsePool::with_body(status, body) + let mut head: Message = Message::new(); + head.status = status; + Response { + head, + body: ResponseBody::Body(body), + error: None, + } } /// The source `error` for this response #[inline] pub fn error(&self) -> Option<&Error> { - self.get_ref().error.as_ref() + self.error.as_ref() } /// Get the response status code #[inline] pub fn status(&self) -> StatusCode { - self.get_ref().head.status + self.head.status } /// Set the `StatusCode` for this response #[inline] pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.get_mut().head.status + &mut self.head.status } /// Get the headers from the response #[inline] pub fn headers(&self) -> &HeaderMap { - &self.get_ref().head.headers + &self.head.headers } /// Get a mutable reference to the headers #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.get_mut().head.headers + &mut self.head.headers } /// Get an iterator for the cookies set by this response #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self - .get_ref() - .head - .headers - .get_all(header::SET_COOKIE) - .iter(), + iter: self.head.headers.get_all(header::SET_COOKIE).iter(), } } /// Add a cookie to this response #[inline] pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { - let h = &mut self.get_mut().head.headers; + let h = &mut self.head.headers; HeaderValue::from_str(&cookie.to_string()) .map(|c| { h.append(header::SET_COOKIE, c); @@ -170,7 +170,7 @@ impl Response { /// the number of cookies removed. #[inline] pub fn del_cookie(&mut self, name: &str) -> usize { - let h = &mut self.get_mut().head.headers; + let h = &mut self.head.headers; let vals: Vec = h .get_all(header::SET_COOKIE) .iter() @@ -196,28 +196,36 @@ impl Response { /// Connection upgrade status #[inline] pub fn upgrade(&self) -> bool { - self.get_ref().head.upgrade() + self.head.upgrade() } /// Keep-alive status for this connection pub fn keep_alive(&self) -> bool { - self.get_ref().head.keep_alive() + self.head.keep_alive() } /// Get body os this response #[inline] - pub(crate) fn body(&self) -> &ResponseBody { - &self.1 + pub fn body(&self) -> &ResponseBody { + &self.body } /// Set a body pub(crate) fn set_body(self, body: B2) -> Response { - Response(self.0, ResponseBody::Body(body)) + Response { + head: self.head, + body: ResponseBody::Body(body), + error: None, + } } /// Drop request's body pub(crate) fn drop_body(self) -> Response<()> { - Response(self.0, ResponseBody::Body(())) + Response { + head: self.head, + body: ResponseBody::Body(()), + error: None, + } } /// Set a body and return previous body value @@ -225,21 +233,14 @@ impl Response { self, body: B2, ) -> (Response, ResponseBody) { - (Response(self.0, ResponseBody::Body(body)), self.1) - } - - /// Size of response in bytes, excluding HTTP headers - pub fn response_size(&self) -> u64 { - self.get_ref().response_size - } - - /// Set response size - pub(crate) fn set_response_size(&mut self, size: u64) { - self.get_mut().response_size = size; - } - - pub(crate) fn release(self) { - ResponsePool::release(self.0); + ( + Response { + head: self.head, + body: ResponseBody::Body(body), + error: self.error, + }, + self.body, + ) } } @@ -248,15 +249,15 @@ impl fmt::Debug for Response { let res = writeln!( f, "\nResponse {:?} {}{}", - self.get_ref().head.version, - self.get_ref().head.status, - self.get_ref().head.reason.unwrap_or(""), + self.head.version, + self.head.status, + self.head.reason.unwrap_or(""), ); let _ = writeln!(f, " headers:"); - for (key, val) in self.get_ref().head.headers.iter() { + for (key, val) in self.head.headers.iter() { let _ = writeln!(f, " {:?}: {:?}", key, val); } - let _ = writeln!(f, " body: {:?}", self.1.length()); + let _ = writeln!(f, " body: {:?}", self.body.length()); res } } @@ -284,17 +285,29 @@ impl<'a> Iterator for CookieIter<'a> { /// This type can be used to construct an instance of `Response` through a /// builder-like pattern. pub struct ResponseBuilder { - response: Option>, + head: Option>, err: Option, cookies: Option, } impl ResponseBuilder { + /// Create response builder + pub fn new(status: StatusCode) -> Self { + let mut head: Message = Message::new(); + head.status = status; + + ResponseBuilder { + head: Some(head), + err: None, + cookies: None, + } + } + /// Set HTTP status code of this response. #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.status = status; + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.status = status; } self } @@ -316,10 +329,10 @@ impl ResponseBuilder { /// ``` #[doc(hidden)] pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match hdr.try_into() { Ok(value) => { - parts.head.headers.append(H::name(), value); + parts.headers.append(H::name(), value); } Err(e) => self.err = Some(e.into()), } @@ -346,11 +359,11 @@ impl ResponseBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.response, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { - parts.head.headers.append(key, value); + parts.headers.append(key, value); } Err(e) => self.err = Some(e.into()), }, @@ -379,11 +392,11 @@ impl ResponseBuilder { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.response, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { - parts.head.headers.insert(key, value); + parts.headers.insert(key, value); } Err(e) => self.err = Some(e.into()), }, @@ -396,8 +409,8 @@ impl ResponseBuilder { /// 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.response, &self.err) { - parts.head.reason = Some(reason); + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.reason = Some(reason); } self } @@ -405,8 +418,8 @@ impl ResponseBuilder { /// Set connection type to KeepAlive #[inline] pub fn keep_alive(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_connection_type(ConnectionType::KeepAlive); + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::KeepAlive); } self } @@ -417,8 +430,8 @@ impl ResponseBuilder { where V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_connection_type(ConnectionType::Upgrade); + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Upgrade); } self.set_header(header::UPGRADE, value) } @@ -426,8 +439,8 @@ impl ResponseBuilder { /// Force close connection, even if it is marked as keep-alive #[inline] pub fn force_close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.head.set_connection_type(ConnectionType::Close); + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.set_connection_type(ConnectionType::Close); } self } @@ -438,10 +451,10 @@ impl ResponseBuilder { where HeaderValue: HttpTryFrom, { - if let Some(parts) = parts(&mut self.response, &self.err) { + if let Some(parts) = parts(&mut self.head, &self.err) { match HeaderValue::try_from(value) { Ok(value) => { - parts.head.headers.insert(header::CONTENT_TYPE, value); + parts.headers.insert(header::CONTENT_TYPE, value); } Err(e) => self.err = Some(e.into()), }; @@ -540,20 +553,6 @@ impl ResponseBuilder { self } - // /// Set write buffer capacity - // /// - // /// This parameter makes sense only for streaming response - // /// or actor. If write buffer reaches specified capacity, stream or actor - // /// get paused. - // /// - // /// Default write buffer capacity is 64kb - // pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { - // if let Some(parts) = parts(&mut self.response, &self.err) { - // parts.write_capacity = cap; - // } - // self - // } - /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. @@ -569,19 +568,23 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } - let mut response = self.response.take().expect("cannot reuse response builder"); + let mut response = self.head.take().expect("cannot reuse response builder"); if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { Ok(val) => { - let _ = response.head.headers.append(header::SET_COOKIE, val); + let _ = response.headers.append(header::SET_COOKIE, val); } Err(e) => return Response::from(Error::from(e)).into_body(), }; } } - Response(response, ResponseBody::Body(body)) + Response { + head: response, + body: ResponseBody::Body(body), + error: None, + } } #[inline] @@ -609,9 +612,8 @@ impl ResponseBuilder { pub fn json2(&mut self, value: &T) -> Response { match serde_json::to_string(value) { Ok(body) => { - let contains = if let Some(parts) = parts(&mut self.response, &self.err) - { - parts.head.headers.contains_key(header::CONTENT_TYPE) + let contains = if let Some(parts) = parts(&mut self.head, &self.err) { + parts.headers.contains_key(header::CONTENT_TYPE) } else { true }; @@ -636,7 +638,7 @@ impl ResponseBuilder { /// This method construct new `ResponseBuilder` pub fn take(&mut self) -> ResponseBuilder { ResponseBuilder { - response: self.response.take(), + head: self.head.take(), err: self.err.take(), cookies: self.cookies.take(), } @@ -646,9 +648,9 @@ impl ResponseBuilder { #[inline] #[allow(clippy::borrowed_box)] fn parts<'a>( - parts: &'a mut Option>, + parts: &'a mut Option>, err: &Option, -) -> Option<&'a mut Box> { +) -> Option<&'a mut Message> { if err.is_some() { return None; } @@ -719,107 +721,6 @@ impl From for Response { } } -struct InnerResponse { - head: ResponseHead, - response_size: u64, - error: Option, - pool: &'static ResponsePool, -} - -impl InnerResponse { - #[inline] - fn new(status: StatusCode, pool: &'static ResponsePool) -> InnerResponse { - InnerResponse { - head: ResponseHead { - status, - version: Version::default(), - headers: HeaderMap::with_capacity(16), - reason: None, - ctype: None, - }, - pool, - response_size: 0, - error: None, - } - } -} - -/// Internal use only! -pub(crate) struct ResponsePool(RefCell>>); - -thread_local!(static POOL: &'static ResponsePool = ResponsePool::pool()); - -impl ResponsePool { - fn pool() -> &'static ResponsePool { - let pool = ResponsePool(RefCell::new(VecDeque::with_capacity(128))); - Box::leak(Box::new(pool)) - } - - pub fn get_pool() -> &'static ResponsePool { - POOL.with(|p| *p) - } - - #[inline] - pub fn get_builder( - pool: &'static ResponsePool, - status: StatusCode, - ) -> ResponseBuilder { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.head.status = status; - ResponseBuilder { - response: Some(msg), - err: None, - cookies: None, - } - } else { - let msg = Box::new(InnerResponse::new(status, pool)); - ResponseBuilder { - response: Some(msg), - err: None, - cookies: None, - } - } - } - - #[inline] - pub fn get_response( - pool: &'static ResponsePool, - status: StatusCode, - body: B, - ) -> Response { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.head.status = status; - Response(msg, ResponseBody::Body(body)) - } else { - Response( - Box::new(InnerResponse::new(status, pool)), - ResponseBody::Body(body), - ) - } - } - - #[inline] - fn get(status: StatusCode) -> ResponseBuilder { - POOL.with(|pool| ResponsePool::get_builder(pool, status)) - } - - #[inline] - fn with_body(status: StatusCode, body: B) -> Response { - POOL.with(|pool| ResponsePool::get_response(pool, status, body)) - } - - #[inline] - fn release(mut inner: Box) { - let mut p = inner.pool.0.borrow_mut(); - if p.len() < 128 { - inner.head.clear(); - inner.response_size = 0; - inner.error = None; - p.push_front(inner); - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/test.rs b/src/test.rs index cd160e60..b0e30872 100644 --- a/src/test.rs +++ b/src/test.rs @@ -154,11 +154,11 @@ impl TestRequest { Request::with_payload(crate::h1::Payload::empty().into()) }; - let inner = req.inner_mut(); - inner.head.uri = uri; - inner.head.method = method; - inner.head.version = version; - inner.head.headers = headers; + let head = req.head_mut(); + head.uri = uri; + head.method = method; + head.version = version; + head.headers = headers; // req.set_cookies(cookies); req From ed7ca7fe079ef6e9ee419bafe54e517657846041 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Feb 2019 21:50:20 -0800 Subject: [PATCH 0905/1635] make Message clonable and expose as public --- Cargo.toml | 2 +- src/lib.rs | 1 + src/message.rs | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ea246762..f940ae86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://actix.rs/api/actix-http/stable/actix_http/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] -license = "MIT/Apache-2.0" +license = "Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" diff --git a/src/lib.rs b/src/lib.rs index f34da84c..932b5485 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; +pub use self::message::{Message, RequestHead, ResponseHead}; pub use self::request::Request; pub use self::response::Response; pub use self::service::{SendError, SendResponse}; diff --git a/src/message.rs b/src/message.rs index 08edeef3..12da9eaf 100644 --- a/src/message.rs +++ b/src/message.rs @@ -169,6 +169,15 @@ impl Message { } } +impl Clone for Message { + fn clone(&self) -> Self { + Message { + inner: self.inner.clone(), + pool: self.pool, + } + } +} + impl std::ops::Deref for Message { type Target = T; From c695358bcb7dac708888309b9b47af852e900cd3 Mon Sep 17 00:00:00 2001 From: cuebyte Date: Fri, 8 Feb 2019 22:33:00 +0100 Subject: [PATCH 0906/1635] Ignored the If-Modified-Since if If-None-Match is specified (#680) (#692) --- CHANGES.md | 4 ++++ src/fs.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 83803abb..1a18e092 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ * Add `from_file` and `from_file_with_config` to `NamedFile` to allow sending files without a known path. #670 +### Fixed + +* Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680 + ## [0.7.18] - 2019-01-10 ### Added diff --git a/src/fs.rs b/src/fs.rs index dcf6c539..604ac550 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -441,6 +441,8 @@ impl Responder for NamedFile { // check last modified let not_modified = if !none_match(etag.as_ref(), req) { true + } else if req.headers().contains_key(header::IF_NONE_MATCH) { + false } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = (last_modified, req.get_header()) { @@ -944,6 +946,8 @@ impl HttpRange { #[cfg(test)] mod tests { use std::fs; + use std::time::Duration; + use std::ops::Add; use super::*; use application::App; @@ -963,6 +967,43 @@ mod tests { assert_eq!(m, mime::APPLICATION_OCTET_STREAM); } + #[test] + fn test_if_modified_since_without_if_none_match() { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_cpu_pool(CpuPool::new(1)); + let since = header::HttpDate::from( + SystemTime::now().add(Duration::from_secs(60))); + + let req = TestRequest::default() + .header(header::IF_MODIFIED_SINCE, since) + .finish(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.status(), + StatusCode::NOT_MODIFIED + ); + } + + #[test] + fn test_if_modified_since_with_if_none_match() { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_cpu_pool(CpuPool::new(1)); + let since = header::HttpDate::from( + SystemTime::now().add(Duration::from_secs(60))); + + let req = TestRequest::default() + .header(header::IF_NONE_MATCH, "miss_etag") + .header(header::IF_MODIFIED_SINCE, since) + .finish(); + let resp = file.respond_to(&req).unwrap(); + assert_ne!( + resp.status(), + StatusCode::NOT_MODIFIED + ); + } + #[test] fn test_named_file_text() { assert!(NamedFile::open("test--").is_err()); From f3ed1b601ea6833fa20efae4d47f0251e6543b3a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 08:44:22 -0800 Subject: [PATCH 0907/1635] Change service response to Into --- src/h1/dispatcher.rs | 13 ++++++++----- src/h1/service.rs | 15 ++++++++++----- src/h2/dispatcher.rs | 14 +++++++++----- src/h2/service.rs | 21 ++++++++++++++------- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 0a0ed04e..667e674c 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -85,8 +85,9 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service, S::Error: Debug, + S::Response: Into>, B: MessageBody, { /// Create http/1 dispatcher. @@ -139,8 +140,9 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service, S::Error: Debug, + S::Response: Into>, B: MessageBody, { fn can_read(&self) -> bool { @@ -224,7 +226,7 @@ where State::ServiceCall(mut fut) => { match fut.poll().map_err(DispatchError::Service)? { Async::Ready(res) => { - let (res, body) = res.replace_body(()); + let (res, body) = res.into().replace_body(()); Some(self.send_response(res, body)?) } Async::NotReady => { @@ -287,7 +289,7 @@ where let mut task = self.service.call(req); match task.poll().map_err(DispatchError::Service)? { Async::Ready(res) => { - let (res, body) = res.replace_body(()); + let (res, body) = res.into().replace_body(()); self.send_response(res, body) } Async::NotReady => Ok(State::ServiceCall(task)), @@ -459,8 +461,9 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service>, + S: Service, S::Error: Debug, + S::Response: Into>, B: MessageBody, { type Item = H1ServiceResult; diff --git a/src/h1/service.rs b/src/h1/service.rs index fbc0a2f0..02a4b15d 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -28,9 +28,10 @@ pub struct H1Service { impl H1Service where - S: NewService, Response = Response> + Clone, + S: NewService> + Clone, S::Service: Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { /// Create new `HttpService` instance. @@ -53,9 +54,10 @@ where impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, + S: NewService + Clone, S::Service: Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { type Request = T; @@ -214,9 +216,10 @@ pub struct H1ServiceResponse { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService, S::Service: Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { type Item = H1ServiceHandler; @@ -240,8 +243,9 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service> + Clone, + S: Service + Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { @@ -256,8 +260,9 @@ where impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone, + S: Service + Clone, S::Error: Debug, + S::Response: Into>, B: MessageBody, { type Request = T; diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 001acc56..b7339fa1 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -50,8 +50,9 @@ pub struct Dispatcher { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, + S::Response: Into>, B: MessageBody + 'static, { pub fn new( @@ -91,8 +92,9 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, + S::Response: Into>, B: MessageBody + 'static, { type Item = (); @@ -149,8 +151,9 @@ enum ServiceResponseState { impl ServiceResponse where - S: Service, Response = Response> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, + S::Response: Into>, B: MessageBody + 'static, { fn prepare_response( @@ -216,8 +219,9 @@ where impl Future for ServiceResponse where - S: Service, Response = Response> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, + S::Response: Into>, B: MessageBody + 'static, { type Item = (); @@ -228,7 +232,7 @@ where ServiceResponseState::ServiceCall(ref mut call, ref mut send) => { match call.poll() { Ok(Async::Ready(res)) => { - let (res, body) = res.replace_body(()); + let (res, body) = res.into().replace_body(()); let mut send = send.take().unwrap(); let mut length = body.length(); diff --git a/src/h2/service.rs b/src/h2/service.rs index 5759b55e..a1526375 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -30,9 +30,10 @@ pub struct H2Service { impl H2Service where - S: NewService, Response = Response> + Clone, + S: NewService> + Clone, S::Service: Clone + 'static, S::Error: Into + Debug + 'static, + S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. @@ -55,9 +56,10 @@ where impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService, Response = Response> + Clone, + S: NewService> + Clone, S::Service: Clone + 'static, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody + 'static, { type Request = T; @@ -235,8 +237,9 @@ pub struct H2ServiceResponse { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, Response = Response>, + S: NewService>, S::Service: Clone + 'static, + S::Response: Into>, S::Error: Into + Debug, B: MessageBody + 'static, { @@ -261,8 +264,9 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service, Response = Response> + Clone + 'static, + S: Service> + Clone + 'static, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody + 'static, { fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { @@ -277,8 +281,9 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + Clone + 'static, + S: Service> + Clone + 'static, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody + 'static, { type Request = T; @@ -312,8 +317,9 @@ enum State { pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + Clone + 'static, + S: Service> + Clone + 'static, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody + 'static, { state: State, @@ -322,8 +328,9 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service, Response = Response> + Clone, + S: Service> + Clone, S::Error: Into + Debug, + S::Response: Into>, B: MessageBody, { type Item = (); From 6a343fae06c90aacd7c843d28c742a52cd7bc9f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 10:33:49 -0800 Subject: [PATCH 0908/1635] simplify Message type --- src/message.rs | 76 +++++++++++++++++++------------------------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/src/message.rs b/src/message.rs index 12da9eaf..7fb45bcc 100644 --- a/src/message.rs +++ b/src/message.rs @@ -45,6 +45,7 @@ pub struct RequestHead { pub version: Version, pub headers: HeaderMap, pub ctype: Option, + pub extensions: RefCell, } impl Default for RequestHead { @@ -55,6 +56,7 @@ impl Default for RequestHead { version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), ctype: None, + extensions: RefCell::new(Extensions::new()), } } } @@ -63,6 +65,7 @@ impl Head for RequestHead { fn clear(&mut self) { self.ctype = None; self.headers.clear(); + self.extensions.borrow_mut().clear(); } fn set_connection_type(&mut self, ctype: ConnectionType) { @@ -84,6 +87,20 @@ impl Head for RequestHead { } } +impl RequestHead { + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.extensions.borrow_mut() + } +} + #[derive(Debug)] pub struct ResponseHead { pub version: Version, @@ -146,7 +163,7 @@ impl ResponseHead { } pub struct Message { - inner: Rc>, + head: Rc, pool: &'static MessagePool, } @@ -155,24 +172,12 @@ impl Message { pub fn new() -> Self { T::pool().get_message() } - - /// Message extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.inner.as_ref().extensions.borrow() - } - - /// Mutable reference to a the message's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.inner.as_ref().extensions.borrow_mut() - } } impl Clone for Message { fn clone(&self) -> Self { Message { - inner: self.inner.clone(), + head: self.head.clone(), pool: self.pool, } } @@ -182,52 +187,27 @@ impl std::ops::Deref for Message { type Target = T; fn deref(&self) -> &Self::Target { - &self.inner.as_ref().head + &self.head.as_ref() } } impl std::ops::DerefMut for Message { fn deref_mut(&mut self) -> &mut Self::Target { - &mut Rc::get_mut(&mut self.inner) - .expect("Multiple copies exist") - .head + Rc::get_mut(&mut self.head).expect("Multiple copies exist") } } impl Drop for Message { fn drop(&mut self) { - if Rc::strong_count(&self.inner) == 1 { - self.pool.release(self.inner.clone()); - } - } -} - -struct MessageInner { - head: T, - extensions: RefCell, -} - -impl MessageInner { - #[inline] - /// Reset request instance - pub fn reset(&mut self) { - self.head.clear(); - self.extensions.borrow_mut().clear(); - } -} - -impl Default for MessageInner { - fn default() -> Self { - MessageInner { - head: T::default(), - extensions: RefCell::new(Extensions::new()), + if Rc::strong_count(&self.head) == 1 { + self.pool.release(self.head.clone()); } } } #[doc(hidden)] /// Request's objects pool -pub struct MessagePool(RefCell>>>); +pub struct MessagePool(RefCell>>); thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); thread_local!(static RESPONSE_POOL: &'static MessagePool = MessagePool::::create()); @@ -243,15 +223,15 @@ impl MessagePool { fn get_message(&'static self) -> Message { if let Some(mut msg) = self.0.borrow_mut().pop_front() { if let Some(r) = Rc::get_mut(&mut msg) { - r.reset(); + r.clear(); } Message { - inner: msg, + head: msg, pool: self, } } else { Message { - inner: Rc::new(MessageInner::default()), + head: Rc::new(T::default()), pool: self, } } @@ -259,7 +239,7 @@ impl MessagePool { #[inline] /// Release request instance - fn release(&self, msg: Rc>) { + fn release(&self, msg: Rc) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { v.push_front(msg); From a66d8589c2fd0048680d68945a47ab20fd3f3938 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 10:45:35 -0800 Subject: [PATCH 0909/1635] add Extensions::contains method --- src/extensions.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/extensions.rs b/src/extensions.rs index f7805641..148e4c18 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -26,6 +26,11 @@ impl Extensions { self.map.insert(TypeId::of::(), Box::new(val)); } + /// Check if container contains entry + pub fn contains(&self) -> bool { + self.map.get(&TypeId::of::()).is_some() + } + /// Get a reference to a type previously inserted on this `Extensions`. pub fn get(&self) -> Option<&T> { self.map From 1af149b9e61090556b3df98e21e11930dc9c337d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 20:27:39 -0800 Subject: [PATCH 0910/1635] remove Clone constraint from handler service --- src/h1/dispatcher.rs | 15 ++++++++------- src/h1/service.rs | 22 +++++++++++----------- src/h2/dispatcher.rs | 7 ++++--- src/h2/service.rs | 35 ++++++++++++++++++++--------------- src/request.rs | 9 --------- 5 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 667e674c..06d4312a 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -5,6 +5,7 @@ use std::time::Instant; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::Service; +use actix_utils::cloneable::CloneableService; use bitflags::bitflags; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; use log::{debug, error, trace}; @@ -35,18 +36,18 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher where S::Error: Debug, { - service: S, + service: CloneableService, flags: Flags, framed: Framed, error: Option>, @@ -85,13 +86,13 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, { /// Create http/1 dispatcher. - pub fn new(stream: T, config: ServiceConfig, service: S) -> Self { + pub fn new(stream: T, config: ServiceConfig, service: CloneableService) -> Self { Dispatcher::with_timeout(stream, config, None, service) } @@ -100,7 +101,7 @@ where stream: T, config: ServiceConfig, timeout: Option, - service: S, + service: CloneableService, ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { @@ -140,7 +141,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/h1/service.rs b/src/h1/service.rs index 02a4b15d..169a80eb 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -4,6 +4,7 @@ use std::net; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoNewService, NewService, Service}; +use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, Poll, Stream}; use log::error; @@ -28,10 +29,10 @@ pub struct H1Service { impl H1Service where - S: NewService> + Clone, - S::Service: Clone, + S: NewService>, S::Error: Debug, S::Response: Into>, + S::Service: 'static, B: MessageBody, { /// Create new `HttpService` instance. @@ -54,10 +55,10 @@ where impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService + Clone, - S::Service: Clone, + S: NewService, S::Error: Debug, S::Response: Into>, + S::Service: 'static, B: MessageBody, { type Request = T; @@ -93,7 +94,6 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where S: NewService, - S::Service: Clone, S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` @@ -217,7 +217,7 @@ impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, - S::Service: Clone, + S::Service: 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -235,22 +235,22 @@ where } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { - srv: S, +pub struct H1ServiceHandler { + srv: CloneableService, cfg: ServiceConfig, _t: PhantomData<(T, B)>, } impl H1ServiceHandler where - S: Service + Clone, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, { fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { - srv, + srv: CloneableService::new(srv), cfg, _t: PhantomData, } @@ -260,7 +260,7 @@ where impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + Clone, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index b7339fa1..5a8c4b85 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -5,6 +5,7 @@ use std::{fmt, mem}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; +use actix_utils::cloneable::CloneableService; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; @@ -37,9 +38,9 @@ bitflags! { } /// Dispatcher for HTTP/2 protocol -pub struct Dispatcher { +pub struct Dispatcher { flags: Flags, - service: S, + service: CloneableService, connection: Connection, config: ServiceConfig, ka_expire: Instant, @@ -56,7 +57,7 @@ where B: MessageBody + 'static, { pub fn new( - service: S, + service: CloneableService, connection: Connection, config: ServiceConfig, timeout: Option, diff --git a/src/h2/service.rs b/src/h2/service.rs index a1526375..16b7e495 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -4,6 +4,7 @@ use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoNewService, NewService, Service}; +use actix_utils::cloneable::CloneableService; use bytes::Bytes; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, Poll, Stream}; @@ -30,8 +31,8 @@ pub struct H2Service { impl H2Service where - S: NewService> + Clone, - S::Service: Clone + 'static, + S: NewService>, + S::Service: 'static, S::Error: Into + Debug + 'static, S::Response: Into>, B: MessageBody + 'static, @@ -56,8 +57,8 @@ where impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService> + Clone, - S::Service: Clone + 'static, + S: NewService>, + S::Service: 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -95,7 +96,7 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where S: NewService>, - S::Service: Clone + 'static, + S::Service: 'static, S::Error: Into + Debug + 'static, { /// Create instance of `H2ServiceBuilder` @@ -238,7 +239,7 @@ impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService>, - S::Service: Clone + 'static, + S::Service: 'static, S::Response: Into>, S::Error: Into + Debug, B: MessageBody + 'static, @@ -256,23 +257,23 @@ where } /// `Service` implementation for http/2 transport -pub struct H2ServiceHandler { - srv: S, +pub struct H2ServiceHandler { + srv: CloneableService, cfg: ServiceConfig, _t: PhantomData<(T, B)>, } impl H2ServiceHandler where - S: Service> + Clone + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, { fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { H2ServiceHandler { - srv, cfg, + srv: CloneableService::new(srv), _t: PhantomData, } } @@ -281,7 +282,7 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + Clone + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -309,15 +310,19 @@ where } } -enum State { +enum State { Incoming(Dispatcher), - Handshake(Option, Option, Handshake), + Handshake( + Option>, + Option, + Handshake, + ), } pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + Clone + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -328,7 +333,7 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + Clone, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody, diff --git a/src/request.rs b/src/request.rs index 0064de4e..b9c35c7a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -153,15 +153,6 @@ impl Request { } self.head().method == Method::CONNECT } - - // #[doc(hidden)] - // /// Note: this method should be called only as part of clone operation - // /// of wrapper type. - // pub fn clone_request(&self) -> Self { - // Request { - // inner: self.inner.clone(), - // } - // } } impl fmt::Debug for Request { From e178db7f74bffad6fa562044c507083c634a3956 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Feb 2019 21:32:44 -0800 Subject: [PATCH 0911/1635] fix test --- src/h1/dispatcher.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 06d4312a..6a176274 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -599,7 +599,9 @@ mod tests { let mut h1 = Dispatcher::new( buf, ServiceConfig::default(), - (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + CloneableService::new( + (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + ), ); assert!(h1.poll().is_ok()); assert!(h1.poll().is_ok()); From f9724fa0ec8f1482fa911f24a0ce6c3a37931385 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Feb 2019 09:54:41 -0800 Subject: [PATCH 0912/1635] add ErrorResponse impl for TimeoutError --- Cargo.toml | 3 +-- src/error.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f940ae86..5bcaea7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ ssl = ["openssl"] actix-service = "0.2.1" actix-codec = "0.1.0" actix-connector = "0.2.0" -actix-utils = "0.2.1" +actix-utils = "0.2.2" base64 = "0.10" backtrace = "0.3" @@ -80,7 +80,6 @@ openssl = { version="0.10", optional = true } actix-rt = "0.1.0" actix-server = { version="0.2", features=["ssl"] } actix-connector = { version="0.2.0", features=["ssl"] } -actix-utils = "0.2.1" actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/src/error.rs b/src/error.rs index f71b429f..cd5cabaa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ use std::string::FromUtf8Error; use std::{fmt, io, result}; // use actix::MailboxError; +use actix_utils::timeout::TimeoutError; use backtrace::Backtrace; use cookie; use derive_more::{Display, From}; @@ -187,6 +188,16 @@ impl From for Error { // } // } +/// Return `GATEWAY_TIMEOUT` for `TimeoutError` +impl ResponseError for TimeoutError { + fn error_response(&self) -> Response { + match self { + TimeoutError::Service(e) => e.error_response(), + TimeoutError::Timeout => Response::new(StatusCode::GATEWAY_TIMEOUT), + } + } +} + /// `InternalServerError` for `JsonError` impl ResponseError for JsonError {} From 32021532c398b550ba09e639a334d09bc60e41b1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Feb 2019 09:55:29 -0800 Subject: [PATCH 0913/1635] export Payload type --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 932b5485..2b9d8c61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ mod httpcodes; pub mod httpmessage; mod json; mod message; +mod payload; mod request; mod response; mod service; @@ -85,7 +86,6 @@ mod service; pub mod error; pub mod h1; pub mod h2; -pub mod payload; pub mod test; pub mod ws; @@ -95,6 +95,7 @@ pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; pub use self::message::{Message, RequestHead, ResponseHead}; +pub use self::payload::Payload; pub use self::request::Request; pub use self::response::Response; pub use self::service::{SendError, SendResponse}; From a41459bf698ca13c56822da1fcf7137c1a452931 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Feb 2019 11:07:42 -0800 Subject: [PATCH 0914/1635] make payload generic --- src/body.rs | 5 +- src/client/h1proto.rs | 5 +- src/client/h2proto.rs | 3 +- src/client/response.rs | 25 ++++----- src/h1/dispatcher.rs | 2 +- src/h1/service.rs | 4 +- src/httpmessage.rs | 117 +++++++++++++++++++---------------------- src/json.rs | 10 ++-- src/payload.rs | 41 ++++++++++++--- src/request.rs | 38 ++++++------- 10 files changed, 127 insertions(+), 123 deletions(-) diff --git a/src/body.rs b/src/body.rs index 1c54d4ce..d3e63f9c 100644 --- a/src/body.rs +++ b/src/body.rs @@ -4,10 +4,7 @@ use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::{Async, Poll, Stream}; -use crate::error::{Error, PayloadError}; - -/// Type represent streaming payload -pub type PayloadStream = Box>; +use crate::error::Error; #[derive(Debug, PartialEq, Copy, Clone)] /// Different type of body diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index 59a03ef4..d1649138 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -9,10 +9,11 @@ use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectorError, SendRequestError}; use super::pool::Acquired; use super::response::ClientResponse; -use crate::body::{BodyLength, MessageBody, PayloadStream}; +use crate::body::{BodyLength, MessageBody}; use crate::error::PayloadError; use crate::h1; use crate::message::RequestHead; +use crate::payload::PayloadStream; pub(crate) fn send_request( io: T, @@ -57,7 +58,7 @@ where release_connection(framed, force_close) } _ => { - res.set_payload(Payload::stream(framed)); + res.set_payload(Payload::stream(framed).into()); } } ok(res) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index ecd18cf8..697d30a4 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -10,7 +10,6 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{request::Request, HttpTryFrom, Version}; use crate::body::{BodyLength, MessageBody}; -use crate::h2::Payload; use crate::message::{RequestHead, ResponseHead}; use super::connection::{ConnectionType, IoConnection}; @@ -111,7 +110,7 @@ where Ok(ClientResponse { head, - payload: RefCell::new(Some(Box::new(Payload::new(body)))), + payload: RefCell::new(body.into()), }) }) .from_err() diff --git a/src/client/response.rs b/src/client/response.rs index 7a83d825..f19e2d17 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,20 +1,19 @@ use std::cell::RefCell; -use std::fmt; +use std::{fmt, mem}; use bytes::Bytes; -use futures::{Async, Poll, Stream}; +use futures::{Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; -use crate::body::PayloadStream; use crate::error::PayloadError; use crate::httpmessage::HttpMessage; use crate::message::{Head, ResponseHead}; +use crate::payload::{Payload, PayloadStream}; /// Client Response -#[derive(Default)] pub struct ClientResponse { pub(crate) head: ResponseHead, - pub(crate) payload: RefCell>, + pub(crate) payload: RefCell, } impl HttpMessage for ClientResponse { @@ -25,8 +24,8 @@ impl HttpMessage for ClientResponse { } #[inline] - fn payload(&self) -> Option { - self.payload.borrow_mut().take() + fn payload(&self) -> Payload { + mem::replace(&mut *self.payload.borrow_mut(), Payload::None) } } @@ -35,7 +34,7 @@ impl ClientResponse { pub fn new() -> ClientResponse { ClientResponse { head: ResponseHead::default(), - payload: RefCell::new(None), + payload: RefCell::new(Payload::None), } } @@ -80,8 +79,8 @@ impl ClientResponse { } /// Set response payload - pub fn set_payload(&mut self, payload: PayloadStream) { - *self.payload.get_mut() = Some(payload); + pub fn set_payload(&mut self, payload: Payload) { + *self.payload.get_mut() = payload; } } @@ -90,11 +89,7 @@ impl Stream for ClientResponse { type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { - if let Some(ref mut payload) = self.payload.get_mut() { - payload.poll() - } else { - Ok(Async::Ready(None)) - } + self.payload.get_mut().poll() } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 6a176274..c242333b 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -316,7 +316,7 @@ where match self.framed.get_codec().message_type() { MessageType::Payload => { let (ps, pl) = Payload::create(false); - req = req.set_payload(pl); + req = req.set_payload(crate::Payload::H1(pl)); self.payload = Some(ps); } MessageType::Stream => { diff --git a/src/h1/service.rs b/src/h1/service.rs index 169a80eb..38186449 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -12,7 +12,7 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, ParseError}; -use crate::payload::Payload; +use crate::payload::PayloadStream; use crate::request::Request; use crate::response::Response; @@ -29,7 +29,7 @@ pub struct H1Service { impl H1Service where - S: NewService>, + S: NewService>, S::Error: Debug, S::Response: Into>, S::Service: 'static, diff --git a/src/httpmessage.rs b/src/httpmessage.rs index f071cd7b..57ad4bf9 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,3 +1,5 @@ +use std::{mem, str}; + use bytes::{Bytes, BytesMut}; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; @@ -8,13 +10,13 @@ use http::{header, HeaderMap}; use mime::Mime; use serde::de::DeserializeOwned; use serde_urlencoded; -use std::str; use crate::error::{ ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, }; use crate::header::Header; use crate::json::JsonBody; +use crate::payload::Payload; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { @@ -25,7 +27,7 @@ pub trait HttpMessage: Sized { fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(&self) -> Option; + fn payload(&self) -> Payload; #[doc(hidden)] /// Get a header @@ -210,7 +212,7 @@ pub trait HttpMessage: Sized { /// Stream to read request line by line. pub struct Readlines { - stream: Option, + stream: Payload, buff: BytesMut, limit: usize, checked_buff: bool, @@ -244,7 +246,7 @@ impl Readlines { fn err(err: ReadlinesError) -> Self { Readlines { - stream: None, + stream: Payload::None, buff: BytesMut::new(), limit: 262_144, checked_buff: true, @@ -292,65 +294,61 @@ impl Stream for Readlines { self.checked_buff = true; } // poll req for more bytes - if let Some(ref mut stream) = self.stream { - match stream.poll() { - Ok(Async::Ready(Some(mut bytes))) => { - // check if there is a newline in bytes - let mut found: Option = None; - for (ind, b) in bytes.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } + match self.stream.poll() { + Ok(Async::Ready(Some(mut bytes))) => { + // check if there is a newline in bytes + let mut found: Option = None; + for (ind, b) in bytes.iter().enumerate() { + if *b == b'\n' { + found = Some(ind); + break; } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&bytes.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - // extend buffer with rest of the bytes; - self.buff.extend_from_slice(&bytes); - self.checked_buff = false; - return Ok(Async::Ready(Some(line))); - } - self.buff.extend_from_slice(&bytes); - Ok(Async::NotReady) } - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - if self.buff.is_empty() { - return Ok(Async::Ready(None)); - } - if self.buff.len() > self.limit { + if let Some(ind) = found { + // check if line is longer than limit + if ind + 1 > self.limit { return Err(ReadlinesError::LimitOverflow); } let enc: *const Encoding = self.encoding as *const Encoding; let line = if enc == UTF_8 { - str::from_utf8(&self.buff) + str::from_utf8(&bytes.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { self.encoding - .decode(&self.buff, DecoderTrap::Strict) + .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) .map_err(|_| ReadlinesError::EncodingError)? }; - self.buff.clear(); - Ok(Async::Ready(Some(line))) + // extend buffer with rest of the bytes; + self.buff.extend_from_slice(&bytes); + self.checked_buff = false; + return Ok(Async::Ready(Some(line))); } - Err(e) => Err(ReadlinesError::from(e)), + self.buff.extend_from_slice(&bytes); + Ok(Async::NotReady) } - } else { - Ok(Async::Ready(None)) + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + if self.buff.is_empty() { + return Ok(Async::Ready(None)); + } + if self.buff.len() > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = self.encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&self.buff) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + self.encoding + .decode(&self.buff, DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + self.buff.clear(); + Ok(Async::Ready(Some(line))) + } + Err(e) => Err(ReadlinesError::from(e)), } } } @@ -359,7 +357,7 @@ impl Stream for Readlines { pub struct MessageBody { limit: usize, length: Option, - stream: Option, + stream: Payload, err: Option, fut: Option>>, } @@ -397,7 +395,7 @@ impl MessageBody { fn err(e: PayloadError) -> Self { MessageBody { - stream: None, + stream: Payload::None, limit: 262_144, fut: None, err: Some(e), @@ -428,16 +426,10 @@ where } } - if self.stream.is_none() { - return Ok(Async::Ready(Bytes::new())); - } - // future let limit = self.limit; self.fut = Some(Box::new( - self.stream - .take() - .expect("Can not be used second time") + mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { @@ -455,7 +447,7 @@ where /// Future that resolves to a parsed urlencoded values. pub struct UrlEncoded { - stream: Option, + stream: Payload, limit: usize, length: Option, encoding: EncodingRef, @@ -500,7 +492,7 @@ impl UrlEncoded { fn err(e: UrlencodedError) -> Self { UrlEncoded { - stream: None, + stream: Payload::None, limit: 262_144, fut: None, err: Some(e), @@ -543,10 +535,7 @@ where // future let encoding = self.encoding; - let fut = self - .stream - .take() - .expect("UrlEncoded could not be used second time") + let fut = mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { diff --git a/src/json.rs b/src/json.rs index f750f545..573fde41 100644 --- a/src/json.rs +++ b/src/json.rs @@ -8,6 +8,7 @@ use serde_json; use crate::error::JsonPayloadError; use crate::httpmessage::HttpMessage; +use crate::payload::Payload; /// Request payload json parser that resolves to a deserialized `T` value. /// @@ -43,7 +44,7 @@ use crate::httpmessage::HttpMessage; pub struct JsonBody { limit: usize, length: Option, - stream: Option, + stream: Payload, err: Option, fut: Option>>, } @@ -61,7 +62,7 @@ impl JsonBody { return JsonBody { limit: 262_144, length: None, - stream: None, + stream: Payload::None, fut: None, err: Some(JsonPayloadError::ContentType), }; @@ -112,10 +113,7 @@ impl Future for JsonBod } } - let fut = self - .stream - .take() - .expect("JsonBody could not be used second time") + let fut = std::mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { diff --git a/src/payload.rs b/src/payload.rs index ede1281e..21e41531 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -1,32 +1,57 @@ use bytes::Bytes; -use derive_more::From; -use futures::{Poll, Stream}; +use futures::{Async, Poll, Stream}; use h2::RecvStream; use crate::error::PayloadError; -#[derive(From)] -pub enum Payload { +/// Type represent boxed payload +pub type PayloadStream = Box>; + +/// Type represent streaming payload +pub enum Payload { + None, H1(crate::h1::Payload), H2(crate::h2::Payload), - Dyn(Box>), + Stream(S), } -impl From for Payload { +impl From for Payload { fn from(v: RecvStream) -> Self { Payload::H2(crate::h2::Payload::new(v)) } } -impl Stream for Payload { +impl From for Payload { + fn from(pl: crate::h1::Payload) -> Self { + Payload::H1(pl) + } +} + +impl From for Payload { + fn from(pl: crate::h2::Payload) -> Self { + Payload::H2(pl) + } +} + +impl From for Payload { + fn from(pl: PayloadStream) -> Self { + Payload::Stream(pl) + } +} + +impl Stream for Payload +where + S: Stream, +{ type Item = Bytes; type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { match self { + Payload::None => Ok(Async::Ready(None)), Payload::H1(ref mut pl) => pl.poll(), Payload::H2(ref mut pl) => pl.poll(), - Payload::Dyn(ref mut pl) => pl.poll(), + Payload::Stream(ref mut pl) => pl.poll(), } } } diff --git a/src/request.rs b/src/request.rs index b9c35c7a..388fe754 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,5 +1,5 @@ use std::cell::{Ref, RefCell, RefMut}; -use std::fmt; +use std::{fmt, mem}; use bytes::Bytes; use futures::Stream; @@ -9,11 +9,11 @@ use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, RequestHead}; -use crate::payload::Payload; +use crate::payload::{Payload, PayloadStream}; /// Request -pub struct Request

    { - pub(crate) payload: RefCell>, +pub struct Request

    { + pub(crate) payload: RefCell>, pub(crate) inner: Message, } @@ -28,53 +28,53 @@ where } #[inline] - fn payload(&self) -> Option

    { - self.payload.borrow_mut().take() + fn payload(&self) -> Payload { + mem::replace(&mut *self.payload.borrow_mut(), Payload::None) } } -impl From> for Request { +impl

    From> for Request

    { fn from(msg: Message) -> Self { Request { - payload: RefCell::new(None), + payload: RefCell::new(Payload::None), inner: msg, } } } -impl Request { +impl Request { /// Create new Request instance - pub fn new() -> Request { + pub fn new() -> Request { Request { - payload: RefCell::new(None), + payload: RefCell::new(Payload::None), inner: Message::new(), } } } -impl Request { +impl

    Request

    { /// Create new Request instance - pub fn with_payload(payload: Payload) -> Request { + pub fn with_payload(payload: Payload

    ) -> Request

    { Request { - payload: RefCell::new(Some(payload.into())), + payload: RefCell::new(payload), inner: Message::new(), } } /// Create new Request instance - pub fn set_payload(self, payload: I) -> Request

    + pub fn set_payload(self, payload: I) -> Request where - I: Into

    , + I: Into>, { Request { - payload: RefCell::new(Some(payload.into())), + payload: RefCell::new(payload.into()), inner: self.inner, } } /// Split request into request head and payload - pub fn into_parts(mut self) -> (Message, Option) { - (self.inner, self.payload.get_mut().take()) + pub fn into_parts(self) -> (Message, Payload

    ) { + (self.inner, self.payload.into_inner()) } #[inline] From 8d4ce0c956f6a81d0b8aec50ad220b352f12e5c0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Feb 2019 11:09:58 -0800 Subject: [PATCH 0915/1635] export PayloadStream --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2b9d8c61..4e6e3795 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,7 @@ pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; pub use self::message::{Message, RequestHead, ResponseHead}; -pub use self::payload::Payload; +pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::Response; pub use self::service::{SendError, SendResponse}; From 0059a55dfb1a535b95791cecf339247702f786e3 Mon Sep 17 00:00:00 2001 From: Michael Edwards Date: Wed, 6 Feb 2019 11:02:33 +0100 Subject: [PATCH 0916/1635] Fix typo --- src/server/http.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/server/http.rs b/src/server/http.rs index 76049981..5ff621af 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -451,9 +451,8 @@ impl H + Send + Clone> 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. + /// This methods panics if no socket address can be bound or an `Actix` system is not yet + /// configured. /// /// ```rust /// extern crate actix_web; From 118606262be9dcfe64b9870308cd2dccb26111d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Feb 2019 13:52:11 -0800 Subject: [PATCH 0917/1635] refactor payload handling --- examples/echo.rs | 2 +- examples/echo2.rs | 2 +- src/client/h1proto.rs | 5 ++- src/client/h2proto.rs | 7 ++-- src/client/response.rs | 22 +++++----- src/h1/dispatcher.rs | 4 +- src/h1/service.rs | 3 +- src/httpmessage.rs | 95 ++++++++++++++++++++++++++++-------------- src/json.rs | 31 +++++++++----- src/message.rs | 25 ++++++++++- src/payload.rs | 20 ++++----- src/request.rs | 71 ++++++++++++++++--------------- tests/test_client.rs | 4 +- tests/test_server.rs | 20 ++++----- 14 files changed, 186 insertions(+), 125 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index 5b68024f..03d5b470 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -18,7 +18,7 @@ fn main() { .client_timeout(1000) .client_disconnect(1000) .server_hostname("localhost") - .finish(|req: Request| { + .finish(|mut req: Request| { req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); diff --git a/examples/echo2.rs b/examples/echo2.rs index daaafa08..2fd9cbcf 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -8,7 +8,7 @@ use futures::Future; use log::info; use std::env; -fn handle_request(req: Request) -> impl Future { +fn handle_request(mut req: Request) -> impl Future { req.body().limit(512).from_err().and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); let mut res = Response::Ok(); diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index d1649138..3329fcfe 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -13,7 +13,6 @@ use crate::body::{BodyLength, MessageBody}; use crate::error::PayloadError; use crate::h1; use crate::message::RequestHead; -use crate::payload::PayloadStream; pub(crate) fn send_request( io: T, @@ -205,7 +204,9 @@ pub(crate) struct Payload { } impl Payload { - pub fn stream(framed: Framed) -> PayloadStream { + pub fn stream( + framed: Framed, + ) -> Box> { Box::new(Payload { framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), }) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index 697d30a4..8804d13f 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::time; use actix_codec::{AsyncRead, AsyncWrite}; @@ -10,7 +9,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{request::Request, HttpTryFrom, Version}; use crate::body::{BodyLength, MessageBody}; -use crate::message::{RequestHead, ResponseHead}; +use crate::message::{Message, RequestHead, ResponseHead}; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; @@ -103,14 +102,14 @@ where .and_then(|resp| { let (parts, body) = resp.into_parts(); - let mut head = ResponseHead::default(); + let mut head: Message = Message::new(); head.version = parts.version; head.status = parts.status; head.headers = parts.headers; Ok(ClientResponse { head, - payload: RefCell::new(body.into()), + payload: body.into(), }) }) .from_err() diff --git a/src/client/response.rs b/src/client/response.rs index f19e2d17..104d28ed 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,5 +1,4 @@ -use std::cell::RefCell; -use std::{fmt, mem}; +use std::fmt; use bytes::Bytes; use futures::{Poll, Stream}; @@ -7,13 +6,13 @@ use http::{HeaderMap, StatusCode, Version}; use crate::error::PayloadError; use crate::httpmessage::HttpMessage; -use crate::message::{Head, ResponseHead}; +use crate::message::{Head, Message, ResponseHead}; use crate::payload::{Payload, PayloadStream}; /// Client Response pub struct ClientResponse { - pub(crate) head: ResponseHead, - pub(crate) payload: RefCell, + pub(crate) head: Message, + pub(crate) payload: Payload, } impl HttpMessage for ClientResponse { @@ -23,9 +22,8 @@ impl HttpMessage for ClientResponse { &self.head.headers } - #[inline] - fn payload(&self) -> Payload { - mem::replace(&mut *self.payload.borrow_mut(), Payload::None) + fn take_payload(&mut self) -> Payload { + std::mem::replace(&mut self.payload, Payload::None) } } @@ -33,8 +31,8 @@ impl ClientResponse { /// Create new Request instance pub fn new() -> ClientResponse { ClientResponse { - head: ResponseHead::default(), - payload: RefCell::new(Payload::None), + head: Message::new(), + payload: Payload::None, } } @@ -80,7 +78,7 @@ impl ClientResponse { /// Set response payload pub fn set_payload(&mut self, payload: Payload) { - *self.payload.get_mut() = payload; + self.payload = payload; } } @@ -89,7 +87,7 @@ impl Stream for ClientResponse { type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { - self.payload.get_mut().poll() + self.payload.poll() } } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index c242333b..22d7ea86 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -316,7 +316,9 @@ where match self.framed.get_codec().message_type() { MessageType::Payload => { let (ps, pl) = Payload::create(false); - req = req.set_payload(crate::Payload::H1(pl)); + let (req1, _) = + req.replace_payload(crate::Payload::H1(pl)); + req = req1; self.payload = Some(ps); } MessageType::Stream => { diff --git a/src/h1/service.rs b/src/h1/service.rs index 38186449..cb8dae54 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -12,7 +12,6 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, ParseError}; -use crate::payload::PayloadStream; use crate::request::Request; use crate::response::Response; @@ -29,7 +28,7 @@ pub struct H1Service { impl H1Service where - S: NewService>, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 57ad4bf9..79f29a72 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,4 +1,4 @@ -use std::{mem, str}; +use std::str; use bytes::{Bytes, BytesMut}; use encoding::all::UTF_8; @@ -21,13 +21,13 @@ use crate::payload::Payload; /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { /// Type of message payload stream - type Stream: Stream + Sized; + type Stream; /// Read the message headers. fn headers(&self) -> &HeaderMap; /// Message payload stream - fn payload(&self) -> Payload; + fn take_payload(&mut self) -> Payload; #[doc(hidden)] /// Get a header @@ -130,7 +130,10 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn body(&self) -> MessageBody { + fn body(&mut self) -> MessageBody + where + Self::Stream: Stream + Sized, + { MessageBody::new(self) } @@ -164,7 +167,10 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn urlencoded(&self) -> UrlEncoded { + fn urlencoded(&mut self) -> UrlEncoded + where + Self::Stream: Stream, + { UrlEncoded::new(self) } @@ -200,12 +206,18 @@ pub trait HttpMessage: Sized { /// } /// # fn main() {} /// ``` - fn json(&self) -> JsonBody { + fn json(&mut self) -> JsonBody + where + Self::Stream: Stream + 'static, + { JsonBody::new(self) } /// Return stream of lines. - fn readlines(&self) -> Readlines { + fn readlines(&mut self) -> Readlines + where + Self::Stream: Stream + 'static, + { Readlines::new(self) } } @@ -220,16 +232,20 @@ pub struct Readlines { err: Option, } -impl Readlines { +impl Readlines +where + T: HttpMessage, + T::Stream: Stream, +{ /// Create a new stream to read request line by line. - fn new(req: &T) -> Self { + fn new(req: &mut T) -> Self { let encoding = match req.encoding() { Ok(enc) => enc, Err(err) => return Self::err(err.into()), }; Readlines { - stream: req.payload(), + stream: req.take_payload(), buff: BytesMut::with_capacity(262_144), limit: 262_144, checked_buff: true, @@ -256,7 +272,11 @@ impl Readlines { } } -impl Stream for Readlines { +impl Stream for Readlines +where + T: HttpMessage, + T::Stream: Stream, +{ type Item = String; type Error = ReadlinesError; @@ -362,9 +382,13 @@ pub struct MessageBody { fut: Option>>, } -impl MessageBody { +impl MessageBody +where + T: HttpMessage, + T::Stream: Stream, +{ /// Create `MessageBody` for request. - pub fn new(req: &T) -> MessageBody { + pub fn new(req: &mut T) -> MessageBody { let mut len = None; if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -379,9 +403,9 @@ impl MessageBody { } MessageBody { + stream: req.take_payload(), limit: 262_144, length: len, - stream: req.payload(), fut: None, err: None, } @@ -406,7 +430,8 @@ impl MessageBody { impl Future for MessageBody where - T: HttpMessage + 'static, + T: HttpMessage, + T::Stream: Stream + 'static, { type Item = Bytes; type Error = PayloadError; @@ -429,7 +454,7 @@ where // future let limit = self.limit; self.fut = Some(Box::new( - mem::replace(&mut self.stream, Payload::None) + std::mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { @@ -455,9 +480,13 @@ pub struct UrlEncoded { fut: Option>>, } -impl UrlEncoded { +impl UrlEncoded +where + T: HttpMessage, + T::Stream: Stream, +{ /// Create a new future to URL encode a request - pub fn new(req: &T) -> UrlEncoded { + pub fn new(req: &mut T) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -482,7 +511,7 @@ impl UrlEncoded { UrlEncoded { encoding, - stream: req.payload(), + stream: req.take_payload(), limit: 262_144, length: len, fut: None, @@ -510,7 +539,8 @@ impl UrlEncoded { impl Future for UrlEncoded where - T: HttpMessage + 'static, + T: HttpMessage, + T::Stream: Stream + 'static, U: DeserializeOwned + 'static, { type Item = U; @@ -535,7 +565,7 @@ where // future let encoding = self.encoding; - let fut = mem::replace(&mut self.stream, Payload::None) + let fut = std::mem::replace(&mut self.stream, Payload::None) .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { @@ -691,7 +721,7 @@ mod tests { #[test] fn test_urlencoded_error() { - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -702,7 +732,7 @@ mod tests { UrlencodedError::UnknownLength ); - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -713,7 +743,7 @@ mod tests { UrlencodedError::Overflow ); - let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") + let mut req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") .header(header::CONTENT_LENGTH, "10") .finish(); assert_eq!( @@ -724,7 +754,7 @@ mod tests { #[test] fn test_urlencoded() { - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) @@ -740,7 +770,7 @@ mod tests { }) ); - let req = TestRequest::with_header( + let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) @@ -759,19 +789,20 @@ mod tests { #[test] fn test_message_body() { - let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().poll().err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), } - let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = + TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), } - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static(b"test")) .finish(); match req.body().poll().ok().unwrap() { @@ -779,7 +810,7 @@ mod tests { _ => unreachable!("error"), } - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).poll().err().unwrap() { @@ -790,14 +821,14 @@ mod tests { #[test] fn test_readlines() { - let req = TestRequest::default() + let mut req = TestRequest::default() .set_payload(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .finish(); - let mut r = Readlines::new(&req); + let mut r = Readlines::new(&mut req); match r.poll().ok().unwrap() { Async::Ready(Some(s)) => assert_eq!( s, diff --git a/src/json.rs b/src/json.rs index 573fde41..026ecb87 100644 --- a/src/json.rs +++ b/src/json.rs @@ -2,11 +2,12 @@ use bytes::BytesMut; use futures::{Future, Poll, Stream}; use http::header::CONTENT_LENGTH; +use bytes::Bytes; use mime; use serde::de::DeserializeOwned; use serde_json; -use crate::error::JsonPayloadError; +use crate::error::{JsonPayloadError, PayloadError}; use crate::httpmessage::HttpMessage; use crate::payload::Payload; @@ -41,7 +42,7 @@ use crate::payload::Payload; /// } /// # fn main() {} /// ``` -pub struct JsonBody { +pub struct JsonBody { limit: usize, length: Option, stream: Payload, @@ -49,9 +50,14 @@ pub struct JsonBody { fut: Option>>, } -impl JsonBody { +impl JsonBody +where + T: HttpMessage, + T::Stream: Stream + 'static, + U: DeserializeOwned + 'static, +{ /// Create `JsonBody` for request. - pub fn new(req: &T) -> Self { + pub fn new(req: &mut T) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) @@ -80,7 +86,7 @@ impl JsonBody { JsonBody { limit: 262_144, length: len, - stream: req.payload(), + stream: req.take_payload(), fut: None, err: None, } @@ -93,7 +99,12 @@ impl JsonBody { } } -impl Future for JsonBody { +impl Future for JsonBody +where + T: HttpMessage, + T::Stream: Stream + 'static, + U: DeserializeOwned + 'static, +{ type Item = U; type Error = JsonPayloadError; @@ -162,11 +173,11 @@ mod tests { #[test] fn test_json_body() { - let req = TestRequest::default().finish(); + let mut req = TestRequest::default().finish(); let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), @@ -175,7 +186,7 @@ mod tests { let mut json = req.json::(); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), @@ -188,7 +199,7 @@ mod tests { let mut json = req.json::().limit(100); assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - let req = TestRequest::default() + let mut req = TestRequest::default() .header( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), diff --git a/src/message.rs b/src/message.rs index 7fb45bcc..812f099e 100644 --- a/src/message.rs +++ b/src/message.rs @@ -2,9 +2,8 @@ use std::cell::{Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::rc::Rc; -use http::{HeaderMap, Method, StatusCode, Uri, Version}; - use crate::extensions::Extensions; +use crate::http::{HeaderMap, Method, StatusCode, Uri, Version}; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -21,6 +20,12 @@ pub enum ConnectionType { pub trait Head: Default + 'static { fn clear(&mut self); + /// Read the message headers. + fn headers(&self) -> &HeaderMap; + + /// Mutable reference to the message headers. + fn headers_mut(&mut self) -> &mut HeaderMap; + /// Connection type fn connection_type(&self) -> ConnectionType; @@ -68,6 +73,14 @@ impl Head for RequestHead { self.extensions.borrow_mut().clear(); } + fn headers(&self) -> &HeaderMap { + &self.headers + } + + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + fn set_connection_type(&mut self, ctype: ConnectionType) { self.ctype = Some(ctype) } @@ -129,6 +142,14 @@ impl Head for ResponseHead { self.headers.clear(); } + fn headers(&self) -> &HeaderMap { + &self.headers + } + + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + fn set_connection_type(&mut self, ctype: ConnectionType) { self.ctype = Some(ctype) } diff --git a/src/payload.rs b/src/payload.rs index 21e41531..bc40fe80 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -15,21 +15,21 @@ pub enum Payload { Stream(S), } -impl From for Payload { - fn from(v: RecvStream) -> Self { - Payload::H2(crate::h2::Payload::new(v)) - } -} - impl From for Payload { - fn from(pl: crate::h1::Payload) -> Self { - Payload::H1(pl) + fn from(v: crate::h1::Payload) -> Self { + Payload::H1(v) } } impl From for Payload { - fn from(pl: crate::h2::Payload) -> Self { - Payload::H2(pl) + fn from(v: crate::h2::Payload) -> Self { + Payload::H2(v) + } +} + +impl From for Payload { + fn from(v: RecvStream) -> Self { + Payload::H2(crate::h2::Payload::new(v)) } } diff --git a/src/request.rs b/src/request.rs index 388fe754..e1b893f9 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,11 +1,8 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::{fmt, mem}; +use std::cell::{Ref, RefMut}; +use std::fmt; -use bytes::Bytes; -use futures::Stream; use http::{header, HeaderMap, Method, Uri, Version}; -use crate::error::PayloadError; use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Message, RequestHead}; @@ -13,31 +10,27 @@ use crate::payload::{Payload, PayloadStream}; /// Request pub struct Request

    { - pub(crate) payload: RefCell>, - pub(crate) inner: Message, + pub(crate) payload: Payload

    , + pub(crate) head: Message, } -impl

    HttpMessage for Request

    -where - P: Stream, -{ +impl

    HttpMessage for Request

    { type Stream = P; fn headers(&self) -> &HeaderMap { &self.head().headers } - #[inline] - fn payload(&self) -> Payload { - mem::replace(&mut *self.payload.borrow_mut(), Payload::None) + fn take_payload(&mut self) -> Payload

    { + std::mem::replace(&mut self.payload, Payload::None) } } -impl

    From> for Request

    { - fn from(msg: Message) -> Self { +impl From> for Request { + fn from(head: Message) -> Self { Request { - payload: RefCell::new(Payload::None), - inner: msg, + head, + payload: Payload::None, } } } @@ -46,8 +39,8 @@ impl Request { /// Create new Request instance pub fn new() -> Request { Request { - payload: RefCell::new(Payload::None), - inner: Message::new(), + head: Message::new(), + payload: Payload::None, } } } @@ -56,38 +49,44 @@ impl

    Request

    { /// Create new Request instance pub fn with_payload(payload: Payload

    ) -> Request

    { Request { - payload: RefCell::new(payload), - inner: Message::new(), + payload, + head: Message::new(), } } /// Create new Request instance - pub fn set_payload(self, payload: I) -> Request - where - I: Into>, - { - Request { - payload: RefCell::new(payload.into()), - inner: self.inner, - } + pub fn replace_payload(self, payload: Payload) -> (Request, Payload

    ) { + let pl = self.payload; + ( + Request { + payload, + head: self.head, + }, + pl, + ) + } + + /// Get request's payload + pub fn take_payload(&mut self) -> Payload

    { + std::mem::replace(&mut self.payload, Payload::None) } /// Split request into request head and payload pub fn into_parts(self) -> (Message, Payload

    ) { - (self.inner, self.payload.into_inner()) + (self.head, self.payload) } #[inline] /// Http message part of the request pub fn head(&self) -> &RequestHead { - &*self.inner + &*self.head } #[inline] #[doc(hidden)] /// Mutable reference to a http message part of the request pub fn head_mut(&mut self) -> &mut RequestHead { - &mut *self.inner + &mut *self.head } /// Request's uri. @@ -135,13 +134,13 @@ impl

    Request

    { /// Request extensions #[inline] pub fn extensions(&self) -> Ref { - self.inner.extensions() + self.head.extensions() } /// Mutable reference to a the request's extensions #[inline] pub fn extensions_mut(&self) -> RefMut { - self.inner.extensions_mut() + self.head.extensions_mut() } /// Check if request requires connection upgrade @@ -155,7 +154,7 @@ impl

    Request

    { } } -impl fmt::Debug for Request { +impl

    fmt::Debug for Request

    { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, diff --git a/tests/test_client.rs b/tests/test_client.rs index 606bac22..6f502b0a 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -47,7 +47,7 @@ fn test_h1_v2() { assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -55,7 +55,7 @@ fn test_h1_v2() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = srv.post().finish().unwrap(); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(request.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response diff --git a/tests/test_server.rs b/tests/test_server.rs index 9fa27e71..53db3840 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -89,7 +89,7 @@ fn test_h2_body() -> std::io::Result<()> { .map_err(|e| println!("Openssl error: {}", e)) .and_then( h2::H2Service::build() - .finish(|req: Request<_>| { + .finish(|mut req: Request<_>| { req.body() .limit(1024 * 1024) .and_then(|body| Ok(Response::Ok().body(body))) @@ -101,7 +101,7 @@ fn test_h2_body() -> std::io::Result<()> { let req = client::ClientRequest::get(srv.surl("/")) .body(data.clone()) .unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); @@ -350,7 +350,7 @@ fn test_headers() { let req = srv.get().finish().unwrap(); - let response = srv.block_on(req.send(&mut connector)).unwrap(); + let mut response = srv.block_on(req.send(&mut connector)).unwrap(); assert!(response.status().is_success()); // read response @@ -387,7 +387,7 @@ fn test_body() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -402,7 +402,7 @@ fn test_head_empty() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -428,7 +428,7 @@ fn test_head_binary() { }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); { @@ -477,7 +477,7 @@ fn test_body_length() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -496,7 +496,7 @@ fn test_body_chunked_explicit() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -517,7 +517,7 @@ fn test_body_chunked_implicit() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); // read response @@ -540,7 +540,7 @@ fn test_response_http_error_handling() { }); let req = srv.get().finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let mut response = srv.send_request(req).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response From e6e83ea57e82b59ad504420b23e2d00950756ce8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 17:01:35 -0800 Subject: [PATCH 0918/1635] add Response::map_body --- src/lib.rs | 3 +-- src/response.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4e6e3795..8750b24c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,12 +89,11 @@ pub mod h2; pub mod test; pub mod ws; -pub use self::body::{Body, MessageBody}; pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; -pub use self::message::{Message, RequestHead, ResponseHead}; +pub use self::message::{Head, Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::Response; diff --git a/src/response.rs b/src/response.rs index d84100fa..0295758b 100644 --- a/src/response.rs +++ b/src/response.rs @@ -242,6 +242,20 @@ impl Response { self.body, ) } + + /// Set a body and return previous body value + pub fn map_body(mut self, f: F) -> Response + where + F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + { + let body = f(&mut self.head, self.body); + + Response { + head: self.head, + body: body, + error: self.error, + } + } } impl fmt::Debug for Response { From 037c3da1729d7fd61e5edb2cf89a29463a5263c2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 18:40:40 -0800 Subject: [PATCH 0919/1635] enable ssl for connector --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5bcaea7c..e4dd6c58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl"] +ssl = ["openssl", "actix-connector/ssl"] [dependencies] actix-service = "0.2.1" From d180b2a1e357d353187a9f67d5b382f63ba77bd0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 18:46:30 -0800 Subject: [PATCH 0920/1635] update tests --- test-server/src/lib.rs | 2 +- tests/test_client.rs | 6 +++--- tests/test_server.rs | 47 +++++++++++++++++++++--------------------- tests/test_ws.rs | 2 +- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 3d6d917e..a13e86cf 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -53,7 +53,7 @@ pub struct TestServerRuntime { impl TestServer { /// Start new test server with application factory - pub fn with_factory( + pub fn new( factory: F, ) -> TestServerRuntime< impl Service diff --git a/tests/test_client.rs b/tests/test_client.rs index 6f502b0a..f44c45cb 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -31,7 +31,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_v2() { env_logger::init(); - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) @@ -65,7 +65,7 @@ fn test_h1_v2() { #[test] fn test_connection_close() { - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { h1::H1Service::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) @@ -79,7 +79,7 @@ fn test_connection_close() { #[test] fn test_with_query_parameter() { - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { h1::H1Service::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { diff --git a/tests/test_server.rs b/tests/test_server.rs index 53db3840..dc1ebcfb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -8,14 +8,15 @@ use bytes::Bytes; use futures::future::{self, ok, Future}; use futures::stream::once; +use actix_http::body::Body; use actix_http::{ - body, client, h1, h2, http, Body, Error, HttpMessage as HttpMessage2, KeepAlive, - Request, Response, + body, client, h1, h2, http, Error, HttpMessage as HttpMessage2, KeepAlive, Request, + Response, }; #[test] fn test_h1() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) @@ -57,7 +58,7 @@ fn ssl_acceptor() -> std::io::Result> { #[test] fn test_h2() -> std::io::Result<()> { let openssl = ssl_acceptor()?; - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { openssl .clone() .map_err(|e| println!("Openssl error: {}", e)) @@ -83,7 +84,7 @@ fn test_h2_body() -> std::io::Result<()> { let data = "HELLOWORLD".to_owned().repeat(64 * 1024); let openssl = ssl_acceptor()?; - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { openssl .clone() .map_err(|e| println!("Openssl error: {}", e)) @@ -111,7 +112,7 @@ fn test_h2_body() -> std::io::Result<()> { #[test] fn test_slow_request() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .client_timeout(100) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -127,7 +128,7 @@ fn test_slow_request() { #[test] fn test_malformed_request() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())).map(|_| ()) }); @@ -140,7 +141,7 @@ fn test_malformed_request() { #[test] fn test_keepalive() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -160,7 +161,7 @@ fn test_keepalive() { #[test] fn test_keepalive_timeout() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .keep_alive(1) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -181,7 +182,7 @@ fn test_keepalive_timeout() { #[test] fn test_keepalive_close() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -201,7 +202,7 @@ fn test_keepalive_close() { #[test] fn test_keepalive_http10_default_close() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -220,7 +221,7 @@ fn test_keepalive_http10_default_close() { #[test] fn test_keepalive_http10() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) .map(|_| ()) @@ -246,7 +247,7 @@ fn test_keepalive_http10() { #[test] fn test_keepalive_disabled() { - let srv = TestServer::with_factory(|| { + let srv = TestServer::new(|| { h1::H1Service::build() .keep_alive(KeepAlive::Disabled) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -271,7 +272,7 @@ fn test_content_length() { StatusCode, }; - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ @@ -320,7 +321,7 @@ fn test_headers() { let data = STR.repeat(10); let data2 = data.clone(); - let mut srv = TestServer::with_factory(move || { + let mut srv = TestServer::new(move || { let data = data.clone(); h1::H1Service::new(move |_| { let mut builder = Response::Ok(); @@ -382,7 +383,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -397,7 +398,7 @@ fn test_body() { #[test] fn test_head_empty() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -420,7 +421,7 @@ fn test_head_empty() { #[test] fn test_head_binary() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) }) @@ -446,7 +447,7 @@ fn test_head_binary() { #[test] fn test_head_binary2() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) }); @@ -465,7 +466,7 @@ fn test_head_binary2() { #[test] fn test_body_length() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( @@ -487,7 +488,7 @@ fn test_body_length() { #[test] fn test_body_chunked_explicit() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) @@ -508,7 +509,7 @@ fn test_body_chunked_explicit() { #[test] fn test_body_chunked_implicit() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) @@ -527,7 +528,7 @@ fn test_body_chunked_implicit() { #[test] fn test_response_http_error_handling() { - let mut srv = TestServer::with_factory(|| { + let mut srv = TestServer::new(|| { h1::H1Service::new(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( diff --git a/tests/test_ws.rs b/tests/test_ws.rs index e5a54c7c..4111ca3d 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -34,7 +34,7 @@ fn ws_service(req: ws::Frame) -> impl Future)| { From 842da939dc490a255a9f1a8b4d3d28b3dcc6647a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 20:24:50 -0800 Subject: [PATCH 0921/1635] fix chunked transfer encoding handling --- src/body.rs | 3 +-- src/client/h2proto.rs | 2 +- src/h1/encoder.rs | 27 ++++++++++++++++++++++----- src/h2/dispatcher.rs | 2 +- src/message.rs | 6 ++++++ src/response.rs | 9 +++++++++ 6 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/body.rs b/src/body.rs index d3e63f9c..1f218c4b 100644 --- a/src/body.rs +++ b/src/body.rs @@ -13,7 +13,6 @@ pub enum BodyLength { Empty, Sized(usize), Sized64(u64), - Chunked, Stream, } @@ -331,7 +330,7 @@ where E: Into, { fn length(&self) -> BodyLength { - BodyLength::Chunked + BodyLength::Stream } fn poll_next(&mut self) -> Poll, Error> { diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index 8804d13f..617c21b6 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -47,7 +47,7 @@ where // Content length let _ = match length { - BodyLength::Chunked | BodyLength::None => None, + BodyLength::None => None, BodyLength::Stream => { skip_len = false; None diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 32c8f9c4..7627f697 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -45,6 +45,8 @@ pub(crate) trait MessageType: Sized { fn headers(&self) -> &HeaderMap; + fn chunked(&self) -> bool; + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>; fn encode_headers( @@ -71,8 +73,10 @@ pub(crate) trait MessageType: Sized { } } match length { - BodyLength::Chunked => { - dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + BodyLength::Stream => { + if self.chunked() { + dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } } BodyLength::Empty => { dst.extend_from_slice(b"\r\ncontent-length: 0\r\n"); @@ -83,7 +87,7 @@ pub(crate) trait MessageType: Sized { write!(dst.writer(), "{}", len)?; dst.extend_from_slice(b"\r\n"); } - BodyLength::None | BodyLength::Stream => dst.extend_from_slice(b"\r\n"), + BodyLength::None => dst.extend_from_slice(b"\r\n"), } // Connection @@ -159,6 +163,10 @@ impl MessageType for Response<()> { Some(self.head().status) } + fn chunked(&self) -> bool { + !self.head().no_chunking + } + fn connection_type(&self) -> Option { self.head().ctype } @@ -188,6 +196,10 @@ impl MessageType for RequestHead { self.ctype } + fn chunked(&self) -> bool { + !self.no_chunking + } + fn headers(&self) -> &HeaderMap { &self.headers } @@ -236,8 +248,13 @@ impl MessageEncoder { BodyLength::Empty => TransferEncoding::empty(), BodyLength::Sized(len) => TransferEncoding::length(len as u64), BodyLength::Sized64(len) => TransferEncoding::length(len), - BodyLength::Chunked => TransferEncoding::chunked(), - BodyLength::Stream => TransferEncoding::eof(), + BodyLength::Stream => { + if message.chunked() { + TransferEncoding::chunked() + } else { + TransferEncoding::eof() + } + } BodyLength::None => TransferEncoding::empty(), }; } else { diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 5a8c4b85..ea8756d2 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -181,7 +181,7 @@ where _ => (), } let _ = match length { - BodyLength::Chunked | BodyLength::None | BodyLength::Stream => None, + BodyLength::None | BodyLength::Stream => None, BodyLength::Empty => res .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), diff --git a/src/message.rs b/src/message.rs index 812f099e..3a1ac130 100644 --- a/src/message.rs +++ b/src/message.rs @@ -36,6 +36,7 @@ pub trait Head: Default + 'static { self.connection_type() == ConnectionType::Upgrade } + /// Check if keep-alive is enabled fn keep_alive(&self) -> bool { self.connection_type() == ConnectionType::KeepAlive } @@ -50,6 +51,7 @@ pub struct RequestHead { pub version: Version, pub headers: HeaderMap, pub ctype: Option, + pub no_chunking: bool, pub extensions: RefCell, } @@ -61,6 +63,7 @@ impl Default for RequestHead { version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), ctype: None, + no_chunking: false, extensions: RefCell::new(Extensions::new()), } } @@ -120,6 +123,7 @@ pub struct ResponseHead { pub status: StatusCode, pub headers: HeaderMap, pub reason: Option<&'static str>, + pub no_chunking: bool, pub(crate) ctype: Option, } @@ -130,6 +134,7 @@ impl Default for ResponseHead { status: StatusCode::OK, headers: HeaderMap::with_capacity(16), reason: None, + no_chunking: false, ctype: None, } } @@ -139,6 +144,7 @@ impl Head for ResponseHead { fn clear(&mut self) { self.ctype = None; self.reason = None; + self.no_chunking = false; self.headers.clear(); } diff --git a/src/response.rs b/src/response.rs index 0295758b..2c627d53 100644 --- a/src/response.rs +++ b/src/response.rs @@ -459,6 +459,15 @@ impl ResponseBuilder { self } + /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. + #[inline] + pub fn no_chunking(&mut self) -> &mut Self { + if let Some(parts) = parts(&mut self.head, &self.err) { + parts.no_chunking = true; + } + self + } + /// Set response content type #[inline] pub fn content_type(&mut self, value: V) -> &mut Self From c8713d045cf5969df0daa3c81b14b272a4492fc6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 21:41:38 -0800 Subject: [PATCH 0922/1635] poll payload again if framed object get flushed during same iteration --- src/h1/dispatcher.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 22d7ea86..9ae8cd2a 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -167,7 +167,7 @@ where } /// Flush stream - fn poll_flush(&mut self) -> Poll<(), DispatchError> { + fn poll_flush(&mut self) -> Poll> { if !self.framed.is_write_buf_empty() { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), @@ -180,11 +180,11 @@ where if self.payload.is_some() && self.state.is_empty() { return Err(DispatchError::PayloadIsNotConsumed); } - Ok(Async::Ready(())) + Ok(Async::Ready(true)) } } } else { - Ok(Async::Ready(())) + Ok(Async::Ready(false)) } } @@ -482,8 +482,12 @@ where } else { inner.poll_keepalive()?; inner.poll_request()?; - inner.poll_response()?; - inner.poll_flush()?; + loop { + inner.poll_response()?; + if let Async::Ready(false) = inner.poll_flush()? { + break; + } + } if inner.flags.contains(Flags::DISCONNECTED) { return Ok(Async::Ready(H1ServiceResult::Disconnected)); From 781f1a3fef466a820bdcf4f841616f0a8c9fa55f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 22:20:00 -0800 Subject: [PATCH 0923/1635] do not skip content length is no chunking is selected --- src/h1/encoder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 7627f697..d4f54d24 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -57,6 +57,7 @@ pub(crate) trait MessageType: Sized { ctype: ConnectionType, config: &ServiceConfig, ) -> io::Result<()> { + let chunked = self.chunked(); let mut skip_len = length != BodyLength::Stream; // Content length @@ -74,8 +75,10 @@ pub(crate) trait MessageType: Sized { } match length { BodyLength::Stream => { - if self.chunked() { + if chunked { dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + } else { + skip_len = false; } } BodyLength::Empty => { From 7f749ac9cccf01ad63f82648dcbfef5d15d715ff Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Feb 2019 22:34:22 -0800 Subject: [PATCH 0924/1635] add missing end of line --- src/h1/encoder.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index d4f54d24..9fe5ba69 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -79,6 +79,7 @@ pub(crate) trait MessageType: Sized { dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } else { skip_len = false; + dst.extend_from_slice(b"\r\n"); } } BodyLength::Empty => { From 60a8da5c05e71a20e214fc6bc392197a2ce70eb0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Feb 2019 21:02:23 -0800 Subject: [PATCH 0925/1635] remove Response constraint --- src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response.rs b/src/response.rs index 2c627d53..8d21d6b3 100644 --- a/src/response.rs +++ b/src/response.rs @@ -16,7 +16,7 @@ use crate::header::{Header, IntoHeaderValue}; use crate::message::{ConnectionType, Head, Message, ResponseHead}; /// An HTTP Response -pub struct Response { +pub struct Response { head: Message, body: ResponseBody, error: Option, From 2f89b12f4faa178e71b2465106ef8454d5c44bcf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Feb 2019 21:05:37 -0800 Subject: [PATCH 0926/1635] remove more response containts --- src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response.rs b/src/response.rs index 8d21d6b3..aab88106 100644 --- a/src/response.rs +++ b/src/response.rs @@ -92,7 +92,7 @@ impl Response { } } -impl Response { +impl Response { #[inline] /// Http message part of the response pub fn head(&self) -> &ResponseHead { From b80ee71785c467958c41d5bd0be55e9b13034491 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Feb 2019 14:21:35 -0800 Subject: [PATCH 0927/1635] use new new service api --- Cargo.toml | 20 +++++++++++++++----- src/h1/service.rs | 6 +++--- src/h2/service.rs | 4 ++-- src/service/senderror.rs | 4 ++-- src/ws/service.rs | 2 +- test-server/Cargo.toml | 10 +++++++--- tests/test_server.rs | 2 -- 7 files changed, 30 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4dd6c58..edf572af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,18 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.2.1" +#actix-service = "0.2.1" actix-codec = "0.1.0" -actix-connector = "0.2.0" -actix-utils = "0.2.2" +#actix-connector = "0.2.0" +#actix-utils = "0.2.2" + +actix-service = { git = "https://github.com/actix/actix-net" } +actix-connector = { git = "https://github.com/actix/actix-net" } +actix-utils = { git = "https://github.com/actix/actix-net" } + +#actix-service = { path = "../actix-net/actix-service" } +#actix-connector = { path = "../actix-net/actix-connector" } +#actix-utils = { path = "../actix-net/actix-utils" } base64 = "0.10" backtrace = "0.3" @@ -78,8 +86,10 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" -actix-server = { version="0.2", features=["ssl"] } -actix-connector = { version="0.2.0", features=["ssl"] } +actix-server = { git = "https://github.com/actix/actix-net", features=["ssl"] } +#actix-server = { path = "../actix-net/actix-server", features=["ssl"] } +#actix-connector = { path = "../actix-net/actix-connector", features=["ssl"] } +actix-connector = { git = "https://github.com/actix/actix-net", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/src/h1/service.rs b/src/h1/service.rs index cb8dae54..4beb4c9e 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -67,9 +67,9 @@ where type Service = H1ServiceHandler; type Future = H1ServiceResponse; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { H1ServiceResponse { - fut: self.srv.new_service(), + fut: self.srv.new_service(&()), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -309,7 +309,7 @@ where type Service = OneRequestService; type Future = FutureResult; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { ok(OneRequestService { config: self.config.clone(), _t: PhantomData, diff --git a/src/h2/service.rs b/src/h2/service.rs index 16b7e495..fcfc0be2 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -70,9 +70,9 @@ where type Service = H2ServiceHandler; type Future = H2ServiceResponse; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { H2ServiceResponse { - fut: self.srv.new_service(), + fut: self.srv.new_service(&()), cfg: Some(self.cfg.clone()), _t: PhantomData, } diff --git a/src/service/senderror.rs b/src/service/senderror.rs index b469a61e..44d36259 100644 --- a/src/service/senderror.rs +++ b/src/service/senderror.rs @@ -34,7 +34,7 @@ where type Service = SendError; type Future = FutureResult; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { ok(SendError(PhantomData)) } } @@ -142,7 +142,7 @@ where type Service = SendResponse; type Future = FutureResult; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { ok(SendResponse(PhantomData)) } } diff --git a/src/ws/service.rs b/src/ws/service.rs index 137d41d4..f3b06605 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -28,7 +28,7 @@ impl NewService for VerifyWebSockets { type Service = VerifyWebSockets; type Future = FutureResult; - fn new_service(&self) -> Self::Future { + fn new_service(&self, _: &()) -> Self::Future { ok(VerifyWebSockets { _t: PhantomData }) } } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 9c71a25c..f5e8afd2 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -33,12 +33,16 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1" -actix-service = "0.2.0" actix-rt = "0.1.0" -actix-server = "0.2.0" -actix-utils = "0.2.0" actix-http = { path=".." } +#actix-service = "0.2.0" +#actix-server = "0.2.0" +#actix-utils = "0.2.0" +actix-service = { git = "https://github.com/actix/actix-net" } +actix-server = { git = "https://github.com/actix/actix-net" } +actix-utils = { git = "https://github.com/actix/actix-net" } + base64 = "0.10" bytes = "0.4" cookie = { version="0.11", features=["percent-encode"] } diff --git a/tests/test_server.rs b/tests/test_server.rs index dc1ebcfb..fd848b82 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -514,7 +514,6 @@ fn test_body_chunked_implicit() { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) }) - .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -537,7 +536,6 @@ fn test_response_http_error_handling() { .body(STR), ) }) - .map(|_| ()) }); let req = srv.get().finish().unwrap(); From 69d710dbce90e9d00539334c063e6d4f70ba45e3 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 27 Feb 2019 12:52:42 +0000 Subject: [PATCH 0928/1635] Add insert and remove() to response builder (#707) --- src/httpresponse.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 168e9bf6..226c847f 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -366,7 +366,7 @@ impl HttpResponseBuilder { self } - /// Set a header. + /// Append a header. /// /// ```rust /// # extern crate actix_web; @@ -394,7 +394,7 @@ impl HttpResponseBuilder { self } - /// Set a header. + /// Append a header. /// /// ```rust /// # extern crate actix_web; @@ -426,6 +426,65 @@ impl HttpResponseBuilder { } self } + /// Set or replace a header with a single value. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, HttpRequest, HttpResponse}; + /// + /// fn index(req: HttpRequest) -> HttpResponse { + /// HttpResponse::Ok() + /// .insert("X-TEST", "value") + /// .insert(http::header::CONTENT_TYPE, "application/json") + /// .finish() + /// } + /// fn main() {} + /// ``` + pub fn insert(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Some(parts) = parts(&mut self.response, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + parts.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } + + /// Remove all instances of a header already set on this `HttpResponseBuilder`. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{http, HttpRequest, HttpResponse}; + /// + /// fn index(req: HttpRequest) -> HttpResponse { + /// HttpResponse::Ok() + /// .header(http::header::CONTENT_TYPE, "nevermind") // won't be used + /// .remove(http::header::CONTENT_TYPE) + /// .finish() + /// } + /// ``` + pub fn remove(&mut self, key: K) -> &mut Self + where HeaderName: HttpTryFrom + { + if let Some(parts) = parts(&mut self.response, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => { + parts.headers.remove(key); + }, + Err(e) => self.err = Some(e.into()), + }; + } + self + } /// Set the custom reason for the response. #[inline] @@ -1128,6 +1187,40 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } + #[test] + fn test_insert() { + let resp = HttpResponse::Ok() + .insert("deleteme", "old value") + .insert("deleteme", "new value") + .finish(); + assert_eq!("new value", resp.headers().get("deleteme").expect("new value")); + } + + #[test] + fn test_remove() { + let resp = HttpResponse::Ok() + .header("deleteme", "value") + .remove("deleteme") + .finish(); + assert!(resp.headers().get("deleteme").is_none()) + } + + #[test] + fn test_remove_replace() { + let resp = HttpResponse::Ok() + .header("some-header", "old_value1") + .header("some-header", "old_value2") + .remove("some-header") + .header("some-header", "new_value1") + .header("some-header", "new_value2") + .remove("unrelated-header") + .finish(); + let mut v = resp.headers().get_all("some-header").into_iter(); + assert_eq!("new_value1", v.next().unwrap()); + assert_eq!("new_value2", v.next().unwrap()); + assert_eq!(None, v.next()); + } + #[test] fn test_upgrade() { let resp = HttpResponse::build(StatusCode::OK).upgrade().finish(); From 80d4cbe301bb72302f409a31ad4144f2805adceb Mon Sep 17 00:00:00 2001 From: Douman Date: Wed, 27 Feb 2019 21:37:20 +0300 Subject: [PATCH 0929/1635] Add change notes for new HttpResponseBuilder --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 1a18e092..d1d838f4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Add `from_file` and `from_file_with_config` to `NamedFile` to allow sending files without a known path. #670 +* Add `insert` and `remove` methods to `HttpResponseBuilder` + ### Fixed * Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680 From 6d11ee683f21c5f2902eb8353180aacadcbebd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Ben=C3=ADcio?= Date: Fri, 1 Mar 2019 05:34:58 -0300 Subject: [PATCH 0930/1635] fixing little typo in docs (#711) --- src/extractor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extractor.rs b/src/extractor.rs index 3c64de9e..33705723 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -193,7 +193,7 @@ impl fmt::Display for Path { } #[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from from the request's query. +/// Extract typed information from the request's query. /// /// ## Example /// From 38c86d4683ba661e29be84aeb0a2e845d326e697 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Mar 2019 20:33:31 -0800 Subject: [PATCH 0931/1635] update tarpaulin travis config --- .travis.yml | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9c9db14..b9727258 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: rust sudo: required dist: trusty +addons: + apt: + packages: + - libssl-dev -cache: - cargo: true - apt: true +cache: cargo matrix: include: @@ -14,32 +16,22 @@ matrix: allow_failures: - rust: nightly -env: - global: - - RUSTFLAGS="-C link-dead-code" - - OPENSSL_VERSION=openssl-1.0.2 - -before_install: - - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl - - sudo apt-get update -qq - - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev - -before_script: - - export PATH=$PATH:~/.cargo/bin +before_cache: | + if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f + fi script: - - | - if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then - cargo clean - cargo test --features="ssl" - fi - - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - RUST_BACKTRACE=1 cargo tarpaulin --features="ssl" --out Xml +- cargo clean +- cargo build --features="ssl" +- cargo test --features="ssl" + +after_success: | + if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then + cargo tarpaulin --features="ssl" --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" - fi + fi # Upload docs #after_success: From 650474ca39b0613a14bd2b4d67084b9f6c92a31c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Mar 2019 21:02:56 -0800 Subject: [PATCH 0932/1635] choose openssl version for travis --- .travis.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index b9727258..4f35c656 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ language: rust sudo: required dist: trusty -addons: - apt: - packages: - - libssl-dev -cache: cargo +cache: + cargo: true + apt: true matrix: include: @@ -16,6 +14,16 @@ matrix: allow_failures: - rust: nightly +env: + global: + - RUSTFLAGS="-C link-dead-code" + - OPENSSL_VERSION=openssl-1.0.2 + +before_install: + - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl + - sudo apt-get update -qq + - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev + before_cache: | if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f From 5fff07402efe32c180ffba8415e9c39a00b40d25 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Mar 2019 21:36:37 -0800 Subject: [PATCH 0933/1635] downgrade tarpaulin --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4f35c656..283437ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: before_cache: | if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f --version 0.6.7 fi script: From 2d7293aaf8fa7e81cec3efc2a50ffbd79f9b1de9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Mar 2019 22:51:32 -0800 Subject: [PATCH 0934/1635] copy actix-web2 --- CHANGES.md | 832 +------- Cargo.toml | 122 +- build.rs | 16 - examples/basic.rs | 54 + rustfmt.toml | 3 - src/app.rs | 648 ++++++ src/application.rs | 416 ++-- src/blocking.rs | 74 + src/body.rs | 391 ---- src/client/connector.rs | 1340 ------------ src/client/mod.rs | 120 -- src/client/parser.rs | 238 --- src/client/pipeline.rs | 553 ----- src/client/request.rs | 783 ------- src/client/response.rs | 124 -- src/client/writer.rs | 412 ---- src/context.rs | 294 --- src/de.rs | 455 ----- src/error.rs | 1426 ------------- src/extensions.rs | 114 -- src/extractor.rs | 1280 ++++++------ src/filter.rs | 327 +++ src/framed_app.rs | 240 +++ src/framed_handler.rs | 379 ++++ src/framed_route.rs | 448 ++++ src/fs.rs | 2354 +++++++++++----------- src/handler.rs | 872 ++++---- src/header/common/accept.rs | 159 -- src/header/common/accept_charset.rs | 69 - src/header/common/accept_encoding.rs | 72 - src/header/common/accept_language.rs | 75 - src/header/common/allow.rs | 85 - src/header/common/cache_control.rs | 254 --- src/header/common/content_disposition.rs | 914 --------- src/header/common/content_language.rs | 65 - src/header/common/content_range.rs | 210 -- src/header/common/content_type.rs | 122 -- src/header/common/date.rs | 42 - src/header/common/etag.rs | 96 - src/header/common/expires.rs | 39 - src/header/common/if_match.rs | 70 - src/header/common/if_modified_since.rs | 39 - src/header/common/if_none_match.rs | 92 - src/header/common/if_range.rs | 115 -- src/header/common/if_unmodified_since.rs | 40 - src/header/common/last_modified.rs | 38 - src/header/common/mod.rs | 350 ---- src/header/common/range.rs | 434 ---- src/header/mod.rs | 471 ----- src/header/shared/charset.rs | 152 -- src/header/shared/encoding.rs | 59 - src/header/shared/entity.rs | 266 --- src/header/shared/httpdate.rs | 119 -- src/header/shared/mod.rs | 14 - src/header/shared/quality_item.rs | 294 --- src/helpers.rs | 709 ++----- src/httpcodes.rs | 84 - src/httpmessage.rs | 855 -------- src/httprequest.rs | 545 ----- src/httpresponse.rs | 1458 -------------- src/info.rs | 78 +- src/json.rs | 519 ----- src/lib.rs | 309 +-- src/middleware/compress.rs | 443 ++++ src/middleware/cors.rs | 1227 ----------- src/middleware/csrf.rs | 275 --- src/middleware/defaultheaders.rs | 147 +- src/middleware/errhandlers.rs | 141 -- src/middleware/identity.rs | 399 ---- src/middleware/logger.rs | 384 ---- src/middleware/mod.rs | 113 +- src/middleware/session.rs | 618 ------ src/multipart.rs | 815 -------- src/param.rs | 334 --- src/payload.rs | 715 ------- src/pipeline.rs | 869 -------- src/pred.rs | 328 --- src/request.rs | 174 ++ src/resource.rs | 513 +++-- src/responder.rs | 259 +++ src/route.rs | 896 ++++---- src/router.rs | 1247 ------------ src/scope.rs | 1236 ------------ src/server/acceptor.rs | 383 ---- src/server/builder.rs | 134 -- src/server/channel.rs | 300 --- src/server/error.rs | 108 - src/server/h1.rs | 1353 ------------- src/server/h1decoder.rs | 541 ----- src/server/h1writer.rs | 364 ---- src/server/h2.rs | 472 ----- src/server/h2writer.rs | 268 --- src/server/handler.rs | 208 -- src/server/helpers.rs | 208 -- src/server/http.rs | 579 ------ src/server/incoming.rs | 69 - src/server/input.rs | 288 --- src/server/message.rs | 284 --- src/server/mod.rs | 370 ---- src/server/output.rs | 760 ------- src/server/service.rs | 272 --- src/server/settings.rs | 503 ----- src/server/ssl/mod.rs | 12 - src/server/ssl/nativetls.rs | 34 - src/server/ssl/openssl.rs | 87 - src/server/ssl/rustls.rs | 87 - src/service.rs | 155 ++ src/state.rs | 120 ++ src/test.rs | 668 +----- src/uri.rs | 177 -- src/with.rs | 383 ---- src/ws/client.rs | 602 ------ src/ws/context.rs | 341 ---- src/ws/frame.rs | 538 ----- src/ws/mask.rs | 152 -- src/ws/mod.rs | 477 ----- src/ws/proto.rs | 318 --- tests/identity.pfx | Bin 5549 -> 0 bytes tests/test space.binary | 1 - tests/test.binary | 1 - tests/test.png | Bin 168 -> 0 bytes tests/test_client.rs | 508 ----- tests/test_custom_pipeline.rs | 81 - tests/test_handlers.rs | 677 ------- tests/test_middleware.rs | 1055 ---------- tests/test_server.rs | 1942 +++++++----------- tests/test_ws.rs | 395 ---- 127 files changed, 7554 insertions(+), 43481 deletions(-) delete mode 100644 build.rs create mode 100644 examples/basic.rs create mode 100644 src/app.rs create mode 100644 src/blocking.rs delete mode 100644 src/body.rs delete mode 100644 src/client/connector.rs delete mode 100644 src/client/mod.rs delete mode 100644 src/client/parser.rs delete mode 100644 src/client/pipeline.rs delete mode 100644 src/client/request.rs delete mode 100644 src/client/response.rs delete mode 100644 src/client/writer.rs delete mode 100644 src/context.rs delete mode 100644 src/de.rs delete mode 100644 src/error.rs delete mode 100644 src/extensions.rs create mode 100644 src/filter.rs create mode 100644 src/framed_app.rs create mode 100644 src/framed_handler.rs create mode 100644 src/framed_route.rs delete mode 100644 src/header/common/accept.rs delete mode 100644 src/header/common/accept_charset.rs delete mode 100644 src/header/common/accept_encoding.rs delete mode 100644 src/header/common/accept_language.rs delete mode 100644 src/header/common/allow.rs delete mode 100644 src/header/common/cache_control.rs delete mode 100644 src/header/common/content_disposition.rs delete mode 100644 src/header/common/content_language.rs delete mode 100644 src/header/common/content_range.rs delete mode 100644 src/header/common/content_type.rs delete mode 100644 src/header/common/date.rs delete mode 100644 src/header/common/etag.rs delete mode 100644 src/header/common/expires.rs delete mode 100644 src/header/common/if_match.rs delete mode 100644 src/header/common/if_modified_since.rs delete mode 100644 src/header/common/if_none_match.rs delete mode 100644 src/header/common/if_range.rs delete mode 100644 src/header/common/if_unmodified_since.rs delete mode 100644 src/header/common/last_modified.rs delete mode 100644 src/header/common/mod.rs delete mode 100644 src/header/common/range.rs delete mode 100644 src/header/mod.rs delete mode 100644 src/header/shared/charset.rs delete mode 100644 src/header/shared/encoding.rs delete mode 100644 src/header/shared/entity.rs delete mode 100644 src/header/shared/httpdate.rs delete mode 100644 src/header/shared/mod.rs delete mode 100644 src/header/shared/quality_item.rs delete mode 100644 src/httpcodes.rs delete mode 100644 src/httpmessage.rs delete mode 100644 src/httprequest.rs delete mode 100644 src/httpresponse.rs delete mode 100644 src/json.rs create mode 100644 src/middleware/compress.rs delete mode 100644 src/middleware/cors.rs delete mode 100644 src/middleware/csrf.rs delete mode 100644 src/middleware/errhandlers.rs delete mode 100644 src/middleware/identity.rs delete mode 100644 src/middleware/logger.rs delete mode 100644 src/middleware/session.rs delete mode 100644 src/multipart.rs delete mode 100644 src/param.rs delete mode 100644 src/payload.rs delete mode 100644 src/pipeline.rs delete mode 100644 src/pred.rs create mode 100644 src/request.rs create mode 100644 src/responder.rs delete mode 100644 src/router.rs delete mode 100644 src/scope.rs delete mode 100644 src/server/acceptor.rs delete mode 100644 src/server/builder.rs delete mode 100644 src/server/channel.rs delete mode 100644 src/server/error.rs delete mode 100644 src/server/h1.rs delete mode 100644 src/server/h1decoder.rs delete mode 100644 src/server/h1writer.rs delete mode 100644 src/server/h2.rs delete mode 100644 src/server/h2writer.rs delete mode 100644 src/server/handler.rs delete mode 100644 src/server/helpers.rs delete mode 100644 src/server/http.rs delete mode 100644 src/server/incoming.rs delete mode 100644 src/server/input.rs delete mode 100644 src/server/message.rs delete mode 100644 src/server/mod.rs delete mode 100644 src/server/output.rs delete mode 100644 src/server/service.rs delete mode 100644 src/server/settings.rs delete mode 100644 src/server/ssl/mod.rs delete mode 100644 src/server/ssl/nativetls.rs delete mode 100644 src/server/ssl/openssl.rs delete mode 100644 src/server/ssl/rustls.rs create mode 100644 src/service.rs create mode 100644 src/state.rs delete mode 100644 src/uri.rs delete mode 100644 src/with.rs delete mode 100644 src/ws/client.rs delete mode 100644 src/ws/context.rs delete mode 100644 src/ws/frame.rs delete mode 100644 src/ws/mask.rs delete mode 100644 src/ws/mod.rs delete mode 100644 src/ws/proto.rs delete mode 100644 tests/identity.pfx delete mode 100644 tests/test space.binary delete mode 100644 tests/test.binary delete mode 100644 tests/test.png delete mode 100644 tests/test_client.rs delete mode 100644 tests/test_custom_pipeline.rs delete mode 100644 tests/test_handlers.rs delete mode 100644 tests/test_middleware.rs delete mode 100644 tests/test_ws.rs diff --git a/CHANGES.md b/CHANGES.md index d1d838f4..b93e282a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,833 +1,5 @@ # Changes -## [x.x.xx] - xxxx-xx-xx +## [0.1.0] - 2018-10-x -### Added - -* Add `from_file` and `from_file_with_config` to `NamedFile` to allow sending files without a known path. #670 - -* Add `insert` and `remove` methods to `HttpResponseBuilder` - -### Fixed - -* Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680 - -## [0.7.18] - 2019-01-10 - -### Added - -* Add `with_cookie` for `TestRequest` to allow users to customize request cookie. #647 - -* Add `cookie` method for `TestRequest` to allow users to add cookie dynamically. - -### Fixed - -* StaticFiles decode special characters in request's path - -* Fix test server listener leak #654 - -## [0.7.17] - 2018-12-25 - -### Added - -* Support for custom content types in `JsonConfig`. #637 - -* Send `HTTP/1.1 100 Continue` if request contains `expect: continue` header #634 - -### Fixed - -* HTTP1 decoder should perform case-insentive comparison for client requests (e.g. `Keep-Alive`). #631 - -* Access-Control-Allow-Origin header should only a return a single, matching origin. #603 - -## [0.7.16] - 2018-12-11 - -### Added - -* Implement `FromRequest` extractor for `Either` - -* Implement `ResponseError` for `SendError` - - -## [0.7.15] - 2018-12-05 - -### Changed - -* `ClientConnector::resolver` now accepts `Into` instead of `Addr`. It enables user to implement own resolver. - -* `QueryConfig` and `PathConfig` are made public. - -* `AsyncResult::async` is changed to `AsyncResult::future` as `async` is reserved keyword in 2018 edition. - -### Added - -* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled - with `PathConfig::default().disable_decoding()` - - -## [0.7.14] - 2018-11-14 - -### Added - -* Add method to configure custom error handler to `Query` and `Path` extractors. - -* Add method to configure `SameSite` option in `CookieIdentityPolicy`. - -* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled - with `PathConfig::default().disable_decoding()` - - -### Fixed - -* Fix websockets connection drop if request contains "content-length" header #567 - -* Fix keep-alive timer reset - -* HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549 - -* Set nodelay for socket #560 - - -## [0.7.13] - 2018-10-14 - -### Fixed - -* Fixed rustls support - -* HttpServer not sending streamed request body on HTTP/2 requests #544 - - -## [0.7.12] - 2018-10-10 - -### Changed - -* Set min version for actix - -* Set min version for actix-net - - -## [0.7.11] - 2018-10-09 - -### Fixed - -* Fixed 204 responses for http/2 - - -## [0.7.10] - 2018-10-09 - -### Fixed - -* Fixed panic during graceful shutdown - - -## [0.7.9] - 2018-10-09 - -### Added - -* Added client shutdown timeout setting - -* Added slow request timeout setting - -* Respond with 408 response on slow request timeout #523 - - -### Fixed - -* HTTP1 decoding errors are reported to the client. #512 - -* Correctly compose multiple allowed origins in CORS. #517 - -* Websocket server finished() isn't called if client disconnects #511 - -* Responses with the following codes: 100, 101, 102, 204 -- are sent without Content-Length header. #521 - -* Correct usage of `no_http2` flag in `bind_*` methods. #519 - - -## [0.7.8] - 2018-09-17 - -### Added - -* Use server `Keep-Alive` setting as slow request timeout #439 - -### Changed - -* Use 5 seconds keep-alive timer by default. - -### Fixed - -* Fixed wrong error message for i16 type #510 - - -## [0.7.7] - 2018-09-11 - -### Fixed - -* Fix linked list of HttpChannels #504 - -* Fix requests to TestServer fail #508 - - -## [0.7.6] - 2018-09-07 - -### Fixed - -* Fix system_exit in HttpServer #501 - -* Fix parsing of route param containin regexes with repetition #500 - -### Changes - -* Unhide `SessionBackend` and `SessionImpl` traits #455 - - -## [0.7.5] - 2018-09-04 - -### Added - -* Added the ability to pass a custom `TlsConnector`. - -* Allow to register handlers on scope level #465 - - -### Fixed - -* Handle socket read disconnect - -* Handling scoped paths without leading slashes #460 - - -### Changed - -* Read client response until eof if connection header set to close #464 - - -## [0.7.4] - 2018-08-23 - -### Added - -* Added `HttpServer::maxconn()` and `HttpServer::maxconnrate()`, - accept backpressure #250 - -* Allow to customize connection handshake process via `HttpServer::listen_with()` - and `HttpServer::bind_with()` methods - -* Support making client connections via `tokio-uds`'s `UnixStream` when "uds" feature is enabled #472 - -### Changed - -* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`. - `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - -* native-tls - 0.2 - -* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461 - -### Fixed - -* Use zlib instead of raw deflate for decoding and encoding payloads with - `Content-Encoding: deflate`. - -* Fixed headers formating for CORS Middleware Access-Control-Expose-Headers #436 - -* Fix adding multiple response headers #446 - -* Client includes port in HOST header when it is not default(e.g. not 80 and 443). #448 - -* Panic during access without routing being set #452 - -* Fixed http/2 error handling - -### Deprecated - -* `HttpServer::no_http2()` is deprecated, use `OpensslAcceptor::with_flags()` or - `RustlsAcceptor::with_flags()` instead - -* `HttpServer::listen_tls()`, `HttpServer::listen_ssl()`, `HttpServer::listen_rustls()` have been - deprecated in favor of `HttpServer::listen_with()` with specific `acceptor`. - -* `HttpServer::bind_tls()`, `HttpServer::bind_ssl()`, `HttpServer::bind_rustls()` have been - deprecated in favor of `HttpServer::bind_with()` with specific `acceptor`. - - -## [0.7.3] - 2018-08-01 - -### Added - -* Support HTTP/2 with rustls #36 - -* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433 - -### Fixed - -* Fixed failure 0.1.2 compatibility - -* Do not override HOST header for client request #428 - -* Gz streaming, use `flate2::write::GzDecoder` #228 - -* HttpRequest::url_for is not working with scopes #429 - -* Fixed headers' formating for CORS Middleware `Access-Control-Expose-Headers` header value to HTTP/1.1 & HTTP/2 spec-compliant format #436 - - -## [0.7.2] - 2018-07-26 - -### Added - -* Add implementation of `FromRequest` for `Option` and `Result` - -* Allow to handle application prefix, i.e. allow to handle `/app` path - for application with `/app` prefix. - Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix) - api doc. - -* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies - -### Changed - -* Upgrade to cookie 0.11 - -* Removed the timestamp from the default logger middleware - -### Fixed - -* Missing response header "content-encoding" #421 - -* Fix stream draining for http/2 connections #290 - - -## [0.7.1] - 2018-07-21 - -### Fixed - -* Fixed default_resource 'not yet implemented' panic #410 - - -## [0.7.0] - 2018-07-21 - -### Added - -* Add `fs::StaticFileConfig` to provide means of customizing static - file services. It allows to map `mime` to `Content-Disposition`, - specify whether to use `ETag` and `Last-Modified` and allowed methods. - -* Add `.has_prefixed_resource()` method to `router::ResourceInfo` - for route matching with prefix awareness - -* Add `HttpMessage::readlines()` for reading line by line. - -* Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. - -* Add method to configure custom error handler to `Form` extractor. - -* Add methods to `HttpResponse` to retrieve, add, and delete cookies - -* Add `.set_content_type()` and `.set_content_disposition()` methods - to `fs::NamedFile` to allow overriding the values inferred by default - -* Add `fs::file_extension_to_mime()` helper function to get the MIME - type for a file extension - -* Add `.content_disposition()` method to parse Content-Disposition of - multipart fields - -* Re-export `actix::prelude::*` as `actix_web::actix` module. - -* `HttpRequest::url_for_static()` for a named route with no variables segments - -* Propagation of the application's default resource to scopes that haven't set a default resource. - - -### Changed - -* Min rustc version is 1.26 - -* Use tokio instead of tokio-core - -* `CookieSessionBackend` sets percent encoded cookies for outgoing HTTP messages. - -* Became possible to use enums with query extractor. - Issue [#371](https://github.com/actix/actix-web/issues/371). - [Example](https://github.com/actix/actix-web/blob/master/tests/test_handlers.rs#L94-L134) - -* `HttpResponse::into_builder()` now moves cookies into the builder - instead of dropping them - -* For safety and performance reasons `Handler::handle()` uses `&self` instead of `&mut self` - -* `Handler::handle()` uses `&HttpRequest` instead of `HttpRequest` - -* Added header `User-Agent: Actix-web/` to default headers when building a request - -* port `Extensions` type from http create, we don't need `Send + Sync` - -* `HttpRequest::query()` returns `Ref>` - -* `HttpRequest::cookies()` returns `Ref>>` - -* `StaticFiles::new()` returns `Result, Error>` instead of `StaticFiles` - -* `StaticFiles` uses the default handler if the file does not exist - - -### Removed - -* Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. - -* Remove `HttpMessage::range()` - - -## [0.6.15] - 2018-07-11 - -### Fixed - -* Fix h2 compatibility #352 - -* Fix duplicate tail of StaticFiles with index_file. #344 - - -## [0.6.14] - 2018-06-21 - -### Added - -* Allow to disable masking for websockets client - -### Fixed - -* SendRequest execution fails with the "internal error: entered unreachable code" #329 - - -## [0.6.13] - 2018-06-11 - -* http/2 end-of-frame is not set if body is empty bytes #307 - -* InternalError can trigger memory unsafety #301 - - -## [0.6.12] - 2018-06-08 - -### Added - -* Add `Host` filter #287 - -* Allow to filter applications - -* Improved failure interoperability with downcasting #285 - -* Allow to use custom resolver for `ClientConnector` - - -## [0.6.11] - 2018-06-05 - -* Support chunked encoding for UrlEncoded body #262 - -* `HttpRequest::url_for()` for a named route with no variables segments #265 - -* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255 - -* CORS: Do not validate Origin header on non-OPTION requests #271 - -* Fix multipart upload "Incomplete" error #282 - - -## [0.6.10] - 2018-05-24 - -### Added - -* Allow to use path without trailing slashes for scope registration #241 - -* Allow to set encoding for exact NamedFile #239 - -### Fixed - -* `TestServer::post()` actually sends `GET` request #240 - - -## 0.6.9 (2018-05-22) - -* Drop connection if request's payload is not fully consumed #236 - -* Fix streaming response with body compression - - -## 0.6.8 (2018-05-20) - -* Fix scope resource path extractor #234 - -* Re-use tcp listener on pause/resume - - -## 0.6.7 (2018-05-17) - -* Fix compilation with --no-default-features - - -## 0.6.6 (2018-05-17) - -* Panic during middleware execution #226 - -* Add support for listen_tls/listen_ssl #224 - -* Implement extractor for `Session` - -* Ranges header support for NamedFile #60 - - -## 0.6.5 (2018-05-15) - -* Fix error handling during request decoding #222 - - -## 0.6.4 (2018-05-11) - -* Fix segfault in ServerSettings::get_response_builder() - - -## 0.6.3 (2018-05-10) - -* Add `Router::with_async()` method for async handler registration. - -* Added error response functions for 501,502,503,504 - -* Fix client request timeout handling - - -## 0.6.2 (2018-05-09) - -* WsWriter trait is optional. - - -## 0.6.1 (2018-05-08) - -* Fix http/2 payload streaming #215 - -* Fix connector's default `keep-alive` and `lifetime` settings #212 - -* Send `ErrorNotFound` instead of `ErrorBadRequest` when path extractor fails #214 - -* Allow to exclude certain endpoints from logging #211 - - -## 0.6.0 (2018-05-08) - -* Add route scopes #202 - -* Allow to use ssl and non-ssl connections at the same time #206 - -* Websocket CloseCode Empty/Status is ambiguous #193 - -* Add Content-Disposition to NamedFile #204 - -* Allow to access Error's backtrace object - -* Allow to override files listing renderer for `StaticFiles` #203 - -* Various extractor usability improvements #207 - - -## 0.5.6 (2018-04-24) - -* Make flate2 crate optional #200 - - -## 0.5.5 (2018-04-24) - -* Fix panic when Websocket is closed with no error code #191 - -* Allow to use rust backend for flate2 crate #199 - -## 0.5.4 (2018-04-19) - -* Add identity service middleware - -* Middleware response() is not invoked if there was an error in async handler #187 - -* Use Display formatting for InternalError Display implementation #188 - - -## 0.5.3 (2018-04-18) - -* Impossible to quote slashes in path parameters #182 - - -## 0.5.2 (2018-04-16) - -* Allow to configure StaticFiles's CpuPool, via static method or env variable - -* Add support for custom handling of Json extractor errors #181 - -* Fix StaticFiles does not support percent encoded paths #177 - -* Fix Client Request with custom Body Stream halting on certain size requests #176 - - -## 0.5.1 (2018-04-12) - -* Client connector provides stats, `ClientConnector::stats()` - -* Fix end-of-stream handling in parse_payload #173 - -* Fix StaticFiles generate a lot of threads #174 - - -## 0.5.0 (2018-04-10) - -* Type-safe path/query/form parameter handling, using serde #70 - -* HttpResponse builder's methods `.body()`, `.finish()`, `.json()` - return `HttpResponse` instead of `Result` - -* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()` - -* Added `signed` and `private` `CookieSessionBackend`s - -* Added `HttpRequest::resource()`, returns current matched resource - -* Added `ErrorHandlers` middleware - -* Fix router cannot parse Non-ASCII characters in URL #137 - -* Fix client connection pooling - -* Fix long client urls #129 - -* Fix panic on invalid URL characters #130 - -* Fix logger request duration calculation #152 - -* Fix prefix and static file serving #168 - - -## 0.4.10 (2018-03-20) - -* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods - -* Allow to set client request timeout - -* Allow to set client websocket handshake timeout - -* Refactor `TestServer` configuration - -* Fix server websockets big payloads support - -* Fix http/2 date header generation - - -## 0.4.9 (2018-03-16) - -* Allow to disable http/2 support - -* Wake payload reading task when data is available - -* Fix server keep-alive handling - -* Send Query Parameters in client requests #120 - -* Move brotli encoding to a feature - -* Add option of default handler for `StaticFiles` handler #57 - -* Add basic client connection pooling - - -## 0.4.8 (2018-03-12) - -* Allow to set read buffer capacity for server request - -* Handle WouldBlock error for socket accept call - - -## 0.4.7 (2018-03-11) - -* Fix panic on unknown content encoding - -* Fix connection get closed too early - -* Fix streaming response handling for http/2 - -* Better sleep on error support - - -## 0.4.6 (2018-03-10) - -* Fix client cookie handling - -* Fix json content type detection - -* Fix CORS middleware #117 - -* Optimize websockets stream support - - -## 0.4.5 (2018-03-07) - -* Fix compression #103 and #104 - -* Fix client cookie handling #111 - -* Non-blocking processing of a `NamedFile` - -* Enable compression support for `NamedFile` - -* Better support for `NamedFile` type - -* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client. - -* Add native-tls support for client - -* Allow client connection timeout to be set #108 - -* Allow to use std::net::TcpListener for HttpServer - -* Handle panics in worker threads - - -## 0.4.4 (2018-03-04) - -* Allow to use Arc> as response/request body - -* Fix handling of requests with an encoded body with a length > 8192 #93 - -## 0.4.3 (2018-03-03) - -* Fix request body read bug - -* Fix segmentation fault #79 - -* Set reuse address before bind #90 - - -## 0.4.2 (2018-03-02) - -* Better naming for websockets implementation - -* Add `Pattern::with_prefix()`, make it more usable outside of actix - -* Add csrf middleware for filter for cross-site request forgery #89 - -* Fix disconnect on idle connections - - -## 0.4.1 (2018-03-01) - -* Rename `Route::p()` to `Route::filter()` - -* Better naming for http codes - -* Fix payload parse in situation when socket data is not ready. - -* Fix Session mutable borrow lifetime #87 - - -## 0.4.0 (2018-02-28) - -* Actix 0.5 compatibility - -* Fix request json/urlencoded loaders - -* Simplify HttpServer type definition - -* Added HttpRequest::encoding() method - -* Added HttpRequest::mime_type() method - -* Added HttpRequest::uri_mut(), allows to modify request uri - -* Added StaticFiles::index_file() - -* Added http client - -* Added websocket client - -* Added TestServer::ws(), test websockets client - -* Added TestServer http client support - -* Allow to override content encoding on application level - - -## 0.3.3 (2018-01-25) - -* Stop processing any events after context stop - -* Re-enable write back-pressure for h1 connections - -* Refactor HttpServer::start_ssl() method - -* Upgrade openssl to 0.10 - - -## 0.3.2 (2018-01-21) - -* Fix HEAD requests handling - -* Log request processing errors - -* Always enable content encoding if encoding explicitly selected - -* Allow multiple Applications on a single server with different state #49 - -* CORS middleware: allowed_headers is defaulting to None #50 - - -## 0.3.1 (2018-01-13) - -* Fix directory entry path #47 - -* Do not enable chunked encoding for HTTP/1.0 - -* Allow explicitly disable chunked encoding - - -## 0.3.0 (2018-01-12) - -* HTTP/2 Support - -* Refactor streaming responses - -* Refactor error handling - -* Asynchronous middlewares - -* Refactor logger middleware - -* Content compression/decompression (br, gzip, deflate) - -* Server multi-threading - -* Graceful shutdown support - - -## 0.2.1 (2017-11-03) - -* Allow to start tls server with `HttpServer::serve_tls` - -* Export `Frame` enum - -* Add conversion impl from `HttpResponse` and `BinaryBody` to a `Frame` - - -## 0.2.0 (2017-10-30) - -* Do not use `http::Uri` as it can not parse some valid paths - -* Refactor response `Body` - -* Refactor `RouteRecognizer` usability - -* Refactor `HttpContext::write` - -* Refactor `Payload` stream - -* Re-use `BinaryBody` for `Frame::Payload` - -* Stop http actor on `write_eof` - -* Fix disconnection handling. - - -## 0.1.0 (2017-10-23) - -* First release +* Initial impl diff --git a/Cargo.toml b/Cargo.toml index bd3cb306..6a61c780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.7.18" +version = "0.1.0" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -10,44 +10,21 @@ repository = "https://github.com/actix/actix-web.git" documentation = "https://actix.rs/api/actix-web/stable/actix_web/" categories = ["network-programming", "asynchronous", "web-programming::http-server", - "web-programming::http-client", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] -build = "build.rs" - -[package.metadata.docs.rs] -features = ["tls", "ssl", "rust-tls", "session", "brotli", "flate2-c"] +edition = "2018" [badges] -travis-ci = { repository = "actix/actix-web", branch = "master" } -appveyor = { repository = "fafhrd91/actix-web-hdy9d" } -codecov = { repository = "actix/actix-web", branch = "master", service = "github" } +travis-ci = { repository = "actix/actix-web2", branch = "master" } +codecov = { repository = "actix/actix-web2", branch = "master", service = "github" } [lib] name = "actix_web" path = "src/lib.rs" [features] -default = ["session", "brotli", "flate2-c", "cell"] - -# tls -tls = ["native-tls", "tokio-tls", "actix-net/tls"] - -# openssl -ssl = ["openssl", "tokio-openssl", "actix-net/ssl"] - -# deprecated, use "ssl" -alpn = ["openssl", "tokio-openssl", "actix-net/ssl"] - -# rustls -rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots", "actix-net/rust-tls"] - -# unix sockets -uds = ["tokio-uds"] - -# sessions feature, session require "ring" crate and c compiler -session = ["cookie/secure"] +default = ["brotli", "flate2-c"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -58,81 +35,54 @@ flate2-c = ["flate2/miniz-sys"] # rust backend for flate2 crate flate2-rust = ["flate2/rust_backend"] -cell = ["actix-net/cell"] - [dependencies] -actix = "0.7.9" -actix-net = "0.2.6" +actix-codec = "0.1.0" +#actix-service = "0.2.1" +#actix-server = "0.2.1" +#actix-utils = "0.2.1" +actix-service = { git = "https://github.com/actix/actix-net.git" } +actix-server = { git = "https://github.com/actix/actix-net.git" } +actix-utils = { git = "https://github.com/actix/actix-net.git" } -v_htmlescape = "0.4" -base64 = "0.10" -bitflags = "1.0" -failure = "^0.1.2" -h2 = "0.1" -http = "^0.1.14" -httparse = "1.3" +actix-rt = "0.1.0" +actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-router = { git = "https://github.com/actix/actix-net.git" } + +bytes = "0.4" +futures = "0.1" +derive_more = "0.14" log = "0.4" +lazy_static = "1.2" mime = "0.3" mime_guess = "2.0.0-alpha" -num_cpus = "1.0" +num_cpus = "1.10" percent-encoding = "1.0" -rand = "0.6" -regex = "1.0" +cookie = { version="0.11", features=["percent-encode"] } +v_htmlescape = "0.4" serde = "1.0" serde_json = "1.0" -sha1 = "0.6" -smallvec = "0.6" -time = "0.1" encoding = "0.2" -language-tags = "0.2" -lazy_static = "1.0" -lazycell = "1.0.0" -parking_lot = "0.7" serde_urlencoded = "^0.5.3" -url = { version="1.7", features=["query_encoding"] } -cookie = { version="0.11", features=["percent-encode"] } +parking_lot = "0.7" +hashbrown = "0.1" +regex = "1" +time = "0.1" +threadpool = "1.7" + +# compression brotli2 = { version="^0.3.2", optional = true } flate2 = { version="^1.0.2", optional = true, default-features = false } -# io -mio = "^0.6.13" -net2 = "0.2" -bytes = "0.4" -byteorder = "1.2" -futures = "0.1" -futures-cpupool = "0.1" -slab = "0.4" -tokio = "0.1" -tokio-io = "0.1" -tokio-tcp = "0.1" -tokio-timer = "0.2.8" -tokio-reactor = "0.1" -tokio-current-thread = "0.1" - -# native-tls -native-tls = { version="0.2", optional = true } -tokio-tls = { version="0.2", optional = true } - -# openssl -openssl = { version="0.10", optional = true } -tokio-openssl = { version="0.2", optional = true } - -#rustls -rustls = { version = "0.14", optional = true } -tokio-rustls = { version = "0.8", optional = true } -webpki = { version = "0.18", optional = true } -webpki-roots = { version = "0.15", optional = true } - -# unix sockets -tokio-uds = { version="0.2", optional = true } - [dev-dependencies] +actix-rt = "0.1.0" +#actix-server = { version="0.2", features=["ssl"] } +actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] } +actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +rand = "0.6" env_logger = "0.6" serde_derive = "1.0" -[build-dependencies] -version_check = "0.1" - [profile.release] lto = true opt-level = 3 diff --git a/build.rs b/build.rs deleted file mode 100644 index c8457944..00000000 --- a/build.rs +++ /dev/null @@ -1,16 +0,0 @@ -extern crate version_check; - -fn main() { - match version_check::is_min_version("1.26.0") { - Some((true, _)) => println!("cargo:rustc-cfg=actix_impl_trait"), - _ => (), - }; - match version_check::is_nightly() { - Some(true) => { - println!("cargo:rustc-cfg=actix_nightly"); - println!("cargo:rustc-cfg=actix_impl_trait"); - } - Some(false) => (), - None => (), - }; -} diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 00000000..9f4701eb --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,54 @@ +use futures::IntoFuture; + +use actix_http::{h1, http::Method, Response}; +use actix_server::Server; +use actix_web2::{middleware, App, Error, HttpRequest, Resource}; + +fn index(req: HttpRequest) -> &'static str { + println!("REQ: {:?}", req); + "Hello world!\r\n" +} + +fn index_async(req: HttpRequest) -> impl IntoFuture { + println!("REQ: {:?}", req); + Ok("Hello world!\r\n") +} + +fn no_params() -> &'static str { + "Hello world!\r\n" +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_server=info,actix_web2=info"); + env_logger::init(); + let sys = actix_rt::System::new("hello-world"); + + Server::build() + .bind("test", "127.0.0.1:8080", || { + h1::H1Service::new( + App::new() + .middleware( + middleware::DefaultHeaders::new().header("X-Version", "0.2"), + ) + .middleware(middleware::Compress::default()) + .resource("/resource1/index.html", |r| r.get(index)) + .service( + "/resource2/index.html", + Resource::new() + .middleware( + middleware::DefaultHeaders::new() + .header("X-Version-R2", "0.3"), + ) + .default_resource(|r| r.to(|| Response::MethodNotAllowed())) + .method(Method::GET, |r| r.to_async(index_async)), + ) + .service("/test1.html", Resource::new().to(|| "Test\r\n")) + .service("/", Resource::new().to(no_params)), + ) + }) + .unwrap() + .workers(1) + .start(); + + let _ = sys.run(); +} diff --git a/rustfmt.toml b/rustfmt.toml index 4fff285e..94bd11d5 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,2 @@ max_width = 89 reorder_imports = true -#wrap_comments = true -fn_args_density = "Compressed" -#use_small_heuristics = false diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 00000000..6ed9b144 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,648 @@ +use std::cell::RefCell; +use std::marker::PhantomData; +use std::rc::Rc; + +use actix_http::body::{Body, MessageBody}; +use actix_http::{Extensions, PayloadStream, Request, Response}; +use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; +use actix_service::{ + AndThenNewService, ApplyNewService, IntoNewService, IntoNewTransform, NewService, + NewTransform, Service, +}; +use futures::future::{ok, Either, FutureResult}; +use futures::{Async, Future, IntoFuture, Poll}; + +use crate::helpers::{ + BoxedHttpNewService, BoxedHttpService, DefaultNewService, HttpDefaultNewService, +}; +use crate::resource::Resource; +use crate::service::{ServiceRequest, ServiceResponse}; +use crate::state::{State, StateFactory, StateFactoryResult}; + +type BoxedResponse = Box>; + +pub trait HttpServiceFactory { + type Factory: NewService; + + fn rdef(&self) -> &ResourceDef; + + fn create(self) -> Self::Factory; +} + +/// Application builder +pub struct App { + services: Vec<( + ResourceDef, + BoxedHttpNewService, ServiceResponse>, + )>, + default: Option, ServiceResponse>>>, + defaults: Vec< + Rc< + RefCell< + Option, ServiceResponse>>>, + >, + >, + >, + endpoint: T, + factory_ref: Rc>>>, + extensions: Extensions, + state: Vec>, + _t: PhantomData<(P, B)>, +} + +impl App> { + /// Create application with empty state. Application can + /// be configured with a builder-like pattern. + pub fn new() -> Self { + App::create() + } +} + +impl Default for App> { + fn default() -> Self { + App::new() + } +} + +impl App> { + /// Create application with specified state. Application can be + /// configured with a builder-like pattern. + /// + /// State is shared with all resources within same application and + /// could be accessed with `HttpRequest::state()` method. + /// + /// **Note**: http server accepts an application factory rather than + /// an application instance. Http server constructs an application + /// instance for each thread, thus application state must be constructed + /// multiple times. If you want to share state between different + /// threads, a shared object should be used, e.g. `Arc`. Application + /// state does not need to be `Send` or `Sync`. + pub fn state(mut self, state: S) -> Self { + self.state.push(Box::new(State::new(state))); + self + } + + /// Set application state. This function is + /// similar to `.state()` but it accepts state factory. State get + /// constructed asynchronously during application initialization. + pub fn state_factory(mut self, state: F) -> Self + where + F: Fn() -> Out + 'static, + Out: IntoFuture + 'static, + Out::Error: std::fmt::Debug, + { + self.state.push(Box::new(State::new(state))); + self + } + + fn create() -> Self { + let fref = Rc::new(RefCell::new(None)); + App { + services: Vec::new(), + default: None, + defaults: Vec::new(), + endpoint: AppEntry::new(fref.clone()), + factory_ref: fref, + extensions: Extensions::new(), + state: Vec::new(), + _t: PhantomData, + } + } +} + +// /// Application router builder +// pub struct AppRouter { +// services: Vec<( +// ResourceDef, +// BoxedHttpNewService, Response>, +// )>, +// default: Option, Response>>>, +// defaults: +// Vec, Response>>>>>>, +// state: AppState, +// endpoint: T, +// factory_ref: Rc>>>, +// extensions: Extensions, +// _t: PhantomData

    , +// } + +impl App +where + P: 'static, + B: MessageBody, + T: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, +{ + /// Configure resource for a specific path. + /// + /// Resources may have variable path segments. For example, a + /// resource with the path `/a/{name}/c` would match all incoming + /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. + /// + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. This is done by + /// looking up the identifier in the `Params` object returned by + /// `HttpRequest.match_info()` method. + /// + /// By default, each segment matches the regular expression `[^{}/]+`. + /// + /// You can also specify a custom regex in the form `{identifier:regex}`: + /// + /// For instance, to route `GET`-requests on any route matching + /// `/users/{userid}/{friend}` and store `userid` and `friend` in + /// the exposed `Params` object: + /// + /// ```rust,ignore + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().resource("/users/{userid}/{friend}", |r| { + /// r.get(|r| r.to(|_| HttpResponse::Ok())); + /// r.head(|r| r.to(|_| HttpResponse::MethodNotAllowed())) + /// }); + /// } + /// ``` + pub fn resource(mut self, path: &str, f: F) -> Self + where + F: FnOnce(Resource

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

    , + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, + { + let rdef = ResourceDef::new(path); + let resource = f(Resource::new()); + self.defaults.push(resource.get_default()); + self.services.push(( + rdef, + Box::new(HttpNewService::new(resource.into_new_service())), + )); + self + } + + /// Default resource to be used if no matching route could be found. + /// + /// Default resource works with resources only and does not work with + /// custom services. + pub fn default_resource(mut self, f: F) -> Self + where + F: FnOnce(Resource

    ) -> R, + R: IntoNewService, + U: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + > + 'static, + { + // create and configure default resource + self.default = Some(Rc::new(Box::new(DefaultNewService::new( + f(Resource::new()).into_new_service(), + )))); + + self + } + + /// Register resource handler service. + pub fn service(mut self, rdef: R, factory: F) -> Self + where + R: Into, + F: IntoNewService, + U: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + > + 'static, + { + self.services.push(( + rdef.into(), + Box::new(HttpNewService::new(factory.into_new_service())), + )); + self + } + + /// Register a middleware. + pub fn middleware( + self, + mw: F, + ) -> App< + P, + B1, + impl NewService< + Request = ServiceRequest

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

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, + B1: MessageBody, + F: IntoNewTransform, + { + let endpoint = ApplyNewService::new(mw, self.endpoint); + App { + endpoint, + state: self.state, + services: self.services, + default: self.default, + defaults: Vec::new(), + factory_ref: self.factory_ref, + extensions: Extensions::new(), + _t: PhantomData, + } + } + + /// Register an external resource. + /// + /// External resources are useful for URL generation purposes only + /// and are never considered for matching at request time. Calls to + /// `HttpRequest::url_for()` will work as expected. + /// + /// ```rust,ignore + /// # extern crate actix_web; + /// use actix_web::{App, HttpRequest, HttpResponse, Result}; + /// + /// fn index(req: &HttpRequest) -> Result { + /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; + /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); + /// Ok(HttpResponse::Ok().into()) + /// } + /// + /// fn main() { + /// let app = App::new() + /// .resource("/index.html", |r| r.get().f(index)) + /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") + /// .finish(); + /// } + /// ``` + pub fn external_resource(self, _name: N, _url: U) -> Self + where + N: AsRef, + U: AsRef, + { + // self.parts + // .as_mut() + // .expect("Use after finish") + // .router + // .register_external(name.as_ref(), ResourceDef::external(url.as_ref())); + self + } +} + +impl + IntoNewService, T, ()>> for App +where + T: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, +{ + fn into_new_service(self) -> AndThenNewService, T, ()> { + // update resource default service + if self.default.is_some() { + for default in &self.defaults { + if default.borrow_mut().is_none() { + *default.borrow_mut() = self.default.clone(); + } + } + } + + // set factory + *self.factory_ref.borrow_mut() = Some(AppFactory { + services: Rc::new(self.services), + }); + + AppStateFactory { + state: self.state, + extensions: Rc::new(RefCell::new(Rc::new(self.extensions))), + _t: PhantomData, + } + .and_then(self.endpoint) + } +} + +/// Service factory to convert `Request` to a `ServiceRequest` +pub struct AppStateFactory

    { + state: Vec>, + extensions: Rc>>, + _t: PhantomData

    , +} + +impl NewService for AppStateFactory

    { + type Request = Request

    ; + type Response = ServiceRequest

    ; + type Error = (); + type InitError = (); + type Service = AppStateService

    ; + type Future = AppStateFactoryResult

    ; + + fn new_service(&self, _: &()) -> Self::Future { + AppStateFactoryResult { + state: self.state.iter().map(|s| s.construct()).collect(), + extensions: self.extensions.clone(), + _t: PhantomData, + } + } +} + +#[doc(hidden)] +pub struct AppStateFactoryResult

    { + state: Vec>, + extensions: Rc>>, + _t: PhantomData

    , +} + +impl

    Future for AppStateFactoryResult

    { + type Item = AppStateService

    ; + type Error = (); + + fn poll(&mut self) -> Poll { + if let Some(extensions) = Rc::get_mut(&mut *self.extensions.borrow_mut()) { + let mut idx = 0; + while idx < self.state.len() { + if let Async::Ready(_) = self.state[idx].poll_result(extensions)? { + self.state.remove(idx); + } else { + idx += 1; + } + } + if !self.state.is_empty() { + return Ok(Async::NotReady); + } + } else { + log::warn!("Multiple copies of app extensions exists"); + } + + Ok(Async::Ready(AppStateService { + extensions: self.extensions.borrow().clone(), + _t: PhantomData, + })) + } +} + +/// Service to convert `Request` to a `ServiceRequest` +pub struct AppStateService

    { + extensions: Rc, + _t: PhantomData

    , +} + +impl

    Service for AppStateService

    { + type Request = Request

    ; + type Response = ServiceRequest

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

    ) -> Self::Future { + ok(ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + self.extensions.clone(), + )) + } +} + +pub struct AppFactory

    { + services: Rc< + Vec<( + ResourceDef, + BoxedHttpNewService, ServiceResponse>, + )>, + >, +} + +impl

    NewService for AppFactory

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = AppService

    ; + type Future = CreateAppService

    ; + + fn new_service(&self, _: &()) -> Self::Future { + CreateAppService { + fut: self + .services + .iter() + .map(|(path, service)| { + CreateAppServiceItem::Future( + Some(path.clone()), + service.new_service(&()), + ) + }) + .collect(), + } + } +} + +type HttpServiceFut

    = + Box, ServiceResponse>, Error = ()>>; + +/// Create app service +#[doc(hidden)] +pub struct CreateAppService

    { + fut: Vec>, +} + +enum CreateAppServiceItem

    { + Future(Option, HttpServiceFut

    ), + Service( + ResourceDef, + BoxedHttpService, ServiceResponse>, + ), +} + +impl

    Future for CreateAppService

    { + type Item = AppService

    ; + type Error = (); + + fn poll(&mut self) -> Poll { + let mut done = true; + + // poll http services + for item in &mut self.fut { + let res = match item { + CreateAppServiceItem::Future(ref mut path, ref mut fut) => { + match fut.poll()? { + Async::Ready(service) => Some((path.take().unwrap(), service)), + Async::NotReady => { + done = false; + None + } + } + } + CreateAppServiceItem::Service(_, _) => continue, + }; + + if let Some((path, service)) = res { + *item = CreateAppServiceItem::Service(path, service); + } + } + + if done { + let router = self + .fut + .drain(..) + .fold(Router::build(), |mut router, item| { + match item { + CreateAppServiceItem::Service(path, service) => { + router.rdef(path, service) + } + CreateAppServiceItem::Future(_, _) => unreachable!(), + } + router + }); + Ok(Async::Ready(AppService { + router: router.finish(), + ready: None, + })) + } else { + Ok(Async::NotReady) + } + } +} + +pub struct AppService

    { + router: Router, ServiceResponse>>, + ready: Option<(ServiceRequest

    , ResourceInfo)>, +} + +impl

    Service for AppService

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type Future = Either>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + if self.ready.is_none() { + Ok(Async::Ready(())) + } else { + Ok(Async::NotReady) + } + } + + fn call(&mut self, mut req: ServiceRequest

    ) -> Self::Future { + if let Some((srv, _info)) = self.router.recognize_mut(req.match_info_mut()) { + Either::A(srv.call(req)) + } else { + let req = req.into_request(); + Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + } + } +} + +pub struct AppServiceResponse(Box>); + +impl Future for AppServiceResponse { + type Item = ServiceResponse; + type Error = (); + + fn poll(&mut self) -> Poll { + self.0.poll().map_err(|_| panic!()) + } +} + +struct HttpNewService>>(T); + +impl HttpNewService +where + T: NewService, Response = ServiceResponse, Error = ()>, + T::Future: 'static, + ::Future: 'static, +{ + pub fn new(service: T) -> Self { + HttpNewService(service) + } +} + +impl NewService for HttpNewService +where + T: NewService, Response = ServiceResponse, Error = ()>, + T::Future: 'static, + T::Service: 'static, + ::Future: 'static, +{ + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = BoxedHttpService, Self::Response>; + type Future = Box>; + + fn new_service(&self, _: &()) -> Self::Future { + Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { + let service: BoxedHttpService<_, _> = Box::new(HttpServiceWrapper { + service, + _t: PhantomData, + }); + Ok(service) + })) + } +} + +struct HttpServiceWrapper { + service: T, + _t: PhantomData<(P,)>, +} + +impl Service for HttpServiceWrapper +where + T::Future: 'static, + T: Service, Response = ServiceResponse, Error = ()>, +{ + type Request = ServiceRequest

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

    ) -> Self::Future { + Box::new(self.service.call(req)) + } +} + +#[doc(hidden)] +pub struct AppEntry

    { + factory: Rc>>>, +} + +impl

    AppEntry

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

    NewService for AppEntry

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = AppService

    ; + type Future = CreateAppService

    ; + + fn new_service(&self, _: &()) -> Self::Future { + self.factory.borrow_mut().as_mut().unwrap().new_service(&()) + } +} diff --git a/src/application.rs b/src/application.rs index d8a6cbe7..6ca4ce28 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,17 +1,21 @@ use std::rc::Rc; +use actix_http::http::ContentEncoding; +use actix_http::{Error, Request, Response}; +use actix_service::Service; +use futures::{Async, Future, Poll}; + use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler}; -use header::ContentEncoding; use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use middleware::Middleware; -use pipeline::{Pipeline, PipelineHandler}; +// use middleware::Middleware; +// use pipeline::{Pipeline, PipelineHandler}; use pred::Predicate; use resource::Resource; use router::{ResourceDef, Router}; -use scope::Scope; -use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; +// use scope::Scope; +// use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; use with::WithFactory; /// Application @@ -21,7 +25,7 @@ pub struct HttpApplication { prefix_len: usize, inner: Rc>, filters: Option>>>, - middlewares: Rc>>>, + // middlewares: Rc>>>, } #[doc(hidden)] @@ -30,16 +34,16 @@ pub struct Inner { encoding: ContentEncoding, } -impl PipelineHandler for Inner { - #[inline] - fn encoding(&self) -> ContentEncoding { - self.encoding - } +// impl PipelineHandler for Inner { +// #[inline] +// fn encoding(&self) -> ContentEncoding { +// self.encoding +// } - fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.router.handle(req) - } -} +// fn handle(&self, req: &HttpRequest) -> AsyncResult { +// self.router.handle(req) +// } +// } impl HttpApplication { #[cfg(test)] @@ -54,10 +58,18 @@ impl HttpApplication { } } -impl HttpHandler for HttpApplication { - type Task = Pipeline>; +impl Service for HttpApplication { + // type Task = Pipeline>; + type Request = actix_http::Request; + type Response = actix_http::Response; + type Error = Error; + type Future = Box>; - fn handle(&self, msg: Request) -> Result>, Request> { + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, msg: actix_http::Request) -> Self::Future { let m = { if self.prefix_len == 0 { true @@ -70,11 +82,12 @@ impl HttpHandler for HttpApplication { }; if m { if let Some(ref filters) = self.filters { - for filter in filters { - if !filter.check(&msg, &self.state) { - return Err(msg); - } - } + //for filter in filters { + // if !filter.check(&msg, &self.state) { + //return Err(msg); + unimplemented!() + // } + //} } let info = self @@ -83,10 +96,12 @@ impl HttpHandler for HttpApplication { .recognize(&msg, &self.state, self.prefix_len); let inner = Rc::clone(&self.inner); - let req = HttpRequest::new(msg, Rc::clone(&self.state), info); - Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner)) + // let req = HttpRequest::new(msg, Rc::clone(&self.state), info); + // Ok(Pipeline::new(req, inner)) + unimplemented!() } else { - Err(msg) + // Err(msg) + unimplemented!() } } } @@ -96,7 +111,7 @@ struct ApplicationParts { prefix: String, router: Router, encoding: ContentEncoding, - middlewares: Vec>>, + // middlewares: Vec>>, filters: Vec>>, } @@ -106,55 +121,10 @@ pub struct App { parts: Option>, } -impl App<()> { - /// Create application with empty state. Application can - /// be configured with a builder-like pattern. - pub fn new() -> App<()> { - App::with_state(()) - } -} - -impl Default for App<()> { - fn default() -> Self { - App::new() - } -} - impl App where S: 'static, { - /// Create application with specified state. Application can be - /// configured with a builder-like pattern. - /// - /// State is shared with all resources within same application and - /// could be accessed with `HttpRequest::state()` method. - /// - /// **Note**: http server accepts an application factory rather than - /// an application instance. Http server constructs an application - /// instance for each thread, thus application state must be constructed - /// multiple times. If you want to share state between different - /// threads, a shared object should be used, e.g. `Arc`. Application - /// state does not need to be `Send` or `Sync`. - pub fn with_state(state: S) -> App { - App { - parts: Some(ApplicationParts { - state, - prefix: "".to_owned(), - router: Router::new(ResourceDef::prefix("")), - middlewares: Vec::new(), - filters: Vec::new(), - encoding: ContentEncoding::Auto, - }), - } - } - - /// Get reference to the application state - pub fn state(&self) -> &S { - let parts = self.parts.as_ref().expect("Use after finish"); - &parts.state - } - /// Set application prefix. /// /// Only requests that match the application's prefix get @@ -205,26 +175,6 @@ where self } - /// Add match predicate to application. - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn main() { - /// App::new() - /// .filter(pred::Host("www.rust-lang.org")) - /// .resource("/path", |r| r.f(|_| HttpResponse::Ok())) - /// # .finish(); - /// # } - /// ``` - pub fn filter + 'static>(mut self, p: T) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.filters.push(Box::new(p)); - } - self - } - /// Configure route for a specific path. /// /// This is a simplified version of the `App::resource()` method. @@ -263,42 +213,42 @@ where self } - /// Configure scope for common root path. - /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/{project_id}", |scope| { - /// scope - /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) - /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) - /// }); - /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(mut self, path: &str, f: F) -> App - where - F: FnOnce(Scope) -> Scope, - { - let scope = f(Scope::new(path)); - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_scope(scope); - self - } + // /// Configure scope for common root path. + // /// + // /// Scopes collect multiple paths under a common path prefix. + // /// Scope path can contain variable path segments as resources. + // /// + // /// ```rust + // /// # extern crate actix_web; + // /// use actix_web::{http, App, HttpRequest, HttpResponse}; + // /// + // /// fn main() { + // /// let app = App::new().scope("/{project_id}", |scope| { + // /// scope + // /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + // /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) + // /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) + // /// }); + // /// } + // /// ``` + // /// + // /// In the above example, three routes get added: + // /// * /{project_id}/path1 + // /// * /{project_id}/path2 + // /// * /{project_id}/path3 + // /// + // pub fn scope(mut self, path: &str, f: F) -> App + // where + // F: FnOnce(Scope) -> Scope, + // { + // let scope = f(Scope::new(path)); + // self.parts + // .as_mut() + // .expect("Use after finish") + // .router + // .register_scope(scope); + // self + // } /// Configure resource for a specific path. /// @@ -377,51 +327,6 @@ where self } - /// Set default content encoding. `ContentEncoding::Auto` is set by default. - pub fn default_encoding(mut self, encoding: ContentEncoding) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - parts.encoding = encoding; - } - self - } - - /// Register an external resource. - /// - /// External resources are useful for URL generation purposes only - /// and are never considered for matching at request time. Calls to - /// `HttpRequest::url_for()` will work as expected. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest, HttpResponse, Result}; - /// - /// fn index(req: &HttpRequest) -> Result { - /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; - /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); - /// Ok(HttpResponse::Ok().into()) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .resource("/index.html", |r| r.get().f(index)) - /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") - /// .finish(); - /// } - /// ``` - pub fn external_resource(mut self, name: T, url: U) -> App - where - T: AsRef, - U: AsRef, - { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_external(name.as_ref(), ResourceDef::external(url.as_ref())); - self - } - /// Configure handler for specific path prefix. /// /// A path prefix consists of valid path segments, i.e for the @@ -458,15 +363,15 @@ where self } - /// Register a middleware. - pub fn middleware>(mut self, mw: M) -> App { - self.parts - .as_mut() - .expect("Use after finish") - .middlewares - .push(Box::new(mw)); - self - } + // /// Register a middleware. + // pub fn middleware>(mut self, mw: M) -> App { + // self.parts + // .as_mut() + // .expect("Use after finish") + // .middlewares + // .push(Box::new(mw)); + // self + // } /// Run external configuration as part of the application building /// process @@ -521,93 +426,93 @@ where inner, filters, state: Rc::new(parts.state), - middlewares: Rc::new(parts.middlewares), + // middlewares: Rc::new(parts.middlewares), prefix: prefix.to_owned(), prefix_len: prefix.len(), } } - /// Convenience method for creating `Box` instances. - /// - /// This method is useful if you need to register multiple - /// application instances with different state. - /// - /// ```rust - /// # use std::thread; - /// # extern crate actix_web; - /// use actix_web::{server, App, HttpResponse}; - /// - /// struct State1; - /// - /// struct State2; - /// - /// fn main() { - /// # thread::spawn(|| { - /// server::new(|| { - /// vec![ - /// App::with_state(State1) - /// .prefix("/app1") - /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - /// .boxed(), - /// App::with_state(State2) - /// .prefix("/app2") - /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - /// .boxed(), - /// ] - /// }).bind("127.0.0.1:8080") - /// .unwrap() - /// .run() - /// # }); - /// } - /// ``` - pub fn boxed(mut self) -> Box>> { - Box::new(BoxedApplication { app: self.finish() }) - } + // /// Convenience method for creating `Box` instances. + // /// + // /// This method is useful if you need to register multiple + // /// application instances with different state. + // /// + // /// ```rust + // /// # use std::thread; + // /// # extern crate actix_web; + // /// use actix_web::{server, App, HttpResponse}; + // /// + // /// struct State1; + // /// + // /// struct State2; + // /// + // /// fn main() { + // /// # thread::spawn(|| { + // /// server::new(|| { + // /// vec![ + // /// App::with_state(State1) + // /// .prefix("/app1") + // /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) + // /// .boxed(), + // /// App::with_state(State2) + // /// .prefix("/app2") + // /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) + // /// .boxed(), + // /// ] + // /// }).bind("127.0.0.1:8080") + // /// .unwrap() + // /// .run() + // /// # }); + // /// } + // /// ``` + // pub fn boxed(mut self) -> Box>> { + // Box::new(BoxedApplication { app: self.finish() }) + // } } -struct BoxedApplication { - app: HttpApplication, -} +// struct BoxedApplication { +// app: HttpApplication, +// } -impl HttpHandler for BoxedApplication { - type Task = Box; +// impl HttpHandler for BoxedApplication { +// type Task = Box; - fn handle(&self, req: Request) -> Result { - self.app.handle(req).map(|t| { - let task: Self::Task = Box::new(t); - task - }) - } -} +// fn handle(&self, req: Request) -> Result { +// self.app.handle(req).map(|t| { +// let task: Self::Task = Box::new(t); +// task +// }) +// } +// } -impl IntoHttpHandler for App { - type Handler = HttpApplication; +// impl IntoHttpHandler for App { +// type Handler = HttpApplication; - fn into_handler(mut self) -> HttpApplication { - self.finish() - } -} +// fn into_handler(mut self) -> HttpApplication { +// self.finish() +// } +// } -impl<'a, S: 'static> IntoHttpHandler for &'a mut App { - type Handler = HttpApplication; +// impl<'a, S: 'static> IntoHttpHandler for &'a mut App { +// type Handler = HttpApplication; - fn into_handler(self) -> HttpApplication { - self.finish() - } -} +// fn into_handler(self) -> HttpApplication { +// self.finish() +// } +// } -#[doc(hidden)] -impl Iterator for App { - type Item = HttpApplication; +// #[doc(hidden)] +// impl Iterator for App { +// type Item = HttpApplication; - fn next(&mut self) -> Option { - if self.parts.is_some() { - Some(self.finish()) - } else { - None - } - } -} +// fn next(&mut self) -> Option { +// if self.parts.is_some() { +// Some(self.finish()) +// } else { +// None +// } +// } +// } #[cfg(test)] mod tests { @@ -773,7 +678,8 @@ mod tests { .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) .route("/test", Method::POST, |_: HttpRequest| { HttpResponse::Created() - }).finish(); + }) + .finish(); let req = TestRequest::with_uri("/test").method(Method::GET).request(); let resp = app.run(req); diff --git a/src/blocking.rs b/src/blocking.rs new file mode 100644 index 00000000..fc2624f6 --- /dev/null +++ b/src/blocking.rs @@ -0,0 +1,74 @@ +//! Thread pool for blocking operations + +use futures::sync::oneshot; +use futures::{Async, Future, Poll}; +use parking_lot::Mutex; +use threadpool::ThreadPool; + +/// Env variable for default cpu pool size +const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL"; + +lazy_static::lazy_static! { + pub(crate) static ref DEFAULT_POOL: Mutex = { + let default = match std::env::var(ENV_CPU_POOL_VAR) { + Ok(val) => { + if let Ok(val) = val.parse() { + val + } else { + log::error!("Can not parse ACTIX_CPU_POOL value"); + num_cpus::get() * 5 + } + } + Err(_) => num_cpus::get() * 5, + }; + Mutex::new( + threadpool::Builder::new() + .thread_name("actix-web".to_owned()) + .num_threads(8) + .build(), + ) + }; +} + +thread_local! { + static POOL: ThreadPool = { + DEFAULT_POOL.lock().clone() + }; +} + +pub enum BlockingError { + Error(E), + Canceled, +} + +/// Execute blocking function on a thread pool, returns future that resolves +/// to result of the function execution. +pub fn run(f: F) -> CpuFuture +where + F: FnOnce() -> Result, +{ + let (tx, rx) = oneshot::channel(); + POOL.with(move |pool| { + let _ = tx.send(f()); + }); + + CpuFuture { rx } +} + +pub struct CpuFuture { + rx: oneshot::Receiver>, +} + +impl Future for CpuFuture { + type Item = I; + type Error = BlockingError; + + fn poll(&mut self) -> Poll { + let res = + futures::try_ready!(self.rx.poll().map_err(|_| BlockingError::Canceled)); + match res { + Ok(val) => Ok(Async::Ready(val)), + Err(err) => Err(BlockingError::Error(err)), + } + } +} diff --git a/src/body.rs b/src/body.rs deleted file mode 100644 index 5487dbba..00000000 --- a/src/body.rs +++ /dev/null @@ -1,391 +0,0 @@ -use bytes::{Bytes, BytesMut}; -use futures::Stream; -use std::borrow::Cow; -use std::sync::Arc; -use std::{fmt, mem}; - -use context::ActorHttpContext; -use error::Error; -use handler::Responder; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -/// Type represent streaming body -pub type BodyStream = Box>; - -/// Represents various types of http message body. -pub enum Body { - /// Empty response. `Content-Length` header is set to `0` - Empty, - /// Specific response body. - Binary(Binary), - /// Unspecified streaming response. Developer is responsible for setting - /// right `Content-Length` or `Transfer-Encoding` headers. - Streaming(BodyStream), - /// Special body type for actor response. - Actor(Box), -} - -/// Represents various types of binary body. -/// `Content-Length` header is set to length of the body. -#[derive(Debug, PartialEq)] -pub enum Binary { - /// Bytes body - Bytes(Bytes), - /// Static slice - Slice(&'static [u8]), - /// Shared string body - #[doc(hidden)] - SharedString(Arc), - /// Shared vec body - SharedVec(Arc>), -} - -impl Body { - /// Does this body streaming. - #[inline] - pub fn is_streaming(&self) -> bool { - match *self { - Body::Streaming(_) | Body::Actor(_) => true, - _ => false, - } - } - - /// Is this binary body. - #[inline] - pub fn is_binary(&self) -> bool { - match *self { - Body::Binary(_) => true, - _ => false, - } - } - - /// Is this binary empy. - #[inline] - pub fn is_empty(&self) -> bool { - match *self { - Body::Empty => true, - _ => false, - } - } - - /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> Body { - Body::Binary(Binary::Bytes(Bytes::from(s))) - } - - /// Is this binary body. - #[inline] - pub(crate) fn binary(self) -> Binary { - match self { - Body::Binary(b) => b, - _ => panic!(), - } - } -} - -impl PartialEq for Body { - fn eq(&self, other: &Body) -> bool { - match *self { - Body::Empty => match *other { - Body::Empty => true, - _ => false, - }, - Body::Binary(ref b) => match *other { - Body::Binary(ref b2) => b == b2, - _ => false, - }, - Body::Streaming(_) | Body::Actor(_) => false, - } - } -} - -impl fmt::Debug for Body { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Body::Empty => write!(f, "Body::Empty"), - Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b), - Body::Streaming(_) => write!(f, "Body::Streaming(_)"), - Body::Actor(_) => write!(f, "Body::Actor(_)"), - } - } -} - -impl From for Body -where - T: Into, -{ - fn from(b: T) -> Body { - Body::Binary(b.into()) - } -} - -impl From> for Body { - fn from(ctx: Box) -> Body { - Body::Actor(ctx) - } -} - -impl Binary { - #[inline] - /// Returns `true` if body is empty - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - #[inline] - /// Length of body in bytes - pub fn len(&self) -> usize { - match *self { - Binary::Bytes(ref bytes) => bytes.len(), - Binary::Slice(slice) => slice.len(), - Binary::SharedString(ref s) => s.len(), - Binary::SharedVec(ref s) => s.len(), - } - } - - /// Create binary body from slice - pub fn from_slice(s: &[u8]) -> Binary { - Binary::Bytes(Bytes::from(s)) - } - - /// Convert Binary to a Bytes instance - pub fn take(&mut self) -> Bytes { - mem::replace(self, Binary::Slice(b"")).into() - } -} - -impl Clone for Binary { - fn clone(&self) -> Binary { - match *self { - Binary::Bytes(ref bytes) => Binary::Bytes(bytes.clone()), - Binary::Slice(slice) => Binary::Bytes(Bytes::from(slice)), - Binary::SharedString(ref s) => Binary::SharedString(s.clone()), - Binary::SharedVec(ref s) => Binary::SharedVec(s.clone()), - } - } -} - -impl Into for Binary { - fn into(self) -> Bytes { - match self { - Binary::Bytes(bytes) => bytes, - Binary::Slice(slice) => Bytes::from(slice), - Binary::SharedString(s) => Bytes::from(s.as_str()), - Binary::SharedVec(s) => Bytes::from(AsRef::<[u8]>::as_ref(s.as_ref())), - } - } -} - -impl From<&'static str> for Binary { - fn from(s: &'static str) -> Binary { - Binary::Slice(s.as_ref()) - } -} - -impl From<&'static [u8]> for Binary { - fn from(s: &'static [u8]) -> Binary { - Binary::Slice(s) - } -} - -impl From> for Binary { - fn from(vec: Vec) -> Binary { - Binary::Bytes(Bytes::from(vec)) - } -} - -impl From> for Binary { - fn from(b: Cow<'static, [u8]>) -> Binary { - match b { - Cow::Borrowed(s) => Binary::Slice(s), - Cow::Owned(vec) => Binary::Bytes(Bytes::from(vec)), - } - } -} - -impl From for Binary { - fn from(s: String) -> Binary { - Binary::Bytes(Bytes::from(s)) - } -} - -impl From> for Binary { - fn from(s: Cow<'static, str>) -> Binary { - match s { - Cow::Borrowed(s) => Binary::Slice(s.as_ref()), - Cow::Owned(s) => Binary::Bytes(Bytes::from(s)), - } - } -} - -impl<'a> From<&'a String> for Binary { - fn from(s: &'a String) -> Binary { - Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s))) - } -} - -impl From for Binary { - fn from(s: Bytes) -> Binary { - Binary::Bytes(s) - } -} - -impl From for Binary { - fn from(s: BytesMut) -> Binary { - Binary::Bytes(s.freeze()) - } -} - -impl From> for Binary { - fn from(body: Arc) -> Binary { - Binary::SharedString(body) - } -} - -impl<'a> From<&'a Arc> for Binary { - fn from(body: &'a Arc) -> Binary { - Binary::SharedString(Arc::clone(body)) - } -} - -impl From>> for Binary { - fn from(body: Arc>) -> Binary { - Binary::SharedVec(body) - } -} - -impl<'a> From<&'a Arc>> for Binary { - fn from(body: &'a Arc>) -> Binary { - Binary::SharedVec(Arc::clone(body)) - } -} - -impl AsRef<[u8]> for Binary { - #[inline] - fn as_ref(&self) -> &[u8] { - match *self { - Binary::Bytes(ref bytes) => bytes.as_ref(), - Binary::Slice(slice) => slice, - Binary::SharedString(ref s) => s.as_bytes(), - Binary::SharedVec(ref s) => s.as_ref().as_ref(), - } - } -} - -impl Responder for Binary { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(HttpResponse::build_from(req) - .content_type("application/octet-stream") - .body(self)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_body_is_streaming() { - assert_eq!(Body::Empty.is_streaming(), false); - assert_eq!(Body::Binary(Binary::from("")).is_streaming(), false); - } - - #[test] - fn test_is_empty() { - assert_eq!(Binary::from("").is_empty(), true); - assert_eq!(Binary::from("test").is_empty(), false); - } - - #[test] - fn test_static_str() { - assert_eq!(Binary::from("test").len(), 4); - assert_eq!(Binary::from("test").as_ref(), b"test"); - } - - #[test] - fn test_cow_str() { - let cow: Cow<'static, str> = Cow::Borrowed("test"); - assert_eq!(Binary::from(cow.clone()).len(), 4); - assert_eq!(Binary::from(cow.clone()).as_ref(), b"test"); - let cow: Cow<'static, str> = Cow::Owned("test".to_owned()); - assert_eq!(Binary::from(cow.clone()).len(), 4); - assert_eq!(Binary::from(cow.clone()).as_ref(), b"test"); - } - - #[test] - fn test_static_bytes() { - assert_eq!(Binary::from(b"test".as_ref()).len(), 4); - assert_eq!(Binary::from(b"test".as_ref()).as_ref(), b"test"); - assert_eq!(Binary::from_slice(b"test".as_ref()).len(), 4); - assert_eq!(Binary::from_slice(b"test".as_ref()).as_ref(), b"test"); - } - - #[test] - fn test_vec() { - assert_eq!(Binary::from(Vec::from("test")).len(), 4); - assert_eq!(Binary::from(Vec::from("test")).as_ref(), b"test"); - } - - #[test] - fn test_bytes() { - assert_eq!(Binary::from(Bytes::from("test")).len(), 4); - assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test"); - } - - #[test] - fn test_cow_bytes() { - let cow: Cow<'static, [u8]> = Cow::Borrowed(b"test"); - assert_eq!(Binary::from(cow.clone()).len(), 4); - assert_eq!(Binary::from(cow.clone()).as_ref(), b"test"); - let cow: Cow<'static, [u8]> = Cow::Owned(Vec::from("test")); - assert_eq!(Binary::from(cow.clone()).len(), 4); - assert_eq!(Binary::from(cow.clone()).as_ref(), b"test"); - } - - #[test] - fn test_arc_string() { - let b = Arc::new("test".to_owned()); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), b"test"); - } - - #[test] - fn test_string() { - let b = "test".to_owned(); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), b"test"); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), b"test"); - } - - #[test] - fn test_shared_vec() { - let b = Arc::new(Vec::from(&b"test"[..])); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b.clone()).as_ref(), &b"test"[..]); - assert_eq!(Binary::from(&b).len(), 4); - assert_eq!(Binary::from(&b).as_ref(), &b"test"[..]); - } - - #[test] - fn test_bytes_mut() { - let b = BytesMut::from("test"); - assert_eq!(Binary::from(b.clone()).len(), 4); - assert_eq!(Binary::from(b).as_ref(), b"test"); - } - - #[test] - fn test_binary_into() { - let bytes = Bytes::from_static(b"test"); - let b: Bytes = Binary::from("test").into(); - assert_eq!(b, bytes); - let b: Bytes = Binary::from(bytes.clone()).into(); - assert_eq!(b, bytes); - } -} diff --git a/src/client/connector.rs b/src/client/connector.rs deleted file mode 100644 index 1d062302..00000000 --- a/src/client/connector.rs +++ /dev/null @@ -1,1340 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use std::net::Shutdown; -use std::time::{Duration, Instant}; -use std::{fmt, io, mem, time}; - -use actix_inner::actors::resolver::{Connect as ResolveConnect, Resolver, ResolverError}; -use actix_inner::{ - fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context, - ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised, - SystemService, WrapFuture, -}; - -use futures::sync::{mpsc, oneshot}; -use futures::{Async, Future, Poll}; -use http::{Error as HttpError, HttpTryFrom, Uri}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -#[cfg(any(feature = "alpn", feature = "ssl"))] -use { - openssl::ssl::{Error as SslError, SslConnector, SslMethod}, - tokio_openssl::SslConnectorExt, -}; - -#[cfg(all( - feature = "tls", - not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) -))] -use { - native_tls::{Error as SslError, TlsConnector as NativeTlsConnector}, - tokio_tls::TlsConnector as SslConnector, -}; - -#[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) -))] -use { - rustls::ClientConfig, std::io::Error as SslError, std::sync::Arc, - tokio_rustls::TlsConnector as SslConnector, webpki::DNSNameRef, webpki_roots, -}; - -#[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls" -)))] -type SslConnector = (); - -use server::IoStream; -use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS}; - -/// Client connector usage stats -#[derive(Default, Message)] -pub struct ClientConnectorStats { - /// Number of waited-on connections - pub waits: usize, - /// Size of the wait queue - pub wait_queue: usize, - /// Number of reused connections - pub reused: usize, - /// Number of opened connections - pub opened: usize, - /// Number of closed connections - pub closed: usize, - /// Number of connections with errors - pub errors: usize, - /// Number of connection timeouts - pub timeouts: usize, -} - -#[derive(Debug)] -/// `Connect` type represents a message that can be sent to -/// `ClientConnector` with a connection request. -pub struct Connect { - pub(crate) uri: Uri, - pub(crate) wait_timeout: Duration, - pub(crate) conn_timeout: Duration, -} - -impl Connect { - /// Create `Connect` message for specified `Uri` - pub fn new(uri: U) -> Result - where - Uri: HttpTryFrom, - { - Ok(Connect { - uri: Uri::try_from(uri).map_err(|e| e.into())?, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - }) - } - - /// Connection timeout, i.e. max time to connect to remote host. - /// Set to 1 second by default. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - self.conn_timeout = timeout; - self - } - - /// If connection pool limits are enabled, wait time indicates - /// max time to wait for a connection to become available. - /// Set to 5 seconds by default. - pub fn wait_timeout(mut self, timeout: Duration) -> Self { - self.wait_timeout = timeout; - self - } -} - -impl Message for Connect { - type Result = Result; -} - -/// Pause connection process for `ClientConnector` -/// -/// All connect requests enter wait state during connector pause. -pub struct Pause { - time: Option, -} - -impl Pause { - /// Create message with pause duration parameter - pub fn new(time: Duration) -> Pause { - Pause { time: Some(time) } - } -} - -impl Default for Pause { - fn default() -> Pause { - Pause { time: None } - } -} - -impl Message for Pause { - type Result = (); -} - -/// Resume connection process for `ClientConnector` -#[derive(Message)] -pub struct Resume; - -/// A set of errors that can occur while connecting to an HTTP host -#[derive(Fail, Debug)] -pub enum ClientConnectorError { - /// Invalid URL - #[fail(display = "Invalid URL")] - InvalidUrl, - - /// SSL feature is not enabled - #[fail(display = "SSL is not supported")] - SslIsNotSupported, - - /// SSL error - #[cfg(any( - feature = "tls", - feature = "alpn", - feature = "ssl", - feature = "rust-tls", - ))] - #[fail(display = "{}", _0)] - SslError(#[cause] SslError), - - /// Resolver error - #[fail(display = "{}", _0)] - Resolver(#[cause] ResolverError), - - /// Connection took too long - #[fail(display = "Timeout while establishing connection")] - Timeout, - - /// Connector has been disconnected - #[fail(display = "Internal error: connector has been disconnected")] - Disconnected, - - /// Connection IO error - #[fail(display = "{}", _0)] - IoError(#[cause] io::Error), -} - -impl From for ClientConnectorError { - fn from(err: ResolverError) -> ClientConnectorError { - match err { - ResolverError::Timeout => ClientConnectorError::Timeout, - _ => ClientConnectorError::Resolver(err), - } - } -} - -struct Waiter { - tx: oneshot::Sender>, - wait: Instant, - conn_timeout: Duration, -} - -enum Paused { - No, - Yes, - Timeout(Instant, Delay), -} - -impl Paused { - fn is_paused(&self) -> bool { - match *self { - Paused::No => false, - _ => true, - } - } -} - -/// `ClientConnector` type is responsible for transport layer of a -/// client connection. -pub struct ClientConnector { - #[allow(dead_code)] - connector: SslConnector, - - stats: ClientConnectorStats, - subscriber: Option>, - - acq_tx: mpsc::UnboundedSender, - acq_rx: Option>, - - resolver: Option>, - conn_lifetime: Duration, - conn_keep_alive: Duration, - limit: usize, - limit_per_host: usize, - acquired: usize, - acquired_per_host: HashMap, - available: HashMap>, - to_close: Vec, - waiters: Option>>, - wait_timeout: Option<(Instant, Delay)>, - paused: Paused, -} - -impl Actor for ClientConnector { - type Context = Context; - - fn started(&mut self, ctx: &mut Self::Context) { - if self.resolver.is_none() { - self.resolver = Some(Resolver::from_registry().recipient()) - } - self.collect_periodic(ctx); - ctx.add_stream(self.acq_rx.take().unwrap()); - ctx.spawn(Maintenance); - } -} - -impl Supervised for ClientConnector {} - -impl SystemService for ClientConnector {} - -impl Default for ClientConnector { - fn default() -> ClientConnector { - let connector = { - #[cfg(all(any(feature = "alpn", feature = "ssl")))] - { - SslConnector::builder(SslMethod::tls()).unwrap().build() - } - - #[cfg(all( - feature = "tls", - not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")) - ))] - { - NativeTlsConnector::builder().build().unwrap().into() - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "tls", feature = "ssl")) - ))] - { - let mut config = ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - SslConnector::from(Arc::new(config)) - } - - #[cfg_attr(rustfmt, rustfmt_skip)] - #[cfg(not(any( - feature = "alpn", feature = "ssl", feature = "tls", feature = "rust-tls")))] - { - () - } - }; - - #[cfg_attr(feature = "cargo-clippy", allow(let_unit_value))] - ClientConnector::with_connector_impl(connector) - } -} - -impl ClientConnector { - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust,ignore - /// # #![cfg(feature="alpn")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate openssl; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// use openssl::ssl::{SslConnector, SslMethod}; - /// - /// fn main() { - /// actix::run(|| { - /// // Start `ClientConnector` with custom `SslConnector` - /// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); - /// let conn = ClientConnector::with_connector(ssl_conn).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: SslConnector) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(connector) - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl", feature = "tls")) - ))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust - /// # #![cfg(feature = "rust-tls")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate rustls; - /// extern crate webpki_roots; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// use rustls::ClientConfig; - /// use std::sync::Arc; - /// - /// fn main() { - /// actix::run(|| { - /// // Start `ClientConnector` with custom `ClientConfig` - /// let mut config = ClientConfig::new(); - /// config - /// .root_store - /// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - /// let conn = ClientConnector::with_connector(config).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: ClientConfig) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(SslConnector::from(Arc::new(connector))) - } - - #[cfg(all( - feature = "tls", - not(any(feature = "ssl", feature = "alpn", feature = "rust-tls")) - ))] - /// Create `ClientConnector` actor with custom `SslConnector` instance. - /// - /// By default `ClientConnector` uses very a simple SSL configuration. - /// With `with_connector` method it is possible to use a custom - /// `SslConnector` object. - /// - /// ```rust - /// # #![cfg(feature = "tls")] - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::{future, Future}; - /// # use std::io::Write; - /// # use actix_web::actix::Actor; - /// extern crate native_tls; - /// extern crate webpki_roots; - /// use native_tls::TlsConnector; - /// use actix_web::{actix, client::ClientConnector, client::Connect}; - /// - /// fn main() { - /// actix::run(|| { - /// let connector = TlsConnector::new().unwrap(); - /// let conn = ClientConnector::with_connector(connector.into()).start(); - /// - /// conn.send( - /// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host - /// .map_err(|_| ()) - /// .and_then(|res| { - /// if let Ok(mut stream) = res { - /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); - /// } - /// # actix::System::current().stop(); - /// Ok(()) - /// }) - /// }); - /// } - /// ``` - pub fn with_connector(connector: SslConnector) -> ClientConnector { - // keep level of indirection for docstrings matching featureflags - Self::with_connector_impl(connector) - } - - #[inline] - fn with_connector_impl(connector: SslConnector) -> ClientConnector { - let (tx, rx) = mpsc::unbounded(); - - ClientConnector { - connector, - stats: ClientConnectorStats::default(), - subscriber: None, - acq_tx: tx, - acq_rx: Some(rx), - resolver: None, - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - limit: 100, - limit_per_host: 0, - acquired: 0, - acquired_per_host: HashMap::new(), - available: HashMap::new(), - to_close: Vec::new(), - waiters: Some(HashMap::new()), - wait_timeout: None, - paused: Paused::No, - } - } - - /// Set total number of simultaneous connections. - /// - /// If limit is 0, the connector has no limit. - /// The default limit size is 100. - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set total number of simultaneous connections to the same endpoint. - /// - /// Endpoints are the same if they have equal (host, port, ssl) triplets. - /// If limit is 0, the connector has no limit. The default limit size is 0. - pub fn limit_per_host(mut self, limit: usize) -> Self { - self.limit_per_host = limit; - self - } - - /// Set keep-alive period for opened connection. - /// - /// Keep-alive period is the period between connection usage. If - /// the delay between repeated usages of the same connection - /// exceeds this period, the connection is closed. - /// Default keep-alive period is 15 seconds. - pub fn conn_keep_alive(mut self, dur: Duration) -> Self { - self.conn_keep_alive = dur; - self - } - - /// Set max lifetime period for connection. - /// - /// Connection lifetime is max lifetime of any opened connection - /// until it is closed regardless of keep-alive period. - /// Default lifetime period is 75 seconds. - pub fn conn_lifetime(mut self, dur: Duration) -> Self { - self.conn_lifetime = dur; - self - } - - /// Subscribe for connector stats. Only one subscriber is supported. - pub fn stats(mut self, subs: Recipient) -> Self { - self.subscriber = Some(subs); - self - } - - /// Use custom resolver actor - /// - /// By default actix's Resolver is used. - pub fn resolver>>(mut self, addr: A) -> Self { - self.resolver = Some(addr.into()); - self - } - - fn acquire(&mut self, key: &Key) -> Acquire { - // check limits - if self.limit > 0 { - if self.acquired >= self.limit { - return Acquire::NotAvailable; - } - if self.limit_per_host > 0 { - if let Some(per_host) = self.acquired_per_host.get(key) { - if *per_host >= self.limit_per_host { - return Acquire::NotAvailable; - } - } - } - } else if self.limit_per_host > 0 { - if let Some(per_host) = self.acquired_per_host.get(key) { - if *per_host >= self.limit_per_host { - return Acquire::NotAvailable; - } - } - } - - self.reserve(key); - - // check if open connection is available - // cleanup stale connections at the same time - if let Some(ref mut connections) = self.available.get_mut(key) { - let now = Instant::now(); - while let Some(conn) = connections.pop_back() { - // check if it still usable - if (now - conn.0) > self.conn_keep_alive - || (now - conn.1.ts) > self.conn_lifetime - { - self.stats.closed += 1; - self.to_close.push(conn.1); - } else { - let mut conn = conn.1; - let mut buf = [0; 2]; - match conn.stream().read(&mut buf) { - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Ok(n) if n > 0 => { - self.stats.closed += 1; - self.to_close.push(conn); - continue; - } - Ok(_) | Err(_) => continue, - } - return Acquire::Acquired(conn); - } - } - } - Acquire::Available - } - - fn reserve(&mut self, key: &Key) { - self.acquired += 1; - let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - 0 - }; - self.acquired_per_host.insert(key.clone(), per_host + 1); - } - - fn release_key(&mut self, key: &Key) { - if self.acquired > 0 { - self.acquired -= 1; - } - let per_host = if let Some(per_host) = self.acquired_per_host.get(key) { - *per_host - } else { - return; - }; - if per_host > 1 { - self.acquired_per_host.insert(key.clone(), per_host - 1); - } else { - self.acquired_per_host.remove(key); - } - } - - fn collect_periodic(&mut self, ctx: &mut Context) { - // check connections for shutdown - let mut idx = 0; - while idx < self.to_close.len() { - match AsyncWrite::shutdown(&mut self.to_close[idx]) { - Ok(Async::NotReady) => idx += 1, - _ => { - self.to_close.swap_remove(idx); - } - } - } - - // re-schedule next collect period - ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect_periodic(ctx)); - - // send stats - let mut stats = mem::replace(&mut self.stats, ClientConnectorStats::default()); - if let Some(ref mut subscr) = self.subscriber { - if let Some(ref waiters) = self.waiters { - for w in waiters.values() { - stats.wait_queue += w.len(); - } - } - let _ = subscr.do_send(stats); - } - } - - // TODO: waiters should be sorted by deadline. maybe timewheel? - fn collect_waiters(&mut self) { - let now = Instant::now(); - let mut next = None; - - for waiters in self.waiters.as_mut().unwrap().values_mut() { - let mut idx = 0; - while idx < waiters.len() { - let wait = waiters[idx].wait; - if wait <= now { - self.stats.timeouts += 1; - let waiter = waiters.swap_remove_back(idx).unwrap(); - let _ = waiter.tx.send(Err(ClientConnectorError::Timeout)); - } else { - if let Some(n) = next { - if wait < n { - next = Some(wait); - } - } else { - next = Some(wait); - } - idx += 1; - } - } - } - - if next.is_some() { - self.install_wait_timeout(next.unwrap()); - } - } - - fn install_wait_timeout(&mut self, time: Instant) { - if let Some(ref mut wait) = self.wait_timeout { - if wait.0 < time { - return; - } - } - - let mut timeout = Delay::new(time); - let _ = timeout.poll(); - self.wait_timeout = Some((time, timeout)); - } - - fn wait_for( - &mut self, key: Key, wait: Duration, conn_timeout: Duration, - ) -> oneshot::Receiver> { - // connection is not available, wait - let (tx, rx) = oneshot::channel(); - - let wait = Instant::now() + wait; - self.install_wait_timeout(wait); - - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.waiters - .as_mut() - .unwrap() - .entry(key) - .or_insert_with(VecDeque::new) - .push_back(waiter); - rx - } - - fn check_availibility(&mut self, ctx: &mut Context) { - // check waiters - let mut act_waiters = self.waiters.take().unwrap(); - - for (key, ref mut waiters) in &mut act_waiters { - while let Some(waiter) = waiters.pop_front() { - if waiter.tx.is_canceled() { - continue; - } - - match self.acquire(key) { - Acquire::Acquired(mut conn) => { - // use existing connection - self.stats.reused += 1; - conn.pool = - Some(AcquiredConn(key.clone(), Some(self.acq_tx.clone()))); - let _ = waiter.tx.send(Ok(conn)); - } - Acquire::NotAvailable => { - waiters.push_front(waiter); - break; - } - Acquire::Available => { - // create new connection - self.connect_waiter(&key, waiter, ctx); - } - } - } - } - - self.waiters = Some(act_waiters); - } - - fn connect_waiter(&mut self, key: &Key, waiter: Waiter, ctx: &mut Context) { - let key = key.clone(); - let conn = AcquiredConn(key.clone(), Some(self.acq_tx.clone())); - - let key2 = key.clone(); - fut::WrapFuture::::actfuture( - self.resolver.as_ref().unwrap().send( - ResolveConnect::host_and_port(&conn.0.host, conn.0.port) - .timeout(waiter.conn_timeout), - ), - ).map_err(move |_, act, _| { - act.release_key(&key2); - () - }).and_then(move |res, act, _| { - #[cfg(any(feature = "alpn", feature = "ssl"))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect_async(&key.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all(feature = "tls", not(any(feature = "alpn", feature = "ssl"))))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - fut::Either::A( - act.connector - .connect(&conn.0.host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl", feature = "tls")) - ))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::Either::B(fut::err(())) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let host = DNSNameRef::try_from_ascii_str(&key.host).unwrap(); - fut::Either::A( - act.connector - .connect(host, stream) - .into_actor(act) - .then(move |res, _, _| { - match res { - Err(e) => { - let _ = waiter.tx.send(Err( - ClientConnectorError::SslError(e), - )); - } - Ok(stream) => { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - } - } - fut::ok(()) - }), - ) - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - fut::Either::B(fut::ok(())) - } - } - } - - #[cfg(not(any( - feature = "alpn", - feature = "ssl", - feature = "tls", - feature = "rust-tls" - )))] - match res { - Err(err) => { - let _ = waiter.tx.send(Err(err.into())); - fut::err(()) - } - Ok(stream) => { - act.stats.opened += 1; - if conn.0.ssl { - let _ = - waiter.tx.send(Err(ClientConnectorError::SslIsNotSupported)); - } else { - let _ = waiter.tx.send(Ok(Connection::new( - conn.0.clone(), - Some(conn), - Box::new(stream), - ))); - }; - fut::ok(()) - } - } - }).spawn(ctx); - } -} - -impl Handler for ClientConnector { - type Result = (); - - fn handle(&mut self, msg: Pause, _: &mut Self::Context) { - if let Some(time) = msg.time { - let when = Instant::now() + time; - let mut timeout = Delay::new(when); - let _ = timeout.poll(); - self.paused = Paused::Timeout(when, timeout); - } else { - self.paused = Paused::Yes; - } - } -} - -impl Handler for ClientConnector { - type Result = (); - - fn handle(&mut self, _: Resume, _: &mut Self::Context) { - self.paused = Paused::No; - } -} - -impl Handler for ClientConnector { - type Result = ActorResponse; - - fn handle(&mut self, msg: Connect, ctx: &mut Self::Context) -> Self::Result { - let uri = &msg.uri; - let wait_timeout = msg.wait_timeout; - let conn_timeout = msg.conn_timeout; - - // host name is required - if uri.host().is_none() { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)); - } - - // supported protocols - let proto = match uri.scheme_part() { - Some(scheme) => match Protocol::from(scheme.as_str()) { - Some(proto) => proto, - None => { - return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)) - } - }, - None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)), - }; - - // check ssl availability - if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS && !HAS_RUSTLS { - return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported)); - } - - let host = uri.host().unwrap().to_owned(); - let port = uri.port_part().map(|port| port.as_u16()).unwrap_or_else(|| proto.port()); - let key = Key { - host, - port, - ssl: proto.is_secure(), - }; - - // check pause state - if self.paused.is_paused() { - let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); - self.stats.waits += 1; - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - match err { - ClientConnectorError::Timeout => (), - _ => { - act.release_key(&key); - } - } - act.stats.errors += 1; - act.check_availibility(ctx); - fut::err(err) - } - }), - ); - } - - // do not re-use websockets connection - if !proto.is_http() { - let (tx, rx) = oneshot::channel(); - let wait = Instant::now() + wait_timeout; - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.connect_waiter(&key, waiter, ctx); - - return ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - act.stats.errors += 1; - act.release_key(&key); - act.check_availibility(ctx); - fut::err(err) - } - }), - ); - } - - // acquire connection - match self.acquire(&key) { - Acquire::Acquired(mut conn) => { - // use existing connection - conn.pool = Some(AcquiredConn(key, Some(self.acq_tx.clone()))); - self.stats.reused += 1; - ActorResponse::async(fut::ok(conn)) - } - Acquire::NotAvailable => { - // connection is not available, wait - let rx = self.wait_for(key.clone(), wait_timeout, conn_timeout); - self.stats.waits += 1; - - ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - match err { - ClientConnectorError::Timeout => (), - _ => { - act.release_key(&key); - } - } - act.stats.errors += 1; - act.check_availibility(ctx); - fut::err(err) - } - }), - ) - } - Acquire::Available => { - let (tx, rx) = oneshot::channel(); - let wait = Instant::now() + wait_timeout; - let waiter = Waiter { - tx, - wait, - conn_timeout, - }; - self.connect_waiter(&key, waiter, ctx); - - ActorResponse::async( - rx.map_err(|_| ClientConnectorError::Disconnected) - .into_actor(self) - .and_then(move |res, act, ctx| match res { - Ok(conn) => fut::ok(conn), - Err(err) => { - act.stats.errors += 1; - act.release_key(&key); - act.check_availibility(ctx); - fut::err(err) - } - }), - ) - } - } - } -} - -impl StreamHandler for ClientConnector { - fn handle(&mut self, msg: AcquiredConnOperation, ctx: &mut Context) { - match msg { - AcquiredConnOperation::Close(conn) => { - self.release_key(&conn.key); - self.to_close.push(conn); - self.stats.closed += 1; - } - AcquiredConnOperation::Release(conn) => { - self.release_key(&conn.key); - if (Instant::now() - conn.ts) < self.conn_lifetime { - self.available - .entry(conn.key.clone()) - .or_insert_with(VecDeque::new) - .push_back(Conn(Instant::now(), conn)); - } else { - self.to_close.push(conn); - self.stats.closed += 1; - } - } - AcquiredConnOperation::ReleaseKey(key) => { - // closed - self.stats.closed += 1; - self.release_key(&key); - } - } - - self.check_availibility(ctx); - } -} - -struct Maintenance; - -impl fut::ActorFuture for Maintenance { - type Item = (); - type Error = (); - type Actor = ClientConnector; - - fn poll( - &mut self, act: &mut ClientConnector, ctx: &mut Context, - ) -> Poll { - // check pause duration - if let Paused::Timeout(inst, _) = act.paused { - if inst <= Instant::now() { - act.paused = Paused::No; - } - } - - // collect wait timers - act.collect_waiters(); - - // check waiters - act.check_availibility(ctx); - - Ok(Async::NotReady) - } -} - -#[derive(PartialEq, Hash, Debug, Clone, Copy)] -enum Protocol { - Http, - Https, - Ws, - Wss, -} - -impl Protocol { - fn from(s: &str) -> Option { - match s { - "http" => Some(Protocol::Http), - "https" => Some(Protocol::Https), - "ws" => Some(Protocol::Ws), - "wss" => Some(Protocol::Wss), - _ => None, - } - } - - fn is_http(self) -> bool { - match self { - Protocol::Https | Protocol::Http => true, - _ => false, - } - } - - fn is_secure(self) -> bool { - match self { - Protocol::Https | Protocol::Wss => true, - _ => false, - } - } - - fn port(self) -> u16 { - match self { - Protocol::Http | Protocol::Ws => 80, - Protocol::Https | Protocol::Wss => 443, - } - } -} - -#[derive(Hash, Eq, PartialEq, Clone, Debug)] -struct Key { - host: String, - port: u16, - ssl: bool, -} - -impl Key { - fn empty() -> Key { - Key { - host: String::new(), - port: 0, - ssl: false, - } - } -} - -#[derive(Debug)] -struct Conn(Instant, Connection); - -enum Acquire { - Acquired(Connection), - Available, - NotAvailable, -} - -enum AcquiredConnOperation { - Close(Connection), - Release(Connection), - ReleaseKey(Key), -} - -struct AcquiredConn(Key, Option>); - -impl AcquiredConn { - fn close(&mut self, conn: Connection) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::Close(conn)); - } - } - fn release(&mut self, conn: Connection) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::Release(conn)); - } - } -} - -impl Drop for AcquiredConn { - fn drop(&mut self) { - if let Some(tx) = self.1.take() { - let _ = tx.unbounded_send(AcquiredConnOperation::ReleaseKey(self.0.clone())); - } - } -} - -/// HTTP client connection -pub struct Connection { - key: Key, - stream: Box, - pool: Option, - ts: Instant, -} - -impl fmt::Debug for Connection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Connection {}:{}", self.key.host, self.key.port) - } -} - -impl Connection { - fn new(key: Key, pool: Option, stream: Box) -> Self { - Connection { - key, - stream, - pool, - ts: Instant::now(), - } - } - - /// Raw IO stream - pub fn stream(&mut self) -> &mut IoStream { - &mut *self.stream - } - - /// Create a new connection from an IO Stream - /// - /// The stream can be a `UnixStream` if the Unix-only "uds" feature is enabled. - /// - /// See also `ClientRequestBuilder::with_connection()`. - pub fn from_stream(io: T) -> Connection { - Connection::new(Key::empty(), None, Box::new(io)) - } - - /// Close connection - pub fn close(mut self) { - if let Some(mut pool) = self.pool.take() { - pool.close(self) - } - } - - /// Release this connection to the connection pool - pub fn release(mut self) { - if let Some(mut pool) = self.pool.take() { - pool.release(self) - } - } -} - -impl IoStream for Connection { - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - IoStream::shutdown(&mut *self.stream, how) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - IoStream::set_nodelay(&mut *self.stream, nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - IoStream::set_linger(&mut *self.stream, dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - IoStream::set_keepalive(&mut *self.stream, dur) - } -} - -impl io::Read for Connection { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.stream.read(buf) - } -} - -impl AsyncRead for Connection {} - -impl io::Write for Connection { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.stream.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.stream.flush() - } -} - -impl AsyncWrite for Connection { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.stream.shutdown() - } -} - -#[cfg(feature = "tls")] -use tokio_tls::TlsStream; - -#[cfg(feature = "tls")] -/// This is temp solution untile actix-net migration -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) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index 5321e4b0..00000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Http client api -//! -//! ```rust -//! # extern crate actix_web; -//! # extern crate actix; -//! # extern crate futures; -//! # extern crate tokio; -//! # use std::process; -//! use actix_web::client; -//! use futures::Future; -//! -//! fn main() { -//! actix::run( -//! || client::get("http://www.rust-lang.org") // <- Create request builder -//! .header("User-Agent", "Actix-web") -//! .finish().unwrap() -//! .send() // <- Send http request -//! .map_err(|_| ()) -//! .and_then(|response| { // <- server http response -//! println!("Response: {:?}", response); -//! # actix::System::current().stop(); -//! Ok(()) -//! }) -//! ); -//! } -//! ``` -mod connector; -mod parser; -mod pipeline; -mod request; -mod response; -mod writer; - -pub use self::connector::{ - ClientConnector, ClientConnectorError, ClientConnectorStats, Connect, Connection, - Pause, Resume, -}; -pub(crate) use self::parser::{HttpResponseParser, HttpResponseParserError}; -pub(crate) use self::pipeline::Pipeline; -pub use self::pipeline::{SendRequest, SendRequestError}; -pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; -pub(crate) use self::writer::HttpClientWriter; - -use error::ResponseError; -use http::Method; -use httpresponse::HttpResponse; - -/// Convert `SendRequestError` to a `HttpResponse` -impl ResponseError for SendRequestError { - fn error_response(&self) -> HttpResponse { - match *self { - SendRequestError::Timeout => HttpResponse::GatewayTimeout(), - SendRequestError::Connector(_) => HttpResponse::BadGateway(), - _ => HttpResponse::InternalServerError(), - }.into() - } -} - -/// Create request builder for `GET` requests -/// -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate actix; -/// # extern crate futures; -/// # extern crate tokio; -/// # extern crate env_logger; -/// # use std::process; -/// use actix_web::client; -/// use futures::Future; -/// -/// fn main() { -/// actix::run( -/// || client::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); -/// } -/// ``` -pub fn get>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder -} - -/// Create request builder for `HEAD` requests -pub fn head>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder -} - -/// Create request builder for `POST` requests -pub fn post>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder -} - -/// Create request builder for `PUT` requests -pub fn put>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder -} - -/// Create request builder for `DELETE` requests -pub fn delete>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder -} diff --git a/src/client/parser.rs b/src/client/parser.rs deleted file mode 100644 index 92a7abe1..00000000 --- a/src/client/parser.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::mem; - -use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{HeaderMap, StatusCode, Version}; -use httparse; - -use error::{ParseError, PayloadError}; - -use server::h1decoder::{EncodingDecoder, HeaderIndex}; -use server::IoStream; - -use super::response::ClientMessage; -use super::ClientResponse; - -const MAX_BUFFER_SIZE: usize = 131_072; -const MAX_HEADERS: usize = 96; - -#[derive(Default)] -pub struct HttpResponseParser { - decoder: Option, - eof: bool, // indicate that we read payload until stream eof -} - -#[derive(Debug, Fail)] -pub enum HttpResponseParserError { - /// Server disconnected - #[fail(display = "Server disconnected")] - Disconnect, - #[fail(display = "{}", _0)] - Error(#[cause] ParseError), -} - -impl HttpResponseParser { - pub fn parse( - &mut self, io: &mut T, buf: &mut BytesMut, - ) -> Poll - where - T: IoStream, - { - loop { - // Don't call parser until we have data to parse. - if !buf.is_empty() { - match HttpResponseParser::parse_message(buf) - .map_err(HttpResponseParserError::Error)? - { - Async::Ready((msg, info)) => { - if let Some((decoder, eof)) = info { - self.eof = eof; - self.decoder = Some(decoder); - } else { - self.eof = false; - self.decoder = None; - } - return Ok(Async::Ready(msg)); - } - Async::NotReady => { - if buf.len() >= MAX_BUFFER_SIZE { - return Err(HttpResponseParserError::Error( - ParseError::TooLarge, - )); - } - // Parser needs more data. - } - } - } - // Read some more data into the buffer for the parser. - match io.read_available(buf) { - Ok(Async::Ready((false, true))) => { - return Err(HttpResponseParserError::Disconnect) - } - Ok(Async::Ready(_)) => (), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(HttpResponseParserError::Error(err.into())), - } - } - } - - pub fn parse_payload( - &mut self, io: &mut T, buf: &mut BytesMut, - ) -> Poll, PayloadError> - where - T: IoStream, - { - if self.decoder.is_some() { - loop { - // read payload - let (not_ready, stream_finished) = match io.read_available(buf) { - Ok(Async::Ready((_, true))) => (false, true), - Ok(Async::Ready((_, false))) => (false, false), - Ok(Async::NotReady) => (true, false), - Err(err) => return Err(err.into()), - }; - - match self.decoder.as_mut().unwrap().decode(buf) { - Ok(Async::Ready(Some(b))) => return Ok(Async::Ready(Some(b))), - Ok(Async::Ready(None)) => { - self.decoder.take(); - return Ok(Async::Ready(None)); - } - Ok(Async::NotReady) => { - if not_ready { - return Ok(Async::NotReady); - } - if stream_finished { - // read untile eof? - if self.eof { - return Ok(Async::Ready(None)); - } else { - return Err(PayloadError::Incomplete); - } - } - } - Err(err) => return Err(err.into()), - } - } - } else { - Ok(Async::Ready(None)) - } - } - - fn parse_message( - buf: &mut BytesMut, - ) -> Poll<(ClientResponse, Option<(EncodingDecoder, bool)>), ParseError> { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; - - let (len, version, status, headers_len) = { - let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let mut resp = httparse::Response::new(&mut parsed); - match resp.parse(buf)? { - httparse::Status::Complete(len) => { - let version = if resp.version.unwrap_or(1) == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - HeaderIndex::record(buf, resp.headers, &mut headers); - let status = StatusCode::from_u16(resp.code.unwrap()) - .map_err(|_| ParseError::Status)?; - - (len, version, status, resp.headers.len()) - } - httparse::Status::Partial => return Ok(Async::NotReady), - } - }; - - let slice = buf.split_to(len).freeze(); - - // convert headers - let mut hdrs = HeaderMap::new(); - for idx in headers[..headers_len].iter() { - if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - hdrs.append(name, value); - } else { - return Err(ParseError::Header); - } - } - - let decoder = if status == StatusCode::SWITCHING_PROTOCOLS { - Some((EncodingDecoder::eof(), true)) - } else if let Some(len) = hdrs.get(header::CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Some((EncodingDecoder::length(len), false)) - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else if chunked(&hdrs)? { - // Chunked encoding - Some((EncodingDecoder::chunked(), false)) - } else if let Some(value) = hdrs.get(header::CONNECTION) { - let close = if let Ok(s) = value.to_str() { - s == "close" - } else { - false - }; - if close { - Some((EncodingDecoder::eof(), true)) - } else { - None - } - } else { - None - }; - - if let Some(decoder) = decoder { - Ok(Async::Ready(( - ClientResponse::new(ClientMessage { - status, - version, - headers: hdrs, - cookies: None, - }), - Some(decoder), - ))) - } else { - Ok(Async::Ready(( - ClientResponse::new(ClientMessage { - status, - version, - headers: hdrs, - cookies: None, - }), - None, - ))) - } - } -} - -/// Check if request has chunked transfer encoding -pub 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) - } -} diff --git a/src/client/pipeline.rs b/src/client/pipeline.rs deleted file mode 100644 index 1dbd2e17..00000000 --- a/src/client/pipeline.rs +++ /dev/null @@ -1,553 +0,0 @@ -use bytes::{Bytes, BytesMut}; -use futures::sync::oneshot; -use futures::{Async, Future, Poll, Stream}; -use http::header::CONTENT_ENCODING; -use std::time::{Duration, Instant}; -use std::{io, mem}; -use tokio_timer::Delay; - -use actix_inner::dev::Request; -use actix::{Addr, SystemService}; - -use super::{ - ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect, - Connection, HttpClientWriter, HttpResponseParser, HttpResponseParserError, -}; -use body::{Body, BodyStream}; -use context::{ActorHttpContext, Frame}; -use error::Error; -use error::PayloadError; -use header::ContentEncoding; -use http::{Method, Uri}; -use httpmessage::HttpMessage; -use server::input::PayloadStream; -use server::WriterState; - -/// A set of errors that can occur during request sending and response reading -#[derive(Fail, Debug)] -pub enum SendRequestError { - /// Response took too long - #[fail(display = "Timeout while waiting for response")] - Timeout, - /// Failed to connect to host - #[fail(display = "Failed to connect to host: {}", _0)] - Connector(#[cause] ClientConnectorError), - /// Error parsing response - #[fail(display = "{}", _0)] - ParseError(#[cause] HttpResponseParserError), - /// Error reading response payload - #[fail(display = "Error reading response payload: {}", _0)] - Io(#[cause] io::Error), -} - -impl From for SendRequestError { - fn from(err: io::Error) -> SendRequestError { - SendRequestError::Io(err) - } -} - -impl From for SendRequestError { - fn from(err: ClientConnectorError) -> SendRequestError { - match err { - ClientConnectorError::Timeout => SendRequestError::Timeout, - _ => SendRequestError::Connector(err), - } - } -} - -enum State { - New, - Connect(Request), - Connection(Connection), - Send(Box), - None, -} - -/// `SendRequest` is a `Future` which represents an asynchronous -/// request sending process. -#[must_use = "SendRequest does nothing unless polled"] -pub struct SendRequest { - req: ClientRequest, - state: State, - conn: Option>, - conn_timeout: Duration, - wait_timeout: Duration, - timeout: Option, -} - -impl SendRequest { - pub(crate) fn new(req: ClientRequest) -> SendRequest { - SendRequest { - req, - conn: None, - state: State::New, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - pub(crate) fn with_connector( - req: ClientRequest, conn: Addr, - ) -> SendRequest { - SendRequest { - req, - conn: Some(conn), - state: State::New, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - pub(crate) fn with_connection(req: ClientRequest, conn: Connection) -> SendRequest { - SendRequest { - req, - state: State::Connection(conn), - conn: None, - timeout: None, - wait_timeout: Duration::from_secs(5), - conn_timeout: Duration::from_secs(1), - } - } - - /// Set request timeout - /// - /// Request timeout is the total time before a response must be received. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(timeout); - self - } - - /// Set connection timeout - /// - /// Connection timeout includes resolving hostname and actual connection to - /// the host. - /// Default value is 1 second. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - self.conn_timeout = timeout; - self - } - - /// Set wait timeout - /// - /// If connections pool limits are enabled, wait time indicates max time - /// to wait for available connection. Default value is 5 seconds. - pub fn wait_timeout(mut self, timeout: Duration) -> Self { - self.wait_timeout = timeout; - self - } -} - -impl Future for SendRequest { - type Item = ClientResponse; - type Error = SendRequestError; - - fn poll(&mut self) -> Poll { - loop { - let state = mem::replace(&mut self.state, State::None); - - match state { - State::New => { - let conn = if let Some(conn) = self.conn.take() { - conn - } else { - ClientConnector::from_registry() - }; - self.state = State::Connect(conn.send(Connect { - uri: self.req.uri().clone(), - wait_timeout: self.wait_timeout, - conn_timeout: self.conn_timeout, - })) - } - State::Connect(mut conn) => match conn.poll() { - Ok(Async::NotReady) => { - self.state = State::Connect(conn); - return Ok(Async::NotReady); - } - Ok(Async::Ready(result)) => match result { - Ok(stream) => self.state = State::Connection(stream), - Err(err) => return Err(err.into()), - }, - Err(_) => { - return Err(SendRequestError::Connector( - ClientConnectorError::Disconnected, - )); - } - }, - State::Connection(conn) => { - let mut writer = HttpClientWriter::new(); - writer.start(&mut self.req)?; - - let body = match self.req.replace_body(Body::Empty) { - Body::Streaming(stream) => IoBody::Payload(stream), - Body::Actor(ctx) => IoBody::Actor(ctx), - _ => IoBody::Done, - }; - - let timeout = self - .timeout - .take() - .unwrap_or_else(|| Duration::from_secs(5)); - - let pl = Box::new(Pipeline { - body, - writer, - conn: Some(conn), - parser: Some(HttpResponseParser::default()), - parser_buf: BytesMut::new(), - disconnected: false, - body_completed: false, - drain: None, - decompress: None, - should_decompress: self.req.response_decompress(), - write_state: RunningState::Running, - timeout: Some(Delay::new(Instant::now() + timeout)), - meth: self.req.method().clone(), - path: self.req.uri().clone(), - }); - self.state = State::Send(pl); - } - State::Send(mut pl) => { - pl.poll_timeout()?; - pl.poll_write().map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("{}", e).as_str()) - })?; - - match pl.parse() { - Ok(Async::Ready(mut resp)) => { - if self.req.method() == Method::HEAD { - pl.parser.take(); - } - resp.set_pipeline(pl); - return Ok(Async::Ready(resp)); - } - Ok(Async::NotReady) => { - self.state = State::Send(pl); - return Ok(Async::NotReady); - } - Err(err) => { - return Err(SendRequestError::ParseError(err)); - } - } - } - State::None => unreachable!(), - } - } - } -} - -pub struct Pipeline { - body: IoBody, - body_completed: bool, - conn: Option, - writer: HttpClientWriter, - parser: Option, - parser_buf: BytesMut, - disconnected: bool, - drain: Option>, - decompress: Option, - should_decompress: bool, - write_state: RunningState, - timeout: Option, - meth: Method, - path: Uri, -} - -enum IoBody { - Payload(BodyStream), - Actor(Box), - Done, -} - -#[derive(Debug, PartialEq)] -enum RunningState { - Running, - Paused, - Done, -} - -impl RunningState { - #[inline] - fn pause(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Paused - } - } - #[inline] - fn resume(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Running - } - } -} - -impl Pipeline { - fn release_conn(&mut self) { - if let Some(conn) = self.conn.take() { - if self.meth == Method::HEAD { - conn.close() - } else { - conn.release() - } - } - } - - #[inline] - fn parse(&mut self) -> Poll { - if let Some(ref mut conn) = self.conn { - match self - .parser - .as_mut() - .unwrap() - .parse(conn, &mut self.parser_buf) - { - Ok(Async::Ready(resp)) => { - // check content-encoding - if self.should_decompress { - if let Some(enc) = resp.headers().get(CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - match ContentEncoding::from(enc) { - ContentEncoding::Auto - | ContentEncoding::Identity => (), - enc => { - self.decompress = Some(PayloadStream::new(enc)) - } - } - } - } - } - - Ok(Async::Ready(resp)) - } - val => val, - } - } else { - Ok(Async::NotReady) - } - } - - #[inline] - pub(crate) fn poll(&mut self) -> Poll, PayloadError> { - if self.conn.is_none() { - return Ok(Async::Ready(None)); - } - let mut need_run = false; - - // need write? - match self - .poll_write() - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))? - { - Async::NotReady => need_run = true, - Async::Ready(_) => { - self.poll_timeout().map_err(|e| { - io::Error::new(io::ErrorKind::Other, format!("{}", e)) - })?; - } - } - - // need read? - if self.parser.is_some() { - let conn: &mut Connection = self.conn.as_mut().unwrap(); - - loop { - match self - .parser - .as_mut() - .unwrap() - .parse_payload(conn, &mut self.parser_buf)? - { - Async::Ready(Some(b)) => { - if let Some(ref mut decompress) = self.decompress { - match decompress.feed_data(b) { - Ok(Some(b)) => return Ok(Async::Ready(Some(b))), - Ok(None) => return Ok(Async::NotReady), - Err(ref err) - if err.kind() == io::ErrorKind::WouldBlock => - { - continue - } - Err(err) => return Err(err.into()), - } - } else { - return Ok(Async::Ready(Some(b))); - } - } - Async::Ready(None) => { - let _ = self.parser.take(); - break; - } - Async::NotReady => return Ok(Async::NotReady), - } - } - } - - // eof - if let Some(mut decompress) = self.decompress.take() { - let res = decompress.feed_eof(); - if let Some(b) = res? { - self.release_conn(); - return Ok(Async::Ready(Some(b))); - } - } - - if need_run { - Ok(Async::NotReady) - } else { - self.release_conn(); - Ok(Async::Ready(None)) - } - } - - fn poll_timeout(&mut self) -> Result<(), SendRequestError> { - if self.timeout.is_some() { - match self.timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(())) => return Err(SendRequestError::Timeout), - Ok(Async::NotReady) => (), - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e).into()), - } - } - Ok(()) - } - - #[inline] - fn poll_write(&mut self) -> Poll<(), Error> { - if self.write_state == RunningState::Done || self.conn.is_none() { - return Ok(Async::Ready(())); - } - - let mut done = false; - if self.drain.is_none() && self.write_state != RunningState::Paused { - 'outter: loop { - let result = match mem::replace(&mut self.body, IoBody::Done) { - IoBody::Payload(mut body) => match body.poll()? { - Async::Ready(None) => { - self.writer.write_eof()?; - self.body_completed = true; - break; - } - Async::Ready(Some(chunk)) => { - self.body = IoBody::Payload(body); - self.writer.write(chunk.as_ref())? - } - Async::NotReady => { - done = true; - self.body = IoBody::Payload(body); - break; - } - }, - IoBody::Actor(mut ctx) => { - if self.disconnected { - ctx.disconnected(); - } - match ctx.poll()? { - Async::Ready(Some(vec)) => { - if vec.is_empty() { - self.body = IoBody::Actor(ctx); - break; - } - let mut res = None; - for frame in vec { - match frame { - Frame::Chunk(None) => { - self.body_completed = true; - self.writer.write_eof()?; - break 'outter; - } - Frame::Chunk(Some(chunk)) => { - res = - Some(self.writer.write(chunk.as_ref())?) - } - Frame::Drain(fut) => self.drain = Some(fut), - } - } - self.body = IoBody::Actor(ctx); - if self.drain.is_some() { - self.write_state.resume(); - break; - } - res.unwrap() - } - Async::Ready(None) => { - done = true; - break; - } - Async::NotReady => { - done = true; - self.body = IoBody::Actor(ctx); - break; - } - } - } - IoBody::Done => { - self.body_completed = true; - done = true; - break; - } - }; - - match result { - WriterState::Pause => { - self.write_state.pause(); - break; - } - WriterState::Done => self.write_state.resume(), - } - } - } - - // flush io but only if we need to - match self - .writer - .poll_completed(self.conn.as_mut().unwrap(), false) - { - Ok(Async::Ready(_)) => { - if self.disconnected - || (self.body_completed && self.writer.is_completed()) - { - self.write_state = RunningState::Done; - } else { - self.write_state.resume(); - } - - // resolve drain futures - if let Some(tx) = self.drain.take() { - let _ = tx.send(()); - } - // restart io processing - if !done || self.write_state == RunningState::Done { - self.poll_write() - } else { - Ok(Async::NotReady) - } - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => Err(err.into()), - } - } -} - -impl Drop for Pipeline { - fn drop(&mut self) { - if let Some(conn) = self.conn.take() { - debug!( - "Client http transaction is not completed, dropping connection: {:?} {:?}", - self.meth, - self.path, - ); - conn.close() - } - } -} - -/// Future that resolves to a complete request body. -impl Stream for Box { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - Pipeline::poll(self) - } -} diff --git a/src/client/request.rs b/src/client/request.rs deleted file mode 100644 index ad08ad13..00000000 --- a/src/client/request.rs +++ /dev/null @@ -1,783 +0,0 @@ -use std::fmt::Write as FmtWrite; -use std::io::Write; -use std::time::Duration; -use std::{fmt, mem}; - -use actix::Addr; -use bytes::{BufMut, Bytes, BytesMut}; -use cookie::{Cookie, CookieJar}; -use futures::Stream; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use serde::Serialize; -use serde_json; -use serde_urlencoded; -use url::Url; - -use super::connector::{ClientConnector, Connection}; -use super::pipeline::SendRequest; -use body::Body; -use error::Error; -use header::{ContentEncoding, Header, IntoHeaderValue}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{uri, Error as HttpError, HeaderMap, HttpTryFrom, Method, Uri, Version}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; - -/// An HTTP Client Request -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate actix; -/// # extern crate futures; -/// # extern crate tokio; -/// # use futures::Future; -/// # use std::process; -/// use actix_web::client; -/// -/// fn main() { -/// actix::run( -/// || client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// # actix::System::current().stop(); -/// Ok(()) -/// }), -/// ); -/// } -/// ``` -pub struct ClientRequest { - uri: Uri, - method: Method, - version: Version, - headers: HeaderMap, - body: Body, - chunked: bool, - upgrade: bool, - timeout: Option, - encoding: ContentEncoding, - response_decompress: bool, - buffer_capacity: usize, - conn: ConnectionType, -} - -enum ConnectionType { - Default, - Connector(Addr), - Connection(Connection), -} - -impl Default for ClientRequest { - fn default() -> ClientRequest { - ClientRequest { - uri: Uri::default(), - method: Method::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - body: Body::Empty, - chunked: false, - upgrade: false, - timeout: None, - encoding: ContentEncoding::Auto, - response_decompress: true, - buffer_capacity: 32_768, - conn: ConnectionType::Default, - } - } -} - -impl ClientRequest { - /// Create request builder for `GET` request - pub fn get>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder - } - - /// Create request builder for `HEAD` request - pub fn head>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder - } - - /// Create request builder for `POST` request - pub fn post>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder - } - - /// Create request builder for `PUT` request - pub fn put>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder - } - - /// Create request builder for `DELETE` request - pub fn delete>(uri: U) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder - } -} - -impl ClientRequest { - /// Create client request builder - pub fn build() -> ClientRequestBuilder { - ClientRequestBuilder { - request: Some(ClientRequest::default()), - err: None, - cookies: None, - default_headers: true, - } - } - - /// Create client request builder - pub fn build_from>(source: T) -> ClientRequestBuilder { - source.into() - } - - /// Get the request URI - #[inline] - pub fn uri(&self) -> &Uri { - &self.uri - } - - /// Set client request URI - #[inline] - pub fn set_uri(&mut self, uri: Uri) { - self.uri = uri - } - - /// Get the request method - #[inline] - pub fn method(&self) -> &Method { - &self.method - } - - /// Set HTTP `Method` for the request - #[inline] - pub fn set_method(&mut self, method: Method) { - self.method = method - } - - /// Get HTTP version for the request - #[inline] - pub fn version(&self) -> Version { - self.version - } - - /// Set http `Version` for the request - #[inline] - pub fn set_version(&mut self, version: Version) { - self.version = version - } - - /// Get the headers from the request - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - /// is chunked encoding enabled - #[inline] - pub fn chunked(&self) -> bool { - self.chunked - } - - /// is upgrade request - #[inline] - pub fn upgrade(&self) -> bool { - self.upgrade - } - - /// Content encoding - #[inline] - pub fn content_encoding(&self) -> ContentEncoding { - self.encoding - } - - /// Decompress response payload - #[inline] - pub fn response_decompress(&self) -> bool { - self.response_decompress - } - - /// Requested write buffer capacity - pub fn write_buffer_capacity(&self) -> usize { - self.buffer_capacity - } - - /// Get body of this response - #[inline] - pub fn body(&self) -> &Body { - &self.body - } - - /// Set a body - pub fn set_body>(&mut self, body: B) { - self.body = body.into(); - } - - /// Extract body, replace it with `Empty` - pub(crate) fn replace_body(&mut self, body: Body) -> Body { - mem::replace(&mut self.body, body) - } - - /// Send request - /// - /// This method returns a future that resolves to a ClientResponse - pub fn send(mut self) -> SendRequest { - let timeout = self.timeout.take(); - let send = match mem::replace(&mut self.conn, ConnectionType::Default) { - ConnectionType::Default => SendRequest::new(self), - ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn), - ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn), - }; - if let Some(timeout) = timeout { - send.timeout(timeout) - } else { - send - } - } -} - -impl fmt::Debug for ClientRequest { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nClientRequest {:?} {}:{}", - self.version, self.method, self.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -/// An HTTP Client request builder -/// -/// This type can be used to construct an instance of `ClientRequest` through a -/// builder-like pattern. -pub struct ClientRequestBuilder { - request: Option, - err: Option, - cookies: Option, - default_headers: bool, -} - -impl ClientRequestBuilder { - /// Set HTTP URI of request. - #[inline] - pub fn uri>(&mut self, uri: U) -> &mut Self { - match Url::parse(uri.as_ref()) { - Ok(url) => self._uri(url.as_str()), - Err(_) => self._uri(uri.as_ref()), - } - } - - fn _uri(&mut self, url: &str) -> &mut Self { - match Uri::try_from(url) { - Ok(uri) => { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.uri = uri; - } - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.method = method; - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn get_method(&mut self) -> &Method { - let parts = self.request.as_ref().expect("cannot reuse request builder"); - &parts.method - } - - /// Set HTTP version of this request. - /// - /// By default requests's HTTP version depends on network stream - #[inline] - pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.version = version; - } - self - } - - /// Set a header. - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_web; - /// # use actix_web::client::*; - /// # - /// use actix_web::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .set(http::header::Date::now()) - /// .set(http::header::ContentType(mime::TEXT_HTML)) - /// .finish() - /// .unwrap(); - /// } - /// ``` - #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// # use actix_web::client::*; - /// # - /// use http::header; - /// - /// fn main() { - /// let req = ClientRequest::build() - /// .header("X-TEST", "value") - /// .header(header::CONTENT_TYPE, "application/json") - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header. - pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header only if it is not yet set. - pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => if !parts.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - } - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content encoding. - /// - /// By default `ContentEncoding::Identity` is used. - #[inline] - pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.encoding = enc; - } - self - } - - /// Enables automatic chunked transfer encoding - #[inline] - pub fn chunked(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.chunked = true; - } - self - } - - /// Enable connection upgrade - #[inline] - pub fn upgrade(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.upgrade = true; - } - self - } - - /// Set request's content type - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - HeaderValue: HttpTryFrom, - { - if let Some(parts) = parts(&mut self.request, &self.err) { - match HeaderValue::try_from(value) { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content length - #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { - let mut wrt = BytesMut::new().writer(); - let _ = write!(wrt, "{}", len); - self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) - } - - /// Set a cookie - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - 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 - } - - /// Do not add default request headers. - /// By default `Accept-Encoding` and `User-Agent` headers are set. - pub fn no_default_headers(&mut self) -> &mut Self { - self.default_headers = false; - self - } - - /// Disable automatic decompress response body - pub fn disable_decompress(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.response_decompress = false; - } - self - } - - /// Set write buffer capacity - /// - /// Default buffer capacity is 32kb - pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.buffer_capacity = cap; - } - self - } - - /// Set request timeout - /// - /// Request timeout is a total time before response should be received. - /// Default value is 5 seconds. - pub fn timeout(&mut self, timeout: Duration) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.timeout = Some(timeout); - } - self - } - - /// Send request using custom connector - pub fn with_connector(&mut self, conn: Addr) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.conn = ConnectionType::Connector(conn); - } - self - } - - /// Send request using existing `Connection` - pub fn with_connection(&mut self, conn: Connection) -> &mut Self { - if let Some(parts) = parts(&mut self.request, &self.err) { - parts.conn = ConnectionType::Connection(conn); - } - self - } - - /// This method calls provided closure with builder reference if - /// value is `true`. - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut ClientRequestBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if - /// value is `Some`. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut ClientRequestBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - - /// Set a body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> Result { - if let Some(e) = self.err.take() { - return Err(e.into()); - } - - if self.default_headers { - // enable br only for https - let https = if let Some(parts) = parts(&mut self.request, &self.err) { - parts - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true) - } else { - true - }; - - if https { - self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); - } else { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); - } - - // set request host header - if let Some(parts) = parts(&mut self.request, &self.err) { - if let Some(host) = parts.uri.host() { - if !parts.headers.contains_key(header::HOST) { - let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - - let _ = match parts.uri.port_part().map(|port| port.as_u16()) { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), - }; - - match wrt.get_mut().take().freeze().try_into() { - Ok(value) => { - parts.headers.insert(header::HOST, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - } - - // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("actix-web/", env!("CARGO_PKG_VERSION")), - ); - } - - let mut request = self.request.take().expect("cannot reuse request builder"); - - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - request.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - request.body = body.into(); - Ok(request) - } - - /// Set a JSON body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Result { - let body = serde_json::to_string(&value)?; - - let contains = if let Some(parts) = parts(&mut self.request, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/json"); - } - - self.body(body) - } - - /// Set a urlencoded body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn form(&mut self, value: T) -> Result { - let body = serde_urlencoded::to_string(&value)?; - - let contains = if let Some(parts) = parts(&mut self.request, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); - } - - self.body(body) - } - - /// Set a streaming body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> Result - where - S: Stream + 'static, - E: Into, - { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) - } - - /// Set an empty body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn finish(&mut self) -> Result { - self.body(Body::Empty) - } - - /// This method construct new `ClientRequestBuilder` - pub fn take(&mut self) -> ClientRequestBuilder { - ClientRequestBuilder { - request: self.request.take(), - err: self.err.take(), - cookies: self.cookies.take(), - default_headers: self.default_headers, - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, err: &Option, -) -> Option<&'a mut ClientRequest> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl fmt::Debug for ClientRequestBuilder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref parts) = self.request { - writeln!( - f, - "\nClientRequestBuilder {:?} {}:{}", - parts.version, parts.method, parts.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in parts.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } else { - write!(f, "ClientRequestBuilder(Consumed)") - } - } -} - -/// Create `ClientRequestBuilder` from `HttpRequest` -/// -/// It is useful for proxy requests. This implementation -/// copies all request headers and the method. -impl<'a, S: 'static> From<&'a HttpRequest> for ClientRequestBuilder { - fn from(req: &'a HttpRequest) -> ClientRequestBuilder { - let mut builder = ClientRequest::build(); - for (key, value) in req.headers() { - builder.header(key.clone(), value.clone()); - } - builder.method(req.method().clone()); - builder - } -} diff --git a/src/client/response.rs b/src/client/response.rs deleted file mode 100644 index 5f1f4264..00000000 --- a/src/client/response.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::cell::RefCell; -use std::{fmt, str}; - -use cookie::Cookie; -use http::header::{self, HeaderValue}; -use http::{HeaderMap, StatusCode, Version}; - -use error::CookieParseError; -use httpmessage::HttpMessage; - -use super::pipeline::Pipeline; - -pub(crate) struct ClientMessage { - pub status: StatusCode, - pub version: Version, - pub headers: HeaderMap, - pub cookies: Option>>, -} - -impl Default for ClientMessage { - fn default() -> ClientMessage { - ClientMessage { - status: StatusCode::OK, - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - cookies: None, - } - } -} - -/// An HTTP Client response -pub struct ClientResponse(ClientMessage, RefCell>>); - -impl HttpMessage for ClientResponse { - type Stream = Box; - - /// Get the headers from the response. - #[inline] - fn headers(&self) -> &HeaderMap { - &self.0.headers - } - - #[inline] - fn payload(&self) -> Box { - self.1 - .borrow_mut() - .take() - .expect("Payload is already consumed.") - } -} - -impl ClientResponse { - pub(crate) fn new(msg: ClientMessage) -> ClientResponse { - ClientResponse(msg, RefCell::new(None)) - } - - pub(crate) fn set_pipeline(&mut self, pl: Box) { - *self.1.borrow_mut() = Some(pl); - } - - /// Get the HTTP version of this response. - #[inline] - pub fn version(&self) -> Version { - self.0.version - } - - /// Get the status from the server. - #[inline] - pub fn status(&self) -> StatusCode { - self.0.status - } - - /// Load response cookies. - pub fn cookies(&self) -> Result>, CookieParseError> { - let mut cookies = Vec::new(); - for val in self.0.headers.get_all(header::SET_COOKIE).iter() { - let s = str::from_utf8(val.as_bytes()).map_err(CookieParseError::from)?; - cookies.push(Cookie::parse_encoded(s)?.into_owned()); - } - Ok(cookies) - } - - /// Return request cookie. - pub fn cookie(&self, name: &str) -> Option { - if let Ok(cookies) = self.cookies() { - for cookie in cookies { - if cookie.name() == name { - return Some(cookie); - } - } - } - None - } -} - -impl fmt::Debug for ClientResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status())?; - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_debug() { - let mut resp = ClientResponse::new(ClientMessage::default()); - resp.0 - .headers - .insert(header::COOKIE, HeaderValue::from_static("cookie1=value1")); - resp.0 - .headers - .insert(header::COOKIE, HeaderValue::from_static("cookie2=value2")); - - let dbg = format!("{:?}", resp); - assert!(dbg.contains("ClientResponse")); - } -} diff --git a/src/client/writer.rs b/src/client/writer.rs deleted file mode 100644 index 321753bb..00000000 --- a/src/client/writer.rs +++ /dev/null @@ -1,412 +0,0 @@ -#![cfg_attr( - feature = "cargo-clippy", - allow(redundant_field_names) -)] - -use std::cell::RefCell; -use std::fmt::Write as FmtWrite; -use std::io::{self, Write}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliEncoder; -use bytes::{BufMut, BytesMut}; -#[cfg(feature = "flate2")] -use flate2::write::{GzEncoder, ZlibEncoder}; -#[cfg(feature = "flate2")] -use flate2::Compression; -use futures::{Async, Poll}; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Version}; -use time::{self, Duration}; -use tokio_io::AsyncWrite; - -use body::{Binary, Body}; -use header::ContentEncoding; -use server::output::{ContentEncoder, Output, TransferEncoding}; -use server::WriterState; - -use client::ClientRequest; - -const AVERAGE_HEADER_SIZE: usize = 30; - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const UPGRADE = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const DISCONNECTED = 0b0000_1000; - } -} - -pub(crate) struct HttpClientWriter { - flags: Flags, - written: u64, - headers_size: u32, - buffer: Output, - buffer_capacity: usize, -} - -impl HttpClientWriter { - pub fn new() -> HttpClientWriter { - HttpClientWriter { - flags: Flags::empty(), - written: 0, - headers_size: 0, - buffer_capacity: 0, - buffer: Output::Buffer(BytesMut::new()), - } - } - - pub fn disconnected(&mut self) { - self.buffer.take(); - } - - pub fn is_completed(&self) -> bool { - self.buffer.is_empty() - } - - // pub fn keepalive(&self) -> bool { - // self.flags.contains(Flags::KEEPALIVE) && - // !self.flags.contains(Flags::UPGRADE) } - - fn write_to_stream( - &mut self, stream: &mut T, - ) -> io::Result { - while !self.buffer.is_empty() { - match stream.write(self.buffer.as_ref().as_ref()) { - Ok(0) => { - self.disconnected(); - return Ok(WriterState::Done); - } - Ok(n) => { - let _ = self.buffer.split_to(n); - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - if self.buffer.len() > self.buffer_capacity { - return Ok(WriterState::Pause); - } else { - return Ok(WriterState::Done); - } - } - Err(err) => return Err(err), - } - } - Ok(WriterState::Done) - } -} - -pub struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl HttpClientWriter { - pub fn start(&mut self, msg: &mut ClientRequest) -> io::Result<()> { - // prepare task - self.buffer = content_encoder(self.buffer.take(), msg); - self.flags.insert(Flags::STARTED); - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - } - - // render message - { - // output buffer - let buffer = self.buffer.as_mut(); - - // status line - writeln!( - Writer(buffer), - "{} {} {:?}\r", - msg.method(), - msg.uri() - .path_and_query() - .map(|u| u.as_str()) - .unwrap_or("/"), - msg.version() - ).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - // write headers - if let Body::Binary(ref bytes) = *msg.body() { - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len()); - } else { - buffer.reserve(msg.headers().len() * AVERAGE_HEADER_SIZE); - } - - 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"); - } - - // set date header - if !msg.headers().contains_key(DATE) { - buffer.extend_from_slice(b"date: "); - set_date(buffer); - buffer.extend_from_slice(b"\r\n\r\n"); - } else { - buffer.extend_from_slice(b"\r\n"); - } - } - self.headers_size = self.buffer.len() as u32; - - if msg.body().is_binary() { - if let Body::Binary(bytes) = msg.replace_body(Body::Empty) { - self.written += bytes.len() as u64; - self.buffer.write(bytes.as_ref())?; - } - } else { - self.buffer_capacity = msg.write_buffer_capacity(); - } - Ok(()) - } - - pub fn write(&mut self, payload: &[u8]) -> io::Result { - self.written += payload.len() as u64; - if !self.flags.contains(Flags::DISCONNECTED) { - self.buffer.write(payload)?; - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - pub fn write_eof(&mut self) -> io::Result<()> { - if self.buffer.write_eof()? { - Ok(()) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } - } - - #[inline] - pub fn poll_completed( - &mut self, stream: &mut T, shutdown: bool, - ) -> Poll<(), io::Error> { - match self.write_to_stream(stream) { - Ok(WriterState::Done) => { - if shutdown { - stream.shutdown() - } else { - Ok(Async::Ready(())) - } - } - Ok(WriterState::Pause) => Ok(Async::NotReady), - Err(err) => Err(err), - } - } -} - -fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { - let version = req.version(); - let mut body = req.replace_body(Body::Empty); - let mut encoding = req.content_encoding(); - - let transfer = match body { - Body::Empty => { - req.headers_mut().remove(CONTENT_LENGTH); - return Output::Empty(buf); - } - Body::Binary(ref mut bytes) => { - #[cfg(any(feature = "flate2", feature = "brotli"))] - { - if encoding.is_compression() { - let mut tmp = BytesMut::new(); - let mut transfer = TransferEncoding::eof(tmp); - let mut enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate( - ZlibEncoder::new(transfer, Compression::default()), - ), - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( - transfer, - Compression::default(), - )), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - ContentEncoder::Br(BrotliEncoder::new(transfer, 5)) - } - ContentEncoding::Auto | ContentEncoding::Identity => { - unreachable!() - } - }; - // TODO return error! - let _ = enc.write(bytes.as_ref()); - let _ = enc.write_eof(); - *bytes = Binary::from(enc.buf_mut().take()); - - req.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); - encoding = ContentEncoding::Identity; - } - let mut b = BytesMut::new(); - let _ = write!(b, "{}", bytes.len()); - req.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof(buf) - } - #[cfg(not(any(feature = "flate2", feature = "brotli")))] - { - let mut b = BytesMut::new(); - let _ = write!(b, "{}", bytes.len()); - req.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap()); - TransferEncoding::eof(buf) - } - } - Body::Streaming(_) | Body::Actor(_) => { - if req.upgrade() { - if version == Version::HTTP_2 { - error!("Connection upgrade is forbidden for HTTP/2"); - } else { - req.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - if encoding != ContentEncoding::Identity { - encoding = ContentEncoding::Identity; - req.headers_mut().remove(CONTENT_ENCODING); - } - TransferEncoding::eof(buf) - } else { - streaming_encoding(buf, version, req) - } - } - }; - - if encoding.is_compression() { - req.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); - } - - req.replace_body(body); - let enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::default())) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default())) - } - #[cfg(feature = "brotli")] - ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 5)), - ContentEncoding::Identity | ContentEncoding::Auto => return Output::TE(transfer), - }; - Output::Encoder(enc) -} - -fn streaming_encoding( - buf: BytesMut, version: Version, req: &mut ClientRequest, -) -> TransferEncoding { - if req.chunked() { - // Enable transfer encoding - req.headers_mut().remove(CONTENT_LENGTH); - if version == Version::HTTP_2 { - req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) - } else { - req.headers_mut() - .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) - } - } else { - // if Content-Length is specified, then use it as length hint - let (len, chunked) = if let Some(len) = req.headers().get(CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - (Some(len), false) - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - (None, true) - }; - - if !chunked { - if let Some(len) = len { - TransferEncoding::length(len, buf) - } else { - TransferEncoding::eof(buf) - } - } else { - // Enable transfer encoding - match version { - Version::HTTP_11 => { - req.headers_mut() - .insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); - TransferEncoding::chunked(buf) - } - _ => { - req.headers_mut().remove(TRANSFER_ENCODING); - TransferEncoding::eof(buf) - } - } - } - } -} - -// "Sun, 06 Nov 1994 08:49:37 GMT".len() -pub const DATE_VALUE_LENGTH: usize = 29; - -fn set_date(dst: &mut BytesMut) { - CACHED.with(|cache| { - let mut cache = cache.borrow_mut(); - let now = time::get_time(); - if now > cache.next_update { - cache.update(now); - } - dst.extend_from_slice(cache.buffer()); - }) -} - -struct CachedDate { - bytes: [u8; DATE_VALUE_LENGTH], - next_update: time::Timespec, -} - -thread_local!(static CACHED: RefCell = RefCell::new(CachedDate { - bytes: [0; DATE_VALUE_LENGTH], - next_update: time::Timespec::new(0, 0), -})); - -impl CachedDate { - fn buffer(&self) -> &[u8] { - &self.bytes[..] - } - - fn update(&mut self, now: time::Timespec) { - write!(&mut self.bytes[..], "{}", time::at_utc(now).rfc822()).unwrap(); - self.next_update = now + Duration::seconds(1); - self.next_update.nsec = 0; - } -} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 71a5af2d..00000000 --- a/src/context.rs +++ /dev/null @@ -1,294 +0,0 @@ -extern crate actix; - -use futures::sync::oneshot; -use futures::sync::oneshot::Sender; -use futures::{Async, Future, Poll}; -use smallvec::SmallVec; -use std::marker::PhantomData; - -use self::actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, -}; -use self::actix::fut::ActorFuture; -use self::actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, -}; - -use body::{Binary, Body}; -use error::{Error, ErrorInternalServerError}; -use httprequest::HttpRequest; - -pub trait ActorHttpContext: 'static { - fn disconnected(&mut self); - fn poll(&mut self) -> Poll>, Error>; -} - -#[derive(Debug)] -pub enum Frame { - Chunk(Option), - Drain(oneshot::Sender<()>), -} - -impl Frame { - pub fn len(&self) -> usize { - match *self { - Frame::Chunk(Some(ref bin)) => bin.len(), - _ => 0, - } - } -} - -/// Execution context for http actors -pub struct HttpContext -where - A: Actor>, -{ - inner: ContextParts, - stream: Option>, - request: HttpRequest, - disconnected: bool, -} - -impl ActorContext for HttpContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for HttpContext -where - A: Actor, -{ - #[inline] - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - #[inline] - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - #[inline] - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl HttpContext -where - A: Actor, -{ - #[inline] - /// Create a new HTTP Context from a request and an actor - pub fn create(req: HttpRequest, actor: A) -> Body { - let mb = Mailbox::default(); - let ctx = HttpContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - Body::Actor(Box::new(HttpContextFut::new(ctx, actor, mb))) - } - - /// Create a new HTTP Context - pub fn with_factory(req: HttpRequest, f: F) -> Body - where - F: FnOnce(&mut Self) -> A + 'static, - { - let mb = Mailbox::default(); - let mut ctx = HttpContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - - let act = f(&mut ctx); - Body::Actor(Box::new(HttpContextFut::new(ctx, act, mb))) - } -} - -impl HttpContext -where - A: Actor, -{ - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - self.request.state() - } - - /// Incoming request - #[inline] - pub fn request(&mut self) -> &mut HttpRequest { - &mut self.request - } - - /// Write payload - #[inline] - pub fn write>(&mut self, data: B) { - if !self.disconnected { - self.add_frame(Frame::Chunk(Some(data.into()))); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Indicate end of streaming payload. Also this method calls `Self::close`. - #[inline] - pub fn write_eof(&mut self) { - self.add_frame(Frame::Chunk(None)); - } - - /// Returns drain future - pub fn drain(&mut self) -> Drain { - let (tx, rx) = oneshot::channel(); - self.add_frame(Frame::Drain(tx)); - Drain::new(rx) - } - - /// Check if connection still open - #[inline] - pub fn connected(&self) -> bool { - !self.disconnected - } - - #[inline] - fn add_frame(&mut self, frame: Frame) { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - if let Some(s) = self.stream.as_mut() { - s.push(frame) - } - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } -} - -impl AsyncContextParts for HttpContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct HttpContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl HttpContextFut -where - A: Actor>, -{ - fn new(ctx: HttpContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - HttpContextFut { fut } - } -} - -impl ActorHttpContext for HttpContextFut -where - A: Actor>, - S: 'static, -{ - #[inline] - fn disconnected(&mut self) { - self.fut.ctx().disconnected = true; - self.fut.ctx().stop(); - } - - fn poll(&mut self) -> Poll>, Error> { - if self.fut.alive() { - match self.fut.poll() { - Ok(Async::NotReady) | Ok(Async::Ready(())) => (), - Err(_) => return Err(ErrorInternalServerError("error")), - } - } - - // frames - if let Some(data) = self.fut.ctx().stream.take() { - Ok(Async::Ready(Some(data))) - } else if self.fut.alive() { - Ok(Async::NotReady) - } else { - Ok(Async::Ready(None)) - } - } -} - -impl ToEnvelope for HttpContext -where - A: Actor> + Handler, - M: Message + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} - -/// Consume a future -pub struct Drain { - fut: oneshot::Receiver<()>, - _a: PhantomData, -} - -impl Drain { - /// Create a drain from a future - pub fn new(fut: oneshot::Receiver<()>) -> Self { - Drain { - fut, - _a: PhantomData, - } - } -} - -impl ActorFuture for Drain { - type Item = (); - type Error = (); - type Actor = A; - - #[inline] - fn poll( - &mut self, _: &mut A, _: &mut ::Context, - ) -> Poll { - self.fut.poll().map_err(|_| ()) - } -} diff --git a/src/de.rs b/src/de.rs deleted file mode 100644 index 05f8914f..00000000 --- a/src/de.rs +++ /dev/null @@ -1,455 +0,0 @@ -use std::rc::Rc; - -use serde::de::{self, Deserializer, Error as DeError, Visitor}; - -use httprequest::HttpRequest; -use param::ParamsIter; -use uri::RESERVED_QUOTER; - -macro_rules! unsupported_type { - ($trait_fn:ident, $name:expr) => { - fn $trait_fn(self, _: V) -> Result - where V: Visitor<'de> - { - Err(de::value::Error::custom(concat!("unsupported type: ", $name))) - } - }; -} - -macro_rules! percent_decode_if_needed { - ($value:expr, $decode:expr) => { - if $decode { - if let Some(ref mut value) = RESERVED_QUOTER.requote($value.as_bytes()) { - Rc::make_mut(value).parse() - } else { - $value.parse() - } - } else { - $value.parse() - } - } -} - -macro_rules! parse_single_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { - fn $trait_fn(self, visitor: V) -> Result - where V: Visitor<'de> - { - if self.req.match_info().len() != 1 { - Err(de::value::Error::custom( - format!("wrong number of parameters: {} expected 1", - self.req.match_info().len()).as_str())) - } else { - let v_parsed = percent_decode_if_needed!(&self.req.match_info()[0], self.decode) - .map_err(|_| de::value::Error::custom( - format!("can not parse {:?} to a {}", &self.req.match_info()[0], $tp) - ))?; - visitor.$visit_fn(v_parsed) - } - } - } -} - -pub struct PathDeserializer<'de, S: 'de> { - req: &'de HttpRequest, - decode: bool, -} - -impl<'de, S: 'de> PathDeserializer<'de, S> { - pub fn new(req: &'de HttpRequest, decode: bool) -> Self { - PathDeserializer { req, decode } - } -} - -impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { - type Error = de::value::Error; - - fn deserialize_map(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_map(ParamsDeserializer { - params: self.req.match_info().iter(), - current: None, - decode: self.decode, - }) - } - - fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_map(visitor) - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.deserialize_unit(visitor) - } - - fn deserialize_newtype_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_tuple( - self, len: usize, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() < len { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected {}", - self.req.match_info().len(), - len - ).as_str(), - )) - } else { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - decode: self.decode, - }) - } - } - - fn deserialize_tuple_struct( - self, _: &'static str, len: usize, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - if self.req.match_info().len() < len { - Err(de::value::Error::custom( - format!( - "wrong number of parameters: {} expected {}", - self.req.match_info().len(), - len - ).as_str(), - )) - } else { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - decode: self.decode, - }) - } - } - - fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: enum")) - } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_seq(ParamsSeq { - params: self.req.match_info().iter(), - decode: self.decode, - }) - } - - unsupported_type!(deserialize_any, "'any'"); - unsupported_type!(deserialize_bytes, "bytes"); - unsupported_type!(deserialize_option, "Option"); - unsupported_type!(deserialize_identifier, "identifier"); - unsupported_type!(deserialize_ignored_any, "ignored_any"); - - parse_single_value!(deserialize_bool, visit_bool, "bool"); - parse_single_value!(deserialize_i8, visit_i8, "i8"); - parse_single_value!(deserialize_i16, visit_i16, "i16"); - parse_single_value!(deserialize_i32, visit_i32, "i32"); - parse_single_value!(deserialize_i64, visit_i64, "i64"); - parse_single_value!(deserialize_u8, visit_u8, "u8"); - parse_single_value!(deserialize_u16, visit_u16, "u16"); - parse_single_value!(deserialize_u32, visit_u32, "u32"); - parse_single_value!(deserialize_u64, visit_u64, "u64"); - parse_single_value!(deserialize_f32, visit_f32, "f32"); - parse_single_value!(deserialize_f64, visit_f64, "f64"); - parse_single_value!(deserialize_string, visit_string, "String"); - parse_single_value!(deserialize_str, visit_string, "String"); - parse_single_value!(deserialize_byte_buf, visit_string, "String"); - parse_single_value!(deserialize_char, visit_char, "char"); - -} - -struct ParamsDeserializer<'de> { - params: ParamsIter<'de>, - current: Option<(&'de str, &'de str)>, - decode: bool, -} - -impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> { - type Error = de::value::Error; - - fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> - where - K: de::DeserializeSeed<'de>, - { - self.current = self.params.next().map(|ref item| (item.0, item.1)); - match self.current { - Some((key, _)) => Ok(Some(seed.deserialize(Key { key })?)), - None => Ok(None), - } - } - - fn next_value_seed(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - if let Some((_, value)) = self.current.take() { - seed.deserialize(Value { value, decode: self.decode }) - } else { - Err(de::value::Error::custom("unexpected item")) - } - } -} - -struct Key<'de> { - key: &'de str, -} - -impl<'de> Deserializer<'de> for Key<'de> { - type Error = de::value::Error; - - fn deserialize_identifier(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_str(self.key) - } - - fn deserialize_any(self, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("Unexpected")) - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes - byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum ignored_any - } -} - -macro_rules! parse_value { - ($trait_fn:ident, $visit_fn:ident, $tp:tt) => { - fn $trait_fn(self, visitor: V) -> Result - where V: Visitor<'de> - { - let v_parsed = percent_decode_if_needed!(&self.value, self.decode) - .map_err(|_| de::value::Error::custom( - format!("can not parse {:?} to a {}", &self.value, $tp) - ))?; - visitor.$visit_fn(v_parsed) - } - } -} - -struct Value<'de> { - value: &'de str, - decode: bool, -} - -impl<'de> Deserializer<'de> for Value<'de> { - type Error = de::value::Error; - - parse_value!(deserialize_bool, visit_bool, "bool"); - parse_value!(deserialize_i8, visit_i8, "i8"); - parse_value!(deserialize_i16, visit_i16, "i16"); - parse_value!(deserialize_i32, visit_i32, "i16"); - parse_value!(deserialize_i64, visit_i64, "i64"); - parse_value!(deserialize_u8, visit_u8, "u8"); - parse_value!(deserialize_u16, visit_u16, "u16"); - parse_value!(deserialize_u32, visit_u32, "u32"); - parse_value!(deserialize_u64, visit_u64, "u64"); - parse_value!(deserialize_f32, visit_f32, "f32"); - parse_value!(deserialize_f64, visit_f64, "f64"); - parse_value!(deserialize_string, visit_string, "String"); - parse_value!(deserialize_byte_buf, visit_string, "String"); - parse_value!(deserialize_char, visit_char, "char"); - - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_unit_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_unit() - } - - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_bytes(self.value.as_bytes()) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_borrowed_str(self.value) - } - - fn deserialize_option(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_some(self) - } - - fn deserialize_enum( - self, _: &'static str, _: &'static [&'static str], visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_enum(ValueEnum { value: self.value }) - } - - fn deserialize_newtype_struct( - self, _: &'static str, visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_tuple(self, _: usize, _: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: tuple")) - } - - fn deserialize_struct( - self, _: &'static str, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: struct")) - } - - fn deserialize_tuple_struct( - self, _: &'static str, _: usize, _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("unsupported type: tuple struct")) - } - - unsupported_type!(deserialize_any, "any"); - unsupported_type!(deserialize_seq, "seq"); - unsupported_type!(deserialize_map, "map"); - unsupported_type!(deserialize_identifier, "identifier"); -} - -struct ParamsSeq<'de> { - params: ParamsIter<'de>, - decode: bool, -} - -impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> { - type Error = de::value::Error; - - fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> - where - T: de::DeserializeSeed<'de>, - { - match self.params.next() { - Some(item) => Ok(Some(seed.deserialize(Value { value: item.1, decode: self.decode })?)), - None => Ok(None), - } - } -} - -struct ValueEnum<'de> { - value: &'de str, -} - -impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { - type Error = de::value::Error; - type Variant = UnitVariant; - - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> - where - V: de::DeserializeSeed<'de>, - { - Ok((seed.deserialize(Key { key: self.value })?, UnitVariant)) - } -} - -struct UnitVariant; - -impl<'de> de::VariantAccess<'de> for UnitVariant { - type Error = de::value::Error; - - fn unit_variant(self) -> Result<(), Self::Error> { - Ok(()) - } - - fn newtype_variant_seed(self, _seed: T) -> Result - where - T: de::DeserializeSeed<'de>, - { - Err(de::value::Error::custom("not supported")) - } - - fn tuple_variant(self, _len: usize, _visitor: V) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("not supported")) - } - - fn struct_variant( - self, _: &'static [&'static str], _: V, - ) -> Result - where - V: Visitor<'de>, - { - Err(de::value::Error::custom("not supported")) - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index f4ea981c..00000000 --- a/src/error.rs +++ /dev/null @@ -1,1426 +0,0 @@ -//! Error and Result module -use std::io::Error as IoError; -use std::str::Utf8Error; -use std::string::FromUtf8Error; -use std::sync::Mutex; -use std::{fmt, io, result}; - -use actix::{MailboxError, SendError}; -use cookie; -use failure::{self, Backtrace, Fail}; -use futures::Canceled; -use http::uri::InvalidUri; -use http::{header, Error as HttpError, StatusCode}; -use http2::Error as Http2Error; -use httparse; -use serde::de::value::Error as DeError; -use serde_json::error::Error as JsonError; -use serde_urlencoded::ser::Error as FormError; -use tokio_timer::Error as TimerError; -pub use url::ParseError as UrlParseError; - -// re-exports -pub use cookie::ParseError as CookieParseError; - -use handler::Responder; -use httprequest::HttpRequest; -use httpresponse::{HttpResponse, HttpResponseParts}; - -/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) -/// for actix web operations -/// -/// 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; - -/// General purpose actix web error. -/// -/// An actix web error is used to carry errors from `failure` or `std::error` -/// through actix in a convenient way. It can be created through -/// converting errors with `into()`. -/// -/// Whenever it is created from an external object a response error is created -/// for it that can be used to create an http response from it this means that -/// if you have access to an actix `Error` you can always get a -/// `ResponseError` reference from it. -pub struct Error { - cause: Box, - backtrace: Option, -} - -impl Error { - /// Deprecated way to reference the underlying response error. - #[deprecated( - since = "0.6.0", - note = "please use `Error::as_response_error()` instead" - )] - pub fn cause(&self) -> &ResponseError { - self.cause.as_ref() - } - - /// Returns a reference to the underlying cause of this `Error` as `Fail` - pub fn as_fail(&self) -> &Fail { - self.cause.as_fail() - } - - /// Returns the reference to the underlying `ResponseError`. - pub fn as_response_error(&self) -> &ResponseError { - self.cause.as_ref() - } - - /// Returns a reference to the Backtrace carried by this error, if it - /// carries one. - /// - /// This uses the same `Backtrace` type that `failure` uses. - pub fn backtrace(&self) -> &Backtrace { - if let Some(bt) = self.cause.backtrace() { - bt - } else { - self.backtrace.as_ref().unwrap() - } - } - - /// Attempts to downcast this `Error` to a particular `Fail` type by - /// reference. - /// - /// If the underlying error is not of type `T`, this will return `None`. - pub fn downcast_ref(&self) -> Option<&T> { - // in the most trivial way the cause is directly of the requested type. - if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) { - return Some(rv); - } - - // in the more complex case the error has been constructed from a failure - // error. This happens because we implement From by - // calling compat() and then storing it here. In failure this is - // represented by a failure::Error being wrapped in a failure::Compat. - // - // So we first downcast into that compat, to then further downcast through - // the failure's Error downcasting system into the original failure. - let compat: Option<&failure::Compat> = - Fail::downcast_ref(self.cause.as_fail()); - compat.and_then(|e| e.get_ref().downcast_ref()) - } -} - -/// Helper trait to downcast a response error into a fail. -/// -/// This is currently not exposed because it's unclear if this is the best way -/// to achieve the downcasting on `Error` for which this is needed. -#[doc(hidden)] -pub trait InternalResponseErrorAsFail { - #[doc(hidden)] - fn as_fail(&self) -> &Fail; - #[doc(hidden)] - fn as_mut_fail(&mut self) -> &mut Fail; -} - -#[doc(hidden)] -impl InternalResponseErrorAsFail for T { - fn as_fail(&self) -> &Fail { - self - } - fn as_mut_fail(&mut self) -> &mut Fail { - self - } -} - -/// Error that can be converted to `HttpResponse` -pub trait ResponseError: Fail + InternalResponseErrorAsFail { - /// Create response for error - /// - /// Internal server error is generated by default. - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) - } -} - -impl ResponseError for SendError -where T: Send + Sync + 'static { -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.cause, f) - } -} - -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(bt) = self.cause.backtrace() { - write!(f, "{:?}\n\n{:?}", &self.cause, bt) - } else { - write!( - f, - "{:?}\n\n{:?}", - &self.cause, - self.backtrace.as_ref().unwrap() - ) - } - } -} - -/// Convert `Error` to a `HttpResponse` instance -impl From for HttpResponse { - fn from(err: Error) -> Self { - HttpResponse::from_error(err) - } -} - -/// `Error` for any error that implements `ResponseError` -impl From for Error { - fn from(err: T) -> Error { - let backtrace = if err.backtrace().is_none() { - Some(Backtrace::new()) - } else { - None - }; - Error { - cause: Box::new(err), - backtrace, - } - } -} - -/// Compatibility for `failure::Error` -impl ResponseError for failure::Compat where - T: fmt::Display + fmt::Debug + Sync + Send + 'static -{} - -impl From for Error { - fn from(err: failure::Error) -> Error { - err.compat().into() - } -} - -/// `InternalServerError` for `JsonError` -impl ResponseError for JsonError {} - -/// `InternalServerError` for `FormError` -impl ResponseError for FormError {} - -/// `InternalServerError` for `TimerError` -impl ResponseError for TimerError {} - -/// `InternalServerError` for `UrlParseError` -impl ResponseError for UrlParseError {} - -/// Return `BAD_REQUEST` for `de::value::Error` -impl ResponseError for DeError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// Return `BAD_REQUEST` for `Utf8Error` -impl ResponseError for Utf8Error { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// Return `InternalServerError` for `HttpError`, -/// Response generation can return `HttpError`, so it is internal error -impl ResponseError for HttpError {} - -/// Return `InternalServerError` for `io::Error` -impl ResponseError for io::Error { - fn error_response(&self) -> HttpResponse { - match self.kind() { - io::ErrorKind::NotFound => HttpResponse::new(StatusCode::NOT_FOUND), - io::ErrorKind::PermissionDenied => HttpResponse::new(StatusCode::FORBIDDEN), - _ => HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR), - } - } -} - -/// `BadRequest` for `InvalidHeaderValue` -impl ResponseError for header::InvalidHeaderValue { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// `BadRequest` for `InvalidHeaderValue` -impl ResponseError for header::InvalidHeaderValueBytes { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// `InternalServerError` for `futures::Canceled` -impl ResponseError for Canceled {} - -/// `InternalServerError` for `actix::MailboxError` -impl ResponseError for MailboxError {} - -/// A set of errors that can occur during parsing HTTP streams -#[derive(Fail, Debug)] -pub enum ParseError { - /// An invalid `Method`, such as `GE.T`. - #[fail(display = "Invalid Method specified")] - Method, - /// An invalid `Uri`, such as `exam ple.domain`. - #[fail(display = "Uri error: {}", _0)] - Uri(InvalidUri), - /// An invalid `HttpVersion`, such as `HTP/1.1` - #[fail(display = "Invalid HTTP version specified")] - Version, - /// An invalid `Header`. - #[fail(display = "Invalid Header provided")] - Header, - /// A message head is too large to be reasonable. - #[fail(display = "Message head is too large")] - TooLarge, - /// A message reached EOF, but is not complete. - #[fail(display = "Message is incomplete")] - Incomplete, - /// An invalid `Status`, such as `1337 ELITE`. - #[fail(display = "Invalid Status provided")] - Status, - /// A timeout occurred waiting for an IO event. - #[allow(dead_code)] - #[fail(display = "Timeout")] - Timeout, - /// An `io::Error` that occurred while trying to read or write to a network - /// stream. - #[fail(display = "IO error: {}", _0)] - Io(#[cause] IoError), - /// Parsing a field as string failed - #[fail(display = "UTF8 error: {}", _0)] - Utf8(#[cause] Utf8Error), -} - -/// Return `BadRequest` for `ParseError` -impl ResponseError for ParseError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -impl From for ParseError { - fn from(err: IoError) -> ParseError { - ParseError::Io(err) - } -} - -impl From for ParseError { - fn from(err: InvalidUri) -> ParseError { - ParseError::Uri(err) - } -} - -impl From for ParseError { - fn from(err: Utf8Error) -> ParseError { - ParseError::Utf8(err) - } -} - -impl From for ParseError { - fn from(err: FromUtf8Error) -> ParseError { - ParseError::Utf8(err.utf8_error()) - } -} - -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::Status => ParseError::Status, - httparse::Error::TooManyHeaders => ParseError::TooLarge, - httparse::Error::Version => ParseError::Version, - } - } -} - -#[derive(Fail, Debug)] -/// A set of errors that can occur during payload parsing -pub enum PayloadError { - /// A payload reached EOF, but is not complete. - #[fail(display = "A payload reached EOF, but is not complete.")] - Incomplete, - /// 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, - /// Io error - #[fail(display = "{}", _0)] - Io(#[cause] IoError), - /// Http2 error - #[fail(display = "{}", _0)] - Http2(#[cause] Http2Error), -} - -impl From for PayloadError { - fn from(err: IoError) -> PayloadError { - PayloadError::Io(err) - } -} - -/// `PayloadError` returns two possible results: -/// -/// - `Overflow` returns `PayloadTooLarge` -/// - Other errors returns `BadRequest` -impl ResponseError for PayloadError { - fn error_response(&self) -> HttpResponse { - match *self { - PayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => HttpResponse::new(StatusCode::BAD_REQUEST), - } - } -} - -/// Return `BadRequest` for `cookie::ParseError` -impl ResponseError for cookie::ParseError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// A set of errors that can occur during parsing multipart streams -#[derive(Fail, Debug)] -pub enum MultipartError { - /// Content-Type header is not found - #[fail(display = "No Content-type header found")] - NoContentType, - /// Can not parse Content-Type header - #[fail(display = "Can not parse Content-Type header")] - ParseContentType, - /// Multipart boundary is not found - #[fail(display = "Multipart boundary is not found")] - Boundary, - /// Multipart stream is incomplete - #[fail(display = "Multipart stream is incomplete")] - Incomplete, - /// Error during field parsing - #[fail(display = "{}", _0)] - Parse(#[cause] ParseError), - /// Payload error - #[fail(display = "{}", _0)] - Payload(#[cause] PayloadError), -} - -impl From for MultipartError { - fn from(err: ParseError) -> MultipartError { - MultipartError::Parse(err) - } -} - -impl From for MultipartError { - fn from(err: PayloadError) -> MultipartError { - MultipartError::Payload(err) - } -} - -/// Return `BadRequest` for `MultipartError` -impl ResponseError for MultipartError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// Error during handling `Expect` header -#[derive(Fail, PartialEq, Debug)] -pub enum ExpectError { - /// Expect header value can not be converted to utf8 - #[fail(display = "Expect header value can not be converted to utf8")] - Encoding, - /// Unknown expect value - #[fail(display = "Unknown expect value")] - UnknownExpect, -} - -impl ResponseError for ExpectError { - fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::EXPECTATION_FAILED, "Unknown Expect") - } -} - -/// A set of error that can occure during parsing content type -#[derive(Fail, PartialEq, Debug)] -pub enum ContentTypeError { - /// Can not parse content type - #[fail(display = "Can not parse content type")] - ParseError, - /// Unknown content encoding - #[fail(display = "Unknown content encoding")] - UnknownEncoding, -} - -/// Return `BadRequest` for `ContentTypeError` -impl ResponseError for ContentTypeError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -/// A set of errors that can occur during parsing urlencoded payloads -#[derive(Fail, Debug)] -pub enum UrlencodedError { - /// Can not decode chunked transfer encoding - #[fail(display = "Can not decode chunked transfer encoding")] - Chunked, - /// Payload size is bigger than allowed. (default: 256kB) - #[fail( - display = "Urlencoded payload size is bigger than allowed. (default: 256kB)" - )] - Overflow, - /// Payload size is now known - #[fail(display = "Payload size is now known")] - UnknownLength, - /// Content type error - #[fail(display = "Content type error")] - ContentType, - /// Parse error - #[fail(display = "Parse error")] - Parse, - /// Payload error - #[fail(display = "Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), -} - -/// Return `BadRequest` for `UrlencodedError` -impl ResponseError for UrlencodedError { - fn error_response(&self) -> HttpResponse { - match *self { - UrlencodedError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - UrlencodedError::UnknownLength => { - HttpResponse::new(StatusCode::LENGTH_REQUIRED) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), - } - } -} - -impl From for UrlencodedError { - fn from(err: PayloadError) -> UrlencodedError { - UrlencodedError::Payload(err) - } -} - -/// A set of errors that can occur during parsing json payloads -#[derive(Fail, Debug)] -pub enum JsonPayloadError { - /// Payload size is bigger than allowed. (default: 256kB) - #[fail(display = "Json payload size is bigger than allowed. (default: 256kB)")] - Overflow, - /// Content type error - #[fail(display = "Content type error")] - ContentType, - /// Deserialize error - #[fail(display = "Json deserialize error: {}", _0)] - Deserialize(#[cause] JsonError), - /// Payload error - #[fail(display = "Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), -} - -/// Return `BadRequest` for `UrlencodedError` -impl ResponseError for JsonPayloadError { - fn error_response(&self) -> HttpResponse { - match *self { - JsonPayloadError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), - } - } -} - -impl From for JsonPayloadError { - fn from(err: PayloadError) -> JsonPayloadError { - JsonPayloadError::Payload(err) - } -} - -impl From for JsonPayloadError { - fn from(err: JsonError) -> JsonPayloadError { - JsonPayloadError::Deserialize(err) - } -} - -/// Error type returned when reading body as lines. -pub enum ReadlinesError { - /// Error when decoding a line. - EncodingError, - /// Payload error. - PayloadError(PayloadError), - /// Line limit exceeded. - LimitOverflow, - /// ContentType error. - ContentTypeError(ContentTypeError), -} - -impl From for ReadlinesError { - fn from(err: PayloadError) -> Self { - ReadlinesError::PayloadError(err) - } -} - -impl From for ReadlinesError { - fn from(err: ContentTypeError) -> Self { - ReadlinesError::ContentTypeError(err) - } -} - -/// 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) - } -} - -/// Errors which can occur when attempting to generate resource uri. -#[derive(Fail, Debug, PartialEq)] -pub enum UrlGenerationError { - /// Resource not found - #[fail(display = "Resource not found")] - ResourceNotFound, - /// Not all path pattern covered - #[fail(display = "Not all path pattern covered")] - NotEnoughElements, - /// URL parse error - #[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) - } -} - -/// Errors which can occur when serving static files. -#[derive(Fail, Debug, PartialEq)] -pub enum StaticFileError { - /// Path is not a directory - #[fail(display = "Path is not a directory. Unable to serve static files")] - IsNotDirectory, - /// Cannot render directory - #[fail(display = "Unable to render directory without index file")] - IsDirectory, -} - -/// Return `NotFound` for `StaticFileError` -impl ResponseError for StaticFileError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::NOT_FOUND) - } -} - -/// Helper type that can wrap any error and generate custom response. -/// -/// In following example any `io::Error` will be converted into "BAD REQUEST" -/// response as opposite to *INTERNAL 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() {} -/// ``` -pub struct InternalError { - cause: T, - status: InternalErrorType, - backtrace: Backtrace, -} - -enum InternalErrorType { - Status(StatusCode), - Response(Box>>), -} - -impl InternalError { - /// Create `InternalError` instance - pub fn new(cause: T, status: StatusCode) -> Self { - InternalError { - cause, - status: InternalErrorType::Status(status), - backtrace: Backtrace::new(), - } - } - - /// Create `InternalError` with predefined `HttpResponse`. - pub fn from_response(cause: T, response: HttpResponse) -> Self { - let resp = response.into_parts(); - InternalError { - cause, - status: InternalErrorType::Response(Box::new(Mutex::new(Some(resp)))), - backtrace: Backtrace::new(), - } - } -} - -impl Fail for InternalError -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - fn backtrace(&self) -> Option<&Backtrace> { - Some(&self.backtrace) - } -} - -impl fmt::Debug for InternalError -where - T: Send + Sync + fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.cause, f) - } -} - -impl fmt::Display for InternalError -where - T: Send + Sync + fmt::Display + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.cause, f) - } -} - -impl ResponseError for InternalError -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - fn error_response(&self) -> HttpResponse { - match self.status { - InternalErrorType::Status(st) => HttpResponse::new(st), - InternalErrorType::Response(ref resp) => { - if let Some(resp) = resp.lock().unwrap().take() { - HttpResponse::from_parts(resp) - } else { - HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) - } - } - } - } -} - -impl Responder for InternalError -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: &HttpRequest) -> Result { - Err(self.into()) - } -} - -/// Helper function that creates wrapper of any error and generate *BAD -/// REQUEST* response. -#[allow(non_snake_case)] -pub fn ErrorBadRequest(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::BAD_REQUEST).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNAUTHORIZED* response. -#[allow(non_snake_case)] -pub fn ErrorUnauthorized(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNAUTHORIZED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PAYMENT_REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorPaymentRequired(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate *FORBIDDEN* -/// response. -#[allow(non_snake_case)] -pub fn ErrorForbidden(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::FORBIDDEN).into() -} - -/// Helper function that creates wrapper of any error and generate *NOT FOUND* -/// response. -#[allow(non_snake_case)] -pub fn ErrorNotFound(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_FOUND).into() -} - -/// Helper function that creates wrapper of any error and generate *METHOD NOT -/// ALLOWED* response. -#[allow(non_snake_case)] -pub fn ErrorMethodNotAllowed(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() -} - -/// Helper function that creates wrapper of any error and generate *NOT -/// ACCEPTABLE* response. -#[allow(non_snake_case)] -pub fn ErrorNotAcceptable(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into() -} - -/// Helper function that creates wrapper of any error and generate *PROXY -/// AUTHENTICATION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorProxyAuthenticationRequired(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate *REQUEST -/// TIMEOUT* response. -#[allow(non_snake_case)] -pub fn ErrorRequestTimeout(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into() -} - -/// Helper function that creates wrapper of any error and generate *CONFLICT* -/// response. -#[allow(non_snake_case)] -pub fn ErrorConflict(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::CONFLICT).into() -} - -/// Helper function that creates wrapper of any error and generate *GONE* -/// response. -#[allow(non_snake_case)] -pub fn ErrorGone(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::GONE).into() -} - -/// Helper function that creates wrapper of any error and generate *LENGTH -/// REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorLengthRequired(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LENGTH_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PRECONDITION FAILED* response. -#[allow(non_snake_case)] -pub fn ErrorPreconditionFailed(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PAYLOAD TOO LARGE* response. -#[allow(non_snake_case)] -pub fn ErrorPayloadTooLarge(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *URI TOO LONG* response. -#[allow(non_snake_case)] -pub fn ErrorUriTooLong(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::URI_TOO_LONG).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNSUPPORTED MEDIA TYPE* response. -#[allow(non_snake_case)] -pub fn ErrorUnsupportedMediaType(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *RANGE NOT SATISFIABLE* response. -#[allow(non_snake_case)] -pub fn ErrorRangeNotSatisfiable(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *EXPECTATION FAILED* response. -#[allow(non_snake_case)] -pub fn ErrorExpectationFailed(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *IM A TEAPOT* response. -#[allow(non_snake_case)] -pub fn ErrorImATeapot(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::IM_A_TEAPOT).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *MISDIRECTED REQUEST* response. -#[allow(non_snake_case)] -pub fn ErrorMisdirectedRequest(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNPROCESSABLE ENTITY* response. -#[allow(non_snake_case)] -pub fn ErrorUnprocessableEntity(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *LOCKED* response. -#[allow(non_snake_case)] -pub fn ErrorLocked(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LOCKED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *FAILED DEPENDENCY* response. -#[allow(non_snake_case)] -pub fn ErrorFailedDependency(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UPGRADE REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorUpgradeRequired(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PRECONDITION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorPreconditionRequired(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *TOO MANY REQUESTS* response. -#[allow(non_snake_case)] -pub fn ErrorTooManyRequests(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *REQUEST HEADER FIELDS TOO LARGE* response. -#[allow(non_snake_case)] -pub fn ErrorRequestHeaderFieldsTooLarge(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNAVAILABLE FOR LEGAL REASONS* response. -#[allow(non_snake_case)] -pub fn ErrorUnavailableForLegalReasons(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *INTERNAL SERVER ERROR* response. -#[allow(non_snake_case)] -pub fn ErrorInternalServerError(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NOT IMPLEMENTED* response. -#[allow(non_snake_case)] -pub fn ErrorNotImplemented(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *BAD GATEWAY* response. -#[allow(non_snake_case)] -pub fn ErrorBadGateway(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::BAD_GATEWAY).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *SERVICE UNAVAILABLE* response. -#[allow(non_snake_case)] -pub fn ErrorServiceUnavailable(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *GATEWAY TIMEOUT* response. -#[allow(non_snake_case)] -pub fn ErrorGatewayTimeout(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *HTTP VERSION NOT SUPPORTED* response. -#[allow(non_snake_case)] -pub fn ErrorHttpVersionNotSupported(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *VARIANT ALSO NEGOTIATES* response. -#[allow(non_snake_case)] -pub fn ErrorVariantAlsoNegotiates(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *INSUFFICIENT STORAGE* response. -#[allow(non_snake_case)] -pub fn ErrorInsufficientStorage(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *LOOP DETECTED* response. -#[allow(non_snake_case)] -pub fn ErrorLoopDetected(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LOOP_DETECTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NOT EXTENDED* response. -#[allow(non_snake_case)] -pub fn ErrorNotExtended(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_EXTENDED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NETWORK AUTHENTICATION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorNetworkAuthenticationRequired(err: T) -> Error -where - T: Send + Sync + fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() -} - -#[cfg(test)] -mod tests { - use super::*; - use cookie::ParseError as CookieParseError; - use failure; - use http::{Error as HttpError, StatusCode}; - use httparse; - use std::env; - use std::error::Error as StdError; - use std::io; - - #[test] - #[cfg(actix_nightly)] - fn test_nightly() { - let resp: HttpResponse = - IoError::new(io::ErrorKind::Other, "test").error_response(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[test] - fn test_into_response() { - let resp: HttpResponse = ParseError::Incomplete.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let resp: HttpResponse = CookieParseError::EmptyName.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let resp: HttpResponse = MultipartError::Boundary.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); - let resp: HttpResponse = err.error_response(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[test] - fn test_as_fail() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.description().to_owned(); - let e = ParseError::Io(orig); - assert_eq!(format!("{}", e.cause().unwrap()), desc); - } - - #[test] - fn test_backtrace() { - let e = ErrorBadRequest("err"); - let _ = e.backtrace(); - } - - #[test] - fn test_error_cause() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.description().to_owned(); - let e = Error::from(orig); - assert_eq!(format!("{}", e.as_fail()), desc); - } - - #[test] - fn test_error_display() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.description().to_owned(); - let e = Error::from(orig); - assert_eq!(format!("{}", e), desc); - } - - #[test] - fn test_error_http_response() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let e = Error::from(orig); - let resp: HttpResponse = e.into(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[test] - fn test_expect_error() { - let resp: HttpResponse = ExpectError::Encoding.error_response(); - assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); - let resp: HttpResponse = ExpectError::UnknownExpect.error_response(); - assert_eq!(resp.status(), StatusCode::EXPECTATION_FAILED); - } - - macro_rules! from { - ($from:expr => $error:pat) => { - match ParseError::from($from) { - e @ $error => { - assert!(format!("{}", e).len() >= 5); - } - e => unreachable!("{:?}", e), - } - }; - } - - macro_rules! from_and_cause { - ($from:expr => $error:pat) => { - match ParseError::from($from) { - e @ $error => { - let desc = format!("{}", e.cause().unwrap()); - assert_eq!(desc, $from.description().to_owned()); - } - _ => unreachable!("{:?}", $from), - } - }; - } - - #[test] - fn test_from() { - from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); - - from!(httparse::Error::HeaderName => ParseError::Header); - from!(httparse::Error::HeaderName => ParseError::Header); - from!(httparse::Error::HeaderValue => ParseError::Header); - from!(httparse::Error::NewLine => ParseError::Header); - from!(httparse::Error::Status => ParseError::Status); - from!(httparse::Error::Token => ParseError::Header); - from!(httparse::Error::TooManyHeaders => ParseError::TooLarge); - from!(httparse::Error::Version => ParseError::Version); - } - - #[test] - fn failure_error() { - const NAME: &str = "RUST_BACKTRACE"; - let old_tb = env::var(NAME); - env::set_var(NAME, "0"); - let error = failure::err_msg("Hello!"); - let resp: Error = error.into(); - assert_eq!( - format!("{:?}", resp), - "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n" - ); - match old_tb { - Ok(x) => env::set_var(NAME, x), - _ => env::remove_var(NAME), - } - } - - #[test] - fn test_internal_error() { - let err = InternalError::from_response( - ExpectError::Encoding, - HttpResponse::Ok().into(), - ); - let resp: HttpResponse = err.error_response(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_error_downcasting_direct() { - #[derive(Debug, Fail)] - #[fail(display = "demo error")] - struct DemoError; - - impl ResponseError for DemoError {} - - let err: Error = DemoError.into(); - let err_ref: &DemoError = err.downcast_ref().unwrap(); - assert_eq!(err_ref.to_string(), "demo error"); - } - - #[test] - fn test_error_downcasting_compat() { - #[derive(Debug, Fail)] - #[fail(display = "demo error")] - struct DemoError; - - impl ResponseError for DemoError {} - - let err: Error = failure::Error::from(DemoError).into(); - let err_ref: &DemoError = err.downcast_ref().unwrap(); - assert_eq!(err_ref.to_string(), "demo error"); - } - - #[test] - fn test_error_helpers() { - let r: HttpResponse = ErrorBadRequest("err").into(); - assert_eq!(r.status(), StatusCode::BAD_REQUEST); - - let r: HttpResponse = ErrorUnauthorized("err").into(); - assert_eq!(r.status(), StatusCode::UNAUTHORIZED); - - let r: HttpResponse = ErrorPaymentRequired("err").into(); - assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED); - - let r: HttpResponse = ErrorForbidden("err").into(); - assert_eq!(r.status(), StatusCode::FORBIDDEN); - - let r: HttpResponse = ErrorNotFound("err").into(); - assert_eq!(r.status(), StatusCode::NOT_FOUND); - - let r: HttpResponse = ErrorMethodNotAllowed("err").into(); - assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); - - let r: HttpResponse = ErrorNotAcceptable("err").into(); - assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE); - - let r: HttpResponse = ErrorProxyAuthenticationRequired("err").into(); - assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); - - let r: HttpResponse = ErrorRequestTimeout("err").into(); - assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); - - let r: HttpResponse = ErrorConflict("err").into(); - assert_eq!(r.status(), StatusCode::CONFLICT); - - let r: HttpResponse = ErrorGone("err").into(); - assert_eq!(r.status(), StatusCode::GONE); - - let r: HttpResponse = ErrorLengthRequired("err").into(); - assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED); - - let r: HttpResponse = ErrorPreconditionFailed("err").into(); - assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); - - let r: HttpResponse = ErrorPayloadTooLarge("err").into(); - assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE); - - let r: HttpResponse = ErrorUriTooLong("err").into(); - assert_eq!(r.status(), StatusCode::URI_TOO_LONG); - - let r: HttpResponse = ErrorUnsupportedMediaType("err").into(); - assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); - - let r: HttpResponse = ErrorRangeNotSatisfiable("err").into(); - assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE); - - let r: HttpResponse = ErrorExpectationFailed("err").into(); - assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); - - let r: HttpResponse = ErrorImATeapot("err").into(); - assert_eq!(r.status(), StatusCode::IM_A_TEAPOT); - - let r: HttpResponse = ErrorMisdirectedRequest("err").into(); - assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST); - - let r: HttpResponse = ErrorUnprocessableEntity("err").into(); - assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY); - - let r: HttpResponse = ErrorLocked("err").into(); - assert_eq!(r.status(), StatusCode::LOCKED); - - let r: HttpResponse = ErrorFailedDependency("err").into(); - assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY); - - let r: HttpResponse = ErrorUpgradeRequired("err").into(); - assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED); - - let r: HttpResponse = ErrorPreconditionRequired("err").into(); - assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED); - - let r: HttpResponse = ErrorTooManyRequests("err").into(); - assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS); - - let r: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into(); - assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); - - let r: HttpResponse = ErrorUnavailableForLegalReasons("err").into(); - assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); - - let r: HttpResponse = ErrorInternalServerError("err").into(); - assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); - - let r: HttpResponse = ErrorNotImplemented("err").into(); - assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED); - - let r: HttpResponse = ErrorBadGateway("err").into(); - assert_eq!(r.status(), StatusCode::BAD_GATEWAY); - - let r: HttpResponse = ErrorServiceUnavailable("err").into(); - assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE); - - let r: HttpResponse = ErrorGatewayTimeout("err").into(); - assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); - - let r: HttpResponse = ErrorHttpVersionNotSupported("err").into(); - assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); - - let r: HttpResponse = ErrorVariantAlsoNegotiates("err").into(); - assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); - - let r: HttpResponse = ErrorInsufficientStorage("err").into(); - assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE); - - let r: HttpResponse = ErrorLoopDetected("err").into(); - assert_eq!(r.status(), StatusCode::LOOP_DETECTED); - - let r: HttpResponse = ErrorNotExtended("err").into(); - assert_eq!(r.status(), StatusCode::NOT_EXTENDED); - - let r: HttpResponse = ErrorNetworkAuthenticationRequired("err").into(); - assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); - } -} diff --git a/src/extensions.rs b/src/extensions.rs deleted file mode 100644 index 430b87bd..00000000 --- a/src/extensions.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::fmt; -use std::hash::{BuildHasherDefault, Hasher}; - -struct IdHasher { - id: u64, -} - -impl Default for IdHasher { - fn default() -> IdHasher { - IdHasher { id: 0 } - } -} - -impl Hasher for IdHasher { - fn write(&mut self, bytes: &[u8]) { - for &x in bytes { - self.id.wrapping_add(u64::from(x)); - } - } - - fn write_u64(&mut self, u: u64) { - self.id = u; - } - - fn finish(&self) -> u64 { - self.id - } -} - -type AnyMap = HashMap, BuildHasherDefault>; - -#[derive(Default)] -/// A type map of request extensions. -pub struct Extensions { - map: AnyMap, -} - -impl Extensions { - /// Create an empty `Extensions`. - #[inline] - pub fn new() -> Extensions { - Extensions { - map: HashMap::default(), - } - } - - /// Insert a type into this `Extensions`. - /// - /// If a extension of this type already existed, it will - /// be returned. - pub fn insert(&mut self, val: T) { - self.map.insert(TypeId::of::(), Box::new(val)); - } - - /// Get a reference to a type previously inserted on this `Extensions`. - pub fn get(&self) -> Option<&T> { - self.map - .get(&TypeId::of::()) - .and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref()) - } - - /// Get a mutable reference to a type previously inserted on this `Extensions`. - pub fn get_mut(&mut self) -> Option<&mut T> { - self.map - .get_mut(&TypeId::of::()) - .and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut()) - } - - /// Remove a type from this `Extensions`. - /// - /// If a extension of this type existed, it will be returned. - pub fn remove(&mut self) -> Option { - self.map.remove(&TypeId::of::()).and_then(|boxed| { - (boxed as Box) - .downcast() - .ok() - .map(|boxed| *boxed) - }) - } - - /// Clear the `Extensions` of all inserted extensions. - #[inline] - pub fn clear(&mut self) { - self.map.clear(); - } -} - -impl fmt::Debug for Extensions { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Extensions").finish() - } -} - -#[test] -fn test_extensions() { - #[derive(Debug, PartialEq)] - struct MyType(i32); - - let mut extensions = Extensions::new(); - - extensions.insert(5i32); - extensions.insert(MyType(10)); - - assert_eq!(extensions.get(), Some(&5i32)); - assert_eq!(extensions.get_mut(), Some(&mut 5i32)); - - assert_eq!(extensions.remove::(), Some(5i32)); - assert!(extensions.get::().is_none()); - - assert_eq!(extensions.get::(), None); - assert_eq!(extensions.get(), Some(&MyType(10))); -} diff --git a/src/extractor.rs b/src/extractor.rs index 33705723..53209ad0 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,4 +1,3 @@ -use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::{fmt, str}; @@ -6,25 +5,34 @@ use std::{fmt, str}; use bytes::Bytes; use encoding::all::UTF_8; use encoding::types::{DecoderTrap, Encoding}; -use futures::{future, Async, Future, Poll}; +use futures::future::{err, ok, Either, FutureResult}; +use futures::{future, Async, Future, IntoFuture, Poll, Stream}; use mime::Mime; use serde::de::{self, DeserializeOwned}; +use serde::Serialize; +use serde_json; use serde_urlencoded; -use de::PathDeserializer; -use error::{Error, ErrorBadRequest, ErrorNotFound, UrlencodedError, ErrorConflict}; -use handler::{AsyncResult, FromRequest}; -use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; -use httprequest::HttpRequest; -use Either; +use actix_http::dev::{JsonBody, MessageBody, UrlEncoded}; +use actix_http::error::{ + Error, ErrorBadRequest, ErrorNotFound, JsonPayloadError, PayloadError, + UrlencodedError, +}; +use actix_http::http::StatusCode; +use actix_http::{HttpMessage, Response}; +use actix_router::PathDeserializer; + +use crate::handler::FromRequest; +use crate::request::HttpRequest; +use crate::responder::Responder; +use crate::service::ServiceRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's path. Information from the path is -/// URL decoded. Decoding of special characters can be disabled through `PathConfig`. +/// Extract typed information from the request's path. /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; @@ -48,7 +56,7 @@ use Either; /// It is possible to extract path information to a specific type that /// implements `Deserialize` trait from *serde*. /// -/// ```rust +/// ```rust,ignore /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; @@ -101,6 +109,15 @@ impl Path { pub fn into_inner(self) -> T { self.inner } + + /// Extract path information from a request + pub fn extract

    (req: &ServiceRequest

    ) -> Result, de::value::Error> + where + T: DeserializeOwned, + { + de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) + .map(|inner| Path { inner }) + } } impl From for Path { @@ -109,74 +126,16 @@ impl From for Path { } } -impl FromRequest for Path +impl FromRequest

    for Path where T: DeserializeOwned, { - type Config = PathConfig; - type Result = Result; + type Error = Error; + type Future = FutureResult; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req = req.clone(); - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - de::Deserialize::deserialize(PathDeserializer::new(&req, cfg.decode)) - .map_err(move |e| (*err)(e, &req2)) - .map(|inner| Path { inner }) - } -} - -/// Path extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{error, http, App, HttpResponse, Path, Result}; -/// -/// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: Path<(u32, String)>) -> Result { -/// Ok(format!("Welcome {}!", info.1)) -/// } -/// -/// fn main() { -/// let app = App::new().resource("/index.html/{id}/{name}", |r| { -/// r.method(http::Method::GET).with_config(index, |cfg| { -/// cfg.0.error_handler(|err, req| { -/// // <- create custom error response -/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() -/// }); -/// }) -/// }); -/// } -/// ``` -pub struct PathConfig { - ehandler: Rc) -> Error>, - decode: bool, -} -impl PathConfig { - /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self - where - F: Fn(serde_urlencoded::de::Error, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } - - /// Disable decoding of URL encoded special charaters from the path - pub fn disable_decoding(&mut self) -> &mut Self - { - self.decode = false; - self - } -} - -impl Default for PathConfig { - fn default() -> Self { - PathConfig { - ehandler: Rc::new(|e, _| ErrorNotFound(e)), - decode: true, - } + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + Self::extract(req).map_err(ErrorNotFound).into_future() } } @@ -193,11 +152,11 @@ impl fmt::Display for Path { } #[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's query. +/// Extract typed information from from the request's query. /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; @@ -253,70 +212,18 @@ impl Query { } } -impl FromRequest for Query +impl FromRequest

    for Query where T: de::DeserializeOwned, { - type Config = QueryConfig; - type Result = Result; + type Error = Error; + type Future = FutureResult; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { serde_urlencoded::from_str::(req.query_string()) - .map_err(move |e| (*err)(e, &req2)) - .map(Query) - } -} - -/// Query extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, http, App, HttpResponse, Query, Result}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: Query) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::GET).with_config(index, |cfg| { -/// cfg.0.error_handler(|err, req| { -/// // <- create custom error response -/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() -/// }); -/// }) -/// }); -/// } -/// ``` -pub struct QueryConfig { - ehandler: Rc) -> Error>, -} -impl QueryConfig { - /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self - where - F: Fn(serde_urlencoded::de::Error, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl Default for QueryConfig { - fn default() -> Self { - QueryConfig { - ehandler: Rc::new(|e, _| e.into()), - } + .map(|val| ok(Query(val))) + .unwrap_or_else(|e| err(e.into())) } } @@ -343,7 +250,7 @@ impl fmt::Display for Query { /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; /// use actix_web::{App, Form, Result}; @@ -384,16 +291,18 @@ impl DerefMut for Form { } } -impl FromRequest for Form +impl FromRequest

    for Form where T: DeserializeOwned + 'static, - S: 'static, + P: Stream + 'static, { - type Config = FormConfig; - type Result = Box>; + type Error = Error; + type Future = Box>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + let cfg = FormConfig::default(); + let req2 = req.clone(); let err = Rc::clone(&cfg.ehandler); Box::new( @@ -419,7 +328,7 @@ impl fmt::Display for Form { /// Form extractor configuration /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; /// use actix_web::{http, App, Form, Result}; @@ -446,12 +355,12 @@ impl fmt::Display for Form { /// ); /// } /// ``` -pub struct FormConfig { +pub struct FormConfig { limit: usize, - ehandler: Rc) -> Error>, + ehandler: Rc Error>, } -impl FormConfig { +impl FormConfig { /// Change max size of payload. By default max size is 256Kb pub fn limit(&mut self, limit: usize) -> &mut Self { self.limit = limit; @@ -461,14 +370,14 @@ impl FormConfig { /// Set custom error handler pub fn error_handler(&mut self, f: F) -> &mut Self where - F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, + F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, { self.ehandler = Rc::new(f); self } } -impl Default for FormConfig { +impl Default for FormConfig { fn default() -> Self { FormConfig { limit: 262_144, @@ -477,6 +386,205 @@ impl Default for FormConfig { } } +/// Json helper +/// +/// Json can be used for two different purpose. First is for json response +/// generation and second is for extracting typed information from request's +/// payload. +/// +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. +/// +/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction +/// process. +/// +/// ## Example +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Json, Result, http}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body +/// fn index(info: Json) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", +/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor +/// } +/// ``` +/// +/// 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,ignore +/// # 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 Json { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl Deref for Json { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Json { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Json +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Json: {:?}", self.0) + } +} + +impl fmt::Display for Json +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl Responder for Json { + type Error = Error; + type Future = FutureResult; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + let body = match serde_json::to_string(&self.0) { + Ok(body) => body, + Err(e) => return err(e.into()), + }; + + ok(Response::build(StatusCode::OK) + .content_type("application/json") + .body(body)) + } +} + +impl FromRequest

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

    ) -> Self::Future { + let cfg = JsonConfig::default(); + + let req2 = req.clone(); + let err = Rc::clone(&cfg.ehandler); + Box::new( + JsonBody::new(req) + .limit(cfg.limit) + .map_err(move |e| (*err)(e, &req2)) + .map(Json), + ) + } +} + +/// Json extractor configuration +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{error, http, App, HttpResponse, Json, Result}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: Json) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource("/index.html", |r| { +/// r.method(http::Method::POST) +/// .with_config(index, |cfg| { +/// cfg.0.limit(4096) // <- change json extractor configuration +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// }); +/// }) +/// }); +/// } +/// ``` +pub struct JsonConfig { + limit: usize, + ehandler: Rc Error>, +} + +impl JsonConfig { + /// Change max size of payload. By default max size is 256Kb + pub fn limit(&mut self, limit: usize) -> &mut Self { + self.limit = limit; + self + } + + /// Set custom error handler + pub fn error_handler(&mut self, f: F) -> &mut Self + where + F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, + { + self.ehandler = Rc::new(f); + self + } +} + +impl Default for JsonConfig { + fn default() -> Self { + JsonConfig { + limit: 262_144, + ehandler: Rc::new(|e, _| e.into()), + } + } +} + /// Request payload extractor. /// /// Loads request's payload and construct Bytes instance. @@ -486,7 +594,7 @@ impl Default for FormConfig { /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// extern crate bytes; /// # extern crate actix_web; /// use actix_web::{http, App, Result}; @@ -501,16 +609,23 @@ impl Default for FormConfig { /// .resource("/index.html", |r| r.method(http::Method::GET).with(index)); /// } /// ``` -impl FromRequest for Bytes { - type Config = PayloadConfig; - type Result = Result>, Error>; +impl

    FromRequest

    for Bytes +where + P: Stream + 'static, +{ + type Error = Error; + type Future = + Either>, FutureResult>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - // check content-type - cfg.check_mimetype(req)?; + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + let cfg = PayloadConfig::default(); - Ok(Box::new(MessageBody::new(req).limit(cfg.limit).from_err())) + if let Err(e) = cfg.check_mimetype(req) { + return Either::B(err(e)); + } + + Either::A(Box::new(MessageBody::new(req).limit(cfg.limit).from_err())) } } @@ -523,7 +638,7 @@ impl FromRequest for Bytes { /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, App, Result}; /// @@ -541,19 +656,30 @@ impl FromRequest for Bytes { /// }); /// } /// ``` -impl FromRequest for String { - type Config = PayloadConfig; - type Result = Result>, Error>; +impl

    FromRequest

    for String +where + P: Stream + 'static, +{ + type Error = Error; + type Future = + Either>, FutureResult>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + let cfg = PayloadConfig::default(); + // check content-type - cfg.check_mimetype(req)?; + if let Err(e) = cfg.check_mimetype(req) { + return Either::B(err(e)); + } // check charset - let encoding = req.encoding()?; + let encoding = match req.encoding() { + Ok(enc) => enc, + Err(e) => return Either::B(err(e.into())), + }; - Ok(Box::new( + Either::A(Box::new( MessageBody::new(req) .limit(cfg.limit) .from_err() @@ -579,7 +705,7 @@ impl FromRequest for String { /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// extern crate rand; /// #[macro_use] extern crate serde_derive; @@ -619,176 +745,30 @@ impl FromRequest for String { /// }); /// } /// ``` -impl FromRequest for Option +impl FromRequest

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

    , + T::Future: 'static, { - type Config = T::Config; - type Result = Box, Error = Error>>; + type Error = Error; + type Future = Box, Error = Error>>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then(|r| match r { + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + Box::new(T::from_request(req).then(|r| match r { Ok(v) => future::ok(Some(v)), Err(_) => future::ok(None), })) } } -/// Extract either one of two fields from the request. -/// -/// If both or none of the fields can be extracted, the default behaviour is to prefer the first -/// successful, last that failed. The behaviour can be changed by setting the appropriate -/// ```EitherCollisionStrategy```. -/// -/// CAVEAT: Most of the time both extractors will be run. Make sure that the extractors you specify -/// can be run one after another (or in parallel). This will always fail for extractors that modify -/// the request state (such as the `Form` extractors that read in the body stream). -/// So Either, Form> will not work correctly - it will only succeed if it matches the first -/// option, but will always fail to match the second (since the body stream will be at the end, and -/// appear to be empty). -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// use actix_web::Either; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } -/// -/// #[derive(Debug, Deserialize)] -/// struct OtherThing { id: String } -/// -/// impl FromRequest for Thing { -/// type Config = (); -/// type Result = Result; -/// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { -/// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) -/// } else { -/// Err(ErrorBadRequest("no luck")) -/// } -/// } -/// } -/// -/// impl FromRequest for OtherThing { -/// type Config = (); -/// type Result = Result; -/// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { -/// if rand::random() { -/// Ok(OtherThing { id: "otherthingy".into() }) -/// } else { -/// Err(ErrorBadRequest("no luck")) -/// } -/// } -/// } -/// -/// /// extract text data from request -/// fn index(supplied_thing: Either) -> Result { -/// match supplied_thing { -/// Either::A(thing) => Ok(format!("Got something: {:?}", thing)), -/// Either::B(other_thing) => Ok(format!("Got anotherthing: {:?}", other_thing)) -/// } -/// } -/// -/// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) -/// }); -/// } -/// ``` -impl FromRequest for Either where A: FromRequest, B: FromRequest { - type Config = EitherConfig; - type Result = AsyncResult>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let a = A::from_request(&req.clone(), &cfg.a).into().map(|a| Either::A(a)); - let b = B::from_request(req, &cfg.b).into().map(|b| Either::B(b)); - - match &cfg.collision_strategy { - EitherCollisionStrategy::PreferA => AsyncResult::future(Box::new(a.or_else(|_| b))), - EitherCollisionStrategy::PreferB => AsyncResult::future(Box::new(b.or_else(|_| a))), - EitherCollisionStrategy::FastestSuccessful => AsyncResult::future(Box::new(a.select2(b).then( |r| match r { - Ok(future::Either::A((ares, _b))) => AsyncResult::ok(ares), - Ok(future::Either::B((bres, _a))) => AsyncResult::ok(bres), - Err(future::Either::A((_aerr, b))) => AsyncResult::future(Box::new(b)), - Err(future::Either::B((_berr, a))) => AsyncResult::future(Box::new(a)) - }))), - EitherCollisionStrategy::ErrorA => AsyncResult::future(Box::new(b.then(|r| match r { - Err(_berr) => AsyncResult::future(Box::new(a)), - Ok(b) => AsyncResult::future(Box::new(a.then( |r| match r { - Ok(_a) => Err(ErrorConflict("Both wings of either extractor completed")), - Err(_arr) => Ok(b) - }))) - }))), - EitherCollisionStrategy::ErrorB => AsyncResult::future(Box::new(a.then(|r| match r { - Err(_aerr) => AsyncResult::future(Box::new(b)), - Ok(a) => AsyncResult::future(Box::new(b.then( |r| match r { - Ok(_b) => Err(ErrorConflict("Both wings of either extractor completed")), - Err(_berr) => Ok(a) - }))) - }))), - } - } -} - -/// Defines the result if neither or both of the extractors supplied to an Either extractor succeed. -#[derive(Debug)] -pub enum EitherCollisionStrategy { - /// If both are successful, return A, if both fail, return error of B - PreferA, - /// If both are successful, return B, if both fail, return error of A - PreferB, - /// Return result of the faster, error of the slower if both fail - FastestSuccessful, - /// Return error if both succeed, return error of A if both fail - ErrorA, - /// Return error if both succeed, return error of B if both fail - ErrorB -} - -impl Default for EitherCollisionStrategy { - fn default() -> Self { - EitherCollisionStrategy::FastestSuccessful - } -} - -///Determines Either extractor configuration -/// -///By default `EitherCollisionStrategy::FastestSuccessful` is used. -pub struct EitherConfig where A: FromRequest, B: FromRequest { - a: A::Config, - b: B::Config, - collision_strategy: EitherCollisionStrategy -} - -impl Default for EitherConfig where A: FromRequest, B: FromRequest { - fn default() -> Self { - EitherConfig { - a: A::Config::default(), - b: B::Config::default(), - collision_strategy: EitherCollisionStrategy::default() - } - } -} - /// Optionally extract a field from the request or extract the Error if unsuccessful /// /// If the FromRequest for T fails, inject Err into handler rather than returning an error response /// /// ## Example /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// extern crate rand; /// #[macro_use] extern crate serde_derive; @@ -798,12 +778,12 @@ impl Default for EitherConfig where A: FromRequest, B: FromRequ /// #[derive(Debug, Deserialize)] /// struct Thing { name: String } /// -/// impl FromRequest for Thing { +/// impl FromRequest for Thing { /// type Config = (); /// type Result = Result; /// /// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { +/// fn from_request(req: &Request, _cfg: &Self::Config) -> Self::Result { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { @@ -827,16 +807,21 @@ impl Default for EitherConfig where A: FromRequest, B: FromRequ /// }); /// } /// ``` -impl FromRequest for Result +impl FromRequest

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

    , + T::Future: 'static, + T::Error: 'static, { - type Config = T::Config; - type Result = Box, Error = Error>>; + type Error = Error; + type Future = Box, Error = Error>>; #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new(T::from_request(req, cfg).into().then(future::ok)) + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + Box::new(T::from_request(req).then(|res| match res { + Ok(v) => ok(Ok(v)), + Err(e) => ok(Err(e)), + })) } } @@ -860,7 +845,7 @@ impl PayloadConfig { self } - fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> { + fn check_mimetype

    (&self, req: &ServiceRequest

    ) -> Result<(), Error> { // check content-type if let Some(ref mt) = self.mimetype { match req.mime_type() { @@ -893,34 +878,26 @@ impl Default for PayloadConfig { macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { /// FromRequest implementation for tuple - impl + 'static),+> FromRequest for ($($T,)+) - where - S: 'static, + impl + 'static),+> FromRequest

    for ($($T,)+) { - type Config = ($($T::Config,)+); - type Result = Box>; + type Error = Error; + type Future = $fut_type; - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - Box::new($fut_type { - s: PhantomData, + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + $fut_type { items: <($(Option<$T>,)+)>::default(), - futs: ($(Some($T::from_request(req, &cfg.$n).into()),)+), - }) + futs: ($($T::from_request(req),)+), + } } } - struct $fut_type),+> - where - S: 'static, - { - s: PhantomData, + #[doc(hidden)] + pub struct $fut_type),+> { items: ($(Option<$T>,)+), - futs: ($(Option>,)+), + futs: ($($T::Future,)+), } - impl),+> Future for $fut_type - where - S: 'static, + impl),+> Future for $fut_type { type Item = ($($T,)+); type Error = Error; @@ -929,14 +906,13 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { let mut ready = true; $( - if self.futs.$n.is_some() { - match self.futs.$n.as_mut().unwrap().poll() { + if self.items.$n.is_none() { + match self.futs.$n.poll() { Ok(Async::Ready(item)) => { self.items.$n = Some(item); - self.futs.$n.take(); } Ok(Async::NotReady) => ready = false, - Err(e) => return Err(e), + Err(e) => return Err(e.into()), } } )+ @@ -952,10 +928,13 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { } }); -impl FromRequest for () { - type Config = (); - type Result = Self; - fn from_request(_req: &HttpRequest, _cfg: &Self::Config) -> Self::Result {} +impl

    FromRequest

    for () { + type Error = Error; + type Future = FutureResult<(), Error>; + + fn from_request(_req: &mut ServiceRequest

    ) -> Self::Future { + ok(()) + } } tuple_from_req!(TupleFromRequest1, (0, A)); @@ -1005,385 +984,298 @@ tuple_from_req!( (7, H), (8, I) ); +tuple_from_req!( + TupleFromRequest10, + (0, A), + (1, B), + (2, C), + (3, D), + (4, E), + (5, F), + (6, G), + (7, H), + (8, I), + (9, J) +); -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - use futures::{Async, Future}; - use http::header; - use mime; - use resource::Resource; - use router::{ResourceDef, Router}; - use test::TestRequest; +// #[cfg(test)] +// mod tests { +// use super::*; +// use actix_http::http::header; +// use actix_http::test::TestRequest; +// use bytes::Bytes; +// use futures::{Async, Future}; +// use mime; +// use serde::{Deserialize, Serialize}; - #[derive(Deserialize, Debug, PartialEq)] - struct Info { - hello: String, - } +// use crate::resource::Resource; +// // use crate::router::{ResourceDef, Router}; - #[derive(Deserialize, Debug, PartialEq)] - struct OtherInfo { - bye: String, - } +// #[derive(Deserialize, Debug, PartialEq)] +// struct Info { +// hello: String, +// } - #[test] - fn test_bytes() { - let cfg = PayloadConfig::default(); - let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); +// #[test] +// fn test_bytes() { +// let cfg = PayloadConfig::default(); +// let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") +// .set_payload(Bytes::from_static(b"hello=world")) +// .finish(); - match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s, Bytes::from_static(b"hello=world")); - } - _ => unreachable!(), - } - } +// match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { +// Async::Ready(s) => { +// assert_eq!(s, Bytes::from_static(b"hello=world")); +// } +// _ => unreachable!(), +// } +// } - #[test] - fn test_string() { - let cfg = PayloadConfig::default(); - let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); +// #[test] +// fn test_string() { +// let cfg = PayloadConfig::default(); +// let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") +// .set_payload(Bytes::from_static(b"hello=world")) +// .finish(); - match String::from_request(&req, &cfg).unwrap().poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s, "hello=world"); - } - _ => unreachable!(), - } - } +// match String::from_request(&req, &cfg).unwrap().poll().unwrap() { +// Async::Ready(s) => { +// assert_eq!(s, "hello=world"); +// } +// _ => unreachable!(), +// } +// } - #[test] - fn test_form() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); +// #[test] +// fn test_form() { +// let req = TestRequest::with_header( +// header::CONTENT_TYPE, +// "application/x-www-form-urlencoded", +// ) +// .header(header::CONTENT_LENGTH, "11") +// .set_payload(Bytes::from_static(b"hello=world")) +// .finish(); - let mut cfg = FormConfig::default(); - cfg.limit(4096); - match Form::::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.hello, "world"); - } - _ => unreachable!(), - } - } +// let mut cfg = FormConfig::default(); +// cfg.limit(4096); +// match Form::::from_request(&req, &cfg).poll().unwrap() { +// Async::Ready(s) => { +// assert_eq!(s.hello, "world"); +// } +// _ => unreachable!(), +// } +// } - #[test] - fn test_option() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).finish(); +// #[test] +// fn test_option() { +// let req = TestRequest::with_header( +// header::CONTENT_TYPE, +// "application/x-www-form-urlencoded", +// ) +// .finish(); - let mut cfg = FormConfig::default(); - cfg.limit(4096); +// let mut cfg = FormConfig::default(); +// cfg.limit(4096); - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!(r, None), - _ => unreachable!(), - } +// match Option::>::from_request(&req, &cfg) +// .poll() +// .unwrap() +// { +// Async::Ready(r) => assert_eq!(r, None), +// _ => unreachable!(), +// } - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); +// let req = TestRequest::with_header( +// header::CONTENT_TYPE, +// "application/x-www-form-urlencoded", +// ) +// .header(header::CONTENT_LENGTH, "9") +// .set_payload(Bytes::from_static(b"hello=world")) +// .finish(); - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!( - r, - Some(Form(Info { - hello: "world".into() - })) - ), - _ => unreachable!(), - } +// match Option::>::from_request(&req, &cfg) +// .poll() +// .unwrap() +// { +// Async::Ready(r) => assert_eq!( +// r, +// Some(Form(Info { +// hello: "world".into() +// })) +// ), +// _ => unreachable!(), +// } - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); +// let req = TestRequest::with_header( +// header::CONTENT_TYPE, +// "application/x-www-form-urlencoded", +// ) +// .header(header::CONTENT_LENGTH, "9") +// .set_payload(Bytes::from_static(b"bye=world")) +// .finish(); - match Option::>::from_request(&req, &cfg) - .poll() - .unwrap() - { - Async::Ready(r) => assert_eq!(r, None), - _ => unreachable!(), - } - } +// match Option::>::from_request(&req, &cfg) +// .poll() +// .unwrap() +// { +// Async::Ready(r) => assert_eq!(r, None), +// _ => unreachable!(), +// } +// } - #[test] - fn test_either() { - let req = TestRequest::default().finish(); - let mut cfg: EitherConfig, Query, _> = EitherConfig::default(); +// #[test] +// fn test_result() { +// let req = TestRequest::with_header( +// header::CONTENT_TYPE, +// "application/x-www-form-urlencoded", +// ) +// .header(header::CONTENT_LENGTH, "11") +// .set_payload(Bytes::from_static(b"hello=world")) +// .finish(); - assert!(Either::, Query>::from_request(&req, &cfg).poll().is_err()); +// match Result::, Error>::from_request(&req, &FormConfig::default()) +// .poll() +// .unwrap() +// { +// Async::Ready(Ok(r)) => assert_eq!( +// r, +// Form(Info { +// hello: "world".into() +// }) +// ), +// _ => unreachable!(), +// } - let req = TestRequest::default().uri("/index?hello=world").finish(); +// let req = TestRequest::with_header( +// header::CONTENT_TYPE, +// "application/x-www-form-urlencoded", +// ) +// .header(header::CONTENT_LENGTH, "9") +// .set_payload(Bytes::from_static(b"bye=world")) +// .finish(); - match Either::, Query>::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))), - _ => unreachable!(), - } +// match Result::, Error>::from_request(&req, &FormConfig::default()) +// .poll() +// .unwrap() +// { +// Async::Ready(r) => assert!(r.is_err()), +// _ => unreachable!(), +// } +// } - let req = TestRequest::default().uri("/index?bye=world").finish(); - match Either::, Query>::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))), - _ => unreachable!(), - } +// #[test] +// fn test_payload_config() { +// let req = TestRequest::default().finish(); +// let mut cfg = PayloadConfig::default(); +// cfg.mimetype(mime::APPLICATION_JSON); +// assert!(cfg.check_mimetype(&req).is_err()); - let req = TestRequest::default().uri("/index?hello=world&bye=world").finish(); - cfg.collision_strategy = EitherCollisionStrategy::PreferA; +// let req = TestRequest::with_header( +// header::CONTENT_TYPE, +// "application/x-www-form-urlencoded", +// ) +// .finish(); +// assert!(cfg.check_mimetype(&req).is_err()); - match Either::, Query>::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(r) => assert_eq!(r, Either::A(Query(Info { hello: "world".into() }))), - _ => unreachable!(), - } +// let req = +// TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); +// assert!(cfg.check_mimetype(&req).is_ok()); +// } - cfg.collision_strategy = EitherCollisionStrategy::PreferB; +// #[derive(Deserialize)] +// struct MyStruct { +// key: String, +// value: String, +// } - match Either::, Query>::from_request(&req, &cfg).poll().unwrap() { - Async::Ready(r) => assert_eq!(r, Either::B(Query(OtherInfo { bye: "world".into() }))), - _ => unreachable!(), - } +// #[derive(Deserialize)] +// struct Id { +// id: String, +// } - cfg.collision_strategy = EitherCollisionStrategy::ErrorA; - assert!(Either::, Query>::from_request(&req, &cfg).poll().is_err()); +// #[derive(Deserialize)] +// struct Test2 { +// key: String, +// value: u32, +// } - cfg.collision_strategy = EitherCollisionStrategy::FastestSuccessful; - assert!(Either::, Query>::from_request(&req, &cfg).poll().is_ok()); - } +// #[test] +// fn test_request_extract() { +// let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - #[test] - fn test_result() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); +// let mut router = Router::<()>::default(); +// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); - match Result::, Error>::from_request(&req, &FormConfig::default()) - .poll() - .unwrap() - { - Async::Ready(Ok(r)) => assert_eq!( - r, - Form(Info { - hello: "world".into() - }) - ), - _ => unreachable!(), - } +// let s = Path::::from_request(&req, &()).unwrap(); +// assert_eq!(s.key, "name"); +// assert_eq!(s.value, "user1"); - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .finish(); +// let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); +// assert_eq!(s.0, "name"); +// assert_eq!(s.1, "user1"); - match Result::, Error>::from_request(&req, &FormConfig::default()) - .poll() - .unwrap() - { - Async::Ready(r) => assert!(r.is_err()), - _ => unreachable!(), - } - } +// let s = Query::::from_request(&req, &()).unwrap(); +// assert_eq!(s.id, "test"); - #[test] - fn test_payload_config() { - let req = TestRequest::default().finish(); - let mut cfg = PayloadConfig::default(); - cfg.mimetype(mime::APPLICATION_JSON); - assert!(cfg.check_mimetype(&req).is_err()); +// let mut router = Router::<()>::default(); +// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); +// let req = TestRequest::with_uri("/name/32/").finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).finish(); - assert!(cfg.check_mimetype(&req).is_err()); +// let s = Path::::from_request(&req, &()).unwrap(); +// assert_eq!(s.as_ref().key, "name"); +// assert_eq!(s.value, 32); - let req = - TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); - assert!(cfg.check_mimetype(&req).is_ok()); - } +// let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); +// assert_eq!(s.0, "name"); +// assert_eq!(s.1, 32); - #[derive(Deserialize)] - struct MyStruct { - key: String, - value: String, - } +// let res = Path::>::extract(&req).unwrap(); +// assert_eq!(res[0], "name".to_owned()); +// assert_eq!(res[1], "32".to_owned()); +// } - #[derive(Deserialize)] - struct Id { - id: String, - } +// #[test] +// fn test_extract_path_single() { +// let mut router = Router::<()>::default(); +// router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - #[derive(Deserialize)] - struct Test2 { - key: String, - value: u32, - } +// let req = TestRequest::with_uri("/32/").finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); +// assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); +// } - #[test] - fn test_request_extract() { - let req = TestRequest::with_uri("/name/user1/?id=test").finish(); +// #[test] +// fn test_tuple_extract() { +// let mut router = Router::<()>::default(); +// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); +// let req = TestRequest::with_uri("/name/user1/?id=test").finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); - let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); +// let res = match <(Path<(String, String)>,)>::extract(&req).poll() { +// Ok(Async::Ready(res)) => res, +// _ => panic!("error"), +// }; +// assert_eq!((res.0).0, "name"); +// assert_eq!((res.0).1, "user1"); - let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); +// let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req) +// .poll() +// { +// Ok(Async::Ready(res)) => res, +// _ => panic!("error"), +// }; +// assert_eq!((res.0).0, "name"); +// assert_eq!((res.0).1, "user1"); +// assert_eq!((res.1).0, "name"); +// assert_eq!((res.1).1, "user1"); - let s = Query::::from_request(&req, &QueryConfig::default()).unwrap(); - assert_eq!(s.id, "test"); - - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let req = TestRequest::with_uri("/name/32/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - - let s = Path::<(String, u8)>::from_request(&req, &PathConfig::default()).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - - let res = Path::>::extract(&req).unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); - } - - #[test] - fn test_extract_path_single() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - - let req = TestRequest::with_uri("/32/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - assert_eq!(*Path::::from_request(&req, &&PathConfig::default()).unwrap(), 32); - } - - #[test] - fn test_extract_path_decode() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - - macro_rules! test_single_value { - ($value:expr, $expected:expr) => { - { - let req = TestRequest::with_uri($value).finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - assert_eq!(*Path::::from_request(&req, &PathConfig::default()).unwrap(), $expected); - } - } - } - - test_single_value!("/%25/", "%"); - test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+="); - test_single_value!("/%2B/", "+"); - test_single_value!("/%252B/", "%2B"); - test_single_value!("/%2F/", "/"); - test_single_value!("/%252F/", "%2F"); - test_single_value!("/http%3A%2F%2Flocalhost%3A80%2Ffoo/", "http://localhost:80/foo"); - test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog"); - test_single_value!( - "/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/", - "http://localhost:80/file/%2Fvar%2Flog%2Fsyslog" - ); - - let req = TestRequest::with_uri("/%25/7/?id=test").finish(); - - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); - assert_eq!(s.key, "%"); - assert_eq!(s.value, 7); - - let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap(); - assert_eq!(s.0, "%"); - assert_eq!(s.1, "7"); - } - - #[test] - fn test_extract_path_no_decode() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - - let req = TestRequest::with_uri("/%25/").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - assert_eq!( - *Path::::from_request( - &req, - &&PathConfig::default().disable_decoding() - ).unwrap(), - "%25" - ); - } - - #[test] - fn test_tuple_extract() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - - let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let info = router.recognize(&req, &(), 0); - let req = req.with_route_info(info); - - let res = match <(Path<(String, String)>,)>::extract(&req).poll() { - Ok(Async::Ready(res)) => res, - _ => panic!("error"), - }; - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - - let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req) - .poll() - { - Ok(Async::Ready(res)) => res, - _ => panic!("error"), - }; - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - assert_eq!((res.1).0, "name"); - assert_eq!((res.1).1, "user1"); - - let () = <()>::extract(&req); - } -} +// let () = <()>::extract(&req); +// } +// } diff --git a/src/filter.rs b/src/filter.rs new file mode 100644 index 00000000..a0566092 --- /dev/null +++ b/src/filter.rs @@ -0,0 +1,327 @@ +//! Route match predicates +#![allow(non_snake_case)] +use actix_http::http::{self, header, HttpTryFrom}; + +use crate::request::HttpRequest; + +/// Trait defines resource predicate. +/// Predicate can modify request object. It is also possible to +/// to store extra attributes on request by using `Extensions` container, +/// Extensions container available via `HttpRequest::extensions()` method. +pub trait Filter { + /// Check if request matches predicate + fn check(&self, request: &HttpRequest) -> bool; +} + +/// Return filter that matches if any of supplied filters. +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// use actix_web2::{filter, App, HttpResponse}; +/// +/// fn main() { +/// App::new().resource("/index.html", |r| { +/// r.route() +/// .filter(pred::Any(pred::Get()).or(pred::Post())) +/// .f(|r| HttpResponse::MethodNotAllowed()) +/// }); +/// } +/// ``` +pub fn Any(filter: F) -> AnyFilter { + AnyFilter(vec![Box::new(filter)]) +} + +/// Matches if any of supplied filters matche. +pub struct AnyFilter(Vec>); + +impl AnyFilter { + /// Add filter to a list of filters to check + pub fn or(mut self, filter: F) -> Self { + self.0.push(Box::new(filter)); + self + } +} + +impl Filter for AnyFilter { + fn check(&self, req: &HttpRequest) -> bool { + for p in &self.0 { + if p.check(req) { + return true; + } + } + false + } +} + +/// Return filter that matches if all of supplied filters match. +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// use actix_web::{pred, App, HttpResponse}; +/// +/// fn main() { +/// App::new().resource("/index.html", |r| { +/// r.route() +/// .filter( +/// pred::All(pred::Get()) +/// .and(pred::Header("content-type", "text/plain")), +/// ) +/// .f(|_| HttpResponse::MethodNotAllowed()) +/// }); +/// } +/// ``` +pub fn All(filter: F) -> AllFilter { + AllFilter(vec![Box::new(filter)]) +} + +/// Matches if all of supplied filters matche. +pub struct AllFilter(Vec>); + +impl AllFilter { + /// Add new predicate to list of predicates to check + pub fn and(mut self, filter: F) -> Self { + self.0.push(Box::new(filter)); + self + } +} + +impl Filter for AllFilter { + fn check(&self, request: &HttpRequest) -> bool { + for p in &self.0 { + if !p.check(request) { + return false; + } + } + true + } +} + +/// Return predicate that matches if supplied predicate does not match. +pub fn Not(filter: F) -> NotFilter { + NotFilter(Box::new(filter)) +} + +#[doc(hidden)] +pub struct NotFilter(Box); + +impl Filter for NotFilter { + fn check(&self, request: &HttpRequest) -> bool { + !self.0.check(request) + } +} + +/// Http method predicate +#[doc(hidden)] +pub struct MethodFilter(http::Method); + +impl Filter for MethodFilter { + fn check(&self, request: &HttpRequest) -> bool { + request.method() == self.0 + } +} + +/// Predicate to match *GET* http method +pub fn Get() -> MethodFilter { + MethodFilter(http::Method::GET) +} + +/// Predicate to match *POST* http method +pub fn Post() -> MethodFilter { + MethodFilter(http::Method::POST) +} + +/// Predicate to match *PUT* http method +pub fn Put() -> MethodFilter { + MethodFilter(http::Method::PUT) +} + +/// Predicate to match *DELETE* http method +pub fn Delete() -> MethodFilter { + MethodFilter(http::Method::DELETE) +} + +/// Predicate to match *HEAD* http method +pub fn Head() -> MethodFilter { + MethodFilter(http::Method::HEAD) +} + +/// Predicate to match *OPTIONS* http method +pub fn Options() -> MethodFilter { + MethodFilter(http::Method::OPTIONS) +} + +/// Predicate to match *CONNECT* http method +pub fn Connect() -> MethodFilter { + MethodFilter(http::Method::CONNECT) +} + +/// Predicate to match *PATCH* http method +pub fn Patch() -> MethodFilter { + MethodFilter(http::Method::PATCH) +} + +/// Predicate to match *TRACE* http method +pub fn Trace() -> MethodFilter { + MethodFilter(http::Method::TRACE) +} + +/// Predicate to match specified http method +pub fn Method(method: http::Method) -> MethodFilter { + MethodFilter(method) +} + +/// Return predicate that matches if request contains specified header and +/// value. +pub fn Header(name: &'static str, value: &'static str) -> HeaderFilter { + HeaderFilter( + header::HeaderName::try_from(name).unwrap(), + header::HeaderValue::from_static(value), + ) +} + +#[doc(hidden)] +pub struct HeaderFilter(header::HeaderName, header::HeaderValue); + +impl Filter for HeaderFilter { + fn check(&self, req: &HttpRequest) -> bool { + if let Some(val) = req.headers().get(&self.0) { + return val == self.1; + } + false + } +} + +/// Return predicate that matches if request contains specified Host name. +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// use actix_web::{pred, App, HttpResponse}; +/// +/// fn main() { +/// App::new().resource("/index.html", |r| { +/// r.route() +/// .filter(pred::Host("www.rust-lang.org")) +/// .f(|_| HttpResponse::MethodNotAllowed()) +/// }); +/// } +/// ``` +pub fn Host>(host: H) -> HostFilter { + HostFilter(host.as_ref().to_string(), None) +} + +#[doc(hidden)] +pub struct HostFilter(String, Option); + +impl HostFilter { + /// Set reuest scheme to match + pub fn scheme>(&mut self, scheme: H) { + self.1 = Some(scheme.as_ref().to_string()) + } +} + +impl Filter for HostFilter { + fn check(&self, _req: &HttpRequest) -> bool { + // let info = req.connection_info(); + // if let Some(ref scheme) = self.1 { + // self.0 == info.host() && scheme == info.scheme() + // } else { + // self.0 == info.host() + // } + false + } +} + +// #[cfg(test)] +// mod tests { +// use actix_http::http::{header, Method}; +// use actix_http::test::TestRequest; + +// use super::*; + +// #[test] +// fn test_header() { +// let req = TestRequest::with_header( +// header::TRANSFER_ENCODING, +// header::HeaderValue::from_static("chunked"), +// ) +// .finish(); + +// let pred = Header("transfer-encoding", "chunked"); +// assert!(pred.check(&req, req.state())); + +// let pred = Header("transfer-encoding", "other"); +// assert!(!pred.check(&req, req.state())); + +// let pred = Header("content-type", "other"); +// assert!(!pred.check(&req, req.state())); +// } + +// #[test] +// fn test_host() { +// let req = TestRequest::default() +// .header( +// header::HOST, +// header::HeaderValue::from_static("www.rust-lang.org"), +// ) +// .finish(); + +// let pred = Host("www.rust-lang.org"); +// assert!(pred.check(&req, req.state())); + +// let pred = Host("localhost"); +// assert!(!pred.check(&req, req.state())); +// } + +// #[test] +// fn test_methods() { +// let req = TestRequest::default().finish(); +// let req2 = TestRequest::default().method(Method::POST).finish(); + +// assert!(Get().check(&req, req.state())); +// assert!(!Get().check(&req2, req2.state())); +// assert!(Post().check(&req2, req2.state())); +// assert!(!Post().check(&req, req.state())); + +// let r = TestRequest::default().method(Method::PUT).finish(); +// assert!(Put().check(&r, r.state())); +// assert!(!Put().check(&req, req.state())); + +// let r = TestRequest::default().method(Method::DELETE).finish(); +// assert!(Delete().check(&r, r.state())); +// assert!(!Delete().check(&req, req.state())); + +// let r = TestRequest::default().method(Method::HEAD).finish(); +// assert!(Head().check(&r, r.state())); +// assert!(!Head().check(&req, req.state())); + +// let r = TestRequest::default().method(Method::OPTIONS).finish(); +// assert!(Options().check(&r, r.state())); +// assert!(!Options().check(&req, req.state())); + +// let r = TestRequest::default().method(Method::CONNECT).finish(); +// assert!(Connect().check(&r, r.state())); +// assert!(!Connect().check(&req, req.state())); + +// let r = TestRequest::default().method(Method::PATCH).finish(); +// assert!(Patch().check(&r, r.state())); +// assert!(!Patch().check(&req, req.state())); + +// let r = TestRequest::default().method(Method::TRACE).finish(); +// assert!(Trace().check(&r, r.state())); +// assert!(!Trace().check(&req, req.state())); +// } + +// #[test] +// fn test_preds() { +// let r = TestRequest::default().method(Method::TRACE).finish(); + +// assert!(Not(Get()).check(&r, r.state())); +// assert!(!Not(Trace()).check(&r, r.state())); + +// assert!(All(Trace()).and(Trace()).check(&r, r.state())); +// assert!(!All(Get()).and(Trace()).check(&r, r.state())); + +// assert!(Any(Get()).or(Trace()).check(&r, r.state())); +// assert!(!Any(Get()).or(Get()).check(&r, r.state())); +// } +// } diff --git a/src/framed_app.rs b/src/framed_app.rs new file mode 100644 index 00000000..ba925414 --- /dev/null +++ b/src/framed_app.rs @@ -0,0 +1,240 @@ +use std::marker::PhantomData; +use std::rc::Rc; + +use actix_codec::Framed; +use actix_http::h1::Codec; +use actix_http::{Request, Response, SendResponse}; +use actix_router::{Path, Router, Url}; +use actix_service::{IntoNewService, NewService, Service}; +use actix_utils::cloneable::CloneableService; +use futures::{Async, Future, Poll}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use crate::app::{HttpServiceFactory, State}; +use crate::framed_handler::FramedRequest; +use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService}; +use crate::request::Request as WebRequest; + +pub type FRequest = (Request, Framed); +type BoxedResponse = Box>; + +/// Application builder +pub struct FramedApp { + services: Vec<(String, BoxedHttpNewService, ()>)>, + state: State, +} + +impl FramedApp { + pub fn new() -> Self { + FramedApp { + services: Vec::new(), + state: State::new(()), + } + } +} + +impl FramedApp { + pub fn with(state: S) -> FramedApp { + FramedApp { + services: Vec::new(), + state: State::new(state), + } + } + + pub fn service(mut self, factory: U) -> Self + where + U: HttpServiceFactory, + U::Factory: NewService, Response = ()> + 'static, + ::Future: 'static, + ::Service: Service>, + <::Service as Service>::Future: 'static, + { + let path = factory.path().to_string(); + self.services.push(( + path, + Box::new(HttpNewService::new(factory.create(self.state.clone()))), + )); + self + } + + pub fn register_service(&mut self, factory: U) + where + U: HttpServiceFactory, + U::Factory: NewService, Response = ()> + 'static, + ::Future: 'static, + ::Service: Service>, + <::Service as Service>::Future: 'static, + { + let path = factory.path().to_string(); + self.services.push(( + path, + Box::new(HttpNewService::new(factory.create(self.state.clone()))), + )); + } +} + +impl IntoNewService> for FramedApp +where + T: AsyncRead + AsyncWrite, +{ + fn into_new_service(self) -> FramedAppFactory { + FramedAppFactory { + state: self.state, + services: Rc::new(self.services), + _t: PhantomData, + } + } +} + +#[derive(Clone)] +pub struct FramedAppFactory { + state: State, + services: Rc, ()>)>>, + _t: PhantomData, +} + +impl NewService for FramedAppFactory +where + T: AsyncRead + AsyncWrite, +{ + type Request = FRequest; + type Response = (); + type Error = (); + type InitError = (); + type Service = CloneableService>; + type Future = CreateService; + + fn new_service(&self) -> Self::Future { + CreateService { + fut: self + .services + .iter() + .map(|(path, service)| { + CreateServiceItem::Future(Some(path.clone()), service.new_service()) + }) + .collect(), + state: self.state.clone(), + } + } +} + +#[doc(hidden)] +pub struct CreateService { + fut: Vec>, + state: State, +} + +enum CreateServiceItem { + Future( + Option, + Box, ()>, Error = ()>>, + ), + Service(String, BoxedHttpService, ()>), +} + +impl Future for CreateService +where + T: AsyncRead + AsyncWrite, +{ + type Item = CloneableService>; + type Error = (); + + fn poll(&mut self) -> Poll { + let mut done = true; + + // poll http services + for item in &mut self.fut { + let res = match item { + CreateServiceItem::Future(ref mut path, ref mut fut) => { + match fut.poll()? { + Async::Ready(service) => Some((path.take().unwrap(), service)), + Async::NotReady => { + done = false; + None + } + } + } + CreateServiceItem::Service(_, _) => continue, + }; + + if let Some((path, service)) = res { + *item = CreateServiceItem::Service(path, service); + } + } + + if done { + let router = self + .fut + .drain(..) + .fold(Router::build(), |mut router, item| { + match item { + CreateServiceItem::Service(path, service) => { + router.path(&path, service) + } + CreateServiceItem::Future(_, _) => unreachable!(), + } + router + }); + Ok(Async::Ready(CloneableService::new(FramedAppService { + router: router.finish(), + state: self.state.clone(), + // default: self.default.take().expect("something is wrong"), + }))) + } else { + Ok(Async::NotReady) + } + } +} + +pub struct FramedAppService { + state: State, + router: Router, ()>>, +} + +impl Service for FramedAppService +where + T: AsyncRead + AsyncWrite, +{ + type Request = FRequest; + type Response = (); + type Error = (); + type Future = BoxedResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + // let mut ready = true; + // for service in &mut self.services { + // if let Async::NotReady = service.poll_ready()? { + // ready = false; + // } + // } + // if ready { + // Ok(Async::Ready(())) + // } else { + // Ok(Async::NotReady) + // } + Ok(Async::Ready(())) + } + + fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { + let mut path = Path::new(Url::new(req.uri().clone())); + + if let Some((srv, _info)) = self.router.recognize_mut(&mut path) { + return srv.call(FramedRequest::new( + WebRequest::new(self.state.clone(), req, path), + framed, + )); + } + // for item in &mut self.services { + // req = match item.handle(req) { + // Ok(fut) => return fut, + // Err(req) => req, + // }; + // } + // self.default.call(req) + Box::new( + SendResponse::send(framed, Response::NotFound().finish().into()) + .map(|_| ()) + .map_err(|_| ()), + ) + } +} diff --git a/src/framed_handler.rs b/src/framed_handler.rs new file mode 100644 index 00000000..109b5f0a --- /dev/null +++ b/src/framed_handler.rs @@ -0,0 +1,379 @@ +use std::marker::PhantomData; +use std::rc::Rc; + +use actix_codec::Framed; +use actix_http::{h1::Codec, Error}; +use actix_service::{NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Async, Future, IntoFuture, Poll}; +use log::error; + +use crate::handler::FromRequest; +use crate::request::Request; + +pub struct FramedError { + pub err: Error, + pub framed: Framed, +} + +pub struct FramedRequest { + req: Request, + framed: Framed, + param: Ex, +} + +impl FramedRequest { + pub fn new(req: Request, framed: Framed) -> Self { + Self { + req, + framed, + param: (), + } + } +} + +impl FramedRequest { + pub fn request(&self) -> &Request { + &self.req + } + + pub fn request_mut(&mut self) -> &mut Request { + &mut self.req + } + + pub fn into_parts(self) -> (Request, Framed, Ex) { + (self.req, self.framed, self.param) + } + + pub fn map(self, op: F) -> FramedRequest + where + F: FnOnce(Ex) -> Ex2, + { + FramedRequest { + req: self.req, + framed: self.framed, + param: op(self.param), + } + } +} + +/// T handler converter factory +pub trait FramedFactory: Clone + 'static +where + R: IntoFuture, + E: Into, +{ + fn call(&self, framed: Framed, param: T, extra: Ex) -> R; +} + +#[doc(hidden)] +pub struct FramedHandle +where + F: FramedFactory, + R: IntoFuture, + E: Into, +{ + hnd: F, + _t: PhantomData<(S, Io, Ex, T, R, E)>, +} + +impl FramedHandle +where + F: FramedFactory, + R: IntoFuture, + E: Into, +{ + pub fn new(hnd: F) -> Self { + FramedHandle { + hnd, + _t: PhantomData, + } + } +} +impl NewService for FramedHandle +where + F: FramedFactory, + R: IntoFuture, + E: Into, +{ + type Request = (T, FramedRequest); + type Response = (); + type Error = FramedError; + type InitError = (); + type Service = FramedHandleService; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(FramedHandleService { + hnd: self.hnd.clone(), + _t: PhantomData, + }) + } +} + +#[doc(hidden)] +pub struct FramedHandleService +where + F: FramedFactory, + R: IntoFuture, + E: Into, +{ + hnd: F, + _t: PhantomData<(S, Io, Ex, T, R, E)>, +} + +impl Service for FramedHandleService +where + F: FramedFactory, + R: IntoFuture, + E: Into, +{ + type Request = (T, FramedRequest); + type Response = (); + type Error = FramedError; + type Future = FramedHandleServiceResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (param, framed): (T, FramedRequest)) -> Self::Future { + let (_, framed, ex) = framed.into_parts(); + FramedHandleServiceResponse { + fut: self.hnd.call(framed, param, ex).into_future(), + _t: PhantomData, + } + } +} + +#[doc(hidden)] +pub struct FramedHandleServiceResponse { + fut: F, + _t: PhantomData, +} + +impl Future for FramedHandleServiceResponse +where + F: Future, + F::Error: Into, +{ + type Item = (); + type Error = FramedError; + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(res)) => Ok(Async::Ready(res.into())), + Err(e) => { + let e: Error = e.into(); + error!("Error in handler: {:?}", e); + Ok(Async::Ready(())) + } + } + } +} + +pub struct FramedExtract +where + T: FromRequest, +{ + cfg: Rc, + _t: PhantomData<(Io, Ex)>, +} + +impl FramedExtract +where + T: FromRequest + 'static, +{ + pub fn new(cfg: T::Config) -> FramedExtract { + FramedExtract { + cfg: Rc::new(cfg), + _t: PhantomData, + } + } +} +impl NewService for FramedExtract +where + T: FromRequest + 'static, +{ + type Request = FramedRequest; + type Response = (T, FramedRequest); + type Error = FramedError; + type InitError = (); + type Service = FramedExtractService; + type Future = FutureResult; + + fn new_service(&self) -> Self::Future { + ok(FramedExtractService { + cfg: self.cfg.clone(), + _t: PhantomData, + }) + } +} + +pub struct FramedExtractService +where + T: FromRequest, +{ + cfg: Rc, + _t: PhantomData<(Io, Ex)>, +} + +impl Service for FramedExtractService +where + T: FromRequest + 'static, +{ + type Request = FramedRequest; + type Response = (T, FramedRequest); + type Error = FramedError; + type Future = FramedExtractResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: FramedRequest) -> Self::Future { + FramedExtractResponse { + fut: T::from_request(&req.request(), self.cfg.as_ref()), + req: Some(req), + } + } +} + +pub struct FramedExtractResponse +where + T: FromRequest + 'static, +{ + req: Option>, + fut: T::Future, +} + +impl Future for FramedExtractResponse +where + T: FromRequest + 'static, +{ + type Item = (T, FramedRequest); + type Error = FramedError; + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(item)) => Ok(Async::Ready((item, self.req.take().unwrap()))), + Err(err) => Err(FramedError { + err: err.into(), + framed: self.req.take().unwrap().into_parts().1, + }), + } + } +} + +macro_rules! factory_tuple ({ ($(($nex:tt, $Ex:ident)),+), $(($n:tt, $T:ident)),+} => { + impl FramedFactory for Func + where Func: Fn(Framed, $($Ex,)+ $($T,)+) -> Res + Clone + 'static, + $($T: FromRequest + 'static,)+ + Res: IntoFuture + 'static, + Err: Into, + { + fn call(&self, framed: Framed, param: ($($T,)+), extra: ($($Ex,)+)) -> Res { + (self)(framed, $(extra.$nex,)+ $(param.$n,)+) + } + } +}); + +macro_rules! factory_tuple_unit ({$(($n:tt, $T:ident)),+} => { + impl FramedFactory for Func + where Func: Fn(Framed, $($T,)+) -> Res + Clone + 'static, + $($T: FromRequest + 'static,)+ + Res: IntoFuture + 'static, + Err: Into, + { + fn call(&self, framed: Framed, param: ($($T,)+), _extra: () ) -> Res { + (self)(framed, $(param.$n,)+) + } + } +}); + +#[cfg_attr(rustfmt, rustfmt_skip)] +mod m { + use super::*; + +factory_tuple_unit!((0, A)); +factory_tuple!(((0, Aex)), (0, A)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A)); + +factory_tuple_unit!((0, A), (1, B)); +factory_tuple!(((0, Aex)), (0, A), (1, B)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B)); + +factory_tuple_unit!((0, A), (1, B), (2, C)); +factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C)); + +factory_tuple_unit!((0, A), (1, B), (2, C), (3, D)); +factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D)); + +factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E)); +factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E)); + +factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); + +factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); + +factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); + +factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); + +factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +} diff --git a/src/framed_route.rs b/src/framed_route.rs new file mode 100644 index 00000000..90555a9c --- /dev/null +++ b/src/framed_route.rs @@ -0,0 +1,448 @@ +use std::marker::PhantomData; + +use actix_http::http::{HeaderName, HeaderValue, Method}; +use actix_http::Error; +use actix_service::{IntoNewService, NewService, NewServiceExt, Service}; +use futures::{try_ready, Async, Future, IntoFuture, Poll}; +use log::{debug, error}; +use tokio_io::{AsyncRead, AsyncWrite}; + +use crate::app::{HttpServiceFactory, State}; +use crate::framed_handler::{ + FramedError, FramedExtract, FramedFactory, FramedHandle, FramedRequest, +}; +use crate::handler::FromRequest; + +/// Resource route definition +/// +/// Route uses builder-like pattern for configuration. +/// If handler is not explicitly set, default *404 Not Found* handler is used. +pub struct FramedRoute { + service: T, + pattern: String, + methods: Vec, + headers: Vec<(HeaderName, HeaderValue)>, + state: PhantomData<(S, Io)>, +} + +impl FramedRoute { + pub fn build(path: &str) -> FramedRoutePatternBuilder { + FramedRoutePatternBuilder::new(path) + } + + pub fn get(path: &str) -> FramedRoutePatternBuilder { + FramedRoutePatternBuilder::new(path).method(Method::GET) + } + + pub fn post(path: &str) -> FramedRoutePatternBuilder { + FramedRoutePatternBuilder::new(path).method(Method::POST) + } + + pub fn put(path: &str) -> FramedRoutePatternBuilder { + FramedRoutePatternBuilder::new(path).method(Method::PUT) + } + + pub fn delete(path: &str) -> FramedRoutePatternBuilder { + FramedRoutePatternBuilder::new(path).method(Method::DELETE) + } +} + +impl FramedRoute +where + T: NewService< + Request = FramedRequest, + Response = (), + Error = FramedError, + > + 'static, +{ + pub fn new>(pattern: &str, factory: F) -> Self { + FramedRoute { + pattern: pattern.to_string(), + service: factory.into_new_service(), + headers: Vec::new(), + methods: Vec::new(), + state: PhantomData, + } + } + + pub fn method(mut self, method: Method) -> Self { + self.methods.push(method); + self + } + + pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self { + self.headers.push((name, value)); + self + } +} + +impl HttpServiceFactory for FramedRoute +where + Io: AsyncRead + AsyncWrite + 'static, + T: NewService< + Request = FramedRequest, + Response = (), + Error = FramedError, + > + 'static, + T::Service: 'static, +{ + type Factory = FramedRouteFactory; + + fn path(&self) -> &str { + &self.pattern + } + + fn create(self, state: State) -> Self::Factory { + FramedRouteFactory { + state, + service: self.service, + pattern: self.pattern, + methods: self.methods, + headers: self.headers, + _t: PhantomData, + } + } +} + +pub struct FramedRouteFactory { + service: T, + pattern: String, + methods: Vec, + headers: Vec<(HeaderName, HeaderValue)>, + state: State, + _t: PhantomData, +} + +impl NewService for FramedRouteFactory +where + Io: AsyncRead + AsyncWrite + 'static, + T: NewService< + Request = FramedRequest, + Response = (), + Error = FramedError, + > + 'static, + T::Service: 'static, +{ + type Request = FramedRequest; + type Response = T::Response; + type Error = (); + type InitError = T::InitError; + type Service = FramedRouteService; + type Future = CreateRouteService; + + fn new_service(&self) -> Self::Future { + CreateRouteService { + fut: self.service.new_service(), + pattern: self.pattern.clone(), + methods: self.methods.clone(), + headers: self.headers.clone(), + state: self.state.clone(), + _t: PhantomData, + } + } +} + +pub struct CreateRouteService { + fut: T::Future, + pattern: String, + methods: Vec, + headers: Vec<(HeaderName, HeaderValue)>, + state: State, + _t: PhantomData, +} + +impl Future for CreateRouteService +where + T: NewService< + Request = FramedRequest, + Response = (), + Error = FramedError, + >, +{ + type Item = FramedRouteService; + type Error = T::InitError; + + fn poll(&mut self) -> Poll { + let service = try_ready!(self.fut.poll()); + + Ok(Async::Ready(FramedRouteService { + service, + state: self.state.clone(), + pattern: self.pattern.clone(), + methods: self.methods.clone(), + headers: self.headers.clone(), + _t: PhantomData, + })) + } +} + +pub struct FramedRouteService { + service: T, + pattern: String, + methods: Vec, + headers: Vec<(HeaderName, HeaderValue)>, + state: State, + _t: PhantomData, +} + +impl Service for FramedRouteService +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, Response = (), Error = FramedError> + + 'static, +{ + type Request = FramedRequest; + type Response = (); + type Error = (); + type Future = FramedRouteServiceResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready().map_err(|e| { + debug!("Service not available: {}", e.err); + () + }) + } + + fn call(&mut self, req: FramedRequest) -> Self::Future { + FramedRouteServiceResponse { + fut: self.service.call(req), + send: None, + _t: PhantomData, + } + } +} + +// impl HttpService<(Request, Framed)> for FramedRouteService +// where +// Io: AsyncRead + AsyncWrite + 'static, +// S: 'static, +// T: Service, Response = (), Error = FramedError> + 'static, +// { +// fn handle( +// &mut self, +// (req, framed): (Request, Framed), +// ) -> Result)> { +// if self.methods.is_empty() +// || !self.methods.is_empty() && self.methods.contains(req.method()) +// { +// if let Some(params) = self.pattern.match_with_params(&req, 0) { +// return Ok(FramedRouteServiceResponse { +// fut: self.service.call(FramedRequest::new( +// WebRequest::new(self.state.clone(), req, params), +// framed, +// )), +// send: None, +// _t: PhantomData, +// }); +// } +// } +// Err((req, framed)) +// } +// } + +#[doc(hidden)] +pub struct FramedRouteServiceResponse { + fut: F, + send: Option>>, + _t: PhantomData, +} + +impl Future for FramedRouteServiceResponse +where + F: Future>, + Io: AsyncRead + AsyncWrite + 'static, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.send { + return match fut.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(_)) => Ok(Async::Ready(())), + Err(e) => { + debug!("Error during error response send: {}", e); + Err(()) + } + }; + }; + + match self.fut.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(_)) => Ok(Async::Ready(())), + Err(e) => { + error!("Error occurred during request handling: {}", e.err); + Err(()) + } + } + } +} + +pub struct FramedRoutePatternBuilder { + pattern: String, + methods: Vec, + headers: Vec<(HeaderName, HeaderValue)>, + state: PhantomData<(Io, S)>, +} + +impl FramedRoutePatternBuilder { + fn new(path: &str) -> FramedRoutePatternBuilder { + FramedRoutePatternBuilder { + pattern: path.to_string(), + methods: Vec::new(), + headers: Vec::new(), + state: PhantomData, + } + } + + pub fn method(mut self, method: Method) -> Self { + self.methods.push(method); + self + } + + pub fn map>( + self, + md: F, + ) -> FramedRouteBuilder + where + T: NewService< + Request = FramedRequest, + Response = FramedRequest, + Error = FramedError, + InitError = (), + >, + { + FramedRouteBuilder { + service: md.into_new_service(), + pattern: self.pattern, + methods: self.methods, + headers: self.headers, + state: PhantomData, + } + } + + pub fn with( + self, + handler: F, + ) -> FramedRoute< + Io, + impl NewService< + Request = FramedRequest, + Response = (), + Error = FramedError, + InitError = (), + >, + S, + > + where + F: FramedFactory, + P: FromRequest + 'static, + R: IntoFuture, + E: Into, + { + FramedRoute { + service: FramedExtract::new(P::Config::default()) + .and_then(FramedHandle::new(handler)), + pattern: self.pattern, + methods: self.methods, + headers: self.headers, + state: PhantomData, + } + } +} + +pub struct FramedRouteBuilder { + service: T, + pattern: String, + methods: Vec, + headers: Vec<(HeaderName, HeaderValue)>, + state: PhantomData<(Io, S, U1, U2)>, +} + +impl FramedRouteBuilder +where + T: NewService< + Request = FramedRequest, + Response = FramedRequest, + Error = FramedError, + InitError = (), + >, +{ + pub fn new>(path: &str, factory: F) -> Self { + FramedRouteBuilder { + service: factory.into_new_service(), + pattern: path.to_string(), + methods: Vec::new(), + headers: Vec::new(), + state: PhantomData, + } + } + + pub fn method(mut self, method: Method) -> Self { + self.methods.push(method); + self + } + + pub fn map>( + self, + md: F, + ) -> FramedRouteBuilder< + Io, + S, + impl NewService< + Request = FramedRequest, + Response = FramedRequest, + Error = FramedError, + InitError = (), + >, + U1, + U3, + > + where + K: NewService< + Request = FramedRequest, + Response = FramedRequest, + Error = FramedError, + InitError = (), + >, + { + FramedRouteBuilder { + service: self.service.from_err().and_then(md.into_new_service()), + pattern: self.pattern, + methods: self.methods, + headers: self.headers, + state: PhantomData, + } + } + + pub fn with( + self, + handler: F, + ) -> FramedRoute< + Io, + impl NewService< + Request = FramedRequest, + Response = (), + Error = FramedError, + InitError = (), + >, + S, + > + where + F: FramedFactory, + P: FromRequest + 'static, + R: IntoFuture, + E: Into, + { + FramedRoute { + service: self + .service + .and_then(FramedExtract::new(P::Config::default())) + .and_then(FramedHandle::new(handler)), + pattern: self.pattern, + methods: self.methods, + headers: self.headers, + state: PhantomData, + } + } +} diff --git a/src/fs.rs b/src/fs.rs index 604ac550..3c83af6e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,34 +1,41 @@ //! Static files support +use std::cell::RefCell; use std::fmt::Write; use std::fs::{DirEntry, File, Metadata}; use std::io::{Read, Seek}; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; +use std::rc::Rc; use std::time::{SystemTime, UNIX_EPOCH}; use std::{cmp, io}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -use v_htmlescape::escape as escape_html_entity; use bytes::Bytes; +use derive_more::Display; use futures::{Async, Future, Poll, Stream}; -use futures_cpupool::{CpuFuture, CpuPool}; use mime; use mime_guess::{get_mime_type, guess_mime_type}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; +use v_htmlescape::escape as escape_html_entity; -use error::{Error, StaticFileError}; -use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; -use header; -use header::{ContentDisposition, DispositionParam, DispositionType}; -use http::{ContentEncoding, Method, StatusCode}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use param::FromParam; -use server::settings::DEFAULT_CPUPOOL; +use actix_http::error::{Error, ErrorInternalServerError, ResponseError}; +use actix_http::http::header::{ + self, ContentDisposition, DispositionParam, DispositionType, +}; +use actix_http::http::{ContentEncoding, Method, StatusCode}; +use actix_http::{HttpMessage, Response}; +use actix_service::{NewService, Service}; +use futures::future::{err, ok, FutureResult}; + +use crate::blocking; +use crate::handler::FromRequest; +use crate::helpers::HttpDefaultNewService; +use crate::request::HttpRequest; +use crate::responder::Responder; +use crate::service::{ServiceRequest, ServiceResponse}; ///Describes `StaticFiles` configiration /// @@ -39,7 +46,7 @@ use server::settings::DEFAULT_CPUPOOL; /// ///## Example /// -///```rust +///```rust,ignore /// extern crate mime; /// extern crate actix_web; /// use actix_web::http::header::DispositionType; @@ -113,7 +120,6 @@ pub struct NamedFile { content_disposition: header::ContentDisposition, md: Metadata, modified: Option, - cpu_pool: Option, encoding: Option, status_code: StatusCode, _cd_map: PhantomData, @@ -127,7 +133,7 @@ impl NamedFile { /// /// # Examples /// - /// ```no_run + /// ```rust,ignore /// extern crate actix_web; /// /// use actix_web::fs::NamedFile; @@ -150,7 +156,7 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ```rust,ignore /// use actix_web::fs::NamedFile; /// /// let file = NamedFile::open("foo.txt"); @@ -168,7 +174,7 @@ impl NamedFile { /// /// # Examples /// - /// ```no_run + /// ```rust,ignore /// extern crate actix_web; /// /// use actix_web::fs::{DefaultConfig, NamedFile}; @@ -183,7 +189,11 @@ impl NamedFile { /// Ok(()) /// } /// ``` - pub fn from_file_with_config>(file: File, path: P, _: C) -> io::Result> { + pub fn from_file_with_config>( + file: File, + path: P, + _: C, + ) -> io::Result> { let path = path.as_ref().to_path_buf(); // Get the name of the file and use it to construct default Content-Type @@ -195,7 +205,7 @@ impl NamedFile { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Provided path has no filename", - )) + )); } }; @@ -210,7 +220,6 @@ impl NamedFile { let md = file.metadata()?; let modified = md.modified().ok(); - let cpu_pool = None; let encoding = None; Ok(NamedFile { path, @@ -219,7 +228,6 @@ impl NamedFile { content_disposition, md, modified, - cpu_pool, encoding, status_code: StatusCode::OK, _cd_map: PhantomData, @@ -230,12 +238,15 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ```rust,ignore /// use actix_web::fs::{DefaultConfig, NamedFile}; /// /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); /// ``` - pub fn open_with_config>(path: P, config: C) -> io::Result> { + pub fn open_with_config>( + path: P, + config: C, + ) -> io::Result> { Self::from_file_with_config(File::open(&path)?, path, config) } @@ -249,7 +260,7 @@ impl NamedFile { /// /// # Examples /// - /// ```rust + /// ```rust,ignore /// # use std::io; /// use actix_web::fs::NamedFile; /// @@ -264,13 +275,6 @@ impl NamedFile { self.path.as_path() } - /// Set `CpuPool` to use - #[inline] - pub fn set_cpu_pool(mut self, cpu_pool: CpuPool) -> Self { - self.cpu_pool = Some(cpu_pool); - self - } - /// Set response **Status Code** pub fn set_status_code(mut self, status: StatusCode) -> Self { self.status_code = status; @@ -352,7 +356,7 @@ impl DerefMut for NamedFile { } /// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { +fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { match req.get_header::() { None | Some(header::IfMatch::Any) => true, Some(header::IfMatch::Items(ref items)) => { @@ -369,7 +373,7 @@ fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool } /// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { +fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { match req.get_header::() { Some(header::IfNoneMatch::Any) => false, Some(header::IfNoneMatch::Items(ref items)) => { @@ -387,34 +391,33 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool } impl Responder for NamedFile { - type Item = HttpResponse; - type Error = io::Error; + type Error = Error; + type Future = FutureResult; - fn respond_to(self, req: &HttpRequest) -> Result { + fn respond_to(self, req: &HttpRequest) -> Self::Future { if self.status_code != StatusCode::OK { - let mut resp = HttpResponse::build(self.status_code); + let mut resp = Response::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) .header( header::CONTENT_DISPOSITION, self.content_disposition.to_string(), ); - - if let Some(current_encoding) = self.encoding { - resp.content_encoding(current_encoding); - } + // TODO blocking by compressing + // if let Some(current_encoding) = self.encoding { + // resp.content_encoding(current_encoding); + // } let reader = ChunkedReadFile { size: self.md.len(), offset: 0, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, counter: 0, }; - return Ok(resp.streaming(reader)); + return ok(resp.streaming(reader)); } if !C::is_method_allowed(req.method()) { - return Ok(HttpResponse::MethodNotAllowed() + return ok(Response::MethodNotAllowed() .header(header::CONTENT_TYPE, "text/plain") .header(header::ALLOW, "GET, HEAD") .body("This resource only supports GET and HEAD.")); @@ -451,20 +454,21 @@ impl Responder for NamedFile { false }; - let mut resp = HttpResponse::build(self.status_code); + let mut resp = Response::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) .header( header::CONTENT_DISPOSITION, self.content_disposition.to_string(), ); - - if let Some(current_encoding) = self.encoding { - resp.content_encoding(current_encoding); - } + // TODO blocking by compressing + // if let Some(current_encoding) = self.encoding { + // resp.content_encoding(current_encoding); + // } resp.if_some(last_modified, |lm, resp| { resp.set(header::LastModified(lm)); - }).if_some(etag, |etag, resp| { + }) + .if_some(etag, |etag, resp| { resp.set(header::ETag(etag)); }); @@ -479,7 +483,8 @@ impl Responder for NamedFile { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { length = rangesvec[0].length; offset = rangesvec[0].start; - resp.content_encoding(ContentEncoding::Identity); + // TODO blocking by compressing + // resp.content_encoding(ContentEncoding::Identity); resp.header( header::CONTENT_RANGE, format!( @@ -491,59 +496,66 @@ impl Responder for NamedFile { ); } else { resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); - return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); + return ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); }; } else { - return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); + return ok(resp.status(StatusCode::BAD_REQUEST).finish()); }; }; resp.header(header::CONTENT_LENGTH, format!("{}", length)); if precondition_failed { - return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); + return ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); } else if not_modified { - return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); + return ok(resp.status(StatusCode::NOT_MODIFIED).finish()); } if *req.method() == Method::HEAD { - Ok(resp.finish()) + ok(resp.finish()) } else { let reader = ChunkedReadFile { offset, size: length, - cpu_pool: self.cpu_pool.unwrap_or_else(|| req.cpu_pool().clone()), file: Some(self.file), fut: None, counter: 0, }; if offset != 0 || length != self.md.len() { - return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); + return ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); }; - Ok(resp.streaming(reader)) + ok(resp.streaming(reader)) } } } #[doc(hidden)] /// A helper created from a `std::fs::File` which reads the file -/// chunk-by-chunk on a `CpuPool`. +/// chunk-by-chunk on a `ThreadPool`. pub struct ChunkedReadFile { size: u64, offset: u64, - cpu_pool: CpuPool, file: Option, - fut: Option>, + fut: Option>, counter: u64, } +fn handle_error(err: blocking::BlockingError) -> Error { + match err { + blocking::BlockingError::Error(err) => err.into(), + blocking::BlockingError::Canceled => { + ErrorInternalServerError("Unexpected error").into() + } + } +} + impl Stream for ChunkedReadFile { type Item = Bytes; type Error = Error; fn poll(&mut self) -> Poll, Error> { if self.fut.is_some() { - return match self.fut.as_mut().unwrap().poll()? { + return match self.fut.as_mut().unwrap().poll().map_err(handle_error)? { Async::Ready((file, bytes)) => { self.fut.take(); self.file = Some(file); @@ -563,7 +575,7 @@ impl Stream for ChunkedReadFile { Ok(Async::Ready(None)) } else { let mut file = self.file.take().expect("Use after completion"); - self.fut = Some(self.cpu_pool.spawn_fn(move || { + self.fut = Some(blocking::run(move || { let max_bytes: usize; max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; let mut buf = Vec::with_capacity(max_bytes); @@ -580,8 +592,8 @@ impl Stream for ChunkedReadFile { } } -type DirectoryRenderer = - Fn(&Directory, &HttpRequest) -> Result; +type DirectoryRenderer = + Fn(&Directory, &HttpRequest) -> Result; /// A directory; responds with the generated directory listing. #[derive(Debug)] @@ -629,10 +641,10 @@ macro_rules! encode_file_name { }; } -fn directory_listing( +fn directory_listing( dir: &Directory, - req: &HttpRequest, -) -> Result { + req: &HttpRequest, +) -> Result { let index_of = format!("Index of {}", req.path()); let mut body = String::new(); let base = Path::new(req.path()); @@ -677,9 +689,12 @@ fn directory_listing( \n", index_of, index_of, body ); - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html)) + Ok(ServiceResponse::new( + req.clone(), + Response::Ok() + .content_type("text/html; charset=utf-8") + .body(html), + )) } /// Static files handling @@ -687,7 +702,7 @@ fn directory_listing( /// `StaticFile` handler must be registered with `App::handler()` method, /// because `StaticFile` handler requires access sub-path information. /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{fs, App}; /// @@ -701,9 +716,10 @@ pub struct StaticFiles { directory: PathBuf, index: Option, show_index: bool, - cpu_pool: CpuPool, - default: Box>, - renderer: Box>, + default: Rc< + RefCell, ServiceResponse>>>>, + >, + renderer: Rc, _chunk_size: usize, _follow_symlinks: bool, _cd_map: PhantomData, @@ -712,21 +728,12 @@ pub struct StaticFiles { impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory. /// - /// `StaticFile` uses `CpuPool` for blocking filesystem operations. - /// By default pool with 20 threads is used. + /// `StaticFile` uses `ThreadPool` for blocking filesystem operations. + /// By default pool with 5x threads of available cpus is used. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. pub fn new>(dir: T) -> Result, Error> { Self::with_config(dir, DefaultConfig) } - - /// Create new `StaticFiles` instance for specified base directory and - /// `CpuPool`. - pub fn with_pool>( - dir: T, - pool: CpuPool, - ) -> Result, Error> { - Self::with_config_pool(dir, pool, DefaultConfig) - } } impl StaticFiles { @@ -735,36 +742,20 @@ impl StaticFiles { /// Identical with `new` but allows to specify configiration to use. pub fn with_config>( dir: T, - config: C, - ) -> Result, Error> { - // use default CpuPool - let pool = { DEFAULT_CPUPOOL.lock().clone() }; - - StaticFiles::with_config_pool(dir, pool, config) - } - - /// Create new `StaticFiles` instance for specified base directory with config and - /// `CpuPool`. - pub fn with_config_pool>( - dir: T, - pool: CpuPool, _: C, ) -> Result, Error> { let dir = dir.into().canonicalize()?; if !dir.is_dir() { - return Err(StaticFileError::IsNotDirectory.into()); + return Err(StaticFilesError::IsNotDirectory.into()); } Ok(StaticFiles { directory: dir, index: None, show_index: false, - cpu_pool: pool, - default: Box::new(WrapHandler::new(|_: &_| { - HttpResponse::new(StatusCode::NOT_FOUND) - })), - renderer: Box::new(directory_listing), + default: Rc::new(RefCell::new(None)), + renderer: Rc::new(directory_listing), _chunk_size: 0, _follow_symlinks: false, _cd_map: PhantomData, @@ -782,11 +773,11 @@ impl StaticFiles { /// Set custom directory renderer pub fn files_listing_renderer(mut self, f: F) -> Self where - for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) - -> Result - + 'static, + for<'r, 's> F: + Fn(&'r Directory, &'s HttpRequest) -> Result + + 'static, { - self.renderer = Box::new(f); + self.renderer = Rc::new(f); self } @@ -798,54 +789,174 @@ impl StaticFiles { self.index = Some(index.into()); self } +} - /// Sets default handler which is used when no matched file could be found. - pub fn default_handler>(mut self, handler: H) -> StaticFiles { - self.default = Box::new(WrapHandler::new(handler)); - self +impl NewService for StaticFiles { + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Service = StaticFilesService; + type InitError = Error; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(StaticFilesService { + directory: self.directory.clone(), + index: self.index.clone(), + show_index: self.show_index, + default: self.default.clone(), + renderer: self.renderer.clone(), + _chunk_size: self._chunk_size, + _follow_symlinks: self._follow_symlinks, + _cd_map: self._cd_map, + }) + } +} + +pub struct StaticFilesService { + directory: PathBuf, + index: Option, + show_index: bool, + default: Rc< + RefCell, ServiceResponse>>>>, + >, + renderer: Rc, + _chunk_size: usize, + _follow_symlinks: bool, + _cd_map: PhantomData, +} + +impl Service for StaticFilesService { + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) } - fn try_handle( - &self, - req: &HttpRequest, - ) -> Result, Error> { - let tail: String = req.match_info().get_decoded("tail").unwrap_or_else(|| "".to_string()); - let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?; - + fn call(&mut self, req: Self::Request) -> Self::Future { + let mut req = req; + let real_path = match PathBuf::from_request(&mut req).poll() { + Ok(Async::Ready(item)) => item, + Ok(Async::NotReady) => unreachable!(), + Err(e) => return err(Error::from(e)), + }; // full filepath - let path = self.directory.join(&relpath).canonicalize()?; + let path = match self.directory.join(&real_path).canonicalize() { + Ok(path) => path, + Err(e) => return err(Error::from(e)), + }; if path.is_dir() { if let Some(ref redir_index) = self.index { let path = path.join(redir_index); - NamedFile::open_with_config(path, C::default())? - .set_cpu_pool(self.cpu_pool.clone()) - .respond_to(&req)? - .respond_to(&req) + match NamedFile::open_with_config(path, C::default()) { + Ok(named_file) => match named_file.respond_to(&req).poll() { + Ok(Async::Ready(item)) => { + ok(ServiceResponse::new(req.clone(), item)) + } + Ok(Async::NotReady) => unreachable!(), + Err(e) => err(Error::from(e)), + }, + Err(e) => err(Error::from(e)), + } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); - Ok((*self.renderer)(&dir, &req)?.into()) + let x = (self.renderer)(&dir, &req); + match x { + Ok(resp) => ok(resp), + Err(e) => err(Error::from(e)), + } } else { - Err(StaticFileError::IsDirectory.into()) + err(StaticFilesError::IsDirectory.into()) } } else { - NamedFile::open_with_config(path, C::default())? - .set_cpu_pool(self.cpu_pool.clone()) - .respond_to(&req)? - .respond_to(&req) + match NamedFile::open_with_config(path, C::default()) { + Ok(named_file) => match named_file.respond_to(&req).poll() { + Ok(Async::Ready(item)) => { + ok(ServiceResponse::new(req.clone(), item)) + } + Ok(Async::NotReady) => unreachable!(), + Err(e) => err(Error::from(e)), + }, + Err(e) => err(Error::from(e)), + } } } } -impl Handler for StaticFiles { - type Result = Result, Error>; +impl

    FromRequest

    for PathBuf { + type Error = UriSegmentError; + type Future = FutureResult; - fn handle(&self, req: &HttpRequest) -> Self::Result { - self.try_handle(req).or_else(|e| { - debug!("StaticFiles: Failed to handle {}: {}", req.path(), e); - Ok(self.default.handle(req)) - }) + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + let path_str = req.match_info().path(); + let mut buf = PathBuf::new(); + for segment in path_str.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) + } +} + +/// Errors which can occur when serving static files. +#[derive(Display, Debug, PartialEq)] +enum StaticFilesError { + /// Path is not a directory + #[display(fmt = "Path is not a directory. Unable to serve static files")] + IsNotDirectory, + + /// Cannot render directory + #[display(fmt = "Unable to render directory without index file")] + IsDirectory, +} + +/// Return `NotFound` for `StaticFilesError` +impl ResponseError for StaticFilesError { + fn error_response(&self) -> Response { + Response::new(StatusCode::NOT_FOUND) + } +} + +#[derive(Display, Debug, PartialEq)] +pub enum UriSegmentError { + /// The segment started with the wrapped invalid character. + #[display(fmt = "The segment started with the wrapped invalid character")] + BadStart(char), + /// The segment contained the wrapped invalid character. + #[display(fmt = "The segment contained the wrapped invalid character")] + BadChar(char), + /// The segment ended with the wrapped invalid character. + #[display(fmt = "The segment ended with the wrapped invalid character")] + BadEnd(char), +} + +/// Return `BadRequest` for `UriSegmentError` +impl ResponseError for UriSegmentError { + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) } } @@ -888,7 +999,7 @@ impl HttpRange { if start_str.is_empty() { // If no start is specified, end specifies the // range start relative to the end of the file. - let mut length: i64 = try!(end_str.parse().map_err(|_| ())); + let mut length: i64 = end_str.parse().map_err(|_| ())?; if length > size_sig { length = size_sig; @@ -931,7 +1042,8 @@ impl HttpRange { length: length as u64, })) } - }).collect::>()?; + }) + .collect::>()?; let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); @@ -943,985 +1055,981 @@ impl HttpRange { } } -#[cfg(test)] -mod tests { - use std::fs; - use std::time::Duration; - use std::ops::Add; - - use super::*; - use application::App; - use body::{Binary, Body}; - use http::{header, Method, StatusCode}; - use test::{self, TestRequest}; - - #[test] - fn test_file_extension_to_mime() { - let m = file_extension_to_mime("jpg"); - assert_eq!(m, mime::IMAGE_JPEG); - - let m = file_extension_to_mime("invalid extension!!"); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - - let m = file_extension_to_mime(""); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - } - - #[test] - fn test_if_modified_since_without_if_none_match() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - let since = header::HttpDate::from( - SystemTime::now().add(Duration::from_secs(60))); - - let req = TestRequest::default() - .header(header::IF_MODIFIED_SINCE, since) - .finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.status(), - StatusCode::NOT_MODIFIED - ); - } - - #[test] - fn test_if_modified_since_with_if_none_match() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - let since = header::HttpDate::from( - SystemTime::now().add(Duration::from_secs(60))); - - let req = TestRequest::default() - .header(header::IF_NONE_MATCH, "miss_etag") - .header(header::IF_MODIFIED_SINCE, since) - .finish(); - let resp = file.respond_to(&req).unwrap(); - assert_ne!( - resp.status(), - StatusCode::NOT_MODIFIED - ); - } - - #[test] - fn test_named_file_text() { - assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[test] - fn test_named_file_set_content_type() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_content_type(mime::TEXT_XML) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/xml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[test] - fn test_named_file_image() { - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[test] - fn test_named_file_image_attachment() { - use header::{ContentDisposition, DispositionParam, DispositionType}; - let cd = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename(String::from("test.png"))], - }; - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_content_disposition(cd) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - } - - #[derive(Default)] - pub struct AllAttachmentConfig; - impl StaticFileConfig for AllAttachmentConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Attachment - } - } - - #[derive(Default)] - pub struct AllInlineConfig; - impl StaticFileConfig for AllInlineConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Inline - } - } - - #[test] - fn test_named_file_image_attachment_and_custom_config() { - let file = NamedFile::open_with_config("tests/test.png", AllAttachmentConfig) - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - - let file = NamedFile::open_with_config("tests/test.png", AllInlineConfig) - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[test] - fn test_named_file_binary() { - let mut file = NamedFile::open("tests/test.binary") - .unwrap() - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.binary\"" - ); - } - - #[test] - fn test_named_file_status_code_text() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_status_code(StatusCode::NOT_FOUND) - .set_cpu_pool(CpuPool::new(1)); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_named_file_ranges_status_code() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/Cargo.toml")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/Cargo.toml")) - .header(header::RANGE, "bytes=1-0") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); - } - - #[test] - fn test_named_file_content_range_headers() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".") - .unwrap() - .index_file("tests/test.binary"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes 10-20/100"); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-5") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes */100"); - } - - #[test] - fn test_named_file_content_length_headers() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".") - .unwrap() - .index_file("tests/test.binary"), - ) - }); - - // Valid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-20") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "11"); - - // Invalid range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .header(header::RANGE, "bytes=10-8") - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "0"); - - // Without range header - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .no_default_headers() - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "100"); - - // chunked - let request = srv - .get() - .uri(srv.url("/t%65st/tests/test.binary")) - .finish() - .unwrap(); - - let response = srv.execute(request.send()).unwrap(); - { - let te = response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(te, "chunked"); - } - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("tests/test.binary").unwrap()); - assert_eq!(bytes, data); - } - - #[test] - fn test_static_files_with_spaces() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "/", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - let request = srv - .get() - .uri(srv.url("/tests/test%20space.binary")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); - assert_eq!(bytes, data); - } - - #[derive(Default)] - pub struct OnlyMethodHeadConfig; - impl StaticFileConfig for OnlyMethodHeadConfig { - fn is_method_allowed(method: &Method) -> bool { - match *method { - Method::HEAD => true, - _ => false, - } - } - } - - #[test] - fn test_named_file_not_allowed() { - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::POST).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::PUT).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::GET).finish(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_named_file_content_encoding() { - let req = TestRequest::default().method(Method::GET).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); - - assert!(file.encoding.is_none()); - let resp = file - .set_content_encoding(ContentEncoding::Identity) - .respond_to(&req) - .unwrap(); - - assert!(resp.content_encoding().is_some()); - assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); - } - - #[test] - fn test_named_file_any_method() { - let req = TestRequest::default().method(Method::POST).finish(); - let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_static_files() { - let mut st = StaticFiles::new(".").unwrap().show_files_listing(); - let req = TestRequest::with_uri("/missing") - .param("tail", "missing") - .finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - st.show_index = false; - let req = TestRequest::default().finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::default().param("tail", "").finish(); - - st.show_index = true; - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - 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")); - } - - #[test] - fn test_static_files_bad_directory() { - let st: Result, Error> = StaticFiles::new("missing"); - assert!(st.is_err()); - - let st: Result, Error> = StaticFiles::new("Cargo.toml"); - assert!(st.is_err()); - } - - #[test] - fn test_default_handler_file_missing() { - let st = StaticFiles::new(".") - .unwrap() - .default_handler(|_: &_| "default content"); - let req = TestRequest::with_uri("/missing") - .param("tail", "missing") - .finish(); - - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body(), - &Body::Binary(Binary::Slice(b"default content")) - ); - } - - #[test] - fn test_serve_index() { - let st = StaticFiles::new(".").unwrap().index_file("test.binary"); - let req = TestRequest::default().uri("/tests").finish(); - - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).expect("content type"), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).expect("content disposition"), - "attachment; filename=\"test.binary\"" - ); - - let req = TestRequest::default().uri("/tests/").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.binary\"" - ); - - // nonexistent index file - let req = TestRequest::default().uri("/tests/unknown").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::default().uri("/tests/unknown/").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_serve_index_nested() { - let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); - let req = TestRequest::default().uri("/src/client").finish(); - let resp = st.handle(&req).respond_to(&req).unwrap(); - let resp = resp.as_msg(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-rust" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"mod.rs\"" - ); - } - - #[test] - fn integration_serve_index_with_prefix() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .prefix("public") - .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) - }); - - let request = srv.get().uri(srv.url("/public")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - assert_eq!(bytes, data); - - let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - assert_eq!(bytes, data); - } - - #[test] - fn integration_serve_index() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - assert_eq!(bytes, data); - - let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - let bytes = srv.execute(response.body()).unwrap(); - let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - assert_eq!(bytes, data); - - // nonexistent index file - let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - - let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn integration_percent_encoded() { - let mut srv = test::TestServer::with_factory(|| { - App::new().handler( - "test", - StaticFiles::new(".").unwrap().index_file("Cargo.toml"), - ) - }); - - let request = srv - .get() - .uri(srv.url("/test/%43argo.toml")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - } - - struct T(&'static str, u64, Vec); - - #[test] - fn test_parse() { - let tests = vec![ - T("", 0, vec![]), - T("", 1000, vec![]), - T("foo", 0, vec![]), - T("bytes=", 0, vec![]), - T("bytes=7", 10, vec![]), - T("bytes= 7 ", 10, vec![]), - T("bytes=1-", 0, vec![]), - T("bytes=5-4", 10, vec![]), - T("bytes=0-2,5-4", 10, vec![]), - T("bytes=2-5,4-3", 10, vec![]), - T("bytes=--5,4--3", 10, vec![]), - T("bytes=A-", 10, vec![]), - T("bytes=A- ", 10, vec![]), - T("bytes=A-Z", 10, vec![]), - T("bytes= -Z", 10, vec![]), - T("bytes=5-Z", 10, vec![]), - T("bytes=Ran-dom, garbage", 10, vec![]), - T("bytes=0x01-0x02", 10, vec![]), - T("bytes= ", 10, vec![]), - T("bytes= , , , ", 10, vec![]), - T( - "bytes=0-9", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=5-", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=0-20", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=15-,0-5", - 10, - vec![HttpRange { - start: 0, - length: 6, - }], - ), - T( - "bytes=1-2,5-", - 10, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 5, - length: 5, - }, - ], - ), - T( - "bytes=-2 , 7-", - 11, - vec![ - HttpRange { - start: 9, - length: 2, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=0-0 ,2-2, 7-", - 11, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 2, - length: 1, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=-5", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=-15", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-499", - 10000, - vec![HttpRange { - start: 0, - length: 500, - }], - ), - T( - "bytes=500-999", - 10000, - vec![HttpRange { - start: 500, - length: 500, - }], - ), - T( - "bytes=-500", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=9500-", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=0-0,-1", - 10000, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 9999, - length: 1, - }, - ], - ), - T( - "bytes=500-600,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 101, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - T( - "bytes=500-700,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 201, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - // Match Apache laxity: - T( - "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", - 11, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 4, - length: 2, - }, - HttpRange { - start: 7, - length: 2, - }, - ], - ), - ]; - - for t in tests { - let header = t.0; - let size = t.1; - let expected = t.2; - - let res = HttpRange::parse(header, size); - - if res.is_err() { - if expected.is_empty() { - continue; - } else { - assert!( - false, - "parse({}, {}) returned error {:?}", - header, - size, - res.unwrap_err() - ); - } - } - - let got = res.unwrap(); - - if got.len() != expected.len() { - assert!( - false, - "len(parseRange({}, {})) = {}, want {}", - header, - size, - got.len(), - expected.len() - ); - continue; - } - - for i in 0..expected.len() { - if got[i].start != expected[i].start { - assert!( - false, - "parseRange({}, {})[{}].start = {}, want {}", - header, size, i, got[i].start, expected[i].start - ) - } - if got[i].length != expected[i].length { - assert!( - false, - "parseRange({}, {})[{}].length = {}, want {}", - header, size, i, got[i].length, expected[i].length - ) - } - } - } - } -} +// #[cfg(test)] +// mod tests { +// use std::fs; +// use std::ops::Add; +// use std::time::Duration; + +// use super::*; +// use application::App; +// use body::{Binary, Body}; +// use http::{header, Method, StatusCode}; +// use test::{self, TestRequest}; + +// #[test] +// fn test_file_extension_to_mime() { +// let m = file_extension_to_mime("jpg"); +// assert_eq!(m, mime::IMAGE_JPEG); + +// let m = file_extension_to_mime("invalid extension!!"); +// assert_eq!(m, mime::APPLICATION_OCTET_STREAM); + +// let m = file_extension_to_mime(""); +// assert_eq!(m, mime::APPLICATION_OCTET_STREAM); +// } + +// #[test] +// fn test_if_modified_since_without_if_none_match() { +// let mut file = NamedFile::open("Cargo.toml") +// .unwrap() +// .set_cpu_pool(CpuPool::new(1)); +// let since = +// header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + +// let req = TestRequest::default() +// .header(header::IF_MODIFIED_SINCE, since) +// .finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); +// } + +// #[test] +// fn test_if_modified_since_with_if_none_match() { +// let mut file = NamedFile::open("Cargo.toml") +// .unwrap() +// .set_cpu_pool(CpuPool::new(1)); +// let since = +// header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + +// let req = TestRequest::default() +// .header(header::IF_NONE_MATCH, "miss_etag") +// .header(header::IF_MODIFIED_SINCE, since) +// .finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); +// } + +// #[test] +// fn test_named_file_text() { +// assert!(NamedFile::open("test--").is_err()); +// let mut file = NamedFile::open("Cargo.toml") +// .unwrap() +// .set_cpu_pool(CpuPool::new(1)); +// { +// file.file(); +// let _f: &File = &file; +// } +// { +// let _f: &mut File = &mut file; +// } + +// let req = TestRequest::default().finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "text/x-toml" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "inline; filename=\"Cargo.toml\"" +// ); +// } + +// #[test] +// fn test_named_file_set_content_type() { +// let mut file = NamedFile::open("Cargo.toml") +// .unwrap() +// .set_content_type(mime::TEXT_XML) +// .set_cpu_pool(CpuPool::new(1)); +// { +// file.file(); +// let _f: &File = &file; +// } +// { +// let _f: &mut File = &mut file; +// } + +// let req = TestRequest::default().finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "text/xml" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "inline; filename=\"Cargo.toml\"" +// ); +// } + +// #[test] +// fn test_named_file_image() { +// let mut file = NamedFile::open("tests/test.png") +// .unwrap() +// .set_cpu_pool(CpuPool::new(1)); +// { +// file.file(); +// let _f: &File = &file; +// } +// { +// let _f: &mut File = &mut file; +// } + +// let req = TestRequest::default().finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "image/png" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "inline; filename=\"test.png\"" +// ); +// } + +// #[test] +// fn test_named_file_image_attachment() { +// use header::{ContentDisposition, DispositionParam, DispositionType}; +// let cd = ContentDisposition { +// disposition: DispositionType::Attachment, +// parameters: vec![DispositionParam::Filename(String::from("test.png"))], +// }; +// let mut file = NamedFile::open("tests/test.png") +// .unwrap() +// .set_content_disposition(cd) +// .set_cpu_pool(CpuPool::new(1)); +// { +// file.file(); +// let _f: &File = &file; +// } +// { +// let _f: &mut File = &mut file; +// } + +// let req = TestRequest::default().finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "image/png" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "attachment; filename=\"test.png\"" +// ); +// } + +// #[derive(Default)] +// pub struct AllAttachmentConfig; +// impl StaticFileConfig for AllAttachmentConfig { +// fn content_disposition_map(_typ: mime::Name) -> DispositionType { +// DispositionType::Attachment +// } +// } + +// #[derive(Default)] +// pub struct AllInlineConfig; +// impl StaticFileConfig for AllInlineConfig { +// fn content_disposition_map(_typ: mime::Name) -> DispositionType { +// DispositionType::Inline +// } +// } + +// #[test] +// fn test_named_file_image_attachment_and_custom_config() { +// let file = NamedFile::open_with_config("tests/test.png", AllAttachmentConfig) +// .unwrap() +// .set_cpu_pool(CpuPool::new(1)); + +// let req = TestRequest::default().finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "image/png" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "attachment; filename=\"test.png\"" +// ); + +// let file = NamedFile::open_with_config("tests/test.png", AllInlineConfig) +// .unwrap() +// .set_cpu_pool(CpuPool::new(1)); + +// let req = TestRequest::default().finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "image/png" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "inline; filename=\"test.png\"" +// ); +// } + +// #[test] +// fn test_named_file_binary() { +// let mut file = NamedFile::open("tests/test.binary") +// .unwrap() +// .set_cpu_pool(CpuPool::new(1)); +// { +// file.file(); +// let _f: &File = &file; +// } +// { +// let _f: &mut File = &mut file; +// } + +// let req = TestRequest::default().finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "application/octet-stream" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "attachment; filename=\"test.binary\"" +// ); +// } + +// #[test] +// fn test_named_file_status_code_text() { +// let mut file = NamedFile::open("Cargo.toml") +// .unwrap() +// .set_status_code(StatusCode::NOT_FOUND) +// .set_cpu_pool(CpuPool::new(1)); +// { +// file.file(); +// let _f: &File = &file; +// } +// { +// let _f: &mut File = &mut file; +// } + +// let req = TestRequest::default().finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "text/x-toml" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "inline; filename=\"Cargo.toml\"" +// ); +// assert_eq!(resp.status(), StatusCode::NOT_FOUND); +// } + +// #[test] +// fn test_named_file_ranges_status_code() { +// let mut srv = test::TestServer::with_factory(|| { +// App::new().handler( +// "test", +// StaticFiles::new(".").unwrap().index_file("Cargo.toml"), +// ) +// }); + +// // Valid range header +// let request = srv +// .get() +// .uri(srv.url("/t%65st/Cargo.toml")) +// .header(header::RANGE, "bytes=10-20") +// .finish() +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); + +// // Invalid range header +// let request = srv +// .get() +// .uri(srv.url("/t%65st/Cargo.toml")) +// .header(header::RANGE, "bytes=1-0") +// .finish() +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); + +// assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); +// } + +// #[test] +// fn test_named_file_content_range_headers() { +// let mut srv = test::TestServer::with_factory(|| { +// App::new().handler( +// "test", +// StaticFiles::new(".") +// .unwrap() +// .index_file("tests/test.binary"), +// ) +// }); + +// // Valid range header +// let request = srv +// .get() +// .uri(srv.url("/t%65st/tests/test.binary")) +// .header(header::RANGE, "bytes=10-20") +// .finish() +// .unwrap(); + +// let response = srv.execute(request.send()).unwrap(); + +// let contentrange = response +// .headers() +// .get(header::CONTENT_RANGE) +// .unwrap() +// .to_str() +// .unwrap(); + +// assert_eq!(contentrange, "bytes 10-20/100"); + +// // Invalid range header +// let request = srv +// .get() +// .uri(srv.url("/t%65st/tests/test.binary")) +// .header(header::RANGE, "bytes=10-5") +// .finish() +// .unwrap(); + +// let response = srv.execute(request.send()).unwrap(); + +// let contentrange = response +// .headers() +// .get(header::CONTENT_RANGE) +// .unwrap() +// .to_str() +// .unwrap(); + +// assert_eq!(contentrange, "bytes */100"); +// } + +// #[test] +// fn test_named_file_content_length_headers() { +// let mut srv = test::TestServer::with_factory(|| { +// App::new().handler( +// "test", +// StaticFiles::new(".") +// .unwrap() +// .index_file("tests/test.binary"), +// ) +// }); + +// // Valid range header +// let request = srv +// .get() +// .uri(srv.url("/t%65st/tests/test.binary")) +// .header(header::RANGE, "bytes=10-20") +// .finish() +// .unwrap(); + +// let response = srv.execute(request.send()).unwrap(); + +// let contentlength = response +// .headers() +// .get(header::CONTENT_LENGTH) +// .unwrap() +// .to_str() +// .unwrap(); + +// assert_eq!(contentlength, "11"); + +// // Invalid range header +// let request = srv +// .get() +// .uri(srv.url("/t%65st/tests/test.binary")) +// .header(header::RANGE, "bytes=10-8") +// .finish() +// .unwrap(); + +// let response = srv.execute(request.send()).unwrap(); + +// let contentlength = response +// .headers() +// .get(header::CONTENT_LENGTH) +// .unwrap() +// .to_str() +// .unwrap(); + +// assert_eq!(contentlength, "0"); + +// // Without range header +// let request = srv +// .get() +// .uri(srv.url("/t%65st/tests/test.binary")) +// .no_default_headers() +// .finish() +// .unwrap(); + +// let response = srv.execute(request.send()).unwrap(); + +// let contentlength = response +// .headers() +// .get(header::CONTENT_LENGTH) +// .unwrap() +// .to_str() +// .unwrap(); + +// assert_eq!(contentlength, "100"); + +// // chunked +// let request = srv +// .get() +// .uri(srv.url("/t%65st/tests/test.binary")) +// .finish() +// .unwrap(); + +// let response = srv.execute(request.send()).unwrap(); +// { +// let te = response +// .headers() +// .get(header::TRANSFER_ENCODING) +// .unwrap() +// .to_str() +// .unwrap(); +// assert_eq!(te, "chunked"); +// } +// let bytes = srv.execute(response.body()).unwrap(); +// let data = Bytes::from(fs::read("tests/test.binary").unwrap()); +// assert_eq!(bytes, data); +// } + +// #[test] +// fn test_static_files_with_spaces() { +// let mut srv = test::TestServer::with_factory(|| { +// App::new() +// .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) +// }); +// let request = srv +// .get() +// .uri(srv.url("/tests/test%20space.binary")) +// .finish() +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::OK); + +// let bytes = srv.execute(response.body()).unwrap(); +// let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); +// assert_eq!(bytes, data); +// } + +// #[derive(Default)] +// pub struct OnlyMethodHeadConfig; +// impl StaticFileConfig for OnlyMethodHeadConfig { +// fn is_method_allowed(method: &Method) -> bool { +// match *method { +// Method::HEAD => true, +// _ => false, +// } +// } +// } + +// #[test] +// fn test_named_file_not_allowed() { +// let file = +// NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); +// let req = TestRequest::default().method(Method::POST).finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + +// let file = +// NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); +// let req = TestRequest::default().method(Method::PUT).finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + +// let file = +// NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); +// let req = TestRequest::default().method(Method::GET).finish(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); +// } + +// #[test] +// fn test_named_file_content_encoding() { +// let req = TestRequest::default().method(Method::GET).finish(); +// let file = NamedFile::open("Cargo.toml").unwrap(); + +// assert!(file.encoding.is_none()); +// let resp = file +// .set_content_encoding(ContentEncoding::Identity) +// .respond_to(&req) +// .unwrap(); + +// assert!(resp.content_encoding().is_some()); +// assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); +// } + +// #[test] +// fn test_named_file_any_method() { +// let req = TestRequest::default().method(Method::POST).finish(); +// let file = NamedFile::open("Cargo.toml").unwrap(); +// let resp = file.respond_to(&req).unwrap(); +// assert_eq!(resp.status(), StatusCode::OK); +// } + +// #[test] +// fn test_static_files() { +// let mut st = StaticFiles::new(".").unwrap().show_files_listing(); +// let req = TestRequest::with_uri("/missing") +// .param("tail", "missing") +// .finish(); +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// assert_eq!(resp.status(), StatusCode::NOT_FOUND); + +// st.show_index = false; +// let req = TestRequest::default().finish(); +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// assert_eq!(resp.status(), StatusCode::NOT_FOUND); + +// let req = TestRequest::default().param("tail", "").finish(); + +// st.show_index = true; +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// 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")); +// } + +// #[test] +// fn test_static_files_bad_directory() { +// let st: Result, Error> = StaticFiles::new("missing"); +// assert!(st.is_err()); + +// let st: Result, Error> = StaticFiles::new("Cargo.toml"); +// assert!(st.is_err()); +// } + +// #[test] +// fn test_default_handler_file_missing() { +// let st = StaticFiles::new(".") +// .unwrap() +// .default_handler(|_: &_| "default content"); +// let req = TestRequest::with_uri("/missing") +// .param("tail", "missing") +// .finish(); + +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// assert_eq!(resp.status(), StatusCode::OK); +// assert_eq!( +// resp.body(), +// &Body::Binary(Binary::Slice(b"default content")) +// ); +// } + +// #[test] +// fn test_serve_index() { +// let st = StaticFiles::new(".").unwrap().index_file("test.binary"); +// let req = TestRequest::default().uri("/tests").finish(); + +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// assert_eq!(resp.status(), StatusCode::OK); +// assert_eq!( +// resp.headers() +// .get(header::CONTENT_TYPE) +// .expect("content type"), +// "application/octet-stream" +// ); +// assert_eq!( +// resp.headers() +// .get(header::CONTENT_DISPOSITION) +// .expect("content disposition"), +// "attachment; filename=\"test.binary\"" +// ); + +// let req = TestRequest::default().uri("/tests/").finish(); +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// assert_eq!(resp.status(), StatusCode::OK); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "application/octet-stream" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "attachment; filename=\"test.binary\"" +// ); + +// // nonexistent index file +// let req = TestRequest::default().uri("/tests/unknown").finish(); +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// assert_eq!(resp.status(), StatusCode::NOT_FOUND); + +// let req = TestRequest::default().uri("/tests/unknown/").finish(); +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// assert_eq!(resp.status(), StatusCode::NOT_FOUND); +// } + +// #[test] +// fn test_serve_index_nested() { +// let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); +// let req = TestRequest::default().uri("/src/client").finish(); +// let resp = st.handle(&req).respond_to(&req).unwrap(); +// let resp = resp.as_msg(); +// assert_eq!(resp.status(), StatusCode::OK); +// assert_eq!( +// resp.headers().get(header::CONTENT_TYPE).unwrap(), +// "text/x-rust" +// ); +// assert_eq!( +// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), +// "inline; filename=\"mod.rs\"" +// ); +// } + +// #[test] +// fn integration_serve_index_with_prefix() { +// let mut srv = test::TestServer::with_factory(|| { +// App::new() +// .prefix("public") +// .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) +// }); + +// let request = srv.get().uri(srv.url("/public")).finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::OK); +// let bytes = srv.execute(response.body()).unwrap(); +// let data = Bytes::from(fs::read("Cargo.toml").unwrap()); +// assert_eq!(bytes, data); + +// let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::OK); +// let bytes = srv.execute(response.body()).unwrap(); +// let data = Bytes::from(fs::read("Cargo.toml").unwrap()); +// assert_eq!(bytes, data); +// } + +// #[test] +// fn integration_serve_index() { +// let mut srv = test::TestServer::with_factory(|| { +// App::new().handler( +// "test", +// StaticFiles::new(".").unwrap().index_file("Cargo.toml"), +// ) +// }); + +// let request = srv.get().uri(srv.url("/test")).finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::OK); +// let bytes = srv.execute(response.body()).unwrap(); +// let data = Bytes::from(fs::read("Cargo.toml").unwrap()); +// assert_eq!(bytes, data); + +// let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::OK); +// let bytes = srv.execute(response.body()).unwrap(); +// let data = Bytes::from(fs::read("Cargo.toml").unwrap()); +// assert_eq!(bytes, data); + +// // nonexistent index file +// let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::NOT_FOUND); + +// let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::NOT_FOUND); +// } + +// #[test] +// fn integration_percent_encoded() { +// let mut srv = test::TestServer::with_factory(|| { +// App::new().handler( +// "test", +// StaticFiles::new(".").unwrap().index_file("Cargo.toml"), +// ) +// }); + +// let request = srv +// .get() +// .uri(srv.url("/test/%43argo.toml")) +// .finish() +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert_eq!(response.status(), StatusCode::OK); +// } + +// struct T(&'static str, u64, Vec); + +// #[test] +// fn test_parse() { +// let tests = vec![ +// T("", 0, vec![]), +// T("", 1000, vec![]), +// T("foo", 0, vec![]), +// T("bytes=", 0, vec![]), +// T("bytes=7", 10, vec![]), +// T("bytes= 7 ", 10, vec![]), +// T("bytes=1-", 0, vec![]), +// T("bytes=5-4", 10, vec![]), +// T("bytes=0-2,5-4", 10, vec![]), +// T("bytes=2-5,4-3", 10, vec![]), +// T("bytes=--5,4--3", 10, vec![]), +// T("bytes=A-", 10, vec![]), +// T("bytes=A- ", 10, vec![]), +// T("bytes=A-Z", 10, vec![]), +// T("bytes= -Z", 10, vec![]), +// T("bytes=5-Z", 10, vec![]), +// T("bytes=Ran-dom, garbage", 10, vec![]), +// T("bytes=0x01-0x02", 10, vec![]), +// T("bytes= ", 10, vec![]), +// T("bytes= , , , ", 10, vec![]), +// T( +// "bytes=0-9", +// 10, +// vec![HttpRange { +// start: 0, +// length: 10, +// }], +// ), +// T( +// "bytes=0-", +// 10, +// vec![HttpRange { +// start: 0, +// length: 10, +// }], +// ), +// T( +// "bytes=5-", +// 10, +// vec![HttpRange { +// start: 5, +// length: 5, +// }], +// ), +// T( +// "bytes=0-20", +// 10, +// vec![HttpRange { +// start: 0, +// length: 10, +// }], +// ), +// T( +// "bytes=15-,0-5", +// 10, +// vec![HttpRange { +// start: 0, +// length: 6, +// }], +// ), +// T( +// "bytes=1-2,5-", +// 10, +// vec![ +// HttpRange { +// start: 1, +// length: 2, +// }, +// HttpRange { +// start: 5, +// length: 5, +// }, +// ], +// ), +// T( +// "bytes=-2 , 7-", +// 11, +// vec![ +// HttpRange { +// start: 9, +// length: 2, +// }, +// HttpRange { +// start: 7, +// length: 4, +// }, +// ], +// ), +// T( +// "bytes=0-0 ,2-2, 7-", +// 11, +// vec![ +// HttpRange { +// start: 0, +// length: 1, +// }, +// HttpRange { +// start: 2, +// length: 1, +// }, +// HttpRange { +// start: 7, +// length: 4, +// }, +// ], +// ), +// T( +// "bytes=-5", +// 10, +// vec![HttpRange { +// start: 5, +// length: 5, +// }], +// ), +// T( +// "bytes=-15", +// 10, +// vec![HttpRange { +// start: 0, +// length: 10, +// }], +// ), +// T( +// "bytes=0-499", +// 10000, +// vec![HttpRange { +// start: 0, +// length: 500, +// }], +// ), +// T( +// "bytes=500-999", +// 10000, +// vec![HttpRange { +// start: 500, +// length: 500, +// }], +// ), +// T( +// "bytes=-500", +// 10000, +// vec![HttpRange { +// start: 9500, +// length: 500, +// }], +// ), +// T( +// "bytes=9500-", +// 10000, +// vec![HttpRange { +// start: 9500, +// length: 500, +// }], +// ), +// T( +// "bytes=0-0,-1", +// 10000, +// vec![ +// HttpRange { +// start: 0, +// length: 1, +// }, +// HttpRange { +// start: 9999, +// length: 1, +// }, +// ], +// ), +// T( +// "bytes=500-600,601-999", +// 10000, +// vec![ +// HttpRange { +// start: 500, +// length: 101, +// }, +// HttpRange { +// start: 601, +// length: 399, +// }, +// ], +// ), +// T( +// "bytes=500-700,601-999", +// 10000, +// vec![ +// HttpRange { +// start: 500, +// length: 201, +// }, +// HttpRange { +// start: 601, +// length: 399, +// }, +// ], +// ), +// // Match Apache laxity: +// T( +// "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", +// 11, +// vec![ +// HttpRange { +// start: 1, +// length: 2, +// }, +// HttpRange { +// start: 4, +// length: 2, +// }, +// HttpRange { +// start: 7, +// length: 2, +// }, +// ], +// ), +// ]; + +// for t in tests { +// let header = t.0; +// let size = t.1; +// let expected = t.2; + +// let res = HttpRange::parse(header, size); + +// if res.is_err() { +// if expected.is_empty() { +// continue; +// } else { +// assert!( +// false, +// "parse({}, {}) returned error {:?}", +// header, +// size, +// res.unwrap_err() +// ); +// } +// } + +// let got = res.unwrap(); + +// if got.len() != expected.len() { +// assert!( +// false, +// "len(parseRange({}, {})) = {}, want {}", +// header, +// size, +// got.len(), +// expected.len() +// ); +// continue; +// } + +// for i in 0..expected.len() { +// if got[i].start != expected[i].start { +// assert!( +// false, +// "parseRange({}, {})[{}].start = {}, want {}", +// header, size, i, got[i].start, expected[i].start +// ) +// } +// if got[i].length != expected[i].length { +// assert!( +// false, +// "parseRange({}, {})[{}].length = {}, want {}", +// header, size, i, got[i].length, expected[i].length +// ) +// } +// } +// } +// } +// } diff --git a/src/handler.rs b/src/handler.rs index c6880818..e957d15e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,562 +1,402 @@ use std::marker::PhantomData; -use std::ops::Deref; -use futures::future::{err, ok, Future}; -use futures::{Async, Poll}; +use actix_http::{Error, Response}; +use actix_service::{NewService, Service}; +use actix_utils::Never; +use futures::future::{ok, FutureResult}; +use futures::{try_ready, Async, Future, IntoFuture, Poll}; -use error::Error; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use resource::DefaultResource; - -/// Trait defines object that could be registered as route handler -#[allow(unused_variables)] -pub trait Handler: 'static { - /// The type of value that handler will return. - type Result: Responder; - - /// Handle request - fn handle(&self, req: &HttpRequest) -> Self::Result; -} - -/// 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; - - /// Convert itself to `AsyncResult` or `Error`. - fn respond_to( - self, req: &HttpRequest, - ) -> Result; -} +use crate::request::HttpRequest; +use crate::responder::Responder; +use crate::service::{ServiceRequest, ServiceResponse}; /// Trait implemented by types that can be extracted from request. /// -/// Types that implement this trait can be used with `Route::with()` method. -pub trait FromRequest: Sized { - /// Configuration for conversion process - type Config: Default; +/// Types that implement this trait can be used with `Route` handlers. +pub trait FromRequest

    : Sized { + /// The associated error which can be returned. + type Error: Into; /// Future that resolves to a Self - type Result: Into>; + type Future: Future; /// Convert request to a Self - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; - - /// Convert request to a Self - /// - /// This method uses default extractor configuration - fn extract(req: &HttpRequest) -> Self::Result { - Self::from_request(req, &Self::Config::default()) - } + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future; } -/// Combines two different responder types into a single type -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # use futures::future::Future; -/// use actix_web::{AsyncResponder, Either, Error, HttpRequest, HttpResponse}; -/// use futures::future::result; -/// -/// type RegisterResult = -/// Either>>; -/// -/// fn index(req: HttpRequest) -> RegisterResult { -/// if is_a_variant() { -/// // <- choose variant A -/// Either::A(HttpResponse::BadRequest().body("Bad data")) -/// } else { -/// Either::B( -/// // <- variant B -/// result(Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!"))) -/// .responder(), -/// ) -/// } -/// } -/// # fn is_a_variant() -> bool { true } -/// # fn main() {} -/// ``` -#[derive(Debug, PartialEq)] -pub enum Either { - /// First branch of the type - A(A), - /// Second branch of the type - B(B), -} - -impl Responder for Either +/// Handler converter factory +pub trait Factory: Clone where - A: Responder, - B: Responder, -{ - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - match self { - Either::A(a) => match a.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - Either::B(b) => match b.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - } - } -} - -impl Future for Either -where - A: Future, - B: Future, -{ - type Item = I; - type Error = E; - - fn poll(&mut self) -> Poll { - match *self { - Either::A(ref mut fut) => fut.poll(), - Either::B(ref mut fut) => fut.poll(), - } - } -} - -impl Responder for Option -where - T: Responder, -{ - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - match self { - Some(t) => match t.respond_to(req) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err.into()), - }, - None => Ok(req.build_response(StatusCode::NOT_FOUND).finish().into()), - } - } -} - -/// Convenience trait that converts `Future` object to a `Boxed` future -/// -/// For example loading json from request's body is async operation. -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # #[macro_use] extern crate serde_derive; -/// use actix_web::{ -/// App, AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse, -/// }; -/// 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 -/// Ok(HttpResponse::Ok().into()) -/// }) -/// // Construct boxed future by using `AsyncResponder::responder()` method -/// .responder() -/// } -/// # fn main() {} -/// ``` -pub trait AsyncResponder: Sized { - /// Convert to a boxed future - 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, - R: Responder + 'static, -{ - type Result = R; - - fn handle(&self, req: &HttpRequest) -> R { - (self)(req) - } -} - -/// Represents async result -/// -/// Result could be in tree different forms. -/// * Ok(T) - ready item -/// * Err(E) - error happen during reply process -/// * Future - reply process completes in the future -pub struct AsyncResult(Option>); - -impl Future for AsyncResult { - type Item = I; - type Error = E; - - fn poll(&mut self) -> Poll { - let res = self.0.take().expect("use after resolve"); - match res { - AsyncResultItem::Ok(msg) => Ok(Async::Ready(msg)), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(mut fut) => match fut.poll() { - Ok(Async::NotReady) => { - self.0 = Some(AsyncResultItem::Future(fut)); - Ok(Async::NotReady) - } - Ok(Async::Ready(msg)) => Ok(Async::Ready(msg)), - Err(err) => Err(err), - }, - } - } -} - -pub(crate) enum AsyncResultItem { - Ok(I), - Err(E), - Future(Box>), -} - -impl AsyncResult { - /// Create async response - #[inline] - pub fn future(fut: Box>) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Future(fut))) - } - - /// Send response - #[inline] - pub fn ok>(ok: R) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Ok(ok.into()))) - } - - /// Send error - #[inline] - pub fn err>(err: R) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Err(err.into()))) - } - - #[inline] - pub(crate) fn into(self) -> AsyncResultItem { - self.0.expect("use after resolve") - } - - #[cfg(test)] - pub(crate) fn as_msg(&self) -> &I { - match self.0.as_ref().unwrap() { - &AsyncResultItem::Ok(ref resp) => resp, - _ => panic!(), - } - } - - #[cfg(test)] - pub(crate) fn as_err(&self) -> Option<&E> { - match self.0.as_ref().unwrap() { - &AsyncResultItem::Err(ref err) => Some(err), - _ => None, - } - } -} - -impl Responder for AsyncResult { - type Item = AsyncResult; - type Error = Error; - - fn respond_to( - self, _: &HttpRequest, - ) -> Result, Error> { - Ok(self) - } -} - -impl Responder for HttpResponse { - type Item = AsyncResult; - type Error = Error; - - #[inline] - fn respond_to( - self, _: &HttpRequest, - ) -> Result, Error> { - Ok(AsyncResult(Some(AsyncResultItem::Ok(self)))) - } -} - -impl From for AsyncResult { - #[inline] - fn from(resp: T) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Ok(resp))) - } -} - -impl> Responder for Result { - type Item = ::Item; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - match self { - Ok(val) => match val.respond_to(req) { - Ok(val) => Ok(val), - Err(err) => Err(err.into()), - }, - Err(err) => Err(err.into()), - } - } -} - -impl> From, E>> for AsyncResult { - #[inline] - fn from(res: Result, E>) -> Self { - match res { - Ok(val) => val, - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl> From> for AsyncResult { - #[inline] - fn from(res: Result) -> Self { - match res { - Ok(val) => AsyncResult(Some(AsyncResultItem::Ok(val))), - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl From>, E>> for AsyncResult -where - T: 'static, - E: Into + 'static, -{ - #[inline] - fn from(res: Result>, E>) -> Self { - match res { - Ok(fut) => AsyncResult(Some(AsyncResultItem::Future(Box::new( - fut.map_err(|e| e.into()), - )))), - Err(err) => AsyncResult(Some(AsyncResultItem::Err(err.into()))), - } - } -} - -impl From>> for AsyncResult { - #[inline] - fn from(fut: Box>) -> AsyncResult { - AsyncResult(Some(AsyncResultItem::Future(fut))) - } -} - -/// Convenience type alias -pub type FutureResponse = Box>; - -impl Responder for Box> -where - I: Responder + 'static, - E: Into + 'static, -{ - type Item = AsyncResult; - type Error = Error; - - #[inline] - fn respond_to( - self, req: &HttpRequest, - ) -> Result, Error> { - let req = req.clone(); - let fut = self - .map_err(|e| e.into()) - .then(move |r| match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => ok(resp), - _ => panic!("Nested async replies are not supported"), - }, - Err(e) => err(e), - }); - Ok(AsyncResult::future(Box::new(fut))) - } -} - -pub(crate) trait RouteHandler: 'static { - fn handle(&self, &HttpRequest) -> AsyncResult; - - fn has_default_resource(&self) -> bool { - false - } - - fn default_resource(&mut self, _: DefaultResource) {} - - fn finish(&mut self) {} -} - -/// Route handler wrapper for Handler -pub(crate) struct WrapHandler -where - H: Handler, R: Responder, - S: 'static, { - h: H, - s: PhantomData, + fn call(&self, param: T) -> R; } -impl WrapHandler +impl Factory<(), R> for F where - H: Handler, + F: Fn() -> R + Clone + 'static, + R: Responder + 'static, +{ + fn call(&self, _: ()) -> R { + (self)() + } +} + +#[doc(hidden)] +pub struct Handle +where + F: Factory, R: Responder, - S: 'static, { - pub fn new(h: H) -> Self { - WrapHandler { h, s: PhantomData } + hnd: F, + _t: PhantomData<(T, R)>, +} + +impl Handle +where + F: Factory, + R: Responder, +{ + pub fn new(hnd: F) -> Self { + Handle { + hnd, + _t: PhantomData, + } + } +} +impl NewService for Handle +where + F: Factory, + R: Responder + 'static, +{ + type Request = (T, HttpRequest); + type Response = ServiceResponse; + type Error = Never; + type InitError = (); + type Service = HandleService; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(HandleService { + hnd: self.hnd.clone(), + _t: PhantomData, + }) } } -impl RouteHandler for WrapHandler +#[doc(hidden)] +pub struct HandleService where - H: Handler, + F: Factory, R: Responder + 'static, - S: 'static, { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - match self.h.handle(req).respond_to(req) { - Ok(reply) => reply.into(), - Err(err) => AsyncResult::err(err.into()), + hnd: F, + _t: PhantomData<(T, R)>, +} + +impl Service for HandleService +where + F: Factory, + R: Responder + 'static, +{ + type Request = (T, HttpRequest); + type Response = ServiceResponse; + type Error = Never; + type Future = HandleServiceResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { + let fut = self.hnd.call(param).respond_to(&req); + HandleServiceResponse { + fut, + req: Some(req), } } } -/// Async route handler -pub(crate) struct AsyncHandler -where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, -{ - h: Box, - s: PhantomData, +pub struct HandleServiceResponse { + fut: T, + req: Option, } -impl AsyncHandler +impl Future for HandleServiceResponse where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - S: 'static, + T: Future, + T::Error: Into, { - pub fn new(h: H) -> Self { - AsyncHandler { - h: Box::new(h), - s: PhantomData, - } - } -} + type Item = ServiceResponse; + type Error = Never; -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) -> AsyncResult { - let req = req.clone(); - let fut = (self.h)(&req).map_err(|e| e.into()).then(move |r| { - match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => Either::A(ok(resp)), - AsyncResultItem::Err(e) => Either::A(err(e)), - AsyncResultItem::Future(fut) => Either::B(fut), - }, - Err(e) => Either::A(err(e)), + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( + self.req.take().unwrap(), + res, + ))), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => { + let res: Response = e.into().into(); + Ok(Async::Ready(ServiceResponse::new( + self.req.take().unwrap(), + res, + ))) } - }); - AsyncResult::future(Box::new(fut)) + } } } -/// Access an application state -/// -/// `S` - application state type -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Path, State}; -/// -/// /// Application state -/// struct MyApp { -/// msg: &'static str, -/// } -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract path info using serde -/// fn index(state: State, path: Path) -> String { -/// format!("{} {}!", state.msg, path.username) -/// } -/// -/// fn main() { -/// let app = App::with_state(MyApp { msg: "Welcome" }).resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor -/// } -/// ``` -pub struct State(HttpRequest); +/// Async handler converter factory +pub trait AsyncFactory: Clone + 'static +where + R: IntoFuture, + R::Item: Into, + R::Error: Into, +{ + fn call(&self, param: T) -> R; +} -impl Deref for State { - type Target = S; - - fn deref(&self) -> &S { - self.0.state() +impl AsyncFactory<(), R> for F +where + F: Fn() -> R + Clone + 'static, + R: IntoFuture, + R::Item: Into, + R::Error: Into, +{ + fn call(&self, _: ()) -> R { + (self)() } } -impl FromRequest for State { - type Config = (); - type Result = State; +#[doc(hidden)] +pub struct AsyncHandle +where + F: AsyncFactory, + R: IntoFuture, + R::Item: Into, + R::Error: Into, +{ + hnd: F, + _t: PhantomData<(T, R)>, +} - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - State(req.clone()) +impl AsyncHandle +where + F: AsyncFactory, + R: IntoFuture, + R::Item: Into, + R::Error: Into, +{ + pub fn new(hnd: F) -> Self { + AsyncHandle { + hnd, + _t: PhantomData, + } } } +impl NewService for AsyncHandle +where + F: AsyncFactory, + R: IntoFuture, + R::Item: Into, + R::Error: Into, +{ + type Request = (T, HttpRequest); + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = AsyncHandleService; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(AsyncHandleService { + hnd: self.hnd.clone(), + _t: PhantomData, + }) + } +} + +#[doc(hidden)] +pub struct AsyncHandleService +where + F: AsyncFactory, + R: IntoFuture, + R::Item: Into, + R::Error: Into, +{ + hnd: F, + _t: PhantomData<(T, R)>, +} + +impl Service for AsyncHandleService +where + F: AsyncFactory, + R: IntoFuture, + R::Item: Into, + R::Error: Into, +{ + type Request = (T, HttpRequest); + type Response = ServiceResponse; + type Error = (); + type Future = AsyncHandleServiceResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { + AsyncHandleServiceResponse { + fut: self.hnd.call(param).into_future(), + req: Some(req), + } + } +} + +#[doc(hidden)] +pub struct AsyncHandleServiceResponse { + fut: T, + req: Option, +} + +impl Future for AsyncHandleServiceResponse +where + T: Future, + T::Item: Into, + T::Error: Into, +{ + type Item = ServiceResponse; + type Error = (); + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( + self.req.take().unwrap(), + res.into(), + ))), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => { + let res: Response = e.into().into(); + Ok(Async::Ready(ServiceResponse::new( + self.req.take().unwrap(), + res, + ))) + } + } + } +} + +/// Extract arguments from request +pub struct Extract> { + _t: PhantomData<(P, T)>, +} + +impl> Extract { + pub fn new() -> Self { + Extract { _t: PhantomData } + } +} + +impl> Default for Extract { + fn default() -> Self { + Self::new() + } +} + +impl> NewService for Extract { + type Request = ServiceRequest

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

    ); + type InitError = (); + type Service = ExtractService; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(ExtractService { _t: PhantomData }) + } +} + +pub struct ExtractService> { + _t: PhantomData<(P, T)>, +} + +impl> Service for ExtractService { + type Request = ServiceRequest

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

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

    ) -> Self::Future { + ExtractResponse { + fut: T::from_request(&mut req), + req: Some(req), + } + } +} + +pub struct ExtractResponse> { + req: Option>, + fut: T::Future, +} + +impl> Future for ExtractResponse { + type Item = (T, HttpRequest); + type Error = (Error, ServiceRequest

    ); + + fn poll(&mut self) -> Poll { + let item = try_ready!(self + .fut + .poll() + .map_err(|e| (e.into(), self.req.take().unwrap()))); + + let req = self.req.take().unwrap(); + let req = req.into_request(); + + Ok(Async::Ready((item, req))) + } +} + +/// FromRequest trait impl for tuples +macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { + impl Factory<($($T,)+), Res> for Func + where Func: Fn($($T,)+) -> Res + Clone + 'static, + //$($T,)+ + Res: Responder + 'static, + { + fn call(&self, param: ($($T,)+)) -> Res { + (self)($(param.$n,)+) + } + } + + impl AsyncFactory<($($T,)+), Res> for Func + where Func: Fn($($T,)+) -> Res + Clone + 'static, + Res: IntoFuture + 'static, + Res::Item: Into, + Res::Error: Into, + { + fn call(&self, param: ($($T,)+)) -> Res { + (self)($(param.$n,)+) + } + } +}); + +#[rustfmt::skip] +mod m { + use super::*; + +factory_tuple!((0, A)); +factory_tuple!((0, A), (1, B)); +factory_tuple!((0, A), (1, B), (2, C)); +factory_tuple!((0, A), (1, B), (2, C), (3, D)); +factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); +factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +} diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs deleted file mode 100644 index d736e53a..00000000 --- a/src/header/common/accept.rs +++ /dev/null @@ -1,159 +0,0 @@ -use header::{qitem, QualityItem}; -use http::header as http; -use mime::{self, Mime}; - -header! { - /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) - /// - /// The `Accept` header field can be used by user agents to specify - /// response media types that are acceptable. Accept header fields can - /// be used to indicate that the request is specifically limited to a - /// small set of desired types, as in the case of a request for an - /// in-line image - /// - /// # ABNF - /// - /// ```text - /// Accept = #( media-range [ accept-params ] ) - /// - /// media-range = ( "*/*" - /// / ( type "/" "*" ) - /// / ( type "/" subtype ) - /// ) *( OWS ";" OWS parameter ) - /// accept-params = weight *( accept-ext ) - /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] - /// ``` - /// - /// # Example values - /// * `audio/*; q=0.2, audio/basic` - /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` - /// - /// # Examples - /// ```rust - /// # extern crate actix_web; - /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_web; - /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_web; - /// extern crate mime; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{Accept, QualityItem, q, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), - /// ]) - /// ); - /// # } - /// ``` - (Accept, http::ACCEPT) => (QualityItem)+ - - test_accept { - // Tests from the RFC - test_header!( - test1, - vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), - ]))); - test_header!( - test2, - vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], - Some(HeaderField(vec![ - QualityItem::new(TEXT_PLAIN, q(500)), - qitem(TEXT_HTML), - QualityItem::new( - "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), - ]))); - // Custom tests - test_header!( - test3, - vec![b"text/plain; charset=utf-8"], - Some(Accept(vec![ - qitem(TEXT_PLAIN_UTF_8), - ]))); - test_header!( - test4, - vec![b"text/plain; charset=utf-8; q=0.5"], - Some(Accept(vec![ - QualityItem::new(TEXT_PLAIN_UTF_8, - q(500)), - ]))); - - #[test] - fn test_fuzzing1() { - use test::TestRequest; - let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish(); - let header = Accept::parse(&req); - assert!(header.is_ok()); - } - } -} - -impl Accept { - /// A constructor to easily create `Accept: */*`. - pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) - } - - /// A constructor to easily create `Accept: application/json`. - pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) - } - - /// A constructor to easily create `Accept: text/*`. - pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) - } - - /// A constructor to easily create `Accept: image/*`. - pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) - } -} diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs deleted file mode 100644 index 674415fb..00000000 --- a/src/header/common/accept_charset.rs +++ /dev/null @@ -1,69 +0,0 @@ -use header::{Charset, QualityItem, ACCEPT_CHARSET}; - -header! { - /// `Accept-Charset` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) - /// - /// The `Accept-Charset` header field can be sent by a user agent to - /// indicate what charsets are acceptable in textual response content. - /// This field allows user agents capable of understanding more - /// comprehensive or special-purpose charsets to signal that capability - /// to an origin server that is capable of representing information in - /// those charsets. - /// - /// # ABNF - /// - /// ```text - /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) - /// ``` - /// - /// # Example values - /// * `iso-8859-5, unicode-1-1;q=0.8` - /// - /// # Examples - /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![ - /// QualityItem::new(Charset::Us_Ascii, q(900)), - /// QualityItem::new(Charset::Iso_8859_10, q(200)), - /// ]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) - /// ); - /// # } - /// ``` - (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ - - test_accept_charset { - /// Test case from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); - } -} diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs deleted file mode 100644 index c90f529b..00000000 --- a/src/header/common/accept_encoding.rs +++ /dev/null @@ -1,72 +0,0 @@ -use header::{Encoding, QualityItem}; - -header! { - /// `Accept-Encoding` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) - /// - /// The `Accept-Encoding` header field can be used by user agents to - /// indicate what response content-codings are - /// acceptable in the response. An `identity` token is used as a synonym - /// for "no encoding" in order to communicate when no encoding is - /// preferred. - /// - /// # ABNF - /// - /// ```text - /// Accept-Encoding = #( codings [ weight ] ) - /// codings = content-coding / "identity" / "*" - /// ``` - /// - /// # Example values - /// * `compress, gzip` - /// * `` - /// * `*` - /// * `compress;q=0.5, gzip;q=1` - /// * `gzip;q=1.0, identity; q=0.5, *;q=0` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), - /// ]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), - /// ]) - /// ); - /// ``` - (AcceptEncoding, "Accept-Encoding") => (QualityItem)* - - test_accept_encoding { - // From the RFC - test_header!(test1, vec![b"compress, gzip"]); - test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - test_header!(test3, vec![b"*"]); - // Note: Removed quality 1 from gzip - test_header!(test4, vec![b"compress;q=0.5, gzip"]); - // Note: Removed quality 1 from gzip - test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); - } -} diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs deleted file mode 100644 index 12593e1a..00000000 --- a/src/header/common/accept_language.rs +++ /dev/null @@ -1,75 +0,0 @@ -use header::{QualityItem, ACCEPT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Accept-Language` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) - /// - /// The `Accept-Language` header field can be used by user agents to - /// indicate the set of natural languages that are preferred in the - /// response. - /// - /// # ABNF - /// - /// ```text - /// Accept-Language = 1#( language-range [ weight ] ) - /// language-range = - /// ``` - /// - /// # Example values - /// * `da, en-gb;q=0.8, en;q=0.7` - /// * `en-us;q=1.0, en;q=0.5, fr` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate language_tags; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// let mut langtag: LanguageTag = Default::default(); - /// langtag.language = Some("en".to_owned()); - /// langtag.region = Some("US".to_owned()); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_web; - /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; - /// # - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag!(da)), - /// QualityItem::new(langtag!(en;;;GB), q(800)), - /// QualityItem::new(langtag!(en), q(700)), - /// ]) - /// ); - /// # } - /// ``` - (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ - - test_accept_language { - // From the RFC - test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); - // Own test - test_header!( - test2, vec![b"en-US, en; q=0.5, fr"], - Some(AcceptLanguage(vec![ - qitem("en-US".parse().unwrap()), - QualityItem::new("en".parse().unwrap(), q(500)), - qitem("fr".parse().unwrap()), - ]))); - } -} diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs deleted file mode 100644 index 5046290d..00000000 --- a/src/header/common/allow.rs +++ /dev/null @@ -1,85 +0,0 @@ -use http::Method; -use http::header; - -header! { - /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) - /// - /// The `Allow` header field lists the set of methods advertised as - /// supported by the target resource. The purpose of this field is - /// strictly to inform the recipient of valid request methods associated - /// with the resource. - /// - /// # ABNF - /// - /// ```text - /// Allow = #method - /// ``` - /// - /// # Example values - /// * `GET, HEAD, PUT` - /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` - /// * `` - /// - /// # Examples - /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Allow; - /// use http::Method; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// Allow(vec![Method::GET]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_web; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Allow; - /// use http::Method; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// Allow(vec![ - /// Method::GET, - /// Method::POST, - /// Method::PATCH, - /// ]) - /// ); - /// # } - /// ``` - (Allow, header::ALLOW) => (Method)* - - test_allow { - // From the RFC - test_header!( - test1, - vec![b"GET, HEAD, PUT"], - Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); - // Own tests - test_header!( - test2, - vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], - Some(HeaderField(vec![ - Method::OPTIONS, - Method::GET, - Method::PUT, - Method::POST, - Method::DELETE, - Method::HEAD, - Method::TRACE, - Method::CONNECT, - Method::PATCH]))); - test_header!( - test3, - vec![b""], - Some(HeaderField(Vec::::new()))); - } -} diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs deleted file mode 100644 index adc60e4a..00000000 --- a/src/header/common/cache_control.rs +++ /dev/null @@ -1,254 +0,0 @@ -use header::{Header, IntoHeaderValue, Writer}; -use header::{fmt_comma_delimited, from_comma_delimited}; -use http::header; -use std::fmt::{self, Write}; -use std::str::FromStr; - -/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) -/// -/// The `Cache-Control` header field is used to specify directives for -/// caches along the request/response chain. Such cache directives are -/// unidirectional in that the presence of a directive in a request does -/// not imply that the same directive is to be given in the response. -/// -/// # ABNF -/// -/// ```text -/// Cache-Control = 1#cache-directive -/// cache-directive = token [ "=" ( token / quoted-string ) ] -/// ``` -/// -/// # Example values -/// -/// * `no-cache` -/// * `private, community="UCI"` -/// * `max-age=30` -/// -/// # Examples -/// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); -/// ``` -/// -/// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(CacheControl(vec![ -/// CacheDirective::NoCache, -/// CacheDirective::Private, -/// CacheDirective::MaxAge(360u32), -/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), -/// ])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct CacheControl(pub Vec); - -__hyper__deref!(CacheControl => Vec); - -//TODO: this could just be the header! macro -impl Header for CacheControl { - fn name() -> header::HeaderName { - header::CACHE_CONTROL - } - - #[inline] - fn parse(msg: &T) -> Result - where - T: ::HttpMessage, - { - let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; - if !directives.is_empty() { - Ok(CacheControl(directives)) - } else { - Err(::error::ParseError::Header) - } - } -} - -impl fmt::Display for CacheControl { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) - } -} - -impl IntoHeaderValue for CacheControl { - type Error = header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_shared(writer.take()) - } -} - -/// `CacheControl` contains a list of these directives. -#[derive(PartialEq, Clone, Debug)] -pub enum CacheDirective { - /// "no-cache" - NoCache, - /// "no-store" - NoStore, - /// "no-transform" - NoTransform, - /// "only-if-cached" - OnlyIfCached, - - // request directives - /// "max-age=delta" - MaxAge(u32), - /// "max-stale=delta" - MaxStale(u32), - /// "min-fresh=delta" - MinFresh(u32), - - // response directives - /// "must-revalidate" - MustRevalidate, - /// "public" - Public, - /// "private" - Private, - /// "proxy-revalidate" - ProxyRevalidate, - /// "s-maxage=delta" - SMaxAge(u32), - - /// Extension directives. Optionally include an argument. - Extension(String, Option), -} - -impl fmt::Display for CacheDirective { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::CacheDirective::*; - fmt::Display::fmt( - match *self { - NoCache => "no-cache", - NoStore => "no-store", - NoTransform => "no-transform", - OnlyIfCached => "only-if-cached", - - MaxAge(secs) => return write!(f, "max-age={}", secs), - MaxStale(secs) => return write!(f, "max-stale={}", secs), - MinFresh(secs) => return write!(f, "min-fresh={}", secs), - - MustRevalidate => "must-revalidate", - Public => "public", - Private => "private", - ProxyRevalidate => "proxy-revalidate", - SMaxAge(secs) => return write!(f, "s-maxage={}", secs), - - Extension(ref name, None) => &name[..], - Extension(ref name, Some(ref arg)) => { - return write!(f, "{}={}", name, arg) - } - }, - f, - ) - } -} - -impl FromStr for CacheDirective { - type Err = Option<::Err>; - fn from_str(s: &str) -> Result::Err>> { - use self::CacheDirective::*; - match s { - "no-cache" => Ok(NoCache), - "no-store" => Ok(NoStore), - "no-transform" => Ok(NoTransform), - "only-if-cached" => Ok(OnlyIfCached), - "must-revalidate" => Ok(MustRevalidate), - "public" => Ok(Public), - "private" => Ok(Private), - "proxy-revalidate" => Ok(ProxyRevalidate), - "" => Err(None), - _ => match s.find('=') { - Some(idx) if idx + 1 < s.len() => { - match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { - ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), - ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), - ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), - ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => { - Ok(Extension(left.to_owned(), Some(right.to_owned()))) - } - } - } - Some(_) => Err(None), - None => Ok(Extension(s.to_owned(), None)), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use header::Header; - use test::TestRequest; - - #[test] - fn test_parse_multiple_headers() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::NoCache, - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_argument() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::MaxAge(100), - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_quote_form() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![CacheDirective::MaxAge(200)])) - ) - } - - #[test] - fn test_parse_extension() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::Extension("foo".to_owned(), None), - CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), - ])) - ) - } - - #[test] - fn test_parse_bad_syntax() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); - let cache: Result = Header::parse(&req); - assert_eq!(cache.ok(), None) - } -} diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs deleted file mode 100644 index 5e8cbd67..00000000 --- a/src/header/common/content_disposition.rs +++ /dev/null @@ -1,914 +0,0 @@ -// # References -// -// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt -// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt -// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ -// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml - -use header; -use header::ExtendedValue; -use header::{Header, IntoHeaderValue, Writer}; -use regex::Regex; - -use std::fmt::{self, Write}; - -/// Split at the index of the first `needle` if it exists or at the end. -fn split_once(haystack: &str, needle: char) -> (&str, &str) { - haystack.find(needle).map_or_else( - || (haystack, ""), - |sc| { - let (first, last) = haystack.split_at(sc); - (first, last.split_at(1).1) - }, - ) -} - -/// Split at the index of the first `needle` if it exists or at the end, trim the right of the -/// first part and the left of the last part. -fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { - let (first, last) = split_once(haystack, needle); - (first.trim_right(), last.trim_left()) -} - -/// The implied disposition of the content of the HTTP body. -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionType { - /// Inline implies default processing - Inline, - /// Attachment implies that the recipient should prompt the user to save the response locally, - /// rather than process it normally (as per its media type). - Attachment, - /// Used in *multipart/form-data* as defined in - /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. - FormData, - /// Extension type. Should be handled by recipients the same way as Attachment - Ext(String), -} - -impl<'a> From<&'a str> for DispositionType { - fn from(origin: &'a str) -> DispositionType { - if origin.eq_ignore_ascii_case("inline") { - DispositionType::Inline - } else if origin.eq_ignore_ascii_case("attachment") { - DispositionType::Attachment - } else if origin.eq_ignore_ascii_case("form-data") { - DispositionType::FormData - } else { - DispositionType::Ext(origin.to_owned()) - } - } -} - -/// Parameter in [`ContentDisposition`]. -/// -/// # Examples -/// ``` -/// use actix_web::http::header::DispositionParam; -/// -/// let param = DispositionParam::Filename(String::from("sample.txt")); -/// assert!(param.is_filename()); -/// assert_eq!(param.as_filename().unwrap(), "sample.txt"); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionParam { - /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from - /// the form. - Name(String), - /// A plain file name. - Filename(String), - /// An extended file name. It must not exist for `ContentType::Formdata` according to - /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). - FilenameExt(ExtendedValue), - /// An unrecognized regular parameter as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should - /// ignore unrecognizable parameters. - Unknown(String, String), - /// An unrecognized extended paramater as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single - /// trailling asterisk is not included. Recipients should ignore unrecognizable parameters. - UnknownExt(String, ExtendedValue), -} - -impl DispositionParam { - /// Returns `true` if the paramater is [`Name`](DispositionParam::Name). - #[inline] - pub fn is_name(&self) -> bool { - self.as_name().is_some() - } - - /// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename). - #[inline] - pub fn is_filename(&self) -> bool { - self.as_filename().is_some() - } - - /// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt). - #[inline] - pub fn is_filename_ext(&self) -> bool { - self.as_filename_ext().is_some() - } - - /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` - #[inline] - /// matches. - pub fn is_unknown>(&self, name: T) -> bool { - self.as_unknown(name).is_some() - } - - /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the - /// `name` matches. - #[inline] - pub fn is_unknown_ext>(&self, name: T) -> bool { - self.as_unknown_ext(name).is_some() - } - - /// Returns the name if applicable. - #[inline] - pub fn as_name(&self) -> Option<&str> { - match self { - DispositionParam::Name(ref name) => Some(name.as_str()), - _ => None, - } - } - - /// Returns the filename if applicable. - #[inline] - pub fn as_filename(&self) -> Option<&str> { - match self { - DispositionParam::Filename(ref filename) => Some(filename.as_str()), - _ => None, - } - } - - /// Returns the filename* if applicable. - #[inline] - pub fn as_filename_ext(&self) -> Option<&ExtendedValue> { - match self { - DispositionParam::FilenameExt(ref value) => Some(value), - _ => None, - } - } - - /// Returns the value of the unrecognized regular parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown>(&self, name: T) -> Option<&str> { - match self { - DispositionParam::Unknown(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value.as_str()) - } - _ => None, - } - } - - /// Returns the value of the unrecognized extended parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - match self { - DispositionParam::UnknownExt(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value) - } - _ => None, - } - } -} - -/// A *Content-Disposition* header. It is compatible to be used either as -/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) -/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as -/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) -/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). -/// -/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if -/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as -/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be -/// used to attach additional metadata, such as the filename to use when saving the response payload -/// locally. -/// -/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that -/// can be used on the subpart of a multipart body to give information about the field it applies to. -/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body -/// itself, *Content-Disposition* has no effect. -/// -/// # ABNF - -/// ```text -/// content-disposition = "Content-Disposition" ":" -/// disposition-type *( ";" disposition-parm ) -/// -/// disposition-type = "inline" | "attachment" | disp-ext-type -/// ; case-insensitive -/// -/// disp-ext-type = token -/// -/// disposition-parm = filename-parm | disp-ext-parm -/// -/// filename-parm = "filename" "=" value -/// | "filename*" "=" ext-value -/// -/// disp-ext-parm = token "=" value -/// | ext-token "=" ext-value -/// -/// ext-token = -/// ``` -/// -/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within -/// *multipart/form-data*. -/// -/// # Example -/// -/// ``` -/// use actix_web::http::header::{ -/// Charset, ContentDisposition, DispositionParam, DispositionType, -/// ExtendedValue, -/// }; -/// -/// let cd1 = ContentDisposition { -/// disposition: DispositionType::Attachment, -/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue { -/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename -/// language_tag: None, // The optional language tag (see `language-tag` crate) -/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename -/// })], -/// }; -/// assert!(cd1.is_attachment()); -/// assert!(cd1.get_filename_ext().is_some()); -/// -/// let cd2 = ContentDisposition { -/// disposition: DispositionType::FormData, -/// parameters: vec![ -/// DispositionParam::Name(String::from("file")), -/// DispositionParam::Filename(String::from("bill.odt")), -/// ], -/// }; -/// assert_eq!(cd2.get_name(), Some("file")); // field name -/// assert_eq!(cd2.get_filename(), Some("bill.odt")); -/// ``` -/// -/// # WARN -/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly -/// change to match local file system conventions if applicable, and do not use directory path -/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3) -/// . -#[derive(Clone, Debug, PartialEq)] -pub struct ContentDisposition { - /// The disposition type - pub disposition: DispositionType, - /// Disposition parameters - pub parameters: Vec, -} - -impl ContentDisposition { - /// Parse a raw Content-Disposition header value. - pub fn from_raw(hv: &header::HeaderValue) -> Result { - // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible - // ASCII characters. So `hv.as_bytes` is necessary here. - let hv = String::from_utf8(hv.as_bytes().to_vec()) - .map_err(|_| ::error::ParseError::Header)?; - let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); - if disp_type.is_empty() { - return Err(::error::ParseError::Header); - } - let mut cd = ContentDisposition { - disposition: disp_type.into(), - parameters: Vec::new(), - }; - - while !left.is_empty() { - let (param_name, new_left) = split_once_and_trim(left, '='); - if param_name.is_empty() || param_name == "*" || new_left.is_empty() { - return Err(::error::ParseError::Header); - } - left = new_left; - if param_name.ends_with('*') { - // extended parameters - let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk - let (ext_value, new_left) = split_once_and_trim(left, ';'); - left = new_left; - let ext_value = header::parse_extended_value(ext_value)?; - - let param = if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::FilenameExt(ext_value) - } else { - DispositionParam::UnknownExt(param_name.to_owned(), ext_value) - }; - cd.parameters.push(param); - } else { - // regular parameters - let value = if left.starts_with('\"') { - // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 - let mut escaping = false; - let mut quoted_string = vec![]; - let mut end = None; - // search for closing quote - for (i, &c) in left.as_bytes().iter().skip(1).enumerate() { - if escaping { - escaping = false; - quoted_string.push(c); - } else if c == 0x5c { - // backslash - escaping = true; - } else if c == 0x22 { - // double quote - end = Some(i + 1); // cuz skipped 1 for the leading quote - break; - } else { - quoted_string.push(c); - } - } - left = &left[end.ok_or(::error::ParseError::Header)? + 1..]; - left = split_once(left, ';').1.trim_left(); - // In fact, it should not be Err if the above code is correct. - String::from_utf8(quoted_string).map_err(|_| ::error::ParseError::Header)? - } else { - // token: won't contains semicolon according to RFC 2616 Section 2.2 - let (token, new_left) = split_once_and_trim(left, ';'); - left = new_left; - token.to_owned() - }; - if value.is_empty() { - return Err(::error::ParseError::Header); - } - - let param = if param_name.eq_ignore_ascii_case("name") { - DispositionParam::Name(value) - } else if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::Filename(value) - } else { - DispositionParam::Unknown(param_name.to_owned(), value) - }; - cd.parameters.push(param); - } - } - - Ok(cd) - } - - /// Returns `true` if it is [`Inline`](DispositionType::Inline). - pub fn is_inline(&self) -> bool { - match self.disposition { - DispositionType::Inline => true, - _ => false, - } - } - - /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). - pub fn is_attachment(&self) -> bool { - match self.disposition { - DispositionType::Attachment => true, - _ => false, - } - } - - /// Returns `true` if it is [`FormData`](DispositionType::FormData). - pub fn is_form_data(&self) -> bool { - match self.disposition { - DispositionType::FormData => true, - _ => false, - } - } - - /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. - pub fn is_ext>(&self, disp_type: T) -> bool { - match self.disposition { - DispositionType::Ext(ref t) - if t.eq_ignore_ascii_case(disp_type.as_ref()) => - { - true - } - _ => false, - } - } - - /// Return the value of *name* if exists. - pub fn get_name(&self) -> Option<&str> { - self.parameters.iter().filter_map(|p| p.as_name()).nth(0) - } - - /// Return the value of *filename* if exists. - pub fn get_filename(&self) -> Option<&str> { - self.parameters - .iter() - .filter_map(|p| p.as_filename()) - .nth(0) - } - - /// Return the value of *filename\** if exists. - pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { - self.parameters - .iter() - .filter_map(|p| p.as_filename_ext()) - .nth(0) - } - - /// Return the value of the parameter which the `name` matches. - pub fn get_unknown>(&self, name: T) -> Option<&str> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown(name)) - .nth(0) - } - - /// Return the value of the extended parameter which the `name` matches. - pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown_ext(name)) - .nth(0) - } -} - -impl IntoHeaderValue for ContentDisposition { - type Error = header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_shared(writer.take()) - } -} - -impl Header for ContentDisposition { - fn name() -> header::HeaderName { - header::CONTENT_DISPOSITION - } - - fn parse(msg: &T) -> Result { - if let Some(h) = msg.headers().get(Self::name()) { - Self::from_raw(&h) - } else { - Err(::error::ParseError::Header) - } - } -} - -impl fmt::Display for DispositionType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DispositionType::Inline => write!(f, "inline"), - DispositionType::Attachment => write!(f, "attachment"), - DispositionType::FormData => write!(f, "form-data"), - DispositionType::Ext(ref s) => write!(f, "{}", s), - } - } -} - -impl fmt::Display for DispositionParam { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and - // backslash should be escaped in quoted-string (i.e. "foobar"). - // Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . - lazy_static! { - static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); - } - match self { - DispositionParam::Name(ref value) => write!(f, "name={}", value), - DispositionParam::Filename(ref value) => { - write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) - } - DispositionParam::Unknown(ref name, ref value) => write!( - f, - "{}=\"{}\"", - name, - &RE.replace_all(value, "\\$0").as_ref() - ), - DispositionParam::FilenameExt(ref ext_value) => { - write!(f, "filename*={}", ext_value) - } - DispositionParam::UnknownExt(ref name, ref ext_value) => { - write!(f, "{}*={}", name, ext_value) - } - } - } -} - -impl fmt::Display for ContentDisposition { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.disposition)?; - self.parameters - .iter() - .map(|param| write!(f, "; {}", param)) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::{ContentDisposition, DispositionParam, DispositionType}; - use header::shared::Charset; - use header::{ExtendedValue, HeaderValue}; - #[test] - fn test_from_raw_basic() { - assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); - - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"sample.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("inline; filename=image.jpg"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Unknown( - String::from("creation-date"), - "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(), - )], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extended() { - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extra_whitespace() { - let a = HeaderValue::from_static( - "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_unordered() { - let a = HeaderValue::from_static( - "form-data; dummy=3; filename=\"sample.png\" ; name=upload;", - // Actually, a trailling semolocon is not compliant. But it is fine to accept. - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - DispositionParam::Name("upload".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_str( - "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"", - ).unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_only_disp() { - let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) - .unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = - ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = ContentDisposition::from_raw(&HeaderValue::from_static( - "unknown-disp-param", - )).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Ext(String::from("unknown-disp-param")), - parameters: vec![], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_mixed_case() { - let a = HeaderValue::from_str( - "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"", - ).unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_unicode() { - /* RFC7578 Section 4.2: - Some commonly deployed systems use multipart/form-data with file names directly encoded - including octets outside the US-ASCII range. The encoding used for the file names is - typically UTF-8, although HTML forms will use the charset associated with the form. - - Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. - (And now, only UTF-8 is handled by this implementation.) - */ - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") - .unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from("文件.webp")), - ], - }; - assert_eq!(a, b); - - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx\"").unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from( - "余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx", - )), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_escape() { - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename( - ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g'] - .iter() - .collect(), - ), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_semicolon() { - let a = - HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![DispositionParam::Filename(String::from( - "A semicolon here;.pdf", - ))], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_uncessary_percent_decode() { - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74.png")), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_param_value_missing() { - let a = HeaderValue::from_static("form-data; name=upload ; filename="); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; filename= "); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_from_raw_param_name_missing() { - let a = HeaderValue::from_static("inline; =\"test.txt\""); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; =diary.odt"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; ="); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_display_extended() { - let as_string = - "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a = HeaderValue::from_static("attachment; filename=colourful.csv"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"colourful.csv\"".to_owned(), - display_rendered - ); - } - - #[test] - fn test_display_quote() { - let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\""; - as_string - .find(['\\', '\"'].iter().collect::().as_str()) - .unwrap(); // ensure `\"` is there - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - } - - #[test] - fn test_display_space_tab() { - let as_string = "form-data; name=upload; filename=\"Space here.png\""; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered); - } - - #[test] - fn test_display_control_characters() { - /* let a = "attachment; filename=\"carriage\rreturn.png\""; - let a = HeaderValue::from_static(a); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"carriage\\\rreturn.png\"", - display_rendered - );*/ - // No way to create a HeaderValue containing a carriage return. - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered); - } - - #[test] - fn test_param_methods() { - let param = DispositionParam::Filename(String::from("sample.txt")); - assert!(param.is_filename()); - assert_eq!(param.as_filename().unwrap(), "sample.txt"); - - let param = DispositionParam::Unknown(String::from("foo"), String::from("bar")); - assert!(param.is_unknown("foo")); - assert_eq!(param.as_unknown("fOo"), Some("bar")); - } - - #[test] - fn test_disposition_methods() { - let cd = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(cd.get_name(), Some("upload")); - assert_eq!(cd.get_unknown("dummy"), Some("3")); - assert_eq!(cd.get_filename(), Some("sample.png")); - assert_eq!(cd.get_unknown_ext("dummy"), None); - assert_eq!(cd.get_unknown("duMMy"), Some("3")); - } -} diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs deleted file mode 100644 index e12d34d0..00000000 --- a/src/header/common/content_language.rs +++ /dev/null @@ -1,65 +0,0 @@ -use header::{QualityItem, CONTENT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Content-Language` header, defined in - /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) - /// - /// The `Content-Language` header field describes the natural language(s) - /// of the intended audience for the representation. Note that this - /// might not be equivalent to all the languages used within the - /// representation. - /// - /// # ABNF - /// - /// ```text - /// Content-Language = 1#language-tag - /// ``` - /// - /// # Example values - /// - /// * `da` - /// * `mi, en` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_web; - /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// # use actix_web::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(en)), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_web; - /// # #[macro_use] extern crate language_tags; - /// use actix_web::HttpResponse; - /// # use actix_web::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(da)), - /// qitem(langtag!(en;;;GB)), - /// ]) - /// ); - /// # } - /// ``` - (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ - - test_content_language { - test_header!(test1, vec![b"da"]); - test_header!(test2, vec![b"mi, en"]); - } -} diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs deleted file mode 100644 index 999307e2..00000000 --- a/src/header/common/content_range.rs +++ /dev/null @@ -1,210 +0,0 @@ -use error::ParseError; -use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, - CONTENT_RANGE}; -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -header! { - /// `Content-Range` header, defined in - /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) - (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] - - test_content_range { - test_header!(test_bytes, - vec![b"bytes 0-499/500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: Some(500) - }))); - - test_header!(test_bytes_unknown_len, - vec![b"bytes 0-499/*"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: None - }))); - - test_header!(test_bytes_unknown_range, - vec![b"bytes */500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: None, - instance_length: Some(500) - }))); - - test_header!(test_unregistered, - vec![b"seconds 1-2"], - Some(ContentRange(ContentRangeSpec::Unregistered { - unit: "seconds".to_owned(), - resp: "1-2".to_owned() - }))); - - test_header!(test_no_len, - vec![b"bytes 0-499"], - None::); - - test_header!(test_only_unit, - vec![b"bytes"], - None::); - - test_header!(test_end_less_than_start, - vec![b"bytes 499-0/500"], - None::); - - test_header!(test_blank, - vec![b""], - None::); - - test_header!(test_bytes_many_spaces, - vec![b"bytes 1-2/500 3"], - None::); - - test_header!(test_bytes_many_slashes, - vec![b"bytes 1-2/500/600"], - None::); - - test_header!(test_bytes_many_dashes, - vec![b"bytes 1-2-3/500"], - None::); - - } -} - -/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) -/// -/// # ABNF -/// -/// ```text -/// Content-Range = byte-content-range -/// / other-content-range -/// -/// byte-content-range = bytes-unit SP -/// ( byte-range-resp / unsatisfied-range ) -/// -/// byte-range-resp = byte-range "/" ( complete-length / "*" ) -/// byte-range = first-byte-pos "-" last-byte-pos -/// unsatisfied-range = "*/" complete-length -/// -/// complete-length = 1*DIGIT -/// -/// other-content-range = other-range-unit SP other-range-resp -/// other-range-resp = *CHAR -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum ContentRangeSpec { - /// Byte range - Bytes { - /// First and last bytes of the range, omitted if request could not be - /// satisfied - range: Option<(u64, u64)>, - - /// Total length of the instance, can be omitted if unknown - instance_length: Option, - }, - - /// Custom range, with unit not registered at IANA - Unregistered { - /// other-range-unit - unit: String, - - /// other-range-resp - resp: String, - }, -} - -fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { - let mut iter = s.splitn(2, separator); - match (iter.next(), iter.next()) { - (Some(a), Some(b)) => Some((a, b)), - _ => None, - } -} - -impl FromStr for ContentRangeSpec { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - let res = match split_in_two(s, ' ') { - Some(("bytes", resp)) => { - let (range, instance_length) = - split_in_two(resp, '/').ok_or(ParseError::Header)?; - - let instance_length = if instance_length == "*" { - None - } else { - Some(instance_length - .parse() - .map_err(|_| ParseError::Header)?) - }; - - let range = if range == "*" { - None - } else { - let (first_byte, last_byte) = - split_in_two(range, '-').ok_or(ParseError::Header)?; - let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?; - let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; - if last_byte < first_byte { - return Err(ParseError::Header); - } - Some((first_byte, last_byte)) - }; - - ContentRangeSpec::Bytes { - range, - instance_length, - } - } - Some((unit, resp)) => ContentRangeSpec::Unregistered { - unit: unit.to_owned(), - resp: resp.to_owned(), - }, - _ => return Err(ParseError::Header), - }; - Ok(res) - } -} - -impl Display for ContentRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ContentRangeSpec::Bytes { - range, - instance_length, - } => { - f.write_str("bytes ")?; - match range { - Some((first_byte, last_byte)) => { - write!(f, "{}-{}", first_byte, last_byte)?; - } - None => { - f.write_str("*")?; - } - }; - f.write_str("/")?; - if let Some(v) = instance_length { - write!(f, "{}", v) - } else { - f.write_str("*") - } - } - ContentRangeSpec::Unregistered { - ref unit, - ref resp, - } => { - f.write_str(unit)?; - f.write_str(" ")?; - f.write_str(resp) - } - } - } -} - -impl IntoHeaderValue for ContentRangeSpec { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_shared(writer.take()) - } -} diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs deleted file mode 100644 index 08900e1c..00000000 --- a/src/header/common/content_type.rs +++ /dev/null @@ -1,122 +0,0 @@ -use header::CONTENT_TYPE; -use mime::{self, Mime}; - -header! { - /// `Content-Type` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) - /// - /// The `Content-Type` header field indicates the media type of the - /// associated representation: either the representation enclosed in the - /// message payload or the selected representation, as determined by the - /// message semantics. The indicated media type defines both the data - /// format and how that data is intended to be processed by a recipient, - /// within the scope of the received message semantics, after any content - /// codings indicated by Content-Encoding are decoded. - /// - /// Although the `mime` crate allows the mime options to be any slice, this crate - /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If - /// this is an issue, it's possible to implement `Header` on a custom struct. - /// - /// # ABNF - /// - /// ```text - /// Content-Type = media-type - /// ``` - /// - /// # Example values - /// - /// * `text/html; charset=utf-8` - /// * `application/json` - /// - /// # Examples - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentType::json() - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_web; - /// use mime::TEXT_HTML; - /// use actix_web::HttpResponse; - /// use actix_web::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentType(TEXT_HTML) - /// ); - /// # } - /// ``` - (ContentType, CONTENT_TYPE) => [Mime] - - test_content_type { - test_header!( - test1, - vec![b"text/html"], - Some(HeaderField(TEXT_HTML))); - } -} - -impl ContentType { - /// A constructor to easily create a `Content-Type: application/json` - /// header. - #[inline] - pub fn json() -> ContentType { - ContentType(mime::APPLICATION_JSON) - } - - /// A constructor to easily create a `Content-Type: text/plain; - /// charset=utf-8` header. - #[inline] - pub fn plaintext() -> ContentType { - ContentType(mime::TEXT_PLAIN_UTF_8) - } - - /// A constructor to easily create a `Content-Type: text/html` header. - #[inline] - pub fn html() -> ContentType { - ContentType(mime::TEXT_HTML) - } - - /// A constructor to easily create a `Content-Type: text/xml` header. - #[inline] - pub fn xml() -> ContentType { - ContentType(mime::TEXT_XML) - } - - /// A constructor to easily create a `Content-Type: - /// application/www-form-url-encoded` header. - #[inline] - pub fn form_url_encoded() -> ContentType { - ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) - } - /// A constructor to easily create a `Content-Type: image/jpeg` header. - #[inline] - pub fn jpeg() -> ContentType { - ContentType(mime::IMAGE_JPEG) - } - - /// A constructor to easily create a `Content-Type: image/png` header. - #[inline] - pub fn png() -> ContentType { - ContentType(mime::IMAGE_PNG) - } - - /// A constructor to easily create a `Content-Type: - /// application/octet-stream` header. - #[inline] - pub fn octet_stream() -> ContentType { - ContentType(mime::APPLICATION_OCTET_STREAM) - } -} - -impl Eq for ContentType {} diff --git a/src/header/common/date.rs b/src/header/common/date.rs deleted file mode 100644 index 88a47bc3..00000000 --- a/src/header/common/date.rs +++ /dev/null @@ -1,42 +0,0 @@ -use header::{HttpDate, DATE}; -use std::time::SystemTime; - -header! { - /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) - /// - /// The `Date` header field represents the date and time at which the - /// message was originated. - /// - /// # ABNF - /// - /// ```text - /// Date = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Tue, 15 Nov 1994 08:12:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Date; - /// use std::time::SystemTime; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(Date(SystemTime::now().into())); - /// ``` - (Date, DATE) => [HttpDate] - - test_date { - test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); - } -} - -impl Date { - /// Create a date instance set to the current system time - pub fn now() -> Date { - Date(SystemTime::now().into()) - } -} diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs deleted file mode 100644 index 39dd908c..00000000 --- a/src/header/common/etag.rs +++ /dev/null @@ -1,96 +0,0 @@ -use header::{EntityTag, ETAG}; - -header! { - /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) - /// - /// The `ETag` header field in a response provides the current entity-tag - /// for the selected representation, as determined at the conclusion of - /// handling the request. An entity-tag is an opaque validator for - /// differentiating between multiple representations of the same - /// resource, regardless of whether those multiple representations are - /// due to resource state changes over time, content negotiation - /// resulting in multiple representations being valid at the same time, - /// or both. An entity-tag consists of an opaque quoted string, possibly - /// prefixed by a weakness indicator. - /// - /// # ABNF - /// - /// ```text - /// ETag = entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `""` - /// - /// # Examples - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ETag, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); - /// ``` - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{ETag, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); - /// ``` - (ETag, ETAG) => [EntityTag] - - test_etag { - // From the RFC - test_header!(test1, - vec![b"\"xyzzy\""], - Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - test_header!(test2, - vec![b"W/\"xyzzy\""], - Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - test_header!(test3, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - // Own tests - test_header!(test4, - vec![b"\"foobar\""], - Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - test_header!(test5, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - test_header!(test6, - vec![b"W/\"weak-etag\""], - Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - test_header!(test7, - vec![b"W/\"\x65\x62\""], - Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - test_header!(test8, - vec![b"W/\"\""], - Some(ETag(EntityTag::new(true, "".to_owned())))); - test_header!(test9, - vec![b"no-dquotes"], - None::); - test_header!(test10, - vec![b"w/\"the-first-w-is-case-sensitive\""], - None::); - test_header!(test11, - vec![b""], - None::); - test_header!(test12, - vec![b"\"unmatched-dquotes1"], - None::); - test_header!(test13, - vec![b"unmatched-dquotes2\""], - None::); - test_header!(test14, - vec![b"matched-\"dquotes\""], - None::); - test_header!(test15, - vec![b"\""], - None::); - } -} diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs deleted file mode 100644 index 4ec66b88..00000000 --- a/src/header/common/expires.rs +++ /dev/null @@ -1,39 +0,0 @@ -use header::{HttpDate, EXPIRES}; - -header! { - /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) - /// - /// The `Expires` header field gives the date/time after which the - /// response is considered stale. - /// - /// The presence of an Expires field does not imply that the original - /// resource will change or cease to exist at, before, or after that - /// time. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// * `Thu, 01 Dec 1994 16:00:00 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::Expires; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - /// builder.set(Expires(expiration.into())); - /// ``` - (Expires, EXPIRES) => [HttpDate] - - test_expires { - // Test case from RFC - test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); - } -} diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs deleted file mode 100644 index 20a2b1e6..00000000 --- a/src/header/common/if_match.rs +++ /dev/null @@ -1,70 +0,0 @@ -use header::{EntityTag, IF_MATCH}; - -header! { - /// `If-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) - /// - /// The `If-Match` header field makes the request method conditional on - /// the recipient origin server either having at least one current - /// representation of the target resource, when the field-value is "*", - /// or having a current representation of the target resource that has an - /// entity-tag matching a member of the list of entity-tags provided in - /// the field-value. - /// - /// An origin server MUST use the strong comparison function when - /// comparing entity-tags for `If-Match`, since the client - /// intends this precondition to prevent the method from being applied if - /// there have been any changes to the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * "xyzzy", "r2d2xxxx", "c3piozzzz" - /// - /// # Examples - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfMatch; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(IfMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{IfMatch, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// IfMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfMatch, IF_MATCH) => {Any / (EntityTag)+} - - test_if_match { - test_header!( - test1, - vec![b"\"xyzzy\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned())]))); - test_header!( - test2, - vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned()), - EntityTag::new(false, "r2d2xxxx".to_owned()), - EntityTag::new(false, "c3piozzzz".to_owned())]))); - test_header!(test3, vec![b"*"], Some(IfMatch::Any)); - } -} diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs deleted file mode 100644 index 1914d34d..00000000 --- a/src/header/common/if_modified_since.rs +++ /dev/null @@ -1,39 +0,0 @@ -use header::{HttpDate, IF_MODIFIED_SINCE}; - -header! { - /// `If-Modified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) - /// - /// The `If-Modified-Since` header field makes a GET or HEAD request - /// method conditional on the selected representation's modification date - /// being more recent than the date provided in the field-value. - /// Transfer of the selected representation's data is avoided if that - /// data has not changed. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfModifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfModifiedSince(modified.into())); - /// ``` - (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] - - test_if_modified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs deleted file mode 100644 index 124f4b8e..00000000 --- a/src/header/common/if_none_match.rs +++ /dev/null @@ -1,92 +0,0 @@ -use header::{EntityTag, IF_NONE_MATCH}; - -header! { - /// `If-None-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) - /// - /// The `If-None-Match` header field makes the request method conditional - /// on a recipient cache or origin server either not having any current - /// representation of the target resource, when the field-value is "*", - /// or having a selected representation with an entity-tag that does not - /// match any of those listed in the field-value. - /// - /// A recipient MUST use the weak comparison function when comparing - /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags - /// can be used for cache validation even if there have been changes to - /// the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-None-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` - /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` - /// * `*` - /// - /// # Examples - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfNoneMatch; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(IfNoneMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::{IfNoneMatch, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// IfNoneMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} - - test_if_none_match { - test_header!(test1, vec![b"\"xyzzy\""]); - test_header!(test2, vec![b"W/\"xyzzy\""]); - test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - test_header!(test5, vec![b"*"]); - } -} - -#[cfg(test)] -mod tests { - use super::IfNoneMatch; - use header::{EntityTag, Header, IF_NONE_MATCH}; - use test::TestRequest; - - #[test] - fn test_if_none_match() { - let mut if_none_match: Result; - - let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); - if_none_match = Header::parse(&req); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - - let req = - TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) - .finish(); - - if_none_match = Header::parse(&req); - let mut entities: Vec = Vec::new(); - let foobar_etag = EntityTag::new(false, "foobar".to_owned()); - let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); - entities.push(foobar_etag); - entities.push(weak_etag); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); - } -} diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs deleted file mode 100644 index dd95b7ba..00000000 --- a/src/header/common/if_range.rs +++ /dev/null @@ -1,115 +0,0 @@ -use error::ParseError; -use header::from_one_raw_str; -use header::{EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, - InvalidHeaderValueBytes, Writer}; -use http::header; -use httpmessage::HttpMessage; -use std::fmt::{self, Display, Write}; - -/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) -/// -/// If a client has a partial copy of a representation and wishes to have -/// an up-to-date copy of the entire representation, it could use the -/// Range header field with a conditional GET (using either or both of -/// If-Unmodified-Since and If-Match.) However, if the precondition -/// fails because the representation has been modified, the client would -/// then have to make a second request to obtain the entire current -/// representation. -/// -/// The `If-Range` header field allows a client to \"short-circuit\" the -/// second request. Informally, its meaning is as follows: if the -/// representation is unchanged, send me the part(s) that I am requesting -/// in Range; otherwise, send me the entire representation. -/// -/// # ABNF -/// -/// ```text -/// If-Range = entity-tag / HTTP-date -/// ``` -/// -/// # Example values -/// -/// * `Sat, 29 Oct 1994 19:43:31 GMT` -/// * `\"xyzzy\"` -/// -/// # Examples -/// -/// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::{EntityTag, IfRange}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(IfRange::EntityTag(EntityTag::new( -/// false, -/// "xyzzy".to_owned(), -/// ))); -/// ``` -/// -/// ```rust -/// use actix_web::HttpResponse; -/// use actix_web::http::header::IfRange; -/// use std::time::{Duration, SystemTime}; -/// -/// let mut builder = HttpResponse::Ok(); -/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); -/// builder.set(IfRange::Date(fetched.into())); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum IfRange { - /// The entity-tag the client has of the resource - EntityTag(EntityTag), - /// The date when the client retrieved the resource - Date(HttpDate), -} - -impl Header for IfRange { - fn name() -> HeaderName { - header::IF_RANGE - } - #[inline] - fn parse(msg: &T) -> Result - where - T: HttpMessage, - { - let etag: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); - if let Ok(etag) = etag { - return Ok(IfRange::EntityTag(etag)); - } - let date: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); - if let Ok(date) = date { - return Ok(IfRange::Date(date)); - } - Err(ParseError::Header) - } -} - -impl Display for IfRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - IfRange::EntityTag(ref x) => Display::fmt(x, f), - IfRange::Date(ref x) => Display::fmt(x, f), - } - } -} - -impl IntoHeaderValue for IfRange { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_shared(writer.take()) - } -} - -#[cfg(test)] -mod test_if_range { - use super::IfRange as HeaderField; - use header::*; - use std::str; - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"xyzzy\""]); - test_header!(test3, vec![b"this-is-invalid"], None::); -} diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs deleted file mode 100644 index f87e760c..00000000 --- a/src/header/common/if_unmodified_since.rs +++ /dev/null @@ -1,40 +0,0 @@ -use header::{HttpDate, IF_UNMODIFIED_SINCE}; - -header! { - /// `If-Unmodified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) - /// - /// The `If-Unmodified-Since` header field makes the request method - /// conditional on the selected representation's last modification date - /// being earlier than or equal to the date provided in the field-value. - /// This field accomplishes the same purpose as If-Match for cases where - /// the user agent does not have an entity-tag for the representation. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::IfUnmodifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfUnmodifiedSince(modified.into())); - /// ``` - (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] - - test_if_unmodified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs deleted file mode 100644 index aba82888..00000000 --- a/src/header/common/last_modified.rs +++ /dev/null @@ -1,38 +0,0 @@ -use header::{HttpDate, LAST_MODIFIED}; - -header! { - /// `Last-Modified` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) - /// - /// The `Last-Modified` header field in a response provides a timestamp - /// indicating the date and time at which the origin server believes the - /// selected representation was last modified, as determined at the - /// conclusion of handling the request. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_web::HttpResponse; - /// use actix_web::http::header::LastModified; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(LastModified(modified.into())); - /// ``` - (LastModified, LAST_MODIFIED) => [HttpDate] - - test_last_modified { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} -} diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs deleted file mode 100644 index e6185b5a..00000000 --- a/src/header/common/mod.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! A Collection of Header implementations for common HTTP Headers. -//! -//! ## Mime -//! -//! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime](https://docs.rs/mime) crate -//! is used, such as `ContentType(pub Mime)`. -#![cfg_attr(rustfmt, rustfmt_skip)] - -pub use self::accept_charset::AcceptCharset; -//pub use self::accept_encoding::AcceptEncoding; -pub use self::accept_language::AcceptLanguage; -pub use self::accept::Accept; -pub use self::allow::Allow; -pub use self::cache_control::{CacheControl, CacheDirective}; -pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; -pub use self::content_language::ContentLanguage; -pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_type::ContentType; -pub use self::date::Date; -pub use self::etag::ETag; -pub use self::expires::Expires; -pub use self::if_match::IfMatch; -pub use self::if_modified_since::IfModifiedSince; -pub use self::if_none_match::IfNoneMatch; -pub use self::if_range::IfRange; -pub use self::if_unmodified_since::IfUnmodifiedSince; -pub use self::last_modified::LastModified; -//pub use self::range::{Range, ByteRangeSpec}; - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__deref { - ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &$to { - &self.0 - } - } - - impl ::std::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut $to { - &mut self.0 - } - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__tm { - ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] - #[cfg(test)] - mod $tm{ - use std::str; - use http::Method; - use $crate::header::*; - use $crate::mime::*; - use super::$id as HeaderField; - $($tf)* - } - - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! test_header { - ($id:ident, $raw:expr) => { - #[test] - fn $id() { - use test; - let raw = $raw; - let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item); - } - let req = req.finish(); - let value = HeaderField::parse(&req); - let result = format!("{}", value.unwrap()); - let expected = String::from_utf8(raw[0].to_vec()).unwrap(); - let result_cmp: Vec = result - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - let expected_cmp: Vec = expected - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - assert_eq!(result_cmp.concat(), expected_cmp.concat()); - } - }; - ($id:ident, $raw:expr, $typed:expr) => { - #[test] - fn $id() { - use $crate::test; - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item); - } - let req = req.finish(); - let val = HeaderField::parse(&req); - let typed: Option = $typed; - // Test parsing - assert_eq!(val.ok(), typed); - // Test formatting - if typed.is_some() { - let raw = &($raw)[..]; - let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap()); - let mut joined = String::new(); - joined.push_str(iter.next().unwrap()); - for s in iter { - joined.push_str(", "); - joined.push_str(s); - } - assert_eq!(format!("{}", typed.unwrap()), joined); - } - } - } -} - -#[macro_export] -macro_rules! header { - // $a:meta: Attributes associated with the header item (usually docs) - // $id:ident: Identifier of the header - // $n:expr: Lowercase name of the header - // $nn:expr: Nice name of the header - - // List header, zero or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - // List header, one or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - // Single value header - ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub $value); - __hyper__deref!($id => $value); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_one_raw_str( - msg.headers().get(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::std::fmt::Display::fmt(&self.0, f) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - self.0.try_into() - } - } - }; - // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub enum $id { - /// Any value is a match - Any, - /// Only the listed items are a match - Items(Vec<$item>), - } - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - let any = msg.headers().get(Self::name()).and_then(|hdr| { - hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); - - if let Some(true) = any { - Ok($id::Any) - } else { - Ok($id::Items( - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name()))?)) - } - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match *self { - $id::Any => f.write_str("*"), - $id::Items(ref fields) => $crate::header::fmt_comma_delimited( - f, &fields[..]) - } - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - - // optional test module - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => ($item)* - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)+ - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* ($id, $name) => [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => {Any / ($item)+} - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; -} - - -mod accept_charset; -//mod accept_encoding; -mod accept_language; -mod accept; -mod allow; -mod cache_control; -mod content_disposition; -mod content_language; -mod content_range; -mod content_type; -mod date; -mod etag; -mod expires; -mod if_match; -mod if_modified_since; -mod if_none_match; -mod if_range; -mod if_unmodified_since; -mod last_modified; -//mod range; diff --git a/src/header/common/range.rs b/src/header/common/range.rs deleted file mode 100644 index 71718fc7..00000000 --- a/src/header/common/range.rs +++ /dev/null @@ -1,434 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use header::parsing::from_one_raw_str; -use header::{Header, Raw}; - -/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) -/// -/// The "Range" header field on a GET request modifies the method -/// semantics to request transfer of only one or more subranges of the -/// selected representation data, rather than the entire selected -/// representation data. -/// -/// # ABNF -/// -/// ```text -/// Range = byte-ranges-specifier / other-ranges-specifier -/// other-ranges-specifier = other-range-unit "=" other-range-set -/// other-range-set = 1*VCHAR -/// -/// bytes-unit = "bytes" -/// -/// byte-ranges-specifier = bytes-unit "=" byte-range-set -/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) -/// byte-range-spec = first-byte-pos "-" [last-byte-pos] -/// first-byte-pos = 1*DIGIT -/// last-byte-pos = 1*DIGIT -/// ``` -/// -/// # Example values -/// -/// * `bytes=1000-` -/// * `bytes=-2000` -/// * `bytes=0-1,30-40` -/// * `bytes=0-10,20-90,-100` -/// * `custom_unit=0-123` -/// * `custom_unit=xxx-yyy` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] -/// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum Range { - /// Byte range - Bytes(Vec), - /// Custom range, with unit not registered at IANA - /// (`other-range-unit`: String , `other-range-set`: String) - Unregistered(String, String), -} - -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] -pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") - FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), - /// Get last x bytes ("-x") - Last(u64), -} - -impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. - /// - /// The resulting range is guaranteed to be a satisfiable range within the - /// bounds of `0 <= from <= to < full_length`. - /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 - /// OK` status code. - /// - /// This function closely follows [RFC 7233][1] section 2.1. - /// As such, it considers ranges to be satisfiable if they meet the - /// following conditions: - /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. - /// - /// The function also computes remainder ranges based on the RFC: - /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). - /// - /// [1]: https://tools.ietf.org/html/rfc7233 - pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { - // If the full length is zero, there is no satisfiable end-inclusive range. - if full_length == 0 { - return None; - } - match self { - &ByteRangeSpec::FromTo(from, to) => { - if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) - } else { - None - } - } - &ByteRangeSpec::AllFrom(from) => { - if from < full_length { - Some((from, full_length - 1)) - } else { - None - } - } - &ByteRangeSpec::Last(last) => { - if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. - if last > full_length { - Some((0, full_length - 1)) - } else { - Some((full_length - last, full_length - 1)) - } - } else { - None - } - } - } - } -} - -impl Range { - /// Get the most common byte range header ("bytes=from-to") - pub fn bytes(from: u64, to: u64) -> Range { - Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) - } - - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") - pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { - Range::Bytes( - ranges - .iter() - .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) - .collect(), - ) - } -} - -impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), - ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), - } - } -} - -impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { - try!(write!(f, "bytes=")); - - for (i, range) in ranges.iter().enumerate() { - if i != 0 { - try!(f.write_str(",")); - } - try!(Display::fmt(range, f)); - } - Ok(()) - } - Range::Unregistered(ref unit, ref range_str) => { - write!(f, "{}={}", unit, range_str) - } - } - } -} - -impl FromStr for Range { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); - - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { - let ranges = from_comma_delimited(ranges); - if ranges.is_empty() { - return Err(::Error::Header); - } - Ok(Range::Bytes(ranges)) - } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok( - Range::Unregistered(unit.to_owned(), range_str.to_owned()), - ), - _ => Err(::Error::Header), - } - } -} - -impl FromStr for ByteRangeSpec { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); - - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => end.parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::Last), - (Some(start), Some("")) => start - .parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::AllFrom), - (Some(start), Some(end)) => match (start.parse(), end.parse()) { - (Ok(start), Ok(end)) if start <= end => { - Ok(ByteRangeSpec::FromTo(start, end)) - } - _ => Err(::Error::Header), - }, - _ => Err(::Error::Header), - } - } -} - -fn from_comma_delimited(s: &str) -> Vec { - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.parse().ok()) - .collect() -} - -impl Header for Range { - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -#[test] -fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); - let r3 = Range::bytes(1, 100); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = - Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::AllFrom(200), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::Last(100), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"abc".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"custom=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"=1-100".into()); - assert_eq!(r.ok(), None); -} - -#[test] -fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set(Range::Bytes(vec![ - ByteRangeSpec::FromTo(0, 1000), - ByteRangeSpec::AllFrom(2000), - ])); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); - - headers.clear(); - headers.set(Range::Bytes(vec![])); - - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); - - headers.clear(); - headers.set(Range::Unregistered( - "custom".to_owned(), - "1-xxx".to_owned(), - )); - - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); -} - -#[test] -fn test_byte_range_spec_to_satisfiable_range() { - assert_eq!( - Some((0, 0)), - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((0, 2)), - ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((1, 2)), - ByteRangeSpec::Last(2).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::Last(1).to_satisfiable_range(3) - ); - assert_eq!( - Some((0, 2)), - ByteRangeSpec::Last(5).to_satisfiable_range(3) - ); - assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); -} diff --git a/src/header/mod.rs b/src/header/mod.rs deleted file mode 100644 index 74e4b03e..00000000 --- a/src/header/mod.rs +++ /dev/null @@ -1,471 +0,0 @@ -//! Various http headers -// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) - -use std::fmt; -use std::str::FromStr; - -use bytes::{Bytes, BytesMut}; -use mime::Mime; -use modhttp::header::GetAll; -use modhttp::Error as HttpError; -use percent_encoding; - -pub use modhttp::header::*; - -use error::ParseError; -use httpmessage::HttpMessage; - -mod common; -mod shared; -#[doc(hidden)] -pub use self::common::*; -#[doc(hidden)] -pub use self::shared::*; - -#[doc(hidden)] -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ - /// Returns the name of the header field - fn name() -> HeaderName; - - /// Parse a header - fn parse(msg: &T) -> Result; -} - -#[doc(hidden)] -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(format!("{}", self))) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - #[cfg(feature = "brotli")] - Br, - /// A format using the zlib structure with deflate algorithm - #[cfg(feature = "flate2")] - Deflate, - /// Gzip algorithm - #[cfg(feature = "flate2")] - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - match self { - ContentEncoding::Identity | ContentEncoding::Auto => false, - _ => true, - } - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - #[cfg(feature = "brotli")] - ContentEncoding::Br => "br", - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => "gzip", - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - #[cfg(feature = "brotli")] - ContentEncoding::Br => 1.1, - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => 1.0, - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -// TODO: remove memory allocation -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - match AsRef::::as_ref(&s.trim().to_lowercase()) { - #[cfg(feature = "brotli")] - "br" => ContentEncoding::Br, - #[cfg(feature = "flate2")] - "gzip" => ContentEncoding::Gzip, - #[cfg(feature = "flate2")] - "deflate" => ContentEncoding::Deflate, - _ => ContentEncoding::Identity, - } - } -} - -#[doc(hidden)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { - fmt::write(self, args) - } -} - -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited( - all: GetAll, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }).filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -// From hyper v0.11.27 src/header/parsing.rs - -/// The value part of an extended parameter consisting of three parts: -/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), -/// and a character sequence representing the actual value (`value`), separated by single quote -/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] -pub struct ExtendedValue { - /// The character set that is used to encode the `value` to a string. - pub charset: Charset, - /// The human language details of the `value`, if available. - pub language_tag: Option, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -/// -/// Extended values are denoted by parameter names that end with `*`. -/// -/// ## ABNF -/// -/// ```text -/// ext-value = charset "'" [ language ] "'" value-chars -/// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// value-chars = *( pct-encoded / attr-char ) -/// -/// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 -/// -/// attr-char = ALPHA / DIGIT -/// / "!" / "#" / "$" / "&" / "+" / "-" / "." -/// / "^" / "_" / "`" / "|" / "~" -/// ; token except ( "*" / "'" / "%" ) -/// ``` -pub fn parse_extended_value(val: &str) -> Result { - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3, '\''); - - // Interpret the first piece as a Charset - let charset: Charset = match parts.next() { - None => return Err(::error::ParseError::Header), - Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?, - }; - - // Interpret the second piece as a language tag - let language_tag: Option = match parts.next() { - None => return Err(::error::ParseError::Header), - Some("") => None, - Some(s) => match s.parse() { - Ok(lt) => Some(lt), - Err(_) => return Err(::error::ParseError::Header), - }, - }; - - // Interpret the third piece as a sequence of value characters - let value: Vec = match parts.next() { - None => return Err(::error::ParseError::Header), - Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), - }; - - Ok(ExtendedValue { - value, - charset, - language_tag, - }) -} - -impl fmt::Display for ExtendedValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let encoded_value = percent_encoding::percent_encode( - &self.value[..], - self::percent_encoding_http::HTTP_VALUE, - ); - if let Some(ref lang) = self.language_tag { - write!(f, "{}'{}'{}", self.charset, lang, encoded_value) - } else { - write!(f, "{}''{}", self.charset, encoded_value) - } - } -} - -/// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 -pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = - percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} -mod percent_encoding_http { - use percent_encoding; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} - -#[cfg(test)] -mod tests { - use super::{parse_extended_value, ExtendedValue}; - use header::shared::Charset; - use language_tags::LanguageTag; - - #[test] - fn test_parse_extended_value_with_encoding_and_language_tag() { - let expected_language_tag = "en".parse::().unwrap(); - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode character U+00A3 (POUND SIGN) - let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Iso_8859_1, extended_value.charset); - assert!(extended_value.language_tag.is_some()); - assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!( - vec![163, b' ', b'r', b'a', b't', b'e', b's'], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_with_encoding() { - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) - // and U+20AC (EURO SIGN) - let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); - assert!(extended_value.language_tag.is_none()); - assert_eq!( - vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_missing_language_tag_and_encoding() { - // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 - let result = parse_extended_value("foo%20bar.html"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted() { - let result = parse_extended_value("UTF-8'missing third part"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted_blank() { - let result = parse_extended_value("blank second part'"); - assert!(result.is_err()); - } - - #[test] - fn test_fmt_extended_value_with_encoding_and_language_tag() { - let extended_value = ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: Some("en".parse().expect("Could not parse language tag")), - value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], - }; - assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); - } - - #[test] - fn test_fmt_extended_value_with_encoding() { - let extended_value = ExtendedValue { - charset: Charset::Ext("UTF-8".to_string()), - language_tag: None, - value: vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - }; - assert_eq!( - "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value) - ); - } -} diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs deleted file mode 100644 index b679971b..00000000 --- a/src/header/shared/charset.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use self::Charset::*; - -/// A Mime charset. -/// -/// The string representation is normalized to upper case. -/// -/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. -/// -/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml -#[derive(Clone, Debug, PartialEq)] -#[allow(non_camel_case_types)] -pub enum Charset { - /// US ASCII - Us_Ascii, - /// ISO-8859-1 - Iso_8859_1, - /// ISO-8859-2 - Iso_8859_2, - /// ISO-8859-3 - Iso_8859_3, - /// ISO-8859-4 - Iso_8859_4, - /// ISO-8859-5 - Iso_8859_5, - /// ISO-8859-6 - Iso_8859_6, - /// ISO-8859-7 - Iso_8859_7, - /// ISO-8859-8 - Iso_8859_8, - /// ISO-8859-9 - Iso_8859_9, - /// ISO-8859-10 - Iso_8859_10, - /// Shift_JIS - Shift_Jis, - /// EUC-JP - Euc_Jp, - /// ISO-2022-KR - Iso_2022_Kr, - /// EUC-KR - Euc_Kr, - /// ISO-2022-JP - Iso_2022_Jp, - /// ISO-2022-JP-2 - Iso_2022_Jp_2, - /// ISO-8859-6-E - Iso_8859_6_E, - /// ISO-8859-6-I - Iso_8859_6_I, - /// ISO-8859-8-E - Iso_8859_8_E, - /// ISO-8859-8-I - Iso_8859_8_I, - /// GB2312 - Gb2312, - /// Big5 - Big5, - /// KOI8-R - Koi8_R, - /// An arbitrary charset specified as a string - Ext(String), -} - -impl Charset { - fn label(&self) -> &str { - match *self { - Us_Ascii => "US-ASCII", - Iso_8859_1 => "ISO-8859-1", - Iso_8859_2 => "ISO-8859-2", - Iso_8859_3 => "ISO-8859-3", - Iso_8859_4 => "ISO-8859-4", - Iso_8859_5 => "ISO-8859-5", - Iso_8859_6 => "ISO-8859-6", - Iso_8859_7 => "ISO-8859-7", - Iso_8859_8 => "ISO-8859-8", - Iso_8859_9 => "ISO-8859-9", - Iso_8859_10 => "ISO-8859-10", - Shift_Jis => "Shift-JIS", - Euc_Jp => "EUC-JP", - Iso_2022_Kr => "ISO-2022-KR", - Euc_Kr => "EUC-KR", - Iso_2022_Jp => "ISO-2022-JP", - Iso_2022_Jp_2 => "ISO-2022-JP-2", - Iso_8859_6_E => "ISO-8859-6-E", - Iso_8859_6_I => "ISO-8859-6-I", - Iso_8859_8_E => "ISO-8859-8-E", - Iso_8859_8_I => "ISO-8859-8-I", - Gb2312 => "GB2312", - Big5 => "big5", - Koi8_R => "KOI8-R", - Ext(ref s) => s, - } - } -} - -impl Display for Charset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.label()) - } -} - -impl FromStr for Charset { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - Ok(match s.to_ascii_uppercase().as_ref() { - "US-ASCII" => Us_Ascii, - "ISO-8859-1" => Iso_8859_1, - "ISO-8859-2" => Iso_8859_2, - "ISO-8859-3" => Iso_8859_3, - "ISO-8859-4" => Iso_8859_4, - "ISO-8859-5" => Iso_8859_5, - "ISO-8859-6" => Iso_8859_6, - "ISO-8859-7" => Iso_8859_7, - "ISO-8859-8" => Iso_8859_8, - "ISO-8859-9" => Iso_8859_9, - "ISO-8859-10" => Iso_8859_10, - "SHIFT-JIS" => Shift_Jis, - "EUC-JP" => Euc_Jp, - "ISO-2022-KR" => Iso_2022_Kr, - "EUC-KR" => Euc_Kr, - "ISO-2022-JP" => Iso_2022_Jp, - "ISO-2022-JP-2" => Iso_2022_Jp_2, - "ISO-8859-6-E" => Iso_8859_6_E, - "ISO-8859-6-I" => Iso_8859_6_I, - "ISO-8859-8-E" => Iso_8859_8_E, - "ISO-8859-8-I" => Iso_8859_8_I, - "GB2312" => Gb2312, - "big5" => Big5, - "KOI8-R" => Koi8_R, - s => Ext(s.to_owned()), - }) - } -} - -#[test] -fn test_parse() { - assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); - assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); - assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); -} - -#[test] -fn test_display() { - assert_eq!("US-ASCII", format!("{}", Us_Ascii)); - assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); -} diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs deleted file mode 100644 index 64027d8a..00000000 --- a/src/header/shared/encoding.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::fmt; -use std::str; - -pub use self::Encoding::{ - Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, -}; - -/// A value to represent an encoding used in `Transfer-Encoding` -/// or `Accept-Encoding` header. -#[derive(Clone, PartialEq, Debug)] -pub enum Encoding { - /// The `chunked` encoding. - Chunked, - /// The `br` encoding. - Brotli, - /// The `gzip` encoding. - Gzip, - /// The `deflate` encoding. - Deflate, - /// The `compress` encoding. - Compress, - /// The `identity` encoding. - Identity, - /// The `trailers` encoding. - Trailers, - /// Some other encoding that is less common, can be any String. - EncodingExt(String), -} - -impl fmt::Display for Encoding { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Chunked => "chunked", - Brotli => "br", - Gzip => "gzip", - Deflate => "deflate", - Compress => "compress", - Identity => "identity", - Trailers => "trailers", - EncodingExt(ref s) => s.as_ref(), - }) - } -} - -impl str::FromStr for Encoding { - type Err = ::error::ParseError; - fn from_str(s: &str) -> Result { - match s { - "chunked" => Ok(Chunked), - "br" => Ok(Brotli), - "deflate" => Ok(Deflate), - "gzip" => Ok(Gzip), - "compress" => Ok(Compress), - "identity" => Ok(Identity), - "trailers" => Ok(Trailers), - _ => Ok(EncodingExt(s.to_owned())), - } - } -} diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs deleted file mode 100644 index 0d3b0a4e..00000000 --- a/src/header/shared/entity.rs +++ /dev/null @@ -1,266 +0,0 @@ -use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -/// check that each char in the slice is either: -/// 1. `%x21`, or -/// 2. in the range `%x23` to `%x7E`, or -/// 3. above `%x80` -fn check_slice_validity(slice: &str) -> bool { - slice - .bytes() - .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) -} - -/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) -/// -/// An entity tag consists of a string enclosed by two literal double quotes. -/// Preceding the first double quote is an optional weakness indicator, -/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and -/// `W/"xyzzy"`. -/// -/// # ABNF -/// -/// ```text -/// entity-tag = [ weak ] opaque-tag -/// weak = %x57.2F ; "W/", case-sensitive -/// opaque-tag = DQUOTE *etagc DQUOTE -/// etagc = %x21 / %x23-7E / obs-text -/// ; VCHAR except double quotes, plus obs-text -/// ``` -/// -/// # Comparison -/// To check if two entity tags are equivalent in an application always use the -/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use -/// `==` to check if two tags are identical. -/// -/// The example below shows the results for a set of entity-tag pairs and -/// both the weak and strong comparison function results: -/// -/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison | -/// |---------|---------|-------------------|-----------------| -/// | `W/"1"` | `W/"1"` | no match | match | -/// | `W/"1"` | `W/"2"` | no match | no match | -/// | `W/"1"` | `"1"` | no match | match | -/// | `"1"` | `"1"` | match | match | -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct EntityTag { - /// Weakness indicator for the tag - pub weak: bool, - /// The opaque string in between the DQUOTEs - tag: String, -} - -impl EntityTag { - /// Constructs a new EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn new(weak: bool, tag: String) -> EntityTag { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - EntityTag { weak, tag } - } - - /// Constructs a new weak EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn weak(tag: String) -> EntityTag { - EntityTag::new(true, tag) - } - - /// Constructs a new strong EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn strong(tag: String) -> EntityTag { - EntityTag::new(false, tag) - } - - /// Get the tag. - pub fn tag(&self) -> &str { - self.tag.as_ref() - } - - /// Set the tag. - /// # Panics - /// If the tag contains invalid characters. - pub fn set_tag(&mut self, tag: String) { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - self.tag = tag - } - - /// For strong comparison two entity-tags are equivalent if both are not - /// weak and their opaque-tags match character-by-character. - pub fn strong_eq(&self, other: &EntityTag) -> bool { - !self.weak && !other.weak && self.tag == other.tag - } - - /// For weak comparison two entity-tags are equivalent if their - /// opaque-tags match character-by-character, regardless of either or - /// both being tagged as "weak". - pub fn weak_eq(&self, other: &EntityTag) -> bool { - self.tag == other.tag - } - - /// The inverse of `EntityTag.strong_eq()`. - pub fn strong_ne(&self, other: &EntityTag) -> bool { - !self.strong_eq(other) - } - - /// The inverse of `EntityTag.weak_eq()`. - pub fn weak_ne(&self, other: &EntityTag) -> bool { - !self.weak_eq(other) - } -} - -impl Display for EntityTag { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.weak { - write!(f, "W/\"{}\"", self.tag) - } else { - write!(f, "\"{}\"", self.tag) - } - } -} - -impl FromStr for EntityTag { - type Err = ::error::ParseError; - - fn from_str(s: &str) -> Result { - let length: usize = s.len(); - let slice = &s[..]; - // Early exits if it doesn't terminate in a DQUOTE. - if !slice.ends_with('"') || slice.len() < 2 { - return Err(::error::ParseError::Header); - } - // The etag is weak if its first char is not a DQUOTE. - if slice.len() >= 2 - && slice.starts_with('"') - && check_slice_validity(&slice[1..length - 1]) - { - // No need to check if the last char is a DQUOTE, - // we already did that above. - return Ok(EntityTag { - weak: false, - tag: slice[1..length - 1].to_owned(), - }); - } else if slice.len() >= 4 - && slice.starts_with("W/\"") - && check_slice_validity(&slice[3..length - 1]) - { - return Ok(EntityTag { - weak: true, - tag: slice[3..length - 1].to_owned(), - }); - } - Err(::error::ParseError::Header) - } -} - -impl IntoHeaderValue for EntityTag { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut wrt = Writer::new(); - write!(wrt, "{}", self).unwrap(); - HeaderValue::from_shared(wrt.take()) - } -} - -#[cfg(test)] -mod tests { - use super::EntityTag; - - #[test] - fn test_etag_parse_success() { - // Expected success - assert_eq!( - "\"foobar\"".parse::().unwrap(), - EntityTag::strong("foobar".to_owned()) - ); - assert_eq!( - "\"\"".parse::().unwrap(), - EntityTag::strong("".to_owned()) - ); - assert_eq!( - "W/\"weaktag\"".parse::().unwrap(), - EntityTag::weak("weaktag".to_owned()) - ); - assert_eq!( - "W/\"\x65\x62\"".parse::().unwrap(), - EntityTag::weak("\x65\x62".to_owned()) - ); - assert_eq!( - "W/\"\"".parse::().unwrap(), - EntityTag::weak("".to_owned()) - ); - } - - #[test] - fn test_etag_parse_failures() { - // Expected failures - assert!("no-dquotes".parse::().is_err()); - assert!( - "w/\"the-first-w-is-case-sensitive\"" - .parse::() - .is_err() - ); - assert!("".parse::().is_err()); - assert!("\"unmatched-dquotes1".parse::().is_err()); - assert!("unmatched-dquotes2\"".parse::().is_err()); - assert!("matched-\"dquotes\"".parse::().is_err()); - } - - #[test] - fn test_etag_fmt() { - assert_eq!( - format!("{}", EntityTag::strong("foobar".to_owned())), - "\"foobar\"" - ); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); - assert_eq!( - format!("{}", EntityTag::weak("weak-etag".to_owned())), - "W/\"weak-etag\"" - ); - assert_eq!( - format!("{}", EntityTag::weak("\u{0065}".to_owned())), - "W/\"\x65\"" - ); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); - } - - #[test] - fn test_cmp() { - // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | - // |---------|---------|-------------------|-----------------| - // | `W/"1"` | `W/"1"` | no match | match | - // | `W/"1"` | `W/"2"` | no match | no match | - // | `W/"1"` | `"1"` | no match | match | - // | `"1"` | `"1"` | match | match | - let mut etag1 = EntityTag::weak("1".to_owned()); - let mut etag2 = EntityTag::weak("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::weak("2".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(!etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::strong("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(!etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - } -} diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs deleted file mode 100644 index 7fd26b12..00000000 --- a/src/header/shared/httpdate.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::fmt::{self, Display}; -use std::io::Write; -use std::str::FromStr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use bytes::{BufMut, BytesMut}; -use http::header::{HeaderValue, InvalidHeaderValueBytes}; -use time; - -use error::ParseError; -use header::IntoHeaderValue; - -/// A timestamp with HTTP formatting and parsing -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct HttpDate(time::Tm); - -impl FromStr for HttpDate { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match time::strptime(s, "%a, %d %b %Y %T %Z") - .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) - .or_else(|_| time::strptime(s, "%c")) - { - Ok(t) => Ok(HttpDate(t)), - Err(_) => Err(ParseError::Header), - } - } -} - -impl Display for HttpDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0.to_utc().rfc822(), f) - } -} - -impl From for HttpDate { - fn from(tm: time::Tm) -> HttpDate { - HttpDate(tm) - } -} - -impl From for HttpDate { - fn from(sys: SystemTime) -> HttpDate { - let tmspec = match sys.duration_since(UNIX_EPOCH) { - Ok(dur) => { - time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) - } - Err(err) => { - let neg = err.duration(); - time::Timespec::new( - -(neg.as_secs() as i64), - -(neg.subsec_nanos() as i32), - ) - } - }; - HttpDate(time::at_utc(tmspec)) - } -} - -impl IntoHeaderValue for HttpDate { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut wrt = BytesMut::with_capacity(29).writer(); - write!(wrt, "{}", self.0.rfc822()).unwrap(); - HeaderValue::from_shared(wrt.get_mut().take().freeze()) - } -} - -impl From for SystemTime { - fn from(date: HttpDate) -> SystemTime { - let spec = date.0.to_timespec(); - if spec.sec >= 0 { - UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) - } else { - UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) - } - } -} - -#[cfg(test)] -mod tests { - use super::HttpDate; - use time::Tm; - - const NOV_07: HttpDate = HttpDate(Tm { - tm_nsec: 0, - tm_sec: 37, - tm_min: 48, - tm_hour: 8, - tm_mday: 7, - tm_mon: 10, - tm_year: 94, - tm_wday: 0, - tm_isdst: 0, - tm_yday: 0, - tm_utcoff: 0, - }); - - #[test] - fn test_date() { - assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), - NOV_07 - ); - assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" - .parse::() - .unwrap(), - NOV_07 - ); - assert_eq!( - "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - NOV_07 - ); - assert!("this-is-no-date".parse::().is_err()); - } -} diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs deleted file mode 100644 index f2bc9163..00000000 --- a/src/header/shared/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Copied for `hyper::header::shared`; - -pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; -pub use self::httpdate::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; -pub use language_tags::LanguageTag; - -mod charset; -mod encoding; -mod entity; -mod httpdate; -mod quality_item; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs deleted file mode 100644 index 80bd7e1c..00000000 --- a/src/header/shared/quality_item.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::cmp; -use std::default::Default; -use std::fmt; -use std::str; - -use self::internal::IntoQuality; - -/// Represents a quality used in quality values. -/// -/// Can be created with the `q` function. -/// -/// # Implementation notes -/// -/// The quality value is defined as a number between 0 and 1 with three decimal -/// places. This means there are 1001 possible values. Since floating point -/// numbers are not exact and the smallest floating point data type (`f32`) -/// consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to -/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality -/// `q=0.532`. -/// -/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) -/// gives more information on quality values in HTTP header fields. -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Quality(u16); - -impl Default for Quality { - fn default() -> Quality { - Quality(1000) - } -} - -/// Represents an item with a quality value as defined in -/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). -#[derive(Clone, PartialEq, Debug)] -pub struct QualityItem { - /// The actual contents of the field. - pub item: T, - /// The quality (client or server preference) for the value. - pub quality: Quality, -} - -impl QualityItem { - /// Creates a new `QualityItem` from an item and a quality. - /// The item can be of any type. - /// The quality should be a value in the range [0, 1]. - pub fn new(item: T, quality: Quality) -> QualityItem { - QualityItem { item, quality } - } -} - -impl cmp::PartialOrd for QualityItem { - fn partial_cmp(&self, other: &QualityItem) -> Option { - self.quality.partial_cmp(&other.quality) - } -} - -impl fmt::Display for QualityItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.item, f)?; - match self.quality.0 { - 1000 => Ok(()), - 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), - } - } -} - -impl str::FromStr for QualityItem { - type Err = ::error::ParseError; - - fn from_str(s: &str) -> Result, ::error::ParseError> { - if !s.is_ascii() { - return Err(::error::ParseError::Header); - } - // Set defaults used if parsing fails. - let mut raw_item = s; - let mut quality = 1f32; - - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); - if parts.len() == 2 { - if parts[0].len() < 2 { - return Err(::error::ParseError::Header); - } - let start = &parts[0][0..2]; - if start == "q=" || start == "Q=" { - let q_part = &parts[0][2..parts[0].len()]; - if q_part.len() > 5 { - return Err(::error::ParseError::Header); - } - match q_part.parse::() { - Ok(q_value) => { - if 0f32 <= q_value && q_value <= 1f32 { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(::error::ParseError::Header); - } - } - Err(_) => return Err(::error::ParseError::Header), - } - } - } - match raw_item.parse::() { - // we already checked above that the quality is within range - Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), - Err(_) => Err(::error::ParseError::Header), - } - } -} - -#[inline] -fn from_f32(f: f32) -> Quality { - // this function is only used internally. A check that `f` is within range - // should be done before calling this method. Just in case, this - // debug_assert should catch if we were forgetful - debug_assert!( - f >= 0f32 && f <= 1f32, - "q value must be between 0.0 and 1.0" - ); - Quality((f * 1000f32) as u16) -} - -/// Convenience function to wrap a value in a `QualityItem` -/// Sets `q` to the default 1.0 -pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Default::default()) -} - -/// Convenience function to create a `Quality` from a float or integer. -/// -/// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality { - val.into_quality() -} - -mod internal { - use super::Quality; - - // TryFrom is probably better, but it's not stable. For now, we want to - // keep the functionality of the `q` function, while allowing it to be - // generic over `f32` and `u16`. - // - // `q` would panic before, so keep that behavior. `TryFrom` can be - // introduced later for a non-panicking conversion. - - pub trait IntoQuality: Sealed + Sized { - fn into_quality(self) -> Quality; - } - - impl IntoQuality for f32 { - fn into_quality(self) -> Quality { - assert!( - self >= 0f32 && self <= 1f32, - "float must be between 0.0 and 1.0" - ); - super::from_f32(self) - } - } - - impl IntoQuality for u16 { - fn into_quality(self) -> Quality { - assert!(self <= 1000, "u16 must be between 0 and 1000"); - Quality(self) - } - } - - pub trait Sealed {} - impl Sealed for u16 {} - impl Sealed for f32 {} -} - -#[cfg(test)] -mod tests { - use super::super::encoding::*; - use super::*; - - #[test] - fn test_quality_item_fmt_q_1() { - let x = qitem(Chunked); - assert_eq!(format!("{}", x), "chunked"); - } - #[test] - fn test_quality_item_fmt_q_0001() { - let x = QualityItem::new(Chunked, Quality(1)); - assert_eq!(format!("{}", x), "chunked; q=0.001"); - } - #[test] - fn test_quality_item_fmt_q_05() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(500), - }; - assert_eq!(format!("{}", x), "identity; q=0.5"); - } - - #[test] - fn test_quality_item_fmt_q_0() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(0), - }; - assert_eq!(x.to_string(), "identity; q=0"); - } - - #[test] - fn test_quality_item_from_str1() { - let x: Result, _> = "chunked".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str2() { - let x: Result, _> = "chunked; q=1".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str3() { - let x: Result, _> = "gzip; q=0.5".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(500), - } - ); - } - #[test] - fn test_quality_item_from_str4() { - let x: Result, _> = "gzip; q=0.273".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(273), - } - ); - } - #[test] - fn test_quality_item_from_str5() { - let x: Result, _> = "gzip; q=0.2739999".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_from_str6() { - let x: Result, _> = "gzip; q=2".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_ordering() { - let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); - let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); - let comparision_result: bool = x.gt(&y); - assert!(comparision_result) - } - - #[test] - fn test_quality() { - assert_eq!(q(0.5), Quality(500)); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid() { - q(-1.0); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid2() { - q(2.0); - } - - #[test] - fn test_fuzzing_bugs() { - assert!("99999;".parse::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) - } -} diff --git a/src/helpers.rs b/src/helpers.rs index e82d6161..860a02a4 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,571 +1,180 @@ -//! Various helpers +use actix_http::Response; +use actix_service::{NewService, Service}; +use futures::future::{ok, FutureResult}; +use futures::{Future, Poll}; -use http::{header, StatusCode}; -use regex::Regex; +pub(crate) type BoxedHttpService = Box< + Service< + Request = Req, + Response = Res, + Error = (), + Future = Box>, + >, +>; -use handler::Handler; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; +pub(crate) type BoxedHttpNewService = Box< + NewService< + Request = Req, + Response = Res, + Error = (), + InitError = (), + Service = BoxedHttpService, + Future = Box, Error = ()>>, + >, +>; -/// Path normalization helper -/// -/// By normalizing it means: -/// -/// - Add a trailing slash to the path. -/// - Remove a trailing slash from 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::*; -/// use actix_web::http::NormalizePath; -/// -/// # fn index(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() -/// # } -/// fn main() { -/// let app = App::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, - re_merge: Regex, - redirect: StatusCode, - not_found: StatusCode, -} +pub(crate) struct HttpNewService(T); -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, - merge: true, - re_merge: Regex::new("//+").unwrap(), - redirect: StatusCode::MOVED_PERMANENTLY, - not_found: StatusCode::NOT_FOUND, - } +impl HttpNewService +where + T: NewService, + T::Response: 'static, + T::Future: 'static, + T::Service: Service, + ::Future: 'static, +{ + pub fn new(service: T) -> Self { + HttpNewService(service) } } -impl NormalizePath { - /// Create new `NormalizePath` instance - pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { - NormalizePath { - append, - merge, - redirect, - re_merge: Regex::new("//+").unwrap(), - not_found: StatusCode::NOT_FOUND, - } +impl NewService for HttpNewService +where + T: NewService, + T::Request: 'static, + T::Response: 'static, + T::Future: 'static, + T::Service: Service + 'static, + ::Future: 'static, +{ + type Request = T::Request; + type Response = T::Response; + type Error = (); + type InitError = (); + type Service = BoxedHttpService; + type Future = Box>; + + fn new_service(&self, _: &()) -> Self::Future { + Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { + let service: BoxedHttpService<_, _> = + Box::new(HttpServiceWrapper { service }); + Ok(service) + })) } } -impl Handler for NormalizePath { - type Result = HttpResponse; +struct HttpServiceWrapper { + service: T, +} - fn handle(&self, req: &HttpRequest) -> Self::Result { - 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 req.resource().has_prefixed_resource(p.as_ref()) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_ref()) - .finish(); - } - // merge slashes and append trailing slash - if self.append && !p.ends_with('/') { - let p = p.as_ref().to_owned() + "/"; - if req.resource().has_prefixed_resource(&p) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); - } - } +impl Service for HttpServiceWrapper +where + T: Service, + T::Request: 'static, + T::Response: 'static, + T::Future: 'static, +{ + type Request = T::Request; + type Response = T::Response; + type Error = (); + type Future = Box>; - // try to remove trailing slash - if p.ends_with('/') { - let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_resource(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } else if p.ends_with('/') { - // try to remove trailing slash - let p = p.as_ref().trim_right_matches('/'); - if req.resource().has_prefixed_resource(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header( - header::LOCATION, - (p.to_owned() + "?" + query).as_str(), - ) - } else { - req.header(header::LOCATION, p) - }.finish(); - } - } - } - // append trailing slash - if self.append && !req.path().ends_with('/') { - let p = req.path().to_owned() + "/"; - if req.resource().has_prefixed_resource(&p) { - let p = if !query.is_empty() { - p + "?" + query - } else { - p - }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .finish(); - } - } - HttpResponse::new(self.not_found) + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready().map_err(|_| ()) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + Box::new(self.service.call(req).map_err(|_| ())) } } -#[cfg(test)] -mod tests { - use super::*; - use application::App; - use http::{header, Method}; - use test::TestRequest; +pub(crate) fn not_found(_: Req) -> FutureResult { + ok(Response::NotFound().finish()) +} - fn index(_req: &HttpRequest) -> HttpResponse { - HttpResponse::new(StatusCode::OK) - } +pub(crate) type HttpDefaultService = Box< + Service< + Request = Req, + Response = Res, + Error = (), + Future = Box>, + >, +>; - #[test] - fn test_normalize_path_trailing_slashes() { - let app = App::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(); +pub(crate) type HttpDefaultNewService = Box< + NewService< + Request = Req, + Response = Res, + Error = (), + InitError = (), + Service = HttpDefaultService, + Future = Box, Error = ()>>, + >, +>; - // trailing slashes - let params = vec![ - ("/resource1", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), - ("/resource2/", "", StatusCode::OK), - ("/resource1?p1=1&p2=2", "", StatusCode::OK), - ( - "/resource1/?p1=1&p2=2", - "/resource1?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/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 = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } - } +pub(crate) struct DefaultNewService { + service: T, +} - #[test] - fn test_prefixed_normalize_path_trailing_slashes() { - let app = App::new() - .prefix("/test") - .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![ - ("/test/resource1", "", StatusCode::OK), - ( - "/test/resource1/", - "/test/resource1", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/test/resource2", - "/test/resource2/", - StatusCode::MOVED_PERMANENTLY, - ), - ("/test/resource2/", "", StatusCode::OK), - ("/test/resource1?p1=1&p2=2", "", StatusCode::OK), - ( - "/test/resource1/?p1=1&p2=2", - "/test/resource1?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/test/resource2?p1=1&p2=2", - "/test/resource2/?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY, - ), - ("/test/resource2/?p1=1&p2=2", "", StatusCode::OK), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - 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 = App::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::MOVED_PERMANENTLY), - ("/resource2", StatusCode::NOT_FOUND), - ("/resource2/", StatusCode::OK), - ("/resource1?p1=1&p2=2", StatusCode::OK), - ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), - ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), - ("/resource2/?p1=1&p2=2", StatusCode::OK), - ]; - for (path, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - } - } - - #[test] - fn test_normalize_path_merge_slashes() { - let app = App::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/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), - ( - "//resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "//resource1//a//b//", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ("/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", - "/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", - "/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", - "/resource1/a/b?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - 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 = App::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/", - "/resource1/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", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "///resource1//a//b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/////resource1/a///b/", - "/resource1/a/b", - StatusCode::MOVED_PERMANENTLY, - ), - ( - "/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", - "/resource1/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, - ), - ( - "///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", - "/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", - "/resource1/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, - ), - ( - "/////resource2/a///b/?p=1", - "/resource2/a/b/?p=1", - StatusCode::MOVED_PERMANENTLY, - ), - ]; - for (path, target, code) in params { - let req = TestRequest::with_uri(path).request(); - let resp = app.run(req); - let r = &resp.as_msg(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap() - ); - } - } +impl DefaultNewService +where + T: NewService + 'static, + T::Future: 'static, + ::Future: 'static, +{ + pub fn new(service: T) -> Self { + DefaultNewService { service } + } +} + +impl NewService for DefaultNewService +where + T: NewService + 'static, + T::Request: 'static, + T::Future: 'static, + T::Service: 'static, + ::Future: 'static, +{ + type Request = T::Request; + type Response = T::Response; + type Error = (); + type InitError = (); + type Service = HttpDefaultService; + type Future = Box>; + + fn new_service(&self, _: &()) -> Self::Future { + Box::new( + self.service + .new_service(&()) + .map_err(|_| ()) + .and_then(|service| { + let service: HttpDefaultService<_, _> = + Box::new(DefaultServiceWrapper { service }); + Ok(service) + }), + ) + } +} + +struct DefaultServiceWrapper { + service: T, +} + +impl Service for DefaultServiceWrapper +where + T: Service + 'static, + T::Future: 'static, +{ + type Request = T::Request; + type Response = T::Response; + type Error = (); + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready().map_err(|_| ()) + } + + fn call(&mut self, req: T::Request) -> Self::Future { + Box::new(self.service.call(req).map_err(|_| ())) } } diff --git a/src/httpcodes.rs b/src/httpcodes.rs deleted file mode 100644 index 41e57d1e..00000000 --- a/src/httpcodes.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Basic http responses -#![allow(non_upper_case_globals)] -use http::StatusCode; -use httpresponse::{HttpResponse, HttpResponseBuilder}; - -macro_rules! STATIC_RESP { - ($name:ident, $status:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> HttpResponseBuilder { - HttpResponse::build($status) - } - }; -} - -impl HttpResponse { - STATIC_RESP!(Ok, StatusCode::OK); - STATIC_RESP!(Created, StatusCode::CREATED); - STATIC_RESP!(Accepted, StatusCode::ACCEPTED); - STATIC_RESP!( - NonAuthoritativeInformation, - StatusCode::NON_AUTHORITATIVE_INFORMATION - ); - - STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); - STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); - STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT); - STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS); - STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); - - STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); - STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY); - STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); - STATIC_RESP!(Found, StatusCode::FOUND); - STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); - STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED); - STATIC_RESP!(UseProxy, StatusCode::USE_PROXY); - STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); - STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); - - STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST); - STATIC_RESP!(NotFound, StatusCode::NOT_FOUND); - STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED); - STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); - STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); - STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); - STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - STATIC_RESP!( - ProxyAuthenticationRequired, - StatusCode::PROXY_AUTHENTICATION_REQUIRED - ); - STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); - STATIC_RESP!(Conflict, StatusCode::CONFLICT); - STATIC_RESP!(Gone, StatusCode::GONE); - STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED); - STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); - STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); - STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); - STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); - STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); - STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); - - STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); - STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); - STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); - STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); - STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); - STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); - STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); - STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); -} - -#[cfg(test)] -mod tests { - use body::Body; - use http::StatusCode; - use httpresponse::HttpResponse; - - #[test] - fn test_build() { - let resp = HttpResponse::Ok().body(Body::Empty); - assert_eq!(resp.status(), StatusCode::OK); - } -} diff --git a/src/httpmessage.rs b/src/httpmessage.rs deleted file mode 100644 index ea5e4d86..00000000 --- a/src/httpmessage.rs +++ /dev/null @@ -1,855 +0,0 @@ -use bytes::{Bytes, BytesMut}; -use encoding::all::UTF_8; -use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, Encoding}; -use encoding::EncodingRef; -use futures::{Async, Future, Poll, Stream}; -use http::{header, HeaderMap}; -use mime::Mime; -use serde::de::DeserializeOwned; -use serde_urlencoded; -use std::str; - -use error::{ - ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, -}; -use header::Header; -use json::JsonBody; -use multipart::Multipart; - -/// Trait that implements general purpose operations on http messages -pub trait HttpMessage: Sized { - /// Type of message payload stream - type Stream: Stream + Sized; - - /// Read the message headers. - fn headers(&self) -> &HeaderMap; - - /// Message payload stream - fn payload(&self) -> Self::Stream; - - #[doc(hidden)] - /// Get a header - fn get_header(&self) -> Option - where - Self: Sized, - { - if self.headers().contains_key(H::name()) { - H::parse(self).ok() - } else { - None - } - } - - /// Read the request content type. If request does not contain - /// *Content-Type* header, empty str get returned. - fn content_type(&self) -> &str { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return content_type.split(';').next().unwrap().trim(); - } - } - "" - } - - /// Get content type encoding - /// - /// UTF-8 is used by default, If request charset is not set. - fn encoding(&self) -> Result { - if let Some(mime_type) = self.mime_type()? { - if let Some(charset) = mime_type.get_param("charset") { - if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) { - Ok(enc) - } else { - Err(ContentTypeError::UnknownEncoding) - } - } else { - Ok(UTF_8) - } - } else { - Ok(UTF_8) - } - } - - /// Convert the request content type to a known mime type. - fn mime_type(&self) -> Result, ContentTypeError> { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return match content_type.parse() { - Ok(mt) => Ok(Some(mt)), - Err(_) => Err(ContentTypeError::ParseError), - }; - } else { - return Err(ContentTypeError::ParseError); - } - } - Ok(None) - } - - /// Check if request has chunked transfer encoding - fn chunked(&self) -> Result { - if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { - if let Ok(s) = encodings.to_str() { - Ok(s.to_lowercase().contains("chunked")) - } else { - Err(ParseError::Header) - } - } else { - Ok(false) - } - } - - /// Load http message body. - /// - /// By default only 256Kb payload reads to a memory, then - /// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` - /// method to change upper limit. - /// - /// ## Server example - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::{ - /// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse, - /// }; - /// use bytes::Bytes; - /// use futures::future::Future; - /// - /// fn index(mut req: HttpRequest) -> FutureResponse { - /// 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(HttpResponse::Ok().into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - fn body(&self) -> MessageBody { - MessageBody::new(self) - } - - /// Parse `application/x-www-form-urlencoded` encoded request's body. - /// Return `UrlEncoded` future. Form can be deserialized to any type that - /// implements `Deserialize` trait from *serde*. - /// - /// Returns error: - /// - /// * content type is not `application/x-www-form-urlencoded` - /// * content-length is greater than 256k - /// - /// ## Server example - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::Future; - /// # use std::collections::HashMap; - /// use actix_web::{FutureResponse, HttpMessage, HttpRequest, HttpResponse}; - /// - /// fn index(mut req: HttpRequest) -> FutureResponse { - /// Box::new( - /// req.urlencoded::>() // <- get UrlEncoded future - /// .from_err() - /// .and_then(|params| { // <- url encoded parameters - /// println!("==== BODY ==== {:?}", params); - /// Ok(HttpResponse::Ok().into()) - /// }), - /// ) - /// } - /// # fn main() {} - /// ``` - fn urlencoded(&self) -> UrlEncoded { - UrlEncoded::new(self) - } - - /// Parse `application/json` encoded body. - /// Return `JsonBody` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - /// - /// ## Server example - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::*; - /// use futures::future::{ok, 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(HttpResponse::Ok().into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - fn json(&self) -> JsonBody { - JsonBody::new::<()>(self, None) - } - - /// Return stream to http payload processes as multipart. - /// - /// Content-type: multipart/form-data; - /// - /// ## Server example - /// - /// ```rust - /// # extern crate actix_web; - /// # extern crate env_logger; - /// # extern crate futures; - /// # extern crate actix; - /// # use std::str; - /// # use actix_web::*; - /// # use actix::FinishStream; - /// # 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(|_| HttpResponse::Ok().into()) - /// .responder() - /// } - /// # fn main() {} - /// ``` - fn multipart(&self) -> Multipart { - let boundary = Multipart::boundary(self.headers()); - Multipart::new(boundary, self.payload()) - } - - /// Return stream of lines. - fn readlines(&self) -> Readlines { - Readlines::new(self) - } -} - -/// Stream to read request line by line. -pub struct Readlines { - stream: T::Stream, - buff: BytesMut, - limit: usize, - checked_buff: bool, - encoding: EncodingRef, - err: Option, -} - -impl Readlines { - /// Create a new stream to read request line by line. - fn new(req: &T) -> Self { - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(err) => return Self::err(req, err.into()), - }; - - Readlines { - stream: req.payload(), - buff: BytesMut::with_capacity(262_144), - limit: 262_144, - checked_buff: true, - err: None, - encoding, - } - } - - /// Change max line size. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - fn err(req: &T, err: ReadlinesError) -> Self { - Readlines { - stream: req.payload(), - buff: BytesMut::new(), - limit: 262_144, - checked_buff: true, - encoding: UTF_8, - err: Some(err), - } - } -} - -impl Stream for Readlines { - type Item = String; - type Error = ReadlinesError; - - fn poll(&mut self) -> Poll, Self::Error> { - if let Some(err) = self.err.take() { - return Err(err); - } - - // check if there is a newline in the buffer - if !self.checked_buff { - let mut found: Option = None; - for (ind, b) in self.buff.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - return Ok(Async::Ready(Some(line))); - } - self.checked_buff = true; - } - // poll req for more bytes - match self.stream.poll() { - Ok(Async::Ready(Some(mut bytes))) => { - // check if there is a newline in bytes - let mut found: Option = None; - for (ind, b) in bytes.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&bytes.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - // extend buffer with rest of the bytes; - self.buff.extend_from_slice(&bytes); - self.checked_buff = false; - return Ok(Async::Ready(Some(line))); - } - self.buff.extend_from_slice(&bytes); - Ok(Async::NotReady) - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - if self.buff.is_empty() { - return Ok(Async::Ready(None)); - } - if self.buff.len() > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&self.buff, DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - self.buff.clear(); - Ok(Async::Ready(Some(line))) - } - Err(e) => Err(ReadlinesError::from(e)), - } - } -} - -/// Future that resolves to a complete http message body. -pub struct MessageBody { - limit: usize, - length: Option, - stream: Option, - err: Option, - fut: Option>>, -} - -impl MessageBody { - /// Create `MessageBody` for request. - pub fn new(req: &T) -> MessageBody { - let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(PayloadError::UnknownLength); - } - } else { - return Self::err(PayloadError::UnknownLength); - } - } - - MessageBody { - limit: 262_144, - length: len, - stream: Some(req.payload()), - fut: None, - err: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - fn err(e: PayloadError) -> Self { - MessageBody { - stream: None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - } - } -} - -impl Future for MessageBody -where - T: HttpMessage + 'static, -{ - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - if let Some(len) = self.length.take() { - if len > self.limit { - return Err(PayloadError::Overflow); - } - } - - // future - let limit = self.limit; - self.fut = Some(Box::new( - self.stream - .take() - .expect("Can not be used second time") - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }).map(|body| body.freeze()), - )); - self.poll() - } -} - -/// Future that resolves to a parsed urlencoded values. -pub struct UrlEncoded { - stream: Option, - limit: usize, - length: Option, - encoding: EncodingRef, - err: Option, - fut: Option>>, -} - -impl UrlEncoded { - /// Create a new future to URL encode a request - pub fn new(req: &T) -> UrlEncoded { - // check content type - if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { - return Self::err(UrlencodedError::ContentType); - } - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(_) => return Self::err(UrlencodedError::ContentType), - }; - - let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(UrlencodedError::UnknownLength); - } - } else { - return Self::err(UrlencodedError::UnknownLength); - } - }; - - UrlEncoded { - encoding, - stream: Some(req.payload()), - limit: 262_144, - length: len, - fut: None, - err: None, - } - } - - fn err(e: UrlencodedError) -> Self { - UrlEncoded { - stream: None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - encoding: UTF_8, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for UrlEncoded -where - T: HttpMessage + 'static, - U: DeserializeOwned + 'static, -{ - type Item = U; - type Error = UrlencodedError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - // payload size - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Err(UrlencodedError::Overflow); - } - } - - // future - let encoding = self.encoding; - let fut = self - .stream - .take() - .expect("UrlEncoded could not be used second time") - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }).and_then(move |body| { - if (encoding as *const Encoding) == UTF_8 { - serde_urlencoded::from_bytes::(&body) - .map_err(|_| UrlencodedError::Parse) - } else { - let body = encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| UrlencodedError::Parse)?; - serde_urlencoded::from_str::(&body) - .map_err(|_| UrlencodedError::Parse) - } - }); - self.fut = Some(Box::new(fut)); - self.poll() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use encoding::all::ISO_8859_2; - use encoding::Encoding; - use futures::Async; - use mime; - use test::TestRequest; - - #[test] - fn test_content_type() { - let req = TestRequest::with_header("content-type", "text/plain").finish(); - assert_eq!(req.content_type(), "text/plain"); - let req = - TestRequest::with_header("content-type", "application/json; charset=utf=8") - .finish(); - assert_eq!(req.content_type(), "application/json"); - let req = TestRequest::default().finish(); - assert_eq!(req.content_type(), ""); - } - - #[test] - fn test_mime_type() { - let req = TestRequest::with_header("content-type", "application/json").finish(); - assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); - let req = TestRequest::default().finish(); - assert_eq!(req.mime_type().unwrap(), None); - let req = - TestRequest::with_header("content-type", "application/json; charset=utf-8") - .finish(); - let mt = req.mime_type().unwrap().unwrap(); - assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); - assert_eq!(mt.type_(), mime::APPLICATION); - assert_eq!(mt.subtype(), mime::JSON); - } - - #[test] - fn test_mime_type_error() { - let req = TestRequest::with_header( - "content-type", - "applicationadfadsfasdflknadsfklnadsfjson", - ).finish(); - assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); - } - - #[test] - fn test_encoding() { - let req = TestRequest::default().finish(); - assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - - let req = TestRequest::with_header("content-type", "application/json").finish(); - assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - - let req = TestRequest::with_header( - "content-type", - "application/json; charset=ISO-8859-2", - ).finish(); - assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); - } - - #[test] - fn test_encoding_error() { - let req = TestRequest::with_header("content-type", "applicatjson").finish(); - assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); - - let req = TestRequest::with_header( - "content-type", - "application/json; charset=kkkttktk", - ).finish(); - assert_eq!( - Some(ContentTypeError::UnknownEncoding), - req.encoding().err() - ); - } - - #[test] - fn test_chunked() { - let req = TestRequest::default().finish(); - assert!(!req.chunked().unwrap()); - - let req = - TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); - assert!(req.chunked().unwrap()); - - let req = TestRequest::default() - .header( - header::TRANSFER_ENCODING, - Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), - ).finish(); - 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, - } - } - } - - #[derive(Deserialize, Debug, PartialEq)] - struct Info { - hello: String, - } - - #[test] - fn test_urlencoded_error() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "xxxx") - .finish(); - assert_eq!( - req.urlencoded::().poll().err().unwrap(), - UrlencodedError::UnknownLength - ); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "1000000") - .finish(); - assert_eq!( - req.urlencoded::().poll().err().unwrap(), - UrlencodedError::Overflow - ); - - let req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") - .header(header::CONTENT_LENGTH, "10") - .finish(); - assert_eq!( - req.urlencoded::().poll().err().unwrap(), - UrlencodedError::ContentType - ); - } - - #[test] - fn test_urlencoded() { - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - let result = req.urlencoded::().poll().ok().unwrap(); - assert_eq!( - result, - Async::Ready(Info { - hello: "world".to_owned() - }) - ); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded; charset=utf-8", - ).header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - let result = req.urlencoded().poll().ok().unwrap(); - assert_eq!( - result, - Async::Ready(Info { - hello: "world".to_owned() - }) - ); - } - - #[test] - fn test_message_body() { - let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); - match req.body().poll().err().unwrap() { - PayloadError::UnknownLength => (), - _ => unreachable!("error"), - } - - let req = TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); - match req.body().poll().err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - - let req = TestRequest::default() - .set_payload(Bytes::from_static(b"test")) - .finish(); - match req.body().poll().ok().unwrap() { - Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), - _ => unreachable!("error"), - } - - let req = TestRequest::default() - .set_payload(Bytes::from_static(b"11111111111111")) - .finish(); - match req.body().limit(5).poll().err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - } - - #[test] - fn test_readlines() { - let req = TestRequest::default() - .set_payload(Bytes::from_static( - b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ - industry. Lorem Ipsum has been the industry's standard dummy\n\ - Contrary to popular belief, Lorem Ipsum is not simply random text.", - )).finish(); - let mut r = Readlines::new(&req); - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "Lorem Ipsum is simply dummy text of the printing and typesetting\n" - ), - _ => unreachable!("error"), - } - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "industry. Lorem Ipsum has been the industry's standard dummy\n" - ), - _ => unreachable!("error"), - } - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "Contrary to popular belief, Lorem Ipsum is not simply random text." - ), - _ => unreachable!("error"), - } - } -} diff --git a/src/httprequest.rs b/src/httprequest.rs deleted file mode 100644 index 0e4f74e5..00000000 --- a/src/httprequest.rs +++ /dev/null @@ -1,545 +0,0 @@ -//! HTTP Request message related code. -use std::cell::{Ref, RefMut}; -use std::collections::HashMap; -use std::net::SocketAddr; -use std::ops::Deref; -use std::rc::Rc; -use std::{fmt, str}; - -use cookie::Cookie; -use futures_cpupool::CpuPool; -use http::{header, HeaderMap, Method, StatusCode, Uri, Version}; -use url::{form_urlencoded, Url}; - -use body::Body; -use error::{CookieParseError, UrlGenerationError}; -use extensions::Extensions; -use handler::FromRequest; -use httpmessage::HttpMessage; -use httpresponse::{HttpResponse, HttpResponseBuilder}; -use info::ConnectionInfo; -use param::Params; -use payload::Payload; -use router::ResourceInfo; -use server::Request; - -struct Query(HashMap); -struct Cookies(Vec>); - -/// An HTTP Request -pub struct HttpRequest { - req: Option, - state: Rc, - resource: ResourceInfo, -} - -impl HttpMessage for HttpRequest { - type Stream = Payload; - - #[inline] - fn headers(&self) -> &HeaderMap { - self.request().headers() - } - - #[inline] - fn payload(&self) -> Payload { - if let Some(payload) = self.request().inner.payload.borrow_mut().take() { - payload - } else { - Payload::empty() - } - } -} - -impl Deref for HttpRequest { - type Target = Request; - - fn deref(&self) -> &Request { - self.request() - } -} - -impl HttpRequest { - #[inline] - pub(crate) fn new( - req: Request, state: Rc, resource: ResourceInfo, - ) -> HttpRequest { - HttpRequest { - state, - resource, - req: Some(req), - } - } - - #[inline] - /// Construct new http request with state. - pub(crate) fn with_state(&self, state: Rc) -> HttpRequest { - HttpRequest { - state, - req: self.req.as_ref().map(|r| r.clone()), - resource: self.resource.clone(), - } - } - - /// Construct new http request with empty state. - pub fn drop_state(&self) -> HttpRequest { - HttpRequest { - state: Rc::new(()), - req: self.req.as_ref().map(|r| r.clone()), - resource: self.resource.clone(), - } - } - - #[inline] - /// Construct new http request with new RouteInfo. - pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest { - resource.merge(&self.resource); - - HttpRequest { - resource, - req: self.req.as_ref().map(|r| r.clone()), - state: self.state.clone(), - } - } - - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - &self.state - } - - #[inline] - /// Server request - pub fn request(&self) -> &Request { - self.req.as_ref().unwrap() - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.request().extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.request().extensions_mut() - } - - /// Default `CpuPool` - #[inline] - #[doc(hidden)] - pub fn cpu_pool(&self) -> &CpuPool { - self.request().server_settings().cpu_pool() - } - - #[inline] - /// Create http response - pub fn response(&self, status: StatusCode, body: Body) -> HttpResponse { - self.request().server_settings().get_response(status, body) - } - - #[inline] - /// Create http response builder - pub fn build_response(&self, status: StatusCode) -> HttpResponseBuilder { - self.request() - .server_settings() - .get_response_builder(status) - } - - /// Read the Request Uri. - #[inline] - pub fn uri(&self) -> &Uri { - self.request().inner.url.uri() - } - - /// Read the Request method. - #[inline] - pub fn method(&self) -> &Method { - &self.request().inner.method - } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> Version { - self.request().inner.version - } - - /// The target path of this Request. - #[inline] - pub fn path(&self) -> &str { - self.request().inner.url.path() - } - - /// Get *ConnectionInfo* for the correct request. - #[inline] - pub fn connection_info(&self) -> Ref { - self.request().connection_info() - } - - /// Generate url for named resource - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::{App, HttpRequest, HttpResponse, http}; - /// # - /// fn index(req: HttpRequest) -> HttpResponse { - /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource - /// HttpResponse::Ok().into() - /// } - /// - /// fn main() { - /// let app = App::new() - /// .resource("/test/{one}/{two}/{three}", |r| { - /// r.name("foo"); // <- set resource name, then it could be used in `url_for` - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .finish(); - /// } - /// ``` - pub fn url_for( - &self, name: &str, elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - self.resource.url_for(&self, name, elements) - } - - /// Generate url for named resource - /// - /// This method is similar to `HttpRequest::url_for()` but it can be used - /// for urls that do not contain variable parts. - pub fn url_for_static(&self, name: &str) -> Result { - const NO_PARAMS: [&str; 0] = []; - self.url_for(name, &NO_PARAMS) - } - - /// This method returns reference to current `ResourceInfo` object. - #[inline] - pub fn resource(&self) -> &ResourceInfo { - &self.resource - } - - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - /// - /// To get client connection information `connection_info()` method should - /// be used. - #[inline] - pub fn peer_addr(&self) -> Option { - self.request().inner.addr - } - - /// url query parameters. - pub fn query(&self) -> Ref> { - if self.extensions().get::().is_none() { - let mut query = HashMap::new(); - for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { - query.insert(key.as_ref().to_string(), val.to_string()); - } - self.extensions_mut().insert(Query(query)); - } - Ref::map(self.extensions(), |ext| &ext.get::().unwrap().0) - } - - /// The query string in the URL. - /// - /// E.g., id=10 - #[inline] - pub fn query_string(&self) -> &str { - if let Some(query) = self.uri().query().as_ref() { - query - } else { - "" - } - } - - /// Load request cookies. - #[inline] - pub fn cookies(&self) -> Result>>, CookieParseError> { - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.request().inner.headers.get_all(header::COOKIE) { - let s = - str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; - for cookie_str in s.split(';').map(|s| s.trim()) { - if !cookie_str.is_empty() { - cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); - } - } - } - self.extensions_mut().insert(Cookies(cookies)); - } - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } - - /// Return request cookie. - #[inline] - pub fn cookie(&self, name: &str) -> Option> { - if let Ok(cookies) = self.cookies() { - for cookie in cookies.iter() { - if cookie.name() == name { - return Some(cookie.to_owned()); - } - } - } - None - } - - pub(crate) fn set_cookies(&mut self, cookies: Option>>) { - if let Some(cookies) = cookies { - self.extensions_mut().insert(Cookies(cookies)); - } - } - - /// Get a reference to the Params object. - /// - /// Params is a container for url parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. - #[inline] - pub fn match_info(&self) -> &Params { - &self.resource.match_info() - } - - /// Check if request requires connection upgrade - pub(crate) fn upgrade(&self) -> bool { - self.request().upgrade() - } - - /// Set read buffer capacity - /// - /// Default buffer capacity is 32Kb. - pub fn set_read_buffer_capacity(&mut self, cap: usize) { - if let Some(payload) = self.request().inner.payload.borrow_mut().as_mut() { - payload.set_read_buffer_capacity(cap) - } - } -} - -impl Drop for HttpRequest { - fn drop(&mut self) { - if let Some(req) = self.req.take() { - req.release(); - } - } -} - -impl Clone for HttpRequest { - fn clone(&self) -> HttpRequest { - HttpRequest { - req: self.req.as_ref().map(|r| r.clone()), - state: self.state.clone(), - resource: self.resource.clone(), - } - } -} - -impl FromRequest for HttpRequest { - type Config = (); - type Result = Self; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - req.clone() - } -} - -impl fmt::Debug for HttpRequest { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nHttpRequest {:?} {}:{}", - self.version(), - self.method(), - self.path() - )?; - if !self.query_string().is_empty() { - writeln!(f, " query: ?{:?}", self.query_string())?; - } - if !self.match_info().is_empty() { - writeln!(f, " params: {:?}", self.match_info())?; - } - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use resource::Resource; - use router::{ResourceDef, Router}; - use test::TestRequest; - - #[test] - fn test_debug() { - 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 = TestRequest::default().finish(); - assert!(req.cookies().unwrap().is_empty()); - } - - #[test] - fn test_request_cookies() { - let req = TestRequest::default() - .header(header::COOKIE, "cookie1=value1") - .header(header::COOKIE, "cookie2=value2") - .finish(); - { - 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_request_query() { - let req = TestRequest::with_uri("/?id=test").finish(); - assert_eq!(req.query_string(), "id=test"); - let query = req.query(); - assert_eq!(&query["id"], "test"); - } - - #[test] - fn test_request_match_info() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/{key}/"))); - - let req = TestRequest::with_uri("/value/?id=test").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.match_info().get("key"), Some("value")); - } - - #[test] - fn test_url_for() { - let mut router = Router::<()>::default(); - let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}")); - resource.name("index"); - router.register_resource(resource); - - let info = router.default_route_info(); - assert!(!info.has_prefixed_resource("/use/")); - assert!(info.has_resource("/user/test.html")); - assert!(info.has_prefixed_resource("/user/test.html")); - assert!(!info.has_resource("/test/unknown")); - assert!(!info.has_prefixed_resource("/test/unknown")); - - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") - .finish_with_router(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" - ); - } - - #[test] - fn test_url_for_with_prefix() { - let mut resource = Resource::new(ResourceDef::new("/user/{name}.html")); - resource.name("index"); - let mut router = Router::<()>::default(); - router.set_prefix("/prefix"); - router.register_resource(resource); - - let mut info = router.default_route_info(); - info.set_prefix(7); - assert!(!info.has_prefixed_resource("/use/")); - assert!(info.has_resource("/user/test.html")); - assert!(!info.has_prefixed_resource("/user/test.html")); - assert!(!info.has_resource("/prefix/user/test.html")); - assert!(info.has_prefixed_resource("/prefix/user/test.html")); - - let req = TestRequest::with_uri("/prefix/test") - .prefix(7) - .header(header::HOST, "www.rust-lang.org") - .finish_with_router(router); - let url = req.url_for("index", &["test"]); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/prefix/user/test.html" - ); - } - - #[test] - fn test_url_for_static() { - let mut resource = Resource::new(ResourceDef::new("/index.html")); - resource.name("index"); - let mut router = Router::<()>::default(); - router.set_prefix("/prefix"); - router.register_resource(resource); - - let mut info = router.default_route_info(); - info.set_prefix(7); - assert!(info.has_resource("/index.html")); - assert!(!info.has_prefixed_resource("/index.html")); - assert!(!info.has_resource("/prefix/index.html")); - assert!(info.has_prefixed_resource("/prefix/index.html")); - - let req = TestRequest::with_uri("/prefix/test") - .prefix(7) - .header(header::HOST, "www.rust-lang.org") - .finish_with_router(router); - let url = req.url_for_static("index"); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/prefix/index.html" - ); - } - - #[test] - fn test_url_for_external() { - let mut router = Router::<()>::default(); - router.register_external( - "youtube", - ResourceDef::external("https://youtube.com/watch/{video_id}"), - ); - - let info = router.default_route_info(); - assert!(!info.has_resource("https://youtube.com/watch/unknown")); - assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown")); - - let req = TestRequest::default().finish_with_router(router); - let url = req.url_for("youtube", &["oHg5SJYRHA0"]); - assert_eq!( - url.ok().unwrap().as_str(), - "https://youtube.com/watch/oHg5SJYRHA0" - ); - } -} diff --git a/src/httpresponse.rs b/src/httpresponse.rs deleted file mode 100644 index 226c847f..00000000 --- a/src/httpresponse.rs +++ /dev/null @@ -1,1458 +0,0 @@ -//! Http response -use std::cell::RefCell; -use std::collections::VecDeque; -use std::io::Write; -use std::{fmt, mem, str}; - -use bytes::{BufMut, Bytes, BytesMut}; -use cookie::{Cookie, CookieJar}; -use futures::Stream; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode, Version}; -use serde::Serialize; -use serde_json; - -use body::Body; -use client::ClientResponse; -use error::Error; -use handler::Responder; -use header::{ContentEncoding, Header, IntoHeaderValue}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; - -/// max write buffer size 64k -pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; - -/// Represents various types of connection -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ConnectionType { - /// Close connection after response - Close, - /// Keep connection alive after response - KeepAlive, - /// Connection is upgraded to different type - Upgrade, -} - -/// An HTTP Response -pub struct HttpResponse(Box, &'static HttpResponsePool); - -impl HttpResponse { - #[inline] - fn get_ref(&self) -> &InnerHttpResponse { - self.0.as_ref() - } - - #[inline] - fn get_mut(&mut self) -> &mut InnerHttpResponse { - self.0.as_mut() - } - - /// Create http response builder with specific status. - #[inline] - pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponsePool::get(status) - } - - /// Create http response builder - #[inline] - pub fn build_from>(source: T) -> HttpResponseBuilder { - source.into() - } - - /// Constructs a response - #[inline] - pub fn new(status: StatusCode) -> HttpResponse { - HttpResponsePool::with_body(status, Body::Empty) - } - - /// Constructs a response with body - #[inline] - pub fn with_body>(status: StatusCode, body: B) -> HttpResponse { - HttpResponsePool::with_body(status, body.into()) - } - - /// Constructs an error response - #[inline] - pub fn from_error(error: Error) -> HttpResponse { - let mut resp = error.as_response_error().error_response(); - resp.get_mut().error = Some(error); - resp - } - - /// Convert `HttpResponse` to a `HttpResponseBuilder` - #[inline] - pub fn into_builder(self) -> HttpResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - for c in self.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - HttpResponseBuilder { - pool: self.1, - response: Some(self.0), - err: None, - cookies: jar, - } - } - - /// The source `error` for this response - #[inline] - pub fn error(&self) -> Option<&Error> { - self.get_ref().error.as_ref() - } - - /// Get the HTTP version of this response - #[inline] - pub fn version(&self) -> Option { - self.get_ref().version - } - - /// Get the headers from the response - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.get_ref().headers - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.get_mut().headers - } - - /// Get an iterator for the cookies set by this response - #[inline] - pub fn cookies(&self) -> CookieIter { - CookieIter { - iter: self.get_ref().headers.get_all(header::SET_COOKIE).iter(), - } - } - - /// Add a cookie to this response - #[inline] - pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { - let h = &mut self.get_mut().headers; - HeaderValue::from_str(&cookie.to_string()) - .map(|c| { - h.append(header::SET_COOKIE, c); - }).map_err(|e| e.into()) - } - - /// Remove all cookies with the given name from this response. Returns - /// the number of cookies removed. - #[inline] - pub fn del_cookie(&mut self, name: &str) -> usize { - let h = &mut self.get_mut().headers; - let vals: Vec = h - .get_all(header::SET_COOKIE) - .iter() - .map(|v| v.to_owned()) - .collect(); - h.remove(header::SET_COOKIE); - - let mut count: usize = 0; - for v in vals { - if let Ok(s) = v.to_str() { - if let Ok(c) = Cookie::parse_encoded(s) { - if c.name() == name { - count += 1; - continue; - } - } - } - h.append(header::SET_COOKIE, v); - } - count - } - - /// Get the response status code - #[inline] - pub fn status(&self) -> StatusCode { - self.get_ref().status - } - - /// Set the `StatusCode` for this response - #[inline] - pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.get_mut().status - } - - /// Get custom reason for the response - #[inline] - pub fn reason(&self) -> &str { - if let Some(reason) = self.get_ref().reason { - reason - } else { - 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.get_mut().reason = Some(reason); - self - } - - /// Set connection type - pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self { - self.get_mut().connection_type = Some(conn); - self - } - - /// Connection upgrade status - #[inline] - pub fn upgrade(&self) -> bool { - 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.get_ref().connection_type { - match ct { - ConnectionType::KeepAlive => Some(true), - ConnectionType::Close | ConnectionType::Upgrade => Some(false), - } - } else { - None - } - } - - /// is chunked encoding enabled - #[inline] - pub fn chunked(&self) -> Option { - self.get_ref().chunked - } - - /// Content encoding - #[inline] - pub fn content_encoding(&self) -> Option { - self.get_ref().encoding - } - - /// Set content encoding - pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { - self.get_mut().encoding = Some(enc); - self - } - - /// Get body of this response - #[inline] - pub fn body(&self) -> &Body { - &self.get_ref().body - } - - /// Set a body - pub fn set_body>(&mut self, body: B) { - 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.get_mut().body, body.into()) - } - - /// Size of response in bytes, excluding HTTP headers - pub fn response_size(&self) -> u64 { - self.get_ref().response_size - } - - /// Set content encoding - pub(crate) fn set_response_size(&mut self, size: u64) { - self.get_mut().response_size = size; - } - - /// Get write buffer capacity - pub fn write_buffer_capacity(&self) -> usize { - self.get_ref().write_capacity - } - - /// Set write buffer capacity - pub fn set_write_buffer_capacity(&mut self, cap: usize) { - self.get_mut().write_capacity = cap; - } - - pub(crate) fn release(self) { - self.1.release(self.0); - } - - pub(crate) fn into_parts(self) -> HttpResponseParts { - self.0.into_parts() - } - - pub(crate) fn from_parts(parts: HttpResponseParts) -> HttpResponse { - HttpResponse( - Box::new(InnerHttpResponse::from_parts(parts)), - HttpResponsePool::get_pool(), - ) - } -} - -impl fmt::Debug for HttpResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let res = writeln!( - f, - "\nHttpResponse {:?} {}{}", - self.get_ref().version, - self.get_ref().status, - self.get_ref().reason.unwrap_or("") - ); - let _ = writeln!(f, " encoding: {:?}", self.get_ref().encoding); - let _ = writeln!(f, " headers:"); - for (key, val) in self.get_ref().headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); - } - res - } -} - -pub struct CookieIter<'a> { - iter: header::ValueIter<'a, HeaderValue>, -} - -impl<'a> Iterator for CookieIter<'a> { - type Item = Cookie<'a>; - - #[inline] - fn next(&mut self) -> Option> { - for v in self.iter.by_ref() { - if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { - return Some(c); - } - } - None - } -} - -/// An HTTP response builder -/// -/// This type can be used to construct an instance of `HttpResponse` through a -/// builder-like pattern. -pub struct HttpResponseBuilder { - pool: &'static HttpResponsePool, - response: Option>, - err: Option, - cookies: Option, -} - -impl HttpResponseBuilder { - /// Set HTTP status code of 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 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) { - parts.version = Some(version); - } - self - } - - /// Append a header. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; - /// - /// fn index(req: HttpRequest) -> Result { - /// Ok(HttpResponse::Ok() - /// .set(http::header::IfModifiedSince( - /// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?, - /// )) - /// .finish()) - /// } - /// fn main() {} - /// ``` - #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.append(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse}; - /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// HttpResponse::Ok() - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// } - /// fn main() {} - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.response, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - /// Set or replace a header with a single value. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse}; - /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// HttpResponse::Ok() - /// .insert("X-TEST", "value") - /// .insert(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// } - /// fn main() {} - /// ``` - pub fn insert(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.response, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Remove all instances of a header already set on this `HttpResponseBuilder`. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse}; - /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// HttpResponse::Ok() - /// .header(http::header::CONTENT_TYPE, "nevermind") // won't be used - /// .remove(http::header::CONTENT_TYPE) - /// .finish() - /// } - /// ``` - pub fn remove(&mut self, key: K) -> &mut Self - where HeaderName: HttpTryFrom - { - if let Some(parts) = parts(&mut self.response, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => { - parts.headers.remove(key); - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// 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.response, &self.err) { - parts.reason = Some(reason); - } - self - } - - /// Set content encoding. - /// - /// 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.response, &self.err) { - parts.encoding = Some(enc); - } - self - } - - /// 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); - } - self - } - - /// Set connection type to Upgrade - #[inline] - #[doc(hidden)] - 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 chunked(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.chunked = Some(true); - } - self - } - - /// Force disable chunked encoding - #[inline] - pub fn no_chunking(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.chunked = Some(false); - } - self - } - - /// Set response content type - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - HeaderValue: HttpTryFrom, - { - 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()), - }; - } - self - } - - /// Set content length - #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { - let mut wrt = BytesMut::new().writer(); - let _ = write!(wrt, "{}", len); - self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) - } - - /// Set a cookie - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; - /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// HttpResponse::Ok() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// } - /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - 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 - } - - /// Remove cookie - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, HttpResponse, Result}; - /// - /// fn index(req: &HttpRequest) -> HttpResponse { - /// let mut builder = HttpResponse::Ok(); - /// - /// if let Some(ref cookie) = req.cookie("name") { - /// builder.del_cookie(cookie); - /// } - /// - /// builder.finish() - /// } - /// ``` - pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { - { - if self.cookies.is_none() { - self.cookies = Some(CookieJar::new()) - } - let jar = self.cookies.as_mut().unwrap(); - let cookie = cookie.clone().into_owned(); - jar.add_original(cookie.clone()); - jar.remove(cookie); - } - self - } - - /// This method calls provided closure with builder reference if value is - /// true. - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut HttpResponseBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if value is - /// Some. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut HttpResponseBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - - /// Set write buffer capacity - /// - /// This parameter makes sense only for streaming response - /// or actor. If write buffer reaches specified capacity, stream or actor - /// get paused. - /// - /// Default write buffer capacity is 64kb - pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self { - if let Some(parts) = parts(&mut self.response, &self.err) { - parts.write_capacity = cap; - } - self - } - - /// Set a body and generate `HttpResponse`. - /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> HttpResponse { - if let Some(e) = self.err.take() { - return Error::from(e).into(); - } - let mut response = self.response.take().expect("cannot reuse response builder"); - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => response.headers.append(header::SET_COOKIE, val), - Err(e) => return Error::from(e).into(), - }; - } - } - response.body = body.into(); - HttpResponse(response, self.pool) - } - - #[inline] - /// Set a streaming body and generate `HttpResponse`. - /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> HttpResponse - where - S: Stream + 'static, - E: Into, - { - self.body(Body::Streaming(Box::new(stream.map_err(|e| e.into())))) - } - - /// Set a json body and generate `HttpResponse` - /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> HttpResponse { - self.json2(&value) - } - - /// Set a json body and generate `HttpResponse` - /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> HttpResponse { - match serde_json::to_string(value) { - Ok(body) => { - let contains = if let Some(parts) = parts(&mut self.response, &self.err) - { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/json"); - } - - self.body(body) - } - Err(e) => Error::from(e).into(), - } - } - - #[inline] - /// Set an empty body and generate `HttpResponse` - /// - /// `HttpResponseBuilder` can not be used after this call. - pub fn finish(&mut self) -> HttpResponse { - self.body(Body::Empty) - } - - /// This method construct new `HttpResponseBuilder` - pub fn take(&mut self) -> HttpResponseBuilder { - HttpResponseBuilder { - pool: self.pool, - response: self.response.take(), - err: self.err.take(), - cookies: self.cookies.take(), - } - } -} - -#[inline] -#[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; - } - parts.as_mut() -} - -/// Helper converters -impl, E: Into> From> for HttpResponse { - fn from(res: Result) -> Self { - match res { - Ok(val) => val.into(), - Err(err) => err.into().into(), - } - } -} - -impl From for HttpResponse { - fn from(mut builder: HttpResponseBuilder) -> Self { - builder.finish() - } -} - -impl Responder for HttpResponseBuilder { - type Item = HttpResponse; - type Error = Error; - - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> Result { - Ok(self.finish()) - } -} - -impl From<&'static str> for HttpResponse { - fn from(val: &'static str) -> Self { - HttpResponse::Ok() - .content_type("text/plain; charset=utf-8") - .body(val) - } -} - -impl Responder for &'static str { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - -impl From<&'static [u8]> for HttpResponse { - fn from(val: &'static [u8]) -> Self { - HttpResponse::Ok() - .content_type("application/octet-stream") - .body(val) - } -} - -impl Responder for &'static [u8] { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -impl From for HttpResponse { - fn from(val: String) -> Self { - HttpResponse::Ok() - .content_type("text/plain; charset=utf-8") - .body(val) - } -} - -impl Responder for String { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - -impl<'a> From<&'a String> for HttpResponse { - fn from(val: &'a String) -> Self { - HttpResponse::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(val) - } -} - -impl<'a> Responder for &'a String { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - -impl From for HttpResponse { - fn from(val: Bytes) -> Self { - HttpResponse::Ok() - .content_type("application/octet-stream") - .body(val) - } -} - -impl Responder for Bytes { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -impl From for HttpResponse { - fn from(val: BytesMut) -> Self { - HttpResponse::Ok() - .content_type("application/octet-stream") - .body(val) - } -} - -impl Responder for BytesMut { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - Ok(req - .build_response(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -/// Create `HttpResponseBuilder` from `ClientResponse` -/// -/// It is useful for proxy response. This implementation -/// copies all responses's headers and status. -impl<'a> From<&'a ClientResponse> for HttpResponseBuilder { - fn from(resp: &'a ClientResponse) -> HttpResponseBuilder { - let mut builder = HttpResponse::build(resp.status()); - for (key, value) in resp.headers() { - builder.header(key.clone(), value.clone()); - } - builder - } -} - -impl<'a, S> From<&'a HttpRequest> for HttpResponseBuilder { - fn from(req: &'a HttpRequest) -> HttpResponseBuilder { - req.request() - .server_settings() - .get_response_builder(StatusCode::OK) - } -} - -#[derive(Debug)] -struct InnerHttpResponse { - version: Option, - headers: HeaderMap, - status: StatusCode, - reason: Option<&'static str>, - body: Body, - chunked: Option, - encoding: Option, - connection_type: Option, - write_capacity: usize, - response_size: u64, - error: Option, -} - -pub(crate) struct HttpResponseParts { - version: Option, - headers: HeaderMap, - status: StatusCode, - reason: Option<&'static str>, - body: Option, - encoding: Option, - connection_type: Option, - error: Option, -} - -impl InnerHttpResponse { - #[inline] - fn new(status: StatusCode, body: Body) -> InnerHttpResponse { - InnerHttpResponse { - status, - body, - version: None, - headers: HeaderMap::with_capacity(16), - reason: None, - chunked: None, - encoding: None, - connection_type: None, - response_size: 0, - write_capacity: MAX_WRITE_BUFFER_SIZE, - error: None, - } - } - - /// This is for failure, we can not have Send + Sync on Streaming and Actor response - fn into_parts(mut self) -> HttpResponseParts { - let body = match mem::replace(&mut self.body, Body::Empty) { - Body::Empty => None, - Body::Binary(mut bin) => Some(bin.take()), - Body::Streaming(_) | Body::Actor(_) => { - error!("Streaming or Actor body is not support by error response"); - None - } - }; - - HttpResponseParts { - body, - version: self.version, - headers: self.headers, - status: self.status, - reason: self.reason, - encoding: self.encoding, - connection_type: self.connection_type, - error: self.error, - } - } - - fn from_parts(parts: HttpResponseParts) -> InnerHttpResponse { - let body = if let Some(ref body) = parts.body { - Body::Binary(body.clone().into()) - } else { - Body::Empty - }; - - InnerHttpResponse { - body, - status: parts.status, - version: parts.version, - headers: parts.headers, - reason: parts.reason, - chunked: None, - encoding: parts.encoding, - connection_type: parts.connection_type, - response_size: 0, - write_capacity: MAX_WRITE_BUFFER_SIZE, - error: parts.error, - } - } -} - -/// Internal use only! -pub(crate) struct HttpResponsePool(RefCell>>); - -thread_local!(static POOL: &'static HttpResponsePool = HttpResponsePool::pool()); - -impl HttpResponsePool { - fn pool() -> &'static HttpResponsePool { - let pool = HttpResponsePool(RefCell::new(VecDeque::with_capacity(128))); - Box::leak(Box::new(pool)) - } - - pub fn get_pool() -> &'static HttpResponsePool { - POOL.with(|p| *p) - } - - #[inline] - pub fn get_builder( - pool: &'static HttpResponsePool, status: StatusCode, - ) -> HttpResponseBuilder { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.status = status; - HttpResponseBuilder { - pool, - response: Some(msg), - err: None, - cookies: None, - } - } else { - let msg = Box::new(InnerHttpResponse::new(status, Body::Empty)); - HttpResponseBuilder { - pool, - response: Some(msg), - err: None, - cookies: None, - } - } - } - - #[inline] - pub fn get_response( - pool: &'static HttpResponsePool, status: StatusCode, body: Body, - ) -> HttpResponse { - if let Some(mut msg) = pool.0.borrow_mut().pop_front() { - msg.status = status; - msg.body = body; - HttpResponse(msg, pool) - } else { - let msg = Box::new(InnerHttpResponse::new(status, body)); - HttpResponse(msg, pool) - } - } - - #[inline] - fn get(status: StatusCode) -> HttpResponseBuilder { - POOL.with(|pool| HttpResponsePool::get_builder(pool, status)) - } - - #[inline] - fn with_body(status: StatusCode, body: Body) -> HttpResponse { - POOL.with(|pool| HttpResponsePool::get_response(pool, status, body)) - } - - #[inline] - fn release(&self, mut inner: Box) { - let mut p = self.0.borrow_mut(); - if p.len() < 128 { - inner.headers.clear(); - inner.version = None; - inner.chunked = None; - inner.reason = None; - inner.encoding = None; - inner.connection_type = None; - inner.response_size = 0; - inner.error = None; - inner.write_capacity = MAX_WRITE_BUFFER_SIZE; - p.push_front(inner); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use body::Binary; - use http; - use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use time::Duration; - - use test::TestRequest; - - #[test] - fn test_debug() { - let resp = HttpResponse::Ok() - .header(COOKIE, HeaderValue::from_static("cookie1=value1; ")) - .header(COOKIE, HeaderValue::from_static("cookie2=value2; ")) - .finish(); - let dbg = format!("{:?}", resp); - assert!(dbg.contains("HttpResponse")); - } - - #[test] - fn test_response_cookies() { - let req = TestRequest::default() - .header(COOKIE, "cookie1=value1") - .header(COOKIE, "cookie2=value2") - .finish(); - let cookies = req.cookies().unwrap(); - - let resp = HttpResponse::Ok() - .cookie( - http::Cookie::build("name", "value") - .domain("www.rust-lang.org") - .path("/test") - .http_only(true) - .max_age(Duration::days(1)) - .finish(), - ).del_cookie(&cookies[0]) - .finish(); - - 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_update_response_cookies() { - let mut r = HttpResponse::Ok() - .cookie(http::Cookie::new("original", "val100")) - .finish(); - - r.add_cookie(&http::Cookie::new("cookie2", "val200")) - .unwrap(); - r.add_cookie(&http::Cookie::new("cookie2", "val250")) - .unwrap(); - r.add_cookie(&http::Cookie::new("cookie3", "val300")) - .unwrap(); - - assert_eq!(r.cookies().count(), 4); - r.del_cookie("cookie2"); - - let mut iter = r.cookies(); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("original", "val100")); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("cookie3", "val300")); - } - - #[test] - fn test_basic_builder() { - let resp = HttpResponse::Ok() - .header("X-TEST", "value") - .version(Version::HTTP_10) - .finish(); - assert_eq!(resp.version(), Some(Version::HTTP_10)); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_insert() { - let resp = HttpResponse::Ok() - .insert("deleteme", "old value") - .insert("deleteme", "new value") - .finish(); - assert_eq!("new value", resp.headers().get("deleteme").expect("new value")); - } - - #[test] - fn test_remove() { - let resp = HttpResponse::Ok() - .header("deleteme", "value") - .remove("deleteme") - .finish(); - assert!(resp.headers().get("deleteme").is_none()) - } - - #[test] - fn test_remove_replace() { - let resp = HttpResponse::Ok() - .header("some-header", "old_value1") - .header("some-header", "old_value2") - .remove("some-header") - .header("some-header", "new_value1") - .header("some-header", "new_value2") - .remove("unrelated-header") - .finish(); - let mut v = resp.headers().get_all("some-header").into_iter(); - assert_eq!("new_value1", v.next().unwrap()); - assert_eq!("new_value2", v.next().unwrap()); - assert_eq!(None, v.next()); - } - - #[test] - fn test_upgrade() { - let resp = HttpResponse::build(StatusCode::OK).upgrade().finish(); - assert!(resp.upgrade()) - } - - #[test] - fn test_force_close() { - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - assert!(!resp.keep_alive().unwrap()) - } - - #[test] - fn test_content_type() { - let resp = HttpResponse::build(StatusCode::OK) - .content_type("text/plain") - .body(Body::Empty); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") - } - - #[test] - fn test_content_encoding() { - let resp = HttpResponse::build(StatusCode::OK).finish(); - assert_eq!(resp.content_encoding(), None); - - #[cfg(feature = "brotli")] - { - let resp = HttpResponse::build(StatusCode::OK) - .content_encoding(ContentEncoding::Br) - .finish(); - assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); - } - - let resp = HttpResponse::build(StatusCode::OK) - .content_encoding(ContentEncoding::Gzip) - .finish(); - assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); - } - - #[test] - fn test_json() { - let resp = HttpResponse::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); - } - - #[test] - fn test_json_ct() { - let resp = HttpResponse::build(StatusCode::OK) - .header(CONTENT_TYPE, "text/json") - .json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); - } - - #[test] - fn test_json2() { - let resp = HttpResponse::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); - } - - #[test] - fn test_json2_ct() { - let resp = HttpResponse::build(StatusCode::OK) - .header(CONTENT_TYPE, "text/json") - .json2(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!( - *resp.body(), - Body::from(Bytes::from_static(b"[\"v1\",\"v2\",\"v3\"]")) - ); - } - - impl Body { - pub(crate) fn bin_ref(&self) -> &Binary { - match *self { - Body::Binary(ref bin) => bin, - _ => panic!(), - } - } - } - - #[test] - fn test_into_response() { - let req = TestRequest::default().finish(); - - let resp: HttpResponse = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test")); - - let resp: HttpResponse = "test".respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test")); - - let resp: HttpResponse = b"test".as_ref().into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); - - let resp: HttpResponse = b"test".as_ref().respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(b"test".as_ref())); - - let resp: HttpResponse = "test".to_owned().into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); - - let resp: HttpResponse = "test".to_owned().respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from("test".to_owned())); - - let resp: HttpResponse = (&"test".to_owned()).into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); - - let resp: HttpResponse = (&"test".to_owned()).respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(&"test".to_owned())); - - let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().bin_ref(), - &Binary::from(Bytes::from_static(b"test")) - ); - - let b = Bytes::from_static(b"test"); - let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.body().bin_ref(), - &Binary::from(Bytes::from_static(b"test")) - ); - - let b = BytesMut::from("test"); - let resp: HttpResponse = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); - - let b = BytesMut::from("test"); - let resp: HttpResponse = b.respond_to(&req).ok().unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), &Binary::from(BytesMut::from("test"))); - } - - #[test] - fn test_into_builder() { - let mut resp: HttpResponse = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - - resp.add_cookie(&http::Cookie::new("cookie1", "val100")) - .unwrap(); - - let mut builder = resp.into_builder(); - let resp = builder.status(StatusCode::BAD_REQUEST).finish(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let cookie = resp.cookies().next().unwrap(); - assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100")); - } -} diff --git a/src/info.rs b/src/info.rs index 43c22123..3b51215f 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,10 +1,17 @@ -use http::header::{self, HeaderName}; -use server::Request; +use std::cell::Ref; + +use actix_http::http::header::{self, HeaderName}; +use actix_http::RequestHead; const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; +pub enum ConnectionInfoError { + UnknownHost, + UnknownScheme, +} + /// `HttpRequest` connection information #[derive(Clone, Default)] pub struct ConnectionInfo { @@ -16,18 +23,22 @@ pub struct ConnectionInfo { impl ConnectionInfo { /// Create *ConnectionInfo* instance for a request. - #[cfg_attr( - feature = "cargo-clippy", - allow(cyclomatic_complexity) - )] - pub fn update(&mut self, req: &Request) { + pub fn get(req: &RequestHead) -> Ref { + if !req.extensions().contains::() { + req.extensions_mut().insert(ConnectionInfo::new(req)); + } + Ref::map(req.extensions(), |e| e.get().unwrap()) + } + + #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] + fn new(req: &RequestHead) -> ConnectionInfo { let mut host = None; let mut scheme = None; let mut remote = None; let mut peer = None; // load forwarded header - for hdr in req.headers().get_all(header::FORWARDED) { + for hdr in req.headers.get_all(header::FORWARDED) { if let Ok(val) = hdr.to_str() { for pair in val.split(';') { for el in pair.split(',') { @@ -35,15 +46,21 @@ impl ConnectionInfo { if let Some(name) = items.next() { if let Some(val) = items.next() { match &name.to_lowercase() as &str { - "for" => if remote.is_none() { - remote = Some(val.trim()); - }, - "proto" => if scheme.is_none() { - scheme = Some(val.trim()); - }, - "host" => if host.is_none() { - host = Some(val.trim()); - }, + "for" => { + if remote.is_none() { + remote = Some(val.trim()); + } + } + "proto" => { + if scheme.is_none() { + scheme = Some(val.trim()); + } + } + "host" => { + if host.is_none() { + host = Some(val.trim()); + } + } _ => (), } } @@ -56,7 +73,7 @@ impl ConnectionInfo { // scheme if scheme.is_none() { if let Some(h) = req - .headers() + .headers .get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) { if let Ok(h) = h.to_str() { @@ -64,7 +81,7 @@ impl ConnectionInfo { } } if scheme.is_none() { - scheme = req.uri().scheme_part().map(|a| a.as_str()); + scheme = req.uri.scheme_part().map(|a| a.as_str()); if scheme.is_none() && req.server_settings().secure() { scheme = Some("https") } @@ -74,7 +91,7 @@ impl ConnectionInfo { // host if host.is_none() { if let Some(h) = req - .headers() + .headers .get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) { if let Ok(h) = h.to_str() { @@ -82,11 +99,11 @@ impl ConnectionInfo { } } if host.is_none() { - if let Some(h) = req.headers().get(header::HOST) { + if let Some(h) = req.headers.get(header::HOST) { host = h.to_str().ok(); } if host.is_none() { - host = req.uri().authority_part().map(|a| a.as_str()); + host = req.uri.authority_part().map(|a| a.as_str()); if host.is_none() { host = Some(req.server_settings().host()); } @@ -97,7 +114,7 @@ impl ConnectionInfo { // remote addr if remote.is_none() { if let Some(h) = req - .headers() + .headers .get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) { if let Ok(h) = h.to_str() { @@ -110,10 +127,12 @@ impl ConnectionInfo { } } - self.scheme = scheme.unwrap_or("http").to_owned(); - self.host = host.unwrap_or("localhost").to_owned(); - self.remote = remote.map(|s| s.to_owned()); - self.peer = peer; + ConnectionInfo { + scheme: scheme.unwrap_or("http").to_owned(), + host: host.unwrap_or("localhost").to_owned(), + remote: remote.map(|s| s.to_owned()), + peer: peer, + } } /// Scheme of the request. @@ -163,7 +182,7 @@ impl ConnectionInfo { #[cfg(test)] mod tests { use super::*; - use test::TestRequest; + use crate::test::TestRequest; #[test] fn test_forwarded() { @@ -177,7 +196,8 @@ mod tests { .header( header::FORWARDED, "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", - ).request(); + ) + .request(); let mut info = ConnectionInfo::default(); info.update(&req); diff --git a/src/json.rs b/src/json.rs deleted file mode 100644 index b04cad2f..00000000 --- a/src/json.rs +++ /dev/null @@ -1,519 +0,0 @@ -use bytes::BytesMut; -use futures::{Future, Poll, Stream}; -use http::header::CONTENT_LENGTH; -use std::fmt; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; - -use mime; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json; - -use error::{Error, JsonPayloadError}; -use handler::{FromRequest, Responder}; -use http::StatusCode; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -/// Json helper -/// -/// Json can be used for two different purpose. First is for json response -/// generation and second is for extracting typed information from request's -/// payload. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Json, Result, http}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor -/// } -/// ``` -/// -/// 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 Json { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Json { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Json { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl fmt::Debug for Json -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Json: {:?}", self.0) - } -} - -impl fmt::Display for Json -where - T: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl Responder for Json { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - let body = serde_json::to_string(&self.0)?; - - Ok(req - .build_response(StatusCode::OK) - .content_type("application/json") - .body(body)) - } -} - -impl FromRequest for Json -where - T: DeserializeOwned + 'static, - S: 'static, -{ - type Config = JsonConfig; - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - Box::new( - JsonBody::new(req, Some(cfg)) - .limit(cfg.limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Json), - ) - } -} - -/// Json extractor configuration -/// -/// ```rust -/// # extern crate actix_web; -/// extern crate mime; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, http, App, HttpResponse, Json, Result}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::POST) -/// .with_config(index, |cfg| { -/// cfg.0.limit(4096) // <- change json extractor configuration -/// .content_type(|mime| { // <- accept text/plain content type -/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN -/// }) -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }); -/// }) -/// }); -/// } -/// ``` -pub struct JsonConfig { - limit: usize, - ehandler: Rc) -> Error>, - content_type: Option bool>>, -} - -impl JsonConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self - where - F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } - - /// Set predicate for allowed content types - pub fn content_type(&mut self, predicate: F) -> &mut Self - where - F: Fn(mime::Mime) -> bool + 'static, - { - self.content_type = Some(Box::new(predicate)); - self - } -} - -impl Default for JsonConfig { - fn default() -> Self { - JsonConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - content_type: None, - } - } -} - -/// Request payload json parser that resolves to a deserialized `T` value. -/// -/// Returns error: -/// -/// * content type is not `application/json` -/// (unless specified in [`JsonConfig`](struct.JsonConfig.html)) -/// * content length is greater than 256k -/// -/// # Server example -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate futures; -/// # #[macro_use] extern crate serde_derive; -/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, HttpResponse}; -/// 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(HttpResponse::Ok().into()) -/// }).responder() -/// } -/// # fn main() {} -/// ``` -pub struct JsonBody { - limit: usize, - length: Option, - stream: Option, - err: Option, - fut: Option>>, -} - -impl JsonBody { - /// Create `JsonBody` for request. - pub fn new(req: &T, cfg: Option<&JsonConfig>) -> Self { - // check content-type - let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) || - cfg.map_or(false, |cfg| { - cfg.content_type.as_ref().map_or(false, |predicate| predicate(mime)) - }) - } else { - false - }; - if !json { - return JsonBody { - limit: 262_144, - length: None, - stream: None, - fut: None, - err: Some(JsonPayloadError::ContentType), - }; - } - - let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } - } - } - - JsonBody { - limit: 262_144, - length: len, - stream: Some(req.payload()), - fut: None, - err: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for JsonBody { - type Item = U; - type Error = JsonPayloadError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Err(JsonPayloadError::Overflow); - } - } - - let fut = self - .stream - .take() - .expect("JsonBody could not be used second time") - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(JsonPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }).and_then(|body| Ok(serde_json::from_slice::(&body)?)); - self.fut = Some(Box::new(fut)); - self.poll() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - use futures::Async; - use http::header; - - use handler::Handler; - use test::TestRequest; - use with::With; - - 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(MyObject { - name: "test".to_owned(), - }); - let resp = json.respond_to(&TestRequest::default().finish()).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/json" - ); - } - - #[test] - fn test_json_body() { - let req = TestRequest::default().finish(); - let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - - let req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ).finish(); - let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - - let req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ).header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ).finish(); - let mut json = req.json::().limit(100); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - - let req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ).header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - - let mut json = req.json::(); - assert_eq!( - json.poll().ok().unwrap(), - Async::Ready(MyObject { - name: "test".to_owned() - }) - ); - } - - #[test] - fn test_with_json() { - let mut cfg = JsonConfig::default(); - cfg.limit(4096); - let handler = With::new(|data: Json| data, cfg); - - let req = TestRequest::default().finish(); - assert!(handler.handle(&req).as_err().is_some()); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ).header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - assert!(handler.handle(&req).as_err().is_none()) - } - - #[test] - fn test_with_json_and_bad_content_type() { - let mut cfg = JsonConfig::default(); - cfg.limit(4096); - let handler = With::new(|data: Json| data, cfg); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ).header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - assert!(handler.handle(&req).as_err().is_some()) - } - - #[test] - fn test_with_json_and_good_custom_content_type() { - let mut cfg = JsonConfig::default(); - cfg.limit(4096); - cfg.content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - }); - let handler = With::new(|data: Json| data, cfg); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ).header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - assert!(handler.handle(&req).as_err().is_none()) - } - - #[test] - fn test_with_json_and_bad_custom_content_type() { - let mut cfg = JsonConfig::default(); - cfg.limit(4096); - cfg.content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - }); - let handler = With::new(|data: Json| data, cfg); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/html"), - ).header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ).set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - assert!(handler.handle(&req).as_err().is_some()) - } -} diff --git a/src/lib.rs b/src/lib.rs index 3b00cda1..f09c11ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,292 +1,37 @@ -//! Actix web is a small, pragmatic, and extremely fast web framework -//! for Rust. -//! -//! ```rust -//! use actix_web::{server, App, Path, Responder}; -//! # use std::thread; -//! -//! fn index(info: Path<(String, u32)>) -> impl Responder { -//! format!("Hello {}! id:{}", info.0, info.1) -//! } -//! -//! fn main() { -//! # thread::spawn(|| { -//! server::new(|| { -//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index)) -//! }).bind("127.0.0.1:8080") -//! .unwrap() -//! .run(); -//! # }); -//! } -//! ``` -//! -//! ## Documentation & community resources -//! -//! Besides the API documentation (which you are currently looking -//! at!), several other resources are available: -//! -//! * [User Guide](https://actix.rs/docs/) -//! * [Chat on gitter](https://gitter.im/actix/actix) -//! * [GitHub repository](https://github.com/actix/actix-web) -//! * [Cargo package](https://crates.io/crates/actix-web) -//! -//! To get started navigating the API documentation you may want to -//! consider looking at the following pages: -//! -//! * [App](struct.App.html): This struct represents an actix-web -//! application and is used to configure routes and other common -//! settings. -//! -//! * [HttpServer](server/struct.HttpServer.html): This struct -//! represents an HTTP server instance and is used to instantiate and -//! configure servers. -//! -//! * [HttpRequest](struct.HttpRequest.html) and -//! [HttpResponse](struct.HttpResponse.html): These structs -//! represent HTTP requests and responses and expose various methods -//! for inspecting, creating and otherwise utilizing them. -//! -//! ## Features -//! -//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols -//! * Streaming and pipelining -//! * Keep-alive and slow requests handling -//! * `WebSockets` server/client -//! * Transparent content compression/decompression (br, gzip, deflate) -//! * Configurable request routing -//! * Graceful server shutdown -//! * Multipart streams -//! * SSL support with OpenSSL or `native-tls` -//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) -//! * Built on top of [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.26 or later -//! -//! ## Package feature -//! -//! * `tls` - enables ssl support via `native-tls` crate -//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` -//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` -//! * `uds` - enables support for making client requests via Unix Domain Sockets. -//! Unix only. Not necessary for *serving* requests. -//! * `session` - enables session support, includes `ring` crate as -//! dependency -//! * `brotli` - enables `brotli` compression support, requires `c` -//! compiler -//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires -//! `c` compiler -//! * `flate2-rust` - experimental rust based implementation for -//! `gzip`, `deflate` compression. -//! -#![cfg_attr(actix_nightly, feature( - specialization, // for impl ErrorResponse for std::error::Error - extern_prelude, - tool_lints, -))] -#![warn(missing_docs)] +#![allow(clippy::type_complexity, dead_code, unused_variables)] -#[macro_use] -extern crate log; -extern crate base64; -extern crate byteorder; -extern crate bytes; -extern crate regex; -extern crate sha1; -extern crate time; -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate failure; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate futures; -extern crate cookie; -extern crate futures_cpupool; -extern crate http as modhttp; -extern crate httparse; -extern crate language_tags; -extern crate lazycell; -extern crate mime; -extern crate mime_guess; -extern crate mio; -extern crate net2; -extern crate parking_lot; -extern crate rand; -extern crate slab; -extern crate tokio; -extern crate tokio_current_thread; -extern crate tokio_io; -extern crate tokio_reactor; -extern crate tokio_tcp; -extern crate tokio_timer; -#[cfg(all(unix, feature = "uds"))] -extern crate tokio_uds; -extern crate url; -#[macro_use] -extern crate serde; -#[cfg(feature = "brotli")] -extern crate brotli2; -extern crate encoding; -#[cfg(feature = "flate2")] -extern crate flate2; -extern crate h2 as http2; -extern crate num_cpus; -extern crate serde_urlencoded; -#[macro_use] -extern crate percent_encoding; -extern crate serde_json; -extern crate smallvec; -extern crate v_htmlescape; - -extern crate actix_net; -#[macro_use] -extern crate actix as actix_inner; - -#[cfg(test)] -#[macro_use] -extern crate serde_derive; - -#[cfg(feature = "tls")] -extern crate native_tls; -#[cfg(feature = "tls")] -extern crate tokio_tls; - -#[cfg(feature = "openssl")] -extern crate openssl; -#[cfg(feature = "openssl")] -extern crate tokio_openssl; - -#[cfg(feature = "rust-tls")] -extern crate rustls; -#[cfg(feature = "rust-tls")] -extern crate tokio_rustls; -#[cfg(feature = "rust-tls")] -extern crate webpki; -#[cfg(feature = "rust-tls")] -extern crate webpki_roots; - -mod application; -mod body; -mod context; -mod de; -mod extensions; +mod app; mod extractor; -mod handler; -mod header; +pub mod handler; mod helpers; -mod httpcodes; -mod httpmessage; -mod httprequest; -mod httpresponse; -mod info; -mod json; -mod param; -mod payload; -mod pipeline; -mod resource; -mod route; -mod router; -mod scope; -mod uri; -mod with; - -pub mod client; -pub mod error; +// mod info; +pub mod blocking; +pub mod filter; pub mod fs; pub mod middleware; -pub mod multipart; -pub mod pred; -pub mod server; -pub mod test; -pub mod ws; -pub use application::App; -pub use body::{Binary, Body}; -pub use context::HttpContext; -pub use error::{Error, ResponseError, Result}; -pub use extensions::Extensions; -pub use extractor::{Form, Path, Query}; -pub use handler::{ - AsyncResponder, Either, FromRequest, FutureResponse, Responder, State, -}; -pub use httpmessage::HttpMessage; -pub use httprequest::HttpRequest; -pub use httpresponse::HttpResponse; -pub use json::Json; -pub use scope::Scope; -pub use server::Request; +mod request; +mod resource; +mod responder; +mod route; +mod service; +mod state; -pub mod actix { - //! Re-exports [actix's](https://docs.rs/actix/) prelude - pub use super::actix_inner::actors::resolver; - pub use super::actix_inner::actors::signal; - pub use super::actix_inner::fut; - pub use super::actix_inner::msgs; - pub use super::actix_inner::prelude::*; - pub use super::actix_inner::{run, spawn}; -} +// re-export for convenience +pub use actix_http::Response as HttpResponse; +pub use actix_http::{http, Error, HttpMessage, ResponseError}; -#[cfg(feature = "openssl")] -pub(crate) const HAS_OPENSSL: bool = true; -#[cfg(not(feature = "openssl"))] -pub(crate) const HAS_OPENSSL: bool = false; - -#[cfg(feature = "tls")] -pub(crate) const HAS_TLS: bool = true; -#[cfg(not(feature = "tls"))] -pub(crate) const HAS_TLS: bool = false; - -#[cfg(feature = "rust-tls")] -pub(crate) const HAS_RUSTLS: bool = true; -#[cfg(not(feature = "rust-tls"))] -pub(crate) const HAS_RUSTLS: bool = false; +pub use crate::app::App; +pub use crate::extractor::{Form, Json, Path, Query}; +pub use crate::handler::FromRequest; +pub use crate::request::HttpRequest; +pub use crate::resource::Resource; +pub use crate::responder::{Either, Responder}; +pub use crate::service::{ServiceRequest, ServiceResponse}; +pub use crate::state::State; 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::*; - //! ``` - - pub use body::BodyStream; - pub use context::Drain; - pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig, EitherConfig, EitherCollisionStrategy}; - pub use handler::{AsyncResult, Handler}; - pub use httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use httpresponse::HttpResponseBuilder; - pub use info::ConnectionInfo; - pub use json::{JsonBody, JsonConfig}; - pub use param::{FromParam, Params}; - pub use payload::{Payload, PayloadBuffer}; - pub use pipeline::Pipeline; - pub use resource::Resource; - pub use route::Route; - pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; -} - -pub mod http { - //! Various HTTP related types - - // re-exports - pub use modhttp::{Method, StatusCode, Version}; - - #[doc(hidden)] - pub use modhttp::{uri, Error, Extensions, HeaderMap, HttpTryFrom, Uri}; - - pub use cookie::{Cookie, CookieBuilder}; - - pub use helpers::NormalizePath; - - /// Various http headers - pub mod header { - pub use header::*; - pub use header::{ - Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag, - }; - } - pub use header::ContentEncoding; - pub use httpresponse::ConnectionType; + pub use crate::app::AppService; + pub use crate::handler::{AsyncFactory, Extract, Factory, Handle}; + pub use crate::route::{Route, RouteBuilder}; + // pub use crate::info::ConnectionInfo; } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs new file mode 100644 index 00000000..fee17ce1 --- /dev/null +++ b/src/middleware/compress.rs @@ -0,0 +1,443 @@ +use std::io::Write; +use std::str::FromStr; +use std::{cmp, fmt, io}; + +use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; +use actix_http::http::header::{ + ContentEncoding, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, +}; +use actix_http::http::{HttpTryFrom, StatusCode}; +use actix_http::{Error, Head, ResponseHead}; +use actix_service::{IntoNewTransform, Service, Transform}; +use bytes::{Bytes, BytesMut}; +use futures::{Async, Future, Poll}; +use log::trace; + +#[cfg(feature = "brotli")] +use brotli2::write::BrotliEncoder; +#[cfg(feature = "flate2")] +use flate2::write::{GzEncoder, ZlibEncoder}; + +use crate::middleware::MiddlewareFactory; +use crate::service::{ServiceRequest, ServiceResponse}; + +#[derive(Debug, Clone)] +pub struct Compress(ContentEncoding); + +impl Compress { + pub fn new(encoding: ContentEncoding) -> Self { + Compress(encoding) + } +} + +impl Default for Compress { + fn default() -> Self { + Compress::new(ContentEncoding::Auto) + } +} + +impl Transform for Compress +where + P: 'static, + B: MessageBody, + S: Service, Response = ServiceResponse>, + S::Future: 'static, +{ + type Request = ServiceRequest

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

    , srv: &mut S) -> Self::Future { + // negotiate content-encoding + let encoding = if let Some(val) = req.headers.get(ACCEPT_ENCODING) { + if let Ok(enc) = val.to_str() { + AcceptEncoding::parse(enc, self.0) + } else { + ContentEncoding::Identity + } + } else { + ContentEncoding::Identity + }; + + CompressResponse { + encoding, + fut: srv.call(req), + } + } +} + +#[doc(hidden)] +pub struct CompressResponse +where + P: 'static, + B: MessageBody, + S: Service, Response = ServiceResponse>, + S::Future: 'static, +{ + fut: S::Future, + encoding: ContentEncoding, +} + +impl Future for CompressResponse +where + P: 'static, + B: MessageBody, + S: Service, Response = ServiceResponse>, + S::Future: 'static, +{ + type Item = ServiceResponse>; + type Error = S::Error; + + fn poll(&mut self) -> Poll { + let resp = futures::try_ready!(self.fut.poll()); + + Ok(Async::Ready(resp.map_body(move |head, body| { + Encoder::body(self.encoding, head, body) + }))) + } +} + +impl IntoNewTransform, S> for Compress +where + P: 'static, + B: MessageBody, + S: Service, Response = ServiceResponse>, + S::Future: 'static, +{ + fn into_new_transform(self) -> MiddlewareFactory { + MiddlewareFactory::new(self) + } +} + +enum EncoderBody { + Body(B), + Other(Box), + None, +} + +pub struct Encoder { + body: EncoderBody, + encoder: Option, +} + +impl MessageBody for Encoder { + fn length(&self) -> BodyLength { + if self.encoder.is_none() { + match self.body { + EncoderBody::Body(ref b) => b.length(), + EncoderBody::Other(ref b) => b.length(), + EncoderBody::None => BodyLength::Empty, + } + } else { + BodyLength::Stream + } + } + + fn poll_next(&mut self) -> Poll, Error> { + loop { + let result = match self.body { + EncoderBody::Body(ref mut b) => b.poll_next()?, + EncoderBody::Other(ref mut b) => b.poll_next()?, + EncoderBody::None => return Ok(Async::Ready(None)), + }; + match result { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(Some(chunk)) => { + if let Some(ref mut encoder) = self.encoder { + if encoder.write(&chunk)? { + return Ok(Async::Ready(Some(encoder.take()))); + } + } else { + return Ok(Async::Ready(Some(chunk))); + } + } + Async::Ready(None) => { + if let Some(encoder) = self.encoder.take() { + let chunk = encoder.finish()?; + if chunk.is_empty() { + return Ok(Async::Ready(None)); + } else { + return Ok(Async::Ready(Some(chunk))); + } + } else { + return Ok(Async::Ready(None)); + } + } + } + } + } +} + +fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { + head.headers_mut().insert( + CONTENT_ENCODING, + HeaderValue::try_from(Bytes::from_static(encoding.as_str().as_bytes())).unwrap(), + ); +} + +impl Encoder { + fn body( + encoding: ContentEncoding, + head: &mut ResponseHead, + body: ResponseBody, + ) -> ResponseBody> { + let has_ce = head.headers().contains_key(CONTENT_ENCODING); + match body { + ResponseBody::Other(b) => match b { + Body::None => ResponseBody::Other(Body::None), + Body::Empty => ResponseBody::Other(Body::Empty), + Body::Bytes(buf) => { + if !(has_ce + || encoding == ContentEncoding::Identity + || encoding == ContentEncoding::Auto) + { + let mut enc = ContentEncoder::encoder(encoding).unwrap(); + + // TODO return error! + let _ = enc.write(buf.as_ref()); + let body = enc.finish().unwrap(); + update_head(encoding, head); + ResponseBody::Other(Body::Bytes(body)) + } else { + ResponseBody::Other(Body::Bytes(buf)) + } + } + Body::Message(stream) => { + if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS { + ResponseBody::Body(Encoder { + body: EncoderBody::Other(stream), + encoder: None, + }) + } else { + update_head(encoding, head); + head.no_chunking = false; + ResponseBody::Body(Encoder { + body: EncoderBody::Other(stream), + encoder: ContentEncoder::encoder(encoding), + }) + } + } + }, + ResponseBody::Body(stream) => { + if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS { + ResponseBody::Body(Encoder { + body: EncoderBody::Body(stream), + encoder: None, + }) + } else { + update_head(encoding, head); + head.no_chunking = false; + ResponseBody::Body(Encoder { + body: EncoderBody::Body(stream), + encoder: ContentEncoder::encoder(encoding), + }) + } + } + } + } +} + +pub(crate) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer { + buf: BytesMut::with_capacity(8192), + } + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl io::Write for Writer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub(crate) enum ContentEncoder { + #[cfg(feature = "flate2")] + Deflate(ZlibEncoder), + #[cfg(feature = "flate2")] + Gzip(GzEncoder), + #[cfg(feature = "brotli")] + Br(BrotliEncoder), +} + +impl fmt::Debug for ContentEncoder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), + } + } +} + +impl ContentEncoder { + fn encoder(encoding: ContentEncoding) -> Option { + match encoding { + #[cfg(feature = "flate2")] + ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( + Writer::new(), + flate2::Compression::fast(), + ))), + #[cfg(feature = "flate2")] + ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( + Writer::new(), + flate2::Compression::fast(), + ))), + #[cfg(feature = "brotli")] + ContentEncoding::Br => { + Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) + } + _ => None, + } + } + + #[inline] + pub(crate) fn take(&mut self) -> Bytes { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), + } + } + + fn finish(self) -> Result { + match self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(encoder) => match encoder.finish() { + Ok(writer) => Ok(writer.buf.freeze()), + Err(err) => Err(err), + }, + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(encoder) => match encoder.finish() { + Ok(writer) => Ok(writer.buf.freeze()), + Err(err) => Err(err), + }, + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(encoder) => match encoder.finish() { + Ok(writer) => Ok(writer.buf.freeze()), + Err(err) => Err(err), + }, + } + } + + fn write(&mut self, data: &[u8]) -> Result { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Err(err) => { + trace!("Error decoding br encoding: {}", err); + Err(err) + } + }, + #[cfg(feature = "flate2")] + ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Err(err) => { + trace!("Error decoding gzip encoding: {}", err); + Err(err) + } + }, + #[cfg(feature = "flate2")] + ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Err(err) => { + trace!("Error decoding deflate encoding: {}", err); + Err(err) + } + }, + } + } +} + +struct AcceptEncoding { + encoding: ContentEncoding, + quality: f64, +} + +impl Eq for AcceptEncoding {} + +impl Ord for AcceptEncoding { + fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering { + if self.quality > other.quality { + cmp::Ordering::Less + } else if self.quality < other.quality { + cmp::Ordering::Greater + } else { + cmp::Ordering::Equal + } + } +} + +impl PartialOrd for AcceptEncoding { + fn partial_cmp(&self, other: &AcceptEncoding) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for AcceptEncoding { + fn eq(&self, other: &AcceptEncoding) -> bool { + self.quality == other.quality + } +} + +impl AcceptEncoding { + fn new(tag: &str) -> Option { + let parts: Vec<&str> = tag.split(';').collect(); + let encoding = match parts.len() { + 0 => return None, + _ => ContentEncoding::from(parts[0]), + }; + let quality = match parts.len() { + 1 => encoding.quality(), + _ => match f64::from_str(parts[1]) { + Ok(q) => q, + Err(_) => 0.0, + }, + }; + Some(AcceptEncoding { encoding, quality }) + } + + /// Parse a raw Accept-Encoding header value into an ordered list. + pub fn parse(raw: &str, encoding: ContentEncoding) -> ContentEncoding { + let mut encodings: Vec<_> = raw + .replace(' ', "") + .split(',') + .map(|l| AcceptEncoding::new(l)) + .collect(); + encodings.sort(); + + for enc in encodings { + if let Some(enc) = enc { + if encoding == ContentEncoding::Auto { + return enc.encoding; + } else if encoding == enc.encoding { + return encoding; + } + } + } + ContentEncoding::Identity + } +} diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs deleted file mode 100644 index 386d0007..00000000 --- a/src/middleware/cors.rs +++ /dev/null @@ -1,1227 +0,0 @@ -//! Cross-origin resource sharing (CORS) for Actix applications -//! -//! CORS middleware could be used with application and with resource. -//! First you need to construct CORS middleware instance. -//! -//! To construct a cors: -//! -//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -//! 2. Use any of the builder methods to set fields in the backend. -//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -//! constructed backend. -//! -//! Cors middleware could be used as parameter for `App::middleware()` or -//! `Resource::middleware()` methods. But you have to use -//! `Cors::for_app()` method to support *preflight* OPTIONS request. -//! -//! -//! # Example -//! -//! ```rust -//! # extern crate actix_web; -//! use actix_web::middleware::cors::Cors; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; -//! -//! fn index(mut req: HttpRequest) -> &'static str { -//! "Hello world" -//! } -//! -//! fn main() { -//! let app = App::new().configure(|app| { -//! Cors::for_app(app) // <- Construct CORS middleware builder -//! .allowed_origin("https://www.rust-lang.org/") -//! .allowed_methods(vec!["GET", "POST"]) -//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) -//! .allowed_header(http::header::CONTENT_TYPE) -//! .max_age(3600) -//! .resource("/index.html", |r| { -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); -//! }) -//! .register() -//! }); -//! } -//! ``` -//! In this example custom *CORS* middleware get registered for "/index.html" -//! endpoint. -//! -//! Cors middleware automatically handle *OPTIONS* preflight request. -use std::collections::HashSet; -use std::iter::FromIterator; -use std::rc::Rc; - -use http::header::{self, HeaderName, HeaderValue}; -use http::{self, HttpTryFrom, Method, StatusCode, Uri}; - -use application::App; -use error::{ResponseError, Result}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; -use resource::Resource; -use router::ResourceDef; -use server::Request; - -/// A set of errors that can occur during processing CORS -#[derive(Debug, Fail)] -pub enum CorsError { - /// The HTTP request header `Origin` is required but was not provided - #[fail( - display = "The HTTP request header `Origin` is required but was not provided" - )] - MissingOrigin, - /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")] - BadOrigin, - /// The request header `Access-Control-Request-Method` is required but is - /// missing - #[fail( - display = "The request header `Access-Control-Request-Method` is required but is missing" - )] - MissingRequestMethod, - /// The request header `Access-Control-Request-Method` has an invalid value - #[fail( - display = "The request header `Access-Control-Request-Method` has an invalid value" - )] - BadRequestMethod, - /// The request header `Access-Control-Request-Headers` has an invalid - /// value - #[fail( - display = "The request header `Access-Control-Request-Headers` has an invalid value" - )] - BadRequestHeaders, - /// The request header `Access-Control-Request-Headers` is required but is - /// missing. - #[fail( - display = "The request header `Access-Control-Request-Headers` is required but is - missing" - )] - MissingRequestHeaders, - /// Origin is not allowed to make this request - #[fail(display = "Origin is not allowed to make this request")] - OriginNotAllowed, - /// Requested method is not allowed - #[fail(display = "Requested method is not allowed")] - MethodNotAllowed, - /// One or more headers requested are not allowed - #[fail(display = "One or more headers requested are not allowed")] - HeadersNotAllowed, -} - -impl ResponseError for CorsError { - fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self)) - } -} - -/// An enum signifying that some of type T is allowed, or `All` (everything is -/// allowed). -/// -/// `Default` is implemented for this enum and is `All`. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum AllOrSome { - /// Everything is allowed. Usually equivalent to the "*" value. - All, - /// Only some of `T` is allowed - Some(T), -} - -impl Default for AllOrSome { - fn default() -> Self { - AllOrSome::All - } -} - -impl AllOrSome { - /// Returns whether this is an `All` variant - pub fn is_all(&self) -> bool { - match *self { - AllOrSome::All => true, - AllOrSome::Some(_) => false, - } - } - - /// Returns whether this is a `Some` variant - pub fn is_some(&self) -> bool { - !self.is_all() - } - - /// Returns &T - pub fn as_ref(&self) -> Option<&T> { - match *self { - AllOrSome::All => None, - AllOrSome::Some(ref t) => Some(t), - } - } -} - -/// `Middleware` for Cross-origin resource sharing support -/// -/// The Cors struct contains the settings for CORS requests to be validated and -/// for responses to be generated. -#[derive(Clone)] -pub struct Cors { - inner: Rc, -} - -struct Inner { - methods: HashSet, - origins: AllOrSome>, - origins_str: Option, - headers: AllOrSome>, - expose_hdrs: Option, - max_age: Option, - preflight: bool, - send_wildcard: bool, - supports_credentials: bool, - vary_header: bool, -} - -impl Default for Cors { - fn default() -> Cors { - let inner = Inner { - origins: AllOrSome::default(), - origins_str: None, - methods: HashSet::from_iter( - vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ].into_iter(), - ), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }; - Cors { - inner: Rc::new(inner), - } - } -} - -impl Cors { - /// Build a new CORS middleware instance - pub fn build() -> CorsBuilder<()> { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: None, - } - } - - /// Create CorsBuilder for a specified application. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .resource("/resource", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn for_app(app: App) -> CorsBuilder { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: Some(app), - } - } - - /// This method register cors middleware with resource and - /// adds route for *OPTIONS* preflight requests. - /// - /// It is possible to register *Cors* middleware with - /// `Resource::middleware()` method, but in that case *Cors* - /// middleware wont be able to handle *OPTIONS* requests. - pub fn register(self, resource: &mut Resource) { - resource - .method(Method::OPTIONS) - .h(|_: &_| HttpResponse::Ok()); - resource.middleware(self); - } - - fn validate_origin(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ORIGIN) { - if let Ok(origin) = hdr.to_str() { - return match self.inner.origins { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_origins) => allowed_origins - .get(origin) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::OriginNotAllowed), - }; - } - Err(CorsError::BadOrigin) - } else { - return match self.inner.origins { - AllOrSome::All => Ok(()), - _ => Err(CorsError::MissingOrigin), - }; - } - } - - fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { - if let Ok(meth) = hdr.to_str() { - if let Ok(method) = Method::try_from(meth) { - return self - .inner - .methods - .get(&method) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::MethodNotAllowed); - } - } - Err(CorsError::BadRequestMethod) - } else { - Err(CorsError::MissingRequestMethod) - } - } - - fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> { - match self.inner.headers { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - if let Ok(headers) = hdr.to_str() { - let mut hdrs = HashSet::new(); - for hdr in headers.split(',') { - match HeaderName::try_from(hdr.trim()) { - Ok(hdr) => hdrs.insert(hdr), - Err(_) => return Err(CorsError::BadRequestHeaders), - }; - } - - if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { - return Err(CorsError::HeadersNotAllowed); - } - return Ok(()); - } - Err(CorsError::BadRequestHeaders) - } else { - Err(CorsError::MissingRequestHeaders) - } - } - } - } -} - -impl Middleware for Cors { - fn start(&self, req: &HttpRequest) -> Result { - if self.inner.preflight && Method::OPTIONS == *req.method() { - self.validate_origin(req)?; - self.validate_allowed_method(&req)?; - self.validate_allowed_headers(&req)?; - - // allowed headers - let headers = if let Some(headers) = self.inner.headers.as_ref() { - Some( - HeaderValue::try_from( - &headers - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).unwrap(), - ) - } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - Some(hdr.clone()) - } else { - None - }; - - Ok(Started::Response( - HttpResponse::Ok() - .if_some(self.inner.max_age.as_ref(), |max_age, resp| { - let _ = resp.header( - header::ACCESS_CONTROL_MAX_AGE, - format!("{}", max_age).as_str(), - ); - }).if_some(headers, |headers, resp| { - let _ = - resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); - }).if_true(self.inner.origins.is_all(), |resp| { - if self.inner.send_wildcard { - resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - } else { - let origin = req.headers().get(header::ORIGIN).unwrap(); - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - origin.clone(), - ); - } - }).if_true(self.inner.origins.is_some(), |resp| { - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); - }).if_true(self.inner.supports_credentials, |resp| { - resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); - }).header( - header::ACCESS_CONTROL_ALLOW_METHODS, - &self - .inner - .methods - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).finish(), - )) - } else { - // Only check requests with a origin header. - if req.headers().contains_key(header::ORIGIN) { - self.validate_origin(req)?; - } - - Ok(Started::Done) - } - } - - fn response( - &self, req: &HttpRequest, mut resp: HttpResponse, - ) -> Result { - match self.inner.origins { - AllOrSome::All => { - if self.inner.send_wildcard { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - HeaderValue::from_static("*"), - ); - } else if let Some(origin) = req.headers().get(header::ORIGIN) { - resp.headers_mut() - .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); - } - } - AllOrSome::Some(ref origins) => { - if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| { - match o.to_str() { - Ok(os) => origins.contains(os), - _ => false - } - }) { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - origin.clone(), - ); - } else { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone() - ); - }; - } - } - - if let Some(ref expose) = self.inner.expose_hdrs { - resp.headers_mut().insert( - header::ACCESS_CONTROL_EXPOSE_HEADERS, - HeaderValue::try_from(expose.as_str()).unwrap(), - ); - } - if self.inner.supports_credentials { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_CREDENTIALS, - HeaderValue::from_static("true"), - ); - } - if self.inner.vary_header { - let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) { - let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); - val.extend(hdr.as_bytes()); - val.extend(b", Origin"); - HeaderValue::try_from(&val[..]).unwrap() - } else { - HeaderValue::from_static("Origin") - }; - resp.headers_mut().insert(header::VARY, value); - } - Ok(Response::Done(resp)) - } -} - -/// Structure that follows the builder pattern for building `Cors` middleware -/// structs. -/// -/// To construct a cors: -/// -/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -/// 2. Use any of the builder methods to set fields in the backend. -/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -/// constructed backend. -/// -/// # Example -/// -/// ```rust -/// # extern crate http; -/// # extern crate actix_web; -/// use actix_web::middleware::cors; -/// use http::header; -/// -/// # fn main() { -/// let cors = cors::Cors::build() -/// .allowed_origin("https://www.rust-lang.org/") -/// .allowed_methods(vec!["GET", "POST"]) -/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) -/// .allowed_header(header::CONTENT_TYPE) -/// .max_age(3600) -/// .finish(); -/// # } -/// ``` -pub struct CorsBuilder { - cors: Option, - methods: bool, - error: Option, - expose_hdrs: HashSet, - resources: Vec>, - app: Option>, -} - -fn cors<'a>( - parts: &'a mut Option, err: &Option, -) -> Option<&'a mut Inner> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl CorsBuilder { - /// Add an origin that are allowed to make requests. - /// Will be verified against the `Origin` request header. - /// - /// When `All` is set, and `send_wildcard` is set, "*" will be sent in - /// the `Access-Control-Allow-Origin` response header. Otherwise, the - /// client's `Origin` request header will be echoed back in the - /// `Access-Control-Allow-Origin` response header. - /// - /// When `Some` is set, the client's `Origin` request header will be - /// checked in a case-sensitive manner. - /// - /// This is the `list of origins` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `All`. - /// - /// Builder panics if supplied origin is not valid uri. - pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - match Uri::try_from(origin) { - Ok(_) => { - if cors.origins.is_all() { - cors.origins = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut origins) = cors.origins { - origins.insert(origin.to_owned()); - } - } - Err(e) => { - self.error = Some(e.into()); - } - } - } - self - } - - /// Set a list of methods which the allowed origins are allowed to access - /// for requests. - /// - /// This is the `list of methods` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]` - pub fn allowed_methods(&mut self, methods: U) -> &mut CorsBuilder - where - U: IntoIterator, - Method: HttpTryFrom, - { - self.methods = true; - if let Some(cors) = cors(&mut self.cors, &self.error) { - for m in methods { - match Method::try_from(m) { - Ok(method) => { - cors.methods.insert(method); - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - } - self - } - - /// Set an allowed header - pub fn allowed_header(&mut self, header: H) -> &mut CorsBuilder - where - HeaderName: HttpTryFrom, - { - if let Some(cors) = cors(&mut self.cors, &self.error) { - match HeaderName::try_from(header) { - Ok(method) => { - if cors.headers.is_all() { - cors.headers = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut headers) = cors.headers { - headers.insert(method); - } - } - Err(e) => self.error = Some(e.into()), - } - } - self - } - - /// Set a list of header field names which can be used when - /// this resource is accessed by allowed origins. - /// - /// If `All` is set, whatever is requested by the client in - /// `Access-Control-Request-Headers` will be echoed back in the - /// `Access-Control-Allow-Headers` header. - /// - /// This is the `list of headers` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// Defaults to `All`. - pub fn allowed_headers(&mut self, headers: U) -> &mut CorsBuilder - where - U: IntoIterator, - HeaderName: HttpTryFrom, - { - if let Some(cors) = cors(&mut self.cors, &self.error) { - for h in headers { - match HeaderName::try_from(h) { - Ok(method) => { - if cors.headers.is_all() { - cors.headers = AllOrSome::Some(HashSet::new()); - } - if let AllOrSome::Some(ref mut headers) = cors.headers { - headers.insert(method); - } - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - } - self - } - - /// Set a list of headers which are safe to expose to the API of a CORS API - /// specification. This corresponds to the - /// `Access-Control-Expose-Headers` response header. - /// - /// This is the `list of exposed headers` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// This defaults to an empty set. - pub fn expose_headers(&mut self, headers: U) -> &mut CorsBuilder - where - U: IntoIterator, - HeaderName: HttpTryFrom, - { - for h in headers { - match HeaderName::try_from(h) { - Ok(method) => { - self.expose_hdrs.insert(method); - } - Err(e) => { - self.error = Some(e.into()); - break; - } - } - } - self - } - - /// Set a maximum time for which this CORS request maybe cached. - /// This value is set as the `Access-Control-Max-Age` header. - /// - /// This defaults to `None` (unset). - pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.max_age = Some(max_age) - } - self - } - - /// Set a wildcard origins - /// - /// If send wildcard is set and the `allowed_origins` parameter is `All`, a - /// wildcard `Access-Control-Allow-Origin` response header is sent, - /// rather than the request’s `Origin` header. - /// - /// This is the `supports credentials flag` in the - /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). - /// - /// This **CANNOT** be used in conjunction with `allowed_origins` set to - /// `All` and `allow_credentials` set to `true`. Depending on the mode - /// of usage, this will either result in an `Error:: - /// CredentialsWithWildcardOrigin` error during actix launch or runtime. - /// - /// Defaults to `false`. - pub fn send_wildcard(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.send_wildcard = true - } - self - } - - /// Allows users to make authenticated requests - /// - /// If true, injects the `Access-Control-Allow-Credentials` header in - /// responses. This allows cookies and credentials to be submitted - /// across domains. - /// - /// This option cannot be used in conjunction with an `allowed_origin` set - /// to `All` and `send_wildcards` set to `true`. - /// - /// Defaults to `false`. - /// - /// Builder panics if credentials are allowed, but the Origin is set to "*". - /// This is not allowed by W3C - pub fn supports_credentials(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.supports_credentials = true - } - self - } - - /// Disable `Vary` header support. - /// - /// When enabled the header `Vary: Origin` will be returned as per the W3 - /// implementation guidelines. - /// - /// Setting this header when the `Access-Control-Allow-Origin` is - /// dynamically generated (e.g. when there is more than one allowed - /// origin, and an Origin than '*' is returned) informs CDNs and other - /// caches that the CORS headers are dynamic, and cannot be cached. - /// - /// By default `vary` header support is enabled. - pub fn disable_vary_header(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.vary_header = false - } - self - } - - /// Disable *preflight* request support. - /// - /// When enabled cors middleware automatically handles *OPTIONS* request. - /// This is useful application level middleware. - /// - /// By default *preflight* support is enabled. - pub fn disable_preflight(&mut self) -> &mut CorsBuilder { - if let Some(cors) = cors(&mut self.cors, &self.error) { - cors.preflight = false - } - self - } - - /// Configure resource for a specific path. - /// - /// This is similar to a `App::resource()` method. Except, cors middleware - /// get registered for the resource. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .allowed_methods(vec!["GET", "POST"]) - /// .allowed_header(http::header::CONTENT_TYPE) - /// .max_age(3600) - /// .resource("/resource1", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .resource("/resource2", |r| { // register another resource - /// r.method(http::Method::HEAD) - /// .f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource handler - let mut resource = Resource::new(ResourceDef::new(path)); - f(&mut resource); - - self.resources.push(resource); - self - } - - fn construct(&mut self) -> Cors { - if !self.methods { - self.allowed_methods(vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ]); - } - - if let Some(e) = self.error.take() { - panic!("{}", e); - } - - let mut cors = self.cors.take().expect("cannot reuse CorsBuilder"); - - if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() { - panic!("Credentials are allowed, but the Origin is set to \"*\""); - } - - if let AllOrSome::Some(ref origins) = cors.origins { - let s = origins - .iter() - .fold(String::new(), |s, v| format!("{}, {}", s, v)); - cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap()); - } - - if !self.expose_hdrs.is_empty() { - cors.expose_hdrs = Some( - self.expose_hdrs - .iter() - .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..] - .to_owned(), - ); - } - Cors { - inner: Rc::new(cors), - } - } - - /// Finishes building and returns the built `Cors` instance. - /// - /// This method panics in case of any configuration error. - pub fn finish(&mut self) -> Cors { - if !self.resources.is_empty() { - panic!( - "CorsBuilder::resource() was used, - to construct CORS `.register(app)` method should be used" - ); - } - self.construct() - } - - /// Finishes building Cors middleware and register middleware for - /// application - /// - /// This method panics in case of any configuration error or if non of - /// resources are registered. - pub fn register(&mut self) -> App { - if self.resources.is_empty() { - panic!("No resources are registered."); - } - - let cors = self.construct(); - let mut app = self - .app - .take() - .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); - - // register resources - for mut resource in self.resources.drain(..) { - cors.clone().register(&mut resource); - app.register_resource(resource); - } - - app - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::{self, TestRequest}; - - impl Started { - fn is_done(&self) -> bool { - match *self { - Started::Done => true, - _ => false, - } - } - fn response(self) -> HttpResponse { - match self { - Started::Response(resp) => resp, - _ => panic!(), - } - } - } - impl Response { - fn response(self) -> HttpResponse { - match self { - Response::Done(resp) => resp, - _ => panic!(), - } - } - } - - #[test] - #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] - fn cors_validates_illegal_allow_credentials() { - Cors::build() - .supports_credentials() - .send_wildcard() - .finish(); - } - - #[test] - #[should_panic(expected = "No resources are registered")] - fn no_resource() { - Cors::build() - .supports_credentials() - .send_wildcard() - .register(); - } - - #[test] - #[should_panic(expected = "Cors::for_app(app)")] - fn no_resource2() { - Cors::build() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register(); - } - - #[test] - fn validate_origin_allows_all_origins() { - let cors = Cors::default(); - let req = TestRequest::with_header("Origin", "https://www.example.com").finish(); - - assert!(cors.start(&req).ok().unwrap().is_done()) - } - - #[test] - fn test_preflight() { - let mut cors = Cors::build() - .send_wildcard() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) - .allowed_header(header::CONTENT_TYPE) - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - - assert!(cors.start(&req).is_err()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") - .method(Method::OPTIONS) - .finish(); - - assert!(cors.start(&req).is_err()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ).method(Method::OPTIONS) - .finish(); - - let resp = cors.start(&req).unwrap().response(); - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"3600"[..], - resp.headers() - .get(header::ACCESS_CONTROL_MAX_AGE) - .unwrap() - .as_bytes() - ); - //assert_eq!( - // &b"authorization,accept,content-type"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap(). - // as_bytes()); assert_eq!( - // &b"POST,GET,OPTIONS"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap(). - // as_bytes()); - - Rc::get_mut(&mut cors.inner).unwrap().preflight = false; - assert!(cors.start(&req).unwrap().is_done()); - } - - // #[test] - // #[should_panic(expected = "MissingOrigin")] - // fn test_validate_missing_origin() { - // let cors = Cors::build() - // .allowed_origin("https://www.example.com") - // .finish(); - // let mut req = HttpRequest::default(); - // cors.start(&req).unwrap(); - // } - - #[test] - #[should_panic(expected = "OriginNotAllowed")] - fn test_validate_not_allowed_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.unknown.com") - .method(Method::GET) - .finish(); - cors.start(&req).unwrap(); - } - - #[test] - fn test_validate_origin() { - let cors = Cors::build() - .allowed_origin("https://www.example.com") - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::GET) - .finish(); - - assert!(cors.start(&req).unwrap().is_done()); - } - - #[test] - fn test_no_origin_response() { - let cors = Cors::build().finish(); - - let req = TestRequest::default().method(Method::GET).finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert!( - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_none() - ); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"https://www.example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - } - - #[test] - fn test_response() { - let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let cors = Cors::build() - .send_wildcard() - .disable_preflight() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(exposed_headers.clone()) - .expose_headers(exposed_headers.clone()) - .allowed_header(header::CONTENT_TYPE) - .finish(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .finish(); - - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - { - let headers = resp - .headers() - .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) - .unwrap() - .to_str() - .unwrap() - .split(',') - .map(|s| s.trim()) - .collect::>(); - - for h in exposed_headers { - assert!(headers.contains(&h.as_str())); - } - } - - let resp: HttpResponse = - HttpResponse::Ok().header(header::VARY, "Accept").finish(); - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"Accept, Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - let cors = Cors::build() - .disable_vary_header() - .allowed_origin("https://www.example.com") - .allowed_origin("https://www.google.com") - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - - let origins_str = resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!( - "https://www.example.com", - origins_str - ); - } - - #[test] - fn cors_resource() { - let mut srv = test::TestServer::with_factory(|| { - App::new().configure(|app| { - Cors::for_app(app) - .allowed_origin("https://www.example.com") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register() - }) - }); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example2.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - } - - #[test] - fn test_multiple_origins() { - let cors = Cors::build() - .allowed_origin("https://example.com") - .allowed_origin("https://example.org") - .allowed_methods(vec![Method::GET]) - .finish(); - - - let req = TestRequest::with_header("Origin", "https://example.com") - .method(Method::GET) - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - - let resp = cors.response(&req, resp).unwrap().response(); - print!("{:?}", resp); - assert_eq!( - &b"https://example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - - let req = TestRequest::with_header("Origin", "https://example.org") - .method(Method::GET) - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - - let resp = cors.response(&req, resp).unwrap().response(); - assert_eq!( - &b"https://example.org"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - } -} diff --git a/src/middleware/csrf.rs b/src/middleware/csrf.rs deleted file mode 100644 index cacfc8d5..00000000 --- a/src/middleware/csrf.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! A filter for cross-site request forgery (CSRF). -//! -//! This middleware is stateless and [based on request -//! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers). -//! -//! By default requests are allowed only if one of these is true: -//! -//! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the -//! applications responsibility to ensure these methods cannot be used to -//! execute unwanted actions. Note that upgrade requests for websockets are -//! also considered safe. -//! * The `Origin` header (added automatically by the browser) matches one -//! of the allowed origins. -//! * There is no `Origin` header but the `Referer` header matches one of -//! the allowed origins. -//! -//! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr) -//! if you want to allow requests with unprotected methods via -//! [CORS](../cors/struct.Cors.html). -//! -//! # Example -//! -//! ``` -//! # extern crate actix_web; -//! use actix_web::middleware::csrf; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; -//! -//! fn handle_post(_: &HttpRequest) -> &'static str { -//! "This action should only be triggered with requests from the same site" -//! } -//! -//! fn main() { -//! let app = App::new() -//! .middleware( -//! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"), -//! ) -//! .resource("/", |r| { -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::POST).f(handle_post); -//! }) -//! .finish(); -//! } -//! ``` -//! -//! In this example the entire application is protected from CSRF. - -use std::borrow::Cow; -use std::collections::HashSet; - -use bytes::Bytes; -use error::{ResponseError, Result}; -use http::{header, HeaderMap, HttpTryFrom, Uri}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Started}; -use server::Request; - -/// Potential cross-site request forgery detected. -#[derive(Debug, Fail)] -pub enum CsrfError { - /// The HTTP request header `Origin` was required but not provided. - #[fail(display = "Origin header required")] - MissingOrigin, - /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display = "Could not parse Origin header")] - BadOrigin, - /// The cross-site request was denied. - #[fail(display = "Cross-site request denied")] - CsrDenied, -} - -impl ResponseError for CsrfError { - fn error_response(&self) -> HttpResponse { - HttpResponse::Forbidden().body(self.to_string()) - } -} - -fn uri_origin(uri: &Uri) -> Option { - match (uri.scheme_part(), uri.host(), uri.port_part().map(|port| port.as_u16())) { - (Some(scheme), Some(host), Some(port)) => { - Some(format!("{}://{}:{}", scheme, host, port)) - } - (Some(scheme), Some(host), None) => Some(format!("{}://{}", scheme, host)), - _ => None, - } -} - -fn origin(headers: &HeaderMap) -> Option, CsrfError>> { - headers - .get(header::ORIGIN) - .map(|origin| { - origin - .to_str() - .map_err(|_| CsrfError::BadOrigin) - .map(|o| o.into()) - }).or_else(|| { - headers.get(header::REFERER).map(|referer| { - Uri::try_from(Bytes::from(referer.as_bytes())) - .ok() - .as_ref() - .and_then(uri_origin) - .ok_or(CsrfError::BadOrigin) - .map(|o| o.into()) - }) - }) -} - -/// A middleware that filters cross-site requests. -/// -/// To construct a CSRF filter: -/// -/// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to -/// start building. -/// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed -/// origins. -/// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve -/// the constructed filter. -/// -/// # Example -/// -/// ``` -/// use actix_web::middleware::csrf; -/// use actix_web::App; -/// -/// # fn main() { -/// let app = App::new() -/// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com")); -/// # } -/// ``` -#[derive(Default)] -pub struct CsrfFilter { - origins: HashSet, - allow_xhr: bool, - allow_missing_origin: bool, - allow_upgrade: bool, -} - -impl CsrfFilter { - /// Start building a `CsrfFilter`. - pub fn new() -> CsrfFilter { - CsrfFilter { - origins: HashSet::new(), - allow_xhr: false, - allow_missing_origin: false, - allow_upgrade: false, - } - } - - /// Add an origin that is allowed to make requests. Will be verified - /// against the `Origin` request header. - pub fn allowed_origin>(mut self, origin: T) -> CsrfFilter { - self.origins.insert(origin.into()); - self - } - - /// Allow all requests with an `X-Requested-With` header. - /// - /// A cross-site attacker should not be able to send requests with custom - /// headers unless a CORS policy whitelists them. Therefore it should be - /// safe to allow requests with an `X-Requested-With` header (added - /// automatically by many JavaScript libraries). - /// - /// This is disabled by default, because in Safari it is possible to - /// circumvent this using redirects and Flash. - /// - /// Use this method to enable more lax filtering. - pub fn allow_xhr(mut self) -> CsrfFilter { - self.allow_xhr = true; - self - } - - /// Allow requests if the expected `Origin` header is missing (and - /// there is no `Referer` to fall back on). - /// - /// The filter is conservative by default, but it should be safe to allow - /// missing `Origin` headers because a cross-site attacker cannot prevent - /// the browser from sending `Origin` on unprotected requests. - pub fn allow_missing_origin(mut self) -> CsrfFilter { - self.allow_missing_origin = true; - self - } - - /// Allow cross-site upgrade requests (for example to open a WebSocket). - pub fn allow_upgrade(mut self) -> CsrfFilter { - self.allow_upgrade = true; - self - } - - fn validate(&self, req: &Request) -> Result<(), CsrfError> { - let is_upgrade = req.headers().contains_key(header::UPGRADE); - let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade); - - if is_safe || (self.allow_xhr && req.headers().contains_key("x-requested-with")) - { - Ok(()) - } else if let Some(header) = origin(req.headers()) { - match header { - Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()), - Ok(_) => Err(CsrfError::CsrDenied), - Err(err) => Err(err), - } - } else if self.allow_missing_origin { - Ok(()) - } else { - Err(CsrfError::MissingOrigin) - } - } -} - -impl Middleware for CsrfFilter { - fn start(&self, req: &HttpRequest) -> Result { - self.validate(req)?; - Ok(Started::Done) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::Method; - use test::TestRequest; - - #[test] - fn test_safe() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header("Origin", "https://www.w3.org") - .method(Method::HEAD) - .finish(); - - assert!(csrf.start(&req).is_ok()); - } - - #[test] - fn test_csrf() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header("Origin", "https://www.w3.org") - .method(Method::POST) - .finish(); - - assert!(csrf.start(&req).is_err()); - } - - #[test] - fn test_referer() { - let csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let req = TestRequest::with_header( - "Referer", - "https://www.example.com/some/path?query=param", - ).method(Method::POST) - .finish(); - - assert!(csrf.start(&req).is_ok()); - } - - #[test] - fn test_upgrade() { - let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com"); - - let lax_csrf = CsrfFilter::new() - .allowed_origin("https://www.example.com") - .allow_upgrade(); - - let req = TestRequest::with_header("Origin", "https://cswsh.com") - .header("Connection", "Upgrade") - .header("Upgrade", "websocket") - .method(Method::GET) - .finish(); - - assert!(strict_csrf.start(&req).is_err()); - assert!(lax_csrf.start(&req).is_ok()); - } -} diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index a33fa6a3..1ace34fe 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -1,17 +1,19 @@ -//! Default response headers -use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; -use http::{HeaderMap, HttpTryFrom}; +//! Middleware for setting default response headers +use std::rc::Rc; -use error::Result; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response}; +use actix_http::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; +use actix_http::http::{HeaderMap, HttpTryFrom}; +use actix_service::{IntoNewTransform, Service, Transform}; +use futures::{Async, Future, Poll}; + +use crate::middleware::MiddlewareFactory; +use crate::service::{ServiceRequest, ServiceResponse}; /// `Middleware` for setting default response headers. /// /// This middleware does not set header if response headers already contains it. /// -/// ```rust +/// ```rust,ignore /// # extern crate actix_web; /// use actix_web::{http, middleware, App, HttpResponse}; /// @@ -22,11 +24,15 @@ use middleware::{Middleware, Response}; /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); /// r.method(http::Method::HEAD) /// .f(|_| HttpResponse::MethodNotAllowed()); -/// }) -/// .finish(); +/// }); /// } /// ``` +#[derive(Clone)] pub struct DefaultHeaders { + inner: Rc, +} + +struct Inner { ct: bool, headers: HeaderMap, } @@ -34,8 +40,10 @@ pub struct DefaultHeaders { impl Default for DefaultHeaders { fn default() -> Self { DefaultHeaders { - ct: false, - headers: HeaderMap::new(), + inner: Rc::new(Inner { + ct: false, + headers: HeaderMap::new(), + }), } } } @@ -48,16 +56,19 @@ impl DefaultHeaders { /// Set a header. #[inline] - #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, HeaderValue: HttpTryFrom, { + #[allow(clippy::match_wild_err_arm)] match HeaderName::try_from(key) { Ok(key) => match HeaderValue::try_from(value) { Ok(value) => { - self.headers.append(key, value); + Rc::get_mut(&mut self.inner) + .expect("Multiple copies exist") + .headers + .append(key, value); } Err(_) => panic!("Can not create header value"), }, @@ -68,53 +79,85 @@ impl DefaultHeaders { /// Set *CONTENT-TYPE* header if response does not contain this header. pub fn content_type(mut self) -> Self { - self.ct = true; + Rc::get_mut(&mut self.inner) + .expect("Multiple copies exist") + .ct = true; self } } -impl Middleware for DefaultHeaders { - fn response(&self, _: &HttpRequest, mut resp: HttpResponse) -> Result { - for (key, value) in self.headers.iter() { - if !resp.headers().contains_key(key) { - resp.headers_mut().insert(key, value.clone()); +impl IntoNewTransform, S> + for DefaultHeaders +where + S: Service, Response = ServiceResponse>, + S::Future: 'static, +{ + fn into_new_transform(self) -> MiddlewareFactory { + MiddlewareFactory::new(self) + } +} + +impl Transform for DefaultHeaders +where + S: Service, Response = ServiceResponse>, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: ServiceRequest, srv: &mut S) -> Self::Future { + let inner = self.inner.clone(); + + Box::new(srv.call(req).map(move |mut res| { + // set response headers + for (key, value) in inner.headers.iter() { + if !res.headers().contains_key(key) { + res.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"), - ); - } - Ok(Response::Done(resp)) + // default content-type + if inner.ct && !res.headers().contains_key(CONTENT_TYPE) { + res.headers_mut().insert( + CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ); + } + + res + })) } } -#[cfg(test)] -mod tests { - use super::*; - use http::header::CONTENT_TYPE; - use test::TestRequest; +// #[cfg(test)] +// mod tests { +// use super::*; +// use actix_http::http::header::CONTENT_TYPE; +// use actix_http::test::TestRequest; - #[test] - fn test_default_headers() { - let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); +// #[test] +// fn test_default_headers() { +// let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); - let req = TestRequest::default().finish(); +// let req = TestRequest::default().finish(); - let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); +// let resp = Response::Ok().finish(); +// let resp = match mw.response(&req, resp) { +// Ok(Response::Done(resp)) => resp, +// _ => panic!(), +// }; +// assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(); - let resp = match mw.response(&req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); - } -} +// let resp = Response::Ok().header(CONTENT_TYPE, "0002").finish(); +// let resp = match mw.response(&req, resp) { +// Ok(Response::Done(resp)) => resp, +// _ => panic!(), +// }; +// assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); +// } +// } diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs deleted file mode 100644 index c7d19d33..00000000 --- a/src/middleware/errhandlers.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::collections::HashMap; - -use error::Result; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response}; - -type ErrorHandler = Fn(&HttpRequest, HttpResponse) -> Result; - -/// `Middleware` for allowing custom handlers for responses. -/// -/// You can use `ErrorHandlers::handler()` method to register a custom error -/// handler for specific status code. You can modify existing response or -/// create completely new one. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::{ErrorHandlers, Response}; -/// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; -/// -/// fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { -/// let mut builder = resp.into_builder(); -/// builder.header(http::header::CONTENT_TYPE, "application/json"); -/// Ok(Response::Done(builder.into())) -/// } -/// -/// fn main() { -/// let app = App::new() -/// .middleware( -/// ErrorHandlers::new() -/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), -/// ) -/// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD) -/// .f(|_| HttpResponse::MethodNotAllowed()); -/// }) -/// .finish(); -/// } -/// ``` -pub struct ErrorHandlers { - handlers: HashMap>>, -} - -impl Default for ErrorHandlers { - fn default() -> Self { - ErrorHandlers { - handlers: HashMap::new(), - } - } -} - -impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance - pub fn new() -> Self { - ErrorHandlers::default() - } - - /// Register error handler for specified status code - pub fn handler(mut self, status: StatusCode, handler: F) -> Self - where - F: Fn(&HttpRequest, HttpResponse) -> Result + 'static, - { - self.handlers.insert(status, Box::new(handler)); - self - } -} - -impl Middleware for ErrorHandlers { - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(handler) = self.handlers.get(&resp.status()) { - handler(req, resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use error::{Error, ErrorInternalServerError}; - use http::header::CONTENT_TYPE; - use http::StatusCode; - use httpmessage::HttpMessage; - use middleware::Started; - use test::{self, TestRequest}; - - fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { - let mut builder = resp.into_builder(); - builder.header(CONTENT_TYPE, "0001"); - Ok(Response::Done(builder.into())) - } - - #[test] - fn test_handler() { - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - - let mut req = TestRequest::default().finish(); - let resp = HttpResponse::InternalServerError().finish(); - let resp = match mw.response(&mut req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - - let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&mut req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert!(!resp.headers().contains_key(CONTENT_TYPE)); - } - - struct MiddlewareOne; - - impl Middleware for MiddlewareOne { - fn start(&self, _: &HttpRequest) -> Result { - Err(ErrorInternalServerError("middleware error")) - } - } - - #[test] - fn test_middleware_start_error() { - let mut srv = test::TestServer::new(move |app| { - app.middleware( - ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), - ).middleware(MiddlewareOne) - .handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001"); - } -} diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs deleted file mode 100644 index a664ba1f..00000000 --- a/src/middleware/identity.rs +++ /dev/null @@ -1,399 +0,0 @@ -//! Request identity service for Actix applications. -//! -//! [**IdentityService**](struct.IdentityService.html) middleware can be -//! used with different policies types to store identity information. -//! -//! By default, only cookie identity policy is implemented. Other backend -//! implementations can be added separately. -//! -//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) -//! uses cookies as identity storage. -//! -//! To access current request identity -//! [**RequestIdentity**](trait.RequestIdentity.html) should be used. -//! *HttpRequest* implements *RequestIdentity* trait. -//! -//! ```rust -//! use actix_web::middleware::identity::RequestIdentity; -//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -//! use actix_web::*; -//! -//! fn index(req: HttpRequest) -> Result { -//! // access request identity -//! if let Some(id) = req.identity() { -//! Ok(format!("Welcome! {}", id)) -//! } else { -//! Ok("Welcome Anonymous!".to_owned()) -//! } -//! } -//! -//! fn login(mut req: HttpRequest) -> HttpResponse { -//! req.remember("User1".to_owned()); // <- remember identity -//! HttpResponse::Ok().finish() -//! } -//! -//! fn logout(mut req: HttpRequest) -> HttpResponse { -//! req.forget(); // <- remove identity -//! HttpResponse::Ok().finish() -//! } -//! -//! fn main() { -//! let app = App::new().middleware(IdentityService::new( -//! // <- create identity middleware -//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend -//! .name("auth-cookie") -//! .secure(false), -//! )); -//! } -//! ``` -use std::rc::Rc; - -use cookie::{Cookie, CookieJar, Key, SameSite}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use time::Duration; - -use error::{Error, Result}; -use http::header::{self, HeaderValue}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; - -/// The helper trait to obtain your identity from a request. -/// -/// ```rust -/// use actix_web::middleware::identity::RequestIdentity; -/// use actix_web::*; -/// -/// fn index(req: HttpRequest) -> Result { -/// // access request identity -/// if let Some(id) = req.identity() { -/// Ok(format!("Welcome! {}", id)) -/// } else { -/// Ok("Welcome Anonymous!".to_owned()) -/// } -/// } -/// -/// fn login(mut req: HttpRequest) -> HttpResponse { -/// req.remember("User1".to_owned()); // <- remember identity -/// HttpResponse::Ok().finish() -/// } -/// -/// fn logout(mut req: HttpRequest) -> HttpResponse { -/// req.forget(); // <- remove identity -/// HttpResponse::Ok().finish() -/// } -/// # fn main() {} -/// ``` -pub trait RequestIdentity { - /// Return the claimed identity of the user associated request or - /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option; - - /// Remember identity. - fn remember(&self, identity: String); - - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&self); -} - -impl RequestIdentity for HttpRequest { - fn identity(&self) -> Option { - if let Some(id) = self.extensions().get::() { - return id.0.identity().map(|s| s.to_owned()); - } - None - } - - fn remember(&self, identity: String) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.as_mut().remember(identity); - } - } - - fn forget(&self) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.forget(); - } - } -} - -/// An identity -pub trait Identity: 'static { - /// Return the claimed identity of the user associated request or - /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option<&str>; - - /// Remember identity. - fn remember(&mut self, key: String); - - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&mut self); - - /// Write session to storage backend. - fn write(&mut self, resp: HttpResponse) -> Result; -} - -/// Identity policy definition. -pub trait IdentityPolicy: Sized + 'static { - /// The associated identity - type Identity: Identity; - - /// The return type of the middleware - type Future: Future; - - /// Parse the session from request and load data from a service identity. - fn from_request(&self, request: &HttpRequest) -> Self::Future; -} - -/// Request identity middleware -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(IdentityService::new( -/// // <- create identity middleware -/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend -/// .name("auth-cookie") -/// .secure(false), -/// )); -/// } -/// ``` -pub struct IdentityService { - backend: T, -} - -impl IdentityService { - /// Create new identity service with specified backend. - pub fn new(backend: T) -> Self { - IdentityService { backend } - } -} - -struct IdentityBox(Box); - -impl> Middleware for IdentityService { - fn start(&self, req: &HttpRequest) -> Result { - let req = req.clone(); - let fut = self.backend.from_request(&req).then(move |res| match res { - Ok(id) => { - req.extensions_mut().insert(IdentityBox(Box::new(id))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(ref mut id) = req.extensions_mut().get_mut::() { - id.0.as_mut().write(resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -#[doc(hidden)] -/// Identity that uses private cookies as identity storage. -pub struct CookieIdentity { - changed: bool, - identity: Option, - inner: Rc, -} - -impl Identity for CookieIdentity { - fn identity(&self) -> Option<&str> { - self.identity.as_ref().map(|s| s.as_ref()) - } - - fn remember(&mut self, value: String) { - self.changed = true; - self.identity = Some(value); - } - - fn forget(&mut self) { - self.changed = true; - self.identity = None; - } - - fn write(&mut self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, self.identity.take()); - } - Ok(Response::Done(resp)) - } -} - -struct CookieIdentityInner { - key: Key, - name: String, - path: String, - domain: Option, - secure: bool, - max_age: Option, - same_site: Option, -} - -impl CookieIdentityInner { - fn new(key: &[u8]) -> CookieIdentityInner { - CookieIdentityInner { - key: Key::from_master(key), - name: "actix-identity".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - max_age: None, - same_site: None, - } - } - - fn set_cookie(&self, resp: &mut HttpResponse, id: Option) -> Result<()> { - let some = id.is_some(); - { - let id = id.unwrap_or_else(String::new); - let mut cookie = Cookie::new(self.name.clone(), id); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(true); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - if let Some(same_site) = self.same_site { - cookie.set_same_site(same_site); - } - - let mut jar = CookieJar::new(); - if some { - jar.private(&self.key).add(cookie); - } else { - jar.add_original(cookie.clone()); - jar.private(&self.key).remove(cookie); - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - } - - Ok(()) - } - - fn load(&self, req: &HttpRequest) -> Option { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); - - let cookie_opt = jar.private(&self.key).get(&self.name); - if let Some(cookie) = cookie_opt { - return Some(cookie.value().into()); - } - } - } - } - None - } -} - -/// Use cookies for request identity storage. -/// -/// The constructors take a key as an argument. -/// This is the private key for cookie - when this value is changed, -/// all identities are lost. The constructors will panic if the key is less -/// than 32 bytes in length. -/// -/// # Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(IdentityService::new( -/// // <- create identity middleware -/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy -/// .domain("www.rust-lang.org") -/// .name("actix_auth") -/// .path("/") -/// .secure(true), -/// )); -/// } -/// ``` -pub struct CookieIdentityPolicy(Rc); - -impl CookieIdentityPolicy { - /// Construct new `CookieIdentityPolicy` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn new(key: &[u8]) -> CookieIdentityPolicy { - CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key))) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().path = value.into(); - self - } - - /// Sets the `name` field in the session cookie being built. - pub fn name>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().name = value.into(); - self - } - - /// Sets the `domain` field in the session cookie being built. - pub fn domain>(mut self, value: S) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } - - /// Sets the `same_site` field in the session cookie being built. - pub fn same_site(mut self, same_site: SameSite) -> Self { - Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site); - self - } -} - -impl IdentityPolicy for CookieIdentityPolicy { - type Identity = CookieIdentity; - type Future = FutureResult; - - fn from_request(&self, req: &HttpRequest) -> Self::Future { - let identity = self.0.load(req); - FutOk(CookieIdentity { - identity, - changed: false, - inner: Rc::clone(&self.0), - }) - } -} diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs deleted file mode 100644 index b7bb1bb8..00000000 --- a/src/middleware/logger.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Request logging middleware -use std::collections::HashSet; -use std::env; -use std::fmt::{self, Display, Formatter}; - -use regex::Regex; -use time; - -use error::Result; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Finished, Middleware, Started}; - -/// `Middleware` for logging request and response info to the terminal. -/// -/// `Logger` middleware uses standard log crate to log information. You should -/// enable logger for `actix_web` package to see access log. -/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar) -/// -/// ## Usage -/// -/// Create `Logger` middleware with the specified `format`. -/// Default `Logger` could be created with `default` method, it uses the -/// default format: -/// -/// ```ignore -/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T -/// ``` -/// ```rust -/// # extern crate actix_web; -/// extern crate env_logger; -/// use actix_web::middleware::Logger; -/// use actix_web::App; -/// -/// fn main() { -/// std::env::set_var("RUST_LOG", "actix_web=info"); -/// env_logger::init(); -/// -/// let app = App::new() -/// .middleware(Logger::default()) -/// .middleware(Logger::new("%a %{User-Agent}i")) -/// .finish(); -/// } -/// ``` -/// -/// ## 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 -/// -/// `%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'] -/// -pub struct Logger { - format: Format, - exclude: HashSet, -} - -impl Logger { - /// Create `Logger` middleware with the specified `format`. - pub fn new(format: &str) -> Logger { - Logger { - format: Format::new(format), - exclude: HashSet::new(), - } - } - - /// Ignore and do not log access info for specified path. - pub fn exclude>(mut self, path: T) -> Self { - self.exclude.insert(path.into()); - self - } -} - -impl Default for Logger { - /// Create `Logger` middleware with format: - /// - /// ```ignore - /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T - /// ``` - fn default() -> Logger { - Logger { - format: Format::default(), - exclude: HashSet::new(), - } - } -} - -struct StartTime(time::Tm); - -impl Logger { - fn log(&self, req: &HttpRequest, resp: &HttpResponse) { - if let Some(entry_time) = req.extensions().get::() { - let render = |fmt: &mut Formatter| { - for unit in &self.format.0 { - unit.render(fmt, req, resp, entry_time.0)?; - } - Ok(()) - }; - info!("{}", FormatDisplay(&render)); - } - } -} - -impl Middleware for Logger { - fn start(&self, req: &HttpRequest) -> Result { - if !self.exclude.contains(req.path()) { - req.extensions_mut().insert(StartTime(time::now())); - } - Ok(Started::Done) - } - - fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { - self.log(req, resp); - Finished::Done - } -} - -/// A formatting style for the `Logger`, consisting of multiple -/// `FormatText`s concatenated into one line. -#[derive(Clone)] -#[doc(hidden)] -struct Format(Vec); - -impl Default for Format { - /// Return the default formatting style for the `Logger`: - fn default() -> Format { - Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#) - } -} - -impl Format { - /// Create a `Format` from a format string. - /// - /// Returns `None` if the format string syntax is incorrect. - pub fn new(s: &str) -> Format { - trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap(); - - let mut idx = 0; - let mut results = Vec::new(); - for cap in fmt.captures_iter(s) { - let m = cap.get(0).unwrap(); - let pos = m.start(); - if idx != pos { - results.push(FormatText::Str(s[idx..pos].to_owned())); - } - idx = m.end(); - - if let Some(key) = cap.get(2) { - results.push(match cap.get(3).unwrap().as_str() { - "i" => FormatText::RequestHeader(key.as_str().to_owned()), - "o" => FormatText::ResponseHeader(key.as_str().to_owned()), - "e" => FormatText::EnvironHeader(key.as_str().to_owned()), - _ => unreachable!(), - }) - } else { - let m = cap.get(1).unwrap(); - results.push(match m.as_str() { - "%" => FormatText::Percent, - "a" => FormatText::RemoteAddr, - "t" => FormatText::RequestTime, - "r" => FormatText::RequestLine, - "s" => FormatText::ResponseStatus, - "b" => FormatText::ResponseSize, - "T" => FormatText::Time, - "D" => FormatText::TimeMillis, - _ => FormatText::Str(m.as_str().to_owned()), - }); - } - } - if idx != s.len() { - results.push(FormatText::Str(s[idx..].to_owned())); - } - - Format(results) - } -} - -/// A string of text to be logged. This is either one of the data -/// fields supported by the `Logger`, or a custom `String`. -#[doc(hidden)] -#[derive(Debug, Clone)] -pub enum FormatText { - Str(String), - Percent, - RequestLine, - RequestTime, - ResponseStatus, - ResponseSize, - Time, - TimeMillis, - RemoteAddr, - RequestHeader(String), - ResponseHeader(String), - EnvironHeader(String), -} - -impl FormatText { - 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), - FormatText::Percent => "%".fmt(fmt), - FormatText::RequestLine => { - if req.query_string().is_empty() { - fmt.write_fmt(format_args!( - "{} {} {:?}", - req.method(), - req.path(), - req.version() - )) - } else { - fmt.write_fmt(format_args!( - "{} {}?{} {:?}", - req.method(), - req.path(), - req.query_string(), - req.version() - )) - } - } - FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt), - FormatText::ResponseSize => resp.response_size().fmt(fmt), - FormatText::Time => { - let rt = time::now() - entry_time; - let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0; - fmt.write_fmt(format_args!("{:.6}", rt)) - } - FormatText::TimeMillis => { - let rt = time::now() - entry_time; - let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0; - fmt.write_fmt(format_args!("{:.6}", rt)) - } - FormatText::RemoteAddr => { - if let Some(remote) = req.connection_info().remote() { - return remote.fmt(fmt); - } else { - "-".fmt(fmt) - } - } - FormatText::RequestTime => entry_time - .strftime("[%d/%b/%Y:%H:%M:%S %z]") - .unwrap() - .fmt(fmt), - FormatText::RequestHeader(ref name) => { - let s = if let Some(val) = req.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - fmt.write_fmt(format_args!("{}", s)) - } - FormatText::ResponseHeader(ref name) => { - let s = if let Some(val) = resp.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - fmt.write_fmt(format_args!("{}", s)) - } - FormatText::EnvironHeader(ref name) => { - if let Ok(val) = env::var(name) { - fmt.write_fmt(format_args!("{}", val)) - } else { - "-".fmt(fmt) - } - } - } - } -} - -pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>); - -impl<'a> fmt::Display for FormatDisplay<'a> { - fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { - (self.0)(fmt) - } -} - -#[cfg(test)] -mod tests { - use time; - - use super::*; - use http::{header, StatusCode}; - use test::TestRequest; - - #[test] - fn test_logger() { - let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ).finish(); - let resp = HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") - .force_close() - .finish(); - - match logger.start(&req) { - Ok(Started::Done) => (), - _ => panic!(), - }; - match logger.finish(&req, &resp) { - Finished::Done => (), - _ => panic!(), - } - let entry_time = time::now(); - let render = |fmt: &mut Formatter| { - for unit in &logger.format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("ACTIX-WEB ttt")); - } - - #[test] - fn test_default_format() { - let format = Format::default(); - - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ).finish(); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - let entry_time = time::now(); - - let render = |fmt: &mut Formatter| { - for unit in &format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET / HTTP/1.1")); - assert!(s.contains("200 0")); - assert!(s.contains("ACTIX-WEB")); - - let req = TestRequest::with_uri("/?test").finish(); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - let entry_time = time::now(); - - let render = |fmt: &mut Formatter| { - for unit in &format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET /?test HTTP/1.1")); - } -} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index c69dbb3e..a8b4b3c6 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,68 +1,65 @@ -//! Middlewares -use futures::Future; +use std::marker::PhantomData; -use error::{Error, Result}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; +use actix_service::{NewTransform, Service, Transform}; +use futures::future::{ok, FutureResult}; -mod logger; +#[cfg(any(feature = "brotli", feature = "flate2"))] +mod compress; +#[cfg(any(feature = "brotli", feature = "flate2"))] +pub use self::compress::Compress; -pub mod cors; -pub mod csrf; mod defaultheaders; -mod errhandlers; -#[cfg(feature = "session")] -pub mod identity; -#[cfg(feature = "session")] -pub mod session; pub use self::defaultheaders::DefaultHeaders; -pub use self::errhandlers::ErrorHandlers; -pub use self::logger::Logger; -/// Middleware start result -pub enum Started { - /// Middleware is completed, continue to next middleware - Done, - /// New http response got generated. If middleware generates response - /// handler execution halts. - Response(HttpResponse), - /// Execution completed, runs future to completion. - Future(Box, Error = Error>>), +/// Helper for middleware service factory +pub struct MiddlewareFactory +where + T: Transform + Clone, + S: Service, +{ + tr: T, + _t: PhantomData, } -/// Middleware execution result -pub enum Response { - /// New http response got generated - Done(HttpResponse), - /// Result is a future that resolves to a new http response - Future(Box>), -} - -/// Middleware finish result -pub enum Finished { - /// Execution completed - Done, - /// Execution completed, but run future to completion - Future(Box>), -} - -/// Middleware definition -#[allow(unused_variables)] -pub trait Middleware: 'static { - /// Method is called when request is ready. It may return - /// future, which should resolve before next middleware get called. - fn start(&self, req: &HttpRequest) -> Result { - Ok(Started::Done) - } - - /// Method is called when handler returns response, - /// but before sending http message to peer. - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - Ok(Response::Done(resp)) - } - - /// Method is called after body stream get sent to peer. - fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { - Finished::Done +impl MiddlewareFactory +where + T: Transform + Clone, + S: Service, +{ + pub fn new(tr: T) -> Self { + MiddlewareFactory { + tr, + _t: PhantomData, + } + } +} + +impl Clone for MiddlewareFactory +where + T: Transform + Clone, + S: Service, +{ + fn clone(&self) -> Self { + Self { + tr: self.tr.clone(), + _t: PhantomData, + } + } +} + +impl NewTransform for MiddlewareFactory +where + T: Transform + Clone, + S: Service, +{ + type Request = T::Request; + type Response = T::Response; + type Error = T::Error; + type Transform = T; + type InitError = (); + type Future = FutureResult; + + fn new_transform(&self) -> Self::Future { + ok(self.tr.clone()) } } diff --git a/src/middleware/session.rs b/src/middleware/session.rs deleted file mode 100644 index 0271a13f..00000000 --- a/src/middleware/session.rs +++ /dev/null @@ -1,618 +0,0 @@ -//! User sessions. -//! -//! Actix provides a general solution for session management. The -//! [**SessionStorage**](struct.SessionStorage.html) -//! middleware can be used with different backend types to store session -//! data in different backends. -//! -//! By default, only cookie session backend is implemented. Other -//! backend implementations can be added. -//! -//! [**CookieSessionBackend**](struct.CookieSessionBackend.html) -//! uses cookies as session storage. `CookieSessionBackend` creates sessions -//! which are limited to storing fewer than 4000 bytes of data, as the payload -//! must fit into a single cookie. An internal server error is generated if a -//! session contains more than 4000 bytes. -//! -//! A cookie may have a security policy of *signed* or *private*. Each has -//! a respective `CookieSessionBackend` constructor. -//! -//! A *signed* cookie may be viewed but not modified by the client. A *private* -//! cookie may neither be viewed nor modified by the client. -//! -//! The constructors take a key as an argument. This is the private key -//! for cookie session - when this value is changed, all session data is lost. -//! -//! In general, you create a `SessionStorage` middleware and initialize it -//! with specific backend implementation, such as a `CookieSessionBackend`. -//! To access session data, -//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session) -//! must be used. This method returns a -//! [*Session*](struct.Session.html) object, which allows us to get or set -//! session data. -//! -//! ```rust -//! # extern crate actix_web; -//! # extern crate actix; -//! use actix_web::{server, App, HttpRequest, Result}; -//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; -//! -//! fn index(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() { -//! actix::System::run(|| { -//! server::new( -//! || App::new().middleware( -//! SessionStorage::new( // <- create session middleware -//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend -//! .secure(false) -//! ))) -//! .bind("127.0.0.1:59880").unwrap() -//! .start(); -//! # actix::System::current().stop(); -//! }); -//! } -//! ``` -use std::cell::RefCell; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::rc::Rc; -use std::sync::Arc; - -use cookie::{Cookie, CookieJar, Key, SameSite}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use http::header::{self, HeaderValue}; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json; -use serde_json::error::Error as JsonError; -use time::Duration; - -use error::{Error, ResponseError, Result}; -use handler::FromRequest; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; - -/// The helper trait to obtain your session data from a request. -/// -/// ```rust -/// use actix_web::middleware::session::RequestSession; -/// use actix_web::*; -/// -/// 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 { - /// Get the session from the request - fn session(&self) -> Session; -} - -impl RequestSession for HttpRequest { - fn session(&self) -> Session { - if let Some(s_impl) = self.extensions().get::>() { - return Session(SessionInner::Session(Arc::clone(&s_impl))); - } - Session(SessionInner::None) - } -} - -/// The high-level interface you use to modify session data. -/// -/// Session object could be obtained with -/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session) -/// method. `RequestSession` trait is implemented for `HttpRequest`. -/// -/// ```rust -/// use actix_web::middleware::session::RequestSession; -/// use actix_web::*; -/// -/// 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(SessionInner); - -enum SessionInner { - Session(Arc), - None, -} - -impl Session { - /// Get a `value` from the session. - pub fn get(&self, key: &str) -> Result> { - match self.0 { - SessionInner::Session(ref sess) => { - if let Some(s) = sess.as_ref().0.borrow().get(key) { - Ok(Some(serde_json::from_str(s)?)) - } else { - Ok(None) - } - } - SessionInner::None => Ok(None), - } - } - - /// Set a `value` from the session. - pub fn set(&self, key: &str, value: T) -> Result<()> { - match self.0 { - SessionInner::Session(ref sess) => { - sess.as_ref() - .0 - .borrow_mut() - .set(key, serde_json::to_string(&value)?); - Ok(()) - } - SessionInner::None => Ok(()), - } - } - - /// Remove value from the session. - pub fn remove(&self, key: &str) { - match self.0 { - SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key), - SessionInner::None => (), - } - } - - /// Clear the session. - pub fn clear(&self) { - match self.0 { - SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(), - SessionInner::None => (), - } - } -} - -/// Extractor implementation for Session type. -/// -/// ```rust -/// # use actix_web::*; -/// use actix_web::middleware::session::Session; -/// -/// fn index(session: Session) -> Result<&'static str> { -/// // access session data -/// if let Some(count) = session.get::("counter")? { -/// session.set("counter", count + 1)?; -/// } else { -/// session.set("counter", 1)?; -/// } -/// -/// Ok("Welcome!") -/// } -/// # fn main() {} -/// ``` -impl FromRequest for Session { - type Config = (); - type Result = Session; - - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - req.session() - } -} - -struct SessionImplCell(RefCell>); - -/// Session storage middleware -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(SessionStorage::new( -/// // <- create session middleware -/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend -/// .secure(false), -/// )); -/// } -/// ``` -pub struct SessionStorage(T, PhantomData); - -impl> SessionStorage { - /// Create session storage - pub fn new(backend: T) -> SessionStorage { - SessionStorage(backend, PhantomData) - } -} - -impl> Middleware for SessionStorage { - fn start(&self, req: &HttpRequest) -> Result { - let mut req = req.clone(); - - let fut = self.0.from_request(&mut req).then(move |res| match res { - Ok(sess) => { - req.extensions_mut() - .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess))))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(s_box) = req.extensions().get::>() { - s_box.0.borrow_mut().write(resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -/// A simple key-value storage interface that is internally used by `Session`. -pub trait SessionImpl: 'static { - /// Get session value by key - fn get(&self, key: &str) -> Option<&str>; - - /// Set session value - fn set(&mut self, key: &str, value: String); - - /// Remove specific key from session - fn remove(&mut self, key: &str); - - /// Remove all values from session - fn clear(&mut self); - - /// Write session to storage backend. - fn write(&self, resp: HttpResponse) -> Result; -} - -/// Session's storage backend trait definition. -pub trait SessionBackend: Sized + 'static { - /// Session item - type Session: SessionImpl; - /// Future that reads session - type ReadFuture: Future; - - /// Parse the session from request and load data from a storage backend. - fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; -} - -/// Session that uses signed cookies as session storage -pub struct CookieSession { - changed: bool, - state: HashMap, - inner: Rc, -} - -/// Errors that can occur during handling cookie session -#[derive(Fail, Debug)] -pub enum CookieSessionError { - /// Size of the serialized session is greater than 4000 bytes. - #[fail(display = "Size of the serialized session is greater than 4000 bytes.")] - Overflow, - /// Fail to serialize session. - #[fail(display = "Fail to serialize session")] - Serialize(JsonError), -} - -impl ResponseError for CookieSessionError {} - -impl SessionImpl for CookieSession { - fn get(&self, key: &str) -> Option<&str> { - if let Some(s) = self.state.get(key) { - Some(s) - } else { - None - } - } - - fn set(&mut self, key: &str, value: String) { - self.changed = true; - self.state.insert(key.to_owned(), value); - } - - fn remove(&mut self, key: &str) { - self.changed = true; - self.state.remove(key); - } - - fn clear(&mut self) { - self.changed = true; - self.state.clear() - } - - fn write(&self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, &self.state); - } - Ok(Response::Done(resp)) - } -} - -enum CookieSecurity { - Signed, - Private, -} - -struct CookieSessionInner { - key: Key, - security: CookieSecurity, - name: String, - path: String, - domain: Option, - secure: bool, - http_only: bool, - max_age: Option, - same_site: Option, -} - -impl CookieSessionInner { - fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { - CookieSessionInner { - security, - key: Key::from_master(key), - name: "actix-session".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - http_only: true, - max_age: None, - same_site: None, - } - } - - fn set_cookie( - &self, resp: &mut HttpResponse, state: &HashMap, - ) -> Result<()> { - let value = - serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; - if value.len() > 4064 { - return Err(CookieSessionError::Overflow.into()); - } - - let mut cookie = Cookie::new(self.name.clone(), value); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(self.http_only); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - if let Some(same_site) = self.same_site { - cookie.set_same_site(same_site); - } - - let mut jar = CookieJar::new(); - - match self.security { - CookieSecurity::Signed => jar.signed(&self.key).add(cookie), - CookieSecurity::Private => jar.private(&self.key).add(cookie), - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.encoded().to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - - Ok(()) - } - - fn load(&self, req: &mut HttpRequest) -> HashMap { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); - - let cookie_opt = match self.security { - CookieSecurity::Signed => jar.signed(&self.key).get(&self.name), - CookieSecurity::Private => { - jar.private(&self.key).get(&self.name) - } - }; - if let Some(cookie) = cookie_opt { - if let Ok(val) = serde_json::from_str(cookie.value()) { - return val; - } - } - } - } - } - HashMap::new() - } -} - -/// Use cookies for session storage. -/// -/// `CookieSessionBackend` creates sessions which are limited to storing -/// fewer than 4000 bytes of data (as the payload must fit into a single -/// cookie). An Internal Server Error is generated if the session contains more -/// than 4000 bytes. -/// -/// A cookie may have a security policy of *signed* or *private*. Each has a -/// respective `CookieSessionBackend` constructor. -/// -/// A *signed* cookie is stored on the client as plaintext alongside -/// a signature such that the cookie may be viewed but not modified by the -/// client. -/// -/// A *private* cookie is stored on the client as encrypted text -/// such that it may neither be viewed nor modified by the client. -/// -/// The constructors take a key as an argument. -/// This is the private key for cookie session - when this value is changed, -/// all session data is lost. The constructors will panic if the key is less -/// than 32 bytes in length. -/// -/// The backend relies on `cookie` crate to create and read cookies. -/// By default all cookies are percent encoded, but certain symbols may -/// cause troubles when reading cookie, if they are not properly percent encoded. -/// -/// # Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::session::CookieSessionBackend; -/// -/// # fn main() { -/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32]) -/// .domain("www.rust-lang.org") -/// .name("actix_session") -/// .path("/") -/// .secure(true); -/// # } -/// ``` -pub struct CookieSessionBackend(Rc); - -impl CookieSessionBackend { - /// Construct new *signed* `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn signed(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(CookieSessionInner::new( - key, - CookieSecurity::Signed, - ))) - } - - /// Construct new *private* `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn private(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(CookieSessionInner::new( - key, - CookieSecurity::Private, - ))) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().path = value.into(); - self - } - - /// Sets the `name` field in the session cookie being built. - pub fn name>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().name = value.into(); - self - } - - /// Sets the `domain` field in the session cookie being built. - pub fn domain>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `http_only` field in the session cookie being built. - pub fn http_only(mut self, value: bool) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().http_only = value; - self - } - - /// Sets the `same_site` field in the session cookie being built. - pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().same_site = Some(value); - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } -} - -impl SessionBackend for CookieSessionBackend { - type Session = CookieSession; - type ReadFuture = FutureResult; - - fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { - let state = self.0.load(req); - FutOk(CookieSession { - changed: false, - inner: Rc::clone(&self.0), - state, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use application::App; - use test; - - #[test] - fn cookie_session() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/", |r| { - r.f(|req| { - let _ = req.session().set("counter", 100); - "test" - }) - }) - }); - - let request = srv.get().uri(srv.url("/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.cookie("actix-session").is_some()); - } - - #[test] - fn cookie_session_extractor() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/", |r| { - r.with(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - }) - }) - }); - - let request = srv.get().uri(srv.url("/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.cookie("actix-session").is_some()); - } -} diff --git a/src/multipart.rs b/src/multipart.rs deleted file mode 100644 index 862f60ec..00000000 --- a/src/multipart.rs +++ /dev/null @@ -1,815 +0,0 @@ -//! Multipart requests support -use std::cell::{RefCell, UnsafeCell}; -use std::marker::PhantomData; -use std::rc::Rc; -use std::{cmp, fmt}; - -use bytes::Bytes; -use futures::task::{current as current_task, Task}; -use futures::{Async, Poll, Stream}; -use http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; -use http::HttpTryFrom; -use httparse; -use mime; - -use error::{MultipartError, ParseError, PayloadError}; -use payload::PayloadBuffer; - -const MAX_HEADERS: usize = 32; - -/// The server-side implementation of `multipart/form-data` requests. -/// -/// This will parse the incoming stream into `MultipartItem` instances via its -/// Stream implementation. -/// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` -/// is used for nested multipart streams. -pub struct Multipart { - safety: Safety, - error: Option, - inner: Option>>>, -} - -/// -pub enum MultipartItem { - /// Multipart field - Field(Field), - /// Nested multipart stream - Nested(Multipart), -} - -enum InnerMultipartItem { - None, - Field(Rc>>), - Multipart(Rc>>), -} - -#[derive(PartialEq, Debug)] -enum InnerState { - /// Stream eof - Eof, - /// Skip data until first boundary - FirstBoundary, - /// Reading boundary - Boundary, - /// Reading Headers, - Headers, -} - -struct InnerMultipart { - payload: PayloadRef, - boundary: String, - state: InnerState, - item: InnerMultipartItem, -} - -impl Multipart<()> { - /// Extract boundary info from headers. - pub fn boundary(headers: &HeaderMap) -> Result { - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - if let Some(boundary) = ct.get_param(mime::BOUNDARY) { - Ok(boundary.as_str().to_owned()) - } else { - Err(MultipartError::Boundary) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::NoContentType) - } - } -} - -impl Multipart -where - S: Stream, -{ - /// Create multipart instance for boundary. - pub fn new(boundary: Result, stream: S) -> Multipart { - match boundary { - Ok(boundary) => Multipart { - error: None, - safety: Safety::new(), - inner: Some(Rc::new(RefCell::new(InnerMultipart { - boundary, - payload: PayloadRef::new(PayloadBuffer::new(stream)), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - }))), - }, - Err(err) => Multipart { - error: Some(err), - safety: Safety::new(), - inner: None, - }, - } - } -} - -impl Stream for Multipart -where - S: Stream, -{ - type Item = MultipartItem; - type Error = MultipartError; - - fn poll(&mut self) -> Poll, Self::Error> { - 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) - } - } -} - -impl InnerMultipart -where - S: Stream, -{ - fn read_headers(payload: &mut PayloadBuffer) -> Poll { - match payload.read_until(b"\r\n\r\n")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(bytes)) => { - let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; - match httparse::parse_headers(&bytes, &mut hdrs) { - Ok(httparse::Status::Complete((_, hdrs))) => { - // convert headers - let mut headers = HeaderMap::with_capacity(hdrs.len()); - for h in hdrs { - if let Ok(name) = HeaderName::try_from(h.name) { - if let Ok(value) = HeaderValue::try_from(h.value) { - headers.append(name, value); - } else { - return Err(ParseError::Header.into()); - } - } else { - return Err(ParseError::Header.into()); - } - } - Ok(Async::Ready(headers)) - } - Ok(httparse::Status::Partial) => Err(ParseError::Header.into()), - Err(err) => Err(ParseError::from(err).into()), - } - } - } - } - - fn read_boundary( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { - // TODO: need to read epilogue - match payload.readline()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(chunk)) => { - if chunk.len() == boundary.len() + 4 - && &chunk[..2] == b"--" - && &chunk[2..boundary.len() + 2] == boundary.as_bytes() - { - Ok(Async::Ready(false)) - } else if chunk.len() == boundary.len() + 6 - && &chunk[..2] == b"--" - && &chunk[2..boundary.len() + 2] == boundary.as_bytes() - && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" - { - Ok(Async::Ready(true)) - } else { - Err(MultipartError::Boundary) - } - } - } - } - - fn skip_until_boundary( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { - let mut eof = false; - loop { - match payload.readline()? { - Async::Ready(Some(chunk)) => { - if chunk.is_empty() { - //ValueError("Could not find starting boundary %r" - //% (self._boundary)) - } - if chunk.len() < boundary.len() { - continue; - } - if &chunk[..2] == b"--" - && &chunk[2..chunk.len() - 2] == boundary.as_bytes() - { - break; - } else { - if chunk.len() < boundary.len() + 2 { - continue; - } - let b: &[u8] = boundary.as_ref(); - if &chunk[..boundary.len()] == b - && &chunk[boundary.len()..boundary.len() + 2] == b"--" - { - eof = true; - break; - } - } - } - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Err(MultipartError::Incomplete), - } - } - Ok(Async::Ready(eof)) - } - - fn poll( - &mut self, safety: &Safety, - ) -> Poll>, MultipartError> { - if self.state == InnerState::Eof { - Ok(Async::Ready(None)) - } else { - // release field - loop { - // Nested multipart streams of fields has to be consumed - // before switching to next - if safety.current() { - let stop = match self.item { - InnerMultipartItem::Field(ref mut field) => { - match field.borrow_mut().poll(safety)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(_)) => continue, - Async::Ready(None) => true, - } - } - InnerMultipartItem::Multipart(ref mut multipart) => { - match multipart.borrow_mut().poll(safety)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(_)) => continue, - Async::Ready(None) => true, - } - } - _ => false, - }; - if stop { - self.item = InnerMultipartItem::None; - } - if let InnerMultipartItem::None = self.item { - break; - } - } - } - - let headers = if let Some(payload) = self.payload.get_mut(safety) { - match self.state { - // read until first boundary - InnerState::FirstBoundary => { - match InnerMultipart::skip_until_boundary( - payload, - &self.boundary, - )? { - Async::Ready(eof) => { - if eof { - self.state = InnerState::Eof; - return Ok(Async::Ready(None)); - } else { - self.state = InnerState::Headers; - } - } - Async::NotReady => return Ok(Async::NotReady), - } - } - // read boundary - InnerState::Boundary => { - match InnerMultipart::read_boundary(payload, &self.boundary)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(eof) => { - if eof { - self.state = InnerState::Eof; - return Ok(Async::Ready(None)); - } else { - self.state = InnerState::Headers; - } - } - } - } - _ => (), - } - - // read field headers for next field - if self.state == InnerState::Headers { - if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? - { - self.state = InnerState::Boundary; - headers - } else { - return Ok(Async::NotReady); - } - } else { - unreachable!() - } - } else { - debug!("NotReady: field is in flight"); - return Ok(Async::NotReady); - }; - - // content type - let mut mt = mime::APPLICATION_OCTET_STREAM; - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - mt = ct; - } - } - } - - self.state = InnerState::Boundary; - - // nested multipart stream - if mt.type_() == mime::MULTIPART { - let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) { - Rc::new(RefCell::new(InnerMultipart { - payload: self.payload.clone(), - boundary: boundary.as_str().to_owned(), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - })) - } else { - return Err(MultipartError::Boundary); - }; - - self.item = InnerMultipartItem::Multipart(Rc::clone(&inner)); - - Ok(Async::Ready(Some(MultipartItem::Nested(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, - )?)); - self.item = InnerMultipartItem::Field(Rc::clone(&field)); - - Ok(Async::Ready(Some(MultipartItem::Field(Field::new( - safety.clone(), - headers, - mt, - field, - ))))) - } - } - } -} - -impl Drop for InnerMultipart { - fn drop(&mut self) { - // InnerMultipartItem::Field has to be dropped first because of Safety. - self.item = InnerMultipartItem::None; - } -} - -/// A single field in a multipart stream -pub struct Field { - ct: mime::Mime, - headers: HeaderMap, - inner: Rc>>, - safety: Safety, -} - -impl Field -where - S: Stream, -{ - fn new( - safety: Safety, headers: HeaderMap, ct: mime::Mime, - inner: Rc>>, - ) -> Self { - Field { - ct, - headers, - inner, - safety, - } - } - - /// Get a map of headers - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Get the content type of the field - pub fn content_type(&self) -> &mime::Mime { - &self.ct - } - - /// Get the content disposition of the field, if it exists - pub fn content_disposition(&self) -> Option { - // RFC 7578: 'Each part MUST contain a Content-Disposition header field - // where the disposition type is "form-data".' - if let Some(content_disposition) = - self.headers.get(::http::header::CONTENT_DISPOSITION) - { - ContentDisposition::from_raw(content_disposition).ok() - } else { - None - } - } -} - -impl Stream for Field -where - S: Stream, -{ - type Item = Bytes; - type Error = MultipartError; - - fn poll(&mut self) -> Poll, Self::Error> { - if self.safety.current() { - self.inner.borrow_mut().poll(&self.safety) - } else { - Ok(Async::NotReady) - } - } -} - -impl fmt::Debug for Field { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nMultipartField: {}", self.ct)?; - writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; - writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -struct InnerField { - payload: Option>, - boundary: String, - eof: bool, - length: Option, -} - -impl InnerField -where - S: Stream, -{ - fn new( - payload: PayloadRef, boundary: String, headers: &HeaderMap, - ) -> Result, PayloadError> { - let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Some(len) - } else { - return Err(PayloadError::Incomplete); - } - } else { - return Err(PayloadError::Incomplete); - } - } else { - None - }; - - Ok(InnerField { - boundary, - payload: Some(payload), - eof: false, - length: len, - }) - } - - /// Reads body part content chunk of the specified size. - /// The body part must has `Content-Length` header with proper value. - fn read_len( - payload: &mut PayloadBuffer, size: &mut u64, - ) -> Poll, MultipartError> { - if *size == 0 { - Ok(Async::Ready(None)) - } else { - match payload.readany() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => Err(MultipartError::Incomplete), - Ok(Async::Ready(Some(mut chunk))) => { - let len = cmp::min(chunk.len() as u64, *size); - *size -= len; - let ch = chunk.split_to(len as usize); - if !chunk.is_empty() { - payload.unprocessed(chunk); - } - Ok(Async::Ready(Some(ch))) - } - Err(err) => Err(err.into()), - } - } - } - - /// Reads content chunk of body part with unknown length. - /// The `Content-Length` header for body part is not necessary. - fn read_stream( - payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll, MultipartError> { - match payload.read_until(b"\r")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { - if chunk.len() == 1 { - payload.unprocessed(chunk); - match payload.read_exact(boundary.len() + 4)? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { - if &chunk[..2] == b"\r\n" - && &chunk[2..4] == b"--" - && &chunk[4..] == boundary.as_bytes() - { - payload.unprocessed(chunk); - Ok(Async::Ready(None)) - } else { - // \r might be part of data stream - let ch = chunk.split_to(1); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) - } - } - } - } else { - let to = chunk.len() - 1; - let ch = chunk.split_to(to); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) - } - } - } - } - - fn poll(&mut self, s: &Safety) -> Poll, MultipartError> { - if self.payload.is_none() { - return Ok(Async::Ready(None)); - } - - let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { - let res = if let Some(ref mut len) = self.length { - InnerField::read_len(payload, len)? - } else { - InnerField::read_stream(payload, &self.boundary)? - }; - - match res { - Async::NotReady => Async::NotReady, - Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), - Async::Ready(None) => { - self.eof = true; - match payload.readline()? { - Async::NotReady => Async::NotReady, - Async::Ready(None) => Async::Ready(None), - Async::Ready(Some(line)) => { - if line.as_ref() != b"\r\n" { - warn!("multipart field did not read all the data or it is malformed"); - } - Async::Ready(None) - } - } - } - } - } else { - Async::NotReady - }; - - if Async::Ready(None) == result { - self.payload.take(); - } - Ok(result) - } -} - -struct PayloadRef { - payload: Rc>>, -} - -impl PayloadRef -where - S: Stream, -{ - fn new(payload: PayloadBuffer) -> PayloadRef { - PayloadRef { - payload: Rc::new(payload.into()), - } - } - - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer> - where - 'a: 'b, - { - // Unsafe: Invariant is inforced by Safety Safety is used as ref counter, - // only top most ref can have mutable access to payload. - if s.current() { - let payload: &mut PayloadBuffer = unsafe { &mut *self.payload.get() }; - Some(payload) - } else { - None - } - } -} - -impl Clone for PayloadRef { - fn clone(&self) -> PayloadRef { - PayloadRef { - payload: Rc::clone(&self.payload), - } - } -} - -/// Counter. It tracks of number of clones of payloads and give access to -/// payload only to top most task panics if Safety get destroyed and it not top -/// most task. -#[derive(Debug)] -struct Safety { - task: Option, - level: usize, - payload: Rc>, -} - -impl Safety { - fn new() -> Safety { - let payload = Rc::new(PhantomData); - Safety { - task: None, - level: Rc::strong_count(&payload), - payload, - } - } - - fn current(&self) -> bool { - Rc::strong_count(&self.payload) == self.level - } -} - -impl Clone for Safety { - fn clone(&self) -> Safety { - let payload = Rc::clone(&self.payload); - Safety { - task: Some(current_task()), - level: Rc::strong_count(&payload), - payload, - } - } -} - -impl Drop for Safety { - fn drop(&mut self) { - // parent task is dead - if Rc::strong_count(&self.payload) != self.level { - panic!("Safety get dropped but it is not from top-most task"); - } - if let Some(task) = self.task.take() { - task.notify() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - use futures::future::{lazy, result}; - use payload::{Payload, PayloadWriter}; - use tokio::runtime::current_thread::Runtime; - - #[test] - fn test_boundary() { - let headers = HeaderMap::new(); - match Multipart::boundary(&headers) { - Err(MultipartError::NoContentType) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("test"), - ); - - match Multipart::boundary(&headers) { - Err(MultipartError::ParseContentType) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("multipart/mixed"), - ); - match Multipart::boundary(&headers) { - Err(MultipartError::Boundary) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static( - "multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"", - ), - ); - - assert_eq!( - Multipart::boundary(&headers).unwrap(), - "5c02368e880e436dab70ed54e1c58209" - ); - } - - #[test] - fn test_multipart() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); - - let bytes = Bytes::from( - "testasdadsad\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - test\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - data\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n"); - sender.feed_data(bytes); - - let mut multipart = Multipart::new( - Ok("abbc761f78ff4d7cb7573b5a23f96ef0".to_owned()), - payload, - ); - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { - { - use http::header::{DispositionParam, DispositionType}; - let cd = field.content_disposition().unwrap(); - assert_eq!(cd.disposition, DispositionType::FormData); - assert_eq!( - cd.parameters[0], - DispositionParam::Name("file".into()) - ); - } - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.poll() { - Ok(Async::Ready(Some(chunk))) => { - assert_eq!(chunk, "test") - } - _ => unreachable!(), - } - match field.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - } - _ => unreachable!(), - }, - _ => unreachable!(), - } - - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.poll() { - Ok(Async::Ready(Some(chunk))) => { - assert_eq!(chunk, "data") - } - _ => unreachable!(), - } - match field.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - } - _ => unreachable!(), - }, - _ => unreachable!(), - } - - match multipart.poll() { - Ok(Async::Ready(None)) => (), - _ => unreachable!(), - } - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } -} diff --git a/src/param.rs b/src/param.rs deleted file mode 100644 index a3f60259..00000000 --- a/src/param.rs +++ /dev/null @@ -1,334 +0,0 @@ -use std; -use std::ops::Index; -use std::path::PathBuf; -use std::rc::Rc; -use std::str::FromStr; - -use http::StatusCode; -use smallvec::SmallVec; - -use error::{InternalError, ResponseError, UriSegmentError}; -use uri::{Url, RESERVED_QUOTER}; - -/// 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; -} - -#[derive(Debug, Clone)] -pub(crate) enum ParamItem { - Static(&'static str), - UrlSegment(u16, u16), -} - -/// Route match information -/// -/// If resource path contains variable patterns, `Params` stores this variables. -#[derive(Debug, Clone)] -pub struct Params { - url: Url, - pub(crate) tail: u16, - pub(crate) segments: SmallVec<[(Rc, ParamItem); 3]>, -} - -impl Params { - pub(crate) fn new() -> Params { - Params { - url: Url::default(), - tail: 0, - segments: SmallVec::new(), - } - } - - pub(crate) fn with_url(url: &Url) -> Params { - Params { - url: url.clone(), - tail: 0, - segments: SmallVec::new(), - } - } - - pub(crate) fn clear(&mut self) { - self.segments.clear(); - } - - pub(crate) fn set_tail(&mut self, tail: u16) { - self.tail = tail; - } - - pub(crate) fn set_url(&mut self, url: Url) { - self.url = url; - } - - pub(crate) fn add(&mut self, name: Rc, value: ParamItem) { - self.segments.push((name, value)); - } - - pub(crate) fn add_static(&mut self, name: &str, value: &'static str) { - self.segments - .push((Rc::new(name.to_string()), ParamItem::Static(value))); - } - - /// Check if there are any matched patterns - pub fn is_empty(&self) -> bool { - self.segments.is_empty() - } - - /// Check number of extracted parameters - pub fn len(&self) -> usize { - self.segments.len() - } - - /// Get matched parameter by name without type conversion - pub fn get(&self, key: &str) -> Option<&str> { - for item in self.segments.iter() { - if key == item.0.as_str() { - return match item.1 { - ParamItem::Static(ref s) => Some(&s), - ParamItem::UrlSegment(s, e) => { - Some(&self.url.path()[(s as usize)..(e as usize)]) - } - }; - } - } - if key == "tail" { - Some(&self.url.path()[(self.tail as usize)..]) - } else { - None - } - } - - /// Get URL-decoded matched parameter by name without type conversion - pub fn get_decoded(&self, key: &str) -> Option { - self.get(key).map(|value| { - if let Some(ref mut value) = RESERVED_QUOTER.requote(value.as_bytes()) { - Rc::make_mut(value).to_string() - } else { - value.to_string() - } - }) - } - - /// Get unprocessed part of path - pub fn unprocessed(&self) -> &str { - &self.url.path()[(self.tail as usize)..] - } - - /// 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("") - } - } - - /// Return iterator to items in parameter container - pub fn iter(&self) -> ParamsIter { - ParamsIter { - idx: 0, - params: self, - } - } -} - -#[derive(Debug)] -pub struct ParamsIter<'a> { - idx: usize, - params: &'a Params, -} - -impl<'a> Iterator for ParamsIter<'a> { - type Item = (&'a str, &'a str); - - #[inline] - fn next(&mut self) -> Option<(&'a str, &'a str)> { - if self.idx < self.params.len() { - let idx = self.idx; - let res = match self.params.segments[idx].1 { - ParamItem::Static(ref s) => &s, - ParamItem::UrlSegment(s, e) => { - &self.params.url.path()[(s as usize)..(e as usize)] - } - }; - self.idx += 1; - return Some((&self.params.segments[idx].0, res)); - } - None - } -} - -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") - } -} - -impl Index for Params { - type Output = str; - - fn index(&self, idx: usize) -> &str { - match self.segments[idx].1 { - ParamItem::Static(ref s) => &s, - ParamItem::UrlSegment(s, e) => &self.url.path()[(s as usize)..(e as usize)], - } - } -} - -/// 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) - } -} - -macro_rules! FROM_STR { - ($type:ty) => { - impl FromParam for $type { - type Err = InternalError<<$type as FromStr>::Err>; - fn from_param(val: &str) -> Result { - <$type as FromStr>::from_str(val) - .map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST)) - } - } - }; -} - -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"])) - ); - } - - #[test] - fn test_get_param_by_name() { - let mut params = Params::new(); - params.add_static("item1", "path"); - params.add_static("item2", "http%3A%2F%2Flocalhost%3A80%2Ffoo"); - - assert_eq!(params.get("item0"), None); - assert_eq!(params.get_decoded("item0"), None); - assert_eq!(params.get("item1"), Some("path")); - assert_eq!(params.get_decoded("item1"), Some("path".to_string())); - assert_eq!( - params.get("item2"), - Some("http%3A%2F%2Flocalhost%3A80%2Ffoo") - ); - assert_eq!( - params.get_decoded("item2"), - Some("http://localhost:80/foo".to_string()) - ); - } -} diff --git a/src/payload.rs b/src/payload.rs deleted file mode 100644 index 2131e3c3..00000000 --- a/src/payload.rs +++ /dev/null @@ -1,715 +0,0 @@ -//! Payload stream -use bytes::{Bytes, BytesMut}; -#[cfg(not(test))] -use futures::task::current as current_task; -use futures::task::Task; -use futures::{Async, Poll, Stream}; -use std::cell::RefCell; -use std::cmp; -use std::collections::VecDeque; -use std::rc::{Rc, Weak}; - -use error::PayloadError; - -/// max buffer size 32k -pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; - -#[derive(Debug, PartialEq)] -pub(crate) enum PayloadStatus { - Read, - Pause, - Dropped, -} - -/// Buffered stream of bytes chunks -/// -/// Payload stores chunks in a vector. First chunk can be received with -/// `.readany()` method. Payload stream is not thread safe. Payload does not -/// notify current task when new data is available. -/// -/// Payload stream can be used as `HttpResponse` body stream. -#[derive(Debug)] -pub struct Payload { - inner: Rc>, -} - -impl 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 }, - ) - } - - /// Create empty payload - #[doc(hidden)] - pub fn empty() -> Payload { - Payload { - inner: Rc::new(RefCell::new(Inner::new(true))), - } - } - - /// Length of the data in this payload - #[cfg(test)] - pub fn len(&self) -> usize { - self.inner.borrow().len() - } - - /// Is payload empty - #[cfg(test)] - pub fn is_empty(&self) -> bool { - self.inner.borrow().len() == 0 - } - - /// Put unused data back to payload - #[inline] - pub fn unread_data(&mut self, data: Bytes) { - self.inner.borrow_mut().unread_data(data); - } - - #[cfg(test)] - pub(crate) fn readall(&self) -> Option { - self.inner.borrow_mut().readall() - } - - #[inline] - /// Set read buffer capacity - /// - /// Default buffer capacity is 32Kb. - pub fn set_read_buffer_capacity(&mut self, cap: usize) { - self.inner.borrow_mut().capacity = cap; - } -} - -impl Stream for Payload { - type Item = Bytes; - type Error = PayloadError; - - #[inline] - fn poll(&mut self) -> Poll, PayloadError> { - self.inner.borrow_mut().readany() - } -} - -impl Clone for Payload { - fn clone(&self) -> Payload { - Payload { - inner: Rc::clone(&self.inner), - } - } -} - -/// Payload writer interface. -pub(crate) trait PayloadWriter { - /// Set stream error. - fn set_error(&mut self, err: PayloadError); - - /// Write eof into a stream which closes reading side of a stream. - fn feed_eof(&mut self); - - /// Feed bytes into a payload stream - fn feed_data(&mut self, data: Bytes); - - /// Need read data - fn need_read(&self) -> PayloadStatus; -} - -/// Sender part of the payload stream -pub struct PayloadSender { - inner: Weak>, -} - -impl PayloadWriter for PayloadSender { - #[inline] - fn set_error(&mut self, err: PayloadError) { - if let Some(shared) = self.inner.upgrade() { - shared.borrow_mut().set_error(err) - } - } - - #[inline] - fn feed_eof(&mut self) { - if let Some(shared) = self.inner.upgrade() { - shared.borrow_mut().feed_eof() - } - } - - #[inline] - fn feed_data(&mut self, data: Bytes) { - if let Some(shared) = self.inner.upgrade() { - shared.borrow_mut().feed_data(data) - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - // we check need_read only if Payload (other side) is alive, - // otherwise always return true (consume payload) - if let Some(shared) = self.inner.upgrade() { - if shared.borrow().need_read { - PayloadStatus::Read - } else { - #[cfg(not(test))] - { - if shared.borrow_mut().io_task.is_none() { - shared.borrow_mut().io_task = Some(current_task()); - } - } - PayloadStatus::Pause - } - } else { - PayloadStatus::Dropped - } - } -} - -#[derive(Debug)] -struct Inner { - len: usize, - eof: bool, - err: Option, - need_read: bool, - items: VecDeque, - capacity: usize, - task: Option, - io_task: Option, -} - -impl Inner { - fn new(eof: bool) -> Self { - Inner { - eof, - len: 0, - err: None, - items: VecDeque::new(), - need_read: true, - capacity: MAX_BUFFER_SIZE, - task: None, - io_task: None, - } - } - - #[inline] - fn set_error(&mut self, err: PayloadError) { - self.err = Some(err); - } - - #[inline] - fn feed_eof(&mut self) { - self.eof = true; - } - - #[inline] - fn feed_data(&mut self, data: Bytes) { - self.len += data.len(); - self.items.push_back(data); - self.need_read = self.len < self.capacity; - if let Some(task) = self.task.take() { - task.notify() - } - } - - #[cfg(test)] - fn len(&self) -> usize { - self.len - } - - #[cfg(test)] - pub(crate) fn readall(&mut self) -> Option { - let len = self.items.iter().map(|b| b.len()).sum(); - if len > 0 { - let mut buf = BytesMut::with_capacity(len); - for item in &self.items { - buf.extend_from_slice(item); - } - self.items = VecDeque::new(); - self.len = 0; - Some(buf.take().freeze()) - } else { - self.need_read = true; - None - } - } - - fn readany(&mut self) -> Poll, PayloadError> { - if let Some(data) = self.items.pop_front() { - self.len -= data.len(); - self.need_read = self.len < self.capacity; - #[cfg(not(test))] - { - if self.need_read && self.task.is_none() { - self.task = Some(current_task()); - } - if let Some(task) = self.io_task.take() { - task.notify() - } - } - Ok(Async::Ready(Some(data))) - } else if let Some(err) = self.err.take() { - Err(err) - } else if self.eof { - Ok(Async::Ready(None)) - } else { - self.need_read = true; - #[cfg(not(test))] - { - if self.task.is_none() { - self.task = Some(current_task()); - } - if let Some(task) = self.io_task.take() { - task.notify() - } - } - Ok(Async::NotReady) - } - } - - fn unread_data(&mut self, data: Bytes) { - self.len += data.len(); - self.items.push_front(data); - } -} - -/// Payload buffer -pub struct PayloadBuffer { - len: usize, - items: VecDeque, - stream: S, -} - -impl PayloadBuffer -where - S: Stream, -{ - /// Create new `PayloadBuffer` instance - pub fn new(stream: S) -> Self { - PayloadBuffer { - len: 0, - items: VecDeque::new(), - stream, - } - } - - /// Get mutable reference to an inner stream. - pub fn get_mut(&mut self) -> &mut S { - &mut self.stream - } - - #[inline] - fn poll_stream(&mut self) -> Poll { - self.stream.poll().map(|res| match res { - Async::Ready(Some(data)) => { - self.len += data.len(); - self.items.push_back(data); - Async::Ready(true) - } - Async::Ready(None) => Async::Ready(false), - Async::NotReady => Async::NotReady, - }) - } - - /// Read first available chunk of bytes - #[inline] - pub fn readany(&mut self) -> Poll, PayloadError> { - if let Some(data) = self.items.pop_front() { - self.len -= data.len(); - Ok(Async::Ready(Some(data))) - } else { - match self.poll_stream()? { - Async::Ready(true) => self.readany(), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Check if buffer contains enough bytes - #[inline] - pub fn can_read(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - Ok(Async::Ready(Some(true))) - } else { - match self.poll_stream()? { - Async::Ready(true) => self.can_read(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Return reference to the first chunk of data - #[inline] - pub fn get_chunk(&mut self) -> Poll, PayloadError> { - if self.items.is_empty() { - match self.poll_stream()? { - Async::Ready(true) => (), - Async::Ready(false) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - } - } - match self.items.front().map(|c| c.as_ref()) { - Some(chunk) => Ok(Async::Ready(Some(chunk))), - None => Ok(Async::NotReady), - } - } - - /// Read exact number of bytes - #[inline] - pub fn read_exact(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - self.len -= size; - let mut chunk = self.items.pop_front().unwrap(); - if size < chunk.len() { - let buf = chunk.split_to(size); - self.items.push_front(chunk); - Ok(Async::Ready(Some(buf))) - } else if size == chunk.len() { - Ok(Async::Ready(Some(chunk))) - } else { - let mut buf = BytesMut::with_capacity(size); - buf.extend_from_slice(&chunk); - - while buf.len() < size { - let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size - buf.len(), chunk.len()); - buf.extend_from_slice(&chunk.split_to(rem)); - if !chunk.is_empty() { - self.items.push_front(chunk); - } - } - Ok(Async::Ready(Some(buf.freeze()))) - } - } else { - match self.poll_stream()? { - Async::Ready(true) => self.read_exact(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Remove specified amount if bytes from buffer - #[inline] - pub fn drop_bytes(&mut self, size: usize) { - if size <= self.len { - self.len -= size; - - let mut len = 0; - while len < size { - let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size - len, chunk.len()); - len += rem; - if rem < chunk.len() { - chunk.split_to(rem); - self.items.push_front(chunk); - } - } - } - } - - /// Copy buffered data - pub fn copy(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - let mut buf = BytesMut::with_capacity(size); - for chunk in &self.items { - if buf.len() < size { - let rem = cmp::min(size - buf.len(), chunk.len()); - buf.extend_from_slice(&chunk[..rem]); - } - if buf.len() == size { - return Ok(Async::Ready(Some(buf))); - } - } - } - - match self.poll_stream()? { - Async::Ready(true) => self.copy(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - - /// Read until specified ending - pub fn read_until(&mut self, line: &[u8]) -> Poll, PayloadError> { - let mut idx = 0; - let mut num = 0; - let mut offset = 0; - let mut found = false; - let mut length = 0; - - for no in 0..self.items.len() { - { - let chunk = &self.items[no]; - for (pos, ch) in chunk.iter().enumerate() { - if *ch == line[idx] { - idx += 1; - if idx == line.len() { - num = no; - offset = pos + 1; - length += pos + 1; - found = true; - break; - } - } else { - idx = 0 - } - } - if !found { - length += chunk.len() - } - } - - if found { - let mut buf = BytesMut::with_capacity(length); - if num > 0 { - for _ in 0..num { - buf.extend_from_slice(&self.items.pop_front().unwrap()); - } - } - if offset > 0 { - let mut chunk = self.items.pop_front().unwrap(); - buf.extend_from_slice(&chunk.split_to(offset)); - if !chunk.is_empty() { - self.items.push_front(chunk) - } - } - self.len -= length; - return Ok(Async::Ready(Some(buf.freeze()))); - } - } - - match self.poll_stream()? { - Async::Ready(true) => self.read_until(line), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - - /// Read bytes until new line delimiter - pub fn readline(&mut self) -> Poll, PayloadError> { - self.read_until(b"\n") - } - - /// Put unprocessed data back to the buffer - pub fn unprocessed(&mut self, data: Bytes) { - self.len += data.len(); - self.items.push_front(data); - } - - /// Get remaining data from the buffer - pub fn remaining(&mut self) -> Bytes { - self.items - .iter_mut() - .fold(BytesMut::new(), |mut b, c| { - b.extend_from_slice(c); - b - }).freeze() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use failure::Fail; - use futures::future::{lazy, result}; - use std::io; - use tokio::runtime::current_thread::Runtime; - - #[test] - fn test_error() { - let err: PayloadError = - io::Error::new(io::ErrorKind::Other, "ParseError").into(); - assert_eq!(format!("{}", err), "ParseError"); - assert_eq!(format!("{}", err.cause().unwrap()), "ParseError"); - - let err = PayloadError::Incomplete; - assert_eq!( - format!("{}", err), - "A payload reached EOF, but is not complete." - ); - } - - #[test] - fn test_basic() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (_, payload) = Payload::new(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(payload.len, 0); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } - - #[test] - fn test_eof() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - sender.feed_data(Bytes::from("data")); - sender.feed_eof(); - - assert_eq!( - Async::Ready(Some(Bytes::from("data"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } - - #[test] - fn test_err() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - - sender.set_error(PayloadError::Incomplete); - payload.readany().err().unwrap(); - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } - - #[test] - fn test_readany() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadBuffer::new(payload); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from("line1"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - - assert_eq!( - Async::Ready(Some(Bytes::from("line2"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } - - #[test] - fn test_readexactly() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from_static(b"li"))), - payload.read_exact(2).ok().unwrap() - ); - assert_eq!(payload.len, 3); - - assert_eq!( - Async::Ready(Some(Bytes::from_static(b"ne1l"))), - payload.read_exact(4).ok().unwrap() - ); - assert_eq!(payload.len, 4); - - sender.set_error(PayloadError::Incomplete); - payload.read_exact(10).err().unwrap(); - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } - - #[test] - fn test_readuntil() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::new(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from("line"))), - payload.read_until(b"ne").ok().unwrap() - ); - assert_eq!(payload.len, 1); - - assert_eq!( - Async::Ready(Some(Bytes::from("1line2"))), - payload.read_until(b"2").ok().unwrap() - ); - assert_eq!(payload.len, 0); - - sender.set_error(PayloadError::Incomplete); - payload.read_until(b"b").err().unwrap(); - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } - - #[test] - fn test_unread_data() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (_, mut payload) = Payload::new(false); - - payload.unread_data(Bytes::from("data")); - assert!(!payload.is_empty()); - assert_eq!(payload.len(), 4); - - assert_eq!( - Async::Ready(Some(Bytes::from("data"))), - payload.poll().ok().unwrap() - ); - - let res: Result<(), ()> = Ok(()); - result(res) - })).unwrap(); - } -} diff --git a/src/pipeline.rs b/src/pipeline.rs deleted file mode 100644 index a938f2eb..00000000 --- a/src/pipeline.rs +++ /dev/null @@ -1,869 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; -use std::{io, mem}; - -use futures::sync::oneshot; -use futures::{Async, Future, Poll, Stream}; -use log::Level::Debug; - -use body::{Body, BodyStream}; -use context::{ActorHttpContext, Frame}; -use error::Error; -use handler::{AsyncResult, AsyncResultItem}; -use header::ContentEncoding; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Finished, Middleware, Response, Started}; -use server::{HttpHandlerTask, Writer, WriterState}; - -#[doc(hidden)] -pub trait PipelineHandler { - fn encoding(&self) -> ContentEncoding; - - fn handle(&self, &HttpRequest) -> AsyncResult; -} - -#[doc(hidden)] -pub struct Pipeline( - PipelineInfo, - PipelineState, - Rc>>>, -); - -enum PipelineState { - None, - Error, - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Response(ProcessResponse), - Finishing(FinishingMiddlewares), - Completed(Completed), -} - -impl> PipelineState { - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - match *self { - PipelineState::Starting(ref mut state) => state.poll(info, mws), - PipelineState::Handler(ref mut state) => state.poll(info, mws), - PipelineState::RunMiddlewares(ref mut state) => state.poll(info, mws), - PipelineState::Finishing(ref mut state) => state.poll(info, mws), - PipelineState::Completed(ref mut state) => state.poll(info), - PipelineState::Response(ref mut state) => state.poll(info, mws), - PipelineState::None | PipelineState::Error => None, - } - } -} - -struct PipelineInfo { - req: HttpRequest, - count: u16, - context: Option>, - error: Option, - disconnected: Option, - encoding: ContentEncoding, -} - -impl PipelineInfo { - fn poll_context(&mut self) -> Poll<(), Error> { - if let Some(ref mut context) = self.context { - match context.poll() { - Err(err) => Err(err), - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(_)) => Ok(Async::Ready(())), - } - } else { - Ok(Async::Ready(())) - } - } -} - -impl> Pipeline { - pub(crate) fn new( - req: HttpRequest, mws: Rc>>>, handler: Rc, - ) -> Pipeline { - let mut info = PipelineInfo { - req, - count: 0, - error: None, - context: None, - disconnected: None, - encoding: handler.encoding(), - }; - let state = StartMiddlewares::init(&mut info, &mws, handler); - - Pipeline(info, state, mws) - } -} - -impl Pipeline { - #[inline] - fn is_done(&self) -> bool { - match self.1 { - PipelineState::None - | PipelineState::Error - | PipelineState::Starting(_) - | PipelineState::Handler(_) - | PipelineState::RunMiddlewares(_) - | PipelineState::Response(_) => true, - PipelineState::Finishing(_) | PipelineState::Completed(_) => false, - } - } -} - -impl> HttpHandlerTask for Pipeline { - fn disconnected(&mut self) { - self.0.disconnected = Some(true); - } - - fn poll_io(&mut self, io: &mut Writer) -> Poll { - let mut state = mem::replace(&mut self.1, PipelineState::None); - - loop { - if let PipelineState::Response(st) = state { - match st.poll_io(io, &mut self.0, &self.2) { - Ok(state) => { - self.1 = state; - if let Some(error) = self.0.error.take() { - return Err(error); - } else { - return Ok(Async::Ready(self.is_done())); - } - } - Err(state) => { - self.1 = state; - return Ok(Async::NotReady); - } - } - } - match state { - PipelineState::None => return Ok(Async::Ready(true)), - PipelineState::Error => { - return Err( - io::Error::new(io::ErrorKind::Other, "Internal error").into() - ) - } - _ => (), - } - - match state.poll(&mut self.0, &self.2) { - Some(st) => state = st, - None => { - return { - self.1 = state; - Ok(Async::NotReady) - } - } - } - } - } - - fn poll_completed(&mut self) -> Poll<(), Error> { - let mut state = mem::replace(&mut self.1, PipelineState::None); - loop { - match state { - PipelineState::None | PipelineState::Error => { - return Ok(Async::Ready(())) - } - _ => (), - } - - if let Some(st) = state.poll(&mut self.0, &self.2) { - state = st; - } else { - self.1 = state; - return Ok(Async::NotReady); - } - } - } -} - -type Fut = Box, Error = Error>>; - -/// Middlewares start executor -struct StartMiddlewares { - hnd: Rc, - fut: Option, - _s: PhantomData, -} - -impl> StartMiddlewares { - fn init( - info: &mut PipelineInfo, mws: &[Box>], hnd: Rc, - ) -> PipelineState { - // execute middlewares, we need this stage because middlewares could be - // non-async and we can move to next state immediately - let len = mws.len() as u16; - - loop { - if info.count == len { - let reply = hnd.handle(&info.req); - return WaitingResponse::init(info, mws, reply); - } else { - match mws[info.count as usize].start(&info.req) { - Ok(Started::Done) => info.count += 1, - Ok(Started::Response(resp)) => { - return RunMiddlewares::init(info, mws, resp); - } - Ok(Started::Future(fut)) => { - return PipelineState::Starting(StartMiddlewares { - hnd, - fut: Some(fut), - _s: PhantomData, - }) - } - Err(err) => { - return RunMiddlewares::init(info, mws, err.into()); - } - } - } - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - let len = mws.len() as u16; - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, mws, resp)); - } - loop { - if info.count == len { - let reply = self.hnd.handle(&info.req); - return Some(WaitingResponse::init(info, mws, reply)); - } else { - let res = mws[info.count as usize].start(&info.req); - match res { - Ok(Started::Done) => info.count += 1, - Ok(Started::Response(resp)) => { - return Some(RunMiddlewares::init(info, mws, resp)); - } - Ok(Started::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init( - info, - mws, - err.into(), - )); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, mws, err.into())); - } - } - } - } -} - -// waiting for response -struct WaitingResponse { - fut: Box>, - _s: PhantomData, - _h: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], - reply: AsyncResult, - ) -> PipelineState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, mws, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, mws, err.into()), - AsyncResultItem::Future(fut) => PipelineState::Handler(WaitingResponse { - fut, - _s: PhantomData, - _h: PhantomData, - }), - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, mws, resp)), - Err(err) => Some(RunMiddlewares::init(info, mws, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, - _h: PhantomData, -} - -impl RunMiddlewares { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], mut resp: HttpResponse, - ) -> PipelineState { - if info.count == 0 { - return ProcessResponse::init(resp); - } - let mut curr = 0; - let len = mws.len(); - - loop { - let state = mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = (curr + 1) as u16; - return ProcessResponse::init(err.into()); - } - Ok(Response::Done(r)) => { - curr += 1; - if curr == len { - return ProcessResponse::init(r); - } else { - r - } - } - Ok(Response::Future(fut)) => { - return PipelineState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - _h: PhantomData, - }); - } - }; - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - let len = mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(ProcessResponse::init(err.into())), - }; - - loop { - if self.curr == len { - return Some(ProcessResponse::init(resp)); - } else { - let state = mws[self.curr].response(&info.req, resp); - match state { - Err(err) => return Some(ProcessResponse::init(err.into())), - Ok(Response::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(Response::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -struct ProcessResponse { - resp: Option, - iostate: IOState, - running: RunningState, - drain: Option>, - _s: PhantomData, - _h: PhantomData, -} - -#[derive(PartialEq, Debug)] -enum RunningState { - Running, - Paused, - Done, -} - -impl RunningState { - #[inline] - fn pause(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Paused - } - } - #[inline] - fn resume(&mut self) { - if *self != RunningState::Done { - *self = RunningState::Running - } - } -} - -enum IOState { - Response, - Payload(BodyStream), - Actor(Box), - Done, -} - -impl ProcessResponse { - #[inline] - fn init(resp: HttpResponse) -> PipelineState { - PipelineState::Response(ProcessResponse { - resp: Some(resp), - iostate: IOState::Response, - running: RunningState::Running, - drain: None, - _s: PhantomData, - _h: PhantomData, - }) - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - // connection is dead at this point - match mem::replace(&mut self.iostate, IOState::Done) { - IOState::Response => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - IOState::Payload(_) => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - IOState::Actor(mut ctx) => { - if info.disconnected.take().is_some() { - ctx.disconnected(); - } - loop { - match ctx.poll() { - Ok(Async::Ready(Some(vec))) => { - if vec.is_empty() { - continue; - } - for frame in vec { - match frame { - Frame::Chunk(None) => { - info.context = Some(ctx); - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - Frame::Chunk(Some(_)) => (), - Frame::Drain(fut) => { - let _ = fut.send(()); - } - } - } - } - Ok(Async::Ready(None)) => { - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )) - } - Ok(Async::NotReady) => { - self.iostate = IOState::Actor(ctx); - return None; - } - Err(err) => { - info.context = Some(ctx); - info.error = Some(err); - return Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - } - IOState::Done => Some(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )), - } - } - - fn poll_io( - mut self, io: &mut Writer, info: &mut PipelineInfo, - mws: &[Box>], - ) -> Result, PipelineState> { - loop { - if self.drain.is_none() && self.running != RunningState::Paused { - // if task is paused, write buffer is probably full - 'inner: loop { - let result = match mem::replace(&mut self.iostate, IOState::Done) { - IOState::Response => { - let encoding = self - .resp - .as_ref() - .unwrap() - .content_encoding() - .unwrap_or(info.encoding); - - let result = match io.start( - &info.req, - self.resp.as_mut().unwrap(), - encoding, - ) { - Ok(res) => res, - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - }; - - if let Some(err) = self.resp.as_ref().unwrap().error() { - if self.resp.as_ref().unwrap().status().is_server_error() - { - error!( - "Error occurred during request handling, status: {} {}", - self.resp.as_ref().unwrap().status(), err - ); - } else { - warn!( - "Error occurred during request handling: {}", - err - ); - } - if log_enabled!(Debug) { - debug!("{:?}", err); - } - } - - // always poll stream or actor for the first time - match self.resp.as_mut().unwrap().replace_body(Body::Empty) { - Body::Streaming(stream) => { - self.iostate = IOState::Payload(stream); - continue 'inner; - } - Body::Actor(ctx) => { - self.iostate = IOState::Actor(ctx); - continue 'inner; - } - _ => (), - } - - result - } - IOState::Payload(mut body) => match body.poll() { - Ok(Async::Ready(None)) => { - if let Err(err) = io.write_eof() { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - break; - } - Ok(Async::Ready(Some(chunk))) => { - self.iostate = IOState::Payload(body); - match io.write(&chunk.into()) { - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - Ok(result) => result, - } - } - Ok(Async::NotReady) => { - self.iostate = IOState::Payload(body); - break; - } - Err(err) => { - info.error = Some(err); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - }, - IOState::Actor(mut ctx) => { - if info.disconnected.take().is_some() { - ctx.disconnected(); - } - match ctx.poll() { - Ok(Async::Ready(Some(vec))) => { - if vec.is_empty() { - self.iostate = IOState::Actor(ctx); - break; - } - let mut res = None; - for frame in vec { - match frame { - Frame::Chunk(None) => { - info.context = Some(ctx); - if let Err(err) = io.write_eof() { - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - ), - ); - } - break 'inner; - } - Frame::Chunk(Some(chunk)) => match io - .write(&chunk) - { - Err(err) => { - info.context = Some(ctx); - info.error = Some(err.into()); - return Ok( - FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - ), - ); - } - Ok(result) => res = Some(result), - }, - Frame::Drain(fut) => self.drain = Some(fut), - } - } - self.iostate = IOState::Actor(ctx); - if self.drain.is_some() { - self.running.resume(); - break 'inner; - } - res.unwrap() - } - Ok(Async::Ready(None)) => break, - Ok(Async::NotReady) => { - self.iostate = IOState::Actor(ctx); - break; - } - Err(err) => { - info.context = Some(ctx); - info.error = Some(err); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - IOState::Done => break, - }; - - match result { - WriterState::Pause => { - self.running.pause(); - break; - } - WriterState::Done => self.running.resume(), - } - } - } - - // flush io but only if we need to - if self.running == RunningState::Paused || self.drain.is_some() { - match io.poll_completed(false) { - Ok(Async::Ready(_)) => { - self.running.resume(); - - // resolve drain futures - if let Some(tx) = self.drain.take() { - let _ = tx.send(()); - } - // restart io processing - continue; - } - Ok(Async::NotReady) => return Err(PipelineState::Response(self)), - Err(err) => { - if let IOState::Actor(mut ctx) = - mem::replace(&mut self.iostate, IOState::Done) - { - ctx.disconnected(); - info.context = Some(ctx); - } - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - } - break; - } - - // response is completed - match self.iostate { - IOState::Done => { - match io.write_eof() { - Ok(_) => (), - Err(err) => { - info.error = Some(err.into()); - return Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )); - } - } - self.resp.as_mut().unwrap().set_response_size(io.written()); - Ok(FinishingMiddlewares::init( - info, - mws, - self.resp.take().unwrap(), - )) - } - _ => Err(PipelineState::Response(self)), - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, - _h: PhantomData, -} - -impl FinishingMiddlewares { - #[inline] - fn init( - info: &mut PipelineInfo, mws: &[Box>], resp: HttpResponse, - ) -> PipelineState { - if info.count == 0 { - resp.release(); - Completed::init(info) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - _h: PhantomData, - }; - if let Some(st) = state.poll(info, mws) { - st - } else { - PipelineState::Finishing(state) - } - } - } - - fn poll( - &mut self, info: &mut PipelineInfo, mws: &[Box>], - ) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - self.resp.take().unwrap().release(); - return Some(Completed::init(info)); - } - - info.count -= 1; - let state = - mws[info.count as usize].finish(&info.req, self.resp.as_ref().unwrap()); - match state { - Finished::Done => { - if info.count == 0 { - self.resp.take().unwrap().release(); - return Some(Completed::init(info)); - } - } - Finished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -#[derive(Debug)] -struct Completed(PhantomData, PhantomData); - -impl Completed { - #[inline] - fn init(info: &mut PipelineInfo) -> PipelineState { - if let Some(ref err) = info.error { - error!("Error occurred during request handling: {}", err); - } - - if info.context.is_none() { - PipelineState::None - } else { - match info.poll_context() { - Ok(Async::NotReady) => { - PipelineState::Completed(Completed(PhantomData, PhantomData)) - } - Ok(Async::Ready(())) => PipelineState::None, - Err(_) => PipelineState::Error, - } - } - } - - #[inline] - fn poll(&mut self, info: &mut PipelineInfo) -> Option> { - match info.poll_context() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(())) => Some(PipelineState::None), - Err(_) => Some(PipelineState::Error), - } - } -} diff --git a/src/pred.rs b/src/pred.rs deleted file mode 100644 index 99d6e608..00000000 --- a/src/pred.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! Route match predicates -#![allow(non_snake_case)] -use std::marker::PhantomData; - -use http; -use http::{header, HttpTryFrom}; -use server::message::Request; - -/// Trait defines resource route predicate. -/// Predicate can modify request object. It is also possible to -/// to store extra attributes on request by using `Extensions` container, -/// Extensions container available via `HttpRequest::extensions()` method. -pub trait Predicate { - /// Check if request matches predicate - fn check(&self, &Request, &S) -> bool; -} - -/// Return predicate that matches if any of supplied predicate matches. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Any(pred::Get()).or(pred::Post())) -/// .f(|r| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn Any + 'static>(pred: P) -> AnyPredicate { - AnyPredicate(vec![Box::new(pred)]) -} - -/// 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: &Request, state: &S) -> bool { - for p in &self.0 { - if p.check(req, state) { - return true; - } - } - false - } -} - -/// Return predicate that matches if all of supplied predicate matches. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter( -/// pred::All(pred::Get()) -/// .and(pred::Header("content-type", "text/plain")), -/// ) -/// .f(|_| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn All + 'static>(pred: P) -> AllPredicate { - AllPredicate(vec![Box::new(pred)]) -} - -/// 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: &Request, state: &S) -> bool { - for p in &self.0 { - if !p.check(req, state) { - return false; - } - } - true - } -} - -/// Return predicate that matches if supplied predicate does not match. -pub fn Not + 'static>(pred: P) -> NotPredicate { - NotPredicate(Box::new(pred)) -} - -#[doc(hidden)] -pub struct NotPredicate(Box>); - -impl Predicate for NotPredicate { - fn check(&self, req: &Request, state: &S) -> bool { - !self.0.check(req, state) - } -} - -/// Http method predicate -#[doc(hidden)] -pub struct MethodPredicate(http::Method, PhantomData); - -impl Predicate for MethodPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - *req.method() == self.0 - } -} - -/// Predicate to match *GET* http method -pub fn Get() -> MethodPredicate { - MethodPredicate(http::Method::GET, PhantomData) -} - -/// Predicate to match *POST* http method -pub fn Post() -> MethodPredicate { - MethodPredicate(http::Method::POST, PhantomData) -} - -/// Predicate to match *PUT* http method -pub fn Put() -> MethodPredicate { - MethodPredicate(http::Method::PUT, PhantomData) -} - -/// Predicate to match *DELETE* http method -pub fn Delete() -> MethodPredicate { - MethodPredicate(http::Method::DELETE, PhantomData) -} - -/// Predicate to match *HEAD* http method -pub fn Head() -> MethodPredicate { - MethodPredicate(http::Method::HEAD, PhantomData) -} - -/// Predicate to match *OPTIONS* http method -pub fn Options() -> MethodPredicate { - MethodPredicate(http::Method::OPTIONS, PhantomData) -} - -/// Predicate to match *CONNECT* http method -pub fn Connect() -> MethodPredicate { - MethodPredicate(http::Method::CONNECT, PhantomData) -} - -/// Predicate to match *PATCH* http method -pub fn Patch() -> MethodPredicate { - MethodPredicate(http::Method::PATCH, PhantomData) -} - -/// Predicate to match *TRACE* http method -pub fn Trace() -> MethodPredicate { - MethodPredicate(http::Method::TRACE, PhantomData) -} - -/// Predicate to match specified http method -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, -) -> HeaderPredicate { - HeaderPredicate( - header::HeaderName::try_from(name).unwrap(), - header::HeaderValue::from_static(value), - PhantomData, - ) -} - -#[doc(hidden)] -pub struct HeaderPredicate(header::HeaderName, header::HeaderValue, PhantomData); - -impl Predicate for HeaderPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - if let Some(val) = req.headers().get(&self.0) { - return val == self.1; - } - false - } -} - -/// Return predicate that matches if request contains specified Host name. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Host("www.rust-lang.org")) -/// .f(|_| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn Host>(host: H) -> HostPredicate { - HostPredicate(host.as_ref().to_string(), None, PhantomData) -} - -#[doc(hidden)] -pub struct HostPredicate(String, Option, PhantomData); - -impl HostPredicate { - /// Set reuest scheme to match - pub fn scheme>(&mut self, scheme: H) { - self.1 = Some(scheme.as_ref().to_string()) - } -} - -impl Predicate for HostPredicate { - fn check(&self, req: &Request, _: &S) -> bool { - let info = req.connection_info(); - if let Some(ref scheme) = self.1 { - self.0 == info.host() && scheme == info.scheme() - } else { - self.0 == info.host() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::{header, Method}; - use test::TestRequest; - - #[test] - fn test_header() { - let req = TestRequest::with_header( - header::TRANSFER_ENCODING, - header::HeaderValue::from_static("chunked"), - ).finish(); - - let pred = Header("transfer-encoding", "chunked"); - assert!(pred.check(&req, req.state())); - - let pred = Header("transfer-encoding", "other"); - assert!(!pred.check(&req, req.state())); - - let pred = Header("content-type", "other"); - assert!(!pred.check(&req, req.state())); - } - - #[test] - fn test_host() { - let req = TestRequest::default() - .header( - header::HOST, - header::HeaderValue::from_static("www.rust-lang.org"), - ).finish(); - - let pred = Host("www.rust-lang.org"); - assert!(pred.check(&req, req.state())); - - let pred = Host("localhost"); - assert!(!pred.check(&req, req.state())); - } - - #[test] - fn test_methods() { - let req = TestRequest::default().finish(); - let req2 = TestRequest::default().method(Method::POST).finish(); - - assert!(Get().check(&req, req.state())); - assert!(!Get().check(&req2, req2.state())); - assert!(Post().check(&req2, req2.state())); - assert!(!Post().check(&req, req.state())); - - let r = TestRequest::default().method(Method::PUT).finish(); - assert!(Put().check(&r, r.state())); - assert!(!Put().check(&req, req.state())); - - let r = TestRequest::default().method(Method::DELETE).finish(); - assert!(Delete().check(&r, r.state())); - assert!(!Delete().check(&req, req.state())); - - let r = TestRequest::default().method(Method::HEAD).finish(); - assert!(Head().check(&r, r.state())); - assert!(!Head().check(&req, req.state())); - - let r = TestRequest::default().method(Method::OPTIONS).finish(); - assert!(Options().check(&r, r.state())); - assert!(!Options().check(&req, req.state())); - - let r = TestRequest::default().method(Method::CONNECT).finish(); - assert!(Connect().check(&r, r.state())); - assert!(!Connect().check(&req, req.state())); - - let r = TestRequest::default().method(Method::PATCH).finish(); - assert!(Patch().check(&r, r.state())); - assert!(!Patch().check(&req, req.state())); - - let r = TestRequest::default().method(Method::TRACE).finish(); - assert!(Trace().check(&r, r.state())); - assert!(!Trace().check(&req, req.state())); - } - - #[test] - fn test_preds() { - let r = TestRequest::default().method(Method::TRACE).finish(); - - assert!(Not(Get()).check(&r, r.state())); - assert!(!Not(Trace()).check(&r, r.state())); - - assert!(All(Trace()).and(Trace()).check(&r, r.state())); - assert!(!All(Get()).and(Trace()).check(&r, r.state())); - - assert!(Any(Get()).or(Trace()).check(&r, r.state())); - assert!(!Any(Get()).or(Get()).check(&r, r.state())); - } -} diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 00000000..571431cc --- /dev/null +++ b/src/request.rs @@ -0,0 +1,174 @@ +use std::cell::{Ref, RefMut}; +use std::fmt; +use std::ops::Deref; +use std::rc::Rc; + +use actix_http::http::{HeaderMap, Method, Uri, Version}; +use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; +use actix_router::{Path, Url}; +use futures::future::{ok, FutureResult}; + +use crate::handler::FromRequest; +use crate::service::ServiceRequest; + +#[derive(Clone)] +pub struct HttpRequest { + head: Message, + pub(crate) path: Path, + extensions: Rc, +} + +impl HttpRequest { + #[inline] + pub fn new( + head: Message, + path: Path, + extensions: Rc, + ) -> HttpRequest { + HttpRequest { + head, + path, + extensions, + } + } +} + +impl HttpRequest { + /// This method returns reference to the request head + #[inline] + pub fn head(&self) -> &RequestHead { + &self.head + } + + /// Request's uri. + #[inline] + pub fn uri(&self) -> &Uri { + &self.head().uri + } + + /// Read the Request method. + #[inline] + pub fn method(&self) -> &Method { + &self.head().method + } + + /// Read the Request Version. + #[inline] + pub fn version(&self) -> Version { + self.head().version + } + + /// The target path of this Request. + #[inline] + pub fn path(&self) -> &str { + self.head().uri.path() + } + + #[inline] + /// Returns Request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head().headers + } + + /// The query string in the URL. + /// + /// E.g., id=10 + #[inline] + pub fn query_string(&self) -> &str { + if let Some(query) = self.uri().query().as_ref() { + query + } else { + "" + } + } + + /// Get a reference to the Path parameters. + /// + /// Params is a container for url parameters. + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. + #[inline] + pub fn match_info(&self) -> &Path { + &self.path + } + + /// Request extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.head.extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() + } + + /// Application extensions + #[inline] + pub fn app_extensions(&self) -> &Extensions { + &self.extensions + } + + // /// Get *ConnectionInfo* for the correct request. + // #[inline] + // pub fn connection_info(&self) -> Ref { + // ConnectionInfo::get(&*self) + // } +} + +impl Deref for HttpRequest { + type Target = RequestHead; + + fn deref(&self) -> &RequestHead { + self.head() + } +} + +impl HttpMessage for HttpRequest { + type Stream = (); + + #[inline] + fn headers(&self) -> &HeaderMap { + self.headers() + } + + #[inline] + fn take_payload(&mut self) -> Payload { + Payload::None + } +} + +impl

    FromRequest

    for HttpRequest { + type Error = Error; + type Future = FutureResult; + + #[inline] + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + ok(req.clone()) + } +} + +impl fmt::Debug for HttpRequest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "\nHttpRequest {:?} {}:{}", + self.head.version, + self.head.method, + self.path() + )?; + if !self.query_string().is_empty() { + writeln!(f, " query: ?{:?}", self.query_string())?; + } + if !self.match_info().is_empty() { + writeln!(f, " params: {:?}", self.match_info())?; + } + writeln!(f, " headers:")?; + for (key, val) in self.headers().iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} diff --git a/src/resource.rs b/src/resource.rs index d884dd44..88f7ae5a 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,82 +1,65 @@ -use std::ops::Deref; +use std::cell::RefCell; use std::rc::Rc; -use futures::Future; -use http::Method; -use smallvec::SmallVec; +use actix_http::{http::Method, Error, Response}; +use actix_service::{ + ApplyNewService, IntoNewService, IntoNewTransform, NewService, NewTransform, Service, +}; +use futures::future::{ok, Either, FutureResult}; +use futures::{Async, Future, IntoFuture, Poll}; -use error::Error; -use handler::{AsyncResult, FromRequest, Handler, Responder}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use pred; -use route::Route; -use router::ResourceDef; -use with::WithFactory; +use crate::handler::{AsyncFactory, Factory, FromRequest}; +use crate::helpers::{DefaultNewService, HttpDefaultNewService, HttpDefaultService}; +use crate::responder::Responder; +use crate::route::{CreateRouteService, Route, RouteBuilder, RouteService}; +use crate::service::{ServiceRequest, ServiceResponse}; -#[derive(Copy, Clone)] -pub(crate) struct RouteId(usize); - -/// *Resource* is an entry in route table which corresponds to requested URL. +/// Resource route definition /// -/// Resource in turn has at least one route. -/// 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 considered matched and route handler get called. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{App, HttpResponse, http}; -/// -/// fn main() { -/// let app = App::new() -/// .resource( -/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok())) -/// .finish(); -/// } -pub struct Resource { - rdef: ResourceDef, - routes: SmallVec<[Route; 3]>, - middlewares: Rc>>>, +/// If handler is not explicitly set, default *404 Not Found* handler is used. +pub struct Resource> { + routes: Vec>, + endpoint: T, + default: Rc< + RefCell, ServiceResponse>>>>, + >, + factory_ref: Rc>>>, } -impl Resource { - /// Create new resource with specified resource definition - pub fn new(rdef: ResourceDef) -> Self { +impl

    Resource

    { + pub fn new() -> Resource

    { + let fref = Rc::new(RefCell::new(None)); + Resource { - rdef, - routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()), + routes: Vec::new(), + endpoint: ResourceEndpoint::new(fref.clone()), + factory_ref: fref, + default: Rc::new(RefCell::new(None)), } } +} - /// Name of the resource - pub(crate) fn get_name(&self) -> &str { - self.rdef.name() - } - - /// Set resource name - pub fn name(&mut self, name: &str) { - self.rdef.set_name(name); - } - - /// Resource definition - pub fn rdef(&self) -> &ResourceDef { - &self.rdef +impl

    Default for Resource

    { + fn default() -> Self { + Self::new() } } -impl Resource { +impl Resource +where + T: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, +{ /// 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; + /// ```rust,ignore /// use actix_web::*; /// /// fn main() { @@ -90,44 +73,72 @@ impl Resource { /// .finish(); /// } /// ``` - pub fn route(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap() + pub fn route(mut self, f: F) -> Self + where + F: FnOnce(RouteBuilder

    ) -> Route

    , + { + self.routes.push(f(Route::build())); + self } /// Register a new `GET` route. - pub fn get(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Get()) + pub fn get(mut self, f: F) -> Self + where + F: Factory + 'static, + I: FromRequest

    + 'static, + R: Responder + 'static, + { + self.routes.push(Route::get().to(f)); + self } /// Register a new `POST` route. - pub fn post(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Post()) + pub fn post(mut self, f: F) -> Self + where + F: Factory + 'static, + I: FromRequest

    + 'static, + R: Responder + 'static, + { + self.routes.push(Route::post().to(f)); + self } /// Register a new `PUT` route. - pub fn put(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Put()) + pub fn put(mut self, f: F) -> Self + where + F: Factory + 'static, + I: FromRequest

    + 'static, + R: Responder + 'static, + { + self.routes.push(Route::put().to(f)); + self } /// Register a new `DELETE` route. - pub fn delete(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Delete()) + pub fn delete(mut self, f: F) -> Self + where + F: Factory + 'static, + I: FromRequest

    + 'static, + R: Responder + 'static, + { + self.routes.push(Route::delete().to(f)); + self } /// Register a new `HEAD` route. - pub fn head(&mut self) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Head()) + pub fn head(mut self, f: F) -> Self + where + F: Factory + 'static, + I: FromRequest

    + 'static, + R: Responder + 'static, + { + self.routes.push(Route::build().method(Method::HEAD).to(f)); + self } /// Register a new route and add method check to route. /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::*; /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } @@ -137,70 +148,23 @@ impl Resource { /// /// This is shortcut for: /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index)); /// ``` - pub fn method(&mut self, method: Method) -> &mut Route { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().filter(pred::Method(method)) - } - - /// Register a new route and add handler object. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.h(handler)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn handler(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().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. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.f(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().f(index)); - /// ``` - pub fn f(&mut self, handler: F) + pub fn method(mut self, method: Method, f: F) -> Self where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, + F: FnOnce(RouteBuilder

    ) -> Route

    , { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().f(handler) + self.routes.push(f(Route::build().method(method))); + self } /// Register a new route and add handler. /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// use actix_web::*; /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } @@ -210,25 +174,25 @@ impl Resource { /// /// This is shortcut for: /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().resource("/", |r| r.route().with(index)); /// ``` - pub fn with(&mut self, handler: F) + pub fn to(mut self, handler: F) -> Self where - F: WithFactory, + F: Factory + 'static, + I: FromRequest

    + 'static, R: Responder + 'static, - T: FromRequest + 'static, { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with(handler); + self.routes.push(Route::build().to(handler)); + self } /// Register a new route and add async handler. /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # extern crate futures; /// use actix_web::*; @@ -243,7 +207,7 @@ impl Resource { /// /// This is shortcut for: /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # extern crate futures; /// # use actix_web::*; @@ -253,72 +217,259 @@ impl Resource { /// # } /// App::new().resource("/", |r| r.route().with_async(index)); /// ``` - pub fn with_async(&mut self, handler: F) + #[allow(clippy::wrong_self_convention)] + pub fn to_async(mut self, handler: F) -> Self where - F: Fn(T) -> R + 'static, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, + F: AsyncFactory, + I: FromRequest

    + 'static, + R: IntoFuture + 'static, + R::Item: Into, + R::Error: Into, { - self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with_async(handler); + self.routes.push(Route::build().to_async(handler)); + self } /// Register a resource middleware /// /// This is similar to `App's` middlewares, but /// middlewares get invoked on resource level. - /// - /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to peer. - pub fn middleware>(&mut self, mw: M) { - Rc::get_mut(&mut self.middlewares) - .unwrap() - .push(Box::new(mw)); + pub fn middleware( + self, + mw: F, + ) -> Resource< + P, + impl NewService< + Request = ServiceRequest

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

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, + F: IntoNewTransform, + { + let endpoint = ApplyNewService::new(mw, self.endpoint); + Resource { + endpoint, + routes: self.routes, + default: self.default, + factory_ref: self.factory_ref, + } } - #[inline] - pub(crate) fn get_route_id(&self, req: &HttpRequest) -> Option { - for idx in 0..self.routes.len() { - if (&self.routes[idx]).check(req) { - return Some(RouteId(idx)); + /// Default resource to be used if no matching route could be found. + pub fn default_resource(mut self, f: F) -> Self + where + F: FnOnce(Resource

    ) -> R, + R: IntoNewService, + U: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + > + 'static, + { + // create and configure default resource + self.default = Rc::new(RefCell::new(Some(Rc::new(Box::new( + DefaultNewService::new(f(Resource::new()).into_new_service()), + ))))); + + self + } + + pub(crate) fn get_default( + &self, + ) -> Rc, ServiceResponse>>>>> + { + self.default.clone() + } +} + +impl IntoNewService for Resource +where + T: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, +{ + fn into_new_service(self) -> T { + *self.factory_ref.borrow_mut() = Some(ResourceFactory { + routes: self.routes, + default: self.default, + }); + + self.endpoint + } +} + +pub struct ResourceFactory

    { + routes: Vec>, + default: Rc< + RefCell, ServiceResponse>>>>, + >, +} + +impl

    NewService for ResourceFactory

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = ResourceService

    ; + type Future = CreateResourceService

    ; + + fn new_service(&self, _: &()) -> Self::Future { + let default_fut = if let Some(ref default) = *self.default.borrow() { + Some(default.new_service(&())) + } else { + None + }; + + CreateResourceService { + fut: self + .routes + .iter() + .map(|route| CreateRouteServiceItem::Future(route.new_service(&()))) + .collect(), + default: None, + default_fut, + } + } +} + +enum CreateRouteServiceItem

    { + Future(CreateRouteService

    ), + Service(RouteService

    ), +} + +pub struct CreateResourceService

    { + fut: Vec>, + default: Option, ServiceResponse>>, + default_fut: Option< + Box< + Future< + Item = HttpDefaultService, ServiceResponse>, + Error = (), + >, + >, + >, +} + +impl

    Future for CreateResourceService

    { + type Item = ResourceService

    ; + type Error = (); + + fn poll(&mut self) -> Poll { + let mut done = true; + + if let Some(ref mut fut) = self.default_fut { + match fut.poll()? { + Async::Ready(default) => self.default = Some(default), + Async::NotReady => done = false, } } - None - } - #[inline] - pub(crate) fn handle( - &self, id: RouteId, req: &HttpRequest, - ) -> AsyncResult { - if self.middlewares.is_empty() { - (&self.routes[id.0]).handle(req) + // poll http services + for item in &mut self.fut { + match item { + CreateRouteServiceItem::Future(ref mut fut) => match fut.poll()? { + Async::Ready(route) => { + *item = CreateRouteServiceItem::Service(route) + } + Async::NotReady => { + done = false; + } + }, + CreateRouteServiceItem::Service(_) => continue, + }; + } + + if done { + let routes = self + .fut + .drain(..) + .map(|item| match item { + CreateRouteServiceItem::Service(service) => service, + CreateRouteServiceItem::Future(_) => unreachable!(), + }) + .collect(); + Ok(Async::Ready(ResourceService { + routes, + default: self.default.take(), + })) } else { - (&self.routes[id.0]).compose(req.clone(), Rc::clone(&self.middlewares)) + Ok(Async::NotReady) } } } -/// Default resource -pub struct DefaultResource(Rc>); +pub struct ResourceService

    { + routes: Vec>, + default: Option, ServiceResponse>>, +} -impl Deref for DefaultResource { - type Target = Resource; +impl

    Service for ResourceService

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type Future = Either< + Box>, + Either< + Box>, + FutureResult, + >, + >; - fn deref(&self) -> &Resource { - self.0.as_ref() + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, mut req: ServiceRequest

    ) -> Self::Future { + for route in self.routes.iter_mut() { + if route.check(&mut req) { + return Either::A(route.call(req)); + } + } + if let Some(ref mut default) = self.default { + Either::B(Either::A(default.call(req))) + } else { + let req = req.into_request(); + Either::B(Either::B(ok(ServiceResponse::new( + req, + Response::NotFound().finish(), + )))) + } } } -impl Clone for DefaultResource { - fn clone(&self) -> Self { - DefaultResource(self.0.clone()) +#[doc(hidden)] +pub struct ResourceEndpoint

    { + factory: Rc>>>, +} + +impl

    ResourceEndpoint

    { + fn new(factory: Rc>>>) -> Self { + ResourceEndpoint { factory } } } -impl From> for DefaultResource { - fn from(res: Resource) -> Self { - DefaultResource(Rc::new(res)) +impl

    NewService for ResourceEndpoint

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = ResourceService

    ; + type Future = CreateResourceService

    ; + + fn new_service(&self, _: &()) -> Self::Future { + self.factory.borrow_mut().as_mut().unwrap().new_service(&()) } } diff --git a/src/responder.rs b/src/responder.rs new file mode 100644 index 00000000..5520c610 --- /dev/null +++ b/src/responder.rs @@ -0,0 +1,259 @@ +use actix_http::dev::ResponseBuilder; +use actix_http::http::StatusCode; +use actix_http::{Error, Response}; +use bytes::{Bytes, BytesMut}; +use futures::future::{err, ok, Either as EitherFuture, FutureResult}; +use futures::{Future, Poll}; + +use crate::request::HttpRequest; + +/// Trait implemented by types that generate http responses. +/// +/// Types that implement this trait can be used as the return type of a handler. +pub trait Responder { + /// The associated error which can be returned. + type Error: Into; + + /// The future response value. + type Future: Future; + + /// Convert itself to `AsyncResult` or `Error`. + fn respond_to(self, req: &HttpRequest) -> Self::Future; +} + +impl Responder for Response { + type Error = Error; + type Future = FutureResult; + + #[inline] + fn respond_to(self, _: &HttpRequest) -> Self::Future { + ok(self) + } +} + +impl Responder for Option +where + T: Responder, +{ + type Error = T::Error; + type Future = EitherFuture>; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + match self { + Some(t) => EitherFuture::A(t.respond_to(req)), + None => EitherFuture::B(ok(Response::build(StatusCode::NOT_FOUND).finish())), + } + } +} + +impl Responder for Result +where + T: Responder, + E: Into, +{ + type Error = Error; + type Future = EitherFuture, FutureResult>; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + match self { + Ok(val) => EitherFuture::A(ResponseFuture::new(val.respond_to(req))), + Err(e) => EitherFuture::B(err(e.into())), + } + } +} + +impl Responder for ResponseBuilder { + type Error = Error; + type Future = FutureResult; + + #[inline] + fn respond_to(mut self, _: &HttpRequest) -> Self::Future { + ok(self.finish()) + } +} + +impl Responder for &'static str { + type Error = Error; + type Future = FutureResult; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + ok(Response::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self)) + } +} + +impl Responder for &'static [u8] { + type Error = Error; + type Future = FutureResult; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + ok(Response::build(StatusCode::OK) + .content_type("application/octet-stream") + .body(self)) + } +} + +impl Responder for String { + type Error = Error; + type Future = FutureResult; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + ok(Response::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self)) + } +} + +impl<'a> Responder for &'a String { + type Error = Error; + type Future = FutureResult; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + ok(Response::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self)) + } +} + +impl Responder for Bytes { + type Error = Error; + type Future = FutureResult; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + ok(Response::build(StatusCode::OK) + .content_type("application/octet-stream") + .body(self)) + } +} + +impl Responder for BytesMut { + type Error = Error; + type Future = FutureResult; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + ok(Response::build(StatusCode::OK) + .content_type("application/octet-stream") + .body(self)) + } +} + +/// Combines two different responder types into a single type +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// # extern crate futures; +/// # use futures::future::Future; +/// use actix_web::{AsyncResponder, Either, Error, Request, Response}; +/// use futures::future::result; +/// +/// type RegisterResult = +/// Either>>; +/// +/// fn index(req: Request) -> RegisterResult { +/// if is_a_variant() { +/// // <- choose variant A +/// Either::A(Response::BadRequest().body("Bad data")) +/// } else { +/// Either::B( +/// // <- variant B +/// result(Ok(Response::Ok() +/// .content_type("text/html") +/// .body("Hello!"))) +/// .responder(), +/// ) +/// } +/// } +/// # fn is_a_variant() -> bool { true } +/// # fn main() {} +/// ``` +pub enum Either { + /// First branch of the type + A(A), + /// Second branch of the type + B(B), +} + +impl Responder for Either +where + A: Responder, + B: Responder, +{ + type Error = Error; + type Future = EitherResponder; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + match self { + Either::A(a) => EitherResponder::A(a.respond_to(req)), + Either::B(b) => EitherResponder::B(b.respond_to(req)), + } + } +} + +pub enum EitherResponder +where + A: Future, + A::Error: Into, + B: Future, + B::Error: Into, +{ + A(A), + B(B), +} + +impl Future for EitherResponder +where + A: Future, + A::Error: Into, + B: Future, + B::Error: Into, +{ + type Item = Response; + type Error = Error; + + fn poll(&mut self) -> Poll { + match self { + EitherResponder::A(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?), + EitherResponder::B(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?), + } + } +} + +impl Responder for Box> +where + I: Responder + 'static, + E: Into + 'static, +{ + type Error = Error; + type Future = Box>; + + #[inline] + fn respond_to(self, req: &HttpRequest) -> Self::Future { + let req = req.clone(); + Box::new( + self.map_err(|e| e.into()) + .and_then(move |r| ResponseFuture(r.respond_to(&req))), + ) + } +} + +pub struct ResponseFuture(T); + +impl ResponseFuture { + pub fn new(fut: T) -> Self { + ResponseFuture(fut) + } +} + +impl Future for ResponseFuture +where + T: Future, + T::Error: Into, +{ + type Item = Response; + type Error = Error; + + fn poll(&mut self) -> Poll { + Ok(self.0.poll().map_err(|e| e.into())?) + } +} diff --git a/src/route.rs b/src/route.rs index 884a367e..574e8e34 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,68 +1,153 @@ use std::marker::PhantomData; use std::rc::Rc; -use futures::{Async, Future, Poll}; +use actix_http::{http::Method, Error, Response}; +use actix_service::{NewService, Service}; +use futures::{Async, Future, IntoFuture, Poll}; -use error::Error; -use handler::{ - AsyncHandler, AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, - RouteHandler, WrapHandler, -}; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{ - Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted, -}; -use pred::Predicate; -use with::{WithAsyncFactory, WithFactory}; +use crate::filter::{self, Filter}; +use crate::handler::{AsyncFactory, AsyncHandle, Extract, Factory, FromRequest, Handle}; +use crate::responder::Responder; +use crate::service::{ServiceRequest, ServiceResponse}; + +type BoxedRouteService = Box< + Service< + Request = Req, + Response = Res, + Error = (), + Future = Box>, + >, +>; + +type BoxedRouteNewService = Box< + NewService< + Request = Req, + Response = Res, + Error = (), + InitError = (), + Service = BoxedRouteService, + Future = Box, Error = ()>>, + >, +>; /// 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: InnerHandler, +pub struct Route

    { + service: BoxedRouteNewService, ServiceResponse>, + filters: Rc>>, } -impl Default for Route { - fn default() -> Route { - Route { - preds: Vec::new(), - handler: InnerHandler::new(|_: &_| HttpResponse::new(StatusCode::NOT_FOUND)), +impl Route

    { + pub fn build() -> RouteBuilder

    { + RouteBuilder::new() + } + + pub fn get() -> RouteBuilder

    { + RouteBuilder::new().method(Method::GET) + } + + pub fn post() -> RouteBuilder

    { + RouteBuilder::new().method(Method::POST) + } + + pub fn put() -> RouteBuilder

    { + RouteBuilder::new().method(Method::PUT) + } + + pub fn delete() -> RouteBuilder

    { + RouteBuilder::new().method(Method::DELETE) + } +} + +impl

    NewService for Route

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = RouteService

    ; + type Future = CreateRouteService

    ; + + fn new_service(&self, _: &()) -> Self::Future { + CreateRouteService { + fut: self.service.new_service(&()), + filters: self.filters.clone(), } } } -impl Route { - #[inline] - pub(crate) fn check(&self, req: &HttpRequest) -> bool { - let state = req.state(); - for pred in &self.preds { - if !pred.check(req, state) { +type RouteFuture

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

    { + fut: RouteFuture

    , + filters: Rc>>, +} + +impl

    Future for CreateRouteService

    { + type Item = RouteService

    ; + type Error = (); + + fn poll(&mut self) -> Poll { + match self.fut.poll()? { + Async::Ready(service) => Ok(Async::Ready(RouteService { + service, + filters: self.filters.clone(), + })), + Async::NotReady => Ok(Async::NotReady), + } + } +} + +pub struct RouteService

    { + service: BoxedRouteService, ServiceResponse>, + filters: Rc>>, +} + +impl

    RouteService

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

    ) -> bool { + for f in self.filters.iter() { + if !f.check(req.request()) { return false; } } true } +} - #[inline] - pub(crate) fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.handler.handle(req) +impl

    Service for RouteService

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() } - #[inline] - pub(crate) fn compose( - &self, req: HttpRequest, mws: Rc>>>, - ) -> AsyncResult { - AsyncResult::future(Box::new(Compose::new(req, mws, self.handler.clone()))) + fn call(&mut self, req: Self::Request) -> Self::Future { + self.service.call(req) + } +} + +pub struct RouteBuilder

    { + filters: Vec>, + _t: PhantomData

    , +} + +impl RouteBuilder

    { + fn new() -> RouteBuilder

    { + RouteBuilder { + filters: Vec::new(), + _t: PhantomData, + } } - /// Add match predicate to route. + /// Add method match filter to the route. /// - /// ```rust + /// ```rust,ignore /// # extern crate actix_web; /// # use actix_web::*; /// # fn main() { @@ -75,41 +160,52 @@ impl Route { /// # .finish(); /// # } /// ``` - pub fn filter + 'static>(&mut self, p: T) -> &mut Self { - self.preds.push(Box::new(p)); + pub fn method(mut self, method: Method) -> Self { + self.filters.push(Box::new(filter::Method(method))); self } - /// Set handler object. Usually call to this method is last call - /// during route configuration, so it does not return reference to self. - pub fn h>(&mut self, handler: H) { - self.handler = InnerHandler::new(handler); + /// Add filter to the route. + /// + /// ```rust,ignore + /// # extern crate actix_web; + /// # use actix_web::*; + /// # fn main() { + /// App::new().resource("/path", |r| { + /// r.route() + /// .filter(pred::Get()) + /// .filter(pred::Header("content-type", "text/plain")) + /// .f(|req| HttpResponse::Ok()) + /// }) + /// # .finish(); + /// # } + /// ``` + pub fn filter(&mut self, f: F) -> &mut Self { + self.filters.push(Box::new(f)); + self } - /// Set handler function. Usually call to this method is last call - /// during route configuration, so it does not return reference to self. - pub fn f(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.handler = InnerHandler::new(handler); - } - - /// Set async handler function. - pub fn a(&mut self, handler: H) - where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - self.handler = InnerHandler::async(handler); - } + // pub fn map>( + // self, + // md: F, + // ) -> RouteServiceBuilder + // where + // T: NewService< + // Request = HandlerRequest, + // Response = HandlerRequest, + // InitError = (), + // >, + // { + // RouteServiceBuilder { + // service: md.into_new_service(), + // filters: self.filters, + // _t: PhantomData, + // } + // } /// Set handler function, use request extractor for parameters. /// - /// ```rust + /// ```rust,ignore /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; @@ -136,7 +232,7 @@ impl Route { /// /// It is possible to use multiple extractors for one handler function. /// - /// ```rust + /// ```rust,ignore /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; @@ -163,56 +259,25 @@ impl Route { /// ); // <- use `with` extractor /// } /// ``` - pub fn with(&mut self, handler: F) + pub fn to(self, handler: F) -> Route

    where - F: WithFactory + 'static, + F: Factory + 'static, + T: FromRequest

    + 'static, R: Responder + 'static, - T: FromRequest + 'static, { - self.h(handler.create()); - } - - /// Set handler function. Same as `.with()` but it allows to configure - /// extractor. Configuration closure accepts config objects as tuple. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Path, Result}; - /// - /// /// extract text data from request - /// fn index(body: String) -> Result { - /// Ok(format!("Body {}!", body)) - /// } - /// - /// fn main() { - /// let app = App::new().resource("/index.html", |r| { - /// r.method(http::Method::GET) - /// .with_config(index, |cfg| { // <- register handler - /// cfg.0.limit(4096); // <- limit size of the payload - /// }) - /// }); - /// } - /// ``` - pub fn with_config(&mut self, handler: F, cfg_f: C) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - C: FnOnce(&mut T::Config), - { - let mut cfg = ::default(); - cfg_f(&mut cfg); - self.h(handler.create_with_config(cfg)); + Route { + service: Box::new(RouteNewService::new( + Extract::new().and_then(Handle::new(handler).map_err(|_| panic!())), + )), + filters: Rc::new(self.filters), + } } /// Set async handler function, use request extractor for parameters. /// Also this method needs to be used if your handler function returns /// `impl Future<>` /// - /// ```rust + /// ```rust,ignore /// # extern crate bytes; /// # extern crate actix_web; /// # extern crate futures; @@ -237,430 +302,233 @@ impl Route { /// ); // <- use `with` extractor /// } /// ``` - pub fn with_async(&mut self, handler: F) + #[allow(clippy::wrong_self_convention)] + pub fn to_async(self, handler: F) -> Route

    where - F: WithAsyncFactory, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, + F: AsyncFactory, + T: FromRequest

    + 'static, + R: IntoFuture + 'static, + R::Item: Into, + R::Error: Into, { - self.h(handler.create()); - } - - /// Set async handler function, use request extractor for parameters. - /// This method allows to configure extractor. Configuration closure - /// accepts config objects as tuple. - /// - /// ```rust - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Form}; - /// use futures::Future; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// fn index(info: Form) -> Box> { - /// unimplemented!() - /// } - /// - /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET) - /// .with_async_config(index, |cfg| { - /// cfg.0.limit(4096); - /// }), - /// ); // <- use `with` extractor - /// } - /// ``` - pub fn with_async_config(&mut self, handler: F, cfg: C) - where - F: WithAsyncFactory, - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - C: FnOnce(&mut T::Config), - { - let mut extractor_cfg = ::default(); - cfg(&mut extractor_cfg); - self.h(handler.create_with_config(extractor_cfg)); - } -} - -/// `RouteHandler` wrapper. This struct is required because it needs to be -/// shared for resource level middlewares. -struct InnerHandler(Rc>>); - -impl InnerHandler { - #[inline] - fn new>(h: H) -> Self { - InnerHandler(Rc::new(Box::new(WrapHandler::new(h)))) - } - - #[inline] - fn async(h: H) -> Self - where - H: Fn(&HttpRequest) -> F + 'static, - F: Future + 'static, - R: Responder + 'static, - E: Into + 'static, - { - InnerHandler(Rc::new(Box::new(AsyncHandler::new(h)))) - } - - #[inline] - pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - self.0.handle(req) - } -} - -impl Clone for InnerHandler { - #[inline] - fn clone(&self) -> Self { - InnerHandler(Rc::clone(&self.0)) - } -} - -/// Compose resource level middlewares with route handler. -struct Compose { - info: ComposeInfo, - state: ComposeState, -} - -struct ComposeInfo { - count: usize, - req: HttpRequest, - mws: Rc>>>, - handler: InnerHandler, -} - -enum ComposeState { - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Finishing(FinishingMiddlewares), - Completed(Response), -} - -impl ComposeState { - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match *self { - ComposeState::Starting(ref mut state) => state.poll(info), - ComposeState::Handler(ref mut state) => state.poll(info), - ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Finishing(ref mut state) => state.poll(info), - ComposeState::Completed(_) => None, + Route { + service: Box::new(RouteNewService::new( + Extract::new().and_then(AsyncHandle::new(handler).map_err(|_| panic!())), + )), + filters: Rc::new(self.filters), } } } -impl Compose { - fn new( - req: HttpRequest, mws: Rc>>>, handler: InnerHandler, - ) -> Self { - let mut info = ComposeInfo { - count: 0, - req, - mws, - handler, - }; - let state = StartMiddlewares::init(&mut info); +pub struct RouteServiceBuilder { + service: T, + filters: Vec>, + _t: PhantomData<(P, U1, U2)>, +} - Compose { state, info } +// impl RouteServiceBuilder +// where +// T: NewService< +// Request = HandlerRequest, +// Response = HandlerRequest, +// Error = Error, +// InitError = (), +// >, +// { +// pub fn new>(factory: F) -> Self { +// RouteServiceBuilder { +// service: factory.into_new_service(), +// filters: Vec::new(), +// _t: PhantomData, +// } +// } + +// /// Add method match filter to the route. +// /// +// /// ```rust +// /// # extern crate actix_web; +// /// # use actix_web::*; +// /// # fn main() { +// /// App::new().resource("/path", |r| { +// /// r.route() +// /// .filter(pred::Get()) +// /// .filter(pred::Header("content-type", "text/plain")) +// /// .f(|req| HttpResponse::Ok()) +// /// }) +// /// # .finish(); +// /// # } +// /// ``` +// pub fn method(mut self, method: Method) -> Self { +// self.filters.push(Box::new(filter::Method(method))); +// self +// } + +// /// Add filter to the route. +// /// +// /// ```rust +// /// # extern crate actix_web; +// /// # use actix_web::*; +// /// # fn main() { +// /// App::new().resource("/path", |r| { +// /// r.route() +// /// .filter(pred::Get()) +// /// .filter(pred::Header("content-type", "text/plain")) +// /// .f(|req| HttpResponse::Ok()) +// /// }) +// /// # .finish(); +// /// # } +// /// ``` +// pub fn filter + 'static>(&mut self, f: F) -> &mut Self { +// self.filters.push(Box::new(f)); +// self +// } + +// pub fn map>( +// self, +// md: F, +// ) -> RouteServiceBuilder< +// impl NewService< +// Request = HandlerRequest, +// Response = HandlerRequest, +// Error = Error, +// InitError = (), +// >, +// S, +// U1, +// U2, +// > +// where +// T1: NewService< +// Request = HandlerRequest, +// Response = HandlerRequest, +// InitError = (), +// >, +// T1::Error: Into, +// { +// RouteServiceBuilder { +// service: self +// .service +// .and_then(md.into_new_service().map_err(|e| e.into())), +// filters: self.filters, +// _t: PhantomData, +// } +// } + +// pub fn to_async(self, handler: F) -> Route +// where +// F: AsyncFactory, +// P: FromRequest + 'static, +// R: IntoFuture, +// R::Item: Into, +// R::Error: Into, +// { +// Route { +// service: self +// .service +// .and_then(Extract::new(P::Config::default())) +// .then(AsyncHandle::new(handler)), +// filters: Rc::new(self.filters), +// } +// } + +// pub fn to(self, handler: F) -> Route +// where +// F: Factory + 'static, +// P: FromRequest + 'static, +// R: Responder + 'static, +// { +// Route { +// service: Box::new(RouteNewService::new( +// self.service +// .and_then(Extract::new(P::Config::default())) +// .and_then(Handle::new(handler)), +// )), +// filters: Rc::new(self.filters), +// } +// } +// } + +struct RouteNewService +where + T: NewService, Error = (Error, ServiceRequest

    )>, +{ + service: T, +} + +impl RouteNewService +where + T: NewService< + Request = ServiceRequest

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

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

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

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

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = BoxedRouteService; + type Future = Box>; - fn poll(&mut self) -> Poll { - loop { - if let ComposeState::Completed(ref mut resp) = self.state { - let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)); - } - if let Some(state) = self.state.poll(&mut self.info) { - self.state = state; - } else { - return Ok(Async::NotReady); - } - } + fn new_service(&self, _: &()) -> Self::Future { + Box::new( + self.service + .new_service(&()) + .map_err(|_| ()) + .and_then(|service| { + let service: BoxedRouteService<_, _> = + Box::new(RouteServiceWrapper { service }); + Ok(service) + }), + ) } } -/// Middlewares start executor -struct StartMiddlewares { - fut: Option, - _s: PhantomData, +struct RouteServiceWrapper>> { + service: T, } -type Fut = Box, Error = Error>>; +impl Service for RouteServiceWrapper +where + T::Future: 'static, + T: Service< + Request = ServiceRequest

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

    ), + >, +{ + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type Future = Box>; -impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); - - loop { - if info.count == len { - let reply = info.handler.handle(&info.req); - return WaitingResponse::init(info, reply); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp); - } - Ok(MiddlewareStarted::Future(fut)) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }); - } - Err(err) => { - return RunMiddlewares::init(info, err.into()); - } - } - } - } + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready().map_err(|_| ()) } - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, resp)); - } - loop { - if info.count == len { - let reply = info.handler.handle(&info.req); - return Some(WaitingResponse::init(info, reply)); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return Some(RunMiddlewares::init(info, resp)); - } - Ok(MiddlewareStarted::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } -} - -type HandlerFuture = Future; - -// waiting for response -struct WaitingResponse { - fut: Box, - _s: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut ComposeInfo, reply: AsyncResult, - ) -> ComposeState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), - AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { - fut, - _s: PhantomData, - }), - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), - Err(err) => Some(RunMiddlewares::init(info, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, -} - -impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { - let mut curr = 0; - let len = info.mws.len(); - - loop { - let state = info.mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = curr + 1; - return FinishingMiddlewares::init(info, err.into()); - } - Ok(MiddlewareResponse::Done(r)) => { - curr += 1; - if curr == len { - return FinishingMiddlewares::init(info, r); - } else { - r - } - } - Ok(MiddlewareResponse::Future(fut)) => { - return ComposeState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - }); - } - }; - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), - }; - - loop { - if self.curr == len { - return Some(FinishingMiddlewares::init(info, resp)); - } else { - let state = info.mws[self.curr].response(&info.req, resp); - match state { - Err(err) => { - return Some(FinishingMiddlewares::init(info, err.into())) - } - Ok(MiddlewareResponse::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(MiddlewareResponse::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, -} - -impl FinishingMiddlewares { - fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { - if info.count == 0 { - Response::init(resp) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - }; - if let Some(st) = state.poll(info) { - st - } else { - ComposeState::Finishing(state) - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - - info.count -= 1; - - let state = info.mws[info.count as usize] - .finish(&info.req, self.resp.as_ref().unwrap()); - match state { - MiddlewareFinished::Done => { - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - } - MiddlewareFinished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -struct Response { - resp: Option, - _s: PhantomData, -} - -impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Completed(Response { - resp: Some(resp), - _s: PhantomData, - }) + fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { + Box::new(self.service.call(req).then(|res| match res { + Ok(res) => Ok(res), + Err((err, req)) => Ok(req.error_response(err)), + })) } } diff --git a/src/router.rs b/src/router.rs deleted file mode 100644 index aa15e46d..00000000 --- a/src/router.rs +++ /dev/null @@ -1,1247 +0,0 @@ -use std::cell::RefCell; -use std::cmp::min; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; -use std::rc::Rc; - -use regex::{escape, Regex}; -use url::Url; - -use error::UrlGenerationError; -use handler::{AsyncResult, FromRequest, Responder, RouteHandler}; -use http::{Method, StatusCode}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use param::{ParamItem, Params}; -use pred::Predicate; -use resource::{DefaultResource, Resource}; -use scope::Scope; -use server::Request; -use with::WithFactory; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum ResourceId { - Default, - Normal(u16), -} - -enum ResourcePattern { - Resource(ResourceDef), - Handler(ResourceDef, Option>>>), - Scope(ResourceDef, Vec>>), -} - -enum ResourceItem { - Resource(Resource), - Handler(Box>), - Scope(Scope), -} - -/// Interface for application router. -pub struct Router { - rmap: Rc, - patterns: Vec>, - resources: Vec>, - default: Option>, -} - -/// Information about current resource -#[derive(Clone)] -pub struct ResourceInfo { - rmap: Rc, - resource: ResourceId, - params: Params, - prefix: u16, -} - -impl ResourceInfo { - /// Name os the resource - #[inline] - pub fn name(&self) -> &str { - if let ResourceId::Normal(idx) = self.resource { - self.rmap.patterns[idx as usize].0.name() - } else { - "" - } - } - - /// This method returns reference to matched `ResourceDef` object. - #[inline] - pub fn rdef(&self) -> Option<&ResourceDef> { - if let ResourceId::Normal(idx) = self.resource { - Some(&self.rmap.patterns[idx as usize].0) - } else { - None - } - } - - pub(crate) fn set_prefix(&mut self, prefix: u16) { - self.prefix = prefix; - } - - /// Get a reference to the Params object. - /// - /// Params is a container for url parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. - #[inline] - pub fn match_info(&self) -> &Params { - &self.params - } - - #[inline] - pub(crate) fn merge(&mut self, info: &ResourceInfo) { - let mut p = info.params.clone(); - p.set_tail(self.params.tail); - for item in &self.params.segments { - p.add(item.0.clone(), item.1.clone()); - } - - self.prefix = info.params.tail; - self.params = p; - } - - /// Generate url for named resource - /// - /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. - /// url_for) for detailed information. - pub fn url_for( - &self, req: &Request, name: &str, elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - let mut path = String::new(); - let mut elements = elements.into_iter(); - - if self - .rmap - .patterns_for(name, &mut path, &mut elements)? - .is_some() - { - if path.starts_with('/') { - let conn = req.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) - } else { - Ok(Url::parse(&path)?) - } - } else { - Err(UrlGenerationError::ResourceNotFound) - } - } - - /// Check if application contains matching resource. - /// - /// 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_resource()` call - /// would return `false`. - pub fn has_resource(&self, path: &str) -> bool { - self.rmap.has_resource(path) - } - - /// Check if application contains matching resource. - /// - /// This method does take `prefix` into account - /// but behaves like `has_route` in case `prefix` is not set in the router. - /// - /// For example if prefix is `/test` and router contains route `/name`, the - /// following path would be recognizable `/test/name` and `has_prefixed_route()` call - /// would return `true`. - /// It will not match against prefix in case it's not given. For example for `/name` - /// with a `/test` prefix would return `false` - pub fn has_prefixed_resource(&self, path: &str) -> bool { - let prefix = self.prefix as usize; - if prefix >= path.len() { - return false; - } - self.rmap.has_resource(&path[prefix..]) - } -} - -pub(crate) struct ResourceMap { - root: ResourceDef, - parent: RefCell>>, - named: HashMap, - patterns: Vec<(ResourceDef, Option>)>, - nested: Vec>, -} - -impl ResourceMap { - fn has_resource(&self, path: &str) -> bool { - let path = if path.is_empty() { "/" } else { path }; - - for (pattern, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if let Some(plen) = pattern.is_prefix_match(path) { - return rmap.has_resource(&path[plen..]); - } - } else if pattern.is_match(path) { - return true; - } - } - false - } - - fn patterns_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if self.pattern_for(name, path, elements)?.is_some() { - Ok(Some(())) - } else { - self.parent_pattern_for(name, path, elements) - } - } - - fn pattern_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(pattern) = self.named.get(name) { - self.fill_root(path, elements)?; - pattern.resource_path(path, elements)?; - Ok(Some(())) - } else { - for rmap in &self.nested { - if rmap.pattern_for(name, path, elements)?.is_some() { - return Ok(Some(())); - } - } - Ok(None) - } - } - - fn fill_root( - &self, path: &mut String, elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - parent.fill_root(path, elements)?; - } - self.root.resource_path(path, elements) - } - - fn parent_pattern_for( - &self, name: &str, path: &mut String, elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - if let Some(pattern) = parent.named.get(name) { - self.fill_root(path, elements)?; - pattern.resource_path(path, elements)?; - Ok(Some(())) - } else { - parent.parent_pattern_for(name, path, elements) - } - } else { - Ok(None) - } - } -} - -impl Default for Router { - fn default() -> Self { - Router::new(ResourceDef::new("")) - } -} - -impl Router { - pub(crate) fn new(root: ResourceDef) -> Self { - Router { - rmap: Rc::new(ResourceMap { - root, - parent: RefCell::new(None), - named: HashMap::new(), - patterns: Vec::new(), - nested: Vec::new(), - }), - resources: Vec::new(), - patterns: Vec::new(), - default: None, - } - } - - #[inline] - pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> ResourceInfo { - ResourceInfo { - params, - prefix: 0, - rmap: self.rmap.clone(), - resource: ResourceId::Normal(idx), - } - } - - #[cfg(test)] - pub(crate) fn default_route_info(&self) -> ResourceInfo { - ResourceInfo { - params: Params::new(), - rmap: self.rmap.clone(), - resource: ResourceId::Default, - prefix: 0, - } - } - - pub(crate) fn set_prefix(&mut self, path: &str) { - Rc::get_mut(&mut self.rmap).unwrap().root = ResourceDef::new(path); - } - - pub(crate) fn register_resource(&mut self, resource: Resource) { - { - let rmap = Rc::get_mut(&mut self.rmap).unwrap(); - - let name = resource.get_name(); - if !name.is_empty() { - assert!( - !rmap.named.contains_key(name), - "Named resource {:?} is registered.", - name - ); - rmap.named.insert(name.to_owned(), resource.rdef().clone()); - } - rmap.patterns.push((resource.rdef().clone(), None)); - } - self.patterns - .push(ResourcePattern::Resource(resource.rdef().clone())); - self.resources.push(ResourceItem::Resource(resource)); - } - - pub(crate) fn register_scope(&mut self, mut scope: Scope) { - Rc::get_mut(&mut self.rmap) - .unwrap() - .patterns - .push((scope.rdef().clone(), Some(scope.router().rmap.clone()))); - Rc::get_mut(&mut self.rmap) - .unwrap() - .nested - .push(scope.router().rmap.clone()); - - let filters = scope.take_filters(); - self.patterns - .push(ResourcePattern::Scope(scope.rdef().clone(), filters)); - self.resources.push(ResourceItem::Scope(scope)); - } - - pub(crate) fn register_handler( - &mut self, path: &str, hnd: Box>, - filters: Option>>>, - ) { - let rdef = ResourceDef::prefix(path); - Rc::get_mut(&mut self.rmap) - .unwrap() - .patterns - .push((rdef.clone(), None)); - self.resources.push(ResourceItem::Handler(hnd)); - self.patterns.push(ResourcePattern::Handler(rdef, filters)); - } - - pub(crate) fn has_default_resource(&self) -> bool { - self.default.is_some() - } - - pub(crate) fn register_default_resource(&mut self, resource: DefaultResource) { - self.default = Some(resource); - } - - pub(crate) fn finish(&mut self) { - for resource in &mut self.resources { - match resource { - ResourceItem::Resource(_) => (), - ResourceItem::Scope(scope) => { - if !scope.has_default_resource() { - if let Some(ref default) = self.default { - scope.default_resource(default.clone()); - } - } - *scope.router().rmap.parent.borrow_mut() = Some(self.rmap.clone()); - scope.finish(); - } - ResourceItem::Handler(hnd) => { - if !hnd.has_default_resource() { - if let Some(ref default) = self.default { - hnd.default_resource(default.clone()); - } - } - hnd.finish() - } - } - } - } - - pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { - let rmap = Rc::get_mut(&mut self.rmap).unwrap(); - assert!( - !rmap.named.contains_key(name), - "Named resource {:?} is registered.", - name - ); - rmap.named.insert(name.to_owned(), rdef); - } - - pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - let out = { - // get resource handler - let mut iterator = self.resources.iter_mut(); - - loop { - if let Some(ref mut resource) = iterator.next() { - if let ResourceItem::Resource(ref mut resource) = resource { - if resource.rdef().pattern() == path { - resource.method(method).with(f); - break None; - } - } - } else { - let mut resource = Resource::new(ResourceDef::new(path)); - resource.method(method).with(f); - break Some(resource); - } - } - }; - if let Some(out) = out { - self.register_resource(out); - } - } - - /// Handle request - pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - let resource = match req.resource().resource { - ResourceId::Normal(idx) => &self.resources[idx as usize], - ResourceId::Default => { - if let Some(ref default) = self.default { - if let Some(id) = default.get_route_id(req) { - return default.handle(id, req); - } - } - return AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)); - } - }; - match resource { - ResourceItem::Resource(ref resource) => { - if let Some(id) = resource.get_route_id(req) { - return resource.handle(id, req); - } - - if let Some(ref default) = self.default { - if let Some(id) = default.get_route_id(req) { - return default.handle(id, req); - } - } - } - ResourceItem::Handler(hnd) => return hnd.handle(req), - ResourceItem::Scope(hnd) => return hnd.handle(req), - } - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) - } - - /// Query for matched resource - pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> ResourceInfo { - if tail <= req.path().len() { - 'outer: for (idx, resource) in self.patterns.iter().enumerate() { - match resource { - ResourcePattern::Resource(rdef) => { - if let Some(params) = rdef.match_with_params(req, tail) { - return self.route_info_params(idx as u16, params); - } - } - ResourcePattern::Handler(rdef, filters) => { - if let Some(params) = rdef.match_prefix_with_params(req, tail) { - if let Some(ref filters) = filters { - for filter in filters { - if !filter.check(req, state) { - continue 'outer; - } - } - } - return self.route_info_params(idx as u16, params); - } - } - ResourcePattern::Scope(rdef, filters) => { - if let Some(params) = rdef.match_prefix_with_params(req, tail) { - for filter in filters { - if !filter.check(req, state) { - continue 'outer; - } - } - return self.route_info_params(idx as u16, params); - } - } - } - } - } - ResourceInfo { - prefix: tail as u16, - params: Params::new(), - rmap: self.rmap.clone(), - resource: ResourceId::Default, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -enum PatternElement { - Str(String), - Var(String), -} - -#[derive(Clone, Debug)] -enum PatternType { - Static(String), - Prefix(String), - Dynamic(Regex, Vec>, usize), -} - -#[derive(Debug, Copy, Clone, PartialEq)] -/// Resource type -pub enum ResourceType { - /// Normal resource - Normal, - /// Resource for application default handler - Default, - /// External resource - External, - /// Unknown resource type - Unset, -} - -/// Resource type describes an entry in resources table -#[derive(Clone, Debug)] -pub struct ResourceDef { - tp: PatternType, - rtp: ResourceType, - name: String, - pattern: String, - elements: Vec, -} - -impl ResourceDef { - /// Parse path pattern and create new `ResourceDef` instance. - /// - /// Panics if path pattern is wrong. - pub fn new(path: &str) -> Self { - ResourceDef::with_prefix(path, false, !path.is_empty()) - } - - /// Parse path pattern and create new `ResourceDef` instance. - /// - /// Use `prefix` type instead of `static`. - /// - /// Panics if path regex pattern is wrong. - pub fn prefix(path: &str) -> Self { - ResourceDef::with_prefix(path, true, !path.is_empty()) - } - - /// Construct external resource def - /// - /// Panics if path pattern is wrong. - pub fn external(path: &str) -> Self { - let mut resource = ResourceDef::with_prefix(path, false, false); - resource.rtp = ResourceType::External; - resource - } - - /// Parse path pattern and create new `ResourceDef` instance with custom prefix - pub fn with_prefix(path: &str, for_prefix: bool, slash: bool) -> Self { - let mut path = path.to_owned(); - if slash && !path.starts_with('/') { - path.insert(0, '/'); - } - let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix); - - let tp = if is_dynamic { - let re = match Regex::new(&pattern) { - Ok(re) => re, - Err(err) => panic!("Wrong path pattern: \"{}\" {}", path, err), - }; - // actix creates one router per thread - let names = re - .capture_names() - .filter_map(|name| name.map(|name| Rc::new(name.to_owned()))) - .collect(); - PatternType::Dynamic(re, names, len) - } else if for_prefix { - PatternType::Prefix(pattern.clone()) - } else { - PatternType::Static(pattern.clone()) - }; - - ResourceDef { - tp, - elements, - name: "".to_string(), - rtp: ResourceType::Normal, - pattern: path.to_owned(), - } - } - - /// Resource type - pub fn rtype(&self) -> ResourceType { - self.rtp - } - - /// Resource name - pub fn name(&self) -> &str { - &self.name - } - - /// Resource name - pub(crate) fn set_name(&mut self, name: &str) { - self.name = name.to_owned(); - } - - /// Path pattern of the resource - pub fn pattern(&self) -> &str { - &self.pattern - } - - /// Is this path a match against this resource? - pub fn is_match(&self, path: &str) -> bool { - match self.tp { - PatternType::Static(ref s) => s == path, - PatternType::Dynamic(ref re, _, _) => re.is_match(path), - PatternType::Prefix(ref s) => path.starts_with(s), - } - } - - fn is_prefix_match(&self, path: &str) -> Option { - let plen = path.len(); - let path = if path.is_empty() { "/" } else { path }; - - match self.tp { - PatternType::Static(ref s) => if s == path { - Some(plen) - } else { - None - }, - PatternType::Dynamic(ref re, _, len) => { - if let Some(captures) = re.captures(path) { - let mut pos = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - pos = m.end(); - } - } - Some(plen + pos + len) - } else { - None - } - } - PatternType::Prefix(ref s) => { - let len = if path == s { - s.len() - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return None; - }; - Some(min(plen, len)) - } - } - } - - /// Are the given path and parameters a match against this resource? - pub fn match_with_params(&self, req: &Request, plen: usize) -> Option { - let path = &req.path()[plen..]; - - match self.tp { - PatternType::Static(ref s) => if s != path { - None - } else { - Some(Params::with_url(req.url())) - }, - PatternType::Dynamic(ref re, ref names, _) => { - if let Some(captures) = re.captures(path) { - let mut params = Params::with_url(req.url()); - let mut idx = 0; - let mut passed = false; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - params.add( - names[idx].clone(), - ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - ), - ); - idx += 1; - } - } - params.set_tail(req.path().len() as u16); - Some(params) - } else { - None - } - } - PatternType::Prefix(ref s) => if !path.starts_with(s) { - None - } else { - Some(Params::with_url(req.url())) - }, - } - } - - /// Is the given path a prefix match and do the parameters match against this resource? - pub fn match_prefix_with_params( - &self, req: &Request, plen: usize, - ) -> Option { - let path = &req.path()[plen..]; - let path = if path.is_empty() { "/" } else { path }; - - match self.tp { - PatternType::Static(ref s) => if s == path { - let mut params = Params::with_url(req.url()); - params.set_tail(req.path().len() as u16); - Some(params) - } else { - None - }, - PatternType::Dynamic(ref re, ref names, len) => { - if let Some(captures) = re.captures(path) { - let mut params = Params::with_url(req.url()); - let mut pos = 0; - let mut passed = false; - let mut idx = 0; - for capture in captures.iter() { - if let Some(ref m) = capture { - if !passed { - passed = true; - continue; - } - - params.add( - names[idx].clone(), - ParamItem::UrlSegment( - (plen + m.start()) as u16, - (plen + m.end()) as u16, - ), - ); - idx += 1; - pos = m.end(); - } - } - params.set_tail((plen + pos + len) as u16); - Some(params) - } else { - None - } - } - PatternType::Prefix(ref s) => { - let len = if path == s { - s.len() - } else if path.starts_with(s) - && (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/')) - { - if s.ends_with('/') { - s.len() - 1 - } else { - s.len() - } - } else { - return None; - }; - let mut params = Params::with_url(req.url()); - params.set_tail(min(req.path().len(), plen + len) as u16); - Some(params) - } - } - } - - /// Build resource path. - pub fn resource_path( - &self, path: &mut String, elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - match self.tp { - PatternType::Prefix(ref p) => path.push_str(p), - PatternType::Static(ref p) => path.push_str(p), - PatternType::Dynamic(..) => { - for el in &self.elements { - match *el { - PatternElement::Str(ref s) => path.push_str(s), - PatternElement::Var(_) => { - if let Some(val) = elements.next() { - path.push_str(val.as_ref()) - } else { - return Err(UrlGenerationError::NotEnoughElements); - } - } - } - } - } - }; - Ok(()) - } - - fn parse_param(pattern: &str) -> (PatternElement, String, &str) { - const DEFAULT_PATTERN: &str = "[^/]+"; - let mut params_nesting = 0usize; - let close_idx = pattern - .find(|c| match c { - '{' => { - params_nesting += 1; - false - } - '}' => { - params_nesting -= 1; - params_nesting == 0 - } - _ => false, - }).expect("malformed param"); - let (mut param, rem) = pattern.split_at(close_idx + 1); - param = ¶m[1..param.len() - 1]; // Remove outer brackets - let (name, pattern) = match param.find(':') { - Some(idx) => { - let (name, pattern) = param.split_at(idx); - (name, &pattern[1..]) - } - None => (param, DEFAULT_PATTERN), - }; - ( - PatternElement::Var(name.to_string()), - format!(r"(?P<{}>{})", &name, &pattern), - rem, - ) - } - - fn parse( - mut pattern: &str, for_prefix: bool, - ) -> (String, Vec, bool, usize) { - if pattern.find('{').is_none() { - return ( - String::from(pattern), - vec![PatternElement::Str(String::from(pattern))], - false, - pattern.chars().count(), - ); - }; - - let mut elems = Vec::new(); - let mut re = String::from("^"); - - while let Some(idx) = pattern.find('{') { - let (prefix, rem) = pattern.split_at(idx); - elems.push(PatternElement::Str(String::from(prefix))); - re.push_str(&escape(prefix)); - let (param_pattern, re_part, rem) = Self::parse_param(rem); - elems.push(param_pattern); - re.push_str(&re_part); - pattern = rem; - } - - elems.push(PatternElement::Str(String::from(pattern))); - re.push_str(&escape(pattern)); - - if !for_prefix { - re.push_str("$"); - } - - (re, elems, true, pattern.chars().count()) - } -} - -impl PartialEq for ResourceDef { - fn eq(&self, other: &ResourceDef) -> bool { - self.pattern == other.pattern - } -} - -impl Eq for ResourceDef {} - -impl Hash for ResourceDef { - fn hash(&self, state: &mut H) { - self.pattern.hash(state); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test::TestRequest; - - #[test] - fn test_recognizer10() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - router.register_resource(Resource::new(ResourceDef::new( - "/name/{val}/index.html", - ))); - router.register_resource(Resource::new(ResourceDef::new("/file/{file}.{ext}"))); - router.register_resource(Resource::new(ResourceDef::new( - "/v{val}/{val2}/index.html", - ))); - router.register_resource(Resource::new(ResourceDef::new("/v/{tail:.*}"))); - router.register_resource(Resource::new(ResourceDef::new("/test2/{test}.html"))); - router.register_resource(Resource::new(ResourceDef::new("/{test}/index.html"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - assert!(info.match_info().is_empty()); - - let req = TestRequest::with_uri("/name/value").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.match_info().get("val").unwrap(), "value"); - assert_eq!(&info.match_info()["val"], "value"); - - let req = TestRequest::with_uri("/name/value2/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(2)); - assert_eq!(info.match_info().get("val").unwrap(), "value2"); - - let req = TestRequest::with_uri("/file/file.gz").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(3)); - assert_eq!(info.match_info().get("file").unwrap(), "file"); - assert_eq!(info.match_info().get("ext").unwrap(), "gz"); - - let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(4)); - assert_eq!(info.match_info().get("val").unwrap(), "test"); - assert_eq!(info.match_info().get("val2").unwrap(), "ttt"); - - let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(5)); - assert_eq!( - info.match_info().get("tail").unwrap(), - "blah-blah/index.html" - ); - - let req = TestRequest::with_uri("/test2/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(6)); - assert_eq!(info.match_info().get("test").unwrap(), "index"); - - let req = TestRequest::with_uri("/bbb/index.html").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(7)); - assert_eq!(info.match_info().get("test").unwrap(), "bbb"); - } - - #[test] - fn test_recognizer_2() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/index.json"))); - router.register_resource(Resource::new(ResourceDef::new("/{source}.json"))); - - let req = TestRequest::with_uri("/index.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - } - - #[test] - fn test_recognizer_with_prefix() { - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test/name").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test/name/value").finish(); - let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.match_info().get("val").unwrap(), "value"); - assert_eq!(&info.match_info()["val"], "value"); - - // same patterns - let mut router = Router::<()>::default(); - router.register_resource(Resource::new(ResourceDef::new("/name"))); - router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); - - let req = TestRequest::with_uri("/name").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test2/name").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Normal(0)); - - let req = TestRequest::with_uri("/test2/name-test").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Default); - - let req = TestRequest::with_uri("/test2/name/ttt").finish(); - let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(&info.match_info()["val"], "ttt"); - } - - #[test] - fn test_parse_static() { - let re = ResourceDef::new("/"); - assert!(re.is_match("/")); - assert!(!re.is_match("/a")); - - let re = ResourceDef::new("/name"); - assert!(re.is_match("/name")); - assert!(!re.is_match("/name1")); - assert!(!re.is_match("/name/")); - assert!(!re.is_match("/name~")); - - let re = ResourceDef::new("/name/"); - assert!(re.is_match("/name/")); - assert!(!re.is_match("/name")); - assert!(!re.is_match("/name/gs")); - - let re = ResourceDef::new("/user/profile"); - assert!(re.is_match("/user/profile")); - assert!(!re.is_match("/user/profile/profile")); - } - - #[test] - fn test_parse_param() { - let re = ResourceDef::new("/user/{id}"); - 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 req = TestRequest::with_uri("/user/profile").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "profile"); - - let req = TestRequest::with_uri("/user/1245125").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "1245125"); - - let re = ResourceDef::new("/v{version}/resource/{id}"); - assert!(re.is_match("/v1/resource/320120")); - assert!(!re.is_match("/v/resource/1")); - assert!(!re.is_match("/resource")); - - let req = TestRequest::with_uri("/v151/resource/adahg32").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("version").unwrap(), "151"); - assert_eq!(info.get("id").unwrap(), "adahg32"); - - let re = ResourceDef::new("/{id:[[:digit:]]{6}}"); - assert!(re.is_match("/012345")); - assert!(!re.is_match("/012")); - assert!(!re.is_match("/01234567")); - assert!(!re.is_match("/XXXXXX")); - - let req = TestRequest::with_uri("/012345").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(info.get("id").unwrap(), "012345"); - } - - #[test] - fn test_resource_prefix() { - let re = ResourceDef::prefix("/name"); - assert!(re.is_match("/name")); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/test/test")); - assert!(re.is_match("/name1")); - assert!(re.is_match("/name~")); - - let re = ResourceDef::prefix("/name/"); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); - } - - #[test] - fn test_reousrce_prefix_dynamic() { - let re = ResourceDef::prefix("/{name}/"); - assert!(re.is_match("/name/")); - assert!(re.is_match("/name/gs")); - assert!(!re.is_match("/name")); - - let req = TestRequest::with_uri("/test2/").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(&info["name"], "test2"); - assert_eq!(&info[0], "test2"); - - let req = TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); - let info = re.match_with_params(&req, 0).unwrap(); - assert_eq!(&info["name"], "test2"); - assert_eq!(&info[0], "test2"); - } - - #[test] - fn test_request_resource() { - let mut router = Router::<()>::default(); - let mut resource = Resource::new(ResourceDef::new("/index.json")); - resource.name("r1"); - router.register_resource(resource); - let mut resource = Resource::new(ResourceDef::new("/test.json")); - resource.name("r2"); - router.register_resource(resource); - - let req = TestRequest::with_uri("/index.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(0)); - - assert_eq!(info.name(), "r1"); - - let req = TestRequest::with_uri("/test.json").finish(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - assert_eq!(info.name(), "r2"); - } - - #[test] - fn test_has_resource() { - let mut router = Router::<()>::default(); - let scope = Scope::new("/test").resource("/name", |_| "done"); - router.register_scope(scope); - - { - let info = router.default_route_info(); - assert!(!info.has_resource("/test")); - assert!(info.has_resource("/test/name")); - } - - let scope = - Scope::new("/test2").nested("/test10", |s| s.resource("/name", |_| "done")); - router.register_scope(scope); - - let info = router.default_route_info(); - assert!(info.has_resource("/test2/test10/name")); - } - - #[test] - fn test_url_for() { - let mut router = Router::<()>::new(ResourceDef::prefix("")); - - let mut resource = Resource::new(ResourceDef::new("/tttt")); - resource.name("r0"); - router.register_resource(resource); - - let scope = Scope::new("/test").resource("/name", |r| { - r.name("r1"); - }); - router.register_scope(scope); - - let scope = Scope::new("/test2") - .nested("/test10", |s| s.resource("/name", |r| r.name("r2"))); - router.register_scope(scope); - router.finish(); - - let req = TestRequest::with_uri("/test").request(); - { - let info = router.default_route_info(); - - let res = info - .url_for(&req, "r0", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/tttt"); - - let res = info - .url_for(&req, "r1", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test/name"); - - let res = info - .url_for(&req, "r2", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); - } - - let req = TestRequest::with_uri("/test/name").request(); - let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, ResourceId::Normal(1)); - - let res = info - .url_for(&req, "r0", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/tttt"); - - let res = info - .url_for(&req, "r1", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test/name"); - - let res = info - .url_for(&req, "r2", Vec::<&'static str>::new()) - .unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name"); - } - - #[test] - fn test_url_for_dynamic() { - let mut router = Router::<()>::new(ResourceDef::prefix("")); - - let mut resource = Resource::new(ResourceDef::new("/{name}/test/index.{ext}")); - resource.name("r0"); - router.register_resource(resource); - - let scope = Scope::new("/{name1}").nested("/{name2}", |s| { - s.resource("/{name3}/test/index.{ext}", |r| r.name("r2")) - }); - router.register_scope(scope); - router.finish(); - - let req = TestRequest::with_uri("/test").request(); - { - let info = router.default_route_info(); - - let res = info.url_for(&req, "r0", vec!["sec1", "html"]).unwrap(); - assert_eq!(res.as_str(), "http://localhost:8080/sec1/test/index.html"); - - let res = info - .url_for(&req, "r2", vec!["sec1", "sec2", "sec3", "html"]) - .unwrap(); - assert_eq!( - res.as_str(), - "http://localhost:8080/sec1/sec2/sec3/test/index.html" - ); - } - } -} diff --git a/src/scope.rs b/src/scope.rs deleted file mode 100644 index fb9e7514..00000000 --- a/src/scope.rs +++ /dev/null @@ -1,1236 +0,0 @@ -use std::marker::PhantomData; -use std::mem; -use std::rc::Rc; - -use futures::{Async, Future, Poll}; - -use error::Error; -use handler::{ - AsyncResult, AsyncResultItem, FromRequest, Handler, Responder, RouteHandler, - WrapHandler, -}; -use http::Method; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{ - Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse, - Started as MiddlewareStarted, -}; -use pred::Predicate; -use resource::{DefaultResource, Resource}; -use router::{ResourceDef, Router}; -use server::Request; -use with::WithFactory; - -/// Resources scope -/// -/// Scope is a set of resources with common root path. -/// Scopes collect multiple paths under a common path prefix. -/// Scope path can contain variable path segments as resources. -/// Scope prefix is always complete path segment, i.e `/app` would -/// be converted to a `/app/` and it would not match `/app` path. -/// -/// You can get variable path segments from `HttpRequest::match_info()`. -/// `Path` extractor also is able to extract scope level variable segments. -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{http, App, HttpRequest, HttpResponse}; -/// -/// fn main() { -/// let app = App::new().scope("/{project_id}/", |scope| { -/// scope -/// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) -/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) -/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) -/// }); -/// } -/// ``` -/// -/// In the above example three routes get registered: -/// * /{project_id}/path1 - reponds to all http method -/// * /{project_id}/path2 - `GET` requests -/// * /{project_id}/path3 - `HEAD` requests -/// -pub struct Scope { - rdef: ResourceDef, - router: Rc>, - filters: Vec>>, - middlewares: Rc>>>, -} - -#[cfg_attr( - feature = "cargo-clippy", - allow(new_without_default_derive) -)] -impl Scope { - /// Create a new scope - pub fn new(path: &str) -> Scope { - let rdef = ResourceDef::prefix(path); - Scope { - rdef: rdef.clone(), - router: Rc::new(Router::new(rdef)), - filters: Vec::new(), - middlewares: Rc::new(Vec::new()), - } - } - - #[inline] - pub(crate) fn rdef(&self) -> &ResourceDef { - &self.rdef - } - - pub(crate) fn router(&self) -> &Router { - self.router.as_ref() - } - - #[inline] - pub(crate) fn take_filters(&mut self) -> Vec>> { - mem::replace(&mut self.filters, Vec::new()) - } - - /// Add match predicate to scope. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, pred, App, HttpRequest, HttpResponse, Path}; - /// - /// fn index(data: Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope - /// .filter(pred::Header("content-type", "text/plain")) - /// .route("/test1", http::Method::GET, index) - /// .route("/test2", http::Method::POST, |_: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// }) - /// }); - /// } - /// ``` - pub fn filter + 'static>(mut self, p: T) -> Self { - self.filters.push(Box::new(p)); - self - } - - /// Create nested scope with new state. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest}; - /// - /// struct AppState; - /// - /// fn index(req: &HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.with_state("/state2", AppState, |scope| { - /// scope.resource("/test1", |r| r.f(index)) - /// }) - /// }); - /// } - /// ``` - pub fn with_state(mut self, path: &str, state: T, f: F) -> Scope - where - F: FnOnce(Scope) -> Scope, - { - let rdef = ResourceDef::prefix(path); - let scope = Scope { - rdef: rdef.clone(), - filters: Vec::new(), - router: Rc::new(Router::new(rdef)), - middlewares: Rc::new(Vec::new()), - }; - let mut scope = f(scope); - - let state = Rc::new(state); - let filters: Vec>> = vec![Box::new(FiltersWrapper { - state: Rc::clone(&state), - filters: scope.take_filters(), - })]; - let handler = Box::new(Wrapper { scope, state }); - - Rc::get_mut(&mut self.router).unwrap().register_handler( - path, - handler, - Some(filters), - ); - - self - } - - /// Create nested scope. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest}; - /// - /// struct AppState; - /// - /// fn index(req: &HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::with_state(AppState).scope("/app", |scope| { - /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index))) - /// }); - /// } - /// ``` - pub fn nested(mut self, path: &str, f: F) -> Scope - where - F: FnOnce(Scope) -> Scope, - { - let rdef = ResourceDef::prefix(&insert_slash(path)); - let scope = Scope { - rdef: rdef.clone(), - filters: Vec::new(), - router: Rc::new(Router::new(rdef)), - middlewares: Rc::new(Vec::new()), - }; - Rc::get_mut(&mut self.router) - .unwrap() - .register_scope(f(scope)); - - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `Scope::resource()` method. - /// Handler functions need to accept one request extractor - /// argument. - /// - /// This method could be called multiple times, in that case - /// multiple routes would be registered for same resource path. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse, Path}; - /// - /// fn index(data: Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.route("/test1", http::Method::GET, index).route( - /// "/test2", - /// http::Method::POST, - /// |_: HttpRequest| HttpResponse::MethodNotAllowed(), - /// ) - /// }); - /// } - /// ``` - pub fn route(mut self, path: &str, method: Method, f: F) -> Scope - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - Rc::get_mut(&mut self.router).unwrap().register_route( - &insert_slash(path), - method, - f, - ); - self - } - - /// Configure resource for a specific path. - /// - /// This method is similar to an `App::resource()` method. - /// Resources may have variable path segments. Resource path uses scope - /// path as a path prefix. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new().scope("/api", |scope| { - /// scope.resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|_| HttpResponse::Ok()) - /// }) - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> Scope - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource - let mut resource = Resource::new(ResourceDef::new(&insert_slash(path))); - f(&mut resource); - - Rc::get_mut(&mut self.router) - .unwrap() - .register_resource(resource); - self - } - - /// Default resource to be used if no matching route could be found. - pub fn default_resource(mut self, f: F) -> Scope - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // create and configure default resource - let mut resource = Resource::new(ResourceDef::new("")); - f(&mut resource); - - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_default_resource(resource.into()); - - self - } - - /// Configure handler for specific path prefix. - /// - /// A path prefix consists of valid path segments, i.e for the - /// prefix `/app` any request with the paths `/app`, `/app/` or - /// `/app/test` would match, but the path `/application` would - /// not. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/scope-prefix", |scope| { - /// scope.handler("/app", |req: &HttpRequest| match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }) - /// }); - /// } - /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> Scope { - let path = insert_slash(path.trim().trim_right_matches('/')); - Rc::get_mut(&mut self.router) - .expect("Multiple copies of scope router") - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); - self - } - - /// Register a scope middleware - /// - /// This is similar to `App's` middlewares, but - /// middlewares get invoked on scope level. - /// - /// *Note* `Middleware::finish()` fires right after response get - /// prepared. It does not wait until body get sent to the peer. - pub fn middleware>(mut self, mw: M) -> Scope { - Rc::get_mut(&mut self.middlewares) - .expect("Can not use after configuration") - .push(Box::new(mw)); - self - } -} - -fn insert_slash(path: &str) -> String { - let mut path = path.to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - path -} - -impl RouteHandler for Scope { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let tail = req.match_info().tail as usize; - - // recognize resources - let info = self.router.recognize(req, req.state(), tail); - let req2 = req.with_route_info(info); - if self.middlewares.is_empty() { - self.router.handle(&req2) - } else { - AsyncResult::future(Box::new(Compose::new( - req2, - Rc::clone(&self.router), - Rc::clone(&self.middlewares), - ))) - } - } - - fn has_default_resource(&self) -> bool { - self.router.has_default_resource() - } - - fn default_resource(&mut self, default: DefaultResource) { - Rc::get_mut(&mut self.router) - .expect("Can not use after configuration") - .register_default_resource(default); - } - - fn finish(&mut self) { - Rc::get_mut(&mut self.router) - .expect("Can not use after configuration") - .finish(); - } -} - -struct Wrapper { - state: Rc, - scope: Scope, -} - -impl RouteHandler for Wrapper { - fn handle(&self, req: &HttpRequest) -> AsyncResult { - let req = req.with_state(Rc::clone(&self.state)); - self.scope.handle(&req) - } -} - -struct FiltersWrapper { - state: Rc, - filters: Vec>>, -} - -impl Predicate for FiltersWrapper { - fn check(&self, req: &Request, _: &S2) -> bool { - for filter in &self.filters { - if !filter.check(&req, &self.state) { - return false; - } - } - true - } -} - -/// Compose resource level middlewares with route handler. -struct Compose { - info: ComposeInfo, - state: ComposeState, -} - -struct ComposeInfo { - count: usize, - req: HttpRequest, - router: Rc>, - mws: Rc>>>, -} - -enum ComposeState { - Starting(StartMiddlewares), - Handler(WaitingResponse), - RunMiddlewares(RunMiddlewares), - Finishing(FinishingMiddlewares), - Completed(Response), -} - -impl ComposeState { - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match *self { - ComposeState::Starting(ref mut state) => state.poll(info), - ComposeState::Handler(ref mut state) => state.poll(info), - ComposeState::RunMiddlewares(ref mut state) => state.poll(info), - ComposeState::Finishing(ref mut state) => state.poll(info), - ComposeState::Completed(_) => None, - } - } -} - -impl Compose { - fn new( - req: HttpRequest, router: Rc>, mws: Rc>>>, - ) -> Self { - let mut info = ComposeInfo { - mws, - req, - router, - count: 0, - }; - let state = StartMiddlewares::init(&mut info); - - Compose { state, info } - } -} - -impl Future for Compose { - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - loop { - if let ComposeState::Completed(ref mut resp) = self.state { - let resp = resp.resp.take().unwrap(); - return Ok(Async::Ready(resp)); - } - if let Some(state) = self.state.poll(&mut self.info) { - self.state = state; - } else { - return Ok(Async::NotReady); - } - } - } -} - -/// Middlewares start executor -struct StartMiddlewares { - fut: Option, - _s: PhantomData, -} - -type Fut = Box, Error = Error>>; - -impl StartMiddlewares { - fn init(info: &mut ComposeInfo) -> ComposeState { - let len = info.mws.len(); - - loop { - if info.count == len { - let reply = info.router.handle(&info.req); - return WaitingResponse::init(info, reply); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return RunMiddlewares::init(info, resp); - } - Ok(MiddlewareStarted::Future(fut)) => { - return ComposeState::Starting(StartMiddlewares { - fut: Some(fut), - _s: PhantomData, - }); - } - Err(err) => { - return RunMiddlewares::init(info, err.into()); - } - } - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - 'outer: loop { - match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => { - return None; - } - Ok(Async::Ready(resp)) => { - info.count += 1; - - if let Some(resp) = resp { - return Some(RunMiddlewares::init(info, resp)); - } - loop { - if info.count == len { - let reply = info.router.handle(&info.req); - return Some(WaitingResponse::init(info, reply)); - } else { - let result = info.mws[info.count].start(&info.req); - match result { - Ok(MiddlewareStarted::Done) => info.count += 1, - Ok(MiddlewareStarted::Response(resp)) => { - return Some(RunMiddlewares::init(info, resp)); - } - Ok(MiddlewareStarted::Future(fut)) => { - self.fut = Some(fut); - continue 'outer; - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } - } - Err(err) => { - return Some(RunMiddlewares::init(info, err.into())); - } - } - } - } -} - -// waiting for response -struct WaitingResponse { - fut: Box>, - _s: PhantomData, -} - -impl WaitingResponse { - #[inline] - fn init( - info: &mut ComposeInfo, reply: AsyncResult, - ) -> ComposeState { - match reply.into() { - AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp), - AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()), - AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse { - fut, - _s: PhantomData, - }), - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - match self.fut.poll() { - Ok(Async::NotReady) => None, - Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)), - Err(err) => Some(RunMiddlewares::init(info, err.into())), - } - } -} - -/// Middlewares response executor -struct RunMiddlewares { - curr: usize, - fut: Option>>, - _s: PhantomData, -} - -impl RunMiddlewares { - fn init(info: &mut ComposeInfo, mut resp: HttpResponse) -> ComposeState { - let mut curr = 0; - let len = info.mws.len(); - - loop { - let state = info.mws[curr].response(&info.req, resp); - resp = match state { - Err(err) => { - info.count = curr + 1; - return FinishingMiddlewares::init(info, err.into()); - } - Ok(MiddlewareResponse::Done(r)) => { - curr += 1; - if curr == len { - return FinishingMiddlewares::init(info, r); - } else { - r - } - } - Ok(MiddlewareResponse::Future(fut)) => { - return ComposeState::RunMiddlewares(RunMiddlewares { - curr, - fut: Some(fut), - _s: PhantomData, - }); - } - }; - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - let len = info.mws.len(); - - loop { - // poll latest fut - let mut resp = match self.fut.as_mut().unwrap().poll() { - Ok(Async::NotReady) => return None, - Ok(Async::Ready(resp)) => { - self.curr += 1; - resp - } - Err(err) => return Some(FinishingMiddlewares::init(info, err.into())), - }; - - loop { - if self.curr == len { - return Some(FinishingMiddlewares::init(info, resp)); - } else { - let state = info.mws[self.curr].response(&info.req, resp); - match state { - Err(err) => { - return Some(FinishingMiddlewares::init(info, err.into())) - } - Ok(MiddlewareResponse::Done(r)) => { - self.curr += 1; - resp = r - } - Ok(MiddlewareResponse::Future(fut)) => { - self.fut = Some(fut); - break; - } - } - } - } - } - } -} - -/// Middlewares start executor -struct FinishingMiddlewares { - resp: Option, - fut: Option>>, - _s: PhantomData, -} - -impl FinishingMiddlewares { - fn init(info: &mut ComposeInfo, resp: HttpResponse) -> ComposeState { - if info.count == 0 { - Response::init(resp) - } else { - let mut state = FinishingMiddlewares { - resp: Some(resp), - fut: None, - _s: PhantomData, - }; - if let Some(st) = state.poll(info) { - st - } else { - ComposeState::Finishing(state) - } - } - } - - fn poll(&mut self, info: &mut ComposeInfo) -> Option> { - loop { - // poll latest fut - let not_ready = if let Some(ref mut fut) = self.fut { - match fut.poll() { - Ok(Async::NotReady) => true, - Ok(Async::Ready(())) => false, - Err(err) => { - error!("Middleware finish error: {}", err); - false - } - } - } else { - false - }; - if not_ready { - return None; - } - self.fut = None; - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - - info.count -= 1; - let state = info.mws[info.count as usize] - .finish(&info.req, self.resp.as_ref().unwrap()); - match state { - MiddlewareFinished::Done => { - if info.count == 0 { - return Some(Response::init(self.resp.take().unwrap())); - } - } - MiddlewareFinished::Future(fut) => { - self.fut = Some(fut); - } - } - } - } -} - -struct Response { - resp: Option, - _s: PhantomData, -} - -impl Response { - fn init(resp: HttpResponse) -> ComposeState { - ComposeState::Completed(Response { - resp: Some(resp), - _s: PhantomData, - }) - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - - use application::App; - use body::Body; - use http::{Method, StatusCode}; - use httprequest::HttpRequest; - use httpresponse::HttpResponse; - use pred; - use test::TestRequest; - - #[test] - fn test_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_root2() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_root3() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_route() { - let app = App::new() - .scope("app", |scope| { - scope - .route("/path1", Method::GET, |_: HttpRequest<_>| { - HttpResponse::Ok() - }).route("/path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_route_without_leading_slash() { - let app = App::new() - .scope("app", |scope| { - scope - .route("path1", Method::GET, |_: HttpRequest<_>| HttpResponse::Ok()) - .route("path1", Method::DELETE, |_: HttpRequest<_>| { - HttpResponse::Ok() - }) - }).finish(); - - let req = TestRequest::with_uri("/app/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }).finish(); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_variable_segment() { - let app = App::new() - .scope("/ab-{project}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/ab-project1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/aa-project1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_with_state() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_with_state_root() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_scope_with_state_root2() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1/", State, |scope| { - scope.resource("", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_scope_with_state_root3() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1/", State, |scope| { - scope.resource("/", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_scope_with_state_filter() { - struct State; - - let app = App::new() - .scope("/app", |scope| { - scope.with_state("/t1", State, |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_nested_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_no_slash() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("t1", |scope| { - scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Created())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - } - - #[test] - fn test_nested_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .filter(pred::Get()) - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_nested_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project_id}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Created().body(format!( - "project: {}", - &r.match_info()["project_id"] - )) - }) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/project_1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); - } - _ => panic!(), - } - } - - #[test] - fn test_nested2_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project}", |scope| { - scope.nested("/{id}", |scope| { - scope.resource("/path1", |r| { - r.f(|r| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - }) - }) - }) - }) - }).finish(); - - let req = TestRequest::with_uri("/app/test/1/path1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - match resp.as_msg().body() { - &Body::Binary(ref b) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/app/test/1/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_default_resource() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - .default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }).finish(); - - let req = TestRequest::with_uri("/app/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/path2").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_default_resource_propagation() { - let app = App::new() - .scope("/app1", |scope| { - scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest())) - }).scope("/app2", |scope| scope) - .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) - .finish(); - - let req = TestRequest::with_uri("/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/app1/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/app2/non-exist").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_handler() { - let app = App::new() - .scope("/scope", |scope| { - scope.handler("/test", |_: &_| HttpResponse::Ok()) - }).finish(); - - let req = TestRequest::with_uri("/scope/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/scope/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/scope/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } -} diff --git a/src/server/acceptor.rs b/src/server/acceptor.rs deleted file mode 100644 index 994b4b7b..00000000 --- a/src/server/acceptor.rs +++ /dev/null @@ -1,383 +0,0 @@ -use std::time::Duration; -use std::{fmt, net}; - -use actix_net::server::ServerMessage; -use actix_net::service::{NewService, Service}; -use futures::future::{err, ok, Either, FutureResult}; -use futures::{Async, Future, Poll}; -use tokio_reactor::Handle; -use tokio_tcp::TcpStream; -use tokio_timer::{sleep, Delay}; - -use super::error::AcceptorError; -use super::IoStream; - -/// This trait indicates types that can create acceptor service for http server. -pub trait AcceptorServiceFactory: Send + Clone + 'static { - type Io: IoStream + Send; - type NewService: NewService; - - fn create(&self) -> Self::NewService; -} - -impl AcceptorServiceFactory for F -where - F: Fn() -> T + Send + Clone + 'static, - T::Response: IoStream + Send, - T: NewService, - T::InitError: fmt::Debug, -{ - type Io = T::Response; - type NewService = T; - - fn create(&self) -> T { - (self)() - } -} - -#[derive(Clone)] -/// Default acceptor service convert `TcpStream` to a `tokio_tcp::TcpStream` -pub(crate) struct DefaultAcceptor; - -impl AcceptorServiceFactory for DefaultAcceptor { - type Io = TcpStream; - type NewService = DefaultAcceptor; - - fn create(&self) -> Self::NewService { - DefaultAcceptor - } -} - -impl NewService for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type InitError = (); - type Service = DefaultAcceptor; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(DefaultAcceptor) - } -} - -impl Service for DefaultAcceptor { - type Request = TcpStream; - type Response = TcpStream; - type Error = (); - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - ok(req) - } -} - -pub(crate) struct TcpAcceptor { - inner: T, -} - -impl TcpAcceptor -where - T: NewService>, - T::InitError: fmt::Debug, -{ - pub(crate) fn new(inner: T) -> Self { - TcpAcceptor { inner } - } -} - -impl NewService for TcpAcceptor -where - T: NewService>, - T::InitError: fmt::Debug, -{ - type Request = net::TcpStream; - type Response = T::Response; - type Error = AcceptorError; - type InitError = T::InitError; - type Service = TcpAcceptorService; - type Future = TcpAcceptorResponse; - - fn new_service(&self) -> Self::Future { - TcpAcceptorResponse { - fut: self.inner.new_service(), - } - } -} - -pub(crate) struct TcpAcceptorResponse -where - T: NewService, - T::InitError: fmt::Debug, -{ - fut: T::Future, -} - -impl Future for TcpAcceptorResponse -where - T: NewService, - T::InitError: fmt::Debug, -{ - type Item = TcpAcceptorService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(service)) => { - Ok(Async::Ready(TcpAcceptorService { inner: service })) - } - Err(e) => { - error!("Can not create accetor service: {:?}", e); - Err(e) - } - } - } -} - -pub(crate) struct TcpAcceptorService { - inner: T, -} - -impl Service for TcpAcceptorService -where - T: Service>, -{ - type Request = net::TcpStream; - type Response = T::Response; - type Error = AcceptorError; - type Future = Either>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready() - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - let stream = TcpStream::from_std(req, &Handle::default()).map_err(|e| { - error!("Can not convert to an async tcp stream: {}", e); - AcceptorError::Io(e) - }); - - match stream { - Ok(stream) => Either::A(self.inner.call(stream)), - Err(e) => Either::B(err(e)), - } - } -} - -#[doc(hidden)] -/// Acceptor timeout middleware -/// -/// Applies timeout to request prcoessing. -pub struct AcceptorTimeout { - inner: T, - timeout: Duration, -} - -impl AcceptorTimeout { - /// Create new `AcceptorTimeout` instance. timeout is in milliseconds. - pub fn new(timeout: u64, inner: T) -> Self { - Self { - inner, - timeout: Duration::from_millis(timeout), - } - } -} - -impl NewService for AcceptorTimeout { - type Request = T::Request; - type Response = T::Response; - type Error = AcceptorError; - type InitError = T::InitError; - type Service = AcceptorTimeoutService; - type Future = AcceptorTimeoutFut; - - fn new_service(&self) -> Self::Future { - AcceptorTimeoutFut { - fut: self.inner.new_service(), - timeout: self.timeout, - } - } -} - -#[doc(hidden)] -pub struct AcceptorTimeoutFut { - fut: T::Future, - timeout: Duration, -} - -impl Future for AcceptorTimeoutFut { - type Item = AcceptorTimeoutService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - let inner = try_ready!(self.fut.poll()); - Ok(Async::Ready(AcceptorTimeoutService { - inner, - timeout: self.timeout, - })) - } -} - -#[doc(hidden)] -/// Acceptor timeout service -/// -/// Applies timeout to request prcoessing. -pub struct AcceptorTimeoutService { - inner: T, - timeout: Duration, -} - -impl Service for AcceptorTimeoutService { - type Request = T::Request; - type Response = T::Response; - type Error = AcceptorError; - type Future = AcceptorTimeoutResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready().map_err(AcceptorError::Service) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - AcceptorTimeoutResponse { - fut: self.inner.call(req), - sleep: sleep(self.timeout), - } - } -} - -#[doc(hidden)] -pub struct AcceptorTimeoutResponse { - fut: T::Future, - sleep: Delay, -} - -impl Future for AcceptorTimeoutResponse { - type Item = T::Response; - type Error = AcceptorError; - - fn poll(&mut self) -> Poll { - match self.fut.poll().map_err(AcceptorError::Service)? { - Async::NotReady => match self.sleep.poll() { - Err(_) => Err(AcceptorError::Timeout), - Ok(Async::Ready(_)) => Err(AcceptorError::Timeout), - Ok(Async::NotReady) => Ok(Async::NotReady), - }, - Async::Ready(resp) => Ok(Async::Ready(resp)), - } - } -} - -pub(crate) struct ServerMessageAcceptor { - inner: T, -} - -impl ServerMessageAcceptor -where - T: NewService, -{ - pub(crate) fn new(inner: T) -> Self { - ServerMessageAcceptor { inner } - } -} - -impl NewService for ServerMessageAcceptor -where - T: NewService, -{ - type Request = ServerMessage; - type Response = (); - type Error = T::Error; - type InitError = T::InitError; - type Service = ServerMessageAcceptorService; - type Future = ServerMessageAcceptorResponse; - - fn new_service(&self) -> Self::Future { - ServerMessageAcceptorResponse { - fut: self.inner.new_service(), - } - } -} - -pub(crate) struct ServerMessageAcceptorResponse -where - T: NewService, -{ - fut: T::Future, -} - -impl Future for ServerMessageAcceptorResponse -where - T: NewService, -{ - type Item = ServerMessageAcceptorService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(service) => Ok(Async::Ready(ServerMessageAcceptorService { - inner: service, - })), - } - } -} - -pub(crate) struct ServerMessageAcceptorService { - inner: T, -} - -impl Service for ServerMessageAcceptorService -where - T: Service, -{ - type Request = ServerMessage; - type Response = (); - type Error = T::Error; - type Future = - Either, FutureResult<(), Self::Error>>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.inner.poll_ready() - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - match req { - ServerMessage::Connect(stream) => { - Either::A(ServerMessageAcceptorServiceFut { - fut: self.inner.call(stream), - }) - } - ServerMessage::Shutdown(_) => Either::B(ok(())), - ServerMessage::ForceShutdown => { - // self.settings - // .head() - // .traverse(|proto: &mut HttpProtocol| proto.shutdown()); - Either::B(ok(())) - } - } - } -} - -pub(crate) struct ServerMessageAcceptorServiceFut { - fut: T::Future, -} - -impl Future for ServerMessageAcceptorServiceFut -where - T: Service, -{ - type Item = (); - type Error = T::Error; - - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(_) => Ok(Async::Ready(())), - } - } -} diff --git a/src/server/builder.rs b/src/server/builder.rs deleted file mode 100644 index ea3638f1..00000000 --- a/src/server/builder.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::{fmt, net}; - -use actix_net::either::Either; -use actix_net::server::{Server, ServiceFactory}; -use actix_net::service::{NewService, NewServiceExt}; - -use super::acceptor::{ - AcceptorServiceFactory, AcceptorTimeout, ServerMessageAcceptor, TcpAcceptor, -}; -use super::error::AcceptorError; -use super::handler::IntoHttpHandler; -use super::service::{HttpService, StreamConfiguration}; -use super::settings::{ServerSettings, ServiceConfig}; -use super::KeepAlive; - -pub(crate) trait ServiceProvider { - fn register( - &self, - server: Server, - lst: net::TcpListener, - host: String, - addr: net::SocketAddr, - keep_alive: KeepAlive, - secure: bool, - client_timeout: u64, - client_shutdown: u64, - ) -> Server; -} - -/// Utility type that builds complete http pipeline -pub(crate) struct HttpServiceBuilder -where - F: Fn() -> H + Send + Clone, -{ - factory: F, - acceptor: A, -} - -impl HttpServiceBuilder -where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, -{ - /// Create http service builder - pub fn new(factory: F, acceptor: A) -> Self { - Self { factory, acceptor } - } - - fn finish( - &self, - host: String, - addr: net::SocketAddr, - keep_alive: KeepAlive, - secure: bool, - client_timeout: u64, - client_shutdown: u64, - ) -> impl ServiceFactory { - let factory = self.factory.clone(); - let acceptor = self.acceptor.clone(); - move || { - let app = (factory)().into_handler(); - let settings = ServiceConfig::new( - app, - keep_alive, - client_timeout, - client_shutdown, - ServerSettings::new(addr, &host, false), - ); - - if secure { - Either::B(ServerMessageAcceptor::new( - TcpAcceptor::new(AcceptorTimeout::new( - client_timeout, - acceptor.create(), - )).map_err(|_| ()) - .map_init_err(|_| ()) - .and_then(StreamConfiguration::new().nodelay(true)) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } else { - Either::A(ServerMessageAcceptor::new( - TcpAcceptor::new(acceptor.create().map_err(AcceptorError::Service)) - .map_err(|_| ()) - .map_init_err(|_| ()) - .and_then(StreamConfiguration::new().nodelay(true)) - .and_then( - HttpService::new(settings) - .map_init_err(|_| ()) - .map_err(|_| ()), - ), - )) - } - } - } -} - -impl ServiceProvider for HttpServiceBuilder -where - F: Fn() -> H + Send + Clone + 'static, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - H: IntoHttpHandler, -{ - fn register( - &self, - server: Server, - lst: net::TcpListener, - host: String, - addr: net::SocketAddr, - keep_alive: KeepAlive, - secure: bool, - client_timeout: u64, - client_shutdown: u64, - ) -> Server { - server.listen2( - "actix-web", - lst, - self.finish( - host, - addr, - keep_alive, - secure, - client_timeout, - client_shutdown, - ), - ) - } -} diff --git a/src/server/channel.rs b/src/server/channel.rs deleted file mode 100644 index d65b05e8..00000000 --- a/src/server/channel.rs +++ /dev/null @@ -1,300 +0,0 @@ -use std::net::Shutdown; -use std::{io, mem, time}; - -use bytes::{Buf, BufMut, BytesMut}; -use futures::{Async, Future, Poll}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -use super::error::HttpDispatchError; -use super::settings::ServiceConfig; -use super::{h1, h2, HttpHandler, IoStream}; -use http::StatusCode; - -const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; - -pub(crate) enum HttpProtocol { - H1(h1::Http1Dispatcher), - H2(h2::Http2), - Unknown(ServiceConfig, T, BytesMut), - None, -} - -// impl HttpProtocol { -// fn shutdown_(&mut self) { -// match self { -// HttpProtocol::H1(ref mut h1) => { -// let io = h1.io(); -// let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); -// let _ = IoStream::shutdown(io, Shutdown::Both); -// } -// HttpProtocol::H2(ref mut h2) => h2.shutdown(), -// HttpProtocol::Unknown(_, io, _) => { -// let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0))); -// let _ = IoStream::shutdown(io, Shutdown::Both); -// } -// HttpProtocol::None => (), -// } -// } -// } - -enum ProtocolKind { - Http1, - Http2, -} - -#[doc(hidden)] -pub struct HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - proto: HttpProtocol, - ka_timeout: Option, -} - -impl HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub(crate) fn new(settings: ServiceConfig, io: T) -> HttpChannel { - let ka_timeout = settings.client_timer(); - - HttpChannel { - ka_timeout, - proto: HttpProtocol::Unknown(settings, io, BytesMut::with_capacity(8192)), - } - } -} - -impl Future for HttpChannel -where - T: IoStream, - H: HttpHandler + 'static, -{ - type Item = (); - type Error = HttpDispatchError; - - fn poll(&mut self) -> Poll { - // keep-alive timer - if self.ka_timeout.is_some() { - match self.ka_timeout.as_mut().unwrap().poll() { - Ok(Async::Ready(_)) => { - trace!("Slow request timed out, close connection"); - let proto = mem::replace(&mut self.proto, HttpProtocol::None); - if let HttpProtocol::Unknown(settings, io, buf) = proto { - self.proto = HttpProtocol::H1(h1::Http1Dispatcher::for_error( - settings, - io, - StatusCode::REQUEST_TIMEOUT, - self.ka_timeout.take(), - buf, - )); - return self.poll(); - } - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => (), - Err(_) => panic!("Something is really wrong"), - } - } - - let mut is_eof = false; - let kind = match self.proto { - HttpProtocol::H1(ref mut h1) => return h1.poll(), - HttpProtocol::H2(ref mut h2) => return h2.poll(), - HttpProtocol::Unknown(_, ref mut io, ref mut buf) => { - let mut err = None; - let mut disconnect = false; - match io.read_available(buf) { - Ok(Async::Ready((read_some, stream_closed))) => { - is_eof = stream_closed; - // Only disconnect if no data was read. - if is_eof && !read_some { - disconnect = true; - } - } - Err(e) => { - err = Some(e.into()); - } - _ => (), - } - if disconnect { - debug!("Ignored premature client disconnection"); - return Ok(Async::Ready(())); - } else if let Some(e) = err { - return Err(e); - } - - if buf.len() >= 14 { - if buf[..14] == HTTP2_PREFACE[..] { - ProtocolKind::Http2 - } else { - ProtocolKind::Http1 - } - } else { - return Ok(Async::NotReady); - } - } - HttpProtocol::None => unreachable!(), - }; - - // upgrade to specific http protocol - let proto = mem::replace(&mut self.proto, HttpProtocol::None); - if let HttpProtocol::Unknown(settings, io, buf) = proto { - match kind { - ProtocolKind::Http1 => { - self.proto = HttpProtocol::H1(h1::Http1Dispatcher::new( - settings, - io, - buf, - is_eof, - self.ka_timeout.take(), - )); - return self.poll(); - } - ProtocolKind::Http2 => { - self.proto = HttpProtocol::H2(h2::Http2::new( - settings, - io, - buf.freeze(), - self.ka_timeout.take(), - )); - return self.poll(); - } - } - } - unreachable!() - } -} - -#[doc(hidden)] -pub struct H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - proto: HttpProtocol, -} - -impl H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub(crate) fn new(settings: ServiceConfig, io: T) -> H1Channel { - H1Channel { - proto: HttpProtocol::H1(h1::Http1Dispatcher::new( - settings, - io, - BytesMut::with_capacity(8192), - false, - None, - )), - } - } -} - -impl Future for H1Channel -where - T: IoStream, - H: HttpHandler + 'static, -{ - type Item = (); - type Error = HttpDispatchError; - - fn poll(&mut self) -> Poll { - match self.proto { - HttpProtocol::H1(ref mut h1) => h1.poll(), - _ => unreachable!(), - } - } -} - -/// Wrapper for `AsyncRead + AsyncWrite` types -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 } - } -} - -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(()) - } - #[inline] - fn set_keepalive(&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, -{ - #[inline] - fn read_buf(&mut self, buf: &mut B) -> Poll { - self.io.read_buf(buf) - } -} - -impl AsyncWrite for WrapperStream -where - T: AsyncRead + AsyncWrite + 'static, -{ - #[inline] - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.shutdown() - } - #[inline] - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.io.write_buf(buf) - } -} diff --git a/src/server/error.rs b/src/server/error.rs deleted file mode 100644 index 70f10099..00000000 --- a/src/server/error.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::io; - -use futures::{Async, Poll}; -use http2; - -use super::{helpers, HttpHandlerTask, Writer}; -use http::{StatusCode, Version}; -use Error; - -/// Errors produced by `AcceptorError` service. -#[derive(Debug)] -pub enum AcceptorError { - /// The inner service error - Service(T), - - /// Io specific error - Io(io::Error), - - /// The request did not complete within the specified timeout. - Timeout, -} - -#[derive(Fail, Debug)] -/// A set of errors that can occur during dispatching http requests -pub enum HttpDispatchError { - /// Application error - #[fail(display = "Application specific error: {}", _0)] - App(Error), - - /// An `io::Error` that occurred while trying to read or write to a network - /// stream. - #[fail(display = "IO error: {}", _0)] - Io(io::Error), - - /// The first request did not complete within the specified timeout. - #[fail(display = "The first request did not complete within the specified timeout")] - SlowRequestTimeout, - - /// Shutdown timeout - #[fail(display = "Connection shutdown timeout")] - ShutdownTimeout, - - /// HTTP2 error - #[fail(display = "HTTP2 error: {}", _0)] - Http2(http2::Error), - - /// Payload is not consumed - #[fail(display = "Task is completed but request's payload is not consumed")] - PayloadIsNotConsumed, - - /// Malformed request - #[fail(display = "Malformed request")] - MalformedRequest, - - /// Internal error - #[fail(display = "Internal error")] - InternalError, - - /// Unknown error - #[fail(display = "Unknown error")] - Unknown, -} - -impl From for HttpDispatchError { - fn from(err: Error) -> Self { - HttpDispatchError::App(err) - } -} - -impl From for HttpDispatchError { - fn from(err: io::Error) -> Self { - HttpDispatchError::Io(err) - } -} - -impl From for HttpDispatchError { - fn from(err: http2::Error) -> Self { - HttpDispatchError::Http2(err) - } -} - -pub(crate) struct ServerError(Version, StatusCode); - -impl ServerError { - pub fn err(ver: Version, status: StatusCode) -> Box { - Box::new(ServerError(ver, status)) - } -} - -impl HttpHandlerTask for ServerError { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - { - let bytes = io.buffer(); - // Buffer should have sufficient capacity for status line - // and extra space - bytes.reserve(helpers::STATUS_LINE_BUF_SIZE + 1); - helpers::write_status_line(self.0, self.1.as_u16(), bytes); - } - // Convert Status Code to Reason. - let reason = self.1.canonical_reason().unwrap_or(""); - io.buffer().extend_from_slice(reason.as_bytes()); - // No response body. - io.buffer().extend_from_slice(b"\r\ncontent-length: 0\r\n"); - // date header - io.set_date(); - Ok(Async::Ready(true)) - } -} diff --git a/src/server/h1.rs b/src/server/h1.rs deleted file mode 100644 index fa7d2fda..00000000 --- a/src/server/h1.rs +++ /dev/null @@ -1,1353 +0,0 @@ -use std::collections::VecDeque; -use std::net::{Shutdown, SocketAddr}; -use std::time::{Duration, Instant}; - -use bytes::BytesMut; -use futures::{Async, Future, Poll}; -use tokio_current_thread::spawn; -use tokio_timer::Delay; - -use body::Binary; -use error::{Error, PayloadError}; -use http::{StatusCode, Version}; -use payload::{Payload, PayloadStatus, PayloadWriter}; - -use super::error::{HttpDispatchError, ServerError}; -use super::h1decoder::{DecoderError, H1Decoder, Message}; -use super::h1writer::H1Writer; -use super::handler::{HttpHandler, HttpHandlerTask, HttpHandlerTaskFut}; -use super::input::PayloadType; -use super::settings::ServiceConfig; -use super::{IoStream, Writer}; - -const MAX_PIPELINED_MESSAGES: usize = 16; - -bitflags! { - pub struct Flags: u8 { - const STARTED = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_0010; - const KEEPALIVE = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const READ_DISCONNECTED = 0b0001_0000; - const WRITE_DISCONNECTED = 0b0010_0000; - const POLLED = 0b0100_0000; - const FLUSHED = 0b1000_0000; - } -} - -/// Dispatcher for HTTP/1.1 protocol -pub struct Http1Dispatcher { - flags: Flags, - settings: ServiceConfig, - addr: Option, - stream: H1Writer, - decoder: H1Decoder, - payload: Option, - buf: BytesMut, - tasks: VecDeque>, - error: Option, - ka_expire: Instant, - ka_timer: Option, -} - -enum Entry { - Task(H::Task, Option<()>), - Error(Box), -} - -impl Entry { - fn into_task(self) -> H::Task { - match self { - Entry::Task(task, _) => task, - Entry::Error(_) => panic!(), - } - } - fn disconnected(&mut self) { - match *self { - Entry::Task(ref mut task, _) => task.disconnected(), - Entry::Error(ref mut task) => task.disconnected(), - } - } - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match *self { - Entry::Task(ref mut task, ref mut except) => { - match except { - Some(_) => { - let _ = io.write(&Binary::from("HTTP/1.1 100 Continue\r\n\r\n")); - } - _ => (), - }; - task.poll_io(io) - } - Entry::Error(ref mut task) => task.poll_io(io), - } - } - fn poll_completed(&mut self) -> Poll<(), Error> { - match *self { - Entry::Task(ref mut task, _) => task.poll_completed(), - Entry::Error(ref mut task) => task.poll_completed(), - } - } -} - -impl Http1Dispatcher -where - T: IoStream, - H: HttpHandler + 'static, -{ - pub fn new( - settings: ServiceConfig, - stream: T, - buf: BytesMut, - is_eof: bool, - keepalive_timer: Option, - ) -> Self { - let addr = stream.peer_addr(); - let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = settings.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (settings.now(), None) - }; - - let flags = if is_eof { - Flags::READ_DISCONNECTED | Flags::FLUSHED - } else if settings.keep_alive_enabled() { - Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED | Flags::FLUSHED - } else { - Flags::empty() - }; - - Http1Dispatcher { - stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(), - payload: None, - tasks: VecDeque::new(), - error: None, - flags, - addr, - buf, - settings, - ka_timer, - ka_expire, - } - } - - pub(crate) fn for_error( - settings: ServiceConfig, - stream: T, - status: StatusCode, - mut keepalive_timer: Option, - buf: BytesMut, - ) -> Self { - if let Some(deadline) = settings.client_timer_expire() { - let _ = keepalive_timer.as_mut().map(|delay| delay.reset(deadline)); - } - - let mut disp = Http1Dispatcher { - flags: Flags::STARTED | Flags::READ_DISCONNECTED | Flags::FLUSHED, - stream: H1Writer::new(stream, settings.clone()), - decoder: H1Decoder::new(), - payload: None, - tasks: VecDeque::new(), - error: None, - addr: None, - ka_timer: keepalive_timer, - ka_expire: settings.now(), - buf, - settings, - }; - disp.push_response_entry(status); - disp - } - - #[inline] - fn can_read(&self) -> bool { - if self.flags.contains(Flags::READ_DISCONNECTED) { - return false; - } - - if let Some(ref info) = self.payload { - info.need_read() == PayloadStatus::Read - } else { - true - } - } - - // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(&mut self, checked: bool) { - self.flags.insert(Flags::READ_DISCONNECTED); - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete); - } - - if !checked || self.tasks.is_empty() { - self.flags - .insert(Flags::WRITE_DISCONNECTED | Flags::FLUSHED); - self.stream.disconnected(); - - // notify all tasks - for mut task in self.tasks.drain(..) { - task.disconnected(); - match task.poll_completed() { - Ok(Async::NotReady) => { - // spawn not completed task, it does not require access to io - // at this point - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - Ok(Async::Ready(_)) => (), - Err(err) => { - error!("Unhandled application error: {}", err); - } - } - } - } - } - - #[inline] - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { - // check connection keep-alive - self.poll_keepalive()?; - - // shutdown - if self.flags.contains(Flags::SHUTDOWN) { - if self.flags.contains(Flags::WRITE_DISCONNECTED) { - return Ok(Async::Ready(())); - } - return self.poll_flush(true); - } - - // process incoming requests - if !self.flags.contains(Flags::WRITE_DISCONNECTED) { - self.poll_handler()?; - - // flush stream - self.poll_flush(false)?; - - // deal with keep-alive and stream eof (client-side write shutdown) - if self.tasks.is_empty() && self.flags.contains(Flags::FLUSHED) { - // handle stream eof - if self - .flags - .intersects(Flags::READ_DISCONNECTED | Flags::WRITE_DISCONNECTED) - { - return Ok(Async::Ready(())); - } - // no keep-alive - if self.flags.contains(Flags::STARTED) - && (!self.flags.contains(Flags::KEEPALIVE_ENABLED) - || !self.flags.contains(Flags::KEEPALIVE)) - { - self.flags.insert(Flags::SHUTDOWN); - return self.poll(); - } - } - Ok(Async::NotReady) - } else if let Some(err) = self.error.take() { - Err(err) - } else { - Ok(Async::Ready(())) - } - } - - /// Flush stream - fn poll_flush(&mut self, shutdown: bool) -> Poll<(), HttpDispatchError> { - if shutdown || self.flags.contains(Flags::STARTED) { - match self.stream.poll_completed(shutdown) { - Ok(Async::NotReady) => { - // mark stream - if !self.stream.flushed() { - self.flags.remove(Flags::FLUSHED); - } - Ok(Async::NotReady) - } - Err(err) => { - debug!("Error sending data: {}", err); - self.client_disconnected(false); - Err(err.into()) - } - Ok(Async::Ready(_)) => { - // if payload is not consumed we can not use connection - if self.payload.is_some() && self.tasks.is_empty() { - return Err(HttpDispatchError::PayloadIsNotConsumed); - } - self.flags.insert(Flags::FLUSHED); - Ok(Async::Ready(())) - } - } - } else { - Ok(Async::Ready(())) - } - } - - /// keep-alive timer. returns `true` is keep-alive, otherwise drop - fn poll_keepalive(&mut self) -> Result<(), HttpDispatchError> { - if let Some(ref mut timer) = self.ka_timer { - match timer.poll() { - Ok(Async::Ready(_)) => { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - let io = self.stream.get_mut(); - let _ = IoStream::set_linger(io, Some(Duration::from_secs(0))); - let _ = IoStream::shutdown(io, Shutdown::Both); - return Err(HttpDispatchError::ShutdownTimeout); - } - if timer.deadline() >= self.ka_expire { - // check for any outstanding request handling - if self.tasks.is_empty() && self.flags.contains(Flags::FLUSHED) { - if !self.flags.contains(Flags::STARTED) { - // timeout on first request (slow request) return 408 - trace!("Slow request timeout"); - self.flags - .insert(Flags::STARTED | Flags::READ_DISCONNECTED); - self.tasks.push_back(Entry::Error(ServerError::err( - Version::HTTP_11, - StatusCode::REQUEST_TIMEOUT, - ))); - } else { - trace!("Keep-alive timeout, close connection"); - self.flags.insert(Flags::SHUTDOWN); - - // start shutdown timer - if let Some(deadline) = - self.settings.client_shutdown_timer() - { - timer.reset(deadline); - let _ = timer.poll(); - } else { - return Ok(()); - } - } - } else if let Some(dl) = self.settings.keep_alive_expire() { - timer.reset(dl); - let _ = timer.poll(); - } - } else { - timer.reset(self.ka_expire); - let _ = timer.poll(); - } - } - Ok(Async::NotReady) => (), - Err(e) => { - error!("Timer error {:?}", e); - return Err(HttpDispatchError::Unknown); - } - } - } - - Ok(()) - } - - #[inline] - /// read data from the stream - pub(self) fn poll_io(&mut self) -> Result { - if !self.flags.contains(Flags::POLLED) { - self.flags.insert(Flags::POLLED); - if !self.buf.is_empty() { - let updated = self.parse()?; - return Ok(updated); - } - } - - // read io from socket - let mut updated = false; - if self.can_read() && self.tasks.len() < MAX_PIPELINED_MESSAGES { - match self.stream.get_mut().read_available(&mut self.buf) { - Ok(Async::Ready((read_some, disconnected))) => { - if read_some && self.parse()? { - updated = true; - } - if disconnected { - self.client_disconnected(true); - } - } - Ok(Async::NotReady) => (), - Err(err) => { - self.client_disconnected(false); - return Err(err.into()); - } - } - } - Ok(updated) - } - - pub(self) fn poll_handler(&mut self) -> Result<(), HttpDispatchError> { - self.poll_io()?; - let mut retry = self.can_read(); - - // process first pipelined response, only first task can do io operation in http/1 - while !self.tasks.is_empty() { - match self.tasks[0].poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - let task = self.tasks.pop_front().unwrap(); - if !ready { - // task is done with io operations but still needs to do more work - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - } - Ok(Async::NotReady) => { - // check if we need timer - if self.ka_timer.is_some() && self.stream.upgrade() { - self.ka_timer.take(); - } - - // if read-backpressure is enabled and we consumed some data. - // we may read more dataand retry - if !retry && self.can_read() && self.poll_io()? { - retry = self.can_read(); - continue; - } - break; - } - Err(err) => { - error!("Unhandled error1: {}", err); - // it is not possible to recover from error - // during pipe handling, so just drop connection - self.client_disconnected(false); - return Err(err.into()); - } - } - } - - // check in-flight messages. all tasks must be alive, - // they need to produce response. if app returned error - // and we can not continue processing incoming requests. - let mut idx = 1; - while idx < self.tasks.len() { - let stop = match self.tasks[idx].poll_completed() { - Ok(Async::NotReady) => false, - Ok(Async::Ready(_)) => true, - Err(err) => { - self.error = Some(err.into()); - true - } - }; - if stop { - // error in task handling or task is completed, - // so no response for this task which means we can not read more requests - // because pipeline sequence is broken. - // but we can safely complete existing tasks - self.flags.insert(Flags::READ_DISCONNECTED); - - for mut task in self.tasks.drain(idx..) { - task.disconnected(); - match task.poll_completed() { - Ok(Async::NotReady) => { - // spawn not completed task, it does not require access to io - // at this point - spawn(HttpHandlerTaskFut::new(task.into_task())); - } - Ok(Async::Ready(_)) => (), - Err(err) => { - error!("Unhandled application error: {}", err); - } - } - } - break; - } else { - idx += 1; - } - } - - Ok(()) - } - - fn push_response_entry(&mut self, status: StatusCode) { - self.tasks - .push_back(Entry::Error(ServerError::err(Version::HTTP_11, status))); - } - - pub(self) fn parse(&mut self) -> Result { - let mut updated = false; - - 'outer: loop { - match self.decoder.decode(&mut self.buf, &self.settings) { - Ok(Some(Message::Message { - mut msg, - mut expect, - payload, - })) => { - updated = true; - self.flags.insert(Flags::STARTED); - - if payload { - let (ps, pl) = Payload::new(false); - *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); - } - - // stream extensions - msg.inner_mut().stream_extensions = - self.stream.get_mut().extensions(); - - // set remote addr - msg.inner_mut().addr = self.addr; - - // search handler for request - match self.settings.handler().handle(msg) { - Ok(mut task) => { - if self.tasks.is_empty() { - if expect { - expect = false; - let _ = self.stream.write(&Binary::from( - "HTTP/1.1 100 Continue\r\n\r\n", - )); - } - match task.poll_io(&mut self.stream) { - Ok(Async::Ready(ready)) => { - // override keep-alive state - if self.stream.keepalive() { - self.flags.insert(Flags::KEEPALIVE); - } else { - self.flags.remove(Flags::KEEPALIVE); - } - // prepare stream for next response - self.stream.reset(); - - if !ready { - // task is done with io operations - // but still needs to do more work - spawn(HttpHandlerTaskFut::new(task)); - } - continue 'outer; - } - Ok(Async::NotReady) => (), - Err(err) => { - error!("Unhandled error: {}", err); - self.client_disconnected(false); - return Err(err.into()); - } - } - } - self.tasks.push_back(Entry::Task( - task, - if expect { Some(()) } else { None }, - )); - continue 'outer; - } - Err(_) => { - // handler is not found - self.push_response_entry(StatusCode::NOT_FOUND); - } - } - } - Ok(Some(Message::Chunk(chunk))) => { - updated = true; - if let Some(ref mut payload) = self.payload { - payload.feed_data(chunk); - } else { - error!("Internal server error: unexpected payload chunk"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); - break; - } - } - Ok(Some(Message::Eof)) => { - updated = true; - if let Some(mut payload) = self.payload.take() { - payload.feed_eof(); - } else { - error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.push_response_entry(StatusCode::INTERNAL_SERVER_ERROR); - self.error = Some(HttpDispatchError::InternalError); - break; - } - } - Ok(None) => { - if self.flags.contains(Flags::READ_DISCONNECTED) { - self.client_disconnected(true); - } - break; - } - Err(e) => { - if let Some(mut payload) = self.payload.take() { - let e = match e { - DecoderError::Io(e) => PayloadError::Io(e), - DecoderError::Error(_) => PayloadError::EncodingCorrupted, - }; - payload.set_error(e); - } - - // Malformed requests should be responded with 400 - self.push_response_entry(StatusCode::BAD_REQUEST); - self.flags.insert(Flags::READ_DISCONNECTED | Flags::STARTED); - self.error = Some(HttpDispatchError::MalformedRequest); - break; - } - } - } - - if self.ka_timer.is_some() && updated { - if let Some(expire) = self.settings.keep_alive_expire() { - self.ka_expire = expire; - } - } - Ok(updated) - } -} - -#[cfg(test)] -mod tests { - use std::net::Shutdown; - use std::{cmp, io, time}; - - use actix::System; - use bytes::{Buf, Bytes, BytesMut}; - use futures::future; - use http::{Method, Version}; - use tokio_io::{AsyncRead, AsyncWrite}; - - use super::*; - use application::{App, HttpApplication}; - use httpmessage::HttpMessage; - use server::h1decoder::Message; - use server::handler::IntoHttpHandler; - use server::settings::{ServerSettings, ServiceConfig}; - use server::{KeepAlive, Request}; - - fn wrk_settings() -> ServiceConfig { - ServiceConfig::::new( - App::new().into_handler(), - KeepAlive::Os, - 5000, - 2000, - ServerSettings::default(), - ) - } - - impl Message { - fn message(self) -> Request { - match self { - Message::Message { msg, .. } => msg, - _ => panic!("error"), - } - } - fn is_payload(&self) -> bool { - match *self { - Message::Message { payload, .. } => payload, - _ => panic!("error"), - } - } - fn chunk(self) -> Bytes { - match self { - Message::Chunk(chunk) => chunk, - _ => panic!("error"), - } - } - fn eof(&self) -> bool { - match *self { - Message::Eof => true, - _ => false, - } - } - } - - macro_rules! parse_ready { - ($e:expr) => {{ - let settings = wrk_settings(); - match H1Decoder::new().decode($e, &settings) { - Ok(Some(msg)) => msg.message(), - Ok(_) => unreachable!("Eof during parsing http request"), - Err(err) => unreachable!("Error during parsing http request: {:?}", err), - } - }}; - } - - macro_rules! expect_parse_err { - ($e:expr) => {{ - let settings = wrk_settings(); - - match H1Decoder::new().decode($e, &settings) { - Err(err) => match err { - DecoderError::Error(_) => (), - _ => unreachable!("Parse error expected"), - }, - _ => unreachable!("Error expected"), - } - }}; - } - - struct Buffer { - buf: Bytes, - err: Option, - } - - impl Buffer { - fn new(data: &'static str) -> Buffer { - Buffer { - buf: Bytes::from(data), - err: None, - } - } - } - - impl AsyncRead for Buffer {} - impl io::Read for Buffer { - fn read(&mut self, dst: &mut [u8]) -> Result { - if self.buf.is_empty() { - if self.err.is_some() { - Err(self.err.take().unwrap()) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - let size = cmp::min(self.buf.len(), dst.len()); - let b = self.buf.split_to(size); - dst[..size].copy_from_slice(&b); - Ok(size) - } - } - } - - impl IoStream for Buffer { - fn shutdown(&mut self, _: Shutdown) -> io::Result<()> { - Ok(()) - } - fn set_nodelay(&mut self, _: bool) -> io::Result<()> { - Ok(()) - } - fn set_linger(&mut self, _: Option) -> io::Result<()> { - Ok(()) - } - fn set_keepalive(&mut 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) - } - } - - #[test] - fn test_req_parse_err() { - let mut sys = System::new("test"); - let _ = sys.block_on(future::lazy(|| { - let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - let readbuf = BytesMut::new(); - let settings = wrk_settings(); - - let mut h1 = - Http1Dispatcher::new(settings.clone(), buf, readbuf, false, None); - assert!(h1.poll_io().is_ok()); - assert!(h1.poll_io().is_ok()); - assert!(h1.flags.contains(Flags::READ_DISCONNECTED)); - assert_eq!(h1.tasks.len(), 1); - future::ok::<_, ()>(()) - })); - } - - #[test] - fn test_parse() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_partial() { - let mut buf = BytesMut::from("PUT /test HTTP/1"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { - Ok(None) => (), - _ => unreachable!("Error"), - } - - buf.extend(b".1\r\n\r\n"); - match reader.decode(&mut buf, &settings) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::PUT); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_post() { - let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_10); - assert_eq!(*req.method(), Method::POST); - assert_eq!(req.path(), "/test2"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_body() { - let mut buf = - BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_body_crlf() { - let mut buf = - BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(); - match reader.decode(&mut buf, &settings) { - Ok(Some(msg)) => { - let mut req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), - b"body" - ); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_partial_eof() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - - buf.extend(b"\r\n"); - match reader.decode(&mut buf, &settings) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_headers_split_field() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(); - assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } - - buf.extend(b"t"); - assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } - - buf.extend(b"es"); - assert! { reader.decode(&mut buf, &settings).unwrap().is_none() } - - buf.extend(b"t: value\r\n\r\n"); - match reader.decode(&mut buf, &settings) { - Ok(Some(msg)) => { - let req = msg.message(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_headers_multi_value() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - Set-Cookie: c1=cookie1\r\n\ - Set-Cookie: c2=cookie2\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - let req = msg.message(); - - let val: Vec<_> = req - .headers() - .get_all("Set-Cookie") - .iter() - .map(|v| v.to_str().unwrap().to_owned()) - .collect(); - assert_eq!(val[0], "c1=cookie1"); - assert_eq!(val[1], "c2=cookie2"); - } - - #[test] - fn test_conn_default_1_0() { - let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_default_1_1() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_close() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: Close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_close_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: Close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_keep_alive_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: Keep-Alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_keep_alive_1_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_other_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(!req.keep_alive()); - } - - #[test] - fn test_conn_other_1_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.keep_alive()); - } - - #[test] - fn test_conn_upgrade() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - upgrade: websockets\r\n\ - connection: upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - upgrade: Websockets\r\n\ - connection: Upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - } - - #[test] - fn test_conn_upgrade_connect_method() { - let mut buf = BytesMut::from( - "CONNECT /test HTTP/1.1\r\n\ - content-type: text/plain\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - } - - #[test] - fn test_request_chunked() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(val); - } else { - unreachable!("Error"); - } - - // type in chunked - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chnked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(!val); - } else { - unreachable!("Error"); - } - } - - #[test] - fn test_headers_content_length_err_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - content-length: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf) - } - - #[test] - fn test_headers_content_length_err_2() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - content-length: -1\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_invalid_header() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - test line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_invalid_name() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - test[]: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_bad_status_line() { - let mut buf = BytesMut::from("getpath \r\n\r\n"); - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_upgrade() { - let settings = wrk_settings(); - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: upgrade\r\n\ - upgrade: websocket\r\n\r\n\ - some raw data", - ); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(!req.keep_alive()); - assert!(req.upgrade()); - assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), - b"some raw data" - ); - } - - #[test] - fn test_http_request_parser_utf8() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - x-test: теÑÑ‚\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!( - req.headers().get("x-test").unwrap().as_bytes(), - "теÑÑ‚".as_bytes() - ); - } - - #[test] - fn test_http_request_parser_two_slashes() { - let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert_eq!(req.path(), "//path"); - } - - #[test] - fn test_http_request_parser_bad_method() { - let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_parser_bad_version() { - let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_chunked_payload() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); - assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), - b"data" - ); - assert_eq!( - reader - .decode(&mut buf, &settings) - .unwrap() - .unwrap() - .chunk() - .as_ref(), - b"line" - ); - assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); - } - - #[test] - fn test_http_request_chunked_payload_and_next_message() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend( - b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ - POST /test2 HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n" - .iter(), - ); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert!(msg.eof()); - - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert!(msg.is_payload()); - let req2 = msg.message(); - assert!(req2.chunked().unwrap()); - assert_eq!(*req2.method(), Method::POST); - assert!(req2.chunked().unwrap()); - } - - #[test] - fn test_http_request_chunked_payload_chunks() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert!(msg.is_payload()); - let req = msg.message(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\n1111\r\n"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"1111"); - - buf.extend(b"4\r\ndata\r"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - - buf.extend(b"\n4"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - - buf.extend(b"\r"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - buf.extend(b"\n"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - - buf.extend(b"li"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"li"); - - //trailers - //buf.feed_data("test: test\r\n"); - //not_ready!(reader.parse(&mut buf, &mut readbuf)); - - buf.extend(b"ne\r\n0\r\n"); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(reader.decode(&mut buf, &settings).unwrap().is_none()); - - buf.extend(b"\r\n"); - assert!(reader.decode(&mut buf, &settings).unwrap().unwrap().eof()); - } - - #[test] - fn test_parse_chunked_payload_chunk_extension() { - let mut buf = BytesMut::from( - &"GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"[..], - ); - let settings = wrk_settings(); - - let mut reader = H1Decoder::new(); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert!(msg.is_payload()); - assert!(msg.message().chunked().unwrap()); - - buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = reader.decode(&mut buf, &settings).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = reader.decode(&mut buf, &settings).unwrap().unwrap(); - assert!(msg.eof()); - } -} diff --git a/src/server/h1decoder.rs b/src/server/h1decoder.rs deleted file mode 100644 index ece6b3cc..00000000 --- a/src/server/h1decoder.rs +++ /dev/null @@ -1,541 +0,0 @@ -use std::{io, mem}; - -use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; -use httparse; - -use super::message::{MessageFlags, Request}; -use super::settings::ServiceConfig; -use error::ParseError; -use http::header::{HeaderName, HeaderValue}; -use http::{header, HttpTryFrom, Method, Uri, Version}; -use uri::Url; - -const MAX_BUFFER_SIZE: usize = 131_072; -const MAX_HEADERS: usize = 96; - -pub(crate) struct H1Decoder { - decoder: Option, -} - -#[derive(Debug)] -pub(crate) enum Message { - Message { - msg: Request, - payload: bool, - expect: bool, - }, - Chunk(Bytes), - Eof, -} - -#[derive(Debug)] -pub(crate) enum DecoderError { - Io(io::Error), - Error(ParseError), -} - -impl From for DecoderError { - fn from(err: io::Error) -> DecoderError { - DecoderError::Io(err) - } -} - -impl H1Decoder { - pub fn new() -> H1Decoder { - H1Decoder { decoder: None } - } - - pub fn decode( - &mut self, - src: &mut BytesMut, - settings: &ServiceConfig, - ) -> Result, DecoderError> { - // read payload - if self.decoder.is_some() { - match self.decoder.as_mut().unwrap().decode(src)? { - Async::Ready(Some(bytes)) => return Ok(Some(Message::Chunk(bytes))), - Async::Ready(None) => { - self.decoder.take(); - return Ok(Some(Message::Eof)); - } - Async::NotReady => return Ok(None), - } - } - - match self - .parse_message(src, settings) - .map_err(DecoderError::Error)? - { - Async::Ready((msg, expect, decoder)) => { - self.decoder = decoder; - Ok(Some(Message::Message { - msg, - expect, - payload: self.decoder.is_some(), - })) - } - Async::NotReady => { - if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - Err(DecoderError::Error(ParseError::TooLarge)) - } else { - Ok(None) - } - } - } - } - - fn parse_message( - &self, - buf: &mut BytesMut, - settings: &ServiceConfig, - ) -> Poll<(Request, bool, Option), ParseError> { - // Parse http message - let mut has_upgrade = false; - let mut chunked = false; - let mut content_length = None; - let mut expect_continue = false; - - let msg = { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let (len, method, path, version, headers_len) = { - let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; - - let mut req = httparse::Request::new(&mut parsed); - match req.parse(buf)? { - httparse::Status::Complete(len) => { - let method = Method::from_bytes(req.method.unwrap().as_bytes()) - .map_err(|_| ParseError::Method)?; - let path = Url::new(Uri::try_from(req.path.unwrap())?); - let version = if req.version.unwrap() == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - HeaderIndex::record(buf, req.headers, &mut headers); - - (len, method, path, version, req.headers.len()) - } - httparse::Status::Partial => return Ok(Async::NotReady), - } - }; - - let slice = buf.split_to(len).freeze(); - - // convert headers - let mut msg = settings.get_request(); - { - let inner = msg.inner_mut(); - inner - .flags - .get_mut() - .set(MessageFlags::KEEPALIVE, version != Version::HTTP_10); - - for idx in headers[..headers_len].iter() { - if let Ok(name) = - HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]) - { - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_shared_unchecked( - slice.slice(idx.value.0, idx.value.1), - ) - }; - match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { - if let Ok(len) = s.parse::() { - content_length = Some(len); - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", len); - return Err(ParseError::Header); - } - } - // transfer-encoding - header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str().map(|s| s.trim()) { - chunked = s.eq_ignore_ascii_case("chunked"); - } else { - return Err(ParseError::Header); - } - } - // connection keep-alive state - header::CONNECTION => { - let ka = if let Ok(conn) = - value.to_str().map(|conn| conn.trim()) - { - if version == Version::HTTP_10 - && conn.eq_ignore_ascii_case("keep-alive") - { - true - } else { - version == Version::HTTP_11 - && !(conn.eq_ignore_ascii_case("close") - || conn.eq_ignore_ascii_case("upgrade")) - } - } else { - false - }; - inner.flags.get_mut().set(MessageFlags::KEEPALIVE, ka); - } - header::UPGRADE => { - has_upgrade = true; - // check content-length, some clients (dart) - // sends "content-length: 0" with websocket upgrade - if let Ok(val) = value.to_str().map(|val| val.trim()) { - if val.eq_ignore_ascii_case("websocket") { - content_length = None; - } - } - } - header::EXPECT => { - if value == "100-continue" { - expect_continue = true - } - } - _ => (), - } - - inner.headers.append(name, value); - } else { - return Err(ParseError::Header); - } - } - - inner.url = path; - inner.method = method; - inner.version = version; - } - msg - }; - - // https://tools.ietf.org/html/rfc7230#section-3.3.3 - let decoder = if chunked { - // Chunked encoding - Some(EncodingDecoder::chunked()) - } else if let Some(len) = content_length { - // Content-Length - Some(EncodingDecoder::length(len)) - } else if has_upgrade || msg.inner.method == Method::CONNECT { - // upgrade(websocket) or connect - Some(EncodingDecoder::eof()) - } else { - None - }; - - Ok(Async::Ready((msg, expect_continue, decoder))) - } -} - -#[derive(Clone, Copy)] -pub(crate) struct HeaderIndex { - pub(crate) name: (usize, usize), - pub(crate) value: (usize, usize), -} - -impl HeaderIndex { - pub(crate) fn record( - bytes: &[u8], - headers: &[httparse::Header], - indices: &mut [HeaderIndex], - ) { - 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); - } - } -} - -/// Decoders to handle different Transfer-Encodings. -/// -/// If a message body does not include a Transfer-Encoding, it *should* -/// include a Content-Length header. -#[derive(Debug, Clone, PartialEq)] -pub struct EncodingDecoder { - kind: Kind, -} - -impl EncodingDecoder { - pub fn length(x: u64) -> EncodingDecoder { - EncodingDecoder { - kind: Kind::Length(x), - } - } - - pub fn chunked() -> EncodingDecoder { - EncodingDecoder { - kind: Kind::Chunked(ChunkedState::Size, 0), - } - } - - pub fn eof() -> EncodingDecoder { - EncodingDecoder { - kind: Kind::Eof(false), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -enum Kind { - /// A Reader used when a Content-Length header is passed with a positive - /// integer. - Length(u64), - /// A Reader used when Transfer-Encoding is `chunked`. - Chunked(ChunkedState, u64), - /// A Reader used for responses that don't indicate a length or chunked. - /// - /// Note: This should only used for `Response`s. It is illegal for a - /// `Request` to be made with both `Content-Length` and - /// `Transfer-Encoding: chunked` missing, as explained from the spec: - /// - /// > If a Transfer-Encoding header field is present in a response and - /// > the chunked transfer coding is not the final encoding, the - /// > message body length is determined by reading the connection until - /// > it is closed by the server. If a Transfer-Encoding header field - /// > is present in a request and the chunked transfer coding is not - /// > the final encoding, the message body length cannot be determined - /// > reliably; the server MUST respond with the 400 (Bad Request) - /// > status code and then close the connection. - Eof(bool), -} - -#[derive(Debug, PartialEq, Clone)] -enum ChunkedState { - Size, - SizeLws, - Extension, - SizeLf, - Body, - BodyCr, - BodyLf, - EndCr, - EndLf, - End, -} - -impl EncodingDecoder { - pub fn decode(&mut self, body: &mut BytesMut) -> Poll, io::Error> { - match self.kind { - Kind::Length(ref mut remaining) => { - if *remaining == 0 { - Ok(Async::Ready(None)) - } else { - if body.is_empty() { - return Ok(Async::NotReady); - } - let len = body.len() as u64; - let buf; - if *remaining > len { - buf = body.take().freeze(); - *remaining -= len; - } else { - buf = body.split_to(*remaining as usize).freeze(); - *remaining = 0; - } - trace!("Length read: {}", buf.len()); - Ok(Async::Ready(Some(buf))) - } - } - Kind::Chunked(ref mut state, ref mut size) => { - loop { - let mut buf = None; - // advances the chunked state - *state = try_ready!(state.step(body, size, &mut buf)); - if *state == ChunkedState::End { - trace!("End of chunked stream"); - return Ok(Async::Ready(None)); - } - if let Some(buf) = buf { - return Ok(Async::Ready(Some(buf))); - } - if body.is_empty() { - return Ok(Async::NotReady); - } - } - } - Kind::Eof(ref mut is_eof) => { - if *is_eof { - Ok(Async::Ready(None)) - } else if !body.is_empty() { - Ok(Async::Ready(Some(body.take().freeze()))) - } else { - Ok(Async::NotReady) - } - } - } - } -} - -macro_rules! byte ( - ($rdr:ident) => ({ - if $rdr.len() > 0 { - let b = $rdr[0]; - $rdr.split_to(1); - b - } else { - return Ok(Async::NotReady) - } - }) -); - -impl ChunkedState { - fn step( - &self, - body: &mut BytesMut, - size: &mut u64, - buf: &mut Option, - ) -> Poll { - use self::ChunkedState::*; - match *self { - Size => ChunkedState::read_size(body, size), - SizeLws => ChunkedState::read_size_lws(body), - Extension => ChunkedState::read_extension(body), - SizeLf => ChunkedState::read_size_lf(body, size), - Body => ChunkedState::read_body(body, size, buf), - BodyCr => ChunkedState::read_body_cr(body), - BodyLf => ChunkedState::read_body_lf(body), - EndCr => ChunkedState::read_end_cr(body), - EndLf => ChunkedState::read_end_lf(body), - End => Ok(Async::Ready(ChunkedState::End)), - } - } - fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll { - let radix = 16; - match byte!(rdr) { - b @ b'0'...b'9' => { - *size *= radix; - *size += u64::from(b - b'0'); - } - b @ b'a'...b'f' => { - *size *= radix; - *size += u64::from(b + 10 - b'a'); - } - b @ b'A'...b'F' => { - *size *= radix; - *size += u64::from(b + 10 - b'A'); - } - b'\t' | b' ' => return Ok(Async::Ready(ChunkedState::SizeLws)), - b';' => return Ok(Async::Ready(ChunkedState::Extension)), - b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)), - _ => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size line: Invalid Size", - )); - } - } - Ok(Async::Ready(ChunkedState::Size)) - } - fn read_size_lws(rdr: &mut BytesMut) -> Poll { - trace!("read_size_lws"); - match byte!(rdr) { - // LWS can follow the chunk size, but no more digits can come - b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)), - b';' => Ok(Async::Ready(ChunkedState::Extension)), - b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size linear white space", - )), - } - } - fn read_extension(rdr: &mut BytesMut) -> Poll { - 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 { - match byte!(rdr) { - b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), - b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size LF", - )), - } - } - - fn read_body( - rdr: &mut BytesMut, - rem: &mut u64, - buf: &mut Option, - ) -> Poll { - trace!("Chunked read, remaining={:?}", rem); - - let len = rdr.len() as u64; - if len == 0 { - Ok(Async::Ready(ChunkedState::Body)) - } else { - let slice; - if *rem > len { - slice = rdr.take().freeze(); - *rem -= len; - } else { - slice = rdr.split_to(*rem as usize).freeze(); - *rem = 0; - } - *buf = Some(slice); - if *rem > 0 { - Ok(Async::Ready(ChunkedState::Body)) - } else { - Ok(Async::Ready(ChunkedState::BodyCr)) - } - } - } - - fn read_body_cr(rdr: &mut BytesMut) -> Poll { - match byte!(rdr) { - b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body CR", - )), - } - } - fn read_body_lf(rdr: &mut BytesMut) -> Poll { - match byte!(rdr) { - b'\n' => Ok(Async::Ready(ChunkedState::Size)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body LF", - )), - } - } - fn read_end_cr(rdr: &mut BytesMut) -> Poll { - match byte!(rdr) { - b'\r' => Ok(Async::Ready(ChunkedState::EndLf)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end CR", - )), - } - } - fn read_end_lf(rdr: &mut BytesMut) -> Poll { - match byte!(rdr) { - b'\n' => Ok(Async::Ready(ChunkedState::End)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end LF", - )), - } - } -} diff --git a/src/server/h1writer.rs b/src/server/h1writer.rs deleted file mode 100644 index 97ce6dff..00000000 --- a/src/server/h1writer.rs +++ /dev/null @@ -1,364 +0,0 @@ -// #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] - -use std::io::{self, Write}; - -use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; -use tokio_io::AsyncWrite; - -use super::helpers; -use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::ServiceConfig; -use super::Request; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use body::{Binary, Body}; -use header::ContentEncoding; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{Method, Version}; -use httpresponse::HttpResponse; - -const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific - -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: T, - written: u64, - headers_size: u32, - buffer: Output, - buffer_capacity: usize, - settings: ServiceConfig, -} - -impl H1Writer { - pub fn new(stream: T, settings: ServiceConfig) -> H1Writer { - H1Writer { - flags: Flags::KEEPALIVE, - written: 0, - headers_size: 0, - buffer: Output::Buffer(settings.get_bytes()), - buffer_capacity: 0, - stream, - settings, - } - } - - pub fn get_mut(&mut self) -> &mut T { - &mut self.stream - } - - pub fn reset(&mut self) { - self.written = 0; - self.flags = Flags::KEEPALIVE; - } - - pub fn flushed(&mut self) -> bool { - self.buffer.is_empty() - } - - pub fn disconnected(&mut self) { - self.flags.insert(Flags::DISCONNECTED); - } - - pub fn upgrade(&self) -> bool { - self.flags.contains(Flags::UPGRADE) - } - - pub fn keepalive(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE) - } - - fn write_data(stream: &mut T, data: &[u8]) -> io::Result { - let mut written = 0; - while written < data.len() { - match stream.write(&data[written..]) { - Ok(0) => { - return Err(io::Error::new(io::ErrorKind::WriteZero, "")); - } - Ok(n) => { - written += n; - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - return Ok(written) - } - Err(err) => return Err(err), - } - } - Ok(written) - } -} - -impl Drop for H1Writer { - fn drop(&mut self) { - if let Some(bytes) = self.buffer.take_option() { - self.settings.release_bytes(bytes); - } - } -} - -impl Writer for H1Writer { - #[inline] - fn written(&self) -> u64 { - self.written - } - - #[inline] - fn set_date(&mut self) { - self.settings.set_date(self.buffer.as_mut(), true) - } - - #[inline] - fn buffer(&mut self) -> &mut BytesMut { - self.buffer.as_mut() - } - - fn start( - &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result { - // prepare task - let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); - self.buffer.for_server(&mut info, &req.inner, msg, encoding); - if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { - self.flags = Flags::STARTED | Flags::KEEPALIVE; - } else { - self.flags = Flags::STARTED; - } - - // Connection upgrade - let version = msg.version().unwrap_or_else(|| req.inner.version); - if msg.upgrade() { - self.flags.insert(Flags::UPGRADE); - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("upgrade")); - } - // keep-alive - else if self.flags.contains(Flags::KEEPALIVE) { - if version < Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("keep-alive")); - } - } else if version >= Version::HTTP_11 { - msg.headers_mut() - .insert(CONNECTION, HeaderValue::from_static("close")); - } - let body = msg.replace_body(Body::Empty); - - // render message - { - // output buffer - let mut buffer = self.buffer.as_mut(); - - let reason = msg.reason().as_bytes(); - if let Body::Binary(ref bytes) = body { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE - + bytes.len() - + reason.len(), - ); - } else { - buffer.reserve( - 256 + msg.headers().len() * AVERAGE_HEADER_SIZE + reason.len(), - ); - } - - // status line - helpers::write_status_line(version, msg.status().as_u16(), &mut buffer); - buffer.extend_from_slice(reason); - - // content length - let mut len_is_set = true; - match info.length { - ResponseLength::Chunked => { - buffer.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - ResponseLength::Length(len) => { - helpers::write_content_length(len, &mut buffer) - } - ResponseLength::Length64(len) => { - buffer.extend_from_slice(b"\r\ncontent-length: "); - write!(buffer.writer(), "{}", len)?; - buffer.extend_from_slice(b"\r\n"); - } - ResponseLength::Zero => { - len_is_set = false; - buffer.extend_from_slice(b"\r\n"); - } - ResponseLength::None => buffer.extend_from_slice(b"\r\n"), - } - if let Some(ce) = info.content_encoding { - buffer.extend_from_slice(b"content-encoding: "); - buffer.extend_from_slice(ce.as_ref()); - buffer.extend_from_slice(b"\r\n"); - } - - // write headers - let mut pos = 0; - let mut has_date = false; - let mut remaining = buffer.remaining_mut(); - let mut buf = unsafe { &mut *(buffer.bytes_mut() as *mut [u8]) }; - for (key, value) in msg.headers() { - match *key { - TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - ResponseLength::Zero => { - len_is_set = true; - } - _ => continue, - }, - DATE => { - has_date = true; - } - _ => (), - } - - let v = value.as_ref(); - let k = key.as_str().as_bytes(); - let len = k.len() + v.len() + 4; - if len > remaining { - unsafe { - buffer.advance_mut(pos); - } - pos = 0; - buffer.reserve(len); - remaining = buffer.remaining_mut(); - unsafe { - buf = &mut *(buffer.bytes_mut() as *mut _); - } - } - - buf[pos..pos + k.len()].copy_from_slice(k); - pos += k.len(); - buf[pos..pos + 2].copy_from_slice(b": "); - pos += 2; - buf[pos..pos + v.len()].copy_from_slice(v); - pos += v.len(); - buf[pos..pos + 2].copy_from_slice(b"\r\n"); - pos += 2; - remaining -= len; - } - unsafe { - buffer.advance_mut(pos); - } - if !len_is_set { - buffer.extend_from_slice(b"content-length: 0\r\n") - } - - // optimized date header, set_date writes \r\n - if !has_date { - self.settings.set_date(&mut buffer, true); - } else { - // msg eof - buffer.extend_from_slice(b"\r\n"); - } - self.headers_size = buffer.len() as u32; - } - - if let Body::Binary(bytes) = body { - self.written = bytes.len() as u64; - self.buffer.write(bytes.as_ref())?; - } else { - // capacity, makes sense only for streaming or actor - self.buffer_capacity = msg.write_buffer_capacity(); - - msg.replace_body(body); - } - Ok(WriterState::Done) - } - - fn write(&mut self, payload: &Binary) -> io::Result { - self.written += payload.len() as u64; - if !self.flags.contains(Flags::DISCONNECTED) { - if self.flags.contains(Flags::STARTED) { - // shortcut for upgraded connection - if self.flags.contains(Flags::UPGRADE) { - if self.buffer.is_empty() { - let pl: &[u8] = payload.as_ref(); - let n = match Self::write_data(&mut self.stream, pl) { - Err(err) => { - self.disconnected(); - return Err(err); - } - Ok(val) => val, - }; - if n < pl.len() { - self.buffer.write(&pl[n..])?; - return Ok(WriterState::Done); - } - } else { - self.buffer.write(payload.as_ref())?; - } - } else { - // TODO: add warning, write after EOF - self.buffer.write(payload.as_ref())?; - } - } else { - // could be response to EXCEPT header - self.buffer.write(payload.as_ref())?; - } - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn write_eof(&mut self) -> io::Result { - if !self.buffer.write_eof()? { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - #[inline] - fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> { - if self.flags.contains(Flags::DISCONNECTED) { - return Err(io::Error::new(io::ErrorKind::Other, "disconnected")); - } - - if !self.buffer.is_empty() { - let written = { - match Self::write_data(&mut self.stream, self.buffer.as_ref().as_ref()) { - Err(err) => { - self.disconnected(); - return Err(err); - } - Ok(val) => val, - } - }; - let _ = self.buffer.split_to(written); - if shutdown && !self.buffer.is_empty() - || (self.buffer.len() > self.buffer_capacity) - { - return Ok(Async::NotReady); - } - } - if shutdown { - self.stream.poll_flush()?; - self.stream.shutdown() - } else { - Ok(self.stream.poll_flush()?) - } - } -} diff --git a/src/server/h2.rs b/src/server/h2.rs deleted file mode 100644 index c9e968a3..00000000 --- a/src/server/h2.rs +++ /dev/null @@ -1,472 +0,0 @@ -use std::collections::VecDeque; -use std::io::{Read, Write}; -use std::net::SocketAddr; -use std::rc::Rc; -use std::time::Instant; -use std::{cmp, io, mem}; - -use bytes::{Buf, Bytes}; -use futures::{Async, Future, Poll, Stream}; -use http2::server::{self, Connection, Handshake, SendResponse}; -use http2::{Reason, RecvStream}; -use modhttp::request::Parts; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_timer::Delay; - -use error::{Error, PayloadError}; -use extensions::Extensions; -use http::{StatusCode, Version}; -use payload::{Payload, PayloadStatus, PayloadWriter}; -use uri::Url; - -use super::error::{HttpDispatchError, ServerError}; -use super::h2writer::H2Writer; -use super::input::PayloadType; -use super::settings::ServiceConfig; -use super::{HttpHandler, HttpHandlerTask, IoStream, Writer}; - -bitflags! { - struct Flags: u8 { - const DISCONNECTED = 0b0000_0001; - const SHUTDOWN = 0b0000_0010; - } -} - -/// HTTP/2 Transport -pub(crate) struct Http2 -where - T: AsyncRead + AsyncWrite + 'static, - H: HttpHandler + 'static, -{ - flags: Flags, - settings: ServiceConfig, - addr: Option, - state: State>, - tasks: VecDeque>, - extensions: Option>, - ka_expire: Instant, - ka_timer: Option, -} - -enum State { - Handshake(Handshake), - Connection(Connection), - Empty, -} - -impl Http2 -where - T: IoStream + 'static, - H: HttpHandler + 'static, -{ - pub fn new( - settings: ServiceConfig, - io: T, - buf: Bytes, - keepalive_timer: Option, - ) -> Self { - let addr = io.peer_addr(); - let extensions = io.extensions(); - - // keep-alive timeout - let (ka_expire, ka_timer) = if let Some(delay) = keepalive_timer { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = settings.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (settings.now(), None) - }; - - Http2 { - flags: Flags::empty(), - tasks: VecDeque::new(), - state: State::Handshake(server::handshake(IoWrapper { - unread: if buf.is_empty() { None } else { Some(buf) }, - inner: io, - })), - addr, - settings, - extensions, - ka_expire, - ka_timer, - } - } - - pub fn poll(&mut self) -> Poll<(), HttpDispatchError> { - self.poll_keepalive()?; - - // server - if let State::Connection(ref mut conn) = self.state { - loop { - // shutdown connection - if self.flags.contains(Flags::SHUTDOWN) { - return conn.poll_close().map_err(|e| e.into()); - } - - let mut not_ready = true; - let disconnected = self.flags.contains(Flags::DISCONNECTED); - - // check in-flight connections - for item in &mut self.tasks { - // read payload - if !disconnected { - item.poll_payload(); - } - - if !item.flags.contains(EntryFlags::EOF) { - if disconnected { - item.flags.insert(EntryFlags::EOF); - } else { - let retry = item.payload.need_read() == PayloadStatus::Read; - loop { - match item.task.poll_io(&mut item.stream) { - Ok(Async::Ready(ready)) => { - if ready { - item.flags.insert( - EntryFlags::EOF | EntryFlags::FINISHED, - ); - } else { - item.flags.insert(EntryFlags::EOF); - } - not_ready = false; - } - Ok(Async::NotReady) => { - if item.payload.need_read() - == PayloadStatus::Read - && !retry - { - continue; - } - } - Err(err) => { - error!("Unhandled error: {}", err); - item.flags.insert( - EntryFlags::EOF - | EntryFlags::ERROR - | EntryFlags::WRITE_DONE, - ); - item.stream.reset(Reason::INTERNAL_ERROR); - } - } - break; - } - } - } - - if item.flags.contains(EntryFlags::EOF) - && !item.flags.contains(EntryFlags::FINISHED) - { - match item.task.poll_completed() { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - item.flags.insert( - EntryFlags::FINISHED | EntryFlags::WRITE_DONE, - ); - } - Err(err) => { - item.flags.insert( - EntryFlags::ERROR - | EntryFlags::WRITE_DONE - | EntryFlags::FINISHED, - ); - error!("Unhandled error: {}", err); - } - } - } - - if item.flags.contains(EntryFlags::FINISHED) - && !item.flags.contains(EntryFlags::WRITE_DONE) - && !disconnected - { - match item.stream.poll_completed(false) { - Ok(Async::NotReady) => (), - Ok(Async::Ready(_)) => { - not_ready = false; - item.flags.insert(EntryFlags::WRITE_DONE); - } - Err(_) => { - item.flags.insert(EntryFlags::ERROR); - } - } - } - } - - // cleanup finished tasks - while !self.tasks.is_empty() { - if self.tasks[0].flags.contains(EntryFlags::FINISHED) - && self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) - || self.tasks[0].flags.contains(EntryFlags::ERROR) - { - self.tasks.pop_front(); - } else { - break; - } - } - - // get request - if !self.flags.contains(Flags::DISCONNECTED) { - match conn.poll() { - Ok(Async::Ready(None)) => { - not_ready = false; - self.flags.insert(Flags::DISCONNECTED); - for entry in &mut self.tasks { - entry.task.disconnected() - } - } - Ok(Async::Ready(Some((req, resp)))) => { - not_ready = false; - let (parts, body) = req.into_parts(); - - // update keep-alive expire - if self.ka_timer.is_some() { - if let Some(expire) = self.settings.keep_alive_expire() { - self.ka_expire = expire; - } - } - - self.tasks.push_back(Entry::new( - parts, - body, - resp, - self.addr, - self.settings.clone(), - self.extensions.clone(), - )); - } - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { - trace!("Connection error: {}", err); - self.flags.insert(Flags::SHUTDOWN); - for entry in &mut self.tasks { - entry.task.disconnected() - } - continue; - } - } - } - - if not_ready { - if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) - { - return conn.poll_close().map_err(|e| e.into()); - } else { - return Ok(Async::NotReady); - } - } - } - } - - // handshake - self.state = if let State::Handshake(ref mut handshake) = self.state { - match handshake.poll() { - Ok(Async::Ready(conn)) => State::Connection(conn), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { - trace!("Error handling connection: {}", err); - return Err(err.into()); - } - } - } else { - mem::replace(&mut self.state, State::Empty) - }; - - self.poll() - } - - /// keep-alive timer. returns `true` is keep-alive, otherwise drop - fn poll_keepalive(&mut self) -> Result<(), HttpDispatchError> { - if let Some(ref mut timer) = self.ka_timer { - match timer.poll() { - Ok(Async::Ready(_)) => { - // if we get timer during shutdown, just drop connection - if self.flags.contains(Flags::SHUTDOWN) { - return Err(HttpDispatchError::ShutdownTimeout); - } - if timer.deadline() >= self.ka_expire { - // check for any outstanding request handling - if self.tasks.is_empty() { - return Err(HttpDispatchError::ShutdownTimeout); - } else if let Some(dl) = self.settings.keep_alive_expire() { - timer.reset(dl); - let _ = timer.poll(); - } - } else { - timer.reset(self.ka_expire); - let _ = timer.poll(); - } - } - Ok(Async::NotReady) => (), - Err(e) => { - error!("Timer error {:?}", e); - return Err(HttpDispatchError::Unknown); - } - } - } - - Ok(()) - } -} - -bitflags! { - struct EntryFlags: u8 { - const EOF = 0b0000_0001; - const REOF = 0b0000_0010; - const ERROR = 0b0000_0100; - const FINISHED = 0b0000_1000; - const WRITE_DONE = 0b0001_0000; - } -} - -enum EntryPipe { - Task(H::Task), - Error(Box), -} - -impl EntryPipe { - fn disconnected(&mut self) { - match *self { - EntryPipe::Task(ref mut task) => task.disconnected(), - EntryPipe::Error(ref mut task) => task.disconnected(), - } - } - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match *self { - EntryPipe::Task(ref mut task) => task.poll_io(io), - EntryPipe::Error(ref mut task) => task.poll_io(io), - } - } - fn poll_completed(&mut self) -> Poll<(), Error> { - match *self { - EntryPipe::Task(ref mut task) => task.poll_completed(), - EntryPipe::Error(ref mut task) => task.poll_completed(), - } - } -} - -struct Entry { - task: EntryPipe, - payload: PayloadType, - recv: RecvStream, - stream: H2Writer, - flags: EntryFlags, -} - -impl Entry { - fn new( - parts: Parts, - recv: RecvStream, - resp: SendResponse, - addr: Option, - settings: ServiceConfig, - extensions: Option>, - ) -> Entry - where - H: HttpHandler + 'static, - { - // Payload and Content-Encoding - let (psender, payload) = Payload::new(false); - - let mut msg = settings.get_request(); - { - let inner = msg.inner_mut(); - inner.url = Url::new(parts.uri); - inner.method = parts.method; - inner.version = parts.version; - inner.headers = parts.headers; - inner.stream_extensions = extensions; - *inner.payload.borrow_mut() = Some(payload); - inner.addr = addr; - } - - // Payload sender - let psender = PayloadType::new(msg.headers(), psender); - - // start request processing - let task = match settings.handler().handle(msg) { - Ok(task) => EntryPipe::Task(task), - Err(_) => EntryPipe::Error(ServerError::err( - Version::HTTP_2, - StatusCode::NOT_FOUND, - )), - }; - - Entry { - task, - recv, - payload: psender, - stream: H2Writer::new(resp, settings), - flags: EntryFlags::empty(), - } - } - - fn poll_payload(&mut self) { - while !self.flags.contains(EntryFlags::REOF) - && self.payload.need_read() == PayloadStatus::Read - { - match self.recv.poll() { - Ok(Async::Ready(Some(chunk))) => { - let l = chunk.len(); - self.payload.feed_data(chunk); - if let Err(err) = self.recv.release_capacity().release_capacity(l) { - self.payload.set_error(PayloadError::Http2(err)); - break; - } - } - Ok(Async::Ready(None)) => { - self.flags.insert(EntryFlags::REOF); - self.payload.feed_eof(); - } - Ok(Async::NotReady) => break, - Err(err) => { - self.payload.set_error(PayloadError::Http2(err)); - break; - } - } - } - } -} - -struct IoWrapper { - unread: Option, - inner: T, -} - -impl Read for IoWrapper { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - if let Some(mut bytes) = self.unread.take() { - let size = cmp::min(buf.len(), bytes.len()); - buf[..size].copy_from_slice(&bytes[..size]); - if bytes.len() > size { - bytes.split_to(size); - self.unread = Some(bytes); - } - Ok(size) - } else { - self.inner.read(buf) - } - } -} - -impl Write for IoWrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) - } - fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } -} - -impl AsyncRead for IoWrapper { - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.inner.prepare_uninitialized_buffer(buf) - } -} - -impl AsyncWrite for IoWrapper { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.inner.shutdown() - } - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.inner.write_buf(buf) - } -} diff --git a/src/server/h2writer.rs b/src/server/h2writer.rs deleted file mode 100644 index fef6f889..00000000 --- a/src/server/h2writer.rs +++ /dev/null @@ -1,268 +0,0 @@ -#![cfg_attr( - feature = "cargo-clippy", - allow(redundant_field_names) -)] - -use std::{cmp, io}; - -use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; -use http2::server::SendResponse; -use http2::{Reason, SendStream}; -use modhttp::Response; - -use super::helpers; -use super::message::Request; -use super::output::{Output, ResponseInfo, ResponseLength}; -use super::settings::ServiceConfig; -use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE}; -use body::{Binary, Body}; -use header::ContentEncoding; -use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, -}; -use http::{HttpTryFrom, Method, Version}; -use httpresponse::HttpResponse; - -const CHUNK_SIZE: usize = 16_384; - -bitflags! { - struct Flags: u8 { - const STARTED = 0b0000_0001; - const DISCONNECTED = 0b0000_0010; - const EOF = 0b0000_0100; - const RESERVED = 0b0000_1000; - } -} - -pub(crate) struct H2Writer { - respond: SendResponse, - stream: Option>, - flags: Flags, - written: u64, - buffer: Output, - buffer_capacity: usize, - settings: ServiceConfig, -} - -impl H2Writer { - pub fn new(respond: SendResponse, settings: ServiceConfig) -> H2Writer { - H2Writer { - stream: None, - flags: Flags::empty(), - written: 0, - buffer: Output::Buffer(settings.get_bytes()), - buffer_capacity: 0, - respond, - settings, - } - } - - pub fn reset(&mut self, reason: Reason) { - if let Some(mut stream) = self.stream.take() { - stream.send_reset(reason) - } - } -} - -impl Drop for H2Writer { - fn drop(&mut self) { - self.settings.release_bytes(self.buffer.take()); - } -} - -impl Writer for H2Writer { - fn written(&self) -> u64 { - self.written - } - - #[inline] - fn set_date(&mut self) { - self.settings.set_date(self.buffer.as_mut(), true) - } - - #[inline] - fn buffer(&mut self) -> &mut BytesMut { - self.buffer.as_mut() - } - - fn start( - &mut self, req: &Request, msg: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result { - // prepare response - self.flags.insert(Flags::STARTED); - let mut info = ResponseInfo::new(req.inner.method == Method::HEAD); - self.buffer.for_server(&mut info, &req.inner, msg, encoding); - - let mut has_date = false; - let mut resp = Response::new(()); - let mut len_is_set = false; - *resp.status_mut() = msg.status(); - *resp.version_mut() = Version::HTTP_2; - for (key, value) in msg.headers().iter() { - match *key { - // http2 specific - CONNECTION | TRANSFER_ENCODING => continue, - CONTENT_ENCODING => if encoding != ContentEncoding::Identity { - continue; - }, - CONTENT_LENGTH => match info.length { - ResponseLength::None => (), - ResponseLength::Zero => { - len_is_set = true; - } - _ => continue, - }, - DATE => has_date = true, - _ => (), - } - resp.headers_mut().append(key, value.clone()); - } - - // set date header - if !has_date { - let mut bytes = BytesMut::with_capacity(29); - self.settings.set_date(&mut bytes, false); - resp.headers_mut() - .insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap()); - } - - // content length - match info.length { - ResponseLength::Zero => { - if !len_is_set { - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); - } - self.flags.insert(Flags::EOF); - } - ResponseLength::Length(len) => { - let mut val = BytesMut::new(); - helpers::convert_usize(len, &mut val); - let l = val.len(); - resp.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(), - ); - } - ResponseLength::Length64(len) => { - let l = format!("{}", len); - resp.headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap()); - } - ResponseLength::None => { - self.flags.insert(Flags::EOF); - } - _ => (), - } - if let Some(ce) = info.content_encoding { - resp.headers_mut() - .insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap()); - } - - trace!("Response: {:?}", resp); - - match self - .respond - .send_response(resp, self.flags.contains(Flags::EOF)) - { - Ok(stream) => self.stream = Some(stream), - Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "err")), - } - - let body = msg.replace_body(Body::Empty); - if let Body::Binary(bytes) = body { - if bytes.is_empty() { - Ok(WriterState::Done) - } else { - self.flags.insert(Flags::EOF); - self.buffer.write(bytes.as_ref())?; - if let Some(ref mut stream) = self.stream { - self.flags.insert(Flags::RESERVED); - stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); - } - Ok(WriterState::Pause) - } - } else { - msg.replace_body(body); - self.buffer_capacity = msg.write_buffer_capacity(); - Ok(WriterState::Done) - } - } - - fn write(&mut self, payload: &Binary) -> io::Result { - if !self.flags.contains(Flags::DISCONNECTED) { - if self.flags.contains(Flags::STARTED) { - // TODO: add warning, write after EOF - self.buffer.write(payload.as_ref())?; - } else { - // might be response for EXCEPT - error!("Not supported"); - } - } - - if self.buffer.len() > self.buffer_capacity { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn write_eof(&mut self) -> io::Result { - self.flags.insert(Flags::EOF); - if !self.buffer.write_eof()? { - Err(io::Error::new( - io::ErrorKind::Other, - "Last payload item, but eof is not reached", - )) - } else if self.buffer.len() > MAX_WRITE_BUFFER_SIZE { - Ok(WriterState::Pause) - } else { - Ok(WriterState::Done) - } - } - - fn poll_completed(&mut self, _shutdown: bool) -> Poll<(), io::Error> { - if !self.flags.contains(Flags::STARTED) { - return Ok(Async::NotReady); - } - - if let Some(ref mut stream) = self.stream { - // reserve capacity - if !self.flags.contains(Flags::RESERVED) && !self.buffer.is_empty() { - self.flags.insert(Flags::RESERVED); - stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE)); - } - - loop { - match stream.poll_capacity() { - Ok(Async::NotReady) => return Ok(Async::NotReady), - Ok(Async::Ready(None)) => return Ok(Async::Ready(())), - Ok(Async::Ready(Some(cap))) => { - let len = self.buffer.len(); - let bytes = self.buffer.split_to(cmp::min(cap, len)); - let eof = - self.buffer.is_empty() && self.flags.contains(Flags::EOF); - self.written += bytes.len() as u64; - - if let Err(e) = stream.send_data(bytes.freeze(), eof) { - return Err(io::Error::new(io::ErrorKind::Other, e)); - } else if !self.buffer.is_empty() { - let cap = cmp::min(self.buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - if eof { - stream.reserve_capacity(0); - continue; - } - self.flags.remove(Flags::RESERVED); - return Ok(Async::Ready(())); - } - } - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)), - } - } - } - Ok(Async::Ready(())) - } -} diff --git a/src/server/handler.rs b/src/server/handler.rs deleted file mode 100644 index 33e50ac3..00000000 --- a/src/server/handler.rs +++ /dev/null @@ -1,208 +0,0 @@ -use futures::{Async, Future, Poll}; - -use super::message::Request; -use super::Writer; -use error::Error; - -/// Low level http request handler -#[allow(unused_variables)] -pub trait HttpHandler: 'static { - /// Request handling task - type Task: HttpHandlerTask; - - /// Handle request - fn handle(&self, req: Request) -> Result; -} - -impl HttpHandler for Box>> { - type Task = Box; - - fn handle(&self, req: Request) -> Result, Request> { - self.as_ref().handle(req) - } -} - -/// Low level http request handler -pub trait HttpHandlerTask { - /// Poll task, this method is used before or after *io* object is available - fn poll_completed(&mut self) -> Poll<(), Error> { - Ok(Async::Ready(())) - } - - /// Poll task when *io* object is available - fn poll_io(&mut self, io: &mut Writer) -> Poll; - - /// Connection is disconnected - fn disconnected(&mut self) {} -} - -impl HttpHandlerTask for Box { - fn poll_io(&mut self, io: &mut Writer) -> Poll { - self.as_mut().poll_io(io) - } -} - -pub(super) struct HttpHandlerTaskFut { - task: T, -} - -impl HttpHandlerTaskFut { - pub(crate) fn new(task: T) -> Self { - Self { task } - } -} - -impl Future for HttpHandlerTaskFut { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll<(), ()> { - self.task.poll_completed().map_err(|_| ()) - } -} - -/// 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 - } -} - -impl IntoHttpHandler for Vec { - type Handler = VecHttpHandler; - - fn into_handler(self) -> Self::Handler { - VecHttpHandler(self.into_iter().map(|item| item.into_handler()).collect()) - } -} - -#[doc(hidden)] -pub struct VecHttpHandler(Vec); - -impl HttpHandler for VecHttpHandler { - type Task = H::Task; - - fn handle(&self, mut req: Request) -> Result { - for h in &self.0 { - req = match h.handle(req) { - Ok(task) => return Ok(task), - Err(e) => e, - }; - } - Err(req) - } -} - -macro_rules! http_handler ({$EN:ident, $(($n:tt, $T:ident)),+} => { - impl<$($T: HttpHandler,)+> HttpHandler for ($($T,)+) { - type Task = $EN<$($T,)+>; - - fn handle(&self, mut req: Request) -> Result { - $( - req = match self.$n.handle(req) { - Ok(task) => return Ok($EN::$T(task)), - Err(e) => e, - }; - )+ - Err(req) - } - } - - #[doc(hidden)] - pub enum $EN<$($T: HttpHandler,)+> { - $($T ($T::Task),)+ - } - - impl<$($T: HttpHandler,)+> HttpHandlerTask for $EN<$($T,)+> - { - fn poll_completed(&mut self) -> Poll<(), Error> { - match self { - $($EN :: $T(ref mut task) => task.poll_completed(),)+ - } - } - - fn poll_io(&mut self, io: &mut Writer) -> Poll { - match self { - $($EN::$T(ref mut task) => task.poll_io(io),)+ - } - } - - /// Connection is disconnected - fn disconnected(&mut self) { - match self { - $($EN::$T(ref mut task) => task.disconnected(),)+ - } - } - } -}); - -http_handler!(HttpHandlerTask1, (0, A)); -http_handler!(HttpHandlerTask2, (0, A), (1, B)); -http_handler!(HttpHandlerTask3, (0, A), (1, B), (2, C)); -http_handler!(HttpHandlerTask4, (0, A), (1, B), (2, C), (3, D)); -http_handler!(HttpHandlerTask5, (0, A), (1, B), (2, C), (3, D), (4, E)); -http_handler!( - HttpHandlerTask6, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F) -); -http_handler!( - HttpHandlerTask7, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G) -); -http_handler!( - HttpHandlerTask8, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H) -); -http_handler!( - HttpHandlerTask9, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I) -); -http_handler!( - HttpHandlerTask10, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I), - (9, J) -); diff --git a/src/server/helpers.rs b/src/server/helpers.rs deleted file mode 100644 index e4ccd8ae..00000000 --- a/src/server/helpers.rs +++ /dev/null @@ -1,208 +0,0 @@ -use bytes::{BufMut, BytesMut}; -use http::Version; -use std::{mem, ptr, slice}; - -const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ - 2021222324252627282930313233343536373839\ - 4041424344454647484950515253545556575859\ - 6061626364656667686970717273747576777879\ - 8081828384858687888990919293949596979899"; - -pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13; - -pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; STATUS_LINE_BUF_SIZE] = [ - 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', - Version::HTTP_09 => { - buf[5] = b'0'; - buf[7] = b'9'; - } - _ => (), - } - - let mut curr: isize = 12; - let buf_ptr = buf.as_mut_ptr(); - let lut_ptr = DEC_DIGITS_LUT.as_ptr(); - let four = n > 999; - - // decode 2 more chars, if > 2 chars - let d1 = (n % 100) << 1; - n /= 100; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); - } - - // decode last 1 or 2 chars - if n < 10 { - curr -= 1; - unsafe { - *buf_ptr.offset(curr) = (n as u8) + b'0'; - } - } else { - let d1 = n << 1; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping( - lut_ptr.offset(d1 as isize), - buf_ptr.offset(curr), - 2, - ); - } - } - - bytes.put_slice(&buf); - if four { - bytes.put(b' '); - } -} - -/// NOTE: bytes object has to contain enough space -pub 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.put_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().add(d1), - buf.as_mut_ptr().offset(18), - 2, - ); - } - bytes.put_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().add(d1), - buf.as_mut_ptr().offset(19), - 2, - ) - }; - - // decode last 1 - buf[18] = (n as u8) + b'0'; - - bytes.put_slice(&buf); - } else { - bytes.put_slice(b"\r\ncontent-length: "); - convert_usize(n, bytes); - } -} - -pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { - let mut curr: isize = 39; - 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(); - - // 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; - unsafe { - 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; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - } - } - - // decode last 1 or 2 chars - if n < 10 { - curr -= 1; - unsafe { - *buf_ptr.offset(curr) = (n as u8) + b'0'; - } - } else { - let d1 = n << 1; - curr -= 2; - unsafe { - 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), - 41 - curr as usize, - )); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_write_content_length() { - let mut bytes = BytesMut::new(); - bytes.reserve(50); - write_content_length(0, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]); - bytes.reserve(50); - write_content_length(9, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]); - bytes.reserve(50); - write_content_length(10, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]); - bytes.reserve(50); - write_content_length(99, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]); - bytes.reserve(50); - write_content_length(100, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]); - bytes.reserve(50); - write_content_length(101, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]); - bytes.reserve(50); - write_content_length(998, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]); - bytes.reserve(50); - write_content_length(1000, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); - bytes.reserve(50); - write_content_length(1001, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); - bytes.reserve(50); - write_content_length(5909, &mut bytes); - assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); - } -} diff --git a/src/server/http.rs b/src/server/http.rs deleted file mode 100644 index 5ff621af..00000000 --- a/src/server/http.rs +++ /dev/null @@ -1,579 +0,0 @@ -use std::{fmt, io, mem, net}; - -use actix::{Addr, System}; -use actix_net::server::Server; -use actix_net::service::NewService; -use actix_net::ssl; - -use net2::TcpBuilder; -use num_cpus; - -#[cfg(feature = "tls")] -use native_tls::TlsAcceptor; - -#[cfg(any(feature = "alpn", feature = "ssl"))] -use openssl::ssl::SslAcceptorBuilder; - -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; - -use super::acceptor::{AcceptorServiceFactory, DefaultAcceptor}; -use super::builder::{HttpServiceBuilder, ServiceProvider}; -use super::{IntoHttpHandler, KeepAlive}; - -struct Socket { - scheme: &'static str, - lst: net::TcpListener, - addr: net::SocketAddr, - handler: Box, -} - -/// An HTTP Server -/// -/// By default it serves HTTP2 when HTTPs is enabled, -/// in order to change it, use `ServerFlags` that can be provided -/// to acceptor service. -pub struct HttpServer -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone, -{ - pub(super) factory: F, - pub(super) host: Option, - pub(super) keep_alive: KeepAlive, - pub(super) client_timeout: u64, - pub(super) client_shutdown: u64, - backlog: i32, - threads: usize, - exit: bool, - shutdown_timeout: u16, - no_http2: bool, - no_signals: bool, - maxconn: usize, - maxconnrate: usize, - sockets: Vec, -} - -impl HttpServer -where - H: IntoHttpHandler + 'static, - F: Fn() -> H + Send + Clone + 'static, -{ - /// Create new http server with application factory - pub fn new(factory: F) -> HttpServer { - HttpServer { - factory, - threads: num_cpus::get(), - host: None, - backlog: 2048, - keep_alive: KeepAlive::Timeout(5), - shutdown_timeout: 30, - exit: false, - no_http2: false, - no_signals: false, - maxconn: 25_600, - maxconnrate: 256, - client_timeout: 5000, - client_shutdown: 5000, - sockets: Vec::new(), - } - } - - /// Set number of workers to start. - /// - /// By default http server uses number of available logical cpu as threads - /// count. - pub fn workers(mut self, num: usize) -> Self { - self.threads = num; - 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. - /// - /// This method should be called before `bind()` method call. - pub fn backlog(mut self, num: i32) -> Self { - self.backlog = num; - self - } - - /// Sets the maximum per-worker number of concurrent connections. - /// - /// All socket listeners will stop accepting connections when this limit is reached - /// for each worker. - /// - /// By default max connections is set to a 25k. - pub fn maxconn(mut self, num: usize) -> Self { - self.maxconn = num; - self - } - - /// Sets the maximum per-worker concurrent connection establish process. - /// - /// All listeners will stop accepting connections when this limit is reached. It - /// can be used to limit the global SSL CPU usage. - /// - /// By default max connections is set to a 256. - pub fn maxconnrate(mut self, num: usize) -> Self { - self.maxconnrate = num; - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: T) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection shutdown timeout in milliseconds. - /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(mut self, val: u64) -> Self { - self.client_shutdown = val; - 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 - } - - /// Stop actix system. - /// - /// `SystemExit` message stops currently running system. - pub fn system_exit(mut self) -> Self { - self.exit = true; - 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. 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 - } - - /// Disable `HTTP/2` support - pub fn no_http2(mut self) -> Self { - self.no_http2 = true; - self - } - - /// Get addresses of bound sockets. - pub fn addrs(&self) -> Vec { - self.sockets.iter().map(|s| s.addr).collect() - } - - /// Get addresses of bound sockets and the scheme for it. - /// - /// This is useful when the server is bound from different sources - /// with some sockets listening on http and some listening on https - /// and the user should be presented with an enumeration of which - /// socket requires which protocol. - pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() - } - - /// Use listener for accepting incoming connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen(mut self, lst: net::TcpListener) -> Self { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "http", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - DefaultAcceptor, - )), - }); - - self - } - - #[doc(hidden)] - /// Use listener for accepting incoming connection requests - pub fn listen_with(mut self, lst: net::TcpListener, acceptor: A) -> Self - where - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new(self.factory.clone(), acceptor)), - }); - - self - } - - #[cfg(feature = "tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { - use actix_net::service::NewServiceExt; - - self.listen_with(lst, move || { - ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_ssl( - self, lst: net::TcpListener, builder: SslAcceptorBuilder, - ) -> io::Result { - use super::{openssl_acceptor_with_flags, ServerFlags}; - use actix_net::service::NewServiceExt; - - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - Ok(self.listen_with(lst, move || { - ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) - })) - } - - #[cfg(feature = "rust-tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self { - use super::{RustlsAcceptor, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - self.listen_with(lst, move || { - RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) - }) - } - - /// The socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind(mut self, addr: S) -> io::Result { - let sockets = self.bind2(addr)?; - - for lst in sockets { - self = self.listen(lst); - } - - Ok(self) - } - - /// Start listening for incoming connections with supplied acceptor. - #[doc(hidden)] - #[cfg_attr( - feature = "cargo-clippy", - allow(needless_pass_by_value) - )] - pub fn bind_with(mut self, addr: S, acceptor: A) -> io::Result - where - S: net::ToSocketAddrs, - A: AcceptorServiceFactory, - ::InitError: fmt::Debug, - { - let sockets = self.bind2(addr)?; - - for lst in sockets { - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - lst, - addr, - scheme: "https", - handler: Box::new(HttpServiceBuilder::new( - self.factory.clone(), - acceptor.clone(), - )), - }); - } - - Ok(self) - } - - fn bind2( - &self, addr: S, - ) -> io::Result> { - let mut err = None; - let mut succ = false; - let mut sockets = Vec::new(); - for addr in addr.to_socket_addrs()? { - match create_tcp_listener(addr, self.backlog) { - Ok(lst) => { - succ = true; - sockets.push(lst); - } - Err(e) => err = Some(e), - } - } - - 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) - } - } - - #[cfg(feature = "tls")] - /// The ssl socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind_tls( - self, addr: S, acceptor: TlsAcceptor, - ) -> io::Result { - use actix_net::service::NewServiceExt; - use actix_net::ssl::NativeTlsAcceptor; - - self.bind_with(addr, move || { - NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_ssl(self, addr: S, builder: SslAcceptorBuilder) -> io::Result - where - S: net::ToSocketAddrs, - { - use super::{openssl_acceptor_with_flags, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - self.bind_with(addr, move || { - ssl::OpensslAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } - - #[cfg(feature = "rust-tls")] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_rustls( - self, addr: S, builder: ServerConfig, - ) -> io::Result { - use super::{RustlsAcceptor, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; - - self.bind_with(addr, move || { - RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ()) - }) - } -} - -impl H + Send + Clone> HttpServer { - /// Start listening for incoming connections. - /// - /// This method starts number of http workers in separate threads. - /// For each address this method starts separate thread which does - /// `accept()` in a loop. - /// - /// This methods panics if no socket address can be bound or an `Actix` system is not yet - /// configured. - /// - /// ```rust - /// extern crate actix_web; - /// extern crate actix; - /// use actix_web::{server, App, HttpResponse}; - /// - /// fn main() { - /// let sys = actix::System::new("example"); // <- create Actix system - /// - /// server::new(|| App::new().resource("/", |r| r.h(|_: &_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .start(); - /// # actix::System::current().stop(); - /// sys.run(); // <- Run actix system, this method starts all async processes - /// } - /// ``` - pub fn start(mut self) -> Addr { - ssl::max_concurrent_ssl_connect(self.maxconnrate); - - let mut srv = Server::new() - .workers(self.threads) - .maxconn(self.maxconn) - .shutdown_timeout(self.shutdown_timeout); - - srv = if self.exit { srv.system_exit() } else { srv }; - srv = if self.no_signals { - srv.disable_signals() - } else { - srv - }; - - let sockets = mem::replace(&mut self.sockets, Vec::new()); - - for socket in sockets { - let host = self - .host - .as_ref() - .map(|h| h.to_owned()) - .unwrap_or_else(|| format!("{}", socket.addr)); - let (secure, client_shutdown) = if socket.scheme == "https" { - (true, self.client_shutdown) - } else { - (false, 0) - }; - srv = socket.handler.register( - srv, - socket.lst, - host, - socket.addr, - self.keep_alive, - secure, - self.client_timeout, - client_shutdown, - ); - } - srv.start() - } - - /// Spawn new thread and start listening for incoming connections. - /// - /// This method spawns new thread and starts new actix system. Other than - /// that it is similar to `start()` method. This method blocks. - /// - /// This methods panics if no socket addresses get bound. - /// - /// ```rust,ignore - /// # extern crate futures; - /// # extern crate actix_web; - /// # use futures::Future; - /// use actix_web::*; - /// - /// fn main() { - /// HttpServer::new(|| App::new().resource("/", |r| r.h(|_| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .run(); - /// } - /// ``` - pub fn run(self) { - let sys = System::new("http-server"); - self.start(); - sys.run(); - } - - /// Register current http server as actix-net's server service - pub fn register(self, mut srv: Server) -> Server { - for socket in self.sockets { - let host = self - .host - .as_ref() - .map(|h| h.to_owned()) - .unwrap_or_else(|| format!("{}", socket.addr)); - let (secure, client_shutdown) = if socket.scheme == "https" { - (true, self.client_shutdown) - } else { - (false, 0) - }; - srv = socket.handler.register( - srv, - socket.lst, - host, - socket.addr, - self.keep_alive, - secure, - self.client_timeout, - client_shutdown, - ); - } - srv - } -} - -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.reuse_address(true)?; - builder.bind(addr)?; - Ok(builder.listen(backlog)?) -} diff --git a/src/server/incoming.rs b/src/server/incoming.rs deleted file mode 100644 index b13bba2a..00000000 --- a/src/server/incoming.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Support for `Stream`, deprecated! -use std::{io, net}; - -use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message}; -use futures::{Future, Stream}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use super::channel::{HttpChannel, WrapperStream}; -use super::handler::{HttpHandler, IntoHttpHandler}; -use super::http::HttpServer; -use super::settings::{ServerSettings, ServiceConfig}; - -impl Message for WrapperStream { - type Result = (); -} - -impl HttpServer -where - H: IntoHttpHandler, - F: Fn() -> H + Send + Clone, -{ - #[doc(hidden)] - #[deprecated(since = "0.7.8")] - /// Start listening for incoming connections from a stream. - /// - /// This method uses only one thread for handling incoming connections. - pub fn start_incoming(self, stream: S, secure: bool) - where - S: Stream + 'static, - T: AsyncRead + AsyncWrite + 'static, - { - // set server settings - let addr: net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); - let apps = (self.factory)().into_handler(); - let settings = ServiceConfig::new( - apps, - self.keep_alive, - self.client_timeout, - self.client_shutdown, - ServerSettings::new(addr, "127.0.0.1:8080", secure), - ); - - // start server - HttpIncoming::create(move |ctx| { - ctx.add_message_stream(stream.map_err(|_| ()).map(WrapperStream::new)); - HttpIncoming { settings } - }); - } -} - -struct HttpIncoming { - settings: ServiceConfig, -} - -impl Actor for HttpIncoming { - type Context = Context; -} - -impl Handler> for HttpIncoming -where - T: AsyncRead + AsyncWrite, - H: HttpHandler, -{ - type Result = (); - - fn handle(&mut self, msg: WrapperStream, _: &mut Context) -> Self::Result { - Arbiter::spawn(HttpChannel::new(self.settings.clone(), msg).map_err(|_| ())); - } -} diff --git a/src/server/input.rs b/src/server/input.rs deleted file mode 100644 index d23d1e99..00000000 --- a/src/server/input.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::io::{self, Write}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliDecoder; -use bytes::{Bytes, BytesMut}; -use error::PayloadError; -#[cfg(feature = "flate2")] -use flate2::write::{GzDecoder, ZlibDecoder}; -use header::ContentEncoding; -use http::header::{HeaderMap, CONTENT_ENCODING}; -use payload::{PayloadSender, PayloadStatus, PayloadWriter}; - -pub(crate) enum PayloadType { - Sender(PayloadSender), - Encoding(Box), -} - -impl PayloadType { - #[cfg(any(feature = "brotli", feature = "flate2"))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - // check content-encoding - let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - ContentEncoding::from(enc) - } else { - ContentEncoding::Auto - } - } else { - ContentEncoding::Auto - }; - - match enc { - ContentEncoding::Auto | ContentEncoding::Identity => { - PayloadType::Sender(sender) - } - _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), - } - } - - #[cfg(not(any(feature = "brotli", feature = "flate2")))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - PayloadType::Sender(sender) - } -} - -impl PayloadWriter for PayloadType { - #[inline] - fn set_error(&mut self, err: PayloadError) { - match *self { - PayloadType::Sender(ref mut sender) => sender.set_error(err), - PayloadType::Encoding(ref mut enc) => enc.set_error(err), - } - } - - #[inline] - fn feed_eof(&mut self) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_eof(), - PayloadType::Encoding(ref mut enc) => enc.feed_eof(), - } - } - - #[inline] - fn feed_data(&mut self, data: Bytes) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_data(data), - PayloadType::Encoding(ref mut enc) => enc.feed_data(data), - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - match *self { - PayloadType::Sender(ref sender) => sender.need_read(), - PayloadType::Encoding(ref enc) => enc.need_read(), - } - } -} - -/// Payload wrapper with content decompression support -pub(crate) struct EncodedPayload { - inner: PayloadSender, - error: bool, - payload: PayloadStream, -} - -impl EncodedPayload { - pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { - EncodedPayload { - inner, - error: false, - payload: PayloadStream::new(enc), - } - } -} - -impl PayloadWriter for EncodedPayload { - fn set_error(&mut self, err: PayloadError) { - self.inner.set_error(err) - } - - fn feed_eof(&mut self) { - if !self.error { - match self.payload.feed_eof() { - Err(err) => { - self.error = true; - self.set_error(PayloadError::Io(err)); - } - Ok(value) => { - if let Some(b) = value { - self.inner.feed_data(b); - } - self.inner.feed_eof(); - } - } - } - } - - fn feed_data(&mut self, data: Bytes) { - if self.error { - return; - } - - match self.payload.feed_data(data) { - Ok(Some(b)) => self.inner.feed_data(b), - Ok(None) => (), - Err(e) => { - self.error = true; - self.set_error(e.into()); - } - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - self.inner.need_read() - } -} - -pub(crate) enum Decoder { - #[cfg(feature = "flate2")] - Deflate(Box>), - #[cfg(feature = "flate2")] - Gzip(Box>), - #[cfg(feature = "brotli")] - Br(Box>), - Identity, -} - -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::with_capacity(8192), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl io::Write for Writer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -/// Payload stream with decompression support -pub(crate) struct PayloadStream { - decoder: Decoder, -} - -impl PayloadStream { - pub fn new(enc: ContentEncoding) -> PayloadStream { - let decoder = match enc { - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - Decoder::Gzip(Box::new(GzDecoder::new(Writer::new()))) - } - _ => Decoder::Identity, - }; - PayloadStream { decoder } - } -} - -impl PayloadStream { - pub fn feed_eof(&mut self) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.finish() { - Ok(mut writer) => { - let b = writer.take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(None), - } - } - - pub fn feed_data(&mut self, data: Bytes) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(Some(data)), - } - } -} diff --git a/src/server/message.rs b/src/server/message.rs deleted file mode 100644 index 9c4bc1ec..00000000 --- a/src/server/message.rs +++ /dev/null @@ -1,284 +0,0 @@ -use std::cell::{Cell, Ref, RefCell, RefMut}; -use std::collections::VecDeque; -use std::fmt; -use std::net::SocketAddr; -use std::rc::Rc; - -use http::{header, HeaderMap, Method, Uri, Version}; - -use extensions::Extensions; -use httpmessage::HttpMessage; -use info::ConnectionInfo; -use payload::Payload; -use server::ServerSettings; -use uri::Url as InnerUrl; - -bitflags! { - pub(crate) struct MessageFlags: u8 { - const KEEPALIVE = 0b0000_0001; - const CONN_INFO = 0b0000_0010; - } -} - -/// Request's context -pub struct Request { - pub(crate) inner: Rc, -} - -pub(crate) struct InnerRequest { - pub(crate) version: Version, - pub(crate) method: Method, - pub(crate) url: InnerUrl, - pub(crate) flags: Cell, - pub(crate) headers: HeaderMap, - pub(crate) extensions: RefCell, - pub(crate) addr: Option, - pub(crate) info: RefCell, - pub(crate) payload: RefCell>, - pub(crate) settings: ServerSettings, - pub(crate) stream_extensions: Option>, - pool: &'static RequestPool, -} - -impl InnerRequest { - #[inline] - /// Reset request instance - pub fn reset(&mut self) { - self.headers.clear(); - self.extensions.borrow_mut().clear(); - self.flags.set(MessageFlags::empty()); - *self.payload.borrow_mut() = None; - } -} - -impl HttpMessage for Request { - type Stream = Payload; - - fn headers(&self) -> &HeaderMap { - &self.inner.headers - } - - #[inline] - fn payload(&self) -> Payload { - if let Some(payload) = self.inner.payload.borrow_mut().take() { - payload - } else { - Payload::empty() - } - } -} - -impl Request { - /// Create new RequestContext instance - pub(crate) fn new(pool: &'static RequestPool, settings: ServerSettings) -> Request { - Request { - inner: Rc::new(InnerRequest { - pool, - settings, - method: Method::GET, - url: InnerUrl::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - flags: Cell::new(MessageFlags::empty()), - addr: None, - info: RefCell::new(ConnectionInfo::default()), - payload: RefCell::new(None), - extensions: RefCell::new(Extensions::new()), - stream_extensions: None, - }), - } - } - - #[inline] - pub(crate) fn inner(&self) -> &InnerRequest { - self.inner.as_ref() - } - - #[inline] - pub(crate) fn inner_mut(&mut self) -> &mut InnerRequest { - Rc::get_mut(&mut self.inner).expect("Multiple copies exist") - } - - #[inline] - pub(crate) fn url(&self) -> &InnerUrl { - &self.inner().url - } - - /// Read the Request Uri. - #[inline] - pub fn uri(&self) -> &Uri { - self.inner().url.uri() - } - - /// Read the Request method. - #[inline] - pub fn method(&self) -> &Method { - &self.inner().method - } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> Version { - self.inner().version - } - - /// The target path of this Request. - #[inline] - pub fn path(&self) -> &str { - self.inner().url.path() - } - - #[inline] - /// Returns Request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.inner().headers - } - - #[inline] - /// Returns mutable Request's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.inner_mut().headers - } - - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - /// - /// To get client connection information `connection_info()` method should - /// be used. - pub fn peer_addr(&self) -> Option { - self.inner().addr - } - - /// Checks if a connection should be kept alive. - #[inline] - pub fn keep_alive(&self) -> bool { - self.inner().flags.get().contains(MessageFlags::KEEPALIVE) - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.inner().extensions.borrow() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.inner().extensions.borrow_mut() - } - - /// Check if request requires connection upgrade - pub fn upgrade(&self) -> bool { - if let Some(conn) = self.inner().headers.get(header::CONNECTION) { - if let Ok(s) = conn.to_str() { - return s.to_lowercase().contains("upgrade"); - } - } - self.inner().method == Method::CONNECT - } - - /// Get *ConnectionInfo* for the correct request. - pub fn connection_info(&self) -> Ref { - if self.inner().flags.get().contains(MessageFlags::CONN_INFO) { - self.inner().info.borrow() - } else { - let mut flags = self.inner().flags.get(); - flags.insert(MessageFlags::CONN_INFO); - self.inner().flags.set(flags); - self.inner().info.borrow_mut().update(self); - self.inner().info.borrow() - } - } - - /// Io stream extensions - #[inline] - pub fn stream_extensions(&self) -> Option<&Extensions> { - self.inner().stream_extensions.as_ref().map(|e| e.as_ref()) - } - - /// Server settings - #[inline] - pub fn server_settings(&self) -> &ServerSettings { - &self.inner().settings - } - - pub(crate) fn clone(&self) -> Self { - Request { - inner: self.inner.clone(), - } - } - - pub(crate) fn release(self) { - let mut inner = self.inner; - if let Some(r) = Rc::get_mut(&mut inner) { - r.reset(); - } else { - return; - } - inner.pool.release(inner); - } -} - -impl fmt::Debug for Request { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nRequest {:?} {}:{}", - self.version(), - self.method(), - self.path() - )?; - if let Some(q) = self.uri().query().as_ref() { - writeln!(f, " query: ?{:?}", q)?; - } - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -pub(crate) struct RequestPool( - RefCell>>, - RefCell, -); - -thread_local!(static POOL: &'static RequestPool = RequestPool::create()); - -impl RequestPool { - fn create() -> &'static RequestPool { - let pool = RequestPool( - RefCell::new(VecDeque::with_capacity(128)), - RefCell::new(ServerSettings::default()), - ); - Box::leak(Box::new(pool)) - } - - pub fn pool(settings: ServerSettings) -> &'static RequestPool { - POOL.with(|p| { - *p.1.borrow_mut() = settings; - *p - }) - } - - #[inline] - pub fn get(pool: &'static RequestPool) -> Request { - if let Some(msg) = pool.0.borrow_mut().pop_front() { - Request { inner: msg } - } else { - Request::new(pool, pool.1.borrow().clone()) - } - } - - #[inline] - /// Release request instance - pub fn release(&self, msg: Rc) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - v.push_front(msg); - } - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 64129854..00000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,370 +0,0 @@ -//! Http server module -//! -//! The module contains everything necessary to setup -//! HTTP server. -//! -//! In order to start HTTP server, first you need to create and configure it -//! using factory that can be supplied to [new](fn.new.html). -//! -//! ## Factory -//! -//! Factory is a function that returns Application, describing how -//! to serve incoming HTTP requests. -//! -//! As the server uses worker pool, the factory function is restricted to trait bounds -//! `Send + Clone + 'static` so that each worker would be able to accept Application -//! without a need for synchronization. -//! -//! If you wish to share part of state among all workers you should -//! wrap it in `Arc` and potentially synchronization primitive like -//! [RwLock](https://doc.rust-lang.org/std/sync/struct.RwLock.html) -//! If the wrapped type is not thread safe. -//! -//! Note though that locking is not advisable for asynchronous programming -//! and you should minimize all locks in your request handlers -//! -//! ## HTTPS Support -//! -//! Actix-web provides support for major crates that provides TLS. -//! Each TLS implementation is provided with [AcceptorService](trait.AcceptorService.html) -//! that describes how HTTP Server accepts connections. -//! -//! For `bind` and `listen` there are corresponding `bind_ssl|tls|rustls` and `listen_ssl|tls|rustls` that accepts -//! these services. -//! -//! **NOTE:** `native-tls` doesn't support `HTTP2` yet -//! -//! ## Signal handling and shutdown -//! -//! By default HTTP Server listens for system signals -//! and, gracefully shuts down at most after 30 seconds. -//! -//! Both signal handling and shutdown timeout can be controlled -//! using corresponding methods. -//! -//! If worker, for some reason, unable to shut down within timeout -//! it is forcibly dropped. -//! -//! ## Example -//! -//! ```rust,ignore -//!extern crate actix; -//!extern crate actix_web; -//!extern crate rustls; -//! -//!use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse, Responder}; -//!use std::io::BufReader; -//!use rustls::internal::pemfile::{certs, rsa_private_keys}; -//!use rustls::{NoClientAuth, ServerConfig}; -//! -//!fn index(req: &HttpRequest) -> Result { -//! Ok(HttpResponse::Ok().content_type("text/plain").body("Welcome!")) -//!} -//! -//!fn load_ssl() -> ServerConfig { -//! use std::io::BufReader; -//! -//! const CERT: &'static [u8] = include_bytes!("../cert.pem"); -//! const KEY: &'static [u8] = include_bytes!("../key.pem"); -//! -//! let mut cert = BufReader::new(CERT); -//! let mut key = BufReader::new(KEY); -//! -//! let mut config = ServerConfig::new(NoClientAuth::new()); -//! let cert_chain = certs(&mut cert).unwrap(); -//! let mut keys = rsa_private_keys(&mut key).unwrap(); -//! config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); -//! -//! config -//!} -//! -//!fn main() { -//! let sys = actix::System::new("http-server"); -//! // load ssl keys -//! let config = load_ssl(); -//! -//! // create and start server at once -//! server::new(|| { -//! App::new() -//! // register simple handler, handle all methods -//! .resource("/index.html", |r| r.f(index)) -//! })) -//! }).bind_rustls("127.0.0.1:8443", config) -//! .unwrap() -//! .start(); -//! -//! println!("Started http server: 127.0.0.1:8080"); -//! //Run system so that server would start accepting connections -//! let _ = sys.run(); -//!} -//! ``` -use std::net::{Shutdown, SocketAddr}; -use std::rc::Rc; -use std::{io, time}; - -use bytes::{BufMut, BytesMut}; -use futures::{Async, Poll}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_tcp::TcpStream; - -pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; - -pub(crate) mod acceptor; -pub(crate) mod builder; -mod channel; -mod error; -pub(crate) mod h1; -pub(crate) mod h1decoder; -mod h1writer; -mod h2; -mod h2writer; -mod handler; -pub(crate) mod helpers; -mod http; -pub(crate) mod incoming; -pub(crate) mod input; -pub(crate) mod message; -pub(crate) mod output; -pub(crate) mod service; -pub(crate) mod settings; -mod ssl; - -pub use self::handler::*; -pub use self::http::HttpServer; -pub use self::message::Request; -pub use self::ssl::*; - -pub use self::error::{AcceptorError, HttpDispatchError}; -pub use self::settings::ServerSettings; - -#[doc(hidden)] -pub use self::acceptor::AcceptorTimeout; - -#[doc(hidden)] -pub use self::settings::{ServiceConfig, ServiceConfigBuilder}; - -#[doc(hidden)] -pub use self::service::{H1Service, HttpService, StreamConfiguration}; - -#[doc(hidden)] -pub use self::helpers::write_content_length; - -use body::Binary; -use extensions::Extensions; -use header::ContentEncoding; -use httpresponse::HttpResponse; - -/// max buffer size 64k -pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; - -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 32_768; - -/// Create new http server with application factory. -/// -/// This is shortcut for `server::HttpServer::new()` method. -/// -/// ```rust -/// # extern crate actix_web; -/// # extern crate actix; -/// use actix_web::{server, App, HttpResponse}; -/// -/// fn main() { -/// let sys = actix::System::new("example"); // <- create Actix system -/// -/// server::new( -/// || App::new() -/// .resource("/", |r| r.f(|_| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090").unwrap() -/// .start(); -/// -/// # actix::System::current().stop(); -/// sys.run(); -/// } -/// ``` -pub fn new(factory: F) -> HttpServer -where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, -{ - HttpServer::new(factory) -} - -#[doc(hidden)] -bitflags! { - ///Flags that can be used to configure HTTP Server. - pub struct ServerFlags: u8 { - ///Use HTTP1 protocol - const HTTP1 = 0b0000_0001; - ///Use HTTP2 protocol - const HTTP2 = 0b0000_0010; - } -} - -#[derive(Debug, PartialEq, Clone, Copy)] -/// Server keep-alive setting -pub enum KeepAlive { - /// Keep alive in seconds - Timeout(usize), - /// Use `SO_KEEPALIVE` socket option, value in seconds - Tcp(usize), - /// Relay on OS to shutdown tcp connection - Os, - /// Disabled - Disabled, -} - -impl From for KeepAlive { - fn from(keepalive: usize) -> Self { - KeepAlive::Timeout(keepalive) - } -} - -impl From> for KeepAlive { - fn from(keepalive: Option) -> Self { - if let Some(keepalive) = keepalive { - KeepAlive::Timeout(keepalive) - } else { - KeepAlive::Disabled - } - } -} - -#[doc(hidden)] -#[derive(Debug)] -pub enum WriterState { - Done, - Pause, -} - -#[doc(hidden)] -/// Stream writer -pub trait Writer { - /// number of bytes written to the stream - fn written(&self) -> u64; - - #[doc(hidden)] - fn set_date(&mut self); - - #[doc(hidden)] - fn buffer(&mut self) -> &mut BytesMut; - - fn start( - &mut self, req: &Request, resp: &mut HttpResponse, encoding: ContentEncoding, - ) -> io::Result; - - fn write(&mut self, payload: &Binary) -> io::Result; - - fn write_eof(&mut self) -> io::Result; - - fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error>; -} - -#[doc(hidden)] -/// Low-level io stream operations -pub trait IoStream: AsyncRead + AsyncWrite + 'static { - fn shutdown(&mut self, how: Shutdown) -> io::Result<()>; - - /// Returns the socket address of the remote peer of this TCP connection. - fn peer_addr(&self) -> Option { - None - } - - /// Sets the value of the TCP_NODELAY option on this socket. - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>; - - fn set_linger(&mut self, dur: Option) -> io::Result<()>; - - fn set_keepalive(&mut self, dur: Option) -> io::Result<()>; - - fn read_available(&mut self, buf: &mut BytesMut) -> Poll<(bool, bool), io::Error> { - let mut read_some = false; - loop { - if buf.remaining_mut() < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE); - } - - let read = unsafe { self.read(buf.bytes_mut()) }; - match read { - Ok(n) => { - if n == 0 { - return Ok(Async::Ready((read_some, true))); - } else { - read_some = true; - unsafe { - buf.advance_mut(n); - } - } - } - Err(e) => { - return if e.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Async::Ready((read_some, false))) - } else { - Ok(Async::NotReady) - } - } else if e.kind() == io::ErrorKind::ConnectionReset && read_some { - Ok(Async::Ready((read_some, true))) - } else { - Err(e) - }; - } - } - } - } - - /// Extra io stream extensions - fn extensions(&self) -> Option> { - None - } -} - -#[cfg(all(unix, feature = "uds"))] -impl IoStream for ::tokio_uds::UnixStream { - #[inline] - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - ::tokio_uds::UnixStream::shutdown(self, how) - } - - #[inline] - fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn set_linger(&mut self, _dur: Option) -> io::Result<()> { - Ok(()) - } - - #[inline] - fn set_keepalive(&mut self, _dur: Option) -> io::Result<()> { - Ok(()) - } -} - -impl IoStream for TcpStream { - #[inline] - fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { - TcpStream::shutdown(self, how) - } - - #[inline] - fn peer_addr(&self) -> Option { - TcpStream::peer_addr(self).ok() - } - - #[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) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - TcpStream::set_keepalive(self, dur) - } -} diff --git a/src/server/output.rs b/src/server/output.rs deleted file mode 100644 index 4a86ffbb..00000000 --- a/src/server/output.rs +++ /dev/null @@ -1,760 +0,0 @@ -use std::fmt::Write as FmtWrite; -use std::io::Write; -use std::str::FromStr; -use std::{cmp, fmt, io, mem}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliEncoder; -use bytes::BytesMut; -#[cfg(feature = "flate2")] -use flate2::write::{GzEncoder, ZlibEncoder}; -#[cfg(feature = "flate2")] -use flate2::Compression; -use http::header::{ACCEPT_ENCODING, CONTENT_LENGTH}; -use http::{StatusCode, Version}; - -use super::message::InnerRequest; -use body::{Binary, Body}; -use header::ContentEncoding; -use httpresponse::HttpResponse; - -#[derive(Debug)] -pub(crate) enum ResponseLength { - Chunked, - Zero, - Length(usize), - Length64(u64), - None, -} - -#[derive(Debug)] -pub(crate) struct ResponseInfo { - head: bool, - pub length: ResponseLength, - pub content_encoding: Option<&'static str>, -} - -impl ResponseInfo { - pub fn new(head: bool) -> Self { - ResponseInfo { - head, - length: ResponseLength::None, - content_encoding: None, - } - } -} - -#[derive(Debug)] -pub(crate) enum Output { - Empty(BytesMut), - Buffer(BytesMut), - Encoder(ContentEncoder), - TE(TransferEncoding), - Done, -} - -impl Output { - pub fn take(&mut self) -> BytesMut { - match mem::replace(self, Output::Done) { - Output::Empty(bytes) => bytes, - Output::Buffer(bytes) => bytes, - Output::Encoder(mut enc) => enc.take_buf(), - Output::TE(mut te) => te.take(), - Output::Done => panic!(), - } - } - - pub fn take_option(&mut self) -> Option { - match mem::replace(self, Output::Done) { - Output::Empty(bytes) => Some(bytes), - Output::Buffer(bytes) => Some(bytes), - Output::Encoder(mut enc) => Some(enc.take_buf()), - Output::TE(mut te) => Some(te.take()), - Output::Done => None, - } - } - - pub fn as_ref(&mut self) -> &BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes, - Output::Buffer(ref mut bytes) => bytes, - Output::Encoder(ref mut enc) => enc.buf_ref(), - Output::TE(ref mut te) => te.buf_ref(), - Output::Done => panic!(), - } - } - pub fn as_mut(&mut self) -> &mut BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes, - Output::Buffer(ref mut bytes) => bytes, - Output::Encoder(ref mut enc) => enc.buf_mut(), - Output::TE(ref mut te) => te.buf_mut(), - Output::Done => panic!(), - } - } - pub fn split_to(&mut self, cap: usize) -> BytesMut { - match self { - Output::Empty(ref mut bytes) => bytes.split_to(cap), - Output::Buffer(ref mut bytes) => bytes.split_to(cap), - Output::Encoder(ref mut enc) => enc.buf_mut().split_to(cap), - Output::TE(ref mut te) => te.buf_mut().split_to(cap), - Output::Done => BytesMut::new(), - } - } - - pub fn len(&self) -> usize { - match self { - Output::Empty(ref bytes) => bytes.len(), - Output::Buffer(ref bytes) => bytes.len(), - Output::Encoder(ref enc) => enc.len(), - Output::TE(ref te) => te.len(), - Output::Done => 0, - } - } - - pub fn is_empty(&self) -> bool { - match self { - Output::Empty(ref bytes) => bytes.is_empty(), - Output::Buffer(ref bytes) => bytes.is_empty(), - Output::Encoder(ref enc) => enc.is_empty(), - Output::TE(ref te) => te.is_empty(), - Output::Done => true, - } - } - - pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { - match self { - Output::Buffer(ref mut bytes) => { - bytes.extend_from_slice(data); - Ok(()) - } - Output::Encoder(ref mut enc) => enc.write(data), - Output::TE(ref mut te) => te.encode(data).map(|_| ()), - Output::Empty(_) | Output::Done => Ok(()), - } - } - - pub fn write_eof(&mut self) -> Result { - match self { - Output::Buffer(_) => Ok(true), - Output::Encoder(ref mut enc) => enc.write_eof(), - Output::TE(ref mut te) => Ok(te.encode_eof()), - Output::Empty(_) | Output::Done => Ok(true), - } - } - - pub(crate) fn for_server( - &mut self, info: &mut ResponseInfo, req: &InnerRequest, resp: &mut HttpResponse, - response_encoding: ContentEncoding, - ) { - let buf = self.take(); - let version = resp.version().unwrap_or_else(|| req.version); - let mut len = 0; - - let has_body = match resp.body() { - Body::Empty => false, - Body::Binary(ref bin) => { - len = bin.len(); - !(response_encoding == ContentEncoding::Auto && len < 96) - } - _ => true, - }; - - // Enable content encoding only if response does not contain Content-Encoding - // header - #[cfg(any(feature = "brotli", feature = "flate2"))] - let mut encoding = if has_body { - let encoding = match response_encoding { - ContentEncoding::Auto => { - // negotiate content-encoding - if let Some(val) = req.headers.get(ACCEPT_ENCODING) { - if let Ok(enc) = val.to_str() { - AcceptEncoding::parse(enc) - } else { - ContentEncoding::Identity - } - } else { - ContentEncoding::Identity - } - } - encoding => encoding, - }; - if encoding.is_compression() { - info.content_encoding = Some(encoding.as_str()); - } - encoding - } else { - ContentEncoding::Identity - }; - #[cfg(not(any(feature = "brotli", feature = "flate2")))] - let mut encoding = ContentEncoding::Identity; - - let transfer = match resp.body() { - Body::Empty => { - info.length = match resp.status() { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::SWITCHING_PROTOCOLS - | StatusCode::PROCESSING => ResponseLength::None, - _ => ResponseLength::Zero, - }; - *self = Output::Empty(buf); - return; - } - Body::Binary(_) => { - #[cfg(any(feature = "brotli", feature = "flate2"))] - { - if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) - { - let mut tmp = BytesMut::new(); - let mut transfer = TransferEncoding::eof(tmp); - let mut enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => ContentEncoder::Deflate( - ZlibEncoder::new(transfer, Compression::fast()), - ), - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => ContentEncoder::Gzip( - GzEncoder::new(transfer, Compression::fast()), - ), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) - } - ContentEncoding::Identity | ContentEncoding::Auto => { - unreachable!() - } - }; - - let bin = resp.replace_body(Body::Empty).binary(); - - // TODO return error! - let _ = enc.write(bin.as_ref()); - let _ = enc.write_eof(); - let body = enc.buf_mut().take(); - len = body.len(); - resp.replace_body(Binary::from(body)); - } - } - - info.length = ResponseLength::Length(len); - if info.head { - *self = Output::Empty(buf); - } else { - *self = Output::Buffer(buf); - } - return; - } - Body::Streaming(_) | Body::Actor(_) => { - if resp.upgrade() { - if version == Version::HTTP_2 { - error!("Connection upgrade is forbidden for HTTP/2"); - } - if encoding != ContentEncoding::Identity { - encoding = ContentEncoding::Identity; - info.content_encoding.take(); - } - TransferEncoding::eof(buf) - } else { - if !(encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) - { - resp.headers_mut().remove(CONTENT_LENGTH); - } - Output::streaming_encoding(info, buf, version, resp) - } - } - }; - // check for head response - if info.head { - resp.set_body(Body::Empty); - *self = Output::Empty(transfer.buf.unwrap()); - return; - } - - let enc = match encoding { - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - ContentEncoder::Deflate(ZlibEncoder::new(transfer, Compression::fast())) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast())) - } - #[cfg(feature = "brotli")] - ContentEncoding::Br => ContentEncoder::Br(BrotliEncoder::new(transfer, 3)), - ContentEncoding::Identity | ContentEncoding::Auto => { - *self = Output::TE(transfer); - return; - } - }; - *self = Output::Encoder(enc); - } - - fn streaming_encoding( - info: &mut ResponseInfo, buf: BytesMut, version: Version, - resp: &mut HttpResponse, - ) -> TransferEncoding { - match resp.chunked() { - Some(true) => { - // Enable transfer encoding - info.length = ResponseLength::Chunked; - if version == Version::HTTP_2 { - TransferEncoding::eof(buf) - } else { - TransferEncoding::chunked(buf) - } - } - Some(false) => TransferEncoding::eof(buf), - None => { - // if Content-Length is specified, then use it as length hint - let (len, chunked) = - if let Some(len) = resp.headers().get(CONTENT_LENGTH) { - // Content-Length - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - (Some(len), false) - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - error!("illegal Content-Length: {:?}", len); - (None, false) - } - } else { - (None, true) - }; - - if !chunked { - if let Some(len) = len { - info.length = ResponseLength::Length64(len); - TransferEncoding::length(len, buf) - } else { - TransferEncoding::eof(buf) - } - } else { - // Enable transfer encoding - info.length = ResponseLength::Chunked; - if version == Version::HTTP_2 { - TransferEncoding::eof(buf) - } else { - TransferEncoding::chunked(buf) - } - } - } - } - } -} - -pub(crate) enum ContentEncoder { - #[cfg(feature = "flate2")] - Deflate(ZlibEncoder), - #[cfg(feature = "flate2")] - Gzip(GzEncoder), - #[cfg(feature = "brotli")] - Br(BrotliEncoder), - Identity(TransferEncoding), -} - -impl fmt::Debug for ContentEncoder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), - ContentEncoder::Identity(_) => writeln!(f, "ContentEncoder(Identity)"), - } - } -} - -impl ContentEncoder { - #[inline] - pub fn len(&self) -> usize { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref encoder) => encoder.get_ref().len(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref encoder) => encoder.get_ref().len(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref encoder) => encoder.get_ref().len(), - ContentEncoder::Identity(ref encoder) => encoder.len(), - } - } - - #[inline] - pub fn is_empty(&self) -> bool { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref encoder) => encoder.get_ref().is_empty(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_empty(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_empty(), - ContentEncoder::Identity(ref encoder) => encoder.is_empty(), - } - } - - #[inline] - pub(crate) fn take_buf(&mut self) -> BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), - ContentEncoder::Identity(ref mut encoder) => encoder.take(), - } - } - - #[inline] - pub(crate) fn buf_mut(&mut self) -> &mut BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_mut(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_mut(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_mut(), - ContentEncoder::Identity(ref mut encoder) => encoder.buf_mut(), - } - } - - #[inline] - pub(crate) fn buf_ref(&mut self) -> &BytesMut { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().buf_ref(), - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().buf_ref(), - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().buf_ref(), - ContentEncoder::Identity(ref mut encoder) => encoder.buf_ref(), - } - } - - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - #[inline(always)] - pub fn write_eof(&mut self) -> Result { - let encoder = - mem::replace(self, ContentEncoder::Identity(TransferEncoding::empty())); - - match encoder { - #[cfg(feature = "brotli")] - ContentEncoder::Br(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(encoder) => match encoder.finish() { - Ok(mut writer) => { - writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(true) - } - Err(err) => Err(err), - }, - ContentEncoder::Identity(mut writer) => { - let res = writer.encode_eof(); - *self = ContentEncoder::Identity(writer); - Ok(res) - } - } - } - - #[cfg_attr(feature = "cargo-clippy", allow(inline_always))] - #[inline(always)] - pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding br encoding: {}", err); - Err(err) - } - }, - #[cfg(feature = "flate2")] - ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding gzip encoding: {}", err); - Err(err) - } - }, - #[cfg(feature = "flate2")] - ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding deflate encoding: {}", err); - Err(err) - } - }, - ContentEncoder::Identity(ref mut encoder) => { - encoder.encode(data)?; - Ok(()) - } - } - } -} - -/// Encoders to handle different Transfer-Encodings. -#[derive(Debug)] -pub(crate) struct TransferEncoding { - buf: Option, - kind: TransferEncodingKind, -} - -#[derive(Debug, PartialEq, Clone)] -enum TransferEncodingKind { - /// An Encoder for when Transfer-Encoding includes `chunked`. - Chunked(bool), - /// An Encoder for when Content-Length is set. - /// - /// Enforces that the body is not longer than the Content-Length header. - Length(u64), - /// An Encoder for when Content-Length is not known. - /// - /// Application decides when to stop writing. - Eof, -} - -impl TransferEncoding { - fn take(&mut self) -> BytesMut { - self.buf.take().unwrap() - } - - fn buf_ref(&mut self) -> &BytesMut { - self.buf.as_ref().unwrap() - } - - fn len(&self) -> usize { - self.buf.as_ref().unwrap().len() - } - - fn is_empty(&self) -> bool { - self.buf.as_ref().unwrap().is_empty() - } - - fn buf_mut(&mut self) -> &mut BytesMut { - self.buf.as_mut().unwrap() - } - - #[inline] - pub fn empty() -> TransferEncoding { - TransferEncoding { - buf: None, - kind: TransferEncodingKind::Eof, - } - } - - #[inline] - pub fn eof(buf: BytesMut) -> TransferEncoding { - TransferEncoding { - buf: Some(buf), - kind: TransferEncodingKind::Eof, - } - } - - #[inline] - pub fn chunked(buf: BytesMut) -> TransferEncoding { - TransferEncoding { - buf: Some(buf), - kind: TransferEncodingKind::Chunked(false), - } - } - - #[inline] - pub fn length(len: u64, buf: BytesMut) -> TransferEncoding { - TransferEncoding { - buf: Some(buf), - kind: TransferEncodingKind::Length(len), - } - } - - /// Encode message. Return `EOF` state of encoder - #[inline] - pub fn encode(&mut self, msg: &[u8]) -> io::Result { - match self.kind { - TransferEncodingKind::Eof => { - let eof = msg.is_empty(); - self.buf.as_mut().unwrap().extend_from_slice(msg); - Ok(eof) - } - TransferEncodingKind::Chunked(ref mut eof) => { - if *eof { - return Ok(true); - } - - if msg.is_empty() { - *eof = true; - self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n"); - } else { - let mut buf = BytesMut::new(); - writeln!(&mut buf, "{:X}\r", msg.len()) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - let b = self.buf.as_mut().unwrap(); - b.reserve(buf.len() + msg.len() + 2); - b.extend_from_slice(buf.as_ref()); - b.extend_from_slice(msg); - b.extend_from_slice(b"\r\n"); - } - Ok(*eof) - } - TransferEncodingKind::Length(ref mut remaining) => { - if *remaining > 0 { - if msg.is_empty() { - return Ok(*remaining == 0); - } - let len = cmp::min(*remaining, msg.len() as u64); - - self.buf - .as_mut() - .unwrap() - .extend_from_slice(&msg[..len as usize]); - - *remaining -= len as u64; - Ok(*remaining == 0) - } else { - Ok(true) - } - } - } - } - - /// Encode eof. Return `EOF` state of encoder - #[inline] - pub fn encode_eof(&mut self) -> bool { - match self.kind { - TransferEncodingKind::Eof => true, - TransferEncodingKind::Length(rem) => rem == 0, - TransferEncodingKind::Chunked(ref mut eof) => { - if !*eof { - *eof = true; - self.buf.as_mut().unwrap().extend_from_slice(b"0\r\n\r\n"); - } - true - } - } - } -} - -impl io::Write for TransferEncoding { - #[inline] - fn write(&mut self, buf: &[u8]) -> io::Result { - if self.buf.is_some() { - self.encode(buf)?; - } - Ok(buf.len()) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -struct AcceptEncoding { - encoding: ContentEncoding, - quality: f64, -} - -impl Eq for AcceptEncoding {} - -impl Ord for AcceptEncoding { - fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering { - if self.quality > other.quality { - cmp::Ordering::Less - } else if self.quality < other.quality { - cmp::Ordering::Greater - } else { - cmp::Ordering::Equal - } - } -} - -impl PartialOrd for AcceptEncoding { - fn partial_cmp(&self, other: &AcceptEncoding) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for AcceptEncoding { - fn eq(&self, other: &AcceptEncoding) -> bool { - self.quality == other.quality - } -} - -impl AcceptEncoding { - fn new(tag: &str) -> Option { - let parts: Vec<&str> = tag.split(';').collect(); - let encoding = match parts.len() { - 0 => return None, - _ => ContentEncoding::from(parts[0]), - }; - let quality = match parts.len() { - 1 => encoding.quality(), - _ => match f64::from_str(parts[1]) { - Ok(q) => q, - Err(_) => 0.0, - }, - }; - Some(AcceptEncoding { encoding, quality }) - } - - /// Parse a raw Accept-Encoding header value into an ordered list. - pub fn parse(raw: &str) -> ContentEncoding { - let mut encodings: Vec<_> = raw - .replace(' ', "") - .split(',') - .map(|l| AcceptEncoding::new(l)) - .collect(); - encodings.sort(); - - for enc in encodings { - if let Some(enc) = enc { - return enc.encoding; - } - } - ContentEncoding::Identity - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - - #[test] - fn test_chunked_te() { - let bytes = BytesMut::new(); - let mut enc = TransferEncoding::chunked(bytes); - { - assert!(!enc.encode(b"test").ok().unwrap()); - assert!(enc.encode(b"").ok().unwrap()); - } - assert_eq!( - enc.buf_mut().take().freeze(), - Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") - ); - } -} diff --git a/src/server/service.rs b/src/server/service.rs deleted file mode 100644 index e3402e30..00000000 --- a/src/server/service.rs +++ /dev/null @@ -1,272 +0,0 @@ -use std::marker::PhantomData; -use std::time::Duration; - -use actix_net::service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Poll}; - -use super::channel::{H1Channel, HttpChannel}; -use super::error::HttpDispatchError; -use super::handler::HttpHandler; -use super::settings::ServiceConfig; -use super::IoStream; - -/// `NewService` implementation for HTTP1/HTTP2 transports -pub struct HttpService -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl HttpService -where - H: HttpHandler, - Io: IoStream, -{ - /// Create new `HttpService` instance. - pub fn new(settings: ServiceConfig) -> Self { - HttpService { - settings, - _t: PhantomData, - } - } -} - -impl NewService for HttpService -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type InitError = (); - type Service = HttpServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(HttpServiceHandler::new(self.settings.clone())) - } -} - -pub struct HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new(settings: ServiceConfig) -> HttpServiceHandler { - HttpServiceHandler { - settings, - _t: PhantomData, - } - } -} - -impl Service for HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type Future = HttpChannel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - HttpChannel::new(self.settings.clone(), req) - } -} - -/// `NewService` implementation for HTTP1 transport -pub struct H1Service -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl H1Service -where - H: HttpHandler, - Io: IoStream, -{ - /// Create new `HttpService` instance. - pub fn new(settings: ServiceConfig) -> Self { - H1Service { - settings, - _t: PhantomData, - } - } -} - -impl NewService for H1Service -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type InitError = (); - type Service = H1ServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(H1ServiceHandler::new(self.settings.clone())) - } -} - -/// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new(settings: ServiceConfig) -> H1ServiceHandler { - H1ServiceHandler { - settings, - _t: PhantomData, - } - } -} - -impl Service for H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type Future = H1Channel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - H1Channel::new(self.settings.clone(), req) - } -} - -/// `NewService` implementation for stream configuration service -/// -/// Stream configuration service allows to change some socket level -/// parameters. for example `tcp nodelay` or `tcp keep-alive`. -pub struct StreamConfiguration { - no_delay: Option, - tcp_ka: Option>, - _t: PhantomData<(T, E)>, -} - -impl Default for StreamConfiguration { - fn default() -> Self { - Self::new() - } -} - -impl StreamConfiguration { - /// Create new `StreamConfigurationService` instance. - pub fn new() -> Self { - Self { - no_delay: None, - tcp_ka: None, - _t: PhantomData, - } - } - - /// Sets the value of the `TCP_NODELAY` option on this socket. - pub fn nodelay(mut self, nodelay: bool) -> Self { - self.no_delay = Some(nodelay); - self - } - - /// Sets whether keepalive messages are enabled to be sent on this socket. - pub fn tcp_keepalive(mut self, keepalive: Option) -> Self { - self.tcp_ka = Some(keepalive); - self - } -} - -impl NewService for StreamConfiguration { - type Request = T; - type Response = T; - type Error = E; - type InitError = (); - type Service = StreamConfigurationService; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(StreamConfigurationService { - no_delay: self.no_delay, - tcp_ka: self.tcp_ka, - _t: PhantomData, - }) - } -} - -/// Stream configuration service -/// -/// Stream configuration service allows to change some socket level -/// parameters. for example `tcp nodelay` or `tcp keep-alive`. -pub struct StreamConfigurationService { - no_delay: Option, - tcp_ka: Option>, - _t: PhantomData<(T, E)>, -} - -impl Service for StreamConfigurationService -where - T: IoStream, -{ - type Request = T; - type Response = T; - type Error = E; - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, mut req: Self::Request) -> Self::Future { - if let Some(no_delay) = self.no_delay { - if req.set_nodelay(no_delay).is_err() { - error!("Can not set socket no-delay option"); - } - } - if let Some(keepalive) = self.tcp_ka { - if req.set_keepalive(keepalive).is_err() { - error!("Can not set socket keep-alive option"); - } - } - - ok(req) - } -} diff --git a/src/server/settings.rs b/src/server/settings.rs deleted file mode 100644 index 66a4eed8..00000000 --- a/src/server/settings.rs +++ /dev/null @@ -1,503 +0,0 @@ -use std::cell::{Cell, RefCell}; -use std::collections::VecDeque; -use std::fmt::Write; -use std::rc::Rc; -use std::time::{Duration, Instant}; -use std::{env, fmt, net}; - -use bytes::BytesMut; -use futures::{future, Future}; -use futures_cpupool::CpuPool; -use http::StatusCode; -use lazycell::LazyCell; -use parking_lot::Mutex; -use time; -use tokio_current_thread::spawn; -use tokio_timer::{sleep, Delay}; - -use super::message::{Request, RequestPool}; -use super::KeepAlive; -use body::Body; -use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool}; - -/// Env variable for default cpu pool size -const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL"; - -lazy_static! { - pub(crate) static ref DEFAULT_CPUPOOL: Mutex = { - let default = match env::var(ENV_CPU_POOL_VAR) { - Ok(val) => { - if let Ok(val) = val.parse() { - val - } else { - error!("Can not parse ACTIX_CPU_POOL value"); - 20 - } - } - Err(_) => 20, - }; - Mutex::new(CpuPool::new(default)) - }; -} - -/// Various server settings -pub struct ServerSettings { - addr: net::SocketAddr, - secure: bool, - host: String, - cpu_pool: LazyCell, - responses: &'static HttpResponsePool, -} - -impl Clone for ServerSettings { - fn clone(&self) -> Self { - ServerSettings { - addr: self.addr, - secure: self.secure, - host: self.host.clone(), - cpu_pool: LazyCell::new(), - responses: HttpResponsePool::get_pool(), - } - } -} - -impl Default for ServerSettings { - fn default() -> Self { - ServerSettings { - addr: "127.0.0.1:8080".parse().unwrap(), - secure: false, - host: "localhost:8080".to_owned(), - responses: HttpResponsePool::get_pool(), - cpu_pool: LazyCell::new(), - } - } -} - -impl ServerSettings { - /// Crate server settings instance - pub(crate) fn new( - addr: net::SocketAddr, host: &str, secure: bool, - ) -> ServerSettings { - let host = host.to_owned(); - let cpu_pool = LazyCell::new(); - let responses = HttpResponsePool::get_pool(); - ServerSettings { - addr, - secure, - host, - cpu_pool, - responses, - } - } - - /// Returns the socket address of the local half of this TCP connection - pub fn local_addr(&self) -> net::SocketAddr { - self.addr - } - - /// Returns true if connection is secure(https) - pub fn secure(&self) -> bool { - self.secure - } - - /// Returns host header value - pub fn host(&self) -> &str { - &self.host - } - - /// Returns default `CpuPool` for server - pub fn cpu_pool(&self) -> &CpuPool { - self.cpu_pool.borrow_with(|| DEFAULT_CPUPOOL.lock().clone()) - } - - #[inline] - pub(crate) fn get_response(&self, status: StatusCode, body: Body) -> HttpResponse { - HttpResponsePool::get_response(&self.responses, status, body) - } - - #[inline] - pub(crate) fn get_response_builder( - &self, status: StatusCode, - ) -> HttpResponseBuilder { - HttpResponsePool::get_builder(&self.responses, status) - } -} - -// "Sun, 06 Nov 1994 08:49:37 GMT".len() -const DATE_VALUE_LENGTH: usize = 29; - -/// Http service configuration -pub struct ServiceConfig(Rc>); - -struct Inner { - handler: H, - keep_alive: Option, - client_timeout: u64, - client_shutdown: u64, - ka_enabled: bool, - bytes: Rc, - messages: &'static RequestPool, - date: Cell>, -} - -impl Clone for ServiceConfig { - fn clone(&self) -> Self { - ServiceConfig(self.0.clone()) - } -} - -impl ServiceConfig { - /// Create instance of `ServiceConfig` - pub(crate) fn new( - handler: H, keep_alive: KeepAlive, client_timeout: u64, client_shutdown: u64, - settings: ServerSettings, - ) -> ServiceConfig { - let (keep_alive, ka_enabled) = match keep_alive { - KeepAlive::Timeout(val) => (val as u64, true), - KeepAlive::Os | KeepAlive::Tcp(_) => (0, true), - KeepAlive::Disabled => (0, false), - }; - let keep_alive = if ka_enabled && keep_alive > 0 { - Some(Duration::from_secs(keep_alive)) - } else { - None - }; - - ServiceConfig(Rc::new(Inner { - handler, - keep_alive, - ka_enabled, - client_timeout, - client_shutdown, - bytes: Rc::new(SharedBytesPool::new()), - messages: RequestPool::pool(settings), - date: Cell::new(None), - })) - } - - /// Create worker settings builder. - pub fn build(handler: H) -> ServiceConfigBuilder { - ServiceConfigBuilder::new(handler) - } - - pub(crate) fn handler(&self) -> &H { - &self.0.handler - } - - #[inline] - /// Keep alive duration if configured. - pub fn keep_alive(&self) -> Option { - self.0.keep_alive - } - - #[inline] - /// Return state of connection keep-alive funcitonality - pub fn keep_alive_enabled(&self) -> bool { - self.0.ka_enabled - } - - pub(crate) fn get_bytes(&self) -> BytesMut { - self.0.bytes.get_bytes() - } - - pub(crate) fn release_bytes(&self, bytes: BytesMut) { - self.0.bytes.release_bytes(bytes) - } - - pub(crate) fn get_request(&self) -> Request { - RequestPool::get(self.0.messages) - } -} - -impl ServiceConfig { - #[inline] - /// Client timeout for first request. - pub fn client_timer(&self) -> Option { - let delay = self.0.client_timeout; - if delay != 0 { - Some(Delay::new(self.now() + Duration::from_millis(delay))) - } else { - None - } - } - - /// Client timeout for first request. - pub fn client_timer_expire(&self) -> Option { - let delay = self.0.client_timeout; - if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) - } else { - None - } - } - - /// Client shutdown timer - pub fn client_shutdown_timer(&self) -> Option { - let delay = self.0.client_shutdown; - if delay != 0 { - Some(self.now() + Duration::from_millis(delay)) - } else { - None - } - } - - #[inline] - /// Return keep-alive timer delay is configured. - pub fn keep_alive_timer(&self) -> Option { - if let Some(ka) = self.0.keep_alive { - Some(Delay::new(self.now() + ka)) - } else { - None - } - } - - /// Keep-alive expire time - pub fn keep_alive_expire(&self) -> Option { - if let Some(ka) = self.0.keep_alive { - Some(self.now() + ka) - } else { - None - } - } - - fn check_date(&self) { - if unsafe { &*self.0.date.as_ptr() }.is_none() { - self.0.date.set(Some(Date::new())); - - // periodic date update - let s = self.clone(); - spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.0.date.set(None); - future::ok(()) - })); - } - } - - pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) { - self.check_date(); - - let date = &unsafe { &*self.0.date.as_ptr() }.as_ref().unwrap().bytes; - - if full { - let mut buf: [u8; 39] = [0; 39]; - buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(date); - buf[35..].copy_from_slice(b"\r\n\r\n"); - dst.extend_from_slice(&buf); - } else { - dst.extend_from_slice(date); - } - } - - #[inline] - pub(crate) fn now(&self) -> Instant { - self.check_date(); - unsafe { &*self.0.date.as_ptr() }.as_ref().unwrap().current - } -} - -/// A service config builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct ServiceConfigBuilder { - handler: H, - keep_alive: KeepAlive, - client_timeout: u64, - client_shutdown: u64, - host: String, - addr: net::SocketAddr, - secure: bool, -} - -impl ServiceConfigBuilder { - /// Create instance of `ServiceConfigBuilder` - pub fn new(handler: H) -> ServiceConfigBuilder { - ServiceConfigBuilder { - handler, - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_shutdown: 5000, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - } - } - - /// Enable secure flag for current server. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: T) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection shutdown timeout in milliseconds. - /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. This timeout affects only secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(mut self, val: u64) -> Self { - self.client_shutdown = val; - 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. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// 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. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: S) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => if let Some(addr) = addrs.next() { - self.addr = addr; - }, - } - self - } - - /// Finish service configuration and create `ServiceConfig` object. - pub fn finish(self) -> ServiceConfig { - let settings = ServerSettings::new(self.addr, &self.host, self.secure); - let client_shutdown = if self.secure { self.client_shutdown } else { 0 }; - - ServiceConfig::new( - self.handler, - self.keep_alive, - self.client_timeout, - client_shutdown, - settings, - ) - } -} - -#[derive(Copy, Clone)] -struct Date { - current: Instant, - bytes: [u8; DATE_VALUE_LENGTH], - pos: usize, -} - -impl Date { - fn new() -> Date { - let mut date = Date { - current: Instant::now(), - bytes: [0; DATE_VALUE_LENGTH], - pos: 0, - }; - date.update(); - date - } - fn update(&mut self) { - self.pos = 0; - self.current = Instant::now(); - write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); - } -} - -impl fmt::Write for Date { - 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(()) - } -} - -#[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) -> BytesMut { - if let Some(bytes) = self.0.borrow_mut().pop_front() { - bytes - } else { - BytesMut::new() - } - } - - pub fn release_bytes(&self, mut bytes: BytesMut) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - bytes.clear(); - v.push_front(bytes); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::future; - use tokio::runtime::current_thread; - - #[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 rt = current_thread::Runtime::new().unwrap(); - - let _ = rt.block_on(future::lazy(|| { - let settings = ServiceConfig::<()>::new( - (), - KeepAlive::Os, - 0, - 0, - ServerSettings::default(), - ); - let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, true); - let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, true); - assert_eq!(buf1, buf2); - future::ok::<_, ()>(()) - })); - } -} diff --git a/src/server/ssl/mod.rs b/src/server/ssl/mod.rs deleted file mode 100644 index c09573fe..00000000 --- a/src/server/ssl/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[cfg(any(feature = "alpn", feature = "ssl"))] -mod openssl; -#[cfg(any(feature = "alpn", feature = "ssl"))] -pub use self::openssl::{openssl_acceptor_with_flags, OpensslAcceptor}; - -#[cfg(feature = "tls")] -mod nativetls; - -#[cfg(feature = "rust-tls")] -mod rustls; -#[cfg(feature = "rust-tls")] -pub use self::rustls::RustlsAcceptor; diff --git a/src/server/ssl/nativetls.rs b/src/server/ssl/nativetls.rs deleted file mode 100644 index a9797ffb..00000000 --- a/src/server/ssl/nativetls.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl::TlsStream; - -use server::IoStream; - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().get_ref().peer_addr() - } - - #[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) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/server/ssl/openssl.rs b/src/server/ssl/openssl.rs deleted file mode 100644 index 9d370f8b..00000000 --- a/src/server/ssl/openssl.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl; -use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_openssl::SslStream; - -use server::{IoStream, ServerFlags}; - -/// Support `SSL` connections via openssl package -/// -/// `ssl` feature enables `OpensslAcceptor` type -pub struct OpensslAcceptor { - _t: ssl::OpensslAcceptor, -} - -impl OpensslAcceptor { - /// Create `OpensslAcceptor` with enabled `HTTP/2` and `HTTP1.1` support. - pub fn new(builder: SslAcceptorBuilder) -> io::Result> { - OpensslAcceptor::with_flags(builder, ServerFlags::HTTP1 | ServerFlags::HTTP2) - } - - /// Create `OpensslAcceptor` with custom server flags. - pub fn with_flags( - builder: SslAcceptorBuilder, flags: ServerFlags, - ) -> io::Result> { - let acceptor = openssl_acceptor_with_flags(builder, flags)?; - - Ok(ssl::OpensslAcceptor::new(acceptor)) - } -} - -/// Configure `SslAcceptorBuilder` with custom server flags. -pub fn openssl_acceptor_with_flags( - mut builder: SslAcceptorBuilder, flags: ServerFlags, -) -> io::Result { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP1) { - protos.extend(b"\x08http/1.1"); - } - if flags.contains(ServerFlags::HTTP2) { - protos.extend(b"\x02h2"); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - } - - if !protos.is_empty() { - builder.set_alpn_protos(&protos)?; - } - - Ok(builder.build()) -} - -impl IoStream for SslStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = self.get_mut().shutdown(); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().get_ref().peer_addr() - } - - #[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) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().get_mut().set_keepalive(dur) - } -} diff --git a/src/server/ssl/rustls.rs b/src/server/ssl/rustls.rs deleted file mode 100644 index a53a53a9..00000000 --- a/src/server/ssl/rustls.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::net::{Shutdown, SocketAddr}; -use std::{io, time}; - -use actix_net::ssl; //::RustlsAcceptor; -use rustls::{ClientSession, ServerConfig, ServerSession}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_rustls::TlsStream; - -use server::{IoStream, ServerFlags}; - -/// Support `SSL` connections via rustls package -/// -/// `rust-tls` feature enables `RustlsAcceptor` type -pub struct RustlsAcceptor { - _t: ssl::RustlsAcceptor, -} - -impl RustlsAcceptor { - /// Create `RustlsAcceptor` with custom server flags. - pub fn with_flags( - mut config: ServerConfig, flags: ServerFlags, - ) -> ssl::RustlsAcceptor { - let mut protos = Vec::new(); - if flags.contains(ServerFlags::HTTP2) { - protos.push("h2".to_string()); - } - if flags.contains(ServerFlags::HTTP1) { - protos.push("http/1.1".to_string()); - } - if !protos.is_empty() { - config.set_protocols(&protos); - } - - ssl::RustlsAcceptor::new(config) - } -} - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_keepalive(dur) - } -} - -impl IoStream for TlsStream { - #[inline] - fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { - let _ = ::shutdown(self); - Ok(()) - } - - #[inline] - fn peer_addr(&self) -> Option { - self.get_ref().0.peer_addr() - } - - #[inline] - fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> { - self.get_mut().0.set_nodelay(nodelay) - } - - #[inline] - fn set_linger(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_linger(dur) - } - - #[inline] - fn set_keepalive(&mut self, dur: Option) -> io::Result<()> { - self.get_mut().0.set_keepalive(dur) - } -} diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 00000000..775bb611 --- /dev/null +++ b/src/service.rs @@ -0,0 +1,155 @@ +use std::rc::Rc; + +use actix_http::body::{Body, MessageBody, ResponseBody}; +use actix_http::http::HeaderMap; +use actix_http::{ + Error, Extensions, HttpMessage, Payload, Request, Response, ResponseHead, +}; +use actix_router::{Path, Url}; + +use crate::request::HttpRequest; + +pub struct ServiceRequest

    { + req: HttpRequest, + payload: Payload

    , +} + +impl

    ServiceRequest

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

    , + extensions: Rc, + ) -> Self { + let (head, payload) = request.into_parts(); + ServiceRequest { + payload, + req: HttpRequest::new(head, path, extensions), + } + } + + #[inline] + pub fn request(&self) -> &HttpRequest { + &self.req + } + + #[inline] + pub fn into_request(self) -> HttpRequest { + self.req + } + + /// Create service response + #[inline] + pub fn into_response(self, res: Response) -> ServiceResponse { + ServiceResponse::new(self.req, res) + } + + /// Create service response for error + #[inline] + pub fn error_response>(self, err: E) -> ServiceResponse { + ServiceResponse::new(self.req, err.into().into()) + } + + #[inline] + pub fn match_info_mut(&mut self) -> &mut Path { + &mut self.req.path + } +} + +impl

    HttpMessage for ServiceRequest

    { + type Stream = P; + + #[inline] + fn headers(&self) -> &HeaderMap { + self.req.headers() + } + + #[inline] + fn take_payload(&mut self) -> Payload { + std::mem::replace(&mut self.payload, Payload::None) + } +} + +impl

    std::ops::Deref for ServiceRequest

    { + type Target = HttpRequest; + + fn deref(&self) -> &HttpRequest { + self.request() + } +} + +pub struct ServiceResponse { + request: HttpRequest, + response: Response, +} + +impl ServiceResponse { + /// Create service response instance + pub fn new(request: HttpRequest, response: Response) -> Self { + ServiceResponse { request, response } + } + + /// Get reference to original request + #[inline] + pub fn request(&self) -> &HttpRequest { + &self.request + } + + /// Get reference to response + #[inline] + pub fn response(&self) -> &Response { + &self.response + } + + /// Get mutable reference to response + #[inline] + pub fn response_mut(&mut self) -> &mut Response { + &mut self.response + } + + /// Get the headers from the response + #[inline] + pub fn headers(&self) -> &HeaderMap { + self.response.headers() + } + + /// Get a mutable reference to the headers + #[inline] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + self.response.headers_mut() + } +} + +impl ServiceResponse { + /// Set a new body + pub fn map_body(self, f: F) -> ServiceResponse + where + F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, + { + let response = self.response.map_body(f); + + ServiceResponse { + response, + request: self.request, + } + } +} + +impl std::ops::Deref for ServiceResponse { + type Target = Response; + + fn deref(&self) -> &Response { + self.response() + } +} + +impl std::ops::DerefMut for ServiceResponse { + fn deref_mut(&mut self) -> &mut Response { + self.response_mut() + } +} + +impl Into> for ServiceResponse { + fn into(self) -> Response { + self.response + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 00000000..82aecc6e --- /dev/null +++ b/src/state.rs @@ -0,0 +1,120 @@ +use std::ops::Deref; +use std::rc::Rc; + +use actix_http::error::{Error, ErrorInternalServerError}; +use actix_http::Extensions; +use futures::future::{err, ok, FutureResult}; +use futures::{Async, Future, IntoFuture, Poll}; + +use crate::handler::FromRequest; +use crate::service::ServiceRequest; + +/// Application state factory +pub(crate) trait StateFactory { + fn construct(&self) -> Box; +} + +pub(crate) trait StateFactoryResult { + fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()>; +} + +/// Application state +pub struct State(Rc); + +impl State { + pub fn new(state: S) -> State { + State(Rc::new(state)) + } + + pub fn get_ref(&self) -> &S { + self.0.as_ref() + } +} + +impl Deref for State { + type Target = S; + + fn deref(&self) -> &S { + self.0.as_ref() + } +} + +impl Clone for State { + fn clone(&self) -> State { + State(self.0.clone()) + } +} + +impl FromRequest

    for State { + type Error = Error; + type Future = FutureResult; + + #[inline] + fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + if let Some(st) = req.app_extensions().get::>() { + ok(st.clone()) + } else { + err(ErrorInternalServerError( + "State is not configured, use App::state()", + )) + } + } +} + +impl StateFactory for State { + fn construct(&self) -> Box { + Box::new(StateFut { st: self.clone() }) + } +} + +struct StateFut { + st: State, +} + +impl StateFactoryResult for StateFut { + fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> { + extensions.insert(self.st.clone()); + Ok(Async::Ready(())) + } +} + +impl StateFactory for F +where + F: Fn() -> Out + 'static, + Out: IntoFuture + 'static, + Out::Error: std::fmt::Debug, +{ + fn construct(&self) -> Box { + Box::new(StateFactoryFut { + fut: (*self)().into_future(), + }) + } +} + +struct StateFactoryFut +where + F: Future, + F::Error: std::fmt::Debug, +{ + fut: F, +} + +impl StateFactoryResult for StateFactoryFut +where + F: Future, + F::Error: std::fmt::Debug, +{ + fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> { + match self.fut.poll() { + Ok(Async::Ready(s)) => { + extensions.insert(State::new(s)); + Ok(Async::Ready(())) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => { + log::error!("Can not construct application state: {:?}", e); + Err(()) + } + } + } +} diff --git a/src/test.rs b/src/test.rs index 1d86db9f..e13dcd16 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,449 +1,23 @@ //! Various helpers for Actix applications to use during testing. -use std::rc::Rc; use std::str::FromStr; -use std::sync::mpsc; -use std::{net, thread}; -use actix::{Actor, Addr, System}; -use actix::actors::signal; +use bytes::Bytes; +use futures::IntoFuture; +use tokio_current_thread::Runtime; -use actix_net::server::Server; -use cookie::Cookie; -use futures::Future; -use http::header::HeaderName; -use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use net2::TcpBuilder; -use tokio::runtime::current_thread::Runtime; +use actix_http::dev::Payload; +use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; +use actix_http::http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use actix_http::Request as HttpRequest; +use actix_router::{Path, Url}; -#[cfg(any(feature = "alpn", feature = "ssl"))] -use openssl::ssl::SslAcceptorBuilder; -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig; +use crate::app::State; +use crate::request::Request; +use crate::service::ServiceRequest; -use application::{App, HttpApplication}; -use body::Binary; -use client::{ClientConnector, ClientRequest, ClientRequestBuilder}; -use error::Error; -use handler::{AsyncResult, AsyncResultItem, Handler, Responder}; -use header::{Header, IntoHeaderValue}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::Middleware; -use param::Params; -use payload::Payload; -use resource::Resource; -use router::Router; -use server::message::{Request, RequestPool}; -use server::{HttpServer, IntoHttpHandler, ServerSettings}; -use uri::Url as InnerUrl; -use ws; - -/// The `TestServer` type. +/// Test `Request` builder /// -/// `TestServer` is very simple test server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ```rust -/// # extern crate actix_web; -/// # use actix_web::*; -/// # -/// # fn my_handler(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() -/// # } -/// # -/// # fn main() { -/// use actix_web::test::TestServer; -/// -/// let mut srv = TestServer::new(|app| app.handler(my_handler)); -/// -/// let req = srv.get().finish().unwrap(); -/// let response = srv.execute(req.send()).unwrap(); -/// assert!(response.status().is_success()); -/// # } -/// ``` -pub struct TestServer { - addr: net::SocketAddr, - ssl: bool, - conn: Addr, - rt: Runtime, - backend: Addr, -} - -impl TestServer { - /// Start new test server - /// - /// This method accepts configuration method. You can add - /// middlewares or set handlers for test application. - pub fn new(config: F) -> Self - where - F: Clone + Send + 'static + Fn(&mut TestApp<()>), - { - TestServerBuilder::new(|| ()).start(config) - } - - /// Create test server builder - pub fn build() -> TestServerBuilder<(), impl Fn() -> () + Clone + Send + 'static> { - TestServerBuilder::new(|| ()) - } - - /// Create test server builder with specific state factory - /// - /// This method can be used for constructing application state. - /// Also it can be used for external dependency initialization, - /// like creating sync actors for diesel integration. - pub fn build_with_state(state: F) -> TestServerBuilder - where - F: Fn() -> S + Clone + Send + 'static, - S: 'static, - { - TestServerBuilder::new(state) - } - - /// Start new test server with application factory - pub fn with_factory(factory: F) -> Self - where - F: Fn() -> H + Send + Clone + 'static, - H: IntoHttpHandler + 'static, - { - let (tx, rx) = mpsc::channel(); - - // run server in separate thread - thread::spawn(move || { - let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - - let srv = HttpServer::new(factory) - .disable_signals() - .listen(tcp) - .keep_alive(5) - .start(); - - tx.send((System::current(), local_addr, TestServer::get_conn(), srv)) - .unwrap(); - sys.run(); - }); - - let (system, addr, conn, backend) = rx.recv().unwrap(); - System::set_current(system); - TestServer { - addr, - conn, - ssl: false, - rt: Runtime::new().unwrap(), - backend, - } - } - - fn get_conn() -> Addr { - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - ClientConnector::with_connector(builder.build()).start() - } - #[cfg(all( - feature = "rust-tls", - not(any(feature = "alpn", feature = "ssl")) - ))] - { - use rustls::ClientConfig; - use std::fs::File; - use std::io::BufReader; - let mut config = ClientConfig::new(); - let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - config.root_store.add_pem_file(pem_file).unwrap(); - ClientConnector::with_connector(config).start() - } - #[cfg(not(any(feature = "alpn", feature = "ssl", feature = "rust-tls")))] - { - ClientConnector::default().start() - } - } - - /// Get firat available unused address - pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = TcpBuilder::new_v4().unwrap(); - socket.bind(&addr).unwrap(); - socket.reuse_address(true).unwrap(); - let tcp = socket.to_tcp_listener().unwrap(); - tcp.local_addr().unwrap() - } - - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - if uri.starts_with('/') { - format!( - "{}://localhost:{}{}", - if self.ssl { "https" } else { "http" }, - self.addr.port(), - uri - ) - } else { - format!( - "{}://localhost:{}/{}", - if self.ssl { "https" } else { "http" }, - self.addr.port(), - uri - ) - } - } - - /// Stop http server - fn stop(&mut self) { - let _ = self.backend.send(signal::Signal(signal::SignalType::Term)).wait(); - System::current().stop(); - } - - /// Execute future on current core - pub fn execute(&mut self, fut: F) -> Result - where - F: Future, - { - self.rt.block_on(fut) - } - - /// Connect to websocket server at a given path - pub fn ws_at( - &mut self, path: &str, - ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - let url = self.url(path); - self.rt - .block_on(ws::Client::with_connector(url, self.conn.clone()).connect()) - } - - /// Connect to a websocket server - pub fn ws( - &mut self, - ) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { - self.ws_at("/") - } - - /// Create `GET` request - pub fn get(&self) -> ClientRequestBuilder { - ClientRequest::get(self.url("/").as_str()) - } - - /// Create `POST` request - pub fn post(&self) -> ClientRequestBuilder { - ClientRequest::post(self.url("/").as_str()) - } - - /// Create `HEAD` request - pub fn head(&self) -> ClientRequestBuilder { - ClientRequest::head(self.url("/").as_str()) - } - - /// Connect to test http server - pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - ClientRequest::build() - .method(meth) - .uri(self.url(path).as_str()) - .with_connector(self.conn.clone()) - .take() - } -} - -impl Drop for TestServer { - fn drop(&mut self) { - self.stop() - } -} - -/// An `TestServer` builder -/// -/// This type can be used to construct an instance of `TestServer` through a -/// builder-like pattern. -pub struct TestServerBuilder -where - F: Fn() -> S + Send + Clone + 'static, -{ - state: F, - #[cfg(any(feature = "alpn", feature = "ssl"))] - ssl: Option, - #[cfg(feature = "rust-tls")] - rust_ssl: Option, -} - -impl TestServerBuilder -where - F: Fn() -> S + Send + Clone + 'static, -{ - /// Create a new test server - pub fn new(state: F) -> TestServerBuilder { - TestServerBuilder { - state, - #[cfg(any(feature = "alpn", feature = "ssl"))] - ssl: None, - #[cfg(feature = "rust-tls")] - rust_ssl: None, - } - } - - #[cfg(any(feature = "alpn", feature = "ssl"))] - /// Create ssl server - pub fn ssl(mut self, ssl: SslAcceptorBuilder) -> Self { - self.ssl = Some(ssl); - self - } - - #[cfg(feature = "rust-tls")] - /// Create rust tls server - pub fn rustls(mut self, ssl: ServerConfig) -> Self { - self.rust_ssl = Some(ssl); - self - } - - #[allow(unused_mut)] - /// Configure test application and run test server - pub fn start(mut self, config: C) -> TestServer - where - C: Fn(&mut TestApp) + Clone + Send + 'static, - { - let (tx, rx) = mpsc::channel(); - - let mut has_ssl = false; - - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - has_ssl = has_ssl || self.ssl.is_some(); - } - - #[cfg(feature = "rust-tls")] - { - has_ssl = has_ssl || self.rust_ssl.is_some(); - } - - // run server in separate thread - thread::spawn(move || { - let addr = TestServer::unused_addr(); - - let sys = System::new("actix-test-server"); - let state = self.state; - let mut srv = HttpServer::new(move || { - let mut app = TestApp::new(state()); - config(&mut app); - app - }).workers(1) - .keep_alive(5) - .disable_signals(); - - - - #[cfg(any(feature = "alpn", feature = "ssl"))] - { - let ssl = self.ssl.take(); - if let Some(ssl) = ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_ssl(tcp, ssl).unwrap(); - } - } - #[cfg(feature = "rust-tls")] - { - let ssl = self.rust_ssl.take(); - if let Some(ssl) = ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen_rustls(tcp, ssl); - } - } - if !has_ssl { - let tcp = net::TcpListener::bind(addr).unwrap(); - srv = srv.listen(tcp); - } - let backend = srv.start(); - - tx.send((System::current(), addr, TestServer::get_conn(), backend)) - .unwrap(); - - sys.run(); - }); - - let (system, addr, conn, backend) = rx.recv().unwrap(); - System::set_current(system); - TestServer { - addr, - conn, - ssl: has_ssl, - rt: Runtime::new().unwrap(), - backend, - } - } -} - -/// Test application helper for testing request handlers. -pub struct TestApp { - app: Option>, -} - -impl TestApp { - fn new(state: S) -> TestApp { - let app = App::with_state(state); - TestApp { app: Some(app) } - } - - /// Register handler for "/" - pub fn handler(&mut self, handler: F) - where - F: Fn(&HttpRequest) -> R + 'static, - R: Responder + 'static, - { - self.app = Some(self.app.take().unwrap().resource("/", |r| r.f(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 - } - - /// Register resource. This method is similar - /// to `App::resource()` method. - pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp - where - F: FnOnce(&mut Resource) -> R + 'static, - { - self.app = Some(self.app.take().unwrap().resource(path, f)); - self - } -} - -impl IntoHttpHandler for TestApp { - type Handler = HttpApplication; - - fn into_handler(mut self) -> HttpApplication { - self.app.take().unwrap().into_handler() - } -} - -#[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 - } - } -} - -/// Test `HttpRequest` builder -/// -/// ```rust +/// ```rust,ignore /// # extern crate http; /// # extern crate actix_web; /// # use http::{header, StatusCode}; @@ -474,10 +48,8 @@ pub struct TestRequest { method: Method, uri: Uri, headers: HeaderMap, - params: Params, - cookies: Option>>, + params: Path, payload: Option, - prefix: u16, } impl Default for TestRequest<()> { @@ -488,10 +60,8 @@ impl Default for TestRequest<()> { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - params: Params::new(), - cookies: None, + params: Path::new(Url::default()), payload: None, - prefix: 0, } } } @@ -515,11 +85,6 @@ impl TestRequest<()> { { TestRequest::default().header(key, value) } - - /// Create TestRequest and set request cookie - pub fn with_cookie(cookie: Cookie<'static>) -> TestRequest<()> { - TestRequest::default().cookie(cookie) - } } impl TestRequest { @@ -531,10 +96,8 @@ impl TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - params: Params::new(), - cookies: None, + params: Path::new(Url::default()), payload: None, - prefix: 0, } } @@ -556,25 +119,6 @@ impl TestRequest { self } - /// set cookie of this request - pub fn cookie(mut self, cookie: Cookie<'static>) -> Self { - if self.cookies.is_some() { - let mut should_insert = true; - let old_cookies = self.cookies.as_mut().unwrap(); - for old_cookie in old_cookies.iter() { - if old_cookie == &cookie { - should_insert = false - }; - }; - if should_insert { - old_cookies.push(cookie); - }; - } else { - self.cookies = Some(vec![cookie]); - }; - self - } - /// Set a header pub fn set(mut self, hdr: H) -> Self { if let Ok(value) = hdr.try_into() { @@ -606,22 +150,15 @@ impl TestRequest { } /// Set request payload - pub fn set_payload>(mut self, data: B) -> Self { - let mut data = data.into(); + pub fn set_payload>(mut self, data: B) -> Self { let mut payload = Payload::empty(); - payload.unread_data(data.take()); + payload.unread_data(data.into()); self.payload = Some(payload); self } - /// Set request's prefix - pub fn prefix(mut self, prefix: u16) -> Self { - self.prefix = prefix; - self - } - /// Complete request creation and generate `HttpRequest` instance - pub fn finish(self) -> HttpRequest { + pub fn finish(self) -> ServiceRequest { let TestRequest { state, method, @@ -629,172 +166,31 @@ impl TestRequest { version, headers, mut params, - cookies, payload, - prefix, - } = self; - let router = Router::<()>::default(); - - let pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); - { - let inner = req.inner_mut(); - inner.method = method; - inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; - *inner.payload.borrow_mut() = payload; - } - params.set_url(req.url().clone()); - let mut info = router.route_info_params(0, params); - info.set_prefix(prefix); - - let mut req = HttpRequest::new(req, Rc::new(state), info); - req.set_cookies(cookies); - req - } - - #[cfg(test)] - /// Complete request creation and generate `HttpRequest` instance - pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest { - let TestRequest { - state, - method, - uri, - version, - headers, - mut params, - cookies, - payload, - prefix, } = self; - let pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); + params.get_mut().update(&uri); + + let mut req = HttpRequest::new(); { let inner = req.inner_mut(); - inner.method = method; - inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; + inner.head.uri = uri; + inner.head.method = method; + inner.head.version = version; + inner.head.headers = headers; *inner.payload.borrow_mut() = payload; } - params.set_url(req.url().clone()); - let mut info = router.route_info_params(0, params); - info.set_prefix(prefix); - let mut req = HttpRequest::new(req, Rc::new(state), info); - req.set_cookies(cookies); - req - } - /// Complete request creation and generate server `Request` instance - pub fn request(self) -> Request { - let TestRequest { - method, - uri, - version, - headers, - payload, - .. - } = self; - - let pool = RequestPool::pool(ServerSettings::default()); - let mut req = RequestPool::get(pool); - { - let inner = req.inner_mut(); - inner.method = method; - inner.url = InnerUrl::new(uri); - inner.version = version; - inner.headers = headers; - *inner.payload.borrow_mut() = payload; - } - req - } - - /// This method generates `HttpRequest` instance and runs handler - /// with generated request. - pub fn run>(self, h: &H) -> Result { - let req = self.finish(); - let resp = h.handle(&req); - - match resp.respond_to(&req) { - Ok(resp) => match resp.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - }, - Err(err) => Err(err.into()), - } - } - - /// 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 sys = System::new("test"); - match sys.block_on(fut) { - Ok(r) => match r.respond_to(&req) { - Ok(reply) => match reply.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - _ => panic!("Nested async replies are not supported"), - }, - Err(e) => Err(e), - }, - Err(err) => Err(err), - } + Request::new(State::new(state), req, params) } /// This method generates `HttpRequest` instance and executes handler - pub fn run_async_result(self, f: F) -> Result + pub fn run_async(self, f: F) -> Result where - F: FnOnce(&HttpRequest) -> R, - R: Into>, + F: FnOnce(&Request) -> R, + R: IntoFuture, { - let req = self.finish(); - let res = f(&req); - - match res.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - } - } - - /// This method generates `HttpRequest` instance and executes handler - pub fn execute(self, f: F) -> Result - where - F: FnOnce(&HttpRequest) -> R, - R: Responder + 'static, - { - let req = self.finish(); - let resp = f(&req); - - match resp.respond_to(&req) { - Ok(resp) => match resp.into().into() { - AsyncResultItem::Ok(resp) => Ok(resp), - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Future(fut) => { - let mut sys = System::new("test"); - sys.block_on(fut) - } - }, - Err(err) => Err(err.into()), - } + let mut rt = Runtime::new().unwrap(); + rt.block_on(f(&self.finish()).into_future()) } } diff --git a/src/uri.rs b/src/uri.rs deleted file mode 100644 index c87cb3d5..00000000 --- a/src/uri.rs +++ /dev/null @@ -1,177 +0,0 @@ -use http::Uri; -use std::rc::Rc; - -// https://tools.ietf.org/html/rfc3986#section-2.2 -const RESERVED_PLUS_EXTRA: &[u8] = b":/?#[]@!$&'()*,+?;=%^ <>\"\\`{}|"; - -// https://tools.ietf.org/html/rfc3986#section-2.3 -const UNRESERVED: &[u8] = - b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~"; - -#[inline] -fn bit_at(array: &[u8], ch: u8) -> bool { - array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0 -} - -#[inline] -fn set_bit(array: &mut [u8], ch: u8) { - array[(ch >> 3) as usize] |= 1 << (ch & 7) -} - -lazy_static! { - static ref UNRESERVED_QUOTER: Quoter = { Quoter::new(UNRESERVED) }; - pub(crate) static ref RESERVED_QUOTER: Quoter = { Quoter::new(RESERVED_PLUS_EXTRA) }; -} - -#[derive(Default, Clone, Debug)] -pub(crate) struct Url { - uri: Uri, - path: Option>, -} - -impl Url { - pub fn new(uri: Uri) -> Url { - let path = UNRESERVED_QUOTER.requote(uri.path().as_bytes()); - - Url { uri, path } - } - - pub fn uri(&self) -> &Uri { - &self.uri - } - - pub fn path(&self) -> &str { - if let Some(ref s) = self.path { - s - } else { - self.uri.path() - } - } -} - -pub(crate) struct Quoter { - safe_table: [u8; 16], -} - -impl Quoter { - pub fn new(safe: &[u8]) -> Quoter { - let mut q = Quoter { - safe_table: [0; 16], - }; - - // prepare safe table - for ch in safe { - set_bit(&mut q.safe_table, *ch) - } - - q - } - - pub fn requote(&self, val: &[u8]) -> Option> { - let mut has_pct = 0; - let mut pct = [b'%', 0, 0]; - let mut idx = 0; - let mut cloned: Option> = None; - - let len = val.len(); - while idx < len { - let ch = val[idx]; - - if has_pct != 0 { - pct[has_pct] = val[idx]; - has_pct += 1; - if has_pct == 3 { - has_pct = 0; - let buf = cloned.as_mut().unwrap(); - - if let Some(ch) = restore_ch(pct[1], pct[2]) { - if ch < 128 { - if bit_at(&self.safe_table, ch) { - buf.push(ch); - idx += 1; - continue; - } - - buf.extend_from_slice(&pct); - } else { - // Not ASCII, decode it - buf.push(ch); - } - } else { - buf.extend_from_slice(&pct[..]); - } - } - } else if ch == b'%' { - has_pct = 1; - if cloned.is_none() { - let mut c = Vec::with_capacity(len); - c.extend_from_slice(&val[..idx]); - cloned = Some(c); - } - } else if let Some(ref mut cloned) = cloned { - cloned.push(ch) - } - idx += 1; - } - - if let Some(data) = cloned { - // Unsafe: we get data from http::Uri, which does utf-8 checks already - // this code only decodes valid pct encoded values - Some(Rc::new(unsafe { String::from_utf8_unchecked(data) })) - } else { - None - } - } -} - -#[inline] -fn from_hex(v: u8) -> Option { - if v >= b'0' && v <= b'9' { - Some(v - 0x30) // ord('0') == 0x30 - } else if v >= b'A' && v <= b'F' { - Some(v - 0x41 + 10) // ord('A') == 0x41 - } else if v > b'a' && v <= b'f' { - Some(v - 0x61 + 10) // ord('a') == 0x61 - } else { - None - } -} - -#[inline] -fn restore_ch(d1: u8, d2: u8) -> Option { - from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2))) -} - - -#[cfg(test)] -mod tests { - use std::rc::Rc; - - use super::*; - - #[test] - fn decode_path() { - assert_eq!(UNRESERVED_QUOTER.requote(b"https://localhost:80/foo"), None); - - assert_eq!( - Rc::try_unwrap(UNRESERVED_QUOTER.requote( - b"https://localhost:80/foo%25" - ).unwrap()).unwrap(), - "https://localhost:80/foo%25".to_string() - ); - - assert_eq!( - Rc::try_unwrap(UNRESERVED_QUOTER.requote( - b"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo" - ).unwrap()).unwrap(), - "http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo".to_string() - ); - - assert_eq!( - Rc::try_unwrap(UNRESERVED_QUOTER.requote( - b"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A" - ).unwrap()).unwrap(), - "http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A".to_string() - ); - } -} \ No newline at end of file diff --git a/src/with.rs b/src/with.rs deleted file mode 100644 index 140e086e..00000000 --- a/src/with.rs +++ /dev/null @@ -1,383 +0,0 @@ -use futures::{Async, Future, Poll}; -use std::marker::PhantomData; -use std::rc::Rc; - -use error::Error; -use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; - -trait FnWith: 'static { - fn call_with(self: &Self, T) -> R; -} - -impl R + 'static> FnWith for F { - fn call_with(self: &Self, arg: T) -> R { - (*self)(arg) - } -} - -#[doc(hidden)] -pub trait WithFactory: 'static -where - T: FromRequest, - R: Responder, -{ - fn create(self) -> With; - - fn create_with_config(self, T::Config) -> With; -} - -#[doc(hidden)] -pub trait WithAsyncFactory: 'static -where - T: FromRequest, - R: Future, - I: Responder, - E: Into, -{ - fn create(self) -> WithAsync; - - fn create_with_config(self, T::Config) -> WithAsync; -} - -#[doc(hidden)] -pub struct With -where - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - cfg: Rc, - _s: PhantomData, -} - -impl With -where - T: FromRequest, - S: 'static, -{ - pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { - With { - cfg: Rc::new(cfg), - hnd: Rc::new(f), - _s: PhantomData, - } - } -} - -impl Handler for With -where - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let mut fut = WithHandlerFut { - req: req.clone(), - started: false, - hnd: Rc::clone(&self.hnd), - cfg: self.cfg.clone(), - fut1: None, - fut2: None, - }; - - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithHandlerFut -where - R: Responder, - T: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg: Rc, - req: HttpRequest, - fut1: Option>>, - fut2: Option>>, -} - -impl Future for WithHandlerFut -where - R: Responder + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut2 { - return fut.poll(); - } - - let item = if !self.started { - self.started = true; - let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); - match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - } - } else { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - } - }; - - let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) { - Ok(item) => item.into(), - Err(e) => return Err(e.into()), - }; - - match item.into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut2 = Some(fut); - self.poll() - } - } - } -} - -#[doc(hidden)] -pub struct WithAsync -where - R: Future, - I: Responder, - E: Into, - T: FromRequest, - S: 'static, -{ - hnd: Rc>, - cfg: Rc, - _s: PhantomData, -} - -impl WithAsync -where - R: Future, - I: Responder, - E: Into, - T: FromRequest, - S: 'static, -{ - pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { - WithAsync { - cfg: Rc::new(cfg), - hnd: Rc::new(f), - _s: PhantomData, - } - } -} - -impl Handler for WithAsync -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Result = AsyncResult; - - fn handle(&self, req: &HttpRequest) -> Self::Result { - let mut fut = WithAsyncHandlerFut { - req: req.clone(), - started: false, - hnd: Rc::clone(&self.hnd), - cfg: Rc::clone(&self.cfg), - fut1: None, - fut2: None, - fut3: None, - }; - - match fut.poll() { - Ok(Async::Ready(resp)) => AsyncResult::ok(resp), - Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)), - Err(e) => AsyncResult::err(e), - } - } -} - -struct WithAsyncHandlerFut -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - started: bool, - hnd: Rc>, - cfg: Rc, - req: HttpRequest, - fut1: Option>>, - fut2: Option, - fut3: Option>>, -} - -impl Future for WithAsyncHandlerFut -where - R: Future + 'static, - I: Responder + 'static, - E: Into + 'static, - T: FromRequest + 'static, - S: 'static, -{ - type Item = HttpResponse; - type Error = Error; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut3 { - return fut.poll(); - } - - if self.fut2.is_some() { - return match self.fut2.as_mut().unwrap().poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(r)) => match r.respond_to(&self.req) { - Ok(r) => match r.into().into() { - AsyncResultItem::Err(err) => Err(err), - AsyncResultItem::Ok(resp) => Ok(Async::Ready(resp)), - AsyncResultItem::Future(fut) => { - self.fut3 = Some(fut); - self.poll() - } - }, - Err(e) => Err(e.into()), - }, - Err(e) => Err(e.into()), - }; - } - - let item = if !self.started { - self.started = true; - let reply = T::from_request(&self.req, self.cfg.as_ref()).into(); - match reply.into() { - AsyncResultItem::Err(err) => return Err(err), - AsyncResultItem::Ok(msg) => msg, - AsyncResultItem::Future(fut) => { - self.fut1 = Some(fut); - return self.poll(); - } - } - } else { - match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), - } - }; - - self.fut2 = Some(self.hnd.as_ref().call_with(item)); - self.poll() - } -} - -macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => { - impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func - where Func: Fn($($T,)+) -> Res + 'static, - $($T: FromRequest + 'static,)+ - Res: Responder + 'static, - State: 'static, - { - fn create(self) -> With<($($T,)+), State, Res> { - With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) - } - - fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> { - With::new(move |($($n,)+)| (self)($($n,)+), cfg) - } - } -}); - -macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => { - impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func - where Func: Fn($($T,)+) -> Res + 'static, - $($T: FromRequest + 'static,)+ - Res: Future, - Item: Responder + 'static, - Err: Into, - State: 'static, - { - fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> { - WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+)) - } - - fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> { - WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg) - } - } -}); - -with_factory_tuple!((a, A)); -with_factory_tuple!((a, A), (b, B)); -with_factory_tuple!((a, A), (b, B), (c, C)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H) -); -with_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H), - (i, I) -); - -with_async_factory_tuple!((a, A)); -with_async_factory_tuple!((a, A), (b, B)); -with_async_factory_tuple!((a, A), (b, B), (c, C)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F)); -with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G)); -with_async_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H) -); -with_async_factory_tuple!( - (a, A), - (b, B), - (c, C), - (d, D), - (e, E), - (f, F), - (g, G), - (h, H), - (i, I) -); diff --git a/src/ws/client.rs b/src/ws/client.rs deleted file mode 100644 index 18789fef..00000000 --- a/src/ws/client.rs +++ /dev/null @@ -1,602 +0,0 @@ -//! Http client request -use std::cell::RefCell; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, io, str}; - -use base64; -use bytes::Bytes; -use cookie::Cookie; -use futures::sync::mpsc::{unbounded, UnboundedSender}; -use futures::{Async, Future, Poll, Stream}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HttpTryFrom, StatusCode}; -use rand; -use sha1::Sha1; - -use actix::{Addr, SystemService}; - -use body::{Binary, Body}; -use error::{Error, UrlParseError}; -use header::IntoHeaderValue; -use httpmessage::HttpMessage; -use payload::PayloadBuffer; - -use client::{ - ClientConnector, ClientRequest, ClientRequestBuilder, HttpResponseParserError, - Pipeline, SendRequest, SendRequestError, -}; - -use super::frame::{Frame, FramedMessage}; -use super::proto::{CloseReason, OpCode}; -use super::{Message, ProtocolError, WsWriter}; - -/// Websocket client error -#[derive(Fail, Debug)] -pub enum ClientError { - /// Invalid url - #[fail(display = "Invalid url")] - InvalidUrl, - /// Invalid response status - #[fail(display = "Invalid response status")] - InvalidResponseStatus(StatusCode), - /// Invalid upgrade header - #[fail(display = "Invalid upgrade header")] - InvalidUpgradeHeader, - /// Invalid connection header - #[fail(display = "Invalid connection header")] - InvalidConnectionHeader(HeaderValue), - /// Missing CONNECTION header - #[fail(display = "Missing CONNECTION header")] - MissingConnectionHeader, - /// Missing SEC-WEBSOCKET-ACCEPT header - #[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")] - MissingWebSocketAcceptHeader, - /// Invalid challenge response - #[fail(display = "Invalid challenge response")] - InvalidChallengeResponse(String, HeaderValue), - /// Http parsing error - #[fail(display = "Http parsing error")] - Http(Error), - /// Url parsing error - #[fail(display = "Url parsing error")] - Url(UrlParseError), - /// Response parsing error - #[fail(display = "Response parsing error")] - ResponseParseError(HttpResponseParserError), - /// Send request error - #[fail(display = "{}", _0)] - SendRequest(SendRequestError), - /// Protocol error - #[fail(display = "{}", _0)] - Protocol(#[cause] ProtocolError), - /// IO Error - #[fail(display = "{}", _0)] - Io(io::Error), - /// "Disconnected" - #[fail(display = "Disconnected")] - Disconnected, -} - -impl From for ClientError { - fn from(err: Error) -> ClientError { - ClientError::Http(err) - } -} - -impl From for ClientError { - fn from(err: UrlParseError) -> ClientError { - ClientError::Url(err) - } -} - -impl From for ClientError { - fn from(err: SendRequestError) -> ClientError { - ClientError::SendRequest(err) - } -} - -impl From for ClientError { - fn from(err: ProtocolError) -> ClientError { - ClientError::Protocol(err) - } -} - -impl From for ClientError { - fn from(err: io::Error) -> ClientError { - ClientError::Io(err) - } -} - -impl From for ClientError { - fn from(err: HttpResponseParserError) -> ClientError { - ClientError::ResponseParseError(err) - } -} - -/// `WebSocket` client -/// -/// Example of `WebSocket` client usage is available in -/// [websocket example]( -/// https://github.com/actix/examples/blob/master/websocket/src/client.rs#L24) -pub struct Client { - request: ClientRequestBuilder, - err: Option, - http_err: Option, - origin: Option, - protocols: Option, - conn: Addr, - max_size: usize, - no_masking: bool, -} - -impl Client { - /// Create new websocket connection - pub fn new>(uri: S) -> Client { - Client::with_connector(uri, ClientConnector::from_registry()) - } - - /// Create new websocket connection with custom `ClientConnector` - pub fn with_connector>(uri: S, conn: Addr) -> Client { - let mut cl = Client { - request: ClientRequest::build(), - err: None, - http_err: None, - origin: None, - protocols: None, - max_size: 65_536, - no_masking: false, - conn, - }; - cl.request.uri(uri.as_ref()); - cl - } - - /// Set supported websocket protocols - pub fn protocols(mut self, protos: U) -> Self - where - U: IntoIterator + 'static, - V: AsRef, - { - let mut protos = protos - .into_iter() - .fold(String::new(), |acc, s| acc + s.as_ref() + ","); - protos.pop(); - self.protocols = Some(protos); - self - } - - /// Set cookie for handshake request - pub fn cookie(mut self, cookie: Cookie) -> Self { - self.request.cookie(cookie); - self - } - - /// Set request Origin - pub fn origin(mut self, origin: V) -> Self - where - HeaderValue: HttpTryFrom, - { - match HeaderValue::try_from(origin) { - Ok(value) => self.origin = Some(value), - Err(e) => self.http_err = Some(e.into()), - } - self - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_frame_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } - - /// Set write buffer capacity - /// - /// Default buffer capacity is 32kb - pub fn write_buffer_capacity(mut self, cap: usize) -> Self { - self.request.write_buffer_capacity(cap); - self - } - - /// Disable payload masking. By default ws client masks frame payload. - pub fn no_masking(mut self) -> Self { - self.no_masking = true; - self - } - - /// Set request header - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - self.request.header(key, value); - self - } - - /// Set websocket handshake timeout - /// - /// Handshake timeout is a total time for successful handshake. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.request.timeout(timeout); - self - } - - /// Connect to websocket server and do ws handshake - pub fn connect(&mut self) -> ClientHandshake { - if let Some(e) = self.err.take() { - ClientHandshake::error(e) - } else if let Some(e) = self.http_err.take() { - ClientHandshake::error(Error::from(e).into()) - } else { - // origin - if let Some(origin) = self.origin.take() { - self.request.set_header(header::ORIGIN, origin); - } - - self.request.upgrade(); - self.request.set_header(header::UPGRADE, "websocket"); - self.request.set_header(header::CONNECTION, "upgrade"); - self.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); - self.request.with_connector(self.conn.clone()); - - if let Some(protocols) = self.protocols.take() { - self.request - .set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); - } - let request = match self.request.finish() { - Ok(req) => req, - Err(err) => return ClientHandshake::error(err.into()), - }; - - if request.uri().host().is_none() { - return ClientHandshake::error(ClientError::InvalidUrl); - } - if let Some(scheme) = request.uri().scheme_part() { - if scheme != "http" - && scheme != "https" - && scheme != "ws" - && scheme != "wss" - { - return ClientHandshake::error(ClientError::InvalidUrl); - } - } else { - return ClientHandshake::error(ClientError::InvalidUrl); - } - - // start handshake - ClientHandshake::new(request, self.max_size, self.no_masking) - } - } -} - -struct Inner { - tx: UnboundedSender, - rx: PayloadBuffer>, - closed: bool, -} - -/// Future that implementes client websocket handshake process. -/// -/// It resolves to a pair of `ClientReader` and `ClientWriter` that -/// can be used for reading and writing websocket frames. -pub struct ClientHandshake { - request: Option, - tx: Option>, - key: String, - error: Option, - max_size: usize, - no_masking: bool, -} - -impl ClientHandshake { - fn new( - mut request: ClientRequest, max_size: usize, no_masking: bool, - ) -> ClientHandshake { - // Generate a random key for the `Sec-WebSocket-Key` header. - // a base64-encoded (see Section 4 of [RFC4648]) value that, - // when decoded, is 16 bytes in length (RFC 6455) - let sec_key: [u8; 16] = rand::random(); - let key = base64::encode(&sec_key); - - request.headers_mut().insert( - header::SEC_WEBSOCKET_KEY, - HeaderValue::try_from(key.as_str()).unwrap(), - ); - - let (tx, rx) = unbounded(); - request.set_body(Body::Streaming(Box::new(rx.map_err(|_| { - io::Error::new(io::ErrorKind::Other, "disconnected").into() - })))); - - ClientHandshake { - key, - max_size, - no_masking, - request: Some(request.send()), - tx: Some(tx), - error: None, - } - } - - fn error(err: ClientError) -> ClientHandshake { - ClientHandshake { - key: String::new(), - request: None, - tx: None, - error: Some(err), - max_size: 0, - no_masking: false, - } - } - - /// Set handshake timeout - /// - /// Handshake timeout is a total time before handshake should be completed. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - if let Some(request) = self.request.take() { - self.request = Some(request.timeout(timeout)); - } - self - } - - /// Set connection timeout - /// - /// Connection timeout includes resolving hostname and actual connection to - /// the host. - /// Default value is 1 second. - pub fn conn_timeout(mut self, timeout: Duration) -> Self { - if let Some(request) = self.request.take() { - self.request = Some(request.conn_timeout(timeout)); - } - self - } -} - -impl Future for ClientHandshake { - type Item = (ClientReader, ClientWriter); - type Error = ClientError; - - fn poll(&mut self) -> Poll { - if let Some(err) = self.error.take() { - return Err(err); - } - - let resp = match self.request.as_mut().unwrap().poll()? { - Async::Ready(response) => { - self.request.take(); - response - } - Async::NotReady => return Ok(Async::NotReady), - }; - - // verify response - if resp.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(ClientError::InvalidResponseStatus(resp.status())); - } - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - trace!("Invalid upgrade header"); - return Err(ClientError::InvalidUpgradeHeader); - } - // Check for "CONNECTION" header - if let Some(conn) = resp.headers().get(header::CONNECTION) { - if let Ok(s) = conn.to_str() { - if !s.to_lowercase().contains("upgrade") { - trace!("Invalid connection header: {}", s); - return Err(ClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - trace!("Invalid connection header: {:?}", conn); - return Err(ClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - trace!("Missing connection header"); - return Err(ClientError::MissingConnectionHeader); - } - - if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT) { - // field is constructed by concatenating /key/ - // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) - const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(self.key.as_ref()); - sha1.update(WS_GUID); - let encoded = base64::encode(&sha1.digest().bytes()); - if key.as_bytes() != encoded.as_bytes() { - trace!( - "Invalid challenge response: expected: {} received: {:?}", - encoded, - key - ); - return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); - } - } else { - trace!("Missing SEC-WEBSOCKET-ACCEPT header"); - return Err(ClientError::MissingWebSocketAcceptHeader); - }; - - let inner = Inner { - tx: self.tx.take().unwrap(), - rx: PayloadBuffer::new(resp.payload()), - closed: false, - }; - - let inner = Rc::new(RefCell::new(inner)); - Ok(Async::Ready(( - ClientReader { - inner: Rc::clone(&inner), - max_size: self.max_size, - no_masking: self.no_masking, - }, - ClientWriter { inner }, - ))) - } -} - -/// Websocket reader client -pub struct ClientReader { - inner: Rc>, - max_size: usize, - no_masking: bool, -} - -impl fmt::Debug for ClientReader { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ws::ClientReader()") - } -} - -impl Stream for ClientReader { - type Item = Message; - type Error = ProtocolError; - - fn poll(&mut self) -> Poll, Self::Error> { - let max_size = self.max_size; - let no_masking = self.no_masking; - let mut inner = self.inner.borrow_mut(); - if inner.closed { - return Ok(Async::Ready(None)); - } - - // read - match Frame::parse(&mut inner.rx, no_masking, max_size) { - Ok(Async::Ready(Some(frame))) => { - let (_finished, opcode, payload) = frame.unpack(); - - match opcode { - // continuation is not supported - OpCode::Continue => { - inner.closed = true; - Err(ProtocolError::NoContinuation) - } - OpCode::Bad => { - inner.closed = true; - Err(ProtocolError::BadOpCode) - } - OpCode::Close => { - inner.closed = true; - let close_reason = Frame::parse_close_payload(&payload); - Ok(Async::Ready(Some(Message::Close(close_reason)))) - } - OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Pong => Ok(Async::Ready(Some(Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => { - inner.closed = true; - Err(ProtocolError::BadEncoding) - } - } - } - } - } - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { - inner.closed = true; - Err(e) - } - } - } -} - -/// Websocket writer client -pub struct ClientWriter { - inner: Rc>, -} - -impl ClientWriter { - /// Write payload - #[inline] - fn write(&mut self, mut data: FramedMessage) { - let inner = self.inner.borrow_mut(); - if !inner.closed { - let _ = inner.tx.unbounded_send(data.0.take()); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write(Frame::message(text.into(), OpCode::Text, true, true)); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write(Frame::message(data, OpCode::Binary, true, true)); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Ping, true, true)); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &str) { - self.write(Frame::message(Vec::from(message), OpCode::Pong, true, true)); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write(Frame::close(reason, true)); - } -} - -impl WsWriter for ClientWriter { - /// Send text frame - #[inline] - fn send_text>(&mut self, text: T) { - self.text(text) - } - - /// Send binary frame - #[inline] - fn send_binary>(&mut self, data: B) { - self.binary(data) - } - - /// Send ping frame - #[inline] - fn send_ping(&mut self, message: &str) { - self.ping(message) - } - - /// Send pong frame - #[inline] - fn send_pong(&mut self, message: &str) { - self.pong(message) - } - - /// Send close frame - #[inline] - fn send_close(&mut self, reason: Option) { - self.close(reason); - } -} diff --git a/src/ws/context.rs b/src/ws/context.rs deleted file mode 100644 index 5e207d43..00000000 --- a/src/ws/context.rs +++ /dev/null @@ -1,341 +0,0 @@ -extern crate actix; - -use bytes::Bytes; -use futures::sync::oneshot::{self, Sender}; -use futures::{Async, Future, Poll, Stream}; -use smallvec::SmallVec; - -use self::actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, - ToEnvelope, -}; -use self::actix::fut::ActorFuture; -use self::actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, - Message as ActixMessage, SpawnHandle, -}; - -use body::{Binary, Body}; -use context::{ActorHttpContext, Drain, Frame as ContextFrame}; -use error::{Error, ErrorInternalServerError, PayloadError}; -use httprequest::HttpRequest; - -use ws::frame::{Frame, FramedMessage}; -use ws::proto::{CloseReason, OpCode}; -use ws::{Message, ProtocolError, WsStream, WsWriter}; - -/// Execution context for `WebSockets` actors -pub struct WebsocketContext -where - A: Actor>, -{ - inner: ContextParts, - stream: Option>, - request: HttpRequest, - disconnected: bool, -} - -impl ActorContext for WebsocketContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for WebsocketContext -where - A: Actor, -{ - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl WebsocketContext -where - A: Actor, -{ - #[inline] - /// Create a new Websocket context from a request and an actor - pub fn create

    (req: HttpRequest, actor: A, stream: WsStream

    ) -> Body - where - A: StreamHandler, - P: Stream + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - ctx.add_stream(stream); - - Body::Actor(Box::new(WebsocketContextFut::new(ctx, actor, mb))) - } - - /// Create a new Websocket context - pub fn with_factory(req: HttpRequest, f: F) -> Body - where - F: FnOnce(&mut Self) -> A + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - - let act = f(&mut ctx); - Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb))) - } -} - -impl WebsocketContext -where - A: Actor, -{ - /// Write payload - /// - /// This is a low-level function that accepts framed messages that should - /// be created using `Frame::message()`. If you want to send text or binary - /// data you should prefer the `text()` or `binary()` convenience functions - /// that handle the framing for you. - #[inline] - pub fn write_raw(&mut self, data: FramedMessage) { - if !self.disconnected { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - let stream = self.stream.as_mut().unwrap(); - stream.push(ContextFrame::Chunk(Some(data.0))); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - self.request.state() - } - - /// Incoming request - #[inline] - pub fn request(&mut self) -> &mut HttpRequest { - &mut self.request - } - - /// Returns drain future - pub fn drain(&mut self) -> Drain { - let (tx, rx) = oneshot::channel(); - self.add_frame(ContextFrame::Drain(tx)); - Drain::new(rx) - } - - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write_raw(Frame::message(text.into(), OpCode::Text, true, false)); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write_raw(Frame::message(data, OpCode::Binary, true, false)); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &str) { - self.write_raw(Frame::message( - Vec::from(message), - OpCode::Ping, - true, - false, - )); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &str) { - self.write_raw(Frame::message( - Vec::from(message), - OpCode::Pong, - true, - false, - )); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write_raw(Frame::close(reason, false)); - } - - /// Check if connection still open - #[inline] - pub fn connected(&self) -> bool { - !self.disconnected - } - - #[inline] - fn add_frame(&mut self, frame: ContextFrame) { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - if let Some(s) = self.stream.as_mut() { - s.push(frame) - } - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } - - /// Set mailbox capacity - /// - /// By default mailbox capacity is 16 messages. - pub fn set_mailbox_capacity(&mut self, cap: usize) { - self.inner.set_mailbox_capacity(cap) - } -} - -impl WsWriter for WebsocketContext -where - A: Actor, - S: 'static, -{ - /// Send text frame - #[inline] - fn send_text>(&mut self, text: T) { - self.text(text) - } - - /// Send binary frame - #[inline] - fn send_binary>(&mut self, data: B) { - self.binary(data) - } - - /// Send ping frame - #[inline] - fn send_ping(&mut self, message: &str) { - self.ping(message) - } - - /// Send pong frame - #[inline] - fn send_pong(&mut self, message: &str) { - self.pong(message) - } - - /// Send close frame - #[inline] - fn send_close(&mut self, reason: Option) { - self.close(reason) - } -} - -impl AsyncContextParts for WebsocketContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct WebsocketContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl WebsocketContextFut -where - A: Actor>, -{ - fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - WebsocketContextFut { fut } - } -} - -impl ActorHttpContext for WebsocketContextFut -where - A: Actor>, - S: 'static, -{ - #[inline] - fn disconnected(&mut self) { - self.fut.ctx().disconnected = true; - self.fut.ctx().stop(); - } - - fn poll(&mut self) -> Poll>, Error> { - if self.fut.alive() && self.fut.poll().is_err() { - return Err(ErrorInternalServerError("error")); - } - - // frames - if let Some(data) = self.fut.ctx().stream.take() { - Ok(Async::Ready(Some(data))) - } else if self.fut.alive() { - Ok(Async::NotReady) - } else { - Ok(Async::Ready(None)) - } - } -} - -impl ToEnvelope for WebsocketContext -where - A: Actor> + Handler, - M: ActixMessage + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} diff --git a/src/ws/frame.rs b/src/ws/frame.rs deleted file mode 100644 index 5e4fd829..00000000 --- a/src/ws/frame.rs +++ /dev/null @@ -1,538 +0,0 @@ -use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; -use bytes::{BufMut, Bytes, BytesMut}; -use futures::{Async, Poll, Stream}; -use rand; -use std::fmt; - -use body::Binary; -use error::PayloadError; -use payload::PayloadBuffer; - -use ws::mask::apply_mask; -use ws::proto::{CloseCode, CloseReason, OpCode}; -use ws::ProtocolError; - -/// A struct representing a `WebSocket` frame. -#[derive(Debug)] -pub struct Frame { - finished: bool, - opcode: OpCode, - payload: Binary, -} - -impl Frame { - /// Destruct frame - pub fn unpack(self) -> (bool, OpCode, Binary) { - (self.finished, self.opcode, self.payload) - } - - /// Create a new Close control frame. - #[inline] - pub fn close(reason: Option, genmask: bool) -> FramedMessage { - let payload = match reason { - None => Vec::new(), - Some(reason) => { - let mut code_bytes = [0; 2]; - NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); - - let mut payload = Vec::from(&code_bytes[..]); - if let Some(description) = reason.description { - payload.extend(description.as_bytes()); - } - payload - } - }; - - Frame::message(payload, OpCode::Close, true, genmask) - } - - #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] - fn read_copy_md( - pl: &mut PayloadBuffer, server: bool, max_size: usize, - ) -> Poll)>, ProtocolError> - where - S: Stream, - { - let mut idx = 2; - let buf = match pl.copy(2)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let first = buf[0]; - let second = buf[1]; - let finished = first & 0x80 != 0; - - // check masking - let masked = second & 0x80 != 0; - if !masked && server { - return Err(ProtocolError::UnmaskedFrame); - } else if masked && !server { - return Err(ProtocolError::MaskedFrame); - } - - // Op code - let opcode = OpCode::from(first & 0x0F); - - if let OpCode::Bad = opcode { - return Err(ProtocolError::InvalidOpcode(first & 0x0F)); - } - - let len = second & 0x7F; - let length = if len == 126 { - let buf = match pl.copy(4)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let len = NetworkEndian::read_uint(&buf[idx..], 2) as usize; - idx += 2; - len - } else if len == 127 { - let buf = match pl.copy(10)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - let len = NetworkEndian::read_uint(&buf[idx..], 8); - if len > max_size as u64 { - return Err(ProtocolError::Overflow); - } - idx += 8; - len as usize - } else { - len as usize - }; - - // check for max allowed size - if length > max_size { - return Err(ProtocolError::Overflow); - } - - let mask = if server { - let buf = match pl.copy(idx + 4)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - }; - - let mask: &[u8] = &buf[idx..idx + 4]; - let mask_u32 = LittleEndian::read_u32(mask); - idx += 4; - Some(mask_u32) - } else { - None - }; - - Ok(Async::Ready(Some((idx, finished, opcode, length, mask)))) - } - - fn read_chunk_md( - chunk: &[u8], server: bool, max_size: usize, - ) -> Poll<(usize, bool, OpCode, usize, Option), ProtocolError> { - let chunk_len = chunk.len(); - - let mut idx = 2; - if chunk_len < 2 { - return Ok(Async::NotReady); - } - - let first = chunk[0]; - let second = chunk[1]; - let finished = first & 0x80 != 0; - - // check masking - let masked = second & 0x80 != 0; - if !masked && server { - return Err(ProtocolError::UnmaskedFrame); - } else if masked && !server { - return Err(ProtocolError::MaskedFrame); - } - - // Op code - let opcode = OpCode::from(first & 0x0F); - - if let OpCode::Bad = opcode { - return Err(ProtocolError::InvalidOpcode(first & 0x0F)); - } - - let len = second & 0x7F; - let length = if len == 126 { - if chunk_len < 4 { - return Ok(Async::NotReady); - } - let len = NetworkEndian::read_uint(&chunk[idx..], 2) as usize; - idx += 2; - len - } else if len == 127 { - if chunk_len < 10 { - return Ok(Async::NotReady); - } - let len = NetworkEndian::read_uint(&chunk[idx..], 8); - if len > max_size as u64 { - return Err(ProtocolError::Overflow); - } - idx += 8; - len as usize - } else { - len as usize - }; - - // check for max allowed size - if length > max_size { - return Err(ProtocolError::Overflow); - } - - let mask = if server { - if chunk_len < idx + 4 { - return Ok(Async::NotReady); - } - - let mask: &[u8] = &chunk[idx..idx + 4]; - let mask_u32 = LittleEndian::read_u32(mask); - idx += 4; - Some(mask_u32) - } else { - None - }; - - Ok(Async::Ready((idx, finished, opcode, length, mask))) - } - - /// Parse the input stream into a frame. - pub fn parse( - pl: &mut PayloadBuffer, server: bool, max_size: usize, - ) -> Poll, ProtocolError> - where - S: Stream, - { - // try to parse ws frame md from one chunk - let result = match pl.get_chunk()? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::Ready(Some(chunk)) => Frame::read_chunk_md(chunk, server, max_size)?, - }; - - let (idx, finished, opcode, length, mask) = match result { - // we may need to join several chunks - Async::NotReady => match Frame::read_copy_md(pl, server, max_size)? { - Async::Ready(Some(item)) => item, - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Ok(Async::Ready(None)), - }, - Async::Ready(item) => item, - }; - - match pl.can_read(idx + length)? { - Async::Ready(Some(true)) => (), - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::Ready(Some(false)) | Async::NotReady => return Ok(Async::NotReady), - } - - // remove prefix - pl.drop_bytes(idx); - - // no need for body - if length == 0 { - return Ok(Async::Ready(Some(Frame { - finished, - opcode, - payload: Binary::from(""), - }))); - } - - let data = match pl.read_exact(length)? { - Async::Ready(Some(buf)) => buf, - Async::Ready(None) => return Ok(Async::Ready(None)), - Async::NotReady => panic!(), - }; - - // control frames must have length <= 125 - match opcode { - OpCode::Ping | OpCode::Pong if length > 125 => { - return Err(ProtocolError::InvalidLength(length)) - } - OpCode::Close if length > 125 => { - debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Async::Ready(Some(Frame::default()))); - } - _ => (), - } - - // unmask - let data = if let Some(mask) = mask { - let mut buf = BytesMut::new(); - buf.extend_from_slice(&data); - apply_mask(&mut buf, mask); - buf.freeze() - } else { - data - }; - - Ok(Async::Ready(Some(Frame { - finished, - opcode, - payload: data.into(), - }))) - } - - /// Parse the payload of a close frame. - pub fn parse_close_payload(payload: &Binary) -> Option { - if payload.len() >= 2 { - let raw_code = NetworkEndian::read_u16(payload.as_ref()); - let code = CloseCode::from(raw_code); - let description = if payload.len() > 2 { - Some(String::from_utf8_lossy(&payload.as_ref()[2..]).into()) - } else { - None - }; - Some(CloseReason { code, description }) - } else { - None - } - } - - /// Generate binary representation - pub fn message>( - data: B, code: OpCode, finished: bool, genmask: bool, - ) -> FramedMessage { - let payload = data.into(); - let one: u8 = if finished { - 0x80 | Into::::into(code) - } else { - code.into() - }; - let payload_len = payload.len(); - let (two, p_len) = if genmask { - (0x80, payload_len + 4) - } else { - (0, payload_len) - }; - - let mut buf = if payload_len < 126 { - let mut buf = BytesMut::with_capacity(p_len + 2); - buf.put_slice(&[one, two | payload_len as u8]); - buf - } else if payload_len <= 65_535 { - let mut buf = BytesMut::with_capacity(p_len + 4); - buf.put_slice(&[one, two | 126]); - buf.put_u16_be(payload_len as u16); - buf - } else { - let mut buf = BytesMut::with_capacity(p_len + 10); - buf.put_slice(&[one, two | 127]); - buf.put_u64_be(payload_len as u64); - buf - }; - - let binary = if genmask { - let mask = rand::random::(); - buf.put_u32_le(mask); - buf.extend_from_slice(payload.as_ref()); - let pos = buf.len() - payload_len; - apply_mask(&mut buf[pos..], mask); - buf.into() - } else { - buf.put_slice(payload.as_ref()); - buf.into() - }; - - FramedMessage(binary) - } -} - -impl Default for Frame { - fn default() -> Frame { - Frame { - finished: true, - opcode: OpCode::Close, - payload: Binary::from(&b""[..]), - } - } -} - -impl fmt::Display for Frame { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - " - - final: {} - opcode: {} - payload length: {} - payload: 0x{} -", - self.finished, - self.opcode, - self.payload.len(), - self.payload - .as_ref() - .iter() - .map(|byte| format!("{:x}", byte)) - .collect::() - ) - } -} - -/// `WebSocket` message with framing. -#[derive(Debug)] -pub struct FramedMessage(pub(crate) Binary); - -#[cfg(test)] -mod tests { - use super::*; - use futures::stream::once; - - fn is_none(frm: &Poll, ProtocolError>) -> bool { - match *frm { - Ok(Async::Ready(None)) => true, - _ => false, - } - } - - fn extract(frm: Poll, ProtocolError>) -> Frame { - match frm { - Ok(Async::Ready(Some(frame))) => frame, - _ => unreachable!("error"), - } - } - - #[test] - fn test_parse() { - let mut buf = PayloadBuffer::new(once(Ok(BytesMut::from( - &[0b0000_0001u8, 0b0000_0001u8][..], - ).freeze()))); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); - - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); - buf.extend(b"1"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - - let frame = extract(Frame::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload.as_ref(), &b"1"[..]); - } - - #[test] - fn test_parse_length0() { - let buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - - let frame = extract(Frame::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert!(frame.payload.is_empty()); - } - - #[test] - fn test_parse_length2() { - let buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); - - let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - buf.extend(&[0u8, 4u8][..]); - buf.extend(b"1234"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - - let frame = extract(Frame::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload.as_ref(), &b"1234"[..]); - } - - #[test] - fn test_parse_length4() { - let buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - assert!(is_none(&Frame::parse(&mut buf, false, 1024))); - - let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); - buf.extend(b"1234"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - - let frame = extract(Frame::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload.as_ref(), &b"1234"[..]); - } - - #[test] - fn test_parse_frame_mask() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]); - buf.extend(b"0001"); - buf.extend(b"1"); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - - assert!(Frame::parse(&mut buf, false, 1024).is_err()); - - let frame = extract(Frame::parse(&mut buf, true, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload, vec![1u8].into()); - } - - #[test] - fn test_parse_frame_no_mask() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); - buf.extend(&[1u8]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - - assert!(Frame::parse(&mut buf, true, 1024).is_err()); - - let frame = extract(Frame::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload, vec![1u8].into()); - } - - #[test] - fn test_parse_frame_max_size() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); - buf.extend(&[1u8, 1u8]); - let mut buf = PayloadBuffer::new(once(Ok(buf.freeze()))); - - assert!(Frame::parse(&mut buf, true, 1).is_err()); - - if let Err(ProtocolError::Overflow) = Frame::parse(&mut buf, false, 0) { - } else { - unreachable!("error"); - } - } - - #[test] - fn test_ping_frame() { - let frame = Frame::message(Vec::from("data"), OpCode::Ping, true, false); - - let mut v = vec![137u8, 4u8]; - v.extend(b"data"); - assert_eq!(frame.0, v.into()); - } - - #[test] - fn test_pong_frame() { - let frame = Frame::message(Vec::from("data"), OpCode::Pong, true, false); - - let mut v = vec![138u8, 4u8]; - v.extend(b"data"); - assert_eq!(frame.0, v.into()); - } - - #[test] - fn test_close_frame() { - let reason = (CloseCode::Normal, "data"); - let frame = Frame::close(Some(reason.into()), false); - - let mut v = vec![136u8, 6u8, 3u8, 232u8]; - v.extend(b"data"); - assert_eq!(frame.0, v.into()); - } - - #[test] - fn test_empty_close_frame() { - let frame = Frame::close(None, false); - assert_eq!(frame.0, vec![0x88, 0x00].into()); - } -} diff --git a/src/ws/mask.rs b/src/ws/mask.rs deleted file mode 100644 index 18ce57bb..00000000 --- a/src/ws/mask.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![cfg_attr(feature = "cargo-clippy", allow(cast_ptr_alignment))] -use std::ptr::copy_nonoverlapping; -use std::slice; - -// Holds a slice guaranteed to be shorter than 8 bytes -struct ShortSlice<'a>(&'a mut [u8]); - -impl<'a> ShortSlice<'a> { - unsafe fn new(slice: &'a mut [u8]) -> Self { - // Sanity check for debug builds - debug_assert!(slice.len() < 8); - ShortSlice(slice) - } - fn len(&self) -> usize { - self.0.len() - } -} - -/// Faster version of `apply_mask()` which operates on 8-byte blocks. -#[inline] -#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] -pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { - // Extend the mask to 64 bits - let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); - // Split the buffer into three segments - let (head, mid, tail) = align_buf(buf); - - // Initial unaligned segment - let head_len = head.len(); - if head_len > 0 { - xor_short(head, mask_u64); - if cfg!(target_endian = "big") { - mask_u64 = mask_u64.rotate_left(8 * head_len as u32); - } else { - mask_u64 = mask_u64.rotate_right(8 * head_len as u32); - } - } - // Aligned segment - for v in mid { - *v ^= mask_u64; - } - // Final unaligned segment - if tail.len() > 0 { - xor_short(tail, mask_u64); - } -} - -#[inline] -// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so -// inefficient, it could be done better. The compiler does not understand that -// a `ShortSlice` must be smaller than a u64. -#[cfg_attr( - feature = "cargo-clippy", - allow(needless_pass_by_value) -)] -fn xor_short(buf: ShortSlice, mask: u64) { - // Unsafe: we know that a `ShortSlice` fits in a u64 - unsafe { - let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len()); - let mut b: u64 = 0; - #[allow(trivial_casts)] - copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); - b ^= mask; - #[allow(trivial_casts)] - copy_nonoverlapping(&b as *const _ as *const u8, ptr, len); - } -} - -#[inline] -// Unsafe: caller must ensure the buffer has the correct size and alignment -unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { - // Assert correct size and alignment in debug builds - debug_assert!(buf.len().trailing_zeros() >= 3); - debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3); - - slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) -} - -#[inline] -// Splits a slice into three parts: an unaligned short head and tail, plus an aligned -// u64 mid section. -fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { - let start_ptr = buf.as_ptr() as usize; - let end_ptr = start_ptr + buf.len(); - - // Round *up* to next aligned boundary for start - let start_aligned = (start_ptr + 7) & !0x7; - // Round *down* to last aligned boundary for end - let end_aligned = end_ptr & !0x7; - - if end_aligned >= start_aligned { - // We have our three segments (head, mid, tail) - let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr); - let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr); - - // Unsafe: we know the middle section is correctly aligned, and the outer - // sections are smaller than 8 bytes - unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) } - } else { - // We didn't cross even one aligned boundary! - - // Unsafe: The outer sections are smaller than 8 bytes - unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) } - } -} - -#[cfg(test)] -mod tests { - use super::apply_mask; - use byteorder::{ByteOrder, LittleEndian}; - - /// A safe unoptimized mask application. - fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { - for (i, byte) in buf.iter_mut().enumerate() { - *byte ^= mask[i & 3]; - } - } - - #[test] - fn test_apply_mask() { - let mask = [0x6d, 0xb6, 0xb2, 0x80]; - let mask_u32: u32 = LittleEndian::read_u32(&mask); - - let unmasked = vec![ - 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, - 0x74, 0xf9, 0x12, 0x03, - ]; - - // Check masking with proper alignment. - { - let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked, &mask); - - let mut masked_fast = unmasked.clone(); - apply_mask(&mut masked_fast, mask_u32); - - assert_eq!(masked, masked_fast); - } - - // Check masking without alignment. - { - let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked[1..], &mask); - - let mut masked_fast = unmasked.clone(); - apply_mask(&mut masked_fast[1..], mask_u32); - - assert_eq!(masked, masked_fast); - } - } -} diff --git a/src/ws/mod.rs b/src/ws/mod.rs deleted file mode 100644 index b0942c0d..00000000 --- a/src/ws/mod.rs +++ /dev/null @@ -1,477 +0,0 @@ -//! `WebSocket` support for Actix -//! -//! To setup a `WebSocket`, first do web socket handshake then on success -//! convert `Payload` into a `WsStream` stream and then use `WsWriter` to -//! communicate with the peer. -//! -//! ## Example -//! -//! ```rust -//! # extern crate actix_web; -//! # extern crate actix; -//! # use actix::prelude::*; -//! # use actix_web::*; -//! use actix_web::{ws, HttpRequest, HttpResponse}; -//! -//! // do websocket handshake and start actor -//! fn ws_index(req: &HttpRequest) -> Result { -//! ws::start(req, Ws) -//! } -//! -//! struct Ws; -//! -//! impl Actor for Ws { -//! type Context = ws::WebsocketContext; -//! } -//! -//! // Handler for ws::Message messages -//! impl StreamHandler for Ws { -//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { -//! match msg { -//! ws::Message::Ping(msg) => ctx.pong(&msg), -//! ws::Message::Text(text) => ctx.text(text), -//! ws::Message::Binary(bin) => ctx.binary(bin), -//! _ => (), -//! } -//! } -//! } -//! # -//! # fn main() { -//! # App::new() -//! # .resource("/ws/", |r| r.f(ws_index)) // <- register websocket route -//! # .finish(); -//! # } -//! ``` -use bytes::Bytes; -use futures::{Async, Poll, Stream}; -use http::{header, Method, StatusCode}; - -use super::actix::{Actor, StreamHandler}; - -use body::Binary; -use error::{Error, PayloadError, ResponseError}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder}; -use payload::PayloadBuffer; - -mod client; -mod context; -mod frame; -mod mask; -mod proto; - -pub use self::client::{ - Client, ClientError, ClientHandshake, ClientReader, ClientWriter, -}; -pub use self::context::WebsocketContext; -pub use self::frame::{Frame, FramedMessage}; -pub use self::proto::{CloseCode, CloseReason, OpCode}; - -/// Websocket protocol errors -#[derive(Fail, Debug)] -pub enum ProtocolError { - /// Received an unmasked frame from client - #[fail(display = "Received an unmasked frame from client")] - UnmaskedFrame, - /// Received a masked frame from server - #[fail(display = "Received a masked frame from server")] - MaskedFrame, - /// Encountered invalid opcode - #[fail(display = "Invalid opcode: {}", _0)] - InvalidOpcode(u8), - /// Invalid control frame length - #[fail(display = "Invalid control frame length: {}", _0)] - InvalidLength(usize), - /// Bad web socket op code - #[fail(display = "Bad web socket op code")] - BadOpCode, - /// A payload reached size limit. - #[fail(display = "A payload reached size limit.")] - Overflow, - /// Continuation is not supported - #[fail(display = "Continuation is not supported.")] - NoContinuation, - /// Bad utf-8 encoding - #[fail(display = "Bad utf-8 encoding.")] - BadEncoding, - /// Payload error - #[fail(display = "Payload error: {}", _0)] - Payload(#[cause] PayloadError), -} - -impl ResponseError for ProtocolError {} - -impl From for ProtocolError { - fn from(err: PayloadError) -> ProtocolError { - ProtocolError::Payload(err) - } -} - -/// Websocket handshake errors -#[derive(Fail, PartialEq, Debug)] -pub enum HandshakeError { - /// Only get method is allowed - #[fail(display = "Method not allowed")] - GetMethodRequired, - /// Upgrade header if not set to websocket - #[fail(display = "Websocket upgrade is expected")] - NoWebsocketUpgrade, - /// Connection header is not set to upgrade - #[fail(display = "Connection upgrade is expected")] - NoConnectionUpgrade, - /// Websocket version header is not set - #[fail(display = "Websocket version header is required")] - NoVersionHeader, - /// Unsupported websocket version - #[fail(display = "Unsupported version")] - UnsupportedVersion, - /// Websocket key is not set or wrong - #[fail(display = "Unknown websocket key")] - BadWebsocketKey, -} - -impl ResponseError for HandshakeError { - fn error_response(&self) -> HttpResponse { - match *self { - HandshakeError::GetMethodRequired => HttpResponse::MethodNotAllowed() - .header(header::ALLOW, "GET") - .finish(), - HandshakeError::NoWebsocketUpgrade => HttpResponse::BadRequest() - .reason("No WebSocket UPGRADE header found") - .finish(), - HandshakeError::NoConnectionUpgrade => HttpResponse::BadRequest() - .reason("No CONNECTION upgrade") - .finish(), - HandshakeError::NoVersionHeader => HttpResponse::BadRequest() - .reason("Websocket version header is required") - .finish(), - HandshakeError::UnsupportedVersion => HttpResponse::BadRequest() - .reason("Unsupported version") - .finish(), - HandshakeError::BadWebsocketKey => HttpResponse::BadRequest() - .reason("Handshake error") - .finish(), - } - } -} - -/// `WebSocket` Message -#[derive(Debug, PartialEq, Message)] -pub enum Message { - /// Text message - Text(String), - /// Binary message - Binary(Binary), - /// Ping message - Ping(String), - /// Pong message - Pong(String), - /// Close message with optional reason - Close(Option), -} - -/// Do websocket handshake and start actor -pub fn start(req: &HttpRequest, actor: A) -> Result -where - A: Actor> + StreamHandler, - S: 'static, -{ - let mut resp = handshake(req)?; - let stream = WsStream::new(req.payload()); - - let body = WebsocketContext::create(req.clone(), actor, stream); - Ok(resp.body(body)) -} - -/// Prepare `WebSocket` handshake response. -/// -/// This function returns handshake `HttpResponse`, ready to send to peer. -/// It does not perform any IO. -/// -// /// `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 { - // WebSocket accepts only GET - if *req.method() != Method::GET { - return Err(HandshakeError::GetMethodRequired); - } - - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - return Err(HandshakeError::NoWebsocketUpgrade); - } - - // Upgrade connection - if !req.upgrade() { - return Err(HandshakeError::NoConnectionUpgrade); - } - - // check supported version - if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) { - return Err(HandshakeError::NoVersionHeader); - } - let supported_ver = { - if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) { - hdr == "13" || hdr == "8" || hdr == "7" - } else { - false - } - }; - if !supported_ver { - return Err(HandshakeError::UnsupportedVersion); - } - - // check client handshake for validity - if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) { - return Err(HandshakeError::BadWebsocketKey); - } - let key = { - let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap(); - proto::hash_key(key.as_ref()) - }; - - Ok(HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) - .connection_type(ConnectionType::Upgrade) - .header(header::UPGRADE, "websocket") - .header(header::TRANSFER_ENCODING, "chunked") - .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) - .take()) -} - -/// Maps `Payload` stream into stream of `ws::Message` items -pub struct WsStream { - rx: PayloadBuffer, - closed: bool, - max_size: usize, -} - -impl WsStream -where - S: Stream, -{ - /// Create new websocket frames stream - pub fn new(stream: S) -> WsStream { - WsStream { - rx: PayloadBuffer::new(stream), - closed: false, - max_size: 65_536, - } - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } -} - -impl Stream for WsStream -where - S: Stream, -{ - type Item = Message; - type Error = ProtocolError; - - fn poll(&mut self) -> Poll, Self::Error> { - if self.closed { - return Ok(Async::Ready(None)); - } - - match Frame::parse(&mut self.rx, true, self.max_size) { - Ok(Async::Ready(Some(frame))) => { - let (finished, opcode, payload) = frame.unpack(); - - // continuation is not supported - if !finished { - self.closed = true; - return Err(ProtocolError::NoContinuation); - } - - match opcode { - OpCode::Continue => Err(ProtocolError::NoContinuation), - OpCode::Bad => { - self.closed = true; - Err(ProtocolError::BadOpCode) - } - OpCode::Close => { - self.closed = true; - let close_reason = Frame::parse_close_payload(&payload); - Ok(Async::Ready(Some(Message::Close(close_reason)))) - } - OpCode::Ping => Ok(Async::Ready(Some(Message::Ping( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Pong => Ok(Async::Ready(Some(Message::Pong( - String::from_utf8_lossy(payload.as_ref()).into(), - )))), - OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))), - OpCode::Text => { - let tmp = Vec::from(payload.as_ref()); - match String::from_utf8(tmp) { - Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(_) => { - self.closed = true; - Err(ProtocolError::BadEncoding) - } - } - } - } - } - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { - self.closed = true; - Err(e) - } - } - } -} - -/// Common writing methods for a websocket. -pub trait WsWriter { - /// Send a text - fn send_text>(&mut self, text: T); - /// Send a binary - fn send_binary>(&mut self, data: B); - /// Send a ping message - fn send_ping(&mut self, message: &str); - /// Send a pong message - fn send_pong(&mut self, message: &str); - /// Close the connection - fn send_close(&mut self, reason: Option); -} - -#[cfg(test)] -mod tests { - use super::*; - use http::{header, Method}; - use test::TestRequest; - - #[test] - fn test_handshake() { - let req = TestRequest::default().method(Method::POST).finish(); - assert_eq!( - HandshakeError::GetMethodRequired, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default().finish(); - assert_eq!( - HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header(header::UPGRADE, header::HeaderValue::from_static("test")) - .finish(); - assert_eq!( - HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ).finish(); - assert_eq!( - HandshakeError::NoConnectionUpgrade, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ).header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ).finish(); - assert_eq!( - HandshakeError::NoVersionHeader, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ).header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ).header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("5"), - ).finish(); - assert_eq!( - HandshakeError::UnsupportedVersion, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ).header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ).header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ).finish(); - assert_eq!( - HandshakeError::BadWebsocketKey, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ).header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ).header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ).header( - header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13"), - ).finish(); - assert_eq!( - StatusCode::SWITCHING_PROTOCOLS, - handshake(&req).unwrap().finish().status() - ); - } - - #[test] - fn test_wserror_http_response() { - let resp: HttpResponse = HandshakeError::GetMethodRequired.error_response(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: HttpResponse = HandshakeError::NoWebsocketUpgrade.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::NoConnectionUpgrade.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::NoVersionHeader.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::UnsupportedVersion.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: HttpResponse = HandshakeError::BadWebsocketKey.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } -} diff --git a/src/ws/proto.rs b/src/ws/proto.rs deleted file mode 100644 index 35fbbe3e..00000000 --- a/src/ws/proto.rs +++ /dev/null @@ -1,318 +0,0 @@ -use base64; -use sha1; -use std::convert::{From, Into}; -use std::fmt; - -use self::OpCode::*; -/// Operation codes as part of rfc6455. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum OpCode { - /// Indicates a continuation frame of a fragmented message. - Continue, - /// Indicates a text data frame. - Text, - /// Indicates a binary data frame. - Binary, - /// Indicates a close control frame. - Close, - /// Indicates a ping control frame. - Ping, - /// Indicates a pong control frame. - Pong, - /// Indicates an invalid opcode was received. - Bad, -} - -impl fmt::Display for OpCode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Continue => write!(f, "CONTINUE"), - Text => write!(f, "TEXT"), - Binary => write!(f, "BINARY"), - Close => write!(f, "CLOSE"), - Ping => write!(f, "PING"), - Pong => write!(f, "PONG"), - Bad => write!(f, "BAD"), - } - } -} - -impl Into for OpCode { - fn into(self) -> u8 { - match self { - Continue => 0, - Text => 1, - Binary => 2, - Close => 8, - Ping => 9, - Pong => 10, - Bad => { - debug_assert!( - false, - "Attempted to convert invalid opcode to u8. This is a bug." - ); - 8 // if this somehow happens, a close frame will help us tear down quickly - } - } - } -} - -impl From for OpCode { - fn from(byte: u8) -> OpCode { - match byte { - 0 => Continue, - 1 => Text, - 2 => Binary, - 8 => Close, - 9 => Ping, - 10 => Pong, - _ => Bad, - } - } -} - -use self::CloseCode::*; -/// Status code used to indicate why an endpoint is closing the `WebSocket` -/// connection. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum CloseCode { - /// Indicates a normal closure, meaning that the purpose for - /// which the connection was established has been fulfilled. - Normal, - /// Indicates that an endpoint is "going away", such as a server - /// going down or a browser having navigated away from a page. - Away, - /// Indicates that an endpoint is terminating the connection due - /// to a protocol error. - Protocol, - /// Indicates that an endpoint is terminating the connection - /// because it has received a type of data it cannot accept (e.g., an - /// endpoint that understands only text data MAY send this if it - /// receives a binary message). - Unsupported, - /// Indicates an abnormal closure. If the abnormal closure was due to an - /// error, this close code will not be used. Instead, the `on_error` method - /// of the handler will be called with the error. However, if the connection - /// is simply dropped, without an error, this close code will be sent to the - /// handler. - Abnormal, - /// Indicates that an endpoint is terminating the connection - /// because it has received data within a message that was not - /// consistent with the type of the message (e.g., non-UTF-8 [RFC3629] - /// data within a text message). - Invalid, - /// Indicates that an endpoint is terminating the connection - /// because it has received a message that violates its policy. This - /// is a generic status code that can be returned when there is no - /// other more suitable status code (e.g., Unsupported or Size) or if there - /// is a need to hide specific details about the policy. - Policy, - /// Indicates that an endpoint is terminating the connection - /// because it has received a message that is too big for it to - /// process. - Size, - /// Indicates that an endpoint (client) is terminating the - /// connection because it has expected the server to negotiate one or - /// more extension, but the server didn't return them in the response - /// message of the WebSocket handshake. The list of extensions that - /// are needed should be given as the reason for closing. - /// Note that this status code is not used by the server, because it - /// can fail the WebSocket handshake instead. - Extension, - /// Indicates that a server is terminating the connection because - /// it encountered an unexpected condition that prevented it from - /// fulfilling the request. - Error, - /// Indicates that the server is restarting. A client may choose to - /// reconnect, and if it does, it should use a randomized delay of 5-30 - /// seconds between attempts. - Restart, - /// Indicates that the server is overloaded and the client should either - /// connect to a different IP (when multiple targets exist), or - /// reconnect to the same IP when a user has performed an action. - Again, - #[doc(hidden)] - Tls, - #[doc(hidden)] - Other(u16), -} - -impl Into for CloseCode { - fn into(self) -> u16 { - match self { - Normal => 1000, - Away => 1001, - Protocol => 1002, - Unsupported => 1003, - Abnormal => 1006, - Invalid => 1007, - Policy => 1008, - Size => 1009, - Extension => 1010, - Error => 1011, - Restart => 1012, - Again => 1013, - Tls => 1015, - Other(code) => code, - } - } -} - -impl From for CloseCode { - fn from(code: u16) -> CloseCode { - match code { - 1000 => Normal, - 1001 => Away, - 1002 => Protocol, - 1003 => Unsupported, - 1006 => Abnormal, - 1007 => Invalid, - 1008 => Policy, - 1009 => Size, - 1010 => Extension, - 1011 => Error, - 1012 => Restart, - 1013 => Again, - 1015 => Tls, - _ => Other(code), - } - } -} - -#[derive(Debug, Eq, PartialEq, Clone)] -/// Reason for closing the connection -pub struct CloseReason { - /// Exit code - pub code: CloseCode, - /// Optional description of the exit code - pub description: Option, -} - -impl From for CloseReason { - fn from(code: CloseCode) -> Self { - CloseReason { - code, - description: None, - } - } -} - -impl> From<(CloseCode, T)> for CloseReason { - fn from(info: (CloseCode, T)) -> Self { - CloseReason { - code: info.0, - description: Some(info.1.into()), - } - } -} - -static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - -// TODO: hash is always same size, we dont need String -pub(crate) fn hash_key(key: &[u8]) -> String { - let mut hasher = sha1::Sha1::new(); - - hasher.update(key); - hasher.update(WS_GUID.as_bytes()); - - base64::encode(&hasher.digest().bytes()) -} - -#[cfg(test)] -mod test { - #![allow(unused_imports, unused_variables, dead_code)] - use super::*; - - macro_rules! opcode_into { - ($from:expr => $opcode:pat) => { - match OpCode::from($from) { - e @ $opcode => (), - e => unreachable!("{:?}", e), - } - }; - } - - macro_rules! opcode_from { - ($from:expr => $opcode:pat) => { - let res: u8 = $from.into(); - match res { - e @ $opcode => (), - e => unreachable!("{:?}", e), - } - }; - } - - #[test] - fn test_to_opcode() { - opcode_into!(0 => OpCode::Continue); - opcode_into!(1 => OpCode::Text); - opcode_into!(2 => OpCode::Binary); - opcode_into!(8 => OpCode::Close); - opcode_into!(9 => OpCode::Ping); - opcode_into!(10 => OpCode::Pong); - opcode_into!(99 => OpCode::Bad); - } - - #[test] - fn test_from_opcode() { - opcode_from!(OpCode::Continue => 0); - opcode_from!(OpCode::Text => 1); - opcode_from!(OpCode::Binary => 2); - opcode_from!(OpCode::Close => 8); - opcode_from!(OpCode::Ping => 9); - opcode_from!(OpCode::Pong => 10); - } - - #[test] - #[should_panic] - fn test_from_opcode_debug() { - opcode_from!(OpCode::Bad => 99); - } - - #[test] - fn test_from_opcode_display() { - assert_eq!(format!("{}", OpCode::Continue), "CONTINUE"); - assert_eq!(format!("{}", OpCode::Text), "TEXT"); - assert_eq!(format!("{}", OpCode::Binary), "BINARY"); - assert_eq!(format!("{}", OpCode::Close), "CLOSE"); - assert_eq!(format!("{}", OpCode::Ping), "PING"); - assert_eq!(format!("{}", OpCode::Pong), "PONG"); - assert_eq!(format!("{}", OpCode::Bad), "BAD"); - } - - #[test] - fn closecode_from_u16() { - assert_eq!(CloseCode::from(1000u16), CloseCode::Normal); - assert_eq!(CloseCode::from(1001u16), CloseCode::Away); - assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol); - assert_eq!(CloseCode::from(1003u16), CloseCode::Unsupported); - assert_eq!(CloseCode::from(1006u16), CloseCode::Abnormal); - assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid); - assert_eq!(CloseCode::from(1008u16), CloseCode::Policy); - assert_eq!(CloseCode::from(1009u16), CloseCode::Size); - assert_eq!(CloseCode::from(1010u16), CloseCode::Extension); - assert_eq!(CloseCode::from(1011u16), CloseCode::Error); - assert_eq!(CloseCode::from(1012u16), CloseCode::Restart); - assert_eq!(CloseCode::from(1013u16), CloseCode::Again); - assert_eq!(CloseCode::from(1015u16), CloseCode::Tls); - assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000)); - } - - #[test] - fn closecode_into_u16() { - assert_eq!(1000u16, Into::::into(CloseCode::Normal)); - assert_eq!(1001u16, Into::::into(CloseCode::Away)); - assert_eq!(1002u16, Into::::into(CloseCode::Protocol)); - assert_eq!(1003u16, Into::::into(CloseCode::Unsupported)); - assert_eq!(1006u16, Into::::into(CloseCode::Abnormal)); - assert_eq!(1007u16, Into::::into(CloseCode::Invalid)); - assert_eq!(1008u16, Into::::into(CloseCode::Policy)); - assert_eq!(1009u16, Into::::into(CloseCode::Size)); - assert_eq!(1010u16, Into::::into(CloseCode::Extension)); - assert_eq!(1011u16, Into::::into(CloseCode::Error)); - assert_eq!(1012u16, Into::::into(CloseCode::Restart)); - assert_eq!(1013u16, Into::::into(CloseCode::Again)); - assert_eq!(1015u16, Into::::into(CloseCode::Tls)); - assert_eq!(2000u16, Into::::into(CloseCode::Other(2000))); - } -} diff --git a/tests/identity.pfx b/tests/identity.pfx deleted file mode 100644 index 946e3b8b8ae10e19a11e7ac6eead66b12fff0014..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5549 zcmY+GRZtv&vTbn*Fa-DD5S(Fv;O=h0gF_fx!{8nycyM=jcXvr}cXtMNxH(n#z4P8j zS68jQyT2EE0A2|kEIfMvo;?yO<4>8N_ZYCqu-O54MhF3T`v0&tdjMMWe%lL7KUFAFV5{u;owkU`~uKqm}O*fBSo5RVYIG` zPAKF6ePO1*(FhW)-QpFAvtO?cVWGD0hs0WO4>qy>9T48E19Q`LuD0r#V<$Vj_c2yp z)4}gHs(AF<^Bo{R_`YId+=%k!c;why`_I0GXxv)FEmD)RG7Vs?g`z@xC^k*0+`Ps8 zZQH$QkvVVeOE(P8=g*0kvj|2!A! zCaUuJ^XO0NPlE$=oQRK9ti`Ic7JJnq;osnZ4OA`G7m{`HKP{cH&`YLS z&7cXaq1TKaOG&uTJqqY25!-%6M82yrFSuJv{`JhJUOE4ZMT1J%h8u3pSuQ$qle0}C z2}0Lk)3OpsFm1yeJ1|XW9gt0oX1PIiJ+HG_ccQbIH}C6hb|aPpRO(@Rt|u~DJIX%l zC+^x;>&_>s!^v1hwDp-biu2rspT|oc!3;MUJ>ui2i(Lamzq&B9OCcN`j&N`^wTcd1 z`DFH$T83h&1Ws&{N(khR##{-N3RSG@_ZPyM3SQ_TAXRRqyV#LUkE0&kEvE8Y61{fKJ+Y= z$eQ*1oOaeF^Z9A#-r@E7LmI)arBxsyT>V+(Ruq)&tBqzOi1sjBwNjPe_jraq(=2r^ zB|%@2bxrAnZr&d!&MNXZn&!iHPAs)aINufHy*7>&8Ap}7vY+Ji590{9Gq+KBpjkN0 zeCWDYHbCrNc%062ljm(+!6eM>ku}@T8$$TYjIT%~Y3z>YH2FxhEJ6lUL_#W3!=?ks zFo969*bgk}a!GRqXMSjjgd&?6Y%hBVuhL~74jyLlXNnJzvi?lWEo_HD7ZkXXB+vYf z**da2rqVV>3tDz^j`grt{G5Ye8`Q@KgQelPxGF%zLkZ=m7N>eor9pQmK6B$F&s9TaFeP0w?b5Q2p`A;be~m;x!mwj;I4ye%g!Op2Z^tb4S}i z#o}zOP0;F#mw3YEF_5DmxuBJQ{MZN!`^i6PwkgfA(n$bs9PQx6aZB5p$2&d$+`VjO zsH)tO=SExUR825&T#?}~JqRDb1{y6YsTMn zYINH#Ru0n1kxJc|=rVjd@`uh*nzIv3n@lt8^$P6GC5xIW1?7XLu5&GtX$Ho%rp5@a z<;*{8D{Rd@ocNX}STxWU(2moCw*4dI^{&HccPIwaXF^%V^P@gTFF*ow(`z_5$lNR7B+?<1E> zULlb}pDx~mAAb5{pg@(IH|PW09Pz6r7!}<9>(fAn8=3nRL|EV^f5o-!1D@`uY_E0= zXWciaPcKF60&?K+MAnU0gz}SuZb>wVAeRY>Lz9(&Zu_rB_JiKSV8HOqO3;vSD41G~ zIYpO4JcJ5K0+;O6x0VlNvNY%;v*XCt2V>-|%pxixP0jfH+xE^8^D3ZXs zS4AQ;$|oz)t4F{7(j7F7l{R}lJhwq)ekCh$|7`g4CXbIghAm6OSZ1L&K5-Y1w$mQa z_MBdP-vu45dbf@y=ls^2$D{4YS(f+0N}W49&$hLSRWl_WZB;uN`i7GQSU2{> z%tc5FX16D4Hc~M^2Q-}r|Th>@2!HT<`AbzLpW6XWl;-{}i)bscjNo9~Z zt>K*BFR$SlC#sBVi?$v)Y?HUyl)E2s`8W6ZXD9n+OhlM3@csT_+o|7G5+{-kChNBE zGoihxT`ld2`0D_n8Kx;B37>D}D7${tb4|NGV4*rXJY< zrP!d-L?ATkL~#^D#VB&5mZjsJsQ(E>4kqY74x%)7DdB0tEho2OB+t;4_6jhwgA|cO znS?z?-;5Y{h$UXY8kALQOM~}sKX{KB4C#6j4aK&Qk9w{6xr6GoWSrQBZ-V^FUe|IuEu|!Ho zP~_-_Q3K`aH^E3h7qd#rjmRUmA*ZPMaLSD!4zIf>se1cE%=z*bSCvx#B3|QUR$0#m9GAZ3L&y-$F1DIC& zT8gc<4SckFy#eY%&9U^Kz5>cD`L{z)t6x+lxMS@K!T}DzX*QtVExm3-I%(wjkMP#> z1(Cx)JgRslsAW|p`13(`X}#T<>eipuNW4PA4R2-@N+Vc&(y87pk_BSeS+d6)P$^TP z`?$x%WPFPWh9eb*vW#gb#gty{p~gAkXo)>T5Uf}c#r*~bw?jGhA9M7i8I5Xoby}f^ z^p4Qzi_ghn7*)HR6CmO5t+f(DYnCA3GX_!BFzYWFViu9Jn~Rh{W4lS>Ien~yruGbu z>(;O4jayyFSMEP1yQ9A{eQ^Hh(X@0+auu!ON=}-efn0d?U5LMh$zI#vo9nqvQ$058 z0%`dSn#OpIe|FctVo1fv^b^C(=MqC(usOYfc9zoBNAJEY_F!S;aKK8MEaXZ2?J0*q z%9PBkwW53|md4e|UjFDs!BwW$KTztoZy9^)QA!(U)itkV!h$GWP*eYJRt+x@x5V%i z1J7`#SEWSE_@>C3i32lz2g3U@e}8X+mAz+L`pS5%*YB7Dxr&GE4t*>KB6I zE-@2=x9^2U&Ux`Z5<+-#U+{a@#CzMU?Leqv9AhmP6iaWp>Oq!NsxHbS=EW1!zJLz{ zMUGYEB7n6q3Er#o<|Z`u0MwrUN4&EGP-_taP%Ho8(tlHkg!X?l`~xi9ztHXDeK*m&V~nDC|pLVujbH(b=fk1EfnQhXPjX1C48B*(7^BO>cFO~ zAH*SPpf#PD###N!E9oD!h87(U5h5>lHJLd?LymUDW$S z^HqD#EbaAcgn)aq3MVH(f*KmTMjHO5dW|_+katLspc=S!v^H3wli*n6ojf^qf3K=y zKr%Yay%^*Xvh0;}J@-TW@bcRzax<}Q{(@FjI+hfy@)|L*hscS(TttTvz_%Nv+(z8GGr?Ic>si}%oD zjS`B@EE+;f!(VuH>B7DBvpXijW%<{YjhQlo9fhwvuO{nH%@)BNiq<1`YY&2FCcm1v z)V&H!Kcj^3Bt0y%<I%;IH=P zJ41oky-lS{I9?XOY~Id^5UX<7RGwt!+GLyLENxHkj-snrrD9wg(faIi6I{jBh1go{ zM*ViN9ui7wip-=5;2)#r3!%>8z?BacE<1_@ALhkFNVIX%-ABNFWSr=jiLQ6%-{KRS zWwq<$wd1t!5~>t;g)EsTUt{vq;Fq-VE-~ml6ZXxgTNfUz_@@T7 zjsLv05k4~6I(7sOoX;Me9Mx*!2n`*&G62*4lL#eRXMj)}# zKr96@p5g@T(q@%oV#NRfPt?05WUo1wQk&aO{Pmwj7rdpbgcv6`Z78Sujv^Cw!}t9r`^Lc8t$WvLza7iBfkmc zDDY9Tit~6VzqS0L=Ayqpm%buj8Ag(=8<}4__o+jk*7sRDS~t>ecLxvu9W`08b;!5* zVykt!s+BvMXq)=q=y6Z4d=}r*-a;}j-*Qyy?4M4cOLy-!>N4}f4H%^$+ju=$d;iJ7 zPw8!Js)`{-TTglxv%Sm^K45gm#!5u2ni5W%dv9_+kEJG*<)*>LnTIO3 zP6HOD&&FrUjtF+byLCKHD-ACb%_vi{l0xPj|E3=MNnc)wn90{jb#I)vL$jsxtHlPu zCN(WOd)y*If;(Pc2D_T*H8TGC6#FjyA&L=1eB$U3^IK?5o$tR7-J`4e7hlpb7w~C+Agyessj{qzudSzQHf)p zE3bG;_o5JE@|R;^XOzYSMRqL`ey& z7cxd^&=J@~sxSdNJ~4`WKG3Pf5(-q%1&@Bp@h^T2p}7IVJL}{Ir#}ZYrY(`qOdx+V z!4-keXG9fPwZ72Z>0InsQ!U51)YwT!#5(o7Q73y)O7Vj^xant+Mw#45BH$9?B1vV- zK-S5Bmx_T9S^3JTc)fx@Q&R72RSU{u74roB{&3hlzknbj{dy%m?DxD5GH*40Tk$q; zDxcEj{-~XXePdPnzs!=S2lkV$IweaW8lBM@z7iZlAu5N%pYus=+D*DQyFs=GYfXl#{yYa^Ur z^wdzws}mB6Y~FeN1FIrbx)Pe=LH~}x^wgbl7Mnn)HlS|~MKWejJ*=#vF+_%p7!)FC z*>GliQit=X=~)<^UO-kl>$hKAhy@PbN%T6E8AFDRIPqP?3nKbu9SC1*x-_q%oa>un z)jigLjSg;Ie=Sf4&{X0(O6asc*}f}KP)&dmbcEmjIu<#h|51e3-$TXpM$ACpy4wx^ z7~&b%^6mSNlP@DXnZzWuf4O5+N;qfkl$8#UJw?Z-lL=nl=k*Qj5K!X_jNe5*ceN|1 zJO(G;HL??jza#IvW7CL&O%M}9wD`Q<7M?twO`H?+^WCB~JG8Y5`NHaesh58pW(2{G0QEPc8si#*pBZg zbmb#%Lx#-6A2aE}_`5AxI8xhZ#FL@NXPYMwK!#-*abrgDL>W=V)NBx_S5!i=ATh~B zoT!rujGkse$2dK9!A$E8ILE})=TNqjus$Y5VZgwNlvHx9Q@B&7X378MKMHcNc9XmD z{1dA1N8T7+{i%3V@mO!?q$Lgg=zTVwG9vk)dN9s4cwe diff --git a/tests/test space.binary b/tests/test space.binary deleted file mode 100644 index ef8ff024..00000000 --- a/tests/test space.binary +++ /dev/null @@ -1 +0,0 @@ -ÂTÇ‘É‚Vù2þvI ª–\ÇRË™–ˆæeÞvDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.– û®õpA !ûߦÙx j+Uc÷±©X”c%Û;ï"yì­AI \ No newline at end of file diff --git a/tests/test.binary b/tests/test.binary deleted file mode 100644 index ef8ff024..00000000 --- a/tests/test.binary +++ /dev/null @@ -1 +0,0 @@ -ÂTÇ‘É‚Vù2þvI ª–\ÇRË™–ˆæeÞvDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.– û®õpA !ûߦÙx j+Uc÷±©X”c%Û;ï"yì­AI \ No newline at end of file diff --git a/tests/test.png b/tests/test.png deleted file mode 100644 index 6b7cdc0b8fb5a439d3bd19f210e89a37a2354e61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>Fdh=h((rL&%EBHDibIqn;8;O;+&tGo0?Yw HttpResponse::Ok().finish(), - None => HttpResponse::BadRequest().finish(), - }) - }); - - let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); - - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_no_decompress() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - - // POST - let request = srv.post().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - let bytes = srv.execute(response.body()).unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_gzip_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_gzip_encoding_large() { - let data = STR.repeat(10); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(100_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(unix, feature = "uds"))] -#[test] -fn test_compatible_with_unix_socket_stream() { - let (stream, _) = tokio_uds::UnixStream::pair().unwrap(); - let _ = client::Connection::from_stream(stream); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_brotli_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .client(http::Method::POST, "/") - .content_encoding(http::ContentEncoding::Br) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_brotli_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(70_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(move |bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .client(http::Method::POST, "/") - .content_encoding(http::ContentEncoding::Br) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_deflate_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_client_deflate_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(70_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(bytes)) - }).responder() - }) - }); - - // client request - let request = srv - .post() - .content_encoding(http::ContentEncoding::Deflate) - .body(data.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_client_streaming_explicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .map_err(Error::from) - .and_then(|body| { - Ok(HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Identity) - .body(body)) - }).responder() - }) - }); - - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - - let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_streaming_implicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_client_cookie_handling() { - use actix_web::http::Cookie; - fn err() -> Error { - use std::io::{Error as IoError, ErrorKind}; - // stub some generic error - Error::from(IoError::from(ErrorKind::NotFound)) - } - let cookie1 = Cookie::build("cookie1", "value1").finish(); - let cookie2 = Cookie::build("cookie2", "value2") - .domain("www.example.org") - .path("/") - .secure(true) - .http_only(true) - .finish(); - // Q: are all these clones really necessary? A: Yes, possibly - let cookie1b = cookie1.clone(); - let cookie2b = cookie2.clone(); - let mut srv = test::TestServer::new(move |app| { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); - app.handler(move |req: &HttpRequest| { - // Check cookies were sent correctly - req.cookie("cookie1") - .ok_or_else(err) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(err()) - } - }).and_then(|()| req.cookie("cookie2").ok_or_else(err)) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(err()) - } - }) - // Send some cookies back - .map(|_| { - HttpResponse::Ok() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - }) - }) - }); - - let request = srv - .get() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, cookie1); - let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, cookie2); -} - -#[test] -fn test_default_headers() { - let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - - let request = srv.get().finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(repr.contains(concat!( - "\"user-agent\": \"actix-web/", - env!("CARGO_PKG_VERSION"), - "\"" - ))); - - let request_override = srv - .get() - .header("User-Agent", "test") - .header("Accept-Encoding", "over_test") - .finish() - .unwrap(); - let repr_override = format!("{:?}", request_override); - assert!(repr_override.contains("\"user-agent\": \"test\"")); - assert!(repr_override.contains("\"accept-encoding\": \"over_test\"")); - assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\"")); - assert!(!repr_override.contains(concat!( - "\"user-agent\": \"Actix-web/", - env!("CARGO_PKG_VERSION"), - "\"" - ))); -} - -#[test] -fn client_read_until_eof() { - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - let lst = net::TcpListener::bind(addr).unwrap(); - - for stream in lst.incoming() { - let mut stream = stream.unwrap(); - let mut b = [0; 1000]; - let _ = stream.read(&mut b).unwrap(); - let _ = stream - .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); - } - }); - - let mut sys = actix::System::new("test"); - - // client request - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = sys.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"welcome!")); -} diff --git a/tests/test_custom_pipeline.rs b/tests/test_custom_pipeline.rs deleted file mode 100644 index 6b5df00e..00000000 --- a/tests/test_custom_pipeline.rs +++ /dev/null @@ -1,81 +0,0 @@ -extern crate actix; -extern crate actix_net; -extern crate actix_web; - -use std::{thread, time}; - -use actix::System; -use actix_net::server::Server; -use actix_net::service::NewServiceExt; -use actix_web::server::{HttpService, KeepAlive, ServiceConfig, StreamConfiguration}; -use actix_web::{client, http, test, App, HttpRequest}; - -#[test] -fn test_custom_pipeline() { - let addr = test::TestServer::unused_addr(); - - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - StreamConfiguration::new() - .nodelay(true) - .tcp_keepalive(Some(time::Duration::from_secs(10))) - .and_then(HttpService::new(settings)) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} - -#[test] -fn test_h1() { - use actix_web::server::H1Service; - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - Server::new() - .bind("test", addr, move || { - let app = App::new() - .route("/", http::Method::GET, |_: HttpRequest| "OK") - .finish(); - let settings = ServiceConfig::build(app) - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_shutdown(1000) - .server_hostname("localhost") - .server_address(addr) - .finish(); - - H1Service::new(settings) - }).unwrap() - .run(); - }); - - let mut sys = System::new("test"); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = sys.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } -} diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs deleted file mode 100644 index debc1626..00000000 --- a/tests/test_handlers.rs +++ /dev/null @@ -1,677 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -extern crate h2; -extern crate http; -extern crate tokio_timer; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; - -use std::io; -use std::time::{Duration, Instant}; - -use actix_web::*; -use bytes::Bytes; -use futures::Future; -use http::StatusCode; -use serde_json::Value; -use tokio_timer::Delay; - -#[derive(Deserialize)] -struct PParam { - username: String, -} - -#[test] -fn test_path_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.with(|p: Path| format!("Welcome {}!", p.username)) - }); - }); - - // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); -} - -#[test] -fn test_async_handler() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|p: Path| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| Ok(format!("Welcome {}!", p.username))) - .responder() - }) - }); - }); - - // client request - let request = srv.get().uri(srv.url("/test/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); -} - -#[test] -fn test_query_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/index.html", |r| { - r.with(|p: Query| format!("Welcome {}!", p.username)) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/index.html?username=test")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); - - // client request - let request = srv.get().uri(srv.url("/index.html")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[derive(Deserialize, Debug)] -pub enum ResponseType { - Token, - Code, -} - -#[derive(Debug, Deserialize)] -pub struct AuthRequest { - id: u64, - response_type: ResponseType, -} - -#[test] -fn test_query_enum_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/index.html", |r| { - r.with(|p: Query| format!("{:?}", p.into_inner())) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/index.html?id=64&response_type=Code")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"AuthRequest { id: 64, response_type: Code }") - ); - - let request = srv - .get() - .uri(srv.url("/index.html?id=64&response_type=Co")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv - .get() - .uri(srv.url("/index.html?response_type=Code")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_async_extractor_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|data: Json| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| Ok(format!("{}", data.0))) - .responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"{\"test\":1}")); -} - -#[derive(Deserialize, Serialize)] -struct FormData { - username: String, -} - -#[test] -fn test_form_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|form: Form| format!("{}", form.username)) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .form(FormData { - username: "test".to_string(), - }).unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"test")); -} - -#[test] -fn test_form_extractor2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_config( - |form: Form| format!("{}", form.username), - |cfg| { - cfg.0.error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ).into() - }); - }, - ); - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/x-www-form-urlencoded") - .body("918237129hdk:D:D:D:D:D:DjASHDKJhaswkjeq") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_client_error()); -} - -#[test] -fn test_path_and_query_extractor() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(p, q): (Path, Query)| { - format!("Welcome {} - {}!", p.username, q.username) - }) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|(_r, p, q): (HttpRequest, Path, Query)| { - format!("Welcome {} - {}!", p.username, q.username) - }) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html?username=test2")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - test2!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with( - |(p, _q, data): (Path, Query, Json)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }, - ) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); -} - -#[test] -fn test_path_and_query_extractor3_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(p, data): (Path, Json)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_path_and_query_extractor4_async() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with(|(data, p): (Json, Path)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_path_and_query_extractor2_async2() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with( - |(p, data, _q): (Path, Json, Query)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }, - ) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async3() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|data: Json, p: Path, _: Query| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", p.username, data.0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_path_and_query_extractor2_async4() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route() - .with(|data: (Json, Path, Query)| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(move |_| { - Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0)) - }).responder() - }) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_scope_and_path_extractor() { - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/sc", |scope| { - scope.resource("/{num}/index.html", |r| { - r.route() - .with(|p: Path<(usize,)>| format!("Welcome {}!", p.0)) - }) - }) - }); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome 10!")); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[test] -fn test_nested_scope_and_path_extractor() { - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/sc", |scope| { - scope.nested("/{num}", |scope| { - scope.resource("/{num}/index.html", |r| { - r.route().with(|p: Path<(usize, usize)>| { - format!("Welcome {} {}!", p.0, p.1) - }) - }) - }) - }) - }); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/12/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!")); - - // client request - let request = srv - .get() - .uri(srv.url("/sc/10/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); -} - -#[cfg(actix_impl_trait)] -fn test_impl_trait( - data: (Json, Path, Query), -) -> impl Future { - Delay::new(Instant::now() + Duration::from_millis(10)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) - .and_then(move |_| Ok(format!("Welcome {} - {}!", data.1.username, (data.0).0))) -} - -#[cfg(actix_impl_trait)] -fn test_impl_trait_err( - _data: (Json, Path, Query), -) -> impl Future { - Delay::new(Instant::now() + Duration::from_millis(10)) - .map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout")) - .and_then(move |_| Err(io::Error::new(io::ErrorKind::Other, "other"))) -} - -#[cfg(actix_impl_trait)] -#[test] -fn test_path_and_query_extractor2_async4_impl_trait() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_async(test_impl_trait) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"Welcome test1 - {\"test\":1}!")); - - // client request - let request = srv - .get() - .uri(srv.url("/test1/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); -} - -#[cfg(actix_impl_trait)] -#[test] -fn test_path_and_query_extractor2_async4_impl_trait_err() { - let mut srv = test::TestServer::new(|app| { - app.resource("/{username}/index.html", |r| { - r.route().with_async(test_impl_trait_err) - }); - }); - - // client request - let request = srv - .post() - .uri(srv.url("/test1/index.html?username=test2")) - .header("content-type", "application/json") - .body("{\"test\": 1}") - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[test] -fn test_non_ascii_route() { - let mut srv = test::TestServer::new(|app| { - app.resource("/中文/index.html", |r| r.f(|_| "success")); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/中文/index.html")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"success")); -} - -#[test] -fn test_unsafe_path_route() { - let mut srv = test::TestServer::new(|app| { - app.resource("/test/{url}", |r| { - r.f(|r| format!("success: {}", &r.match_info()["url"])) - }); - }); - - // client request - let request = srv - .get() - .uri(srv.url("/test/http%3A%2F%2Fexample.com")) - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!( - bytes, - Bytes::from_static(b"success: http%3A%2F%2Fexample.com") - ); -} diff --git a/tests/test_middleware.rs b/tests/test_middleware.rs deleted file mode 100644 index 6cb6ee36..00000000 --- a/tests/test_middleware.rs +++ /dev/null @@ -1,1055 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate futures; -extern crate tokio_timer; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::thread; -use std::time::{Duration, Instant}; - -use actix_web::error::{Error, ErrorInternalServerError}; -use actix_web::*; -use futures::{future, Future}; -use tokio_timer::Delay; - -struct MiddlewareTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareTest { - fn start(&self, _: &HttpRequest) -> Result { - self.start - .store(self.start.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - Ok(middleware::Started::Done) - } - - fn response( - &self, _: &HttpRequest, resp: HttpResponse, - ) -> Result { - self.response - .store(self.response.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - Ok(middleware::Response::Done(resp)) - } - - fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { - self.finish - .store(self.finish.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - middleware::Finished::Done - } -} - -#[test] -fn test_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut 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(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_resource_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut 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(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_scope_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/", |r| { - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", |r| { - r.middleware(mw); - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_handler() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| { - r.route().a(|_| { - Delay::new(Instant::now() + Duration::from_millis(10)) - .and_then(|_| Ok(HttpResponse::Ok())) - }) - }) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -fn index_test_middleware_async_error(_: &HttpRequest) -> FutureResponse { - future::result(Err(error::ErrorBadRequest("TEST"))).responder() -} - -#[test] -fn test_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).handler(index_test_middleware_async_error) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }).resource("/test", |r| r.f(index_test_middleware_async_error)) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_error() { - let req = Arc::new(AtomicUsize::new(0)); - let resp = Arc::new(AtomicUsize::new(0)); - let fin = Arc::new(AtomicUsize::new(0)); - - let act_req = Arc::clone(&req); - let act_resp = Arc::clone(&resp); - let act_fin = Arc::clone(&fin); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_req), - response: Arc::clone(&act_resp), - finish: Arc::clone(&act_fin), - }; - - App::new().resource("/test", move |r| { - r.middleware(mw); - r.f(index_test_middleware_async_error); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - assert_eq!(req.load(Ordering::Relaxed), 1); - assert_eq!(resp.load(Ordering::Relaxed), 1); - assert_eq!(fin.load(Ordering::Relaxed), 1); -} - -struct MiddlewareAsyncTest { - start: Arc, - response: Arc, - finish: Arc, -} - -impl middleware::Middleware for MiddlewareAsyncTest { - fn start(&self, _: &HttpRequest) -> Result { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let start = Arc::clone(&self.start); - Ok(middleware::Started::Future(Box::new( - to.from_err().and_then(move |_| { - start.fetch_add(1, Ordering::Relaxed); - Ok(None) - }), - ))) - } - - fn response( - &self, _: &HttpRequest, resp: HttpResponse, - ) -> Result { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let response = Arc::clone(&self.response); - Ok(middleware::Response::Future(Box::new( - to.from_err().and_then(move |_| { - response.fetch_add(1, Ordering::Relaxed); - Ok(resp) - }), - ))) - } - - fn finish(&self, _: &HttpRequest, _: &HttpResponse) -> middleware::Finished { - let to = Delay::new(Instant::now() + Duration::from_millis(10)); - - let finish = Arc::clone(&self.finish); - middleware::Finished::Future(Box::new(to.from_err().and_then(move |_| { - finish.fetch_add(1, Ordering::Relaxed); - Ok(()) - }))) - } -} - -#[test] -fn test_async_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::new(move |app| { - app.middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(50)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_sync_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(50)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_scope_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_async_scope_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - App::new().scope("/scope", |scope| { - scope - .middleware(MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).middleware(MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }).resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(20)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_resource_middleware() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_async_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - let mw2 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(mw2); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -#[test] -fn test_async_sync_resource_middleware_multiple() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareAsyncTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - let mw2 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(mw2); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - assert_eq!(num1.load(Ordering::Relaxed), 2); - assert_eq!(num2.load(Ordering::Relaxed), 2); - - thread::sleep(Duration::from_millis(40)); - assert_eq!(num3.load(Ordering::Relaxed), 2); -} - -struct MiddlewareWithErr; - -impl middleware::Middleware for MiddlewareWithErr { - fn start(&self, _: &HttpRequest) -> Result { - Err(ErrorInternalServerError("middleware error")) - } -} - -struct MiddlewareAsyncWithErr; - -impl middleware::Middleware for MiddlewareAsyncWithErr { - fn start(&self, _: &HttpRequest) -> Result { - Ok(middleware::Started::Future(Box::new(future::err( - ErrorInternalServerError("middleware error"), - )))) - } -} - -#[test] -fn test_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new() - .middleware(mw1) - .middleware(MiddlewareWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new() - .middleware(mw1) - .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().scope("/scope", |scope| { - scope - .middleware(mw1) - .middleware(MiddlewareWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_scope_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().scope("/scope", |scope| { - scope - .middleware(mw1) - .middleware(MiddlewareAsyncWithErr) - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - }) - }); - - let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(MiddlewareWithErr); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[test] -fn test_resource_middleware_async_chain_with_error() { - let num1 = Arc::new(AtomicUsize::new(0)); - let num2 = Arc::new(AtomicUsize::new(0)); - let num3 = Arc::new(AtomicUsize::new(0)); - - let act_num1 = Arc::clone(&num1); - let act_num2 = Arc::clone(&num2); - let act_num3 = Arc::clone(&num3); - - let mut srv = test::TestServer::with_factory(move || { - let mw1 = MiddlewareTest { - start: Arc::clone(&act_num1), - response: Arc::clone(&act_num2), - finish: Arc::clone(&act_num3), - }; - App::new().resource("/test", move |r| { - r.middleware(mw1); - r.middleware(MiddlewareAsyncWithErr); - r.f(|_| HttpResponse::Ok()); - }) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - srv.execute(request.send()).unwrap(); - - assert_eq!(num1.load(Ordering::Relaxed), 1); - assert_eq!(num2.load(Ordering::Relaxed), 1); - assert_eq!(num3.load(Ordering::Relaxed), 1); -} - -#[cfg(feature = "session")] -#[test] -fn test_session_storage_middleware() { - use actix_web::middleware::session::{ - CookieSessionBackend, RequestSession, SessionStorage, - }; - - const SIMPLE_NAME: &'static str = "simple"; - const SIMPLE_PAYLOAD: &'static str = "kantan"; - const COMPLEX_NAME: &'static str = "test"; - const COMPLEX_PAYLOAD: &'static str = "url=https://test.com&generate_204"; - //TODO: investigate how to handle below input - //const COMPLEX_PAYLOAD: &'static str = "FJc%26continue_url%3Dhttp%253A%252F%252Fconnectivitycheck.gstatic.com%252Fgenerate_204"; - - let mut srv = test::TestServer::with_factory(move || { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/index", move |r| { - r.f(|req| { - let res = req.session().set(COMPLEX_NAME, COMPLEX_PAYLOAD); - assert!(res.is_ok()); - let value = req.session().get::(COMPLEX_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); - - let res = req.session().set(SIMPLE_NAME, SIMPLE_PAYLOAD); - assert!(res.is_ok()); - let value = req.session().get::(SIMPLE_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); - - HttpResponse::Ok() - }) - }).resource("/expect_cookie", move |r| { - r.f(|req| { - let _cookies = req.cookies().expect("To get cookies"); - - let value = req.session().get::(SIMPLE_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), SIMPLE_PAYLOAD); - - let value = req.session().get::(COMPLEX_NAME); - assert!(value.is_ok()); - let value = value.unwrap(); - assert!(value.is_some()); - assert_eq!(value.unwrap(), COMPLEX_PAYLOAD); - - HttpResponse::Ok() - }) - }) - }); - - let request = srv.get().uri(srv.url("/index")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert!(response.headers().contains_key("set-cookie")); - let set_cookie = response.headers().get("set-cookie"); - assert!(set_cookie.is_some()); - let set_cookie = set_cookie.unwrap().to_str().expect("Convert to str"); - - let request = srv - .get() - .uri(srv.url("/expect_cookie")) - .header("cookie", set_cookie.split(';').next().unwrap()) - .finish() - .unwrap(); - - srv.execute(request.send()).unwrap(); -} diff --git a/tests/test_server.rs b/tests/test_server.rs index f3c9bf9d..2d01d270 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,48 +1,18 @@ -extern crate actix; -extern crate actix_net; -extern crate actix_web; -#[cfg(feature = "brotli")] -extern crate brotli2; -extern crate bytes; -extern crate flate2; -extern crate futures; -extern crate h2; -extern crate http as modhttp; -extern crate rand; -extern crate tokio; -extern crate tokio_current_thread; -extern crate tokio_current_thread as current_thread; -extern crate tokio_reactor; -extern crate tokio_tcp; - -#[cfg(feature = "tls")] -extern crate native_tls; -#[cfg(feature = "ssl")] -extern crate openssl; -#[cfg(feature = "rust-tls")] -extern crate rustls; - use std::io::{Read, Write}; -use std::sync::Arc; -use std::{thread, time}; -#[cfg(feature = "brotli")] -use brotli2::write::{BrotliDecoder, BrotliEncoder}; -use bytes::{Bytes, BytesMut}; +use actix_http::http::header::{ + ContentEncoding, ACCEPT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, +}; +use actix_http::{h1, Error, HttpMessage, Response}; +use actix_http_test::TestServer; +use brotli2::write::BrotliDecoder; +use bytes::Bytes; use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; -use flate2::Compression; -use futures::stream::once; -use futures::{Future, Stream}; -use h2::client as h2client; -use modhttp::Request; -use rand::distributions::Alphanumeric; -use rand::Rng; -use tokio::runtime::current_thread::Runtime; -use tokio_current_thread::spawn; -use tokio_tcp::TcpStream; +use flate2::write::ZlibDecoder; +use futures::stream::once; //Future, Stream +use rand::{distributions::Alphanumeric, Rng}; -use actix_web::*; +use actix_web2::{middleware, App}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -66,240 +36,35 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[test] -#[cfg(unix)] -fn test_start() { - use actix::System; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.start(); - let _ = tx.send((addr, srv_addr, System::current())); - }); - }); - let (addr, srv_addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - // pause - let _ = srv_addr.send(server::PauseServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .timeout(time::Duration::from_millis(200)) - .finish() - .unwrap(); - assert!(rt.block_on(req.send()).is_err()); - } - - // resume - let _ = srv_addr.send(server::ResumeServer).wait(); - thread::sleep(time::Duration::from_millis(200)); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - let _ = sys.stop(); -} - -#[test] -#[cfg(unix)] -fn test_shutdown() { - use actix::System; - use std::net; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - let srv_addr = srv.shutdown_timeout(1).start(); - let _ = tx.send((addr, srv_addr, System::current())); - }); - }); - let (addr, srv_addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - srv_addr.do_send(server::StopServer { graceful: true }); - assert!(response.status().is_success()); - } - - thread::sleep(time::Duration::from_millis(1000)); - assert!(net::TcpStream::connect(addr).is_err()); - - let _ = sys.stop(); -} - -#[test] -#[cfg(unix)] -fn test_panic() { - use actix::System; - use std::sync::mpsc; - - let _ = test::TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(|| { - System::run(move || { - let srv = server::new(|| { - App::new() - .resource("/panic", |r| { - r.method(http::Method::GET).f(|_| -> &'static str { - panic!("error"); - }); - }).resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }).workers(1); - - let srv = srv.bind("127.0.0.1:0").unwrap(); - let addr = srv.addrs()[0]; - srv.start(); - let _ = tx.send((addr, System::current())); - }); - }); - let (addr, sys) = rx.recv().unwrap(); - System::set_current(sys.clone()); - - let mut rt = Runtime::new().unwrap(); - { - let req = client::ClientRequest::get(format!("http://{}/panic", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()); - assert!(response.is_err()); - } - - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()); - assert!(response.is_err()); - } - { - let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) - .finish() - .unwrap(); - let response = rt.block_on(req.send()).unwrap(); - assert!(response.status().is_success()); - } - - let _ = sys.stop(); -} - -#[test] -fn test_simple() { - let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok())); - let req = srv.get().finish().unwrap(); - let response = srv.execute(req.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_headers() { - let data = STR.repeat(10); - let srv_data = Arc::new(data.clone()); - let mut srv = test::TestServer::new(move |app| { - let data = srv_data.clone(); - app.handler(move |_| { - let mut builder = HttpResponse::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - builder.body(data.as_ref()) - }) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - #[test] fn test_body() { - let mut srv = - test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + let mut srv = TestServer::new(|| { + h1::H1Service::new( + App::new().resource("/", |r| r.get(|| Response::Ok().body(STR))), + ) + }); let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[test] fn test_body_gzip() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(STR) - }) + let mut srv = TestServer::new(|| { + h1::H1Service::new( + App::new() + .middleware(middleware::Compress::new(ContentEncoding::Gzip)) + .resource("/", |r| r.get(|| Response::Ok().body(STR))), + ) }); - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let request = srv.get().finish().unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); // read response @@ -315,19 +80,19 @@ fn test_body_gzip() { #[test] fn test_body_gzip_large() { let data = STR.repeat(10); - let srv_data = Arc::new(data.clone()); + let srv_data = data.clone(); - let mut srv = test::TestServer::new(move |app| { + let mut srv = TestServer::new(move || { let data = srv_data.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()) - }) + h1::H1Service::new( + App::new() + .middleware(middleware::Compress::new(ContentEncoding::Gzip)) + .resource("/", |r| r.get(move || Response::Ok().body(data.clone()))), + ) }); - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let request = srv.get().finish().unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); // read response @@ -346,19 +111,19 @@ fn test_body_gzip_large_random() { .sample_iter(&Alphanumeric) .take(70_000) .collect::(); - let srv_data = Arc::new(data.clone()); + let srv_data = data.clone(); - let mut srv = test::TestServer::new(move |app| { + let mut srv = TestServer::new(move || { let data = srv_data.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(data.as_ref()) - }) + h1::H1Service::new( + App::new() + .middleware(middleware::Compress::new(ContentEncoding::Gzip)) + .resource("/", |r| r.get(move || Response::Ok().body(data.clone()))), + ) }); - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let request = srv.get().finish().unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); // read response @@ -374,18 +139,27 @@ fn test_body_gzip_large_random() { #[test] fn test_body_chunked_implicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new() + .middleware(middleware::Compress::new(ContentEncoding::Gzip)) + .resource("/", |r| { + r.get(move || { + Response::Ok().streaming(once(Ok::<_, Error>( + Bytes::from_static(STR.as_ref()), + ))) + }) + }), + ) }); - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let request = srv.get().finish().unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); + assert_eq!( + response.headers().get(TRANSFER_ENCODING).unwrap(), + &b"chunked"[..] + ); // read response let bytes = srv.execute(response.body()).unwrap(); @@ -397,20 +171,24 @@ fn test_body_chunked_implicit() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "brotli")] #[test] fn test_body_br_streaming() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(Body::Streaming(Box::new(body))) - }) + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new() + .middleware(middleware::Compress::new(ContentEncoding::Br)) + .resource("/", |r| { + r.get(move || { + Response::Ok().streaming(once(Ok::<_, Error>( + Bytes::from_static(STR.as_ref()), + ))) + }) + }), + ) }); - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let request = srv.get().header(ACCEPT_ENCODING, "br").finish().unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); // read response @@ -423,49 +201,20 @@ fn test_body_br_streaming() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -#[test] -fn test_head_empty() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| HttpResponse::Ok().content_length(STR.len() as u64).finish()) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert!(bytes.is_empty()); -} - #[test] fn test_head_binary() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .content_length(100) - .body(STR) - }) + let mut srv = TestServer::new(move || { + h1::H1Service::new(App::new().resource("/", |r| { + r.head(move || Response::Ok().content_length(100).body(STR)) + })) }); let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); + let len = response.headers().get(CONTENT_LENGTH).unwrap(); assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); } @@ -475,121 +224,41 @@ fn test_head_binary() { } #[test] -fn test_head_binary2() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(STR) - }) - }); - - let request = srv.head().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[test] -fn test_body_length() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .content_length(STR.len() as u64) - .content_encoding(http::ContentEncoding::Identity) - .body(Body::Streaming(Box::new(body))) - }) +fn test_no_chunking() { + let mut srv = TestServer::new(move || { + h1::H1Service::new(App::new().resource("/", |r| { + r.get(move || { + Response::Ok() + .no_chunking() + .content_length(STR.len() as u64) + .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) + }) + })) }); let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); + assert!(!response.headers().contains_key(TRANSFER_ENCODING)); // read response let bytes = srv.execute(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_body_chunked_explicit() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - HttpResponse::Ok() - .chunked() - .content_encoding(http::ContentEncoding::Gzip) - .body(Body::Streaming(Box::new(body))) - }) - }); - - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_body_identity() { - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - let enc2 = enc.clone(); - - let mut srv = test::TestServer::new(move |app| { - let enc3 = enc2.clone(); - app.handler(move |_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc3.clone()) - }) - }); - - // client request - let request = srv - .get() - .header("accept-encoding", "deflate") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - - // decode deflate - assert_eq!(bytes, Bytes::from(STR)); -} - #[test] fn test_body_deflate() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Deflate) - .body(STR) - }) + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new() + .middleware(middleware::Compress::new(ContentEncoding::Deflate)) + .resource("/", |r| r.get(move || Response::Ok().body(STR))), + ) }); // client request - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let request = srv.get().finish().unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); // read response @@ -602,20 +271,19 @@ fn test_body_deflate() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "brotli")] #[test] fn test_body_brotli() { - let mut srv = test::TestServer::new(|app| { - app.handler(|_| { - HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Br) - .body(STR) - }) + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new() + .middleware(middleware::Compress::new(ContentEncoding::Br)) + .resource("/", |r| r.get(move || Response::Ok().body(STR))), + ) }); // client request - let request = srv.get().disable_decompress().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); + let request = srv.get().header(ACCEPT_ENCODING, "br").finish().unwrap(); + let mut response = srv.send_request(request).unwrap(); assert!(response.status().is_success()); // read response @@ -628,773 +296,623 @@ fn test_body_brotli() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -#[test] -fn test_gzip_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_gzip_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(60_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "gzip") - .body(enc.clone()) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_deflate_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_reading_deflate_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[test] -fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "deflate") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding() { - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding_large() { - let data = STR.repeat(10); - let mut srv = test::TestServer::new(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post() - .header(http::header::CONTENT_ENCODING, "br") - .body(enc) - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.execute(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "brotli", feature = "ssl"))] -#[test] -fn test_brotli_encoding_large_ssl() { - use actix::{Actor, System}; - use openssl::ssl::{ - SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, - }; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = STR.repeat(10); - let srv = test::TestServer::build().ssl(builder).start(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // body - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(srv.url("/")) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "br") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "rust-tls", feature = "ssl"))] -#[test] -fn test_reading_deflate_encoding_large_random_ssl() { - use actix::{Actor, System}; - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, rsa_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let srv = test::TestServer::build().rustls(config).start(|app| { - app.handler(|req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }); - - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(srv.url("/")) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "deflate") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "tls", feature = "ssl"))] -#[test] -fn test_reading_deflate_encoding_large_random_tls() { - use native_tls::{Identity, TlsAcceptor}; - use openssl::ssl::{ - SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, - }; - use std::fs::File; - use std::sync::mpsc; - - use actix::{Actor, System}; - let (tx, rx) = mpsc::channel(); - - // load ssl keys - let mut file = File::open("tests/identity.pfx").unwrap(); - let mut identity = vec![]; - file.read_to_end(&mut identity).unwrap(); - let identity = Identity::from_pkcs12(&identity, "1").unwrap(); - let acceptor = TlsAcceptor::new(identity).unwrap(); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - server::new(|| { - App::new().handler("/", |req: &HttpRequest| { - req.body() - .and_then(|bytes: Bytes| { - Ok(HttpResponse::Ok() - .content_encoding(http::ContentEncoding::Identity) - .body(bytes)) - }).responder() - }) - }).bind_tls(addr, acceptor) - .unwrap() - .start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - let mut rt = System::new("test"); - - // client connector - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let conn = client::ClientConnector::with_connector(builder.build()).start(); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = client::ClientRequest::build() - .uri(format!("https://{}/", addr)) - .method(http::Method::POST) - .header(http::header::CONTENT_ENCODING, "deflate") - .with_connector(conn) - .body(enc) - .unwrap(); - let response = rt.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = rt.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); - - let _ = sys.stop(); -} - -#[test] -fn test_h2() { - let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); - let addr = srv.addr(); - thread::sleep(time::Duration::from_millis(500)); - - let mut core = Runtime::new().unwrap(); - let tcp = TcpStream::connect(&addr); - - let tcp = tcp - .then(|res| h2client::handshake(res.unwrap())) - .then(move |res| { - let (mut client, h2) = res.unwrap(); - - let request = Request::builder() - .uri(format!("https://{}/", addr).as_str()) - .body(()) - .unwrap(); - let (response, _) = client.send_request(request, false).unwrap(); - - // Spawn a task to run the conn... - spawn(h2.map_err(|e| println!("GOT ERR={:?}", e))); - - response.and_then(|response| { - assert_eq!(response.status(), http::StatusCode::OK); - - let (_, body) = response.into_parts(); - - body.fold(BytesMut::new(), |mut b, c| -> Result<_, h2::Error> { - b.extend(c); - Ok(b) - }) - }) - }); - let _res = core.block_on(tcp); - // assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_application() { - let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); -} - -#[test] -fn test_default_404_handler_response() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .prefix("/app") - .resource("", |r| r.f(|_| HttpResponse::Ok())) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - }); - let addr = srv.addr(); - - let mut buf = [0; 24]; - let request = TcpStream::connect(&addr) - .and_then(|sock| { - tokio::io::write_all(sock, "HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n") - .and_then(|(sock, _)| tokio::io::read_exact(sock, &mut buf)) - .and_then(|(_, buf)| Ok(buf)) - }).map_err(|e| panic!("{:?}", e)); - let response = srv.execute(request).unwrap(); - let rep = String::from_utf8_lossy(&response[..]); - assert!(rep.contains("HTTP/1.1 404 Not Found")); -} - -#[test] -fn test_server_cookies() { - use actix_web::http; - - let mut srv = test::TestServer::with_factory(|| { - App::new().resource("/", |r| { - r.f(|_| { - HttpResponse::Ok() - .cookie( - http::CookieBuilder::new("first", "first_value") - .http_only(true) - .finish(), - ).cookie(http::Cookie::new("second", "first_value")) - .cookie(http::Cookie::new("second", "second_value")) - .finish() - }) - }) - }); - - let first_cookie = http::CookieBuilder::new("first", "first_value") - .http_only(true) - .finish(); - let second_cookie = http::Cookie::new("second", "second_value"); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let cookies = response.cookies().expect("To have cookies"); - assert_eq!(cookies.len(), 2); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } - - let first_cookie = first_cookie.to_string(); - let second_cookie = second_cookie.to_string(); - //Check that we have exactly two instances of raw cookie headers - let cookies = response - .headers() - .get_all(http::header::SET_COOKIE) - .iter() - .map(|header| header.to_str().expect("To str").to_string()) - .collect::>(); - assert_eq!(cookies.len(), 2); - if cookies[0] == first_cookie { - assert_eq!(cookies[1], second_cookie); - } else { - assert_eq!(cookies[0], second_cookie); - assert_eq!(cookies[1], first_cookie); - } -} - -#[test] -fn test_slow_request() { - use actix::System; - use std::net; - use std::sync::mpsc; - let (tx, rx) = mpsc::channel(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - vec![App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - })] - }); - - let srv = srv.bind(addr).unwrap(); - srv.client_timeout(200).start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - thread::sleep(time::Duration::from_millis(200)); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - sys.stop(); -} - -#[test] -fn test_malformed_request() { - use actix::System; - use std::net; - use std::sync::mpsc; - let (tx, rx) = mpsc::channel(); - - let addr = test::TestServer::unused_addr(); - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - let _ = srv.bind(addr).unwrap().start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - thread::sleep(time::Duration::from_millis(200)); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 400 Bad Request")); - - sys.stop(); -} - -#[test] -fn test_app_404() { - let mut srv = test::TestServer::with_factory(|| { - App::new().prefix("/prefix").resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - let request = srv.client(http::Method::GET, "/prefix/").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let request = srv.client(http::Method::GET, "/").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::NOT_FOUND); -} - -#[test] -#[cfg(feature = "ssl")] -fn test_ssl_handshake_timeout() { - use actix::System; - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - use std::net; - use std::sync::mpsc; - - let (tx, rx) = mpsc::channel(); - let addr = test::TestServer::unused_addr(); - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - thread::spawn(move || { - System::run(move || { - let srv = server::new(|| { - App::new().resource("/", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()) - }) - }); - - srv.bind_ssl(addr, builder) - .unwrap() - .workers(1) - .client_timeout(200) - .start(); - let _ = tx.send(System::current()); - }); - }); - let sys = rx.recv().unwrap(); - - let mut stream = net::TcpStream::connect(addr).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.is_empty()); - - let _ = sys.stop(); -} - -#[test] -fn test_content_length() { - use actix_web::http::header::{HeaderName, HeaderValue}; - use http::StatusCode; - - let mut srv = test::TestServer::new(move |app| { - app.resource("/{status}", |r| { - r.f(|req: &HttpRequest| { - let indx: usize = - req.match_info().get("status").unwrap().parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - HttpResponse::new(statuses[indx]) - }) - }); - }); - - let addr = srv.addr(); - let mut get_resp = |i| { - let url = format!("http://{}/{}", addr, i); - let req = srv.get().uri(url).finish().unwrap(); - srv.execute(req.send()).unwrap() - }; - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - - for i in 0..4 { - let response = get_resp(i); - assert_eq!(response.headers().get(&header), None); - } - for i in 4..6 { - let response = get_resp(i); - assert_eq!(response.headers().get(&header), Some(&value)); - } -} +// #[test] +// fn test_gzip_encoding() { +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// // client request +// let mut e = GzEncoder::new(Vec::new(), Compression::default()); +// e.write_all(STR.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// let request = srv +// .post() +// .header(http::header::CONTENT_ENCODING, "gzip") +// .body(enc.clone()) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +// } + +// #[test] +// fn test_gzip_encoding_large() { +// let data = STR.repeat(10); +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// // client request +// let mut e = GzEncoder::new(Vec::new(), Compression::default()); +// e.write_all(data.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// let request = srv +// .post() +// .header(http::header::CONTENT_ENCODING, "gzip") +// .body(enc.clone()) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[test] +// fn test_reading_gzip_encoding_large_random() { +// let data = rand::thread_rng() +// .sample_iter(&Alphanumeric) +// .take(60_000) +// .collect::(); + +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// // client request +// let mut e = GzEncoder::new(Vec::new(), Compression::default()); +// e.write_all(data.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// let request = srv +// .post() +// .header(http::header::CONTENT_ENCODING, "gzip") +// .body(enc.clone()) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes.len(), data.len()); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[test] +// fn test_reading_deflate_encoding() { +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); +// e.write_all(STR.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// // client request +// let request = srv +// .post() +// .header(http::header::CONTENT_ENCODING, "deflate") +// .body(enc) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +// } + +// #[test] +// fn test_reading_deflate_encoding_large() { +// let data = STR.repeat(10); +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); +// e.write_all(data.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// // client request +// let request = srv +// .post() +// .header(http::header::CONTENT_ENCODING, "deflate") +// .body(enc) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[test] +// fn test_reading_deflate_encoding_large_random() { +// let data = rand::thread_rng() +// .sample_iter(&Alphanumeric) +// .take(160_000) +// .collect::(); + +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); +// e.write_all(data.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// // client request +// let request = srv +// .post() +// .header(http::header::CONTENT_ENCODING, "deflate") +// .body(enc) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes.len(), data.len()); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[cfg(feature = "brotli")] +// #[test] +// fn test_brotli_encoding() { +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// let mut e = BrotliEncoder::new(Vec::new(), 5); +// e.write_all(STR.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// // client request +// let request = srv +// .post() +// .header(http::header::CONTENT_ENCODING, "br") +// .body(enc) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +// } + +// #[cfg(feature = "brotli")] +// #[test] +// fn test_brotli_encoding_large() { +// let data = STR.repeat(10); +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// let mut e = BrotliEncoder::new(Vec::new(), 5); +// e.write_all(data.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// // client request +// let request = srv +// .post() +// .header(http::header::CONTENT_ENCODING, "br") +// .body(enc) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[cfg(all(feature = "brotli", feature = "ssl"))] +// #[test] +// fn test_brotli_encoding_large_ssl() { +// use actix::{Actor, System}; +// use openssl::ssl::{ +// SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, +// }; +// // load ssl keys +// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); +// builder +// .set_private_key_file("tests/key.pem", SslFiletype::PEM) +// .unwrap(); +// builder +// .set_certificate_chain_file("tests/cert.pem") +// .unwrap(); + +// let data = STR.repeat(10); +// let srv = test::TestServer::build().ssl(builder).start(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); +// let mut rt = System::new("test"); + +// // client connector +// let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); +// builder.set_verify(SslVerifyMode::NONE); +// let conn = client::ClientConnector::with_connector(builder.build()).start(); + +// // body +// let mut e = BrotliEncoder::new(Vec::new(), 5); +// e.write_all(data.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// // client request +// let request = client::ClientRequest::build() +// .uri(srv.url("/")) +// .method(http::Method::POST) +// .header(http::header::CONTENT_ENCODING, "br") +// .with_connector(conn) +// .body(enc) +// .unwrap(); +// let response = rt.block_on(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = rt.block_on(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[cfg(all(feature = "rust-tls", feature = "ssl"))] +// #[test] +// fn test_reading_deflate_encoding_large_random_ssl() { +// use actix::{Actor, System}; +// use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; +// use rustls::internal::pemfile::{certs, rsa_private_keys}; +// use rustls::{NoClientAuth, ServerConfig}; +// use std::fs::File; +// use std::io::BufReader; + +// // load ssl keys +// let mut config = ServerConfig::new(NoClientAuth::new()); +// let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); +// let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); +// let cert_chain = certs(cert_file).unwrap(); +// let mut keys = rsa_private_keys(key_file).unwrap(); +// config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + +// let data = rand::thread_rng() +// .sample_iter(&Alphanumeric) +// .take(160_000) +// .collect::(); + +// let srv = test::TestServer::build().rustls(config).start(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// let mut rt = System::new("test"); + +// // client connector +// let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); +// builder.set_verify(SslVerifyMode::NONE); +// let conn = client::ClientConnector::with_connector(builder.build()).start(); + +// // encode data +// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); +// e.write_all(data.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// // client request +// let request = client::ClientRequest::build() +// .uri(srv.url("/")) +// .method(http::Method::POST) +// .header(http::header::CONTENT_ENCODING, "deflate") +// .with_connector(conn) +// .body(enc) +// .unwrap(); +// let response = rt.block_on(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = rt.block_on(response.body()).unwrap(); +// assert_eq!(bytes.len(), data.len()); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[cfg(all(feature = "tls", feature = "ssl"))] +// #[test] +// fn test_reading_deflate_encoding_large_random_tls() { +// use native_tls::{Identity, TlsAcceptor}; +// use openssl::ssl::{ +// SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, +// }; +// use std::fs::File; +// use std::sync::mpsc; + +// use actix::{Actor, System}; +// let (tx, rx) = mpsc::channel(); + +// // load ssl keys +// let mut file = File::open("tests/identity.pfx").unwrap(); +// let mut identity = vec![]; +// file.read_to_end(&mut identity).unwrap(); +// let identity = Identity::from_pkcs12(&identity, "1").unwrap(); +// let acceptor = TlsAcceptor::new(identity).unwrap(); + +// // load ssl keys +// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); +// builder +// .set_private_key_file("tests/key.pem", SslFiletype::PEM) +// .unwrap(); +// builder +// .set_certificate_chain_file("tests/cert.pem") +// .unwrap(); + +// let data = rand::thread_rng() +// .sample_iter(&Alphanumeric) +// .take(160_000) +// .collect::(); + +// let addr = test::TestServer::unused_addr(); +// thread::spawn(move || { +// System::run(move || { +// server::new(|| { +// App::new().handler("/", |req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Identity) +// .body(bytes)) +// }) +// .responder() +// }) +// }) +// .bind_tls(addr, acceptor) +// .unwrap() +// .start(); +// let _ = tx.send(System::current()); +// }); +// }); +// let sys = rx.recv().unwrap(); + +// let mut rt = System::new("test"); + +// // client connector +// let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); +// builder.set_verify(SslVerifyMode::NONE); +// let conn = client::ClientConnector::with_connector(builder.build()).start(); + +// // encode data +// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); +// e.write_all(data.as_ref()).unwrap(); +// let enc = e.finish().unwrap(); + +// // client request +// let request = client::ClientRequest::build() +// .uri(format!("https://{}/", addr)) +// .method(http::Method::POST) +// .header(http::header::CONTENT_ENCODING, "deflate") +// .with_connector(conn) +// .body(enc) +// .unwrap(); +// let response = rt.block_on(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = rt.block_on(response.body()).unwrap(); +// assert_eq!(bytes.len(), data.len()); +// assert_eq!(bytes, Bytes::from(data)); + +// let _ = sys.stop(); +// } + +// #[test] +// fn test_server_cookies() { +// use actix_web::http; + +// let mut srv = test::TestServer::with_factory(|| { +// App::new().resource("/", |r| { +// r.f(|_| { +// HttpResponse::Ok() +// .cookie( +// http::CookieBuilder::new("first", "first_value") +// .http_only(true) +// .finish(), +// ) +// .cookie(http::Cookie::new("second", "first_value")) +// .cookie(http::Cookie::new("second", "second_value")) +// .finish() +// }) +// }) +// }); + +// let first_cookie = http::CookieBuilder::new("first", "first_value") +// .http_only(true) +// .finish(); +// let second_cookie = http::Cookie::new("second", "second_value"); + +// let request = srv.get().finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// let cookies = response.cookies().expect("To have cookies"); +// assert_eq!(cookies.len(), 2); +// if cookies[0] == first_cookie { +// assert_eq!(cookies[1], second_cookie); +// } else { +// assert_eq!(cookies[0], second_cookie); +// assert_eq!(cookies[1], first_cookie); +// } + +// let first_cookie = first_cookie.to_string(); +// let second_cookie = second_cookie.to_string(); +// //Check that we have exactly two instances of raw cookie headers +// let cookies = response +// .headers() +// .get_all(http::header::SET_COOKIE) +// .iter() +// .map(|header| header.to_str().expect("To str").to_string()) +// .collect::>(); +// assert_eq!(cookies.len(), 2); +// if cookies[0] == first_cookie { +// assert_eq!(cookies[1], second_cookie); +// } else { +// assert_eq!(cookies[0], second_cookie); +// assert_eq!(cookies[1], first_cookie); +// } +// } + +// #[test] +// fn test_slow_request() { +// use actix::System; +// use std::net; +// use std::sync::mpsc; +// let (tx, rx) = mpsc::channel(); + +// let addr = test::TestServer::unused_addr(); +// thread::spawn(move || { +// System::run(move || { +// let srv = server::new(|| { +// vec![App::new().resource("/", |r| { +// r.method(http::Method::GET).f(|_| HttpResponse::Ok()) +// })] +// }); + +// let srv = srv.bind(addr).unwrap(); +// srv.client_timeout(200).start(); +// let _ = tx.send(System::current()); +// }); +// }); +// let sys = rx.recv().unwrap(); + +// thread::sleep(time::Duration::from_millis(200)); + +// let mut stream = net::TcpStream::connect(addr).unwrap(); +// let mut data = String::new(); +// let _ = stream.read_to_string(&mut data); +// assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + +// let mut stream = net::TcpStream::connect(addr).unwrap(); +// let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); +// let mut data = String::new(); +// let _ = stream.read_to_string(&mut data); +// assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + +// sys.stop(); +// } + +// #[test] +// #[cfg(feature = "ssl")] +// fn test_ssl_handshake_timeout() { +// use actix::System; +// use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; +// use std::net; +// use std::sync::mpsc; + +// let (tx, rx) = mpsc::channel(); +// let addr = test::TestServer::unused_addr(); + +// // load ssl keys +// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); +// builder +// .set_private_key_file("tests/key.pem", SslFiletype::PEM) +// .unwrap(); +// builder +// .set_certificate_chain_file("tests/cert.pem") +// .unwrap(); + +// thread::spawn(move || { +// System::run(move || { +// let srv = server::new(|| { +// App::new().resource("/", |r| { +// r.method(http::Method::GET).f(|_| HttpResponse::Ok()) +// }) +// }); + +// srv.bind_ssl(addr, builder) +// .unwrap() +// .workers(1) +// .client_timeout(200) +// .start(); +// let _ = tx.send(System::current()); +// }); +// }); +// let sys = rx.recv().unwrap(); + +// let mut stream = net::TcpStream::connect(addr).unwrap(); +// let mut data = String::new(); +// let _ = stream.read_to_string(&mut data); +// assert!(data.is_empty()); + +// let _ = sys.stop(); +// } diff --git a/tests/test_ws.rs b/tests/test_ws.rs deleted file mode 100644 index cb46bc7e..00000000 --- a/tests/test_ws.rs +++ /dev/null @@ -1,395 +0,0 @@ -extern crate actix; -extern crate actix_web; -extern crate bytes; -extern crate futures; -extern crate http; -extern crate rand; - -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::{thread, time}; - -use bytes::Bytes; -use futures::Stream; -use rand::distributions::Alphanumeric; -use rand::Rng; - -#[cfg(feature = "ssl")] -extern crate openssl; -#[cfg(feature = "rust-tls")] -extern crate rustls; - -use actix::prelude::*; -use actix_web::*; - -struct Ws; - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[test] -fn test_simple() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(ws::CloseCode::Normal.into())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - ); -} - -// websocket resource helper function -fn start_ws_resource(req: &HttpRequest) -> Result { - ws::start(req, Ws) -} - -#[test] -fn test_simple_path() { - const PATH: &str = "/v1/ws/"; - - // Create a websocket at a specific path. - let mut srv = test::TestServer::new(|app| { - app.resource(PATH, |r| r.route().f(start_ws_resource)); - }); - // fetch the sockets for the resource at a given path. - let (reader, mut writer) = srv.ws_at(PATH).unwrap(); - - writer.text("text"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - - writer.binary(b"text".as_ref()); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Binary(Bytes::from_static(b"text").into())) - ); - - writer.ping("ping"); - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); - - writer.close(Some(ws::CloseCode::Normal.into())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!( - item, - Some(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - ); -} - -#[test] -fn test_empty_close_code() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.close(None); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(None))); -} - -#[test] -fn test_close_description() { - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (reader, mut writer) = srv.ws().unwrap(); - - let close_reason: ws::CloseReason = - (ws::CloseCode::Normal, "close description").into(); - writer.close(Some(close_reason.clone())); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Close(Some(close_reason)))); -} - -#[test] -fn test_large_text() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(65_536) - .collect::(); - - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (mut reader, mut writer) = srv.ws().unwrap(); - - for _ in 0..100 { - writer.text(data.clone()); - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, Some(ws::Message::Text(data.clone()))); - } -} - -#[test] -fn test_large_bin() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(65_536) - .collect::(); - - let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); - let (mut reader, mut writer) = srv.ws().unwrap(); - - for _ in 0..100 { - writer.binary(data.clone()); - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone())))); - } -} - -#[test] -fn test_client_frame_size() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(131_072) - .collect::(); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| -> Result { - let mut resp = ws::handshake(req)?; - let stream = ws::WsStream::new(req.payload()).max_size(131_072); - - let body = ws::WebsocketContext::create(req.clone(), Ws, stream); - Ok(resp.body(body)) - }) - }); - let (reader, mut writer) = srv.ws().unwrap(); - - writer.binary(data.clone()); - match srv.execute(reader.into_future()).err().unwrap().0 { - ws::ProtocolError::Overflow => (), - _ => panic!(), - } -} - -struct Ws2 { - count: usize, - bin: bool, -} - -impl Actor for Ws2 { - type Context = ws::WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - self.send(ctx); - } -} - -impl Ws2 { - fn send(&mut self, ctx: &mut ws::WebsocketContext) { - if self.bin { - ctx.binary(Vec::from("0".repeat(65_536))); - } else { - ctx.text("0".repeat(65_536)); - } - ctx.drain() - .and_then(|_, act, ctx| { - act.count += 1; - if act.count != 10_000 { - act.send(ctx); - } - actix::fut::ok(()) - }).wait(ctx); - } -} - -impl StreamHandler for Ws2 { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[test] -fn test_server_send_text() { - let data = Some(ws::Message::Text("0".repeat(65_536))); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -fn test_server_send_bin() { - let data = Some(ws::Message::Binary(Binary::from("0".repeat(65_536)))); - - let mut srv = test::TestServer::new(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: true, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -#[cfg(feature = "ssl")] -fn test_ws_server_ssl() { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let mut srv = test::TestServer::build().ssl(builder).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - let (mut reader, _writer) = srv.ws().unwrap(); - - let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -#[test] -#[cfg(feature = "rust-tls")] -fn test_ws_server_rust_tls() { - use rustls::internal::pemfile::{certs, rsa_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let mut srv = test::TestServer::build().rustls(config).start(|app| { - app.handler(|req| { - ws::start( - req, - Ws2 { - count: 0, - bin: false, - }, - ) - }) - }); - - let (mut reader, _writer) = srv.ws().unwrap(); - - let data = Some(ws::Message::Text("0".repeat(65_536))); - for _ in 0..10_000 { - let (item, r) = srv.execute(reader.into_future()).unwrap(); - reader = r; - assert_eq!(item, data); - } -} - -struct WsStopped(Arc); - -impl Actor for WsStopped { - type Context = ws::WebsocketContext; - - fn stopped(&mut self, _: &mut Self::Context) { - self.0.fetch_add(1, Ordering::Relaxed); - } -} - -impl StreamHandler for WsStopped { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Text(text) => ctx.text(text), - _ => (), - } - } -} - -#[test] -fn test_ws_stopped() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let mut srv = test::TestServer::new(move |app| { - let num3 = num2.clone(); - app.handler(move |req| ws::start(req, WsStopped(num3.clone()))) - }); - { - let (reader, mut writer) = srv.ws().unwrap(); - writer.text("text"); - writer.close(None); - let (item, _) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); - } - thread::sleep(time::Duration::from_millis(100)); - - assert_eq!(num.load(Ordering::Relaxed), 1); -} From e6d04d24cce00d4f0d08da518325f0ecdf195b5e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 1 Mar 2019 23:59:44 -0800 Subject: [PATCH 0935/1635] move fs to separate crate --- Cargo.toml | 6 + Makefile | 14 - actix-web-fs/CHANGES.md | 5 + actix-web-fs/Cargo.toml | 42 +++ actix-web-fs/README.md | 82 +++++ src/fs.rs => actix-web-fs/src/lib.rs | 30 +- examples/basic.rs | 2 +- src/app.rs | 118 ++----- src/framed_app.rs | 240 -------------- src/framed_handler.rs | 379 ---------------------- src/framed_route.rs | 448 --------------------------- src/helpers.rs | 180 ----------- src/lib.rs | 3 +- src/resource.rs | 39 +-- tests/test_server.rs | 2 +- 15 files changed, 184 insertions(+), 1406 deletions(-) delete mode 100644 Makefile create mode 100644 actix-web-fs/CHANGES.md create mode 100644 actix-web-fs/Cargo.toml create mode 100644 actix-web-fs/README.md rename src/fs.rs => actix-web-fs/src/lib.rs (99%) delete mode 100644 src/framed_app.rs delete mode 100644 src/framed_handler.rs delete mode 100644 src/framed_route.rs delete mode 100644 src/helpers.rs diff --git a/Cargo.toml b/Cargo.toml index 6a61c780..e9c298fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,12 @@ codecov = { repository = "actix/actix-web2", branch = "master", service = "githu name = "actix_web" path = "src/lib.rs" +[workspace] +members = [ + ".", + "actix-web-fs", +] + [features] default = ["brotli", "flate2-c"] diff --git a/Makefile b/Makefile deleted file mode 100644 index e3b8b2cf..00000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.PHONY: default build test doc book clean - -CARGO_FLAGS := --features "$(FEATURES) alpn tls" - -default: test - -build: - cargo build $(CARGO_FLAGS) - -test: build clippy - cargo test $(CARGO_FLAGS) - -doc: build - cargo doc --no-deps $(CARGO_FLAGS) diff --git a/actix-web-fs/CHANGES.md b/actix-web-fs/CHANGES.md new file mode 100644 index 00000000..b93e282a --- /dev/null +++ b/actix-web-fs/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0] - 2018-10-x + +* Initial impl diff --git a/actix-web-fs/Cargo.toml b/actix-web-fs/Cargo.toml new file mode 100644 index 00000000..5900a936 --- /dev/null +++ b/actix-web-fs/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "actix-web-fs" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Static files support for Actix web." +readme = "README.md" +keywords = ["actix", "http", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://actix.rs/api/actix-web/stable/actix_web/" +categories = ["asynchronous", "web-programming::http-server"] +license = "MIT/Apache-2.0" +edition = "2018" +workspace = ".." + +[lib] +name = "actix_web_fs" +path = "src/lib.rs" + +[dependencies] +actix-web = { path=".." } +actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-service = { git = "https://github.com/actix/actix-net.git" } + +bytes = "0.4" +futures = "0.1" +derive_more = "0.14" +log = "0.4" +mime = "0.3" +mime_guess = "2.0.0-alpha" +percent-encoding = "1.0" +v_htmlescape = "0.4" + +[dev-dependencies] +actix-rt = "0.1.0" +#actix-server = { version="0.2", features=["ssl"] } +actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] } +actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +rand = "0.6" +env_logger = "0.6" +serde_derive = "1.0" diff --git a/actix-web-fs/README.md b/actix-web-fs/README.md new file mode 100644 index 00000000..c7e195de --- /dev/null +++ b/actix-web-fs/README.md @@ -0,0 +1,82 @@ +# 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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +Actix web is a simple, pragmatic and extremely fast web framework for Rust. + +* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols +* Streaming and pipelining +* Keep-alive and slow requests handling +* Client/server [WebSockets](https://actix.rs/docs/websockets/) support +* Transparent content compression/decompression (br, gzip, deflate) +* Configurable [request routing](https://actix.rs/docs/url-dispatch/) +* Multipart streams +* Static assets +* SSL support with OpenSSL or `native-tls` +* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) +* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) +* Built on top of [Actix actor framework](https://github.com/actix/actix) +* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support. + +## Documentation & community resources + +* [User Guide](https://actix.rs/docs/) +* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) +* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-web](https://crates.io/crates/actix-web) +* Minimum supported Rust version: 1.31 or later + +## Example + +```rust +extern crate actix_web; +use actix_web::{http, server, App, Path, Responder}; + +fn index(info: Path<(u32, String)>) -> impl Responder { + format!("Hello {}! id:{}", info.1, info.0) +} + +fn main() { + server::new( + || App::new() + .route("/{id}/{name}/index.html", http::Method::GET, index)) + .bind("127.0.0.1:8080").unwrap() + .run(); +} +``` + +### More examples + +* [Basics](https://github.com/actix/examples/tree/master/basics/) +* [Stateful](https://github.com/actix/examples/tree/master/state/) +* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) +* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) +* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) +* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / + [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates +* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) +* [r2d2](https://github.com/actix/examples/tree/master/r2d2/) +* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) +* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) +* [Json](https://github.com/actix/examples/tree/master/json/) + +You may consider checking out +[this directory](https://github.com/actix/examples/tree/master/) for more examples. + +## Benchmarks + +* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) + +## 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](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. + +## Code of Conduct + +Contribution to the actix-web crate is organized under the terms of the +Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to +intervene to uphold that code of conduct. diff --git a/src/fs.rs b/actix-web-fs/src/lib.rs similarity index 99% rename from src/fs.rs rename to actix-web-fs/src/lib.rs index 3c83af6e..c2ac5f3a 100644 --- a/src/fs.rs +++ b/actix-web-fs/src/lib.rs @@ -27,15 +27,14 @@ use actix_http::http::header::{ }; use actix_http::http::{ContentEncoding, Method, StatusCode}; use actix_http::{HttpMessage, Response}; +use actix_service::boxed::BoxedNewService; use actix_service::{NewService, Service}; +use actix_web::{ + blocking, FromRequest, HttpRequest, Responder, ServiceRequest, ServiceResponse, +}; use futures::future::{err, ok, FutureResult}; -use crate::blocking; -use crate::handler::FromRequest; -use crate::helpers::HttpDefaultNewService; -use crate::request::HttpRequest; -use crate::responder::Responder; -use crate::service::{ServiceRequest, ServiceResponse}; +type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; ///Describes `StaticFiles` configiration /// @@ -101,6 +100,7 @@ pub trait StaticFileConfig: Default { ///[StaticFileConfig](trait.StaticFileConfig.html) #[derive(Default)] pub struct DefaultConfig; + impl StaticFileConfig for DefaultConfig {} /// Return the MIME type associated with a filename extension (case-insensitive). @@ -716,9 +716,7 @@ pub struct StaticFiles { directory: PathBuf, index: Option, show_index: bool, - default: Rc< - RefCell, ServiceResponse>>>>, - >, + default: Rc>>>>, renderer: Rc, _chunk_size: usize, _follow_symlinks: bool, @@ -817,9 +815,7 @@ pub struct StaticFilesService { directory: PathBuf, index: Option, show_index: bool, - default: Rc< - RefCell, ServiceResponse>>>>, - >, + default: Rc>>>>, renderer: Rc, _chunk_size: usize, _follow_symlinks: bool, @@ -838,8 +834,8 @@ impl Service for StaticFilesService { fn call(&mut self, req: Self::Request) -> Self::Future { let mut req = req; - let real_path = match PathBuf::from_request(&mut req).poll() { - Ok(Async::Ready(item)) => item, + let real_path = match PathBufWrp::from_request(&mut req).poll() { + Ok(Async::Ready(item)) => item.0, Ok(Async::NotReady) => unreachable!(), Err(e) => return err(Error::from(e)), }; @@ -888,7 +884,9 @@ impl Service for StaticFilesService { } } -impl

    FromRequest

    for PathBuf { +struct PathBufWrp(PathBuf); + +impl

    FromRequest

    for PathBufWrp { type Error = UriSegmentError; type Future = FutureResult; @@ -917,7 +915,7 @@ impl

    FromRequest

    for PathBuf { } } - ok(buf) + ok(PathBufWrp(buf)) } } diff --git a/examples/basic.rs b/examples/basic.rs index 9f4701eb..a18581f9 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -2,7 +2,7 @@ use futures::IntoFuture; use actix_http::{h1, http::Method, Response}; use actix_server::Server; -use actix_web2::{middleware, App, Error, HttpRequest, Resource}; +use actix_web::{middleware, App, Error, HttpRequest, Resource}; fn index(req: HttpRequest) -> &'static str { println!("REQ: {:?}", req); diff --git a/src/app.rs b/src/app.rs index 6ed9b144..7c077706 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use actix_http::body::{Body, MessageBody}; use actix_http::{Extensions, PayloadStream, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; +use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ AndThenNewService, ApplyNewService, IntoNewService, IntoNewTransform, NewService, NewTransform, Service, @@ -12,13 +13,12 @@ use actix_service::{ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::helpers::{ - BoxedHttpNewService, BoxedHttpService, DefaultNewService, HttpDefaultNewService, -}; use crate::resource::Resource; use crate::service::{ServiceRequest, ServiceResponse}; use crate::state::{State, StateFactory, StateFactoryResult}; +type HttpService

    = BoxedService, ServiceResponse, ()>; +type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; type BoxedResponse = Box>; pub trait HttpServiceFactory { @@ -31,18 +31,9 @@ pub trait HttpServiceFactory { /// Application builder pub struct App { - services: Vec<( - ResourceDef, - BoxedHttpNewService, ServiceResponse>, - )>, - default: Option, ServiceResponse>>>, - defaults: Vec< - Rc< - RefCell< - Option, ServiceResponse>>>, - >, - >, - >, + services: Vec<(ResourceDef, HttpNewService

    )>, + default: Option>>, + defaults: Vec>>>>>, endpoint: T, factory_ref: Rc>>>, extensions: Extensions, @@ -181,10 +172,8 @@ where let rdef = ResourceDef::new(path); let resource = f(Resource::new()); self.defaults.push(resource.get_default()); - self.services.push(( - rdef, - Box::new(HttpNewService::new(resource.into_new_service())), - )); + self.services + .push((rdef, boxed::new_service(resource.into_new_service()))); self } @@ -203,9 +192,9 @@ where > + 'static, { // create and configure default resource - self.default = Some(Rc::new(Box::new(DefaultNewService::new( - f(Resource::new()).into_new_service(), - )))); + self.default = Some(Rc::new(boxed::new_service( + f(Resource::new()).into_new_service().map_init_err(|_| ()), + ))); self } @@ -223,7 +212,7 @@ where { self.services.push(( rdef.into(), - Box::new(HttpNewService::new(factory.into_new_service())), + boxed::new_service(factory.into_new_service().map_init_err(|_| ())), )); self } @@ -422,15 +411,10 @@ impl

    Service for AppStateService

    { } pub struct AppFactory

    { - services: Rc< - Vec<( - ResourceDef, - BoxedHttpNewService, ServiceResponse>, - )>, - >, + services: Rc)>>, } -impl

    NewService for AppFactory

    { +impl NewService for AppFactory

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); @@ -454,8 +438,7 @@ impl

    NewService for AppFactory

    { } } -type HttpServiceFut

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

    = Box, Error = ()>>; /// Create app service #[doc(hidden)] @@ -465,10 +448,7 @@ pub struct CreateAppService

    { enum CreateAppServiceItem

    { Future(Option, HttpServiceFut

    ), - Service( - ResourceDef, - BoxedHttpService, ServiceResponse>, - ), + Service(ResourceDef, HttpService

    ), } impl

    Future for CreateAppService

    { @@ -522,7 +502,7 @@ impl

    Future for CreateAppService

    { } pub struct AppService

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

    , ResourceInfo)>, } @@ -561,68 +541,6 @@ impl Future for AppServiceResponse { } } -struct HttpNewService>>(T); - -impl HttpNewService -where - T: NewService, Response = ServiceResponse, Error = ()>, - T::Future: 'static, - ::Future: 'static, -{ - pub fn new(service: T) -> Self { - HttpNewService(service) - } -} - -impl NewService for HttpNewService -where - T: NewService, Response = ServiceResponse, Error = ()>, - T::Future: 'static, - T::Service: 'static, - ::Future: 'static, -{ - type Request = ServiceRequest

    ; - type Response = ServiceResponse; - type Error = (); - type InitError = (); - type Service = BoxedHttpService, Self::Response>; - type Future = Box>; - - fn new_service(&self, _: &()) -> Self::Future { - Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { - let service: BoxedHttpService<_, _> = Box::new(HttpServiceWrapper { - service, - _t: PhantomData, - }); - Ok(service) - })) - } -} - -struct HttpServiceWrapper { - service: T, - _t: PhantomData<(P,)>, -} - -impl Service for HttpServiceWrapper -where - T::Future: 'static, - T: Service, Response = ServiceResponse, Error = ()>, -{ - type Request = ServiceRequest

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

    ) -> Self::Future { - Box::new(self.service.call(req)) - } -} - #[doc(hidden)] pub struct AppEntry

    { factory: Rc>>>, @@ -634,7 +552,7 @@ impl

    AppEntry

    { } } -impl

    NewService for AppEntry

    { +impl NewService for AppEntry

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); diff --git a/src/framed_app.rs b/src/framed_app.rs deleted file mode 100644 index ba925414..00000000 --- a/src/framed_app.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; - -use actix_codec::Framed; -use actix_http::h1::Codec; -use actix_http::{Request, Response, SendResponse}; -use actix_router::{Path, Router, Url}; -use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; -use futures::{Async, Future, Poll}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use crate::app::{HttpServiceFactory, State}; -use crate::framed_handler::FramedRequest; -use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService}; -use crate::request::Request as WebRequest; - -pub type FRequest = (Request, Framed); -type BoxedResponse = Box>; - -/// Application builder -pub struct FramedApp { - services: Vec<(String, BoxedHttpNewService, ()>)>, - state: State, -} - -impl FramedApp { - pub fn new() -> Self { - FramedApp { - services: Vec::new(), - state: State::new(()), - } - } -} - -impl FramedApp { - pub fn with(state: S) -> FramedApp { - FramedApp { - services: Vec::new(), - state: State::new(state), - } - } - - pub fn service(mut self, factory: U) -> Self - where - U: HttpServiceFactory, - U::Factory: NewService, Response = ()> + 'static, - ::Future: 'static, - ::Service: Service>, - <::Service as Service>::Future: 'static, - { - let path = factory.path().to_string(); - self.services.push(( - path, - Box::new(HttpNewService::new(factory.create(self.state.clone()))), - )); - self - } - - pub fn register_service(&mut self, factory: U) - where - U: HttpServiceFactory, - U::Factory: NewService, Response = ()> + 'static, - ::Future: 'static, - ::Service: Service>, - <::Service as Service>::Future: 'static, - { - let path = factory.path().to_string(); - self.services.push(( - path, - Box::new(HttpNewService::new(factory.create(self.state.clone()))), - )); - } -} - -impl IntoNewService> for FramedApp -where - T: AsyncRead + AsyncWrite, -{ - fn into_new_service(self) -> FramedAppFactory { - FramedAppFactory { - state: self.state, - services: Rc::new(self.services), - _t: PhantomData, - } - } -} - -#[derive(Clone)] -pub struct FramedAppFactory { - state: State, - services: Rc, ()>)>>, - _t: PhantomData, -} - -impl NewService for FramedAppFactory -where - T: AsyncRead + AsyncWrite, -{ - type Request = FRequest; - type Response = (); - type Error = (); - type InitError = (); - type Service = CloneableService>; - type Future = CreateService; - - fn new_service(&self) -> Self::Future { - CreateService { - fut: self - .services - .iter() - .map(|(path, service)| { - CreateServiceItem::Future(Some(path.clone()), service.new_service()) - }) - .collect(), - state: self.state.clone(), - } - } -} - -#[doc(hidden)] -pub struct CreateService { - fut: Vec>, - state: State, -} - -enum CreateServiceItem { - Future( - Option, - Box, ()>, Error = ()>>, - ), - Service(String, BoxedHttpService, ()>), -} - -impl Future for CreateService -where - T: AsyncRead + AsyncWrite, -{ - type Item = CloneableService>; - type Error = (); - - fn poll(&mut self) -> Poll { - let mut done = true; - - // poll http services - for item in &mut self.fut { - let res = match item { - CreateServiceItem::Future(ref mut path, ref mut fut) => { - match fut.poll()? { - Async::Ready(service) => Some((path.take().unwrap(), service)), - Async::NotReady => { - done = false; - None - } - } - } - CreateServiceItem::Service(_, _) => continue, - }; - - if let Some((path, service)) = res { - *item = CreateServiceItem::Service(path, service); - } - } - - if done { - let router = self - .fut - .drain(..) - .fold(Router::build(), |mut router, item| { - match item { - CreateServiceItem::Service(path, service) => { - router.path(&path, service) - } - CreateServiceItem::Future(_, _) => unreachable!(), - } - router - }); - Ok(Async::Ready(CloneableService::new(FramedAppService { - router: router.finish(), - state: self.state.clone(), - // default: self.default.take().expect("something is wrong"), - }))) - } else { - Ok(Async::NotReady) - } - } -} - -pub struct FramedAppService { - state: State, - router: Router, ()>>, -} - -impl Service for FramedAppService -where - T: AsyncRead + AsyncWrite, -{ - type Request = FRequest; - type Response = (); - type Error = (); - type Future = BoxedResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - // let mut ready = true; - // for service in &mut self.services { - // if let Async::NotReady = service.poll_ready()? { - // ready = false; - // } - // } - // if ready { - // Ok(Async::Ready(())) - // } else { - // Ok(Async::NotReady) - // } - Ok(Async::Ready(())) - } - - fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { - let mut path = Path::new(Url::new(req.uri().clone())); - - if let Some((srv, _info)) = self.router.recognize_mut(&mut path) { - return srv.call(FramedRequest::new( - WebRequest::new(self.state.clone(), req, path), - framed, - )); - } - // for item in &mut self.services { - // req = match item.handle(req) { - // Ok(fut) => return fut, - // Err(req) => req, - // }; - // } - // self.default.call(req) - Box::new( - SendResponse::send(framed, Response::NotFound().finish().into()) - .map(|_| ()) - .map_err(|_| ()), - ) - } -} diff --git a/src/framed_handler.rs b/src/framed_handler.rs deleted file mode 100644 index 109b5f0a..00000000 --- a/src/framed_handler.rs +++ /dev/null @@ -1,379 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; - -use actix_codec::Framed; -use actix_http::{h1::Codec, Error}; -use actix_service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Future, IntoFuture, Poll}; -use log::error; - -use crate::handler::FromRequest; -use crate::request::Request; - -pub struct FramedError { - pub err: Error, - pub framed: Framed, -} - -pub struct FramedRequest { - req: Request, - framed: Framed, - param: Ex, -} - -impl FramedRequest { - pub fn new(req: Request, framed: Framed) -> Self { - Self { - req, - framed, - param: (), - } - } -} - -impl FramedRequest { - pub fn request(&self) -> &Request { - &self.req - } - - pub fn request_mut(&mut self) -> &mut Request { - &mut self.req - } - - pub fn into_parts(self) -> (Request, Framed, Ex) { - (self.req, self.framed, self.param) - } - - pub fn map(self, op: F) -> FramedRequest - where - F: FnOnce(Ex) -> Ex2, - { - FramedRequest { - req: self.req, - framed: self.framed, - param: op(self.param), - } - } -} - -/// T handler converter factory -pub trait FramedFactory: Clone + 'static -where - R: IntoFuture, - E: Into, -{ - fn call(&self, framed: Framed, param: T, extra: Ex) -> R; -} - -#[doc(hidden)] -pub struct FramedHandle -where - F: FramedFactory, - R: IntoFuture, - E: Into, -{ - hnd: F, - _t: PhantomData<(S, Io, Ex, T, R, E)>, -} - -impl FramedHandle -where - F: FramedFactory, - R: IntoFuture, - E: Into, -{ - pub fn new(hnd: F) -> Self { - FramedHandle { - hnd, - _t: PhantomData, - } - } -} -impl NewService for FramedHandle -where - F: FramedFactory, - R: IntoFuture, - E: Into, -{ - type Request = (T, FramedRequest); - type Response = (); - type Error = FramedError; - type InitError = (); - type Service = FramedHandleService; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(FramedHandleService { - hnd: self.hnd.clone(), - _t: PhantomData, - }) - } -} - -#[doc(hidden)] -pub struct FramedHandleService -where - F: FramedFactory, - R: IntoFuture, - E: Into, -{ - hnd: F, - _t: PhantomData<(S, Io, Ex, T, R, E)>, -} - -impl Service for FramedHandleService -where - F: FramedFactory, - R: IntoFuture, - E: Into, -{ - type Request = (T, FramedRequest); - type Response = (); - type Error = FramedError; - type Future = FramedHandleServiceResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, (param, framed): (T, FramedRequest)) -> Self::Future { - let (_, framed, ex) = framed.into_parts(); - FramedHandleServiceResponse { - fut: self.hnd.call(framed, param, ex).into_future(), - _t: PhantomData, - } - } -} - -#[doc(hidden)] -pub struct FramedHandleServiceResponse { - fut: F, - _t: PhantomData, -} - -impl Future for FramedHandleServiceResponse -where - F: Future, - F::Error: Into, -{ - type Item = (); - type Error = FramedError; - - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(res)) => Ok(Async::Ready(res.into())), - Err(e) => { - let e: Error = e.into(); - error!("Error in handler: {:?}", e); - Ok(Async::Ready(())) - } - } - } -} - -pub struct FramedExtract -where - T: FromRequest, -{ - cfg: Rc, - _t: PhantomData<(Io, Ex)>, -} - -impl FramedExtract -where - T: FromRequest + 'static, -{ - pub fn new(cfg: T::Config) -> FramedExtract { - FramedExtract { - cfg: Rc::new(cfg), - _t: PhantomData, - } - } -} -impl NewService for FramedExtract -where - T: FromRequest + 'static, -{ - type Request = FramedRequest; - type Response = (T, FramedRequest); - type Error = FramedError; - type InitError = (); - type Service = FramedExtractService; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(FramedExtractService { - cfg: self.cfg.clone(), - _t: PhantomData, - }) - } -} - -pub struct FramedExtractService -where - T: FromRequest, -{ - cfg: Rc, - _t: PhantomData<(Io, Ex)>, -} - -impl Service for FramedExtractService -where - T: FromRequest + 'static, -{ - type Request = FramedRequest; - type Response = (T, FramedRequest); - type Error = FramedError; - type Future = FramedExtractResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: FramedRequest) -> Self::Future { - FramedExtractResponse { - fut: T::from_request(&req.request(), self.cfg.as_ref()), - req: Some(req), - } - } -} - -pub struct FramedExtractResponse -where - T: FromRequest + 'static, -{ - req: Option>, - fut: T::Future, -} - -impl Future for FramedExtractResponse -where - T: FromRequest + 'static, -{ - type Item = (T, FramedRequest); - type Error = FramedError; - - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(item)) => Ok(Async::Ready((item, self.req.take().unwrap()))), - Err(err) => Err(FramedError { - err: err.into(), - framed: self.req.take().unwrap().into_parts().1, - }), - } - } -} - -macro_rules! factory_tuple ({ ($(($nex:tt, $Ex:ident)),+), $(($n:tt, $T:ident)),+} => { - impl FramedFactory for Func - where Func: Fn(Framed, $($Ex,)+ $($T,)+) -> Res + Clone + 'static, - $($T: FromRequest + 'static,)+ - Res: IntoFuture + 'static, - Err: Into, - { - fn call(&self, framed: Framed, param: ($($T,)+), extra: ($($Ex,)+)) -> Res { - (self)(framed, $(extra.$nex,)+ $(param.$n,)+) - } - } -}); - -macro_rules! factory_tuple_unit ({$(($n:tt, $T:ident)),+} => { - impl FramedFactory for Func - where Func: Fn(Framed, $($T,)+) -> Res + Clone + 'static, - $($T: FromRequest + 'static,)+ - Res: IntoFuture + 'static, - Err: Into, - { - fn call(&self, framed: Framed, param: ($($T,)+), _extra: () ) -> Res { - (self)(framed, $(param.$n,)+) - } - } -}); - -#[cfg_attr(rustfmt, rustfmt_skip)] -mod m { - use super::*; - -factory_tuple_unit!((0, A)); -factory_tuple!(((0, Aex)), (0, A)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A)); - -factory_tuple_unit!((0, A), (1, B)); -factory_tuple!(((0, Aex)), (0, A), (1, B)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B)); - -factory_tuple_unit!((0, A), (1, B), (2, C)); -factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C)); - -factory_tuple_unit!((0, A), (1, B), (2, C), (3, D)); -factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D)); - -factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E)); - -factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); - -factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); - -factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); - -factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); - -factory_tuple_unit!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -factory_tuple!(((0, Aex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -factory_tuple!(((0, Aex), (1, Bex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -factory_tuple!(((0, Aex), (1, Bex), (2, Cex), (3, Dex), (4, Eex), (5, Fex)), (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -} diff --git a/src/framed_route.rs b/src/framed_route.rs deleted file mode 100644 index 90555a9c..00000000 --- a/src/framed_route.rs +++ /dev/null @@ -1,448 +0,0 @@ -use std::marker::PhantomData; - -use actix_http::http::{HeaderName, HeaderValue, Method}; -use actix_http::Error; -use actix_service::{IntoNewService, NewService, NewServiceExt, Service}; -use futures::{try_ready, Async, Future, IntoFuture, Poll}; -use log::{debug, error}; -use tokio_io::{AsyncRead, AsyncWrite}; - -use crate::app::{HttpServiceFactory, State}; -use crate::framed_handler::{ - FramedError, FramedExtract, FramedFactory, FramedHandle, FramedRequest, -}; -use crate::handler::FromRequest; - -/// Resource route definition -/// -/// Route uses builder-like pattern for configuration. -/// If handler is not explicitly set, default *404 Not Found* handler is used. -pub struct FramedRoute { - service: T, - pattern: String, - methods: Vec, - headers: Vec<(HeaderName, HeaderValue)>, - state: PhantomData<(S, Io)>, -} - -impl FramedRoute { - pub fn build(path: &str) -> FramedRoutePatternBuilder { - FramedRoutePatternBuilder::new(path) - } - - pub fn get(path: &str) -> FramedRoutePatternBuilder { - FramedRoutePatternBuilder::new(path).method(Method::GET) - } - - pub fn post(path: &str) -> FramedRoutePatternBuilder { - FramedRoutePatternBuilder::new(path).method(Method::POST) - } - - pub fn put(path: &str) -> FramedRoutePatternBuilder { - FramedRoutePatternBuilder::new(path).method(Method::PUT) - } - - pub fn delete(path: &str) -> FramedRoutePatternBuilder { - FramedRoutePatternBuilder::new(path).method(Method::DELETE) - } -} - -impl FramedRoute -where - T: NewService< - Request = FramedRequest, - Response = (), - Error = FramedError, - > + 'static, -{ - pub fn new>(pattern: &str, factory: F) -> Self { - FramedRoute { - pattern: pattern.to_string(), - service: factory.into_new_service(), - headers: Vec::new(), - methods: Vec::new(), - state: PhantomData, - } - } - - pub fn method(mut self, method: Method) -> Self { - self.methods.push(method); - self - } - - pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self { - self.headers.push((name, value)); - self - } -} - -impl HttpServiceFactory for FramedRoute -where - Io: AsyncRead + AsyncWrite + 'static, - T: NewService< - Request = FramedRequest, - Response = (), - Error = FramedError, - > + 'static, - T::Service: 'static, -{ - type Factory = FramedRouteFactory; - - fn path(&self) -> &str { - &self.pattern - } - - fn create(self, state: State) -> Self::Factory { - FramedRouteFactory { - state, - service: self.service, - pattern: self.pattern, - methods: self.methods, - headers: self.headers, - _t: PhantomData, - } - } -} - -pub struct FramedRouteFactory { - service: T, - pattern: String, - methods: Vec, - headers: Vec<(HeaderName, HeaderValue)>, - state: State, - _t: PhantomData, -} - -impl NewService for FramedRouteFactory -where - Io: AsyncRead + AsyncWrite + 'static, - T: NewService< - Request = FramedRequest, - Response = (), - Error = FramedError, - > + 'static, - T::Service: 'static, -{ - type Request = FramedRequest; - type Response = T::Response; - type Error = (); - type InitError = T::InitError; - type Service = FramedRouteService; - type Future = CreateRouteService; - - fn new_service(&self) -> Self::Future { - CreateRouteService { - fut: self.service.new_service(), - pattern: self.pattern.clone(), - methods: self.methods.clone(), - headers: self.headers.clone(), - state: self.state.clone(), - _t: PhantomData, - } - } -} - -pub struct CreateRouteService { - fut: T::Future, - pattern: String, - methods: Vec, - headers: Vec<(HeaderName, HeaderValue)>, - state: State, - _t: PhantomData, -} - -impl Future for CreateRouteService -where - T: NewService< - Request = FramedRequest, - Response = (), - Error = FramedError, - >, -{ - type Item = FramedRouteService; - type Error = T::InitError; - - fn poll(&mut self) -> Poll { - let service = try_ready!(self.fut.poll()); - - Ok(Async::Ready(FramedRouteService { - service, - state: self.state.clone(), - pattern: self.pattern.clone(), - methods: self.methods.clone(), - headers: self.headers.clone(), - _t: PhantomData, - })) - } -} - -pub struct FramedRouteService { - service: T, - pattern: String, - methods: Vec, - headers: Vec<(HeaderName, HeaderValue)>, - state: State, - _t: PhantomData, -} - -impl Service for FramedRouteService -where - Io: AsyncRead + AsyncWrite + 'static, - T: Service, Response = (), Error = FramedError> - + 'static, -{ - type Request = FramedRequest; - type Response = (); - type Error = (); - type Future = FramedRouteServiceResponse; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready().map_err(|e| { - debug!("Service not available: {}", e.err); - () - }) - } - - fn call(&mut self, req: FramedRequest) -> Self::Future { - FramedRouteServiceResponse { - fut: self.service.call(req), - send: None, - _t: PhantomData, - } - } -} - -// impl HttpService<(Request, Framed)> for FramedRouteService -// where -// Io: AsyncRead + AsyncWrite + 'static, -// S: 'static, -// T: Service, Response = (), Error = FramedError> + 'static, -// { -// fn handle( -// &mut self, -// (req, framed): (Request, Framed), -// ) -> Result)> { -// if self.methods.is_empty() -// || !self.methods.is_empty() && self.methods.contains(req.method()) -// { -// if let Some(params) = self.pattern.match_with_params(&req, 0) { -// return Ok(FramedRouteServiceResponse { -// fut: self.service.call(FramedRequest::new( -// WebRequest::new(self.state.clone(), req, params), -// framed, -// )), -// send: None, -// _t: PhantomData, -// }); -// } -// } -// Err((req, framed)) -// } -// } - -#[doc(hidden)] -pub struct FramedRouteServiceResponse { - fut: F, - send: Option>>, - _t: PhantomData, -} - -impl Future for FramedRouteServiceResponse -where - F: Future>, - Io: AsyncRead + AsyncWrite + 'static, -{ - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.send { - return match fut.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(_)) => Ok(Async::Ready(())), - Err(e) => { - debug!("Error during error response send: {}", e); - Err(()) - } - }; - }; - - match self.fut.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(_)) => Ok(Async::Ready(())), - Err(e) => { - error!("Error occurred during request handling: {}", e.err); - Err(()) - } - } - } -} - -pub struct FramedRoutePatternBuilder { - pattern: String, - methods: Vec, - headers: Vec<(HeaderName, HeaderValue)>, - state: PhantomData<(Io, S)>, -} - -impl FramedRoutePatternBuilder { - fn new(path: &str) -> FramedRoutePatternBuilder { - FramedRoutePatternBuilder { - pattern: path.to_string(), - methods: Vec::new(), - headers: Vec::new(), - state: PhantomData, - } - } - - pub fn method(mut self, method: Method) -> Self { - self.methods.push(method); - self - } - - pub fn map>( - self, - md: F, - ) -> FramedRouteBuilder - where - T: NewService< - Request = FramedRequest, - Response = FramedRequest, - Error = FramedError, - InitError = (), - >, - { - FramedRouteBuilder { - service: md.into_new_service(), - pattern: self.pattern, - methods: self.methods, - headers: self.headers, - state: PhantomData, - } - } - - pub fn with( - self, - handler: F, - ) -> FramedRoute< - Io, - impl NewService< - Request = FramedRequest, - Response = (), - Error = FramedError, - InitError = (), - >, - S, - > - where - F: FramedFactory, - P: FromRequest + 'static, - R: IntoFuture, - E: Into, - { - FramedRoute { - service: FramedExtract::new(P::Config::default()) - .and_then(FramedHandle::new(handler)), - pattern: self.pattern, - methods: self.methods, - headers: self.headers, - state: PhantomData, - } - } -} - -pub struct FramedRouteBuilder { - service: T, - pattern: String, - methods: Vec, - headers: Vec<(HeaderName, HeaderValue)>, - state: PhantomData<(Io, S, U1, U2)>, -} - -impl FramedRouteBuilder -where - T: NewService< - Request = FramedRequest, - Response = FramedRequest, - Error = FramedError, - InitError = (), - >, -{ - pub fn new>(path: &str, factory: F) -> Self { - FramedRouteBuilder { - service: factory.into_new_service(), - pattern: path.to_string(), - methods: Vec::new(), - headers: Vec::new(), - state: PhantomData, - } - } - - pub fn method(mut self, method: Method) -> Self { - self.methods.push(method); - self - } - - pub fn map>( - self, - md: F, - ) -> FramedRouteBuilder< - Io, - S, - impl NewService< - Request = FramedRequest, - Response = FramedRequest, - Error = FramedError, - InitError = (), - >, - U1, - U3, - > - where - K: NewService< - Request = FramedRequest, - Response = FramedRequest, - Error = FramedError, - InitError = (), - >, - { - FramedRouteBuilder { - service: self.service.from_err().and_then(md.into_new_service()), - pattern: self.pattern, - methods: self.methods, - headers: self.headers, - state: PhantomData, - } - } - - pub fn with( - self, - handler: F, - ) -> FramedRoute< - Io, - impl NewService< - Request = FramedRequest, - Response = (), - Error = FramedError, - InitError = (), - >, - S, - > - where - F: FramedFactory, - P: FromRequest + 'static, - R: IntoFuture, - E: Into, - { - FramedRoute { - service: self - .service - .and_then(FramedExtract::new(P::Config::default())) - .and_then(FramedHandle::new(handler)), - pattern: self.pattern, - methods: self.methods, - headers: self.headers, - state: PhantomData, - } - } -} diff --git a/src/helpers.rs b/src/helpers.rs deleted file mode 100644 index 860a02a4..00000000 --- a/src/helpers.rs +++ /dev/null @@ -1,180 +0,0 @@ -use actix_http::Response; -use actix_service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Future, Poll}; - -pub(crate) type BoxedHttpService = Box< - Service< - Request = Req, - Response = Res, - Error = (), - Future = Box>, - >, ->; - -pub(crate) type BoxedHttpNewService = Box< - NewService< - Request = Req, - Response = Res, - Error = (), - InitError = (), - Service = BoxedHttpService, - Future = Box, Error = ()>>, - >, ->; - -pub(crate) struct HttpNewService(T); - -impl HttpNewService -where - T: NewService, - T::Response: 'static, - T::Future: 'static, - T::Service: Service, - ::Future: 'static, -{ - pub fn new(service: T) -> Self { - HttpNewService(service) - } -} - -impl NewService for HttpNewService -where - T: NewService, - T::Request: 'static, - T::Response: 'static, - T::Future: 'static, - T::Service: Service + 'static, - ::Future: 'static, -{ - type Request = T::Request; - type Response = T::Response; - type Error = (); - type InitError = (); - type Service = BoxedHttpService; - type Future = Box>; - - fn new_service(&self, _: &()) -> Self::Future { - Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { - let service: BoxedHttpService<_, _> = - Box::new(HttpServiceWrapper { service }); - Ok(service) - })) - } -} - -struct HttpServiceWrapper { - service: T, -} - -impl Service for HttpServiceWrapper -where - T: Service, - T::Request: 'static, - T::Response: 'static, - T::Future: 'static, -{ - type Request = T::Request; - type Response = T::Response; - type Error = (); - type Future = Box>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready().map_err(|_| ()) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - Box::new(self.service.call(req).map_err(|_| ())) - } -} - -pub(crate) fn not_found(_: Req) -> FutureResult { - ok(Response::NotFound().finish()) -} - -pub(crate) type HttpDefaultService = Box< - Service< - Request = Req, - Response = Res, - Error = (), - Future = Box>, - >, ->; - -pub(crate) type HttpDefaultNewService = Box< - NewService< - Request = Req, - Response = Res, - Error = (), - InitError = (), - Service = HttpDefaultService, - Future = Box, Error = ()>>, - >, ->; - -pub(crate) struct DefaultNewService { - service: T, -} - -impl DefaultNewService -where - T: NewService + 'static, - T::Future: 'static, - ::Future: 'static, -{ - pub fn new(service: T) -> Self { - DefaultNewService { service } - } -} - -impl NewService for DefaultNewService -where - T: NewService + 'static, - T::Request: 'static, - T::Future: 'static, - T::Service: 'static, - ::Future: 'static, -{ - type Request = T::Request; - type Response = T::Response; - type Error = (); - type InitError = (); - type Service = HttpDefaultService; - type Future = Box>; - - fn new_service(&self, _: &()) -> Self::Future { - Box::new( - self.service - .new_service(&()) - .map_err(|_| ()) - .and_then(|service| { - let service: HttpDefaultService<_, _> = - Box::new(DefaultServiceWrapper { service }); - Ok(service) - }), - ) - } -} - -struct DefaultServiceWrapper { - service: T, -} - -impl Service for DefaultServiceWrapper -where - T: Service + 'static, - T::Future: 'static, -{ - type Request = T::Request; - type Response = T::Response; - type Error = (); - type Future = Box>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready().map_err(|_| ()) - } - - fn call(&mut self, req: T::Request) -> Self::Future { - Box::new(self.service.call(req).map_err(|_| ())) - } -} diff --git a/src/lib.rs b/src/lib.rs index f09c11ce..b4b12eb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,10 @@ mod app; mod extractor; pub mod handler; -mod helpers; +// mod helpers; // mod info; pub mod blocking; pub mod filter; -pub mod fs; pub mod middleware; mod request; mod resource; diff --git a/src/resource.rs b/src/resource.rs index 88f7ae5a..80ac8d83 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; use std::rc::Rc; use actix_http::{http::Method, Error, Response}; +use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ ApplyNewService, IntoNewService, IntoNewTransform, NewService, NewTransform, Service, }; @@ -9,11 +10,13 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; use crate::handler::{AsyncFactory, Factory, FromRequest}; -use crate::helpers::{DefaultNewService, HttpDefaultNewService, HttpDefaultService}; use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteBuilder, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; +type HttpService

    = BoxedService, ServiceResponse, ()>; +type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; + /// Resource route definition /// /// Route uses builder-like pattern for configuration. @@ -21,9 +24,7 @@ use crate::service::{ServiceRequest, ServiceResponse}; pub struct Resource> { routes: Vec>, endpoint: T, - default: Rc< - RefCell, ServiceResponse>>>>, - >, + default: Rc>>>>, factory_ref: Rc>>>, } @@ -277,17 +278,14 @@ where > + 'static, { // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(Box::new( - DefaultNewService::new(f(Resource::new()).into_new_service()), + self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( + f(Resource::new()).into_new_service().map_init_err(|_| ()), ))))); self } - pub(crate) fn get_default( - &self, - ) -> Rc, ServiceResponse>>>>> - { + pub(crate) fn get_default(&self) -> Rc>>>> { self.default.clone() } } @@ -313,12 +311,10 @@ where pub struct ResourceFactory

    { routes: Vec>, - default: Rc< - RefCell, ServiceResponse>>>>, - >, + default: Rc>>>>, } -impl

    NewService for ResourceFactory

    { +impl NewService for ResourceFactory

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); @@ -352,15 +348,8 @@ enum CreateRouteServiceItem

    { pub struct CreateResourceService

    { fut: Vec>, - default: Option, ServiceResponse>>, - default_fut: Option< - Box< - Future< - Item = HttpDefaultService, ServiceResponse>, - Error = (), - >, - >, - >, + default: Option>, + default_fut: Option, Error = ()>>>, } impl

    Future for CreateResourceService

    { @@ -413,7 +402,7 @@ impl

    Future for CreateResourceService

    { pub struct ResourceService

    { routes: Vec>, - default: Option, ServiceResponse>>, + default: Option>, } impl

    Service for ResourceService

    { @@ -461,7 +450,7 @@ impl

    ResourceEndpoint

    { } } -impl

    NewService for ResourceEndpoint

    { +impl NewService for ResourceEndpoint

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); diff --git a/tests/test_server.rs b/tests/test_server.rs index 2d01d270..590cc0e7 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -12,7 +12,7 @@ use flate2::write::ZlibDecoder; use futures::stream::once; //Future, Stream use rand::{distributions::Alphanumeric, Rng}; -use actix_web2::{middleware, App}; +use actix_web::{middleware, App}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ From bc3c29c39868bf940f19de1960e0f876b15c5636 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 00:04:39 -0800 Subject: [PATCH 0936/1635] update version --- .travis.yml | 10 ++++------ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9b1bcff5..077989d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,15 +32,13 @@ script: - | if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then cargo clean - cargo check --features rust-tls - cargo check --features ssl - cargo check --features tls - cargo test --features="ssl,tls,rust-tls,uds" -- --nocapture + cargo check + cargo test -- --nocapture fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - RUST_BACKTRACE=1 cargo tarpaulin --features="ssl,tls,rust-tls" --out Xml + RUST_BACKTRACE=1 cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi @@ -49,7 +47,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --features "ssl,tls,rust-tls,session" --no-deps && + cargo doc --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/Cargo.toml b/Cargo.toml index e9c298fa..3cc42d80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "0.1.0" +version = "1.0.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From fdf30118378f0b84004d39624daa7ba36ac67535 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 09:05:07 -0800 Subject: [PATCH 0937/1635] add responder for unit type --- Cargo.toml | 1 + src/application.rs | 785 --------------------------------------------- src/lib.rs | 1 - src/responder.rs | 26 +- 4 files changed, 15 insertions(+), 798 deletions(-) delete mode 100644 src/application.rs diff --git a/Cargo.toml b/Cargo.toml index 3cc42d80..bfd06101 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ actix-router = { git = "https://github.com/actix/actix-net.git" } bytes = "0.4" futures = "0.1" derive_more = "0.14" +either = "1.5.1" log = "0.4" lazy_static = "1.2" mime = "0.3" diff --git a/src/application.rs b/src/application.rs deleted file mode 100644 index 6ca4ce28..00000000 --- a/src/application.rs +++ /dev/null @@ -1,785 +0,0 @@ -use std::rc::Rc; - -use actix_http::http::ContentEncoding; -use actix_http::{Error, Request, Response}; -use actix_service::Service; -use futures::{Async, Future, Poll}; - -use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler}; -use http::Method; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -// use middleware::Middleware; -// use pipeline::{Pipeline, PipelineHandler}; -use pred::Predicate; -use resource::Resource; -use router::{ResourceDef, Router}; -// use scope::Scope; -// use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; -use with::WithFactory; - -/// Application -pub struct HttpApplication { - state: Rc, - prefix: String, - prefix_len: usize, - inner: Rc>, - filters: Option>>>, - // middlewares: Rc>>>, -} - -#[doc(hidden)] -pub struct Inner { - router: Router, - encoding: ContentEncoding, -} - -// impl PipelineHandler for Inner { -// #[inline] -// fn encoding(&self) -> ContentEncoding { -// self.encoding -// } - -// fn handle(&self, req: &HttpRequest) -> AsyncResult { -// self.router.handle(req) -// } -// } - -impl HttpApplication { - #[cfg(test)] - pub(crate) fn run(&self, req: Request) -> AsyncResult { - let info = self - .inner - .router - .recognize(&req, &self.state, self.prefix_len); - let req = HttpRequest::new(req, Rc::clone(&self.state), info); - - self.inner.handle(&req) - } -} - -impl Service for HttpApplication { - // type Task = Pipeline>; - type Request = actix_http::Request; - type Response = actix_http::Response; - type Error = Error; - type Future = Box>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, msg: actix_http::Request) -> Self::Future { - let m = { - if self.prefix_len == 0 { - true - } else { - let path = msg.path(); - path.starts_with(&self.prefix) - && (path.len() == self.prefix_len - || path.split_at(self.prefix_len).1.starts_with('/')) - } - }; - if m { - if let Some(ref filters) = self.filters { - //for filter in filters { - // if !filter.check(&msg, &self.state) { - //return Err(msg); - unimplemented!() - // } - //} - } - - let info = self - .inner - .router - .recognize(&msg, &self.state, self.prefix_len); - - let inner = Rc::clone(&self.inner); - // let req = HttpRequest::new(msg, Rc::clone(&self.state), info); - // Ok(Pipeline::new(req, inner)) - unimplemented!() - } else { - // Err(msg) - unimplemented!() - } - } -} - -struct ApplicationParts { - state: S, - prefix: String, - router: Router, - encoding: ContentEncoding, - // middlewares: Vec>>, - filters: Vec>>, -} - -/// Structure that follows the builder pattern for building application -/// instances. -pub struct App { - parts: Option>, -} - -impl App -where - S: 'static, -{ - /// Set application prefix. - /// - /// Only requests that match the application's prefix get - /// processed by this application. - /// - /// The application prefix always contains a leading slash (`/`). - /// If the supplied prefix does not contain leading slash, it is - /// inserted. - /// - /// Prefix should consist of valid path segments. i.e for an - /// application with the prefix `/app` any request with the paths - /// `/app`, `/app/` or `/app/test` would match, but the path - /// `/application` would not. - /// - /// In the following example only requests with an `/app/` path - /// prefix get handled. Requests with path `/app/test/` would be - /// handled, while requests with the paths `/application` or - /// `/other/...` would return `NOT FOUND`. It is also possible to - /// handle `/app` path, to do this you can register resource for - /// empty string `""` - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .prefix("/app") - /// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path - /// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path - /// .resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .finish(); - /// } - /// ``` - pub fn prefix>(mut self, prefix: P) -> App { - { - let parts = self.parts.as_mut().expect("Use after finish"); - let mut prefix = prefix.into(); - if !prefix.starts_with('/') { - prefix.insert(0, '/') - } - parts.router.set_prefix(&prefix); - parts.prefix = prefix; - } - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `App::resource()` method. - /// Handler functions need to accept one request extractor - /// argument. - /// - /// This method could be called multiple times, in that case - /// multiple routes would be registered for same resource path. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .route("/test", http::Method::GET, |_: HttpRequest| { - /// HttpResponse::Ok() - /// }) - /// .route("/test", http::Method::POST, |_: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// }); - /// } - /// ``` - pub fn route(mut self, path: &str, method: Method, f: F) -> App - where - F: WithFactory, - R: Responder + 'static, - T: FromRequest + 'static, - { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_route(path, method, f); - - self - } - - // /// Configure scope for common root path. - // /// - // /// Scopes collect multiple paths under a common path prefix. - // /// Scope path can contain variable path segments as resources. - // /// - // /// ```rust - // /// # extern crate actix_web; - // /// use actix_web::{http, App, HttpRequest, HttpResponse}; - // /// - // /// fn main() { - // /// let app = App::new().scope("/{project_id}", |scope| { - // /// scope - // /// .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - // /// .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) - // /// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) - // /// }); - // /// } - // /// ``` - // /// - // /// In the above example, three routes get added: - // /// * /{project_id}/path1 - // /// * /{project_id}/path2 - // /// * /{project_id}/path3 - // /// - // pub fn scope(mut self, path: &str, f: F) -> App - // where - // F: FnOnce(Scope) -> Scope, - // { - // let scope = f(Scope::new(path)); - // self.parts - // .as_mut() - // .expect("Use after finish") - // .router - // .register_scope(scope); - // self - // } - - /// Configure resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().resource("/users/{userid}/{friend}", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> App - where - F: FnOnce(&mut Resource) -> R + 'static, - { - { - let parts = self.parts.as_mut().expect("Use after finish"); - - // create resource - let mut resource = Resource::new(ResourceDef::new(path)); - - // configure - f(&mut resource); - - parts.router.register_resource(resource); - } - self - } - - /// Configure resource for a specific path. - #[doc(hidden)] - pub fn register_resource(&mut self, resource: Resource) { - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_resource(resource); - } - - /// Default resource to be used if no matching route could be found. - pub fn default_resource(mut self, f: F) -> App - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // create and configure default resource - let mut resource = Resource::new(ResourceDef::new("")); - f(&mut resource); - - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_default_resource(resource.into()); - - self - } - - /// Configure handler for specific path prefix. - /// - /// A path prefix consists of valid path segments, i.e for the - /// prefix `/app` any request with the paths `/app`, `/app/` or - /// `/app/test` would match, but the path `/application` would - /// not. - /// - /// Path tail is available as `tail` parameter in request's match_dict. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().handler("/app", |req: &HttpRequest| match *req.method() { - /// http::Method::GET => HttpResponse::Ok(), - /// http::Method::POST => HttpResponse::MethodNotAllowed(), - /// _ => HttpResponse::NotFound(), - /// }); - /// } - /// ``` - pub fn handler>(mut self, path: &str, handler: H) -> App { - { - let mut path = path.trim().trim_right_matches('/').to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - self.parts - .as_mut() - .expect("Use after finish") - .router - .register_handler(&path, Box::new(WrapHandler::new(handler)), None); - } - self - } - - // /// Register a middleware. - // pub fn middleware>(mut self, mw: M) -> App { - // self.parts - // .as_mut() - // .expect("Use after finish") - // .middlewares - // .push(Box::new(mw)); - // self - // } - - /// Run external configuration as part of the application building - /// process - /// - /// This function is useful for moving parts of configuration to a - /// different module or event library. For example we can move - /// some of the resources' configuration to different module. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{fs, middleware, App, HttpResponse}; - /// - /// // this function could be located in different module - /// fn config(app: App) -> App { - /// app.resource("/test", |r| { - /// r.get().f(|_| HttpResponse::Ok()); - /// r.head().f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .middleware(middleware::Logger::default()) - /// .configure(config) // <- register resources - /// .handler("/static", fs::StaticFiles::new(".").unwrap()); - /// } - /// ``` - pub fn configure(self, cfg: F) -> App - where - F: Fn(App) -> App, - { - cfg(self) - } - - /// Finish application configuration and create `HttpHandler` object. - pub fn finish(&mut self) -> HttpApplication { - let mut parts = self.parts.take().expect("Use after finish"); - let prefix = parts.prefix.trim().trim_right_matches('/'); - parts.router.finish(); - - let inner = Rc::new(Inner { - router: parts.router, - encoding: parts.encoding, - }); - let filters = if parts.filters.is_empty() { - None - } else { - Some(parts.filters) - }; - - HttpApplication { - inner, - filters, - state: Rc::new(parts.state), - // middlewares: Rc::new(parts.middlewares), - prefix: prefix.to_owned(), - prefix_len: prefix.len(), - } - } - - // /// Convenience method for creating `Box` instances. - // /// - // /// This method is useful if you need to register multiple - // /// application instances with different state. - // /// - // /// ```rust - // /// # use std::thread; - // /// # extern crate actix_web; - // /// use actix_web::{server, App, HttpResponse}; - // /// - // /// struct State1; - // /// - // /// struct State2; - // /// - // /// fn main() { - // /// # thread::spawn(|| { - // /// server::new(|| { - // /// vec![ - // /// App::with_state(State1) - // /// .prefix("/app1") - // /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - // /// .boxed(), - // /// App::with_state(State2) - // /// .prefix("/app2") - // /// .resource("/", |r| r.f(|r| HttpResponse::Ok())) - // /// .boxed(), - // /// ] - // /// }).bind("127.0.0.1:8080") - // /// .unwrap() - // /// .run() - // /// # }); - // /// } - // /// ``` - // pub fn boxed(mut self) -> Box>> { - // Box::new(BoxedApplication { app: self.finish() }) - // } -} - -// struct BoxedApplication { -// app: HttpApplication, -// } - -// impl HttpHandler for BoxedApplication { -// type Task = Box; - -// fn handle(&self, req: Request) -> Result { -// self.app.handle(req).map(|t| { -// let task: Self::Task = Box::new(t); -// task -// }) -// } -// } - -// impl IntoHttpHandler for App { -// type Handler = HttpApplication; - -// fn into_handler(mut self) -> HttpApplication { -// self.finish() -// } -// } - -// impl<'a, S: 'static> IntoHttpHandler for &'a mut App { -// type Handler = HttpApplication; - -// fn into_handler(self) -> HttpApplication { -// self.finish() -// } -// } - -// #[doc(hidden)] -// impl Iterator for App { -// type Item = HttpApplication; - -// fn next(&mut self) -> Option { -// if self.parts.is_some() { -// Some(self.finish()) -// } else { -// None -// } -// } -// } - -#[cfg(test)] -mod tests { - use super::*; - use body::{Binary, Body}; - use http::StatusCode; - use httprequest::HttpRequest; - use httpresponse::HttpResponse; - use pred; - use test::{TestRequest, TestServer}; - - #[test] - fn test_default_resource() { - let app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let app = App::new() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed())) - .finish(); - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[test] - fn test_unhandled_prefix() { - let app = App::new() - .prefix("/test") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let ctx = TestRequest::default().request(); - assert!(app.handle(ctx).is_err()); - } - - #[test] - fn test_state() { - let app = App::with_state(10) - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let req = TestRequest::with_state(10).request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - } - - #[test] - fn test_prefix() { - let app = App::new() - .prefix("/test") - .resource("/blah", |r| r.f(|_| HttpResponse::Ok())) - .finish(); - let req = TestRequest::with_uri("/test").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/test/blah").request(); - let resp = app.handle(req); - assert!(resp.is_ok()); - - let req = TestRequest::with_uri("/testing").request(); - let resp = app.handle(req); - assert!(resp.is_err()); - } - - #[test] - fn test_handler() { - let app = App::new() - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler2() { - let app = App::new() - .handler("test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler_with_prefix() { - let app = App::new() - .prefix("prefix") - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/prefix/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/prefix/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/prefix/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_route() { - let app = App::new() - .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) - .route("/test", Method::POST, |_: HttpRequest| { - HttpResponse::Created() - }) - .finish(); - - let req = TestRequest::with_uri("/test").method(Method::GET).request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_handler_prefix() { - let app = App::new() - .prefix("/app") - .handler("/test", |_: &_| HttpResponse::Ok()) - .finish(); - - let req = TestRequest::with_uri("/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/test").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/test/").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/test/app").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/testapp").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/blah").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_option_responder() { - let app = App::new() - .resource("/none", |r| r.f(|_| -> Option<&'static str> { None })) - .resource("/some", |r| r.f(|_| Some("some"))) - .finish(); - - let req = TestRequest::with_uri("/none").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/some").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::OK); - assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some"))); - } - - #[test] - fn test_filter() { - let mut srv = TestServer::with_factory(|| { - App::new() - .filter(pred::Get()) - .handler("/test", |_: &_| HttpResponse::Ok()) - }); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv.post().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - } - - #[test] - fn test_prefix_root() { - let mut srv = TestServer::with_factory(|| { - App::new() - .prefix("/test") - .resource("/", |r| r.f(|_| HttpResponse::Ok())) - .resource("", |r| r.f(|_| HttpResponse::Created())) - }); - - let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::CREATED); - } - -} diff --git a/src/lib.rs b/src/lib.rs index b4b12eb1..74fa0a94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ mod app; mod extractor; pub mod handler; -// mod helpers; // mod info; pub mod blocking; pub mod filter; diff --git a/src/responder.rs b/src/responder.rs index 5520c610..b3ec7ec7 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,13 +1,11 @@ -use actix_http::dev::ResponseBuilder; -use actix_http::http::StatusCode; -use actix_http::{Error, Response}; +use actix_http::{dev::ResponseBuilder, http::StatusCode, Error, Response}; use bytes::{Bytes, BytesMut}; use futures::future::{err, ok, Either as EitherFuture, FutureResult}; use futures::{Future, Poll}; use crate::request::HttpRequest; -/// Trait implemented by types that generate http responses. +/// Trait implemented by types that can be converted to a http response. /// /// Types that implement this trait can be used as the return type of a handler. pub trait Responder { @@ -72,6 +70,15 @@ impl Responder for ResponseBuilder { } } +impl Responder for () { + type Error = Error; + type Future = FutureResult; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + ok(Response::build(StatusCode::OK).finish()) + } +} + impl Responder for &'static str { type Error = Error; type Future = FutureResult; @@ -167,12 +174,7 @@ impl Responder for BytesMut { /// # fn is_a_variant() -> bool { true } /// # fn main() {} /// ``` -pub enum Either { - /// First branch of the type - A(A), - /// Second branch of the type - B(B), -} +pub type Either = either::Either; impl Responder for Either where @@ -184,8 +186,8 @@ where fn respond_to(self, req: &HttpRequest) -> Self::Future { match self { - Either::A(a) => EitherResponder::A(a.respond_to(req)), - Either::B(b) => EitherResponder::B(b.respond_to(req)), + either::Either::Left(a) => EitherResponder::A(a.respond_to(req)), + either::Either::Right(b) => EitherResponder::B(b.respond_to(req)), } } } From cc20fee62884d70990fbf0d0b4013172d8e2759d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 11:53:05 -0800 Subject: [PATCH 0938/1635] add request chain services --- src/app.rs | 480 ++++++++++++++++++++++++++----------- src/blocking.rs | 12 +- src/extractor.rs | 1 + src/lib.rs | 4 +- src/middleware/compress.rs | 3 - src/route.rs | 10 +- 6 files changed, 360 insertions(+), 150 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7c077706..78f718db 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; -use actix_http::body::{Body, MessageBody}; +use actix_http::body::MessageBody; use actix_http::{Extensions, PayloadStream, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; @@ -30,32 +30,45 @@ pub trait HttpServiceFactory { } /// Application builder -pub struct App { - services: Vec<(ResourceDef, HttpNewService

    )>, - default: Option>>, - defaults: Vec>>>>>, - endpoint: T, - factory_ref: Rc>>>, +pub struct App +where + T: NewService, Response = ServiceRequest

    >, +{ + chain: T, extensions: Extensions, state: Vec>, - _t: PhantomData<(P, B)>, + _t: PhantomData<(P,)>, } -impl App> { - /// Create application with empty state. Application can +impl App { + /// Create application builder with empty state. Application can /// be configured with a builder-like pattern. pub fn new() -> Self { - App::create() + App { + chain: AppChain, + extensions: Extensions::new(), + state: Vec::new(), + _t: PhantomData, + } } } -impl Default for App> { +impl Default for App { fn default() -> Self { App::new() } } -impl App> { +impl App +where + P: 'static, + T: NewService< + Request = ServiceRequest, + Response = ServiceRequest

    , + Error = (), + InitError = (), + >, +{ /// Create application with specified state. Application can be /// configured with a builder-like pattern. /// @@ -86,38 +99,172 @@ impl App> { self } - fn create() -> Self { + /// Configure resource for a specific path. + /// + /// Resources may have variable path segments. For example, a + /// resource with the path `/a/{name}/c` would match all incoming + /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. + /// + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. This is done by + /// looking up the identifier in the `Params` object returned by + /// `HttpRequest.match_info()` method. + /// + /// By default, each segment matches the regular expression `[^{}/]+`. + /// + /// You can also specify a custom regex in the form `{identifier:regex}`: + /// + /// For instance, to route `GET`-requests on any route matching + /// `/users/{userid}/{friend}` and store `userid` and `friend` in + /// the exposed `Params` object: + /// + /// ```rust,ignore + /// # extern crate actix_web; + /// use actix_web::{http, App, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().resource("/users/{userid}/{friend}", |r| { + /// r.get(|r| r.to(|_| HttpResponse::Ok())); + /// r.head(|r| r.to(|_| HttpResponse::MethodNotAllowed())) + /// }); + /// } + /// ``` + pub fn resource(self, path: &str, f: F) -> AppRouter> + where + F: FnOnce(Resource

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

    , + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, + { + let rdef = ResourceDef::new(path); + let resource = f(Resource::new()); + let default = resource.get_default(); + let fref = Rc::new(RefCell::new(None)); + AppRouter { + chain: self.chain, + services: vec![(rdef, boxed::new_service(resource.into_new_service()))], + default: None, + defaults: vec![default], + endpoint: AppEntry::new(fref.clone()), + factory_ref: fref, + extensions: self.extensions, + state: self.state, + _t: PhantomData, + } + } + + /// Register a middleware. + pub fn middleware( + self, + mw: F, + ) -> AppRouter< + T, + P, + B, + impl NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, + > + where + M: NewTransform< + AppService

    , + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, + B: MessageBody, + F: IntoNewTransform>, + { + let fref = Rc::new(RefCell::new(None)); + let endpoint = ApplyNewService::new(mw, AppEntry::new(fref.clone())); + AppRouter { + endpoint, + chain: self.chain, + state: self.state, + services: Vec::new(), + default: None, + defaults: Vec::new(), + factory_ref: fref, + extensions: self.extensions, + _t: PhantomData, + } + } + + /// Register a request modifier. It can modify any request parameters + /// including payload stream. + pub fn chain( + self, + chain: C, + ) -> App< + P1, + impl NewService< + Request = ServiceRequest, + Response = ServiceRequest, + Error = (), + InitError = (), + >, + > + where + C: NewService< + (), + Request = ServiceRequest

    , + Response = ServiceRequest, + Error = (), + InitError = (), + >, + F: IntoNewService, + { + let chain = self.chain.and_then(chain.into_new_service()); App { + chain, + state: self.state, + extensions: self.extensions, + _t: PhantomData, + } + } + + /// Complete applicatin chain configuration and start resource + /// configuration. + pub fn router(self) -> AppRouter> { + let fref = Rc::new(RefCell::new(None)); + AppRouter { + chain: self.chain, services: Vec::new(), default: None, defaults: Vec::new(), endpoint: AppEntry::new(fref.clone()), factory_ref: fref, - extensions: Extensions::new(), - state: Vec::new(), + extensions: self.extensions, + state: self.state, _t: PhantomData, } } } -// /// Application router builder -// pub struct AppRouter { -// services: Vec<( -// ResourceDef, -// BoxedHttpNewService, Response>, -// )>, -// default: Option, Response>>>, -// defaults: -// Vec, Response>>>>>>, -// state: AppState, -// endpoint: T, -// factory_ref: Rc>>>, -// extensions: Extensions, -// _t: PhantomData

    , -// } +/// Structure that follows the builder pattern for building application +/// instances. +pub struct AppRouter { + chain: C, + services: Vec<(ResourceDef, HttpNewService

    )>, + default: Option>>, + defaults: Vec>>>>>, + endpoint: T, + factory_ref: Rc>>>, + extensions: Extensions, + state: Vec>, + _t: PhantomData<(P, B)>, +} -impl App +impl AppRouter where P: 'static, B: MessageBody, @@ -221,7 +368,8 @@ where pub fn middleware( self, mw: F, - ) -> App< + ) -> AppRouter< + C, P, B1, impl NewService< @@ -243,14 +391,15 @@ where F: IntoNewTransform, { let endpoint = ApplyNewService::new(mw, self.endpoint); - App { + AppRouter { endpoint, + chain: self.chain, state: self.state, services: self.services, default: self.default, - defaults: Vec::new(), + defaults: self.defaults, factory_ref: self.factory_ref, - extensions: Extensions::new(), + extensions: self.extensions, _t: PhantomData, } } @@ -292,8 +441,8 @@ where } } -impl - IntoNewService, T, ()>> for App +impl + IntoNewService, T, ()>> for AppRouter where T: NewService< Request = ServiceRequest

    , @@ -301,8 +450,14 @@ where Error = (), InitError = (), >, + C: NewService< + Request = ServiceRequest, + Response = ServiceRequest

    , + Error = (), + InitError = (), + >, { - fn into_new_service(self) -> AndThenNewService, T, ()> { + fn into_new_service(self) -> AndThenNewService, T, ()> { // update resource default service if self.default.is_some() { for default in &self.defaults { @@ -317,99 +472,15 @@ where services: Rc::new(self.services), }); - AppStateFactory { + AppInit { + chain: self.chain, state: self.state, extensions: Rc::new(RefCell::new(Rc::new(self.extensions))), - _t: PhantomData, } .and_then(self.endpoint) } } -/// Service factory to convert `Request` to a `ServiceRequest` -pub struct AppStateFactory

    { - state: Vec>, - extensions: Rc>>, - _t: PhantomData

    , -} - -impl NewService for AppStateFactory

    { - type Request = Request

    ; - type Response = ServiceRequest

    ; - type Error = (); - type InitError = (); - type Service = AppStateService

    ; - type Future = AppStateFactoryResult

    ; - - fn new_service(&self, _: &()) -> Self::Future { - AppStateFactoryResult { - state: self.state.iter().map(|s| s.construct()).collect(), - extensions: self.extensions.clone(), - _t: PhantomData, - } - } -} - -#[doc(hidden)] -pub struct AppStateFactoryResult

    { - state: Vec>, - extensions: Rc>>, - _t: PhantomData

    , -} - -impl

    Future for AppStateFactoryResult

    { - type Item = AppStateService

    ; - type Error = (); - - fn poll(&mut self) -> Poll { - if let Some(extensions) = Rc::get_mut(&mut *self.extensions.borrow_mut()) { - let mut idx = 0; - while idx < self.state.len() { - if let Async::Ready(_) = self.state[idx].poll_result(extensions)? { - self.state.remove(idx); - } else { - idx += 1; - } - } - if !self.state.is_empty() { - return Ok(Async::NotReady); - } - } else { - log::warn!("Multiple copies of app extensions exists"); - } - - Ok(Async::Ready(AppStateService { - extensions: self.extensions.borrow().clone(), - _t: PhantomData, - })) - } -} - -/// Service to convert `Request` to a `ServiceRequest` -pub struct AppStateService

    { - extensions: Rc, - _t: PhantomData

    , -} - -impl

    Service for AppStateService

    { - type Request = Request

    ; - type Response = ServiceRequest

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

    ) -> Self::Future { - ok(ServiceRequest::new( - Path::new(Url::new(req.uri().clone())), - req, - self.extensions.clone(), - )) - } -} - pub struct AppFactory

    { services: Rc)>>, } @@ -530,17 +601,6 @@ impl

    Service for AppService

    { } } -pub struct AppServiceResponse(Box>); - -impl Future for AppServiceResponse { - type Item = ServiceResponse; - type Error = (); - - fn poll(&mut self) -> Poll { - self.0.poll().map_err(|_| panic!()) - } -} - #[doc(hidden)] pub struct AppEntry

    { factory: Rc>>>, @@ -564,3 +624,151 @@ impl NewService for AppEntry

    { self.factory.borrow_mut().as_mut().unwrap().new_service(&()) } } + +#[doc(hidden)] +pub struct AppChain; + +impl NewService<()> for AppChain { + type Request = ServiceRequest; + type Response = ServiceRequest; + type Error = (); + type InitError = (); + type Service = AppChain; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(AppChain) + } +} + +impl Service for AppChain { + type Request = ServiceRequest; + type Response = ServiceRequest; + type Error = (); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + ok(req) + } +} + +/// Service factory to convert `Request` to a `ServiceRequest` +pub struct AppInit +where + C: NewService, Response = ServiceRequest

    >, +{ + chain: C, + state: Vec>, + extensions: Rc>>, +} + +impl NewService for AppInit +where + C: NewService< + Request = ServiceRequest, + Response = ServiceRequest

    , + InitError = (), + >, +{ + type Request = Request; + type Response = ServiceRequest

    ; + type Error = C::Error; + type InitError = C::InitError; + type Service = AppInitService; + type Future = AppInitResult; + + fn new_service(&self, _: &()) -> Self::Future { + AppInitResult { + chain: self.chain.new_service(&()), + state: self.state.iter().map(|s| s.construct()).collect(), + extensions: self.extensions.clone(), + } + } +} + +#[doc(hidden)] +pub struct AppInitResult +where + C: NewService< + Request = ServiceRequest, + Response = ServiceRequest

    , + InitError = (), + >, +{ + chain: C::Future, + state: Vec>, + extensions: Rc>>, +} + +impl Future for AppInitResult +where + C: NewService< + Request = ServiceRequest, + Response = ServiceRequest

    , + InitError = (), + >, +{ + type Item = AppInitService; + type Error = C::InitError; + + fn poll(&mut self) -> Poll { + if let Some(extensions) = Rc::get_mut(&mut *self.extensions.borrow_mut()) { + let mut idx = 0; + while idx < self.state.len() { + if let Async::Ready(_) = self.state[idx].poll_result(extensions)? { + self.state.remove(idx); + } else { + idx += 1; + } + } + if !self.state.is_empty() { + return Ok(Async::NotReady); + } + } else { + log::warn!("Multiple copies of app extensions exists"); + } + + let chain = futures::try_ready!(self.chain.poll()); + + Ok(Async::Ready(AppInitService { + chain, + extensions: self.extensions.borrow().clone(), + })) + } +} + +/// Service to convert `Request` to a `ServiceRequest` +pub struct AppInitService +where + C: Service, Response = ServiceRequest

    >, +{ + chain: C, + extensions: Rc, +} + +impl Service for AppInitService +where + C: Service, Response = ServiceRequest

    >, +{ + type Request = Request; + type Response = ServiceRequest

    ; + type Error = C::Error; + type Future = C::Future; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.chain.poll_ready() + } + + fn call(&mut self, req: Request) -> Self::Future { + let req = ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + self.extensions.clone(), + ); + self.chain.call(req) + } +} diff --git a/src/blocking.rs b/src/blocking.rs index fc2624f6..abf4282c 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -24,7 +24,7 @@ lazy_static::lazy_static! { Mutex::new( threadpool::Builder::new() .thread_name("actix-web".to_owned()) - .num_threads(8) + .num_threads(default) .build(), ) }; @@ -45,11 +45,15 @@ pub enum BlockingError { /// to result of the function execution. pub fn run(f: F) -> CpuFuture where - F: FnOnce() -> Result, + F: FnOnce() -> Result + Send + 'static, + I: Send + 'static, + E: Send + 'static, { let (tx, rx) = oneshot::channel(); - POOL.with(move |pool| { - let _ = tx.send(f()); + POOL.with(|pool| { + pool.execute(move || { + let _ = tx.send(f()); + }) }); CpuFuture { rx } diff --git a/src/extractor.rs b/src/extractor.rs index 53209ad0..48c70b86 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::{fmt, str}; diff --git a/src/lib.rs b/src/lib.rs index 74fa0a94..c70f47e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(clippy::type_complexity, dead_code, unused_variables)] +#![allow(clippy::type_complexity)] mod app; mod extractor; @@ -28,7 +28,7 @@ pub use crate::service::{ServiceRequest, ServiceResponse}; pub use crate::state::State; pub mod dev { - pub use crate::app::AppService; + pub use crate::app::AppRouter; pub use crate::handler::{AsyncFactory, Extract, Factory, Handle}; pub use crate::route::{Route, RouteBuilder}; // pub use crate::info::ConnectionInfo; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index fee17ce1..5d5586cf 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -117,7 +117,6 @@ where enum EncoderBody { Body(B), Other(Box), - None, } pub struct Encoder { @@ -131,7 +130,6 @@ impl MessageBody for Encoder { match self.body { EncoderBody::Body(ref b) => b.length(), EncoderBody::Other(ref b) => b.length(), - EncoderBody::None => BodyLength::Empty, } } else { BodyLength::Stream @@ -143,7 +141,6 @@ impl MessageBody for Encoder { let result = match self.body { EncoderBody::Body(ref mut b) => b.poll_next()?, EncoderBody::Other(ref mut b) => b.poll_next()?, - EncoderBody::None => return Ok(Async::Ready(None)), }; match result { Async::NotReady => return Ok(Async::NotReady), diff --git a/src/route.rs b/src/route.rs index 574e8e34..4abb2f1d 100644 --- a/src/route.rs +++ b/src/route.rs @@ -320,11 +320,11 @@ impl RouteBuilder

    { } } -pub struct RouteServiceBuilder { - service: T, - filters: Vec>, - _t: PhantomData<(P, U1, U2)>, -} +// pub struct RouteServiceBuilder { +// service: T, +// filters: Vec>, +// _t: PhantomData<(P, U1, U2)>, +// } // impl RouteServiceBuilder // where From 75fbb9748051db5fdb70f8cbc0cb5d1e111ded15 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 13:57:00 -0800 Subject: [PATCH 0939/1635] update new transform trait --- src/handler.rs | 9 ++++----- src/middleware/mod.rs | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index e957d15e..8076d4d4 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,8 +1,7 @@ use std::marker::PhantomData; use actix_http::{Error, Response}; -use actix_service::{NewService, Service}; -use actix_utils::Never; +use actix_service::{NewService, Service, Void}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; @@ -71,7 +70,7 @@ where { type Request = (T, HttpRequest); type Response = ServiceResponse; - type Error = Never; + type Error = Void; type InitError = (); type Service = HandleService; type Future = FutureResult; @@ -101,7 +100,7 @@ where { type Request = (T, HttpRequest); type Response = ServiceResponse; - type Error = Never; + type Error = Void; type Future = HandleServiceResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -128,7 +127,7 @@ where T::Error: Into, { type Item = ServiceResponse; - type Error = Never; + type Error = Void; fn poll(&mut self) -> Poll { match self.fut.poll() { diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index a8b4b3c6..8ef316b4 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -47,7 +47,7 @@ where } } -impl NewTransform for MiddlewareFactory +impl NewTransform for MiddlewareFactory where T: Transform + Clone, S: Service, @@ -59,7 +59,7 @@ where type InitError = (); type Future = FutureResult; - fn new_transform(&self) -> Self::Future { + fn new_transform(&self, _: &C) -> Self::Future { ok(self.tr.clone()) } } From 3454812b687d2d22e4f1148d624574cf829aef32 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 13:59:12 -0800 Subject: [PATCH 0940/1635] rename actix-web-fs crate --- Cargo.toml | 2 +- {actix-web-fs => staticfiles}/CHANGES.md | 0 {actix-web-fs => staticfiles}/Cargo.toml | 4 ++-- {actix-web-fs => staticfiles}/README.md | 0 {actix-web-fs => staticfiles}/src/lib.rs | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename {actix-web-fs => staticfiles}/CHANGES.md (100%) rename {actix-web-fs => staticfiles}/Cargo.toml (95%) rename {actix-web-fs => staticfiles}/README.md (100%) rename {actix-web-fs => staticfiles}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index bfd06101..fa654686 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ path = "src/lib.rs" [workspace] members = [ ".", - "actix-web-fs", + "staticfiles", ] [features] diff --git a/actix-web-fs/CHANGES.md b/staticfiles/CHANGES.md similarity index 100% rename from actix-web-fs/CHANGES.md rename to staticfiles/CHANGES.md diff --git a/actix-web-fs/Cargo.toml b/staticfiles/Cargo.toml similarity index 95% rename from actix-web-fs/Cargo.toml rename to staticfiles/Cargo.toml index 5900a936..c2516302 100644 --- a/actix-web-fs/Cargo.toml +++ b/staticfiles/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "actix-web-fs" +name = "actix-staticfiles" version = "0.1.0" authors = ["Nikolay Kim "] description = "Static files support for Actix web." @@ -14,7 +14,7 @@ edition = "2018" workspace = ".." [lib] -name = "actix_web_fs" +name = "actix_staticfiles" path = "src/lib.rs" [dependencies] diff --git a/actix-web-fs/README.md b/staticfiles/README.md similarity index 100% rename from actix-web-fs/README.md rename to staticfiles/README.md diff --git a/actix-web-fs/src/lib.rs b/staticfiles/src/lib.rs similarity index 100% rename from actix-web-fs/src/lib.rs rename to staticfiles/src/lib.rs From 9394a4e2a5513de678d17fd2669d5810254bcd71 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 14:07:21 -0800 Subject: [PATCH 0941/1635] cleanup dependencies --- Cargo.toml | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fa654686..d5b2fa3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" -documentation = "https://actix.rs/api/actix-web/stable/actix_web/" +documentation = "https://docs.rs/actix-web/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] @@ -16,8 +16,9 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" [badges] -travis-ci = { repository = "actix/actix-web2", branch = "master" } -codecov = { repository = "actix/actix-web2", branch = "master", service = "github" } +travis-ci = { repository = "actix/actix-web", branch = "master" } +appveyor = { repository = "fafhrd91/actix-web-hdy9d" } +codecov = { repository = "actix/actix-web", branch = "master", service = "github" } [lib] name = "actix_web" @@ -46,34 +47,24 @@ actix-codec = "0.1.0" #actix-service = "0.2.1" #actix-server = "0.2.1" #actix-utils = "0.2.1" -actix-service = { git = "https://github.com/actix/actix-net.git" } -actix-server = { git = "https://github.com/actix/actix-net.git" } actix-utils = { git = "https://github.com/actix/actix-net.git" } - -actix-rt = "0.1.0" actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } +actix-service = { git = "https://github.com/actix/actix-net.git" } bytes = "0.4" -futures = "0.1" derive_more = "0.14" either = "1.5.1" +encoding = "0.2" +futures = "0.1" log = "0.4" lazy_static = "1.2" mime = "0.3" -mime_guess = "2.0.0-alpha" num_cpus = "1.10" -percent-encoding = "1.0" -cookie = { version="0.11", features=["percent-encode"] } -v_htmlescape = "0.4" +parking_lot = "0.7" serde = "1.0" serde_json = "1.0" -encoding = "0.2" serde_urlencoded = "^0.5.3" -parking_lot = "0.7" -hashbrown = "0.1" -regex = "1" -time = "0.1" threadpool = "1.7" # compression From de9b38295f2778e1f5a62e6971727257727679ab Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 15:08:10 -0800 Subject: [PATCH 0942/1635] update deps --- Cargo.toml | 20 +++++--------------- test-server/Cargo.toml | 10 +++------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index edf572af..403f3303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,18 +37,10 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -#actix-service = "0.2.1" +actix-service = "0.3.0" actix-codec = "0.1.0" -#actix-connector = "0.2.0" -#actix-utils = "0.2.2" - -actix-service = { git = "https://github.com/actix/actix-net" } -actix-connector = { git = "https://github.com/actix/actix-net" } -actix-utils = { git = "https://github.com/actix/actix-net" } - -#actix-service = { path = "../actix-net/actix-service" } -#actix-connector = { path = "../actix-net/actix-connector" } -#actix-utils = { path = "../actix-net/actix-utils" } +actix-connector = "0.3.0" +actix-utils = "0.3.0" base64 = "0.10" backtrace = "0.3" @@ -86,10 +78,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" -actix-server = { git = "https://github.com/actix/actix-net", features=["ssl"] } -#actix-server = { path = "../actix-net/actix-server", features=["ssl"] } -#actix-connector = { path = "../actix-net/actix-connector", features=["ssl"] } -actix-connector = { git = "https://github.com/actix/actix-net", features=["ssl"] } +actix-server = { version = "0.3.0", features=["ssl"] } +actix-connector = { version = "0.3.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index f5e8afd2..cf4364c4 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -35,13 +35,9 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] actix-codec = "0.1" actix-rt = "0.1.0" actix-http = { path=".." } - -#actix-service = "0.2.0" -#actix-server = "0.2.0" -#actix-utils = "0.2.0" -actix-service = { git = "https://github.com/actix/actix-net" } -actix-server = { git = "https://github.com/actix/actix-net" } -actix-utils = { git = "https://github.com/actix/actix-net" } +actix-service = "0.3.0" +actix-server = "0.3.0" +actix-utils = "0.3.0" base64 = "0.10" bytes = "0.4" From 0081b9d4467e04a719e1715e8d141e05960ebc40 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 15:59:05 -0800 Subject: [PATCH 0943/1635] improve ergomonics of TestRequest --- src/response.rs | 1 - src/test.rs | 55 +++++++++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/response.rs b/src/response.rs index aab88106..80748746 100644 --- a/src/response.rs +++ b/src/response.rs @@ -669,7 +669,6 @@ impl ResponseBuilder { } #[inline] -#[allow(clippy::borrowed_box)] fn parts<'a>( parts: &'a mut Option>, err: &Option, diff --git a/src/test.rs b/src/test.rs index b0e30872..74e5da71 100644 --- a/src/test.rs +++ b/src/test.rs @@ -37,7 +37,9 @@ use crate::Request; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` -pub struct TestRequest { +pub struct TestRequest(Option); + +struct Inner { version: Version, method: Method, uri: Uri, @@ -49,7 +51,7 @@ pub struct TestRequest { impl Default for TestRequest { fn default() -> TestRequest { - TestRequest { + TestRequest(Some(Inner { method: Method::GET, uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, @@ -57,19 +59,19 @@ impl Default for TestRequest { _cookies: None, payload: None, prefix: 0, - } + })) } } impl TestRequest { /// Create TestRequest and set request uri pub fn with_uri(path: &str) -> TestRequest { - TestRequest::default().uri(path) + TestRequest::default().uri(path).take() } /// Create TestRequest and set header pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest::default().set(hdr) + TestRequest::default().set(hdr).take() } /// Create TestRequest and set header @@ -78,45 +80,45 @@ impl TestRequest { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - TestRequest::default().header(key, value) + TestRequest::default().header(key, value).take() } /// Set HTTP version of this request - pub fn version(mut self, ver: Version) -> Self { - self.version = ver; + pub fn version(&mut self, ver: Version) -> &mut Self { + parts(&mut self.0).version = ver; self } /// Set HTTP method of this request - pub fn method(mut self, meth: Method) -> Self { - self.method = meth; + pub fn method(&mut self, meth: Method) -> &mut Self { + parts(&mut self.0).method = meth; self } /// Set HTTP Uri of this request - pub fn uri(mut self, path: &str) -> Self { - self.uri = Uri::from_str(path).unwrap(); + pub fn uri(&mut self, path: &str) -> &mut Self { + parts(&mut self.0).uri = Uri::from_str(path).unwrap(); self } /// Set a header - pub fn set(mut self, hdr: H) -> Self { + pub fn set(&mut self, hdr: H) -> &mut Self { if let Ok(value) = hdr.try_into() { - self.headers.append(H::name(), value); + parts(&mut self.0).headers.append(H::name(), value); return self; } panic!("Can not set header"); } /// Set a header - pub fn header(mut self, key: K, value: V) -> Self + pub fn header(&mut self, key: K, value: V) -> &mut Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { if let Ok(key) = HeaderName::try_from(key) { if let Ok(value) = value.try_into() { - self.headers.append(key, value); + parts(&mut self.0).headers.append(key, value); return self; } } @@ -124,29 +126,27 @@ impl TestRequest { } /// Set request payload - pub fn set_payload>(mut self, data: B) -> Self { + pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); payload.unread_data(data.into()); - self.payload = Some(payload.into()); + parts(&mut self.0).payload = Some(payload.into()); self } - /// Set request's prefix - pub fn prefix(mut self, prefix: u16) -> Self { - self.prefix = prefix; - self + pub(crate) fn take(&mut self) -> TestRequest { + TestRequest(self.0.take()) } /// Complete request creation and generate `Request` instance - pub fn finish(self) -> Request { - let TestRequest { + pub fn finish(&mut self) -> Request { + let Inner { method, uri, version, headers, payload, .. - } = self; + } = self.0.take().expect("cannot reuse test request builder");; let mut req = if let Some(pl) = payload { Request::with_payload(pl) @@ -251,3 +251,8 @@ impl TestRequest { // } // } } + +#[inline] +fn parts<'a>(parts: &'a mut Option) -> &'a mut Inner { + parts.as_mut().expect("cannot reuse test request builder") +} From 00ea195601d9d1daf2336329c18154038097c6dc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 16:04:43 -0800 Subject: [PATCH 0944/1635] TestRequest::take public --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 74e5da71..4b925d02 100644 --- a/src/test.rs +++ b/src/test.rs @@ -133,7 +133,7 @@ impl TestRequest { self } - pub(crate) fn take(&mut self) -> TestRequest { + pub fn take(&mut self) -> TestRequest { TestRequest(self.0.take()) } From e4198a037a9d42d1546791c62f3605dec5909dab Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 16:24:14 -0800 Subject: [PATCH 0945/1635] add TestServiceRequest builder --- Cargo.toml | 11 +- src/app.rs | 13 ++- src/filter.rs | 288 ++++++++++++++++++++++++++------------------------ src/lib.rs | 1 + src/route.rs | 2 +- src/test.rs | 173 ++++++++++++------------------ 6 files changed, 229 insertions(+), 259 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5b2fa3f..30d23d02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,13 +44,11 @@ flate2-rust = ["flate2/rust_backend"] [dependencies] actix-codec = "0.1.0" -#actix-service = "0.2.1" -#actix-server = "0.2.1" -#actix-utils = "0.2.1" -actix-utils = { git = "https://github.com/actix/actix-net.git" } +actix-service = "0.3.0" +actix-utils = "0.3.0" + actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } -actix-service = { git = "https://github.com/actix/actix-net.git" } bytes = "0.4" derive_more = "0.14" @@ -73,8 +71,7 @@ flate2 = { version="^1.0.2", optional = true, default-features = false } [dev-dependencies] actix-rt = "0.1.0" -#actix-server = { version="0.2", features=["ssl"] } -actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] } +actix-server = { version="0.3.0", features=["ssl"] } actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } rand = "0.6" diff --git a/src/app.rs b/src/app.rs index 78f718db..2e45f2d3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; -use actix_http::body::MessageBody; +use actix_http::body::{Body, MessageBody}; use actix_http::{Extensions, PayloadStream, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; @@ -130,7 +130,7 @@ where /// }); /// } /// ``` - pub fn resource(self, path: &str, f: F) -> AppRouter> + pub fn resource(self, path: &str, f: F) -> AppRouter> where F: FnOnce(Resource

    ) -> Resource, U: NewService< @@ -159,16 +159,16 @@ where } /// Register a middleware. - pub fn middleware( + pub fn middleware( self, mw: F, ) -> AppRouter< T, P, - B, + Body, impl NewService< Request = ServiceRequest

    , - Response = ServiceResponse, + Response = ServiceResponse, Error = (), InitError = (), >, @@ -177,11 +177,10 @@ where M: NewTransform< AppService

    , Request = ServiceRequest

    , - Response = ServiceResponse, + Response = ServiceResponse, Error = (), InitError = (), >, - B: MessageBody, F: IntoNewTransform>, { let fref = Rc::new(RefCell::new(None)); diff --git a/src/filter.rs b/src/filter.rs index a0566092..37c23d73 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -16,14 +16,13 @@ pub trait Filter { /// Return filter that matches if any of supplied filters. /// /// ```rust,ignore -/// # extern crate actix_web; -/// use actix_web2::{filter, App, HttpResponse}; +/// use actix_web::{filter, App, HttpResponse}; /// /// fn main() { /// App::new().resource("/index.html", |r| { /// r.route() /// .filter(pred::Any(pred::Get()).or(pred::Post())) -/// .f(|r| HttpResponse::MethodNotAllowed()) +/// .to(|| HttpResponse::MethodNotAllowed()) /// }); /// } /// ``` @@ -55,18 +54,18 @@ impl Filter for AnyFilter { /// Return filter that matches if all of supplied filters match. /// -/// ```rust,ignore +/// ```rust /// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; +/// use actix_web::{filter, App, HttpResponse}; /// /// fn main() { /// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter( -/// pred::All(pred::Get()) -/// .and(pred::Header("content-type", "text/plain")), +/// r.route( +/// |r| r.filter( +/// filter::All(filter::Get()) +/// .and(filter::Header("content-type", "text/plain")), /// ) -/// .f(|_| HttpResponse::MethodNotAllowed()) +/// .to(|| HttpResponse::MethodNotAllowed())) /// }); /// } /// ``` @@ -191,137 +190,146 @@ impl Filter for HeaderFilter { } } -/// Return predicate that matches if request contains specified Host name. -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// use actix_web::{pred, App, HttpResponse}; -/// -/// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Host("www.rust-lang.org")) -/// .f(|_| HttpResponse::MethodNotAllowed()) -/// }); -/// } -/// ``` -pub fn Host>(host: H) -> HostFilter { - HostFilter(host.as_ref().to_string(), None) -} +// /// Return predicate that matches if request contains specified Host name. +// /// +// /// ```rust,ignore +// /// # extern crate actix_web; +// /// use actix_web::{pred, App, HttpResponse}; +// /// +// /// fn main() { +// /// App::new().resource("/index.html", |r| { +// /// r.route() +// /// .filter(pred::Host("www.rust-lang.org")) +// /// .f(|_| HttpResponse::MethodNotAllowed()) +// /// }); +// /// } +// /// ``` +// pub fn Host>(host: H) -> HostFilter { +// HostFilter(host.as_ref().to_string(), None) +// } -#[doc(hidden)] -pub struct HostFilter(String, Option); +// #[doc(hidden)] +// pub struct HostFilter(String, Option); -impl HostFilter { - /// Set reuest scheme to match - pub fn scheme>(&mut self, scheme: H) { - self.1 = Some(scheme.as_ref().to_string()) - } -} - -impl Filter for HostFilter { - fn check(&self, _req: &HttpRequest) -> bool { - // let info = req.connection_info(); - // if let Some(ref scheme) = self.1 { - // self.0 == info.host() && scheme == info.scheme() - // } else { - // self.0 == info.host() - // } - false - } -} - -// #[cfg(test)] -// mod tests { -// use actix_http::http::{header, Method}; -// use actix_http::test::TestRequest; - -// use super::*; - -// #[test] -// fn test_header() { -// let req = TestRequest::with_header( -// header::TRANSFER_ENCODING, -// header::HeaderValue::from_static("chunked"), -// ) -// .finish(); - -// let pred = Header("transfer-encoding", "chunked"); -// assert!(pred.check(&req, req.state())); - -// let pred = Header("transfer-encoding", "other"); -// assert!(!pred.check(&req, req.state())); - -// let pred = Header("content-type", "other"); -// assert!(!pred.check(&req, req.state())); -// } - -// #[test] -// fn test_host() { -// let req = TestRequest::default() -// .header( -// header::HOST, -// header::HeaderValue::from_static("www.rust-lang.org"), -// ) -// .finish(); - -// let pred = Host("www.rust-lang.org"); -// assert!(pred.check(&req, req.state())); - -// let pred = Host("localhost"); -// assert!(!pred.check(&req, req.state())); -// } - -// #[test] -// fn test_methods() { -// let req = TestRequest::default().finish(); -// let req2 = TestRequest::default().method(Method::POST).finish(); - -// assert!(Get().check(&req, req.state())); -// assert!(!Get().check(&req2, req2.state())); -// assert!(Post().check(&req2, req2.state())); -// assert!(!Post().check(&req, req.state())); - -// let r = TestRequest::default().method(Method::PUT).finish(); -// assert!(Put().check(&r, r.state())); -// assert!(!Put().check(&req, req.state())); - -// let r = TestRequest::default().method(Method::DELETE).finish(); -// assert!(Delete().check(&r, r.state())); -// assert!(!Delete().check(&req, req.state())); - -// let r = TestRequest::default().method(Method::HEAD).finish(); -// assert!(Head().check(&r, r.state())); -// assert!(!Head().check(&req, req.state())); - -// let r = TestRequest::default().method(Method::OPTIONS).finish(); -// assert!(Options().check(&r, r.state())); -// assert!(!Options().check(&req, req.state())); - -// let r = TestRequest::default().method(Method::CONNECT).finish(); -// assert!(Connect().check(&r, r.state())); -// assert!(!Connect().check(&req, req.state())); - -// let r = TestRequest::default().method(Method::PATCH).finish(); -// assert!(Patch().check(&r, r.state())); -// assert!(!Patch().check(&req, req.state())); - -// let r = TestRequest::default().method(Method::TRACE).finish(); -// assert!(Trace().check(&r, r.state())); -// assert!(!Trace().check(&req, req.state())); -// } - -// #[test] -// fn test_preds() { -// let r = TestRequest::default().method(Method::TRACE).finish(); - -// assert!(Not(Get()).check(&r, r.state())); -// assert!(!Not(Trace()).check(&r, r.state())); - -// assert!(All(Trace()).and(Trace()).check(&r, r.state())); -// assert!(!All(Get()).and(Trace()).check(&r, r.state())); - -// assert!(Any(Get()).or(Trace()).check(&r, r.state())); -// assert!(!Any(Get()).or(Get()).check(&r, r.state())); +// impl HostFilter { +// /// Set reuest scheme to match +// pub fn scheme>(&mut self, scheme: H) { +// self.1 = Some(scheme.as_ref().to_string()) // } // } + +// impl Filter for HostFilter { +// fn check(&self, _req: &HttpRequest) -> bool { +// // let info = req.connection_info(); +// // if let Some(ref scheme) = self.1 { +// // self.0 == info.host() && scheme == info.scheme() +// // } else { +// // self.0 == info.host() +// // } +// false +// } +// } + +#[cfg(test)] +mod tests { + use crate::test::TestServiceRequest; + use actix_http::http::{header, Method}; + + use super::*; + + #[test] + fn test_header() { + let req = TestServiceRequest::with_header(header::TRANSFER_ENCODING, "chunked") + .finish() + .into_request(); + + let pred = Header("transfer-encoding", "chunked"); + assert!(pred.check(&req)); + + let pred = Header("transfer-encoding", "other"); + assert!(!pred.check(&req)); + + let pred = Header("content-type", "other"); + assert!(!pred.check(&req)); + } + + // #[test] + // fn test_host() { + // let req = TestServiceRequest::default() + // .header( + // header::HOST, + // header::HeaderValue::from_static("www.rust-lang.org"), + // ) + // .request(); + + // let pred = Host("www.rust-lang.org"); + // assert!(pred.check(&req)); + + // let pred = Host("localhost"); + // assert!(!pred.check(&req)); + // } + + #[test] + fn test_methods() { + let req = TestServiceRequest::default().finish().into_request(); + let req2 = TestServiceRequest::default() + .method(Method::POST) + .finish() + .into_request(); + + assert!(Get().check(&req)); + assert!(!Get().check(&req2)); + assert!(Post().check(&req2)); + assert!(!Post().check(&req)); + + let r = TestServiceRequest::default().method(Method::PUT).finish(); + assert!(Put().check(&r,)); + assert!(!Put().check(&req,)); + + let r = TestServiceRequest::default() + .method(Method::DELETE) + .finish(); + assert!(Delete().check(&r,)); + assert!(!Delete().check(&req,)); + + let r = TestServiceRequest::default().method(Method::HEAD).finish(); + assert!(Head().check(&r,)); + assert!(!Head().check(&req,)); + + let r = TestServiceRequest::default() + .method(Method::OPTIONS) + .finish(); + assert!(Options().check(&r,)); + assert!(!Options().check(&req,)); + + let r = TestServiceRequest::default() + .method(Method::CONNECT) + .finish(); + assert!(Connect().check(&r,)); + assert!(!Connect().check(&req,)); + + let r = TestServiceRequest::default().method(Method::PATCH).finish(); + assert!(Patch().check(&r,)); + assert!(!Patch().check(&req,)); + + let r = TestServiceRequest::default().method(Method::TRACE).finish(); + assert!(Trace().check(&r,)); + assert!(!Trace().check(&req,)); + } + + #[test] + fn test_preds() { + let r = TestServiceRequest::default() + .method(Method::TRACE) + .request(); + + assert!(Not(Get()).check(&r,)); + assert!(!Not(Trace()).check(&r,)); + + assert!(All(Trace()).and(Trace()).check(&r,)); + assert!(!All(Get()).and(Trace()).check(&r,)); + + assert!(Any(Get()).or(Trace()).check(&r,)); + assert!(!Any(Get()).or(Get()).check(&r,)); + } +} diff --git a/src/lib.rs b/src/lib.rs index c70f47e8..70ce9607 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ mod responder; mod route; mod service; mod state; +pub mod test; // re-export for convenience pub use actix_http::Response as HttpResponse; diff --git a/src/route.rs b/src/route.rs index 4abb2f1d..d5e11424 100644 --- a/src/route.rs +++ b/src/route.rs @@ -180,7 +180,7 @@ impl RouteBuilder

    { /// # .finish(); /// # } /// ``` - pub fn filter(&mut self, f: F) -> &mut Self { + pub fn filter(mut self, f: F) -> Self { self.filters.push(Box::new(f)); self } diff --git a/src/test.rs b/src/test.rs index e13dcd16..d67696b1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,18 +1,15 @@ //! Various helpers for Actix applications to use during testing. -use std::str::FromStr; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; -use bytes::Bytes; -use futures::IntoFuture; -use tokio_current_thread::Runtime; - -use actix_http::dev::Payload; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; -use actix_http::http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use actix_http::Request as HttpRequest; +use actix_http::http::{HttpTryFrom, Method, Version}; +use actix_http::test::TestRequest; +use actix_http::{Extensions, PayloadStream}; use actix_router::{Path, Url}; +use bytes::Bytes; -use crate::app::State; -use crate::request::Request; +use crate::request::HttpRequest; use crate::service::ServiceRequest; /// Test `Request` builder @@ -42,90 +39,71 @@ use crate::service::ServiceRequest; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` -pub struct TestRequest { - state: S, - version: Version, - method: Method, - uri: Uri, - headers: HeaderMap, - params: Path, - payload: Option, +pub struct TestServiceRequest { + req: TestRequest, + extensions: Extensions, } -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: Path::new(Url::default()), - payload: None, +impl Default for TestServiceRequest { + fn default() -> TestServiceRequest { + TestServiceRequest { + req: TestRequest::default(), + extensions: Extensions::new(), } } } -impl TestRequest<()> { +impl TestServiceRequest { /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestRequest<()> { - TestRequest::default().uri(path) + pub fn with_uri(path: &str) -> TestServiceRequest { + TestServiceRequest { + req: TestRequest::default().uri(path).take(), + extensions: Extensions::new(), + } } /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest<()> { - TestRequest::default().set(hdr) + pub fn with_hdr(hdr: H) -> TestServiceRequest { + TestServiceRequest { + req: TestRequest::default().set(hdr).take(), + extensions: Extensions::new(), + } } /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest<()> + pub fn with_header(key: K, value: V) -> TestServiceRequest where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - TestRequest::default().header(key, value) - } -} - -impl TestRequest { - /// Start HttpRequest build process with application state - pub fn with_state(state: S) -> TestRequest { - TestRequest { - state, - method: Method::GET, - uri: Uri::from_str("/").unwrap(), - version: Version::HTTP_11, - headers: HeaderMap::new(), - params: Path::new(Url::default()), - payload: None, + TestServiceRequest { + req: TestRequest::default().header(key, value).take(), + extensions: Extensions::new(), } } /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { - self.version = ver; + self.req.version(ver); self } /// Set HTTP method of this request pub fn method(mut self, meth: Method) -> Self { - self.method = meth; + self.req.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.req.uri(path); self } /// Set a header pub fn set(mut self, hdr: H) -> Self { - if let Ok(value) = hdr.try_into() { - self.headers.append(H::name(), value); - return self; - } - panic!("Can not set header"); + self.req.set(hdr); + self } /// Set a header @@ -134,63 +112,50 @@ impl TestRequest { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into() { - 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_static(name, value); + self.req.header(key, value); self } /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { - let mut payload = Payload::empty(); - payload.unread_data(data.into()); - self.payload = Some(payload); + self.req.set_payload(data); self } - /// Complete request creation and generate `HttpRequest` instance - pub fn finish(self) -> ServiceRequest { - let TestRequest { - state, - method, - uri, - version, - headers, - mut params, - payload, - } = self; + /// Complete request creation and generate `ServiceRequest` instance + pub fn finish(mut self) -> ServiceRequest { + let req = self.req.finish(); - params.get_mut().update(&uri); - - let mut req = HttpRequest::new(); - { - let inner = req.inner_mut(); - inner.head.uri = uri; - inner.head.method = method; - inner.head.version = version; - inner.head.headers = headers; - *inner.payload.borrow_mut() = payload; - } - - Request::new(State::new(state), req, params) + ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + Rc::new(self.extensions), + ) } - /// This method generates `HttpRequest` instance and executes handler - pub fn run_async(self, f: F) -> Result - where - F: FnOnce(&Request) -> R, - R: IntoFuture, - { - let mut rt = Runtime::new().unwrap(); - rt.block_on(f(&self.finish()).into_future()) + /// Complete request creation and generate `HttpRequest` instance + pub fn request(mut self) -> HttpRequest { + let req = self.req.finish(); + + ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + Rc::new(self.extensions), + ) + .into_request() + } +} + +impl Deref for TestServiceRequest { + type Target = TestRequest; + + fn deref(&self) -> &TestRequest { + &self.req + } +} + +impl DerefMut for TestServiceRequest { + fn deref_mut(&mut self) -> &mut TestRequest { + &mut self.req } } From 2d0495093c29b63daeed0a88aba3c7856ccb5733 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 18:37:09 -0800 Subject: [PATCH 0946/1635] add Payload::take method --- src/payload.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/payload.rs b/src/payload.rs index bc40fe80..8f96bab9 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -39,6 +39,13 @@ impl From for Payload { } } +impl Payload { + /// Takes current payload and replaces it with `None` value + fn take(&mut self) -> Payload { + std::mem::replace(self, Payload::None) + } +} + impl Stream for Payload where S: Stream, From 8103d3327068d6ce4de977abbe007b0f104c65de Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 19:19:56 -0800 Subject: [PATCH 0947/1635] use custom request for FromRequest trait --- examples/basic.rs | 10 +-- src/app.rs | 8 +-- src/extractor.rs | 26 ++++---- src/filter.rs | 46 +++++++------ src/handler.rs | 15 +++-- src/lib.rs | 81 ++++++++++++++++++++++- src/request.rs | 6 +- src/resource.rs | 96 ++------------------------- src/route.rs | 83 +++++++++++------------- src/service.rs | 151 ++++++++++++++++++++++++++++++++++++++++--- src/state.rs | 4 +- tests/test_server.rs | 32 +++++---- 12 files changed, 342 insertions(+), 216 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index a18581f9..886efb7b 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -2,7 +2,7 @@ use futures::IntoFuture; use actix_http::{h1, http::Method, Response}; use actix_server::Server; -use actix_web::{middleware, App, Error, HttpRequest, Resource}; +use actix_web::{middleware, web, App, Error, HttpRequest, Resource, Route}; fn index(req: HttpRequest) -> &'static str { println!("REQ: {:?}", req); @@ -31,7 +31,7 @@ fn main() { middleware::DefaultHeaders::new().header("X-Version", "0.2"), ) .middleware(middleware::Compress::default()) - .resource("/resource1/index.html", |r| r.get(index)) + .resource("/resource1/index.html", |r| r.route(web::get().to(index))) .service( "/resource2/index.html", Resource::new() @@ -39,8 +39,10 @@ fn main() { middleware::DefaultHeaders::new() .header("X-Version-R2", "0.3"), ) - .default_resource(|r| r.to(|| Response::MethodNotAllowed())) - .method(Method::GET, |r| r.to_async(index_async)), + .default_resource(|r| { + r.route(Route::new().to(|| Response::MethodNotAllowed())) + }) + .route(web::method(Method::GET).to_async(index_async)), ) .service("/test1.html", Resource::new().to(|| "Test\r\n")) .service("/", Resource::new().to(no_params)), diff --git a/src/app.rs b/src/app.rs index 2e45f2d3..d9219091 100644 --- a/src/app.rs +++ b/src/app.rs @@ -159,16 +159,16 @@ where } /// Register a middleware. - pub fn middleware( + pub fn middleware( self, mw: F, ) -> AppRouter< T, P, - Body, + B, impl NewService< Request = ServiceRequest

    , - Response = ServiceResponse, + Response = ServiceResponse, Error = (), InitError = (), >, @@ -177,7 +177,7 @@ where M: NewTransform< AppService

    , Request = ServiceRequest

    , - Response = ServiceResponse, + Response = ServiceResponse, Error = (), InitError = (), >, diff --git a/src/extractor.rs b/src/extractor.rs index 48c70b86..522ce721 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -26,7 +26,7 @@ use actix_router::PathDeserializer; use crate::handler::FromRequest; use crate::request::HttpRequest; use crate::responder::Responder; -use crate::service::ServiceRequest; +use crate::service::ServiceFromRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's path. @@ -112,7 +112,7 @@ impl Path { } /// Extract path information from a request - pub fn extract

    (req: &ServiceRequest

    ) -> Result, de::value::Error> + pub fn extract

    (req: &ServiceFromRequest

    ) -> Result, de::value::Error> where T: DeserializeOwned, { @@ -135,7 +135,7 @@ where type Future = FutureResult; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { Self::extract(req).map_err(ErrorNotFound).into_future() } } @@ -221,7 +221,7 @@ where type Future = FutureResult; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { serde_urlencoded::from_str::(req.query_string()) .map(|val| ok(Query(val))) .unwrap_or_else(|e| err(e.into())) @@ -301,7 +301,7 @@ where type Future = Box>; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { let cfg = FormConfig::default(); let req2 = req.clone(); @@ -511,7 +511,7 @@ where type Future = Box>; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { let cfg = JsonConfig::default(); let req2 = req.clone(); @@ -619,7 +619,7 @@ where Either>, FutureResult>; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { let cfg = PayloadConfig::default(); if let Err(e) = cfg.check_mimetype(req) { @@ -666,7 +666,7 @@ where Either>, FutureResult>; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { let cfg = PayloadConfig::default(); // check content-type @@ -755,7 +755,7 @@ where type Future = Box, Error = Error>>; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { Box::new(T::from_request(req).then(|r| match r { Ok(v) => future::ok(Some(v)), Err(_) => future::ok(None), @@ -818,7 +818,7 @@ where type Future = Box, Error = Error>>; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { Box::new(T::from_request(req).then(|res| match res { Ok(v) => ok(Ok(v)), Err(e) => ok(Err(e)), @@ -846,7 +846,7 @@ impl PayloadConfig { self } - fn check_mimetype

    (&self, req: &ServiceRequest

    ) -> Result<(), Error> { + fn check_mimetype

    (&self, req: &ServiceFromRequest

    ) -> Result<(), Error> { // check content-type if let Some(ref mt) = self.mimetype { match req.mime_type() { @@ -884,7 +884,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { type Error = Error; type Future = $fut_type; - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { $fut_type { items: <($(Option<$T>,)+)>::default(), futs: ($($T::from_request(req),)+), @@ -933,7 +933,7 @@ impl

    FromRequest

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

    ) -> Self::Future { + fn from_request(_req: &mut ServiceFromRequest

    ) -> Self::Future { ok(()) } } diff --git a/src/filter.rs b/src/filter.rs index 37c23d73..e3d87b76 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,8 +1,7 @@ //! Route match predicates #![allow(non_snake_case)] use actix_http::http::{self, header, HttpTryFrom}; - -use crate::request::HttpRequest; +use actix_http::RequestHead; /// Trait defines resource predicate. /// Predicate can modify request object. It is also possible to @@ -10,20 +9,21 @@ use crate::request::HttpRequest; /// Extensions container available via `HttpRequest::extensions()` method. pub trait Filter { /// Check if request matches predicate - fn check(&self, request: &HttpRequest) -> bool; + fn check(&self, request: &RequestHead) -> bool; } /// Return filter that matches if any of supplied filters. /// -/// ```rust,ignore -/// use actix_web::{filter, App, HttpResponse}; +/// ```rust +/// use actix_web::{web, filter, App, HttpResponse}; /// /// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route() -/// .filter(pred::Any(pred::Get()).or(pred::Post())) -/// .to(|| HttpResponse::MethodNotAllowed()) -/// }); +/// App::new().resource("/index.html", |r| +/// r.route( +/// web::route() +/// .filter(filter::Any(filter::Get()).or(filter::Post())) +/// .to(|| HttpResponse::MethodNotAllowed())) +/// ); /// } /// ``` pub fn Any(filter: F) -> AnyFilter { @@ -42,7 +42,7 @@ impl AnyFilter { } impl Filter for AnyFilter { - fn check(&self, req: &HttpRequest) -> bool { + fn check(&self, req: &RequestHead) -> bool { for p in &self.0 { if p.check(req) { return true; @@ -56,15 +56,13 @@ impl Filter for AnyFilter { /// /// ```rust /// # extern crate actix_web; -/// use actix_web::{filter, App, HttpResponse}; +/// use actix_web::{filter, web, App, HttpResponse}; /// /// fn main() { /// App::new().resource("/index.html", |r| { -/// r.route( -/// |r| r.filter( -/// filter::All(filter::Get()) -/// .and(filter::Header("content-type", "text/plain")), -/// ) +/// r.route(web::route() +/// .filter( +/// filter::All(filter::Get()).and(filter::Header("content-type", "text/plain"))) /// .to(|| HttpResponse::MethodNotAllowed())) /// }); /// } @@ -85,7 +83,7 @@ impl AllFilter { } impl Filter for AllFilter { - fn check(&self, request: &HttpRequest) -> bool { + fn check(&self, request: &RequestHead) -> bool { for p in &self.0 { if !p.check(request) { return false; @@ -104,7 +102,7 @@ pub fn Not(filter: F) -> NotFilter { pub struct NotFilter(Box); impl Filter for NotFilter { - fn check(&self, request: &HttpRequest) -> bool { + fn check(&self, request: &RequestHead) -> bool { !self.0.check(request) } } @@ -114,8 +112,8 @@ impl Filter for NotFilter { pub struct MethodFilter(http::Method); impl Filter for MethodFilter { - fn check(&self, request: &HttpRequest) -> bool { - request.method() == self.0 + fn check(&self, request: &RequestHead) -> bool { + request.method == self.0 } } @@ -182,8 +180,8 @@ pub fn Header(name: &'static str, value: &'static str) -> HeaderFilter { pub struct HeaderFilter(header::HeaderName, header::HeaderValue); impl Filter for HeaderFilter { - fn check(&self, req: &HttpRequest) -> bool { - if let Some(val) = req.headers().get(&self.0) { + fn check(&self, req: &RequestHead) -> bool { + if let Some(val) = req.headers.get(&self.0) { return val == self.1; } false @@ -219,7 +217,7 @@ impl Filter for HeaderFilter { // } // impl Filter for HostFilter { -// fn check(&self, _req: &HttpRequest) -> bool { +// fn check(&self, _req: &RequestHead) -> bool { // // let info = req.connection_info(); // // if let Some(ref scheme) = self.1 { // // self.0 == info.host() && scheme == info.scheme() diff --git a/src/handler.rs b/src/handler.rs index 8076d4d4..31ae796b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -7,7 +7,7 @@ use futures::{try_ready, Async, Future, IntoFuture, Poll}; use crate::request::HttpRequest; use crate::responder::Responder; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; /// Trait implemented by types that can be extracted from request. /// @@ -20,7 +20,7 @@ pub trait FromRequest

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

    ) -> Self::Future; + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future; } /// Handler converter factory @@ -306,7 +306,7 @@ impl> Default for Extract { impl> NewService for Extract { type Request = ServiceRequest

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

    ); + type Error = (Error, ServiceFromRequest

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

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

    ); + type Error = (Error, ServiceFromRequest

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

    ) -> Self::Future { + fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { + let mut req = req.into(); ExtractResponse { fut: T::from_request(&mut req), req: Some(req), @@ -339,13 +340,13 @@ impl> Service for ExtractService { } pub struct ExtractResponse> { - req: Option>, + req: Option>, fut: T::Future, } impl> Future for ExtractResponse { type Item = (T, HttpRequest); - type Error = (Error, ServiceRequest

    ); + type Error = (Error, ServiceFromRequest

    ); fn poll(&mut self) -> Poll { let item = try_ready!(self diff --git a/src/lib.rs b/src/lib.rs index 70ce9607..0e81b65a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,12 +25,91 @@ pub use crate::handler::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::responder::{Either, Responder}; +pub use crate::route::Route; pub use crate::service::{ServiceRequest, ServiceResponse}; pub use crate::state::State; +pub mod web { + use actix_http::{http::Method, Error, Response}; + use futures::IntoFuture; + + use crate::handler::{AsyncFactory, Factory, FromRequest}; + use crate::responder::Responder; + use crate::Route; + + pub fn route() -> Route

    { + Route::new() + } + + pub fn get() -> Route

    { + Route::get() + } + + pub fn post() -> Route

    { + Route::post() + } + + pub fn put() -> Route

    { + Route::put() + } + + pub fn delete() -> Route

    { + Route::delete() + } + + pub fn head() -> Route

    { + Route::new().method(Method::HEAD) + } + + pub fn method(method: Method) -> Route

    { + Route::new().method(method) + } + + /// Create a new route and add handler. + /// + /// ```rust + /// use actix_web::{web, App, HttpResponse}; + /// + /// fn index() -> HttpResponse { + /// unimplemented!() + /// } + /// + /// App::new().resource("/", |r| r.route(web::to(index))); + /// ``` + pub fn to(handler: F) -> Route

    + where + F: Factory + 'static, + I: FromRequest

    + 'static, + R: Responder + 'static, + { + Route::new().to(handler) + } + + /// Create a new route and add async handler. + /// + /// ```rust + /// use actix_web::{web, App, HttpResponse, Error}; + /// + /// fn index() -> impl futures::Future { + /// futures::future::ok(HttpResponse::Ok().finish()) + /// } + /// + /// App::new().resource("/", |r| r.route(web::to_async(index))); + /// ``` + pub fn to_async(handler: F) -> Route

    + where + F: AsyncFactory, + I: FromRequest

    + 'static, + R: IntoFuture + 'static, + R::Item: Into, + R::Error: Into, + { + Route::new().to_async(handler) + } +} + pub mod dev { pub use crate::app::AppRouter; pub use crate::handler::{AsyncFactory, Extract, Factory, Handle}; - pub use crate::route::{Route, RouteBuilder}; // pub use crate::info::ConnectionInfo; } diff --git a/src/request.rs b/src/request.rs index 571431cc..538064f1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -9,11 +9,11 @@ use actix_router::{Path, Url}; use futures::future::{ok, FutureResult}; use crate::handler::FromRequest; -use crate::service::ServiceRequest; +use crate::service::ServiceFromRequest; #[derive(Clone)] pub struct HttpRequest { - head: Message, + pub(crate) head: Message, pub(crate) path: Path, extensions: Rc, } @@ -145,7 +145,7 @@ impl

    FromRequest

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

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { ok(req.clone()) } } diff --git a/src/resource.rs b/src/resource.rs index 80ac8d83..ab6096c5 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; -use actix_http::{http::Method, Error, Response}; +use actix_http::{Error, Response}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ ApplyNewService, IntoNewService, IntoNewTransform, NewService, NewTransform, Service, @@ -11,7 +11,7 @@ use futures::{Async, Future, IntoFuture, Poll}; use crate::handler::{AsyncFactory, Factory, FromRequest}; use crate::responder::Responder; -use crate::route::{CreateRouteService, Route, RouteBuilder, RouteService}; +use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; type HttpService

    = BoxedService, ServiceResponse, ()>; @@ -74,92 +74,8 @@ where /// .finish(); /// } /// ``` - pub fn route(mut self, f: F) -> Self - where - F: FnOnce(RouteBuilder

    ) -> Route

    , - { - self.routes.push(f(Route::build())); - self - } - - /// Register a new `GET` route. - pub fn get(mut self, f: F) -> Self - where - F: Factory + 'static, - I: FromRequest

    + 'static, - R: Responder + 'static, - { - self.routes.push(Route::get().to(f)); - self - } - - /// Register a new `POST` route. - pub fn post(mut self, f: F) -> Self - where - F: Factory + 'static, - I: FromRequest

    + 'static, - R: Responder + 'static, - { - self.routes.push(Route::post().to(f)); - self - } - - /// Register a new `PUT` route. - pub fn put(mut self, f: F) -> Self - where - F: Factory + 'static, - I: FromRequest

    + 'static, - R: Responder + 'static, - { - self.routes.push(Route::put().to(f)); - self - } - - /// Register a new `DELETE` route. - pub fn delete(mut self, f: F) -> Self - where - F: Factory + 'static, - I: FromRequest

    + 'static, - R: Responder + 'static, - { - self.routes.push(Route::delete().to(f)); - self - } - - /// Register a new `HEAD` route. - pub fn head(mut self, f: F) -> Self - where - F: Factory + 'static, - I: FromRequest

    + 'static, - R: Responder + 'static, - { - self.routes.push(Route::build().method(Method::HEAD).to(f)); - self - } - - /// Register a new route and add method check to route. - /// - /// ```rust,ignore - /// # extern crate actix_web; - /// use actix_web::*; - /// fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// - /// App::new().resource("/", |r| r.method(http::Method::GET).f(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust,ignore - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: &HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().filter(pred::Get()).f(index)); - /// ``` - pub fn method(mut self, method: Method, f: F) -> Self - where - F: FnOnce(RouteBuilder

    ) -> Route

    , - { - self.routes.push(f(Route::build().method(method))); + pub fn route(mut self, route: Route

    ) -> Self { + self.routes.push(route); self } @@ -187,7 +103,7 @@ where I: FromRequest

    + 'static, R: Responder + 'static, { - self.routes.push(Route::build().to(handler)); + self.routes.push(Route::new().to(handler)); self } @@ -227,7 +143,7 @@ where R::Item: Into, R::Error: Into, { - self.routes.push(Route::build().to_async(handler)); + self.routes.push(Route::new().to_async(handler)); self } diff --git a/src/route.rs b/src/route.rs index d5e11424..99117afe 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,4 +1,3 @@ -use std::marker::PhantomData; use std::rc::Rc; use actix_http::{http::Method, Error, Response}; @@ -8,7 +7,8 @@ use futures::{Async, Future, IntoFuture, Poll}; use crate::filter::{self, Filter}; use crate::handler::{AsyncFactory, AsyncHandle, Extract, Factory, FromRequest, Handle}; use crate::responder::Responder; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; +use crate::HttpResponse; type BoxedRouteService = Box< Service< @@ -40,24 +40,29 @@ pub struct Route

    { } impl Route

    { - pub fn build() -> RouteBuilder

    { - RouteBuilder::new() + pub fn new() -> Route

    { + Route { + service: Box::new(RouteNewService::new(Extract::new().and_then( + Handle::new(|| HttpResponse::NotFound()).map_err(|_| panic!()), + ))), + filters: Rc::new(Vec::new()), + } } - pub fn get() -> RouteBuilder

    { - RouteBuilder::new().method(Method::GET) + pub fn get() -> Route

    { + Route::new().method(Method::GET) } - pub fn post() -> RouteBuilder

    { - RouteBuilder::new().method(Method::POST) + pub fn post() -> Route

    { + Route::new().method(Method::POST) } - pub fn put() -> RouteBuilder

    { - RouteBuilder::new().method(Method::PUT) + pub fn put() -> Route

    { + Route::new().method(Method::PUT) } - pub fn delete() -> RouteBuilder

    { - RouteBuilder::new().method(Method::DELETE) + pub fn delete() -> Route

    { + Route::new().method(Method::DELETE) } } @@ -109,7 +114,7 @@ pub struct RouteService

    { impl

    RouteService

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

    ) -> bool { for f in self.filters.iter() { - if !f.check(req.request()) { + if !f.check(req.head()) { return false; } } @@ -132,19 +137,7 @@ impl

    Service for RouteService

    { } } -pub struct RouteBuilder

    { - filters: Vec>, - _t: PhantomData

    , -} - -impl RouteBuilder

    { - fn new() -> RouteBuilder

    { - RouteBuilder { - filters: Vec::new(), - _t: PhantomData, - } - } - +impl Route

    { /// Add method match filter to the route. /// /// ```rust,ignore @@ -161,7 +154,9 @@ impl RouteBuilder

    { /// # } /// ``` pub fn method(mut self, method: Method) -> Self { - self.filters.push(Box::new(filter::Method(method))); + Rc::get_mut(&mut self.filters) + .unwrap() + .push(Box::new(filter::Method(method))); self } @@ -181,7 +176,7 @@ impl RouteBuilder

    { /// # } /// ``` pub fn filter(mut self, f: F) -> Self { - self.filters.push(Box::new(f)); + Rc::get_mut(&mut self.filters).unwrap().push(Box::new(f)); self } @@ -259,18 +254,16 @@ impl RouteBuilder

    { /// ); // <- use `with` extractor /// } /// ``` - pub fn to(self, handler: F) -> Route

    + pub fn to(mut self, handler: F) -> Route

    where F: Factory + 'static, T: FromRequest

    + 'static, R: Responder + 'static, { - Route { - service: Box::new(RouteNewService::new( - Extract::new().and_then(Handle::new(handler).map_err(|_| panic!())), - )), - filters: Rc::new(self.filters), - } + self.service = Box::new(RouteNewService::new( + Extract::new().and_then(Handle::new(handler).map_err(|_| panic!())), + )); + self } /// Set async handler function, use request extractor for parameters. @@ -303,7 +296,7 @@ impl RouteBuilder

    { /// } /// ``` #[allow(clippy::wrong_self_convention)] - pub fn to_async(self, handler: F) -> Route

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

    + 'static, @@ -311,12 +304,10 @@ impl RouteBuilder

    { R::Item: Into, R::Error: Into, { - Route { - service: Box::new(RouteNewService::new( - Extract::new().and_then(AsyncHandle::new(handler).map_err(|_| panic!())), - )), - filters: Rc::new(self.filters), - } + self.service = Box::new(RouteNewService::new( + Extract::new().and_then(AsyncHandle::new(handler).map_err(|_| panic!())), + )); + self } } @@ -450,7 +441,7 @@ impl RouteBuilder

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

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

    )>, { service: T, } @@ -460,7 +451,7 @@ where T: NewService< Request = ServiceRequest

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

    ), + Error = (Error, ServiceFromRequest

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

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

    ), + Error = (Error, ServiceFromRequest

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

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

    ), + Error = (Error, ServiceFromRequest

    ), >, { type Request = ServiceRequest

    ; diff --git a/src/service.rs b/src/service.rs index 775bb611..078cb223 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,9 +1,11 @@ +use std::cell::{Ref, RefMut}; use std::rc::Rc; use actix_http::body::{Body, MessageBody, ResponseBody}; -use actix_http::http::HeaderMap; +use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{ - Error, Extensions, HttpMessage, Payload, Request, Response, ResponseHead, + Error, Extensions, HttpMessage, Payload, Request, RequestHead, Response, + ResponseHead, }; use actix_router::{Path, Url}; @@ -27,11 +29,6 @@ impl

    ServiceRequest

    { } } - #[inline] - pub fn request(&self) -> &HttpRequest { - &self.req - } - #[inline] pub fn into_request(self) -> HttpRequest { self.req @@ -49,10 +46,93 @@ impl

    ServiceRequest

    { ServiceResponse::new(self.req, err.into().into()) } + /// This method returns reference to the request head + #[inline] + pub fn head(&self) -> &RequestHead { + &self.req.head + } + + /// This method returns reference to the request head + #[inline] + pub fn head_mut(&mut self) -> &mut RequestHead { + &mut self.req.head + } + + /// Request's uri. + #[inline] + pub fn uri(&self) -> &Uri { + &self.head().uri + } + + /// Read the Request method. + #[inline] + pub fn method(&self) -> &Method { + &self.head().method + } + + /// Read the Request Version. + #[inline] + pub fn version(&self) -> Version { + self.head().version + } + + /// The target path of this Request. + #[inline] + pub fn path(&self) -> &str { + self.head().uri.path() + } + + #[inline] + /// Returns Request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head().headers + } + + /// The query string in the URL. + /// + /// E.g., id=10 + #[inline] + pub fn query_string(&self) -> &str { + if let Some(query) = self.uri().query().as_ref() { + query + } else { + "" + } + } + + /// Get a reference to the Path parameters. + /// + /// Params is a container for url parameters. + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. + #[inline] + pub fn match_info(&self) -> &Path { + &self.req.path + } + #[inline] pub fn match_info_mut(&mut self) -> &mut Path { &mut self.req.path } + + /// Request extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.req.head.extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.req.head.extensions_mut() + } + + /// Application extensions + #[inline] + pub fn app_extensions(&self) -> &Extensions { + self.req.app_extensions() + } } impl

    HttpMessage for ServiceRequest

    { @@ -70,10 +150,65 @@ impl

    HttpMessage for ServiceRequest

    { } impl

    std::ops::Deref for ServiceRequest

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

    std::ops::DerefMut for ServiceRequest

    { + fn deref_mut(&mut self) -> &mut RequestHead { + self.head_mut() + } +} + +pub struct ServiceFromRequest

    { + req: HttpRequest, + payload: Payload

    , +} + +impl

    ServiceFromRequest

    { + #[inline] + pub fn into_request(self) -> HttpRequest { + self.req + } + + /// Create service response for error + #[inline] + pub fn error_response>(self, err: E) -> ServiceResponse { + ServiceResponse::new(self.req, err.into().into()) + } +} + +impl

    std::ops::Deref for ServiceFromRequest

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

    HttpMessage for ServiceFromRequest

    { + type Stream = P; + + #[inline] + fn headers(&self) -> &HeaderMap { + self.req.headers() + } + + #[inline] + fn take_payload(&mut self) -> Payload { + std::mem::replace(&mut self.payload, Payload::None) + } +} + +impl

    From> for ServiceFromRequest

    { + fn from(req: ServiceRequest

    ) -> Self { + Self { + req: req.req, + payload: req.payload, + } } } diff --git a/src/state.rs b/src/state.rs index 82aecc6e..db263777 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,7 +7,7 @@ use futures::future::{err, ok, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; use crate::handler::FromRequest; -use crate::service::ServiceRequest; +use crate::service::ServiceFromRequest; /// Application state factory pub(crate) trait StateFactory { @@ -50,7 +50,7 @@ impl FromRequest

    for State { type Future = FutureResult; #[inline] - fn from_request(req: &mut ServiceRequest

    ) -> Self::Future { + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { if let Some(st) = req.app_extensions().get::>() { ok(st.clone()) } else { diff --git a/tests/test_server.rs b/tests/test_server.rs index 590cc0e7..d28f207c 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -12,7 +12,7 @@ use flate2::write::ZlibDecoder; use futures::stream::once; //Future, Stream use rand::{distributions::Alphanumeric, Rng}; -use actix_web::{middleware, App}; +use actix_web::{middleware, web, App}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -40,7 +40,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn test_body() { let mut srv = TestServer::new(|| { h1::H1Service::new( - App::new().resource("/", |r| r.get(|| Response::Ok().body(STR))), + App::new().resource("/", |r| r.route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -59,7 +59,7 @@ fn test_body_gzip() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| r.get(|| Response::Ok().body(STR))), + .resource("/", |r| r.route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -87,7 +87,9 @@ fn test_body_gzip_large() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| r.get(move || Response::Ok().body(data.clone()))), + .resource("/", |r| { + r.route(web::to(move || Response::Ok().body(data.clone()))) + }), ) }); @@ -118,7 +120,9 @@ fn test_body_gzip_large_random() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| r.get(move || Response::Ok().body(data.clone()))), + .resource("/", |r| { + r.route(web::to(move || Response::Ok().body(data.clone()))) + }), ) }); @@ -144,11 +148,11 @@ fn test_body_chunked_implicit() { App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) .resource("/", |r| { - r.get(move || { + r.route(web::get().to(move || { Response::Ok().streaming(once(Ok::<_, Error>( Bytes::from_static(STR.as_ref()), ))) - }) + })) }), ) }); @@ -178,11 +182,11 @@ fn test_body_br_streaming() { App::new() .middleware(middleware::Compress::new(ContentEncoding::Br)) .resource("/", |r| { - r.get(move || { + r.route(web::to(move || { Response::Ok().streaming(once(Ok::<_, Error>( Bytes::from_static(STR.as_ref()), ))) - }) + })) }), ) }); @@ -205,7 +209,7 @@ fn test_body_br_streaming() { fn test_head_binary() { let mut srv = TestServer::new(move || { h1::H1Service::new(App::new().resource("/", |r| { - r.head(move || Response::Ok().content_length(100).body(STR)) + r.route(web::head().to(move || Response::Ok().content_length(100).body(STR))) })) }); @@ -227,12 +231,12 @@ fn test_head_binary() { fn test_no_chunking() { let mut srv = TestServer::new(move || { h1::H1Service::new(App::new().resource("/", |r| { - r.get(move || { + r.route(web::to(move || { Response::Ok() .no_chunking() .content_length(STR.len() as u64) .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) - }) + })) })) }); @@ -252,7 +256,7 @@ fn test_body_deflate() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Deflate)) - .resource("/", |r| r.get(move || Response::Ok().body(STR))), + .resource("/", |r| r.route(web::to(move || Response::Ok().body(STR)))), ) }); @@ -277,7 +281,7 @@ fn test_body_brotli() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Br)) - .resource("/", |r| r.get(move || Response::Ok().body(STR))), + .resource("/", |r| r.route(web::to(move || Response::Ok().body(STR)))), ) }); From b535adf637ea85176bfaabc78e7f6eed7358ef41 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 21:22:01 -0800 Subject: [PATCH 0948/1635] add IntoFuture impl for Response and ResponseBuilder --- src/header/common/mod.rs | 6 +++--- src/response.rs | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index adc7484a..30dfcaa6 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -81,7 +81,7 @@ macro_rules! test_header { let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); for item in a { - req = req.header(HeaderField::name(), item); + req = req.header(HeaderField::name(), item).take(); } let req = req.finish(); let value = HeaderField::parse(&req); @@ -104,11 +104,11 @@ macro_rules! test_header { #[test] fn $id() { use $crate::test; - + let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); for item in a { - req = req.header(HeaderField::name(), item); + req.header(HeaderField::name(), item); } let req = req.finish(); let val = HeaderField::parse(&req); diff --git a/src/response.rs b/src/response.rs index 80748746..9b503de1 100644 --- a/src/response.rs +++ b/src/response.rs @@ -4,6 +4,7 @@ use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; +use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; use http::header::{self, HeaderName, HeaderValue}; use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; @@ -276,6 +277,16 @@ impl fmt::Debug for Response { } } +impl IntoFuture for Response { + type Item = Response; + type Error = Error; + type Future = FutureResult; + + fn into_future(self) -> Self::Future { + ok(self) + } +} + pub struct CookieIter<'a> { iter: header::ValueIter<'a, HeaderValue>, } @@ -679,6 +690,16 @@ fn parts<'a>( parts.as_mut() } +impl IntoFuture for ResponseBuilder { + type Item = Response; + type Error = Error; + type Future = FutureResult; + + fn into_future(mut self) -> Self::Future { + ok(self.finish()) + } +} + /// Helper converters impl, E: Into> From> for Response { fn from(res: Result) -> Self { From 352e7b7a754cb48e09922b8cd3a7c883369d2128 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 21:35:31 -0800 Subject: [PATCH 0949/1635] update tests for defaultheaders middleware --- src/middleware/defaultheaders.rs | 74 +++++++++++++++++++------------- src/service.rs | 11 +++++ 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 1ace34fe..83bb94c6 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -13,17 +13,15 @@ use crate::service::{ServiceRequest, ServiceResponse}; /// /// This middleware does not set header if response headers already contains it. /// -/// ```rust,ignore -/// # extern crate actix_web; -/// use actix_web::{http, middleware, App, HttpResponse}; +/// ```rust +/// use actix_web::{web, http, middleware, App, HttpResponse}; /// /// fn main() { /// let app = App::new() /// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) /// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD) -/// .f(|_| HttpResponse::MethodNotAllowed()); +/// r.route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) /// }); /// } /// ``` @@ -134,30 +132,48 @@ where } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use actix_http::http::header::CONTENT_TYPE; -// use actix_http::test::TestRequest; +#[cfg(test)] +mod tests { + use actix_http::http::header::CONTENT_TYPE; + use actix_service::FnService; -// #[test] -// fn test_default_headers() { -// let mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); + use super::*; + use crate::test::TestServiceRequest; + use crate::{HttpResponse, ServiceRequest}; -// let req = TestRequest::default().finish(); + #[test] + fn test_default_headers() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let mut mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); + let mut srv = FnService::new(|req: ServiceRequest<_>| { + req.into_response(HttpResponse::Ok().finish()) + }); -// let resp = Response::Ok().finish(); -// let resp = match mw.response(&req, resp) { -// Ok(Response::Done(resp)) => resp, -// _ => panic!(), -// }; -// assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + let req = TestServiceRequest::default().finish(); + let resp = rt.block_on(mw.call(req, &mut srv)).unwrap(); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); -// let resp = Response::Ok().header(CONTENT_TYPE, "0002").finish(); -// let resp = match mw.response(&req, resp) { -// Ok(Response::Done(resp)) => resp, -// _ => panic!(), -// }; -// assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); -// } -// } + let req = TestServiceRequest::default().finish(); + let mut srv = FnService::new(|req: ServiceRequest<_>| { + req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) + }); + let resp = rt.block_on(mw.call(req, &mut srv)).unwrap(); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); + } + + #[test] + fn test_content_type() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let mut mw = DefaultHeaders::new().content_type(); + let mut srv = FnService::new(|req: ServiceRequest<_>| { + req.into_response(HttpResponse::Ok().finish()) + }); + + let req = TestServiceRequest::default().finish(); + let resp = rt.block_on(mw.call(req, &mut srv)).unwrap(); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + "application/octet-stream" + ); + } +} diff --git a/src/service.rs b/src/service.rs index 078cb223..99af73c1 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,6 +8,7 @@ use actix_http::{ ResponseHead, }; use actix_router::{Path, Url}; +use futures::future::{ok, FutureResult, IntoFuture}; use crate::request::HttpRequest; @@ -288,3 +289,13 @@ impl Into> for ServiceResponse { self.response } } + +impl IntoFuture for ServiceResponse { + type Item = ServiceResponse; + type Error = Error; + type Future = FutureResult, Error>; + + fn into_future(self) -> Self::Future { + ok(self) + } +} From d5c54a18675f2ed7159307ea616ebbcce85d3444 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 22:03:45 -0800 Subject: [PATCH 0950/1635] update extractor tests --- src/app.rs | 36 +++++------ src/extractor.rs | 105 ++++++++++++++----------------- src/filter.rs | 32 ++++------ src/middleware/defaultheaders.rs | 8 +-- src/middleware/mod.rs | 13 ---- src/test.rs | 49 +++++---------- 6 files changed, 97 insertions(+), 146 deletions(-) diff --git a/src/app.rs b/src/app.rs index d9219091..ae510621 100644 --- a/src/app.rs +++ b/src/app.rs @@ -29,7 +29,8 @@ pub trait HttpServiceFactory { fn create(self) -> Self::Factory; } -/// Application builder +/// Application builder - structure that follows the builder pattern +/// for building application instances. pub struct App where T: NewService, Response = ServiceRequest

    >, @@ -69,11 +70,8 @@ where InitError = (), >, { - /// Create application with specified state. Application can be - /// configured with a builder-like pattern. - /// - /// State is shared with all resources within same application and - /// could be accessed with `HttpRequest::state()` method. + /// Set application state. Applicatin state could be accessed + /// by using `State` extractor where `T` is state type. /// /// **Note**: http server accepts an application factory rather than /// an application instance. Http server constructs an application @@ -86,7 +84,7 @@ where self } - /// Set application state. This function is + /// Set application state factory. This function is /// similar to `.state()` but it accepts state factory. State get /// constructed asynchronously during application initialization. pub fn state_factory(mut self, state: F) -> Self @@ -119,14 +117,14 @@ where /// `/users/{userid}/{friend}` and store `userid` and `friend` in /// the exposed `Params` object: /// - /// ```rust,ignore + /// ```rust /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; + /// use actix_web::{web, http, App, HttpResponse}; /// /// fn main() { /// let app = App::new().resource("/users/{userid}/{friend}", |r| { - /// r.get(|r| r.to(|_| HttpResponse::Ok())); - /// r.head(|r| r.to(|_| HttpResponse::MethodNotAllowed())) + /// r.route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) /// }); /// } /// ``` @@ -294,15 +292,17 @@ where /// `/users/{userid}/{friend}` and store `userid` and `friend` in /// the exposed `Params` object: /// - /// ```rust,ignore - /// # extern crate actix_web; - /// use actix_web::{http, App, HttpResponse}; + /// ```rust + /// use actix_web::{web, http, App, HttpResponse}; /// /// fn main() { - /// let app = App::new().resource("/users/{userid}/{friend}", |r| { - /// r.get(|r| r.to(|_| HttpResponse::Ok())); - /// r.head(|r| r.to(|_| HttpResponse::MethodNotAllowed())) - /// }); + /// let app = App::new() + /// .resource("/users/{userid}/{friend}", |r| { + /// r.route(web::to(|| HttpResponse::Ok())) + /// }) + /// .resource("/index.html", |r| { + /// r.route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// }); /// } /// ``` pub fn resource(mut self, path: &str, f: F) -> Self diff --git a/src/extractor.rs b/src/extractor.rs index 522ce721..04dfb81a 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -999,73 +999,60 @@ tuple_from_req!( (9, J) ); -// #[cfg(test)] -// mod tests { -// use super::*; -// use actix_http::http::header; -// use actix_http::test::TestRequest; -// use bytes::Bytes; -// use futures::{Async, Future}; -// use mime; -// use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests { + use actix_http::http::header; + use bytes::Bytes; + use serde_derive::Deserialize; -// use crate::resource::Resource; -// // use crate::router::{ResourceDef, Router}; + use super::*; + use crate::test::TestRequest; -// #[derive(Deserialize, Debug, PartialEq)] -// struct Info { -// hello: String, -// } + #[derive(Deserialize, Debug, PartialEq)] + struct Info { + hello: String, + } -// #[test] -// fn test_bytes() { -// let cfg = PayloadConfig::default(); -// let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") -// .set_payload(Bytes::from_static(b"hello=world")) -// .finish(); + #[test] + fn test_bytes() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .finish() + .into(); -// match Bytes::from_request(&req, &cfg).unwrap().poll().unwrap() { -// Async::Ready(s) => { -// assert_eq!(s, Bytes::from_static(b"hello=world")); -// } -// _ => unreachable!(), -// } -// } + let s = rt.block_on(Bytes::from_request(&mut req)).unwrap(); + assert_eq!(s, Bytes::from_static(b"hello=world")); + } -// #[test] -// fn test_string() { -// let cfg = PayloadConfig::default(); -// let req = TestRequest::with_header(header::CONTENT_LENGTH, "11") -// .set_payload(Bytes::from_static(b"hello=world")) -// .finish(); + #[test] + fn test_string() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .finish() + .into(); -// match String::from_request(&req, &cfg).unwrap().poll().unwrap() { -// Async::Ready(s) => { -// assert_eq!(s, "hello=world"); -// } -// _ => unreachable!(), -// } -// } + let s = rt.block_on(String::from_request(&mut req)).unwrap(); + assert_eq!(s, "hello=world"); + } -// #[test] -// fn test_form() { -// let req = TestRequest::with_header( -// header::CONTENT_TYPE, -// "application/x-www-form-urlencoded", -// ) -// .header(header::CONTENT_LENGTH, "11") -// .set_payload(Bytes::from_static(b"hello=world")) -// .finish(); + #[test] + fn test_form() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .finish() + .into(); -// let mut cfg = FormConfig::default(); -// cfg.limit(4096); -// match Form::::from_request(&req, &cfg).poll().unwrap() { -// Async::Ready(s) => { -// assert_eq!(s.hello, "world"); -// } -// _ => unreachable!(), -// } -// } + let s = rt.block_on(Form::::from_request(&mut req)).unwrap(); + assert_eq!(s.hello, "world"); + } +} // #[test] // fn test_option() { diff --git a/src/filter.rs b/src/filter.rs index e3d87b76..9b49c9dd 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -230,14 +230,14 @@ impl Filter for HeaderFilter { #[cfg(test)] mod tests { - use crate::test::TestServiceRequest; use actix_http::http::{header, Method}; use super::*; + use crate::test::TestRequest; #[test] fn test_header() { - let req = TestServiceRequest::with_header(header::TRANSFER_ENCODING, "chunked") + let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked") .finish() .into_request(); @@ -269,8 +269,8 @@ mod tests { #[test] fn test_methods() { - let req = TestServiceRequest::default().finish().into_request(); - let req2 = TestServiceRequest::default() + let req = TestRequest::default().finish().into_request(); + let req2 = TestRequest::default() .method(Method::POST) .finish() .into_request(); @@ -280,46 +280,38 @@ mod tests { assert!(Post().check(&req2)); assert!(!Post().check(&req)); - let r = TestServiceRequest::default().method(Method::PUT).finish(); + let r = TestRequest::default().method(Method::PUT).finish(); assert!(Put().check(&r,)); assert!(!Put().check(&req,)); - let r = TestServiceRequest::default() - .method(Method::DELETE) - .finish(); + let r = TestRequest::default().method(Method::DELETE).finish(); assert!(Delete().check(&r,)); assert!(!Delete().check(&req,)); - let r = TestServiceRequest::default().method(Method::HEAD).finish(); + let r = TestRequest::default().method(Method::HEAD).finish(); assert!(Head().check(&r,)); assert!(!Head().check(&req,)); - let r = TestServiceRequest::default() - .method(Method::OPTIONS) - .finish(); + let r = TestRequest::default().method(Method::OPTIONS).finish(); assert!(Options().check(&r,)); assert!(!Options().check(&req,)); - let r = TestServiceRequest::default() - .method(Method::CONNECT) - .finish(); + let r = TestRequest::default().method(Method::CONNECT).finish(); assert!(Connect().check(&r,)); assert!(!Connect().check(&req,)); - let r = TestServiceRequest::default().method(Method::PATCH).finish(); + let r = TestRequest::default().method(Method::PATCH).finish(); assert!(Patch().check(&r,)); assert!(!Patch().check(&req,)); - let r = TestServiceRequest::default().method(Method::TRACE).finish(); + let r = TestRequest::default().method(Method::TRACE).finish(); assert!(Trace().check(&r,)); assert!(!Trace().check(&req,)); } #[test] fn test_preds() { - let r = TestServiceRequest::default() - .method(Method::TRACE) - .request(); + let r = TestRequest::default().method(Method::TRACE).request(); assert!(Not(Get()).check(&r,)); assert!(!Not(Trace()).check(&r,)); diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 83bb94c6..fa287b28 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -138,7 +138,7 @@ mod tests { use actix_service::FnService; use super::*; - use crate::test::TestServiceRequest; + use crate::test::TestRequest; use crate::{HttpResponse, ServiceRequest}; #[test] @@ -149,11 +149,11 @@ mod tests { req.into_response(HttpResponse::Ok().finish()) }); - let req = TestServiceRequest::default().finish(); + let req = TestRequest::default().finish(); let resp = rt.block_on(mw.call(req, &mut srv)).unwrap(); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let req = TestServiceRequest::default().finish(); + let req = TestRequest::default().finish(); let mut srv = FnService::new(|req: ServiceRequest<_>| { req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) }); @@ -169,7 +169,7 @@ mod tests { req.into_response(HttpResponse::Ok().finish()) }); - let req = TestServiceRequest::default().finish(); + let req = TestRequest::default().finish(); let resp = rt.block_on(mw.call(req, &mut srv)).unwrap(); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 8ef316b4..85127ee2 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -34,19 +34,6 @@ where } } -impl Clone for MiddlewareFactory -where - T: Transform + Clone, - S: Service, -{ - fn clone(&self) -> Self { - Self { - tr: self.tr.clone(), - _t: PhantomData, - } - } -} - impl NewTransform for MiddlewareFactory where T: Transform + Clone, diff --git a/src/test.rs b/src/test.rs index d67696b1..d6caf897 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,10 +1,9 @@ //! Various helpers for Actix applications to use during testing. -use std::ops::{Deref, DerefMut}; use std::rc::Rc; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, Version}; -use actix_http::test::TestRequest; +use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{Extensions, PayloadStream}; use actix_router::{Path, Url}; use bytes::Bytes; @@ -39,45 +38,45 @@ use crate::service::ServiceRequest; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` -pub struct TestServiceRequest { - req: TestRequest, +pub struct TestRequest { + req: HttpTestRequest, extensions: Extensions, } -impl Default for TestServiceRequest { - fn default() -> TestServiceRequest { - TestServiceRequest { - req: TestRequest::default(), +impl Default for TestRequest { + fn default() -> TestRequest { + TestRequest { + req: HttpTestRequest::default(), extensions: Extensions::new(), } } } -impl TestServiceRequest { +impl TestRequest { /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestServiceRequest { - TestServiceRequest { - req: TestRequest::default().uri(path).take(), + pub fn with_uri(path: &str) -> TestRequest { + TestRequest { + req: HttpTestRequest::default().uri(path).take(), extensions: Extensions::new(), } } /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestServiceRequest { - TestServiceRequest { - req: TestRequest::default().set(hdr).take(), + pub fn with_hdr(hdr: H) -> TestRequest { + TestRequest { + req: HttpTestRequest::default().set(hdr).take(), extensions: Extensions::new(), } } /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestServiceRequest + pub fn with_header(key: K, value: V) -> TestRequest where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - TestServiceRequest { - req: TestRequest::default().header(key, value).take(), + TestRequest { + req: HttpTestRequest::default().header(key, value).take(), extensions: Extensions::new(), } } @@ -145,17 +144,3 @@ impl TestServiceRequest { .into_request() } } - -impl Deref for TestServiceRequest { - type Target = TestRequest; - - fn deref(&self) -> &TestRequest { - &self.req - } -} - -impl DerefMut for TestServiceRequest { - fn deref_mut(&mut self) -> &mut TestRequest { - &mut self.req - } -} From 115b30d9ccf52c9f19609bf13862dfdd58b99d0b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 22:11:24 -0800 Subject: [PATCH 0951/1635] add state example --- src/app.rs | 21 ++++++++++++++++ src/extractor.rs | 65 ++++++++---------------------------------------- 2 files changed, 31 insertions(+), 55 deletions(-) diff --git a/src/app.rs b/src/app.rs index ae510621..e9b10081 100644 --- a/src/app.rs +++ b/src/app.rs @@ -79,6 +79,27 @@ where /// multiple times. If you want to share state between different /// threads, a shared object should be used, e.g. `Arc`. Application /// state does not need to be `Send` or `Sync`. + /// + /// ```rust + /// use std::cell::Cell; + /// use actix_web::{web, State, App}; + /// + /// struct MyState { + /// counter: Cell, + /// } + /// + /// fn index(state: State) { + /// state.counter.set(state.counter.get() + 1); + /// } + /// + /// fn main() { + /// let app = App::new() + /// .state(MyState{ counter: Cell::new(0) }) + /// .resource( + /// "/index.html", + /// |r| r.route(web::get().to(index))); + /// } + /// ``` pub fn state(mut self, state: S) -> Self { self.state.push(Box::new(State::new(state))); self diff --git a/src/extractor.rs b/src/extractor.rs index 04dfb81a..ea7681b0 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -938,66 +938,21 @@ impl

    FromRequest

    for () { } } +#[rustfmt::skip] +mod m { + use super::*; + tuple_from_req!(TupleFromRequest1, (0, A)); tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); -tuple_from_req!( - TupleFromRequest6, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F) -); -tuple_from_req!( - TupleFromRequest7, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G) -); -tuple_from_req!( - TupleFromRequest8, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H) -); -tuple_from_req!( - TupleFromRequest9, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I) -); -tuple_from_req!( - TupleFromRequest10, - (0, A), - (1, B), - (2, C), - (3, D), - (4, E), - (5, F), - (6, G), - (7, H), - (8, I), - (9, J) -); +tuple_from_req!(TupleFromRequest6, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); +tuple_from_req!(TupleFromRequest7, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); +tuple_from_req!(TupleFromRequest8, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); +tuple_from_req!(TupleFromRequest9, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); +tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); +} #[cfg(test)] mod tests { From b320dc127a3aa9a64b6b59808138b56edbb1ea56 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 22:22:45 -0800 Subject: [PATCH 0952/1635] remove unused code --- .travis.yml | 7 ++++++- src/app.rs | 23 ----------------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 077989d2..f0dc9e90 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ matrix: include: - rust: stable - rust: beta - - rust: nightly + - rust: nightly-2019-03-02 allow_failures: - rust: nightly @@ -24,6 +24,11 @@ before_install: - sudo apt-get update -qq - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev +before_cache: | + if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f + fi + # Add clippy before_script: - export PATH=$PATH:~/.cargo/bin diff --git a/src/app.rs b/src/app.rs index e9b10081..2a2380b2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -54,12 +54,6 @@ impl App { } } -impl Default for App { - fn default() -> Self { - App::new() - } -} - impl App where P: 'static, @@ -249,23 +243,6 @@ where _t: PhantomData, } } - - /// Complete applicatin chain configuration and start resource - /// configuration. - pub fn router(self) -> AppRouter> { - let fref = Rc::new(RefCell::new(None)); - AppRouter { - chain: self.chain, - services: Vec::new(), - default: None, - defaults: Vec::new(), - endpoint: AppEntry::new(fref.clone()), - factory_ref: fref, - extensions: self.extensions, - state: self.state, - _t: PhantomData, - } - } } /// Structure that follows the builder pattern for building application From 08fcb6891e5f29cd615c7c839aaa004dee652959 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 2 Mar 2019 22:33:46 -0800 Subject: [PATCH 0953/1635] use specific nightly version for travis --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f0dc9e90..15a0b04e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,8 @@ before_install: - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin fi # Add clippy @@ -35,13 +35,13 @@ before_script: script: - | - if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then + if [[ "$TRAVIS_RUST_VERSION" != "nightly-2019-03-02" ]]; then cargo clean cargo check cargo test -- --nocapture fi - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin RUST_BACKTRACE=1 cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) From 6df85e32dfa2ef7c2c3d587c65c8a3602406e772 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 00:57:48 -0800 Subject: [PATCH 0954/1635] added extractor configuration system --- src/extractor.rs | 83 ++++++++++++++++++++++++++++++++++++++---------- src/handler.rs | 58 ++++++++++++++++++++++++++------- src/lib.rs | 4 +-- src/request.rs | 1 + src/resource.rs | 2 +- src/route.rs | 66 ++++++++++++++++++++++++++++++++++---- src/service.rs | 29 +++++++++++------ src/state.rs | 1 + src/test.rs | 16 ++++++++-- 9 files changed, 210 insertions(+), 50 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index ea7681b0..24e4c8af 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -23,7 +23,7 @@ use actix_http::http::StatusCode; use actix_http::{HttpMessage, Response}; use actix_router::PathDeserializer; -use crate::handler::FromRequest; +use crate::handler::{ConfigStorage, ExtractorConfig, FromRequest}; use crate::request::HttpRequest; use crate::responder::Responder; use crate::service::ServiceFromRequest; @@ -133,6 +133,7 @@ where { type Error = Error; type Future = FutureResult; + type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { @@ -219,6 +220,7 @@ where { type Error = Error; type Future = FutureResult; + type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { @@ -299,16 +301,18 @@ where { type Error = Error; type Future = Box>; + type Config = FormConfig; #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - let cfg = FormConfig::default(); - let req2 = req.clone(); + let cfg = req.load_config::(); + + let limit = cfg.limit; let err = Rc::clone(&cfg.ehandler); Box::new( UrlEncoded::new(req) - .limit(cfg.limit) + .limit(limit) .map_err(move |e| (*err)(e, &req2)) .map(Form), ) @@ -356,6 +360,7 @@ impl fmt::Display for Form { /// ); /// } /// ``` +#[derive(Clone)] pub struct FormConfig { limit: usize, ehandler: Rc Error>, @@ -363,13 +368,13 @@ pub struct FormConfig { impl FormConfig { /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { + pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self + pub fn error_handler(mut self, f: F) -> Self where F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, { @@ -378,6 +383,8 @@ impl FormConfig { } } +impl ExtractorConfig for FormConfig {} + impl Default for FormConfig { fn default() -> Self { FormConfig { @@ -509,16 +516,18 @@ where { type Error = Error; type Future = Box>; + type Config = JsonConfig; #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - let cfg = JsonConfig::default(); - let req2 = req.clone(); + let cfg = req.load_config::(); + + let limit = cfg.limit; let err = Rc::clone(&cfg.ehandler); Box::new( JsonBody::new(req) - .limit(cfg.limit) + .limit(limit) .map_err(move |e| (*err)(e, &req2)) .map(Json), ) @@ -555,6 +564,7 @@ where /// }); /// } /// ``` +#[derive(Clone)] pub struct JsonConfig { limit: usize, ehandler: Rc Error>, @@ -562,13 +572,13 @@ pub struct JsonConfig { impl JsonConfig { /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { + pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self + pub fn error_handler(mut self, f: F) -> Self where F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, { @@ -577,6 +587,8 @@ impl JsonConfig { } } +impl ExtractorConfig for JsonConfig {} + impl Default for JsonConfig { fn default() -> Self { JsonConfig { @@ -617,16 +629,18 @@ where type Error = Error; type Future = Either>, FutureResult>; + type Config = PayloadConfig; #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - let cfg = PayloadConfig::default(); + let cfg = req.load_config::(); if let Err(e) = cfg.check_mimetype(req) { return Either::B(err(e)); } - Either::A(Box::new(MessageBody::new(req).limit(cfg.limit).from_err())) + let limit = cfg.limit; + Either::A(Box::new(MessageBody::new(req).limit(limit).from_err())) } } @@ -664,10 +678,11 @@ where type Error = Error; type Future = Either>, FutureResult>; + type Config = PayloadConfig; #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - let cfg = PayloadConfig::default(); + let cfg = req.load_config::(); // check content-type if let Err(e) = cfg.check_mimetype(req) { @@ -679,10 +694,11 @@ where Ok(enc) => enc, Err(e) => return Either::B(err(e.into())), }; + let limit = cfg.limit; Either::A(Box::new( MessageBody::new(req) - .limit(cfg.limit) + .limit(limit) .from_err() .and_then(move |body| { let enc: *const Encoding = encoding as *const Encoding; @@ -753,6 +769,7 @@ where { type Error = Error; type Future = Box, Error = Error>>; + type Config = T::Config; #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { @@ -816,6 +833,7 @@ where { type Error = Error; type Future = Box, Error = Error>>; + type Config = T::Config; #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { @@ -827,21 +845,27 @@ where } /// Payload configuration for request's payload. +#[derive(Clone)] pub struct PayloadConfig { limit: usize, mimetype: Option, } impl PayloadConfig { + /// Create `PayloadConfig` instance and set max size of payload. + pub fn new(limit: usize) -> Self { + Self::default().limit(limit) + } + /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { + pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } /// Set required mime-type of the request. By default mime type is not /// enforced. - pub fn mimetype(&mut self, mt: Mime) -> &mut Self { + pub fn mimetype(mut self, mt: Mime) -> Self { self.mimetype = Some(mt); self } @@ -867,6 +891,8 @@ impl PayloadConfig { } } +impl ExtractorConfig for PayloadConfig {} + impl Default for PayloadConfig { fn default() -> Self { PayloadConfig { @@ -876,6 +902,16 @@ impl Default for PayloadConfig { } } +macro_rules! tuple_config ({ $($T:ident),+} => { + impl<$($T,)+> ExtractorConfig for ($($T,)+) + where $($T: ExtractorConfig + Clone,)+ + { + fn store_default(ext: &mut ConfigStorage) { + $($T::store_default(ext);)+ + } + } +}); + macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { /// FromRequest implementation for tuple @@ -883,6 +919,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { { type Error = Error; type Future = $fut_type; + type Config = ($($T::Config,)+); fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { $fut_type { @@ -932,6 +969,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { impl

    FromRequest

    for () { type Error = Error; type Future = FutureResult<(), Error>; + type Config = (); fn from_request(_req: &mut ServiceFromRequest

    ) -> Self::Future { ok(()) @@ -942,6 +980,17 @@ impl

    FromRequest

    for () { mod m { use super::*; +tuple_config!(A); +tuple_config!(A, B); +tuple_config!(A, B, C); +tuple_config!(A, B, C, D); +tuple_config!(A, B, C, D, E); +tuple_config!(A, B, C, D, E, F); +tuple_config!(A, B, C, D, E, F, G); +tuple_config!(A, B, C, D, E, F, G, H); +tuple_config!(A, B, C, D, E, F, G, H, I); +tuple_config!(A, B, C, D, E, F, G, H, I, J); + tuple_from_req!(TupleFromRequest1, (0, A)); tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); diff --git a/src/handler.rs b/src/handler.rs index 31ae796b..313422ed 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,6 +1,8 @@ +use std::cell::RefCell; use std::marker::PhantomData; +use std::rc::Rc; -use actix_http::{Error, Response}; +use actix_http::{Error, Extensions, Response}; use actix_service::{NewService, Service, Void}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; @@ -19,10 +21,41 @@ pub trait FromRequest

    : Sized { /// Future that resolves to a Self type Future: Future; + /// Configuration for the extractor + type Config: ExtractorConfig; + /// Convert request to a Self fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future; } +/// Storage for extractor configs +#[derive(Default)] +pub struct ConfigStorage { + pub(crate) storage: Option>, +} + +impl ConfigStorage { + pub fn store(&mut self, config: C) { + if self.storage.is_none() { + self.storage = Some(Rc::new(Extensions::new())); + } + if let Some(ref mut ext) = self.storage { + Rc::get_mut(ext).unwrap().insert(config); + } + } +} + +pub trait ExtractorConfig: Default + Clone + 'static { + /// Set default configuration to config storage + fn store_default(ext: &mut ConfigStorage) { + ext.store(Self::default()) + } +} + +impl ExtractorConfig for () { + fn store_default(_: &mut ConfigStorage) {} +} + /// Handler converter factory pub trait Factory: Clone where @@ -288,18 +321,16 @@ where /// Extract arguments from request pub struct Extract> { + config: Rc>>>, _t: PhantomData<(P, T)>, } impl> Extract { - pub fn new() -> Self { - Extract { _t: PhantomData } - } -} - -impl> Default for Extract { - fn default() -> Self { - Self::new() + pub fn new(config: Rc>>>) -> Self { + Extract { + config, + _t: PhantomData, + } } } @@ -312,11 +343,15 @@ impl> NewService for Extract { type Future = FutureResult; fn new_service(&self, _: &()) -> Self::Future { - ok(ExtractService { _t: PhantomData }) + ok(ExtractService { + _t: PhantomData, + config: self.config.borrow().clone(), + }) } } pub struct ExtractService> { + config: Option>, _t: PhantomData<(P, T)>, } @@ -331,7 +366,7 @@ impl> Service for ExtractService { } fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { - let mut req = req.into(); + let mut req = ServiceFromRequest::new(req, self.config.clone()); ExtractResponse { fut: T::from_request(&mut req), req: Some(req), @@ -365,7 +400,6 @@ impl> Future for ExtractResponse { macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { impl Factory<($($T,)+), Res> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, - //$($T,)+ Res: Responder + 'static, { fn call(&self, param: ($($T,)+)) -> Res { diff --git a/src/lib.rs b/src/lib.rs index 0e81b65a..8ad689aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![allow(clippy::type_complexity)] mod app; -mod extractor; +pub mod extractor; pub mod handler; // mod info; pub mod blocking; @@ -20,7 +20,7 @@ pub use actix_http::Response as HttpResponse; pub use actix_http::{http, Error, HttpMessage, ResponseError}; pub use crate::app::App; -pub use crate::extractor::{Form, Json, Path, Query}; +pub use crate::extractor::{Form, Json, Path, PayloadConfig, Query}; pub use crate::handler::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; diff --git a/src/request.rs b/src/request.rs index 538064f1..a7c84b53 100644 --- a/src/request.rs +++ b/src/request.rs @@ -143,6 +143,7 @@ impl HttpMessage for HttpRequest { impl

    FromRequest

    for HttpRequest { type Error = Error; type Future = FutureResult; + type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { diff --git a/src/resource.rs b/src/resource.rs index ab6096c5..0bee0ecd 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -75,7 +75,7 @@ where /// } /// ``` pub fn route(mut self, route: Route

    ) -> Self { - self.routes.push(route); + self.routes.push(route.finish()); self } diff --git a/src/route.rs b/src/route.rs index 99117afe..578ba79e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,11 +1,15 @@ +use std::cell::RefCell; use std::rc::Rc; -use actix_http::{http::Method, Error, Response}; +use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; use crate::filter::{self, Filter}; -use crate::handler::{AsyncFactory, AsyncHandle, Extract, Factory, FromRequest, Handle}; +use crate::handler::{ + AsyncFactory, AsyncHandle, ConfigStorage, Extract, ExtractorConfig, Factory, + FromRequest, Handle, +}; use crate::responder::Responder; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -37,33 +41,50 @@ type BoxedRouteNewService = Box< pub struct Route

    { service: BoxedRouteNewService, ServiceResponse>, filters: Rc>>, + config: ConfigStorage, + config_ref: Rc>>>, } impl Route

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

    { + let config_ref = Rc::new(RefCell::new(None)); Route { - service: Box::new(RouteNewService::new(Extract::new().and_then( - Handle::new(|| HttpResponse::NotFound()).map_err(|_| panic!()), - ))), + service: Box::new(RouteNewService::new( + Extract::new(config_ref.clone()).and_then( + Handle::new(|| HttpResponse::NotFound()).map_err(|_| panic!()), + ), + )), filters: Rc::new(Vec::new()), + config: ConfigStorage::default(), + config_ref, } } + /// Create new `GET` route. pub fn get() -> Route

    { Route::new().method(Method::GET) } + /// Create new `POST` route. pub fn post() -> Route

    { Route::new().method(Method::POST) } + /// Create new `PUT` route. pub fn put() -> Route

    { Route::new().method(Method::PUT) } + /// Create new `DELETE` route. pub fn delete() -> Route

    { Route::new().method(Method::DELETE) } + + pub(crate) fn finish(self) -> Self { + *self.config_ref.borrow_mut() = self.config.storage.clone(); + self + } } impl

    NewService for Route

    { @@ -260,8 +281,10 @@ impl Route

    { T: FromRequest

    + 'static, R: Responder + 'static, { + T::Config::store_default(&mut self.config); self.service = Box::new(RouteNewService::new( - Extract::new().and_then(Handle::new(handler).map_err(|_| panic!())), + Extract::new(self.config_ref.clone()) + .and_then(Handle::new(handler).map_err(|_| panic!())), )); self } @@ -305,10 +328,39 @@ impl Route

    { R::Error: Into, { self.service = Box::new(RouteNewService::new( - Extract::new().and_then(AsyncHandle::new(handler).map_err(|_| panic!())), + Extract::new(self.config_ref.clone()) + .and_then(AsyncHandle::new(handler).map_err(|_| panic!())), )); self } + + /// This method allows to add extractor configuration + /// for specific route. + /// + /// ```rust + /// use actix_web::{web, extractor, App}; + /// + /// /// extract text data from request + /// fn index(body: String) -> String { + /// format!("Body {}!", body) + /// } + /// + /// fn main() { + /// let app = App::new().resource("/index.html", |r| { + /// r.route( + /// web::get() + /// // limit size of the payload + /// .config(extractor::PayloadConfig::new(4096)) + /// // register handler + /// .to(index) + /// ) + /// }); + /// } + /// ``` + pub fn config(mut self, config: C) -> Self { + self.config.store(config); + self + } } // pub struct RouteServiceBuilder { diff --git a/src/service.rs b/src/service.rs index 99af73c1..5602a613 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::cell::{Ref, RefMut}; use std::rc::Rc; @@ -167,9 +168,18 @@ impl

    std::ops::DerefMut for ServiceRequest

    { pub struct ServiceFromRequest

    { req: HttpRequest, payload: Payload

    , + config: Option>, } impl

    ServiceFromRequest

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

    , config: Option>) -> Self { + Self { + req: req.req, + payload: req.payload, + config, + } + } + #[inline] pub fn into_request(self) -> HttpRequest { self.req @@ -180,6 +190,16 @@ impl

    ServiceFromRequest

    { pub fn error_response>(self, err: E) -> ServiceResponse { ServiceResponse::new(self.req, err.into().into()) } + + /// Load extractor configuration + pub fn load_config(&self) -> Cow { + if let Some(ref ext) = self.config { + if let Some(cfg) = ext.get::() { + return Cow::Borrowed(cfg); + } + } + Cow::Owned(T::default()) + } } impl

    std::ops::Deref for ServiceFromRequest

    { @@ -204,15 +224,6 @@ impl

    HttpMessage for ServiceFromRequest

    { } } -impl

    From> for ServiceFromRequest

    { - fn from(req: ServiceRequest

    ) -> Self { - Self { - req: req.req, - payload: req.payload, - } - } -} - pub struct ServiceResponse { request: HttpRequest, response: Response, diff --git a/src/state.rs b/src/state.rs index db263777..4a450245 100644 --- a/src/state.rs +++ b/src/state.rs @@ -48,6 +48,7 @@ impl Clone for State { impl FromRequest

    for State { type Error = Error; type Future = FutureResult; + type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { diff --git a/src/test.rs b/src/test.rs index d6caf897..4899cfe4 100644 --- a/src/test.rs +++ b/src/test.rs @@ -9,7 +9,7 @@ use actix_router::{Path, Url}; use bytes::Bytes; use crate::request::HttpRequest; -use crate::service::ServiceRequest; +use crate::service::{ServiceFromRequest, ServiceRequest}; /// Test `Request` builder /// @@ -133,7 +133,7 @@ impl TestRequest { } /// Complete request creation and generate `HttpRequest` instance - pub fn request(mut self) -> HttpRequest { + pub fn to_request(mut self) -> HttpRequest { let req = self.req.finish(); ServiceRequest::new( @@ -143,4 +143,16 @@ impl TestRequest { ) .into_request() } + + /// Complete request creation and generate `ServiceFromRequest` instance + pub fn to_from(mut self) -> ServiceFromRequest { + let req = self.req.finish(); + + let req = ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + Rc::new(self.extensions), + ); + ServiceFromRequest::new(req, None) + } } From a8f3dec527dd3ce3d0aef1139e23a367bec5f7f4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 01:03:28 -0800 Subject: [PATCH 0955/1635] use tarpaulin from cache --- .travis.yml | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 15a0b04e..da8f33be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,19 +34,9 @@ before_script: - export PATH=$PATH:~/.cargo/bin script: - - | - if [[ "$TRAVIS_RUST_VERSION" != "nightly-2019-03-02" ]]; then - cargo clean - cargo check - cargo test -- --nocapture - fi - - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin - RUST_BACKTRACE=1 cargo tarpaulin --out Xml - bash <(curl -s https://codecov.io/bash) - echo "Uploaded code coverage" - fi + - cargo clean + - cargo check + - cargo test -- --nocapture # Upload docs after_success: @@ -58,3 +48,9 @@ after_success: ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && echo "Uploaded documentation" fi + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + cargo tarpaulin --features="ssl" --out Xml + bash <(curl -s https://codecov.io/bash) + echo "Uploaded code coverage" + fi From f90ca868ca76f8e3d63475f3f661558f1e96330f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 01:12:06 -0800 Subject: [PATCH 0956/1635] update tests --- src/extractor.rs | 9 +++------ src/filter.rs | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index 24e4c8af..5fa9af61 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1022,8 +1022,7 @@ mod tests { let mut rt = actix_rt::Runtime::new().unwrap(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .finish() - .into(); + .to_from(); let s = rt.block_on(Bytes::from_request(&mut req)).unwrap(); assert_eq!(s, Bytes::from_static(b"hello=world")); @@ -1034,8 +1033,7 @@ mod tests { let mut rt = actix_rt::Runtime::new().unwrap(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .finish() - .into(); + .to_from(); let s = rt.block_on(String::from_request(&mut req)).unwrap(); assert_eq!(s, "hello=world"); @@ -1050,8 +1048,7 @@ mod tests { ) .header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) - .finish() - .into(); + .to_from(); let s = rt.block_on(Form::::from_request(&mut req)).unwrap(); assert_eq!(s.hello, "world"); diff --git a/src/filter.rs b/src/filter.rs index 9b49c9dd..501c60d8 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -311,7 +311,7 @@ mod tests { #[test] fn test_preds() { - let r = TestRequest::default().method(Method::TRACE).request(); + let r = TestRequest::default().method(Method::TRACE).to_request(); assert!(Not(Get()).check(&r,)); assert!(!Not(Trace()).check(&r,)); From 015364edf841d37a4a4ddd977934aa87fd77ede8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 08:00:12 -0800 Subject: [PATCH 0957/1635] fix travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index da8f33be..d994a80d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,7 +50,7 @@ after_success: fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then - cargo tarpaulin --features="ssl" --out Xml + cargo tarpaulin --out Xml bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From b81ae899f6472d58eb525715308ed9ad3f59b241 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 08:24:09 -0800 Subject: [PATCH 0958/1635] better naming --- .travis.yml | 1 - src/app.rs | 60 ++++++++++++++++++++++++++++------------------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index d994a80d..1d3c227a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,6 @@ before_script: script: - cargo clean - - cargo check - cargo test -- --nocapture # Upload docs diff --git a/src/app.rs b/src/app.rs index 2a2380b2..c9c23d9c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -188,13 +188,13 @@ where > where M: NewTransform< - AppService

    , + AppRouting

    , Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoNewTransform>, + F: IntoNewTransform>, { let fref = Rc::new(RefCell::new(None)); let endpoint = ApplyNewService::new(mw, AppEntry::new(fref.clone())); @@ -253,7 +253,7 @@ pub struct AppRouter { default: Option>>, defaults: Vec>>>>>, endpoint: T, - factory_ref: Rc>>>, + factory_ref: Rc>>>, extensions: Extensions, state: Vec>, _t: PhantomData<(P, B)>, @@ -465,7 +465,7 @@ where } // set factory - *self.factory_ref.borrow_mut() = Some(AppFactory { + *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { services: Rc::new(self.services), }); @@ -478,25 +478,25 @@ where } } -pub struct AppFactory

    { +pub struct AppRoutingFactory

    { services: Rc)>>, } -impl NewService for AppFactory

    { +impl NewService for AppRoutingFactory

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type InitError = (); - type Service = AppService

    ; - type Future = CreateAppService

    ; + type Service = AppRouting

    ; + type Future = AppRoutingFactoryResponse

    ; fn new_service(&self, _: &()) -> Self::Future { - CreateAppService { + AppRoutingFactoryResponse { fut: self .services .iter() .map(|(path, service)| { - CreateAppServiceItem::Future( + CreateAppRoutingItem::Future( Some(path.clone()), service.new_service(&()), ) @@ -510,17 +510,17 @@ type HttpServiceFut

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

    { - fut: Vec>, +pub struct AppRoutingFactoryResponse

    { + fut: Vec>, } -enum CreateAppServiceItem

    { +enum CreateAppRoutingItem

    { Future(Option, HttpServiceFut

    ), Service(ResourceDef, HttpService

    ), } -impl

    Future for CreateAppService

    { - type Item = AppService

    ; +impl

    Future for AppRoutingFactoryResponse

    { + type Item = AppRouting

    ; type Error = (); fn poll(&mut self) -> Poll { @@ -529,7 +529,7 @@ impl

    Future for CreateAppService

    { // poll http services for item in &mut self.fut { let res = match item { - CreateAppServiceItem::Future(ref mut path, ref mut fut) => { + CreateAppRoutingItem::Future(ref mut path, ref mut fut) => { match fut.poll()? { Async::Ready(service) => Some((path.take().unwrap(), service)), Async::NotReady => { @@ -538,11 +538,11 @@ impl

    Future for CreateAppService

    { } } } - CreateAppServiceItem::Service(_, _) => continue, + CreateAppRoutingItem::Service(_, _) => continue, }; if let Some((path, service)) = res { - *item = CreateAppServiceItem::Service(path, service); + *item = CreateAppRoutingItem::Service(path, service); } } @@ -552,14 +552,14 @@ impl

    Future for CreateAppService

    { .drain(..) .fold(Router::build(), |mut router, item| { match item { - CreateAppServiceItem::Service(path, service) => { + CreateAppRoutingItem::Service(path, service) => { router.rdef(path, service) } - CreateAppServiceItem::Future(_, _) => unreachable!(), + CreateAppRoutingItem::Future(_, _) => unreachable!(), } router }); - Ok(Async::Ready(AppService { + Ok(Async::Ready(AppRouting { router: router.finish(), ready: None, })) @@ -569,12 +569,12 @@ impl

    Future for CreateAppService

    { } } -pub struct AppService

    { +pub struct AppRouting

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

    , ResourceInfo)>, } -impl

    Service for AppService

    { +impl

    Service for AppRouting

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); @@ -599,12 +599,13 @@ impl

    Service for AppService

    { } #[doc(hidden)] +/// Wrapper service for routing pub struct AppEntry

    { - factory: Rc>>>, + factory: Rc>>>, } impl

    AppEntry

    { - fn new(factory: Rc>>>) -> Self { + fn new(factory: Rc>>>) -> Self { AppEntry { factory } } } @@ -614,8 +615,8 @@ impl NewService for AppEntry

    { type Response = ServiceResponse; type Error = (); type InitError = (); - type Service = AppService

    ; - type Future = CreateAppService

    ; + type Service = AppRouting

    ; + type Future = AppRoutingFactoryResponse

    ; fn new_service(&self, _: &()) -> Self::Future { self.factory.borrow_mut().as_mut().unwrap().new_service(&()) @@ -644,16 +645,19 @@ impl Service for AppChain { type Error = (); type Future = FutureResult; + #[inline] fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } + #[inline] fn call(&mut self, req: Self::Request) -> Self::Future { ok(req) } } -/// Service factory to convert `Request` to a `ServiceRequest` +/// Service factory to convert `Request` to a `ServiceRequest`. +/// It also executes state factories. pub struct AppInit where C: NewService, Response = ServiceRequest

    >, From 237677be15d00b6074b18eb214790ccbd21eb349 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 12:09:38 -0800 Subject: [PATCH 0959/1635] rename filter to guard --- src/{filter.rs => guard.rs} | 139 +++++++++++---------- src/lib.rs | 2 +- src/resource.rs | 51 ++++---- src/route.rs | 243 ++++++++---------------------------- 4 files changed, 145 insertions(+), 290 deletions(-) rename src/{filter.rs => guard.rs} (67%) diff --git a/src/filter.rs b/src/guard.rs similarity index 67% rename from src/filter.rs rename to src/guard.rs index 501c60d8..10a56921 100644 --- a/src/filter.rs +++ b/src/guard.rs @@ -1,47 +1,48 @@ -//! Route match predicates +//! Route match guards. #![allow(non_snake_case)] use actix_http::http::{self, header, HttpTryFrom}; use actix_http::RequestHead; -/// Trait defines resource predicate. -/// Predicate can modify request object. It is also possible to +/// Trait defines resource guards. Guards are used for routes selection. +/// +/// Guard can not modify request object. But it is possible to /// to store extra attributes on request by using `Extensions` container, -/// Extensions container available via `HttpRequest::extensions()` method. -pub trait Filter { +/// Extensions container available via `RequestHead::extensions()` method. +pub trait Guard { /// Check if request matches predicate fn check(&self, request: &RequestHead) -> bool; } -/// Return filter that matches if any of supplied filters. +/// Return guard that matches if any of supplied guards. /// /// ```rust -/// use actix_web::{web, filter, App, HttpResponse}; +/// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { /// App::new().resource("/index.html", |r| /// r.route( /// web::route() -/// .filter(filter::Any(filter::Get()).or(filter::Post())) +/// .guard(guard::Any(guard::Get()).or(guard::Post())) /// .to(|| HttpResponse::MethodNotAllowed())) /// ); /// } /// ``` -pub fn Any(filter: F) -> AnyFilter { - AnyFilter(vec![Box::new(filter)]) +pub fn Any(guard: F) -> AnyGuard { + AnyGuard(vec![Box::new(guard)]) } -/// Matches if any of supplied filters matche. -pub struct AnyFilter(Vec>); +/// Matches if any of supplied guards matche. +pub struct AnyGuard(Vec>); -impl AnyFilter { - /// Add filter to a list of filters to check - pub fn or(mut self, filter: F) -> Self { - self.0.push(Box::new(filter)); +impl AnyGuard { + /// Add guard to a list of guards to check + pub fn or(mut self, guard: F) -> Self { + self.0.push(Box::new(guard)); self } } -impl Filter for AnyFilter { +impl Guard for AnyGuard { fn check(&self, req: &RequestHead) -> bool { for p in &self.0 { if p.check(req) { @@ -52,37 +53,37 @@ impl Filter for AnyFilter { } } -/// Return filter that matches if all of supplied filters match. +/// Return guard that matches if all of the supplied guards. /// /// ```rust /// # extern crate actix_web; -/// use actix_web::{filter, web, App, HttpResponse}; +/// use actix_web::{guard, web, App, HttpResponse}; /// /// fn main() { /// App::new().resource("/index.html", |r| { /// r.route(web::route() -/// .filter( -/// filter::All(filter::Get()).and(filter::Header("content-type", "text/plain"))) +/// .guard( +/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain"))) /// .to(|| HttpResponse::MethodNotAllowed())) /// }); /// } /// ``` -pub fn All(filter: F) -> AllFilter { - AllFilter(vec![Box::new(filter)]) +pub fn All(guard: F) -> AllGuard { + AllGuard(vec![Box::new(guard)]) } -/// Matches if all of supplied filters matche. -pub struct AllFilter(Vec>); +/// Matches if all of supplied guards. +pub struct AllGuard(Vec>); -impl AllFilter { - /// Add new predicate to list of predicates to check - pub fn and(mut self, filter: F) -> Self { - self.0.push(Box::new(filter)); +impl AllGuard { + /// Add new guard to the list of guards to check + pub fn and(mut self, guard: F) -> Self { + self.0.push(Box::new(guard)); self } } -impl Filter for AllFilter { +impl Guard for AllGuard { fn check(&self, request: &RequestHead) -> bool { for p in &self.0 { if !p.check(request) { @@ -93,93 +94,93 @@ impl Filter for AllFilter { } } -/// Return predicate that matches if supplied predicate does not match. -pub fn Not(filter: F) -> NotFilter { - NotFilter(Box::new(filter)) +/// Return guard that matches if supplied guard does not match. +pub fn Not(guard: F) -> NotGuard { + NotGuard(Box::new(guard)) } #[doc(hidden)] -pub struct NotFilter(Box); +pub struct NotGuard(Box); -impl Filter for NotFilter { +impl Guard for NotGuard { fn check(&self, request: &RequestHead) -> bool { !self.0.check(request) } } -/// Http method predicate +/// Http method guard #[doc(hidden)] -pub struct MethodFilter(http::Method); +pub struct MethodGuard(http::Method); -impl Filter for MethodFilter { +impl Guard for MethodGuard { fn check(&self, request: &RequestHead) -> bool { request.method == self.0 } } -/// Predicate to match *GET* http method -pub fn Get() -> MethodFilter { - MethodFilter(http::Method::GET) +/// Guard to match *GET* http method +pub fn Get() -> MethodGuard { + MethodGuard(http::Method::GET) } /// Predicate to match *POST* http method -pub fn Post() -> MethodFilter { - MethodFilter(http::Method::POST) +pub fn Post() -> MethodGuard { + MethodGuard(http::Method::POST) } /// Predicate to match *PUT* http method -pub fn Put() -> MethodFilter { - MethodFilter(http::Method::PUT) +pub fn Put() -> MethodGuard { + MethodGuard(http::Method::PUT) } /// Predicate to match *DELETE* http method -pub fn Delete() -> MethodFilter { - MethodFilter(http::Method::DELETE) +pub fn Delete() -> MethodGuard { + MethodGuard(http::Method::DELETE) } /// Predicate to match *HEAD* http method -pub fn Head() -> MethodFilter { - MethodFilter(http::Method::HEAD) +pub fn Head() -> MethodGuard { + MethodGuard(http::Method::HEAD) } /// Predicate to match *OPTIONS* http method -pub fn Options() -> MethodFilter { - MethodFilter(http::Method::OPTIONS) +pub fn Options() -> MethodGuard { + MethodGuard(http::Method::OPTIONS) } /// Predicate to match *CONNECT* http method -pub fn Connect() -> MethodFilter { - MethodFilter(http::Method::CONNECT) +pub fn Connect() -> MethodGuard { + MethodGuard(http::Method::CONNECT) } /// Predicate to match *PATCH* http method -pub fn Patch() -> MethodFilter { - MethodFilter(http::Method::PATCH) +pub fn Patch() -> MethodGuard { + MethodGuard(http::Method::PATCH) } /// Predicate to match *TRACE* http method -pub fn Trace() -> MethodFilter { - MethodFilter(http::Method::TRACE) +pub fn Trace() -> MethodGuard { + MethodGuard(http::Method::TRACE) } /// Predicate to match specified http method -pub fn Method(method: http::Method) -> MethodFilter { - MethodFilter(method) +pub fn Method(method: http::Method) -> MethodGuard { + MethodGuard(method) } /// Return predicate that matches if request contains specified header and /// value. -pub fn Header(name: &'static str, value: &'static str) -> HeaderFilter { - HeaderFilter( +pub fn Header(name: &'static str, value: &'static str) -> HeaderGuard { + HeaderGuard( header::HeaderName::try_from(name).unwrap(), header::HeaderValue::from_static(value), ) } #[doc(hidden)] -pub struct HeaderFilter(header::HeaderName, header::HeaderValue); +pub struct HeaderGuard(header::HeaderName, header::HeaderValue); -impl Filter for HeaderFilter { +impl Guard for HeaderGuard { fn check(&self, req: &RequestHead) -> bool { if let Some(val) = req.headers.get(&self.0) { return val == self.1; @@ -197,26 +198,26 @@ impl Filter for HeaderFilter { // /// fn main() { // /// App::new().resource("/index.html", |r| { // /// r.route() -// /// .filter(pred::Host("www.rust-lang.org")) +// /// .guard(pred::Host("www.rust-lang.org")) // /// .f(|_| HttpResponse::MethodNotAllowed()) // /// }); // /// } // /// ``` -// pub fn Host>(host: H) -> HostFilter { -// HostFilter(host.as_ref().to_string(), None) +// pub fn Host>(host: H) -> HostGuard { +// HostGuard(host.as_ref().to_string(), None) // } // #[doc(hidden)] -// pub struct HostFilter(String, Option); +// pub struct HostGuard(String, Option); -// impl HostFilter { +// impl HostGuard { // /// Set reuest scheme to match // pub fn scheme>(&mut self, scheme: H) { // self.1 = Some(scheme.as_ref().to_string()) // } // } -// impl Filter for HostFilter { +// impl Guard for HostGuard { // fn check(&self, _req: &RequestHead) -> bool { // // let info = req.connection_info(); // // if let Some(ref scheme) = self.1 { diff --git a/src/lib.rs b/src/lib.rs index 8ad689aa..e876a7ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ pub mod extractor; pub mod handler; // mod info; pub mod blocking; -pub mod filter; +pub mod guard; pub mod middleware; mod request; mod resource; diff --git a/src/resource.rs b/src/resource.rs index 0bee0ecd..98c2dc11 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -56,22 +56,21 @@ where InitError = (), >, { - /// Register a new route and return mutable reference to *Route* object. - /// *Route* is used for route configuration, i.e. adding predicates, + /// Register a new route. + /// *Route* is used for route configuration, i.e. adding guards, /// setting up handler. /// - /// ```rust,ignore - /// use actix_web::*; + /// ```rust + /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { /// let app = App::new() /// .resource("/", |r| { - /// r.route() - /// .filter(pred::Any(pred::Get()).or(pred::Put())) - /// .filter(pred::Header("Content-Type", "text/plain")) - /// .f(|r| HttpResponse::Ok()) - /// }) - /// .finish(); + /// r.route(web::route() + /// .guard(guard::Any(guard::Get()).or(guard::Put())) + /// .guard(guard::Header("Content-Type", "text/plain")) + /// .to(|| HttpResponse::Ok())) + /// }); /// } /// ``` pub fn route(mut self, route: Route

    ) -> Self { @@ -81,21 +80,23 @@ where /// Register a new route and add handler. /// - /// ```rust,ignore - /// # extern crate actix_web; + /// ```rust /// use actix_web::*; - /// fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// - /// App::new().resource("/", |r| r.with(index)); + /// fn index(req: HttpRequest) -> HttpResponse { + /// unimplemented!() + /// } + /// + /// App::new().resource("/", |r| r.to(index)); /// ``` /// /// This is shortcut for: /// - /// ```rust,ignore + /// ```rust /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route().with(index)); + /// App::new().resource("/", |r| r.route(web::route().to(index))); /// ``` pub fn to(mut self, handler: F) -> Self where @@ -109,30 +110,26 @@ where /// Register a new route and add async handler. /// - /// ```rust,ignore - /// # extern crate actix_web; - /// # extern crate futures; + /// ```rust /// use actix_web::*; - /// use futures::future::Future; + /// use futures::future::{ok, Future}; /// - /// fn index(req: HttpRequest) -> Box> { - /// unimplemented!() + /// fn index(req: HttpRequest) -> impl Future { + /// ok(HttpResponse::Ok().finish()) /// } /// - /// App::new().resource("/", |r| r.with_async(index)); + /// App::new().resource("/", |r| r.to_async(index)); /// ``` /// /// This is shortcut for: /// - /// ```rust,ignore - /// # extern crate actix_web; - /// # extern crate futures; + /// ```rust /// # use actix_web::*; /// # use futures::future::Future; /// # fn index(req: HttpRequest) -> Box> { /// # unimplemented!() /// # } - /// App::new().resource("/", |r| r.route().with_async(index)); + /// App::new().resource("/", |r| r.route(web::route().to_async(index))); /// ``` #[allow(clippy::wrong_self_convention)] pub fn to_async(mut self, handler: F) -> Self diff --git a/src/route.rs b/src/route.rs index 578ba79e..16a4fc5b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,7 +5,7 @@ use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::filter::{self, Filter}; +use crate::guard::{self, Guard}; use crate::handler::{ AsyncFactory, AsyncHandle, ConfigStorage, Extract, ExtractorConfig, Factory, FromRequest, Handle, @@ -40,7 +40,7 @@ type BoxedRouteNewService = Box< /// If handler is not explicitly set, default *404 Not Found* handler is used. pub struct Route

    { service: BoxedRouteNewService, ServiceResponse>, - filters: Rc>>, + guards: Rc>>, config: ConfigStorage, config_ref: Rc>>>, } @@ -55,7 +55,7 @@ impl Route

    { Handle::new(|| HttpResponse::NotFound()).map_err(|_| panic!()), ), )), - filters: Rc::new(Vec::new()), + guards: Rc::new(Vec::new()), config: ConfigStorage::default(), config_ref, } @@ -98,7 +98,7 @@ impl

    NewService for Route

    { fn new_service(&self, _: &()) -> Self::Future { CreateRouteService { fut: self.service.new_service(&()), - filters: self.filters.clone(), + guards: self.guards.clone(), } } } @@ -109,7 +109,7 @@ type RouteFuture

    = Box< pub struct CreateRouteService

    { fut: RouteFuture

    , - filters: Rc>>, + guards: Rc>>, } impl

    Future for CreateRouteService

    { @@ -120,7 +120,7 @@ impl

    Future for CreateRouteService

    { match self.fut.poll()? { Async::Ready(service) => Ok(Async::Ready(RouteService { service, - filters: self.filters.clone(), + guards: self.guards.clone(), })), Async::NotReady => Ok(Async::NotReady), } @@ -129,12 +129,12 @@ impl

    Future for CreateRouteService

    { pub struct RouteService

    { service: BoxedRouteService, ServiceResponse>, - filters: Rc>>, + guards: Rc>>, } impl

    RouteService

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

    ) -> bool { - for f in self.filters.iter() { + for f in self.guards.iter() { if !f.check(req.head()) { return false; } @@ -159,45 +159,41 @@ impl

    Service for RouteService

    { } impl Route

    { - /// Add method match filter to the route. + /// Add method guard to the route. /// - /// ```rust,ignore - /// # extern crate actix_web; + /// ```rust /// # use actix_web::*; /// # fn main() { /// App::new().resource("/path", |r| { - /// r.route() - /// .filter(pred::Get()) - /// .filter(pred::Header("content-type", "text/plain")) - /// .f(|req| HttpResponse::Ok()) - /// }) - /// # .finish(); + /// r.route(web::get() + /// .guard(guard::Get()) + /// .guard(guard::Header("content-type", "text/plain")) + /// .to(|req: HttpRequest| HttpResponse::Ok())) + /// }); /// # } /// ``` pub fn method(mut self, method: Method) -> Self { - Rc::get_mut(&mut self.filters) + Rc::get_mut(&mut self.guards) .unwrap() - .push(Box::new(filter::Method(method))); + .push(Box::new(guard::Method(method))); self } - /// Add filter to the route. + /// Add guard to the route. /// - /// ```rust,ignore - /// # extern crate actix_web; + /// ```rust /// # use actix_web::*; /// # fn main() { /// App::new().resource("/path", |r| { - /// r.route() - /// .filter(pred::Get()) - /// .filter(pred::Header("content-type", "text/plain")) - /// .f(|req| HttpResponse::Ok()) - /// }) - /// # .finish(); + /// r.route(web::route() + /// .guard(guard::Get()) + /// .guard(guard::Header("content-type", "text/plain")) + /// .to(|req: HttpRequest| HttpResponse::Ok())) + /// }); /// # } /// ``` - pub fn filter(mut self, f: F) -> Self { - Rc::get_mut(&mut self.filters).unwrap().push(Box::new(f)); + pub fn guard(mut self, f: F) -> Self { + Rc::get_mut(&mut self.guards).unwrap().push(Box::new(f)); self } @@ -214,19 +210,16 @@ impl Route

    { // { // RouteServiceBuilder { // service: md.into_new_service(), - // filters: self.filters, + // guards: self.guards, // _t: PhantomData, // } // } - /// Set handler function, use request extractor for parameters. + /// Set handler function, use request extractors for parameters. /// - /// ```rust,ignore - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; + /// ```rust /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Path, Result}; + /// use actix_web::{web, http, App, Path}; /// /// #[derive(Deserialize)] /// struct Info { @@ -234,27 +227,24 @@ impl Route

    { /// } /// /// /// extract path info using serde - /// fn index(info: Path) -> Result { - /// Ok(format!("Welcome {}!", info.username)) + /// fn index(info: Path) -> String { + /// format!("Welcome {}!", info.username) /// } /// /// fn main() { /// let app = App::new().resource( /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index), - /// ); // <- use `with` extractor + /// |r| r.route(web::get().to(index)), // <- register handler + /// ); /// } /// ``` /// /// It is possible to use multiple extractors for one handler function. /// - /// ```rust,ignore - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// #[macro_use] extern crate serde_derive; + /// ```rust /// # use std::collections::HashMap; - /// use actix_web::{http, App, Json, Path, Query, Result}; + /// # use serde_derive::Deserialize; + /// use actix_web::{web, http, App, Json, Path, Query}; /// /// #[derive(Deserialize)] /// struct Info { @@ -262,17 +252,15 @@ impl Route

    { /// } /// /// /// extract path info using serde - /// fn index( - /// path: Path, query: Query>, body: Json, - /// ) -> Result { - /// Ok(format!("Welcome {}!", path.username)) + /// fn index(path: Path, query: Query>, body: Json) -> String { + /// format!("Welcome {}!", path.username) /// } /// /// fn main() { /// let app = App::new().resource( /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with(index), - /// ); // <- use `with` extractor + /// |r| r.route(web::method(http::Method::GET).to(index)), + /// ); /// } /// ``` pub fn to(mut self, handler: F) -> Route

    @@ -289,16 +277,13 @@ impl Route

    { self } - /// Set async handler function, use request extractor for parameters. - /// Also this method needs to be used if your handler function returns - /// `impl Future<>` + /// Set async handler function, use request extractors for parameters. + /// This method has to be used if your handler function returns `impl Future<>` /// - /// ```rust,ignore - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; + /// ```rust + /// # use futures::future::ok; /// #[macro_use] extern crate serde_derive; - /// use actix_web::{http, App, Error, Path}; + /// use actix_web::{web, http, App, Error, Path}; /// use futures::Future; /// /// #[derive(Deserialize)] @@ -307,15 +292,15 @@ impl Route

    { /// } /// /// /// extract path info using serde - /// fn index(info: Path) -> Box> { - /// unimplemented!() + /// fn index(info: Path) -> impl Future { + /// ok("Hello World!") /// } /// /// fn main() { /// let app = App::new().resource( /// "/{username}/index.html", // <- define path parameters - /// |r| r.method(http::Method::GET).with_async(index), - /// ); // <- use `with` extractor + /// |r| r.route(web::get().to_async(index)), // <- register async handler + /// ); /// } /// ``` #[allow(clippy::wrong_self_convention)] @@ -363,134 +348,6 @@ impl Route

    { } } -// pub struct RouteServiceBuilder { -// service: T, -// filters: Vec>, -// _t: PhantomData<(P, U1, U2)>, -// } - -// impl RouteServiceBuilder -// where -// T: NewService< -// Request = HandlerRequest, -// Response = HandlerRequest, -// Error = Error, -// InitError = (), -// >, -// { -// pub fn new>(factory: F) -> Self { -// RouteServiceBuilder { -// service: factory.into_new_service(), -// filters: Vec::new(), -// _t: PhantomData, -// } -// } - -// /// Add method match filter to the route. -// /// -// /// ```rust -// /// # extern crate actix_web; -// /// # use actix_web::*; -// /// # fn main() { -// /// App::new().resource("/path", |r| { -// /// r.route() -// /// .filter(pred::Get()) -// /// .filter(pred::Header("content-type", "text/plain")) -// /// .f(|req| HttpResponse::Ok()) -// /// }) -// /// # .finish(); -// /// # } -// /// ``` -// pub fn method(mut self, method: Method) -> Self { -// self.filters.push(Box::new(filter::Method(method))); -// self -// } - -// /// Add filter to the route. -// /// -// /// ```rust -// /// # extern crate actix_web; -// /// # use actix_web::*; -// /// # fn main() { -// /// App::new().resource("/path", |r| { -// /// r.route() -// /// .filter(pred::Get()) -// /// .filter(pred::Header("content-type", "text/plain")) -// /// .f(|req| HttpResponse::Ok()) -// /// }) -// /// # .finish(); -// /// # } -// /// ``` -// pub fn filter + 'static>(&mut self, f: F) -> &mut Self { -// self.filters.push(Box::new(f)); -// self -// } - -// pub fn map>( -// self, -// md: F, -// ) -> RouteServiceBuilder< -// impl NewService< -// Request = HandlerRequest, -// Response = HandlerRequest, -// Error = Error, -// InitError = (), -// >, -// S, -// U1, -// U2, -// > -// where -// T1: NewService< -// Request = HandlerRequest, -// Response = HandlerRequest, -// InitError = (), -// >, -// T1::Error: Into, -// { -// RouteServiceBuilder { -// service: self -// .service -// .and_then(md.into_new_service().map_err(|e| e.into())), -// filters: self.filters, -// _t: PhantomData, -// } -// } - -// pub fn to_async(self, handler: F) -> Route -// where -// F: AsyncFactory, -// P: FromRequest + 'static, -// R: IntoFuture, -// R::Item: Into, -// R::Error: Into, -// { -// Route { -// service: self -// .service -// .and_then(Extract::new(P::Config::default())) -// .then(AsyncHandle::new(handler)), -// filters: Rc::new(self.filters), -// } -// } - -// pub fn to(self, handler: F) -> Route -// where -// F: Factory + 'static, -// P: FromRequest + 'static, -// R: Responder + 'static, -// { -// Route { -// service: Box::new(RouteNewService::new( -// self.service -// .and_then(Extract::new(P::Config::default())) -// .and_then(Handle::new(handler)), -// )), -// filters: Rc::new(self.filters), -// } -// } -// } - struct RouteNewService where T: NewService, Error = (Error, ServiceFromRequest

    )>, From e50d4c5e0e7bcaaf50031ff1aaebc222910e3517 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 13:53:31 -0800 Subject: [PATCH 0960/1635] rename extractor module to extract, re-enable doc tests --- Cargo.toml | 1 - src/{extractor.rs => extract.rs} | 279 +++++++++++++++++-------------- src/handler.rs | 54 +----- src/lib.rs | 18 +- src/request.rs | 2 +- src/resource.rs | 3 +- src/responder.rs | 61 ++++--- src/route.rs | 18 +- src/state.rs | 2 +- src/test.rs | 5 +- 10 files changed, 215 insertions(+), 228 deletions(-) rename src/{extractor.rs => extract.rs} (83%) diff --git a/Cargo.toml b/Cargo.toml index 30d23d02..03b1794f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ actix-router = { git = "https://github.com/actix/actix-net.git" } bytes = "0.4" derive_more = "0.14" -either = "1.5.1" encoding = "0.2" futures = "0.1" log = "0.4" diff --git a/src/extractor.rs b/src/extract.rs similarity index 83% rename from src/extractor.rs rename to src/extract.rs index 5fa9af61..d6e53327 100644 --- a/src/extractor.rs +++ b/src/extract.rs @@ -20,65 +20,103 @@ use actix_http::error::{ UrlencodedError, }; use actix_http::http::StatusCode; -use actix_http::{HttpMessage, Response}; +use actix_http::{Extensions, HttpMessage, Response}; use actix_router::PathDeserializer; -use crate::handler::{ConfigStorage, ExtractorConfig, FromRequest}; use crate::request::HttpRequest; use crate::responder::Responder; use crate::service::ServiceFromRequest; +/// Trait implemented by types that can be extracted from request. +/// +/// Types that implement this trait can be used with `Route` handlers. +pub trait FromRequest

    : Sized { + /// The associated error which can be returned. + type Error: Into; + + /// Future that resolves to a Self + type Future: IntoFuture; + + /// Configuration for the extractor + type Config: ExtractorConfig; + + /// Convert request to a Self + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future; +} + +/// Storage for extractor configs +#[derive(Default)] +pub struct ConfigStorage { + pub(crate) storage: Option>, +} + +impl ConfigStorage { + pub fn store(&mut self, config: C) { + if self.storage.is_none() { + self.storage = Some(Rc::new(Extensions::new())); + } + if let Some(ref mut ext) = self.storage { + Rc::get_mut(ext).unwrap().insert(config); + } + } +} + +pub trait ExtractorConfig: Default + Clone + 'static { + /// Set default configuration to config storage + fn store_default(ext: &mut ConfigStorage) { + ext.store(Self::default()) + } +} + +impl ExtractorConfig for () { + fn store_default(_: &mut ConfigStorage) {} +} + #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's path. /// /// ## Example /// -/// ```rust,ignore -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// use actix_web::{http, App, Path, Result}; +/// ```rust +/// use actix_web::{web, http, App, extract::Path}; /// /// /// extract path info from "/{username}/{count}/index.html" url /// /// {username} - deserializes to a String /// /// {count} - - deserializes to a u32 -/// fn index(info: Path<(String, u32)>) -> Result { -/// Ok(format!("Welcome {}! {}", info.0, info.1)) +/// fn index(info: Path<(String, u32)>) -> String { +/// format!("Welcome {}! {}", info.0, info.1) /// } /// /// fn main() { /// let app = App::new().resource( /// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor +/// |r| r.route(web::get().to(index)) // <- register handler with `Path` extractor +/// ); /// } /// ``` /// /// It is possible to extract path information to a specific type that /// implements `Deserialize` trait from *serde*. /// -/// ```rust,ignore -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; +/// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Path, Result}; +/// use actix_web::{web, App, extract::Path, Error}; /// /// #[derive(Deserialize)] /// struct Info { /// username: String, /// } /// -/// /// extract path info using serde -/// fn index(info: Path) -> Result { +/// /// extract `Info` from a path using serde +/// fn index(info: Path) -> Result { /// Ok(format!("Welcome {}!", info.username)) /// } /// /// fn main() { /// let app = App::new().resource( /// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index), -/// ); // <- use `with` extractor +/// |r| r.route(web::get().to(index)) // <- use handler with Path` extractor +/// ); /// } /// ``` pub struct Path { @@ -112,7 +150,7 @@ impl Path { } /// Extract path information from a request - pub fn extract

    (req: &ServiceFromRequest

    ) -> Result, de::value::Error> + pub fn extract(req: &HttpRequest) -> Result, de::value::Error> where T: DeserializeOwned, { @@ -158,13 +196,9 @@ impl fmt::Display for Path { /// /// ## Example /// -/// ```rust,ignore -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; +/// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Query, http}; -/// +/// use actix_web::{web, extract, App}; /// ///#[derive(Debug, Deserialize)] ///pub enum ResponseType { @@ -178,17 +212,17 @@ impl fmt::Display for Path { /// response_type: ResponseType, ///} /// -/// // use `with` extractor for query info -/// // this handler get called only if request's query contains `username` field +/// // Use `Query` extractor for query information. +/// // This handler get called only if request's query contains `username` field /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: Query) -> String { +/// fn index(info: extract::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } /// /// fn main() { /// let app = App::new().resource( /// "/index.html", -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor +/// |r| r.route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` pub struct Query(T); @@ -253,21 +287,21 @@ impl fmt::Display for Query { /// /// ## Example /// -/// ```rust,ignore +/// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Form, Result}; +/// use actix_web::{web, App, extract::Form}; /// /// #[derive(Deserialize)] /// struct FormData { /// username: String, /// } /// -/// /// extract form data using serde -/// /// this handler get called only if content type is *x-www-form-urlencoded* +/// /// Extract form data using serde. +/// /// This handler get called only if content type is *x-www-form-urlencoded* /// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) +/// fn index(form: Form) -> String { +/// format!("Welcome {}!", form.username) /// } /// # fn main() {} /// ``` @@ -333,19 +367,18 @@ impl fmt::Display for Form { /// Form extractor configuration /// -/// ```rust,ignore -/// # extern crate actix_web; +/// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Form, Result}; +/// use actix_web::{web, extract, App, Result}; /// /// #[derive(Deserialize)] /// struct FormData { /// username: String, /// } /// -/// /// extract form data using serde. -/// /// custom configuration is used for this handler, max payload size is 4k -/// fn index(form: Form) -> Result { +/// /// Extract form data using serde. +/// /// Custom configuration is used for this handler, max payload size is 4k +/// fn index(form: extract::Form) -> Result { /// Ok(format!("Welcome {}!", form.username)) /// } /// @@ -353,11 +386,11 @@ impl fmt::Display for Form { /// let app = App::new().resource( /// "/index.html", /// |r| { -/// r.method(http::Method::GET) -/// // register form handler and change form extractor configuration -/// .with_config(index, |cfg| {cfg.0.limit(4096);}) -/// }, -/// ); +/// r.route(web::get() +/// // change `Form` extractor configuration +/// .config(extract::FormConfig::default().limit(4097)) +/// .to(index)) +/// }); /// } /// ``` #[derive(Clone)] @@ -408,10 +441,9 @@ impl Default for FormConfig { /// /// ## Example /// -/// ```rust,ignore -/// # extern crate actix_web; +/// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Json, Result, http}; +/// use actix_web::{web, extract, App}; /// /// #[derive(Deserialize)] /// struct Info { @@ -419,14 +451,14 @@ impl Default for FormConfig { /// } /// /// /// deserialize `Info` from request's body -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) +/// fn index(info: extract::Json) -> String { +/// format!("Welcome {}!", info.username) /// } /// /// fn main() { /// let app = App::new().resource( /// "/index.html", -/// |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor +/// |r| r.route(web::post().to(index))); /// } /// ``` /// @@ -435,8 +467,7 @@ impl Default for FormConfig { /// to serialize into *JSON*. The type `T` must implement the `Serialize` /// trait from *serde*. /// -/// ```rust,ignore -/// # extern crate actix_web; +/// ```rust /// # #[macro_use] extern crate serde_derive; /// # use actix_web::*; /// # @@ -447,7 +478,7 @@ impl Default for FormConfig { /// /// fn index(req: HttpRequest) -> Result> { /// Ok(Json(MyObj { -/// name: req.match_info().query("name")?, +/// name: req.match_info().get("name").unwrap().to_string(), /// })) /// } /// # fn main() {} @@ -536,10 +567,9 @@ where /// Json extractor configuration /// -/// ```rust,ignore -/// # extern crate actix_web; +/// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, http, App, HttpResponse, Json, Result}; +/// use actix_web::{error, extract, web, App, HttpResponse, Json}; /// /// #[derive(Deserialize)] /// struct Info { @@ -547,20 +577,20 @@ where /// } /// /// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: Json) -> Result { -/// Ok(format!("Welcome {}!", info.username)) +/// fn index(info: Json) -> String { +/// format!("Welcome {}!", info.username) /// } /// /// fn main() { /// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::POST) -/// .with_config(index, |cfg| { -/// cfg.0.limit(4096) // <- change json extractor configuration -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }); -/// }) +/// r.route(web::post().config( +/// // change json extractor configuration +/// extract::JsonConfig::default().limit(4096) +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// })) +/// .to(index)) /// }); /// } /// ``` @@ -598,7 +628,7 @@ impl Default for JsonConfig { } } -/// Request payload extractor. +/// Request binary data from a request's payload. /// /// Loads request's payload and construct Bytes instance. /// @@ -607,19 +637,18 @@ impl Default for JsonConfig { /// /// ## Example /// -/// ```rust,ignore -/// extern crate bytes; -/// # extern crate actix_web; -/// use actix_web::{http, App, Result}; +/// ```rust +/// use bytes::Bytes; +/// use actix_web::{web, App}; /// -/// /// extract text data from request -/// fn index(body: bytes::Bytes) -> Result { -/// Ok(format!("Body {:?}!", body)) +/// /// extract binary data from request +/// fn index(body: Bytes) -> String { +/// format!("Body {:?}!", body) /// } /// /// fn main() { /// let app = App::new() -/// .resource("/index.html", |r| r.method(http::Method::GET).with(index)); +/// .resource("/index.html", |r| r.route(web::get().to(index))); /// } /// ``` impl

    FromRequest

    for Bytes @@ -644,7 +673,7 @@ where } } -/// Extract text information from the request's body. +/// Extract text information from a request's body. /// /// Text extractor automatically decode body according to the request's charset. /// @@ -653,21 +682,20 @@ where /// /// ## Example /// -/// ```rust,ignore -/// # extern crate actix_web; -/// use actix_web::{http, App, Result}; +/// ```rust +/// use actix_web::{web, extract, App}; /// /// /// extract text data from request -/// fn index(body: String) -> Result { -/// Ok(format!("Body {}!", body)) +/// fn index(text: String) -> String { +/// format!("Body {}!", text) /// } /// /// fn main() { /// let app = App::new().resource("/index.html", |r| { -/// r.method(http::Method::GET) -/// .with_config(index, |cfg| { // <- register handler with extractor params -/// cfg.0.limit(4096); // <- limit size of the payload -/// }) +/// r.route( +/// web::get() +/// .config(extract::PayloadConfig::new(4096)) // <- limit size of the payload +/// .to(index)) // <- register handler with extractor params /// }); /// } /// ``` @@ -722,22 +750,23 @@ where /// /// ## Example /// -/// ```rust,ignore -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; +/// ```rust +/// # #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, Error, FromRequest, ServiceFromRequest}; /// use actix_web::error::ErrorBadRequest; +/// use rand; /// /// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } +/// struct Thing { +/// name: String +/// } /// -/// impl FromRequest for Thing { +/// impl

    FromRequest

    for Thing { +/// type Error = Error; +/// type Future = Result; /// type Config = (); -/// type Result = Result; /// -/// #[inline] -/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { +/// fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { @@ -747,18 +776,18 @@ where /// } /// } /// -/// /// extract text data from request -/// fn index(supplied_thing: Option) -> Result { +/// /// extract `Thing` from request +/// fn index(supplied_thing: Option) -> String { /// match supplied_thing { /// // Puns not intended -/// Some(thing) => Ok(format!("Got something: {:?}", thing)), -/// None => Ok(format!("No thing!")) +/// Some(thing) => format!("Got something: {:?}", thing), +/// None => format!("No thing!") /// } /// } /// /// fn main() { /// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) +/// r.route(web::post().to(index)) /// }); /// } /// ``` @@ -773,7 +802,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - Box::new(T::from_request(req).then(|r| match r { + Box::new(T::from_request(req).into_future().then(|r| match r { Ok(v) => future::ok(Some(v)), Err(_) => future::ok(None), })) @@ -782,46 +811,46 @@ where /// Optionally extract a field from the request or extract the Error if unsuccessful /// -/// If the FromRequest for T fails, inject Err into handler rather than returning an error response +/// If the `FromRequest` for T fails, inject Err into handler rather than returning an error response /// /// ## Example /// -/// ```rust,ignore -/// # extern crate actix_web; -/// extern crate rand; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; +/// ```rust +/// # #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, Result, Error, FromRequest, ServiceFromRequest}; /// use actix_web::error::ErrorBadRequest; +/// use rand; /// /// #[derive(Debug, Deserialize)] -/// struct Thing { name: String } +/// struct Thing { +/// name: String +/// } /// -/// impl FromRequest for Thing { +/// impl

    FromRequest

    for Thing { +/// type Error = Error; +/// type Future = Result; /// type Config = (); -/// type Result = Result; /// -/// #[inline] -/// fn from_request(req: &Request, _cfg: &Self::Config) -> Self::Result { +/// fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { /// Err(ErrorBadRequest("no luck")) /// } -/// /// } /// } /// -/// /// extract text data from request -/// fn index(supplied_thing: Result) -> Result { +/// /// extract `Thing` from request +/// fn index(supplied_thing: Result) -> String { /// match supplied_thing { -/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)), -/// Err(e) => Ok(format!("Error extracting thing: {}", e)) +/// Ok(thing) => format!("Got thing: {:?}", thing), +/// Err(e) => format!("Error extracting thing: {}", e) /// } /// } /// /// fn main() { /// let app = App::new().resource("/users/:first", |r| { -/// r.method(http::Method::POST).with(index) +/// r.route(web::post().to(index)) /// }); /// } /// ``` @@ -837,7 +866,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - Box::new(T::from_request(req).then(|res| match res { + Box::new(T::from_request(req).into_future().then(|res| match res { Ok(v) => ok(Ok(v)), Err(e) => ok(Err(e)), })) @@ -924,7 +953,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { $fut_type { items: <($(Option<$T>,)+)>::default(), - futs: ($($T::from_request(req),)+), + futs: ($($T::from_request(req).into_future(),)+), } } } @@ -932,7 +961,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { #[doc(hidden)] pub struct $fut_type),+> { items: ($(Option<$T>,)+), - futs: ($($T::Future,)+), + futs: ($(<$T::Future as futures::IntoFuture>::Future,)+), } impl),+> Future for $fut_type diff --git a/src/handler.rs b/src/handler.rs index 313422ed..98a36c56 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -7,55 +7,11 @@ use actix_service::{NewService, Service, Void}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; +use crate::extract::FromRequest; use crate::request::HttpRequest; use crate::responder::Responder; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; -/// Trait implemented by types that can be extracted from request. -/// -/// Types that implement this trait can be used with `Route` handlers. -pub trait FromRequest

    : Sized { - /// The associated error which can be returned. - type Error: Into; - - /// Future that resolves to a Self - type Future: Future; - - /// Configuration for the extractor - type Config: ExtractorConfig; - - /// Convert request to a Self - fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future; -} - -/// Storage for extractor configs -#[derive(Default)] -pub struct ConfigStorage { - pub(crate) storage: Option>, -} - -impl ConfigStorage { - pub fn store(&mut self, config: C) { - if self.storage.is_none() { - self.storage = Some(Rc::new(Extensions::new())); - } - if let Some(ref mut ext) = self.storage { - Rc::get_mut(ext).unwrap().insert(config); - } - } -} - -pub trait ExtractorConfig: Default + Clone + 'static { - /// Set default configuration to config storage - fn store_default(ext: &mut ConfigStorage) { - ext.store(Self::default()) - } -} - -impl ExtractorConfig for () { - fn store_default(_: &mut ConfigStorage) {} -} - /// Handler converter factory pub trait Factory: Clone where @@ -134,14 +90,14 @@ where type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Void; - type Future = HandleServiceResponse; + type Future = HandleServiceResponse<::Future>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - let fut = self.hnd.call(param).respond_to(&req); + let fut = self.hnd.call(param).respond_to(&req).into_future(); HandleServiceResponse { fut, req: Some(req), @@ -368,7 +324,7 @@ impl> Service for ExtractService { fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { let mut req = ServiceFromRequest::new(req, self.config.clone()); ExtractResponse { - fut: T::from_request(&mut req), + fut: T::from_request(&mut req).into_future(), req: Some(req), } } @@ -376,7 +332,7 @@ impl> Service for ExtractService { pub struct ExtractResponse> { req: Option>, - fut: T::Future, + fut: ::Future, } impl> Future for ExtractResponse { diff --git a/src/lib.rs b/src/lib.rs index e876a7ea..f61bf0ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![allow(clippy::type_complexity)] mod app; -pub mod extractor; +pub mod extract; pub mod handler; // mod info; pub mod blocking; @@ -17,23 +17,23 @@ pub mod test; // re-export for convenience pub use actix_http::Response as HttpResponse; -pub use actix_http::{http, Error, HttpMessage, ResponseError}; +pub use actix_http::{error, http, Error, HttpMessage, ResponseError, Result}; pub use crate::app::App; -pub use crate::extractor::{Form, Json, Path, PayloadConfig, Query}; -pub use crate::handler::FromRequest; +pub use crate::extract::{FromRequest, Json}; pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::responder::{Either, Responder}; pub use crate::route::Route; -pub use crate::service::{ServiceRequest, ServiceResponse}; +pub use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; pub use crate::state::State; pub mod web { use actix_http::{http::Method, Error, Response}; use futures::IntoFuture; - use crate::handler::{AsyncFactory, Factory, FromRequest}; + use crate::extract::FromRequest; + use crate::handler::{AsyncFactory, Factory}; use crate::responder::Responder; use crate::Route; @@ -107,9 +107,3 @@ pub mod web { Route::new().to_async(handler) } } - -pub mod dev { - pub use crate::app::AppRouter; - pub use crate::handler::{AsyncFactory, Extract, Factory, Handle}; - // pub use crate::info::ConnectionInfo; -} diff --git a/src/request.rs b/src/request.rs index a7c84b53..48a2dd83 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,7 +8,7 @@ use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; use futures::future::{ok, FutureResult}; -use crate::handler::FromRequest; +use crate::extract::FromRequest; use crate::service::ServiceFromRequest; #[derive(Clone)] diff --git a/src/resource.rs b/src/resource.rs index 98c2dc11..f05e998f 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -9,7 +9,8 @@ use actix_service::{ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::handler::{AsyncFactory, Factory, FromRequest}; +use crate::extract::FromRequest; +use crate::handler::{AsyncFactory, Factory}; use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; diff --git a/src/responder.rs b/src/responder.rs index b3ec7ec7..22588a9c 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,7 +1,7 @@ use actix_http::{dev::ResponseBuilder, http::StatusCode, Error, Response}; use bytes::{Bytes, BytesMut}; use futures::future::{err, ok, Either as EitherFuture, FutureResult}; -use futures::{Future, Poll}; +use futures::{Future, IntoFuture, Poll}; use crate::request::HttpRequest; @@ -13,7 +13,7 @@ pub trait Responder { type Error: Into; /// The future response value. - type Future: Future; + type Future: IntoFuture; /// Convert itself to `AsyncResult` or `Error`. fn respond_to(self, req: &HttpRequest) -> Self::Future; @@ -34,11 +34,14 @@ where T: Responder, { type Error = T::Error; - type Future = EitherFuture>; + type Future = EitherFuture< + ::Future, + FutureResult, + >; fn respond_to(self, req: &HttpRequest) -> Self::Future { match self { - Some(t) => EitherFuture::A(t.respond_to(req)), + Some(t) => EitherFuture::A(t.respond_to(req).into_future()), None => EitherFuture::B(ok(Response::build(StatusCode::NOT_FOUND).finish())), } } @@ -50,11 +53,16 @@ where E: Into, { type Error = Error; - type Future = EitherFuture, FutureResult>; + type Future = EitherFuture< + ResponseFuture<::Future>, + FutureResult, + >; fn respond_to(self, req: &HttpRequest) -> Self::Future { match self { - Ok(val) => EitherFuture::A(ResponseFuture::new(val.respond_to(req))), + Ok(val) => { + EitherFuture::A(ResponseFuture::new(val.respond_to(req).into_future())) + } Err(e) => EitherFuture::B(err(e.into())), } } @@ -147,34 +155,36 @@ impl Responder for BytesMut { /// Combines two different responder types into a single type /// -/// ```rust,ignore -/// # extern crate actix_web; -/// # extern crate futures; -/// # use futures::future::Future; -/// use actix_web::{AsyncResponder, Either, Error, Request, Response}; -/// use futures::future::result; +/// ```rust +/// # use futures::future::{ok, Future}; +/// use actix_web::{Either, Error, HttpResponse}; /// /// type RegisterResult = -/// Either>>; +/// Either>>; /// -/// fn index(req: Request) -> RegisterResult { +/// fn index() -> RegisterResult { /// if is_a_variant() { -/// // <- choose variant A -/// Either::A(Response::BadRequest().body("Bad data")) +/// // <- choose left variant +/// Either::A(HttpResponse::BadRequest().body("Bad data")) /// } else { /// Either::B( -/// // <- variant B -/// result(Ok(Response::Ok() +/// // <- Right variant +/// Box::new(ok(HttpResponse::Ok() /// .content_type("text/html") /// .body("Hello!"))) -/// .responder(), /// ) /// } /// } /// # fn is_a_variant() -> bool { true } /// # fn main() {} /// ``` -pub type Either = either::Either; +#[derive(Debug, PartialEq)] +pub enum Either { + /// First branch of the type + A(A), + /// Second branch of the type + B(B), +} impl Responder for Either where @@ -182,12 +192,15 @@ where B: Responder, { type Error = Error; - type Future = EitherResponder; + type Future = EitherResponder< + ::Future, + ::Future, + >; fn respond_to(self, req: &HttpRequest) -> Self::Future { match self { - either::Either::Left(a) => EitherResponder::A(a.respond_to(req)), - either::Either::Right(b) => EitherResponder::B(b.respond_to(req)), + Either::A(a) => EitherResponder::A(a.respond_to(req).into_future()), + Either::B(b) => EitherResponder::B(b.respond_to(req).into_future()), } } } @@ -234,7 +247,7 @@ where let req = req.clone(); Box::new( self.map_err(|e| e.into()) - .and_then(move |r| ResponseFuture(r.respond_to(&req))), + .and_then(move |r| ResponseFuture(r.respond_to(&req).into_future())), ) } } diff --git a/src/route.rs b/src/route.rs index 16a4fc5b..72abeb32 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,11 +5,9 @@ use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::extract::{ConfigStorage, ExtractorConfig, FromRequest}; use crate::guard::{self, Guard}; -use crate::handler::{ - AsyncFactory, AsyncHandle, ConfigStorage, Extract, ExtractorConfig, Factory, - FromRequest, Handle, -}; +use crate::handler::{AsyncFactory, AsyncHandle, Extract, Factory, Handle}; use crate::responder::Responder; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -219,7 +217,7 @@ impl Route

    { /// /// ```rust /// #[macro_use] extern crate serde_derive; - /// use actix_web::{web, http, App, Path}; + /// use actix_web::{web, http, App, extract::Path}; /// /// #[derive(Deserialize)] /// struct Info { @@ -244,7 +242,7 @@ impl Route

    { /// ```rust /// # use std::collections::HashMap; /// # use serde_derive::Deserialize; - /// use actix_web::{web, http, App, Json, Path, Query}; + /// use actix_web::{web, App, Json, extract::Path, extract::Query}; /// /// #[derive(Deserialize)] /// struct Info { @@ -259,7 +257,7 @@ impl Route

    { /// fn main() { /// let app = App::new().resource( /// "/{username}/index.html", // <- define path parameters - /// |r| r.route(web::method(http::Method::GET).to(index)), + /// |r| r.route(web::get().to(index)), /// ); /// } /// ``` @@ -283,7 +281,7 @@ impl Route

    { /// ```rust /// # use futures::future::ok; /// #[macro_use] extern crate serde_derive; - /// use actix_web::{web, http, App, Error, Path}; + /// use actix_web::{web, App, Error, extract::Path}; /// use futures::Future; /// /// #[derive(Deserialize)] @@ -323,7 +321,7 @@ impl Route

    { /// for specific route. /// /// ```rust - /// use actix_web::{web, extractor, App}; + /// use actix_web::{web, extract, App}; /// /// /// extract text data from request /// fn index(body: String) -> String { @@ -335,7 +333,7 @@ impl Route

    { /// r.route( /// web::get() /// // limit size of the payload - /// .config(extractor::PayloadConfig::new(4096)) + /// .config(extract::PayloadConfig::new(4096)) /// // register handler /// .to(index) /// ) diff --git a/src/state.rs b/src/state.rs index 4a450245..168a8c89 100644 --- a/src/state.rs +++ b/src/state.rs @@ -6,7 +6,7 @@ use actix_http::Extensions; use futures::future::{err, ok, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::handler::FromRequest; +use crate::extract::FromRequest; use crate::service::ServiceFromRequest; /// Application state factory diff --git a/src/test.rs b/src/test.rs index 4899cfe4..b4447f8b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -14,13 +14,10 @@ use crate::service::{ServiceFromRequest, ServiceRequest}; /// Test `Request` builder /// /// ```rust,ignore -/// # extern crate http; -/// # extern crate actix_web; -/// # use http::{header, StatusCode}; /// # use actix_web::*; /// use actix_web::test::TestRequest; /// -/// fn index(req: &HttpRequest) -> HttpResponse { +/// fn index(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { /// HttpResponse::Ok().into() /// } else { From 360082f99ffed06d19388905bf3023b81a6c80aa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 14:45:56 -0800 Subject: [PATCH 0961/1635] update api docs --- src/app.rs | 4 +- src/extract.rs | 168 ++++++++++++++++++++++++++++++++++++++++-------- src/lib.rs | 11 +++- src/request.rs | 27 ++++++-- src/resource.rs | 46 +++++++++++-- src/state.rs | 48 +++++++------- 6 files changed, 237 insertions(+), 67 deletions(-) diff --git a/src/app.rs b/src/app.rs index c9c23d9c..34a663ae 100644 --- a/src/app.rs +++ b/src/app.rs @@ -245,8 +245,8 @@ where } } -/// Structure that follows the builder pattern for building application -/// instances. +/// Application router builder - Structure that follows the builder pattern +/// for building application instances. pub struct AppRouter { chain: C, services: Vec<(ResourceDef, HttpNewService

    )>, diff --git a/src/extract.rs b/src/extract.rs index d6e53327..c0af6016 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -165,17 +165,63 @@ impl From for Path { } } +/// Extract typed information from the request's path. +/// +/// ## Example +/// +/// ```rust +/// use actix_web::{web, http, App, extract::Path}; +/// +/// /// extract path info from "/{username}/{count}/index.html" url +/// /// {username} - deserializes to a String +/// /// {count} - - deserializes to a u32 +/// fn index(info: Path<(String, u32)>) -> String { +/// format!("Welcome {}! {}", info.0, info.1) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/{username}/{count}/index.html", // <- define path parameters +/// |r| r.route(web::get().to(index)) // <- register handler with `Path` extractor +/// ); +/// } +/// ``` +/// +/// It is possible to extract path information to a specific type that +/// implements `Deserialize` trait from *serde*. +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, extract::Path, Error}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// extract `Info` from a path using serde +/// fn index(info: Path) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/{username}/index.html", // <- define path parameters +/// |r| r.route(web::get().to(index)) // <- use handler with Path` extractor +/// ); +/// } +/// ``` impl FromRequest

    for Path where T: DeserializeOwned, { type Error = Error; - type Future = FutureResult; + type Future = Result; type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - Self::extract(req).map_err(ErrorNotFound).into_future() + Self::extract(req).map_err(ErrorNotFound) } } @@ -200,17 +246,17 @@ impl fmt::Display for Path { /// #[macro_use] extern crate serde_derive; /// use actix_web::{web, extract, App}; /// -///#[derive(Debug, Deserialize)] -///pub enum ResponseType { +/// #[derive(Debug, Deserialize)] +/// pub enum ResponseType { /// Token, /// Code -///} +/// } /// -///#[derive(Deserialize)] -///pub struct AuthRequest { +/// #[derive(Deserialize)] +/// pub struct AuthRequest { /// id: u64, /// response_type: ResponseType, -///} +/// } /// /// // Use `Query` extractor for query information. /// // This handler get called only if request's query contains `username` field @@ -248,19 +294,52 @@ impl Query { } } +/// Extract typed information from from the request's query. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, extract, App}; +/// +/// #[derive(Debug, Deserialize)] +/// pub enum ResponseType { +/// Token, +/// Code +/// } +/// +/// #[derive(Deserialize)] +/// pub struct AuthRequest { +/// id: u64, +/// response_type: ResponseType, +/// } +/// +/// // Use `Query` extractor for query information. +/// // This handler get called only if request's query contains `username` field +/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` +/// fn index(info: extract::Query) -> String { +/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", +/// |r| r.route(web::get().to(index))); // <- use `Query` extractor +/// } +/// ``` impl FromRequest

    for Query where T: de::DeserializeOwned, { type Error = Error; - type Future = FutureResult; + type Future = Result; type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { serde_urlencoded::from_str::(req.query_string()) - .map(|val| ok(Query(val))) - .unwrap_or_else(|e| err(e.into())) + .map(|val| Ok(Query(val))) + .unwrap_or_else(|e| Err(e.into())) } } @@ -282,7 +361,7 @@ impl fmt::Display for Query { /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. /// -/// [**FormConfig**](dev/struct.FormConfig.html) allows to configure extraction +/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction /// process. /// /// ## Example @@ -436,7 +515,7 @@ impl Default for FormConfig { /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. /// -/// [**JsonConfig**](dev/struct.JsonConfig.html) allows to configure extraction +/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction /// process. /// /// ## Example @@ -526,20 +605,51 @@ where impl Responder for Json { type Error = Error; - type Future = FutureResult; + type Future = Result; fn respond_to(self, _: &HttpRequest) -> Self::Future { let body = match serde_json::to_string(&self.0) { Ok(body) => body, - Err(e) => return err(e.into()), + Err(e) => return Err(e.into()), }; - ok(Response::build(StatusCode::OK) + Ok(Response::build(StatusCode::OK) .content_type("application/json") .body(body)) } } +/// Json extractor. Allow to extract typed information from request's +/// payload. +/// +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. +/// +/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// process. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, extract, App}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body +/// fn index(info: extract::Json) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", +/// |r| r.route(web::post().to(index))); +/// } +/// ``` impl FromRequest

    for Json where T: DeserializeOwned + 'static, @@ -632,7 +742,7 @@ impl Default for JsonConfig { /// /// Loads request's payload and construct Bytes instance. /// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure +/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure /// extraction process. /// /// ## Example @@ -677,7 +787,7 @@ where /// /// Text extractor automatically decode body according to the request's charset. /// -/// [**PayloadConfig**](dev/struct.PayloadConfig.html) allows to configure +/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure /// extraction process. /// /// ## Example @@ -931,6 +1041,17 @@ impl Default for PayloadConfig { } } +#[doc(hidden)] +impl

    FromRequest

    for () { + type Error = Error; + type Future = FutureResult<(), Error>; + type Config = (); + + fn from_request(_req: &mut ServiceFromRequest

    ) -> Self::Future { + ok(()) + } +} + macro_rules! tuple_config ({ $($T:ident),+} => { impl<$($T,)+> ExtractorConfig for ($($T,)+) where $($T: ExtractorConfig + Clone,)+ @@ -944,6 +1065,7 @@ macro_rules! tuple_config ({ $($T:ident),+} => { macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { /// FromRequest implementation for tuple + #[doc(hidden)] impl + 'static),+> FromRequest

    for ($($T,)+) { type Error = Error; @@ -995,16 +1117,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { } }); -impl

    FromRequest

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

    ) -> Self::Future { - ok(()) - } -} - #[rustfmt::skip] mod m { use super::*; diff --git a/src/lib.rs b/src/lib.rs index f61bf0ac..37fd7591 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ mod app; pub mod extract; -pub mod handler; +mod handler; // mod info; pub mod blocking; pub mod guard; @@ -19,7 +19,7 @@ pub mod test; pub use actix_http::Response as HttpResponse; pub use actix_http::{error, http, Error, HttpMessage, ResponseError, Result}; -pub use crate::app::App; +pub use crate::app::{App, AppRouter}; pub use crate::extract::{FromRequest, Json}; pub use crate::request::HttpRequest; pub use crate::resource::Resource; @@ -37,30 +37,37 @@ pub mod web { use crate::responder::Responder; use crate::Route; + /// Create **route** without configuration. pub fn route() -> Route

    { Route::new() } + /// Create **route** with `GET` method guard. pub fn get() -> Route

    { Route::get() } + /// Create **route** with `POST` method guard. pub fn post() -> Route

    { Route::post() } + /// Create **route** with `PUT` method guard. pub fn put() -> Route

    { Route::put() } + /// Create **route** with `DELETE` method guard. pub fn delete() -> Route

    { Route::delete() } + /// Create **route** with `HEAD` method guard. pub fn head() -> Route

    { Route::new().method(Method::HEAD) } + /// Create **route** and add method guard. pub fn method(method: Method) -> Route

    { Route::new().method(method) } diff --git a/src/request.rs b/src/request.rs index 48a2dd83..d90627f5 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,12 +6,12 @@ use std::rc::Rc; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; -use futures::future::{ok, FutureResult}; use crate::extract::FromRequest; use crate::service::ServiceFromRequest; #[derive(Clone)] +/// An HTTP Request pub struct HttpRequest { pub(crate) head: Message, pub(crate) path: Path, @@ -20,7 +20,7 @@ pub struct HttpRequest { impl HttpRequest { #[inline] - pub fn new( + pub(crate) fn new( head: Message, path: Path, extensions: Rc, @@ -140,14 +140,33 @@ impl HttpMessage for HttpRequest { } } +/// It is possible to get `HttpRequest` as an extractor handler parameter +/// +/// ## Example +/// +/// ```rust +/// # #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, HttpRequest}; +/// +/// /// extract `Thing` from request +/// fn index(req: HttpRequest) -> String { +/// format!("Got thing: {:?}", req) +/// } +/// +/// fn main() { +/// let app = App::new().resource("/users/:first", |r| { +/// r.route(web::get().to(index)) +/// }); +/// } +/// ``` impl

    FromRequest

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

    ) -> Self::Future { - ok(req.clone()) + Ok(req.clone()) } } diff --git a/src/resource.rs b/src/resource.rs index f05e998f..342d801d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -18,10 +18,24 @@ use crate::service::{ServiceRequest, ServiceResponse}; type HttpService

    = BoxedService, ServiceResponse, ()>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; -/// Resource route definition +/// *Resource* is an entry in route table which corresponds to requested URL. /// -/// Route uses builder-like pattern for configuration. -/// If handler is not explicitly set, default *404 Not Found* handler is used. +/// Resource in turn has at least one route. +/// Route consists of an handlers objects and list of guards +/// (objects that implement `Guard` trait). +/// Resources and rouets uses builder-like pattern for configuration. +/// During request handling, resource object iterate through all routes +/// and check guards for specific route, if request matches all +/// guards, route considered matched and route handler get called. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new() +/// .resource( +/// "/", |r| r.route(web::get().to(|| HttpResponse::Ok()))); +/// } pub struct Resource> { routes: Vec>, endpoint: T, @@ -58,8 +72,6 @@ where >, { /// Register a new route. - /// *Route* is used for route configuration, i.e. adding guards, - /// setting up handler. /// /// ```rust /// use actix_web::{web, guard, App, HttpResponse}; @@ -74,12 +86,31 @@ where /// }); /// } /// ``` + /// + /// Multiple routes could be added to a resource. + /// + /// ```rust + /// use actix_web::{web, guard, App, HttpResponse}; + /// + /// fn main() { + /// let app = App::new() + /// .resource("/container/", |r| { + /// r.route(web::get().to(get_handler)) + /// .route(web::post().to(post_handler)) + /// .route(web::delete().to(delete_handler)) + /// }); + /// } + /// # fn get_handler() {} + /// # fn post_handler() {} + /// # fn delete_handler() {} + /// ``` pub fn route(mut self, route: Route

    ) -> Self { self.routes.push(route.finish()); self } - /// Register a new route and add handler. + /// Register a new route and add handler. This route get called for all + /// requests. /// /// ```rust /// use actix_web::*; @@ -148,7 +179,8 @@ where /// Register a resource middleware /// /// This is similar to `App's` middlewares, but - /// middlewares get invoked on resource level. + /// middleware is not allowed to change response type (i.e modify response's body). + /// Middleware get invoked on resource level. pub fn middleware( self, mw: F, diff --git a/src/state.rs b/src/state.rs index 168a8c89..d4e4c894 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,7 +3,6 @@ use std::rc::Rc; use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; -use futures::future::{err, ok, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; use crate::extract::FromRequest; @@ -19,60 +18,61 @@ pub(crate) trait StateFactoryResult { } /// Application state -pub struct State(Rc); +pub struct State(Rc); -impl State { - pub fn new(state: S) -> State { +impl State { + pub(crate) fn new(state: T) -> State { State(Rc::new(state)) } - pub fn get_ref(&self) -> &S { + /// Get referecnce to inner state type. + pub fn get_ref(&self) -> &T { self.0.as_ref() } } -impl Deref for State { - type Target = S; +impl Deref for State { + type Target = T; - fn deref(&self) -> &S { + fn deref(&self) -> &T { self.0.as_ref() } } -impl Clone for State { - fn clone(&self) -> State { +impl Clone for State { + fn clone(&self) -> State { State(self.0.clone()) } } -impl FromRequest

    for State { +impl FromRequest

    for State { type Error = Error; - type Future = FutureResult; + type Future = Result; type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - if let Some(st) = req.app_extensions().get::>() { - ok(st.clone()) + if let Some(st) = req.app_extensions().get::>() { + Ok(st.clone()) } else { - err(ErrorInternalServerError( - "State is not configured, use App::state()", + Err(ErrorInternalServerError( + "State is not configured, to configure use App::state()", )) } } } -impl StateFactory for State { +impl StateFactory for State { fn construct(&self) -> Box { Box::new(StateFut { st: self.clone() }) } } -struct StateFut { - st: State, +struct StateFut { + st: State, } -impl StateFactoryResult for StateFut { +impl StateFactoryResult for StateFut { fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> { extensions.insert(self.st.clone()); Ok(Async::Ready(())) @@ -92,17 +92,17 @@ where } } -struct StateFactoryFut +struct StateFactoryFut where - F: Future, + F: Future, F::Error: std::fmt::Debug, { fut: F, } -impl StateFactoryResult for StateFactoryFut +impl StateFactoryResult for StateFactoryFut where - F: Future, + F: Future, F::Error: std::fmt::Debug, { fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> { From 8502c32a3c576463e2b24d47244d543210c86d74 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 15:32:47 -0800 Subject: [PATCH 0962/1635] re-enable extractor tests --- src/extract.rs | 411 +++++++++++++++++++++++-------------------------- src/service.rs | 5 + src/test.rs | 6 + 3 files changed, 206 insertions(+), 216 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index c0af6016..3b5c7e74 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1044,11 +1044,11 @@ impl Default for PayloadConfig { #[doc(hidden)] impl

    FromRequest

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

    ) -> Self::Future { - ok(()) + Ok(()) } } @@ -1147,6 +1147,7 @@ tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, #[cfg(test)] mod tests { use actix_http::http::header; + use actix_router::ResourceDef; use bytes::Bytes; use serde_derive::Deserialize; @@ -1194,218 +1195,196 @@ mod tests { let s = rt.block_on(Form::::from_request(&mut req)).unwrap(); assert_eq!(s.hello, "world"); } + + #[test] + fn test_option() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .config(FormConfig::default().limit(4096)) + .to_from(); + + let r = rt + .block_on(Option::>::from_request(&mut req)) + .unwrap(); + assert_eq!(r, None); + + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"hello=world")) + .to_from(); + + let r = rt + .block_on(Option::>::from_request(&mut req)) + .unwrap(); + assert_eq!( + r, + Some(Form(Info { + hello: "world".into() + })) + ); + + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"bye=world")) + .to_from(); + + let r = rt + .block_on(Option::>::from_request(&mut req)) + .unwrap(); + assert_eq!(r, None); + } + + #[test] + fn test_result() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .to_from(); + + let r = rt + .block_on(Result::, Error>::from_request(&mut req)) + .unwrap() + .unwrap(); + assert_eq!( + r, + Form(Info { + hello: "world".into() + }) + ); + + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"bye=world")) + .to_from(); + + let r = rt + .block_on(Result::, Error>::from_request(&mut req)) + .unwrap(); + assert!(r.is_err()); + } + + #[test] + fn test_payload_config() { + let req = TestRequest::default().to_from(); + let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); + assert!(cfg.check_mimetype(&req).is_err()); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .to_from(); + assert!(cfg.check_mimetype(&req).is_err()); + + let req = + TestRequest::with_header(header::CONTENT_TYPE, "application/json").to_from(); + assert!(cfg.check_mimetype(&req).is_ok()); + } + + #[derive(Deserialize)] + struct MyStruct { + key: String, + value: String, + } + + #[derive(Deserialize)] + struct Id { + id: String, + } + + #[derive(Deserialize)] + struct Test2 { + key: String, + value: u32, + } + + #[test] + fn test_request_extract() { + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from(); + + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); + + let s = Path::::from_request(&mut req).unwrap(); + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); + + let s = Path::<(String, String)>::from_request(&mut req).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); + + let s = Query::::from_request(&mut req).unwrap(); + assert_eq!(s.id, "test"); + + let mut req = TestRequest::with_uri("/name/32/").to_from(); + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); + + let s = Path::::from_request(&mut req).unwrap(); + assert_eq!(s.as_ref().key, "name"); + assert_eq!(s.value, 32); + + let s = Path::<(String, u8)>::from_request(&mut req).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); + + let res = Path::>::from_request(&mut req).unwrap(); + assert_eq!(res[0], "name".to_owned()); + assert_eq!(res[1], "32".to_owned()); + } + + #[test] + fn test_extract_path_single() { + let resource = ResourceDef::new("/{value}/"); + + let mut req = TestRequest::with_uri("/32/").to_from(); + resource.match_path(req.match_info_mut()); + + assert_eq!(*Path::::from_request(&mut req).unwrap(), 32); + } + + #[test] + fn test_tuple_extract() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let resource = ResourceDef::new("/{key}/{value}/"); + + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from(); + resource.match_path(req.match_info_mut()); + + let res = rt + .block_on(<(Path<(String, String)>,)>::from_request(&mut req)) + .unwrap(); + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); + + let res = rt + .block_on( + <(Path<(String, String)>, Path<(String, String)>)>::from_request( + &mut req, + ), + ) + .unwrap(); + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); + assert_eq!((res.1).0, "name"); + assert_eq!((res.1).1, "user1"); + + let () = <()>::from_request(&mut req).unwrap(); + } } - -// #[test] -// fn test_option() { -// let req = TestRequest::with_header( -// header::CONTENT_TYPE, -// "application/x-www-form-urlencoded", -// ) -// .finish(); - -// let mut cfg = FormConfig::default(); -// cfg.limit(4096); - -// match Option::>::from_request(&req, &cfg) -// .poll() -// .unwrap() -// { -// Async::Ready(r) => assert_eq!(r, None), -// _ => unreachable!(), -// } - -// let req = TestRequest::with_header( -// header::CONTENT_TYPE, -// "application/x-www-form-urlencoded", -// ) -// .header(header::CONTENT_LENGTH, "9") -// .set_payload(Bytes::from_static(b"hello=world")) -// .finish(); - -// match Option::>::from_request(&req, &cfg) -// .poll() -// .unwrap() -// { -// Async::Ready(r) => assert_eq!( -// r, -// Some(Form(Info { -// hello: "world".into() -// })) -// ), -// _ => unreachable!(), -// } - -// let req = TestRequest::with_header( -// header::CONTENT_TYPE, -// "application/x-www-form-urlencoded", -// ) -// .header(header::CONTENT_LENGTH, "9") -// .set_payload(Bytes::from_static(b"bye=world")) -// .finish(); - -// match Option::>::from_request(&req, &cfg) -// .poll() -// .unwrap() -// { -// Async::Ready(r) => assert_eq!(r, None), -// _ => unreachable!(), -// } -// } - -// #[test] -// fn test_result() { -// let req = TestRequest::with_header( -// header::CONTENT_TYPE, -// "application/x-www-form-urlencoded", -// ) -// .header(header::CONTENT_LENGTH, "11") -// .set_payload(Bytes::from_static(b"hello=world")) -// .finish(); - -// match Result::, Error>::from_request(&req, &FormConfig::default()) -// .poll() -// .unwrap() -// { -// Async::Ready(Ok(r)) => assert_eq!( -// r, -// Form(Info { -// hello: "world".into() -// }) -// ), -// _ => unreachable!(), -// } - -// let req = TestRequest::with_header( -// header::CONTENT_TYPE, -// "application/x-www-form-urlencoded", -// ) -// .header(header::CONTENT_LENGTH, "9") -// .set_payload(Bytes::from_static(b"bye=world")) -// .finish(); - -// match Result::, Error>::from_request(&req, &FormConfig::default()) -// .poll() -// .unwrap() -// { -// Async::Ready(r) => assert!(r.is_err()), -// _ => unreachable!(), -// } -// } - -// #[test] -// fn test_payload_config() { -// let req = TestRequest::default().finish(); -// let mut cfg = PayloadConfig::default(); -// cfg.mimetype(mime::APPLICATION_JSON); -// assert!(cfg.check_mimetype(&req).is_err()); - -// let req = TestRequest::with_header( -// header::CONTENT_TYPE, -// "application/x-www-form-urlencoded", -// ) -// .finish(); -// assert!(cfg.check_mimetype(&req).is_err()); - -// let req = -// TestRequest::with_header(header::CONTENT_TYPE, "application/json").finish(); -// assert!(cfg.check_mimetype(&req).is_ok()); -// } - -// #[derive(Deserialize)] -// struct MyStruct { -// key: String, -// value: String, -// } - -// #[derive(Deserialize)] -// struct Id { -// id: String, -// } - -// #[derive(Deserialize)] -// struct Test2 { -// key: String, -// value: u32, -// } - -// #[test] -// fn test_request_extract() { -// let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - -// let mut router = Router::<()>::default(); -// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); -// let info = router.recognize(&req, &(), 0); -// let req = req.with_route_info(info); - -// let s = Path::::from_request(&req, &()).unwrap(); -// assert_eq!(s.key, "name"); -// assert_eq!(s.value, "user1"); - -// let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); -// assert_eq!(s.0, "name"); -// assert_eq!(s.1, "user1"); - -// let s = Query::::from_request(&req, &()).unwrap(); -// assert_eq!(s.id, "test"); - -// let mut router = Router::<()>::default(); -// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); -// let req = TestRequest::with_uri("/name/32/").finish(); -// let info = router.recognize(&req, &(), 0); -// let req = req.with_route_info(info); - -// let s = Path::::from_request(&req, &()).unwrap(); -// assert_eq!(s.as_ref().key, "name"); -// assert_eq!(s.value, 32); - -// let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); -// assert_eq!(s.0, "name"); -// assert_eq!(s.1, 32); - -// let res = Path::>::extract(&req).unwrap(); -// assert_eq!(res[0], "name".to_owned()); -// assert_eq!(res[1], "32".to_owned()); -// } - -// #[test] -// fn test_extract_path_single() { -// let mut router = Router::<()>::default(); -// router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - -// let req = TestRequest::with_uri("/32/").finish(); -// let info = router.recognize(&req, &(), 0); -// let req = req.with_route_info(info); -// assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); -// } - -// #[test] -// fn test_tuple_extract() { -// let mut router = Router::<()>::default(); -// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - -// let req = TestRequest::with_uri("/name/user1/?id=test").finish(); -// let info = router.recognize(&req, &(), 0); -// let req = req.with_route_info(info); - -// let res = match <(Path<(String, String)>,)>::extract(&req).poll() { -// Ok(Async::Ready(res)) => res, -// _ => panic!("error"), -// }; -// assert_eq!((res.0).0, "name"); -// assert_eq!((res.0).1, "user1"); - -// let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req) -// .poll() -// { -// Ok(Async::Ready(res)) => res, -// _ => panic!("error"), -// }; -// assert_eq!((res.0).0, "name"); -// assert_eq!((res.0).1, "user1"); -// assert_eq!((res.1).0, "name"); -// assert_eq!((res.1).1, "user1"); - -// let () = <()>::extract(&req); -// } -// } diff --git a/src/service.rs b/src/service.rs index 5602a613..a515300a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -185,6 +185,11 @@ impl

    ServiceFromRequest

    { self.req } + #[inline] + pub fn match_info_mut(&mut self) -> &mut Path { + &mut self.req.path + } + /// Create service response for error #[inline] pub fn error_response>(self, err: E) -> ServiceResponse { diff --git a/src/test.rs b/src/test.rs index b4447f8b..46fd45d5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -118,6 +118,12 @@ impl TestRequest { self } + /// Set request config + pub fn config(mut self, data: T) -> Self { + self.extensions.insert(data); + self + } + /// Complete request creation and generate `ServiceRequest` instance pub fn finish(mut self) -> ServiceRequest { let req = self.req.finish(); From 34171fa7f570a8188491445fec86a2d427e48bb0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 21:02:01 -0800 Subject: [PATCH 0963/1635] add scopes --- Cargo.toml | 1 + src/app.rs | 120 ++++++- src/guard.rs | 4 +- src/lib.rs | 2 + src/scope.rs | 872 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/test.rs | 9 +- 6 files changed, 1004 insertions(+), 4 deletions(-) create mode 100644 src/scope.rs diff --git a/Cargo.toml b/Cargo.toml index 03b1794f..370089e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ actix-utils = "0.3.0" actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } +#actix-router = { path = "../actix-net/router" } bytes = "0.4" derive_more = "0.14" diff --git a/src/app.rs b/src/app.rs index 34a663ae..27658011 100644 --- a/src/app.rs +++ b/src/app.rs @@ -14,6 +14,7 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; use crate::resource::Resource; +use crate::scope::Scope; use crate::service::{ServiceRequest, ServiceResponse}; use crate::state::{State, StateFactory, StateFactoryResult}; @@ -112,6 +113,52 @@ where self } + /// Configure scope for common root path. + /// + /// Scopes collect multiple paths under a common path prefix. + /// Scope path can contain variable path segments as resources. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{App, HttpRequest, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().scope("/{project_id}", |scope| { + /// scope + /// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) + /// .resource("/path2", |r| r.to(|| HttpResponse::Ok())) + /// .resource("/path3", |r| r.to(|| HttpResponse::MethodNotAllowed())) + /// }); + /// } + /// ``` + /// + /// In the above example, three routes get added: + /// * /{project_id}/path1 + /// * /{project_id}/path2 + /// * /{project_id}/path3 + /// + pub fn scope(self, path: &str, f: F) -> AppRouter> + where + F: FnOnce(Scope

    ) -> Scope

    , + { + let scope = f(Scope::new(path)); + let rdef = scope.rdef().clone(); + let default = scope.get_default(); + + let fref = Rc::new(RefCell::new(None)); + AppRouter { + chain: self.chain, + services: vec![(rdef, boxed::new_service(scope.into_new_service()))], + default: None, + defaults: vec![default], + endpoint: AppEntry::new(fref.clone()), + factory_ref: fref, + extensions: self.extensions, + state: self.state, + _t: PhantomData, + } + } + /// Configure resource for a specific path. /// /// Resources may have variable path segments. For example, a @@ -243,6 +290,18 @@ where _t: PhantomData, } } + + /// 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. + /// + /// By default host name is set to a "localhost" value. + pub fn hostname(self, _val: &str) -> Self { + // self.host = val.to_owned(); + self + } } /// Application router builder - Structure that follows the builder pattern @@ -270,6 +329,42 @@ where InitError = (), >, { + /// Configure scope for common root path. + /// + /// Scopes collect multiple paths under a common path prefix. + /// Scope path can contain variable path segments as resources. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{App, HttpRequest, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().scope("/{project_id}", |scope| { + /// scope + /// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) + /// .resource("/path2", |r| r.to(|| HttpResponse::Ok())) + /// .resource("/path3", |r| r.to(|| HttpResponse::MethodNotAllowed())) + /// }); + /// } + /// ``` + /// + /// In the above example, three routes get added: + /// * /{project_id}/path1 + /// * /{project_id}/path2 + /// * /{project_id}/path3 + /// + pub fn scope(mut self, path: &str, f: F) -> Self + where + F: FnOnce(Scope

    ) -> Scope

    , + { + let scope = f(Scope::new(path)); + let rdef = scope.rdef().clone(); + self.defaults.push(scope.get_default()); + self.services + .push((rdef, boxed::new_service(scope.into_new_service()))); + self + } + /// Configure resource for a specific path. /// /// Resources may have variable path segments. For example, a @@ -466,6 +561,7 @@ where // set factory *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { + default: self.default.clone(), services: Rc::new(self.services), }); @@ -480,6 +576,7 @@ where pub struct AppRoutingFactory

    { services: Rc)>>, + default: Option>>, } impl NewService for AppRoutingFactory

    { @@ -491,6 +588,12 @@ impl NewService for AppRoutingFactory

    { type Future = AppRoutingFactoryResponse

    ; fn new_service(&self, _: &()) -> Self::Future { + let default_fut = if let Some(ref default) = self.default { + Some(default.new_service(&())) + } else { + None + }; + AppRoutingFactoryResponse { fut: self .services @@ -502,6 +605,8 @@ impl NewService for AppRoutingFactory

    { ) }) .collect(), + default: None, + default_fut, } } } @@ -512,6 +617,8 @@ type HttpServiceFut

    = Box, Error = ()>>; #[doc(hidden)] pub struct AppRoutingFactoryResponse

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

    { @@ -526,6 +633,13 @@ impl

    Future for AppRoutingFactoryResponse

    { fn poll(&mut self) -> Poll { let mut done = true; + if let Some(ref mut fut) = self.default_fut { + match fut.poll()? { + Async::Ready(default) => self.default = Some(default), + Async::NotReady => done = false, + } + } + // poll http services for item in &mut self.fut { let res = match item { @@ -560,8 +674,9 @@ impl

    Future for AppRoutingFactoryResponse

    { router }); Ok(Async::Ready(AppRouting { - router: router.finish(), ready: None, + router: router.finish(), + default: self.default.take(), })) } else { Ok(Async::NotReady) @@ -572,6 +687,7 @@ impl

    Future for AppRoutingFactoryResponse

    { pub struct AppRouting

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

    , ResourceInfo)>, + default: Option>, } impl

    Service for AppRouting

    { @@ -591,6 +707,8 @@ impl

    Service for AppRouting

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

    ) -> Self::Future { if let Some((srv, _info)) = self.router.recognize_mut(req.match_info_mut()) { Either::A(srv.call(req)) + } else if let Some(ref mut default) = self.default { + Either::A(default.call(req)) } else { let req = req.into_request(); Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) diff --git a/src/guard.rs b/src/guard.rs index 10a56921..c8948b3b 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -312,7 +312,9 @@ mod tests { #[test] fn test_preds() { - let r = TestRequest::default().method(Method::TRACE).to_request(); + let r = TestRequest::default() + .method(Method::TRACE) + .to_http_request(); assert!(Not(Get()).check(&r,)); assert!(!Not(Trace()).check(&r,)); diff --git a/src/lib.rs b/src/lib.rs index 37fd7591..18a6de06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod request; mod resource; mod responder; mod route; +mod scope; mod service; mod state; pub mod test; @@ -25,6 +26,7 @@ pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::responder::{Either, Responder}; pub use crate::route::Route; +pub use crate::scope::Scope; pub use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; pub use crate::state::State; diff --git a/src/scope.rs b/src/scope.rs new file mode 100644 index 00000000..5327f953 --- /dev/null +++ b/src/scope.rs @@ -0,0 +1,872 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use actix_http::Response; +use actix_router::{ResourceDef, ResourceInfo, Router}; +use actix_service::boxed::{self, BoxedNewService, BoxedService}; +use actix_service::{ + ApplyNewService, IntoNewService, IntoNewTransform, NewService, NewTransform, Service, +}; +use futures::future::{ok, Either, Future, FutureResult}; +use futures::{Async, Poll}; + +use crate::guard::Guard; +use crate::resource::Resource; +use crate::route::Route; +use crate::service::{ServiceRequest, ServiceResponse}; + +type HttpService

    = BoxedService, ServiceResponse, ()>; +type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; +type BoxedResponse = Box>; + +/// Resources scope +/// +/// Scope is a set of resources with common root path. +/// Scopes collect multiple paths under a common path prefix. +/// Scope path can contain variable path segments as resources. +/// Scope prefix is always complete path segment, i.e `/app` would +/// be converted to a `/app/` and it would not match `/app` path. +/// +/// You can get variable path segments from `HttpRequest::match_info()`. +/// `Path` extractor also is able to extract scope level variable segments. +/// +/// ```rust +/// use actix_web::{App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().scope("/{project_id}/", |scope| { +/// scope +/// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) +/// .resource("/path2", |r| r.to(|| HttpResponse::Ok())) +/// .resource("/path3", |r| r.to(|| HttpResponse::MethodNotAllowed())) +/// }); +/// } +/// ``` +/// +/// In the above example three routes get registered: +/// * /{project_id}/path1 - reponds to all http method +/// * /{project_id}/path2 - `GET` requests +/// * /{project_id}/path3 - `HEAD` requests +/// +pub struct Scope> { + endpoint: T, + rdef: ResourceDef, + services: Vec<(ResourceDef, HttpNewService

    )>, + guards: Rc>>, + default: Rc>>>>, + defaults: Vec>>>>>, + factory_ref: Rc>>>, +} + +impl Scope

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

    { + let fref = Rc::new(RefCell::new(None)); + let rdef = ResourceDef::prefix(&insert_slash(path)); + Scope { + endpoint: ScopeEndpoint::new(fref.clone()), + rdef: rdef.clone(), + guards: Rc::new(Vec::new()), + services: Vec::new(), + default: Rc::new(RefCell::new(None)), + defaults: Vec::new(), + factory_ref: fref, + } + } +} + +impl Scope +where + T: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, +{ + #[inline] + pub(crate) fn rdef(&self) -> &ResourceDef { + &self.rdef + } + + /// Add guard to a scope. + /// + /// ```rust + /// use actix_web::{web, guard, App, HttpRequest, HttpResponse, extract::Path}; + /// + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new().scope("/app", |scope| { + /// scope + /// .guard(guard::Header("content-type", "text/plain")) + /// .route("/test1",web::get().to(index)) + /// .route("/test2", web::post().to(|r: HttpRequest| { + /// HttpResponse::MethodNotAllowed() + /// })) + /// }); + /// } + /// ``` + pub fn guard(mut self, guard: G) -> Self { + Rc::get_mut(&mut self.guards).unwrap().push(Box::new(guard)); + self + } + + /// Create nested scope. + /// + /// ```rust + /// use actix_web::{App, HttpRequest}; + /// + /// struct AppState; + /// + /// fn index(req: HttpRequest) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new().scope("/app", |scope| { + /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.to(index))) + /// }); + /// } + /// ``` + pub fn nested(mut self, path: &str, f: F) -> Self + where + F: FnOnce(Scope

    ) -> Scope

    , + { + let scope = f(Scope::new(path)); + let rdef = scope.rdef().clone(); + self.defaults.push(scope.get_default()); + self.services + .push((rdef, boxed::new_service(scope.into_new_service()))); + + self + } + + /// Configure route for a specific path. + /// + /// This is a simplified version of the `Scope::resource()` method. + /// This method can not be could multiple times, in that case + /// multiple resources with one route would be registered for same resource path. + /// + /// ```rust + /// use actix_web::{web, App, HttpResponse, extract::Path}; + /// + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new().scope("/app", |scope| { + /// scope.route("/test1", web::get().to(index)) + /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())) + /// }); + /// } + /// ``` + pub fn route(self, path: &str, route: Route

    ) -> Self { + self.resource(path, move |r| r.route(route)) + } + + /// configure resource for a specific path. + /// + /// This method is similar to an `App::resource()` method. + /// Resources may have variable path segments. Resource path uses scope + /// path as a path prefix. + /// + /// ```rust + /// use actix_web::*; + /// + /// fn main() { + /// let app = App::new().scope("/api", |scope| { + /// scope.resource("/users/{userid}/{friend}", |r| { + /// r.route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// .route(web::route() + /// .guard(guard::Any(guard::Get()).or(guard::Put())) + /// .guard(guard::Header("Content-Type", "text/plain")) + /// .to(|| HttpResponse::Ok())) + /// }) + /// }); + /// } + /// ``` + pub fn resource(mut self, path: &str, f: F) -> Self + where + F: FnOnce(Resource

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

    , + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, + { + // add resource + let rdef = ResourceDef::new(&insert_slash(path)); + let resource = f(Resource::new()); + self.defaults.push(resource.get_default()); + self.services + .push((rdef, boxed::new_service(resource.into_new_service()))); + self + } + + /// Default resource to be used if no matching route could be found. + pub fn default_resource(mut self, f: F) -> Self + where + F: FnOnce(Resource

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

    , + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, + { + // create and configure default resource + self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( + f(Resource::new()).into_new_service().map_init_err(|_| ()), + ))))); + + self + } + + /// Register a scope middleware + /// + /// This is similar to `App's` middlewares, but + /// middleware is not allowed to change response type (i.e modify response's body). + /// Middleware get invoked on scope level. + pub fn middleware( + self, + mw: F, + ) -> Scope< + P, + impl NewService< + Request = ServiceRequest

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

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, + F: IntoNewTransform, + { + let endpoint = ApplyNewService::new(mw, self.endpoint); + Scope { + endpoint, + rdef: self.rdef, + guards: self.guards, + services: self.services, + default: self.default, + defaults: self.defaults, + factory_ref: self.factory_ref, + } + } + + pub(crate) fn get_default(&self) -> Rc>>>> { + self.default.clone() + } +} + +fn insert_slash(path: &str) -> String { + let mut path = path.to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/'); + }; + path +} + +impl IntoNewService for Scope +where + T: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, +{ + fn into_new_service(self) -> T { + // update resource default service + if let Some(ref d) = *self.default.as_ref().borrow() { + for default in &self.defaults { + if default.borrow_mut().is_none() { + *default.borrow_mut() = Some(d.clone()); + } + } + } + + *self.factory_ref.borrow_mut() = Some(ScopeFactory { + default: self.default.clone(), + services: Rc::new(self.services), + }); + + self.endpoint + } +} + +pub struct ScopeFactory

    { + services: Rc)>>, + default: Rc>>>>, +} + +impl NewService for ScopeFactory

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = ScopeService

    ; + type Future = ScopeFactoryResponse

    ; + + fn new_service(&self, _: &()) -> Self::Future { + let default_fut = if let Some(ref default) = *self.default.borrow() { + Some(default.new_service(&())) + } else { + None + }; + + ScopeFactoryResponse { + fut: self + .services + .iter() + .map(|(path, service)| { + CreateScopeServiceItem::Future( + Some(path.clone()), + service.new_service(&()), + ) + }) + .collect(), + default: None, + default_fut, + } + } +} + +/// Create app service +#[doc(hidden)] +pub struct ScopeFactoryResponse

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

    = Box, Error = ()>>; + +enum CreateScopeServiceItem

    { + Future(Option, HttpServiceFut

    ), + Service(ResourceDef, HttpService

    ), +} + +impl

    Future for ScopeFactoryResponse

    { + type Item = ScopeService

    ; + type Error = (); + + fn poll(&mut self) -> Poll { + let mut done = true; + + if let Some(ref mut fut) = self.default_fut { + match fut.poll()? { + Async::Ready(default) => self.default = Some(default), + Async::NotReady => done = false, + } + } + + // poll http services + for item in &mut self.fut { + let res = match item { + CreateScopeServiceItem::Future(ref mut path, ref mut fut) => { + match fut.poll()? { + Async::Ready(service) => Some((path.take().unwrap(), service)), + Async::NotReady => { + done = false; + None + } + } + } + CreateScopeServiceItem::Service(_, _) => continue, + }; + + if let Some((path, service)) = res { + *item = CreateScopeServiceItem::Service(path, service); + } + } + + if done { + let router = self + .fut + .drain(..) + .fold(Router::build(), |mut router, item| { + match item { + CreateScopeServiceItem::Service(path, service) => { + router.rdef(path, service) + } + CreateScopeServiceItem::Future(_, _) => unreachable!(), + } + router + }); + Ok(Async::Ready(ScopeService { + router: router.finish(), + default: self.default.take(), + _ready: None, + })) + } else { + Ok(Async::NotReady) + } + } +} + +pub struct ScopeService

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

    , ResourceInfo)>, +} + +impl

    Service for ScopeService

    { + type Request = ServiceRequest

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

    ) -> Self::Future { + if let Some((srv, _info)) = self.router.recognize_mut(req.match_info_mut()) { + Either::A(srv.call(req)) + } else if let Some(ref mut default) = self.default { + Either::A(default.call(req)) + } else { + let req = req.into_request(); + Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + } + } +} + +#[doc(hidden)] +pub struct ScopeEndpoint

    { + factory: Rc>>>, +} + +impl

    ScopeEndpoint

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

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = ScopeService

    ; + type Future = ScopeFactoryResponse

    ; + + fn new_service(&self, _: &()) -> Self::Future { + self.factory.borrow_mut().as_mut().unwrap().new_service(&()) + } +} + +#[cfg(test)] +mod tests { + use actix_http::body::{Body, ResponseBody}; + use actix_http::http::{Method, StatusCode}; + use actix_service::{IntoNewService, NewService, Service}; + use bytes::Bytes; + + use crate::test::TestRequest; + use crate::{web, App, HttpRequest, HttpResponse}; + + #[test] + fn test_scope() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope.resource("/path1", |r| r.to(|| HttpResponse::Ok())) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_scope_root() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope + .resource("", |r| r.to(|| HttpResponse::Ok())) + .resource("/", |r| r.to(|| HttpResponse::Created())) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + } + + #[test] + fn test_scope_root2() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app/", |scope| { + scope.resource("", |r| r.to(|| HttpResponse::Ok())) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_scope_root3() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app/", |scope| { + scope.resource("/", |r| r.to(|| HttpResponse::Ok())) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[test] + fn test_scope_route() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("app", |scope| { + scope.resource("/path1", |r| { + r.route(web::get().to(|| HttpResponse::Ok())) + .route(web::delete().to(|| HttpResponse::Ok())) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[test] + fn test_scope_route_without_leading_slash() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("app", |scope| { + scope.resource("path1", |r| { + r.route(web::get().to(|| HttpResponse::Ok())) + .route(web::delete().to(|| HttpResponse::Ok())) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + // #[test] + // fn test_scope_guard() { + // let mut rt = actix_rt::Runtime::new().unwrap(); + // let app = App::new() + // .scope("/app", |scope| { + // scope + // .guard(guard::Get()) + // .resource("/path1", |r| r.to(|| HttpResponse::Ok())) + // }) + // .into_new_service(); + // let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + // let req = TestRequest::with_uri("/app/path1") + // .method(Method::POST) + // .to_request(); + // let resp = rt.block_on(srv.call(req)).unwrap(); + // assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + // let req = TestRequest::with_uri("/app/path1") + // .method(Method::GET) + // .to_request(); + // let resp = rt.block_on(srv.call(req)).unwrap(); + // assert_eq!(resp.status(), StatusCode::OK); + // } + + #[test] + fn test_scope_variable_segment() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/ab-{project}", |scope| { + scope.resource("/path1", |r| { + r.to(|r: HttpRequest| { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + }) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/ab-project1/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + match resp.body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project1")); + } + _ => panic!(), + } + + let req = TestRequest::with_uri("/aa-project1/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[test] + fn test_nested_scope() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope.nested("/t1", |scope| { + scope.resource("/path1", |r| r.to(|| HttpResponse::Created())) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/t1/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + } + + #[test] + fn test_nested_scope_no_slash() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope.nested("t1", |scope| { + scope.resource("/path1", |r| r.to(|| HttpResponse::Created())) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/t1/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + } + + #[test] + fn test_nested_scope_root() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope.nested("/t1", |scope| { + scope + .resource("", |r| r.to(|| HttpResponse::Ok())) + .resource("/", |r| r.to(|| HttpResponse::Created())) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/t1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/t1/").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + } + + // #[test] + // fn test_nested_scope_filter() { + // let app = App::new() + // .scope("/app", |scope| { + // scope.nested("/t1", |scope| { + // scope + // .filter(pred::Get()) + // .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) + // }) + // }) + // .finish(); + + // let req = TestRequest::with_uri("/app/t1/path1") + // .method(Method::POST) + // .request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + // let req = TestRequest::with_uri("/app/t1/path1") + // .method(Method::GET) + // .request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::OK); + // } + + #[test] + fn test_nested_scope_with_variable_segment() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope.nested("/{project_id}", |scope| { + scope.resource("/path1", |r| { + r.to(|r: HttpRequest| { + HttpResponse::Created().body(format!( + "project: {}", + &r.match_info()["project_id"] + )) + }) + }) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/project_1/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + + match resp.body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project_1")); + } + _ => panic!(), + } + } + + #[test] + fn test_nested2_scope_with_variable_segment() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope.nested("/{project}", |scope| { + scope.nested("/{id}", |scope| { + scope.resource("/path1", |r| { + r.to(|r: HttpRequest| { + HttpResponse::Created().body(format!( + "project: {} - {}", + &r.match_info()["project"], + &r.match_info()["id"], + )) + }) + }) + }) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/test/1/path1").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + + match resp.body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); + } + _ => panic!(), + } + + let req = TestRequest::with_uri("/app/test/1/path2").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[test] + fn test_default_resource() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope + .resource("/path1", |r| r.to(|| HttpResponse::Ok())) + .default_resource(|r| r.to(|| HttpResponse::BadRequest())) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/app/path2").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let req = TestRequest::with_uri("/path2").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[test] + fn test_default_resource_propagation() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app1", |scope| { + scope.default_resource(|r| r.to(|| HttpResponse::BadRequest())) + }) + .scope("/app2", |scope| scope) + .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/non-exist").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let req = TestRequest::with_uri("/app1/non-exist").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let req = TestRequest::with_uri("/app2/non-exist").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + } +} diff --git a/src/test.rs b/src/test.rs index 46fd45d5..684817ec 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, Version}; use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{Extensions, PayloadStream}; +use actix_http::{Extensions, PayloadStream, Request}; use actix_router::{Path, Url}; use bytes::Bytes; @@ -135,8 +135,13 @@ impl TestRequest { ) } + /// Complete request creation and generate `Request` instance + pub fn to_request(mut self) -> Request { + self.req.finish() + } + /// Complete request creation and generate `HttpRequest` instance - pub fn to_request(mut self) -> HttpRequest { + pub fn to_http_request(mut self) -> HttpRequest { let req = self.req.finish(); ServiceRequest::new( From 5c61321565d02f8c08b73d31a90676a48ab08694 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 21:40:03 -0800 Subject: [PATCH 0964/1635] fix state factory support, tests for state and state factory --- src/app.rs | 175 +++++++++++++++++++++++++++++++++++++++++++++-- src/responder.rs | 37 ++++++++++ src/scope.rs | 2 +- 3 files changed, 208 insertions(+), 6 deletions(-) diff --git a/src/app.rs b/src/app.rs index 27658011..3940b9fc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -14,7 +14,7 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; use crate::resource::Resource; -use crate::scope::Scope; +use crate::scope::{insert_slash, Scope}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::state::{State, StateFactory, StateFactoryResult}; @@ -103,13 +103,13 @@ where /// Set application state factory. This function is /// similar to `.state()` but it accepts state factory. State get /// constructed asynchronously during application initialization. - pub fn state_factory(mut self, state: F) -> Self + pub fn state_factory(mut self, state: F) -> Self where F: Fn() -> Out + 'static, Out: IntoFuture + 'static, Out::Error: std::fmt::Debug, { - self.state.push(Box::new(State::new(state))); + self.state.push(Box::new(state)); self } @@ -200,7 +200,7 @@ where InitError = (), > + 'static, { - let rdef = ResourceDef::new(path); + let rdef = ResourceDef::new(&insert_slash(path)); let resource = f(Resource::new()); let default = resource.get_default(); @@ -408,7 +408,7 @@ where InitError = (), > + 'static, { - let rdef = ResourceDef::new(path); + let rdef = ResourceDef::new(&insert_slash(path)); let resource = f(Resource::new()); self.defaults.push(resource.get_default()); self.services @@ -891,3 +891,168 @@ where self.chain.call(req) } } + +#[cfg(test)] +mod tests { + use actix_http::http::StatusCode; + + use super::*; + use crate::test::TestRequest; + use crate::{HttpResponse, State}; + + #[test] + fn test_default_resource() { + let mut rt = actix_rt::Runtime::new().unwrap(); + + let app = App::new() + .resource("/test", |r| r.to(|| HttpResponse::Ok())) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/test").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/blah").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let app = App::new() + .resource("/test", |r| r.to(|| HttpResponse::Ok())) + .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/blah").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + } + + #[test] + fn test_state() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .state(10usize) + .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::default().to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let app = App::new() + .state(10u32) + .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::default().to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } + + #[test] + fn test_state_factory() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .state_factory(|| Ok::<_, ()>(10usize)) + .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::default().to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let app = App::new() + .state_factory(|| Ok::<_, ()>(10u32)) + .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::default().to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } + + // #[test] + // fn test_handler() { + // let app = App::new() + // .handler("/test", |_: &_| HttpResponse::Ok()) + // .finish(); + + // let req = TestRequest::with_uri("/test").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::OK); + + // let req = TestRequest::with_uri("/test/").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::OK); + + // let req = TestRequest::with_uri("/test/app").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::OK); + + // let req = TestRequest::with_uri("/testapp").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + // let req = TestRequest::with_uri("/blah").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + // } + + // #[test] + // fn test_handler2() { + // let app = App::new() + // .handler("test", |_: &_| HttpResponse::Ok()) + // .finish(); + + // let req = TestRequest::with_uri("/test").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::OK); + + // let req = TestRequest::with_uri("/test/").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::OK); + + // let req = TestRequest::with_uri("/test/app").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::OK); + + // let req = TestRequest::with_uri("/testapp").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + + // let req = TestRequest::with_uri("/blah").request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + // } + + // #[test] + // fn test_route() { + // let app = App::new() + // .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) + // .route("/test", Method::POST, |_: HttpRequest| { + // HttpResponse::Created() + // }) + // .finish(); + + // let req = TestRequest::with_uri("/test").method(Method::GET).request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::OK); + + // let req = TestRequest::with_uri("/test") + // .method(Method::POST) + // .request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::CREATED); + + // let req = TestRequest::with_uri("/test") + // .method(Method::HEAD) + // .request(); + // let resp = app.run(req); + // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + // } +} diff --git a/src/responder.rs b/src/responder.rs index 22588a9c..8e7f66b4 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -272,3 +272,40 @@ where Ok(self.0.poll().map_err(|e| e.into())?) } } + +#[cfg(test)] +mod tests { + // use actix_http::body::Body; + use actix_http::body::{Body, ResponseBody}; + use actix_http::http::StatusCode; + use actix_service::{IntoNewService, NewService, Service}; + use bytes::Bytes; + + use crate::test::TestRequest; + use crate::App; + + #[test] + fn test_option_responder() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .resource("/none", |r| r.to(|| -> Option<&'static str> { None })) + .resource("/some", |r| r.to(|| Some("some"))) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); + + let req = TestRequest::with_uri("/none").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/some").to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + match resp.body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"some")); + } + _ => panic!(), + } + } +} diff --git a/src/scope.rs b/src/scope.rs index 5327f953..ec6bc035 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -272,7 +272,7 @@ where } } -fn insert_slash(path: &str) -> String { +pub(crate) fn insert_slash(path: &str) -> String { let mut path = path.to_owned(); if !path.is_empty() && !path.starts_with('/') { path.insert(0, '/'); From e442ddb1671ad9fb1644c3e7f150d6f834cccf78 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 11:47:53 -0800 Subject: [PATCH 0965/1635] allow scope level guards --- Cargo.toml | 1 - src/app.rs | 92 +++++++++++++++--------- src/scope.rs | 185 +++++++++++++++++++++++++++++-------------------- src/service.rs | 8 ++- 4 files changed, 179 insertions(+), 107 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 370089e7..03b1794f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,6 @@ actix-utils = "0.3.0" actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } -#actix-router = { path = "../actix-net/router" } bytes = "0.4" derive_more = "0.14" diff --git a/src/app.rs b/src/app.rs index 3940b9fc..119f1a21 100644 --- a/src/app.rs +++ b/src/app.rs @@ -13,11 +13,13 @@ use actix_service::{ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::guard::Guard; use crate::resource::Resource; use crate::scope::{insert_slash, Scope}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::state::{State, StateFactory, StateFactoryResult}; +type Guards = Vec>; type HttpService

    = BoxedService, ServiceResponse, ()>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; type BoxedResponse = Box>; @@ -141,14 +143,15 @@ where where F: FnOnce(Scope

    ) -> Scope

    , { - let scope = f(Scope::new(path)); + let mut scope = f(Scope::new(path)); let rdef = scope.rdef().clone(); let default = scope.get_default(); + let guards = scope.take_guards(); let fref = Rc::new(RefCell::new(None)); AppRouter { chain: self.chain, - services: vec![(rdef, boxed::new_service(scope.into_new_service()))], + services: vec![(rdef, boxed::new_service(scope.into_new_service()), guards)], default: None, defaults: vec![default], endpoint: AppEntry::new(fref.clone()), @@ -201,13 +204,13 @@ where > + 'static, { let rdef = ResourceDef::new(&insert_slash(path)); - let resource = f(Resource::new()); - let default = resource.get_default(); + let res = f(Resource::new()); + let default = res.get_default(); let fref = Rc::new(RefCell::new(None)); AppRouter { chain: self.chain, - services: vec![(rdef, boxed::new_service(resource.into_new_service()))], + services: vec![(rdef, boxed::new_service(res.into_new_service()), None)], default: None, defaults: vec![default], endpoint: AppEntry::new(fref.clone()), @@ -308,7 +311,7 @@ where /// for building application instances. pub struct AppRouter { chain: C, - services: Vec<(ResourceDef, HttpNewService

    )>, + services: Vec<(ResourceDef, HttpNewService

    , Option)>, default: Option>>, defaults: Vec>>>>>, endpoint: T, @@ -357,11 +360,12 @@ where where F: FnOnce(Scope

    ) -> Scope

    , { - let scope = f(Scope::new(path)); + let mut scope = f(Scope::new(path)); let rdef = scope.rdef().clone(); + let guards = scope.take_guards(); self.defaults.push(scope.get_default()); self.services - .push((rdef, boxed::new_service(scope.into_new_service()))); + .push((rdef, boxed::new_service(scope.into_new_service()), guards)); self } @@ -411,8 +415,11 @@ where let rdef = ResourceDef::new(&insert_slash(path)); let resource = f(Resource::new()); self.defaults.push(resource.get_default()); - self.services - .push((rdef, boxed::new_service(resource.into_new_service()))); + self.services.push(( + rdef, + boxed::new_service(resource.into_new_service()), + None, + )); self } @@ -452,6 +459,7 @@ where self.services.push(( rdef.into(), boxed::new_service(factory.into_new_service().map_init_err(|_| ())), + None, )); self } @@ -562,7 +570,12 @@ where // set factory *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { default: self.default.clone(), - services: Rc::new(self.services), + services: Rc::new( + self.services + .into_iter() + .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards))) + .collect(), + ), }); AppInit { @@ -575,7 +588,7 @@ where } pub struct AppRoutingFactory

    { - services: Rc)>>, + services: Rc, RefCell>)>>, default: Option>>, } @@ -598,9 +611,10 @@ impl NewService for AppRoutingFactory

    { fut: self .services .iter() - .map(|(path, service)| { + .map(|(path, service, guards)| { CreateAppRoutingItem::Future( Some(path.clone()), + guards.borrow_mut().take(), service.new_service(&()), ) }) @@ -622,8 +636,8 @@ pub struct AppRoutingFactoryResponse

    { } enum CreateAppRoutingItem

    { - Future(Option, HttpServiceFut

    ), - Service(ResourceDef, HttpService

    ), + Future(Option, Option, HttpServiceFut

    ), + Service(ResourceDef, Option, HttpService

    ), } impl

    Future for AppRoutingFactoryResponse

    { @@ -643,20 +657,24 @@ impl

    Future for AppRoutingFactoryResponse

    { // poll http services for item in &mut self.fut { let res = match item { - CreateAppRoutingItem::Future(ref mut path, ref mut fut) => { - match fut.poll()? { - Async::Ready(service) => Some((path.take().unwrap(), service)), - Async::NotReady => { - done = false; - None - } + CreateAppRoutingItem::Future( + ref mut path, + ref mut guards, + ref mut fut, + ) => match fut.poll()? { + Async::Ready(service) => { + Some((path.take().unwrap(), guards.take(), service)) } - } - CreateAppRoutingItem::Service(_, _) => continue, + Async::NotReady => { + done = false; + None + } + }, + CreateAppRoutingItem::Service(_, _, _) => continue, }; - if let Some((path, service)) = res { - *item = CreateAppRoutingItem::Service(path, service); + if let Some((path, guards, service)) = res { + *item = CreateAppRoutingItem::Service(path, guards, service); } } @@ -666,10 +684,11 @@ impl

    Future for AppRoutingFactoryResponse

    { .drain(..) .fold(Router::build(), |mut router, item| { match item { - CreateAppRoutingItem::Service(path, service) => { - router.rdef(path, service) + CreateAppRoutingItem::Service(path, guards, service) => { + router.rdef(path, service); + router.set_user_data(guards); } - CreateAppRoutingItem::Future(_, _) => unreachable!(), + CreateAppRoutingItem::Future(_, _, _) => unreachable!(), } router }); @@ -685,7 +704,7 @@ impl

    Future for AppRoutingFactoryResponse

    { } pub struct AppRouting

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

    , ResourceInfo)>, default: Option>, } @@ -705,7 +724,18 @@ impl

    Service for AppRouting

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

    ) -> Self::Future { - if let Some((srv, _info)) = self.router.recognize_mut(req.match_info_mut()) { + let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + if let Some(ref guards) = guards { + for f in guards { + if !f.check(req.head()) { + return false; + } + } + } + true + }); + + if let Some((srv, _info)) = res { Either::A(srv.call(req)) } else if let Some(ref mut default) = self.default { Either::A(default.call(req)) diff --git a/src/scope.rs b/src/scope.rs index ec6bc035..2ed18a42 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -15,6 +15,7 @@ use crate::resource::Resource; use crate::route::Route; use crate::service::{ServiceRequest, ServiceResponse}; +type Guards = Vec>; type HttpService

    = BoxedService, ServiceResponse, ()>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; type BoxedResponse = Box>; @@ -51,8 +52,8 @@ type BoxedResponse = Box>; pub struct Scope> { endpoint: T, rdef: ResourceDef, - services: Vec<(ResourceDef, HttpNewService

    )>, - guards: Rc>>, + services: Vec<(ResourceDef, HttpNewService

    , Option)>, + guards: Vec>, default: Rc>>>>, defaults: Vec>>>>>, factory_ref: Rc>>>, @@ -66,7 +67,7 @@ impl Scope

    { Scope { endpoint: ScopeEndpoint::new(fref.clone()), rdef: rdef.clone(), - guards: Rc::new(Vec::new()), + guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), defaults: Vec::new(), @@ -110,7 +111,7 @@ where /// } /// ``` pub fn guard(mut self, guard: G) -> Self { - Rc::get_mut(&mut self.guards).unwrap().push(Box::new(guard)); + self.guards.push(Box::new(guard)); self } @@ -135,11 +136,12 @@ where where F: FnOnce(Scope

    ) -> Scope

    , { - let scope = f(Scope::new(path)); + let mut scope = f(Scope::new(path)); let rdef = scope.rdef().clone(); + let guards = scope.take_guards(); self.defaults.push(scope.get_default()); self.services - .push((rdef, boxed::new_service(scope.into_new_service()))); + .push((rdef, boxed::new_service(scope.into_new_service()), guards)); self } @@ -204,8 +206,11 @@ where let rdef = ResourceDef::new(&insert_slash(path)); let resource = f(Resource::new()); self.defaults.push(resource.get_default()); - self.services - .push((rdef, boxed::new_service(resource.into_new_service()))); + self.services.push(( + rdef, + boxed::new_service(resource.into_new_service()), + None, + )); self } @@ -270,6 +275,14 @@ where pub(crate) fn get_default(&self) -> Rc>>>> { self.default.clone() } + + pub(crate) fn take_guards(&mut self) -> Option>> { + if self.guards.is_empty() { + None + } else { + Some(std::mem::replace(&mut self.guards, Vec::new())) + } + } } pub(crate) fn insert_slash(path: &str) -> String { @@ -301,7 +314,12 @@ where *self.factory_ref.borrow_mut() = Some(ScopeFactory { default: self.default.clone(), - services: Rc::new(self.services), + services: Rc::new( + self.services + .into_iter() + .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards))) + .collect(), + ), }); self.endpoint @@ -309,7 +327,7 @@ where } pub struct ScopeFactory

    { - services: Rc)>>, + services: Rc, RefCell>)>>, default: Rc>>>>, } @@ -332,9 +350,10 @@ impl NewService for ScopeFactory

    { fut: self .services .iter() - .map(|(path, service)| { + .map(|(path, service, guards)| { CreateScopeServiceItem::Future( Some(path.clone()), + guards.borrow_mut().take(), service.new_service(&()), ) }) @@ -356,8 +375,8 @@ pub struct ScopeFactoryResponse

    { type HttpServiceFut

    = Box, Error = ()>>; enum CreateScopeServiceItem

    { - Future(Option, HttpServiceFut

    ), - Service(ResourceDef, HttpService

    ), + Future(Option, Option, HttpServiceFut

    ), + Service(ResourceDef, Option, HttpService

    ), } impl

    Future for ScopeFactoryResponse

    { @@ -377,20 +396,24 @@ impl

    Future for ScopeFactoryResponse

    { // poll http services for item in &mut self.fut { let res = match item { - CreateScopeServiceItem::Future(ref mut path, ref mut fut) => { - match fut.poll()? { - Async::Ready(service) => Some((path.take().unwrap(), service)), - Async::NotReady => { - done = false; - None - } + CreateScopeServiceItem::Future( + ref mut path, + ref mut guards, + ref mut fut, + ) => match fut.poll()? { + Async::Ready(service) => { + Some((path.take().unwrap(), guards.take(), service)) } - } - CreateScopeServiceItem::Service(_, _) => continue, + Async::NotReady => { + done = false; + None + } + }, + CreateScopeServiceItem::Service(_, _, _) => continue, }; - if let Some((path, service)) = res { - *item = CreateScopeServiceItem::Service(path, service); + if let Some((path, guards, service)) = res { + *item = CreateScopeServiceItem::Service(path, guards, service); } } @@ -400,10 +423,11 @@ impl

    Future for ScopeFactoryResponse

    { .drain(..) .fold(Router::build(), |mut router, item| { match item { - CreateScopeServiceItem::Service(path, service) => { - router.rdef(path, service) + CreateScopeServiceItem::Service(path, guards, service) => { + router.rdef(path, service); + router.set_user_data(guards); } - CreateScopeServiceItem::Future(_, _) => unreachable!(), + CreateScopeServiceItem::Future(_, _, _) => unreachable!(), } router }); @@ -419,7 +443,7 @@ impl

    Future for ScopeFactoryResponse

    { } pub struct ScopeService

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

    , ResourceInfo)>, } @@ -435,7 +459,18 @@ impl

    Service for ScopeService

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

    ) -> Self::Future { - if let Some((srv, _info)) = self.router.recognize_mut(req.match_info_mut()) { + let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + if let Some(ref guards) = guards { + for f in guards { + if !f.check(req.head()) { + return false; + } + } + } + true + }); + + if let Some((srv, _info)) = res { Either::A(srv.call(req)) } else if let Some(ref mut default) = self.default { Either::A(default.call(req)) @@ -478,7 +513,7 @@ mod tests { use bytes::Bytes; use crate::test::TestRequest; - use crate::{web, App, HttpRequest, HttpResponse}; + use crate::{guard, web, App, HttpRequest, HttpResponse}; #[test] fn test_scope() { @@ -614,30 +649,30 @@ mod tests { assert_eq!(resp.status(), StatusCode::NOT_FOUND); } - // #[test] - // fn test_scope_guard() { - // let mut rt = actix_rt::Runtime::new().unwrap(); - // let app = App::new() - // .scope("/app", |scope| { - // scope - // .guard(guard::Get()) - // .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - // }) - // .into_new_service(); - // let mut srv = rt.block_on(app.new_service(&())).unwrap(); + #[test] + fn test_scope_guard() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope + .guard(guard::Get()) + .resource("/path1", |r| r.to(|| HttpResponse::Ok())) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); - // let req = TestRequest::with_uri("/app/path1") - // .method(Method::POST) - // .to_request(); - // let resp = rt.block_on(srv.call(req)).unwrap(); - // assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - // let req = TestRequest::with_uri("/app/path1") - // .method(Method::GET) - // .to_request(); - // let resp = rt.block_on(srv.call(req)).unwrap(); - // assert_eq!(resp.status(), StatusCode::OK); - // } + let req = TestRequest::with_uri("/app/path1") + .method(Method::GET) + .to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } #[test] fn test_scope_variable_segment() { @@ -728,30 +763,32 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); } - // #[test] - // fn test_nested_scope_filter() { - // let app = App::new() - // .scope("/app", |scope| { - // scope.nested("/t1", |scope| { - // scope - // .filter(pred::Get()) - // .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - // }) - // }) - // .finish(); + #[test] + fn test_nested_scope_filter() { + let mut rt = actix_rt::Runtime::new().unwrap(); + let app = App::new() + .scope("/app", |scope| { + scope.nested("/t1", |scope| { + scope + .guard(guard::Get()) + .resource("/path1", |r| r.to(|| HttpResponse::Ok())) + }) + }) + .into_new_service(); + let mut srv = rt.block_on(app.new_service(&())).unwrap(); - // let req = TestRequest::with_uri("/app/t1/path1") - // .method(Method::POST) - // .request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::POST) + .to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - // let req = TestRequest::with_uri("/app/t1/path1") - // .method(Method::GET) - // .request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::OK); - // } + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::GET) + .to_request(); + let resp = rt.block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } #[test] fn test_nested_scope_with_variable_segment() { diff --git a/src/service.rs b/src/service.rs index a515300a..637a8668 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,7 +8,7 @@ use actix_http::{ Error, Extensions, HttpMessage, Payload, Request, RequestHead, Response, ResponseHead, }; -use actix_router::{Path, Url}; +use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; use crate::request::HttpRequest; @@ -137,6 +137,12 @@ impl

    ServiceRequest

    { } } +impl

    Resource for ServiceRequest

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

    HttpMessage for ServiceRequest

    { type Stream = P; From bd4124587a1fae9e14a31d5ecaf050f7b454d186 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 13:25:35 -0800 Subject: [PATCH 0966/1635] provide block_on function for testing purpose --- Cargo.toml | 2 +- src/app.rs | 50 +++++++------ src/extract.rs | 48 ++++--------- src/guard.rs | 74 ++++++++++--------- src/middleware/defaultheaders.rs | 16 ++--- src/responder.rs | 7 +- src/scope.rs | 118 +++++++++++++------------------ src/test.rs | 67 +++++++++++++++--- 8 files changed, 207 insertions(+), 175 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 03b1794f..3b88c300 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ flate2-rust = ["flate2/rust_backend"] actix-codec = "0.1.0" actix-service = "0.3.0" actix-utils = "0.3.0" +actix-rt = "0.1.0" actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } @@ -69,7 +70,6 @@ brotli2 = { version="^0.3.2", optional = true } flate2 = { version="^1.0.2", optional = true, default-features = false } [dev-dependencies] -actix-rt = "0.1.0" actix-server = { version="0.3.0", features=["ssl"] } actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } diff --git a/src/app.rs b/src/app.rs index 119f1a21..e1479080 100644 --- a/src/app.rs +++ b/src/app.rs @@ -924,85 +924,95 @@ where #[cfg(test)] mod tests { - use actix_http::http::StatusCode; + use actix_http::http::{Method, StatusCode}; use super::*; - use crate::test::TestRequest; - use crate::{HttpResponse, State}; + use crate::test::{block_on, TestRequest}; + use crate::{web, HttpResponse, State}; #[test] fn test_default_resource() { - let mut rt = actix_rt::Runtime::new().unwrap(); - let app = App::new() .resource("/test", |r| r.to(|| HttpResponse::Ok())) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/test").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/blah").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let app = App::new() .resource("/test", |r| r.to(|| HttpResponse::Ok())) + .resource("/test2", |r| { + r.default_resource(|r| r.to(|| HttpResponse::Created())) + .route(web::get().to(|| HttpResponse::Ok())) + }) .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/blah").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let req = TestRequest::with_uri("/test2").to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test2") + .method(Method::POST) + .to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); } #[test] fn test_state() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .state(10usize) .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::default().to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let app = App::new() .state(10u32) .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::default().to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } #[test] fn test_state_factory() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .state_factory(|| Ok::<_, ()>(10usize)) .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::default().to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let app = App::new() .state_factory(|| Ok::<_, ()>(10u32)) .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::default().to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } diff --git a/src/extract.rs b/src/extract.rs index 3b5c7e74..7350d7d9 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1152,7 +1152,7 @@ mod tests { use serde_derive::Deserialize; use super::*; - use crate::test::TestRequest; + use crate::test::{block_on, TestRequest}; #[derive(Deserialize, Debug, PartialEq)] struct Info { @@ -1161,29 +1161,26 @@ mod tests { #[test] fn test_bytes() { - let mut rt = actix_rt::Runtime::new().unwrap(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) .to_from(); - let s = rt.block_on(Bytes::from_request(&mut req)).unwrap(); + let s = block_on(Bytes::from_request(&mut req)).unwrap(); assert_eq!(s, Bytes::from_static(b"hello=world")); } #[test] fn test_string() { - let mut rt = actix_rt::Runtime::new().unwrap(); let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) .to_from(); - let s = rt.block_on(String::from_request(&mut req)).unwrap(); + let s = block_on(String::from_request(&mut req)).unwrap(); assert_eq!(s, "hello=world"); } #[test] fn test_form() { - let mut rt = actix_rt::Runtime::new().unwrap(); let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", @@ -1192,13 +1189,12 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .to_from(); - let s = rt.block_on(Form::::from_request(&mut req)).unwrap(); + let s = block_on(Form::::from_request(&mut req)).unwrap(); assert_eq!(s.hello, "world"); } #[test] fn test_option() { - let mut rt = actix_rt::Runtime::new().unwrap(); let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", @@ -1206,9 +1202,7 @@ mod tests { .config(FormConfig::default().limit(4096)) .to_from(); - let r = rt - .block_on(Option::>::from_request(&mut req)) - .unwrap(); + let r = block_on(Option::>::from_request(&mut req)).unwrap(); assert_eq!(r, None); let mut req = TestRequest::with_header( @@ -1219,9 +1213,7 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .to_from(); - let r = rt - .block_on(Option::>::from_request(&mut req)) - .unwrap(); + let r = block_on(Option::>::from_request(&mut req)).unwrap(); assert_eq!( r, Some(Form(Info { @@ -1237,15 +1229,12 @@ mod tests { .set_payload(Bytes::from_static(b"bye=world")) .to_from(); - let r = rt - .block_on(Option::>::from_request(&mut req)) - .unwrap(); + let r = block_on(Option::>::from_request(&mut req)).unwrap(); assert_eq!(r, None); } #[test] fn test_result() { - let mut rt = actix_rt::Runtime::new().unwrap(); let mut req = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", @@ -1254,8 +1243,7 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .to_from(); - let r = rt - .block_on(Result::, Error>::from_request(&mut req)) + let r = block_on(Result::, Error>::from_request(&mut req)) .unwrap() .unwrap(); assert_eq!( @@ -1273,9 +1261,7 @@ mod tests { .set_payload(Bytes::from_static(b"bye=world")) .to_from(); - let r = rt - .block_on(Result::, Error>::from_request(&mut req)) - .unwrap(); + let r = block_on(Result::, Error>::from_request(&mut req)).unwrap(); assert!(r.is_err()); } @@ -1361,25 +1347,19 @@ mod tests { #[test] fn test_tuple_extract() { - let mut rt = actix_rt::Runtime::new().unwrap(); let resource = ResourceDef::new("/{key}/{value}/"); let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from(); resource.match_path(req.match_info_mut()); - let res = rt - .block_on(<(Path<(String, String)>,)>::from_request(&mut req)) - .unwrap(); + let res = block_on(<(Path<(String, String)>,)>::from_request(&mut req)).unwrap(); assert_eq!((res.0).0, "name"); assert_eq!((res.0).1, "user1"); - let res = rt - .block_on( - <(Path<(String, String)>, Path<(String, String)>)>::from_request( - &mut req, - ), - ) - .unwrap(); + let res = block_on( + <(Path<(String, String)>, Path<(String, String)>)>::from_request(&mut req), + ) + .unwrap(); assert_eq!((res.0).0, "name"); assert_eq!((res.0).1, "user1"); assert_eq!((res.1).0, "name"); diff --git a/src/guard.rs b/src/guard.rs index c8948b3b..93b6e132 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -239,8 +239,7 @@ mod tests { #[test] fn test_header() { let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked") - .finish() - .into_request(); + .to_http_request(); let pred = Header("transfer-encoding", "chunked"); assert!(pred.check(&req)); @@ -270,44 +269,55 @@ mod tests { #[test] fn test_methods() { - let req = TestRequest::default().finish().into_request(); + let req = TestRequest::default().to_http_request(); let req2 = TestRequest::default() .method(Method::POST) - .finish() - .into_request(); + .to_http_request(); assert!(Get().check(&req)); assert!(!Get().check(&req2)); assert!(Post().check(&req2)); assert!(!Post().check(&req)); - let r = TestRequest::default().method(Method::PUT).finish(); - assert!(Put().check(&r,)); - assert!(!Put().check(&req,)); + let r = TestRequest::default().method(Method::PUT).to_http_request(); + assert!(Put().check(&r)); + assert!(!Put().check(&req)); - let r = TestRequest::default().method(Method::DELETE).finish(); - assert!(Delete().check(&r,)); - assert!(!Delete().check(&req,)); + let r = TestRequest::default() + .method(Method::DELETE) + .to_http_request(); + assert!(Delete().check(&r)); + assert!(!Delete().check(&req)); - let r = TestRequest::default().method(Method::HEAD).finish(); - assert!(Head().check(&r,)); - assert!(!Head().check(&req,)); + let r = TestRequest::default() + .method(Method::HEAD) + .to_http_request(); + assert!(Head().check(&r)); + assert!(!Head().check(&req)); - let r = TestRequest::default().method(Method::OPTIONS).finish(); - assert!(Options().check(&r,)); - assert!(!Options().check(&req,)); + let r = TestRequest::default() + .method(Method::OPTIONS) + .to_http_request(); + assert!(Options().check(&r)); + assert!(!Options().check(&req)); - let r = TestRequest::default().method(Method::CONNECT).finish(); - assert!(Connect().check(&r,)); - assert!(!Connect().check(&req,)); + let r = TestRequest::default() + .method(Method::CONNECT) + .to_http_request(); + assert!(Connect().check(&r)); + assert!(!Connect().check(&req)); - let r = TestRequest::default().method(Method::PATCH).finish(); - assert!(Patch().check(&r,)); - assert!(!Patch().check(&req,)); + let r = TestRequest::default() + .method(Method::PATCH) + .to_http_request(); + assert!(Patch().check(&r)); + assert!(!Patch().check(&req)); - let r = TestRequest::default().method(Method::TRACE).finish(); - assert!(Trace().check(&r,)); - assert!(!Trace().check(&req,)); + let r = TestRequest::default() + .method(Method::TRACE) + .to_http_request(); + assert!(Trace().check(&r)); + assert!(!Trace().check(&req)); } #[test] @@ -316,13 +326,13 @@ mod tests { .method(Method::TRACE) .to_http_request(); - assert!(Not(Get()).check(&r,)); - assert!(!Not(Trace()).check(&r,)); + assert!(Not(Get()).check(&r)); + assert!(!Not(Trace()).check(&r)); - assert!(All(Trace()).and(Trace()).check(&r,)); - assert!(!All(Get()).and(Trace()).check(&r,)); + assert!(All(Trace()).and(Trace()).check(&r)); + assert!(!All(Get()).and(Trace()).check(&r)); - assert!(Any(Get()).or(Trace()).check(&r,)); - assert!(!Any(Get()).or(Get()).check(&r,)); + assert!(Any(Get()).or(Trace()).check(&r)); + assert!(!Any(Get()).or(Get()).check(&r)); } } diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index fa287b28..40bf9f1c 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -138,39 +138,37 @@ mod tests { use actix_service::FnService; use super::*; - use crate::test::TestRequest; + use crate::test::{block_on, TestRequest}; use crate::{HttpResponse, ServiceRequest}; #[test] fn test_default_headers() { - let mut rt = actix_rt::Runtime::new().unwrap(); let mut mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); let mut srv = FnService::new(|req: ServiceRequest<_>| { req.into_response(HttpResponse::Ok().finish()) }); - let req = TestRequest::default().finish(); - let resp = rt.block_on(mw.call(req, &mut srv)).unwrap(); + let req = TestRequest::default().to_service(); + let resp = block_on(mw.call(req, &mut srv)).unwrap(); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let req = TestRequest::default().finish(); + let req = TestRequest::default().to_service(); let mut srv = FnService::new(|req: ServiceRequest<_>| { req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) }); - let resp = rt.block_on(mw.call(req, &mut srv)).unwrap(); + let resp = block_on(mw.call(req, &mut srv)).unwrap(); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); } #[test] fn test_content_type() { - let mut rt = actix_rt::Runtime::new().unwrap(); let mut mw = DefaultHeaders::new().content_type(); let mut srv = FnService::new(|req: ServiceRequest<_>| { req.into_response(HttpResponse::Ok().finish()) }); - let req = TestRequest::default().finish(); - let resp = rt.block_on(mw.call(req, &mut srv)).unwrap(); + let req = TestRequest::default().to_service(); + let resp = block_on(mw.call(req, &mut srv)).unwrap(); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), "application/octet-stream" diff --git a/src/responder.rs b/src/responder.rs index 8e7f66b4..b2fd848f 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -286,19 +286,18 @@ mod tests { #[test] fn test_option_responder() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .resource("/none", |r| r.to(|| -> Option<&'static str> { None })) .resource("/some", |r| r.to(|| Some("some"))) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = TestRequest::block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/none").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = TestRequest::block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/some").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = TestRequest::block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); match resp.body() { ResponseBody::Body(Body::Bytes(ref b)) => { diff --git a/src/scope.rs b/src/scope.rs index 2ed18a42..7aeb5041 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -32,14 +32,14 @@ type BoxedResponse = Box>; /// `Path` extractor also is able to extract scope level variable segments. /// /// ```rust -/// use actix_web::{App, HttpResponse}; +/// use actix_web::{web, App, HttpResponse}; /// /// fn main() { /// let app = App::new().scope("/{project_id}/", |scope| { /// scope /// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) -/// .resource("/path2", |r| r.to(|| HttpResponse::Ok())) -/// .resource("/path3", |r| r.to(|| HttpResponse::MethodNotAllowed())) +/// .resource("/path2", |r| r.route(web::get().to(|| HttpResponse::Ok()))) +/// .resource("/path3", |r| r.route(web::head().to(|| HttpResponse::MethodNotAllowed()))) /// }); /// } /// ``` @@ -512,27 +512,25 @@ mod tests { use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; - use crate::test::TestRequest; + use crate::test::{block_on, TestRequest}; use crate::{guard, web, App, HttpRequest, HttpResponse}; #[test] fn test_scope() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope.resource("/path1", |r| r.to(|| HttpResponse::Ok())) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_scope_root() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope @@ -540,58 +538,55 @@ mod tests { .resource("/", |r| r.to(|| HttpResponse::Created())) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); } #[test] fn test_scope_root2() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app/", |scope| { scope.resource("", |r| r.to(|| HttpResponse::Ok())) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_scope_root3() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app/", |scope| { scope.resource("/", |r| r.to(|| HttpResponse::Ok())) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[test] fn test_scope_route() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("app", |scope| { scope.resource("/path1", |r| { @@ -600,28 +595,27 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::DELETE) .to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[test] fn test_scope_route_without_leading_slash() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("app", |scope| { scope.resource("path1", |r| { @@ -630,28 +624,27 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::DELETE) .to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[test] fn test_scope_guard() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope @@ -659,24 +652,23 @@ mod tests { .resource("/path1", |r| r.to(|| HttpResponse::Ok())) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/path1") .method(Method::GET) .to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_scope_variable_segment() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/ab-{project}", |scope| { scope.resource("/path1", |r| { @@ -687,10 +679,10 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/ab-project1/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); match resp.body() { @@ -702,13 +694,12 @@ mod tests { } let req = TestRequest::with_uri("/aa-project1/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[test] fn test_nested_scope() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { @@ -716,16 +707,15 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); } #[test] fn test_nested_scope_no_slash() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope.nested("t1", |scope| { @@ -733,16 +723,15 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); } #[test] fn test_nested_scope_root() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { @@ -752,20 +741,19 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/t1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/t1/").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); } #[test] fn test_nested_scope_filter() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope.nested("/t1", |scope| { @@ -775,24 +763,23 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) .to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::GET) .to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_nested_scope_with_variable_segment() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope.nested("/{project_id}", |scope| { @@ -807,10 +794,10 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/project_1/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); match resp.body() { @@ -824,7 +811,6 @@ mod tests { #[test] fn test_nested2_scope_with_variable_segment() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope.nested("/{project}", |scope| { @@ -842,10 +828,10 @@ mod tests { }) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/test/1/path1").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); match resp.body() { @@ -857,13 +843,12 @@ mod tests { } let req = TestRequest::with_uri("/app/test/1/path2").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[test] fn test_default_resource() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app", |scope| { scope @@ -871,20 +856,19 @@ mod tests { .default_resource(|r| r.to(|| HttpResponse::BadRequest())) }) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/app/path2").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/path2").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[test] fn test_default_resource_propagation() { - let mut rt = actix_rt::Runtime::new().unwrap(); let app = App::new() .scope("/app1", |scope| { scope.default_resource(|r| r.to(|| HttpResponse::BadRequest())) @@ -892,18 +876,18 @@ mod tests { .scope("/app2", |scope| scope) .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())) .into_new_service(); - let mut srv = rt.block_on(app.new_service(&())).unwrap(); + let mut srv = block_on(app.new_service(&())).unwrap(); let req = TestRequest::with_uri("/non-exist").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let req = TestRequest::with_uri("/app1/non-exist").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/app2/non-exist").to_request(); - let resp = rt.block_on(srv.call(req)).unwrap(); + let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } } diff --git a/src/test.rs b/src/test.rs index 684817ec..7ceedacc 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,4 +1,5 @@ //! Various helpers for Actix applications to use during testing. +use std::cell::RefCell; use std::rc::Rc; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; @@ -6,16 +7,47 @@ use actix_http::http::{HttpTryFrom, Method, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{Extensions, PayloadStream, Request}; use actix_router::{Path, Url}; +use actix_rt::Runtime; use bytes::Bytes; +use futures::Future; use crate::request::HttpRequest; use crate::service::{ServiceFromRequest, ServiceRequest}; -/// Test `Request` builder +thread_local! { + static RT: RefCell = { + RefCell::new(Runtime::new().unwrap()) + }; +} + +/// Runs the provided future, blocking the current thread until the future +/// completes. +/// +/// This function can be used to synchronously block the current thread +/// until the provided `future` has resolved either successfully or with an +/// error. The result of the future is then returned from this function +/// call. +/// +/// Note that this function is intended to be used only for testing purpose. +/// This function panics on nested call. +pub fn block_on(f: F) -> Result +where + F: Future, +{ + RT.with(move |rt| rt.borrow_mut().block_on(f)) +} + +/// Test `Request` builder. +/// +/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. +/// You can generate various types of request via TestRequest's methods: +/// * `TestRequest::to_request` creates `actix_http::Request` instance. +/// * `TestRequest::to_service` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. +/// * `TestRequest::to_from` creates `ServiceFromRequest` instance, which is used for testing extractors. +/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// /// ```rust,ignore -/// # use actix_web::*; -/// use actix_web::test::TestRequest; +/// use actix_web::test; /// /// fn index(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { @@ -26,12 +58,14 @@ use crate::service::{ServiceFromRequest, ServiceRequest}; /// } /// /// fn main() { -/// let resp = TestRequest::with_header("content-type", "text/plain") -/// .run(&index) -/// .unwrap(); +/// let req = test::TestRequest::with_header("content-type", "text/plain") +/// .to_http_request(); +/// +/// let resp = test::block_on(index(req)); /// assert_eq!(resp.status(), StatusCode::OK); /// -/// let resp = TestRequest::default().run(&index).unwrap(); +/// let req = test::TestRequest::default().to_http_request(); +/// let resp = test::block_on(index(req)); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` @@ -125,7 +159,7 @@ impl TestRequest { } /// Complete request creation and generate `ServiceRequest` instance - pub fn finish(mut self) -> ServiceRequest { + pub fn to_service(mut self) -> ServiceRequest { let req = self.req.finish(); ServiceRequest::new( @@ -163,4 +197,21 @@ impl TestRequest { ); ServiceFromRequest::new(req, None) } + + /// Runs the provided future, blocking the current thread until the future + /// completes. + /// + /// This function can be used to synchronously block the current thread + /// until the provided `future` has resolved either successfully or with an + /// error. The result of the future is then returned from this function + /// call. + /// + /// Note that this function is intended to be used only for testing purpose. + /// This function panics on nested call. + pub fn block_on(f: F) -> Result + where + F: Future, + { + block_on(f) + } } From a88b3b090d4bea412b48cc6be6713ebc87cf0b8b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 15:58:39 -0800 Subject: [PATCH 0967/1635] allow to specify service config for h1 service --- src/config.rs | 4 ++-- src/h1/service.rs | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 960f1370..a9e705c9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -65,7 +65,7 @@ impl Default for ServiceConfig { impl ServiceConfig { /// Create instance of `ServiceConfig` - pub(crate) fn new( + pub fn new( keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, @@ -282,7 +282,7 @@ impl ServiceConfigBuilder { } /// Finish service configuration and create `ServiceConfig` object. - pub fn finish(self) -> ServiceConfig { + pub fn finish(&mut self) -> ServiceConfig { ServiceConfig::new(self.keep_alive, self.client_timeout, self.client_disconnect) } } diff --git a/src/h1/service.rs b/src/h1/service.rs index 4beb4c9e..4c1fb9a8 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -34,7 +34,7 @@ where S::Service: 'static, B: MessageBody, { - /// Create new `HttpService` instance. + /// Create new `HttpService` instance with default config. pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); @@ -45,6 +45,15 @@ where } } + /// Create new `HttpService` instance with config. + pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { + H1Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + /// Create builder for `HttpService` instance. pub fn build() -> H1ServiceBuilder { H1ServiceBuilder::new() From 2e79562c9d2d16a376e321ff42e662cdc905d0c4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 16:29:03 -0800 Subject: [PATCH 0968/1635] add HttpServer type --- Cargo.toml | 20 +- examples/basic.rs | 54 ++--- src/lib.rs | 2 + src/server.rs | 521 +++++++++++++++++++++++++++++++++++++++++ staticfiles/Cargo.toml | 3 +- 5 files changed, 569 insertions(+), 31 deletions(-) create mode 100644 src/server.rs diff --git a/Cargo.toml b/Cargo.toml index 3b88c300..1bc3af3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,9 @@ members = [ "staticfiles", ] +[package.metadata.docs.rs] +features = ["ssl", "tls", "rust-tls"] + [features] default = ["brotli", "flate2-c"] @@ -42,6 +45,15 @@ flate2-c = ["flate2/miniz-sys"] # rust backend for flate2 crate flate2-rust = ["flate2/rust_backend"] +# tls +tls = ["native-tls", "actix-server/ssl"] + +# openssl +ssl = ["openssl", "actix-server/ssl"] + +# rustls +# rust-tls = ["rustls", "actix-server/rustls"] + [dependencies] actix-codec = "0.1.0" actix-service = "0.3.0" @@ -50,6 +62,7 @@ actix-rt = "0.1.0" actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } +actix-server = { git = "https://github.com/actix/actix-net.git" } bytes = "0.4" derive_more = "0.14" @@ -58,6 +71,7 @@ futures = "0.1" log = "0.4" lazy_static = "1.2" mime = "0.3" +net2 = "0.2.33" num_cpus = "1.10" parking_lot = "0.7" serde = "1.0" @@ -69,8 +83,12 @@ threadpool = "1.7" brotli2 = { version="^0.3.2", optional = true } flate2 = { version="^1.0.2", optional = true, default-features = false } +# ssl support +native-tls = { version="0.2", optional = true } +openssl = { version="0.10", optional = true } +# rustls = { version = "^0.15", optional = true } + [dev-dependencies] -actix-server = { version="0.3.0", features=["ssl"] } actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } rand = "0.6" diff --git a/examples/basic.rs b/examples/basic.rs index 886efb7b..3cb07959 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,8 +1,9 @@ use futures::IntoFuture; -use actix_http::{h1, http::Method, Response}; -use actix_server::Server; -use actix_web::{middleware, web, App, Error, HttpRequest, Resource, Route}; +use actix_web::{ + http::Method, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, + Resource, Route, +}; fn index(req: HttpRequest) -> &'static str { println!("REQ: {:?}", req); @@ -18,39 +19,34 @@ fn no_params() -> &'static str { "Hello world!\r\n" } -fn main() { +fn main() -> std::io::Result<()> { ::std::env::set_var("RUST_LOG", "actix_server=info,actix_web2=info"); env_logger::init(); let sys = actix_rt::System::new("hello-world"); - Server::build() - .bind("test", "127.0.0.1:8080", || { - h1::H1Service::new( - App::new() + HttpServer::new(|| { + App::new() + .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) + .middleware(middleware::Compress::default()) + .resource("/resource1/index.html", |r| r.route(web::get().to(index))) + .service( + "/resource2/index.html", + Resource::new() .middleware( - middleware::DefaultHeaders::new().header("X-Version", "0.2"), + middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), ) - .middleware(middleware::Compress::default()) - .resource("/resource1/index.html", |r| r.route(web::get().to(index))) - .service( - "/resource2/index.html", - Resource::new() - .middleware( - middleware::DefaultHeaders::new() - .header("X-Version-R2", "0.3"), - ) - .default_resource(|r| { - r.route(Route::new().to(|| Response::MethodNotAllowed())) - }) - .route(web::method(Method::GET).to_async(index_async)), - ) - .service("/test1.html", Resource::new().to(|| "Test\r\n")) - .service("/", Resource::new().to(no_params)), + .default_resource(|r| { + r.route(Route::new().to(|| HttpResponse::MethodNotAllowed())) + }) + .route(web::method(Method::GET).to_async(index_async)), ) - }) - .unwrap() - .workers(1) - .start(); + .service("/test1.html", Resource::new().to(|| "Test\r\n")) + .service("/", Resource::new().to(no_params)) + }) + .bind("127.0.0.1:8080")? + .workers(1) + .start(); let _ = sys.run(); + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 18a6de06..f21c5e43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ mod resource; mod responder; mod route; mod scope; +mod server; mod service; mod state; pub mod test; @@ -27,6 +28,7 @@ pub use crate::resource::Resource; pub use crate::responder::{Either, Responder}; pub use crate::route::Route; pub use crate::scope::Scope; +pub use crate::server::HttpServer; pub use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; pub use crate::state::State; diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 00000000..95cab2b0 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,521 @@ +use std::marker::PhantomData; +use std::sync::Arc; +use std::{fmt, io, net}; + +use actix_http::{body::MessageBody, h1, KeepAlive, Request, Response, ServiceConfig}; +use actix_rt::System; +use actix_server::{Server, ServerBuilder}; +use actix_service::{IntoNewService, NewService}; +use parking_lot::Mutex; + +use net2::TcpBuilder; + +#[cfg(feature = "tls")] +use native_tls::TlsAcceptor; + +#[cfg(feature = "ssl")] +use openssl::ssl::{SslAcceptor, SslAcceptorBuilder}; + +struct Socket { + scheme: &'static str, + addr: net::SocketAddr, +} + +struct Config { + keep_alive: KeepAlive, + client_timeout: u64, + client_shutdown: u64, +} + +/// An HTTP Server. +/// +/// Create new http server with application factory. +/// +/// ```rust +/// use std::io; +/// use actix_web::{App, HttpResponse, HttpServer}; +/// +/// fn main() -> io::Result<()> { +/// let sys = actix_rt::System::new("example"); // <- create Actix runtime +/// +/// HttpServer::new( +/// || App::new() +/// .resource("/", |r| r.to(|| HttpResponse::Ok()))) +/// .bind("127.0.0.1:59090")? +/// .start(); +/// +/// # actix_rt::System::current().stop(); +/// sys.run(); +/// Ok(()) +/// } +/// ``` +pub struct HttpServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoNewService, + S: NewService, + S::Error: fmt::Debug, + S::Response: Into>, + S::Service: 'static, + B: MessageBody, +{ + pub(super) factory: F, + pub(super) host: Option, + config: Arc>, + backlog: i32, + sockets: Vec, + builder: Option, + _t: PhantomData<(S, B)>, +} + +impl HttpServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoNewService, + S: NewService, + S::Error: fmt::Debug, + S::Response: Into>, + S::Service: 'static, + B: MessageBody, +{ + /// Create new http server with application factory + pub fn new(factory: F) -> Self { + HttpServer { + factory, + host: None, + backlog: 2048, + config: Arc::new(Mutex::new(Config { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_shutdown: 5000, + })), + sockets: Vec::new(), + builder: Some(ServerBuilder::default()), + _t: PhantomData, + } + } + + /// Set number of workers to start. + /// + /// By default http server uses number of available logical cpu as threads + /// count. + pub fn workers(mut self, num: usize) -> Self { + self.builder = Some(self.builder.take().unwrap().workers(num)); + 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. + /// + /// This method should be called before `bind()` method call. + pub fn backlog(mut self, num: i32) -> Self { + self.backlog = num; + self + } + + /// Sets the maximum per-worker number of concurrent connections. + /// + /// All socket listeners will stop accepting connections when this limit is reached + /// for each worker. + /// + /// By default max connections is set to a 25k. + pub fn maxconn(mut self, num: usize) -> Self { + self.builder = Some(self.builder.take().unwrap().maxconn(num)); + self + } + + /// Sets the maximum per-worker concurrent connection establish process. + /// + /// All listeners will stop accepting connections when this limit is reached. It + /// can be used to limit the global SSL CPU usage. + /// + /// By default max connections is set to a 256. + pub fn maxconnrate(mut self, num: usize) -> Self { + self.builder = Some(self.builder.take().unwrap().maxconnrate(num)); + self + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(self, val: T) -> Self { + self.config.lock().keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(self, val: u64) -> Self { + self.config.lock().client_timeout = val; + self + } + + /// Set server connection shutdown timeout in milliseconds. + /// + /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete + /// within this time, the request is dropped. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_shutdown(self, val: u64) -> Self { + self.config.lock().client_shutdown = val; + 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 + } + + /// Stop actix system. + pub fn system_exit(mut self) -> Self { + self.builder = Some(self.builder.take().unwrap().system_exit()); + self + } + + /// Disable signal handling + pub fn disable_signals(mut self) -> Self { + self.builder = Some(self.builder.take().unwrap().disable_signals()); + 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.builder = Some(self.builder.take().unwrap().shutdown_timeout(sec)); + self + } + + /// Get addresses of bound sockets. + pub fn addrs(&self) -> Vec { + self.sockets.iter().map(|s| s.addr).collect() + } + + /// Get addresses of bound sockets and the scheme for it. + /// + /// This is useful when the server is bound from different sources + /// with some sockets listening on http and some listening on https + /// and the user should be presented with an enumeration of which + /// socket requires which protocol. + pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { + self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() + } + + /// Use listener for accepting incoming connection requests + /// + /// HttpServer does not change any configuration for TcpListener, + /// it needs to be configured before passing it to listen() method. + pub fn listen(mut self, lst: net::TcpListener) -> Self { + let cfg = self.config.clone(); + let factory = self.factory.clone(); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + scheme: "http", + }); + + self.builder = Some(self.builder.take().unwrap().listen( + format!("actix-web-service-{}", addr), + lst, + move || { + let c = cfg.lock(); + let service_config = + ServiceConfig::new(c.keep_alive, c.client_timeout, 0); + h1::H1Service::with_config(service_config, factory()) + }, + )); + + self + } + + #[cfg(feature = "tls")] + /// Use listener for accepting incoming tls connection requests + /// + /// HttpServer does not change any configuration for TcpListener, + /// it needs to be configured before passing it to listen() method. + pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { + use actix_net::service::NewServiceExt; + + self.listen_with(lst, move || { + ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) + }) + } + + #[cfg(feature = "ssl")] + /// Use listener for accepting incoming tls connection requests + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn listen_ssl( + mut self, + lst: net::TcpListener, + builder: SslAcceptorBuilder, + ) -> io::Result { + self.listen_ssl_inner(lst, openssl_acceptor(builder)?); + Ok(self) + } + + #[cfg(feature = "ssl")] + fn listen_ssl_inner(&mut self, lst: net::TcpListener, acceptor: SslAcceptor) { + use actix_server::ssl::{OpensslAcceptor, SslError}; + + let acceptor = OpensslAcceptor::new(acceptor); + let factory = self.factory.clone(); + let cfg = self.config.clone(); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + scheme: "http", + }); + + self.builder = Some(self.builder.take().unwrap().listen( + format!("actix-web-service-{}", addr), + lst, + move || { + let c = cfg.lock(); + let service_config = + ServiceConfig::new(c.keep_alive, c.client_timeout, c.client_timeout); + acceptor.clone().map_err(|e| SslError::Ssl(e)).and_then( + h1::H1Service::with_config(service_config, factory()) + .map_err(|e| SslError::Service(e)) + .map_init_err(|_| ()), + ) + }, + )); + } + + #[cfg(feature = "rust-tls")] + /// Use listener for accepting incoming tls connection requests + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self { + use super::{RustlsAcceptor, ServerFlags}; + use actix_net::service::NewServiceExt; + + self.listen_with(lst, move || { + RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) + }) + } + + /// The socket address to bind + /// + /// To bind multiple addresses this method can be called multiple times. + pub fn bind(mut self, addr: A) -> io::Result { + let sockets = self.bind2(addr)?; + + for lst in sockets { + self = self.listen(lst); + } + + Ok(self) + } + + fn bind2( + &self, + addr: A, + ) -> io::Result> { + let mut err = None; + let mut succ = false; + let mut sockets = Vec::new(); + for addr in addr.to_socket_addrs()? { + match create_tcp_listener(addr, self.backlog) { + Ok(lst) => { + succ = true; + sockets.push(lst); + } + Err(e) => err = Some(e), + } + } + + 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) + } + } + + #[cfg(feature = "tls")] + /// The ssl socket address to bind + /// + /// To bind multiple addresses this method can be called multiple times. + pub fn bind_tls( + self, + addr: A, + acceptor: TlsAcceptor, + ) -> io::Result { + use actix_net::service::NewServiceExt; + use actix_net::ssl::NativeTlsAcceptor; + + self.bind_with(addr, move || { + NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) + }) + } + + #[cfg(feature = "ssl")] + /// Start listening for incoming tls connections. + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn bind_ssl( + mut self, + addr: A, + builder: SslAcceptorBuilder, + ) -> io::Result + where + A: net::ToSocketAddrs, + { + let sockets = self.bind2(addr)?; + let acceptor = openssl_acceptor(builder)?; + + for lst in sockets { + self.listen_ssl_inner(lst, acceptor.clone()); + } + + Ok(self) + } + + #[cfg(feature = "rust-tls")] + /// Start listening for incoming tls connections. + /// + /// This method sets alpn protocols to "h2" and "http/1.1" + pub fn bind_rustls( + self, + addr: A, + builder: ServerConfig, + ) -> io::Result { + use super::{RustlsAcceptor, ServerFlags}; + use actix_net::service::NewServiceExt; + + // alpn support + let flags = if self.no_http2 { + ServerFlags::HTTP1 + } else { + ServerFlags::HTTP1 | ServerFlags::HTTP2 + }; + + self.bind_with(addr, move || { + RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ()) + }) + } +} + +impl HttpServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoNewService, + S: NewService, + S::Error: fmt::Debug, + S::Response: Into>, + S::Service: 'static, + B: MessageBody, +{ + /// Start listening for incoming connections. + /// + /// This method starts number of http workers in separate threads. + /// For each address this method starts separate thread which does + /// `accept()` in a loop. + /// + /// This methods panics if no socket address can be bound or an `Actix` system is not yet + /// configured. + /// + /// ```rust + /// use actix_web::{App, HttpResponse, HttpServer}; + /// + /// fn main() { + /// let sys = actix_rt::System::new("example"); // <- create Actix system + /// + /// HttpServer::new(|| App::new().resource("/", |r| r.to(|| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0") + /// .expect("Can not bind to 127.0.0.1:0") + /// .start(); + /// # actix_rt::System::current().stop(); + /// sys.run(); // <- Run actix system, this method starts all async processes + /// } + /// ``` + pub fn start(mut self) -> Server { + self.builder.take().unwrap().start() + } + + /// Spawn new thread and start listening for incoming connections. + /// + /// This method spawns new thread and starts new actix system. Other than + /// that it is similar to `start()` method. This method blocks. + /// + /// This methods panics if no socket addresses get bound. + /// + /// ```rust,ignore + /// use actix_web::{App, HttpResponse, HttpServer}; + /// + /// fn main() { + /// HttpServer::new(|| App::new().resource("/", |r| r.to(|| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0") + /// .expect("Can not bind to 127.0.0.1:0") + /// .run(); + /// } + /// ``` + pub fn run(self) { + let sys = System::new("http-server"); + self.start(); + sys.run(); + } +} + +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.reuse_address(true)?; + builder.bind(addr)?; + Ok(builder.listen(backlog)?) +} + +#[cfg(feature = "ssl")] +/// Configure `SslAcceptorBuilder` with custom server flags. +fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result { + use openssl::ssl::AlpnError; + + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x08http/1.1\x02h2")?; + + Ok(builder.build()) +} diff --git a/staticfiles/Cargo.toml b/staticfiles/Cargo.toml index c2516302..0aa58970 100644 --- a/staticfiles/Cargo.toml +++ b/staticfiles/Cargo.toml @@ -20,7 +20,7 @@ path = "src/lib.rs" [dependencies] actix-web = { path=".." } actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-service = { git = "https://github.com/actix/actix-net.git" } +actix-service = "0.3.0" bytes = "0.4" futures = "0.1" @@ -34,6 +34,7 @@ v_htmlescape = "0.4" [dev-dependencies] actix-rt = "0.1.0" #actix-server = { version="0.2", features=["ssl"] } +actix-web = { path="..", features=["ssl"] } actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] } actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } From 65a313c78b16272bf7918459111fdb1117ad2ca2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 19:51:09 -0800 Subject: [PATCH 0969/1635] update utils dep --- Cargo.toml | 4 ++-- src/client/connector.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 403f3303..35342b8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,10 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.3.0" +actix-service = "0.3.1" actix-codec = "0.1.0" actix-connector = "0.3.0" -actix-utils = "0.3.0" +actix-utils = "0.3.1" base64 = "0.10" backtrace = "0.3" diff --git a/src/client/connector.rs b/src/client/connector.rs index 32ba5012..ccb5dbce 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -2,7 +2,7 @@ use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connector::{Resolver, TcpConnector}; -use actix_service::{Apply, Service, ServiceExt}; +use actix_service::{Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; @@ -140,8 +140,8 @@ impl Connector { > + Clone { #[cfg(not(feature = "ssl"))] { - let connector = Apply::new( - TimeoutService::new(self.timeout), + let connector = TimeoutService::new( + self.timeout, self.resolver.map_err(ConnectorError::from).and_then( TcpConnector::default() .from_err() @@ -168,8 +168,8 @@ impl Connector { const H2: &[u8] = b"h2"; use actix_connector::ssl::OpensslConnector; - let ssl_service = Apply::new( - TimeoutService::new(self.timeout), + let ssl_service = TimeoutService::new( + self.timeout, self.resolver .clone() .map_err(ConnectorError::from) @@ -197,8 +197,8 @@ impl Connector { TimeoutError::Timeout => ConnectorError::Timeout, }); - let tcp_service = Apply::new( - TimeoutService::new(self.timeout), + let tcp_service = TimeoutService::new( + self.timeout, self.resolver.map_err(ConnectorError::from).and_then( TcpConnector::default() .from_err() From 3a456ec1489cc83a5a5bee74071d7139be44b8f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 20:46:33 -0800 Subject: [PATCH 0970/1635] update actix-service dependency --- Cargo.toml | 5 +++-- src/h1/service.rs | 6 +++--- src/h2/service.rs | 6 +++--- test-server/Cargo.toml | 7 ++++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35342b8e..86a9c29c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.3.1" +actix-service = "0.3.2" actix-codec = "0.1.0" actix-connector = "0.3.0" actix-utils = "0.3.1" @@ -78,7 +78,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.1.0" -actix-server = { version = "0.3.0", features=["ssl"] } +#actix-server = { version = "0.3.0", features=["ssl"] } +actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } actix-connector = { version = "0.3.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" diff --git a/src/h1/service.rs b/src/h1/service.rs index 4c1fb9a8..acc7217b 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -6,7 +6,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; -use futures::{try_ready, Async, Future, Poll, Stream}; +use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use log::error; use crate::body::MessageBody; @@ -78,7 +78,7 @@ where fn new_service(&self, _: &()) -> Self::Future { H1ServiceResponse { - fut: self.srv.new_service(&()), + fut: self.srv.new_service(&()).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -216,7 +216,7 @@ where #[doc(hidden)] pub struct H1ServiceResponse { - fut: S::Future, + fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, } diff --git a/src/h2/service.rs b/src/h2/service.rs index fcfc0be2..583f5edd 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -7,7 +7,7 @@ use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::Bytes; use futures::future::{ok, FutureResult}; -use futures::{try_ready, Async, Future, Poll, Stream}; +use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use h2::server::{self, Connection, Handshake}; use h2::RecvStream; use log::error; @@ -72,7 +72,7 @@ where fn new_service(&self, _: &()) -> Self::Future { H2ServiceResponse { - fut: self.srv.new_service(&()), + fut: self.srv.new_service(&()).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -230,7 +230,7 @@ where #[doc(hidden)] pub struct H2ServiceResponse { - fut: S::Future, + fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index cf4364c4..df56b8f3 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -35,9 +35,10 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] actix-codec = "0.1" actix-rt = "0.1.0" actix-http = { path=".." } -actix-service = "0.3.0" -actix-server = "0.3.0" -actix-utils = "0.3.0" +actix-service = "0.3.2" +#actix-server = "0.3.0" +actix-server = { git="https://github.com/actix/actix-net.git" } +actix-utils = "0.3.2" base64 = "0.10" bytes = "0.4" From 42f030d3f49555a90e0d0be83e1e949a17d6f6ac Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 2 Mar 2019 12:55:31 +0300 Subject: [PATCH 0971/1635] Ensure that Content-Length zero is specified in empty request --- CHANGES.md | 2 + src/client/writer.rs | 91 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d1d838f4..1a326028 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ * Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680 +* Do not remove `Content-Length` on `Body::Empty` and insert zero value if it is missing for `POST` and `PUT` methods. + ## [0.7.18] - 2019-01-10 ### Added diff --git a/src/client/writer.rs b/src/client/writer.rs index 321753bb..4091ed21 100644 --- a/src/client/writer.rs +++ b/src/client/writer.rs @@ -16,9 +16,9 @@ use flate2::write::{GzEncoder, ZlibEncoder}; use flate2::Compression; use futures::{Async, Poll}; use http::header::{ - HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, + self, HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, }; -use http::{HttpTryFrom, Version}; +use http::{Method, HttpTryFrom, Version}; use time::{self, Duration}; use tokio_io::AsyncWrite; @@ -223,7 +223,19 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output { let transfer = match body { Body::Empty => { - req.headers_mut().remove(CONTENT_LENGTH); + match req.method() { + //Insert zero content-length only if user hasn't added it. + //We don't really need it for other methods as they are not supposed to carry payload + &Method::POST | &Method::PUT | &Method::PATCH => { + req.headers_mut() + .entry(CONTENT_LENGTH) + .expect("CONTENT_LENGTH to be valid header name") + .or_insert(header::HeaderValue::from_static("0")); + }, + _ => { + req.headers_mut().remove(CONTENT_LENGTH); + } + } return Output::Empty(buf); } Body::Binary(ref mut bytes) => { @@ -410,3 +422,76 @@ impl CachedDate { self.next_update.nsec = 0; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_content_encoder_empty_body() { + let mut req = ClientRequest::post("http://google.com").finish().expect("Create request"); + + let result = content_encoder(BytesMut::new(), &mut req); + + match result { + Output::Empty(buf) => { + assert_eq!(buf.len(), 0); + let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty POST"); + assert_eq!(content_len, "0"); + }, + _ => panic!("Unexpected result, should be Output::Empty"), + } + + req.set_method(Method::GET); + + let result = content_encoder(BytesMut::new(), &mut req); + + match result { + Output::Empty(buf) => { + assert_eq!(buf.len(), 0); + assert!(!req.headers().contains_key(CONTENT_LENGTH)); + }, + _ => panic!("Unexpected result, should be Output::Empty"), + } + + req.set_method(Method::PUT); + + let result = content_encoder(BytesMut::new(), &mut req); + + match result { + Output::Empty(buf) => { + assert_eq!(buf.len(), 0); + let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty PUT"); + assert_eq!(content_len, "0"); + }, + _ => panic!("Unexpected result, should be Output::Empty"), + } + + req.set_method(Method::DELETE); + + let result = content_encoder(BytesMut::new(), &mut req); + + match result { + Output::Empty(buf) => { + assert_eq!(buf.len(), 0); + assert!(!req.headers().contains_key(CONTENT_LENGTH)); + }, + _ => panic!("Unexpected result, should be Output::Empty"), + } + + req.set_method(Method::PATCH); + + let result = content_encoder(BytesMut::new(), &mut req); + + match result { + Output::Empty(buf) => { + assert_eq!(buf.len(), 0); + let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty PATCH"); + assert_eq!(content_len, "0"); + }, + _ => panic!("Unexpected result, should be Output::Empty"), + } + + + } +} From b6fe1dacf2c8f547138cbef91c3b8fc4330429c6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 4 Mar 2019 21:37:57 -0800 Subject: [PATCH 0972/1635] update middleware impl --- Cargo.toml | 7 +++- src/app.rs | 16 ++++---- src/middleware/compress.rs | 51 +++++++++++++++-------- src/middleware/defaultheaders.rs | 70 ++++++++++++++++++++++---------- src/middleware/mod.rs | 45 -------------------- src/resource.rs | 8 ++-- src/scope.rs | 8 ++-- 7 files changed, 102 insertions(+), 103 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1bc3af3b..0b4ad38a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,8 @@ ssl = ["openssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1.0" -actix-service = "0.3.0" -actix-utils = "0.3.0" +actix-service = "0.3.2" +actix-utils = "0.3.1" actix-rt = "0.1.0" actix-http = { git = "https://github.com/actix/actix-http.git" } @@ -99,3 +99,6 @@ serde_derive = "1.0" lto = true opt-level = 3 codegen-units = 1 + +[patch.crates-io] +actix-service = { git = "https://github.com/actix/actix-net.git" } diff --git a/src/app.rs b/src/app.rs index e1479080..8336fcca 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,8 +7,8 @@ use actix_http::{Extensions, PayloadStream, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - AndThenNewService, ApplyNewService, IntoNewService, IntoNewTransform, NewService, - NewTransform, Service, + AndThenNewService, ApplyTransform, IntoNewService, IntoTransform, NewService, + Service, Transform, }; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; @@ -237,17 +237,17 @@ where >, > where - M: NewTransform< + M: Transform< AppRouting

    , Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoNewTransform>, + F: IntoTransform>, { let fref = Rc::new(RefCell::new(None)); - let endpoint = ApplyNewService::new(mw, AppEntry::new(fref.clone())); + let endpoint = ApplyTransform::new(mw, AppEntry::new(fref.clone())); AppRouter { endpoint, chain: self.chain, @@ -480,7 +480,7 @@ where >, > where - M: NewTransform< + M: Transform< T::Service, Request = ServiceRequest

    , Response = ServiceResponse, @@ -488,9 +488,9 @@ where InitError = (), >, B1: MessageBody, - F: IntoNewTransform, + F: IntoTransform, { - let endpoint = ApplyNewService::new(mw, self.endpoint); + let endpoint = ApplyTransform::new(mw, self.endpoint); AppRouter { endpoint, chain: self.chain, diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 5d5586cf..b95553cb 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -8,8 +8,9 @@ use actix_http::http::header::{ }; use actix_http::http::{HttpTryFrom, StatusCode}; use actix_http::{Error, Head, ResponseHead}; -use actix_service::{IntoNewTransform, Service, Transform}; +use actix_service::{Service, Transform}; use bytes::{Bytes, BytesMut}; +use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; use log::trace; @@ -18,7 +19,6 @@ use brotli2::write::BrotliEncoder; #[cfg(feature = "flate2")] use flate2::write::{GzEncoder, ZlibEncoder}; -use crate::middleware::MiddlewareFactory; use crate::service::{ServiceRequest, ServiceResponse}; #[derive(Debug, Clone)] @@ -37,6 +37,33 @@ impl Default for Compress { } impl Transform for Compress +where + P: 'static, + B: MessageBody, + S: Service, Response = ServiceResponse>, + S::Future: 'static, +{ + type Request = ServiceRequest

    ; + type Response = ServiceResponse>; + type Error = S::Error; + type InitError = (); + type Transform = CompressMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(CompressMiddleware { + service, + encoding: self.0, + }) + } +} + +pub struct CompressMiddleware { + service: S, + encoding: ContentEncoding, +} + +impl Service for CompressMiddleware where P: 'static, B: MessageBody, @@ -49,14 +76,14 @@ where type Future = CompressResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + self.service.poll_ready() } - fn call(&mut self, req: ServiceRequest

    , srv: &mut S) -> Self::Future { + fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { // negotiate content-encoding let encoding = if let Some(val) = req.headers.get(ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { - AcceptEncoding::parse(enc, self.0) + AcceptEncoding::parse(enc, self.encoding) } else { ContentEncoding::Identity } @@ -66,7 +93,7 @@ where CompressResponse { encoding, - fut: srv.call(req), + fut: self.service.call(req), } } } @@ -102,18 +129,6 @@ where } } -impl IntoNewTransform, S> for Compress -where - P: 'static, - B: MessageBody, - S: Service, Response = ServiceResponse>, - S::Future: 'static, -{ - fn into_new_transform(self) -> MiddlewareFactory { - MiddlewareFactory::new(self) - } -} - enum EncoderBody { Body(B), Other(Box), diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 40bf9f1c..2bd1d5d4 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -3,10 +3,10 @@ use std::rc::Rc; use actix_http::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use actix_http::http::{HeaderMap, HttpTryFrom}; -use actix_service::{IntoNewTransform, Service, Transform}; -use futures::{Async, Future, Poll}; +use actix_service::{Service, Transform}; +use futures::future::{ok, FutureResult}; +use futures::{Future, Poll}; -use crate::middleware::MiddlewareFactory; use crate::service::{ServiceRequest, ServiceResponse}; /// `Middleware` for setting default response headers. @@ -84,35 +84,49 @@ impl DefaultHeaders { } } -impl IntoNewTransform, S> - for DefaultHeaders +impl Transform for DefaultHeaders where - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { - fn into_new_transform(self) -> MiddlewareFactory { - MiddlewareFactory::new(self) + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = S::Error; + type InitError = (); + type Transform = DefaultHeadersMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(DefaultHeadersMiddleware { + service, + inner: self.inner.clone(), + }) } } -impl Transform for DefaultHeaders +pub struct DefaultHeadersMiddleware { + service: S, + inner: Rc, +} + +impl Service for DefaultHeadersMiddleware where - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { - type Request = ServiceRequest; + type Request = ServiceRequest

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

    ) -> Self::Future { let inner = self.inner.clone(); - Box::new(srv.call(req).map(move |mut res| { + Box::new(self.service.call(req).map(move |mut res| { // set response headers for (key, value) in inner.headers.iter() { if !res.headers().contains_key(key) { @@ -143,32 +157,44 @@ mod tests { #[test] fn test_default_headers() { - let mut mw = DefaultHeaders::new().header(CONTENT_TYPE, "0001"); - let mut srv = FnService::new(|req: ServiceRequest<_>| { + let srv = FnService::new(|req: ServiceRequest<_>| { req.into_response(HttpResponse::Ok().finish()) }); + let mut mw = block_on( + DefaultHeaders::new() + .header(CONTENT_TYPE, "0001") + .new_transform(srv), + ) + .unwrap(); let req = TestRequest::default().to_service(); - let resp = block_on(mw.call(req, &mut srv)).unwrap(); + let resp = block_on(mw.call(req)).unwrap(); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); let req = TestRequest::default().to_service(); - let mut srv = FnService::new(|req: ServiceRequest<_>| { + let srv = FnService::new(|req: ServiceRequest<_>| { req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) }); - let resp = block_on(mw.call(req, &mut srv)).unwrap(); + let mut mw = block_on( + DefaultHeaders::new() + .header(CONTENT_TYPE, "0001") + .new_transform(srv), + ) + .unwrap(); + let resp = block_on(mw.call(req)).unwrap(); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); } #[test] fn test_content_type() { - let mut mw = DefaultHeaders::new().content_type(); - let mut srv = FnService::new(|req: ServiceRequest<_>| { + let srv = FnService::new(|req: ServiceRequest<_>| { req.into_response(HttpResponse::Ok().finish()) }); + let mut mw = + block_on(DefaultHeaders::new().content_type().new_transform(srv)).unwrap(); let req = TestRequest::default().to_service(); - let resp = block_on(mw.call(req, &mut srv)).unwrap(); + let resp = block_on(mw.call(req)).unwrap(); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), "application/octet-stream" diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 85127ee2..fc992302 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,8 +1,3 @@ -use std::marker::PhantomData; - -use actix_service::{NewTransform, Service, Transform}; -use futures::future::{ok, FutureResult}; - #[cfg(any(feature = "brotli", feature = "flate2"))] mod compress; #[cfg(any(feature = "brotli", feature = "flate2"))] @@ -10,43 +5,3 @@ pub use self::compress::Compress; mod defaultheaders; pub use self::defaultheaders::DefaultHeaders; - -/// Helper for middleware service factory -pub struct MiddlewareFactory -where - T: Transform + Clone, - S: Service, -{ - tr: T, - _t: PhantomData, -} - -impl MiddlewareFactory -where - T: Transform + Clone, - S: Service, -{ - pub fn new(tr: T) -> Self { - MiddlewareFactory { - tr, - _t: PhantomData, - } - } -} - -impl NewTransform for MiddlewareFactory -where - T: Transform + Clone, - S: Service, -{ - type Request = T::Request; - type Response = T::Response; - type Error = T::Error; - type Transform = T; - type InitError = (); - type Future = FutureResult; - - fn new_transform(&self, _: &C) -> Self::Future { - ok(self.tr.clone()) - } -} diff --git a/src/resource.rs b/src/resource.rs index 342d801d..755b8d07 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use actix_http::{Error, Response}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - ApplyNewService, IntoNewService, IntoNewTransform, NewService, NewTransform, Service, + ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform, }; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; @@ -194,16 +194,16 @@ where >, > where - M: NewTransform< + M: Transform< T::Service, Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoNewTransform, + F: IntoTransform, { - let endpoint = ApplyNewService::new(mw, self.endpoint); + let endpoint = ApplyTransform::new(mw, self.endpoint); Resource { endpoint, routes: self.routes, diff --git a/src/scope.rs b/src/scope.rs index 7aeb5041..b255ac14 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -5,7 +5,7 @@ use actix_http::Response; use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - ApplyNewService, IntoNewService, IntoNewTransform, NewService, NewTransform, Service, + ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform, }; use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, Poll}; @@ -251,16 +251,16 @@ where >, > where - M: NewTransform< + M: Transform< T::Service, Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoNewTransform, + F: IntoTransform, { - let endpoint = ApplyNewService::new(mw, self.endpoint); + let endpoint = ApplyTransform::new(mw, self.endpoint); Scope { endpoint, rdef: self.rdef, From ce0b17259858a88ef7cec46f29053c617701c896 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 09:30:11 -0800 Subject: [PATCH 0973/1635] update actix-service --- Cargo.toml | 13 +++++++---- src/client/connector.rs | 50 ++++++++++++---------------------------- src/client/pool.rs | 15 +++--------- src/client/request.rs | 2 +- src/h1/dispatcher.rs | 14 +++++------ src/h1/service.rs | 35 ++++++++++++++-------------- src/h2/dispatcher.rs | 18 +++++++++------ src/h2/service.rs | 34 ++++++++++++++------------- src/service/senderror.rs | 12 ++++------ src/ws/client/service.rs | 11 ++++----- src/ws/service.rs | 6 ++--- src/ws/transport.rs | 10 ++++---- test-server/Cargo.toml | 6 +++-- test-server/src/lib.rs | 12 ++++------ 14 files changed, 105 insertions(+), 133 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 86a9c29c..1705479b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,14 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-connector/ssl"] [dependencies] -actix-service = "0.3.2" +#actix-service = "0.3.2" actix-codec = "0.1.0" -actix-connector = "0.3.0" -actix-utils = "0.3.1" +#actix-connector = "0.3.0" +#actix-utils = "0.3.1" + +actix-connector = { git="https://github.com/actix/actix-net.git" } +actix-service = { git="https://github.com/actix/actix-net.git" } +actix-utils = { git="https://github.com/actix/actix-net.git" } base64 = "0.10" backtrace = "0.3" @@ -80,7 +84,8 @@ openssl = { version="0.10", optional = true } actix-rt = "0.1.0" #actix-server = { version = "0.3.0", features=["ssl"] } actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } -actix-connector = { version = "0.3.0", features=["ssl"] } +#actix-connector = { version = "0.3.0", features=["ssl"] } +actix-connector = { git="https://github.com/actix/actix-net.git", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/src/client/connector.rs b/src/client/connector.rs index ccb5dbce..b35e6af9 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -133,11 +133,8 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { + ) -> impl Service + Clone + { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( @@ -314,12 +311,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Request = Connect, + Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Request = Connect, + Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, @@ -333,12 +330,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Request = Connect, + Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, > + Clone, T2: Service< - Request = Connect, + Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, > + Clone, @@ -351,22 +348,21 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Request = Connect, + Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Request = Connect, + Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, { - type Request = Connect; type Response = EitherConnection; type Error = ConnectorError; type Future = Either< @@ -401,23 +397,15 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, + T: Service, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseA where - T: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -435,23 +423,15 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T: Service, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseB where - T: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/pool.rs b/src/client/pool.rs index 188980cb..425e8939 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -48,11 +48,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { pub(crate) fn new( connector: T, @@ -88,16 +84,11 @@ where } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { - type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< diff --git a/src/client/request.rs b/src/client/request.rs index 7e971756..637635d0 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -176,7 +176,7 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 9ae8cd2a..7024ce3a 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -36,14 +36,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher +pub struct Dispatcher + 'static, B: MessageBody> where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher +struct InnerDispatcher + 'static, B: MessageBody> where S::Error: Debug, { @@ -67,13 +67,13 @@ enum DispatcherMessage { Error(Response<()>), } -enum State { +enum State, B: MessageBody> { None, ServiceCall(S::Future), SendPayload(ResponseBody), } -impl State { +impl, B: MessageBody> State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -86,7 +86,7 @@ impl State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -141,7 +141,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -464,7 +464,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/h1/service.rs b/src/h1/service.rs index acc7217b..1c4f1ae3 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -28,14 +28,14 @@ pub struct H1Service { impl H1Service where - S: NewService, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, { /// Create new `HttpService` instance with default config. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -46,7 +46,10 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { + pub fn with_config>( + cfg: ServiceConfig, + service: F, + ) -> Self { H1Service { cfg, srv: service.into_new_service(), @@ -60,16 +63,15 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, { - type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type InitError = S::InitError; @@ -101,7 +103,7 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where - S: NewService, + S: NewService, S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` @@ -199,7 +201,7 @@ where pub fn finish(self, service: F) -> H1Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -215,7 +217,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse { +pub struct H1ServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -224,7 +226,7 @@ pub struct H1ServiceResponse { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug, S::Response: Into>, @@ -251,7 +253,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -265,15 +267,14 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, { - type Request = T; type Response = H1ServiceResult; type Error = DispatchError; type Future = Dispatcher; @@ -307,11 +308,10 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { - type Request = T; type Response = (Request, Framed); type Error = ParseError; type InitError = (); @@ -333,11 +333,10 @@ pub struct OneRequestService { _t: PhantomData, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { - type Request = T; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index ea8756d2..7f5409c6 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -38,7 +38,11 @@ bitflags! { } /// Dispatcher for HTTP/2 protocol -pub struct Dispatcher { +pub struct Dispatcher< + T: AsyncRead + AsyncWrite, + S: Service> + 'static, + B: MessageBody, +> { flags: Flags, service: CloneableService, connection: Connection, @@ -51,7 +55,7 @@ pub struct Dispatcher Dispatcher where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -93,7 +97,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -139,20 +143,20 @@ where } } -struct ServiceResponse { +struct ServiceResponse>, B> { state: ServiceResponseState, config: ServiceConfig, buffer: Option, } -enum ServiceResponseState { +enum ServiceResponseState>, B> { ServiceCall(S::Future, Option>), SendPayload(SendStream, ResponseBody), } impl ServiceResponse where - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -220,7 +224,7 @@ where impl Future for ServiceResponse where - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + fmt::Debug, S::Response: Into>, B: MessageBody + 'static, diff --git a/src/h2/service.rs b/src/h2/service.rs index 583f5edd..e225e9fc 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -31,14 +31,14 @@ pub struct H2Service { impl H2Service where - S: NewService>, + S: NewService>, S::Service: 'static, S::Error: Into + Debug + 'static, S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H2Service { @@ -54,16 +54,15 @@ where } } -impl NewService for H2Service +impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService>, S::Service: 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, { - type Request = T; type Response = (); type Error = DispatchError<()>; type InitError = S::InitError; @@ -95,7 +94,7 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where - S: NewService>, + S: NewService>, S::Service: 'static, S::Error: Into + Debug + 'static, { @@ -213,7 +212,7 @@ where pub fn finish(self, service: F) -> H2Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService>, { let cfg = ServiceConfig::new( self.keep_alive, @@ -229,7 +228,7 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse { +pub struct H2ServiceResponse>, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -238,7 +237,7 @@ pub struct H2ServiceResponse { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService>, S::Service: 'static, S::Response: Into>, S::Error: Into + Debug, @@ -265,7 +264,7 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -279,15 +278,14 @@ where } } -impl Service for H2ServiceHandler +impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, { - type Request = T; type Response = (); type Error = DispatchError<()>; type Future = H2ServiceHandlerResponse; @@ -310,7 +308,11 @@ where } } -enum State { +enum State< + T: AsyncRead + AsyncWrite, + S: Service> + 'static, + B: MessageBody, +> { Incoming(Dispatcher), Handshake( Option>, @@ -322,7 +324,7 @@ enum State { pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody + 'static, @@ -333,7 +335,7 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service> + 'static, S::Error: Into + Debug, S::Response: Into>, B: MessageBody, diff --git a/src/service/senderror.rs b/src/service/senderror.rs index 44d36259..8268c666 100644 --- a/src/service/senderror.rs +++ b/src/service/senderror.rs @@ -22,12 +22,11 @@ where } } -impl NewService for SendError +impl NewService)>> for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { - type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); @@ -39,12 +38,11 @@ where } } -impl Service for SendError +impl Service)>> for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { - type Request = Result)>; type Response = R; type Error = (E, Framed); type Future = Either)>, SendErrorFut>; @@ -130,12 +128,11 @@ where } } -impl NewService for SendResponse +impl NewService<(Response, Framed)> for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { - type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); @@ -147,12 +144,11 @@ where } } -impl Service for SendResponse +impl Service<(Response, Framed)> for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { - type Request = (Response, Framed); type Response = Framed; type Error = Error; type Future = SendResponseFut; diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 586873d1..c48b6e0c 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -26,7 +26,7 @@ pub type DefaultClient = Client; /// WebSocket's client pub struct Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { connector: T, @@ -34,7 +34,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -51,7 +51,7 @@ impl Default for Client { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -61,13 +61,12 @@ where } } -impl Service for Client +impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { - type Request = Connect; type Response = Framed; type Error = ClientError; type Future = Either< diff --git a/src/ws/service.rs b/src/ws/service.rs index f3b06605..bbd9f782 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -20,8 +20,7 @@ impl Default for VerifyWebSockets { } } -impl NewService for VerifyWebSockets { - type Request = (Request, Framed); +impl NewService<(Request, Framed)> for VerifyWebSockets { type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); @@ -33,8 +32,7 @@ impl NewService for VerifyWebSockets { } } -impl Service for VerifyWebSockets { - type Request = (Request, Framed); +impl Service<(Request, Framed)> for VerifyWebSockets { type Response = (Request, Framed); type Error = (HandshakeError, Framed); type Future = FutureResult; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index da7782be..6a4f4d22 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -7,7 +7,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service + 'static, + S: Service + 'static, T: AsyncRead + AsyncWrite, { inner: FramedTransport, @@ -16,17 +16,17 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Transport { inner: FramedTransport::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Transport { inner: FramedTransport::new(framed, service), } @@ -36,7 +36,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index df56b8f3..6a401cc5 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -35,10 +35,12 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] actix-codec = "0.1" actix-rt = "0.1.0" actix-http = { path=".." } -actix-service = "0.3.2" +#actix-service = "0.3.2" +actix-service = { git="https://github.com/actix/actix-net.git" } #actix-server = "0.3.0" actix-server = { git="https://github.com/actix/actix-net.git" } -actix-utils = "0.3.2" +#actix-utils = "0.3.2" +actix-utils = { git="https://github.com/actix/actix-net.git" } base64 = "0.10" bytes = "0.4" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index a13e86cf..3afee682 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -56,8 +56,7 @@ impl TestServer { pub fn new( factory: F, ) -> TestServerRuntime< - impl Service - + Clone, + impl Service + Clone, > { let (tx, rx) = mpsc::channel(); @@ -89,11 +88,8 @@ impl TestServer { } fn new_connector( - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { + ) -> impl Service + Clone + { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -206,7 +202,7 @@ impl TestServerRuntime { impl TestServerRuntime where - T: Service + Clone, + T: Service + Clone, T::Response: Connection, { /// Connect to websocket server at a given path From 03248028a9a7f140a168fdc9a2797ee81210cec7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 10:08:08 -0800 Subject: [PATCH 0974/1635] update actix-service --- Cargo.toml | 15 ++++- src/app.rs | 111 ++++++++++++------------------- src/handler.rs | 18 ++--- src/middleware/compress.rs | 14 ++-- src/middleware/defaultheaders.rs | 10 ++- src/middleware/mod.rs | 3 + src/resource.rs | 30 ++++----- src/route.rs | 47 +++++++------ src/scope.rs | 25 +++---- src/server.rs | 12 ++-- src/service.rs | 6 +- 11 files changed, 132 insertions(+), 159 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0b4ad38a..f473ac55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "rust-tls"] +features = ["ssl", "tls", "rust-tls"] #, "session"] [features] default = ["brotli", "flate2-c"] @@ -45,6 +45,9 @@ flate2-c = ["flate2/miniz-sys"] # rust backend for flate2 crate flate2-rust = ["flate2/rust_backend"] +# sessions feature, session require "ring" crate and c compiler +# session = ["actix-session"] + # tls tls = ["native-tls", "actix-server/ssl"] @@ -56,10 +59,13 @@ ssl = ["openssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1.0" -actix-service = "0.3.2" -actix-utils = "0.3.1" +#actix-service = "0.3.2" +#actix-utils = "0.3.1" actix-rt = "0.1.0" +actix-service = { git = "https://github.com/actix/actix-net.git" } +actix-utils = { git = "https://github.com/actix/actix-net.git" } + actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } actix-server = { git = "https://github.com/actix/actix-net.git" } @@ -79,6 +85,9 @@ serde_json = "1.0" serde_urlencoded = "^0.5.3" threadpool = "1.7" +# middlewares +# actix-session = { path="session", optional = true } + # compression brotli2 = { version="^0.3.2", optional = true } flate2 = { version="^1.0.2", optional = true, default-features = false } diff --git a/src/app.rs b/src/app.rs index 8336fcca..b2164501 100644 --- a/src/app.rs +++ b/src/app.rs @@ -25,7 +25,7 @@ type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, type BoxedResponse = Box>; pub trait HttpServiceFactory { - type Factory: NewService; + type Factory: NewService; fn rdef(&self) -> &ResourceDef; @@ -36,7 +36,7 @@ pub trait HttpServiceFactory { /// for building application instances. pub struct App where - T: NewService, Response = ServiceRequest

    >, + T: NewService>, { chain: T, extensions: Extensions, @@ -61,7 +61,7 @@ impl App where P: 'static, T: NewService< - Request = ServiceRequest, + ServiceRequest, Response = ServiceRequest

    , Error = (), InitError = (), @@ -197,7 +197,7 @@ where where F: FnOnce(Resource

    ) -> Resource, U: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -230,7 +230,7 @@ where P, B, impl NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -239,12 +239,12 @@ where where M: Transform< AppRouting

    , - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoTransform>, + F: IntoTransform, ServiceRequest

    >, { let fref = Rc::new(RefCell::new(None)); let endpoint = ApplyTransform::new(mw, AppEntry::new(fref.clone())); @@ -269,7 +269,7 @@ where ) -> App< P1, impl NewService< - Request = ServiceRequest, + ServiceRequest, Response = ServiceRequest, Error = (), InitError = (), @@ -277,13 +277,12 @@ where > where C: NewService< - (), - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceRequest, Error = (), InitError = (), >, - F: IntoNewService, + F: IntoNewService>, { let chain = self.chain.and_then(chain.into_new_service()); App { @@ -326,7 +325,7 @@ where P: 'static, B: MessageBody, T: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -406,7 +405,7 @@ where where F: FnOnce(Resource

    ) -> Resource, U: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -430,12 +429,9 @@ where pub fn default_resource(mut self, f: F) -> Self where F: FnOnce(Resource

    ) -> R, - R: IntoNewService, - U: NewService< - Request = ServiceRequest

    , - Response = ServiceResponse, - Error = (), - > + 'static, + R: IntoNewService>, + U: NewService, Response = ServiceResponse, Error = ()> + + 'static, { // create and configure default resource self.default = Some(Rc::new(boxed::new_service( @@ -449,12 +445,9 @@ where pub fn service(mut self, rdef: R, factory: F) -> Self where R: Into, - F: IntoNewService, - U: NewService< - Request = ServiceRequest

    , - Response = ServiceResponse, - Error = (), - > + 'static, + F: IntoNewService>, + U: NewService, Response = ServiceResponse, Error = ()> + + 'static, { self.services.push(( rdef.into(), @@ -473,7 +466,7 @@ where P, B1, impl NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -482,13 +475,13 @@ where where M: Transform< T::Service, - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, B1: MessageBody, - F: IntoTransform, + F: IntoTransform>, { let endpoint = ApplyTransform::new(mw, self.endpoint); AppRouter { @@ -542,22 +535,23 @@ where } impl - IntoNewService, T, ()>> for AppRouter + IntoNewService, T>, Request> + for AppRouter where T: NewService< - Request = ServiceRequest

    , + ServiceRequest

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

    , Error = (), InitError = (), >, { - fn into_new_service(self) -> AndThenNewService, T, ()> { + fn into_new_service(self) -> AndThenNewService, T> { // update resource default service if self.default.is_some() { for default in &self.defaults { @@ -592,8 +586,7 @@ pub struct AppRoutingFactory

    { default: Option>>, } -impl NewService for AppRoutingFactory

    { - type Request = ServiceRequest

    ; +impl NewService> for AppRoutingFactory

    { type Response = ServiceResponse; type Error = (); type InitError = (); @@ -709,8 +702,7 @@ pub struct AppRouting

    { default: Option>, } -impl

    Service for AppRouting

    { - type Request = ServiceRequest

    ; +impl

    Service> for AppRouting

    { type Response = ServiceResponse; type Error = (); type Future = Either>; @@ -758,8 +750,7 @@ impl

    AppEntry

    { } } -impl NewService for AppEntry

    { - type Request = ServiceRequest

    ; +impl NewService> for AppEntry

    { type Response = ServiceResponse; type Error = (); type InitError = (); @@ -774,9 +765,8 @@ impl NewService for AppEntry

    { #[doc(hidden)] pub struct AppChain; -impl NewService<()> for AppChain { - type Request = ServiceRequest; - type Response = ServiceRequest; +impl NewService for AppChain { + type Response = ServiceRequest; type Error = (); type InitError = (); type Service = AppChain; @@ -787,9 +777,8 @@ impl NewService<()> for AppChain { } } -impl Service for AppChain { - type Request = ServiceRequest; - type Response = ServiceRequest; +impl Service for AppChain { + type Response = ServiceRequest; type Error = (); type Future = FutureResult; @@ -799,7 +788,7 @@ impl Service for AppChain { } #[inline] - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { ok(req) } } @@ -808,22 +797,17 @@ impl Service for AppChain { /// It also executes state factories. pub struct AppInit where - C: NewService, Response = ServiceRequest

    >, + C: NewService>, { chain: C, state: Vec>, extensions: Rc>>, } -impl NewService for AppInit +impl NewService for AppInit where - C: NewService< - Request = ServiceRequest, - Response = ServiceRequest

    , - InitError = (), - >, + C: NewService, InitError = ()>, { - type Request = Request; type Response = ServiceRequest

    ; type Error = C::Error; type InitError = C::InitError; @@ -842,11 +826,7 @@ where #[doc(hidden)] pub struct AppInitResult where - C: NewService< - Request = ServiceRequest, - Response = ServiceRequest

    , - InitError = (), - >, + C: NewService, InitError = ()>, { chain: C::Future, state: Vec>, @@ -855,11 +835,7 @@ where impl Future for AppInitResult where - C: NewService< - Request = ServiceRequest, - Response = ServiceRequest

    , - InitError = (), - >, + C: NewService, InitError = ()>, { type Item = AppInitService; type Error = C::InitError; @@ -893,17 +869,16 @@ where /// Service to convert `Request` to a `ServiceRequest` pub struct AppInitService where - C: Service, Response = ServiceRequest

    >, + C: Service>, { chain: C, extensions: Rc, } -impl Service for AppInitService +impl Service for AppInitService where - C: Service, Response = ServiceRequest

    >, + C: Service>, { - type Request = Request; type Response = ServiceRequest

    ; type Error = C::Error; type Future = C::Future; @@ -912,7 +887,7 @@ where self.chain.poll_ready() } - fn call(&mut self, req: Request) -> Self::Future { + fn call(&mut self, req: Request) -> Self::Future { let req = ServiceRequest::new( Path::new(Url::new(req.uri().clone())), req, diff --git a/src/handler.rs b/src/handler.rs index 98a36c56..442dc60d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -52,12 +52,11 @@ where } } } -impl NewService for Handle +impl NewService<(T, HttpRequest)> for Handle where F: Factory, R: Responder + 'static, { - type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Void; type InitError = (); @@ -82,12 +81,11 @@ where _t: PhantomData<(T, R)>, } -impl Service for HandleService +impl Service<(T, HttpRequest)> for HandleService where F: Factory, R: Responder + 'static, { - type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Void; type Future = HandleServiceResponse<::Future>; @@ -184,14 +182,13 @@ where } } } -impl NewService for AsyncHandle +impl NewService<(T, HttpRequest)> for AsyncHandle where F: AsyncFactory, R: IntoFuture, R::Item: Into, R::Error: Into, { - type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = (); type InitError = (); @@ -218,14 +215,13 @@ where _t: PhantomData<(T, R)>, } -impl Service for AsyncHandleService +impl Service<(T, HttpRequest)> for AsyncHandleService where F: AsyncFactory, R: IntoFuture, R::Item: Into, R::Error: Into, { - type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = (); type Future = AsyncHandleServiceResponse; @@ -290,8 +286,7 @@ impl> Extract { } } -impl> NewService for Extract { - type Request = ServiceRequest

    ; +impl> NewService> for Extract { type Response = (T, HttpRequest); type Error = (Error, ServiceFromRequest

    ); type InitError = (); @@ -311,8 +306,7 @@ pub struct ExtractService> { _t: PhantomData<(P, T)>, } -impl> Service for ExtractService { - type Request = ServiceRequest

    ; +impl> Service> for ExtractService { type Response = (T, HttpRequest); type Error = (Error, ServiceFromRequest

    ); type Future = ExtractResponse; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index b95553cb..c6f090a6 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -36,14 +36,13 @@ impl Default for Compress { } } -impl Transform for Compress +impl Transform> for Compress where P: 'static, B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { - type Request = ServiceRequest

    ; type Response = ServiceResponse>; type Error = S::Error; type InitError = (); @@ -63,14 +62,13 @@ pub struct CompressMiddleware { encoding: ContentEncoding, } -impl Service for CompressMiddleware +impl Service> for CompressMiddleware where P: 'static, B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { - type Request = ServiceRequest

    ; type Response = ServiceResponse>; type Error = S::Error; type Future = CompressResponse; @@ -103,7 +101,7 @@ pub struct CompressResponse where P: 'static, B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { fut: S::Future, @@ -114,7 +112,7 @@ impl Future for CompressResponse where P: 'static, B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { type Item = ServiceResponse>; diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 2bd1d5d4..5fd51919 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -84,12 +84,11 @@ impl DefaultHeaders { } } -impl Transform for DefaultHeaders +impl Transform> for DefaultHeaders where - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { - type Request = ServiceRequest

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

    ; type Response = ServiceResponse; type Error = S::Error; type Future = Box>; diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index fc992302..8c4cd754 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -5,3 +5,6 @@ pub use self::compress::Compress; mod defaultheaders; pub use self::defaultheaders::DefaultHeaders; + +#[cfg(feature = "session")] +pub use actix_session as session; diff --git a/src/resource.rs b/src/resource.rs index 755b8d07..8d81ead0 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -65,7 +65,7 @@ impl

    Default for Resource

    { impl Resource where T: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -187,7 +187,7 @@ where ) -> Resource< P, impl NewService< - Request = ServiceRequest

    , + ServiceRequest

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

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoTransform, + F: IntoTransform>, { let endpoint = ApplyTransform::new(mw, self.endpoint); Resource { @@ -216,12 +216,9 @@ where pub fn default_resource(mut self, f: F) -> Self where F: FnOnce(Resource

    ) -> R, - R: IntoNewService, - U: NewService< - Request = ServiceRequest

    , - Response = ServiceResponse, - Error = (), - > + 'static, + R: IntoNewService>, + U: NewService, Response = ServiceResponse, Error = ()> + + 'static, { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( @@ -236,10 +233,10 @@ where } } -impl IntoNewService for Resource +impl IntoNewService> for Resource where T: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -260,8 +257,7 @@ pub struct ResourceFactory

    { default: Rc>>>>, } -impl NewService for ResourceFactory

    { - type Request = ServiceRequest

    ; +impl NewService> for ResourceFactory

    { type Response = ServiceResponse; type Error = (); type InitError = (); @@ -351,8 +347,7 @@ pub struct ResourceService

    { default: Option>, } -impl

    Service for ResourceService

    { - type Request = ServiceRequest

    ; +impl

    Service> for ResourceService

    { type Response = ServiceResponse; type Error = (); type Future = Either< @@ -396,8 +391,7 @@ impl

    ResourceEndpoint

    { } } -impl NewService for ResourceEndpoint

    { - type Request = ServiceRequest

    ; +impl NewService> for ResourceEndpoint

    { type Response = ServiceResponse; type Error = (); type InitError = (); diff --git a/src/route.rs b/src/route.rs index 72abeb32..42e78488 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::marker::PhantomData; use std::rc::Rc; use actix_http::{http::Method, Error, Extensions, Response}; @@ -14,7 +15,7 @@ use crate::HttpResponse; type BoxedRouteService = Box< Service< - Request = Req, + Req, Response = Res, Error = (), Future = Box>, @@ -23,7 +24,7 @@ type BoxedRouteService = Box< type BoxedRouteNewService = Box< NewService< - Request = Req, + Req, Response = Res, Error = (), InitError = (), @@ -85,8 +86,7 @@ impl Route

    { } } -impl

    NewService for Route

    { - type Request = ServiceRequest

    ; +impl

    NewService> for Route

    { type Response = ServiceResponse; type Error = (); type InitError = (); @@ -141,8 +141,7 @@ impl

    RouteService

    { } } -impl

    Service for RouteService

    { - type Request = ServiceRequest

    ; +impl

    Service> for RouteService

    { type Response = ServiceResponse; type Error = (); type Future = Box>; @@ -151,7 +150,7 @@ impl

    Service for RouteService

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

    ) -> Self::Future { self.service.call(req) } } @@ -348,43 +347,46 @@ impl Route

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

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

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

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

    , + ServiceRequest

    , Response = ServiceResponse, Error = (Error, ServiceFromRequest

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

    , + ServiceRequest

    , Response = ServiceResponse, Error = (Error, ServiceFromRequest

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

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

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

    , + ServiceRequest

    , Response = ServiceResponse, Error = (Error, ServiceFromRequest

    ), >, { - type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type Future = Box>; diff --git a/src/scope.rs b/src/scope.rs index b255ac14..fa7392b4 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -79,7 +79,7 @@ impl Scope

    { impl Scope where T: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -196,7 +196,7 @@ where where F: FnOnce(Resource

    ) -> Resource, U: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -219,7 +219,7 @@ where where F: FnOnce(Resource

    ) -> Resource, U: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -244,7 +244,7 @@ where ) -> Scope< P, impl NewService< - Request = ServiceRequest

    , + ServiceRequest

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

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoTransform, + F: IntoTransform>, { let endpoint = ApplyTransform::new(mw, self.endpoint); Scope { @@ -293,10 +293,10 @@ pub(crate) fn insert_slash(path: &str) -> String { path } -impl IntoNewService for Scope +impl IntoNewService> for Scope where T: NewService< - Request = ServiceRequest

    , + ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -331,8 +331,7 @@ pub struct ScopeFactory

    { default: Rc>>>>, } -impl NewService for ScopeFactory

    { - type Request = ServiceRequest

    ; +impl NewService> for ScopeFactory

    { type Response = ServiceResponse; type Error = (); type InitError = (); @@ -448,8 +447,7 @@ pub struct ScopeService

    { _ready: Option<(ServiceRequest

    , ResourceInfo)>, } -impl

    Service for ScopeService

    { - type Request = ServiceRequest

    ; +impl

    Service> for ScopeService

    { type Response = ServiceResponse; type Error = (); type Future = Either>; @@ -492,8 +490,7 @@ impl

    ScopeEndpoint

    { } } -impl NewService for ScopeEndpoint

    { - type Request = ServiceRequest

    ; +impl NewService> for ScopeEndpoint

    { type Response = ServiceResponse; type Error = (); type InitError = (); diff --git a/src/server.rs b/src/server.rs index 95cab2b0..78ba692e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -52,8 +52,8 @@ struct Config { pub struct HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: fmt::Debug, S::Response: Into>, S::Service: 'static, @@ -71,8 +71,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: fmt::Debug, S::Response: Into>, S::Service: 'static, @@ -431,8 +431,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: fmt::Debug, S::Response: Into>, S::Service: 'static, diff --git a/src/service.rs b/src/service.rs index 637a8668..50b2924a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -5,15 +5,15 @@ use std::rc::Rc; use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{ - Error, Extensions, HttpMessage, Payload, Request, RequestHead, Response, - ResponseHead, + Error, Extensions, HttpMessage, Payload, PayloadStream, Request, RequestHead, + Response, ResponseHead, }; use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; use crate::request::HttpRequest; -pub struct ServiceRequest

    { +pub struct ServiceRequest

    { req: HttpRequest, payload: Payload

    , } From 6457996cf1b404245e23cfff0c0836a8051ce37d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 10:12:49 -0800 Subject: [PATCH 0975/1635] move session to separate crate --- session/src/lib.rs | 618 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 618 insertions(+) create mode 100644 session/src/lib.rs diff --git a/session/src/lib.rs b/session/src/lib.rs new file mode 100644 index 00000000..0271a13f --- /dev/null +++ b/session/src/lib.rs @@ -0,0 +1,618 @@ +//! User sessions. +//! +//! Actix provides a general solution for session management. The +//! [**SessionStorage**](struct.SessionStorage.html) +//! middleware can be used with different backend types to store session +//! data in different backends. +//! +//! By default, only cookie session backend is implemented. Other +//! backend implementations can be added. +//! +//! [**CookieSessionBackend**](struct.CookieSessionBackend.html) +//! uses cookies as session storage. `CookieSessionBackend` creates sessions +//! which are limited to storing fewer than 4000 bytes of data, as the payload +//! must fit into a single cookie. An internal server error is generated if a +//! session contains more than 4000 bytes. +//! +//! A cookie may have a security policy of *signed* or *private*. Each has +//! a respective `CookieSessionBackend` constructor. +//! +//! A *signed* cookie may be viewed but not modified by the client. A *private* +//! cookie may neither be viewed nor modified by the client. +//! +//! The constructors take a key as an argument. This is the private key +//! for cookie session - when this value is changed, all session data is lost. +//! +//! In general, you create a `SessionStorage` middleware and initialize it +//! with specific backend implementation, such as a `CookieSessionBackend`. +//! To access session data, +//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session) +//! must be used. This method returns a +//! [*Session*](struct.Session.html) object, which allows us to get or set +//! session data. +//! +//! ```rust +//! # extern crate actix_web; +//! # extern crate actix; +//! use actix_web::{server, App, HttpRequest, Result}; +//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; +//! +//! fn index(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() { +//! actix::System::run(|| { +//! server::new( +//! || App::new().middleware( +//! SessionStorage::new( // <- create session middleware +//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend +//! .secure(false) +//! ))) +//! .bind("127.0.0.1:59880").unwrap() +//! .start(); +//! # actix::System::current().stop(); +//! }); +//! } +//! ``` +use std::cell::RefCell; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::rc::Rc; +use std::sync::Arc; + +use cookie::{Cookie, CookieJar, Key, SameSite}; +use futures::future::{err as FutErr, ok as FutOk, FutureResult}; +use futures::Future; +use http::header::{self, HeaderValue}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json; +use serde_json::error::Error as JsonError; +use time::Duration; + +use error::{Error, ResponseError, Result}; +use handler::FromRequest; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middleware::{Middleware, Response, Started}; + +/// The helper trait to obtain your session data from a request. +/// +/// ```rust +/// use actix_web::middleware::session::RequestSession; +/// use actix_web::*; +/// +/// 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 { + /// Get the session from the request + fn session(&self) -> Session; +} + +impl RequestSession for HttpRequest { + fn session(&self) -> Session { + if let Some(s_impl) = self.extensions().get::>() { + return Session(SessionInner::Session(Arc::clone(&s_impl))); + } + Session(SessionInner::None) + } +} + +/// The high-level interface you use to modify session data. +/// +/// Session object could be obtained with +/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session) +/// method. `RequestSession` trait is implemented for `HttpRequest`. +/// +/// ```rust +/// use actix_web::middleware::session::RequestSession; +/// use actix_web::*; +/// +/// 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(SessionInner); + +enum SessionInner { + Session(Arc), + None, +} + +impl Session { + /// Get a `value` from the session. + pub fn get(&self, key: &str) -> Result> { + match self.0 { + SessionInner::Session(ref sess) => { + if let Some(s) = sess.as_ref().0.borrow().get(key) { + Ok(Some(serde_json::from_str(s)?)) + } else { + Ok(None) + } + } + SessionInner::None => Ok(None), + } + } + + /// Set a `value` from the session. + pub fn set(&self, key: &str, value: T) -> Result<()> { + match self.0 { + SessionInner::Session(ref sess) => { + sess.as_ref() + .0 + .borrow_mut() + .set(key, serde_json::to_string(&value)?); + Ok(()) + } + SessionInner::None => Ok(()), + } + } + + /// Remove value from the session. + pub fn remove(&self, key: &str) { + match self.0 { + SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key), + SessionInner::None => (), + } + } + + /// Clear the session. + pub fn clear(&self) { + match self.0 { + SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(), + SessionInner::None => (), + } + } +} + +/// Extractor implementation for Session type. +/// +/// ```rust +/// # use actix_web::*; +/// use actix_web::middleware::session::Session; +/// +/// fn index(session: Session) -> Result<&'static str> { +/// // access session data +/// if let Some(count) = session.get::("counter")? { +/// session.set("counter", count + 1)?; +/// } else { +/// session.set("counter", 1)?; +/// } +/// +/// Ok("Welcome!") +/// } +/// # fn main() {} +/// ``` +impl FromRequest for Session { + type Config = (); + type Result = Session; + + #[inline] + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + req.session() + } +} + +struct SessionImplCell(RefCell>); + +/// Session storage middleware +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage}; +/// use actix_web::App; +/// +/// fn main() { +/// let app = App::new().middleware(SessionStorage::new( +/// // <- create session middleware +/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend +/// .secure(false), +/// )); +/// } +/// ``` +pub struct SessionStorage(T, PhantomData); + +impl> SessionStorage { + /// Create session storage + pub fn new(backend: T) -> SessionStorage { + SessionStorage(backend, PhantomData) + } +} + +impl> Middleware for SessionStorage { + fn start(&self, req: &HttpRequest) -> Result { + let mut req = req.clone(); + + let fut = self.0.from_request(&mut req).then(move |res| match res { + Ok(sess) => { + req.extensions_mut() + .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess))))); + FutOk(None) + } + Err(err) => FutErr(err), + }); + Ok(Started::Future(Box::new(fut))) + } + + fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { + if let Some(s_box) = req.extensions().get::>() { + s_box.0.borrow_mut().write(resp) + } else { + Ok(Response::Done(resp)) + } + } +} + +/// A simple key-value storage interface that is internally used by `Session`. +pub trait SessionImpl: 'static { + /// Get session value by key + fn get(&self, key: &str) -> Option<&str>; + + /// Set session value + fn set(&mut self, key: &str, value: String); + + /// Remove specific key from session + fn remove(&mut self, key: &str); + + /// Remove all values from session + fn clear(&mut self); + + /// Write session to storage backend. + fn write(&self, resp: HttpResponse) -> Result; +} + +/// Session's storage backend trait definition. +pub trait SessionBackend: Sized + 'static { + /// Session item + type Session: SessionImpl; + /// Future that reads session + type ReadFuture: Future; + + /// Parse the session from request and load data from a storage backend. + fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; +} + +/// Session that uses signed cookies as session storage +pub struct CookieSession { + changed: bool, + state: HashMap, + inner: Rc, +} + +/// Errors that can occur during handling cookie session +#[derive(Fail, Debug)] +pub enum CookieSessionError { + /// Size of the serialized session is greater than 4000 bytes. + #[fail(display = "Size of the serialized session is greater than 4000 bytes.")] + Overflow, + /// Fail to serialize session. + #[fail(display = "Fail to serialize session")] + Serialize(JsonError), +} + +impl ResponseError for CookieSessionError {} + +impl SessionImpl for CookieSession { + fn get(&self, key: &str) -> Option<&str> { + if let Some(s) = self.state.get(key) { + Some(s) + } else { + None + } + } + + fn set(&mut self, key: &str, value: String) { + self.changed = true; + self.state.insert(key.to_owned(), value); + } + + fn remove(&mut self, key: &str) { + self.changed = true; + self.state.remove(key); + } + + fn clear(&mut self) { + self.changed = true; + self.state.clear() + } + + fn write(&self, mut resp: HttpResponse) -> Result { + if self.changed { + let _ = self.inner.set_cookie(&mut resp, &self.state); + } + Ok(Response::Done(resp)) + } +} + +enum CookieSecurity { + Signed, + Private, +} + +struct CookieSessionInner { + key: Key, + security: CookieSecurity, + name: String, + path: String, + domain: Option, + secure: bool, + http_only: bool, + max_age: Option, + same_site: Option, +} + +impl CookieSessionInner { + fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { + CookieSessionInner { + security, + key: Key::from_master(key), + name: "actix-session".to_owned(), + path: "/".to_owned(), + domain: None, + secure: true, + http_only: true, + max_age: None, + same_site: None, + } + } + + fn set_cookie( + &self, resp: &mut HttpResponse, state: &HashMap, + ) -> Result<()> { + let value = + serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; + if value.len() > 4064 { + return Err(CookieSessionError::Overflow.into()); + } + + let mut cookie = Cookie::new(self.name.clone(), value); + cookie.set_path(self.path.clone()); + cookie.set_secure(self.secure); + cookie.set_http_only(self.http_only); + + if let Some(ref domain) = self.domain { + cookie.set_domain(domain.clone()); + } + + if let Some(max_age) = self.max_age { + cookie.set_max_age(max_age); + } + + if let Some(same_site) = self.same_site { + cookie.set_same_site(same_site); + } + + let mut jar = CookieJar::new(); + + match self.security { + CookieSecurity::Signed => jar.signed(&self.key).add(cookie), + CookieSecurity::Private => jar.private(&self.key).add(cookie), + } + + for cookie in jar.delta() { + let val = HeaderValue::from_str(&cookie.encoded().to_string())?; + resp.headers_mut().append(header::SET_COOKIE, val); + } + + Ok(()) + } + + fn load(&self, req: &mut HttpRequest) -> HashMap { + if let Ok(cookies) = req.cookies() { + for cookie in cookies.iter() { + if cookie.name() == self.name { + let mut jar = CookieJar::new(); + jar.add_original(cookie.clone()); + + let cookie_opt = match self.security { + CookieSecurity::Signed => jar.signed(&self.key).get(&self.name), + CookieSecurity::Private => { + jar.private(&self.key).get(&self.name) + } + }; + if let Some(cookie) = cookie_opt { + if let Ok(val) = serde_json::from_str(cookie.value()) { + return val; + } + } + } + } + } + HashMap::new() + } +} + +/// Use cookies for session storage. +/// +/// `CookieSessionBackend` creates sessions which are limited to storing +/// fewer than 4000 bytes of data (as the payload must fit into a single +/// cookie). An Internal Server Error is generated if the session contains more +/// than 4000 bytes. +/// +/// A cookie may have a security policy of *signed* or *private*. Each has a +/// respective `CookieSessionBackend` constructor. +/// +/// A *signed* cookie is stored on the client as plaintext alongside +/// a signature such that the cookie may be viewed but not modified by the +/// client. +/// +/// A *private* cookie is stored on the client as encrypted text +/// such that it may neither be viewed nor modified by the client. +/// +/// The constructors take a key as an argument. +/// This is the private key for cookie session - when this value is changed, +/// all session data is lost. The constructors will panic if the key is less +/// than 32 bytes in length. +/// +/// The backend relies on `cookie` crate to create and read cookies. +/// By default all cookies are percent encoded, but certain symbols may +/// cause troubles when reading cookie, if they are not properly percent encoded. +/// +/// # Example +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::middleware::session::CookieSessionBackend; +/// +/// # fn main() { +/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32]) +/// .domain("www.rust-lang.org") +/// .name("actix_session") +/// .path("/") +/// .secure(true); +/// # } +/// ``` +pub struct CookieSessionBackend(Rc); + +impl CookieSessionBackend { + /// Construct new *signed* `CookieSessionBackend` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn signed(key: &[u8]) -> CookieSessionBackend { + CookieSessionBackend(Rc::new(CookieSessionInner::new( + key, + CookieSecurity::Signed, + ))) + } + + /// Construct new *private* `CookieSessionBackend` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn private(key: &[u8]) -> CookieSessionBackend { + CookieSessionBackend(Rc::new(CookieSessionInner::new( + key, + CookieSecurity::Private, + ))) + } + + /// Sets the `path` field in the session cookie being built. + pub fn path>(mut self, value: S) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().path = value.into(); + self + } + + /// Sets the `name` field in the session cookie being built. + pub fn name>(mut self, value: S) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().name = value.into(); + self + } + + /// Sets the `domain` field in the session cookie being built. + pub fn domain>(mut self, value: S) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); + self + } + + /// Sets the `secure` field in the session cookie being built. + /// + /// If the `secure` field is set, a cookie will only be transmitted when the + /// connection is secure - i.e. `https` + pub fn secure(mut self, value: bool) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().secure = value; + self + } + + /// Sets the `http_only` field in the session cookie being built. + pub fn http_only(mut self, value: bool) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().http_only = value; + self + } + + /// Sets the `same_site` field in the session cookie being built. + pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().same_site = Some(value); + self + } + + /// Sets the `max-age` field in the session cookie being built. + pub fn max_age(mut self, value: Duration) -> CookieSessionBackend { + Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); + self + } +} + +impl SessionBackend for CookieSessionBackend { + type Session = CookieSession; + type ReadFuture = FutureResult; + + fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { + let state = self.0.load(req); + FutOk(CookieSession { + changed: false, + inner: Rc::clone(&self.0), + state, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use application::App; + use test; + + #[test] + fn cookie_session() { + let mut srv = test::TestServer::with_factory(|| { + App::new() + .middleware(SessionStorage::new( + CookieSessionBackend::signed(&[0; 32]).secure(false), + )).resource("/", |r| { + r.f(|req| { + let _ = req.session().set("counter", 100); + "test" + }) + }) + }); + + let request = srv.get().uri(srv.url("/")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.cookie("actix-session").is_some()); + } + + #[test] + fn cookie_session_extractor() { + let mut srv = test::TestServer::with_factory(|| { + App::new() + .middleware(SessionStorage::new( + CookieSessionBackend::signed(&[0; 32]).secure(false), + )).resource("/", |r| { + r.with(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + }) + }) + }); + + let request = srv.get().uri(srv.url("/")).finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.cookie("actix-session").is_some()); + } +} From 01329af1c2c6f5cbeff5631598f772e68c51b65f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 10:18:46 -0800 Subject: [PATCH 0976/1635] fix non ssl code --- src/client/connector.rs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index b35e6af9..d1fd802e 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -238,11 +238,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -250,11 +246,8 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - > + Clone, + T: Service + + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -263,20 +256,15 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { - type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< - as Service>::Future, + as Service>::Future, FutureResult, ConnectorError>, >; From 96477d42cb0c97660a89a7b9eea1076936712048 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 13:16:26 -0800 Subject: [PATCH 0977/1635] extend HttpMessage trait, add api to work with requests cookies --- Cargo.toml | 5 +-- src/client/response.rs | 19 +++++++++- src/h1/decoder.rs | 3 +- src/httpmessage.rs | 50 +++++++++++++++++++++++- src/message.rs | 16 ++++++++ src/request.rs | 42 +++++++++------------ src/response.rs | 86 +++++++++++++++++------------------------- src/ws/mod.rs | 1 + 8 files changed, 140 insertions(+), 82 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1705479b..6ab1b090 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,10 +28,7 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["session"] - -# sessions feature, session require "ring" crate and c compiler -session = ["cookie/secure"] +default = [] # openssl ssl = ["openssl", "actix-connector/ssl"] diff --git a/src/client/response.rs b/src/client/response.rs index 104d28ed..236a6338 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,3 +1,4 @@ +use std::cell::{Ref, RefMut}; use std::fmt; use bytes::Bytes; @@ -5,6 +6,7 @@ use futures::{Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; use crate::error::PayloadError; +use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Head, Message, ResponseHead}; use crate::payload::{Payload, PayloadStream}; @@ -22,6 +24,18 @@ impl HttpMessage for ClientResponse { &self.head.headers } + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head.headers + } + + fn extensions(&self) -> Ref { + self.head.extensions() + } + + fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() + } + fn take_payload(&mut self) -> Payload { std::mem::replace(&mut self.payload, Payload::None) } @@ -30,8 +44,11 @@ impl HttpMessage for ClientResponse { impl ClientResponse { /// Create new Request instance pub fn new() -> ClientResponse { + let head: Message = Message::new(); + head.extensions_mut().clear(); + ClientResponse { - head: Message::new(), + head, payload: Payload::None, } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 80bca94c..77b76c24 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -163,7 +163,7 @@ impl MessageType for Request { } fn headers_mut(&mut self) -> &mut HeaderMap { - self.headers_mut() + &mut self.head_mut().headers } fn decode(src: &mut BytesMut) -> Result, ParseError> { @@ -832,6 +832,7 @@ mod tests { "GET /test HTTP/1.0\r\n\ connection: close\r\n\r\n", ); + let req = parse_ready!(&mut buf); assert_eq!(req.head().ctype, Some(ConnectionType::Close)); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 79f29a72..17447af6 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,6 +1,8 @@ +use std::cell::{Ref, RefMut}; use std::str; use bytes::{Bytes, BytesMut}; +use cookie::Cookie; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::types::{DecoderTrap, Encoding}; @@ -12,12 +14,16 @@ use serde::de::DeserializeOwned; use serde_urlencoded; use crate::error::{ - ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, + ContentTypeError, CookieParseError, ParseError, PayloadError, ReadlinesError, + UrlencodedError, }; +use crate::extensions::Extensions; use crate::header::Header; use crate::json::JsonBody; use crate::payload::Payload; +struct Cookies(Vec>); + /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { /// Type of message payload stream @@ -26,9 +32,18 @@ pub trait HttpMessage: Sized { /// Read the message headers. fn headers(&self) -> &HeaderMap; + /// Mutable reference to the message's headers. + fn headers_mut(&mut self) -> &mut HeaderMap; + /// Message payload stream fn take_payload(&mut self) -> Payload; + /// Request's extensions container + fn extensions(&self) -> Ref; + + /// Mutable reference to a the request's extensions container + fn extensions_mut(&self) -> RefMut; + #[doc(hidden)] /// Get a header fn get_header(&self) -> Option @@ -100,6 +115,39 @@ pub trait HttpMessage: Sized { } } + /// Load request cookies. + #[inline] + fn cookies(&self) -> Result>>, CookieParseError> { + if self.extensions().get::().is_none() { + let mut cookies = Vec::new(); + for hdr in self.headers().get_all(header::COOKIE) { + let s = + str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; + for cookie_str in s.split(';').map(|s| s.trim()) { + if !cookie_str.is_empty() { + cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); + } + } + } + self.extensions_mut().insert(Cookies(cookies)); + } + Ok(Ref::map(self.extensions(), |ext| { + &ext.get::().unwrap().0 + })) + } + + /// Return request cookie. + fn cookie(&self, name: &str) -> Option> { + if let Ok(cookies) = self.cookies() { + for cookie in cookies.iter() { + if cookie.name() == name { + return Some(cookie.to_owned()); + } + } + } + None + } + /// Load http message body. /// /// By default only 256Kb payload reads to a memory, then diff --git a/src/message.rs b/src/message.rs index 3a1ac130..f9dfe973 100644 --- a/src/message.rs +++ b/src/message.rs @@ -125,6 +125,7 @@ pub struct ResponseHead { pub reason: Option<&'static str>, pub no_chunking: bool, pub(crate) ctype: Option, + pub(crate) extensions: RefCell, } impl Default for ResponseHead { @@ -136,10 +137,25 @@ impl Default for ResponseHead { reason: None, no_chunking: false, ctype: None, + extensions: RefCell::new(Extensions::new()), } } } +impl ResponseHead { + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.extensions.borrow_mut() + } +} + impl Head for ResponseHead { fn clear(&mut self) { self.ctype = None; diff --git a/src/request.rs b/src/request.rs index e1b893f9..761a159d 100644 --- a/src/request.rs +++ b/src/request.rs @@ -17,10 +17,28 @@ pub struct Request

    { impl

    HttpMessage for Request

    { type Stream = P; + #[inline] fn headers(&self) -> &HeaderMap { &self.head().headers } + #[inline] + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head_mut().headers + } + + /// Request extensions + #[inline] + fn extensions(&self) -> Ref { + self.head.extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() + } + fn take_payload(&mut self) -> Payload

    { std::mem::replace(&mut self.payload, Payload::None) } @@ -119,30 +137,6 @@ impl

    Request

    { self.head().uri.path() } - #[inline] - /// Returns Request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - #[inline] - /// Returns mutable Request's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.head.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.head.extensions_mut() - } - /// Check if request requires connection upgrade pub fn upgrade(&self) -> bool { if let Some(conn) = self.head().headers.get(header::CONNECTION) { diff --git a/src/response.rs b/src/response.rs index 9b503de1..649e7fd8 100644 --- a/src/response.rs +++ b/src/response.rs @@ -766,12 +766,14 @@ impl From for Response { #[cfg(test)] mod tests { + use time::Duration; + use super::*; use crate::body::Body; use crate::http; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - - // use test::TestRequest; + use crate::httpmessage::HttpMessage; + use crate::test::TestRequest; #[test] fn test_debug() { @@ -783,38 +785,39 @@ mod tests { assert!(dbg.contains("Response")); } - // #[test] - // fn test_response_cookies() { - // let req = TestRequest::default() - // .header(COOKIE, "cookie1=value1") - // .header(COOKIE, "cookie2=value2") - // .finish(); - // let cookies = req.cookies().unwrap(); + #[test] + fn test_response_cookies() { + let req = TestRequest::default() + .header(COOKIE, "cookie1=value1") + .header(COOKIE, "cookie2=value2") + .finish(); + let cookies = req.cookies().unwrap(); - // let resp = Response::Ok() - // .cookie( - // http::Cookie::build("name", "value") - // .domain("www.rust-lang.org") - // .path("/test") - // .http_only(true) - // .max_age(Duration::days(1)) - // .finish(), - // ).del_cookie(&cookies[0]) - // .finish(); + let resp = Response::Ok() + .cookie( + http::Cookie::build("name", "value") + .domain("www.rust-lang.org") + .path("/test") + .http_only(true) + .max_age(Duration::days(1)) + .finish(), + ) + .del_cookie(&cookies[0]) + .finish(); - // 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" - // ); - // } + 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_update_response_cookies() { @@ -871,25 +874,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - // #[test] - // fn test_content_encoding() { - // let resp = Response::build(StatusCode::OK).finish(); - // assert_eq!(resp.content_encoding(), None); - - // #[cfg(feature = "brotli")] - // { - // let resp = Response::build(StatusCode::OK) - // .content_encoding(ContentEncoding::Br) - // .finish(); - // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); - // } - - // let resp = Response::build(StatusCode::OK) - // .content_encoding(ContentEncoding::Gzip) - // .finish(); - // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); - // } - #[test] fn test_json() { let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 2d629c73..a8de59dd 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -9,6 +9,7 @@ use derive_more::{Display, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; +use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::{Response, ResponseBuilder}; From 200cae19a9e21e2d24e57bd37f434cfce6c36a7d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 14:39:06 -0800 Subject: [PATCH 0978/1635] add HttpMessage impl &mut T --- src/httpmessage.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 17447af6..d95f82f5 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -270,6 +270,37 @@ pub trait HttpMessage: Sized { } } +impl<'a, T> HttpMessage for &'a mut T +where + T: HttpMessage, +{ + type Stream = T::Stream; + + fn headers(&self) -> &HeaderMap { + (**self).headers() + } + + /// Mutable reference to the message's headers. + fn headers_mut(&mut self) -> &mut HeaderMap { + (**self).headers_mut() + } + + /// Message payload stream + fn take_payload(&mut self) -> Payload { + (**self).take_payload() + } + + /// Request's extensions container + fn extensions(&self) -> Ref { + (**self).extensions() + } + + /// Mutable reference to a the request's extensions container + fn extensions_mut(&self) -> RefMut { + (**self).extensions_mut() + } +} + /// Stream to read request line by line. pub struct Readlines { stream: Payload, From 0d2116156a0fa90590e0be2e496e9406db8aa3f3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 17:24:24 -0800 Subject: [PATCH 0979/1635] Messagebody constraint is not required from Response::into_body --- src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response.rs b/src/response.rs index 649e7fd8..8d32570a 100644 --- a/src/response.rs +++ b/src/response.rs @@ -80,7 +80,7 @@ impl Response { } /// Convert response to response with body - pub fn into_body(self) -> Response { + pub fn into_body(self) -> Response { let b = match self.body { ResponseBody::Body(b) => b, ResponseBody::Other(b) => b, From 496ee8d0395d8ac7714b6c60d012e6e6283e8422 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 18:14:30 -0800 Subject: [PATCH 0980/1635] remove more MessageBody constraints from Response --- src/response.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/response.rs b/src/response.rs index 8d32570a..277890e4 100644 --- a/src/response.rs +++ b/src/response.rs @@ -212,7 +212,7 @@ impl Response { } /// Set a body - pub(crate) fn set_body(self, body: B2) -> Response { + pub(crate) fn set_body(self, body: B2) -> Response { Response { head: self.head, body: ResponseBody::Body(body), @@ -230,10 +230,7 @@ impl Response { } /// Set a body and return previous body value - pub(crate) fn replace_body( - self, - body: B2, - ) -> (Response, ResponseBody) { + pub(crate) fn replace_body(self, body: B2) -> (Response, ResponseBody) { ( Response { head: self.head, @@ -245,7 +242,7 @@ impl Response { } /// Set a body and return previous body value - pub fn map_body(mut self, f: F) -> Response + pub fn map_body(mut self, f: F) -> Response where F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, { @@ -597,7 +594,7 @@ impl ResponseBuilder { /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. - pub fn message_body(&mut self, body: B) -> Response { + pub fn message_body(&mut self, body: B) -> Response { if let Some(e) = self.err.take() { return Response::from(Error::from(e)).into_body(); } From 143ef87b666559dd6c25ddce04db494040eef809 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 18:47:18 -0800 Subject: [PATCH 0981/1635] add session and cookie session backend --- Cargo.toml | 1 + session/Cargo.toml | 51 ++++ session/src/cookie.rs | 360 +++++++++++++++++++++++ session/src/lib.rs | 652 +++++++----------------------------------- src/request.rs | 38 +-- src/service.rs | 85 ++++-- src/test.rs | 47 ++- 7 files changed, 646 insertions(+), 588 deletions(-) create mode 100644 session/Cargo.toml create mode 100644 session/src/cookie.rs diff --git a/Cargo.toml b/Cargo.toml index f473ac55..2f69c7ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ path = "src/lib.rs" [workspace] members = [ ".", + "session", "staticfiles", ] diff --git a/session/Cargo.toml b/session/Cargo.toml new file mode 100644 index 00000000..3bbeb4f8 --- /dev/null +++ b/session/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "actix-session" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Session for actix web framework." +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://docs.rs/actix-web/" +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +workspace = ".." +edition = "2018" + +[lib] +name = "actix_session" +path = "src/lib.rs" + +[features] +default = ["cookie-session"] + +# sessions feature, session require "ring" crate and c compiler +cookie-session = ["cookie/secure"] + +[dependencies] +actix-web = { path=".." } +actix-codec = "0.1.0" + +#actix-service = "0.3.2" +#actix-utils = "0.3.1" +actix-service = { git = "https://github.com/actix/actix-net.git" } +actix-utils = { git = "https://github.com/actix/actix-net.git" } + +actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-router = { git = "https://github.com/actix/actix-net.git" } +actix-server = { git = "https://github.com/actix/actix-net.git" } + +bytes = "0.4" +cookie = { version="0.11", features=["percent-encode"], optional=true } +derive_more = "0.14" +encoding = "0.2" +futures = "0.1" +hashbrown = "0.1.8" +log = "0.4" +serde = "1.0" +serde_json = "1.0" +time = "0.1" + +[dev-dependencies] +actix-rt = "0.1.0" diff --git a/session/src/cookie.rs b/session/src/cookie.rs new file mode 100644 index 00000000..9cde02e0 --- /dev/null +++ b/session/src/cookie.rs @@ -0,0 +1,360 @@ +//! Cookie session. +//! +//! [**CookieSession**](struct.CookieSession.html) +//! uses cookies as session storage. `CookieSession` creates sessions +//! which are limited to storing fewer than 4000 bytes of data, as the payload +//! must fit into a single cookie. An internal server error is generated if a +//! session contains more than 4000 bytes. +//! +//! A cookie may have a security policy of *signed* or *private*. Each has +//! a respective `CookieSession` constructor. +//! +//! A *signed* cookie may be viewed but not modified by the client. A *private* +//! cookie may neither be viewed nor modified by the client. +//! +//! The constructors take a key as an argument. This is the private key +//! for cookie session - when this value is changed, all session data is lost. + +use std::collections::HashMap; +use std::rc::Rc; + +use actix_service::{Service, Transform}; +use actix_web::http::{header::SET_COOKIE, HeaderValue}; +use actix_web::{Error, HttpMessage, ResponseError, ServiceRequest, ServiceResponse}; +use cookie::{Cookie, CookieJar, Key, SameSite}; +use derive_more::{Display, From}; +use futures::future::{ok, Future, FutureResult}; +use futures::Poll; +use serde_json::error::Error as JsonError; +use time::Duration; + +use crate::Session; + +/// Errors that can occur during handling cookie session +#[derive(Debug, From, Display)] +pub enum CookieSessionError { + /// Size of the serialized session is greater than 4000 bytes. + #[display(fmt = "Size of the serialized session is greater than 4000 bytes.")] + Overflow, + /// Fail to serialize session. + #[display(fmt = "Fail to serialize session")] + Serialize(JsonError), +} + +impl ResponseError for CookieSessionError {} + +enum CookieSecurity { + Signed, + Private, +} + +struct CookieSessionInner { + key: Key, + security: CookieSecurity, + name: String, + path: String, + domain: Option, + secure: bool, + http_only: bool, + max_age: Option, + same_site: Option, +} + +impl CookieSessionInner { + fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { + CookieSessionInner { + security, + key: Key::from_master(key), + name: "actix-session".to_owned(), + path: "/".to_owned(), + domain: None, + secure: true, + http_only: true, + max_age: None, + same_site: None, + } + } + + fn set_cookie( + &self, + res: &mut ServiceResponse, + state: impl Iterator, + ) -> Result<(), Error> { + let state: HashMap = state.collect(); + let value = + serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; + if value.len() > 4064 { + return Err(CookieSessionError::Overflow.into()); + } + + let mut cookie = Cookie::new(self.name.clone(), value); + cookie.set_path(self.path.clone()); + cookie.set_secure(self.secure); + cookie.set_http_only(self.http_only); + + if let Some(ref domain) = self.domain { + cookie.set_domain(domain.clone()); + } + + if let Some(max_age) = self.max_age { + cookie.set_max_age(max_age); + } + + if let Some(same_site) = self.same_site { + cookie.set_same_site(same_site); + } + + let mut jar = CookieJar::new(); + + match self.security { + CookieSecurity::Signed => jar.signed(&self.key).add(cookie), + CookieSecurity::Private => jar.private(&self.key).add(cookie), + } + + for cookie in jar.delta() { + let val = HeaderValue::from_str(&cookie.encoded().to_string())?; + res.headers_mut().append(SET_COOKIE, val); + } + + Ok(()) + } + + fn load

    (&self, req: &ServiceRequest

    ) -> HashMap { + if let Ok(cookies) = req.cookies() { + for cookie in cookies.iter() { + if cookie.name() == self.name { + let mut jar = CookieJar::new(); + jar.add_original(cookie.clone()); + + let cookie_opt = match self.security { + CookieSecurity::Signed => jar.signed(&self.key).get(&self.name), + CookieSecurity::Private => { + jar.private(&self.key).get(&self.name) + } + }; + if let Some(cookie) = cookie_opt { + if let Ok(val) = serde_json::from_str(cookie.value()) { + return val; + } + } + } + } + } + HashMap::new() + } +} + +/// Use cookies for session storage. +/// +/// `CookieSession` creates sessions which are limited to storing +/// fewer than 4000 bytes of data (as the payload must fit into a single +/// cookie). An Internal Server Error is generated if the session contains more +/// than 4000 bytes. +/// +/// A cookie may have a security policy of *signed* or *private*. Each has a +/// respective `CookieSessionBackend` constructor. +/// +/// A *signed* cookie is stored on the client as plaintext alongside +/// a signature such that the cookie may be viewed but not modified by the +/// client. +/// +/// A *private* cookie is stored on the client as encrypted text +/// such that it may neither be viewed nor modified by the client. +/// +/// The constructors take a key as an argument. +/// This is the private key for cookie session - when this value is changed, +/// all session data is lost. The constructors will panic if the key is less +/// than 32 bytes in length. +/// +/// The backend relies on `cookie` crate to create and read cookies. +/// By default all cookies are percent encoded, but certain symbols may +/// cause troubles when reading cookie, if they are not properly percent encoded. +/// +/// # Example +/// +/// ```rust +/// use actix_session::CookieSession; +/// use actix_web::{App, HttpResponse, HttpServer}; +/// +/// fn main() { +/// let app = App::new().middleware( +/// CookieSession::signed(&[0; 32]) +/// .domain("www.rust-lang.org") +/// .name("actix_session") +/// .path("/") +/// .secure(true)) +/// .resource("/", |r| r.to(|| HttpResponse::Ok())); +/// } +/// ``` +pub struct CookieSession(Rc); + +impl CookieSession { + /// Construct new *signed* `CookieSessionBackend` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn signed(key: &[u8]) -> CookieSession { + CookieSession(Rc::new(CookieSessionInner::new( + key, + CookieSecurity::Signed, + ))) + } + + /// Construct new *private* `CookieSessionBackend` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn private(key: &[u8]) -> CookieSession { + CookieSession(Rc::new(CookieSessionInner::new( + key, + CookieSecurity::Private, + ))) + } + + /// Sets the `path` field in the session cookie being built. + pub fn path>(mut self, value: S) -> CookieSession { + Rc::get_mut(&mut self.0).unwrap().path = value.into(); + self + } + + /// Sets the `name` field in the session cookie being built. + pub fn name>(mut self, value: S) -> CookieSession { + Rc::get_mut(&mut self.0).unwrap().name = value.into(); + self + } + + /// Sets the `domain` field in the session cookie being built. + pub fn domain>(mut self, value: S) -> CookieSession { + Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); + self + } + + /// Sets the `secure` field in the session cookie being built. + /// + /// If the `secure` field is set, a cookie will only be transmitted when the + /// connection is secure - i.e. `https` + pub fn secure(mut self, value: bool) -> CookieSession { + Rc::get_mut(&mut self.0).unwrap().secure = value; + self + } + + /// Sets the `http_only` field in the session cookie being built. + pub fn http_only(mut self, value: bool) -> CookieSession { + Rc::get_mut(&mut self.0).unwrap().http_only = value; + self + } + + /// Sets the `same_site` field in the session cookie being built. + pub fn same_site(mut self, value: SameSite) -> CookieSession { + Rc::get_mut(&mut self.0).unwrap().same_site = Some(value); + self + } + + /// Sets the `max-age` field in the session cookie being built. + pub fn max_age(mut self, value: Duration) -> CookieSession { + Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); + self + } +} + +impl Transform> for CookieSession +where + S: Service, Response = ServiceResponse>, + S::Future: 'static, + S::Error: 'static, +{ + type Response = ServiceResponse; + type Error = S::Error; + type InitError = (); + type Transform = CookieSessionMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(CookieSessionMiddleware { + service, + inner: self.0.clone(), + }) + } +} + +/// Cookie session middleware +pub struct CookieSessionMiddleware { + service: S, + inner: Rc, +} + +impl Service> for CookieSessionMiddleware +where + S: Service, Response = ServiceResponse>, + S::Future: 'static, + S::Error: 'static, +{ + type Response = ServiceResponse; + type Error = S::Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + //self.service.poll_ready().map_err(|e| e.into()) + self.service.poll_ready() + } + + fn call(&mut self, mut req: ServiceRequest

    ) -> Self::Future { + let inner = self.inner.clone(); + let state = self.inner.load(&req); + Session::set_session(state.into_iter(), &mut req); + + Box::new(self.service.call(req).map(move |mut res| { + if let Some(state) = Session::get_changes(&mut res) { + res.checked_expr(|res| inner.set_cookie(res, state)) + } else { + res + } + })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, App}; + + #[test] + fn cookie_session() { + let mut app = test::init_service( + App::new() + .middleware(CookieSession::signed(&[0; 32]).secure(false)) + .resource("/", |r| { + r.to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + }) + }), + ); + + let request = test::TestRequest::get().to_request(); + let response = test::block_on(app.call(request)).unwrap(); + assert!(response + .cookies() + .find(|c| c.name() == "actix-session") + .is_some()); + } + + #[test] + fn cookie_session_extractor() { + let mut app = test::init_service( + App::new() + .middleware(CookieSession::signed(&[0; 32]).secure(false)) + .resource("/", |r| { + r.to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + }) + }), + ); + + let request = test::TestRequest::get().to_request(); + let response = test::block_on(app.call(request)).unwrap(); + assert!(response + .cookies() + .find(|c| c.name() == "actix-session") + .is_some()); + } +} diff --git a/session/src/lib.rs b/session/src/lib.rs index 0271a13f..f57e11f2 100644 --- a/session/src/lib.rs +++ b/session/src/lib.rs @@ -1,121 +1,61 @@ //! User sessions. //! -//! Actix provides a general solution for session management. The -//! [**SessionStorage**](struct.SessionStorage.html) -//! middleware can be used with different backend types to store session -//! data in different backends. +//! Actix provides a general solution for session management. Session +//! middlewares could provide different implementations which could +//! be accessed via general session api. //! //! By default, only cookie session backend is implemented. Other //! backend implementations can be added. //! -//! [**CookieSessionBackend**](struct.CookieSessionBackend.html) -//! uses cookies as session storage. `CookieSessionBackend` creates sessions -//! which are limited to storing fewer than 4000 bytes of data, as the payload -//! must fit into a single cookie. An internal server error is generated if a -//! session contains more than 4000 bytes. -//! -//! A cookie may have a security policy of *signed* or *private*. Each has -//! a respective `CookieSessionBackend` constructor. -//! -//! A *signed* cookie may be viewed but not modified by the client. A *private* -//! cookie may neither be viewed nor modified by the client. -//! -//! The constructors take a key as an argument. This is the private key -//! for cookie session - when this value is changed, all session data is lost. -//! -//! In general, you create a `SessionStorage` middleware and initialize it -//! with specific backend implementation, such as a `CookieSessionBackend`. -//! To access session data, -//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session) -//! must be used. This method returns a -//! [*Session*](struct.Session.html) object, which allows us to get or set -//! session data. +//! In general, you insert a *session* middleware and initialize it +//! , such as a `CookieSessionBackend`. To access session data, +//! [*Session*](struct.Session.html) extractor must be used. Session +//! extractor allows us to get or set session data. //! //! ```rust -//! # extern crate actix_web; -//! # extern crate actix; -//! use actix_web::{server, App, HttpRequest, Result}; -//! use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; +//! use actix_web::{App, HttpServer, HttpResponse, Error}; +//! use actix_session::{Session, CookieSession}; //! -//! fn index(req: HttpRequest) -> Result<&'static str> { +//! fn index(session: Session) -> Result<&'static str, Error> { //! // access session data -//! if let Some(count) = req.session().get::("counter")? { +//! if let Some(count) = session.get::("counter")? { //! println!("SESSION value: {}", count); -//! req.session().set("counter", count+1)?; +//! session.set("counter", count+1)?; //! } else { -//! req.session().set("counter", 1)?; +//! session.set("counter", 1)?; //! } //! //! Ok("Welcome!") //! } //! -//! fn main() { -//! actix::System::run(|| { -//! server::new( -//! || App::new().middleware( -//! SessionStorage::new( // <- create session middleware -//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend +//! fn main() -> std::io::Result<()> { +//! let sys = actix_rt::System::new("example"); // <- create Actix runtime +//! +//! HttpServer::new( +//! || App::new().middleware( +//! CookieSession::signed(&[0; 32]) // <- create cookie based session middleware //! .secure(false) -//! ))) -//! .bind("127.0.0.1:59880").unwrap() -//! .start(); -//! # actix::System::current().stop(); -//! }); +//! ) +//! .resource("/", |r| r.to(|| HttpResponse::Ok()))) +//! .bind("127.0.0.1:59880")? +//! .start(); +//! # actix_rt::System::current().stop(); +//! sys.run(); +//! Ok(()) //! } //! ``` use std::cell::RefCell; -use std::collections::HashMap; -use std::marker::PhantomData; use std::rc::Rc; -use std::sync::Arc; -use cookie::{Cookie, CookieJar, Key, SameSite}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; -use http::header::{self, HeaderValue}; +use actix_web::{Error, FromRequest, HttpMessage}; +use actix_web::{ServiceFromRequest, ServiceRequest, ServiceResponse}; +use hashbrown::HashMap; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; -use serde_json::error::Error as JsonError; -use time::Duration; -use error::{Error, ResponseError, Result}; -use handler::FromRequest; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; - -/// The helper trait to obtain your session data from a request. -/// -/// ```rust -/// use actix_web::middleware::session::RequestSession; -/// use actix_web::*; -/// -/// 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 { - /// Get the session from the request - fn session(&self) -> Session; -} - -impl RequestSession for HttpRequest { - fn session(&self) -> Session { - if let Some(s_impl) = self.extensions().get::>() { - return Session(SessionInner::Session(Arc::clone(&s_impl))); - } - Session(SessionInner::None) - } -} +mod cookie; +pub use crate::cookie::CookieSession; /// The high-level interface you use to modify session data. /// @@ -124,80 +64,9 @@ impl RequestSession for HttpRequest { /// method. `RequestSession` trait is implemented for `HttpRequest`. /// /// ```rust -/// use actix_web::middleware::session::RequestSession; +/// use actix_session::Session; /// use actix_web::*; /// -/// 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(SessionInner); - -enum SessionInner { - Session(Arc), - None, -} - -impl Session { - /// Get a `value` from the session. - pub fn get(&self, key: &str) -> Result> { - match self.0 { - SessionInner::Session(ref sess) => { - if let Some(s) = sess.as_ref().0.borrow().get(key) { - Ok(Some(serde_json::from_str(s)?)) - } else { - Ok(None) - } - } - SessionInner::None => Ok(None), - } - } - - /// Set a `value` from the session. - pub fn set(&self, key: &str, value: T) -> Result<()> { - match self.0 { - SessionInner::Session(ref sess) => { - sess.as_ref() - .0 - .borrow_mut() - .set(key, serde_json::to_string(&value)?); - Ok(()) - } - SessionInner::None => Ok(()), - } - } - - /// Remove value from the session. - pub fn remove(&self, key: &str) { - match self.0 { - SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().remove(key), - SessionInner::None => (), - } - } - - /// Clear the session. - pub fn clear(&self) { - match self.0 { - SessionInner::Session(ref sess) => sess.as_ref().0.borrow_mut().clear(), - SessionInner::None => (), - } - } -} - -/// Extractor implementation for Session type. -/// -/// ```rust -/// # use actix_web::*; -/// use actix_web::middleware::session::Session; -/// /// fn index(session: Session) -> Result<&'static str> { /// // access session data /// if let Some(count) = session.get::("counter")? { @@ -210,409 +79,108 @@ impl Session { /// } /// # fn main() {} /// ``` -impl FromRequest for Session { - type Config = (); - type Result = Session; +pub struct Session(Rc>); - #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { - req.session() - } +#[derive(Default)] +struct SessionInner { + state: HashMap, + changed: bool, } -struct SessionImplCell(RefCell>); - -/// Session storage middleware -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::session::{CookieSessionBackend, SessionStorage}; -/// use actix_web::App; -/// -/// fn main() { -/// let app = App::new().middleware(SessionStorage::new( -/// // <- create session middleware -/// CookieSessionBackend::signed(&[0; 32]) // <- create cookie session backend -/// .secure(false), -/// )); -/// } -/// ``` -pub struct SessionStorage(T, PhantomData); - -impl> SessionStorage { - /// Create session storage - pub fn new(backend: T) -> SessionStorage { - SessionStorage(backend, PhantomData) - } -} - -impl> Middleware for SessionStorage { - fn start(&self, req: &HttpRequest) -> Result { - let mut req = req.clone(); - - let fut = self.0.from_request(&mut req).then(move |res| match res { - Ok(sess) => { - req.extensions_mut() - .insert(Arc::new(SessionImplCell(RefCell::new(Box::new(sess))))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(s_box) = req.extensions().get::>() { - s_box.0.borrow_mut().write(resp) +impl Session { + /// Get a `value` from the session. + pub fn get(&self, key: &str) -> Result, Error> { + if let Some(s) = self.0.borrow().state.get(key) { + Ok(Some(serde_json::from_str(s)?)) } else { - Ok(Response::Done(resp)) + Ok(None) } } -} -/// A simple key-value storage interface that is internally used by `Session`. -pub trait SessionImpl: 'static { - /// Get session value by key - fn get(&self, key: &str) -> Option<&str>; + /// Set a `value` from the session. + pub fn set(&self, key: &str, value: T) -> Result<(), Error> { + let mut inner = self.0.borrow_mut(); + inner.changed = true; + inner + .state + .insert(key.to_owned(), serde_json::to_string(&value)?); + Ok(()) + } - /// Set session value - fn set(&mut self, key: &str, value: String); + /// Remove value from the session. + pub fn remove(&self, key: &str) { + let mut inner = self.0.borrow_mut(); + inner.changed = true; + inner.state.remove(key); + } - /// Remove specific key from session - fn remove(&mut self, key: &str); + /// Clear the session. + pub fn clear(&self) { + let mut inner = self.0.borrow_mut(); + inner.changed = true; + inner.state.clear() + } - /// Remove all values from session - fn clear(&mut self); + pub fn set_session

    ( + data: impl Iterator, + req: &mut ServiceRequest

    , + ) { + let session = Session::get_session(req); + let mut inner = session.0.borrow_mut(); + inner.state.extend(data); + } - /// Write session to storage backend. - fn write(&self, resp: HttpResponse) -> Result; -} - -/// Session's storage backend trait definition. -pub trait SessionBackend: Sized + 'static { - /// Session item - type Session: SessionImpl; - /// Future that reads session - type ReadFuture: Future; - - /// Parse the session from request and load data from a storage backend. - fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; -} - -/// Session that uses signed cookies as session storage -pub struct CookieSession { - changed: bool, - state: HashMap, - inner: Rc, -} - -/// Errors that can occur during handling cookie session -#[derive(Fail, Debug)] -pub enum CookieSessionError { - /// Size of the serialized session is greater than 4000 bytes. - #[fail(display = "Size of the serialized session is greater than 4000 bytes.")] - Overflow, - /// Fail to serialize session. - #[fail(display = "Fail to serialize session")] - Serialize(JsonError), -} - -impl ResponseError for CookieSessionError {} - -impl SessionImpl for CookieSession { - fn get(&self, key: &str) -> Option<&str> { - if let Some(s) = self.state.get(key) { - Some(s) + pub fn get_changes( + res: &mut ServiceResponse, + ) -> Option> { + if let Some(s_impl) = res + .request() + .extensions() + .get::>>() + { + let state = + std::mem::replace(&mut s_impl.borrow_mut().state, HashMap::new()); + Some(state.into_iter()) } else { None } } - fn set(&mut self, key: &str, value: String) { - self.changed = true; - self.state.insert(key.to_owned(), value); - } - - fn remove(&mut self, key: &str) { - self.changed = true; - self.state.remove(key); - } - - fn clear(&mut self) { - self.changed = true; - self.state.clear() - } - - fn write(&self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, &self.state); + fn get_session(req: R) -> Session { + if let Some(s_impl) = req.extensions().get::>>() { + return Session(Rc::clone(&s_impl)); } - Ok(Response::Done(resp)) + let inner = Rc::new(RefCell::new(SessionInner::default())); + req.extensions_mut().insert(inner.clone()); + Session(inner) } } -enum CookieSecurity { - Signed, - Private, -} - -struct CookieSessionInner { - key: Key, - security: CookieSecurity, - name: String, - path: String, - domain: Option, - secure: bool, - http_only: bool, - max_age: Option, - same_site: Option, -} - -impl CookieSessionInner { - fn new(key: &[u8], security: CookieSecurity) -> CookieSessionInner { - CookieSessionInner { - security, - key: Key::from_master(key), - name: "actix-session".to_owned(), - path: "/".to_owned(), - domain: None, - secure: true, - http_only: true, - max_age: None, - same_site: None, - } - } - - fn set_cookie( - &self, resp: &mut HttpResponse, state: &HashMap, - ) -> Result<()> { - let value = - serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; - if value.len() > 4064 { - return Err(CookieSessionError::Overflow.into()); - } - - let mut cookie = Cookie::new(self.name.clone(), value); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(self.http_only); - - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - if let Some(same_site) = self.same_site { - cookie.set_same_site(same_site); - } - - let mut jar = CookieJar::new(); - - match self.security { - CookieSecurity::Signed => jar.signed(&self.key).add(cookie), - CookieSecurity::Private => jar.private(&self.key).add(cookie), - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.encoded().to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } - - Ok(()) - } - - fn load(&self, req: &mut HttpRequest) -> HashMap { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); - - let cookie_opt = match self.security { - CookieSecurity::Signed => jar.signed(&self.key).get(&self.name), - CookieSecurity::Private => { - jar.private(&self.key).get(&self.name) - } - }; - if let Some(cookie) = cookie_opt { - if let Ok(val) = serde_json::from_str(cookie.value()) { - return val; - } - } - } - } - } - HashMap::new() - } -} - -/// Use cookies for session storage. -/// -/// `CookieSessionBackend` creates sessions which are limited to storing -/// fewer than 4000 bytes of data (as the payload must fit into a single -/// cookie). An Internal Server Error is generated if the session contains more -/// than 4000 bytes. -/// -/// A cookie may have a security policy of *signed* or *private*. Each has a -/// respective `CookieSessionBackend` constructor. -/// -/// A *signed* cookie is stored on the client as plaintext alongside -/// a signature such that the cookie may be viewed but not modified by the -/// client. -/// -/// A *private* cookie is stored on the client as encrypted text -/// such that it may neither be viewed nor modified by the client. -/// -/// The constructors take a key as an argument. -/// This is the private key for cookie session - when this value is changed, -/// all session data is lost. The constructors will panic if the key is less -/// than 32 bytes in length. -/// -/// The backend relies on `cookie` crate to create and read cookies. -/// By default all cookies are percent encoded, but certain symbols may -/// cause troubles when reading cookie, if they are not properly percent encoded. -/// -/// # Example +/// Extractor implementation for Session type. /// /// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::session::CookieSessionBackend; +/// # use actix_web::*; +/// use actix_session::Session; /// -/// # fn main() { -/// let backend: CookieSessionBackend = CookieSessionBackend::signed(&[0; 32]) -/// .domain("www.rust-lang.org") -/// .name("actix_session") -/// .path("/") -/// .secure(true); -/// # } +/// fn index(session: Session) -> Result<&'static str> { +/// // access session data +/// if let Some(count) = session.get::("counter")? { +/// session.set("counter", count + 1)?; +/// } else { +/// session.set("counter", 1)?; +/// } +/// +/// Ok("Welcome!") +/// } +/// # fn main() {} /// ``` -pub struct CookieSessionBackend(Rc); +impl

    FromRequest

    for Session { + type Error = Error; + type Future = Result; + type Config = (); -impl CookieSessionBackend { - /// Construct new *signed* `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn signed(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(CookieSessionInner::new( - key, - CookieSecurity::Signed, - ))) - } - - /// Construct new *private* `CookieSessionBackend` instance. - /// - /// Panics if key length is less than 32 bytes. - pub fn private(key: &[u8]) -> CookieSessionBackend { - CookieSessionBackend(Rc::new(CookieSessionInner::new( - key, - CookieSecurity::Private, - ))) - } - - /// Sets the `path` field in the session cookie being built. - pub fn path>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().path = value.into(); - self - } - - /// Sets the `name` field in the session cookie being built. - pub fn name>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().name = value.into(); - self - } - - /// Sets the `domain` field in the session cookie being built. - pub fn domain>(mut self, value: S) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); - self - } - - /// Sets the `secure` field in the session cookie being built. - /// - /// If the `secure` field is set, a cookie will only be transmitted when the - /// connection is secure - i.e. `https` - pub fn secure(mut self, value: bool) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().secure = value; - self - } - - /// Sets the `http_only` field in the session cookie being built. - pub fn http_only(mut self, value: bool) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().http_only = value; - self - } - - /// Sets the `same_site` field in the session cookie being built. - pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().same_site = Some(value); - self - } - - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieSessionBackend { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); - self - } -} - -impl SessionBackend for CookieSessionBackend { - type Session = CookieSession; - type ReadFuture = FutureResult; - - fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { - let state = self.0.load(req); - FutOk(CookieSession { - changed: false, - inner: Rc::clone(&self.0), - state, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use application::App; - use test; - - #[test] - fn cookie_session() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/", |r| { - r.f(|req| { - let _ = req.session().set("counter", 100); - "test" - }) - }) - }); - - let request = srv.get().uri(srv.url("/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.cookie("actix-session").is_some()); - } - - #[test] - fn cookie_session_extractor() { - let mut srv = test::TestServer::with_factory(|| { - App::new() - .middleware(SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]).secure(false), - )).resource("/", |r| { - r.with(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - }) - }) - }); - - let request = srv.get().uri(srv.url("/")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.cookie("actix-session").is_some()); + #[inline] + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { + Ok(Session::get_session(req)) } } diff --git a/src/request.rs b/src/request.rs index d90627f5..75daf59d 100644 --- a/src/request.rs +++ b/src/request.rs @@ -64,12 +64,6 @@ impl HttpRequest { self.head().uri.path() } - #[inline] - /// Returns Request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - /// The query string in the URL. /// /// E.g., id=10 @@ -93,18 +87,6 @@ impl HttpRequest { &self.path } - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.head.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.head.extensions_mut() - } - /// Application extensions #[inline] pub fn app_extensions(&self) -> &Extensions { @@ -130,8 +112,26 @@ impl HttpMessage for HttpRequest { type Stream = (); #[inline] + /// Returns Request's headers. fn headers(&self) -> &HeaderMap { - self.headers() + &self.head().headers + } + + #[inline] + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head.headers + } + + /// Request extensions + #[inline] + fn extensions(&self) -> Ref { + self.head.extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() } #[inline] diff --git a/src/service.rs b/src/service.rs index 50b2924a..0da66439 100644 --- a/src/service.rs +++ b/src/service.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::cell::{Ref, RefMut}; use std::rc::Rc; -use actix_http::body::{Body, MessageBody, ResponseBody}; +use actix_http::body::{Body, ResponseBody}; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{ Error, Extensions, HttpMessage, Payload, PayloadStream, Request, RequestHead, @@ -84,12 +84,6 @@ impl

    ServiceRequest

    { self.head().uri.path() } - #[inline] - /// Returns Request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - /// The query string in the URL. /// /// E.g., id=10 @@ -118,18 +112,6 @@ impl

    ServiceRequest

    { &mut self.req.path } - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.req.head.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.req.head.extensions_mut() - } - /// Application extensions #[inline] pub fn app_extensions(&self) -> &Extensions { @@ -147,8 +129,27 @@ impl

    HttpMessage for ServiceRequest

    { type Stream = P; #[inline] + /// Returns Request's headers. fn headers(&self) -> &HeaderMap { - self.req.headers() + &self.head().headers + } + + #[inline] + /// Mutable reference to the request's headers. + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head_mut().headers + } + + /// Request extensions + #[inline] + fn extensions(&self) -> Ref { + self.req.head.extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + fn extensions_mut(&self) -> RefMut { + self.req.head.extensions_mut() } #[inline] @@ -229,6 +230,23 @@ impl

    HttpMessage for ServiceFromRequest

    { self.req.headers() } + #[inline] + fn headers_mut(&mut self) -> &mut HeaderMap { + self.req.headers_mut() + } + + /// Request extensions + #[inline] + fn extensions(&self) -> Ref { + self.req.head.extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + fn extensions_mut(&self) -> RefMut { + self.req.head.extensions_mut() + } + #[inline] fn take_payload(&mut self) -> Payload { std::mem::replace(&mut self.payload, Payload::None) @@ -275,11 +293,26 @@ impl ServiceResponse { pub fn headers_mut(&mut self) -> &mut HeaderMap { self.response.headers_mut() } + + /// Execute closure and in case of error convert it to response. + pub fn checked_expr(mut self, f: F) -> Self + where + F: FnOnce(&mut Self) -> Result<(), E>, + E: Into, + { + match f(&mut self) { + Ok(_) => self, + Err(err) => { + let res: Response = err.into().into(); + ServiceResponse::new(self.request, res.into_body()) + } + } + } } -impl ServiceResponse { +impl ServiceResponse { /// Set a new body - pub fn map_body(self, f: F) -> ServiceResponse + pub fn map_body(self, f: F) -> ServiceResponse where F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, { @@ -292,7 +325,7 @@ impl ServiceResponse { } } -impl std::ops::Deref for ServiceResponse { +impl std::ops::Deref for ServiceResponse { type Target = Response; fn deref(&self) -> &Response { @@ -300,19 +333,19 @@ impl std::ops::Deref for ServiceResponse { } } -impl std::ops::DerefMut for ServiceResponse { +impl std::ops::DerefMut for ServiceResponse { fn deref_mut(&mut self) -> &mut Response { self.response_mut() } } -impl Into> for ServiceResponse { +impl Into> for ServiceResponse { fn into(self) -> Response { self.response } } -impl IntoFuture for ServiceResponse { +impl IntoFuture for ServiceResponse { type Item = ServiceResponse; type Error = Error; type Future = FutureResult, Error>; diff --git a/src/test.rs b/src/test.rs index 7ceedacc..8b6667a4 100644 --- a/src/test.rs +++ b/src/test.rs @@ -8,11 +8,12 @@ use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{Extensions, PayloadStream, Request}; use actix_router::{Path, Url}; use actix_rt::Runtime; +use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; use futures::Future; use crate::request::HttpRequest; -use crate::service::{ServiceFromRequest, ServiceRequest}; +use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; thread_local! { static RT: RefCell = { @@ -37,6 +38,34 @@ where RT.with(move |rt| rt.borrow_mut().block_on(f)) } +/// This method accepts application builder instance, and constructs +/// service. +/// +/// ```rust +/// use actix_http::http::{test, App, HttpResponse}; +/// +/// fn main() { +/// let app = test::init_service( +/// App::new() +/// .resource("/test", |r| r.to(|| HttpResponse::Ok())) +/// ) +/// +/// let req = TestRequest::with_uri("/test").to_request(); +/// let resp = block_on(srv.call(req)).unwrap(); +/// assert_eq!(resp.status(), StatusCode::OK); +/// } +/// ``` +pub fn init_service( + app: R, +) -> impl Service, Error = E> +where + R: IntoNewService, + S: NewService, Error = E>, + S::InitError: std::fmt::Debug, +{ + block_on(app.into_new_service().new_service(&())).unwrap() +} + /// Test `Request` builder. /// /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. @@ -112,6 +141,22 @@ impl TestRequest { } } + /// Create TestRequest and set method to `Method::GET` + pub fn get() -> TestRequest { + TestRequest { + req: HttpTestRequest::default().method(Method::GET).take(), + extensions: Extensions::new(), + } + } + + /// Create TestRequest and set method to `Method::POST` + pub fn post() -> TestRequest { + TestRequest { + req: HttpTestRequest::default().method(Method::POST).take(), + extensions: Extensions::new(), + } + } + /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { self.req.version(ver); From 0cf73f1a04d23995bffd8db21f00107713baf209 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 18:52:29 -0800 Subject: [PATCH 0982/1635] move session to different folder --- Cargo.toml | 2 +- {session => actix-session}/Cargo.toml | 0 {session => actix-session}/src/cookie.rs | 0 {session => actix-session}/src/lib.rs | 0 src/test.rs | 14 +++++++++----- 5 files changed, 10 insertions(+), 6 deletions(-) rename {session => actix-session}/Cargo.toml (100%) rename {session => actix-session}/src/cookie.rs (100%) rename {session => actix-session}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 2f69c7ef..2f50b210 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ path = "src/lib.rs" [workspace] members = [ ".", - "session", + "actix-session", "staticfiles", ] diff --git a/session/Cargo.toml b/actix-session/Cargo.toml similarity index 100% rename from session/Cargo.toml rename to actix-session/Cargo.toml diff --git a/session/src/cookie.rs b/actix-session/src/cookie.rs similarity index 100% rename from session/src/cookie.rs rename to actix-session/src/cookie.rs diff --git a/session/src/lib.rs b/actix-session/src/lib.rs similarity index 100% rename from session/src/lib.rs rename to actix-session/src/lib.rs diff --git a/src/test.rs b/src/test.rs index 8b6667a4..8495ea89 100644 --- a/src/test.rs +++ b/src/test.rs @@ -42,16 +42,20 @@ where /// service. /// /// ```rust -/// use actix_http::http::{test, App, HttpResponse}; +/// use actix_web::{test, App, HttpResponse, http::StatusCode}; +/// use actix_service::Service; /// /// fn main() { -/// let app = test::init_service( +/// let mut app = test::init_service( /// App::new() /// .resource("/test", |r| r.to(|| HttpResponse::Ok())) -/// ) +/// ); /// -/// let req = TestRequest::with_uri("/test").to_request(); -/// let resp = block_on(srv.call(req)).unwrap(); +/// // Create request object +/// let req = test::TestRequest::with_uri("/test").to_request(); +/// +/// // Execute application +/// let resp = test::block_on(app.call(req)).unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` From 81273f71ef521fe9ec131d13ab4a57e19bb13c99 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 19:03:59 -0800 Subject: [PATCH 0983/1635] update tests --- src/app.rs | 68 +++++++++++++++++++++++++--------------------------- src/scope.rs | 33 ++++++++++--------------- src/test.rs | 10 ++++---- 3 files changed, 51 insertions(+), 60 deletions(-) diff --git a/src/app.rs b/src/app.rs index b2164501..e38180c4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -902,16 +902,14 @@ mod tests { use actix_http::http::{Method, StatusCode}; use super::*; - use crate::test::{block_on, TestRequest}; + use crate::test::{self, block_on, TestRequest}; use crate::{web, HttpResponse, State}; #[test] fn test_default_resource() { - let app = App::new() - .resource("/test", |r| r.to(|| HttpResponse::Ok())) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); - + let mut srv = test::init_service( + App::new().resource("/test", |r| r.to(|| HttpResponse::Ok())), + ); let req = TestRequest::with_uri("/test").to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -920,15 +918,15 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let app = App::new() - .resource("/test", |r| r.to(|| HttpResponse::Ok())) - .resource("/test2", |r| { - r.default_resource(|r| r.to(|| HttpResponse::Created())) - .route(web::get().to(|| HttpResponse::Ok())) - }) - .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = test::init_service( + App::new() + .resource("/test", |r| r.to(|| HttpResponse::Ok())) + .resource("/test2", |r| { + r.default_resource(|r| r.to(|| HttpResponse::Created())) + .route(web::get().to(|| HttpResponse::Ok())) + }) + .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), + ); let req = TestRequest::with_uri("/blah").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -947,21 +945,21 @@ mod tests { #[test] fn test_state() { - let app = App::new() - .state(10usize) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = test::init_service( + App::new() + .state(10usize) + .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + ); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let app = App::new() - .state(10u32) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = test::init_service( + App::new() + .state(10u32) + .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + ); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -970,21 +968,21 @@ mod tests { #[test] fn test_state_factory() { - let app = App::new() - .state_factory(|| Ok::<_, ()>(10usize)) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = test::init_service( + App::new() + .state_factory(|| Ok::<_, ()>(10usize)) + .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + ); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let app = App::new() - .state_factory(|| Ok::<_, ()>(10u32)) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = test::init_service( + App::new() + .state_factory(|| Ok::<_, ()>(10u32)) + .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + ); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); diff --git a/src/scope.rs b/src/scope.rs index fa7392b4..dc88388f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -509,17 +509,14 @@ mod tests { use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; - use crate::test::{block_on, TestRequest}; + use crate::test::{self, block_on, TestRequest}; use crate::{guard, web, App, HttpRequest, HttpResponse}; #[test] fn test_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.resource("/path1", |r| r.to(|| HttpResponse::Ok())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = test::init_service(App::new().scope("/app", |scope| { + scope.resource("/path1", |r| r.to(|| HttpResponse::Ok())) + })); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -528,14 +525,11 @@ mod tests { #[test] fn test_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("", |r| r.to(|| HttpResponse::Ok())) - .resource("/", |r| r.to(|| HttpResponse::Created())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = test::init_service(App::new().scope("/app", |scope| { + scope + .resource("", |r| r.to(|| HttpResponse::Ok())) + .resource("/", |r| r.to(|| HttpResponse::Created())) + })); let req = TestRequest::with_uri("/app").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -548,12 +542,9 @@ mod tests { #[test] fn test_scope_root2() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("", |r| r.to(|| HttpResponse::Ok())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = test::init_service(App::new().scope("/app/", |scope| { + scope.resource("", |r| r.to(|| HttpResponse::Ok())) + })); let req = TestRequest::with_uri("/app").to_request(); let resp = block_on(srv.call(req)).unwrap(); diff --git a/src/test.rs b/src/test.rs index 8495ea89..22bfe0c3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -41,7 +41,7 @@ where /// This method accepts application builder instance, and constructs /// service. /// -/// ```rust +/// ```rust,ignore /// use actix_web::{test, App, HttpResponse, http::StatusCode}; /// use actix_service::Service; /// @@ -80,7 +80,9 @@ where /// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// /// ```rust,ignore -/// use actix_web::test; +/// # use futures::IntoFuture; +/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; +/// use actix_web::http::{header, StatusCode}; /// /// fn index(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { @@ -94,11 +96,11 @@ where /// let req = test::TestRequest::with_header("content-type", "text/plain") /// .to_http_request(); /// -/// let resp = test::block_on(index(req)); +/// let resp = test::block_on(index(req).into_future()).unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); -/// let resp = test::block_on(index(req)); +/// let resp = test::block_on(index(req).into_future()).unwrap(); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` From d85468f7e107ef5116e3fad40dc9b9ed57bfdb04 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 19:07:07 -0800 Subject: [PATCH 0984/1635] do not expose headers_mut via HttpMessage --- src/client/response.rs | 4 ---- src/httpmessage.rs | 8 -------- src/request.rs | 10 +++++----- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/client/response.rs b/src/client/response.rs index 236a6338..7c6cdf64 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -24,10 +24,6 @@ impl HttpMessage for ClientResponse { &self.head.headers } - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head.headers - } - fn extensions(&self) -> Ref { self.head.extensions() } diff --git a/src/httpmessage.rs b/src/httpmessage.rs index d95f82f5..3c049c09 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -32,9 +32,6 @@ pub trait HttpMessage: Sized { /// Read the message headers. fn headers(&self) -> &HeaderMap; - /// Mutable reference to the message's headers. - fn headers_mut(&mut self) -> &mut HeaderMap; - /// Message payload stream fn take_payload(&mut self) -> Payload; @@ -280,11 +277,6 @@ where (**self).headers() } - /// Mutable reference to the message's headers. - fn headers_mut(&mut self) -> &mut HeaderMap { - (**self).headers_mut() - } - /// Message payload stream fn take_payload(&mut self) -> Payload { (**self).take_payload() diff --git a/src/request.rs b/src/request.rs index 761a159d..0ea251ea 100644 --- a/src/request.rs +++ b/src/request.rs @@ -22,11 +22,6 @@ impl

    HttpMessage for Request

    { &self.head().headers } - #[inline] - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - /// Request extensions #[inline] fn extensions(&self) -> Ref { @@ -107,6 +102,11 @@ impl

    Request

    { &mut *self.head } + /// Mutable reference to the message's headers. + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head_mut().headers + } + /// Request's uri. #[inline] pub fn uri(&self) -> &Uri { From f71354783e3d7d75fe9210fa612745be0a1588b3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 19:10:45 -0800 Subject: [PATCH 0985/1635] update HttpMessage impls --- src/request.rs | 5 ----- src/service.rs | 17 ++++++----------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/request.rs b/src/request.rs index 75daf59d..211f60b8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -117,11 +117,6 @@ impl HttpMessage for HttpRequest { &self.head().headers } - #[inline] - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head.headers - } - /// Request extensions #[inline] fn extensions(&self) -> Ref { diff --git a/src/service.rs b/src/service.rs index 0da66439..7d17527a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -78,6 +78,12 @@ impl

    ServiceRequest

    { self.head().version } + #[inline] + /// Returns mutable Request's headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head_mut().headers + } + /// The target path of this Request. #[inline] pub fn path(&self) -> &str { @@ -134,12 +140,6 @@ impl

    HttpMessage for ServiceRequest

    { &self.head().headers } - #[inline] - /// Mutable reference to the request's headers. - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - /// Request extensions #[inline] fn extensions(&self) -> Ref { @@ -230,11 +230,6 @@ impl

    HttpMessage for ServiceFromRequest

    { self.req.headers() } - #[inline] - fn headers_mut(&mut self) -> &mut HeaderMap { - self.req.headers_mut() - } - /// Request extensions #[inline] fn extensions(&self) -> Ref { From 0de47211b2065d48a6ac7d341870b05ab5db8f79 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 19:30:44 -0800 Subject: [PATCH 0986/1635] tune App::default_resource signature --- src/app.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app.rs b/src/app.rs index e38180c4..27ca5c95 100644 --- a/src/app.rs +++ b/src/app.rs @@ -426,12 +426,15 @@ where /// /// Default resource works with resources only and does not work with /// custom services. - pub fn default_resource(mut self, f: F) -> Self + pub fn default_resource(mut self, f: F) -> Self where - F: FnOnce(Resource

    ) -> R, - R: IntoNewService>, - U: NewService, Response = ServiceResponse, Error = ()> - + 'static, + F: FnOnce(Resource

    ) -> Resource, + U: NewService< + ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, { // create and configure default resource self.default = Some(Rc::new(boxed::new_service( From 1a80b70868a79cc5fc03f04b105adac7bae36769 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 19:41:50 -0800 Subject: [PATCH 0987/1635] add Responder impl for InternalError --- src/responder.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/responder.rs b/src/responder.rs index b2fd848f..dedfa1b4 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,3 +1,4 @@ +use actix_http::error::InternalError; use actix_http::{dev::ResponseBuilder, http::StatusCode, Error, Response}; use bytes::{Bytes, BytesMut}; use futures::future::{err, ok, Either as EitherFuture, FutureResult}; @@ -252,6 +253,18 @@ where } } +impl Responder for InternalError +where + T: std::fmt::Debug + std::fmt::Display + 'static, +{ + type Error = Error; + type Future = Result; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + Err(self.into()) + } +} + pub struct ResponseFuture(T); impl ResponseFuture { From 34c8b95a35a8830e70a888cbb063678ee8b16b32 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 21:15:18 -0800 Subject: [PATCH 0988/1635] allow to extract body from response --- src/body.rs | 6 ++++++ src/response.rs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/body.rs b/src/body.rs index 1f218c4b..d72f6c37 100644 --- a/src/body.rs +++ b/src/body.rs @@ -59,6 +59,12 @@ impl ResponseBody { } } +impl ResponseBody { + pub fn take_body(&mut self) -> ResponseBody { + std::mem::replace(self, ResponseBody::Other(Body::None)) + } +} + impl ResponseBody { pub fn as_ref(&self) -> Option<&B> { if let ResponseBody::Body(ref b) = self { diff --git a/src/response.rs b/src/response.rs index 277890e4..4e1fe214 100644 --- a/src/response.rs +++ b/src/response.rs @@ -254,6 +254,11 @@ impl Response { error: self.error, } } + + /// Extract response body + pub fn take_body(&mut self) -> ResponseBody { + self.body.take_body() + } } impl fmt::Debug for Response { From 889d67a356a163fb444a4e7921394c765695427a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 21:19:12 -0800 Subject: [PATCH 0989/1635] add Stream impl for ResponseBody --- src/body.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/body.rs b/src/body.rs index d72f6c37..b7e8ec98 100644 --- a/src/body.rs +++ b/src/body.rs @@ -91,6 +91,15 @@ impl MessageBody for ResponseBody { } } +impl Stream for ResponseBody { + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.poll_next() + } +} + /// Represents various types of http message body. pub enum Body { /// Empty response. `Content-Length` header is not set. From 6efc3438b83497577b8d791b77c6600e95660f35 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Mar 2019 22:10:08 -0800 Subject: [PATCH 0990/1635] refactor and enable some tests for staticfiles --- .travis.yml | 4 +- Cargo.toml | 2 +- {staticfiles => actix-staticfiles}/CHANGES.md | 0 {staticfiles => actix-staticfiles}/Cargo.toml | 3 +- {staticfiles => actix-staticfiles}/README.md | 0 actix-staticfiles/src/config.rs | 70 + actix-staticfiles/src/error.rs | 41 + actix-staticfiles/src/lib.rs | 1482 ++++++++++++ actix-staticfiles/src/named.rs | 438 ++++ actix-staticfiles/tests/test space.binary | 1 + actix-staticfiles/tests/test.binary | 1 + actix-staticfiles/tests/test.png | Bin 0 -> 168 bytes examples/basic.rs | 25 +- src/app.rs | 45 +- src/lib.rs | 24 +- src/service.rs | 64 +- src/test.rs | 28 + staticfiles/src/lib.rs | 2033 ----------------- 18 files changed, 2198 insertions(+), 2063 deletions(-) rename {staticfiles => actix-staticfiles}/CHANGES.md (100%) rename {staticfiles => actix-staticfiles}/Cargo.toml (93%) rename {staticfiles => actix-staticfiles}/README.md (100%) create mode 100644 actix-staticfiles/src/config.rs create mode 100644 actix-staticfiles/src/error.rs create mode 100644 actix-staticfiles/src/lib.rs create mode 100644 actix-staticfiles/src/named.rs create mode 100644 actix-staticfiles/tests/test space.binary create mode 100644 actix-staticfiles/tests/test.binary create mode 100644 actix-staticfiles/tests/test.png delete mode 100644 staticfiles/src/lib.rs diff --git a/.travis.yml b/.travis.yml index 1d3c227a..55a03ec8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ before_script: script: - cargo clean - - cargo test -- --nocapture + - cargo test --all -- --nocapture # Upload docs after_success: @@ -49,7 +49,7 @@ after_success: fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then - cargo tarpaulin --out Xml + cargo tarpaulin --out Xml --all bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi diff --git a/Cargo.toml b/Cargo.toml index 2f50b210..acacb2f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ path = "src/lib.rs" members = [ ".", "actix-session", - "staticfiles", + "actix-staticfiles", ] [package.metadata.docs.rs] diff --git a/staticfiles/CHANGES.md b/actix-staticfiles/CHANGES.md similarity index 100% rename from staticfiles/CHANGES.md rename to actix-staticfiles/CHANGES.md diff --git a/staticfiles/Cargo.toml b/actix-staticfiles/Cargo.toml similarity index 93% rename from staticfiles/Cargo.toml rename to actix-staticfiles/Cargo.toml index 0aa58970..0a551792 100644 --- a/staticfiles/Cargo.toml +++ b/actix-staticfiles/Cargo.toml @@ -20,7 +20,8 @@ path = "src/lib.rs" [dependencies] actix-web = { path=".." } actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-service = "0.3.0" +actix-service = { git = "https://github.com/actix/actix-net.git" } +#actix-service = "0.3.0" bytes = "0.4" futures = "0.1" diff --git a/staticfiles/README.md b/actix-staticfiles/README.md similarity index 100% rename from staticfiles/README.md rename to actix-staticfiles/README.md diff --git a/actix-staticfiles/src/config.rs b/actix-staticfiles/src/config.rs new file mode 100644 index 00000000..da72da20 --- /dev/null +++ b/actix-staticfiles/src/config.rs @@ -0,0 +1,70 @@ +use actix_http::http::header::DispositionType; +use actix_web::http::Method; +use mime; + +/// Describes `StaticFiles` configiration +/// +/// To configure actix's static resources you need +/// to define own configiration type and implement any method +/// you wish to customize. +/// As trait implements reasonable defaults for Actix. +/// +/// ## Example +/// +/// ```rust,ignore +/// extern crate mime; +/// extern crate actix_web; +/// use actix_web::http::header::DispositionType; +/// use actix_web::fs::{StaticFileConfig, NamedFile}; +/// +/// #[derive(Default)] +/// struct MyConfig; +/// +/// impl StaticFileConfig for MyConfig { +/// fn content_disposition_map(typ: mime::Name) -> DispositionType { +/// DispositionType::Attachment +/// } +/// } +/// +/// let file = NamedFile::open_with_config("foo.txt", MyConfig); +/// ``` +pub trait StaticFileConfig: Default { + ///Describes mapping for mime type to content disposition header + /// + ///By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. + ///Others are mapped to Attachment + fn content_disposition_map(typ: mime::Name) -> DispositionType { + match typ { + mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, + _ => DispositionType::Attachment, + } + } + + ///Describes whether Actix should attempt to calculate `ETag` + /// + ///Defaults to `true` + fn is_use_etag() -> bool { + true + } + + ///Describes whether Actix should use last modified date of file. + /// + ///Defaults to `true` + fn is_use_last_modifier() -> bool { + true + } + + ///Describes allowed methods to access static resources. + /// + ///By default all methods are allowed + fn is_method_allowed(_method: &Method) -> bool { + true + } +} + +///Default content disposition as described in +///[StaticFileConfig](trait.StaticFileConfig.html) +#[derive(Default)] +pub struct DefaultConfig; + +impl StaticFileConfig for DefaultConfig {} diff --git a/actix-staticfiles/src/error.rs b/actix-staticfiles/src/error.rs new file mode 100644 index 00000000..f165a618 --- /dev/null +++ b/actix-staticfiles/src/error.rs @@ -0,0 +1,41 @@ +use actix_web::{http::StatusCode, HttpResponse, ResponseError}; +use derive_more::Display; + +/// Errors which can occur when serving static files. +#[derive(Display, Debug, PartialEq)] +pub enum StaticFilesError { + /// Path is not a directory + #[display(fmt = "Path is not a directory. Unable to serve static files")] + IsNotDirectory, + + /// Cannot render directory + #[display(fmt = "Unable to render directory without index file")] + IsDirectory, +} + +/// Return `NotFound` for `StaticFilesError` +impl ResponseError for StaticFilesError { + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::NOT_FOUND) + } +} + +#[derive(Display, Debug, PartialEq)] +pub enum UriSegmentError { + /// The segment started with the wrapped invalid character. + #[display(fmt = "The segment started with the wrapped invalid character")] + BadStart(char), + /// The segment contained the wrapped invalid character. + #[display(fmt = "The segment contained the wrapped invalid character")] + BadChar(char), + /// The segment ended with the wrapped invalid character. + #[display(fmt = "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) + } +} diff --git a/actix-staticfiles/src/lib.rs b/actix-staticfiles/src/lib.rs new file mode 100644 index 00000000..01306d4b --- /dev/null +++ b/actix-staticfiles/src/lib.rs @@ -0,0 +1,1482 @@ +//! Static files support +use std::cell::RefCell; +use std::fmt::Write; +use std::fs::{DirEntry, File}; +use std::io::{Read, Seek}; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; +use std::rc::Rc; +use std::{cmp, io}; + +use bytes::Bytes; +use futures::{Async, Future, Poll, Stream}; +use mime; +use mime_guess::get_mime_type; +use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; +use v_htmlescape::escape as escape_html_entity; + +use actix_http::error::{Error, ErrorInternalServerError}; +use actix_service::{boxed::BoxedNewService, NewService, Service}; +use actix_web::dev::{self, HttpServiceFactory, ResourceDef, Url}; +use actix_web::{ + blocking, FromRequest, HttpRequest, HttpResponse, Responder, ServiceFromRequest, + ServiceRequest, ServiceResponse, +}; +use futures::future::{ok, FutureResult}; + +mod config; +mod error; +mod named; + +use self::error::{StaticFilesError, UriSegmentError}; +pub use crate::config::{DefaultConfig, StaticFileConfig}; +pub use crate::named::NamedFile; + +type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; + +/// Return the MIME type associated with a filename extension (case-insensitive). +/// If `ext` is empty or no associated type for the extension was found, returns +/// the type `application/octet-stream`. +#[inline] +pub fn file_extension_to_mime(ext: &str) -> mime::Mime { + get_mime_type(ext) +} + +#[doc(hidden)] +/// A helper created from a `std::fs::File` which reads the file +/// chunk-by-chunk on a `ThreadPool`. +pub struct ChunkedReadFile { + size: u64, + offset: u64, + file: Option, + fut: Option>, + counter: u64, +} + +fn handle_error(err: blocking::BlockingError) -> Error { + match err { + blocking::BlockingError::Error(err) => err.into(), + blocking::BlockingError::Canceled => { + ErrorInternalServerError("Unexpected error").into() + } + } +} + +impl Stream for ChunkedReadFile { + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + if self.fut.is_some() { + return match self.fut.as_mut().unwrap().poll().map_err(handle_error)? { + Async::Ready((file, bytes)) => { + self.fut.take(); + self.file = Some(file); + self.offset += bytes.len() as u64; + self.counter += bytes.len() as u64; + Ok(Async::Ready(Some(bytes))) + } + Async::NotReady => Ok(Async::NotReady), + }; + } + + let size = self.size; + let offset = self.offset; + let counter = self.counter; + + if size == counter { + Ok(Async::Ready(None)) + } else { + let mut file = self.file.take().expect("Use after completion"); + self.fut = Some(blocking::run(move || { + let max_bytes: usize; + max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; + let mut buf = Vec::with_capacity(max_bytes); + file.seek(io::SeekFrom::Start(offset))?; + let nbytes = + file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; + if nbytes == 0 { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + Ok((file, Bytes::from(buf))) + })); + self.poll() + } + } +} + +type DirectoryRenderer = + Fn(&Directory, &HttpRequest) -> Result; + +/// A directory; responds with the generated directory listing. +#[derive(Debug)] +pub struct Directory { + /// Base directory + pub base: PathBuf, + /// Path of subdirectory to generate listing for + pub path: PathBuf, +} + +impl Directory { + /// Create a new directory + pub fn new(base: PathBuf, path: PathBuf) -> Directory { + Directory { base, path } + } + + /// Is this entry visible from this directory? + pub fn is_visible(&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 + } +} + +// show file url as relative to static path +macro_rules! encode_file_url { + ($path:ident) => { + utf8_percent_encode(&$path.to_string_lossy(), DEFAULT_ENCODE_SET) + }; +} + +// " -- " & -- & ' -- ' < -- < > -- > / -- / +macro_rules! encode_file_name { + ($entry:ident) => { + escape_html_entity(&$entry.file_name().to_string_lossy()) + }; +} + +fn directory_listing( + dir: &Directory, + 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 dir.path.read_dir()? { + if dir.is_visible(&entry) { + let entry = entry.unwrap(); + let p = match entry.path().strip_prefix(&dir.path) { + Ok(p) => base.join(p), + Err(_) => continue, + }; + + // 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, + "

  • {}/
  • ", + encode_file_url!(p), + encode_file_name!(entry), + ); + } else { + let _ = write!( + body, + "
  • {}
  • ", + encode_file_url!(p), + encode_file_name!(entry), + ); + } + } else { + continue; + } + } + } + + let html = format!( + "\ + {}\ +

    {}

    \ +
      \ + {}\ +
    \n", + index_of, index_of, body + ); + Ok(ServiceResponse::new( + req.clone(), + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html), + )) +} + +/// Static files handling +/// +/// `StaticFile` handler must be registered with `App::handler()` method, +/// because `StaticFile` handler requires access sub-path information. +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// use actix_web::{fs, App}; +/// +/// fn main() { +/// let app = App::new() +/// .handler("/static", fs::StaticFiles::new(".").unwrap()) +/// .finish(); +/// } +/// ``` +pub struct StaticFiles { + path: ResourceDef, + directory: PathBuf, + index: Option, + show_index: bool, + default: Rc>>>>, + renderer: Rc, + _chunk_size: usize, + _follow_symlinks: bool, + _cd_map: PhantomData, +} + +impl StaticFiles { + /// Create new `StaticFiles` instance for specified base directory. + /// + /// `StaticFile` uses `ThreadPool` for blocking filesystem operations. + /// By default pool with 5x threads of available cpus is used. + /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. + pub fn new>(path: &str, dir: T) -> Result, Error> { + Self::with_config(path, dir, DefaultConfig) + } +} + +impl StaticFiles { + /// Create new `StaticFiles` instance for specified base directory. + /// + /// Identical with `new` but allows to specify configiration to use. + pub fn with_config>( + path: &str, + dir: T, + _: C, + ) -> Result, Error> { + let dir = dir.into().canonicalize()?; + + if !dir.is_dir() { + return Err(StaticFilesError::IsNotDirectory.into()); + } + + Ok(StaticFiles { + path: ResourceDef::root_prefix(path), + directory: dir, + index: None, + show_index: false, + default: Rc::new(RefCell::new(None)), + renderer: Rc::new(directory_listing), + _chunk_size: 0, + _follow_symlinks: false, + _cd_map: PhantomData, + }) + } + + /// Show files listing for directories. + /// + /// By default show files listing is disabled. + pub fn show_files_listing(mut self) -> Self { + self.show_index = true; + self + } + + /// Set custom directory renderer + pub fn files_listing_renderer(mut self, f: F) -> Self + where + for<'r, 's> F: + Fn(&'r Directory, &'s HttpRequest) -> Result + + 'static, + { + self.renderer = Rc::new(f); + self + } + + /// Set index file + /// + /// Shows specific index file for directory "/" instead of + /// showing files listing. + pub fn index_file>(mut self, index: T) -> StaticFiles { + self.index = Some(index.into()); + self + } +} + +impl HttpServiceFactory

    for StaticFiles { + type Factory = Self; + + fn rdef(&self) -> &ResourceDef { + &self.path + } + + fn create(self) -> Self { + self + } +} + +impl NewService> + for StaticFiles +{ + type Response = ServiceResponse; + type Error = (); + type Service = StaticFilesService; + type InitError = (); + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(StaticFilesService { + directory: self.directory.clone(), + index: self.index.clone(), + show_index: self.show_index, + default: self.default.clone(), + renderer: self.renderer.clone(), + _chunk_size: self._chunk_size, + _follow_symlinks: self._follow_symlinks, + _cd_map: self._cd_map, + }) + } +} + +pub struct StaticFilesService { + directory: PathBuf, + index: Option, + show_index: bool, + default: Rc>>>>, + renderer: Rc, + _chunk_size: usize, + _follow_symlinks: bool, + _cd_map: PhantomData, +} + +impl Service> for StaticFilesService { + type Response = ServiceResponse; + type Error = (); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { + let (req, _) = req.into_parts(); + + let real_path = match PathBufWrp::get_pathbuf(req.match_info()) { + Ok(item) => item, + Err(e) => return ok(ServiceResponse::from_err(e, req.clone())), + }; + + // full filepath + let path = match self.directory.join(&real_path.0).canonicalize() { + Ok(path) => path, + Err(e) => return ok(ServiceResponse::from_err(e, req.clone())), + }; + + if path.is_dir() { + if let Some(ref redir_index) = self.index { + let path = path.join(redir_index); + + match NamedFile::open_with_config(path, C::default()) { + Ok(named_file) => match named_file.respond_to(&req) { + Ok(item) => ok(ServiceResponse::new(req.clone(), item)), + Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + }, + Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + } + } else if self.show_index { + let dir = Directory::new(self.directory.clone(), path); + let x = (self.renderer)(&dir, &req); + match x { + Ok(resp) => ok(resp), + Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + } + } else { + ok(ServiceResponse::from_err( + StaticFilesError::IsDirectory, + req.clone(), + )) + } + } else { + match NamedFile::open_with_config(path, C::default()) { + Ok(named_file) => match named_file.respond_to(&req) { + Ok(item) => ok(ServiceResponse::new(req.clone(), item)), + Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + }, + Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + } + } + } +} + +struct PathBufWrp(PathBuf); + +impl PathBufWrp { + fn get_pathbuf(path: &dev::Path) -> Result { + let path_str = path.path(); + let mut buf = PathBuf::new(); + for segment in path_str.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(PathBufWrp(buf)) + } +} + +impl

    FromRequest

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

    ) -> Self::Future { + PathBufWrp::get_pathbuf(req.match_info()) + } +} + +/// HTTP Range header representation. +#[derive(Debug, Clone, Copy)] +struct HttpRange { + pub start: u64, + pub length: u64, +} + +static PREFIX: &'static str = "bytes="; +const PREFIX_LEN: usize = 6; + +impl HttpRange { + /// Parses Range HTTP header string as per RFC 2616. + /// + /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). + /// `size` is full size of response (file). + fn parse(header: &str, size: u64) -> Result, ()> { + if header.is_empty() { + return Ok(Vec::new()); + } + if !header.starts_with(PREFIX) { + return Err(()); + } + + let size_sig = size as i64; + let mut no_overlap = false; + + let all_ranges: Vec> = header[PREFIX_LEN..] + .split(',') + .map(|x| x.trim()) + .filter(|x| !x.is_empty()) + .map(|ra| { + let mut start_end_iter = ra.split('-'); + + let start_str = start_end_iter.next().ok_or(())?.trim(); + let end_str = start_end_iter.next().ok_or(())?.trim(); + + if start_str.is_empty() { + // If no start is specified, end specifies the + // range start relative to the end of the file. + let mut length: i64 = end_str.parse().map_err(|_| ())?; + + if length > size_sig { + length = size_sig; + } + + Ok(Some(HttpRange { + start: (size_sig - length) as u64, + length: length as u64, + })) + } else { + let start: i64 = start_str.parse().map_err(|_| ())?; + + if start < 0 { + return Err(()); + } + if start >= size_sig { + no_overlap = true; + return Ok(None); + } + + let length = if end_str.is_empty() { + // If no end is specified, range extends to end of the file. + size_sig - start + } else { + let mut end: i64 = end_str.parse().map_err(|_| ())?; + + if start > end { + return Err(()); + } + + if end >= size_sig { + end = size_sig - 1; + } + + end - start + 1 + }; + + Ok(Some(HttpRange { + start: start as u64, + length: length as u64, + })) + } + }) + .collect::>()?; + + let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); + + if no_overlap && ranges.is_empty() { + return Err(()); + } + + Ok(ranges) + } +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::ops::Add; + use std::time::{Duration, SystemTime}; + + use bytes::BytesMut; + + use super::*; + use actix_web::http::{header, header::DispositionType, Method, StatusCode}; + use actix_web::test::{self, TestRequest}; + use actix_web::App; + + #[test] + fn test_file_extension_to_mime() { + let m = file_extension_to_mime("jpg"); + assert_eq!(m, mime::IMAGE_JPEG); + + let m = file_extension_to_mime("invalid extension!!"); + assert_eq!(m, mime::APPLICATION_OCTET_STREAM); + + let m = file_extension_to_mime(""); + assert_eq!(m, mime::APPLICATION_OCTET_STREAM); + } + + #[test] + fn test_if_modified_since_without_if_none_match() { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = + header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + + let req = TestRequest::default() + .header(header::IF_MODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); + } + + #[test] + fn test_if_modified_since_with_if_none_match() { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = + header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + + let req = TestRequest::default() + .header(header::IF_NONE_MATCH, "miss_etag") + .header(header::IF_MODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); + } + + #[test] + fn test_named_file_text() { + 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 req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + } + + #[test] + fn test_named_file_set_content_type() { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_content_type(mime::TEXT_XML); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/xml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + } + + #[test] + fn test_named_file_image() { + let mut file = NamedFile::open("tests/test.png").unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"test.png\"" + ); + } + + #[test] + fn test_named_file_image_attachment() { + use header::{ContentDisposition, DispositionParam, DispositionType}; + let cd = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Filename(String::from("test.png"))], + }; + let mut file = NamedFile::open("tests/test.png") + .unwrap() + .set_content_disposition(cd); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.png\"" + ); + } + + #[derive(Default)] + pub struct AllAttachmentConfig; + impl StaticFileConfig for AllAttachmentConfig { + fn content_disposition_map(_typ: mime::Name) -> DispositionType { + DispositionType::Attachment + } + } + + #[derive(Default)] + pub struct AllInlineConfig; + impl StaticFileConfig for AllInlineConfig { + fn content_disposition_map(_typ: mime::Name) -> DispositionType { + DispositionType::Inline + } + } + + #[test] + fn test_named_file_image_attachment_and_custom_config() { + let file = + NamedFile::open_with_config("tests/test.png", AllAttachmentConfig).unwrap(); + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.png\"" + ); + + let file = + NamedFile::open_with_config("tests/test.png", AllInlineConfig).unwrap(); + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"test.png\"" + ); + } + + #[test] + fn test_named_file_binary() { + let mut file = NamedFile::open("tests/test.binary").unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "application/octet-stream" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.binary\"" + ); + } + + #[test] + fn test_named_file_status_code_text() { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_status_code(StatusCode::NOT_FOUND); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[test] + fn test_named_file_ranges_status_code() { + let mut srv = test::init_service( + App::new().service( + StaticFiles::new("/test", ".") + .unwrap() + .index_file("Cargo.toml"), + ), + ); + + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/Cargo.toml") + .header(header::RANGE, "bytes=10-20") + .to_request(); + let response = test::call_success(&mut srv, request); + assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); + + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/Cargo.toml") + .header(header::RANGE, "bytes=1-0") + .to_request(); + let response = test::call_success(&mut srv, request); + + assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); + } + + #[test] + fn test_named_file_content_range_headers() { + let mut srv = test::init_service( + App::new().service( + StaticFiles::new("/test", ".") + .unwrap() + .index_file("tests/test.binary"), + ), + ); + + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-20") + .to_request(); + + let response = test::call_success(&mut srv, request); + let contentrange = response + .headers() + .get(header::CONTENT_RANGE) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentrange, "bytes 10-20/100"); + + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-5") + .to_request(); + let response = test::call_success(&mut srv, request); + + let contentrange = response + .headers() + .get(header::CONTENT_RANGE) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentrange, "bytes */100"); + } + + #[test] + fn test_named_file_content_length_headers() { + let mut srv = test::init_service( + App::new().service( + StaticFiles::new("test", ".") + .unwrap() + .index_file("tests/test.binary"), + ), + ); + + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-20") + .to_request(); + let response = test::call_success(&mut srv, request); + + let contentlength = response + .headers() + .get(header::CONTENT_LENGTH) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentlength, "11"); + + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-8") + .to_request(); + let response = test::call_success(&mut srv, request); + assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); + + // Without range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + // .no_default_headers() + .to_request(); + let response = test::call_success(&mut srv, request); + + let contentlength = response + .headers() + .get(header::CONTENT_LENGTH) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentlength, "100"); + + // chunked + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .to_request(); + let mut response = test::call_success(&mut srv, request); + + // with enabled compression + // { + // let te = response + // .headers() + // .get(header::TRANSFER_ENCODING) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(te, "chunked"); + // } + + let bytes = + test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { + b.extend(c); + Ok::<_, Error>(b) + })) + .unwrap(); + let data = Bytes::from(fs::read("tests/test.binary").unwrap()); + assert_eq!(bytes.freeze(), data); + } + + #[test] + fn test_static_files_with_spaces() { + let mut srv = test::init_service( + App::new() + .service(StaticFiles::new("/", ".").unwrap().index_file("Cargo.toml")), + ); + let request = TestRequest::get() + .uri("/tests/test%20space.binary") + .to_request(); + let mut response = test::call_success(&mut srv, request); + assert_eq!(response.status(), StatusCode::OK); + + let bytes = + test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { + b.extend(c); + Ok::<_, Error>(b) + })) + .unwrap(); + + let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); + assert_eq!(bytes.freeze(), data); + } + + #[derive(Default)] + pub struct OnlyMethodHeadConfig; + impl StaticFileConfig for OnlyMethodHeadConfig { + fn is_method_allowed(method: &Method) -> bool { + match *method { + Method::HEAD => true, + _ => false, + } + } + } + + #[test] + fn test_named_file_not_allowed() { + let file = + NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + let req = TestRequest::default() + .method(Method::POST) + .to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let file = + NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + let req = TestRequest::default().method(Method::PUT).to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let file = + NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + let req = TestRequest::default().method(Method::GET).to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + } + + // #[test] + // fn test_named_file_content_encoding() { + // let req = TestRequest::default().method(Method::GET).finish(); + // let file = NamedFile::open("Cargo.toml").unwrap(); + + // assert!(file.encoding.is_none()); + // let resp = file + // .set_content_encoding(ContentEncoding::Identity) + // .respond_to(&req) + // .unwrap(); + + // assert!(resp.content_encoding().is_some()); + // assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); + // } + + #[test] + fn test_named_file_any_method() { + let req = TestRequest::default() + .method(Method::POST) + .to_http_request(); + let file = NamedFile::open("Cargo.toml").unwrap(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_static_files() { + let mut srv = test::init_service( + App::new().service(StaticFiles::new("/", ".").unwrap().show_files_listing()), + ); + let req = TestRequest::with_uri("/missing").to_request(); + + let resp = test::call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let mut srv = + test::init_service(App::new().service(StaticFiles::new("/", ".").unwrap())); + + let req = TestRequest::default().to_request(); + let resp = test::call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let mut srv = test::init_service( + App::new().service(StaticFiles::new("/", ".").unwrap().show_files_listing()), + ); + let req = TestRequest::with_uri("/tests").to_request(); + let mut resp = test::call_success(&mut srv, req); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/html; charset=utf-8" + ); + + let bytes = + test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| { + b.extend(c); + Ok::<_, Error>(b) + })) + .unwrap(); + assert!(format!("{:?}", bytes).contains("/tests/test.png")); + } + + #[test] + fn test_static_files_bad_directory() { + let st: Result, Error> = StaticFiles::new("/", "missing"); + assert!(st.is_err()); + + let st: Result, Error> = StaticFiles::new("/", "Cargo.toml"); + assert!(st.is_err()); + } + + // #[test] + // fn test_default_handler_file_missing() { + // let st = StaticFiles::new(".") + // .unwrap() + // .default_handler(|_: &_| "default content"); + // let req = TestRequest::with_uri("/missing") + // .param("tail", "missing") + // .finish(); + + // let resp = st.handle(&req).respond_to(&req).unwrap(); + // let resp = resp.as_msg(); + // assert_eq!(resp.status(), StatusCode::OK); + // assert_eq!( + // resp.body(), + // &Body::Binary(Binary::Slice(b"default content")) + // ); + // } + + // #[test] + // fn test_serve_index() { + // let st = StaticFiles::new(".").unwrap().index_file("test.binary"); + // let req = TestRequest::default().uri("/tests").finish(); + + // let resp = st.handle(&req).respond_to(&req).unwrap(); + // let resp = resp.as_msg(); + // assert_eq!(resp.status(), StatusCode::OK); + // assert_eq!( + // resp.headers() + // .get(header::CONTENT_TYPE) + // .expect("content type"), + // "application/octet-stream" + // ); + // assert_eq!( + // resp.headers() + // .get(header::CONTENT_DISPOSITION) + // .expect("content disposition"), + // "attachment; filename=\"test.binary\"" + // ); + + // let req = TestRequest::default().uri("/tests/").finish(); + // let resp = st.handle(&req).respond_to(&req).unwrap(); + // let resp = resp.as_msg(); + // assert_eq!(resp.status(), StatusCode::OK); + // assert_eq!( + // resp.headers().get(header::CONTENT_TYPE).unwrap(), + // "application/octet-stream" + // ); + // assert_eq!( + // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + // "attachment; filename=\"test.binary\"" + // ); + + // // nonexistent index file + // let req = TestRequest::default().uri("/tests/unknown").finish(); + // let resp = st.handle(&req).respond_to(&req).unwrap(); + // let resp = resp.as_msg(); + // assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + // let req = TestRequest::default().uri("/tests/unknown/").finish(); + // let resp = st.handle(&req).respond_to(&req).unwrap(); + // let resp = resp.as_msg(); + // assert_eq!(resp.status(), StatusCode::NOT_FOUND); + // } + + // #[test] + // fn test_serve_index_nested() { + // let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); + // let req = TestRequest::default().uri("/src/client").finish(); + // let resp = st.handle(&req).respond_to(&req).unwrap(); + // let resp = resp.as_msg(); + // assert_eq!(resp.status(), StatusCode::OK); + // assert_eq!( + // resp.headers().get(header::CONTENT_TYPE).unwrap(), + // "text/x-rust" + // ); + // assert_eq!( + // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + // "inline; filename=\"mod.rs\"" + // ); + // } + + // #[test] + // fn integration_serve_index() { + // let mut srv = test::TestServer::with_factory(|| { + // App::new().handler( + // "test", + // StaticFiles::new(".").unwrap().index_file("Cargo.toml"), + // ) + // }); + + // let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + // let response = srv.execute(request.send()).unwrap(); + // assert_eq!(response.status(), StatusCode::OK); + // let bytes = srv.execute(response.body()).unwrap(); + // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + // assert_eq!(bytes, data); + + // let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); + // let response = srv.execute(request.send()).unwrap(); + // assert_eq!(response.status(), StatusCode::OK); + // let bytes = srv.execute(response.body()).unwrap(); + // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + // assert_eq!(bytes, data); + + // // nonexistent index file + // let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); + // let response = srv.execute(request.send()).unwrap(); + // assert_eq!(response.status(), StatusCode::NOT_FOUND); + + // let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); + // let response = srv.execute(request.send()).unwrap(); + // assert_eq!(response.status(), StatusCode::NOT_FOUND); + // } + + // #[test] + // fn integration_percent_encoded() { + // let mut srv = test::TestServer::with_factory(|| { + // App::new().handler( + // "test", + // StaticFiles::new(".").unwrap().index_file("Cargo.toml"), + // ) + // }); + + // let request = srv + // .get() + // .uri(srv.url("/test/%43argo.toml")) + // .finish() + // .unwrap(); + // let response = srv.execute(request.send()).unwrap(); + // assert_eq!(response.status(), StatusCode::OK); + // } + + struct T(&'static str, u64, Vec); + + #[test] + fn test_parse() { + let tests = vec![ + T("", 0, vec![]), + T("", 1000, vec![]), + T("foo", 0, vec![]), + T("bytes=", 0, vec![]), + T("bytes=7", 10, vec![]), + T("bytes= 7 ", 10, vec![]), + T("bytes=1-", 0, vec![]), + T("bytes=5-4", 10, vec![]), + T("bytes=0-2,5-4", 10, vec![]), + T("bytes=2-5,4-3", 10, vec![]), + T("bytes=--5,4--3", 10, vec![]), + T("bytes=A-", 10, vec![]), + T("bytes=A- ", 10, vec![]), + T("bytes=A-Z", 10, vec![]), + T("bytes= -Z", 10, vec![]), + T("bytes=5-Z", 10, vec![]), + T("bytes=Ran-dom, garbage", 10, vec![]), + T("bytes=0x01-0x02", 10, vec![]), + T("bytes= ", 10, vec![]), + T("bytes= , , , ", 10, vec![]), + T( + "bytes=0-9", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=0-", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=5-", + 10, + vec![HttpRange { + start: 5, + length: 5, + }], + ), + T( + "bytes=0-20", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=15-,0-5", + 10, + vec![HttpRange { + start: 0, + length: 6, + }], + ), + T( + "bytes=1-2,5-", + 10, + vec![ + HttpRange { + start: 1, + length: 2, + }, + HttpRange { + start: 5, + length: 5, + }, + ], + ), + T( + "bytes=-2 , 7-", + 11, + vec![ + HttpRange { + start: 9, + length: 2, + }, + HttpRange { + start: 7, + length: 4, + }, + ], + ), + T( + "bytes=0-0 ,2-2, 7-", + 11, + vec![ + HttpRange { + start: 0, + length: 1, + }, + HttpRange { + start: 2, + length: 1, + }, + HttpRange { + start: 7, + length: 4, + }, + ], + ), + T( + "bytes=-5", + 10, + vec![HttpRange { + start: 5, + length: 5, + }], + ), + T( + "bytes=-15", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=0-499", + 10000, + vec![HttpRange { + start: 0, + length: 500, + }], + ), + T( + "bytes=500-999", + 10000, + vec![HttpRange { + start: 500, + length: 500, + }], + ), + T( + "bytes=-500", + 10000, + vec![HttpRange { + start: 9500, + length: 500, + }], + ), + T( + "bytes=9500-", + 10000, + vec![HttpRange { + start: 9500, + length: 500, + }], + ), + T( + "bytes=0-0,-1", + 10000, + vec![ + HttpRange { + start: 0, + length: 1, + }, + HttpRange { + start: 9999, + length: 1, + }, + ], + ), + T( + "bytes=500-600,601-999", + 10000, + vec![ + HttpRange { + start: 500, + length: 101, + }, + HttpRange { + start: 601, + length: 399, + }, + ], + ), + T( + "bytes=500-700,601-999", + 10000, + vec![ + HttpRange { + start: 500, + length: 201, + }, + HttpRange { + start: 601, + length: 399, + }, + ], + ), + // Match Apache laxity: + T( + "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", + 11, + vec![ + HttpRange { + start: 1, + length: 2, + }, + HttpRange { + start: 4, + length: 2, + }, + HttpRange { + start: 7, + length: 2, + }, + ], + ), + ]; + + for t in tests { + let header = t.0; + let size = t.1; + let expected = t.2; + + let res = HttpRange::parse(header, size); + + if res.is_err() { + if expected.is_empty() { + continue; + } else { + assert!( + false, + "parse({}, {}) returned error {:?}", + header, + size, + res.unwrap_err() + ); + } + } + + let got = res.unwrap(); + + if got.len() != expected.len() { + assert!( + false, + "len(parseRange({}, {})) = {}, want {}", + header, + size, + got.len(), + expected.len() + ); + continue; + } + + for i in 0..expected.len() { + if got[i].start != expected[i].start { + assert!( + false, + "parseRange({}, {})[{}].start = {}, want {}", + header, size, i, got[i].start, expected[i].start + ) + } + if got[i].length != expected[i].length { + assert!( + false, + "parseRange({}, {})[{}].length = {}, want {}", + header, size, i, got[i].length, expected[i].length + ) + } + } + } + } +} diff --git a/actix-staticfiles/src/named.rs b/actix-staticfiles/src/named.rs new file mode 100644 index 00000000..5fba0483 --- /dev/null +++ b/actix-staticfiles/src/named.rs @@ -0,0 +1,438 @@ +use std::fs::{File, Metadata}; +use std::io; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::path::{Path, PathBuf}; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[cfg(unix)] +use std::os::unix::fs::MetadataExt; + +use mime; +use mime_guess::guess_mime_type; + +use actix_http::error::Error; +use actix_http::http::header::{self, ContentDisposition, DispositionParam}; +use actix_web::http::{ContentEncoding, Method, StatusCode}; +use actix_web::{HttpMessage, HttpRequest, HttpResponse, Responder}; + +use crate::config::{DefaultConfig, StaticFileConfig}; +use crate::{ChunkedReadFile, HttpRange}; + +/// A file with an associated name. +#[derive(Debug)] +pub struct NamedFile { + path: PathBuf, + file: File, + pub(crate) content_type: mime::Mime, + pub(crate) content_disposition: header::ContentDisposition, + pub(crate) md: Metadata, + modified: Option, + encoding: Option, + pub(crate) status_code: StatusCode, + _cd_map: PhantomData, +} + +impl NamedFile { + /// Creates an instance from a previously opened file. + /// + /// The given `path` need not exist and is only used to determine the `ContentType` and + /// `ContentDisposition` headers. + /// + /// # Examples + /// + /// ```rust,ignore + /// extern crate actix_web; + /// + /// use actix_web::fs::NamedFile; + /// use std::io::{self, Write}; + /// use std::env; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::create("foo.txt")?; + /// file.write_all(b"Hello, world!")?; + /// let named_file = NamedFile::from_file(file, "bar.txt")?; + /// Ok(()) + /// } + /// ``` + pub fn from_file>(file: File, path: P) -> io::Result { + Self::from_file_with_config(file, path, DefaultConfig) + } + + /// Attempts to open a file in read-only mode. + /// + /// # Examples + /// + /// ```rust,ignore + /// use actix_web::fs::NamedFile; + /// + /// let file = NamedFile::open("foo.txt"); + /// ``` + pub fn open>(path: P) -> io::Result { + Self::open_with_config(path, DefaultConfig) + } +} + +impl NamedFile { + /// Creates an instance from a previously opened file using the provided configuration. + /// + /// The given `path` need not exist and is only used to determine the `ContentType` and + /// `ContentDisposition` headers. + /// + /// # Examples + /// + /// ```rust,ignore + /// extern crate actix_web; + /// + /// use actix_web::fs::{DefaultConfig, NamedFile}; + /// use std::io::{self, Write}; + /// use std::env; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::create("foo.txt")?; + /// file.write_all(b"Hello, world!")?; + /// let named_file = NamedFile::from_file_with_config(file, "bar.txt", DefaultConfig)?; + /// Ok(()) + /// } + /// ``` + pub fn from_file_with_config>( + file: File, + path: P, + _: C, + ) -> io::Result> { + let path = path.as_ref().to_path_buf(); + + // Get the name of the file and use it to construct default Content-Type + // and Content-Disposition values + let (content_type, content_disposition) = { + let filename = match path.file_name() { + Some(name) => name.to_string_lossy(), + None => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Provided path has no filename", + )); + } + }; + + let ct = guess_mime_type(&path); + let disposition_type = C::content_disposition_map(ct.type_()); + let cd = ContentDisposition { + disposition: disposition_type, + parameters: vec![DispositionParam::Filename(filename.into_owned())], + }; + (ct, cd) + }; + + let md = file.metadata()?; + let modified = md.modified().ok(); + let encoding = None; + Ok(NamedFile { + path, + file, + content_type, + content_disposition, + md, + modified, + encoding, + status_code: StatusCode::OK, + _cd_map: PhantomData, + }) + } + + /// Attempts to open a file in read-only mode using provided configuration. + /// + /// # Examples + /// + /// ```rust,ignore + /// use actix_web::fs::{DefaultConfig, NamedFile}; + /// + /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); + /// ``` + pub fn open_with_config>( + path: P, + config: C, + ) -> io::Result> { + Self::from_file_with_config(File::open(&path)?, path, config) + } + + /// Returns reference to the underlying `File` object. + #[inline] + pub fn file(&self) -> &File { + &self.file + } + + /// Retrieve the path of this file. + /// + /// # Examples + /// + /// ```rust,ignore + /// # use std::io; + /// use actix_web::fs::NamedFile; + /// + /// # 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.path.as_path() + } + + /// Set response **Status Code** + pub fn set_status_code(mut self, status: StatusCode) -> Self { + self.status_code = status; + self + } + + /// Set the MIME Content-Type for serving this file. By default + /// the Content-Type is inferred from the filename extension. + #[inline] + pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { + self.content_type = mime_type; + self + } + + /// Set the Content-Disposition for serving this file. This allows + /// changing the inline/attachment disposition as well as the filename + /// sent to the peer. By default the disposition is `inline` for text, + /// image, and video content types, and `attachment` otherwise, and + /// the filename is taken from the path provided in the `open` method + /// after converting it to UTF-8 using + /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). + #[inline] + pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { + self.content_disposition = cd; + self + } + + /// Set content encoding for serving this file + #[inline] + pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { + self.encoding = Some(enc); + self + } + + pub(crate) fn etag(&self) -> Option { + // This etag format is similar to Apache's. + self.modified.as_ref().map(|mtime| { + let ino = { + #[cfg(unix)] + { + self.md.ino() + } + #[cfg(not(unix))] + { + 0 + } + }; + + let dur = mtime + .duration_since(UNIX_EPOCH) + .expect("modification time must be after epoch"); + header::EntityTag::strong(format!( + "{:x}:{:x}:{:x}:{:x}", + ino, + self.md.len(), + dur.as_secs(), + dur.subsec_nanos() + )) + }) + } + + pub(crate) fn last_modified(&self) -> Option { + self.modified.map(|mtime| mtime.into()) + } +} + +impl Deref for NamedFile { + type Target = File; + + fn deref(&self) -> &File { + &self.file + } +} + +impl DerefMut for NamedFile { + fn deref_mut(&mut self) -> &mut File { + &mut self.file + } +} + +/// Returns true if `req` has no `If-Match` header or one which matches `etag`. +fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { + match req.get_header::() { + None | Some(header::IfMatch::Any) => true, + Some(header::IfMatch::Items(ref items)) => { + if let Some(some_etag) = etag { + for item in items { + if item.strong_eq(some_etag) { + return true; + } + } + } + false + } + } +} + +/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. +fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { + match req.get_header::() { + Some(header::IfNoneMatch::Any) => false, + Some(header::IfNoneMatch::Items(ref items)) => { + if let Some(some_etag) = etag { + for item in items { + if item.weak_eq(some_etag) { + return false; + } + } + } + true + } + None => true, + } +} + +impl Responder for NamedFile { + type Error = Error; + type Future = Result; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + if self.status_code != StatusCode::OK { + let mut resp = HttpResponse::build(self.status_code); + resp.set(header::ContentType(self.content_type.clone())) + .header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); + // TODO blocking by compressing + // if let Some(current_encoding) = self.encoding { + // resp.content_encoding(current_encoding); + // } + let reader = ChunkedReadFile { + size: self.md.len(), + offset: 0, + file: Some(self.file), + fut: None, + counter: 0, + }; + return Ok(resp.streaming(reader)); + } + + if !C::is_method_allowed(req.method()) { + return Ok(HttpResponse::MethodNotAllowed() + .header(header::CONTENT_TYPE, "text/plain") + .header(header::ALLOW, "GET, HEAD") + .body("This resource only supports GET and HEAD.")); + } + + let etag = if C::is_use_etag() { self.etag() } else { None }; + let last_modified = if C::is_use_last_modifier() { + self.last_modified() + } else { + None + }; + + // check preconditions + let precondition_failed = if !any_match(etag.as_ref(), req) { + true + } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = + (last_modified, req.get_header()) + { + m > since + } else { + false + }; + + // check last modified + let not_modified = if !none_match(etag.as_ref(), req) { + true + } else if req.headers().contains_key(header::IF_NONE_MATCH) { + false + } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = + (last_modified, req.get_header()) + { + m <= since + } else { + false + }; + + let mut resp = HttpResponse::build(self.status_code); + resp.set(header::ContentType(self.content_type.clone())) + .header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); + // TODO blocking by compressing + // if let Some(current_encoding) = self.encoding { + // resp.content_encoding(current_encoding); + // } + + resp.if_some(last_modified, |lm, resp| { + resp.set(header::LastModified(lm)); + }) + .if_some(etag, |etag, resp| { + resp.set(header::ETag(etag)); + }); + + resp.header(header::ACCEPT_RANGES, "bytes"); + + let mut length = self.md.len(); + let mut offset = 0; + + // check for range header + if let Some(ranges) = req.headers().get(header::RANGE) { + if let Ok(rangesheader) = ranges.to_str() { + if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { + length = rangesvec[0].length; + offset = rangesvec[0].start; + // TODO blocking by compressing + // resp.content_encoding(ContentEncoding::Identity); + resp.header( + header::CONTENT_RANGE, + format!( + "bytes {}-{}/{}", + offset, + offset + length - 1, + self.md.len() + ), + ); + } else { + resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); + return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); + }; + } else { + return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); + }; + }; + + resp.header(header::CONTENT_LENGTH, format!("{}", length)); + + if precondition_failed { + return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); + } else if not_modified { + return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); + } + + if *req.method() == Method::HEAD { + Ok(resp.finish()) + } else { + let reader = ChunkedReadFile { + offset, + size: length, + file: Some(self.file), + fut: None, + counter: 0, + }; + if offset != 0 || length != self.md.len() { + return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); + }; + Ok(resp.streaming(reader)) + } + } +} diff --git a/actix-staticfiles/tests/test space.binary b/actix-staticfiles/tests/test space.binary new file mode 100644 index 00000000..ef8ff024 --- /dev/null +++ b/actix-staticfiles/tests/test space.binary @@ -0,0 +1 @@ +ÂTÇ‘É‚Vù2þvI ª–\ÇRË™–ˆæeÞvDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.– û®õpA !ûߦÙx j+Uc÷±©X”c%Û;ï"yì­AI \ No newline at end of file diff --git a/actix-staticfiles/tests/test.binary b/actix-staticfiles/tests/test.binary new file mode 100644 index 00000000..ef8ff024 --- /dev/null +++ b/actix-staticfiles/tests/test.binary @@ -0,0 +1 @@ +ÂTÇ‘É‚Vù2þvI ª–\ÇRË™–ˆæeÞvDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.– û®õpA !ûߦÙx j+Uc÷±©X”c%Û;ï"yì­AI \ No newline at end of file diff --git a/actix-staticfiles/tests/test.png b/actix-staticfiles/tests/test.png new file mode 100644 index 0000000000000000000000000000000000000000..6b7cdc0b8fb5a439d3bd19f210e89a37a2354e61 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>Fdh=h((rL&%EBHDibIqn;8;O;+&tGo0?Yw &'static str { @@ -29,19 +28,17 @@ fn main() -> std::io::Result<()> { .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .middleware(middleware::Compress::default()) .resource("/resource1/index.html", |r| r.route(web::get().to(index))) - .service( - "/resource2/index.html", - Resource::new() - .middleware( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_resource(|r| { - r.route(Route::new().to(|| HttpResponse::MethodNotAllowed())) - }) - .route(web::method(Method::GET).to_async(index_async)), - ) - .service("/test1.html", Resource::new().to(|| "Test\r\n")) - .service("/", Resource::new().to(no_params)) + .resource("/resource2/index.html", |r| { + r.middleware( + middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), + ) + .default_resource(|r| { + r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) + }) + .route(web::method(Method::GET).to_async(index_async)) + }) + .resource("/test1.html", |r| r.to(|| "Test\r\n")) + .resource("/", |r| r.to(no_params)) }) .bind("127.0.0.1:8080")? .workers(1) diff --git a/src/app.rs b/src/app.rs index 27ca5c95..d503d8dd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -24,8 +24,13 @@ type HttpService

    = BoxedService, ServiceResponse, ()>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; type BoxedResponse = Box>; -pub trait HttpServiceFactory { - type Factory: NewService; +pub trait HttpServiceFactory

    { + type Factory: NewService< + ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >; fn rdef(&self) -> &ResourceDef; @@ -293,6 +298,29 @@ where } } + /// Register resource handler service. + pub fn service(self, service: F) -> AppRouter> + where + F: HttpServiceFactory

    + 'static, + { + let fref = Rc::new(RefCell::new(None)); + AppRouter { + chain: self.chain, + services: vec![( + service.rdef().clone(), + boxed::new_service(service.create().map_init_err(|_| ())), + None, + )], + default: None, + defaults: vec![], + endpoint: AppEntry::new(fref.clone()), + factory_ref: fref, + extensions: self.extensions, + state: self.state, + _t: PhantomData, + } + } + /// Set server host name. /// /// Host name is used by application router aa a hostname for url @@ -445,16 +473,15 @@ where } /// Register resource handler service. - pub fn service(mut self, rdef: R, factory: F) -> Self + pub fn service(mut self, factory: F) -> Self where - R: Into, - F: IntoNewService>, - U: NewService, Response = ServiceResponse, Error = ()> - + 'static, + F: HttpServiceFactory

    + 'static, { + let rdef = factory.rdef().clone(); + self.services.push(( - rdef.into(), - boxed::new_service(factory.into_new_service().map_init_err(|_| ())), + rdef, + boxed::new_service(factory.create().map_init_err(|_| ())), None, )); self diff --git a/src/lib.rs b/src/lib.rs index f21c5e43..44dcde35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,9 @@ pub mod test; // re-export for convenience pub use actix_http::Response as HttpResponse; -pub use actix_http::{error, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{body, error, http, Error, HttpMessage, ResponseError, Result}; -pub use crate::app::{App, AppRouter}; +pub use crate::app::App; pub use crate::extract::{FromRequest, Json}; pub use crate::request::HttpRequest; pub use crate::resource::Resource; @@ -32,6 +32,26 @@ pub use crate::server::HttpServer; pub use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; pub use crate::state::State; +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::*; + //! ``` + + pub use crate::app::{AppRouter, HttpServiceFactory}; + pub use actix_http::body::{Body, MessageBody, ResponseBody}; + pub use actix_http::dev::ResponseBuilder as HttpResponseBuilder; + pub use actix_http::{ + Extensions, Payload, PayloadStream, RequestHead, ResponseHead, + }; + pub use actix_router::{Path, ResourceDef, Url}; +} + pub mod web { use actix_http::{http::Method, Error, Response}; use futures::IntoFuture; diff --git a/src/service.rs b/src/service.rs index 7d17527a..c9666e31 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,8 +1,9 @@ use std::borrow::Cow; use std::cell::{Ref, RefMut}; +use std::fmt; use std::rc::Rc; -use actix_http::body::{Body, ResponseBody}; +use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{ Error, Extensions, HttpMessage, Payload, PayloadStream, Request, RequestHead, @@ -123,6 +124,11 @@ impl

    ServiceRequest

    { pub fn app_extensions(&self) -> &Extensions { self.req.app_extensions() } + + /// Deconstruct request into parts + pub fn into_parts(self) -> (HttpRequest, Payload

    ) { + (self.req, self.payload) + } } impl

    Resource for ServiceRequest

    { @@ -172,6 +178,29 @@ impl

    std::ops::DerefMut for ServiceRequest

    { } } +impl

    fmt::Debug for ServiceRequest

    { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "\nServiceRequest {:?} {}:{}", + self.head().version, + self.head().method, + self.path() + )?; + if !self.query_string().is_empty() { + writeln!(f, " query: ?{:?}", self.query_string())?; + } + if !self.match_info().is_empty() { + writeln!(f, " params: {:?}", self.match_info())?; + } + writeln!(f, " headers:")?; + for (key, val) in self.headers().iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} + pub struct ServiceFromRequest

    { req: HttpRequest, payload: Payload

    , @@ -259,6 +288,16 @@ impl ServiceResponse { ServiceResponse { request, response } } + /// Create service response from the error + pub fn from_err>(err: E, request: HttpRequest) -> Self { + let e: Error = err.into(); + let res: Response = e.into(); + ServiceResponse { + request, + response: res.into_body(), + } + } + /// Get reference to original request #[inline] pub fn request(&self) -> &HttpRequest { @@ -303,6 +342,11 @@ impl ServiceResponse { } } } + + /// Extract response body + pub fn take_body(&mut self) -> ResponseBody { + self.response.take_body() + } } impl ServiceResponse { @@ -349,3 +393,21 @@ impl IntoFuture for ServiceResponse { ok(self) } } + +impl fmt::Debug for ServiceResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let res = writeln!( + f, + "\nServiceResponse {:?} {}{}", + self.response.head().version, + self.response.head().status, + self.response.head().reason.unwrap_or(""), + ); + let _ = writeln!(f, " headers:"); + for (key, val) in self.response.head().headers.iter() { + let _ = writeln!(f, " {:?}: {:?}", key, val); + } + let _ = writeln!(f, " body: {:?}", self.response.body().length()); + res + } +} diff --git a/src/test.rs b/src/test.rs index 22bfe0c3..ccc4b38e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -70,6 +70,34 @@ where block_on(app.into_new_service().new_service(&())).unwrap() } +/// Calls service and waits for response future completion. +/// +/// ```rust,ignore +/// use actix_web::{test, App, HttpResponse, http::StatusCode}; +/// use actix_service::Service; +/// +/// fn main() { +/// let mut app = test::init_service( +/// App::new() +/// .resource("/test", |r| r.to(|| HttpResponse::Ok())) +/// ); +/// +/// // Create request object +/// let req = test::TestRequest::with_uri("/test").to_request(); +/// +/// // Call application +/// let resp = test::call_succ_service(&mut app, req); +/// assert_eq!(resp.status(), StatusCode::OK); +/// } +/// ``` +pub fn call_success(app: &mut S, req: R) -> S::Response +where + S: Service, Error = E>, + E: std::fmt::Debug, +{ + block_on(app.call(req)).unwrap() +} + /// Test `Request` builder. /// /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. diff --git a/staticfiles/src/lib.rs b/staticfiles/src/lib.rs deleted file mode 100644 index c2ac5f3a..00000000 --- a/staticfiles/src/lib.rs +++ /dev/null @@ -1,2033 +0,0 @@ -//! Static files support -use std::cell::RefCell; -use std::fmt::Write; -use std::fs::{DirEntry, File, Metadata}; -use std::io::{Read, Seek}; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::rc::Rc; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::{cmp, io}; - -#[cfg(unix)] -use std::os::unix::fs::MetadataExt; - -use bytes::Bytes; -use derive_more::Display; -use futures::{Async, Future, Poll, Stream}; -use mime; -use mime_guess::{get_mime_type, guess_mime_type}; -use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; -use v_htmlescape::escape as escape_html_entity; - -use actix_http::error::{Error, ErrorInternalServerError, ResponseError}; -use actix_http::http::header::{ - self, ContentDisposition, DispositionParam, DispositionType, -}; -use actix_http::http::{ContentEncoding, Method, StatusCode}; -use actix_http::{HttpMessage, Response}; -use actix_service::boxed::BoxedNewService; -use actix_service::{NewService, Service}; -use actix_web::{ - blocking, FromRequest, HttpRequest, Responder, ServiceRequest, ServiceResponse, -}; -use futures::future::{err, ok, FutureResult}; - -type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; - -///Describes `StaticFiles` configiration -/// -///To configure actix's static resources you need -///to define own configiration type and implement any method -///you wish to customize. -///As trait implements reasonable defaults for Actix. -/// -///## Example -/// -///```rust,ignore -/// extern crate mime; -/// extern crate actix_web; -/// use actix_web::http::header::DispositionType; -/// use actix_web::fs::{StaticFileConfig, NamedFile}; -/// -/// #[derive(Default)] -/// struct MyConfig; -/// -/// impl StaticFileConfig for MyConfig { -/// fn content_disposition_map(typ: mime::Name) -> DispositionType { -/// DispositionType::Attachment -/// } -/// } -/// -/// let file = NamedFile::open_with_config("foo.txt", MyConfig); -///``` -pub trait StaticFileConfig: Default { - ///Describes mapping for mime type to content disposition header - /// - ///By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. - ///Others are mapped to Attachment - fn content_disposition_map(typ: mime::Name) -> DispositionType { - match typ { - mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, - _ => DispositionType::Attachment, - } - } - - ///Describes whether Actix should attempt to calculate `ETag` - /// - ///Defaults to `true` - fn is_use_etag() -> bool { - true - } - - ///Describes whether Actix should use last modified date of file. - /// - ///Defaults to `true` - fn is_use_last_modifier() -> bool { - true - } - - ///Describes allowed methods to access static resources. - /// - ///By default all methods are allowed - fn is_method_allowed(_method: &Method) -> bool { - true - } -} - -///Default content disposition as described in -///[StaticFileConfig](trait.StaticFileConfig.html) -#[derive(Default)] -pub struct DefaultConfig; - -impl StaticFileConfig for DefaultConfig {} - -/// Return the MIME type associated with a filename extension (case-insensitive). -/// If `ext` is empty or no associated type for the extension was found, returns -/// the type `application/octet-stream`. -#[inline] -pub fn file_extension_to_mime(ext: &str) -> mime::Mime { - get_mime_type(ext) -} - -/// A file with an associated name. -#[derive(Debug)] -pub struct NamedFile { - path: PathBuf, - file: File, - content_type: mime::Mime, - content_disposition: header::ContentDisposition, - md: Metadata, - modified: Option, - encoding: Option, - status_code: StatusCode, - _cd_map: PhantomData, -} - -impl NamedFile { - /// Creates an instance from a previously opened file. - /// - /// The given `path` need not exist and is only used to determine the `ContentType` and - /// `ContentDisposition` headers. - /// - /// # Examples - /// - /// ```rust,ignore - /// extern crate actix_web; - /// - /// use actix_web::fs::NamedFile; - /// use std::io::{self, Write}; - /// use std::env; - /// use std::fs::File; - /// - /// fn main() -> io::Result<()> { - /// let mut file = File::create("foo.txt")?; - /// file.write_all(b"Hello, world!")?; - /// let named_file = NamedFile::from_file(file, "bar.txt")?; - /// Ok(()) - /// } - /// ``` - pub fn from_file>(file: File, path: P) -> io::Result { - Self::from_file_with_config(file, path, DefaultConfig) - } - - /// Attempts to open a file in read-only mode. - /// - /// # Examples - /// - /// ```rust,ignore - /// use actix_web::fs::NamedFile; - /// - /// let file = NamedFile::open("foo.txt"); - /// ``` - pub fn open>(path: P) -> io::Result { - Self::open_with_config(path, DefaultConfig) - } -} - -impl NamedFile { - /// Creates an instance from a previously opened file using the provided configuration. - /// - /// The given `path` need not exist and is only used to determine the `ContentType` and - /// `ContentDisposition` headers. - /// - /// # Examples - /// - /// ```rust,ignore - /// extern crate actix_web; - /// - /// use actix_web::fs::{DefaultConfig, NamedFile}; - /// use std::io::{self, Write}; - /// use std::env; - /// use std::fs::File; - /// - /// fn main() -> io::Result<()> { - /// let mut file = File::create("foo.txt")?; - /// file.write_all(b"Hello, world!")?; - /// let named_file = NamedFile::from_file_with_config(file, "bar.txt", DefaultConfig)?; - /// Ok(()) - /// } - /// ``` - pub fn from_file_with_config>( - file: File, - path: P, - _: C, - ) -> io::Result> { - let path = path.as_ref().to_path_buf(); - - // Get the name of the file and use it to construct default Content-Type - // and Content-Disposition values - let (content_type, content_disposition) = { - let filename = match path.file_name() { - Some(name) => name.to_string_lossy(), - None => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Provided path has no filename", - )); - } - }; - - let ct = guess_mime_type(&path); - let disposition_type = C::content_disposition_map(ct.type_()); - let cd = ContentDisposition { - disposition: disposition_type, - parameters: vec![DispositionParam::Filename(filename.into_owned())], - }; - (ct, cd) - }; - - let md = file.metadata()?; - let modified = md.modified().ok(); - let encoding = None; - Ok(NamedFile { - path, - file, - content_type, - content_disposition, - md, - modified, - encoding, - status_code: StatusCode::OK, - _cd_map: PhantomData, - }) - } - - /// Attempts to open a file in read-only mode using provided configuration. - /// - /// # Examples - /// - /// ```rust,ignore - /// use actix_web::fs::{DefaultConfig, NamedFile}; - /// - /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); - /// ``` - pub fn open_with_config>( - path: P, - config: C, - ) -> io::Result> { - Self::from_file_with_config(File::open(&path)?, path, config) - } - - /// Returns reference to the underlying `File` object. - #[inline] - pub fn file(&self) -> &File { - &self.file - } - - /// Retrieve the path of this file. - /// - /// # Examples - /// - /// ```rust,ignore - /// # use std::io; - /// use actix_web::fs::NamedFile; - /// - /// # 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.path.as_path() - } - - /// Set response **Status Code** - pub fn set_status_code(mut self, status: StatusCode) -> Self { - self.status_code = status; - self - } - - /// Set the MIME Content-Type for serving this file. By default - /// the Content-Type is inferred from the filename extension. - #[inline] - pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { - self.content_type = mime_type; - self - } - - /// Set the Content-Disposition for serving this file. This allows - /// changing the inline/attachment disposition as well as the filename - /// sent to the peer. By default the disposition is `inline` for text, - /// image, and video content types, and `attachment` otherwise, and - /// the filename is taken from the path provided in the `open` method - /// after converting it to UTF-8 using - /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). - #[inline] - pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { - self.content_disposition = cd; - self - } - - /// Set content encoding for serving this file - #[inline] - pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { - self.encoding = Some(enc); - self - } - - fn etag(&self) -> Option { - // This etag format is similar to Apache's. - self.modified.as_ref().map(|mtime| { - let ino = { - #[cfg(unix)] - { - self.md.ino() - } - #[cfg(not(unix))] - { - 0 - } - }; - - let dur = mtime - .duration_since(UNIX_EPOCH) - .expect("modification time must be after epoch"); - header::EntityTag::strong(format!( - "{:x}:{:x}:{:x}:{:x}", - ino, - self.md.len(), - dur.as_secs(), - dur.subsec_nanos() - )) - }) - } - - fn last_modified(&self) -> Option { - self.modified.map(|mtime| mtime.into()) - } -} - -impl Deref for NamedFile { - type Target = File; - - fn deref(&self) -> &File { - &self.file - } -} - -impl DerefMut for NamedFile { - fn deref_mut(&mut self) -> &mut File { - &mut self.file - } -} - -/// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - None | Some(header::IfMatch::Any) => true, - Some(header::IfMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.strong_eq(some_etag) { - return true; - } - } - } - false - } - } -} - -/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - Some(header::IfNoneMatch::Any) => false, - Some(header::IfNoneMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.weak_eq(some_etag) { - return false; - } - } - } - true - } - None => true, - } -} - -impl Responder for NamedFile { - type Error = Error; - type Future = FutureResult; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - if self.status_code != StatusCode::OK { - let mut resp = Response::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - // TODO blocking by compressing - // if let Some(current_encoding) = self.encoding { - // resp.content_encoding(current_encoding); - // } - let reader = ChunkedReadFile { - size: self.md.len(), - offset: 0, - file: Some(self.file), - fut: None, - counter: 0, - }; - return ok(resp.streaming(reader)); - } - - if !C::is_method_allowed(req.method()) { - return ok(Response::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")); - } - - let etag = if C::is_use_etag() { self.etag() } else { None }; - let last_modified = if C::is_use_last_modifier() { - self.last_modified() - } else { - None - }; - - // check preconditions - let precondition_failed = if !any_match(etag.as_ref(), req) { - true - } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = - (last_modified, req.get_header()) - { - m > since - } else { - false - }; - - // check last modified - let not_modified = if !none_match(etag.as_ref(), req) { - true - } else if req.headers().contains_key(header::IF_NONE_MATCH) { - false - } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = - (last_modified, req.get_header()) - { - m <= since - } else { - false - }; - - let mut resp = Response::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - // TODO blocking by compressing - // if let Some(current_encoding) = self.encoding { - // resp.content_encoding(current_encoding); - // } - - resp.if_some(last_modified, |lm, resp| { - resp.set(header::LastModified(lm)); - }) - .if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); - - resp.header(header::ACCEPT_RANGES, "bytes"); - - let mut length = self.md.len(); - let mut offset = 0; - - // check for range header - if let Some(ranges) = req.headers().get(header::RANGE) { - if let Ok(rangesheader) = ranges.to_str() { - if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { - length = rangesvec[0].length; - offset = rangesvec[0].start; - // TODO blocking by compressing - // resp.content_encoding(ContentEncoding::Identity); - resp.header( - header::CONTENT_RANGE, - format!( - "bytes {}-{}/{}", - offset, - offset + length - 1, - self.md.len() - ), - ); - } else { - resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); - return ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); - }; - } else { - return ok(resp.status(StatusCode::BAD_REQUEST).finish()); - }; - }; - - resp.header(header::CONTENT_LENGTH, format!("{}", length)); - - if precondition_failed { - return ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); - } else if not_modified { - return ok(resp.status(StatusCode::NOT_MODIFIED).finish()); - } - - if *req.method() == Method::HEAD { - ok(resp.finish()) - } else { - let reader = ChunkedReadFile { - offset, - size: length, - file: Some(self.file), - fut: None, - counter: 0, - }; - if offset != 0 || length != self.md.len() { - return ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); - }; - ok(resp.streaming(reader)) - } - } -} - -#[doc(hidden)] -/// A helper created from a `std::fs::File` which reads the file -/// chunk-by-chunk on a `ThreadPool`. -pub struct ChunkedReadFile { - size: u64, - offset: u64, - file: Option, - fut: Option>, - counter: u64, -} - -fn handle_error(err: blocking::BlockingError) -> Error { - match err { - blocking::BlockingError::Error(err) => err.into(), - blocking::BlockingError::Canceled => { - ErrorInternalServerError("Unexpected error").into() - } - } -} - -impl Stream for ChunkedReadFile { - type Item = Bytes; - type Error = Error; - - fn poll(&mut self) -> Poll, Error> { - if self.fut.is_some() { - return match self.fut.as_mut().unwrap().poll().map_err(handle_error)? { - Async::Ready((file, bytes)) => { - self.fut.take(); - self.file = Some(file); - self.offset += bytes.len() as u64; - self.counter += bytes.len() as u64; - Ok(Async::Ready(Some(bytes))) - } - Async::NotReady => Ok(Async::NotReady), - }; - } - - let size = self.size; - let offset = self.offset; - let counter = self.counter; - - if size == counter { - Ok(Async::Ready(None)) - } else { - let mut file = self.file.take().expect("Use after completion"); - self.fut = Some(blocking::run(move || { - let max_bytes: usize; - max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; - let nbytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - if nbytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - Ok((file, Bytes::from(buf))) - })); - self.poll() - } - } -} - -type DirectoryRenderer = - Fn(&Directory, &HttpRequest) -> Result; - -/// A directory; responds with the generated directory listing. -#[derive(Debug)] -pub struct Directory { - /// Base directory - pub base: PathBuf, - /// Path of subdirectory to generate listing for - pub path: PathBuf, -} - -impl Directory { - /// Create a new directory - pub fn new(base: PathBuf, path: PathBuf) -> Directory { - Directory { base, path } - } - - /// Is this entry visible from this directory? - pub fn is_visible(&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 - } -} - -// show file url as relative to static path -macro_rules! encode_file_url { - ($path:ident) => { - utf8_percent_encode(&$path.to_string_lossy(), DEFAULT_ENCODE_SET) - }; -} - -// " -- " & -- & ' -- ' < -- < > -- > / -- / -macro_rules! encode_file_name { - ($entry:ident) => { - escape_html_entity(&$entry.file_name().to_string_lossy()) - }; -} - -fn directory_listing( - dir: &Directory, - 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 dir.path.read_dir()? { - if dir.is_visible(&entry) { - let entry = entry.unwrap(); - let p = match entry.path().strip_prefix(&dir.path) { - Ok(p) => base.join(p), - Err(_) => continue, - }; - - // 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, - "

  • {}/
  • ", - encode_file_url!(p), - encode_file_name!(entry), - ); - } else { - let _ = write!( - body, - "
  • {}
  • ", - encode_file_url!(p), - encode_file_name!(entry), - ); - } - } else { - continue; - } - } - } - - let html = format!( - "\ - {}\ -

    {}

    \ -
      \ - {}\ -
    \n", - index_of, index_of, body - ); - Ok(ServiceResponse::new( - req.clone(), - Response::Ok() - .content_type("text/html; charset=utf-8") - .body(html), - )) -} - -/// Static files handling -/// -/// `StaticFile` handler must be registered with `App::handler()` method, -/// because `StaticFile` handler requires access sub-path information. -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// use actix_web::{fs, App}; -/// -/// fn main() { -/// let app = App::new() -/// .handler("/static", fs::StaticFiles::new(".").unwrap()) -/// .finish(); -/// } -/// ``` -pub struct StaticFiles { - directory: PathBuf, - index: Option, - show_index: bool, - default: Rc>>>>, - renderer: Rc, - _chunk_size: usize, - _follow_symlinks: bool, - _cd_map: PhantomData, -} - -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. - /// - /// `StaticFile` uses `ThreadPool` for blocking filesystem operations. - /// By default pool with 5x threads of available cpus is used. - /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. - pub fn new>(dir: T) -> Result, Error> { - Self::with_config(dir, DefaultConfig) - } -} - -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. - /// - /// Identical with `new` but allows to specify configiration to use. - pub fn with_config>( - dir: T, - _: C, - ) -> Result, Error> { - let dir = dir.into().canonicalize()?; - - if !dir.is_dir() { - return Err(StaticFilesError::IsNotDirectory.into()); - } - - Ok(StaticFiles { - directory: dir, - index: None, - show_index: false, - default: Rc::new(RefCell::new(None)), - renderer: Rc::new(directory_listing), - _chunk_size: 0, - _follow_symlinks: false, - _cd_map: PhantomData, - }) - } - - /// Show files listing for directories. - /// - /// By default show files listing is disabled. - pub fn show_files_listing(mut self) -> Self { - self.show_index = true; - self - } - - /// Set custom directory renderer - pub fn files_listing_renderer(mut self, f: F) -> Self - where - for<'r, 's> F: - Fn(&'r Directory, &'s HttpRequest) -> Result - + 'static, - { - self.renderer = Rc::new(f); - self - } - - /// Set index file - /// - /// Shows specific index file for directory "/" instead of - /// showing files listing. - pub fn index_file>(mut self, index: T) -> StaticFiles { - self.index = Some(index.into()); - self - } -} - -impl NewService for StaticFiles { - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Service = StaticFilesService; - type InitError = Error; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(StaticFilesService { - directory: self.directory.clone(), - index: self.index.clone(), - show_index: self.show_index, - default: self.default.clone(), - renderer: self.renderer.clone(), - _chunk_size: self._chunk_size, - _follow_symlinks: self._follow_symlinks, - _cd_map: self._cd_map, - }) - } -} - -pub struct StaticFilesService { - directory: PathBuf, - index: Option, - show_index: bool, - default: Rc>>>>, - renderer: Rc, - _chunk_size: usize, - _follow_symlinks: bool, - _cd_map: PhantomData, -} - -impl Service for StaticFilesService { - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - let mut req = req; - let real_path = match PathBufWrp::from_request(&mut req).poll() { - Ok(Async::Ready(item)) => item.0, - Ok(Async::NotReady) => unreachable!(), - Err(e) => return err(Error::from(e)), - }; - // full filepath - let path = match self.directory.join(&real_path).canonicalize() { - Ok(path) => path, - Err(e) => return err(Error::from(e)), - }; - - if path.is_dir() { - if let Some(ref redir_index) = self.index { - let path = path.join(redir_index); - - match NamedFile::open_with_config(path, C::default()) { - Ok(named_file) => match named_file.respond_to(&req).poll() { - Ok(Async::Ready(item)) => { - ok(ServiceResponse::new(req.clone(), item)) - } - Ok(Async::NotReady) => unreachable!(), - Err(e) => err(Error::from(e)), - }, - Err(e) => err(Error::from(e)), - } - } else if self.show_index { - let dir = Directory::new(self.directory.clone(), path); - let x = (self.renderer)(&dir, &req); - match x { - Ok(resp) => ok(resp), - Err(e) => err(Error::from(e)), - } - } else { - err(StaticFilesError::IsDirectory.into()) - } - } else { - match NamedFile::open_with_config(path, C::default()) { - Ok(named_file) => match named_file.respond_to(&req).poll() { - Ok(Async::Ready(item)) => { - ok(ServiceResponse::new(req.clone(), item)) - } - Ok(Async::NotReady) => unreachable!(), - Err(e) => err(Error::from(e)), - }, - Err(e) => err(Error::from(e)), - } - } - } -} - -struct PathBufWrp(PathBuf); - -impl

    FromRequest

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

    ) -> Self::Future { - let path_str = req.match_info().path(); - let mut buf = PathBuf::new(); - for segment in path_str.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(PathBufWrp(buf)) - } -} - -/// Errors which can occur when serving static files. -#[derive(Display, Debug, PartialEq)] -enum StaticFilesError { - /// Path is not a directory - #[display(fmt = "Path is not a directory. Unable to serve static files")] - IsNotDirectory, - - /// Cannot render directory - #[display(fmt = "Unable to render directory without index file")] - IsDirectory, -} - -/// Return `NotFound` for `StaticFilesError` -impl ResponseError for StaticFilesError { - fn error_response(&self) -> Response { - Response::new(StatusCode::NOT_FOUND) - } -} - -#[derive(Display, Debug, PartialEq)] -pub enum UriSegmentError { - /// The segment started with the wrapped invalid character. - #[display(fmt = "The segment started with the wrapped invalid character")] - BadStart(char), - /// The segment contained the wrapped invalid character. - #[display(fmt = "The segment contained the wrapped invalid character")] - BadChar(char), - /// The segment ended with the wrapped invalid character. - #[display(fmt = "The segment ended with the wrapped invalid character")] - BadEnd(char), -} - -/// Return `BadRequest` for `UriSegmentError` -impl ResponseError for UriSegmentError { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) - } -} - -/// HTTP Range header representation. -#[derive(Debug, Clone, Copy)] -struct HttpRange { - pub start: u64, - pub length: u64, -} - -static PREFIX: &'static str = "bytes="; -const PREFIX_LEN: usize = 6; - -impl HttpRange { - /// Parses Range HTTP header string as per RFC 2616. - /// - /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). - /// `size` is full size of response (file). - fn parse(header: &str, size: u64) -> Result, ()> { - if header.is_empty() { - return Ok(Vec::new()); - } - if !header.starts_with(PREFIX) { - return Err(()); - } - - let size_sig = size as i64; - let mut no_overlap = false; - - let all_ranges: Vec> = header[PREFIX_LEN..] - .split(',') - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(|ra| { - let mut start_end_iter = ra.split('-'); - - let start_str = start_end_iter.next().ok_or(())?.trim(); - let end_str = start_end_iter.next().ok_or(())?.trim(); - - if start_str.is_empty() { - // If no start is specified, end specifies the - // range start relative to the end of the file. - let mut length: i64 = end_str.parse().map_err(|_| ())?; - - if length > size_sig { - length = size_sig; - } - - Ok(Some(HttpRange { - start: (size_sig - length) as u64, - length: length as u64, - })) - } else { - let start: i64 = start_str.parse().map_err(|_| ())?; - - if start < 0 { - return Err(()); - } - if start >= size_sig { - no_overlap = true; - return Ok(None); - } - - let length = if end_str.is_empty() { - // If no end is specified, range extends to end of the file. - size_sig - start - } else { - let mut end: i64 = end_str.parse().map_err(|_| ())?; - - if start > end { - return Err(()); - } - - if end >= size_sig { - end = size_sig - 1; - } - - end - start + 1 - }; - - Ok(Some(HttpRange { - start: start as u64, - length: length as u64, - })) - } - }) - .collect::>()?; - - let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); - - if no_overlap && ranges.is_empty() { - return Err(()); - } - - Ok(ranges) - } -} - -// #[cfg(test)] -// mod tests { -// use std::fs; -// use std::ops::Add; -// use std::time::Duration; - -// use super::*; -// use application::App; -// use body::{Binary, Body}; -// use http::{header, Method, StatusCode}; -// use test::{self, TestRequest}; - -// #[test] -// fn test_file_extension_to_mime() { -// let m = file_extension_to_mime("jpg"); -// assert_eq!(m, mime::IMAGE_JPEG); - -// let m = file_extension_to_mime("invalid extension!!"); -// assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - -// let m = file_extension_to_mime(""); -// assert_eq!(m, mime::APPLICATION_OCTET_STREAM); -// } - -// #[test] -// fn test_if_modified_since_without_if_none_match() { -// let mut file = NamedFile::open("Cargo.toml") -// .unwrap() -// .set_cpu_pool(CpuPool::new(1)); -// let since = -// header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); - -// let req = TestRequest::default() -// .header(header::IF_MODIFIED_SINCE, since) -// .finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); -// } - -// #[test] -// fn test_if_modified_since_with_if_none_match() { -// let mut file = NamedFile::open("Cargo.toml") -// .unwrap() -// .set_cpu_pool(CpuPool::new(1)); -// let since = -// header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); - -// let req = TestRequest::default() -// .header(header::IF_NONE_MATCH, "miss_etag") -// .header(header::IF_MODIFIED_SINCE, since) -// .finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); -// } - -// #[test] -// fn test_named_file_text() { -// assert!(NamedFile::open("test--").is_err()); -// let mut file = NamedFile::open("Cargo.toml") -// .unwrap() -// .set_cpu_pool(CpuPool::new(1)); -// { -// file.file(); -// let _f: &File = &file; -// } -// { -// let _f: &mut File = &mut file; -// } - -// let req = TestRequest::default().finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "text/x-toml" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "inline; filename=\"Cargo.toml\"" -// ); -// } - -// #[test] -// fn test_named_file_set_content_type() { -// let mut file = NamedFile::open("Cargo.toml") -// .unwrap() -// .set_content_type(mime::TEXT_XML) -// .set_cpu_pool(CpuPool::new(1)); -// { -// file.file(); -// let _f: &File = &file; -// } -// { -// let _f: &mut File = &mut file; -// } - -// let req = TestRequest::default().finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "text/xml" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "inline; filename=\"Cargo.toml\"" -// ); -// } - -// #[test] -// fn test_named_file_image() { -// let mut file = NamedFile::open("tests/test.png") -// .unwrap() -// .set_cpu_pool(CpuPool::new(1)); -// { -// file.file(); -// let _f: &File = &file; -// } -// { -// let _f: &mut File = &mut file; -// } - -// let req = TestRequest::default().finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "image/png" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "inline; filename=\"test.png\"" -// ); -// } - -// #[test] -// fn test_named_file_image_attachment() { -// use header::{ContentDisposition, DispositionParam, DispositionType}; -// let cd = ContentDisposition { -// disposition: DispositionType::Attachment, -// parameters: vec![DispositionParam::Filename(String::from("test.png"))], -// }; -// let mut file = NamedFile::open("tests/test.png") -// .unwrap() -// .set_content_disposition(cd) -// .set_cpu_pool(CpuPool::new(1)); -// { -// file.file(); -// let _f: &File = &file; -// } -// { -// let _f: &mut File = &mut file; -// } - -// let req = TestRequest::default().finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "image/png" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "attachment; filename=\"test.png\"" -// ); -// } - -// #[derive(Default)] -// pub struct AllAttachmentConfig; -// impl StaticFileConfig for AllAttachmentConfig { -// fn content_disposition_map(_typ: mime::Name) -> DispositionType { -// DispositionType::Attachment -// } -// } - -// #[derive(Default)] -// pub struct AllInlineConfig; -// impl StaticFileConfig for AllInlineConfig { -// fn content_disposition_map(_typ: mime::Name) -> DispositionType { -// DispositionType::Inline -// } -// } - -// #[test] -// fn test_named_file_image_attachment_and_custom_config() { -// let file = NamedFile::open_with_config("tests/test.png", AllAttachmentConfig) -// .unwrap() -// .set_cpu_pool(CpuPool::new(1)); - -// let req = TestRequest::default().finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "image/png" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "attachment; filename=\"test.png\"" -// ); - -// let file = NamedFile::open_with_config("tests/test.png", AllInlineConfig) -// .unwrap() -// .set_cpu_pool(CpuPool::new(1)); - -// let req = TestRequest::default().finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "image/png" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "inline; filename=\"test.png\"" -// ); -// } - -// #[test] -// fn test_named_file_binary() { -// let mut file = NamedFile::open("tests/test.binary") -// .unwrap() -// .set_cpu_pool(CpuPool::new(1)); -// { -// file.file(); -// let _f: &File = &file; -// } -// { -// let _f: &mut File = &mut file; -// } - -// let req = TestRequest::default().finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "application/octet-stream" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "attachment; filename=\"test.binary\"" -// ); -// } - -// #[test] -// fn test_named_file_status_code_text() { -// let mut file = NamedFile::open("Cargo.toml") -// .unwrap() -// .set_status_code(StatusCode::NOT_FOUND) -// .set_cpu_pool(CpuPool::new(1)); -// { -// file.file(); -// let _f: &File = &file; -// } -// { -// let _f: &mut File = &mut file; -// } - -// let req = TestRequest::default().finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "text/x-toml" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "inline; filename=\"Cargo.toml\"" -// ); -// assert_eq!(resp.status(), StatusCode::NOT_FOUND); -// } - -// #[test] -// fn test_named_file_ranges_status_code() { -// let mut srv = test::TestServer::with_factory(|| { -// App::new().handler( -// "test", -// StaticFiles::new(".").unwrap().index_file("Cargo.toml"), -// ) -// }); - -// // Valid range header -// let request = srv -// .get() -// .uri(srv.url("/t%65st/Cargo.toml")) -// .header(header::RANGE, "bytes=10-20") -// .finish() -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); - -// // Invalid range header -// let request = srv -// .get() -// .uri(srv.url("/t%65st/Cargo.toml")) -// .header(header::RANGE, "bytes=1-0") -// .finish() -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); - -// assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); -// } - -// #[test] -// fn test_named_file_content_range_headers() { -// let mut srv = test::TestServer::with_factory(|| { -// App::new().handler( -// "test", -// StaticFiles::new(".") -// .unwrap() -// .index_file("tests/test.binary"), -// ) -// }); - -// // Valid range header -// let request = srv -// .get() -// .uri(srv.url("/t%65st/tests/test.binary")) -// .header(header::RANGE, "bytes=10-20") -// .finish() -// .unwrap(); - -// let response = srv.execute(request.send()).unwrap(); - -// let contentrange = response -// .headers() -// .get(header::CONTENT_RANGE) -// .unwrap() -// .to_str() -// .unwrap(); - -// assert_eq!(contentrange, "bytes 10-20/100"); - -// // Invalid range header -// let request = srv -// .get() -// .uri(srv.url("/t%65st/tests/test.binary")) -// .header(header::RANGE, "bytes=10-5") -// .finish() -// .unwrap(); - -// let response = srv.execute(request.send()).unwrap(); - -// let contentrange = response -// .headers() -// .get(header::CONTENT_RANGE) -// .unwrap() -// .to_str() -// .unwrap(); - -// assert_eq!(contentrange, "bytes */100"); -// } - -// #[test] -// fn test_named_file_content_length_headers() { -// let mut srv = test::TestServer::with_factory(|| { -// App::new().handler( -// "test", -// StaticFiles::new(".") -// .unwrap() -// .index_file("tests/test.binary"), -// ) -// }); - -// // Valid range header -// let request = srv -// .get() -// .uri(srv.url("/t%65st/tests/test.binary")) -// .header(header::RANGE, "bytes=10-20") -// .finish() -// .unwrap(); - -// let response = srv.execute(request.send()).unwrap(); - -// let contentlength = response -// .headers() -// .get(header::CONTENT_LENGTH) -// .unwrap() -// .to_str() -// .unwrap(); - -// assert_eq!(contentlength, "11"); - -// // Invalid range header -// let request = srv -// .get() -// .uri(srv.url("/t%65st/tests/test.binary")) -// .header(header::RANGE, "bytes=10-8") -// .finish() -// .unwrap(); - -// let response = srv.execute(request.send()).unwrap(); - -// let contentlength = response -// .headers() -// .get(header::CONTENT_LENGTH) -// .unwrap() -// .to_str() -// .unwrap(); - -// assert_eq!(contentlength, "0"); - -// // Without range header -// let request = srv -// .get() -// .uri(srv.url("/t%65st/tests/test.binary")) -// .no_default_headers() -// .finish() -// .unwrap(); - -// let response = srv.execute(request.send()).unwrap(); - -// let contentlength = response -// .headers() -// .get(header::CONTENT_LENGTH) -// .unwrap() -// .to_str() -// .unwrap(); - -// assert_eq!(contentlength, "100"); - -// // chunked -// let request = srv -// .get() -// .uri(srv.url("/t%65st/tests/test.binary")) -// .finish() -// .unwrap(); - -// let response = srv.execute(request.send()).unwrap(); -// { -// let te = response -// .headers() -// .get(header::TRANSFER_ENCODING) -// .unwrap() -// .to_str() -// .unwrap(); -// assert_eq!(te, "chunked"); -// } -// let bytes = srv.execute(response.body()).unwrap(); -// let data = Bytes::from(fs::read("tests/test.binary").unwrap()); -// assert_eq!(bytes, data); -// } - -// #[test] -// fn test_static_files_with_spaces() { -// let mut srv = test::TestServer::with_factory(|| { -// App::new() -// .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) -// }); -// let request = srv -// .get() -// .uri(srv.url("/tests/test%20space.binary")) -// .finish() -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::OK); - -// let bytes = srv.execute(response.body()).unwrap(); -// let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); -// assert_eq!(bytes, data); -// } - -// #[derive(Default)] -// pub struct OnlyMethodHeadConfig; -// impl StaticFileConfig for OnlyMethodHeadConfig { -// fn is_method_allowed(method: &Method) -> bool { -// match *method { -// Method::HEAD => true, -// _ => false, -// } -// } -// } - -// #[test] -// fn test_named_file_not_allowed() { -// let file = -// NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); -// let req = TestRequest::default().method(Method::POST).finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - -// let file = -// NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); -// let req = TestRequest::default().method(Method::PUT).finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - -// let file = -// NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); -// let req = TestRequest::default().method(Method::GET).finish(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); -// } - -// #[test] -// fn test_named_file_content_encoding() { -// let req = TestRequest::default().method(Method::GET).finish(); -// let file = NamedFile::open("Cargo.toml").unwrap(); - -// assert!(file.encoding.is_none()); -// let resp = file -// .set_content_encoding(ContentEncoding::Identity) -// .respond_to(&req) -// .unwrap(); - -// assert!(resp.content_encoding().is_some()); -// assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); -// } - -// #[test] -// fn test_named_file_any_method() { -// let req = TestRequest::default().method(Method::POST).finish(); -// let file = NamedFile::open("Cargo.toml").unwrap(); -// let resp = file.respond_to(&req).unwrap(); -// assert_eq!(resp.status(), StatusCode::OK); -// } - -// #[test] -// fn test_static_files() { -// let mut st = StaticFiles::new(".").unwrap().show_files_listing(); -// let req = TestRequest::with_uri("/missing") -// .param("tail", "missing") -// .finish(); -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// assert_eq!(resp.status(), StatusCode::NOT_FOUND); - -// st.show_index = false; -// let req = TestRequest::default().finish(); -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// assert_eq!(resp.status(), StatusCode::NOT_FOUND); - -// let req = TestRequest::default().param("tail", "").finish(); - -// st.show_index = true; -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// 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")); -// } - -// #[test] -// fn test_static_files_bad_directory() { -// let st: Result, Error> = StaticFiles::new("missing"); -// assert!(st.is_err()); - -// let st: Result, Error> = StaticFiles::new("Cargo.toml"); -// assert!(st.is_err()); -// } - -// #[test] -// fn test_default_handler_file_missing() { -// let st = StaticFiles::new(".") -// .unwrap() -// .default_handler(|_: &_| "default content"); -// let req = TestRequest::with_uri("/missing") -// .param("tail", "missing") -// .finish(); - -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// assert_eq!(resp.status(), StatusCode::OK); -// assert_eq!( -// resp.body(), -// &Body::Binary(Binary::Slice(b"default content")) -// ); -// } - -// #[test] -// fn test_serve_index() { -// let st = StaticFiles::new(".").unwrap().index_file("test.binary"); -// let req = TestRequest::default().uri("/tests").finish(); - -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// assert_eq!(resp.status(), StatusCode::OK); -// assert_eq!( -// resp.headers() -// .get(header::CONTENT_TYPE) -// .expect("content type"), -// "application/octet-stream" -// ); -// assert_eq!( -// resp.headers() -// .get(header::CONTENT_DISPOSITION) -// .expect("content disposition"), -// "attachment; filename=\"test.binary\"" -// ); - -// let req = TestRequest::default().uri("/tests/").finish(); -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// assert_eq!(resp.status(), StatusCode::OK); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "application/octet-stream" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "attachment; filename=\"test.binary\"" -// ); - -// // nonexistent index file -// let req = TestRequest::default().uri("/tests/unknown").finish(); -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// assert_eq!(resp.status(), StatusCode::NOT_FOUND); - -// let req = TestRequest::default().uri("/tests/unknown/").finish(); -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// assert_eq!(resp.status(), StatusCode::NOT_FOUND); -// } - -// #[test] -// fn test_serve_index_nested() { -// let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); -// let req = TestRequest::default().uri("/src/client").finish(); -// let resp = st.handle(&req).respond_to(&req).unwrap(); -// let resp = resp.as_msg(); -// assert_eq!(resp.status(), StatusCode::OK); -// assert_eq!( -// resp.headers().get(header::CONTENT_TYPE).unwrap(), -// "text/x-rust" -// ); -// assert_eq!( -// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), -// "inline; filename=\"mod.rs\"" -// ); -// } - -// #[test] -// fn integration_serve_index_with_prefix() { -// let mut srv = test::TestServer::with_factory(|| { -// App::new() -// .prefix("public") -// .handler("/", StaticFiles::new(".").unwrap().index_file("Cargo.toml")) -// }); - -// let request = srv.get().uri(srv.url("/public")).finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::OK); -// let bytes = srv.execute(response.body()).unwrap(); -// let data = Bytes::from(fs::read("Cargo.toml").unwrap()); -// assert_eq!(bytes, data); - -// let request = srv.get().uri(srv.url("/public/")).finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::OK); -// let bytes = srv.execute(response.body()).unwrap(); -// let data = Bytes::from(fs::read("Cargo.toml").unwrap()); -// assert_eq!(bytes, data); -// } - -// #[test] -// fn integration_serve_index() { -// let mut srv = test::TestServer::with_factory(|| { -// App::new().handler( -// "test", -// StaticFiles::new(".").unwrap().index_file("Cargo.toml"), -// ) -// }); - -// let request = srv.get().uri(srv.url("/test")).finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::OK); -// let bytes = srv.execute(response.body()).unwrap(); -// let data = Bytes::from(fs::read("Cargo.toml").unwrap()); -// assert_eq!(bytes, data); - -// let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::OK); -// let bytes = srv.execute(response.body()).unwrap(); -// let data = Bytes::from(fs::read("Cargo.toml").unwrap()); -// assert_eq!(bytes, data); - -// // nonexistent index file -// let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::NOT_FOUND); - -// let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::NOT_FOUND); -// } - -// #[test] -// fn integration_percent_encoded() { -// let mut srv = test::TestServer::with_factory(|| { -// App::new().handler( -// "test", -// StaticFiles::new(".").unwrap().index_file("Cargo.toml"), -// ) -// }); - -// let request = srv -// .get() -// .uri(srv.url("/test/%43argo.toml")) -// .finish() -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert_eq!(response.status(), StatusCode::OK); -// } - -// struct T(&'static str, u64, Vec); - -// #[test] -// fn test_parse() { -// let tests = vec![ -// T("", 0, vec![]), -// T("", 1000, vec![]), -// T("foo", 0, vec![]), -// T("bytes=", 0, vec![]), -// T("bytes=7", 10, vec![]), -// T("bytes= 7 ", 10, vec![]), -// T("bytes=1-", 0, vec![]), -// T("bytes=5-4", 10, vec![]), -// T("bytes=0-2,5-4", 10, vec![]), -// T("bytes=2-5,4-3", 10, vec![]), -// T("bytes=--5,4--3", 10, vec![]), -// T("bytes=A-", 10, vec![]), -// T("bytes=A- ", 10, vec![]), -// T("bytes=A-Z", 10, vec![]), -// T("bytes= -Z", 10, vec![]), -// T("bytes=5-Z", 10, vec![]), -// T("bytes=Ran-dom, garbage", 10, vec![]), -// T("bytes=0x01-0x02", 10, vec![]), -// T("bytes= ", 10, vec![]), -// T("bytes= , , , ", 10, vec![]), -// T( -// "bytes=0-9", -// 10, -// vec![HttpRange { -// start: 0, -// length: 10, -// }], -// ), -// T( -// "bytes=0-", -// 10, -// vec![HttpRange { -// start: 0, -// length: 10, -// }], -// ), -// T( -// "bytes=5-", -// 10, -// vec![HttpRange { -// start: 5, -// length: 5, -// }], -// ), -// T( -// "bytes=0-20", -// 10, -// vec![HttpRange { -// start: 0, -// length: 10, -// }], -// ), -// T( -// "bytes=15-,0-5", -// 10, -// vec![HttpRange { -// start: 0, -// length: 6, -// }], -// ), -// T( -// "bytes=1-2,5-", -// 10, -// vec![ -// HttpRange { -// start: 1, -// length: 2, -// }, -// HttpRange { -// start: 5, -// length: 5, -// }, -// ], -// ), -// T( -// "bytes=-2 , 7-", -// 11, -// vec![ -// HttpRange { -// start: 9, -// length: 2, -// }, -// HttpRange { -// start: 7, -// length: 4, -// }, -// ], -// ), -// T( -// "bytes=0-0 ,2-2, 7-", -// 11, -// vec![ -// HttpRange { -// start: 0, -// length: 1, -// }, -// HttpRange { -// start: 2, -// length: 1, -// }, -// HttpRange { -// start: 7, -// length: 4, -// }, -// ], -// ), -// T( -// "bytes=-5", -// 10, -// vec![HttpRange { -// start: 5, -// length: 5, -// }], -// ), -// T( -// "bytes=-15", -// 10, -// vec![HttpRange { -// start: 0, -// length: 10, -// }], -// ), -// T( -// "bytes=0-499", -// 10000, -// vec![HttpRange { -// start: 0, -// length: 500, -// }], -// ), -// T( -// "bytes=500-999", -// 10000, -// vec![HttpRange { -// start: 500, -// length: 500, -// }], -// ), -// T( -// "bytes=-500", -// 10000, -// vec![HttpRange { -// start: 9500, -// length: 500, -// }], -// ), -// T( -// "bytes=9500-", -// 10000, -// vec![HttpRange { -// start: 9500, -// length: 500, -// }], -// ), -// T( -// "bytes=0-0,-1", -// 10000, -// vec![ -// HttpRange { -// start: 0, -// length: 1, -// }, -// HttpRange { -// start: 9999, -// length: 1, -// }, -// ], -// ), -// T( -// "bytes=500-600,601-999", -// 10000, -// vec![ -// HttpRange { -// start: 500, -// length: 101, -// }, -// HttpRange { -// start: 601, -// length: 399, -// }, -// ], -// ), -// T( -// "bytes=500-700,601-999", -// 10000, -// vec![ -// HttpRange { -// start: 500, -// length: 201, -// }, -// HttpRange { -// start: 601, -// length: 399, -// }, -// ], -// ), -// // Match Apache laxity: -// T( -// "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", -// 11, -// vec![ -// HttpRange { -// start: 1, -// length: 2, -// }, -// HttpRange { -// start: 4, -// length: 2, -// }, -// HttpRange { -// start: 7, -// length: 2, -// }, -// ], -// ), -// ]; - -// for t in tests { -// let header = t.0; -// let size = t.1; -// let expected = t.2; - -// let res = HttpRange::parse(header, size); - -// if res.is_err() { -// if expected.is_empty() { -// continue; -// } else { -// assert!( -// false, -// "parse({}, {}) returned error {:?}", -// header, -// size, -// res.unwrap_err() -// ); -// } -// } - -// let got = res.unwrap(); - -// if got.len() != expected.len() { -// assert!( -// false, -// "len(parseRange({}, {})) = {}, want {}", -// header, -// size, -// got.len(), -// expected.len() -// ); -// continue; -// } - -// for i in 0..expected.len() { -// if got[i].start != expected[i].start { -// assert!( -// false, -// "parseRange({}, {})[{}].start = {}, want {}", -// header, size, i, got[i].start, expected[i].start -// ) -// } -// if got[i].length != expected[i].length { -// assert!( -// false, -// "parseRange({}, {})[{}].length = {}, want {}", -// header, size, i, got[i].length, expected[i].length -// ) -// } -// } -// } -// } -// } From ceca96da281ec0b7d3bdce202f1eb84d2447ce15 Mon Sep 17 00:00:00 2001 From: Stephen Ellis Date: Wed, 6 Mar 2019 01:56:12 -0800 Subject: [PATCH 0991/1635] Added HTTP Authentication for Client (#540) --- CHANGES.md | 2 ++ src/client/request.rs | 24 ++++++++++++++++++++++++ tests/test_client.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 1a326028..8cce7159 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Add `insert` and `remove` methods to `HttpResponseBuilder` +* Add client HTTP Authentication methods `.basic_auth()` and `.bearer_auth()`. #540 + ### Fixed * Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680 diff --git a/src/client/request.rs b/src/client/request.rs index ad08ad13..89789933 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -12,6 +12,7 @@ use serde::Serialize; use serde_json; use serde_urlencoded; use url::Url; +use base64::encode; use super::connector::{ClientConnector, Connection}; use super::pipeline::SendRequest; @@ -485,6 +486,29 @@ impl ClientRequestBuilder { self } + /// Set HTTP basic authorization + pub fn basic_auth(&mut self, username: U, password: Option

    ) -> &mut Self + where + U: fmt::Display, + P: fmt::Display, + { + let auth = match password { + Some(password) => format!("{}:{}", username, password), + None => format!("{}", username) + }; + let header_value = format!("Basic {}", encode(&auth)); + self.header(header::AUTHORIZATION, &*header_value) + } + + /// Set HTTP bearer authentication + pub fn bearer_auth( &mut self, token: T) -> &mut Self + where + T: fmt::Display, + { + let header_value = format!("Bearer {}", token); + self.header(header::AUTHORIZATION, &*header_value) + } + /// Set content length #[inline] pub fn content_length(&mut self, len: u64) -> &mut Self { diff --git a/tests/test_client.rs b/tests/test_client.rs index 9808f3e6..f3151e3a 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -506,3 +506,31 @@ fn client_read_until_eof() { let bytes = sys.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(b"welcome!")); } + +#[test] +fn client_basic_auth() { + let mut srv = + test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + /// set authorization header to Basic + let request = srv + .get() + .basic_auth("username", Some("password")) + .finish() + .unwrap(); + let repr = format!("{:?}", request); + assert!(repr.contains("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")); +} + +#[test] +fn client_bearer_auth() { + let mut srv = + test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + /// set authorization header to Bearer + let request = srv + .get() + .bearer_auth("someS3cr3tAutht0k3n") + .finish() + .unwrap(); + let repr = format!("{:?}", request); + assert!(repr.contains("Bearer someS3cr3tAutht0k3n")); +} \ No newline at end of file From 3fc28c5d073abd131f3988019553409e0a14616c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 09:27:02 -0800 Subject: [PATCH 0992/1635] simplify StaticFile constructor, move HttpRange to separate module --- actix-staticfiles/src/lib.rs | 447 +++------------------------------ actix-staticfiles/src/named.rs | 5 +- actix-staticfiles/src/range.rs | 375 +++++++++++++++++++++++++++ 3 files changed, 407 insertions(+), 420 deletions(-) create mode 100644 actix-staticfiles/src/range.rs diff --git a/actix-staticfiles/src/lib.rs b/actix-staticfiles/src/lib.rs index 01306d4b..81d8269c 100644 --- a/actix-staticfiles/src/lib.rs +++ b/actix-staticfiles/src/lib.rs @@ -27,10 +27,12 @@ use futures::future::{ok, FutureResult}; mod config; mod error; mod named; +mod range; use self::error::{StaticFilesError, UriSegmentError}; pub use crate::config::{DefaultConfig, StaticFileConfig}; pub use crate::named::NamedFile; +pub use crate::range::HttpRange; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; @@ -212,17 +214,15 @@ fn directory_listing( /// Static files handling /// -/// `StaticFile` handler must be registered with `App::handler()` method, -/// because `StaticFile` handler requires access sub-path information. +/// `StaticFile` handler must be registered with `App::service()` method. /// -/// ```rust,ignore -/// # extern crate actix_web; -/// use actix_web::{fs, App}; +/// ```rust +/// use actix_web::App; +/// use actix_staticfiles as fs; /// /// fn main() { /// let app = App::new() -/// .handler("/static", fs::StaticFiles::new(".").unwrap()) -/// .finish(); +/// .service(fs::StaticFiles::new("/static", ".")); /// } /// ``` pub struct StaticFiles { @@ -243,7 +243,7 @@ impl StaticFiles { /// `StaticFile` uses `ThreadPool` for blocking filesystem operations. /// By default pool with 5x threads of available cpus is used. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. - pub fn new>(path: &str, dir: T) -> Result, Error> { + pub fn new>(path: &str, dir: T) -> StaticFiles { Self::with_config(path, dir, DefaultConfig) } } @@ -252,18 +252,13 @@ impl StaticFiles { /// Create new `StaticFiles` instance for specified base directory. /// /// Identical with `new` but allows to specify configiration to use. - pub fn with_config>( - path: &str, - dir: T, - _: C, - ) -> Result, Error> { - let dir = dir.into().canonicalize()?; - + pub fn with_config>(path: &str, dir: T, _: C) -> StaticFiles { + let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new()); if !dir.is_dir() { - return Err(StaticFilesError::IsNotDirectory.into()); + log::error!("Specified path is not a directory"); } - Ok(StaticFiles { + StaticFiles { path: ResourceDef::root_prefix(path), directory: dir, index: None, @@ -273,7 +268,7 @@ impl StaticFiles { _chunk_size: 0, _follow_symlinks: false, _cd_map: PhantomData, - }) + } } /// Show files listing for directories. @@ -452,101 +447,6 @@ impl

    FromRequest

    for PathBufWrp { } } -/// HTTP Range header representation. -#[derive(Debug, Clone, Copy)] -struct HttpRange { - pub start: u64, - pub length: u64, -} - -static PREFIX: &'static str = "bytes="; -const PREFIX_LEN: usize = 6; - -impl HttpRange { - /// Parses Range HTTP header string as per RFC 2616. - /// - /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). - /// `size` is full size of response (file). - fn parse(header: &str, size: u64) -> Result, ()> { - if header.is_empty() { - return Ok(Vec::new()); - } - if !header.starts_with(PREFIX) { - return Err(()); - } - - let size_sig = size as i64; - let mut no_overlap = false; - - let all_ranges: Vec> = header[PREFIX_LEN..] - .split(',') - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(|ra| { - let mut start_end_iter = ra.split('-'); - - let start_str = start_end_iter.next().ok_or(())?.trim(); - let end_str = start_end_iter.next().ok_or(())?.trim(); - - if start_str.is_empty() { - // If no start is specified, end specifies the - // range start relative to the end of the file. - let mut length: i64 = end_str.parse().map_err(|_| ())?; - - if length > size_sig { - length = size_sig; - } - - Ok(Some(HttpRange { - start: (size_sig - length) as u64, - length: length as u64, - })) - } else { - let start: i64 = start_str.parse().map_err(|_| ())?; - - if start < 0 { - return Err(()); - } - if start >= size_sig { - no_overlap = true; - return Ok(None); - } - - let length = if end_str.is_empty() { - // If no end is specified, range extends to end of the file. - size_sig - start - } else { - let mut end: i64 = end_str.parse().map_err(|_| ())?; - - if start > end { - return Err(()); - } - - if end >= size_sig { - end = size_sig - 1; - } - - end - start + 1 - }; - - Ok(Some(HttpRange { - start: start as u64, - length: length as u64, - })) - } - }) - .collect::>()?; - - let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); - - if no_overlap && ranges.is_empty() { - return Err(()); - } - - Ok(ranges) - } -} - #[cfg(test)] mod tests { use std::fs; @@ -800,11 +700,7 @@ mod tests { #[test] fn test_named_file_ranges_status_code() { let mut srv = test::init_service( - App::new().service( - StaticFiles::new("/test", ".") - .unwrap() - .index_file("Cargo.toml"), - ), + App::new().service(StaticFiles::new("/test", ".").index_file("Cargo.toml")), ); // Valid range header @@ -828,11 +724,8 @@ mod tests { #[test] fn test_named_file_content_range_headers() { let mut srv = test::init_service( - App::new().service( - StaticFiles::new("/test", ".") - .unwrap() - .index_file("tests/test.binary"), - ), + App::new() + .service(StaticFiles::new("/test", ".").index_file("tests/test.binary")), ); // Valid range header @@ -871,11 +764,8 @@ mod tests { #[test] fn test_named_file_content_length_headers() { let mut srv = test::init_service( - App::new().service( - StaticFiles::new("test", ".") - .unwrap() - .index_file("tests/test.binary"), - ), + App::new() + .service(StaticFiles::new("test", ".").index_file("tests/test.binary")), ); // Valid range header @@ -948,8 +838,7 @@ mod tests { #[test] fn test_static_files_with_spaces() { let mut srv = test::init_service( - App::new() - .service(StaticFiles::new("/", ".").unwrap().index_file("Cargo.toml")), + App::new().service(StaticFiles::new("/", ".").index_file("Cargo.toml")), ); let request = TestRequest::get() .uri("/tests/test%20space.binary") @@ -1030,22 +919,21 @@ mod tests { #[test] fn test_static_files() { let mut srv = test::init_service( - App::new().service(StaticFiles::new("/", ".").unwrap().show_files_listing()), + App::new().service(StaticFiles::new("/", ".").show_files_listing()), ); let req = TestRequest::with_uri("/missing").to_request(); let resp = test::call_success(&mut srv, req); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = - test::init_service(App::new().service(StaticFiles::new("/", ".").unwrap())); + let mut srv = test::init_service(App::new().service(StaticFiles::new("/", "."))); let req = TestRequest::default().to_request(); let resp = test::call_success(&mut srv, req); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let mut srv = test::init_service( - App::new().service(StaticFiles::new("/", ".").unwrap().show_files_listing()), + App::new().service(StaticFiles::new("/", ".").show_files_listing()), ); let req = TestRequest::with_uri("/tests").to_request(); let mut resp = test::call_success(&mut srv, req); @@ -1065,17 +953,13 @@ mod tests { #[test] fn test_static_files_bad_directory() { - let st: Result, Error> = StaticFiles::new("/", "missing"); - assert!(st.is_err()); - - let st: Result, Error> = StaticFiles::new("/", "Cargo.toml"); - assert!(st.is_err()); + let _st: StaticFiles<()> = StaticFiles::new("/", "missing"); + let _st: StaticFiles<()> = StaticFiles::new("/", "Cargo.toml"); } // #[test] // fn test_default_handler_file_missing() { // let st = StaticFiles::new(".") - // .unwrap() // .default_handler(|_: &_| "default content"); // let req = TestRequest::with_uri("/missing") // .param("tail", "missing") @@ -1092,7 +976,7 @@ mod tests { // #[test] // fn test_serve_index() { - // let st = StaticFiles::new(".").unwrap().index_file("test.binary"); + // let st = StaticFiles::new(".").index_file("test.binary"); // let req = TestRequest::default().uri("/tests").finish(); // let resp = st.handle(&req).respond_to(&req).unwrap(); @@ -1138,7 +1022,7 @@ mod tests { // #[test] // fn test_serve_index_nested() { - // let st = StaticFiles::new(".").unwrap().index_file("mod.rs"); + // let st = StaticFiles::new(".").index_file("mod.rs"); // let req = TestRequest::default().uri("/src/client").finish(); // let resp = st.handle(&req).respond_to(&req).unwrap(); // let resp = resp.as_msg(); @@ -1158,7 +1042,7 @@ mod tests { // let mut srv = test::TestServer::with_factory(|| { // App::new().handler( // "test", - // StaticFiles::new(".").unwrap().index_file("Cargo.toml"), + // StaticFiles::new(".").index_file("Cargo.toml"), // ) // }); @@ -1191,7 +1075,7 @@ mod tests { // let mut srv = test::TestServer::with_factory(|| { // App::new().handler( // "test", - // StaticFiles::new(".").unwrap().index_file("Cargo.toml"), + // StaticFiles::new(".").index_file("Cargo.toml"), // ) // }); @@ -1204,279 +1088,4 @@ mod tests { // assert_eq!(response.status(), StatusCode::OK); // } - struct T(&'static str, u64, Vec); - - #[test] - fn test_parse() { - let tests = vec![ - T("", 0, vec![]), - T("", 1000, vec![]), - T("foo", 0, vec![]), - T("bytes=", 0, vec![]), - T("bytes=7", 10, vec![]), - T("bytes= 7 ", 10, vec![]), - T("bytes=1-", 0, vec![]), - T("bytes=5-4", 10, vec![]), - T("bytes=0-2,5-4", 10, vec![]), - T("bytes=2-5,4-3", 10, vec![]), - T("bytes=--5,4--3", 10, vec![]), - T("bytes=A-", 10, vec![]), - T("bytes=A- ", 10, vec![]), - T("bytes=A-Z", 10, vec![]), - T("bytes= -Z", 10, vec![]), - T("bytes=5-Z", 10, vec![]), - T("bytes=Ran-dom, garbage", 10, vec![]), - T("bytes=0x01-0x02", 10, vec![]), - T("bytes= ", 10, vec![]), - T("bytes= , , , ", 10, vec![]), - T( - "bytes=0-9", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=5-", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=0-20", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=15-,0-5", - 10, - vec![HttpRange { - start: 0, - length: 6, - }], - ), - T( - "bytes=1-2,5-", - 10, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 5, - length: 5, - }, - ], - ), - T( - "bytes=-2 , 7-", - 11, - vec![ - HttpRange { - start: 9, - length: 2, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=0-0 ,2-2, 7-", - 11, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 2, - length: 1, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=-5", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=-15", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-499", - 10000, - vec![HttpRange { - start: 0, - length: 500, - }], - ), - T( - "bytes=500-999", - 10000, - vec![HttpRange { - start: 500, - length: 500, - }], - ), - T( - "bytes=-500", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=9500-", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=0-0,-1", - 10000, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 9999, - length: 1, - }, - ], - ), - T( - "bytes=500-600,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 101, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - T( - "bytes=500-700,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 201, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - // Match Apache laxity: - T( - "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", - 11, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 4, - length: 2, - }, - HttpRange { - start: 7, - length: 2, - }, - ], - ), - ]; - - for t in tests { - let header = t.0; - let size = t.1; - let expected = t.2; - - let res = HttpRange::parse(header, size); - - if res.is_err() { - if expected.is_empty() { - continue; - } else { - assert!( - false, - "parse({}, {}) returned error {:?}", - header, - size, - res.unwrap_err() - ); - } - } - - let got = res.unwrap(); - - if got.len() != expected.len() { - assert!( - false, - "len(parseRange({}, {})) = {}, want {}", - header, - size, - got.len(), - expected.len() - ); - continue; - } - - for i in 0..expected.len() { - if got[i].start != expected[i].start { - assert!( - false, - "parseRange({}, {})[{}].start = {}, want {}", - header, size, i, got[i].start, expected[i].start - ) - } - if got[i].length != expected[i].length { - assert!( - false, - "parseRange({}, {})[{}].length = {}, want {}", - header, size, i, got[i].length, expected[i].length - ) - } - } - } - } } diff --git a/actix-staticfiles/src/named.rs b/actix-staticfiles/src/named.rs index 5fba0483..2fc1c454 100644 --- a/actix-staticfiles/src/named.rs +++ b/actix-staticfiles/src/named.rs @@ -17,7 +17,8 @@ use actix_web::http::{ContentEncoding, Method, StatusCode}; use actix_web::{HttpMessage, HttpRequest, HttpResponse, Responder}; use crate::config::{DefaultConfig, StaticFileConfig}; -use crate::{ChunkedReadFile, HttpRange}; +use crate::range::HttpRange; +use crate::ChunkedReadFile; /// A file with an associated name. #[derive(Debug)] @@ -303,6 +304,8 @@ impl Responder for NamedFile { type Future = Result; fn respond_to(self, req: &HttpRequest) -> Self::Future { + println!("RESP: {:?}", req); + if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) diff --git a/actix-staticfiles/src/range.rs b/actix-staticfiles/src/range.rs new file mode 100644 index 00000000..d97a35e7 --- /dev/null +++ b/actix-staticfiles/src/range.rs @@ -0,0 +1,375 @@ +/// HTTP Range header representation. +#[derive(Debug, Clone, Copy)] +pub struct HttpRange { + pub start: u64, + pub length: u64, +} + +static PREFIX: &'static str = "bytes="; +const PREFIX_LEN: usize = 6; + +impl HttpRange { + /// Parses Range HTTP header string as per RFC 2616. + /// + /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). + /// `size` is full size of response (file). + pub fn parse(header: &str, size: u64) -> Result, ()> { + if header.is_empty() { + return Ok(Vec::new()); + } + if !header.starts_with(PREFIX) { + return Err(()); + } + + let size_sig = size as i64; + let mut no_overlap = false; + + let all_ranges: Vec> = header[PREFIX_LEN..] + .split(',') + .map(|x| x.trim()) + .filter(|x| !x.is_empty()) + .map(|ra| { + let mut start_end_iter = ra.split('-'); + + let start_str = start_end_iter.next().ok_or(())?.trim(); + let end_str = start_end_iter.next().ok_or(())?.trim(); + + if start_str.is_empty() { + // If no start is specified, end specifies the + // range start relative to the end of the file. + let mut length: i64 = end_str.parse().map_err(|_| ())?; + + if length > size_sig { + length = size_sig; + } + + Ok(Some(HttpRange { + start: (size_sig - length) as u64, + length: length as u64, + })) + } else { + let start: i64 = start_str.parse().map_err(|_| ())?; + + if start < 0 { + return Err(()); + } + if start >= size_sig { + no_overlap = true; + return Ok(None); + } + + let length = if end_str.is_empty() { + // If no end is specified, range extends to end of the file. + size_sig - start + } else { + let mut end: i64 = end_str.parse().map_err(|_| ())?; + + if start > end { + return Err(()); + } + + if end >= size_sig { + end = size_sig - 1; + } + + end - start + 1 + }; + + Ok(Some(HttpRange { + start: start as u64, + length: length as u64, + })) + } + }) + .collect::>()?; + + let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); + + if no_overlap && ranges.is_empty() { + return Err(()); + } + + Ok(ranges) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct T(&'static str, u64, Vec); + + #[test] + fn test_parse() { + let tests = vec![ + T("", 0, vec![]), + T("", 1000, vec![]), + T("foo", 0, vec![]), + T("bytes=", 0, vec![]), + T("bytes=7", 10, vec![]), + T("bytes= 7 ", 10, vec![]), + T("bytes=1-", 0, vec![]), + T("bytes=5-4", 10, vec![]), + T("bytes=0-2,5-4", 10, vec![]), + T("bytes=2-5,4-3", 10, vec![]), + T("bytes=--5,4--3", 10, vec![]), + T("bytes=A-", 10, vec![]), + T("bytes=A- ", 10, vec![]), + T("bytes=A-Z", 10, vec![]), + T("bytes= -Z", 10, vec![]), + T("bytes=5-Z", 10, vec![]), + T("bytes=Ran-dom, garbage", 10, vec![]), + T("bytes=0x01-0x02", 10, vec![]), + T("bytes= ", 10, vec![]), + T("bytes= , , , ", 10, vec![]), + T( + "bytes=0-9", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=0-", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=5-", + 10, + vec![HttpRange { + start: 5, + length: 5, + }], + ), + T( + "bytes=0-20", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=15-,0-5", + 10, + vec![HttpRange { + start: 0, + length: 6, + }], + ), + T( + "bytes=1-2,5-", + 10, + vec![ + HttpRange { + start: 1, + length: 2, + }, + HttpRange { + start: 5, + length: 5, + }, + ], + ), + T( + "bytes=-2 , 7-", + 11, + vec![ + HttpRange { + start: 9, + length: 2, + }, + HttpRange { + start: 7, + length: 4, + }, + ], + ), + T( + "bytes=0-0 ,2-2, 7-", + 11, + vec![ + HttpRange { + start: 0, + length: 1, + }, + HttpRange { + start: 2, + length: 1, + }, + HttpRange { + start: 7, + length: 4, + }, + ], + ), + T( + "bytes=-5", + 10, + vec![HttpRange { + start: 5, + length: 5, + }], + ), + T( + "bytes=-15", + 10, + vec![HttpRange { + start: 0, + length: 10, + }], + ), + T( + "bytes=0-499", + 10000, + vec![HttpRange { + start: 0, + length: 500, + }], + ), + T( + "bytes=500-999", + 10000, + vec![HttpRange { + start: 500, + length: 500, + }], + ), + T( + "bytes=-500", + 10000, + vec![HttpRange { + start: 9500, + length: 500, + }], + ), + T( + "bytes=9500-", + 10000, + vec![HttpRange { + start: 9500, + length: 500, + }], + ), + T( + "bytes=0-0,-1", + 10000, + vec![ + HttpRange { + start: 0, + length: 1, + }, + HttpRange { + start: 9999, + length: 1, + }, + ], + ), + T( + "bytes=500-600,601-999", + 10000, + vec![ + HttpRange { + start: 500, + length: 101, + }, + HttpRange { + start: 601, + length: 399, + }, + ], + ), + T( + "bytes=500-700,601-999", + 10000, + vec![ + HttpRange { + start: 500, + length: 201, + }, + HttpRange { + start: 601, + length: 399, + }, + ], + ), + // Match Apache laxity: + T( + "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", + 11, + vec![ + HttpRange { + start: 1, + length: 2, + }, + HttpRange { + start: 4, + length: 2, + }, + HttpRange { + start: 7, + length: 2, + }, + ], + ), + ]; + + for t in tests { + let header = t.0; + let size = t.1; + let expected = t.2; + + let res = HttpRange::parse(header, size); + + if res.is_err() { + if expected.is_empty() { + continue; + } else { + assert!( + false, + "parse({}, {}) returned error {:?}", + header, + size, + res.unwrap_err() + ); + } + } + + let got = res.unwrap(); + + if got.len() != expected.len() { + assert!( + false, + "len(parseRange({}, {})) = {}, want {}", + header, + size, + got.len(), + expected.len() + ); + continue; + } + + for i in 0..expected.len() { + if got[i].start != expected[i].start { + assert!( + false, + "parseRange({}, {})[{}].start = {}, want {}", + header, size, i, got[i].start, expected[i].start + ) + } + if got[i].length != expected[i].length { + assert!( + false, + "parseRange({}, {})[{}].length = {}, want {}", + header, size, i, got[i].length, expected[i].length + ) + } + } + } + } +} From db566a634cf7562f1d9ea69965aa8ecb8f92725e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 10:03:18 -0800 Subject: [PATCH 0993/1635] make State type Send compatible --- src/state.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/state.rs b/src/state.rs index d4e4c894..265c6f01 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,5 @@ use std::ops::Deref; -use std::rc::Rc; +use std::sync::Arc; use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; @@ -18,11 +18,11 @@ pub(crate) trait StateFactoryResult { } /// Application state -pub struct State(Rc); +pub struct State(Arc); impl State { pub(crate) fn new(state: T) -> State { - State(Rc::new(state)) + State(Arc::new(state)) } /// Get referecnce to inner state type. From db39a604ae0859e50f47f79cfc4a05e1fc2711ca Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 10:03:37 -0800 Subject: [PATCH 0994/1635] implement ResponseError trait for BlockingError --- src/blocking.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/blocking.rs b/src/blocking.rs index abf4282c..01be30dd 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -1,10 +1,15 @@ //! Thread pool for blocking operations +use std::fmt; + +use derive_more::Display; use futures::sync::oneshot; use futures::{Async, Future, Poll}; use parking_lot::Mutex; use threadpool::ThreadPool; +use crate::ResponseError; + /// Env variable for default cpu pool size const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL"; @@ -36,18 +41,23 @@ thread_local! { }; } -pub enum BlockingError { +#[derive(Debug, Display)] +pub enum BlockingError { + #[display(fmt = "{:?}", _0)] Error(E), + #[display(fmt = "Thread pool is gone")] Canceled, } +impl ResponseError for BlockingError {} + /// Execute blocking function on a thread pool, returns future that resolves /// to result of the function execution. pub fn run(f: F) -> CpuFuture where F: FnOnce() -> Result + Send + 'static, I: Send + 'static, - E: Send + 'static, + E: Send + fmt::Debug + 'static, { let (tx, rx) = oneshot::channel(); POOL.with(|pool| { @@ -63,7 +73,7 @@ pub struct CpuFuture { rx: oneshot::Receiver>, } -impl Future for CpuFuture { +impl Future for CpuFuture { type Item = I; type Error = BlockingError; From ad08e856d736897c6a591f87ad315806af44fac3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 10:30:17 -0800 Subject: [PATCH 0995/1635] update actix-rt --- Cargo.toml | 2 +- test-server/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ab1b090..6493404d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } openssl = { version="0.10", optional = true } [dev-dependencies] -actix-rt = "0.1.0" +actix-rt = "0.2.0" #actix-server = { version = "0.3.0", features=["ssl"] } actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } #actix-connector = { version = "0.3.0", features=["ssl"] } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 6a401cc5..554ab20e 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -33,7 +33,7 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1" -actix-rt = "0.1.0" +actix-rt = "0.2.0" actix-http = { path=".." } #actix-service = "0.3.2" actix-service = { git="https://github.com/actix/actix-net.git" } From 5cde4dc479ce5c08dc7a2db9bf47afa2d2e5a9e7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 10:41:07 -0800 Subject: [PATCH 0996/1635] update actix-rt --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index acacb2f2..a17669d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ ssl = ["openssl", "actix-server/ssl"] actix-codec = "0.1.0" #actix-service = "0.3.2" #actix-utils = "0.3.1" -actix-rt = "0.1.0" +actix-rt = "0.2.0" actix-service = { git = "https://github.com/actix/actix-net.git" } actix-utils = { git = "https://github.com/actix/actix-net.git" } From b689bb92608a51c708edd764111702134e1cf788 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 11:45:33 -0800 Subject: [PATCH 0997/1635] add failure support --- Cargo.toml | 9 +++++- src/error.rs | 78 +++++++++++++++------------------------------------- 2 files changed, 30 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6493404d..a0eb1731 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,11 +28,15 @@ name = "actix_http" path = "src/lib.rs" [features] -default = [] +default = ["fail"] # openssl ssl = ["openssl", "actix-connector/ssl"] +# failure integration. it is on by default, it will be off in future versions +# actix itself does not use failure anymore +fail = ["failure"] + [dependencies] #actix-service = "0.3.2" actix-codec = "0.1.0" @@ -77,6 +81,9 @@ trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } # openssl openssl = { version="0.10", optional = true } +# failure is optional +failure = { version = "0.1.5", optional = true } + [dev-dependencies] actix-rt = "0.2.0" #actix-server = { version = "0.3.0", features=["ssl"] } diff --git a/src/error.rs b/src/error.rs index cd5cabaa..4762f27f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -66,28 +66,6 @@ impl Error { } } - // /// Attempts to downcast this `Error` to a particular `Fail` type by - // /// reference. - // /// - // /// If the underlying error is not of type `T`, this will return `None`. - // pub fn downcast_ref(&self) -> Option<&T> { - // // in the most trivial way the cause is directly of the requested type. - // if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) { - // return Some(rv); - // } - - // // in the more complex case the error has been constructed from a failure - // // error. This happens because we implement From by - // // calling compat() and then storing it here. In failure this is - // // represented by a failure::Error being wrapped in a failure::Compat. - // // - // // So we first downcast into that compat, to then further downcast through - // // the failure's Error downcasting system into the original failure. - // let compat: Option<&failure::Compat> = - // Fail::downcast_ref(self.cause.as_fail()); - // compat.and_then(|e| e.get_ref().downcast_ref()) - // } - /// Converts error to a response instance and set error message as response body pub fn response_with_message(self) -> Response { let message = format!("{}", self); @@ -96,28 +74,6 @@ impl Error { } } -// /// Helper trait to downcast a response error into a fail. -// /// -// /// This is currently not exposed because it's unclear if this is the best way -// /// to achieve the downcasting on `Error` for which this is needed. -// #[doc(hidden)] -// pub trait InternalResponseErrorAsFail { -// #[doc(hidden)] -// fn as_fail(&self) -> &Fail; -// #[doc(hidden)] -// fn as_mut_fail(&mut self) -> &mut Fail; -// } - -// #[doc(hidden)] -// impl InternalResponseErrorAsFail for T { -// fn as_fail(&self) -> &Fail { -// self -// } -// fn as_mut_fail(&mut self) -> &mut Fail { -// self -// } -// } - /// Error that can be converted to `Response` pub trait ResponseError: fmt::Debug + fmt::Display { /// Create response for error @@ -176,18 +132,6 @@ impl From for Error { } } -// /// Compatibility for `failure::Error` -// impl ResponseError for failure::Compat where -// T: fmt::Display + fmt::Debug + Sync + Send + 'static -// { -// } - -// impl From for Error { -// fn from(err: failure::Error) -> Error { -// err.compat().into() -// } -// } - /// Return `GATEWAY_TIMEOUT` for `TimeoutError` impl ResponseError for TimeoutError { fn error_response(&self) -> Response { @@ -1023,6 +967,28 @@ where InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() } +#[cfg(feature = "fail")] +mod failure_integration { + use super::*; + use failure::{self, Fail}; + + /// Compatibility for `failure::Error` + impl ResponseError for failure::Compat + where + T: fmt::Display + fmt::Debug + Sync + Send + 'static, + { + fn error_response(&self) -> Response { + Response::new(StatusCode::INTERNAL_SERVER_ERROR) + } + } + + impl From for Error { + fn from(err: failure::Error) -> Error { + err.compat().into() + } + } +} + #[cfg(test)] mod tests { use super::*; From fe22e831447551a661f1e3d75185a16b259eba88 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 15:47:15 -0800 Subject: [PATCH 0998/1635] refactor service registration process; unify services and resources --- actix-session/src/cookie.rs | 26 +- actix-session/src/lib.rs | 14 +- actix-staticfiles/src/lib.rs | 30 +- examples/basic.rs | 28 +- src/app.rs | 420 +++++++++------------------- src/config.rs | 103 +++++++ src/extract.rs | 104 +++---- src/guard.rs | 15 +- src/lib.rs | 91 +++++- src/middleware/defaultheaders.rs | 9 +- src/request.rs | 7 +- src/resource.rs | 117 ++++++-- src/responder.rs | 21 +- src/route.rs | 43 +-- src/scope.rs | 456 +++++++++++++------------------ src/server.rs | 40 +-- src/service.rs | 35 +++ tests/test_server.rs | 65 ++--- 18 files changed, 845 insertions(+), 779 deletions(-) create mode 100644 src/config.rs diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 9cde02e0..7fd5ec64 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -174,7 +174,7 @@ impl CookieSessionInner { /// /// ```rust /// use actix_session::CookieSession; -/// use actix_web::{App, HttpResponse, HttpServer}; +/// use actix_web::{web, App, HttpResponse, HttpServer}; /// /// fn main() { /// let app = App::new().middleware( @@ -183,7 +183,7 @@ impl CookieSessionInner { /// .name("actix_session") /// .path("/") /// .secure(true)) -/// .resource("/", |r| r.to(|| HttpResponse::Ok())); +/// .service(web::resource("/").to(|| HttpResponse::Ok())); /// } /// ``` pub struct CookieSession(Rc); @@ -314,19 +314,17 @@ where #[cfg(test)] mod tests { use super::*; - use actix_web::{test, App}; + use actix_web::{test, web, App}; #[test] fn cookie_session() { let mut app = test::init_service( App::new() .middleware(CookieSession::signed(&[0; 32]).secure(false)) - .resource("/", |r| { - r.to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - }) - }), + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })), ); let request = test::TestRequest::get().to_request(); @@ -342,12 +340,10 @@ mod tests { let mut app = test::init_service( App::new() .middleware(CookieSession::signed(&[0; 32]).secure(false)) - .resource("/", |r| { - r.to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - }) - }), + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })), ); let request = test::TestRequest::get().to_request(); diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index f57e11f2..62bc5c8f 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -13,7 +13,7 @@ //! extractor allows us to get or set session data. //! //! ```rust -//! use actix_web::{App, HttpServer, HttpResponse, Error}; +//! use actix_web::{web, App, HttpServer, HttpResponse, Error}; //! use actix_session::{Session, CookieSession}; //! //! fn index(session: Session) -> Result<&'static str, Error> { @@ -29,19 +29,17 @@ //! } //! //! fn main() -> std::io::Result<()> { -//! let sys = actix_rt::System::new("example"); // <- create Actix runtime -//! +//! # std::thread::spawn(|| //! HttpServer::new( //! || App::new().middleware( //! CookieSession::signed(&[0; 32]) // <- create cookie based session middleware //! .secure(false) //! ) -//! .resource("/", |r| r.to(|| HttpResponse::Ok()))) +//! .service(web::resource("/").to(|| HttpResponse::Ok()))) //! .bind("127.0.0.1:59880")? -//! .start(); -//! # actix_rt::System::current().stop(); -//! sys.run(); -//! Ok(()) +//! .run() +//! # ); +//! # Ok(()) //! } //! ``` use std::cell::RefCell; diff --git a/actix-staticfiles/src/lib.rs b/actix-staticfiles/src/lib.rs index 81d8269c..7c3f6849 100644 --- a/actix-staticfiles/src/lib.rs +++ b/actix-staticfiles/src/lib.rs @@ -17,7 +17,7 @@ use v_htmlescape::escape as escape_html_entity; use actix_http::error::{Error, ErrorInternalServerError}; use actix_service::{boxed::BoxedNewService, NewService, Service}; -use actix_web::dev::{self, HttpServiceFactory, ResourceDef, Url}; +use actix_web::dev::{self, AppConfig, HttpServiceFactory, ResourceDef, Url}; use actix_web::{ blocking, FromRequest, HttpRequest, HttpResponse, Responder, ServiceFromRequest, ServiceRequest, ServiceResponse, @@ -226,7 +226,7 @@ fn directory_listing( /// } /// ``` pub struct StaticFiles { - path: ResourceDef, + path: String, directory: PathBuf, index: Option, show_index: bool, @@ -259,7 +259,7 @@ impl StaticFiles { } StaticFiles { - path: ResourceDef::root_prefix(path), + path: path.to_string(), directory: dir, index: None, show_index: false, @@ -300,15 +300,21 @@ impl StaticFiles { } } -impl HttpServiceFactory

    for StaticFiles { - type Factory = Self; - - fn rdef(&self) -> &ResourceDef { - &self.path - } - - fn create(self) -> Self { - self +impl HttpServiceFactory

    for StaticFiles +where + P: 'static, + C: StaticFileConfig + 'static, +{ + fn register(self, config: &mut AppConfig

    ) { + if self.default.borrow().is_none() { + *self.default.borrow_mut() = Some(config.default_service()); + } + let rdef = if config.is_root() { + ResourceDef::root_prefix(&self.path) + } else { + ResourceDef::prefix(&self.path) + }; + config.register_service(rdef, None, self) } } diff --git a/examples/basic.rs b/examples/basic.rs index 7c72439e..5fd862d4 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,23 +27,23 @@ fn main() -> std::io::Result<()> { App::new() .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .middleware(middleware::Compress::default()) - .resource("/resource1/index.html", |r| r.route(web::get().to(index))) - .resource("/resource2/index.html", |r| { - r.middleware( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_resource(|r| { - r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) - }) - .route(web::method(Method::GET).to_async(index_async)) - }) - .resource("/test1.html", |r| r.to(|| "Test\r\n")) - .resource("/", |r| r.to(no_params)) + .service(web::resource("/resource1/index.html").route(web::get().to(index))) + .service( + web::resource("/resource2/index.html") + .middleware( + middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), + ) + .default_resource(|r| { + r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) + }) + .route(web::method(Method::GET).to_async(index_async)), + ) + .service(web::resource("/test1.html").to(|| "Test\r\n")) + .service(web::resource("/").to(no_params)) }) .bind("127.0.0.1:8080")? .workers(1) .start(); - let _ = sys.run(); - Ok(()) + sys.run() } diff --git a/src/app.rs b/src/app.rs index d503d8dd..7c194d27 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,16 +7,20 @@ use actix_http::{Extensions, PayloadStream, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - AndThenNewService, ApplyTransform, IntoNewService, IntoTransform, NewService, - Service, Transform, + fn_service, AndThenNewService, ApplyTransform, IntoNewService, IntoTransform, + NewService, Service, Transform, }; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::config::AppConfig; use crate::guard::Guard; use crate::resource::Resource; -use crate::scope::{insert_slash, Scope}; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::route::Route; +use crate::service::{ + HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, + ServiceResponse, +}; use crate::state::{State, StateFactory, StateFactoryResult}; type Guards = Vec>; @@ -24,19 +28,6 @@ type HttpService

    = BoxedService, ServiceResponse, ()>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; type BoxedResponse = Box>; -pub trait HttpServiceFactory

    { - type Factory: NewService< - ServiceRequest

    , - Response = ServiceResponse, - Error = (), - InitError = (), - >; - - fn rdef(&self) -> &ResourceDef; - - fn create(self) -> Self::Factory; -} - /// Application builder - structure that follows the builder pattern /// for building application instances. pub struct App @@ -97,9 +88,9 @@ where /// fn main() { /// let app = App::new() /// .state(MyState{ counter: Cell::new(0) }) - /// .resource( - /// "/index.html", - /// |r| r.route(web::get().to(index))); + /// .service( + /// web::resource("/index.html").route( + /// web::get().to(index))); /// } /// ``` pub fn state(mut self, state: S) -> Self { @@ -120,112 +111,6 @@ where self } - /// Configure scope for common root path. - /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/{project_id}", |scope| { - /// scope - /// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - /// .resource("/path2", |r| r.to(|| HttpResponse::Ok())) - /// .resource("/path3", |r| r.to(|| HttpResponse::MethodNotAllowed())) - /// }); - /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(self, path: &str, f: F) -> AppRouter> - where - F: FnOnce(Scope

    ) -> Scope

    , - { - let mut scope = f(Scope::new(path)); - let rdef = scope.rdef().clone(); - let default = scope.get_default(); - let guards = scope.take_guards(); - - let fref = Rc::new(RefCell::new(None)); - AppRouter { - chain: self.chain, - services: vec![(rdef, boxed::new_service(scope.into_new_service()), guards)], - default: None, - defaults: vec![default], - endpoint: AppEntry::new(fref.clone()), - factory_ref: fref, - extensions: self.extensions, - state: self.state, - _t: PhantomData, - } - } - - /// Configure resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().resource("/users/{userid}/{friend}", |r| { - /// r.route(web::get().to(|| HttpResponse::Ok())) - /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// }); - /// } - /// ``` - pub fn resource(self, path: &str, f: F) -> AppRouter> - where - F: FnOnce(Resource

    ) -> Resource, - U: NewService< - ServiceRequest

    , - Response = ServiceResponse, - Error = (), - InitError = (), - > + 'static, - { - let rdef = ResourceDef::new(&insert_slash(path)); - let res = f(Resource::new()); - let default = res.get_default(); - - let fref = Rc::new(RefCell::new(None)); - AppRouter { - chain: self.chain, - services: vec![(rdef, boxed::new_service(res.into_new_service()), None)], - default: None, - defaults: vec![default], - endpoint: AppEntry::new(fref.clone()), - factory_ref: fref, - extensions: self.extensions, - state: self.state, - _t: PhantomData, - } - } - /// Register a middleware. pub fn middleware( self, @@ -259,7 +144,6 @@ where state: self.state, services: Vec::new(), default: None, - defaults: Vec::new(), factory_ref: fref, extensions: self.extensions, _t: PhantomData, @@ -298,25 +182,52 @@ where } } - /// Register resource handler service. + /// Configure route for a specific path. + /// + /// This is a simplified version of the `App::service()` method. + /// This method can not be could multiple times, in that case + /// multiple resources with one route would be registered for same resource path. + /// + /// ```rust + /// use actix_web::{web, App, HttpResponse, extract::Path}; + /// + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .route("/test1", web::get().to(index)) + /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); + /// } + /// ``` + pub fn route( + self, + path: &str, + mut route: Route

    , + ) -> AppRouter> { + self.service( + Resource::new(path) + .add_guards(route.take_guards()) + .route(route), + ) + } + + /// Register http service. pub fn service(self, service: F) -> AppRouter> where F: HttpServiceFactory

    + 'static, { let fref = Rc::new(RefCell::new(None)); + AppRouter { chain: self.chain, - services: vec![( - service.rdef().clone(), - boxed::new_service(service.create().map_init_err(|_| ())), - None, - )], default: None, - defaults: vec![], endpoint: AppEntry::new(fref.clone()), factory_ref: fref, extensions: self.extensions, state: self.state, + services: vec![Box::new(ServiceFactoryWrapper::new(service))], _t: PhantomData, } } @@ -338,10 +249,9 @@ where /// for building application instances. pub struct AppRouter { chain: C, - services: Vec<(ResourceDef, HttpNewService

    , Option)>, - default: Option>>, - defaults: Vec>>>>>, endpoint: T, + services: Vec>>, + default: Option>>, factory_ref: Rc>>>, extensions: Extensions, state: Vec>, @@ -359,131 +269,48 @@ where InitError = (), >, { - /// Configure scope for common root path. + /// Configure route for a specific path. /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. + /// This is a simplified version of the `App::service()` method. + /// This method can not be could multiple times, in that case + /// multiple resources with one route would be registered for same resource path. /// /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest, HttpResponse}; + /// use actix_web::{web, App, HttpResponse, extract::Path}; /// - /// fn main() { - /// let app = App::new().scope("/{project_id}", |scope| { - /// scope - /// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - /// .resource("/path2", |r| r.to(|| HttpResponse::Ok())) - /// .resource("/path3", |r| r.to(|| HttpResponse::MethodNotAllowed())) - /// }); + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(mut self, path: &str, f: F) -> Self - where - F: FnOnce(Scope

    ) -> Scope

    , - { - let mut scope = f(Scope::new(path)); - let rdef = scope.rdef().clone(); - let guards = scope.take_guards(); - self.defaults.push(scope.get_default()); - self.services - .push((rdef, boxed::new_service(scope.into_new_service()), guards)); - self - } - - /// Configure resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// use actix_web::{web, http, App, HttpResponse}; /// /// fn main() { /// let app = App::new() - /// .resource("/users/{userid}/{friend}", |r| { - /// r.route(web::to(|| HttpResponse::Ok())) - /// }) - /// .resource("/index.html", |r| { - /// r.route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// }); + /// .route("/test1", web::get().to(index)) + /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); /// } /// ``` - pub fn resource(mut self, path: &str, f: F) -> Self - where - F: FnOnce(Resource

    ) -> Resource, - U: NewService< - ServiceRequest

    , - Response = ServiceResponse, - Error = (), - InitError = (), - > + 'static, - { - let rdef = ResourceDef::new(&insert_slash(path)); - let resource = f(Resource::new()); - self.defaults.push(resource.get_default()); - self.services.push(( - rdef, - boxed::new_service(resource.into_new_service()), - None, - )); - self + pub fn route(self, path: &str, mut route: Route

    ) -> Self { + self.service( + Resource::new(path) + .add_guards(route.take_guards()) + .route(route), + ) } - /// Default resource to be used if no matching route could be found. + /// Register http service. /// - /// Default resource works with resources only and does not work with - /// custom services. - pub fn default_resource(mut self, f: F) -> Self - where - F: FnOnce(Resource

    ) -> Resource, - U: NewService< - ServiceRequest

    , - Response = ServiceResponse, - Error = (), - InitError = (), - > + 'static, - { - // create and configure default resource - self.default = Some(Rc::new(boxed::new_service( - f(Resource::new()).into_new_service().map_init_err(|_| ()), - ))); - - self - } - - /// Register resource handler service. + /// Http service is any type that implements `HttpServiceFactory` trait. + /// + /// Actix web provides several services implementations: + /// + /// * *Resource* is an entry in route table which corresponds to requested URL. + /// * *Scope* is a set of resources with common root path. + /// * "StaticFiles" is a service for static files support pub fn service(mut self, factory: F) -> Self where F: HttpServiceFactory

    + 'static, { - let rdef = factory.rdef().clone(); - - self.services.push(( - rdef, - boxed::new_service(factory.create().map_init_err(|_| ())), - None, - )); + self.services + .push(Box::new(ServiceFactoryWrapper::new(factory))); self } @@ -520,13 +347,34 @@ where state: self.state, services: self.services, default: self.default, - defaults: self.defaults, factory_ref: self.factory_ref, extensions: self.extensions, _t: PhantomData, } } + /// Default resource to be used if no matching route could be found. + /// + /// Default resource works with resources only and does not work with + /// custom services. + pub fn default_resource(mut self, f: F) -> Self + where + F: FnOnce(Resource

    ) -> Resource, + U: NewService< + ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, + { + // create and configure default resource + self.default = Some(Rc::new(boxed::new_service( + f(Resource::new("")).into_new_service().map_init_err(|_| ()), + ))); + + self + } + /// Register an external resource. /// /// External resources are useful for URL generation purposes only @@ -583,19 +431,30 @@ where { fn into_new_service(self) -> AndThenNewService, T> { // update resource default service - if self.default.is_some() { - for default in &self.defaults { - if default.borrow_mut().is_none() { - *default.borrow_mut() = self.default.clone(); - } - } - } + let default = self.default.unwrap_or_else(|| { + Rc::new(boxed::new_service(fn_service(|req: ServiceRequest

    | { + Ok(req.into_response(Response::NotFound().finish())) + }))) + }); + + let mut config = AppConfig::new( + "127.0.0.1:8080".parse().unwrap(), + "localhost:8080".to_owned(), + false, + default.clone(), + ); + + // register services + self.services + .into_iter() + .for_each(|mut srv| srv.register(&mut config)); // set factory *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { - default: self.default.clone(), + default: default, services: Rc::new( - self.services + config + .into_services() .into_iter() .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards))) .collect(), @@ -613,7 +472,7 @@ where pub struct AppRoutingFactory

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

    { @@ -624,12 +483,6 @@ impl NewService> for AppRoutingFactory

    { type Future = AppRoutingFactoryResponse

    ; fn new_service(&self, _: &()) -> Self::Future { - let default_fut = if let Some(ref default) = self.default { - Some(default.new_service(&())) - } else { - None - }; - AppRoutingFactoryResponse { fut: self .services @@ -643,7 +496,7 @@ impl NewService> for AppRoutingFactory

    { }) .collect(), default: None, - default_fut, + default_fut: Some(self.default.new_service(&())), } } } @@ -929,16 +782,15 @@ where #[cfg(test)] mod tests { - use actix_http::http::{Method, StatusCode}; - use super::*; - use crate::test::{self, block_on, TestRequest}; + use crate::http::{Method, StatusCode}; + use crate::test::{self, block_on, init_service, TestRequest}; use crate::{web, HttpResponse, State}; #[test] fn test_default_resource() { - let mut srv = test::init_service( - App::new().resource("/test", |r| r.to(|| HttpResponse::Ok())), + let mut srv = init_service( + App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), ); let req = TestRequest::with_uri("/test").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -948,13 +800,14 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service( + let mut srv = init_service( App::new() - .resource("/test", |r| r.to(|| HttpResponse::Ok())) - .resource("/test2", |r| { - r.default_resource(|r| r.to(|| HttpResponse::Created())) - .route(web::get().to(|| HttpResponse::Ok())) - }) + .service(web::resource("/test").to(|| HttpResponse::Ok())) + .service( + web::resource("/test2") + .default_resource(|r| r.to(|| HttpResponse::Created())) + .route(web::get().to(|| HttpResponse::Ok())), + ) .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), ); @@ -975,22 +828,21 @@ mod tests { #[test] fn test_state() { - let mut srv = test::init_service( + let mut srv = init_service( App::new() .state(10usize) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + .service(web::resource("/").to(|_: State| HttpResponse::Ok())), ); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = test::init_service( + let mut srv = init_service( App::new() .state(10u32) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + .service(web::resource("/").to(|_: State| HttpResponse::Ok())), ); - let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -998,22 +850,20 @@ mod tests { #[test] fn test_state_factory() { - let mut srv = test::init_service( + let mut srv = init_service( App::new() .state_factory(|| Ok::<_, ()>(10usize)) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + .service(web::resource("/").to(|_: State| HttpResponse::Ok())), ); - let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = test::init_service( + let mut srv = init_service( App::new() .state_factory(|| Ok::<_, ()>(10u32)) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + .service(web::resource("/").to(|_: State| HttpResponse::Ok())), ); - let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..483b0a50 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,103 @@ +use std::net::SocketAddr; +use std::rc::Rc; + +use actix_router::ResourceDef; +use actix_service::{boxed, IntoNewService, NewService}; + +use crate::guard::Guard; +use crate::service::{ServiceRequest, ServiceResponse}; + +type Guards = Vec>; +type HttpNewService

    = + boxed::BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; + +/// Application configuration +pub struct AppConfig

    { + addr: SocketAddr, + secure: bool, + host: String, + root: bool, + default: Rc>, + services: Vec<(ResourceDef, HttpNewService

    , Option)>, +} + +impl AppConfig

    { + /// Crate server settings instance + pub(crate) fn new( + addr: SocketAddr, + host: String, + secure: bool, + default: Rc>, + ) -> Self { + AppConfig { + addr, + secure, + host, + default, + root: true, + services: Vec::new(), + } + } + + /// Check if root is beeing configured + pub fn is_root(&self) -> bool { + self.root + } + + pub(crate) fn into_services( + self, + ) -> Vec<(ResourceDef, HttpNewService

    , Option)> { + self.services + } + + pub(crate) fn clone_config(&self) -> Self { + AppConfig { + addr: self.addr, + secure: self.secure, + host: self.host.clone(), + default: self.default.clone(), + services: Vec::new(), + root: false, + } + } + + /// Returns the socket address of the local half of this TCP connection + pub fn local_addr(&self) -> SocketAddr { + self.addr + } + + /// Returns true if connection is secure(https) + pub fn secure(&self) -> bool { + self.secure + } + + /// Returns host header value + pub fn host(&self) -> &str { + &self.host + } + + pub fn default_service(&self) -> Rc> { + self.default.clone() + } + + pub fn register_service( + &mut self, + rdef: ResourceDef, + guards: Option>>, + service: F, + ) where + F: IntoNewService>, + S: NewService< + ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, + { + self.services.push(( + rdef, + boxed::new_service(service.into_new_service()), + guards, + )); + } +} diff --git a/src/extract.rs b/src/extract.rs index 7350d7d9..0b212aba 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -88,9 +88,9 @@ impl ExtractorConfig for () { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.route(web::get().to(index)) // <- register handler with `Path` extractor +/// let app = App::new().service( +/// web::resource("/{username}/{count}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- register handler with `Path` extractor /// ); /// } /// ``` @@ -113,9 +113,9 @@ impl ExtractorConfig for () { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.route(web::get().to(index)) // <- use handler with Path` extractor +/// let app = App::new().service( +/// web::resource("/{username}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- use handler with Path` extractor /// ); /// } /// ``` @@ -180,9 +180,9 @@ impl From for Path { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.route(web::get().to(index)) // <- register handler with `Path` extractor +/// let app = App::new().service( +/// web::resource("/{username}/{count}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- register handler with `Path` extractor /// ); /// } /// ``` @@ -205,9 +205,9 @@ impl From for Path { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.route(web::get().to(index)) // <- use handler with Path` extractor +/// let app = App::new().service( +/// web::resource("/{username}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- use handler with Path` extractor /// ); /// } /// ``` @@ -266,9 +266,8 @@ impl fmt::Display for Path { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.route(web::get().to(index))); // <- use `Query` extractor +/// let app = App::new().service( +/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` pub struct Query(T); @@ -322,9 +321,9 @@ impl Query { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.route(web::get().to(index))); // <- use `Query` extractor +/// let app = App::new().service( +/// web::resource("/index.html") +/// .route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` impl FromRequest

    for Query @@ -462,14 +461,13 @@ impl fmt::Display for Form { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| { -/// r.route(web::get() +/// let app = App::new().service( +/// web::resource("/index.html") +/// .route(web::get() /// // change `Form` extractor configuration /// .config(extract::FormConfig::default().limit(4097)) /// .to(index)) -/// }); +/// ); /// } /// ``` #[derive(Clone)] @@ -535,9 +533,10 @@ impl Default for FormConfig { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.route(web::post().to(index))); +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().to(index)) +/// ); /// } /// ``` /// @@ -645,9 +644,10 @@ impl Responder for Json { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.route(web::post().to(index))); +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().to(index)) +/// ); /// } /// ``` impl FromRequest

    for Json @@ -692,16 +692,17 @@ where /// } /// /// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.route(web::post().config( -/// // change json extractor configuration -/// extract::JsonConfig::default().limit(4096) -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// })) -/// .to(index)) -/// }); +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().config( +/// // change json extractor configuration +/// extract::JsonConfig::default().limit(4096) +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// })) +/// .to(index)) +/// ); /// } /// ``` #[derive(Clone)] @@ -757,8 +758,10 @@ impl Default for JsonConfig { /// } /// /// fn main() { -/// let app = App::new() -/// .resource("/index.html", |r| r.route(web::get().to(index))); +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to(index)) +/// ); /// } /// ``` impl

    FromRequest

    for Bytes @@ -801,12 +804,12 @@ where /// } /// /// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.route( +/// let app = App::new().service( +/// web::resource("/index.html").route( /// web::get() /// .config(extract::PayloadConfig::new(4096)) // <- limit size of the payload /// .to(index)) // <- register handler with extractor params -/// }); +/// ); /// } /// ``` impl

    FromRequest

    for String @@ -896,9 +899,10 @@ where /// } /// /// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.route(web::post().to(index)) -/// }); +/// let app = App::new().service( +/// web::resource("/users/:first").route( +/// web::post().to(index)) +/// ); /// } /// ``` impl FromRequest

    for Option @@ -959,9 +963,9 @@ where /// } /// /// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.route(web::post().to(index)) -/// }); +/// let app = App::new().service( +/// web::resource("/users/:first").route(web::post().to(index)) +/// ); /// } /// ``` impl FromRequest

    for Result diff --git a/src/guard.rs b/src/guard.rs index 93b6e132..1632b997 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -19,11 +19,10 @@ pub trait Guard { /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { -/// App::new().resource("/index.html", |r| -/// r.route( -/// web::route() -/// .guard(guard::Any(guard::Get()).or(guard::Post())) -/// .to(|| HttpResponse::MethodNotAllowed())) +/// App::new().service(web::resource("/index.html").route( +/// web::route() +/// .guard(guard::Any(guard::Get()).or(guard::Post())) +/// .to(|| HttpResponse::MethodNotAllowed())) /// ); /// } /// ``` @@ -60,12 +59,12 @@ impl Guard for AnyGuard { /// use actix_web::{guard, web, App, HttpResponse}; /// /// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route(web::route() +/// App::new().service(web::resource("/index.html").route( +/// web::route() /// .guard( /// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain"))) /// .to(|| HttpResponse::MethodNotAllowed())) -/// }); +/// ); /// } /// ``` pub fn All(guard: F) -> AllGuard { diff --git a/src/lib.rs b/src/lib.rs index 44dcde35..3400fe29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod extract; mod handler; // mod info; pub mod blocking; +mod config; pub mod guard; pub mod middleware; mod request; @@ -43,13 +44,24 @@ pub mod dev { //! use actix_web::dev::*; //! ``` - pub use crate::app::{AppRouter, HttpServiceFactory}; + pub use crate::app::AppRouter; + pub use crate::config::AppConfig; + pub use crate::service::HttpServiceFactory; + pub use actix_http::body::{Body, MessageBody, ResponseBody}; pub use actix_http::dev::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ Extensions, Payload, PayloadStream, RequestHead, ResponseHead, }; pub use actix_router::{Path, ResourceDef, Url}; + + pub(crate) fn insert_slash(path: &str) -> String { + let mut path = path.to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/'); + }; + path + } } pub mod web { @@ -58,8 +70,74 @@ pub mod web { use crate::extract::FromRequest; use crate::handler::{AsyncFactory, Factory}; + use crate::resource::Resource; use crate::responder::Responder; - use crate::Route; + use crate::route::Route; + use crate::scope::Scope; + + /// Create resource for a specific path. + /// + /// Resources may have variable path segments. For example, a + /// resource with the path `/a/{name}/c` would match all incoming + /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. + /// + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. This is done by + /// looking up the identifier in the `Params` object returned by + /// `HttpRequest.match_info()` method. + /// + /// By default, each segment matches the regular expression `[^{}/]+`. + /// + /// You can also specify a custom regex in the form `{identifier:regex}`: + /// + /// For instance, to route `GET`-requests on any route matching + /// `/users/{userid}/{friend}` and store `userid` and `friend` in + /// the exposed `Params` object: + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, http, App, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().service( + /// web::resource("/users/{userid}/{friend}") + /// .route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// ``` + pub fn resource(path: &str) -> Resource

    { + Resource::new(path) + } + + /// Configure scope for common root path. + /// + /// Scopes collect multiple paths under a common path prefix. + /// Scope path can contain variable path segments as resources. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, App, HttpRequest, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().service( + /// web::scope("/{project_id}") + /// .service(web::resource("/path1").to(|| HttpResponse::Ok())) + /// .service(web::resource("/path2").to(|| HttpResponse::Ok())) + /// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// ``` + /// + /// In the above example, three routes get added: + /// * /{project_id}/path1 + /// * /{project_id}/path2 + /// * /{project_id}/path3 + /// + pub fn scope(path: &str) -> Scope

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

    { @@ -105,7 +183,10 @@ pub mod web { /// unimplemented!() /// } /// - /// App::new().resource("/", |r| r.route(web::to(index))); + /// App::new().service( + /// web::resource("/").route( + /// web::to(index)) + /// ); /// ``` pub fn to(handler: F) -> Route

    where @@ -125,7 +206,9 @@ pub mod web { /// futures::future::ok(HttpResponse::Ok().finish()) /// } /// - /// App::new().resource("/", |r| r.route(web::to_async(index))); + /// App::new().service(web::resource("/").route( + /// web::to_async(index)) + /// ); /// ``` pub fn to_async(handler: F) -> Route

    where diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 5fd51919..137913d2 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -19,10 +19,11 @@ use crate::service::{ServiceRequest, ServiceResponse}; /// fn main() { /// let app = App::new() /// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) -/// .resource("/test", |r| { -/// r.route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) -/// }); +/// .service( +/// web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) +/// ); /// } /// ``` #[derive(Clone)] diff --git a/src/request.rs b/src/request.rs index 211f60b8..1c86cac3 100644 --- a/src/request.rs +++ b/src/request.rs @@ -149,9 +149,10 @@ impl HttpMessage for HttpRequest { /// } /// /// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.route(web::get().to(index)) -/// }); +/// let app = App::new().service( +/// web::resource("/users/{first}").route( +/// web::get().to(index)) +/// ); /// } /// ``` impl

    FromRequest

    for HttpRequest { diff --git a/src/resource.rs b/src/resource.rs index 8d81ead0..b5cf640c 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -9,7 +9,9 @@ use actix_service::{ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::dev::{insert_slash, AppConfig, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; +use crate::guard::Guard; use crate::handler::{AsyncFactory, Factory}; use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; @@ -32,38 +34,37 @@ type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { -/// let app = App::new() -/// .resource( -/// "/", |r| r.route(web::get().to(|| HttpResponse::Ok()))); +/// let app = App::new().service( +/// web::resource("/") +/// .route(web::get().to(|| HttpResponse::Ok()))); /// } pub struct Resource> { - routes: Vec>, endpoint: T, + rdef: String, + routes: Vec>, + guards: Vec>, default: Rc>>>>, factory_ref: Rc>>>, } impl

    Resource

    { - pub fn new() -> Resource

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

    { let fref = Rc::new(RefCell::new(None)); Resource { routes: Vec::new(), + rdef: path.to_string(), endpoint: ResourceEndpoint::new(fref.clone()), factory_ref: fref, + guards: Vec::new(), default: Rc::new(RefCell::new(None)), } } } -impl

    Default for Resource

    { - fn default() -> Self { - Self::new() - } -} - -impl Resource +impl Resource where + P: 'static, T: NewService< ServiceRequest

    , Response = ServiceResponse, @@ -71,19 +72,52 @@ where InitError = (), >, { + /// Add match guard to a resource. + /// + /// ```rust + /// use actix_web::{web, guard, App, HttpResponse, extract::Path}; + /// + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .service( + /// web::resource("/app") + /// .guard(guard::Header("content-type", "text/plain")) + /// .route(web::get().to(index)) + /// ) + /// .service( + /// web::resource("/app") + /// .guard(guard::Header("content-type", "text/json")) + /// .route(web::get().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// ``` + pub fn guard(mut self, guard: G) -> Self { + self.guards.push(Box::new(guard)); + self + } + + pub(crate) fn add_guards(mut self, guards: Vec>) -> Self { + self.guards.extend(guards); + self + } + /// Register a new route. /// /// ```rust /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .resource("/", |r| { - /// r.route(web::route() + /// let app = App::new().service( + /// web::resource("/").route( + /// web::route() /// .guard(guard::Any(guard::Get()).or(guard::Put())) /// .guard(guard::Header("Content-Type", "text/plain")) /// .to(|| HttpResponse::Ok())) - /// }); + /// ); /// } /// ``` /// @@ -93,12 +127,12 @@ where /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .resource("/container/", |r| { - /// r.route(web::get().to(get_handler)) + /// let app = App::new().service( + /// web::resource("/container/") + /// .route(web::get().to(get_handler)) /// .route(web::post().to(post_handler)) /// .route(web::delete().to(delete_handler)) - /// }); + /// ); /// } /// # fn get_handler() {} /// # fn post_handler() {} @@ -109,8 +143,7 @@ where self } - /// Register a new route and add handler. This route get called for all - /// requests. + /// Register a new route and add handler. This route matches all requests. /// /// ```rust /// use actix_web::*; @@ -119,7 +152,7 @@ where /// unimplemented!() /// } /// - /// App::new().resource("/", |r| r.to(index)); + /// App::new().service(web::resource("/").to(index)); /// ``` /// /// This is shortcut for: @@ -128,7 +161,7 @@ where /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route(web::route().to(index))); + /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` pub fn to(mut self, handler: F) -> Self where @@ -150,7 +183,7 @@ where /// ok(HttpResponse::Ok().finish()) /// } /// - /// App::new().resource("/", |r| r.to_async(index)); + /// App::new().service(web::resource("/").to_async(index)); /// ``` /// /// This is shortcut for: @@ -161,7 +194,7 @@ where /// # fn index(req: HttpRequest) -> Box> { /// # unimplemented!() /// # } - /// App::new().resource("/", |r| r.route(web::route().to_async(index))); + /// App::new().service(web::resource("/").route(web::route().to_async(index))); /// ``` #[allow(clippy::wrong_self_convention)] pub fn to_async(mut self, handler: F) -> Self @@ -206,6 +239,8 @@ where let endpoint = ApplyTransform::new(mw, self.endpoint); Resource { endpoint, + rdef: self.rdef, + guards: self.guards, routes: self.routes, default: self.default, factory_ref: self.factory_ref, @@ -222,14 +257,38 @@ where { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f(Resource::new()).into_new_service().map_init_err(|_| ()), + f(Resource::new("")).into_new_service().map_init_err(|_| ()), ))))); self } +} - pub(crate) fn get_default(&self) -> Rc>>>> { - self.default.clone() +impl HttpServiceFactory

    for Resource +where + P: 'static, + T: NewService< + ServiceRequest

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

    ) { + if self.default.borrow().is_none() { + *self.default.borrow_mut() = Some(config.default_service()); + } + let guards = if self.guards.is_empty() { + None + } else { + Some(std::mem::replace(&mut self.guards, Vec::new())) + }; + let rdef = if config.is_root() { + ResourceDef::new(&insert_slash(&self.rdef)) + } else { + ResourceDef::new(&insert_slash(&self.rdef)) + }; + config.register_service(rdef, guards, self) } } diff --git a/src/responder.rs b/src/responder.rs index dedfa1b4..9e9e0f10 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -288,22 +288,21 @@ where #[cfg(test)] mod tests { - // use actix_http::body::Body; - use actix_http::body::{Body, ResponseBody}; - use actix_http::http::StatusCode; - use actix_service::{IntoNewService, NewService, Service}; + use actix_service::Service; use bytes::Bytes; - use crate::test::TestRequest; - use crate::App; + use crate::body::{Body, ResponseBody}; + use crate::http::StatusCode; + use crate::test::{init_service, TestRequest}; + use crate::{web, App}; #[test] fn test_option_responder() { - let app = App::new() - .resource("/none", |r| r.to(|| -> Option<&'static str> { None })) - .resource("/some", |r| r.to(|| Some("some"))) - .into_new_service(); - let mut srv = TestRequest::block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new() + .service(web::resource("/none").to(|| -> Option<&'static str> { None })) + .service(web::resource("/some").to(|| Some("some"))), + ); let req = TestRequest::with_uri("/none").to_request(); let resp = TestRequest::block_on(srv.call(req)).unwrap(); diff --git a/src/route.rs b/src/route.rs index 42e78488..bac897ab 100644 --- a/src/route.rs +++ b/src/route.rs @@ -84,6 +84,10 @@ impl Route

    { *self.config_ref.borrow_mut() = self.config.storage.clone(); self } + + pub(crate) fn take_guards(&mut self) -> Vec> { + std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new()) + } } impl

    NewService> for Route

    { @@ -161,12 +165,12 @@ impl Route

    { /// ```rust /// # use actix_web::*; /// # fn main() { - /// App::new().resource("/path", |r| { - /// r.route(web::get() - /// .guard(guard::Get()) + /// App::new().service(web::resource("/path").route( + /// web::get() + /// .method(http::Method::CONNECT) /// .guard(guard::Header("content-type", "text/plain")) /// .to(|req: HttpRequest| HttpResponse::Ok())) - /// }); + /// ); /// # } /// ``` pub fn method(mut self, method: Method) -> Self { @@ -181,12 +185,12 @@ impl Route

    { /// ```rust /// # use actix_web::*; /// # fn main() { - /// App::new().resource("/path", |r| { - /// r.route(web::route() + /// App::new().service(web::resource("/path").route( + /// web::route() /// .guard(guard::Get()) /// .guard(guard::Header("content-type", "text/plain")) /// .to(|req: HttpRequest| HttpResponse::Ok())) - /// }); + /// ); /// # } /// ``` pub fn guard(mut self, f: F) -> Self { @@ -229,9 +233,9 @@ impl Route

    { /// } /// /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.route(web::get().to(index)), // <- register handler + /// let app = App::new().service( + /// web::resource("/{username}/index.html") // <- define path parameters + /// .route(web::get().to(index)) // <- register handler /// ); /// } /// ``` @@ -254,9 +258,9 @@ impl Route

    { /// } /// /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.route(web::get().to(index)), + /// let app = App::new().service( + /// web::resource("/{username}/index.html") // <- define path parameters + /// .route(web::get().to(index)) /// ); /// } /// ``` @@ -294,9 +298,9 @@ impl Route

    { /// } /// /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.route(web::get().to_async(index)), // <- register async handler + /// let app = App::new().service( + /// web::resource("/{username}/index.html") // <- define path parameters + /// .route(web::get().to_async(index)) // <- register async handler /// ); /// } /// ``` @@ -328,15 +332,14 @@ impl Route

    { /// } /// /// fn main() { - /// let app = App::new().resource("/index.html", |r| { - /// r.route( + /// let app = App::new().service( + /// web::resource("/index.html").route( /// web::get() /// // limit size of the payload /// .config(extract::PayloadConfig::new(4096)) /// // register handler /// .to(index) - /// ) - /// }); + /// )); /// } /// ``` pub fn config(mut self, config: C) -> Self { diff --git a/src/scope.rs b/src/scope.rs index dc88388f..9bc0b50d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -10,10 +10,13 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, Poll}; +use crate::dev::{insert_slash, AppConfig, HttpServiceFactory}; use crate::guard::Guard; use crate::resource::Resource; use crate::route::Route; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::service::{ + ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, +}; type Guards = Vec>; type HttpService

    = BoxedService, ServiceResponse, ()>; @@ -35,12 +38,12 @@ type BoxedResponse = Box>; /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { -/// let app = App::new().scope("/{project_id}/", |scope| { -/// scope -/// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) -/// .resource("/path2", |r| r.route(web::get().to(|| HttpResponse::Ok()))) -/// .resource("/path3", |r| r.route(web::head().to(|| HttpResponse::MethodNotAllowed()))) -/// }); +/// let app = App::new().service( +/// web::scope("/{project_id}/") +/// .service(web::resource("/path1").to(|| HttpResponse::Ok())) +/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) +/// .service(web::resource("/path3").route(web::head().to(|| HttpResponse::MethodNotAllowed()))) +/// ); /// } /// ``` /// @@ -51,11 +54,10 @@ type BoxedResponse = Box>; /// pub struct Scope> { endpoint: T, - rdef: ResourceDef, - services: Vec<(ResourceDef, HttpNewService

    , Option)>, + rdef: String, + services: Vec>>, guards: Vec>, default: Rc>>>>, - defaults: Vec>>>>>, factory_ref: Rc>>>, } @@ -63,21 +65,20 @@ impl Scope

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

    { let fref = Rc::new(RefCell::new(None)); - let rdef = ResourceDef::prefix(&insert_slash(path)); Scope { endpoint: ScopeEndpoint::new(fref.clone()), - rdef: rdef.clone(), + rdef: path.to_string(), guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), - defaults: Vec::new(), factory_ref: fref, } } } -impl Scope +impl Scope where + P: 'static, T: NewService< ServiceRequest

    , Response = ServiceResponse, @@ -85,12 +86,7 @@ where InitError = (), >, { - #[inline] - pub(crate) fn rdef(&self) -> &ResourceDef { - &self.rdef - } - - /// Add guard to a scope. + /// Add match guard to a scope. /// /// ```rust /// use actix_web::{web, guard, App, HttpRequest, HttpResponse, extract::Path}; @@ -100,14 +96,14 @@ where /// } /// /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope + /// let app = App::new().service( + /// web::scope("/app") /// .guard(guard::Header("content-type", "text/plain")) - /// .route("/test1",web::get().to(index)) + /// .route("/test1", web::get().to(index)) /// .route("/test2", web::post().to(|r: HttpRequest| { /// HttpResponse::MethodNotAllowed() /// })) - /// }); + /// ); /// } /// ``` pub fn guard(mut self, guard: G) -> Self { @@ -115,10 +111,10 @@ where self } - /// Create nested scope. + /// Create nested service. /// /// ```rust - /// use actix_web::{App, HttpRequest}; + /// use actix_web::{web, App, HttpRequest}; /// /// struct AppState; /// @@ -127,28 +123,25 @@ where /// } /// /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.to(index))) - /// }); + /// let app = App::new().service( + /// web::scope("/app").service( + /// web::scope("/v1") + /// .service(web::resource("/test1").to(index))) + /// ); /// } /// ``` - pub fn nested(mut self, path: &str, f: F) -> Self + pub fn service(mut self, factory: F) -> Self where - F: FnOnce(Scope

    ) -> Scope

    , + F: HttpServiceFactory

    + 'static, { - let mut scope = f(Scope::new(path)); - let rdef = scope.rdef().clone(); - let guards = scope.take_guards(); - self.defaults.push(scope.get_default()); self.services - .push((rdef, boxed::new_service(scope.into_new_service()), guards)); - + .push(Box::new(ServiceFactoryWrapper::new(factory))); self } /// Configure route for a specific path. /// - /// This is a simplified version of the `Scope::resource()` method. + /// This is a simplified version of the `Scope::service()` method. /// This method can not be could multiple times, in that case /// multiple resources with one route would be registered for same resource path. /// @@ -160,58 +153,19 @@ where /// } /// /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.route("/test1", web::get().to(index)) + /// let app = App::new().service( + /// web::scope("/app") + /// .route("/test1", web::get().to(index)) /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())) - /// }); + /// ); /// } /// ``` - pub fn route(self, path: &str, route: Route

    ) -> Self { - self.resource(path, move |r| r.route(route)) - } - - /// configure resource for a specific path. - /// - /// This method is similar to an `App::resource()` method. - /// Resources may have variable path segments. Resource path uses scope - /// path as a path prefix. - /// - /// ```rust - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new().scope("/api", |scope| { - /// scope.resource("/users/{userid}/{friend}", |r| { - /// r.route(web::get().to(|| HttpResponse::Ok())) - /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// .route(web::route() - /// .guard(guard::Any(guard::Get()).or(guard::Put())) - /// .guard(guard::Header("Content-Type", "text/plain")) - /// .to(|| HttpResponse::Ok())) - /// }) - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> Self - where - F: FnOnce(Resource

    ) -> Resource, - U: NewService< - ServiceRequest

    , - Response = ServiceResponse, - Error = (), - InitError = (), - > + 'static, - { - // add resource - let rdef = ResourceDef::new(&insert_slash(path)); - let resource = f(Resource::new()); - self.defaults.push(resource.get_default()); - self.services.push(( - rdef, - boxed::new_service(resource.into_new_service()), - None, - )); - self + pub fn route(self, path: &str, mut route: Route

    ) -> Self { + self.service( + Resource::new(path) + .add_guards(route.take_guards()) + .route(route), + ) } /// Default resource to be used if no matching route could be found. @@ -227,7 +181,7 @@ where { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f(Resource::new()).into_new_service().map_init_err(|_| ()), + f(Resource::new("")).into_new_service().map_init_err(|_| ()), ))))); self @@ -267,62 +221,53 @@ where guards: self.guards, services: self.services, default: self.default, - defaults: self.defaults, factory_ref: self.factory_ref, } } - - pub(crate) fn get_default(&self) -> Rc>>>> { - self.default.clone() - } - - pub(crate) fn take_guards(&mut self) -> Option>> { - if self.guards.is_empty() { - None - } else { - Some(std::mem::replace(&mut self.guards, Vec::new())) - } - } } -pub(crate) fn insert_slash(path: &str) -> String { - let mut path = path.to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - path -} - -impl IntoNewService> for Scope +impl HttpServiceFactory

    for Scope where + P: 'static, T: NewService< - ServiceRequest

    , - Response = ServiceResponse, - Error = (), - InitError = (), - >, + ServiceRequest

    , + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, { - fn into_new_service(self) -> T { - // update resource default service - if let Some(ref d) = *self.default.as_ref().borrow() { - for default in &self.defaults { - if default.borrow_mut().is_none() { - *default.borrow_mut() = Some(d.clone()); - } - } + fn register(self, config: &mut AppConfig

    ) { + if self.default.borrow().is_none() { + *self.default.borrow_mut() = Some(config.default_service()); } + // register services + let mut cfg = config.clone_config(); + self.services + .into_iter() + .for_each(|mut srv| srv.register(&mut cfg)); + *self.factory_ref.borrow_mut() = Some(ScopeFactory { default: self.default.clone(), services: Rc::new( - self.services + cfg.into_services() .into_iter() .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards))) .collect(), ), }); - self.endpoint + let guards = if self.guards.is_empty() { + None + } else { + Some(self.guards) + }; + let rdef = if config.is_root() { + ResourceDef::prefix(&insert_slash(&self.rdef)) + } else { + ResourceDef::prefix(&insert_slash(&self.rdef)) + }; + config.register_service(rdef, guards, self.endpoint) } } @@ -504,19 +449,22 @@ impl NewService> for ScopeEndpoint

    { #[cfg(test)] mod tests { - use actix_http::body::{Body, ResponseBody}; - use actix_http::http::{Method, StatusCode}; - use actix_service::{IntoNewService, NewService, Service}; + use actix_service::Service; use bytes::Bytes; - use crate::test::{self, block_on, TestRequest}; + use crate::body::{Body, ResponseBody}; + use crate::http::{Method, StatusCode}; + use crate::test::{block_on, init_service, TestRequest}; use crate::{guard, web, App, HttpRequest, HttpResponse}; #[test] fn test_scope() { - let mut srv = test::init_service(App::new().scope("/app", |scope| { - scope.resource("/path1", |r| r.to(|| HttpResponse::Ok())) - })); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -525,11 +473,13 @@ mod tests { #[test] fn test_scope_root() { - let mut srv = test::init_service(App::new().scope("/app", |scope| { - scope - .resource("", |r| r.to(|| HttpResponse::Ok())) - .resource("/", |r| r.to(|| HttpResponse::Created())) - })); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), + ); let req = TestRequest::with_uri("/app").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -542,9 +492,9 @@ mod tests { #[test] fn test_scope_root2() { - let mut srv = test::init_service(App::new().scope("/app/", |scope| { - scope.resource("", |r| r.to(|| HttpResponse::Ok())) - })); + let mut srv = init_service(App::new().service( + web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), + )); let req = TestRequest::with_uri("/app").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -557,12 +507,9 @@ mod tests { #[test] fn test_scope_root3() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("/", |r| r.to(|| HttpResponse::Ok())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service(App::new().service( + web::scope("/app/").service(web::resource("/").to(|| HttpResponse::Ok())), + )); let req = TestRequest::with_uri("/app").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -575,15 +522,13 @@ mod tests { #[test] fn test_scope_route() { - let app = App::new() - .scope("app", |scope| { - scope.resource("/path1", |r| { - r.route(web::get().to(|| HttpResponse::Ok())) - .route(web::delete().to(|| HttpResponse::Ok())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("app") + .route("/path1", web::get().to(|| HttpResponse::Ok())) + .route("/path1", web::delete().to(|| HttpResponse::Ok())), + ), + ); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -604,15 +549,15 @@ mod tests { #[test] fn test_scope_route_without_leading_slash() { - let app = App::new() - .scope("app", |scope| { - scope.resource("path1", |r| { - r.route(web::get().to(|| HttpResponse::Ok())) - .route(web::delete().to(|| HttpResponse::Ok())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("app").service( + web::resource("path1") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::delete().to(|| HttpResponse::Ok())), + ), + ), + ); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -633,14 +578,13 @@ mod tests { #[test] fn test_scope_guard() { - let app = App::new() - .scope("/app", |scope| { - scope + let mut srv = init_service( + App::new().service( + web::scope("/app") .guard(guard::Get()) - .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) @@ -657,17 +601,13 @@ mod tests { #[test] fn test_scope_variable_segment() { - let app = App::new() - .scope("/ab-{project}", |scope| { - scope.resource("/path1", |r| { - r.to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = + init_service(App::new().service(web::scope("/ab-{project}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + }), + ))); let req = TestRequest::with_uri("/ab-project1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -688,14 +628,14 @@ mod tests { #[test] fn test_nested_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope.resource("/path1", |r| r.to(|| HttpResponse::Created())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::scope("/t1").service( + web::resource("/path1").to(|| HttpResponse::Created()), + )), + ), + ); let req = TestRequest::with_uri("/app/t1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -704,14 +644,14 @@ mod tests { #[test] fn test_nested_scope_no_slash() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("t1", |scope| { - scope.resource("/path1", |r| r.to(|| HttpResponse::Created())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::scope("t1").service( + web::resource("/path1").to(|| HttpResponse::Created()), + )), + ), + ); let req = TestRequest::with_uri("/app/t1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -720,16 +660,15 @@ mod tests { #[test] fn test_nested_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .resource("", |r| r.to(|| HttpResponse::Ok())) - .resource("/", |r| r.to(|| HttpResponse::Created())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), + ), + ); let req = TestRequest::with_uri("/app/t1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -742,16 +681,15 @@ mod tests { #[test] fn test_nested_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") .guard(guard::Get()) - .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ), + ); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) @@ -768,21 +706,14 @@ mod tests { #[test] fn test_nested_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project_id}", |scope| { - scope.resource("/path1", |r| { - r.to(|r: HttpRequest| { - HttpResponse::Created().body(format!( - "project: {}", - &r.match_info()["project_id"] - )) - }) - }) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project_id}").service(web::resource("/path1").to( + |r: HttpRequest| { + HttpResponse::Created() + .body(format!("project: {}", &r.match_info()["project_id"])) + }, + )), + ))); let req = TestRequest::with_uri("/app/project_1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -799,24 +730,17 @@ mod tests { #[test] fn test_nested2_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project}", |scope| { - scope.nested("/{id}", |scope| { - scope.resource("/path1", |r| { - r.to(|r: HttpRequest| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - }) - }) - }) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project}").service(web::scope("/{id}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Created().body(format!( + "project: {} - {}", + &r.match_info()["project"], + &r.match_info()["id"], + )) + }), + )), + ))); let req = TestRequest::with_uri("/app/test/1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -837,14 +761,13 @@ mod tests { #[test] fn test_default_resource() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - .default_resource(|r| r.to(|| HttpResponse::BadRequest())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())) + .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + ), + ); let req = TestRequest::with_uri("/app/path2").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -857,14 +780,15 @@ mod tests { #[test] fn test_default_resource_propagation() { - let app = App::new() - .scope("/app1", |scope| { - scope.default_resource(|r| r.to(|| HttpResponse::BadRequest())) - }) - .scope("/app2", |scope| scope) - .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new() + .service( + web::scope("/app1") + .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + ) + .service(web::scope("/app2")) + .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), + ); let req = TestRequest::with_uri("/non-exist").to_request(); let resp = block_on(srv.call(req)).unwrap(); diff --git a/src/server.rs b/src/server.rs index 78ba692e..c28cb280 100644 --- a/src/server.rs +++ b/src/server.rs @@ -33,20 +33,19 @@ struct Config { /// /// ```rust /// use std::io; -/// use actix_web::{App, HttpResponse, HttpServer}; +/// use actix_web::{web, App, HttpResponse, HttpServer}; /// /// fn main() -> io::Result<()> { /// let sys = actix_rt::System::new("example"); // <- create Actix runtime /// /// HttpServer::new( /// || App::new() -/// .resource("/", |r| r.to(|| HttpResponse::Ok()))) +/// .service(web::resource("/").to(|| HttpResponse::Ok()))) /// .bind("127.0.0.1:59090")? /// .start(); /// /// # actix_rt::System::current().stop(); -/// sys.run(); -/// Ok(()) +/// sys.run() /// } /// ``` pub struct HttpServer @@ -448,17 +447,17 @@ where /// configured. /// /// ```rust - /// use actix_web::{App, HttpResponse, HttpServer}; + /// use std::io; + /// use actix_web::{web, App, HttpResponse, HttpServer}; /// - /// fn main() { + /// fn main() -> io::Result<()> { /// let sys = actix_rt::System::new("example"); // <- create Actix system /// - /// HttpServer::new(|| App::new().resource("/", |r| r.to(|| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") + /// HttpServer::new(|| App::new().service(web::resource("/").to(|| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0")? /// .start(); /// # actix_rt::System::current().stop(); - /// sys.run(); // <- Run actix system, this method starts all async processes + /// sys.run() // <- Run actix system, this method starts all async processes /// } /// ``` pub fn start(mut self) -> Server { @@ -472,20 +471,23 @@ where /// /// This methods panics if no socket addresses get bound. /// - /// ```rust,ignore - /// use actix_web::{App, HttpResponse, HttpServer}; + /// ```rust + /// use std::io; + /// use actix_web::{web, App, HttpResponse, HttpServer}; /// - /// fn main() { - /// HttpServer::new(|| App::new().resource("/", |r| r.to(|| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .run(); + /// fn main() -> io::Result<()> { + /// # std::thread::spawn(|| { + /// HttpServer::new(|| App::new().service(web::resource("/").to(|| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0")? + /// .run() + /// # }); + /// # Ok(()) /// } /// ``` - pub fn run(self) { + pub fn run(self) -> io::Result<()> { let sys = System::new("http-server"); self.start(); - sys.run(); + sys.run() } } diff --git a/src/service.rs b/src/service.rs index c9666e31..e2213060 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::cell::{Ref, RefMut}; use std::fmt; +use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody, ResponseBody}; @@ -12,8 +13,42 @@ use actix_http::{ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; +use crate::config::AppConfig; use crate::request::HttpRequest; +pub trait HttpServiceFactory

    { + fn register(self, config: &mut AppConfig

    ); +} + +pub(crate) trait ServiceFactory

    { + fn register(&mut self, config: &mut AppConfig

    ); +} + +pub(crate) struct ServiceFactoryWrapper { + factory: Option, + _t: PhantomData

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

    for ServiceFactoryWrapper +where + T: HttpServiceFactory

    , +{ + fn register(&mut self, config: &mut AppConfig

    ) { + if let Some(item) = self.factory.take() { + item.register(config) + } + } +} + pub struct ServiceRequest

    { req: HttpRequest, payload: Payload

    , diff --git a/tests/test_server.rs b/tests/test_server.rs index d28f207c..ebe968fa 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -40,7 +40,8 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn test_body() { let mut srv = TestServer::new(|| { h1::H1Service::new( - App::new().resource("/", |r| r.route(web::to(|| Response::Ok().body(STR)))), + App::new() + .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -59,7 +60,7 @@ fn test_body_gzip() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| r.route(web::to(|| Response::Ok().body(STR)))), + .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -87,9 +88,10 @@ fn test_body_gzip_large() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| { - r.route(web::to(move || Response::Ok().body(data.clone()))) - }), + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), ) }); @@ -120,9 +122,10 @@ fn test_body_gzip_large_random() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| { - r.route(web::to(move || Response::Ok().body(data.clone()))) - }), + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), ) }); @@ -147,13 +150,11 @@ fn test_body_chunked_implicit() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| { - r.route(web::get().to(move || { - Response::Ok().streaming(once(Ok::<_, Error>( - Bytes::from_static(STR.as_ref()), - ))) - })) - }), + .service(web::resource("/").route(web::get().to(move || { + Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( + STR.as_ref(), + )))) + }))), ) }); @@ -181,13 +182,11 @@ fn test_body_br_streaming() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Br)) - .resource("/", |r| { - r.route(web::to(move || { - Response::Ok().streaming(once(Ok::<_, Error>( - Bytes::from_static(STR.as_ref()), - ))) - })) - }), + .service(web::resource("/").route(web::to(move || { + Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( + STR.as_ref(), + )))) + }))), ) }); @@ -208,9 +207,9 @@ fn test_body_br_streaming() { #[test] fn test_head_binary() { let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().resource("/", |r| { - r.route(web::head().to(move || Response::Ok().content_length(100).body(STR))) - })) + h1::H1Service::new(App::new().service(web::resource("/").route( + web::head().to(move || Response::Ok().content_length(100).body(STR)), + ))) }); let request = srv.head().finish().unwrap(); @@ -230,14 +229,14 @@ fn test_head_binary() { #[test] fn test_no_chunking() { let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().resource("/", |r| { - r.route(web::to(move || { + h1::H1Service::new(App::new().service(web::resource("/").route(web::to( + move || { Response::Ok() .no_chunking() .content_length(STR.len() as u64) .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) - })) - })) + }, + )))) }); let request = srv.get().finish().unwrap(); @@ -256,7 +255,9 @@ fn test_body_deflate() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Deflate)) - .resource("/", |r| r.route(web::to(move || Response::Ok().body(STR)))), + .service( + web::resource("/").route(web::to(move || Response::Ok().body(STR))), + ), ) }); @@ -281,7 +282,9 @@ fn test_body_brotli() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Br)) - .resource("/", |r| r.route(web::to(move || Response::Ok().body(STR)))), + .service( + web::resource("/").route(web::to(move || Response::Ok().body(STR))), + ), ) }); From 561a89b8b3aa84fb1993c9f7380d8dc077972cef Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 17:32:41 -0800 Subject: [PATCH 0999/1635] copy logger --- logger.rs | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 logger.rs diff --git a/logger.rs b/logger.rs new file mode 100644 index 00000000..b7bb1bb8 --- /dev/null +++ b/logger.rs @@ -0,0 +1,384 @@ +//! Request logging middleware +use std::collections::HashSet; +use std::env; +use std::fmt::{self, Display, Formatter}; + +use regex::Regex; +use time; + +use error::Result; +use httpmessage::HttpMessage; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middleware::{Finished, Middleware, Started}; + +/// `Middleware` for logging request and response info to the terminal. +/// +/// `Logger` middleware uses standard log crate to log information. You should +/// enable logger for `actix_web` package to see access log. +/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar) +/// +/// ## Usage +/// +/// Create `Logger` middleware with the specified `format`. +/// Default `Logger` could be created with `default` method, it uses the +/// default format: +/// +/// ```ignore +/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T +/// ``` +/// ```rust +/// # extern crate actix_web; +/// extern crate env_logger; +/// use actix_web::middleware::Logger; +/// use actix_web::App; +/// +/// fn main() { +/// std::env::set_var("RUST_LOG", "actix_web=info"); +/// env_logger::init(); +/// +/// let app = App::new() +/// .middleware(Logger::default()) +/// .middleware(Logger::new("%a %{User-Agent}i")) +/// .finish(); +/// } +/// ``` +/// +/// ## 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 +/// +/// `%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'] +/// +pub struct Logger { + format: Format, + exclude: HashSet, +} + +impl Logger { + /// Create `Logger` middleware with the specified `format`. + pub fn new(format: &str) -> Logger { + Logger { + format: Format::new(format), + exclude: HashSet::new(), + } + } + + /// Ignore and do not log access info for specified path. + pub fn exclude>(mut self, path: T) -> Self { + self.exclude.insert(path.into()); + self + } +} + +impl Default for Logger { + /// Create `Logger` middleware with format: + /// + /// ```ignore + /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T + /// ``` + fn default() -> Logger { + Logger { + format: Format::default(), + exclude: HashSet::new(), + } + } +} + +struct StartTime(time::Tm); + +impl Logger { + fn log(&self, req: &HttpRequest, resp: &HttpResponse) { + if let Some(entry_time) = req.extensions().get::() { + let render = |fmt: &mut Formatter| { + for unit in &self.format.0 { + unit.render(fmt, req, resp, entry_time.0)?; + } + Ok(()) + }; + info!("{}", FormatDisplay(&render)); + } + } +} + +impl Middleware for Logger { + fn start(&self, req: &HttpRequest) -> Result { + if !self.exclude.contains(req.path()) { + req.extensions_mut().insert(StartTime(time::now())); + } + Ok(Started::Done) + } + + fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { + self.log(req, resp); + Finished::Done + } +} + +/// A formatting style for the `Logger`, consisting of multiple +/// `FormatText`s concatenated into one line. +#[derive(Clone)] +#[doc(hidden)] +struct Format(Vec); + +impl Default for Format { + /// Return the default formatting style for the `Logger`: + fn default() -> Format { + Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#) + } +} + +impl Format { + /// Create a `Format` from a format string. + /// + /// Returns `None` if the format string syntax is incorrect. + pub fn new(s: &str) -> Format { + trace!("Access log format: {}", s); + let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap(); + + let mut idx = 0; + let mut results = Vec::new(); + for cap in fmt.captures_iter(s) { + let m = cap.get(0).unwrap(); + let pos = m.start(); + if idx != pos { + results.push(FormatText::Str(s[idx..pos].to_owned())); + } + idx = m.end(); + + if let Some(key) = cap.get(2) { + results.push(match cap.get(3).unwrap().as_str() { + "i" => FormatText::RequestHeader(key.as_str().to_owned()), + "o" => FormatText::ResponseHeader(key.as_str().to_owned()), + "e" => FormatText::EnvironHeader(key.as_str().to_owned()), + _ => unreachable!(), + }) + } else { + let m = cap.get(1).unwrap(); + results.push(match m.as_str() { + "%" => FormatText::Percent, + "a" => FormatText::RemoteAddr, + "t" => FormatText::RequestTime, + "r" => FormatText::RequestLine, + "s" => FormatText::ResponseStatus, + "b" => FormatText::ResponseSize, + "T" => FormatText::Time, + "D" => FormatText::TimeMillis, + _ => FormatText::Str(m.as_str().to_owned()), + }); + } + } + if idx != s.len() { + results.push(FormatText::Str(s[idx..].to_owned())); + } + + Format(results) + } +} + +/// A string of text to be logged. This is either one of the data +/// fields supported by the `Logger`, or a custom `String`. +#[doc(hidden)] +#[derive(Debug, Clone)] +pub enum FormatText { + Str(String), + Percent, + RequestLine, + RequestTime, + ResponseStatus, + ResponseSize, + Time, + TimeMillis, + RemoteAddr, + RequestHeader(String), + ResponseHeader(String), + EnvironHeader(String), +} + +impl FormatText { + 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), + FormatText::Percent => "%".fmt(fmt), + FormatText::RequestLine => { + if req.query_string().is_empty() { + fmt.write_fmt(format_args!( + "{} {} {:?}", + req.method(), + req.path(), + req.version() + )) + } else { + fmt.write_fmt(format_args!( + "{} {}?{} {:?}", + req.method(), + req.path(), + req.query_string(), + req.version() + )) + } + } + FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt), + FormatText::ResponseSize => resp.response_size().fmt(fmt), + FormatText::Time => { + let rt = time::now() - entry_time; + let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0; + fmt.write_fmt(format_args!("{:.6}", rt)) + } + FormatText::TimeMillis => { + let rt = time::now() - entry_time; + let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0; + fmt.write_fmt(format_args!("{:.6}", rt)) + } + FormatText::RemoteAddr => { + if let Some(remote) = req.connection_info().remote() { + return remote.fmt(fmt); + } else { + "-".fmt(fmt) + } + } + FormatText::RequestTime => entry_time + .strftime("[%d/%b/%Y:%H:%M:%S %z]") + .unwrap() + .fmt(fmt), + FormatText::RequestHeader(ref name) => { + let s = if let Some(val) = req.headers().get(name) { + if let Ok(s) = val.to_str() { + s + } else { + "-" + } + } else { + "-" + }; + fmt.write_fmt(format_args!("{}", s)) + } + FormatText::ResponseHeader(ref name) => { + let s = if let Some(val) = resp.headers().get(name) { + if let Ok(s) = val.to_str() { + s + } else { + "-" + } + } else { + "-" + }; + fmt.write_fmt(format_args!("{}", s)) + } + FormatText::EnvironHeader(ref name) => { + if let Ok(val) = env::var(name) { + fmt.write_fmt(format_args!("{}", val)) + } else { + "-".fmt(fmt) + } + } + } + } +} + +pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>); + +impl<'a> fmt::Display for FormatDisplay<'a> { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { + (self.0)(fmt) + } +} + +#[cfg(test)] +mod tests { + use time; + + use super::*; + use http::{header, StatusCode}; + use test::TestRequest; + + #[test] + fn test_logger() { + let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); + + let req = TestRequest::with_header( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + ).finish(); + let resp = HttpResponse::build(StatusCode::OK) + .header("X-Test", "ttt") + .force_close() + .finish(); + + match logger.start(&req) { + Ok(Started::Done) => (), + _ => panic!(), + }; + match logger.finish(&req, &resp) { + Finished::Done => (), + _ => panic!(), + } + let entry_time = time::now(); + let render = |fmt: &mut Formatter| { + for unit in &logger.format.0 { + unit.render(fmt, &req, &resp, entry_time)?; + } + Ok(()) + }; + let s = format!("{}", FormatDisplay(&render)); + assert!(s.contains("ACTIX-WEB ttt")); + } + + #[test] + fn test_default_format() { + let format = Format::default(); + + let req = TestRequest::with_header( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + ).finish(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let entry_time = time::now(); + + let render = |fmt: &mut Formatter| { + for unit in &format.0 { + unit.render(fmt, &req, &resp, entry_time)?; + } + Ok(()) + }; + let s = format!("{}", FormatDisplay(&render)); + assert!(s.contains("GET / HTTP/1.1")); + assert!(s.contains("200 0")); + assert!(s.contains("ACTIX-WEB")); + + let req = TestRequest::with_uri("/?test").finish(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + let entry_time = time::now(); + + let render = |fmt: &mut Formatter| { + for unit in &format.0 { + unit.render(fmt, &req, &resp, entry_time)?; + } + Ok(()) + }; + let s = format!("{}", FormatDisplay(&render)); + assert!(s.contains("GET /?test HTTP/1.1")); + } +} From 244fff9e0af6284e92cc38417f1d295c8e20d5aa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 19:19:27 -0800 Subject: [PATCH 1000/1635] added Logger middleware --- Cargo.toml | 2 + src/app.rs | 4 +- src/lib.rs | 2 +- src/middleware/defaultheaders.rs | 6 +- logger.rs => src/middleware/logger.rs | 399 +++++++++++++++++--------- src/middleware/mod.rs | 3 + src/resource.rs | 2 +- src/route.rs | 5 +- src/scope.rs | 6 +- 9 files changed, 279 insertions(+), 150 deletions(-) rename logger.rs => src/middleware/logger.rs (52%) diff --git a/Cargo.toml b/Cargo.toml index a17669d1..2abb4c72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,10 +81,12 @@ mime = "0.3" net2 = "0.2.33" num_cpus = "1.10" parking_lot = "0.7" +regex = "1.0" serde = "1.0" serde_json = "1.0" serde_urlencoded = "^0.5.3" threadpool = "1.7" +time = "0.1" # middlewares # actix-session = { path="session", optional = true } diff --git a/src/app.rs b/src/app.rs index 7c194d27..f62c064a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -451,7 +451,7 @@ where // set factory *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { - default: default, + default, services: Rc::new( config .into_services() @@ -784,7 +784,7 @@ where mod tests { use super::*; use crate::http::{Method, StatusCode}; - use crate::test::{self, block_on, init_service, TestRequest}; + use crate::test::{block_on, init_service, TestRequest}; use crate::{web, HttpResponse, State}; #[test] diff --git a/src/lib.rs b/src/lib.rs index 3400fe29..fd1b21f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ pub mod dev { pub use crate::config::AppConfig; pub use crate::service::HttpServiceFactory; - pub use actix_http::body::{Body, MessageBody, ResponseBody}; + pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; pub use actix_http::dev::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ Extensions, Payload, PayloadStream, RequestHead, ResponseHead, diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 137913d2..f4def58d 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -1,12 +1,12 @@ //! Middleware for setting default response headers use std::rc::Rc; -use actix_http::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; -use actix_http::http::{HeaderMap, HttpTryFrom}; use actix_service::{Service, Transform}; use futures::future::{ok, FutureResult}; use futures::{Future, Poll}; +use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; +use crate::http::{HeaderMap, HttpTryFrom}; use crate::service::{ServiceRequest, ServiceResponse}; /// `Middleware` for setting default response headers. @@ -147,10 +147,10 @@ where #[cfg(test)] mod tests { - use actix_http::http::header::CONTENT_TYPE; use actix_service::FnService; use super::*; + use crate::http::header::CONTENT_TYPE; use crate::test::{block_on, TestRequest}; use crate::{HttpResponse, ServiceRequest}; diff --git a/logger.rs b/src/middleware/logger.rs similarity index 52% rename from logger.rs rename to src/middleware/logger.rs index b7bb1bb8..769d8428 100644 --- a/logger.rs +++ b/src/middleware/logger.rs @@ -2,15 +2,19 @@ use std::collections::HashSet; use std::env; use std::fmt::{self, Display, Formatter}; +use std::rc::Rc; +use actix_service::{Service, Transform}; +use bytes::Bytes; +use futures::future::{ok, FutureResult}; +use futures::{Async, Future, Poll}; use regex::Regex; use time; -use error::Result; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Finished, Middleware, Started}; +use crate::dev::{BodyLength, MessageBody, ResponseBody}; +use crate::error::{Error, Result}; +use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{HttpMessage, HttpResponse}; /// `Middleware` for logging request and response info to the terminal. /// @@ -69,7 +73,9 @@ use middleware::{Finished, Middleware, Started}; /// /// `%{FOO}e` os.environ['FOO'] /// -pub struct Logger { +pub struct Logger(Rc); + +struct Inner { format: Format, exclude: HashSet, } @@ -77,15 +83,18 @@ pub struct Logger { impl Logger { /// Create `Logger` middleware with the specified `format`. pub fn new(format: &str) -> Logger { - Logger { + Logger(Rc::new(Inner { format: Format::new(format), exclude: HashSet::new(), - } + })) } /// Ignore and do not log access info for specified path. pub fn exclude>(mut self, path: T) -> Self { - self.exclude.insert(path.into()); + Rc::get_mut(&mut self.0) + .unwrap() + .exclude + .insert(path.into()); self } } @@ -97,40 +106,147 @@ impl Default for Logger { /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` fn default() -> Logger { - Logger { + Logger(Rc::new(Inner { format: Format::default(), exclude: HashSet::new(), + })) + } +} + +impl Transform> for Logger +where + S: Service, Response = ServiceResponse>, + B: MessageBody, +{ + type Response = ServiceResponse>; + type Error = S::Error; + type InitError = (); + type Transform = LoggerMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(LoggerMiddleware { + service, + inner: self.0.clone(), + }) + } +} + +/// Logger middleware +pub struct LoggerMiddleware { + inner: Rc, + service: S, +} + +impl Service> for LoggerMiddleware +where + S: Service, Response = ServiceResponse>, + B: MessageBody, +{ + type Response = ServiceResponse>; + type Error = S::Error; + type Future = LoggerResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { + if self.inner.exclude.contains(req.path()) { + LoggerResponse { + fut: self.service.call(req), + format: None, + time: time::now(), + } + } else { + let now = time::now(); + let mut format = self.inner.format.clone(); + + for unit in &mut format.0 { + unit.render_request(now, &req); + } + LoggerResponse { + fut: self.service.call(req), + format: Some(format), + time: now, + } } } } -struct StartTime(time::Tm); +#[doc(hidden)] +pub struct LoggerResponse +where + B: MessageBody, + S: Service, Response = ServiceResponse>, +{ + fut: S::Future, + time: time::Tm, + format: Option, +} -impl Logger { - fn log(&self, req: &HttpRequest, resp: &HttpResponse) { - if let Some(entry_time) = req.extensions().get::() { +impl Future for LoggerResponse +where + B: MessageBody, + S: Service, Response = ServiceResponse>, +{ + type Item = ServiceResponse>; + type Error = S::Error; + + fn poll(&mut self) -> Poll { + let res = futures::try_ready!(self.fut.poll()); + + if let Some(ref mut format) = self.format { + for unit in &mut format.0 { + unit.render_response(&res); + } + } + + Ok(Async::Ready(res.map_body(move |_, body| { + ResponseBody::Body(StreamLog { + body, + size: 0, + time: self.time, + format: self.format.take(), + }) + }))) + } +} + +pub struct StreamLog { + body: ResponseBody, + format: Option, + size: usize, + time: time::Tm, +} + +impl Drop for StreamLog { + fn drop(&mut self) { + if let Some(ref format) = self.format { let render = |fmt: &mut Formatter| { - for unit in &self.format.0 { - unit.render(fmt, req, resp, entry_time.0)?; + for unit in &format.0 { + unit.render(fmt, self.size, self.time)?; } Ok(()) }; - info!("{}", FormatDisplay(&render)); + log::info!("{}", FormatDisplay(&render)); } } } -impl Middleware for Logger { - fn start(&self, req: &HttpRequest) -> Result { - if !self.exclude.contains(req.path()) { - req.extensions_mut().insert(StartTime(time::now())); - } - Ok(Started::Done) +impl MessageBody for StreamLog { + fn length(&self) -> BodyLength { + self.body.length() } - fn finish(&self, req: &HttpRequest, resp: &HttpResponse) -> Finished { - self.log(req, resp); - Finished::Done + fn poll_next(&mut self) -> Poll, Error> { + match self.body.poll_next()? { + Async::Ready(Some(chunk)) => { + self.size += chunk.len(); + Ok(Async::Ready(Some(chunk))) + } + val => Ok(val), + } } } @@ -152,7 +268,7 @@ impl Format { /// /// Returns `None` if the format string syntax is incorrect. pub fn new(s: &str) -> Format { - trace!("Access log format: {}", s); + log::trace!("Access log format: {}", s); let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap(); let mut idx = 0; @@ -215,33 +331,16 @@ pub enum FormatText { } impl FormatText { - fn render( - &self, fmt: &mut Formatter, req: &HttpRequest, resp: &HttpResponse, + fn render( + &self, + fmt: &mut Formatter, + size: usize, entry_time: time::Tm, ) -> Result<(), fmt::Error> { match *self { FormatText::Str(ref string) => fmt.write_str(string), FormatText::Percent => "%".fmt(fmt), - FormatText::RequestLine => { - if req.query_string().is_empty() { - fmt.write_fmt(format_args!( - "{} {} {:?}", - req.method(), - req.path(), - req.version() - )) - } else { - fmt.write_fmt(format_args!( - "{} {}?{} {:?}", - req.method(), - req.path(), - req.query_string(), - req.version() - )) - } - } - FormatText::ResponseStatus => resp.status().as_u16().fmt(fmt), - FormatText::ResponseSize => resp.response_size().fmt(fmt), + FormatText::ResponseSize => size.fmt(fmt), FormatText::Time => { let rt = time::now() - entry_time; let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0; @@ -252,17 +351,71 @@ impl FormatText { let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0; fmt.write_fmt(format_args!("{:.6}", rt)) } - FormatText::RemoteAddr => { - if let Some(remote) = req.connection_info().remote() { - return remote.fmt(fmt); + // FormatText::RemoteAddr => { + // if let Some(remote) = req.connection_info().remote() { + // return remote.fmt(fmt); + // } else { + // "-".fmt(fmt) + // } + // } + FormatText::EnvironHeader(ref name) => { + if let Ok(val) = env::var(name) { + fmt.write_fmt(format_args!("{}", val)) } else { "-".fmt(fmt) } } - FormatText::RequestTime => entry_time - .strftime("[%d/%b/%Y:%H:%M:%S %z]") - .unwrap() - .fmt(fmt), + _ => Ok(()), + } + } + + fn render_response(&mut self, res: &HttpResponse) { + match *self { + FormatText::ResponseStatus => { + *self = FormatText::Str(format!("{}", res.status().as_u16())) + } + FormatText::ResponseHeader(ref name) => { + let s = if let Some(val) = res.headers().get(name) { + if let Ok(s) = val.to_str() { + s + } else { + "-" + } + } else { + "-" + }; + *self = FormatText::Str(s.to_string()) + } + _ => (), + } + } + + fn render_request

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

    ) { + match *self { + FormatText::RequestLine => { + *self = if req.query_string().is_empty() { + FormatText::Str(format!( + "{} {} {:?}", + req.method(), + req.path(), + req.version() + )) + } else { + FormatText::Str(format!( + "{} {}?{} {:?}", + req.method(), + req.path(), + req.query_string(), + req.version() + )) + }; + } + FormatText::RequestTime => { + *self = FormatText::Str(format!( + "{:?}", + now.strftime("[%d/%b/%Y:%H:%M:%S %z]").unwrap() + )) + } FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { if let Ok(s) = val.to_str() { @@ -273,27 +426,9 @@ impl FormatText { } else { "-" }; - fmt.write_fmt(format_args!("{}", s)) - } - FormatText::ResponseHeader(ref name) => { - let s = if let Some(val) = resp.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - fmt.write_fmt(format_args!("{}", s)) - } - FormatText::EnvironHeader(ref name) => { - if let Ok(val) = env::var(name) { - fmt.write_fmt(format_args!("{}", val)) - } else { - "-".fmt(fmt) - } + *self = FormatText::Str(s.to_string()); } + _ => (), } } } @@ -308,77 +443,67 @@ impl<'a> fmt::Display for FormatDisplay<'a> { #[cfg(test)] mod tests { - use time; + use actix_service::{FnService, Service, Transform}; use super::*; - use http::{header, StatusCode}; - use test::TestRequest; + use crate::http::{header, StatusCode}; + use crate::test::{block_on, TestRequest}; #[test] fn test_logger() { + let srv = FnService::new(|req: ServiceRequest<_>| { + req.into_response( + HttpResponse::build(StatusCode::OK) + .header("X-Test", "ttt") + .finish(), + ) + }); let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ).finish(); - let resp = HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") - .force_close() - .finish(); - - match logger.start(&req) { - Ok(Started::Done) => (), - _ => panic!(), - }; - match logger.finish(&req, &resp) { - Finished::Done => (), - _ => panic!(), - } - let entry_time = time::now(); - let render = |fmt: &mut Formatter| { - for unit in &logger.format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("ACTIX-WEB ttt")); - } - - #[test] - fn test_default_format() { - let format = Format::default(); + let mut srv = block_on(logger.new_transform(srv)).unwrap(); let req = TestRequest::with_header( header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"), - ).finish(); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - let entry_time = time::now(); - - let render = |fmt: &mut Formatter| { - for unit in &format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET / HTTP/1.1")); - assert!(s.contains("200 0")); - assert!(s.contains("ACTIX-WEB")); - - let req = TestRequest::with_uri("/?test").finish(); - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - let entry_time = time::now(); - - let render = |fmt: &mut Formatter| { - for unit in &format.0 { - unit.render(fmt, &req, &resp, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET /?test HTTP/1.1")); + ) + .to_service(); + let _res = block_on(srv.call(req)); } + + // #[test] + // fn test_default_format() { + // let format = Format::default(); + + // let req = TestRequest::with_header( + // header::USER_AGENT, + // header::HeaderValue::from_static("ACTIX-WEB"), + // ) + // .finish(); + // let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + // let entry_time = time::now(); + + // let render = |fmt: &mut Formatter| { + // for unit in &format.0 { + // unit.render(fmt, &req, &resp, entry_time)?; + // } + // Ok(()) + // }; + // let s = format!("{}", FormatDisplay(&render)); + // assert!(s.contains("GET / HTTP/1.1")); + // assert!(s.contains("200 0")); + // assert!(s.contains("ACTIX-WEB")); + + // let req = TestRequest::with_uri("/?test").finish(); + // let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + // let entry_time = time::now(); + + // let render = |fmt: &mut Formatter| { + // for unit in &format.0 { + // unit.render(fmt, &req, &resp, entry_time)?; + // } + // Ok(()) + // }; + // let s = format!("{}", FormatDisplay(&render)); + // assert!(s.contains("GET /?test HTTP/1.1")); + // } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 8c4cd754..d63ca893 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -8,3 +8,6 @@ pub use self::defaultheaders::DefaultHeaders; #[cfg(feature = "session")] pub use actix_session as session; + +mod logger; +pub use self::logger::Logger; diff --git a/src/resource.rs b/src/resource.rs index b5cf640c..ddcbbcd3 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -286,7 +286,7 @@ where let rdef = if config.is_root() { ResourceDef::new(&insert_slash(&self.rdef)) } else { - ResourceDef::new(&insert_slash(&self.rdef)) + ResourceDef::new(&self.rdef) }; config.register_service(rdef, guards, self) } diff --git a/src/route.rs b/src/route.rs index bac897ab..b611164e 100644 --- a/src/route.rs +++ b/src/route.rs @@ -50,9 +50,8 @@ impl Route

    { let config_ref = Rc::new(RefCell::new(None)); Route { service: Box::new(RouteNewService::new( - Extract::new(config_ref.clone()).and_then( - Handle::new(|| HttpResponse::NotFound()).map_err(|_| panic!()), - ), + Extract::new(config_ref.clone()) + .and_then(Handle::new(HttpResponse::NotFound).map_err(|_| panic!())), )), guards: Rc::new(Vec::new()), config: ConfigStorage::default(), diff --git a/src/scope.rs b/src/scope.rs index 9bc0b50d..29f44fc4 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -10,7 +10,7 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, Poll}; -use crate::dev::{insert_slash, AppConfig, HttpServiceFactory}; +use crate::dev::{AppConfig, HttpServiceFactory}; use crate::guard::Guard; use crate::resource::Resource; use crate::route::Route; @@ -263,9 +263,9 @@ where Some(self.guards) }; let rdef = if config.is_root() { - ResourceDef::prefix(&insert_slash(&self.rdef)) + ResourceDef::root_prefix(&self.rdef) } else { - ResourceDef::prefix(&insert_slash(&self.rdef)) + ResourceDef::prefix(&self.rdef) }; config.register_service(rdef, guards, self.endpoint) } From 60c048c8cd1e47e25aeb8973a941a58e9d8ee0e2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 19:27:18 -0800 Subject: [PATCH 1001/1635] fix nested resources --- src/middleware/logger.rs | 5 +---- src/resource.rs | 2 +- src/scope.rs | 11 +++++------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 769d8428..d8b4e643 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -32,8 +32,6 @@ use crate::{HttpMessage, HttpResponse}; /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` /// ```rust -/// # extern crate actix_web; -/// extern crate env_logger; /// use actix_web::middleware::Logger; /// use actix_web::App; /// @@ -43,8 +41,7 @@ use crate::{HttpMessage, HttpResponse}; /// /// let app = App::new() /// .middleware(Logger::default()) -/// .middleware(Logger::new("%a %{User-Agent}i")) -/// .finish(); +/// .middleware(Logger::new("%a %{User-Agent}i")); /// } /// ``` /// diff --git a/src/resource.rs b/src/resource.rs index ddcbbcd3..157b181e 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -283,7 +283,7 @@ where } else { Some(std::mem::replace(&mut self.guards, Vec::new())) }; - let rdef = if config.is_root() { + let rdef = if config.is_root() || !self.rdef.is_empty() { ResourceDef::new(&insert_slash(&self.rdef)) } else { ResourceDef::new(&self.rdef) diff --git a/src/scope.rs b/src/scope.rs index 29f44fc4..5580b15e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -262,12 +262,11 @@ where } else { Some(self.guards) }; - let rdef = if config.is_root() { - ResourceDef::root_prefix(&self.rdef) - } else { - ResourceDef::prefix(&self.rdef) - }; - config.register_service(rdef, guards, self.endpoint) + config.register_service( + ResourceDef::root_prefix(&self.rdef), + guards, + self.endpoint, + ) } } From e25483a0d58ebf6b688fb195d94471223a082cc1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 21:12:35 -0800 Subject: [PATCH 1002/1635] fix warnings --- src/error.rs | 12 +----------- src/header/common/content_disposition.rs | 4 ++-- src/header/shared/quality_item.rs | 2 +- src/request.rs | 2 +- src/test.rs | 2 -- src/ws/client/mod.rs | 14 -------------- 6 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/error.rs b/src/error.rs index 4762f27f..a3037b98 100644 --- a/src/error.rs +++ b/src/error.rs @@ -970,23 +970,13 @@ where #[cfg(feature = "fail")] mod failure_integration { use super::*; - use failure::{self, Fail}; /// Compatibility for `failure::Error` - impl ResponseError for failure::Compat - where - T: fmt::Display + fmt::Debug + Sync + Send + 'static, - { + impl ResponseError for failure::Error { fn error_response(&self) -> Response { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } } - - impl From for Error { - fn from(err: failure::Error) -> Error { - err.compat().into() - } - } } #[cfg(test)] diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs index e04f9c89..700400da 100644 --- a/src/header/common/content_disposition.rs +++ b/src/header/common/content_disposition.rs @@ -27,7 +27,7 @@ fn split_once(haystack: &str, needle: char) -> (&str, &str) { /// first part and the left of the last part. fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { let (first, last) = split_once(haystack, needle); - (first.trim_right(), last.trim_left()) + (first.trim_end(), last.trim_start()) } /// The implied disposition of the content of the HTTP body. @@ -324,7 +324,7 @@ impl ContentDisposition { } } left = &left[end.ok_or(crate::error::ParseError::Header)? + 1..]; - left = split_once(left, ';').1.trim_left(); + left = split_once(left, ';').1.trim_start(); // In fact, it should not be Err if the above code is correct. String::from_utf8(quoted_string) .map_err(|_| crate::error::ParseError::Header)? diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs index 07c20658..fc3930c5 100644 --- a/src/header/shared/quality_item.rs +++ b/src/header/shared/quality_item.rs @@ -58,7 +58,7 @@ impl fmt::Display for QualityItem { match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), + x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')), } } } diff --git a/src/request.rs b/src/request.rs index 0ea251ea..a645c7ae 100644 --- a/src/request.rs +++ b/src/request.rs @@ -103,7 +103,7 @@ impl

    Request

    { } /// Mutable reference to the message's headers. - fn headers_mut(&mut self) -> &mut HeaderMap { + pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head_mut().headers } diff --git a/src/test.rs b/src/test.rs index 4b925d02..e2e0fd76 100644 --- a/src/test.rs +++ b/src/test.rs @@ -46,7 +46,6 @@ struct Inner { headers: HeaderMap, _cookies: Option>>, payload: Option, - prefix: u16, } impl Default for TestRequest { @@ -58,7 +57,6 @@ impl Default for TestRequest { headers: HeaderMap::new(), _cookies: None, payload: None, - prefix: 0, })) } } diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 12c7229b..8e59e846 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -25,20 +25,6 @@ impl Protocol { } } - fn is_http(self) -> bool { - match self { - Protocol::Https | Protocol::Http => true, - _ => false, - } - } - - fn is_secure(self) -> bool { - match self { - Protocol::Https | Protocol::Wss => true, - _ => false, - } - } - fn port(self) -> u16 { match self { Protocol::Http | Protocol::Ws => 80, From 3b069e0568cbd2ecc129afd0ace04a968cc4cb0c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 22:56:34 -0800 Subject: [PATCH 1003/1635] added combined http1/2 service --- Cargo.toml | 3 +- src/error.rs | 25 +-- src/h1/dispatcher.rs | 53 +++-- src/h1/mod.rs | 27 +-- src/h1/service.rs | 15 +- src/h2/dispatcher.rs | 28 ++- src/h2/mod.rs | 18 +- src/h2/service.rs | 72 +++---- src/lib.rs | 1 + src/service/mod.rs | 2 + src/service/service.rs | 446 +++++++++++++++++++++++++++++++++++++++++ test-server/src/lib.rs | 2 +- tests/test_server.rs | 52 ++++- 13 files changed, 575 insertions(+), 169 deletions(-) create mode 100644 src/service/service.rs diff --git a/Cargo.toml b/Cargo.toml index a0eb1731..9d55b85c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,8 @@ fail = ["failure"] [dependencies] #actix-service = "0.3.2" -actix-codec = "0.1.0" +actix-codec = "0.1.1" + #actix-connector = "0.3.0" #actix-utils = "0.3.1" diff --git a/src/error.rs b/src/error.rs index a3037b98..696162f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -328,12 +328,11 @@ impl ResponseError for cookie::ParseError { } } -#[derive(Debug, Display)] +#[derive(Debug, Display, From)] /// A set of errors that can occur during dispatching http requests -pub enum DispatchError { +pub enum DispatchError { /// Service error - #[display(fmt = "Service specific error: {:?}", _0)] - Service(E), + Service, /// An `io::Error` that occurred while trying to read or write to a network /// stream. @@ -373,24 +372,6 @@ pub enum DispatchError { Unknown, } -impl From for DispatchError { - fn from(err: ParseError) -> Self { - DispatchError::Parse(err) - } -} - -impl From for DispatchError { - fn from(err: io::Error) -> Self { - DispatchError::Io(err) - } -} - -impl From for DispatchError { - fn from(err: h2::Error) -> Self { - DispatchError::H2(err) - } -} - /// A set of error that can occure during parsing content type #[derive(PartialEq, Debug, Display)] pub enum ContentTypeError { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 7024ce3a..42ab33e7 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -20,7 +20,7 @@ use crate::response::Response; use super::codec::Codec; use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; -use super::{H1ServiceResult, Message, MessageType}; +use super::{Message, MessageType}; const MAX_PIPELINED_MESSAGES: usize = 16; @@ -50,7 +50,7 @@ where service: CloneableService, flags: Flags, framed: Framed, - error: Option>, + error: Option, config: ServiceConfig, state: State, @@ -93,12 +93,17 @@ where { /// Create http/1 dispatcher. pub fn new(stream: T, config: ServiceConfig, service: CloneableService) -> Self { - Dispatcher::with_timeout(stream, config, None, service) + Dispatcher::with_timeout( + Framed::new(stream, Codec::new(config.clone())), + config, + None, + service, + ) } /// Create http/1 dispatcher with slow request timeout. pub fn with_timeout( - stream: T, + framed: Framed, config: ServiceConfig, timeout: Option, service: CloneableService, @@ -109,7 +114,6 @@ where } else { Flags::empty() }; - let framed = Framed::new(stream, Codec::new(config.clone())); // keep-alive timer let (ka_expire, ka_timer) = if let Some(delay) = timeout { @@ -167,7 +171,7 @@ where } /// Flush stream - fn poll_flush(&mut self) -> Poll> { + fn poll_flush(&mut self) -> Poll { if !self.framed.is_write_buf_empty() { match self.framed.poll_complete() { Ok(Async::NotReady) => Ok(Async::NotReady), @@ -192,7 +196,7 @@ where &mut self, message: Response<()>, body: ResponseBody, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { self.framed .force_send(Message::Item((message, body.length()))) .map_err(|err| { @@ -210,7 +214,7 @@ where } } - fn poll_response(&mut self) -> Result<(), DispatchError> { + fn poll_response(&mut self) -> Result<(), DispatchError> { let mut retry = self.can_read(); loop { let state = match mem::replace(&mut self.state, State::None) { @@ -225,7 +229,7 @@ where None => None, }, State::ServiceCall(mut fut) => { - match fut.poll().map_err(DispatchError::Service)? { + match fut.poll().map_err(|_| DispatchError::Service)? { Async::Ready(res) => { let (res, body) = res.into().replace_body(()); Some(self.send_response(res, body)?) @@ -283,12 +287,9 @@ where Ok(()) } - fn handle_request( - &mut self, - req: Request, - ) -> Result, DispatchError> { + fn handle_request(&mut self, req: Request) -> Result, DispatchError> { let mut task = self.service.call(req); - match task.poll().map_err(DispatchError::Service)? { + match task.poll().map_err(|_| DispatchError::Service)? { Async::Ready(res) => { let (res, body) = res.into().replace_body(()); self.send_response(res, body) @@ -298,7 +299,7 @@ where } /// Process one incoming requests - pub(self) fn poll_request(&mut self) -> Result> { + pub(self) fn poll_request(&mut self) -> Result { // limit a mount of non processed requests if self.messages.len() >= MAX_PIPELINED_MESSAGES { return Ok(false); @@ -400,7 +401,7 @@ where } /// keep-alive timer - fn poll_keepalive(&mut self) -> Result<(), DispatchError> { + fn poll_keepalive(&mut self) -> Result<(), DispatchError> { if self.ka_timer.is_none() { return Ok(()); } @@ -469,8 +470,8 @@ where S::Response: Into>, B: MessageBody, { - type Item = H1ServiceResult; - type Error = DispatchError; + type Item = (); + type Error = DispatchError; #[inline] fn poll(&mut self) -> Poll { @@ -490,7 +491,7 @@ where } if inner.flags.contains(Flags::DISCONNECTED) { - return Ok(Async::Ready(H1ServiceResult::Disconnected)); + return Ok(Async::Ready(())); } // keep-alive and stream errors @@ -523,14 +524,12 @@ where }; let mut inner = self.inner.take().unwrap(); - if shutdown { - Ok(Async::Ready(H1ServiceResult::Shutdown( - inner.framed.into_inner(), - ))) - } else { - let req = inner.unhandled.take().unwrap(); - Ok(Async::Ready(H1ServiceResult::Unhandled(req, inner.framed))) - } + + // TODO: shutdown + Ok(Async::Ready(())) + //Ok(Async::Ready(HttpServiceResult::Shutdown( + // inner.framed.into_inner(), + //))) } } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 9054c266..e3d63c52 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -1,7 +1,4 @@ //! HTTP/1 implementation -use std::fmt; - -use actix_codec::Framed; use bytes::Bytes; mod client; @@ -18,29 +15,6 @@ pub use self::dispatcher::Dispatcher; pub use self::payload::{Payload, PayloadBuffer}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; -use crate::request::Request; - -/// H1 service response type -pub enum H1ServiceResult { - Disconnected, - Shutdown(T), - Unhandled(Request, Framed), -} - -impl fmt::Debug for H1ServiceResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - H1ServiceResult::Disconnected => write!(f, "H1ServiceResult::Disconnected"), - H1ServiceResult::Shutdown(ref v) => { - write!(f, "H1ServiceResult::Shutdown({:?})", v) - } - H1ServiceResult::Unhandled(ref req, _) => { - write!(f, "H1ServiceResult::Unhandled({:?})", req) - } - } - } -} - #[derive(Debug)] /// Codec message pub enum Message { @@ -67,6 +41,7 @@ pub enum MessageType { #[cfg(test)] mod tests { use super::*; + use crate::request::Request; impl Message { pub fn message(self) -> Request { diff --git a/src/h1/service.rs b/src/h1/service.rs index 1c4f1ae3..229229e6 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -17,7 +17,7 @@ use crate::response::Response; use super::codec::Codec; use super::dispatcher::Dispatcher; -use super::{H1ServiceResult, Message}; +use super::Message; /// `NewService` implementation for HTTP1 transport pub struct H1Service { @@ -72,8 +72,8 @@ where S::Service: 'static, B: MessageBody, { - type Response = H1ServiceResult; - type Error = DispatchError; + type Response = (); + type Error = DispatchError; type InitError = S::InitError; type Service = H1ServiceHandler; type Future = H1ServiceResponse; @@ -275,12 +275,15 @@ where S::Response: Into>, B: MessageBody, { - type Response = H1ServiceResult; - type Error = DispatchError; + type Response = (); + type Error = DispatchError; type Future = Dispatcher; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(DispatchError::Service) + self.srv.poll_ready().map_err(|e| { + log::error!("Http service readiness error: {:?}", e); + DispatchError::Service + }) } fn call(&mut self, req: T) -> Self::Future { diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 7f5409c6..be813bd5 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -26,8 +26,6 @@ use crate::payload::Payload; use crate::request::Request; use crate::response::Response; -use super::H2ServiceResult; - const CHUNK_SIZE: usize = 16_384; bitflags! { @@ -40,7 +38,7 @@ bitflags! { /// Dispatcher for HTTP/2 protocol pub struct Dispatcher< T: AsyncRead + AsyncWrite, - S: Service> + 'static, + S: Service + 'static, B: MessageBody, > { flags: Flags, @@ -55,8 +53,8 @@ pub struct Dispatcher< impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + fmt::Debug, + S: Service + 'static, + S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -97,13 +95,13 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + fmt::Debug, + S: Service + 'static, + S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, { type Item = (); - type Error = DispatchError<()>; + type Error = DispatchError; #[inline] fn poll(&mut self) -> Poll { @@ -143,21 +141,21 @@ where } } -struct ServiceResponse>, B> { +struct ServiceResponse, B> { state: ServiceResponseState, config: ServiceConfig, buffer: Option, } -enum ServiceResponseState>, B> { +enum ServiceResponseState, B> { ServiceCall(S::Future, Option>), SendPayload(SendStream, ResponseBody), } impl ServiceResponse where - S: Service> + 'static, - S::Error: Into + fmt::Debug, + S: Service + 'static, + S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -224,8 +222,8 @@ where impl Future for ServiceResponse where - S: Service> + 'static, - S::Error: Into + fmt::Debug, + S: Service + 'static, + S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -258,7 +256,7 @@ where } Ok(Async::NotReady) => Ok(Async::NotReady), Err(e) => { - let res: Response = e.into().into(); + let res: Response = Response::InternalServerError().finish(); let (res, body) = res.replace_body(()); let mut send = send.take().unwrap(); diff --git a/src/h2/mod.rs b/src/h2/mod.rs index 55e05760..c5972123 100644 --- a/src/h2/mod.rs +++ b/src/h2/mod.rs @@ -9,26 +9,10 @@ use h2::RecvStream; mod dispatcher; mod service; +pub use self::dispatcher::Dispatcher; pub use self::service::H2Service; use crate::error::PayloadError; -/// H1 service response type -pub enum H2ServiceResult { - Disconnected, - Shutdown(T), -} - -impl fmt::Debug for H2ServiceResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - H2ServiceResult::Disconnected => write!(f, "H2ServiceResult::Disconnected"), - H2ServiceResult::Shutdown(ref v) => { - write!(f, "H2ServiceResult::Shutdown({:?})", v) - } - } - } -} - /// H2 receive stream pub struct Payload { pl: RecvStream, diff --git a/src/h2/service.rs b/src/h2/service.rs index e225e9fc..a47f507b 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -20,7 +20,6 @@ use crate::request::Request; use crate::response::Response; use super::dispatcher::Dispatcher; -use super::H2ServiceResult; /// `NewService` implementation for HTTP2 transport pub struct H2Service { @@ -31,14 +30,14 @@ pub struct H2Service { impl H2Service where - S: NewService>, + S: NewService, S::Service: 'static, - S::Error: Into + Debug + 'static, + S::Error: Debug + 'static, S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H2Service { @@ -57,14 +56,14 @@ where impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService, S::Service: 'static, - S::Error: Into + Debug, + S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { type Response = (); - type Error = DispatchError<()>; + type Error = DispatchError; type InitError = S::InitError; type Service = H2ServiceHandler; type Future = H2ServiceResponse; @@ -94,9 +93,9 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where - S: NewService>, + S: NewService, S::Service: 'static, - S::Error: Into + Debug + 'static, + S::Error: Debug + 'static, { /// Create instance of `H2ServiceBuilder` pub fn new() -> H2ServiceBuilder { @@ -189,30 +188,11 @@ where self } - // #[cfg(feature = "ssl")] - // /// Configure alpn protocols for SslAcceptorBuilder. - // pub fn configure_openssl( - // builder: &mut openssl::ssl::SslAcceptorBuilder, - // ) -> io::Result<()> { - // let protos: &[u8] = b"\x02h2"; - // builder.set_alpn_select_callback(|_, protos| { - // const H2: &[u8] = b"\x02h2"; - // if protos.windows(3).any(|window| window == H2) { - // Ok(b"h2") - // } else { - // Err(openssl::ssl::AlpnError::NOACK) - // } - // }); - // builder.set_alpn_protos(&protos)?; - - // Ok(()) - // } - /// Finish service configuration and create `H1Service` instance. pub fn finish(self, service: F) -> H2Service where B: MessageBody, - F: IntoNewService>, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -228,7 +208,7 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse>, B> { +pub struct H2ServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -237,10 +217,10 @@ pub struct H2ServiceResponse>, B> { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService>, + S: NewService, S::Service: 'static, S::Response: Into>, - S::Error: Into + Debug, + S::Error: Debug, B: MessageBody + 'static, { type Item = H2ServiceHandler; @@ -264,8 +244,8 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service> + 'static, - S::Error: Into + Debug, + S: Service + 'static, + S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -281,19 +261,19 @@ where impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + Debug, + S: Service + 'static, + S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { type Response = (); - type Error = DispatchError<()>; + type Error = DispatchError; type Future = H2ServiceHandlerResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.srv.poll_ready().map_err(|e| { error!("Service readiness error: {:?}", e); - DispatchError::Service(()) + DispatchError::Service }) } @@ -308,11 +288,7 @@ where } } -enum State< - T: AsyncRead + AsyncWrite, - S: Service> + 'static, - B: MessageBody, -> { +enum State + 'static, B: MessageBody> { Incoming(Dispatcher), Handshake( Option>, @@ -324,8 +300,8 @@ enum State< pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + Debug, + S: Service + 'static, + S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { @@ -335,13 +311,13 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service> + 'static, - S::Error: Into + Debug, + S: Service + 'static, + S::Error: Debug, S::Response: Into>, B: MessageBody, { type Item = (); - type Error = DispatchError<()>; + type Error = DispatchError; fn poll(&mut self) -> Poll { match self.state { diff --git a/src/lib.rs b/src/lib.rs index 8750b24c..74a46fd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,7 @@ pub use self::message::{Head, Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::Response; +pub use self::service::HttpService; pub use self::service::{SendError, SendResponse}; pub mod dev { diff --git a/src/service/mod.rs b/src/service/mod.rs index 83a40bd1..25e95bf6 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,3 +1,5 @@ mod senderror; +mod service; pub use self::senderror::{SendError, SendResponse}; +pub use self::service::HttpService; diff --git a/src/service/service.rs b/src/service/service.rs new file mode 100644 index 00000000..5f6ee819 --- /dev/null +++ b/src/service/service.rs @@ -0,0 +1,446 @@ +use std::fmt::Debug; +use std::marker::PhantomData; +use std::{fmt, io, net}; + +use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; +use actix_service::{IntoNewService, NewService, Service}; +use actix_utils::cloneable::CloneableService; +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use futures::{try_ready, Async, Future, IntoFuture, Poll}; +use h2::server::{self, Handshake}; +use log::error; + +use crate::body::MessageBody; +use crate::config::{KeepAlive, ServiceConfig}; +use crate::error::DispatchError; +use crate::request::Request; +use crate::response::Response; + +use crate::{h1, h2::Dispatcher}; + +/// `NewService` HTTP1.1/HTTP2 transport implementation +pub struct HttpService { + srv: S, + cfg: ServiceConfig, + _t: PhantomData<(T, B)>, +} + +impl HttpService +where + S: NewService, + S::Service: 'static, + S::Error: Debug + 'static, + S::Response: Into>, + B: MessageBody + 'static, +{ + /// Create new `HttpService` instance. + pub fn new>(service: F) -> Self { + let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); + + HttpService { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + + /// Create builder for `HttpService` instance. + pub fn build() -> HttpServiceBuilder { + HttpServiceBuilder::new() + } +} + +impl NewService for HttpService +where + T: AsyncRead + AsyncWrite + 'static, + S: NewService, + S::Service: 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody + 'static, +{ + type Response = (); + type Error = DispatchError; + type InitError = S::InitError; + type Service = HttpServiceHandler; + type Future = HttpServiceResponse; + + fn new_service(&self, _: &()) -> Self::Future { + HttpServiceResponse { + fut: self.srv.new_service(&()).into_future(), + cfg: Some(self.cfg.clone()), + _t: PhantomData, + } + } +} + +/// A http service factory builder +/// +/// This type can be used to construct an instance of `ServiceConfig` through a +/// builder-like pattern. +pub struct HttpServiceBuilder { + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, + host: String, + addr: net::SocketAddr, + secure: bool, + _t: PhantomData<(T, S)>, +} + +impl HttpServiceBuilder +where + S: NewService, + S::Service: 'static, + S::Error: Debug + 'static, +{ + /// Create instance of `HttpServiceBuilder` type + pub fn new() -> HttpServiceBuilder { + HttpServiceBuilder { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_disconnect: 0, + secure: false, + host: "localhost".to_owned(), + addr: "127.0.0.1:8080".parse().unwrap(), + _t: PhantomData, + } + } + + /// Enable secure flag for current server. + /// This flags also enables `client disconnect timeout`. + /// + /// By default this flag is set to false. + pub fn secure(mut self) -> Self { + self.secure = true; + if self.client_disconnect == 0 { + self.client_disconnect = 3000; + } + self + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: U) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; + 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. + /// + /// By default host name is set to a "localhost" value. + pub fn server_hostname(mut self, val: &str) -> Self { + self.host = val.to_owned(); + self + } + + /// Set server ip address. + /// + /// 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. + /// + /// By default server address is set to a "127.0.0.1:8080" + pub fn server_address(mut self, addr: U) -> Self { + match addr.to_socket_addrs() { + Err(err) => error!("Can not convert to SocketAddr: {}", err), + Ok(mut addrs) => { + if let Some(addr) = addrs.next() { + self.addr = addr; + } + } + } + self + } + + // #[cfg(feature = "ssl")] + // /// Configure alpn protocols for SslAcceptorBuilder. + // pub fn configure_openssl( + // builder: &mut openssl::ssl::SslAcceptorBuilder, + // ) -> io::Result<()> { + // let protos: &[u8] = b"\x02h2"; + // builder.set_alpn_select_callback(|_, protos| { + // const H2: &[u8] = b"\x02h2"; + // if protos.windows(3).any(|window| window == H2) { + // Ok(b"h2") + // } else { + // Err(openssl::ssl::AlpnError::NOACK) + // } + // }); + // builder.set_alpn_protos(&protos)?; + + // Ok(()) + // } + + /// Finish service configuration and create `HttpService` instance. + pub fn finish(self, service: F) -> HttpService + where + B: MessageBody, + F: IntoNewService, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + HttpService { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } +} + +#[doc(hidden)] +pub struct HttpServiceResponse, B> { + fut: ::Future, + cfg: Option, + _t: PhantomData<(T, B)>, +} + +impl Future for HttpServiceResponse +where + T: AsyncRead + AsyncWrite, + S: NewService, + S::Service: 'static, + S::Response: Into>, + S::Error: Debug, + B: MessageBody + 'static, +{ + type Item = HttpServiceHandler; + type Error = S::InitError; + + fn poll(&mut self) -> Poll { + let service = try_ready!(self.fut.poll()); + Ok(Async::Ready(HttpServiceHandler::new( + self.cfg.take().unwrap(), + service, + ))) + } +} + +/// `Service` implementation for http transport +pub struct HttpServiceHandler { + srv: CloneableService, + cfg: ServiceConfig, + _t: PhantomData<(T, B)>, +} + +impl HttpServiceHandler +where + S: Service + 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody + 'static, +{ + fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler { + HttpServiceHandler { + cfg, + srv: CloneableService::new(srv), + _t: PhantomData, + } + } +} + +impl Service for HttpServiceHandler +where + T: AsyncRead + AsyncWrite + 'static, + S: Service + 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody + 'static, +{ + type Response = (); + type Error = DispatchError; + type Future = HttpServiceHandlerResponse; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.srv.poll_ready().map_err(|e| { + error!("Service readiness error: {:?}", e); + DispatchError::Service + }) + } + + fn call(&mut self, req: T) -> Self::Future { + HttpServiceHandlerResponse { + state: State::Unknown(Some(( + req, + BytesMut::with_capacity(14), + self.cfg.clone(), + self.srv.clone(), + ))), + } + } +} + +enum State + 'static, B: MessageBody> +where + S::Error: fmt::Debug, + T: AsyncRead + AsyncWrite + 'static, +{ + H1(h1::Dispatcher), + H2(Dispatcher, S, B>), + Unknown(Option<(T, BytesMut, ServiceConfig, CloneableService)>), + Handshake(Option<(Handshake, Bytes>, ServiceConfig, CloneableService)>), +} + +pub struct HttpServiceHandlerResponse +where + T: AsyncRead + AsyncWrite + 'static, + S: Service + 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody + 'static, +{ + state: State, +} + +const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; + +impl Future for HttpServiceHandlerResponse +where + T: AsyncRead + AsyncWrite, + S: Service + 'static, + S::Error: Debug, + S::Response: Into>, + B: MessageBody, +{ + type Item = (); + type Error = DispatchError; + + fn poll(&mut self) -> Poll { + match self.state { + State::H1(ref mut disp) => disp.poll(), + State::H2(ref mut disp) => disp.poll(), + State::Unknown(ref mut data) => { + if let Some(ref mut item) = data { + loop { + unsafe { + let b = item.1.bytes_mut(); + let n = { try_ready!(item.0.poll_read(b)) }; + item.1.advance_mut(n); + if item.1.len() >= HTTP2_PREFACE.len() { + break; + } + } + } + } else { + panic!() + } + let (io, buf, cfg, srv) = data.take().unwrap(); + if buf[..14] == HTTP2_PREFACE[..] { + let io = Io { + inner: io, + unread: Some(buf), + }; + self.state = + State::Handshake(Some((server::handshake(io), cfg, srv))); + } else { + let framed = Framed::from_parts(FramedParts::with_read_buf( + io, + h1::Codec::new(cfg.clone()), + buf, + )); + self.state = + State::H1(h1::Dispatcher::with_timeout(framed, cfg, None, srv)) + } + self.poll() + } + State::Handshake(ref mut data) => { + let conn = if let Some(ref mut item) = data { + match item.0.poll() { + Ok(Async::Ready(conn)) => conn, + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(err) => { + trace!("H2 handshake error: {}", err); + return Err(err.into()); + } + } + } else { + panic!() + }; + let (_, cfg, srv) = data.take().unwrap(); + self.state = State::H2(Dispatcher::new(srv, conn, cfg, None)); + self.poll() + } + } + } +} + +/// Wrapper for `AsyncRead + AsyncWrite` types +struct Io { + unread: Option, + inner: T, +} + +impl io::Read for Io { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if let Some(mut bytes) = self.unread.take() { + let size = std::cmp::min(buf.len(), bytes.len()); + buf[..size].copy_from_slice(&bytes[..size]); + if bytes.len() > size { + bytes.split_to(size); + self.unread = Some(bytes); + } + Ok(size) + } else { + self.inner.read(buf) + } + } +} + +impl io::Write for Io { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl AsyncRead for Io { + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.inner.prepare_uninitialized_buffer(buf) + } +} + +impl AsyncWrite for Io { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.inner.shutdown() + } + fn write_buf(&mut self, buf: &mut B) -> Poll { + self.inner.write_buf(buf) + } +} diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 3afee682..695a477d 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -73,7 +73,7 @@ impl TestServer { .start(); tx.send((System::current(), local_addr)).unwrap(); - sys.run(); + sys.run() }); let (system, addr) = rx.recv().unwrap(); diff --git a/tests/test_server.rs b/tests/test_server.rs index fd848b82..4cffdd09 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -10,8 +10,8 @@ use futures::stream::once; use actix_http::body::Body; use actix_http::{ - body, client, h1, h2, http, Error, HttpMessage as HttpMessage2, KeepAlive, Request, - Response, + body, client, h1, h2, http, Error, HttpMessage as HttpMessage2, HttpService, + KeepAlive, Request, Response, }; #[test] @@ -31,6 +31,26 @@ fn test_h1() { assert!(response.status().is_success()); } +#[test] +fn test_h1_2() { + let mut srv = TestServer::new(|| { + HttpService::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .server_hostname("localhost") + .finish(|req: Request| { + assert_eq!(req.version(), http::Version::HTTP_11); + future::ok::<_, ()>(Response::Ok().finish()) + }) + .map(|_| ()) + }); + + let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); +} + #[cfg(feature = "ssl")] fn ssl_acceptor() -> std::io::Result> { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; @@ -71,7 +91,30 @@ fn test_h2() -> std::io::Result<()> { let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); let response = srv.send_request(req).unwrap(); - println!("RES: {:?}", response); + assert!(response.status().is_success()); + Ok(()) +} + +#[cfg(feature = "ssl")] +#[test] +fn test_h2_1() -> std::io::Result<()> { + let openssl = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert_eq!(req.version(), http::Version::HTTP_2); + future::ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -79,9 +122,6 @@ fn test_h2() -> std::io::Result<()> { #[cfg(feature = "ssl")] #[test] fn test_h2_body() -> std::io::Result<()> { - // std::env::set_var("RUST_LOG", "actix_http=trace"); - // env_logger::init(); - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); let openssl = ssl_acceptor()?; let mut srv = TestServer::new(move || { From 6d639ae3df10be9e10449774ab96c46c7ca11483 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 22:59:56 -0800 Subject: [PATCH 1004/1635] allow to create http services with config --- src/h2/service.rs | 12 ++++++++++++ src/service/service.rs | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/h2/service.rs b/src/h2/service.rs index a47f507b..53062994 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -47,6 +47,18 @@ where } } + /// Create new `HttpService` instance with config. + pub fn with_config>( + cfg: ServiceConfig, + service: F, + ) -> Self { + H2Service { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + /// Create builder for `HttpService` instance. pub fn build() -> H2ServiceBuilder { H2ServiceBuilder::new() diff --git a/src/service/service.rs b/src/service/service.rs index 5f6ee819..3d0009ed 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -44,6 +44,18 @@ where } } + /// Create new `HttpService` instance with config. + pub fn with_config>( + cfg: ServiceConfig, + service: F, + ) -> Self { + HttpService { + cfg, + srv: service.into_new_service(), + _t: PhantomData, + } + } + /// Create builder for `HttpService` instance. pub fn build() -> HttpServiceBuilder { HttpServiceBuilder::new() From 6e638129c54a558cde24c34974de8443206f7517 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 23:06:14 -0800 Subject: [PATCH 1005/1635] use generic HttpService --- src/server.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/server.rs b/src/server.rs index c28cb280..d6d88d00 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,7 +2,9 @@ use std::marker::PhantomData; use std::sync::Arc; use std::{fmt, io, net}; -use actix_http::{body::MessageBody, h1, KeepAlive, Request, Response, ServiceConfig}; +use actix_http::{ + body::MessageBody, HttpService, KeepAlive, Request, Response, ServiceConfig, +}; use actix_rt::System; use actix_server::{Server, ServerBuilder}; use actix_service::{IntoNewService, NewService}; @@ -72,10 +74,10 @@ where F: Fn() -> I + Send + Clone + 'static, I: IntoNewService, S: NewService, - S::Error: fmt::Debug, + S::Error: fmt::Debug + 'static, S::Response: Into>, S::Service: 'static, - B: MessageBody, + B: MessageBody + 'static, { /// Create new http server with application factory pub fn new(factory: F) -> Self { @@ -244,7 +246,7 @@ where let c = cfg.lock(); let service_config = ServiceConfig::new(c.keep_alive, c.client_timeout, 0); - h1::H1Service::with_config(service_config, factory()) + HttpService::with_config(service_config, factory()) }, )); @@ -298,7 +300,7 @@ where let service_config = ServiceConfig::new(c.keep_alive, c.client_timeout, c.client_timeout); acceptor.clone().map_err(|e| SslError::Ssl(e)).and_then( - h1::H1Service::with_config(service_config, factory()) + HttpService::with_config(service_config, factory()) .map_err(|e| SslError::Service(e)) .map_init_err(|_| ()), ) From e56691bcf23b4a9b37dc42823562fef9e8f4c514 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 23:39:08 -0800 Subject: [PATCH 1006/1635] rename to Files --- Cargo.toml | 2 +- {actix-staticfiles => actix-files}/CHANGES.md | 0 {actix-staticfiles => actix-files}/Cargo.toml | 2 +- {actix-staticfiles => actix-files}/README.md | 0 .../src/config.rs | 0 .../src/error.rs | 6 +- {actix-staticfiles => actix-files}/src/lib.rs | 64 +++++++++--------- .../src/named.rs | 0 .../src/range.rs | 0 .../tests/test space.binary | 0 .../tests/test.binary | 0 .../tests/test.png | Bin 12 files changed, 37 insertions(+), 37 deletions(-) rename {actix-staticfiles => actix-files}/CHANGES.md (100%) rename {actix-staticfiles => actix-files}/Cargo.toml (97%) rename {actix-staticfiles => actix-files}/README.md (100%) rename {actix-staticfiles => actix-files}/src/config.rs (100%) rename {actix-staticfiles => actix-files}/src/error.rs (91%) rename {actix-staticfiles => actix-files}/src/lib.rs (94%) rename {actix-staticfiles => actix-files}/src/named.rs (100%) rename {actix-staticfiles => actix-files}/src/range.rs (100%) rename {actix-staticfiles => actix-files}/tests/test space.binary (100%) rename {actix-staticfiles => actix-files}/tests/test.binary (100%) rename {actix-staticfiles => actix-files}/tests/test.png (100%) diff --git a/Cargo.toml b/Cargo.toml index 2abb4c72..7b2e6c99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,8 @@ path = "src/lib.rs" [workspace] members = [ ".", + "actix-files", "actix-session", - "actix-staticfiles", ] [package.metadata.docs.rs] diff --git a/actix-staticfiles/CHANGES.md b/actix-files/CHANGES.md similarity index 100% rename from actix-staticfiles/CHANGES.md rename to actix-files/CHANGES.md diff --git a/actix-staticfiles/Cargo.toml b/actix-files/Cargo.toml similarity index 97% rename from actix-staticfiles/Cargo.toml rename to actix-files/Cargo.toml index 0a551792..7082d167 100644 --- a/actix-staticfiles/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "actix-staticfiles" +name = "actix-files" version = "0.1.0" authors = ["Nikolay Kim "] description = "Static files support for Actix web." diff --git a/actix-staticfiles/README.md b/actix-files/README.md similarity index 100% rename from actix-staticfiles/README.md rename to actix-files/README.md diff --git a/actix-staticfiles/src/config.rs b/actix-files/src/config.rs similarity index 100% rename from actix-staticfiles/src/config.rs rename to actix-files/src/config.rs diff --git a/actix-staticfiles/src/error.rs b/actix-files/src/error.rs similarity index 91% rename from actix-staticfiles/src/error.rs rename to actix-files/src/error.rs index f165a618..ca99fa81 100644 --- a/actix-staticfiles/src/error.rs +++ b/actix-files/src/error.rs @@ -3,7 +3,7 @@ use derive_more::Display; /// Errors which can occur when serving static files. #[derive(Display, Debug, PartialEq)] -pub enum StaticFilesError { +pub enum FilesError { /// Path is not a directory #[display(fmt = "Path is not a directory. Unable to serve static files")] IsNotDirectory, @@ -13,8 +13,8 @@ pub enum StaticFilesError { IsDirectory, } -/// Return `NotFound` for `StaticFilesError` -impl ResponseError for StaticFilesError { +/// Return `NotFound` for `FilesError` +impl ResponseError for FilesError { fn error_response(&self) -> HttpResponse { HttpResponse::new(StatusCode::NOT_FOUND) } diff --git a/actix-staticfiles/src/lib.rs b/actix-files/src/lib.rs similarity index 94% rename from actix-staticfiles/src/lib.rs rename to actix-files/src/lib.rs index 7c3f6849..c6b52f04 100644 --- a/actix-staticfiles/src/lib.rs +++ b/actix-files/src/lib.rs @@ -29,7 +29,7 @@ mod error; mod named; mod range; -use self::error::{StaticFilesError, UriSegmentError}; +use self::error::{FilesError, UriSegmentError}; pub use crate::config::{DefaultConfig, StaticFileConfig}; pub use crate::named::NamedFile; pub use crate::range::HttpRange; @@ -222,10 +222,10 @@ fn directory_listing( /// /// fn main() { /// let app = App::new() -/// .service(fs::StaticFiles::new("/static", ".")); +/// .service(fs::Files::new("/static", ".")); /// } /// ``` -pub struct StaticFiles { +pub struct Files { path: String, directory: PathBuf, index: Option, @@ -237,28 +237,28 @@ pub struct StaticFiles { _cd_map: PhantomData, } -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. +impl Files { + /// Create new `Files` instance for specified base directory. /// /// `StaticFile` uses `ThreadPool` for blocking filesystem operations. /// By default pool with 5x threads of available cpus is used. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. - pub fn new>(path: &str, dir: T) -> StaticFiles { + pub fn new>(path: &str, dir: T) -> Files { Self::with_config(path, dir, DefaultConfig) } } -impl StaticFiles { - /// Create new `StaticFiles` instance for specified base directory. +impl Files { + /// Create new `Files` instance for specified base directory. /// /// Identical with `new` but allows to specify configiration to use. - pub fn with_config>(path: &str, dir: T, _: C) -> StaticFiles { + pub fn with_config>(path: &str, dir: T, _: C) -> Files { let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new()); if !dir.is_dir() { log::error!("Specified path is not a directory"); } - StaticFiles { + Files { path: path.to_string(), directory: dir, index: None, @@ -294,13 +294,13 @@ impl StaticFiles { /// /// Shows specific index file for directory "/" instead of /// showing files listing. - pub fn index_file>(mut self, index: T) -> StaticFiles { + pub fn index_file>(mut self, index: T) -> Files { self.index = Some(index.into()); self } } -impl HttpServiceFactory

    for StaticFiles +impl HttpServiceFactory

    for Files where P: 'static, C: StaticFileConfig + 'static, @@ -319,16 +319,16 @@ where } impl NewService> - for StaticFiles + for Files { type Response = ServiceResponse; type Error = (); - type Service = StaticFilesService; + type Service = FilesService; type InitError = (); type Future = FutureResult; fn new_service(&self, _: &()) -> Self::Future { - ok(StaticFilesService { + ok(FilesService { directory: self.directory.clone(), index: self.index.clone(), show_index: self.show_index, @@ -341,7 +341,7 @@ impl NewService> } } -pub struct StaticFilesService { +pub struct FilesService { directory: PathBuf, index: Option, show_index: bool, @@ -352,7 +352,7 @@ pub struct StaticFilesService { _cd_map: PhantomData, } -impl Service> for StaticFilesService { +impl Service> for FilesService { type Response = ServiceResponse; type Error = (); type Future = FutureResult; @@ -395,7 +395,7 @@ impl Service> for StaticFilesService

    = StaticFiles::new("/", "missing"); - let _st: StaticFiles<()> = StaticFiles::new("/", "Cargo.toml"); + let _st: Files<()> = Files::new("/", "missing"); + let _st: Files<()> = Files::new("/", "Cargo.toml"); } // #[test] // fn test_default_handler_file_missing() { - // let st = StaticFiles::new(".") + // let st = Files::new(".") // .default_handler(|_: &_| "default content"); // let req = TestRequest::with_uri("/missing") // .param("tail", "missing") @@ -982,7 +982,7 @@ mod tests { // #[test] // fn test_serve_index() { - // let st = StaticFiles::new(".").index_file("test.binary"); + // let st = Files::new(".").index_file("test.binary"); // let req = TestRequest::default().uri("/tests").finish(); // let resp = st.handle(&req).respond_to(&req).unwrap(); @@ -1028,7 +1028,7 @@ mod tests { // #[test] // fn test_serve_index_nested() { - // let st = StaticFiles::new(".").index_file("mod.rs"); + // let st = Files::new(".").index_file("mod.rs"); // let req = TestRequest::default().uri("/src/client").finish(); // let resp = st.handle(&req).respond_to(&req).unwrap(); // let resp = resp.as_msg(); @@ -1048,7 +1048,7 @@ mod tests { // let mut srv = test::TestServer::with_factory(|| { // App::new().handler( // "test", - // StaticFiles::new(".").index_file("Cargo.toml"), + // Files::new(".").index_file("Cargo.toml"), // ) // }); @@ -1081,7 +1081,7 @@ mod tests { // let mut srv = test::TestServer::with_factory(|| { // App::new().handler( // "test", - // StaticFiles::new(".").index_file("Cargo.toml"), + // Files::new(".").index_file("Cargo.toml"), // ) // }); diff --git a/actix-staticfiles/src/named.rs b/actix-files/src/named.rs similarity index 100% rename from actix-staticfiles/src/named.rs rename to actix-files/src/named.rs diff --git a/actix-staticfiles/src/range.rs b/actix-files/src/range.rs similarity index 100% rename from actix-staticfiles/src/range.rs rename to actix-files/src/range.rs diff --git a/actix-staticfiles/tests/test space.binary b/actix-files/tests/test space.binary similarity index 100% rename from actix-staticfiles/tests/test space.binary rename to actix-files/tests/test space.binary diff --git a/actix-staticfiles/tests/test.binary b/actix-files/tests/test.binary similarity index 100% rename from actix-staticfiles/tests/test.binary rename to actix-files/tests/test.binary diff --git a/actix-staticfiles/tests/test.png b/actix-files/tests/test.png similarity index 100% rename from actix-staticfiles/tests/test.png rename to actix-files/tests/test.png From 1151b5bf7c39f6beec1fdce83701df0c4c4b018e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 23:43:03 -0800 Subject: [PATCH 1007/1635] fix crate name --- actix-files/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 7082d167..bd61c880 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -14,7 +14,7 @@ edition = "2018" workspace = ".." [lib] -name = "actix_staticfiles" +name = "actix_files" path = "src/lib.rs" [dependencies] From 22708e78a9043c16038d24d5edb7941b4241891e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 11:09:42 -0800 Subject: [PATCH 1008/1635] added proc-macros for route registration --- Cargo.toml | 2 + actix-files/src/lib.rs | 10 +-- actix-web-codegen/Cargo.toml | 15 +++++ actix-web-codegen/src/lib.rs | 115 ++++++++++++++++++++++++++++++++ actix-web-codegen/src/server.rs | 31 +++++++++ examples/basic.rs | 13 ++-- src/handler.rs | 48 ++++++------- src/lib.rs | 18 +++++ src/route.rs | 11 +-- 9 files changed, 221 insertions(+), 42 deletions(-) create mode 100644 actix-web-codegen/Cargo.toml create mode 100644 actix-web-codegen/src/lib.rs create mode 100644 actix-web-codegen/src/server.rs diff --git a/Cargo.toml b/Cargo.toml index 7b2e6c99..f9e2266e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ ".", "actix-files", "actix-session", + "actix-web-codegen", ] [package.metadata.docs.rs] @@ -63,6 +64,7 @@ actix-codec = "0.1.0" #actix-service = "0.3.2" #actix-utils = "0.3.1" actix-rt = "0.2.0" +actix-web-codegen = { path="actix-web-codegen" } actix-service = { git = "https://github.com/actix/actix-net.git" } actix-utils = { git = "https://github.com/actix/actix-net.git" } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index c6b52f04..c08cae9c 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -318,9 +318,7 @@ where } } -impl NewService> - for Files -{ +impl NewService> for Files { type Response = ServiceResponse; type Error = (); type Service = FilesService; @@ -730,8 +728,7 @@ mod tests { #[test] fn test_named_file_content_range_headers() { let mut srv = test::init_service( - App::new() - .service(Files::new("/test", ".").index_file("tests/test.binary")), + App::new().service(Files::new("/test", ".").index_file("tests/test.binary")), ); // Valid range header @@ -770,8 +767,7 @@ mod tests { #[test] fn test_named_file_content_length_headers() { let mut srv = test::init_service( - App::new() - .service(Files::new("test", ".").index_file("tests/test.binary")), + App::new().service(Files::new("test", ".").index_file("tests/test.binary")), ); // Valid range header diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml new file mode 100644 index 00000000..24ed36b7 --- /dev/null +++ b/actix-web-codegen/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "actix-web-codegen" +description = "Actix web codegen macros" +version = "0.1.0" +authors = ["Nikolay Kim "] +license = "MIT/Apache-2.0" +edition = "2018" +workspace = ".." + +[lib] +proc-macro = true + +[dependencies] +quote = "0.6" +syn = { version = "0.15", features = ["full", "parsing"] } diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs new file mode 100644 index 00000000..1052c82a --- /dev/null +++ b/actix-web-codegen/src/lib.rs @@ -0,0 +1,115 @@ +#![recursion_limit = "512"] + +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse_macro_input; + +/// #[get("path")] attribute +#[proc_macro_attribute] +pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + if args.is_empty() { + panic!("invalid server definition, expected: #[get(\"some path\")]"); + } + + // path + let path = match args[0] { + syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { + let fname = quote!(#fname).to_string(); + fname.as_str()[1..fname.len() - 1].to_owned() + } + _ => panic!("resource path"), + }; + + let ast: syn::ItemFn = syn::parse(input).unwrap(); + let name = ast.ident.clone(); + + (quote! { + #[allow(non_camel_case_types)] + struct #name; + + impl actix_web::dev::HttpServiceFactory

    for #name { + fn register(self, config: &mut actix_web::dev::AppConfig

    ) { + #ast + actix_web::dev::HttpServiceFactory::register( + actix_web::Resource::new(#path) + .route(actix_web::web::get().to(#name)), config); + } + } + }) + .into() +} + +/// #[post("path")] attribute +#[proc_macro_attribute] +pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + if args.is_empty() { + panic!("invalid server definition, expected: #[get(\"some path\")]"); + } + + // path + let path = match args[0] { + syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { + let fname = quote!(#fname).to_string(); + fname.as_str()[1..fname.len() - 1].to_owned() + } + _ => panic!("resource path"), + }; + + let ast: syn::ItemFn = syn::parse(input).unwrap(); + let name = ast.ident.clone(); + + (quote! { + #[allow(non_camel_case_types)] + struct #name; + + impl actix_web::dev::HttpServiceFactory

    for #name { + fn register(self, config: &mut actix_web::dev::AppConfig

    ) { + #ast + actix_web::dev::HttpServiceFactory::register( + actix_web::Resource::new(#path) + .route(actix_web::web::post().to(#name)), config); + } + } + }) + .into() +} + +/// #[put("path")] attribute +#[proc_macro_attribute] +pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + if args.is_empty() { + panic!("invalid server definition, expected: #[get(\"some path\")]"); + } + + // path + let path = match args[0] { + syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { + let fname = quote!(#fname).to_string(); + fname.as_str()[1..fname.len() - 1].to_owned() + } + _ => panic!("resource path"), + }; + + let ast: syn::ItemFn = syn::parse(input).unwrap(); + let name = ast.ident.clone(); + + (quote! { + #[allow(non_camel_case_types)] + struct #name; + + impl actix_web::dev::HttpServiceFactory

    for #name { + fn register(self, config: &mut actix_web::dev::AppConfig

    ) { + #ast + actix_web::dev::HttpServiceFactory::register( + actix_web::Resource::new(#path) + .route(actix_web::web::put().to(#name)), config); + } + } + }) + .into() +} diff --git a/actix-web-codegen/src/server.rs b/actix-web-codegen/src/server.rs new file mode 100644 index 00000000..43e663f3 --- /dev/null +++ b/actix-web-codegen/src/server.rs @@ -0,0 +1,31 @@ +use std::collections::HashSet; +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn; + +/// Thrift mux server impl +pub struct Server {} + +impl Server { + fn new() -> Server { + Server {} + } + + /// generate servers + pub fn generate(input: TokenStream) { + let mut srv = Server::new(); + let ast: syn::ItemFn = syn::parse2(input).unwrap(); + println!("T: {:?}", ast.ident); + + // quote! { + // #ast + + // #(#servers)* + // } + } +} diff --git a/examples/basic.rs b/examples/basic.rs index 5fd862d4..39633f52 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,9 +1,9 @@ use futures::IntoFuture; -use actix_web::{ - http::Method, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, -}; +use actix_web::macros::get; +use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +#[get("/resource1/index.html")] fn index(req: HttpRequest) -> &'static str { println!("REQ: {:?}", req); "Hello world!\r\n" @@ -14,6 +14,7 @@ fn index_async(req: HttpRequest) -> impl IntoFuture &'static str { "Hello world!\r\n" } @@ -27,7 +28,8 @@ fn main() -> std::io::Result<()> { App::new() .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .middleware(middleware::Compress::default()) - .service(web::resource("/resource1/index.html").route(web::get().to(index))) + .service(index) + .service(no_params) .service( web::resource("/resource2/index.html") .middleware( @@ -36,10 +38,9 @@ fn main() -> std::io::Result<()> { .default_resource(|r| { r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) }) - .route(web::method(Method::GET).to_async(index_async)), + .route(web::get().to_async(index_async)), ) .service(web::resource("/test1.html").to(|| "Test\r\n")) - .service(web::resource("/").to(no_params)) }) .bind("127.0.0.1:8080")? .workers(1) diff --git a/src/handler.rs b/src/handler.rs index 442dc60d..435d9a8b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -31,7 +31,7 @@ where } #[doc(hidden)] -pub struct Handle +pub struct Handler where F: Factory, R: Responder, @@ -40,19 +40,19 @@ where _t: PhantomData<(T, R)>, } -impl Handle +impl Handler where F: Factory, R: Responder, { pub fn new(hnd: F) -> Self { - Handle { + Handler { hnd, _t: PhantomData, } } } -impl NewService<(T, HttpRequest)> for Handle +impl NewService<(T, HttpRequest)> for Handler where F: Factory, R: Responder + 'static, @@ -60,11 +60,11 @@ where type Response = ServiceResponse; type Error = Void; type InitError = (); - type Service = HandleService; + type Service = HandlerService; type Future = FutureResult; fn new_service(&self, _: &()) -> Self::Future { - ok(HandleService { + ok(HandlerService { hnd: self.hnd.clone(), _t: PhantomData, }) @@ -72,7 +72,7 @@ where } #[doc(hidden)] -pub struct HandleService +pub struct HandlerService where F: Factory, R: Responder + 'static, @@ -81,14 +81,14 @@ where _t: PhantomData<(T, R)>, } -impl Service<(T, HttpRequest)> for HandleService +impl Service<(T, HttpRequest)> for HandlerService where F: Factory, R: Responder + 'static, { type Response = ServiceResponse; type Error = Void; - type Future = HandleServiceResponse<::Future>; + type Future = HandlerServiceResponse<::Future>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) @@ -96,19 +96,19 @@ where fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { let fut = self.hnd.call(param).respond_to(&req).into_future(); - HandleServiceResponse { + HandlerServiceResponse { fut, req: Some(req), } } } -pub struct HandleServiceResponse { +pub struct HandlerServiceResponse { fut: T, req: Option, } -impl Future for HandleServiceResponse +impl Future for HandlerServiceResponse where T: Future, T::Error: Into, @@ -157,7 +157,7 @@ where } #[doc(hidden)] -pub struct AsyncHandle +pub struct AsyncHandler where F: AsyncFactory, R: IntoFuture, @@ -168,7 +168,7 @@ where _t: PhantomData<(T, R)>, } -impl AsyncHandle +impl AsyncHandler where F: AsyncFactory, R: IntoFuture, @@ -176,13 +176,13 @@ where R::Error: Into, { pub fn new(hnd: F) -> Self { - AsyncHandle { + AsyncHandler { hnd, _t: PhantomData, } } } -impl NewService<(T, HttpRequest)> for AsyncHandle +impl NewService<(T, HttpRequest)> for AsyncHandler where F: AsyncFactory, R: IntoFuture, @@ -192,11 +192,11 @@ where type Response = ServiceResponse; type Error = (); type InitError = (); - type Service = AsyncHandleService; + type Service = AsyncHandlerService; type Future = FutureResult; fn new_service(&self, _: &()) -> Self::Future { - ok(AsyncHandleService { + ok(AsyncHandlerService { hnd: self.hnd.clone(), _t: PhantomData, }) @@ -204,7 +204,7 @@ where } #[doc(hidden)] -pub struct AsyncHandleService +pub struct AsyncHandlerService where F: AsyncFactory, R: IntoFuture, @@ -215,7 +215,7 @@ where _t: PhantomData<(T, R)>, } -impl Service<(T, HttpRequest)> for AsyncHandleService +impl Service<(T, HttpRequest)> for AsyncHandlerService where F: AsyncFactory, R: IntoFuture, @@ -224,14 +224,14 @@ where { type Response = ServiceResponse; type Error = (); - type Future = AsyncHandleServiceResponse; + type Future = AsyncHandlerServiceResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) } fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - AsyncHandleServiceResponse { + AsyncHandlerServiceResponse { fut: self.hnd.call(param).into_future(), req: Some(req), } @@ -239,12 +239,12 @@ where } #[doc(hidden)] -pub struct AsyncHandleServiceResponse { +pub struct AsyncHandlerServiceResponse { fut: T, req: Option, } -impl Future for AsyncHandleServiceResponse +impl Future for AsyncHandlerServiceResponse where T: Future, T::Item: Into, diff --git a/src/lib.rs b/src/lib.rs index fd1b21f3..dd60c7b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,24 @@ mod service; mod state; pub mod test; +/// Attribute macros for route registration +/// +/// ```rust +/// use actix_web::{macros, App, HttpResponse}; +/// +/// #[macros::get("/index.html")] +/// fn index() -> HttpResponse { +/// HttpResponse::Ok().finish() +/// } +/// +/// fn main() { +/// let app = App::new().service(index); +/// } +/// ``` +pub mod macros { + pub use actix_web_codegen::{get, post, put}; +} + // re-export for convenience pub use actix_http::Response as HttpResponse; pub use actix_http::{body, error, http, Error, HttpMessage, ResponseError, Result}; diff --git a/src/route.rs b/src/route.rs index b611164e..9538dfd2 100644 --- a/src/route.rs +++ b/src/route.rs @@ -8,7 +8,7 @@ use futures::{Async, Future, IntoFuture, Poll}; use crate::extract::{ConfigStorage, ExtractorConfig, FromRequest}; use crate::guard::{self, Guard}; -use crate::handler::{AsyncFactory, AsyncHandle, Extract, Factory, Handle}; +use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; use crate::responder::Responder; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -50,8 +50,9 @@ impl Route

    { let config_ref = Rc::new(RefCell::new(None)); Route { service: Box::new(RouteNewService::new( - Extract::new(config_ref.clone()) - .and_then(Handle::new(HttpResponse::NotFound).map_err(|_| panic!())), + Extract::new(config_ref.clone()).and_then( + Handler::new(HttpResponse::NotFound).map_err(|_| panic!()), + ), )), guards: Rc::new(Vec::new()), config: ConfigStorage::default(), @@ -272,7 +273,7 @@ impl Route

    { T::Config::store_default(&mut self.config); self.service = Box::new(RouteNewService::new( Extract::new(self.config_ref.clone()) - .and_then(Handle::new(handler).map_err(|_| panic!())), + .and_then(Handler::new(handler).map_err(|_| panic!())), )); self } @@ -314,7 +315,7 @@ impl Route

    { { self.service = Box::new(RouteNewService::new( Extract::new(self.config_ref.clone()) - .and_then(AsyncHandle::new(handler).map_err(|_| panic!())), + .and_then(AsyncHandler::new(handler).map_err(|_| panic!())), )); self } From ceb6d45bf240ae228e4ce7f4bc17f5b747e4e6ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 11:43:46 -0800 Subject: [PATCH 1009/1635] reexpost extractors in web module --- actix-web-codegen/src/lib.rs | 9 +++++--- examples/basic.rs | 14 ++++++------ src/app.rs | 42 ++++++++++++++++-------------------- src/extract.rs | 8 +++---- src/lib.rs | 6 ++++-- src/route.rs | 4 ++-- 6 files changed, 41 insertions(+), 42 deletions(-) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 1052c82a..26b422d7 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -35,7 +35,8 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { #ast actix_web::dev::HttpServiceFactory::register( actix_web::Resource::new(#path) - .route(actix_web::web::get().to(#name)), config); + .guard(actix_web::guard::Get()) + .to(#name), config); } } }) @@ -71,7 +72,8 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { #ast actix_web::dev::HttpServiceFactory::register( actix_web::Resource::new(#path) - .route(actix_web::web::post().to(#name)), config); + .guard(actix_web::guard::Post()) + .to(#name), config); } } }) @@ -107,7 +109,8 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { #ast actix_web::dev::HttpServiceFactory::register( actix_web::Resource::new(#path) - .route(actix_web::web::put().to(#name)), config); + .guard(actix_web::guard::Put()) + .to(#name), config); } } }) diff --git a/examples/basic.rs b/examples/basic.rs index 39633f52..3f832780 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -3,10 +3,10 @@ use futures::IntoFuture; use actix_web::macros::get; use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; -#[get("/resource1/index.html")] -fn index(req: HttpRequest) -> &'static str { +#[get("/resource1/{name}/index.html")] +fn index(req: HttpRequest, name: web::Path) -> String { println!("REQ: {:?}", req); - "Hello world!\r\n" + format!("Hello: {}!\r\n", name) } fn index_async(req: HttpRequest) -> impl IntoFuture { @@ -20,14 +20,14 @@ fn no_params() -> &'static str { } fn main() -> std::io::Result<()> { - ::std::env::set_var("RUST_LOG", "actix_server=info,actix_web2=info"); + std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init(); - let sys = actix_rt::System::new("hello-world"); HttpServer::new(|| { App::new() .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .middleware(middleware::Compress::default()) + .middleware(middleware::Logger::default()) .service(index) .service(no_params) .service( @@ -44,7 +44,5 @@ fn main() -> std::io::Result<()> { }) .bind("127.0.0.1:8080")? .workers(1) - .start(); - - sys.run() + .run() } diff --git a/src/app.rs b/src/app.rs index f62c064a..1cff1788 100644 --- a/src/app.rs +++ b/src/app.rs @@ -75,13 +75,13 @@ where /// /// ```rust /// use std::cell::Cell; - /// use actix_web::{web, State, App}; + /// use actix_web::{web, App}; /// /// struct MyState { /// counter: Cell, /// } /// - /// fn index(state: State) { + /// fn index(state: web::State) { /// state.counter.set(state.counter.get() + 1); /// } /// @@ -785,7 +785,7 @@ mod tests { use super::*; use crate::http::{Method, StatusCode}; use crate::test::{block_on, init_service, TestRequest}; - use crate::{web, HttpResponse, State}; + use crate::{web, HttpResponse}; #[test] fn test_default_resource() { @@ -828,21 +828,19 @@ mod tests { #[test] fn test_state() { - let mut srv = init_service( - App::new() - .state(10usize) - .service(web::resource("/").to(|_: State| HttpResponse::Ok())), - ); + let mut srv = + init_service(App::new().state(10usize).service( + web::resource("/").to(|_: web::State| HttpResponse::Ok()), + )); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = init_service( - App::new() - .state(10u32) - .service(web::resource("/").to(|_: State| HttpResponse::Ok())), - ); + let mut srv = + init_service(App::new().state(10u32).service( + web::resource("/").to(|_: web::State| HttpResponse::Ok()), + )); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -850,20 +848,18 @@ mod tests { #[test] fn test_state_factory() { - let mut srv = init_service( - App::new() - .state_factory(|| Ok::<_, ()>(10usize)) - .service(web::resource("/").to(|_: State| HttpResponse::Ok())), - ); + let mut srv = + init_service(App::new().state_factory(|| Ok::<_, ()>(10usize)).service( + web::resource("/").to(|_: web::State| HttpResponse::Ok()), + )); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = init_service( - App::new() - .state_factory(|| Ok::<_, ()>(10u32)) - .service(web::resource("/").to(|_: State| HttpResponse::Ok())), - ); + let mut srv = + init_service(App::new().state_factory(|| Ok::<_, ()>(10u32)).service( + web::resource("/").to(|_: web::State| HttpResponse::Ok()), + )); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); diff --git a/src/extract.rs b/src/extract.rs index 0b212aba..6c838901 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -554,8 +554,8 @@ impl Default for FormConfig { /// name: String, /// } /// -/// fn index(req: HttpRequest) -> Result> { -/// Ok(Json(MyObj { +/// fn index(req: HttpRequest) -> Result> { +/// Ok(web::Json(MyObj { /// name: req.match_info().get("name").unwrap().to_string(), /// })) /// } @@ -679,7 +679,7 @@ where /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, extract, web, App, HttpResponse, Json}; +/// use actix_web::{error, extract, web, App, HttpResponse}; /// /// #[derive(Deserialize)] /// struct Info { @@ -687,7 +687,7 @@ where /// } /// /// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: Json) -> String { +/// fn index(info: web::Json) -> String { /// format!("Welcome {}!", info.username) /// } /// diff --git a/src/lib.rs b/src/lib.rs index dd60c7b8..35a88b98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ pub use actix_http::Response as HttpResponse; pub use actix_http::{body, error, http, Error, HttpMessage, ResponseError, Result}; pub use crate::app::App; -pub use crate::extract::{FromRequest, Json}; +pub use crate::extract::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::responder::{Either, Responder}; @@ -49,7 +49,6 @@ pub use crate::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; pub use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; -pub use crate::state::State; pub mod dev { //! The `actix-web` prelude for library developers @@ -93,6 +92,9 @@ pub mod web { use crate::route::Route; use crate::scope::Scope; + pub use crate::extract::{Json, Path, Query}; + pub use crate::state::State; + /// Create resource for a specific path. /// /// Resources may have variable path segments. For example, a diff --git a/src/route.rs b/src/route.rs index 9538dfd2..45bc6534 100644 --- a/src/route.rs +++ b/src/route.rs @@ -245,7 +245,7 @@ impl Route

    { /// ```rust /// # use std::collections::HashMap; /// # use serde_derive::Deserialize; - /// use actix_web::{web, App, Json, extract::Path, extract::Query}; + /// use actix_web::{web, App}; /// /// #[derive(Deserialize)] /// struct Info { @@ -253,7 +253,7 @@ impl Route

    { /// } /// /// /// extract path info using serde - /// fn index(path: Path, query: Query>, body: Json) -> String { + /// fn index(path: web::Path, query: web::Query>, body: web::Json) -> String { /// format!("Welcome {}!", path.username) /// } /// From d77954d19e99aabaa8749ead28f5a03435b0b656 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 12:32:40 -0800 Subject: [PATCH 1010/1635] fix files test --- actix-files/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index c08cae9c..5dd98dcc 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -214,11 +214,11 @@ fn directory_listing( /// Static files handling /// -/// `StaticFile` handler must be registered with `App::service()` method. +/// `Files` service must be registered with `App::service()` method. /// /// ```rust /// use actix_web::App; -/// use actix_staticfiles as fs; +/// use actix_files as fs; /// /// fn main() { /// let app = App::new() @@ -240,7 +240,7 @@ pub struct Files { impl Files { /// Create new `Files` instance for specified base directory. /// - /// `StaticFile` uses `ThreadPool` for blocking filesystem operations. + /// `File` uses `ThreadPool` for blocking filesystem operations. /// By default pool with 5x threads of available cpus is used. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. pub fn new>(path: &str, dir: T) -> Files { From b211966c28112f808005ee19a92c94ad650ce86c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 13:33:40 -0800 Subject: [PATCH 1011/1635] Payload extractor --- actix-web-codegen/src/server.rs | 31 ----------- examples/basic.rs | 4 +- src/extract.rs | 95 ++++++++++++++++++++++++++++++++- src/lib.rs | 29 ++++------ 4 files changed, 107 insertions(+), 52 deletions(-) delete mode 100644 actix-web-codegen/src/server.rs diff --git a/actix-web-codegen/src/server.rs b/actix-web-codegen/src/server.rs deleted file mode 100644 index 43e663f3..00000000 --- a/actix-web-codegen/src/server.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::collections::HashSet; -use std::env; -use std::fs::File; -use std::io::Read; -use std::path::PathBuf; - -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn; - -/// Thrift mux server impl -pub struct Server {} - -impl Server { - fn new() -> Server { - Server {} - } - - /// generate servers - pub fn generate(input: TokenStream) { - let mut srv = Server::new(); - let ast: syn::ItemFn = syn::parse2(input).unwrap(); - println!("T: {:?}", ast.ident); - - // quote! { - // #ast - - // #(#servers)* - // } - } -} diff --git a/examples/basic.rs b/examples/basic.rs index 3f832780..f8b81648 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,6 +1,8 @@ use futures::IntoFuture; -use actix_web::macros::get; +#[macro_use] +extern crate actix_web; + use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; #[get("/resource1/{name}/index.html")] diff --git a/src/extract.rs b/src/extract.rs index 6c838901..ac04f1c4 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -409,7 +409,7 @@ impl DerefMut for Form { impl FromRequest

    for Form where T: DeserializeOwned + 'static, - P: Stream + 'static, + P: Stream + 'static, { type Error = Error; type Future = Box>; @@ -653,7 +653,7 @@ impl Responder for Json { impl FromRequest

    for Json where T: DeserializeOwned + 'static, - P: Stream + 'static, + P: Stream + 'static, { type Error = Error; type Future = Box>; @@ -739,6 +739,97 @@ impl Default for JsonConfig { } } +/// Payload extractor returns request 's payload stream. +/// +/// ## Example +/// +/// ```rust +/// use futures::{Future, Stream}; +/// use actix_web::{web, error, App, Error, HttpResponse}; +/// +/// /// extract binary data from request +/// fn index

    (body: web::Payload

    ) -> impl Future +/// where +/// P: Stream +/// { +/// body.map_err(Error::from) +/// .fold(web::BytesMut::new(), move |mut body, chunk| { +/// body.extend_from_slice(&chunk); +/// Ok::<_, Error>(body) +/// }) +/// .and_then(|body| { +/// format!("Body {:?}!", body); +/// Ok(HttpResponse::Ok().finish()) +/// }) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to_async(index)) +/// ); +/// } +/// ``` +pub struct Payload(crate::dev::Payload); + +impl Stream for Payload +where + T: Stream, +{ + type Item = Bytes; + type Error = PayloadError; + + #[inline] + fn poll(&mut self) -> Poll, PayloadError> { + self.0.poll() + } +} + +/// Get request's payload stream +/// +/// ## Example +/// +/// ```rust +/// use futures::{Future, Stream}; +/// use actix_web::{web, error, App, Error, HttpResponse}; +/// +/// /// extract binary data from request +/// fn index

    (body: web::Payload

    ) -> impl Future +/// where +/// P: Stream +/// { +/// body.map_err(Error::from) +/// .fold(web::BytesMut::new(), move |mut body, chunk| { +/// body.extend_from_slice(&chunk); +/// Ok::<_, Error>(body) +/// }) +/// .and_then(|body| { +/// format!("Body {:?}!", body); +/// Ok(HttpResponse::Ok().finish()) +/// }) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to_async(index)) +/// ); +/// } +/// ``` +impl

    FromRequest

    for Payload

    +where + P: Stream, +{ + type Error = Error; + type Future = Result, Error>; + type Config = (); + + #[inline] + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { + Ok(Payload(req.take_payload())) + } +} + /// Request binary data from a request's payload. /// /// Loads request's payload and construct Bytes instance. diff --git a/src/lib.rs b/src/lib.rs index 35a88b98..ad4a2a86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,23 +18,12 @@ mod service; mod state; pub mod test; -/// Attribute macros for route registration -/// -/// ```rust -/// use actix_web::{macros, App, HttpResponse}; -/// -/// #[macros::get("/index.html")] -/// fn index() -> HttpResponse { -/// HttpResponse::Ok().finish() -/// } -/// -/// fn main() { -/// let app = App::new().service(index); -/// } -/// ``` -pub mod macros { - pub use actix_web_codegen::{get, post, put}; -} +#[allow(unused_imports)] +#[macro_use] +extern crate actix_web_codegen; + +#[doc(hidden)] +pub use actix_web_codegen::*; // re-export for convenience pub use actix_http::Response as HttpResponse; @@ -85,6 +74,9 @@ pub mod web { use actix_http::{http::Method, Error, Response}; use futures::IntoFuture; + pub use actix_http::Response as HttpResponse; + pub use bytes::{Bytes, BytesMut}; + use crate::extract::FromRequest; use crate::handler::{AsyncFactory, Factory}; use crate::resource::Resource; @@ -92,7 +84,8 @@ pub mod web { use crate::route::Route; use crate::scope::Scope; - pub use crate::extract::{Json, Path, Query}; + pub use crate::extract::{Json, Path, Payload, Query}; + pub use crate::request::HttpRequest; pub use crate::state::State; /// Create resource for a specific path. From 0e57b4ad618bcf0d4c4140acd0bb268c4060211e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 14:01:52 -0800 Subject: [PATCH 1012/1635] export extractor configs via web module --- src/app.rs | 8 ++++---- src/extract.rs | 50 ++++++++++++++++++++++++------------------------- src/lib.rs | 5 +++-- src/resource.rs | 4 ++-- src/route.rs | 12 ++++++------ src/scope.rs | 8 ++++---- 6 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1cff1788..c1c019a3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -189,9 +189,9 @@ where /// multiple resources with one route would be registered for same resource path. /// /// ```rust - /// use actix_web::{web, App, HttpResponse, extract::Path}; + /// use actix_web::{web, App, HttpResponse}; /// - /// fn index(data: Path<(String, String)>) -> &'static str { + /// fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// @@ -276,9 +276,9 @@ where /// multiple resources with one route would be registered for same resource path. /// /// ```rust - /// use actix_web::{web, App, HttpResponse, extract::Path}; + /// use actix_web::{web, App, HttpResponse}; /// - /// fn index(data: Path<(String, String)>) -> &'static str { + /// fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// diff --git a/src/extract.rs b/src/extract.rs index ac04f1c4..c34d9df7 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -78,12 +78,12 @@ impl ExtractorConfig for () { /// ## Example /// /// ```rust -/// use actix_web::{web, http, App, extract::Path}; +/// use actix_web::{web, App}; /// /// /// extract path info from "/{username}/{count}/index.html" url /// /// {username} - deserializes to a String /// /// {count} - - deserializes to a u32 -/// fn index(info: Path<(String, u32)>) -> String { +/// fn index(info: web::Path<(String, u32)>) -> String { /// format!("Welcome {}! {}", info.0, info.1) /// } /// @@ -100,7 +100,7 @@ impl ExtractorConfig for () { /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App, extract::Path, Error}; +/// use actix_web::{web, App, Error}; /// /// #[derive(Deserialize)] /// struct Info { @@ -108,7 +108,7 @@ impl ExtractorConfig for () { /// } /// /// /// extract `Info` from a path using serde -/// fn index(info: Path) -> Result { +/// fn index(info: web::Path) -> Result { /// Ok(format!("Welcome {}!", info.username)) /// } /// @@ -170,12 +170,12 @@ impl From for Path { /// ## Example /// /// ```rust -/// use actix_web::{web, http, App, extract::Path}; +/// use actix_web::{web, App}; /// /// /// extract path info from "/{username}/{count}/index.html" url /// /// {username} - deserializes to a String /// /// {count} - - deserializes to a u32 -/// fn index(info: Path<(String, u32)>) -> String { +/// fn index(info: web::Path<(String, u32)>) -> String { /// format!("Welcome {}! {}", info.0, info.1) /// } /// @@ -192,7 +192,7 @@ impl From for Path { /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App, extract::Path, Error}; +/// use actix_web::{web, App, Error}; /// /// #[derive(Deserialize)] /// struct Info { @@ -200,7 +200,7 @@ impl From for Path { /// } /// /// /// extract `Info` from a path using serde -/// fn index(info: Path) -> Result { +/// fn index(info: web::Path) -> Result { /// Ok(format!("Welcome {}!", info.username)) /// } /// @@ -244,7 +244,7 @@ impl fmt::Display for Path { /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, extract, App}; +/// use actix_web::{web, App}; /// /// #[derive(Debug, Deserialize)] /// pub enum ResponseType { @@ -261,7 +261,7 @@ impl fmt::Display for Path { /// // Use `Query` extractor for query information. /// // This handler get called only if request's query contains `username` field /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: extract::Query) -> String { +/// fn index(info: web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } /// @@ -299,7 +299,7 @@ impl Query { /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, extract, App}; +/// use actix_web::{web, App}; /// /// #[derive(Debug, Deserialize)] /// pub enum ResponseType { @@ -316,7 +316,7 @@ impl Query { /// // Use `Query` extractor for query information. /// // This handler get called only if request's query contains `username` field /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: extract::Query) -> String { +/// fn index(info: web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } /// @@ -368,7 +368,7 @@ impl fmt::Display for Query { /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App, extract::Form}; +/// use actix_web::{web, App}; /// /// #[derive(Deserialize)] /// struct FormData { @@ -378,7 +378,7 @@ impl fmt::Display for Query { /// /// Extract form data using serde. /// /// This handler get called only if content type is *x-www-form-urlencoded* /// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: Form) -> String { +/// fn index(form: web::Form) -> String { /// format!("Welcome {}!", form.username) /// } /// # fn main() {} @@ -447,7 +447,7 @@ impl fmt::Display for Form { /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, extract, App, Result}; +/// use actix_web::{web, App, Result}; /// /// #[derive(Deserialize)] /// struct FormData { @@ -456,7 +456,7 @@ impl fmt::Display for Form { /// /// /// Extract form data using serde. /// /// Custom configuration is used for this handler, max payload size is 4k -/// fn index(form: extract::Form) -> Result { +/// fn index(form: web::Form) -> Result { /// Ok(format!("Welcome {}!", form.username)) /// } /// @@ -465,7 +465,7 @@ impl fmt::Display for Form { /// web::resource("/index.html") /// .route(web::get() /// // change `Form` extractor configuration -/// .config(extract::FormConfig::default().limit(4097)) +/// .config(web::FormConfig::default().limit(4097)) /// .to(index)) /// ); /// } @@ -520,7 +520,7 @@ impl Default for FormConfig { /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, extract, App}; +/// use actix_web::{web, App}; /// /// #[derive(Deserialize)] /// struct Info { @@ -528,7 +528,7 @@ impl Default for FormConfig { /// } /// /// /// deserialize `Info` from request's body -/// fn index(info: extract::Json) -> String { +/// fn index(info: web::Json) -> String { /// format!("Welcome {}!", info.username) /// } /// @@ -631,7 +631,7 @@ impl Responder for Json { /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, extract, App}; +/// use actix_web::{web, App}; /// /// #[derive(Deserialize)] /// struct Info { @@ -639,7 +639,7 @@ impl Responder for Json { /// } /// /// /// deserialize `Info` from request's body -/// fn index(info: extract::Json) -> String { +/// fn index(info: web::Json) -> String { /// format!("Welcome {}!", info.username) /// } /// @@ -679,7 +679,7 @@ where /// /// ```rust /// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, extract, web, App, HttpResponse}; +/// use actix_web::{error, web, App, HttpResponse}; /// /// #[derive(Deserialize)] /// struct Info { @@ -696,7 +696,7 @@ where /// web::resource("/index.html").route( /// web::post().config( /// // change json extractor configuration -/// extract::JsonConfig::default().limit(4096) +/// web::JsonConfig::default().limit(4096) /// .error_handler(|err, req| { // <- create custom error response /// error::InternalError::from_response( /// err, HttpResponse::Conflict().finish()).into() @@ -887,7 +887,7 @@ where /// ## Example /// /// ```rust -/// use actix_web::{web, extract, App}; +/// use actix_web::{web, App}; /// /// /// extract text data from request /// fn index(text: String) -> String { @@ -898,7 +898,7 @@ where /// let app = App::new().service( /// web::resource("/index.html").route( /// web::get() -/// .config(extract::PayloadConfig::new(4096)) // <- limit size of the payload +/// .config(web::PayloadConfig::new(4096)) // <- limit size of the payload /// .to(index)) // <- register handler with extractor params /// ); /// } diff --git a/src/lib.rs b/src/lib.rs index ad4a2a86..def2abcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![allow(clippy::type_complexity)] mod app; -pub mod extract; +mod extract; mod handler; // mod info; pub mod blocking; @@ -84,7 +84,8 @@ pub mod web { use crate::route::Route; use crate::scope::Scope; - pub use crate::extract::{Json, Path, Payload, Query}; + pub use crate::extract::{Form, Json, Path, Payload, Query}; + pub use crate::extract::{FormConfig, JsonConfig, PayloadConfig}; pub use crate::request::HttpRequest; pub use crate::state::State; diff --git a/src/resource.rs b/src/resource.rs index 157b181e..13afff70 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -75,9 +75,9 @@ where /// Add match guard to a resource. /// /// ```rust - /// use actix_web::{web, guard, App, HttpResponse, extract::Path}; + /// use actix_web::{web, guard, App, HttpResponse}; /// - /// fn index(data: Path<(String, String)>) -> &'static str { + /// fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// diff --git a/src/route.rs b/src/route.rs index 45bc6534..d1a8320d 100644 --- a/src/route.rs +++ b/src/route.rs @@ -220,7 +220,7 @@ impl Route

    { /// /// ```rust /// #[macro_use] extern crate serde_derive; - /// use actix_web::{web, http, App, extract::Path}; + /// use actix_web::{web, http, App}; /// /// #[derive(Deserialize)] /// struct Info { @@ -228,7 +228,7 @@ impl Route

    { /// } /// /// /// extract path info using serde - /// fn index(info: Path) -> String { + /// fn index(info: web::Path) -> String { /// format!("Welcome {}!", info.username) /// } /// @@ -284,7 +284,7 @@ impl Route

    { /// ```rust /// # use futures::future::ok; /// #[macro_use] extern crate serde_derive; - /// use actix_web::{web, App, Error, extract::Path}; + /// use actix_web::{web, App, Error}; /// use futures::Future; /// /// #[derive(Deserialize)] @@ -293,7 +293,7 @@ impl Route

    { /// } /// /// /// extract path info using serde - /// fn index(info: Path) -> impl Future { + /// fn index(info: web::Path) -> impl Future { /// ok("Hello World!") /// } /// @@ -324,7 +324,7 @@ impl Route

    { /// for specific route. /// /// ```rust - /// use actix_web::{web, extract, App}; + /// use actix_web::{web, App}; /// /// /// extract text data from request /// fn index(body: String) -> String { @@ -336,7 +336,7 @@ impl Route

    { /// web::resource("/index.html").route( /// web::get() /// // limit size of the payload - /// .config(extract::PayloadConfig::new(4096)) + /// .config(web::PayloadConfig::new(4096)) /// // register handler /// .to(index) /// )); diff --git a/src/scope.rs b/src/scope.rs index 5580b15e..a6358869 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -89,9 +89,9 @@ where /// Add match guard to a scope. /// /// ```rust - /// use actix_web::{web, guard, App, HttpRequest, HttpResponse, extract::Path}; + /// use actix_web::{web, guard, App, HttpRequest, HttpResponse}; /// - /// fn index(data: Path<(String, String)>) -> &'static str { + /// fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// @@ -146,9 +146,9 @@ where /// multiple resources with one route would be registered for same resource path. /// /// ```rust - /// use actix_web::{web, App, HttpResponse, extract::Path}; + /// use actix_web::{web, App, HttpResponse}; /// - /// fn index(data: Path<(String, String)>) -> &'static str { + /// fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// From c2a350b33fba39ed7e175dfaa5b5ddd1b3da88aa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 14:40:20 -0800 Subject: [PATCH 1013/1635] export blocking via web module --- src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index def2abcf..6cf18a2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ pub mod web { pub use actix_http::Response as HttpResponse; pub use bytes::{Bytes, BytesMut}; + use crate::blocking::CpuFuture; use crate::extract::FromRequest; use crate::handler::{AsyncFactory, Factory}; use crate::resource::Resource; @@ -234,4 +235,15 @@ pub mod web { { Route::new().to_async(handler) } + + /// Execute blocking function on a thread pool, returns future that resolves + /// to result of the function execution. + pub fn blocking(f: F) -> CpuFuture + where + F: FnOnce() -> Result + Send + 'static, + I: Send + 'static, + E: Send + std::fmt::Debug + 'static, + { + crate::blocking::run(f) + } } From b6b2eadb3ac0230c0b2c688b6f3e586a5821884d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 14:41:43 -0800 Subject: [PATCH 1014/1635] rename blocking fn --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6cf18a2a..6e809fb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -238,7 +238,7 @@ pub mod web { /// Execute blocking function on a thread pool, returns future that resolves /// to result of the function execution. - pub fn blocking(f: F) -> CpuFuture + pub fn block(f: F) -> CpuFuture where F: FnOnce() -> Result + Send + 'static, I: Send + 'static, From 88e5059910bfc54be07b52043c2af97a1fa9790d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 15:37:39 -0800 Subject: [PATCH 1015/1635] add doc string to guards --- src/guard.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/guard.rs b/src/guard.rs index 1632b997..f9565d0f 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -1,4 +1,30 @@ //! Route match guards. +//! +//! Guards are one of the way how actix-web router chooses +//! handler service. In essence it just function that accepts +//! reference to a `RequestHead` instance and returns boolean. +//! It is possible to add guards to *scopes*, *resources* +//! and *routes*. Actix provide several guards by default, like various +//! http methods, header, etc. To become a guard, type must implement `Guard` +//! trait. Simple functions coulds guards as well. +//! +//! Guard can not modify request object. But it is possible to +//! to store extra attributes on a request by using `Extensions` container. +//! Extensions container available via `RequestHead::extensions()` method. +//! +//! ```rust +//! use actix_web::{web, http, dev, guard, App, HttpResponse}; +//! +//! fn main() { +//! App::new().service(web::resource("/index.html").route( +//! web::route() +//! .guard(guard::Post()) +//! .guard(|head: &dev::RequestHead| head.method == http::Method::GET) +//! .to(|| HttpResponse::MethodNotAllowed())) +//! ); +//! } +//! ``` + #![allow(non_snake_case)] use actix_http::http::{self, header, HttpTryFrom}; use actix_http::RequestHead; @@ -13,6 +39,18 @@ pub trait Guard { fn check(&self, request: &RequestHead) -> bool; } +#[doc(hidden)] +pub struct FnGuard bool + 'static>(F); + +impl Guard for F +where + F: Fn(&RequestHead) -> bool + 'static, +{ + fn check(&self, head: &RequestHead) -> bool { + (*self)(head) + } +} + /// Return guard that matches if any of supplied guards. /// /// ```rust From eef687ec80897b476498a70f037472bef22a3994 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 15:51:24 -0800 Subject: [PATCH 1016/1635] remove unneeded methods --- src/lib.rs | 29 +++++++++++++++++------------ src/responder.rs | 2 +- src/route.rs | 20 -------------------- src/scope.rs | 2 +- 4 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6e809fb6..a61387e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ pub use actix_web_codegen::*; // re-export for convenience pub use actix_http::Response as HttpResponse; -pub use actix_http::{body, error, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{error, http, Error, HttpMessage, ResponseError, Result}; pub use crate::app::App; pub use crate::extract::FromRequest; @@ -154,37 +154,42 @@ pub mod web { Scope::new(path) } - /// Create **route** without configuration. + /// Create *route* without configuration. pub fn route() -> Route

    { Route::new() } - /// Create **route** with `GET` method guard. + /// Create *route* with `GET` method guard. pub fn get() -> Route

    { - Route::get() + Route::new().method(Method::GET) } - /// Create **route** with `POST` method guard. + /// Create *route* with `POST` method guard. pub fn post() -> Route

    { - Route::post() + Route::new().method(Method::POST) } - /// Create **route** with `PUT` method guard. + /// Create *route* with `PUT` method guard. pub fn put() -> Route

    { - Route::put() + Route::new().method(Method::PUT) } - /// Create **route** with `DELETE` method guard. + /// Create *route* with `PATCH` method guard. + pub fn patch() -> Route

    { + Route::new().method(Method::PATCH) + } + + /// Create *route* with `DELETE` method guard. pub fn delete() -> Route

    { - Route::delete() + Route::new().method(Method::DELETE) } - /// Create **route** with `HEAD` method guard. + /// Create *route* with `HEAD` method guard. pub fn head() -> Route

    { Route::new().method(Method::HEAD) } - /// Create **route** and add method guard. + /// Create *route* and add method guard. pub fn method(method: Method) -> Route

    { Route::new().method(method) } diff --git a/src/responder.rs b/src/responder.rs index 9e9e0f10..6dce300a 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -291,7 +291,7 @@ mod tests { use actix_service::Service; use bytes::Bytes; - use crate::body::{Body, ResponseBody}; + use crate::dev::{Body, ResponseBody}; use crate::http::StatusCode; use crate::test::{init_service, TestRequest}; use crate::{web, App}; diff --git a/src/route.rs b/src/route.rs index d1a8320d..f7b99050 100644 --- a/src/route.rs +++ b/src/route.rs @@ -60,26 +60,6 @@ impl Route

    { } } - /// Create new `GET` route. - pub fn get() -> Route

    { - Route::new().method(Method::GET) - } - - /// Create new `POST` route. - pub fn post() -> Route

    { - Route::new().method(Method::POST) - } - - /// Create new `PUT` route. - pub fn put() -> Route

    { - Route::new().method(Method::PUT) - } - - /// Create new `DELETE` route. - pub fn delete() -> Route

    { - Route::new().method(Method::DELETE) - } - pub(crate) fn finish(self) -> Self { *self.config_ref.borrow_mut() = self.config.storage.clone(); self diff --git a/src/scope.rs b/src/scope.rs index a6358869..2c2e3c2b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -451,7 +451,7 @@ mod tests { use actix_service::Service; use bytes::Bytes; - use crate::body::{Body, ResponseBody}; + use crate::dev::{Body, ResponseBody}; use crate::http::{Method, StatusCode}; use crate::test::{block_on, init_service, TestRequest}; use crate::{guard, web, App, HttpRequest, HttpResponse}; From 2f6df111832112ad012687557f66b112ef0d1ed1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 7 Mar 2019 19:31:17 -0800 Subject: [PATCH 1017/1635] do not execute blocking fn if result is not required --- src/blocking.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/blocking.rs b/src/blocking.rs index 01be30dd..fc9cec29 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -41,6 +41,7 @@ thread_local! { }; } +/// Blocking operation execution error #[derive(Debug, Display)] pub enum BlockingError { #[display(fmt = "{:?}", _0)] @@ -62,13 +63,17 @@ where let (tx, rx) = oneshot::channel(); POOL.with(|pool| { pool.execute(move || { - let _ = tx.send(f()); + if !tx.is_canceled() { + let _ = tx.send(f()); + } }) }); CpuFuture { rx } } +/// Blocking operation completion future. It resolves with results +/// of blocking function execution. pub struct CpuFuture { rx: oneshot::Receiver>, } From e3245223893a648db8c4cd5ccc426a4aef9e16e6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Mar 2019 22:47:49 -0800 Subject: [PATCH 1018/1635] listen method has different signature --- test-server/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 695a477d..dd7bc362 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -67,7 +67,7 @@ impl TestServer { let local_addr = tcp.local_addr().unwrap(); Server::build() - .listen("test", tcp, factory) + .listen("test", tcp, factory)? .workers(1) .disable_signals() .start(); From ca73f178c927d9e3d786952826005e652f00fa35 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 07:37:23 -0800 Subject: [PATCH 1019/1635] revert generic service request; add ServerConfig to service factories --- Cargo.toml | 2 ++ examples/echo.rs | 10 +++--- examples/echo2.rs | 10 +++--- examples/framed_hello.rs | 10 +++--- examples/hello-world.rs | 10 +++--- src/client/connector.rs | 68 ++++++++++++++++++++++++++++------------ src/client/pool.rs | 15 +++++++-- src/client/request.rs | 2 +- src/h1/dispatcher.rs | 14 ++++----- src/h1/service.rs | 39 +++++++++++++---------- src/h2/dispatcher.rs | 14 ++++----- src/h2/service.rs | 41 ++++++++++++++---------- src/lib.rs | 3 +- src/service/senderror.rs | 12 ++++--- src/service/service.rs | 37 ++++++++++++---------- src/ws/client/mod.rs | 14 +++++++++ src/ws/client/service.rs | 11 ++++--- src/ws/service.rs | 6 ++-- src/ws/transport.rs | 10 +++--- test-server/src/lib.rs | 12 ++++--- tests/test_server.rs | 21 ++++++++----- 21 files changed, 222 insertions(+), 139 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9d55b85c..594ab88d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ actix-codec = "0.1.1" actix-connector = { git="https://github.com/actix/actix-net.git" } actix-service = { git="https://github.com/actix/actix-net.git" } actix-utils = { git="https://github.com/actix/actix-net.git" } +actix-server-config = { git="https://github.com/actix/actix-net.git" } base64 = "0.10" backtrace = "0.3" @@ -92,6 +93,7 @@ actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] #actix-connector = { version = "0.3.0", features=["ssl"] } actix-connector = { git="https://github.com/actix/actix-net.git", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } + env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } diff --git a/examples/echo.rs b/examples/echo.rs index 03d5b470..eb4aaa65 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,3 +1,5 @@ +use std::{env, io}; + use actix_http::HttpMessage; use actix_http::{h1, Request, Response}; use actix_server::Server; @@ -6,9 +8,8 @@ use bytes::Bytes; use futures::Future; use http::header::HeaderValue; use log::info; -use std::env; -fn main() { +fn main() -> io::Result<()> { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); @@ -27,7 +28,6 @@ fn main() { }) }) .map(|_| ()) - }) - .unwrap() - .run(); + })? + .run() } diff --git a/examples/echo2.rs b/examples/echo2.rs index 2fd9cbcf..3bb8d83d 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -1,3 +1,5 @@ +use std::{env, io}; + use actix_http::http::HeaderValue; use actix_http::HttpMessage; use actix_http::{h1, Error, Request, Response}; @@ -6,7 +8,6 @@ use actix_service::NewService; use bytes::Bytes; use futures::Future; use log::info; -use std::env; fn handle_request(mut req: Request) -> impl Future { req.body().limit(512).from_err().and_then(|bytes: Bytes| { @@ -17,7 +18,7 @@ fn handle_request(mut req: Request) -> impl Future io::Result<()> { env::set_var("RUST_LOG", "echo=info"); env_logger::init(); @@ -29,7 +30,6 @@ fn main() { .server_hostname("localhost") .finish(|_req: Request| handle_request(_req)) .map(|_| ()) - }) - .unwrap() - .run(); + })? + .run() } diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 5bbc3be9..ff397785 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -1,3 +1,5 @@ +use std::{env, io}; + use actix_codec::Framed; use actix_http::{h1, Response, SendResponse, ServiceConfig}; use actix_server::Server; @@ -5,9 +7,8 @@ use actix_service::NewService; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use futures::Future; -use std::env; -fn main() { +fn main() -> io::Result<()> { env::set_var("RUST_LOG", "framed_hello=info"); env_logger::init(); @@ -20,7 +21,6 @@ fn main() { .map_err(|_| ()) .map(|_| ()) }) - }) - .unwrap() - .run(); + })? + .run() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index e0c322a2..05d69fed 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,12 +1,13 @@ +use std::{env, io}; + use actix_http::{h1, Response}; use actix_server::Server; use actix_service::NewService; use futures::future; use http::header::HeaderValue; use log::info; -use std::env; -fn main() { +fn main() -> io::Result<()> { env::set_var("RUST_LOG", "hello_world=info"); env_logger::init(); @@ -23,7 +24,6 @@ fn main() { future::ok::<_, ()>(res.body("Hello world!")) }) .map(|_| ()) - }) - .unwrap() - .run(); + })? + .run() } diff --git a/src/client/connector.rs b/src/client/connector.rs index d1fd802e..5eb85ba6 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -133,8 +133,11 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service + Clone - { + ) -> impl Service< + Request = Connect, + Response = impl Connection, + Error = ConnectorError, + > + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( @@ -238,7 +241,11 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { pub(crate) tcp_pool: ConnectionPool, } @@ -246,8 +253,11 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service - + Clone, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + > + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -256,15 +266,16 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, T: Service, { + type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< - as Service>::Future, + as Service>::Future, FutureResult, ConnectorError>, >; @@ -299,12 +310,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, @@ -318,12 +329,12 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, > + Clone, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, > + Clone, @@ -336,21 +347,22 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service< - Connect, + Request = Connect, Response = (Connect, Io1, Protocol), Error = ConnectorError, >, T2: Service< - Connect, + Request = Connect, Response = (Connect, Io2, Protocol), Error = ConnectorError, >, { + type Request = Connect; type Response = EitherConnection; type Error = ConnectorError; type Future = Either< @@ -385,15 +397,23 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseA where - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io1, Protocol), + Error = ConnectorError, + >, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -411,15 +431,23 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, { - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseB where - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io2, Protocol), + Error = ConnectorError, + >, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/pool.rs b/src/client/pool.rs index 425e8939..188980cb 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -48,7 +48,11 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { pub(crate) fn new( connector: T, @@ -84,11 +88,16 @@ where } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { + type Request = Connect; type Response = IoConnection; type Error = ConnectorError; type Future = Either< diff --git a/src/client/request.rs b/src/client/request.rs index 637635d0..7e971756 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -176,7 +176,7 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 42ab33e7..a7eb96c3 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -36,14 +36,14 @@ bitflags! { } /// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher + 'static, B: MessageBody> +pub struct Dispatcher + 'static, B: MessageBody> where S::Error: Debug, { inner: Option>, } -struct InnerDispatcher + 'static, B: MessageBody> +struct InnerDispatcher + 'static, B: MessageBody> where S::Error: Debug, { @@ -67,13 +67,13 @@ enum DispatcherMessage { Error(Response<()>), } -enum State, B: MessageBody> { +enum State, B: MessageBody> { None, ServiceCall(S::Future), SendPayload(ResponseBody), } -impl, B: MessageBody> State { +impl, B: MessageBody> State { fn is_empty(&self) -> bool { if let State::None = self { true @@ -86,7 +86,7 @@ impl, B: MessageBody> State { impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -145,7 +145,7 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -465,7 +465,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/h1/service.rs b/src/h1/service.rs index 229229e6..6a36678b 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::net; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_server_config::ServerConfig as SrvConfig; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; @@ -28,14 +29,14 @@ pub struct H1Service { impl H1Service where - S: NewService, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, { /// Create new `HttpService` instance with default config. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -46,7 +47,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( + pub fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -63,24 +64,25 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Error: Debug, S::Response: Into>, S::Service: 'static, B: MessageBody, { + type Request = T; type Response = (); type Error = DispatchError; type InitError = S::InitError; type Service = H1ServiceHandler; type Future = H1ServiceResponse; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H1ServiceResponse { - fut: self.srv.new_service(&()).into_future(), + fut: self.srv.new_service(cfg).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -103,7 +105,7 @@ pub struct H1ServiceBuilder { impl H1ServiceBuilder where - S: NewService, + S: NewService, S::Error: Debug, { /// Create instance of `ServiceConfigBuilder` @@ -201,7 +203,7 @@ where pub fn finish(self, service: F) -> H1Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -217,7 +219,7 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse, B> { +pub struct H1ServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -226,7 +228,7 @@ pub struct H1ServiceResponse, B> { impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug, S::Response: Into>, @@ -253,7 +255,7 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, @@ -267,14 +269,15 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, { + type Request = T; type Response = (); type Error = DispatchError; type Future = Dispatcher; @@ -311,17 +314,18 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { + type Request = T; type Response = (Request, Framed); type Error = ParseError; type InitError = (); type Service = OneRequestService; type Future = FutureResult; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, _: &SrvConfig) -> Self::Future { ok(OneRequestService { config: self.config.clone(), _t: PhantomData, @@ -336,10 +340,11 @@ pub struct OneRequestService { _t: PhantomData, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { + type Request = T; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index be813bd5..a3c731eb 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -38,7 +38,7 @@ bitflags! { /// Dispatcher for HTTP/2 protocol pub struct Dispatcher< T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, B: MessageBody, > { flags: Flags, @@ -53,7 +53,7 @@ pub struct Dispatcher< impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -95,7 +95,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -141,20 +141,20 @@ where } } -struct ServiceResponse, B> { +struct ServiceResponse { state: ServiceResponseState, config: ServiceConfig, buffer: Option, } -enum ServiceResponseState, B> { +enum ServiceResponseState { ServiceCall(S::Future, Option>), SendPayload(SendStream, ResponseBody), } impl ServiceResponse where - S: Service + 'static, + S: Service + 'static, S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, @@ -222,7 +222,7 @@ where impl Future for ServiceResponse where - S: Service + 'static, + S: Service + 'static, S::Error: fmt::Debug, S::Response: Into>, B: MessageBody + 'static, diff --git a/src/h2/service.rs b/src/h2/service.rs index 53062994..57515d4e 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_server_config::ServerConfig as SrvConfig; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::Bytes; @@ -30,14 +31,14 @@ pub struct H2Service { impl H2Service where - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug + 'static, S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H2Service { @@ -48,7 +49,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( + pub fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -65,24 +66,25 @@ where } } -impl NewService for H2Service +impl NewService for H2Service where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { + type Request = T; type Response = (); type Error = DispatchError; type InitError = S::InitError; type Service = H2ServiceHandler; type Future = H2ServiceResponse; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H2ServiceResponse { - fut: self.srv.new_service(&()).into_future(), + fut: self.srv.new_service(cfg).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -105,7 +107,7 @@ pub struct H2ServiceBuilder { impl H2ServiceBuilder where - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug + 'static, { @@ -204,7 +206,7 @@ where pub fn finish(self, service: F) -> H2Service where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -220,7 +222,7 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse, B> { +pub struct H2ServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -229,7 +231,7 @@ pub struct H2ServiceResponse, B> { impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Response: Into>, S::Error: Debug, @@ -256,7 +258,7 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, @@ -270,14 +272,15 @@ where } } -impl Service for H2ServiceHandler +impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { + type Request = T; type Response = (); type Error = DispatchError; type Future = H2ServiceHandlerResponse; @@ -300,7 +303,11 @@ where } } -enum State + 'static, B: MessageBody> { +enum State< + T: AsyncRead + AsyncWrite, + S: Service + 'static, + B: MessageBody, +> { Incoming(Dispatcher), Handshake( Option>, @@ -312,7 +319,7 @@ enum State + 'static, B: MessageB pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, @@ -323,7 +330,7 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/lib.rs b/src/lib.rs index 74a46fd1..9d8bc50f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,8 +97,7 @@ pub use self::message::{Head, Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::Response; -pub use self::service::HttpService; -pub use self::service::{SendError, SendResponse}; +pub use self::service::{HttpService, SendError, SendResponse}; pub mod dev { //! The `actix-web` prelude for library developers diff --git a/src/service/senderror.rs b/src/service/senderror.rs index 8268c666..44d36259 100644 --- a/src/service/senderror.rs +++ b/src/service/senderror.rs @@ -22,11 +22,12 @@ where } } -impl NewService)>> for SendError +impl NewService for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { + type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); @@ -38,11 +39,12 @@ where } } -impl Service)>> for SendError +impl Service for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, { + type Request = Result)>; type Response = R; type Error = (E, Framed); type Future = Either)>, SendErrorFut>; @@ -128,11 +130,12 @@ where } } -impl NewService<(Response, Framed)> for SendResponse +impl NewService for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { + type Request = (Response, Framed); type Response = Framed; type Error = Error; type InitError = (); @@ -144,11 +147,12 @@ where } } -impl Service<(Response, Framed)> for SendResponse +impl Service for SendResponse where T: AsyncRead + AsyncWrite, B: MessageBody, { + type Request = (Response, Framed); type Response = Framed; type Error = Error; type Future = SendResponseFut; diff --git a/src/service/service.rs b/src/service/service.rs index 3d0009ed..0fa5d665 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::{fmt, io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; +use actix_server_config::ServerConfig as SrvConfig; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -27,14 +28,14 @@ pub struct HttpService { impl HttpService where - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug + 'static, S::Response: Into>, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); HttpService { @@ -45,7 +46,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( + pub fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -62,24 +63,25 @@ where } } -impl NewService for HttpService +impl NewService for HttpService where T: AsyncRead + AsyncWrite + 'static, - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { + type Request = T; type Response = (); type Error = DispatchError; type InitError = S::InitError; type Service = HttpServiceHandler; type Future = HttpServiceResponse; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, cfg: &SrvConfig) -> Self::Future { HttpServiceResponse { - fut: self.srv.new_service(&()).into_future(), + fut: self.srv.new_service(cfg).into_future(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -102,7 +104,7 @@ pub struct HttpServiceBuilder { impl HttpServiceBuilder where - S: NewService, + S: NewService, S::Service: 'static, S::Error: Debug + 'static, { @@ -220,7 +222,7 @@ where pub fn finish(self, service: F) -> HttpService where B: MessageBody, - F: IntoNewService, + F: IntoNewService, { let cfg = ServiceConfig::new( self.keep_alive, @@ -236,7 +238,7 @@ where } #[doc(hidden)] -pub struct HttpServiceResponse, B> { +pub struct HttpServiceResponse, B> { fut: ::Future, cfg: Option, _t: PhantomData<(T, B)>, @@ -245,7 +247,7 @@ pub struct HttpServiceResponse, B> { impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite, - S: NewService, + S: NewService, S::Service: 'static, S::Response: Into>, S::Error: Debug, @@ -272,7 +274,7 @@ pub struct HttpServiceHandler { impl HttpServiceHandler where - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, @@ -286,14 +288,15 @@ where } } -impl Service for HttpServiceHandler +impl Service for HttpServiceHandler where T: AsyncRead + AsyncWrite + 'static, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { + type Request = T; type Response = (); type Error = DispatchError; type Future = HttpServiceHandlerResponse; @@ -317,7 +320,7 @@ where } } -enum State + 'static, B: MessageBody> +enum State + 'static, B: MessageBody> where S::Error: fmt::Debug, T: AsyncRead + AsyncWrite + 'static, @@ -331,7 +334,7 @@ where pub struct HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite + 'static, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, @@ -344,7 +347,7 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; impl Future for HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite, - S: Service + 'static, + S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody, diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 8e59e846..12c7229b 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -25,6 +25,20 @@ impl Protocol { } } + fn is_http(self) -> bool { + match self { + Protocol::Https | Protocol::Http => true, + _ => false, + } + } + + fn is_secure(self) -> bool { + match self { + Protocol::Https | Protocol::Wss => true, + _ => false, + } + } + fn port(self) -> u16 { match self { Protocol::Http | Protocol::Ws => 80, diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index c48b6e0c..586873d1 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -26,7 +26,7 @@ pub type DefaultClient = Client; /// WebSocket's client pub struct Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { connector: T, @@ -34,7 +34,7 @@ where impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -51,7 +51,7 @@ impl Default for Client { impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -61,12 +61,13 @@ where } } -impl Service for Client +impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { + type Request = Connect; type Response = Framed; type Error = ClientError; type Future = Either< diff --git a/src/ws/service.rs b/src/ws/service.rs index bbd9f782..f3b06605 100644 --- a/src/ws/service.rs +++ b/src/ws/service.rs @@ -20,7 +20,8 @@ impl Default for VerifyWebSockets { } } -impl NewService<(Request, Framed)> for VerifyWebSockets { +impl NewService for VerifyWebSockets { + type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); @@ -32,7 +33,8 @@ impl NewService<(Request, Framed)> for VerifyWebSockets { } } -impl Service<(Request, Framed)> for VerifyWebSockets { +impl Service for VerifyWebSockets { + type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type Future = FutureResult; diff --git a/src/ws/transport.rs b/src/ws/transport.rs index 6a4f4d22..da7782be 100644 --- a/src/ws/transport.rs +++ b/src/ws/transport.rs @@ -7,7 +7,7 @@ use super::{Codec, Frame, Message}; pub struct Transport where - S: Service + 'static, + S: Service + 'static, T: AsyncRead + AsyncWrite, { inner: FramedTransport, @@ -16,17 +16,17 @@ where impl Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Transport { inner: FramedTransport::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Transport { inner: FramedTransport::new(framed, service), } @@ -36,7 +36,7 @@ where impl Future for Transport where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index dd7bc362..6b70fbc1 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -56,7 +56,8 @@ impl TestServer { pub fn new( factory: F, ) -> TestServerRuntime< - impl Service + Clone, + impl Service + + Clone, > { let (tx, rx) = mpsc::channel(); @@ -88,8 +89,11 @@ impl TestServer { } fn new_connector( - ) -> impl Service + Clone - { + ) -> impl Service< + Request = Connect, + Response = impl Connection, + Error = ConnectorError, + > + Clone { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -202,7 +206,7 @@ impl TestServerRuntime { impl TestServerRuntime where - T: Service + Clone, + T: Service + Clone, T::Response: Connection, { /// Connect to websocket server at a given path diff --git a/tests/test_server.rs b/tests/test_server.rs index 4cffdd09..b7cd5557 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -565,17 +565,22 @@ fn test_body_chunked_implicit() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +use actix_server_config::ServerConfig; +use actix_service::fn_cfg_factory; + #[test] fn test_response_http_error_handling() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) - }) + h1::H1Service::new(fn_cfg_factory(|_: &ServerConfig| { + Ok::<_, ()>(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }) + })) }); let req = srv.get().finish().unwrap(); From aadcdaa3d6b109bc169e2d083bad8817594b789f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 07:39:34 -0800 Subject: [PATCH 1020/1635] add resource map, it allow to check if router has resource and it allows to generate urls for named resources --- Cargo.toml | 3 + actix-files/src/lib.rs | 31 +++++++ src/app.rs | 24 +++++- src/config.rs | 17 +++- src/error.rs | 20 +++++ src/lib.rs | 4 +- src/request.rs | 46 ++++++++++ src/resource.rs | 2 +- src/rmap.rs | 188 +++++++++++++++++++++++++++++++++++++++++ src/scope.rs | 19 ++++- src/server.rs | 8 +- src/service.rs | 4 +- src/test.rs | 13 ++- 13 files changed, 361 insertions(+), 18 deletions(-) create mode 100644 src/error.rs create mode 100644 src/rmap.rs diff --git a/Cargo.toml b/Cargo.toml index f9e2266e..5e2027c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,11 +72,13 @@ actix-utils = { git = "https://github.com/actix/actix-net.git" } actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } actix-server = { git = "https://github.com/actix/actix-net.git" } +#actix-router = { path="../actix-net/router" } bytes = "0.4" derive_more = "0.14" encoding = "0.2" futures = "0.1" +hashbrown = "0.1.8" log = "0.4" lazy_static = "1.2" mime = "0.3" @@ -89,6 +91,7 @@ serde_json = "1.0" serde_urlencoded = "^0.5.3" threadpool = "1.7" time = "0.1" +url = { version="1.7", features=["query_encoding"] } # middlewares # actix-session = { path="session", optional = true } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 5dd98dcc..17efdd81 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -1090,4 +1090,35 @@ mod tests { // assert_eq!(response.status(), StatusCode::OK); // } + #[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/app.rs b/src/app.rs index c1c019a3..b4f6e535 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,6 +16,7 @@ use futures::{Async, Future, IntoFuture, Poll}; use crate::config::AppConfig; use crate::guard::Guard; use crate::resource::Resource; +use crate::rmap::ResourceMap; use crate::route::Route; use crate::service::{ HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, @@ -449,19 +450,29 @@ where .into_iter() .for_each(|mut srv| srv.register(&mut config)); - // set factory + let mut rmap = ResourceMap::new(ResourceDef::new("")); + + // complete pipeline creation *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { default, services: Rc::new( config .into_services() .into_iter() - .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards))) + .map(|(mut rdef, srv, guards, nested)| { + rmap.add(&mut rdef, nested); + (rdef, srv, RefCell::new(guards)) + }) .collect(), ), }); + // complete ResourceMap tree creation + let rmap = Rc::new(rmap); + rmap.finish(rmap.clone()); + AppInit { + rmap, chain: self.chain, state: self.state, extensions: Rc::new(RefCell::new(Rc::new(self.extensions))), @@ -561,8 +572,7 @@ impl

    Future for AppRoutingFactoryResponse

    { .fold(Router::build(), |mut router, item| { match item { CreateAppRoutingItem::Service(path, guards, service) => { - router.rdef(path, service); - router.set_user_data(guards); + router.rdef(path, service).2 = guards; } CreateAppRoutingItem::Future(_, _, _) => unreachable!(), } @@ -683,6 +693,7 @@ where C: NewService>, { chain: C, + rmap: Rc, state: Vec>, extensions: Rc>>, } @@ -702,6 +713,7 @@ where chain: self.chain.new_service(&()), state: self.state.iter().map(|s| s.construct()).collect(), extensions: self.extensions.clone(), + rmap: self.rmap.clone(), } } } @@ -712,6 +724,7 @@ where C: NewService, InitError = ()>, { chain: C::Future, + rmap: Rc, state: Vec>, extensions: Rc>>, } @@ -744,6 +757,7 @@ where Ok(Async::Ready(AppInitService { chain, + rmap: self.rmap.clone(), extensions: self.extensions.borrow().clone(), })) } @@ -755,6 +769,7 @@ where C: Service>, { chain: C, + rmap: Rc, extensions: Rc, } @@ -774,6 +789,7 @@ where let req = ServiceRequest::new( Path::new(Url::new(req.uri().clone())), req, + self.rmap.clone(), self.extensions.clone(), ); self.chain.call(req) diff --git a/src/config.rs b/src/config.rs index 483b0a50..4afd213c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,6 +5,7 @@ use actix_router::ResourceDef; use actix_service::{boxed, IntoNewService, NewService}; use crate::guard::Guard; +use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; type Guards = Vec>; @@ -18,7 +19,12 @@ pub struct AppConfig

    { host: String, root: bool, default: Rc>, - services: Vec<(ResourceDef, HttpNewService

    , Option)>, + services: Vec<( + ResourceDef, + HttpNewService

    , + Option, + Option>, + )>, } impl AppConfig

    { @@ -46,7 +52,12 @@ impl AppConfig

    { pub(crate) fn into_services( self, - ) -> Vec<(ResourceDef, HttpNewService

    , Option)> { + ) -> Vec<( + ResourceDef, + HttpNewService

    , + Option, + Option>, + )> { self.services } @@ -85,6 +96,7 @@ impl AppConfig

    { rdef: ResourceDef, guards: Option>>, service: F, + nested: Option>, ) where F: IntoNewService>, S: NewService< @@ -98,6 +110,7 @@ impl AppConfig

    { rdef, boxed::new_service(service.into_new_service()), guards, + nested, )); } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..d1c0d3ca --- /dev/null +++ b/src/error.rs @@ -0,0 +1,20 @@ +pub use actix_http::error::*; +use derive_more::{Display, From}; +use url::ParseError as UrlParseError; + +/// Errors which can occur when attempting to generate resource uri. +#[derive(Debug, PartialEq, Display, From)] +pub enum UrlGenerationError { + /// Resource not found + #[display(fmt = "Resource not found")] + ResourceNotFound, + /// Not all path pattern covered + #[display(fmt = "Not all path pattern covered")] + NotEnoughElements, + /// URL parse error + #[display(fmt = "{}", _0)] + ParseError(UrlParseError), +} + +/// `InternalServerError` for `UrlGeneratorError` +impl ResponseError for UrlGenerationError {} diff --git a/src/lib.rs b/src/lib.rs index a61387e8..94bf1ba7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,11 +6,13 @@ mod handler; // mod info; pub mod blocking; mod config; +pub mod error; pub mod guard; pub mod middleware; mod request; mod resource; mod responder; +mod rmap; mod route; mod scope; mod server; @@ -27,7 +29,7 @@ pub use actix_web_codegen::*; // re-export for convenience pub use actix_http::Response as HttpResponse; -pub use actix_http::{error, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{http, Error, HttpMessage, ResponseError, Result}; pub use crate::app::App; pub use crate::extract::FromRequest; diff --git a/src/request.rs b/src/request.rs index 1c86cac3..6655f1ba 100644 --- a/src/request.rs +++ b/src/request.rs @@ -7,7 +7,9 @@ use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; +use crate::error::UrlGenerationError; use crate::extract::FromRequest; +use crate::rmap::ResourceMap; use crate::service::ServiceFromRequest; #[derive(Clone)] @@ -15,6 +17,7 @@ use crate::service::ServiceFromRequest; pub struct HttpRequest { pub(crate) head: Message, pub(crate) path: Path, + rmap: Rc, extensions: Rc, } @@ -23,11 +26,13 @@ impl HttpRequest { pub(crate) fn new( head: Message, path: Path, + rmap: Rc, extensions: Rc, ) -> HttpRequest { HttpRequest { head, path, + rmap, extensions, } } @@ -93,6 +98,47 @@ impl HttpRequest { &self.extensions } + /// Generate url for named resource + /// + /// ```rust + /// # extern crate actix_web; + /// # use actix_web::{App, HttpRequest, HttpResponse, http}; + /// # + /// fn index(req: HttpRequest) -> HttpResponse { + /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource + /// HttpResponse::Ok().into() + /// } + /// + /// fn main() { + /// let app = App::new() + /// .resource("/test/{one}/{two}/{three}", |r| { + /// r.name("foo"); // <- set resource name, then it could be used in `url_for` + /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); + /// }) + /// .finish(); + /// } + /// ``` + pub fn url_for( + &self, + name: &str, + elements: U, + ) -> Result + where + U: IntoIterator, + I: AsRef, + { + self.rmap.url_for(&self, name, elements) + } + + /// Generate url for named resource + /// + /// This method is similar to `HttpRequest::url_for()` but it can be used + /// for urls that do not contain variable parts. + pub fn url_for_static(&self, name: &str) -> Result { + const NO_PARAMS: [&str; 0] = []; + self.url_for(name, &NO_PARAMS) + } + // /// Get *ConnectionInfo* for the correct request. // #[inline] // pub fn connection_info(&self) -> Ref { diff --git a/src/resource.rs b/src/resource.rs index 13afff70..a1177ca0 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -288,7 +288,7 @@ where } else { ResourceDef::new(&self.rdef) }; - config.register_service(rdef, guards, self) + config.register_service(rdef, guards, self, None) } } diff --git a/src/rmap.rs b/src/rmap.rs new file mode 100644 index 00000000..4922084b --- /dev/null +++ b/src/rmap.rs @@ -0,0 +1,188 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use actix_router::ResourceDef; +use hashbrown::HashMap; +use url::Url; + +use crate::error::UrlGenerationError; +use crate::request::HttpRequest; + +#[derive(Clone, Debug)] +pub struct ResourceMap { + root: ResourceDef, + parent: RefCell>>, + named: HashMap, + patterns: Vec<(ResourceDef, Option>)>, +} + +impl ResourceMap { + pub fn new(root: ResourceDef) -> Self { + ResourceMap { + root, + parent: RefCell::new(None), + named: HashMap::new(), + patterns: Vec::new(), + } + } + + pub fn add(&mut self, pattern: &mut ResourceDef, nested: Option>) { + pattern.set_id(self.patterns.len() as u16); + self.patterns.push((pattern.clone(), nested)); + if !pattern.name().is_empty() { + self.named + .insert(pattern.name().to_string(), pattern.clone()); + } + } + + pub(crate) fn finish(&self, current: Rc) { + for (_, nested) in &self.patterns { + if let Some(ref nested) = nested { + *nested.parent.borrow_mut() = Some(current.clone()) + } + } + } +} + +impl ResourceMap { + /// Generate url for named resource + /// + /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. + /// url_for) for detailed information. + pub fn url_for( + &self, + req: &HttpRequest, + name: &str, + elements: U, + ) -> Result + where + U: IntoIterator, + I: AsRef, + { + let mut path = String::new(); + let mut elements = elements.into_iter(); + + if self.patterns_for(name, &mut path, &mut elements)?.is_some() { + if path.starts_with('/') { + // let conn = req.connection_info(); + // Ok(Url::parse(&format!( + // "{}://{}{}", + // conn.scheme(), + // conn.host(), + // path + // ))?) + unimplemented!() + } else { + Ok(Url::parse(&path)?) + } + } else { + Err(UrlGenerationError::ResourceNotFound) + } + } + + pub fn has_resource(&self, path: &str) -> bool { + let path = if path.is_empty() { "/" } else { path }; + + for (pattern, rmap) in &self.patterns { + if let Some(ref rmap) = rmap { + if let Some(plen) = pattern.is_prefix_match(path) { + return rmap.has_resource(&path[plen..]); + } + } else if pattern.is_match(path) { + return true; + } + } + false + } + + fn patterns_for( + &self, + name: &str, + path: &mut String, + elements: &mut U, + ) -> Result, UrlGenerationError> + where + U: Iterator, + I: AsRef, + { + if self.pattern_for(name, path, elements)?.is_some() { + Ok(Some(())) + } else { + self.parent_pattern_for(name, path, elements) + } + } + + fn pattern_for( + &self, + name: &str, + path: &mut String, + elements: &mut U, + ) -> Result, UrlGenerationError> + where + U: Iterator, + I: AsRef, + { + if let Some(pattern) = self.named.get(name) { + self.fill_root(path, elements)?; + if pattern.resource_path(path, elements) { + Ok(Some(())) + } else { + Err(UrlGenerationError::NotEnoughElements) + } + } else { + for (_, rmap) in &self.patterns { + if let Some(ref rmap) = rmap { + if rmap.pattern_for(name, path, elements)?.is_some() { + return Ok(Some(())); + } + } + } + Ok(None) + } + } + + fn fill_root( + &self, + path: &mut String, + elements: &mut U, + ) -> Result<(), UrlGenerationError> + where + U: Iterator, + I: AsRef, + { + if let Some(ref parent) = *self.parent.borrow() { + parent.fill_root(path, elements)?; + } + if self.root.resource_path(path, elements) { + Ok(()) + } else { + Err(UrlGenerationError::NotEnoughElements) + } + } + + fn parent_pattern_for( + &self, + name: &str, + path: &mut String, + elements: &mut U, + ) -> Result, UrlGenerationError> + where + U: Iterator, + I: AsRef, + { + if let Some(ref parent) = *self.parent.borrow() { + if let Some(pattern) = parent.named.get(name) { + self.fill_root(path, elements)?; + if pattern.resource_path(path, elements) { + Ok(Some(())) + } else { + Err(UrlGenerationError::NotEnoughElements) + } + } else { + parent.parent_pattern_for(name, path, elements) + } + } else { + Ok(None) + } + } +} diff --git a/src/scope.rs b/src/scope.rs index 2c2e3c2b..15c652c8 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -13,6 +13,7 @@ use futures::{Async, Poll}; use crate::dev::{AppConfig, HttpServiceFactory}; use crate::guard::Guard; use crate::resource::Resource; +use crate::rmap::ResourceMap; use crate::route::Route; use crate::service::{ ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, @@ -237,35 +238,46 @@ where > + 'static, { fn register(self, config: &mut AppConfig

    ) { + // update default resource if needed if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); } - // register services + // register nested services let mut cfg = config.clone_config(); self.services .into_iter() .for_each(|mut srv| srv.register(&mut cfg)); + let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); + + // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { default: self.default.clone(), services: Rc::new( cfg.into_services() .into_iter() - .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards))) + .map(|(mut rdef, srv, guards, nested)| { + rmap.add(&mut rdef, nested); + (rdef, srv, RefCell::new(guards)) + }) .collect(), ), }); + // get guards let guards = if self.guards.is_empty() { None } else { Some(self.guards) }; + + // register final service config.register_service( ResourceDef::root_prefix(&self.rdef), guards, self.endpoint, + Some(Rc::new(rmap)), ) } } @@ -367,8 +379,7 @@ impl

    Future for ScopeFactoryResponse

    { .fold(Router::build(), |mut router, item| { match item { CreateScopeServiceItem::Service(path, guards, service) => { - router.rdef(path, service); - router.set_user_data(guards); + router.rdef(path, service).2 = guards; } CreateScopeServiceItem::Future(_, _, _) => unreachable!(), } diff --git a/src/server.rs b/src/server.rs index d6d88d00..d3ae5b2b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -230,7 +230,7 @@ where /// /// HttpServer does not change any configuration for TcpListener, /// it needs to be configured before passing it to listen() method. - pub fn listen(mut self, lst: net::TcpListener) -> Self { + pub fn listen(mut self, lst: net::TcpListener) -> io::Result { let cfg = self.config.clone(); let factory = self.factory.clone(); let addr = lst.local_addr().unwrap(); @@ -248,9 +248,9 @@ where ServiceConfig::new(c.keep_alive, c.client_timeout, 0); HttpService::with_config(service_config, factory()) }, - )); + )?); - self + Ok(self) } #[cfg(feature = "tls")] @@ -328,7 +328,7 @@ where let sockets = self.bind2(addr)?; for lst in sockets { - self = self.listen(lst); + self = self.listen(lst)?; } Ok(self) diff --git a/src/service.rs b/src/service.rs index e2213060..ba811458 100644 --- a/src/service.rs +++ b/src/service.rs @@ -15,6 +15,7 @@ use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::AppConfig; use crate::request::HttpRequest; +use crate::rmap::ResourceMap; pub trait HttpServiceFactory

    { fn register(self, config: &mut AppConfig

    ); @@ -58,12 +59,13 @@ impl

    ServiceRequest

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

    , + rmap: Rc, extensions: Rc, ) -> Self { let (head, payload) = request.into_parts(); ServiceRequest { payload, - req: HttpRequest::new(head, path, extensions), + req: HttpRequest::new(head, path, rmap, extensions), } } diff --git a/src/test.rs b/src/test.rs index ccc4b38e..e4cdefbe 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,13 +6,14 @@ use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{Extensions, PayloadStream, Request}; -use actix_router::{Path, Url}; +use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; use futures::Future; use crate::request::HttpRequest; +use crate::rmap::ResourceMap; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; thread_local! { @@ -135,6 +136,7 @@ where pub struct TestRequest { req: HttpTestRequest, extensions: Extensions, + rmap: ResourceMap, } impl Default for TestRequest { @@ -142,6 +144,7 @@ impl Default for TestRequest { TestRequest { req: HttpTestRequest::default(), extensions: Extensions::new(), + rmap: ResourceMap::new(ResourceDef::new("")), } } } @@ -152,6 +155,7 @@ impl TestRequest { TestRequest { req: HttpTestRequest::default().uri(path).take(), extensions: Extensions::new(), + rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -160,6 +164,7 @@ impl TestRequest { TestRequest { req: HttpTestRequest::default().set(hdr).take(), extensions: Extensions::new(), + rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -172,6 +177,7 @@ impl TestRequest { TestRequest { req: HttpTestRequest::default().header(key, value).take(), extensions: Extensions::new(), + rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -180,6 +186,7 @@ impl TestRequest { TestRequest { req: HttpTestRequest::default().method(Method::GET).take(), extensions: Extensions::new(), + rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -188,6 +195,7 @@ impl TestRequest { TestRequest { req: HttpTestRequest::default().method(Method::POST).take(), extensions: Extensions::new(), + rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -244,6 +252,7 @@ impl TestRequest { ServiceRequest::new( Path::new(Url::new(req.uri().clone())), req, + Rc::new(self.rmap), Rc::new(self.extensions), ) } @@ -260,6 +269,7 @@ impl TestRequest { ServiceRequest::new( Path::new(Url::new(req.uri().clone())), req, + Rc::new(self.rmap), Rc::new(self.extensions), ) .into_request() @@ -272,6 +282,7 @@ impl TestRequest { let req = ServiceRequest::new( Path::new(Url::new(req.uri().clone())), req, + Rc::new(self.rmap), Rc::new(self.extensions), ); ServiceFromRequest::new(req, None) From fde55ffa14884e045590efb7135171f722cbde1d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 09:49:11 -0800 Subject: [PATCH 1021/1635] revert generic request parameter for service; support ServerConfig as new factory config --- Cargo.toml | 2 +- src/app.rs | 419 +++-------------------------- src/app_service.rs | 439 +++++++++++++++++++++++++++++++ src/config.rs | 4 +- src/handler.rs | 18 +- src/lib.rs | 1 + src/middleware/compress.rs | 17 +- src/middleware/defaultheaders.rs | 10 +- src/middleware/logger.rs | 18 +- src/resource.rs | 32 ++- src/route.rs | 30 ++- src/scope.rs | 21 +- src/server.rs | 13 +- src/test.rs | 17 +- 14 files changed, 581 insertions(+), 460 deletions(-) create mode 100644 src/app_service.rs diff --git a/Cargo.toml b/Cargo.toml index 5e2027c5..dbc0a65d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ actix-utils = { git = "https://github.com/actix/actix-net.git" } actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } actix-server = { git = "https://github.com/actix/actix-net.git" } -#actix-router = { path="../actix-net/router" } +actix-server-config = { git = "https://github.com/actix/actix-net.git" } bytes = "0.4" derive_more = "0.14" diff --git a/src/app.rs b/src/app.rs index b4f6e535..76a3a1ce 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,37 +3,30 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody}; -use actix_http::{Extensions, PayloadStream, Request, Response}; -use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; -use actix_service::boxed::{self, BoxedNewService, BoxedService}; +use actix_http::{Extensions, PayloadStream}; +use actix_server_config::ServerConfig; +use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ - fn_service, AndThenNewService, ApplyTransform, IntoNewService, IntoTransform, - NewService, Service, Transform, + ApplyTransform, IntoNewService, IntoTransform, NewService, Transform, }; -use futures::future::{ok, Either, FutureResult}; -use futures::{Async, Future, IntoFuture, Poll}; +use futures::IntoFuture; -use crate::config::AppConfig; -use crate::guard::Guard; +use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; use crate::resource::Resource; -use crate::rmap::ResourceMap; use crate::route::Route; use crate::service::{ HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; -use crate::state::{State, StateFactory, StateFactoryResult}; +use crate::state::{State, StateFactory}; -type Guards = Vec>; -type HttpService

    = BoxedService, ServiceResponse, ()>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; -type BoxedResponse = Box>; /// Application builder - structure that follows the builder pattern /// for building application instances. pub struct App where - T: NewService>, + T: NewService>, { chain: T, extensions: Extensions, @@ -58,7 +51,7 @@ impl App where P: 'static, T: NewService< - ServiceRequest, + Request = ServiceRequest, Response = ServiceRequest

    , Error = (), InitError = (), @@ -121,7 +114,7 @@ where P, B, impl NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -130,12 +123,12 @@ where where M: Transform< AppRouting

    , - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoTransform, ServiceRequest

    >, + F: IntoTransform>, { let fref = Rc::new(RefCell::new(None)); let endpoint = ApplyTransform::new(mw, AppEntry::new(fref.clone())); @@ -159,7 +152,7 @@ where ) -> App< P1, impl NewService< - ServiceRequest, + Request = ServiceRequest, Response = ServiceRequest, Error = (), InitError = (), @@ -167,12 +160,12 @@ where > where C: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceRequest, Error = (), InitError = (), >, - F: IntoNewService>, + F: IntoNewService, { let chain = self.chain.and_then(chain.into_new_service()); App { @@ -264,7 +257,7 @@ where P: 'static, B: MessageBody, T: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -324,7 +317,7 @@ where P, B1, impl NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -333,13 +326,13 @@ where where M: Transform< T::Service, - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, B1: MessageBody, - F: IntoTransform>, + F: IntoTransform, { let endpoint = ApplyTransform::new(mw, self.endpoint); AppRouter { @@ -362,7 +355,7 @@ where where F: FnOnce(Resource

    ) -> Resource, U: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -413,391 +406,39 @@ where } } -impl - IntoNewService, T>, Request> +impl IntoNewService, ServerConfig> for AppRouter where T: NewService< - ServiceRequest

    , + Request = ServiceRequest

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

    , Error = (), InitError = (), >, { - fn into_new_service(self) -> AndThenNewService, T> { - // update resource default service - let default = self.default.unwrap_or_else(|| { - Rc::new(boxed::new_service(fn_service(|req: ServiceRequest

    | { - Ok(req.into_response(Response::NotFound().finish())) - }))) - }); - - let mut config = AppConfig::new( - "127.0.0.1:8080".parse().unwrap(), - "localhost:8080".to_owned(), - false, - default.clone(), - ); - - // register services - self.services - .into_iter() - .for_each(|mut srv| srv.register(&mut config)); - - let mut rmap = ResourceMap::new(ResourceDef::new("")); - - // complete pipeline creation - *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { - default, - services: Rc::new( - config - .into_services() - .into_iter() - .map(|(mut rdef, srv, guards, nested)| { - rmap.add(&mut rdef, nested); - (rdef, srv, RefCell::new(guards)) - }) - .collect(), - ), - }); - - // complete ResourceMap tree creation - let rmap = Rc::new(rmap); - rmap.finish(rmap.clone()); - + fn into_new_service(self) -> AppInit { AppInit { - rmap, chain: self.chain, state: self.state, + endpoint: self.endpoint, + services: RefCell::new(self.services), + default: self.default, + factory_ref: self.factory_ref, extensions: Rc::new(RefCell::new(Rc::new(self.extensions))), } - .and_then(self.endpoint) - } -} - -pub struct AppRoutingFactory

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

    { - type Response = ServiceResponse; - type Error = (); - type InitError = (); - type Service = AppRouting

    ; - type Future = AppRoutingFactoryResponse

    ; - - fn new_service(&self, _: &()) -> Self::Future { - AppRoutingFactoryResponse { - fut: self - .services - .iter() - .map(|(path, service, guards)| { - CreateAppRoutingItem::Future( - Some(path.clone()), - guards.borrow_mut().take(), - service.new_service(&()), - ) - }) - .collect(), - default: None, - default_fut: Some(self.default.new_service(&())), - } - } -} - -type HttpServiceFut

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

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

    { - Future(Option, Option, HttpServiceFut

    ), - Service(ResourceDef, Option, HttpService

    ), -} - -impl

    Future for AppRoutingFactoryResponse

    { - type Item = AppRouting

    ; - type Error = (); - - fn poll(&mut self) -> Poll { - let mut done = true; - - if let Some(ref mut fut) = self.default_fut { - match fut.poll()? { - Async::Ready(default) => self.default = Some(default), - Async::NotReady => done = false, - } - } - - // poll http services - for item in &mut self.fut { - let res = match item { - CreateAppRoutingItem::Future( - ref mut path, - ref mut guards, - ref mut fut, - ) => match fut.poll()? { - Async::Ready(service) => { - Some((path.take().unwrap(), guards.take(), service)) - } - Async::NotReady => { - done = false; - None - } - }, - CreateAppRoutingItem::Service(_, _, _) => continue, - }; - - if let Some((path, guards, service)) = res { - *item = CreateAppRoutingItem::Service(path, guards, service); - } - } - - if done { - let router = self - .fut - .drain(..) - .fold(Router::build(), |mut router, item| { - match item { - CreateAppRoutingItem::Service(path, guards, service) => { - router.rdef(path, service).2 = guards; - } - CreateAppRoutingItem::Future(_, _, _) => unreachable!(), - } - router - }); - Ok(Async::Ready(AppRouting { - ready: None, - router: router.finish(), - default: self.default.take(), - })) - } else { - Ok(Async::NotReady) - } - } -} - -pub struct AppRouting

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

    , ResourceInfo)>, - default: Option>, -} - -impl

    Service> for AppRouting

    { - type Response = ServiceResponse; - type Error = (); - type Future = Either>; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - if self.ready.is_none() { - Ok(Async::Ready(())) - } else { - Ok(Async::NotReady) - } - } - - fn call(&mut self, mut req: ServiceRequest

    ) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { - if let Some(ref guards) = guards { - for f in guards { - if !f.check(req.head()) { - return false; - } - } - } - true - }); - - if let Some((srv, _info)) = res { - Either::A(srv.call(req)) - } else if let Some(ref mut default) = self.default { - Either::A(default.call(req)) - } else { - let req = req.into_request(); - Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) - } - } -} - -#[doc(hidden)] -/// Wrapper service for routing -pub struct AppEntry

    { - factory: Rc>>>, -} - -impl

    AppEntry

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

    { - type Response = ServiceResponse; - type Error = (); - type InitError = (); - type Service = AppRouting

    ; - type Future = AppRoutingFactoryResponse

    ; - - fn new_service(&self, _: &()) -> Self::Future { - self.factory.borrow_mut().as_mut().unwrap().new_service(&()) - } -} - -#[doc(hidden)] -pub struct AppChain; - -impl NewService for AppChain { - type Response = ServiceRequest; - type Error = (); - type InitError = (); - type Service = AppChain; - type Future = FutureResult; - - fn new_service(&self, _: &()) -> Self::Future { - ok(AppChain) - } -} - -impl Service for AppChain { - type Response = ServiceRequest; - type Error = (); - type Future = FutureResult; - - #[inline] - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - #[inline] - fn call(&mut self, req: ServiceRequest) -> Self::Future { - ok(req) - } -} - -/// Service factory to convert `Request` to a `ServiceRequest`. -/// It also executes state factories. -pub struct AppInit -where - C: NewService>, -{ - chain: C, - rmap: Rc, - state: Vec>, - extensions: Rc>>, -} - -impl NewService for AppInit -where - C: NewService, InitError = ()>, -{ - type Response = ServiceRequest

    ; - type Error = C::Error; - type InitError = C::InitError; - type Service = AppInitService; - type Future = AppInitResult; - - fn new_service(&self, _: &()) -> Self::Future { - AppInitResult { - chain: self.chain.new_service(&()), - state: self.state.iter().map(|s| s.construct()).collect(), - extensions: self.extensions.clone(), - rmap: self.rmap.clone(), - } - } -} - -#[doc(hidden)] -pub struct AppInitResult -where - C: NewService, InitError = ()>, -{ - chain: C::Future, - rmap: Rc, - state: Vec>, - extensions: Rc>>, -} - -impl Future for AppInitResult -where - C: NewService, InitError = ()>, -{ - type Item = AppInitService; - type Error = C::InitError; - - fn poll(&mut self) -> Poll { - if let Some(extensions) = Rc::get_mut(&mut *self.extensions.borrow_mut()) { - let mut idx = 0; - while idx < self.state.len() { - if let Async::Ready(_) = self.state[idx].poll_result(extensions)? { - self.state.remove(idx); - } else { - idx += 1; - } - } - if !self.state.is_empty() { - return Ok(Async::NotReady); - } - } else { - log::warn!("Multiple copies of app extensions exists"); - } - - let chain = futures::try_ready!(self.chain.poll()); - - Ok(Async::Ready(AppInitService { - chain, - rmap: self.rmap.clone(), - extensions: self.extensions.borrow().clone(), - })) - } -} - -/// Service to convert `Request` to a `ServiceRequest` -pub struct AppInitService -where - C: Service>, -{ - chain: C, - rmap: Rc, - extensions: Rc, -} - -impl Service for AppInitService -where - C: Service>, -{ - type Response = ServiceRequest

    ; - type Error = C::Error; - type Future = C::Future; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.chain.poll_ready() - } - - fn call(&mut self, req: Request) -> Self::Future { - let req = ServiceRequest::new( - Path::new(Url::new(req.uri().clone())), - req, - self.rmap.clone(), - self.extensions.clone(), - ); - self.chain.call(req) } } #[cfg(test)] mod tests { + use actix_service::Service; + use super::*; use crate::http::{Method, StatusCode}; use crate::test::{block_on, init_service, TestRequest}; diff --git a/src/app_service.rs b/src/app_service.rs new file mode 100644 index 00000000..094486d9 --- /dev/null +++ b/src/app_service.rs @@ -0,0 +1,439 @@ +use std::cell::RefCell; +use std::marker::PhantomData; +use std::rc::Rc; + +use actix_http::{Extensions, Request, Response}; +use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; +use actix_server_config::ServerConfig; +use actix_service::boxed::{self, BoxedNewService, BoxedService}; +use actix_service::{fn_service, AndThen, NewService, Service, ServiceExt}; +use futures::future::{ok, Either, FutureResult}; +use futures::{Async, Future, Poll}; + +use crate::config::AppConfig; +use crate::guard::Guard; +use crate::rmap::ResourceMap; +use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; +use crate::state::{StateFactory, StateFactoryResult}; + +type Guards = Vec>; +type HttpService

    = BoxedService, ServiceResponse, ()>; +type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; +type BoxedResponse = Box>; + +/// Service factory to convert `Request` to a `ServiceRequest`. +/// It also executes state factories. +pub struct AppInit +where + C: NewService>, + T: NewService< + Request = ServiceRequest

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

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

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

    | { + Ok(req.into_response(Response::NotFound().finish())) + }))) + }); + + let mut config = AppConfig::new( + "127.0.0.1:8080".parse().unwrap(), + "localhost:8080".to_owned(), + false, + default.clone(), + ); + + // register services + std::mem::replace(&mut *self.services.borrow_mut(), Vec::new()) + .into_iter() + .for_each(|mut srv| srv.register(&mut config)); + + let mut rmap = ResourceMap::new(ResourceDef::new("")); + + // complete pipeline creation + *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { + default, + services: Rc::new( + config + .into_services() + .into_iter() + .map(|(mut rdef, srv, guards, nested)| { + rmap.add(&mut rdef, nested); + (rdef, srv, RefCell::new(guards)) + }) + .collect(), + ), + }); + + // complete ResourceMap tree creation + let rmap = Rc::new(rmap); + rmap.finish(rmap.clone()); + + AppInitResult { + chain: None, + chain_fut: self.chain.new_service(&()), + endpoint: None, + endpoint_fut: self.endpoint.new_service(&()), + state: self.state.iter().map(|s| s.construct()).collect(), + extensions: self.extensions.clone(), + rmap, + _t: PhantomData, + } + } +} + +pub struct AppInitResult +where + C: NewService, + T: NewService, +{ + chain: Option, + endpoint: Option, + chain_fut: C::Future, + endpoint_fut: T::Future, + rmap: Rc, + state: Vec>, + extensions: Rc>>, + _t: PhantomData<(P, B)>, +} + +impl Future for AppInitResult +where + C: NewService< + Request = ServiceRequest, + Response = ServiceRequest

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

    , + Response = ServiceResponse, + Error = (), + InitError = (), + >, +{ + type Item = AndThen, T::Service>; + type Error = C::InitError; + + fn poll(&mut self) -> Poll { + if let Some(extensions) = Rc::get_mut(&mut *self.extensions.borrow_mut()) { + let mut idx = 0; + while idx < self.state.len() { + if let Async::Ready(_) = self.state[idx].poll_result(extensions)? { + self.state.remove(idx); + } else { + idx += 1; + } + } + if !self.state.is_empty() { + return Ok(Async::NotReady); + } + } else { + log::warn!("Multiple copies of app extensions exists"); + } + + if self.chain.is_none() { + if let Async::Ready(srv) = self.chain_fut.poll()? { + self.chain = Some(srv); + } + } + + if self.endpoint.is_none() { + if let Async::Ready(srv) = self.endpoint_fut.poll()? { + self.endpoint = Some(srv); + } + } + + if self.chain.is_some() && self.endpoint.is_some() { + Ok(Async::Ready( + AppInitService { + chain: self.chain.take().unwrap(), + rmap: self.rmap.clone(), + extensions: self.extensions.borrow().clone(), + } + .and_then(self.endpoint.take().unwrap()), + )) + } else { + Ok(Async::NotReady) + } + } +} + +/// Service to convert `Request` to a `ServiceRequest` +pub struct AppInitService +where + C: Service, Error = ()>, +{ + chain: C, + rmap: Rc, + extensions: Rc, +} + +impl Service for AppInitService +where + C: Service, Error = ()>, +{ + type Request = Request; + type Response = ServiceRequest

    ; + type Error = C::Error; + type Future = C::Future; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.chain.poll_ready() + } + + fn call(&mut self, req: Request) -> Self::Future { + let req = ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + self.rmap.clone(), + self.extensions.clone(), + ); + self.chain.call(req) + } +} + +pub struct AppRoutingFactory

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

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = AppRouting

    ; + type Future = AppRoutingFactoryResponse

    ; + + fn new_service(&self, _: &()) -> Self::Future { + AppRoutingFactoryResponse { + fut: self + .services + .iter() + .map(|(path, service, guards)| { + CreateAppRoutingItem::Future( + Some(path.clone()), + guards.borrow_mut().take(), + service.new_service(&()), + ) + }) + .collect(), + default: None, + default_fut: Some(self.default.new_service(&())), + } + } +} + +type HttpServiceFut

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

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

    { + Future(Option, Option, HttpServiceFut

    ), + Service(ResourceDef, Option, HttpService

    ), +} + +impl

    Future for AppRoutingFactoryResponse

    { + type Item = AppRouting

    ; + type Error = (); + + fn poll(&mut self) -> Poll { + let mut done = true; + + if let Some(ref mut fut) = self.default_fut { + match fut.poll()? { + Async::Ready(default) => self.default = Some(default), + Async::NotReady => done = false, + } + } + + // poll http services + for item in &mut self.fut { + let res = match item { + CreateAppRoutingItem::Future( + ref mut path, + ref mut guards, + ref mut fut, + ) => match fut.poll()? { + Async::Ready(service) => { + Some((path.take().unwrap(), guards.take(), service)) + } + Async::NotReady => { + done = false; + None + } + }, + CreateAppRoutingItem::Service(_, _, _) => continue, + }; + + if let Some((path, guards, service)) = res { + *item = CreateAppRoutingItem::Service(path, guards, service); + } + } + + if done { + let router = self + .fut + .drain(..) + .fold(Router::build(), |mut router, item| { + match item { + CreateAppRoutingItem::Service(path, guards, service) => { + router.rdef(path, service).2 = guards; + } + CreateAppRoutingItem::Future(_, _, _) => unreachable!(), + } + router + }); + Ok(Async::Ready(AppRouting { + ready: None, + router: router.finish(), + default: self.default.take(), + })) + } else { + Ok(Async::NotReady) + } + } +} + +pub struct AppRouting

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

    , ResourceInfo)>, + default: Option>, +} + +impl

    Service for AppRouting

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type Future = Either>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + if self.ready.is_none() { + Ok(Async::Ready(())) + } else { + Ok(Async::NotReady) + } + } + + fn call(&mut self, mut req: ServiceRequest

    ) -> Self::Future { + let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + if let Some(ref guards) = guards { + for f in guards { + if !f.check(req.head()) { + return false; + } + } + } + true + }); + + if let Some((srv, _info)) = res { + Either::A(srv.call(req)) + } else if let Some(ref mut default) = self.default { + Either::A(default.call(req)) + } else { + let req = req.into_request(); + Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + } + } +} + +/// Wrapper service for routing +pub struct AppEntry

    { + factory: Rc>>>, +} + +impl

    AppEntry

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

    { + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = (); + type InitError = (); + type Service = AppRouting

    ; + type Future = AppRoutingFactoryResponse

    ; + + fn new_service(&self, _: &()) -> Self::Future { + self.factory.borrow_mut().as_mut().unwrap().new_service(&()) + } +} + +#[doc(hidden)] +pub struct AppChain; + +impl NewService for AppChain { + type Request = ServiceRequest; + type Response = ServiceRequest; + type Error = (); + type InitError = (); + type Service = AppChain; + type Future = FutureResult; + + fn new_service(&self, _: &()) -> Self::Future { + ok(AppChain) + } +} + +impl Service for AppChain { + type Request = ServiceRequest; + type Response = ServiceRequest; + type Error = (); + type Future = FutureResult; + + #[inline] + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + #[inline] + fn call(&mut self, req: ServiceRequest) -> Self::Future { + ok(req) + } +} diff --git a/src/config.rs b/src/config.rs index 4afd213c..47c2f7c4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -98,9 +98,9 @@ impl AppConfig

    { service: F, nested: Option>, ) where - F: IntoNewService>, + F: IntoNewService, S: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), diff --git a/src/handler.rs b/src/handler.rs index 435d9a8b..87645651 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -52,11 +52,12 @@ where } } } -impl NewService<(T, HttpRequest)> for Handler +impl NewService for Handler where F: Factory, R: Responder + 'static, { + type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Void; type InitError = (); @@ -81,11 +82,12 @@ where _t: PhantomData<(T, R)>, } -impl Service<(T, HttpRequest)> for HandlerService +impl Service for HandlerService where F: Factory, R: Responder + 'static, { + type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Void; type Future = HandlerServiceResponse<::Future>; @@ -182,13 +184,14 @@ where } } } -impl NewService<(T, HttpRequest)> for AsyncHandler +impl NewService for AsyncHandler where F: AsyncFactory, R: IntoFuture, R::Item: Into, R::Error: Into, { + type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = (); type InitError = (); @@ -215,13 +218,14 @@ where _t: PhantomData<(T, R)>, } -impl Service<(T, HttpRequest)> for AsyncHandlerService +impl Service for AsyncHandlerService where F: AsyncFactory, R: IntoFuture, R::Item: Into, R::Error: Into, { + type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = (); type Future = AsyncHandlerServiceResponse; @@ -286,7 +290,8 @@ impl> Extract { } } -impl> NewService> for Extract { +impl> NewService for Extract { + type Request = ServiceRequest

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

    ); type InitError = (); @@ -306,7 +311,8 @@ pub struct ExtractService> { _t: PhantomData<(P, T)>, } -impl> Service> for ExtractService { +impl> Service for ExtractService { + type Request = ServiceRequest

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

    ); type Future = ExtractResponse; diff --git a/src/lib.rs b/src/lib.rs index 94bf1ba7..19f466b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::type_complexity)] mod app; +mod app_service; mod extract; mod handler; // mod info; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index c6f090a6..b3880a53 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -1,4 +1,5 @@ use std::io::Write; +use std::marker::PhantomData; use std::str::FromStr; use std::{cmp, fmt, io}; @@ -36,13 +37,14 @@ impl Default for Compress { } } -impl Transform> for Compress +impl Transform for Compress where P: 'static, B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { + type Request = ServiceRequest

    ; type Response = ServiceResponse>; type Error = S::Error; type InitError = (); @@ -62,13 +64,14 @@ pub struct CompressMiddleware { encoding: ContentEncoding, } -impl Service> for CompressMiddleware +impl Service for CompressMiddleware where P: 'static, B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { + type Request = ServiceRequest

    ; type Response = ServiceResponse>; type Error = S::Error; type Future = CompressResponse; @@ -92,6 +95,7 @@ where CompressResponse { encoding, fut: self.service.call(req), + _t: PhantomData, } } } @@ -101,18 +105,19 @@ pub struct CompressResponse where P: 'static, B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, S::Future: 'static, { fut: S::Future, encoding: ContentEncoding, + _t: PhantomData<(P, B)>, } impl Future for CompressResponse where P: 'static, B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { type Item = ServiceResponse>; diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index f4def58d..b4927962 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -85,11 +85,12 @@ impl DefaultHeaders { } } -impl Transform> for DefaultHeaders +impl Transform for DefaultHeaders where - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, { + type Request = ServiceRequest

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

    ; type Response = ServiceResponse; type Error = S::Error; type Future = Box>; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d8b4e643..4af3e10d 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::env; use std::fmt::{self, Display, Formatter}; +use std::marker::PhantomData; use std::rc::Rc; use actix_service::{Service, Transform}; @@ -110,11 +111,12 @@ impl Default for Logger { } } -impl Transform> for Logger +impl Transform for Logger where - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, B: MessageBody, { + type Request = ServiceRequest

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

    ; type Response = ServiceResponse>; type Error = S::Error; type Future = LoggerResponse; @@ -154,6 +157,7 @@ where fut: self.service.call(req), format: None, time: time::now(), + _t: PhantomData, } } else { let now = time::now(); @@ -166,6 +170,7 @@ where fut: self.service.call(req), format: Some(format), time: now, + _t: PhantomData, } } } @@ -175,17 +180,18 @@ where pub struct LoggerResponse where B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, { fut: S::Future, time: time::Tm, format: Option, + _t: PhantomData<(P, B)>, } impl Future for LoggerResponse where B: MessageBody, - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, { type Item = ServiceResponse>; type Error = S::Error; diff --git a/src/resource.rs b/src/resource.rs index a1177ca0..cc831665 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -66,7 +66,7 @@ impl Resource where P: 'static, T: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -220,7 +220,7 @@ where ) -> Resource< P, impl NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -229,12 +229,12 @@ where where M: Transform< T::Service, - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoTransform>, + F: IntoTransform, { let endpoint = ApplyTransform::new(mw, self.endpoint); Resource { @@ -251,9 +251,12 @@ where pub fn default_resource(mut self, f: F) -> Self where F: FnOnce(Resource

    ) -> R, - R: IntoNewService>, - U: NewService, Response = ServiceResponse, Error = ()> - + 'static, + R: IntoNewService, + U: NewService< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = (), + > + 'static, { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( @@ -268,7 +271,7 @@ impl HttpServiceFactory

    for Resource where P: 'static, T: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -292,10 +295,10 @@ where } } -impl IntoNewService> for Resource +impl IntoNewService for Resource where T: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -316,7 +319,8 @@ pub struct ResourceFactory

    { default: Rc>>>>, } -impl NewService> for ResourceFactory

    { +impl NewService for ResourceFactory

    { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type InitError = (); @@ -406,7 +410,8 @@ pub struct ResourceService

    { default: Option>, } -impl

    Service> for ResourceService

    { +impl

    Service for ResourceService

    { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type Future = Either< @@ -450,7 +455,8 @@ impl

    ResourceEndpoint

    { } } -impl NewService> for ResourceEndpoint

    { +impl NewService for ResourceEndpoint

    { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type InitError = (); diff --git a/src/route.rs b/src/route.rs index f7b99050..c189c61b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -15,7 +15,7 @@ use crate::HttpResponse; type BoxedRouteService = Box< Service< - Req, + Request = Req, Response = Res, Error = (), Future = Box>, @@ -24,7 +24,7 @@ type BoxedRouteService = Box< type BoxedRouteNewService = Box< NewService< - Req, + Request = Req, Response = Res, Error = (), InitError = (), @@ -70,7 +70,8 @@ impl Route

    { } } -impl

    NewService> for Route

    { +impl

    NewService for Route

    { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type InitError = (); @@ -125,7 +126,8 @@ impl

    RouteService

    { } } -impl

    Service> for RouteService

    { +impl

    Service for RouteService

    { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type Future = Box>; @@ -330,7 +332,7 @@ impl Route

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

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

    )>, { service: T, _t: PhantomData

    , @@ -339,13 +341,13 @@ where impl RouteNewService where T: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (Error, ServiceFromRequest

    ), >, T::Future: 'static, T::Service: 'static, - >>::Future: 'static, + ::Future: 'static, { pub fn new(service: T) -> Self { RouteNewService { @@ -355,17 +357,18 @@ where } } -impl NewService> for RouteNewService +impl NewService for RouteNewService where T: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (Error, ServiceFromRequest

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

    ; type Response = ServiceResponse; type Error = (); type InitError = (); @@ -389,20 +392,21 @@ where } } -struct RouteServiceWrapper>> { +struct RouteServiceWrapper { service: T, _t: PhantomData

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

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (Error, ServiceFromRequest

    ), >, { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type Future = Box>; diff --git a/src/scope.rs b/src/scope.rs index 15c652c8..6c511c69 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -81,7 +81,7 @@ impl Scope where P: 'static, T: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -174,7 +174,7 @@ where where F: FnOnce(Resource

    ) -> Resource, U: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -199,7 +199,7 @@ where ) -> Scope< P, impl NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -208,12 +208,12 @@ where where M: Transform< T::Service, - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), >, - F: IntoTransform>, + F: IntoTransform, { let endpoint = ApplyTransform::new(mw, self.endpoint); Scope { @@ -231,7 +231,7 @@ impl HttpServiceFactory

    for Scope where P: 'static, T: NewService< - ServiceRequest

    , + Request = ServiceRequest

    , Response = ServiceResponse, Error = (), InitError = (), @@ -287,7 +287,8 @@ pub struct ScopeFactory

    { default: Rc>>>>, } -impl NewService> for ScopeFactory

    { +impl NewService for ScopeFactory

    { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type InitError = (); @@ -402,7 +403,8 @@ pub struct ScopeService

    { _ready: Option<(ServiceRequest

    , ResourceInfo)>, } -impl

    Service> for ScopeService

    { +impl

    Service for ScopeService

    { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type Future = Either>; @@ -445,7 +447,8 @@ impl

    ScopeEndpoint

    { } } -impl NewService> for ScopeEndpoint

    { +impl NewService for ScopeEndpoint

    { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type InitError = (); diff --git a/src/server.rs b/src/server.rs index d3ae5b2b..d9574365 100644 --- a/src/server.rs +++ b/src/server.rs @@ -7,6 +7,7 @@ use actix_http::{ }; use actix_rt::System; use actix_server::{Server, ServerBuilder}; +use actix_server_config::ServerConfig; use actix_service::{IntoNewService, NewService}; use parking_lot::Mutex; @@ -53,8 +54,8 @@ struct Config { pub struct HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: fmt::Debug, S::Response: Into>, S::Service: 'static, @@ -72,8 +73,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: fmt::Debug + 'static, S::Response: Into>, S::Service: 'static, @@ -432,8 +433,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: fmt::Debug, S::Response: Into>, S::Service: 'static, diff --git a/src/test.rs b/src/test.rs index e4cdefbe..c88835a3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -8,6 +8,7 @@ use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{Extensions, PayloadStream, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; +use actix_server_config::ServerConfig; use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; use futures::Future; @@ -62,13 +63,19 @@ where /// ``` pub fn init_service( app: R, -) -> impl Service, Error = E> +) -> impl Service, Error = E> where - R: IntoNewService, - S: NewService, Error = E>, + R: IntoNewService, + S: NewService< + ServerConfig, + Request = Request, + Response = ServiceResponse, + Error = E, + >, S::InitError: std::fmt::Debug, { - block_on(app.into_new_service().new_service(&())).unwrap() + let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap()); + block_on(app.into_new_service().new_service(&cfg)).unwrap() } /// Calls service and waits for response future completion. @@ -93,7 +100,7 @@ where /// ``` pub fn call_success(app: &mut S, req: R) -> S::Response where - S: Service, Error = E>, + S: Service, Error = E>, E: std::fmt::Debug, { block_on(app.call(req)).unwrap() From d026821924cc97482c334952e214f0f86b1ee0e0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 10:39:06 -0800 Subject: [PATCH 1022/1635] unify service builders --- examples/echo.rs | 7 +- examples/echo2.rs | 10 +- examples/hello-world.rs | 7 +- src/builder.rs | 141 ++++++++++++++++++++++++++++ src/config.rs | 118 +----------------------- src/h1/service.rs | 136 --------------------------- src/h2/service.rs | 135 --------------------------- src/lib.rs | 4 +- src/service/service.rs | 155 +------------------------------ tests/test_client.rs | 8 +- tests/test_server.rs | 198 ++++++++++++++++++++++++++++------------ 11 files changed, 300 insertions(+), 619 deletions(-) create mode 100644 src/builder.rs diff --git a/examples/echo.rs b/examples/echo.rs index eb4aaa65..8ec0e6a9 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,9 +1,8 @@ use std::{env, io}; use actix_http::HttpMessage; -use actix_http::{h1, Request, Response}; +use actix_http::{HttpService, Request, Response}; use actix_server::Server; -use actix_service::NewService; use bytes::Bytes; use futures::Future; use http::header::HeaderValue; @@ -15,10 +14,9 @@ fn main() -> io::Result<()> { Server::build() .bind("echo", "127.0.0.1:8080", || { - h1::H1Service::build() + HttpService::build() .client_timeout(1000) .client_disconnect(1000) - .server_hostname("localhost") .finish(|mut req: Request| { req.body().limit(512).and_then(|bytes: Bytes| { info!("request body: {:?}", bytes); @@ -27,7 +25,6 @@ fn main() -> io::Result<()> { Ok(res.body(bytes)) }) }) - .map(|_| ()) })? .run() } diff --git a/examples/echo2.rs b/examples/echo2.rs index 3bb8d83d..101adc1c 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -2,9 +2,8 @@ use std::{env, io}; use actix_http::http::HeaderValue; use actix_http::HttpMessage; -use actix_http::{h1, Error, Request, Response}; +use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; -use actix_service::NewService; use bytes::Bytes; use futures::Future; use log::info; @@ -24,12 +23,7 @@ fn main() -> io::Result<()> { Server::build() .bind("echo", "127.0.0.1:8080", || { - h1::H1Service::build() - .client_timeout(1000) - .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_req: Request| handle_request(_req)) - .map(|_| ()) + HttpService::build().finish(|_req: Request| handle_request(_req)) })? .run() } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 05d69fed..6e382039 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,8 +1,7 @@ use std::{env, io}; -use actix_http::{h1, Response}; +use actix_http::{HttpService, Response}; use actix_server::Server; -use actix_service::NewService; use futures::future; use http::header::HeaderValue; use log::info; @@ -13,17 +12,15 @@ fn main() -> io::Result<()> { Server::build() .bind("hello-world", "127.0.0.1:8080", || { - h1::H1Service::build() + HttpService::build() .client_timeout(1000) .client_disconnect(1000) - .server_hostname("localhost") .finish(|_req| { info!("{:?}", _req); let mut res = Response::Ok(); res.header("x-head", HeaderValue::from_static("dummy value!")); future::ok::<_, ()>(res.body("Hello world!")) }) - .map(|_| ()) })? .run() } diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 00000000..c55b8d5f --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,141 @@ +use std::fmt::Debug; +use std::marker::PhantomData; + +use actix_server_config::ServerConfig as SrvConfig; +use actix_service::{IntoNewService, NewService}; + +use crate::body::MessageBody; +use crate::config::{KeepAlive, ServiceConfig}; +use crate::request::Request; +use crate::response::Response; + +use crate::h1::H1Service; +use crate::h2::H2Service; +use crate::service::HttpService; + +/// A http service builder +/// +/// This type can be used to construct an instance of `http service` through a +/// builder-like pattern. +pub struct HttpServiceBuilder { + keep_alive: KeepAlive, + client_timeout: u64, + client_disconnect: u64, + _t: PhantomData<(T, S)>, +} + +impl HttpServiceBuilder +where + S: NewService, + S::Error: Debug + 'static, + S::Service: 'static, +{ + /// Create instance of `ServiceConfigBuilder` + pub fn new() -> HttpServiceBuilder { + HttpServiceBuilder { + keep_alive: KeepAlive::Timeout(5), + client_timeout: 5000, + client_disconnect: 0, + _t: PhantomData, + } + } + + /// Set server keep-alive setting. + /// + /// By default keep alive is set to a 5 seconds. + pub fn keep_alive>(mut self, val: U) -> Self { + self.keep_alive = val.into(); + self + } + + /// Set server client timeout in milliseconds for first request. + /// + /// Defines a timeout for reading client request header. If a client does not transmit + /// the entire set headers within this time, the request is terminated with + /// the 408 (Request Time-out) error. + /// + /// To disable timeout set value to 0. + /// + /// By default client timeout is set to 5000 milliseconds. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } + + /// Set server connection disconnect timeout in milliseconds. + /// + /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete + /// within this time, the request get dropped. This timeout affects secure connections. + /// + /// To disable timeout set value to 0. + /// + /// By default disconnect timeout is set to 3000 milliseconds. + pub fn client_disconnect(mut self, val: u64) -> Self { + self.client_disconnect = val; + self + } + + // #[cfg(feature = "ssl")] + // /// Configure alpn protocols for SslAcceptorBuilder. + // pub fn configure_openssl( + // builder: &mut openssl::ssl::SslAcceptorBuilder, + // ) -> io::Result<()> { + // let protos: &[u8] = b"\x02h2"; + // builder.set_alpn_select_callback(|_, protos| { + // const H2: &[u8] = b"\x02h2"; + // if protos.windows(3).any(|window| window == H2) { + // Ok(b"h2") + // } else { + // Err(openssl::ssl::AlpnError::NOACK) + // } + // }); + // builder.set_alpn_protos(&protos)?; + + // Ok(()) + // } + + /// Finish service configuration and create *http service* for HTTP/1 protocol. + pub fn h1(self, service: F) -> H1Service + where + B: MessageBody + 'static, + F: IntoNewService, + S::Response: Into>, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + H1Service::with_config(cfg, service.into_new_service()) + } + + /// Finish service configuration and create *http service* for HTTP/2 protocol. + pub fn h2(self, service: F) -> H2Service + where + B: MessageBody + 'static, + F: IntoNewService, + S::Response: Into>, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + H2Service::with_config(cfg, service.into_new_service()) + } + + /// Finish service configuration and create `HttpService` instance. + pub fn finish(self, service: F) -> HttpService + where + B: MessageBody + 'static, + F: IntoNewService, + S::Response: Into>, + { + let cfg = ServiceConfig::new( + self.keep_alive, + self.client_timeout, + self.client_disconnect, + ); + HttpService::with_config(cfg, service.into_new_service()) + } +} diff --git a/src/config.rs b/src/config.rs index a9e705c9..3c7df2fe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,11 @@ use std::cell::UnsafeCell; +use std::fmt; use std::fmt::Write; use std::rc::Rc; use std::time::{Duration, Instant}; -use std::{fmt, net}; use bytes::BytesMut; use futures::{future, Future}; -use log::error; use time; use tokio_timer::{sleep, Delay}; @@ -90,11 +89,6 @@ impl ServiceConfig { })) } - /// Create worker settings builder. - pub fn build() -> ServiceConfigBuilder { - ServiceConfigBuilder::new() - } - #[inline] /// Keep alive duration if configured. pub fn keep_alive(&self) -> Option { @@ -177,116 +171,6 @@ impl ServiceConfig { } } -/// A service config builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct ServiceConfigBuilder { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - host: String, - addr: net::SocketAddr, - secure: bool, -} - -impl ServiceConfigBuilder { - /// Create instance of `ServiceConfigBuilder` - pub fn new() -> ServiceConfigBuilder { - ServiceConfigBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - } - } - - /// Enable secure flag for current server. - /// This flags also enables `client disconnect timeout`. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - if self.client_disconnect == 0 { - self.client_disconnect = 3000; - } - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: T) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - 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. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// 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. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: S) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - self.addr = addr; - } - } - } - self - } - - /// Finish service configuration and create `ServiceConfig` object. - pub fn finish(&mut self) -> ServiceConfig { - ServiceConfig::new(self.keep_alive, self.client_timeout, self.client_disconnect) - } -} - struct Date { bytes: [u8; DATE_VALUE_LENGTH], pos: usize, diff --git a/src/h1/service.rs b/src/h1/service.rs index 6a36678b..e55ff0d9 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -1,6 +1,5 @@ use std::fmt::Debug; use std::marker::PhantomData; -use std::net; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::ServerConfig as SrvConfig; @@ -8,7 +7,6 @@ use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; -use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; @@ -57,11 +55,6 @@ where _t: PhantomData, } } - - /// Create builder for `HttpService` instance. - pub fn build() -> H1ServiceBuilder { - H1ServiceBuilder::new() - } } impl NewService for H1Service @@ -89,135 +82,6 @@ where } } -/// A http/1 new service builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct H1ServiceBuilder { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - host: String, - addr: net::SocketAddr, - secure: bool, - _t: PhantomData<(T, S)>, -} - -impl H1ServiceBuilder -where - S: NewService, - S::Error: Debug, -{ - /// Create instance of `ServiceConfigBuilder` - pub fn new() -> H1ServiceBuilder { - H1ServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - _t: PhantomData, - } - } - - /// Enable secure flag for current server. - /// This flags also enables `client disconnect timeout`. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - if self.client_disconnect == 0 { - self.client_disconnect = 3000; - } - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: U) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - 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. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// 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. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: U) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - self.addr = addr; - } - } - } - self - } - - /// Finish service configuration and create `H1Service` instance. - pub fn finish(self, service: F) -> H1Service - where - B: MessageBody, - F: IntoNewService, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - ); - H1Service { - cfg, - srv: service.into_new_service(), - _t: PhantomData, - } - } -} - #[doc(hidden)] pub struct H1ServiceResponse, B> { fut: ::Future, diff --git a/src/h2/service.rs b/src/h2/service.rs index 57515d4e..ce7c3b5d 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -59,11 +59,6 @@ where _t: PhantomData, } } - - /// Create builder for `HttpService` instance. - pub fn build() -> H2ServiceBuilder { - H2ServiceBuilder::new() - } } impl NewService for H2Service @@ -91,136 +86,6 @@ where } } -/// A http/2 new service builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct H2ServiceBuilder { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - host: String, - addr: net::SocketAddr, - secure: bool, - _t: PhantomData<(T, S)>, -} - -impl H2ServiceBuilder -where - S: NewService, - S::Service: 'static, - S::Error: Debug + 'static, -{ - /// Create instance of `H2ServiceBuilder` - pub fn new() -> H2ServiceBuilder { - H2ServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - _t: PhantomData, - } - } - - /// Enable secure flag for current server. - /// This flags also enables `client disconnect timeout`. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - if self.client_disconnect == 0 { - self.client_disconnect = 3000; - } - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: U) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - 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. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// 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. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: U) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - self.addr = addr; - } - } - } - self - } - - /// Finish service configuration and create `H1Service` instance. - pub fn finish(self, service: F) -> H2Service - where - B: MessageBody, - F: IntoNewService, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - ); - H2Service { - cfg, - srv: service.into_new_service(), - _t: PhantomData, - } - } -} - #[doc(hidden)] pub struct H2ServiceResponse, B> { fut: ::Future, diff --git a/src/lib.rs b/src/lib.rs index 9d8bc50f..41ee55fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,7 @@ extern crate log; pub mod body; +mod builder; pub mod client; mod config; mod extensions; @@ -89,7 +90,8 @@ pub mod h2; pub mod test; pub mod ws; -pub use self::config::{KeepAlive, ServiceConfig, ServiceConfigBuilder}; +pub use self::builder::HttpServiceBuilder; +pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; diff --git a/src/service/service.rs b/src/service/service.rs index 0fa5d665..ac28c77a 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; use std::marker::PhantomData; -use std::{fmt, io, net}; +use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; use actix_server_config::ServerConfig as SrvConfig; @@ -12,11 +12,11 @@ use h2::server::{self, Handshake}; use log::error; use crate::body::MessageBody; +use crate::builder::HttpServiceBuilder; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::DispatchError; use crate::request::Request; use crate::response::Response; - use crate::{h1, h2::Dispatcher}; /// `NewService` HTTP1.1/HTTP2 transport implementation @@ -46,7 +46,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -88,155 +88,6 @@ where } } -/// A http service factory builder -/// -/// This type can be used to construct an instance of `ServiceConfig` through a -/// builder-like pattern. -pub struct HttpServiceBuilder { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - host: String, - addr: net::SocketAddr, - secure: bool, - _t: PhantomData<(T, S)>, -} - -impl HttpServiceBuilder -where - S: NewService, - S::Service: 'static, - S::Error: Debug + 'static, -{ - /// Create instance of `HttpServiceBuilder` type - pub fn new() -> HttpServiceBuilder { - HttpServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - host: "localhost".to_owned(), - addr: "127.0.0.1:8080".parse().unwrap(), - _t: PhantomData, - } - } - - /// Enable secure flag for current server. - /// This flags also enables `client disconnect timeout`. - /// - /// By default this flag is set to false. - pub fn secure(mut self) -> Self { - self.secure = true; - if self.client_disconnect == 0 { - self.client_disconnect = 3000; - } - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: U) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - 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. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); - self - } - - /// Set server ip address. - /// - /// 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. - /// - /// By default server address is set to a "127.0.0.1:8080" - pub fn server_address(mut self, addr: U) -> Self { - match addr.to_socket_addrs() { - Err(err) => error!("Can not convert to SocketAddr: {}", err), - Ok(mut addrs) => { - if let Some(addr) = addrs.next() { - self.addr = addr; - } - } - } - self - } - - // #[cfg(feature = "ssl")] - // /// Configure alpn protocols for SslAcceptorBuilder. - // pub fn configure_openssl( - // builder: &mut openssl::ssl::SslAcceptorBuilder, - // ) -> io::Result<()> { - // let protos: &[u8] = b"\x02h2"; - // builder.set_alpn_select_callback(|_, protos| { - // const H2: &[u8] = b"\x02h2"; - // if protos.windows(3).any(|window| window == H2) { - // Ok(b"h2") - // } else { - // Err(openssl::ssl::AlpnError::NOACK) - // } - // }); - // builder.set_alpn_protos(&protos)?; - - // Ok(()) - // } - - /// Finish service configuration and create `HttpService` instance. - pub fn finish(self, service: F) -> HttpService - where - B: MessageBody, - F: IntoNewService, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - ); - HttpService { - cfg, - srv: service.into_new_service(), - _t: PhantomData, - } - } -} - #[doc(hidden)] pub struct HttpServiceResponse, B> { fut: ::Future, diff --git a/tests/test_client.rs b/tests/test_client.rs index f44c45cb..782e487c 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -3,7 +3,7 @@ use bytes::Bytes; use futures::future::{self, ok}; use actix_http::HttpMessage; -use actix_http::{client, h1, Request, Response}; +use actix_http::{client, HttpService, Request, Response}; use actix_http_test::TestServer; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -32,7 +32,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn test_h1_v2() { env_logger::init(); let mut srv = TestServer::new(move || { - h1::H1Service::build() + HttpService::build() .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); @@ -66,7 +66,7 @@ fn test_h1_v2() { #[test] fn test_connection_close() { let mut srv = TestServer::new(move || { - h1::H1Service::build() + HttpService::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); @@ -80,7 +80,7 @@ fn test_connection_close() { #[test] fn test_with_query_parameter() { let mut srv = TestServer::new(move || { - h1::H1Service::build() + HttpService::build() .finish(|req: Request| { if req.uri().query().unwrap().contains("qp=") { ok::<_, ()>(Response::Ok().finish()) diff --git a/tests/test_server.rs b/tests/test_server.rs index b7cd5557..7a28bca8 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -10,20 +10,18 @@ use futures::stream::once; use actix_http::body::Body; use actix_http::{ - body, client, h1, h2, http, Error, HttpMessage as HttpMessage2, HttpService, - KeepAlive, Request, Response, + body, client, http, Error, HttpMessage as HttpMessage2, HttpService, KeepAlive, + Request, Response, }; #[test] fn test_h1() { let mut srv = TestServer::new(|| { - h1::H1Service::build() + HttpService::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_disconnect(1000) - .server_hostname("localhost") - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); @@ -38,7 +36,6 @@ fn test_h1_2() { .keep_alive(KeepAlive::Disabled) .client_timeout(1000) .client_disconnect(1000) - .server_hostname("localhost") .finish(|req: Request| { assert_eq!(req.version(), http::Version::HTTP_11); future::ok::<_, ()>(Response::Ok().finish()) @@ -83,8 +80,8 @@ fn test_h2() -> std::io::Result<()> { .clone() .map_err(|e| println!("Openssl error: {}", e)) .and_then( - h2::H2Service::build() - .finish(|_| future::ok::<_, Error>(Response::Ok().finish())) + HttpService::build() + .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) .map_err(|_| ()), ) }); @@ -129,8 +126,8 @@ fn test_h2_body() -> std::io::Result<()> { .clone() .map_err(|e| println!("Openssl error: {}", e)) .and_then( - h2::H2Service::build() - .finish(|mut req: Request<_>| { + HttpService::build() + .h2(|mut req: Request<_>| { req.body() .limit(1024 * 1024) .and_then(|body| Ok(Response::Ok().body(body))) @@ -153,10 +150,9 @@ fn test_h2_body() -> std::io::Result<()> { #[test] fn test_slow_request() { let srv = TestServer::new(|| { - h1::H1Service::build() + HttpService::build() .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -167,9 +163,9 @@ fn test_slow_request() { } #[test] -fn test_malformed_request() { +fn test_http1_malformed_request() { let srv = TestServer::new(|| { - h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().finish())).map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -180,11 +176,9 @@ fn test_malformed_request() { } #[test] -fn test_keepalive() { +fn test_http1_keepalive() { let srv = TestServer::new(|| { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -200,12 +194,11 @@ fn test_keepalive() { } #[test] -fn test_keepalive_timeout() { +fn test_http1_keepalive_timeout() { let srv = TestServer::new(|| { - h1::H1Service::build() + HttpService::build() .keep_alive(1) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -221,11 +214,9 @@ fn test_keepalive_timeout() { } #[test] -fn test_keepalive_close() { +fn test_http1_keepalive_close() { let srv = TestServer::new(|| { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -241,11 +232,9 @@ fn test_keepalive_close() { } #[test] -fn test_keepalive_http10_default_close() { +fn test_http10_keepalive_default_close() { let srv = TestServer::new(|| { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -260,11 +249,9 @@ fn test_keepalive_http10_default_close() { } #[test] -fn test_keepalive_http10() { +fn test_http10_keepalive() { let srv = TestServer::new(|| { - h1::H1Service::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -286,12 +273,11 @@ fn test_keepalive_http10() { } #[test] -fn test_keepalive_disabled() { +fn test_http1_keepalive_disabled() { let srv = TestServer::new(|| { - h1::H1Service::build() + HttpService::build() .keep_alive(KeepAlive::Disabled) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .map(|_| ()) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -313,7 +299,7 @@ fn test_content_length() { }; let mut srv = TestServer::new(|| { - h1::H1Service::new(|req: Request| { + HttpService::build().h1(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ StatusCode::NO_CONTENT, @@ -325,7 +311,6 @@ fn test_content_length() { ]; future::ok::<_, ()>(Response::new(statuses[indx])) }) - .map(|_| ()) }); let header = HeaderName::from_static("content-length"); @@ -356,6 +341,65 @@ fn test_content_length() { } } +// TODO: fix +// #[test] +// fn test_h2_content_length() { +// use actix_http::http::{ +// header::{HeaderName, HeaderValue}, +// StatusCode, +// }; +// let openssl = ssl_acceptor().unwrap(); + +// let mut srv = TestServer::new(move || { +// openssl +// .clone() +// .map_err(|e| println!("Openssl error: {}", e)) +// .and_then( +// HttpService::build() +// .h2(|req: Request| { +// let indx: usize = req.uri().path()[1..].parse().unwrap(); +// let statuses = [ +// StatusCode::NO_CONTENT, +// StatusCode::CONTINUE, +// StatusCode::SWITCHING_PROTOCOLS, +// StatusCode::PROCESSING, +// StatusCode::OK, +// StatusCode::NOT_FOUND, +// ]; +// future::ok::<_, ()>(Response::new(statuses[indx])) +// }) +// .map_err(|_| ()), +// ) +// }); + +// let header = HeaderName::from_static("content-length"); +// let value = HeaderValue::from_static("0"); + +// { +// for i in 0..4 { +// let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) +// .finish() +// .unwrap(); +// let response = srv.send_request(req).unwrap(); +// assert_eq!(response.headers().get(&header), None); + +// let req = client::ClientRequest::head(srv.surl(&format!("/{}", i))) +// .finish() +// .unwrap(); +// let response = srv.send_request(req).unwrap(); +// assert_eq!(response.headers().get(&header), None); +// } + +// for i in 4..6 { +// let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) +// .finish() +// .unwrap(); +// let response = srv.send_request(req).unwrap(); +// assert_eq!(response.headers().get(&header), Some(&value)); +// } +// } +// } + #[test] fn test_headers() { let data = STR.repeat(10); @@ -363,7 +407,7 @@ fn test_headers() { let mut srv = TestServer::new(move || { let data = data.clone(); - h1::H1Service::new(move |_| { + HttpService::build().h1(move |_| { let mut builder = Response::Ok(); for idx in 0..90 { builder.header( @@ -384,9 +428,8 @@ fn test_headers() { ); } future::ok::<_, ()>(builder.body(data.clone())) - }).map(|_| ()) + }) }); - let mut connector = srv.new_connector(); let req = srv.get().finish().unwrap(); @@ -399,6 +442,52 @@ fn test_headers() { assert_eq!(bytes, Bytes::from(data2)); } +#[test] +fn test_h2_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + let data = data.clone(); + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build().h2(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(builder.body(data.clone())) + }).map_err(|_| ())) + }); + let mut connector = srv.new_connector(); + + let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); + let mut response = srv.block_on(req.send(&mut connector)).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data2)); +} + const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -424,7 +513,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| future::ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); let req = srv.get().finish().unwrap(); @@ -439,7 +528,7 @@ fn test_body() { #[test] fn test_head_empty() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); @@ -462,10 +551,9 @@ fn test_head_empty() { #[test] fn test_head_binary() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { + HttpService::build().h1(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) }) - .map(|_| ()) }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); @@ -488,7 +576,7 @@ fn test_head_binary() { #[test] fn test_head_binary2() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| ok::<_, ()>(Response::Ok().body(STR))).map(|_| ()) + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); @@ -507,14 +595,13 @@ fn test_head_binary2() { #[test] fn test_body_length() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { + HttpService::build().h1(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() .body(Body::from_message(body::SizedStream::new(STR.len(), body))), ) }) - .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -529,11 +616,10 @@ fn test_body_length() { #[test] fn test_body_chunked_explicit() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { + HttpService::build().h1(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) }) - .map(|_| ()) }); let req = srv.get().finish().unwrap(); @@ -550,7 +636,7 @@ fn test_body_chunked_explicit() { #[test] fn test_body_chunked_implicit() { let mut srv = TestServer::new(|| { - h1::H1Service::new(|_| { + HttpService::build().h1(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) }) @@ -571,7 +657,7 @@ use actix_service::fn_cfg_factory; #[test] fn test_response_http_error_handling() { let mut srv = TestServer::new(|| { - h1::H1Service::new(fn_cfg_factory(|_: &ServerConfig| { + HttpService::build().h1(fn_cfg_factory(|_: &ServerConfig| { Ok::<_, ()>(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( From c0ce7f0bae77ed61330d80fba2b8ec6fdf838556 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 10:53:00 -0800 Subject: [PATCH 1023/1635] update http service usage; add app host --- src/app.rs | 11 ++++-- src/server.rs | 97 +++++++++++++++++++++++++-------------------------- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/app.rs b/src/app.rs index 76a3a1ce..42ce62d8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -31,6 +31,7 @@ where chain: T, extensions: Extensions, state: Vec>, + host: String, _t: PhantomData<(P,)>, } @@ -42,6 +43,7 @@ impl App { chain: AppChain, extensions: Extensions::new(), state: Vec::new(), + host: "localhost:8080".to_string(), _t: PhantomData, } } @@ -140,6 +142,7 @@ where default: None, factory_ref: fref, extensions: self.extensions, + host: self.host, _t: PhantomData, } } @@ -172,6 +175,7 @@ where chain, state: self.state, extensions: self.extensions, + host: self.host, _t: PhantomData, } } @@ -221,6 +225,7 @@ where factory_ref: fref, extensions: self.extensions, state: self.state, + host: self.host, services: vec![Box::new(ServiceFactoryWrapper::new(service))], _t: PhantomData, } @@ -233,8 +238,8 @@ where /// html#method.host) documentation for more information. /// /// By default host name is set to a "localhost" value. - pub fn hostname(self, _val: &str) -> Self { - // self.host = val.to_owned(); + pub fn hostname(mut self, val: &str) -> Self { + self.host = val.to_owned(); self } } @@ -249,6 +254,7 @@ pub struct AppRouter { factory_ref: Rc>>>, extensions: Extensions, state: Vec>, + host: String, _t: PhantomData<(P, B)>, } @@ -343,6 +349,7 @@ where default: self.default, factory_ref: self.factory_ref, extensions: self.extensions, + host: self.host, _t: PhantomData, } } diff --git a/src/server.rs b/src/server.rs index d9574365..5d717817 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,9 +2,7 @@ use std::marker::PhantomData; use std::sync::Arc; use std::{fmt, io, net}; -use actix_http::{ - body::MessageBody, HttpService, KeepAlive, Request, Response, ServiceConfig, -}; +use actix_http::{body::MessageBody, HttpService, KeepAlive, Request, Response}; use actix_rt::System; use actix_server::{Server, ServerBuilder}; use actix_server_config::ServerConfig; @@ -13,8 +11,8 @@ use parking_lot::Mutex; use net2::TcpBuilder; -#[cfg(feature = "tls")] -use native_tls::TlsAcceptor; +// #[cfg(feature = "tls")] +// use native_tls::TlsAcceptor; #[cfg(feature = "ssl")] use openssl::ssl::{SslAcceptor, SslAcceptorBuilder}; @@ -245,27 +243,28 @@ where lst, move || { let c = cfg.lock(); - let service_config = - ServiceConfig::new(c.keep_alive, c.client_timeout, 0); - HttpService::with_config(service_config, factory()) + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .finish(factory()) }, )?); Ok(self) } - #[cfg(feature = "tls")] - /// Use listener for accepting incoming tls connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen_tls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { - use actix_net::service::NewServiceExt; + // #[cfg(feature = "tls")] + // /// Use listener for accepting incoming tls connection requests + // /// + // /// HttpServer does not change any configuration for TcpListener, + // /// it needs to be configured before passing it to listen() method. + // pub fn listen_nativetls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { + // use actix_server::ssl; - self.listen_with(lst, move || { - ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } + // self.listen_with(lst, move || { + // ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) + // }) + // } #[cfg(feature = "ssl")] /// Use listener for accepting incoming tls connection requests @@ -276,12 +275,16 @@ where lst: net::TcpListener, builder: SslAcceptorBuilder, ) -> io::Result { - self.listen_ssl_inner(lst, openssl_acceptor(builder)?); + self.listen_ssl_inner(lst, openssl_acceptor(builder)?)?; Ok(self) } #[cfg(feature = "ssl")] - fn listen_ssl_inner(&mut self, lst: net::TcpListener, acceptor: SslAcceptor) { + fn listen_ssl_inner( + &mut self, + lst: net::TcpListener, + acceptor: SslAcceptor, + ) -> io::Result<()> { use actix_server::ssl::{OpensslAcceptor, SslError}; let acceptor = OpensslAcceptor::new(acceptor); @@ -298,15 +301,18 @@ where lst, move || { let c = cfg.lock(); - let service_config = - ServiceConfig::new(c.keep_alive, c.client_timeout, c.client_timeout); acceptor.clone().map_err(|e| SslError::Ssl(e)).and_then( - HttpService::with_config(service_config, factory()) + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown) + .finish(factory()) .map_err(|e| SslError::Service(e)) .map_init_err(|_| ()), ) }, - )); + )?); + Ok(()) } #[cfg(feature = "rust-tls")] @@ -315,7 +321,6 @@ where /// This method sets alpn protocols to "h2" and "http/1.1" pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self { use super::{RustlsAcceptor, ServerFlags}; - use actix_net::service::NewServiceExt; self.listen_with(lst, move || { RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) @@ -366,22 +371,21 @@ where } } - #[cfg(feature = "tls")] - /// The ssl socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind_tls( - self, - addr: A, - acceptor: TlsAcceptor, - ) -> io::Result { - use actix_net::service::NewServiceExt; - use actix_net::ssl::NativeTlsAcceptor; + // #[cfg(feature = "tls")] + // /// The ssl socket address to bind + // /// + // /// To bind multiple addresses this method can be called multiple times. + // pub fn bind_nativetls( + // self, + // addr: A, + // acceptor: TlsAcceptor, + // ) -> io::Result { + // use actix_server::ssl::NativeTlsAcceptor; - self.bind_with(addr, move || { - NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - }) - } + // self.bind_with(addr, move || { + // NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) + // }) + // } #[cfg(feature = "ssl")] /// Start listening for incoming tls connections. @@ -399,7 +403,7 @@ where let acceptor = openssl_acceptor(builder)?; for lst in sockets { - self.listen_ssl_inner(lst, acceptor.clone()); + self.listen_ssl_inner(lst, acceptor.clone())?; } Ok(self) @@ -415,14 +419,7 @@ where builder: ServerConfig, ) -> io::Result { use super::{RustlsAcceptor, ServerFlags}; - use actix_net::service::NewServiceExt; - - // alpn support - let flags = if self.no_http2 { - ServerFlags::HTTP1 - } else { - ServerFlags::HTTP1 | ServerFlags::HTTP2 - }; + use actix_service::NewServiceExt; self.bind_with(addr, move || { RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ()) From 9c7056e9b84f63c78587e4ceab32f8c4b6e526aa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 13:38:56 -0800 Subject: [PATCH 1024/1635] fix connector --- src/builder.rs | 2 +- src/client/connector.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index c55b8d5f..1df96b0e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -69,7 +69,7 @@ where /// /// To disable timeout set value to 0. /// - /// By default disconnect timeout is set to 3000 milliseconds. + /// By default disconnect timeout is set to 0. pub fn client_disconnect(mut self, val: u64) -> Self { self.client_disconnect = val; self diff --git a/src/client/connector.rs b/src/client/connector.rs index 5eb85ba6..ccb5dbce 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -269,7 +269,11 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service< + Request = Connect, + Response = (Connect, Io, Protocol), + Error = ConnectorError, + >, { type Request = Connect; type Response = IoConnection; From 54678308d0da4e917ab1cb98a62c986044e5d824 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 14:06:24 -0800 Subject: [PATCH 1025/1635] propogate app config with http request; add tests for url_for --- Cargo.toml | 3 +- actix-files/src/lib.rs | 41 ++++----- actix-files/src/named.rs | 2 - actix-session/src/cookie.rs | 10 ++- actix-web-codegen/src/lib.rs | 6 +- src/app.rs | 61 +++++++------- src/app_service.rs | 59 ++++++------- src/config.rs | 106 +++++++++++++++++------- src/error.rs | 2 + src/info.rs | 61 ++++++-------- src/lib.rs | 12 +-- src/request.rs | 156 +++++++++++++++++++++++++++++++---- src/resource.rs | 20 ++++- src/rmap.rs | 15 ++-- src/scope.rs | 4 +- src/service.rs | 18 ++-- src/state.rs | 2 +- src/test.rs | 34 +++++--- 18 files changed, 397 insertions(+), 215 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dbc0a65d..9c340825 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,9 +70,10 @@ actix-service = { git = "https://github.com/actix/actix-net.git" } actix-utils = { git = "https://github.com/actix/actix-net.git" } actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-router = { git = "https://github.com/actix/actix-net.git" } +#actix-router = { git = "https://github.com/actix/actix-net.git" } actix-server = { git = "https://github.com/actix/actix-net.git" } actix-server-config = { git = "https://github.com/actix/actix-net.git" } +actix-router = { path = "../actix-net/router" } bytes = "0.4" derive_more = "0.14" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 17efdd81..14c25be7 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -17,7 +17,7 @@ use v_htmlescape::escape as escape_html_entity; use actix_http::error::{Error, ErrorInternalServerError}; use actix_service::{boxed::BoxedNewService, NewService, Service}; -use actix_web::dev::{self, AppConfig, HttpServiceFactory, ResourceDef, Url}; +use actix_web::dev::{HttpServiceFactory, ResourceDef, ServiceConfig}; use actix_web::{ blocking, FromRequest, HttpRequest, HttpResponse, Responder, ServiceFromRequest, ServiceRequest, ServiceResponse, @@ -305,7 +305,7 @@ where P: 'static, C: StaticFileConfig + 'static, { - fn register(self, config: &mut AppConfig

    ) { + fn register(self, config: &mut ServiceConfig

    ) { if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); } @@ -314,11 +314,12 @@ where } else { ResourceDef::prefix(&self.path) }; - config.register_service(rdef, None, self) + config.register_service(rdef, None, self, None) } } -impl NewService> for Files { +impl NewService for Files { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type Service = FilesService; @@ -350,7 +351,8 @@ pub struct FilesService { _cd_map: PhantomData, } -impl Service> for FilesService { +impl Service for FilesService { + type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = (); type Future = FutureResult; @@ -362,7 +364,7 @@ impl Service> for FilesService { fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { let (req, _) = req.into_parts(); - let real_path = match PathBufWrp::get_pathbuf(req.match_info()) { + let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { Ok(item) => item, Err(e) => return ok(ServiceResponse::from_err(e, req.clone())), }; @@ -409,13 +411,13 @@ impl Service> for FilesService { } } +#[derive(Debug)] struct PathBufWrp(PathBuf); impl PathBufWrp { - fn get_pathbuf(path: &dev::Path) -> Result { - let path_str = path.path(); + fn get_pathbuf(path: &str) -> Result { let mut buf = PathBuf::new(); - for segment in path_str.split('/') { + for segment in path.split('/') { if segment == ".." { buf.pop(); } else if segment.starts_with('.') { @@ -447,13 +449,14 @@ impl

    FromRequest

    for PathBufWrp { type Config = (); fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - PathBufWrp::get_pathbuf(req.match_info()) + PathBufWrp::get_pathbuf(req.match_info().path()) } } #[cfg(test)] mod tests { use std::fs; + use std::iter::FromIterator; use std::ops::Add; use std::time::{Duration, SystemTime}; @@ -1093,32 +1096,32 @@ mod tests { #[test] fn test_path_buf() { assert_eq!( - PathBuf::from_param("/test/.tt"), + PathBufWrp::get_pathbuf("/test/.tt").map(|t| t.0), Err(UriSegmentError::BadStart('.')) ); assert_eq!( - PathBuf::from_param("/test/*tt"), + PathBufWrp::get_pathbuf("/test/*tt").map(|t| t.0), Err(UriSegmentError::BadStart('*')) ); assert_eq!( - PathBuf::from_param("/test/tt:"), + PathBufWrp::get_pathbuf("/test/tt:").map(|t| t.0), Err(UriSegmentError::BadEnd(':')) ); assert_eq!( - PathBuf::from_param("/test/tt<"), + PathBufWrp::get_pathbuf("/test/tt<").map(|t| t.0), Err(UriSegmentError::BadEnd('<')) ); assert_eq!( - PathBuf::from_param("/test/tt>"), + PathBufWrp::get_pathbuf("/test/tt>").map(|t| t.0), Err(UriSegmentError::BadEnd('>')) ); assert_eq!( - PathBuf::from_param("/seg1/seg2/"), - Ok(PathBuf::from_iter(vec!["seg1", "seg2"])) + PathBufWrp::get_pathbuf("/seg1/seg2/").unwrap().0, + PathBuf::from_iter(vec!["seg1", "seg2"]) ); assert_eq!( - PathBuf::from_param("/seg1/../seg2/"), - Ok(PathBuf::from_iter(vec!["seg2"])) + PathBufWrp::get_pathbuf("/seg1/../seg2/").unwrap().0, + PathBuf::from_iter(vec!["seg2"]) ); } } diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 2fc1c454..6372a183 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -304,8 +304,6 @@ impl Responder for NamedFile { type Future = Result; fn respond_to(self, req: &HttpRequest) -> Self::Future { - println!("RESP: {:?}", req); - if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 7fd5ec64..e2503145 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -255,12 +255,13 @@ impl CookieSession { } } -impl Transform> for CookieSession +impl Transform for CookieSession where - S: Service, Response = ServiceResponse>, + S: Service, Response = ServiceResponse>, S::Future: 'static, S::Error: 'static, { + type Request = ServiceRequest

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

    ; type Response = ServiceResponse; type Error = S::Error; type Future = Box>; diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 26b422d7..13d1b97f 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -31,7 +31,7 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { struct #name; impl actix_web::dev::HttpServiceFactory

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

    ) { + fn register(self, config: &mut actix_web::dev::ServiceConfig

    ) { #ast actix_web::dev::HttpServiceFactory::register( actix_web::Resource::new(#path) @@ -68,7 +68,7 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { struct #name; impl actix_web::dev::HttpServiceFactory

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

    ) { + fn register(self, config: &mut actix_web::dev::ServiceConfig

    ) { #ast actix_web::dev::HttpServiceFactory::register( actix_web::Resource::new(#path) @@ -105,7 +105,7 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { struct #name; impl actix_web::dev::HttpServiceFactory

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

    ) { + fn register(self, config: &mut actix_web::dev::ServiceConfig

    ) { #ast actix_web::dev::HttpServiceFactory::register( actix_web::Resource::new(#path) diff --git a/src/app.rs b/src/app.rs index 42ce62d8..29dd1ab6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,7 +3,8 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody}; -use actix_http::{Extensions, PayloadStream}; +use actix_http::PayloadStream; +use actix_router::ResourceDef; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ @@ -12,6 +13,7 @@ use actix_service::{ use futures::IntoFuture; use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; +use crate::config::{AppConfig, AppConfigInner}; use crate::resource::Resource; use crate::route::Route; use crate::service::{ @@ -29,9 +31,8 @@ where T: NewService>, { chain: T, - extensions: Extensions, state: Vec>, - host: String, + config: AppConfigInner, _t: PhantomData<(P,)>, } @@ -41,9 +42,8 @@ impl App { pub fn new() -> Self { App { chain: AppChain, - extensions: Extensions::new(), state: Vec::new(), - host: "localhost:8080".to_string(), + config: AppConfigInner::default(), _t: PhantomData, } } @@ -141,8 +141,8 @@ where services: Vec::new(), default: None, factory_ref: fref, - extensions: self.extensions, - host: self.host, + config: self.config, + external: Vec::new(), _t: PhantomData, } } @@ -174,8 +174,7 @@ where App { chain, state: self.state, - extensions: self.extensions, - host: self.host, + config: self.config, _t: PhantomData, } } @@ -223,10 +222,10 @@ where default: None, endpoint: AppEntry::new(fref.clone()), factory_ref: fref, - extensions: self.extensions, state: self.state, - host: self.host, + config: self.config, services: vec![Box::new(ServiceFactoryWrapper::new(service))], + external: Vec::new(), _t: PhantomData, } } @@ -239,7 +238,7 @@ where /// /// By default host name is set to a "localhost" value. pub fn hostname(mut self, val: &str) -> Self { - self.host = val.to_owned(); + self.config.host = val.to_owned(); self } } @@ -252,9 +251,9 @@ pub struct AppRouter { services: Vec>>, default: Option>>, factory_ref: Rc>>>, - extensions: Extensions, state: Vec>, - host: String, + config: AppConfigInner, + external: Vec, _t: PhantomData<(P, B)>, } @@ -348,8 +347,8 @@ where services: self.services, default: self.default, factory_ref: self.factory_ref, - extensions: self.extensions, - host: self.host, + config: self.config, + external: self.external, _t: PhantomData, } } @@ -382,33 +381,30 @@ where /// and are never considered for matching at request time. Calls to /// `HttpRequest::url_for()` will work as expected. /// - /// ```rust,ignore - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest, HttpResponse, Result}; + /// ```rust + /// use actix_web::{web, App, HttpRequest, HttpResponse, Result}; /// - /// fn index(req: &HttpRequest) -> Result { - /// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; - /// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); + /// fn index(req: HttpRequest) -> Result { + /// let url = req.url_for("youtube", &["asdlkjqme"])?; + /// assert_eq!(url.as_str(), "https://youtube.com/watch/asdlkjqme"); /// Ok(HttpResponse::Ok().into()) /// } /// /// fn main() { /// let app = App::new() - /// .resource("/index.html", |r| r.get().f(index)) - /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") - /// .finish(); + /// .service(web::resource("/index.html").route( + /// web::get().to(index))) + /// .external_resource("youtube", "https://youtube.com/watch/{video_id}"); /// } /// ``` - pub fn external_resource(self, _name: N, _url: U) -> Self + pub fn external_resource(mut self, name: N, url: U) -> Self where N: AsRef, U: AsRef, { - // self.parts - // .as_mut() - // .expect("Use after finish") - // .router - // .register_external(name.as_ref(), ResourceDef::external(url.as_ref())); + let mut rdef = ResourceDef::new(url.as_ref()); + *rdef.name_mut() = name.as_ref().to_string(); + self.external.push(rdef); self } } @@ -435,9 +431,10 @@ where state: self.state, endpoint: self.endpoint, services: RefCell::new(self.services), + external: RefCell::new(self.external), default: self.default, factory_ref: self.factory_ref, - extensions: Rc::new(RefCell::new(Rc::new(self.extensions))), + config: RefCell::new(AppConfig(Rc::new(self.config))), } } } diff --git a/src/app_service.rs b/src/app_service.rs index 094486d9..75e4b316 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; -use actix_http::{Extensions, Request, Response}; +use actix_http::{Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService, BoxedService}; @@ -10,7 +10,7 @@ use actix_service::{fn_service, AndThen, NewService, Service, ServiceExt}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll}; -use crate::config::AppConfig; +use crate::config::{AppConfig, ServiceConfig}; use crate::guard::Guard; use crate::rmap::ResourceMap; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; @@ -36,10 +36,11 @@ where pub(crate) chain: C, pub(crate) endpoint: T, pub(crate) state: Vec>, - pub(crate) extensions: Rc>>, + pub(crate) config: RefCell, pub(crate) services: RefCell>>>, pub(crate) default: Option>>, pub(crate) factory_ref: Rc>>>, + pub(crate) external: RefCell>, } impl NewService for AppInit @@ -64,7 +65,7 @@ where type Service = AndThen, T::Service>; type Future = AppInitResult; - fn new_service(&self, _: &ServerConfig) -> Self::Future { + fn new_service(&self, cfg: &ServerConfig) -> Self::Future { // update resource default service let default = self.default.clone().unwrap_or_else(|| { Rc::new(boxed::new_service(fn_service(|req: ServiceRequest

    | { @@ -72,12 +73,15 @@ where }))) }); - let mut config = AppConfig::new( - "127.0.0.1:8080".parse().unwrap(), - "localhost:8080".to_owned(), - false, - default.clone(), - ); + { + let mut c = self.config.borrow_mut(); + let loc_cfg = Rc::get_mut(&mut c.0).unwrap(); + loc_cfg.secure = cfg.secure(); + loc_cfg.addr = cfg.local_addr(); + } + + let mut config = + ServiceConfig::new(self.config.borrow().clone(), default.clone()); // register services std::mem::replace(&mut *self.services.borrow_mut(), Vec::new()) @@ -101,6 +105,11 @@ where ), }); + // external resources + for mut rdef in std::mem::replace(&mut *self.external.borrow_mut(), Vec::new()) { + rmap.add(&mut rdef, None); + } + // complete ResourceMap tree creation let rmap = Rc::new(rmap); rmap.finish(rmap.clone()); @@ -111,7 +120,7 @@ where endpoint: None, endpoint_fut: self.endpoint.new_service(&()), state: self.state.iter().map(|s| s.construct()).collect(), - extensions: self.extensions.clone(), + config: self.config.borrow().clone(), rmap, _t: PhantomData, } @@ -129,7 +138,7 @@ where endpoint_fut: T::Future, rmap: Rc, state: Vec>, - extensions: Rc>>, + config: AppConfig, _t: PhantomData<(P, B)>, } @@ -152,20 +161,14 @@ where type Error = C::InitError; fn poll(&mut self) -> Poll { - if let Some(extensions) = Rc::get_mut(&mut *self.extensions.borrow_mut()) { - let mut idx = 0; - while idx < self.state.len() { - if let Async::Ready(_) = self.state[idx].poll_result(extensions)? { - self.state.remove(idx); - } else { - idx += 1; - } + let mut idx = 0; + let mut extensions = self.config.0.extensions.borrow_mut(); + while idx < self.state.len() { + if let Async::Ready(_) = self.state[idx].poll_result(&mut extensions)? { + self.state.remove(idx); + } else { + idx += 1; } - if !self.state.is_empty() { - return Ok(Async::NotReady); - } - } else { - log::warn!("Multiple copies of app extensions exists"); } if self.chain.is_none() { @@ -185,7 +188,7 @@ where AppInitService { chain: self.chain.take().unwrap(), rmap: self.rmap.clone(), - extensions: self.extensions.borrow().clone(), + config: self.config.clone(), } .and_then(self.endpoint.take().unwrap()), )) @@ -202,7 +205,7 @@ where { chain: C, rmap: Rc, - extensions: Rc, + config: AppConfig, } impl Service for AppInitService @@ -223,7 +226,7 @@ where Path::new(Url::new(req.uri().clone())), req, self.rmap.clone(), - self.extensions.clone(), + self.config.clone(), ); self.chain.call(req) } diff --git a/src/config.rs b/src/config.rs index 47c2f7c4..f84376c7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,8 @@ +use std::cell::{Ref, RefCell}; use std::net::SocketAddr; use std::rc::Rc; +use actix_http::Extensions; use actix_router::ResourceDef; use actix_service::{boxed, IntoNewService, NewService}; @@ -13,10 +15,8 @@ type HttpNewService

    = boxed::BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; /// Application configuration -pub struct AppConfig

    { - addr: SocketAddr, - secure: bool, - host: String, +pub struct ServiceConfig

    { + config: AppConfig, root: bool, default: Rc>, services: Vec<( @@ -27,18 +27,11 @@ pub struct AppConfig

    { )>, } -impl AppConfig

    { +impl ServiceConfig

    { /// Crate server settings instance - pub(crate) fn new( - addr: SocketAddr, - host: String, - secure: bool, - default: Rc>, - ) -> Self { - AppConfig { - addr, - secure, - host, + pub(crate) fn new(config: AppConfig, default: Rc>) -> Self { + ServiceConfig { + config, default, root: true, services: Vec::new(), @@ -62,31 +55,20 @@ impl AppConfig

    { } pub(crate) fn clone_config(&self) -> Self { - AppConfig { - addr: self.addr, - secure: self.secure, - host: self.host.clone(), + ServiceConfig { + config: self.config.clone(), default: self.default.clone(), services: Vec::new(), root: false, } } - /// Returns the socket address of the local half of this TCP connection - pub fn local_addr(&self) -> SocketAddr { - self.addr - } - - /// Returns true if connection is secure(https) - pub fn secure(&self) -> bool { - self.secure - } - - /// Returns host header value - pub fn host(&self) -> &str { - &self.host + /// Service configuration + pub fn config(&self) -> &AppConfig { + &self.config } + /// Default resource pub fn default_service(&self) -> Rc> { self.default.clone() } @@ -114,3 +96,63 @@ impl AppConfig

    { )); } } + +#[derive(Clone)] +pub struct AppConfig(pub(crate) Rc); + +impl AppConfig { + pub(crate) fn new(inner: AppConfigInner) -> Self { + AppConfig(Rc::new(inner)) + } + + /// 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. + /// + /// By default host name is set to a "localhost" value. + pub fn host(&self) -> &str { + &self.0.host + } + + /// Returns true if connection is secure(https) + pub fn secure(&self) -> bool { + self.0.secure + } + + /// Returns the socket address of the local half of this TCP connection + pub fn local_addr(&self) -> SocketAddr { + self.0.addr + } + + /// Resource map + pub fn rmap(&self) -> &ResourceMap { + &self.0.rmap + } + + /// Application extensions + pub fn extensions(&self) -> Ref { + self.0.extensions.borrow() + } +} + +pub(crate) struct AppConfigInner { + pub(crate) secure: bool, + pub(crate) host: String, + pub(crate) addr: SocketAddr, + pub(crate) rmap: ResourceMap, + pub(crate) extensions: RefCell, +} + +impl Default for AppConfigInner { + fn default() -> AppConfigInner { + AppConfigInner { + secure: false, + addr: "127.0.0.1:8080".parse().unwrap(), + host: "localhost:8080".to_owned(), + rmap: ResourceMap::new(ResourceDef::new("")), + extensions: RefCell::new(Extensions::new()), + } + } +} diff --git a/src/error.rs b/src/error.rs index d1c0d3ca..54ca74dc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +//! Error and Result module + pub use actix_http::error::*; use derive_more::{Display, From}; use url::ParseError as UrlParseError; diff --git a/src/info.rs b/src/info.rs index 3b51215f..c058bd51 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,19 +1,14 @@ use std::cell::Ref; -use actix_http::http::header::{self, HeaderName}; -use actix_http::RequestHead; +use crate::dev::{AppConfig, RequestHead}; +use crate::http::header::{self, HeaderName}; const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; -pub enum ConnectionInfoError { - UnknownHost, - UnknownScheme, -} - /// `HttpRequest` connection information -#[derive(Clone, Default)] +#[derive(Debug, Clone, Default)] pub struct ConnectionInfo { scheme: String, host: String, @@ -23,19 +18,19 @@ pub struct ConnectionInfo { impl ConnectionInfo { /// Create *ConnectionInfo* instance for a request. - pub fn get(req: &RequestHead) -> Ref { + pub fn get<'a>(req: &'a RequestHead, cfg: &AppConfig) -> Ref<'a, Self> { if !req.extensions().contains::() { - req.extensions_mut().insert(ConnectionInfo::new(req)); + req.extensions_mut().insert(ConnectionInfo::new(req, cfg)); } Ref::map(req.extensions(), |e| e.get().unwrap()) } #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] - fn new(req: &RequestHead) -> ConnectionInfo { + fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { let mut host = None; let mut scheme = None; let mut remote = None; - let mut peer = None; + let peer = None; // load forwarded header for hdr in req.headers.get_all(header::FORWARDED) { @@ -82,7 +77,7 @@ impl ConnectionInfo { } if scheme.is_none() { scheme = req.uri.scheme_part().map(|a| a.as_str()); - if scheme.is_none() && req.server_settings().secure() { + if scheme.is_none() && cfg.secure() { scheme = Some("https") } } @@ -105,7 +100,7 @@ impl ConnectionInfo { if host.is_none() { host = req.uri.authority_part().map(|a| a.as_str()); if host.is_none() { - host = Some(req.server_settings().host()); + host = Some(cfg.host()); } } } @@ -121,10 +116,10 @@ impl ConnectionInfo { remote = h.split(',').next().map(|v| v.trim()); } } - if remote.is_none() { - // get peeraddr from socketaddr - peer = req.peer_addr().map(|addr| format!("{}", addr)); - } + // if remote.is_none() { + // get peeraddr from socketaddr + // peer = req.peer_addr().map(|addr| format!("{}", addr)); + // } } ConnectionInfo { @@ -186,9 +181,8 @@ mod tests { #[test] fn test_forwarded() { - let req = TestRequest::default().request(); - let mut info = ConnectionInfo::default(); - info.update(&req); + let req = TestRequest::default().to_http_request(); + let info = req.connection_info(); assert_eq!(info.scheme(), "http"); assert_eq!(info.host(), "localhost:8080"); @@ -197,44 +191,39 @@ mod tests { header::FORWARDED, "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", ) - .request(); + .to_http_request(); - let mut info = ConnectionInfo::default(); - info.update(&req); + let info = req.connection_info(); assert_eq!(info.scheme(), "https"); assert_eq!(info.host(), "rust-lang.org"); assert_eq!(info.remote(), Some("192.0.2.60")); let req = TestRequest::default() .header(header::HOST, "rust-lang.org") - .request(); + .to_http_request(); - let mut info = ConnectionInfo::default(); - info.update(&req); + let info = req.connection_info(); assert_eq!(info.scheme(), "http"); assert_eq!(info.host(), "rust-lang.org"); assert_eq!(info.remote(), None); let req = TestRequest::default() .header(X_FORWARDED_FOR, "192.0.2.60") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); + .to_http_request(); + let info = req.connection_info(); assert_eq!(info.remote(), Some("192.0.2.60")); let req = TestRequest::default() .header(X_FORWARDED_HOST, "192.0.2.60") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); + .to_http_request(); + let info = req.connection_info(); assert_eq!(info.host(), "192.0.2.60"); assert_eq!(info.remote(), None); let req = TestRequest::default() .header(X_FORWARDED_PROTO, "https") - .request(); - let mut info = ConnectionInfo::default(); - info.update(&req); + .to_http_request(); + let info = req.connection_info(); assert_eq!(info.scheme(), "https"); } } diff --git a/src/lib.rs b/src/lib.rs index 19f466b4..6329d53c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,13 +2,13 @@ mod app; mod app_service; -mod extract; -mod handler; -// mod info; pub mod blocking; mod config; pub mod error; +mod extract; pub mod guard; +mod handler; +mod info; pub mod middleware; mod request; mod resource; @@ -54,7 +54,9 @@ pub mod dev { //! ``` pub use crate::app::AppRouter; - pub use crate::config::AppConfig; + pub use crate::config::{AppConfig, ServiceConfig}; + pub use crate::info::ConnectionInfo; + pub use crate::rmap::ResourceMap; pub use crate::service::HttpServiceFactory; pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; @@ -62,7 +64,7 @@ pub mod dev { pub use actix_http::{ Extensions, Payload, PayloadStream, RequestHead, ResponseHead, }; - pub use actix_router::{Path, ResourceDef, Url}; + pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub(crate) fn insert_slash(path: &str) -> String { let mut path = path.to_owned(); diff --git a/src/request.rs b/src/request.rs index 6655f1ba..71751483 100644 --- a/src/request.rs +++ b/src/request.rs @@ -7,8 +7,10 @@ use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; +use crate::config::AppConfig; use crate::error::UrlGenerationError; use crate::extract::FromRequest; +use crate::info::ConnectionInfo; use crate::rmap::ResourceMap; use crate::service::ServiceFromRequest; @@ -18,7 +20,7 @@ pub struct HttpRequest { pub(crate) head: Message, pub(crate) path: Path, rmap: Rc, - extensions: Rc, + config: AppConfig, } impl HttpRequest { @@ -27,13 +29,13 @@ impl HttpRequest { head: Message, path: Path, rmap: Rc, - extensions: Rc, + config: AppConfig, ) -> HttpRequest { HttpRequest { head, path, rmap, - extensions, + config, } } } @@ -92,17 +94,17 @@ impl HttpRequest { &self.path } - /// Application extensions + /// App config #[inline] - pub fn app_extensions(&self) -> &Extensions { - &self.extensions + pub fn config(&self) -> &AppConfig { + &self.config } /// Generate url for named resource /// /// ```rust /// # extern crate actix_web; - /// # use actix_web::{App, HttpRequest, HttpResponse, http}; + /// # use actix_web::{web, App, HttpRequest, HttpResponse}; /// # /// fn index(req: HttpRequest) -> HttpResponse { /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource @@ -111,11 +113,10 @@ impl HttpRequest { /// /// fn main() { /// let app = App::new() - /// .resource("/test/{one}/{two}/{three}", |r| { - /// r.name("foo"); // <- set resource name, then it could be used in `url_for` - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .finish(); + /// .service(web::resource("/test/{one}/{two}/{three}") + /// .name("foo") // <- set resource name, then it could be used in `url_for` + /// .route(web::get().to(|| HttpResponse::Ok())) + /// ); /// } /// ``` pub fn url_for( @@ -139,11 +140,11 @@ impl HttpRequest { self.url_for(name, &NO_PARAMS) } - // /// Get *ConnectionInfo* for the correct request. - // #[inline] - // pub fn connection_info(&self) -> Ref { - // ConnectionInfo::get(&*self) - // } + /// Get *ConnectionInfo* for the current request. + #[inline] + pub fn connection_info(&self) -> Ref { + ConnectionInfo::get(&*self, &*self.config()) + } } impl Deref for HttpRequest { @@ -234,3 +235,124 @@ impl fmt::Debug for HttpRequest { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::dev::{ResourceDef, ResourceMap}; + use crate::http::header; + use crate::test::TestRequest; + + #[test] + fn test_debug() { + let req = + TestRequest::with_header("content-type", "text/plain").to_http_request(); + let dbg = format!("{:?}", req); + assert!(dbg.contains("HttpRequest")); + } + + #[test] + fn test_no_request_cookies() { + let req = TestRequest::default().to_http_request(); + assert!(req.cookies().unwrap().is_empty()); + } + + #[test] + fn test_request_cookies() { + let req = TestRequest::default() + .header(header::COOKIE, "cookie1=value1") + .header(header::COOKIE, "cookie2=value2") + .to_http_request(); + { + 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_request_query() { + let req = TestRequest::with_uri("/?id=test").to_http_request(); + assert_eq!(req.query_string(), "id=test"); + } + + #[test] + fn test_url_for() { + let mut res = ResourceDef::new("/user/{name}.{ext}"); + *res.name_mut() = "index".to_string(); + + let mut rmap = ResourceMap::new(ResourceDef::new("")); + rmap.add(&mut res, None); + assert!(rmap.has_resource("/user/test.html")); + assert!(!rmap.has_resource("/test/unknown")); + + let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") + .rmap(rmap) + .to_http_request(); + + 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" + ); + } + + #[test] + fn test_url_for_static() { + let mut rdef = ResourceDef::new("/index.html"); + *rdef.name_mut() = "index".to_string(); + + let mut rmap = ResourceMap::new(ResourceDef::new("")); + rmap.add(&mut rdef, None); + + assert!(rmap.has_resource("/index.html")); + + let req = TestRequest::with_uri("/test") + .header(header::HOST, "www.rust-lang.org") + .rmap(rmap) + .to_http_request(); + let url = req.url_for_static("index"); + assert_eq!( + url.ok().unwrap().as_str(), + "http://www.rust-lang.org/index.html" + ); + } + + #[test] + fn test_url_for_external() { + let mut rdef = ResourceDef::new("https://youtube.com/watch/{video_id}"); + + *rdef.name_mut() = "youtube".to_string(); + + let mut rmap = ResourceMap::new(ResourceDef::new("")); + rmap.add(&mut rdef, None); + assert!(rmap.has_resource("https://youtube.com/watch/unknown")); + + let req = TestRequest::default().rmap(rmap).to_http_request(); + let url = req.url_for("youtube", &["oHg5SJYRHA0"]); + assert_eq!( + url.ok().unwrap().as_str(), + "https://youtube.com/watch/oHg5SJYRHA0" + ); + } +} diff --git a/src/resource.rs b/src/resource.rs index cc831665..57f6f710 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -9,7 +9,7 @@ use actix_service::{ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::dev::{insert_slash, AppConfig, HttpServiceFactory, ResourceDef}; +use crate::dev::{insert_slash, HttpServiceFactory, ResourceDef, ServiceConfig}; use crate::extract::FromRequest; use crate::guard::Guard; use crate::handler::{AsyncFactory, Factory}; @@ -41,6 +41,7 @@ type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, pub struct Resource> { endpoint: T, rdef: String, + name: Option, routes: Vec>, guards: Vec>, default: Rc>>>>, @@ -54,6 +55,7 @@ impl

    Resource

    { Resource { routes: Vec::new(), rdef: path.to_string(), + name: None, endpoint: ResourceEndpoint::new(fref.clone()), factory_ref: fref, guards: Vec::new(), @@ -72,6 +74,14 @@ where InitError = (), >, { + /// Set resource name. + /// + /// Name is used for url generation. + pub fn name(mut self, name: &str) -> Self { + self.name = Some(name.to_string()); + self + } + /// Add match guard to a resource. /// /// ```rust @@ -240,6 +250,7 @@ where Resource { endpoint, rdef: self.rdef, + name: self.name, guards: self.guards, routes: self.routes, default: self.default, @@ -277,7 +288,7 @@ where InitError = (), > + 'static, { - fn register(mut self, config: &mut AppConfig

    ) { + fn register(mut self, config: &mut ServiceConfig

    ) { if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); } @@ -286,11 +297,14 @@ where } else { Some(std::mem::replace(&mut self.guards, Vec::new())) }; - let rdef = if config.is_root() || !self.rdef.is_empty() { + let mut rdef = if config.is_root() || !self.rdef.is_empty() { ResourceDef::new(&insert_slash(&self.rdef)) } else { ResourceDef::new(&self.rdef) }; + if let Some(ref name) = self.name { + *rdef.name_mut() = name.clone(); + } config.register_service(rdef, guards, self, None) } } diff --git a/src/rmap.rs b/src/rmap.rs index 4922084b..35fe8ee3 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -64,14 +64,13 @@ impl ResourceMap { if self.patterns_for(name, &mut path, &mut elements)?.is_some() { if path.starts_with('/') { - // let conn = req.connection_info(); - // Ok(Url::parse(&format!( - // "{}://{}{}", - // conn.scheme(), - // conn.host(), - // path - // ))?) - unimplemented!() + let conn = req.connection_info(); + Ok(Url::parse(&format!( + "{}://{}{}", + conn.scheme(), + conn.host(), + path + ))?) } else { Ok(Url::parse(&path)?) } diff --git a/src/scope.rs b/src/scope.rs index 6c511c69..3b506173 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -10,7 +10,7 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, Poll}; -use crate::dev::{AppConfig, HttpServiceFactory}; +use crate::dev::{HttpServiceFactory, ServiceConfig}; use crate::guard::Guard; use crate::resource::Resource; use crate::rmap::ResourceMap; @@ -237,7 +237,7 @@ where InitError = (), > + 'static, { - fn register(self, config: &mut AppConfig

    ) { + fn register(self, config: &mut ServiceConfig

    ) { // update default resource if needed if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); diff --git a/src/service.rs b/src/service.rs index ba811458..f4b63a46 100644 --- a/src/service.rs +++ b/src/service.rs @@ -13,16 +13,16 @@ use actix_http::{ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; -use crate::config::AppConfig; +use crate::config::{AppConfig, ServiceConfig}; use crate::request::HttpRequest; use crate::rmap::ResourceMap; pub trait HttpServiceFactory

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

    ); + fn register(self, config: &mut ServiceConfig

    ); } pub(crate) trait ServiceFactory

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

    ); + fn register(&mut self, config: &mut ServiceConfig

    ); } pub(crate) struct ServiceFactoryWrapper { @@ -43,7 +43,7 @@ impl ServiceFactory

    for ServiceFactoryWrapper where T: HttpServiceFactory

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

    ) { + fn register(&mut self, config: &mut ServiceConfig

    ) { if let Some(item) = self.factory.take() { item.register(config) } @@ -60,12 +60,12 @@ impl

    ServiceRequest

    { path: Path, request: Request

    , rmap: Rc, - extensions: Rc, + config: AppConfig, ) -> Self { let (head, payload) = request.into_parts(); ServiceRequest { payload, - req: HttpRequest::new(head, path, rmap, extensions), + req: HttpRequest::new(head, path, rmap, config), } } @@ -156,10 +156,10 @@ impl

    ServiceRequest

    { &mut self.req.path } - /// Application extensions + /// Service configuration #[inline] - pub fn app_extensions(&self) -> &Extensions { - self.req.app_extensions() + pub fn app_config(&self) -> &AppConfig { + self.req.config() } /// Deconstruct request into parts diff --git a/src/state.rs b/src/state.rs index 265c6f01..2c623c70 100644 --- a/src/state.rs +++ b/src/state.rs @@ -52,7 +52,7 @@ impl FromRequest

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

    ) -> Self::Future { - if let Some(st) = req.app_extensions().get::>() { + if let Some(st) = req.config().extensions().get::>() { Ok(st.clone()) } else { Err(ErrorInternalServerError( diff --git a/src/test.rs b/src/test.rs index c88835a3..b47daa2c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, Version}; use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{Extensions, PayloadStream, Request}; +use actix_http::{PayloadStream, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; use actix_server_config::ServerConfig; @@ -13,6 +13,7 @@ use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; use futures::Future; +use crate::config::{AppConfig, AppConfigInner}; use crate::request::HttpRequest; use crate::rmap::ResourceMap; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; @@ -142,16 +143,16 @@ where /// ``` pub struct TestRequest { req: HttpTestRequest, - extensions: Extensions, rmap: ResourceMap, + config: AppConfigInner, } impl Default for TestRequest { fn default() -> TestRequest { TestRequest { req: HttpTestRequest::default(), - extensions: Extensions::new(), rmap: ResourceMap::new(ResourceDef::new("")), + config: AppConfigInner::default(), } } } @@ -161,8 +162,8 @@ impl TestRequest { pub fn with_uri(path: &str) -> TestRequest { TestRequest { req: HttpTestRequest::default().uri(path).take(), - extensions: Extensions::new(), rmap: ResourceMap::new(ResourceDef::new("")), + config: AppConfigInner::default(), } } @@ -170,7 +171,7 @@ impl TestRequest { pub fn with_hdr(hdr: H) -> TestRequest { TestRequest { req: HttpTestRequest::default().set(hdr).take(), - extensions: Extensions::new(), + config: AppConfigInner::default(), rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -183,7 +184,7 @@ impl TestRequest { { TestRequest { req: HttpTestRequest::default().header(key, value).take(), - extensions: Extensions::new(), + config: AppConfigInner::default(), rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -192,7 +193,7 @@ impl TestRequest { pub fn get() -> TestRequest { TestRequest { req: HttpTestRequest::default().method(Method::GET).take(), - extensions: Extensions::new(), + config: AppConfigInner::default(), rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -201,7 +202,7 @@ impl TestRequest { pub fn post() -> TestRequest { TestRequest { req: HttpTestRequest::default().method(Method::POST).take(), - extensions: Extensions::new(), + config: AppConfigInner::default(), rmap: ResourceMap::new(ResourceDef::new("")), } } @@ -247,8 +248,15 @@ impl TestRequest { } /// Set request config - pub fn config(mut self, data: T) -> Self { - self.extensions.insert(data); + pub fn config(self, data: T) -> Self { + self.config.extensions.borrow_mut().insert(data); + self + } + + #[cfg(test)] + /// Set request config + pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self { + self.rmap = rmap; self } @@ -260,7 +268,7 @@ impl TestRequest { Path::new(Url::new(req.uri().clone())), req, Rc::new(self.rmap), - Rc::new(self.extensions), + AppConfig::new(self.config), ) } @@ -277,7 +285,7 @@ impl TestRequest { Path::new(Url::new(req.uri().clone())), req, Rc::new(self.rmap), - Rc::new(self.extensions), + AppConfig::new(self.config), ) .into_request() } @@ -290,7 +298,7 @@ impl TestRequest { Path::new(Url::new(req.uri().clone())), req, Rc::new(self.rmap), - Rc::new(self.extensions), + AppConfig::new(self.config), ); ServiceFromRequest::new(req, None) } From d2dba028f60c1b9d4f75bc96ece6162baa3dfcd4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 14:07:43 -0800 Subject: [PATCH 1026/1635] fix dependency link --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c340825..dbc0a65d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,10 +70,9 @@ actix-service = { git = "https://github.com/actix/actix-net.git" } actix-utils = { git = "https://github.com/actix/actix-net.git" } actix-http = { git = "https://github.com/actix/actix-http.git" } -#actix-router = { git = "https://github.com/actix/actix-net.git" } +actix-router = { git = "https://github.com/actix/actix-net.git" } actix-server = { git = "https://github.com/actix/actix-net.git" } actix-server-config = { git = "https://github.com/actix/actix-net.git" } -actix-router = { path = "../actix-net/router" } bytes = "0.4" derive_more = "0.14" From 6c4be45787ed0a3f684f844a67402eeebb395c77 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 14:33:33 -0800 Subject: [PATCH 1027/1635] update deps --- Cargo.toml | 14 ++++---------- test-server/Cargo.toml | 8 +++----- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 594ab88d..ae81f152 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,15 +38,10 @@ ssl = ["openssl", "actix-connector/ssl"] fail = ["failure"] [dependencies] -#actix-service = "0.3.2" +actix-service = "0.3.3" actix-codec = "0.1.1" - -#actix-connector = "0.3.0" -#actix-utils = "0.3.1" - -actix-connector = { git="https://github.com/actix/actix-net.git" } -actix-service = { git="https://github.com/actix/actix-net.git" } -actix-utils = { git="https://github.com/actix/actix-net.git" } +actix-connector = "0.3.0" +actix-utils = "0.3.3" actix-server-config = { git="https://github.com/actix/actix-net.git" } base64 = "0.10" @@ -90,8 +85,7 @@ failure = { version = "0.1.5", optional = true } actix-rt = "0.2.0" #actix-server = { version = "0.3.0", features=["ssl"] } actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } -#actix-connector = { version = "0.3.0", features=["ssl"] } -actix-connector = { git="https://github.com/actix/actix-net.git", features=["ssl"] } +actix-connector = { version = "0.3.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 554ab20e..49f18f04 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -32,15 +32,13 @@ session = ["cookie/secure"] ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] [dependencies] -actix-codec = "0.1" +actix-codec = "0.1.1" actix-rt = "0.2.0" actix-http = { path=".." } -#actix-service = "0.3.2" -actix-service = { git="https://github.com/actix/actix-net.git" } +actix-service = "0.3.3" #actix-server = "0.3.0" actix-server = { git="https://github.com/actix/actix-net.git" } -#actix-utils = "0.3.2" -actix-utils = { git="https://github.com/actix/actix-net.git" } +actix-utils = "0.3.2" base64 = "0.10" bytes = "0.4" From 85664cc6f7499b57ca979341780278eabeed1f83 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 14:56:18 -0800 Subject: [PATCH 1028/1635] update deps --- Cargo.toml | 12 +++--------- actix-files/Cargo.toml | 3 +-- actix-session/Cargo.toml | 12 ++++-------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dbc0a65d..6be1996e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,16 +61,13 @@ ssl = ["openssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1.0" -#actix-service = "0.3.2" -#actix-utils = "0.3.1" +actix-service = "0.3.3" +actix-utils = "0.3.3" +actix-router = "0.1.0" actix-rt = "0.2.0" actix-web-codegen = { path="actix-web-codegen" } -actix-service = { git = "https://github.com/actix/actix-net.git" } -actix-utils = { git = "https://github.com/actix/actix-net.git" } - actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-router = { git = "https://github.com/actix/actix-net.git" } actix-server = { git = "https://github.com/actix/actix-net.git" } actix-server-config = { git = "https://github.com/actix/actix-net.git" } @@ -116,6 +113,3 @@ serde_derive = "1.0" lto = true opt-level = 3 codegen-units = 1 - -[patch.crates-io] -actix-service = { git = "https://github.com/actix/actix-net.git" } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index bd61c880..c0f38b9a 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -20,8 +20,7 @@ path = "src/lib.rs" [dependencies] actix-web = { path=".." } actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-service = { git = "https://github.com/actix/actix-net.git" } -#actix-service = "0.3.0" +actix-service = "0.3.3" bytes = "0.4" futures = "0.1" diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 3bbeb4f8..421c6fc4 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -25,13 +25,9 @@ cookie-session = ["cookie/secure"] [dependencies] actix-web = { path=".." } -actix-codec = "0.1.0" - -#actix-service = "0.3.2" -#actix-utils = "0.3.1" -actix-service = { git = "https://github.com/actix/actix-net.git" } -actix-utils = { git = "https://github.com/actix/actix-net.git" } - +actix-codec = "0.1.1" +actix-service = "0.3.3" +actix-utils = "0.3.3" actix-http = { git = "https://github.com/actix/actix-http.git" } actix-router = { git = "https://github.com/actix/actix-net.git" } actix-server = { git = "https://github.com/actix/actix-net.git" } @@ -48,4 +44,4 @@ serde_json = "1.0" time = "0.1" [dev-dependencies] -actix-rt = "0.1.0" +actix-rt = "0.2.0" From 513ce0b08d51b75d73a98d6087cd329ee95ba09c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 17:42:35 -0800 Subject: [PATCH 1029/1635] add json and form client request's method --- src/client/request.rs | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/client/request.rs b/src/client/request.rs index 7e971756..efae5b94 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -7,6 +7,8 @@ use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use serde::Serialize; +use serde_json; use crate::body::{BodyStream, MessageBody}; use crate::error::Error; @@ -558,6 +560,48 @@ impl ClientRequestBuilder { Ok(ClientRequest { head, body }) } + /// Set a JSON body and generate `ClientRequest` + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn json( + &mut self, + value: T, + ) -> Result, Error> { + let body = serde_json::to_string(&value)?; + + let contains = if let Some(head) = parts(&mut self.head, &self.err) { + head.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; + if !contains { + self.header(header::CONTENT_TYPE, "application/json"); + } + + Ok(self.body(body)?) + } + + /// Set a urlencoded body and generate `ClientRequest` + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn form( + &mut self, + value: T, + ) -> Result, Error> { + let body = serde_urlencoded::to_string(&value)?; + + let contains = if let Some(head) = parts(&mut self.head, &self.err) { + head.headers.contains_key(header::CONTENT_TYPE) + } else { + true + }; + if !contains { + self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); + } + + Ok(self.body(body)?) + } + /// Set an streaming body and generate `ClientRequest`. /// /// `ClientRequestBuilder` can not be used after this call. From 134863d5c828fad574d75fa40347a7cfc379a5b4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 18:04:40 -0800 Subject: [PATCH 1030/1635] move middlewares --- errhandlers.rs | 141 +++++++++++++++++ identity.rs | 399 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 540 insertions(+) create mode 100644 errhandlers.rs create mode 100644 identity.rs diff --git a/errhandlers.rs b/errhandlers.rs new file mode 100644 index 00000000..c7d19d33 --- /dev/null +++ b/errhandlers.rs @@ -0,0 +1,141 @@ +use std::collections::HashMap; + +use error::Result; +use http::StatusCode; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middleware::{Middleware, Response}; + +type ErrorHandler = Fn(&HttpRequest, HttpResponse) -> Result; + +/// `Middleware` for allowing custom handlers for responses. +/// +/// You can use `ErrorHandlers::handler()` method to register a custom error +/// handler for specific status code. You can modify existing response or +/// create completely new one. +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::middleware::{ErrorHandlers, Response}; +/// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; +/// +/// fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { +/// let mut builder = resp.into_builder(); +/// builder.header(http::header::CONTENT_TYPE, "application/json"); +/// Ok(Response::Done(builder.into())) +/// } +/// +/// fn main() { +/// let app = App::new() +/// .middleware( +/// ErrorHandlers::new() +/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), +/// ) +/// .resource("/test", |r| { +/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); +/// r.method(http::Method::HEAD) +/// .f(|_| HttpResponse::MethodNotAllowed()); +/// }) +/// .finish(); +/// } +/// ``` +pub struct ErrorHandlers { + handlers: HashMap>>, +} + +impl Default for ErrorHandlers { + fn default() -> Self { + ErrorHandlers { + handlers: HashMap::new(), + } + } +} + +impl ErrorHandlers { + /// Construct new `ErrorHandlers` instance + pub fn new() -> Self { + ErrorHandlers::default() + } + + /// Register error handler for specified status code + pub fn handler(mut self, status: StatusCode, handler: F) -> Self + where + F: Fn(&HttpRequest, HttpResponse) -> Result + 'static, + { + self.handlers.insert(status, Box::new(handler)); + self + } +} + +impl Middleware for ErrorHandlers { + fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { + if let Some(handler) = self.handlers.get(&resp.status()) { + handler(req, resp) + } else { + Ok(Response::Done(resp)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use error::{Error, ErrorInternalServerError}; + use http::header::CONTENT_TYPE; + use http::StatusCode; + use httpmessage::HttpMessage; + use middleware::Started; + use test::{self, TestRequest}; + + fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { + let mut builder = resp.into_builder(); + builder.header(CONTENT_TYPE, "0001"); + Ok(Response::Done(builder.into())) + } + + #[test] + fn test_handler() { + let mw = + ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + + let mut req = TestRequest::default().finish(); + let resp = HttpResponse::InternalServerError().finish(); + let resp = match mw.response(&mut req, resp) { + Ok(Response::Done(resp)) => resp, + _ => panic!(), + }; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + + let resp = HttpResponse::Ok().finish(); + let resp = match mw.response(&mut req, resp) { + Ok(Response::Done(resp)) => resp, + _ => panic!(), + }; + assert!(!resp.headers().contains_key(CONTENT_TYPE)); + } + + struct MiddlewareOne; + + impl Middleware for MiddlewareOne { + fn start(&self, _: &HttpRequest) -> Result { + Err(ErrorInternalServerError("middleware error")) + } + } + + #[test] + fn test_middleware_start_error() { + let mut srv = test::TestServer::new(move |app| { + app.middleware( + ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), + ).middleware(MiddlewareOne) + .handler(|_| HttpResponse::Ok()) + }); + + let request = srv.get().finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001"); + } +} diff --git a/identity.rs b/identity.rs new file mode 100644 index 00000000..a664ba1f --- /dev/null +++ b/identity.rs @@ -0,0 +1,399 @@ +//! Request identity service for Actix applications. +//! +//! [**IdentityService**](struct.IdentityService.html) middleware can be +//! used with different policies types to store identity information. +//! +//! By default, only cookie identity policy is implemented. Other backend +//! implementations can be added separately. +//! +//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html) +//! uses cookies as identity storage. +//! +//! To access current request identity +//! [**RequestIdentity**](trait.RequestIdentity.html) should be used. +//! *HttpRequest* implements *RequestIdentity* trait. +//! +//! ```rust +//! use actix_web::middleware::identity::RequestIdentity; +//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; +//! use actix_web::*; +//! +//! fn index(req: HttpRequest) -> Result { +//! // access request identity +//! if let Some(id) = req.identity() { +//! Ok(format!("Welcome! {}", id)) +//! } else { +//! Ok("Welcome Anonymous!".to_owned()) +//! } +//! } +//! +//! fn login(mut req: HttpRequest) -> HttpResponse { +//! req.remember("User1".to_owned()); // <- remember identity +//! HttpResponse::Ok().finish() +//! } +//! +//! fn logout(mut req: HttpRequest) -> HttpResponse { +//! req.forget(); // <- remove identity +//! HttpResponse::Ok().finish() +//! } +//! +//! fn main() { +//! let app = App::new().middleware(IdentityService::new( +//! // <- create identity middleware +//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend +//! .name("auth-cookie") +//! .secure(false), +//! )); +//! } +//! ``` +use std::rc::Rc; + +use cookie::{Cookie, CookieJar, Key, SameSite}; +use futures::future::{err as FutErr, ok as FutOk, FutureResult}; +use futures::Future; +use time::Duration; + +use error::{Error, Result}; +use http::header::{self, HeaderValue}; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middleware::{Middleware, Response, Started}; + +/// The helper trait to obtain your identity from a request. +/// +/// ```rust +/// use actix_web::middleware::identity::RequestIdentity; +/// use actix_web::*; +/// +/// fn index(req: HttpRequest) -> Result { +/// // access request identity +/// if let Some(id) = req.identity() { +/// Ok(format!("Welcome! {}", id)) +/// } else { +/// Ok("Welcome Anonymous!".to_owned()) +/// } +/// } +/// +/// fn login(mut req: HttpRequest) -> HttpResponse { +/// req.remember("User1".to_owned()); // <- remember identity +/// HttpResponse::Ok().finish() +/// } +/// +/// fn logout(mut req: HttpRequest) -> HttpResponse { +/// req.forget(); // <- remove identity +/// HttpResponse::Ok().finish() +/// } +/// # fn main() {} +/// ``` +pub trait RequestIdentity { + /// Return the claimed identity of the user associated request or + /// ``None`` if no identity can be found associated with the request. + fn identity(&self) -> Option; + + /// Remember identity. + fn remember(&self, identity: String); + + /// This method is used to 'forget' the current identity on subsequent + /// requests. + fn forget(&self); +} + +impl RequestIdentity for HttpRequest { + fn identity(&self) -> Option { + if let Some(id) = self.extensions().get::() { + return id.0.identity().map(|s| s.to_owned()); + } + None + } + + fn remember(&self, identity: String) { + if let Some(id) = self.extensions_mut().get_mut::() { + return id.0.as_mut().remember(identity); + } + } + + fn forget(&self) { + if let Some(id) = self.extensions_mut().get_mut::() { + return id.0.forget(); + } + } +} + +/// An identity +pub trait Identity: 'static { + /// Return the claimed identity of the user associated request or + /// ``None`` if no identity can be found associated with the request. + fn identity(&self) -> Option<&str>; + + /// Remember identity. + fn remember(&mut self, key: String); + + /// This method is used to 'forget' the current identity on subsequent + /// requests. + fn forget(&mut self); + + /// Write session to storage backend. + fn write(&mut self, resp: HttpResponse) -> Result; +} + +/// Identity policy definition. +pub trait IdentityPolicy: Sized + 'static { + /// The associated identity + type Identity: Identity; + + /// The return type of the middleware + type Future: Future; + + /// Parse the session from request and load data from a service identity. + fn from_request(&self, request: &HttpRequest) -> Self::Future; +} + +/// Request identity middleware +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; +/// use actix_web::App; +/// +/// fn main() { +/// let app = App::new().middleware(IdentityService::new( +/// // <- create identity middleware +/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend +/// .name("auth-cookie") +/// .secure(false), +/// )); +/// } +/// ``` +pub struct IdentityService { + backend: T, +} + +impl IdentityService { + /// Create new identity service with specified backend. + pub fn new(backend: T) -> Self { + IdentityService { backend } + } +} + +struct IdentityBox(Box); + +impl> Middleware for IdentityService { + fn start(&self, req: &HttpRequest) -> Result { + let req = req.clone(); + let fut = self.backend.from_request(&req).then(move |res| match res { + Ok(id) => { + req.extensions_mut().insert(IdentityBox(Box::new(id))); + FutOk(None) + } + Err(err) => FutErr(err), + }); + Ok(Started::Future(Box::new(fut))) + } + + fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { + if let Some(ref mut id) = req.extensions_mut().get_mut::() { + id.0.as_mut().write(resp) + } else { + Ok(Response::Done(resp)) + } + } +} + +#[doc(hidden)] +/// Identity that uses private cookies as identity storage. +pub struct CookieIdentity { + changed: bool, + identity: Option, + inner: Rc, +} + +impl Identity for CookieIdentity { + fn identity(&self) -> Option<&str> { + self.identity.as_ref().map(|s| s.as_ref()) + } + + fn remember(&mut self, value: String) { + self.changed = true; + self.identity = Some(value); + } + + fn forget(&mut self) { + self.changed = true; + self.identity = None; + } + + fn write(&mut self, mut resp: HttpResponse) -> Result { + if self.changed { + let _ = self.inner.set_cookie(&mut resp, self.identity.take()); + } + Ok(Response::Done(resp)) + } +} + +struct CookieIdentityInner { + key: Key, + name: String, + path: String, + domain: Option, + secure: bool, + max_age: Option, + same_site: Option, +} + +impl CookieIdentityInner { + fn new(key: &[u8]) -> CookieIdentityInner { + CookieIdentityInner { + key: Key::from_master(key), + name: "actix-identity".to_owned(), + path: "/".to_owned(), + domain: None, + secure: true, + max_age: None, + same_site: None, + } + } + + fn set_cookie(&self, resp: &mut HttpResponse, id: Option) -> Result<()> { + let some = id.is_some(); + { + let id = id.unwrap_or_else(String::new); + let mut cookie = Cookie::new(self.name.clone(), id); + cookie.set_path(self.path.clone()); + cookie.set_secure(self.secure); + cookie.set_http_only(true); + + if let Some(ref domain) = self.domain { + cookie.set_domain(domain.clone()); + } + + if let Some(max_age) = self.max_age { + cookie.set_max_age(max_age); + } + + if let Some(same_site) = self.same_site { + cookie.set_same_site(same_site); + } + + let mut jar = CookieJar::new(); + if some { + jar.private(&self.key).add(cookie); + } else { + jar.add_original(cookie.clone()); + jar.private(&self.key).remove(cookie); + } + + for cookie in jar.delta() { + let val = HeaderValue::from_str(&cookie.to_string())?; + resp.headers_mut().append(header::SET_COOKIE, val); + } + } + + Ok(()) + } + + fn load(&self, req: &HttpRequest) -> Option { + if let Ok(cookies) = req.cookies() { + for cookie in cookies.iter() { + if cookie.name() == self.name { + let mut jar = CookieJar::new(); + jar.add_original(cookie.clone()); + + let cookie_opt = jar.private(&self.key).get(&self.name); + if let Some(cookie) = cookie_opt { + return Some(cookie.value().into()); + } + } + } + } + None + } +} + +/// Use cookies for request identity storage. +/// +/// The constructors take a key as an argument. +/// This is the private key for cookie - when this value is changed, +/// all identities are lost. The constructors will panic if the key is less +/// than 32 bytes in length. +/// +/// # Example +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; +/// use actix_web::App; +/// +/// fn main() { +/// let app = App::new().middleware(IdentityService::new( +/// // <- create identity middleware +/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy +/// .domain("www.rust-lang.org") +/// .name("actix_auth") +/// .path("/") +/// .secure(true), +/// )); +/// } +/// ``` +pub struct CookieIdentityPolicy(Rc); + +impl CookieIdentityPolicy { + /// Construct new `CookieIdentityPolicy` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn new(key: &[u8]) -> CookieIdentityPolicy { + CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key))) + } + + /// Sets the `path` field in the session cookie being built. + pub fn path>(mut self, value: S) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().path = value.into(); + self + } + + /// Sets the `name` field in the session cookie being built. + pub fn name>(mut self, value: S) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().name = value.into(); + self + } + + /// Sets the `domain` field in the session cookie being built. + pub fn domain>(mut self, value: S) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into()); + self + } + + /// Sets the `secure` field in the session cookie being built. + /// + /// If the `secure` field is set, a cookie will only be transmitted when the + /// connection is secure - i.e. `https` + pub fn secure(mut self, value: bool) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().secure = value; + self + } + + /// Sets the `max-age` field in the session cookie being built. + pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); + self + } + + /// Sets the `same_site` field in the session cookie being built. + pub fn same_site(mut self, same_site: SameSite) -> Self { + Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site); + self + } +} + +impl IdentityPolicy for CookieIdentityPolicy { + type Identity = CookieIdentity; + type Future = FutureResult; + + fn from_request(&self, req: &HttpRequest) -> Self::Future { + let identity = self.0.load(req); + FutOk(CookieIdentity { + identity, + changed: false, + inner: Rc::clone(&self.0), + }) + } +} From 12f0c78091a7d2ec668345e231aed8ad3318b88d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 20:40:09 -0800 Subject: [PATCH 1031/1635] port identity middleware --- Cargo.toml | 7 +- errhandlers.rs | 141 ---------- identity.rs => src/middleware/identity.rs | 320 +++++++++++++--------- src/middleware/mod.rs | 7 +- src/service.rs | 11 +- 5 files changed, 211 insertions(+), 275 deletions(-) delete mode 100644 errhandlers.rs rename identity.rs => src/middleware/identity.rs (53%) diff --git a/Cargo.toml b/Cargo.toml index 6be1996e..c64f4bc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,10 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "rust-tls"] #, "session"] +features = ["ssl", "tls", "rust-tls", "session"] [features] -default = ["brotli", "flate2-c"] +default = ["brotli", "flate2-c", "session"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -48,7 +48,7 @@ flate2-c = ["flate2/miniz-sys"] flate2-rust = ["flate2/rust_backend"] # sessions feature, session require "ring" crate and c compiler -# session = ["actix-session"] +session = ["cookie/secure"] # tls tls = ["native-tls", "actix-server/ssl"] @@ -72,6 +72,7 @@ actix-server = { git = "https://github.com/actix/actix-net.git" } actix-server-config = { git = "https://github.com/actix/actix-net.git" } bytes = "0.4" +cookie = { version="0.11", features=["percent-encode"] } derive_more = "0.14" encoding = "0.2" futures = "0.1" diff --git a/errhandlers.rs b/errhandlers.rs deleted file mode 100644 index c7d19d33..00000000 --- a/errhandlers.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::collections::HashMap; - -use error::Result; -use http::StatusCode; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response}; - -type ErrorHandler = Fn(&HttpRequest, HttpResponse) -> Result; - -/// `Middleware` for allowing custom handlers for responses. -/// -/// You can use `ErrorHandlers::handler()` method to register a custom error -/// handler for specific status code. You can modify existing response or -/// create completely new one. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::{ErrorHandlers, Response}; -/// use actix_web::{http, App, HttpRequest, HttpResponse, Result}; -/// -/// fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { -/// let mut builder = resp.into_builder(); -/// builder.header(http::header::CONTENT_TYPE, "application/json"); -/// Ok(Response::Done(builder.into())) -/// } -/// -/// fn main() { -/// let app = App::new() -/// .middleware( -/// ErrorHandlers::new() -/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), -/// ) -/// .resource("/test", |r| { -/// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -/// r.method(http::Method::HEAD) -/// .f(|_| HttpResponse::MethodNotAllowed()); -/// }) -/// .finish(); -/// } -/// ``` -pub struct ErrorHandlers { - handlers: HashMap>>, -} - -impl Default for ErrorHandlers { - fn default() -> Self { - ErrorHandlers { - handlers: HashMap::new(), - } - } -} - -impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance - pub fn new() -> Self { - ErrorHandlers::default() - } - - /// Register error handler for specified status code - pub fn handler(mut self, status: StatusCode, handler: F) -> Self - where - F: Fn(&HttpRequest, HttpResponse) -> Result + 'static, - { - self.handlers.insert(status, Box::new(handler)); - self - } -} - -impl Middleware for ErrorHandlers { - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(handler) = self.handlers.get(&resp.status()) { - handler(req, resp) - } else { - Ok(Response::Done(resp)) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use error::{Error, ErrorInternalServerError}; - use http::header::CONTENT_TYPE; - use http::StatusCode; - use httpmessage::HttpMessage; - use middleware::Started; - use test::{self, TestRequest}; - - fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { - let mut builder = resp.into_builder(); - builder.header(CONTENT_TYPE, "0001"); - Ok(Response::Done(builder.into())) - } - - #[test] - fn test_handler() { - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - - let mut req = TestRequest::default().finish(); - let resp = HttpResponse::InternalServerError().finish(); - let resp = match mw.response(&mut req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - - let resp = HttpResponse::Ok().finish(); - let resp = match mw.response(&mut req, resp) { - Ok(Response::Done(resp)) => resp, - _ => panic!(), - }; - assert!(!resp.headers().contains_key(CONTENT_TYPE)); - } - - struct MiddlewareOne; - - impl Middleware for MiddlewareOne { - fn start(&self, _: &HttpRequest) -> Result { - Err(ErrorInternalServerError("middleware error")) - } - } - - #[test] - fn test_middleware_start_error() { - let mut srv = test::TestServer::new(move |app| { - app.middleware( - ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), - ).middleware(MiddlewareOne) - .handler(|_| HttpResponse::Ok()) - }); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.headers().get(CONTENT_TYPE).unwrap(), "0001"); - } -} diff --git a/identity.rs b/src/middleware/identity.rs similarity index 53% rename from identity.rs rename to src/middleware/identity.rs index a664ba1f..d04ed717 100644 --- a/identity.rs +++ b/src/middleware/identity.rs @@ -14,26 +14,26 @@ //! *HttpRequest* implements *RequestIdentity* trait. //! //! ```rust -//! use actix_web::middleware::identity::RequestIdentity; +//! use actix_web::middleware::identity::Identity; //! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; //! use actix_web::*; //! -//! fn index(req: HttpRequest) -> Result { +//! fn index(id: Identity) -> String { //! // access request identity -//! if let Some(id) = req.identity() { -//! Ok(format!("Welcome! {}", id)) +//! if let Some(id) = id.identity() { +//! format!("Welcome! {}", id) //! } else { -//! Ok("Welcome Anonymous!".to_owned()) +//! "Welcome Anonymous!".to_owned() //! } //! } //! -//! fn login(mut req: HttpRequest) -> HttpResponse { -//! req.remember("User1".to_owned()); // <- remember identity +//! fn login(id: Idenity) -> HttpResponse { +//! id.remember("User1".to_owned()); // <- remember identity //! HttpResponse::Ok().finish() //! } //! -//! fn logout(mut req: HttpRequest) -> HttpResponse { -//! req.forget(); // <- remove identity +//! fn logout(id: Identity) -> HttpResponse { +//! id.forget(); // <- remove identity //! HttpResponse::Ok().finish() //! } //! @@ -42,118 +42,144 @@ //! // <- create identity middleware //! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend //! .name("auth-cookie") -//! .secure(false), +//! .secure(false)) +//! .service(web::resource("/index.html").to(index) +//! .service(web::resource("/login.html").to(login) +//! .service(web::resource("/logout.html").to(logout) //! )); //! } //! ``` +use std::cell::RefCell; use std::rc::Rc; +use actix_service::{Service, Transform}; use cookie::{Cookie, CookieJar, Key, SameSite}; -use futures::future::{err as FutErr, ok as FutOk, FutureResult}; -use futures::Future; +use futures::future::{ok, Either, FutureResult}; +use futures::{Future, IntoFuture, Poll}; use time::Duration; -use error::{Error, Result}; -use http::header::{self, HeaderValue}; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; +use crate::error::{Error, Result}; +use crate::http::header::{self, HeaderValue}; +use crate::request::HttpRequest; +use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; +use crate::FromRequest; +use crate::HttpMessage; -/// The helper trait to obtain your identity from a request. +/// The extractor type to obtain your identity from a request. /// /// ```rust -/// use actix_web::middleware::identity::RequestIdentity; /// use actix_web::*; +/// use actix_web::middleware::identity::Identity; /// -/// fn index(req: HttpRequest) -> Result { +/// fn index(id: Identity) -> Result { /// // access request identity -/// if let Some(id) = req.identity() { +/// if let Some(id) = id.identity() { /// Ok(format!("Welcome! {}", id)) /// } else { /// Ok("Welcome Anonymous!".to_owned()) /// } /// } /// -/// fn login(mut req: HttpRequest) -> HttpResponse { -/// req.remember("User1".to_owned()); // <- remember identity +/// fn login(id: Identity) -> HttpResponse { +/// id.remember("User1".to_owned()); // <- remember identity /// HttpResponse::Ok().finish() /// } /// -/// fn logout(mut req: HttpRequest) -> HttpResponse { -/// req.forget(); // <- remove identity +/// fn logout(id: Identity) -> HttpResponse { +/// id.forget(); // <- remove identity /// HttpResponse::Ok().finish() /// } /// # fn main() {} /// ``` -pub trait RequestIdentity { +#[derive(Clone)] +pub struct Identity(HttpRequest); + +impl Identity { /// Return the claimed identity of the user associated request or /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option; + pub fn identity(&self) -> Option { + if let Some(id) = self.0.extensions().get::() { + id.id.clone() + } else { + None + } + } /// Remember identity. - fn remember(&self, identity: String); + pub fn remember(&self, identity: String) { + if let Some(id) = self.0.extensions_mut().get_mut::() { + id.id = Some(identity); + id.changed = true; + } + } /// This method is used to 'forget' the current identity on subsequent /// requests. - fn forget(&self); -} - -impl RequestIdentity for HttpRequest { - fn identity(&self) -> Option { - if let Some(id) = self.extensions().get::() { - return id.0.identity().map(|s| s.to_owned()); - } - None - } - - fn remember(&self, identity: String) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.as_mut().remember(identity); - } - } - - fn forget(&self) { - if let Some(id) = self.extensions_mut().get_mut::() { - return id.0.forget(); + pub fn forget(&self) { + if let Some(id) = self.0.extensions_mut().get_mut::() { + id.id = None; + id.changed = true; } } } -/// An identity -pub trait Identity: 'static { - /// Return the claimed identity of the user associated request or - /// ``None`` if no identity can be found associated with the request. - fn identity(&self) -> Option<&str>; +struct IdentityItem { + id: Option, + changed: bool, +} - /// Remember identity. - fn remember(&mut self, key: String); +/// Extractor implementation for Identity type. +/// +/// ```rust +/// # use actix_web::*; +/// use actix_web::middleware::identity::Identity; +/// +/// fn index(id: Identity) -> String { +/// // access request identity +/// if let Some(id) = id.identity() { +/// format!("Welcome! {}", id) +/// } else { +/// "Welcome Anonymous!".to_owned() +/// } +/// } +/// # fn main() {} +/// ``` +impl

    FromRequest

    for Identity { + type Error = Error; + type Future = Result; + type Config = (); - /// This method is used to 'forget' the current identity on subsequent - /// requests. - fn forget(&mut self); - - /// Write session to storage backend. - fn write(&mut self, resp: HttpResponse) -> Result; + #[inline] + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { + Ok(Identity(req.clone())) + } } /// Identity policy definition. -pub trait IdentityPolicy: Sized + 'static { - /// The associated identity - type Identity: Identity; +pub trait IdentityPolicy: Sized + 'static { + /// The return type of the middleware + type Future: IntoFuture, Error = Error>; /// The return type of the middleware - type Future: Future; + type ResponseFuture: IntoFuture; /// Parse the session from request and load data from a service identity. - fn from_request(&self, request: &HttpRequest) -> Self::Future; + fn from_request

    (&self, request: &mut ServiceRequest

    ) -> Self::Future; + + /// Write changes to response + fn to_response( + &self, + identity: Option, + changed: bool, + response: &mut ServiceResponse, + ) -> Self::ResponseFuture; } /// Request identity middleware /// /// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; /// use actix_web::App; +/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; /// /// fn main() { /// let app = App::new().middleware(IdentityService::new( @@ -165,68 +191,97 @@ pub trait IdentityPolicy: Sized + 'static { /// } /// ``` pub struct IdentityService { - backend: T, + backend: Rc, } impl IdentityService { /// Create new identity service with specified backend. pub fn new(backend: T) -> Self { - IdentityService { backend } - } -} - -struct IdentityBox(Box); - -impl> Middleware for IdentityService { - fn start(&self, req: &HttpRequest) -> Result { - let req = req.clone(); - let fut = self.backend.from_request(&req).then(move |res| match res { - Ok(id) => { - req.extensions_mut().insert(IdentityBox(Box::new(id))); - FutOk(None) - } - Err(err) => FutErr(err), - }); - Ok(Started::Future(Box::new(fut))) - } - - fn response(&self, req: &HttpRequest, resp: HttpResponse) -> Result { - if let Some(ref mut id) = req.extensions_mut().get_mut::() { - id.0.as_mut().write(resp) - } else { - Ok(Response::Done(resp)) + IdentityService { + backend: Rc::new(backend), } } } -#[doc(hidden)] -/// Identity that uses private cookies as identity storage. -pub struct CookieIdentity { - changed: bool, - identity: Option, - inner: Rc, +impl Transform for IdentityService +where + P: 'static, + S: Service, Response = ServiceResponse> + 'static, + S::Future: 'static, + T: IdentityPolicy, + B: 'static, +{ + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = S::Error; + type InitError = (); + type Transform = IdentityServiceMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(IdentityServiceMiddleware { + backend: self.backend.clone(), + service: Rc::new(RefCell::new(service)), + }) + } } -impl Identity for CookieIdentity { - fn identity(&self) -> Option<&str> { - self.identity.as_ref().map(|s| s.as_ref()) +pub struct IdentityServiceMiddleware { + backend: Rc, + service: Rc>, +} + +impl Service for IdentityServiceMiddleware +where + P: 'static, + B: 'static, + S: Service, Response = ServiceResponse> + 'static, + S::Future: 'static, + T: IdentityPolicy, +{ + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = S::Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.borrow_mut().poll_ready() } - fn remember(&mut self, value: String) { - self.changed = true; - self.identity = Some(value); - } + fn call(&mut self, mut req: ServiceRequest

    ) -> Self::Future { + let srv = self.service.clone(); + let backend = self.backend.clone(); - fn forget(&mut self) { - self.changed = true; - self.identity = None; - } + Box::new( + self.backend.from_request(&mut req).into_future().then( + move |res| match res { + Ok(id) => { + req.extensions_mut() + .insert(IdentityItem { id, changed: false }); - fn write(&mut self, mut resp: HttpResponse) -> Result { - if self.changed { - let _ = self.inner.set_cookie(&mut resp, self.identity.take()); - } - Ok(Response::Done(resp)) + Either::A(srv.borrow_mut().call(req).and_then(move |mut res| { + let id = + res.request().extensions_mut().remove::(); + + if let Some(id) = id { + return Either::A( + backend + .to_response(id.id, id.changed, &mut res) + .into_future() + .then(move |t| match t { + Ok(_) => Ok(res), + Err(e) => Ok(res.error_response(e)), + }), + ); + } else { + Either::B(ok(res)) + } + })) + } + Err(err) => Either::B(ok(req.error_response(err))), + }, + ), + ) } } @@ -253,7 +308,11 @@ impl CookieIdentityInner { } } - fn set_cookie(&self, resp: &mut HttpResponse, id: Option) -> Result<()> { + fn set_cookie( + &self, + resp: &mut ServiceResponse, + id: Option, + ) -> Result<()> { let some = id.is_some(); { let id = id.unwrap_or_else(String::new); @@ -291,7 +350,7 @@ impl CookieIdentityInner { Ok(()) } - fn load(&self, req: &HttpRequest) -> Option { + fn load(&self, req: &ServiceRequest) -> Option { if let Ok(cookies) = req.cookies() { for cookie in cookies.iter() { if cookie.name() == self.name { @@ -384,16 +443,23 @@ impl CookieIdentityPolicy { } } -impl IdentityPolicy for CookieIdentityPolicy { - type Identity = CookieIdentity; - type Future = FutureResult; +impl IdentityPolicy for CookieIdentityPolicy { + type Future = Result, Error>; + type ResponseFuture = Result<(), Error>; - fn from_request(&self, req: &HttpRequest) -> Self::Future { - let identity = self.0.load(req); - FutOk(CookieIdentity { - identity, - changed: false, - inner: Rc::clone(&self.0), - }) + fn from_request

    (&self, req: &mut ServiceRequest

    ) -> Self::Future { + Ok(self.0.load(req)) + } + + fn to_response( + &self, + id: Option, + changed: bool, + res: &mut ServiceResponse, + ) -> Self::ResponseFuture { + if changed { + let _ = self.0.set_cookie(res, id); + } + Ok(()) } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index d63ca893..288c1d63 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -6,8 +6,11 @@ pub use self::compress::Compress; mod defaultheaders; pub use self::defaultheaders::DefaultHeaders; -#[cfg(feature = "session")] -pub use actix_session as session; +// #[cfg(feature = "session")] +// pub use actix_session as session; mod logger; pub use self::logger::Logger; + +#[cfg(feature = "session")] +pub mod identity; diff --git a/src/service.rs b/src/service.rs index f4b63a46..612fe4e8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -82,8 +82,9 @@ impl

    ServiceRequest

    { /// Create service response for error #[inline] - pub fn error_response>(self, err: E) -> ServiceResponse { - ServiceResponse::new(self.req, err.into().into()) + pub fn error_response>(self, err: E) -> ServiceResponse { + let res: Response = err.into().into(); + ServiceResponse::new(self.req, res.into_body()) } /// This method returns reference to the request head @@ -335,6 +336,12 @@ impl ServiceResponse { } } + /// Create service response for error + #[inline] + pub fn error_response>(self, err: E) -> Self { + Self::from_err(err, self.request) + } + /// Get reference to original request #[inline] pub fn request(&self) -> &HttpRequest { From be9031c55ef38b012476524b7562cfd58318931a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 20:48:05 -0800 Subject: [PATCH 1032/1635] update doc api --- src/middleware/identity.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index d04ed717..74e5f341 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -10,8 +10,7 @@ //! uses cookies as identity storage. //! //! To access current request identity -//! [**RequestIdentity**](trait.RequestIdentity.html) should be used. -//! *HttpRequest* implements *RequestIdentity* trait. +//! [**Identity**](trait.Identity.html) extractor should be used. //! //! ```rust //! use actix_web::middleware::identity::Identity; @@ -226,6 +225,7 @@ where } } +#[doc(hidden)] pub struct IdentityServiceMiddleware { backend: Rc, service: Rc>, From 3a2035a121048234b8cc63215b3df248a4e60c40 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 9 Mar 2019 21:15:26 -0800 Subject: [PATCH 1033/1635] fix doc tests --- src/middleware/identity.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 74e5f341..7cf739bc 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -26,7 +26,7 @@ //! } //! } //! -//! fn login(id: Idenity) -> HttpResponse { +//! fn login(id: Identity) -> HttpResponse { //! id.remember("User1".to_owned()); // <- remember identity //! HttpResponse::Ok().finish() //! } @@ -41,11 +41,10 @@ //! // <- create identity middleware //! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend //! .name("auth-cookie") -//! .secure(false)) -//! .service(web::resource("/index.html").to(index) -//! .service(web::resource("/login.html").to(login) -//! .service(web::resource("/logout.html").to(logout) -//! )); +//! .secure(false))) +//! .service(web::resource("/index.html").to(index)) +//! .service(web::resource("/login.html").to(login)) +//! .service(web::resource("/logout.html").to(logout)); //! } //! ``` use std::cell::RefCell; From 9b8812423c0efbb8acadf991f4ab883c6f1fee7b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 09:20:58 -0700 Subject: [PATCH 1034/1635] reexport Server controller form actix-server --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 6329d53c..32207024 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,7 @@ pub mod dev { Extensions, Payload, PayloadStream, RequestHead, ResponseHead, }; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; + pub use actix_server::Server; pub(crate) fn insert_slash(path: &str) -> String { let mut path = path.to_owned(); From 49d65fb07ace03b8f2750ebe3608255eb350f817 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 09:34:25 -0700 Subject: [PATCH 1035/1635] move extract to submodule --- src/{extract.rs => extract/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{extract.rs => extract/mod.rs} (100%) diff --git a/src/extract.rs b/src/extract/mod.rs similarity index 100% rename from src/extract.rs rename to src/extract/mod.rs From ee8725b58112d756117bf6fd5601fa14622dfb14 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 10:01:24 -0700 Subject: [PATCH 1036/1635] move extractors to separate submod --- src/extract/mod.rs | 997 +-------------------------------------------- 1 file changed, 20 insertions(+), 977 deletions(-) diff --git a/src/extract/mod.rs b/src/extract/mod.rs index c34d9df7..5d08dc07 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -1,32 +1,24 @@ -#![allow(dead_code)] -use std::ops::{Deref, DerefMut}; use std::rc::Rc; -use std::{fmt, str}; -use bytes::Bytes; -use encoding::all::UTF_8; -use encoding::types::{DecoderTrap, Encoding}; -use futures::future::{err, ok, Either, FutureResult}; -use futures::{future, Async, Future, IntoFuture, Poll, Stream}; -use mime::Mime; -use serde::de::{self, DeserializeOwned}; -use serde::Serialize; -use serde_json; -use serde_urlencoded; +use actix_http::error::Error; +use actix_http::Extensions; +use futures::future::ok; +use futures::{future, Async, Future, IntoFuture, Poll}; -use actix_http::dev::{JsonBody, MessageBody, UrlEncoded}; -use actix_http::error::{ - Error, ErrorBadRequest, ErrorNotFound, JsonPayloadError, PayloadError, - UrlencodedError, -}; -use actix_http::http::StatusCode; -use actix_http::{Extensions, HttpMessage, Response}; -use actix_router::PathDeserializer; - -use crate::request::HttpRequest; -use crate::responder::Responder; use crate::service::ServiceFromRequest; +mod form; +mod json; +mod path; +mod payload; +mod query; + +pub use self::form::{Form, FormConfig}; +pub use self::json::{Json, JsonConfig}; +pub use self::path::Path; +pub use self::payload::{Payload, PayloadConfig}; +pub use self::query::Query; + /// Trait implemented by types that can be extracted from request. /// /// Types that implement this trait can be used with `Route` handlers. @@ -72,882 +64,6 @@ impl ExtractorConfig for () { fn store_default(_: &mut ConfigStorage) {} } -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's path. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// fn index(info: web::Path<(String, u32)>) -> String { -/// format!("Welcome {}! {}", info.0, info.1) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/{count}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- register handler with `Path` extractor -/// ); -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// -/// ```rust -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App, Error}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract `Info` from a path using serde -/// fn index(info: web::Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- use handler with Path` extractor -/// ); -/// } -/// ``` -pub struct Path { - inner: T, -} - -impl AsRef for Path { - fn as_ref(&self) -> &T { - &self.inner - } -} - -impl Deref for Path { - type Target = T; - - fn deref(&self) -> &T { - &self.inner - } -} - -impl DerefMut for Path { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Path { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.inner - } - - /// Extract path information from a request - pub fn extract(req: &HttpRequest) -> Result, de::value::Error> - where - T: DeserializeOwned, - { - de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) - .map(|inner| Path { inner }) - } -} - -impl From for Path { - fn from(inner: T) -> Path { - Path { inner } - } -} - -/// Extract typed information from the request's path. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// fn index(info: web::Path<(String, u32)>) -> String { -/// format!("Welcome {}! {}", info.0, info.1) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/{count}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- register handler with `Path` extractor -/// ); -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// -/// ```rust -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App, Error}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract `Info` from a path using serde -/// fn index(info: web::Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- use handler with Path` extractor -/// ); -/// } -/// ``` -impl FromRequest

    for Path -where - T: DeserializeOwned, -{ - type Error = Error; - type Future = Result; - type Config = (); - - #[inline] - fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - Self::extract(req).map_err(ErrorNotFound) - } -} - -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -impl fmt::Display for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from from the request's query. -/// -/// ## Example -/// -/// ```rust -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App}; -/// -/// #[derive(Debug, Deserialize)] -/// pub enum ResponseType { -/// Token, -/// Code -/// } -/// -/// #[derive(Deserialize)] -/// pub struct AuthRequest { -/// id: u64, -/// response_type: ResponseType, -/// } -/// -/// // Use `Query` extractor for query information. -/// // This handler get called only if request's query contains `username` field -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: web::Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor -/// } -/// ``` -pub struct Query(T); - -impl Deref for Query { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Query { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl Query { - /// Deconstruct to a inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -/// Extract typed information from from the request's query. -/// -/// ## Example -/// -/// ```rust -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App}; -/// -/// #[derive(Debug, Deserialize)] -/// pub enum ResponseType { -/// Token, -/// Code -/// } -/// -/// #[derive(Deserialize)] -/// pub struct AuthRequest { -/// id: u64, -/// response_type: ResponseType, -/// } -/// -/// // Use `Query` extractor for query information. -/// // This handler get called only if request's query contains `username` field -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: web::Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// .route(web::get().to(index))); // <- use `Query` extractor -/// } -/// ``` -impl FromRequest

    for Query -where - T: de::DeserializeOwned, -{ - type Error = Error; - type Future = Result; - type Config = (); - - #[inline] - fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - serde_urlencoded::from_str::(req.query_string()) - .map(|val| Ok(Query(val))) - .unwrap_or_else(|e| Err(e.into())) - } -} - -impl fmt::Debug for Query { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Query { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's body. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// Extract form data using serde. -/// /// This handler get called only if content type is *x-www-form-urlencoded* -/// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: web::Form) -> String { -/// format!("Welcome {}!", form.username) -/// } -/// # fn main() {} -/// ``` -pub struct Form(pub T); - -impl Form { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Form { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Form { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl FromRequest

    for Form -where - T: DeserializeOwned + 'static, - P: Stream + 'static, -{ - type Error = Error; - type Future = Box>; - type Config = FormConfig; - - #[inline] - fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - let req2 = req.clone(); - let cfg = req.load_config::(); - - let limit = cfg.limit; - let err = Rc::clone(&cfg.ehandler); - Box::new( - UrlEncoded::new(req) - .limit(limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Form), - ) - } -} - -impl fmt::Debug for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Form { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Form extractor configuration -/// -/// ```rust -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// Extract form data using serde. -/// /// Custom configuration is used for this handler, max payload size is 4k -/// fn index(form: web::Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// .route(web::get() -/// // change `Form` extractor configuration -/// .config(web::FormConfig::default().limit(4097)) -/// .to(index)) -/// ); -/// } -/// ``` -#[derive(Clone)] -pub struct FormConfig { - limit: usize, - ehandler: Rc Error>, -} - -impl FormConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(mut self, f: F) -> Self - where - F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl ExtractorConfig for FormConfig {} - -impl Default for FormConfig { - fn default() -> Self { - FormConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - } - } -} - -/// Json helper -/// -/// Json can be used for two different purpose. First is for json response -/// generation and second is for extracting typed information from request's -/// payload. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body -/// fn index(info: web::Json) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::post().to(index)) -/// ); -/// } -/// ``` -/// -/// 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 -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// # -/// #[derive(Serialize)] -/// struct MyObj { -/// name: String, -/// } -/// -/// fn index(req: HttpRequest) -> Result> { -/// Ok(web::Json(MyObj { -/// name: req.match_info().get("name").unwrap().to_string(), -/// })) -/// } -/// # fn main() {} -/// ``` -pub struct Json(pub T); - -impl Json { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for Json { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Json { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl fmt::Debug for Json -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Json: {:?}", self.0) - } -} - -impl fmt::Display for Json -where - T: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl Responder for Json { - type Error = Error; - type Future = Result; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let body = match serde_json::to_string(&self.0) { - Ok(body) => body, - Err(e) => return Err(e.into()), - }; - - Ok(Response::build(StatusCode::OK) - .content_type("application/json") - .body(body)) - } -} - -/// Json extractor. Allow to extract typed information from request's -/// payload. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body -/// fn index(info: web::Json) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::post().to(index)) -/// ); -/// } -/// ``` -impl FromRequest

    for Json -where - T: DeserializeOwned + 'static, - P: Stream + 'static, -{ - type Error = Error; - type Future = Box>; - type Config = JsonConfig; - - #[inline] - fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - let req2 = req.clone(); - let cfg = req.load_config::(); - - let limit = cfg.limit; - let err = Rc::clone(&cfg.ehandler); - Box::new( - JsonBody::new(req) - .limit(limit) - .map_err(move |e| (*err)(e, &req2)) - .map(Json), - ) - } -} - -/// Json extractor configuration -/// -/// ```rust -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{error, web, App, HttpResponse}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: web::Json) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::post().config( -/// // change json extractor configuration -/// web::JsonConfig::default().limit(4096) -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// })) -/// .to(index)) -/// ); -/// } -/// ``` -#[derive(Clone)] -pub struct JsonConfig { - limit: usize, - ehandler: Rc Error>, -} - -impl JsonConfig { - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(mut self, f: F) -> Self - where - F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl ExtractorConfig for JsonConfig {} - -impl Default for JsonConfig { - fn default() -> Self { - JsonConfig { - limit: 262_144, - ehandler: Rc::new(|e, _| e.into()), - } - } -} - -/// Payload extractor returns request 's payload stream. -/// -/// ## Example -/// -/// ```rust -/// use futures::{Future, Stream}; -/// use actix_web::{web, error, App, Error, HttpResponse}; -/// -/// /// extract binary data from request -/// fn index

    (body: web::Payload

    ) -> impl Future -/// where -/// P: Stream -/// { -/// body.map_err(Error::from) -/// .fold(web::BytesMut::new(), move |mut body, chunk| { -/// body.extend_from_slice(&chunk); -/// Ok::<_, Error>(body) -/// }) -/// .and_then(|body| { -/// format!("Body {:?}!", body); -/// Ok(HttpResponse::Ok().finish()) -/// }) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to_async(index)) -/// ); -/// } -/// ``` -pub struct Payload(crate::dev::Payload); - -impl Stream for Payload -where - T: Stream, -{ - type Item = Bytes; - type Error = PayloadError; - - #[inline] - fn poll(&mut self) -> Poll, PayloadError> { - self.0.poll() - } -} - -/// Get request's payload stream -/// -/// ## Example -/// -/// ```rust -/// use futures::{Future, Stream}; -/// use actix_web::{web, error, App, Error, HttpResponse}; -/// -/// /// extract binary data from request -/// fn index

    (body: web::Payload

    ) -> impl Future -/// where -/// P: Stream -/// { -/// body.map_err(Error::from) -/// .fold(web::BytesMut::new(), move |mut body, chunk| { -/// body.extend_from_slice(&chunk); -/// Ok::<_, Error>(body) -/// }) -/// .and_then(|body| { -/// format!("Body {:?}!", body); -/// Ok(HttpResponse::Ok().finish()) -/// }) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to_async(index)) -/// ); -/// } -/// ``` -impl

    FromRequest

    for Payload

    -where - P: Stream, -{ - type Error = Error; - type Future = Result, Error>; - type Config = (); - - #[inline] - fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - Ok(Payload(req.take_payload())) - } -} - -/// Request binary data from a request's payload. -/// -/// Loads request's payload and construct Bytes instance. -/// -/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// use bytes::Bytes; -/// use actix_web::{web, App}; -/// -/// /// extract binary data from request -/// fn index(body: Bytes) -> String { -/// format!("Body {:?}!", body) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to(index)) -/// ); -/// } -/// ``` -impl

    FromRequest

    for Bytes -where - P: Stream + 'static, -{ - type Error = Error; - type Future = - Either>, FutureResult>; - type Config = PayloadConfig; - - #[inline] - fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - let cfg = req.load_config::(); - - if let Err(e) = cfg.check_mimetype(req) { - return Either::B(err(e)); - } - - let limit = cfg.limit; - Either::A(Box::new(MessageBody::new(req).limit(limit).from_err())) - } -} - -/// Extract text information from a request's body. -/// -/// Text extractor automatically decode body according to the request's charset. -/// -/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// -/// /// extract text data from request -/// fn index(text: String) -> String { -/// format!("Body {}!", text) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get() -/// .config(web::PayloadConfig::new(4096)) // <- limit size of the payload -/// .to(index)) // <- register handler with extractor params -/// ); -/// } -/// ``` -impl

    FromRequest

    for String -where - P: Stream + 'static, -{ - type Error = Error; - type Future = - Either>, FutureResult>; - type Config = PayloadConfig; - - #[inline] - fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { - let cfg = req.load_config::(); - - // check content-type - if let Err(e) = cfg.check_mimetype(req) { - return Either::B(err(e)); - } - - // check charset - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(e) => return Either::B(err(e.into())), - }; - let limit = cfg.limit; - - Either::A(Box::new( - MessageBody::new(req) - .limit(limit) - .from_err() - .and_then(move |body| { - let enc: *const Encoding = encoding as *const Encoding; - if enc == UTF_8 { - Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) - } else { - Ok(encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))?) - } - }), - )) - } -} - /// Optionally extract a field from the request /// /// If the FromRequest for T fails, return None rather than returning an error response @@ -1009,7 +125,10 @@ where fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { Box::new(T::from_request(req).into_future().then(|r| match r { Ok(v) => future::ok(Some(v)), - Err(_) => future::ok(None), + Err(e) => { + log::debug!("Error for Option extractor: {}", e.into()); + future::ok(None) + } })) } } @@ -1078,64 +197,6 @@ where } } -/// Payload configuration for request's payload. -#[derive(Clone)] -pub struct PayloadConfig { - limit: usize, - mimetype: Option, -} - -impl PayloadConfig { - /// Create `PayloadConfig` instance and set max size of payload. - pub fn new(limit: usize) -> Self { - Self::default().limit(limit) - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set required mime-type of the request. By default mime type is not - /// enforced. - pub fn mimetype(mut self, mt: Mime) -> Self { - self.mimetype = Some(mt); - self - } - - fn check_mimetype

    (&self, req: &ServiceFromRequest

    ) -> Result<(), Error> { - // check content-type - if let Some(ref mt) = self.mimetype { - match req.mime_type() { - Ok(Some(ref req_mt)) => { - if mt != req_mt { - return Err(ErrorBadRequest("Unexpected Content-Type")); - } - } - Ok(None) => { - return Err(ErrorBadRequest("Content-Type is expected")); - } - Err(err) => { - return Err(err.into()); - } - } - } - Ok(()) - } -} - -impl ExtractorConfig for PayloadConfig {} - -impl Default for PayloadConfig { - fn default() -> Self { - PayloadConfig { - limit: 262_144, - mimetype: None, - } - } -} - #[doc(hidden)] impl

    FromRequest

    for () { type Error = Error; @@ -1360,24 +421,6 @@ mod tests { assert!(r.is_err()); } - #[test] - fn test_payload_config() { - let req = TestRequest::default().to_from(); - let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .to_from(); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = - TestRequest::with_header(header::CONTENT_TYPE, "application/json").to_from(); - assert!(cfg.check_mimetype(&req).is_ok()); - } - #[derive(Deserialize)] struct MyStruct { key: String, From 16c42be4a2a753f0ec2f96875a37a0487bfbe4e5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 10:53:56 -0700 Subject: [PATCH 1037/1635] simplify extractor configuration, config is optional now --- src/extract/form.rs | 197 +++++++++++++++++++++++ src/extract/json.rs | 259 ++++++++++++++++++++++++++++++ src/extract/mod.rs | 75 +-------- src/extract/path.rs | 176 +++++++++++++++++++++ src/extract/payload.rs | 313 +++++++++++++++++++++++++++++++++++++ src/extract/query.rs | 126 +++++++++++++++ src/middleware/identity.rs | 1 - src/request.rs | 1 - src/route.rs | 18 ++- src/service.rs | 10 +- src/state.rs | 1 - 11 files changed, 1086 insertions(+), 91 deletions(-) create mode 100644 src/extract/form.rs create mode 100644 src/extract/json.rs create mode 100644 src/extract/path.rs create mode 100644 src/extract/payload.rs create mode 100644 src/extract/query.rs diff --git a/src/extract/form.rs b/src/extract/form.rs new file mode 100644 index 00000000..19849ac8 --- /dev/null +++ b/src/extract/form.rs @@ -0,0 +1,197 @@ +//! Form extractor + +use std::rc::Rc; +use std::{fmt, ops}; + +use actix_http::dev::UrlEncoded; +use actix_http::error::{Error, UrlencodedError}; +use bytes::Bytes; +use futures::{Future, Stream}; +use serde::de::DeserializeOwned; + +use crate::extract::FromRequest; +use crate::request::HttpRequest; +use crate::service::ServiceFromRequest; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +/// Extract typed information from the request's body. +/// +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. +/// +/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction +/// process. +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// /// Extract form data using serde. +/// /// This handler get called only if content type is *x-www-form-urlencoded* +/// /// and content of the request could be deserialized to a `FormData` struct +/// fn index(form: web::Form) -> String { +/// format!("Welcome {}!", form.username) +/// } +/// # fn main() {} +/// ``` +pub struct Form(pub T); + +impl Form { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Form { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Form { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl FromRequest

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

    ) -> Self::Future { + let req2 = req.clone(); + let (limit, err) = req + .load_config::() + .map(|c| (c.limit, c.ehandler.clone())) + .unwrap_or((16384, None)); + + Box::new( + UrlEncoded::new(req) + .limit(limit) + .map_err(move |e| { + if let Some(err) = err { + (*err)(e, &req2) + } else { + e.into() + } + }) + .map(Form), + ) + } +} + +impl fmt::Debug for Form { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for Form { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Form extractor configuration +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, Result}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// /// Extract form data using serde. +/// /// Custom configuration is used for this handler, max payload size is 4k +/// fn index(form: web::Form) -> Result { +/// Ok(format!("Welcome {}!", form.username)) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html") +/// .route(web::get() +/// // change `Form` extractor configuration +/// .config(web::FormConfig::default().limit(4097)) +/// .to(index)) +/// ); +/// } +/// ``` +#[derive(Clone)] +pub struct FormConfig { + limit: usize, + ehandler: Option Error>>, +} + +impl FormConfig { + /// Change max size of payload. By default max size is 16Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set custom error handler + pub fn error_handler(mut self, f: F) -> Self + where + F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, + { + self.ehandler = Some(Rc::new(f)); + self + } +} + +impl Default for FormConfig { + fn default() -> Self { + FormConfig { + limit: 16384, + ehandler: None, + } + } +} + +#[cfg(test)] +mod tests { + use actix_http::http::header; + use bytes::Bytes; + use serde_derive::Deserialize; + + use super::*; + use crate::test::{block_on, TestRequest}; + + #[derive(Deserialize, Debug, PartialEq)] + struct Info { + hello: String, + } + + #[test] + fn test_form() { + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .to_from(); + + let s = block_on(Form::::from_request(&mut req)).unwrap(); + assert_eq!(s.hello, "world"); + } +} diff --git a/src/extract/json.rs b/src/extract/json.rs new file mode 100644 index 00000000..f74b082d --- /dev/null +++ b/src/extract/json.rs @@ -0,0 +1,259 @@ +//! Json extractor/responder + +use std::rc::Rc; +use std::{fmt, ops}; + +use bytes::Bytes; +use futures::{Future, Stream}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json; + +use actix_http::dev::JsonBody; +use actix_http::error::{Error, JsonPayloadError}; +use actix_http::http::StatusCode; +use actix_http::Response; + +use crate::extract::FromRequest; +use crate::request::HttpRequest; +use crate::responder::Responder; +use crate::service::ServiceFromRequest; + +/// Json helper +/// +/// Json can be used for two different purpose. First is for json response +/// generation and second is for extracting typed information from request's +/// payload. +/// +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. +/// +/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// process. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body +/// fn index(info: web::Json) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().to(index)) +/// ); +/// } +/// ``` +/// +/// 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 +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// # +/// #[derive(Serialize)] +/// struct MyObj { +/// name: String, +/// } +/// +/// fn index(req: HttpRequest) -> Result> { +/// Ok(web::Json(MyObj { +/// name: req.match_info().get("name").unwrap().to_string(), +/// })) +/// } +/// # fn main() {} +/// ``` +pub struct Json(pub T); + +impl Json { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Json { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Json { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Json +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Json: {:?}", self.0) + } +} + +impl fmt::Display for Json +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl Responder for Json { + type Error = Error; + type Future = Result; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + let body = match serde_json::to_string(&self.0) { + Ok(body) => body, + Err(e) => return Err(e.into()), + }; + + Ok(Response::build(StatusCode::OK) + .content_type("application/json") + .body(body)) + } +} + +/// Json extractor. Allow to extract typed information from request's +/// payload. +/// +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. +/// +/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// process. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body +/// fn index(info: web::Json) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().to(index)) +/// ); +/// } +/// ``` +impl FromRequest

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

    ) -> Self::Future { + let req2 = req.clone(); + let (limit, err) = req + .load_config::() + .map(|c| (c.limit, c.ehandler.clone())) + .unwrap_or((32768, None)); + + Box::new( + JsonBody::new(req) + .limit(limit) + .map_err(move |e| { + if let Some(err) = err { + (*err)(e, &req2) + } else { + e.into() + } + }) + .map(Json), + ) + } +} + +/// Json extractor configuration +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{error, web, App, HttpResponse}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: web::Json) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().config( +/// // change json extractor configuration +/// web::JsonConfig::default().limit(4096) +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// })) +/// .to(index)) +/// ); +/// } +/// ``` +#[derive(Clone)] +pub struct JsonConfig { + limit: usize, + ehandler: Option Error>>, +} + +impl JsonConfig { + /// Change max size of payload. By default max size is 32Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set custom error handler + pub fn error_handler(mut self, f: F) -> Self + where + F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, + { + self.ehandler = Some(Rc::new(f)); + self + } +} + +impl Default for JsonConfig { + fn default() -> Self { + JsonConfig { + limit: 32768, + ehandler: None, + } + } +} diff --git a/src/extract/mod.rs b/src/extract/mod.rs index 5d08dc07..d8958b2d 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -1,7 +1,6 @@ -use std::rc::Rc; +//! Request extractors use actix_http::error::Error; -use actix_http::Extensions; use futures::future::ok; use futures::{future, Async, Future, IntoFuture, Poll}; @@ -29,41 +28,10 @@ pub trait FromRequest

    : Sized { /// Future that resolves to a Self type Future: IntoFuture; - /// Configuration for the extractor - type Config: ExtractorConfig; - /// Convert request to a Self fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future; } -/// Storage for extractor configs -#[derive(Default)] -pub struct ConfigStorage { - pub(crate) storage: Option>, -} - -impl ConfigStorage { - pub fn store(&mut self, config: C) { - if self.storage.is_none() { - self.storage = Some(Rc::new(Extensions::new())); - } - if let Some(ref mut ext) = self.storage { - Rc::get_mut(ext).unwrap().insert(config); - } - } -} - -pub trait ExtractorConfig: Default + Clone + 'static { - /// Set default configuration to config storage - fn store_default(ext: &mut ConfigStorage) { - ext.store(Self::default()) - } -} - -impl ExtractorConfig for () { - fn store_default(_: &mut ConfigStorage) {} -} - /// Optionally extract a field from the request /// /// If the FromRequest for T fails, return None rather than returning an error response @@ -84,7 +52,6 @@ impl ExtractorConfig for () { /// impl

    FromRequest

    for Thing { /// type Error = Error; /// type Future = Result; -/// type Config = (); /// /// fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { /// if rand::random() { @@ -119,7 +86,6 @@ where { type Error = Error; type Future = Box, Error = Error>>; - type Config = T::Config; #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { @@ -153,7 +119,6 @@ where /// impl

    FromRequest

    for Thing { /// type Error = Error; /// type Future = Result; -/// type Config = (); /// /// fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { /// if rand::random() { @@ -186,7 +151,6 @@ where { type Error = Error; type Future = Box, Error = Error>>; - type Config = T::Config; #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { @@ -201,23 +165,12 @@ where impl

    FromRequest

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

    ) -> Self::Future { Ok(()) } } -macro_rules! tuple_config ({ $($T:ident),+} => { - impl<$($T,)+> ExtractorConfig for ($($T,)+) - where $($T: ExtractorConfig + Clone,)+ - { - fn store_default(ext: &mut ConfigStorage) { - $($T::store_default(ext);)+ - } - } -}); - macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { /// FromRequest implementation for tuple @@ -226,7 +179,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { { type Error = Error; type Future = $fut_type; - type Config = ($($T::Config,)+); fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { $fut_type { @@ -277,17 +229,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { mod m { use super::*; -tuple_config!(A); -tuple_config!(A, B); -tuple_config!(A, B, C); -tuple_config!(A, B, C, D); -tuple_config!(A, B, C, D, E); -tuple_config!(A, B, C, D, E, F); -tuple_config!(A, B, C, D, E, F, G); -tuple_config!(A, B, C, D, E, F, G, H); -tuple_config!(A, B, C, D, E, F, G, H, I); -tuple_config!(A, B, C, D, E, F, G, H, I, J); - tuple_from_req!(TupleFromRequest1, (0, A)); tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); @@ -335,20 +276,6 @@ mod tests { assert_eq!(s, "hello=world"); } - #[test] - fn test_form() { - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); - - let s = block_on(Form::::from_request(&mut req)).unwrap(); - assert_eq!(s.hello, "world"); - } - #[test] fn test_option() { let mut req = TestRequest::with_header( diff --git a/src/extract/path.rs b/src/extract/path.rs new file mode 100644 index 00000000..d9461263 --- /dev/null +++ b/src/extract/path.rs @@ -0,0 +1,176 @@ +//! Path extractor + +use std::{fmt, ops}; + +use actix_http::error::{Error, ErrorNotFound}; +use actix_router::PathDeserializer; +use serde::de; + +use crate::request::HttpRequest; +use crate::service::ServiceFromRequest; + +use super::FromRequest; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +/// Extract typed information from the request's path. +/// +/// ## Example +/// +/// ```rust +/// use actix_web::{web, App}; +/// +/// /// extract path info from "/{username}/{count}/index.html" url +/// /// {username} - deserializes to a String +/// /// {count} - - deserializes to a u32 +/// fn index(info: web::Path<(String, u32)>) -> String { +/// format!("Welcome {}! {}", info.0, info.1) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{username}/{count}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- register handler with `Path` extractor +/// ); +/// } +/// ``` +/// +/// It is possible to extract path information to a specific type that +/// implements `Deserialize` trait from *serde*. +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, Error}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// extract `Info` from a path using serde +/// fn index(info: web::Path) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{username}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- use handler with Path` extractor +/// ); +/// } +/// ``` +pub struct Path { + inner: T, +} + +impl Path { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.inner + } + + /// Extract path information from a request + pub fn extract(req: &HttpRequest) -> Result, de::value::Error> + where + T: de::DeserializeOwned, + { + de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) + .map(|inner| Path { inner }) + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &T { + &self.inner + } +} + +impl ops::Deref for Path { + type Target = T; + + fn deref(&self) -> &T { + &self.inner + } +} + +impl ops::DerefMut for Path { + fn deref_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl From for Path { + fn from(inner: T) -> Path { + Path { inner } + } +} + +impl fmt::Debug for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl fmt::Display for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// Extract typed information from the request's path. +/// +/// ## Example +/// +/// ```rust +/// use actix_web::{web, App}; +/// +/// /// extract path info from "/{username}/{count}/index.html" url +/// /// {username} - deserializes to a String +/// /// {count} - - deserializes to a u32 +/// fn index(info: web::Path<(String, u32)>) -> String { +/// format!("Welcome {}! {}", info.0, info.1) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{username}/{count}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- register handler with `Path` extractor +/// ); +/// } +/// ``` +/// +/// It is possible to extract path information to a specific type that +/// implements `Deserialize` trait from *serde*. +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, Error}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// extract `Info` from a path using serde +/// fn index(info: web::Path) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{username}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- use handler with Path` extractor +/// ); +/// } +/// ``` +impl FromRequest

    for Path +where + T: de::DeserializeOwned, +{ + type Error = Error; + type Future = Result; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { + Self::extract(req).map_err(ErrorNotFound) + } +} diff --git a/src/extract/payload.rs b/src/extract/payload.rs new file mode 100644 index 00000000..82024c0d --- /dev/null +++ b/src/extract/payload.rs @@ -0,0 +1,313 @@ +//! Payload/Bytes/String extractors +use std::str; + +use actix_http::dev::MessageBody; +use actix_http::error::{Error, ErrorBadRequest, PayloadError}; +use actix_http::HttpMessage; +use bytes::Bytes; +use encoding::all::UTF_8; +use encoding::types::{DecoderTrap, Encoding}; +use futures::future::{err, Either, FutureResult}; +use futures::{Future, Poll, Stream}; +use mime::Mime; + +use crate::extract::FromRequest; +use crate::service::ServiceFromRequest; + +/// Payload extractor returns request 's payload stream. +/// +/// ## Example +/// +/// ```rust +/// use futures::{Future, Stream}; +/// use actix_web::{web, error, App, Error, HttpResponse}; +/// +/// /// extract binary data from request +/// fn index

    (body: web::Payload

    ) -> impl Future +/// where +/// P: Stream +/// { +/// body.map_err(Error::from) +/// .fold(web::BytesMut::new(), move |mut body, chunk| { +/// body.extend_from_slice(&chunk); +/// Ok::<_, Error>(body) +/// }) +/// .and_then(|body| { +/// format!("Body {:?}!", body); +/// Ok(HttpResponse::Ok().finish()) +/// }) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to_async(index)) +/// ); +/// } +/// ``` +pub struct Payload(crate::dev::Payload); + +impl Stream for Payload +where + T: Stream, +{ + type Item = Bytes; + type Error = PayloadError; + + #[inline] + fn poll(&mut self) -> Poll, PayloadError> { + self.0.poll() + } +} + +/// Get request's payload stream +/// +/// ## Example +/// +/// ```rust +/// use futures::{Future, Stream}; +/// use actix_web::{web, error, App, Error, HttpResponse}; +/// +/// /// extract binary data from request +/// fn index

    (body: web::Payload

    ) -> impl Future +/// where +/// P: Stream +/// { +/// body.map_err(Error::from) +/// .fold(web::BytesMut::new(), move |mut body, chunk| { +/// body.extend_from_slice(&chunk); +/// Ok::<_, Error>(body) +/// }) +/// .and_then(|body| { +/// format!("Body {:?}!", body); +/// Ok(HttpResponse::Ok().finish()) +/// }) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to_async(index)) +/// ); +/// } +/// ``` +impl

    FromRequest

    for Payload

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

    ) -> Self::Future { + Ok(Payload(req.take_payload())) + } +} + +/// Request binary data from a request's payload. +/// +/// Loads request's payload and construct Bytes instance. +/// +/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure +/// extraction process. +/// +/// ## Example +/// +/// ```rust +/// use bytes::Bytes; +/// use actix_web::{web, App}; +/// +/// /// extract binary data from request +/// fn index(body: Bytes) -> String { +/// format!("Body {:?}!", body) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to(index)) +/// ); +/// } +/// ``` +impl

    FromRequest

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

    ) -> Self::Future { + let mut tmp; + let cfg = if let Some(cfg) = req.load_config::() { + cfg + } else { + tmp = PayloadConfig::default(); + &tmp + }; + + if let Err(e) = cfg.check_mimetype(req) { + return Either::B(err(e)); + } + + let limit = cfg.limit; + Either::A(Box::new(MessageBody::new(req).limit(limit).from_err())) + } +} + +/// Extract text information from a request's body. +/// +/// Text extractor automatically decode body according to the request's charset. +/// +/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure +/// extraction process. +/// +/// ## Example +/// +/// ```rust +/// use actix_web::{web, App}; +/// +/// /// extract text data from request +/// fn index(text: String) -> String { +/// format!("Body {}!", text) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get() +/// .config(web::PayloadConfig::new(4096)) // <- limit size of the payload +/// .to(index)) // <- register handler with extractor params +/// ); +/// } +/// ``` +impl

    FromRequest

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

    ) -> Self::Future { + let mut tmp; + let cfg = if let Some(cfg) = req.load_config::() { + cfg + } else { + tmp = PayloadConfig::default(); + &tmp + }; + + // check content-type + if let Err(e) = cfg.check_mimetype(req) { + return Either::B(err(e)); + } + + // check charset + let encoding = match req.encoding() { + Ok(enc) => enc, + Err(e) => return Either::B(err(e.into())), + }; + let limit = cfg.limit; + + Either::A(Box::new( + MessageBody::new(req) + .limit(limit) + .from_err() + .and_then(move |body| { + let enc: *const Encoding = encoding as *const Encoding; + if enc == UTF_8 { + Ok(str::from_utf8(body.as_ref()) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned()) + } else { + Ok(encoding + .decode(&body, DecoderTrap::Strict) + .map_err(|_| ErrorBadRequest("Can not decode body"))?) + } + }), + )) + } +} +/// Payload configuration for request's payload. +#[derive(Clone)] +pub struct PayloadConfig { + limit: usize, + mimetype: Option, +} + +impl PayloadConfig { + /// Create `PayloadConfig` instance and set max size of payload. + pub fn new(limit: usize) -> Self { + Self::default().limit(limit) + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set required mime-type of the request. By default mime type is not + /// enforced. + pub fn mimetype(mut self, mt: Mime) -> Self { + self.mimetype = Some(mt); + self + } + + fn check_mimetype

    (&self, req: &ServiceFromRequest

    ) -> Result<(), Error> { + // check content-type + if let Some(ref mt) = self.mimetype { + match req.mime_type() { + Ok(Some(ref req_mt)) => { + if mt != req_mt { + return Err(ErrorBadRequest("Unexpected Content-Type")); + } + } + Ok(None) => { + return Err(ErrorBadRequest("Content-Type is expected")); + } + Err(err) => { + return Err(err.into()); + } + } + } + Ok(()) + } +} + +impl Default for PayloadConfig { + fn default() -> Self { + PayloadConfig { + limit: 262_144, + mimetype: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::header; + use crate::test::TestRequest; + + #[test] + fn test_payload_config() { + let req = TestRequest::default().to_from(); + let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); + assert!(cfg.check_mimetype(&req).is_err()); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .to_from(); + assert!(cfg.check_mimetype(&req).is_err()); + + let req = + TestRequest::with_header(header::CONTENT_TYPE, "application/json").to_from(); + assert!(cfg.check_mimetype(&req).is_ok()); + } +} diff --git a/src/extract/query.rs b/src/extract/query.rs new file mode 100644 index 00000000..f0eb6a7a --- /dev/null +++ b/src/extract/query.rs @@ -0,0 +1,126 @@ +//! Query extractor + +use std::{fmt, ops}; + +use actix_http::error::Error; +use serde::de; +use serde_urlencoded; + +use crate::extract::FromRequest; +use crate::service::ServiceFromRequest; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +/// Extract typed information from from the request's query. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Debug, Deserialize)] +/// pub enum ResponseType { +/// Token, +/// Code +/// } +/// +/// #[derive(Deserialize)] +/// pub struct AuthRequest { +/// id: u64, +/// response_type: ResponseType, +/// } +/// +/// // Use `Query` extractor for query information. +/// // This handler get called only if request's query contains `username` field +/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` +/// fn index(info: web::Query) -> String { +/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor +/// } +/// ``` +pub struct Query(T); + +impl Query { + /// Deconstruct to a inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Query { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Query { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Query { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for Query { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Extract typed information from from the request's query. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Debug, Deserialize)] +/// pub enum ResponseType { +/// Token, +/// Code +/// } +/// +/// #[derive(Deserialize)] +/// pub struct AuthRequest { +/// id: u64, +/// response_type: ResponseType, +/// } +/// +/// // Use `Query` extractor for query information. +/// // This handler get called only if request's query contains `username` field +/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` +/// fn index(info: web::Query) -> String { +/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html") +/// .route(web::get().to(index))); // <- use `Query` extractor +/// } +/// ``` +impl FromRequest

    for Query +where + T: de::DeserializeOwned, +{ + type Error = Error; + type Future = Result; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { + serde_urlencoded::from_str::(req.query_string()) + .map(|val| Ok(Query(val))) + .unwrap_or_else(|e| Err(e.into())) + } +} diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 7cf739bc..f3ccca93 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -145,7 +145,6 @@ struct IdentityItem { impl

    FromRequest

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

    ) -> Self::Future { diff --git a/src/request.rs b/src/request.rs index 71751483..5517302f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -205,7 +205,6 @@ impl HttpMessage for HttpRequest { impl

    FromRequest

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

    ) -> Self::Future { diff --git a/src/route.rs b/src/route.rs index c189c61b..1955a81a 100644 --- a/src/route.rs +++ b/src/route.rs @@ -6,7 +6,7 @@ use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::extract::{ConfigStorage, ExtractorConfig, FromRequest}; +use crate::extract::FromRequest; use crate::guard::{self, Guard}; use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; use crate::responder::Responder; @@ -40,7 +40,7 @@ type BoxedRouteNewService = Box< pub struct Route

    { service: BoxedRouteNewService, ServiceResponse>, guards: Rc>>, - config: ConfigStorage, + config: Option, config_ref: Rc>>>, } @@ -55,13 +55,13 @@ impl Route

    { ), )), guards: Rc::new(Vec::new()), - config: ConfigStorage::default(), + config: None, config_ref, } } - pub(crate) fn finish(self) -> Self { - *self.config_ref.borrow_mut() = self.config.storage.clone(); + pub(crate) fn finish(mut self) -> Self { + *self.config_ref.borrow_mut() = self.config.take().map(|e| Rc::new(e)); self } @@ -252,7 +252,6 @@ impl Route

    { T: FromRequest

    + 'static, R: Responder + 'static, { - T::Config::store_default(&mut self.config); self.service = Box::new(RouteNewService::new( Extract::new(self.config_ref.clone()) .and_then(Handler::new(handler).map_err(|_| panic!())), @@ -324,8 +323,11 @@ impl Route

    { /// )); /// } /// ``` - pub fn config(mut self, config: C) -> Self { - self.config.store(config); + pub fn config(mut self, config: C) -> Self { + if self.config.is_none() { + self.config = Some(Extensions::new()); + } + self.config.as_mut().unwrap().insert(config); self } } diff --git a/src/service.rs b/src/service.rs index 612fe4e8..08330282 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::cell::{Ref, RefMut}; use std::fmt; use std::marker::PhantomData; @@ -271,13 +270,12 @@ impl

    ServiceFromRequest

    { } /// Load extractor configuration - pub fn load_config(&self) -> Cow { + pub fn load_config(&self) -> Option<&T> { if let Some(ref ext) = self.config { - if let Some(cfg) = ext.get::() { - return Cow::Borrowed(cfg); - } + ext.get::() + } else { + None } - Cow::Owned(T::default()) } } diff --git a/src/state.rs b/src/state.rs index 2c623c70..b70540c0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -48,7 +48,6 @@ impl Clone for State { impl FromRequest

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

    ) -> Self::Future { From b6c11357983a195b277edf76d0302baf5c51f459 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 10:56:53 -0700 Subject: [PATCH 1038/1635] hide blocking mod --- src/error.rs | 2 ++ src/lib.rs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 54ca74dc..fd0ee998 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,8 @@ pub use actix_http::error::*; use derive_more::{Display, From}; use url::ParseError as UrlParseError; +pub use crate::blocking::BlockingError; + /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, From)] pub enum UrlGenerationError { diff --git a/src/lib.rs b/src/lib.rs index 32207024..f6f722be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ mod app; mod app_service; -pub mod blocking; +mod blocking; mod config; pub mod error; mod extract; @@ -54,6 +54,7 @@ pub mod dev { //! ``` pub use crate::app::AppRouter; + pub use crate::blocking::CpuFuture; pub use crate::config::{AppConfig, ServiceConfig}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; From 039efc570384c87fcb3442cf3b0a2d5f930d92ef Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 11:04:50 -0700 Subject: [PATCH 1039/1635] move tests to different mods --- src/extract/mod.rs | 52 ------------------------------------------ src/extract/path.rs | 42 ++++++++++++++++++++++++++++++++++ src/extract/payload.rs | 24 ++++++++++++++++++- 3 files changed, 65 insertions(+), 53 deletions(-) diff --git a/src/extract/mod.rs b/src/extract/mod.rs index d8958b2d..78c6ba79 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -256,26 +256,6 @@ mod tests { hello: String, } - #[test] - fn test_bytes() { - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); - - let s = block_on(Bytes::from_request(&mut req)).unwrap(); - assert_eq!(s, Bytes::from_static(b"hello=world")); - } - - #[test] - fn test_string() { - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); - - let s = block_on(String::from_request(&mut req)).unwrap(); - assert_eq!(s, "hello=world"); - } - #[test] fn test_option() { let mut req = TestRequest::with_header( @@ -400,36 +380,4 @@ mod tests { assert_eq!(res[1], "32".to_owned()); } - #[test] - fn test_extract_path_single() { - let resource = ResourceDef::new("/{value}/"); - - let mut req = TestRequest::with_uri("/32/").to_from(); - resource.match_path(req.match_info_mut()); - - assert_eq!(*Path::::from_request(&mut req).unwrap(), 32); - } - - #[test] - fn test_tuple_extract() { - let resource = ResourceDef::new("/{key}/{value}/"); - - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from(); - resource.match_path(req.match_info_mut()); - - let res = block_on(<(Path<(String, String)>,)>::from_request(&mut req)).unwrap(); - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - - let res = block_on( - <(Path<(String, String)>, Path<(String, String)>)>::from_request(&mut req), - ) - .unwrap(); - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - assert_eq!((res.1).0, "name"); - assert_eq!((res.1).1, "user1"); - - let () = <()>::from_request(&mut req).unwrap(); - } } diff --git a/src/extract/path.rs b/src/extract/path.rs index d9461263..fc6811c7 100644 --- a/src/extract/path.rs +++ b/src/extract/path.rs @@ -174,3 +174,45 @@ where Self::extract(req).map_err(ErrorNotFound) } } + +#[cfg(test)] +mod tests { + use actix_router::ResourceDef; + + use super::*; + use crate::test::{block_on, TestRequest}; + + #[test] + fn test_extract_path_single() { + let resource = ResourceDef::new("/{value}/"); + + let mut req = TestRequest::with_uri("/32/").to_from(); + resource.match_path(req.match_info_mut()); + + assert_eq!(*Path::::from_request(&mut req).unwrap(), 32); + } + + #[test] + fn test_tuple_extract() { + let resource = ResourceDef::new("/{key}/{value}/"); + + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from(); + resource.match_path(req.match_info_mut()); + + let res = block_on(<(Path<(String, String)>,)>::from_request(&mut req)).unwrap(); + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); + + let res = block_on( + <(Path<(String, String)>, Path<(String, String)>)>::from_request(&mut req), + ) + .unwrap(); + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); + assert_eq!((res.1).0, "name"); + assert_eq!((res.1).1, "user1"); + + let () = <()>::from_request(&mut req).unwrap(); + } + +} diff --git a/src/extract/payload.rs b/src/extract/payload.rs index 82024c0d..13532eee 100644 --- a/src/extract/payload.rs +++ b/src/extract/payload.rs @@ -289,9 +289,11 @@ impl Default for PayloadConfig { #[cfg(test)] mod tests { + use bytes::Bytes; + use super::*; use crate::http::header; - use crate::test::TestRequest; + use crate::test::{block_on, TestRequest}; #[test] fn test_payload_config() { @@ -310,4 +312,24 @@ mod tests { TestRequest::with_header(header::CONTENT_TYPE, "application/json").to_from(); assert!(cfg.check_mimetype(&req).is_ok()); } + + #[test] + fn test_bytes() { + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .to_from(); + + let s = block_on(Bytes::from_request(&mut req)).unwrap(); + assert_eq!(s, Bytes::from_static(b"hello=world")); + } + + #[test] + fn test_string() { + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .to_from(); + + let s = block_on(String::from_request(&mut req)).unwrap(); + assert_eq!(s, "hello=world"); + } } From 79875ea03955c9ae56b4efd79d54dfe7157d0a3b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 14:22:53 -0700 Subject: [PATCH 1040/1635] update deps --- actix-files/src/lib.rs | 19 ++++++++----------- actix-session/src/lib.rs | 1 - 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 14c25be7..3ac17619 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -15,11 +15,11 @@ use mime_guess::get_mime_type; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use v_htmlescape::escape as escape_html_entity; -use actix_http::error::{Error, ErrorInternalServerError}; use actix_service::{boxed::BoxedNewService, NewService, Service}; -use actix_web::dev::{HttpServiceFactory, ResourceDef, ServiceConfig}; +use actix_web::dev::{CpuFuture, HttpServiceFactory, ResourceDef, ServiceConfig}; +use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; use actix_web::{ - blocking, FromRequest, HttpRequest, HttpResponse, Responder, ServiceFromRequest, + web, FromRequest, HttpRequest, HttpResponse, Responder, ServiceFromRequest, ServiceRequest, ServiceResponse, }; use futures::future::{ok, FutureResult}; @@ -51,16 +51,14 @@ pub struct ChunkedReadFile { size: u64, offset: u64, file: Option, - fut: Option>, + fut: Option>, counter: u64, } -fn handle_error(err: blocking::BlockingError) -> Error { +fn handle_error(err: BlockingError) -> Error { match err { - blocking::BlockingError::Error(err) => err.into(), - blocking::BlockingError::Canceled => { - ErrorInternalServerError("Unexpected error").into() - } + BlockingError::Error(err) => err.into(), + BlockingError::Canceled => ErrorInternalServerError("Unexpected error").into(), } } @@ -90,7 +88,7 @@ impl Stream for ChunkedReadFile { Ok(Async::Ready(None)) } else { let mut file = self.file.take().expect("Use after completion"); - self.fut = Some(blocking::run(move || { + self.fut = Some(web::block(move || { let max_bytes: usize; max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; let mut buf = Vec::with_capacity(max_bytes); @@ -446,7 +444,6 @@ impl PathBufWrp { impl

    FromRequest

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

    ) -> Self::Future { PathBufWrp::get_pathbuf(req.match_info().path()) diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 62bc5c8f..79b7e1f9 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -175,7 +175,6 @@ impl Session { impl

    FromRequest

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

    ) -> Self::Future { From d7557720394a4518d720f54c31f7dd17d11ada3c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 15:30:31 -0700 Subject: [PATCH 1041/1635] add From impls for ResponseBuilder --- src/response.rs | 80 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/src/response.rs b/src/response.rs index 4e1fe214..34ac54ea 100644 --- a/src/response.rs +++ b/src/response.rs @@ -57,28 +57,6 @@ impl Response { resp } - /// Convert `Response` to a `ResponseBuilder` - #[inline] - pub fn into_builder(self) -> ResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - for c in self.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - ResponseBuilder { - head: Some(self.head), - err: None, - cookies: jar, - } - } - /// Convert response to response with body pub fn into_body(self) -> Response { let b = match self.body { @@ -692,6 +670,62 @@ fn parts<'a>( parts.as_mut() } +/// Convert `Response` to a `ResponseBuilder`. Body get dropped. +impl From> for ResponseBuilder { + fn from(res: Response) -> ResponseBuilder { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + for c in res.cookies() { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } + } + + ResponseBuilder { + head: Some(res.head), + err: None, + cookies: jar, + } + } +} + +/// Convert `ResponseHead` to a `ResponseBuilder` +impl<'a> From<&'a ResponseHead> for ResponseBuilder { + fn from(head: &'a ResponseHead) -> ResponseBuilder { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + let cookies = CookieIter { + iter: head.headers.get_all(header::SET_COOKIE).iter(), + }; + for c in cookies { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } + } + + let mut msg: Message = Message::new(); + msg.version = head.version; + msg.status = head.status; + msg.reason = head.reason; + msg.headers = head.headers.clone(); + msg.no_chunking = head.no_chunking; + + ResponseBuilder { + head: Some(msg), + err: None, + cookies: jar, + } + } +} + impl IntoFuture for ResponseBuilder { type Item = Response; type Error = Error; @@ -989,7 +1023,7 @@ mod tests { resp.add_cookie(&http::Cookie::new("cookie1", "val100")) .unwrap(); - let mut builder = resp.into_builder(); + let mut builder: ResponseBuilder = resp.into(); let resp = builder.status(StatusCode::BAD_REQUEST).finish(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); From 4d96abb639eca231ab26ef6bb11cd4ba102c4040 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 16:35:38 -0700 Subject: [PATCH 1042/1635] use actix_web::Error for middleware errors --- src/app.rs | 29 ++--- src/app_service.rs | 32 ++--- src/config.rs | 5 +- src/handler.rs | 6 +- src/lib.rs | 5 +- src/middleware/defaultheaders.rs | 3 +- src/middleware/errhandlers.rs | 210 +++++++++++++++++++++++++++++++ src/middleware/mod.rs | 8 +- src/resource.rs | 25 ++-- src/route.rs | 18 +-- src/scope.rs | 24 ++-- src/service.rs | 18 +-- src/test.rs | 7 +- 13 files changed, 305 insertions(+), 85 deletions(-) create mode 100644 src/middleware/errhandlers.rs diff --git a/src/app.rs b/src/app.rs index 29dd1ab6..54b5ded2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,8 +3,6 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody}; -use actix_http::PayloadStream; -use actix_router::ResourceDef; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ @@ -14,6 +12,8 @@ use futures::IntoFuture; use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; use crate::config::{AppConfig, AppConfigInner}; +use crate::dev::{PayloadStream, ResourceDef}; +use crate::error::Error; use crate::resource::Resource; use crate::route::Route; use crate::service::{ @@ -22,7 +22,8 @@ use crate::service::{ }; use crate::state::{State, StateFactory}; -type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; +type HttpNewService

    = + BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; /// Application builder - structure that follows the builder pattern /// for building application instances. @@ -55,7 +56,7 @@ where T: NewService< Request = ServiceRequest, Response = ServiceRequest

    , - Error = (), + Error = Error, InitError = (), >, { @@ -118,7 +119,7 @@ where impl NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, > @@ -127,7 +128,7 @@ where AppRouting

    , Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, F: IntoTransform>, @@ -157,7 +158,7 @@ where impl NewService< Request = ServiceRequest, Response = ServiceRequest, - Error = (), + Error = Error, InitError = (), >, > @@ -165,7 +166,7 @@ where C: NewService< Request = ServiceRequest

    , Response = ServiceRequest, - Error = (), + Error = Error, InitError = (), >, F: IntoNewService, @@ -264,7 +265,7 @@ where T: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, { @@ -324,7 +325,7 @@ where impl NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, > @@ -333,7 +334,7 @@ where T::Service, Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, B1: MessageBody, @@ -363,7 +364,7 @@ where U: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), > + 'static, { @@ -415,13 +416,13 @@ where T: NewService< Request = ServiceRequest

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

    , - Error = (), + Error = Error, InitError = (), >, { diff --git a/src/app_service.rs b/src/app_service.rs index 75e4b316..c59b80bc 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -11,15 +11,17 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll}; use crate::config::{AppConfig, ServiceConfig}; +use crate::error::Error; use crate::guard::Guard; use crate::rmap::ResourceMap; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; use crate::state::{StateFactory, StateFactoryResult}; type Guards = Vec>; -type HttpService

    = BoxedService, ServiceResponse, ()>; -type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; -type BoxedResponse = Box>; +type HttpService

    = BoxedService, ServiceResponse, Error>; +type HttpNewService

    = + BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; +type BoxedResponse = Box>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes state factories. @@ -29,7 +31,7 @@ where T: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, { @@ -48,13 +50,13 @@ where C: NewService< Request = ServiceRequest, Response = ServiceRequest

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

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, { @@ -147,13 +149,13 @@ where C: NewService< Request = ServiceRequest, Response = ServiceRequest

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

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, { @@ -201,7 +203,7 @@ where /// Service to convert `Request` to a `ServiceRequest` pub struct AppInitService where - C: Service, Error = ()>, + C: Service, Error = Error>, { chain: C, rmap: Rc, @@ -210,7 +212,7 @@ where impl Service for AppInitService where - C: Service, Error = ()>, + C: Service, Error = Error>, { type Request = Request; type Response = ServiceRequest

    ; @@ -240,7 +242,7 @@ pub struct AppRoutingFactory

    { impl NewService for AppRoutingFactory

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = AppRouting

    ; type Future = AppRoutingFactoryResponse

    ; @@ -350,7 +352,7 @@ pub struct AppRouting

    { impl

    Service for AppRouting

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type Future = Either>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -398,7 +400,7 @@ impl

    AppEntry

    { impl NewService for AppEntry

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = AppRouting

    ; type Future = AppRoutingFactoryResponse

    ; @@ -414,7 +416,7 @@ pub struct AppChain; impl NewService for AppChain { type Request = ServiceRequest; type Response = ServiceRequest; - type Error = (); + type Error = Error; type InitError = (); type Service = AppChain; type Future = FutureResult; @@ -427,7 +429,7 @@ impl NewService for AppChain { impl Service for AppChain { type Request = ServiceRequest; type Response = ServiceRequest; - type Error = (); + type Error = Error; type Future = FutureResult; #[inline] diff --git a/src/config.rs b/src/config.rs index f84376c7..ceb58feb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,13 +6,14 @@ use actix_http::Extensions; use actix_router::ResourceDef; use actix_service::{boxed, IntoNewService, NewService}; +use crate::error::Error; use crate::guard::Guard; use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; type Guards = Vec>; type HttpNewService

    = - boxed::BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; + boxed::BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; /// Application configuration pub struct ServiceConfig

    { @@ -84,7 +85,7 @@ impl ServiceConfig

    { S: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), > + 'static, { diff --git a/src/handler.rs b/src/handler.rs index 87645651..4ff3193c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -193,7 +193,7 @@ where { type Request = (T, HttpRequest); type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = AsyncHandlerService; type Future = FutureResult; @@ -227,7 +227,7 @@ where { type Request = (T, HttpRequest); type Response = ServiceResponse; - type Error = (); + type Error = Error; type Future = AsyncHandlerServiceResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -255,7 +255,7 @@ where T::Error: Into, { type Item = ServiceResponse; - type Error = (); + type Error = Error; fn poll(&mut self) -> Poll { match self.fut.poll() { diff --git a/src/lib.rs b/src/lib.rs index f6f722be..c04480af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,6 @@ pub use crate::responder::{Either, Responder}; pub use crate::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; -pub use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; pub mod dev { //! The `actix-web` prelude for library developers @@ -58,7 +57,9 @@ pub mod dev { pub use crate::config::{AppConfig, ServiceConfig}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; - pub use crate::service::HttpServiceFactory; + pub use crate::service::{ + HttpServiceFactory, ServiceFromRequest, ServiceRequest, ServiceResponse, + }; pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; pub use actix_http::dev::ResponseBuilder as HttpResponseBuilder; diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index b4927962..bca2cf6e 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -152,9 +152,10 @@ mod tests { use actix_service::FnService; use super::*; + use crate::dev::ServiceRequest; use crate::http::header::CONTENT_TYPE; use crate::test::{block_on, TestRequest}; - use crate::{HttpResponse, ServiceRequest}; + use crate::HttpResponse; #[test] fn test_default_headers() { diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs new file mode 100644 index 00000000..7a79aae1 --- /dev/null +++ b/src/middleware/errhandlers.rs @@ -0,0 +1,210 @@ +use std::rc::Rc; + +use actix_service::{Service, Transform}; +use futures::future::{err, ok, Either, Future, FutureResult}; +use futures::Poll; +use hashbrown::HashMap; + +use crate::dev::{ServiceRequest, ServiceResponse}; +use crate::error::{Error, Result}; +use crate::http::StatusCode; + +/// Error handler response +pub enum ErrorHandlerResponse { + /// New http response got generated + Response(ServiceResponse), + /// Result is a future that resolves to a new http response + Future(Box, Error = Error>>), +} + +type ErrorHandler = Fn(ServiceResponse) -> Result>; + +/// `Middleware` for allowing custom handlers for responses. +/// +/// You can use `ErrorHandlers::handler()` method to register a custom error +/// handler for specific status code. You can modify existing response or +/// create completely new one. +/// +/// ## Example +/// +/// ```rust +/// use actix_web::middleware::{ErrorHandlers, ErrorHandlerResponse}; +/// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result}; +/// +/// fn render_500(mut res: dev::ServiceResponse) -> Result> { +/// res.response_mut() +/// .headers_mut() +/// .insert(http::header::CONTENT_TYPE, http::HeaderValue::from_static("Error")); +/// Ok(ErrorHandlerResponse::Response(res)) +/// } +/// +/// fn main() { +/// let app = App::new() +/// .middleware( +/// ErrorHandlers::new() +/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), +/// ) +/// .service(web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()) +/// )); +/// } +/// ``` +pub struct ErrorHandlers { + handlers: Rc>>>, +} + +impl Default for ErrorHandlers { + fn default() -> Self { + ErrorHandlers { + handlers: Rc::new(HashMap::new()), + } + } +} + +impl ErrorHandlers { + /// Construct new `ErrorHandlers` instance + pub fn new() -> Self { + ErrorHandlers::default() + } + + /// Register error handler for specified status code + pub fn handler(mut self, status: StatusCode, handler: F) -> Self + where + F: Fn(ServiceResponse) -> Result> + 'static, + { + Rc::get_mut(&mut self.handlers) + .unwrap() + .insert(status, Box::new(handler)); + self + } +} + +impl Transform for ErrorHandlers +where + S: Service< + Request = ServiceRequest

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

    ; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = ErrorHandlersMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(ErrorHandlersMiddleware { + service, + handlers: self.handlers.clone(), + }) + } +} + +pub struct ErrorHandlersMiddleware { + service: S, + handlers: Rc>>>, +} + +impl Service for ErrorHandlersMiddleware +where + S: Service< + Request = ServiceRequest

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

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

    ) -> Self::Future { + let handlers = self.handlers.clone(); + + Box::new(self.service.call(req).and_then(move |res| { + if let Some(handler) = handlers.get(&res.status()) { + match handler(res) { + Ok(ErrorHandlerResponse::Response(res)) => Either::A(ok(res)), + Ok(ErrorHandlerResponse::Future(fut)) => Either::B(fut), + Err(e) => Either::A(err(e)), + } + } else { + Either::A(ok(res)) + } + })) + } +} + +#[cfg(test)] +mod tests { + use actix_service::FnService; + use futures::future::ok; + + use super::*; + use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; + use crate::test::{self, TestRequest}; + use crate::HttpResponse; + + fn render_500(mut res: ServiceResponse) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); + Ok(ErrorHandlerResponse::Response(res)) + } + + #[test] + fn test_handler() { + let srv = FnService::new(|req: ServiceRequest<_>| { + req.into_response(HttpResponse::InternalServerError().finish()) + }); + + let mut mw = test::block_on( + ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) + .new_transform(srv), + ) + .unwrap(); + + let resp = test::call_success(&mut mw, TestRequest::default().to_service()); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + } + + fn render_500_async( + mut res: ServiceResponse, + ) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); + Ok(ErrorHandlerResponse::Future(Box::new(ok(res)))) + } + + #[test] + fn test_handler_async() { + let srv = FnService::new(|req: ServiceRequest<_>| { + req.into_response(HttpResponse::InternalServerError().finish()) + }); + + let mut mw = test::block_on( + ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) + .new_transform(srv), + ) + .unwrap(); + + let resp = test::call_success(&mut mw, TestRequest::default().to_service()); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + } +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 288c1d63..6e55cd67 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -4,13 +4,15 @@ mod compress; pub use self::compress::Compress; mod defaultheaders; +mod errhandlers; +mod logger; + pub use self::defaultheaders::DefaultHeaders; +pub use self::errhandlers::{ErrorHandlerResponse, ErrorHandlers}; +pub use self::logger::Logger; // #[cfg(feature = "session")] // pub use actix_session as session; -mod logger; -pub use self::logger::Logger; - #[cfg(feature = "session")] pub mod identity; diff --git a/src/resource.rs b/src/resource.rs index 57f6f710..e4fe65c0 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -17,8 +17,9 @@ use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; -type HttpService

    = BoxedService, ServiceResponse, ()>; -type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; +type HttpService

    = BoxedService, ServiceResponse, Error>; +type HttpNewService

    = + BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; /// *Resource* is an entry in route table which corresponds to requested URL. /// @@ -70,7 +71,7 @@ where T: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, { @@ -232,7 +233,7 @@ where impl NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, > @@ -241,7 +242,7 @@ where T::Service, Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, F: IntoTransform, @@ -266,7 +267,7 @@ where U: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, > + 'static, { // create and configure default resource @@ -284,7 +285,7 @@ where T: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), > + 'static, { @@ -314,7 +315,7 @@ where T: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, { @@ -336,7 +337,7 @@ pub struct ResourceFactory

    { impl NewService for ResourceFactory

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = ResourceService

    ; type Future = CreateResourceService

    ; @@ -427,9 +428,9 @@ pub struct ResourceService

    { impl

    Service for ResourceService

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type Future = Either< - Box>, + Box>, Either< Box>, FutureResult, @@ -472,7 +473,7 @@ impl

    ResourceEndpoint

    { impl NewService for ResourceEndpoint

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = ResourceService

    ; type Future = CreateResourceService

    ; diff --git a/src/route.rs b/src/route.rs index 1955a81a..707da3d8 100644 --- a/src/route.rs +++ b/src/route.rs @@ -17,8 +17,8 @@ type BoxedRouteService = Box< Service< Request = Req, Response = Res, - Error = (), - Future = Box>, + Error = Error, + Future = Box>, >, >; @@ -26,7 +26,7 @@ type BoxedRouteNewService = Box< NewService< Request = Req, Response = Res, - Error = (), + Error = Error, InitError = (), Service = BoxedRouteService, Future = Box, Error = ()>>, @@ -73,7 +73,7 @@ impl Route

    { impl

    NewService for Route

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = RouteService

    ; type Future = CreateRouteService

    ; @@ -129,7 +129,7 @@ impl

    RouteService

    { impl

    Service for RouteService

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -188,7 +188,7 @@ impl Route

    { // T: NewService< // Request = HandlerRequest, // Response = HandlerRequest, - // InitError = (), + // InitError = Error, // >, // { // RouteServiceBuilder { @@ -372,7 +372,7 @@ where { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = BoxedRouteService, Self::Response>; type Future = Box>; @@ -410,11 +410,11 @@ where { type Request = ServiceRequest

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

    ) -> Self::Future { diff --git a/src/scope.rs b/src/scope.rs index 3b506173..9f5b650c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -11,6 +11,7 @@ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, Poll}; use crate::dev::{HttpServiceFactory, ServiceConfig}; +use crate::error::Error; use crate::guard::Guard; use crate::resource::Resource; use crate::rmap::ResourceMap; @@ -20,9 +21,10 @@ use crate::service::{ }; type Guards = Vec>; -type HttpService

    = BoxedService, ServiceResponse, ()>; -type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; -type BoxedResponse = Box>; +type HttpService

    = BoxedService, ServiceResponse, Error>; +type HttpNewService

    = + BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; +type BoxedResponse = Box>; /// Resources scope /// @@ -83,7 +85,7 @@ where T: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, { @@ -176,7 +178,7 @@ where U: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), > + 'static, { @@ -201,7 +203,7 @@ where impl NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, > @@ -210,7 +212,7 @@ where T::Service, Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), >, F: IntoTransform, @@ -233,7 +235,7 @@ where T: NewService< Request = ServiceRequest

    , Response = ServiceResponse, - Error = (), + Error = Error, InitError = (), > + 'static, { @@ -290,7 +292,7 @@ pub struct ScopeFactory

    { impl NewService for ScopeFactory

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = ScopeService

    ; type Future = ScopeFactoryResponse

    ; @@ -406,7 +408,7 @@ pub struct ScopeService

    { impl

    Service for ScopeService

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type Future = Either>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -450,7 +452,7 @@ impl

    ScopeEndpoint

    { impl NewService for ScopeEndpoint

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type InitError = (); type Service = ScopeService

    ; type Future = ScopeFactoryResponse

    ; diff --git a/src/service.rs b/src/service.rs index 08330282..e907a1ab 100644 --- a/src/service.rs +++ b/src/service.rs @@ -340,6 +340,12 @@ impl ServiceResponse { Self::from_err(err, self.request) } + /// Create service response + #[inline] + pub fn into_response(self, response: Response) -> ServiceResponse { + ServiceResponse::new(self.request, response) + } + /// Get reference to original request #[inline] pub fn request(&self) -> &HttpRequest { @@ -358,18 +364,6 @@ impl ServiceResponse { &mut self.response } - /// Get the headers from the response - #[inline] - pub fn headers(&self) -> &HeaderMap { - self.response.headers() - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - self.response.headers_mut() - } - /// Execute closure and in case of error convert it to response. pub fn checked_expr(mut self, f: F) -> Self where diff --git a/src/test.rs b/src/test.rs index b47daa2c..44592400 100644 --- a/src/test.rs +++ b/src/test.rs @@ -14,9 +14,9 @@ use bytes::Bytes; use futures::Future; use crate::config::{AppConfig, AppConfigInner}; -use crate::request::HttpRequest; use crate::rmap::ResourceMap; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; +use crate::{HttpRequest, HttpResponse}; thread_local! { static RT: RefCell = { @@ -277,6 +277,11 @@ impl TestRequest { self.req.finish() } + /// Complete request creation and generate `ServiceResponse` instance + pub fn to_response(self, res: HttpResponse) -> ServiceResponse { + self.to_service().into_response(res) + } + /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { let req = self.req.finish(); From 615fbb49bdeb3fd8efa83672a7b71caf4f146465 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 17:00:03 -0700 Subject: [PATCH 1043/1635] support cookies in TestRequest --- src/test.rs | 117 +++++++++++----------------------------------------- 1 file changed, 24 insertions(+), 93 deletions(-) diff --git a/src/test.rs b/src/test.rs index e2e0fd76..4ac30be0 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,10 +1,12 @@ //! Test Various helpers for Actix applications to use during testing. +use std::fmt::Write as FmtWrite; use std::str::FromStr; use bytes::Bytes; -use cookie::Cookie; -use http::header::HeaderName; +use cookie::{Cookie, CookieJar}; +use http::header::{self, HeaderName, HeaderValue}; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; @@ -44,7 +46,7 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, - _cookies: Option>>, + cookies: CookieJar, payload: Option, } @@ -55,7 +57,7 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - _cookies: None, + cookies: CookieJar::new(), payload: None, })) } @@ -123,6 +125,12 @@ impl TestRequest { panic!("Can not create header"); } + /// Set cookie for this request + pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + parts(&mut self.0).cookies.add(cookie.into_owned()); + self + } + /// Set request payload pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); @@ -143,7 +151,7 @@ impl TestRequest { version, headers, payload, - .. + cookies, } = self.0.take().expect("cannot reuse test request builder");; let mut req = if let Some(pl) = payload { @@ -158,96 +166,19 @@ impl TestRequest { head.version = version; head.headers = headers; - // req.set_cookies(cookies); + let mut cookie = String::new(); + for c in cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + req } - - // /// This method generates `HttpRequest` instance and runs handler - // /// with generated request. - // pub fn run>(self, h: &H) -> Result { - // let req = self.finish(); - // let resp = h.handle(&req); - - // match resp.respond_to(&req) { - // Ok(resp) => match resp.into().into() { - // AsyncResultItem::Ok(resp) => Ok(resp), - // AsyncResultItem::Err(err) => Err(err), - // AsyncResultItem::Future(fut) => { - // let mut sys = System::new("test"); - // sys.block_on(fut) - // } - // }, - // Err(err) => Err(err.into()), - // } - // } - - // /// 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 sys = System::new("test"); - // match sys.block_on(fut) { - // Ok(r) => match r.respond_to(&req) { - // Ok(reply) => match reply.into().into() { - // AsyncResultItem::Ok(resp) => Ok(resp), - // _ => panic!("Nested async replies are not supported"), - // }, - // Err(e) => Err(e), - // }, - // Err(err) => Err(err), - // } - // } - - // /// This method generates `HttpRequest` instance and executes handler - // pub fn run_async_result(self, f: F) -> Result - // where - // F: FnOnce(&HttpRequest) -> R, - // R: Into>, - // { - // let req = self.finish(); - // let res = f(&req); - - // match res.into().into() { - // AsyncResultItem::Ok(resp) => Ok(resp), - // AsyncResultItem::Err(err) => Err(err), - // AsyncResultItem::Future(fut) => { - // let mut sys = System::new("test"); - // sys.block_on(fut) - // } - // } - // } - - // /// This method generates `HttpRequest` instance and executes handler - // pub fn execute(self, f: F) -> Result - // where - // F: FnOnce(&HttpRequest) -> R, - // R: Responder + 'static, - // { - // let req = self.finish(); - // let resp = f(&req); - - // match resp.respond_to(&req) { - // Ok(resp) => match resp.into().into() { - // AsyncResultItem::Ok(resp) => Ok(resp), - // AsyncResultItem::Err(err) => Err(err), - // AsyncResultItem::Future(fut) => { - // let mut sys = System::new("test"); - // sys.block_on(fut) - // } - // }, - // Err(err) => Err(err.into()), - // } - // } } #[inline] From 50a0cb5653d6d7558caa6a97b0526120486ec8ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 17:02:14 -0700 Subject: [PATCH 1044/1635] do no move self --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 4ac30be0..63ad1499 100644 --- a/src/test.rs +++ b/src/test.rs @@ -126,7 +126,7 @@ impl TestRequest { } /// Set cookie for this request - pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self } From 64360041944638c45f658b9c58b66186127f4344 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 17:06:43 -0700 Subject: [PATCH 1045/1635] set test cookie if it is not empty --- src/test.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test.rs b/src/test.rs index 63ad1499..c60d2d01 100644 --- a/src/test.rs +++ b/src/test.rs @@ -172,10 +172,12 @@ impl TestRequest { let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let _ = write!(&mut cookie, "; {}={}", name, value); } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); + if !cookie.is_empty() { + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } req } From 0f0d6b65cab6dfa162c13c4f6c6ba6dbd355357c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 16:39:46 -0700 Subject: [PATCH 1046/1635] update service request/response location --- actix-files/src/lib.rs | 15 ++++++++------- actix-session/src/cookie.rs | 3 ++- actix-session/src/lib.rs | 2 +- src/extract/mod.rs | 8 ++++---- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 3ac17619..07fc0063 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -16,12 +16,12 @@ use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use v_htmlescape::escape as escape_html_entity; use actix_service::{boxed::BoxedNewService, NewService, Service}; -use actix_web::dev::{CpuFuture, HttpServiceFactory, ResourceDef, ServiceConfig}; -use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; -use actix_web::{ - web, FromRequest, HttpRequest, HttpResponse, Responder, ServiceFromRequest, +use actix_web::dev::{ + CpuFuture, HttpServiceFactory, ResourceDef, ServiceConfig, ServiceFromRequest, ServiceRequest, ServiceResponse, }; +use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; +use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; use futures::future::{ok, FutureResult}; mod config; @@ -34,7 +34,8 @@ pub use crate::config::{DefaultConfig, StaticFileConfig}; pub use crate::named::NamedFile; pub use crate::range::HttpRange; -type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, (), ()>; +type HttpNewService

    = + BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; /// Return the MIME type associated with a filename extension (case-insensitive). /// If `ext` is empty or no associated type for the extension was found, returns @@ -319,7 +320,7 @@ where impl NewService for Files { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type Service = FilesService; type InitError = (); type Future = FutureResult; @@ -352,7 +353,7 @@ pub struct FilesService { impl Service for FilesService { type Request = ServiceRequest

    ; type Response = ServiceResponse; - type Error = (); + type Error = Error; type Future = FutureResult; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index e2503145..37c552ea 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -19,8 +19,9 @@ use std::collections::HashMap; use std::rc::Rc; use actix_service::{Service, Transform}; +use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::{header::SET_COOKIE, HeaderValue}; -use actix_web::{Error, HttpMessage, ResponseError, ServiceRequest, ServiceResponse}; +use actix_web::{Error, HttpMessage, ResponseError}; use cookie::{Cookie, CookieJar, Key, SameSite}; use derive_more::{Display, From}; use futures::future::{ok, Future, FutureResult}; diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 79b7e1f9..1dd367ba 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -45,8 +45,8 @@ use std::cell::RefCell; use std::rc::Rc; +use actix_web::dev::{ServiceFromRequest, ServiceRequest, ServiceResponse}; use actix_web::{Error, FromRequest, HttpMessage}; -use actix_web::{ServiceFromRequest, ServiceRequest, ServiceResponse}; use hashbrown::HashMap; use serde::de::DeserializeOwned; use serde::Serialize; diff --git a/src/extract/mod.rs b/src/extract/mod.rs index 78c6ba79..25a046d4 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -40,7 +40,7 @@ pub trait FromRequest

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

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

    ) -> Self::Future { +/// fn from_request(req: &mut dev::ServiceFromRequest

    ) -> Self::Future { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { @@ -107,7 +107,7 @@ where /// /// ```rust /// # #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App, Result, Error, FromRequest, ServiceFromRequest}; +/// use actix_web::{web, dev, App, Result, Error, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use rand; /// @@ -120,7 +120,7 @@ where /// type Error = Error; /// type Future = Result; /// -/// fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { +/// fn from_request(req: &mut dev::ServiceFromRequest

    ) -> Self::Future { /// if rand::random() { /// Ok(Thing { name: "thingy".into() }) /// } else { From b8829bbf221dc5611973d524305ca59aaf10b3d6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 17:10:41 -0700 Subject: [PATCH 1047/1635] add identity middleware tests --- src/middleware/identity.rs | 66 ++++++++++++++++++++++++++++++++++++++ src/test.rs | 7 ++++ 2 files changed, 73 insertions(+) diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index f3ccca93..d0a4146a 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -461,3 +461,69 @@ impl IdentityPolicy for CookieIdentityPolicy { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::StatusCode; + use crate::test::{self, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[test] + fn test_identity() { + let mut srv = test::init_service( + App::new() + .middleware(IdentityService::new( + CookieIdentityPolicy::new(&[0; 32]) + .domain("www.rust-lang.org") + .name("actix_auth") + .path("/") + .secure(true), + )) + .service(web::resource("/index").to(|id: Identity| { + if id.identity().is_some() { + HttpResponse::Created() + } else { + HttpResponse::Ok() + } + })) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })) + .service(web::resource("/logout").to(|id: Identity| { + if id.identity().is_some() { + id.forget(); + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + })), + ); + let resp = + test::call_success(&mut srv, TestRequest::with_uri("/index").to_request()); + assert_eq!(resp.status(), StatusCode::OK); + + let resp = + test::call_success(&mut srv, TestRequest::with_uri("/login").to_request()); + assert_eq!(resp.status(), StatusCode::OK); + let c = resp.cookies().next().unwrap().to_owned(); + + let resp = test::call_success( + &mut srv, + TestRequest::with_uri("/index") + .cookie(c.clone()) + .to_request(), + ); + assert_eq!(resp.status(), StatusCode::CREATED); + + let resp = test::call_success( + &mut srv, + TestRequest::with_uri("/logout") + .cookie(c.clone()) + .to_request(), + ); + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)) + } +} diff --git a/src/test.rs b/src/test.rs index 44592400..03700b67 100644 --- a/src/test.rs +++ b/src/test.rs @@ -11,6 +11,7 @@ use actix_rt::Runtime; use actix_server_config::ServerConfig; use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; +use cookie::Cookie; use futures::Future; use crate::config::{AppConfig, AppConfigInner}; @@ -241,6 +242,12 @@ impl TestRequest { self } + /// Set cookie for this request + pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + self.req.cookie(cookie); + self + } + /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { self.req.set_payload(data); From 9680423025a9ece3e0d3bb4148655b9867120c7c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 18:33:47 -0700 Subject: [PATCH 1048/1635] Add more tests for route --- src/app.rs | 80 ---------------------------------------------------- src/lib.rs | 3 +- src/route.rs | 38 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 81 deletions(-) diff --git a/src/app.rs b/src/app.rs index 54b5ded2..2e2a8c2d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -526,84 +526,4 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - - // #[test] - // fn test_handler() { - // let app = App::new() - // .handler("/test", |_: &_| HttpResponse::Ok()) - // .finish(); - - // let req = TestRequest::with_uri("/test").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::OK); - - // let req = TestRequest::with_uri("/test/").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::OK); - - // let req = TestRequest::with_uri("/test/app").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::OK); - - // let req = TestRequest::with_uri("/testapp").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - // let req = TestRequest::with_uri("/blah").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - // } - - // #[test] - // fn test_handler2() { - // let app = App::new() - // .handler("test", |_: &_| HttpResponse::Ok()) - // .finish(); - - // let req = TestRequest::with_uri("/test").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::OK); - - // let req = TestRequest::with_uri("/test/").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::OK); - - // let req = TestRequest::with_uri("/test/app").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::OK); - - // let req = TestRequest::with_uri("/testapp").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - - // let req = TestRequest::with_uri("/blah").request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - // } - - // #[test] - // fn test_route() { - // let app = App::new() - // .route("/test", Method::GET, |_: HttpRequest| HttpResponse::Ok()) - // .route("/test", Method::POST, |_: HttpRequest| { - // HttpResponse::Created() - // }) - // .finish(); - - // let req = TestRequest::with_uri("/test").method(Method::GET).request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::OK); - - // let req = TestRequest::with_uri("/test") - // .method(Method::POST) - // .request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::CREATED); - - // let req = TestRequest::with_uri("/test") - // .method(Method::HEAD) - // .request(); - // let resp = app.run(req); - // assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); - // } } diff --git a/src/lib.rs b/src/lib.rs index c04480af..62f6399d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,7 +79,7 @@ pub mod dev { } pub mod web { - use actix_http::{http::Method, Error, Response}; + use actix_http::{http::Method, Response}; use futures::IntoFuture; pub use actix_http::Response as HttpResponse; @@ -93,6 +93,7 @@ pub mod web { use crate::route::Route; use crate::scope::Scope; + pub use crate::error::Error; pub use crate::extract::{Form, Json, Path, Payload, Query}; pub use crate::extract::{FormConfig, JsonConfig, PayloadConfig}; pub use crate::request::HttpRequest; diff --git a/src/route.rs b/src/route.rs index 707da3d8..5d339a3b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -424,3 +424,41 @@ where })) } } + +#[cfg(test)] +mod tests { + use crate::http::{Method, StatusCode}; + use crate::test::{call_success, init_service, TestRequest}; + use crate::{web, App, Error, HttpResponse}; + + #[test] + fn test_route() { + let mut srv = init_service( + App::new().service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .route( + web::post().to_async(|| Ok::<_, Error>(HttpResponse::Created())), + ), + ), + ); + + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::CREATED); + + let req = TestRequest::with_uri("/test") + .method(Method::HEAD) + .to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } +} From cc7f6b5eef4d9e2f67265f421e8d1e39fdf70168 Mon Sep 17 00:00:00 2001 From: David McGuire Date: Sun, 10 Mar 2019 21:26:54 -0700 Subject: [PATCH 1049/1635] Fix preflight CORS header compliance; refactor previous patch. (#717) --- CHANGES.md | 2 + src/middleware/cors.rs | 119 +++++++++++++++++++++++++---------------- 2 files changed, 74 insertions(+), 47 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8cce7159..76f3465e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,8 @@ * Do not remove `Content-Length` on `Body::Empty` and insert zero value if it is missing for `POST` and `PUT` methods. +* Fix preflight CORS header compliance; refactor previous patch (#603). #717 + ## [0.7.18] - 2019-01-10 ### Added diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 386d0007..80ee5b19 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -307,6 +307,32 @@ impl Cors { } } + fn access_control_allow_origin(&self, req: &Request) -> Option { + match self.inner.origins { + AllOrSome::All => { + if self.inner.send_wildcard { + Some(HeaderValue::from_static("*")) + } else if let Some(origin) = req.headers().get(header::ORIGIN) { + Some(origin.clone()) + } else { + None + } + } + AllOrSome::Some(ref origins) => { + if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| { + match o.to_str() { + Ok(os) => origins.contains(os), + _ => false + } + }) { + Some(origin.clone()) + } else { + Some(self.inner.origins_str.as_ref().unwrap().clone()) + } + } + } + } + fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> { if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { if let Ok(meth) = hdr.to_str() { @@ -390,21 +416,9 @@ impl Middleware for Cors { }).if_some(headers, |headers, resp| { let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); - }).if_true(self.inner.origins.is_all(), |resp| { - if self.inner.send_wildcard { - resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - } else { - let origin = req.headers().get(header::ORIGIN).unwrap(); - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - origin.clone(), - ); - } - }).if_true(self.inner.origins.is_some(), |resp| { - resp.header( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone(), - ); + }).if_some(self.access_control_allow_origin(&req), |origin, resp| { + let _ = + resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin); }).if_true(self.inner.supports_credentials, |resp| { resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); }).header( @@ -430,37 +444,11 @@ impl Middleware for Cors { fn response( &self, req: &HttpRequest, mut resp: HttpResponse, ) -> Result { - match self.inner.origins { - AllOrSome::All => { - if self.inner.send_wildcard { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - HeaderValue::from_static("*"), - ); - } else if let Some(origin) = req.headers().get(header::ORIGIN) { - resp.headers_mut() - .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); - } - } - AllOrSome::Some(ref origins) => { - if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| { - match o.to_str() { - Ok(os) => origins.contains(os), - _ => false - } - }) { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - origin.clone(), - ); - } else { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - self.inner.origins_str.as_ref().unwrap().clone() - ); - }; - } - } + + if let Some(origin) = self.access_control_allow_origin(req) { + resp.headers_mut() + .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); + }; if let Some(ref expose) = self.inner.expose_hdrs { resp.headers_mut().insert( @@ -1201,7 +1189,6 @@ mod tests { let resp: HttpResponse = HttpResponse::Ok().into(); let resp = cors.response(&req, resp).unwrap().response(); - print!("{:?}", resp); assert_eq!( &b"https://example.com"[..], resp.headers() @@ -1224,4 +1211,42 @@ mod tests { .as_bytes() ); } + + #[test] + fn test_multiple_origins_preflight() { + let cors = Cors::build() + .allowed_origin("https://example.com") + .allowed_origin("https://example.org") + .allowed_methods(vec![Method::GET]) + .finish(); + + + let req = TestRequest::with_header("Origin", "https://example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") + .method(Method::OPTIONS) + .finish(); + + let resp = cors.start(&req).ok().unwrap().response(); + assert_eq!( + &b"https://example.com"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + + let req = TestRequest::with_header("Origin", "https://example.org") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") + .method(Method::OPTIONS) + .finish(); + + let resp = cors.start(&req).ok().unwrap().response(); + assert_eq!( + &b"https://example.org"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + } } From ad43ca735b748c41bc6ff0da8948544be85e590d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 15:09:42 -0700 Subject: [PATCH 1050/1635] update server service requirenments --- examples/framed_hello.rs | 7 +-- src/builder.rs | 6 +-- src/h1/service.rs | 67 ++++++++++++++-------------- src/h2/service.rs | 40 ++++++++--------- src/service/service.rs | 94 +++++++++++++++++++++++++++------------- tests/test_server.rs | 2 +- tests/test_ws.rs | 6 ++- 7 files changed, 130 insertions(+), 92 deletions(-) diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index ff397785..74b0f7df 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -2,8 +2,8 @@ use std::{env, io}; use actix_codec::Framed; use actix_http::{h1, Response, SendResponse, ServiceConfig}; -use actix_server::Server; -use actix_service::NewService; +use actix_server::{Io, Server}; +use actix_service::{fn_service, NewService}; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use futures::Future; @@ -14,7 +14,8 @@ fn main() -> io::Result<()> { Server::build() .bind("framed_hello", "127.0.0.1:8080", || { - IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) + fn_service(|io: Io<_>| Ok(io.into_parts().0)) + .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(_req, _framed): (_, Framed<_, _>)| { SendResponse::send(_framed, Response::Ok().body("Hello world!")) diff --git a/src/builder.rs b/src/builder.rs index 1df96b0e..2f7466a9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -95,7 +95,7 @@ where // } /// Finish service configuration and create *http service* for HTTP/1 protocol. - pub fn h1(self, service: F) -> H1Service + pub fn h1(self, service: F) -> H1Service where B: MessageBody + 'static, F: IntoNewService, @@ -110,7 +110,7 @@ where } /// Finish service configuration and create *http service* for HTTP/2 protocol. - pub fn h2(self, service: F) -> H2Service + pub fn h2(self, service: F) -> H2Service where B: MessageBody + 'static, F: IntoNewService, @@ -125,7 +125,7 @@ where } /// Finish service configuration and create `HttpService` instance. - pub fn finish(self, service: F) -> HttpService + pub fn finish(self, service: F) -> HttpService where B: MessageBody + 'static, F: IntoNewService, diff --git a/src/h1/service.rs b/src/h1/service.rs index e55ff0d9..f3301b9b 100644 --- a/src/h1/service.rs +++ b/src/h1/service.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_server_config::ServerConfig as SrvConfig; +use actix_server_config::{Io, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; @@ -19,13 +19,13 @@ use super::dispatcher::Dispatcher; use super::Message; /// `NewService` implementation for HTTP1 transport -pub struct H1Service { +pub struct H1Service { srv: S, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl H1Service +impl H1Service where S: NewService, S::Error: Debug, @@ -57,7 +57,7 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: AsyncRead + AsyncWrite, S: NewService, @@ -66,12 +66,12 @@ where S::Service: 'static, B: MessageBody, { - type Request = T; + type Request = Io; type Response = (); type Error = DispatchError; type InitError = S::InitError; - type Service = H1ServiceHandler; - type Future = H1ServiceResponse; + type Service = H1ServiceHandler; + type Future = H1ServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H1ServiceResponse { @@ -83,13 +83,13 @@ where } #[doc(hidden)] -pub struct H1ServiceResponse, B> { +pub struct H1ServiceResponse, B> { fut: ::Future, cfg: Option, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl Future for H1ServiceResponse +impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, @@ -98,7 +98,7 @@ where S::Response: Into>, B: MessageBody, { - type Item = H1ServiceHandler; + type Item = H1ServiceHandler; type Error = S::InitError; fn poll(&mut self) -> Poll { @@ -111,20 +111,20 @@ where } /// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl H1ServiceHandler +impl H1ServiceHandler where S: Service, S::Error: Debug, S::Response: Into>, B: MessageBody, { - fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { + fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), cfg, @@ -133,7 +133,7 @@ where } } -impl Service for H1ServiceHandler +impl Service for H1ServiceHandler where T: AsyncRead + AsyncWrite, S: Service, @@ -141,7 +141,7 @@ where S::Response: Into>, B: MessageBody, { - type Request = T; + type Request = Io; type Response = (); type Error = DispatchError; type Future = Dispatcher; @@ -153,19 +153,19 @@ where }) } - fn call(&mut self, req: T) -> Self::Future { - Dispatcher::new(req, self.cfg.clone(), self.srv.clone()) + fn call(&mut self, req: Self::Request) -> Self::Future { + Dispatcher::new(req.into_parts().0, self.cfg.clone(), self.srv.clone()) } } /// `NewService` implementation for `OneRequestService` service #[derive(Default)] -pub struct OneRequest { +pub struct OneRequest { config: ServiceConfig, - _t: PhantomData, + _t: PhantomData<(T, P)>, } -impl OneRequest +impl OneRequest where T: AsyncRead + AsyncWrite, { @@ -178,15 +178,15 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: AsyncRead + AsyncWrite, { - type Request = T; + type Request = Io; type Response = (Request, Framed); type Error = ParseError; type InitError = (); - type Service = OneRequestService; + type Service = OneRequestService; type Future = FutureResult; fn new_service(&self, _: &SrvConfig) -> Self::Future { @@ -199,16 +199,16 @@ where /// `Service` implementation for HTTP1 transport. Reads one request and returns /// request and framed object. -pub struct OneRequestService { +pub struct OneRequestService { config: ServiceConfig, - _t: PhantomData, + _t: PhantomData<(T, P)>, } -impl Service for OneRequestService +impl Service for OneRequestService where T: AsyncRead + AsyncWrite, { - type Request = T; + type Request = Io; type Response = (Request, Framed); type Error = ParseError; type Future = OneRequestServiceResponse; @@ -217,9 +217,12 @@ where Ok(Async::Ready(())) } - fn call(&mut self, req: T) -> Self::Future { + fn call(&mut self, req: Self::Request) -> Self::Future { OneRequestServiceResponse { - framed: Some(Framed::new(req, Codec::new(self.config.clone()))), + framed: Some(Framed::new( + req.into_parts().0, + Codec::new(self.config.clone()), + )), } } } diff --git a/src/h2/service.rs b/src/h2/service.rs index ce7c3b5d..6ab37919 100644 --- a/src/h2/service.rs +++ b/src/h2/service.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::{io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_server_config::ServerConfig as SrvConfig; +use actix_server_config::{Io, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::Bytes; @@ -23,13 +23,13 @@ use crate::response::Response; use super::dispatcher::Dispatcher; /// `NewService` implementation for HTTP2 transport -pub struct H2Service { +pub struct H2Service { srv: S, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl H2Service +impl H2Service where S: NewService, S::Service: 'static, @@ -61,7 +61,7 @@ where } } -impl NewService for H2Service +impl NewService for H2Service where T: AsyncRead + AsyncWrite, S: NewService, @@ -70,12 +70,12 @@ where S::Response: Into>, B: MessageBody + 'static, { - type Request = T; + type Request = Io; type Response = (); type Error = DispatchError; type InitError = S::InitError; - type Service = H2ServiceHandler; - type Future = H2ServiceResponse; + type Service = H2ServiceHandler; + type Future = H2ServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H2ServiceResponse { @@ -87,13 +87,13 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse, B> { +pub struct H2ServiceResponse, B> { fut: ::Future, cfg: Option, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl Future for H2ServiceResponse +impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, @@ -102,7 +102,7 @@ where S::Error: Debug, B: MessageBody + 'static, { - type Item = H2ServiceHandler; + type Item = H2ServiceHandler; type Error = S::InitError; fn poll(&mut self) -> Poll { @@ -115,20 +115,20 @@ where } /// `Service` implementation for http/2 transport -pub struct H2ServiceHandler { +pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl H2ServiceHandler +impl H2ServiceHandler where S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { - fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { + fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { H2ServiceHandler { cfg, srv: CloneableService::new(srv), @@ -137,7 +137,7 @@ where } } -impl Service for H2ServiceHandler +impl Service for H2ServiceHandler where T: AsyncRead + AsyncWrite, S: Service + 'static, @@ -145,7 +145,7 @@ where S::Response: Into>, B: MessageBody + 'static, { - type Request = T; + type Request = Io; type Response = (); type Error = DispatchError; type Future = H2ServiceHandlerResponse; @@ -157,12 +157,12 @@ where }) } - fn call(&mut self, req: T) -> Self::Future { + fn call(&mut self, req: Self::Request) -> Self::Future { H2ServiceHandlerResponse { state: State::Handshake( Some(self.srv.clone()), Some(self.cfg.clone()), - server::handshake(req), + server::handshake(req.into_parts().0), ), } } diff --git a/src/service/service.rs b/src/service/service.rs index ac28c77a..3ddf5573 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::{fmt, io}; use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts}; -use actix_server_config::ServerConfig as SrvConfig; +use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use bytes::{Buf, BufMut, Bytes, BytesMut}; @@ -20,13 +20,27 @@ use crate::response::Response; use crate::{h1, h2::Dispatcher}; /// `NewService` HTTP1.1/HTTP2 transport implementation -pub struct HttpService { +pub struct HttpService { srv: S, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl HttpService +impl HttpService +where + S: NewService, + S::Service: 'static, + S::Error: Debug + 'static, + S::Response: Into>, + B: MessageBody + 'static, +{ + /// Create builder for `HttpService` instance. + pub fn build() -> HttpServiceBuilder { + HttpServiceBuilder::new() + } +} + +impl HttpService where S: NewService, S::Service: 'static, @@ -56,14 +70,9 @@ where _t: PhantomData, } } - - /// Create builder for `HttpService` instance. - pub fn build() -> HttpServiceBuilder { - HttpServiceBuilder::new() - } } -impl NewService for HttpService +impl NewService for HttpService where T: AsyncRead + AsyncWrite + 'static, S: NewService, @@ -72,12 +81,12 @@ where S::Response: Into>, B: MessageBody + 'static, { - type Request = T; + type Request = ServerIo; type Response = (); type Error = DispatchError; type InitError = S::InitError; - type Service = HttpServiceHandler; - type Future = HttpServiceResponse; + type Service = HttpServiceHandler; + type Future = HttpServiceResponse; fn new_service(&self, cfg: &SrvConfig) -> Self::Future { HttpServiceResponse { @@ -89,13 +98,13 @@ where } #[doc(hidden)] -pub struct HttpServiceResponse, B> { +pub struct HttpServiceResponse, B> { fut: ::Future, cfg: Option, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl Future for HttpServiceResponse +impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite, S: NewService, @@ -104,7 +113,7 @@ where S::Error: Debug, B: MessageBody + 'static, { - type Item = HttpServiceHandler; + type Item = HttpServiceHandler; type Error = S::InitError; fn poll(&mut self) -> Poll { @@ -117,20 +126,20 @@ where } /// `Service` implementation for http transport -pub struct HttpServiceHandler { +pub struct HttpServiceHandler { srv: CloneableService, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData<(T, P, B)>, } -impl HttpServiceHandler +impl HttpServiceHandler where S: Service + 'static, S::Error: Debug, S::Response: Into>, B: MessageBody + 'static, { - fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler { + fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler { HttpServiceHandler { cfg, srv: CloneableService::new(srv), @@ -139,7 +148,7 @@ where } } -impl Service for HttpServiceHandler +impl Service for HttpServiceHandler where T: AsyncRead + AsyncWrite + 'static, S: Service + 'static, @@ -147,7 +156,7 @@ where S::Response: Into>, B: MessageBody + 'static, { - type Request = T; + type Request = ServerIo; type Response = (); type Error = DispatchError; type Future = HttpServiceHandlerResponse; @@ -159,14 +168,37 @@ where }) } - fn call(&mut self, req: T) -> Self::Future { - HttpServiceHandlerResponse { - state: State::Unknown(Some(( - req, - BytesMut::with_capacity(14), - self.cfg.clone(), - self.srv.clone(), - ))), + fn call(&mut self, req: Self::Request) -> Self::Future { + let (io, params, proto) = req.into_parts(); + match proto { + Protocol::Http2 => { + let io = Io { + inner: io, + unread: None, + }; + HttpServiceHandlerResponse { + state: State::Handshake(Some(( + server::handshake(io), + self.cfg.clone(), + self.srv.clone(), + ))), + } + } + Protocol::Http10 | Protocol::Http11 => HttpServiceHandlerResponse { + state: State::H1(h1::Dispatcher::new( + io, + self.cfg.clone(), + self.srv.clone(), + )), + }, + _ => HttpServiceHandlerResponse { + state: State::Unknown(Some(( + io, + BytesMut::with_capacity(14), + self.cfg.clone(), + self.srv.clone(), + ))), + }, } } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 7a28bca8..3771d35c 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -49,7 +49,7 @@ fn test_h1_2() { } #[cfg(feature = "ssl")] -fn ssl_acceptor() -> std::io::Result> { +fn ssl_acceptor() -> std::io::Result> { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 4111ca3d..634b9acd 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -2,7 +2,8 @@ use std::io; use actix_codec::Framed; use actix_http_test::TestServer; -use actix_service::NewService; +use actix_server::Io; +use actix_service::{fn_service, NewService}; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use bytes::{Bytes, BytesMut}; @@ -35,7 +36,8 @@ fn ws_service(req: ws::Frame) -> impl Future| Ok(io.into_parts().0)) + .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { // validate request From eae48f9612d2391ead1963a2f49aabfd5b420aac Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 15:26:05 -0700 Subject: [PATCH 1051/1635] use server backlog --- src/server.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/server.rs b/src/server.rs index 5d717817..f80e5a0e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -83,12 +83,12 @@ where HttpServer { factory, host: None, - backlog: 2048, config: Arc::new(Mutex::new(Config { keep_alive: KeepAlive::Timeout(5), client_timeout: 5000, client_shutdown: 5000, })), + backlog: 1024, sockets: Vec::new(), builder: Some(ServerBuilder::default()), _t: PhantomData, @@ -114,8 +114,9 @@ where /// 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; + pub fn backlog(mut self, backlog: i32) -> Self { + self.backlog = backlog; + self.builder = Some(self.builder.take().unwrap().backlog(backlog)); self } From e15e4f18fd8588c64fbf4096e40553de36278af1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 16:42:33 -0700 Subject: [PATCH 1052/1635] update tests --- src/client/h2proto.rs | 12 +- src/h1/dispatcher.rs | 36 ++-- src/h2/dispatcher.rs | 10 +- src/h2/mod.rs | 5 +- test-server/src/lib.rs | 5 + tests/test_server.rs | 411 ++++++++++++++++++++++++++++++++++------- 6 files changed, 383 insertions(+), 96 deletions(-) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index 617c21b6..c05aeddb 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -6,10 +6,11 @@ use futures::future::{err, Either}; use futures::{Async, Future, Poll}; use h2::{client::SendRequest, SendStream}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use http::{request::Request, HttpTryFrom, Version}; +use http::{request::Request, HttpTryFrom, Method, Version}; use crate::body::{BodyLength, MessageBody}; use crate::message::{Message, RequestHead, ResponseHead}; +use crate::payload::Payload; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; @@ -28,6 +29,7 @@ where B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.length()); + let head_req = head.method == Method::HEAD; let length = body.length(); let eof = match length { BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => true, @@ -99,18 +101,16 @@ where } } }) - .and_then(|resp| { + .and_then(move |resp| { let (parts, body) = resp.into_parts(); + let payload = if head_req { Payload::None } else { body.into() }; let mut head: Message = Message::new(); head.version = parts.version; head.status = parts.status; head.headers = parts.headers; - Ok(ClientResponse { - head, - payload: body.into(), - }) + Ok(ClientResponse { head, payload }) }) .from_err() } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index a7eb96c3..8543aa21 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -228,18 +228,21 @@ where } None => None, }, - State::ServiceCall(mut fut) => { - match fut.poll().map_err(|_| DispatchError::Service)? { - Async::Ready(res) => { - let (res, body) = res.into().replace_body(()); - Some(self.send_response(res, body)?) - } - Async::NotReady => { - self.state = State::ServiceCall(fut); - None - } + State::ServiceCall(mut fut) => match fut.poll() { + Ok(Async::Ready(res)) => { + let (res, body) = res.into().replace_body(()); + Some(self.send_response(res, body)?) } - } + Ok(Async::NotReady) => { + self.state = State::ServiceCall(fut); + None + } + Err(_e) => { + let res: Response = Response::InternalServerError().finish(); + let (res, body) = res.replace_body(()); + Some(self.send_response(res, body.into_body())?) + } + }, State::SendPayload(mut stream) => { loop { if !self.framed.is_write_buf_full() { @@ -289,12 +292,17 @@ where fn handle_request(&mut self, req: Request) -> Result, DispatchError> { let mut task = self.service.call(req); - match task.poll().map_err(|_| DispatchError::Service)? { - Async::Ready(res) => { + match task.poll() { + Ok(Async::Ready(res)) => { let (res, body) = res.into().replace_body(()); self.send_response(res, body) } - Async::NotReady => Ok(State::ServiceCall(task)), + Ok(Async::NotReady) => Ok(State::ServiceCall(task)), + Err(_e) => { + let res: Response = Response::InternalServerError().finish(); + let (res, body) = res.replace_body(()); + self.send_response(res, body.into_body()) + } } } diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index a3c731eb..28465b50 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -107,9 +107,7 @@ where fn poll(&mut self) -> Poll { loop { match self.connection.poll()? { - Async::Ready(None) => { - self.flags.insert(Flags::DISCONNECTED); - } + Async::Ready(None) => return Ok(Async::Ready(())), Async::Ready(Some((req, res))) => { // update keep-alive expire if self.ka_timer.is_some() { @@ -255,7 +253,7 @@ where } } Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { + Err(_e) => { let res: Response = Response::InternalServerError().finish(); let (res, body) = res.replace_body(()); @@ -304,7 +302,9 @@ where } } else { match body.poll_next() { - Ok(Async::NotReady) => return Ok(Async::NotReady), + Ok(Async::NotReady) => { + return Ok(Async::NotReady); + } Ok(Async::Ready(None)) => { if let Err(e) = stream.send_data(Bytes::new(), true) { warn!("{:?}", e); diff --git a/src/h2/mod.rs b/src/h2/mod.rs index c5972123..919317e0 100644 --- a/src/h2/mod.rs +++ b/src/h2/mod.rs @@ -40,7 +40,10 @@ impl Stream for Payload { } Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => Err(err.into()), + Err(err) => { + println!("======== {:?}", err); + Err(err.into()) + } } } } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 6b70fbc1..d810d891 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -167,6 +167,11 @@ impl TestServerRuntime { ClientRequest::get(self.url("/").as_str()) } + /// Create https `GET` request + pub fn sget(&self) -> ClientRequestBuilder { + ClientRequest::get(self.surl("/").as_str()) + } + /// Create `POST` request pub fn post(&self) -> ClientRequestBuilder { ClientRequest::post(self.url("/").as_str()) diff --git a/tests/test_server.rs b/tests/test_server.rs index 3771d35c..49a943db 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -3,15 +3,16 @@ use std::time::Duration; use std::{net, thread}; use actix_http_test::TestServer; -use actix_service::NewService; +use actix_server_config::ServerConfig; +use actix_service::{fn_cfg_factory, NewService}; use bytes::Bytes; use futures::future::{self, ok, Future}; use futures::stream::once; use actix_http::body::Body; use actix_http::{ - body, client, http, Error, HttpMessage as HttpMessage2, HttpService, KeepAlive, - Request, Response, + body, client, error, http, http::header, Error, HttpMessage as HttpMessage2, + HttpService, KeepAlive, Request, Response, }; #[test] @@ -152,7 +153,7 @@ fn test_slow_request() { let srv = TestServer::new(|| { HttpService::build() .client_timeout(100) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) }); let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); @@ -341,67 +342,66 @@ fn test_content_length() { } } -// TODO: fix -// #[test] -// fn test_h2_content_length() { -// use actix_http::http::{ -// header::{HeaderName, HeaderValue}, -// StatusCode, -// }; -// let openssl = ssl_acceptor().unwrap(); +#[test] +fn test_h2_content_length() { + use actix_http::http::{ + header::{HeaderName, HeaderValue}, + StatusCode, + }; + let openssl = ssl_acceptor().unwrap(); -// let mut srv = TestServer::new(move || { -// openssl -// .clone() -// .map_err(|e| println!("Openssl error: {}", e)) -// .and_then( -// HttpService::build() -// .h2(|req: Request| { -// let indx: usize = req.uri().path()[1..].parse().unwrap(); -// let statuses = [ -// StatusCode::NO_CONTENT, -// StatusCode::CONTINUE, -// StatusCode::SWITCHING_PROTOCOLS, -// StatusCode::PROCESSING, -// StatusCode::OK, -// StatusCode::NOT_FOUND, -// ]; -// future::ok::<_, ()>(Response::new(statuses[indx])) -// }) -// .map_err(|_| ()), -// ) -// }); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); -// let header = HeaderName::from_static("content-length"); -// let value = HeaderValue::from_static("0"); + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); -// { -// for i in 0..4 { -// let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) -// .finish() -// .unwrap(); -// let response = srv.send_request(req).unwrap(); -// assert_eq!(response.headers().get(&header), None); + { + for i in 0..4 { + let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) + .finish() + .unwrap(); + let response = srv.send_request(req).unwrap(); + assert_eq!(response.headers().get(&header), None); -// let req = client::ClientRequest::head(srv.surl(&format!("/{}", i))) -// .finish() -// .unwrap(); -// let response = srv.send_request(req).unwrap(); -// assert_eq!(response.headers().get(&header), None); -// } + let req = client::ClientRequest::head(srv.surl(&format!("/{}", i))) + .finish() + .unwrap(); + let response = srv.send_request(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + } -// for i in 4..6 { -// let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) -// .finish() -// .unwrap(); -// let response = srv.send_request(req).unwrap(); -// assert_eq!(response.headers().get(&header), Some(&value)); -// } -// } -// } + for i in 4..6 { + let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) + .finish() + .unwrap(); + let response = srv.send_request(req).unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} #[test] -fn test_headers() { +fn test_h1_headers() { let data = STR.repeat(10); let data2 = data.clone(); @@ -511,7 +511,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; #[test] -fn test_body() { +fn test_h1_body() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); @@ -526,7 +526,30 @@ fn test_body() { } #[test] -fn test_head_empty() { +fn test_h2_body2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h1_head_empty() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); @@ -549,7 +572,39 @@ fn test_head_empty() { } #[test] -fn test_head_binary() { +fn test_h2_head_empty() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), http::Version::HTTP_2); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h1_head_binary() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) @@ -574,7 +629,42 @@ fn test_head_binary() { } #[test] -fn test_head_binary2() { +fn test_h2_head_binary() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h1_head_binary2() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); @@ -593,7 +683,34 @@ fn test_head_binary2() { } #[test] -fn test_body_length() { +fn test_h2_head_binary2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); + let response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[test] +fn test_h1_body_length() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); @@ -614,17 +731,58 @@ fn test_body_length() { } #[test] -fn test_body_chunked_explicit() { +fn test_h2_body_length() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().body(Body::from_message( + body::SizedStream::new(STR.len(), body), + ))) + }) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h1_body_chunked_explicit() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) }) }); let req = srv.get().finish().unwrap(); let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); + assert_eq!( + response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "chunked" + ); // read response let bytes = srv.block_on(response.body()).unwrap(); @@ -634,7 +792,41 @@ fn test_body_chunked_explicit() { } #[test] -fn test_body_chunked_implicit() { +fn test_h2_body_chunked_explicit() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h1_body_chunked_implicit() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); @@ -645,17 +837,23 @@ fn test_body_chunked_implicit() { let req = srv.get().finish().unwrap(); let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); + assert_eq!( + response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "chunked" + ); // read response let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -use actix_server_config::ServerConfig; -use actix_service::fn_cfg_factory; - #[test] -fn test_response_http_error_handling() { +fn test_h1_response_http_error_handling() { let mut srv = TestServer::new(|| { HttpService::build().h1(fn_cfg_factory(|_: &ServerConfig| { Ok::<_, ()>(|_| { @@ -677,3 +875,76 @@ fn test_response_http_error_handling() { let bytes = srv.block_on(response.body()).unwrap(); assert!(bytes.is_empty()); } + +#[test] +fn test_h2_response_http_error_handling() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(fn_cfg_factory(|_: &ServerConfig| { + Ok::<_, ()>(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }) + })) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h1_service_error() { + let mut srv = TestServer::new(|| { + HttpService::build() + .h1(|_| Err::(error::ErrorBadRequest("error"))) + }); + + let req = srv.get().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_service_error() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| Err::(error::ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); + + let req = srv.sget().finish().unwrap(); + let mut response = srv.send_request(req).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert!(bytes.is_empty()); +} From 409888fcd52a865549e71149bf8c28423cf1b0ea Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 16:47:12 -0700 Subject: [PATCH 1053/1635] remove debug print, remove unused flags --- src/h2/dispatcher.rs | 9 --------- src/h2/mod.rs | 5 +---- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index 28465b50..cbba34c0 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -28,20 +28,12 @@ use crate::response::Response; const CHUNK_SIZE: usize = 16_384; -bitflags! { - struct Flags: u8 { - const DISCONNECTED = 0b0000_0001; - const SHUTDOWN = 0b0000_0010; - } -} - /// Dispatcher for HTTP/2 protocol pub struct Dispatcher< T: AsyncRead + AsyncWrite, S: Service + 'static, B: MessageBody, > { - flags: Flags, service: CloneableService, connection: Connection, config: ServiceConfig, @@ -86,7 +78,6 @@ where ka_expire, ka_timer, connection, - flags: Flags::empty(), _t: PhantomData, } } diff --git a/src/h2/mod.rs b/src/h2/mod.rs index 919317e0..c5972123 100644 --- a/src/h2/mod.rs +++ b/src/h2/mod.rs @@ -40,10 +40,7 @@ impl Stream for Payload { } Ok(Async::Ready(None)) => Ok(Async::Ready(None)), Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => { - println!("======== {:?}", err); - Err(err.into()) - } + Err(err) => Err(err.into()), } } } From 00d47acedc9cebdaa73f773a41991bd0a6b3b6af Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 17:56:48 -0700 Subject: [PATCH 1054/1635] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5a25567..467e67a9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![Build status](https://ci.appveyor.com/api/projects/status/i51p65u2bukstgwg/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-http-b1qsn/branch/master) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix http [![Build Status](https://travis-ci.org/actix/actix-http.svg?branch=master)](https://travis-ci.org/actix/actix-http) [![codecov](https://codecov.io/gh/actix/actix-http/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-http) [![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Actix http From a2c4639074baa0b1275d0b46b9560eafb88d0f28 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 23:11:51 -0700 Subject: [PATCH 1055/1635] move blocking code to actix-rt --- Cargo.toml | 5 +-- src/blocking.rs | 93 ------------------------------------------------- src/error.rs | 2 -- src/lib.rs | 7 ++-- 4 files changed, 4 insertions(+), 103 deletions(-) delete mode 100644 src/blocking.rs diff --git a/Cargo.toml b/Cargo.toml index c64f4bc6..e2439294 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ actix-codec = "0.1.0" actix-service = "0.3.3" actix-utils = "0.3.3" actix-router = "0.1.0" -actix-rt = "0.2.0" +actix-rt = "0.2.1" actix-web-codegen = { path="actix-web-codegen" } actix-http = { git = "https://github.com/actix/actix-http.git" } @@ -78,16 +78,13 @@ encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" log = "0.4" -lazy_static = "1.2" mime = "0.3" net2 = "0.2.33" -num_cpus = "1.10" parking_lot = "0.7" regex = "1.0" serde = "1.0" serde_json = "1.0" serde_urlencoded = "^0.5.3" -threadpool = "1.7" time = "0.1" url = { version="1.7", features=["query_encoding"] } diff --git a/src/blocking.rs b/src/blocking.rs deleted file mode 100644 index fc9cec29..00000000 --- a/src/blocking.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Thread pool for blocking operations - -use std::fmt; - -use derive_more::Display; -use futures::sync::oneshot; -use futures::{Async, Future, Poll}; -use parking_lot::Mutex; -use threadpool::ThreadPool; - -use crate::ResponseError; - -/// Env variable for default cpu pool size -const ENV_CPU_POOL_VAR: &str = "ACTIX_CPU_POOL"; - -lazy_static::lazy_static! { - pub(crate) static ref DEFAULT_POOL: Mutex = { - let default = match std::env::var(ENV_CPU_POOL_VAR) { - Ok(val) => { - if let Ok(val) = val.parse() { - val - } else { - log::error!("Can not parse ACTIX_CPU_POOL value"); - num_cpus::get() * 5 - } - } - Err(_) => num_cpus::get() * 5, - }; - Mutex::new( - threadpool::Builder::new() - .thread_name("actix-web".to_owned()) - .num_threads(default) - .build(), - ) - }; -} - -thread_local! { - static POOL: ThreadPool = { - DEFAULT_POOL.lock().clone() - }; -} - -/// Blocking operation execution error -#[derive(Debug, Display)] -pub enum BlockingError { - #[display(fmt = "{:?}", _0)] - Error(E), - #[display(fmt = "Thread pool is gone")] - Canceled, -} - -impl ResponseError for BlockingError {} - -/// Execute blocking function on a thread pool, returns future that resolves -/// to result of the function execution. -pub fn run(f: F) -> CpuFuture -where - F: FnOnce() -> Result + Send + 'static, - I: Send + 'static, - E: Send + fmt::Debug + 'static, -{ - let (tx, rx) = oneshot::channel(); - POOL.with(|pool| { - pool.execute(move || { - if !tx.is_canceled() { - let _ = tx.send(f()); - } - }) - }); - - CpuFuture { rx } -} - -/// Blocking operation completion future. It resolves with results -/// of blocking function execution. -pub struct CpuFuture { - rx: oneshot::Receiver>, -} - -impl Future for CpuFuture { - type Item = I; - type Error = BlockingError; - - fn poll(&mut self) -> Poll { - let res = - futures::try_ready!(self.rx.poll().map_err(|_| BlockingError::Canceled)); - match res { - Ok(val) => Ok(Async::Ready(val)), - Err(err) => Err(BlockingError::Error(err)), - } - } -} diff --git a/src/error.rs b/src/error.rs index fd0ee998..54ca74dc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,8 +4,6 @@ pub use actix_http::error::*; use derive_more::{Display, From}; use url::ParseError as UrlParseError; -pub use crate::blocking::BlockingError; - /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, From)] pub enum UrlGenerationError { diff --git a/src/lib.rs b/src/lib.rs index 62f6399d..0094818d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,6 @@ mod app; mod app_service; -mod blocking; mod config; pub mod error; mod extract; @@ -53,7 +52,6 @@ pub mod dev { //! ``` pub use crate::app::AppRouter; - pub use crate::blocking::CpuFuture; pub use crate::config::{AppConfig, ServiceConfig}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; @@ -67,6 +65,7 @@ pub mod dev { Extensions, Payload, PayloadStream, RequestHead, ResponseHead, }; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; + pub use actix_rt::blocking::CpuFuture; pub use actix_server::Server; pub(crate) fn insert_slash(path: &str) -> String { @@ -80,12 +79,12 @@ pub mod dev { pub mod web { use actix_http::{http::Method, Response}; + use actix_rt::blocking::{self, CpuFuture}; use futures::IntoFuture; pub use actix_http::Response as HttpResponse; pub use bytes::{Bytes, BytesMut}; - use crate::blocking::CpuFuture; use crate::extract::FromRequest; use crate::handler::{AsyncFactory, Factory}; use crate::resource::Resource; @@ -258,6 +257,6 @@ pub mod web { I: Send + 'static, E: Send + std::fmt::Debug + 'static, { - crate::blocking::run(f) + blocking::run(f) } } From 7242d967014ae0266191d471210df52569cbc0b4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 11 Mar 2019 23:19:05 -0700 Subject: [PATCH 1056/1635] map BlockingError --- actix-files/src/lib.rs | 10 +++++----- src/error.rs | 21 +++++++++++++++++++++ src/lib.rs | 11 +++++------ 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 07fc0063..b9240009 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -17,8 +17,8 @@ use v_htmlescape::escape as escape_html_entity; use actix_service::{boxed::BoxedNewService, NewService, Service}; use actix_web::dev::{ - CpuFuture, HttpServiceFactory, ResourceDef, ServiceConfig, ServiceFromRequest, - ServiceRequest, ServiceResponse, + HttpServiceFactory, ResourceDef, ServiceConfig, ServiceFromRequest, ServiceRequest, + ServiceResponse, }; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; @@ -52,7 +52,7 @@ pub struct ChunkedReadFile { size: u64, offset: u64, file: Option, - fut: Option>, + fut: Option>>>, counter: u64, } @@ -89,7 +89,7 @@ impl Stream for ChunkedReadFile { Ok(Async::Ready(None)) } else { let mut file = self.file.take().expect("Use after completion"); - self.fut = Some(web::block(move || { + self.fut = Some(Box::new(web::block(move || { let max_bytes: usize; max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; let mut buf = Vec::with_capacity(max_bytes); @@ -100,7 +100,7 @@ impl Stream for ChunkedReadFile { return Err(io::ErrorKind::UnexpectedEof.into()); } Ok((file, Bytes::from(buf))) - })); + }))); self.poll() } } diff --git a/src/error.rs b/src/error.rs index 54ca74dc..06840708 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ //! Error and Result module +use std::fmt; pub use actix_http::error::*; use derive_more::{Display, From}; @@ -20,3 +21,23 @@ pub enum UrlGenerationError { /// `InternalServerError` for `UrlGeneratorError` impl ResponseError for UrlGenerationError {} + +/// Blocking operation execution error +#[derive(Debug, Display)] +pub enum BlockingError { + #[display(fmt = "{:?}", _0)] + Error(E), + #[display(fmt = "Thread pool is gone")] + Canceled, +} + +impl ResponseError for BlockingError {} + +impl From> for BlockingError { + fn from(err: actix_rt::blocking::BlockingError) -> Self { + match err { + actix_rt::blocking::BlockingError::Error(e) => BlockingError::Error(e), + actix_rt::blocking::BlockingError::Canceled => BlockingError::Canceled, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 0094818d..d653fd1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,7 +65,6 @@ pub mod dev { Extensions, Payload, PayloadStream, RequestHead, ResponseHead, }; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; - pub use actix_rt::blocking::CpuFuture; pub use actix_server::Server; pub(crate) fn insert_slash(path: &str) -> String { @@ -79,8 +78,8 @@ pub mod dev { pub mod web { use actix_http::{http::Method, Response}; - use actix_rt::blocking::{self, CpuFuture}; - use futures::IntoFuture; + use actix_rt::blocking; + use futures::{Future, IntoFuture}; pub use actix_http::Response as HttpResponse; pub use bytes::{Bytes, BytesMut}; @@ -92,7 +91,7 @@ pub mod web { use crate::route::Route; use crate::scope::Scope; - pub use crate::error::Error; + pub use crate::error::{BlockingError, Error}; pub use crate::extract::{Form, Json, Path, Payload, Query}; pub use crate::extract::{FormConfig, JsonConfig, PayloadConfig}; pub use crate::request::HttpRequest; @@ -251,12 +250,12 @@ pub mod web { /// Execute blocking function on a thread pool, returns future that resolves /// to result of the function execution. - pub fn block(f: F) -> CpuFuture + pub fn block(f: F) -> impl Future> where F: FnOnce() -> Result + Send + 'static, I: Send + 'static, E: Send + std::fmt::Debug + 'static, { - blocking::run(f) + blocking::run(f).from_err() } } From 402a40ab27562bbd0927f25a28753bff5996ba86 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Mar 2019 16:55:16 -0700 Subject: [PATCH 1057/1635] update actix-server dep --- Cargo.toml | 7 ++++--- examples/framed_hello.rs | 3 ++- test-server/Cargo.toml | 6 +++--- tests/test_server.rs | 4 +++- tests/test_ws.rs | 3 ++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae81f152..9c0f84c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,11 +38,11 @@ ssl = ["openssl", "actix-connector/ssl"] fail = ["failure"] [dependencies] -actix-service = "0.3.3" +actix-service = "0.3.4" actix-codec = "0.1.1" actix-connector = "0.3.0" -actix-utils = "0.3.3" -actix-server-config = { git="https://github.com/actix/actix-net.git" } +actix-utils = "0.3.4" +actix-server-config = "0.1.0" base64 = "0.10" backtrace = "0.3" @@ -91,3 +91,4 @@ actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } +tokio-tcp = "0.1" \ No newline at end of file diff --git a/examples/framed_hello.rs b/examples/framed_hello.rs index 74b0f7df..7d4c13d3 100644 --- a/examples/framed_hello.rs +++ b/examples/framed_hello.rs @@ -7,6 +7,7 @@ use actix_service::{fn_service, NewService}; use actix_utils::framed::IntoFramed; use actix_utils::stream::TakeItem; use futures::Future; +use tokio_tcp::TcpStream; fn main() -> io::Result<()> { env::set_var("RUST_LOG", "framed_hello=info"); @@ -14,7 +15,7 @@ fn main() -> io::Result<()> { Server::build() .bind("framed_hello", "127.0.0.1:8080", || { - fn_service(|io: Io<_>| Ok(io.into_parts().0)) + fn_service(|io: Io| Ok(io.into_parts().0)) .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(_req, _framed): (_, Framed<_, _>)| { diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 49f18f04..2b469773 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -33,12 +33,12 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1.1" -actix-rt = "0.2.0" +actix-rt = "0.2.1" actix-http = { path=".." } -actix-service = "0.3.3" +actix-service = "0.3.4" #actix-server = "0.3.0" actix-server = { git="https://github.com/actix/actix-net.git" } -actix-utils = "0.3.2" +actix-utils = "0.3.4" base64 = "0.10" bytes = "0.4" diff --git a/tests/test_server.rs b/tests/test_server.rs index 49a943db..8266bb9a 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -2,6 +2,7 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; +use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; use actix_service::{fn_cfg_factory, NewService}; @@ -50,7 +51,8 @@ fn test_h1_2() { } #[cfg(feature = "ssl")] -fn ssl_acceptor() -> std::io::Result> { +fn ssl_acceptor( +) -> std::io::Result> { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); diff --git a/tests/test_ws.rs b/tests/test_ws.rs index 634b9acd..d8942fb1 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -9,6 +9,7 @@ use actix_utils::stream::TakeItem; use bytes::{Bytes, BytesMut}; use futures::future::{ok, Either}; use futures::{Future, Sink, Stream}; +use tokio_tcp::TcpStream; use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig}; @@ -36,7 +37,7 @@ fn ws_service(req: ws::Frame) -> impl Future| Ok(io.into_parts().0)) + fn_service(|io: Io| Ok(io.into_parts().0)) .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) .and_then(TakeItem::new().map_err(|_| ())) .and_then(|(req, framed): (_, Framed<_, _>)| { From f627d0105552ee44cd75bd799143107f4ae57e54 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Mar 2019 17:04:08 -0700 Subject: [PATCH 1058/1635] update actix-server --- Cargo.toml | 3 +-- test-server/Cargo.toml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c0f84c5..8e0f3a68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,8 +83,7 @@ failure = { version = "0.1.5", optional = true } [dev-dependencies] actix-rt = "0.2.0" -#actix-server = { version = "0.3.0", features=["ssl"] } -actix-server = { git="https://github.com/actix/actix-net.git", features=["ssl"] } +actix-server = { version = "0.4.0", features=["ssl"] } actix-connector = { version = "0.3.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 2b469773..b7535f99 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -36,8 +36,7 @@ actix-codec = "0.1.1" actix-rt = "0.2.1" actix-http = { path=".." } actix-service = "0.3.4" -#actix-server = "0.3.0" -actix-server = { git="https://github.com/actix/actix-net.git" } +actix-server = "0.4.0" actix-utils = "0.3.4" base64 = "0.10" From 28f01beaec78568f38ddaea3d39ff13085b59923 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Mar 2019 17:06:08 -0700 Subject: [PATCH 1059/1635] update deps --- Cargo.toml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e2439294..87a54b6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,15 +61,14 @@ ssl = ["openssl", "actix-server/ssl"] [dependencies] actix-codec = "0.1.0" -actix-service = "0.3.3" -actix-utils = "0.3.3" +actix-service = "0.3.4" +actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.1" actix-web-codegen = { path="actix-web-codegen" } - actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-server = { git = "https://github.com/actix/actix-net.git" } -actix-server-config = { git = "https://github.com/actix/actix-net.git" } +actix-server = "0.4.0" +actix-server-config = "0.1.0" bytes = "0.4" cookie = { version="0.11", features=["percent-encode"] } From 86405cfe7a3e4784200f41fa7ec2f29f572d7819 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 12 Mar 2019 22:57:09 -0700 Subject: [PATCH 1060/1635] more tests --- src/responder.rs | 95 +++++++++++++++++++++++++++++++++++++--- src/server.rs | 4 +- src/test.rs | 13 +++++- tests/test_httpserver.rs | 75 +++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 tests/test_httpserver.rs diff --git a/src/responder.rs b/src/responder.rs index 6dce300a..ace360c6 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -261,7 +261,8 @@ where type Future = Result; fn respond_to(self, _: &HttpRequest) -> Self::Future { - Err(self.into()) + let err: Error = self.into(); + Ok(err.into()) } } @@ -289,12 +290,13 @@ where #[cfg(test)] mod tests { use actix_service::Service; - use bytes::Bytes; + use bytes::{Bytes, BytesMut}; + use super::*; use crate::dev::{Body, ResponseBody}; - use crate::http::StatusCode; - use crate::test::{init_service, TestRequest}; - use crate::{web, App}; + use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; + use crate::test::{block_on, init_service, TestRequest}; + use crate::{error, web, App, HttpResponse}; #[test] fn test_option_responder() { @@ -319,4 +321,87 @@ mod tests { _ => panic!(), } } + + trait BodyTest { + fn bin_ref(&self) -> &[u8]; + fn body(&self) -> &Body; + } + + impl BodyTest for ResponseBody { + fn bin_ref(&self) -> &[u8] { + match self { + ResponseBody::Body(ref b) => match b { + Body::Bytes(ref bin) => &bin, + _ => panic!(), + }, + ResponseBody::Other(ref b) => match b { + Body::Bytes(ref bin) => &bin, + _ => panic!(), + }, + } + } + fn body(&self) -> &Body { + match self { + ResponseBody::Body(ref b) => b, + ResponseBody::Other(ref b) => b, + } + } + } + + #[test] + fn test_responder() { + let req = TestRequest::default().to_http_request(); + + let resp: HttpResponse = block_on(().respond_to(&req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(*resp.body().body(), Body::Empty); + + let resp: HttpResponse = block_on("test".respond_to(&req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + + let resp: HttpResponse = block_on(b"test".respond_to(&req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + + let resp: HttpResponse = block_on("test".to_string().respond_to(&req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + + let resp: HttpResponse = + block_on(Bytes::from_static(b"test").respond_to(&req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + + let resp: HttpResponse = + block_on(BytesMut::from(b"test".as_ref()).respond_to(&req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + + let resp: HttpResponse = + error::InternalError::new("err", StatusCode::BAD_REQUEST) + .respond_to(&req) + .unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } } diff --git a/src/server.rs b/src/server.rs index f80e5a0e..9055be30 100644 --- a/src/server.rs +++ b/src/server.rs @@ -182,8 +182,8 @@ where /// 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); + pub fn server_hostname>(mut self, val: T) -> Self { + self.host = Some(val.as_ref().to_owned()); self } diff --git a/src/test.rs b/src/test.rs index 03700b67..57a6d396 100644 --- a/src/test.rs +++ b/src/test.rs @@ -12,7 +12,7 @@ use actix_server_config::ServerConfig; use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; use cookie::Cookie; -use futures::Future; +use futures::future::{lazy, Future}; use crate::config::{AppConfig, AppConfigInner}; use crate::rmap::ResourceMap; @@ -42,6 +42,17 @@ where RT.with(move |rt| rt.borrow_mut().block_on(f)) } +/// Runs the provided function, with runtime enabled. +/// +/// Note that this function is intended to be used only for testing purpose. +/// This function panics on nested call. +pub fn run_on(f: F) -> Result +where + F: Fn() -> Result, +{ + RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| f()))) +} + /// This method accepts application builder instance, and constructs /// service. /// diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs new file mode 100644 index 00000000..d2bc07ac --- /dev/null +++ b/tests/test_httpserver.rs @@ -0,0 +1,75 @@ +use net2::TcpBuilder; +use std::sync::mpsc; +use std::{net, thread, time::Duration}; + +use actix_http::{client, Response}; + +use actix_web::{test, web, App, HttpServer}; + +fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + 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() +} + +#[test] +#[cfg(unix)] +fn test_start() { + let addr = unused_addr(); + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let sys = actix_rt::System::new("test"); + + let srv = HttpServer::new(|| { + App::new().service( + web::resource("/").route(web::to(|| Response::Ok().body("test"))), + ) + }) + .workers(1) + .backlog(1) + .maxconn(10) + .maxconnrate(10) + .keep_alive(10) + .client_timeout(5000) + .client_shutdown(0) + .server_hostname("localhost") + .system_exit() + .disable_signals() + .bind(format!("{}", addr)) + .unwrap() + .start(); + + let _ = tx.send((srv, actix_rt::System::current())); + let _ = sys.run(); + }); + let (srv, sys) = rx.recv().unwrap(); + + let mut connector = test::run_on(|| { + Ok::<_, ()>( + client::Connector::default() + .timeout(Duration::from_millis(100)) + .service(), + ) + }) + .unwrap(); + let host = format!("http://{}", addr); + + let response = test::block_on( + client::ClientRequest::get(host.clone()) + .finish() + .unwrap() + .send(&mut connector), + ) + .unwrap(); + assert!(response.status().is_success()); + + // stop + let _ = srv.stop(false); + + thread::sleep(Duration::from_millis(100)); + let _ = sys.stop(); +} From 17ecdd63d23f1b6ee424f67a0ad0852d9125f1af Mon Sep 17 00:00:00 2001 From: Luca Bruno Date: Wed, 13 Mar 2019 15:20:18 +0100 Subject: [PATCH 1061/1635] httpresponse: add constructor for HttpResponseBuilder (#697) --- src/httpresponse.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/httpresponse.rs b/src/httpresponse.rs index 226c847f..48f7b4c2 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -48,10 +48,10 @@ impl HttpResponse { self.0.as_mut() } - /// Create http response builder with specific status. + /// Create a new HTTP response builder with specific status. #[inline] pub fn build(status: StatusCode) -> HttpResponseBuilder { - HttpResponsePool::get(status) + HttpResponseBuilder::new(status) } /// Create http response builder @@ -346,6 +346,12 @@ pub struct HttpResponseBuilder { } impl HttpResponseBuilder { + /// Create a new HTTP response builder with specific status. + #[inline] + pub fn new(status: StatusCode) -> HttpResponseBuilder { + HttpResponsePool::get(status) + } + /// Set HTTP status code of this response. #[inline] pub fn status(&mut self, status: StatusCode) -> &mut Self { @@ -1177,6 +1183,14 @@ mod tests { assert_eq!((v.name(), v.value()), ("cookie3", "val300")); } + #[test] + fn test_builder_new() { + let resp = HttpResponseBuilder::new(StatusCode::BAD_REQUEST) + .finish(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[test] fn test_basic_builder() { let resp = HttpResponse::Ok() From 1941aa021798563b516d788bc60fe633b7f47944 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Mar 2019 14:41:40 -0700 Subject: [PATCH 1062/1635] use actix-connect crate --- Cargo.toml | 6 +- examples/client.rs | 2 +- src/client/connect.rs | 23 ++-- src/client/connector.rs | 255 ++++++++++++++++++--------------------- src/client/error.rs | 53 +++++--- src/client/h1proto.rs | 4 +- src/client/mod.rs | 2 +- src/client/pool.rs | 42 +++---- src/client/request.rs | 27 +++-- src/ws/client/error.rs | 4 +- src/ws/client/mod.rs | 2 +- src/ws/client/service.rs | 40 +++--- src/ws/mod.rs | 2 +- test-server/src/lib.rs | 108 +++++++---------- tests/test_client.rs | 6 +- tests/test_server.rs | 4 +- 16 files changed, 280 insertions(+), 300 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8e0f3a68..ee9d28ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ path = "src/lib.rs" default = ["fail"] # openssl -ssl = ["openssl", "actix-connector/ssl"] +ssl = ["openssl", "actix-connect/ssl"] # failure integration. it is on by default, it will be off in future versions # actix itself does not use failure anymore @@ -40,7 +40,8 @@ fail = ["failure"] [dependencies] actix-service = "0.3.4" actix-codec = "0.1.1" -actix-connector = "0.3.0" +#actix-connector = "0.3.0" +actix-connect = { path="../actix-net/actix-connect" } actix-utils = "0.3.4" actix-server-config = "0.1.0" @@ -71,6 +72,7 @@ sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.5.3" time = "0.1" +tokio-tcp = "0.1.3" tokio-timer = "0.2" tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } diff --git a/examples/client.rs b/examples/client.rs index 06b708e2..7f5f8c91 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -8,7 +8,7 @@ fn main() -> Result<(), Error> { env_logger::init(); System::new("test").block_on(lazy(|| { - let mut connector = client::Connector::default().service(); + let mut connector = client::Connector::new().service(); client::ClientRequest::get("https://www.rust-lang.org/") // <- Create request builder .header("User-Agent", "Actix-web") diff --git a/src/client/connect.rs b/src/client/connect.rs index f4112cfa..43be5770 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -1,8 +1,7 @@ -use actix_connector::{RequestHost, RequestPort}; use http::uri::Uri; -use http::{Error as HttpError, HttpTryFrom}; +use http::HttpTryFrom; -use super::error::{ConnectorError, InvalidUrlKind}; +use super::error::InvalidUrl; use super::pool::Key; #[derive(Debug)] @@ -19,7 +18,7 @@ impl Connect { } /// Construct `Uri` instance and create `Connect` message. - pub fn try_from(uri: U) -> Result + pub fn try_from(uri: U) -> Result where Uri: HttpTryFrom, { @@ -40,30 +39,26 @@ impl Connect { self.uri.authority_part().unwrap().clone().into() } - pub(crate) fn validate(&self) -> Result<(), ConnectorError> { + pub(crate) fn validate(&self) -> Result<(), InvalidUrl> { if self.uri.host().is_none() { - Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingHost)) + Err(InvalidUrl::MissingHost) } else if self.uri.scheme_part().is_none() { - Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingScheme)) + Err(InvalidUrl::MissingScheme) } else if let Some(scheme) = self.uri.scheme_part() { match scheme.as_str() { "http" | "ws" | "https" | "wss" => Ok(()), - _ => Err(ConnectorError::InvalidUrl(InvalidUrlKind::UnknownScheme)), + _ => Err(InvalidUrl::UnknownScheme), } } else { Ok(()) } } -} -impl RequestHost for Connect { - fn host(&self) -> &str { + pub(crate) fn host(&self) -> &str { &self.uri.host().unwrap() } -} -impl RequestPort for Connect { - fn port(&self) -> u16 { + pub(crate) fn port(&self) -> u16 { if let Some(port) = self.uri.port() { port } else if let Some(scheme) = self.uri.scheme_part() { diff --git a/src/client/connector.rs b/src/client/connector.rs index ccb5dbce..1579cd5e 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -1,14 +1,16 @@ +use std::fmt; +use std::marker::PhantomData; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connector::{Resolver, TcpConnector}; -use actix_service::{Service, ServiceExt}; +use actix_connect::{default_connector, Stream}; +use actix_service::{apply_fn, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; -use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; +use tokio_tcp::TcpStream; use super::connect::Connect; use super::connection::Connection; -use super::error::ConnectorError; +use super::error::ConnectError; use super::pool::{ConnectionPool, Protocol}; #[cfg(feature = "ssl")] @@ -19,20 +21,28 @@ type SslConnector = (); /// Http client connector builde instance. /// `Connector` type uses builder-like pattern for connector service construction. -pub struct Connector { - resolver: Resolver, +pub struct Connector { + connector: T, timeout: Duration, conn_lifetime: Duration, conn_keep_alive: Duration, disconnect_timeout: Duration, limit: usize, #[allow(dead_code)] - connector: SslConnector, + ssl: SslConnector, + _t: PhantomData, } -impl Default for Connector { - fn default() -> Connector { - let connector = { +impl Connector<(), ()> { + pub fn new() -> Connector< + impl Service< + Request = actix_connect::Connect, + Response = Stream, + Error = actix_connect::ConnectError, + > + Clone, + TcpStream, + > { + let ssl = { #[cfg(feature = "ssl")] { use log::error; @@ -49,30 +59,51 @@ impl Default for Connector { }; Connector { - connector, - resolver: Resolver::default(), + ssl, + connector: default_connector(), timeout: Duration::from_secs(1), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), disconnect_timeout: Duration::from_millis(3000), limit: 100, + _t: PhantomData, } } } -impl Connector { - /// Use custom resolver. - pub fn resolver(mut self, resolver: Resolver) -> Self { - self.resolver = resolver;; - self - } - - /// Use custom resolver configuration. - pub fn resolver_config(mut self, cfg: ResolverConfig, opts: ResolverOpts) -> Self { - self.resolver = Resolver::new(cfg, opts); - self +impl Connector { + /// Use custom connector. + pub fn connector(self, connector: T1) -> Connector + where + U1: AsyncRead + AsyncWrite + fmt::Debug, + T1: Service< + Request = actix_connect::Connect, + Response = Stream, + Error = actix_connect::ConnectError, + > + Clone, + { + Connector { + connector, + timeout: self.timeout, + conn_lifetime: self.conn_lifetime, + conn_keep_alive: self.conn_keep_alive, + disconnect_timeout: self.disconnect_timeout, + limit: self.limit, + ssl: self.ssl, + _t: PhantomData, + } } +} +impl Connector +where + U: AsyncRead + AsyncWrite + fmt::Debug + 'static, + T: Service< + Request = actix_connect::Connect, + Response = Stream, + Error = actix_connect::ConnectError, + > + Clone, +{ /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. /// Set to 1 second by default. pub fn timeout(mut self, timeout: Duration) -> Self { @@ -83,7 +114,7 @@ impl Connector { #[cfg(feature = "ssl")] /// Use custom `SslConnector` instance. pub fn ssl(mut self, connector: SslConnector) -> Self { - self.connector = connector; + self.ssl = connector; self } @@ -133,24 +164,18 @@ impl Connector { /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { + ) -> impl Service + + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( self.timeout, - self.resolver.map_err(ConnectorError::from).and_then( - TcpConnector::default() - .from_err() - .map(|(msg, io)| (msg, io, Protocol::Http1)), - ), + self.connector + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectorError::Timeout, + TimeoutError::Timeout => ConnectError::Timeout, }); connect_impl::InnerConnector { @@ -166,48 +191,49 @@ impl Connector { #[cfg(feature = "ssl")] { const H2: &[u8] = b"h2"; - use actix_connector::ssl::OpensslConnector; + use actix_connect::ssl::OpensslConnector; let ssl_service = TimeoutService::new( self.timeout, - self.resolver - .clone() - .map_err(ConnectorError::from) - .and_then(TcpConnector::default().from_err()) - .and_then( - OpensslConnector::service(self.connector) - .map_err(ConnectorError::from) - .map(|(msg, io)| { - let h2 = io - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (msg, io, Protocol::Http2) - } else { - (msg, io, Protocol::Http1) - } - }), - ), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectorError::Timeout, - }); - - let tcp_service = TimeoutService::new( - self.timeout, - self.resolver.map_err(ConnectorError::from).and_then( - TcpConnector::default() - .from_err() - .map(|(msg, io)| (msg, io, Protocol::Http1)), + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(actix_connect::Connect::new(msg.host(), msg.port())) + }) + .map_err(ConnectError::from) + .and_then( + OpensslConnector::service(self.ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (sock, Protocol::Http2) + } else { + (sock, Protocol::Http1) + } + }), ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectorError::Timeout, + TimeoutError::Timeout => ConnectError::Timeout, + }); + + let tcp_service = TimeoutService::new( + self.timeout, + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(actix_connect::Connect::new(msg.host(), msg.port())) + }) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), + ) + .map_err(|e| match e { + TimeoutError::Service(e) => e, + TimeoutError::Timeout => ConnectError::Timeout, }); connect_impl::InnerConnector { @@ -253,11 +279,8 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - > + Clone, + T: Service + + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -269,11 +292,7 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { type Request = Connect; type Response = IoConnection; @@ -289,7 +308,7 @@ mod connect_impl { fn call(&mut self, req: Connect) -> Self::Future { if req.is_secure() { - Either::B(err(ConnectorError::SslIsNotSupported)) + Either::B(err(ConnectError::SslIsNotSupported)) } else if let Err(e) = req.validate() { Either::B(err(e)) } else { @@ -303,7 +322,7 @@ mod connect_impl { mod connect_impl { use std::marker::PhantomData; - use futures::future::{err, Either, FutureResult}; + use futures::future::{Either, FutureResult}; use futures::{Async, Future, Poll}; use super::*; @@ -313,16 +332,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, - T2: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -332,16 +343,10 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - > + Clone, - T2: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - > + Clone, + T1: Service + + Clone, + T2: Service + + Clone, { fn clone(&self) -> Self { InnerConnector { @@ -355,20 +360,12 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, - T2: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T1: Service, + T2: Service, { type Request = Connect; type Response = EitherConnection; - type Error = ConnectorError; + type Error = ConnectError; type Future = Either< FutureResult, Either< @@ -382,9 +379,7 @@ mod connect_impl { } fn call(&mut self, req: Connect) -> Self::Future { - if let Err(e) = req.validate() { - Either::A(err(e)) - } else if req.is_secure() { + if req.is_secure() { Either::B(Either::B(InnerConnectorResponseB { fut: self.ssl_pool.call(req), _t: PhantomData, @@ -401,11 +396,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -413,16 +404,12 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service< - Request = Connect, - Response = (Connect, Io1, Protocol), - Error = ConnectorError, - >, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { type Item = EitherConnection; - type Error = ConnectorError; + type Error = ConnectError; fn poll(&mut self) -> Poll { match self.fut.poll()? { @@ -435,11 +422,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -447,16 +430,12 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service< - Request = Connect, - Response = (Connect, Io2, Protocol), - Error = ConnectorError, - >, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { type Item = EitherConnection; - type Error = ConnectorError; + type Error = ConnectError; fn poll(&mut self) -> Poll { match self.fut.poll()? { diff --git a/src/client/error.rs b/src/client/error.rs index 6c91ff97..4fce904f 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -11,11 +11,7 @@ use crate::response::Response; /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] -pub enum ConnectorError { - /// Invalid URL - #[display(fmt = "Invalid URL")] - InvalidUrl(InvalidUrlKind), - +pub enum ConnectError { /// SSL feature is not enabled #[display(fmt = "SSL is not supported")] SslIsNotSupported, @@ -45,24 +41,30 @@ pub enum ConnectorError { #[display(fmt = "Internal error: connector has been disconnected")] Disconnected, + /// Unresolved host name + #[display(fmt = "Connector received `Connect` method with unresolved host")] + Unresolverd, + /// Connection io error #[display(fmt = "{}", _0)] Io(io::Error), } -#[derive(Debug, Display)] -pub enum InvalidUrlKind { - #[display(fmt = "Missing url scheme")] - MissingScheme, - #[display(fmt = "Unknown url scheme")] - UnknownScheme, - #[display(fmt = "Missing host name")] - MissingHost, +impl From for ConnectError { + fn from(err: actix_connect::ConnectError) -> ConnectError { + match err { + actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), + actix_connect::ConnectError::NoRecords => ConnectError::NoRecords, + actix_connect::ConnectError::InvalidInput => panic!(), + actix_connect::ConnectError::Unresolverd => ConnectError::Unresolverd, + actix_connect::ConnectError::Io(e) => ConnectError::Io(e), + } + } } #[cfg(feature = "ssl")] -impl From> for ConnectorError { - fn from(err: HandshakeError) -> ConnectorError { +impl From> for ConnectError { + fn from(err: HandshakeError) -> ConnectError { match err { HandshakeError::SetupFailure(stack) => SslError::from(stack).into(), HandshakeError::Failure(stream) => stream.into_error().into(), @@ -71,12 +73,27 @@ impl From> for ConnectorError { } } +#[derive(Debug, Display, From)] +pub enum InvalidUrl { + #[display(fmt = "Missing url scheme")] + MissingScheme, + #[display(fmt = "Unknown url scheme")] + UnknownScheme, + #[display(fmt = "Missing host name")] + MissingHost, + #[display(fmt = "Url parse error: {}", _0)] + HttpError(http::Error), +} + /// A set of errors that can occur during request sending and response reading #[derive(Debug, Display, From)] pub enum SendRequestError { + /// Invalid URL + #[display(fmt = "Invalid URL: {}", _0)] + Url(InvalidUrl), /// Failed to connect to host #[display(fmt = "Failed to connect to host: {}", _0)] - Connector(ConnectorError), + Connect(ConnectError), /// Error sending request Send(io::Error), /// Error parsing response @@ -92,10 +109,10 @@ pub enum SendRequestError { impl ResponseError for SendRequestError { fn error_response(&self) -> Response { match *self { - SendRequestError::Connector(ConnectorError::Timeout) => { + SendRequestError::Connect(ConnectError::Timeout) => { Response::GatewayTimeout() } - SendRequestError::Connector(_) => Response::BadGateway(), + SendRequestError::Connect(_) => Response::BadGateway(), _ => Response::InternalServerError(), } .into() diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index 3329fcfe..34521cc2 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -6,7 +6,7 @@ use futures::future::{err, ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; -use super::error::{ConnectorError, SendRequestError}; +use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; use super::response::ClientResponse; use crate::body::{BodyLength, MessageBody}; @@ -62,7 +62,7 @@ where } ok(res) } else { - err(ConnectorError::Disconnected.into()) + err(ConnectError::Disconnected.into()) } }) }) diff --git a/src/client/mod.rs b/src/client/mod.rs index 8d041827..0bff97e4 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -12,6 +12,6 @@ mod response; pub use self::connect::Connect; pub use self::connection::Connection; pub use self::connector::Connector; -pub use self::error::{ConnectorError, InvalidUrlKind, SendRequestError}; +pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; pub use self::request::{ClientRequest, ClientRequestBuilder}; pub use self::response::ClientResponse; diff --git a/src/client/pool.rs b/src/client/pool.rs index 188980cb..214b7a38 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -20,7 +20,7 @@ use tokio_timer::{sleep, Delay}; use super::connect::Connect; use super::connection::{ConnectionType, IoConnection}; -use super::error::ConnectorError; +use super::error::ConnectError; #[derive(Clone, Copy, PartialEq)] pub enum Protocol { @@ -48,11 +48,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { pub(crate) fn new( connector: T, @@ -91,17 +87,13 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { type Request = Connect; type Response = IoConnection; - type Error = ConnectorError; + type Error = ConnectError; type Future = Either< - FutureResult, ConnectorError>, + FutureResult, Either, OpenConnection>, >; @@ -151,7 +143,7 @@ where { key: Key, token: usize, - rx: oneshot::Receiver, ConnectorError>>, + rx: oneshot::Receiver, ConnectError>>, inner: Option>>>, } @@ -173,7 +165,7 @@ where Io: AsyncRead + AsyncWrite, { type Item = IoConnection; - type Error = ConnectorError; + type Error = ConnectError; fn poll(&mut self) -> Poll { match self.rx.poll() { @@ -187,7 +179,7 @@ where Ok(Async::NotReady) => Ok(Async::NotReady), Err(_) => { let _ = self.inner.take(); - Err(ConnectorError::Disconnected) + Err(ConnectError::Disconnected) } } } @@ -206,7 +198,7 @@ where impl OpenConnection where - F: Future, + F: Future, Io: AsyncRead + AsyncWrite + 'static, { fn new(key: Key, inner: Rc>>, fut: F) -> Self { @@ -234,11 +226,11 @@ where impl Future for OpenConnection where - F: Future, + F: Future, Io: AsyncRead + AsyncWrite, { type Item = IoConnection; - type Error = ConnectorError; + type Error = ConnectError; fn poll(&mut self) -> Poll { if let Some(ref mut h2) = self.h2 { @@ -258,7 +250,7 @@ where match self.fut.poll() { Err(err) => Err(err), - Ok(Async::Ready((_, io, proto))) => { + Ok(Async::Ready((io, proto))) => { let _ = self.inner.take(); if proto == Protocol::Http1 { Ok(Async::Ready(IoConnection::new( @@ -289,7 +281,7 @@ where // impl OpenWaitingConnection // where -// F: Future + 'static, +// F: Future + 'static, // Io: AsyncRead + AsyncWrite + 'static, // { // fn spawn( @@ -323,7 +315,7 @@ where // impl Future for OpenWaitingConnection // where -// F: Future, +// F: Future, // Io: AsyncRead + AsyncWrite, // { // type Item = (); @@ -402,7 +394,7 @@ pub(crate) struct Inner { available: HashMap>>, waiters: Slab<( Connect, - oneshot::Sender, ConnectorError>>, + oneshot::Sender, ConnectError>>, )>, waiters_queue: IndexSet<(Key, usize)>, task: AtomicTask, @@ -444,7 +436,7 @@ where &mut self, connect: Connect, ) -> ( - oneshot::Receiver, ConnectorError>>, + oneshot::Receiver, ConnectError>>, usize, ) { let (tx, rx) = oneshot::channel(); @@ -534,7 +526,7 @@ where // impl Future for ConnectorPoolSupport // where // Io: AsyncRead + AsyncWrite + 'static, -// T: Service, +// T: Service, // T::Future: 'static, // { // type Item = (); diff --git a/src/client/request.rs b/src/client/request.rs index efae5b94..199e13b9 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -5,6 +5,7 @@ use std::io::Write; use actix_service::Service; use bytes::{BufMut, Bytes, BytesMut}; use cookie::{Cookie, CookieJar}; +use futures::future::{err, Either}; use futures::{Future, Stream}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use serde::Serialize; @@ -21,7 +22,7 @@ use crate::message::{ConnectionType, Head, RequestHead}; use super::connection::Connection; use super::response::ClientResponse; -use super::{Connect, ConnectorError, SendRequestError}; +use super::{Connect, ConnectError, SendRequestError}; /// An HTTP Client Request /// @@ -32,7 +33,8 @@ use super::{Connect, ConnectorError, SendRequestError}; /// /// fn main() { /// System::new("test").block_on(lazy(|| { -/// let mut connector = client::Connector::default().service(); +/// let mut connector = client::Connector::new().service(); +/// /// client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .finish().unwrap() @@ -178,17 +180,24 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; - connector - // connect to the host - .call(Connect::new(head.uri.clone())) - .from_err() - // send request - .and_then(move |connection| connection.send_request(head, body)) + let connect = Connect::new(head.uri.clone()); + if let Err(e) = connect.validate() { + Either::A(err(e.into())) + } else { + Either::B( + connector + // connect to the host + .call(connect) + .from_err() + // send request + .and_then(move |connection| connection.send_request(head, body)), + ) + } } } diff --git a/src/ws/client/error.rs b/src/ws/client/error.rs index 1eccb0b9..ae1e3996 100644 --- a/src/ws/client/error.rs +++ b/src/ws/client/error.rs @@ -1,7 +1,7 @@ //! Http client request use std::io; -use actix_connector::ConnectorError; +use actix_connect::ConnectError; use derive_more::{Display, From}; use http::{header::HeaderValue, Error as HttpError, StatusCode}; @@ -43,7 +43,7 @@ pub enum ClientError { Protocol(ProtocolError), /// Connect error #[display(fmt = "Connector error: {:?}", _0)] - Connect(ConnectorError), + Connect(ConnectError), /// IO Error #[display(fmt = "{}", _0)] Io(io::Error), diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 12c7229b..0dbf081c 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -4,7 +4,7 @@ mod service; pub use self::connect::Connect; pub use self::error::ClientError; -pub use self::service::{Client, DefaultClient}; +pub use self::service::Client; #[derive(PartialEq, Hash, Debug, Clone, Copy)] pub(crate) enum Protocol { diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 586873d1..e3781e15 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -2,8 +2,8 @@ use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_connector::{Connect as TcpConnect, ConnectorError, DefaultConnector}; -use actix_service::Service; +use actix_connect::{default_connector, Connect as TcpConnect, ConnectError}; +use actix_service::{apply_fn, Service}; use base64; use futures::future::{err, Either, FutureResult}; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; @@ -20,21 +20,29 @@ use crate::ws::Codec; use super::{ClientError, Connect, Protocol}; -/// Default client, uses default connector. -pub type DefaultClient = Client; - /// WebSocket's client -pub struct Client -where - T: Service, - T::Response: AsyncRead + AsyncWrite, -{ +pub struct Client { connector: T, } +impl Client<()> { + /// Create client with default connector. + pub fn default() -> Client< + impl Service< + Request = TcpConnect, + Response = impl AsyncRead + AsyncWrite, + Error = ConnectError, + > + Clone, + > { + Client::new(apply_fn(default_connector(), |msg: TcpConnect, srv| { + srv.call(msg).map(|stream| stream.into_parts().0) + })) + } +} + impl Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -43,15 +51,9 @@ where } } -impl Default for Client { - fn default() -> Self { - Client::new(DefaultConnector::default()) - } -} - impl Clone for Client where - T: Service + Clone, + T: Service + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -63,7 +65,7 @@ where impl Service for Client where - T: Service, + T: Service, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { diff --git a/src/ws/mod.rs b/src/ws/mod.rs index a8de59dd..8f9bc83b 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -21,7 +21,7 @@ mod proto; mod service; mod transport; -pub use self::client::{Client, ClientError, Connect, DefaultClient}; +pub use self::client::{Client, ClientError, Connect}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{CloseCode, CloseReason, OpCode}; diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index d810d891..3bb5feff 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -1,18 +1,17 @@ //! Various helpers for Actix applications to use during testing. use std::sync::mpsc; -use std::{net, thread}; +use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::MessageBody; use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, Connection, Connector, - ConnectorError, SendRequestError, + ClientRequest, ClientRequestBuilder, ClientResponse, Connect, ConnectError, + Connection, Connector, SendRequestError, }; use actix_http::ws; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; use actix_service::Service; - use futures::future::{lazy, Future}; use http::Method; use net2::TcpBuilder; @@ -44,21 +43,15 @@ use net2::TcpBuilder; /// ``` pub struct TestServer; -/// -pub struct TestServerRuntime { +/// Test server controller +pub struct TestServerRuntime { addr: net::SocketAddr, - conn: T, rt: Runtime, } impl TestServer { /// Start new test server with application factory - pub fn new( - factory: F, - ) -> TestServerRuntime< - impl Service - + Clone, - > { + pub fn new(factory: F) -> TestServerRuntime { let (tx, rx) = mpsc::channel(); // run server in separate thread @@ -79,35 +72,9 @@ impl TestServer { let (system, addr) = rx.recv().unwrap(); System::set_current(system); - - let mut rt = Runtime::new().unwrap(); - let conn = rt - .block_on(lazy(|| Ok::<_, ()>(TestServer::new_connector()))) - .unwrap(); - - TestServerRuntime { addr, conn, rt } - } - - fn new_connector( - ) -> impl Service< - Request = Connect, - Response = impl Connection, - Error = ConnectorError, - > + Clone { - #[cfg(feature = "ssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Connector::default().ssl(builder.build()).service() - } - #[cfg(not(feature = "ssl"))] - { - Connector::default().service() + TestServerRuntime { + addr, + rt: Runtime::new().unwrap(), } } @@ -122,7 +89,7 @@ impl TestServer { } } -impl TestServerRuntime { +impl TestServerRuntime { /// Execute future on current core pub fn block_on(&mut self, fut: F) -> Result where @@ -131,12 +98,12 @@ impl TestServerRuntime { self.rt.block_on(fut) } - /// Execute future on current core - pub fn execute(&mut self, fut: F) -> Result + /// Execute function on current core + pub fn execute(&mut self, fut: F) -> R where - F: Future, + F: FnOnce() -> R, { - self.rt.block_on(fut) + self.rt.block_on(lazy(|| Ok::<_, ()>(fut()))).unwrap() } /// Construct test server url @@ -190,17 +157,37 @@ impl TestServerRuntime { .take() } - /// Http connector - pub fn connector(&mut self) -> &mut T { - &mut self.conn + fn new_connector( + ) -> impl Service + + Clone { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + Connector::new() + .timeout(time::Duration::from_millis(500)) + .ssl(builder.build()) + .service() + } + #[cfg(not(feature = "ssl"))] + { + Connector::new() + .timeout(time::Duration::from_millis(500)) + .service() + } } /// Http connector - pub fn new_connector(&mut self) -> T - where - T: Clone, - { - self.conn.clone() + pub fn connector( + &mut self, + ) -> impl Service + + Clone { + self.execute(|| TestServerRuntime::new_connector()) } /// Stop http server @@ -209,11 +196,7 @@ impl TestServerRuntime { } } -impl TestServerRuntime -where - T: Service + Clone, - T::Response: Connection, -{ +impl TestServerRuntime { /// Connect to websocket server at a given path pub fn ws_at( &mut self, @@ -236,11 +219,12 @@ where &mut self, req: ClientRequest, ) -> Result { - self.rt.block_on(req.send(&mut self.conn)) + let mut conn = self.connector(); + self.rt.block_on(req.send(&mut conn)) } } -impl Drop for TestServerRuntime { +impl Drop for TestServerRuntime { fn drop(&mut self) { self.stop() } diff --git a/tests/test_client.rs b/tests/test_client.rs index 782e487c..90e1a4f4 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -36,7 +36,7 @@ fn test_h1_v2() { .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let request = srv.get().finish().unwrap(); let response = srv.block_on(request.send(&mut connector)).unwrap(); @@ -70,7 +70,7 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let request = srv.get().close().finish().unwrap(); let response = srv.block_on(request.send(&mut connector)).unwrap(); @@ -90,7 +90,7 @@ fn test_with_query_parameter() { }) .map(|_| ()) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let request = client::ClientRequest::get(srv.url("/?qp=5")) .finish() diff --git a/tests/test_server.rs b/tests/test_server.rs index 8266bb9a..98f74094 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -432,7 +432,7 @@ fn test_h1_headers() { future::ok::<_, ()>(builder.body(data.clone())) }) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let req = srv.get().finish().unwrap(); @@ -479,7 +479,7 @@ fn test_h2_headers() { future::ok::<_, ()>(builder.body(data.clone())) }).map_err(|_| ())) }); - let mut connector = srv.new_connector(); + let mut connector = srv.connector(); let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); let mut response = srv.block_on(req.send(&mut connector)).unwrap(); From 033a8d890cc276ee4ebdbbbd614aa2c20d085c49 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Mar 2019 15:57:33 -0700 Subject: [PATCH 1063/1635] update actix connect --- Cargo.toml | 3 +-- src/client/connect.rs | 7 +++++-- src/client/connector.rs | 32 ++++++++++++++++---------------- src/ws/client/service.rs | 16 ++++++++-------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ee9d28ac..11f532c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,7 @@ fail = ["failure"] [dependencies] actix-service = "0.3.4" actix-codec = "0.1.1" -#actix-connector = "0.3.0" -actix-connect = { path="../actix-net/actix-connect" } +actix-connect = { git = "https://github.com/actix/actix-net.git" } actix-utils = "0.3.4" actix-server-config = "0.1.0" diff --git a/src/client/connect.rs b/src/client/connect.rs index 43be5770..93626b0a 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -1,3 +1,4 @@ +use actix_connect::Address; use http::uri::Uri; use http::HttpTryFrom; @@ -53,12 +54,14 @@ impl Connect { Ok(()) } } +} - pub(crate) fn host(&self) -> &str { +impl Address for Connect { + fn host(&self) -> &str { &self.uri.host().unwrap() } - pub(crate) fn port(&self) -> u16 { + fn port(&self) -> u16 { if let Some(port) = self.uri.port() { port } else if let Some(scheme) = self.uri.scheme_part() { diff --git a/src/client/connector.rs b/src/client/connector.rs index 1579cd5e..aaa88abc 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -3,7 +3,9 @@ use std::marker::PhantomData; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connect::{default_connector, Stream}; +use actix_connect::{ + default_connector, Connect as TcpConnect, Connection as TcpConnection, +}; use actix_service::{apply_fn, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use tokio_tcp::TcpStream; @@ -36,8 +38,8 @@ pub struct Connector { impl Connector<(), ()> { pub fn new() -> Connector< impl Service< - Request = actix_connect::Connect, - Response = Stream, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, TcpStream, @@ -77,8 +79,8 @@ impl Connector { where U1: AsyncRead + AsyncWrite + fmt::Debug, T1: Service< - Request = actix_connect::Connect, - Response = Stream, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, { @@ -99,8 +101,8 @@ impl Connector where U: AsyncRead + AsyncWrite + fmt::Debug + 'static, T: Service< - Request = actix_connect::Connect, - Response = Stream, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, { @@ -170,8 +172,10 @@ where { let connector = TimeoutService::new( self.timeout, - self.connector - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector, |msg: Connect, srv| { + srv.call(actix_connect::Connect::with_request(msg)) + }) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -196,7 +200,7 @@ where let ssl_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg.host(), msg.port())) + srv.call(actix_connect::Connect::with_request(msg)) }) .map_err(ConnectError::from) .and_then( @@ -226,7 +230,7 @@ where let tcp_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg.host(), msg.port())) + srv.call(actix_connect::Connect::with_request(msg)) }) .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), @@ -267,11 +271,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service< - Request = Connect, - Response = (Connect, Io, Protocol), - Error = ConnectorError, - >, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index e3781e15..7be30993 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_connect::{default_connector, Connect as TcpConnect, ConnectError}; +use actix_connect::{default_connector, Address, Connect as TcpConnect, ConnectError}; use actix_service::{apply_fn, Service}; use base64; use futures::future::{err, Either, FutureResult}; @@ -29,12 +29,12 @@ impl Client<()> { /// Create client with default connector. pub fn default() -> Client< impl Service< - Request = TcpConnect, + Request = TcpConnect<(String, u16)>, Response = impl AsyncRead + AsyncWrite, Error = ConnectError, > + Clone, > { - Client::new(apply_fn(default_connector(), |msg: TcpConnect, srv| { + Client::new(apply_fn(default_connector(), |msg: TcpConnect<_>, srv| { srv.call(msg).map(|stream| stream.into_parts().0) })) } @@ -42,7 +42,7 @@ impl Client<()> { impl Client where - T: Service, + T: Service, Error = ConnectError>, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -53,7 +53,7 @@ where impl Clone for Client where - T: Service + Clone, + T: Service, Error = ConnectError> + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -65,7 +65,7 @@ where impl Service for Client where - T: Service, + T: Service, Error = ConnectError>, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { @@ -130,8 +130,8 @@ where ); // prep connection - let connect = TcpConnect::new( - request.uri().host().unwrap(), + let connect = TcpConnect::from_string( + request.uri().host().unwrap().to_string(), request.uri().port().unwrap_or_else(|| proto.port()), ); From 3a24a75d137fa41ba9db8b5b099b1b76c92b40d8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Mar 2019 16:56:11 -0700 Subject: [PATCH 1064/1635] update dep --- src/client/connector.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index aaa88abc..b8b583a9 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -173,7 +173,7 @@ where let connector = TimeoutService::new( self.timeout, apply_fn(self.connector, |msg: Connect, srv| { - srv.call(actix_connect::Connect::with_request(msg)) + srv.call(actix_connect::Connect::with(msg)) }) .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) @@ -200,7 +200,7 @@ where let ssl_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::with_request(msg)) + srv.call(actix_connect::Connect::with(msg)) }) .map_err(ConnectError::from) .and_then( @@ -230,7 +230,7 @@ where let tcp_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::with_request(msg)) + srv.call(actix_connect::Connect::with(msg)) }) .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), From d2c755bb47907f35e5883507b0a56cb23e4f4bf5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 13 Mar 2019 22:57:28 -0700 Subject: [PATCH 1065/1635] update client connector --- src/client/connect.rs | 7 ++++--- src/client/connector.rs | 6 +++--- src/ws/client/service.rs | 14 ++++++-------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/client/connect.rs b/src/client/connect.rs index 93626b0a..82e5e45c 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -61,8 +61,8 @@ impl Address for Connect { &self.uri.host().unwrap() } - fn port(&self) -> u16 { - if let Some(port) = self.uri.port() { + fn port(&self) -> Option { + let port = if let Some(port) = self.uri.port() { port } else if let Some(scheme) = self.uri.scheme_part() { match scheme.as_str() { @@ -72,6 +72,7 @@ impl Address for Connect { } } else { 80 - } + }; + Some(port) } } diff --git a/src/client/connector.rs b/src/client/connector.rs index b8b583a9..c764b93c 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -173,7 +173,7 @@ where let connector = TimeoutService::new( self.timeout, apply_fn(self.connector, |msg: Connect, srv| { - srv.call(actix_connect::Connect::with(msg)) + srv.call(actix_connect::Connect::new(msg)) }) .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) @@ -200,7 +200,7 @@ where let ssl_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::with(msg)) + srv.call(actix_connect::Connect::new(msg)) }) .map_err(ConnectError::from) .and_then( @@ -230,7 +230,7 @@ where let tcp_service = TimeoutService::new( self.timeout, apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::with(msg)) + srv.call(actix_connect::Connect::new(msg)) }) .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 7be30993..1aa39124 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -29,7 +29,7 @@ impl Client<()> { /// Create client with default connector. pub fn default() -> Client< impl Service< - Request = TcpConnect<(String, u16)>, + Request = TcpConnect, Response = impl AsyncRead + AsyncWrite, Error = ConnectError, > + Clone, @@ -42,7 +42,7 @@ impl Client<()> { impl Client where - T: Service, Error = ConnectError>, + T: Service, Error = ConnectError>, T::Response: AsyncRead + AsyncWrite, { /// Create new websocket's client factory @@ -53,7 +53,7 @@ where impl Clone for Client where - T: Service, Error = ConnectError> + Clone, + T: Service, Error = ConnectError> + Clone, T::Response: AsyncRead + AsyncWrite, { fn clone(&self) -> Self { @@ -65,7 +65,7 @@ where impl Service for Client where - T: Service, Error = ConnectError>, + T: Service, Error = ConnectError>, T::Response: AsyncRead + AsyncWrite + 'static, T::Future: 'static, { @@ -130,10 +130,8 @@ where ); // prep connection - let connect = TcpConnect::from_string( - request.uri().host().unwrap().to_string(), - request.uri().port().unwrap_or_else(|| proto.port()), - ); + let connect = TcpConnect::new(request.uri().host().unwrap().to_string()) + .set_port(request.uri().port().unwrap_or_else(|| proto.port())); let fut = Box::new( self.connector From bf8262196f2d05f053ebcac774d2578c86b815af Mon Sep 17 00:00:00 2001 From: Jannik Keye Date: Thu, 14 Mar 2019 09:36:10 +0100 Subject: [PATCH 1066/1635] feat: enable use of patch as request method (#718) --- CHANGES.md | 2 ++ src/client/mod.rs | 7 +++++++ src/client/request.rs | 7 +++++++ src/resource.rs | 6 ++++++ src/test.rs | 5 +++++ tests/test_server.rs | 8 ++++++++ 6 files changed, 35 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 76f3465e..57333613 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ * Add client HTTP Authentication methods `.basic_auth()` and `.bearer_auth()`. #540 +* Add support for PATCH HTTP method + ### Fixed * Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680 diff --git a/src/client/mod.rs b/src/client/mod.rs index 5321e4b0..8c15fae4 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -105,6 +105,13 @@ pub fn post>(uri: U) -> ClientRequestBuilder { builder } +/// Create request builder for `PATCH` requests +pub fn patch>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::PATCH).uri(uri); + builder +} + /// Create request builder for `PUT` requests pub fn put>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); diff --git a/src/client/request.rs b/src/client/request.rs index 89789933..bf5145df 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -112,6 +112,13 @@ impl ClientRequest { builder } + /// Create request builder for `PATCH` request + pub fn patch>(uri: U) -> ClientRequestBuilder { + let mut builder = ClientRequest::build(); + builder.method(Method::PATCH).uri(uri); + builder + } + /// Create request builder for `PUT` request pub fn put>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); diff --git a/src/resource.rs b/src/resource.rs index d884dd44..78aea07c 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -107,6 +107,12 @@ impl Resource { self.routes.last_mut().unwrap().filter(pred::Post()) } + /// Register a new `PATCH` route. + pub fn patch(&mut self) -> &mut Route { + self.routes.push(Route::default()); + self.routes.last_mut().unwrap().filter(pred::Patch()) + } + /// Register a new `PUT` route. pub fn put(&mut self) -> &mut Route { self.routes.push(Route::default()); diff --git a/src/test.rs b/src/test.rs index 1d86db9f..584c02a5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -239,6 +239,11 @@ impl TestServer { ClientRequest::post(self.url("/").as_str()) } + /// Create `PATCH` request + pub fn patch(&self) -> ClientRequestBuilder { + ClientRequest::patch(self.url("/").as_str()) + } + /// Create `HEAD` request pub fn head(&self) -> ClientRequestBuilder { ClientRequest::head(self.url("/").as_str()) diff --git a/tests/test_server.rs b/tests/test_server.rs index f3c9bf9d..68482bb1 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1398,3 +1398,11 @@ fn test_content_length() { assert_eq!(response.headers().get(&header), Some(&value)); } } + +#[test] +fn test_patch_method() { + let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok())); + let req = srv.patch().finish().unwrap(); + let response = srv.execute(req.send()).unwrap(); + assert!(response.status().is_success()); +} \ No newline at end of file From b8bfd29d2c5e9b5a9ce10e27a1e1898d54e40444 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 11:52:52 -0700 Subject: [PATCH 1067/1635] use Uri as client connect message --- Cargo.toml | 2 +- src/client/connect.rs | 78 ---------------------- src/client/connector.rs | 136 +++++++++++++++++++-------------------- src/client/mod.rs | 2 - src/client/pool.rs | 132 ++++--------------------------------- src/client/request.rs | 37 +++++++---- src/ws/client/service.rs | 4 +- test-server/src/lib.rs | 14 ++-- 8 files changed, 112 insertions(+), 293 deletions(-) delete mode 100644 src/client/connect.rs diff --git a/Cargo.toml b/Cargo.toml index 11f532c8..3b9a8498 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" h2 = "0.1.16" -http = "0.1.8" +http = "0.1.16" httparse = "1.3" indexmap = "1.0" lazy_static = "1.0" diff --git a/src/client/connect.rs b/src/client/connect.rs deleted file mode 100644 index 82e5e45c..00000000 --- a/src/client/connect.rs +++ /dev/null @@ -1,78 +0,0 @@ -use actix_connect::Address; -use http::uri::Uri; -use http::HttpTryFrom; - -use super::error::InvalidUrl; -use super::pool::Key; - -#[derive(Debug)] -/// `Connect` type represents a message that can be sent to -/// `Connector` with a connection request. -pub struct Connect { - pub(crate) uri: Uri, -} - -impl Connect { - /// Create `Connect` message for specified `Uri` - pub fn new(uri: Uri) -> Connect { - Connect { uri } - } - - /// Construct `Uri` instance and create `Connect` message. - pub fn try_from(uri: U) -> Result - where - Uri: HttpTryFrom, - { - Ok(Connect { - uri: Uri::try_from(uri).map_err(|e| e.into())?, - }) - } - - pub(crate) fn is_secure(&self) -> bool { - if let Some(scheme) = self.uri.scheme_part() { - scheme.as_str() == "https" - } else { - false - } - } - - pub(crate) fn key(&self) -> Key { - self.uri.authority_part().unwrap().clone().into() - } - - pub(crate) fn validate(&self) -> Result<(), InvalidUrl> { - if self.uri.host().is_none() { - Err(InvalidUrl::MissingHost) - } else if self.uri.scheme_part().is_none() { - Err(InvalidUrl::MissingScheme) - } else if let Some(scheme) = self.uri.scheme_part() { - match scheme.as_str() { - "http" | "ws" | "https" | "wss" => Ok(()), - _ => Err(InvalidUrl::UnknownScheme), - } - } else { - Ok(()) - } - } -} - -impl Address for Connect { - fn host(&self) -> &str { - &self.uri.host().unwrap() - } - - fn port(&self) -> Option { - let port = if let Some(port) = self.uri.port() { - port - } else if let Some(scheme) = self.uri.scheme_part() { - match scheme.as_str() { - "http" | "ws" => 80, - "https" | "wss" => 443, - _ => 80, - } - } else { - 80 - }; - Some(port) - } -} diff --git a/src/client/connector.rs b/src/client/connector.rs index c764b93c..b8054151 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -8,9 +8,9 @@ use actix_connect::{ }; use actix_service::{apply_fn, Service, ServiceExt}; use actix_utils::timeout::{TimeoutError, TimeoutService}; +use http::Uri; use tokio_tcp::TcpStream; -use super::connect::Connect; use super::connection::Connection; use super::error::ConnectError; use super::pool::{ConnectionPool, Protocol}; @@ -38,8 +38,8 @@ pub struct Connector { impl Connector<(), ()> { pub fn new() -> Connector< impl Service< - Request = TcpConnect, - Response = TcpConnection, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, TcpStream, @@ -79,8 +79,8 @@ impl Connector { where U1: AsyncRead + AsyncWrite + fmt::Debug, T1: Service< - Request = TcpConnect, - Response = TcpConnection, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, { @@ -101,8 +101,8 @@ impl Connector where U: AsyncRead + AsyncWrite + fmt::Debug + 'static, T: Service< - Request = TcpConnect, - Response = TcpConnection, + Request = TcpConnect, + Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, { @@ -166,16 +166,14 @@ where /// Finish configuration process and create connector service. pub fn service( self, - ) -> impl Service - + Clone { + ) -> impl Service + Clone + { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( self.timeout, - apply_fn(self.connector, |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg)) - }) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector, |msg: Uri, srv| srv.call(msg.into())) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -199,28 +197,26 @@ where let ssl_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg)) - }) - .map_err(ConnectError::from) - .and_then( - OpensslConnector::service(self.ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (sock, Protocol::Http2) - } else { - (sock, Protocol::Http1) - } - }), - ), + apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) + .map_err(ConnectError::from) + .and_then( + OpensslConnector::service(self.ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (sock, Protocol::Http2) + } else { + (sock, Protocol::Http1) + } + }), + ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -229,11 +225,9 @@ where let tcp_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(actix_connect::Connect::new(msg)) - }) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -271,7 +265,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -279,7 +273,7 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service + T: Service + Clone, { fn clone(&self) -> Self { @@ -292,9 +286,9 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Connect; + type Request = Uri; type Response = IoConnection; type Error = ConnectorError; type Future = Either< @@ -306,13 +300,12 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Connect) -> Self::Future { - if req.is_secure() { - Either::B(err(ConnectError::SslIsNotSupported)) - } else if let Err(e) = req.validate() { - Either::B(err(e)) - } else { - Either::A(self.tcp_pool.call(req)) + fn call(&mut self, req: Uri) -> Self::Future { + match req.scheme_str() { + Some("https") | Some("wss") => { + Either::B(err(ConnectError::SslIsNotSupported)) + } + _ => Either::A(self.tcp_pool.call(req)), } } } @@ -332,8 +325,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -343,9 +336,9 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service + T1: Service + Clone, - T2: Service + T2: Service + Clone, { fn clone(&self) -> Self { @@ -360,10 +353,10 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { - type Request = Connect; + type Request = Uri; type Response = EitherConnection; type Error = ConnectError; type Future = Either< @@ -378,17 +371,18 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Connect) -> Self::Future { - if req.is_secure() { - Either::B(Either::B(InnerConnectorResponseB { - fut: self.ssl_pool.call(req), - _t: PhantomData, - })) - } else { - Either::B(Either::A(InnerConnectorResponseA { + fn call(&mut self, req: Uri) -> Self::Future { + match req.scheme_str() { + Some("https") | Some("wss") => { + Either::B(Either::B(InnerConnectorResponseB { + fut: self.ssl_pool.call(req), + _t: PhantomData, + })) + } + _ => Either::B(Either::A(InnerConnectorResponseA { fut: self.tcp_pool.call(req), _t: PhantomData, - })) + })), } } } @@ -396,7 +390,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -404,7 +398,7 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -422,7 +416,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -430,7 +424,7 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/src/client/mod.rs b/src/client/mod.rs index 0bff97e4..86b1a0cc 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,5 +1,4 @@ //! Http client api -mod connect; mod connection; mod connector; mod error; @@ -9,7 +8,6 @@ mod pool; mod request; mod response; -pub use self::connect::Connect; pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; diff --git a/src/client/pool.rs b/src/client/pool.rs index 214b7a38..a94b1e52 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -7,18 +7,17 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; use bytes::Bytes; -use futures::future::{ok, Either, FutureResult}; +use futures::future::{err, ok, Either, FutureResult}; use futures::task::AtomicTask; use futures::unsync::oneshot; use futures::{Async, Future, Poll}; use h2::client::{handshake, Handshake}; use hashbrown::HashMap; -use http::uri::Authority; +use http::uri::{Authority, Uri}; use indexmap::IndexSet; use slab::Slab; use tokio_timer::{sleep, Delay}; -use super::connect::Connect; use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; @@ -48,7 +47,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) fn new( connector: T, @@ -87,9 +86,9 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Connect; + type Request = Uri; type Response = IoConnection; type Error = ConnectError; type Future = Either< @@ -101,8 +100,12 @@ where self.0.poll_ready() } - fn call(&mut self, req: Connect) -> Self::Future { - let key = req.key(); + fn call(&mut self, req: Uri) -> Self::Future { + let key = if let Some(authority) = req.authority_part() { + authority.clone().into() + } else { + return Either::A(err(ConnectError::Unresolverd)); + }; // acquire connection match self.1.as_ref().borrow_mut().acquire(&key) { @@ -268,110 +271,6 @@ where } } -// struct OpenWaitingConnection -// where -// Io: AsyncRead + AsyncWrite + 'static, -// { -// fut: F, -// key: Key, -// h2: Option>, -// rx: Option, ConnectorError>>>, -// inner: Option>>>, -// } - -// impl OpenWaitingConnection -// where -// F: Future + 'static, -// Io: AsyncRead + AsyncWrite + 'static, -// { -// fn spawn( -// key: Key, -// rx: oneshot::Sender, ConnectorError>>, -// inner: Rc>>, -// fut: F, -// ) { -// tokio_current_thread::spawn(OpenWaitingConnection { -// key, -// fut, -// h2: None, -// rx: Some(rx), -// inner: Some(inner), -// }) -// } -// } - -// impl Drop for OpenWaitingConnection -// where -// Io: AsyncRead + AsyncWrite + 'static, -// { -// fn drop(&mut self) { -// if let Some(inner) = self.inner.take() { -// let mut inner = inner.as_ref().borrow_mut(); -// inner.release(); -// inner.check_availibility(); -// } -// } -// } - -// impl Future for OpenWaitingConnection -// where -// F: Future, -// Io: AsyncRead + AsyncWrite, -// { -// type Item = (); -// type Error = (); - -// fn poll(&mut self) -> Poll { -// if let Some(ref mut h2) = self.h2 { -// return match h2.poll() { -// Ok(Async::Ready((snd, connection))) => { -// tokio_current_thread::spawn(connection.map_err(|_| ())); -// let _ = self.rx.take().unwrap().send(Ok(IoConnection::new( -// ConnectionType::H2(snd), -// Instant::now(), -// Some(Acquired(self.key.clone(), self.inner.clone())), -// ))); -// Ok(Async::Ready(())) -// } -// Ok(Async::NotReady) => Ok(Async::NotReady), -// Err(e) => { -// let _ = self.inner.take(); -// if let Some(rx) = self.rx.take() { -// let _ = rx.send(Err(e.into())); -// } - -// Err(()) -// } -// }; -// } - -// match self.fut.poll() { -// Err(err) => { -// let _ = self.inner.take(); -// if let Some(rx) = self.rx.take() { -// let _ = rx.send(Err(err)); -// } -// Err(()) -// } -// Ok(Async::Ready((_, io, proto))) => { -// let _ = self.inner.take(); -// if proto == Protocol::Http1 { -// let _ = self.rx.take().unwrap().send(Ok(IoConnection::new( -// ConnectionType::H1(io), -// Instant::now(), -// Some(Acquired(self.key.clone(), self.inner.clone())), -// ))); -// } else { -// self.h2 = Some(handshake(io)); -// return self.poll(); -// } -// Ok(Async::Ready(())) -// } -// Ok(Async::NotReady) => Ok(Async::NotReady), -// } -// } -// } - enum Acquire { Acquired(ConnectionType, Instant), Available, @@ -392,10 +291,7 @@ pub(crate) struct Inner { limit: usize, acquired: usize, available: HashMap>>, - waiters: Slab<( - Connect, - oneshot::Sender, ConnectError>>, - )>, + waiters: Slab<(Uri, oneshot::Sender, ConnectError>>)>, waiters_queue: IndexSet<(Key, usize)>, task: AtomicTask, } @@ -434,14 +330,14 @@ where /// connection is not available, wait fn wait_for( &mut self, - connect: Connect, + connect: Uri, ) -> ( oneshot::Receiver, ConnectError>>, usize, ) { let (tx, rx) = oneshot::channel(); - let key = connect.key(); + let key: Key = connect.authority_part().unwrap().clone().into(); let entry = self.waiters.vacant_entry(); let token = entry.key(); entry.insert((connect, tx)); diff --git a/src/client/request.rs b/src/client/request.rs index 199e13b9..7c7079fb 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -21,8 +21,8 @@ use crate::http::{ use crate::message::{ConnectionType, Head, RequestHead}; use super::connection::Connection; +use super::error::{ConnectError, InvalidUrl, SendRequestError}; use super::response::ClientResponse; -use super::{Connect, ConnectError, SendRequestError}; /// An HTTP Client Request /// @@ -180,23 +180,32 @@ where ) -> impl Future where B: 'static, - T: Service, + T: Service, I: Connection, { let Self { head, body } = self; - let connect = Connect::new(head.uri.clone()); - if let Err(e) = connect.validate() { - Either::A(err(e.into())) + let uri = head.uri.clone(); + + // validate uri + if uri.host().is_none() { + Either::A(err(InvalidUrl::MissingHost.into())) + } else if uri.scheme_part().is_none() { + Either::A(err(InvalidUrl::MissingScheme.into())) + } else if let Some(scheme) = uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" | "https" | "wss" => Either::B( + connector + // connect to the host + .call(uri) + .from_err() + // send request + .and_then(move |connection| connection.send_request(head, body)), + ), + _ => Either::A(err(InvalidUrl::UnknownScheme.into())), + } } else { - Either::B( - connector - // connect to the host - .call(connect) - .from_err() - // send request - .and_then(move |connection| connection.send_request(head, body)), - ) + Either::A(err(InvalidUrl::UnknownScheme.into())) } } } @@ -529,7 +538,7 @@ impl ClientRequestBuilder { if !parts.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match parts.uri.port() { + let _ = match parts.uri.port_u16() { None | Some(80) | Some(443) => write!(wrt, "{}", host), Some(port) => write!(wrt, "{}:{}", host, port), }; diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index 1aa39124..a0a9b203 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_connect::{default_connector, Address, Connect as TcpConnect, ConnectError}; +use actix_connect::{default_connector, Connect as TcpConnect, ConnectError}; use actix_service::{apply_fn, Service}; use base64; use futures::future::{err, Either, FutureResult}; @@ -131,7 +131,7 @@ where // prep connection let connect = TcpConnect::new(request.uri().host().unwrap().to_string()) - .set_port(request.uri().port().unwrap_or_else(|| proto.port())); + .set_port(request.uri().port_u16().unwrap_or_else(|| proto.port())); let fut = Box::new( self.connector diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 3bb5feff..26bca787 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -5,10 +5,10 @@ use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::MessageBody; use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, Connect, ConnectError, - Connection, Connector, SendRequestError, + ClientRequest, ClientRequestBuilder, ClientResponse, ConnectError, Connection, + Connector, SendRequestError, }; -use actix_http::ws; +use actix_http::{http::Uri, ws}; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; use actix_service::Service; @@ -158,8 +158,8 @@ impl TestServerRuntime { } fn new_connector( - ) -> impl Service - + Clone { + ) -> impl Service + Clone + { #[cfg(feature = "ssl")] { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; @@ -185,8 +185,8 @@ impl TestServerRuntime { /// Http connector pub fn connector( &mut self, - ) -> impl Service - + Clone { + ) -> impl Service + Clone + { self.execute(|| TestServerRuntime::new_connector()) } From 1f9467e880aaa53b3c2186070a0f1dca447499c5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 12:01:35 -0700 Subject: [PATCH 1068/1635] update tests --- tests/test_httpserver.rs | 2 +- tests/test_server.rs | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index d2bc07ac..764d50ca 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -50,7 +50,7 @@ fn test_start() { let mut connector = test::run_on(|| { Ok::<_, ()>( - client::Connector::default() + client::Connector::new() .timeout(Duration::from_millis(100)) .service(), ) diff --git a/tests/test_server.rs b/tests/test_server.rs index ebe968fa..ffdc473a 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -69,7 +69,7 @@ fn test_body_gzip() { assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -100,7 +100,7 @@ fn test_body_gzip_large() { assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -134,7 +134,7 @@ fn test_body_gzip_large_random() { assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -167,7 +167,7 @@ fn test_body_chunked_implicit() { ); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -195,7 +195,7 @@ fn test_body_br_streaming() { assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode br let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); @@ -222,7 +222,7 @@ fn test_head_binary() { } // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert!(bytes.is_empty()); } @@ -245,7 +245,7 @@ fn test_no_chunking() { assert!(!response.headers().contains_key(TRANSFER_ENCODING)); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -267,7 +267,7 @@ fn test_body_deflate() { assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode deflate let mut e = ZlibDecoder::new(Vec::new()); @@ -294,7 +294,7 @@ fn test_body_brotli() { assert!(response.status().is_success()); // read response - let bytes = srv.execute(response.body()).unwrap(); + let bytes = srv.block_on(response.body()).unwrap(); // decode brotli let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); @@ -327,11 +327,11 @@ fn test_body_brotli() { // .header(http::header::CONTENT_ENCODING, "gzip") // .body(enc.clone()) // .unwrap(); -// let response = srv.execute(request.send()).unwrap(); +// let response = srv.block_on(request.send()).unwrap(); // assert!(response.status().is_success()); // // read response -// let bytes = srv.execute(response.body()).unwrap(); +// let bytes = srv.block_on(response.body()).unwrap(); // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // } @@ -360,11 +360,11 @@ fn test_body_brotli() { // .header(http::header::CONTENT_ENCODING, "gzip") // .body(enc.clone()) // .unwrap(); -// let response = srv.execute(request.send()).unwrap(); +// let response = srv.block_on(request.send()).unwrap(); // assert!(response.status().is_success()); // // read response -// let bytes = srv.execute(response.body()).unwrap(); +// let bytes = srv.block_on(response.body()).unwrap(); // assert_eq!(bytes, Bytes::from(data)); // } @@ -397,11 +397,11 @@ fn test_body_brotli() { // .header(http::header::CONTENT_ENCODING, "gzip") // .body(enc.clone()) // .unwrap(); -// let response = srv.execute(request.send()).unwrap(); +// let response = srv.block_on(request.send()).unwrap(); // assert!(response.status().is_success()); // // read response -// let bytes = srv.execute(response.body()).unwrap(); +// let bytes = srv.block_on(response.body()).unwrap(); // assert_eq!(bytes.len(), data.len()); // assert_eq!(bytes, Bytes::from(data)); // } @@ -430,11 +430,11 @@ fn test_body_brotli() { // .header(http::header::CONTENT_ENCODING, "deflate") // .body(enc) // .unwrap(); -// let response = srv.execute(request.send()).unwrap(); +// let response = srv.block_on(request.send()).unwrap(); // assert!(response.status().is_success()); // // read response -// let bytes = srv.execute(response.body()).unwrap(); +// let bytes = srv.block_on(response.body()).unwrap(); // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // } @@ -463,11 +463,11 @@ fn test_body_brotli() { // .header(http::header::CONTENT_ENCODING, "deflate") // .body(enc) // .unwrap(); -// let response = srv.execute(request.send()).unwrap(); +// let response = srv.block_on(request.send()).unwrap(); // assert!(response.status().is_success()); // // read response -// let bytes = srv.execute(response.body()).unwrap(); +// let bytes = srv.block_on(response.body()).unwrap(); // assert_eq!(bytes, Bytes::from(data)); // } @@ -500,7 +500,7 @@ fn test_body_brotli() { // .header(http::header::CONTENT_ENCODING, "deflate") // .body(enc) // .unwrap(); -// let response = srv.execute(request.send()).unwrap(); +// let response = srv.block_on(request.send()).unwrap(); // assert!(response.status().is_success()); // // read response From 76bb30dc3ae263791b10f055826696f8d605e987 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 13:06:29 -0700 Subject: [PATCH 1069/1635] fix names --- src/client/connector.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/connector.rs b/src/client/connector.rs index b8054151..de161506 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -265,7 +265,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -286,14 +286,14 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { type Request = Uri; type Response = IoConnection; - type Error = ConnectorError; + type Error = ConnectError; type Future = Either< as Service>::Future, - FutureResult, ConnectorError>, + FutureResult, ConnectError>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { From 15ba40d3ab1c4335d0b887ab8d55939ad20761a3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 13:08:05 -0700 Subject: [PATCH 1070/1635] fix non ssl connector --- src/client/connector.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/connector.rs b/src/client/connector.rs index de161506..804756ce 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -173,6 +173,7 @@ where let connector = TimeoutService::new( self.timeout, apply_fn(self.connector, |msg: Uri, srv| srv.call(msg.into())) + .map_err(ConnectError::from) .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { From ce4a2629f35c28ce80a09423e9386764f35df14e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Mar 2019 22:56:06 -0700 Subject: [PATCH 1071/1635] update actix-connect --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b9a8498..a68489f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ fail = ["failure"] [dependencies] actix-service = "0.3.4" actix-codec = "0.1.1" -actix-connect = { git = "https://github.com/actix/actix-net.git" } +actix-connect = "0.1.0" actix-utils = "0.3.4" actix-server-config = "0.1.0" @@ -85,7 +85,7 @@ failure = { version = "0.1.5", optional = true } [dev-dependencies] actix-rt = "0.2.0" actix-server = { version = "0.4.0", features=["ssl"] } -actix-connector = { version = "0.3.0", features=["ssl"] } +actix-connect = { version = "0.1.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } env_logger = "0.6" From 414614e1b50d3cc14c44341bcc0ea7cce21c7b43 Mon Sep 17 00:00:00 2001 From: lagudomeze Date: Sat, 16 Mar 2019 12:08:39 +0800 Subject: [PATCH 1072/1635] change marco import (#727) --- examples/basic.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index f8b81648..e8591f77 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,9 +1,6 @@ use futures::IntoFuture; -#[macro_use] -extern crate actix_web; - -use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web::{get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; #[get("/resource1/{name}/index.html")] fn index(req: HttpRequest, name: web::Path) -> String { From d93fe157b9cbb568860c7830209248eda3eeeb11 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 11:58:01 -0700 Subject: [PATCH 1073/1635] use better name Route::data instead of Route::config --- src/extract/form.rs | 2 +- src/extract/json.rs | 2 +- src/extract/mod.rs | 2 +- src/extract/payload.rs | 2 +- src/route.rs | 50 ++++++++++++++---------------------------- src/test.rs | 4 ++-- 6 files changed, 22 insertions(+), 40 deletions(-) diff --git a/src/extract/form.rs b/src/extract/form.rs index 19849ac8..6b13c5f8 100644 --- a/src/extract/form.rs +++ b/src/extract/form.rs @@ -130,7 +130,7 @@ impl fmt::Display for Form { /// web::resource("/index.html") /// .route(web::get() /// // change `Form` extractor configuration -/// .config(web::FormConfig::default().limit(4097)) +/// .data(web::FormConfig::default().limit(4097)) /// .to(index)) /// ); /// } diff --git a/src/extract/json.rs b/src/extract/json.rs index f74b082d..3847e71a 100644 --- a/src/extract/json.rs +++ b/src/extract/json.rs @@ -215,7 +215,7 @@ where /// fn main() { /// let app = App::new().service( /// web::resource("/index.html").route( -/// web::post().config( +/// web::post().data( /// // change json extractor configuration /// web::JsonConfig::default().limit(4096) /// .error_handler(|err, req| { // <- create custom error response diff --git a/src/extract/mod.rs b/src/extract/mod.rs index 25a046d4..738f8918 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -262,7 +262,7 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) - .config(FormConfig::default().limit(4096)) + .route_data(FormConfig::default().limit(4096)) .to_from(); let r = block_on(Option::>::from_request(&mut req)).unwrap(); diff --git a/src/extract/payload.rs b/src/extract/payload.rs index 13532eee..3fc0c964 100644 --- a/src/extract/payload.rs +++ b/src/extract/payload.rs @@ -177,7 +177,7 @@ where /// let app = App::new().service( /// web::resource("/index.html").route( /// web::get() -/// .config(web::PayloadConfig::new(4096)) // <- limit size of the payload +/// .data(web::PayloadConfig::new(4096)) // <- limit size of the payload /// .to(index)) // <- register handler with extractor params /// ); /// } diff --git a/src/route.rs b/src/route.rs index 5d339a3b..30905d40 100644 --- a/src/route.rs +++ b/src/route.rs @@ -40,28 +40,28 @@ type BoxedRouteNewService = Box< pub struct Route

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

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

    { - let config_ref = Rc::new(RefCell::new(None)); + let data_ref = Rc::new(RefCell::new(None)); Route { service: Box::new(RouteNewService::new( - Extract::new(config_ref.clone()).and_then( + Extract::new(data_ref.clone()).and_then( Handler::new(HttpResponse::NotFound).map_err(|_| panic!()), ), )), guards: Rc::new(Vec::new()), - config: None, - config_ref, + data: None, + data_ref, } } pub(crate) fn finish(mut self) -> Self { - *self.config_ref.borrow_mut() = self.config.take().map(|e| Rc::new(e)); + *self.data_ref.borrow_mut() = self.data.take().map(|e| Rc::new(e)); self } @@ -180,24 +180,6 @@ impl Route

    { self } - // pub fn map>( - // self, - // md: F, - // ) -> RouteServiceBuilder - // where - // T: NewService< - // Request = HandlerRequest, - // Response = HandlerRequest, - // InitError = Error, - // >, - // { - // RouteServiceBuilder { - // service: md.into_new_service(), - // guards: self.guards, - // _t: PhantomData, - // } - // } - /// Set handler function, use request extractors for parameters. /// /// ```rust @@ -253,7 +235,7 @@ impl Route

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

    { R::Error: Into, { self.service = Box::new(RouteNewService::new( - Extract::new(self.config_ref.clone()) + Extract::new(self.data_ref.clone()) .and_then(AsyncHandler::new(handler).map_err(|_| panic!())), )); self } - /// This method allows to add extractor configuration - /// for specific route. + /// Provide route specific data. This method allows to add extractor + /// configuration or specific state available via `RouteData` extractor. /// /// ```rust /// use actix_web::{web, App}; @@ -317,17 +299,17 @@ impl Route

    { /// web::resource("/index.html").route( /// web::get() /// // limit size of the payload - /// .config(web::PayloadConfig::new(4096)) + /// .data(web::PayloadConfig::new(4096)) /// // register handler /// .to(index) /// )); /// } /// ``` - pub fn config(mut self, config: C) -> Self { - if self.config.is_none() { - self.config = Some(Extensions::new()); + pub fn data(mut self, data: C) -> Self { + if self.data.is_none() { + self.data = Some(Extensions::new()); } - self.config.as_mut().unwrap().insert(config); + self.data.as_mut().unwrap().insert(data); self } } diff --git a/src/test.rs b/src/test.rs index 57a6d396..4db268f1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -265,8 +265,8 @@ impl TestRequest { self } - /// Set request config - pub fn config(self, data: T) -> Self { + /// Set route data + pub fn route_data(self, data: T) -> Self { self.config.extensions.borrow_mut().insert(data); self } From b1e267bce41be057d4c620c31c098fe099698f8f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 20:17:27 -0700 Subject: [PATCH 1074/1635] rename State to a Data --- src/app.rs | 75 +++++++++++++++++++-------------------- src/app_service.rs | 16 ++++----- src/{state.rs => data.rs} | 52 +++++++++++++-------------- src/lib.rs | 6 ++-- 4 files changed, 74 insertions(+), 75 deletions(-) rename src/{state.rs => data.rs} (63%) diff --git a/src/app.rs b/src/app.rs index 2e2a8c2d..b146fb4c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,6 +12,7 @@ use futures::IntoFuture; use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; use crate::config::{AppConfig, AppConfigInner}; +use crate::data::{Data, DataFactory}; use crate::dev::{PayloadStream, ResourceDef}; use crate::error::Error; use crate::resource::Resource; @@ -20,7 +21,6 @@ use crate::service::{ HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; -use crate::state::{State, StateFactory}; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; @@ -32,18 +32,17 @@ where T: NewService>, { chain: T, - state: Vec>, + data: Vec>, config: AppConfigInner, _t: PhantomData<(P,)>, } impl App { - /// Create application builder with empty state. Application can - /// be configured with a builder-like pattern. + /// Create application builder. Application can be configured with a builder-like pattern. pub fn new() -> Self { App { chain: AppChain, - state: Vec::new(), + data: Vec::new(), config: AppConfigInner::default(), _t: PhantomData, } @@ -60,51 +59,51 @@ where InitError = (), >, { - /// Set application state. Applicatin state could be accessed - /// by using `State` extractor where `T` is state type. + /// Set application data. Applicatin data could be accessed + /// by using `Data` extractor where `T` is data type. /// /// **Note**: http server accepts an application factory rather than /// an application instance. Http server constructs an application - /// instance for each thread, thus application state must be constructed - /// multiple times. If you want to share state between different + /// instance for each thread, thus application data must be constructed + /// multiple times. If you want to share data between different /// threads, a shared object should be used, e.g. `Arc`. Application - /// state does not need to be `Send` or `Sync`. + /// data does not need to be `Send` or `Sync`. /// /// ```rust /// use std::cell::Cell; /// use actix_web::{web, App}; /// - /// struct MyState { + /// struct MyData { /// counter: Cell, /// } /// - /// fn index(state: web::State) { - /// state.counter.set(state.counter.get() + 1); + /// fn index(data: web::Data) { + /// data.counter.set(data.counter.get() + 1); /// } /// /// fn main() { /// let app = App::new() - /// .state(MyState{ counter: Cell::new(0) }) + /// .data(MyData{ counter: Cell::new(0) }) /// .service( /// web::resource("/index.html").route( /// web::get().to(index))); /// } /// ``` - pub fn state(mut self, state: S) -> Self { - self.state.push(Box::new(State::new(state))); + pub fn data(mut self, data: S) -> Self { + self.data.push(Box::new(Data::new(data))); self } - /// Set application state factory. This function is - /// similar to `.state()` but it accepts state factory. State get + /// Set application data factory. This function is + /// similar to `.data()` but it accepts data factory. Data object get /// constructed asynchronously during application initialization. - pub fn state_factory(mut self, state: F) -> Self + pub fn data_factory(mut self, data: F) -> Self where F: Fn() -> Out + 'static, Out: IntoFuture + 'static, Out::Error: std::fmt::Debug, { - self.state.push(Box::new(state)); + self.data.push(Box::new(data)); self } @@ -138,7 +137,7 @@ where AppRouter { endpoint, chain: self.chain, - state: self.state, + data: self.data, services: Vec::new(), default: None, factory_ref: fref, @@ -174,7 +173,7 @@ where let chain = self.chain.and_then(chain.into_new_service()); App { chain, - state: self.state, + data: self.data, config: self.config, _t: PhantomData, } @@ -183,7 +182,7 @@ where /// Configure route for a specific path. /// /// This is a simplified version of the `App::service()` method. - /// This method can not be could multiple times, in that case + /// This method can be used multiple times with same path, in that case /// multiple resources with one route would be registered for same resource path. /// /// ```rust @@ -223,7 +222,7 @@ where default: None, endpoint: AppEntry::new(fref.clone()), factory_ref: fref, - state: self.state, + data: self.data, config: self.config, services: vec![Box::new(ServiceFactoryWrapper::new(service))], external: Vec::new(), @@ -233,7 +232,7 @@ where /// Set server host name. /// - /// Host name is used by application router aa a hostname for url + /// Host name is used by application router as a hostname for url /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. /// html#method.host) documentation for more information. /// @@ -252,7 +251,7 @@ pub struct AppRouter { services: Vec>>, default: Option>>, factory_ref: Rc>>>, - state: Vec>, + data: Vec>, config: AppConfigInner, external: Vec, _t: PhantomData<(P, B)>, @@ -344,7 +343,7 @@ where AppRouter { endpoint, chain: self.chain, - state: self.state, + data: self.data, services: self.services, default: self.default, factory_ref: self.factory_ref, @@ -429,7 +428,7 @@ where fn into_new_service(self) -> AppInit { AppInit { chain: self.chain, - state: self.state, + data: self.data, endpoint: self.endpoint, services: RefCell::new(self.services), external: RefCell::new(self.external), @@ -489,10 +488,10 @@ mod tests { } #[test] - fn test_state() { + fn test_data() { let mut srv = - init_service(App::new().state(10usize).service( - web::resource("/").to(|_: web::State| HttpResponse::Ok()), + init_service(App::new().data(10usize).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )); let req = TestRequest::default().to_request(); @@ -500,8 +499,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); let mut srv = - init_service(App::new().state(10u32).service( - web::resource("/").to(|_: web::State| HttpResponse::Ok()), + init_service(App::new().data(10u32).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -509,18 +508,18 @@ mod tests { } #[test] - fn test_state_factory() { + fn test_data_factory() { let mut srv = - init_service(App::new().state_factory(|| Ok::<_, ()>(10usize)).service( - web::resource("/").to(|_: web::State| HttpResponse::Ok()), + init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let mut srv = - init_service(App::new().state_factory(|| Ok::<_, ()>(10u32)).service( - web::resource("/").to(|_: web::State| HttpResponse::Ok()), + init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); diff --git a/src/app_service.rs b/src/app_service.rs index c59b80bc..0bf3d309 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -11,11 +11,11 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll}; use crate::config::{AppConfig, ServiceConfig}; +use crate::data::{DataFactory, DataFactoryResult}; use crate::error::Error; use crate::guard::Guard; use crate::rmap::ResourceMap; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; -use crate::state::{StateFactory, StateFactoryResult}; type Guards = Vec>; type HttpService

    = BoxedService, ServiceResponse, Error>; @@ -24,7 +24,7 @@ type HttpNewService

    = type BoxedResponse = Box>; /// Service factory to convert `Request` to a `ServiceRequest`. -/// It also executes state factories. +/// It also executes data factories. pub struct AppInit where C: NewService>, @@ -37,7 +37,7 @@ where { pub(crate) chain: C, pub(crate) endpoint: T, - pub(crate) state: Vec>, + pub(crate) data: Vec>, pub(crate) config: RefCell, pub(crate) services: RefCell>>>, pub(crate) default: Option>>, @@ -121,7 +121,7 @@ where chain_fut: self.chain.new_service(&()), endpoint: None, endpoint_fut: self.endpoint.new_service(&()), - state: self.state.iter().map(|s| s.construct()).collect(), + data: self.data.iter().map(|s| s.construct()).collect(), config: self.config.borrow().clone(), rmap, _t: PhantomData, @@ -139,7 +139,7 @@ where chain_fut: C::Future, endpoint_fut: T::Future, rmap: Rc, - state: Vec>, + data: Vec>, config: AppConfig, _t: PhantomData<(P, B)>, } @@ -165,9 +165,9 @@ where fn poll(&mut self) -> Poll { let mut idx = 0; let mut extensions = self.config.0.extensions.borrow_mut(); - while idx < self.state.len() { - if let Async::Ready(_) = self.state[idx].poll_result(&mut extensions)? { - self.state.remove(idx); + while idx < self.data.len() { + if let Async::Ready(_) = self.data[idx].poll_result(&mut extensions)? { + self.data.remove(idx); } else { idx += 1; } diff --git a/src/state.rs b/src/data.rs similarity index 63% rename from src/state.rs rename to src/data.rs index b70540c0..a172cb35 100644 --- a/src/state.rs +++ b/src/data.rs @@ -8,21 +8,21 @@ use futures::{Async, Future, IntoFuture, Poll}; use crate::extract::FromRequest; use crate::service::ServiceFromRequest; -/// Application state factory -pub(crate) trait StateFactory { - fn construct(&self) -> Box; +/// Application data factory +pub(crate) trait DataFactory { + fn construct(&self) -> Box; } -pub(crate) trait StateFactoryResult { +pub(crate) trait DataFactoryResult { fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()>; } /// Application state -pub struct State(Arc); +pub struct Data(Arc); -impl State { - pub(crate) fn new(state: T) -> State { - State(Arc::new(state)) +impl Data { + pub(crate) fn new(state: T) -> Data { + Data(Arc::new(state)) } /// Get referecnce to inner state type. @@ -31,7 +31,7 @@ impl State { } } -impl Deref for State { +impl Deref for Data { type Target = T; fn deref(&self) -> &T { @@ -39,19 +39,19 @@ impl Deref for State { } } -impl Clone for State { - fn clone(&self) -> State { - State(self.0.clone()) +impl Clone for Data { + fn clone(&self) -> Data { + Data(self.0.clone()) } } -impl FromRequest

    for State { +impl FromRequest

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

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

    for State { } } -impl StateFactory for State { - fn construct(&self) -> Box { - Box::new(StateFut { st: self.clone() }) +impl DataFactory for Data { + fn construct(&self) -> Box { + Box::new(DataFut { st: self.clone() }) } } -struct StateFut { - st: State, +struct DataFut { + st: Data, } -impl StateFactoryResult for StateFut { +impl DataFactoryResult for DataFut { fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> { extensions.insert(self.st.clone()); Ok(Async::Ready(())) } } -impl StateFactory for F +impl DataFactory for F where F: Fn() -> Out + 'static, Out: IntoFuture + 'static, Out::Error: std::fmt::Debug, { - fn construct(&self) -> Box { - Box::new(StateFactoryFut { + fn construct(&self) -> Box { + Box::new(DataFactoryFut { fut: (*self)().into_future(), }) } } -struct StateFactoryFut +struct DataFactoryFut where F: Future, F::Error: std::fmt::Debug, @@ -99,7 +99,7 @@ where fut: F, } -impl StateFactoryResult for StateFactoryFut +impl DataFactoryResult for DataFactoryFut where F: Future, F::Error: std::fmt::Debug, @@ -107,7 +107,7 @@ where fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> { match self.fut.poll() { Ok(Async::Ready(s)) => { - extensions.insert(State::new(s)); + extensions.insert(Data::new(s)); Ok(Async::Ready(())) } Ok(Async::NotReady) => Ok(Async::NotReady), diff --git a/src/lib.rs b/src/lib.rs index d653fd1c..843ad103 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod app; mod app_service; mod config; +mod data; pub mod error; mod extract; pub mod guard; @@ -17,7 +18,6 @@ mod route; mod scope; mod server; mod service; -mod state; pub mod test; #[allow(unused_imports)] @@ -37,7 +37,6 @@ pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::responder::{Either, Responder}; pub use crate::route::Route; -pub use crate::scope::Scope; pub use crate::server::HttpServer; pub mod dev { @@ -77,6 +76,7 @@ pub mod dev { } pub mod web { + //! Various types use actix_http::{http::Method, Response}; use actix_rt::blocking; use futures::{Future, IntoFuture}; @@ -91,11 +91,11 @@ pub mod web { use crate::route::Route; use crate::scope::Scope; + pub use crate::data::Data; pub use crate::error::{BlockingError, Error}; pub use crate::extract::{Form, Json, Path, Payload, Query}; pub use crate::extract::{FormConfig, JsonConfig, PayloadConfig}; pub use crate::request::HttpRequest; - pub use crate::state::State; /// Create resource for a specific path. /// From 60386f1791e6bd005889d566eba1ba0b76699401 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 21:09:11 -0700 Subject: [PATCH 1075/1635] introduce RouteData extractor --- examples/basic.rs | 4 +- src/app.rs | 20 ----- src/data.rs | 182 ++++++++++++++++++++++++++++++++++++++++- src/extract/form.rs | 2 +- src/extract/json.rs | 2 +- src/extract/payload.rs | 4 +- src/lib.rs | 2 +- src/route.rs | 3 +- src/service.rs | 16 ++-- 9 files changed, 198 insertions(+), 37 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index e8591f77..756f1b79 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,6 +1,8 @@ use futures::IntoFuture; -use actix_web::{get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web::{ + get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, +}; #[get("/resource1/{name}/index.html")] fn index(req: HttpRequest, name: web::Path) -> String { diff --git a/src/app.rs b/src/app.rs index b146fb4c..8c416808 100644 --- a/src/app.rs +++ b/src/app.rs @@ -487,26 +487,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); } - #[test] - fn test_data() { - let mut srv = - init_service(App::new().data(10usize).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let mut srv = - init_service(App::new().data(10u32).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - #[test] fn test_data_factory() { let mut srv = diff --git a/src/data.rs b/src/data.rs index a172cb35..6fb8e0b9 100644 --- a/src/data.rs +++ b/src/data.rs @@ -17,7 +17,45 @@ pub(crate) trait DataFactoryResult { fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()>; } -/// Application state +/// Application data. +/// +/// Application data is an arbitrary data attached to the app. +/// Application data is available to all routes and could be added +/// during application configuration process +/// with `App::data()` method. +/// +/// Applicatin data could be accessed by using `Data` +/// extractor where `T` is data type. +/// +/// **Note**: http server accepts an application factory rather than +/// an application instance. Http server constructs an application +/// instance for each thread, thus application data must be constructed +/// multiple times. If you want to share data between different +/// threads, a shared object should be used, e.g. `Arc`. Application +/// data does not need to be `Send` or `Sync`. +/// +/// ```rust +/// use std::cell::Cell; +/// use actix_web::{web, App}; +/// +/// struct MyData { +/// counter: Cell, +/// } +/// +/// /// Use `Data` extractor to access data in handler. +/// fn index(data: web::Data) { +/// data.counter.set(data.counter.get() + 1); +/// } +/// +/// fn main() { +/// let app = App::new() +/// // Store `MyData` in application storage. +/// .data(MyData{ counter: Cell::new(0) }) +/// .service( +/// web::resource("/index.html").route( +/// web::get().to(index))); +/// } +/// ``` pub struct Data(Arc); impl Data { @@ -25,7 +63,7 @@ impl Data { Data(Arc::new(state)) } - /// Get referecnce to inner state type. + /// Get referecnce to inner app data. pub fn get_ref(&self) -> &T { self.0.as_ref() } @@ -55,7 +93,7 @@ impl FromRequest

    for Data { Ok(st.clone()) } else { Err(ErrorInternalServerError( - "State is not configured, to configure use App::state()", + "App data is not configured, to configure use App::data()", )) } } @@ -118,3 +156,141 @@ where } } } + +/// Route data. +/// +/// Route data is an arbitrary data attached to specific route. +/// Route data could be added to route during route configuration process +/// with `Route::data()` method. Route data is also used as an extractor +/// configuration storage. Route data could be accessed in handler +/// via `RouteData` extractor. +/// +/// ```rust +/// # use std::cell::Cell; +/// use actix_web::{web, App}; +/// +/// struct MyData { +/// counter: Cell, +/// } +/// +/// /// Use `RouteData` extractor to access data in handler. +/// fn index(data: web::RouteData) { +/// data.counter.set(data.counter.get() + 1); +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get() +/// // Store `MyData` in route storage +/// .data(MyData{ counter: Cell::new(0) }) +/// // Route data could be used as extractor configuration storage, +/// // limit size of the payload +/// .data(web::PayloadConfig::new(4096)) +/// // register handler +/// .to(index) +/// )); +/// } +/// ``` +/// +/// If route data is not set for a handler, using `RouteData` extractor +/// would cause `Internal Server error` response. +pub struct RouteData(Arc); + +impl RouteData { + pub(crate) fn new(state: T) -> RouteData { + RouteData(Arc::new(state)) + } + + /// Get referecnce to inner data object. + pub fn get_ref(&self) -> &T { + self.0.as_ref() + } +} + +impl Deref for RouteData { + type Target = T; + + fn deref(&self) -> &T { + self.0.as_ref() + } +} + +impl Clone for RouteData { + fn clone(&self) -> RouteData { + RouteData(self.0.clone()) + } +} + +impl FromRequest

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

    ) -> Self::Future { + if let Some(st) = req.route_data::() { + Ok(st.clone()) + } else { + Err(ErrorInternalServerError( + "Route data is not configured, to configure use Route::data()", + )) + } + } +} + +#[cfg(test)] +mod tests { + use actix_service::Service; + + use crate::http::StatusCode; + use crate::test::{block_on, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[test] + fn test_data_extractor() { + let mut srv = + init_service(App::new().data(10usize).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let mut srv = + init_service(App::new().data(10u32).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } + + #[test] + fn test_route_data_extractor() { + let mut srv = init_service(App::new().service(web::resource("/").route( + web::get().data(10usize).to(|data: web::RouteData| { + let _ = data.clone(); + HttpResponse::Ok() + }), + ))); + + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + // different type + let mut srv = init_service( + App::new().service( + web::resource("/").route( + web::get() + .data(10u32) + .to(|_: web::RouteData| HttpResponse::Ok()), + ), + ), + ); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } +} diff --git a/src/extract/form.rs b/src/extract/form.rs index 6b13c5f8..4a5e9729 100644 --- a/src/extract/form.rs +++ b/src/extract/form.rs @@ -77,7 +77,7 @@ where fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .load_config::() + .route_data::() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((16384, None)); diff --git a/src/extract/json.rs b/src/extract/json.rs index 3847e71a..92b7f20f 100644 --- a/src/extract/json.rs +++ b/src/extract/json.rs @@ -177,7 +177,7 @@ where fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .load_config::() + .route_data::() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((32768, None)); diff --git a/src/extract/payload.rs b/src/extract/payload.rs index 3fc0c964..7164a544 100644 --- a/src/extract/payload.rs +++ b/src/extract/payload.rs @@ -140,7 +140,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { let mut tmp; - let cfg = if let Some(cfg) = req.load_config::() { + let cfg = if let Some(cfg) = req.route_data::() { cfg } else { tmp = PayloadConfig::default(); @@ -193,7 +193,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

    ) -> Self::Future { let mut tmp; - let cfg = if let Some(cfg) = req.load_config::() { + let cfg = if let Some(cfg) = req.route_data::() { cfg } else { tmp = PayloadConfig::default(); diff --git a/src/lib.rs b/src/lib.rs index 843ad103..b22e05da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,7 +91,7 @@ pub mod web { use crate::route::Route; use crate::scope::Scope; - pub use crate::data::Data; + pub use crate::data::{Data, RouteData}; pub use crate::error::{BlockingError, Error}; pub use crate::extract::{Form, Json, Path, Payload, Query}; pub use crate::extract::{FormConfig, JsonConfig, PayloadConfig}; diff --git a/src/route.rs b/src/route.rs index 30905d40..c44ed713 100644 --- a/src/route.rs +++ b/src/route.rs @@ -6,6 +6,7 @@ use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::data::RouteData; use crate::extract::FromRequest; use crate::guard::{self, Guard}; use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; @@ -309,7 +310,7 @@ impl Route

    { if self.data.is_none() { self.data = Some(Extensions::new()); } - self.data.as_mut().unwrap().insert(data); + self.data.as_mut().unwrap().insert(RouteData::new(data)); self } } diff --git a/src/service.rs b/src/service.rs index e907a1ab..b8c3a158 100644 --- a/src/service.rs +++ b/src/service.rs @@ -13,6 +13,7 @@ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::{AppConfig, ServiceConfig}; +use crate::data::RouteData; use crate::request::HttpRequest; use crate::rmap::ResourceMap; @@ -241,15 +242,15 @@ impl

    fmt::Debug for ServiceRequest

    { pub struct ServiceFromRequest

    { req: HttpRequest, payload: Payload

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

    ServiceFromRequest

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

    , config: Option>) -> Self { + pub(crate) fn new(req: ServiceRequest

    , data: Option>) -> Self { Self { req: req.req, payload: req.payload, - config, + data, } } @@ -269,10 +270,11 @@ impl

    ServiceFromRequest

    { ServiceResponse::new(self.req, err.into().into()) } - /// Load extractor configuration - pub fn load_config(&self) -> Option<&T> { - if let Some(ref ext) = self.config { - ext.get::() + /// Load route data. Route data could be set during + /// route configuration with `Route::data()` method. + pub fn route_data(&self) -> Option<&RouteData> { + if let Some(ref ext) = self.data { + ext.get::>() } else { None } From e396c90c9ec0cdfb3b384dd4c16ea8a5b6e96757 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 21:13:16 -0700 Subject: [PATCH 1076/1635] update api doc --- src/data.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/data.rs b/src/data.rs index 6fb8e0b9..a53015c2 100644 --- a/src/data.rs +++ b/src/data.rs @@ -34,6 +34,9 @@ pub(crate) trait DataFactoryResult { /// threads, a shared object should be used, e.g. `Arc`. Application /// data does not need to be `Send` or `Sync`. /// +/// If route data is not set for a handler, using `Data` extractor would +/// cause *Internal Server Error* response. +/// /// ```rust /// use std::cell::Cell; /// use actix_web::{web, App}; @@ -165,6 +168,9 @@ where /// configuration storage. Route data could be accessed in handler /// via `RouteData` extractor. /// +/// If route data is not set for a handler, using `RouteData` extractor +/// would cause *Internal Server Error* response. +/// /// ```rust /// # use std::cell::Cell; /// use actix_web::{web, App}; @@ -192,9 +198,6 @@ where /// )); /// } /// ``` -/// -/// If route data is not set for a handler, using `RouteData` extractor -/// would cause `Internal Server error` response. pub struct RouteData(Arc); impl RouteData { From 4a4826b23a72c539c26c9e69b9a69ec38a3fd828 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 21:35:02 -0700 Subject: [PATCH 1077/1635] cleanup doc strings and clippy warnings --- src/app.rs | 15 ++++++++++----- src/info.rs | 4 ++-- src/lib.rs | 4 ++-- src/resource.rs | 13 +++++++------ src/route.rs | 2 +- src/scope.rs | 26 ++++++++++++++++++-------- src/test.rs | 5 +++-- 7 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/app.rs b/src/app.rs index 8c416808..c4f2e33b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -148,7 +148,7 @@ where } /// Register a request modifier. It can modify any request parameters - /// including payload stream. + /// including payload stream type. pub fn chain( self, chain: C, @@ -211,6 +211,14 @@ where } /// Register http service. + /// + /// Http service is any type that implements `HttpServiceFactory` trait. + /// + /// Actix web provides several services implementations: + /// + /// * *Resource* is an entry in resource table which corresponds to requested URL. + /// * *Scope* is a set of resources with common root path. + /// * "StaticFiles" is a service for static files support pub fn service(self, service: F) -> AppRouter> where F: HttpServiceFactory

    + 'static, @@ -301,7 +309,7 @@ where /// /// Actix web provides several services implementations: /// - /// * *Resource* is an entry in route table which corresponds to requested URL. + /// * *Resource* is an entry in resource table which corresponds to requested URL. /// * *Scope* is a set of resources with common root path. /// * "StaticFiles" is a service for static files support pub fn service(mut self, factory: F) -> Self @@ -354,9 +362,6 @@ where } /// Default resource to be used if no matching route could be found. - /// - /// Default resource works with resources only and does not work with - /// custom services. pub fn default_resource(mut self, f: F) -> Self where F: FnOnce(Resource

    ) -> Resource, diff --git a/src/info.rs b/src/info.rs index c058bd51..9a97c335 100644 --- a/src/info.rs +++ b/src/info.rs @@ -25,7 +25,7 @@ impl ConnectionInfo { Ref::map(req.extensions(), |e| e.get().unwrap()) } - #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] + #[allow(clippy::cyclomatic_complexity)] fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { let mut host = None; let mut scheme = None; @@ -123,10 +123,10 @@ impl ConnectionInfo { } ConnectionInfo { + peer, scheme: scheme.unwrap_or("http").to_owned(), host: host.unwrap_or("localhost").to_owned(), remote: remote.map(|s| s.to_owned()), - peer: peer, } } diff --git a/src/lib.rs b/src/lib.rs index b22e05da..509fcc60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(clippy::type_complexity)] +#![allow(clippy::type_complexity, clippy::new_without_default)] mod app; mod app_service; @@ -84,6 +84,7 @@ pub mod web { pub use actix_http::Response as HttpResponse; pub use bytes::{Bytes, BytesMut}; + use crate::error::{BlockingError, Error}; use crate::extract::FromRequest; use crate::handler::{AsyncFactory, Factory}; use crate::resource::Resource; @@ -92,7 +93,6 @@ pub mod web { use crate::scope::Scope; pub use crate::data::{Data, RouteData}; - pub use crate::error::{BlockingError, Error}; pub use crate::extract::{Form, Json, Path, Payload, Query}; pub use crate::extract::{FormConfig, JsonConfig, PayloadConfig}; pub use crate::request::HttpRequest; diff --git a/src/resource.rs b/src/resource.rs index e4fe65c0..46b3e2a8 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -21,7 +21,7 @@ type HttpService

    = BoxedService, ServiceResponse, Error>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; -/// *Resource* is an entry in route table which corresponds to requested URL. +/// *Resource* is an entry in resources table which corresponds to requested URL. /// /// Resource in turn has at least one route. /// Route consists of an handlers objects and list of guards @@ -132,7 +132,8 @@ where /// } /// ``` /// - /// Multiple routes could be added to a resource. + /// Multiple routes could be added to a resource. Resource object uses + /// match guards for route selection. /// /// ```rust /// use actix_web::{web, guard, App, HttpResponse}; @@ -220,11 +221,11 @@ where self } - /// Register a resource middleware + /// Register a resource middleware. /// - /// This is similar to `App's` middlewares, but - /// middleware is not allowed to change response type (i.e modify response's body). - /// Middleware get invoked on resource level. + /// This is similar to `App's` middlewares, but middleware get invoked on resource level. + /// Resource level middlewares are not allowed to change response + /// type (i.e modify response's body). pub fn middleware( self, mw: F, diff --git a/src/route.rs b/src/route.rs index c44ed713..626b0951 100644 --- a/src/route.rs +++ b/src/route.rs @@ -62,7 +62,7 @@ impl Route

    { } pub(crate) fn finish(mut self) -> Self { - *self.data_ref.borrow_mut() = self.data.take().map(|e| Rc::new(e)); + *self.data_ref.borrow_mut() = self.data.take().map(Rc::new); self } diff --git a/src/scope.rs b/src/scope.rs index 9f5b650c..bf3261f2 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -26,7 +26,7 @@ type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; type BoxedResponse = Box>; -/// Resources scope +/// Resources scope. /// /// Scope is a set of resources with common root path. /// Scopes collect multiple paths under a common path prefix. @@ -114,7 +114,15 @@ where self } - /// Create nested service. + /// Register http service. + /// + /// This is similar to `App's` service registration. + /// + /// Actix web provides several services implementations: + /// + /// * *Resource* is an entry in resource table which corresponds to requested URL. + /// * *Scope* is a set of resources with common root path. + /// * "StaticFiles" is a service for static files support /// /// ```rust /// use actix_web::{web, App, HttpRequest}; @@ -145,7 +153,7 @@ where /// Configure route for a specific path. /// /// This is a simplified version of the `Scope::service()` method. - /// This method can not be could multiple times, in that case + /// This method can be called multiple times, in that case /// multiple resources with one route would be registered for same resource path. /// /// ```rust @@ -172,6 +180,8 @@ where } /// Default resource to be used if no matching route could be found. + /// + /// If default resource is not registered, app's default resource is being used. pub fn default_resource(mut self, f: F) -> Self where F: FnOnce(Resource

    ) -> Resource, @@ -190,11 +200,11 @@ where self } - /// Register a scope middleware + /// Register a scope level middleware. /// - /// This is similar to `App's` middlewares, but - /// middleware is not allowed to change response type (i.e modify response's body). - /// Middleware get invoked on scope level. + /// This is similar to `App's` middlewares, but middleware get invoked on scope level. + /// Scope level middlewares are not allowed to change response + /// type (i.e modify response's body). pub fn middleware( self, mw: F, @@ -322,7 +332,7 @@ impl NewService for ScopeFactory

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

    { fut: Vec>, diff --git a/src/test.rs b/src/test.rs index 4db268f1..13db5977 100644 --- a/src/test.rs +++ b/src/test.rs @@ -50,7 +50,7 @@ pub fn run_on(f: F) -> Result where F: Fn() -> Result, { - RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| f()))) + RT.with(move |rt| rt.borrow_mut().block_on(lazy(f))) } /// This method accepts application builder instance, and constructs @@ -169,6 +169,7 @@ impl Default for TestRequest { } } +#[allow(clippy::wrong_self_convention)] impl TestRequest { /// Create TestRequest and set request uri pub fn with_uri(path: &str) -> TestRequest { @@ -254,7 +255,7 @@ impl TestRequest { } /// Set cookie for this request - pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + pub fn cookie(mut self, cookie: Cookie) -> Self { self.req.cookie(cookie); self } From 725ee3d3961f9a76105c45579d1e7e8c999fee3c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 21:43:48 -0700 Subject: [PATCH 1078/1635] rename extract to types --- src/{extract/mod.rs => extract.rs} | 13 +------------ src/lib.rs | 5 +++-- src/{extract => types}/form.rs | 0 src/{extract => types}/json.rs | 0 src/types/mod.rs | 13 +++++++++++++ src/{extract => types}/path.rs | 3 +-- src/{extract => types}/payload.rs | 0 src/{extract => types}/query.rs | 0 8 files changed, 18 insertions(+), 16 deletions(-) rename src/{extract/mod.rs => extract.rs} (97%) rename src/{extract => types}/form.rs (100%) rename src/{extract => types}/json.rs (100%) create mode 100644 src/types/mod.rs rename src/{extract => types}/path.rs (99%) rename src/{extract => types}/payload.rs (100%) rename src/{extract => types}/query.rs (100%) diff --git a/src/extract/mod.rs b/src/extract.rs similarity index 97% rename from src/extract/mod.rs rename to src/extract.rs index 738f8918..4cd04be2 100644 --- a/src/extract/mod.rs +++ b/src/extract.rs @@ -6,18 +6,6 @@ use futures::{future, Async, Future, IntoFuture, Poll}; use crate::service::ServiceFromRequest; -mod form; -mod json; -mod path; -mod payload; -mod query; - -pub use self::form::{Form, FormConfig}; -pub use self::json::{Json, JsonConfig}; -pub use self::path::Path; -pub use self::payload::{Payload, PayloadConfig}; -pub use self::query::Query; - /// Trait implemented by types that can be extracted from request. /// /// Types that implement this trait can be used with `Route` handlers. @@ -250,6 +238,7 @@ mod tests { use super::*; use crate::test::{block_on, TestRequest}; + use crate::types::{Form, FormConfig, Path, Query}; #[derive(Deserialize, Debug, PartialEq)] struct Info { diff --git a/src/lib.rs b/src/lib.rs index 509fcc60..dc0493a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ mod scope; mod server; mod service; pub mod test; +mod types; #[allow(unused_imports)] #[macro_use] @@ -93,9 +94,9 @@ pub mod web { use crate::scope::Scope; pub use crate::data::{Data, RouteData}; - pub use crate::extract::{Form, Json, Path, Payload, Query}; - pub use crate::extract::{FormConfig, JsonConfig, PayloadConfig}; pub use crate::request::HttpRequest; + pub use crate::types::{Form, Json, Path, Payload, Query}; + pub use crate::types::{FormConfig, JsonConfig, PayloadConfig}; /// Create resource for a specific path. /// diff --git a/src/extract/form.rs b/src/types/form.rs similarity index 100% rename from src/extract/form.rs rename to src/types/form.rs diff --git a/src/extract/json.rs b/src/types/json.rs similarity index 100% rename from src/extract/json.rs rename to src/types/json.rs diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 00000000..b5f8de60 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,13 @@ +//! Helper types + +mod form; +mod json; +mod path; +mod payload; +mod query; + +pub use self::form::{Form, FormConfig}; +pub use self::json::{Json, JsonConfig}; +pub use self::path::Path; +pub use self::payload::{Payload, PayloadConfig}; +pub use self::query::Query; diff --git a/src/extract/path.rs b/src/types/path.rs similarity index 99% rename from src/extract/path.rs rename to src/types/path.rs index fc6811c7..4e678479 100644 --- a/src/extract/path.rs +++ b/src/types/path.rs @@ -8,8 +8,7 @@ use serde::de; use crate::request::HttpRequest; use crate::service::ServiceFromRequest; - -use super::FromRequest; +use crate::FromRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's path. diff --git a/src/extract/payload.rs b/src/types/payload.rs similarity index 100% rename from src/extract/payload.rs rename to src/types/payload.rs diff --git a/src/extract/query.rs b/src/types/query.rs similarity index 100% rename from src/extract/query.rs rename to src/types/query.rs From c80884904ccc66ba926a2da69127e8e1d08ddb7b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 22:04:09 -0700 Subject: [PATCH 1079/1635] move JsonBody from actix-http --- src/lib.rs | 4 +- src/types/json.rs | 195 ++++++++++++++++++++++++++++++++++++++++++++-- src/types/mod.rs | 2 +- 3 files changed, 192 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dc0493a8..18cf93f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,7 @@ pub mod dev { pub use crate::service::{ HttpServiceFactory, ServiceFromRequest, ServiceRequest, ServiceResponse, }; + pub use crate::types::json::JsonBody; pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; pub use actix_http::dev::ResponseBuilder as HttpResponseBuilder; @@ -95,8 +96,7 @@ pub mod web { pub use crate::data::{Data, RouteData}; pub use crate::request::HttpRequest; - pub use crate::types::{Form, Json, Path, Payload, Query}; - pub use crate::types::{FormConfig, JsonConfig, PayloadConfig}; + pub use crate::types::*; /// Create resource for a specific path. /// diff --git a/src/types/json.rs b/src/types/json.rs index 92b7f20f..74ee5eb2 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -3,16 +3,15 @@ use std::rc::Rc; use std::{fmt, ops}; -use bytes::Bytes; -use futures::{Future, Stream}; +use bytes::{Bytes, BytesMut}; +use futures::{Future, Poll, Stream}; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; -use actix_http::dev::JsonBody; -use actix_http::error::{Error, JsonPayloadError}; -use actix_http::http::StatusCode; -use actix_http::Response; +use actix_http::error::{Error, JsonPayloadError, PayloadError}; +use actix_http::http::{header::CONTENT_LENGTH, StatusCode}; +use actix_http::{HttpMessage, Payload, Response}; use crate::extract::FromRequest; use crate::request::HttpRequest; @@ -257,3 +256,187 @@ impl Default for JsonConfig { } } } + +/// Request's payload json parser, it resolves to a deserialized `T` value. +/// This future could be used with `ServiceRequest` and `ServiceFromRequest`. +/// +/// Returns error: +/// +/// * content type is not `application/json` +/// * content length is greater than 256k +pub struct JsonBody { + limit: usize, + length: Option, + stream: Payload, + err: Option, + fut: Option>>, +} + +impl JsonBody +where + T: HttpMessage, + T::Stream: Stream + 'static, + U: DeserializeOwned + 'static, +{ + /// Create `JsonBody` for request. + pub fn new(req: &mut T) -> Self { + // check content-type + let json = if let Ok(Some(mime)) = req.mime_type() { + mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) + } else { + false + }; + if !json { + return JsonBody { + limit: 262_144, + length: None, + stream: Payload::None, + fut: None, + err: Some(JsonPayloadError::ContentType), + }; + } + + let mut len = None; + if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Ok(s) = l.to_str() { + if let Ok(l) = s.parse::() { + len = Some(l) + } + } + } + + JsonBody { + limit: 262_144, + length: len, + stream: req.take_payload(), + fut: None, + err: None, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Future for JsonBody +where + T: HttpMessage, + T::Stream: Stream + 'static, + U: DeserializeOwned + 'static, +{ + type Item = U; + type Error = JsonPayloadError; + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut { + return fut.poll(); + } + + if let Some(err) = self.err.take() { + return Err(err); + } + + let limit = self.limit; + if let Some(len) = self.length.take() { + if len > limit { + return Err(JsonPayloadError::Overflow); + } + } + + let fut = std::mem::replace(&mut self.stream, Payload::None) + .from_err() + .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(JsonPayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .and_then(|body| Ok(serde_json::from_slice::(&body)?)); + self.fut = Some(Box::new(fut)); + self.poll() + } +} + +#[cfg(test)] +mod tests { + use bytes::Bytes; + use serde_derive::{Deserialize, Serialize}; + + use super::*; + use crate::http::header; + use crate::test::{block_on, TestRequest}; + + fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { + match err { + 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_body() { + let mut req = TestRequest::default().to_request(); + let json = block_on(req.json::()); + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let mut req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + ) + .to_request(); + let json = block_on(req.json::()); + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let mut req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ) + .to_request(); + + let json = block_on(req.json::().limit(100)); + assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); + + let mut req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .to_request(); + + let json = block_on(req.json::()); + assert_eq!( + json.ok().unwrap(), + MyObject { + name: "test".to_owned() + } + ); + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index b5f8de60..2fc3ca93 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,7 +1,7 @@ //! Helper types mod form; -mod json; +pub(crate) mod json; mod path; mod payload; mod query; From fd141ef9b109c11ba6fb7007b261364037608ed8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 22:10:15 -0700 Subject: [PATCH 1080/1635] move json to actix-web --- src/httpmessage.rs | 40 -------- src/json.rs | 222 --------------------------------------------- src/lib.rs | 2 - 3 files changed, 264 deletions(-) delete mode 100644 src/json.rs diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 3c049c09..8573e917 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -19,7 +19,6 @@ use crate::error::{ }; use crate::extensions::Extensions; use crate::header::Header; -use crate::json::JsonBody; use crate::payload::Payload; struct Cookies(Vec>); @@ -219,45 +218,6 @@ pub trait HttpMessage: Sized { UrlEncoded::new(self) } - /// Parse `application/json` encoded body. - /// Return `JsonBody` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - /// - /// ## Server example - /// - /// ```rust,ignore - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::*; - /// use futures::future::{ok, 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(Response::Ok().into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - fn json(&mut self) -> JsonBody - where - Self::Stream: Stream + 'static, - { - JsonBody::new(self) - } - /// Return stream of lines. fn readlines(&mut self) -> Readlines where diff --git a/src/json.rs b/src/json.rs deleted file mode 100644 index 026ecb87..00000000 --- a/src/json.rs +++ /dev/null @@ -1,222 +0,0 @@ -use bytes::BytesMut; -use futures::{Future, Poll, Stream}; -use http::header::CONTENT_LENGTH; - -use bytes::Bytes; -use mime; -use serde::de::DeserializeOwned; -use serde_json; - -use crate::error::{JsonPayloadError, PayloadError}; -use crate::httpmessage::HttpMessage; -use crate::payload::Payload; - -/// 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 -/// -/// # Server example -/// -/// ```rust,ignore -/// # extern crate actix_web; -/// # extern crate futures; -/// # #[macro_use] extern crate serde_derive; -/// use actix_web::{AsyncResponder, Error, HttpMessage, HttpRequest, Response}; -/// 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(Response::Ok().into()) -/// }).responder() -/// } -/// # fn main() {} -/// ``` -pub struct JsonBody { - limit: usize, - length: Option, - stream: Payload, - err: Option, - fut: Option>>, -} - -impl JsonBody -where - T: HttpMessage, - T::Stream: Stream + 'static, - U: DeserializeOwned + 'static, -{ - /// Create `JsonBody` for request. - pub fn new(req: &mut T) -> Self { - // check content-type - let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) - } else { - false - }; - if !json { - return JsonBody { - limit: 262_144, - length: None, - stream: Payload::None, - fut: None, - err: Some(JsonPayloadError::ContentType), - }; - } - - let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } - } - } - - JsonBody { - limit: 262_144, - length: len, - stream: req.take_payload(), - fut: None, - err: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for JsonBody -where - T: HttpMessage, - T::Stream: Stream + 'static, - U: DeserializeOwned + 'static, -{ - type Item = U; - type Error = JsonPayloadError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Err(JsonPayloadError::Overflow); - } - } - - let fut = std::mem::replace(&mut self.stream, Payload::None) - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(JsonPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); - self.fut = Some(Box::new(fut)); - self.poll() - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - use futures::Async; - use http::header; - use serde_derive::{Deserialize, Serialize}; - - use super::*; - use crate::test::TestRequest; - - 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_body() { - let mut req = TestRequest::default().finish(); - let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - - let mut req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ) - .finish(); - let mut json = req.json::(); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType); - - let mut req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ) - .finish(); - let mut json = req.json::().limit(100); - assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow); - - let mut req = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - - 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 41ee55fe..443266a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,7 +77,6 @@ mod header; mod helpers; mod httpcodes; pub mod httpmessage; -mod json; mod message; mod payload; mod request; @@ -113,7 +112,6 @@ pub mod dev { //! ``` pub use crate::httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use crate::json::JsonBody; pub use crate::response::ResponseBuilder; } From 9012c46fe1e2ced25480e3aa4fd200899368e81a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 00:48:40 -0700 Subject: [PATCH 1081/1635] move payload futures from actix-http --- Cargo.toml | 2 +- src/error.rs | 101 +++++++++++++++++ src/lib.rs | 3 + src/types/form.rs | 238 ++++++++++++++++++++++++++++++++++++++--- src/types/json.rs | 8 +- src/types/mod.rs | 5 +- src/types/payload.rs | 145 ++++++++++++++++++++++++- src/types/readlines.rs | 210 ++++++++++++++++++++++++++++++++++++ 8 files changed, 688 insertions(+), 24 deletions(-) create mode 100644 src/types/readlines.rs diff --git a/Cargo.toml b/Cargo.toml index 87a54b6a..ba36dd2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ mime = "0.3" net2 = "0.2.33" parking_lot = "0.7" regex = "1.0" -serde = "1.0" +serde = { version = "1.0", features=["derive"] } serde_json = "1.0" serde_urlencoded = "^0.5.3" time = "0.1" diff --git a/src/error.rs b/src/error.rs index 06840708..bf224a22 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,8 +3,12 @@ use std::fmt; pub use actix_http::error::*; use derive_more::{Display, From}; +use serde_json::error::Error as JsonError; use url::ParseError as UrlParseError; +use crate::http::StatusCode; +use crate::HttpResponse; + /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, From)] pub enum UrlGenerationError { @@ -41,3 +45,100 @@ impl From> for BlockingError } } } + +/// A set of errors that can occur during parsing urlencoded payloads +#[derive(Debug, Display, From)] +pub enum UrlencodedError { + /// Can not decode chunked transfer encoding + #[display(fmt = "Can not decode chunked transfer encoding")] + Chunked, + /// Payload size is bigger than allowed. (default: 256kB) + #[display(fmt = "Urlencoded payload size is bigger than allowed. (default: 256kB)")] + Overflow, + /// Payload size is now known + #[display(fmt = "Payload size is now known")] + UnknownLength, + /// Content type error + #[display(fmt = "Content type error")] + ContentType, + /// Parse error + #[display(fmt = "Parse error")] + Parse, + /// Payload error + #[display(fmt = "Error that occur during reading payload: {}", _0)] + Payload(PayloadError), +} + +/// Return `BadRequest` for `UrlencodedError` +impl ResponseError for UrlencodedError { + fn error_response(&self) -> HttpResponse { + match *self { + UrlencodedError::Overflow => { + HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) + } + UrlencodedError::UnknownLength => { + HttpResponse::new(StatusCode::LENGTH_REQUIRED) + } + _ => HttpResponse::new(StatusCode::BAD_REQUEST), + } + } +} + +/// A set of errors that can occur during parsing json payloads +#[derive(Debug, Display, From)] +pub enum JsonPayloadError { + /// Payload size is bigger than allowed. (default: 256kB) + #[display(fmt = "Json payload size is bigger than allowed. (default: 256kB)")] + Overflow, + /// Content type error + #[display(fmt = "Content type error")] + ContentType, + /// Deserialize error + #[display(fmt = "Json deserialize error: {}", _0)] + Deserialize(JsonError), + /// Payload error + #[display(fmt = "Error that occur during reading payload: {}", _0)] + Payload(PayloadError), +} + +/// Return `BadRequest` for `UrlencodedError` +impl ResponseError for JsonPayloadError { + fn error_response(&self) -> HttpResponse { + match *self { + JsonPayloadError::Overflow => { + HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) + } + _ => HttpResponse::new(StatusCode::BAD_REQUEST), + } + } +} + +/// Error type returned when reading body as lines. +#[derive(From, Display, Debug)] +pub enum ReadlinesError { + /// Error when decoding a line. + #[display(fmt = "Encoding error")] + /// Payload size is bigger than allowed. (default: 256kB) + EncodingError, + /// Payload error. + #[display(fmt = "Error that occur during reading payload: {}", _0)] + Payload(PayloadError), + /// Line limit exceeded. + #[display(fmt = "Line limit exceeded")] + LimitOverflow, + /// ContentType error. + #[display(fmt = "Content-type error")] + ContentTypeError(ContentTypeError), +} + +/// Return `BadRequest` for `ReadlinesError` +impl ResponseError for ReadlinesError { + fn error_response(&self) -> HttpResponse { + match *self { + ReadlinesError::LimitOverflow => { + HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) + } + _ => HttpResponse::new(StatusCode::BAD_REQUEST), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 18cf93f4..d6bcf4e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,10 @@ pub mod dev { pub use crate::service::{ HttpServiceFactory, ServiceFromRequest, ServiceRequest, ServiceResponse, }; + pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; + pub use crate::types::payload::HttpMessageBody; + pub use crate::types::readlines::Readlines; pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; pub use actix_http::dev::ResponseBuilder as HttpResponseBuilder; diff --git a/src/types/form.rs b/src/types/form.rs index 4a5e9729..58fa3761 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -3,13 +3,17 @@ use std::rc::Rc; use std::{fmt, ops}; -use actix_http::dev::UrlEncoded; -use actix_http::error::{Error, UrlencodedError}; -use bytes::Bytes; -use futures::{Future, Stream}; +use actix_http::error::{Error, PayloadError, UrlencodedError}; +use actix_http::{HttpMessage, Payload}; +use bytes::{Bytes, BytesMut}; +use encoding::all::UTF_8; +use encoding::types::{DecoderTrap, Encoding}; +use encoding::EncodingRef; +use futures::{Future, Poll, Stream}; use serde::de::DeserializeOwned; use crate::extract::FromRequest; +use crate::http::header::CONTENT_LENGTH; use crate::request::HttpRequest; use crate::service::ServiceFromRequest; @@ -167,13 +171,145 @@ impl Default for FormConfig { } } +/// Future that resolves to a parsed urlencoded values. +/// +/// Parse `application/x-www-form-urlencoded` encoded request's body. +/// Return `UrlEncoded` future. Form can be deserialized to any type that +/// implements `Deserialize` trait from *serde*. +/// +/// Returns error: +/// +/// * content type is not `application/x-www-form-urlencoded` +/// * content-length is greater than 32k +/// +pub struct UrlEncoded { + stream: Payload, + limit: usize, + length: Option, + encoding: EncodingRef, + err: Option, + fut: Option>>, +} + +impl UrlEncoded +where + T: HttpMessage, + T::Stream: Stream, +{ + /// Create a new future to URL encode a request + pub fn new(req: &mut T) -> UrlEncoded { + // check content type + if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { + return Self::err(UrlencodedError::ContentType); + } + let encoding = match req.encoding() { + Ok(enc) => enc, + Err(_) => return Self::err(UrlencodedError::ContentType), + }; + + let mut len = None; + if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Ok(s) = l.to_str() { + if let Ok(l) = s.parse::() { + len = Some(l) + } else { + return Self::err(UrlencodedError::UnknownLength); + } + } else { + return Self::err(UrlencodedError::UnknownLength); + } + }; + + UrlEncoded { + encoding, + stream: req.take_payload(), + limit: 32_768, + length: len, + fut: None, + err: None, + } + } + + fn err(e: UrlencodedError) -> Self { + UrlEncoded { + stream: Payload::None, + limit: 32_768, + fut: None, + err: Some(e), + length: None, + encoding: UTF_8, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Future for UrlEncoded +where + T: HttpMessage, + T::Stream: Stream + 'static, + U: DeserializeOwned + 'static, +{ + type Item = U; + type Error = UrlencodedError; + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut { + return fut.poll(); + } + + if let Some(err) = self.err.take() { + return Err(err); + } + + // payload size + let limit = self.limit; + if let Some(len) = self.length.take() { + if len > limit { + return Err(UrlencodedError::Overflow); + } + } + + // future + let encoding = self.encoding; + let fut = std::mem::replace(&mut self.stream, Payload::None) + .from_err() + .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(UrlencodedError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .and_then(move |body| { + if (encoding as *const Encoding) == UTF_8 { + serde_urlencoded::from_bytes::(&body) + .map_err(|_| UrlencodedError::Parse) + } else { + let body = encoding + .decode(&body, DecoderTrap::Strict) + .map_err(|_| UrlencodedError::Parse)?; + serde_urlencoded::from_str::(&body) + .map_err(|_| UrlencodedError::Parse) + } + }); + self.fut = Some(Box::new(fut)); + self.poll() + } +} + #[cfg(test)] mod tests { - use actix_http::http::header; use bytes::Bytes; - use serde_derive::Deserialize; + use serde::Deserialize; use super::*; + use crate::http::header::CONTENT_TYPE; use crate::test::{block_on, TestRequest}; #[derive(Deserialize, Debug, PartialEq)] @@ -183,15 +319,91 @@ mod tests { #[test] fn test_form() { - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); + let mut req = + TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .to_from(); let s = block_on(Form::::from_request(&mut req)).unwrap(); assert_eq!(s.hello, "world"); } + + fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { + match err { + UrlencodedError::Chunked => match other { + UrlencodedError::Chunked => true, + _ => false, + }, + UrlencodedError::Overflow => match other { + UrlencodedError::Overflow => true, + _ => false, + }, + UrlencodedError::UnknownLength => match other { + UrlencodedError::UnknownLength => true, + _ => false, + }, + UrlencodedError::ContentType => match other { + UrlencodedError::ContentType => true, + _ => false, + }, + _ => false, + } + } + + #[test] + fn test_urlencoded_error() { + let mut req = + TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(CONTENT_LENGTH, "xxxx") + .to_request(); + let info = block_on(UrlEncoded::<_, Info>::new(&mut req)); + assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); + + let mut req = + TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(CONTENT_LENGTH, "1000000") + .to_request(); + let info = block_on(UrlEncoded::<_, Info>::new(&mut req)); + assert!(eq(info.err().unwrap(), UrlencodedError::Overflow)); + + let mut req = TestRequest::with_header(CONTENT_TYPE, "text/plain") + .header(CONTENT_LENGTH, "10") + .to_request(); + let info = block_on(UrlEncoded::<_, Info>::new(&mut req)); + assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); + } + + #[test] + fn test_urlencoded() { + let mut req = + TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .to_request(); + + let info = block_on(UrlEncoded::<_, Info>::new(&mut req)).unwrap(); + assert_eq!( + info, + Info { + hello: "world".to_owned() + } + ); + + let mut req = TestRequest::with_header( + CONTENT_TYPE, + "application/x-www-form-urlencoded; charset=utf-8", + ) + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .to_request(); + + let info = block_on(UrlEncoded::<_, Info>::new(&mut req)).unwrap(); + assert_eq!( + info, + Info { + hello: "world".to_owned() + } + ); + } } diff --git a/src/types/json.rs b/src/types/json.rs index 74ee5eb2..18a6be90 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -393,7 +393,7 @@ mod tests { #[test] fn test_json_body() { let mut req = TestRequest::default().to_request(); - let json = block_on(req.json::()); + let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let mut req = TestRequest::default() @@ -402,7 +402,7 @@ mod tests { header::HeaderValue::from_static("application/text"), ) .to_request(); - let json = block_on(req.json::()); + let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let mut req = TestRequest::default() @@ -416,7 +416,7 @@ mod tests { ) .to_request(); - let json = block_on(req.json::().limit(100)); + let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); let mut req = TestRequest::default() @@ -431,7 +431,7 @@ mod tests { .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .to_request(); - let json = block_on(req.json::()); + let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); assert_eq!( json.ok().unwrap(), MyObject { diff --git a/src/types/mod.rs b/src/types/mod.rs index 2fc3ca93..30ee7309 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,10 +1,11 @@ //! Helper types -mod form; +pub(crate) mod form; pub(crate) mod json; mod path; -mod payload; +pub(crate) mod payload; mod query; +pub(crate) mod readlines; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; diff --git a/src/types/payload.rs b/src/types/payload.rs index 7164a544..402486b6 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -1,10 +1,9 @@ //! Payload/Bytes/String extractors use std::str; -use actix_http::dev::MessageBody; use actix_http::error::{Error, ErrorBadRequest, PayloadError}; use actix_http::HttpMessage; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use encoding::all::UTF_8; use encoding::types::{DecoderTrap, Encoding}; use futures::future::{err, Either, FutureResult}; @@ -12,6 +11,7 @@ use futures::{Future, Poll, Stream}; use mime::Mime; use crate::extract::FromRequest; +use crate::http::header; use crate::service::ServiceFromRequest; /// Payload extractor returns request 's payload stream. @@ -152,7 +152,7 @@ where } let limit = cfg.limit; - Either::A(Box::new(MessageBody::new(req).limit(limit).from_err())) + Either::A(Box::new(HttpMessageBody::new(req).limit(limit).from_err())) } } @@ -213,7 +213,7 @@ where let limit = cfg.limit; Either::A(Box::new( - MessageBody::new(req) + HttpMessageBody::new(req) .limit(limit) .from_err() .and_then(move |body| { @@ -287,6 +287,109 @@ impl Default for PayloadConfig { } } +/// Future that resolves to a complete http message body. +/// +/// Load http message body. +/// +/// By default only 256Kb payload reads to a memory, then +/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` +/// method to change upper limit. +pub struct HttpMessageBody { + limit: usize, + length: Option, + stream: actix_http::Payload, + err: Option, + fut: Option>>, +} + +impl HttpMessageBody +where + T: HttpMessage, + T::Stream: Stream, +{ + /// Create `MessageBody` for request. + pub fn new(req: &mut T) -> HttpMessageBody { + let mut len = None; + if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = l.to_str() { + if let Ok(l) = s.parse::() { + len = Some(l) + } else { + return Self::err(PayloadError::UnknownLength); + } + } else { + return Self::err(PayloadError::UnknownLength); + } + } + + HttpMessageBody { + stream: req.take_payload(), + limit: 262_144, + length: len, + fut: None, + err: None, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + fn err(e: PayloadError) -> Self { + HttpMessageBody { + stream: actix_http::Payload::None, + limit: 262_144, + fut: None, + err: Some(e), + length: None, + } + } +} + +impl Future for HttpMessageBody +where + T: HttpMessage, + T::Stream: Stream + 'static, +{ + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut { + return fut.poll(); + } + + if let Some(err) = self.err.take() { + return Err(err); + } + + if let Some(len) = self.length.take() { + if len > self.limit { + return Err(PayloadError::Overflow); + } + } + + // future + let limit = self.limit; + self.fut = Some(Box::new( + std::mem::replace(&mut self.stream, actix_http::Payload::None) + .from_err() + .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(PayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .map(|body| body.freeze()), + )); + self.poll() + } +} + #[cfg(test)] mod tests { use bytes::Bytes; @@ -332,4 +435,38 @@ mod tests { let s = block_on(String::from_request(&mut req)).unwrap(); assert_eq!(s, "hello=world"); } + + #[test] + fn test_message_body() { + let mut req = + TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").to_request(); + let res = block_on(HttpMessageBody::new(&mut req)); + match res.err().unwrap() { + PayloadError::UnknownLength => (), + _ => unreachable!("error"), + } + + let mut req = + TestRequest::with_header(header::CONTENT_LENGTH, "1000000").to_request(); + let res = block_on(HttpMessageBody::new(&mut req)); + match res.err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } + + let mut req = TestRequest::default() + .set_payload(Bytes::from_static(b"test")) + .to_request(); + let res = block_on(HttpMessageBody::new(&mut req)); + assert_eq!(res.ok().unwrap(), Bytes::from_static(b"test")); + + let mut req = TestRequest::default() + .set_payload(Bytes::from_static(b"11111111111111")) + .to_request(); + let res = block_on(HttpMessageBody::new(&mut req).limit(5)); + match res.err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } + } } diff --git a/src/types/readlines.rs b/src/types/readlines.rs new file mode 100644 index 00000000..2c7f699a --- /dev/null +++ b/src/types/readlines.rs @@ -0,0 +1,210 @@ +use std::str; + +use bytes::{Bytes, BytesMut}; +use encoding::all::UTF_8; +use encoding::types::{DecoderTrap, Encoding}; +use encoding::EncodingRef; +use futures::{Async, Poll, Stream}; + +use crate::dev::Payload; +use crate::error::{PayloadError, ReadlinesError}; +use crate::HttpMessage; + +/// Stream to read request line by line. +pub struct Readlines { + stream: Payload, + buff: BytesMut, + limit: usize, + checked_buff: bool, + encoding: EncodingRef, + err: Option, +} + +impl Readlines +where + T: HttpMessage, + T::Stream: Stream, +{ + /// Create a new stream to read request line by line. + pub fn new(req: &mut T) -> Self { + let encoding = match req.encoding() { + Ok(enc) => enc, + Err(err) => return Self::err(err.into()), + }; + + Readlines { + stream: req.take_payload(), + buff: BytesMut::with_capacity(262_144), + limit: 262_144, + checked_buff: true, + err: None, + encoding, + } + } + + /// Change max line size. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + fn err(err: ReadlinesError) -> Self { + Readlines { + stream: Payload::None, + buff: BytesMut::new(), + limit: 262_144, + checked_buff: true, + encoding: UTF_8, + err: Some(err), + } + } +} + +impl Stream for Readlines +where + T: HttpMessage, + T::Stream: Stream, +{ + type Item = String; + type Error = ReadlinesError; + + fn poll(&mut self) -> Poll, Self::Error> { + if let Some(err) = self.err.take() { + return Err(err); + } + + // check if there is a newline in the buffer + if !self.checked_buff { + let mut found: Option = None; + for (ind, b) in self.buff.iter().enumerate() { + if *b == b'\n' { + found = Some(ind); + break; + } + } + if let Some(ind) = found { + // check if line is longer than limit + if ind + 1 > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = self.encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&self.buff.split_to(ind + 1)) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + self.encoding + .decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + return Ok(Async::Ready(Some(line))); + } + self.checked_buff = true; + } + // poll req for more bytes + match self.stream.poll() { + Ok(Async::Ready(Some(mut bytes))) => { + // check if there is a newline in bytes + let mut found: Option = None; + for (ind, b) in bytes.iter().enumerate() { + if *b == b'\n' { + found = Some(ind); + break; + } + } + if let Some(ind) = found { + // check if line is longer than limit + if ind + 1 > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = self.encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&bytes.split_to(ind + 1)) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + self.encoding + .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + // extend buffer with rest of the bytes; + self.buff.extend_from_slice(&bytes); + self.checked_buff = false; + return Ok(Async::Ready(Some(line))); + } + self.buff.extend_from_slice(&bytes); + Ok(Async::NotReady) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => { + if self.buff.is_empty() { + return Ok(Async::Ready(None)); + } + if self.buff.len() > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = self.encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&self.buff) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + self.encoding + .decode(&self.buff, DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + self.buff.clear(); + Ok(Async::Ready(Some(line))) + } + Err(e) => Err(ReadlinesError::from(e)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::{block_on, TestRequest}; + + #[test] + fn test_readlines() { + let mut req = TestRequest::default() + .set_payload(Bytes::from_static( + b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ + industry. Lorem Ipsum has been the industry's standard dummy\n\ + Contrary to popular belief, Lorem Ipsum is not simply random text.", + )) + .to_request(); + let stream = match block_on(Readlines::new(&mut req).into_future()) { + Ok((Some(s), stream)) => { + assert_eq!( + s, + "Lorem Ipsum is simply dummy text of the printing and typesetting\n" + ); + stream + } + _ => unreachable!("error"), + }; + + let stream = match block_on(stream.into_future()) { + Ok((Some(s), stream)) => { + assert_eq!( + s, + "industry. Lorem Ipsum has been the industry's standard dummy\n" + ); + stream + } + _ => unreachable!("error"), + }; + + match block_on(stream.into_future()) { + Ok((Some(s), stream)) => { + assert_eq!( + s, + "Contrary to popular belief, Lorem Ipsum is not simply random text." + ); + } + _ => unreachable!("error"), + } + } +} From fa66a07ec5070621292b411ab48b501cad18002e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 01:02:51 -0700 Subject: [PATCH 1082/1635] move httpmessage futures to actix-web --- examples/echo.rs | 27 +- examples/echo2.rs | 25 +- src/error.rs | 74 ----- src/httpmessage.rs | 627 +------------------------------------------ src/lib.rs | 17 +- tests/test_client.rs | 22 +- tests/test_server.rs | 54 ++-- 7 files changed, 84 insertions(+), 762 deletions(-) diff --git a/examples/echo.rs b/examples/echo.rs index 8ec0e6a9..c36292c4 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,10 +1,9 @@ use std::{env, io}; -use actix_http::HttpMessage; -use actix_http::{HttpService, Request, Response}; +use actix_http::{error::PayloadError, HttpService, Request, Response}; use actix_server::Server; -use bytes::Bytes; -use futures::Future; +use bytes::BytesMut; +use futures::{Future, Stream}; use http::header::HeaderValue; use log::info; @@ -18,12 +17,20 @@ fn main() -> io::Result<()> { .client_timeout(1000) .client_disconnect(1000) .finish(|mut req: Request| { - req.body().limit(512).and_then(|bytes: Bytes| { - info!("request body: {:?}", bytes); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - Ok(res.body(bytes)) - }) + req.take_payload() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) + .and_then(|bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header( + "x-head", + HeaderValue::from_static("dummy value!"), + ); + Ok(res.body(bytes)) + }) }) })? .run() diff --git a/examples/echo2.rs b/examples/echo2.rs index 101adc1c..b239796b 100644 --- a/examples/echo2.rs +++ b/examples/echo2.rs @@ -1,20 +1,25 @@ use std::{env, io}; use actix_http::http::HeaderValue; -use actix_http::HttpMessage; -use actix_http::{Error, HttpService, Request, Response}; +use actix_http::{error::PayloadError, Error, HttpService, Request, Response}; use actix_server::Server; -use bytes::Bytes; -use futures::Future; +use bytes::BytesMut; +use futures::{Future, Stream}; use log::info; fn handle_request(mut req: Request) -> impl Future { - req.body().limit(512).from_err().and_then(|bytes: Bytes| { - info!("request body: {:?}", bytes); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - Ok(res.body(bytes)) - }) + req.take_payload() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) + .from_err() + .and_then(|bytes| { + info!("request body: {:?}", bytes); + let mut res = Response::Ok(); + res.header("x-head", HeaderValue::from_static("dummy value!")); + Ok(res.body(bytes)) + }) } fn main() -> io::Result<()> { diff --git a/src/error.rs b/src/error.rs index 696162f8..e0a416ef 100644 --- a/src/error.rs +++ b/src/error.rs @@ -390,80 +390,6 @@ impl ResponseError for ContentTypeError { } } -/// A set of errors that can occur during parsing urlencoded payloads -#[derive(Debug, Display, From)] -pub enum UrlencodedError { - /// Can not decode chunked transfer encoding - #[display(fmt = "Can not decode chunked transfer encoding")] - Chunked, - /// Payload size is bigger than allowed. (default: 256kB) - #[display(fmt = "Urlencoded payload size is bigger than allowed. (default: 256kB)")] - Overflow, - /// Payload size is now known - #[display(fmt = "Payload size is now known")] - UnknownLength, - /// Content type error - #[display(fmt = "Content type error")] - ContentType, - /// Parse error - #[display(fmt = "Parse error")] - Parse, - /// Payload error - #[display(fmt = "Error that occur during reading payload: {}", _0)] - Payload(PayloadError), -} - -/// Return `BadRequest` for `UrlencodedError` -impl ResponseError for UrlencodedError { - fn error_response(&self) -> Response { - match *self { - UrlencodedError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), - UrlencodedError::UnknownLength => Response::new(StatusCode::LENGTH_REQUIRED), - _ => Response::new(StatusCode::BAD_REQUEST), - } - } -} - -/// A set of errors that can occur during parsing json payloads -#[derive(Debug, Display, From)] -pub enum JsonPayloadError { - /// Payload size is bigger than allowed. (default: 256kB) - #[display(fmt = "Json payload size is bigger than allowed. (default: 256kB)")] - Overflow, - /// Content type error - #[display(fmt = "Content type error")] - ContentType, - /// Deserialize error - #[display(fmt = "Json deserialize error: {}", _0)] - Deserialize(JsonError), - /// Payload error - #[display(fmt = "Error that occur during reading payload: {}", _0)] - Payload(PayloadError), -} - -/// Return `BadRequest` for `UrlencodedError` -impl ResponseError for JsonPayloadError { - fn error_response(&self) -> Response { - match *self { - JsonPayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => Response::new(StatusCode::BAD_REQUEST), - } - } -} - -/// Error type returned when reading body as lines. -#[derive(From)] -pub enum ReadlinesError { - /// Error when decoding a line. - EncodingError, - /// Payload error. - PayloadError(PayloadError), - /// Line limit exceeded. - LimitOverflow, - /// ContentType error. - ContentTypeError(ContentTypeError), -} - /// Helper type that can wrap any error and generate custom response. /// /// In following example any `io::Error` will be converted into "BAD REQUEST" diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 8573e917..117e10a8 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,22 +1,14 @@ use std::cell::{Ref, RefMut}; use std::str; -use bytes::{Bytes, BytesMut}; use cookie::Cookie; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, Encoding}; use encoding::EncodingRef; -use futures::{Async, Future, Poll, Stream}; use http::{header, HeaderMap}; use mime::Mime; -use serde::de::DeserializeOwned; -use serde_urlencoded; -use crate::error::{ - ContentTypeError, CookieParseError, ParseError, PayloadError, ReadlinesError, - UrlencodedError, -}; +use crate::error::{ContentTypeError, CookieParseError, ParseError}; use crate::extensions::Extensions; use crate::header::Header; use crate::payload::Payload; @@ -143,88 +135,6 @@ pub trait HttpMessage: Sized { } None } - - /// Load http message body. - /// - /// By default only 256Kb payload reads to a memory, then - /// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` - /// method to change upper limit. - /// - /// ## Server example - /// - /// ```rust,ignore - /// # extern crate bytes; - /// # extern crate actix_web; - /// # extern crate futures; - /// # #[macro_use] extern crate serde_derive; - /// use actix_web::{ - /// AsyncResponder, FutureResponse, HttpMessage, HttpRequest, Response, - /// }; - /// use bytes::Bytes; - /// use futures::future::Future; - /// - /// fn index(mut req: HttpRequest) -> FutureResponse { - /// 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(Response::Ok().into()) - /// }).responder() - /// } - /// # fn main() {} - /// ``` - fn body(&mut self) -> MessageBody - where - Self::Stream: Stream + Sized, - { - MessageBody::new(self) - } - - /// Parse `application/x-www-form-urlencoded` encoded request's body. - /// Return `UrlEncoded` future. Form can be deserialized to any type that - /// implements `Deserialize` trait from *serde*. - /// - /// Returns error: - /// - /// * content type is not `application/x-www-form-urlencoded` - /// * content-length is greater than 256k - /// - /// ## Server example - /// - /// ```rust,ignore - /// # extern crate actix_web; - /// # extern crate futures; - /// # use futures::Future; - /// # use std::collections::HashMap; - /// use actix_web::{FutureResponse, HttpMessage, HttpRequest, Response}; - /// - /// fn index(mut req: HttpRequest) -> FutureResponse { - /// Box::new( - /// req.urlencoded::>() // <- get UrlEncoded future - /// .from_err() - /// .and_then(|params| { // <- url encoded parameters - /// println!("==== BODY ==== {:?}", params); - /// Ok(Response::Ok().into()) - /// }), - /// ) - /// } - /// # fn main() {} - /// ``` - fn urlencoded(&mut self) -> UrlEncoded - where - Self::Stream: Stream, - { - UrlEncoded::new(self) - } - - /// Return stream of lines. - fn readlines(&mut self) -> Readlines - where - Self::Stream: Stream + 'static, - { - Readlines::new(self) - } } impl<'a, T> HttpMessage for &'a mut T @@ -253,383 +163,12 @@ where } } -/// Stream to read request line by line. -pub struct Readlines { - stream: Payload, - buff: BytesMut, - limit: usize, - checked_buff: bool, - encoding: EncodingRef, - err: Option, -} - -impl Readlines -where - T: HttpMessage, - T::Stream: Stream, -{ - /// Create a new stream to read request line by line. - fn new(req: &mut T) -> Self { - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(err) => return Self::err(err.into()), - }; - - Readlines { - stream: req.take_payload(), - buff: BytesMut::with_capacity(262_144), - limit: 262_144, - checked_buff: true, - err: None, - encoding, - } - } - - /// Change max line size. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - fn err(err: ReadlinesError) -> Self { - Readlines { - stream: Payload::None, - buff: BytesMut::new(), - limit: 262_144, - checked_buff: true, - encoding: UTF_8, - err: Some(err), - } - } -} - -impl Stream for Readlines -where - T: HttpMessage, - T::Stream: Stream, -{ - type Item = String; - type Error = ReadlinesError; - - fn poll(&mut self) -> Poll, Self::Error> { - if let Some(err) = self.err.take() { - return Err(err); - } - - // check if there is a newline in the buffer - if !self.checked_buff { - let mut found: Option = None; - for (ind, b) in self.buff.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - return Ok(Async::Ready(Some(line))); - } - self.checked_buff = true; - } - // poll req for more bytes - match self.stream.poll() { - Ok(Async::Ready(Some(mut bytes))) => { - // check if there is a newline in bytes - let mut found: Option = None; - for (ind, b) in bytes.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&bytes.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - // extend buffer with rest of the bytes; - self.buff.extend_from_slice(&bytes); - self.checked_buff = false; - return Ok(Async::Ready(Some(line))); - } - self.buff.extend_from_slice(&bytes); - Ok(Async::NotReady) - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - if self.buff.is_empty() { - return Ok(Async::Ready(None)); - } - if self.buff.len() > self.limit { - return Err(ReadlinesError::LimitOverflow); - } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - self.encoding - .decode(&self.buff, DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? - }; - self.buff.clear(); - Ok(Async::Ready(Some(line))) - } - Err(e) => Err(ReadlinesError::from(e)), - } - } -} - -/// Future that resolves to a complete http message body. -pub struct MessageBody { - limit: usize, - length: Option, - stream: Payload, - err: Option, - fut: Option>>, -} - -impl MessageBody -where - T: HttpMessage, - T::Stream: Stream, -{ - /// Create `MessageBody` for request. - pub fn new(req: &mut T) -> MessageBody { - let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(PayloadError::UnknownLength); - } - } else { - return Self::err(PayloadError::UnknownLength); - } - } - - MessageBody { - stream: req.take_payload(), - limit: 262_144, - length: len, - fut: None, - err: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - fn err(e: PayloadError) -> Self { - MessageBody { - stream: Payload::None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - } - } -} - -impl Future for MessageBody -where - T: HttpMessage, - T::Stream: Stream + 'static, -{ - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - if let Some(len) = self.length.take() { - if len > self.limit { - return Err(PayloadError::Overflow); - } - } - - // future - let limit = self.limit; - self.fut = Some(Box::new( - std::mem::replace(&mut self.stream, Payload::None) - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .map(|body| body.freeze()), - )); - self.poll() - } -} - -/// Future that resolves to a parsed urlencoded values. -pub struct UrlEncoded { - stream: Payload, - limit: usize, - length: Option, - encoding: EncodingRef, - err: Option, - fut: Option>>, -} - -impl UrlEncoded -where - T: HttpMessage, - T::Stream: Stream, -{ - /// Create a new future to URL encode a request - pub fn new(req: &mut T) -> UrlEncoded { - // check content type - if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { - return Self::err(UrlencodedError::ContentType); - } - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(_) => return Self::err(UrlencodedError::ContentType), - }; - - let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(UrlencodedError::UnknownLength); - } - } else { - return Self::err(UrlencodedError::UnknownLength); - } - }; - - UrlEncoded { - encoding, - stream: req.take_payload(), - limit: 262_144, - length: len, - fut: None, - err: None, - } - } - - fn err(e: UrlencodedError) -> Self { - UrlEncoded { - stream: Payload::None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - encoding: UTF_8, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for UrlEncoded -where - T: HttpMessage, - T::Stream: Stream + 'static, - U: DeserializeOwned + 'static, -{ - type Item = U; - type Error = UrlencodedError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - - if let Some(err) = self.err.take() { - return Err(err); - } - - // payload size - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Err(UrlencodedError::Overflow); - } - } - - // future - let encoding = self.encoding; - let fut = std::mem::replace(&mut self.stream, Payload::None) - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(move |body| { - if (encoding as *const Encoding) == UTF_8 { - serde_urlencoded::from_bytes::(&body) - .map_err(|_| UrlencodedError::Parse) - } else { - let body = encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| UrlencodedError::Parse)?; - serde_urlencoded::from_str::(&body) - .map_err(|_| UrlencodedError::Parse) - } - }); - self.fut = Some(Box::new(fut)); - self.poll() - } -} - #[cfg(test)] mod tests { + use bytes::Bytes; use encoding::all::ISO_8859_2; use encoding::Encoding; - use futures::Async; use mime; - use serde_derive::Deserialize; use super::*; use crate::test::TestRequest; @@ -720,166 +259,4 @@ mod tests { .finish(); 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, - } - } - } - - #[derive(Deserialize, Debug, PartialEq)] - struct Info { - hello: String, - } - - #[test] - fn test_urlencoded_error() { - let mut 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 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 req = TestRequest::with_header(header::CONTENT_TYPE, "text/plain") - .header(header::CONTENT_LENGTH, "10") - .finish(); - assert_eq!( - req.urlencoded::().poll().err().unwrap(), - UrlencodedError::ContentType - ); - } - - #[test] - fn test_urlencoded() { - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - let result = req.urlencoded::().poll().ok().unwrap(); - assert_eq!( - result, - Async::Ready(Info { - hello: "world".to_owned() - }) - ); - - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded; charset=utf-8", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .finish(); - - let result = req.urlencoded().poll().ok().unwrap(); - assert_eq!( - result, - Async::Ready(Info { - hello: "world".to_owned() - }) - ); - } - - #[test] - fn test_message_body() { - let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); - match req.body().poll().err().unwrap() { - PayloadError::UnknownLength => (), - _ => unreachable!("error"), - } - - let mut req = - TestRequest::with_header(header::CONTENT_LENGTH, "1000000").finish(); - match req.body().poll().err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - - let mut req = TestRequest::default() - .set_payload(Bytes::from_static(b"test")) - .finish(); - match req.body().poll().ok().unwrap() { - Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), - _ => unreachable!("error"), - } - - let mut req = TestRequest::default() - .set_payload(Bytes::from_static(b"11111111111111")) - .finish(); - match req.body().limit(5).poll().err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - } - - #[test] - fn test_readlines() { - let mut req = TestRequest::default() - .set_payload(Bytes::from_static( - b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ - industry. Lorem Ipsum has been the industry's standard dummy\n\ - Contrary to popular belief, Lorem Ipsum is not simply random text.", - )) - .finish(); - let mut r = Readlines::new(&mut req); - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "Lorem Ipsum is simply dummy text of the printing and typesetting\n" - ), - _ => unreachable!("error"), - } - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "industry. Lorem Ipsum has been the industry's standard dummy\n" - ), - _ => unreachable!("error"), - } - match r.poll().ok().unwrap() { - Async::Ready(Some(s)) => assert_eq!( - s, - "Contrary to popular belief, Lorem Ipsum is not simply random text." - ), - _ => unreachable!("error"), - } - } } diff --git a/src/lib.rs b/src/lib.rs index 443266a9..9a87b77f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,24 +97,9 @@ pub use self::httpmessage::HttpMessage; pub use self::message::{Head, Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; -pub use self::response::Response; +pub use self::response::{Response, ResponseBuilder}; pub use self::service::{HttpService, SendError, SendResponse}; -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_http::dev::*; - //! ``` - - pub use crate::httpmessage::{MessageBody, Readlines, UrlEncoded}; - pub use crate::response::ResponseBuilder; -} - pub mod http { //! Various HTTP related types diff --git a/tests/test_client.rs b/tests/test_client.rs index 90e1a4f4..2832b1b7 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -1,9 +1,11 @@ use actix_service::NewService; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use futures::future::{self, ok}; +use futures::{Future, Stream}; -use actix_http::HttpMessage; -use actix_http::{client, HttpService, Request, Response}; +use actix_http::{ + client, error::PayloadError, HttpMessage, HttpService, Request, Response, +}; use actix_http_test::TestServer; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -28,6 +30,16 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; +fn load_body(stream: S) -> impl Future +where + S: Stream, +{ + stream.fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) +} + #[test] fn test_h1_v2() { env_logger::init(); @@ -51,7 +63,7 @@ fn test_h1_v2() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); let request = srv.post().finish().unwrap(); @@ -59,7 +71,7 @@ fn test_h1_v2() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 98f74094..8a7316cd 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -6,16 +6,27 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; use actix_service::{fn_cfg_factory, NewService}; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; use futures::future::{self, ok, Future}; -use futures::stream::once; +use futures::stream::{once, Stream}; use actix_http::body::Body; +use actix_http::error::PayloadError; use actix_http::{ body, client, error, http, http::header, Error, HttpMessage as HttpMessage2, HttpService, KeepAlive, Request, Response, }; +fn load_body(stream: S) -> impl Future +where + S: Stream, +{ + stream.fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) +} + #[test] fn test_h1() { let mut srv = TestServer::new(|| { @@ -131,8 +142,7 @@ fn test_h2_body() -> std::io::Result<()> { .and_then( HttpService::build() .h2(|mut req: Request<_>| { - req.body() - .limit(1024 * 1024) + load_body(req.take_payload()) .and_then(|body| Ok(Response::Ok().body(body))) }) .map_err(|_| ()), @@ -145,7 +155,7 @@ fn test_h2_body() -> std::io::Result<()> { let mut response = srv.send_request(req).unwrap(); assert!(response.status().is_success()); - let body = srv.block_on(response.body().limit(1024 * 1024)).unwrap(); + let body = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(&body, data.as_bytes()); Ok(()) } @@ -440,7 +450,7 @@ fn test_h1_headers() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from(data2)); } @@ -486,7 +496,7 @@ fn test_h2_headers() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from(data2)); } @@ -523,7 +533,7 @@ fn test_h1_body() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -546,7 +556,7 @@ fn test_h2_body2() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -569,7 +579,7 @@ fn test_h1_head_empty() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -601,7 +611,7 @@ fn test_h2_head_empty() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -626,7 +636,7 @@ fn test_h1_head_binary() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -661,7 +671,7 @@ fn test_h2_head_binary() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -728,7 +738,7 @@ fn test_h1_body_length() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -756,7 +766,7 @@ fn test_h2_body_length() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -787,7 +797,7 @@ fn test_h1_body_chunked_explicit() { ); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -821,7 +831,7 @@ fn test_h2_body_chunked_explicit() { assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -850,7 +860,7 @@ fn test_h1_body_chunked_implicit() { ); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -874,7 +884,7 @@ fn test_h1_response_http_error_handling() { assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -907,7 +917,7 @@ fn test_h2_response_http_error_handling() { assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -923,7 +933,7 @@ fn test_h1_service_error() { assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } @@ -947,6 +957,6 @@ fn test_h2_service_error() { assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert!(bytes.is_empty()); } From b550f9ecf449a71274f118f1b91e8c9a121e8986 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 01:08:56 -0700 Subject: [PATCH 1083/1635] update imports --- src/lib.rs | 2 +- src/responder.rs | 2 +- src/types/form.rs | 3 ++- src/types/json.rs | 2 +- src/types/readlines.rs | 2 +- tests/test_server.rs | 24 ++++++++++++------------ 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d6bcf4e3..dbf8f743 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,7 @@ pub mod dev { pub use crate::types::readlines::Readlines; pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; - pub use actix_http::dev::ResponseBuilder as HttpResponseBuilder; + pub use actix_http::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ Extensions, Payload, PayloadStream, RequestHead, ResponseHead, }; diff --git a/src/responder.rs b/src/responder.rs index ace360c6..5f98e6e8 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,5 +1,5 @@ use actix_http::error::InternalError; -use actix_http::{dev::ResponseBuilder, http::StatusCode, Error, Response}; +use actix_http::{http::StatusCode, Error, Response, ResponseBuilder}; use bytes::{Bytes, BytesMut}; use futures::future::{err, ok, Either as EitherFuture, FutureResult}; use futures::{Future, IntoFuture, Poll}; diff --git a/src/types/form.rs b/src/types/form.rs index 58fa3761..cd4d09bb 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use std::{fmt, ops}; -use actix_http::error::{Error, PayloadError, UrlencodedError}; +use actix_http::error::{Error, PayloadError}; use actix_http::{HttpMessage, Payload}; use bytes::{Bytes, BytesMut}; use encoding::all::UTF_8; @@ -12,6 +12,7 @@ use encoding::EncodingRef; use futures::{Future, Poll, Stream}; use serde::de::DeserializeOwned; +use crate::error::UrlencodedError; use crate::extract::FromRequest; use crate::http::header::CONTENT_LENGTH; use crate::request::HttpRequest; diff --git a/src/types/json.rs b/src/types/json.rs index 18a6be90..4fc2748f 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -9,10 +9,10 @@ use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; -use actix_http::error::{Error, JsonPayloadError, PayloadError}; use actix_http::http::{header::CONTENT_LENGTH, StatusCode}; use actix_http::{HttpMessage, Payload, Response}; +use crate::error::{Error, JsonPayloadError, PayloadError}; use crate::extract::FromRequest; use crate::request::HttpRequest; use crate::responder::Responder; diff --git a/src/types/readlines.rs b/src/types/readlines.rs index 2c7f699a..c23b8443 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -198,7 +198,7 @@ mod tests { }; match block_on(stream.into_future()) { - Ok((Some(s), stream)) => { + Ok((Some(s), _)) => { assert_eq!( s, "Contrary to popular belief, Lorem Ipsum is not simply random text." diff --git a/tests/test_server.rs b/tests/test_server.rs index ffdc473a..965d444f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -3,7 +3,7 @@ use std::io::{Read, Write}; use actix_http::http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; -use actix_http::{h1, Error, HttpMessage, Response}; +use actix_http::{h1, Error, Response}; use actix_http_test::TestServer; use brotli2::write::BrotliDecoder; use bytes::Bytes; @@ -12,7 +12,7 @@ use flate2::write::ZlibDecoder; use futures::stream::once; //Future, Stream use rand::{distributions::Alphanumeric, Rng}; -use actix_web::{middleware, web, App}; +use actix_web::{dev::HttpMessageBody, middleware, web, App}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -50,7 +50,7 @@ fn test_body() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -69,7 +69,7 @@ fn test_body_gzip() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -100,7 +100,7 @@ fn test_body_gzip_large() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -134,7 +134,7 @@ fn test_body_gzip_large_random() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -167,7 +167,7 @@ fn test_body_chunked_implicit() { ); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); // decode let mut e = GzDecoder::new(&bytes[..]); @@ -195,7 +195,7 @@ fn test_body_br_streaming() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); // decode br let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); @@ -222,7 +222,7 @@ fn test_head_binary() { } // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); assert!(bytes.is_empty()); } @@ -245,7 +245,7 @@ fn test_no_chunking() { assert!(!response.headers().contains_key(TRANSFER_ENCODING)); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -267,7 +267,7 @@ fn test_body_deflate() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); // decode deflate let mut e = ZlibDecoder::new(Vec::new()); @@ -294,7 +294,7 @@ fn test_body_brotli() { assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); // decode brotli let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); From 7435c5e9bf8dc1db407a8a1659a9bea2934dc427 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 01:49:00 -0700 Subject: [PATCH 1084/1635] temp fix for tarpaulin --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 55a03ec8..32e6c136 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ after_success: fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then - cargo tarpaulin --out Xml --all + taskset -c 0 cargo tarpaulin --out Xml --all bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 2b5f9f05118efda14a08febf386f665519596a04 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 08:50:25 -0700 Subject: [PATCH 1085/1635] temp fix for tarpaulin --- .travis.yml | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 283437ce..ff881505 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ matrix: include: - rust: stable - rust: beta - - rust: nightly + - rust: nightly-2019-03-02 allow_failures: - - rust: nightly + - rust: nightly-2019-03-02 env: global: @@ -25,8 +25,8 @@ before_install: - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f --version 0.6.7 + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin fi script: @@ -34,20 +34,19 @@ script: - cargo build --features="ssl" - cargo test --features="ssl" -after_success: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - cargo tarpaulin --features="ssl" --out Xml - bash <(curl -s https://codecov.io/bash) - echo "Uploaded code coverage" - fi - # Upload docs -#after_success: -# - | -# if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then -# cargo doc --features "session" --no-deps && -# echo "" > target/doc/index.html && -# git clone https://github.com/davisp/ghp-import.git && -# ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && -# echo "Uploaded documentation" -# fi +after_success: + - | + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then + cargo doc --no-deps && + echo "" > target/doc/index.html && + git clone https://github.com/davisp/ghp-import.git && + ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && + echo "Uploaded documentation" + fi + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + taskset -c 0 cargo tarpaulin --features="ssl" --out Xml + bash <(curl -s https://codecov.io/bash) + echo "Uploaded code coverage" + fi From c14c66d2b00427482f7fd3b3e54af80a257a2641 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 09:52:41 -0700 Subject: [PATCH 1086/1635] add json extractor tests --- .travis.yml | 2 +- src/error.rs | 4 +-- src/responder.rs | 4 +-- src/test.rs | 24 +++++++++++--- src/types/json.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 105 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 32e6c136..9caaac1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: - rust: beta - rust: nightly-2019-03-02 allow_failures: - - rust: nightly + - rust: nightly-2019-03-02 env: global: diff --git a/src/error.rs b/src/error.rs index bf224a22..2231473f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -87,8 +87,8 @@ impl ResponseError for UrlencodedError { /// A set of errors that can occur during parsing json payloads #[derive(Debug, Display, From)] pub enum JsonPayloadError { - /// Payload size is bigger than allowed. (default: 256kB) - #[display(fmt = "Json payload size is bigger than allowed. (default: 256kB)")] + /// Payload size is bigger than allowed. (default: 32kB) + #[display(fmt = "Json payload size is bigger than allowed.")] Overflow, /// Content type error #[display(fmt = "Content type error")] diff --git a/src/responder.rs b/src/responder.rs index 5f98e6e8..871670bd 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -288,7 +288,7 @@ where } #[cfg(test)] -mod tests { +pub(crate) mod tests { use actix_service::Service; use bytes::{Bytes, BytesMut}; @@ -322,7 +322,7 @@ mod tests { } } - trait BodyTest { + pub(crate) trait BodyTest { fn bin_ref(&self) -> &[u8]; fn body(&self) -> &Body; } diff --git a/src/test.rs b/src/test.rs index 13db5977..fe9fb024 100644 --- a/src/test.rs +++ b/src/test.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, Version}; use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{PayloadStream, Request}; +use actix_http::{Extensions, PayloadStream, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; use actix_server_config::ServerConfig; @@ -15,6 +15,7 @@ use cookie::Cookie; use futures::future::{lazy, Future}; use crate::config::{AppConfig, AppConfigInner}; +use crate::data::RouteData; use crate::rmap::ResourceMap; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; use crate::{HttpRequest, HttpResponse}; @@ -157,6 +158,7 @@ pub struct TestRequest { req: HttpTestRequest, rmap: ResourceMap, config: AppConfigInner, + route_data: Extensions, } impl Default for TestRequest { @@ -165,6 +167,7 @@ impl Default for TestRequest { req: HttpTestRequest::default(), rmap: ResourceMap::new(ResourceDef::new("")), config: AppConfigInner::default(), + route_data: Extensions::new(), } } } @@ -177,6 +180,7 @@ impl TestRequest { req: HttpTestRequest::default().uri(path).take(), rmap: ResourceMap::new(ResourceDef::new("")), config: AppConfigInner::default(), + route_data: Extensions::new(), } } @@ -186,6 +190,7 @@ impl TestRequest { req: HttpTestRequest::default().set(hdr).take(), config: AppConfigInner::default(), rmap: ResourceMap::new(ResourceDef::new("")), + route_data: Extensions::new(), } } @@ -199,6 +204,7 @@ impl TestRequest { req: HttpTestRequest::default().header(key, value).take(), config: AppConfigInner::default(), rmap: ResourceMap::new(ResourceDef::new("")), + route_data: Extensions::new(), } } @@ -208,6 +214,7 @@ impl TestRequest { req: HttpTestRequest::default().method(Method::GET).take(), config: AppConfigInner::default(), rmap: ResourceMap::new(ResourceDef::new("")), + route_data: Extensions::new(), } } @@ -217,6 +224,7 @@ impl TestRequest { req: HttpTestRequest::default().method(Method::POST).take(), config: AppConfigInner::default(), rmap: ResourceMap::new(ResourceDef::new("")), + route_data: Extensions::new(), } } @@ -266,12 +274,20 @@ impl TestRequest { self } - /// Set route data - pub fn route_data(self, data: T) -> Self { + /// Set application data. This is equivalent of `App::data()` method + /// for testing purpose. + pub fn app_data(self, data: T) -> Self { self.config.extensions.borrow_mut().insert(data); self } + /// Set route data. This is equivalent of `Route::data()` method + /// for testing purpose. + pub fn route_data(mut self, data: T) -> Self { + self.route_data.insert(RouteData::new(data)); + self + } + #[cfg(test)] /// Set request config pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self { @@ -324,7 +340,7 @@ impl TestRequest { Rc::new(self.rmap), AppConfig::new(self.config), ); - ServiceFromRequest::new(req, None) + ServiceFromRequest::new(req, Some(Rc::new(self.route_data))) } /// Runs the provided future, blocking the current thread until the future diff --git a/src/types/json.rs b/src/types/json.rs index 4fc2748f..9e13d994 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -371,6 +371,11 @@ mod tests { use crate::http::header; use crate::test::{block_on, TestRequest}; + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct MyObject { + name: String, + } + fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { match err { JsonPayloadError::Overflow => match other { @@ -385,9 +390,81 @@ mod tests { } } - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct MyObject { - name: String, + #[test] + fn test_responder() { + let req = TestRequest::default().to_http_request(); + + let j = Json(MyObject { + name: "test".to_string(), + }); + let resp = j.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("application/json") + ); + + use crate::responder::tests::BodyTest; + assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}"); + } + + #[test] + fn test_extract() { + let mut req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .to_from(); + + let s = block_on(Json::::from_request(&mut req)).unwrap(); + assert_eq!(s.name, "test"); + assert_eq!( + s.into_inner(), + MyObject { + name: "test".to_string() + } + ); + + let mut req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .route_data(JsonConfig::default().limit(10)) + .to_from(); + let s = block_on(Json::::from_request(&mut req)); + assert!(format!("{}", s.err().unwrap()) + .contains("Json payload size is bigger than allowed.")); + + let mut req = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .route_data( + JsonConfig::default() + .limit(10) + .error_handler(|_, _| JsonPayloadError::ContentType.into()), + ) + .to_from(); + let s = block_on(Json::::from_request(&mut req)); + assert!(format!("{}", s.err().unwrap()).contains("Content type error")); } #[test] From 9bd0f29ca3396924a607e8a1c5312f79ae157cab Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 10:11:10 -0700 Subject: [PATCH 1087/1635] add tests for error and some responders --- src/error.rs | 31 +++++++++++++++++++++++++++++++ src/responder.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/error.rs b/src/error.rs index 2231473f..fc0f9fdf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -142,3 +142,34 @@ impl ResponseError for ReadlinesError { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_urlencoded_error() { + let resp: HttpResponse = UrlencodedError::Overflow.error_response(); + assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); + let resp: HttpResponse = UrlencodedError::UnknownLength.error_response(); + assert_eq!(resp.status(), StatusCode::LENGTH_REQUIRED); + let resp: HttpResponse = UrlencodedError::ContentType.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[test] + fn test_json_payload_error() { + let resp: HttpResponse = JsonPayloadError::Overflow.error_response(); + assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); + let resp: HttpResponse = JsonPayloadError::ContentType.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[test] + fn test_readlines_error() { + let resp: HttpResponse = ReadlinesError::LimitOverflow.error_response(); + assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); + let resp: HttpResponse = ReadlinesError::EncodingError.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } +} diff --git a/src/responder.rs b/src/responder.rs index 871670bd..50467883 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -380,6 +380,15 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); + let resp: HttpResponse = + block_on((&"test".to_string()).respond_to(&req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + let resp: HttpResponse = block_on(Bytes::from_static(b"test").respond_to(&req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -398,10 +407,32 @@ pub(crate) mod tests { HeaderValue::from_static("application/octet-stream") ); + // InternalError let resp: HttpResponse = error::InternalError::new("err", StatusCode::BAD_REQUEST) .respond_to(&req) .unwrap(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } + + #[test] + fn test_result_responder() { + let req = TestRequest::default().to_http_request(); + + // Result + let resp: HttpResponse = + block_on(Ok::<_, Error>("test".to_string()).respond_to(&req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); + + let res = block_on( + Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) + .respond_to(&req), + ); + assert!(res.is_err()); + } } From 6b66681827aa1f42041fef9c55bd13bc0d80990c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 13:47:20 -0700 Subject: [PATCH 1088/1635] add basic actors integration --- Cargo.toml | 1 + actix-web-actors/Cargo.toml | 28 ++++ actix-web-actors/src/context.rs | 254 ++++++++++++++++++++++++++++++++ actix-web-actors/src/lib.rs | 4 + 4 files changed, 287 insertions(+) create mode 100644 actix-web-actors/Cargo.toml create mode 100644 actix-web-actors/src/context.rs create mode 100644 actix-web-actors/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index ba36dd2c..df06f3a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ ".", "actix-files", "actix-session", + "actix-web-actors", "actix-web-codegen", ] diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml new file mode 100644 index 00000000..698acc1c --- /dev/null +++ b/actix-web-actors/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "actix-web-actors" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Actix actors support for actix web framework." +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://docs.rs/actix-web-actors/" +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +workspace = ".." +edition = "2018" + +[lib] +name = "actix_web_actors" +path = "src/lib.rs" + +[dependencies] +actix-web = { path=".." } +actix = { git = "https://github.com/actix/actix.git" } + +bytes = "0.4" +futures = "0.1" + +[dev-dependencies] +actix-rt = "0.2.0" diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs new file mode 100644 index 00000000..646698ef --- /dev/null +++ b/actix-web-actors/src/context.rs @@ -0,0 +1,254 @@ +use std::collections::VecDeque; + +use actix::dev::{ + AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, +}; +use actix::fut::ActorFuture; +use actix::{ + Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, +}; +use actix_web::error::{Error, ErrorInternalServerError}; +use bytes::Bytes; +use futures::sync::oneshot::Sender; +use futures::{Async, Future, Poll, Stream}; + +/// Execution context for http actors +pub struct HttpContext +where + A: Actor>, +{ + inner: ContextParts, + stream: VecDeque>, +} + +impl ActorContext for HttpContext +where + A: Actor, +{ + fn stop(&mut self) { + self.inner.stop(); + } + fn terminate(&mut self) { + self.inner.terminate() + } + fn state(&self) -> ActorState { + self.inner.state() + } +} + +impl AsyncContext for HttpContext +where + A: Actor, +{ + #[inline] + fn spawn(&mut self, fut: F) -> SpawnHandle + where + F: ActorFuture + 'static, + { + self.inner.spawn(fut) + } + + #[inline] + fn wait(&mut self, fut: F) + where + F: ActorFuture + 'static, + { + self.inner.wait(fut) + } + + #[doc(hidden)] + #[inline] + fn waiting(&self) -> bool { + self.inner.waiting() + || self.inner.state() == ActorState::Stopping + || self.inner.state() == ActorState::Stopped + } + + #[inline] + fn cancel_future(&mut self, handle: SpawnHandle) -> bool { + self.inner.cancel_future(handle) + } + + #[inline] + fn address(&self) -> Addr { + self.inner.address() + } +} + +impl HttpContext +where + A: Actor, +{ + #[inline] + /// Create a new HTTP Context from a request and an actor + pub fn create(actor: A) -> impl Stream { + let mb = Mailbox::default(); + let ctx = HttpContext { + inner: ContextParts::new(mb.sender_producer()), + stream: VecDeque::new(), + }; + HttpContextFut::new(ctx, actor, mb) + } + + /// Create a new HTTP Context + pub fn with_factory(f: F) -> impl Stream + where + F: FnOnce(&mut Self) -> A + 'static, + { + let mb = Mailbox::default(); + let mut ctx = HttpContext { + inner: ContextParts::new(mb.sender_producer()), + stream: VecDeque::new(), + }; + + let act = f(&mut ctx); + HttpContextFut::new(ctx, act, mb) + } +} + +impl HttpContext +where + A: Actor, +{ + /// Write payload + #[inline] + pub fn write(&mut self, data: Bytes) { + self.stream.push_back(Some(data)); + } + + /// Indicate end of streaming payload. Also this method calls `Self::close`. + #[inline] + pub fn write_eof(&mut self) { + self.stream.push_back(None); + } + + /// Handle of the running future + /// + /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. + pub fn handle(&self) -> SpawnHandle { + self.inner.curr_handle() + } +} + +impl AsyncContextParts for HttpContext +where + A: Actor, +{ + fn parts(&mut self) -> &mut ContextParts { + &mut self.inner + } +} + +struct HttpContextFut +where + A: Actor>, +{ + fut: ContextFut>, +} + +impl HttpContextFut +where + A: Actor>, +{ + fn new(ctx: HttpContext, act: A, mailbox: Mailbox) -> Self { + let fut = ContextFut::new(ctx, act, mailbox); + HttpContextFut { fut } + } +} + +impl Stream for HttpContextFut +where + A: Actor>, +{ + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + if self.fut.alive() { + match self.fut.poll() { + Ok(Async::NotReady) | Ok(Async::Ready(())) => (), + Err(_) => return Err(ErrorInternalServerError("error")), + } + } + + // frames + if let Some(data) = self.fut.ctx().stream.pop_front() { + Ok(Async::Ready(data)) + } else if self.fut.alive() { + Ok(Async::NotReady) + } else { + Ok(Async::Ready(None)) + } + } +} + +impl ToEnvelope for HttpContext +where + A: Actor> + Handler, + M: Message + Send + 'static, + M::Result: Send, +{ + fn pack(msg: M, tx: Option>) -> Envelope { + Envelope::new(msg, tx) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use actix::Actor; + use actix_web::dev::HttpMessageBody; + use actix_web::http::StatusCode; + use actix_web::test::{block_on, call_success, init_service, TestRequest}; + use actix_web::{web, App, HttpRequest, HttpResponse}; + use bytes::{Bytes, BytesMut}; + + use super::*; + + struct MyActor { + count: usize, + } + + impl Actor for MyActor { + type Context = HttpContext; + + fn started(&mut self, ctx: &mut Self::Context) { + ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); + } + } + + impl MyActor { + fn write(&mut self, ctx: &mut HttpContext) { + self.count += 1; + if self.count > 3 { + ctx.write_eof() + } else { + ctx.write(Bytes::from(format!("LINE-{}", self.count).as_bytes())); + ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); + } + } + } + + #[test] + fn test_default_resource() { + let mut srv = + init_service(App::new().service(web::resource("/test").to(|| { + HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) + }))); + + let req = TestRequest::with_uri("/test").to_request(); + let mut resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + + let body = block_on(resp.take_body().fold( + BytesMut::new(), + move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, Error>(body) + }, + )) + .unwrap(); + assert_eq!(body.freeze(), Bytes::from_static(b"LINE-1LINE-2LINE-3")); + } +} diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs new file mode 100644 index 00000000..47adf5f0 --- /dev/null +++ b/actix-web-actors/src/lib.rs @@ -0,0 +1,4 @@ +//! Actix actors integration for Actix web framework +mod context; + +pub use self::context::HttpContext; From a07ea00cc4811466a09b8afda7b88e4bc115e663 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 13:55:03 -0700 Subject: [PATCH 1089/1635] add basic test for proc macro --- tests/test_macro.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/test_macro.rs diff --git a/tests/test_macro.rs b/tests/test_macro.rs new file mode 100644 index 00000000..62b5d618 --- /dev/null +++ b/tests/test_macro.rs @@ -0,0 +1,17 @@ +use actix_http::HttpService; +use actix_http_test::TestServer; +use actix_web::{get, App, HttpResponse, Responder}; + +#[get("/test")] +fn test() -> impl Responder { + HttpResponse::Ok() +} + +#[test] +fn test_body() { + let mut srv = TestServer::new(|| HttpService::new(App::new().service(test))); + + let request = srv.get().uri(srv.url("/test")).finish().unwrap(); + let response = srv.send_request(request).unwrap(); + assert!(response.status().is_success()); +} From 88152740c690d42b21f9bb5c90426661e4455885 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 20:20:10 -0700 Subject: [PATCH 1090/1635] move macros tests to codegen crate --- actix-web-codegen/Cargo.toml | 5 +++++ {tests => actix-web-codegen/tests}/test_macro.rs | 0 2 files changed, 5 insertions(+) rename {tests => actix-web-codegen/tests}/test_macro.rs (100%) diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 24ed36b7..d87b71ba 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -13,3 +13,8 @@ proc-macro = true [dependencies] quote = "0.6" syn = { version = "0.15", features = ["full", "parsing"] } + +[dev-dependencies] +actix-web = { path = ".." } +actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } diff --git a/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs similarity index 100% rename from tests/test_macro.rs rename to actix-web-codegen/tests/test_macro.rs From 85c2887b3086c2f207f6be4c42ec83522052fb72 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 21:09:50 -0700 Subject: [PATCH 1091/1635] export ws::hash_key --- src/error.rs | 43 ------------------------------------------- src/ws/mod.rs | 2 +- src/ws/proto.rs | 2 +- 3 files changed, 2 insertions(+), 45 deletions(-) diff --git a/src/error.rs b/src/error.rs index e0a416ef..6bc40133 100644 --- a/src/error.rs +++ b/src/error.rs @@ -981,23 +981,6 @@ mod tests { from!(httparse::Error::Version => ParseError::Version); } - // #[test] - // fn failure_error() { - // const NAME: &str = "RUST_BACKTRACE"; - // let old_tb = env::var(NAME); - // env::set_var(NAME, "0"); - // let error = failure::err_msg("Hello!"); - // let resp: Error = error.into(); - // assert_eq!( - // format!("{:?}", resp), - // "Compat { error: ErrorMessage { msg: \"Hello!\" } }\n\n" - // ); - // match old_tb { - // Ok(x) => env::set_var(NAME, x), - // _ => env::remove_var(NAME), - // } - // } - #[test] fn test_internal_error() { let err = @@ -1006,32 +989,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - // #[test] - // fn test_error_downcasting_direct() { - // #[derive(Debug, Display)] - // #[display(fmt = "demo error")] - // struct DemoError; - - // impl ResponseError for DemoError {} - - // let err: Error = DemoError.into(); - // let err_ref: &DemoError = err.downcast_ref().unwrap(); - // assert_eq!(err_ref.to_string(), "demo error"); - // } - - // #[test] - // fn test_error_downcasting_compat() { - // #[derive(Debug, Display)] - // #[display(fmt = "demo error")] - // struct DemoError; - - // impl ResponseError for DemoError {} - - // let err: Error = failure::Error::from(DemoError).into(); - // let err_ref: &DemoError = err.downcast_ref().unwrap(); - // assert_eq!(err_ref.to_string(), "demo error"); - // } - #[test] fn test_error_helpers() { let r: Response = ErrorBadRequest("err").into(); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 8f9bc83b..3d3f5b92 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -24,7 +24,7 @@ mod transport; pub use self::client::{Client, ClientError, Connect}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; -pub use self::proto::{CloseCode, CloseReason, OpCode}; +pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; pub use self::service::VerifyWebSockets; pub use self::transport::Transport; diff --git a/src/ws/proto.rs b/src/ws/proto.rs index 35fbbe3e..eef87474 100644 --- a/src/ws/proto.rs +++ b/src/ws/proto.rs @@ -209,7 +209,7 @@ impl> From<(CloseCode, T)> for CloseReason { static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // TODO: hash is always same size, we dont need String -pub(crate) fn hash_key(key: &[u8]) -> String { +pub fn hash_key(key: &[u8]) -> String { let mut hasher = sha1::Sha1::new(); hasher.update(key); From f26d4b6a23de57c7fec54476f711599f63906f27 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 21:57:53 -0700 Subject: [PATCH 1092/1635] do not chunk websocket stream --- src/h1/client.rs | 1 + src/h1/codec.rs | 1 + src/h1/dispatcher.rs | 10 +++++----- src/h1/encoder.rs | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/h1/client.rs b/src/h1/client.rs index de4d10e1..85185126 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -211,6 +211,7 @@ impl Encoder for ClientCodec { dst, &mut msg, false, + false, inner.version, length, inner.ctype, diff --git a/src/h1/codec.rs b/src/h1/codec.rs index 23feda50..c66364c0 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -170,6 +170,7 @@ impl Encoder for Codec { dst, &mut res, self.flags.contains(Flags::HEAD), + self.flags.contains(Flags::STREAM), self.version, length, self.ctype, diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8543aa21..82813a52 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -323,17 +323,17 @@ where match msg { Message::Item(mut req) => { match self.framed.get_codec().message_type() { - MessageType::Payload => { + MessageType::Payload | MessageType::Stream => { let (ps, pl) = Payload::create(false); let (req1, _) = req.replace_payload(crate::Payload::H1(pl)); req = req1; self.payload = Some(ps); } - MessageType::Stream => { - self.unhandled = Some(req); - return Ok(updated); - } + //MessageType::Stream => { + // self.unhandled = Some(req); + // return Ok(updated); + //} _ => (), } diff --git a/src/h1/encoder.rs b/src/h1/encoder.rs index 9fe5ba69..712d123e 100644 --- a/src/h1/encoder.rs +++ b/src/h1/encoder.rs @@ -241,6 +241,7 @@ impl MessageEncoder { dst: &mut BytesMut, message: &mut T, head: bool, + stream: bool, version: Version, length: BodyLength, ctype: ConnectionType, @@ -253,7 +254,7 @@ impl MessageEncoder { BodyLength::Sized(len) => TransferEncoding::length(len as u64), BodyLength::Sized64(len) => TransferEncoding::length(len), BodyLength::Stream => { - if message.chunked() { + if message.chunked() && !stream { TransferEncoding::chunked() } else { TransferEncoding::eof() From fd3e351c318c31e98dbd0354afb0cba654b0cdbf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 22:02:03 -0700 Subject: [PATCH 1093/1635] add websockets context --- Cargo.toml | 4 +- actix-web-actors/Cargo.toml | 7 +- actix-web-actors/src/context.rs | 3 +- actix-web-actors/src/lib.rs | 6 + actix-web-actors/src/ws.rs | 408 ++++++++++++++++++++++++++++++ actix-web-actors/tests/test_ws.rs | 67 +++++ src/lib.rs | 2 +- 7 files changed, 490 insertions(+), 7 deletions(-) create mode 100644 actix-web-actors/src/ws.rs create mode 100644 actix-web-actors/tests/test_ws.rs diff --git a/Cargo.toml b/Cargo.toml index df06f3a1..d98c926b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,14 +61,14 @@ ssl = ["openssl", "actix-server/ssl"] # rust-tls = ["rustls", "actix-server/rustls"] [dependencies] -actix-codec = "0.1.0" +actix-codec = "0.1.1" actix-service = "0.3.4" actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.1" actix-web-codegen = { path="actix-web-codegen" } actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-server = "0.4.0" +actix-server = "0.4.1" actix-server-config = "0.1.0" bytes = "0.4" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 698acc1c..db42a1a2 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -20,9 +20,12 @@ path = "src/lib.rs" [dependencies] actix-web = { path=".." } actix = { git = "https://github.com/actix/actix.git" } - +actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-codec = "0.1.1" bytes = "0.4" futures = "0.1" [dev-dependencies] -actix-rt = "0.2.0" +env_logger = "0.6" +actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index 646698ef..da473ff3 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -198,10 +198,9 @@ mod tests { use std::time::Duration; use actix::Actor; - use actix_web::dev::HttpMessageBody; use actix_web::http::StatusCode; use actix_web::test::{block_on, call_success, init_service, TestRequest}; - use actix_web::{web, App, HttpRequest, HttpResponse}; + use actix_web::{web, App, HttpResponse}; use bytes::{Bytes, BytesMut}; use super::*; diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 47adf5f0..0d447865 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,4 +1,10 @@ //! Actix actors integration for Actix web framework mod context; +mod ws; pub use self::context::HttpContext; +pub use self::ws::{ws_handshake, ws_start, WebsocketContext}; + +pub use actix_http::ws::CloseCode as WsCloseCode; +pub use actix_http::ws::ProtocolError as WsProtocolError; +pub use actix_http::ws::{Frame as WsFrame, Message as WsMessage}; diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs new file mode 100644 index 00000000..dca0f46d --- /dev/null +++ b/actix-web-actors/src/ws.rs @@ -0,0 +1,408 @@ +use std::collections::VecDeque; +use std::io; + +use actix::dev::{ + AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, + ToEnvelope, +}; +use actix::fut::ActorFuture; +use actix::{ + Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, + Message as ActixMessage, SpawnHandle, +}; +use actix_codec::{Decoder, Encoder}; +use actix_http::ws::{ + hash_key, CloseReason, Codec, Frame, HandshakeError, Message, ProtocolError, +}; +use actix_web::dev::{Head, HttpResponseBuilder}; +use actix_web::error::{Error, ErrorInternalServerError, PayloadError}; +use actix_web::http::{header, Method, StatusCode}; +use actix_web::{HttpMessage, HttpRequest, HttpResponse}; +use bytes::{Bytes, BytesMut}; +use futures::sync::oneshot::Sender; +use futures::{Async, Future, Poll, Stream}; + +/// Do websocket handshake and start ws actor. +pub fn ws_start( + actor: A, + req: &HttpRequest, + stream: T, +) -> Result +where + A: Actor> + StreamHandler, + T: Stream + 'static, +{ + let mut res = ws_handshake(req)?; + Ok(res.streaming(WebsocketContext::create(actor, stream))) +} + +/// Prepare `WebSocket` handshake response. +/// +/// This function returns handshake `HttpResponse`, ready to send to peer. +/// It does not perform any IO. +/// +// /// `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 ws_handshake(req: &HttpRequest) -> Result { + // WebSocket accepts only GET + if *req.method() != Method::GET { + return Err(HandshakeError::GetMethodRequired); + } + + // Check for "UPGRADE" to websocket header + let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) { + if let Ok(s) = hdr.to_str() { + s.to_lowercase().contains("websocket") + } else { + false + } + } else { + false + }; + if !has_hdr { + return Err(HandshakeError::NoWebsocketUpgrade); + } + + // Upgrade connection + if !req.upgrade() { + return Err(HandshakeError::NoConnectionUpgrade); + } + + // check supported version + if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) { + return Err(HandshakeError::NoVersionHeader); + } + let supported_ver = { + if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) { + hdr == "13" || hdr == "8" || hdr == "7" + } else { + false + } + }; + if !supported_ver { + return Err(HandshakeError::UnsupportedVersion); + } + + // check client handshake for validity + if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) { + return Err(HandshakeError::BadWebsocketKey); + } + let key = { + let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap(); + hash_key(key.as_ref()) + }; + + Ok(HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) + .header(header::CONNECTION, "upgrade") + .header(header::UPGRADE, "websocket") + .header(header::TRANSFER_ENCODING, "chunked") + .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) + .take()) +} + +/// Execution context for `WebSockets` actors +pub struct WebsocketContext +where + A: Actor>, +{ + inner: ContextParts, + messages: VecDeque>, +} + +impl ActorContext for WebsocketContext +where + A: Actor, +{ + fn stop(&mut self) { + self.inner.stop(); + } + + fn terminate(&mut self) { + self.inner.terminate() + } + + fn state(&self) -> ActorState { + self.inner.state() + } +} + +impl AsyncContext for WebsocketContext +where + A: Actor, +{ + fn spawn(&mut self, fut: F) -> SpawnHandle + where + F: ActorFuture + 'static, + { + self.inner.spawn(fut) + } + + fn wait(&mut self, fut: F) + where + F: ActorFuture + 'static, + { + self.inner.wait(fut) + } + + #[doc(hidden)] + #[inline] + fn waiting(&self) -> bool { + self.inner.waiting() + || self.inner.state() == ActorState::Stopping + || self.inner.state() == ActorState::Stopped + } + + fn cancel_future(&mut self, handle: SpawnHandle) -> bool { + self.inner.cancel_future(handle) + } + + #[inline] + fn address(&self) -> Addr { + self.inner.address() + } +} + +impl WebsocketContext +where + A: Actor, +{ + #[inline] + /// Create a new Websocket context from a request and an actor + pub fn create(actor: A, stream: S) -> impl Stream + where + A: StreamHandler, + S: Stream + 'static, + { + let mb = Mailbox::default(); + let mut ctx = WebsocketContext { + inner: ContextParts::new(mb.sender_producer()), + messages: VecDeque::new(), + }; + ctx.add_stream(WsStream::new(stream)); + + WebsocketContextFut::new(ctx, actor, mb) + } + + /// Create a new Websocket context + pub fn with_factory( + stream: S, + f: F, + ) -> impl Stream + where + F: FnOnce(&mut Self) -> A + 'static, + A: StreamHandler, + S: Stream + 'static, + { + let mb = Mailbox::default(); + let mut ctx = WebsocketContext { + inner: ContextParts::new(mb.sender_producer()), + messages: VecDeque::new(), + }; + ctx.add_stream(WsStream::new(stream)); + + let act = f(&mut ctx); + + WebsocketContextFut::new(ctx, act, mb) + } +} + +impl WebsocketContext +where + A: Actor, +{ + /// Write payload + /// + /// This is a low-level function that accepts framed messages that should + /// be created using `Frame::message()`. If you want to send text or binary + /// data you should prefer the `text()` or `binary()` convenience functions + /// that handle the framing for you. + #[inline] + pub fn write_raw(&mut self, msg: Message) { + self.messages.push_back(Some(msg)); + } + + /// Send text frame + #[inline] + pub fn text>(&mut self, text: T) { + self.write_raw(Message::Text(text.into())); + } + + /// Send binary frame + #[inline] + pub fn binary>(&mut self, data: B) { + self.write_raw(Message::Binary(data.into())); + } + + /// Send ping frame + #[inline] + pub fn ping(&mut self, message: &str) { + self.write_raw(Message::Ping(message.to_string())); + } + + /// Send pong frame + #[inline] + pub fn pong(&mut self, message: &str) { + self.write_raw(Message::Pong(message.to_string())); + } + + /// Send close frame + #[inline] + pub fn close(&mut self, reason: Option) { + self.write_raw(Message::Close(reason)); + } + + /// Handle of the running future + /// + /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. + pub fn handle(&self) -> SpawnHandle { + self.inner.curr_handle() + } + + /// Set mailbox capacity + /// + /// By default mailbox capacity is 16 messages. + pub fn set_mailbox_capacity(&mut self, cap: usize) { + self.inner.set_mailbox_capacity(cap) + } +} + +impl AsyncContextParts for WebsocketContext +where + A: Actor, +{ + fn parts(&mut self) -> &mut ContextParts { + &mut self.inner + } +} + +struct WebsocketContextFut +where + A: Actor>, +{ + fut: ContextFut>, + encoder: Codec, + buf: BytesMut, + closed: bool, +} + +impl WebsocketContextFut +where + A: Actor>, +{ + fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox) -> Self { + let fut = ContextFut::new(ctx, act, mailbox); + WebsocketContextFut { + fut, + encoder: Codec::new(), + buf: BytesMut::new(), + closed: false, + } + } +} + +impl Stream for WebsocketContextFut +where + A: Actor>, +{ + type Item = Bytes; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + if self.fut.alive() && self.fut.poll().is_err() { + return Err(ErrorInternalServerError("error")); + } + + // encode messages + while let Some(item) = self.fut.ctx().messages.pop_front() { + if let Some(msg) = item { + self.encoder.encode(msg, &mut self.buf)?; + } else { + self.closed = true; + break; + } + } + + if !self.buf.is_empty() { + Ok(Async::Ready(Some(self.buf.take().freeze()))) + } else if self.fut.alive() && !self.closed { + Ok(Async::NotReady) + } else { + Ok(Async::Ready(None)) + } + } +} + +impl ToEnvelope for WebsocketContext +where + A: Actor> + Handler, + M: ActixMessage + Send + 'static, + M::Result: Send, +{ + fn pack(msg: M, tx: Option>) -> Envelope { + Envelope::new(msg, tx) + } +} + +struct WsStream { + stream: S, + decoder: Codec, + buf: BytesMut, + closed: bool, +} + +impl WsStream +where + S: Stream, +{ + fn new(stream: S) -> Self { + Self { + stream, + decoder: Codec::new(), + buf: BytesMut::new(), + closed: false, + } + } +} + +impl Stream for WsStream +where + S: Stream, +{ + type Item = Frame; + type Error = ProtocolError; + + fn poll(&mut self) -> Poll, Self::Error> { + if !self.closed { + loop { + match self.stream.poll() { + Ok(Async::Ready(Some(chunk))) => { + self.buf.extend_from_slice(&chunk[..]); + } + Ok(Async::Ready(None)) => { + self.closed = true; + break; + } + Ok(Async::NotReady) => break, + Err(e) => { + return Err(ProtocolError::Io(io::Error::new( + io::ErrorKind::Other, + format!("{}", e), + ))); + } + } + } + } + + match self.decoder.decode(&mut self.buf)? { + None => { + if self.closed { + Ok(Async::Ready(None)) + } else { + Ok(Async::NotReady) + } + } + Some(frm) => Ok(Async::Ready(Some(frm))), + } + } +} diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs new file mode 100644 index 00000000..0a214644 --- /dev/null +++ b/actix-web-actors/tests/test_ws.rs @@ -0,0 +1,67 @@ +use actix::prelude::*; +use actix_http::HttpService; +use actix_http_test::TestServer; +use actix_web::{web, App, HttpRequest}; +use actix_web_actors::*; +use bytes::{Bytes, BytesMut}; +use futures::{Sink, Stream}; + +struct Ws; + +impl Actor for Ws { + type Context = WebsocketContext; +} + +impl StreamHandler for Ws { + fn handle(&mut self, msg: WsFrame, ctx: &mut Self::Context) { + match msg { + WsFrame::Ping(msg) => ctx.pong(&msg), + WsFrame::Text(text) => { + ctx.text(String::from_utf8_lossy(&text.unwrap())).to_owned() + } + WsFrame::Binary(bin) => ctx.binary(bin.unwrap()), + WsFrame::Close(reason) => ctx.close(reason), + _ => (), + } + } +} + +#[test] +fn test_simple() { + let mut srv = + TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload<_>| ws_start(Ws, &req, stream), + ))) + }); + + // client service + let framed = srv.ws().unwrap(); + let framed = srv + .block_on(framed.send(WsMessage::Text("text".to_string()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(WsFrame::Text(Some(BytesMut::from("text"))))); + + let framed = srv + .block_on(framed.send(WsMessage::Binary("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!( + item, + Some(WsFrame::Binary(Some(Bytes::from_static(b"text").into()))) + ); + + let framed = srv + .block_on(framed.send(WsMessage::Ping("text".into()))) + .unwrap(); + let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(WsFrame::Pong("text".to_string().into()))); + + let framed = srv + .block_on(framed.send(WsMessage::Close(Some(WsCloseCode::Normal.into())))) + .unwrap(); + + let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); + assert_eq!(item, Some(WsFrame::Close(Some(WsCloseCode::Normal.into())))); +} diff --git a/src/lib.rs b/src/lib.rs index dbf8f743..f59cbc26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ pub mod dev { pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; pub use actix_http::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ - Extensions, Payload, PayloadStream, RequestHead, ResponseHead, + Extensions, Head, Payload, PayloadStream, RequestHead, ResponseHead, }; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; From 6ab76658680b7a1b1f9c4ed01b68b1c00a3140bc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 22:11:50 -0700 Subject: [PATCH 1094/1635] export ws module --- actix-web-actors/src/lib.rs | 7 +---- actix-web-actors/src/ws.rs | 17 ++++++------ actix-web-actors/tests/test_ws.rs | 44 ++++++++++++++++--------------- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 0d447865..5b64d7e0 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,10 +1,5 @@ //! Actix actors integration for Actix web framework mod context; -mod ws; +pub mod ws; pub use self::context::HttpContext; -pub use self::ws::{ws_handshake, ws_start, WebsocketContext}; - -pub use actix_http::ws::CloseCode as WsCloseCode; -pub use actix_http::ws::ProtocolError as WsProtocolError; -pub use actix_http::ws::{Frame as WsFrame, Message as WsMessage}; diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index dca0f46d..b5f5c08c 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -1,3 +1,4 @@ +//! Websocket integration use std::collections::VecDeque; use std::io; @@ -11,9 +12,11 @@ use actix::{ Message as ActixMessage, SpawnHandle, }; use actix_codec::{Decoder, Encoder}; -use actix_http::ws::{ - hash_key, CloseReason, Codec, Frame, HandshakeError, Message, ProtocolError, +use actix_http::ws::hash_key; +pub use actix_http::ws::{ + CloseCode, CloseReason, Codec, Frame, HandshakeError, Message, ProtocolError, }; + use actix_web::dev::{Head, HttpResponseBuilder}; use actix_web::error::{Error, ErrorInternalServerError, PayloadError}; use actix_web::http::{header, Method, StatusCode}; @@ -23,16 +26,12 @@ use futures::sync::oneshot::Sender; use futures::{Async, Future, Poll, Stream}; /// Do websocket handshake and start ws actor. -pub fn ws_start( - actor: A, - req: &HttpRequest, - stream: T, -) -> Result +pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where A: Actor> + StreamHandler, T: Stream + 'static, { - let mut res = ws_handshake(req)?; + let mut res = handshake(req)?; Ok(res.streaming(WebsocketContext::create(actor, stream))) } @@ -44,7 +43,7 @@ where // /// `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 ws_handshake(req: &HttpRequest) -> Result { +pub fn handshake(req: &HttpRequest) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 0a214644..ea9c8d8f 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -9,18 +9,18 @@ use futures::{Sink, Stream}; struct Ws; impl Actor for Ws { - type Context = WebsocketContext; + type Context = ws::WebsocketContext; } -impl StreamHandler for Ws { - fn handle(&mut self, msg: WsFrame, ctx: &mut Self::Context) { +impl StreamHandler for Ws { + fn handle(&mut self, msg: ws::Frame, ctx: &mut Self::Context) { match msg { - WsFrame::Ping(msg) => ctx.pong(&msg), - WsFrame::Text(text) => { + ws::Frame::Ping(msg) => ctx.pong(&msg), + ws::Frame::Text(text) => { ctx.text(String::from_utf8_lossy(&text.unwrap())).to_owned() } - WsFrame::Binary(bin) => ctx.binary(bin.unwrap()), - WsFrame::Close(reason) => ctx.close(reason), + ws::Frame::Binary(bin) => ctx.binary(bin.unwrap()), + ws::Frame::Close(reason) => ctx.close(reason), _ => (), } } @@ -28,40 +28,42 @@ impl StreamHandler for Ws { #[test] fn test_simple() { - let mut srv = - TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").to( - |req: HttpRequest, stream: web::Payload<_>| ws_start(Ws, &req, stream), - ))) - }); + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload<_>| ws::start(Ws, &req, stream), + ))) + }); // client service let framed = srv.ws().unwrap(); let framed = srv - .block_on(framed.send(WsMessage::Text("text".to_string()))) + .block_on(framed.send(ws::Message::Text("text".to_string()))) .unwrap(); let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(WsFrame::Text(Some(BytesMut::from("text"))))); + assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); let framed = srv - .block_on(framed.send(WsMessage::Binary("text".into()))) + .block_on(framed.send(ws::Message::Binary("text".into()))) .unwrap(); let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); assert_eq!( item, - Some(WsFrame::Binary(Some(Bytes::from_static(b"text").into()))) + Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) ); let framed = srv - .block_on(framed.send(WsMessage::Ping("text".into()))) + .block_on(framed.send(ws::Message::Ping("text".into()))) .unwrap(); let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(WsFrame::Pong("text".to_string().into()))); + assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); let framed = srv - .block_on(framed.send(WsMessage::Close(Some(WsCloseCode::Normal.into())))) + .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) .unwrap(); let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(WsFrame::Close(Some(WsCloseCode::Normal.into())))); + assert_eq!( + item, + Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) + ); } From b0343eb22d8a371fb84cdf304e4eb58f94dad700 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 22:31:10 -0700 Subject: [PATCH 1095/1635] simplify ws stream interface --- actix-web-actors/src/ws.rs | 35 ++++++++++++++++++++++++------- actix-web-actors/tests/test_ws.rs | 14 ++++++------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index b5f5c08c..54632627 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -12,9 +12,9 @@ use actix::{ Message as ActixMessage, SpawnHandle, }; use actix_codec::{Decoder, Encoder}; -use actix_http::ws::hash_key; +use actix_http::ws::{hash_key, Codec}; pub use actix_http::ws::{ - CloseCode, CloseReason, Codec, Frame, HandshakeError, Message, ProtocolError, + CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, }; use actix_web::dev::{Head, HttpResponseBuilder}; @@ -28,7 +28,7 @@ use futures::{Async, Future, Poll, Stream}; /// Do websocket handshake and start ws actor. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where - A: Actor> + StreamHandler, + A: Actor> + StreamHandler, T: Stream + 'static, { let mut res = handshake(req)?; @@ -170,7 +170,7 @@ where /// Create a new Websocket context from a request and an actor pub fn create(actor: A, stream: S) -> impl Stream where - A: StreamHandler, + A: StreamHandler, S: Stream + 'static, { let mb = Mailbox::default(); @@ -190,7 +190,7 @@ where ) -> impl Stream where F: FnOnce(&mut Self) -> A + 'static, - A: StreamHandler, + A: StreamHandler, S: Stream + 'static, { let mb = Mailbox::default(); @@ -368,7 +368,7 @@ impl Stream for WsStream where S: Stream, { - type Item = Frame; + type Item = Message; type Error = ProtocolError; fn poll(&mut self) -> Poll, Self::Error> { @@ -401,7 +401,28 @@ where Ok(Async::NotReady) } } - Some(frm) => Ok(Async::Ready(Some(frm))), + Some(frm) => { + let msg = match frm { + Frame::Text(data) => { + if let Some(data) = data { + Message::Text( + std::str::from_utf8(&data) + .map_err(|_| ProtocolError::BadEncoding)? + .to_string(), + ) + } else { + Message::Text(String::new()) + } + } + Frame::Binary(data) => Message::Binary( + data.map(|b| b.freeze()).unwrap_or_else(|| Bytes::new()), + ), + Frame::Ping(s) => Message::Ping(s), + Frame::Pong(s) => Message::Pong(s), + Frame::Close(reason) => Message::Close(reason), + }; + Ok(Async::Ready(Some(msg))) + } } } } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index ea9c8d8f..202d562c 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -12,15 +12,13 @@ impl Actor for Ws { type Context = ws::WebsocketContext; } -impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Frame, ctx: &mut Self::Context) { +impl StreamHandler for Ws { + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { match msg { - ws::Frame::Ping(msg) => ctx.pong(&msg), - ws::Frame::Text(text) => { - ctx.text(String::from_utf8_lossy(&text.unwrap())).to_owned() - } - ws::Frame::Binary(bin) => ctx.binary(bin.unwrap()), - ws::Frame::Close(reason) => ctx.close(reason), + ws::Message::Ping(msg) => ctx.pong(&msg), + ws::Message::Text(text) => ctx.text(text), + ws::Message::Binary(bin) => ctx.binary(bin), + ws::Message::Close(reason) => ctx.close(reason), _ => (), } } From 3301a46264fb5c49456b38d0b275845ec77d2784 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 22:56:13 -0700 Subject: [PATCH 1096/1635] proper connection upgrade check --- src/message.rs | 12 ++++++++++-- src/ws/mod.rs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/message.rs b/src/message.rs index f9dfe973..bf465ee6 100644 --- a/src/message.rs +++ b/src/message.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use std::rc::Rc; use crate::extensions::Extensions; -use crate::http::{HeaderMap, Method, StatusCode, Uri, Version}; +use crate::http::{header, HeaderMap, Method, StatusCode, Uri, Version}; /// Represents various types of connection #[derive(Copy, Clone, PartialEq, Debug)] @@ -33,7 +33,15 @@ pub trait Head: Default + 'static { fn set_connection_type(&mut self, ctype: ConnectionType); fn upgrade(&self) -> bool { - self.connection_type() == ConnectionType::Upgrade + if let Some(hdr) = self.headers().get(header::CONNECTION) { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("upgrade") + } else { + false + } + } else { + false + } } /// Check if keep-alive is enabled diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 3d3f5b92..88fabde9 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -132,7 +132,7 @@ pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> { // Check for "UPGRADE" to websocket header let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) { if let Ok(s) = hdr.to_str() { - s.to_lowercase().contains("websocket") + s.to_ascii_lowercase().contains("websocket") } else { false } From efe3025395ac2202a22639e3ac98f67617af0685 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 17 Mar 2019 22:57:27 -0700 Subject: [PATCH 1097/1635] add handshake test --- actix-web-actors/src/ws.rs | 122 ++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 54632627..a5d26623 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -52,7 +52,7 @@ pub fn handshake(req: &HttpRequest) -> Result Date: Mon, 18 Mar 2019 05:26:12 -0700 Subject: [PATCH 1098/1635] fix response upgrade type --- src/message.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/message.rs b/src/message.rs index bf465ee6..4e46093f 100644 --- a/src/message.rs +++ b/src/message.rs @@ -106,6 +106,18 @@ impl Head for RequestHead { } } + fn upgrade(&self) -> bool { + if let Some(hdr) = self.headers().get(header::CONNECTION) { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("upgrade") + } else { + false + } + } else { + false + } + } + fn pool() -> &'static MessagePool { REQUEST_POOL.with(|p| *p) } @@ -194,6 +206,10 @@ impl Head for ResponseHead { } } + fn upgrade(&self) -> bool { + self.connection_type() == ConnectionType::Upgrade + } + fn pool() -> &'static MessagePool { RESPONSE_POOL.with(|p| *p) } From 8872f3b590266b08b9ada4502b05397863d3e200 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Mar 2019 05:30:18 -0700 Subject: [PATCH 1099/1635] fix ws upgrade --- actix-web-actors/src/ws.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index a5d26623..cef5080c 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -93,8 +93,7 @@ pub fn handshake(req: &HttpRequest) -> Result Date: Mon, 18 Mar 2019 09:44:48 -0700 Subject: [PATCH 1100/1635] handle socket shutdown for h1 connections --- src/client/h2proto.rs | 6 +-- src/config.rs | 10 ++-- src/h1/dispatcher.rs | 112 ++++++++++++++++++++++------------------- src/h2/dispatcher.rs | 2 +- src/payload.rs | 2 +- src/service/service.rs | 2 +- src/ws/client/mod.rs | 24 ++++----- 7 files changed, 81 insertions(+), 77 deletions(-) diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index c05aeddb..bf2d3e1b 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -5,7 +5,7 @@ use bytes::Bytes; use futures::future::{err, Either}; use futures::{Async, Future, Poll}; use h2::{client::SendRequest, SendStream}; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; +use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; use crate::body::{BodyLength, MessageBody}; @@ -45,7 +45,7 @@ where *req.version_mut() = Version::HTTP_2; let mut skip_len = true; - let mut has_date = false; + // let mut has_date = false; // Content length let _ = match length { @@ -72,7 +72,7 @@ where match *key { CONNECTION | TRANSFER_ENCODING => continue, // http2 specific CONTENT_LENGTH if skip_len => continue, - DATE => has_date = true, + // DATE => has_date = true, _ => (), } req.headers_mut().append(key, value.clone()); diff --git a/src/config.rs b/src/config.rs index 3c7df2fe..f7d7f5f9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -85,7 +85,7 @@ impl ServiceConfig { ka_enabled, client_timeout, client_disconnect, - timer: DateService::with(Duration::from_millis(500)), + timer: DateService::new(), })) } @@ -204,14 +204,12 @@ impl fmt::Write for Date { struct DateService(Rc); struct DateServiceInner { - interval: Duration, current: UnsafeCell>, } impl DateServiceInner { - fn new(interval: Duration) -> Self { + fn new() -> Self { DateServiceInner { - interval, current: UnsafeCell::new(None), } } @@ -232,8 +230,8 @@ impl DateServiceInner { } impl DateService { - fn with(resolution: Duration) -> Self { - DateService(Rc::new(DateServiceInner::new(resolution))) + fn new() -> Self { + DateService(Rc::new(DateServiceInner::new())) } fn check_date(&self) { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 82813a52..8e21e9b0 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -7,7 +7,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::Service; use actix_utils::cloneable::CloneableService; use bitflags::bitflags; -use futures::{try_ready, Async, Future, Poll, Sink, Stream}; +use futures::{Async, Future, Poll, Sink, Stream}; use log::{debug, error, trace}; use tokio_timer::Delay; @@ -32,6 +32,7 @@ bitflags! { const POLLED = 0b0000_1000; const SHUTDOWN = 0b0010_0000; const DISCONNECTED = 0b0100_0000; + const DROPPING = 0b1000_0000; } } @@ -56,7 +57,6 @@ where state: State, payload: Option, messages: VecDeque, - unhandled: Option, ka_expire: Instant, ka_timer: Option, @@ -131,7 +131,6 @@ where state: State::None, error: None, messages: VecDeque::new(), - unhandled: None, service, flags, config, @@ -411,8 +410,19 @@ where /// keep-alive timer fn poll_keepalive(&mut self) -> Result<(), DispatchError> { if self.ka_timer.is_none() { - return Ok(()); + // shutdown timeout + if self.flags.contains(Flags::SHUTDOWN) { + if let Some(interval) = self.config.client_disconnect_timer() { + self.ka_timer = Some(Delay::new(interval)); + } else { + self.flags.insert(Flags::DISCONNECTED); + return Ok(()); + } + } else { + return Ok(()); + } } + match self.ka_timer.as_mut().unwrap().poll().map_err(|e| { error!("Timer error {:?}", e); DispatchError::Unknown @@ -436,6 +446,8 @@ where let _ = timer.poll(); } } else { + // no shutdown timeout, drop socket + self.flags.insert(Flags::DISCONNECTED); return Ok(()); } } else { @@ -483,61 +495,55 @@ where #[inline] fn poll(&mut self) -> Poll { - let shutdown = if let Some(ref mut inner) = self.inner { - if inner.flags.contains(Flags::SHUTDOWN) { - inner.poll_keepalive()?; - try_ready!(inner.poll_flush()); - true + let inner = self.inner.as_mut().unwrap(); + + if inner.flags.contains(Flags::SHUTDOWN) { + inner.poll_keepalive()?; + if inner.flags.contains(Flags::DISCONNECTED) { + Ok(Async::Ready(())) } else { - inner.poll_keepalive()?; - inner.poll_request()?; - loop { - inner.poll_response()?; - if let Async::Ready(false) = inner.poll_flush()? { - break; - } - } - - if inner.flags.contains(Flags::DISCONNECTED) { - return Ok(Async::Ready(())); - } - - // keep-alive and stream errors - if inner.state.is_empty() && inner.framed.is_write_buf_empty() { - if let Some(err) = inner.error.take() { - return Err(err); - } - // unhandled request (upgrade or connect) - else if inner.unhandled.is_some() { - false - } - // disconnect if keep-alive is not enabled - else if inner.flags.contains(Flags::STARTED) - && !inner.flags.intersects(Flags::KEEPALIVE) - { - true - } - // disconnect if shutdown - else if inner.flags.contains(Flags::SHUTDOWN) { - true - } else { - return Ok(Async::NotReady); - } - } else { - return Ok(Async::NotReady); + // try_ready!(inner.poll_flush()); + match inner.framed.get_mut().shutdown()? { + Async::Ready(_) => Ok(Async::Ready(())), + Async::NotReady => Ok(Async::NotReady), } } } else { - unreachable!() - }; + inner.poll_keepalive()?; + inner.poll_request()?; + loop { + inner.poll_response()?; + if let Async::Ready(false) = inner.poll_flush()? { + break; + } + } - let mut inner = self.inner.take().unwrap(); + if inner.flags.contains(Flags::DISCONNECTED) { + return Ok(Async::Ready(())); + } - // TODO: shutdown - Ok(Async::Ready(())) - //Ok(Async::Ready(HttpServiceResult::Shutdown( - // inner.framed.into_inner(), - //))) + // keep-alive and stream errors + if inner.state.is_empty() && inner.framed.is_write_buf_empty() { + if let Some(err) = inner.error.take() { + return Err(err); + } + // disconnect if keep-alive is not enabled + else if inner.flags.contains(Flags::STARTED) + && !inner.flags.intersects(Flags::KEEPALIVE) + { + inner.flags.insert(Flags::SHUTDOWN); + self.poll() + } + // disconnect if shutdown + else if inner.flags.contains(Flags::SHUTDOWN) { + self.poll() + } else { + return Ok(Async::NotReady); + } + } else { + return Ok(Async::NotReady); + } + } } } diff --git a/src/h2/dispatcher.rs b/src/h2/dispatcher.rs index cbba34c0..ea63dc2b 100644 --- a/src/h2/dispatcher.rs +++ b/src/h2/dispatcher.rs @@ -56,7 +56,7 @@ where config: ServiceConfig, timeout: Option, ) -> Self { - let keepalive = config.keep_alive_enabled(); + // let keepalive = config.keep_alive_enabled(); // let flags = if keepalive { // Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED // } else { diff --git a/src/payload.rs b/src/payload.rs index 8f96bab9..91e6b5c9 100644 --- a/src/payload.rs +++ b/src/payload.rs @@ -41,7 +41,7 @@ impl From for Payload { impl Payload { /// Takes current payload and replaces it with `None` value - fn take(&mut self) -> Payload { + pub fn take(&mut self) -> Payload { std::mem::replace(self, Payload::None) } } diff --git a/src/service/service.rs b/src/service/service.rs index 3ddf5573..0bc1634d 100644 --- a/src/service/service.rs +++ b/src/service/service.rs @@ -169,7 +169,7 @@ where } fn call(&mut self, req: Self::Request) -> Self::Future { - let (io, params, proto) = req.into_parts(); + let (io, _, proto) = req.into_parts(); match proto { Protocol::Http2 => { let io = Io { diff --git a/src/ws/client/mod.rs b/src/ws/client/mod.rs index 0dbf081c..a5c22196 100644 --- a/src/ws/client/mod.rs +++ b/src/ws/client/mod.rs @@ -25,19 +25,19 @@ impl Protocol { } } - fn is_http(self) -> bool { - match self { - Protocol::Https | Protocol::Http => true, - _ => false, - } - } + // fn is_http(self) -> bool { + // match self { + // Protocol::Https | Protocol::Http => true, + // _ => false, + // } + // } - fn is_secure(self) -> bool { - match self { - Protocol::Https | Protocol::Wss => true, - _ => false, - } - } + // fn is_secure(self) -> bool { + // match self { + // Protocol::Https | Protocol::Wss => true, + // _ => false, + // } + // } fn port(self) -> u16 { match self { From c5c7b244be264154ad9b60335a88722b0139206b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Mar 2019 09:40:20 -0700 Subject: [PATCH 1101/1635] cookie is optional --- .travis.yml | 4 +- Cargo.toml | 24 +++++------ src/client/request.rs | 37 +++++++++++------ src/error.rs | 17 +++++--- src/h1/dispatcher.rs | 3 +- src/httpmessage.rs | 11 ++++- src/lib.rs | 1 + src/response.rs | 89 ++++++++++++++++++++++++++-------------- src/test.rs | 57 +++++++++++++------------ src/ws/client/connect.rs | 2 + 10 files changed, 151 insertions(+), 94 deletions(-) diff --git a/.travis.yml b/.travis.yml index ff881505..02fbd42c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,8 @@ before_cache: | script: - cargo clean -- cargo build --features="ssl" -- cargo test --features="ssl" +- cargo build --all-features +- cargo test --all-features # Upload docs after_success: diff --git a/Cargo.toml b/Cargo.toml index a68489f5..84b974be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,20 +7,20 @@ readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-http.git" -documentation = "https://actix.rs/api/actix-http/stable/actix_http/" +documentation = "https://docs.rs/actix-http/" 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"] edition = "2018" [package.metadata.docs.rs] -features = ["session"] +features = ["ssl", "fail", "cookie"] [badges] travis-ci = { repository = "actix/actix-http", branch = "master" } -appveyor = { repository = "fafhrd91/actix-http-b1qsn" } +# appveyor = { repository = "fafhrd91/actix-http-b1qsn" } codecov = { repository = "actix/actix-http", branch = "master", service = "github" } [lib] @@ -28,13 +28,15 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["fail"] +default = [] # openssl ssl = ["openssl", "actix-connect/ssl"] -# failure integration. it is on by default, it will be off in future versions -# actix itself does not use failure anymore +# cookies integration +cookies = ["cookie"] + +# failure integration. actix does not use failure anymore fail = ["failure"] [dependencies] @@ -49,7 +51,6 @@ backtrace = "0.3" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" -cookie = { version="0.11", features=["percent-encode"] } derive_more = "0.14" encoding = "0.2" futures = "0.1" @@ -76,11 +77,10 @@ tokio-timer = "0.2" tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } -# openssl -openssl = { version="0.10", optional = true } - -# failure is optional +# optional deps +cookie = { version="0.11", features=["percent-encode"], optional = true } failure = { version = "0.1.5", optional = true } +openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.0" diff --git a/src/client/request.rs b/src/client/request.rs index 7c7079fb..26713aa4 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -1,13 +1,12 @@ use std::fmt; -use std::fmt::Write as FmtWrite; use std::io::Write; use actix_service::Service; use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; use futures::future::{err, Either}; use futures::{Future, Stream}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use serde::Serialize; use serde_json; @@ -58,6 +57,7 @@ impl ClientRequest<()> { ClientRequestBuilder { head: Some(RequestHead::default()), err: None, + #[cfg(feature = "cookies")] cookies: None, default_headers: true, } @@ -235,6 +235,7 @@ where pub struct ClientRequestBuilder { head: Option, err: Option, + #[cfg(feature = "cookies")] cookies: Option, default_headers: bool, } @@ -441,6 +442,7 @@ impl ClientRequestBuilder { self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) } + #[cfg(feature = "cookies")] /// Set a cookie /// /// ```rust @@ -560,20 +562,28 @@ impl ClientRequestBuilder { ); } + #[allow(unused_mut)] let mut head = self.head.take().expect("cannot reuse request builder"); - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); + #[cfg(feature = "cookies")] + { + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + use std::fmt::Write; + + // set cookies + if let Some(ref mut jar) = self.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = + percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); } Ok(ClientRequest { head, body }) } @@ -646,6 +656,7 @@ impl ClientRequestBuilder { ClientRequestBuilder { head: self.head.take(), err: self.err.take(), + #[cfg(feature = "cookies")] cookies: self.cookies.take(), default_headers: self.default_headers, } diff --git a/src/error.rs b/src/error.rs index 6bc40133..820071b1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,7 @@ use std::{fmt, io, result}; // use actix::MailboxError; use actix_utils::timeout::TimeoutError; use backtrace::Backtrace; +#[cfg(feature = "cookies")] use cookie; use derive_more::{Display, From}; use futures::Canceled; @@ -19,7 +20,8 @@ use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; use tokio_timer::Error as TimerError; -// re-exports +// re-export for convinience +#[cfg(feature = "cookies")] pub use cookie::ParseError as CookieParseError; use crate::body::Body; @@ -322,6 +324,7 @@ impl ResponseError for PayloadError { } /// Return `BadRequest` for `cookie::ParseError` +#[cfg(feature = "cookies")] impl ResponseError for cookie::ParseError { fn error_response(&self) -> Response { Response::new(StatusCode::BAD_REQUEST) @@ -889,7 +892,6 @@ mod failure_integration { #[cfg(test)] mod tests { use super::*; - use cookie::ParseError as CookieParseError; use http::{Error as HttpError, StatusCode}; use httparse; use std::error::Error as StdError; @@ -900,14 +902,19 @@ mod tests { let resp: Response = ParseError::Incomplete.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = CookieParseError::EmptyName.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + #[cfg(feature = "cookies")] + #[test] + fn test_cookie_parse() { + use cookie::ParseError as CookieParseError; + let resp: Response = CookieParseError::EmptyName.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + #[test] fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8e21e9b0..afeabc82 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -613,13 +613,12 @@ mod tests { let mut sys = actix_rt::System::new("test"); let _ = sys.block_on(lazy(|| { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - let readbuf = BytesMut::new(); let mut h1 = Dispatcher::new( buf, ServiceConfig::default(), CloneableService::new( - (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + (|_| ok::<_, Error>(Response::Ok().finish())).into_service(), ), ); assert!(h1.poll().is_ok()); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 117e10a8..60821d30 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,18 +1,23 @@ use std::cell::{Ref, RefMut}; use std::str; -use cookie::Cookie; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::EncodingRef; use http::{header, HeaderMap}; use mime::Mime; -use crate::error::{ContentTypeError, CookieParseError, ParseError}; +use crate::error::{ContentTypeError, ParseError}; use crate::extensions::Extensions; use crate::header::Header; use crate::payload::Payload; +#[cfg(feature = "cookies")] +use crate::error::CookieParseError; +#[cfg(feature = "cookies")] +use cookie::Cookie; + +#[cfg(feature = "cookies")] struct Cookies(Vec>); /// Trait that implements general purpose operations on http messages @@ -105,6 +110,7 @@ pub trait HttpMessage: Sized { /// Load request cookies. #[inline] + #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); @@ -125,6 +131,7 @@ pub trait HttpMessage: Sized { } /// Return request cookie. + #[cfg(feature = "cookies")] fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { for cookie in cookies.iter() { diff --git a/src/lib.rs b/src/lib.rs index 9a87b77f..85efe5e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,6 +113,7 @@ pub mod http { #[doc(hidden)] pub use http::uri::PathAndQuery; + #[cfg(feature = "cookies")] pub use cookie::{Cookie, CookieBuilder}; /// Various http headers diff --git a/src/response.rs b/src/response.rs index 34ac54ea..5281c2d9 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,6 +3,7 @@ use std::io::Write; use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; @@ -128,6 +129,7 @@ impl Response { /// Get an iterator for the cookies set by this response #[inline] + #[cfg(feature = "cookies")] pub fn cookies(&self) -> CookieIter { CookieIter { iter: self.head.headers.get_all(header::SET_COOKIE).iter(), @@ -136,6 +138,7 @@ impl Response { /// Add a cookie to this response #[inline] + #[cfg(feature = "cookies")] pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { let h = &mut self.head.headers; HeaderValue::from_str(&cookie.to_string()) @@ -148,6 +151,7 @@ impl Response { /// Remove all cookies with the given name from this response. Returns /// the number of cookies removed. #[inline] + #[cfg(feature = "cookies")] pub fn del_cookie(&mut self, name: &str) -> usize { let h = &mut self.head.headers; let vals: Vec = h @@ -267,10 +271,12 @@ impl IntoFuture for Response { } } +#[cfg(feature = "cookies")] pub struct CookieIter<'a> { iter: header::ValueIter<'a, HeaderValue>, } +#[cfg(feature = "cookies")] impl<'a> Iterator for CookieIter<'a> { type Item = Cookie<'a>; @@ -292,6 +298,7 @@ impl<'a> Iterator for CookieIter<'a> { pub struct ResponseBuilder { head: Option>, err: Option, + #[cfg(feature = "cookies")] cookies: Option, } @@ -304,6 +311,7 @@ impl ResponseBuilder { ResponseBuilder { head: Some(head), err: None, + #[cfg(feature = "cookies")] cookies: None, } } @@ -503,6 +511,7 @@ impl ResponseBuilder { /// .finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -530,6 +539,7 @@ impl ResponseBuilder { /// builder.finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { { if self.cookies.is_none() { @@ -582,15 +592,20 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } + #[allow(unused_mut)] let mut response = self.head.take().expect("cannot reuse response builder"); - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => { - let _ = response.headers.append(header::SET_COOKIE, val); - } - Err(e) => return Response::from(Error::from(e)).into_body(), - }; + + #[cfg(feature = "cookies")] + { + if let Some(ref jar) = self.cookies { + for cookie in jar.delta() { + match HeaderValue::from_str(&cookie.to_string()) { + Ok(val) => { + let _ = response.headers.append(header::SET_COOKIE, val); + } + Err(e) => return Response::from(Error::from(e)).into_body(), + }; + } } } @@ -654,6 +669,7 @@ impl ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), + #[cfg(feature = "cookies")] cookies: self.cookies.take(), } } @@ -674,7 +690,9 @@ fn parts<'a>( impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { // If this response has cookies, load them into a jar + #[cfg(feature = "cookies")] let mut jar: Option = None; + #[cfg(feature = "cookies")] for c in res.cookies() { if let Some(ref mut j) = jar { j.add_original(c.into_owned()); @@ -688,6 +706,7 @@ impl From> for ResponseBuilder { ResponseBuilder { head: Some(res.head), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } @@ -697,17 +716,22 @@ impl From> for ResponseBuilder { impl<'a> From<&'a ResponseHead> for ResponseBuilder { fn from(head: &'a ResponseHead) -> ResponseBuilder { // If this response has cookies, load them into a jar + #[cfg(feature = "cookies")] let mut jar: Option = None; - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE).iter(), - }; - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); + + #[cfg(feature = "cookies")] + { + let cookies = CookieIter { + iter: head.headers.get_all(header::SET_COOKIE).iter(), + }; + for c in cookies { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } } } @@ -721,6 +745,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { ResponseBuilder { head: Some(msg), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } @@ -802,14 +827,9 @@ impl From for Response { #[cfg(test)] mod tests { - use time::Duration; - use super::*; use crate::body::Body; - use crate::http; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use crate::httpmessage::HttpMessage; - use crate::test::TestRequest; #[test] fn test_debug() { @@ -822,8 +842,11 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_response_cookies() { - let req = TestRequest::default() + use crate::httpmessage::HttpMessage; + + let req = crate::test::TestRequest::default() .header(COOKIE, "cookie1=value1") .header(COOKIE, "cookie2=value2") .finish(); @@ -831,11 +854,11 @@ mod tests { let resp = Response::Ok() .cookie( - http::Cookie::build("name", "value") + crate::http::Cookie::build("name", "value") .domain("www.rust-lang.org") .path("/test") .http_only(true) - .max_age(Duration::days(1)) + .max_age(time::Duration::days(1)) .finish(), ) .del_cookie(&cookies[0]) @@ -856,16 +879,17 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_update_response_cookies() { let mut r = Response::Ok() - .cookie(http::Cookie::new("original", "val100")) + .cookie(crate::http::Cookie::new("original", "val100")) .finish(); - r.add_cookie(&http::Cookie::new("cookie2", "val200")) + r.add_cookie(&crate::http::Cookie::new("cookie2", "val200")) .unwrap(); - r.add_cookie(&http::Cookie::new("cookie2", "val250")) + r.add_cookie(&crate::http::Cookie::new("cookie2", "val250")) .unwrap(); - r.add_cookie(&http::Cookie::new("cookie3", "val300")) + r.add_cookie(&crate::http::Cookie::new("cookie3", "val300")) .unwrap(); assert_eq!(r.cookies().count(), 4); @@ -1016,11 +1040,14 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_into_builder() { + use crate::httpmessage::HttpMessage; + let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); - resp.add_cookie(&http::Cookie::new("cookie1", "val100")) + resp.add_cookie(&crate::http::Cookie::new("cookie1", "val100")) .unwrap(); let mut builder: ResponseBuilder = resp.into(); diff --git a/src/test.rs b/src/test.rs index c60d2d01..2d4b3d0f 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,12 +1,11 @@ //! Test Various helpers for Actix applications to use during testing. -use std::fmt::Write as FmtWrite; use std::str::FromStr; use bytes::Bytes; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; -use http::header::{self, HeaderName, HeaderValue}; +use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; @@ -46,6 +45,7 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, + #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -57,6 +57,7 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), + #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, })) @@ -126,6 +127,7 @@ impl TestRequest { } /// Set cookie for this request + #[cfg(feature = "cookies")] pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self @@ -145,38 +147,39 @@ impl TestRequest { /// Complete request creation and generate `Request` instance pub fn finish(&mut self) -> Request { - let Inner { - method, - uri, - version, - headers, - payload, - cookies, - } = self.0.take().expect("cannot reuse test request builder");; + let inner = self.0.take().expect("cannot reuse test request builder");; - let mut req = if let Some(pl) = payload { + let mut req = if let Some(pl) = inner.payload { Request::with_payload(pl) } else { Request::with_payload(crate::h1::Payload::empty().into()) }; let head = req.head_mut(); - head.uri = uri; - head.method = method; - head.version = version; - head.headers = headers; + head.uri = inner.uri; + head.method = inner.method; + head.version = inner.version; + head.headers = inner.headers; - let mut cookie = String::new(); - for c in cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - if !cookie.is_empty() { - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); + #[cfg(feature = "cookies")] + { + use std::fmt::Write as FmtWrite; + + use http::header::{self, HeaderValue}; + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + + let mut cookie = String::new(); + for c in inner.cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + if !cookie.is_empty() { + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } } req diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs index 09d02563..5e877a64 100644 --- a/src/ws/client/connect.rs +++ b/src/ws/client/connect.rs @@ -1,6 +1,7 @@ //! Http client request use std::str; +#[cfg(feature = "cookies")] use cookie::Cookie; use http::header::{HeaderName, HeaderValue}; use http::{Error as HttpError, HttpTryFrom}; @@ -50,6 +51,7 @@ impl Connect { self } + #[cfg(feature = "cookies")] /// Set cookie for handshake request pub fn cookie(mut self, cookie: Cookie) -> Self { self.request.cookie(cookie); From 535b407ac0628b71016a9dc181aa2d92104d195d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Mar 2019 10:06:54 -0700 Subject: [PATCH 1102/1635] make cookies optional --- Cargo.toml | 13 ++++++------- src/request.rs | 2 ++ src/test.rs | 2 ++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d98c926b..6920bc09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,10 +34,10 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "rust-tls", "session"] +features = ["ssl", "tls", "rust-tls", "brotli", "flate2-c", "cookies"] [features] -default = ["brotli", "flate2-c", "session"] +default = ["brotli", "flate2-c", "cookies"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -49,7 +49,7 @@ flate2-c = ["flate2/miniz-sys"] flate2-rust = ["flate2/rust_backend"] # sessions feature, session require "ring" crate and c compiler -session = ["cookie/secure"] +cookies = ["cookie", "actix-http/cookies"] # tls tls = ["native-tls", "actix-server/ssl"] @@ -67,12 +67,11 @@ actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.1" actix-web-codegen = { path="actix-web-codegen" } -actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-http = { git = "https://github.com/actix/actix-http.git", features=["fail"] } actix-server = "0.4.1" actix-server-config = "0.1.0" bytes = "0.4" -cookie = { version="0.11", features=["percent-encode"] } derive_more = "0.14" encoding = "0.2" futures = "0.1" @@ -88,8 +87,8 @@ serde_urlencoded = "^0.5.3" time = "0.1" url = { version="1.7", features=["query_encoding"] } -# middlewares -# actix-session = { path="session", optional = true } +# cookies support +cookie = { version="0.11", features=["secure", "percent-encode"], optional = true } # compression brotli2 = { version="^0.3.2", optional = true } diff --git a/src/request.rs b/src/request.rs index 5517302f..1722925f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -251,12 +251,14 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_no_request_cookies() { let req = TestRequest::default().to_http_request(); assert!(req.cookies().unwrap().is_empty()); } #[test] + #[cfg(feature = "cookies")] fn test_request_cookies() { let req = TestRequest::default() .header(header::COOKIE, "cookie1=value1") diff --git a/src/test.rs b/src/test.rs index fe9fb024..ed9cf27c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -11,6 +11,7 @@ use actix_rt::Runtime; use actix_server_config::ServerConfig; use actix_service::{IntoNewService, NewService, Service}; use bytes::Bytes; +#[cfg(feature = "cookies")] use cookie::Cookie; use futures::future::{lazy, Future}; @@ -262,6 +263,7 @@ impl TestRequest { self } + #[cfg(feature = "cookies")] /// Set cookie for this request pub fn cookie(mut self, cookie: Cookie) -> Self { self.req.cookie(cookie); From 60050307bd330a78db2c19878153153950cd86a5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Mar 2019 11:18:31 -0700 Subject: [PATCH 1103/1635] session feature is renamed to cookies --- src/middleware/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 6e55cd67..9b13a20a 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -11,8 +11,5 @@ pub use self::defaultheaders::DefaultHeaders; pub use self::errhandlers::{ErrorHandlerResponse, ErrorHandlers}; pub use self::logger::Logger; -// #[cfg(feature = "session")] -// pub use actix_session as session; - -#[cfg(feature = "session")] +#[cfg(feature = "cookies")] pub mod identity; From 5b06f2bee5663ed51cfb964e25360f7d10390515 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Mar 2019 21:29:16 -0700 Subject: [PATCH 1104/1635] port cors middleware --- README.md | 23 +- src/middleware/cors.rs | 1173 ++++++++++++++---------------- src/middleware/defaultheaders.rs | 7 +- src/middleware/mod.rs | 1 + src/test.rs | 27 +- 5 files changed, 582 insertions(+), 649 deletions(-) diff --git a/README.md b/README.md index c7e195de..ce9efbb7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. -* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols +* Supported *HTTP/1.x* and *HTTP/2.0* protocols * Streaming and pipelining * Keep-alive and slow requests handling * Client/server [WebSockets](https://actix.rs/docs/websockets/) support @@ -13,33 +13,33 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * SSL support with OpenSSL or `native-tls` * Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -* Built on top of [Actix actor framework](https://github.com/actix/actix) +* Supports [Actix actor framework](https://github.com/actix/actix) * Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support. ## Documentation & community resources * [User Guide](https://actix.rs/docs/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/) +* [API Documentation (Releases)](https://docs.rs/actix-web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.31 or later +* Minimum supported Rust version: 1.32 or later ## Example ```rust -extern crate actix_web; -use actix_web::{http, server, App, Path, Responder}; +use actix_web::{web, App, HttpServer, Responder}; -fn index(info: Path<(u32, String)>) -> impl Responder { +fn index(info: web::Path<(u32, String)>) -> impl Responder { format!("Hello {}! id:{}", info.1, info.0) } -fn main() { - server::new( +fn main() -> std::io::Result<()> { + HttpServer::new( || App::new() - .route("/{id}/{name}/index.html", http::Method::GET, index)) - .bind("127.0.0.1:8080").unwrap() + .service(web::resource("/{id}/{name}/index.html") + .route(web::get().to(index))) + .bind("127.0.0.1:8080")? .run(); } ``` @@ -48,7 +48,6 @@ fn main() { * [Basics](https://github.com/actix/examples/tree/master/basics/) * [Stateful](https://github.com/actix/examples/tree/master/state/) -* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) * [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) * [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) * [Tera](https://github.com/actix/examples/tree/master/template_tera/) / diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 80ee5b19..8f33d69b 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -1,45 +1,36 @@ //! Cross-origin resource sharing (CORS) for Actix applications //! //! CORS middleware could be used with application and with resource. -//! First you need to construct CORS middleware instance. -//! -//! To construct a cors: -//! -//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building. -//! 2. Use any of the builder methods to set fields in the backend. -//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the -//! constructed backend. -//! -//! Cors middleware could be used as parameter for `App::middleware()` or -//! `Resource::middleware()` methods. But you have to use -//! `Cors::for_app()` method to support *preflight* OPTIONS request. -//! +//! Cors middleware could be used as parameter for `App::middleware()`, +//! `Resource::middleware()` or `Scope::middleware()` methods. //! //! # Example //! //! ```rust -//! # extern crate actix_web; //! use actix_web::middleware::cors::Cors; -//! use actix_web::{http, App, HttpRequest, HttpResponse}; +//! use actix_web::{http, web, App, HttpRequest, HttpResponse, HttpServer}; //! -//! fn index(mut req: HttpRequest) -> &'static str { +//! fn index(req: HttpRequest) -> &'static str { //! "Hello world" //! } //! -//! fn main() { -//! let app = App::new().configure(|app| { -//! Cors::for_app(app) // <- Construct CORS middleware builder -//! .allowed_origin("https://www.rust-lang.org/") -//! .allowed_methods(vec!["GET", "POST"]) -//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) -//! .allowed_header(http::header::CONTENT_TYPE) -//! .max_age(3600) -//! .resource("/index.html", |r| { -//! r.method(http::Method::GET).f(|_| HttpResponse::Ok()); -//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); -//! }) -//! .register() -//! }); +//! fn main() -> std::io::Result<()> { +//! HttpServer::new(|| App::new() +//! .middleware( +//! Cors::new() // <- Construct CORS middleware builder +//! .allowed_origin("https://www.rust-lang.org/") +//! .allowed_methods(vec!["GET", "POST"]) +//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) +//! .allowed_header(http::header::CONTENT_TYPE) +//! .max_age(3600)) +//! .service( +//! web::resource("/index.html") +//! .route(web::get().to(index)) +//! .route(web::head().to(|| HttpResponse::MethodNotAllowed())) +//! )) +//! .bind("127.0.0.1:8080")?; +//! +//! Ok(()) //! } //! ``` //! In this example custom *CORS* middleware get registered for "/index.html" @@ -50,68 +41,67 @@ use std::collections::HashSet; use std::iter::FromIterator; use std::rc::Rc; -use http::header::{self, HeaderName, HeaderValue}; -use http::{self, HttpTryFrom, Method, StatusCode, Uri}; +use actix_service::{IntoTransform, Service, Transform}; +use derive_more::Display; +use futures::future::{ok, Either, Future, FutureResult}; +use futures::Poll; -use application::App; -use error::{ResponseError, Result}; -use httpmessage::HttpMessage; -use httprequest::HttpRequest; -use httpresponse::HttpResponse; -use middleware::{Middleware, Response, Started}; -use resource::Resource; -use router::ResourceDef; -use server::Request; +use crate::dev::{Head, RequestHead}; +use crate::error::{ResponseError, Result}; +use crate::http::header::{self, HeaderName, HeaderValue}; +use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri}; +use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{HttpMessage, HttpResponse}; /// A set of errors that can occur during processing CORS -#[derive(Debug, Fail)] +#[derive(Debug, Display)] pub enum CorsError { /// The HTTP request header `Origin` is required but was not provided - #[fail( - display = "The HTTP request header `Origin` is required but was not provided" + #[display( + fmt = "The HTTP request header `Origin` is required but was not provided" )] MissingOrigin, /// The HTTP request header `Origin` could not be parsed correctly. - #[fail(display = "The HTTP request header `Origin` could not be parsed correctly.")] + #[display(fmt = "The HTTP request header `Origin` could not be parsed correctly.")] BadOrigin, /// The request header `Access-Control-Request-Method` is required but is /// missing - #[fail( - display = "The request header `Access-Control-Request-Method` is required but is missing" + #[display( + fmt = "The request header `Access-Control-Request-Method` is required but is missing" )] MissingRequestMethod, /// The request header `Access-Control-Request-Method` has an invalid value - #[fail( - display = "The request header `Access-Control-Request-Method` has an invalid value" + #[display( + fmt = "The request header `Access-Control-Request-Method` has an invalid value" )] BadRequestMethod, /// The request header `Access-Control-Request-Headers` has an invalid /// value - #[fail( - display = "The request header `Access-Control-Request-Headers` has an invalid value" + #[display( + fmt = "The request header `Access-Control-Request-Headers` has an invalid value" )] BadRequestHeaders, /// The request header `Access-Control-Request-Headers` is required but is /// missing. - #[fail( - display = "The request header `Access-Control-Request-Headers` is required but is + #[display( + fmt = "The request header `Access-Control-Request-Headers` is required but is missing" )] MissingRequestHeaders, /// Origin is not allowed to make this request - #[fail(display = "Origin is not allowed to make this request")] + #[display(fmt = "Origin is not allowed to make this request")] OriginNotAllowed, /// Requested method is not allowed - #[fail(display = "Requested method is not allowed")] + #[display(fmt = "Requested method is not allowed")] MethodNotAllowed, /// One or more headers requested are not allowed - #[fail(display = "One or more headers requested are not allowed")] + #[display(fmt = "One or more headers requested are not allowed")] HeadersNotAllowed, } impl ResponseError for CorsError { fn error_response(&self) -> HttpResponse { - HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self)) + HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self).into()) } } @@ -156,327 +146,6 @@ impl AllOrSome { } } -/// `Middleware` for Cross-origin resource sharing support -/// -/// The Cors struct contains the settings for CORS requests to be validated and -/// for responses to be generated. -#[derive(Clone)] -pub struct Cors { - inner: Rc, -} - -struct Inner { - methods: HashSet, - origins: AllOrSome>, - origins_str: Option, - headers: AllOrSome>, - expose_hdrs: Option, - max_age: Option, - preflight: bool, - send_wildcard: bool, - supports_credentials: bool, - vary_header: bool, -} - -impl Default for Cors { - fn default() -> Cors { - let inner = Inner { - origins: AllOrSome::default(), - origins_str: None, - methods: HashSet::from_iter( - vec![ - Method::GET, - Method::HEAD, - Method::POST, - Method::OPTIONS, - Method::PUT, - Method::PATCH, - Method::DELETE, - ].into_iter(), - ), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }; - Cors { - inner: Rc::new(inner), - } - } -} - -impl Cors { - /// Build a new CORS middleware instance - pub fn build() -> CorsBuilder<()> { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: None, - } - } - - /// Create CorsBuilder for a specified application. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .resource("/resource", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn for_app(app: App) -> CorsBuilder { - CorsBuilder { - cors: Some(Inner { - origins: AllOrSome::All, - origins_str: None, - methods: HashSet::new(), - headers: AllOrSome::All, - expose_hdrs: None, - max_age: None, - preflight: true, - send_wildcard: false, - supports_credentials: false, - vary_header: true, - }), - methods: false, - error: None, - expose_hdrs: HashSet::new(), - resources: Vec::new(), - app: Some(app), - } - } - - /// This method register cors middleware with resource and - /// adds route for *OPTIONS* preflight requests. - /// - /// It is possible to register *Cors* middleware with - /// `Resource::middleware()` method, but in that case *Cors* - /// middleware wont be able to handle *OPTIONS* requests. - pub fn register(self, resource: &mut Resource) { - resource - .method(Method::OPTIONS) - .h(|_: &_| HttpResponse::Ok()); - resource.middleware(self); - } - - fn validate_origin(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ORIGIN) { - if let Ok(origin) = hdr.to_str() { - return match self.inner.origins { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_origins) => allowed_origins - .get(origin) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::OriginNotAllowed), - }; - } - Err(CorsError::BadOrigin) - } else { - return match self.inner.origins { - AllOrSome::All => Ok(()), - _ => Err(CorsError::MissingOrigin), - }; - } - } - - fn access_control_allow_origin(&self, req: &Request) -> Option { - match self.inner.origins { - AllOrSome::All => { - if self.inner.send_wildcard { - Some(HeaderValue::from_static("*")) - } else if let Some(origin) = req.headers().get(header::ORIGIN) { - Some(origin.clone()) - } else { - None - } - } - AllOrSome::Some(ref origins) => { - if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| { - match o.to_str() { - Ok(os) => origins.contains(os), - _ => false - } - }) { - Some(origin.clone()) - } else { - Some(self.inner.origins_str.as_ref().unwrap().clone()) - } - } - } - } - - fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { - if let Ok(meth) = hdr.to_str() { - if let Ok(method) = Method::try_from(meth) { - return self - .inner - .methods - .get(&method) - .and_then(|_| Some(())) - .ok_or_else(|| CorsError::MethodNotAllowed); - } - } - Err(CorsError::BadRequestMethod) - } else { - Err(CorsError::MissingRequestMethod) - } - } - - fn validate_allowed_headers(&self, req: &Request) -> Result<(), CorsError> { - match self.inner.headers { - AllOrSome::All => Ok(()), - AllOrSome::Some(ref allowed_headers) => { - if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - if let Ok(headers) = hdr.to_str() { - let mut hdrs = HashSet::new(); - for hdr in headers.split(',') { - match HeaderName::try_from(hdr.trim()) { - Ok(hdr) => hdrs.insert(hdr), - Err(_) => return Err(CorsError::BadRequestHeaders), - }; - } - - if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { - return Err(CorsError::HeadersNotAllowed); - } - return Ok(()); - } - Err(CorsError::BadRequestHeaders) - } else { - Err(CorsError::MissingRequestHeaders) - } - } - } - } -} - -impl Middleware for Cors { - fn start(&self, req: &HttpRequest) -> Result { - if self.inner.preflight && Method::OPTIONS == *req.method() { - self.validate_origin(req)?; - self.validate_allowed_method(&req)?; - self.validate_allowed_headers(&req)?; - - // allowed headers - let headers = if let Some(headers) = self.inner.headers.as_ref() { - Some( - HeaderValue::try_from( - &headers - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).unwrap(), - ) - } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) - { - Some(hdr.clone()) - } else { - None - }; - - Ok(Started::Response( - HttpResponse::Ok() - .if_some(self.inner.max_age.as_ref(), |max_age, resp| { - let _ = resp.header( - header::ACCESS_CONTROL_MAX_AGE, - format!("{}", max_age).as_str(), - ); - }).if_some(headers, |headers, resp| { - let _ = - resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); - }).if_some(self.access_control_allow_origin(&req), |origin, resp| { - let _ = - resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin); - }).if_true(self.inner.supports_credentials, |resp| { - resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); - }).header( - header::ACCESS_CONTROL_ALLOW_METHODS, - &self - .inner - .methods - .iter() - .fold(String::new(), |s, v| s + "," + v.as_str()) - .as_str()[1..], - ).finish(), - )) - } else { - // Only check requests with a origin header. - if req.headers().contains_key(header::ORIGIN) { - self.validate_origin(req)?; - } - - Ok(Started::Done) - } - } - - fn response( - &self, req: &HttpRequest, mut resp: HttpResponse, - ) -> Result { - - if let Some(origin) = self.access_control_allow_origin(req) { - resp.headers_mut() - .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); - }; - - if let Some(ref expose) = self.inner.expose_hdrs { - resp.headers_mut().insert( - header::ACCESS_CONTROL_EXPOSE_HEADERS, - HeaderValue::try_from(expose.as_str()).unwrap(), - ); - } - if self.inner.supports_credentials { - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_CREDENTIALS, - HeaderValue::from_static("true"), - ); - } - if self.inner.vary_header { - let value = if let Some(hdr) = resp.headers_mut().get(header::VARY) { - let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); - val.extend(hdr.as_bytes()); - val.extend(b", Origin"); - HeaderValue::try_from(&val[..]).unwrap() - } else { - HeaderValue::from_static("Origin") - }; - resp.headers_mut().insert(header::VARY, value); - } - Ok(Response::Done(resp)) - } -} - /// Structure that follows the builder pattern for building `Cors` middleware /// structs. /// @@ -490,40 +159,77 @@ impl Middleware for Cors { /// # Example /// /// ```rust -/// # extern crate http; -/// # extern crate actix_web; +/// use actix_web::http::header; /// use actix_web::middleware::cors; -/// use http::header; /// /// # fn main() { -/// let cors = cors::Cors::build() +/// let cors = cors::Cors::new() /// .allowed_origin("https://www.rust-lang.org/") /// .allowed_methods(vec!["GET", "POST"]) /// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) /// .allowed_header(header::CONTENT_TYPE) -/// .max_age(3600) -/// .finish(); +/// .max_age(3600); /// # } /// ``` -pub struct CorsBuilder { +pub struct Cors { cors: Option, methods: bool, error: Option, expose_hdrs: HashSet, - resources: Vec>, - app: Option>, } -fn cors<'a>( - parts: &'a mut Option, err: &Option, -) -> Option<&'a mut Inner> { - if err.is_some() { - return None; +impl Cors { + /// Build a new CORS middleware instance + pub fn new() -> Cors { + Cors { + cors: Some(Inner { + origins: AllOrSome::All, + origins_str: None, + methods: HashSet::new(), + headers: AllOrSome::All, + expose_hdrs: None, + max_age: None, + preflight: true, + send_wildcard: false, + supports_credentials: false, + vary_header: true, + }), + methods: false, + error: None, + expose_hdrs: HashSet::new(), + } + } + + /// Build a new CORS default middleware + pub fn default() -> CorsFactory { + let inner = Inner { + origins: AllOrSome::default(), + origins_str: None, + methods: HashSet::from_iter( + vec![ + Method::GET, + Method::HEAD, + Method::POST, + Method::OPTIONS, + Method::PUT, + Method::PATCH, + Method::DELETE, + ] + .into_iter(), + ), + headers: AllOrSome::All, + expose_hdrs: None, + max_age: None, + preflight: true, + send_wildcard: false, + supports_credentials: false, + vary_header: true, + }; + CorsFactory { + inner: Rc::new(inner), + } } - parts.as_mut() -} -impl CorsBuilder { /// Add an origin that are allowed to make requests. /// Will be verified against the `Origin` request header. /// @@ -541,7 +247,7 @@ impl CorsBuilder { /// Defaults to `All`. /// /// Builder panics if supplied origin is not valid uri. - pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder { + pub fn allowed_origin(mut self, origin: &str) -> Cors { if let Some(cors) = cors(&mut self.cors, &self.error) { match Uri::try_from(origin) { Ok(_) => { @@ -567,7 +273,7 @@ impl CorsBuilder { /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]` - pub fn allowed_methods(&mut self, methods: U) -> &mut CorsBuilder + pub fn allowed_methods(mut self, methods: U) -> Cors where U: IntoIterator, Method: HttpTryFrom, @@ -590,7 +296,7 @@ impl CorsBuilder { } /// Set an allowed header - pub fn allowed_header(&mut self, header: H) -> &mut CorsBuilder + pub fn allowed_header(mut self, header: H) -> Cors where HeaderName: HttpTryFrom, { @@ -621,7 +327,7 @@ impl CorsBuilder { /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// Defaults to `All`. - pub fn allowed_headers(&mut self, headers: U) -> &mut CorsBuilder + pub fn allowed_headers(mut self, headers: U) -> Cors where U: IntoIterator, HeaderName: HttpTryFrom, @@ -655,7 +361,7 @@ impl CorsBuilder { /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model). /// /// This defaults to an empty set. - pub fn expose_headers(&mut self, headers: U) -> &mut CorsBuilder + pub fn expose_headers(mut self, headers: U) -> Cors where U: IntoIterator, HeaderName: HttpTryFrom, @@ -678,7 +384,7 @@ impl CorsBuilder { /// This value is set as the `Access-Control-Max-Age` header. /// /// This defaults to `None` (unset). - pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder { + pub fn max_age(mut self, max_age: usize) -> Cors { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.max_age = Some(max_age) } @@ -700,7 +406,7 @@ impl CorsBuilder { /// CredentialsWithWildcardOrigin` error during actix launch or runtime. /// /// Defaults to `false`. - pub fn send_wildcard(&mut self) -> &mut CorsBuilder { + pub fn send_wildcard(mut self) -> Cors { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.send_wildcard = true } @@ -720,7 +426,7 @@ impl CorsBuilder { /// /// Builder panics if credentials are allowed, but the Origin is set to "*". /// This is not allowed by W3C - pub fn supports_credentials(&mut self) -> &mut CorsBuilder { + pub fn supports_credentials(mut self) -> Cors { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.supports_credentials = true } @@ -738,7 +444,7 @@ impl CorsBuilder { /// caches that the CORS headers are dynamic, and cannot be cached. /// /// By default `vary` header support is enabled. - pub fn disable_vary_header(&mut self) -> &mut CorsBuilder { + pub fn disable_vary_header(mut self) -> Cors { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.vary_header = false } @@ -751,57 +457,32 @@ impl CorsBuilder { /// This is useful application level middleware. /// /// By default *preflight* support is enabled. - pub fn disable_preflight(&mut self) -> &mut CorsBuilder { + pub fn disable_preflight(mut self) -> Cors { if let Some(cors) = cors(&mut self.cors, &self.error) { cors.preflight = false } self } +} - /// Configure resource for a specific path. - /// - /// This is similar to a `App::resource()` method. Except, cors middleware - /// get registered for the resource. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::middleware::cors::Cors; - /// use actix_web::{http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().configure( - /// |app| { - /// Cors::for_app(app) // <- Construct CORS builder - /// .allowed_origin("https://www.rust-lang.org/") - /// .allowed_methods(vec!["GET", "POST"]) - /// .allowed_header(http::header::CONTENT_TYPE) - /// .max_age(3600) - /// .resource("/resource1", |r| { // register resource - /// r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - /// }) - /// .resource("/resource2", |r| { // register another resource - /// r.method(http::Method::HEAD) - /// .f(|_| HttpResponse::MethodNotAllowed()); - /// }) - /// .register() - /// }, // construct CORS and return application instance - /// ); - /// } - /// ``` - pub fn resource(&mut self, path: &str, f: F) -> &mut CorsBuilder - where - F: FnOnce(&mut Resource) -> R + 'static, - { - // add resource handler - let mut resource = Resource::new(ResourceDef::new(path)); - f(&mut resource); - - self.resources.push(resource); - self +fn cors<'a>( + parts: &'a mut Option, + err: &Option, +) -> Option<&'a mut Inner> { + if err.is_some() { + return None; } + parts.as_mut() +} - fn construct(&mut self) -> Cors { - if !self.methods { +impl IntoTransform for Cors +where + S: Service, Response = ServiceResponse> + 'static, + P: 'static, + B: 'static, +{ + fn into_transform(self) -> CorsFactory { + let mut slf = if !self.methods { self.allowed_methods(vec![ Method::GET, Method::HEAD, @@ -810,14 +491,16 @@ impl CorsBuilder { Method::PUT, Method::PATCH, Method::DELETE, - ]); - } + ]) + } else { + self + }; - if let Some(e) = self.error.take() { + if let Some(e) = slf.error.take() { panic!("{}", e); } - let mut cors = self.cors.take().expect("cannot reuse CorsBuilder"); + let mut cors = slf.cors.take().expect("cannot reuse CorsBuilder"); if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() { panic!("Credentials are allowed, but the Origin is set to \"*\""); @@ -830,152 +513,383 @@ impl CorsBuilder { cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap()); } - if !self.expose_hdrs.is_empty() { + if !slf.expose_hdrs.is_empty() { cors.expose_hdrs = Some( - self.expose_hdrs + slf.expose_hdrs .iter() .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..] .to_owned(), ); } - Cors { + + CorsFactory { inner: Rc::new(cors), } } +} - /// Finishes building and returns the built `Cors` instance. - /// - /// This method panics in case of any configuration error. - pub fn finish(&mut self) -> Cors { - if !self.resources.is_empty() { - panic!( - "CorsBuilder::resource() was used, - to construct CORS `.register(app)` method should be used" - ); +/// `Middleware` for Cross-origin resource sharing support +/// +/// The Cors struct contains the settings for CORS requests to be validated and +/// for responses to be generated. +pub struct CorsFactory { + inner: Rc, +} + +impl Transform for CorsFactory +where + S: Service, Response = ServiceResponse>, + S::Future: 'static, + S::Error: 'static, + P: 'static, + B: 'static, +{ + type Request = ServiceRequest

    ; + type Response = ServiceResponse; + type Error = S::Error; + type InitError = (); + type Transform = CorsMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(CorsMiddleware { + service, + inner: self.inner.clone(), + }) + } +} + +/// `Middleware` for Cross-origin resource sharing support +/// +/// The Cors struct contains the settings for CORS requests to be validated and +/// for responses to be generated. +#[derive(Clone)] +pub struct CorsMiddleware { + service: S, + inner: Rc, +} + +struct Inner { + methods: HashSet, + origins: AllOrSome>, + origins_str: Option, + headers: AllOrSome>, + expose_hdrs: Option, + max_age: Option, + preflight: bool, + send_wildcard: bool, + supports_credentials: bool, + vary_header: bool, +} + +impl Inner { + fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> { + if let Some(hdr) = req.headers().get(header::ORIGIN) { + if let Ok(origin) = hdr.to_str() { + return match self.origins { + AllOrSome::All => Ok(()), + AllOrSome::Some(ref allowed_origins) => allowed_origins + .get(origin) + .and_then(|_| Some(())) + .ok_or_else(|| CorsError::OriginNotAllowed), + }; + } + Err(CorsError::BadOrigin) + } else { + return match self.origins { + AllOrSome::All => Ok(()), + _ => Err(CorsError::MissingOrigin), + }; } - self.construct() } - /// Finishes building Cors middleware and register middleware for - /// application - /// - /// This method panics in case of any configuration error or if non of - /// resources are registered. - pub fn register(&mut self) -> App { - if self.resources.is_empty() { - panic!("No resources are registered."); + fn access_control_allow_origin(&self, req: &RequestHead) -> Option { + match self.origins { + AllOrSome::All => { + if self.send_wildcard { + Some(HeaderValue::from_static("*")) + } else if let Some(origin) = req.headers().get(header::ORIGIN) { + Some(origin.clone()) + } else { + None + } + } + AllOrSome::Some(ref origins) => { + if let Some(origin) = + req.headers() + .get(header::ORIGIN) + .filter(|o| match o.to_str() { + Ok(os) => origins.contains(os), + _ => false, + }) + { + Some(origin.clone()) + } else { + Some(self.origins_str.as_ref().unwrap().clone()) + } + } } + } - let cors = self.construct(); - let mut app = self - .app - .take() - .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); - - // register resources - for mut resource in self.resources.drain(..) { - cors.clone().register(&mut resource); - app.register_resource(resource); + fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> { + if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { + if let Ok(meth) = hdr.to_str() { + if let Ok(method) = Method::try_from(meth) { + return self + .methods + .get(&method) + .and_then(|_| Some(())) + .ok_or_else(|| CorsError::MethodNotAllowed); + } + } + Err(CorsError::BadRequestMethod) + } else { + Err(CorsError::MissingRequestMethod) } + } - app + fn validate_allowed_headers(&self, req: &RequestHead) -> Result<(), CorsError> { + match self.headers { + AllOrSome::All => Ok(()), + AllOrSome::Some(ref allowed_headers) => { + if let Some(hdr) = + req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + { + if let Ok(headers) = hdr.to_str() { + let mut hdrs = HashSet::new(); + for hdr in headers.split(',') { + match HeaderName::try_from(hdr.trim()) { + Ok(hdr) => hdrs.insert(hdr), + Err(_) => return Err(CorsError::BadRequestHeaders), + }; + } + + if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { + return Err(CorsError::HeadersNotAllowed); + } + return Ok(()); + } + Err(CorsError::BadRequestHeaders) + } else { + Err(CorsError::MissingRequestHeaders) + } + } + } + } +} + +impl Service for CorsMiddleware +where + S: Service, Response = ServiceResponse>, + S::Future: 'static, + S::Error: 'static, + P: 'static, + B: 'static, +{ + type Request = ServiceRequest

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

    ) -> Self::Future { + if self.inner.preflight && Method::OPTIONS == *req.method() { + if let Err(e) = self + .inner + .validate_origin(&req) + .and_then(|_| self.inner.validate_allowed_method(&req)) + .and_then(|_| self.inner.validate_allowed_headers(&req)) + { + return Either::A(ok(req.error_response(e))); + } + + // allowed headers + let headers = if let Some(headers) = self.inner.headers.as_ref() { + Some( + HeaderValue::try_from( + &headers + .iter() + .fold(String::new(), |s, v| s + "," + v.as_str()) + .as_str()[1..], + ) + .unwrap(), + ) + } else if let Some(hdr) = + req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + { + Some(hdr.clone()) + } else { + None + }; + + let res = HttpResponse::Ok() + .if_some(self.inner.max_age.as_ref(), |max_age, resp| { + let _ = resp.header( + header::ACCESS_CONTROL_MAX_AGE, + format!("{}", max_age).as_str(), + ); + }) + .if_some(headers, |headers, resp| { + let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers); + }) + .if_some( + self.inner.access_control_allow_origin(&req), + |origin, resp| { + let _ = resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin); + }, + ) + .if_true(self.inner.supports_credentials, |resp| { + resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); + }) + .header( + header::ACCESS_CONTROL_ALLOW_METHODS, + &self + .inner + .methods + .iter() + .fold(String::new(), |s, v| s + "," + v.as_str()) + .as_str()[1..], + ) + .finish() + .into_body(); + + Either::A(ok(req.into_response(res))) + } else if req.headers().contains_key(header::ORIGIN) { + // Only check requests with a origin header. + if let Err(e) = self.inner.validate_origin(&req) { + return Either::A(ok(req.error_response(e))); + } + + let inner = self.inner.clone(); + + Either::B(Either::B(Box::new(self.service.call(req).and_then( + move |mut res| { + if let Some(origin) = + inner.access_control_allow_origin(&res.request()) + { + res.headers_mut() + .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); + }; + + if let Some(ref expose) = inner.expose_hdrs { + res.headers_mut().insert( + header::ACCESS_CONTROL_EXPOSE_HEADERS, + HeaderValue::try_from(expose.as_str()).unwrap(), + ); + } + if inner.supports_credentials { + res.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_CREDENTIALS, + HeaderValue::from_static("true"), + ); + } + if inner.vary_header { + let value = + if let Some(hdr) = res.headers_mut().get(header::VARY) { + let mut val: Vec = + Vec::with_capacity(hdr.as_bytes().len() + 8); + val.extend(hdr.as_bytes()); + val.extend(b", Origin"); + HeaderValue::try_from(&val[..]).unwrap() + } else { + HeaderValue::from_static("Origin") + }; + res.headers_mut().insert(header::VARY, value); + } + Ok(res) + }, + )))) + } else { + Either::B(Either::A(self.service.call(req))) + } } } #[cfg(test)] mod tests { - use super::*; - use test::{self, TestRequest}; + use actix_service::{FnService, Transform}; - impl Started { - fn is_done(&self) -> bool { - match *self { - Started::Done => true, - _ => false, - } - } - fn response(self) -> HttpResponse { - match self { - Started::Response(resp) => resp, - _ => panic!(), - } - } - } - impl Response { - fn response(self) -> HttpResponse { - match self { - Response::Done(resp) => resp, - _ => panic!(), - } + use super::*; + use crate::dev::PayloadStream; + use crate::test::{self, block_on, TestRequest}; + + impl Cors { + fn finish(self, srv: S) -> CorsMiddleware + where + S: Service, Response = ServiceResponse> + + 'static, + S::Future: 'static, + S::Error: 'static, + P: 'static, + B: 'static, + { + block_on( + IntoTransform::::into_transform(self).new_transform(srv), + ) + .unwrap() } } #[test] #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] fn cors_validates_illegal_allow_credentials() { - Cors::build() + let _cors = Cors::new() .supports_credentials() .send_wildcard() - .finish(); - } - - #[test] - #[should_panic(expected = "No resources are registered")] - fn no_resource() { - Cors::build() - .supports_credentials() - .send_wildcard() - .register(); - } - - #[test] - #[should_panic(expected = "Cors::for_app(app)")] - fn no_resource2() { - Cors::build() - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register(); + .finish(test::ok_service()); } #[test] fn validate_origin_allows_all_origins() { - let cors = Cors::default(); - let req = TestRequest::with_header("Origin", "https://www.example.com").finish(); + let mut cors = Cors::new().finish(test::ok_service()); + let req = + TestRequest::with_header("Origin", "https://www.example.com").to_service(); - assert!(cors.start(&req).ok().unwrap().is_done()) + let resp = test::call_success(&mut cors, req); + assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_preflight() { - let mut cors = Cors::build() + let mut cors = Cors::new() .send_wildcard() .max_age(3600) .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_header(header::CONTENT_TYPE) - .finish(); + .finish(test::ok_service()); let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) - .finish(); + .to_service(); - assert!(cors.start(&req).is_err()); + assert!(cors.inner.validate_allowed_method(&req).is_err()); + assert!(cors.inner.validate_allowed_headers(&req).is_err()); let req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") .method(Method::OPTIONS) - .finish(); + .to_service(); - assert!(cors.start(&req).is_err()); + assert!(cors.inner.validate_allowed_method(&req).is_err()); + assert!(cors.inner.validate_allowed_headers(&req).is_err()); let req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") .header( header::ACCESS_CONTROL_REQUEST_HEADERS, "AUTHORIZATION,ACCEPT", - ).method(Method::OPTIONS) - .finish(); + ) + .method(Method::OPTIONS) + .to_service(); - let resp = cors.start(&req).unwrap().response(); + let resp = test::call_success(&mut cors, req); assert_eq!( &b"*"[..], resp.headers() @@ -990,16 +904,39 @@ mod tests { .unwrap() .as_bytes() ); - //assert_eq!( - // &b"authorization,accept,content-type"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_HEADERS).unwrap(). - // as_bytes()); assert_eq!( - // &b"POST,GET,OPTIONS"[..], - // resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap(). - // as_bytes()); + let hdr = resp + .headers() + .get(header::ACCESS_CONTROL_ALLOW_HEADERS) + .unwrap() + .to_str() + .unwrap(); + assert!(hdr.contains("authorization")); + assert!(hdr.contains("accept")); + assert!(hdr.contains("content-type")); + + let methods = resp + .headers() + .get(header::ACCESS_CONTROL_ALLOW_METHODS) + .unwrap() + .to_str() + .unwrap(); + assert!(methods.contains("POST")); + assert!(methods.contains("GET")); + assert!(methods.contains("OPTIONS")); Rc::get_mut(&mut cors.inner).unwrap().preflight = false; - assert!(cors.start(&req).unwrap().is_done()); + + let req = TestRequest::with_header("Origin", "https://www.example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") + .header( + header::ACCESS_CONTROL_REQUEST_HEADERS, + "AUTHORIZATION,ACCEPT", + ) + .method(Method::OPTIONS) + .to_service(); + + let resp = test::call_success(&mut cors, req); + assert_eq!(resp.status(), StatusCode::OK); } // #[test] @@ -1015,46 +952,47 @@ mod tests { #[test] #[should_panic(expected = "OriginNotAllowed")] fn test_validate_not_allowed_origin() { - let cors = Cors::build() + let cors = Cors::new() .allowed_origin("https://www.example.com") - .finish(); + .finish(test::ok_service()); let req = TestRequest::with_header("Origin", "https://www.unknown.com") .method(Method::GET) - .finish(); - cors.start(&req).unwrap(); + .to_service(); + cors.inner.validate_origin(&req).unwrap(); + cors.inner.validate_allowed_method(&req).unwrap(); + cors.inner.validate_allowed_headers(&req).unwrap(); } #[test] fn test_validate_origin() { - let cors = Cors::build() + let mut cors = Cors::new() .allowed_origin("https://www.example.com") - .finish(); + .finish(test::ok_service()); let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::GET) - .finish(); + .to_service(); - assert!(cors.start(&req).unwrap().is_done()); + let resp = test::call_success(&mut cors, req); + assert_eq!(resp.status(), StatusCode::OK); } #[test] fn test_no_origin_response() { - let cors = Cors::build().finish(); + let mut cors = Cors::new().disable_preflight().finish(test::ok_service()); - let req = TestRequest::default().method(Method::GET).finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); - assert!( - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_none() - ); + let req = TestRequest::default().method(Method::GET).to_service(); + let resp = test::call_success(&mut cors, req); + assert!(resp + .headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .is_none()); let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) - .finish(); - let resp = cors.response(&req, resp).unwrap().response(); + .to_service(); + let resp = test::call_success(&mut cors, req); assert_eq!( &b"https://www.example.com"[..], resp.headers() @@ -1067,7 +1005,7 @@ mod tests { #[test] fn test_response() { let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let cors = Cors::build() + let mut cors = Cors::new() .send_wildcard() .disable_preflight() .max_age(3600) @@ -1075,14 +1013,13 @@ mod tests { .allowed_headers(exposed_headers.clone()) .expose_headers(exposed_headers.clone()) .allowed_header(header::CONTENT_TYPE) - .finish(); + .finish(test::ok_service()); let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) - .finish(); + .to_service(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); + let resp = test::call_success(&mut cors, req); assert_eq!( &b"*"[..], resp.headers() @@ -1111,21 +1048,40 @@ mod tests { } } - let resp: HttpResponse = - HttpResponse::Ok().header(header::VARY, "Accept").finish(); - let resp = cors.response(&req, resp).unwrap().response(); + let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; + let mut cors = Cors::new() + .send_wildcard() + .disable_preflight() + .max_age(3600) + .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) + .allowed_headers(exposed_headers.clone()) + .expose_headers(exposed_headers.clone()) + .allowed_header(header::CONTENT_TYPE) + .finish(FnService::new(move |req: ServiceRequest| { + req.into_response( + HttpResponse::Ok().header(header::VARY, "Accept").finish(), + ) + })); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .to_service(); + let resp = test::call_success(&mut cors, req); assert_eq!( &b"Accept, Origin"[..], resp.headers().get(header::VARY).unwrap().as_bytes() ); - let cors = Cors::build() + let mut cors = Cors::new() .disable_vary_header() .allowed_origin("https://www.example.com") .allowed_origin("https://www.google.com") - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); - let resp = cors.response(&req, resp).unwrap().response(); + .finish(test::ok_service()); + + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") + .to_service(); + let resp = test::call_success(&mut cors, req); let origins_str = resp .headers() @@ -1134,61 +1090,22 @@ mod tests { .to_str() .unwrap(); - assert_eq!( - "https://www.example.com", - origins_str - ); - } - - #[test] - fn cors_resource() { - let mut srv = test::TestServer::with_factory(|| { - App::new().configure(|app| { - Cors::for_app(app) - .allowed_origin("https://www.example.com") - .resource("/test", |r| r.f(|_| HttpResponse::Ok())) - .register() - }) - }); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example2.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - - let request = srv - .get() - .uri(srv.url("/test")) - .header("ORIGIN", "https://www.example.com") - .finish() - .unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert_eq!(response.status(), StatusCode::OK); + assert_eq!("https://www.example.com", origins_str); } #[test] fn test_multiple_origins() { - let cors = Cors::build() + let mut cors = Cors::new() .allowed_origin("https://example.com") .allowed_origin("https://example.org") .allowed_methods(vec![Method::GET]) - .finish(); - + .finish(test::ok_service()); let req = TestRequest::with_header("Origin", "https://example.com") .method(Method::GET) - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); + .to_service(); - let resp = cors.response(&req, resp).unwrap().response(); + let resp = test::call_success(&mut cors, req); assert_eq!( &b"https://example.com"[..], resp.headers() @@ -1199,10 +1116,9 @@ mod tests { let req = TestRequest::with_header("Origin", "https://example.org") .method(Method::GET) - .finish(); - let resp: HttpResponse = HttpResponse::Ok().into(); + .to_service(); - let resp = cors.response(&req, resp).unwrap().response(); + let resp = test::call_success(&mut cors, req); assert_eq!( &b"https://example.org"[..], resp.headers() @@ -1214,19 +1130,18 @@ mod tests { #[test] fn test_multiple_origins_preflight() { - let cors = Cors::build() + let mut cors = Cors::new() .allowed_origin("https://example.com") .allowed_origin("https://example.org") .allowed_methods(vec![Method::GET]) - .finish(); - + .finish(test::ok_service()); let req = TestRequest::with_header("Origin", "https://example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") .method(Method::OPTIONS) - .finish(); + .to_service(); - let resp = cors.start(&req).ok().unwrap().response(); + let resp = test::call_success(&mut cors, req); assert_eq!( &b"https://example.com"[..], resp.headers() @@ -1238,9 +1153,9 @@ mod tests { let req = TestRequest::with_header("Origin", "https://example.org") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") .method(Method::OPTIONS) - .finish(); + .to_service(); - let resp = cors.start(&req).ok().unwrap().response(); + let resp = test::call_success(&mut cors, req); assert_eq!( &b"https://example.org"[..], resp.headers() diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index bca2cf6e..4d879cda 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -154,18 +154,15 @@ mod tests { use super::*; use crate::dev::ServiceRequest; use crate::http::header::CONTENT_TYPE; - use crate::test::{block_on, TestRequest}; + use crate::test::{block_on, ok_service, TestRequest}; use crate::HttpResponse; #[test] fn test_default_headers() { - let srv = FnService::new(|req: ServiceRequest<_>| { - req.into_response(HttpResponse::Ok().finish()) - }); let mut mw = block_on( DefaultHeaders::new() .header(CONTENT_TYPE, "0001") - .new_transform(srv), + .new_transform(ok_service()), ) .unwrap(); diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 9b13a20a..b997cca2 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -3,6 +3,7 @@ mod compress; #[cfg(any(feature = "brotli", feature = "flate2"))] pub use self::compress::Compress; +pub mod cors; mod defaultheaders; mod errhandlers; mod logger; diff --git a/src/test.rs b/src/test.rs index ed9cf27c..7e79659a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -3,13 +3,13 @@ use std::cell::RefCell; use std::rc::Rc; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; -use actix_http::http::{HttpTryFrom, Method, Version}; +use actix_http::http::{HttpTryFrom, Method, StatusCode, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{Extensions, PayloadStream, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; use actix_server_config::ServerConfig; -use actix_service::{IntoNewService, NewService, Service}; +use actix_service::{FnService, IntoNewService, NewService, Service}; use bytes::Bytes; #[cfg(feature = "cookies")] use cookie::Cookie; @@ -17,9 +17,10 @@ use futures::future::{lazy, Future}; use crate::config::{AppConfig, AppConfigInner}; use crate::data::RouteData; +use crate::dev::Body; use crate::rmap::ResourceMap; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; -use crate::{HttpRequest, HttpResponse}; +use crate::{Error, HttpRequest, HttpResponse}; thread_local! { static RT: RefCell = { @@ -55,6 +56,26 @@ where RT.with(move |rt| rt.borrow_mut().block_on(lazy(f))) } +pub fn ok_service() -> impl Service< + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, +> { + default_service(StatusCode::OK) +} + +pub fn default_service( + status_code: StatusCode, +) -> impl Service< + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, +> { + FnService::new(move |req: ServiceRequest| { + req.into_response(HttpResponse::build(status_code).finish()) + }) +} + /// This method accepts application builder instance, and constructs /// service. /// From 548f6f89bf95bc2e2475074c33221768a1d1515e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Mar 2019 21:39:02 -0700 Subject: [PATCH 1105/1635] allow to get app data via HttpRequest --- src/request.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/request.rs b/src/request.rs index 1722925f..33b63acd 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,6 +8,7 @@ use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; use crate::config::AppConfig; +use crate::data::Data; use crate::error::UrlGenerationError; use crate::extract::FromRequest; use crate::info::ConnectionInfo; @@ -100,6 +101,16 @@ impl HttpRequest { &self.config } + /// Get an application data stored with `App::data()` method during + /// application configuration. + pub fn app_data(&self) -> Option> { + if let Some(st) = self.config.extensions().get::>() { + Some(st.clone()) + } else { + None + } + } + /// Generate url for named resource /// /// ```rust @@ -239,8 +250,9 @@ impl fmt::Debug for HttpRequest { mod tests { use super::*; use crate::dev::{ResourceDef, ResourceMap}; - use crate::http::header; - use crate::test::TestRequest; + use crate::http::{header, StatusCode}; + use crate::test::{call_success, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; #[test] fn test_debug() { @@ -356,4 +368,35 @@ mod tests { "https://youtube.com/watch/oHg5SJYRHA0" ); } + + #[test] + fn test_app_data() { + let mut srv = init_service(App::new().data(10usize).service( + web::resource("/").to(|req: HttpRequest| { + if req.app_data::().is_some() { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )); + + let req = TestRequest::default().to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + + let mut srv = init_service(App::new().data(10u32).service( + web::resource("/").to(|req: HttpRequest| { + if req.app_data::().is_some() { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )); + + let req = TestRequest::default().to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } } From bc01d39d4d5485e62edbf2cb94570cde63b7ed54 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 23 Mar 2019 22:03:40 -0700 Subject: [PATCH 1106/1635] add error response test for cors --- src/middleware/cors.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 8f33d69b..9ba09256 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -871,6 +871,8 @@ mod tests { assert!(cors.inner.validate_allowed_method(&req).is_err()); assert!(cors.inner.validate_allowed_headers(&req).is_err()); + let resp = test::call_success(&mut cors, req); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") From 307b2e5b0e2732a600ca6d34df18a2559ded0c03 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 11:29:35 -0700 Subject: [PATCH 1107/1635] fix compress features --- .travis.yml | 6 +++--- src/app.rs | 2 +- src/middleware/compress.rs | 29 ++++++++++++++++------------- src/middleware/mod.rs | 1 + 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9caaac1b..061c8b8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,13 +35,13 @@ before_script: script: - cargo clean - - cargo test --all -- --nocapture + - cargo test --all-features --all -- --nocapture # Upload docs after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --no-deps && + cargo doc --all-features --no-deps && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && @@ -49,7 +49,7 @@ after_success: fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then - taskset -c 0 cargo tarpaulin --out Xml --all + taskset -c 0 cargo tarpaulin --out Xml --all --all-features bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi diff --git a/src/app.rs b/src/app.rs index c4f2e33b..991288dd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -361,7 +361,7 @@ where } } - /// Default resource to be used if no matching route could be found. + /// Default resource to be used if no matching resource could be found. pub fn default_resource(mut self, f: F) -> Self where F: FnOnce(Resource

    ) -> Resource, diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index b3880a53..3c4718fe 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -1,3 +1,4 @@ +/// `Middleware` for compressing response body. use std::io::Write; use std::marker::PhantomData; use std::str::FromStr; @@ -17,15 +18,17 @@ use log::trace; #[cfg(feature = "brotli")] use brotli2::write::BrotliEncoder; -#[cfg(feature = "flate2")] +#[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] use flate2::write::{GzEncoder, ZlibEncoder}; use crate::service::{ServiceRequest, ServiceResponse}; #[derive(Debug, Clone)] +/// `Middleware` for compressing response body. pub struct Compress(ContentEncoding); impl Compress { + /// Create new `Compress` middleware with default encoding. pub fn new(encoding: ContentEncoding) -> Self { Compress(encoding) } @@ -283,9 +286,9 @@ impl io::Write for Writer { } pub(crate) enum ContentEncoder { - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] Deflate(ZlibEncoder), - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] Gzip(GzEncoder), #[cfg(feature = "brotli")] Br(BrotliEncoder), @@ -296,9 +299,9 @@ impl fmt::Debug for ContentEncoder { match *self { #[cfg(feature = "brotli")] ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), } } @@ -307,12 +310,12 @@ impl fmt::Debug for ContentEncoder { impl ContentEncoder { fn encoder(encoding: ContentEncoding) -> Option { match encoding { - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( Writer::new(), flate2::Compression::fast(), ))), - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( Writer::new(), flate2::Compression::fast(), @@ -330,9 +333,9 @@ impl ContentEncoder { match *self { #[cfg(feature = "brotli")] ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), } } @@ -344,12 +347,12 @@ impl ContentEncoder { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoder::Gzip(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoder::Deflate(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), @@ -367,7 +370,7 @@ impl ContentEncoder { Err(err) } }, - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), Err(err) => { @@ -375,7 +378,7 @@ impl ContentEncoder { Err(err) } }, - #[cfg(feature = "flate2")] + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), Err(err) => { diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index b997cca2..e984b1d8 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,3 +1,4 @@ +//! Middlewares #[cfg(any(feature = "brotli", feature = "flate2"))] mod compress; #[cfg(any(feature = "brotli", feature = "flate2"))] From ede32c8b3fbb29c5ea096c6f5d36ec1d0b46f2e8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 11:32:30 -0700 Subject: [PATCH 1108/1635] export errhandlers module --- src/middleware/errhandlers.rs | 2 ++ src/middleware/mod.rs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 7a79aae1..41bdb384 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -1,3 +1,4 @@ +/// Custom handlers service for responses. use std::rc::Rc; use actix_service::{Service, Transform}; @@ -106,6 +107,7 @@ where } } +#[doc(hidden)] pub struct ErrorHandlersMiddleware { service: S, handlers: Rc>>>, diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index e984b1d8..998b5905 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -6,11 +6,10 @@ pub use self::compress::Compress; pub mod cors; mod defaultheaders; -mod errhandlers; +pub mod errhandlers; mod logger; pub use self::defaultheaders::DefaultHeaders; -pub use self::errhandlers::{ErrorHandlerResponse, ErrorHandlers}; pub use self::logger::Logger; #[cfg(feature = "cookies")] From 913155d34ccb88f558dd933098bf3c6f7daef21a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 11:47:23 -0700 Subject: [PATCH 1109/1635] update doc strings --- src/lib.rs | 77 +++++++++++++++++++++++++++++++++++ src/middleware/errhandlers.rs | 2 +- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f59cbc26..926ca6d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,80 @@ +//! Actix web is a small, pragmatic, and extremely fast web framework +//! for Rust. +//! +//! ```rust +//! use actix_web::{web, App, Responder, HttpServer}; +//! # use std::thread; +//! +//! fn index(info: web::Path<(String, u32)>) -> impl Responder { +//! format!("Hello {}! id:{}", info.0, info.1) +//! } +//! +//! fn main() -> std::io::Result<()> { +//! # thread::spawn(|| { +//! HttpServer::new(|| App::new().service( +//! web::resource("/{name}/{id}/index.html").to(index)) +//! ) +//! .bind("127.0.0.1:8080")? +//! .run() +//! # }); +//! # Ok(()) +//! } +//! ``` +//! +//! ## Documentation & community resources +//! +//! Besides the API documentation (which you are currently looking +//! at!), several other resources are available: +//! +//! * [User Guide](https://actix.rs/docs/) +//! * [Chat on gitter](https://gitter.im/actix/actix) +//! * [GitHub repository](https://github.com/actix/actix-web) +//! * [Cargo package](https://crates.io/crates/actix-web) +//! +//! To get started navigating the API documentation you may want to +//! consider looking at the following pages: +//! +//! * [App](struct.App.html): This struct represents an actix-web +//! application and is used to configure routes and other common +//! settings. +//! +//! * [HttpServer](struct.HttpServer.html): This struct +//! represents an HTTP server instance and is used to instantiate and +//! configure servers. +//! +//! * [HttpRequest](struct.HttpRequest.html) and +//! [HttpResponse](struct.HttpResponse.html): These structs +//! represent HTTP requests and responses and expose various methods +//! for inspecting, creating and otherwise utilizing them. +//! +//! ## Features +//! +//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols +//! * Streaming and pipelining +//! * Keep-alive and slow requests handling +//! * `WebSockets` server/client +//! * Transparent content compression/decompression (br, gzip, deflate) +//! * Configurable request routing +//! * Multipart streams +//! * SSL support with OpenSSL or `native-tls` +//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) +//! * Supports [Actix actor framework](https://github.com/actix/actix) +//! * Supported Rust version: 1.32 or later +//! +//! ## Package feature +//! +//! * `tls` - enables ssl support via `native-tls` crate +//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` +//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` +//! * `cookies` - enables cookies support, includes `ring` crate as +//! dependency +//! * `brotli` - enables `brotli` compression support, requires `c` +//! compiler +//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires +//! `c` compiler +//! * `flate2-rust` - experimental rust based implementation for +//! `gzip`, `deflate` compression. +//! #![allow(clippy::type_complexity, clippy::new_without_default)] mod app; diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 41bdb384..78a09bc0 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -29,7 +29,7 @@ type ErrorHandler = Fn(ServiceResponse) -> Result> /// ## Example /// /// ```rust -/// use actix_web::middleware::{ErrorHandlers, ErrorHandlerResponse}; +/// use actix_web::middleware::errhandlers::{ErrorHandlers, ErrorHandlerResponse}; /// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result}; /// /// fn render_500(mut res: dev::ServiceResponse) -> Result> { From c1e8d8363c4453fd771855373d3877ec8d8b978b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 11:49:26 -0700 Subject: [PATCH 1110/1635] fix errhandlers doc string --- src/middleware/errhandlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 78a09bc0..ca9b1cd5 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -1,4 +1,4 @@ -/// Custom handlers service for responses. +//! Custom handlers service for responses. use std::rc::Rc; use actix_service::{Service, Transform}; From 9932a342ef9f6fcde4fffb6c9264ce01e9fb289e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 11:59:35 -0700 Subject: [PATCH 1111/1635] export Scope --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 926ca6d8..79e1ba34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,7 @@ pub use crate::request::HttpRequest; pub use crate::resource::Resource; pub use crate::responder::{Either, Responder}; pub use crate::route::Route; +pub use crate::scope::Scope; pub use crate::server::HttpServer; pub mod dev { From ffb3324129fa84595e37fb11a5162116498e88c0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 16:15:34 -0700 Subject: [PATCH 1112/1635] do not use default resource from app, return 405 if no matching route found --- src/resource.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/src/resource.rs b/src/resource.rs index 46b3e2a8..29ff0785 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -39,6 +39,10 @@ type HttpNewService

    = /// web::resource("/") /// .route(web::get().to(|| HttpResponse::Ok()))); /// } +/// ``` +/// +/// If no matching route could be found, *405* response code get returned. +/// Default behavior could be overriden with `default_resource()` method. pub struct Resource> { endpoint: T, rdef: String, @@ -261,6 +265,8 @@ where } /// Default resource to be used if no matching route could be found. + /// By default *405* response get returned. Resource does not use + /// default handler from `App` or `Scope`. pub fn default_resource(mut self, f: F) -> Self where F: FnOnce(Resource

    ) -> R, @@ -291,9 +297,6 @@ where > + 'static, { fn register(mut self, config: &mut ServiceConfig

    ) { - if self.default.borrow().is_none() { - *self.default.borrow_mut() = Some(config.default_service()); - } let guards = if self.guards.is_empty() { None } else { @@ -454,7 +457,7 @@ impl

    Service for ResourceService

    { let req = req.into_request(); Either::B(Either::B(ok(ServiceResponse::new( req, - Response::NotFound().finish(), + Response::MethodNotAllowed().finish(), )))) } } @@ -483,3 +486,48 @@ impl NewService for ResourceEndpoint

    { self.factory.borrow_mut().as_mut().unwrap().new_service(&()) } } + +#[cfg(test)] +mod tests { + use crate::http::{Method, StatusCode}; + use crate::test::{call_success, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[test] + fn test_default_resource() { + let mut srv = init_service( + App::new() + .service( + web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), + ) + .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let mut srv = init_service( + App::new().service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + ), + ); + + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } +} From b95e99a09e5318651a3f8588a8f8090a760f1261 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 16:17:59 -0700 Subject: [PATCH 1113/1635] update changes --- CHANGES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ccbb8f07..e44f5dc3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ # Changes -## [1.0.0] - 2019-10-x +## [1.0.0-alpha.1] - 2019-03-x + +### Changed + +* Return 405 response if no matching route found within resource #538 From ed322c175ee5d245b5b5e88e03d2bf97a8ab2a6d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 16:28:16 -0700 Subject: [PATCH 1114/1635] update tests --- src/route.rs | 2 +- src/scope.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/route.rs b/src/route.rs index 626b0951..1f1aed47 100644 --- a/src/route.rs +++ b/src/route.rs @@ -442,6 +442,6 @@ mod tests { .method(Method::HEAD) .to_request(); let resp = call_success(&mut srv, req); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } } diff --git a/src/scope.rs b/src/scope.rs index bf3261f2..4a894450 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -598,7 +598,7 @@ mod tests { .method(Method::POST) .to_request(); let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } #[test] From e37e81af0b80e9a90ae49950b6e45cac998340dd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 17:00:59 -0700 Subject: [PATCH 1115/1635] simplify Payload extractor --- actix-files/Cargo.toml | 8 -------- actix-files/src/config.rs | 31 +++++++++++++---------------- actix-files/src/named.rs | 29 +++++++++++---------------- actix-session/Cargo.toml | 10 +--------- actix-web-actors/tests/test_ws.rs | 11 ++++++----- src/types/payload.rs | 33 +++++++++++++++++-------------- 6 files changed, 51 insertions(+), 71 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c0f38b9a..aa93ac22 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -32,12 +32,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-rt = "0.1.0" -#actix-server = { version="0.2", features=["ssl"] } actix-web = { path="..", features=["ssl"] } -actix-server = { git = "https://github.com/actix/actix-net.git", features=["ssl"] } -actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } -actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } -rand = "0.6" -env_logger = "0.6" -serde_derive = "1.0" diff --git a/actix-files/src/config.rs b/actix-files/src/config.rs index da72da20..7ad65ae7 100644 --- a/actix-files/src/config.rs +++ b/actix-files/src/config.rs @@ -1,5 +1,4 @@ -use actix_http::http::header::DispositionType; -use actix_web::http::Method; +use actix_web::http::{header::DispositionType, Method}; use mime; /// Describes `StaticFiles` configiration @@ -11,11 +10,9 @@ use mime; /// /// ## Example /// -/// ```rust,ignore -/// extern crate mime; -/// extern crate actix_web; +/// ```rust /// use actix_web::http::header::DispositionType; -/// use actix_web::fs::{StaticFileConfig, NamedFile}; +/// use actix_files::{StaticFileConfig, NamedFile}; /// /// #[derive(Default)] /// struct MyConfig; @@ -29,10 +26,10 @@ use mime; /// let file = NamedFile::open_with_config("foo.txt", MyConfig); /// ``` pub trait StaticFileConfig: Default { - ///Describes mapping for mime type to content disposition header + /// Describes mapping for mime type to content disposition header /// - ///By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. - ///Others are mapped to Attachment + /// By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. + /// Others are mapped to Attachment fn content_disposition_map(typ: mime::Name) -> DispositionType { match typ { mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, @@ -40,30 +37,30 @@ pub trait StaticFileConfig: Default { } } - ///Describes whether Actix should attempt to calculate `ETag` + /// Describes whether Actix should attempt to calculate `ETag` /// - ///Defaults to `true` + /// Defaults to `true` fn is_use_etag() -> bool { true } - ///Describes whether Actix should use last modified date of file. + /// Describes whether Actix should use last modified date of file. /// - ///Defaults to `true` + /// Defaults to `true` fn is_use_last_modifier() -> bool { true } - ///Describes allowed methods to access static resources. + /// Describes allowed methods to access static resources. /// - ///By default all methods are allowed + /// By default all methods are allowed fn is_method_allowed(_method: &Method) -> bool { true } } -///Default content disposition as described in -///[StaticFileConfig](trait.StaticFileConfig.html) +/// Default content disposition as described in +/// [StaticFileConfig](trait.StaticFileConfig.html) #[derive(Default)] pub struct DefaultConfig; diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 6372a183..2bfa3067 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -11,10 +11,9 @@ use std::os::unix::fs::MetadataExt; use mime; use mime_guess::guess_mime_type; -use actix_http::error::Error; -use actix_http::http::header::{self, ContentDisposition, DispositionParam}; +use actix_web::http::header::{self, ContentDisposition, DispositionParam}; use actix_web::http::{ContentEncoding, Method, StatusCode}; -use actix_web::{HttpMessage, HttpRequest, HttpResponse, Responder}; +use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use crate::config::{DefaultConfig, StaticFileConfig}; use crate::range::HttpRange; @@ -42,10 +41,8 @@ impl NamedFile { /// /// # Examples /// - /// ```rust,ignore - /// extern crate actix_web; - /// - /// use actix_web::fs::NamedFile; + /// ```rust + /// use actix_files::NamedFile; /// use std::io::{self, Write}; /// use std::env; /// use std::fs::File; @@ -65,8 +62,8 @@ impl NamedFile { /// /// # Examples /// - /// ```rust,ignore - /// use actix_web::fs::NamedFile; + /// ```rust + /// use actix_files::NamedFile; /// /// let file = NamedFile::open("foo.txt"); /// ``` @@ -83,10 +80,8 @@ impl NamedFile { /// /// # Examples /// - /// ```rust,ignore - /// extern crate actix_web; - /// - /// use actix_web::fs::{DefaultConfig, NamedFile}; + /// ```rust + /// use actix_files::{DefaultConfig, NamedFile}; /// use std::io::{self, Write}; /// use std::env; /// use std::fs::File; @@ -147,8 +142,8 @@ impl NamedFile { /// /// # Examples /// - /// ```rust,ignore - /// use actix_web::fs::{DefaultConfig, NamedFile}; + /// ```rust + /// use actix_files::{DefaultConfig, NamedFile}; /// /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); /// ``` @@ -169,9 +164,9 @@ impl NamedFile { /// /// # Examples /// - /// ```rust,ignore + /// ```rust /// # use std::io; - /// use actix_web::fs::NamedFile; + /// use actix_files::NamedFile; /// /// # fn path() -> io::Result<()> { /// let file = NamedFile::open("test.txt")?; diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 421c6fc4..554f3d7f 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -25,23 +25,15 @@ cookie-session = ["cookie/secure"] [dependencies] actix-web = { path=".." } -actix-codec = "0.1.1" actix-service = "0.3.3" -actix-utils = "0.3.3" -actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-router = { git = "https://github.com/actix/actix-net.git" } -actix-server = { git = "https://github.com/actix/actix-net.git" } - bytes = "0.4" cookie = { version="0.11", features=["percent-encode"], optional=true } derive_more = "0.14" -encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" -log = "0.4" serde = "1.0" serde_json = "1.0" time = "0.1" [dev-dependencies] -actix-rt = "0.2.0" +actix-rt = "0.2.1" diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 202d562c..687cf431 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -26,11 +26,12 @@ impl StreamHandler for Ws { #[test] fn test_simple() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").to( - |req: HttpRequest, stream: web::Payload<_>| ws::start(Ws, &req, stream), - ))) - }); + let mut srv = + TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").to( + |req: HttpRequest, stream: web::Payload| ws::start(Ws, &req, stream), + ))) + }); // client service let framed = srv.ws().unwrap(); diff --git a/src/types/payload.rs b/src/types/payload.rs index 402486b6..170b9c62 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -23,9 +23,7 @@ use crate::service::ServiceFromRequest; /// use actix_web::{web, error, App, Error, HttpResponse}; /// /// /// extract binary data from request -/// fn index

    (body: web::Payload

    ) -> impl Future -/// where -/// P: Stream +/// fn index(body: web::Payload) -> impl Future /// { /// body.map_err(Error::from) /// .fold(web::BytesMut::new(), move |mut body, chunk| { @@ -45,12 +43,9 @@ use crate::service::ServiceFromRequest; /// ); /// } /// ``` -pub struct Payload(crate::dev::Payload); +pub struct Payload(crate::dev::Payload>>); -impl Stream for Payload -where - T: Stream, -{ +impl Stream for Payload { type Item = Bytes; type Error = PayloadError; @@ -69,9 +64,7 @@ where /// use actix_web::{web, error, App, Error, HttpResponse}; /// /// /// extract binary data from request -/// fn index

    (body: web::Payload

    ) -> impl Future -/// where -/// P: Stream +/// fn index(body: web::Payload) -> impl Future /// { /// body.map_err(Error::from) /// .fold(web::BytesMut::new(), move |mut body, chunk| { @@ -91,16 +84,26 @@ where /// ); /// } /// ``` -impl

    FromRequest

    for Payload

    +impl

    FromRequest

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

    ) -> Self::Future { - Ok(Payload(req.take_payload())) + let pl = match req.take_payload() { + crate::dev::Payload::Stream(s) => { + let pl: Box> = + Box::new(s); + crate::dev::Payload::Stream(pl) + } + crate::dev::Payload::None => crate::dev::Payload::None, + crate::dev::Payload::H1(pl) => crate::dev::Payload::H1(pl), + crate::dev::Payload::H2(pl) => crate::dev::Payload::H2(pl), + }; + Ok(Payload(pl)) } } From 51e4dcf3b36b1399b72174c82101b8ad4492d185 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 17:13:17 -0700 Subject: [PATCH 1116/1635] update test doc string --- src/test.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test.rs b/src/test.rs index 7e79659a..c5936ea3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -80,13 +80,13 @@ pub fn default_service( /// service. /// /// ```rust,ignore -/// use actix_web::{test, App, HttpResponse, http::StatusCode}; /// use actix_service::Service; +/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// /// fn main() { /// let mut app = test::init_service( /// App::new() -/// .resource("/test", |r| r.to(|| HttpResponse::Ok())) +/// .service(web::resource("/test").to(|| HttpResponse::Ok())) /// ); /// /// // Create request object @@ -123,7 +123,7 @@ where /// fn main() { /// let mut app = test::init_service( /// App::new() -/// .resource("/test", |r| r.to(|| HttpResponse::Ok())) +/// .service(web::resource("/test").to(|| HttpResponse::Ok())) /// ); /// /// // Create request object From 1970c99522ef37d4a5fbed404b9b100912fad69a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 24 Mar 2019 20:21:20 -0700 Subject: [PATCH 1117/1635] add session test --- actix-session/src/lib.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 1dd367ba..ae0fd23c 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -181,3 +181,30 @@ impl

    FromRequest

    for Session { Ok(Session::get_session(req)) } } + +#[cfg(test)] +mod tests { + use actix_web::{test, HttpResponse}; + + use super::*; + + #[test] + fn session() { + let mut req = test::TestRequest::default().to_service(); + + Session::set_session( + vec![("key".to_string(), "\"value\"".to_string())].into_iter(), + &mut req, + ); + let session = Session::get_session(&mut req); + let res = session.get::("key").unwrap(); + assert_eq!(res, Some("value".to_string())); + + session.set("key2", "value2".to_string()).unwrap(); + session.remove("key"); + + let mut res = req.into_response(HttpResponse::Ok().finish()); + let changes: Vec<_> = Session::get_changes(&mut res).unwrap().collect(); + assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]); + } +} From 939d2e745c9ae3e5ba1f240f0613efa8db66390c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Mar 2019 12:47:58 -0700 Subject: [PATCH 1118/1635] rename Resource::middleware to Resource::wrap and add wrap_fn for fn middlewares --- examples/basic.rs | 2 +- src/lib.rs | 26 ++++++++++ src/resource.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 149 insertions(+), 4 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 756f1b79..ee7e4c96 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -33,7 +33,7 @@ fn main() -> std::io::Result<()> { .service(no_params) .service( web::resource("/resource2/index.html") - .middleware( + .wrap( middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), ) .default_resource(|r| { diff --git a/src/lib.rs b/src/lib.rs index 79e1ba34..8ae7156c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -340,4 +340,30 @@ pub mod web { { blocking::run(f).from_err() } + + use actix_service::{fn_transform, Service, Transform}; + + use crate::service::{ServiceRequest, ServiceResponse}; + + /// Create middleare + pub fn md( + f: F, + ) -> impl Transform< + S, + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = Error, + InitError = (), + > + where + S: Service< + Request = ServiceRequest

    , + Response = ServiceResponse, + Error = Error, + >, + F: FnMut(ServiceRequest

    , &mut S) -> R + Clone, + R: IntoFuture, Error = Error>, + { + fn_transform(f) + } } diff --git a/src/resource.rs b/src/resource.rs index 29ff0785..5d567131 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -230,7 +230,9 @@ where /// This is similar to `App's` middlewares, but middleware get invoked on resource level. /// Resource level middlewares are not allowed to change response /// type (i.e modify response's body). - pub fn middleware( + /// + /// **Note**: middlewares get called in opposite order of middlewares registration. + pub fn wrap( self, mw: F, ) -> Resource< @@ -264,6 +266,57 @@ where } } + /// Register a resource middleware function. + /// + /// This function accepts instance of `ServiceRequest` type and + /// mutable reference to the next middleware in chain. + /// + /// This is similar to `App's` middlewares, but middleware get invoked on resource level. + /// Resource level middlewares are not allowed to change response + /// type (i.e modify response's body). + /// + /// ```rust + /// use actix_service::Service; + /// # use futures::Future; + /// use actix_web::{web, App}; + /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new().service( + /// web::resource("/index.html") + /// .wrap_fn(|req, srv| + /// srv.call(req).map(|mut res| { + /// res.headers_mut().insert( + /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), + /// ); + /// res + /// })) + /// .route(web::get().to(index))); + /// } + /// ``` + pub fn wrap_fn( + self, + mw: F, + ) -> Resource< + P, + impl NewService< + Request = ServiceRequest

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

    , &mut T::Service) -> R + Clone, + R: IntoFuture, + { + self.wrap(mw) + } + /// Default resource to be used if no matching route could be found. /// By default *405* response get returned. Resource does not use /// default handler from `App` or `Scope`. @@ -489,9 +542,75 @@ impl NewService for ResourceEndpoint

    { #[cfg(test)] mod tests { - use crate::http::{Method, StatusCode}; + use actix_service::Service; + use futures::{Future, IntoFuture}; + + use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::service::{ServiceRequest, ServiceResponse}; use crate::test::{call_success, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::{web, App, Error, HttpResponse}; + + fn md1( + req: ServiceRequest

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

    , + Response = ServiceResponse, + Error = Error, + >, + { + srv.call(req).map(|mut res| { + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); + res + }) + } + + #[test] + fn test_middleware() { + let mut srv = init_service( + App::new().service( + web::resource("/test") + .wrap(md1) + .route(web::get().to(|| HttpResponse::Ok())), + ), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[test] + fn test_middleware_fn() { + let mut srv = init_service( + App::new().service( + web::resource("/test") + .wrap_fn(|req, srv| { + srv.call(req).map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + }) + .route(web::get().to(|| HttpResponse::Ok())), + ), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } #[test] fn test_default_resource() { From 86a21c956c247ab098a7631bc18ae60032ef5eac Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Mar 2019 13:02:10 -0700 Subject: [PATCH 1119/1635] rename .middleware to .wrap --- actix-session/src/cookie.rs | 6 ++--- actix-session/src/lib.rs | 2 +- examples/basic.rs | 6 ++--- src/app.rs | 4 +-- src/middleware/cors.rs | 6 ++--- src/middleware/defaultheaders.rs | 2 +- src/middleware/errhandlers.rs | 2 +- src/middleware/identity.rs | 8 +++--- src/middleware/logger.rs | 4 +-- src/resource.rs | 4 +-- src/scope.rs | 43 +++++++++++++++++++++++++++++--- tests/test_server.rs | 14 +++++------ 12 files changed, 68 insertions(+), 33 deletions(-) diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 37c552ea..2d5bd708 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -178,7 +178,7 @@ impl CookieSessionInner { /// use actix_web::{web, App, HttpResponse, HttpServer}; /// /// fn main() { -/// let app = App::new().middleware( +/// let app = App::new().wrap( /// CookieSession::signed(&[0; 32]) /// .domain("www.rust-lang.org") /// .name("actix_session") @@ -323,7 +323,7 @@ mod tests { fn cookie_session() { let mut app = test::init_service( App::new() - .middleware(CookieSession::signed(&[0; 32]).secure(false)) + .wrap(CookieSession::signed(&[0; 32]).secure(false)) .service(web::resource("/").to(|ses: Session| { let _ = ses.set("counter", 100); "test" @@ -342,7 +342,7 @@ mod tests { fn cookie_session_extractor() { let mut app = test::init_service( App::new() - .middleware(CookieSession::signed(&[0; 32]).secure(false)) + .wrap(CookieSession::signed(&[0; 32]).secure(false)) .service(web::resource("/").to(|ses: Session| { let _ = ses.set("counter", 100); "test" diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index ae0fd23c..819773c6 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -31,7 +31,7 @@ //! fn main() -> std::io::Result<()> { //! # std::thread::spawn(|| //! HttpServer::new( -//! || App::new().middleware( +//! || App::new().wrap( //! CookieSession::signed(&[0; 32]) // <- create cookie based session middleware //! .secure(false) //! ) diff --git a/examples/basic.rs b/examples/basic.rs index ee7e4c96..91119657 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -26,9 +26,9 @@ fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() - .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) - .middleware(middleware::Compress::default()) - .middleware(middleware::Logger::default()) + .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) + .wrap(middleware::Compress::default()) + .wrap(middleware::Logger::default()) .service(index) .service(no_params) .service( diff --git a/src/app.rs b/src/app.rs index 991288dd..94a47afe 100644 --- a/src/app.rs +++ b/src/app.rs @@ -108,7 +108,7 @@ where } /// Register a middleware. - pub fn middleware( + pub fn wrap( self, mw: F, ) -> AppRouter< @@ -322,7 +322,7 @@ where } /// Register a middleware. - pub fn middleware( + pub fn wrap( self, mw: F, ) -> AppRouter< diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 9ba09256..b6acf429 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -1,8 +1,8 @@ //! Cross-origin resource sharing (CORS) for Actix applications //! //! CORS middleware could be used with application and with resource. -//! Cors middleware could be used as parameter for `App::middleware()`, -//! `Resource::middleware()` or `Scope::middleware()` methods. +//! Cors middleware could be used as parameter for `App::wrap()`, +//! `Resource::wrap()` or `Scope::wrap()` methods. //! //! # Example //! @@ -16,7 +16,7 @@ //! //! fn main() -> std::io::Result<()> { //! HttpServer::new(|| App::new() -//! .middleware( +//! .wrap( //! Cors::new() // <- Construct CORS middleware builder //! .allowed_origin("https://www.rust-lang.org/") //! .allowed_methods(vec!["GET", "POST"]) diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 4d879cda..ca5b8f80 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -18,7 +18,7 @@ use crate::service::{ServiceRequest, ServiceResponse}; /// /// fn main() { /// let app = App::new() -/// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) +/// .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) /// .service( /// web::resource("/test") /// .route(web::get().to(|| HttpResponse::Ok())) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index ca9b1cd5..4f253722 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -41,7 +41,7 @@ type ErrorHandler = Fn(ServiceResponse) -> Result> /// /// fn main() { /// let app = App::new() -/// .middleware( +/// .wrap( /// ErrorHandlers::new() /// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), /// ) diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index d0a4146a..e94f99db 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -37,7 +37,7 @@ //! } //! //! fn main() { -//! let app = App::new().middleware(IdentityService::new( +//! let app = App::new().wrap(IdentityService::new( //! // <- create identity middleware //! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend //! .name("auth-cookie") @@ -179,7 +179,7 @@ pub trait IdentityPolicy: Sized + 'static { /// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; /// /// fn main() { -/// let app = App::new().middleware(IdentityService::new( +/// let app = App::new().wrap(IdentityService::new( /// // <- create identity middleware /// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend /// .name("auth-cookie") @@ -381,7 +381,7 @@ impl CookieIdentityInner { /// use actix_web::App; /// /// fn main() { -/// let app = App::new().middleware(IdentityService::new( +/// let app = App::new().wrap(IdentityService::new( /// // <- create identity middleware /// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy /// .domain("www.rust-lang.org") @@ -473,7 +473,7 @@ mod tests { fn test_identity() { let mut srv = test::init_service( App::new() - .middleware(IdentityService::new( + .wrap(IdentityService::new( CookieIdentityPolicy::new(&[0; 32]) .domain("www.rust-lang.org") .name("actix_auth") diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 4af3e10d..42f344f0 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -41,8 +41,8 @@ use crate::{HttpMessage, HttpResponse}; /// env_logger::init(); /// /// let app = App::new() -/// .middleware(Logger::default()) -/// .middleware(Logger::new("%a %{User-Agent}i")); +/// .wrap(Logger::default()) +/// .wrap(Logger::new("%a %{User-Agent}i")); /// } /// ``` /// diff --git a/src/resource.rs b/src/resource.rs index 5d567131..632e9c33 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -550,7 +550,7 @@ mod tests { use crate::test::{call_success, init_service, TestRequest}; use crate::{web, App, Error, HttpResponse}; - fn md1( + fn md( req: ServiceRequest

    , srv: &mut S, ) -> impl IntoFuture, Error = Error> @@ -573,7 +573,7 @@ mod tests { let mut srv = init_service( App::new().service( web::resource("/test") - .wrap(md1) + .wrap(md) .route(web::get().to(|| HttpResponse::Ok())), ), ); diff --git a/src/scope.rs b/src/scope.rs index 4a894450..1be594f1 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -205,7 +205,7 @@ where /// This is similar to `App's` middlewares, but middleware get invoked on scope level. /// Scope level middlewares are not allowed to change response /// type (i.e modify response's body). - pub fn middleware( + pub fn wrap( self, mw: F, ) -> Scope< @@ -476,11 +476,13 @@ impl NewService for ScopeEndpoint

    { mod tests { use actix_service::Service; use bytes::Bytes; + use futures::{Future, IntoFuture}; use crate::dev::{Body, ResponseBody}; - use crate::http::{Method, StatusCode}; - use crate::test::{block_on, init_service, TestRequest}; - use crate::{guard, web, App, HttpRequest, HttpResponse}; + use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::service::{ServiceRequest, ServiceResponse}; + use crate::test::{block_on, call_success, init_service, TestRequest}; + use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; #[test] fn test_scope() { @@ -827,4 +829,37 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } + + fn md( + req: ServiceRequest

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

    , + Response = ServiceResponse, + Error = Error, + >, + { + srv.call(req).map(|mut res| { + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); + res + }) + } + + #[test] + fn test_middleware() { + let mut srv = + init_service(App::new().service(web::scope("app").wrap(md).service( + web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), + ))); + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } } diff --git a/tests/test_server.rs b/tests/test_server.rs index 965d444f..2742164a 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -59,7 +59,7 @@ fn test_body_gzip() { let mut srv = TestServer::new(|| { h1::H1Service::new( App::new() - .middleware(middleware::Compress::new(ContentEncoding::Gzip)) + .wrap(middleware::Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -87,7 +87,7 @@ fn test_body_gzip_large() { let data = srv_data.clone(); h1::H1Service::new( App::new() - .middleware(middleware::Compress::new(ContentEncoding::Gzip)) + .wrap(middleware::Compress::new(ContentEncoding::Gzip)) .service( web::resource("/") .route(web::to(move || Response::Ok().body(data.clone()))), @@ -121,7 +121,7 @@ fn test_body_gzip_large_random() { let data = srv_data.clone(); h1::H1Service::new( App::new() - .middleware(middleware::Compress::new(ContentEncoding::Gzip)) + .wrap(middleware::Compress::new(ContentEncoding::Gzip)) .service( web::resource("/") .route(web::to(move || Response::Ok().body(data.clone()))), @@ -149,7 +149,7 @@ fn test_body_chunked_implicit() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .middleware(middleware::Compress::new(ContentEncoding::Gzip)) + .wrap(middleware::Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::get().to(move || { Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( STR.as_ref(), @@ -181,7 +181,7 @@ fn test_body_br_streaming() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .middleware(middleware::Compress::new(ContentEncoding::Br)) + .wrap(middleware::Compress::new(ContentEncoding::Br)) .service(web::resource("/").route(web::to(move || { Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( STR.as_ref(), @@ -254,7 +254,7 @@ fn test_body_deflate() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .middleware(middleware::Compress::new(ContentEncoding::Deflate)) + .wrap(middleware::Compress::new(ContentEncoding::Deflate)) .service( web::resource("/").route(web::to(move || Response::Ok().body(STR))), ), @@ -281,7 +281,7 @@ fn test_body_brotli() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .middleware(middleware::Compress::new(ContentEncoding::Br)) + .wrap(middleware::Compress::new(ContentEncoding::Br)) .service( web::resource("/").route(web::to(move || Response::Ok().body(STR))), ), From d30027ac5b13d517645492055100ab76632a62f9 Mon Sep 17 00:00:00 2001 From: Douman Date: Mon, 25 Mar 2019 23:02:37 +0300 Subject: [PATCH 1120/1635] Remove StaticFilesConfig (#731) * Remove StaticFilesConfig * Applying comments * Impl Clone for Files --- actix-files/Cargo.toml | 1 + actix-files/src/config.rs | 67 ------------ actix-files/src/lib.rs | 221 ++++++++++++++++++-------------------- actix-files/src/named.rs | 126 ++++++++++------------ 4 files changed, 162 insertions(+), 253 deletions(-) delete mode 100644 actix-files/src/config.rs diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index aa93ac22..65faa5e8 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -22,6 +22,7 @@ actix-web = { path=".." } actix-http = { git = "https://github.com/actix/actix-http.git" } actix-service = "0.3.3" +bitflags = "1" bytes = "0.4" futures = "0.1" derive_more = "0.14" diff --git a/actix-files/src/config.rs b/actix-files/src/config.rs deleted file mode 100644 index 7ad65ae7..00000000 --- a/actix-files/src/config.rs +++ /dev/null @@ -1,67 +0,0 @@ -use actix_web::http::{header::DispositionType, Method}; -use mime; - -/// Describes `StaticFiles` configiration -/// -/// To configure actix's static resources you need -/// to define own configiration type and implement any method -/// you wish to customize. -/// As trait implements reasonable defaults for Actix. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::http::header::DispositionType; -/// use actix_files::{StaticFileConfig, NamedFile}; -/// -/// #[derive(Default)] -/// struct MyConfig; -/// -/// impl StaticFileConfig for MyConfig { -/// fn content_disposition_map(typ: mime::Name) -> DispositionType { -/// DispositionType::Attachment -/// } -/// } -/// -/// let file = NamedFile::open_with_config("foo.txt", MyConfig); -/// ``` -pub trait StaticFileConfig: Default { - /// Describes mapping for mime type to content disposition header - /// - /// By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline. - /// Others are mapped to Attachment - fn content_disposition_map(typ: mime::Name) -> DispositionType { - match typ { - mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, - _ => DispositionType::Attachment, - } - } - - /// Describes whether Actix should attempt to calculate `ETag` - /// - /// Defaults to `true` - fn is_use_etag() -> bool { - true - } - - /// Describes whether Actix should use last modified date of file. - /// - /// Defaults to `true` - fn is_use_last_modifier() -> bool { - true - } - - /// Describes allowed methods to access static resources. - /// - /// By default all methods are allowed - fn is_method_allowed(_method: &Method) -> bool { - true - } -} - -/// Default content disposition as described in -/// [StaticFileConfig](trait.StaticFileConfig.html) -#[derive(Default)] -pub struct DefaultConfig; - -impl StaticFileConfig for DefaultConfig {} diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index b9240009..31ff4cda 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -3,7 +3,6 @@ use std::cell::RefCell; use std::fmt::Write; use std::fs::{DirEntry, File}; use std::io::{Read, Seek}; -use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::{cmp, io}; @@ -22,15 +21,14 @@ use actix_web::dev::{ }; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; +use actix_web::http::header::{DispositionType}; use futures::future::{ok, FutureResult}; -mod config; mod error; mod named; mod range; use self::error::{FilesError, UriSegmentError}; -pub use crate::config::{DefaultConfig, StaticFileConfig}; pub use crate::named::NamedFile; pub use crate::range::HttpRange; @@ -211,6 +209,8 @@ fn directory_listing( )) } +type MimeOverride = Fn(&mime::Name) -> DispositionType; + /// Static files handling /// /// `Files` service must be registered with `App::service()` method. @@ -224,16 +224,34 @@ fn directory_listing( /// .service(fs::Files::new("/static", ".")); /// } /// ``` -pub struct Files { +pub struct Files { path: String, directory: PathBuf, index: Option, show_index: bool, default: Rc>>>>, renderer: Rc, + mime_override: Option>, _chunk_size: usize, _follow_symlinks: bool, - _cd_map: PhantomData, + file_flags: named::Flags, +} + +impl Clone for Files { + fn clone(&self) -> Self { + Self { + directory: self.directory.clone(), + index: self.index.clone(), + show_index: self.show_index, + default: self.default.clone(), + renderer: self.renderer.clone(), + _chunk_size: self._chunk_size, + _follow_symlinks: self._follow_symlinks, + file_flags: self.file_flags, + path: self.path.clone(), + mime_override: self.mime_override.clone(), + } + } } impl Files { @@ -243,15 +261,6 @@ impl Files { /// By default pool with 5x threads of available cpus is used. /// Pool size can be changed by setting ACTIX_CPU_POOL environment variable. pub fn new>(path: &str, dir: T) -> Files { - Self::with_config(path, dir, DefaultConfig) - } -} - -impl Files { - /// Create new `Files` instance for specified base directory. - /// - /// Identical with `new` but allows to specify configiration to use. - pub fn with_config>(path: &str, dir: T, _: C) -> Files { let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new()); if !dir.is_dir() { log::error!("Specified path is not a directory"); @@ -264,9 +273,10 @@ impl Files { show_index: false, default: Rc::new(RefCell::new(None)), renderer: Rc::new(directory_listing), + mime_override: None, _chunk_size: 0, _follow_symlinks: false, - _cd_map: PhantomData, + file_flags: named::Flags::default(), } } @@ -289,20 +299,44 @@ impl Files { self } + /// Specifies mime override callback + pub fn mime_override(mut self, f: F) -> Self where F: Fn(&mime::Name) -> DispositionType + 'static { + self.mime_override = Some(Rc::new(f)); + self + } + /// Set index file /// /// Shows specific index file for directory "/" instead of /// showing files listing. - pub fn index_file>(mut self, index: T) -> Files { + pub fn index_file>(mut self, index: T) -> Self { self.index = Some(index.into()); self } + + #[inline] + ///Specifies whether to use ETag or not. + /// + ///Default is true. + pub fn use_etag(mut self, value: bool) -> Self { + self.file_flags.set(named::Flags::ETAG, value); + self + } + + #[inline] + ///Specifies whether to use Last-Modified or not. + /// + ///Default is true. + pub fn use_last_modified(mut self, value: bool) -> Self { + self.file_flags.set(named::Flags::LAST_MD, value); + self + } + } -impl HttpServiceFactory

    for Files +impl

    HttpServiceFactory

    for Files

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

    ) { if self.default.borrow().is_none() { @@ -317,40 +351,20 @@ where } } -impl NewService for Files { +impl

    NewService for Files

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = Error; - type Service = FilesService; + type Service = Self; type InitError = (); type Future = FutureResult; fn new_service(&self, _: &()) -> Self::Future { - ok(FilesService { - directory: self.directory.clone(), - index: self.index.clone(), - show_index: self.show_index, - default: self.default.clone(), - renderer: self.renderer.clone(), - _chunk_size: self._chunk_size, - _follow_symlinks: self._follow_symlinks, - _cd_map: self._cd_map, - }) + ok(self.clone()) } } -pub struct FilesService { - directory: PathBuf, - index: Option, - show_index: bool, - default: Rc>>>>, - renderer: Rc, - _chunk_size: usize, - _follow_symlinks: bool, - _cd_map: PhantomData, -} - -impl Service for FilesService { +impl

    Service for Files

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = Error; @@ -378,10 +392,18 @@ impl Service for FilesService { if let Some(ref redir_index) = self.index { let path = path.join(redir_index); - match NamedFile::open_with_config(path, C::default()) { - Ok(named_file) => match named_file.respond_to(&req) { - Ok(item) => ok(ServiceResponse::new(req.clone(), item)), - Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + match NamedFile::open(path) { + Ok(mut named_file) => { + if let Some(ref mime_override) = self.mime_override { + let new_disposition = mime_override(&named_file.content_type.type_()); + named_file.content_disposition.disposition = new_disposition; + } + + named_file.flags = self.file_flags; + match named_file.respond_to(&req) { + Ok(item) => ok(ServiceResponse::new(req.clone(), item)), + Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + } }, Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } @@ -399,10 +421,18 @@ impl Service for FilesService { )) } } else { - match NamedFile::open_with_config(path, C::default()) { - Ok(named_file) => match named_file.respond_to(&req) { - Ok(item) => ok(ServiceResponse::new(req.clone(), item)), - Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + match NamedFile::open(path) { + Ok(mut named_file) => { + if let Some(ref mime_override) = self.mime_override { + let new_disposition = mime_override(&named_file.content_type.type_()); + named_file.content_disposition.disposition = new_disposition; + } + + named_file.flags = self.file_flags; + match named_file.respond_to(&req) { + Ok(item) => ok(ServiceResponse::new(req.clone(), item)), + Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + } }, Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } @@ -606,53 +636,6 @@ mod tests { ); } - #[derive(Default)] - pub struct AllAttachmentConfig; - impl StaticFileConfig for AllAttachmentConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Attachment - } - } - - #[derive(Default)] - pub struct AllInlineConfig; - impl StaticFileConfig for AllInlineConfig { - fn content_disposition_map(_typ: mime::Name) -> DispositionType { - DispositionType::Inline - } - } - - #[test] - fn test_named_file_image_attachment_and_custom_config() { - let file = - NamedFile::open_with_config("tests/test.png", AllAttachmentConfig).unwrap(); - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - - let file = - NamedFile::open_with_config("tests/test.png", AllInlineConfig).unwrap(); - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - #[test] fn test_named_file_binary() { let mut file = NamedFile::open("tests/test.binary").unwrap(); @@ -702,6 +685,25 @@ mod tests { assert_eq!(resp.status(), StatusCode::NOT_FOUND); } + #[test] + fn test_mime_override() { + fn all_attachment(_: &mime::Name) -> DispositionType { + DispositionType::Attachment + } + + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").mime_override(all_attachment).index_file("Cargo.toml")), + ); + + let request = TestRequest::get().uri("/").to_request(); + let response = test::call_success(&mut srv, request); + assert_eq!(response.status(), StatusCode::OK); + + let content_disposition = response.headers().get(header::CONTENT_DISPOSITION).expect("To have CONTENT_DISPOSITION"); + let content_disposition = content_disposition.to_str().expect("Convert CONTENT_DISPOSITION to str"); + assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); + } + #[test] fn test_named_file_ranges_status_code() { let mut srv = test::init_service( @@ -860,21 +862,10 @@ mod tests { assert_eq!(bytes.freeze(), data); } - #[derive(Default)] - pub struct OnlyMethodHeadConfig; - impl StaticFileConfig for OnlyMethodHeadConfig { - fn is_method_allowed(method: &Method) -> bool { - match *method { - Method::HEAD => true, - _ => false, - } - } - } - #[test] fn test_named_file_not_allowed() { let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + NamedFile::open("Cargo.toml").unwrap(); let req = TestRequest::default() .method(Method::POST) .to_http_request(); @@ -882,16 +873,10 @@ mod tests { assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); + NamedFile::open("Cargo.toml").unwrap(); let req = TestRequest::default().method(Method::PUT).to_http_request(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let file = - NamedFile::open_with_config("Cargo.toml", OnlyMethodHeadConfig).unwrap(); - let req = TestRequest::default().method(Method::GET).to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } // #[test] @@ -910,9 +895,9 @@ mod tests { // } #[test] - fn test_named_file_any_method() { + fn test_named_file_allowed_method() { let req = TestRequest::default() - .method(Method::POST) + .method(Method::GET) .to_http_request(); let file = NamedFile::open("Cargo.toml").unwrap(); let resp = file.respond_to(&req).unwrap(); diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 2bfa3067..d2bf2569 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -1,6 +1,5 @@ use std::fs::{File, Metadata}; use std::io; -use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -8,20 +7,33 @@ use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; +use bitflags::bitflags; use mime; use mime_guess::guess_mime_type; -use actix_web::http::header::{self, ContentDisposition, DispositionParam}; +use actix_web::http::header::{self, DispositionType, ContentDisposition, DispositionParam}; use actix_web::http::{ContentEncoding, Method, StatusCode}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; -use crate::config::{DefaultConfig, StaticFileConfig}; use crate::range::HttpRange; use crate::ChunkedReadFile; +bitflags! { + pub(crate) struct Flags: u32 { + const ETAG = 0b00000001; + const LAST_MD = 0b00000010; + } +} + +impl Default for Flags { + fn default() -> Self { + Flags::all() + } +} + /// A file with an associated name. #[derive(Debug)] -pub struct NamedFile { +pub struct NamedFile { path: PathBuf, file: File, pub(crate) content_type: mime::Mime, @@ -30,7 +42,7 @@ pub struct NamedFile { modified: Option, encoding: Option, pub(crate) status_code: StatusCode, - _cd_map: PhantomData, + pub(crate) flags: Flags, } impl NamedFile { @@ -55,49 +67,6 @@ impl NamedFile { /// } /// ``` pub fn from_file>(file: File, path: P) -> io::Result { - Self::from_file_with_config(file, path, DefaultConfig) - } - - /// Attempts to open a file in read-only mode. - /// - /// # Examples - /// - /// ```rust - /// use actix_files::NamedFile; - /// - /// let file = NamedFile::open("foo.txt"); - /// ``` - pub fn open>(path: P) -> io::Result { - Self::open_with_config(path, DefaultConfig) - } -} - -impl NamedFile { - /// Creates an instance from a previously opened file using the provided configuration. - /// - /// The given `path` need not exist and is only used to determine the `ContentType` and - /// `ContentDisposition` headers. - /// - /// # Examples - /// - /// ```rust - /// use actix_files::{DefaultConfig, NamedFile}; - /// use std::io::{self, Write}; - /// use std::env; - /// use std::fs::File; - /// - /// fn main() -> io::Result<()> { - /// let mut file = File::create("foo.txt")?; - /// file.write_all(b"Hello, world!")?; - /// let named_file = NamedFile::from_file_with_config(file, "bar.txt", DefaultConfig)?; - /// Ok(()) - /// } - /// ``` - pub fn from_file_with_config>( - file: File, - path: P, - _: C, - ) -> io::Result> { let path = path.as_ref().to_path_buf(); // Get the name of the file and use it to construct default Content-Type @@ -114,7 +83,10 @@ impl NamedFile { }; let ct = guess_mime_type(&path); - let disposition_type = C::content_disposition_map(ct.type_()); + let disposition_type = match ct.type_() { + mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, + _ => DispositionType::Attachment, + }; let cd = ContentDisposition { disposition: disposition_type, parameters: vec![DispositionParam::Filename(filename.into_owned())], @@ -134,24 +106,21 @@ impl NamedFile { modified, encoding, status_code: StatusCode::OK, - _cd_map: PhantomData, + flags: Flags::default(), }) } - /// Attempts to open a file in read-only mode using provided configuration. + /// Attempts to open a file in read-only mode. /// /// # Examples /// /// ```rust - /// use actix_files::{DefaultConfig, NamedFile}; + /// use actix_files::NamedFile; /// - /// let file = NamedFile::open_with_config("foo.txt", DefaultConfig); + /// let file = NamedFile::open("foo.txt"); /// ``` - pub fn open_with_config>( - path: P, - config: C, - ) -> io::Result> { - Self::from_file_with_config(File::open(&path)?, path, config) + pub fn open>(path: P) -> io::Result { + Self::from_file(File::open(&path)?, path) } /// Returns reference to the underlying `File` object. @@ -213,6 +182,24 @@ impl NamedFile { self } + #[inline] + ///Specifies whether to use ETag or not. + /// + ///Default is true. + pub fn use_etag(mut self, value: bool) -> Self { + self.flags.set(Flags::ETAG, value); + self + } + + #[inline] + ///Specifies whether to use Last-Modified or not. + /// + ///Default is true. + pub fn use_last_modified(mut self, value: bool) -> Self { + self.flags.set(Flags::LAST_MD, value); + self + } + pub(crate) fn etag(&self) -> Option { // This etag format is similar to Apache's. self.modified.as_ref().map(|mtime| { @@ -245,7 +232,7 @@ impl NamedFile { } } -impl Deref for NamedFile { +impl Deref for NamedFile { type Target = File; fn deref(&self) -> &File { @@ -253,7 +240,7 @@ impl Deref for NamedFile { } } -impl DerefMut for NamedFile { +impl DerefMut for NamedFile { fn deref_mut(&mut self) -> &mut File { &mut self.file } @@ -294,7 +281,7 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } } -impl Responder for NamedFile { +impl Responder for NamedFile { type Error = Error; type Future = Result; @@ -320,15 +307,18 @@ impl Responder for NamedFile { return Ok(resp.streaming(reader)); } - if !C::is_method_allowed(req.method()) { - return Ok(HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")); + match req.method() { + &Method::HEAD | &Method::GET => (), + _ => { + return Ok(HttpResponse::MethodNotAllowed() + .header(header::CONTENT_TYPE, "text/plain") + .header(header::ALLOW, "GET, HEAD") + .body("This resource only supports GET and HEAD.")); + } } - let etag = if C::is_use_etag() { self.etag() } else { None }; - let last_modified = if C::is_use_last_modifier() { + let etag = if self.flags.contains(Flags::ETAG) { self.etag() } else { None }; + let last_modified = if self.flags.contains(Flags::LAST_MD) { self.last_modified() } else { None From e18227cc3d97369e90159fafcfc5b80b5d73b917 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Mar 2019 13:43:02 -0700 Subject: [PATCH 1121/1635] add wrap_fn to App and Scope --- actix-files/src/lib.rs | 43 ++++++---- actix-files/src/named.rs | 16 ++-- src/app.rs | 174 ++++++++++++++++++++++++++++++++++++++- src/scope.rs | 75 ++++++++++++++++- 4 files changed, 282 insertions(+), 26 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 31ff4cda..8254c5fe 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -20,8 +20,8 @@ use actix_web::dev::{ ServiceResponse, }; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; +use actix_web::http::header::DispositionType; use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; -use actix_web::http::header::{DispositionType}; use futures::future::{ok, FutureResult}; mod error; @@ -300,7 +300,10 @@ impl Files { } /// Specifies mime override callback - pub fn mime_override(mut self, f: F) -> Self where F: Fn(&mime::Name) -> DispositionType + 'static { + pub fn mime_override(mut self, f: F) -> Self + where + F: Fn(&mime::Name) -> DispositionType + 'static, + { self.mime_override = Some(Rc::new(f)); self } @@ -331,7 +334,6 @@ impl Files { self.file_flags.set(named::Flags::LAST_MD, value); self } - } impl

    HttpServiceFactory

    for Files

    @@ -395,7 +397,8 @@ impl

    Service for Files

    { match NamedFile::open(path) { Ok(mut named_file) => { if let Some(ref mime_override) = self.mime_override { - let new_disposition = mime_override(&named_file.content_type.type_()); + let new_disposition = + mime_override(&named_file.content_type.type_()); named_file.content_disposition.disposition = new_disposition; } @@ -404,7 +407,7 @@ impl

    Service for Files

    { Ok(item) => ok(ServiceResponse::new(req.clone(), item)), Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } - }, + } Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } } else if self.show_index { @@ -424,7 +427,8 @@ impl

    Service for Files

    { match NamedFile::open(path) { Ok(mut named_file) => { if let Some(ref mime_override) = self.mime_override { - let new_disposition = mime_override(&named_file.content_type.type_()); + let new_disposition = + mime_override(&named_file.content_type.type_()); named_file.content_disposition.disposition = new_disposition; } @@ -433,7 +437,7 @@ impl

    Service for Files

    { Ok(item) => ok(ServiceResponse::new(req.clone(), item)), Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } - }, + } Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } } @@ -692,15 +696,24 @@ mod tests { } let mut srv = test::init_service( - App::new().service(Files::new("/", ".").mime_override(all_attachment).index_file("Cargo.toml")), + App::new().service( + Files::new("/", ".") + .mime_override(all_attachment) + .index_file("Cargo.toml"), + ), ); let request = TestRequest::get().uri("/").to_request(); let response = test::call_success(&mut srv, request); assert_eq!(response.status(), StatusCode::OK); - let content_disposition = response.headers().get(header::CONTENT_DISPOSITION).expect("To have CONTENT_DISPOSITION"); - let content_disposition = content_disposition.to_str().expect("Convert CONTENT_DISPOSITION to str"); + let content_disposition = response + .headers() + .get(header::CONTENT_DISPOSITION) + .expect("To have CONTENT_DISPOSITION"); + let content_disposition = content_disposition + .to_str() + .expect("Convert CONTENT_DISPOSITION to str"); assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); } @@ -864,16 +877,14 @@ mod tests { #[test] fn test_named_file_not_allowed() { - let file = - NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open("Cargo.toml").unwrap(); let req = TestRequest::default() .method(Method::POST) .to_http_request(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let file = - NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open("Cargo.toml").unwrap(); let req = TestRequest::default().method(Method::PUT).to_http_request(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); @@ -896,9 +907,7 @@ mod tests { #[test] fn test_named_file_allowed_method() { - let req = TestRequest::default() - .method(Method::GET) - .to_http_request(); + let req = TestRequest::default().method(Method::GET).to_http_request(); let file = NamedFile::open("Cargo.toml").unwrap(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::OK); diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index d2bf2569..7bc37054 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -11,7 +11,9 @@ use bitflags::bitflags; use mime; use mime_guess::guess_mime_type; -use actix_web::http::header::{self, DispositionType, ContentDisposition, DispositionParam}; +use actix_web::http::header::{ + self, ContentDisposition, DispositionParam, DispositionType, +}; use actix_web::http::{ContentEncoding, Method, StatusCode}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; @@ -311,13 +313,17 @@ impl Responder for NamedFile { &Method::HEAD | &Method::GET => (), _ => { return Ok(HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")); + .header(header::CONTENT_TYPE, "text/plain") + .header(header::ALLOW, "GET, HEAD") + .body("This resource only supports GET and HEAD.")); } } - let etag = if self.flags.contains(Flags::ETAG) { self.etag() } else { None }; + let etag = if self.flags.contains(Flags::ETAG) { + self.etag() + } else { + None + }; let last_modified = if self.flags.contains(Flags::LAST_MD) { self.last_modified() } else { diff --git a/src/app.rs b/src/app.rs index 94a47afe..f46f5252 100644 --- a/src/app.rs +++ b/src/app.rs @@ -147,6 +147,51 @@ where } } + /// Register a middleware function. + /// + /// ```rust + /// use actix_service::Service; + /// # use futures::Future; + /// use actix_web::{web, App}; + /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap_fn(|req, srv| + /// srv.call(req).map(|mut res| { + /// res.headers_mut().insert( + /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), + /// ); + /// res + /// })) + /// .route("/index.html", web::get().to(index)); + /// } + /// ``` + pub fn wrap_fn( + self, + mw: F, + ) -> AppRouter< + T, + P, + B, + impl NewService< + Request = ServiceRequest

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

    , &mut AppRouting

    ) -> R + Clone, + R: IntoFuture, Error = Error>, + { + self.wrap(mw) + } + /// Register a request modifier. It can modify any request parameters /// including payload stream type. pub fn chain( @@ -361,6 +406,29 @@ where } } + /// Register a middleware function. + pub fn wrap_fn( + self, + mw: F, + ) -> AppRouter< + C, + P, + B1, + impl NewService< + Request = ServiceRequest

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

    , &mut T::Service) -> R + Clone, + R: IntoFuture, Error = Error>, + { + self.wrap(mw) + } + /// Default resource to be used if no matching resource could be found. pub fn default_resource(mut self, f: F) -> Self where @@ -447,11 +515,13 @@ where #[cfg(test)] mod tests { use actix_service::Service; + use futures::{Future, IntoFuture}; use super::*; - use crate::http::{Method, StatusCode}; - use crate::test::{block_on, init_service, TestRequest}; - use crate::{web, HttpResponse}; + use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::service::{ServiceRequest, ServiceResponse}; + use crate::test::{block_on, call_success, init_service, TestRequest}; + use crate::{web, Error, HttpResponse}; #[test] fn test_default_resource() { @@ -510,4 +580,102 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + + fn md( + req: ServiceRequest

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

    , + Response = ServiceResponse, + Error = Error, + >, + { + srv.call(req).map(|mut res| { + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); + res + }) + } + + #[test] + fn test_wrap() { + let mut srv = init_service( + App::new() + .wrap(md) + .route("/test", web::get().to(|| HttpResponse::Ok())), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[test] + fn test_router_wrap() { + let mut srv = init_service( + App::new() + .route("/test", web::get().to(|| HttpResponse::Ok())) + .wrap(md), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[test] + fn test_wrap_fn() { + let mut srv = init_service( + App::new() + .wrap_fn(|req, srv| { + srv.call(req).map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + }) + .service(web::resource("/test").to(|| HttpResponse::Ok())), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[test] + fn test_router_wrap_fn() { + let mut srv = init_service( + App::new() + .route("/test", web::get().to(|| HttpResponse::Ok())) + .wrap_fn(|req, srv| { + srv.call(req).map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + }), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } } diff --git a/src/scope.rs b/src/scope.rs index 1be594f1..8c72824f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -8,7 +8,7 @@ use actix_service::{ ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform, }; use futures::future::{ok, Either, Future, FutureResult}; -use futures::{Async, Poll}; +use futures::{Async, IntoFuture, Poll}; use crate::dev::{HttpServiceFactory, ServiceConfig}; use crate::error::Error; @@ -237,6 +237,53 @@ where factory_ref: self.factory_ref, } } + + /// Register a scope level middleware function. + /// + /// This function accepts instance of `ServiceRequest` type and + /// mutable reference to the next middleware in chain. + /// + /// ```rust + /// use actix_service::Service; + /// # use futures::Future; + /// use actix_web::{web, App}; + /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new().service( + /// web::scope("/app") + /// .wrap_fn(|req, srv| + /// srv.call(req).map(|mut res| { + /// res.headers_mut().insert( + /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), + /// ); + /// res + /// })) + /// .route("/index.html", web::get().to(index))); + /// } + /// ``` + pub fn wrap_fn( + self, + mw: F, + ) -> Scope< + P, + impl NewService< + Request = ServiceRequest

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

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

    for Scope @@ -862,4 +909,30 @@ mod tests { HeaderValue::from_static("0001") ); } + + #[test] + fn test_middleware_fn() { + let mut srv = init_service( + App::new().service( + web::scope("app") + .wrap_fn(|req, srv| { + srv.call(req).map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + }) + .route("/test", web::get().to(|| HttpResponse::Ok())), + ), + ); + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } } From 8d1195d8acc8c0f56ac1555e6515d67c8eab0501 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Mar 2019 14:33:34 -0700 Subject: [PATCH 1122/1635] add async handler tests --- Cargo.toml | 1 + src/resource.rs | 15 +++++++++++++++ src/route.rs | 47 +++++++++++++++++++++++++++++++++++++---------- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6920bc09..185f3fc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ actix-http-test = { git = "https://github.com/actix/actix-http.git", features=[" rand = "0.6" env_logger = "0.6" serde_derive = "1.0" +tokio-timer = "0.2.8" [profile.release] lto = true diff --git a/src/resource.rs b/src/resource.rs index 632e9c33..55237157 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -542,8 +542,11 @@ impl NewService for ResourceEndpoint

    { #[cfg(test)] mod tests { + use std::time::Duration; + use actix_service::Service; use futures::{Future, IntoFuture}; + use tokio_timer::sleep; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; @@ -573,6 +576,7 @@ mod tests { let mut srv = init_service( App::new().service( web::resource("/test") + .name("test") .wrap(md) .route(web::get().to(|| HttpResponse::Ok())), ), @@ -612,6 +616,17 @@ mod tests { ); } + #[test] + fn test_to_async() { + let mut srv = + init_service(App::new().service(web::resource("/test").to_async(|| { + sleep(Duration::from_millis(100)).then(|_| HttpResponse::Ok()) + }))); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + } + #[test] fn test_default_resource() { let mut srv = init_service( diff --git a/src/route.rs b/src/route.rs index 1f1aed47..7f1cee3d 100644 --- a/src/route.rs +++ b/src/route.rs @@ -410,21 +410,36 @@ where #[cfg(test)] mod tests { + use std::time::Duration; + + use futures::Future; + use tokio_timer::sleep; + use crate::http::{Method, StatusCode}; use crate::test::{call_success, init_service, TestRequest}; - use crate::{web, App, Error, HttpResponse}; + use crate::{error, web, App, HttpResponse}; #[test] fn test_route() { - let mut srv = init_service( - App::new().service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())) - .route( - web::post().to_async(|| Ok::<_, Error>(HttpResponse::Created())), - ), - ), - ); + let mut srv = + init_service( + App::new().service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::put().to(|| { + Err::(error::ErrorBadRequest("err")) + })) + .route(web::post().to_async(|| { + sleep(Duration::from_millis(100)) + .then(|_| HttpResponse::Created()) + })) + .route(web::delete().to_async(|| { + sleep(Duration::from_millis(100)).then(|_| { + Err::(error::ErrorBadRequest("err")) + }) + })), + ), + ); let req = TestRequest::with_uri("/test") .method(Method::GET) @@ -438,6 +453,18 @@ mod tests { let resp = call_success(&mut srv, req); assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/test") + .method(Method::PUT) + .to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let req = TestRequest::with_uri("/test") + .method(Method::DELETE) + .to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/test") .method(Method::HEAD) .to_request(); From 9037473e0fc60b668cfb2b68beeed43d1a8891a0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Mar 2019 21:52:45 -0700 Subject: [PATCH 1123/1635] update client error --- Cargo.toml | 2 +- src/body.rs | 10 ++++++++++ src/client/connection.rs | 2 +- src/client/error.rs | 4 ++++ src/client/request.rs | 11 ++++++++--- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 84b974be..da11a585 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ failure = { version = "0.1.5", optional = true } openssl = { version="0.10", optional = true } [dev-dependencies] -actix-rt = "0.2.0" +actix-rt = "0.2.1" actix-server = { version = "0.4.0", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } actix-http-test = { path="test-server", features=["ssl"] } diff --git a/src/body.rs b/src/body.rs index b7e8ec98..e1399e6b 100644 --- a/src/body.rs +++ b/src/body.rs @@ -45,6 +45,16 @@ impl MessageBody for () { } } +impl MessageBody for Box { + fn length(&self) -> BodyLength { + self.as_ref().length() + } + + fn poll_next(&mut self) -> Poll, Error> { + self.as_mut().poll_next() + } +} + pub enum ResponseBody { Body(B), Other(Body), diff --git a/src/client/connection.rs b/src/client/connection.rs index 683738e2..8de23bd2 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -21,7 +21,7 @@ pub(crate) enum ConnectionType { pub trait Connection { type Future: Future; - /// Close connection + /// Send request and body fn send_request( self, head: RequestHead, diff --git a/src/client/error.rs b/src/client/error.rs index 4fce904f..69ec4958 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -7,6 +7,7 @@ use trust_dns_resolver::error::ResolveError; use openssl::ssl::{Error as SslError, HandshakeError}; use crate::error::{Error, ParseError, ResponseError}; +use crate::http::Error as HttpError; use crate::response::Response; /// A set of errors that can occur while connecting to an HTTP host @@ -98,6 +99,9 @@ pub enum SendRequestError { Send(io::Error), /// Error parsing response Response(ParseError), + /// Http error + #[display(fmt = "{}", _0)] + Http(HttpError), /// Http2 error #[display(fmt = "{}", _0)] H2(h2::Error), diff --git a/src/client/request.rs b/src/client/request.rs index 26713aa4..134a4264 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -118,6 +118,11 @@ impl ClientRequest where B: MessageBody, { + /// Create new client request + pub fn new(head: RequestHead, body: B) -> Self { + ClientRequest { head, body } + } + /// Get the request URI #[inline] pub fn uri(&self) -> &Uri { @@ -174,14 +179,14 @@ where // Send request /// /// This method returns a future that resolves to a ClientResponse - pub fn send( + pub fn send( self, connector: &mut T, ) -> impl Future where B: 'static, - T: Service, - I: Connection, + T: Service, + T::Response: Connection, { let Self { head, body } = self; From 83d44473496ba0abd4d530cef3334cdf82c95ede Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Mar 2019 21:58:01 -0700 Subject: [PATCH 1124/1635] add http client --- Cargo.toml | 7 +- awc/Cargo.toml | 49 +++++ awc/src/builder.rs | 85 ++++++++ awc/src/connect.rs | 38 ++++ awc/src/lib.rs | 120 ++++++++++++ awc/src/request.rs | 471 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 + 7 files changed, 773 insertions(+), 1 deletion(-) create mode 100644 awc/Cargo.toml create mode 100644 awc/src/builder.rs create mode 100644 awc/src/connect.rs create mode 100644 awc/src/lib.rs create mode 100644 awc/src/request.rs diff --git a/Cargo.toml b/Cargo.toml index 185f3fc3..2a9883ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ path = "src/lib.rs" [workspace] members = [ ".", + "awc", "actix-files", "actix-session", "actix-web-actors", @@ -37,7 +38,10 @@ members = [ features = ["ssl", "tls", "rust-tls", "brotli", "flate2-c", "cookies"] [features] -default = ["brotli", "flate2-c", "cookies"] +default = ["brotli", "flate2-c", "cookies", "client"] + +# http client +client = ["awc"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -70,6 +74,7 @@ actix-web-codegen = { path="actix-web-codegen" } actix-http = { git = "https://github.com/actix/actix-http.git", features=["fail"] } actix-server = "0.4.1" actix-server-config = "0.1.0" +awc = { path = "awc", optional = true } bytes = "0.4" derive_more = "0.14" diff --git a/awc/Cargo.toml b/awc/Cargo.toml new file mode 100644 index 00000000..f316d062 --- /dev/null +++ b/awc/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "awc" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Actix web client." +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://docs.rs/awc/" +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +workspace = ".." +edition = "2018" + +[lib] +name = "awc" +path = "src/lib.rs" + +[features] +default = ["cookies"] + +# openssl +ssl = ["openssl", "actix-http/ssl"] + +# cookies integration +cookies = ["cookie", "actix-http/cookies"] + +[dependencies] +actix-service = "0.3.4" +actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-codec = "0.1.1" +bytes = "0.4" +futures = "0.1" +log =" 0.4" +percent-encoding = "1.0" +serde = "1.0" +serde_json = "1.0" +serde_urlencoded = "0.5.3" + +cookie = { version="0.11", features=["percent-encode"], optional = true } +openssl = { version="0.10", optional = true } + +[dev-dependencies] +env_logger = "0.6" +mime = "0.3" +actix-rt = "0.2.1" +actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } diff --git a/awc/src/builder.rs b/awc/src/builder.rs new file mode 100644 index 00000000..3104e524 --- /dev/null +++ b/awc/src/builder.rs @@ -0,0 +1,85 @@ +use std::cell::RefCell; +use std::fmt; +use std::rc::Rc; + +use actix_http::client::Connector; +use actix_http::http::{header::IntoHeaderValue, HeaderMap, HeaderName, HttpTryFrom}; + +use crate::connect::{Connect, ConnectorWrapper}; +use crate::Client; + +/// An HTTP Client builder +/// +/// This type can be used to construct an instance of `Client` through a +/// builder-like pattern. +pub struct ClientBuilder { + connector: Rc>, + default_headers: bool, + allow_redirects: bool, + max_redirects: usize, + headers: HeaderMap, +} + +impl ClientBuilder { + pub fn new() -> Self { + ClientBuilder { + default_headers: true, + allow_redirects: true, + max_redirects: 10, + headers: HeaderMap::new(), + connector: Rc::new(RefCell::new(ConnectorWrapper( + Connector::new().service(), + ))), + } + } + + /// Do not follow redirects. + /// + /// Redirects are allowed by default. + pub fn disable_redirects(mut self) -> Self { + self.allow_redirects = false; + self + } + + /// Set max number of redirects. + /// + /// Max redirects is set to 10 by default. + pub fn max_redirects(mut self, num: usize) -> Self { + self.max_redirects = num; + self + } + + /// Do not add default request headers. + /// By default `Accept-Encoding` and `User-Agent` headers are set. + pub fn skip_default_headers(mut self) -> Self { + self.default_headers = false; + self + } + + /// Add default header. This header adds to every request. + pub fn header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + >::Error: fmt::Debug, + V: IntoHeaderValue, + V::Error: fmt::Debug, + { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.headers.append(key, value); + } + Err(e) => log::error!("Header value error: {:?}", e), + }, + Err(e) => log::error!("Header name error: {:?}", e), + } + self + } + + /// Finish build process and create `Client`. + pub fn finish(self) -> Client { + Client { + connector: self.connector, + } + } +} diff --git a/awc/src/connect.rs b/awc/src/connect.rs new file mode 100644 index 00000000..c52bc34c --- /dev/null +++ b/awc/src/connect.rs @@ -0,0 +1,38 @@ +use actix_http::body::Body; +use actix_http::client::{ClientResponse, ConnectError, Connection, SendRequestError}; +use actix_http::{http, RequestHead}; +use actix_service::Service; +use futures::Future; + +pub(crate) struct ConnectorWrapper(pub T); + +pub(crate) trait Connect { + fn send_request( + &mut self, + head: RequestHead, + body: Body, + ) -> Box>; +} + +impl Connect for ConnectorWrapper +where + T: Service, + T::Response: Connection, + ::Future: 'static, + T::Future: 'static, +{ + fn send_request( + &mut self, + head: RequestHead, + body: Body, + ) -> Box> { + Box::new( + self.0 + // connect to the host + .call(head.uri.clone()) + .from_err() + // send request + .and_then(move |connection| connection.send_request(head, body)), + ) + } +} diff --git a/awc/src/lib.rs b/awc/src/lib.rs new file mode 100644 index 00000000..b1a309c4 --- /dev/null +++ b/awc/src/lib.rs @@ -0,0 +1,120 @@ +use std::cell::RefCell; +use std::rc::Rc; + +pub use actix_http::client::{ + ClientResponse, ConnectError, InvalidUrl, SendRequestError, +}; +pub use actix_http::http; + +use actix_http::client::Connector; +use actix_http::http::{HttpTryFrom, Method, Uri}; + +mod builder; +mod connect; +mod request; + +pub use self::builder::ClientBuilder; +pub use self::request::ClientRequest; + +use self::connect::{Connect, ConnectorWrapper}; + +/// An HTTP Client Request +/// +/// ```rust +/// # use futures::future::{Future, lazy}; +/// use actix_rt::System; +/// use awc::Client; +/// +/// fn main() { +/// System::new("test").block_on(lazy(|| { +/// let mut client = Client::default(); +/// +/// client.get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .send() // <- Send http request +/// .map_err(|_| ()) +/// .and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// Ok(()) +/// }) +/// })); +/// } +/// ``` +#[derive(Clone)] +pub struct Client { + pub(crate) connector: Rc>, +} + +impl Default for Client { + fn default() -> Self { + Client { + connector: Rc::new(RefCell::new(ConnectorWrapper( + Connector::new().service(), + ))), + } + } +} + +impl Client { + /// Build client instance. + pub fn build() -> ClientBuilder { + ClientBuilder::new() + } + + /// Construct HTTP request. + pub fn request(&self, method: Method, url: U) -> ClientRequest + where + Uri: HttpTryFrom, + { + ClientRequest::new(method, url, self.connector.clone()) + } + + pub fn get(&self, url: U) -> ClientRequest + where + Uri: HttpTryFrom, + { + ClientRequest::new(Method::GET, url, self.connector.clone()) + } + + pub fn head(&self, url: U) -> ClientRequest + where + Uri: HttpTryFrom, + { + ClientRequest::new(Method::HEAD, url, self.connector.clone()) + } + + pub fn put(&self, url: U) -> ClientRequest + where + Uri: HttpTryFrom, + { + ClientRequest::new(Method::PUT, url, self.connector.clone()) + } + + pub fn post(&self, url: U) -> ClientRequest + where + Uri: HttpTryFrom, + { + ClientRequest::new(Method::POST, url, self.connector.clone()) + } + + pub fn patch(&self, url: U) -> ClientRequest + where + Uri: HttpTryFrom, + { + ClientRequest::new(Method::PATCH, url, self.connector.clone()) + } + + pub fn delete(&self, url: U) -> ClientRequest + where + Uri: HttpTryFrom, + { + ClientRequest::new(Method::DELETE, url, self.connector.clone()) + } + + pub fn options(&self, url: U) -> ClientRequest + where + Uri: HttpTryFrom, + { + ClientRequest::new(Method::OPTIONS, url, self.connector.clone()) + } +} diff --git a/awc/src/request.rs b/awc/src/request.rs new file mode 100644 index 00000000..90dfebcc --- /dev/null +++ b/awc/src/request.rs @@ -0,0 +1,471 @@ +use std::cell::RefCell; +use std::fmt; +use std::io::Write; +use std::rc::Rc; + +use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "cookies")] +use cookie::{Cookie, CookieJar}; +use futures::future::{err, Either}; +use futures::{Future, Stream}; +use serde::Serialize; +use serde_json; + +use actix_http::body::{Body, BodyStream}; +use actix_http::client::{ClientResponse, InvalidUrl, SendRequestError}; +use actix_http::http::header::{self, Header, IntoHeaderValue}; +use actix_http::http::{ + uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom, + Method, Uri, Version, +}; +use actix_http::{Error, Head, RequestHead}; + +use crate::Connect; + +/// An HTTP Client request builder +/// +/// This type can be used to construct an instance of `ClientRequest` through a +/// builder-like pattern. +/// +/// ```rust +/// use futures::future::{Future, lazy}; +/// use actix_rt::System; +/// use actix_http::client; +/// +/// fn main() { +/// System::new("test").block_on(lazy(|| { +/// let mut connector = client::Connector::new().service(); +/// +/// client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .finish().unwrap() +/// .send(&mut connector) // <- Send http request +/// .map_err(|_| ()) +/// .and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// Ok(()) +/// }) +/// })); +/// } +/// ``` +pub struct ClientRequest { + head: RequestHead, + err: Option, + #[cfg(feature = "cookies")] + cookies: Option, + default_headers: bool, + connector: Rc>, +} + +impl ClientRequest { + /// Create new client request builder. + pub(crate) fn new( + method: Method, + uri: U, + connector: Rc>, + ) -> Self + where + Uri: HttpTryFrom, + { + let mut err = None; + let mut head = RequestHead::default(); + head.method = method; + + match Uri::try_from(uri) { + Ok(uri) => head.uri = uri, + Err(e) => err = Some(e.into()), + } + + ClientRequest { + head, + err, + connector, + #[cfg(feature = "cookies")] + cookies: None, + default_headers: true, + } + } + + /// Set HTTP method of this request. + #[inline] + pub fn method(mut self, method: Method) -> Self { + self.head.method = method; + self + } + + #[doc(hidden)] + /// Set HTTP version of this request. + /// + /// By default requests's HTTP version depends on network stream + #[inline] + pub fn version(mut self, version: Version) -> Self { + self.head.version = version; + self + } + + /// Set a header. + /// + /// ```rust + /// fn main() { + /// # actix_rt::System::new("test").block_on(futures::future::lazy(|| { + /// let req = awc::Client::new() + /// .get("http://www.rust-lang.org") + /// .set(awc::http::header::Date::now()) + /// .set(awc::http::header::ContentType(mime::TEXT_HTML)); + /// # Ok::<_, ()>(()) + /// # })); + /// } + /// ``` + pub fn set(mut self, hdr: H) -> Self { + match hdr.try_into() { + Ok(value) => { + self.head.headers.insert(H::name(), value); + } + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Append a header. + /// + /// Header gets appended to existing header. + /// To override header use `set_header()` method. + /// + /// ```rust + /// # extern crate actix_http; + /// # + /// use actix_http::{client, http}; + /// + /// fn main() { + /// let req = client::ClientRequest::build() + /// .header("X-TEST", "value") + /// .header(http::header::CONTENT_TYPE, "application/json") + /// .finish() + /// .unwrap(); + /// } + /// ``` + pub fn header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.head.headers.append(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Insert a header, replaces existing header. + pub fn set_header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.head.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Insert a header only if it is not yet set. + pub fn set_header_if_none(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + match HeaderName::try_from(key) { + Ok(key) => { + if !self.head.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + self.head.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + } + } + } + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Close connection + #[inline] + pub fn close_connection(mut self) -> Self { + self.head.set_connection_type(ConnectionType::Close); + self + } + + /// Set request's content type + #[inline] + pub fn content_type(mut self, value: V) -> Self + where + HeaderValue: HttpTryFrom, + { + match HeaderValue::try_from(value) { + Ok(value) => { + let _ = self.head.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Set content length + #[inline] + pub fn content_length(self, len: u64) -> Self { + let mut wrt = BytesMut::new().writer(); + let _ = write!(wrt, "{}", len); + self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) + } + + #[cfg(feature = "cookies")] + /// Set a cookie + /// + /// ```rust + /// # use actix_rt::System; + /// # use futures::future::{lazy, Future}; + /// fn main() { + /// System::new("test").block_on(lazy(|| { + /// awc::Client::new().get("https://www.rust-lang.org") + /// .cookie( + /// awc::http::Cookie::build("name", "value") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .secure(true) + /// .http_only(true) + /// .finish(), + /// ) + /// .send() + /// .map_err(|_| ()) + /// .and_then(|response| { + /// println!("Response: {:?}", response); + /// Ok(()) + /// }) + /// })); + /// } + /// ``` + pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { + if self.cookies.is_none() { + let mut jar = CookieJar::new(); + jar.add(cookie.into_owned()); + self.cookies = Some(jar) + } else { + self.cookies.as_mut().unwrap().add(cookie.into_owned()); + } + self + } + + /// Do not add default request headers. + /// By default `Accept-Encoding` and `User-Agent` headers are set. + pub fn no_default_headers(mut self) -> Self { + self.default_headers = false; + self + } + + /// This method calls provided closure with builder reference if + /// value is `true`. + pub fn if_true(mut self, value: bool, f: F) -> Self + where + F: FnOnce(&mut ClientRequest), + { + if value { + f(&mut self); + } + self + } + + /// This method calls provided closure with builder reference if + /// value is `Some`. + pub fn if_some(mut self, value: Option, f: F) -> Self + where + F: FnOnce(T, &mut ClientRequest), + { + if let Some(val) = value { + f(val, &mut self); + } + self + } + + /// Complete request construction and send body. + pub fn send_body( + mut self, + body: B, + ) -> impl Future + where + B: Into, + { + if let Some(e) = self.err.take() { + return Either::A(err(e.into())); + } + + let mut slf = if self.default_headers { + // enable br only for https + let https = self + .head + .uri + .scheme_part() + .map(|s| s == &uri::Scheme::HTTPS) + .unwrap_or(true); + + let mut slf = if https { + self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate") + } else { + self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + }; + + // set request host header + if let Some(host) = slf.head.uri.host() { + if !slf.head.headers.contains_key(header::HOST) { + let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); + + let _ = match slf.head.uri.port_u16() { + None | Some(80) | Some(443) => write!(wrt, "{}", host), + Some(port) => write!(wrt, "{}:{}", host, port), + }; + + match wrt.get_mut().take().freeze().try_into() { + Ok(value) => { + slf.head.headers.insert(header::HOST, value); + } + Err(e) => slf.err = Some(e.into()), + } + } + } + + // user agent + slf.set_header_if_none( + header::USER_AGENT, + concat!("actix-http/", env!("CARGO_PKG_VERSION")), + ) + } else { + self + }; + + #[allow(unused_mut)] + let mut head = slf.head; + + #[cfg(feature = "cookies")] + { + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + use std::fmt::Write; + + // set cookies + if let Some(ref mut jar) = slf.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = + percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } + } + + let uri = head.uri.clone(); + + // validate uri + if uri.host().is_none() { + Either::A(err(InvalidUrl::MissingHost.into())) + } else if uri.scheme_part().is_none() { + Either::A(err(InvalidUrl::MissingScheme.into())) + } else if let Some(scheme) = uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" | "https" | "wss" => { + Either::B(slf.connector.borrow_mut().send_request(head, body.into())) + } + _ => Either::A(err(InvalidUrl::UnknownScheme.into())), + } + } else { + Either::A(err(InvalidUrl::UnknownScheme.into())) + } + } + + /// Set a JSON body and generate `ClientRequest` + pub fn send_json( + self, + value: T, + ) -> impl Future { + let body = match serde_json::to_string(&value) { + Ok(body) => body, + Err(e) => return Either::A(err(Error::from(e).into())), + }; + // set content-type + let slf = if !self.head.headers.contains_key(header::CONTENT_TYPE) { + self.header(header::CONTENT_TYPE, "application/json") + } else { + self + }; + + Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) + } + + /// Set a urlencoded body and generate `ClientRequest` + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn send_form( + self, + value: T, + ) -> impl Future { + let body = match serde_urlencoded::to_string(&value) { + Ok(body) => body, + Err(e) => return Either::A(err(Error::from(e).into())), + }; + + let slf = if !self.head.headers.contains_key(header::CONTENT_TYPE) { + self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") + } else { + self + }; + + Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) + } + + /// Set an streaming body and generate `ClientRequest`. + pub fn send_stream( + self, + stream: S, + ) -> impl Future + where + S: Stream + 'static, + E: Into + 'static, + { + self.send_body(Body::from_message(BodyStream::new(stream))) + } + + /// Set an empty body and generate `ClientRequest`. + pub fn send(self) -> impl Future { + self.send_body(Body::Empty) + } +} + +impl fmt::Debug for ClientRequest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "\nClientRequest {:?} {}:{}", + self.head.version, self.head.method, self.head.uri + )?; + writeln!(f, " headers:")?; + for (key, val) in self.head.headers.iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8ae7156c..1bf29213 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,7 @@ //! //! ## Package feature //! +//! * `client` - enables http client //! * `tls` - enables ssl support via `native-tls` crate //! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` //! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` @@ -105,6 +106,9 @@ extern crate actix_web_codegen; #[doc(hidden)] pub use actix_web_codegen::*; +#[cfg(feature = "client")] +pub use awc as client; + // re-export for convenience pub use actix_http::Response as HttpResponse; pub use actix_http::{http, Error, HttpMessage, ResponseError, Result}; From 254b61e8002fc45446c24662f47e49749b1157d6 Mon Sep 17 00:00:00 2001 From: Max Frai Date: Tue, 26 Mar 2019 18:07:19 +0200 Subject: [PATCH 1125/1635] Fix copy/paste mistake in error message (#733) --- actix-web-codegen/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 13d1b97f..16123930 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -48,7 +48,7 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); if args.is_empty() { - panic!("invalid server definition, expected: #[get(\"some path\")]"); + panic!("invalid server definition, expected: #[post(\"some path\")]"); } // path @@ -85,7 +85,7 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); if args.is_empty() { - panic!("invalid server definition, expected: #[get(\"some path\")]"); + panic!("invalid server definition, expected: #[put(\"some path\")]"); } // path From cc24c77acc992c06a57dd13f3316020f7a90cf6d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 09:11:27 -0700 Subject: [PATCH 1126/1635] add Client::new() --- awc/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/awc/src/lib.rs b/awc/src/lib.rs index b1a309c4..885af48e 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -56,6 +56,11 @@ impl Default for Client { } impl Client { + /// Create new client instance with default settings. + pub fn new() -> Client { + Client::default() + } + /// Build client instance. pub fn build() -> ClientBuilder { ClientBuilder::new() From b254113d9fcdf0a9c156f5d8868051e9088aa8c1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 11:41:38 -0700 Subject: [PATCH 1127/1635] move high level client code from actix-http --- awc/Cargo.toml | 1 - awc/src/builder.rs | 19 ++++++++- awc/src/connect.rs | 7 ++- awc/src/lib.rs | 6 +-- awc/src/request.rs | 24 +++++------ awc/src/response.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 awc/src/response.rs diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f316d062..b43a8d47 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -29,7 +29,6 @@ cookies = ["cookie", "actix-http/cookies"] [dependencies] actix-service = "0.3.4" actix-http = { git = "https://github.com/actix/actix-http.git" } -actix-codec = "0.1.1" bytes = "0.4" futures = "0.1" log =" 0.4" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 3104e524..68694868 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -2,8 +2,11 @@ use std::cell::RefCell; use std::fmt; use std::rc::Rc; -use actix_http::client::Connector; -use actix_http::http::{header::IntoHeaderValue, HeaderMap, HeaderName, HttpTryFrom}; +use actix_http::client::{ConnectError, Connection, Connector}; +use actix_http::http::{ + header::IntoHeaderValue, HeaderMap, HeaderName, HttpTryFrom, Uri, +}; +use actix_service::Service; use crate::connect::{Connect, ConnectorWrapper}; use crate::Client; @@ -33,6 +36,18 @@ impl ClientBuilder { } } + /// Use custom connector service. + pub fn connector(mut self, connector: T) -> Self + where + T: Service + 'static, + T::Response: Connection, + ::Future: 'static, + T::Future: 'static, + { + self.connector = Rc::new(RefCell::new(ConnectorWrapper(connector))); + self + } + /// Do not follow redirects. /// /// Redirects are allowed by default. diff --git a/awc/src/connect.rs b/awc/src/connect.rs index c52bc34c..a0766279 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,9 +1,11 @@ use actix_http::body::Body; -use actix_http::client::{ClientResponse, ConnectError, Connection, SendRequestError}; +use actix_http::client::{ConnectError, Connection, SendRequestError}; use actix_http::{http, RequestHead}; use actix_service::Service; use futures::Future; +use crate::response::ClientResponse; + pub(crate) struct ConnectorWrapper(pub T); pub(crate) trait Connect { @@ -32,7 +34,8 @@ where .call(head.uri.clone()) .from_err() // send request - .and_then(move |connection| connection.send_request(head, body)), + .and_then(move |connection| connection.send_request(head, body)) + .map(|(head, payload)| ClientResponse::new(head, payload)), ) } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 885af48e..89acf7d5 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,9 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; -pub use actix_http::client::{ - ClientResponse, ConnectError, InvalidUrl, SendRequestError, -}; +pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; pub use actix_http::http; use actix_http::client::Connector; @@ -12,9 +10,11 @@ use actix_http::http::{HttpTryFrom, Method, Uri}; mod builder; mod connect; mod request; +mod response; pub use self::builder::ClientBuilder; pub use self::request::ClientRequest; +pub use self::response::ClientResponse; use self::connect::{Connect, ConnectorWrapper}; diff --git a/awc/src/request.rs b/awc/src/request.rs index 90dfebcc..f23aa7ef 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -12,7 +12,7 @@ use serde::Serialize; use serde_json; use actix_http::body::{Body, BodyStream}; -use actix_http::client::{ClientResponse, InvalidUrl, SendRequestError}; +use actix_http::client::{InvalidUrl, SendRequestError}; use actix_http::http::header::{self, Header, IntoHeaderValue}; use actix_http::http::{ uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom, @@ -20,6 +20,7 @@ use actix_http::http::{ }; use actix_http::{Error, Head, RequestHead}; +use crate::response::ClientResponse; use crate::Connect; /// An HTTP Client request builder @@ -30,18 +31,15 @@ use crate::Connect; /// ```rust /// use futures::future::{Future, lazy}; /// use actix_rt::System; -/// use actix_http::client; /// /// fn main() { /// System::new("test").block_on(lazy(|| { -/// let mut connector = client::Connector::new().service(); -/// -/// client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder +/// awc::Client::new() +/// .get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send(&mut connector) // <- Send http request +/// .send() // <- Send http request /// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response +/// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); /// Ok(()) /// }) @@ -137,11 +135,13 @@ impl ClientRequest { /// use actix_http::{client, http}; /// /// fn main() { - /// let req = client::ClientRequest::build() + /// # actix_rt::System::new("test").block_on(futures::future::lazy(|| { + /// let req = awc::Client::new() + /// .get("http://www.rust-lang.org") /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// .unwrap(); + /// .header(http::header::CONTENT_TYPE, "application/json"); + /// # Ok::<_, ()>(()) + /// # })); /// } /// ``` pub fn header(mut self, key: K, value: V) -> Self diff --git a/awc/src/response.rs b/awc/src/response.rs new file mode 100644 index 00000000..0ae66df0 --- /dev/null +++ b/awc/src/response.rs @@ -0,0 +1,102 @@ +use std::cell::{Ref, RefMut}; +use std::fmt; + +use bytes::Bytes; +use futures::{Poll, Stream}; + +use actix_http::error::PayloadError; +use actix_http::http::{HeaderMap, StatusCode, Version}; +use actix_http::{Extensions, Head, HttpMessage, Payload, PayloadStream, ResponseHead}; + +/// Client Response +pub struct ClientResponse { + pub(crate) head: ResponseHead, + pub(crate) payload: Payload, +} + +impl HttpMessage for ClientResponse { + type Stream = PayloadStream; + + fn headers(&self) -> &HeaderMap { + &self.head.headers + } + + fn extensions(&self) -> Ref { + self.head.extensions() + } + + fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() + } + + fn take_payload(&mut self) -> Payload { + std::mem::replace(&mut self.payload, Payload::None) + } +} + +impl ClientResponse { + /// Create new Request instance + pub(crate) fn new(head: ResponseHead, payload: Payload) -> ClientResponse { + ClientResponse { head, payload } + } + + #[inline] + pub(crate) fn head(&self) -> &ResponseHead { + &self.head + } + + #[inline] + pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { + &mut self.head + } + + /// Read the Request Version. + #[inline] + pub fn version(&self) -> Version { + self.head().version + } + + /// Get the status from the server. + #[inline] + pub fn status(&self) -> StatusCode { + self.head().status + } + + #[inline] + /// Returns Request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head().headers + } + + #[inline] + /// Returns mutable Request's headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head_mut().headers + } + + /// Checks if a connection should be kept alive. + #[inline] + pub fn keep_alive(&self) -> bool { + self.head().keep_alive() + } +} + +impl Stream for ClientResponse { + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + self.payload.poll() + } +} + +impl fmt::Debug for ClientResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; + writeln!(f, " headers:")?; + for (key, val) in self.headers().iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} From 2c7da28ef9fab2265c8149daedfdfc60b7ba3641 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 11:43:22 -0700 Subject: [PATCH 1128/1635] move high level client code to awc crate --- src/client/connection.rs | 10 +- src/client/h1proto.rs | 38 +-- src/client/h2proto.rs | 9 +- src/client/mod.rs | 4 - src/client/request.rs | 699 --------------------------------------- src/client/response.rs | 116 ------- src/h1/client.rs | 9 +- src/h1/decoder.rs | 15 +- src/h1/dispatcher.rs | 2 +- src/lib.rs | 62 +--- src/response.rs | 2 - src/ws/client/connect.rs | 37 ++- src/ws/client/service.rs | 50 +-- test-server/Cargo.toml | 3 +- test-server/src/lib.rs | 121 ++++--- tests/test_client.rs | 31 +- tests/test_server.rs | 129 +++----- 17 files changed, 211 insertions(+), 1126 deletions(-) delete mode 100644 src/client/request.rs delete mode 100644 src/client/response.rs diff --git a/src/client/connection.rs b/src/client/connection.rs index 8de23bd2..e8c1201a 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -6,11 +6,11 @@ use futures::Future; use h2::client::SendRequest; use crate::body::MessageBody; -use crate::message::RequestHead; +use crate::message::{RequestHead, ResponseHead}; +use crate::payload::Payload; use super::error::SendRequestError; use super::pool::Acquired; -use super::response::ClientResponse; use super::{h1proto, h2proto}; pub(crate) enum ConnectionType { @@ -19,7 +19,7 @@ pub(crate) enum ConnectionType { } pub trait Connection { - type Future: Future; + type Future: Future; /// Send request and body fn send_request( @@ -80,7 +80,7 @@ impl Connection for IoConnection where T: AsyncRead + AsyncWrite + 'static, { - type Future = Box>; + type Future = Box>; fn send_request( mut self, @@ -117,7 +117,7 @@ where A: AsyncRead + AsyncWrite + 'static, B: AsyncRead + AsyncWrite + 'static, { - type Future = Box>; + type Future = Box>; fn send_request( self, diff --git a/src/client/h1proto.rs b/src/client/h1proto.rs index 34521cc2..2e29484f 100644 --- a/src/client/h1proto.rs +++ b/src/client/h1proto.rs @@ -2,17 +2,18 @@ use std::{io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use bytes::Bytes; -use futures::future::{err, ok, Either}; +use futures::future::{ok, Either}; use futures::{Async, Future, Poll, Sink, Stream}; +use crate::error::PayloadError; +use crate::h1; +use crate::message::{RequestHead, ResponseHead}; +use crate::payload::{Payload, PayloadStream}; + use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; -use super::response::ClientResponse; use crate::body::{BodyLength, MessageBody}; -use crate::error::PayloadError; -use crate::h1; -use crate::message::RequestHead; pub(crate) fn send_request( io: T, @@ -20,7 +21,7 @@ pub(crate) fn send_request( body: B, created: time::Instant, pool: Option>, -) -> impl Future +) -> impl Future where T: AsyncRead + AsyncWrite + 'static, B: MessageBody, @@ -50,19 +51,20 @@ where .into_future() .map_err(|(e, _)| SendRequestError::from(e)) .and_then(|(item, framed)| { - if let Some(mut res) = item { + if let Some(res) = item { match framed.get_codec().message_type() { h1::MessageType::None => { let force_close = !framed.get_codec().keepalive(); - release_connection(framed, force_close) + release_connection(framed, force_close); + Ok((res, Payload::None)) } _ => { - res.set_payload(Payload::stream(framed).into()); + let pl: PayloadStream = Box::new(PlStream::new(framed)); + Ok((res, pl.into())) } } - ok(res) } else { - err(ConnectError::Disconnected.into()) + Err(ConnectError::Disconnected.into()) } }) }) @@ -199,21 +201,19 @@ where } } -pub(crate) struct Payload { +pub(crate) struct PlStream { framed: Option>, } -impl Payload { - pub fn stream( - framed: Framed, - ) -> Box> { - Box::new(Payload { +impl PlStream { + fn new(framed: Framed) -> Self { + PlStream { framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), - }) + } } } -impl Stream for Payload { +impl Stream for PlStream { type Item = Bytes; type Error = PayloadError; diff --git a/src/client/h2proto.rs b/src/client/h2proto.rs index bf2d3e1b..9ad72262 100644 --- a/src/client/h2proto.rs +++ b/src/client/h2proto.rs @@ -9,13 +9,12 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; use crate::body::{BodyLength, MessageBody}; -use crate::message::{Message, RequestHead, ResponseHead}; +use crate::message::{RequestHead, ResponseHead}; use crate::payload::Payload; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; use super::pool::Acquired; -use super::response::ClientResponse; pub(crate) fn send_request( io: SendRequest, @@ -23,7 +22,7 @@ pub(crate) fn send_request( body: B, created: time::Instant, pool: Option>, -) -> impl Future +) -> impl Future where T: AsyncRead + AsyncWrite + 'static, B: MessageBody, @@ -105,12 +104,12 @@ where let (parts, body) = resp.into_parts(); let payload = if head_req { Payload::None } else { body.into() }; - let mut head: Message = Message::new(); + let mut head = ResponseHead::default(); head.version = parts.version; head.status = parts.status; head.headers = parts.headers; - Ok(ClientResponse { head, payload }) + Ok((head, payload)) }) .from_err() } diff --git a/src/client/mod.rs b/src/client/mod.rs index 86b1a0cc..87c37474 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -5,11 +5,7 @@ mod error; mod h1proto; mod h2proto; mod pool; -mod request; -mod response; pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; -pub use self::request::{ClientRequest, ClientRequestBuilder}; -pub use self::response::ClientResponse; diff --git a/src/client/request.rs b/src/client/request.rs deleted file mode 100644 index 134a4264..00000000 --- a/src/client/request.rs +++ /dev/null @@ -1,699 +0,0 @@ -use std::fmt; -use std::io::Write; - -use actix_service::Service; -use bytes::{BufMut, Bytes, BytesMut}; -#[cfg(feature = "cookies")] -use cookie::{Cookie, CookieJar}; -use futures::future::{err, Either}; -use futures::{Future, Stream}; -use serde::Serialize; -use serde_json; - -use crate::body::{BodyStream, MessageBody}; -use crate::error::Error; -use crate::header::{self, Header, IntoHeaderValue}; -use crate::http::{ - uri, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, - Uri, Version, -}; -use crate::message::{ConnectionType, Head, RequestHead}; - -use super::connection::Connection; -use super::error::{ConnectError, InvalidUrl, SendRequestError}; -use super::response::ClientResponse; - -/// An HTTP Client Request -/// -/// ```rust -/// use futures::future::{Future, lazy}; -/// use actix_rt::System; -/// use actix_http::client; -/// -/// fn main() { -/// System::new("test").block_on(lazy(|| { -/// let mut connector = client::Connector::new().service(); -/// -/// client::ClientRequest::get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .finish().unwrap() -/// .send(&mut connector) // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// Ok(()) -/// }) -/// })); -/// } -/// ``` -pub struct ClientRequest { - head: RequestHead, - body: B, -} - -impl ClientRequest<()> { - /// Create client request builder - pub fn build() -> ClientRequestBuilder { - ClientRequestBuilder { - head: Some(RequestHead::default()), - err: None, - #[cfg(feature = "cookies")] - cookies: None, - default_headers: true, - } - } - - /// Create request builder for `GET` request - pub fn get(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::GET).uri(uri); - builder - } - - /// Create request builder for `HEAD` request - pub fn head(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::HEAD).uri(uri); - builder - } - - /// Create request builder for `POST` request - pub fn post(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::POST).uri(uri); - builder - } - - /// Create request builder for `PUT` request - pub fn put(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::PUT).uri(uri); - builder - } - - /// Create request builder for `DELETE` request - pub fn delete(uri: U) -> ClientRequestBuilder - where - Uri: HttpTryFrom, - { - let mut builder = ClientRequest::build(); - builder.method(Method::DELETE).uri(uri); - builder - } -} - -impl ClientRequest -where - B: MessageBody, -{ - /// Create new client request - pub fn new(head: RequestHead, body: B) -> Self { - ClientRequest { head, body } - } - - /// Get the request URI - #[inline] - pub fn uri(&self) -> &Uri { - &self.head.uri - } - - /// Set client request URI - #[inline] - pub fn set_uri(&mut self, uri: Uri) { - self.head.uri = uri - } - - /// Get the request method - #[inline] - pub fn method(&self) -> &Method { - &self.head.method - } - - /// Set HTTP `Method` for the request - #[inline] - pub fn set_method(&mut self, method: Method) { - self.head.method = method - } - - /// Get HTTP version for the request - #[inline] - pub fn version(&self) -> Version { - self.head.version - } - - /// Set http `Version` for the request - #[inline] - pub fn set_version(&mut self, version: Version) { - self.head.version = version - } - - /// Get the headers from the request - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head.headers - } - - /// Deconstruct ClientRequest to a RequestHead and body tuple - pub fn into_parts(self) -> (RequestHead, B) { - (self.head, self.body) - } - - // Send request - /// - /// This method returns a future that resolves to a ClientResponse - pub fn send( - self, - connector: &mut T, - ) -> impl Future - where - B: 'static, - T: Service, - T::Response: Connection, - { - let Self { head, body } = self; - - let uri = head.uri.clone(); - - // validate uri - if uri.host().is_none() { - Either::A(err(InvalidUrl::MissingHost.into())) - } else if uri.scheme_part().is_none() { - Either::A(err(InvalidUrl::MissingScheme.into())) - } else if let Some(scheme) = uri.scheme_part() { - match scheme.as_str() { - "http" | "ws" | "https" | "wss" => Either::B( - connector - // connect to the host - .call(uri) - .from_err() - // send request - .and_then(move |connection| connection.send_request(head, body)), - ), - _ => Either::A(err(InvalidUrl::UnknownScheme.into())), - } - } else { - Either::A(err(InvalidUrl::UnknownScheme.into())) - } - } -} - -impl fmt::Debug for ClientRequest -where - B: MessageBody, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!( - f, - "\nClientRequest {:?} {}:{}", - self.head.version, self.head.method, self.head.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in self.head.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -/// An HTTP Client request builder -/// -/// This type can be used to construct an instance of `ClientRequest` through a -/// builder-like pattern. -pub struct ClientRequestBuilder { - head: Option, - err: Option, - #[cfg(feature = "cookies")] - cookies: Option, - default_headers: bool, -} - -impl ClientRequestBuilder { - /// Set HTTP URI of request. - #[inline] - pub fn uri(&mut self, uri: U) -> &mut Self - where - Uri: HttpTryFrom, - { - match Uri::try_from(uri) { - Ok(uri) => { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.uri = uri; - } - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.method = method; - } - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn get_method(&mut self) -> &Method { - let parts = self.head.as_ref().expect("cannot reuse request builder"); - &parts.method - } - - /// Set HTTP version of this request. - /// - /// By default requests's HTTP version depends on network stream - #[inline] - pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.version = version; - } - self - } - - /// Set a header. - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_http; - /// # - /// use actix_http::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .set(http::header::Date::now()) - /// .set(http::header::ContentType(mime::TEXT_HTML)) - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - /// - /// ```rust - /// # extern crate actix_http; - /// # - /// use actix_http::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header. - pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header only if it is not yet set. - pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => { - if !parts.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Enable connection upgrade - #[inline] - pub fn upgrade(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Upgrade); - } - } - self.set_header(header::UPGRADE, value) - } - - /// Close connection - #[inline] - pub fn close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Close); - } - self - } - - /// Set request's content type - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - HeaderValue: HttpTryFrom, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderValue::try_from(value) { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content length - #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { - let mut wrt = BytesMut::new().writer(); - let _ = write!(wrt, "{}", len); - self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) - } - - #[cfg(feature = "cookies")] - /// Set a cookie - /// - /// ```rust - /// # extern crate actix_http; - /// use actix_http::{client, http}; - /// - /// fn main() { - /// let req = client::ClientRequest::build() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// .unwrap(); - /// } - /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - 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 - } - - /// Do not add default request headers. - /// By default `Accept-Encoding` and `User-Agent` headers are set. - pub fn no_default_headers(&mut self) -> &mut Self { - self.default_headers = false; - self - } - - /// This method calls provided closure with builder reference if - /// value is `true`. - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut ClientRequestBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if - /// value is `Some`. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut ClientRequestBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - - /// Set a body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn body( - &mut self, - body: B, - ) -> Result, HttpError> { - if let Some(e) = self.err.take() { - return Err(e); - } - - if self.default_headers { - // enable br only for https - let https = if let Some(parts) = parts(&mut self.head, &self.err) { - parts - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true) - } else { - true - }; - - if https { - self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate"); - } else { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); - } - - // set request host header - if let Some(parts) = parts(&mut self.head, &self.err) { - if let Some(host) = parts.uri.host() { - if !parts.headers.contains_key(header::HOST) { - let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - - let _ = match parts.uri.port_u16() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), - }; - - match wrt.get_mut().take().freeze().try_into() { - Ok(value) => { - parts.headers.insert(header::HOST, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - } - - // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("actix-http/", env!("CARGO_PKG_VERSION")), - ); - } - - #[allow(unused_mut)] - let mut head = self.head.take().expect("cannot reuse request builder"); - - #[cfg(feature = "cookies")] - { - use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; - use std::fmt::Write; - - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = - percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - } - Ok(ClientRequest { head, body }) - } - - /// Set a JSON body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn json( - &mut self, - value: T, - ) -> Result, Error> { - let body = serde_json::to_string(&value)?; - - let contains = if let Some(head) = parts(&mut self.head, &self.err) { - head.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/json"); - } - - Ok(self.body(body)?) - } - - /// Set a urlencoded body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn form( - &mut self, - value: T, - ) -> Result, Error> { - let body = serde_urlencoded::to_string(&value)?; - - let contains = if let Some(head) = parts(&mut self.head, &self.err) { - head.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded"); - } - - Ok(self.body(body)?) - } - - /// Set an streaming body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn stream( - &mut self, - stream: S, - ) -> Result, HttpError> - where - S: Stream, - E: Into + 'static, - { - self.body(BodyStream::new(stream)) - } - - /// Set an empty body and generate `ClientRequest`. - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn finish(&mut self) -> Result, HttpError> { - self.body(()) - } - - /// This method construct new `ClientRequestBuilder` - pub fn take(&mut self) -> ClientRequestBuilder { - ClientRequestBuilder { - head: self.head.take(), - err: self.err.take(), - #[cfg(feature = "cookies")] - cookies: self.cookies.take(), - default_headers: self.default_headers, - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut RequestHead> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl fmt::Debug for ClientRequestBuilder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref parts) = self.head { - writeln!( - f, - "\nClientRequestBuilder {:?} {}:{}", - parts.version, parts.method, parts.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in parts.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } else { - write!(f, "ClientRequestBuilder(Consumed)") - } - } -} diff --git a/src/client/response.rs b/src/client/response.rs deleted file mode 100644 index 7c6cdf64..00000000 --- a/src/client/response.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::cell::{Ref, RefMut}; -use std::fmt; - -use bytes::Bytes; -use futures::{Poll, Stream}; -use http::{HeaderMap, StatusCode, Version}; - -use crate::error::PayloadError; -use crate::extensions::Extensions; -use crate::httpmessage::HttpMessage; -use crate::message::{Head, Message, ResponseHead}; -use crate::payload::{Payload, PayloadStream}; - -/// Client Response -pub struct ClientResponse { - pub(crate) head: Message, - pub(crate) payload: Payload, -} - -impl HttpMessage for ClientResponse { - type Stream = PayloadStream; - - fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - fn extensions(&self) -> Ref { - self.head.extensions() - } - - fn extensions_mut(&self) -> RefMut { - self.head.extensions_mut() - } - - fn take_payload(&mut self) -> Payload { - std::mem::replace(&mut self.payload, Payload::None) - } -} - -impl ClientResponse { - /// Create new Request instance - pub fn new() -> ClientResponse { - let head: Message = Message::new(); - head.extensions_mut().clear(); - - ClientResponse { - head, - payload: Payload::None, - } - } - - #[inline] - pub(crate) fn head(&self) -> &ResponseHead { - &self.head - } - - #[inline] - pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { - &mut self.head - } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> Version { - self.head().version - } - - /// Get the status from the server. - #[inline] - pub fn status(&self) -> StatusCode { - self.head().status - } - - #[inline] - /// Returns Request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - #[inline] - /// Returns mutable Request's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - - /// Checks if a connection should be kept alive. - #[inline] - pub fn keep_alive(&self) -> bool { - self.head().keep_alive() - } - - /// Set response payload - pub fn set_payload(&mut self, payload: Payload) { - self.payload = payload; - } -} - -impl Stream for ClientResponse { - type Item = Bytes; - type Error = PayloadError; - - fn poll(&mut self) -> Poll, Self::Error> { - self.payload.poll() - } -} - -impl fmt::Debug for ClientResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} diff --git a/src/h1/client.rs b/src/h1/client.rs index 85185126..b3a5a5d9 100644 --- a/src/h1/client.rs +++ b/src/h1/client.rs @@ -13,11 +13,10 @@ use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; use super::{Message, MessageType}; use crate::body::BodyLength; -use crate::client::ClientResponse; use crate::config::ServiceConfig; use crate::error::{ParseError, PayloadError}; use crate::helpers; -use crate::message::{ConnectionType, Head, MessagePool, RequestHead}; +use crate::message::{ConnectionType, Head, MessagePool, RequestHead, ResponseHead}; bitflags! { struct Flags: u8 { @@ -41,7 +40,7 @@ pub struct ClientPayloadCodec { struct ClientCodecInner { config: ServiceConfig, - decoder: decoder::MessageDecoder, + decoder: decoder::MessageDecoder, payload: Option, version: Version, ctype: ConnectionType, @@ -123,14 +122,14 @@ impl ClientPayloadCodec { } impl Decoder for ClientCodec { - type Item = ClientResponse; + type Item = ResponseHead; type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - if let Some(ctype) = req.head().ctype { + if let Some(ctype) = req.ctype { // do not use peer's keep-alive self.inner.ctype = if ctype == ConnectionType::KeepAlive { self.inner.ctype diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 77b76c24..a1b221c0 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -9,9 +9,8 @@ use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version}; use httparse; use log::{debug, error, trace}; -use crate::client::ClientResponse; use crate::error::ParseError; -use crate::message::ConnectionType; +use crate::message::{ConnectionType, ResponseHead}; use crate::request::Request; const MAX_BUFFER_SIZE: usize = 131_072; @@ -227,13 +226,13 @@ impl MessageType for Request { } } -impl MessageType for ClientResponse { +impl MessageType for ResponseHead { fn set_connection_type(&mut self, ctype: Option) { - self.head.ctype = ctype; + self.ctype = ctype; } fn headers_mut(&mut self) -> &mut HeaderMap { - self.headers_mut() + &mut self.headers } fn decode(src: &mut BytesMut) -> Result, ParseError> { @@ -263,7 +262,7 @@ impl MessageType for ClientResponse { } }; - let mut msg = ClientResponse::new(); + let mut msg = ResponseHead::default(); // convert headers let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; @@ -281,8 +280,8 @@ impl MessageType for ClientResponse { PayloadType::None }; - msg.head.status = status; - msg.head.version = ver; + msg.status = status; + msg.version = ver; Ok(Some((msg, decoder))) } diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index afeabc82..09a17fcf 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -553,7 +553,7 @@ mod tests { use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::IntoService; - use bytes::{Buf, Bytes, BytesMut}; + use bytes::{Buf, Bytes}; use futures::future::{lazy, ok}; use super::*; diff --git a/src/lib.rs b/src/lib.rs index 85efe5e4..b41ce7ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,64 +1,4 @@ -//! Actix web is a small, pragmatic, and extremely fast web framework -//! for Rust. -//! -//! ```rust,ignore -//! use actix_web::{server, App, Path, Responder}; -//! # use std::thread; -//! -//! fn index(info: Path<(String, u32)>) -> impl Responder { -//! format!("Hello {}! id:{}", info.0, info.1) -//! } -//! -//! fn main() { -//! # thread::spawn(|| { -//! server::new(|| { -//! App::new().resource("/{name}/{id}/index.html", |r| r.with(index)) -//! }).bind("127.0.0.1:8080") -//! .unwrap() -//! .run(); -//! # }); -//! } -//! ``` -//! -//! ## Documentation & community resources -//! -//! Besides the API documentation (which you are currently looking -//! at!), several other resources are available: -//! -//! * [User Guide](https://actix.rs/docs/) -//! * [Chat on gitter](https://gitter.im/actix/actix) -//! * [GitHub repository](https://github.com/actix/actix-web) -//! * [Cargo package](https://crates.io/crates/actix-web) -//! -//! To get started navigating the API documentation you may want to -//! consider looking at the following pages: -//! -//! * [App](struct.App.html): This struct represents an actix-web -//! application and is used to configure routes and other common -//! settings. -//! -//! * [HttpServer](server/struct.HttpServer.html): This struct -//! represents an HTTP server instance and is used to instantiate and -//! configure servers. -//! -//! * [Request](struct.Request.html) and -//! [Response](struct.Response.html): These structs -//! represent HTTP requests and responses and expose various methods -//! for inspecting, creating and otherwise utilizing them. -//! -//! ## Features -//! -//! * Supported *HTTP/1.x* protocol -//! * Streaming and pipelining -//! * Keep-alive and slow requests handling -//! * `WebSockets` server/client -//! * Supported Rust version: 1.26 or later -//! -//! ## Package feature -//! -//! * `session` - enables session support, includes `ring` crate as -//! dependency -//! +//! Basic http primitives for actix-net framework. #![allow( clippy::type_complexity, clippy::new_without_default, diff --git a/src/response.rs b/src/response.rs index 5281c2d9..31c0010a 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1042,8 +1042,6 @@ mod tests { #[test] #[cfg(feature = "cookies")] fn test_into_builder() { - use crate::httpmessage::HttpMessage; - let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs index 5e877a64..2760967e 100644 --- a/src/ws/client/connect.rs +++ b/src/ws/client/connect.rs @@ -4,15 +4,15 @@ use std::str; #[cfg(feature = "cookies")] use cookie::Cookie; use http::header::{HeaderName, HeaderValue}; -use http::{Error as HttpError, HttpTryFrom}; +use http::{Error as HttpError, HttpTryFrom, Uri}; use super::ClientError; -use crate::client::{ClientRequest, ClientRequestBuilder}; use crate::header::IntoHeaderValue; +use crate::message::RequestHead; /// `WebSocket` connection pub struct Connect { - pub(super) request: ClientRequestBuilder, + pub(super) head: RequestHead, pub(super) err: Option, pub(super) http_err: Option, pub(super) origin: Option, @@ -25,7 +25,7 @@ impl Connect { /// Create new websocket connection pub fn new>(uri: S) -> Connect { let mut cl = Connect { - request: ClientRequest::build(), + head: RequestHead::default(), err: None, http_err: None, origin: None, @@ -33,7 +33,12 @@ impl Connect { max_size: 65_536, server_mode: false, }; - cl.request.uri(uri.as_ref()); + + match Uri::try_from(uri.as_ref()) { + Ok(uri) => cl.head.uri = uri, + Err(e) => cl.http_err = Some(e.into()), + } + cl } @@ -51,12 +56,12 @@ impl Connect { self } - #[cfg(feature = "cookies")] - /// Set cookie for handshake request - pub fn cookie(mut self, cookie: Cookie) -> Self { - self.request.cookie(cookie); - self - } + // #[cfg(feature = "cookies")] + // /// Set cookie for handshake request + // pub fn cookie(mut self, cookie: Cookie) -> Self { + // self.request.cookie(cookie); + // self + // } /// Set request Origin pub fn origin(mut self, origin: V) -> Self @@ -90,7 +95,15 @@ impl Connect { HeaderName: HttpTryFrom, V: IntoHeaderValue, { - self.request.header(key, value); + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.head.headers.append(key, value); + } + Err(e) => self.http_err = Some(e.into()), + }, + Err(e) => self.http_err = Some(e.into()), + } self } } diff --git a/src/ws/client/service.rs b/src/ws/client/service.rs index a0a9b203..8a0840f9 100644 --- a/src/ws/client/service.rs +++ b/src/ws/client/service.rs @@ -14,8 +14,8 @@ use rand; use sha1::Sha1; use crate::body::BodyLength; -use crate::client::ClientResponse; use crate::h1; +use crate::message::{ConnectionType, Head, ResponseHead}; use crate::ws::Codec; use super::{ClientError, Connect, Protocol}; @@ -89,27 +89,35 @@ where } else { // origin if let Some(origin) = req.origin.take() { - req.request.set_header(header::ORIGIN, origin); + req.head.headers.insert(header::ORIGIN, origin); } - req.request.upgrade("websocket"); - req.request.set_header(header::SEC_WEBSOCKET_VERSION, "13"); + req.head.set_connection_type(ConnectionType::Upgrade); + req.head + .headers + .insert(header::UPGRADE, HeaderValue::from_static("websocket")); + req.head.headers.insert( + header::SEC_WEBSOCKET_VERSION, + HeaderValue::from_static("13"), + ); if let Some(protocols) = req.protocols.take() { - req.request - .set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str()); + req.head.headers.insert( + header::SEC_WEBSOCKET_PROTOCOL, + HeaderValue::try_from(protocols.as_str()).unwrap(), + ); } - let mut request = match req.request.finish() { - Ok(req) => req, - Err(e) => return Either::A(err(e.into())), + if let Some(e) = req.http_err { + return Either::A(err(e.into())); }; - if request.uri().host().is_none() { + let mut request = req.head; + if request.uri.host().is_none() { return Either::A(err(ClientError::InvalidUrl)); } // supported protocols - let proto = if let Some(scheme) = request.uri().scheme_part() { + let proto = if let Some(scheme) = request.uri.scheme_part() { match Protocol::from(scheme.as_str()) { Some(proto) => proto, None => return Either::A(err(ClientError::InvalidUrl)), @@ -124,14 +132,14 @@ where let sec_key: [u8; 16] = rand::random(); let key = base64::encode(&sec_key); - request.headers_mut().insert( + request.headers.insert( header::SEC_WEBSOCKET_KEY, HeaderValue::try_from(key.as_str()).unwrap(), ); // prep connection - let connect = TcpConnect::new(request.uri().host().unwrap().to_string()) - .set_port(request.uri().port_u16().unwrap_or_else(|| proto.port())); + let connect = TcpConnect::new(request.uri.host().unwrap().to_string()) + .set_port(request.uri.port_u16().unwrap_or_else(|| proto.port())); let fut = Box::new( self.connector @@ -141,7 +149,7 @@ where // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed - .send((request.into_parts().0, BodyLength::None).into()) + .send((request, BodyLength::None).into()) .map_err(ClientError::from) .and_then(|framed| { framed @@ -172,7 +180,7 @@ where { fut: Box< Future< - Item = (Option, Framed), + Item = (Option, Framed), Error = ClientError, >, >, @@ -198,11 +206,11 @@ where }; // verify response - if res.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(ClientError::InvalidResponseStatus(res.status())); + if res.status != StatusCode::SWITCHING_PROTOCOLS { + return Err(ClientError::InvalidResponseStatus(res.status)); } // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = res.headers().get(header::UPGRADE) { + let has_hdr = if let Some(hdr) = res.headers.get(header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_lowercase().contains("websocket") } else { @@ -216,7 +224,7 @@ where return Err(ClientError::InvalidUpgradeHeader); } // Check for "CONNECTION" header - if let Some(conn) = res.headers().get(header::CONNECTION) { + if let Some(conn) = res.headers.get(header::CONNECTION) { if let Ok(s) = conn.to_str() { if !s.to_lowercase().contains("upgrade") { trace!("Invalid connection header: {}", s); @@ -231,7 +239,7 @@ where return Err(ClientError::MissingConnectionHeader); } - if let Some(key) = res.headers().get(header::SEC_WEBSOCKET_ACCEPT) { + if let Some(key) = res.headers.get(header::SEC_WEBSOCKET_ACCEPT) { // field is constructed by concatenating /key/ // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index b7535f99..313bc55c 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -29,7 +29,7 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl", "actix-http/ssl", "actix-server/ssl"] +ssl = ["openssl", "actix-http/ssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.1" @@ -38,6 +38,7 @@ actix-http = { path=".." } actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" +awc = { git = "https://github.com/actix/actix-web.git" } base64 = "0.10" bytes = "0.4" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 26bca787..77329e70 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -3,15 +3,12 @@ use std::sync::mpsc; use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::body::MessageBody; -use actix_http::client::{ - ClientRequest, ClientRequestBuilder, ClientResponse, ConnectError, Connection, - Connector, SendRequestError, -}; -use actix_http::{http::Uri, ws}; +use actix_http::client::Connector; +use actix_http::ws; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; use actix_service::Service; +use awc::{Client, ClientRequest}; use futures::future::{lazy, Future}; use http::Method; use net2::TcpBuilder; @@ -47,6 +44,7 @@ pub struct TestServer; pub struct TestServerRuntime { addr: net::SocketAddr, rt: Runtime, + client: Client, } impl TestServer { @@ -71,11 +69,39 @@ impl TestServer { }); let (system, addr) = rx.recv().unwrap(); + let mut rt = Runtime::new().unwrap(); + + let client = rt + .block_on(lazy(move || { + let connector = { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = + SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").map_err( + |e| log::error!("Can not set alpn protocol: {:?}", e), + ); + Connector::new() + .timeout(time::Duration::from_millis(500)) + .ssl(builder.build()) + .service() + } + #[cfg(not(feature = "ssl"))] + { + Connector::new() + .timeout(time::Duration::from_millis(500)) + .service() + } + }; + + Ok::(Client::build().connector(connector).finish()) + })) + .unwrap(); System::set_current(system); - TestServerRuntime { - addr, - rt: Runtime::new().unwrap(), - } + TestServerRuntime { addr, rt, client } } /// Get firat available unused address @@ -130,64 +156,38 @@ impl TestServerRuntime { } /// Create `GET` request - pub fn get(&self) -> ClientRequestBuilder { - ClientRequest::get(self.url("/").as_str()) + pub fn get(&self) -> ClientRequest { + self.client.get(self.url("/").as_str()) } /// Create https `GET` request - pub fn sget(&self) -> ClientRequestBuilder { - ClientRequest::get(self.surl("/").as_str()) + pub fn sget(&self) -> ClientRequest { + self.client.get(self.surl("/").as_str()) } /// Create `POST` request - pub fn post(&self) -> ClientRequestBuilder { - ClientRequest::post(self.url("/").as_str()) + pub fn post(&self) -> ClientRequest { + self.client.post(self.url("/").as_str()) + } + + /// Create https `POST` request + pub fn spost(&self) -> ClientRequest { + self.client.post(self.surl("/").as_str()) } /// Create `HEAD` request - pub fn head(&self) -> ClientRequestBuilder { - ClientRequest::head(self.url("/").as_str()) + pub fn head(&self) -> ClientRequest { + self.client.head(self.url("/").as_str()) + } + + /// Create https `HEAD` request + pub fn shead(&self) -> ClientRequest { + self.client.head(self.surl("/").as_str()) } /// Connect to test http server - pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { - ClientRequest::build() - .method(meth) - .uri(self.url(path).as_str()) - .take() - } - - fn new_connector( - ) -> impl Service + Clone - { - #[cfg(feature = "ssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Connector::new() - .timeout(time::Duration::from_millis(500)) - .ssl(builder.build()) - .service() - } - #[cfg(not(feature = "ssl"))] - { - Connector::new() - .timeout(time::Duration::from_millis(500)) - .service() - } - } - - /// Http connector - pub fn connector( - &mut self, - ) -> impl Service + Clone - { - self.execute(|| TestServerRuntime::new_connector()) + pub fn request>(&self, method: Method, path: S) -> ClientRequest { + self.client.request(method, path.as_ref()) } /// Stop http server @@ -213,15 +213,6 @@ impl TestServerRuntime { ) -> Result, ws::ClientError> { self.ws_at("/") } - - /// Send request and read response message - pub fn send_request( - &mut self, - req: ClientRequest, - ) -> Result { - let mut conn = self.connector(); - self.rt.block_on(req.send(&mut conn)) - } } impl Drop for TestServerRuntime { diff --git a/tests/test_client.rs b/tests/test_client.rs index 2832b1b7..1ca7437d 100644 --- a/tests/test_client.rs +++ b/tests/test_client.rs @@ -4,7 +4,7 @@ use futures::future::{self, ok}; use futures::{Future, Stream}; use actix_http::{ - client, error::PayloadError, HttpMessage, HttpService, Request, Response, + error::PayloadError, http, HttpMessage, HttpService, Request, Response, }; use actix_http_test::TestServer; @@ -48,26 +48,18 @@ fn test_h1_v2() { .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let mut connector = srv.connector(); - - let request = srv.get().finish().unwrap(); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); - let request = srv.get().header("x-test", "111").finish().unwrap(); - let repr = format!("{:?}", request); - assert!(repr.contains("ClientRequest")); - assert!(repr.contains("x-test")); - - let mut response = srv.block_on(request.send(&mut connector)).unwrap(); + let request = srv.get().header("x-test", "111").send(); + let mut response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); // read response let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let request = srv.post().finish().unwrap(); - let mut response = srv.block_on(request.send(&mut connector)).unwrap(); + let mut response = srv.block_on(srv.post().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -82,10 +74,7 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let mut connector = srv.connector(); - - let request = srv.get().close().finish().unwrap(); - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let response = srv.block_on(srv.get().close_connection().send()).unwrap(); assert!(response.status().is_success()); } @@ -102,12 +91,8 @@ fn test_with_query_parameter() { }) .map(|_| ()) }); - let mut connector = srv.connector(); - let request = client::ClientRequest::get(srv.url("/?qp=5")) - .finish() - .unwrap(); - - let response = srv.block_on(request.send(&mut connector)).unwrap(); + let request = srv.request(http::Method::GET, srv.url("/?qp=5")).send(); + let response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 8a7316cd..5777c569 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -13,8 +13,8 @@ use futures::stream::{once, Stream}; use actix_http::body::Body; use actix_http::error::PayloadError; use actix_http::{ - body, client, error, http, http::header, Error, HttpMessage as HttpMessage2, - HttpService, KeepAlive, Request, Response, + body, error, http, http::header, Error, HttpMessage as HttpMessage2, HttpService, + KeepAlive, Request, Response, }; fn load_body(stream: S) -> impl Future @@ -37,8 +37,7 @@ fn test_h1() { .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); - let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); } @@ -56,8 +55,7 @@ fn test_h1_2() { .map(|_| ()) }); - let req = client::ClientRequest::get(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); } @@ -100,8 +98,7 @@ fn test_h2() -> std::io::Result<()> { ) }); - let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -124,8 +121,7 @@ fn test_h2_1() -> std::io::Result<()> { ) }); - let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -149,10 +145,7 @@ fn test_h2_body() -> std::io::Result<()> { ) }); - let req = client::ClientRequest::get(srv.surl("/")) - .body(data.clone()) - .unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send_body(data.clone())).unwrap(); assert!(response.status().is_success()); let body = srv.block_on(load_body(response.take_payload())).unwrap(); @@ -331,24 +324,24 @@ fn test_content_length() { { for i in 0..4 { - let req = client::ClientRequest::get(srv.url(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::GET, srv.url(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), None); - let req = client::ClientRequest::head(srv.url(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::HEAD, srv.url(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = client::ClientRequest::get(srv.url(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::GET, srv.url(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } @@ -389,24 +382,24 @@ fn test_h2_content_length() { { for i in 0..4 { - let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), None); - let req = client::ClientRequest::head(srv.surl(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = client::ClientRequest::get(srv.surl(&format!("/{}", i))) - .finish() - .unwrap(); - let response = srv.send_request(req).unwrap(); + let req = srv + .request(http::Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } @@ -442,11 +435,8 @@ fn test_h1_headers() { future::ok::<_, ()>(builder.body(data.clone())) }) }); - let mut connector = srv.connector(); - let req = srv.get().finish().unwrap(); - - let mut response = srv.block_on(req.send(&mut connector)).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -489,10 +479,8 @@ fn test_h2_headers() { future::ok::<_, ()>(builder.body(data.clone())) }).map_err(|_| ())) }); - let mut connector = srv.connector(); - let req = client::ClientRequest::get(srv.surl("/")).finish().unwrap(); - let mut response = srv.block_on(req.send(&mut connector)).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -528,8 +516,7 @@ fn test_h1_body() { HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -551,8 +538,7 @@ fn test_h2_body2() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -566,8 +552,7 @@ fn test_h1_head_empty() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -597,8 +582,7 @@ fn test_h2_head_empty() { ) }); - let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.shead().send()).unwrap(); assert!(response.status().is_success()); assert_eq!(response.version(), http::Version::HTTP_2); @@ -623,8 +607,7 @@ fn test_h1_head_binary() { }) }); - let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -658,8 +641,7 @@ fn test_h2_head_binary() { ) }); - let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.shead().send()).unwrap(); assert!(response.status().is_success()); { @@ -681,8 +663,7 @@ fn test_h1_head_binary2() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let req = client::ClientRequest::head(srv.url("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -708,8 +689,7 @@ fn test_h2_head_binary2() { ) }); - let req = client::ClientRequest::head(srv.surl("/")).finish().unwrap(); - let response = srv.send_request(req).unwrap(); + let response = srv.block_on(srv.shead().send()).unwrap(); assert!(response.status().is_success()); { @@ -733,8 +713,7 @@ fn test_h1_body_length() { }) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -761,8 +740,7 @@ fn test_h2_body_length() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -783,8 +761,7 @@ fn test_h1_body_chunked_explicit() { }) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -825,8 +802,7 @@ fn test_h2_body_chunked_explicit() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); @@ -846,8 +822,7 @@ fn test_h1_body_chunked_implicit() { }) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -879,8 +854,7 @@ fn test_h1_response_http_error_handling() { })) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -912,8 +886,7 @@ fn test_h2_response_http_error_handling() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -928,8 +901,7 @@ fn test_h1_service_error() { .h1(|_| Err::(error::ErrorBadRequest("error"))) }); - let req = srv.get().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -952,8 +924,7 @@ fn test_h2_service_error() { ) }); - let req = srv.sget().finish().unwrap(); - let mut response = srv.send_request(req).unwrap(); + let mut response = srv.block_on(srv.sget().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response From 50c0ddb3cdac458cc39282830e8815156774d10d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 12:31:51 -0700 Subject: [PATCH 1129/1635] update tests --- Cargo.toml | 7 +++-- actix-http/Cargo.toml | 1 + actix-http/test-server/Cargo.toml | 2 +- awc/Cargo.toml | 6 ++-- {actix-http/examples => examples}/client.rs | 11 +++---- tests/test_httpserver.rs | 20 ++++++------ tests/test_server.rs | 34 +++++++++------------ 7 files changed, 36 insertions(+), 45 deletions(-) rename {actix-http/examples => examples}/client.rs (74%) diff --git a/Cargo.toml b/Cargo.toml index 2a9883ea..44262e84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ path = "src/lib.rs" members = [ ".", "awc", + "actix-http", "actix-files", "actix-session", "actix-web-actors", @@ -71,7 +72,7 @@ actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.1" actix-web-codegen = { path="actix-web-codegen" } -actix-http = { git = "https://github.com/actix/actix-http.git", features=["fail"] } +actix-http = { path = "actix-http", features=["fail"] } actix-server = "0.4.1" actix-server-config = "0.1.0" awc = { path = "awc", optional = true } @@ -105,8 +106,8 @@ openssl = { version="0.10", optional = true } # rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } -actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http = { path = "actix-http", features=["ssl"] } +actix-http-test = { path = "actix-http/test-server", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index da11a585..798508a9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -14,6 +14,7 @@ categories = ["network-programming", "asynchronous", license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" +workspace = ".." [package.metadata.docs.rs] features = ["ssl", "fail", "cookie"] diff --git a/actix-http/test-server/Cargo.toml b/actix-http/test-server/Cargo.toml index 313bc55c..316f3e36 100644 --- a/actix-http/test-server/Cargo.toml +++ b/actix-http/test-server/Cargo.toml @@ -38,7 +38,7 @@ actix-http = { path=".." } actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" -awc = { git = "https://github.com/actix/actix-web.git" } +awc = { path = "../../awc" } base64 = "0.10" bytes = "0.4" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b43a8d47..233fe59d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -28,7 +28,7 @@ cookies = ["cookie", "actix-http/cookies"] [dependencies] actix-service = "0.3.4" -actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-http = { path = "../actix-http/" } bytes = "0.4" futures = "0.1" log =" 0.4" @@ -44,5 +44,5 @@ openssl = { version="0.10", optional = true } env_logger = "0.6" mime = "0.3" actix-rt = "0.2.1" -actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } -actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http = { path = "../actix-http/", features=["ssl"] } +actix-http-test = { path = "../actix-http/test-server/", features=["ssl"] } diff --git a/actix-http/examples/client.rs b/examples/client.rs similarity index 74% rename from actix-http/examples/client.rs rename to examples/client.rs index 7f5f8c91..c4df6f7d 100644 --- a/actix-http/examples/client.rs +++ b/examples/client.rs @@ -1,4 +1,4 @@ -use actix_http::{client, Error}; +use actix_http::Error; use actix_rt::System; use bytes::BytesMut; use futures::{future::lazy, Future, Stream}; @@ -8,13 +8,10 @@ fn main() -> Result<(), Error> { env_logger::init(); System::new("test").block_on(lazy(|| { - let mut connector = client::Connector::new().service(); - - client::ClientRequest::get("https://www.rust-lang.org/") // <- Create request builder + awc::Client::new() + .get("https://www.rust-lang.org/") // <- Create request builder .header("User-Agent", "Actix-web") - .finish() - .unwrap() - .send(&mut connector) // <- Send http request + .send() // <- Send http request .from_err() .and_then(|response| { // <- server http response diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 764d50ca..4a3850f1 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -48,23 +48,21 @@ fn test_start() { }); let (srv, sys) = rx.recv().unwrap(); - let mut connector = test::run_on(|| { + let client = test::run_on(|| { Ok::<_, ()>( - client::Connector::new() - .timeout(Duration::from_millis(100)) - .service(), + awc::Client::build() + .connector( + client::Connector::new() + .timeout(Duration::from_millis(100)) + .service(), + ) + .finish(), ) }) .unwrap(); let host = format!("http://{}", addr); - let response = test::block_on( - client::ClientRequest::get(host.clone()) - .finish() - .unwrap() - .send(&mut connector), - ) - .unwrap(); + let response = test::block_on(client.get(host.clone()).send()).unwrap(); assert!(response.status().is_success()); // stop diff --git a/tests/test_server.rs b/tests/test_server.rs index 2742164a..9c0f1f65 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -45,8 +45,7 @@ fn test_body() { ) }); - let request = srv.get().finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -64,8 +63,7 @@ fn test_body_gzip() { ) }); - let request = srv.get().finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -95,8 +93,7 @@ fn test_body_gzip_large() { ) }); - let request = srv.get().finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -129,8 +126,7 @@ fn test_body_gzip_large_random() { ) }); - let request = srv.get().finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -158,8 +154,7 @@ fn test_body_chunked_implicit() { ) }); - let request = srv.get().finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response.headers().get(TRANSFER_ENCODING).unwrap(), @@ -190,8 +185,9 @@ fn test_body_br_streaming() { ) }); - let request = srv.get().header(ACCEPT_ENCODING, "br").finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv + .block_on(srv.get().header(ACCEPT_ENCODING, "br").send()) + .unwrap(); assert!(response.status().is_success()); // read response @@ -212,8 +208,7 @@ fn test_head_binary() { ))) }); - let request = srv.head().finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -239,8 +234,7 @@ fn test_no_chunking() { )))) }); - let request = srv.get().finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); assert!(!response.headers().contains_key(TRANSFER_ENCODING)); @@ -262,8 +256,7 @@ fn test_body_deflate() { }); // client request - let request = srv.get().finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -289,8 +282,9 @@ fn test_body_brotli() { }); // client request - let request = srv.get().header(ACCEPT_ENCODING, "br").finish().unwrap(); - let mut response = srv.send_request(request).unwrap(); + let mut response = srv + .block_on(srv.get().header(ACCEPT_ENCODING, "br").send()) + .unwrap(); assert!(response.status().is_success()); // read response From 9451ba71f4386ddcbd4c338efbfe673d71cf1802 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 12:50:51 -0700 Subject: [PATCH 1130/1635] update cargo files --- Cargo.toml | 2 +- actix-files/Cargo.toml | 6 ++-- actix-http/Cargo.toml | 9 +++--- actix-session/Cargo.toml | 4 +-- actix-web-actors/Cargo.toml | 8 ++--- actix-web-codegen/Cargo.toml | 6 ++-- actix-web-codegen/tests/test_macro.rs | 6 ++-- awc/Cargo.toml | 4 +-- .../test-server => test-server}/Cargo.toml | 14 +++++---- .../test-server => test-server}/src/lib.rs | 30 +++++++++++-------- 10 files changed, 48 insertions(+), 41 deletions(-) rename {actix-http/test-server => test-server}/Cargo.toml (84%) rename {actix-http/test-server => test-server}/src/lib.rs (91%) diff --git a/Cargo.toml b/Cargo.toml index 44262e84..40f8c7c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-http = { path = "actix-http", features=["ssl"] } -actix-http-test = { path = "actix-http/test-server", features=["ssl"] } +actix-http-test = { path = "test-server", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 65faa5e8..ba8fbb6c 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "actix-files" -version = "0.1.0" +version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] description = "Static files support for Actix web." readme = "README.md" keywords = ["actix", "http", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" -documentation = "https://actix.rs/api/actix-web/stable/actix_web/" +documentation = "https://docs.rs/actix-files/" categories = ["asynchronous", "web-programming::http-server"] license = "MIT/Apache-2.0" edition = "2018" @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = { path=".." } -actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-http = { path="../actix-http" } actix-service = "0.3.3" bitflags = "1" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 798508a9..3b403ac2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0" +version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix http" readme = "README.md" @@ -20,9 +20,8 @@ workspace = ".." features = ["ssl", "fail", "cookie"] [badges] -travis-ci = { repository = "actix/actix-http", branch = "master" } -# appveyor = { repository = "fafhrd91/actix-http-b1qsn" } -codecov = { repository = "actix/actix-http", branch = "master", service = "github" } +travis-ci = { repository = "actix/actix-web", branch = "master" } +codecov = { repository = "actix/actix-web", branch = "master", service = "github" } [lib] name = "actix_http" @@ -87,7 +86,7 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.1" actix-server = { version = "0.4.0", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } -actix-http-test = { path="test-server", features=["ssl"] } +actix-http-test = { path="../test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 554f3d7f..3adcc8f5 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "actix-session" -version = "0.1.0" +version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-web/" +documentation = "https://docs.rs/actix-session/" license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] workspace = ".." diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index db42a1a2..95b72661 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "0.1.0" +version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -20,12 +20,12 @@ path = "src/lib.rs" [dependencies] actix-web = { path=".." } actix = { git = "https://github.com/actix/actix.git" } -actix-http = { git = "https://github.com/actix/actix-http.git" } +actix-http = { path = "../actix-http/" } actix-codec = "0.1.1" bytes = "0.4" futures = "0.1" [dev-dependencies] env_logger = "0.6" -actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } -actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http = { path = "../actix-http/", features=["ssl"] } +actix-http-test = { path = "../test-server/", features=["ssl"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index d87b71ba..3785acb3 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "actix-web-codegen" description = "Actix web codegen macros" -version = "0.1.0" +version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] license = "MIT/Apache-2.0" edition = "2018" @@ -16,5 +16,5 @@ syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] actix-web = { path = ".." } -actix-http = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } -actix-http-test = { git = "https://github.com/actix/actix-http.git", features=["ssl"] } +actix-http = { path = "../actix-http/", features=["ssl"] } +actix-http-test = { path = "../test-server/", features=["ssl"] } diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 62b5d618..8bf2c88b 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,6 +1,6 @@ use actix_http::HttpService; use actix_http_test::TestServer; -use actix_web::{get, App, HttpResponse, Responder}; +use actix_web::{get, http, App, HttpResponse, Responder}; #[get("/test")] fn test() -> impl Responder { @@ -11,7 +11,7 @@ fn test() -> impl Responder { fn test_body() { let mut srv = TestServer::new(|| HttpService::new(App::new().service(test))); - let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - let response = srv.send_request(request).unwrap(); + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 233fe59d..023bd088 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0" +version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix web client." readme = "README.md" @@ -45,4 +45,4 @@ env_logger = "0.6" mime = "0.3" actix-rt = "0.2.1" actix-http = { path = "../actix-http/", features=["ssl"] } -actix-http-test = { path = "../actix-http/test-server/", features=["ssl"] } +actix-http-test = { path = "../test-server/", features=["ssl"] } diff --git a/actix-http/test-server/Cargo.toml b/test-server/Cargo.toml similarity index 84% rename from actix-http/test-server/Cargo.toml rename to test-server/Cargo.toml index 316f3e36..6959adbe 100644 --- a/actix-http/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "actix-http-test" -version = "0.1.0" +version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] -description = "Actix http" +description = "Actix http test server" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" -documentation = "https://actix.rs/api/actix-web/stable/actix_web/" +documentation = "https://docs.rs/actix-http-test/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" +workspace = ".." [package.metadata.docs.rs] features = ["session"] @@ -34,11 +35,11 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.1" actix-rt = "0.2.1" -actix-http = { path=".." } +actix-http = { path="../actix-http" } actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" -awc = { path = "../../awc" } +awc = { path = "../awc" } base64 = "0.10" bytes = "0.4" @@ -58,3 +59,6 @@ tokio-tcp = "0.1" tokio-timer = "0.2" openssl = { version="0.10", optional = true } + +[dev-dependencies] +actix-web = { path=".." } diff --git a/actix-http/test-server/src/lib.rs b/test-server/src/lib.rs similarity index 91% rename from actix-http/test-server/src/lib.rs rename to test-server/src/lib.rs index 77329e70..7cd94d4d 100644 --- a/actix-http/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -21,22 +21,26 @@ use net2::TcpBuilder; /// # Examples /// /// ```rust -/// # extern crate actix_web; -/// # use actix_web::*; +/// use actix_http::HttpService; +/// use actix_http_test::TestServer; +/// use actix_web::{web, App, HttpResponse}; /// # -/// # fn my_handler(req: &HttpRequest) -> HttpResponse { -/// # HttpResponse::Ok().into() -/// # } -/// # -/// # fn main() { -/// use actix_web::test::TestServer; +/// fn my_handler() -> HttpResponse { +/// HttpResponse::Ok().into() +/// } /// -/// let mut srv = TestServer::new(|app| app.handler(my_handler)); +/// fn main() { +/// let mut srv = TestServer::new( +/// || HttpService::new( +/// App::new().service( +/// web::resource("/").to(my_handler)) +/// ) +/// ); /// -/// let req = srv.get().finish().unwrap(); -/// let response = srv.execute(req.send()).unwrap(); -/// assert!(response.status().is_success()); -/// # } +/// let req = srv.get(); +/// let response = srv.block_on(req.send()).unwrap(); +/// assert!(response.status().is_success()); +/// } /// ``` pub struct TestServer; From 1904b01fc0586bcefed1df9c3c04d34c65f6a995 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 15:14:32 -0700 Subject: [PATCH 1131/1635] add content-encoding decompression --- Cargo.toml | 16 +- actix-http/Cargo.toml | 13 + actix-http/src/encoding/decoder.rs | 191 ++++++++++++ actix-http/src/encoding/encoder.rs | 234 +++++++++++++++ actix-http/src/encoding/mod.rs | 35 +++ actix-http/src/lib.rs | 1 + src/app.rs | 4 +- src/app_service.rs | 2 +- src/middleware/compress.rs | 278 +----------------- src/middleware/decompress.rs | 60 ++++ src/middleware/mod.rs | 5 + src/resource.rs | 2 +- src/scope.rs | 2 +- src/service.rs | 16 +- src/test.rs | 3 +- tests/test_server.rs | 456 ++++++++++++++--------------- 16 files changed, 780 insertions(+), 538 deletions(-) create mode 100644 actix-http/src/encoding/decoder.rs create mode 100644 actix-http/src/encoding/encoder.rs create mode 100644 actix-http/src/encoding/mod.rs create mode 100644 src/middleware/decompress.rs diff --git a/Cargo.toml b/Cargo.toml index 40f8c7c4..22c2efe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "rust-tls", "brotli", "flate2-c", "cookies"] +features = ["ssl", "tls", "rust-tls", "brotli", "flate2-c", "cookies", "client"] [features] default = ["brotli", "flate2-c", "cookies", "client"] @@ -45,13 +45,13 @@ default = ["brotli", "flate2-c", "cookies", "client"] client = ["awc"] # brotli encoding, requires c compiler -brotli = ["brotli2"] +brotli = ["actix-http/brotli2"] # miniz-sys backend for flate2 crate -flate2-c = ["flate2/miniz-sys"] +flate2-c = ["actix-http/flate2-c"] # rust backend for flate2 crate -flate2-rust = ["flate2/rust_backend"] +flate2-rust = ["actix-http/flate2-rust"] # sessions feature, session require "ring" crate and c compiler cookies = ["cookie", "actix-http/cookies"] @@ -96,22 +96,20 @@ url = { version="1.7", features=["query_encoding"] } # cookies support cookie = { version="0.11", features=["secure", "percent-encode"], optional = true } -# compression -brotli2 = { version="^0.3.2", optional = true } -flate2 = { version="^1.0.2", optional = true, default-features = false } - # ssl support native-tls = { version="0.2", optional = true } openssl = { version="0.10", optional = true } # rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { path = "actix-http", features=["ssl"] } +actix-http = { path = "actix-http", features=["ssl", "brotli", "flate2-c"] } actix-http-test = { path = "test-server", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" tokio-timer = "0.2.8" +brotli2 = { version="^0.3.2" } +flate2 = { version="^1.0.2" } [profile.release] lto = true diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3b403ac2..7b73e7e2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -36,6 +36,15 @@ ssl = ["openssl", "actix-connect/ssl"] # cookies integration cookies = ["cookie"] +# brotli encoding, requires c compiler +brotli = ["brotli2"] + +# miniz-sys backend for flate2 crate +flate2-c = ["flate2/miniz-sys"] + +# rust backend for flate2 crate +flate2-rust = ["flate2/rust_backend"] + # failure integration. actix does not use failure anymore fail = ["failure"] @@ -77,6 +86,10 @@ tokio-timer = "0.2" tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } +# compression +brotli2 = { version="^0.3.2", optional = true } +flate2 = { version="^1.0.2", optional = true, default-features = false } + # optional deps cookie = { version="0.11", features=["percent-encode"], optional = true } failure = { version = "0.1.5", optional = true } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs new file mode 100644 index 00000000..a922d173 --- /dev/null +++ b/actix-http/src/encoding/decoder.rs @@ -0,0 +1,191 @@ +use std::io::{self, Write}; + +use bytes::Bytes; +use futures::{Async, Poll, Stream}; + +#[cfg(feature = "brotli")] +use brotli2::write::BrotliDecoder; +#[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] +use flate2::write::{GzDecoder, ZlibDecoder}; + +use super::Writer; +use crate::error::PayloadError; +use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}; + +pub struct Decoder { + stream: T, + decoder: Option, +} + +impl Decoder +where + T: Stream, +{ + pub fn new(stream: T, encoding: ContentEncoding) -> Self { + let decoder = match encoding { + #[cfg(feature = "brotli")] + ContentEncoding::Br => Some(ContentDecoder::Br(Box::new( + BrotliDecoder::new(Writer::new()), + ))), + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( + ZlibDecoder::new(Writer::new()), + ))), + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new( + GzDecoder::new(Writer::new()), + ))), + _ => None, + }; + Decoder { stream, decoder } + } + + pub fn from_headers(headers: &HeaderMap, stream: T) -> Self { + // check content-encoding + let encoding = if let Some(enc) = headers.get(CONTENT_ENCODING) { + if let Ok(enc) = enc.to_str() { + ContentEncoding::from(enc) + } else { + ContentEncoding::Identity + } + } else { + ContentEncoding::Identity + }; + + Self::new(stream, encoding) + } +} + +impl Stream for Decoder +where + T: Stream, +{ + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll, Self::Error> { + loop { + match self.stream.poll()? { + Async::Ready(Some(chunk)) => { + if let Some(ref mut decoder) = self.decoder { + match decoder.feed_data(chunk) { + Ok(Some(chunk)) => return Ok(Async::Ready(Some(chunk))), + Ok(None) => continue, + Err(e) => return Err(e.into()), + } + } else { + break; + } + } + Async::Ready(None) => { + return if let Some(mut decoder) = self.decoder.take() { + match decoder.feed_eof() { + Ok(chunk) => Ok(Async::Ready(chunk)), + Err(e) => Err(e.into()), + } + } else { + Ok(Async::Ready(None)) + }; + } + Async::NotReady => break, + } + } + Ok(Async::NotReady) + } +} + +enum ContentDecoder { + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + Deflate(Box>), + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + Gzip(Box>), + #[cfg(feature = "brotli")] + Br(Box>), +} + +impl ContentDecoder { + fn feed_eof(&mut self) -> io::Result> { + match self { + #[cfg(feature = "brotli")] + ContentDecoder::Br(ref mut decoder) => match decoder.finish() { + Ok(mut writer) => { + let b = writer.take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() { + Ok(_) => { + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + } + } + + fn feed_data(&mut self, data: Bytes) -> io::Result> { + match self { + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { + Ok(_) => { + decoder.flush()?; + let b = decoder.get_mut().take(); + if !b.is_empty() { + Ok(Some(b)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + }, + } + } +} diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs new file mode 100644 index 00000000..1985dcdf --- /dev/null +++ b/actix-http/src/encoding/encoder.rs @@ -0,0 +1,234 @@ +//! Stream encoder +use std::io::{self, Write}; + +use bytes::Bytes; +use futures::{Async, Poll}; + +#[cfg(feature = "brotli")] +use brotli2::write::BrotliEncoder; +#[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] +use flate2::write::{GzEncoder, ZlibEncoder}; + +use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; +use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; +use crate::http::{HeaderValue, HttpTryFrom, StatusCode}; +use crate::{Error, Head, ResponseHead}; + +use super::Writer; + +pub struct Encoder { + body: EncoderBody, + encoder: Option, +} + +impl Encoder { + pub fn response( + encoding: ContentEncoding, + head: &mut ResponseHead, + body: ResponseBody, + ) -> ResponseBody> { + let has_ce = head.headers().contains_key(CONTENT_ENCODING); + match body { + ResponseBody::Other(b) => match b { + Body::None => ResponseBody::Other(Body::None), + Body::Empty => ResponseBody::Other(Body::Empty), + Body::Bytes(buf) => { + if !(has_ce + || encoding == ContentEncoding::Identity + || encoding == ContentEncoding::Auto) + { + let mut enc = ContentEncoder::encoder(encoding).unwrap(); + + // TODO return error! + let _ = enc.write(buf.as_ref()); + let body = enc.finish().unwrap(); + update_head(encoding, head); + ResponseBody::Other(Body::Bytes(body)) + } else { + ResponseBody::Other(Body::Bytes(buf)) + } + } + Body::Message(stream) => { + if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS { + ResponseBody::Body(Encoder { + body: EncoderBody::Other(stream), + encoder: None, + }) + } else { + update_head(encoding, head); + head.no_chunking = false; + ResponseBody::Body(Encoder { + body: EncoderBody::Other(stream), + encoder: ContentEncoder::encoder(encoding), + }) + } + } + }, + ResponseBody::Body(stream) => { + if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS { + ResponseBody::Body(Encoder { + body: EncoderBody::Body(stream), + encoder: None, + }) + } else { + update_head(encoding, head); + head.no_chunking = false; + ResponseBody::Body(Encoder { + body: EncoderBody::Body(stream), + encoder: ContentEncoder::encoder(encoding), + }) + } + } + } + } +} + +enum EncoderBody { + Body(B), + Other(Box), +} + +impl MessageBody for Encoder { + fn length(&self) -> BodyLength { + if self.encoder.is_none() { + match self.body { + EncoderBody::Body(ref b) => b.length(), + EncoderBody::Other(ref b) => b.length(), + } + } else { + BodyLength::Stream + } + } + + fn poll_next(&mut self) -> Poll, Error> { + loop { + let result = match self.body { + EncoderBody::Body(ref mut b) => b.poll_next()?, + EncoderBody::Other(ref mut b) => b.poll_next()?, + }; + match result { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(Some(chunk)) => { + if let Some(ref mut encoder) = self.encoder { + if encoder.write(&chunk)? { + return Ok(Async::Ready(Some(encoder.take()))); + } + } else { + return Ok(Async::Ready(Some(chunk))); + } + } + Async::Ready(None) => { + if let Some(encoder) = self.encoder.take() { + let chunk = encoder.finish()?; + if chunk.is_empty() { + return Ok(Async::Ready(None)); + } else { + return Ok(Async::Ready(Some(chunk))); + } + } else { + return Ok(Async::Ready(None)); + } + } + } + } + } +} + +fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { + head.headers_mut().insert( + CONTENT_ENCODING, + HeaderValue::try_from(Bytes::from_static(encoding.as_str().as_bytes())).unwrap(), + ); +} + +enum ContentEncoder { + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + Deflate(ZlibEncoder), + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + Gzip(GzEncoder), + #[cfg(feature = "brotli")] + Br(BrotliEncoder), +} + +impl ContentEncoder { + fn encoder(encoding: ContentEncoding) -> Option { + match encoding { + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( + Writer::new(), + flate2::Compression::fast(), + ))), + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( + Writer::new(), + flate2::Compression::fast(), + ))), + #[cfg(feature = "brotli")] + ContentEncoding::Br => { + Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) + } + _ => None, + } + } + + #[inline] + pub(crate) fn take(&mut self) -> Bytes { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), + } + } + + fn finish(self) -> Result { + match self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(encoder) => match encoder.finish() { + Ok(writer) => Ok(writer.buf.freeze()), + Err(err) => Err(err), + }, + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoder::Gzip(encoder) => match encoder.finish() { + Ok(writer) => Ok(writer.buf.freeze()), + Err(err) => Err(err), + }, + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoder::Deflate(encoder) => match encoder.finish() { + Ok(writer) => Ok(writer.buf.freeze()), + Err(err) => Err(err), + }, + } + } + + fn write(&mut self, data: &[u8]) -> Result { + match *self { + #[cfg(feature = "brotli")] + ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Err(err) => { + trace!("Error decoding br encoding: {}", err); + Err(err) + } + }, + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Err(err) => { + trace!("Error decoding gzip encoding: {}", err); + Err(err) + } + }, + #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { + Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), + Err(err) => { + trace!("Error decoding deflate encoding: {}", err); + Err(err) + } + }, + } + } +} diff --git a/actix-http/src/encoding/mod.rs b/actix-http/src/encoding/mod.rs new file mode 100644 index 00000000..b55a43a7 --- /dev/null +++ b/actix-http/src/encoding/mod.rs @@ -0,0 +1,35 @@ +//! Content-Encoding support +use std::io; + +use bytes::{Bytes, BytesMut}; + +mod decoder; +mod encoder; + +pub use self::decoder::Decoder; +pub use self::encoder::Encoder; + +pub(self) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer { + buf: BytesMut::with_capacity(8192), + } + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl io::Write for Writer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index b41ce7ae..edc06c2a 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -12,6 +12,7 @@ pub mod body; mod builder; pub mod client; mod config; +pub mod encoding; mod extensions; mod header; mod helpers; diff --git a/src/app.rs b/src/app.rs index f46f5252..b8efdd38 100644 --- a/src/app.rs +++ b/src/app.rs @@ -193,10 +193,10 @@ where } /// Register a request modifier. It can modify any request parameters - /// including payload stream type. + /// including request payload type. pub fn chain( self, - chain: C, + chain: F, ) -> App< P1, impl NewService< diff --git a/src/app_service.rs b/src/app_service.rs index 0bf3d309..236eed9f 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -380,7 +380,7 @@ impl

    Service for AppRouting

    { } else if let Some(ref mut default) = self.default { Either::A(default.call(req)) } else { - let req = req.into_request(); + let req = req.into_parts().0; Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 3c4718fe..5ffe9afb 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -1,25 +1,14 @@ -/// `Middleware` for compressing response body. -use std::io::Write; +//! `Middleware` for compressing response body. +use std::cmp; use std::marker::PhantomData; use std::str::FromStr; -use std::{cmp, fmt, io}; -use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; -use actix_http::http::header::{ - ContentEncoding, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING, -}; -use actix_http::http::{HttpTryFrom, StatusCode}; -use actix_http::{Error, Head, ResponseHead}; +use actix_http::body::MessageBody; +use actix_http::encoding::Encoder; +use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; use actix_service::{Service, Transform}; -use bytes::{Bytes, BytesMut}; use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; -use log::trace; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliEncoder; -#[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] -use flate2::write::{GzEncoder, ZlibEncoder}; use crate::service::{ServiceRequest, ServiceResponse}; @@ -130,266 +119,11 @@ where let resp = futures::try_ready!(self.fut.poll()); Ok(Async::Ready(resp.map_body(move |head, body| { - Encoder::body(self.encoding, head, body) + Encoder::response(self.encoding, head, body) }))) } } -enum EncoderBody { - Body(B), - Other(Box), -} - -pub struct Encoder { - body: EncoderBody, - encoder: Option, -} - -impl MessageBody for Encoder { - fn length(&self) -> BodyLength { - if self.encoder.is_none() { - match self.body { - EncoderBody::Body(ref b) => b.length(), - EncoderBody::Other(ref b) => b.length(), - } - } else { - BodyLength::Stream - } - } - - fn poll_next(&mut self) -> Poll, Error> { - loop { - let result = match self.body { - EncoderBody::Body(ref mut b) => b.poll_next()?, - EncoderBody::Other(ref mut b) => b.poll_next()?, - }; - match result { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(chunk)) => { - if let Some(ref mut encoder) = self.encoder { - if encoder.write(&chunk)? { - return Ok(Async::Ready(Some(encoder.take()))); - } - } else { - return Ok(Async::Ready(Some(chunk))); - } - } - Async::Ready(None) => { - if let Some(encoder) = self.encoder.take() { - let chunk = encoder.finish()?; - if chunk.is_empty() { - return Ok(Async::Ready(None)); - } else { - return Ok(Async::Ready(Some(chunk))); - } - } else { - return Ok(Async::Ready(None)); - } - } - } - } - } -} - -fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { - head.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::try_from(Bytes::from_static(encoding.as_str().as_bytes())).unwrap(), - ); -} - -impl Encoder { - fn body( - encoding: ContentEncoding, - head: &mut ResponseHead, - body: ResponseBody, - ) -> ResponseBody> { - let has_ce = head.headers().contains_key(CONTENT_ENCODING); - match body { - ResponseBody::Other(b) => match b { - Body::None => ResponseBody::Other(Body::None), - Body::Empty => ResponseBody::Other(Body::Empty), - Body::Bytes(buf) => { - if !(has_ce - || encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto) - { - let mut enc = ContentEncoder::encoder(encoding).unwrap(); - - // TODO return error! - let _ = enc.write(buf.as_ref()); - let body = enc.finish().unwrap(); - update_head(encoding, head); - ResponseBody::Other(Body::Bytes(body)) - } else { - ResponseBody::Other(Body::Bytes(buf)) - } - } - Body::Message(stream) => { - if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS { - ResponseBody::Body(Encoder { - body: EncoderBody::Other(stream), - encoder: None, - }) - } else { - update_head(encoding, head); - head.no_chunking = false; - ResponseBody::Body(Encoder { - body: EncoderBody::Other(stream), - encoder: ContentEncoder::encoder(encoding), - }) - } - } - }, - ResponseBody::Body(stream) => { - if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS { - ResponseBody::Body(Encoder { - body: EncoderBody::Body(stream), - encoder: None, - }) - } else { - update_head(encoding, head); - head.no_chunking = false; - ResponseBody::Body(Encoder { - body: EncoderBody::Body(stream), - encoder: ContentEncoder::encoder(encoding), - }) - } - } - } - } -} - -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::with_capacity(8192), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl io::Write for Writer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub(crate) enum ContentEncoder { - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - Deflate(ZlibEncoder), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - Gzip(GzEncoder), - #[cfg(feature = "brotli")] - Br(BrotliEncoder), -} - -impl fmt::Debug for ContentEncoder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(_) => writeln!(f, "ContentEncoder(Brotli)"), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoder::Deflate(_) => writeln!(f, "ContentEncoder(Deflate)"), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoder::Gzip(_) => writeln!(f, "ContentEncoder(Gzip)"), - } - } -} - -impl ContentEncoder { - fn encoder(encoding: ContentEncoding) -> Option { - match encoding { - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( - Writer::new(), - flate2::Compression::fast(), - ))), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( - Writer::new(), - flate2::Compression::fast(), - ))), - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) - } - _ => None, - } - } - - #[inline] - pub(crate) fn take(&mut self) -> Bytes { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), - } - } - - fn finish(self) -> Result { - match self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(encoder) => match encoder.finish() { - Ok(writer) => Ok(writer.buf.freeze()), - Err(err) => Err(err), - }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoder::Gzip(encoder) => match encoder.finish() { - Ok(writer) => Ok(writer.buf.freeze()), - Err(err) => Err(err), - }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoder::Deflate(encoder) => match encoder.finish() { - Ok(writer) => Ok(writer.buf.freeze()), - Err(err) => Err(err), - }, - } - } - - fn write(&mut self, data: &[u8]) -> Result { - match *self { - #[cfg(feature = "brotli")] - ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), - Err(err) => { - trace!("Error decoding br encoding: {}", err); - Err(err) - } - }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), - Err(err) => { - trace!("Error decoding gzip encoding: {}", err); - Err(err) - } - }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] - ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), - Err(err) => { - trace!("Error decoding deflate encoding: {}", err); - Err(err) - } - }, - } - } -} - struct AcceptEncoding { encoding: ContentEncoding, quality: f64, diff --git a/src/middleware/decompress.rs b/src/middleware/decompress.rs new file mode 100644 index 00000000..d0a9bfd2 --- /dev/null +++ b/src/middleware/decompress.rs @@ -0,0 +1,60 @@ +//! Chain service for decompressing request payload. +use std::marker::PhantomData; + +use actix_http::encoding::Decoder; +use actix_service::{NewService, Service}; +use bytes::Bytes; +use futures::future::{ok, FutureResult}; +use futures::{Async, Poll, Stream}; + +use crate::dev::Payload; +use crate::error::{Error, PayloadError}; +use crate::service::ServiceRequest; +use crate::HttpMessage; + +pub struct Decompress

    (PhantomData

    ); + +impl

    Decompress

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

    NewService for Decompress

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

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

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

    Service for Decompress

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

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

    ) -> Self::Future { + let (req, payload) = req.into_parts(); + let payload = Decoder::from_headers(req.headers(), payload); + ok(ServiceRequest::from_parts(req, Payload::Stream(payload))) + } +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 998b5905..764cd9a3 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -4,6 +4,11 @@ mod compress; #[cfg(any(feature = "brotli", feature = "flate2"))] pub use self::compress::Compress; +#[cfg(any(feature = "brotli", feature = "flate2"))] +mod decompress; +#[cfg(any(feature = "brotli", feature = "flate2"))] +pub use self::decompress::Decompress; + pub mod cors; mod defaultheaders; pub mod errhandlers; diff --git a/src/resource.rs b/src/resource.rs index 55237157..b24e8dd5 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -507,7 +507,7 @@ impl

    Service for ResourceService

    { if let Some(ref mut default) = self.default { Either::B(Either::A(default.call(req))) } else { - let req = req.into_request(); + let req = req.into_parts().0; Either::B(Either::B(ok(ServiceResponse::new( req, Response::MethodNotAllowed().finish(), diff --git a/src/scope.rs b/src/scope.rs index 8c72824f..d45609c5 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -489,7 +489,7 @@ impl

    Service for ScopeService

    { } else if let Some(ref mut default) = self.default { Either::A(default.call(req)) } else { - let req = req.into_request(); + let req = req.into_parts().0; Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) } } diff --git a/src/service.rs b/src/service.rs index b8c3a158..5a042208 100644 --- a/src/service.rs +++ b/src/service.rs @@ -69,9 +69,14 @@ impl

    ServiceRequest

    { } } - #[inline] - pub fn into_request(self) -> HttpRequest { - self.req + /// Construct service request from parts + pub fn from_parts(req: HttpRequest, payload: Payload

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

    ) { + (self.req, self.payload) } /// Create service response @@ -162,11 +167,6 @@ impl

    ServiceRequest

    { pub fn app_config(&self) -> &AppConfig { self.req.config() } - - /// Deconstruct request into parts - pub fn into_parts(self) -> (HttpRequest, Payload

    ) { - (self.req, self.payload) - } } impl

    Resource for ServiceRequest

    { diff --git a/src/test.rs b/src/test.rs index c5936ea3..9e1f01f9 100644 --- a/src/test.rs +++ b/src/test.rs @@ -350,7 +350,8 @@ impl TestRequest { Rc::new(self.rmap), AppConfig::new(self.config), ) - .into_request() + .into_parts() + .0 } /// Complete request creation and generate `ServiceFromRequest` instance diff --git a/tests/test_server.rs b/tests/test_server.rs index 9c0f1f65..acea029c 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,14 +1,16 @@ use std::io::{Read, Write}; use actix_http::http::header::{ - ContentEncoding, ACCEPT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, + ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, + TRANSFER_ENCODING, }; -use actix_http::{h1, Error, Response}; +use actix_http::{h1, Error, HttpService, Response}; use actix_http_test::TestServer; -use brotli2::write::BrotliDecoder; +use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; use flate2::read::GzDecoder; -use flate2::write::ZlibDecoder; +use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; +use flate2::Compression; use futures::stream::once; //Future, Stream use rand::{distributions::Alphanumeric, Rng}; @@ -297,278 +299,246 @@ fn test_body_brotli() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -// #[test] -// fn test_gzip_encoding() { -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[test] +fn test_gzip_encoding() { + let mut srv = TestServer::new(move || { + HttpService::new( + App::new().chain(middleware::Decompress::new()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); -// // client request -// let mut e = GzEncoder::new(Vec::new(), Compression::default()); -// e.write_all(STR.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); -// let request = srv -// .post() -// .header(http::header::CONTENT_ENCODING, "gzip") -// .body(enc.clone()) -// .unwrap(); -// let response = srv.block_on(request.send()).unwrap(); -// assert!(response.status().is_success()); + let request = srv + .post() + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); -// // read response -// let bytes = srv.block_on(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// #[test] -// fn test_gzip_encoding_large() { -// let data = STR.repeat(10); -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[test] +fn test_gzip_encoding_large() { + let data = STR.repeat(10); + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new().chain(middleware::Decompress::new()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); -// // client request -// let mut e = GzEncoder::new(Vec::new(), Compression::default()); -// e.write_all(data.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); -// let request = srv -// .post() -// .header(http::header::CONTENT_ENCODING, "gzip") -// .body(enc.clone()) -// .unwrap(); -// let response = srv.block_on(request.send()).unwrap(); -// assert!(response.status().is_success()); + let request = srv + .post() + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); -// // read response -// let bytes = srv.block_on(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from(data)); -// } + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} -// #[test] -// fn test_reading_gzip_encoding_large_random() { -// let data = rand::thread_rng() -// .sample_iter(&Alphanumeric) -// .take(60_000) -// .collect::(); +#[test] +fn test_reading_gzip_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(60_000) + .collect::(); -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); + let mut srv = TestServer::new(move || { + HttpService::new( + App::new().chain(middleware::Decompress::new()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); -// // client request -// let mut e = GzEncoder::new(Vec::new(), Compression::default()); -// e.write_all(data.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); -// let request = srv -// .post() -// .header(http::header::CONTENT_ENCODING, "gzip") -// .body(enc.clone()) -// .unwrap(); -// let response = srv.block_on(request.send()).unwrap(); -// assert!(response.status().is_success()); + let request = srv + .post() + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); -// // read response -// let bytes = srv.block_on(response.body()).unwrap(); -// assert_eq!(bytes.len(), data.len()); -// assert_eq!(bytes, Bytes::from(data)); -// } + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); +} -// #[test] -// fn test_reading_deflate_encoding() { -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[test] +fn test_reading_deflate_encoding() { + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new().chain(middleware::Decompress::new()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); -// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); -// e.write_all(STR.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); -// // client request -// let request = srv -// .post() -// .header(http::header::CONTENT_ENCODING, "deflate") -// .body(enc) -// .unwrap(); -// let response = srv.block_on(request.send()).unwrap(); -// assert!(response.status().is_success()); + // client request + let request = srv + .post() + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); -// // read response -// let bytes = srv.block_on(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// #[test] -// fn test_reading_deflate_encoding_large() { -// let data = STR.repeat(10); -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[test] +fn test_reading_deflate_encoding_large() { + let data = STR.repeat(10); + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new().chain(middleware::Decompress::new()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); -// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); -// e.write_all(data.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); -// // client request -// let request = srv -// .post() -// .header(http::header::CONTENT_ENCODING, "deflate") -// .body(enc) -// .unwrap(); -// let response = srv.block_on(request.send()).unwrap(); -// assert!(response.status().is_success()); + // client request + let request = srv + .post() + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); -// // read response -// let bytes = srv.block_on(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from(data)); -// } + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} -// #[test] -// fn test_reading_deflate_encoding_large_random() { -// let data = rand::thread_rng() -// .sample_iter(&Alphanumeric) -// .take(160_000) -// .collect::(); +#[test] +fn test_reading_deflate_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new().chain(middleware::Decompress::new()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); -// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); -// e.write_all(data.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); -// // client request -// let request = srv -// .post() -// .header(http::header::CONTENT_ENCODING, "deflate") -// .body(enc) -// .unwrap(); -// let response = srv.block_on(request.send()).unwrap(); -// assert!(response.status().is_success()); + // client request + let request = srv + .post() + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes.len(), data.len()); -// assert_eq!(bytes, Bytes::from(data)); -// } + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); +} -// #[cfg(feature = "brotli")] -// #[test] -// fn test_brotli_encoding() { -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[cfg(feature = "brotli")] +#[test] +fn test_brotli_encoding() { + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new().chain(middleware::Decompress::new()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); -// let mut e = BrotliEncoder::new(Vec::new(), 5); -// e.write_all(STR.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); -// // client request -// let request = srv -// .post() -// .header(http::header::CONTENT_ENCODING, "br") -// .body(enc) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + // client request + let request = srv + .post() + .header(CONTENT_ENCODING, "br") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// #[cfg(feature = "brotli")] -// #[test] -// fn test_brotli_encoding_large() { -// let data = STR.repeat(10); -// let mut srv = test::TestServer::new(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[cfg(feature = "brotli")] +#[test] +fn test_brotli_encoding_large() { + let data = STR.repeat(10); + let mut srv = TestServer::new(move || { + h1::H1Service::new( + App::new().chain(middleware::Decompress::new()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); -// let mut e = BrotliEncoder::new(Vec::new(), 5); -// e.write_all(data.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); -// // client request -// let request = srv -// .post() -// .header(http::header::CONTENT_ENCODING, "br") -// .body(enc) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + // client request + let request = srv + .post() + .header(CONTENT_ENCODING, "br") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from(data)); -// } + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} // #[cfg(all(feature = "brotli", feature = "ssl"))] // #[test] From 2629699b6206997f57872553354c0020adc768c9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 18:46:06 -0700 Subject: [PATCH 1132/1635] rename flate2-c feature to flate2-zlib --- Cargo.toml | 8 ++++---- actix-http/Cargo.toml | 2 +- actix-http/src/encoding/decoder.rs | 24 ++++++++++++++---------- actix-http/src/encoding/encoder.rs | 22 +++++++++++----------- awc/Cargo.toml | 14 +++++++++++++- src/lib.rs | 2 +- src/middleware/mod.rs | 8 ++++---- 7 files changed, 48 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22c2efe9..363989bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,10 +36,10 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "rust-tls", "brotli", "flate2-c", "cookies", "client"] +features = ["ssl", "tls", "rust-tls", "brotli", "flate2-zlib", "cookies", "client"] [features] -default = ["brotli", "flate2-c", "cookies", "client"] +default = ["brotli", "flate2-zlib", "cookies", "client"] # http client client = ["awc"] @@ -48,7 +48,7 @@ client = ["awc"] brotli = ["actix-http/brotli2"] # miniz-sys backend for flate2 crate -flate2-c = ["actix-http/flate2-c"] +flate2-zlib = ["actix-http/flate2-zlib"] # rust backend for flate2 crate flate2-rust = ["actix-http/flate2-rust"] @@ -102,7 +102,7 @@ openssl = { version="0.10", optional = true } # rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { path = "actix-http", features=["ssl", "brotli", "flate2-c"] } +actix-http = { path = "actix-http", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { path = "test-server", features=["ssl"] } rand = "0.6" env_logger = "0.6" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7b73e7e2..427024e2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -40,7 +40,7 @@ cookies = ["cookie"] brotli = ["brotli2"] # miniz-sys backend for flate2 crate -flate2-c = ["flate2/miniz-sys"] +flate2-zlib = ["flate2/miniz-sys"] # rust backend for flate2 crate flate2-rust = ["flate2/rust_backend"] diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index a922d173..b4246c64 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -5,7 +5,7 @@ use futures::{Async, Poll, Stream}; #[cfg(feature = "brotli")] use brotli2::write::BrotliDecoder; -#[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] use flate2::write::{GzDecoder, ZlibDecoder}; use super::Writer; @@ -27,11 +27,11 @@ where ContentEncoding::Br => Some(ContentDecoder::Br(Box::new( BrotliDecoder::new(Writer::new()), ))), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( ZlibDecoder::new(Writer::new()), ))), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new( GzDecoder::new(Writer::new()), ))), @@ -95,15 +95,16 @@ where } enum ContentDecoder { - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] Deflate(Box>), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] Gzip(Box>), #[cfg(feature = "brotli")] Br(Box>), } impl ContentDecoder { + #[allow(unreachable_patterns)] fn feed_eof(&mut self) -> io::Result> { match self { #[cfg(feature = "brotli")] @@ -118,7 +119,7 @@ impl ContentDecoder { } Err(e) => Err(e), }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); @@ -130,7 +131,7 @@ impl ContentDecoder { } Err(e) => Err(e), }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); @@ -142,12 +143,14 @@ impl ContentDecoder { } Err(e) => Err(e), }, + _ => Ok(None), } } + #[allow(unreachable_patterns)] fn feed_data(&mut self, data: Bytes) -> io::Result> { match self { - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; @@ -160,7 +163,7 @@ impl ContentDecoder { } Err(e) => Err(e), }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; @@ -173,7 +176,7 @@ impl ContentDecoder { } Err(e) => Err(e), }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; @@ -186,6 +189,7 @@ impl ContentDecoder { } Err(e) => Err(e), }, + _ => Ok(Some(data)), } } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 1985dcdf..0778cc26 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -6,7 +6,7 @@ use futures::{Async, Poll}; #[cfg(feature = "brotli")] use brotli2::write::BrotliEncoder; -#[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] use flate2::write::{GzEncoder, ZlibEncoder}; use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; @@ -142,9 +142,9 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { } enum ContentEncoder { - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] Deflate(ZlibEncoder), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] Gzip(GzEncoder), #[cfg(feature = "brotli")] Br(BrotliEncoder), @@ -153,12 +153,12 @@ enum ContentEncoder { impl ContentEncoder { fn encoder(encoding: ContentEncoding) -> Option { match encoding { - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( Writer::new(), flate2::Compression::fast(), ))), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( Writer::new(), flate2::Compression::fast(), @@ -176,9 +176,9 @@ impl ContentEncoder { match *self { #[cfg(feature = "brotli")] ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), } } @@ -190,12 +190,12 @@ impl ContentEncoder { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoder::Gzip(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoder::Deflate(encoder) => match encoder.finish() { Ok(writer) => Ok(writer.buf.freeze()), Err(err) => Err(err), @@ -213,7 +213,7 @@ impl ContentEncoder { Err(err) } }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), Err(err) => { @@ -221,7 +221,7 @@ impl ContentEncoder { Err(err) } }, - #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(!encoder.get_ref().buf.is_empty()), Err(err) => { diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 023bd088..72b72d36 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -17,8 +17,11 @@ edition = "2018" name = "awc" path = "src/lib.rs" +[package.metadata.docs.rs] +features = ["ssl", "brotli", "flate2-zlib", "cookies"] + [features] -default = ["cookies"] +default = ["cookies", "brotli", "flate2-zlib"] # openssl ssl = ["openssl", "actix-http/ssl"] @@ -26,6 +29,15 @@ ssl = ["openssl", "actix-http/ssl"] # cookies integration cookies = ["cookie", "actix-http/cookies"] +# brotli encoding, requires c compiler +brotli = ["actix-http/brotli2"] + +# miniz-sys backend for flate2 crate +flate2-zlib = ["actix-http/flate2-zlib"] + +# rust backend for flate2 crate +flate2-rust = ["actix-http/flate2-rust"] + [dependencies] actix-service = "0.3.4" actix-http = { path = "../actix-http/" } diff --git a/src/lib.rs b/src/lib.rs index 1bf29213..a21032db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ //! dependency //! * `brotli` - enables `brotli` compression support, requires `c` //! compiler -//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires +//! * `flate2-zlib` - enables `gzip`, `deflate` compression support, requires //! `c` compiler //! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 764cd9a3..aee0ae3d 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,12 +1,12 @@ //! Middlewares -#[cfg(any(feature = "brotli", feature = "flate2"))] +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] mod compress; -#[cfg(any(feature = "brotli", feature = "flate2"))] +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] pub use self::compress::Compress; -#[cfg(any(feature = "brotli", feature = "flate2"))] +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] mod decompress; -#[cfg(any(feature = "brotli", feature = "flate2"))] +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] pub use self::decompress::Decompress; pub mod cors; From 1cca25c27631f023d1fc844c83250321e7f470ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 20:45:00 -0700 Subject: [PATCH 1133/1635] add client decompression support --- actix-http/src/encoding/decoder.rs | 4 +- awc/Cargo.toml | 10 +- awc/src/lib.rs | 1 + awc/src/request.rs | 149 ++++++--- awc/src/response.rs | 146 ++++++++- awc/tests/test_client.rs | 508 +++++++++++++++++++++++++++++ tests/test_server.rs | 24 +- 7 files changed, 775 insertions(+), 67 deletions(-) create mode 100644 awc/tests/test_client.rs diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index b4246c64..8be6702f 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -74,7 +74,7 @@ where Err(e) => return Err(e.into()), } } else { - break; + return Ok(Async::Ready(Some(chunk))); } } Async::Ready(None) => { @@ -150,7 +150,7 @@ impl ContentDecoder { #[allow(unreachable_patterns)] fn feed_data(&mut self, data: Bytes) -> io::Result> { match self { - #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] + #[cfg(feature = "brotli")] ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 72b72d36..88c3be42 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -30,7 +30,7 @@ ssl = ["openssl", "actix-http/ssl"] cookies = ["cookie", "actix-http/cookies"] # brotli encoding, requires c compiler -brotli = ["actix-http/brotli2"] +brotli = ["actix-http/brotli"] # miniz-sys backend for flate2 crate flate2-zlib = ["actix-http/flate2-zlib"] @@ -53,8 +53,12 @@ cookie = { version="0.11", features=["percent-encode"], optional = true } openssl = { version="0.10", optional = true } [dev-dependencies] -env_logger = "0.6" -mime = "0.3" actix-rt = "0.2.1" +actix-web = { path = "..", features=["ssl"] } actix-http = { path = "../actix-http/", features=["ssl"] } actix-http-test = { path = "../test-server/", features=["ssl"] } +brotli2 = { version="^0.3.2" } +flate2 = { version="^1.0.2" } +env_logger = "0.6" +mime = "0.3" +rand = "0.6" diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 89acf7d5..4898a062 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; use std::rc::Rc; pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; +pub use actix_http::error::PayloadError; pub use actix_http::http; use actix_http::client::Connector; diff --git a/awc/src/request.rs b/awc/src/request.rs index f23aa7ef..90f9a1ab 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -13,15 +13,24 @@ use serde_json; use actix_http::body::{Body, BodyStream}; use actix_http::client::{InvalidUrl, SendRequestError}; -use actix_http::http::header::{self, Header, IntoHeaderValue}; +use actix_http::encoding::Decoder; +use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; use actix_http::http::{ uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use actix_http::{Error, Head, RequestHead}; +use actix_http::{Error, Head, Payload, RequestHead}; use crate::response::ClientResponse; -use crate::Connect; +use crate::{Connect, PayloadError}; + +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] +const HTTPS_ENCODING: &str = "br, gzip, deflate"; +#[cfg(all( + any(feature = "flate2-zlib", feature = "flate2-rust"), + not(feature = "brotli") +))] +const HTTPS_ENCODING: &str = "gzip, deflate"; /// An HTTP Client request builder /// @@ -52,6 +61,7 @@ pub struct ClientRequest { #[cfg(feature = "cookies")] cookies: Option, default_headers: bool, + response_decompress: bool, connector: Rc>, } @@ -81,6 +91,7 @@ impl ClientRequest { #[cfg(feature = "cookies")] cookies: None, default_headers: true, + response_decompress: true, } } @@ -275,6 +286,12 @@ impl ClientRequest { self } + /// Disable automatic decompress of response's body + pub fn no_decompress(mut self) -> Self { + self.response_decompress = false; + self + } + /// This method calls provided closure with builder reference if /// value is `true`. pub fn if_true(mut self, value: bool, f: F) -> Self @@ -303,7 +320,10 @@ impl ClientRequest { pub fn send_body( mut self, body: B, - ) -> impl Future + ) -> impl Future< + Item = ClientResponse>, + Error = SendRequestError, + > where B: Into, { @@ -311,42 +331,44 @@ impl ClientRequest { return Either::A(err(e.into())); } - let mut slf = if self.default_headers { - // enable br only for https - let https = self - .head - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true); - - let mut slf = if https { - self.set_header_if_none(header::ACCEPT_ENCODING, "br, gzip, deflate") - } else { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") - }; + // validate uri + let uri = &self.head.uri; + if uri.host().is_none() { + return Either::A(err(InvalidUrl::MissingHost.into())); + } else if uri.scheme_part().is_none() { + return Either::A(err(InvalidUrl::MissingScheme.into())); + } else if let Some(scheme) = uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" | "https" | "wss" => (), + _ => return Either::A(err(InvalidUrl::UnknownScheme.into())), + } + } else { + return Either::A(err(InvalidUrl::UnknownScheme.into())); + } + // set default headers + let slf = if self.default_headers { // set request host header - if let Some(host) = slf.head.uri.host() { - if !slf.head.headers.contains_key(header::HOST) { + if let Some(host) = self.head.uri.host() { + if !self.head.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match slf.head.uri.port_u16() { + let _ = match self.head.uri.port_u16() { None | Some(80) | Some(443) => write!(wrt, "{}", host), Some(port) => write!(wrt, "{}:{}", host, port), }; match wrt.get_mut().take().freeze().try_into() { Ok(value) => { - slf.head.headers.insert(header::HOST, value); + self.head.headers.insert(header::HOST, value); } - Err(e) => slf.err = Some(e.into()), + Err(e) => return Either::A(err(HttpError::from(e).into())), } } } // user agent - slf.set_header_if_none( + self.set_header_if_none( header::USER_AGENT, concat!("actix-http/", env!("CARGO_PKG_VERSION")), ) @@ -354,6 +376,32 @@ impl ClientRequest { self }; + // enable br only for https + let https = slf + .head + .uri + .scheme_part() + .map(|s| s == &uri::Scheme::HTTPS) + .unwrap_or(true); + + #[cfg(any( + feature = "brotli", + feature = "flate2-zlib", + feature = "flate2-rust" + ))] + let mut slf = { + if https { + slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) + } else { + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] + { + slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + } + #[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))] + slf + } + }; + #[allow(unused_mut)] let mut head = slf.head; @@ -378,30 +426,32 @@ impl ClientRequest { } } - let uri = head.uri.clone(); + let response_decompress = slf.response_decompress; - // validate uri - if uri.host().is_none() { - Either::A(err(InvalidUrl::MissingHost.into())) - } else if uri.scheme_part().is_none() { - Either::A(err(InvalidUrl::MissingScheme.into())) - } else if let Some(scheme) = uri.scheme_part() { - match scheme.as_str() { - "http" | "ws" | "https" | "wss" => { - Either::B(slf.connector.borrow_mut().send_request(head, body.into())) - } - _ => Either::A(err(InvalidUrl::UnknownScheme.into())), - } - } else { - Either::A(err(InvalidUrl::UnknownScheme.into())) - } + let fut = slf + .connector + .borrow_mut() + .send_request(head, body.into()) + .map(move |res| { + res.map_body(|head, payload| { + if response_decompress { + Payload::Stream(Decoder::from_headers(&head.headers, payload)) + } else { + Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) + } + }) + }); + Either::B(fut) } /// Set a JSON body and generate `ClientRequest` pub fn send_json( self, value: T, - ) -> impl Future { + ) -> impl Future< + Item = ClientResponse>, + Error = SendRequestError, + > { let body = match serde_json::to_string(&value) { Ok(body) => body, Err(e) => return Either::A(err(Error::from(e).into())), @@ -422,7 +472,10 @@ impl ClientRequest { pub fn send_form( self, value: T, - ) -> impl Future { + ) -> impl Future< + Item = ClientResponse>, + Error = SendRequestError, + > { let body = match serde_urlencoded::to_string(&value) { Ok(body) => body, Err(e) => return Either::A(err(Error::from(e).into())), @@ -441,7 +494,10 @@ impl ClientRequest { pub fn send_stream( self, stream: S, - ) -> impl Future + ) -> impl Future< + Item = ClientResponse>, + Error = SendRequestError, + > where S: Stream + 'static, E: Into + 'static, @@ -450,7 +506,12 @@ impl ClientRequest { } /// Set an empty body and generate `ClientRequest`. - pub fn send(self) -> impl Future { + pub fn send( + self, + ) -> impl Future< + Item = ClientResponse>, + Error = SendRequestError, + > { self.send_body(Body::Empty) } } diff --git a/awc/src/response.rs b/awc/src/response.rs index 0ae66df0..4525bbc1 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -1,21 +1,22 @@ use std::cell::{Ref, RefMut}; use std::fmt; -use bytes::Bytes; -use futures::{Poll, Stream}; +use bytes::{Bytes, BytesMut}; +use futures::{Future, Poll, Stream}; use actix_http::error::PayloadError; +use actix_http::http::header::CONTENT_LENGTH; use actix_http::http::{HeaderMap, StatusCode, Version}; use actix_http::{Extensions, Head, HttpMessage, Payload, PayloadStream, ResponseHead}; /// Client Response -pub struct ClientResponse { +pub struct ClientResponse { pub(crate) head: ResponseHead, - pub(crate) payload: Payload, + pub(crate) payload: Payload, } -impl HttpMessage for ClientResponse { - type Stream = PayloadStream; +impl HttpMessage for ClientResponse { + type Stream = S; fn headers(&self) -> &HeaderMap { &self.head.headers @@ -29,14 +30,14 @@ impl HttpMessage for ClientResponse { self.head.extensions_mut() } - fn take_payload(&mut self) -> Payload { + fn take_payload(&mut self) -> Payload { std::mem::replace(&mut self.payload, Payload::None) } } -impl ClientResponse { +impl ClientResponse { /// Create new Request instance - pub(crate) fn new(head: ResponseHead, payload: Payload) -> ClientResponse { + pub(crate) fn new(head: ResponseHead, payload: Payload) -> Self { ClientResponse { head, payload } } @@ -79,9 +80,35 @@ impl ClientResponse { pub fn keep_alive(&self) -> bool { self.head().keep_alive() } + + /// Set a body and return previous body value + pub fn map_body(mut self, f: F) -> ClientResponse + where + F: FnOnce(&mut ResponseHead, Payload) -> Payload, + { + let payload = f(&mut self.head, self.payload); + + ClientResponse { + payload, + head: self.head, + } + } } -impl Stream for ClientResponse { +impl ClientResponse +where + S: Stream + 'static, +{ + /// Load http response's body. + pub fn body(self) -> MessageBody { + MessageBody::new(self) + } +} + +impl Stream for ClientResponse +where + S: Stream, +{ type Item = Bytes; type Error = PayloadError; @@ -90,7 +117,7 @@ impl Stream for ClientResponse { } } -impl fmt::Debug for ClientResponse { +impl fmt::Debug for ClientResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; writeln!(f, " headers:")?; @@ -100,3 +127,100 @@ impl fmt::Debug for ClientResponse { Ok(()) } } + +/// Future that resolves to a complete http message body. +pub struct MessageBody { + limit: usize, + length: Option, + stream: Option>, + err: Option, + fut: Option>>, +} + +impl MessageBody +where + S: Stream + 'static, +{ + /// Create `MessageBody` for request. + pub fn new(res: ClientResponse) -> MessageBody { + let mut len = None; + if let Some(l) = res.headers().get(CONTENT_LENGTH) { + if let Ok(s) = l.to_str() { + if let Ok(l) = s.parse::() { + len = Some(l) + } else { + return Self::err(PayloadError::UnknownLength); + } + } else { + return Self::err(PayloadError::UnknownLength); + } + } + + MessageBody { + limit: 262_144, + length: len, + stream: Some(res), + fut: None, + err: None, + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + fn err(e: PayloadError) -> Self { + MessageBody { + stream: None, + limit: 262_144, + fut: None, + err: Some(e), + length: None, + } + } +} + +impl Future for MessageBody +where + S: Stream + 'static, +{ + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut { + return fut.poll(); + } + + if let Some(err) = self.err.take() { + return Err(err); + } + + if let Some(len) = self.length.take() { + if len > self.limit { + return Err(PayloadError::Overflow); + } + } + + // future + let limit = self.limit; + self.fut = Some(Box::new( + self.stream + .take() + .expect("Can not be used second time") + .from_err() + .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(PayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .map(|body| body.freeze()), + )); + self.poll() + } +} diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs new file mode 100644 index 00000000..f7605b59 --- /dev/null +++ b/awc/tests/test_client.rs @@ -0,0 +1,508 @@ +use std::io::{Read, Write}; +use std::{net, thread}; + +use brotli2::write::BrotliEncoder; +use bytes::Bytes; +use flate2::write::{GzEncoder, ZlibEncoder}; +use flate2::Compression; +use futures::stream::once; +use futures::Future; +use rand::Rng; + +use actix_http::HttpService; +use actix_http_test::TestServer; +use actix_web::{middleware, web, App, HttpRequest, HttpResponse}; + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_simple() { + let mut srv = + TestServer::new(|| { + HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + )) + }); + + let request = srv.get().header("x-test", "111").send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + let response = srv.block_on(srv.post().send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +// #[test] +// fn test_connection_close() { +// let mut srv = +// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + +// let request = srv.get().header("Connection", "close").finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); +// } + +// #[test] +// fn test_with_query_parameter() { +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| match req.query().get("qp") { +// Some(_) => HttpResponse::Ok().finish(), +// None => HttpResponse::BadRequest().finish(), +// }) +// }); + +// let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); + +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); +// } + +// #[test] +// fn test_no_decompress() { +// let mut srv = +// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + +// let request = srv.get().disable_decompress().finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); + +// let mut e = GzDecoder::new(&bytes[..]); +// let mut dec = Vec::new(); +// e.read_to_end(&mut dec).unwrap(); +// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + +// // POST +// let request = srv.post().disable_decompress().finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); + +// let bytes = srv.execute(response.body()).unwrap(); +// let mut e = GzDecoder::new(&bytes[..]); +// let mut dec = Vec::new(); +// e.read_to_end(&mut dec).unwrap(); +// assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +// } + +#[test] +fn test_client_gzip_encoding() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to(|| { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let data = e.finish().unwrap(); + + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + })))) + }); + + // client request + let response = srv.block_on(srv.post().send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_client_gzip_encoding_large() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to(|| { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.repeat(10).as_ref()).unwrap(); + let data = e.finish().unwrap(); + + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + })))) + }); + + // client request + let response = srv.block_on(srv.post().send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(STR.repeat(10))); +} + +#[test] +fn test_client_gzip_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(100_000) + .collect::(); + + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |data: Bytes| { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(&data).unwrap(); + let data = e.finish().unwrap(); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + }, + )))) + }); + + // client request + let response = srv.block_on(srv.post().send_body(data.clone())).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} + +#[test] +fn test_client_brotli_encoding() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |data: Bytes| { + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(&data).unwrap(); + let data = e.finish().unwrap(); + HttpResponse::Ok() + .header("content-encoding", "br") + .body(data) + }, + )))) + }); + + // client request + let response = srv.block_on(srv.post().send_body(STR)).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +// #[test] +// fn test_client_brotli_encoding_large_random() { +// let data = rand::thread_rng() +// .sample_iter(&rand::distributions::Alphanumeric) +// .take(70_000) +// .collect::(); + +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(move |bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Gzip) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// // client request +// let request = srv +// .client(http::Method::POST, "/") +// .content_encoding(http::ContentEncoding::Br) +// .body(data.clone()) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes.len(), data.len()); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[cfg(feature = "brotli")] +// #[test] +// fn test_client_deflate_encoding() { +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Br) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// // client request +// let request = srv +// .post() +// .content_encoding(http::ContentEncoding::Deflate) +// .body(STR) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +// } + +// #[test] +// fn test_client_deflate_encoding_large_random() { +// let data = rand::thread_rng() +// .sample_iter(&rand::distributions::Alphanumeric) +// .take(70_000) +// .collect::(); + +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .and_then(|bytes: Bytes| { +// Ok(HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Br) +// .body(bytes)) +// }) +// .responder() +// }) +// }); + +// // client request +// let request = srv +// .post() +// .content_encoding(http::ContentEncoding::Deflate) +// .body(data.clone()) +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from(data)); +// } + +// #[test] +// fn test_client_streaming_explicit() { +// let mut srv = test::TestServer::new(|app| { +// app.handler(|req: &HttpRequest| { +// req.body() +// .map_err(Error::from) +// .and_then(|body| { +// Ok(HttpResponse::Ok() +// .chunked() +// .content_encoding(http::ContentEncoding::Identity) +// .body(body)) +// }) +// .responder() +// }) +// }); + +// let body = once(Ok(Bytes::from_static(STR.as_ref()))); + +// let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +// } + +// #[test] +// fn test_body_streaming_implicit() { +// let mut srv = test::TestServer::new(|app| { +// app.handler(|_| { +// let body = once(Ok(Bytes::from_static(STR.as_ref()))); +// HttpResponse::Ok() +// .content_encoding(http::ContentEncoding::Gzip) +// .body(Body::Streaming(Box::new(body))) +// }) +// }); + +// let request = srv.get().finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = srv.execute(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +// } + +// #[test] +// fn test_client_cookie_handling() { +// use actix_web::http::Cookie; +// fn err() -> Error { +// use std::io::{Error as IoError, ErrorKind}; +// // stub some generic error +// Error::from(IoError::from(ErrorKind::NotFound)) +// } +// let cookie1 = Cookie::build("cookie1", "value1").finish(); +// let cookie2 = Cookie::build("cookie2", "value2") +// .domain("www.example.org") +// .path("/") +// .secure(true) +// .http_only(true) +// .finish(); +// // Q: are all these clones really necessary? A: Yes, possibly +// let cookie1b = cookie1.clone(); +// let cookie2b = cookie2.clone(); +// let mut srv = test::TestServer::new(move |app| { +// let cookie1 = cookie1b.clone(); +// let cookie2 = cookie2b.clone(); +// app.handler(move |req: &HttpRequest| { +// // Check cookies were sent correctly +// req.cookie("cookie1") +// .ok_or_else(err) +// .and_then(|c1| { +// if c1.value() == "value1" { +// Ok(()) +// } else { +// Err(err()) +// } +// }) +// .and_then(|()| req.cookie("cookie2").ok_or_else(err)) +// .and_then(|c2| { +// if c2.value() == "value2" { +// Ok(()) +// } else { +// Err(err()) +// } +// }) +// // Send some cookies back +// .map(|_| { +// HttpResponse::Ok() +// .cookie(cookie1.clone()) +// .cookie(cookie2.clone()) +// .finish() +// }) +// }) +// }); + +// let request = srv +// .get() +// .cookie(cookie1.clone()) +// .cookie(cookie2.clone()) +// .finish() +// .unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); +// let c1 = response.cookie("cookie1").expect("Missing cookie1"); +// assert_eq!(c1, cookie1); +// let c2 = response.cookie("cookie2").expect("Missing cookie2"); +// assert_eq!(c2, cookie2); +// } + +// #[test] +// fn test_default_headers() { +// let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); + +// let request = srv.get().finish().unwrap(); +// let repr = format!("{:?}", request); +// assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); +// assert!(repr.contains(concat!( +// "\"user-agent\": \"actix-web/", +// env!("CARGO_PKG_VERSION"), +// "\"" +// ))); + +// let request_override = srv +// .get() +// .header("User-Agent", "test") +// .header("Accept-Encoding", "over_test") +// .finish() +// .unwrap(); +// let repr_override = format!("{:?}", request_override); +// assert!(repr_override.contains("\"user-agent\": \"test\"")); +// assert!(repr_override.contains("\"accept-encoding\": \"over_test\"")); +// assert!(!repr_override.contains("\"accept-encoding\": \"gzip, deflate\"")); +// assert!(!repr_override.contains(concat!( +// "\"user-agent\": \"Actix-web/", +// env!("CARGO_PKG_VERSION"), +// "\"" +// ))); +// } + +// #[test] +// fn client_read_until_eof() { +// let addr = test::TestServer::unused_addr(); + +// thread::spawn(move || { +// let lst = net::TcpListener::bind(addr).unwrap(); + +// for stream in lst.incoming() { +// let mut stream = stream.unwrap(); +// let mut b = [0; 1000]; +// let _ = stream.read(&mut b).unwrap(); +// let _ = stream +// .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); +// } +// }); + +// let mut sys = actix::System::new("test"); + +// // client request +// let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) +// .finish() +// .unwrap(); +// let response = sys.block_on(req.send()).unwrap(); +// assert!(response.status().is_success()); + +// // read response +// let bytes = sys.block_on(response.body()).unwrap(); +// assert_eq!(bytes, Bytes::from_static(b"welcome!")); +// } + +// #[test] +// fn client_basic_auth() { +// let mut srv = +// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); +// /// set authorization header to Basic +// let request = srv +// .get() +// .basic_auth("username", Some("password")) +// .finish() +// .unwrap(); +// let repr = format!("{:?}", request); +// assert!(repr.contains("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")); +// } + +// #[test] +// fn client_bearer_auth() { +// let mut srv = +// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); +// /// set authorization header to Bearer +// let request = srv +// .get() +// .bearer_auth("someS3cr3tAutht0k3n") +// .finish() +// .unwrap(); +// let repr = format!("{:?}", request); +// assert!(repr.contains("Bearer someS3cr3tAutht0k3n")); +// } diff --git a/tests/test_server.rs b/tests/test_server.rs index acea029c..29998bc0 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -65,7 +65,7 @@ fn test_body_gzip() { ) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -95,7 +95,7 @@ fn test_body_gzip_large() { ) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -128,7 +128,7 @@ fn test_body_gzip_large_random() { ) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -156,7 +156,7 @@ fn test_body_chunked_implicit() { ) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response.headers().get(TRANSFER_ENCODING).unwrap(), @@ -188,7 +188,12 @@ fn test_body_br_streaming() { }); let mut response = srv - .block_on(srv.get().header(ACCEPT_ENCODING, "br").send()) + .block_on( + srv.get() + .header(ACCEPT_ENCODING, "br") + .no_decompress() + .send(), + ) .unwrap(); assert!(response.status().is_success()); @@ -258,7 +263,7 @@ fn test_body_deflate() { }); // client request - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -285,7 +290,12 @@ fn test_body_brotli() { // client request let mut response = srv - .block_on(srv.get().header(ACCEPT_ENCODING, "br").send()) + .block_on( + srv.get() + .header(ACCEPT_ENCODING, "br") + .no_decompress() + .send(), + ) .unwrap(); assert!(response.status().is_success()); From ab597dd98a4bd2f768e2ee419fa752e97ddcec2e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 20:57:06 -0700 Subject: [PATCH 1134/1635] Added HTTP Authentication for Client #540 --- awc/Cargo.toml | 1 + awc/src/request.rs | 24 +++++++++++ awc/tests/test_client.rs | 89 +++++++++++++++++++++++++--------------- 3 files changed, 82 insertions(+), 32 deletions(-) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 88c3be42..e08169c9 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -41,6 +41,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-service = "0.3.4" actix-http = { path = "../actix-http/" } +base64 = "0.10.1" bytes = "0.4" futures = "0.1" log =" 0.4" diff --git a/awc/src/request.rs b/awc/src/request.rs index 90f9a1ab..649797df 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -242,6 +242,30 @@ impl ClientRequest { self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) } + /// Set HTTP basic authorization + pub fn basic_auth(self, username: U, password: Option

    ) -> Self + where + U: fmt::Display, + P: fmt::Display, + { + let auth = match password { + Some(password) => format!("{}:{}", username, password), + None => format!("{}", username), + }; + self.header( + header::AUTHORIZATION, + format!("Basic {}", base64::encode(&auth)), + ) + } + + /// Set HTTP bearer authentication + pub fn bearer_auth(self, token: T) -> Self + where + T: fmt::Display, + { + self.header(header::AUTHORIZATION, format!("Bearer {}", token)) + } + #[cfg(feature = "cookies")] /// Set a cookie /// diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index f7605b59..ac07eb6d 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,17 +1,14 @@ -use std::io::{Read, Write}; -use std::{net, thread}; +use std::io::Write; use brotli2::write::BrotliEncoder; use bytes::Bytes; -use flate2::write::{GzEncoder, ZlibEncoder}; +use flate2::write::GzEncoder; use flate2::Compression; -use futures::stream::once; -use futures::Future; use rand::Rng; use actix_http::HttpService; use actix_http_test::TestServer; -use actix_web::{middleware, web, App, HttpRequest, HttpResponse}; +use actix_web::{http::header, web, App, HttpMessage, HttpRequest, HttpResponse}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -479,30 +476,58 @@ fn test_client_brotli_encoding() { // assert_eq!(bytes, Bytes::from_static(b"welcome!")); // } -// #[test] -// fn client_basic_auth() { -// let mut srv = -// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); -// /// set authorization header to Basic -// let request = srv -// .get() -// .basic_auth("username", Some("password")) -// .finish() -// .unwrap(); -// let repr = format!("{:?}", request); -// assert!(repr.contains("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")); -// } +#[test] +fn client_basic_auth() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().route( + "/", + web::to(|req: HttpRequest| { + if req + .headers() + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap() + == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + }); -// #[test] -// fn client_bearer_auth() { -// let mut srv = -// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); -// /// set authorization header to Bearer -// let request = srv -// .get() -// .bearer_auth("someS3cr3tAutht0k3n") -// .finish() -// .unwrap(); -// let repr = format!("{:?}", request); -// assert!(repr.contains("Bearer someS3cr3tAutht0k3n")); -// } + // set authorization header to Basic + let request = srv.get().basic_auth("username", Some("password")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); +} + +#[test] +fn client_bearer_auth() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().route( + "/", + web::to(|req: HttpRequest| { + if req + .headers() + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap() + == "Bearer someS3cr3tAutht0k3n" + { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + }); + + // set authorization header to Bearer + let request = srv.get().bearer_auth("someS3cr3tAutht0k3n"); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); +} From 5703bd8160e7f0788af70740f84f9973272b09eb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 21:31:18 -0700 Subject: [PATCH 1135/1635] fix client cookies parsing --- awc/src/request.rs | 4 +- awc/src/response.rs | 27 ++++++++- awc/tests/test_client.rs | 126 +++++++++++++++++++-------------------- 3 files changed, 91 insertions(+), 66 deletions(-) diff --git a/awc/src/request.rs b/awc/src/request.rs index 649797df..16e42939 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -242,7 +242,7 @@ impl ClientRequest { self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) } - /// Set HTTP basic authorization + /// Set HTTP basic authorization header pub fn basic_auth(self, username: U, password: Option

    ) -> Self where U: fmt::Display, @@ -258,7 +258,7 @@ impl ClientRequest { ) } - /// Set HTTP bearer authentication + /// Set HTTP bearer authentication header pub fn bearer_auth(self, token: T) -> Self where T: fmt::Display, diff --git a/awc/src/response.rs b/awc/src/response.rs index 4525bbc1..5806dc91 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -5,10 +5,15 @@ use bytes::{Bytes, BytesMut}; use futures::{Future, Poll, Stream}; use actix_http::error::PayloadError; -use actix_http::http::header::CONTENT_LENGTH; +use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE}; use actix_http::http::{HeaderMap, StatusCode, Version}; use actix_http::{Extensions, Head, HttpMessage, Payload, PayloadStream, ResponseHead}; +#[cfg(feature = "cookies")] +use actix_http::error::CookieParseError; +#[cfg(feature = "cookies")] +use cookie::Cookie; + /// Client Response pub struct ClientResponse { pub(crate) head: ResponseHead, @@ -33,6 +38,26 @@ impl HttpMessage for ClientResponse { fn take_payload(&mut self) -> Payload { std::mem::replace(&mut self.payload, Payload::None) } + + /// Load request cookies. + #[inline] + #[cfg(feature = "cookies")] + fn cookies(&self) -> Result>>, CookieParseError> { + struct Cookies(Vec>); + + if self.extensions().get::().is_none() { + let mut cookies = Vec::new(); + for hdr in self.headers().get_all(SET_COOKIE) { + let s = std::str::from_utf8(hdr.as_bytes()) + .map_err(CookieParseError::from)?; + cookies.push(Cookie::parse_encoded(s)?.into_owned()); + } + self.extensions_mut().insert(Cookies(cookies)); + } + Ok(Ref::map(self.extensions(), |ext| { + &ext.get::().unwrap().0 + })) + } } impl ClientResponse { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index ac07eb6d..698b5ab7 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -8,7 +8,7 @@ use rand::Rng; use actix_http::HttpService; use actix_http_test::TestServer; -use actix_web::{http::header, web, App, HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -352,69 +352,69 @@ fn test_client_brotli_encoding() { // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // } -// #[test] -// fn test_client_cookie_handling() { -// use actix_web::http::Cookie; -// fn err() -> Error { -// use std::io::{Error as IoError, ErrorKind}; -// // stub some generic error -// Error::from(IoError::from(ErrorKind::NotFound)) -// } -// let cookie1 = Cookie::build("cookie1", "value1").finish(); -// let cookie2 = Cookie::build("cookie2", "value2") -// .domain("www.example.org") -// .path("/") -// .secure(true) -// .http_only(true) -// .finish(); -// // Q: are all these clones really necessary? A: Yes, possibly -// let cookie1b = cookie1.clone(); -// let cookie2b = cookie2.clone(); -// let mut srv = test::TestServer::new(move |app| { -// let cookie1 = cookie1b.clone(); -// let cookie2 = cookie2b.clone(); -// app.handler(move |req: &HttpRequest| { -// // Check cookies were sent correctly -// req.cookie("cookie1") -// .ok_or_else(err) -// .and_then(|c1| { -// if c1.value() == "value1" { -// Ok(()) -// } else { -// Err(err()) -// } -// }) -// .and_then(|()| req.cookie("cookie2").ok_or_else(err)) -// .and_then(|c2| { -// if c2.value() == "value2" { -// Ok(()) -// } else { -// Err(err()) -// } -// }) -// // Send some cookies back -// .map(|_| { -// HttpResponse::Ok() -// .cookie(cookie1.clone()) -// .cookie(cookie2.clone()) -// .finish() -// }) -// }) -// }); +#[test] +fn test_client_cookie_handling() { + use actix_web::http::Cookie; + fn err() -> Error { + use std::io::{Error as IoError, ErrorKind}; + // stub some generic error + Error::from(IoError::from(ErrorKind::NotFound)) + } + let cookie1 = Cookie::build("cookie1", "value1").finish(); + let cookie2 = Cookie::build("cookie2", "value2") + .domain("www.example.org") + .path("/") + .secure(true) + .http_only(true) + .finish(); + // Q: are all these clones really necessary? A: Yes, possibly + let cookie1b = cookie1.clone(); + let cookie2b = cookie2.clone(); -// let request = srv -// .get() -// .cookie(cookie1.clone()) -// .cookie(cookie2.clone()) -// .finish() -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); -// let c1 = response.cookie("cookie1").expect("Missing cookie1"); -// assert_eq!(c1, cookie1); -// let c2 = response.cookie("cookie2").expect("Missing cookie2"); -// assert_eq!(c2, cookie2); -// } + let mut srv = TestServer::new(move || { + let cookie1 = cookie1b.clone(); + let cookie2 = cookie2b.clone(); + + HttpService::new(App::new().route( + "/", + web::to(move |req: HttpRequest| { + // Check cookies were sent correctly + req.cookie("cookie1") + .ok_or_else(err) + .and_then(|c1| { + if c1.value() == "value1" { + Ok(()) + } else { + Err(err()) + } + }) + .and_then(|()| req.cookie("cookie2").ok_or_else(err)) + .and_then(|c2| { + if c2.value() == "value2" { + Ok(()) + } else { + Err(err()) + } + }) + // Send some cookies back + .map(|_| { + HttpResponse::Ok() + .cookie(cookie1.clone()) + .cookie(cookie2.clone()) + .finish() + }) + }), + )) + }); + + let request = srv.get().cookie(cookie1.clone()).cookie(cookie2.clone()); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + let c1 = response.cookie("cookie1").expect("Missing cookie1"); + assert_eq!(c1, cookie1); + let c2 = response.cookie("cookie2").expect("Missing cookie2"); + assert_eq!(c2, cookie2); +} // #[test] // fn test_default_headers() { From d49a8ba53bb9f9d6997a7c9d470c18a59c149527 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 21:54:57 -0700 Subject: [PATCH 1136/1635] add client TestResponse --- actix-http/src/h1/payload.rs | 46 +++-------- awc/src/lib.rs | 1 + awc/src/response.rs | 39 +++++++++ awc/src/test.rs | 155 +++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 35 deletions(-) create mode 100644 awc/src/test.rs diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 6665a0e4..979dd015 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -1,14 +1,14 @@ //! Payload stream -use bytes::{Bytes, BytesMut}; -#[cfg(not(test))] -use futures::task::current as current_task; -use futures::task::Task; -use futures::{Async, Poll, Stream}; use std::cell::RefCell; use std::cmp; use std::collections::VecDeque; use std::rc::{Rc, Weak}; +use bytes::{Bytes, BytesMut}; +use futures::task::current as current_task; +use futures::task::Task; +use futures::{Async, Poll, Stream}; + use crate::error::PayloadError; /// max buffer size 32k @@ -79,11 +79,6 @@ impl Payload { self.inner.borrow_mut().unread_data(data); } - #[cfg(test)] - pub(crate) fn readall(&self) -> Option { - self.inner.borrow_mut().readall() - } - #[inline] /// Set read buffer capacity /// @@ -226,35 +221,16 @@ impl Inner { self.len } - #[cfg(test)] - pub(crate) fn readall(&mut self) -> Option { - let len = self.items.iter().map(|b| b.len()).sum(); - if len > 0 { - let mut buf = BytesMut::with_capacity(len); - for item in &self.items { - buf.extend_from_slice(item); - } - self.items = VecDeque::new(); - self.len = 0; - Some(buf.take().freeze()) - } else { - self.need_read = true; - None - } - } - fn readany(&mut self) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); self.need_read = self.len < self.capacity; - #[cfg(not(test))] - { - if self.need_read && self.task.is_none() { - self.task = Some(current_task()); - } - if let Some(task) = self.io_task.take() { - task.notify() - } + + if self.need_read && self.task.is_none() && !self.eof { + self.task = Some(current_task()); + } + if let Some(task) = self.io_task.take() { + task.notify() } Ok(Async::Ready(Some(data))) } else if let Some(err) = self.err.take() { diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 4898a062..8ce5b35f 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -12,6 +12,7 @@ mod builder; mod connect; mod request; mod response; +pub mod test; pub use self::builder::ClientBuilder; pub use self::request::ClientRequest; diff --git a/awc/src/response.rs b/awc/src/response.rs index 5806dc91..abff771c 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -249,3 +249,42 @@ where self.poll() } } + +#[cfg(test)] +mod tests { + use super::*; + use futures::Async; + + use crate::{http::header, test::TestResponse}; + + #[test] + fn test_body() { + let req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + match req.body().poll().err().unwrap() { + PayloadError::UnknownLength => (), + _ => unreachable!("error"), + } + + let req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + match req.body().poll().err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } + + let req = TestResponse::default() + .set_payload(Bytes::from_static(b"test")) + .finish(); + match req.body().poll().ok().unwrap() { + Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), + _ => unreachable!("error"), + } + + let req = TestResponse::default() + .set_payload(Bytes::from_static(b"11111111111111")) + .finish(); + match req.body().limit(5).poll().err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } + } +} diff --git a/awc/src/test.rs b/awc/src/test.rs new file mode 100644 index 00000000..464abdd5 --- /dev/null +++ b/awc/src/test.rs @@ -0,0 +1,155 @@ +//! Test Various helpers for Actix applications to use during testing. + +use actix_http::http::header::{Header, IntoHeaderValue}; +use actix_http::http::{HeaderName, HttpTryFrom, Version}; +use actix_http::{h1, Payload, ResponseHead}; +use bytes::Bytes; +#[cfg(feature = "cookies")] +use cookie::{Cookie, CookieJar}; + +use crate::ClientResponse; + +/// Test `ClientResponse` builder +/// +/// ```rust,ignore +/// # extern crate http; +/// # extern crate actix_web; +/// # use http::{header, StatusCode}; +/// # use actix_web::*; +/// use actix_web::test::TestRequest; +/// +/// fn index(req: &HttpRequest) -> Response { +/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { +/// Response::Ok().into() +/// } else { +/// Response::BadRequest().into() +/// } +/// } +/// +/// 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 TestResponse(Option); + +struct Inner { + head: ResponseHead, + #[cfg(feature = "cookies")] + cookies: CookieJar, + payload: Option, +} + +impl Default for TestResponse { + fn default() -> TestResponse { + TestResponse(Some(Inner { + head: ResponseHead::default(), + #[cfg(feature = "cookies")] + cookies: CookieJar::new(), + payload: None, + })) + } +} + +impl TestResponse { + /// Create TestRequest and set header + pub fn with_header(key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + Self::default().header(key, value).take() + } + + /// Set HTTP version of this request + pub fn version(&mut self, ver: Version) -> &mut Self { + parts(&mut self.0).head.version = ver; + self + } + + /// Set a header + pub fn set(&mut self, hdr: H) -> &mut Self { + if let Ok(value) = hdr.try_into() { + parts(&mut self.0).head.headers.append(H::name(), value); + return self; + } + panic!("Can not set header"); + } + + /// Set a header + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if let Ok(key) = HeaderName::try_from(key) { + if let Ok(value) = value.try_into() { + parts(&mut self.0).head.headers.append(key, value); + return self; + } + } + panic!("Can not create header"); + } + + /// Set cookie for this request + #[cfg(feature = "cookies")] + pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { + parts(&mut self.0).cookies.add(cookie.into_owned()); + self + } + + /// Set request payload + pub fn set_payload>(&mut self, data: B) -> &mut Self { + let mut payload = h1::Payload::empty(); + payload.unread_data(data.into()); + parts(&mut self.0).payload = Some(payload.into()); + self + } + + pub fn take(&mut self) -> Self { + Self(self.0.take()) + } + + /// Complete request creation and generate `Request` instance + pub fn finish(&mut self) -> ClientResponse { + let inner = self.0.take().expect("cannot reuse test request builder");; + let mut head = inner.head; + + #[cfg(feature = "cookies")] + { + use std::fmt::Write as FmtWrite; + + use actix_http::http::header::{self, HeaderValue}; + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + + let mut cookie = String::new(); + for c in inner.cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + if !cookie.is_empty() { + head.headers.insert( + header::SET_COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } + } + + if let Some(pl) = inner.payload { + ClientResponse::new(head, pl) + } else { + ClientResponse::new(head, h1::Payload::empty().into()) + } + } +} + +#[inline] +fn parts<'a>(parts: &'a mut Option) -> &'a mut Inner { + parts.as_mut().expect("cannot reuse test request builder") +} From 959aebb24f006faa53511813e65f3baa2c98960a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 22:03:00 -0700 Subject: [PATCH 1137/1635] simplify TestResponse builder --- awc/src/test.rs | 86 ++++++++++++++----------------------------------- 1 file changed, 24 insertions(+), 62 deletions(-) diff --git a/awc/src/test.rs b/awc/src/test.rs index 464abdd5..165694d8 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -10,35 +10,7 @@ use cookie::{Cookie, CookieJar}; use crate::ClientResponse; /// Test `ClientResponse` builder -/// -/// ```rust,ignore -/// # extern crate http; -/// # extern crate actix_web; -/// # use http::{header, StatusCode}; -/// # use actix_web::*; -/// use actix_web::test::TestRequest; -/// -/// fn index(req: &HttpRequest) -> Response { -/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// Response::Ok().into() -/// } else { -/// Response::BadRequest().into() -/// } -/// } -/// -/// 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 TestResponse(Option); - -struct Inner { +pub struct TestResponse { head: ResponseHead, #[cfg(feature = "cookies")] cookies: CookieJar, @@ -47,78 +19,73 @@ struct Inner { impl Default for TestResponse { fn default() -> TestResponse { - TestResponse(Some(Inner { + TestResponse { head: ResponseHead::default(), #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, - })) + } } } impl TestResponse { - /// Create TestRequest and set header + /// Create TestResponse and set header pub fn with_header(key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - Self::default().header(key, value).take() + Self::default().header(key, value) } - /// Set HTTP version of this request - pub fn version(&mut self, ver: Version) -> &mut Self { - parts(&mut self.0).head.version = ver; + /// Set HTTP version of this response + pub fn version(mut self, ver: Version) -> Self { + self.head.version = ver; self } /// Set a header - pub fn set(&mut self, hdr: H) -> &mut Self { + pub fn set(mut self, hdr: H) -> Self { if let Ok(value) = hdr.try_into() { - parts(&mut self.0).head.headers.append(H::name(), value); + self.head.headers.append(H::name(), value); return self; } panic!("Can not set header"); } - /// Set a header - pub fn header(&mut self, key: K, value: V) -> &mut Self + /// Append a header + pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { if let Ok(key) = HeaderName::try_from(key) { if let Ok(value) = value.try_into() { - parts(&mut self.0).head.headers.append(key, value); + self.head.headers.append(key, value); return self; } } panic!("Can not create header"); } - /// Set cookie for this request + /// Set cookie for this response #[cfg(feature = "cookies")] - pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { - parts(&mut self.0).cookies.add(cookie.into_owned()); + pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + self.cookies.add(cookie.into_owned()); self } - /// Set request payload - pub fn set_payload>(&mut self, data: B) -> &mut Self { + /// Set response's payload + pub fn set_payload>(mut self, data: B) -> Self { let mut payload = h1::Payload::empty(); payload.unread_data(data.into()); - parts(&mut self.0).payload = Some(payload.into()); + self.payload = Some(payload.into()); self } - pub fn take(&mut self) -> Self { - Self(self.0.take()) - } - - /// Complete request creation and generate `Request` instance - pub fn finish(&mut self) -> ClientResponse { - let inner = self.0.take().expect("cannot reuse test request builder");; - let mut head = inner.head; + /// Complete response creation and generate `ClientResponse` instance + pub fn finish(self) -> ClientResponse { + let mut head = self.head; #[cfg(feature = "cookies")] { @@ -128,7 +95,7 @@ impl TestResponse { use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; let mut cookie = String::new(); - for c in inner.cookies.delta() { + for c in self.cookies.delta() { let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let _ = write!(&mut cookie, "; {}={}", name, value); @@ -141,15 +108,10 @@ impl TestResponse { } } - if let Some(pl) = inner.payload { + if let Some(pl) = self.payload { ClientResponse::new(head, pl) } else { ClientResponse::new(head, h1::Payload::empty().into()) } } } - -#[inline] -fn parts<'a>(parts: &'a mut Option) -> &'a mut Inner { - parts.as_mut().expect("cannot reuse test request builder") -} From b7570b2476ff605ef407b411c71790a79b0d4bdb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 22:33:01 -0700 Subject: [PATCH 1138/1635] remove unused code --- awc/src/builder.rs | 9 +++++---- awc/src/request.rs | 10 ++++------ awc/src/response.rs | 17 ----------------- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 68694868..562ff241 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -65,13 +65,14 @@ impl ClientBuilder { } /// Do not add default request headers. - /// By default `Accept-Encoding` and `User-Agent` headers are set. - pub fn skip_default_headers(mut self) -> Self { + /// By default `Date` and `User-Agent` headers are set. + pub fn no_default_headers(mut self) -> Self { self.default_headers = false; self } - /// Add default header. This header adds to every request. + /// Add default header. Headers adds byt this method + /// get added to every request. pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, @@ -91,7 +92,7 @@ impl ClientBuilder { self } - /// Finish build process and create `Client`. + /// Finish build process and create `Client` instance. pub fn finish(self) -> Client { Client { connector: self.connector, diff --git a/awc/src/request.rs b/awc/src/request.rs index 16e42939..c944c6ca 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -141,13 +141,11 @@ impl ClientRequest { /// To override header use `set_header()` method. /// /// ```rust - /// # extern crate actix_http; - /// # - /// use actix_http::{client, http}; + /// use awc::{http, Client}; /// /// fn main() { /// # actix_rt::System::new("test").block_on(futures::future::lazy(|| { - /// let req = awc::Client::new() + /// let req = Client::new() /// .get("http://www.rust-lang.org") /// .header("X-TEST", "value") /// .header(http::header::CONTENT_TYPE, "application/json"); @@ -304,7 +302,7 @@ impl ClientRequest { } /// Do not add default request headers. - /// By default `Accept-Encoding` and `User-Agent` headers are set. + /// By default `Date` and `User-Agent` headers are set. pub fn no_default_headers(mut self) -> Self { self.default_headers = false; self @@ -394,7 +392,7 @@ impl ClientRequest { // user agent self.set_header_if_none( header::USER_AGENT, - concat!("actix-http/", env!("CARGO_PKG_VERSION")), + concat!("awc/", env!("CARGO_PKG_VERSION")), ) } else { self diff --git a/awc/src/response.rs b/awc/src/response.rs index abff771c..03606a76 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -71,11 +71,6 @@ impl ClientResponse { &self.head } - #[inline] - pub(crate) fn head_mut(&mut self) -> &mut ResponseHead { - &mut self.head - } - /// Read the Request Version. #[inline] pub fn version(&self) -> Version { @@ -94,18 +89,6 @@ impl ClientResponse { &self.head().headers } - #[inline] - /// Returns mutable Request's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - - /// Checks if a connection should be kept alive. - #[inline] - pub fn keep_alive(&self) -> bool { - self.head().keep_alive() - } - /// Set a body and return previous body value pub fn map_body(mut self, f: F) -> ClientResponse where From b6b37d3ea3702eaa3a3384b37af8ddfe0b630150 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Mar 2019 23:25:24 -0700 Subject: [PATCH 1139/1635] Add Client::request_from --- awc/src/lib.rs | 20 +++++++++++++++++++- awc/src/request.rs | 2 +- awc/src/response.rs | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 8ce5b35f..ac7dcf2f 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -3,7 +3,7 @@ use std::rc::Rc; pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; pub use actix_http::error::PayloadError; -pub use actix_http::http; +pub use actix_http::{http, RequestHead}; use actix_http::client::Connector; use actix_http::http::{HttpTryFrom, Method, Uri}; @@ -76,6 +76,24 @@ impl Client { ClientRequest::new(method, url, self.connector.clone()) } + /// Create `ClientRequest` from `RequestHead` + /// + /// It is useful for proxy requests. This implementation + /// copies all headers and the method. + pub fn request_from(&self, url: U, head: &RequestHead) -> ClientRequest + where + Uri: HttpTryFrom, + { + let mut req = + ClientRequest::new(head.method.clone(), url, self.connector.clone()); + + for (key, value) in &head.headers { + req.head.headers.insert(key.clone(), value.clone()); + } + + req + } + pub fn get(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, diff --git a/awc/src/request.rs b/awc/src/request.rs index c944c6ca..d25ecc42 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -56,7 +56,7 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; /// } /// ``` pub struct ClientRequest { - head: RequestHead, + pub(crate) head: RequestHead, err: Option, #[cfg(feature = "cookies")] cookies: Option, diff --git a/awc/src/response.rs b/awc/src/response.rs index 03606a76..3b77eaa6 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -7,7 +7,7 @@ use futures::{Future, Poll, Stream}; use actix_http::error::PayloadError; use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE}; use actix_http::http::{HeaderMap, StatusCode, Version}; -use actix_http::{Extensions, Head, HttpMessage, Payload, PayloadStream, ResponseHead}; +use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; #[cfg(feature = "cookies")] use actix_http::error::CookieParseError; From faa3ea8e5bb0a4561b78288a6ee4b80e0f066517 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Mar 2019 09:24:55 -0700 Subject: [PATCH 1140/1635] rename BodyLength to BodySize --- actix-http/.gitignore | 14 ----- actix-http/.travis.yml | 52 ------------------ actix-http/Cargo.toml | 1 - actix-http/src/body.rs | 85 ++++++++++++++--------------- actix-http/src/client/h1proto.rs | 4 +- actix-http/src/client/h2proto.rs | 14 ++--- actix-http/src/encoding/encoder.rs | 6 +- actix-http/src/h1/client.rs | 4 +- actix-http/src/h1/codec.rs | 4 +- actix-http/src/h1/dispatcher.rs | 4 +- actix-http/src/h1/encoder.rs | 36 ++++++------ actix-http/src/h2/dispatcher.rs | 18 +++--- actix-http/src/service/senderror.rs | 8 +-- actix-http/src/ws/client/service.rs | 4 +- awc/src/lib.rs | 27 ++++++++- awc/src/test.rs | 3 +- src/lib.rs | 35 ++++++++++-- src/middleware/logger.rs | 4 +- 18 files changed, 151 insertions(+), 172 deletions(-) delete mode 100644 actix-http/.gitignore delete mode 100644 actix-http/.travis.yml diff --git a/actix-http/.gitignore b/actix-http/.gitignore deleted file mode 100644 index 42d0755d..00000000 --- a/actix-http/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -Cargo.lock -target/ -guide/build/ -/gh-pages - -*.so -*.out -*.pyc -*.pid -*.sock -*~ - -# These are backup files generated by rustfmt -**/*.rs.bk diff --git a/actix-http/.travis.yml b/actix-http/.travis.yml deleted file mode 100644 index 02fbd42c..00000000 --- a/actix-http/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -language: rust -sudo: required -dist: trusty - -cache: - cargo: true - apt: true - -matrix: - include: - - rust: stable - - rust: beta - - rust: nightly-2019-03-02 - allow_failures: - - rust: nightly-2019-03-02 - -env: - global: - - RUSTFLAGS="-C link-dead-code" - - OPENSSL_VERSION=openssl-1.0.2 - -before_install: - - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl - - sudo apt-get update -qq - - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev - -before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin - fi - -script: -- cargo clean -- cargo build --all-features -- cargo test --all-features - -# Upload docs -after_success: - - | - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --no-deps && - echo "" > target/doc/index.html && - git clone https://github.com/davisp/ghp-import.git && - ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && - echo "Uploaded documentation" - fi - - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then - taskset -c 0 cargo tarpaulin --features="ssl" --out Xml - bash <(curl -s https://codecov.io/bash) - echo "Uploaded code coverage" - fi diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 427024e2..99d80b0b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -12,7 +12,6 @@ categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] license = "MIT/Apache-2.0" -exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" workspace = ".." diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index e1399e6b..85717ba8 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -7,8 +7,8 @@ use futures::{Async, Poll, Stream}; use crate::error::Error; #[derive(Debug, PartialEq, Copy, Clone)] -/// Different type of body -pub enum BodyLength { +/// Body size hint +pub enum BodySize { None, Empty, Sized(usize), @@ -16,13 +16,13 @@ pub enum BodyLength { Stream, } -impl BodyLength { +impl BodySize { pub fn is_eof(&self) -> bool { match self { - BodyLength::None - | BodyLength::Empty - | BodyLength::Sized(0) - | BodyLength::Sized64(0) => true, + BodySize::None + | BodySize::Empty + | BodySize::Sized(0) + | BodySize::Sized64(0) => true, _ => false, } } @@ -30,14 +30,14 @@ impl BodyLength { /// Type that provides this trait can be streamed to a peer. pub trait MessageBody { - fn length(&self) -> BodyLength; + fn length(&self) -> BodySize; fn poll_next(&mut self) -> Poll, Error>; } impl MessageBody for () { - fn length(&self) -> BodyLength { - BodyLength::Empty + fn length(&self) -> BodySize { + BodySize::Empty } fn poll_next(&mut self) -> Poll, Error> { @@ -46,7 +46,7 @@ impl MessageBody for () { } impl MessageBody for Box { - fn length(&self) -> BodyLength { + fn length(&self) -> BodySize { self.as_ref().length() } @@ -86,7 +86,7 @@ impl ResponseBody { } impl MessageBody for ResponseBody { - fn length(&self) -> BodyLength { + fn length(&self) -> BodySize { match self { ResponseBody::Body(ref body) => body.length(), ResponseBody::Other(ref body) => body.length(), @@ -135,11 +135,11 @@ impl Body { } impl MessageBody for Body { - fn length(&self) -> BodyLength { + fn length(&self) -> BodySize { match self { - Body::None => BodyLength::None, - Body::Empty => BodyLength::Empty, - Body::Bytes(ref bin) => BodyLength::Sized(bin.len()), + Body::None => BodySize::None, + Body::Empty => BodySize::Empty, + Body::Bytes(ref bin) => BodySize::Sized(bin.len()), Body::Message(ref body) => body.length(), } } @@ -235,8 +235,8 @@ impl From for Body { } impl MessageBody for Bytes { - fn length(&self) -> BodyLength { - BodyLength::Sized(self.len()) + fn length(&self) -> BodySize { + BodySize::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -249,8 +249,8 @@ impl MessageBody for Bytes { } impl MessageBody for BytesMut { - fn length(&self) -> BodyLength { - BodyLength::Sized(self.len()) + fn length(&self) -> BodySize { + BodySize::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -265,8 +265,8 @@ impl MessageBody for BytesMut { } impl MessageBody for &'static str { - fn length(&self) -> BodyLength { - BodyLength::Sized(self.len()) + fn length(&self) -> BodySize { + BodySize::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -281,8 +281,8 @@ impl MessageBody for &'static str { } impl MessageBody for &'static [u8] { - fn length(&self) -> BodyLength { - BodyLength::Sized(self.len()) + fn length(&self) -> BodySize { + BodySize::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -297,8 +297,8 @@ impl MessageBody for &'static [u8] { } impl MessageBody for Vec { - fn length(&self) -> BodyLength { - BodyLength::Sized(self.len()) + fn length(&self) -> BodySize { + BodySize::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -314,8 +314,8 @@ impl MessageBody for Vec { } impl MessageBody for String { - fn length(&self) -> BodyLength { - BodyLength::Sized(self.len()) + fn length(&self) -> BodySize { + BodySize::Sized(self.len()) } fn poll_next(&mut self) -> Poll, Error> { @@ -354,8 +354,8 @@ where S: Stream, E: Into, { - fn length(&self) -> BodyLength { - BodyLength::Stream + fn length(&self) -> BodySize { + BodySize::Stream } fn poll_next(&mut self) -> Poll, Error> { @@ -383,8 +383,8 @@ impl MessageBody for SizedStream where S: Stream, { - fn length(&self) -> BodyLength { - BodyLength::Sized(self.size) + fn length(&self) -> BodySize { + BodySize::Sized(self.size) } fn poll_next(&mut self) -> Poll, Error> { @@ -416,50 +416,47 @@ mod tests { #[test] fn test_static_str() { - assert_eq!(Body::from("").length(), BodyLength::Sized(0)); - assert_eq!(Body::from("test").length(), BodyLength::Sized(4)); + assert_eq!(Body::from("").length(), BodySize::Sized(0)); + assert_eq!(Body::from("test").length(), BodySize::Sized(4)); assert_eq!(Body::from("test").get_ref(), b"test"); } #[test] fn test_static_bytes() { - assert_eq!(Body::from(b"test".as_ref()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b"test".as_ref()).length(), BodySize::Sized(4)); assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!( Body::from_slice(b"test".as_ref()).length(), - BodyLength::Sized(4) + BodySize::Sized(4) ); assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); } #[test] fn test_vec() { - assert_eq!(Body::from(Vec::from("test")).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(Vec::from("test")).length(), BodySize::Sized(4)); assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); } #[test] fn test_bytes() { - assert_eq!( - Body::from(Bytes::from("test")).length(), - BodyLength::Sized(4) - ); + assert_eq!(Body::from(Bytes::from("test")).length(), BodySize::Sized(4)); assert_eq!(Body::from(Bytes::from("test")).get_ref(), b"test"); } #[test] fn test_string() { let b = "test".to_owned(); - assert_eq!(Body::from(b.clone()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - assert_eq!(Body::from(&b).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(&b).length(), BodySize::Sized(4)); assert_eq!(Body::from(&b).get_ref(), b"test"); } #[test] fn test_bytes_mut() { let b = BytesMut::from("test"); - assert_eq!(Body::from(b.clone()).length(), BodyLength::Sized(4)); + assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4)); assert_eq!(Body::from(b).get_ref(), b"test"); } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 2e29484f..b7b8d4a0 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -13,7 +13,7 @@ use crate::payload::{Payload, PayloadStream}; use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; -use crate::body::{BodyLength, MessageBody}; +use crate::body::{BodySize, MessageBody}; pub(crate) fn send_request( io: T, @@ -40,7 +40,7 @@ where .from_err() // send request body .and_then(move |framed| match body.length() { - BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => { + BodySize::None | BodySize::Empty | BodySize::Sized(0) => { Either::A(ok(framed)) } _ => Either::B(SendBody::new(body, framed)), diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 9ad72262..d45716ab 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -8,7 +8,7 @@ use h2::{client::SendRequest, SendStream}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; -use crate::body::{BodyLength, MessageBody}; +use crate::body::{BodySize, MessageBody}; use crate::message::{RequestHead, ResponseHead}; use crate::payload::Payload; @@ -31,7 +31,7 @@ where let head_req = head.method == Method::HEAD; let length = body.length(); let eof = match length { - BodyLength::None | BodyLength::Empty | BodyLength::Sized(0) => true, + BodySize::None | BodySize::Empty | BodySize::Sized(0) => true, _ => false, }; @@ -48,19 +48,19 @@ where // Content length let _ = match length { - BodyLength::None => None, - BodyLength::Stream => { + BodySize::None => None, + BodySize::Stream => { skip_len = false; None } - BodyLength::Empty => req + BodySize::Empty => req .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodyLength::Sized(len) => req.headers_mut().insert( + BodySize::Sized(len) => req.headers_mut().insert( CONTENT_LENGTH, HeaderValue::try_from(format!("{}", len)).unwrap(), ), - BodyLength::Sized64(len) => req.headers_mut().insert( + BodySize::Sized64(len) => req.headers_mut().insert( CONTENT_LENGTH, HeaderValue::try_from(format!("{}", len)).unwrap(), ), diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 0778cc26..af861b9d 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -9,7 +9,7 @@ use brotli2::write::BrotliEncoder; #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] use flate2::write::{GzEncoder, ZlibEncoder}; -use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; +use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; use crate::http::{HeaderValue, HttpTryFrom, StatusCode}; use crate::{Error, Head, ResponseHead}; @@ -89,14 +89,14 @@ enum EncoderBody { } impl MessageBody for Encoder { - fn length(&self) -> BodyLength { + fn length(&self) -> BodySize { if self.encoder.is_none() { match self.body { EncoderBody::Body(ref b) => b.length(), EncoderBody::Other(ref b) => b.length(), } } else { - BodyLength::Stream + BodySize::Stream } } diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index b3a5a5d9..fbdc8bde 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -12,7 +12,7 @@ use http::{Method, Version}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; use super::{Message, MessageType}; -use crate::body::BodyLength; +use crate::body::BodySize; use crate::config::ServiceConfig; use crate::error::{ParseError, PayloadError}; use crate::helpers; @@ -179,7 +179,7 @@ impl Decoder for ClientPayloadCodec { } impl Encoder for ClientCodec { - type Item = Message<(RequestHead, BodyLength)>; + type Item = Message<(RequestHead, BodySize)>; type Error = io::Error; fn encode( diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index c66364c0..9bb41709 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -11,7 +11,7 @@ use http::{Method, StatusCode, Version}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; use super::{decoder, encoder}; use super::{Message, MessageType}; -use crate::body::BodyLength; +use crate::body::BodySize; use crate::config::ServiceConfig; use crate::error::ParseError; use crate::helpers; @@ -140,7 +140,7 @@ impl Decoder for Codec { } impl Encoder for Codec { - type Item = Message<(Response<()>, BodyLength)>; + type Item = Message<(Response<()>, BodySize)>; type Error = io::Error; fn encode( diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 09a17fcf..34204bf5 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -11,7 +11,7 @@ use futures::{Async, Future, Poll, Sink, Stream}; use log::{debug, error, trace}; use tokio_timer::Delay; -use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; +use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::DispatchError; use crate::error::{ParseError, PayloadError}; @@ -208,7 +208,7 @@ where self.flags .set(Flags::KEEPALIVE, self.framed.get_codec().keepalive()); match body.length() { - BodyLength::None | BodyLength::Empty => Ok(State::None), + BodySize::None | BodySize::Empty => Ok(State::None), _ => Ok(State::SendPayload(body)), } } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 712d123e..dbf6d440 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -11,7 +11,7 @@ use http::header::{ }; use http::{HeaderMap, Method, StatusCode, Version}; -use crate::body::BodyLength; +use crate::body::BodySize; use crate::config::ServiceConfig; use crate::header::ContentEncoding; use crate::helpers; @@ -23,7 +23,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; #[derive(Debug)] pub(crate) struct MessageEncoder { - pub length: BodyLength, + pub length: BodySize, pub te: TransferEncoding, _t: PhantomData, } @@ -31,7 +31,7 @@ pub(crate) struct MessageEncoder { impl Default for MessageEncoder { fn default() -> Self { MessageEncoder { - length: BodyLength::None, + length: BodySize::None, te: TransferEncoding::empty(), _t: PhantomData, } @@ -53,28 +53,28 @@ pub(crate) trait MessageType: Sized { &mut self, dst: &mut BytesMut, version: Version, - mut length: BodyLength, + mut length: BodySize, ctype: ConnectionType, config: &ServiceConfig, ) -> io::Result<()> { let chunked = self.chunked(); - let mut skip_len = length != BodyLength::Stream; + let mut skip_len = length != BodySize::Stream; // Content length if let Some(status) = self.status() { match status { StatusCode::NO_CONTENT | StatusCode::CONTINUE - | StatusCode::PROCESSING => length = BodyLength::None, + | StatusCode::PROCESSING => length = BodySize::None, StatusCode::SWITCHING_PROTOCOLS => { skip_len = true; - length = BodyLength::Stream; + length = BodySize::Stream; } _ => (), } } match length { - BodyLength::Stream => { + BodySize::Stream => { if chunked { dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") } else { @@ -82,16 +82,16 @@ pub(crate) trait MessageType: Sized { dst.extend_from_slice(b"\r\n"); } } - BodyLength::Empty => { + BodySize::Empty => { dst.extend_from_slice(b"\r\ncontent-length: 0\r\n"); } - BodyLength::Sized(len) => helpers::write_content_length(len, dst), - BodyLength::Sized64(len) => { + BodySize::Sized(len) => helpers::write_content_length(len, dst), + BodySize::Sized64(len) => { dst.extend_from_slice(b"\r\ncontent-length: "); write!(dst.writer(), "{}", len)?; dst.extend_from_slice(b"\r\n"); } - BodyLength::None => dst.extend_from_slice(b"\r\n"), + BodySize::None => dst.extend_from_slice(b"\r\n"), } // Connection @@ -243,24 +243,24 @@ impl MessageEncoder { head: bool, stream: bool, version: Version, - length: BodyLength, + length: BodySize, ctype: ConnectionType, config: &ServiceConfig, ) -> io::Result<()> { // transfer encoding if !head { self.te = match length { - BodyLength::Empty => TransferEncoding::empty(), - BodyLength::Sized(len) => TransferEncoding::length(len as u64), - BodyLength::Sized64(len) => TransferEncoding::length(len), - BodyLength::Stream => { + BodySize::Empty => TransferEncoding::empty(), + BodySize::Sized(len) => TransferEncoding::length(len as u64), + BodySize::Sized64(len) => TransferEncoding::length(len), + BodySize::Stream => { if message.chunked() && !stream { TransferEncoding::chunked() } else { TransferEncoding::eof() } } - BodyLength::None => TransferEncoding::empty(), + BodySize::None => TransferEncoding::empty(), }; } else { self.te = TransferEncoding::empty(); diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index ea63dc2b..9b43be66 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -18,7 +18,7 @@ use http::HttpTryFrom; use log::{debug, error, trace}; use tokio_timer::Delay; -use crate::body::{Body, BodyLength, MessageBody, ResponseBody}; +use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::message::ResponseHead; @@ -151,10 +151,10 @@ where fn prepare_response( &self, head: &ResponseHead, - length: &mut BodyLength, + length: &mut BodySize, ) -> http::Response<()> { let mut has_date = false; - let mut skip_len = length != &BodyLength::Stream; + let mut skip_len = length != &BodySize::Stream; let mut res = http::Response::new(()); *res.status_mut() = head.status; @@ -164,23 +164,23 @@ where match head.status { http::StatusCode::NO_CONTENT | http::StatusCode::CONTINUE - | http::StatusCode::PROCESSING => *length = BodyLength::None, + | http::StatusCode::PROCESSING => *length = BodySize::None, http::StatusCode::SWITCHING_PROTOCOLS => { skip_len = true; - *length = BodyLength::Stream; + *length = BodySize::Stream; } _ => (), } let _ = match length { - BodyLength::None | BodyLength::Stream => None, - BodyLength::Empty => res + BodySize::None | BodySize::Stream => None, + BodySize::Empty => res .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodyLength::Sized(len) => res.headers_mut().insert( + BodySize::Sized(len) => res.headers_mut().insert( CONTENT_LENGTH, HeaderValue::try_from(format!("{}", len)).unwrap(), ), - BodyLength::Sized64(len) => res.headers_mut().insert( + BodySize::Sized64(len) => res.headers_mut().insert( CONTENT_LENGTH, HeaderValue::try_from(format!("{}", len)).unwrap(), ), diff --git a/actix-http/src/service/senderror.rs b/actix-http/src/service/senderror.rs index 44d36259..03fe5976 100644 --- a/actix-http/src/service/senderror.rs +++ b/actix-http/src/service/senderror.rs @@ -5,7 +5,7 @@ use actix_service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Sink}; -use crate::body::{BodyLength, MessageBody, ResponseBody}; +use crate::body::{BodySize, MessageBody, ResponseBody}; use crate::error::{Error, ResponseError}; use crate::h1::{Codec, Message}; use crate::response::Response; @@ -61,7 +61,7 @@ where let (res, _body) = res.replace_body(()); Either::B(SendErrorFut { framed: Some(framed), - res: Some((res, BodyLength::Empty).into()), + res: Some((res, BodySize::Empty).into()), err: Some(e), _t: PhantomData, }) @@ -71,7 +71,7 @@ where } pub struct SendErrorFut { - res: Option, BodyLength)>>, + res: Option, BodySize)>>, framed: Option>, err: Option, _t: PhantomData, @@ -172,7 +172,7 @@ where } pub struct SendResponseFut { - res: Option, BodyLength)>>, + res: Option, BodySize)>>, body: Option>, framed: Option>, } diff --git a/actix-http/src/ws/client/service.rs b/actix-http/src/ws/client/service.rs index 8a0840f9..cb3fb6f3 100644 --- a/actix-http/src/ws/client/service.rs +++ b/actix-http/src/ws/client/service.rs @@ -13,7 +13,7 @@ use log::trace; use rand; use sha1::Sha1; -use crate::body::BodyLength; +use crate::body::BodySize; use crate::h1; use crate::message::{ConnectionType, Head, ResponseHead}; use crate::ws::Codec; @@ -149,7 +149,7 @@ where // h1 protocol let framed = Framed::new(io, h1::ClientCodec::default()); framed - .send((request, BodyLength::None).into()) + .send((request, BodySize::None).into()) .map_err(ClientError::from) .and_then(|framed| { framed diff --git a/awc/src/lib.rs b/awc/src/lib.rs index ac7dcf2f..3bad8caa 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,12 +1,35 @@ +//! An HTTP Client +//! +//! ```rust +//! # use futures::future::{Future, lazy}; +//! use actix_rt::System; +//! use awc::Client; +//! +//! fn main() { +//! System::new("test").block_on(lazy(|| { +//! let mut client = Client::default(); +//! +//! client.get("http://www.rust-lang.org") // <- Create request builder +//! .header("User-Agent", "Actix-web") +//! .send() // <- Send http request +//! .map_err(|_| ()) +//! .and_then(|response| { // <- server http response +//! println!("Response: {:?}", response); +//! Ok(()) +//! }) +//! })); +//! } +//! ``` use std::cell::RefCell; use std::rc::Rc; pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; pub use actix_http::error::PayloadError; -pub use actix_http::{http, RequestHead}; +pub use actix_http::http; use actix_http::client::Connector; use actix_http::http::{HttpTryFrom, Method, Uri}; +use actix_http::RequestHead; mod builder; mod connect; @@ -20,7 +43,7 @@ pub use self::response::ClientResponse; use self::connect::{Connect, ConnectorWrapper}; -/// An HTTP Client Request +/// An HTTP Client /// /// ```rust /// # use futures::future::{Future, lazy}; diff --git a/awc/src/test.rs b/awc/src/test.rs index 165694d8..395e6290 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,5 +1,4 @@ -//! Test Various helpers for Actix applications to use during testing. - +//! Test helpers for actix http client to use during testing. use actix_http::http::header::{Header, IntoHeaderValue}; use actix_http::http::{HeaderName, HttpTryFrom, Version}; use actix_http::{h1, Payload, ResponseHead}; diff --git a/src/lib.rs b/src/lib.rs index a21032db..54709b47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,9 +106,6 @@ extern crate actix_web_codegen; #[doc(hidden)] pub use actix_web_codegen::*; -#[cfg(feature = "client")] -pub use awc as client; - // re-export for convenience pub use actix_http::Response as HttpResponse; pub use actix_http::{http, Error, HttpMessage, ResponseError, Result}; @@ -145,7 +142,7 @@ pub mod dev { pub use crate::types::payload::HttpMessageBody; pub use crate::types::readlines::Readlines; - pub use actix_http::body::{Body, BodyLength, MessageBody, ResponseBody}; + pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody}; pub use actix_http::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ Extensions, Head, Payload, PayloadStream, RequestHead, ResponseHead, @@ -371,3 +368,33 @@ pub mod web { fn_transform(f) } } + +#[cfg(feature = "client")] +pub mod client { + //! An HTTP Client + //! + //! ```rust + //! # use futures::future::{Future, lazy}; + //! use actix_rt::System; + //! use actix_web::client::Client; + //! + //! fn main() { + //! System::new("test").block_on(lazy(|| { + //! let mut client = Client::default(); + //! + //! client.get("http://www.rust-lang.org") // <- Create request builder + //! .header("User-Agent", "Actix-web") + //! .send() // <- Send http request + //! .map_err(|_| ()) + //! .and_then(|response| { // <- server http response + //! println!("Response: {:?}", response); + //! Ok(()) + //! }) + //! })); + //! } + //! ``` + pub use awc::{ + test, Client, ClientBuilder, ClientRequest, ClientResponse, ConnectError, + InvalidUrl, PayloadError, SendRequestError, + }; +} diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 42f344f0..cd52048f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -12,7 +12,7 @@ use futures::{Async, Future, Poll}; use regex::Regex; use time; -use crate::dev::{BodyLength, MessageBody, ResponseBody}; +use crate::dev::{BodySize, MessageBody, ResponseBody}; use crate::error::{Error, Result}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::{HttpMessage, HttpResponse}; @@ -238,7 +238,7 @@ impl Drop for StreamLog { } impl MessageBody for StreamLog { - fn length(&self) -> BodyLength { + fn length(&self) -> BodySize { self.body.length() } From fb9c94c3e0b490a20f90d4893a53369cb1989971 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Mar 2019 09:31:07 -0700 Subject: [PATCH 1141/1635] remove Backtrace from error --- actix-http/Cargo.toml | 1 - actix-http/src/error.rs | 49 +---------------------------------------- 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 99d80b0b..809d4d67 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -55,7 +55,6 @@ actix-utils = "0.3.4" actix-server-config = "0.1.0" base64 = "0.10" -backtrace = "0.3" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 820071b1..23e6728d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -7,7 +7,6 @@ use std::{fmt, io, result}; // use actix::MailboxError; use actix_utils::timeout::TimeoutError; -use backtrace::Backtrace; #[cfg(feature = "cookies")] use cookie; use derive_more::{Display, From}; @@ -47,7 +46,6 @@ pub type Result = result::Result; /// `ResponseError` reference from it. pub struct Error { cause: Box, - backtrace: Option, } impl Error { @@ -56,18 +54,6 @@ impl Error { self.cause.as_ref() } - /// Returns a reference to the Backtrace carried by this error, if it - /// carries one. - /// - /// This uses the same `Backtrace` type that `failure` uses. - pub fn backtrace(&self) -> &Backtrace { - if let Some(bt) = self.cause.backtrace() { - bt - } else { - self.backtrace.as_ref().unwrap() - } - } - /// Converts error to a response instance and set error message as response body pub fn response_with_message(self) -> Response { let message = format!("{}", self); @@ -84,11 +70,6 @@ pub trait ResponseError: fmt::Debug + fmt::Display { fn error_response(&self) -> Response { Response::new(StatusCode::INTERNAL_SERVER_ERROR) } - - /// Response - fn backtrace(&self) -> Option<&Backtrace> { - None - } } impl fmt::Display for Error { @@ -99,16 +80,7 @@ impl fmt::Display for Error { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(bt) = self.cause.backtrace() { - write!(f, "{:?}\n\n{:?}", &self.cause, bt) - } else { - write!( - f, - "{:?}\n\n{:?}", - &self.cause, - self.backtrace.as_ref().unwrap() - ) - } + write!(f, "{:?}\n", &self.cause) } } @@ -122,14 +94,8 @@ impl From for Response { /// `Error` for any error that implements `ResponseError` impl From for Error { fn from(err: T) -> Error { - let backtrace = if err.backtrace().is_none() { - Some(Backtrace::new()) - } else { - None - }; Error { cause: Box::new(err), - backtrace, } } } @@ -412,7 +378,6 @@ impl ResponseError for ContentTypeError { pub struct InternalError { cause: T, status: InternalErrorType, - backtrace: Backtrace, } enum InternalErrorType { @@ -426,7 +391,6 @@ impl InternalError { InternalError { cause, status: InternalErrorType::Status(status), - backtrace: Backtrace::new(), } } @@ -435,7 +399,6 @@ impl InternalError { InternalError { cause, status: InternalErrorType::Response(RefCell::new(Some(response))), - backtrace: Backtrace::new(), } } } @@ -462,10 +425,6 @@ impl ResponseError for InternalError where T: fmt::Debug + fmt::Display + 'static, { - fn backtrace(&self) -> Option<&Backtrace> { - Some(&self.backtrace) - } - fn error_response(&self) -> Response { match self.status { InternalErrorType::Status(st) => Response::new(st), @@ -922,12 +881,6 @@ mod tests { assert_eq!(format!("{}", e.as_response_error()), "IO error: other"); } - #[test] - fn test_backtrace() { - let e = ErrorBadRequest("err"); - let _ = e.backtrace(); - } - #[test] fn test_error_cause() { let orig = io::Error::new(io::ErrorKind::Other, "other"); From 3edc515bacccb95ce47ea46ab8ad265d248b60c4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Mar 2019 10:38:01 -0700 Subject: [PATCH 1142/1635] refactor RequestHead/ResponseHead --- actix-http/src/encoding/encoder.rs | 6 +- actix-http/src/h1/client.rs | 2 +- actix-http/src/h1/codec.rs | 2 +- actix-http/src/h1/decoder.rs | 27 +-- actix-http/src/h1/encoder.rs | 18 +- actix-http/src/lib.rs | 2 +- actix-http/src/message.rs | 250 +++++++++++++++++----------- actix-http/src/response.rs | 6 +- actix-http/src/ws/client/service.rs | 2 +- actix-web-actors/src/ws.rs | 2 +- awc/src/request.rs | 2 +- src/lib.rs | 2 +- src/middleware/cors.rs | 2 +- 13 files changed, 191 insertions(+), 132 deletions(-) diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index af861b9d..fcac3a42 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -12,7 +12,7 @@ use flate2::write::{GzEncoder, ZlibEncoder}; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; use crate::http::{HeaderValue, HttpTryFrom, StatusCode}; -use crate::{Error, Head, ResponseHead}; +use crate::{Error, ResponseHead}; use super::Writer; @@ -56,7 +56,7 @@ impl Encoder { }) } else { update_head(encoding, head); - head.no_chunking = false; + head.no_chunking(false); ResponseBody::Body(Encoder { body: EncoderBody::Other(stream), encoder: ContentEncoder::encoder(encoding), @@ -72,7 +72,7 @@ impl Encoder { }) } else { update_head(encoding, head); - head.no_chunking = false; + head.no_chunking(false); ResponseBody::Body(Encoder { body: EncoderBody::Body(stream), encoder: ContentEncoder::encoder(encoding), diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index fbdc8bde..6a50c027 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -129,7 +129,7 @@ impl Decoder for ClientCodec { debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); if let Some((req, payload)) = self.inner.decoder.decode(src)? { - if let Some(ctype) = req.ctype { + if let Some(ctype) = req.ctype() { // do not use peer's keep-alive self.inner.ctype = if ctype == ConnectionType::KeepAlive { self.inner.ctype diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 9bb41709..ceb1027e 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -154,7 +154,7 @@ impl Encoder for Codec { res.head_mut().version = self.version; // connection status - self.ctype = if let Some(ct) = res.head().ctype { + self.ctype = if let Some(ct) = res.head().ctype() { if ct == ConnectionType::KeepAlive { self.ctype } else { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index a1b221c0..9b97713f 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -158,7 +158,9 @@ pub(crate) trait MessageType: Sized { impl MessageType for Request { fn set_connection_type(&mut self, ctype: Option) { - self.head_mut().ctype = ctype; + if let Some(ctype) = ctype { + self.head_mut().set_connection_type(ctype); + } } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -228,7 +230,9 @@ impl MessageType for Request { impl MessageType for ResponseHead { fn set_connection_type(&mut self, ctype: Option) { - self.ctype = ctype; + if let Some(ctype) = ctype { + ResponseHead::set_connection_type(self, ctype); + } } fn headers_mut(&mut self) -> &mut HeaderMap { @@ -814,7 +818,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.head().ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().connection_type(), ConnectionType::Close); let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ @@ -822,7 +826,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.head().ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] @@ -834,7 +838,7 @@ mod tests { let req = parse_ready!(&mut buf); - assert_eq!(req.head().ctype, Some(ConnectionType::Close)); + assert_eq!(req.head().connection_type(), ConnectionType::Close); } #[test] @@ -845,7 +849,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); let mut buf = BytesMut::from( "GET /test HTTP/1.0\r\n\ @@ -853,7 +857,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] @@ -864,7 +868,7 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.head().ctype, Some(ConnectionType::KeepAlive)); + assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } #[test] @@ -886,7 +890,6 @@ mod tests { ); let req = parse_ready!(&mut buf); - assert_eq!(req.head().ctype, None); assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); } @@ -900,7 +903,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); let mut buf = BytesMut::from( "GET /test HTTP/1.1\r\n\ @@ -910,7 +913,7 @@ mod tests { let req = parse_ready!(&mut buf); assert!(req.upgrade()); - assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); } #[test] @@ -1008,7 +1011,7 @@ mod tests { ); let mut reader = MessageDecoder::::default(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(req.head().ctype, Some(ConnectionType::Upgrade)); + assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); assert!(req.upgrade()); assert!(pl.is_unhandled()); } diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index dbf6d440..382ebe5f 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -15,7 +15,7 @@ use crate::body::BodySize; use crate::config::ServiceConfig; use crate::header::ContentEncoding; use crate::helpers; -use crate::message::{ConnectionType, RequestHead, ResponseHead}; +use crate::message::{ConnectionType, Head, RequestHead, ResponseHead}; use crate::request::Request; use crate::response::Response; @@ -41,7 +41,7 @@ impl Default for MessageEncoder { pub(crate) trait MessageType: Sized { fn status(&self) -> Option; - fn connection_type(&self) -> Option; + // fn connection_type(&self) -> Option; fn headers(&self) -> &HeaderMap; @@ -168,12 +168,12 @@ impl MessageType for Response<()> { } fn chunked(&self) -> bool { - !self.head().no_chunking + self.head().chunked() } - fn connection_type(&self) -> Option { - self.head().ctype - } + //fn connection_type(&self) -> Option { + // self.head().ctype + //} fn headers(&self) -> &HeaderMap { &self.head().headers @@ -196,12 +196,8 @@ impl MessageType for RequestHead { None } - fn connection_type(&self) -> Option { - self.ctype - } - fn chunked(&self) -> bool { - !self.no_chunking + self.chunked() } fn headers(&self) -> &HeaderMap { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index edc06c2a..565fe445 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -35,7 +35,7 @@ pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; -pub use self::message::{Head, Message, RequestHead, ResponseHead}; +pub use self::message::{Message, RequestHead, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::{Response, ResponseBuilder}; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 4e46093f..a1e9e3c6 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -2,6 +2,8 @@ use std::cell::{Ref, RefCell, RefMut}; use std::collections::VecDeque; use std::rc::Rc; +use bitflags::bitflags; + use crate::extensions::Extensions; use crate::http::{header, HeaderMap, Method, StatusCode, Uri, Version}; @@ -16,39 +18,22 @@ pub enum ConnectionType { Upgrade, } +bitflags! { + pub(crate) struct Flags: u8 { + const CLOSE = 0b0000_0001; + const KEEP_ALIVE = 0b0000_0010; + const UPGRADE = 0b0000_0100; + const NO_CHUNKING = 0b0000_1000; + const ENC_BR = 0b0001_0000; + const ENC_DEFLATE = 0b0010_0000; + const ENC_GZIP = 0b0100_0000; + } +} + #[doc(hidden)] pub trait Head: Default + 'static { fn clear(&mut self); - /// Read the message headers. - fn headers(&self) -> &HeaderMap; - - /// Mutable reference to the message headers. - fn headers_mut(&mut self) -> &mut HeaderMap; - - /// Connection type - fn connection_type(&self) -> ConnectionType; - - /// Set connection type of the message - fn set_connection_type(&mut self, ctype: ConnectionType); - - fn upgrade(&self) -> bool { - if let Some(hdr) = self.headers().get(header::CONNECTION) { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("upgrade") - } else { - false - } - } else { - false - } - } - - /// Check if keep-alive is enabled - fn keep_alive(&self) -> bool { - self.connection_type() == ConnectionType::KeepAlive - } - fn pool() -> &'static MessagePool; } @@ -58,9 +43,8 @@ pub struct RequestHead { pub method: Method, pub version: Version, pub headers: HeaderMap, - pub ctype: Option, - pub no_chunking: bool, pub extensions: RefCell, + flags: Flags, } impl Default for RequestHead { @@ -70,8 +54,7 @@ impl Default for RequestHead { method: Method::default(), version: Version::HTTP_11, headers: HeaderMap::with_capacity(16), - ctype: None, - no_chunking: false, + flags: Flags::empty(), extensions: RefCell::new(Extensions::new()), } } @@ -79,45 +62,11 @@ impl Default for RequestHead { impl Head for RequestHead { fn clear(&mut self) { - self.ctype = None; + self.flags = Flags::empty(); self.headers.clear(); self.extensions.borrow_mut().clear(); } - fn headers(&self) -> &HeaderMap { - &self.headers - } - - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - fn set_connection_type(&mut self, ctype: ConnectionType) { - self.ctype = Some(ctype) - } - - fn connection_type(&self) -> ConnectionType { - if let Some(ct) = self.ctype { - ct - } else if self.version < Version::HTTP_11 { - ConnectionType::Close - } else { - ConnectionType::KeepAlive - } - } - - fn upgrade(&self) -> bool { - if let Some(hdr) = self.headers().get(header::CONNECTION) { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("upgrade") - } else { - false - } - } else { - false - } - } - fn pool() -> &'static MessagePool { REQUEST_POOL.with(|p| *p) } @@ -135,6 +84,70 @@ impl RequestHead { pub fn extensions_mut(&self) -> RefMut { self.extensions.borrow_mut() } + + /// Read the message headers. + pub fn headers(&self) -> &HeaderMap { + &self.headers + } + + /// Mutable reference to the message headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.headers + } + + #[inline] + /// Set connection type of the message + pub fn set_connection_type(&mut self, ctype: ConnectionType) { + match ctype { + ConnectionType::Close => self.flags.insert(Flags::CLOSE), + ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), + ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), + } + } + + #[inline] + /// Connection type + pub fn connection_type(&self) -> ConnectionType { + if self.flags.contains(Flags::CLOSE) { + ConnectionType::Close + } else if self.flags.contains(Flags::KEEP_ALIVE) { + ConnectionType::KeepAlive + } else if self.flags.contains(Flags::UPGRADE) { + ConnectionType::Upgrade + } else if self.version < Version::HTTP_11 { + ConnectionType::Close + } else { + ConnectionType::KeepAlive + } + } + + /// Connection upgrade status + pub fn upgrade(&self) -> bool { + if let Some(hdr) = self.headers().get(header::CONNECTION) { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("upgrade") + } else { + false + } + } else { + false + } + } + + #[inline] + /// Get response body chunking state + pub fn chunked(&self) -> bool { + !self.flags.contains(Flags::NO_CHUNKING) + } + + #[inline] + pub fn no_chunking(&mut self, val: bool) { + if val { + self.flags.insert(Flags::NO_CHUNKING); + } else { + self.flags.remove(Flags::NO_CHUNKING); + } + } } #[derive(Debug)] @@ -143,9 +156,8 @@ pub struct ResponseHead { pub status: StatusCode, pub headers: HeaderMap, pub reason: Option<&'static str>, - pub no_chunking: bool, - pub(crate) ctype: Option, pub(crate) extensions: RefCell, + flags: Flags, } impl Default for ResponseHead { @@ -155,13 +167,24 @@ impl Default for ResponseHead { status: StatusCode::OK, headers: HeaderMap::with_capacity(16), reason: None, - no_chunking: false, - ctype: None, + flags: Flags::empty(), extensions: RefCell::new(Extensions::new()), } } } +impl Head for ResponseHead { + fn clear(&mut self) { + self.reason = None; + self.flags = Flags::empty(); + self.headers.clear(); + } + + fn pool() -> &'static MessagePool { + RESPONSE_POOL.with(|p| *p) + } +} + impl ResponseHead { /// Message extensions #[inline] @@ -174,31 +197,37 @@ impl ResponseHead { pub fn extensions_mut(&self) -> RefMut { self.extensions.borrow_mut() } -} -impl Head for ResponseHead { - fn clear(&mut self) { - self.ctype = None; - self.reason = None; - self.no_chunking = false; - self.headers.clear(); - } - - fn headers(&self) -> &HeaderMap { + #[inline] + /// Read the message headers. + pub fn headers(&self) -> &HeaderMap { &self.headers } - fn headers_mut(&mut self) -> &mut HeaderMap { + #[inline] + /// Mutable reference to the message headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.headers } - fn set_connection_type(&mut self, ctype: ConnectionType) { - self.ctype = Some(ctype) + #[inline] + /// Set connection type of the message + pub fn set_connection_type(&mut self, ctype: ConnectionType) { + match ctype { + ConnectionType::Close => self.flags.insert(Flags::CLOSE), + ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), + ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), + } } - fn connection_type(&self) -> ConnectionType { - if let Some(ct) = self.ctype { - ct + #[inline] + pub fn connection_type(&self) -> ConnectionType { + if self.flags.contains(Flags::CLOSE) { + ConnectionType::Close + } else if self.flags.contains(Flags::KEEP_ALIVE) { + ConnectionType::KeepAlive + } else if self.flags.contains(Flags::UPGRADE) { + ConnectionType::Upgrade } else if self.version < Version::HTTP_11 { ConnectionType::Close } else { @@ -206,16 +235,18 @@ impl Head for ResponseHead { } } - fn upgrade(&self) -> bool { + #[inline] + /// Check if keep-alive is enabled + pub fn keep_alive(&self) -> bool { + self.connection_type() == ConnectionType::KeepAlive + } + + #[inline] + /// Check upgrade status of this message + pub fn upgrade(&self) -> bool { self.connection_type() == ConnectionType::Upgrade } - fn pool() -> &'static MessagePool { - RESPONSE_POOL.with(|p| *p) - } -} - -impl ResponseHead { /// Get custom reason for the response #[inline] pub fn reason(&self) -> &str { @@ -227,6 +258,35 @@ impl ResponseHead { .unwrap_or("") } } + + #[inline] + pub(crate) fn ctype(&self) -> Option { + if self.flags.contains(Flags::CLOSE) { + Some(ConnectionType::Close) + } else if self.flags.contains(Flags::KEEP_ALIVE) { + Some(ConnectionType::KeepAlive) + } else if self.flags.contains(Flags::UPGRADE) { + Some(ConnectionType::Upgrade) + } else { + None + } + } + + #[inline] + /// Get response body chunking state + pub fn chunked(&self) -> bool { + !self.flags.contains(Flags::NO_CHUNKING) + } + + #[inline] + /// Set no chunking for payload + pub fn no_chunking(&mut self, val: bool) { + if val { + self.flags.insert(Flags::NO_CHUNKING); + } else { + self.flags.remove(Flags::NO_CHUNKING); + } + } } pub struct Message { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 31c0010a..3b33e1f9 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -15,7 +15,7 @@ use serde_json; use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; use crate::error::Error; use crate::header::{Header, IntoHeaderValue}; -use crate::message::{ConnectionType, Head, Message, ResponseHead}; +use crate::message::{ConnectionType, Message, ResponseHead}; /// An HTTP Response pub struct Response { @@ -462,7 +462,7 @@ impl ResponseBuilder { #[inline] pub fn no_chunking(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.head, &self.err) { - parts.no_chunking = true; + parts.no_chunking(true); } self } @@ -740,7 +740,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { msg.status = head.status; msg.reason = head.reason; msg.headers = head.headers.clone(); - msg.no_chunking = head.no_chunking; + msg.no_chunking(!head.chunked()); ResponseBuilder { head: Some(msg), diff --git a/actix-http/src/ws/client/service.rs b/actix-http/src/ws/client/service.rs index cb3fb6f3..bc86e516 100644 --- a/actix-http/src/ws/client/service.rs +++ b/actix-http/src/ws/client/service.rs @@ -15,7 +15,7 @@ use sha1::Sha1; use crate::body::BodySize; use crate::h1; -use crate::message::{ConnectionType, Head, ResponseHead}; +use crate::message::{ConnectionType, ResponseHead}; use crate::ws::Codec; use super::{ClientError, Connect, Protocol}; diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index cef5080c..43601188 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -17,7 +17,7 @@ pub use actix_http::ws::{ CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, }; -use actix_web::dev::{Head, HttpResponseBuilder}; +use actix_web::dev::HttpResponseBuilder; use actix_web::error::{Error, ErrorInternalServerError, PayloadError}; use actix_web::http::{header, Method, StatusCode}; use actix_web::{HttpMessage, HttpRequest, HttpResponse}; diff --git a/awc/src/request.rs b/awc/src/request.rs index d25ecc42..7beb737e 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -19,7 +19,7 @@ use actix_http::http::{ uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use actix_http::{Error, Head, Payload, RequestHead}; +use actix_http::{Error, Payload, RequestHead}; use crate::response::ClientResponse; use crate::{Connect, PayloadError}; diff --git a/src/lib.rs b/src/lib.rs index 54709b47..5b0ce784 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ pub mod dev { pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody}; pub use actix_http::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ - Extensions, Head, Payload, PayloadStream, RequestHead, ResponseHead, + Extensions, Payload, PayloadStream, RequestHead, ResponseHead, }; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index b6acf429..2ece543d 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -46,7 +46,7 @@ use derive_more::Display; use futures::future::{ok, Either, Future, FutureResult}; use futures::Poll; -use crate::dev::{Head, RequestHead}; +use crate::dev::RequestHead; use crate::error::{ResponseError, Result}; use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri}; From e254fe4f9c37da954c4bf544ca30207415dbd426 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Mar 2019 11:29:31 -0700 Subject: [PATCH 1143/1635] allow to override response body encoding --- actix-files/src/named.rs | 12 ++++---- actix-http/src/message.rs | 3 -- actix-http/src/response.rs | 16 ++++++++++ examples/basic.rs | 2 +- src/middleware/compress.rs | 40 ++++++++++++++++++++++++- src/middleware/decompress.rs | 16 ++++++++++ src/middleware/mod.rs | 9 +++--- tests/test_server.rs | 58 ++++++++++++++++++++++++++---------- 8 files changed, 125 insertions(+), 31 deletions(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 7bc37054..842a0e5e 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -15,6 +15,7 @@ use actix_web::http::header::{ self, ContentDisposition, DispositionParam, DispositionType, }; use actix_web::http::{ContentEncoding, Method, StatusCode}; +use actix_web::middleware::encoding::BodyEncoding; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use crate::range::HttpRange; @@ -360,10 +361,10 @@ impl Responder for NamedFile { header::CONTENT_DISPOSITION, self.content_disposition.to_string(), ); - // TODO blocking by compressing - // if let Some(current_encoding) = self.encoding { - // resp.content_encoding(current_encoding); - // } + // default compressing + if let Some(current_encoding) = self.encoding { + resp.encoding(current_encoding); + } resp.if_some(last_modified, |lm, resp| { resp.set(header::LastModified(lm)); @@ -383,8 +384,7 @@ impl Responder for NamedFile { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { length = rangesvec[0].length; offset = rangesvec[0].start; - // TODO blocking by compressing - // resp.content_encoding(ContentEncoding::Identity); + resp.encoding(ContentEncoding::Identity); resp.header( header::CONTENT_RANGE, format!( diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index a1e9e3c6..3466f66d 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -24,9 +24,6 @@ bitflags! { const KEEP_ALIVE = 0b0000_0010; const UPGRADE = 0b0000_0100; const NO_CHUNKING = 0b0000_1000; - const ENC_BR = 0b0001_0000; - const ENC_DEFLATE = 0b0010_0000; - const ENC_GZIP = 0b0100_0000; } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 3b33e1f9..29a850fa 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,4 +1,5 @@ //! Http response +use std::cell::{Ref, RefMut}; use std::io::Write; use std::{fmt, str}; @@ -14,6 +15,7 @@ use serde_json; use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; use crate::error::Error; +use crate::extensions::Extensions; use crate::header::{Header, IntoHeaderValue}; use crate::message::{ConnectionType, Message, ResponseHead}; @@ -577,6 +579,20 @@ impl ResponseBuilder { self } + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions.borrow() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut { + let head = self.head.as_ref().expect("cannot reuse response builder"); + head.extensions.borrow_mut() + } + /// Set a body and generate `Response`. /// /// `ResponseBuilder` can not be used after this call. diff --git a/examples/basic.rs b/examples/basic.rs index 91119657..1191b371 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) - .wrap(middleware::Compress::default()) + .wrap(middleware::encoding::Compress::default()) .wrap(middleware::Logger::default()) .service(index) .service(no_params) diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 5ffe9afb..5c6bad87 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -6,14 +6,46 @@ use std::str::FromStr; use actix_http::body::MessageBody; use actix_http::encoding::Encoder; use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; +use actix_http::ResponseBuilder; use actix_service::{Service, Transform}; use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; use crate::service::{ServiceRequest, ServiceResponse}; +struct Enc(ContentEncoding); + +/// Helper trait that allows to set specific encoding for response. +pub trait BodyEncoding { + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self; +} + +impl BodyEncoding for ResponseBuilder { + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(Enc(encoding)); + self + } +} + #[derive(Debug, Clone)] /// `Middleware` for compressing response body. +/// +/// Use `BodyEncoding` trait for overriding response compression. +/// To disable compression set encoding to `ContentEncoding::Identity` value. +/// +/// ```rust +/// use actix_web::{web, middleware::encoding, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new() +/// .wrap(encoding::Compress::default()) +/// .service( +/// web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// } +/// ``` pub struct Compress(ContentEncoding); impl Compress { @@ -118,8 +150,14 @@ where fn poll(&mut self) -> Poll { let resp = futures::try_ready!(self.fut.poll()); + let enc = if let Some(enc) = resp.head().extensions().get::() { + enc.0 + } else { + self.encoding + }; + Ok(Async::Ready(resp.map_body(move |head, body| { - Encoder::response(self.encoding, head, body) + Encoder::response(enc, head, body) }))) } } diff --git a/src/middleware/decompress.rs b/src/middleware/decompress.rs index d0a9bfd2..eaffbbdb 100644 --- a/src/middleware/decompress.rs +++ b/src/middleware/decompress.rs @@ -12,6 +12,22 @@ use crate::error::{Error, PayloadError}; use crate::service::ServiceRequest; use crate::HttpMessage; +/// `Middleware` for decompressing request's payload. +/// `Decompress` middleware must be added with `App::chain()` method. +/// +/// ```rust +/// use actix_web::{web, middleware::encoding, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new() +/// .chain(encoding::Decompress::new()) +/// .service( +/// web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// } +/// ``` pub struct Decompress

    (PhantomData

    ); impl

    Decompress

    diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index aee0ae3d..037d0006 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,13 +1,14 @@ //! Middlewares #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] mod compress; -#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] -pub use self::compress::Compress; - #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] mod decompress; #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] -pub use self::decompress::Decompress; +pub mod encoding { + //! Middlewares for compressing/decompressing payloads. + pub use super::compress::{BodyEncoding, Compress}; + pub use super::decompress::Decompress; +} pub mod cors; mod defaultheaders; diff --git a/tests/test_server.rs b/tests/test_server.rs index 29998bc0..364f9262 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -14,7 +14,7 @@ use flate2::Compression; use futures::stream::once; //Future, Stream use rand::{distributions::Alphanumeric, Rng}; -use actix_web::{dev::HttpMessageBody, middleware, web, App}; +use actix_web::{dev::HttpMessageBody, middleware::encoding, web, App}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -60,7 +60,7 @@ fn test_body_gzip() { let mut srv = TestServer::new(|| { h1::H1Service::new( App::new() - .wrap(middleware::Compress::new(ContentEncoding::Gzip)) + .wrap(encoding::Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -78,6 +78,32 @@ fn test_body_gzip() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[test] +fn test_body_encoding_override() { + let mut srv = TestServer::new(|| { + h1::H1Service::new( + App::new() + .wrap(encoding::Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + use actix_web::middleware::encoding::BodyEncoding; + Response::Ok().encoding(ContentEncoding::Deflate).body(STR) + }))), + ) + }); + + let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + + // decode + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + #[test] fn test_body_gzip_large() { let data = STR.repeat(10); @@ -87,7 +113,7 @@ fn test_body_gzip_large() { let data = srv_data.clone(); h1::H1Service::new( App::new() - .wrap(middleware::Compress::new(ContentEncoding::Gzip)) + .wrap(encoding::Compress::new(ContentEncoding::Gzip)) .service( web::resource("/") .route(web::to(move || Response::Ok().body(data.clone()))), @@ -120,7 +146,7 @@ fn test_body_gzip_large_random() { let data = srv_data.clone(); h1::H1Service::new( App::new() - .wrap(middleware::Compress::new(ContentEncoding::Gzip)) + .wrap(encoding::Compress::new(ContentEncoding::Gzip)) .service( web::resource("/") .route(web::to(move || Response::Ok().body(data.clone()))), @@ -147,7 +173,7 @@ fn test_body_chunked_implicit() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .wrap(middleware::Compress::new(ContentEncoding::Gzip)) + .wrap(encoding::Compress::new(ContentEncoding::Gzip)) .service(web::resource("/").route(web::get().to(move || { Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( STR.as_ref(), @@ -178,7 +204,7 @@ fn test_body_br_streaming() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .wrap(middleware::Compress::new(ContentEncoding::Br)) + .wrap(encoding::Compress::new(ContentEncoding::Br)) .service(web::resource("/").route(web::to(move || { Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( STR.as_ref(), @@ -255,7 +281,7 @@ fn test_body_deflate() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .wrap(middleware::Compress::new(ContentEncoding::Deflate)) + .wrap(encoding::Compress::new(ContentEncoding::Deflate)) .service( web::resource("/").route(web::to(move || Response::Ok().body(STR))), ), @@ -281,7 +307,7 @@ fn test_body_brotli() { let mut srv = TestServer::new(move || { h1::H1Service::new( App::new() - .wrap(middleware::Compress::new(ContentEncoding::Br)) + .wrap(encoding::Compress::new(ContentEncoding::Br)) .service( web::resource("/").route(web::to(move || Response::Ok().body(STR))), ), @@ -313,7 +339,7 @@ fn test_body_brotli() { fn test_gzip_encoding() { let mut srv = TestServer::new(move || { HttpService::new( - App::new().chain(middleware::Decompress::new()).service( + App::new().chain(encoding::Decompress::new()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -342,7 +368,7 @@ fn test_gzip_encoding_large() { let data = STR.repeat(10); let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(middleware::Decompress::new()).service( + App::new().chain(encoding::Decompress::new()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -375,7 +401,7 @@ fn test_reading_gzip_encoding_large_random() { let mut srv = TestServer::new(move || { HttpService::new( - App::new().chain(middleware::Decompress::new()).service( + App::new().chain(encoding::Decompress::new()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -404,7 +430,7 @@ fn test_reading_gzip_encoding_large_random() { fn test_reading_deflate_encoding() { let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(middleware::Decompress::new()).service( + App::new().chain(encoding::Decompress::new()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -433,7 +459,7 @@ fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(middleware::Decompress::new()).service( + App::new().chain(encoding::Decompress::new()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -466,7 +492,7 @@ fn test_reading_deflate_encoding_large_random() { let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(middleware::Decompress::new()).service( + App::new().chain(encoding::Decompress::new()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -496,7 +522,7 @@ fn test_reading_deflate_encoding_large_random() { fn test_brotli_encoding() { let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(middleware::Decompress::new()).service( + App::new().chain(encoding::Decompress::new()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), @@ -526,7 +552,7 @@ fn test_brotli_encoding_large() { let data = STR.repeat(10); let mut srv = TestServer::new(move || { h1::H1Service::new( - App::new().chain(middleware::Decompress::new()).service( + App::new().chain(encoding::Decompress::new()).service( web::resource("/") .route(web::to(move |body: Bytes| Response::Ok().body(body))), ), From c59937784e1dee1383f6e70f8b7eac5ca268a903 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Mar 2019 18:53:19 -0700 Subject: [PATCH 1144/1635] add client websockets support --- actix-http/Cargo.toml | 2 +- actix-http/src/client/connection.rs | 145 ++++++- actix-http/src/client/error.rs | 3 + actix-http/src/client/h1proto.rs | 26 ++ actix-http/src/client/pool.rs | 60 --- actix-http/src/h1/decoder.rs | 1 - actix-http/src/ws/client/connect.rs | 109 ----- actix-http/src/ws/client/mod.rs | 48 --- actix-http/src/ws/client/service.rs | 272 ------------ actix-http/src/ws/mod.rs | 2 - awc/Cargo.toml | 6 + awc/src/connect.rs | 94 ++++- .../src/ws/client => awc/src}/error.rs | 47 +-- awc/src/lib.rs | 12 +- awc/src/request.rs | 4 +- awc/src/ws.rs | 398 ++++++++++++++++++ {actix-http => awc}/tests/test_ws.rs | 0 src/lib.rs | 6 +- test-server/src/lib.rs | 10 +- 19 files changed, 709 insertions(+), 536 deletions(-) delete mode 100644 actix-http/src/ws/client/connect.rs delete mode 100644 actix-http/src/ws/client/mod.rs delete mode 100644 actix-http/src/ws/client/service.rs rename {actix-http/src/ws/client => awc/src}/error.rs (55%) create mode 100644 awc/src/ws.rs rename {actix-http => awc}/tests/test_ws.rs (100%) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 809d4d67..fefe05c4 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -49,7 +49,7 @@ fail = ["failure"] [dependencies] actix-service = "0.3.4" -actix-codec = "0.1.1" +actix-codec = "0.1.2" actix-connect = "0.1.0" actix-utils = "0.3.4" actix-server-config = "0.1.0" diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index e8c1201a..267c85d3 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,11 +1,13 @@ -use std::{fmt, time}; +use std::{fmt, io, time}; -use actix_codec::{AsyncRead, AsyncWrite}; -use bytes::Bytes; -use futures::Future; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use bytes::{Buf, Bytes}; +use futures::future::{err, Either, Future, FutureResult}; +use futures::Poll; use h2::client::SendRequest; use crate::body::MessageBody; +use crate::h1::ClientCodec; use crate::message::{RequestHead, ResponseHead}; use crate::payload::Payload; @@ -19,6 +21,7 @@ pub(crate) enum ConnectionType { } pub trait Connection { + type Io: AsyncRead + AsyncWrite; type Future: Future; /// Send request and body @@ -27,6 +30,14 @@ pub trait Connection { head: RequestHead, body: B, ) -> Self::Future; + + type TunnelFuture: Future< + Item = (ResponseHead, Framed), + Error = SendRequestError, + >; + + /// Send request, returns Response and Framed + fn open_tunnel(self, head: RequestHead) -> Self::TunnelFuture; } pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { @@ -80,6 +91,7 @@ impl Connection for IoConnection where T: AsyncRead + AsyncWrite + 'static, { + type Io = T; type Future = Box>; fn send_request( @@ -104,6 +116,35 @@ where )), } } + + type TunnelFuture = Either< + Box< + Future< + Item = (ResponseHead, Framed), + Error = SendRequestError, + >, + >, + FutureResult<(ResponseHead, Framed), SendRequestError>, + >; + + /// Send request, returns Response and Framed + fn open_tunnel(mut self, head: RequestHead) -> Self::TunnelFuture { + match self.io.take().unwrap() { + ConnectionType::H1(io) => { + Either::A(Box::new(h1proto::open_tunnel(io, head))) + } + ConnectionType::H2(io) => { + if let Some(mut pool) = self.pool.take() { + pool.release(IoConnection::new( + ConnectionType::H2(io), + self.created, + None, + )); + } + Either::B(err(SendRequestError::TunnelNotSupported)) + } + } + } } #[allow(dead_code)] @@ -117,6 +158,7 @@ where A: AsyncRead + AsyncWrite + 'static, B: AsyncRead + AsyncWrite + 'static, { + type Io = EitherIo; type Future = Box>; fn send_request( @@ -129,4 +171,99 @@ where EitherConnection::B(con) => con.send_request(head, body), } } + + type TunnelFuture = Box< + Future< + Item = (ResponseHead, Framed), + Error = SendRequestError, + >, + >; + + /// Send request, returns Response and Framed + fn open_tunnel(self, head: RequestHead) -> Self::TunnelFuture { + match self { + EitherConnection::A(con) => Box::new( + con.open_tunnel(head) + .map(|(head, framed)| (head, framed.map_io(|io| EitherIo::A(io)))), + ), + EitherConnection::B(con) => Box::new( + con.open_tunnel(head) + .map(|(head, framed)| (head, framed.map_io(|io| EitherIo::B(io)))), + ), + } + } +} + +pub enum EitherIo { + A(A), + B(B), +} + +impl io::Read for EitherIo +where + A: io::Read, + B: io::Read, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + EitherIo::A(ref mut val) => val.read(buf), + EitherIo::B(ref mut val) => val.read(buf), + } + } +} + +impl AsyncRead for EitherIo +where + A: AsyncRead, + B: AsyncRead, +{ + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + match self { + EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf), + EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf), + } + } +} + +impl io::Write for EitherIo +where + A: io::Write, + B: io::Write, +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + EitherIo::A(ref mut val) => val.write(buf), + EitherIo::B(ref mut val) => val.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match self { + EitherIo::A(ref mut val) => val.flush(), + EitherIo::B(ref mut val) => val.flush(), + } + } +} + +impl AsyncWrite for EitherIo +where + A: AsyncWrite, + B: AsyncWrite, +{ + fn shutdown(&mut self) -> Poll<(), io::Error> { + match self { + EitherIo::A(ref mut val) => val.shutdown(), + EitherIo::B(ref mut val) => val.shutdown(), + } + } + + fn write_buf(&mut self, buf: &mut U) -> Poll + where + Self: Sized, + { + match self { + EitherIo::A(ref mut val) => val.write_buf(buf), + EitherIo::B(ref mut val) => val.write_buf(buf), + } + } } diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 69ec4958..e67db546 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -105,6 +105,9 @@ pub enum SendRequestError { /// Http2 error #[display(fmt = "{}", _0)] H2(h2::Error), + /// Tunnels are not supported for http2 connection + #[display(fmt = "Tunnels are not supported for http2 connection")] + TunnelNotSupported, /// Error sending request body Body(Error), } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index b7b8d4a0..5fec9c4f 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -70,6 +70,32 @@ where }) } +pub(crate) fn open_tunnel( + io: T, + head: RequestHead, +) -> impl Future), Error = SendRequestError> +where + T: AsyncRead + AsyncWrite + 'static, +{ + // create Framed and send reqest + Framed::new(io, h1::ClientCodec::default()) + .send((head, BodySize::None).into()) + .from_err() + // read response + .and_then(|framed| { + framed + .into_future() + .map_err(|(e, _)| SendRequestError::from(e)) + .and_then(|(head, framed)| { + if let Some(head) = head { + Ok((head, framed)) + } else { + Err(SendRequestError::from(ConnectError::Disconnected)) + } + }) + }) +} + #[doc(hidden)] /// HTTP client connection pub struct H1Connection { diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index a94b1e52..aff11181 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -411,66 +411,6 @@ where } } -// struct ConnectorPoolSupport -// where -// Io: AsyncRead + AsyncWrite + 'static, -// { -// connector: T, -// inner: Rc>>, -// } - -// impl Future for ConnectorPoolSupport -// where -// Io: AsyncRead + AsyncWrite + 'static, -// T: Service, -// T::Future: 'static, -// { -// type Item = (); -// type Error = (); - -// fn poll(&mut self) -> Poll { -// let mut inner = self.inner.as_ref().borrow_mut(); -// inner.task.register(); - -// // check waiters -// loop { -// let (key, token) = { -// if let Some((key, token)) = inner.waiters_queue.get_index(0) { -// (key.clone(), *token) -// } else { -// break; -// } -// }; -// match inner.acquire(&key) { -// Acquire::NotAvailable => break, -// Acquire::Acquired(io, created) => { -// let (_, tx) = inner.waiters.remove(token); -// if let Err(conn) = tx.send(Ok(IoConnection::new( -// io, -// created, -// Some(Acquired(key.clone(), Some(self.inner.clone()))), -// ))) { -// let (io, created) = conn.unwrap().into_inner(); -// inner.release_conn(&key, io, created); -// } -// } -// Acquire::Available => { -// let (connect, tx) = inner.waiters.remove(token); -// OpenWaitingConnection::spawn( -// key.clone(), -// tx, -// self.inner.clone(), -// self.connector.call(connect), -// ); -// } -// } -// let _ = inner.waiters_queue.swap_remove_index(0); -// } - -// Ok(Async::NotReady) -// } -// } - struct CloseConnection { io: T, timeout: Delay, diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 9b97713f..dfd9fe25 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -611,7 +611,6 @@ mod tests { use super::*; use crate::error::ParseError; use crate::httpmessage::HttpMessage; - use crate::message::Head; impl PayloadType { fn unwrap(self) -> PayloadDecoder { diff --git a/actix-http/src/ws/client/connect.rs b/actix-http/src/ws/client/connect.rs deleted file mode 100644 index 2760967e..00000000 --- a/actix-http/src/ws/client/connect.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Http client request -use std::str; - -#[cfg(feature = "cookies")] -use cookie::Cookie; -use http::header::{HeaderName, HeaderValue}; -use http::{Error as HttpError, HttpTryFrom, Uri}; - -use super::ClientError; -use crate::header::IntoHeaderValue; -use crate::message::RequestHead; - -/// `WebSocket` connection -pub struct Connect { - pub(super) head: RequestHead, - pub(super) err: Option, - pub(super) http_err: Option, - pub(super) origin: Option, - pub(super) protocols: Option, - pub(super) max_size: usize, - pub(super) server_mode: bool, -} - -impl Connect { - /// Create new websocket connection - pub fn new>(uri: S) -> Connect { - let mut cl = Connect { - head: RequestHead::default(), - err: None, - http_err: None, - origin: None, - protocols: None, - max_size: 65_536, - server_mode: false, - }; - - match Uri::try_from(uri.as_ref()) { - Ok(uri) => cl.head.uri = uri, - Err(e) => cl.http_err = Some(e.into()), - } - - cl - } - - /// Set supported websocket protocols - pub fn protocols(mut self, protos: U) -> Self - where - U: IntoIterator + 'static, - V: AsRef, - { - let mut protos = protos - .into_iter() - .fold(String::new(), |acc, s| acc + s.as_ref() + ","); - protos.pop(); - self.protocols = Some(protos); - self - } - - // #[cfg(feature = "cookies")] - // /// Set cookie for handshake request - // pub fn cookie(mut self, cookie: Cookie) -> Self { - // self.request.cookie(cookie); - // self - // } - - /// Set request Origin - pub fn origin(mut self, origin: V) -> Self - where - HeaderValue: HttpTryFrom, - { - match HeaderValue::try_from(origin) { - Ok(value) => self.origin = Some(value), - Err(e) => self.http_err = Some(e.into()), - } - self - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_frame_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } - - /// Disable payload masking. By default ws client masks frame payload. - pub fn server_mode(mut self) -> Self { - self.server_mode = true; - self - } - - /// Set request header - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.head.headers.append(key, value); - } - Err(e) => self.http_err = Some(e.into()), - }, - Err(e) => self.http_err = Some(e.into()), - } - self - } -} diff --git a/actix-http/src/ws/client/mod.rs b/actix-http/src/ws/client/mod.rs deleted file mode 100644 index a5c22196..00000000 --- a/actix-http/src/ws/client/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -mod connect; -mod error; -mod service; - -pub use self::connect::Connect; -pub use self::error::ClientError; -pub use self::service::Client; - -#[derive(PartialEq, Hash, Debug, Clone, Copy)] -pub(crate) enum Protocol { - Http, - Https, - Ws, - Wss, -} - -impl Protocol { - fn from(s: &str) -> Option { - match s { - "http" => Some(Protocol::Http), - "https" => Some(Protocol::Https), - "ws" => Some(Protocol::Ws), - "wss" => Some(Protocol::Wss), - _ => None, - } - } - - // fn is_http(self) -> bool { - // match self { - // Protocol::Https | Protocol::Http => true, - // _ => false, - // } - // } - - // fn is_secure(self) -> bool { - // match self { - // Protocol::Https | Protocol::Wss => true, - // _ => false, - // } - // } - - fn port(self) -> u16 { - match self { - Protocol::Http | Protocol::Ws => 80, - Protocol::Https | Protocol::Wss => 443, - } - } -} diff --git a/actix-http/src/ws/client/service.rs b/actix-http/src/ws/client/service.rs deleted file mode 100644 index bc86e516..00000000 --- a/actix-http/src/ws/client/service.rs +++ /dev/null @@ -1,272 +0,0 @@ -//! websockets client -use std::marker::PhantomData; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_connect::{default_connector, Connect as TcpConnect, ConnectError}; -use actix_service::{apply_fn, Service}; -use base64; -use futures::future::{err, Either, FutureResult}; -use futures::{try_ready, Async, Future, Poll, Sink, Stream}; -use http::header::{self, HeaderValue}; -use http::{HttpTryFrom, StatusCode}; -use log::trace; -use rand; -use sha1::Sha1; - -use crate::body::BodySize; -use crate::h1; -use crate::message::{ConnectionType, ResponseHead}; -use crate::ws::Codec; - -use super::{ClientError, Connect, Protocol}; - -/// WebSocket's client -pub struct Client { - connector: T, -} - -impl Client<()> { - /// Create client with default connector. - pub fn default() -> Client< - impl Service< - Request = TcpConnect, - Response = impl AsyncRead + AsyncWrite, - Error = ConnectError, - > + Clone, - > { - Client::new(apply_fn(default_connector(), |msg: TcpConnect<_>, srv| { - srv.call(msg).map(|stream| stream.into_parts().0) - })) - } -} - -impl Client -where - T: Service, Error = ConnectError>, - T::Response: AsyncRead + AsyncWrite, -{ - /// Create new websocket's client factory - pub fn new(connector: T) -> Self { - Client { connector } - } -} - -impl Clone for Client -where - T: Service, Error = ConnectError> + Clone, - T::Response: AsyncRead + AsyncWrite, -{ - fn clone(&self) -> Self { - Client { - connector: self.connector.clone(), - } - } -} - -impl Service for Client -where - T: Service, Error = ConnectError>, - T::Response: AsyncRead + AsyncWrite + 'static, - T::Future: 'static, -{ - type Request = Connect; - type Response = Framed; - type Error = ClientError; - type Future = Either< - FutureResult, - ClientResponseFut, - >; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.connector.poll_ready().map_err(ClientError::from) - } - - fn call(&mut self, mut req: Connect) -> Self::Future { - if let Some(e) = req.err.take() { - Either::A(err(e)) - } else if let Some(e) = req.http_err.take() { - Either::A(err(e.into())) - } else { - // origin - if let Some(origin) = req.origin.take() { - req.head.headers.insert(header::ORIGIN, origin); - } - - req.head.set_connection_type(ConnectionType::Upgrade); - req.head - .headers - .insert(header::UPGRADE, HeaderValue::from_static("websocket")); - req.head.headers.insert( - header::SEC_WEBSOCKET_VERSION, - HeaderValue::from_static("13"), - ); - - if let Some(protocols) = req.protocols.take() { - req.head.headers.insert( - header::SEC_WEBSOCKET_PROTOCOL, - HeaderValue::try_from(protocols.as_str()).unwrap(), - ); - } - if let Some(e) = req.http_err { - return Either::A(err(e.into())); - }; - - let mut request = req.head; - if request.uri.host().is_none() { - return Either::A(err(ClientError::InvalidUrl)); - } - - // supported protocols - let proto = if let Some(scheme) = request.uri.scheme_part() { - match Protocol::from(scheme.as_str()) { - Some(proto) => proto, - None => return Either::A(err(ClientError::InvalidUrl)), - } - } else { - return Either::A(err(ClientError::InvalidUrl)); - }; - - // Generate a random key for the `Sec-WebSocket-Key` header. - // a base64-encoded (see Section 4 of [RFC4648]) value that, - // when decoded, is 16 bytes in length (RFC 6455) - let sec_key: [u8; 16] = rand::random(); - let key = base64::encode(&sec_key); - - request.headers.insert( - header::SEC_WEBSOCKET_KEY, - HeaderValue::try_from(key.as_str()).unwrap(), - ); - - // prep connection - let connect = TcpConnect::new(request.uri.host().unwrap().to_string()) - .set_port(request.uri.port_u16().unwrap_or_else(|| proto.port())); - - let fut = Box::new( - self.connector - .call(connect) - .map_err(ClientError::from) - .and_then(move |io| { - // h1 protocol - let framed = Framed::new(io, h1::ClientCodec::default()); - framed - .send((request, BodySize::None).into()) - .map_err(ClientError::from) - .and_then(|framed| { - framed - .into_future() - .map_err(|(e, _)| ClientError::from(e)) - }) - }), - ); - - // start handshake - Either::B(ClientResponseFut { - key, - fut, - max_size: req.max_size, - server_mode: req.server_mode, - _t: PhantomData, - }) - } - } -} - -/// Future that implementes client websocket handshake process. -/// -/// It resolves to a `Framed` instance. -pub struct ClientResponseFut -where - T: AsyncRead + AsyncWrite, -{ - fut: Box< - Future< - Item = (Option, Framed), - Error = ClientError, - >, - >, - key: String, - max_size: usize, - server_mode: bool, - _t: PhantomData, -} - -impl Future for ClientResponseFut -where - T: AsyncRead + AsyncWrite, -{ - type Item = Framed; - type Error = ClientError; - - fn poll(&mut self) -> Poll { - let (item, framed) = try_ready!(self.fut.poll()); - - let res = match item { - Some(res) => res, - None => return Err(ClientError::Disconnected), - }; - - // verify response - if res.status != StatusCode::SWITCHING_PROTOCOLS { - return Err(ClientError::InvalidResponseStatus(res.status)); - } - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = res.headers.get(header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - trace!("Invalid upgrade header"); - return Err(ClientError::InvalidUpgradeHeader); - } - // Check for "CONNECTION" header - if let Some(conn) = res.headers.get(header::CONNECTION) { - if let Ok(s) = conn.to_str() { - if !s.to_lowercase().contains("upgrade") { - trace!("Invalid connection header: {}", s); - return Err(ClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - trace!("Invalid connection header: {:?}", conn); - return Err(ClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - trace!("Missing connection header"); - return Err(ClientError::MissingConnectionHeader); - } - - if let Some(key) = res.headers.get(header::SEC_WEBSOCKET_ACCEPT) { - // field is constructed by concatenating /key/ - // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) - const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(self.key.as_ref()); - sha1.update(WS_GUID); - let encoded = base64::encode(&sha1.digest().bytes()); - if key.as_bytes() != encoded.as_bytes() { - trace!( - "Invalid challenge response: expected: {} received: {:?}", - encoded, - key - ); - return Err(ClientError::InvalidChallengeResponse(encoded, key.clone())); - } - } else { - trace!("Missing SEC-WEBSOCKET-ACCEPT header"); - return Err(ClientError::MissingWebSocketAcceptHeader); - }; - - // websockets codec - let codec = if self.server_mode { - Codec::new().max_size(self.max_size) - } else { - Codec::new().max_size(self.max_size).client_mode() - }; - - Ok(Async::Ready(framed.into_framed(codec))) - } -} diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 88fabde9..065c34d9 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -13,7 +13,6 @@ use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::{Response, ResponseBuilder}; -mod client; mod codec; mod frame; mod mask; @@ -21,7 +20,6 @@ mod proto; mod service; mod transport; -pub use self::client::{Client, ClientError, Connect}; pub use self::codec::{Codec, Frame, Message}; pub use self::frame::Parser; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e08169c9..c915475f 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -39,13 +39,16 @@ flate2-zlib = ["actix-http/flate2-zlib"] flate2-rust = ["actix-http/flate2-rust"] [dependencies] +actix-codec = "0.1.1" actix-service = "0.3.4" actix-http = { path = "../actix-http/" } base64 = "0.10.1" bytes = "0.4" +derive_more = "0.14" futures = "0.1" log =" 0.4" percent-encoding = "1.0" +rand = "0.6" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.5.3" @@ -58,8 +61,11 @@ actix-rt = "0.2.1" actix-web = { path = "..", features=["ssl"] } actix-http = { path = "../actix-http/", features=["ssl"] } actix-http-test = { path = "../test-server/", features=["ssl"] } +actix-utils = "0.3.4" +actix-server = { version = "0.4.0", features=["ssl"] } brotli2 = { version="^0.3.2" } flate2 = { version="^1.0.2" } env_logger = "0.6" mime = "0.3" rand = "0.6" +tokio-tcp = "0.1" \ No newline at end of file diff --git a/awc/src/connect.rs b/awc/src/connect.rs index a0766279..77cd1fbf 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,8 +1,12 @@ +use std::io; + +use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::Body; use actix_http::client::{ConnectError, Connection, SendRequestError}; -use actix_http::{http, RequestHead}; +use actix_http::h1::ClientCodec; +use actix_http::{http, RequestHead, ResponseHead}; use actix_service::Service; -use futures::Future; +use futures::{Future, Poll}; use crate::response::ClientResponse; @@ -14,13 +18,26 @@ pub(crate) trait Connect { head: RequestHead, body: Body, ) -> Box>; + + /// Send request, returns Response and Framed + fn open_tunnel( + &mut self, + head: RequestHead, + ) -> Box< + Future< + Item = (ResponseHead, Framed), + Error = SendRequestError, + >, + >; } impl Connect for ConnectorWrapper where T: Service, T::Response: Connection, + ::Io: 'static, ::Future: 'static, + ::TunnelFuture: 'static, T::Future: 'static, { fn send_request( @@ -38,4 +55,77 @@ where .map(|(head, payload)| ClientResponse::new(head, payload)), ) } + + fn open_tunnel( + &mut self, + head: RequestHead, + ) -> Box< + Future< + Item = (ResponseHead, Framed), + Error = SendRequestError, + >, + > { + Box::new( + self.0 + // connect to the host + .call(head.uri.clone()) + .from_err() + // send request + .and_then(move |connection| connection.open_tunnel(head)) + .map(|(head, framed)| { + let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); + (head, framed) + }), + ) + } +} + +trait AsyncSocket { + fn as_read(&self) -> &AsyncRead; + fn as_read_mut(&mut self) -> &mut AsyncRead; + fn as_write(&mut self) -> &mut AsyncWrite; +} + +struct Socket(T); + +impl AsyncSocket for Socket { + fn as_read(&self) -> &AsyncRead { + &self.0 + } + fn as_read_mut(&mut self) -> &mut AsyncRead { + &mut self.0 + } + fn as_write(&mut self) -> &mut AsyncWrite { + &mut self.0 + } +} + +pub struct BoxedSocket(Box); + +impl io::Read for BoxedSocket { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.as_read_mut().read(buf) + } +} + +impl AsyncRead for BoxedSocket { + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.0.as_read().prepare_uninitialized_buffer(buf) + } +} + +impl io::Write for BoxedSocket { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.as_write().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.as_write().flush() + } +} + +impl AsyncWrite for BoxedSocket { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.0.as_write().shutdown() + } } diff --git a/actix-http/src/ws/client/error.rs b/awc/src/error.rs similarity index 55% rename from actix-http/src/ws/client/error.rs rename to awc/src/error.rs index ae1e3996..d3f1c1a1 100644 --- a/actix-http/src/ws/client/error.rs +++ b/awc/src/error.rs @@ -1,19 +1,14 @@ -//! Http client request -use std::io; +//! Http client errors +pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; +pub use actix_http::error::PayloadError; +pub use actix_http::ws::ProtocolError as WsProtocolError; -use actix_connect::ConnectError; +use actix_http::http::{header::HeaderValue, Error as HttpError, StatusCode}; use derive_more::{Display, From}; -use http::{header::HeaderValue, Error as HttpError, StatusCode}; - -use crate::error::ParseError; -use crate::ws::ProtocolError; /// Websocket client error #[derive(Debug, Display, From)] -pub enum ClientError { - /// Invalid url - #[display(fmt = "Invalid url")] - InvalidUrl, +pub enum WsClientError { /// Invalid response status #[display(fmt = "Invalid response status")] InvalidResponseStatus(StatusCode), @@ -32,22 +27,22 @@ pub enum ClientError { /// Invalid challenge response #[display(fmt = "Invalid challenge response")] InvalidChallengeResponse(String, HeaderValue), - /// Http parsing error - #[display(fmt = "Http parsing error")] - Http(HttpError), - /// Response parsing error - #[display(fmt = "Response parsing error: {}", _0)] - ParseError(ParseError), /// Protocol error #[display(fmt = "{}", _0)] - Protocol(ProtocolError), - /// Connect error - #[display(fmt = "Connector error: {:?}", _0)] - Connect(ConnectError), - /// IO Error + Protocol(WsProtocolError), + /// Send request error #[display(fmt = "{}", _0)] - Io(io::Error), - /// "Disconnected" - #[display(fmt = "Disconnected")] - Disconnected, + SendRequest(SendRequestError), +} + +impl From for WsClientError { + fn from(err: InvalidUrl) -> Self { + WsClientError::SendRequest(err.into()) + } +} + +impl From for WsClientError { + fn from(err: HttpError) -> Self { + WsClientError::SendRequest(err.into()) + } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 3bad8caa..9f5ca1f2 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -23,8 +23,6 @@ use std::cell::RefCell; use std::rc::Rc; -pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; -pub use actix_http::error::PayloadError; pub use actix_http::http; use actix_http::client::Connector; @@ -33,13 +31,16 @@ use actix_http::RequestHead; mod builder; mod connect; +pub mod error; mod request; mod response; pub mod test; +mod ws; pub use self::builder::ClientBuilder; pub use self::request::ClientRequest; pub use self::response::ClientResponse; +pub use self::ws::WebsocketsRequest; use self::connect::{Connect, ConnectorWrapper}; @@ -165,4 +166,11 @@ impl Client { { ClientRequest::new(Method::OPTIONS, url, self.connector.clone()) } + + pub fn ws(&self, url: U) -> WebsocketsRequest + where + Uri: HttpTryFrom, + { + WebsocketsRequest::new(url, self.connector.clone()) + } } diff --git a/awc/src/request.rs b/awc/src/request.rs index 7beb737e..c0962ebf 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -12,7 +12,6 @@ use serde::Serialize; use serde_json; use actix_http::body::{Body, BodyStream}; -use actix_http::client::{InvalidUrl, SendRequestError}; use actix_http::encoding::Decoder; use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; use actix_http::http::{ @@ -21,8 +20,9 @@ use actix_http::http::{ }; use actix_http::{Error, Payload, RequestHead}; +use crate::connect::Connect; +use crate::error::{InvalidUrl, PayloadError, SendRequestError}; use crate::response::ClientResponse; -use crate::{Connect, PayloadError}; #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] const HTTPS_ENCODING: &str = "br, gzip, deflate"; diff --git a/awc/src/ws.rs b/awc/src/ws.rs new file mode 100644 index 00000000..f959e62c --- /dev/null +++ b/awc/src/ws.rs @@ -0,0 +1,398 @@ +//! Websockets client +use std::cell::RefCell; +use std::io::Write; +use std::rc::Rc; +use std::{fmt, str}; + +use actix_codec::Framed; +use actix_http::{ws, Payload, RequestHead}; +use bytes::{BufMut, BytesMut}; +#[cfg(feature = "cookies")] +use cookie::{Cookie, CookieJar}; +use futures::future::{err, Either, Future}; + +use crate::connect::{BoxedSocket, Connect}; +use crate::error::{InvalidUrl, WsClientError}; +use crate::http::header::{ + self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION, +}; +use crate::http::{ + ConnectionType, Error as HttpError, HttpTryFrom, Method, StatusCode, Uri, Version, +}; +use crate::response::ClientResponse; + +/// `WebSocket` connection +pub struct WebsocketsRequest { + head: RequestHead, + err: Option, + origin: Option, + protocols: Option, + max_size: usize, + server_mode: bool, + default_headers: bool, + #[cfg(feature = "cookies")] + cookies: Option, + connector: Rc>, +} + +impl WebsocketsRequest { + /// Create new websocket connection + pub(crate) fn new(uri: U, connector: Rc>) -> Self + where + Uri: HttpTryFrom, + { + let mut err = None; + let mut head = RequestHead::default(); + head.method = Method::GET; + head.version = Version::HTTP_11; + + match Uri::try_from(uri) { + Ok(uri) => head.uri = uri, + Err(e) => err = Some(e.into()), + } + + WebsocketsRequest { + head, + err, + connector, + origin: None, + protocols: None, + max_size: 65_536, + server_mode: false, + #[cfg(feature = "cookies")] + cookies: None, + default_headers: true, + } + } + + /// Set supported websocket protocols + pub fn protocols(mut self, protos: U) -> Self + where + U: IntoIterator + 'static, + V: AsRef, + { + let mut protos = protos + .into_iter() + .fold(String::new(), |acc, s| acc + s.as_ref() + ","); + protos.pop(); + self.protocols = Some(protos); + self + } + + #[cfg(feature = "cookies")] + /// Set a cookie + pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { + if self.cookies.is_none() { + let mut jar = CookieJar::new(); + jar.add(cookie.into_owned()); + self.cookies = Some(jar) + } else { + self.cookies.as_mut().unwrap().add(cookie.into_owned()); + } + self + } + + /// Set request Origin + pub fn origin(mut self, origin: V) -> Self + where + HeaderValue: HttpTryFrom, + { + match HeaderValue::try_from(origin) { + Ok(value) => self.origin = Some(value), + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Set max frame size + /// + /// By default max size is set to 64kb + pub fn max_frame_size(mut self, size: usize) -> Self { + self.max_size = size; + self + } + + /// Disable payload masking. By default ws client masks frame payload. + pub fn server_mode(mut self) -> Self { + self.server_mode = true; + self + } + + /// Do not add default request headers. + /// By default `Date` and `User-Agent` headers are set. + pub fn no_default_headers(mut self) -> Self { + self.default_headers = false; + self + } + + /// Append a header. + /// + /// Header gets appended to existing header. + /// To override header use `set_header()` method. + pub fn header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.head.headers.append(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Insert a header, replaces existing header. + pub fn set_header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.head.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Insert a header only if it is not yet set. + pub fn set_header_if_none(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + match HeaderName::try_from(key) { + Ok(key) => { + if !self.head.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + self.head.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + } + } + } + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Set HTTP basic authorization header + pub fn basic_auth(self, username: U, password: Option

    ) -> Self + where + U: fmt::Display, + P: fmt::Display, + { + let auth = match password { + Some(password) => format!("{}:{}", username, password), + None => format!("{}", username), + }; + self.header(AUTHORIZATION, format!("Basic {}", base64::encode(&auth))) + } + + /// Set HTTP bearer authentication header + pub fn bearer_auth(self, token: T) -> Self + where + T: fmt::Display, + { + self.header(AUTHORIZATION, format!("Bearer {}", token)) + } + + /// Complete request construction and connect. + pub fn connect( + mut self, + ) -> impl Future< + Item = (ClientResponse, Framed), + Error = WsClientError, + > { + if let Some(e) = self.err.take() { + return Either::A(err(e.into())); + } + + // validate uri + let uri = &self.head.uri; + if uri.host().is_none() { + return Either::A(err(InvalidUrl::MissingHost.into())); + } else if uri.scheme_part().is_none() { + return Either::A(err(InvalidUrl::MissingScheme.into())); + } else if let Some(scheme) = uri.scheme_part() { + match scheme.as_str() { + "http" | "ws" | "https" | "wss" => (), + _ => return Either::A(err(InvalidUrl::UnknownScheme.into())), + } + } else { + return Either::A(err(InvalidUrl::UnknownScheme.into())); + } + + // set default headers + let mut slf = if self.default_headers { + // set request host header + if let Some(host) = self.head.uri.host() { + if !self.head.headers.contains_key(header::HOST) { + let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); + + let _ = match self.head.uri.port_u16() { + None | Some(80) | Some(443) => write!(wrt, "{}", host), + Some(port) => write!(wrt, "{}:{}", host, port), + }; + + match wrt.get_mut().take().freeze().try_into() { + Ok(value) => { + self.head.headers.insert(header::HOST, value); + } + Err(e) => return Either::A(err(HttpError::from(e).into())), + } + } + } + + // user agent + self.set_header_if_none( + header::USER_AGENT, + concat!("awc/", env!("CARGO_PKG_VERSION")), + ) + } else { + self + }; + + #[allow(unused_mut)] + let mut head = slf.head; + + #[cfg(feature = "cookies")] + { + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + use std::fmt::Write; + + // set cookies + if let Some(ref mut jar) = slf.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = + percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } + } + + // origin + if let Some(origin) = slf.origin.take() { + head.headers.insert(header::ORIGIN, origin); + } + + head.set_connection_type(ConnectionType::Upgrade); + head.headers + .insert(header::UPGRADE, HeaderValue::from_static("websocket")); + head.headers.insert( + header::SEC_WEBSOCKET_VERSION, + HeaderValue::from_static("13"), + ); + + if let Some(protocols) = slf.protocols.take() { + head.headers.insert( + header::SEC_WEBSOCKET_PROTOCOL, + HeaderValue::try_from(protocols.as_str()).unwrap(), + ); + } + + // Generate a random key for the `Sec-WebSocket-Key` header. + // a base64-encoded (see Section 4 of [RFC4648]) value that, + // when decoded, is 16 bytes in length (RFC 6455) + let sec_key: [u8; 16] = rand::random(); + let key = base64::encode(&sec_key); + + head.headers.insert( + header::SEC_WEBSOCKET_KEY, + HeaderValue::try_from(key.as_str()).unwrap(), + ); + + let max_size = slf.max_size; + let server_mode = slf.server_mode; + + let fut = slf + .connector + .borrow_mut() + .open_tunnel(head) + .from_err() + .and_then(move |(head, framed)| { + // verify response + if head.status != StatusCode::SWITCHING_PROTOCOLS { + return Err(WsClientError::InvalidResponseStatus(head.status)); + } + // Check for "UPGRADE" to websocket header + let has_hdr = if let Some(hdr) = head.headers.get(header::UPGRADE) { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("websocket") + } else { + false + } + } else { + false + }; + if !has_hdr { + log::trace!("Invalid upgrade header"); + return Err(WsClientError::InvalidUpgradeHeader); + } + // Check for "CONNECTION" header + if let Some(conn) = head.headers.get(header::CONNECTION) { + if let Ok(s) = conn.to_str() { + if !s.to_ascii_lowercase().contains("upgrade") { + log::trace!("Invalid connection header: {}", s); + return Err(WsClientError::InvalidConnectionHeader( + conn.clone(), + )); + } + } else { + log::trace!("Invalid connection header: {:?}", conn); + return Err(WsClientError::InvalidConnectionHeader(conn.clone())); + } + } else { + log::trace!("Missing connection header"); + return Err(WsClientError::MissingConnectionHeader); + } + + if let Some(hdr_key) = head.headers.get(header::SEC_WEBSOCKET_ACCEPT) { + let encoded = ws::hash_key(key.as_ref()); + if hdr_key.as_bytes() != encoded.as_bytes() { + log::trace!( + "Invalid challenge response: expected: {} received: {:?}", + encoded, + key + ); + return Err(WsClientError::InvalidChallengeResponse( + encoded, + hdr_key.clone(), + )); + } + } else { + log::trace!("Missing SEC-WEBSOCKET-ACCEPT header"); + return Err(WsClientError::MissingWebSocketAcceptHeader); + }; + + // response and ws framed + Ok(( + ClientResponse::new(head, Payload::None), + framed.map_codec(|_| { + if server_mode { + ws::Codec::new().max_size(max_size) + } else { + ws::Codec::new().max_size(max_size).client_mode() + } + }), + )) + }); + Either::B(fut) + } +} diff --git a/actix-http/tests/test_ws.rs b/awc/tests/test_ws.rs similarity index 100% rename from actix-http/tests/test_ws.rs rename to awc/tests/test_ws.rs diff --git a/src/lib.rs b/src/lib.rs index 5b0ce784..d3d66c61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -393,8 +393,8 @@ pub mod client { //! })); //! } //! ``` - pub use awc::{ - test, Client, ClientBuilder, ClientRequest, ClientResponse, ConnectError, - InvalidUrl, PayloadError, SendRequestError, + pub use awc::error::{ + ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError, }; + pub use awc::{test, Client, ClientBuilder, ClientRequest, ClientResponse}; } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 7cd94d4d..07a0e0b4 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -7,7 +7,6 @@ use actix_http::client::Connector; use actix_http::ws; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; -use actix_service::Service; use awc::{Client, ClientRequest}; use futures::future::{lazy, Future}; use http::Method; @@ -205,16 +204,19 @@ impl TestServerRuntime { pub fn ws_at( &mut self, path: &str, - ) -> Result, ws::ClientError> { + ) -> Result, awc::error::WsClientError> + { let url = self.url(path); + let connect = self.client.ws(url).connect(); self.rt - .block_on(lazy(|| ws::Client::default().call(ws::Connect::new(url)))) + .block_on(lazy(move || connect.map(|(_, framed)| framed))) } /// Connect to a websocket server pub fn ws( &mut self, - ) -> Result, ws::ClientError> { + ) -> Result, awc::error::WsClientError> + { self.ws_at("/") } } From 4309d9b88c90fb2448af127384d9b3cb8b11b932 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 05:04:39 -0700 Subject: [PATCH 1145/1635] port multipart support --- Cargo.toml | 1 + actix-http/src/error.rs | 14 + src/error.rs | 36 ++ src/test.rs | 7 +- src/types/mod.rs | 2 + src/types/multipart.rs | 1137 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 1194 insertions(+), 3 deletions(-) create mode 100644 src/types/multipart.rs diff --git a/Cargo.toml b/Cargo.toml index 363989bf..63a607e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ derive_more = "0.14" encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" +httparse = "1.3" log = "0.4" mime = "0.3" net2 = "0.2.33" diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 23e6728d..a026fe9d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -905,6 +905,20 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + #[test] + fn test_payload_error() { + let err: PayloadError = + io::Error::new(io::ErrorKind::Other, "ParseError").into(); + assert_eq!(format!("{}", err), "ParseError"); + assert_eq!(format!("{}", err.cause().unwrap()), "ParseError"); + + let err = PayloadError::Incomplete; + assert_eq!( + format!("{}", err), + "A payload reached EOF, but is not complete." + ); + } + macro_rules! from { ($from:expr => $error:pat) => { match ParseError::from($from) { diff --git a/src/error.rs b/src/error.rs index fc0f9fdf..f7610d50 100644 --- a/src/error.rs +++ b/src/error.rs @@ -143,6 +143,36 @@ impl ResponseError for ReadlinesError { } } +/// A set of errors that can occur during parsing multipart streams +#[derive(Debug, Display, From)] +pub enum MultipartError { + /// Content-Type header is not found + #[display(fmt = "No Content-type header found")] + NoContentType, + /// Can not parse Content-Type header + #[display(fmt = "Can not parse Content-Type header")] + ParseContentType, + /// Multipart boundary is not found + #[display(fmt = "Multipart boundary is not found")] + Boundary, + /// Multipart stream is incomplete + #[display(fmt = "Multipart stream is incomplete")] + Incomplete, + /// Error during field parsing + #[display(fmt = "{}", _0)] + Parse(ParseError), + /// Payload error + #[display(fmt = "{}", _0)] + Payload(PayloadError), +} + +/// Return `BadRequest` for `MultipartError` +impl ResponseError for MultipartError { + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST) + } +} + #[cfg(test)] mod tests { use super::*; @@ -172,4 +202,10 @@ mod tests { let resp: HttpResponse = ReadlinesError::EncodingError.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } + + #[test] + fn test_multipart_error() { + let resp: HttpResponse = MultipartError::Boundary.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } } diff --git a/src/test.rs b/src/test.rs index 9e1f01f9..4fdb6ea3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -49,11 +49,12 @@ where /// /// Note that this function is intended to be used only for testing purpose. /// This function panics on nested call. -pub fn run_on(f: F) -> Result +pub fn run_on(f: F) -> R where - F: Fn() -> Result, + F: Fn() -> R, { - RT.with(move |rt| rt.borrow_mut().block_on(lazy(f))) + RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f())))) + .unwrap() } pub fn ok_service() -> impl Service< diff --git a/src/types/mod.rs b/src/types/mod.rs index 30ee7309..c9aed94f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod form; pub(crate) mod json; +mod multipart; mod path; pub(crate) mod payload; mod query; @@ -9,6 +10,7 @@ pub(crate) mod readlines; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; +pub use self::multipart::{Multipart, MultipartItem}; pub use self::path::Path; pub use self::payload::{Payload, PayloadConfig}; pub use self::query::Query; diff --git a/src/types/multipart.rs b/src/types/multipart.rs new file mode 100644 index 00000000..d66053ff --- /dev/null +++ b/src/types/multipart.rs @@ -0,0 +1,1137 @@ +//! Multipart payload support +use std::cell::{RefCell, UnsafeCell}; +use std::collections::VecDeque; +use std::marker::PhantomData; +use std::rc::Rc; +use std::{cmp, fmt}; + +use bytes::{Bytes, BytesMut}; +use futures::task::{current as current_task, Task}; +use futures::{Async, Poll, Stream}; +use httparse; +use mime; + +use crate::error::{Error, MultipartError, ParseError, PayloadError}; +use crate::extract::FromRequest; +use crate::http::header::{ + self, ContentDisposition, HeaderMap, HeaderName, HeaderValue, +}; +use crate::http::HttpTryFrom; +use crate::service::ServiceFromRequest; +use crate::HttpMessage; + +const MAX_HEADERS: usize = 32; + +/// The server-side implementation of `multipart/form-data` requests. +/// +/// This will parse the incoming stream into `MultipartItem` instances via its +/// Stream implementation. +/// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` +/// is used for nested multipart streams. +pub struct Multipart { + safety: Safety, + error: Option, + inner: Option>>, +} + +/// Multipart item +pub enum MultipartItem { + /// Multipart field + Field(Field), + /// Nested multipart stream + Nested(Multipart), +} + +/// Get request's payload as multipart stream +/// +/// Content-type: multipart/form-data; +/// +/// ## Server example +/// +/// ```rust +/// # use futures::{Future, Stream}; +/// # use futures::future::{ok, result, Either}; +/// use actix_web::{web, HttpResponse, Error}; +/// +/// fn index(payload: web::Multipart) -> impl Future { +/// payload.from_err() // <- get multipart stream for current request +/// .and_then(|item| match item { // <- iterate over multipart items +/// web::MultipartItem::Field(field) => { +/// // Field in turn is stream of *Bytes* object +/// Either::A(field.from_err() +/// .fold((), |_, chunk| { +/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk)); +/// Ok::<_, Error>(()) +/// })) +/// }, +/// web::MultipartItem::Nested(mp) => { +/// // Or item could be nested Multipart stream +/// Either::B(ok(())) +/// } +/// }) +/// .fold((), |_, _| Ok::<_, Error>(())) +/// .map(|_| HttpResponse::Ok().into()) +/// } +/// # fn main() {} +/// ``` +impl

    FromRequest

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

    ) -> Self::Future { + let pl = req.take_payload(); + Ok(Multipart::new(req.headers(), pl)) + } +} + +enum InnerMultipartItem { + None, + Field(Rc>), + Multipart(Rc>), +} + +#[derive(PartialEq, Debug)] +enum InnerState { + /// Stream eof + Eof, + /// Skip data until first boundary + FirstBoundary, + /// Reading boundary + Boundary, + /// Reading Headers, + Headers, +} + +struct InnerMultipart { + payload: PayloadRef, + boundary: String, + state: InnerState, + item: InnerMultipartItem, +} + +impl Multipart { + /// Create multipart instance for boundary. + pub fn new(headers: &HeaderMap, stream: S) -> Multipart + where + S: Stream + 'static, + { + match Self::boundary(headers) { + Ok(boundary) => Multipart { + error: None, + safety: Safety::new(), + inner: Some(Rc::new(RefCell::new(InnerMultipart { + boundary, + payload: PayloadRef::new(PayloadBuffer::new(stream)), + state: InnerState::FirstBoundary, + item: InnerMultipartItem::None, + }))), + }, + Err(err) => Multipart { + error: Some(err), + safety: Safety::new(), + inner: None, + }, + } + } + + /// Extract boundary info from headers. + fn boundary(headers: &HeaderMap) -> Result { + if let Some(content_type) = headers.get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + if let Ok(ct) = content_type.parse::() { + if let Some(boundary) = ct.get_param(mime::BOUNDARY) { + Ok(boundary.as_str().to_owned()) + } else { + Err(MultipartError::Boundary) + } + } else { + Err(MultipartError::ParseContentType) + } + } else { + Err(MultipartError::ParseContentType) + } + } else { + Err(MultipartError::NoContentType) + } + } +} + +impl Stream for Multipart { + type Item = MultipartItem; + type Error = MultipartError; + + fn poll(&mut self) -> Poll, Self::Error> { + if let Some(err) = self.error.take() { + Err(err) + } else if self.safety.current() { + self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) + } else { + Ok(Async::NotReady) + } + } +} + +impl InnerMultipart { + fn read_headers(payload: &mut PayloadBuffer) -> Poll { + match payload.read_until(b"\r\n\r\n")? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(None) => Err(MultipartError::Incomplete), + Async::Ready(Some(bytes)) => { + let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; + match httparse::parse_headers(&bytes, &mut hdrs) { + Ok(httparse::Status::Complete((_, hdrs))) => { + // convert headers + let mut headers = HeaderMap::with_capacity(hdrs.len()); + for h in hdrs { + if let Ok(name) = HeaderName::try_from(h.name) { + if let Ok(value) = HeaderValue::try_from(h.value) { + headers.append(name, value); + } else { + return Err(ParseError::Header.into()); + } + } else { + return Err(ParseError::Header.into()); + } + } + Ok(Async::Ready(headers)) + } + Ok(httparse::Status::Partial) => Err(ParseError::Header.into()), + Err(err) => Err(ParseError::from(err).into()), + } + } + } + } + + fn read_boundary( + payload: &mut PayloadBuffer, + boundary: &str, + ) -> Poll { + // TODO: need to read epilogue + match payload.readline()? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(None) => Err(MultipartError::Incomplete), + Async::Ready(Some(chunk)) => { + if chunk.len() == boundary.len() + 4 + && &chunk[..2] == b"--" + && &chunk[2..boundary.len() + 2] == boundary.as_bytes() + { + Ok(Async::Ready(false)) + } else if chunk.len() == boundary.len() + 6 + && &chunk[..2] == b"--" + && &chunk[2..boundary.len() + 2] == boundary.as_bytes() + && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" + { + Ok(Async::Ready(true)) + } else { + Err(MultipartError::Boundary) + } + } + } + } + + fn skip_until_boundary( + payload: &mut PayloadBuffer, + boundary: &str, + ) -> Poll { + let mut eof = false; + loop { + match payload.readline()? { + Async::Ready(Some(chunk)) => { + if chunk.is_empty() { + //ValueError("Could not find starting boundary %r" + //% (self._boundary)) + } + if chunk.len() < boundary.len() { + continue; + } + if &chunk[..2] == b"--" + && &chunk[2..chunk.len() - 2] == boundary.as_bytes() + { + break; + } else { + if chunk.len() < boundary.len() + 2 { + continue; + } + let b: &[u8] = boundary.as_ref(); + if &chunk[..boundary.len()] == b + && &chunk[boundary.len()..boundary.len() + 2] == b"--" + { + eof = true; + break; + } + } + } + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(None) => return Err(MultipartError::Incomplete), + } + } + Ok(Async::Ready(eof)) + } + + fn poll(&mut self, safety: &Safety) -> Poll, MultipartError> { + if self.state == InnerState::Eof { + Ok(Async::Ready(None)) + } else { + // release field + loop { + // Nested multipart streams of fields has to be consumed + // before switching to next + if safety.current() { + let stop = match self.item { + InnerMultipartItem::Field(ref mut field) => { + match field.borrow_mut().poll(safety)? { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(Some(_)) => continue, + Async::Ready(None) => true, + } + } + InnerMultipartItem::Multipart(ref mut multipart) => { + match multipart.borrow_mut().poll(safety)? { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(Some(_)) => continue, + Async::Ready(None) => true, + } + } + _ => false, + }; + if stop { + self.item = InnerMultipartItem::None; + } + if let InnerMultipartItem::None = self.item { + break; + } + } + } + + let headers = if let Some(payload) = self.payload.get_mut(safety) { + match self.state { + // read until first boundary + InnerState::FirstBoundary => { + match InnerMultipart::skip_until_boundary( + payload, + &self.boundary, + )? { + Async::Ready(eof) => { + if eof { + self.state = InnerState::Eof; + return Ok(Async::Ready(None)); + } else { + self.state = InnerState::Headers; + } + } + Async::NotReady => return Ok(Async::NotReady), + } + } + // read boundary + InnerState::Boundary => { + match InnerMultipart::read_boundary(payload, &self.boundary)? { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(eof) => { + if eof { + self.state = InnerState::Eof; + return Ok(Async::Ready(None)); + } else { + self.state = InnerState::Headers; + } + } + } + } + _ => (), + } + + // read field headers for next field + if self.state == InnerState::Headers { + if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? + { + self.state = InnerState::Boundary; + headers + } else { + return Ok(Async::NotReady); + } + } else { + unreachable!() + } + } else { + log::debug!("NotReady: field is in flight"); + return Ok(Async::NotReady); + }; + + // content type + let mut mt = mime::APPLICATION_OCTET_STREAM; + if let Some(content_type) = headers.get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + if let Ok(ct) = content_type.parse::() { + mt = ct; + } + } + } + + self.state = InnerState::Boundary; + + // nested multipart stream + if mt.type_() == mime::MULTIPART { + let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) { + Rc::new(RefCell::new(InnerMultipart { + payload: self.payload.clone(), + boundary: boundary.as_str().to_owned(), + state: InnerState::FirstBoundary, + item: InnerMultipartItem::None, + })) + } else { + return Err(MultipartError::Boundary); + }; + + self.item = InnerMultipartItem::Multipart(Rc::clone(&inner)); + + Ok(Async::Ready(Some(MultipartItem::Nested(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, + )?)); + self.item = InnerMultipartItem::Field(Rc::clone(&field)); + + Ok(Async::Ready(Some(MultipartItem::Field(Field::new( + safety.clone(), + headers, + mt, + field, + ))))) + } + } + } +} + +impl Drop for InnerMultipart { + fn drop(&mut self) { + // InnerMultipartItem::Field has to be dropped first because of Safety. + self.item = InnerMultipartItem::None; + } +} + +/// A single field in a multipart stream +pub struct Field { + ct: mime::Mime, + headers: HeaderMap, + inner: Rc>, + safety: Safety, +} + +impl Field { + fn new( + safety: Safety, + headers: HeaderMap, + ct: mime::Mime, + inner: Rc>, + ) -> Self { + Field { + ct, + headers, + inner, + safety, + } + } + + /// Get a map of headers + pub fn headers(&self) -> &HeaderMap { + &self.headers + } + + /// Get the content type of the field + pub fn content_type(&self) -> &mime::Mime { + &self.ct + } + + /// Get the content disposition of the field, if it exists + pub fn content_disposition(&self) -> Option { + // RFC 7578: 'Each part MUST contain a Content-Disposition header field + // where the disposition type is "form-data".' + if let Some(content_disposition) = self.headers.get(header::CONTENT_DISPOSITION) + { + ContentDisposition::from_raw(content_disposition).ok() + } else { + None + } + } +} + +impl Stream for Field { + type Item = Bytes; + type Error = MultipartError; + + fn poll(&mut self) -> Poll, Self::Error> { + if self.safety.current() { + self.inner.borrow_mut().poll(&self.safety) + } else { + Ok(Async::NotReady) + } + } +} + +impl fmt::Debug for Field { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "\nMultipartField: {}", self.ct)?; + writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; + writeln!(f, " headers:")?; + for (key, val) in self.headers.iter() { + writeln!(f, " {:?}: {:?}", key, val)?; + } + Ok(()) + } +} + +struct InnerField { + payload: Option, + boundary: String, + eof: bool, + length: Option, +} + +impl InnerField { + fn new( + payload: PayloadRef, + boundary: String, + headers: &HeaderMap, + ) -> Result { + let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + Some(len) + } else { + return Err(PayloadError::Incomplete(None)); + } + } else { + return Err(PayloadError::Incomplete(None)); + } + } else { + None + }; + + Ok(InnerField { + boundary, + payload: Some(payload), + eof: false, + length: len, + }) + } + + /// Reads body part content chunk of the specified size. + /// The body part must has `Content-Length` header with proper value. + fn read_len( + payload: &mut PayloadBuffer, + size: &mut u64, + ) -> Poll, MultipartError> { + if *size == 0 { + Ok(Async::Ready(None)) + } else { + match payload.readany() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(None)) => Err(MultipartError::Incomplete), + Ok(Async::Ready(Some(mut chunk))) => { + let len = cmp::min(chunk.len() as u64, *size); + *size -= len; + let ch = chunk.split_to(len as usize); + if !chunk.is_empty() { + payload.unprocessed(chunk); + } + Ok(Async::Ready(Some(ch))) + } + Err(err) => Err(err.into()), + } + } + } + + /// Reads content chunk of body part with unknown length. + /// The `Content-Length` header for body part is not necessary. + fn read_stream( + payload: &mut PayloadBuffer, + boundary: &str, + ) -> Poll, MultipartError> { + match payload.read_until(b"\r")? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(None) => Err(MultipartError::Incomplete), + Async::Ready(Some(mut chunk)) => { + if chunk.len() == 1 { + payload.unprocessed(chunk); + match payload.read_exact(boundary.len() + 4)? { + Async::NotReady => Ok(Async::NotReady), + Async::Ready(None) => Err(MultipartError::Incomplete), + Async::Ready(Some(mut chunk)) => { + if &chunk[..2] == b"\r\n" + && &chunk[2..4] == b"--" + && &chunk[4..] == boundary.as_bytes() + { + payload.unprocessed(chunk); + Ok(Async::Ready(None)) + } else { + // \r might be part of data stream + let ch = chunk.split_to(1); + payload.unprocessed(chunk); + Ok(Async::Ready(Some(ch))) + } + } + } + } else { + let to = chunk.len() - 1; + let ch = chunk.split_to(to); + payload.unprocessed(chunk); + Ok(Async::Ready(Some(ch))) + } + } + } + } + + fn poll(&mut self, s: &Safety) -> Poll, MultipartError> { + if self.payload.is_none() { + return Ok(Async::Ready(None)); + } + + let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { + let res = if let Some(ref mut len) = self.length { + InnerField::read_len(payload, len)? + } else { + InnerField::read_stream(payload, &self.boundary)? + }; + + match res { + Async::NotReady => Async::NotReady, + Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), + Async::Ready(None) => { + self.eof = true; + match payload.readline()? { + Async::NotReady => Async::NotReady, + Async::Ready(None) => Async::Ready(None), + Async::Ready(Some(line)) => { + if line.as_ref() != b"\r\n" { + log::warn!("multipart field did not read all the data or it is malformed"); + } + Async::Ready(None) + } + } + } + } + } else { + Async::NotReady + }; + + if Async::Ready(None) == result { + self.payload.take(); + } + Ok(result) + } +} + +struct PayloadRef { + payload: Rc>, +} + +impl PayloadRef { + fn new(payload: PayloadBuffer) -> PayloadRef { + PayloadRef { + payload: Rc::new(payload.into()), + } + } + + fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer> + where + 'a: 'b, + { + // Unsafe: Invariant is inforced by Safety Safety is used as ref counter, + // only top most ref can have mutable access to payload. + if s.current() { + let payload: &mut PayloadBuffer = unsafe { &mut *self.payload.get() }; + Some(payload) + } else { + None + } + } +} + +impl Clone for PayloadRef { + fn clone(&self) -> PayloadRef { + PayloadRef { + payload: Rc::clone(&self.payload), + } + } +} + +/// Counter. It tracks of number of clones of payloads and give access to +/// payload only to top most task panics if Safety get destroyed and it not top +/// most task. +#[derive(Debug)] +struct Safety { + task: Option, + level: usize, + payload: Rc>, +} + +impl Safety { + fn new() -> Safety { + let payload = Rc::new(PhantomData); + Safety { + task: None, + level: Rc::strong_count(&payload), + payload, + } + } + + fn current(&self) -> bool { + Rc::strong_count(&self.payload) == self.level + } +} + +impl Clone for Safety { + fn clone(&self) -> Safety { + let payload = Rc::clone(&self.payload); + Safety { + task: Some(current_task()), + level: Rc::strong_count(&payload), + payload, + } + } +} + +impl Drop for Safety { + fn drop(&mut self) { + // parent task is dead + if Rc::strong_count(&self.payload) != self.level { + panic!("Safety get dropped but it is not from top-most task"); + } + if let Some(task) = self.task.take() { + task.notify() + } + } +} + +/// Payload buffer +pub struct PayloadBuffer { + len: usize, + items: VecDeque, + stream: Box>, +} + +impl PayloadBuffer { + /// Create new `PayloadBuffer` instance + pub fn new(stream: S) -> Self + where + S: Stream + 'static, + { + PayloadBuffer { + len: 0, + items: VecDeque::new(), + stream: Box::new(stream), + } + } + + #[inline] + fn poll_stream(&mut self) -> Poll { + self.stream.poll().map(|res| match res { + Async::Ready(Some(data)) => { + self.len += data.len(); + self.items.push_back(data); + Async::Ready(true) + } + Async::Ready(None) => Async::Ready(false), + Async::NotReady => Async::NotReady, + }) + } + + /// Read first available chunk of bytes + #[inline] + pub fn readany(&mut self) -> Poll, PayloadError> { + if let Some(data) = self.items.pop_front() { + self.len -= data.len(); + Ok(Async::Ready(Some(data))) + } else { + match self.poll_stream()? { + Async::Ready(true) => self.readany(), + Async::Ready(false) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } + } + } + + /// Read exact number of bytes + #[inline] + pub fn read_exact(&mut self, size: usize) -> Poll, PayloadError> { + if size <= self.len { + self.len -= size; + let mut chunk = self.items.pop_front().unwrap(); + if size < chunk.len() { + let buf = chunk.split_to(size); + self.items.push_front(chunk); + Ok(Async::Ready(Some(buf))) + } else if size == chunk.len() { + Ok(Async::Ready(Some(chunk))) + } else { + let mut buf = BytesMut::with_capacity(size); + buf.extend_from_slice(&chunk); + + while buf.len() < size { + let mut chunk = self.items.pop_front().unwrap(); + let rem = cmp::min(size - buf.len(), chunk.len()); + buf.extend_from_slice(&chunk.split_to(rem)); + if !chunk.is_empty() { + self.items.push_front(chunk); + } + } + Ok(Async::Ready(Some(buf.freeze()))) + } + } else { + match self.poll_stream()? { + Async::Ready(true) => self.read_exact(size), + Async::Ready(false) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } + } + } + + /// Read until specified ending + pub fn read_until(&mut self, line: &[u8]) -> Poll, PayloadError> { + let mut idx = 0; + let mut num = 0; + let mut offset = 0; + let mut found = false; + let mut length = 0; + + for no in 0..self.items.len() { + { + let chunk = &self.items[no]; + for (pos, ch) in chunk.iter().enumerate() { + if *ch == line[idx] { + idx += 1; + if idx == line.len() { + num = no; + offset = pos + 1; + length += pos + 1; + found = true; + break; + } + } else { + idx = 0 + } + } + if !found { + length += chunk.len() + } + } + + if found { + let mut buf = BytesMut::with_capacity(length); + if num > 0 { + for _ in 0..num { + buf.extend_from_slice(&self.items.pop_front().unwrap()); + } + } + if offset > 0 { + let mut chunk = self.items.pop_front().unwrap(); + buf.extend_from_slice(&chunk.split_to(offset)); + if !chunk.is_empty() { + self.items.push_front(chunk) + } + } + self.len -= length; + return Ok(Async::Ready(Some(buf.freeze()))); + } + } + + match self.poll_stream()? { + Async::Ready(true) => self.read_until(line), + Async::Ready(false) => Ok(Async::Ready(None)), + Async::NotReady => Ok(Async::NotReady), + } + } + + /// Read bytes until new line delimiter + pub fn readline(&mut self) -> Poll, PayloadError> { + self.read_until(b"\n") + } + + /// Put unprocessed data back to the buffer + pub fn unprocessed(&mut self, data: Bytes) { + self.len += data.len(); + self.items.push_front(data); + } +} + +#[cfg(test)] +mod tests { + use bytes::Bytes; + use futures::unsync::mpsc; + + use super::*; + use crate::http::header::{DispositionParam, DispositionType}; + use crate::test::run_on; + + #[test] + fn test_boundary() { + let headers = HeaderMap::new(); + match Multipart::boundary(&headers) { + Err(MultipartError::NoContentType) => (), + _ => unreachable!("should not happen"), + } + + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("test"), + ); + + match Multipart::boundary(&headers) { + Err(MultipartError::ParseContentType) => (), + _ => unreachable!("should not happen"), + } + + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("multipart/mixed"), + ); + match Multipart::boundary(&headers) { + Err(MultipartError::Boundary) => (), + _ => unreachable!("should not happen"), + } + + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static( + "multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"", + ), + ); + + assert_eq!( + Multipart::boundary(&headers).unwrap(), + "5c02368e880e436dab70ed54e1c58209" + ); + } + + fn create_stream() -> ( + mpsc::UnboundedSender>, + impl Stream, + ) { + let (tx, rx) = mpsc::unbounded(); + + (tx, rx.map_err(|_| panic!()).and_then(|res| res)) + } + + #[test] + fn test_multipart() { + run_on(|| { + let (sender, payload) = create_stream(); + + let bytes = Bytes::from( + "testasdadsad\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ + Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ + test\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ + data\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n", + ); + sender.unbounded_send(Ok(bytes)).unwrap(); + + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static( + "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", + ), + ); + + let mut multipart = Multipart::new(&headers, payload); + match multipart.poll() { + Ok(Async::Ready(Some(item))) => match item { + MultipartItem::Field(mut field) => { + { + let cd = field.content_disposition().unwrap(); + assert_eq!(cd.disposition, DispositionType::FormData); + assert_eq!( + cd.parameters[0], + DispositionParam::Name("file".into()) + ); + } + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); + + match field.poll() { + Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "test"), + _ => unreachable!(), + } + match field.poll() { + Ok(Async::Ready(None)) => (), + _ => unreachable!(), + } + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + + match multipart.poll() { + Ok(Async::Ready(Some(item))) => match item { + MultipartItem::Field(mut field) => { + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); + + match field.poll() { + Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"), + _ => unreachable!(), + } + match field.poll() { + Ok(Async::Ready(None)) => (), + _ => unreachable!(), + } + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + + match multipart.poll() { + Ok(Async::Ready(None)) => (), + _ => unreachable!(), + } + }); + } + + #[test] + fn test_basic() { + run_on(|| { + let (_sender, payload) = create_stream(); + { + let mut payload = PayloadBuffer::new(payload); + assert_eq!(payload.len, 0); + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + } + }); + } + + #[test] + fn test_eof() { + run_on(|| { + let (sender, payload) = create_stream(); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + sender.unbounded_send(Ok(Bytes::from("data"))).unwrap(); + drop(sender); + + assert_eq!( + Async::Ready(Some(Bytes::from("data"))), + payload.readany().ok().unwrap() + ); + assert_eq!(payload.len, 0); + assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); + }); + } + + #[test] + fn test_err() { + run_on(|| { + let (sender, payload) = create_stream(); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); + + sender + .unbounded_send(Err(PayloadError::Incomplete(None))) + .unwrap(); + payload.readany().err().unwrap(); + }); + } + + #[test] + fn test_readany() { + run_on(|| { + let (sender, payload) = create_stream(); + let mut payload = PayloadBuffer::new(payload); + + sender.unbounded_send(Ok(Bytes::from("line1"))).unwrap(); + sender.unbounded_send(Ok(Bytes::from("line2"))).unwrap(); + + assert_eq!( + Async::Ready(Some(Bytes::from("line1"))), + payload.readany().ok().unwrap() + ); + assert_eq!(payload.len, 0); + + assert_eq!( + Async::Ready(Some(Bytes::from("line2"))), + payload.readany().ok().unwrap() + ); + assert_eq!(payload.len, 0); + }); + } + + #[test] + fn test_readexactly() { + run_on(|| { + let (sender, payload) = create_stream(); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); + + sender.unbounded_send(Ok(Bytes::from("line1"))).unwrap(); + sender.unbounded_send(Ok(Bytes::from("line2"))).unwrap(); + + assert_eq!( + Async::Ready(Some(Bytes::from_static(b"li"))), + payload.read_exact(2).ok().unwrap() + ); + assert_eq!(payload.len, 3); + + assert_eq!( + Async::Ready(Some(Bytes::from_static(b"ne1l"))), + payload.read_exact(4).ok().unwrap() + ); + assert_eq!(payload.len, 4); + + sender + .unbounded_send(Err(PayloadError::Incomplete(None))) + .unwrap(); + payload.read_exact(10).err().unwrap(); + }); + } + + #[test] + fn test_readuntil() { + run_on(|| { + let (sender, payload) = create_stream(); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); + + sender.unbounded_send(Ok(Bytes::from("line1"))).unwrap(); + sender.unbounded_send(Ok(Bytes::from("line2"))).unwrap(); + + assert_eq!( + Async::Ready(Some(Bytes::from("line"))), + payload.read_until(b"ne").ok().unwrap() + ); + assert_eq!(payload.len, 1); + + assert_eq!( + Async::Ready(Some(Bytes::from("1line2"))), + payload.read_until(b"2").ok().unwrap() + ); + assert_eq!(payload.len, 0); + + sender + .unbounded_send(Err(PayloadError::Incomplete(None))) + .unwrap(); + payload.read_until(b"b").err().unwrap(); + }); + } +} From 6e0fe7db2dcd737582b05673028d098ef9a0c58a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 05:16:43 -0700 Subject: [PATCH 1146/1635] use actix-threadpool for blocking calls --- Cargo.toml | 3 ++- src/error.rs | 8 ++++---- src/lib.rs | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 63a607e2..5a75e9ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,11 +70,12 @@ actix-codec = "0.1.1" actix-service = "0.3.4" actix-utils = "0.3.4" actix-router = "0.1.0" -actix-rt = "0.2.1" +actix-rt = "0.2.2" actix-web-codegen = { path="actix-web-codegen" } actix-http = { path = "actix-http", features=["fail"] } actix-server = "0.4.1" actix-server-config = "0.1.0" +actix-threadpool = "0.1.0" awc = { path = "awc", optional = true } bytes = "0.4" diff --git a/src/error.rs b/src/error.rs index f7610d50..984b46e0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,11 +37,11 @@ pub enum BlockingError { impl ResponseError for BlockingError {} -impl From> for BlockingError { - fn from(err: actix_rt::blocking::BlockingError) -> Self { +impl From> for BlockingError { + fn from(err: actix_threadpool::BlockingError) -> Self { match err { - actix_rt::blocking::BlockingError::Error(e) => BlockingError::Error(e), - actix_rt::blocking::BlockingError::Canceled => BlockingError::Canceled, + actix_threadpool::BlockingError::Error(e) => BlockingError::Error(e), + actix_threadpool::BlockingError::Canceled => BlockingError::Canceled, } } } diff --git a/src/lib.rs b/src/lib.rs index d3d66c61..ec5a9e6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,7 +162,6 @@ pub mod dev { pub mod web { //! Various types use actix_http::{http::Method, Response}; - use actix_rt::blocking; use futures::{Future, IntoFuture}; pub use actix_http::Response as HttpResponse; @@ -339,7 +338,7 @@ pub mod web { I: Send + 'static, E: Send + std::fmt::Debug + 'static, { - blocking::run(f).from_err() + actix_threadpool::run(f).from_err() } use actix_service::{fn_transform, Service, Transform}; From e84c95968fe89a47475309e3fad5e7f9c7b1a1cd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 05:34:33 -0700 Subject: [PATCH 1147/1635] reuse PayloadBuffer from actix-http --- actix-http/src/error.rs | 12 +- actix-http/src/h1/payload.rs | 9 -- src/types/multipart.rs | 288 +---------------------------------- 3 files changed, 12 insertions(+), 297 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index a026fe9d..4329970d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -254,7 +254,10 @@ impl From for ParseError { /// A set of errors that can occur during payload parsing pub enum PayloadError { /// A payload reached EOF, but is not complete. - #[display(fmt = "A payload reached EOF, but is not complete.")] + #[display( + fmt = "A payload reached EOF, but is not complete. With error: {:?}", + _0 + )] Incomplete(Option), /// Content encoding stream corruption #[display(fmt = "Can not decode content-encoding.")] @@ -909,13 +912,12 @@ mod tests { fn test_payload_error() { let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into(); - assert_eq!(format!("{}", err), "ParseError"); - assert_eq!(format!("{}", err.cause().unwrap()), "ParseError"); + assert!(format!("{}", err).contains("ParseError")); - let err = PayloadError::Incomplete; + let err = PayloadError::Incomplete(None); assert_eq!( format!("{}", err), - "A payload reached EOF, but is not complete." + "A payload reached EOF, but is not complete. With error: None" ); } diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 979dd015..73d05c4b 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -502,15 +502,6 @@ mod tests { use actix_rt::Runtime; use futures::future::{lazy, result}; - #[test] - fn test_error() { - let err = PayloadError::Incomplete(None); - assert_eq!( - format!("{}", err), - "A payload reached EOF, but is not complete." - ); - } - #[test] fn test_basic() { Runtime::new() diff --git a/src/types/multipart.rs b/src/types/multipart.rs index d66053ff..50ef3813 100644 --- a/src/types/multipart.rs +++ b/src/types/multipart.rs @@ -1,11 +1,10 @@ //! Multipart payload support use std::cell::{RefCell, UnsafeCell}; -use std::collections::VecDeque; use std::marker::PhantomData; use std::rc::Rc; use std::{cmp, fmt}; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use futures::task::{current as current_task, Task}; use futures::{Async, Poll, Stream}; use httparse; @@ -22,6 +21,9 @@ use crate::HttpMessage; const MAX_HEADERS: usize = 32; +type PayloadBuffer = + actix_http::h1::PayloadBuffer>>; + /// The server-side implementation of `multipart/form-data` requests. /// /// This will parse the incoming stream into `MultipartItem` instances via its @@ -125,7 +127,7 @@ impl Multipart { safety: Safety::new(), inner: Some(Rc::new(RefCell::new(InnerMultipart { boundary, - payload: PayloadRef::new(PayloadBuffer::new(stream)), + payload: PayloadRef::new(PayloadBuffer::new(Box::new(stream))), state: InnerState::FirstBoundary, item: InnerMultipartItem::None, }))), @@ -712,157 +714,6 @@ impl Drop for Safety { } } -/// Payload buffer -pub struct PayloadBuffer { - len: usize, - items: VecDeque, - stream: Box>, -} - -impl PayloadBuffer { - /// Create new `PayloadBuffer` instance - pub fn new(stream: S) -> Self - where - S: Stream + 'static, - { - PayloadBuffer { - len: 0, - items: VecDeque::new(), - stream: Box::new(stream), - } - } - - #[inline] - fn poll_stream(&mut self) -> Poll { - self.stream.poll().map(|res| match res { - Async::Ready(Some(data)) => { - self.len += data.len(); - self.items.push_back(data); - Async::Ready(true) - } - Async::Ready(None) => Async::Ready(false), - Async::NotReady => Async::NotReady, - }) - } - - /// Read first available chunk of bytes - #[inline] - pub fn readany(&mut self) -> Poll, PayloadError> { - if let Some(data) = self.items.pop_front() { - self.len -= data.len(); - Ok(Async::Ready(Some(data))) - } else { - match self.poll_stream()? { - Async::Ready(true) => self.readany(), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Read exact number of bytes - #[inline] - pub fn read_exact(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - self.len -= size; - let mut chunk = self.items.pop_front().unwrap(); - if size < chunk.len() { - let buf = chunk.split_to(size); - self.items.push_front(chunk); - Ok(Async::Ready(Some(buf))) - } else if size == chunk.len() { - Ok(Async::Ready(Some(chunk))) - } else { - let mut buf = BytesMut::with_capacity(size); - buf.extend_from_slice(&chunk); - - while buf.len() < size { - let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size - buf.len(), chunk.len()); - buf.extend_from_slice(&chunk.split_to(rem)); - if !chunk.is_empty() { - self.items.push_front(chunk); - } - } - Ok(Async::Ready(Some(buf.freeze()))) - } - } else { - match self.poll_stream()? { - Async::Ready(true) => self.read_exact(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Read until specified ending - pub fn read_until(&mut self, line: &[u8]) -> Poll, PayloadError> { - let mut idx = 0; - let mut num = 0; - let mut offset = 0; - let mut found = false; - let mut length = 0; - - for no in 0..self.items.len() { - { - let chunk = &self.items[no]; - for (pos, ch) in chunk.iter().enumerate() { - if *ch == line[idx] { - idx += 1; - if idx == line.len() { - num = no; - offset = pos + 1; - length += pos + 1; - found = true; - break; - } - } else { - idx = 0 - } - } - if !found { - length += chunk.len() - } - } - - if found { - let mut buf = BytesMut::with_capacity(length); - if num > 0 { - for _ in 0..num { - buf.extend_from_slice(&self.items.pop_front().unwrap()); - } - } - if offset > 0 { - let mut chunk = self.items.pop_front().unwrap(); - buf.extend_from_slice(&chunk.split_to(offset)); - if !chunk.is_empty() { - self.items.push_front(chunk) - } - } - self.len -= length; - return Ok(Async::Ready(Some(buf.freeze()))); - } - } - - match self.poll_stream()? { - Async::Ready(true) => self.read_until(line), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - - /// Read bytes until new line delimiter - pub fn readline(&mut self) -> Poll, PayloadError> { - self.read_until(b"\n") - } - - /// Put unprocessed data back to the buffer - pub fn unprocessed(&mut self, data: Bytes) { - self.len += data.len(); - self.items.push_front(data); - } -} - #[cfg(test)] mod tests { use bytes::Bytes; @@ -1005,133 +856,4 @@ mod tests { } }); } - - #[test] - fn test_basic() { - run_on(|| { - let (_sender, payload) = create_stream(); - { - let mut payload = PayloadBuffer::new(payload); - assert_eq!(payload.len, 0); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - } - }); - } - - #[test] - fn test_eof() { - run_on(|| { - let (sender, payload) = create_stream(); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - sender.unbounded_send(Ok(Bytes::from("data"))).unwrap(); - drop(sender); - - assert_eq!( - Async::Ready(Some(Bytes::from("data"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); - }); - } - - #[test] - fn test_err() { - run_on(|| { - let (sender, payload) = create_stream(); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - - sender - .unbounded_send(Err(PayloadError::Incomplete(None))) - .unwrap(); - payload.readany().err().unwrap(); - }); - } - - #[test] - fn test_readany() { - run_on(|| { - let (sender, payload) = create_stream(); - let mut payload = PayloadBuffer::new(payload); - - sender.unbounded_send(Ok(Bytes::from("line1"))).unwrap(); - sender.unbounded_send(Ok(Bytes::from("line2"))).unwrap(); - - assert_eq!( - Async::Ready(Some(Bytes::from("line1"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - - assert_eq!( - Async::Ready(Some(Bytes::from("line2"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - }); - } - - #[test] - fn test_readexactly() { - run_on(|| { - let (sender, payload) = create_stream(); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); - - sender.unbounded_send(Ok(Bytes::from("line1"))).unwrap(); - sender.unbounded_send(Ok(Bytes::from("line2"))).unwrap(); - - assert_eq!( - Async::Ready(Some(Bytes::from_static(b"li"))), - payload.read_exact(2).ok().unwrap() - ); - assert_eq!(payload.len, 3); - - assert_eq!( - Async::Ready(Some(Bytes::from_static(b"ne1l"))), - payload.read_exact(4).ok().unwrap() - ); - assert_eq!(payload.len, 4); - - sender - .unbounded_send(Err(PayloadError::Incomplete(None))) - .unwrap(); - payload.read_exact(10).err().unwrap(); - }); - } - - #[test] - fn test_readuntil() { - run_on(|| { - let (sender, payload) = create_stream(); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); - - sender.unbounded_send(Ok(Bytes::from("line1"))).unwrap(); - sender.unbounded_send(Ok(Bytes::from("line2"))).unwrap(); - - assert_eq!( - Async::Ready(Some(Bytes::from("line"))), - payload.read_until(b"ne").ok().unwrap() - ); - assert_eq!(payload.len, 1); - - assert_eq!( - Async::Ready(Some(Bytes::from("1line2"))), - payload.read_until(b"2").ok().unwrap() - ); - assert_eq!(payload.len, 0); - - sender - .unbounded_send(Err(PayloadError::Incomplete(None))) - .unwrap(); - payload.read_until(b"b").err().unwrap(); - }); - } } From 5795850bbb6dce3fd6ac0b8a7b10819a1e2500b9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 11:08:24 -0700 Subject: [PATCH 1148/1635] decompress payload in cpu threadpool --- actix-http/Cargo.toml | 3 +- actix-http/src/encoding/decoder.rs | 74 ++++++++++++++++++++---------- actix-http/src/error.rs | 40 ++++++++++++++-- awc/src/request.rs | 2 +- src/error.rs | 22 --------- src/lib.rs | 6 +-- src/middleware/decompress.rs | 2 +- 7 files changed, 91 insertions(+), 58 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index fefe05c4..cdaeb1fc 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -53,6 +53,7 @@ actix-codec = "0.1.2" actix-connect = "0.1.0" actix-utils = "0.3.4" actix-server-config = "0.1.0" +actix-threadpool = "0.1.0" base64 = "0.10" bitflags = "1.0" @@ -94,7 +95,7 @@ failure = { version = "0.1.5", optional = true } openssl = { version="0.10", optional = true } [dev-dependencies] -actix-rt = "0.2.1" +actix-rt = "0.2.2" actix-server = { version = "0.4.0", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } actix-http-test = { path="../test-server", features=["ssl"] } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 8be6702f..ae2b4ae6 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -1,27 +1,31 @@ use std::io::{self, Write}; -use bytes::Bytes; -use futures::{Async, Poll, Stream}; - +use actix_threadpool::{run, CpuFuture}; #[cfg(feature = "brotli")] use brotli2::write::BrotliDecoder; +use bytes::Bytes; #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] use flate2::write::{GzDecoder, ZlibDecoder}; +use futures::{try_ready, Async, Future, Poll, Stream}; use super::Writer; use crate::error::PayloadError; use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}; -pub struct Decoder { - stream: T, +pub struct Decoder { decoder: Option, + stream: S, + eof: bool, + fut: Option, ContentDecoder), io::Error>>, } -impl Decoder +impl Decoder where - T: Stream, + S: Stream, { - pub fn new(stream: T, encoding: ContentEncoding) -> Self { + /// Construct a decoder. + #[inline] + pub fn new(stream: S, encoding: ContentEncoding) -> Decoder { let decoder = match encoding { #[cfg(feature = "brotli")] ContentEncoding::Br => Some(ContentDecoder::Br(Box::new( @@ -37,10 +41,17 @@ where ))), _ => None, }; - Decoder { stream, decoder } + Decoder { + decoder, + stream, + fut: None, + eof: false, + } } - pub fn from_headers(headers: &HeaderMap, stream: T) -> Self { + /// Construct decoder based on headers. + #[inline] + pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder { // check content-encoding let encoding = if let Some(enc) = headers.get(CONTENT_ENCODING) { if let Ok(enc) = enc.to_str() { @@ -56,35 +67,50 @@ where } } -impl Stream for Decoder +impl Stream for Decoder where - T: Stream, + S: Stream, { type Item = Bytes; type Error = PayloadError; fn poll(&mut self) -> Poll, Self::Error> { loop { + if let Some(ref mut fut) = self.fut { + let (chunk, decoder) = try_ready!(fut.poll()); + self.decoder = Some(decoder); + self.fut.take(); + if let Some(chunk) = chunk { + return Ok(Async::Ready(Some(chunk))); + } + } + + if self.eof { + return Ok(Async::Ready(None)); + } + match self.stream.poll()? { Async::Ready(Some(chunk)) => { - if let Some(ref mut decoder) = self.decoder { - match decoder.feed_data(chunk) { - Ok(Some(chunk)) => return Ok(Async::Ready(Some(chunk))), - Ok(None) => continue, - Err(e) => return Err(e.into()), - } + if let Some(mut decoder) = self.decoder.take() { + self.fut = Some(run(move || { + let chunk = decoder.feed_data(chunk)?; + Ok((chunk, decoder)) + })); + continue; } else { return Ok(Async::Ready(Some(chunk))); } } Async::Ready(None) => { - return if let Some(mut decoder) = self.decoder.take() { - match decoder.feed_eof() { - Ok(chunk) => Ok(Async::Ready(chunk)), - Err(e) => Err(e.into()), - } + self.eof = true; + if let Some(mut decoder) = self.decoder.take() { + self.fut = Some(run(move || { + let chunk = decoder.feed_eof()?; + Ok((chunk, decoder)) + })); + continue; } else { - Ok(Async::Ready(None)) + return Ok(Async::Ready(None)); }; } Async::NotReady => break, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 4329970d..e6cc0e07 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,11 +1,11 @@ //! Error and Result module use std::cell::RefCell; -use std::io::Error as IoError; use std::str::Utf8Error; use std::string::FromUtf8Error; use std::{fmt, io, result}; // use actix::MailboxError; +pub use actix_threadpool::BlockingError; use actix_utils::timeout::TimeoutError; #[cfg(feature = "cookies")] use cookie; @@ -126,6 +126,9 @@ impl ResponseError for DeError { } } +/// `InternalServerError` for `BlockingError` +impl ResponseError for BlockingError {} + /// Return `BAD_REQUEST` for `Utf8Error` impl ResponseError for Utf8Error { fn error_response(&self) -> Response { @@ -199,7 +202,7 @@ pub enum ParseError { /// An `io::Error` that occurred while trying to read or write to a network /// stream. #[display(fmt = "IO error: {}", _0)] - Io(IoError), + Io(io::Error), /// Parsing a field as string failed #[display(fmt = "UTF8 error: {}", _0)] Utf8(Utf8Error), @@ -212,8 +215,8 @@ impl ResponseError for ParseError { } } -impl From for ParseError { - fn from(err: IoError) -> ParseError { +impl From for ParseError { + fn from(err: io::Error) -> ParseError { ParseError::Io(err) } } @@ -250,7 +253,7 @@ impl From for ParseError { } } -#[derive(Display, Debug, From)] +#[derive(Display, Debug)] /// A set of errors that can occur during payload parsing pub enum PayloadError { /// A payload reached EOF, but is not complete. @@ -271,6 +274,21 @@ pub enum PayloadError { /// Http2 payload error #[display(fmt = "{}", _0)] Http2Payload(h2::Error), + /// Io error + #[display(fmt = "{}", _0)] + Io(io::Error), +} + +impl From for PayloadError { + fn from(err: h2::Error) -> Self { + PayloadError::Http2Payload(err) + } +} + +impl From> for PayloadError { + fn from(err: Option) -> Self { + PayloadError::Incomplete(err) + } } impl From for PayloadError { @@ -279,6 +297,18 @@ impl From for PayloadError { } } +impl From> for PayloadError { + fn from(err: BlockingError) -> Self { + match err { + BlockingError::Error(e) => PayloadError::Io(e), + BlockingError::Canceled => PayloadError::Io(io::Error::new( + io::ErrorKind::Other, + "Thread pool is gone", + )), + } + } +} + /// `PayloadError` returns two possible results: /// /// - `Overflow` returns `PayloadTooLarge` diff --git a/awc/src/request.rs b/awc/src/request.rs index c0962ebf..dde51a8f 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -457,7 +457,7 @@ impl ClientRequest { .map(move |res| { res.map_body(|head, payload| { if response_decompress { - Payload::Stream(Decoder::from_headers(&head.headers, payload)) + Payload::Stream(Decoder::from_headers(payload, &head.headers)) } else { Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) } diff --git a/src/error.rs b/src/error.rs index 984b46e0..02e17241 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,4 @@ //! Error and Result module -use std::fmt; - pub use actix_http::error::*; use derive_more::{Display, From}; use serde_json::error::Error as JsonError; @@ -26,26 +24,6 @@ pub enum UrlGenerationError { /// `InternalServerError` for `UrlGeneratorError` impl ResponseError for UrlGenerationError {} -/// Blocking operation execution error -#[derive(Debug, Display)] -pub enum BlockingError { - #[display(fmt = "{:?}", _0)] - Error(E), - #[display(fmt = "Thread pool is gone")] - Canceled, -} - -impl ResponseError for BlockingError {} - -impl From> for BlockingError { - fn from(err: actix_threadpool::BlockingError) -> Self { - match err { - actix_threadpool::BlockingError::Error(e) => BlockingError::Error(e), - actix_threadpool::BlockingError::Canceled => BlockingError::Canceled, - } - } -} - /// A set of errors that can occur during parsing urlencoded payloads #[derive(Debug, Display, From)] pub enum UrlencodedError { diff --git a/src/lib.rs b/src/lib.rs index ec5a9e6a..7a4f4bfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -162,6 +162,7 @@ pub mod dev { pub mod web { //! Various types use actix_http::{http::Method, Response}; + use actix_service::{fn_transform, Service, Transform}; use futures::{Future, IntoFuture}; pub use actix_http::Response as HttpResponse; @@ -174,6 +175,7 @@ pub mod web { use crate::responder::Responder; use crate::route::Route; use crate::scope::Scope; + use crate::service::{ServiceRequest, ServiceResponse}; pub use crate::data::{Data, RouteData}; pub use crate::request::HttpRequest; @@ -341,10 +343,6 @@ pub mod web { actix_threadpool::run(f).from_err() } - use actix_service::{fn_transform, Service, Transform}; - - use crate::service::{ServiceRequest, ServiceResponse}; - /// Create middleare pub fn md( f: F, diff --git a/src/middleware/decompress.rs b/src/middleware/decompress.rs index eaffbbdb..84d35737 100644 --- a/src/middleware/decompress.rs +++ b/src/middleware/decompress.rs @@ -70,7 +70,7 @@ where fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { let (req, payload) = req.into_parts(); - let payload = Decoder::from_headers(req.headers(), payload); + let payload = Decoder::from_headers(payload, req.headers()); ok(ServiceRequest::from_parts(req, Payload::Stream(payload))) } } From 605ce051274b0432e06067e2d78e3d8649078791 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 12:32:59 -0700 Subject: [PATCH 1149/1635] App::enable_encoding() allows to enable compression and decompression --- src/app.rs | 99 +++++++++++++++++++++++++++++--------------- tests/test_server.rs | 28 +++++++++++++ 2 files changed, 94 insertions(+), 33 deletions(-) diff --git a/src/app.rs b/src/app.rs index b8efdd38..0daa54b6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,18 +3,21 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody}; +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] +use actix_http::encoding::{Decoder, Encoder}; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ ApplyTransform, IntoNewService, IntoTransform, NewService, Transform, }; -use futures::IntoFuture; +use bytes::Bytes; +use futures::{IntoFuture, Stream}; use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; use crate::config::{AppConfig, AppConfigInner}; use crate::data::{Data, DataFactory}; -use crate::dev::{PayloadStream, ResourceDef}; -use crate::error::Error; +use crate::dev::{Payload, PayloadStream, ResourceDef}; +use crate::error::{Error, PayloadError}; use crate::resource::Resource; use crate::route::Route; use crate::service::{ @@ -27,17 +30,17 @@ type HttpNewService

    = /// Application builder - structure that follows the builder pattern /// for building application instances. -pub struct App +pub struct App where - T: NewService>, + T: NewService, Response = ServiceRequest>, { chain: T, data: Vec>, config: AppConfigInner, - _t: PhantomData<(P,)>, + _t: PhantomData<(In, Out)>, } -impl App { +impl App { /// Create application builder. Application can be configured with a builder-like pattern. pub fn new() -> Self { App { @@ -49,12 +52,13 @@ impl App { } } -impl App +impl App where - P: 'static, + In: 'static, + Out: 'static, T: NewService< - Request = ServiceRequest, - Response = ServiceRequest

    , + Request = ServiceRequest, + Response = ServiceRequest, Error = Error, InitError = (), >, @@ -97,11 +101,11 @@ where /// Set application data factory. This function is /// similar to `.data()` but it accepts data factory. Data object get /// constructed asynchronously during application initialization. - pub fn data_factory(mut self, data: F) -> Self + pub fn data_factory(mut self, data: F) -> Self where - F: Fn() -> Out + 'static, - Out: IntoFuture + 'static, - Out::Error: std::fmt::Debug, + F: Fn() -> R + 'static, + R: IntoFuture + 'static, + R::Error: std::fmt::Debug, { self.data.push(Box::new(data)); self @@ -113,10 +117,10 @@ where mw: F, ) -> AppRouter< T, - P, + Out, B, impl NewService< - Request = ServiceRequest

    , + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -124,13 +128,13 @@ where > where M: Transform< - AppRouting

    , - Request = ServiceRequest

    , + AppRouting, + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, - F: IntoTransform>, + F: IntoTransform>, { let fref = Rc::new(RefCell::new(None)); let endpoint = ApplyTransform::new(mw, AppEntry::new(fref.clone())); @@ -176,17 +180,17 @@ where mw: F, ) -> AppRouter< T, - P, + Out, B, impl NewService< - Request = ServiceRequest

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

    , &mut AppRouting

    ) -> R + Clone, + F: FnMut(ServiceRequest, &mut AppRouting) -> R + Clone, R: IntoFuture, Error = Error>, { self.wrap(mw) @@ -194,22 +198,23 @@ where /// Register a request modifier. It can modify any request parameters /// including request payload type. - pub fn chain( + pub fn chain( self, chain: F, ) -> App< - P1, + In, + P, impl NewService< - Request = ServiceRequest, - Response = ServiceRequest, + Request = ServiceRequest, + Response = ServiceRequest

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

    , - Response = ServiceRequest, + Request = ServiceRequest, + Response = ServiceRequest

    , Error = Error, InitError = (), >, @@ -246,8 +251,8 @@ where pub fn route( self, path: &str, - mut route: Route

    , - ) -> AppRouter> { + mut route: Route, + ) -> AppRouter> { self.service( Resource::new(path) .add_guards(route.take_guards()) @@ -264,9 +269,9 @@ where /// * *Resource* is an entry in resource table which corresponds to requested URL. /// * *Scope* is a set of resources with common root path. /// * "StaticFiles" is a service for static files support - pub fn service(self, service: F) -> AppRouter> + pub fn service(self, service: F) -> AppRouter> where - F: HttpServiceFactory

    + 'static, + F: HttpServiceFactory + 'static, { let fref = Rc::new(RefCell::new(None)); @@ -294,6 +299,34 @@ where self.config.host = val.to_owned(); self } + + #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] + /// Enable content compression and decompression. + pub fn enable_encoding( + self, + ) -> AppRouter< + impl NewService< + Request = ServiceRequest, + Response = ServiceRequest>>, + Error = Error, + InitError = (), + >, + Decoder>, + Encoder, + impl NewService< + Request = ServiceRequest>>, + Response = ServiceResponse>, + Error = Error, + InitError = (), + >, + > + where + Out: Stream, + { + use crate::middleware::encoding::{Compress, Decompress}; + + self.chain(Decompress::new()).wrap(Compress::default()) + } } /// Application router builder - Structure that follows the builder pattern diff --git a/tests/test_server.rs b/tests/test_server.rs index 364f9262..c30cf67f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -335,6 +335,34 @@ fn test_body_brotli() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[test] +fn test_encoding() { + let mut srv = TestServer::new(move || { + HttpService::new( + App::new().enable_encoding().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); + + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + let request = srv + .post() + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + #[test] fn test_gzip_encoding() { let mut srv = TestServer::new(move || { From 9cca86e60d8d3b5ebb77ddff7c00a621206a8572 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 12:45:41 -0700 Subject: [PATCH 1150/1635] prepear actix-http release --- actix-http/CHANGES.md | 2 +- actix-http/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 74fa4a22..95ec1c35 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,5 @@ # Changes -## [0.1.0] - 2019-01-x +## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index cdaeb1fc..5862ac84 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -2,9 +2,9 @@ name = "actix-http" version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] -description = "Actix http" +description = "Actix http primitives" readme = "README.md" -keywords = ["http", "web", "framework", "async", "futures"] +keywords = ["actix", "http", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-http.git" documentation = "https://docs.rs/actix-http/" @@ -16,7 +16,7 @@ edition = "2018" workspace = ".." [package.metadata.docs.rs] -features = ["ssl", "fail", "cookie"] +features = ["ssl", "fail", "cookie", "brotli", "flate2-zlib"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } From 9c198a0d29f7827616380a9db84830643f2755ce Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 13:46:26 -0700 Subject: [PATCH 1151/1635] alpha.1 release --- CHANGES.md | 4 +- Cargo.toml | 17 ++++---- README.md | 9 ++-- actix-files/CHANGES.md | 2 +- actix-files/Cargo.toml | 10 ++--- actix-files/README.md | 83 +----------------------------------- actix-http/Cargo.toml | 2 +- actix-session/CHANGES.md | 5 +++ actix-session/Cargo.toml | 6 +-- actix-session/README.md | 1 + actix-web-actors/CHANGES.md | 5 +++ actix-web-actors/Cargo.toml | 18 ++++---- actix-web-actors/README.md | 1 + actix-web-codegen/CHANGES.md | 5 +++ actix-web-codegen/Cargo.toml | 7 +-- actix-web-codegen/README.md | 1 + awc/CHANGES.md | 5 +++ awc/Cargo.toml | 20 ++++----- awc/README.md | 1 + test-server/CHANGES.md | 5 +++ test-server/Cargo.toml | 6 +-- test-server/README.md | 1 + 22 files changed, 82 insertions(+), 132 deletions(-) create mode 100644 actix-session/CHANGES.md create mode 100644 actix-session/README.md create mode 100644 actix-web-actors/CHANGES.md create mode 100644 actix-web-actors/README.md create mode 100644 actix-web-codegen/CHANGES.md create mode 100644 actix-web-codegen/README.md create mode 100644 awc/CHANGES.md create mode 100644 awc/README.md create mode 100644 test-server/CHANGES.md create mode 100644 test-server/README.md diff --git a/CHANGES.md b/CHANGES.md index e44f5dc3..13eeb67d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,9 @@ # Changes -## [1.0.0-alpha.1] - 2019-03-x +## [1.0.0-alpha.1] - 2019-03-28 ### Changed +* Complete architecture re-design. + * Return 405 response if no matching route found within resource #538 diff --git a/Cargo.toml b/Cargo.toml index 5a75e9ac..f0bcabee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "1.0.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" -keywords = ["http", "web", "framework", "async", "futures"] +keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-web/" @@ -17,7 +17,6 @@ edition = "2018" [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } -appveyor = { repository = "fafhrd91/actix-web-hdy9d" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" } [lib] @@ -71,12 +70,12 @@ actix-service = "0.3.4" actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.2" -actix-web-codegen = { path="actix-web-codegen" } -actix-http = { path = "actix-http", features=["fail"] } +actix-web-codegen = "0.1.0-alpha.1" +actix-http = { version = "0.1.0-alpha.1", features=["fail"] } actix-server = "0.4.1" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" -awc = { path = "awc", optional = true } +awc = { version = "0.1.0-alpha.1", optional = true } bytes = "0.4" derive_more = "0.14" @@ -104,14 +103,14 @@ openssl = { version="0.10", optional = true } # rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { path = "actix-http", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { path = "test-server", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.1", features=["ssl", "brotli", "flate2-zlib"] } +actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" tokio-timer = "0.2.8" -brotli2 = { version="^0.3.2" } -flate2 = { version="^1.0.2" } +brotli2 = "^0.3.2" +flate2 = "^1.0.2" [profile.release] lto = true diff --git a/README.md b/README.md index ce9efbb7..35b886ad 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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-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 simple, pragmatic and extremely fast web framework for Rust. @@ -10,11 +10,10 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Configurable [request routing](https://actix.rs/docs/url-dispatch/) * Multipart streams * Static assets -* SSL support with OpenSSL or `native-tls` +* SSL support with OpenSSL or native-tls * Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Supports [Actix actor framework](https://github.com/actix/actix) -* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support. ## Documentation & community resources @@ -36,8 +35,8 @@ fn index(info: web::Path<(u32, String)>) -> impl Responder { fn main() -> std::io::Result<()> { HttpServer::new( - || App::new() - .service(web::resource("/{id}/{name}/index.html") + || App::new().service( + web::resource("/{id}/{name}/index.html") .route(web::get().to(index))) .bind("127.0.0.1:8080")? .run(); diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index b93e282a..95ec1c35 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,5 @@ # Changes -## [0.1.0] - 2018-10-x +## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index ba8fbb6c..d6ae6754 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -2,7 +2,7 @@ name = "actix-files" version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] -description = "Static files support for Actix web." +description = "Static files support for actix web." readme = "README.md" keywords = ["actix", "http", "async", "futures"] homepage = "https://actix.rs" @@ -18,13 +18,13 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { path=".." } -actix-http = { path="../actix-http" } +actix-web = "1.0.0-alpha.1" +actix-http = "0.1.0-alpha.1" actix-service = "0.3.3" bitflags = "1" bytes = "0.4" -futures = "0.1" +futures = "0.1.25" derive_more = "0.14" log = "0.4" mime = "0.3" @@ -33,4 +33,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { path="..", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.1", features=["ssl"] } diff --git a/actix-files/README.md b/actix-files/README.md index c7e195de..5b133f57 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -1,82 +1 @@ -# 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](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -Actix web is a simple, pragmatic and extremely fast web framework for Rust. - -* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/docs/http2/) protocols -* Streaming and pipelining -* Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/docs/websockets/) support -* Transparent content compression/decompression (br, gzip, deflate) -* Configurable [request routing](https://actix.rs/docs/url-dispatch/) -* Multipart streams -* Static assets -* SSL support with OpenSSL or `native-tls` -* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) -* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -* Built on top of [Actix actor framework](https://github.com/actix/actix) -* Experimental [Async/Await](https://github.com/mehcode/actix-web-async-await) support. - -## Documentation & community resources - -* [User Guide](https://actix.rs/docs/) -* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (Releases)](https://actix.rs/api/actix-web/stable/actix_web/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.31 or later - -## Example - -```rust -extern crate actix_web; -use actix_web::{http, server, App, Path, Responder}; - -fn index(info: Path<(u32, String)>) -> impl Responder { - format!("Hello {}! id:{}", info.1, info.0) -} - -fn main() { - server::new( - || App::new() - .route("/{id}/{name}/index.html", http::Method::GET, index)) - .bind("127.0.0.1:8080").unwrap() - .run(); -} -``` - -### More examples - -* [Basics](https://github.com/actix/examples/tree/master/basics/) -* [Stateful](https://github.com/actix/examples/tree/master/state/) -* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/) -* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/) -* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/) -* [Tera](https://github.com/actix/examples/tree/master/template_tera/) / - [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates -* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/) -* [r2d2](https://github.com/actix/examples/tree/master/r2d2/) -* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/) -* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) -* [Json](https://github.com/actix/examples/tree/master/json/) - -You may consider checking out -[this directory](https://github.com/actix/examples/tree/master/) for more examples. - -## Benchmarks - -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) - -## 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](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. - -## Code of Conduct - -Contribution to the actix-web crate is organized under the terms of the -Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to -intervene to uphold that code of conduct. +# Static files support for actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-files)](https://crates.io/crates/actix-files) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 5862ac84..07f71286 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -98,7 +98,7 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.2" actix-server = { version = "0.4.0", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } -actix-http-test = { path="../test-server", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md new file mode 100644 index 00000000..95ec1c35 --- /dev/null +++ b/actix-session/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0-alpha.1] - 2019-03-28 + +* Initial impl diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 3adcc8f5..a2b8e0e1 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -24,16 +24,16 @@ default = ["cookie-session"] cookie-session = ["cookie/secure"] [dependencies] -actix-web = { path=".." } +actix-web = "1.0.0-alpha.1" actix-service = "0.3.3" bytes = "0.4" cookie = { version="0.11", features=["percent-encode"], optional=true } derive_more = "0.14" -futures = "0.1" +futures = "0.1.25" hashbrown = "0.1.8" serde = "1.0" serde_json = "1.0" time = "0.1" [dev-dependencies] -actix-rt = "0.2.1" +actix-rt = "0.2.2" diff --git a/actix-session/README.md b/actix-session/README.md new file mode 100644 index 00000000..504fe150 --- /dev/null +++ b/actix-session/README.md @@ -0,0 +1 @@ +# Session for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-session)](https://crates.io/crates/actix-session) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md new file mode 100644 index 00000000..95ec1c35 --- /dev/null +++ b/actix-web-actors/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0-alpha.1] - 2019-03-28 + +* Initial impl diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 95b72661..c0ef89fa 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "actix-web-actors" -version = "0.1.0-alpha.1" +version = "1.0.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" -keywords = ["http", "web", "framework", "async", "futures"] +keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-web-actors/" @@ -18,14 +18,14 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix-web = { path=".." } -actix = { git = "https://github.com/actix/actix.git" } -actix-http = { path = "../actix-http/" } -actix-codec = "0.1.1" +actix = "0.8.0-alpha.1" +actix-web = "1.0.0-alpha.1" +actix-http = "0.1.0-alpha.1" +actix-codec = "0.1.2" bytes = "0.4" -futures = "0.1" +futures = "0.1.25" [dev-dependencies] env_logger = "0.6" -actix-http = { path = "../actix-http/", features=["ssl"] } -actix-http-test = { path = "../test-server/", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md new file mode 100644 index 00000000..c7099038 --- /dev/null +++ b/actix-web-actors/README.md @@ -0,0 +1 @@ +Actix actors support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-actors)](https://crates.io/crates/actix-web-actors) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md new file mode 100644 index 00000000..95ec1c35 --- /dev/null +++ b/actix-web-codegen/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0-alpha.1] - 2019-03-28 + +* Initial impl diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 3785acb3..8c9ce00c 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "actix-web-codegen" -description = "Actix web codegen macros" version = "0.1.0-alpha.1" +description = "Actix web proc macros" +readme = "README.md" authors = ["Nikolay Kim "] license = "MIT/Apache-2.0" edition = "2018" @@ -16,5 +17,5 @@ syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] actix-web = { path = ".." } -actix-http = { path = "../actix-http/", features=["ssl"] } -actix-http-test = { path = "../test-server/", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md new file mode 100644 index 00000000..c44a5fc7 --- /dev/null +++ b/actix-web-codegen/README.md @@ -0,0 +1 @@ +# Macros for actix-web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/awc/CHANGES.md b/awc/CHANGES.md new file mode 100644 index 00000000..95ec1c35 --- /dev/null +++ b/awc/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0-alpha.1] - 2019-03-28 + +* Initial impl diff --git a/awc/Cargo.toml b/awc/Cargo.toml index c915475f..65724dd7 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -2,9 +2,9 @@ name = "awc" version = "0.1.0-alpha.1" authors = ["Nikolay Kim "] -description = "Actix web client." +description = "Actix http client." readme = "README.md" -keywords = ["http", "web", "framework", "async", "futures"] +keywords = ["actix", "http", "framework", "async", "web"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/awc/" @@ -41,11 +41,11 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.1" actix-service = "0.3.4" -actix-http = { path = "../actix-http/" } +actix-http = "0.1.0-alpha.1" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" -futures = "0.1" +futures = "0.1.25" log =" 0.4" percent-encoding = "1.0" rand = "0.6" @@ -57,14 +57,14 @@ cookie = { version="0.11", features=["percent-encode"], optional = true } openssl = { version="0.10", optional = true } [dev-dependencies] -actix-rt = "0.2.1" -actix-web = { path = "..", features=["ssl"] } -actix-http = { path = "../actix-http/", features=["ssl"] } -actix-http-test = { path = "../test-server/", features=["ssl"] } +actix-rt = "0.2.2" +actix-web = { version = "1.0.0-alpha.1", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.0", features=["ssl"] } -brotli2 = { version="^0.3.2" } -flate2 = { version="^1.0.2" } +brotli2 = { version="0.3.2" } +flate2 = { version="1.0.2" } env_logger = "0.6" mime = "0.3" rand = "0.6" diff --git a/awc/README.md b/awc/README.md new file mode 100644 index 00000000..bb64559c --- /dev/null +++ b/awc/README.md @@ -0,0 +1 @@ +# Actix http client [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/awc)](https://crates.io/crates/awc) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md new file mode 100644 index 00000000..95ec1c35 --- /dev/null +++ b/test-server/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0-alpha.1] - 2019-03-28 + +* Initial impl diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 6959adbe..b6bbcf16 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -35,11 +35,11 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.1" actix-rt = "0.2.1" -actix-http = { path="../actix-http" } +actix-http = "0.1.0-alpha.1" actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" -awc = { path = "../awc" } +awc = "0.1.0-alpha.1" base64 = "0.10" bytes = "0.4" @@ -61,4 +61,4 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = { path=".." } +actix-web = "1.0.0-alpha.1" diff --git a/test-server/README.md b/test-server/README.md new file mode 100644 index 00000000..596dddf8 --- /dev/null +++ b/test-server/README.md @@ -0,0 +1 @@ +# Actix http test server [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-http-test)](https://crates.io/crates/actix-http-test) [![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) From a2c9ff3a33830fc5d3d2f9b6a04cb7e4945ab7ab Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 14:10:03 -0700 Subject: [PATCH 1152/1635] back to development --- Cargo.toml | 12 ++++++------ actix-http/Cargo.toml | 4 ++-- actix-http/tests/test_server.rs | 4 ++-- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 8 ++++---- test-server/Cargo.toml | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0bcabee..25d950a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,11 +71,11 @@ actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" -actix-http = { version = "0.1.0-alpha.1", features=["fail"] } +actix-http = { path = "actix-http", features=["fail"] } actix-server = "0.4.1" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" -awc = { version = "0.1.0-alpha.1", optional = true } +awc = { path = "awc", optional = true } bytes = "0.4" derive_more = "0.14" @@ -103,14 +103,14 @@ openssl = { version="0.10", optional = true } # rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.0-alpha.1", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } +actix-http = { path = "actix-http", features=["ssl", "brotli", "flate2-zlib"] } +actix-http-test = { path = "test-server", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" tokio-timer = "0.2.8" -brotli2 = "^0.3.2" -flate2 = "^1.0.2" +brotli2 = "0.3.2" +flate2 = "1.0.2" [profile.release] lto = true diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 07f71286..9734bd1a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -98,9 +98,9 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.2" actix-server = { version = "0.4.0", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } +actix-http-test = { path = "../test-server", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } -tokio-tcp = "0.1" \ No newline at end of file +tokio-tcp = "0.1" diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 5777c569..455edfec 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -13,8 +13,8 @@ use futures::stream::{once, Stream}; use actix_http::body::Body; use actix_http::error::PayloadError; use actix_http::{ - body, error, http, http::header, Error, HttpMessage as HttpMessage2, HttpService, - KeepAlive, Request, Response, + body, error, http, http::header, Error, HttpMessage, HttpService, KeepAlive, + Request, Response, }; fn load_body(stream: S) -> impl Future diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 8c9ce00c..1f04cfd5 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -16,6 +16,6 @@ quote = "0.6" syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] -actix-web = { path = ".." } +actix-web = { version = "1.0.0-alpha.1" } actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 65724dd7..de514299 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -41,7 +41,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.1" actix-service = "0.3.4" -actix-http = "0.1.0-alpha.1" +actix-http = { path = "../actix-http" } base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -58,9 +58,9 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-alpha.1", features=["ssl"] } -actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } +actix-web = { path = "..", features=["ssl"] } +actix-http = { path = "../actix-http", features=["ssl"] } +actix-http-test = { path = "../test-server", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.0", features=["ssl"] } brotli2 = { version="0.3.2" } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index b6bbcf16..582d96ed 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -35,11 +35,11 @@ ssl = ["openssl", "actix-http/ssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.1" actix-rt = "0.2.1" -actix-http = "0.1.0-alpha.1" +actix-http = { path = "../actix-http" } actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" -awc = "0.1.0-alpha.1" +awc = { path = "../awc" } base64 = "0.10" bytes = "0.4" @@ -61,4 +61,4 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-alpha.1" +actix-web = { path = ".." } From 878f32c4950247d49b9ffeb63385e4a133ee750c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 14:27:07 -0700 Subject: [PATCH 1153/1635] fix tests for no-default-features --- .travis.yml | 3 ++- src/app.rs | 1 + tests/test_httpserver.rs | 41 ++++++++++++++++++++++------------------ tests/test_server.rs | 24 ++++++++++++++++++++--- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 061c8b8e..7f61201f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,8 @@ before_script: - export PATH=$PATH:~/.cargo/bin script: - - cargo clean + - cargo update + - cargo check --all --no-default-features - cargo test --all-features --all -- --nocapture # Upload docs diff --git a/src/app.rs b/src/app.rs index 0daa54b6..a6dfdd55 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,6 +10,7 @@ use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ ApplyTransform, IntoNewService, IntoTransform, NewService, Transform, }; +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] use bytes::Bytes; use futures::{IntoFuture, Stream}; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 4a3850f1..bafe578e 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -2,9 +2,8 @@ use net2::TcpBuilder; use std::sync::mpsc; use std::{net, thread, time::Duration}; -use actix_http::{client, Response}; - -use actix_web::{test, web, App, HttpServer}; +use actix_http::Response; +use actix_web::{web, App, HttpServer}; fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); @@ -48,22 +47,28 @@ fn test_start() { }); let (srv, sys) = rx.recv().unwrap(); - let client = test::run_on(|| { - Ok::<_, ()>( - awc::Client::build() - .connector( - client::Connector::new() - .timeout(Duration::from_millis(100)) - .service(), - ) - .finish(), - ) - }) - .unwrap(); - let host = format!("http://{}", addr); + #[cfg(feature = "client")] + { + use actix_http::client; + use actix_web::test; - let response = test::block_on(client.get(host.clone()).send()).unwrap(); - assert!(response.status().is_success()); + let client = test::run_on(|| { + Ok::<_, ()>( + awc::Client::build() + .connector( + client::Connector::new() + .timeout(Duration::from_millis(100)) + .service(), + ) + .finish(), + ) + }) + .unwrap(); + let host = format!("http://{}", addr); + + let response = test::block_on(client.get(host.clone()).send()).unwrap(); + assert!(response.status().is_success()); + } // stop let _ = srv.stop(false); diff --git a/tests/test_server.rs b/tests/test_server.rs index c30cf67f..cd5e95d2 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,10 +11,13 @@ use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; use flate2::Compression; -use futures::stream::once; //Future, Stream +use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; -use actix_web::{dev::HttpMessageBody, middleware::encoding, web, App}; +use actix_web::{dev::HttpMessageBody, web, App}; + +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] +use actix_web::middleware::encoding; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -55,6 +58,7 @@ fn test_body() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip() { let mut srv = TestServer::new(|| { @@ -78,6 +82,7 @@ fn test_body_gzip() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_encoding_override() { let mut srv = TestServer::new(|| { @@ -104,6 +109,7 @@ fn test_body_encoding_override() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip_large() { let data = STR.repeat(10); @@ -134,6 +140,7 @@ fn test_body_gzip_large() { assert_eq!(Bytes::from(dec), Bytes::from(data)); } +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip_large_random() { let data = rand::thread_rng() @@ -168,6 +175,7 @@ fn test_body_gzip_large_random() { assert_eq!(Bytes::from(dec), Bytes::from(data)); } +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_chunked_implicit() { let mut srv = TestServer::new(move || { @@ -200,6 +208,7 @@ fn test_body_chunked_implicit() { } #[test] +#[cfg(feature = "brotli")] fn test_body_br_streaming() { let mut srv = TestServer::new(move || { h1::H1Service::new( @@ -277,6 +286,7 @@ fn test_no_chunking() { } #[test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_body_deflate() { let mut srv = TestServer::new(move || { h1::H1Service::new( @@ -303,6 +313,7 @@ fn test_body_deflate() { } #[test] +#[cfg(any(feature = "brotli"))] fn test_body_brotli() { let mut srv = TestServer::new(move || { h1::H1Service::new( @@ -336,6 +347,7 @@ fn test_body_brotli() { } #[test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_encoding() { let mut srv = TestServer::new(move || { HttpService::new( @@ -364,6 +376,7 @@ fn test_encoding() { } #[test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_gzip_encoding() { let mut srv = TestServer::new(move || { HttpService::new( @@ -392,6 +405,7 @@ fn test_gzip_encoding() { } #[test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_gzip_encoding_large() { let data = STR.repeat(10); let mut srv = TestServer::new(move || { @@ -421,6 +435,7 @@ fn test_gzip_encoding_large() { } #[test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_gzip_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -455,6 +470,7 @@ fn test_reading_gzip_encoding_large_random() { } #[test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding() { let mut srv = TestServer::new(move || { h1::H1Service::new( @@ -483,6 +499,7 @@ fn test_reading_deflate_encoding() { } #[test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); let mut srv = TestServer::new(move || { @@ -512,6 +529,7 @@ fn test_reading_deflate_encoding_large() { } #[test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) @@ -545,8 +563,8 @@ fn test_reading_deflate_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } -#[cfg(feature = "brotli")] #[test] +#[cfg(feature = "brotli")] fn test_brotli_encoding() { let mut srv = TestServer::new(move || { h1::H1Service::new( From 670a457013d6797d03f32f3b2dc0742192d5deb6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 14:28:59 -0700 Subject: [PATCH 1154/1635] fix docs.rs feature list --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 25d950a9..46ece2c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "rust-tls", "brotli", "flate2-zlib", "cookies", "client"] +features = ["ssl", "tls", "brotli", "flate2-zlib", "cookies", "client"] [features] default = ["brotli", "flate2-zlib", "cookies", "client"] From 1d79f1652935430cd4b552a94cd2fb1ea1195ff4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 14:30:38 -0700 Subject: [PATCH 1155/1635] update release api docs link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35b886ad..876d95f6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [User Guide](https://actix.rs/docs/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (Releases)](https://docs.rs/actix-web/) +* [API Documentation (Releases)](https://docs.rs/actix-web/0.7.18/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) * Minimum supported Rust version: 1.32 or later From 9710e9b01f66eb6d26384e29de8d3c34cb65e94b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 14:46:33 -0700 Subject: [PATCH 1156/1635] Re-export actix_http::client::Connector --- Cargo.toml | 2 +- awc/CHANGES.md | 7 +++++++ awc/Cargo.toml | 2 +- awc/src/lib.rs | 3 +-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 46ece2c2..19fb0e47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ default = ["brotli", "flate2-zlib", "cookies", "client"] client = ["awc"] # brotli encoding, requires c compiler -brotli = ["actix-http/brotli2"] +brotli = ["actix-http/brotli"] # miniz-sys backend for flate2 crate flate2-zlib = ["actix-http/flate2-zlib"] diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 95ec1c35..4656df39 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.0-alpha.2] - 2019-04-xx + +### Added + +* Re-export `actix_http::client::Connector` + + ## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/awc/Cargo.toml b/awc/Cargo.toml index de514299..69c9e983 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 9f5ca1f2..ca237798 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -23,9 +23,8 @@ use std::cell::RefCell; use std::rc::Rc; -pub use actix_http::http; +pub use actix_http::{client::Connector, http}; -use actix_http::client::Connector; use actix_http::http::{HttpTryFrom, Method, Uri}; use actix_http::RequestHead; From c4a8bbe47b760f239a7dca04a5cca4e732e8a190 Mon Sep 17 00:00:00 2001 From: joekyo Date: Fri, 29 Mar 2019 11:03:17 +0800 Subject: [PATCH 1157/1635] fix the example in README.md (#739) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 876d95f6..3cd0f365 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,9 @@ fn main() -> std::io::Result<()> { HttpServer::new( || App::new().service( web::resource("/{id}/{name}/index.html") - .route(web::get().to(index))) + .route(web::get().to(index)))) .bind("127.0.0.1:8080")? - .run(); + .run() } ``` From 80ff7d40a10ad76faa968d37ea837242937b5e58 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 20:27:47 -0700 Subject: [PATCH 1158/1635] enable awc/ssl if ssl features is enabled --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 19fb0e47..be5b6c90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ cookies = ["cookie", "actix-http/cookies"] tls = ["native-tls", "actix-server/ssl"] # openssl -ssl = ["openssl", "actix-server/ssl"] +ssl = ["openssl", "actix-server/ssl", "awc/ssl"] # rustls # rust-tls = ["rustls", "actix-server/rustls"] From 3b897da8e25f7b09b8e56eb1c022df1e16279478 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 21:15:26 -0700 Subject: [PATCH 1159/1635] Do not use thread pool for decomression if chunk size is smaller than 2048 --- actix-http/CHANGES.md | 7 +++++++ actix-http/Cargo.toml | 2 +- actix-http/src/encoding/decoder.rs | 26 ++++++++++++++++---------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 95ec1c35..e9596349 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.0-alpha.2] - 2019-xx-xx + +### Changed + +* Do not use thread pool for decomression if chunk size is smaller than 2048. + + ## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9734bd1a..180cda78 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index ae2b4ae6..16d15e90 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -12,6 +12,8 @@ use super::Writer; use crate::error::PayloadError; use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}; +const INPLACE: usize = 2049; + pub struct Decoder { decoder: Option, stream: S, @@ -92,10 +94,18 @@ where match self.stream.poll()? { Async::Ready(Some(chunk)) => { if let Some(mut decoder) = self.decoder.take() { - self.fut = Some(run(move || { + if chunk.len() < INPLACE { let chunk = decoder.feed_data(chunk)?; - Ok((chunk, decoder)) - })); + self.decoder = Some(decoder); + if let Some(chunk) = chunk { + return Ok(Async::Ready(Some(chunk))); + } + } else { + self.fut = Some(run(move || { + let chunk = decoder.feed_data(chunk)?; + Ok((chunk, decoder)) + })); + } continue; } else { return Ok(Async::Ready(Some(chunk))); @@ -103,14 +113,10 @@ where } Async::Ready(None) => { self.eof = true; - if let Some(mut decoder) = self.decoder.take() { - self.fut = Some(run(move || { - let chunk = decoder.feed_eof()?; - Ok((chunk, decoder)) - })); - continue; + return if let Some(mut decoder) = self.decoder.take() { + Ok(Async::Ready(decoder.feed_eof()?)) } else { - return Ok(Async::Ready(None)); + Ok(Async::Ready(None)) }; } Async::NotReady => break, From ea4d98d669616e42cd1d05e6b74b0a341db72686 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 21:48:35 -0700 Subject: [PATCH 1160/1635] Session wide headers, basic and bearer auth --- awc/CHANGES.md | 4 ++ awc/src/builder.rs | 83 +++++++++++++++++++++++++++++++--- awc/src/lib.rs | 43 ++++++++++++------ awc/src/request.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++- awc/src/test.rs | 19 ++++++++ awc/src/ws.rs | 2 +- 6 files changed, 237 insertions(+), 22 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4656df39..b24ae50b 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -6,6 +6,10 @@ * Re-export `actix_http::client::Connector` +* Session wide headers + +* Session wide basic and bearer auth + ## [0.1.0-alpha.1] - 2019-03-28 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 562ff241..d53d0d44 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -3,13 +3,11 @@ use std::fmt; use std::rc::Rc; use actix_http::client::{ConnectError, Connection, Connector}; -use actix_http::http::{ - header::IntoHeaderValue, HeaderMap, HeaderName, HttpTryFrom, Uri, -}; +use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom, Uri}; use actix_service::Service; use crate::connect::{Connect, ConnectorWrapper}; -use crate::Client; +use crate::{Client, ClientConfig}; /// An HTTP Client builder /// @@ -77,7 +75,7 @@ impl ClientBuilder { where HeaderName: HttpTryFrom, >::Error: fmt::Debug, - V: IntoHeaderValue, + V: header::IntoHeaderValue, V::Error: fmt::Debug, { match HeaderName::try_from(key) { @@ -92,10 +90,85 @@ impl ClientBuilder { self } + /// Set client wide HTTP basic authorization header + pub fn basic_auth(self, username: U, password: Option<&str>) -> Self + where + U: fmt::Display, + { + let auth = match password { + Some(password) => format!("{}:{}", username, password), + None => format!("{}", username), + }; + self.header( + header::AUTHORIZATION, + format!("Basic {}", base64::encode(&auth)), + ) + } + + /// Set client wide HTTP bearer authentication header + pub fn bearer_auth(self, token: T) -> Self + where + T: fmt::Display, + { + self.header(header::AUTHORIZATION, format!("Bearer {}", token)) + } + /// Finish build process and create `Client` instance. pub fn finish(self) -> Client { Client { connector: self.connector, + config: Rc::new(ClientConfig { + headers: self.headers, + }), } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test; + + #[test] + fn client_basic_auth() { + test::run_on(|| { + let client = ClientBuilder::new().basic_auth("username", Some("password")); + assert_eq!( + client + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + ); + + let client = ClientBuilder::new().basic_auth("username", None); + assert_eq!( + client + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU=" + ); + }); + } + + #[test] + fn client_bearer_auth() { + test::run_on(|| { + let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n"); + assert_eq!( + client + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Bearer someS3cr3tAutht0k3n" + ); + }) + } +} diff --git a/awc/src/lib.rs b/awc/src/lib.rs index ca237798..3518bf8b 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -25,7 +25,7 @@ use std::rc::Rc; pub use actix_http::{client::Connector, http}; -use actix_http::http::{HttpTryFrom, Method, Uri}; +use actix_http::http::{HeaderMap, HttpTryFrom, Method, Uri}; use actix_http::RequestHead; mod builder; @@ -68,6 +68,11 @@ use self::connect::{Connect, ConnectorWrapper}; #[derive(Clone)] pub struct Client { pub(crate) connector: Rc>, + pub(crate) config: Rc, +} + +pub(crate) struct ClientConfig { + pub(crate) headers: HeaderMap, } impl Default for Client { @@ -76,6 +81,9 @@ impl Default for Client { connector: Rc::new(RefCell::new(ConnectorWrapper( Connector::new().service(), ))), + config: Rc::new(ClientConfig { + headers: HeaderMap::new(), + }), } } } @@ -96,7 +104,12 @@ impl Client { where Uri: HttpTryFrom, { - ClientRequest::new(method, url, self.connector.clone()) + let mut req = ClientRequest::new(method, url, self.connector.clone()); + + for (key, value) in &self.config.headers { + req.head.headers.insert(key.clone(), value.clone()); + } + req } /// Create `ClientRequest` from `RequestHead` @@ -107,13 +120,10 @@ impl Client { where Uri: HttpTryFrom, { - let mut req = - ClientRequest::new(head.method.clone(), url, self.connector.clone()); - + let mut req = self.request(head.method.clone(), url); for (key, value) in &head.headers { req.head.headers.insert(key.clone(), value.clone()); } - req } @@ -121,55 +131,60 @@ impl Client { where Uri: HttpTryFrom, { - ClientRequest::new(Method::GET, url, self.connector.clone()) + self.request(Method::GET, url) } pub fn head(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::HEAD, url, self.connector.clone()) + self.request(Method::HEAD, url) } pub fn put(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::PUT, url, self.connector.clone()) + self.request(Method::PUT, url) } pub fn post(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::POST, url, self.connector.clone()) + self.request(Method::POST, url) } pub fn patch(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::PATCH, url, self.connector.clone()) + self.request(Method::PATCH, url) } pub fn delete(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::DELETE, url, self.connector.clone()) + self.request(Method::DELETE, url) } pub fn options(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, { - ClientRequest::new(Method::OPTIONS, url, self.connector.clone()) + self.request(Method::OPTIONS, url) } pub fn ws(&self, url: U) -> WebsocketsRequest where Uri: HttpTryFrom, { - WebsocketsRequest::new(url, self.connector.clone()) + let mut req = WebsocketsRequest::new(url, self.connector.clone()); + + for (key, value) in &self.config.headers { + req.head.headers.insert(key.clone(), value.clone()); + } + req } } diff --git a/awc/src/request.rs b/awc/src/request.rs index dde51a8f..2e778cfc 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -241,10 +241,9 @@ impl ClientRequest { } /// Set HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option

    ) -> Self + pub fn basic_auth(self, username: U, password: Option<&str>) -> Self where U: fmt::Display, - P: fmt::Display, { let auth = match password { Some(password) => format!("{}:{}", username, password), @@ -552,3 +551,108 @@ impl fmt::Debug for ClientRequest { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test, Client}; + + #[test] + fn test_debug() { + test::run_on(|| { + let request = Client::new().get("/").header("x-test", "111"); + let repr = format!("{:?}", request); + assert!(repr.contains("ClientRequest")); + assert!(repr.contains("x-test")); + }) + } + + #[test] + fn test_client_header() { + test::run_on(|| { + let req = Client::build() + .header(header::CONTENT_TYPE, "111") + .finish() + .get("/"); + + assert_eq!( + req.head + .headers + .get(header::CONTENT_TYPE) + .unwrap() + .to_str() + .unwrap(), + "111" + ); + }) + } + + #[test] + fn test_client_header_override() { + test::run_on(|| { + let req = Client::build() + .header(header::CONTENT_TYPE, "111") + .finish() + .get("/") + .set_header(header::CONTENT_TYPE, "222"); + + assert_eq!( + req.head + .headers + .get(header::CONTENT_TYPE) + .unwrap() + .to_str() + .unwrap(), + "222" + ); + }) + } + + #[test] + fn client_basic_auth() { + test::run_on(|| { + let client = Client::new() + .get("/") + .basic_auth("username", Some("password")); + assert_eq!( + client + .head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + ); + + let client = Client::new().get("/").basic_auth("username", None); + assert_eq!( + client + .head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU=" + ); + }); + } + + #[test] + fn client_bearer_auth() { + test::run_on(|| { + let client = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); + assert_eq!( + client + .head + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Bearer someS3cr3tAutht0k3n" + ); + }) + } +} diff --git a/awc/src/test.rs b/awc/src/test.rs index 395e6290..7723b9d2 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -8,6 +8,25 @@ use cookie::{Cookie, CookieJar}; use crate::ClientResponse; +#[cfg(test)] +thread_local! { + static RT: std::cell::RefCell = { + std::cell::RefCell::new(actix_rt::Runtime::new().unwrap()) + }; +} + +#[cfg(test)] +pub fn run_on(f: F) -> R +where + F: Fn() -> R, +{ + RT.with(move |rt| { + rt.borrow_mut() + .block_on(futures::future::lazy(|| Ok::<_, ()>(f()))) + }) + .unwrap() +} + /// Test `ClientResponse` builder pub struct TestResponse { head: ResponseHead, diff --git a/awc/src/ws.rs b/awc/src/ws.rs index f959e62c..ec7fc0da 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -23,7 +23,7 @@ use crate::response::ClientResponse; /// `WebSocket` connection pub struct WebsocketsRequest { - head: RequestHead, + pub(crate) head: RequestHead, err: Option, origin: Option, protocols: Option, From 1e7096a63a5f07d41fdb10dacca7376d8833b03a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Mar 2019 22:33:41 -0700 Subject: [PATCH 1161/1635] add request timeout --- actix-http/src/client/error.rs | 3 +++ awc/CHANGES.md | 8 +++--- awc/Cargo.toml | 1 + awc/src/builder.rs | 46 +++++++++++++++++++++++----------- awc/src/lib.rs | 28 ++++++++++----------- awc/src/request.rs | 29 +++++++++++++-------- awc/src/ws.rs | 28 +++++++++++++++------ awc/tests/test_client.rs | 26 +++++++++++++++++++ 8 files changed, 119 insertions(+), 50 deletions(-) diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index e67db546..fc4b5b72 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -105,6 +105,9 @@ pub enum SendRequestError { /// Http2 error #[display(fmt = "{}", _0)] H2(h2::Error), + /// Response took too long + #[display(fmt = "Timeout out while waiting for response")] + Timeout, /// Tunnels are not supported for http2 connection #[display(fmt = "Tunnels are not supported for http2 connection")] TunnelNotSupported, diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b24ae50b..9192e2db 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -4,11 +4,13 @@ ### Added -* Re-export `actix_http::client::Connector` +* Request timeout. -* Session wide headers +* Re-export `actix_http::client::Connector`. -* Session wide basic and bearer auth +* Session wide headers. + +* Session wide basic and bearer auth. ## [0.1.0-alpha.1] - 2019-03-28 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 69c9e983..cd94057f 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -52,6 +52,7 @@ rand = "0.6" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.5.3" +tokio-timer = "0.2.8" cookie = { version="0.11", features=["percent-encode"], optional = true } openssl = { version="0.10", optional = true } diff --git a/awc/src/builder.rs b/awc/src/builder.rs index d53d0d44..6ef145bf 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,12 +1,13 @@ use std::cell::RefCell; use std::fmt; use std::rc::Rc; +use std::time::Duration; use actix_http::client::{ConnectError, Connection, Connector}; use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom, Uri}; use actix_service::Service; -use crate::connect::{Connect, ConnectorWrapper}; +use crate::connect::ConnectorWrapper; use crate::{Client, ClientConfig}; /// An HTTP Client builder @@ -14,11 +15,10 @@ use crate::{Client, ClientConfig}; /// This type can be used to construct an instance of `Client` through a /// builder-like pattern. pub struct ClientBuilder { - connector: Rc>, + config: ClientConfig, default_headers: bool, allow_redirects: bool, max_redirects: usize, - headers: HeaderMap, } impl ClientBuilder { @@ -27,10 +27,13 @@ impl ClientBuilder { default_headers: true, allow_redirects: true, max_redirects: 10, - headers: HeaderMap::new(), - connector: Rc::new(RefCell::new(ConnectorWrapper( - Connector::new().service(), - ))), + config: ClientConfig { + headers: HeaderMap::new(), + timeout: Some(Duration::from_secs(5)), + connector: RefCell::new(Box::new(ConnectorWrapper( + Connector::new().service(), + ))), + }, } } @@ -42,7 +45,22 @@ impl ClientBuilder { ::Future: 'static, T::Future: 'static, { - self.connector = Rc::new(RefCell::new(ConnectorWrapper(connector))); + self.config.connector = RefCell::new(Box::new(ConnectorWrapper(connector))); + self + } + + /// Set request timeout + /// + /// Request timeout is the total time before a response must be received. + /// Default value is 5 seconds. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.config.timeout = Some(timeout); + self + } + + /// Disable request timeout. + pub fn disable_timeout(mut self) -> Self { + self.config.timeout = None; self } @@ -81,7 +99,7 @@ impl ClientBuilder { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { Ok(value) => { - self.headers.append(key, value); + self.config.headers.append(key, value); } Err(e) => log::error!("Header value error: {:?}", e), }, @@ -115,12 +133,7 @@ impl ClientBuilder { /// Finish build process and create `Client` instance. pub fn finish(self) -> Client { - Client { - connector: self.connector, - config: Rc::new(ClientConfig { - headers: self.headers, - }), - } + Client(Rc::new(self.config)) } } @@ -135,6 +148,7 @@ mod tests { let client = ClientBuilder::new().basic_auth("username", Some("password")); assert_eq!( client + .config .headers .get(header::AUTHORIZATION) .unwrap() @@ -146,6 +160,7 @@ mod tests { let client = ClientBuilder::new().basic_auth("username", None); assert_eq!( client + .config .headers .get(header::AUTHORIZATION) .unwrap() @@ -162,6 +177,7 @@ mod tests { let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n"); assert_eq!( client + .config .headers .get(header::AUTHORIZATION) .unwrap() diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 3518bf8b..9a8daeb4 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -22,6 +22,7 @@ //! ``` use std::cell::RefCell; use std::rc::Rc; +use std::time::Duration; pub use actix_http::{client::Connector, http}; @@ -66,25 +67,23 @@ use self::connect::{Connect, ConnectorWrapper}; /// } /// ``` #[derive(Clone)] -pub struct Client { - pub(crate) connector: Rc>, - pub(crate) config: Rc, -} +pub struct Client(Rc); pub(crate) struct ClientConfig { + pub(crate) connector: RefCell>, pub(crate) headers: HeaderMap, + pub(crate) timeout: Option, } impl Default for Client { fn default() -> Self { - Client { - connector: Rc::new(RefCell::new(ConnectorWrapper( + Client(Rc::new(ClientConfig { + connector: RefCell::new(Box::new(ConnectorWrapper( Connector::new().service(), ))), - config: Rc::new(ClientConfig { - headers: HeaderMap::new(), - }), - } + headers: HeaderMap::new(), + timeout: Some(Duration::from_secs(5)), + })) } } @@ -104,9 +103,9 @@ impl Client { where Uri: HttpTryFrom, { - let mut req = ClientRequest::new(method, url, self.connector.clone()); + let mut req = ClientRequest::new(method, url, self.0.clone()); - for (key, value) in &self.config.headers { + for (key, value) in &self.0.headers { req.head.headers.insert(key.clone(), value.clone()); } req @@ -180,9 +179,8 @@ impl Client { where Uri: HttpTryFrom, { - let mut req = WebsocketsRequest::new(url, self.connector.clone()); - - for (key, value) in &self.config.headers { + let mut req = WebsocketsRequest::new(url, self.0.clone()); + for (key, value) in &self.0.headers { req.head.headers.insert(key.clone(), value.clone()); } req diff --git a/awc/src/request.rs b/awc/src/request.rs index 2e778cfc..170be75f 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::fmt; use std::io::Write; use std::rc::Rc; @@ -10,6 +9,7 @@ use futures::future::{err, Either}; use futures::{Future, Stream}; use serde::Serialize; use serde_json; +use tokio_timer::Timeout; use actix_http::body::{Body, BodyStream}; use actix_http::encoding::Decoder; @@ -20,9 +20,9 @@ use actix_http::http::{ }; use actix_http::{Error, Payload, RequestHead}; -use crate::connect::Connect; use crate::error::{InvalidUrl, PayloadError, SendRequestError}; use crate::response::ClientResponse; +use crate::ClientConfig; #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] const HTTPS_ENCODING: &str = "br, gzip, deflate"; @@ -62,16 +62,12 @@ pub struct ClientRequest { cookies: Option, default_headers: bool, response_decompress: bool, - connector: Rc>, + config: Rc, } impl ClientRequest { /// Create new client request builder. - pub(crate) fn new( - method: Method, - uri: U, - connector: Rc>, - ) -> Self + pub(crate) fn new(method: Method, uri: U, config: Rc) -> Self where Uri: HttpTryFrom, { @@ -87,7 +83,7 @@ impl ClientRequest { ClientRequest { head, err, - connector, + config, #[cfg(feature = "cookies")] cookies: None, default_headers: true, @@ -450,6 +446,7 @@ impl ClientRequest { let response_decompress = slf.response_decompress; let fut = slf + .config .connector .borrow_mut() .send_request(head, body.into()) @@ -462,7 +459,19 @@ impl ClientRequest { } }) }); - Either::B(fut) + + // set request timeout + if let Some(timeout) = slf.config.timeout { + Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { + if let Some(e) = e.into_inner() { + e + } else { + SendRequestError::Timeout + } + }))) + } else { + Either::B(Either::B(fut)) + } } /// Set a JSON body and generate `ClientRequest` diff --git a/awc/src/ws.rs b/awc/src/ws.rs index ec7fc0da..26594531 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -1,5 +1,4 @@ //! Websockets client -use std::cell::RefCell; use std::io::Write; use std::rc::Rc; use std::{fmt, str}; @@ -10,9 +9,10 @@ use bytes::{BufMut, BytesMut}; #[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; use futures::future::{err, Either, Future}; +use tokio_timer::Timeout; -use crate::connect::{BoxedSocket, Connect}; -use crate::error::{InvalidUrl, WsClientError}; +use crate::connect::BoxedSocket; +use crate::error::{InvalidUrl, SendRequestError, WsClientError}; use crate::http::header::{ self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION, }; @@ -20,6 +20,7 @@ use crate::http::{ ConnectionType, Error as HttpError, HttpTryFrom, Method, StatusCode, Uri, Version, }; use crate::response::ClientResponse; +use crate::ClientConfig; /// `WebSocket` connection pub struct WebsocketsRequest { @@ -32,12 +33,12 @@ pub struct WebsocketsRequest { default_headers: bool, #[cfg(feature = "cookies")] cookies: Option, - connector: Rc>, + config: Rc, } impl WebsocketsRequest { /// Create new websocket connection - pub(crate) fn new(uri: U, connector: Rc>) -> Self + pub(crate) fn new(uri: U, config: Rc) -> Self where Uri: HttpTryFrom, { @@ -54,7 +55,7 @@ impl WebsocketsRequest { WebsocketsRequest { head, err, - connector, + config, origin: None, protocols: None, max_size: 65_536, @@ -322,6 +323,7 @@ impl WebsocketsRequest { let server_mode = slf.server_mode; let fut = slf + .config .connector .borrow_mut() .open_tunnel(head) @@ -393,6 +395,18 @@ impl WebsocketsRequest { }), )) }); - Either::B(fut) + + // set request timeout + if let Some(timeout) = slf.config.timeout { + Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { + if let Some(e) = e.into_inner() { + e + } else { + SendRequestError::Timeout.into() + } + }))) + } else { + Either::B(Either::B(fut)) + } } } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 698b5ab7..b2d6f8e9 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,14 +1,17 @@ use std::io::Write; +use std::time::Duration; use brotli2::write::BrotliEncoder; use bytes::Bytes; use flate2::write::GzEncoder; use flate2::Compression; +use futures::future::Future; use rand::Rng; use actix_http::HttpService; use actix_http_test::TestServer; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; +use awc::error::SendRequestError; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -57,6 +60,29 @@ fn test_simple() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[test] +fn test_timeout() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to_async( + || { + tokio_timer::sleep(Duration::from_millis(200)) + .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) + }, + )))) + }); + + let client = srv.execute(|| { + awc::Client::build() + .timeout(Duration::from_millis(50)) + .finish() + }); + let request = client.get(srv.url("/")).send(); + match srv.block_on(request) { + Err(SendRequestError::Timeout) => (), + _ => panic!(), + } +} + // #[test] // fn test_connection_close() { // let mut srv = From 19a0b8046bb62612970041d02a68357040f50e1a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 11:13:36 -0700 Subject: [PATCH 1162/1635] remove actix reference --- actix-http/src/error.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index e6cc0e07..b062fdf4 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -4,7 +4,6 @@ use std::str::Utf8Error; use std::string::FromUtf8Error; use std::{fmt, io, result}; -// use actix::MailboxError; pub use actix_threadpool::BlockingError; use actix_utils::timeout::TimeoutError; #[cfg(feature = "cookies")] @@ -168,9 +167,6 @@ impl ResponseError for header::InvalidHeaderValueBytes { /// `InternalServerError` for `futures::Canceled` impl ResponseError for Canceled {} -// /// `InternalServerError` for `actix::MailboxError` -// impl ResponseError for MailboxError {} - /// A set of errors that can occur during parsing HTTP streams #[derive(Debug, Display)] pub enum ParseError { From 709475b2bbd72b01bba7cd23412e601dd0351df1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 11:59:38 -0700 Subject: [PATCH 1163/1635] multipart::Field renamed to MultipartField --- CHANGES.md | 6 ++++++ src/types/mod.rs | 2 +- src/types/multipart.rs | 21 +++++++++------------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 13eeb67d..47b0f987 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [1.0.0-alpha.2] - 2019-03-29 + +### Changed + +* multipart::Field renamed to MultipartField + ## [1.0.0-alpha.1] - 2019-03-28 ### Changed diff --git a/src/types/mod.rs b/src/types/mod.rs index c9aed94f..9a0a0880 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -10,7 +10,7 @@ pub(crate) mod readlines; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; -pub use self::multipart::{Multipart, MultipartItem}; +pub use self::multipart::{Multipart, MultipartField, MultipartItem}; pub use self::path::Path; pub use self::payload::{Payload, PayloadConfig}; pub use self::query::Query; diff --git a/src/types/multipart.rs b/src/types/multipart.rs index 50ef3813..65a64d5e 100644 --- a/src/types/multipart.rs +++ b/src/types/multipart.rs @@ -39,7 +39,7 @@ pub struct Multipart { /// Multipart item pub enum MultipartItem { /// Multipart field - Field(Field), + Field(MultipartField), /// Nested multipart stream Nested(Multipart), } @@ -402,12 +402,9 @@ impl InnerMultipart { )?)); self.item = InnerMultipartItem::Field(Rc::clone(&field)); - Ok(Async::Ready(Some(MultipartItem::Field(Field::new( - safety.clone(), - headers, - mt, - field, - ))))) + Ok(Async::Ready(Some(MultipartItem::Field( + MultipartField::new(safety.clone(), headers, mt, field), + )))) } } } @@ -421,21 +418,21 @@ impl Drop for InnerMultipart { } /// A single field in a multipart stream -pub struct Field { +pub struct MultipartField { ct: mime::Mime, headers: HeaderMap, inner: Rc>, safety: Safety, } -impl Field { +impl MultipartField { fn new( safety: Safety, headers: HeaderMap, ct: mime::Mime, inner: Rc>, ) -> Self { - Field { + MultipartField { ct, headers, inner, @@ -466,7 +463,7 @@ impl Field { } } -impl Stream for Field { +impl Stream for MultipartField { type Item = Bytes; type Error = MultipartError; @@ -479,7 +476,7 @@ impl Stream for Field { } } -impl fmt::Debug for Field { +impl fmt::Debug for MultipartField { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "\nMultipartField: {}", self.ct)?; writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; From 058b1d56e6b3c5efe48a28a25d9308915d435d20 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 13:49:21 -0700 Subject: [PATCH 1164/1635] Export ws sub-module with websockets related types --- awc/CHANGES.md | 6 +++++- awc/src/error.rs | 1 + awc/src/lib.rs | 15 +++++++++++---- awc/src/ws.rs | 4 +++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9192e2db..dcc38c31 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.0-alpha.2] - 2019-04-xx +## [0.1.0-alpha.2] - 2019-03-xx ### Added @@ -12,6 +12,10 @@ * Session wide basic and bearer auth. +### Changed + +* Export `ws` sub-module with websockets related types + ## [0.1.0-alpha.1] - 2019-03-28 diff --git a/awc/src/error.rs b/awc/src/error.rs index d3f1c1a1..8f51fd7d 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,6 +1,7 @@ //! Http client errors pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; pub use actix_http::error::PayloadError; +pub use actix_http::ws::HandshakeError as WsHandshakeError; pub use actix_http::ws::ProtocolError as WsProtocolError; use actix_http::http::{header::HeaderValue, Error as HttpError, StatusCode}; diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 9a8daeb4..92f749d0 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -35,12 +35,11 @@ pub mod error; mod request; mod response; pub mod test; -mod ws; +pub mod ws; pub use self::builder::ClientBuilder; pub use self::request::ClientRequest; pub use self::response::ClientResponse; -pub use self::ws::WebsocketsRequest; use self::connect::{Connect, ConnectorWrapper}; @@ -126,6 +125,7 @@ impl Client { req } + /// Construct HTTP *GET* request. pub fn get(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, @@ -133,6 +133,7 @@ impl Client { self.request(Method::GET, url) } + /// Construct HTTP *HEAD* request. pub fn head(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, @@ -140,6 +141,7 @@ impl Client { self.request(Method::HEAD, url) } + /// Construct HTTP *PUT* request. pub fn put(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, @@ -147,6 +149,7 @@ impl Client { self.request(Method::PUT, url) } + /// Construct HTTP *POST* request. pub fn post(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, @@ -154,6 +157,7 @@ impl Client { self.request(Method::POST, url) } + /// Construct HTTP *PATCH* request. pub fn patch(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, @@ -161,6 +165,7 @@ impl Client { self.request(Method::PATCH, url) } + /// Construct HTTP *DELETE* request. pub fn delete(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, @@ -168,6 +173,7 @@ impl Client { self.request(Method::DELETE, url) } + /// Construct HTTP *OPTIONS* request. pub fn options(&self, url: U) -> ClientRequest where Uri: HttpTryFrom, @@ -175,11 +181,12 @@ impl Client { self.request(Method::OPTIONS, url) } - pub fn ws(&self, url: U) -> WebsocketsRequest + /// Construct WebSockets request. + pub fn ws(&self, url: U) -> ws::WebsocketsRequest where Uri: HttpTryFrom, { - let mut req = WebsocketsRequest::new(url, self.0.clone()); + let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); for (key, value) in &self.0.headers { req.head.headers.insert(key.clone(), value.clone()); } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 26594531..9697210d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -11,6 +11,8 @@ use cookie::{Cookie, CookieJar}; use futures::future::{err, Either, Future}; use tokio_timer::Timeout; +pub use actix_http::ws::{CloseCode, CloseReason, Frame, Message}; + use crate::connect::BoxedSocket; use crate::error::{InvalidUrl, SendRequestError, WsClientError}; use crate::http::header::{ @@ -208,7 +210,7 @@ impl WebsocketsRequest { self.header(AUTHORIZATION, format!("Bearer {}", token)) } - /// Complete request construction and connect. + /// Complete request construction and connect to a websockets server. pub fn connect( mut self, ) -> impl Future< From 744d82431da34da01ddabe063579581c5a96a48d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 14:07:37 -0700 Subject: [PATCH 1165/1635] add per request timeout --- awc/CHANGES.md | 9 +++++---- awc/src/builder.rs | 2 +- awc/src/request.rs | 18 +++++++++++++++--- awc/tests/test_client.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index dcc38c31..cd97ed86 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,17 +1,18 @@ # Changes -## [0.1.0-alpha.2] - 2019-03-xx +## [0.1.0-alpha.2] - 2019-03-29 ### Added -* Request timeout. - -* Re-export `actix_http::client::Connector`. +* Per request and session wide request timeout. * Session wide headers. * Session wide basic and bearer auth. +* Re-export `actix_http::client::Connector`. + + ### Changed * Export `ws` sub-module with websockets related types diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 6ef145bf..dcea5595 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -87,7 +87,7 @@ impl ClientBuilder { self } - /// Add default header. Headers adds byt this method + /// Add default header. Headers added by this method /// get added to every request. pub fn header(mut self, key: K, value: V) -> Self where diff --git a/awc/src/request.rs b/awc/src/request.rs index 170be75f..a29c3e60 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,6 +1,7 @@ use std::fmt; use std::io::Write; use std::rc::Rc; +use std::time::Duration; use bytes::{BufMut, Bytes, BytesMut}; #[cfg(feature = "cookies")] @@ -62,6 +63,7 @@ pub struct ClientRequest { cookies: Option, default_headers: bool, response_decompress: bool, + timeout: Option, config: Rc, } @@ -86,6 +88,7 @@ impl ClientRequest { config, #[cfg(feature = "cookies")] cookies: None, + timeout: None, default_headers: true, response_decompress: true, } @@ -309,6 +312,15 @@ impl ClientRequest { self } + /// Set request timeout. Overrides client wide timeout setting. + /// + /// Request timeout is the total time before a response must be received. + /// Default value is 5 seconds. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } + /// This method calls provided closure with builder reference if /// value is `true`. pub fn if_true(mut self, value: bool, f: F) -> Self @@ -443,10 +455,10 @@ impl ClientRequest { } } + let config = slf.config; let response_decompress = slf.response_decompress; - let fut = slf - .config + let fut = config .connector .borrow_mut() .send_request(head, body.into()) @@ -461,7 +473,7 @@ impl ClientRequest { }); // set request timeout - if let Some(timeout) = slf.config.timeout { + if let Some(timeout) = slf.timeout.or_else(|| config.timeout.clone()) { Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { if let Some(e) = e.into_inner() { e diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index b2d6f8e9..51791d67 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -83,6 +83,32 @@ fn test_timeout() { } } +#[test] +fn test_timeout_override() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to_async( + || { + tokio_timer::sleep(Duration::from_millis(200)) + .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) + }, + )))) + }); + + let client = srv.execute(|| { + awc::Client::build() + .timeout(Duration::from_millis(50000)) + .finish() + }); + let request = client + .get(srv.url("/")) + .timeout(Duration::from_millis(50)) + .send(); + match srv.block_on(request) { + Err(SendRequestError::Timeout) => (), + _ => panic!(), + } +} + // #[test] // fn test_connection_close() { // let mut srv = From aebeb511cd3a1c0e1ae7217214c13ce42ec983f5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 14:26:11 -0700 Subject: [PATCH 1166/1635] explicit impl traits for ws connect --- awc/src/ws.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 9697210d..ae281737 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -3,7 +3,7 @@ use std::io::Write; use std::rc::Rc; use std::{fmt, str}; -use actix_codec::Framed; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{ws, Payload, RequestHead}; use bytes::{BufMut, BytesMut}; #[cfg(feature = "cookies")] @@ -13,7 +13,6 @@ use tokio_timer::Timeout; pub use actix_http::ws::{CloseCode, CloseReason, Frame, Message}; -use crate::connect::BoxedSocket; use crate::error::{InvalidUrl, SendRequestError, WsClientError}; use crate::http::header::{ self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION, @@ -214,7 +213,10 @@ impl WebsocketsRequest { pub fn connect( mut self, ) -> impl Future< - Item = (ClientResponse, Framed), + Item = ( + ClientResponse, + Framed, + ), Error = WsClientError, > { if let Some(e) = self.err.take() { From 5eb3f1154ec085ad1a94c809efc8955cdbe3b173 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 14:27:22 -0700 Subject: [PATCH 1167/1635] revert --- awc/src/ws.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index ae281737..9697210d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -3,7 +3,7 @@ use std::io::Write; use std::rc::Rc; use std::{fmt, str}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_codec::Framed; use actix_http::{ws, Payload, RequestHead}; use bytes::{BufMut, BytesMut}; #[cfg(feature = "cookies")] @@ -13,6 +13,7 @@ use tokio_timer::Timeout; pub use actix_http::ws::{CloseCode, CloseReason, Frame, Message}; +use crate::connect::BoxedSocket; use crate::error::{InvalidUrl, SendRequestError, WsClientError}; use crate::http::header::{ self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION, @@ -213,10 +214,7 @@ impl WebsocketsRequest { pub fn connect( mut self, ) -> impl Future< - Item = ( - ClientResponse, - Framed, - ), + Item = (ClientResponse, Framed), Error = WsClientError, > { if let Some(e) = self.err.take() { From e9bbde6832722cff9bac0069cd33bb7e801f2005 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 16:27:18 -0700 Subject: [PATCH 1168/1635] allow to override request's uri --- awc/CHANGES.md | 2 ++ awc/src/request.rs | 28 +++++++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index cd97ed86..e0e83214 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -15,6 +15,8 @@ ### Changed +* Allow to override request's uri + * Export `ws` sub-module with websockets related types diff --git a/awc/src/request.rs b/awc/src/request.rs index a29c3e60..bdde6faf 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -73,25 +73,31 @@ impl ClientRequest { where Uri: HttpTryFrom, { - let mut err = None; - let mut head = RequestHead::default(); - head.method = method; - - match Uri::try_from(uri) { - Ok(uri) => head.uri = uri, - Err(e) => err = Some(e.into()), - } - ClientRequest { - head, - err, config, + head: RequestHead::default(), + err: None, #[cfg(feature = "cookies")] cookies: None, timeout: None, default_headers: true, response_decompress: true, } + .method(method) + .uri(uri) + } + + /// Set HTTP URI of request. + #[inline] + pub fn uri(mut self, uri: U) -> Self + where + Uri: HttpTryFrom, + { + match Uri::try_from(uri) { + Ok(uri) => self.head.uri = uri, + Err(e) => self.err = Some(e.into()), + } + self } /// Set HTTP method of this request. From c126713f4058995d198761d32a83b12030f49d81 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 16:28:19 -0700 Subject: [PATCH 1169/1635] add rustls support to HttpServer --- Cargo.toml | 9 ++- README.md | 2 +- actix-http/tests/cert.pem | 32 ++++---- actix-http/tests/identity.pfx | Bin 5549 -> 0 bytes actix-http/tests/key.pem | 55 +++++++------ src/server.rs | 101 +++++++++++++----------- tests/cert.pem | 47 +++++------- tests/key.pem | 74 ++++++------------ tests/test_server.rs | 140 ++++++++++++++++++++-------------- 9 files changed, 234 insertions(+), 226 deletions(-) delete mode 100644 actix-http/tests/identity.pfx diff --git a/Cargo.toml b/Cargo.toml index be5b6c90..06c88c57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "brotli", "flate2-zlib", "cookies", "client"] +features = ["ssl", "tls", "brotli", "flate2-zlib", "cookies", "client", "rust-tls"] [features] default = ["brotli", "flate2-zlib", "cookies", "client"] @@ -62,7 +62,7 @@ tls = ["native-tls", "actix-server/ssl"] ssl = ["openssl", "actix-server/ssl", "awc/ssl"] # rustls -# rust-tls = ["rustls", "actix-server/rustls"] +rust-tls = ["rustls", "actix-server/rust-tls"] [dependencies] actix-codec = "0.1.1" @@ -100,7 +100,7 @@ cookie = { version="0.11", features=["secure", "percent-encode"], optional = tru # ssl support native-tls = { version="0.2", optional = true } openssl = { version="0.10", optional = true } -# rustls = { version = "^0.15", optional = true } +rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { path = "actix-http", features=["ssl", "brotli", "flate2-zlib"] } @@ -112,6 +112,9 @@ tokio-timer = "0.2.8" brotli2 = "0.3.2" flate2 = "1.0.2" +[replace] +"cookie:0.11.0" = { git = 'https://github.com/alexcrichton/cookie-rs.git' } + [profile.release] lto = true opt-level = 3 diff --git a/README.md b/README.md index 3cd0f365..9829ab86 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Configurable [request routing](https://actix.rs/docs/url-dispatch/) * Multipart streams * Static assets -* SSL support with OpenSSL or native-tls +* SSL support with OpenSSL or Rustls * Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Supports [Actix actor framework](https://github.com/actix/actix) diff --git a/actix-http/tests/cert.pem b/actix-http/tests/cert.pem index 5e195d98..eafad524 100644 --- a/actix-http/tests/cert.pem +++ b/actix-http/tests/cert.pem @@ -1,16 +1,20 @@ -----BEGIN CERTIFICATE----- -MIICljCCAX4CCQDFdWu66640QjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJ1 -czAeFw0xOTAyMDQyMzEyNTBaFw0yMDAyMDQyMzEyNTBaMA0xCzAJBgNVBAYTAnVz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzZUXMnS5X8HWxTvHAc82 -Q2d32fiPQGtD+fp3OV90l6RC9jgMdH4yTVUgX5mYYcW0k89RaP8g61H6b76F9gcd -yZ1idqKI1AU9aeBUPV8wkrouhR/6Omv8fA7yr9tVmNo53jPN7WyKoBoU0r7Yj9Ez -g3qjv/808Jlgby3EhduruyyfdvSt5ZFXnOz2D3SF9DS4yrM2jSw4ZTuoVMfZ8vZe -FVzLo/+sV8qokU6wBTEOAmZQ7e/zZV4qAoH2Z3Vj/uD1Zr/MXYyh81RdXpDqIXwV -Z29LEOa2eTGFEdvfG+tdvvuIvSdF3+WbLrwn2ECfwJ8zmKyTauPRV4pj7ks+wkBI -EQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB6dmuWBOpFfDdu0mdsDb8XnJY1svjH -4kbztXhjQJ/WuhCUIwvXFyz9dqQCq+TbJUbUEzZJEfaq1uaI3iB5wd35ArSoAGJA -k0lonzyeSM+cmNOe/5BPqWhd1qPwbsfgMoCCkZUoTT5Rvw6yt00XIqZzMqrsvRBX -hAcUW3zBtFQNP6aQqsMdn4ClZE0WHf+LzWy2NQh+Sf46tSYBHELfdUawgR789PB4 -/gNjAeklq06JmE/3gELijwaijVIuUsMC9ua//ITk4YIFpqanPtka+7BpfTegPGNs -HCj1g7Jot97oQMuvDOJeso91aiSA+gutepCClZICT8LxNRkY3ZlXYp92 +MIIDPjCCAiYCCQCmkoCBehOyYTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww +CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xOTAzMjky +MzE5MDlaFw0yMDAzMjgyMzE5MDlaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD +QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY +MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmmbt8r +YNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2ZBro +AuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo4YI4 +xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN124x +giFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ+K/Y +p/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +AQAEWn3WAwAbd64f5jo2w4076s2qFiCJjPWoxO6bO75FgFFtw/NNev8pxGVw1ehg +HiTO6VRYolL5S/RKOchjA83AcDEBjgf8fKtvTmE9kxZSUIo4kIvv8V9ZM72gJhDN +8D/lXduTZ9JMwLOa1NUB8/I6CbaU3VzWkfodArKKpQF3M+LLgK03i12PD0KPQ5zv +bwaNoQo6cTmPNIdsVZETRvPqONiCUaQV57G74dGtjeirCh/DO5EYRtb1thgS7TGm ++Xg8OC5vZ6g0+xsrSqDBmWNtlI7S3bsL5C3LIEOOAL1ZJHRy2KvIGQ9ipb3XjnKS +N7/wlQduRyPH7oaD/o4xf5Gt -----END CERTIFICATE----- diff --git a/actix-http/tests/identity.pfx b/actix-http/tests/identity.pfx deleted file mode 100644 index 946e3b8b8ae10e19a11e7ac6eead66b12fff0014..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5549 zcmY+GRZtv&vTbn*Fa-DD5S(Fv;O=h0gF_fx!{8nycyM=jcXvr}cXtMNxH(n#z4P8j zS68jQyT2EE0A2|kEIfMvo;?yO<4>8N_ZYCqu-O54MhF3T`v0&tdjMMWe%lL7KUFAFV5{u;owkU`~uKqm}O*fBSo5RVYIG` zPAKF6ePO1*(FhW)-QpFAvtO?cVWGD0hs0WO4>qy>9T48E19Q`LuD0r#V<$Vj_c2yp z)4}gHs(AF<^Bo{R_`YId+=%k!c;why`_I0GXxv)FEmD)RG7Vs?g`z@xC^k*0+`Ps8 zZQH$QkvVVeOE(P8=g*0kvj|2!A! zCaUuJ^XO0NPlE$=oQRK9ti`Ic7JJnq;osnZ4OA`G7m{`HKP{cH&`YLS z&7cXaq1TKaOG&uTJqqY25!-%6M82yrFSuJv{`JhJUOE4ZMT1J%h8u3pSuQ$qle0}C z2}0Lk)3OpsFm1yeJ1|XW9gt0oX1PIiJ+HG_ccQbIH}C6hb|aPpRO(@Rt|u~DJIX%l zC+^x;>&_>s!^v1hwDp-biu2rspT|oc!3;MUJ>ui2i(Lamzq&B9OCcN`j&N`^wTcd1 z`DFH$T83h&1Ws&{N(khR##{-N3RSG@_ZPyM3SQ_TAXRRqyV#LUkE0&kEvE8Y61{fKJ+Y= z$eQ*1oOaeF^Z9A#-r@E7LmI)arBxsyT>V+(Ruq)&tBqzOi1sjBwNjPe_jraq(=2r^ zB|%@2bxrAnZr&d!&MNXZn&!iHPAs)aINufHy*7>&8Ap}7vY+Ji590{9Gq+KBpjkN0 zeCWDYHbCrNc%062ljm(+!6eM>ku}@T8$$TYjIT%~Y3z>YH2FxhEJ6lUL_#W3!=?ks zFo969*bgk}a!GRqXMSjjgd&?6Y%hBVuhL~74jyLlXNnJzvi?lWEo_HD7ZkXXB+vYf z**da2rqVV>3tDz^j`grt{G5Ye8`Q@KgQelPxGF%zLkZ=m7N>eor9pQmK6B$F&s9TaFeP0w?b5Q2p`A;be~m;x!mwj;I4ye%g!Op2Z^tb4S}i z#o}zOP0;F#mw3YEF_5DmxuBJQ{MZN!`^i6PwkgfA(n$bs9PQx6aZB5p$2&d$+`VjO zsH)tO=SExUR825&T#?}~JqRDb1{y6YsTMn zYINH#Ru0n1kxJc|=rVjd@`uh*nzIv3n@lt8^$P6GC5xIW1?7XLu5&GtX$Ho%rp5@a z<;*{8D{Rd@ocNX}STxWU(2moCw*4dI^{&HccPIwaXF^%V^P@gTFF*ow(`z_5$lNR7B+?<1E> zULlb}pDx~mAAb5{pg@(IH|PW09Pz6r7!}<9>(fAn8=3nRL|EV^f5o-!1D@`uY_E0= zXWciaPcKF60&?K+MAnU0gz}SuZb>wVAeRY>Lz9(&Zu_rB_JiKSV8HOqO3;vSD41G~ zIYpO4JcJ5K0+;O6x0VlNvNY%;v*XCt2V>-|%pxixP0jfH+xE^8^D3ZXs zS4AQ;$|oz)t4F{7(j7F7l{R}lJhwq)ekCh$|7`g4CXbIghAm6OSZ1L&K5-Y1w$mQa z_MBdP-vu45dbf@y=ls^2$D{4YS(f+0N}W49&$hLSRWl_WZB;uN`i7GQSU2{> z%tc5FX16D4Hc~M^2Q-}r|Th>@2!HT<`AbzLpW6XWl;-{}i)bscjNo9~Z zt>K*BFR$SlC#sBVi?$v)Y?HUyl)E2s`8W6ZXD9n+OhlM3@csT_+o|7G5+{-kChNBE zGoihxT`ld2`0D_n8Kx;B37>D}D7${tb4|NGV4*rXJY< zrP!d-L?ATkL~#^D#VB&5mZjsJsQ(E>4kqY74x%)7DdB0tEho2OB+t;4_6jhwgA|cO znS?z?-;5Y{h$UXY8kALQOM~}sKX{KB4C#6j4aK&Qk9w{6xr6GoWSrQBZ-V^FUe|IuEu|!Ho zP~_-_Q3K`aH^E3h7qd#rjmRUmA*ZPMaLSD!4zIf>se1cE%=z*bSCvx#B3|QUR$0#m9GAZ3L&y-$F1DIC& zT8gc<4SckFy#eY%&9U^Kz5>cD`L{z)t6x+lxMS@K!T}DzX*QtVExm3-I%(wjkMP#> z1(Cx)JgRslsAW|p`13(`X}#T<>eipuNW4PA4R2-@N+Vc&(y87pk_BSeS+d6)P$^TP z`?$x%WPFPWh9eb*vW#gb#gty{p~gAkXo)>T5Uf}c#r*~bw?jGhA9M7i8I5Xoby}f^ z^p4Qzi_ghn7*)HR6CmO5t+f(DYnCA3GX_!BFzYWFViu9Jn~Rh{W4lS>Ien~yruGbu z>(;O4jayyFSMEP1yQ9A{eQ^Hh(X@0+auu!ON=}-efn0d?U5LMh$zI#vo9nqvQ$058 z0%`dSn#OpIe|FctVo1fv^b^C(=MqC(usOYfc9zoBNAJEY_F!S;aKK8MEaXZ2?J0*q z%9PBkwW53|md4e|UjFDs!BwW$KTztoZy9^)QA!(U)itkV!h$GWP*eYJRt+x@x5V%i z1J7`#SEWSE_@>C3i32lz2g3U@e}8X+mAz+L`pS5%*YB7Dxr&GE4t*>KB6I zE-@2=x9^2U&Ux`Z5<+-#U+{a@#CzMU?Leqv9AhmP6iaWp>Oq!NsxHbS=EW1!zJLz{ zMUGYEB7n6q3Er#o<|Z`u0MwrUN4&EGP-_taP%Ho8(tlHkg!X?l`~xi9ztHXDeK*m&V~nDC|pLVujbH(b=fk1EfnQhXPjX1C48B*(7^BO>cFO~ zAH*SPpf#PD###N!E9oD!h87(U5h5>lHJLd?LymUDW$S z^HqD#EbaAcgn)aq3MVH(f*KmTMjHO5dW|_+katLspc=S!v^H3wli*n6ojf^qf3K=y zKr%Yay%^*Xvh0;}J@-TW@bcRzax<}Q{(@FjI+hfy@)|L*hscS(TttTvz_%Nv+(z8GGr?Ic>si}%oD zjS`B@EE+;f!(VuH>B7DBvpXijW%<{YjhQlo9fhwvuO{nH%@)BNiq<1`YY&2FCcm1v z)V&H!Kcj^3Bt0y%<I%;IH=P zJ41oky-lS{I9?XOY~Id^5UX<7RGwt!+GLyLENxHkj-snrrD9wg(faIi6I{jBh1go{ zM*ViN9ui7wip-=5;2)#r3!%>8z?BacE<1_@ALhkFNVIX%-ABNFWSr=jiLQ6%-{KRS zWwq<$wd1t!5~>t;g)EsTUt{vq;Fq-VE-~ml6ZXxgTNfUz_@@T7 zjsLv05k4~6I(7sOoX;Me9Mx*!2n`*&G62*4lL#eRXMj)}# zKr96@p5g@T(q@%oV#NRfPt?05WUo1wQk&aO{Pmwj7rdpbgcv6`Z78Sujv^Cw!}t9r`^Lc8t$WvLza7iBfkmc zDDY9Tit~6VzqS0L=Ayqpm%buj8Ag(=8<}4__o+jk*7sRDS~t>ecLxvu9W`08b;!5* zVykt!s+BvMXq)=q=y6Z4d=}r*-a;}j-*Qyy?4M4cOLy-!>N4}f4H%^$+ju=$d;iJ7 zPw8!Js)`{-TTglxv%Sm^K45gm#!5u2ni5W%dv9_+kEJG*<)*>LnTIO3 zP6HOD&&FrUjtF+byLCKHD-ACb%_vi{l0xPj|E3=MNnc)wn90{jb#I)vL$jsxtHlPu zCN(WOd)y*If;(Pc2D_T*H8TGC6#FjyA&L=1eB$U3^IK?5o$tR7-J`4e7hlpb7w~C+Agyessj{qzudSzQHf)p zE3bG;_o5JE@|R;^XOzYSMRqL`ey& z7cxd^&=J@~sxSdNJ~4`WKG3Pf5(-q%1&@Bp@h^T2p}7IVJL}{Ir#}ZYrY(`qOdx+V z!4-keXG9fPwZ72Z>0InsQ!U51)YwT!#5(o7Q73y)O7Vj^xant+Mw#45BH$9?B1vV- zK-S5Bmx_T9S^3JTc)fx@Q&R72RSU{u74roB{&3hlzknbj{dy%m?DxD5GH*40Tk$q; zDxcEj{-~XXePdPnzs!=S2lkV$IweaW8lBM@z7iZlAu5N%pYus=+D*DQyFs=GYfXl#{yYa^Ur z^wdzws}mB6Y~FeN1FIrbx)Pe=LH~}x^wgbl7Mnn)HlS|~MKWejJ*=#vF+_%p7!)FC z*>GliQit=X=~)<^UO-kl>$hKAhy@PbN%T6E8AFDRIPqP?3nKbu9SC1*x-_q%oa>un z)jigLjSg;Ie=Sf4&{X0(O6asc*}f}KP)&dmbcEmjIu<#h|51e3-$TXpM$ACpy4wx^ z7~&b%^6mSNlP@DXnZzWuf4O5+N;qfkl$8#UJw?Z-lL=nl=k*Qj5K!X_jNe5*ceN|1 zJO(G;HL??jza#IvW7CL&O%M}9wD`Q<7M?twO`H?+^WCB~JG8Y5`NHaesh58pW(2{G0QEPc8si#*pBZg zbmb#%Lx#-6A2aE}_`5AxI8xhZ#FL@NXPYMwK!#-*abrgDL>W=V)NBx_S5!i=ATh~B zoT!rujGkse$2dK9!A$E8ILE})=TNqjus$Y5VZgwNlvHx9Q@B&7X378MKMHcNc9XmD z{1dA1N8T7+{i%3V@mO!?q$Lgg=zTVwG9vk)dN9s4cwe diff --git a/actix-http/tests/key.pem b/actix-http/tests/key.pem index 50ded0ce..2afbf549 100644 --- a/actix-http/tests/key.pem +++ b/actix-http/tests/key.pem @@ -1,28 +1,27 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDNlRcydLlfwdbF -O8cBzzZDZ3fZ+I9Aa0P5+nc5X3SXpEL2OAx0fjJNVSBfmZhhxbSTz1Fo/yDrUfpv -voX2Bx3JnWJ2oojUBT1p4FQ9XzCSui6FH/o6a/x8DvKv21WY2jneM83tbIqgGhTS -vtiP0TODeqO//zTwmWBvLcSF26u7LJ929K3lkVec7PYPdIX0NLjKszaNLDhlO6hU -x9ny9l4VXMuj/6xXyqiRTrAFMQ4CZlDt7/NlXioCgfZndWP+4PVmv8xdjKHzVF1e -kOohfBVnb0sQ5rZ5MYUR298b612++4i9J0Xf5ZsuvCfYQJ/AnzOYrJNq49FXimPu -Sz7CQEgRAgMBAAECggEBALC547EaKmko5wmyM4dYq9sRzTPxuqO0EkGIkIkfh8j8 -ChxDXmGeQnu8HBJSpW4XWP5fkCpkd9YTKOh6rgorX+37f7NgUaOBxaOIlqITfFwF -9Qu3y5IBVpEHAJUwRcsaffiILBRX5GtxQElSijRHsLLr8GySZN4X25B3laNEjcJe -NWJrDaxOn0m0MMGRvBpM8PaZu1Mn9NWxt04b/fteVLdN4TAcuY9TgvVZBq92S2FM -qvZcnJCQckNOuMOptVdP45qPkerKUohpOcqBfIiWFaalC378jE3Dm68p7slt3R6y -I1wVqCI4+MZfM3CtKcYJV0fdqklJCvXORvRiT8OZKakCgYEA5YnhgXOu4CO4DR1T -Lacv716DPyHeKVa6TbHhUhWw4bLwNLUsEL98jeU9SZ6VH8enBlDm5pCsp2i//t9n -8hoykN4L0rS4EyAGENouTRkLhtHfjTAKTKDK8cNvEaS8NOBJWrI0DTiHtFbCRBvI -zRx5VhrB5H4DDbqn7QV9g+GBKvMCgYEA5Ug3bN0RNUi3KDoIRcnWc06HsX307su7 -tB4cGqXJqVOJCrkk5sefGF502+W8m3Ldjaakr+Q9BoOdZX6boZnFtVetT8Hyzk1C -Rkiyz3GcwovOkQK//UmljsuRjgHF+PuQGX5ol4YlJtXU21k5bCsi1Tmyp7IufiGV -AQRMVZVbeesCgYA/QBZGwKTgmJcf7gO8ocRAto999wwr4f0maazIHLgICXHNZFsH -JmzhANk5jxxSjIaG5AYsZJNe8ittxQv0l6l1Z+pkHm5Wvs1NGYIGtq8JcI2kbyd3 -ZBtoMU1K1FUUUPWFq3NSbVBfrkSL1ggoFP+ObYMePmcDAntBgfDLRXl9ZwKBgQCt -/dh5l2UIn27Gawt+EkXX6L8WVTQ6xoZhj/vZyPe4tDip14gGTXQQ5RUfDj7LZCZ2 -6P/OrpAU0mnt7F8kCfI7xBY0EUU1gvGJLn/q5heElt2hs4mIJ4woSZjiP7xBTn2y -qveqDNVCnEBUWGg4Cp/7WTaXBaM8ejV9uQpIY/gwEwKBgQCCYnd9fD8L4nGyOLoD -eUzMV7G8TZfinlxCNMVXfpn4Z8OaYHOk5NiujHK55w4ghx06NQw038qhnX0ogbjU -caWOwCIbrYgx2fwYuOZbJFXdjWlpjIK3RFOcbNCNgCRLT6Lgz4uZYZ9RVftADvMi -zR1QsLWnIvARbTtOPfZqizT2gQ== ------END PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmm +bt8rYNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2 +ZBroAuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo +4YI4xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN +124xgiFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ ++K/Yp/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABAoIBAQC4lzyQd+ITEbi+ +dTxJuQj94hgHB1htgKqU888SLI5F9nP6n67y9hb5N9WygSp6UWbGqYTFYwxlPMKr +22p2WjL5NTsTcm+XdIKQZW/3y06Mn4qFefsT9XURaZriCjihfU2BRaCCNARSUzwd +ZH4I6n9mM7KaH71aa7v6ZVoahE9tXPR6hM+SHQEySW4pWkEu98VpNNeIt6vP7WF9 +ONGbRa+0En4xgkuaxem2ZYa/GZFFtdQRkroNMhIRlfcPpkjy8DCc8E5RAkOzKC3O +lnxQwt+tdNNkGZz02ed2hx/YHPwFYy76y6hK5dxq74iKIaOc8U5t0HjB1zVfwiR0 +5mcxMncxAoGBAP+RivwXZ4FcxDY1uyziF+rwlC/1RujQFEWXIxsXCnba5DH3yKul +iKEIZPZtGhpsnQe367lcXcn7tztuoVjpAnk5L+hQY64tLwYbHeRcOMJ75C2y8FFC +NeG5sQsrk3IU1+jhGvrbE7UgOeAuWJmv0M1vPNB/+hGoZBW5W5uU1x89AoGBANtA +AhLtAcqQ/Qh2SpVhLljh7U85Be9tbCGua09clkYFzh3bcoBolXKH18Veww0TP0yF +0748CKw1A+ITbTVFV+vKvi4jzIxS7mr4wYtVCMssbttQN7y3l30IDxJwa9j3zTJx +IUn5OMMLv1JyitLId8HdOy1AdU3MkpJzdLyi1mFfAoGBAL3kL4fGABM/kU7SN6RO +zgS0AvdrYOeljBp1BRGg2hab58g02vamxVEZgqMTR7zwjPDqOIz+03U7wda4Ccyd +PUhDNJSB/r6xNepshZZi642eLlnCRguqjYyNw72QADtYv2B6uehAlXEUY8xtw0lW +OGgcSeyF2pH6M3tswWNlgT3lAoGAQ/BttBelOnP7NKgTLH7Usc4wjyAIaszpePZn +Ykw6dLBP0oixzoCZ7seRYSOgJWkVcEz39Db+KP60mVWTvbIjMHm+vOVy+Pip0JQM +xXQwKWU3ZNZSrzPkyWW55ejYQn9nIn5T5mxH3ojBXHcJ9Y8RLQ20zKzwrI77zE3i +mqGK9NkCgYEAq3dzHI0DGAJrR19sWl2LcqI19sj5a91tHx4cl1dJXS/iApOLLieU +zyUGkwfsqjHPAZ7GacICeBojIn/7KdPdlSKAbGVAU3d4qzvFS0qmWzObplBz3niT +Xnep2XLaVXqwlFJZZ6AHeKzYmMH0d0raiou2bpEUBqYizy2fi3NI4mA= +-----END RSA PRIVATE KEY----- diff --git a/src/server.rs b/src/server.rs index 9055be30..2817f549 100644 --- a/src/server.rs +++ b/src/server.rs @@ -11,11 +11,10 @@ use parking_lot::Mutex; use net2::TcpBuilder; -// #[cfg(feature = "tls")] -// use native_tls::TlsAcceptor; - #[cfg(feature = "ssl")] use openssl::ssl::{SslAcceptor, SslAcceptorBuilder}; +#[cfg(feature = "rust-tls")] +use rustls::ServerConfig as RustlsServerConfig; struct Socket { scheme: &'static str, @@ -254,19 +253,6 @@ where Ok(self) } - // #[cfg(feature = "tls")] - // /// Use listener for accepting incoming tls connection requests - // /// - // /// HttpServer does not change any configuration for TcpListener, - // /// it needs to be configured before passing it to listen() method. - // pub fn listen_nativetls(self, lst: net::TcpListener, acceptor: TlsAcceptor) -> Self { - // use actix_server::ssl; - - // self.listen_with(lst, move || { - // ssl::NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - // }) - // } - #[cfg(feature = "ssl")] /// Use listener for accepting incoming tls connection requests /// @@ -294,7 +280,7 @@ where let addr = lst.local_addr().unwrap(); self.sockets.push(Socket { addr, - scheme: "http", + scheme: "https", }); self.builder = Some(self.builder.take().unwrap().listen( @@ -320,12 +306,52 @@ where /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_rustls(self, lst: net::TcpListener, config: ServerConfig) -> Self { - use super::{RustlsAcceptor, ServerFlags}; + pub fn listen_rustls( + mut self, + lst: net::TcpListener, + config: RustlsServerConfig, + ) -> io::Result { + self.listen_rustls_inner(lst, config)?; + Ok(self) + } - self.listen_with(lst, move || { - RustlsAcceptor::with_flags(config.clone(), flags).map_err(|_| ()) - }) + #[cfg(feature = "rust-tls")] + fn listen_rustls_inner( + &mut self, + lst: net::TcpListener, + mut config: RustlsServerConfig, + ) -> io::Result<()> { + use actix_server::ssl::{RustlsAcceptor, SslError}; + + let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; + config.set_protocols(&protos); + + let acceptor = RustlsAcceptor::new(config); + let factory = self.factory.clone(); + let cfg = self.config.clone(); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + scheme: "https", + }); + + self.builder = Some(self.builder.take().unwrap().listen( + format!("actix-web-service-{}", addr), + lst, + move || { + let c = cfg.lock(); + acceptor.clone().map_err(|e| SslError::Ssl(e)).and_then( + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown) + .finish(factory()) + .map_err(|e| SslError::Service(e)) + .map_init_err(|_| ()), + ) + }, + )?); + Ok(()) } /// The socket address to bind @@ -372,22 +398,6 @@ where } } - // #[cfg(feature = "tls")] - // /// The ssl socket address to bind - // /// - // /// To bind multiple addresses this method can be called multiple times. - // pub fn bind_nativetls( - // self, - // addr: A, - // acceptor: TlsAcceptor, - // ) -> io::Result { - // use actix_server::ssl::NativeTlsAcceptor; - - // self.bind_with(addr, move || { - // NativeTlsAcceptor::new(acceptor.clone()).map_err(|_| ()) - // }) - // } - #[cfg(feature = "ssl")] /// Start listening for incoming tls connections. /// @@ -415,16 +425,15 @@ where /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn bind_rustls( - self, + mut self, addr: A, - builder: ServerConfig, + config: RustlsServerConfig, ) -> io::Result { - use super::{RustlsAcceptor, ServerFlags}; - use actix_service::NewServiceExt; - - self.bind_with(addr, move || { - RustlsAcceptor::with_flags(builder.clone(), flags).map_err(|_| ()) - }) + let sockets = self.bind2(addr)?; + for lst in sockets { + self.listen_rustls_inner(lst, config.clone())?; + } + Ok(self) } } diff --git a/tests/cert.pem b/tests/cert.pem index db04fbfa..eafad524 100644 --- a/tests/cert.pem +++ b/tests/cert.pem @@ -1,31 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV -BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh -bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4 -MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD -T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF -cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk -L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ -EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU -05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh -4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA -2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng -dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4 -e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT -2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa -TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID -AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB -AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm -ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g -4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1 -hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe -0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq -seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi -7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO -3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5 -XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq -GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr -E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv +MIIDPjCCAiYCCQCmkoCBehOyYTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww +CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xOTAzMjky +MzE5MDlaFw0yMDAzMjgyMzE5MDlaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD +QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY +MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmmbt8r +YNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2ZBro +AuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo4YI4 +xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN124x +giFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ+K/Y +p/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +AQAEWn3WAwAbd64f5jo2w4076s2qFiCJjPWoxO6bO75FgFFtw/NNev8pxGVw1ehg +HiTO6VRYolL5S/RKOchjA83AcDEBjgf8fKtvTmE9kxZSUIo4kIvv8V9ZM72gJhDN +8D/lXduTZ9JMwLOa1NUB8/I6CbaU3VzWkfodArKKpQF3M+LLgK03i12PD0KPQ5zv +bwaNoQo6cTmPNIdsVZETRvPqONiCUaQV57G74dGtjeirCh/DO5EYRtb1thgS7TGm ++Xg8OC5vZ6g0+xsrSqDBmWNtlI7S3bsL5C3LIEOOAL1ZJHRy2KvIGQ9ipb3XjnKS +N7/wlQduRyPH7oaD/o4xf5Gt -----END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem index aac387c6..2afbf549 100644 --- a/tests/key.pem +++ b/tests/key.pem @@ -1,51 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP -n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M -IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5 -4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ -WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk -oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli -JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6 -/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD -YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP -wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA -69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA -AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/ -9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm -YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR -6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM -ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI -7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab -L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+ -vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ -b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz -0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL -OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI -6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC -71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g -9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu -bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb -IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga -/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc -KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2 -iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP -tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD -jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY -l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj -gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh -Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q -1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW -t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI -fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9 -5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt -+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc -3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf -cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T -qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU -DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K -5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc -fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc -Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ -4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6 -I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c= +MIIEpQIBAAKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmm +bt8rYNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2 +ZBroAuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo +4YI4xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN +124xgiFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ ++K/Yp/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABAoIBAQC4lzyQd+ITEbi+ +dTxJuQj94hgHB1htgKqU888SLI5F9nP6n67y9hb5N9WygSp6UWbGqYTFYwxlPMKr +22p2WjL5NTsTcm+XdIKQZW/3y06Mn4qFefsT9XURaZriCjihfU2BRaCCNARSUzwd +ZH4I6n9mM7KaH71aa7v6ZVoahE9tXPR6hM+SHQEySW4pWkEu98VpNNeIt6vP7WF9 +ONGbRa+0En4xgkuaxem2ZYa/GZFFtdQRkroNMhIRlfcPpkjy8DCc8E5RAkOzKC3O +lnxQwt+tdNNkGZz02ed2hx/YHPwFYy76y6hK5dxq74iKIaOc8U5t0HjB1zVfwiR0 +5mcxMncxAoGBAP+RivwXZ4FcxDY1uyziF+rwlC/1RujQFEWXIxsXCnba5DH3yKul +iKEIZPZtGhpsnQe367lcXcn7tztuoVjpAnk5L+hQY64tLwYbHeRcOMJ75C2y8FFC +NeG5sQsrk3IU1+jhGvrbE7UgOeAuWJmv0M1vPNB/+hGoZBW5W5uU1x89AoGBANtA +AhLtAcqQ/Qh2SpVhLljh7U85Be9tbCGua09clkYFzh3bcoBolXKH18Veww0TP0yF +0748CKw1A+ITbTVFV+vKvi4jzIxS7mr4wYtVCMssbttQN7y3l30IDxJwa9j3zTJx +IUn5OMMLv1JyitLId8HdOy1AdU3MkpJzdLyi1mFfAoGBAL3kL4fGABM/kU7SN6RO +zgS0AvdrYOeljBp1BRGg2hab58g02vamxVEZgqMTR7zwjPDqOIz+03U7wda4Ccyd +PUhDNJSB/r6xNepshZZi642eLlnCRguqjYyNw72QADtYv2B6uehAlXEUY8xtw0lW +OGgcSeyF2pH6M3tswWNlgT3lAoGAQ/BttBelOnP7NKgTLH7Usc4wjyAIaszpePZn +Ykw6dLBP0oixzoCZ7seRYSOgJWkVcEz39Db+KP60mVWTvbIjMHm+vOVy+Pip0JQM +xXQwKWU3ZNZSrzPkyWW55ejYQn9nIn5T5mxH3ojBXHcJ9Y8RLQ20zKzwrI77zE3i +mqGK9NkCgYEAq3dzHI0DGAJrR19sWl2LcqI19sj5a91tHx4cl1dJXS/iApOLLieU +zyUGkwfsqjHPAZ7GacICeBojIn/7KdPdlSKAbGVAU3d4qzvFS0qmWzObplBz3niT +Xnep2XLaVXqwlFJZZ6AHeKzYmMH0d0raiou2bpEUBqYizy2fi3NI4mA= -----END RSA PRIVATE KEY----- diff --git a/tests/test_server.rs b/tests/test_server.rs index cd5e95d2..717df233 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,4 +1,6 @@ use std::io::{Read, Write}; +use std::sync::mpsc; +use std::thread; use actix_http::http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, @@ -14,10 +16,12 @@ use flate2::Compression; use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; -use actix_web::{dev::HttpMessageBody, web, App}; +use actix_web::{dev::HttpMessageBody, http, test, web, App, HttpResponse, HttpServer}; #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] use actix_web::middleware::encoding; +#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] +use actix_web::middleware::encoding::BodyEncoding; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -678,69 +682,93 @@ fn test_brotli_encoding_large() { // assert_eq!(bytes, Bytes::from(data)); // } -// #[cfg(all(feature = "rust-tls", feature = "ssl"))] -// #[test] -// fn test_reading_deflate_encoding_large_random_ssl() { -// use actix::{Actor, System}; -// use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; -// use rustls::internal::pemfile::{certs, rsa_private_keys}; -// use rustls::{NoClientAuth, ServerConfig}; -// use std::fs::File; -// use std::io::BufReader; +#[cfg(all( + feature = "rust-tls", + feature = "ssl", + any(feature = "flate2-zlib", feature = "flate2-rust") +))] +#[test] +fn test_reading_deflate_encoding_large_random_ssl() { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use rustls::internal::pemfile::{certs, rsa_private_keys}; + use rustls::{NoClientAuth, ServerConfig}; + use std::fs::File; + use std::io::BufReader; -// // load ssl keys -// let mut config = ServerConfig::new(NoClientAuth::new()); -// let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); -// let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); -// let cert_chain = certs(cert_file).unwrap(); -// let mut keys = rsa_private_keys(key_file).unwrap(); -// config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + let addr = TestServer::unused_addr(); + let (tx, rx) = mpsc::channel(); -// let data = rand::thread_rng() -// .sample_iter(&Alphanumeric) -// .take(160_000) -// .collect::(); + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); -// let srv = test::TestServer::build().rustls(config).start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); + thread::spawn(move || { + let sys = actix_rt::System::new("test"); -// let mut rt = System::new("test"); + // load ssl keys + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = rsa_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); -// // client connector -// let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); -// builder.set_verify(SslVerifyMode::NONE); -// let conn = client::ClientConnector::with_connector(builder.build()).start(); + let srv = HttpServer::new(|| { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + Ok::<_, Error>( + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .body(bytes), + ) + }))) + }) + .bind_rustls(addr, config) + .unwrap() + .start(); -// // encode data -// let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); -// e.write_all(data.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + let _ = tx.send((srv, actix_rt::System::current())); + let _ = sys.run(); + }); + let (srv, _sys) = rx.recv().unwrap(); + let client = test::run_on(|| { + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); -// // client request -// let request = client::ClientRequest::build() -// .uri(srv.url("/")) -// .method(http::Method::POST) -// .header(http::header::CONTENT_ENCODING, "deflate") -// .with_connector(conn) -// .body(enc) -// .unwrap(); -// let response = rt.block_on(request.send()).unwrap(); -// assert!(response.status().is_success()); + awc::Client::build() + .connector( + awc::Connector::new() + .timeout(std::time::Duration::from_millis(500)) + .ssl(builder.build()) + .service(), + ) + .finish() + }); -// // read response -// let bytes = rt.block_on(response.body()).unwrap(); -// assert_eq!(bytes.len(), data.len()); -// assert_eq!(bytes, Bytes::from(data)); -// } + // encode data + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let _req = client + .post(format!("https://{}/", addr)) + .header(http::header::CONTENT_ENCODING, "deflate") + .send_body(enc); + + // TODO: fix + // let response = test::block_on(req).unwrap(); + // assert!(response.status().is_success()); + + // read response + // let bytes = test::block_on(response.body()).unwrap(); + // assert_eq!(bytes.len(), data.len()); + // assert_eq!(bytes, Bytes::from(data)); + + // stop + let _ = srv.stop(false); +} // #[cfg(all(feature = "tls", feature = "ssl"))] // #[test] From 00526f60dc6834314caaab341359758939f94fa5 Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 30 Mar 2019 02:29:11 +0300 Subject: [PATCH 1170/1635] Impl BodyEncoding for Response (#740) --- actix-http/src/response.rs | 12 ++++++++++++ src/middleware/compress.rs | 9 ++++++++- tests/test_server.rs | 24 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 29a850fa..4da0f642 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -189,6 +189,18 @@ impl Response { self.head.keep_alive() } + /// Responses extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.head.extensions.borrow() + } + + /// Mutable reference to a the response's extensions + #[inline] + pub fn extensions_mut(&mut self) -> RefMut { + self.head.extensions.borrow_mut() + } + /// Get body os this response #[inline] pub fn body(&self) -> &ResponseBody { diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 5c6bad87..d797e125 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use actix_http::body::MessageBody; use actix_http::encoding::Encoder; use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; -use actix_http::ResponseBuilder; +use actix_http::{Response, ResponseBuilder}; use actix_service::{Service, Transform}; use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; @@ -27,6 +27,13 @@ impl BodyEncoding for ResponseBuilder { } } +impl BodyEncoding for Response { + fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { + self.extensions_mut().insert(Enc(encoding)); + self + } +} + #[derive(Debug, Clone)] /// `Middleware` for compressing response body. /// diff --git a/tests/test_server.rs b/tests/test_server.rs index 717df233..11ba1b6a 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -96,10 +96,20 @@ fn test_body_encoding_override() { .service(web::resource("/").route(web::to(|| { use actix_web::middleware::encoding::BodyEncoding; Response::Ok().encoding(ContentEncoding::Deflate).body(STR) + }))) + .service(web::resource("/raw").route(web::to(|| { + use actix_web::middleware::encoding::BodyEncoding; + let body = actix_web::dev::Body::Bytes(STR.into()); + let mut response = Response::with_body(actix_web::http::StatusCode::OK, body); + + response.encoding(ContentEncoding::Deflate); + + response }))), ) }); + // Builder let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); assert!(response.status().is_success()); @@ -111,6 +121,20 @@ fn test_body_encoding_override() { e.write_all(bytes.as_ref()).unwrap(); let dec = e.finish().unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + // Raw Response + let mut response = srv.block_on(srv.request(actix_web::http::Method::GET, srv.url("/raw")).no_decompress().send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(HttpMessageBody::new(&mut response)).unwrap(); + + // decode + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] From 3220777ff989b922c7e124f63d296221316d9e87 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 18:22:49 -0700 Subject: [PATCH 1171/1635] Added ws::Message::Nop, no-op websockets message --- actix-http/CHANGES.md | 6 +++++- actix-http/src/ws/codec.rs | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e9596349..5659597c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [0.1.0-alpha.2] - 2019-xx-xx +## [0.1.0-alpha.2] - 2019-03-29 + +### Added + +* Added ws::Message::Nop, no-op websockets message ### Changed diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 286d15f8..ad599ffa 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -18,6 +18,8 @@ pub enum Message { Pong(String), /// Close message with optional reason Close(Option), + /// No-op. Useful for actix-net services + Nop, } /// `WebSocket` frame @@ -87,6 +89,7 @@ impl Encoder for Codec { Parser::write_message(dst, txt, OpCode::Pong, true, !self.server) } Message::Close(reason) => Parser::write_close(dst, reason, !self.server), + Message::Nop => (), } Ok(()) } From 193f8fb2d9b11995b1e1dc35041e2f4d10095299 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 18:51:07 -0700 Subject: [PATCH 1172/1635] update tests --- actix-http/Cargo.toml | 3 +- actix-http/tests/test_client.rs | 8 ++-- actix-http/tests/test_server.rs | 75 ++++++++++++++++----------------- awc/src/ws.rs | 8 ++-- test-server/Cargo.toml | 5 +-- test-server/src/lib.rs | 26 ++++++++---- tests/test_server.rs | 12 ++++-- 7 files changed, 74 insertions(+), 63 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 180cda78..0c910cb1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -96,8 +96,9 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-server = { version = "0.4.0", features=["ssl"] } +actix-server = { version = "0.4.1", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } +#actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } actix-http-test = { path = "../test-server", features=["ssl"] } env_logger = "0.6" diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 1ca7437d..78b99970 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -52,18 +52,18 @@ fn test_h1_v2() { assert!(response.status().is_success()); let request = srv.get().header("x-test", "111").send(); - let mut response = srv.block_on(request).unwrap(); + let response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let mut response = srv.block_on(srv.post().send()).unwrap(); + let response = srv.block_on(srv.post().send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 455edfec..f1f82b08 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -13,8 +13,7 @@ use futures::stream::{once, Stream}; use actix_http::body::Body; use actix_http::error::PayloadError; use actix_http::{ - body, error, http, http::header, Error, HttpMessage, HttpService, KeepAlive, - Request, Response, + body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, }; fn load_body(stream: S) -> impl Future @@ -145,10 +144,10 @@ fn test_h2_body() -> std::io::Result<()> { ) }); - let mut response = srv.block_on(srv.sget().send_body(data.clone())).unwrap(); + let response = srv.block_on(srv.sget().send_body(data.clone())).unwrap(); assert!(response.status().is_success()); - let body = srv.block_on(load_body(response.take_payload())).unwrap(); + let body = srv.load_body(response).unwrap(); assert_eq!(&body, data.as_bytes()); Ok(()) } @@ -436,11 +435,11 @@ fn test_h1_headers() { }) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from(data2)); } @@ -480,11 +479,11 @@ fn test_h2_headers() { }).map_err(|_| ())) }); - let mut response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from(data2)); } @@ -516,11 +515,11 @@ fn test_h1_body() { HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -538,11 +537,11 @@ fn test_h2_body2() { ) }); - let mut response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -552,7 +551,7 @@ fn test_h1_head_empty() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let mut response = srv.block_on(srv.head().send()).unwrap(); + let response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -564,7 +563,7 @@ fn test_h1_head_empty() { } // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } @@ -582,7 +581,7 @@ fn test_h2_head_empty() { ) }); - let mut response = srv.block_on(srv.shead().send()).unwrap(); + let response = srv.block_on(srv.shead().send()).unwrap(); assert!(response.status().is_success()); assert_eq!(response.version(), http::Version::HTTP_2); @@ -595,7 +594,7 @@ fn test_h2_head_empty() { } // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } @@ -607,7 +606,7 @@ fn test_h1_head_binary() { }) }); - let mut response = srv.block_on(srv.head().send()).unwrap(); + let response = srv.block_on(srv.head().send()).unwrap(); assert!(response.status().is_success()); { @@ -619,7 +618,7 @@ fn test_h1_head_binary() { } // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } @@ -641,7 +640,7 @@ fn test_h2_head_binary() { ) }); - let mut response = srv.block_on(srv.shead().send()).unwrap(); + let response = srv.block_on(srv.shead().send()).unwrap(); assert!(response.status().is_success()); { @@ -653,7 +652,7 @@ fn test_h2_head_binary() { } // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } @@ -713,11 +712,11 @@ fn test_h1_body_length() { }) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -740,11 +739,11 @@ fn test_h2_body_length() { ) }); - let mut response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -761,7 +760,7 @@ fn test_h1_body_chunked_explicit() { }) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -774,7 +773,7 @@ fn test_h1_body_chunked_explicit() { ); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -802,12 +801,12 @@ fn test_h2_body_chunked_explicit() { ) }); - let mut response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert!(response.status().is_success()); assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); // decode assert_eq!(bytes, Bytes::from_static(STR.as_ref())); @@ -822,7 +821,7 @@ fn test_h1_body_chunked_implicit() { }) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -835,7 +834,7 @@ fn test_h1_body_chunked_implicit() { ); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } @@ -854,11 +853,11 @@ fn test_h1_response_http_error_handling() { })) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } @@ -886,11 +885,11 @@ fn test_h2_response_http_error_handling() { ) }); - let mut response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } @@ -901,11 +900,11 @@ fn test_h1_service_error() { .h1(|_| Err::(error::ErrorBadRequest("error"))) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } @@ -924,10 +923,10 @@ fn test_h2_service_error() { ) }); - let mut response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget().send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response - let bytes = srv.block_on(load_body(response.take_payload())).unwrap(); + let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 9697210d..bc023c06 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -11,7 +11,7 @@ use cookie::{Cookie, CookieJar}; use futures::future::{err, Either, Future}; use tokio_timer::Timeout; -pub use actix_http::ws::{CloseCode, CloseReason, Frame, Message}; +pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; use crate::connect::BoxedSocket; use crate::error::{InvalidUrl, SendRequestError, WsClientError}; @@ -213,10 +213,8 @@ impl WebsocketsRequest { /// Complete request construction and connect to a websockets server. pub fn connect( mut self, - ) -> impl Future< - Item = (ClientResponse, Framed), - Error = WsClientError, - > { + ) -> impl Future), Error = WsClientError> + { if let Some(e) = self.err.take() { return Either::A(err(e.into())); } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 582d96ed..a9b58483 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -30,12 +30,11 @@ default = ["session"] session = ["cookie/secure"] # openssl -ssl = ["openssl", "actix-http/ssl", "actix-server/ssl", "awc/ssl"] +ssl = ["openssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.1" actix-rt = "0.2.1" -actix-http = { path = "../actix-http" } actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" @@ -61,4 +60,4 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = { path = ".." } +actix-web = "1.0.0-alpha.1" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 07a0e0b4..9eec065c 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -3,12 +3,12 @@ use std::sync::mpsc; use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::client::Connector; -use actix_http::ws; use actix_rt::{Runtime, System}; use actix_server::{Server, StreamServiceFactory}; -use awc::{Client, ClientRequest}; -use futures::future::{lazy, Future}; +use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; +use bytes::Bytes; +use futures::future::lazy; +use futures::{Future, Stream}; use http::Method; use net2::TcpBuilder; @@ -193,13 +193,16 @@ impl TestServerRuntime { self.client.request(method, path.as_ref()) } - /// Stop http server - fn stop(&mut self) { - System::current().stop(); + pub fn load_body( + &mut self, + response: ClientResponse, + ) -> Result + where + S: Stream + 'static, + { + self.block_on(response.body().limit(10_485_760)) } -} -impl TestServerRuntime { /// Connect to websocket server at a given path pub fn ws_at( &mut self, @@ -219,6 +222,11 @@ impl TestServerRuntime { { self.ws_at("/") } + + /// Stop http server + fn stop(&mut self) { + System::current().stop(); + } } impl Drop for TestServerRuntime { diff --git a/tests/test_server.rs b/tests/test_server.rs index 11ba1b6a..fc590ff0 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -100,7 +100,8 @@ fn test_body_encoding_override() { .service(web::resource("/raw").route(web::to(|| { use actix_web::middleware::encoding::BodyEncoding; let body = actix_web::dev::Body::Bytes(STR.into()); - let mut response = Response::with_body(actix_web::http::StatusCode::OK, body); + let mut response = + Response::with_body(actix_web::http::StatusCode::OK, body); response.encoding(ContentEncoding::Deflate); @@ -123,7 +124,13 @@ fn test_body_encoding_override() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); // Raw Response - let mut response = srv.block_on(srv.request(actix_web::http::Method::GET, srv.url("/raw")).no_decompress().send()).unwrap(); + let mut response = srv + .block_on( + srv.request(actix_web::http::Method::GET, srv.url("/raw")) + .no_decompress() + .send(), + ) + .unwrap(); assert!(response.status().is_success()); // read response @@ -134,7 +141,6 @@ fn test_body_encoding_override() { e.write_all(bytes.as_ref()).unwrap(); let dec = e.finish().unwrap(); assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] From d846328f36573a4b28ce354db73618049c0658fd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 21:13:39 -0700 Subject: [PATCH 1173/1635] fork cookie crate --- Cargo.toml | 12 +- actix-files/Cargo.toml | 7 +- actix-http/Cargo.toml | 12 +- actix-http/src/client/connection.rs | 4 +- actix-http/src/cookie/builder.rs | 240 +++++ actix-http/src/cookie/delta.rs | 71 ++ actix-http/src/cookie/draft.rs | 98 ++ actix-http/src/cookie/jar.rs | 653 ++++++++++++++ actix-http/src/cookie/mod.rs | 1087 +++++++++++++++++++++++ actix-http/src/cookie/parse.rs | 426 +++++++++ actix-http/src/cookie/secure/key.rs | 180 ++++ actix-http/src/cookie/secure/macros.rs | 40 + actix-http/src/cookie/secure/mod.rs | 10 + actix-http/src/cookie/secure/private.rs | 226 +++++ actix-http/src/cookie/secure/signed.rs | 185 ++++ actix-http/src/error.rs | 12 +- actix-http/src/h1/dispatcher.rs | 6 +- actix-http/src/h2/service.rs | 2 +- actix-http/src/httpmessage.rs | 11 +- actix-http/src/lib.rs | 18 +- actix-http/src/response.rs | 66 +- actix-http/src/test.rs | 40 +- actix-http/tests/test_client.rs | 17 +- actix-session/Cargo.toml | 6 +- actix-session/src/cookie.rs | 2 +- actix-web-actors/Cargo.toml | 12 +- actix-web-codegen/Cargo.toml | 9 +- awc/Cargo.toml | 9 +- awc/src/lib.rs | 2 +- awc/src/request.rs | 38 +- awc/src/response.rs | 5 +- awc/src/test.rs | 41 +- awc/src/ws.rs | 38 +- src/lib.rs | 2 +- src/middleware/identity.rs | 2 +- src/middleware/mod.rs | 2 +- src/request.rs | 2 - src/test.rs | 4 +- test-server/Cargo.toml | 11 +- test-server/src/lib.rs | 2 +- 40 files changed, 3357 insertions(+), 253 deletions(-) create mode 100644 actix-http/src/cookie/builder.rs create mode 100644 actix-http/src/cookie/delta.rs create mode 100644 actix-http/src/cookie/draft.rs create mode 100644 actix-http/src/cookie/jar.rs create mode 100644 actix-http/src/cookie/mod.rs create mode 100644 actix-http/src/cookie/parse.rs create mode 100644 actix-http/src/cookie/secure/key.rs create mode 100644 actix-http/src/cookie/secure/macros.rs create mode 100644 actix-http/src/cookie/secure/mod.rs create mode 100644 actix-http/src/cookie/secure/private.rs create mode 100644 actix-http/src/cookie/secure/signed.rs diff --git a/Cargo.toml b/Cargo.toml index 06c88c57..1bd089f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,10 +35,10 @@ members = [ ] [package.metadata.docs.rs] -features = ["ssl", "tls", "brotli", "flate2-zlib", "cookies", "client", "rust-tls"] +features = ["ssl", "tls", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] [features] -default = ["brotli", "flate2-zlib", "cookies", "client"] +default = ["brotli", "flate2-zlib", "secure-cookies", "client"] # http client client = ["awc"] @@ -53,7 +53,7 @@ flate2-zlib = ["actix-http/flate2-zlib"] flate2-rust = ["actix-http/flate2-rust"] # sessions feature, session require "ring" crate and c compiler -cookies = ["cookie", "actix-http/cookies"] +secure-cookies = ["actix-http/secure-cookies"] # tls tls = ["native-tls", "actix-server/ssl"] @@ -94,9 +94,6 @@ serde_urlencoded = "^0.5.3" time = "0.1" url = { version="1.7", features=["query_encoding"] } -# cookies support -cookie = { version="0.11", features=["secure", "percent-encode"], optional = true } - # ssl support native-tls = { version="0.2", optional = true } openssl = { version="0.10", optional = true } @@ -112,9 +109,6 @@ tokio-timer = "0.2.8" brotli2 = "0.3.2" flate2 = "1.0.2" -[replace] -"cookie:0.11.0" = { git = 'https://github.com/alexcrichton/cookie-rs.git' } - [profile.release] lto = true opt-level = 3 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index d6ae6754..058a1998 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,8 +18,8 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.1" -actix-http = "0.1.0-alpha.1" +#actix-web = "1.0.0-alpha.1" +actix-web = { path = ".." } actix-service = "0.3.3" bitflags = "1" @@ -33,4 +33,5 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0-alpha.1", features=["ssl"] } +#actix-web = { version = "1.0.0-alpha.1", features=["ssl"] } +actix-web = { path = "..", features=["ssl"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0c910cb1..e9bb5d9b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -16,7 +16,7 @@ edition = "2018" workspace = ".." [package.metadata.docs.rs] -features = ["ssl", "fail", "cookie", "brotli", "flate2-zlib"] +features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } @@ -32,9 +32,6 @@ default = [] # openssl ssl = ["openssl", "actix-connect/ssl"] -# cookies integration -cookies = ["cookie"] - # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -47,6 +44,9 @@ flate2-rust = ["flate2/rust_backend"] # failure integration. actix does not use failure anymore fail = ["failure"] +# support for secure cookies +secure-cookies = ["ring"] + [dependencies] actix-service = "0.3.4" actix-codec = "0.1.2" @@ -85,12 +85,14 @@ tokio-timer = "0.2" tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } +# for secure cookie +ring = { version = "0.14.6", optional = true } + # compression brotli2 = { version="^0.3.2", optional = true } flate2 = { version="^1.0.2", optional = true, default-features = false } # optional deps -cookie = { version="0.11", features=["percent-encode"], optional = true } failure = { version = "0.1.5", optional = true } openssl = { version="0.10", optional = true } diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 267c85d3..4522dbbd 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -184,11 +184,11 @@ where match self { EitherConnection::A(con) => Box::new( con.open_tunnel(head) - .map(|(head, framed)| (head, framed.map_io(|io| EitherIo::A(io)))), + .map(|(head, framed)| (head, framed.map_io(EitherIo::A))), ), EitherConnection::B(con) => Box::new( con.open_tunnel(head) - .map(|(head, framed)| (head, framed.map_io(|io| EitherIo::B(io)))), + .map(|(head, framed)| (head, framed.map_io(EitherIo::B))), ), } } diff --git a/actix-http/src/cookie/builder.rs b/actix-http/src/cookie/builder.rs new file mode 100644 index 00000000..2635572a --- /dev/null +++ b/actix-http/src/cookie/builder.rs @@ -0,0 +1,240 @@ +use std::borrow::Cow; + +use time::{Duration, Tm}; + +use super::{Cookie, SameSite}; + +/// Structure that follows the builder pattern for building `Cookie` structs. +/// +/// To construct a cookie: +/// +/// 1. Call [`Cookie::build`](struct.Cookie.html#method.build) to start building. +/// 2. Use any of the builder methods to set fields in the cookie. +/// 3. Call [finish](#method.finish) to retrieve the built cookie. +/// +/// # Example +/// +/// ```rust +/// use actix_http::cookie::Cookie; +/// use time::Duration; +/// +/// # fn main() { +/// let cookie: Cookie = Cookie::build("name", "value") +/// .domain("www.rust-lang.org") +/// .path("/") +/// .secure(true) +/// .http_only(true) +/// .max_age(Duration::days(1)) +/// .finish(); +/// # } +/// ``` +#[derive(Debug, Clone)] +pub struct CookieBuilder { + /// The cookie being built. + cookie: Cookie<'static>, +} + +impl CookieBuilder { + /// Creates a new `CookieBuilder` instance from the given name and value. + /// + /// This method is typically called indirectly via + /// [Cookie::build](struct.Cookie.html#method.build). + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::build("foo", "bar").finish(); + /// assert_eq!(c.name_value(), ("foo", "bar")); + /// ``` + pub fn new(name: N, value: V) -> CookieBuilder + where + N: Into>, + V: Into>, + { + CookieBuilder { + cookie: Cookie::new(name, value), + } + } + + /// Sets the `expires` field in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// # fn main() { + /// let c = Cookie::build("foo", "bar") + /// .expires(time::now()) + /// .finish(); + /// + /// assert!(c.expires().is_some()); + /// # } + /// ``` + #[inline] + pub fn expires(mut self, when: Tm) -> CookieBuilder { + self.cookie.set_expires(when); + self + } + + /// Sets the `max_age` field in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// # fn main() { + /// let c = Cookie::build("foo", "bar") + /// .max_age(time::Duration::minutes(30)) + /// .finish(); + /// + /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); + /// # } + /// ``` + #[inline] + pub fn max_age(mut self, value: Duration) -> CookieBuilder { + self.cookie.set_max_age(value); + self + } + + /// Sets the `domain` field in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::build("foo", "bar") + /// .domain("www.rust-lang.org") + /// .finish(); + /// + /// assert_eq!(c.domain(), Some("www.rust-lang.org")); + /// ``` + pub fn domain>>(mut self, value: D) -> CookieBuilder { + self.cookie.set_domain(value); + self + } + + /// Sets the `path` field in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::build("foo", "bar") + /// .path("/") + /// .finish(); + /// + /// assert_eq!(c.path(), Some("/")); + /// ``` + pub fn path>>(mut self, path: P) -> CookieBuilder { + self.cookie.set_path(path); + self + } + + /// Sets the `secure` field in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::build("foo", "bar") + /// .secure(true) + /// .finish(); + /// + /// assert_eq!(c.secure(), Some(true)); + /// ``` + #[inline] + pub fn secure(mut self, value: bool) -> CookieBuilder { + self.cookie.set_secure(value); + self + } + + /// Sets the `http_only` field in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::build("foo", "bar") + /// .http_only(true) + /// .finish(); + /// + /// assert_eq!(c.http_only(), Some(true)); + /// ``` + #[inline] + pub fn http_only(mut self, value: bool) -> CookieBuilder { + self.cookie.set_http_only(value); + self + } + + /// Sets the `same_site` field in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{Cookie, SameSite}; + /// + /// let c = Cookie::build("foo", "bar") + /// .same_site(SameSite::Strict) + /// .finish(); + /// + /// assert_eq!(c.same_site(), Some(SameSite::Strict)); + /// ``` + #[inline] + pub fn same_site(mut self, value: SameSite) -> CookieBuilder { + self.cookie.set_same_site(value); + self + } + + /// Makes the cookie being built 'permanent' by extending its expiration and + /// max age 20 years into the future. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// use time::Duration; + /// + /// # fn main() { + /// let c = Cookie::build("foo", "bar") + /// .permanent() + /// .finish(); + /// + /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); + /// # assert!(c.expires().is_some()); + /// # } + /// ``` + #[inline] + pub fn permanent(mut self) -> CookieBuilder { + self.cookie.make_permanent(); + self + } + + /// Finishes building and returns the built `Cookie`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::build("foo", "bar") + /// .domain("crates.io") + /// .path("/") + /// .finish(); + /// + /// assert_eq!(c.name_value(), ("foo", "bar")); + /// assert_eq!(c.domain(), Some("crates.io")); + /// assert_eq!(c.path(), Some("/")); + /// ``` + #[inline] + pub fn finish(self) -> Cookie<'static> { + self.cookie + } +} diff --git a/actix-http/src/cookie/delta.rs b/actix-http/src/cookie/delta.rs new file mode 100644 index 00000000..a001a5bb --- /dev/null +++ b/actix-http/src/cookie/delta.rs @@ -0,0 +1,71 @@ +use std::borrow::Borrow; +use std::hash::{Hash, Hasher}; +use std::ops::{Deref, DerefMut}; + +use super::Cookie; + +/// A `DeltaCookie` is a helper structure used in a cookie jar. It wraps a +/// `Cookie` so that it can be hashed and compared purely by name. It further +/// records whether the wrapped cookie is a "removal" cookie, that is, a cookie +/// that when sent to the client removes the named cookie on the client's +/// machine. +#[derive(Clone, Debug)] +pub struct DeltaCookie { + pub cookie: Cookie<'static>, + pub removed: bool, +} + +impl DeltaCookie { + /// Create a new `DeltaCookie` that is being added to a jar. + #[inline] + pub fn added(cookie: Cookie<'static>) -> DeltaCookie { + DeltaCookie { + cookie, + removed: false, + } + } + + /// Create a new `DeltaCookie` that is being removed from a jar. The + /// `cookie` should be a "removal" cookie. + #[inline] + pub fn removed(cookie: Cookie<'static>) -> DeltaCookie { + DeltaCookie { + cookie, + removed: true, + } + } +} + +impl Deref for DeltaCookie { + type Target = Cookie<'static>; + + fn deref(&self) -> &Cookie<'static> { + &self.cookie + } +} + +impl DerefMut for DeltaCookie { + fn deref_mut(&mut self) -> &mut Cookie<'static> { + &mut self.cookie + } +} + +impl PartialEq for DeltaCookie { + fn eq(&self, other: &DeltaCookie) -> bool { + self.name() == other.name() + } +} + +impl Eq for DeltaCookie {} + +impl Hash for DeltaCookie { + fn hash(&self, state: &mut H) { + self.name().hash(state); + } +} + +impl Borrow for DeltaCookie { + fn borrow(&self) -> &str { + self.name() + } +} diff --git a/actix-http/src/cookie/draft.rs b/actix-http/src/cookie/draft.rs new file mode 100644 index 00000000..36213394 --- /dev/null +++ b/actix-http/src/cookie/draft.rs @@ -0,0 +1,98 @@ +//! This module contains types that represent cookie properties that are not yet +//! standardized. That is, _draft_ features. + +use std::fmt; + +/// The `SameSite` cookie attribute. +/// +/// A cookie with a `SameSite` attribute is imposed restrictions on when it is +/// sent to the origin server in a cross-site request. If the `SameSite` +/// attribute is "Strict", then the cookie is never sent in cross-site requests. +/// If the `SameSite` attribute is "Lax", the cookie is only sent in cross-site +/// requests with "safe" HTTP methods, i.e, `GET`, `HEAD`, `OPTIONS`, `TRACE`. +/// If the `SameSite` attribute is not present (made explicit via the +/// `SameSite::None` variant), then the cookie will be sent as normal. +/// +/// **Note:** This cookie attribute is an HTTP draft! Its meaning and definition +/// are subject to change. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SameSite { + /// The "Strict" `SameSite` attribute. + Strict, + /// The "Lax" `SameSite` attribute. + Lax, + /// No `SameSite` attribute. + None, +} + +impl SameSite { + /// Returns `true` if `self` is `SameSite::Strict` and `false` otherwise. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::SameSite; + /// + /// let strict = SameSite::Strict; + /// assert!(strict.is_strict()); + /// assert!(!strict.is_lax()); + /// assert!(!strict.is_none()); + /// ``` + #[inline] + pub fn is_strict(self) -> bool { + match self { + SameSite::Strict => true, + SameSite::Lax | SameSite::None => false, + } + } + + /// Returns `true` if `self` is `SameSite::Lax` and `false` otherwise. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::SameSite; + /// + /// let lax = SameSite::Lax; + /// assert!(lax.is_lax()); + /// assert!(!lax.is_strict()); + /// assert!(!lax.is_none()); + /// ``` + #[inline] + pub fn is_lax(self) -> bool { + match self { + SameSite::Lax => true, + SameSite::Strict | SameSite::None => false, + } + } + + /// Returns `true` if `self` is `SameSite::None` and `false` otherwise. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::SameSite; + /// + /// let none = SameSite::None; + /// assert!(none.is_none()); + /// assert!(!none.is_lax()); + /// assert!(!none.is_strict()); + /// ``` + #[inline] + pub fn is_none(self) -> bool { + match self { + SameSite::None => true, + SameSite::Lax | SameSite::Strict => false, + } + } +} + +impl fmt::Display for SameSite { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SameSite::Strict => write!(f, "Strict"), + SameSite::Lax => write!(f, "Lax"), + SameSite::None => Ok(()), + } + } +} diff --git a/actix-http/src/cookie/jar.rs b/actix-http/src/cookie/jar.rs new file mode 100644 index 00000000..d9ab8f05 --- /dev/null +++ b/actix-http/src/cookie/jar.rs @@ -0,0 +1,653 @@ +use std::collections::HashSet; +use std::mem::replace; + +use time::{self, Duration}; + +use super::delta::DeltaCookie; +use super::Cookie; + +#[cfg(feature = "secure-cookies")] +use super::secure::{Key, PrivateJar, SignedJar}; + +/// A collection of cookies that tracks its modifications. +/// +/// A `CookieJar` provides storage for any number of cookies. Any changes made +/// to the jar are tracked; the changes can be retrieved via the +/// [delta](#method.delta) method which returns an interator over the changes. +/// +/// # Usage +/// +/// A jar's life begins via [new](#method.new) and calls to +/// [`add_original`](#method.add_original): +/// +/// ```rust +/// use actix_http::cookie::{Cookie, CookieJar}; +/// +/// let mut jar = CookieJar::new(); +/// jar.add_original(Cookie::new("name", "value")); +/// jar.add_original(Cookie::new("second", "another")); +/// ``` +/// +/// Cookies can be added via [add](#method.add) and removed via +/// [remove](#method.remove). Finally, cookies can be looked up via +/// [get](#method.get): +/// +/// ```rust +/// # use actix_http::cookie::{Cookie, CookieJar}; +/// let mut jar = CookieJar::new(); +/// jar.add(Cookie::new("a", "one")); +/// jar.add(Cookie::new("b", "two")); +/// +/// assert_eq!(jar.get("a").map(|c| c.value()), Some("one")); +/// assert_eq!(jar.get("b").map(|c| c.value()), Some("two")); +/// +/// jar.remove(Cookie::named("b")); +/// assert!(jar.get("b").is_none()); +/// ``` +/// +/// # Deltas +/// +/// A jar keeps track of any modifications made to it over time. The +/// modifications are recorded as cookies. The modifications can be retrieved +/// via [delta](#method.delta). Any new `Cookie` added to a jar via `add` +/// results in the same `Cookie` appearing in the `delta`; cookies added via +/// `add_original` do not count towards the delta. Any _original_ cookie that is +/// removed from a jar results in a "removal" cookie appearing in the delta. A +/// "removal" cookie is a cookie that a server sends so that the cookie is +/// removed from the client's machine. +/// +/// Deltas are typically used to create `Set-Cookie` headers corresponding to +/// the changes made to a cookie jar over a period of time. +/// +/// ```rust +/// # use actix_http::cookie::{Cookie, CookieJar}; +/// let mut jar = CookieJar::new(); +/// +/// // original cookies don't affect the delta +/// jar.add_original(Cookie::new("original", "value")); +/// assert_eq!(jar.delta().count(), 0); +/// +/// // new cookies result in an equivalent `Cookie` in the delta +/// jar.add(Cookie::new("a", "one")); +/// jar.add(Cookie::new("b", "two")); +/// assert_eq!(jar.delta().count(), 2); +/// +/// // removing an original cookie adds a "removal" cookie to the delta +/// jar.remove(Cookie::named("original")); +/// assert_eq!(jar.delta().count(), 3); +/// +/// // removing a new cookie that was added removes that `Cookie` from the delta +/// jar.remove(Cookie::named("a")); +/// assert_eq!(jar.delta().count(), 2); +/// ``` +#[derive(Default, Debug, Clone)] +pub struct CookieJar { + original_cookies: HashSet, + delta_cookies: HashSet, +} + +impl CookieJar { + /// Creates an empty cookie jar. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::CookieJar; + /// + /// let jar = CookieJar::new(); + /// assert_eq!(jar.iter().count(), 0); + /// ``` + pub fn new() -> CookieJar { + CookieJar::default() + } + + /// Returns a reference to the `Cookie` inside this jar with the name + /// `name`. If no such cookie exists, returns `None`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie}; + /// + /// let mut jar = CookieJar::new(); + /// assert!(jar.get("name").is_none()); + /// + /// jar.add(Cookie::new("name", "value")); + /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value")); + /// ``` + pub fn get(&self, name: &str) -> Option<&Cookie<'static>> { + self.delta_cookies + .get(name) + .or_else(|| self.original_cookies.get(name)) + .and_then(|c| if !c.removed { Some(&c.cookie) } else { None }) + } + + /// Adds an "original" `cookie` to this jar. If an original cookie with the + /// same name already exists, it is replaced with `cookie`. Cookies added + /// with `add` take precedence and are not replaced by this method. + /// + /// Adding an original cookie does not affect the [delta](#method.delta) + /// computation. This method is intended to be used to seed the cookie jar + /// with cookies received from a client's HTTP message. + /// + /// For accurate `delta` computations, this method should not be called + /// after calling `remove`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie}; + /// + /// let mut jar = CookieJar::new(); + /// jar.add_original(Cookie::new("name", "value")); + /// jar.add_original(Cookie::new("second", "two")); + /// + /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value")); + /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two")); + /// assert_eq!(jar.iter().count(), 2); + /// assert_eq!(jar.delta().count(), 0); + /// ``` + pub fn add_original(&mut self, cookie: Cookie<'static>) { + self.original_cookies.replace(DeltaCookie::added(cookie)); + } + + /// Adds `cookie` to this jar. If a cookie with the same name already + /// exists, it is replaced with `cookie`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie}; + /// + /// let mut jar = CookieJar::new(); + /// jar.add(Cookie::new("name", "value")); + /// jar.add(Cookie::new("second", "two")); + /// + /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value")); + /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two")); + /// assert_eq!(jar.iter().count(), 2); + /// assert_eq!(jar.delta().count(), 2); + /// ``` + pub fn add(&mut self, cookie: Cookie<'static>) { + self.delta_cookies.replace(DeltaCookie::added(cookie)); + } + + /// Removes `cookie` from this jar. If an _original_ cookie with the same + /// name as `cookie` is present in the jar, a _removal_ cookie will be + /// present in the `delta` computation. To properly generate the removal + /// cookie, `cookie` must contain the same `path` and `domain` as the cookie + /// that was initially set. + /// + /// A "removal" cookie is a cookie that has the same name as the original + /// cookie but has an empty value, a max-age of 0, and an expiration date + /// far in the past. + /// + /// # Example + /// + /// Removing an _original_ cookie results in a _removal_ cookie: + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie}; + /// use time::Duration; + /// + /// # fn main() { + /// let mut jar = CookieJar::new(); + /// + /// // Assume this cookie originally had a path of "/" and domain of "a.b". + /// jar.add_original(Cookie::new("name", "value")); + /// + /// // If the path and domain were set, they must be provided to `remove`. + /// jar.remove(Cookie::build("name", "").path("/").domain("a.b").finish()); + /// + /// // The delta will contain the removal cookie. + /// let delta: Vec<_> = jar.delta().collect(); + /// assert_eq!(delta.len(), 1); + /// assert_eq!(delta[0].name(), "name"); + /// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0))); + /// # } + /// ``` + /// + /// Removing a new cookie does not result in a _removal_ cookie: + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie}; + /// + /// let mut jar = CookieJar::new(); + /// jar.add(Cookie::new("name", "value")); + /// assert_eq!(jar.delta().count(), 1); + /// + /// jar.remove(Cookie::named("name")); + /// assert_eq!(jar.delta().count(), 0); + /// ``` + pub fn remove(&mut self, mut cookie: Cookie<'static>) { + if self.original_cookies.contains(cookie.name()) { + cookie.set_value(""); + cookie.set_max_age(Duration::seconds(0)); + cookie.set_expires(time::now() - Duration::days(365)); + self.delta_cookies.replace(DeltaCookie::removed(cookie)); + } else { + self.delta_cookies.remove(cookie.name()); + } + } + + /// Removes `cookie` from this jar completely. This method differs from + /// `remove` in that no delta cookie is created under any condition. Neither + /// the `delta` nor `iter` methods will return a cookie that is removed + /// using this method. + /// + /// # Example + /// + /// Removing an _original_ cookie; no _removal_ cookie is generated: + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie}; + /// use time::Duration; + /// + /// # fn main() { + /// let mut jar = CookieJar::new(); + /// + /// // Add an original cookie and a new cookie. + /// jar.add_original(Cookie::new("name", "value")); + /// jar.add(Cookie::new("key", "value")); + /// assert_eq!(jar.delta().count(), 1); + /// assert_eq!(jar.iter().count(), 2); + /// + /// // Now force remove the original cookie. + /// jar.force_remove(Cookie::new("name", "value")); + /// assert_eq!(jar.delta().count(), 1); + /// assert_eq!(jar.iter().count(), 1); + /// + /// // Now force remove the new cookie. + /// jar.force_remove(Cookie::new("key", "value")); + /// assert_eq!(jar.delta().count(), 0); + /// assert_eq!(jar.iter().count(), 0); + /// # } + /// ``` + pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) { + self.original_cookies.remove(cookie.name()); + self.delta_cookies.remove(cookie.name()); + } + + /// Removes all cookies from this cookie jar. + #[deprecated( + since = "0.7.0", + note = "calling this method may not remove \ + all cookies since the path and domain are not specified; use \ + `remove` instead" + )] + pub fn clear(&mut self) { + self.delta_cookies.clear(); + for delta in replace(&mut self.original_cookies, HashSet::new()) { + self.remove(delta.cookie); + } + } + + /// Returns an iterator over cookies that represent the changes to this jar + /// over time. These cookies can be rendered directly as `Set-Cookie` header + /// values to affect the changes made to this jar on the client. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie}; + /// + /// let mut jar = CookieJar::new(); + /// jar.add_original(Cookie::new("name", "value")); + /// jar.add_original(Cookie::new("second", "two")); + /// + /// // Add new cookies. + /// jar.add(Cookie::new("new", "third")); + /// jar.add(Cookie::new("another", "fourth")); + /// jar.add(Cookie::new("yac", "fifth")); + /// + /// // Remove some cookies. + /// jar.remove(Cookie::named("name")); + /// jar.remove(Cookie::named("another")); + /// + /// // Delta contains two new cookies ("new", "yac") and a removal ("name"). + /// assert_eq!(jar.delta().count(), 3); + /// ``` + pub fn delta(&self) -> Delta { + Delta { + iter: self.delta_cookies.iter(), + } + } + + /// Returns an iterator over all of the cookies present in this jar. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie}; + /// + /// let mut jar = CookieJar::new(); + /// + /// jar.add_original(Cookie::new("name", "value")); + /// jar.add_original(Cookie::new("second", "two")); + /// + /// jar.add(Cookie::new("new", "third")); + /// jar.add(Cookie::new("another", "fourth")); + /// jar.add(Cookie::new("yac", "fifth")); + /// + /// jar.remove(Cookie::named("name")); + /// jar.remove(Cookie::named("another")); + /// + /// // There are three cookies in the jar: "second", "new", and "yac". + /// # assert_eq!(jar.iter().count(), 3); + /// for cookie in jar.iter() { + /// match cookie.name() { + /// "second" => assert_eq!(cookie.value(), "two"), + /// "new" => assert_eq!(cookie.value(), "third"), + /// "yac" => assert_eq!(cookie.value(), "fifth"), + /// _ => unreachable!("there are only three cookies in the jar") + /// } + /// } + /// ``` + pub fn iter(&self) -> Iter { + Iter { + delta_cookies: self + .delta_cookies + .iter() + .chain(self.original_cookies.difference(&self.delta_cookies)), + } + } + + /// Returns a `PrivateJar` with `self` as its parent jar using the key `key` + /// to sign/encrypt and verify/decrypt cookies added/retrieved from the + /// child jar. + /// + /// Any modifications to the child jar will be reflected on the parent jar, + /// and any retrievals from the child jar will be made from the parent jar. + /// + /// This method is only available when the `secure` feature is enabled. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{Cookie, CookieJar, Key}; + /// + /// // Generate a secure key. + /// let key = Key::generate(); + /// + /// // Add a private (signed + encrypted) cookie. + /// let mut jar = CookieJar::new(); + /// jar.private(&key).add(Cookie::new("private", "text")); + /// + /// // The cookie's contents are encrypted. + /// assert_ne!(jar.get("private").unwrap().value(), "text"); + /// + /// // They can be decrypted and verified through the child jar. + /// assert_eq!(jar.private(&key).get("private").unwrap().value(), "text"); + /// + /// // A tampered with cookie does not validate but still exists. + /// let mut cookie = jar.get("private").unwrap().clone(); + /// jar.add(Cookie::new("private", cookie.value().to_string() + "!")); + /// assert!(jar.private(&key).get("private").is_none()); + /// assert!(jar.get("private").is_some()); + /// ``` + #[cfg(feature = "secure-cookies")] + pub fn private(&mut self, key: &Key) -> PrivateJar { + PrivateJar::new(self, key) + } + + /// Returns a `SignedJar` with `self` as its parent jar using the key `key` + /// to sign/verify cookies added/retrieved from the child jar. + /// + /// Any modifications to the child jar will be reflected on the parent jar, + /// and any retrievals from the child jar will be made from the parent jar. + /// + /// This method is only available when the `secure` feature is enabled. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{Cookie, CookieJar, Key}; + /// + /// // Generate a secure key. + /// let key = Key::generate(); + /// + /// // Add a signed cookie. + /// let mut jar = CookieJar::new(); + /// jar.signed(&key).add(Cookie::new("signed", "text")); + /// + /// // The cookie's contents are signed but still in plaintext. + /// assert_ne!(jar.get("signed").unwrap().value(), "text"); + /// assert!(jar.get("signed").unwrap().value().contains("text")); + /// + /// // They can be verified through the child jar. + /// assert_eq!(jar.signed(&key).get("signed").unwrap().value(), "text"); + /// + /// // A tampered with cookie does not validate but still exists. + /// let mut cookie = jar.get("signed").unwrap().clone(); + /// jar.add(Cookie::new("signed", cookie.value().to_string() + "!")); + /// assert!(jar.signed(&key).get("signed").is_none()); + /// assert!(jar.get("signed").is_some()); + /// ``` + #[cfg(feature = "secure-cookies")] + pub fn signed(&mut self, key: &Key) -> SignedJar { + SignedJar::new(self, key) + } +} + +use std::collections::hash_set::Iter as HashSetIter; + +/// Iterator over the changes to a cookie jar. +pub struct Delta<'a> { + iter: HashSetIter<'a, DeltaCookie>, +} + +impl<'a> Iterator for Delta<'a> { + type Item = &'a Cookie<'static>; + + fn next(&mut self) -> Option<&'a Cookie<'static>> { + self.iter.next().map(|c| &c.cookie) + } +} + +use std::collections::hash_map::RandomState; +use std::collections::hash_set::Difference; +use std::iter::Chain; + +/// Iterator over all of the cookies in a jar. +pub struct Iter<'a> { + delta_cookies: + Chain, Difference<'a, DeltaCookie, RandomState>>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a Cookie<'static>; + + fn next(&mut self) -> Option<&'a Cookie<'static>> { + for cookie in self.delta_cookies.by_ref() { + if !cookie.removed { + return Some(&*cookie); + } + } + + None + } +} + +#[cfg(test)] +mod test { + use super::{Cookie, CookieJar, Key}; + + #[test] + #[allow(deprecated)] + fn simple() { + let mut c = CookieJar::new(); + + c.add(Cookie::new("test", "")); + c.add(Cookie::new("test2", "")); + c.remove(Cookie::named("test")); + + assert!(c.get("test").is_none()); + assert!(c.get("test2").is_some()); + + c.add(Cookie::new("test3", "")); + c.clear(); + + assert!(c.get("test").is_none()); + assert!(c.get("test2").is_none()); + assert!(c.get("test3").is_none()); + } + + #[test] + fn jar_is_send() { + fn is_send(_: T) -> bool { + true + } + + assert!(is_send(CookieJar::new())) + } + + #[test] + #[cfg(feature = "secure-cookies")] + fn iter() { + let key = Key::generate(); + let mut c = CookieJar::new(); + + c.add_original(Cookie::new("original", "original")); + + c.add(Cookie::new("test", "test")); + c.add(Cookie::new("test2", "test2")); + c.add(Cookie::new("test3", "test3")); + assert_eq!(c.iter().count(), 4); + + c.signed(&key).add(Cookie::new("signed", "signed")); + c.private(&key).add(Cookie::new("encrypted", "encrypted")); + assert_eq!(c.iter().count(), 6); + + c.remove(Cookie::named("test")); + assert_eq!(c.iter().count(), 5); + + c.remove(Cookie::named("signed")); + c.remove(Cookie::named("test2")); + assert_eq!(c.iter().count(), 3); + + c.add(Cookie::new("test2", "test2")); + assert_eq!(c.iter().count(), 4); + + c.remove(Cookie::named("test2")); + assert_eq!(c.iter().count(), 3); + } + + #[test] + #[cfg(feature = "secure-cookies")] + fn delta() { + use std::collections::HashMap; + use time::Duration; + + let mut c = CookieJar::new(); + + c.add_original(Cookie::new("original", "original")); + c.add_original(Cookie::new("original1", "original1")); + + c.add(Cookie::new("test", "test")); + c.add(Cookie::new("test2", "test2")); + c.add(Cookie::new("test3", "test3")); + c.add(Cookie::new("test4", "test4")); + + c.remove(Cookie::named("test")); + c.remove(Cookie::named("original")); + + assert_eq!(c.delta().count(), 4); + + let names: HashMap<_, _> = c.delta().map(|c| (c.name(), c.max_age())).collect(); + + assert!(names.get("test2").unwrap().is_none()); + assert!(names.get("test3").unwrap().is_none()); + assert!(names.get("test4").unwrap().is_none()); + assert_eq!(names.get("original").unwrap(), &Some(Duration::seconds(0))); + } + + #[test] + fn replace_original() { + let mut jar = CookieJar::new(); + jar.add_original(Cookie::new("original_a", "a")); + jar.add_original(Cookie::new("original_b", "b")); + assert_eq!(jar.get("original_a").unwrap().value(), "a"); + + jar.add(Cookie::new("original_a", "av2")); + assert_eq!(jar.get("original_a").unwrap().value(), "av2"); + } + + #[test] + fn empty_delta() { + let mut jar = CookieJar::new(); + jar.add(Cookie::new("name", "val")); + assert_eq!(jar.delta().count(), 1); + + jar.remove(Cookie::named("name")); + assert_eq!(jar.delta().count(), 0); + + jar.add_original(Cookie::new("name", "val")); + assert_eq!(jar.delta().count(), 0); + + jar.remove(Cookie::named("name")); + assert_eq!(jar.delta().count(), 1); + + jar.add(Cookie::new("name", "val")); + assert_eq!(jar.delta().count(), 1); + + jar.remove(Cookie::named("name")); + assert_eq!(jar.delta().count(), 1); + } + + #[test] + fn add_remove_add() { + let mut jar = CookieJar::new(); + jar.add_original(Cookie::new("name", "val")); + assert_eq!(jar.delta().count(), 0); + + jar.remove(Cookie::named("name")); + assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); + assert_eq!(jar.delta().count(), 1); + + // The cookie's been deleted. Another original doesn't change that. + jar.add_original(Cookie::new("name", "val")); + assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); + assert_eq!(jar.delta().count(), 1); + + jar.remove(Cookie::named("name")); + assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); + assert_eq!(jar.delta().count(), 1); + + jar.add(Cookie::new("name", "val")); + assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1); + assert_eq!(jar.delta().count(), 1); + + jar.remove(Cookie::named("name")); + assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); + assert_eq!(jar.delta().count(), 1); + } + + #[test] + fn replace_remove() { + let mut jar = CookieJar::new(); + jar.add_original(Cookie::new("name", "val")); + assert_eq!(jar.delta().count(), 0); + + jar.add(Cookie::new("name", "val")); + assert_eq!(jar.delta().count(), 1); + assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1); + + jar.remove(Cookie::named("name")); + assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); + } + + #[test] + fn remove_with_path() { + let mut jar = CookieJar::new(); + jar.add_original(Cookie::build("name", "val").finish()); + assert_eq!(jar.iter().count(), 1); + assert_eq!(jar.delta().count(), 0); + assert_eq!(jar.iter().filter(|c| c.path().is_none()).count(), 1); + + jar.remove(Cookie::build("name", "").path("/").finish()); + assert_eq!(jar.iter().count(), 0); + assert_eq!(jar.delta().count(), 1); + assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); + assert_eq!(jar.delta().filter(|c| c.path() == Some("/")).count(), 1); + } +} diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs new file mode 100644 index 00000000..5545624a --- /dev/null +++ b/actix-http/src/cookie/mod.rs @@ -0,0 +1,1087 @@ +//! https://github.com/alexcrichton/cookie-rs fork +//! +//! HTTP cookie parsing and cookie jar management. +//! +//! This crates provides the [`Cookie`](struct.Cookie.html) type, which directly +//! maps to an HTTP cookie, and the [`CookieJar`](struct.CookieJar.html) type, +//! which allows for simple management of many cookies as well as encryption and +//! signing of cookies for session management. +//! +//! # Features +//! +//! This crates can be configured at compile-time through the following Cargo +//! features: +//! +//! +//! * **secure** (disabled by default) +//! +//! Enables signed and private (signed + encrypted) cookie jars. +//! +//! When this feature is enabled, the +//! [signed](struct.CookieJar.html#method.signed) and +//! [private](struct.CookieJar.html#method.private) method of `CookieJar` and +//! [`SignedJar`](struct.SignedJar.html) and +//! [`PrivateJar`](struct.PrivateJar.html) structures are available. The jars +//! act as "children jars", allowing for easy retrieval and addition of signed +//! and/or encrypted cookies to a cookie jar. When this feature is disabled, +//! none of the types are available. +//! +//! * **percent-encode** (disabled by default) +//! +//! Enables percent encoding and decoding of names and values in cookies. +//! +//! When this feature is enabled, the +//! [encoded](struct.Cookie.html#method.encoded) and +//! [`parse_encoded`](struct.Cookie.html#method.parse_encoded) methods of +//! `Cookie` become available. The `encoded` method returns a wrapper around a +//! `Cookie` whose `Display` implementation percent-encodes the name and value +//! of the cookie. The `parse_encoded` method percent-decodes the name and +//! value of a `Cookie` during parsing. When this feature is disabled, the +//! `encoded` and `parse_encoded` methods are not available. +//! +//! You can enable features via the `Cargo.toml` file: +//! +//! ```ignore +//! [dependencies.cookie] +//! features = ["secure", "percent-encode"] +//! ``` + +#![doc(html_root_url = "https://docs.rs/cookie/0.11")] +#![deny(missing_docs)] + +mod builder; +mod delta; +mod draft; +mod jar; +mod parse; + +#[cfg(feature = "secure-cookies")] +#[macro_use] +mod secure; +#[cfg(feature = "secure-cookies")] +pub use secure::*; + +use std::borrow::Cow; +use std::fmt; +use std::str::FromStr; + +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use time::{Duration, Tm}; + +pub use builder::CookieBuilder; +pub use draft::*; +pub use jar::{CookieJar, Delta, Iter}; +use parse::parse_cookie; +pub use parse::ParseError; + +#[derive(Debug, Clone)] +enum CookieStr { + /// An string derived from indexes (start, end). + Indexed(usize, usize), + /// A string derived from a concrete string. + Concrete(Cow<'static, str>), +} + +impl CookieStr { + /// Retrieves the string `self` corresponds to. If `self` is derived from + /// indexes, the corresponding subslice of `string` is returned. Otherwise, + /// the concrete string is returned. + /// + /// # Panics + /// + /// Panics if `self` is an indexed string and `string` is None. + fn to_str<'s>(&'s self, string: Option<&'s Cow>) -> &'s str { + match *self { + CookieStr::Indexed(i, j) => { + let s = string.expect( + "`Some` base string must exist when \ + converting indexed str to str! (This is a module invariant.)", + ); + &s[i..j] + } + CookieStr::Concrete(ref cstr) => &*cstr, + } + } + + fn to_raw_str<'s, 'c: 's>(&'s self, string: &'s Cow<'c, str>) -> Option<&'c str> { + match *self { + CookieStr::Indexed(i, j) => match *string { + Cow::Borrowed(s) => Some(&s[i..j]), + Cow::Owned(_) => None, + }, + CookieStr::Concrete(_) => None, + } + } +} + +/// Representation of an HTTP cookie. +/// +/// # Constructing a `Cookie` +/// +/// To construct a cookie with only a name/value, use the [new](#method.new) +/// method: +/// +/// ```rust +/// use actix_http::cookie::Cookie; +/// +/// let cookie = Cookie::new("name", "value"); +/// assert_eq!(&cookie.to_string(), "name=value"); +/// ``` +/// +/// To construct more elaborate cookies, use the [build](#method.build) method +/// and [`CookieBuilder`](struct.CookieBuilder.html) methods: +/// +/// ```rust +/// use actix_http::cookie::Cookie; +/// +/// let cookie = Cookie::build("name", "value") +/// .domain("www.rust-lang.org") +/// .path("/") +/// .secure(true) +/// .http_only(true) +/// .finish(); +/// ``` +#[derive(Debug, Clone)] +pub struct Cookie<'c> { + /// Storage for the cookie string. Only used if this structure was derived + /// from a string that was subsequently parsed. + cookie_string: Option>, + /// The cookie's name. + name: CookieStr, + /// The cookie's value. + value: CookieStr, + /// The cookie's expiration, if any. + expires: Option, + /// The cookie's maximum age, if any. + max_age: Option, + /// The cookie's domain, if any. + domain: Option, + /// The cookie's path domain, if any. + path: Option, + /// Whether this cookie was marked Secure. + secure: Option, + /// Whether this cookie was marked HttpOnly. + http_only: Option, + /// The draft `SameSite` attribute. + same_site: Option, +} + +impl Cookie<'static> { + /// Creates a new `Cookie` with the given name and value. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let cookie = Cookie::new("name", "value"); + /// assert_eq!(cookie.name_value(), ("name", "value")); + /// ``` + pub fn new(name: N, value: V) -> Cookie<'static> + where + N: Into>, + V: Into>, + { + Cookie { + cookie_string: None, + name: CookieStr::Concrete(name.into()), + value: CookieStr::Concrete(value.into()), + expires: None, + max_age: None, + domain: None, + path: None, + secure: None, + http_only: None, + same_site: None, + } + } + + /// Creates a new `Cookie` with the given name and an empty value. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let cookie = Cookie::named("name"); + /// assert_eq!(cookie.name(), "name"); + /// assert!(cookie.value().is_empty()); + /// ``` + pub fn named(name: N) -> Cookie<'static> + where + N: Into>, + { + Cookie::new(name, "") + } + + /// Creates a new `CookieBuilder` instance from the given key and value + /// strings. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::build("foo", "bar").finish(); + /// assert_eq!(c.name_value(), ("foo", "bar")); + /// ``` + pub fn build(name: N, value: V) -> CookieBuilder + where + N: Into>, + V: Into>, + { + CookieBuilder::new(name, value) + } +} + +impl<'c> Cookie<'c> { + /// Parses a `Cookie` from the given HTTP cookie header value string. Does + /// not perform any percent-decoding. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::parse("foo=bar%20baz; HttpOnly").unwrap(); + /// assert_eq!(c.name_value(), ("foo", "bar%20baz")); + /// assert_eq!(c.http_only(), Some(true)); + /// ``` + pub fn parse(s: S) -> Result, ParseError> + where + S: Into>, + { + parse_cookie(s, false) + } + + /// Parses a `Cookie` from the given HTTP cookie header value string where + /// the name and value fields are percent-encoded. Percent-decodes the + /// name/value fields. + /// + /// This API requires the `percent-encode` feature to be enabled on this + /// crate. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::parse_encoded("foo=bar%20baz; HttpOnly").unwrap(); + /// assert_eq!(c.name_value(), ("foo", "bar baz")); + /// assert_eq!(c.http_only(), Some(true)); + /// ``` + pub fn parse_encoded(s: S) -> Result, ParseError> + where + S: Into>, + { + parse_cookie(s, true) + } + + /// Wraps `self` in an `EncodedCookie`: a cost-free wrapper around `Cookie` + /// whose `Display` implementation percent-encodes the name and value of the + /// wrapped `Cookie`. + /// + /// This method is only available when the `percent-encode` feature is + /// enabled. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let mut c = Cookie::new("my name", "this; value?"); + /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F"); + /// ``` + pub fn encoded<'a>(&'a self) -> EncodedCookie<'a, 'c> { + EncodedCookie(self) + } + + /// Converts `self` into a `Cookie` with a static lifetime. This method + /// results in at most one allocation. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::new("a", "b"); + /// let owned_cookie = c.into_owned(); + /// assert_eq!(owned_cookie.name_value(), ("a", "b")); + /// ``` + pub fn into_owned(self) -> Cookie<'static> { + Cookie { + cookie_string: self.cookie_string.map(|s| s.into_owned().into()), + name: self.name, + value: self.value, + expires: self.expires, + max_age: self.max_age, + domain: self.domain, + path: self.path, + secure: self.secure, + http_only: self.http_only, + same_site: self.same_site, + } + } + + /// Returns the name of `self`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::new("name", "value"); + /// assert_eq!(c.name(), "name"); + /// ``` + #[inline] + pub fn name(&self) -> &str { + self.name.to_str(self.cookie_string.as_ref()) + } + + /// Returns the value of `self`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::new("name", "value"); + /// assert_eq!(c.value(), "value"); + /// ``` + #[inline] + pub fn value(&self) -> &str { + self.value.to_str(self.cookie_string.as_ref()) + } + + /// Returns the name and value of `self` as a tuple of `(name, value)`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::new("name", "value"); + /// assert_eq!(c.name_value(), ("name", "value")); + /// ``` + #[inline] + pub fn name_value(&self) -> (&str, &str) { + (self.name(), self.value()) + } + + /// Returns whether this cookie was marked `HttpOnly` or not. Returns + /// `Some(true)` when the cookie was explicitly set (manually or parsed) as + /// `HttpOnly`, `Some(false)` when `http_only` was manually set to `false`, + /// and `None` otherwise. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::parse("name=value; httponly").unwrap(); + /// assert_eq!(c.http_only(), Some(true)); + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.http_only(), None); + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.http_only(), None); + /// + /// // An explicitly set "false" value. + /// c.set_http_only(false); + /// assert_eq!(c.http_only(), Some(false)); + /// + /// // An explicitly set "true" value. + /// c.set_http_only(true); + /// assert_eq!(c.http_only(), Some(true)); + /// ``` + #[inline] + pub fn http_only(&self) -> Option { + self.http_only + } + + /// Returns whether this cookie was marked `Secure` or not. Returns + /// `Some(true)` when the cookie was explicitly set (manually or parsed) as + /// `Secure`, `Some(false)` when `secure` was manually set to `false`, and + /// `None` otherwise. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::parse("name=value; Secure").unwrap(); + /// assert_eq!(c.secure(), Some(true)); + /// + /// let mut c = Cookie::parse("name=value").unwrap(); + /// assert_eq!(c.secure(), None); + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.secure(), None); + /// + /// // An explicitly set "false" value. + /// c.set_secure(false); + /// assert_eq!(c.secure(), Some(false)); + /// + /// // An explicitly set "true" value. + /// c.set_secure(true); + /// assert_eq!(c.secure(), Some(true)); + /// ``` + #[inline] + pub fn secure(&self) -> Option { + self.secure + } + + /// Returns the `SameSite` attribute of this cookie if one was specified. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{Cookie, SameSite}; + /// + /// let c = Cookie::parse("name=value; SameSite=Lax").unwrap(); + /// assert_eq!(c.same_site(), Some(SameSite::Lax)); + /// ``` + #[inline] + pub fn same_site(&self) -> Option { + self.same_site + } + + /// Returns the specified max-age of the cookie if one was specified. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::parse("name=value").unwrap(); + /// assert_eq!(c.max_age(), None); + /// + /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap(); + /// assert_eq!(c.max_age().map(|age| age.num_hours()), Some(1)); + /// ``` + #[inline] + pub fn max_age(&self) -> Option { + self.max_age + } + + /// Returns the `Path` of the cookie if one was specified. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::parse("name=value").unwrap(); + /// assert_eq!(c.path(), None); + /// + /// let c = Cookie::parse("name=value; Path=/").unwrap(); + /// assert_eq!(c.path(), Some("/")); + /// + /// let c = Cookie::parse("name=value; path=/sub").unwrap(); + /// assert_eq!(c.path(), Some("/sub")); + /// ``` + #[inline] + pub fn path(&self) -> Option<&str> { + match self.path { + Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())), + None => None, + } + } + + /// Returns the `Domain` of the cookie if one was specified. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::parse("name=value").unwrap(); + /// assert_eq!(c.domain(), None); + /// + /// let c = Cookie::parse("name=value; Domain=crates.io").unwrap(); + /// assert_eq!(c.domain(), Some("crates.io")); + /// ``` + #[inline] + pub fn domain(&self) -> Option<&str> { + match self.domain { + Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())), + None => None, + } + } + + /// Returns the `Expires` time of the cookie if one was specified. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let c = Cookie::parse("name=value").unwrap(); + /// assert_eq!(c.expires(), None); + /// + /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT"; + /// let cookie_str = format!("name=value; Expires={}", expire_time); + /// let c = Cookie::parse(cookie_str).unwrap(); + /// assert_eq!(c.expires().map(|t| t.tm_year), Some(117)); + /// ``` + #[inline] + pub fn expires(&self) -> Option { + self.expires + } + + /// Sets the name of `self` to `name`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.name(), "name"); + /// + /// c.set_name("foo"); + /// assert_eq!(c.name(), "foo"); + /// ``` + pub fn set_name>>(&mut self, name: N) { + self.name = CookieStr::Concrete(name.into()) + } + + /// Sets the value of `self` to `value`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.value(), "value"); + /// + /// c.set_value("bar"); + /// assert_eq!(c.value(), "bar"); + /// ``` + pub fn set_value>>(&mut self, value: V) { + self.value = CookieStr::Concrete(value.into()) + } + + /// Sets the value of `http_only` in `self` to `value`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.http_only(), None); + /// + /// c.set_http_only(true); + /// assert_eq!(c.http_only(), Some(true)); + /// ``` + #[inline] + pub fn set_http_only(&mut self, value: bool) { + self.http_only = Some(value); + } + + /// Sets the value of `secure` in `self` to `value`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.secure(), None); + /// + /// c.set_secure(true); + /// assert_eq!(c.secure(), Some(true)); + /// ``` + #[inline] + pub fn set_secure(&mut self, value: bool) { + self.secure = Some(value); + } + + /// Sets the value of `same_site` in `self` to `value`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{Cookie, SameSite}; + /// + /// let mut c = Cookie::new("name", "value"); + /// assert!(c.same_site().is_none()); + /// + /// c.set_same_site(SameSite::Strict); + /// assert_eq!(c.same_site(), Some(SameSite::Strict)); + /// ``` + #[inline] + pub fn set_same_site(&mut self, value: SameSite) { + self.same_site = Some(value); + } + + /// Sets the value of `max_age` in `self` to `value`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// use time::Duration; + /// + /// # fn main() { + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.max_age(), None); + /// + /// c.set_max_age(Duration::hours(10)); + /// assert_eq!(c.max_age(), Some(Duration::hours(10))); + /// # } + /// ``` + #[inline] + pub fn set_max_age(&mut self, value: Duration) { + self.max_age = Some(value); + } + + /// Sets the `path` of `self` to `path`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.path(), None); + /// + /// c.set_path("/"); + /// assert_eq!(c.path(), Some("/")); + /// ``` + pub fn set_path>>(&mut self, path: P) { + self.path = Some(CookieStr::Concrete(path.into())); + } + + /// Sets the `domain` of `self` to `domain`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.domain(), None); + /// + /// c.set_domain("rust-lang.org"); + /// assert_eq!(c.domain(), Some("rust-lang.org")); + /// ``` + pub fn set_domain>>(&mut self, domain: D) { + self.domain = Some(CookieStr::Concrete(domain.into())); + } + + /// Sets the expires field of `self` to `time`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// # fn main() { + /// let mut c = Cookie::new("name", "value"); + /// assert_eq!(c.expires(), None); + /// + /// let mut now = time::now(); + /// now.tm_year += 1; + /// + /// c.set_expires(now); + /// assert!(c.expires().is_some()) + /// # } + /// ``` + #[inline] + pub fn set_expires(&mut self, time: Tm) { + self.expires = Some(time); + } + + /// Makes `self` a "permanent" cookie by extending its expiration and max + /// age 20 years into the future. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// use time::Duration; + /// + /// # fn main() { + /// let mut c = Cookie::new("foo", "bar"); + /// assert!(c.expires().is_none()); + /// assert!(c.max_age().is_none()); + /// + /// c.make_permanent(); + /// assert!(c.expires().is_some()); + /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); + /// # } + /// ``` + pub fn make_permanent(&mut self) { + let twenty_years = Duration::days(365 * 20); + self.set_max_age(twenty_years); + self.set_expires(time::now() + twenty_years); + } + + fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(true) = self.http_only() { + write!(f, "; HttpOnly")?; + } + + if let Some(true) = self.secure() { + write!(f, "; Secure")?; + } + + if let Some(same_site) = self.same_site() { + if !same_site.is_none() { + write!(f, "; SameSite={}", same_site)?; + } + } + + if let Some(path) = self.path() { + write!(f, "; Path={}", path)?; + } + + if let Some(domain) = self.domain() { + write!(f, "; Domain={}", domain)?; + } + + if let Some(max_age) = self.max_age() { + write!(f, "; Max-Age={}", max_age.num_seconds())?; + } + + if let Some(time) = self.expires() { + write!(f, "; Expires={}", time.rfc822())?; + } + + Ok(()) + } + + /// Returns the name of `self` as a string slice of the raw string `self` + /// was originally parsed from. If `self` was not originally parsed from a + /// raw string, returns `None`. + /// + /// This method differs from [name](#method.name) in that it returns a + /// string with the same lifetime as the originally parsed string. This + /// lifetime may outlive `self`. If a longer lifetime is not required, or + /// you're unsure if you need a longer lifetime, use [name](#method.name). + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let cookie_string = format!("{}={}", "foo", "bar"); + /// + /// // `c` will be dropped at the end of the scope, but `name` will live on + /// let name = { + /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); + /// c.name_raw() + /// }; + /// + /// assert_eq!(name, Some("foo")); + /// ``` + #[inline] + pub fn name_raw(&self) -> Option<&'c str> { + self.cookie_string + .as_ref() + .and_then(|s| self.name.to_raw_str(s)) + } + + /// Returns the value of `self` as a string slice of the raw string `self` + /// was originally parsed from. If `self` was not originally parsed from a + /// raw string, returns `None`. + /// + /// This method differs from [value](#method.value) in that it returns a + /// string with the same lifetime as the originally parsed string. This + /// lifetime may outlive `self`. If a longer lifetime is not required, or + /// you're unsure if you need a longer lifetime, use [value](#method.value). + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let cookie_string = format!("{}={}", "foo", "bar"); + /// + /// // `c` will be dropped at the end of the scope, but `value` will live on + /// let value = { + /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); + /// c.value_raw() + /// }; + /// + /// assert_eq!(value, Some("bar")); + /// ``` + #[inline] + pub fn value_raw(&self) -> Option<&'c str> { + self.cookie_string + .as_ref() + .and_then(|s| self.value.to_raw_str(s)) + } + + /// Returns the `Path` of `self` as a string slice of the raw string `self` + /// was originally parsed from. If `self` was not originally parsed from a + /// raw string, or if `self` doesn't contain a `Path`, or if the `Path` has + /// changed since parsing, returns `None`. + /// + /// This method differs from [path](#method.path) in that it returns a + /// string with the same lifetime as the originally parsed string. This + /// lifetime may outlive `self`. If a longer lifetime is not required, or + /// you're unsure if you need a longer lifetime, use [path](#method.path). + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let cookie_string = format!("{}={}; Path=/", "foo", "bar"); + /// + /// // `c` will be dropped at the end of the scope, but `path` will live on + /// let path = { + /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); + /// c.path_raw() + /// }; + /// + /// assert_eq!(path, Some("/")); + /// ``` + #[inline] + pub fn path_raw(&self) -> Option<&'c str> { + match (self.path.as_ref(), self.cookie_string.as_ref()) { + (Some(path), Some(string)) => path.to_raw_str(string), + _ => None, + } + } + + /// Returns the `Domain` of `self` as a string slice of the raw string + /// `self` was originally parsed from. If `self` was not originally parsed + /// from a raw string, or if `self` doesn't contain a `Domain`, or if the + /// `Domain` has changed since parsing, returns `None`. + /// + /// This method differs from [domain](#method.domain) in that it returns a + /// string with the same lifetime as the originally parsed string. This + /// lifetime may outlive `self` struct. If a longer lifetime is not + /// required, or you're unsure if you need a longer lifetime, use + /// [domain](#method.domain). + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let cookie_string = format!("{}={}; Domain=crates.io", "foo", "bar"); + /// + /// //`c` will be dropped at the end of the scope, but `domain` will live on + /// let domain = { + /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); + /// c.domain_raw() + /// }; + /// + /// assert_eq!(domain, Some("crates.io")); + /// ``` + #[inline] + pub fn domain_raw(&self) -> Option<&'c str> { + match (self.domain.as_ref(), self.cookie_string.as_ref()) { + (Some(domain), Some(string)) => domain.to_raw_str(string), + _ => None, + } + } +} + +/// Wrapper around `Cookie` whose `Display` implementation percent-encodes the +/// cookie's name and value. +/// +/// A value of this type can be obtained via the +/// [encoded](struct.Cookie.html#method.encoded) method on +/// [Cookie](struct.Cookie.html). This type should only be used for its +/// `Display` implementation. +/// +/// This type is only available when the `percent-encode` feature is enabled. +/// +/// # Example +/// +/// ```rust +/// use actix_http::cookie::Cookie; +/// +/// let mut c = Cookie::new("my name", "this; value?"); +/// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F"); +/// ``` +pub struct EncodedCookie<'a, 'c: 'a>(&'a Cookie<'c>); + +impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Percent-encode the name and value. + let name = percent_encode(self.0.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(self.0.value().as_bytes(), USERINFO_ENCODE_SET); + + // Write out the name/value pair and the cookie's parameters. + write!(f, "{}={}", name, value)?; + self.0.fmt_parameters(f) + } +} + +impl<'c> fmt::Display for Cookie<'c> { + /// Formats the cookie `self` as a `Set-Cookie` header value. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// let mut cookie = Cookie::build("foo", "bar") + /// .path("/") + /// .finish(); + /// + /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); + /// ``` + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}={}", self.name(), self.value())?; + self.fmt_parameters(f) + } +} + +impl FromStr for Cookie<'static> { + type Err = ParseError; + + fn from_str(s: &str) -> Result, ParseError> { + Cookie::parse(s).map(|c| c.into_owned()) + } +} + +impl<'a, 'b> PartialEq> for Cookie<'a> { + fn eq(&self, other: &Cookie<'b>) -> bool { + let so_far_so_good = self.name() == other.name() + && self.value() == other.value() + && self.http_only() == other.http_only() + && self.secure() == other.secure() + && self.max_age() == other.max_age() + && self.expires() == other.expires(); + + if !so_far_so_good { + return false; + } + + match (self.path(), other.path()) { + (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {} + (None, None) => {} + _ => return false, + }; + + match (self.domain(), other.domain()) { + (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {} + (None, None) => {} + _ => return false, + }; + + true + } +} + +#[cfg(test)] +mod tests { + use super::{Cookie, SameSite}; + use time::{strptime, Duration}; + + #[test] + fn format() { + let cookie = Cookie::new("foo", "bar"); + assert_eq!(&cookie.to_string(), "foo=bar"); + + let cookie = Cookie::build("foo", "bar").http_only(true).finish(); + assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly"); + + let cookie = Cookie::build("foo", "bar") + .max_age(Duration::seconds(10)) + .finish(); + assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10"); + + let cookie = Cookie::build("foo", "bar").secure(true).finish(); + assert_eq!(&cookie.to_string(), "foo=bar; Secure"); + + let cookie = Cookie::build("foo", "bar").path("/").finish(); + assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); + + let cookie = Cookie::build("foo", "bar") + .domain("www.rust-lang.org") + .finish(); + assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org"); + + let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; + let expires = strptime(time_str, "%a, %d %b %Y %H:%M:%S %Z").unwrap(); + let cookie = Cookie::build("foo", "bar").expires(expires).finish(); + assert_eq!( + &cookie.to_string(), + "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT" + ); + + let cookie = Cookie::build("foo", "bar") + .same_site(SameSite::Strict) + .finish(); + assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict"); + + let cookie = Cookie::build("foo", "bar") + .same_site(SameSite::Lax) + .finish(); + assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax"); + + let cookie = Cookie::build("foo", "bar") + .same_site(SameSite::None) + .finish(); + assert_eq!(&cookie.to_string(), "foo=bar"); + } + + #[test] + fn cookie_string_long_lifetimes() { + let cookie_string = + "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned(); + let (name, value, path, domain) = { + // Create a cookie passing a slice + let c = Cookie::parse(cookie_string.as_str()).unwrap(); + (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) + }; + + assert_eq!(name, Some("bar")); + assert_eq!(value, Some("baz")); + assert_eq!(path, Some("/subdir")); + assert_eq!(domain, Some("crates.io")); + } + + #[test] + fn owned_cookie_string() { + let cookie_string = + "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned(); + let (name, value, path, domain) = { + // Create a cookie passing an owned string + let c = Cookie::parse(cookie_string).unwrap(); + (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) + }; + + assert_eq!(name, None); + assert_eq!(value, None); + assert_eq!(path, None); + assert_eq!(domain, None); + } + + #[test] + fn owned_cookie_struct() { + let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io"; + let (name, value, path, domain) = { + // Create an owned cookie + let c = Cookie::parse(cookie_string).unwrap().into_owned(); + + (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) + }; + + assert_eq!(name, None); + assert_eq!(value, None); + assert_eq!(path, None); + assert_eq!(domain, None); + } + + #[test] + fn format_encoded() { + let cookie = Cookie::build("foo !?=", "bar;; a").finish(); + let cookie_str = cookie.encoded().to_string(); + assert_eq!(&cookie_str, "foo%20!%3F%3D=bar%3B%3B%20a"); + + let cookie = Cookie::parse_encoded(cookie_str).unwrap(); + assert_eq!(cookie.name_value(), ("foo !?=", "bar;; a")); + } +} diff --git a/actix-http/src/cookie/parse.rs b/actix-http/src/cookie/parse.rs new file mode 100644 index 00000000..ebbd6ffc --- /dev/null +++ b/actix-http/src/cookie/parse.rs @@ -0,0 +1,426 @@ +use std::borrow::Cow; +use std::cmp; +use std::convert::From; +use std::error::Error; +use std::fmt; +use std::str::Utf8Error; + +use percent_encoding::percent_decode; +use time::{self, Duration}; + +use super::{Cookie, CookieStr, SameSite}; + +/// Enum corresponding to a parsing error. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ParseError { + /// The cookie did not contain a name/value pair. + MissingPair, + /// The cookie's name was empty. + EmptyName, + /// Decoding the cookie's name or value resulted in invalid UTF-8. + Utf8Error(Utf8Error), + /// It is discouraged to exhaustively match on this enum as its variants may + /// grow without a breaking-change bump in version numbers. + #[doc(hidden)] + __Nonexhasutive, +} + +impl ParseError { + /// Returns a description of this error as a string + pub fn as_str(&self) -> &'static str { + match *self { + ParseError::MissingPair => "the cookie is missing a name/value pair", + ParseError::EmptyName => "the cookie's name is empty", + ParseError::Utf8Error(_) => { + "decoding the cookie's name or value resulted in invalid UTF-8" + } + ParseError::__Nonexhasutive => unreachable!("__Nonexhasutive ParseError"), + } + } +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl From for ParseError { + fn from(error: Utf8Error) -> ParseError { + ParseError::Utf8Error(error) + } +} + +impl Error for ParseError { + fn description(&self) -> &str { + self.as_str() + } +} + +fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> { + let haystack_start = haystack.as_ptr() as usize; + let needle_start = needle.as_ptr() as usize; + + if needle_start < haystack_start { + return None; + } + + if (needle_start + needle.len()) > (haystack_start + haystack.len()) { + return None; + } + + let start = needle_start - haystack_start; + let end = start + needle.len(); + Some((start, end)) +} + +fn name_val_decoded( + name: &str, + val: &str, +) -> Result<(CookieStr, CookieStr), ParseError> { + let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?; + let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?; + let name = CookieStr::Concrete(Cow::Owned(decoded_name.into_owned())); + let val = CookieStr::Concrete(Cow::Owned(decoded_value.into_owned())); + + Ok((name, val)) +} + +// This function does the real parsing but _does not_ set the `cookie_string` in +// the returned cookie object. This only exists so that the borrow to `s` is +// returned at the end of the call, allowing the `cookie_string` field to be +// set in the outer `parse` function. +fn parse_inner<'c>(s: &str, decode: bool) -> Result, ParseError> { + let mut attributes = s.split(';'); + let key_value = match attributes.next() { + Some(s) => s, + _ => panic!(), + }; + + // Determine the name = val. + let (name, value) = match key_value.find('=') { + Some(i) => (key_value[..i].trim(), key_value[(i + 1)..].trim()), + None => return Err(ParseError::MissingPair), + }; + + if name.is_empty() { + return Err(ParseError::EmptyName); + } + + // Create a cookie with all of the defaults. We'll fill things in while we + // iterate through the parameters below. + let (name, value) = if decode { + name_val_decoded(name, value)? + } else { + let name_indexes = indexes_of(name, s).expect("name sub"); + let value_indexes = indexes_of(value, s).expect("value sub"); + let name = CookieStr::Indexed(name_indexes.0, name_indexes.1); + let value = CookieStr::Indexed(value_indexes.0, value_indexes.1); + + (name, value) + }; + + let mut cookie = Cookie { + name, + value, + cookie_string: None, + expires: None, + max_age: None, + domain: None, + path: None, + secure: None, + http_only: None, + same_site: None, + }; + + for attr in attributes { + let (key, value) = match attr.find('=') { + Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())), + None => (attr.trim(), None), + }; + + match (&*key.to_ascii_lowercase(), value) { + ("secure", _) => cookie.secure = Some(true), + ("httponly", _) => cookie.http_only = Some(true), + ("max-age", Some(v)) => { + // See RFC 6265 Section 5.2.2, negative values indicate that the + // earliest possible expiration time should be used, so set the + // max age as 0 seconds. + cookie.max_age = match v.parse() { + Ok(val) if val <= 0 => Some(Duration::zero()), + Ok(val) => { + // Don't panic if the max age seconds is greater than what's supported by + // `Duration`. + let val = cmp::min(val, Duration::max_value().num_seconds()); + Some(Duration::seconds(val)) + } + Err(_) => continue, + }; + } + ("domain", Some(mut domain)) if !domain.is_empty() => { + if domain.starts_with('.') { + domain = &domain[1..]; + } + + let (i, j) = indexes_of(domain, s).expect("domain sub"); + cookie.domain = Some(CookieStr::Indexed(i, j)); + } + ("path", Some(v)) => { + let (i, j) = indexes_of(v, s).expect("path sub"); + cookie.path = Some(CookieStr::Indexed(i, j)); + } + ("samesite", Some(v)) => { + if v.eq_ignore_ascii_case("strict") { + cookie.same_site = Some(SameSite::Strict); + } else if v.eq_ignore_ascii_case("lax") { + cookie.same_site = Some(SameSite::Lax); + } else { + // We do nothing here, for now. When/if the `SameSite` + // attribute becomes standard, the spec says that we should + // ignore this cookie, i.e, fail to parse it, when an + // invalid value is passed in. The draft is at + // http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-same-site.html. + } + } + ("expires", Some(v)) => { + // Try strptime with three date formats according to + // http://tools.ietf.org/html/rfc2616#section-3.3.1. Try + // additional ones as encountered in the real world. + let tm = time::strptime(v, "%a, %d %b %Y %H:%M:%S %Z") + .or_else(|_| time::strptime(v, "%A, %d-%b-%y %H:%M:%S %Z")) + .or_else(|_| time::strptime(v, "%a, %d-%b-%Y %H:%M:%S %Z")) + .or_else(|_| time::strptime(v, "%a %b %d %H:%M:%S %Y")); + + if let Ok(time) = tm { + cookie.expires = Some(time) + } + } + _ => { + // We're going to be permissive here. If we have no idea what + // this is, then it's something nonstandard. We're not going to + // store it (because it's not compliant), but we're also not + // going to emit an error. + } + } + } + + Ok(cookie) +} + +pub fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result, ParseError> +where + S: Into>, +{ + let s = cow.into(); + let mut cookie = parse_inner(&s, decode)?; + cookie.cookie_string = Some(s); + Ok(cookie) +} + +#[cfg(test)] +mod tests { + use super::{Cookie, SameSite}; + use time::{strptime, Duration}; + + macro_rules! assert_eq_parse { + ($string:expr, $expected:expr) => { + let cookie = match Cookie::parse($string) { + Ok(cookie) => cookie, + Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e), + }; + + assert_eq!(cookie, $expected); + }; + } + + macro_rules! assert_ne_parse { + ($string:expr, $expected:expr) => { + let cookie = match Cookie::parse($string) { + Ok(cookie) => cookie, + Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e), + }; + + assert_ne!(cookie, $expected); + }; + } + + #[test] + fn parse_same_site() { + let expected = Cookie::build("foo", "bar") + .same_site(SameSite::Lax) + .finish(); + + assert_eq_parse!("foo=bar; SameSite=Lax", expected); + assert_eq_parse!("foo=bar; SameSite=lax", expected); + assert_eq_parse!("foo=bar; SameSite=LAX", expected); + assert_eq_parse!("foo=bar; samesite=Lax", expected); + assert_eq_parse!("foo=bar; SAMESITE=Lax", expected); + + let expected = Cookie::build("foo", "bar") + .same_site(SameSite::Strict) + .finish(); + + assert_eq_parse!("foo=bar; SameSite=Strict", expected); + assert_eq_parse!("foo=bar; SameSITE=Strict", expected); + assert_eq_parse!("foo=bar; SameSite=strict", expected); + assert_eq_parse!("foo=bar; SameSite=STrICT", expected); + assert_eq_parse!("foo=bar; SameSite=STRICT", expected); + } + + #[test] + fn parse() { + assert!(Cookie::parse("bar").is_err()); + assert!(Cookie::parse("=bar").is_err()); + assert!(Cookie::parse(" =bar").is_err()); + assert!(Cookie::parse("foo=").is_ok()); + + let expected = Cookie::build("foo", "bar=baz").finish(); + assert_eq_parse!("foo=bar=baz", expected); + + let mut expected = Cookie::build("foo", "bar").finish(); + assert_eq_parse!("foo=bar", expected); + assert_eq_parse!("foo = bar", expected); + assert_eq_parse!(" foo=bar ", expected); + assert_eq_parse!(" foo=bar ;Domain=", expected); + assert_eq_parse!(" foo=bar ;Domain= ", expected); + assert_eq_parse!(" foo=bar ;Ignored", expected); + + let mut unexpected = Cookie::build("foo", "bar").http_only(false).finish(); + assert_ne_parse!(" foo=bar ;HttpOnly", unexpected); + assert_ne_parse!(" foo=bar; httponly", unexpected); + + expected.set_http_only(true); + assert_eq_parse!(" foo=bar ;HttpOnly", expected); + assert_eq_parse!(" foo=bar ;httponly", expected); + assert_eq_parse!(" foo=bar ;HTTPONLY=whatever", expected); + assert_eq_parse!(" foo=bar ; sekure; HTTPONLY", expected); + + expected.set_secure(true); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure", expected); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure=aaaa", expected); + + unexpected.set_http_only(true); + unexpected.set_secure(true); + assert_ne_parse!(" foo=bar ;HttpOnly; skeure", unexpected); + assert_ne_parse!(" foo=bar ;HttpOnly; =secure", unexpected); + assert_ne_parse!(" foo=bar ;HttpOnly;", unexpected); + + unexpected.set_secure(false); + assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected); + assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected); + assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected); + + expected.set_max_age(Duration::zero()); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=0", expected); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0 ", expected); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=-1", expected); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", expected); + + expected.set_max_age(Duration::minutes(1)); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=60", expected); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 60 ", expected); + + expected.set_max_age(Duration::seconds(4)); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4", expected); + assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 4 ", expected); + + unexpected.set_secure(true); + unexpected.set_max_age(Duration::minutes(1)); + assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=122", unexpected); + assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 38 ", unexpected); + assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=51", unexpected); + assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", unexpected); + assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0", unexpected); + + expected.set_path("/"); + assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/", expected); + assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/", expected); + + expected.set_path("/foo"); + assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", expected); + assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/foo", expected); + assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path=/foo", expected); + assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path = /foo", expected); + + unexpected.set_max_age(Duration::seconds(4)); + unexpected.set_path("/bar"); + assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", unexpected); + assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/baz", unexpected); + + expected.set_domain("www.foo.com"); + assert_eq_parse!( + " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ + Domain=www.foo.com", + expected + ); + + expected.set_domain("foo.com"); + assert_eq_parse!( + " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ + Domain=foo.com", + expected + ); + assert_eq_parse!( + " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ + Domain=FOO.COM", + expected + ); + + unexpected.set_path("/foo"); + unexpected.set_domain("bar.com"); + assert_ne_parse!( + " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ + Domain=foo.com", + unexpected + ); + assert_ne_parse!( + " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ + Domain=FOO.COM", + unexpected + ); + + let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; + let expires = strptime(time_str, "%a, %d %b %Y %H:%M:%S %Z").unwrap(); + expected.set_expires(expires); + assert_eq_parse!( + " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ + Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", + expected + ); + + unexpected.set_domain("foo.com"); + let bad_expires = strptime(time_str, "%a, %d %b %Y %H:%S:%M %Z").unwrap(); + expected.set_expires(bad_expires); + assert_ne_parse!( + " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ + Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", + unexpected + ); + } + + #[test] + fn odd_characters() { + let expected = Cookie::new("foo", "b%2Fr"); + assert_eq_parse!("foo=b%2Fr", expected); + } + + #[test] + fn odd_characters_encoded() { + let expected = Cookie::new("foo", "b/r"); + let cookie = match Cookie::parse_encoded("foo=b%2Fr") { + Ok(cookie) => cookie, + Err(e) => panic!("Failed to parse: {:?}", e), + }; + + assert_eq!(cookie, expected); + } + + #[test] + fn do_not_panic_on_large_max_ages() { + let max_seconds = Duration::max_value().num_seconds(); + let expected = Cookie::build("foo", "bar") + .max_age(Duration::seconds(max_seconds)) + .finish(); + assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected); + } +} diff --git a/actix-http/src/cookie/secure/key.rs b/actix-http/src/cookie/secure/key.rs new file mode 100644 index 00000000..3b8a0af7 --- /dev/null +++ b/actix-http/src/cookie/secure/key.rs @@ -0,0 +1,180 @@ +use ring::digest::{Algorithm, SHA256}; +use ring::hkdf::expand; +use ring::hmac::SigningKey; +use ring::rand::{SecureRandom, SystemRandom}; + +use super::private::KEY_LEN as PRIVATE_KEY_LEN; +use super::signed::KEY_LEN as SIGNED_KEY_LEN; + +static HKDF_DIGEST: &'static Algorithm = &SHA256; +const KEYS_INFO: &'static str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; + +/// A cryptographic master key for use with `Signed` and/or `Private` jars. +/// +/// This structure encapsulates secure, cryptographic keys for use with both +/// [PrivateJar](struct.PrivateJar.html) and [SignedJar](struct.SignedJar.html). +/// It can be derived from a single master key via +/// [from_master](#method.from_master) or generated from a secure random source +/// via [generate](#method.generate). A single instance of `Key` can be used for +/// both a `PrivateJar` and a `SignedJar`. +/// +/// This type is only available when the `secure` feature is enabled. +#[derive(Clone)] +pub struct Key { + signing_key: [u8; SIGNED_KEY_LEN], + encryption_key: [u8; PRIVATE_KEY_LEN], +} + +impl Key { + /// Derives new signing/encryption keys from a master key. + /// + /// The master key must be at least 256-bits (32 bytes). For security, the + /// master key _must_ be cryptographically random. The keys are derived + /// deterministically from the master key. + /// + /// # Panics + /// + /// Panics if `key` is less than 32 bytes in length. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Key; + /// + /// # /* + /// let master_key = { /* a cryptographically random key >= 32 bytes */ }; + /// # */ + /// # let master_key: &Vec = &(0..32).collect(); + /// + /// let key = Key::from_master(master_key); + /// ``` + pub fn from_master(key: &[u8]) -> Key { + if key.len() < 32 { + panic!( + "bad master key length: expected at least 32 bytes, found {}", + key.len() + ); + } + + // Expand the user's key into two. + let prk = SigningKey::new(HKDF_DIGEST, key); + let mut both_keys = [0; SIGNED_KEY_LEN + PRIVATE_KEY_LEN]; + expand(&prk, KEYS_INFO.as_bytes(), &mut both_keys); + + // Copy the keys into their respective arrays. + let mut signing_key = [0; SIGNED_KEY_LEN]; + let mut encryption_key = [0; PRIVATE_KEY_LEN]; + signing_key.copy_from_slice(&both_keys[..SIGNED_KEY_LEN]); + encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]); + + Key { + signing_key: signing_key, + encryption_key: encryption_key, + } + } + + /// Generates signing/encryption keys from a secure, random source. Keys are + /// generated nondeterministically. + /// + /// # Panics + /// + /// Panics if randomness cannot be retrieved from the operating system. See + /// [try_generate](#method.try_generate) for a non-panicking version. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Key; + /// + /// let key = Key::generate(); + /// ``` + pub fn generate() -> Key { + Self::try_generate().expect("failed to generate `Key` from randomness") + } + + /// Attempts to generate signing/encryption keys from a secure, random + /// source. Keys are generated nondeterministically. If randomness cannot be + /// retrieved from the underlying operating system, returns `None`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Key; + /// + /// let key = Key::try_generate(); + /// ``` + pub fn try_generate() -> Option { + let mut sign_key = [0; SIGNED_KEY_LEN]; + let mut enc_key = [0; PRIVATE_KEY_LEN]; + + let rng = SystemRandom::new(); + if rng.fill(&mut sign_key).is_err() || rng.fill(&mut enc_key).is_err() { + return None; + } + + Some(Key { + signing_key: sign_key, + encryption_key: enc_key, + }) + } + + /// Returns the raw bytes of a key suitable for signing cookies. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Key; + /// + /// let key = Key::generate(); + /// let signing_key = key.signing(); + /// ``` + pub fn signing(&self) -> &[u8] { + &self.signing_key[..] + } + + /// Returns the raw bytes of a key suitable for encrypting cookies. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Key; + /// + /// let key = Key::generate(); + /// let encryption_key = key.encryption(); + /// ``` + pub fn encryption(&self) -> &[u8] { + &self.encryption_key[..] + } +} + +#[cfg(test)] +mod test { + use super::Key; + + #[test] + fn deterministic_from_master() { + let master_key: Vec = (0..32).collect(); + + let key_a = Key::from_master(&master_key); + let key_b = Key::from_master(&master_key); + + assert_eq!(key_a.signing(), key_b.signing()); + assert_eq!(key_a.encryption(), key_b.encryption()); + assert_ne!(key_a.encryption(), key_a.signing()); + + let master_key_2: Vec = (32..64).collect(); + let key_2 = Key::from_master(&master_key_2); + + assert_ne!(key_2.signing(), key_a.signing()); + assert_ne!(key_2.encryption(), key_a.encryption()); + } + + #[test] + fn non_deterministic_generate() { + let key_a = Key::generate(); + let key_b = Key::generate(); + + assert_ne!(key_a.signing(), key_b.signing()); + assert_ne!(key_a.encryption(), key_b.encryption()); + } +} diff --git a/actix-http/src/cookie/secure/macros.rs b/actix-http/src/cookie/secure/macros.rs new file mode 100644 index 00000000..089047c4 --- /dev/null +++ b/actix-http/src/cookie/secure/macros.rs @@ -0,0 +1,40 @@ +#[cfg(test)] +macro_rules! assert_simple_behaviour { + ($clear:expr, $secure:expr) => {{ + assert_eq!($clear.iter().count(), 0); + + $secure.add(Cookie::new("name", "val")); + assert_eq!($clear.iter().count(), 1); + assert_eq!($secure.get("name").unwrap().value(), "val"); + assert_ne!($clear.get("name").unwrap().value(), "val"); + + $secure.add(Cookie::new("another", "two")); + assert_eq!($clear.iter().count(), 2); + + $clear.remove(Cookie::named("another")); + assert_eq!($clear.iter().count(), 1); + + $secure.remove(Cookie::named("name")); + assert_eq!($clear.iter().count(), 0); + }}; +} + +#[cfg(test)] +macro_rules! assert_secure_behaviour { + ($clear:expr, $secure:expr) => {{ + $secure.add(Cookie::new("secure", "secure")); + assert!($clear.get("secure").unwrap().value() != "secure"); + assert!($secure.get("secure").unwrap().value() == "secure"); + + let mut cookie = $clear.get("secure").unwrap().clone(); + let new_val = format!("{}l", cookie.value()); + cookie.set_value(new_val); + $clear.add(cookie); + assert!($secure.get("secure").is_none()); + + let mut cookie = $clear.get("secure").unwrap().clone(); + cookie.set_value("foobar"); + $clear.add(cookie); + assert!($secure.get("secure").is_none()); + }}; +} diff --git a/actix-http/src/cookie/secure/mod.rs b/actix-http/src/cookie/secure/mod.rs new file mode 100644 index 00000000..e0fba973 --- /dev/null +++ b/actix-http/src/cookie/secure/mod.rs @@ -0,0 +1,10 @@ +//! Fork of https://github.com/alexcrichton/cookie-rs +#[macro_use] +mod macros; +mod key; +mod private; +mod signed; + +pub use self::key::*; +pub use self::private::*; +pub use self::signed::*; diff --git a/actix-http/src/cookie/secure/private.rs b/actix-http/src/cookie/secure/private.rs new file mode 100644 index 00000000..8b56991f --- /dev/null +++ b/actix-http/src/cookie/secure/private.rs @@ -0,0 +1,226 @@ +use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM}; +use ring::aead::{OpeningKey, SealingKey}; +use ring::rand::{SecureRandom, SystemRandom}; + +use super::Key; +use crate::cookie::{Cookie, CookieJar}; + +// Keep these in sync, and keep the key len synced with the `private` docs as +// well as the `KEYS_INFO` const in secure::Key. +static ALGO: &'static Algorithm = &AES_256_GCM; +const NONCE_LEN: usize = 12; +pub const KEY_LEN: usize = 32; + +/// A child cookie jar that provides authenticated encryption for its cookies. +/// +/// A _private_ child jar signs and encrypts all the cookies added to it and +/// verifies and decrypts cookies retrieved from it. Any cookies stored in a +/// `PrivateJar` are simultaneously assured confidentiality, integrity, and +/// authenticity. In other words, clients cannot discover nor tamper with the +/// contents of a cookie, nor can they fabricate cookie data. +/// +/// This type is only available when the `secure` feature is enabled. +pub struct PrivateJar<'a> { + parent: &'a mut CookieJar, + key: [u8; KEY_LEN], +} + +impl<'a> PrivateJar<'a> { + /// Creates a new child `PrivateJar` with parent `parent` and key `key`. + /// This method is typically called indirectly via the `signed` method of + /// `CookieJar`. + #[doc(hidden)] + pub fn new(parent: &'a mut CookieJar, key: &Key) -> PrivateJar<'a> { + let mut key_array = [0u8; KEY_LEN]; + key_array.copy_from_slice(key.encryption()); + PrivateJar { + parent: parent, + key: key_array, + } + } + + /// Given a sealed value `str` and a key name `name`, where the nonce is + /// prepended to the original value and then both are Base64 encoded, + /// verifies and decrypts the sealed value and returns it. If there's a + /// problem, returns an `Err` with a string describing the issue. + fn unseal(&self, name: &str, value: &str) -> Result { + let mut data = base64::decode(value).map_err(|_| "bad base64 value")?; + if data.len() <= NONCE_LEN { + return Err("length of decoded data is <= NONCE_LEN"); + } + + let ad = Aad::from(name.as_bytes()); + let key = OpeningKey::new(ALGO, &self.key).expect("opening key"); + let (nonce, sealed) = data.split_at_mut(NONCE_LEN); + let nonce = + Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`"); + let unsealed = open_in_place(&key, nonce, ad, 0, sealed) + .map_err(|_| "invalid key/nonce/value: bad seal")?; + + ::std::str::from_utf8(unsealed) + .map(|s| s.to_string()) + .map_err(|_| "bad unsealed utf8") + } + + /// Returns a reference to the `Cookie` inside this jar with the name `name` + /// and authenticates and decrypts the cookie's value, returning a `Cookie` + /// with the decrypted value. If the cookie cannot be found, or the cookie + /// fails to authenticate or decrypt, `None` is returned. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie, Key}; + /// + /// let key = Key::generate(); + /// let mut jar = CookieJar::new(); + /// let mut private_jar = jar.private(&key); + /// assert!(private_jar.get("name").is_none()); + /// + /// private_jar.add(Cookie::new("name", "value")); + /// assert_eq!(private_jar.get("name").unwrap().value(), "value"); + /// ``` + pub fn get(&self, name: &str) -> Option> { + if let Some(cookie_ref) = self.parent.get(name) { + let mut cookie = cookie_ref.clone(); + if let Ok(value) = self.unseal(name, cookie.value()) { + cookie.set_value(value); + return Some(cookie); + } + } + + None + } + + /// Adds `cookie` to the parent jar. The cookie's value is encrypted with + /// authenticated encryption assuring confidentiality, integrity, and + /// authenticity. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie, Key}; + /// + /// let key = Key::generate(); + /// let mut jar = CookieJar::new(); + /// jar.private(&key).add(Cookie::new("name", "value")); + /// + /// assert_ne!(jar.get("name").unwrap().value(), "value"); + /// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value"); + /// ``` + pub fn add(&mut self, mut cookie: Cookie<'static>) { + self.encrypt_cookie(&mut cookie); + + // Add the sealed cookie to the parent. + self.parent.add(cookie); + } + + /// Adds an "original" `cookie` to parent jar. The cookie's value is + /// encrypted with authenticated encryption assuring confidentiality, + /// integrity, and authenticity. Adding an original cookie does not affect + /// the [`CookieJar::delta()`](struct.CookieJar.html#method.delta) + /// computation. This method is intended to be used to seed the cookie jar + /// with cookies received from a client's HTTP message. + /// + /// For accurate `delta` computations, this method should not be called + /// after calling `remove`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie, Key}; + /// + /// let key = Key::generate(); + /// let mut jar = CookieJar::new(); + /// jar.private(&key).add_original(Cookie::new("name", "value")); + /// + /// assert_eq!(jar.iter().count(), 1); + /// assert_eq!(jar.delta().count(), 0); + /// ``` + pub fn add_original(&mut self, mut cookie: Cookie<'static>) { + self.encrypt_cookie(&mut cookie); + + // Add the sealed cookie to the parent. + self.parent.add_original(cookie); + } + + /// Encrypts the cookie's value with + /// authenticated encryption assuring confidentiality, integrity, and authenticity. + fn encrypt_cookie(&self, cookie: &mut Cookie) { + let mut data; + let output_len = { + // Create the `SealingKey` structure. + let key = SealingKey::new(ALGO, &self.key).expect("sealing key creation"); + + // Create a vec to hold the [nonce | cookie value | overhead]. + let overhead = ALGO.tag_len(); + let cookie_val = cookie.value().as_bytes(); + data = vec![0; NONCE_LEN + cookie_val.len() + overhead]; + + // Randomly generate the nonce, then copy the cookie value as input. + let (nonce, in_out) = data.split_at_mut(NONCE_LEN); + SystemRandom::new() + .fill(nonce) + .expect("couldn't random fill nonce"); + in_out[..cookie_val.len()].copy_from_slice(cookie_val); + let nonce = Nonce::try_assume_unique_for_key(nonce) + .expect("invalid length of `nonce`"); + + // Use cookie's name as associated data to prevent value swapping. + let ad = Aad::from(cookie.name().as_bytes()); + + // Perform the actual sealing operation and get the output length. + seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal") + }; + + // Base64 encode the nonce and encrypted value. + let sealed_value = base64::encode(&data[..(NONCE_LEN + output_len)]); + cookie.set_value(sealed_value); + } + + /// Removes `cookie` from the parent jar. + /// + /// For correct removal, the passed in `cookie` must contain the same `path` + /// and `domain` as the cookie that was initially set. + /// + /// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more + /// details. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie, Key}; + /// + /// let key = Key::generate(); + /// let mut jar = CookieJar::new(); + /// let mut private_jar = jar.private(&key); + /// + /// private_jar.add(Cookie::new("name", "value")); + /// assert!(private_jar.get("name").is_some()); + /// + /// private_jar.remove(Cookie::named("name")); + /// assert!(private_jar.get("name").is_none()); + /// ``` + pub fn remove(&mut self, cookie: Cookie<'static>) { + self.parent.remove(cookie); + } +} + +#[cfg(test)] +mod test { + use super::{Cookie, CookieJar, Key}; + + #[test] + fn simple() { + let key = Key::generate(); + let mut jar = CookieJar::new(); + assert_simple_behaviour!(jar, jar.private(&key)); + } + + #[test] + fn private() { + let key = Key::generate(); + let mut jar = CookieJar::new(); + assert_secure_behaviour!(jar, jar.private(&key)); + } +} diff --git a/actix-http/src/cookie/secure/signed.rs b/actix-http/src/cookie/secure/signed.rs new file mode 100644 index 00000000..5a4ffb76 --- /dev/null +++ b/actix-http/src/cookie/secure/signed.rs @@ -0,0 +1,185 @@ +use ring::digest::{Algorithm, SHA256}; +use ring::hmac::{sign, verify_with_own_key as verify, SigningKey}; + +use super::Key; +use crate::cookie::{Cookie, CookieJar}; + +// Keep these in sync, and keep the key len synced with the `signed` docs as +// well as the `KEYS_INFO` const in secure::Key. +static HMAC_DIGEST: &'static Algorithm = &SHA256; +const BASE64_DIGEST_LEN: usize = 44; +pub const KEY_LEN: usize = 32; + +/// A child cookie jar that authenticates its cookies. +/// +/// A _signed_ child jar signs all the cookies added to it and verifies cookies +/// retrieved from it. Any cookies stored in a `SignedJar` are assured integrity +/// and authenticity. In other words, clients cannot tamper with the contents of +/// a cookie nor can they fabricate cookie values, but the data is visible in +/// plaintext. +/// +/// This type is only available when the `secure` feature is enabled. +pub struct SignedJar<'a> { + parent: &'a mut CookieJar, + key: SigningKey, +} + +impl<'a> SignedJar<'a> { + /// Creates a new child `SignedJar` with parent `parent` and key `key`. This + /// method is typically called indirectly via the `signed` method of + /// `CookieJar`. + #[doc(hidden)] + pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> { + SignedJar { + parent: parent, + key: SigningKey::new(HMAC_DIGEST, key.signing()), + } + } + + /// Given a signed value `str` where the signature is prepended to `value`, + /// verifies the signed value and returns it. If there's a problem, returns + /// an `Err` with a string describing the issue. + fn verify(&self, cookie_value: &str) -> Result { + if cookie_value.len() < BASE64_DIGEST_LEN { + return Err("length of value is <= BASE64_DIGEST_LEN"); + } + + let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN); + let sig = base64::decode(digest_str).map_err(|_| "bad base64 digest")?; + + verify(&self.key, value.as_bytes(), &sig) + .map(|_| value.to_string()) + .map_err(|_| "value did not verify") + } + + /// Returns a reference to the `Cookie` inside this jar with the name `name` + /// and verifies the authenticity and integrity of the cookie's value, + /// returning a `Cookie` with the authenticated value. If the cookie cannot + /// be found, or the cookie fails to verify, `None` is returned. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie, Key}; + /// + /// let key = Key::generate(); + /// let mut jar = CookieJar::new(); + /// let mut signed_jar = jar.signed(&key); + /// assert!(signed_jar.get("name").is_none()); + /// + /// signed_jar.add(Cookie::new("name", "value")); + /// assert_eq!(signed_jar.get("name").unwrap().value(), "value"); + /// ``` + pub fn get(&self, name: &str) -> Option> { + if let Some(cookie_ref) = self.parent.get(name) { + let mut cookie = cookie_ref.clone(); + if let Ok(value) = self.verify(cookie.value()) { + cookie.set_value(value); + return Some(cookie); + } + } + + None + } + + /// Adds `cookie` to the parent jar. The cookie's value is signed assuring + /// integrity and authenticity. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie, Key}; + /// + /// let key = Key::generate(); + /// let mut jar = CookieJar::new(); + /// jar.signed(&key).add(Cookie::new("name", "value")); + /// + /// assert_ne!(jar.get("name").unwrap().value(), "value"); + /// assert!(jar.get("name").unwrap().value().contains("value")); + /// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value"); + /// ``` + pub fn add(&mut self, mut cookie: Cookie<'static>) { + self.sign_cookie(&mut cookie); + self.parent.add(cookie); + } + + /// Adds an "original" `cookie` to this jar. The cookie's value is signed + /// assuring integrity and authenticity. Adding an original cookie does not + /// affect the [`CookieJar::delta()`](struct.CookieJar.html#method.delta) + /// computation. This method is intended to be used to seed the cookie jar + /// with cookies received from a client's HTTP message. + /// + /// For accurate `delta` computations, this method should not be called + /// after calling `remove`. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie, Key}; + /// + /// let key = Key::generate(); + /// let mut jar = CookieJar::new(); + /// jar.signed(&key).add_original(Cookie::new("name", "value")); + /// + /// assert_eq!(jar.iter().count(), 1); + /// assert_eq!(jar.delta().count(), 0); + /// ``` + pub fn add_original(&mut self, mut cookie: Cookie<'static>) { + self.sign_cookie(&mut cookie); + self.parent.add_original(cookie); + } + + /// Signs the cookie's value assuring integrity and authenticity. + fn sign_cookie(&self, cookie: &mut Cookie) { + let digest = sign(&self.key, cookie.value().as_bytes()); + let mut new_value = base64::encode(digest.as_ref()); + new_value.push_str(cookie.value()); + cookie.set_value(new_value); + } + + /// Removes `cookie` from the parent jar. + /// + /// For correct removal, the passed in `cookie` must contain the same `path` + /// and `domain` as the cookie that was initially set. + /// + /// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more + /// details. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::{CookieJar, Cookie, Key}; + /// + /// let key = Key::generate(); + /// let mut jar = CookieJar::new(); + /// let mut signed_jar = jar.signed(&key); + /// + /// signed_jar.add(Cookie::new("name", "value")); + /// assert!(signed_jar.get("name").is_some()); + /// + /// signed_jar.remove(Cookie::named("name")); + /// assert!(signed_jar.get("name").is_none()); + /// ``` + pub fn remove(&mut self, cookie: Cookie<'static>) { + self.parent.remove(cookie); + } +} + +#[cfg(test)] +mod test { + use super::{Cookie, CookieJar, Key}; + + #[test] + fn simple() { + let key = Key::generate(); + let mut jar = CookieJar::new(); + assert_simple_behaviour!(jar, jar.signed(&key)); + } + + #[test] + fn private() { + let key = Key::generate(); + let mut jar = CookieJar::new(); + assert_secure_behaviour!(jar, jar.signed(&key)); + } +} diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index b062fdf4..45bf067d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -6,8 +6,6 @@ use std::{fmt, io, result}; pub use actix_threadpool::BlockingError; use actix_utils::timeout::TimeoutError; -#[cfg(feature = "cookies")] -use cookie; use derive_more::{Display, From}; use futures::Canceled; use http::uri::InvalidUri; @@ -19,8 +17,7 @@ use serde_urlencoded::ser::Error as FormError; use tokio_timer::Error as TimerError; // re-export for convinience -#[cfg(feature = "cookies")] -pub use cookie::ParseError as CookieParseError; +pub use crate::cookie::ParseError as CookieParseError; use crate::body::Body; use crate::response::Response; @@ -79,7 +76,7 @@ impl fmt::Display for Error { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}\n", &self.cause) + writeln!(f, "{:?}", &self.cause) } } @@ -319,8 +316,7 @@ impl ResponseError for PayloadError { } /// Return `BadRequest` for `cookie::ParseError` -#[cfg(feature = "cookies")] -impl ResponseError for cookie::ParseError { +impl ResponseError for crate::cookie::ParseError { fn error_response(&self) -> Response { Response::new(StatusCode::BAD_REQUEST) } @@ -895,10 +891,8 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[cfg(feature = "cookies")] #[test] fn test_cookie_parse() { - use cookie::ParseError as CookieParseError; let resp: Response = CookieParseError::EmptyName.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 34204bf5..96db0812 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -525,7 +525,7 @@ where // keep-alive and stream errors if inner.state.is_empty() && inner.framed.is_write_buf_empty() { if let Some(err) = inner.error.take() { - return Err(err); + Err(err) } // disconnect if keep-alive is not enabled else if inner.flags.contains(Flags::STARTED) @@ -538,10 +538,10 @@ where else if inner.flags.contains(Flags::SHUTDOWN) { self.poll() } else { - return Ok(Async::NotReady); + Ok(Async::NotReady) } } else { - return Ok(Async::NotReady); + Ok(Async::NotReady) } } } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 6ab37919..9d9a19e2 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -220,7 +220,7 @@ where Ok(Async::NotReady) => Ok(Async::NotReady), Err(err) => { trace!("H2 handshake error: {}", err); - return Err(err.into()); + Err(err.into()) } } } diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/httpmessage.rs index 60821d30..7a2db52d 100644 --- a/actix-http/src/httpmessage.rs +++ b/actix-http/src/httpmessage.rs @@ -7,17 +7,12 @@ use encoding::EncodingRef; use http::{header, HeaderMap}; use mime::Mime; -use crate::error::{ContentTypeError, ParseError}; +use crate::cookie::Cookie; +use crate::error::{ContentTypeError, CookieParseError, ParseError}; use crate::extensions::Extensions; use crate::header::Header; use crate::payload::Payload; -#[cfg(feature = "cookies")] -use crate::error::CookieParseError; -#[cfg(feature = "cookies")] -use cookie::Cookie; - -#[cfg(feature = "cookies")] struct Cookies(Vec>); /// Trait that implements general purpose operations on http messages @@ -110,7 +105,6 @@ pub trait HttpMessage: Sized { /// Load request cookies. #[inline] - #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); @@ -131,7 +125,6 @@ pub trait HttpMessage: Sized { } /// Return request cookie. - #[cfg(feature = "cookies")] fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { for cookie in cookies.iter() { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 565fe445..a8c44e83 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,9 +1,5 @@ //! Basic http primitives for actix-net framework. -#![allow( - clippy::type_complexity, - clippy::new_without_default, - clippy::new_without_default_derive -)] +#![allow(clippy::type_complexity, clippy::new_without_default)] #[macro_use] extern crate log; @@ -24,6 +20,7 @@ mod request; mod response; mod service; +pub mod cookie; pub mod error; pub mod h1; pub mod h2; @@ -46,16 +43,11 @@ pub mod http { // re-exports pub use http::header::{HeaderName, HeaderValue}; + pub use http::uri::PathAndQuery; + pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri}; pub use http::{Method, StatusCode, Version}; - #[doc(hidden)] - pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri}; - - #[doc(hidden)] - pub use http::uri::PathAndQuery; - - #[cfg(feature = "cookies")] - pub use cookie::{Cookie, CookieBuilder}; + pub use crate::cookie::{Cookie, CookieBuilder}; /// Various http headers pub mod header { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 4da0f642..a6e8f613 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -4,8 +4,6 @@ use std::io::Write; use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; -#[cfg(feature = "cookies")] -use cookie::{Cookie, CookieJar}; use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; use http::header::{self, HeaderName, HeaderValue}; @@ -14,6 +12,7 @@ use serde::Serialize; use serde_json; use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; +use crate::cookie::{Cookie, CookieJar}; use crate::error::Error; use crate::extensions::Extensions; use crate::header::{Header, IntoHeaderValue}; @@ -131,7 +130,6 @@ impl Response { /// Get an iterator for the cookies set by this response #[inline] - #[cfg(feature = "cookies")] pub fn cookies(&self) -> CookieIter { CookieIter { iter: self.head.headers.get_all(header::SET_COOKIE).iter(), @@ -140,7 +138,6 @@ impl Response { /// Add a cookie to this response #[inline] - #[cfg(feature = "cookies")] pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { let h = &mut self.head.headers; HeaderValue::from_str(&cookie.to_string()) @@ -153,7 +150,6 @@ impl Response { /// Remove all cookies with the given name from this response. Returns /// the number of cookies removed. #[inline] - #[cfg(feature = "cookies")] pub fn del_cookie(&mut self, name: &str) -> usize { let h = &mut self.head.headers; let vals: Vec = h @@ -245,8 +241,8 @@ impl Response { let body = f(&mut self.head, self.body); Response { + body, head: self.head, - body: body, error: self.error, } } @@ -285,12 +281,10 @@ impl IntoFuture for Response { } } -#[cfg(feature = "cookies")] pub struct CookieIter<'a> { iter: header::ValueIter<'a, HeaderValue>, } -#[cfg(feature = "cookies")] impl<'a> Iterator for CookieIter<'a> { type Item = Cookie<'a>; @@ -312,7 +306,6 @@ impl<'a> Iterator for CookieIter<'a> { pub struct ResponseBuilder { head: Option>, err: Option, - #[cfg(feature = "cookies")] cookies: Option, } @@ -325,7 +318,6 @@ impl ResponseBuilder { ResponseBuilder { head: Some(head), err: None, - #[cfg(feature = "cookies")] cookies: None, } } @@ -525,7 +517,6 @@ impl ResponseBuilder { /// .finish() /// } /// ``` - #[cfg(feature = "cookies")] pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -553,7 +544,6 @@ impl ResponseBuilder { /// builder.finish() /// } /// ``` - #[cfg(feature = "cookies")] pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { { if self.cookies.is_none() { @@ -620,20 +610,16 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } - #[allow(unused_mut)] let mut response = self.head.take().expect("cannot reuse response builder"); - #[cfg(feature = "cookies")] - { - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => { - let _ = response.headers.append(header::SET_COOKIE, val); - } - Err(e) => return Response::from(Error::from(e)).into_body(), - }; - } + if let Some(ref jar) = self.cookies { + for cookie in jar.delta() { + match HeaderValue::from_str(&cookie.to_string()) { + Ok(val) => { + let _ = response.headers.append(header::SET_COOKIE, val); + } + Err(e) => return Response::from(Error::from(e)).into_body(), + }; } } @@ -697,7 +683,6 @@ impl ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), - #[cfg(feature = "cookies")] cookies: self.cookies.take(), } } @@ -718,9 +703,7 @@ fn parts<'a>( impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { // If this response has cookies, load them into a jar - #[cfg(feature = "cookies")] let mut jar: Option = None; - #[cfg(feature = "cookies")] for c in res.cookies() { if let Some(ref mut j) = jar { j.add_original(c.into_owned()); @@ -734,7 +717,6 @@ impl From> for ResponseBuilder { ResponseBuilder { head: Some(res.head), err: None, - #[cfg(feature = "cookies")] cookies: jar, } } @@ -744,22 +726,18 @@ impl From> for ResponseBuilder { impl<'a> From<&'a ResponseHead> for ResponseBuilder { fn from(head: &'a ResponseHead) -> ResponseBuilder { // If this response has cookies, load them into a jar - #[cfg(feature = "cookies")] let mut jar: Option = None; - #[cfg(feature = "cookies")] - { - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE).iter(), - }; - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } + let cookies = CookieIter { + iter: head.headers.get_all(header::SET_COOKIE).iter(), + }; + for c in cookies { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); } } @@ -773,7 +751,6 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { ResponseBuilder { head: Some(msg), err: None, - #[cfg(feature = "cookies")] cookies: jar, } } @@ -870,7 +847,6 @@ mod tests { } #[test] - #[cfg(feature = "cookies")] fn test_response_cookies() { use crate::httpmessage::HttpMessage; @@ -907,7 +883,6 @@ mod tests { } #[test] - #[cfg(feature = "cookies")] fn test_update_response_cookies() { let mut r = Response::Ok() .cookie(crate::http::Cookie::new("original", "val100")) @@ -1068,7 +1043,6 @@ mod tests { } #[test] - #[cfg(feature = "cookies")] fn test_into_builder() { let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 2d4b3d0f..02316124 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -1,12 +1,13 @@ //! Test Various helpers for Actix applications to use during testing. +use std::fmt::Write as FmtWrite; use std::str::FromStr; use bytes::Bytes; -#[cfg(feature = "cookies")] -use cookie::{Cookie, CookieJar}; -use http::header::HeaderName; +use http::header::{self, HeaderName, HeaderValue}; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use crate::cookie::{Cookie, CookieJar}; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; use crate::Request; @@ -45,7 +46,6 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, - #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -57,7 +57,6 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), - #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, })) @@ -127,7 +126,6 @@ impl TestRequest { } /// Set cookie for this request - #[cfg(feature = "cookies")] pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self @@ -161,25 +159,17 @@ impl TestRequest { head.version = inner.version; head.headers = inner.headers; - #[cfg(feature = "cookies")] - { - use std::fmt::Write as FmtWrite; - - use http::header::{self, HeaderValue}; - use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; - - let mut cookie = String::new(); - for c in inner.cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - if !cookie.is_empty() { - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } + let mut cookie = String::new(); + for c in inner.cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + if !cookie.is_empty() { + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); } req diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 78b99970..ea0c5eb9 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,11 +1,8 @@ use actix_service::NewService; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use futures::future::{self, ok}; -use futures::{Future, Stream}; -use actix_http::{ - error::PayloadError, http, HttpMessage, HttpService, Request, Response, -}; +use actix_http::{http, HttpService, Request, Response}; use actix_http_test::TestServer; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -30,16 +27,6 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -fn load_body(stream: S) -> impl Future -where - S: Stream, -{ - stream.fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, PayloadError>(body) - }) -} - #[test] fn test_h1_v2() { env_logger::init(); diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index a2b8e0e1..6569286c 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -21,13 +21,13 @@ path = "src/lib.rs" default = ["cookie-session"] # sessions feature, session require "ring" crate and c compiler -cookie-session = ["cookie/secure"] +cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-alpha.1" +#actix-web = "1.0.0-alpha.1" +actix-web = { path = ".." } actix-service = "0.3.3" bytes = "0.4" -cookie = { version="0.11", features=["percent-encode"], optional=true } derive_more = "0.14" futures = "0.1.25" hashbrown = "0.1.8" diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 2d5bd708..9e4fe78b 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -19,10 +19,10 @@ use std::collections::HashMap; use std::rc::Rc; use actix_service::{Service, Transform}; +use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::{header::SET_COOKIE, HeaderValue}; use actix_web::{Error, HttpMessage, ResponseError}; -use cookie::{Cookie, CookieJar, Key, SameSite}; use derive_more::{Display, From}; use futures::future::{ok, Future, FutureResult}; use futures::Poll; diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c0ef89fa..55cfd47d 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,14 +18,16 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.8.0-alpha.1" -actix-web = "1.0.0-alpha.1" -actix-http = "0.1.0-alpha.1" +actix = "0.8.0-alpha.2" +#actix-web = "1.0.0-alpha.1" +#actix-http = "0.1.0-alpha.1" +actix-web = { path=".." } +actix-http = { path="../actix-http" } actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" [dev-dependencies] env_logger = "0.6" -actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } +#actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } +actix-http-test = { path = "../test-server", features=["ssl"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 1f04cfd5..c3d88883 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -16,6 +16,9 @@ quote = "0.6" syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] -actix-web = { version = "1.0.0-alpha.1" } -actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } +#actix-web = { version = "1.0.0-alpha.1" } +#actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } +#actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } +actix-web = { path = ".." } +actix-http = { path = "../actix-http", features=["ssl"] } +actix-http-test = { path = "../test-server", features=["ssl"] } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index cd94057f..148bf97d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -18,17 +18,14 @@ name = "awc" path = "src/lib.rs" [package.metadata.docs.rs] -features = ["ssl", "brotli", "flate2-zlib", "cookies"] +features = ["ssl", "brotli", "flate2-zlib"] [features] -default = ["cookies", "brotli", "flate2-zlib"] +default = ["brotli", "flate2-zlib"] # openssl ssl = ["openssl", "actix-http/ssl"] -# cookies integration -cookies = ["cookie", "actix-http/cookies"] - # brotli encoding, requires c compiler brotli = ["actix-http/brotli"] @@ -53,8 +50,6 @@ serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.5.3" tokio-timer = "0.2.8" - -cookie = { version="0.11", features=["percent-encode"], optional = true } openssl = { version="0.10", optional = true } [dev-dependencies] diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 92f749d0..ff1fb3fe 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -24,7 +24,7 @@ use std::cell::RefCell; use std::rc::Rc; use std::time::Duration; -pub use actix_http::{client::Connector, http}; +pub use actix_http::{client::Connector, cookie, http}; use actix_http::http::{HeaderMap, HttpTryFrom, Method, Uri}; use actix_http::RequestHead; diff --git a/awc/src/request.rs b/awc/src/request.rs index bdde6faf..a462479e 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,18 +1,19 @@ use std::fmt; +use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; use std::time::Duration; use bytes::{BufMut, Bytes, BytesMut}; -#[cfg(feature = "cookies")] -use cookie::{Cookie, CookieJar}; use futures::future::{err, Either}; use futures::{Future, Stream}; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use serde::Serialize; use serde_json; use tokio_timer::Timeout; use actix_http::body::{Body, BodyStream}; +use actix_http::cookie::{Cookie, CookieJar}; use actix_http::encoding::Decoder; use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; use actix_http::http::{ @@ -59,7 +60,6 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; pub struct ClientRequest { pub(crate) head: RequestHead, err: Option, - #[cfg(feature = "cookies")] cookies: Option, default_headers: bool, response_decompress: bool, @@ -77,7 +77,6 @@ impl ClientRequest { config, head: RequestHead::default(), err: None, - #[cfg(feature = "cookies")] cookies: None, timeout: None, default_headers: true, @@ -268,7 +267,6 @@ impl ClientRequest { self.header(header::AUTHORIZATION, format!("Bearer {}", token)) } - #[cfg(feature = "cookies")] /// Set a cookie /// /// ```rust @@ -437,28 +435,20 @@ impl ClientRequest { } }; - #[allow(unused_mut)] let mut head = slf.head; - #[cfg(feature = "cookies")] - { - use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; - use std::fmt::Write; - - // set cookies - if let Some(ref mut jar) = slf.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = - percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); + // set cookies + if let Some(ref mut jar) = slf.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); } let config = slf.config; diff --git a/awc/src/response.rs b/awc/src/response.rs index 3b77eaa6..038a9a33 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -9,10 +9,8 @@ use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE}; use actix_http::http::{HeaderMap, StatusCode, Version}; use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; -#[cfg(feature = "cookies")] +use actix_http::cookie::Cookie; use actix_http::error::CookieParseError; -#[cfg(feature = "cookies")] -use cookie::Cookie; /// Client Response pub struct ClientResponse { @@ -41,7 +39,6 @@ impl HttpMessage for ClientResponse { /// Load request cookies. #[inline] - #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { struct Cookies(Vec>); diff --git a/awc/src/test.rs b/awc/src/test.rs index 7723b9d2..5e595d15 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,10 +1,12 @@ //! Test helpers for actix http client to use during testing. -use actix_http::http::header::{Header, IntoHeaderValue}; +use std::fmt::Write as FmtWrite; + +use actix_http::cookie::{Cookie, CookieJar}; +use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; use actix_http::http::{HeaderName, HttpTryFrom, Version}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; -#[cfg(feature = "cookies")] -use cookie::{Cookie, CookieJar}; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::ClientResponse; @@ -30,7 +32,6 @@ where /// Test `ClientResponse` builder pub struct TestResponse { head: ResponseHead, - #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -39,7 +40,6 @@ impl Default for TestResponse { fn default() -> TestResponse { TestResponse { head: ResponseHead::default(), - #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, } @@ -87,7 +87,6 @@ impl TestResponse { } /// Set cookie for this response - #[cfg(feature = "cookies")] pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { self.cookies.add(cookie.into_owned()); self @@ -105,25 +104,17 @@ impl TestResponse { pub fn finish(self) -> ClientResponse { let mut head = self.head; - #[cfg(feature = "cookies")] - { - use std::fmt::Write as FmtWrite; - - use actix_http::http::header::{self, HeaderValue}; - use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; - - let mut cookie = String::new(); - for c in self.cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - if !cookie.is_empty() { - head.headers.insert( - header::SET_COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } + let mut cookie = String::new(); + for c in self.cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + if !cookie.is_empty() { + head.headers.insert( + header::SET_COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); } if let Some(pl) = self.payload { diff --git a/awc/src/ws.rs b/awc/src/ws.rs index bc023c06..bbeaa061 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -1,14 +1,15 @@ //! Websockets client +use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; use std::{fmt, str}; use actix_codec::Framed; +use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use bytes::{BufMut, BytesMut}; -#[cfg(feature = "cookies")] -use cookie::{Cookie, CookieJar}; use futures::future::{err, Either, Future}; +use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use tokio_timer::Timeout; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; @@ -33,7 +34,6 @@ pub struct WebsocketsRequest { max_size: usize, server_mode: bool, default_headers: bool, - #[cfg(feature = "cookies")] cookies: Option, config: Rc, } @@ -62,7 +62,6 @@ impl WebsocketsRequest { protocols: None, max_size: 65_536, server_mode: false, - #[cfg(feature = "cookies")] cookies: None, default_headers: true, } @@ -82,7 +81,6 @@ impl WebsocketsRequest { self } - #[cfg(feature = "cookies")] /// Set a cookie pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { if self.cookies.is_none() { @@ -264,28 +262,20 @@ impl WebsocketsRequest { self }; - #[allow(unused_mut)] let mut head = slf.head; - #[cfg(feature = "cookies")] - { - use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; - use std::fmt::Write; - - // set cookies - if let Some(ref mut jar) = slf.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = - percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); + // set cookies + if let Some(ref mut jar) = slf.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); } // origin diff --git a/src/lib.rs b/src/lib.rs index 7a4f4bfb..2bef6a52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,7 +108,7 @@ pub use actix_web_codegen::*; // re-export for convenience pub use actix_http::Response as HttpResponse; -pub use actix_http::{http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{cookie, http, Error, HttpMessage, ResponseError, Result}; pub use crate::app::App; pub use crate::extract::FromRequest; diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index e94f99db..34979e16 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -51,11 +51,11 @@ use std::cell::RefCell; use std::rc::Rc; use actix_service::{Service, Transform}; -use cookie::{Cookie, CookieJar, Key, SameSite}; use futures::future::{ok, Either, FutureResult}; use futures::{Future, IntoFuture, Poll}; use time::Duration; +use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::error::{Error, Result}; use crate::http::header::{self, HeaderValue}; use crate::request::HttpRequest; diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 037d0006..6b6253fb 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -18,5 +18,5 @@ mod logger; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; -#[cfg(feature = "cookies")] +#[cfg(feature = "secure-cookies")] pub mod identity; diff --git a/src/request.rs b/src/request.rs index 33b63acd..c524d497 100644 --- a/src/request.rs +++ b/src/request.rs @@ -263,14 +263,12 @@ mod tests { } #[test] - #[cfg(feature = "cookies")] fn test_no_request_cookies() { let req = TestRequest::default().to_http_request(); assert!(req.cookies().unwrap().is_empty()); } #[test] - #[cfg(feature = "cookies")] fn test_request_cookies() { let req = TestRequest::default() .header(header::COOKIE, "cookie1=value1") diff --git a/src/test.rs b/src/test.rs index 4fdb6ea3..a9aa2278 100644 --- a/src/test.rs +++ b/src/test.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; use std::rc::Rc; +use actix_http::cookie::Cookie; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, StatusCode, Version}; use actix_http::test::TestRequest as HttpTestRequest; @@ -11,8 +12,6 @@ use actix_rt::Runtime; use actix_server_config::ServerConfig; use actix_service::{FnService, IntoNewService, NewService, Service}; use bytes::Bytes; -#[cfg(feature = "cookies")] -use cookie::Cookie; use futures::future::{lazy, Future}; use crate::config::{AppConfig, AppConfigInner}; @@ -285,7 +284,6 @@ impl TestRequest { self } - #[cfg(feature = "cookies")] /// Set cookie for this request pub fn cookie(mut self, cookie: Cookie) -> Self { self.req.cookie(cookie); diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index a9b58483..97947230 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -17,17 +17,14 @@ edition = "2018" workspace = ".." [package.metadata.docs.rs] -features = ["session"] +features = [] [lib] name = "actix_http_test" path = "src/lib.rs" [features] -default = ["session"] - -# sessions feature, session require "ring" crate and c compiler -session = ["cookie/secure"] +default = [] # openssl ssl = ["openssl", "actix-server/ssl", "awc/ssl"] @@ -42,7 +39,6 @@ awc = { path = "../awc" } base64 = "0.10" bytes = "0.4" -cookie = { version="0.11", features=["percent-encode"] } futures = "0.1" http = "0.1.8" log = "0.4" @@ -60,4 +56,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-alpha.1" +actix-web = { path = ".." } +actix-http = { path = "../actix-http" } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 9eec065c..75f75b1e 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -23,7 +23,7 @@ use net2::TcpBuilder; /// use actix_http::HttpService; /// use actix_http_test::TestServer; /// use actix_web::{web, App, HttpResponse}; -/// # +/// /// fn my_handler() -> HttpResponse { /// HttpResponse::Ok().into() /// } From a20b9fd354f530ac689292bd77ac2f410b939707 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 22:06:14 -0700 Subject: [PATCH 1174/1635] prepare aplha2 release --- CHANGES.md | 6 ++++++ Cargo.toml | 10 +++++----- actix-files/Cargo.toml | 11 ++++------- actix-http/Cargo.toml | 4 +--- actix-http/src/response.rs | 35 +++++++++++++++-------------------- actix-session/CHANGES.md | 6 ++++++ actix-session/Cargo.toml | 7 +++---- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 11 ++++------- actix-web-codegen/Cargo.toml | 9 +++------ awc/Cargo.toml | 10 +++++----- test-server/CHANGES.md | 7 +++++++ test-server/Cargo.toml | 9 ++------- 13 files changed, 65 insertions(+), 64 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 47b0f987..e1863591 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,8 +2,14 @@ ## [1.0.0-alpha.2] - 2019-03-29 +### Added + +* rustls support + ### Changed +* use forked cookie + * multipart::Field renamed to MultipartField ## [1.0.0-alpha.1] - 2019-03-28 diff --git a/Cargo.toml b/Cargo.toml index 1bd089f0..f7f28509 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -71,11 +71,11 @@ actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" -actix-http = { path = "actix-http", features=["fail"] } +actix-http = { version = "0.1.0-alpha.2", features=["fail"] } actix-server = "0.4.1" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" -awc = { path = "awc", optional = true } +awc = { version = "0.1.0-alpha.2", optional = true } bytes = "0.4" derive_more = "0.14" @@ -100,8 +100,8 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { path = "actix-http", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { path = "test-server", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.2", features=["ssl", "brotli", "flate2-zlib"] } +actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 058a1998..3f1bad69 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,10 +18,8 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -#actix-web = "1.0.0-alpha.1" -actix-web = { path = ".." } -actix-service = "0.3.3" - +actix-web = "1.0.0-alpha.2" +actix-service = "0.3.4" bitflags = "1" bytes = "0.4" futures = "0.1.25" @@ -33,5 +31,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -#actix-web = { version = "1.0.0-alpha.1", features=["ssl"] } -actix-web = { path = "..", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.2", features=["ssl"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e9bb5d9b..2de624b7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -100,9 +100,7 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.2" actix-server = { version = "0.4.1", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } -#actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } -actix-http-test = { path = "../test-server", features=["ssl"] } - +actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index a6e8f613..88eb7dcc 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -333,11 +333,10 @@ impl ResponseBuilder { /// Set a header. /// - /// ```rust,ignore - /// # extern crate actix_web; - /// use actix_web::{http, Request, Response, Result}; + /// ```rust + /// use actix_http::{http, Request, Response, Result}; /// - /// fn index(req: HttpRequest) -> Result { + /// fn index(req: Request) -> Result { /// Ok(Response::Ok() /// .set(http::header::IfModifiedSince( /// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?, @@ -361,11 +360,10 @@ impl ResponseBuilder { /// Append a header to existing headers. /// - /// ```rust,ignore - /// # extern crate actix_web; - /// use actix_web::{http, Request, Response}; + /// ```rust + /// use actix_http::{http, Request, Response}; /// - /// fn index(req: HttpRequest) -> Response { + /// fn index(req: Request) -> Response { /// Response::Ok() /// .header("X-TEST", "value") /// .header(http::header::CONTENT_TYPE, "application/json") @@ -394,11 +392,10 @@ impl ResponseBuilder { /// Set a header. /// - /// ```rust,ignore - /// # extern crate actix_web; - /// use actix_web::{http, Request, Response}; + /// ```rust + /// use actix_http::{http, Request, Response}; /// - /// fn index(req: HttpRequest) -> Response { + /// fn index(req: Request) -> Response { /// Response::Ok() /// .set_header("X-TEST", "value") /// .set_header(http::header::CONTENT_TYPE, "application/json") @@ -500,11 +497,10 @@ impl ResponseBuilder { /// Set a cookie /// - /// ```rust,ignore - /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, Response, Result}; + /// ```rust + /// use actix_http::{http, Request, Response}; /// - /// fn index(req: HttpRequest) -> Response { + /// fn index(req: Request) -> Response { /// Response::Ok() /// .cookie( /// http::Cookie::build("name", "value") @@ -530,11 +526,10 @@ impl ResponseBuilder { /// Remove cookie /// - /// ```rust,ignore - /// # extern crate actix_web; - /// use actix_web::{http, HttpRequest, Response, Result}; + /// ```rust + /// use actix_http::{http, Request, Response, HttpMessage}; /// - /// fn index(req: &HttpRequest) -> Response { + /// fn index(req: Request) -> Response { /// let mut builder = Response::Ok(); /// /// if let Some(ref cookie) = req.cookie("name") { diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 95ec1c35..3cd15609 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.1.0-alpha.2] - 2019-03-29 + +* Update actix-web + +* Use new feature name for secure cookies + ## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 6569286c..e39dc714 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,9 +24,8 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -#actix-web = "1.0.0-alpha.1" -actix-web = { path = ".." } -actix-service = "0.3.3" +actix-web = "1.0.0-alpha.2" +actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" futures = "0.1.25" diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 95ec1c35..9b142798 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-alpha.2] - 2019-03-29 + +* Update actix-http and actix-web + ## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 55cfd47d..759d6fc3 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -19,15 +19,12 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0-alpha.2" -#actix-web = "1.0.0-alpha.1" -#actix-http = "0.1.0-alpha.1" -actix-web = { path=".." } -actix-http = { path="../actix-http" } +actix-web = "1.0.0-alpha.2" +actix-http = "0.1.0-alpha.2" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" [dev-dependencies] env_logger = "0.6" -#actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } -actix-http-test = { path = "../test-server", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index c3d88883..da264076 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -16,9 +16,6 @@ quote = "0.6" syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] -#actix-web = { version = "1.0.0-alpha.1" } -#actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } -#actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } -actix-web = { path = ".." } -actix-http = { path = "../actix-http", features=["ssl"] } -actix-http-test = { path = "../test-server", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.2" } +actix-http = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } \ No newline at end of file diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 148bf97d..c2cc9e7f 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.1" actix-service = "0.3.4" -actix-http = { path = "../actix-http" } +actix-http = "0.1.0-alpa.2" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -54,11 +54,11 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { path = "..", features=["ssl"] } -actix-http = { path = "../actix-http", features=["ssl"] } -actix-http-test = { path = "../test-server", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.2", features=["ssl"] } +actix-http = { version = "0.1.0-alpa.2", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } actix-utils = "0.3.4" -actix-server = { version = "0.4.0", features=["ssl"] } +actix-server = { version = "0.4.1", features=["ssl"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 95ec1c35..cac5a2af 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.0-alpha.2] - 2019-03-29 + +* Added TestServerRuntime::load_body() method + +* Update actix-http and awc libraries + + ## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 97947230..838f2d8d 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.1" actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" -awc = { path = "../awc" } +awc = "0.1.0-alpha.2" base64 = "0.10" bytes = "0.4" @@ -52,9 +52,4 @@ serde_urlencoded = "0.5.3" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" - openssl = { version="0.10", optional = true } - -[dev-dependencies] -actix-web = { path = ".." } -actix-http = { path = "../actix-http" } From 2e159d1eb9b55a0bf3755e5af12d4e03548eb34b Mon Sep 17 00:00:00 2001 From: Douman Date: Sat, 30 Mar 2019 17:53:45 +0300 Subject: [PATCH 1175/1635] test-server: Request functions should accept path (#743) --- test-server/src/lib.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 75f75b1e..b64ff433 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -159,33 +159,33 @@ impl TestServerRuntime { } /// Create `GET` request - pub fn get(&self) -> ClientRequest { - self.client.get(self.url("/").as_str()) + pub fn get>(&self, path: S) -> ClientRequest { + self.client.get(self.url(path.as_ref()).as_str()) } /// Create https `GET` request - pub fn sget(&self) -> ClientRequest { - self.client.get(self.surl("/").as_str()) + pub fn sget>(&self, path: S) -> ClientRequest { + self.client.get(self.surl(path.as_ref()).as_str()) } /// Create `POST` request - pub fn post(&self) -> ClientRequest { - self.client.post(self.url("/").as_str()) + pub fn post>(&self, path: S) -> ClientRequest { + self.client.post(self.url(path.as_ref()).as_str()) } /// Create https `POST` request - pub fn spost(&self) -> ClientRequest { - self.client.post(self.surl("/").as_str()) + pub fn spost>(&self, path: S) -> ClientRequest { + self.client.post(self.surl(path.as_ref()).as_str()) } /// Create `HEAD` request - pub fn head(&self) -> ClientRequest { - self.client.head(self.url("/").as_str()) + pub fn head>(&self, path: S) -> ClientRequest { + self.client.head(self.url(path.as_ref()).as_str()) } /// Create https `HEAD` request - pub fn shead(&self) -> ClientRequest { - self.client.head(self.surl("/").as_str()) + pub fn shead>(&self, path: S) -> ClientRequest { + self.client.head(self.surl(path.as_ref()).as_str()) } /// Connect to test http server From 724e9c2efb62e52aaff716217d7e2bb09564d1b5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 30 Mar 2019 07:56:09 -0700 Subject: [PATCH 1176/1635] replace deprecated fn --- .travis.yml | 2 +- src/app.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f61201f..c3698625 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --all-features --no-deps && + cargo doc --all-features && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/src/app.rs b/src/app.rs index a6dfdd55..9cdfc436 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,7 +8,7 @@ use actix_http::encoding::{Decoder, Encoder}; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ - ApplyTransform, IntoNewService, IntoTransform, NewService, Transform, + apply_transform, IntoNewService, IntoTransform, NewService, Transform, }; #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] use bytes::Bytes; @@ -138,7 +138,7 @@ where F: IntoTransform>, { let fref = Rc::new(RefCell::new(None)); - let endpoint = ApplyTransform::new(mw, AppEntry::new(fref.clone())); + let endpoint = apply_transform(mw, AppEntry::new(fref.clone())); AppRouter { endpoint, chain: self.chain, @@ -426,7 +426,7 @@ where B1: MessageBody, F: IntoTransform, { - let endpoint = ApplyTransform::new(mw, self.endpoint); + let endpoint = apply_transform(mw, self.endpoint); AppRouter { endpoint, chain: self.chain, From 457b75c9950657b1c402570da4b2538f5a4e0141 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 30 Mar 2019 10:04:38 -0700 Subject: [PATCH 1177/1635] update api docs; move web to submodule --- CHANGES.md | 5 + Cargo.toml | 2 +- src/lib.rs | 213 +-------------------------------------- src/test.rs | 2 + src/web.rs | 285 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 298 insertions(+), 209 deletions(-) create mode 100644 src/web.rs diff --git a/CHANGES.md b/CHANGES.md index e1863591..e8389910 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +### Removed + +* Removed unused `actix_web::web::md()` + + ## [1.0.0-alpha.2] - 2019-03-29 ### Added diff --git a/Cargo.toml b/Cargo.toml index f7f28509..fb53008e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 2bef6a52..cb29fa5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,9 @@ //! represents an HTTP server instance and is used to instantiate and //! configure servers. //! +//! * [web](web/index.html): This module +//! provide essentials helper functions and types for application registration. +//! //! * [HttpRequest](struct.HttpRequest.html) and //! [HttpResponse](struct.HttpResponse.html): These structs //! represent HTTP requests and responses and expose various methods @@ -67,7 +70,7 @@ //! * `tls` - enables ssl support via `native-tls` crate //! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` //! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` -//! * `cookies` - enables cookies support, includes `ring` crate as +//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as //! dependency //! * `brotli` - enables `brotli` compression support, requires `c` //! compiler @@ -98,6 +101,7 @@ mod server; mod service; pub mod test; mod types; +pub mod web; #[allow(unused_imports)] #[macro_use] @@ -159,213 +163,6 @@ pub mod dev { } } -pub mod web { - //! Various types - use actix_http::{http::Method, Response}; - use actix_service::{fn_transform, Service, Transform}; - use futures::{Future, IntoFuture}; - - pub use actix_http::Response as HttpResponse; - pub use bytes::{Bytes, BytesMut}; - - use crate::error::{BlockingError, Error}; - use crate::extract::FromRequest; - use crate::handler::{AsyncFactory, Factory}; - use crate::resource::Resource; - use crate::responder::Responder; - use crate::route::Route; - use crate::scope::Scope; - use crate::service::{ServiceRequest, ServiceResponse}; - - pub use crate::data::{Data, RouteData}; - pub use crate::request::HttpRequest; - pub use crate::types::*; - - /// Create resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/users/{userid}/{friend}") - /// .route(web::get().to(|| HttpResponse::Ok())) - /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } - /// ``` - pub fn resource(path: &str) -> Resource

    { - Resource::new(path) - } - - /// Configure scope for common root path. - /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/{project_id}") - /// .service(web::resource("/path1").to(|| HttpResponse::Ok())) - /// .service(web::resource("/path2").to(|| HttpResponse::Ok())) - /// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(path: &str) -> Scope

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

    { - Route::new() - } - - /// Create *route* with `GET` method guard. - pub fn get() -> Route

    { - Route::new().method(Method::GET) - } - - /// Create *route* with `POST` method guard. - pub fn post() -> Route

    { - Route::new().method(Method::POST) - } - - /// Create *route* with `PUT` method guard. - pub fn put() -> Route

    { - Route::new().method(Method::PUT) - } - - /// Create *route* with `PATCH` method guard. - pub fn patch() -> Route

    { - Route::new().method(Method::PATCH) - } - - /// Create *route* with `DELETE` method guard. - pub fn delete() -> Route

    { - Route::new().method(Method::DELETE) - } - - /// Create *route* with `HEAD` method guard. - pub fn head() -> Route

    { - Route::new().method(Method::HEAD) - } - - /// Create *route* and add method guard. - pub fn method(method: Method) -> Route

    { - Route::new().method(method) - } - - /// Create a new route and add handler. - /// - /// ```rust - /// use actix_web::{web, App, HttpResponse}; - /// - /// fn index() -> HttpResponse { - /// unimplemented!() - /// } - /// - /// App::new().service( - /// web::resource("/").route( - /// web::to(index)) - /// ); - /// ``` - pub fn to(handler: F) -> Route

    - where - F: Factory + 'static, - I: FromRequest

    + 'static, - R: Responder + 'static, - { - Route::new().to(handler) - } - - /// Create a new route and add async handler. - /// - /// ```rust - /// use actix_web::{web, App, HttpResponse, Error}; - /// - /// fn index() -> impl futures::Future { - /// futures::future::ok(HttpResponse::Ok().finish()) - /// } - /// - /// App::new().service(web::resource("/").route( - /// web::to_async(index)) - /// ); - /// ``` - pub fn to_async(handler: F) -> Route

    - where - F: AsyncFactory, - I: FromRequest

    + 'static, - R: IntoFuture + 'static, - R::Item: Into, - R::Error: Into, - { - Route::new().to_async(handler) - } - - /// Execute blocking function on a thread pool, returns future that resolves - /// to result of the function execution. - pub fn block(f: F) -> impl Future> - where - F: FnOnce() -> Result + Send + 'static, - I: Send + 'static, - E: Send + std::fmt::Debug + 'static, - { - actix_threadpool::run(f).from_err() - } - - /// Create middleare - pub fn md( - f: F, - ) -> impl Transform< - S, - Request = ServiceRequest

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

    , - Response = ServiceResponse, - Error = Error, - >, - F: FnMut(ServiceRequest

    , &mut S) -> R + Clone, - R: IntoFuture, Error = Error>, - { - fn_transform(f) - } -} - #[cfg(feature = "client")] pub mod client { //! An HTTP Client diff --git a/src/test.rs b/src/test.rs index a9aa2278..f18fc2b3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -56,6 +56,7 @@ where .unwrap() } +/// Create service that always responds with `HttpResponse::Ok()` pub fn ok_service() -> impl Service< Request = ServiceRequest, Response = ServiceResponse, @@ -64,6 +65,7 @@ pub fn ok_service() -> impl Service< default_service(StatusCode::OK) } +/// Create service that responds with response with specified status code pub fn default_service( status_code: StatusCode, ) -> impl Service< diff --git a/src/web.rs b/src/web.rs new file mode 100644 index 00000000..65b3cfc7 --- /dev/null +++ b/src/web.rs @@ -0,0 +1,285 @@ +//! Essentials helper functions and types for application registration. +use actix_http::{http::Method, Response}; +use futures::{Future, IntoFuture}; + +pub use actix_http::Response as HttpResponse; +pub use bytes::{Bytes, BytesMut}; + +use crate::error::{BlockingError, Error}; +use crate::extract::FromRequest; +use crate::handler::{AsyncFactory, Factory}; +use crate::resource::Resource; +use crate::responder::Responder; +use crate::route::Route; +use crate::scope::Scope; + +pub use crate::data::{Data, RouteData}; +pub use crate::request::HttpRequest; +pub use crate::types::*; + +/// Create resource for a specific path. +/// +/// Resources may have variable path segments. For example, a +/// resource with the path `/a/{name}/c` would match all incoming +/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. +/// +/// A variable segment is specified in the form `{identifier}`, +/// where the identifier can be used later in a request handler to +/// access the matched value for that segment. This is done by +/// looking up the identifier in the `Params` object returned by +/// `HttpRequest.match_info()` method. +/// +/// By default, each segment matches the regular expression `[^{}/]+`. +/// +/// You can also specify a custom regex in the form `{identifier:regex}`: +/// +/// For instance, to route `GET`-requests on any route matching +/// `/users/{userid}/{friend}` and store `userid` and `friend` in +/// the exposed `Params` object: +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/users/{userid}/{friend}") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// } +/// ``` +pub fn resource(path: &str) -> Resource

    { + Resource::new(path) +} + +/// Configure scope for common root path. +/// +/// Scopes collect multiple paths under a common path prefix. +/// Scope path can contain variable path segments as resources. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::scope("/{project_id}") +/// .service(web::resource("/path1").to(|| HttpResponse::Ok())) +/// .service(web::resource("/path2").to(|| HttpResponse::Ok())) +/// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// } +/// ``` +/// +/// In the above example, three routes get added: +/// * /{project_id}/path1 +/// * /{project_id}/path2 +/// * /{project_id}/path3 +/// +pub fn scope(path: &str) -> Scope

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

    { + Route::new() +} + +/// Create *route* with `GET` method guard. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{project_id}") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// ); +/// } +/// ``` +/// +/// In the above example, one `GET` route get added: +/// * /{project_id} +/// +pub fn get() -> Route

    { + Route::new().method(Method::GET) +} + +/// Create *route* with `POST` method guard. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{project_id}") +/// .route(web::post().to(|| HttpResponse::Ok())) +/// ); +/// } +/// ``` +/// +/// In the above example, one `POST` route get added: +/// * /{project_id} +/// +pub fn post() -> Route

    { + Route::new().method(Method::POST) +} + +/// Create *route* with `PUT` method guard. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{project_id}") +/// .route(web::put().to(|| HttpResponse::Ok())) +/// ); +/// } +/// ``` +/// +/// In the above example, one `PUT` route get added: +/// * /{project_id} +/// +pub fn put() -> Route

    { + Route::new().method(Method::PUT) +} + +/// Create *route* with `PATCH` method guard. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{project_id}") +/// .route(web::patch().to(|| HttpResponse::Ok())) +/// ); +/// } +/// ``` +/// +/// In the above example, one `PATCH` route get added: +/// * /{project_id} +/// +pub fn patch() -> Route

    { + Route::new().method(Method::PATCH) +} + +/// Create *route* with `DELETE` method guard. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{project_id}") +/// .route(web::delete().to(|| HttpResponse::Ok())) +/// ); +/// } +/// ``` +/// +/// In the above example, one `DELETE` route get added: +/// * /{project_id} +/// +pub fn delete() -> Route

    { + Route::new().method(Method::DELETE) +} + +/// Create *route* with `HEAD` method guard. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{project_id}") +/// .route(web::head().to(|| HttpResponse::Ok())) +/// ); +/// } +/// ``` +/// +/// In the above example, one `HEAD` route get added: +/// * /{project_id} +/// +pub fn head() -> Route

    { + Route::new().method(Method::HEAD) +} + +/// Create *route* and add method guard. +/// +/// ```rust +/// use actix_web::{web, http, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{project_id}") +/// .route(web::method(http::Method::GET).to(|| HttpResponse::Ok())) +/// ); +/// } +/// ``` +/// +/// In the above example, one `GET` route get added: +/// * /{project_id} +/// +pub fn method(method: Method) -> Route

    { + Route::new().method(method) +} + +/// Create a new route and add handler. +/// +/// ```rust +/// use actix_web::{web, App, HttpResponse}; +/// +/// fn index() -> HttpResponse { +/// unimplemented!() +/// } +/// +/// App::new().service( +/// web::resource("/").route( +/// web::to(index)) +/// ); +/// ``` +pub fn to(handler: F) -> Route

    +where + F: Factory + 'static, + I: FromRequest

    + 'static, + R: Responder + 'static, +{ + Route::new().to(handler) +} + +/// Create a new route and add async handler. +/// +/// ```rust +/// # use futures::future::{ok, Future}; +/// use actix_web::{web, App, HttpResponse, Error}; +/// +/// fn index() -> impl Future { +/// ok(HttpResponse::Ok().finish()) +/// } +/// +/// App::new().service(web::resource("/").route( +/// web::to_async(index)) +/// ); +/// ``` +pub fn to_async(handler: F) -> Route

    +where + F: AsyncFactory, + I: FromRequest

    + 'static, + R: IntoFuture + 'static, + R::Item: Into, + R::Error: Into, +{ + Route::new().to_async(handler) +} + +/// Execute blocking function on a thread pool, returns future that resolves +/// to result of the function execution. +pub fn block(f: F) -> impl Future> +where + F: FnOnce() -> Result + Send + 'static, + I: Send + 'static, + E: Send + std::fmt::Debug + 'static, +{ + actix_threadpool::run(f).from_err() +} From 6fcbe4bcdabbc8623bbf8a2676e04f8ef9af292b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 30 Mar 2019 11:33:31 -0700 Subject: [PATCH 1178/1635] add fn_guard --- src/guard.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/guard.rs b/src/guard.rs index f9565d0f..4dcd7ba8 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -39,15 +39,35 @@ pub trait Guard { fn check(&self, request: &RequestHead) -> bool; } -#[doc(hidden)] -pub struct FnGuard bool + 'static>(F); - -impl Guard for F +/// Return guard that matches if all of the supplied guards. +/// +/// ```rust +/// use actix_web::{guard, web, App, HttpResponse}; +/// +/// fn main() { +/// App::new().service(web::resource("/index.html").route( +/// web::route() +/// .guard( +/// guard::fn_guard(|req| req.headers().contains_key("content-type"))) +/// .to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// } +/// ``` +pub fn fn_guard(f: F) -> impl Guard where - F: Fn(&RequestHead) -> bool + 'static, + F: Fn(&RequestHead) -> bool, +{ + FnGuard(f) +} + +struct FnGuard bool>(F); + +impl Guard for FnGuard +where + F: Fn(&RequestHead) -> bool, { fn check(&self, head: &RequestHead) -> bool { - (*self)(head) + (self.0)(head) } } @@ -93,7 +113,6 @@ impl Guard for AnyGuard { /// Return guard that matches if all of the supplied guards. /// /// ```rust -/// # extern crate actix_web; /// use actix_web::{guard, web, App, HttpResponse}; /// /// fn main() { From 351df84cca19bfc0ac4c8d44bc9749d5d23f3607 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 30 Mar 2019 11:37:56 -0700 Subject: [PATCH 1179/1635] update stable release api doc link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9829ab86..35ea0bf0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [User Guide](https://actix.rs/docs/) * [API Documentation (Development)](https://actix.rs/actix-web/actix_web/) -* [API Documentation (Releases)](https://docs.rs/actix-web/0.7.18/actix_web/) +* [API Documentation (0.7 Release)](https://docs.rs/actix-web/0.7.19/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) * Minimum supported Rust version: 1.32 or later From 1a871d708e70aabcad9a1f97a48642b41fca8732 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 30 Mar 2019 12:13:21 -0700 Subject: [PATCH 1180/1635] update guard doc test --- Cargo.toml | 2 +- src/guard.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fb53008e..3abe3129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ actix-router = "0.1.0" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" actix-http = { version = "0.1.0-alpha.2", features=["fail"] } -actix-server = "0.4.1" +actix-server = "0.4.2" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" awc = { version = "0.1.0-alpha.2", optional = true } diff --git a/src/guard.rs b/src/guard.rs index 4dcd7ba8..fa9088e2 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -19,7 +19,7 @@ //! App::new().service(web::resource("/index.html").route( //! web::route() //! .guard(guard::Post()) -//! .guard(|head: &dev::RequestHead| head.method == http::Method::GET) +//! .guard(guard::fn_guard(|head| head.method == http::Method::GET)) //! .to(|| HttpResponse::MethodNotAllowed())) //! ); //! } From 7596d0b7cbf4558355b7fe79a2a8e5b9d9015f34 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 30 Mar 2019 20:48:00 -0700 Subject: [PATCH 1181/1635] fix fn_guard doc string --- src/guard.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/guard.rs b/src/guard.rs index fa9088e2..44e4891e 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -39,7 +39,7 @@ pub trait Guard { fn check(&self, request: &RequestHead) -> bool; } -/// Return guard that matches if all of the supplied guards. +/// Create guard object for supplied function. /// /// ```rust /// use actix_web::{guard, web, App, HttpResponse}; @@ -48,7 +48,9 @@ pub trait Guard { /// App::new().service(web::resource("/index.html").route( /// web::route() /// .guard( -/// guard::fn_guard(|req| req.headers().contains_key("content-type"))) +/// guard::fn_guard( +/// |req| req.headers() +/// .contains_key("content-type"))) /// .to(|| HttpResponse::MethodNotAllowed())) /// ); /// } From ddf5089bffec1f4729560891e0daf35f010fce8c Mon Sep 17 00:00:00 2001 From: Llaurence <40535137+Llaurence@users.noreply.github.com> Date: Sun, 31 Mar 2019 13:26:56 +0000 Subject: [PATCH 1182/1635] Warn when an unsealed private cookie isn't valid UTF-8 (#746) --- actix-http/src/cookie/secure/private.rs | 98 +++++++++++++++++-------- 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/actix-http/src/cookie/secure/private.rs b/actix-http/src/cookie/secure/private.rs index 8b56991f..74352d72 100644 --- a/actix-http/src/cookie/secure/private.rs +++ b/actix-http/src/cookie/secure/private.rs @@ -1,3 +1,6 @@ +use std::str; + +use log::warn; use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM}; use ring::aead::{OpeningKey, SealingKey}; use ring::rand::{SecureRandom, SystemRandom}; @@ -57,9 +60,14 @@ impl<'a> PrivateJar<'a> { let unsealed = open_in_place(&key, nonce, ad, 0, sealed) .map_err(|_| "invalid key/nonce/value: bad seal")?; - ::std::str::from_utf8(unsealed) - .map(|s| s.to_string()) - .map_err(|_| "bad unsealed utf8") + if let Ok(unsealed_utf8) = str::from_utf8(unsealed) { + Ok(unsealed_utf8.to_string()) + } else { + warn!("Private cookie does not have utf8 content! +It is likely the secret key used to encrypt them has been leaked. +Please change it as soon as possible."); + Err("bad unsealed utf8") + } } /// Returns a reference to the `Cookie` inside this jar with the name `name` @@ -147,34 +155,12 @@ impl<'a> PrivateJar<'a> { /// Encrypts the cookie's value with /// authenticated encryption assuring confidentiality, integrity, and authenticity. fn encrypt_cookie(&self, cookie: &mut Cookie) { - let mut data; - let output_len = { - // Create the `SealingKey` structure. - let key = SealingKey::new(ALGO, &self.key).expect("sealing key creation"); - - // Create a vec to hold the [nonce | cookie value | overhead]. - let overhead = ALGO.tag_len(); - let cookie_val = cookie.value().as_bytes(); - data = vec![0; NONCE_LEN + cookie_val.len() + overhead]; - - // Randomly generate the nonce, then copy the cookie value as input. - let (nonce, in_out) = data.split_at_mut(NONCE_LEN); - SystemRandom::new() - .fill(nonce) - .expect("couldn't random fill nonce"); - in_out[..cookie_val.len()].copy_from_slice(cookie_val); - let nonce = Nonce::try_assume_unique_for_key(nonce) - .expect("invalid length of `nonce`"); - - // Use cookie's name as associated data to prevent value swapping. - let ad = Aad::from(cookie.name().as_bytes()); - - // Perform the actual sealing operation and get the output length. - seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal") - }; + let name = cookie.name().as_bytes(); + let value = cookie.value().as_bytes(); + let data = encrypt_name_value(name, value, &self.key); // Base64 encode the nonce and encrypted value. - let sealed_value = base64::encode(&data[..(NONCE_LEN + output_len)]); + let sealed_value = base64::encode(&data); cookie.set_value(sealed_value); } @@ -206,9 +192,38 @@ impl<'a> PrivateJar<'a> { } } +fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec { + // Create the `SealingKey` structure. + let key = SealingKey::new(ALGO, key).expect("sealing key creation"); + + // Create a vec to hold the [nonce | cookie value | overhead]. + let overhead = ALGO.tag_len(); + let mut data = vec![0; NONCE_LEN + value.len() + overhead]; + + // Randomly generate the nonce, then copy the cookie value as input. + let (nonce, in_out) = data.split_at_mut(NONCE_LEN); + SystemRandom::new() + .fill(nonce) + .expect("couldn't random fill nonce"); + in_out[..value.len()].copy_from_slice(value); + let nonce = Nonce::try_assume_unique_for_key(nonce) + .expect("invalid length of `nonce`"); + + // Use cookie's name as associated data to prevent value swapping. + let ad = Aad::from(name); + + // Perform the actual sealing operation and get the output length. + let output_len = seal_in_place(&key, nonce, ad, in_out, overhead) + .expect("in-place seal"); + + // Remove the overhead and return the sealed content. + data.truncate(NONCE_LEN + output_len); + data +} + #[cfg(test)] mod test { - use super::{Cookie, CookieJar, Key}; + use super::{Cookie, CookieJar, Key, encrypt_name_value}; #[test] fn simple() { @@ -223,4 +238,27 @@ mod test { let mut jar = CookieJar::new(); assert_secure_behaviour!(jar, jar.private(&key)); } + + #[test] + fn non_utf8() { + let key = Key::generate(); + let mut jar = CookieJar::new(); + + let name = "malicious"; + let mut assert_non_utf8 = |value: &[u8]| { + let sealed = encrypt_name_value(name.as_bytes(), value, &key.encryption()); + let encoded = base64::encode(&sealed); + assert_eq!(jar.private(&key).unseal(name, &encoded), Err("bad unsealed utf8")); + jar.add(Cookie::new(name, encoded)); + assert_eq!(jar.private(&key).get(name), None); + }; + + assert_non_utf8(&[0x72, 0xfb, 0xdf, 0x74]); // rûst in ISO/IEC 8859-1 + + let mut malicious = String::from(r#"{"id":"abc123??%X","admin":true}"#) + .into_bytes(); + malicious[8] |= 0b1100_0000; + malicious[9] |= 0b1100_0000; + assert_non_utf8(&malicious); + } } From ce8294740e919c9f7960902a74fde0592515be2f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Mar 2019 17:04:34 -0700 Subject: [PATCH 1183/1635] fix tests with disabled features --- actix-http/src/cookie/jar.rs | 4 +++- actix-http/src/cookie/secure/private.rs | 27 +++++++++++++++---------- actix-http/src/lib.rs | 1 + actix-http/tests/test_server.rs | 11 ++++++++++ 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/actix-http/src/cookie/jar.rs b/actix-http/src/cookie/jar.rs index d9ab8f05..b60d73fe 100644 --- a/actix-http/src/cookie/jar.rs +++ b/actix-http/src/cookie/jar.rs @@ -470,7 +470,9 @@ impl<'a> Iterator for Iter<'a> { #[cfg(test)] mod test { - use super::{Cookie, CookieJar, Key}; + #[cfg(feature = "secure-cookies")] + use super::Key; + use super::{Cookie, CookieJar}; #[test] #[allow(deprecated)] diff --git a/actix-http/src/cookie/secure/private.rs b/actix-http/src/cookie/secure/private.rs index 74352d72..32368730 100644 --- a/actix-http/src/cookie/secure/private.rs +++ b/actix-http/src/cookie/secure/private.rs @@ -63,9 +63,11 @@ impl<'a> PrivateJar<'a> { if let Ok(unsealed_utf8) = str::from_utf8(unsealed) { Ok(unsealed_utf8.to_string()) } else { - warn!("Private cookie does not have utf8 content! + warn!( + "Private cookie does not have utf8 content! It is likely the secret key used to encrypt them has been leaked. -Please change it as soon as possible."); +Please change it as soon as possible." + ); Err("bad unsealed utf8") } } @@ -206,15 +208,15 @@ fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec { .fill(nonce) .expect("couldn't random fill nonce"); in_out[..value.len()].copy_from_slice(value); - let nonce = Nonce::try_assume_unique_for_key(nonce) - .expect("invalid length of `nonce`"); + let nonce = + Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`"); // Use cookie's name as associated data to prevent value swapping. let ad = Aad::from(name); // Perform the actual sealing operation and get the output length. - let output_len = seal_in_place(&key, nonce, ad, in_out, overhead) - .expect("in-place seal"); + let output_len = + seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal"); // Remove the overhead and return the sealed content. data.truncate(NONCE_LEN + output_len); @@ -223,7 +225,7 @@ fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec { #[cfg(test)] mod test { - use super::{Cookie, CookieJar, Key, encrypt_name_value}; + use super::{encrypt_name_value, Cookie, CookieJar, Key}; #[test] fn simple() { @@ -248,15 +250,18 @@ mod test { let mut assert_non_utf8 = |value: &[u8]| { let sealed = encrypt_name_value(name.as_bytes(), value, &key.encryption()); let encoded = base64::encode(&sealed); - assert_eq!(jar.private(&key).unseal(name, &encoded), Err("bad unsealed utf8")); + assert_eq!( + jar.private(&key).unseal(name, &encoded), + Err("bad unsealed utf8") + ); jar.add(Cookie::new(name, encoded)); assert_eq!(jar.private(&key).get(name), None); }; - assert_non_utf8(&[0x72, 0xfb, 0xdf, 0x74]); // rûst in ISO/IEC 8859-1 + assert_non_utf8(&[0x72, 0xfb, 0xdf, 0x74]); // rûst in ISO/IEC 8859-1 - let mut malicious = String::from(r#"{"id":"abc123??%X","admin":true}"#) - .into_bytes(); + let mut malicious = + String::from(r#"{"id":"abc123??%X","admin":true}"#).into_bytes(); malicious[8] |= 0b1100_0000; malicious[9] |= 0b1100_0000; assert_non_utf8(&malicious); diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index a8c44e83..088125ae 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -8,6 +8,7 @@ pub mod body; mod builder; pub mod client; mod config; +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust", feature = "brotli"))] pub mod encoding; mod extensions; mod header; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index f1f82b08..a18d1962 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -16,6 +16,7 @@ use actix_http::{ body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, }; +#[cfg(feature = "ssl")] fn load_body(stream: S) -> impl Future where S: Stream, @@ -346,6 +347,7 @@ fn test_content_length() { } } +#[cfg(feature = "ssl")] #[test] fn test_h2_content_length() { use actix_http::http::{ @@ -443,6 +445,7 @@ fn test_h1_headers() { assert_eq!(bytes, Bytes::from(data2)); } +#[cfg(feature = "ssl")] #[test] fn test_h2_headers() { let data = STR.repeat(10); @@ -523,6 +526,7 @@ fn test_h1_body() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "ssl")] #[test] fn test_h2_body2() { let openssl = ssl_acceptor().unwrap(); @@ -567,6 +571,7 @@ fn test_h1_head_empty() { assert!(bytes.is_empty()); } +#[cfg(feature = "ssl")] #[test] fn test_h2_head_empty() { let openssl = ssl_acceptor().unwrap(); @@ -622,6 +627,7 @@ fn test_h1_head_binary() { assert!(bytes.is_empty()); } +#[cfg(feature = "ssl")] #[test] fn test_h2_head_binary() { let openssl = ssl_acceptor().unwrap(); @@ -674,6 +680,7 @@ fn test_h1_head_binary2() { } } +#[cfg(feature = "ssl")] #[test] fn test_h2_head_binary2() { let openssl = ssl_acceptor().unwrap(); @@ -720,6 +727,7 @@ fn test_h1_body_length() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "ssl")] #[test] fn test_h2_body_length() { let openssl = ssl_acceptor().unwrap(); @@ -779,6 +787,7 @@ fn test_h1_body_chunked_explicit() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[cfg(feature = "ssl")] #[test] fn test_h2_body_chunked_explicit() { let openssl = ssl_acceptor().unwrap(); @@ -861,6 +870,7 @@ fn test_h1_response_http_error_handling() { assert!(bytes.is_empty()); } +#[cfg(feature = "ssl")] #[test] fn test_h2_response_http_error_handling() { let openssl = ssl_acceptor().unwrap(); @@ -908,6 +918,7 @@ fn test_h1_service_error() { assert!(bytes.is_empty()); } +#[cfg(feature = "ssl")] #[test] fn test_h2_service_error() { let openssl = ssl_acceptor().unwrap(); From e4b3f7945803747e70ccfc4bbd2b1dc6e1c93a0f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Mar 2019 17:05:02 -0700 Subject: [PATCH 1184/1635] allocate enough space --- actix-http/src/ws/frame.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index d4c15627..652746b8 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -172,13 +172,14 @@ impl Parser { }; if payload_len < 126 { + dst.reserve(p_len + 2 + if mask { 4 } else { 0 }); dst.put_slice(&[one, two | payload_len as u8]); } else if payload_len <= 65_535 { - dst.reserve(p_len + 4); + dst.reserve(p_len + 4 + if mask { 4 } else { 0 }); dst.put_slice(&[one, two | 126]); dst.put_u16_be(payload_len as u16); } else { - dst.reserve(p_len + 10); + dst.reserve(p_len + 10 + if mask { 4 } else { 0 }); dst.put_slice(&[one, two | 127]); dst.put_u64_be(payload_len as u64); }; @@ -186,7 +187,7 @@ impl Parser { if mask { let mask = rand::random::(); dst.put_u32_le(mask); - dst.extend_from_slice(payload.as_ref()); + dst.put_slice(payload.as_ref()); let pos = dst.len() - payload_len; apply_mask(&mut dst[pos..], mask); } else { From ab45974e355277c3265ffd5fb54fb6b171bf9c12 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Mar 2019 18:19:18 -0700 Subject: [PATCH 1185/1635] add default handler --- actix-files/CHANGES.md | 5 + actix-files/src/lib.rs | 236 +++++++++++++++++++++++++++------------ actix-files/src/named.rs | 2 +- src/resource.rs | 4 +- 4 files changed, 171 insertions(+), 76 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 95ec1c35..4fe8fadb 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.0-alpha.2] - 2019-04-xx + +* Add default handler support + + ## [0.1.0-alpha.1] - 2019-03-28 * Initial impl diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 8254c5fe..60ccd81d 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -7,23 +7,23 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use std::{cmp, io}; +use actix_service::boxed::{self, BoxedNewService, BoxedService}; +use actix_service::{IntoNewService, NewService, Service}; +use actix_web::dev::{ + HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceFromRequest, + ServiceRequest, ServiceResponse, +}; +use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; +use actix_web::http::header::DispositionType; +use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; use bytes::Bytes; +use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Stream}; use mime; use mime_guess::get_mime_type; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use v_htmlescape::escape as escape_html_entity; -use actix_service::{boxed::BoxedNewService, NewService, Service}; -use actix_web::dev::{ - HttpServiceFactory, ResourceDef, ServiceConfig, ServiceFromRequest, ServiceRequest, - ServiceResponse, -}; -use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; -use actix_web::http::header::DispositionType; -use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; -use futures::future::{ok, FutureResult}; - mod error; mod named; mod range; @@ -32,6 +32,7 @@ use self::error::{FilesError, UriSegmentError}; pub use crate::named::NamedFile; pub use crate::range::HttpRange; +type HttpService

    = BoxedService, ServiceResponse, Error>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

    , ServiceResponse, Error, ()>; @@ -232,8 +233,6 @@ pub struct Files { default: Rc>>>>, renderer: Rc, mime_override: Option>, - _chunk_size: usize, - _follow_symlinks: bool, file_flags: named::Flags, } @@ -245,8 +244,6 @@ impl Clone for Files { show_index: self.show_index, default: self.default.clone(), renderer: self.renderer.clone(), - _chunk_size: self._chunk_size, - _follow_symlinks: self._follow_symlinks, file_flags: self.file_flags, path: self.path.clone(), mime_override: self.mime_override.clone(), @@ -274,8 +271,6 @@ impl Files { default: Rc::new(RefCell::new(None)), renderer: Rc::new(directory_listing), mime_override: None, - _chunk_size: 0, - _follow_symlinks: false, file_flags: named::Flags::default(), } } @@ -334,6 +329,24 @@ impl Files { self.file_flags.set(named::Flags::LAST_MD, value); self } + + /// Sets default handler which is used when no matched file could be found. + pub fn default_handler(mut self, f: F) -> Self + where + F: IntoNewService, + U: NewService< + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, + > + 'static, + { + // create and configure default resource + self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( + f.into_new_service().map_init_err(|_| ()), + ))))); + + self + } } impl

    HttpServiceFactory

    for Files

    @@ -353,41 +366,95 @@ where } } -impl

    NewService for Files

    { +impl NewService for Files

    { type Request = ServiceRequest

    ; type Response = ServiceResponse; type Error = Error; - type Service = Self; + type Service = FilesService

    ; type InitError = (); - type Future = FutureResult; + type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { - ok(self.clone()) + let mut srv = FilesService { + directory: self.directory.clone(), + index: self.index.clone(), + show_index: self.show_index, + default: None, + renderer: self.renderer.clone(), + mime_override: self.mime_override.clone(), + file_flags: self.file_flags, + }; + + if let Some(ref default) = *self.default.borrow() { + Box::new( + default + .new_service(&()) + .map(move |default| { + srv.default = Some(default); + srv + }) + .map_err(|_| ()), + ) + } else { + Box::new(ok(srv)) + } } } -impl

    Service for Files

    { +pub struct FilesService

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

    FilesService

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

    , + ) -> Either< + FutureResult, + Box>, + > { + log::debug!("Files: Failed to handle {}: {}", req.path(), e); + if let Some(ref mut default) = self.default { + Either::B(default.call(ServiceRequest::from_parts(req, payload))) + } else { + Either::A(ok(ServiceResponse::from_err(e, req.clone()))) + } + } +} + +impl

    Service for FilesService

    { type Request = ServiceRequest

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

    ) -> Self::Future { - let (req, _) = req.into_parts(); + let (req, pl) = req.into_parts(); let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { Ok(item) => item, - Err(e) => return ok(ServiceResponse::from_err(e, req.clone())), + Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))), }; // full filepath let path = match self.directory.join(&real_path.0).canonicalize() { Ok(path) => path, - Err(e) => return ok(ServiceResponse::from_err(e, req.clone())), + Err(e) => return self.handle_err(e, req, pl), }; if path.is_dir() { @@ -403,25 +470,25 @@ impl

    Service for Files

    { } named_file.flags = self.file_flags; - match named_file.respond_to(&req) { - Ok(item) => ok(ServiceResponse::new(req.clone(), item)), - Err(e) => ok(ServiceResponse::from_err(e, req.clone())), - } + Either::A(ok(match named_file.respond_to(&req) { + Ok(item) => ServiceResponse::new(req.clone(), item), + Err(e) => ServiceResponse::from_err(e, req.clone()), + })) } - Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + Err(e) => return self.handle_err(e, req, pl), } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); let x = (self.renderer)(&dir, &req); match x { - Ok(resp) => ok(resp), - Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + Ok(resp) => Either::A(ok(resp)), + Err(e) => return self.handle_err(e, req, pl), } } else { - ok(ServiceResponse::from_err( + Either::A(ok(ServiceResponse::from_err( FilesError::IsDirectory, req.clone(), - )) + ))) } } else { match NamedFile::open(path) { @@ -434,11 +501,15 @@ impl

    Service for Files

    { named_file.flags = self.file_flags; match named_file.respond_to(&req) { - Ok(item) => ok(ServiceResponse::new(req.clone(), item)), - Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + Ok(item) => { + Either::A(ok(ServiceResponse::new(req.clone(), item))) + } + Err(e) => { + Either::A(ok(ServiceResponse::from_err(e, req.clone()))) + } } } - Err(e) => ok(ServiceResponse::from_err(e, req.clone())), + Err(e) => self.handle_err(e, req, pl), } } } @@ -833,15 +904,15 @@ mod tests { let mut response = test::call_success(&mut srv, request); // with enabled compression - // { - // let te = response - // .headers() - // .get(header::TRANSFER_ENCODING) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(te, "chunked"); - // } + { + let te = response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(); + assert_eq!(te, "chunked"); + } let bytes = test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { @@ -890,20 +961,32 @@ mod tests { assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } - // #[test] - // fn test_named_file_content_encoding() { - // let req = TestRequest::default().method(Method::GET).finish(); - // let file = NamedFile::open("Cargo.toml").unwrap(); + #[test] + fn test_named_file_content_encoding() { + let mut srv = test::init_service(App::new().enable_encoding().service( + web::resource("/").to(|| { + NamedFile::open("Cargo.toml") + .unwrap() + .set_content_encoding(header::ContentEncoding::Identity) + }), + )); - // assert!(file.encoding.is_none()); - // let resp = file - // .set_content_encoding(ContentEncoding::Identity) - // .respond_to(&req) - // .unwrap(); + let request = TestRequest::get() + .uri("/") + .header(header::ACCEPT_ENCODING, "gzip") + .to_request(); + let res = test::call_success(&mut srv, request); + assert_eq!(res.status(), StatusCode::OK); - // assert!(resp.content_encoding().is_some()); - // assert_eq!(resp.content_encoding().unwrap().as_str(), "identity"); - // } + assert_eq!( + res.headers() + .get(header::CONTENT_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "identity" + ); + } #[test] fn test_named_file_allowed_method() { @@ -954,22 +1037,29 @@ mod tests { let _st: Files<()> = Files::new("/", "Cargo.toml"); } - // #[test] - // fn test_default_handler_file_missing() { - // let st = Files::new(".") - // .default_handler(|_: &_| "default content"); - // let req = TestRequest::with_uri("/missing") - // .param("tail", "missing") - // .finish(); + #[test] + fn test_default_handler_file_missing() { + let mut st = test::block_on( + Files::new("/", ".") + .default_handler(|req: ServiceRequest<_>| { + Ok(req.into_response(HttpResponse::Ok().body("default content"))) + }) + .new_service(&()), + ) + .unwrap(); + let req = TestRequest::with_uri("/missing").to_service(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.body(), - // &Body::Binary(Binary::Slice(b"default content")) - // ); - // } + let mut resp = test::call_success(&mut st, req); + assert_eq!(resp.status(), StatusCode::OK); + let bytes = + test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| { + b.extend(c); + Ok::<_, Error>(b) + })) + .unwrap(); + + assert_eq!(bytes.freeze(), Bytes::from_static(b"default content")); + } // #[test] // fn test_serve_index() { diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 842a0e5e..4ee1a3ca 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -43,7 +43,7 @@ pub struct NamedFile { pub(crate) content_disposition: header::ContentDisposition, pub(crate) md: Metadata, modified: Option, - encoding: Option, + pub(crate) encoding: Option, pub(crate) status_code: StatusCode, pub(crate) flags: Flags, } diff --git a/src/resource.rs b/src/resource.rs index b24e8dd5..957795cd 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use actix_http::{Error, Response}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform, + apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform, }; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; @@ -254,7 +254,7 @@ where >, F: IntoTransform, { - let endpoint = ApplyTransform::new(mw, self.endpoint); + let endpoint = apply_transform(mw, self.endpoint); Resource { endpoint, rdef: self.rdef, From 15c5a3bcfb23bd779507c235235de9e90547612b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Mar 2019 18:57:54 -0700 Subject: [PATCH 1186/1635] fix test --- actix-files/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 60ccd81d..54b4f961 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -904,15 +904,15 @@ mod tests { let mut response = test::call_success(&mut srv, request); // with enabled compression - { - let te = response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(te, "chunked"); - } + // { + // let te = response + // .headers() + // .get(header::TRANSFER_ENCODING) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(te, "chunked"); + // } let bytes = test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { From 34695f4bcebdeef38f098bd21341a661d0bf706c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 31 Mar 2019 20:43:00 -0700 Subject: [PATCH 1187/1635] rename test methods; update tests --- CHANGES.md | 7 +++ src/middleware/cors.rs | 34 +++++++------- src/middleware/defaultheaders.rs | 6 +-- src/middleware/errhandlers.rs | 4 +- src/middleware/logger.rs | 62 ++++++++++++------------- src/service.rs | 27 +++++++++++ src/test.rs | 12 ++--- tests/test_httpserver.rs | 79 +++++++++++++++++++++++++++++++- 8 files changed, 169 insertions(+), 62 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e8389910..39975fb4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +### Changed + +* Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` + +* Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` + + ### Removed * Removed unused `actix_web::web::md()` diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 2ece543d..8924eb0a 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -848,8 +848,8 @@ mod tests { #[test] fn validate_origin_allows_all_origins() { let mut cors = Cors::new().finish(test::ok_service()); - let req = - TestRequest::with_header("Origin", "https://www.example.com").to_service(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!(resp.status(), StatusCode::OK); @@ -867,7 +867,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); assert!(cors.inner.validate_allowed_method(&req).is_err()); assert!(cors.inner.validate_allowed_headers(&req).is_err()); @@ -877,7 +877,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); assert!(cors.inner.validate_allowed_method(&req).is_err()); assert!(cors.inner.validate_allowed_headers(&req).is_err()); @@ -889,7 +889,7 @@ mod tests { "AUTHORIZATION,ACCEPT", ) .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!( @@ -935,7 +935,7 @@ mod tests { "AUTHORIZATION,ACCEPT", ) .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!(resp.status(), StatusCode::OK); @@ -960,7 +960,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.unknown.com") .method(Method::GET) - .to_service(); + .to_srv_request(); cors.inner.validate_origin(&req).unwrap(); cors.inner.validate_allowed_method(&req).unwrap(); cors.inner.validate_allowed_headers(&req).unwrap(); @@ -974,7 +974,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::GET) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!(resp.status(), StatusCode::OK); @@ -984,7 +984,7 @@ mod tests { fn test_no_origin_response() { let mut cors = Cors::new().disable_preflight().finish(test::ok_service()); - let req = TestRequest::default().method(Method::GET).to_service(); + let req = TestRequest::default().method(Method::GET).to_srv_request(); let resp = test::call_success(&mut cors, req); assert!(resp .headers() @@ -993,7 +993,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!( &b"https://www.example.com"[..], @@ -1019,7 +1019,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!( @@ -1066,7 +1066,7 @@ mod tests { })); let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!( &b"Accept, Origin"[..], @@ -1082,7 +1082,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); let origins_str = resp @@ -1105,7 +1105,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://example.com") .method(Method::GET) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!( @@ -1118,7 +1118,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://example.org") .method(Method::GET) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!( @@ -1141,7 +1141,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!( @@ -1155,7 +1155,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://example.org") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") .method(Method::OPTIONS) - .to_service(); + .to_srv_request(); let resp = test::call_success(&mut cors, req); assert_eq!( diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index ca5b8f80..72e866db 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -166,11 +166,11 @@ mod tests { ) .unwrap(); - let req = TestRequest::default().to_service(); + let req = TestRequest::default().to_srv_request(); let resp = block_on(mw.call(req)).unwrap(); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let req = TestRequest::default().to_service(); + let req = TestRequest::default().to_srv_request(); let srv = FnService::new(|req: ServiceRequest<_>| { req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) }); @@ -192,7 +192,7 @@ mod tests { let mut mw = block_on(DefaultHeaders::new().content_type().new_transform(srv)).unwrap(); - let req = TestRequest::default().to_service(); + let req = TestRequest::default().to_srv_request(); let resp = block_on(mw.call(req)).unwrap(); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 4f253722..a69bdaf9 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -180,7 +180,7 @@ mod tests { ) .unwrap(); - let resp = test::call_success(&mut mw, TestRequest::default().to_service()); + let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -206,7 +206,7 @@ mod tests { ) .unwrap(); - let resp = test::call_success(&mut mw, TestRequest::default().to_service()); + let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request()); assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index cd52048f..d9c9b138 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -469,44 +469,40 @@ mod tests { header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"), ) - .to_service(); + .to_srv_request(); let _res = block_on(srv.call(req)); } - // #[test] - // fn test_default_format() { - // let format = Format::default(); + #[test] + fn test_default_format() { + let mut format = Format::default(); - // let req = TestRequest::with_header( - // header::USER_AGENT, - // header::HeaderValue::from_static("ACTIX-WEB"), - // ) - // .finish(); - // let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - // let entry_time = time::now(); + let req = TestRequest::with_header( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + ) + .to_srv_request(); - // let render = |fmt: &mut Formatter| { - // for unit in &format.0 { - // unit.render(fmt, &req, &resp, entry_time)?; - // } - // Ok(()) - // }; - // let s = format!("{}", FormatDisplay(&render)); - // assert!(s.contains("GET / HTTP/1.1")); - // assert!(s.contains("200 0")); - // assert!(s.contains("ACTIX-WEB")); + let now = time::now(); + for unit in &mut format.0 { + unit.render_request(now, &req); + } - // let req = TestRequest::with_uri("/?test").finish(); - // let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - // let entry_time = time::now(); + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + for unit in &mut format.0 { + unit.render_response(&resp); + } - // let render = |fmt: &mut Formatter| { - // for unit in &format.0 { - // unit.render(fmt, &req, &resp, entry_time)?; - // } - // Ok(()) - // }; - // let s = format!("{}", FormatDisplay(&render)); - // assert!(s.contains("GET /?test HTTP/1.1")); - // } + let entry_time = time::now(); + let render = |fmt: &mut Formatter| { + for unit in &format.0 { + unit.render(fmt, 1024, entry_time)?; + } + Ok(()) + }; + let s = format!("{}", FormatDisplay(&render)); + assert!(s.contains("GET / HTTP/1.1")); + assert!(s.contains("200 1024")); + assert!(s.contains("ACTIX-WEB")); + } } diff --git a/src/service.rs b/src/service.rs index 5a042208..c260f25b 100644 --- a/src/service.rs +++ b/src/service.rs @@ -449,3 +449,30 @@ impl fmt::Debug for ServiceResponse { res } } + +#[cfg(test)] +mod tests { + use crate::test::TestRequest; + use crate::HttpResponse; + + #[test] + fn test_fmt_debug() { + let req = TestRequest::get() + .uri("/index.html?test=1") + .header("x-test", "111") + .to_srv_request(); + let s = format!("{:?}", req); + assert!(s.contains("ServiceRequest")); + assert!(s.contains("test=1")); + assert!(s.contains("x-test")); + + let res = HttpResponse::Ok().header("x-test", "111").finish(); + let res = TestRequest::post() + .uri("/index.html?test=1") + .to_srv_response(res); + + let s = format!("{:?}", res); + assert!(s.contains("ServiceResponse")); + assert!(s.contains("x-test")); + } +} diff --git a/src/test.rs b/src/test.rs index f18fc2b3..209edac5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -320,7 +320,7 @@ impl TestRequest { } /// Complete request creation and generate `ServiceRequest` instance - pub fn to_service(mut self) -> ServiceRequest { + pub fn to_srv_request(mut self) -> ServiceRequest { let req = self.req.finish(); ServiceRequest::new( @@ -331,16 +331,16 @@ impl TestRequest { ) } + /// Complete request creation and generate `ServiceResponse` instance + pub fn to_srv_response(self, res: HttpResponse) -> ServiceResponse { + self.to_srv_request().into_response(res) + } + /// Complete request creation and generate `Request` instance pub fn to_request(mut self) -> Request { self.req.finish() } - /// Complete request creation and generate `ServiceResponse` instance - pub fn to_response(self, res: HttpResponse) -> ServiceResponse { - self.to_service().into_response(res) - } - /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { let req = self.req.finish(); diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index bafe578e..dca3377c 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -2,8 +2,11 @@ use net2::TcpBuilder; use std::sync::mpsc; use std::{net, thread, time::Duration}; +#[cfg(feature = "ssl")] +use openssl::ssl::SslAcceptorBuilder; + use actix_http::Response; -use actix_web::{web, App, HttpServer}; +use actix_web::{test, web, App, HttpServer}; fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); @@ -76,3 +79,77 @@ fn test_start() { thread::sleep(Duration::from_millis(100)); let _ = sys.stop(); } + +#[cfg(feature = "ssl")] +fn ssl_acceptor() -> std::io::Result { + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); + Ok(builder) +} + +#[test] +#[cfg(feature = "ssl")] +fn test_start_ssl() { + let addr = unused_addr(); + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let sys = actix_rt::System::new("test"); + let builder = ssl_acceptor().unwrap(); + + let srv = HttpServer::new(|| { + App::new().service( + web::resource("/").route(web::to(|| Response::Ok().body("test"))), + ) + }) + .workers(1) + .shutdown_timeout(1) + .system_exit() + .disable_signals() + .bind_ssl(format!("{}", addr), builder) + .unwrap() + .start(); + + let _ = tx.send((srv, actix_rt::System::current())); + let _ = sys.run(); + }); + let (srv, sys) = rx.recv().unwrap(); + + let client = test::run_on(|| { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + + Ok::<_, ()>( + awc::Client::build() + .connector( + awc::Connector::new() + .ssl(builder.build()) + .timeout(Duration::from_millis(100)) + .service(), + ) + .finish(), + ) + }) + .unwrap(); + let host = format!("https://{}", addr); + + let response = test::block_on(client.get(host.clone()).send()).unwrap(); + assert!(response.status().is_success()); + + // stop + let _ = srv.stop(false); + + thread::sleep(Duration::from_millis(100)); + let _ = sys.stop(); +} From 220c04b7b38ef39beb8cba0ef977d6c4f9e50aa4 Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 1 Apr 2019 09:30:11 -0400 Subject: [PATCH 1188/1635] added docs for wrap and wrap_fn --- src/app.rs | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index 9cdfc436..0c5671f7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -112,7 +112,26 @@ where self } - /// Register a middleware. + /// Registers heavyweight Application-level middleware, in the form of a + /// middleware type, that runs during inbound and/or outbound processing in the + /// request lifecycle (request -> response). + /// + /// ```rust + /// use actix_service::Service; + /// # use futures::Future; + /// use actix_web::{middleware, web, App}; + /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .route("/index.html", web::get().to(index)); + /// } + /// ``` pub fn wrap( self, mw: F, @@ -152,7 +171,9 @@ where } } - /// Register a middleware function. + /// Registers lightweight Application-level middleware, in the form of a + /// closure, that runs during inbound and/or outbound processing in the + /// request lifecycle (request -> response). /// /// ```rust /// use actix_service::Service; @@ -400,7 +421,9 @@ where self } - /// Register a middleware. + /// Registers heavyweight Route-level middleware, in the form of a + /// middleware type, that runs during inbound and/or outbound processing in the + /// request lifecycle (request -> response). pub fn wrap( self, mw: F, @@ -440,7 +463,9 @@ where } } - /// Register a middleware function. + /// Registers lightweight Route-level middleware, in the form of a + /// closure, that runs during inbound and/or outbound processing in the + /// request lifecycle (request -> response). pub fn wrap_fn( self, mw: F, From 8800b8ef13d8b52d78a2125ffbb8951156638bca Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 1 Apr 2019 09:59:21 -0400 Subject: [PATCH 1189/1635] mentioned re-use in wrap doc --- src/app.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index 0c5671f7..cfa6c98e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -113,8 +113,8 @@ where } /// Registers heavyweight Application-level middleware, in the form of a - /// middleware type, that runs during inbound and/or outbound processing in the - /// request lifecycle (request -> response). + /// re-usable middleware type, that runs during inbound and/or outbound + /// processing in the request lifecycle (request -> response). /// /// ```rust /// use actix_service::Service; @@ -422,8 +422,8 @@ where } /// Registers heavyweight Route-level middleware, in the form of a - /// middleware type, that runs during inbound and/or outbound processing in the - /// request lifecycle (request -> response). + /// re-usable middleware type, that runs during inbound and/or outbound + /// processing in the request lifecycle (request -> response). pub fn wrap( self, mw: F, From 96fd61f3d5451bbd97588f53378beb8274715863 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Apr 2019 10:26:09 -0700 Subject: [PATCH 1190/1635] rust 1.31.0 compatibility --- .travis.yml | 1 + Cargo.toml | 4 ++++ actix-files/src/lib.rs | 6 ++++-- actix-http/CHANGES.md | 7 +++++++ actix-http/src/cookie/mod.rs | 12 ++++++------ src/lib.rs | 2 +- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index c3698625..00f64d24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ cache: matrix: include: + - rust: 1.31.0 - rust: stable - rust: beta - rust: nightly-2019-03-02 diff --git a/Cargo.toml b/Cargo.toml index 3abe3129..b5d0e876 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,3 +113,7 @@ flate2 = "1.0.2" lto = true opt-level = 3 codegen-units = 1 + +[patch.crates-io] +actix = { git = "https://github.com/actix/actix.git" } +actix-http = { path = "actix-http" } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 54b4f961..d5a47653 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -566,7 +566,10 @@ mod tests { use bytes::BytesMut; use super::*; - use actix_web::http::{header, header::DispositionType, Method, StatusCode}; + use actix_web::http::header::{ + self, ContentDisposition, DispositionParam, DispositionType, + }; + use actix_web::http::{Method, StatusCode}; use actix_web::test::{self, TestRequest}; use actix_web::App; @@ -683,7 +686,6 @@ mod tests { #[test] fn test_named_file_image_attachment() { - use header::{ContentDisposition, DispositionParam, DispositionType}; let cd = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![DispositionParam::Filename(String::from("test.png"))], diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 5659597c..c5c02865 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.0-alpha.3] - 2019-04-xx + +### Fixed + +* Rust 1.31.0 compatibility + + ## [0.1.0-alpha.2] - 2019-03-29 ### Added diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index 5545624a..0f5f4548 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -59,7 +59,7 @@ mod parse; #[macro_use] mod secure; #[cfg(feature = "secure-cookies")] -pub use secure::*; +pub use self::secure::*; use std::borrow::Cow; use std::fmt; @@ -68,11 +68,11 @@ use std::str::FromStr; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use time::{Duration, Tm}; -pub use builder::CookieBuilder; -pub use draft::*; -pub use jar::{CookieJar, Delta, Iter}; -use parse::parse_cookie; -pub use parse::ParseError; +pub use self::builder::CookieBuilder; +pub use self::draft::*; +pub use self::jar::{CookieJar, Delta, Iter}; +use self::parse::parse_cookie; +pub use self::parse::ParseError; #[derive(Debug, Clone)] enum CookieStr { diff --git a/src/lib.rs b/src/lib.rs index cb29fa5b..ca496883 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ //! * SSL support with OpenSSL or `native-tls` //! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) //! * Supports [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.32 or later +//! * Supported Rust version: 1.31 or later //! //! ## Package feature //! From 6c195d85215b31f280e1f21399c2643e7bff922c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Apr 2019 10:26:25 -0700 Subject: [PATCH 1191/1635] add Derev for ClientRequest --- awc/CHANGES.md | 8 ++++++++ awc/src/request.rs | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e0e83214..6e472035 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,13 @@ # Changes + +## [0.1.0-alpha.3] - 2019-04-xx + +### Added + +* Added `Deref` for `ClientRequest`. + + ## [0.1.0-alpha.2] - 2019-03-29 ### Added diff --git a/awc/src/request.rs b/awc/src/request.rs index a462479e..f732657d 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -554,6 +554,20 @@ impl ClientRequest { } } +impl std::ops::Deref for ClientRequest { + type Target = RequestHead; + + fn deref(&self) -> &RequestHead { + &self.head + } +} + +impl std::ops::DerefMut for ClientRequest { + fn deref_mut(&mut self) -> &mut RequestHead { + &mut self.head + } +} + impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( From c5fa6c1abe686ce9afc5a0fd456b1325dab54a01 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Apr 2019 11:29:26 -0700 Subject: [PATCH 1192/1635] do not consume response --- awc/CHANGES.md | 7 +++++++ awc/Cargo.toml | 2 +- awc/src/lib.rs | 2 +- awc/src/response.rs | 17 +++++++++-------- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 6e472035..9ca5b22d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -7,6 +7,13 @@ * Added `Deref` for `ClientRequest`. +* Export `MessageBody` type + + +### Changed + +* `ClientResponse::body()` does not consume response object. + ## [0.1.0-alpha.2] - 2019-03-29 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index c2cc9e7f..ef9143ec 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" diff --git a/awc/src/lib.rs b/awc/src/lib.rs index ff1fb3fe..e2c04dbb 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -39,7 +39,7 @@ pub mod ws; pub use self::builder::ClientBuilder; pub use self::request::ClientRequest; -pub use self::response::ClientResponse; +pub use self::response::{ClientResponse, MessageBody}; use self::connect::{Connect, ConnectorWrapper}; diff --git a/awc/src/response.rs b/awc/src/response.rs index 038a9a33..2548d719 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -105,7 +105,7 @@ where S: Stream + 'static, { /// Load http response's body. - pub fn body(self) -> MessageBody { + pub fn body(&mut self) -> MessageBody { MessageBody::new(self) } } @@ -137,7 +137,7 @@ impl fmt::Debug for ClientResponse { pub struct MessageBody { limit: usize, length: Option, - stream: Option>, + stream: Option>, err: Option, fut: Option>>, } @@ -147,7 +147,7 @@ where S: Stream + 'static, { /// Create `MessageBody` for request. - pub fn new(res: ClientResponse) -> MessageBody { + pub fn new(res: &mut ClientResponse) -> MessageBody { let mut len = None; if let Some(l) = res.headers().get(CONTENT_LENGTH) { if let Ok(s) = l.to_str() { @@ -164,7 +164,7 @@ where MessageBody { limit: 262_144, length: len, - stream: Some(res), + stream: Some(res.take_payload()), fut: None, err: None, } @@ -239,19 +239,20 @@ mod tests { #[test] fn test_body() { - let req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().poll().err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), } - let req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = + TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().poll().err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), } - let req = TestResponse::default() + let mut req = TestResponse::default() .set_payload(Bytes::from_static(b"test")) .finish(); match req.body().poll().ok().unwrap() { @@ -259,7 +260,7 @@ mod tests { _ => unreachable!("error"), } - let req = TestResponse::default() + let mut req = TestResponse::default() .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).poll().err().unwrap() { From 5c4e4edda4e057e9dac379b01c5b183a4c70278d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Apr 2019 11:51:18 -0700 Subject: [PATCH 1193/1635] add ClientResponse::json() --- awc/CHANGES.md | 2 + awc/Cargo.toml | 2 +- awc/src/error.rs | 27 ++++++ awc/src/lib.rs | 2 +- awc/src/response.rs | 194 ++++++++++++++++++++++++++++++++++++++++++-- awc/src/test.rs | 12 ++- src/error.rs | 2 +- 7 files changed, 232 insertions(+), 9 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9ca5b22d..c3359318 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -9,6 +9,8 @@ * Export `MessageBody` type +* `ClientResponse::json()` - Loads and parse `application/json` encoded body + ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ef9143ec..fdaf0a55 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -44,6 +44,7 @@ bytes = "0.4" derive_more = "0.14" futures = "0.1.25" log =" 0.4" +mime = "0.3" percent-encoding = "1.0" rand = "0.6" serde = "1.0" @@ -62,6 +63,5 @@ actix-server = { version = "0.4.1", features=["ssl"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" -mime = "0.3" rand = "0.6" tokio-tcp = "0.1" \ No newline at end of file diff --git a/awc/src/error.rs b/awc/src/error.rs index 8f51fd7d..bbfd9b97 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -4,6 +4,9 @@ pub use actix_http::error::PayloadError; pub use actix_http::ws::HandshakeError as WsHandshakeError; pub use actix_http::ws::ProtocolError as WsProtocolError; +use actix_http::{Response, ResponseError}; +use serde_json::error::Error as JsonError; + use actix_http::http::{header::HeaderValue, Error as HttpError, StatusCode}; use derive_more::{Display, From}; @@ -47,3 +50,27 @@ impl From for WsClientError { WsClientError::SendRequest(err.into()) } } + +/// A set of errors that can occur during parsing json payloads +#[derive(Debug, Display, From)] +pub enum JsonPayloadError { + /// Payload size is bigger than allowed. (default: 32kB) + #[display(fmt = "Json payload size is bigger than allowed.")] + Overflow, + /// Content type error + #[display(fmt = "Content type error")] + ContentType, + /// Deserialize error + #[display(fmt = "Json deserialize error: {}", _0)] + Deserialize(JsonError), + /// Payload error + #[display(fmt = "Error that occur during reading payload: {}", _0)] + Payload(PayloadError), +} + +/// Return `InternlaServerError` for `JsonPayloadError` +impl ResponseError for JsonPayloadError { + fn error_response(&self) -> Response { + Response::new(StatusCode::INTERNAL_SERVER_ERROR) + } +} diff --git a/awc/src/lib.rs b/awc/src/lib.rs index e2c04dbb..8d0ac6a5 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -39,7 +39,7 @@ pub mod ws; pub use self::builder::ClientBuilder; pub use self::request::ClientRequest; -pub use self::response::{ClientResponse, MessageBody}; +pub use self::response::{ClientResponse, JsonBody, MessageBody}; use self::connect::{Connect, ConnectorWrapper}; diff --git a/awc/src/response.rs b/awc/src/response.rs index 2548d719..b9173520 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -4,13 +4,14 @@ use std::fmt; use bytes::{Bytes, BytesMut}; use futures::{Future, Poll, Stream}; -use actix_http::error::PayloadError; +use actix_http::cookie::Cookie; +use actix_http::error::{CookieParseError, PayloadError}; use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE}; use actix_http::http::{HeaderMap, StatusCode, Version}; use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; +use serde::de::DeserializeOwned; -use actix_http::cookie::Cookie; -use actix_http::error::CookieParseError; +use crate::error::JsonPayloadError; /// Client Response pub struct ClientResponse { @@ -104,10 +105,21 @@ impl ClientResponse where S: Stream + 'static, { - /// Load http response's body. + /// Loads http response's body. pub fn body(&mut self) -> MessageBody { MessageBody::new(self) } + + /// Loads and 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 + pub fn json(&mut self) -> JsonBody { + JsonBody::new(self) + } } impl Stream for ClientResponse @@ -230,12 +242,115 @@ where } } +/// Response's payload json parser, it resolves to a deserialized `T` value. +/// +/// Returns error: +/// +/// * content type is not `application/json` +/// * content length is greater than 64k +pub struct JsonBody { + limit: usize, + length: Option, + stream: Payload, + err: Option, + fut: Option>>, +} + +impl JsonBody +where + S: Stream + 'static, + U: DeserializeOwned, +{ + /// Create `JsonBody` for request. + pub fn new(req: &mut ClientResponse) -> Self { + // check content-type + let json = if let Ok(Some(mime)) = req.mime_type() { + mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) + } else { + false + }; + if !json { + return JsonBody { + limit: 65536, + length: None, + stream: Payload::None, + fut: None, + err: Some(JsonPayloadError::ContentType), + }; + } + + let mut len = None; + if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Ok(s) = l.to_str() { + if let Ok(l) = s.parse::() { + len = Some(l) + } + } + } + + JsonBody { + limit: 65536, + length: len, + stream: req.take_payload(), + fut: None, + err: None, + } + } + + /// Change max size of payload. By default max size is 64Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } +} + +impl Future for JsonBody +where + T: Stream + 'static, + U: DeserializeOwned + 'static, +{ + type Item = U; + type Error = JsonPayloadError; + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut { + return fut.poll(); + } + + if let Some(err) = self.err.take() { + return Err(err); + } + + let limit = self.limit; + if let Some(len) = self.length.take() { + if len > limit { + return Err(JsonPayloadError::Overflow); + } + } + + let fut = std::mem::replace(&mut self.stream, Payload::None) + .from_err() + .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(JsonPayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .and_then(|body| Ok(serde_json::from_slice::(&body)?)); + self.fut = Some(Box::new(fut)); + self.poll() + } +} + #[cfg(test)] mod tests { use super::*; use futures::Async; + use serde::{Deserialize, Serialize}; - use crate::{http::header, test::TestResponse}; + use crate::{http::header, test::block_on, test::TestResponse}; #[test] fn test_body() { @@ -268,4 +383,73 @@ mod tests { _ => unreachable!("error"), } } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct MyObject { + name: String, + } + + fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { + match err { + JsonPayloadError::Overflow => match other { + JsonPayloadError::Overflow => true, + _ => false, + }, + JsonPayloadError::ContentType => match other { + JsonPayloadError::ContentType => true, + _ => false, + }, + _ => false, + } + } + + #[test] + fn test_json_body() { + let mut req = TestResponse::default().finish(); + let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + ) + .finish(); + let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ) + .finish(); + + let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100)); + assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); + + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); + + let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); + assert_eq!( + json.ok().unwrap(), + MyObject { + name: "test".to_owned() + } + ); + } } diff --git a/awc/src/test.rs b/awc/src/test.rs index 5e595d15..1c772905 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -6,6 +6,8 @@ use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; use actix_http::http::{HeaderName, HttpTryFrom, Version}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; +#[cfg(test)] +use futures::Future; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::ClientResponse; @@ -18,7 +20,7 @@ thread_local! { } #[cfg(test)] -pub fn run_on(f: F) -> R +pub(crate) fn run_on(f: F) -> R where F: Fn() -> R, { @@ -29,6 +31,14 @@ where .unwrap() } +#[cfg(test)] +pub(crate) fn block_on(f: F) -> Result +where + F: Future, +{ + RT.with(move |rt| rt.borrow_mut().block_on(f)) +} + /// Test `ClientResponse` builder pub struct TestResponse { head: ResponseHead, diff --git a/src/error.rs b/src/error.rs index 02e17241..78dc2fb6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -79,7 +79,7 @@ pub enum JsonPayloadError { Payload(PayloadError), } -/// Return `BadRequest` for `UrlencodedError` +/// Return `BadRequest` for `JsonPayloadError` impl ResponseError for JsonPayloadError { fn error_response(&self) -> HttpResponse { match *self { From 03dfbdfcdd16ea7e863d76e59c742af802f2f513 Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 1 Apr 2019 14:52:05 -0400 Subject: [PATCH 1194/1635] updated wrap and wrap fn descriptions, still requiring viable examples --- src/app.rs | 38 ++++++++++++++++++++++++++------------ src/scope.rs | 20 ++++++++++++-------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/app.rs b/src/app.rs index cfa6c98e..9535dac2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -112,9 +112,12 @@ where self } - /// Registers heavyweight Application-level middleware, in the form of a - /// re-usable middleware type, that runs during inbound and/or outbound - /// processing in the request lifecycle (request -> response). + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound and/or outbound processing in the request + /// lifecycle (request -> response), modifying request/response as + /// necessary, across all requests managed by the *Application*. + /// + /// Use middleware when you need to read or modify *every* request or response in some way. /// /// ```rust /// use actix_service::Service; @@ -171,9 +174,12 @@ where } } - /// Registers lightweight Application-level middleware, in the form of a - /// closure, that runs during inbound and/or outbound processing in the - /// request lifecycle (request -> response). + /// Registers middleware, in the form of a closure, that runs during inbound + /// and/or outbound processing in the request lifecycle (request -> response), + /// modifying request/response as necessary, across all requests managed by + /// the *Application*. + /// + /// Use middleware when you need to read or modify *every* request or response in some way. /// /// ```rust /// use actix_service::Service; @@ -421,9 +427,13 @@ where self } - /// Registers heavyweight Route-level middleware, in the form of a - /// re-usable middleware type, that runs during inbound and/or outbound - /// processing in the request lifecycle (request -> response). + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound and/or outbound processing in the request + /// lifecycle (request -> response), modifying request/response as + /// necessary, across all requests managed by the *Route*. + /// + /// Use middleware when you need to read or modify *every* request or response in some way. + /// pub fn wrap( self, mw: F, @@ -463,9 +473,13 @@ where } } - /// Registers lightweight Route-level middleware, in the form of a - /// closure, that runs during inbound and/or outbound processing in the - /// request lifecycle (request -> response). + /// Registers middleware, in the form of a closure, that runs during inbound + /// and/or outbound processing in the request lifecycle (request -> response), + /// modifying request/response as necessary, across all requests managed by + /// the *Route*. + /// + /// Use middleware when you need to read or modify *every* request or response in some way. + /// pub fn wrap_fn( self, mw: F, diff --git a/src/scope.rs b/src/scope.rs index d45609c5..3fdc4ccb 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -200,11 +200,14 @@ where self } - /// Register a scope level middleware. + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound processing in the request + /// lifecycle (request -> response), modifying request as + /// necessary, across all requests managed by the *Scope*. Note that + /// Scope-level middleware is only used for inbound requests, not outbound + /// responses. /// - /// This is similar to `App's` middlewares, but middleware get invoked on scope level. - /// Scope level middlewares are not allowed to change response - /// type (i.e modify response's body). + /// Use middleware when you need to read or modify *every* request in some way. pub fn wrap( self, mw: F, @@ -238,10 +241,11 @@ where } } - /// Register a scope level middleware function. - /// - /// This function accepts instance of `ServiceRequest` type and - /// mutable reference to the next middleware in chain. + /// Registers middleware, in the form of a closure, that runs during inbound + /// processing in the request lifecycle (request -> response), modifying + /// request as necessary, across all requests managed by the *Scope*. + /// Note that Scope-level middleware is only used for inbound requests, + /// not outbound responses. /// /// ```rust /// use actix_service::Service; From 3dd3f7bc92aa25ef700d05ddf23ff346d8318204 Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 1 Apr 2019 15:10:28 -0400 Subject: [PATCH 1195/1635] updated scope wrap doc --- src/scope.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/scope.rs b/src/scope.rs index 3fdc4ccb..0dfaaf06 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -203,9 +203,10 @@ where /// Registers middleware, in the form of a middleware component (type), /// that runs during inbound processing in the request /// lifecycle (request -> response), modifying request as - /// necessary, across all requests managed by the *Scope*. Note that - /// Scope-level middleware is only used for inbound requests, not outbound - /// responses. + /// necessary, across all requests managed by the *Scope*. Scope-level + /// middleware is more limited in what it can modify, relative to Route or + /// Application level middleware, in that Scope-level middleware can not modify + /// ServiceResponse. /// /// Use middleware when you need to read or modify *every* request in some way. pub fn wrap( @@ -244,8 +245,9 @@ where /// Registers middleware, in the form of a closure, that runs during inbound /// processing in the request lifecycle (request -> response), modifying /// request as necessary, across all requests managed by the *Scope*. - /// Note that Scope-level middleware is only used for inbound requests, - /// not outbound responses. + /// Scope-level middleware is more limited in what it can modify, relative + /// to Route or Application level middleware, in that Scope-level middleware + /// can not modify ServiceResponse. /// /// ```rust /// use actix_service::Service; From 38afc933046f3ddaf953576ae91be39506b8bbbf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Apr 2019 15:19:34 -0700 Subject: [PATCH 1196/1635] Use non-consuming builder pattern for ClientRequest --- awc/CHANGES.md | 2 + awc/src/lib.rs | 4 +- awc/src/request.rs | 281 +++++++++++++++++++++++++-------------------- 3 files changed, 161 insertions(+), 126 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index c3359318..4ae63493 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -14,6 +14,8 @@ ### Changed +* Use non-consuming builder pattern for `ClientRequest`. + * `ClientResponse::body()` does not consume response object. diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 8d0ac6a5..db994431 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -105,7 +105,7 @@ impl Client { let mut req = ClientRequest::new(method, url, self.0.clone()); for (key, value) in &self.0.headers { - req.head.headers.insert(key.clone(), value.clone()); + req.set_header_if_none(key.clone(), value.clone()); } req } @@ -120,7 +120,7 @@ impl Client { { let mut req = self.request(head.method.clone(), url); for (key, value) in &head.headers { - req.head.headers.insert(key.clone(), value.clone()); + req.set_header_if_none(key.clone(), value.clone()); } req } diff --git a/awc/src/request.rs b/awc/src/request.rs index f732657d..807a2897 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -58,7 +58,7 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; /// } /// ``` pub struct ClientRequest { - pub(crate) head: RequestHead, + pub(crate) head: Option, err: Option, cookies: Option, default_headers: bool, @@ -73,36 +73,40 @@ impl ClientRequest { where Uri: HttpTryFrom, { - ClientRequest { + let mut req = ClientRequest { config, - head: RequestHead::default(), + head: Some(RequestHead::default()), err: None, cookies: None, timeout: None, default_headers: true, response_decompress: true, - } - .method(method) - .uri(uri) + }; + req.method(method).uri(uri); + req } /// Set HTTP URI of request. #[inline] - pub fn uri(mut self, uri: U) -> Self + pub fn uri(&mut self, uri: U) -> &mut Self where Uri: HttpTryFrom, { - match Uri::try_from(uri) { - Ok(uri) => self.head.uri = uri, - Err(e) => self.err = Some(e.into()), + if let Some(head) = parts(&mut self.head, &self.err) { + match Uri::try_from(uri) { + Ok(uri) => head.uri = uri, + Err(e) => self.err = Some(e.into()), + } } self } /// Set HTTP method of this request. #[inline] - pub fn method(mut self, method: Method) -> Self { - self.head.method = method; + pub fn method(&mut self, method: Method) -> &mut Self { + if let Some(head) = parts(&mut self.head, &self.err) { + head.method = method; + } self } @@ -111,8 +115,10 @@ impl ClientRequest { /// /// By default requests's HTTP version depends on network stream #[inline] - pub fn version(mut self, version: Version) -> Self { - self.head.version = version; + pub fn version(&mut self, version: Version) -> &mut Self { + if let Some(head) = parts(&mut self.head, &self.err) { + head.version = version; + } self } @@ -129,12 +135,14 @@ impl ClientRequest { /// # })); /// } /// ``` - pub fn set(mut self, hdr: H) -> Self { - match hdr.try_into() { - Ok(value) => { - self.head.headers.insert(H::name(), value); + pub fn set(&mut self, hdr: H) -> &mut Self { + if let Some(head) = parts(&mut self.head, &self.err) { + match hdr.try_into() { + Ok(value) => { + head.headers.insert(H::name(), value); + } + Err(e) => self.err = Some(e.into()), } - Err(e) => self.err = Some(e.into()), } self } @@ -157,95 +165,106 @@ impl ClientRequest { /// # })); /// } /// ``` - pub fn header(mut self, key: K, value: V) -> Self + pub fn header(&mut self, key: K, value: V) -> &mut Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.head.headers.append(key, value); - } + if let Some(head) = parts(&mut self.head, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + head.headers.append(key, value); + } + Err(e) => self.err = Some(e.into()), + }, Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), + } } self } /// Insert a header, replaces existing header. - pub fn set_header(mut self, key: K, value: V) -> Self + pub fn set_header(&mut self, key: K, value: V) -> &mut Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.head.headers.insert(key, value); - } + if let Some(head) = parts(&mut self.head, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + head.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), + }, Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), + } } self } /// Insert a header only if it is not yet set. - pub fn set_header_if_none(mut self, key: K, value: V) -> Self + pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - match HeaderName::try_from(key) { - Ok(key) => { - if !self.head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - self.head.headers.insert(key, value); + if let Some(head) = parts(&mut self.head, &self.err) { + match HeaderName::try_from(key) { + Ok(key) => { + if !head.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + head.headers.insert(key, value); + } + Err(e) => self.err = Some(e.into()), } - Err(e) => self.err = Some(e.into()), } } + Err(e) => self.err = Some(e.into()), } - Err(e) => self.err = Some(e.into()), } self } - /// Close connection + /// Close connection instead of returning it back to connections pool. + /// This setting affect only http/1 connections. #[inline] - pub fn close_connection(mut self) -> Self { - self.head.set_connection_type(ConnectionType::Close); + pub fn close_connection(&mut self) -> &mut Self { + if let Some(head) = parts(&mut self.head, &self.err) { + head.set_connection_type(ConnectionType::Close); + } self } /// Set request's content type #[inline] - pub fn content_type(mut self, value: V) -> Self + pub fn content_type(&mut self, value: V) -> &mut Self where HeaderValue: HttpTryFrom, { - match HeaderValue::try_from(value) { - Ok(value) => { - let _ = self.head.headers.insert(header::CONTENT_TYPE, value); + if let Some(head) = parts(&mut self.head, &self.err) { + match HeaderValue::try_from(value) { + Ok(value) => { + let _ = head.headers.insert(header::CONTENT_TYPE, value); + } + Err(e) => self.err = Some(e.into()), } - Err(e) => self.err = Some(e.into()), } self } /// Set content length #[inline] - pub fn content_length(self, len: u64) -> Self { + pub fn content_length(&mut self, len: u64) -> &mut Self { let mut wrt = BytesMut::new().writer(); let _ = write!(wrt, "{}", len); self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) } /// Set HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option<&str>) -> Self + pub fn basic_auth(&mut self, username: U, password: Option<&str>) -> &mut Self where U: fmt::Display, { @@ -260,7 +279,7 @@ impl ClientRequest { } /// Set HTTP bearer authentication header - pub fn bearer_auth(self, token: T) -> Self + pub fn bearer_auth(&mut self, token: T) -> &mut Self where T: fmt::Display, { @@ -292,7 +311,7 @@ impl ClientRequest { /// })); /// } /// ``` - pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { + pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); jar.add(cookie.into_owned()); @@ -305,13 +324,13 @@ impl ClientRequest { /// Do not add default request headers. /// By default `Date` and `User-Agent` headers are set. - pub fn no_default_headers(mut self) -> Self { + pub fn no_default_headers(&mut self) -> &mut Self { self.default_headers = false; self } /// Disable automatic decompress of response's body - pub fn no_decompress(mut self) -> Self { + pub fn no_decompress(&mut self) -> &mut Self { self.response_decompress = false; self } @@ -320,38 +339,38 @@ impl ClientRequest { /// /// Request timeout is the total time before a response must be received. /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { + pub fn timeout(&mut self, timeout: Duration) -> &mut Self { self.timeout = Some(timeout); self } /// This method calls provided closure with builder reference if /// value is `true`. - pub fn if_true(mut self, value: bool, f: F) -> Self + pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where F: FnOnce(&mut ClientRequest), { if value { - f(&mut self); + f(self); } self } /// This method calls provided closure with builder reference if /// value is `Some`. - pub fn if_some(mut self, value: Option, f: F) -> Self + pub fn if_some(&mut self, value: Option, f: F) -> &mut Self where F: FnOnce(T, &mut ClientRequest), { if let Some(val) = value { - f(val, &mut self); + f(val, self); } self } /// Complete request construction and send body. pub fn send_body( - mut self, + &mut self, body: B, ) -> impl Future< Item = ClientResponse>, @@ -364,8 +383,10 @@ impl ClientRequest { return Either::A(err(e.into())); } + let mut head = self.head.take().expect("cannot reuse response builder"); + // validate uri - let uri = &self.head.uri; + let uri = &head.uri; if uri.host().is_none() { return Either::A(err(InvalidUrl::MissingHost.into())); } else if uri.scheme_part().is_none() { @@ -380,20 +401,20 @@ impl ClientRequest { } // set default headers - let slf = if self.default_headers { + if self.default_headers { // set request host header - if let Some(host) = self.head.uri.host() { - if !self.head.headers.contains_key(header::HOST) { + if let Some(host) = head.uri.host() { + if !head.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match self.head.uri.port_u16() { + let _ = match head.uri.port_u16() { None | Some(80) | Some(443) => write!(wrt, "{}", host), Some(port) => write!(wrt, "{}:{}", host, port), }; match wrt.get_mut().take().freeze().try_into() { Ok(value) => { - self.head.headers.insert(header::HOST, value); + head.headers.insert(header::HOST, value); } Err(e) => return Either::A(err(HttpError::from(e).into())), } @@ -404,14 +425,11 @@ impl ClientRequest { self.set_header_if_none( header::USER_AGENT, concat!("awc/", env!("CARGO_PKG_VERSION")), - ) - } else { - self - }; + ); + } // enable br only for https - let https = slf - .head + let https = head .uri .scheme_part() .map(|s| s == &uri::Scheme::HTTPS) @@ -422,23 +440,19 @@ impl ClientRequest { feature = "flate2-zlib", feature = "flate2-rust" ))] - let mut slf = { + { if https { - slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) + self.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING); } else { #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { - slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); } - #[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))] - slf } - }; - - let mut head = slf.head; + } // set cookies - if let Some(ref mut jar) = slf.cookies { + if let Some(ref mut jar) = self.cookies { let mut cookie = String::new(); for c in jar.delta() { let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); @@ -451,8 +465,8 @@ impl ClientRequest { ); } - let config = slf.config; - let response_decompress = slf.response_decompress; + let config = self.config.as_ref(); + let response_decompress = self.response_decompress; let fut = config .connector @@ -469,7 +483,7 @@ impl ClientRequest { }); // set request timeout - if let Some(timeout) = slf.timeout.or_else(|| config.timeout.clone()) { + if let Some(timeout) = self.timeout.or_else(|| config.timeout.clone()) { Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { if let Some(e) = e.into_inner() { e @@ -484,7 +498,7 @@ impl ClientRequest { /// Set a JSON body and generate `ClientRequest` pub fn send_json( - self, + &mut self, value: T, ) -> impl Future< Item = ClientResponse>, @@ -494,21 +508,18 @@ impl ClientRequest { Ok(body) => body, Err(e) => return Either::A(err(Error::from(e).into())), }; - // set content-type - let slf = if !self.head.headers.contains_key(header::CONTENT_TYPE) { - self.header(header::CONTENT_TYPE, "application/json") - } else { - self - }; - Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) + // set content-type + self.set_header_if_none(header::CONTENT_TYPE, "application/json"); + + Either::B(self.send_body(Body::Bytes(Bytes::from(body)))) } /// Set a urlencoded body and generate `ClientRequest` /// /// `ClientRequestBuilder` can not be used after this call. pub fn send_form( - self, + &mut self, value: T, ) -> impl Future< Item = ClientResponse>, @@ -519,18 +530,18 @@ impl ClientRequest { Err(e) => return Either::A(err(Error::from(e).into())), }; - let slf = if !self.head.headers.contains_key(header::CONTENT_TYPE) { - self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") - } else { - self - }; + // set content-type + self.set_header_if_none( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ); - Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) + Either::B(self.send_body(Body::Bytes(Bytes::from(body)))) } /// Set an streaming body and generate `ClientRequest`. pub fn send_stream( - self, + &mut self, stream: S, ) -> impl Future< Item = ClientResponse>, @@ -545,7 +556,7 @@ impl ClientRequest { /// Set an empty body and generate `ClientRequest`. pub fn send( - self, + &mut self, ) -> impl Future< Item = ClientResponse>, Error = SendRequestError, @@ -558,31 +569,44 @@ impl std::ops::Deref for ClientRequest { type Target = RequestHead; fn deref(&self) -> &RequestHead { - &self.head + self.head.as_ref().expect("cannot reuse response builder") } } impl std::ops::DerefMut for ClientRequest { fn deref_mut(&mut self) -> &mut RequestHead { - &mut self.head + self.head.as_mut().expect("cannot reuse response builder") } } impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let head = self.head.as_ref().expect("cannot reuse response builder"); + writeln!( f, "\nClientRequest {:?} {}:{}", - self.head.version, self.head.method, self.head.uri + head.version, head.method, head.uri )?; writeln!(f, " headers:")?; - for (key, val) in self.head.headers.iter() { + for (key, val) in head.headers.iter() { writeln!(f, " {:?}: {:?}", key, val)?; } Ok(()) } } +#[inline] +fn parts<'a>( + parts: &'a mut Option, + err: &Option, +) -> Option<&'a mut RequestHead> { + if err.is_some() { + return None; + } + parts.as_mut() +} + #[cfg(test)] mod tests { use super::*; @@ -591,7 +615,8 @@ mod tests { #[test] fn test_debug() { test::run_on(|| { - let request = Client::new().get("/").header("x-test", "111"); + let mut request = Client::new().get("/"); + request.header("x-test", "111"); let repr = format!("{:?}", request); assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); @@ -608,6 +633,8 @@ mod tests { assert_eq!( req.head + .as_ref() + .unwrap() .headers .get(header::CONTENT_TYPE) .unwrap() @@ -621,14 +648,16 @@ mod tests { #[test] fn test_client_header_override() { test::run_on(|| { - let req = Client::build() + let mut req = Client::build() .header(header::CONTENT_TYPE, "111") .finish() - .get("/") - .set_header(header::CONTENT_TYPE, "222"); + .get("/"); + req.set_header(header::CONTENT_TYPE, "222"); assert_eq!( req.head + .as_ref() + .unwrap() .headers .get(header::CONTENT_TYPE) .unwrap() @@ -642,12 +671,12 @@ mod tests { #[test] fn client_basic_auth() { test::run_on(|| { - let client = Client::new() - .get("/") - .basic_auth("username", Some("password")); + let mut req = Client::new().get("/"); + req.basic_auth("username", Some("password")); assert_eq!( - client - .head + req.head + .as_ref() + .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() @@ -656,10 +685,12 @@ mod tests { "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" ); - let client = Client::new().get("/").basic_auth("username", None); + let mut req = Client::new().get("/"); + req.basic_auth("username", None); assert_eq!( - client - .head + req.head + .as_ref() + .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() @@ -673,10 +704,12 @@ mod tests { #[test] fn client_bearer_auth() { test::run_on(|| { - let client = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); + let mut req = Client::new().get("/"); + req.bearer_auth("someS3cr3tAutht0k3n"); assert_eq!( - client - .head + req.head + .as_ref() + .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() From 2d4348927880c873bbe59af66ae7d3cd689a178e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Apr 2019 17:53:30 -0700 Subject: [PATCH 1197/1635] ClientRequest::json() accepts reference instead of object --- awc/CHANGES.md | 2 ++ awc/src/request.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4ae63493..cd963507 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -16,6 +16,8 @@ * Use non-consuming builder pattern for `ClientRequest`. +* `ClientRequest::json()` accepts reference instead of object. + * `ClientResponse::body()` does not consume response object. diff --git a/awc/src/request.rs b/awc/src/request.rs index 807a2897..78404b31 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -499,7 +499,7 @@ impl ClientRequest { /// Set a JSON body and generate `ClientRequest` pub fn send_json( &mut self, - value: T, + value: &T, ) -> impl Future< Item = ClientResponse>, Error = SendRequestError, From 1bd0995d7aedbe263ecb26e4d798024f5543df79 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Apr 2019 18:00:38 -0700 Subject: [PATCH 1198/1635] remove unneded & --- awc/src/request.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awc/src/request.rs b/awc/src/request.rs index 78404b31..0b89581a 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -504,7 +504,7 @@ impl ClientRequest { Item = ClientResponse>, Error = SendRequestError, > { - let body = match serde_json::to_string(&value) { + let body = match serde_json::to_string(value) { Ok(body) => body, Err(e) => return Either::A(err(Error::from(e).into())), }; @@ -520,12 +520,12 @@ impl ClientRequest { /// `ClientRequestBuilder` can not be used after this call. pub fn send_form( &mut self, - value: T, + value: &T, ) -> impl Future< Item = ClientResponse>, Error = SendRequestError, > { - let body = match serde_urlencoded::to_string(&value) { + let body = match serde_urlencoded::to_string(value) { Ok(body) => body, Err(e) => return Either::A(err(Error::from(e).into())), }; From c27fbdc35f2c86fa56dc62088abed2eb108aeccd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 10:19:56 -0700 Subject: [PATCH 1199/1635] Preallocate read buffer for h1 codec, #749 --- actix-http/CHANGES.md | 2 ++ actix-http/Cargo.toml | 2 +- actix-http/src/h1/codec.rs | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c5c02865..79187e7a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,8 @@ * Rust 1.31.0 compatibility +* Preallocate read buffer for h1 codec + ## [0.1.0-alpha.2] - 2019-03-29 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2de624b7..a9fda44e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index ceb1027e..e4895f2d 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -19,6 +19,9 @@ use crate::message::{ConnectionType, Head, ResponseHead}; use crate::request::Request; use crate::response::Response; +const LW: usize = 2 * 1024; +const HW: usize = 32 * 1024; + bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; @@ -105,6 +108,11 @@ impl Decoder for Codec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + let cap = src.capacity(); + if cap < LW { + src.reserve(HW - cap); + } + if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), From d067b1d5f1cec0edcc4bdfcf0887b65763cad0b0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 10:53:44 -0700 Subject: [PATCH 1200/1635] do not use static --- awc/src/error.rs | 3 - awc/src/response.rs | 141 ++++++++++++++++++++++---------------------- 2 files changed, 72 insertions(+), 72 deletions(-) diff --git a/awc/src/error.rs b/awc/src/error.rs index bbfd9b97..20654bdf 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -54,9 +54,6 @@ impl From for WsClientError { /// A set of errors that can occur during parsing json payloads #[derive(Debug, Display, From)] pub enum JsonPayloadError { - /// Payload size is bigger than allowed. (default: 32kB) - #[display(fmt = "Json payload size is bigger than allowed.")] - Overflow, /// Content type error #[display(fmt = "Content type error")] ContentType, diff --git a/awc/src/response.rs b/awc/src/response.rs index b9173520..a4719a9a 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -1,8 +1,9 @@ use std::cell::{Ref, RefMut}; use std::fmt; +use std::marker::PhantomData; use bytes::{Bytes, BytesMut}; -use futures::{Future, Poll, Stream}; +use futures::{Async, Future, Poll, Stream}; use actix_http::cookie::Cookie; use actix_http::error::{CookieParseError, PayloadError}; @@ -103,7 +104,7 @@ impl ClientResponse { impl ClientResponse where - S: Stream + 'static, + S: Stream, { /// Loads http response's body. pub fn body(&mut self) -> MessageBody { @@ -147,16 +148,14 @@ impl fmt::Debug for ClientResponse { /// Future that resolves to a complete http message body. pub struct MessageBody { - limit: usize, length: Option, - stream: Option>, err: Option, - fut: Option>>, + fut: Option>, } impl MessageBody where - S: Stream + 'static, + S: Stream, { /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { @@ -174,24 +173,22 @@ where } MessageBody { - limit: 262_144, length: len, - stream: Some(res.take_payload()), - fut: None, err: None, + fut: Some(ReadBody::new(res.take_payload(), 262_144)), } } /// Change max size of payload. By default max size is 256Kb pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; + if let Some(ref mut fut) = self.fut { + fut.limit = limit; + } self } fn err(e: PayloadError) -> Self { MessageBody { - stream: None, - limit: 262_144, fut: None, err: Some(e), length: None, @@ -201,44 +198,23 @@ where impl Future for MessageBody where - S: Stream + 'static, + S: Stream, { type Item = Bytes; type Error = PayloadError; fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - if let Some(err) = self.err.take() { return Err(err); } if let Some(len) = self.length.take() { - if len > self.limit { + if len > self.fut.as_ref().unwrap().limit { return Err(PayloadError::Overflow); } } - // future - let limit = self.limit; - self.fut = Some(Box::new( - self.stream - .take() - .expect("Can not be used second time") - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .map(|body| body.freeze()), - )); - self.poll() + self.fut.as_mut().unwrap().poll() } } @@ -249,16 +225,15 @@ where /// * content type is not `application/json` /// * content length is greater than 64k pub struct JsonBody { - limit: usize, length: Option, - stream: Payload, err: Option, - fut: Option>>, + fut: Option>, + _t: PhantomData, } impl JsonBody where - S: Stream + 'static, + S: Stream, U: DeserializeOwned, { /// Create `JsonBody` for request. @@ -271,11 +246,10 @@ where }; if !json { return JsonBody { - limit: 65536, length: None, - stream: Payload::None, fut: None, err: Some(JsonPayloadError::ContentType), + _t: PhantomData, }; } @@ -289,58 +263,84 @@ where } JsonBody { - limit: 65536, length: len, - stream: req.take_payload(), - fut: None, err: None, + fut: Some(ReadBody::new(req.take_payload(), 65536)), + _t: PhantomData, } } /// Change max size of payload. By default max size is 64Kb pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; + if let Some(ref mut fut) = self.fut { + fut.limit = limit; + } self } } impl Future for JsonBody where - T: Stream + 'static, + T: Stream, U: DeserializeOwned + 'static, { type Item = U; type Error = JsonPayloadError; fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut { - return fut.poll(); - } - if let Some(err) = self.err.take() { return Err(err); } - let limit = self.limit; if let Some(len) = self.length.take() { - if len > limit { - return Err(JsonPayloadError::Overflow); + if len > self.fut.as_ref().unwrap().limit { + return Err(JsonPayloadError::Payload(PayloadError::Overflow)); } } - let fut = std::mem::replace(&mut self.stream, Payload::None) - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(JsonPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) + let body = futures::try_ready!(self.fut.as_mut().unwrap().poll()); + Ok(Async::Ready(serde_json::from_slice::(&body)?)) + } +} + +struct ReadBody { + stream: Payload, + buf: BytesMut, + limit: usize, +} + +impl ReadBody { + fn new(stream: Payload, limit: usize) -> Self { + Self { + stream, + buf: BytesMut::with_capacity(std::cmp::min(limit, 32768)), + limit, + } + } +} + +impl Future for ReadBody +where + S: Stream, +{ + type Item = Bytes; + type Error = PayloadError; + + fn poll(&mut self) -> Poll { + loop { + return match self.stream.poll()? { + Async::Ready(Some(chunk)) => { + if (self.buf.len() + chunk.len()) > self.limit { + Err(PayloadError::Overflow) + } else { + self.buf.extend_from_slice(&chunk); + continue; + } } - }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); - self.fut = Some(Box::new(fut)); - self.poll() + Async::Ready(None) => Ok(Async::Ready(self.buf.take().freeze())), + Async::NotReady => Ok(Async::NotReady), + }; + } } } @@ -391,8 +391,8 @@ mod tests { fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { match err { - JsonPayloadError::Overflow => match other { - JsonPayloadError::Overflow => true, + JsonPayloadError::Payload(PayloadError::Overflow) => match other { + JsonPayloadError::Payload(PayloadError::Overflow) => true, _ => false, }, JsonPayloadError::ContentType => match other { @@ -430,7 +430,10 @@ mod tests { .finish(); let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100)); - assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); + assert!(json_eq( + json.err().unwrap(), + JsonPayloadError::Payload(PayloadError::Overflow) + )); let mut req = TestResponse::default() .header( From 49a499ce7460db83b0e6b697c8b6d84498b179bc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 11:11:32 -0700 Subject: [PATCH 1201/1635] properly allocate read buffer --- actix-http/src/h1/client.rs | 8 ++++++-- actix-http/src/h1/codec.rs | 16 ++++++---------- actix-http/src/h1/mod.rs | 12 +++++++++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 6a50c027..f93bc496 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -10,7 +10,7 @@ use http::header::{ use http::{Method, Version}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; -use super::{decoder, encoder}; +use super::{decoder, encoder, reserve_readbuf}; use super::{Message, MessageType}; use crate::body::BodySize; use crate::config::ServiceConfig; @@ -150,6 +150,7 @@ impl Decoder for ClientCodec { } else { self.inner.payload = None; } + reserve_readbuf(src); Ok(Some(req)) } else { Ok(None) @@ -168,7 +169,10 @@ impl Decoder for ClientPayloadCodec { ); Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(Some(chunk)), + Some(PayloadItem::Chunk(chunk)) => { + reserve_readbuf(src); + Some(Some(chunk)) + } Some(PayloadItem::Eof) => { self.inner.payload.take(); Some(None) diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index e4895f2d..6e891e7c 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -9,7 +9,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{Method, StatusCode, Version}; use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; -use super::{decoder, encoder}; +use super::{decoder, encoder, reserve_readbuf}; use super::{Message, MessageType}; use crate::body::BodySize; use crate::config::ServiceConfig; @@ -19,9 +19,6 @@ use crate::message::{ConnectionType, Head, ResponseHead}; use crate::request::Request; use crate::response::Response; -const LW: usize = 2 * 1024; -const HW: usize = 32 * 1024; - bitflags! { struct Flags: u8 { const HEAD = 0b0000_0001; @@ -108,14 +105,12 @@ impl Decoder for Codec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let cap = src.capacity(); - if cap < LW { - src.reserve(HW - cap); - } - if self.payload.is_some() { Ok(match self.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), + Some(PayloadItem::Chunk(chunk)) => { + reserve_readbuf(src); + Some(Message::Chunk(Some(chunk))) + } Some(PayloadItem::Eof) => { self.payload.take(); Some(Message::Chunk(None)) @@ -140,6 +135,7 @@ impl Decoder for Codec { self.flags.insert(Flags::STREAM); } } + reserve_readbuf(src); Ok(Some(Message::Item(req))) } else { Ok(None) diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index e3d63c52..472d7347 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -1,5 +1,5 @@ //! HTTP/1 implementation -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; mod client; mod codec; @@ -38,6 +38,16 @@ pub enum MessageType { Stream, } +const LW: usize = 2 * 1024; +const HW: usize = 32 * 1024; + +pub(crate) fn reserve_readbuf(src: &mut BytesMut) { + let cap = src.capacity(); + if cap < LW { + src.reserve(HW - cap); + } +} + #[cfg(test)] mod tests { use super::*; From e282ef792522e5398acd72b29b04127cd0b75d6e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 12:51:16 -0700 Subject: [PATCH 1202/1635] return back consuming builder --- awc/CHANGES.md | 4 - awc/src/lib.rs | 4 +- awc/src/request.rs | 317 ++++++++++++++++++++------------------------- src/app.rs | 12 +- src/scope.rs | 10 +- 5 files changed, 152 insertions(+), 195 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index cd963507..4bc9fc0b 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -5,8 +5,6 @@ ### Added -* Added `Deref` for `ClientRequest`. - * Export `MessageBody` type * `ClientResponse::json()` - Loads and parse `application/json` encoded body @@ -14,8 +12,6 @@ ### Changed -* Use non-consuming builder pattern for `ClientRequest`. - * `ClientRequest::json()` accepts reference instead of object. * `ClientResponse::body()` does not consume response object. diff --git a/awc/src/lib.rs b/awc/src/lib.rs index db994431..5f9adb46 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -105,7 +105,7 @@ impl Client { let mut req = ClientRequest::new(method, url, self.0.clone()); for (key, value) in &self.0.headers { - req.set_header_if_none(key.clone(), value.clone()); + req = req.set_header_if_none(key.clone(), value.clone()); } req } @@ -120,7 +120,7 @@ impl Client { { let mut req = self.request(head.method.clone(), url); for (key, value) in &head.headers { - req.set_header_if_none(key.clone(), value.clone()); + req = req.set_header_if_none(key.clone(), value.clone()); } req } diff --git a/awc/src/request.rs b/awc/src/request.rs index 0b89581a..4e3ab47d 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -17,8 +17,8 @@ use actix_http::cookie::{Cookie, CookieJar}; use actix_http::encoding::Decoder; use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; use actix_http::http::{ - uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom, - Method, Uri, Version, + uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, + HttpTryFrom, Method, Uri, Version, }; use actix_http::{Error, Payload, RequestHead}; @@ -58,7 +58,7 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; /// } /// ``` pub struct ClientRequest { - pub(crate) head: Option, + pub(crate) head: RequestHead, err: Option, cookies: Option, default_headers: bool, @@ -73,40 +73,36 @@ impl ClientRequest { where Uri: HttpTryFrom, { - let mut req = ClientRequest { + ClientRequest { config, - head: Some(RequestHead::default()), + head: RequestHead::default(), err: None, cookies: None, timeout: None, default_headers: true, response_decompress: true, - }; - req.method(method).uri(uri); - req + } + .method(method) + .uri(uri) } /// Set HTTP URI of request. #[inline] - pub fn uri(&mut self, uri: U) -> &mut Self + pub fn uri(mut self, uri: U) -> Self where Uri: HttpTryFrom, { - if let Some(head) = parts(&mut self.head, &self.err) { - match Uri::try_from(uri) { - Ok(uri) => head.uri = uri, - Err(e) => self.err = Some(e.into()), - } + match Uri::try_from(uri) { + Ok(uri) => self.head.uri = uri, + Err(e) => self.err = Some(e.into()), } self } /// Set HTTP method of this request. #[inline] - pub fn method(&mut self, method: Method) -> &mut Self { - if let Some(head) = parts(&mut self.head, &self.err) { - head.method = method; - } + pub fn method(mut self, method: Method) -> Self { + self.head.method = method; self } @@ -115,13 +111,23 @@ impl ClientRequest { /// /// By default requests's HTTP version depends on network stream #[inline] - pub fn version(&mut self, version: Version) -> &mut Self { - if let Some(head) = parts(&mut self.head, &self.err) { - head.version = version; - } + pub fn version(mut self, version: Version) -> Self { + self.head.version = version; self } + #[inline] + /// Returns request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head.headers + } + + #[inline] + /// Returns request's mutable headers. + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head.headers + } + /// Set a header. /// /// ```rust @@ -135,14 +141,12 @@ impl ClientRequest { /// # })); /// } /// ``` - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(head) = parts(&mut self.head, &self.err) { - match hdr.try_into() { - Ok(value) => { - head.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), + pub fn set(mut self, hdr: H) -> Self { + match hdr.try_into() { + Ok(value) => { + self.head.headers.insert(H::name(), value); } + Err(e) => self.err = Some(e.into()), } self } @@ -165,65 +169,59 @@ impl ClientRequest { /// # })); /// } /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self + pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(head) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - head.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + let _ = self.head.headers.append(key, value); + } Err(e) => self.err = Some(e.into()), - } + }, + Err(e) => self.err = Some(e.into()), } self } /// Insert a header, replaces existing header. - pub fn set_header(&mut self, key: K, value: V) -> &mut Self + pub fn set_header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(head) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - head.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + let _ = self.head.headers.insert(key, value); + } Err(e) => self.err = Some(e.into()), - } + }, + Err(e) => self.err = Some(e.into()), } self } /// Insert a header only if it is not yet set. - pub fn set_header_if_none(&mut self, key: K, value: V) -> &mut Self + pub fn set_header_if_none(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { - if let Some(head) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => { - if !head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - head.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), + match HeaderName::try_from(key) { + Ok(key) => { + if !self.head.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => { + let _ = self.head.headers.insert(key, value); } + Err(e) => self.err = Some(e.into()), } } - Err(e) => self.err = Some(e.into()), } + Err(e) => self.err = Some(e.into()), } self } @@ -231,40 +229,36 @@ impl ClientRequest { /// Close connection instead of returning it back to connections pool. /// This setting affect only http/1 connections. #[inline] - pub fn close_connection(&mut self) -> &mut Self { - if let Some(head) = parts(&mut self.head, &self.err) { - head.set_connection_type(ConnectionType::Close); - } + pub fn close_connection(mut self) -> Self { + self.head.set_connection_type(ConnectionType::Close); self } /// Set request's content type #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self + pub fn content_type(mut self, value: V) -> Self where HeaderValue: HttpTryFrom, { - if let Some(head) = parts(&mut self.head, &self.err) { - match HeaderValue::try_from(value) { - Ok(value) => { - let _ = head.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), + match HeaderValue::try_from(value) { + Ok(value) => { + let _ = self.head.headers.insert(header::CONTENT_TYPE, value); } + Err(e) => self.err = Some(e.into()), } self } /// Set content length #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { + pub fn content_length(self, len: u64) -> Self { let mut wrt = BytesMut::new().writer(); let _ = write!(wrt, "{}", len); self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) } /// Set HTTP basic authorization header - pub fn basic_auth(&mut self, username: U, password: Option<&str>) -> &mut Self + pub fn basic_auth(self, username: U, password: Option<&str>) -> Self where U: fmt::Display, { @@ -279,7 +273,7 @@ impl ClientRequest { } /// Set HTTP bearer authentication header - pub fn bearer_auth(&mut self, token: T) -> &mut Self + pub fn bearer_auth(self, token: T) -> Self where T: fmt::Display, { @@ -311,7 +305,7 @@ impl ClientRequest { /// })); /// } /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { + pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); jar.add(cookie.into_owned()); @@ -324,13 +318,13 @@ impl ClientRequest { /// Do not add default request headers. /// By default `Date` and `User-Agent` headers are set. - pub fn no_default_headers(&mut self) -> &mut Self { + pub fn no_default_headers(mut self) -> Self { self.default_headers = false; self } /// Disable automatic decompress of response's body - pub fn no_decompress(&mut self) -> &mut Self { + pub fn no_decompress(mut self) -> Self { self.response_decompress = false; self } @@ -339,38 +333,38 @@ impl ClientRequest { /// /// Request timeout is the total time before a response must be received. /// Default value is 5 seconds. - pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); self } /// This method calls provided closure with builder reference if /// value is `true`. - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self + pub fn if_true(mut self, value: bool, f: F) -> Self where F: FnOnce(&mut ClientRequest), { if value { - f(self); + f(&mut self); } self } /// This method calls provided closure with builder reference if /// value is `Some`. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self + pub fn if_some(mut self, value: Option, f: F) -> Self where F: FnOnce(T, &mut ClientRequest), { if let Some(val) = value { - f(val, self); + f(val, &mut self); } self } /// Complete request construction and send body. pub fn send_body( - &mut self, + mut self, body: B, ) -> impl Future< Item = ClientResponse>, @@ -383,10 +377,8 @@ impl ClientRequest { return Either::A(err(e.into())); } - let mut head = self.head.take().expect("cannot reuse response builder"); - // validate uri - let uri = &head.uri; + let uri = &self.head.uri; if uri.host().is_none() { return Either::A(err(InvalidUrl::MissingHost.into())); } else if uri.scheme_part().is_none() { @@ -403,18 +395,18 @@ impl ClientRequest { // set default headers if self.default_headers { // set request host header - if let Some(host) = head.uri.host() { - if !head.headers.contains_key(header::HOST) { + if let Some(host) = self.head.uri.host() { + if !self.head.headers.contains_key(header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match head.uri.port_u16() { + let _ = match self.head.uri.port_u16() { None | Some(80) | Some(443) => write!(wrt, "{}", host), Some(port) => write!(wrt, "{}:{}", host, port), }; match wrt.get_mut().take().freeze().try_into() { Ok(value) => { - head.headers.insert(header::HOST, value); + self.head.headers.insert(header::HOST, value); } Err(e) => return Either::A(err(HttpError::from(e).into())), } @@ -422,32 +414,11 @@ impl ClientRequest { } // user agent - self.set_header_if_none( - header::USER_AGENT, - concat!("awc/", env!("CARGO_PKG_VERSION")), - ); - } - - // enable br only for https - let https = head - .uri - .scheme_part() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true); - - #[cfg(any( - feature = "brotli", - feature = "flate2-zlib", - feature = "flate2-rust" - ))] - { - if https { - self.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING); - } else { - #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] - { - self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate"); - } + if !self.head.headers.contains_key(&header::USER_AGENT) { + self.head.headers.insert( + header::USER_AGENT, + HeaderValue::from_static(concat!("awc/", env!("CARGO_PKG_VERSION"))), + ); } } @@ -459,14 +430,43 @@ impl ClientRequest { let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); let _ = write!(&mut cookie, "; {}={}", name, value); } - head.headers.insert( + self.head.headers.insert( header::COOKIE, HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), ); } - let config = self.config.as_ref(); - let response_decompress = self.response_decompress; + let slf = self; + + // enable br only for https + #[cfg(any( + feature = "brotli", + feature = "flate2-zlib", + feature = "flate2-rust" + ))] + let slf = { + let https = slf + .head + .uri + .scheme_part() + .map(|s| s == &uri::Scheme::HTTPS) + .unwrap_or(true); + + if https { + slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) + } else { + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] + { + slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + } + #[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))] + slf + } + }; + + let head = slf.head; + let config = slf.config.as_ref(); + let response_decompress = slf.response_decompress; let fut = config .connector @@ -483,7 +483,7 @@ impl ClientRequest { }); // set request timeout - if let Some(timeout) = self.timeout.or_else(|| config.timeout.clone()) { + if let Some(timeout) = slf.timeout.or_else(|| config.timeout.clone()) { Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { if let Some(e) = e.into_inner() { e @@ -498,7 +498,7 @@ impl ClientRequest { /// Set a JSON body and generate `ClientRequest` pub fn send_json( - &mut self, + self, value: &T, ) -> impl Future< Item = ClientResponse>, @@ -510,16 +510,16 @@ impl ClientRequest { }; // set content-type - self.set_header_if_none(header::CONTENT_TYPE, "application/json"); + let slf = self.set_header_if_none(header::CONTENT_TYPE, "application/json"); - Either::B(self.send_body(Body::Bytes(Bytes::from(body)))) + Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) } /// Set a urlencoded body and generate `ClientRequest` /// /// `ClientRequestBuilder` can not be used after this call. pub fn send_form( - &mut self, + self, value: &T, ) -> impl Future< Item = ClientResponse>, @@ -531,17 +531,17 @@ impl ClientRequest { }; // set content-type - self.set_header_if_none( + let slf = self.set_header_if_none( header::CONTENT_TYPE, "application/x-www-form-urlencoded", ); - Either::B(self.send_body(Body::Bytes(Bytes::from(body)))) + Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) } /// Set an streaming body and generate `ClientRequest`. pub fn send_stream( - &mut self, + self, stream: S, ) -> impl Future< Item = ClientResponse>, @@ -556,7 +556,7 @@ impl ClientRequest { /// Set an empty body and generate `ClientRequest`. pub fn send( - &mut self, + self, ) -> impl Future< Item = ClientResponse>, Error = SendRequestError, @@ -565,48 +565,21 @@ impl ClientRequest { } } -impl std::ops::Deref for ClientRequest { - type Target = RequestHead; - - fn deref(&self) -> &RequestHead { - self.head.as_ref().expect("cannot reuse response builder") - } -} - -impl std::ops::DerefMut for ClientRequest { - fn deref_mut(&mut self) -> &mut RequestHead { - self.head.as_mut().expect("cannot reuse response builder") - } -} - impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let head = self.head.as_ref().expect("cannot reuse response builder"); - writeln!( f, "\nClientRequest {:?} {}:{}", - head.version, head.method, head.uri + self.head.version, self.head.method, self.head.uri )?; writeln!(f, " headers:")?; - for (key, val) in head.headers.iter() { + for (key, val) in self.head.headers.iter() { writeln!(f, " {:?}: {:?}", key, val)?; } Ok(()) } } -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut RequestHead> { - if err.is_some() { - return None; - } - parts.as_mut() -} - #[cfg(test)] mod tests { use super::*; @@ -615,8 +588,7 @@ mod tests { #[test] fn test_debug() { test::run_on(|| { - let mut request = Client::new().get("/"); - request.header("x-test", "111"); + let request = Client::new().get("/").header("x-test", "111"); let repr = format!("{:?}", request); assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); @@ -633,8 +605,6 @@ mod tests { assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::CONTENT_TYPE) .unwrap() @@ -648,16 +618,14 @@ mod tests { #[test] fn test_client_header_override() { test::run_on(|| { - let mut req = Client::build() + let req = Client::build() .header(header::CONTENT_TYPE, "111") .finish() - .get("/"); - req.set_header(header::CONTENT_TYPE, "222"); + .get("/") + .set_header(header::CONTENT_TYPE, "222"); assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::CONTENT_TYPE) .unwrap() @@ -671,12 +639,11 @@ mod tests { #[test] fn client_basic_auth() { test::run_on(|| { - let mut req = Client::new().get("/"); - req.basic_auth("username", Some("password")); + let req = Client::new() + .get("/") + .basic_auth("username", Some("password")); assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() @@ -685,12 +652,9 @@ mod tests { "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" ); - let mut req = Client::new().get("/"); - req.basic_auth("username", None); + let req = Client::new().get("/").basic_auth("username", None); assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() @@ -704,12 +668,9 @@ mod tests { #[test] fn client_bearer_auth() { test::run_on(|| { - let mut req = Client::new().get("/"); - req.bearer_auth("someS3cr3tAutht0k3n"); + let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); assert_eq!( req.head - .as_ref() - .unwrap() .headers .get(header::AUTHORIZATION) .unwrap() diff --git a/src/app.rs b/src/app.rs index 9535dac2..fd91d072 100644 --- a/src/app.rs +++ b/src/app.rs @@ -112,9 +112,9 @@ where self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound and/or outbound processing in the request - /// lifecycle (request -> response), modifying request/response as + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound and/or outbound processing in the request + /// lifecycle (request -> response), modifying request/response as /// necessary, across all requests managed by the *Application*. /// /// Use middleware when you need to read or modify *every* request or response in some way. @@ -427,9 +427,9 @@ where self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound and/or outbound processing in the request - /// lifecycle (request -> response), modifying request/response as + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound and/or outbound processing in the request + /// lifecycle (request -> response), modifying request/response as /// necessary, across all requests managed by the *Route*. /// /// Use middleware when you need to read or modify *every* request or response in some way. diff --git a/src/scope.rs b/src/scope.rs index 0dfaaf06..874240e7 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -200,12 +200,12 @@ where self } - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound processing in the request - /// lifecycle (request -> response), modifying request as + /// Registers middleware, in the form of a middleware component (type), + /// that runs during inbound processing in the request + /// lifecycle (request -> response), modifying request as /// necessary, across all requests managed by the *Scope*. Scope-level /// middleware is more limited in what it can modify, relative to Route or - /// Application level middleware, in that Scope-level middleware can not modify + /// Application level middleware, in that Scope-level middleware can not modify /// ServiceResponse. /// /// Use middleware when you need to read or modify *every* request in some way. @@ -243,7 +243,7 @@ where } /// Registers middleware, in the form of a closure, that runs during inbound - /// processing in the request lifecycle (request -> response), modifying + /// processing in the request lifecycle (request -> response), modifying /// request as necessary, across all requests managed by the *Scope*. /// Scope-level middleware is more limited in what it can modify, relative /// to Route or Application level middleware, in that Scope-level middleware From bca31eb7adbc9010291e530c42c5e2f99921715a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 13:35:01 -0700 Subject: [PATCH 1203/1635] remove Deref --- CHANGES.md | 2 + Cargo.toml | 3 ++ actix-http/tests/test_client.rs | 10 +++-- actix-http/tests/test_server.rs | 48 +++++++++++----------- awc/src/response.rs | 2 +- awc/tests/test_client.rs | 32 +++++++-------- src/data.rs | 2 +- src/guard.rs | 63 ++++++++++++++++------------- src/middleware/compress.rs | 4 +- src/middleware/cors.rs | 28 ++++++------- src/middleware/decompress.rs | 1 - src/middleware/identity.rs | 4 +- src/middleware/logger.rs | 4 +- src/request.rs | 31 +++++++++----- src/responder.rs | 2 +- src/scope.rs | 6 +-- src/service.rs | 72 ++++++++++++++++----------------- src/types/form.rs | 2 +- src/types/json.rs | 2 +- src/types/path.rs | 2 +- src/types/query.rs | 2 +- tests/test_server.rs | 42 +++++++++---------- 22 files changed, 192 insertions(+), 172 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 39975fb4..655c23ce 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` +* Removed `Deref` impls + ### Removed diff --git a/Cargo.toml b/Cargo.toml index b5d0e876..b8bd6efc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,4 +116,7 @@ codegen-units = 1 [patch.crates-io] actix = { git = "https://github.com/actix/actix.git" } +actix-web = { path = "." } actix-http = { path = "actix-http" } +actix-http-test = { path = "test-server" } +awc = { path = "awc" } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index ea0c5eb9..109a3e4c 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -35,10 +35,10 @@ fn test_h1_v2() { .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); - let request = srv.get().header("x-test", "111").send(); + let request = srv.get("/").header("x-test", "111").send(); let response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); @@ -46,7 +46,7 @@ fn test_h1_v2() { let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let response = srv.block_on(srv.post().send()).unwrap(); + let response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -61,7 +61,9 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let response = srv.block_on(srv.get().close_connection().send()).unwrap(); + let response = srv + .block_on(srv.get("/").close_connection().send()) + .unwrap(); assert!(response.status().is_success()); } diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index a18d1962..85cab929 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -37,7 +37,7 @@ fn test_h1() { .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); } @@ -55,7 +55,7 @@ fn test_h1_2() { .map(|_| ()) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); } @@ -98,7 +98,7 @@ fn test_h2() -> std::io::Result<()> { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -121,7 +121,7 @@ fn test_h2_1() -> std::io::Result<()> { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); Ok(()) } @@ -145,7 +145,7 @@ fn test_h2_body() -> std::io::Result<()> { ) }); - let response = srv.block_on(srv.sget().send_body(data.clone())).unwrap(); + let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); assert!(response.status().is_success()); let body = srv.load_body(response).unwrap(); @@ -437,7 +437,7 @@ fn test_h1_headers() { }) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -482,7 +482,7 @@ fn test_h2_headers() { }).map_err(|_| ())) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -518,7 +518,7 @@ fn test_h1_body() { HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -541,7 +541,7 @@ fn test_h2_body2() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -555,7 +555,7 @@ fn test_h1_head_empty() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.head().send()).unwrap(); + let response = srv.block_on(srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -586,7 +586,7 @@ fn test_h2_head_empty() { ) }); - let response = srv.block_on(srv.shead().send()).unwrap(); + let response = srv.block_on(srv.shead("/").send()).unwrap(); assert!(response.status().is_success()); assert_eq!(response.version(), http::Version::HTTP_2); @@ -611,7 +611,7 @@ fn test_h1_head_binary() { }) }); - let response = srv.block_on(srv.head().send()).unwrap(); + let response = srv.block_on(srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -646,7 +646,7 @@ fn test_h2_head_binary() { ) }); - let response = srv.block_on(srv.shead().send()).unwrap(); + let response = srv.block_on(srv.shead("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -668,7 +668,7 @@ fn test_h1_head_binary2() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.head().send()).unwrap(); + let response = srv.block_on(srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -695,7 +695,7 @@ fn test_h2_head_binary2() { ) }); - let response = srv.block_on(srv.shead().send()).unwrap(); + let response = srv.block_on(srv.shead("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -719,7 +719,7 @@ fn test_h1_body_length() { }) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -747,7 +747,7 @@ fn test_h2_body_length() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -768,7 +768,7 @@ fn test_h1_body_chunked_explicit() { }) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -810,7 +810,7 @@ fn test_h2_body_chunked_explicit() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert!(response.status().is_success()); assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); @@ -830,7 +830,7 @@ fn test_h1_body_chunked_implicit() { }) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -862,7 +862,7 @@ fn test_h1_response_http_error_handling() { })) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -895,7 +895,7 @@ fn test_h2_response_http_error_handling() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -910,7 +910,7 @@ fn test_h1_service_error() { .h1(|_| Err::(error::ErrorBadRequest("error"))) }); - let response = srv.block_on(srv.get().send()).unwrap(); + let response = srv.block_on(srv.get("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -934,7 +934,7 @@ fn test_h2_service_error() { ) }); - let response = srv.block_on(srv.sget().send()).unwrap(); + let response = srv.block_on(srv.sget("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response diff --git a/awc/src/response.rs b/awc/src/response.rs index a4719a9a..73194d67 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -83,7 +83,7 @@ impl ClientResponse { } #[inline] - /// Returns Request's headers. + /// Returns request's headers. pub fn headers(&self) -> &HeaderMap { &self.head().headers } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 51791d67..6aed72e4 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -44,7 +44,7 @@ fn test_simple() { )) }); - let request = srv.get().header("x-test", "111").send(); + let request = srv.get("/").header("x-test", "111").send(); let response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); @@ -52,7 +52,7 @@ fn test_simple() { let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let response = srv.block_on(srv.post().send()).unwrap(); + let response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -114,7 +114,7 @@ fn test_timeout_override() { // let mut srv = // test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); -// let request = srv.get().header("Connection", "close").finish().unwrap(); +// let request = srv.get("/").header("Connection", "close").finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); // } @@ -128,7 +128,7 @@ fn test_timeout_override() { // }) // }); -// let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap(); +// let request = srv.get("/").uri(srv.url("/?qp=5").as_str()).finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); @@ -139,7 +139,7 @@ fn test_timeout_override() { // let mut srv = // test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); -// let request = srv.get().disable_decompress().finish().unwrap(); +// let request = srv.get("/").disable_decompress().finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); @@ -177,7 +177,7 @@ fn test_client_gzip_encoding() { }); // client request - let response = srv.block_on(srv.post().send()).unwrap(); + let response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -200,7 +200,7 @@ fn test_client_gzip_encoding_large() { }); // client request - let response = srv.block_on(srv.post().send()).unwrap(); + let response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -229,7 +229,7 @@ fn test_client_gzip_encoding_large_random() { }); // client request - let response = srv.block_on(srv.post().send_body(data.clone())).unwrap(); + let response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); assert!(response.status().is_success()); // read response @@ -253,7 +253,7 @@ fn test_client_brotli_encoding() { }); // client request - let response = srv.block_on(srv.post().send_body(STR)).unwrap(); + let response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); assert!(response.status().is_success()); // read response @@ -375,7 +375,7 @@ fn test_client_brotli_encoding() { // let body = once(Ok(Bytes::from_static(STR.as_ref()))); -// let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap(); +// let request = srv.get("/").body(Body::Streaming(Box::new(body))).unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); @@ -395,7 +395,7 @@ fn test_client_brotli_encoding() { // }) // }); -// let request = srv.get().finish().unwrap(); +// let request = srv.get("/").finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); @@ -459,7 +459,7 @@ fn test_client_cookie_handling() { )) }); - let request = srv.get().cookie(cookie1.clone()).cookie(cookie2.clone()); + let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); let c1 = response.cookie("cookie1").expect("Missing cookie1"); @@ -472,7 +472,7 @@ fn test_client_cookie_handling() { // fn test_default_headers() { // let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR))); -// let request = srv.get().finish().unwrap(); +// let request = srv.get("/").finish().unwrap(); // let repr = format!("{:?}", request); // assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\"")); // assert!(repr.contains(concat!( @@ -482,7 +482,7 @@ fn test_client_cookie_handling() { // ))); // let request_override = srv -// .get() +// .get("/") // .header("User-Agent", "test") // .header("Accept-Encoding", "over_test") // .finish() @@ -551,7 +551,7 @@ fn client_basic_auth() { }); // set authorization header to Basic - let request = srv.get().basic_auth("username", Some("password")); + let request = srv.get("/").basic_auth("username", Some("password")); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); } @@ -579,7 +579,7 @@ fn client_bearer_auth() { }); // set authorization header to Bearer - let request = srv.get().bearer_auth("someS3cr3tAutht0k3n"); + let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); } diff --git a/src/data.rs b/src/data.rs index a53015c2..a79a303b 100644 --- a/src/data.rs +++ b/src/data.rs @@ -92,7 +92,7 @@ impl FromRequest

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

    ) -> Self::Future { - if let Some(st) = req.config().extensions().get::>() { + if let Some(st) = req.request().config().extensions().get::>() { Ok(st.clone()) } else { Err(ErrorInternalServerError( diff --git a/src/guard.rs b/src/guard.rs index 44e4891e..0990e876 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -73,6 +73,15 @@ where } } +impl Guard for F +where + F: Fn(&RequestHead) -> bool, +{ + fn check(&self, head: &RequestHead) -> bool { + (self)(head) + } +} + /// Return guard that matches if any of supplied guards. /// /// ```rust @@ -300,13 +309,13 @@ mod tests { .to_http_request(); let pred = Header("transfer-encoding", "chunked"); - assert!(pred.check(&req)); + assert!(pred.check(req.head())); let pred = Header("transfer-encoding", "other"); - assert!(!pred.check(&req)); + assert!(!pred.check(req.head())); let pred = Header("content-type", "other"); - assert!(!pred.check(&req)); + assert!(!pred.check(req.head())); } // #[test] @@ -332,50 +341,50 @@ mod tests { .method(Method::POST) .to_http_request(); - assert!(Get().check(&req)); - assert!(!Get().check(&req2)); - assert!(Post().check(&req2)); - assert!(!Post().check(&req)); + assert!(Get().check(req.head())); + assert!(!Get().check(req2.head())); + assert!(Post().check(req2.head())); + assert!(!Post().check(req.head())); let r = TestRequest::default().method(Method::PUT).to_http_request(); - assert!(Put().check(&r)); - assert!(!Put().check(&req)); + assert!(Put().check(r.head())); + assert!(!Put().check(req.head())); let r = TestRequest::default() .method(Method::DELETE) .to_http_request(); - assert!(Delete().check(&r)); - assert!(!Delete().check(&req)); + assert!(Delete().check(r.head())); + assert!(!Delete().check(req.head())); let r = TestRequest::default() .method(Method::HEAD) .to_http_request(); - assert!(Head().check(&r)); - assert!(!Head().check(&req)); + assert!(Head().check(r.head())); + assert!(!Head().check(req.head())); let r = TestRequest::default() .method(Method::OPTIONS) .to_http_request(); - assert!(Options().check(&r)); - assert!(!Options().check(&req)); + assert!(Options().check(r.head())); + assert!(!Options().check(req.head())); let r = TestRequest::default() .method(Method::CONNECT) .to_http_request(); - assert!(Connect().check(&r)); - assert!(!Connect().check(&req)); + assert!(Connect().check(r.head())); + assert!(!Connect().check(req.head())); let r = TestRequest::default() .method(Method::PATCH) .to_http_request(); - assert!(Patch().check(&r)); - assert!(!Patch().check(&req)); + assert!(Patch().check(r.head())); + assert!(!Patch().check(req.head())); let r = TestRequest::default() .method(Method::TRACE) .to_http_request(); - assert!(Trace().check(&r)); - assert!(!Trace().check(&req)); + assert!(Trace().check(r.head())); + assert!(!Trace().check(req.head())); } #[test] @@ -384,13 +393,13 @@ mod tests { .method(Method::TRACE) .to_http_request(); - assert!(Not(Get()).check(&r)); - assert!(!Not(Trace()).check(&r)); + assert!(Not(Get()).check(r.head())); + assert!(!Not(Trace()).check(r.head())); - assert!(All(Trace()).and(Trace()).check(&r)); - assert!(!All(Get()).and(Trace()).check(&r)); + assert!(All(Trace()).and(Trace()).check(r.head())); + assert!(!All(Get()).and(Trace()).check(r.head())); - assert!(Any(Get()).or(Trace()).check(&r)); - assert!(!Any(Get()).or(Get()).check(&r)); + assert!(Any(Get()).or(Trace()).check(r.head())); + assert!(!Any(Get()).or(Get()).check(r.head())); } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index d797e125..f7475440 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -113,7 +113,7 @@ where fn call(&mut self, req: ServiceRequest

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

    FromRequest

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

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

    FromRequest

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

    ) -> Self::Future { - Ok(req.clone()) + Ok(req.request().clone()) } } diff --git a/src/responder.rs b/src/responder.rs index 50467883..3e067628 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -313,7 +313,7 @@ pub(crate) mod tests { let req = TestRequest::with_uri("/some").to_request(); let resp = TestRequest::block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - match resp.body() { + match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"some")); diff --git a/src/scope.rs b/src/scope.rs index 874240e7..7ad2d95e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -693,7 +693,7 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - match resp.body() { + match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: project1")); @@ -799,7 +799,7 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); - match resp.body() { + match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: project_1")); @@ -826,7 +826,7 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); - match resp.body() { + match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes: Bytes = b.clone().into(); assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); diff --git a/src/service.rs b/src/service.rs index c260f25b..13aae869 100644 --- a/src/service.rs +++ b/src/service.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody, ResponseBody}; -use actix_http::http::{HeaderMap, Method, Uri, Version}; +use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::{ Error, Extensions, HttpMessage, Payload, PayloadStream, Request, RequestHead, Response, ResponseHead, @@ -123,7 +123,13 @@ impl

    ServiceRequest

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

    HttpMessage for ServiceRequest

    { } } -impl

    std::ops::Deref for ServiceRequest

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

    std::ops::DerefMut for ServiceRequest

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

    fmt::Debug for ServiceRequest

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

    ServiceFromRequest

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

    ServiceFromRequest

    { } } -impl

    std::ops::Deref for ServiceFromRequest

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

    HttpMessage for ServiceFromRequest

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

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

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

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

    ) -> Self::Future { - serde_urlencoded::from_str::(req.query_string()) + serde_urlencoded::from_str::(req.request().query_string()) .map(|val| Ok(Query(val))) .unwrap_or_else(|e| Err(e.into())) } diff --git a/tests/test_server.rs b/tests/test_server.rs index fc590ff0..3c5d0906 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -54,7 +54,7 @@ fn test_body() { ) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -73,7 +73,7 @@ fn test_body_gzip() { ) }); - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -111,7 +111,7 @@ fn test_body_encoding_override() { }); // Builder - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -161,7 +161,7 @@ fn test_body_gzip_large() { ) }); - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -195,7 +195,7 @@ fn test_body_gzip_large_random() { ) }); - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -224,7 +224,7 @@ fn test_body_chunked_implicit() { ) }); - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response.headers().get(TRANSFER_ENCODING).unwrap(), @@ -258,7 +258,7 @@ fn test_body_br_streaming() { let mut response = srv .block_on( - srv.get() + srv.get("/") .header(ACCEPT_ENCODING, "br") .no_decompress() .send(), @@ -284,7 +284,7 @@ fn test_head_binary() { ))) }); - let mut response = srv.block_on(srv.head().send()).unwrap(); + let mut response = srv.block_on(srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -310,7 +310,7 @@ fn test_no_chunking() { )))) }); - let mut response = srv.block_on(srv.get().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").send()).unwrap(); assert!(response.status().is_success()); assert!(!response.headers().contains_key(TRANSFER_ENCODING)); @@ -333,7 +333,7 @@ fn test_body_deflate() { }); // client request - let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap(); + let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap(); assert!(response.status().is_success()); // read response @@ -362,7 +362,7 @@ fn test_body_brotli() { // client request let mut response = srv .block_on( - srv.get() + srv.get("/") .header(ACCEPT_ENCODING, "br") .no_decompress() .send(), @@ -398,7 +398,7 @@ fn test_encoding() { let enc = e.finish().unwrap(); let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "gzip") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -427,7 +427,7 @@ fn test_gzip_encoding() { let enc = e.finish().unwrap(); let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "gzip") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -457,7 +457,7 @@ fn test_gzip_encoding_large() { let enc = e.finish().unwrap(); let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "gzip") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -491,7 +491,7 @@ fn test_reading_gzip_encoding_large_random() { let enc = e.finish().unwrap(); let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "gzip") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -521,7 +521,7 @@ fn test_reading_deflate_encoding() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "deflate") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -551,7 +551,7 @@ fn test_reading_deflate_encoding_large() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "deflate") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -585,7 +585,7 @@ fn test_reading_deflate_encoding_large_random() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "deflate") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -615,7 +615,7 @@ fn test_brotli_encoding() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "br") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -645,7 +645,7 @@ fn test_brotli_encoding_large() { // client request let request = srv - .post() + .post("/") .header(CONTENT_ENCODING, "br") .send_body(enc.clone()); let mut response = srv.block_on(request).unwrap(); @@ -912,7 +912,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { // .finish(); // let second_cookie = http::Cookie::new("second", "second_value"); -// let request = srv.get().finish().unwrap(); +// let request = srv.get("/").finish().unwrap(); // let response = srv.execute(request.send()).unwrap(); // assert!(response.status().is_success()); From deac983bc702b0b0ed0f40a9017fcc32b064067d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 14:04:28 -0700 Subject: [PATCH 1204/1635] fix test-server workspace setup --- Cargo.toml | 1 + test-server/Cargo.toml | 4 ++++ test-server/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b8bd6efc..3634d8e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "actix-session", "actix-web-actors", "actix-web-codegen", + "test-server", ] [package.metadata.docs.rs] diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 838f2d8d..16a99279 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -53,3 +53,7 @@ time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" openssl = { version="0.10", optional = true } + +[dev-dependencies] +actix-web = "1.0.0-alpa.2" +actix-http = "0.1.0-alpa.2" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index b64ff433..154345b4 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -36,7 +36,7 @@ use net2::TcpBuilder; /// ) /// ); /// -/// let req = srv.get(); +/// let req = srv.get("/"); /// let response = srv.block_on(req.send()).unwrap(); /// assert!(response.status().is_success()); /// } From f100976ef0d7309e88ef72e26165d02446fd0f65 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 14:08:30 -0700 Subject: [PATCH 1205/1635] rename close_connection to force_close --- awc/CHANGES.md | 2 ++ awc/src/request.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4bc9fc0b..a91b28be 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -16,6 +16,8 @@ * `ClientResponse::body()` does not consume response object. +* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` + ## [0.1.0-alpha.2] - 2019-03-29 diff --git a/awc/src/request.rs b/awc/src/request.rs index 4e3ab47d..b96b39e2 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -226,10 +226,10 @@ impl ClientRequest { self } - /// Close connection instead of returning it back to connections pool. + /// Force close connection instead of returning it back to connections pool. /// This setting affect only http/1 connections. #[inline] - pub fn close_connection(mut self) -> Self { + pub fn force_close(mut self) -> Self { self.head.set_connection_type(ConnectionType::Close); self } From 00000fb316d7813324e31b8c8ea2f10fb618b57d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 14:27:54 -0700 Subject: [PATCH 1206/1635] mut obj --- test-server/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 154345b4..98bef99b 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -195,7 +195,7 @@ impl TestServerRuntime { pub fn load_body( &mut self, - response: ClientResponse, + mut response: ClientResponse, ) -> Result where S: Stream + 'static, From db1f7651a3405d15a73adea5498240eba84be0ea Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 14:47:59 -0700 Subject: [PATCH 1207/1635] more patch cratesio --- Cargo.toml | 4 ++++ actix-http/tests/test_client.rs | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3634d8e1..c1a2e184 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,4 +120,8 @@ actix = { git = "https://github.com/actix/actix.git" } actix-web = { path = "." } actix-http = { path = "actix-http" } actix-http-test = { path = "test-server" } +actix-web-codegen = { path = "actix-web-codegen" } +actix-web-actors = { path = "actix-web-actors" } +actix-session = { path = "actix-session" } +actix-files = { path = "actix-files" } awc = { path = "awc" } diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 109a3e4c..817164f8 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -61,9 +61,7 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - let response = srv - .block_on(srv.get("/").close_connection().send()) - .unwrap(); + let response = srv.block_on(srv.get("/").force_close().send()).unwrap(); assert!(response.status().is_success()); } From 4227cddd307cead39121aaff07d19e27b235ebb8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 15:00:10 -0700 Subject: [PATCH 1208/1635] fix dev dependencies --- Cargo.toml | 2 +- actix-files/src/lib.rs | 4 ++-- actix-http/Cargo.toml | 2 +- actix-session/src/cookie.rs | 2 ++ actix-session/src/lib.rs | 2 +- actix-web-actors/src/ws.rs | 2 +- awc/Cargo.toml | 2 +- awc/tests/test_client.rs | 12 ++++++------ 8 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c1a2e184..c5ef5f06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.3" +version = "1.0.0-alpha.2" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index d5a47653..8404ab31 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -552,7 +552,7 @@ impl

    FromRequest

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

    ) -> Self::Future { - PathBufWrp::get_pathbuf(req.match_info().path()) + PathBufWrp::get_pathbuf(req.request().match_info().path()) } } @@ -1049,7 +1049,7 @@ mod tests { .new_service(&()), ) .unwrap(); - let req = TestRequest::with_uri("/missing").to_service(); + let req = TestRequest::with_uri("/missing").to_srv_request(); let mut resp = test::call_success(&mut st, req); assert_eq!(resp.status(), StatusCode::OK); diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a9fda44e..2de624b7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0-alpha.3" +version = "0.1.0-alpha.2" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 9e4fe78b..f7b4ec03 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -333,6 +333,7 @@ mod tests { let request = test::TestRequest::get().to_request(); let response = test::block_on(app.call(request)).unwrap(); assert!(response + .response() .cookies() .find(|c| c.name() == "actix-session") .is_some()); @@ -352,6 +353,7 @@ mod tests { let request = test::TestRequest::get().to_request(); let response = test::block_on(app.call(request)).unwrap(); assert!(response + .response() .cookies() .find(|c| c.name() == "actix-session") .is_some()); diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 819773c6..0cd1b9ed 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -190,7 +190,7 @@ mod tests { #[test] fn session() { - let mut req = test::TestRequest::default().to_service(); + let mut req = test::TestRequest::default().to_srv_request(); Session::set_session( vec![("key".to_string(), "\"value\"".to_string())].into_iter(), diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 43601188..b2c0d404 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -64,7 +64,7 @@ pub fn handshake(req: &HttpRequest) -> Result"] description = "Actix http client." readme = "README.md" diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 6aed72e4..a2882708 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -45,14 +45,14 @@ fn test_simple() { }); let request = srv.get("/").header("x-test", "111").send(); - let response = srv.block_on(request).unwrap(); + let mut response = srv.block_on(request).unwrap(); assert!(response.status().is_success()); // read response let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let response = srv.block_on(srv.post("/").send()).unwrap(); + let mut response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -177,7 +177,7 @@ fn test_client_gzip_encoding() { }); // client request - let response = srv.block_on(srv.post("/").send()).unwrap(); + let mut response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -200,7 +200,7 @@ fn test_client_gzip_encoding_large() { }); // client request - let response = srv.block_on(srv.post("/").send()).unwrap(); + let mut response = srv.block_on(srv.post("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -229,7 +229,7 @@ fn test_client_gzip_encoding_large_random() { }); // client request - let response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); + let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); assert!(response.status().is_success()); // read response @@ -253,7 +253,7 @@ fn test_client_brotli_encoding() { }); // client request - let response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); + let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); assert!(response.status().is_success()); // read response From 3aebe09e5c4a64389efad68fe4856cf8e1d81c24 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 15:47:46 -0700 Subject: [PATCH 1209/1635] travis --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 00f64d24..bcf05103 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ matrix: - rust: 1.31.0 - rust: stable - rust: beta - - rust: nightly-2019-03-02 + - rust: nightly-2019-04-02 allow_failures: - - rust: nightly-2019-03-02 + - rust: nightly-2019-04-02 env: global: @@ -26,7 +26,7 @@ before_install: - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin fi @@ -35,6 +35,7 @@ before_script: - export PATH=$PATH:~/.cargo/bin script: + - cargo clean - cargo update - cargo check --all --no-default-features - cargo test --all-features --all -- --nocapture @@ -50,7 +51,7 @@ after_success: echo "Uploaded documentation" fi - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then taskset -c 0 cargo tarpaulin --out Xml --all --all-features bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" From 51d5006ccf2f16ab0cfecf8b8d95f81894850c12 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 20:50:25 -0700 Subject: [PATCH 1210/1635] Detect socket disconnection during protocol selection --- actix-http/CHANGES.md | 2 ++ actix-http/src/service/service.rs | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 79187e7a..e5a16231 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -8,6 +8,8 @@ * Preallocate read buffer for h1 codec +* Detect socket disconnection during protocol selection + ## [0.1.0-alpha.2] - 2019-03-29 diff --git a/actix-http/src/service/service.rs b/actix-http/src/service/service.rs index 0bc1634d..50a1a6bd 100644 --- a/actix-http/src/service/service.rs +++ b/actix-http/src/service/service.rs @@ -247,7 +247,10 @@ where loop { unsafe { let b = item.1.bytes_mut(); - let n = { try_ready!(item.0.poll_read(b)) }; + let n = try_ready!(item.0.poll_read(b)); + if n == 0 { + return Ok(Async::Ready(())); + } item.1.advance_mut(n); if item.1.len() >= HTTP2_PREFACE.len() { break; From 442f5057dd537caf3fa0dc2abb4e128e0292fb09 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 21:49:31 -0700 Subject: [PATCH 1211/1635] alpha.3 release --- .travis.yml | 2 +- CHANGES.md | 3 ++- Cargo.toml | 10 +++++----- actix-files/CHANGES.md | 2 +- actix-files/Cargo.toml | 4 ++-- actix-http/CHANGES.md | 6 +++++- actix-http/Cargo.toml | 4 ++-- actix-session/CHANGES.md | 4 ++++ actix-session/Cargo.toml | 4 ++-- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 8 ++++---- actix-web-actors/src/ws.rs | 2 +- awc/CHANGES.md | 3 +-- awc/Cargo.toml | 10 +++++----- test-server/CHANGES.md | 5 +++++ test-server/Cargo.toml | 8 ++++---- 16 files changed, 48 insertions(+), 31 deletions(-) diff --git a/.travis.yml b/.travis.yml index bcf05103..b1880541 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --all-features && + cargo doc --no-deps --all-features && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && diff --git a/CHANGES.md b/CHANGES.md index 655c23ce..d6ff547d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## [1.0.0-alpha.3] - 2019-04-02 + ### Changed * Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` @@ -8,7 +10,6 @@ * Removed `Deref` impls - ### Removed * Removed unused `actix_web::web::md()` diff --git a/Cargo.toml b/Cargo.toml index c5ef5f06..0e2fb32a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -72,11 +72,11 @@ actix-utils = "0.3.4" actix-router = "0.1.0" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.1" -actix-http = { version = "0.1.0-alpha.2", features=["fail"] } +actix-http = { version = "0.1.0-alpha.3", features=["fail"] } actix-server = "0.4.2" actix-server-config = "0.1.0" actix-threadpool = "0.1.0" -awc = { version = "0.1.0-alpha.2", optional = true } +awc = { version = "0.1.0-alpha.3", optional = true } bytes = "0.4" derive_more = "0.14" @@ -101,8 +101,8 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.0-alpha.2", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-http = { version = "0.1.0-alpha.3", features=["ssl", "brotli", "flate2-zlib"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 4fe8fadb..7c46b40f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.0-alpha.2] - 2019-04-xx +## [0.1.0-alpha.2] - 2019-04-02 * Add default handler support diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3f1bad69..a1044c6d 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.2" +actix-web = "1.0.0-alpha.3" actix-service = "0.3.4" bitflags = "1" bytes = "0.4" @@ -31,4 +31,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0-alpha.2", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.3", features=["ssl"] } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e5a16231..79995b77 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [0.1.0-alpha.3] - 2019-04-xx +## [0.1.0-alpha.3] - 2019-04-02 + +### Added + +* Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2de624b7..9d4e1521 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -100,7 +100,7 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.2" actix-server = { version = "0.4.1", features=["ssl"] } actix-connect = { version = "0.1.0", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 3cd15609..85e1123a 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-alpha.3] - 2019-04-02 + +* Update actix-web + ## [0.1.0-alpha.2] - 2019-03-29 * Update actix-web diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index e39dc714..956906fa 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,7 +24,7 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-alpha.2" +actix-web = "1.0.0-alpha.3" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 9b142798..34592aaf 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-alpha.3] - 2019-04-02 + +* Update actix-http and actix-web + ## [0.1.0-alpha.2] - 2019-03-29 * Update actix-http and actix-web diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 759d6fc3..598d3945 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -19,12 +19,12 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0-alpha.2" -actix-web = "1.0.0-alpha.2" -actix-http = "0.1.0-alpha.2" +actix-web = "1.0.0-alpha.3" +actix-http = "0.1.0-alpha.3" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" [dev-dependencies] env_logger = "0.6" -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index b2c0d404..64222256 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -20,7 +20,7 @@ pub use actix_http::ws::{ use actix_web::dev::HttpResponseBuilder; use actix_web::error::{Error, ErrorInternalServerError, PayloadError}; use actix_web::http::{header, Method, StatusCode}; -use actix_web::{HttpMessage, HttpRequest, HttpResponse}; +use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; use futures::sync::oneshot::Sender; use futures::{Async, Future, Poll, Stream}; diff --git a/awc/CHANGES.md b/awc/CHANGES.md index a91b28be..0f0bd9f5 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,7 +1,6 @@ # Changes - -## [0.1.0-alpha.3] - 2019-04-xx +## [0.1.0-alpha.3] - 2019-04-02 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ef04d32d..81d91e19 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.1" actix-service = "0.3.4" -actix-http = "0.1.0-alpa.2" +actix-http = "0.1.0-alpa.3" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -55,9 +55,9 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-alpha.2", features=["ssl"] } -actix-http = { version = "0.1.0-alpa.2", features=["ssl"] } -actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] } +actix-web = { version = "1.0.0-alpha.3", features=["ssl"] } +actix-http = { version = "0.1.0-alpa.3", features=["ssl"] } +actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.1", features=["ssl"] } brotli2 = { version="0.3.2" } diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index cac5a2af..14a8ce62 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.0-alpha.3] - 2019-04-02 + +* Request functions accept path #743 + + ## [0.1.0-alpha.2] - 2019-03-29 * Added TestServerRuntime::load_body() method diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 16a99279..f85e2b15 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.1" actix-service = "0.3.4" actix-server = "0.4.0" actix-utils = "0.3.4" -awc = "0.1.0-alpha.2" +awc = "0.1.0-alpha.3" base64 = "0.10" bytes = "0.4" @@ -55,5 +55,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-alpa.2" -actix-http = "0.1.0-alpa.2" +actix-web = "1.0.0-alpa.3" +actix-http = "0.1.0-alpa.3" From 2a89b995aa9f6a611fc1d645d5bd69d30ecdfd23 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 2 Apr 2019 21:56:38 -0700 Subject: [PATCH 1212/1635] do not cleanup travis build --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b1880541..b1b0769e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,6 @@ before_script: - export PATH=$PATH:~/.cargo/bin script: - - cargo clean - cargo update - cargo check --all --no-default-features - cargo test --all-features --all -- --nocapture @@ -44,7 +43,7 @@ script: after_success: - | if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then - cargo doc --no-deps --all-features && + cargo doc --all-features && echo "" > target/doc/index.html && git clone https://github.com/davisp/ghp-import.git && ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && From f56072954bb16e21308617a3bc0cff5f38ef10fb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 03:20:20 -0700 Subject: [PATCH 1213/1635] remove PayloadBuffer --- actix-http/CHANGES.md | 4 + actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/payload.rs | 398 +---------------------------------- 3 files changed, 6 insertions(+), 398 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 79995b77..eef0bdaf 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,9 @@ # Changes +### Deleted + +* Removed PayloadBuffer + ## [0.1.0-alpha.3] - 2019-04-02 ### Added diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 472d7347..3bf69b38 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -12,7 +12,7 @@ mod service; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; -pub use self::payload::{Payload, PayloadBuffer}; +pub use self::payload::Payload; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; #[derive(Debug)] diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 73d05c4b..18796225 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -1,10 +1,9 @@ //! Payload stream use std::cell::RefCell; -use std::cmp; use std::collections::VecDeque; use std::rc::{Rc, Weak}; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use futures::task::current as current_task; use futures::task::Task; use futures::{Async, Poll, Stream}; @@ -258,407 +257,12 @@ impl Inner { } } -/// Payload buffer -pub struct PayloadBuffer { - len: usize, - items: VecDeque, - stream: S, -} - -impl PayloadBuffer -where - S: Stream, -{ - /// Create new `PayloadBuffer` instance - pub fn new(stream: S) -> Self { - PayloadBuffer { - len: 0, - items: VecDeque::new(), - stream, - } - } - - /// Get mutable reference to an inner stream. - pub fn get_mut(&mut self) -> &mut S { - &mut self.stream - } - - #[inline] - fn poll_stream(&mut self) -> Poll { - self.stream.poll().map(|res| match res { - Async::Ready(Some(data)) => { - self.len += data.len(); - self.items.push_back(data); - Async::Ready(true) - } - Async::Ready(None) => Async::Ready(false), - Async::NotReady => Async::NotReady, - }) - } - - /// Read first available chunk of bytes - #[inline] - pub fn readany(&mut self) -> Poll, PayloadError> { - if let Some(data) = self.items.pop_front() { - self.len -= data.len(); - Ok(Async::Ready(Some(data))) - } else { - match self.poll_stream()? { - Async::Ready(true) => self.readany(), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Check if buffer contains enough bytes - #[inline] - pub fn can_read(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - Ok(Async::Ready(Some(true))) - } else { - match self.poll_stream()? { - Async::Ready(true) => self.can_read(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Return reference to the first chunk of data - #[inline] - pub fn get_chunk(&mut self) -> Poll, PayloadError> { - if self.items.is_empty() { - match self.poll_stream()? { - Async::Ready(true) => (), - Async::Ready(false) => return Ok(Async::Ready(None)), - Async::NotReady => return Ok(Async::NotReady), - } - } - match self.items.front().map(|c| c.as_ref()) { - Some(chunk) => Ok(Async::Ready(Some(chunk))), - None => Ok(Async::NotReady), - } - } - - /// Read exact number of bytes - #[inline] - pub fn read_exact(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - self.len -= size; - let mut chunk = self.items.pop_front().unwrap(); - if size < chunk.len() { - let buf = chunk.split_to(size); - self.items.push_front(chunk); - Ok(Async::Ready(Some(buf))) - } else if size == chunk.len() { - Ok(Async::Ready(Some(chunk))) - } else { - let mut buf = BytesMut::with_capacity(size); - buf.extend_from_slice(&chunk); - - while buf.len() < size { - let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size - buf.len(), chunk.len()); - buf.extend_from_slice(&chunk.split_to(rem)); - if !chunk.is_empty() { - self.items.push_front(chunk); - } - } - Ok(Async::Ready(Some(buf.freeze()))) - } - } else { - match self.poll_stream()? { - Async::Ready(true) => self.read_exact(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - } - - /// Remove specified amount if bytes from buffer - #[inline] - pub fn drop_bytes(&mut self, size: usize) { - if size <= self.len { - self.len -= size; - - let mut len = 0; - while len < size { - let mut chunk = self.items.pop_front().unwrap(); - let rem = cmp::min(size - len, chunk.len()); - len += rem; - if rem < chunk.len() { - chunk.split_to(rem); - self.items.push_front(chunk); - } - } - } - } - - /// Copy buffered data - pub fn copy(&mut self, size: usize) -> Poll, PayloadError> { - if size <= self.len { - let mut buf = BytesMut::with_capacity(size); - for chunk in &self.items { - if buf.len() < size { - let rem = cmp::min(size - buf.len(), chunk.len()); - buf.extend_from_slice(&chunk[..rem]); - } - if buf.len() == size { - return Ok(Async::Ready(Some(buf))); - } - } - } - - match self.poll_stream()? { - Async::Ready(true) => self.copy(size), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - - /// Read until specified ending - pub fn read_until(&mut self, line: &[u8]) -> Poll, PayloadError> { - let mut idx = 0; - let mut num = 0; - let mut offset = 0; - let mut found = false; - let mut length = 0; - - for no in 0..self.items.len() { - { - let chunk = &self.items[no]; - for (pos, ch) in chunk.iter().enumerate() { - if *ch == line[idx] { - idx += 1; - if idx == line.len() { - num = no; - offset = pos + 1; - length += pos + 1; - found = true; - break; - } - } else { - idx = 0 - } - } - if !found { - length += chunk.len() - } - } - - if found { - let mut buf = BytesMut::with_capacity(length); - if num > 0 { - for _ in 0..num { - buf.extend_from_slice(&self.items.pop_front().unwrap()); - } - } - if offset > 0 { - let mut chunk = self.items.pop_front().unwrap(); - buf.extend_from_slice(&chunk.split_to(offset)); - if !chunk.is_empty() { - self.items.push_front(chunk) - } - } - self.len -= length; - return Ok(Async::Ready(Some(buf.freeze()))); - } - } - - match self.poll_stream()? { - Async::Ready(true) => self.read_until(line), - Async::Ready(false) => Ok(Async::Ready(None)), - Async::NotReady => Ok(Async::NotReady), - } - } - - /// Read bytes until new line delimiter - pub fn readline(&mut self) -> Poll, PayloadError> { - self.read_until(b"\n") - } - - /// Put unprocessed data back to the buffer - pub fn unprocessed(&mut self, data: Bytes) { - self.len += data.len(); - self.items.push_front(data); - } - - /// Get remaining data from the buffer - pub fn remaining(&mut self) -> Bytes { - self.items - .iter_mut() - .fold(BytesMut::new(), |mut b, c| { - b.extend_from_slice(c); - b - }) - .freeze() - } -} - #[cfg(test)] mod tests { use super::*; use actix_rt::Runtime; use futures::future::{lazy, result}; - #[test] - fn test_basic() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (_, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(payload.len, 0); - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_eof() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - sender.feed_data(Bytes::from("data")); - sender.feed_eof(); - - assert_eq!( - Async::Ready(Some(Bytes::from("data"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - assert_eq!(Async::Ready(None), payload.readany().ok().unwrap()); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_err() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.readany().ok().unwrap()); - - sender.set_error(PayloadError::Incomplete(None)); - payload.readany().err().unwrap(); - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_readany() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from("line1"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - - assert_eq!( - Async::Ready(Some(Bytes::from("line2"))), - payload.readany().ok().unwrap() - ); - assert_eq!(payload.len, 0); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_readexactly() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap()); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from_static(b"li"))), - payload.read_exact(2).ok().unwrap() - ); - assert_eq!(payload.len, 3); - - assert_eq!( - Async::Ready(Some(Bytes::from_static(b"ne1l"))), - payload.read_exact(4).ok().unwrap() - ); - assert_eq!(payload.len, 4); - - sender.set_error(PayloadError::Incomplete(None)); - payload.read_exact(10).err().unwrap(); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - - #[test] - fn test_readuntil() { - Runtime::new() - .unwrap() - .block_on(lazy(|| { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap()); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - - assert_eq!( - Async::Ready(Some(Bytes::from("line"))), - payload.read_until(b"ne").ok().unwrap() - ); - assert_eq!(payload.len, 1); - - assert_eq!( - Async::Ready(Some(Bytes::from("1line2"))), - payload.read_until(b"2").ok().unwrap() - ); - assert_eq!(payload.len, 0); - - sender.set_error(PayloadError::Incomplete(None)); - payload.read_until(b"b").err().unwrap(); - - let res: Result<(), ()> = Ok(()); - result(res) - })) - .unwrap(); - } - #[test] fn test_unread_data() { Runtime::new() From e738361e09b7533ab77f5269400b6429622e6a67 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 12:28:58 -0700 Subject: [PATCH 1214/1635] move multipart support to separate crate --- CHANGES.md | 7 + Cargo.toml | 2 +- actix-http/CHANGES.md | 2 + actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/payload.rs | 4 +- actix-multipart/CHANGES.md | 5 + actix-multipart/Cargo.toml | 34 ++ actix-multipart/README.md | 1 + actix-multipart/src/error.rs | 46 ++ actix-multipart/src/extractor.rs | 57 +++ actix-multipart/src/lib.rs | 6 + .../src/server.rs | 420 ++++++++++++------ src/types/mod.rs | 2 - 13 files changed, 454 insertions(+), 134 deletions(-) create mode 100644 actix-multipart/CHANGES.md create mode 100644 actix-multipart/Cargo.toml create mode 100644 actix-multipart/README.md create mode 100644 actix-multipart/src/error.rs create mode 100644 actix-multipart/src/extractor.rs create mode 100644 actix-multipart/src/lib.rs rename src/types/multipart.rs => actix-multipart/src/server.rs (70%) diff --git a/CHANGES.md b/CHANGES.md index d6ff547d..fc690ee5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [1.0.0-alpha.3] - 2019-04-xx + +### Changed + +* Move multipart support to actix-multipart crate + + ## [1.0.0-alpha.3] - 2019-04-02 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 0e2fb32a..507be4bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "actix-http", "actix-files", "actix-session", + "actix-multipart", "actix-web-actors", "actix-web-codegen", "test-server", @@ -83,7 +84,6 @@ derive_more = "0.14" encoding = "0.2" futures = "0.1" hashbrown = "0.1.8" -httparse = "1.3" log = "0.4" mime = "0.3" net2 = "0.2.33" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index eef0bdaf..3ae481db 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## [0.1.0-alpha.4] - 2019-04-xx + ### Deleted * Removed PayloadBuffer diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 3bf69b38..a05f2800 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -12,7 +12,7 @@ mod service; pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; -pub use self::payload::Payload; +pub use self::payload::{Payload, PayloadWriter}; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; #[derive(Debug)] diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 18796225..bef87f7d 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -14,7 +14,7 @@ use crate::error::PayloadError; pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; #[derive(Debug, PartialEq)] -pub(crate) enum PayloadStatus { +pub enum PayloadStatus { Read, Pause, Dropped, @@ -106,7 +106,7 @@ impl Clone for Payload { } /// Payload writer interface. -pub(crate) trait PayloadWriter { +pub trait PayloadWriter { /// Set stream error. fn set_error(&mut self, err: PayloadError); diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md new file mode 100644 index 00000000..6be07f2e --- /dev/null +++ b/actix-multipart/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0-alpha.1] - 2019-04-xx + +* Split multipart support to separate crate diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml new file mode 100644 index 00000000..006f7066 --- /dev/null +++ b/actix-multipart/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "actix-multipart" +version = "0.1.0-alpha.1" +authors = ["Nikolay Kim "] +description = "Multipart support for actix web framework." +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://docs.rs/actix-multipart/" +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +workspace = ".." +edition = "2018" + +[lib] +name = "actix_multipart" +path = "src/lib.rs" + +[dependencies] +actix-web = "1.0.0-alpha.3" +actix-service = "0.3.4" +bytes = "0.4" +derive_more = "0.14" +httparse = "1.3" +futures = "0.1.25" +log = "0.4" +mime = "0.3" +time = "0.1" +twoway = "0.2" + +[dev-dependencies] +actix-rt = "0.2.2" +actix-http = "0.1.0-alpha.3" \ No newline at end of file diff --git a/actix-multipart/README.md b/actix-multipart/README.md new file mode 100644 index 00000000..2a65840a --- /dev/null +++ b/actix-multipart/README.md @@ -0,0 +1 @@ +# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-session)](https://crates.io/crates/actix-session) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs new file mode 100644 index 00000000..1b872187 --- /dev/null +++ b/actix-multipart/src/error.rs @@ -0,0 +1,46 @@ +//! Error and Result module +use actix_web::error::{ParseError, PayloadError}; +use actix_web::http::StatusCode; +use actix_web::{HttpResponse, ResponseError}; +use derive_more::{Display, From}; + +/// A set of errors that can occur during parsing multipart streams +#[derive(Debug, Display, From)] +pub enum MultipartError { + /// Content-Type header is not found + #[display(fmt = "No Content-type header found")] + NoContentType, + /// Can not parse Content-Type header + #[display(fmt = "Can not parse Content-Type header")] + ParseContentType, + /// Multipart boundary is not found + #[display(fmt = "Multipart boundary is not found")] + Boundary, + /// Multipart stream is incomplete + #[display(fmt = "Multipart stream is incomplete")] + Incomplete, + /// Error during field parsing + #[display(fmt = "{}", _0)] + Parse(ParseError), + /// Payload error + #[display(fmt = "{}", _0)] + Payload(PayloadError), +} + +/// Return `BadRequest` for `MultipartError` +impl ResponseError for MultipartError { + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_multipart_error() { + let resp: HttpResponse = MultipartError::Boundary.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } +} diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs new file mode 100644 index 00000000..18c26c6f --- /dev/null +++ b/actix-multipart/src/extractor.rs @@ -0,0 +1,57 @@ +//! Multipart payload support +use bytes::Bytes; +use futures::Stream; + +use actix_web::dev::ServiceFromRequest; +use actix_web::error::{Error, PayloadError}; +use actix_web::FromRequest; +use actix_web::HttpMessage; + +use crate::server::Multipart; + +/// Get request's payload as multipart stream +/// +/// Content-type: multipart/form-data; +/// +/// ## Server example +/// +/// ```rust +/// # use futures::{Future, Stream}; +/// # use futures::future::{ok, result, Either}; +/// use actix_web::{web, HttpResponse, Error}; +/// use actix_multipart as mp; +/// +/// fn index(payload: mp::Multipart) -> impl Future { +/// payload.from_err() // <- get multipart stream for current request +/// .and_then(|item| match item { // <- iterate over multipart items +/// mp::Item::Field(field) => { +/// // Field in turn is stream of *Bytes* object +/// Either::A(field.from_err() +/// .fold((), |_, chunk| { +/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk)); +/// Ok::<_, Error>(()) +/// })) +/// }, +/// mp::Item::Nested(mp) => { +/// // Or item could be nested Multipart stream +/// Either::B(ok(())) +/// } +/// }) +/// .fold((), |_, _| Ok::<_, Error>(())) +/// .map(|_| HttpResponse::Ok().into()) +/// } +/// # fn main() {} +/// ``` +impl

    FromRequest

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

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

    FromRequest

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

    ) -> Self::Future { - let pl = req.take_payload(); - Ok(Multipart::new(req.headers(), pl)) - } -} - enum InnerMultipartItem { None, Field(Rc>), @@ -163,14 +113,18 @@ impl Multipart { } impl Stream for Multipart { - type Item = MultipartItem; + type Item = Item; type Error = MultipartError; fn poll(&mut self) -> Poll, Self::Error> { if let Some(err) = self.error.take() { Err(err) } else if self.safety.current() { - self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety) + let mut inner = self.inner.as_mut().unwrap().borrow_mut(); + if let Some(payload) = inner.payload.get_mut(&self.safety) { + payload.poll_stream()?; + } + inner.poll(&self.safety) } else { Ok(Async::NotReady) } @@ -178,11 +132,18 @@ impl Stream for Multipart { } impl InnerMultipart { - fn read_headers(payload: &mut PayloadBuffer) -> Poll { - match payload.read_until(b"\r\n\r\n")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(bytes)) => { + fn read_headers( + payload: &mut PayloadBuffer, + ) -> Result, MultipartError> { + match payload.read_until(b"\r\n\r\n") { + None => { + if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(None) + } + } + Some(bytes) => { let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; match httparse::parse_headers(&bytes, &mut hdrs) { Ok(httparse::Status::Complete((_, hdrs))) => { @@ -199,7 +160,7 @@ impl InnerMultipart { return Err(ParseError::Header.into()); } } - Ok(Async::Ready(headers)) + Ok(Some(headers)) } Ok(httparse::Status::Partial) => Err(ParseError::Header.into()), Err(err) => Err(ParseError::from(err).into()), @@ -211,23 +172,28 @@ impl InnerMultipart { fn read_boundary( payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { + ) -> Result, MultipartError> { // TODO: need to read epilogue - match payload.readline()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(chunk)) => { + match payload.readline() { + None => { + if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(None) + } + } + Some(chunk) => { if chunk.len() == boundary.len() + 4 && &chunk[..2] == b"--" && &chunk[2..boundary.len() + 2] == boundary.as_bytes() { - Ok(Async::Ready(false)) + Ok(Some(false)) } else if chunk.len() == boundary.len() + 6 && &chunk[..2] == b"--" && &chunk[2..boundary.len() + 2] == boundary.as_bytes() && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" { - Ok(Async::Ready(true)) + Ok(Some(true)) } else { Err(MultipartError::Boundary) } @@ -238,11 +204,11 @@ impl InnerMultipart { fn skip_until_boundary( payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll { + ) -> Result, MultipartError> { let mut eof = false; loop { - match payload.readline()? { - Async::Ready(Some(chunk)) => { + match payload.readline() { + Some(chunk) => { if chunk.is_empty() { //ValueError("Could not find starting boundary %r" //% (self._boundary)) @@ -267,14 +233,19 @@ impl InnerMultipart { } } } - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Err(MultipartError::Incomplete), + None => { + return if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(None) + }; + } } } - Ok(Async::Ready(eof)) + Ok(Some(eof)) } - fn poll(&mut self, safety: &Safety) -> Poll, MultipartError> { + fn poll(&mut self, safety: &Safety) -> Poll, MultipartError> { if self.state == InnerState::Eof { Ok(Async::Ready(None)) } else { @@ -317,7 +288,7 @@ impl InnerMultipart { payload, &self.boundary, )? { - Async::Ready(eof) => { + Some(eof) => { if eof { self.state = InnerState::Eof; return Ok(Async::Ready(None)); @@ -325,14 +296,14 @@ impl InnerMultipart { self.state = InnerState::Headers; } } - Async::NotReady => return Ok(Async::NotReady), + None => return Ok(Async::NotReady), } } // read boundary InnerState::Boundary => { match InnerMultipart::read_boundary(payload, &self.boundary)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(eof) => { + None => return Ok(Async::NotReady), + Some(eof) => { if eof { self.state = InnerState::Eof; return Ok(Async::Ready(None)); @@ -347,8 +318,7 @@ impl InnerMultipart { // read field headers for next field if self.state == InnerState::Headers { - if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? - { + if let Some(headers) = InnerMultipart::read_headers(payload)? { self.state = InnerState::Boundary; headers } else { @@ -389,7 +359,7 @@ impl InnerMultipart { self.item = InnerMultipartItem::Multipart(Rc::clone(&inner)); - Ok(Async::Ready(Some(MultipartItem::Nested(Multipart { + Ok(Async::Ready(Some(Item::Nested(Multipart { safety: safety.clone(), error: None, inner: Some(inner), @@ -402,9 +372,12 @@ impl InnerMultipart { )?)); self.item = InnerMultipartItem::Field(Rc::clone(&field)); - Ok(Async::Ready(Some(MultipartItem::Field( - MultipartField::new(safety.clone(), headers, mt, field), - )))) + Ok(Async::Ready(Some(Item::Field(Field::new( + safety.clone(), + headers, + mt, + field, + ))))) } } } @@ -418,21 +391,21 @@ impl Drop for InnerMultipart { } /// A single field in a multipart stream -pub struct MultipartField { +pub struct Field { ct: mime::Mime, headers: HeaderMap, inner: Rc>, safety: Safety, } -impl MultipartField { +impl Field { fn new( safety: Safety, headers: HeaderMap, ct: mime::Mime, inner: Rc>, ) -> Self { - MultipartField { + Field { ct, headers, inner, @@ -463,22 +436,28 @@ impl MultipartField { } } -impl Stream for MultipartField { +impl Stream for Field { type Item = Bytes; type Error = MultipartError; fn poll(&mut self) -> Poll, Self::Error> { if self.safety.current() { - self.inner.borrow_mut().poll(&self.safety) + let mut inner = self.inner.borrow_mut(); + if let Some(payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) + { + payload.poll_stream()?; + } + + inner.poll(&self.safety) } else { Ok(Async::NotReady) } } } -impl fmt::Debug for MultipartField { +impl fmt::Debug for Field { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nMultipartField: {}", self.ct)?; + writeln!(f, "\nField: {}", self.ct)?; writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; writeln!(f, " headers:")?; for (key, val) in self.headers.iter() { @@ -532,10 +511,8 @@ impl InnerField { if *size == 0 { Ok(Async::Ready(None)) } else { - match payload.readany() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => Err(MultipartError::Incomplete), - Ok(Async::Ready(Some(mut chunk))) => { + match payload.read_max(*size) { + Some(mut chunk) => { let len = cmp::min(chunk.len() as u64, *size); *size -= len; let ch = chunk.split_to(len as usize); @@ -544,7 +521,13 @@ impl InnerField { } Ok(Async::Ready(Some(ch))) } - Err(err) => Err(err.into()), + None => { + if payload.eof && (*size != 0) { + Err(MultipartError::Incomplete) + } else { + Ok(Async::NotReady) + } + } } } } @@ -555,16 +538,26 @@ impl InnerField { payload: &mut PayloadBuffer, boundary: &str, ) -> Poll, MultipartError> { - match payload.read_until(b"\r")? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { + match payload.read_until(b"\r") { + None => { + if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(Async::NotReady) + } + } + Some(mut chunk) => { if chunk.len() == 1 { payload.unprocessed(chunk); - match payload.read_exact(boundary.len() + 4)? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(None) => Err(MultipartError::Incomplete), - Async::Ready(Some(mut chunk)) => { + match payload.read_exact(boundary.len() + 4) { + None => { + if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(Async::NotReady) + } + } + Some(mut chunk) => { if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" && &chunk[4..] == boundary.as_bytes() @@ -606,10 +599,9 @@ impl InnerField { Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), Async::Ready(None) => { self.eof = true; - match payload.readline()? { - Async::NotReady => Async::NotReady, - Async::Ready(None) => Async::Ready(None), - Async::Ready(Some(line)) => { + match payload.readline() { + None => Async::Ready(None), + Some(line) => { if line.as_ref() != b"\r\n" { log::warn!("multipart field did not read all the data or it is malformed"); } @@ -711,14 +703,86 @@ impl Drop for Safety { } } +/// Payload buffer +struct PayloadBuffer { + eof: bool, + buf: BytesMut, + stream: Box>, +} + +impl PayloadBuffer { + /// Create new `PayloadBuffer` instance + fn new(stream: S) -> Self + where + S: Stream + 'static, + { + PayloadBuffer { + eof: false, + buf: BytesMut::new(), + stream: Box::new(stream), + } + } + + fn poll_stream(&mut self) -> Result<(), PayloadError> { + loop { + match self.stream.poll()? { + Async::Ready(Some(data)) => self.buf.extend_from_slice(&data), + Async::Ready(None) => { + self.eof = true; + return Ok(()); + } + Async::NotReady => return Ok(()), + } + } + } + + /// Read exact number of bytes + #[inline] + fn read_exact(&mut self, size: usize) -> Option { + if size <= self.buf.len() { + Some(self.buf.split_to(size).freeze()) + } else { + None + } + } + + fn read_max(&mut self, size: u64) -> Option { + if !self.buf.is_empty() { + let size = std::cmp::min(self.buf.len() as u64, size) as usize; + Some(self.buf.split_to(size).freeze()) + } else { + None + } + } + + /// Read until specified ending + pub fn read_until(&mut self, line: &[u8]) -> Option { + twoway::find_bytes(&self.buf, line) + .map(|idx| self.buf.split_to(idx + line.len()).freeze()) + } + + /// Read bytes until new line delimiter + pub fn readline(&mut self) -> Option { + self.read_until(b"\n") + } + + /// Put unprocessed data back to the buffer + pub fn unprocessed(&mut self, data: Bytes) { + let buf = BytesMut::from(data); + let buf = std::mem::replace(&mut self.buf, buf); + self.buf.extend_from_slice(&buf); + } +} + #[cfg(test)] mod tests { + use actix_http::h1::{Payload, PayloadWriter}; use bytes::Bytes; use futures::unsync::mpsc; use super::*; - use crate::http::header::{DispositionParam, DispositionType}; - use crate::test::run_on; + use actix_web::http::header::{DispositionParam, DispositionType}; + use actix_web::test::run_on; #[test] fn test_boundary() { @@ -799,9 +863,9 @@ mod tests { ); let mut multipart = Multipart::new(&headers, payload); - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { + match multipart.poll().unwrap() { + Async::Ready(Some(item)) => match item { + Item::Field(mut field) => { { let cd = field.content_disposition().unwrap(); assert_eq!(cd.disposition, DispositionType::FormData); @@ -813,12 +877,12 @@ mod tests { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); - match field.poll() { - Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "test"), + match field.poll().unwrap() { + Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), _ => unreachable!(), } - match field.poll() { - Ok(Async::Ready(None)) => (), + match field.poll().unwrap() { + Async::Ready(None) => (), _ => unreachable!(), } } @@ -827,9 +891,9 @@ mod tests { _ => unreachable!(), } - match multipart.poll() { - Ok(Async::Ready(Some(item))) => match item { - MultipartItem::Field(mut field) => { + match multipart.poll().unwrap() { + Async::Ready(Some(item)) => match item { + Item::Field(mut field) => { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); @@ -847,10 +911,110 @@ mod tests { _ => unreachable!(), } - match multipart.poll() { - Ok(Async::Ready(None)) => (), + match multipart.poll().unwrap() { + Async::Ready(None) => (), _ => unreachable!(), } }); } + + #[test] + fn test_basic() { + run_on(|| { + let (_, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(payload.buf.len(), 0); + payload.poll_stream().unwrap(); + assert_eq!(None, payload.read_max(1)); + }) + } + + #[test] + fn test_eof() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(None, payload.read_max(4)); + sender.feed_data(Bytes::from("data")); + sender.feed_eof(); + payload.poll_stream().unwrap(); + + assert_eq!(Some(Bytes::from("data")), payload.read_max(4)); + assert_eq!(payload.buf.len(), 0); + assert_eq!(None, payload.read_max(1)); + assert!(payload.eof); + }) + } + + #[test] + fn test_err() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + assert_eq!(None, payload.read_max(1)); + sender.set_error(PayloadError::Incomplete(None)); + payload.poll_stream().err().unwrap(); + }) + } + + #[test] + fn test_readmax() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + payload.poll_stream().unwrap(); + assert_eq!(payload.buf.len(), 10); + + assert_eq!(Some(Bytes::from("line1")), payload.read_max(5)); + assert_eq!(payload.buf.len(), 5); + + assert_eq!(Some(Bytes::from("line2")), payload.read_max(5)); + assert_eq!(payload.buf.len(), 0); + }) + } + + #[test] + fn test_readexactly() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(None, payload.read_exact(2)); + + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + payload.poll_stream().unwrap(); + + assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2)); + assert_eq!(payload.buf.len(), 8); + + assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4)); + assert_eq!(payload.buf.len(), 4); + }) + } + + #[test] + fn test_readuntil() { + run_on(|| { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + + assert_eq!(None, payload.read_until(b"ne")); + + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + payload.poll_stream().unwrap(); + + assert_eq!(Some(Bytes::from("line")), payload.read_until(b"ne")); + assert_eq!(payload.buf.len(), 6); + + assert_eq!(Some(Bytes::from("1line2")), payload.read_until(b"2")); + assert_eq!(payload.buf.len(), 0); + }) + } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 9a0a0880..30ee7309 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod form; pub(crate) mod json; -mod multipart; mod path; pub(crate) mod payload; mod query; @@ -10,7 +9,6 @@ pub(crate) mod readlines; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; -pub use self::multipart::{Multipart, MultipartField, MultipartItem}; pub use self::path::Path; pub use self::payload::{Payload, PayloadConfig}; pub use self::query::Query; From dfa0abf5a5e158f44bea043b765cdf7625bfa236 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 12:44:47 -0700 Subject: [PATCH 1215/1635] Export IntoHeaderValue --- actix-http/CHANGES.md | 4 ++++ actix-http/src/header/mod.rs | 22 ++++++++++++++++++++-- src/error.rs | 36 ------------------------------------ 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3ae481db..742ff8d3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,10 @@ ## [0.1.0-alpha.4] - 2019-04-xx +### Changed + +* Export IntoHeaderValue + ### Deleted * Removed PayloadBuffer diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 1ef1bd19..deedd693 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -20,7 +20,6 @@ pub use self::common::*; #[doc(hidden)] pub use self::shared::*; -#[doc(hidden)] /// A trait for any object that will represent a header field and value. pub trait Header where @@ -33,7 +32,6 @@ where fn parse(msg: &T) -> Result; } -#[doc(hidden)] /// A trait for any object that can be Converted to a `HeaderValue` pub trait IntoHeaderValue: Sized { /// The type returned in the event of a conversion error. @@ -97,6 +95,26 @@ impl IntoHeaderValue for String { } } +impl IntoHeaderValue for usize { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + let s = format!("{}", self); + HeaderValue::from_shared(Bytes::from(s)) + } +} + +impl IntoHeaderValue for u64 { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + let s = format!("{}", self); + HeaderValue::from_shared(Bytes::from(s)) + } +} + impl IntoHeaderValue for Mime { type Error = InvalidHeaderValueBytes; diff --git a/src/error.rs b/src/error.rs index 78dc2fb6..74b890f0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -121,36 +121,6 @@ impl ResponseError for ReadlinesError { } } -/// A set of errors that can occur during parsing multipart streams -#[derive(Debug, Display, From)] -pub enum MultipartError { - /// Content-Type header is not found - #[display(fmt = "No Content-type header found")] - NoContentType, - /// Can not parse Content-Type header - #[display(fmt = "Can not parse Content-Type header")] - ParseContentType, - /// Multipart boundary is not found - #[display(fmt = "Multipart boundary is not found")] - Boundary, - /// Multipart stream is incomplete - #[display(fmt = "Multipart stream is incomplete")] - Incomplete, - /// Error during field parsing - #[display(fmt = "{}", _0)] - Parse(ParseError), - /// Payload error - #[display(fmt = "{}", _0)] - Payload(PayloadError), -} - -/// Return `BadRequest` for `MultipartError` -impl ResponseError for MultipartError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - #[cfg(test)] mod tests { use super::*; @@ -180,10 +150,4 @@ mod tests { let resp: HttpResponse = ReadlinesError::EncodingError.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } - - #[test] - fn test_multipart_error() { - let resp: HttpResponse = MultipartError::Boundary.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } } From 237bfba1ed2e7fd1e739093c24ab75fa01a8fac6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 15:09:31 -0700 Subject: [PATCH 1216/1635] add App::configure() - allow to offload app configuration to different methods --- CHANGES.md | 6 +- actix-multipart/README.md | 2 +- src/app.rs | 90 ++++++++++++++++++++++- src/config.rs | 146 +++++++++++++++++++++++++++++++++++++- src/web.rs | 1 + 5 files changed, 241 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fc690ee5..4aaef951 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [1.0.0-alpha.3] - 2019-04-xx +## [1.0.0-alpha.4] - 2019-04-xx + +### Added + +* `App::configure()` allow to offload app configuration to different methods ### Changed diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 2a65840a..2739ff3d 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -1 +1 @@ -# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-session)](https://crates.io/crates/actix-session) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/src/app.rs b/src/app.rs index fd91d072..80256945 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use futures::{IntoFuture, Stream}; use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory}; -use crate::config::{AppConfig, AppConfigInner}; +use crate::config::{AppConfig, AppConfigInner, RouterConfig}; use crate::data::{Data, DataFactory}; use crate::dev::{Payload, PayloadStream, ResourceDef}; use crate::error::{Error, PayloadError}; @@ -257,6 +257,55 @@ where } } + /// Run external configuration as part of the application building + /// process + /// + /// This function is useful for moving parts of configuration to a + /// different module or even library. For example, + /// some of the resource's configuration could be moved to different module. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, middleware, App, HttpResponse}; + /// + /// // this function could be located in different module + /// fn config

    (cfg: &mut web::RouterConfig

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

    (cfg: &mut web::RouterConfig

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

    ), + { + let mut cfg = RouterConfig::new(); + f(&mut cfg); + self.data.extend(cfg.data); + self.services.extend(cfg.services); + self.external.extend(cfg.external); + + self + } + /// Configure route for a specific path. /// /// This is a simplified version of the `App::service()` method. diff --git a/src/config.rs b/src/config.rs index ceb58feb..1e552291 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,11 +5,18 @@ use std::rc::Rc; use actix_http::Extensions; use actix_router::ResourceDef; use actix_service::{boxed, IntoNewService, NewService}; +use futures::IntoFuture; +use crate::data::{Data, DataFactory}; use crate::error::Error; use crate::guard::Guard; +use crate::resource::Resource; use crate::rmap::ResourceMap; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::route::Route; +use crate::service::{ + HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, + ServiceResponse, +}; type Guards = Vec>; type HttpNewService

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

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

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

    + 'static, + { + self.services + .push(Box::new(ServiceFactoryWrapper::new(factory))); + self + } + + /// Register an external resource. + /// + /// External resources are useful for URL generation purposes only + /// and are never considered for matching at request time. Calls to + /// `HttpRequest::url_for()` will work as expected. + /// + /// This is same as `App::external_service()` method. + pub fn external_resource(&mut self, name: N, url: U) -> &mut Self + where + N: AsRef, + U: AsRef, + { + let mut rdef = ResourceDef::new(url.as_ref()); + *rdef.name_mut() = name.as_ref().to_string(); + self.external.push(rdef); + self + } +} + +#[cfg(test)] +mod tests { + use actix_service::Service; + + use super::*; + use crate::http::StatusCode; + use crate::test::{block_on, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[test] + fn test_data() { + let cfg = |cfg: &mut RouterConfig<_>| { + cfg.data(10usize); + }; + + let mut srv = + init_service(App::new().configure(cfg).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_data_factory() { + let cfg = |cfg: &mut RouterConfig<_>| { + cfg.data_factory(|| Ok::<_, ()>(10usize)); + }; + + let mut srv = + init_service(App::new().configure(cfg).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let cfg2 = |cfg: &mut RouterConfig<_>| { + cfg.data_factory(|| Ok::<_, ()>(10u32)); + }; + let mut srv = init_service( + App::new() + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) + .configure(cfg2), + ); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } +} diff --git a/src/web.rs b/src/web.rs index 65b3cfc7..94c98c22 100644 --- a/src/web.rs +++ b/src/web.rs @@ -13,6 +13,7 @@ use crate::responder::Responder; use crate::route::Route; use crate::scope::Scope; +pub use crate::config::RouterConfig; pub use crate::data::{Data, RouteData}; pub use crate::request::HttpRequest; pub use crate::types::*; From cef3dc3586ba64787ca2e4cc395fbd3d7c4478cd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 3 Apr 2019 15:25:52 -0700 Subject: [PATCH 1217/1635] added app_data() method --- CHANGES.md | 5 +++++ src/service.rs | 22 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 4aaef951..7a6e9b9b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,11 @@ * `App::configure()` allow to offload app configuration to different methods +* Added `ServiceRequest::app_data()`, returns `Data` + +* Added `ServiceFromRequest::app_data()`, returns `Data` + + ### Changed * Move multipart support to actix-multipart crate diff --git a/src/service.rs b/src/service.rs index 13aae869..0f11b89e 100644 --- a/src/service.rs +++ b/src/service.rs @@ -13,7 +13,7 @@ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::{AppConfig, ServiceConfig}; -use crate::data::RouteData; +use crate::data::{Data, RouteData}; use crate::request::HttpRequest; use crate::rmap::ResourceMap; @@ -173,6 +173,16 @@ impl

    ServiceRequest

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

    Resource for ServiceRequest

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

    ServiceFromRequest

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

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

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

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

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

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

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

    fmt::Debug for Request

    { writeln!(f, " query: ?{:?}", q)?; } writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { + for (key, val) in self.headers() { writeln!(f, " {:?}: {:?}", key, val)?; } Ok(()) diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index c3fed133..707c9af6 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -6,8 +6,6 @@ use std::{fmt, io, str}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; use serde::Serialize; use serde_json; @@ -16,11 +14,13 @@ use crate::cookie::{Cookie, CookieJar}; use crate::error::Error; use crate::extensions::Extensions; use crate::header::{Header, IntoHeaderValue}; -use crate::message::{ConnectionType, Message, ResponseHead}; +use crate::http::header::{self, HeaderName, HeaderValue}; +use crate::http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode}; +use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; /// An HTTP Response pub struct Response { - head: Message, + head: BoxedResponseHead, body: ResponseBody, error: Option, } @@ -41,7 +41,7 @@ impl Response { /// Constructs a response #[inline] pub fn new(status: StatusCode) -> Response { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; Response { @@ -93,7 +93,7 @@ impl Response { /// Constructs a response with body #[inline] pub fn with_body(status: StatusCode, body: B) -> Response { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; Response { head, @@ -136,7 +136,7 @@ impl Response { #[inline] pub fn cookies(&self) -> CookieIter { CookieIter { - iter: self.head.headers.get_all(header::SET_COOKIE).iter(), + iter: self.head.headers.get_all(header::SET_COOKIE), } } @@ -158,7 +158,6 @@ impl Response { let h = &mut self.head.headers; let vals: Vec = h .get_all(header::SET_COOKIE) - .iter() .map(|v| v.to_owned()) .collect(); h.remove(header::SET_COOKIE); @@ -286,7 +285,7 @@ impl IntoFuture for Response { } pub struct CookieIter<'a> { - iter: header::ValueIter<'a, HeaderValue>, + iter: header::GetAll<'a>, } impl<'a> Iterator for CookieIter<'a> { @@ -320,7 +319,7 @@ impl<'a> io::Write for Writer<'a> { /// This type can be used to construct an instance of `Response` through a /// builder-like pattern. pub struct ResponseBuilder { - head: Option>, + head: Option, err: Option, cookies: Option, } @@ -328,7 +327,7 @@ pub struct ResponseBuilder { impl ResponseBuilder { /// Create response builder pub fn new(status: StatusCode) -> Self { - let mut head: Message = Message::new(); + let mut head = BoxedResponseHead::new(); head.status = status; ResponseBuilder { @@ -701,13 +700,13 @@ impl ResponseBuilder { #[inline] fn parts<'a>( - parts: &'a mut Option>, + parts: &'a mut Option, err: &Option, -) -> Option<&'a mut Message> { +) -> Option<&'a mut ResponseHead> { if err.is_some() { return None; } - parts.as_mut() + parts.as_mut().map(|r| &mut **r) } /// Convert `Response` to a `ResponseBuilder`. Body get dropped. @@ -740,7 +739,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { let mut jar: Option = None; let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE).iter(), + iter: head.headers.get_all(header::SET_COOKIE), }; for c in cookies { if let Some(ref mut j) = jar { @@ -752,11 +751,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } } - let mut msg: Message = Message::new(); + let mut msg = BoxedResponseHead::new(); msg.version = head.version; msg.status = head.status; msg.reason = head.reason; - msg.headers = head.headers.clone(); + // msg.headers = head.headers.clone(); msg.no_chunking(!head.chunked()); ResponseBuilder { @@ -845,7 +844,7 @@ impl From for Response { mod tests { use super::*; use crate::body::Body; - use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; + use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; #[test] fn test_debug() { @@ -876,13 +875,12 @@ mod tests { .max_age(time::Duration::days(1)) .finish(), ) - .del_cookie(&cookies[0]) + .del_cookie(&cookies[1]) .finish(); let mut val: Vec<_> = resp .headers() - .get_all("Set-Cookie") - .iter() + .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) .collect(); val.sort(); @@ -911,9 +909,9 @@ mod tests { let mut iter = r.cookies(); let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("original", "val100")); - let v = iter.next().unwrap(); assert_eq!((v.name(), v.value()), ("cookie3", "val300")); + let v = iter.next().unwrap(); + assert_eq!((v.name(), v.value()), ("original", "val100")); } #[test] @@ -1049,6 +1047,7 @@ mod tests { resp.headers().get(CONTENT_TYPE).unwrap(), HeaderValue::from_static("application/octet-stream") ); + assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().get_ref(), b"test"); } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 02316124..302d75d7 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -4,10 +4,11 @@ use std::str::FromStr; use bytes::Bytes; use http::header::{self, HeaderName, HeaderValue}; -use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; +use http::{HttpTryFrom, Method, Uri, Version}; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::cookie::{Cookie, CookieJar}; +use crate::header::HeaderMap; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; use crate::Request; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c1536af6..39559b08 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -92,7 +92,7 @@ impl Multipart { /// Extract boundary info from headers. fn boundary(headers: &HeaderMap) -> Result { - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { + if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { if let Ok(ct) = content_type.parse::() { if let Some(boundary) = ct.get_param(mime::BOUNDARY) { @@ -334,7 +334,7 @@ impl InnerMultipart { // content type let mut mt = mime::APPLICATION_OCTET_STREAM; - if let Some(content_type) = headers.get(header::CONTENT_TYPE) { + if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { if let Ok(ct) = content_type.parse::() { mt = ct; @@ -427,7 +427,7 @@ impl Field { pub fn content_disposition(&self) -> Option { // RFC 7578: 'Each part MUST contain a Content-Disposition header field // where the disposition type is "form-data".' - if let Some(content_disposition) = self.headers.get(header::CONTENT_DISPOSITION) + if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) { ContentDisposition::from_raw(content_disposition).ok() } else { @@ -480,7 +480,7 @@ impl InnerField { boundary: String, headers: &HeaderMap, ) -> Result { - let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) { + let len = if let Some(len) = headers.get(&header::CONTENT_LENGTH) { if let Ok(s) = len.to_str() { if let Ok(len) = s.parse::() { Some(len) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 64222256..0ef3c916 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -50,7 +50,7 @@ pub fn handshake(req: &HttpRequest) -> Result Result Result, { let mut req = self.request(head.method.clone(), url); - for (key, value) in &head.headers { + for (key, value) in head.headers.iter() { req = req.set_header_if_none(key.clone(), value.clone()); } req @@ -187,7 +187,7 @@ impl Client { Uri: HttpTryFrom, { let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); - for (key, value) in &self.0.headers { + for (key, value) in self.0.headers.iter() { req.head.headers.insert(key.clone(), value.clone()); } req diff --git a/awc/src/request.rs b/awc/src/request.rs index 32ab7d78..09a7eecc 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -396,7 +396,7 @@ impl ClientRequest { if self.default_headers { // set request host header if let Some(host) = self.head.uri.host() { - if !self.head.headers.contains_key(header::HOST) { + if !self.head.headers.contains_key(&header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let _ = match self.head.uri.port_u16() { @@ -414,7 +414,7 @@ impl ClientRequest { } // user agent - if !self.head.headers.contains_key(header::USER_AGENT) { + if !self.head.headers.contains_key(&header::USER_AGENT) { self.head.headers.insert( header::USER_AGENT, HeaderValue::from_static(concat!("awc/", env!("CARGO_PKG_VERSION"))), diff --git a/awc/src/response.rs b/awc/src/response.rs index b6d7bba6..b9b007b8 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -46,7 +46,7 @@ impl HttpMessage for ClientResponse { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); - for hdr in self.headers().get_all(SET_COOKIE) { + for hdr in self.headers().get_all(&SET_COOKIE) { let s = std::str::from_utf8(hdr.as_bytes()) .map_err(CookieParseError::from)?; cookies.push(Cookie::parse_encoded(s)?.into_owned()); @@ -160,7 +160,7 @@ where /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { let mut len = None; - if let Some(l) = res.headers().get(CONTENT_LENGTH) { + if let Some(l) = res.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) @@ -254,7 +254,7 @@ where } let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index a2851898..967820f3 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -236,7 +236,7 @@ impl WebsocketsRequest { let mut slf = if self.default_headers { // set request host header if let Some(host) = self.head.uri.host() { - if !self.head.headers.contains_key(header::HOST) { + if !self.head.headers.contains_key(&header::HOST) { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let _ = match self.head.uri.port_u16() { @@ -324,7 +324,7 @@ impl WebsocketsRequest { return Err(WsClientError::InvalidResponseStatus(head.status)); } // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = head.headers.get(header::UPGRADE) { + let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_ascii_lowercase().contains("websocket") } else { @@ -338,7 +338,7 @@ impl WebsocketsRequest { return Err(WsClientError::InvalidUpgradeHeader); } // Check for "CONNECTION" header - if let Some(conn) = head.headers.get(header::CONNECTION) { + if let Some(conn) = head.headers.get(&header::CONNECTION) { if let Ok(s) = conn.to_str() { if !s.to_ascii_lowercase().contains("upgrade") { log::trace!("Invalid connection header: {}", s); @@ -355,7 +355,7 @@ impl WebsocketsRequest { return Err(WsClientError::MissingConnectionHeader); } - if let Some(hdr_key) = head.headers.get(header::SEC_WEBSOCKET_ACCEPT) { + if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { let encoded = ws::hash_key(key.as_ref()); if hdr_key.as_bytes() != encoded.as_bytes() { log::trace!( diff --git a/src/info.rs b/src/info.rs index 9a97c335..ece17bf0 100644 --- a/src/info.rs +++ b/src/info.rs @@ -33,7 +33,7 @@ impl ConnectionInfo { let peer = None; // load forwarded header - for hdr in req.headers.get_all(header::FORWARDED) { + for hdr in req.headers.get_all(&header::FORWARDED) { if let Ok(val) = hdr.to_str() { for pair in val.split(';') { for el in pair.split(',') { @@ -69,7 +69,7 @@ impl ConnectionInfo { if scheme.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) { if let Ok(h) = h.to_str() { scheme = h.split(',').next().map(|v| v.trim()); @@ -87,14 +87,14 @@ impl ConnectionInfo { if host.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) { if let Ok(h) = h.to_str() { host = h.split(',').next().map(|v| v.trim()); } } if host.is_none() { - if let Some(h) = req.headers.get(header::HOST) { + if let Some(h) = req.headers.get(&header::HOST) { host = h.to_str().ok(); } if host.is_none() { @@ -110,7 +110,7 @@ impl ConnectionInfo { if remote.is_none() { if let Some(h) = req .headers - .get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) + .get(&HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) { if let Ok(h) = h.to_str() { remote = h.split(',').next().map(|v| v.trim()); diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index ed394371..a4b6a460 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -109,7 +109,7 @@ where fn call(&mut self, req: ServiceRequest

    ) -> Self::Future { // negotiate content-encoding - let encoding = if let Some(val) = req.headers().get(ACCEPT_ENCODING) { + let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { AcceptEncoding::parse(enc, self.encoding) } else { diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index f003ac95..27d24b43 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -584,7 +584,7 @@ struct Inner { impl Inner { fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ORIGIN) { + if let Some(hdr) = req.headers().get(&header::ORIGIN) { if let Ok(origin) = hdr.to_str() { return match self.origins { AllOrSome::All => Ok(()), @@ -608,7 +608,7 @@ impl Inner { AllOrSome::All => { if self.send_wildcard { Some(HeaderValue::from_static("*")) - } else if let Some(origin) = req.headers().get(header::ORIGIN) { + } else if let Some(origin) = req.headers().get(&header::ORIGIN) { Some(origin.clone()) } else { None @@ -617,7 +617,7 @@ impl Inner { AllOrSome::Some(ref origins) => { if let Some(origin) = req.headers() - .get(header::ORIGIN) + .get(&header::ORIGIN) .filter(|o| match o.to_str() { Ok(os) => origins.contains(os), _ => false, @@ -632,7 +632,7 @@ impl Inner { } fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> { - if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) { + if let Some(hdr) = req.headers().get(&header::ACCESS_CONTROL_REQUEST_METHOD) { if let Ok(meth) = hdr.to_str() { if let Ok(method) = Method::try_from(meth) { return self @@ -653,7 +653,7 @@ impl Inner { AllOrSome::All => Ok(()), AllOrSome::Some(ref allowed_headers) => { if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS) { if let Ok(headers) = hdr.to_str() { let mut hdrs = HashSet::new(); @@ -720,7 +720,7 @@ where .unwrap(), ) } else if let Some(hdr) = - req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) + req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS) { Some(hdr.clone()) } else { @@ -759,7 +759,7 @@ where .into_body(); Either::A(ok(req.into_response(res))) - } else if req.headers().contains_key(header::ORIGIN) { + } else if req.headers().contains_key(&header::ORIGIN) { // Only check requests with a origin header. if let Err(e) = self.inner.validate_origin(req.head()) { return Either::A(ok(req.error_response(e))); @@ -790,7 +790,7 @@ where } if inner.vary_header { let value = - if let Some(hdr) = res.headers_mut().get(header::VARY) { + if let Some(hdr) = res.headers_mut().get(&header::VARY) { let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); val.extend(hdr.as_bytes()); @@ -893,20 +893,20 @@ mod tests { assert_eq!( &b"*"[..], resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN) .unwrap() .as_bytes() ); assert_eq!( &b"3600"[..], resp.headers() - .get(header::ACCESS_CONTROL_MAX_AGE) + .get(&header::ACCESS_CONTROL_MAX_AGE) .unwrap() .as_bytes() ); let hdr = resp .headers() - .get(header::ACCESS_CONTROL_ALLOW_HEADERS) + .get(&header::ACCESS_CONTROL_ALLOW_HEADERS) .unwrap() .to_str() .unwrap(); diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 72e866db..a2bc6f27 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -131,11 +131,11 @@ where // set response headers for (key, value) in inner.headers.iter() { if !res.headers().contains_key(key) { - res.headers_mut().insert(key, value.clone()); + res.headers_mut().insert(key.clone(), value.clone()); } } // default content-type - if inner.ct && !res.headers().contains_key(CONTENT_TYPE) { + if inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { res.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"), diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index f4b7517d..aaf38138 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -14,6 +14,7 @@ use time; use crate::dev::{BodySize, MessageBody, ResponseBody}; use crate::error::{Error, Result}; +use crate::http::{HeaderName, HttpTryFrom}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -288,8 +289,12 @@ impl Format { if let Some(key) = cap.get(2) { results.push(match cap.get(3).unwrap().as_str() { - "i" => FormatText::RequestHeader(key.as_str().to_owned()), - "o" => FormatText::ResponseHeader(key.as_str().to_owned()), + "i" => FormatText::RequestHeader( + HeaderName::try_from(key.as_str()).unwrap(), + ), + "o" => FormatText::ResponseHeader( + HeaderName::try_from(key.as_str()).unwrap(), + ), "e" => FormatText::EnvironHeader(key.as_str().to_owned()), _ => unreachable!(), }) @@ -332,8 +337,8 @@ pub enum FormatText { TimeMillis, RemoteAddr, UrlPath, - RequestHeader(String), - ResponseHeader(String), + RequestHeader(HeaderName), + ResponseHeader(HeaderName), EnvironHeader(String), } diff --git a/src/request.rs b/src/request.rs index b5ba7412..2eab1ee1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -286,10 +286,10 @@ mod tests { { let cookies = req.cookies().unwrap(); assert_eq!(cookies.len(), 2); - assert_eq!(cookies[0].name(), "cookie1"); - assert_eq!(cookies[0].value(), "value1"); - assert_eq!(cookies[1].name(), "cookie2"); - assert_eq!(cookies[1].value(), "value2"); + assert_eq!(cookies[0].name(), "cookie2"); + assert_eq!(cookies[0].value(), "value2"); + assert_eq!(cookies[1].name(), "cookie1"); + assert_eq!(cookies[1].value(), "value1"); } let cookie = req.cookie("cookie1"); diff --git a/src/types/form.rs b/src/types/form.rs index 812a08e5..b2171e54 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -209,7 +209,7 @@ where }; let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/src/types/json.rs b/src/types/json.rs index c8ed5afd..f7b94a9b 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -297,7 +297,7 @@ where } let mut len = None; - if let Some(l) = req.headers().get(CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/src/types/payload.rs b/src/types/payload.rs index 170b9c62..9cdbd057 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -313,7 +313,7 @@ where /// Create `MessageBody` for request. pub fn new(req: &mut T) -> HttpMessageBody { let mut len = None; - if let Some(l) = req.headers().get(header::CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) From 68d2203dd6382b1dd7a18bced26d3471f0100b8c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 08:17:29 -0700 Subject: [PATCH 1238/1635] run travis with stable rust only --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b1b0769e..20e4c4a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ cache: matrix: include: - - rust: 1.31.0 - rust: stable - rust: beta - rust: nightly-2019-04-02 From ec09d6fbe651083ef85b3391e967179134f84451 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 10:03:38 -0700 Subject: [PATCH 1239/1635] optimize encode headers and body split --- actix-http/src/body.rs | 2 +- actix-http/src/h1/encoder.rs | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 0d015b2e..88b6c492 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -153,7 +153,7 @@ impl MessageBody for Body { if len == 0 { Ok(Async::Ready(None)) } else { - Ok(Async::Ready(Some(bin.split_to(len)))) + Ok(Async::Ready(Some(mem::replace(bin, Bytes::new())))) } } Body::Message(ref mut body) => body.poll_next(), diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 9a81fb2b..8f98fe67 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -9,8 +9,7 @@ use bytes::{BufMut, Bytes, BytesMut}; use crate::body::BodySize; use crate::config::ServiceConfig; -use crate::header::map; -use crate::header::ContentEncoding; +use crate::header::{map, ContentEncoding}; use crate::helpers; use crate::http::header::{ HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, @@ -75,32 +74,31 @@ pub(crate) trait MessageType: Sized { match length { BodySize::Stream => { if chunked { - dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n") + dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n") } else { skip_len = false; - dst.extend_from_slice(b"\r\n"); + dst.put_slice(b"\r\n"); } } BodySize::Empty => { - dst.extend_from_slice(b"\r\ncontent-length: 0\r\n"); + dst.put_slice(b"\r\ncontent-length: 0\r\n"); } BodySize::Sized(len) => helpers::write_content_length(len, dst), BodySize::Sized64(len) => { - dst.extend_from_slice(b"\r\ncontent-length: "); - write!(dst.writer(), "{}", len)?; - dst.extend_from_slice(b"\r\n"); + dst.put_slice(b"\r\ncontent-length: "); + write!(dst.writer(), "{}\r\n", len)?; } - BodySize::None => dst.extend_from_slice(b"\r\n"), + BodySize::None => dst.put_slice(b"\r\n"), } // Connection match ctype { - ConnectionType::Upgrade => dst.extend_from_slice(b"connection: upgrade\r\n"), + ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"), ConnectionType::KeepAlive if version < Version::HTTP_11 => { - dst.extend_from_slice(b"connection: keep-alive\r\n") + dst.put_slice(b"connection: keep-alive\r\n") } ConnectionType::Close if version >= Version::HTTP_11 => { - dst.extend_from_slice(b"connection: close\r\n") + dst.put_slice(b"connection: close\r\n") } _ => (), } @@ -129,7 +127,7 @@ pub(crate) trait MessageType: Sized { dst.advance_mut(pos); } pos = 0; - dst.reserve(len); + dst.reserve(len * 2); remaining = dst.remaining_mut(); unsafe { buf = &mut *(dst.bytes_mut() as *mut _); @@ -154,7 +152,7 @@ pub(crate) trait MessageType: Sized { dst.advance_mut(pos); } pos = 0; - dst.reserve(len); + dst.reserve(len * 2); remaining = dst.remaining_mut(); unsafe { buf = &mut *(dst.bytes_mut() as *mut _); @@ -209,7 +207,7 @@ impl MessageType for Response<()> { // status line helpers::write_status_line(head.version, head.status.as_u16(), dst); - dst.extend_from_slice(reason); + dst.put_slice(reason); Ok(()) } } @@ -228,6 +226,7 @@ impl MessageType for RequestHead { } fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { + dst.reserve(256 + self.headers.len() * AVERAGE_HEADER_SIZE); write!( Writer(dst), "{} {} {}", From 219baf332344e31e0a8ed63beb32aacadf9ce56b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 10:29:26 -0700 Subject: [PATCH 1240/1635] remove PayloadWriter trait --- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/payload.rs | 33 +++++---------------------------- actix-multipart/src/server.rs | 2 +- 4 files changed, 8 insertions(+), 31 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 61c284a9..bff05ab5 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -19,7 +19,7 @@ use crate::request::Request; use crate::response::Response; use super::codec::Codec; -use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; +use super::payload::{Payload, PayloadSender, PayloadStatus}; use super::{Message, MessageType}; const LW_BUFFER_SIZE: usize = 4096; diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index dd29547e..79d7cda8 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -14,7 +14,7 @@ pub use self::client::{ClientCodec, ClientPayloadCodec}; pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; pub use self::expect::ExpectHandler; -pub use self::payload::{Payload, PayloadWriter}; +pub use self::payload::Payload; pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; #[derive(Debug)] diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index bef87f7d..e880d46a 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -97,58 +97,35 @@ impl Stream for Payload { } } -impl Clone for Payload { - fn clone(&self) -> Payload { - Payload { - inner: Rc::clone(&self.inner), - } - } -} - -/// Payload writer interface. -pub trait PayloadWriter { - /// Set stream error. - fn set_error(&mut self, err: PayloadError); - - /// Write eof into a stream which closes reading side of a stream. - fn feed_eof(&mut self); - - /// Feed bytes into a payload stream - fn feed_data(&mut self, data: Bytes); - - /// Need read data - fn need_read(&self) -> PayloadStatus; -} - /// Sender part of the payload stream pub struct PayloadSender { inner: Weak>, } -impl PayloadWriter for PayloadSender { +impl PayloadSender { #[inline] - fn set_error(&mut self, err: PayloadError) { + pub fn set_error(&mut self, err: PayloadError) { if let Some(shared) = self.inner.upgrade() { shared.borrow_mut().set_error(err) } } #[inline] - fn feed_eof(&mut self) { + pub fn feed_eof(&mut self) { if let Some(shared) = self.inner.upgrade() { shared.borrow_mut().feed_eof() } } #[inline] - fn feed_data(&mut self, data: Bytes) { + pub fn feed_data(&mut self, data: Bytes) { if let Some(shared) = self.inner.upgrade() { shared.borrow_mut().feed_data(data) } } #[inline] - fn need_read(&self) -> PayloadStatus { + pub fn need_read(&self) -> PayloadStatus { // we check need_read only if Payload (other side) is alive, // otherwise always return true (consume payload) if let Some(shared) = self.inner.upgrade() { diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 39559b08..2dae5fd3 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -776,7 +776,7 @@ impl PayloadBuffer { #[cfg(test)] mod tests { - use actix_http::h1::{Payload, PayloadWriter}; + use actix_http::h1::Payload; use bytes::Bytes; use futures::unsync::mpsc; From 3c650ca194ffe3fa58652b57325436be1681e744 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 10:40:45 -0700 Subject: [PATCH 1241/1635] remove buffer capacity for payload --- actix-http/Cargo.toml | 1 + actix-http/src/h1/payload.rs | 14 ++------------ actix-http/src/message.rs | 3 ++- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index fe9b62d1..528ab617 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -59,6 +59,7 @@ base64 = "0.10" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" +copyless = "0.1.2" derive_more = "0.14" either = "1.5.2" encoding = "0.2" diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index e880d46a..28acb64b 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -77,14 +77,6 @@ impl Payload { pub fn unread_data(&mut self, data: Bytes) { self.inner.borrow_mut().unread_data(data); } - - #[inline] - /// Set read buffer capacity - /// - /// Default buffer capacity is 32Kb. - pub fn set_read_buffer_capacity(&mut self, cap: usize) { - self.inner.borrow_mut().capacity = cap; - } } impl Stream for Payload { @@ -153,7 +145,6 @@ struct Inner { err: Option, need_read: bool, items: VecDeque, - capacity: usize, task: Option, io_task: Option, } @@ -166,7 +157,6 @@ impl Inner { err: None, items: VecDeque::new(), need_read: true, - capacity: MAX_BUFFER_SIZE, task: None, io_task: None, } @@ -186,7 +176,7 @@ impl Inner { fn feed_data(&mut self, data: Bytes) { self.len += data.len(); self.items.push_back(data); - self.need_read = self.len < self.capacity; + self.need_read = self.len < MAX_BUFFER_SIZE; if let Some(task) = self.task.take() { task.notify() } @@ -200,7 +190,7 @@ impl Inner { fn readany(&mut self) -> Poll, PayloadError> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); - self.need_read = self.len < self.capacity; + self.need_read = self.len < MAX_BUFFER_SIZE; if self.need_read && self.task.is_none() && !self.eof { self.task = Some(current_task()); diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 25bc55ba..e1fb3a11 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -3,6 +3,7 @@ use std::collections::VecDeque; use std::rc::Rc; use bitflags::bitflags; +use copyless::BoxHelper; use crate::extensions::Extensions; use crate::header::HeaderMap; @@ -417,7 +418,7 @@ impl BoxedResponsePool { BoxedResponseHead { head: Some(head) } } else { BoxedResponseHead { - head: Some(Box::new(ResponseHead::default())), + head: Some(Box::alloc().init(ResponseHead::default())), } } } From 75b213a6f04dea816895639c94561d55e63facf7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Apr 2019 14:43:07 -0700 Subject: [PATCH 1242/1635] refactor FromRequest trait --- CHANGES.md | 2 + actix-files/src/lib.rs | 8 +-- actix-multipart/src/extractor.rs | 9 +-- actix-session/src/lib.rs | 18 ++--- src/data.rs | 9 +-- src/extract.rs | 110 ++++++++++++++++++------------- src/handler.rs | 35 +++++----- src/lib.rs | 4 +- src/middleware/identity.rs | 10 ++- src/request.rs | 57 ++++++++++------ src/route.rs | 10 +-- src/service.rs | 92 +------------------------- src/test.rs | 33 ++++++---- src/types/form.rs | 61 ++++++++--------- src/types/json.rs | 66 +++++++++---------- src/types/path.rs | 34 +++++----- src/types/payload.rs | 85 ++++++++++++------------ src/types/query.rs | 7 +- 18 files changed, 298 insertions(+), 352 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b7e0d742..3c619eee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ ### Changed +* `FromRequest` trait refactoring + * Move multipart support to actix-multipart crate diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index e2fa06e1..6820d362 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -10,8 +10,8 @@ use std::{cmp, io}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{IntoNewService, NewService, Service}; use actix_web::dev::{ - HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceFromRequest, - ServiceRequest, ServiceResponse, + HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest, + ServiceResponse, }; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; use actix_web::http::header::DispositionType; @@ -551,8 +551,8 @@ impl

    FromRequest

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

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

    ) -> Self::Future { + PathBufWrp::get_pathbuf(req.match_info().path()) } } diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 18c26c6f..94eb4c30 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -2,10 +2,8 @@ use bytes::Bytes; use futures::Stream; -use actix_web::dev::ServiceFromRequest; use actix_web::error::{Error, PayloadError}; -use actix_web::FromRequest; -use actix_web::HttpMessage; +use actix_web::{dev::Payload, FromRequest, HttpRequest}; use crate::server::Multipart; @@ -50,8 +48,7 @@ where type Future = Result; #[inline] - fn from_request(req: &mut ServiceFromRequest

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

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

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

    FromRequest

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    FromRequest

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

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

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

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

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

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

    ); + type Error = (Error, ServiceRequest

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

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

    ); + type Error = (Error, ServiceRequest

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

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

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

    ); + type Error = (Error, ServiceRequest

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

    FromRequest

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

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

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

    FromRequest

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

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

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

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

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

    )>, { service: T, _t: PhantomData

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

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

    ), + Error = (Error, ServiceRequest

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

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

    ), + Error = (Error, ServiceRequest

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

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

    ), + Error = (Error, ServiceRequest

    ), >, { type Request = ServiceRequest

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

    ServiceRequest

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

    fmt::Debug for ServiceRequest

    { } } -pub struct ServiceFromRequest

    { - req: HttpRequest, - payload: Payload

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

    ServiceFromRequest

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

    , data: Option>) -> Self { - Self { - req: req.req, - payload: req.payload, - data, - } - } - - #[inline] - /// Get reference to inner HttpRequest - pub fn request(&self) -> &HttpRequest { - &self.req - } - - #[inline] - /// Convert this request into a HttpRequest - pub fn into_request(self) -> HttpRequest { - self.req - } - - #[inline] - /// Get match information for this request - pub fn match_info_mut(&mut self) -> &mut Path { - &mut self.req.path - } - - /// Create service response for error - #[inline] - pub fn error_response>(self, err: E) -> ServiceResponse { - ServiceResponse::new(self.req, err.into().into()) - } - - /// Get an application data stored with `App::data()` method during - /// application configuration. - pub fn app_data(&self) -> Option> { - if let Some(st) = self.req.config().extensions().get::>() { - Some(st.clone()) - } else { - None - } - } - - /// Load route data. Route data could be set during - /// route configuration with `Route::data()` method. - pub fn route_data(&self) -> Option<&RouteData> { - if let Some(ref ext) = self.data { - ext.get::>() - } else { - None - } - } -} - -impl

    HttpMessage for ServiceFromRequest

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    (&self, req: &ServiceFromRequest

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

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

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

    HttpMessageBody

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

    ) -> HttpMessageBody

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

    Future for HttpMessageBody

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

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

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

    FilesService

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

    = BoxedService, ServiceResponse, Error>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

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

    Service for AppRouting

    { type Request = ServiceRequest

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

    Service for AppRouting

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

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

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

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

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

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

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

    Service for ResourceService

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

    Service for ResourceService

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

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

    { pub fn new() -> Route

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

    Service for RouteService

    { type Request = ServiceRequest

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

    { T: FromRequest

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

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

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

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

    = BoxedService, ServiceResponse, Error>; type HttpNewService

    = BoxedNewService<(), ServiceRequest

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

    { fn register(self, config: &mut ServiceConfig

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

    { } impl

    ServiceRequest

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

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

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

    ServiceRequest

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

    ServiceRequest

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

    HttpMessage for ServiceRequest

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    = BoxedService, ServiceResponse, Error>; -type HttpNewService

    = - BoxedNewService<(), ServiceRequest

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

    HttpServiceFactory

    for Files

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

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

    { - type Request = ServiceRequest

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

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

    { } } -pub struct FilesService

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

    FilesService

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

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

    FilesService

    { } } -impl

    Service for FilesService

    { - type Request = ServiceRequest

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

    Service for FilesService

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

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

    FromRequest

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

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

    FromRequest

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

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

    (&self, req: &ServiceRequest

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

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

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

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

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

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

    FromRequest

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

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

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

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

    = - BoxedNewService<(), ServiceRequest

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

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

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

    (cfg: &mut web::RouterConfig

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

    , - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - /// Run external configuration as part of the application building - /// process - /// - /// This function is useful for moving parts of configuration to a - /// different module or even library. For example, - /// some of the resource's configuration could be moved to different module. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, middleware, App, HttpResponse}; - /// - /// // this function could be located in different module - /// fn config

    (cfg: &mut web::RouterConfig

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

    ), - { - let mut cfg = RouterConfig::new(); - f(&mut cfg); - self.data.extend(cfg.data); - self.services.extend(cfg.services); - self.external.extend(cfg.external); - - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `App::service()` method. - /// This method can not be could multiple times, in that case - /// multiple resources with one route would be registered for same resource path. - /// - /// ```rust - /// use actix_web::{web, App, HttpResponse}; - /// - /// fn index(data: web::Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .route("/test1", web::get().to(index)) - /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); - /// } - /// ``` - pub fn route(self, path: &str, mut route: Route

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

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

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

    , - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B1: MessageBody, - F: IntoTransform, - { - let endpoint = apply_transform(mw, self.endpoint); - AppRouter { - endpoint, - chain: self.chain, - data: self.data, - services: self.services, - default: self.default, - factory_ref: self.factory_ref, - config: self.config, - external: self.external, - _t: PhantomData, - } - } - - /// Registers middleware, in the form of a closure, that runs during inbound - /// and/or outbound processing in the request lifecycle (request -> response), - /// modifying request/response as necessary, across all requests managed by - /// the *Route*. - /// - /// Use middleware when you need to read or modify *every* request or response in some way. - /// - pub fn wrap_fn( - self, - mw: F, - ) -> AppRouter< - C, - P, - B1, - impl NewService< - Request = ServiceRequest

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

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

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

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

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

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

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

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

    = BoxedService, ServiceResponse, Error>; -type HttpNewService

    = - BoxedNewService<(), ServiceRequest

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

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

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

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

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

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

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

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

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

    { - type Request = ServiceRequest

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

    ; - type Future = AppRoutingFactoryResponse

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

    { } } -type HttpServiceFut

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

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

    { - Future(Option, Option, HttpServiceFut

    ), - Service(ResourceDef, Option, HttpService

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

    Future for AppRoutingFactoryResponse

    { - type Item = AppRouting

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

    Future for AppRoutingFactoryResponse

    { } } -pub struct AppRouting

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

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

    Service for AppRouting

    { - type Request = ServiceRequest

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

    Service for AppRouting

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

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

    Service for AppRouting

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

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

    AppEntry

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

    { - type Request = ServiceRequest

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

    ; - type Future = AppRoutingFactoryResponse

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

    = - boxed::BoxedNewService<(), ServiceRequest

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

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

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

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

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

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

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

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

    , + Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -169,13 +169,13 @@ impl Default for AppConfigInner { /// Part of application configuration could be offloaded /// to set of external methods. This could help with /// modularization of big application configuration. -pub struct RouterConfig { - pub(crate) services: Vec>>, +pub struct RouterConfig { + pub(crate) services: Vec>, pub(crate) data: Vec>, pub(crate) external: Vec, } -impl RouterConfig

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

    { /// Configure route for a specific path. /// /// This is same as `App::route()` method. - pub fn route(&mut self, path: &str, mut route: Route

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

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

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

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

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

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

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

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

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

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

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

    FromRequest

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

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

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

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

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

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

    FromRequest

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

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

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

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

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

    FromRequest

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    (PhantomData

    ); - -impl

    Decompress

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

    NewService for Decompress

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

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

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

    Service for Decompress

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

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

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

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

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

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

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

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

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

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

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

    FromRequest

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

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

    (&self, request: &mut ServiceRequest

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

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

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

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

    (&self, req: &mut ServiceRequest

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

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

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

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

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

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

    FromRequest

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

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

    = BoxedService, ServiceResponse, Error>; -type HttpNewService

    = - BoxedNewService<(), ServiceRequest

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

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

    Resource

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

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

    Resource

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    { - type Request = ServiceRequest

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

    ; - type Future = CreateResourceService

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

    { } } -enum CreateRouteServiceItem

    { - Future(CreateRouteService

    ), - Service(RouteService

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

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

    Future for CreateResourceService

    { - type Item = ResourceService

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

    Future for CreateResourceService

    { } } -pub struct ResourceService

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

    Service for ResourceService

    { - type Request = ServiceRequest

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

    Service for ResourceService

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

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

    Service for ResourceService

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

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

    ResourceEndpoint

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

    { - type Request = ServiceRequest

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

    ; - type Future = CreateResourceService

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

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

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

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

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

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

    { } } -impl

    NewService for Route

    { - type Request = ServiceRequest

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

    ; - type Future = CreateRouteService

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

    NewService for Route

    { } } -type RouteFuture

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

    { - fut: RouteFuture

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

    Future for CreateRouteService

    { - type Item = RouteService

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

    Future for CreateRouteService

    { } } -pub struct RouteService

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

    RouteService

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

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

    RouteService

    { } } -impl

    Service for RouteService

    { - type Request = ServiceRequest

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

    Service for RouteService

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    = BoxedService, ServiceResponse, Error>; -type HttpNewService

    = - BoxedNewService<(), ServiceRequest

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    { - type Request = ServiceRequest

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

    ; - type Future = ScopeFactoryResponse

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

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

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

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

    { - Future(Option, Option, HttpServiceFut

    ), - Service(ResourceDef, Option, HttpService

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

    Future for ScopeFactoryResponse

    { - type Item = ScopeService

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

    Future for ScopeFactoryResponse

    { } } -pub struct ScopeService

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

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

    Service for ScopeService

    { - type Request = ServiceRequest

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

    Service for ScopeService

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

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

    Service for ScopeService

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

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

    ScopeEndpoint

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

    { - type Request = ServiceRequest

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

    ; - type Future = ScopeFactoryResponse

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

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

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

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

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

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

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

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

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

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

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

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

    , + payload: Payload, } -impl

    ServiceRequest

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

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

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

    ServiceRequest

    { } } -impl

    Resource for ServiceRequest

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

    HttpMessage for ServiceRequest

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

    HttpMessage for ServiceRequest

    { } } -impl

    fmt::Debug for ServiceRequest

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

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

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

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

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

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

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

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

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

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

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

    FromRequest

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

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

    FromRequest

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

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

    FromRequest

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

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

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

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

    HttpMessageBody

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

    ) -> HttpMessageBody

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

    Future for HttpMessageBody

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Request

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

    Request

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

    fmt::Debug for Request

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

    fmt::Debug for Request

    { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use http::HttpTryFrom; + + #[test] + fn test_basics() { + let msg = Message::new(); + let mut req = Request::from(msg); + req.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); + + *req.uri_mut() = Uri::try_from("/index.html?q=1").unwrap(); + assert_eq!(req.uri().path(), "/index.html"); + assert_eq!(req.uri().query(), Some("q=1")); + + let s = format!("{:?}", req); + println!("T: {:?}", s); + assert!(s.contains("Request HTTP/1.1 GET:/index.html")); + } +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 718aa7d4..33c18b00 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -17,7 +17,7 @@ use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; use actix_web::middleware::{BodyEncoding, Compress}; -use actix_web::{http, test, web, App, HttpResponse, HttpServer}; +use actix_web::{dev, http, test, web, App, HttpResponse, HttpServer}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -89,6 +89,39 @@ fn test_body_gzip() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] +#[test] +fn test_body_gzip2() { + let mut srv = TestServer::new(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + Response::Ok().body(STR).into_body::() + }))), + ) + }); + + let mut response = srv + .block_on( + srv.get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send(), + ) + .unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.block_on(response.body()).unwrap(); + + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_encoding_override() { From e659e09e29228bad11041089d94e4bad35a57f1d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Apr 2019 11:01:04 -0700 Subject: [PATCH 1307/1635] update tests --- src/extract.rs | 57 +-------------------------------------------- src/types/form.rs | 4 ---- src/types/path.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++ src/types/query.rs | 32 +++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 60 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 9023ea49..6d414fbc 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -265,13 +265,12 @@ tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, #[cfg(test)] mod tests { use actix_http::http::header; - use actix_router::ResourceDef; use bytes::Bytes; use serde_derive::Deserialize; use super::*; use crate::test::{block_on, TestRequest}; - use crate::types::{Form, FormConfig, Path, Query}; + use crate::types::{Form, FormConfig}; #[derive(Deserialize, Debug, PartialEq)] struct Info { @@ -350,58 +349,4 @@ mod tests { block_on(Result::, Error>::from_request(&req, &mut pl)).unwrap(); assert!(r.is_err()); } - - #[derive(Deserialize)] - struct MyStruct { - key: String, - value: String, - } - - #[derive(Deserialize)] - struct Id { - id: String, - } - - #[derive(Deserialize)] - struct Test2 { - key: String, - value: u32, - } - - #[test] - fn test_request_extract() { - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); - - let (req, mut pl) = req.into_parts(); - let s = Path::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - - let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); - - let s = Query::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.id, "test"); - - let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); - - let (req, mut pl) = req.into_parts(); - let s = Path::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - - let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - - let res = Path::>::from_request(&req, &mut pl).unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); - } } diff --git a/src/types/form.rs b/src/types/form.rs index 249f33b3..e8f78c49 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -331,10 +331,6 @@ mod tests { fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { match err { - UrlencodedError::Chunked => match other { - UrlencodedError::Chunked => true, - _ => false, - }, UrlencodedError::Overflow => match other { UrlencodedError::Overflow => true, _ => false, diff --git a/src/types/path.rs b/src/types/path.rs index 13a35d5e..5f0a05af 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -171,10 +171,25 @@ where #[cfg(test)] mod tests { use actix_router::ResourceDef; + use derive_more::Display; + use serde_derive::Deserialize; use super::*; use crate::test::{block_on, TestRequest}; + #[derive(Deserialize, Debug, Display)] + #[display(fmt = "MyStruct({}, {})", key, value)] + struct MyStruct { + key: String, + value: String, + } + + #[derive(Deserialize)] + struct Test2 { + key: String, + value: u32, + } + #[test] fn test_extract_path_single() { let resource = ResourceDef::new("/{value}/"); @@ -184,6 +199,7 @@ mod tests { let (req, mut pl) = req.into_parts(); assert_eq!(*Path::::from_request(&req, &mut pl).unwrap(), 32); + assert!(Path::::from_request(&req, &mut pl).is_err()); } #[test] @@ -213,4 +229,46 @@ mod tests { let () = <()>::from_request(&req, &mut pl).unwrap(); } + #[test] + fn test_request_extract() { + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); + + let (req, mut pl) = req.into_parts(); + let mut s = Path::::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); + s.value = "user2".to_string(); + assert_eq!(s.value, "user2"); + assert_eq!( + format!("{}, {:?}", s, s), + "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" + ); + let s = s.into_inner(); + assert_eq!(s.value, "user2"); + + let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); + + let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); + + let (req, mut pl) = req.into_parts(); + let s = Path::::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.as_ref().key, "name"); + assert_eq!(s.value, 32); + + let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); + + let res = Path::>::from_request(&req, &mut pl).unwrap(); + assert_eq!(res[0], "name".to_owned()); + assert_eq!(res[1], "32".to_owned()); + } + } diff --git a/src/types/query.rs b/src/types/query.rs index 596254be..f9f545d6 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -133,3 +133,35 @@ where }) } } + +#[cfg(test)] +mod tests { + use derive_more::Display; + use serde_derive::Deserialize; + + use super::*; + use crate::test::TestRequest; + + #[derive(Deserialize, Debug, Display)] + struct Id { + id: String, + } + + #[test] + fn test_request_extract() { + let req = TestRequest::with_uri("/name/user1/").to_srv_request(); + let (req, mut pl) = req.into_parts(); + assert!(Query::::from_request(&req, &mut pl).is_err()); + + let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + let (req, mut pl) = req.into_parts(); + + let mut s = Query::::from_request(&req, &mut pl).unwrap(); + assert_eq!(s.id, "test"); + assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + + s.id = "test1".to_string(); + let s = s.into_inner(); + assert_eq!(s.id, "test1"); + } +} From 75e340137d48a0564f440e3d522c7a9806623316 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Apr 2019 12:23:56 -0700 Subject: [PATCH 1308/1635] use local version of http-test --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 535bcb82..229cb6dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,7 @@ codegen-units = 1 [patch.crates-io] actix-web = { path = "." } actix-http = { path = "actix-http" } +actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } actix-web-actors = { path = "actix-web-actors" } actix-session = { path = "actix-session" } From da86b6e062655e9aa228965985edbde612f0304d Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 18 Apr 2019 18:06:32 -0400 Subject: [PATCH 1309/1635] added put and patch to TestRequest, docs, and test --- src/test.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/test.rs b/src/test.rs index ad40a032..a8eed388 100644 --- a/src/test.rs +++ b/src/test.rs @@ -335,6 +335,16 @@ impl TestRequest { pub fn post() -> TestRequest { TestRequest::default().method(Method::POST) } + + /// Create TestRequest and set method to `Method::PUT` + pub fn put() -> TestRequest { + TestRequest::default().method(Method::PUT) + } + + /// Create TestRequest and set method to `Method::PATCH` + pub fn patch() -> TestRequest { + TestRequest::default().method(Method::PATCH) + } /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { @@ -493,6 +503,34 @@ mod tests { assert_eq!(*data.get_ref(), 10); } + #[test] + fn test_request_methods() { + let mut app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::put().to(|| HttpResponse::Ok().body("put!"))) + .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))), + ), + ); + + let put_req = TestRequest::put() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); + + let result = read_response(&mut app, put_req); + assert_eq!(result, Bytes::from_static(b"put!")); + + + let patch_req = TestRequest::patch() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); + + let result = read_response(&mut app, patch_req); + assert_eq!(result, Bytes::from_static(b"patch!")); + } + #[test] fn test_response() { let mut app = init_service( From aa255298ef12c7642dbed6a31fc64d287eb14245 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Apr 2019 16:03:13 -0700 Subject: [PATCH 1310/1635] make ServiceRequest::from_parts private, as it is not safe to create from parts --- CHANGES.md | 2 ++ actix-files/src/lib.rs | 32 ++++++++++++++++---------------- src/service.rs | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 00518764..c37cb51c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,8 @@ * Rename `test::call_success` to `test::call_service` +* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. + ### Fixed * Fixed `TestRequest::app_data()` diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 8ff6b932..4038a548 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -411,17 +411,16 @@ impl FilesService { fn handle_err( &mut self, e: io::Error, - req: HttpRequest, - payload: Payload, + req: ServiceRequest, ) -> Either< FutureResult, Box>, > { log::debug!("Files: Failed to handle {}: {}", req.path(), e); if let Some(ref mut default) = self.default { - default.call(ServiceRequest::from_parts(req, payload)) + default.call(req) } else { - Either::A(ok(ServiceResponse::from_err(e, req.clone()))) + Either::A(ok(req.error_response(e))) } } } @@ -440,17 +439,17 @@ impl Service for FilesService { } fn call(&mut self, req: ServiceRequest) -> Self::Future { - let (req, pl) = req.into_parts(); + // let (req, pl) = req.into_parts(); let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { Ok(item) => item, - Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))), + Err(e) => return Either::A(ok(req.error_response(e))), }; // full filepath let path = match self.directory.join(&real_path.0).canonicalize() { Ok(path) => path, - Err(e) => return self.handle_err(e, req, pl), + Err(e) => return self.handle_err(e, req), }; if path.is_dir() { @@ -466,24 +465,26 @@ impl Service for FilesService { } named_file.flags = self.file_flags; + let (req, _) = req.into_parts(); Either::A(ok(match named_file.respond_to(&req) { - Ok(item) => ServiceResponse::new(req.clone(), item), - Err(e) => ServiceResponse::from_err(e, req.clone()), + Ok(item) => ServiceResponse::new(req, item), + Err(e) => ServiceResponse::from_err(e, req), })) } - Err(e) => return self.handle_err(e, req, pl), + Err(e) => return self.handle_err(e, req), } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); + let (req, _) = req.into_parts(); let x = (self.renderer)(&dir, &req); match x { Ok(resp) => Either::A(ok(resp)), - Err(e) => return self.handle_err(e, req, pl), + Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req))), } } else { Either::A(ok(ServiceResponse::from_err( FilesError::IsDirectory, - req.clone(), + req.into_parts().0, ))) } } else { @@ -496,16 +497,15 @@ impl Service for FilesService { } named_file.flags = self.file_flags; + let (req, _) = req.into_parts(); match named_file.respond_to(&req) { Ok(item) => { Either::A(ok(ServiceResponse::new(req.clone(), item))) } - Err(e) => { - Either::A(ok(ServiceResponse::from_err(e, req.clone()))) - } + Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))), } } - Err(e) => self.handle_err(e, req, pl), + Err(e) => self.handle_err(e, req), } } } diff --git a/src/service.rs b/src/service.rs index 5303436c..396daab4 100644 --- a/src/service.rs +++ b/src/service.rs @@ -53,7 +53,7 @@ pub struct ServiceRequest { impl ServiceRequest { /// Construct service request from parts - pub fn from_parts(req: HttpRequest, payload: Payload) -> Self { + pub(crate) fn from_parts(req: HttpRequest, payload: Payload) -> Self { ServiceRequest { req, payload } } From bfe0df5ab0b8a25db7ae2dc004ba133c9cee15a8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Apr 2019 21:28:23 -0700 Subject: [PATCH 1311/1635] update tests --- actix-framed/tests/test_server.rs | 7 +++- awc/src/request.rs | 12 +++++++ awc/src/ws.rs | 53 +++++++++++++++++++------------ awc/tests/test_client.rs | 33 +++++++++++++++++++ 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs index 964403e1..00f6a97d 100644 --- a/actix-framed/tests/test_server.rs +++ b/actix-framed/tests/test_server.rs @@ -1,5 +1,5 @@ use actix_codec::{AsyncRead, AsyncWrite}; -use actix_http::{body, ws, Error, HttpService, Response}; +use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response}; use actix_http_test::TestServer; use actix_service::{IntoNewService, NewService}; use actix_utils::framed::FramedTransport; @@ -99,6 +99,11 @@ fn test_service() { ) }); + // non ws request + let res = srv.block_on(srv.get("/index.html").send()).unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + + // not found assert!(srv.ws_at("/test").is_err()); // client service diff --git a/awc/src/request.rs b/awc/src/request.rs index c868d052..a280dfce 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -566,9 +566,21 @@ mod tests { .version(Version::HTTP_2) .set(header::Date(SystemTime::now().into())) .content_type("plain/text") + .if_true(true, |req| req.header(header::SERVER, "awc")) + .if_true(false, |req| req.header(header::EXPECT, "awc")) + .if_some(Some("server"), |val, req| { + req.header(header::USER_AGENT, val) + }) + .if_some(Option::<&str>::None, |_, req| { + req.header(header::ALLOW, "1") + }) .content_length(100); assert!(req.headers().contains_key(header::CONTENT_TYPE)); assert!(req.headers().contains_key(header::DATE)); + assert!(req.headers().contains_key(header::SERVER)); + assert!(req.headers().contains_key(header::USER_AGENT)); + assert!(!req.headers().contains_key(header::ALLOW)); + assert!(!req.headers().contains_key(header::EXPECT)); assert_eq!(req.head.version, Version::HTTP_2); let _ = req.headers_mut(); let _ = req.send_body(""); diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 1ab6d563..028330ab 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -445,30 +445,41 @@ mod tests { .unwrap(), "Bearer someS3cr3tAutht0k3n" ); + let _ = req.connect(); } #[test] fn basics() { - let req = Client::new() - .ws("/") - .origin("test-origin") - .max_frame_size(100) - .server_mode() - .protocols(&["v1", "v2"]) - .set_header_if_none(header::CONTENT_TYPE, "json") - .set_header_if_none(header::CONTENT_TYPE, "text") - .cookie(Cookie::build("cookie1", "value1").finish()); - assert_eq!( - req.origin.as_ref().unwrap().to_str().unwrap(), - "test-origin" - ); - assert_eq!(req.max_size, 100); - assert_eq!(req.server_mode, true); - assert_eq!(req.protocols, Some("v1,v2".to_string())); - assert_eq!( - req.head.headers.get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("json") - ); - let _ = req.connect(); + actix_http_test::run_on(|| { + let req = Client::new() + .ws("http://localhost/") + .origin("test-origin") + .max_frame_size(100) + .server_mode() + .protocols(&["v1", "v2"]) + .set_header_if_none(header::CONTENT_TYPE, "json") + .set_header_if_none(header::CONTENT_TYPE, "text") + .cookie(Cookie::build("cookie1", "value1").finish()); + assert_eq!( + req.origin.as_ref().unwrap().to_str().unwrap(), + "test-origin" + ); + assert_eq!(req.max_size, 100); + assert_eq!(req.server_mode, true); + assert_eq!(req.protocols, Some("v1,v2".to_string())); + assert_eq!( + req.head.headers.get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("json") + ); + let _ = req.connect(); + }); + + assert!(Client::new().ws("/").connect().poll().is_err()); + assert!(Client::new().ws("http:///test").connect().poll().is_err()); + assert!(Client::new() + .ws("hmm://test.com/") + .connect() + .poll() + .is_err()); } } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 0f0652c4..afccdff8 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::io::{Read, Write}; use std::time::Duration; @@ -63,6 +64,38 @@ fn test_simple() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } +#[test] +fn test_json() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service( + web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), + )) + }); + + let request = srv + .get("/") + .header("x-test", "111") + .send_json(&"TEST".to_string()); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); +} + +#[test] +fn test_form() { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |_: web::Form>| HttpResponse::Ok(), + )))) + }); + + let mut data = HashMap::new(); + let _ = data.insert("key".to_string(), "TEST".to_string()); + + let request = srv.get("/").header("x-test", "111").send_form(&data); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); +} + #[test] fn test_timeout() { let mut srv = TestServer::new(|| { From 1e7f97a111c7fb0b047253bd4b0882d2fd471fb8 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 19 Apr 2019 23:53:49 +0300 Subject: [PATCH 1312/1635] Add Normalization middleware for in place (#783) --- src/middleware/mod.rs | 1 + src/middleware/normalize.rs | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/middleware/normalize.rs diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 59d467c0..3df92625 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -6,6 +6,7 @@ pub mod cors; mod defaultheaders; pub mod errhandlers; mod logger; +pub mod normalize; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs new file mode 100644 index 00000000..4b0afe7a --- /dev/null +++ b/src/middleware/normalize.rs @@ -0,0 +1,109 @@ +//! `Middleware` to normalize request's URI + +use regex::Regex; +use actix_service::{Service, Transform}; +use futures::future::{self, FutureResult}; + +use crate::service::{ServiceRequest, ServiceResponse}; + +#[derive(Default, Clone, Copy)] +/// `Middleware` to normalize request's URI in place +/// +/// Performs following: +/// +/// - Merges multiple slashes into one. +pub struct NormalizePath; + +impl Transform for NormalizePath +where + S: Service, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type InitError = (); + type Transform = NormalizePathNormalization; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + future::ok(NormalizePathNormalization { + service, + merge_slash: Regex::new("//+").unwrap() + }) + } +} + +pub struct NormalizePathNormalization { + service: S, + merge_slash: Regex, +} + +impl Service for NormalizePathNormalization +where + S: Service, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { + let head = req.head_mut(); + + let path = head.uri.path(); + let original_len = path.len(); + let path = self.merge_slash.replace_all(path, "/"); + + if original_len != path.len() { + head.uri = path.parse().unwrap(); + } + + self.service.call(req) + } +} + +#[cfg(test)] +mod tests { + use actix_service::FnService; + + use super::*; + use crate::dev::ServiceRequest; + use crate::http::header::CONTENT_TYPE; + use crate::test::{block_on, TestRequest}; + use crate::HttpResponse; + + #[test] + fn test_in_place_normalization() { + let srv = FnService::new(|req: ServiceRequest| { + assert_eq!("/v1/something/", req.path()); + req.into_response(HttpResponse::Ok().finish()) + }); + + let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap(); + + let req = TestRequest::with_uri("/v1//something////").to_srv_request(); + let res = block_on(normalize.call(req)).unwrap(); + assert!(res.status().is_success()); + } + + #[test] + fn should_normalize_nothing() { + const URI: &str = "/v1/something/"; + + let srv = FnService::new(|req: ServiceRequest| { + assert_eq!(URI, req.path()); + req.into_response(HttpResponse::Ok().finish()) + }); + + let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap(); + + let req = TestRequest::with_uri(URI).to_srv_request(); + let res = block_on(normalize.call(req)).unwrap(); + assert!(res.status().is_success()); + } + +} From 791f22bbc82c3fe809238ea32ad64e4385993783 Mon Sep 17 00:00:00 2001 From: Kilerd Chan Date: Sat, 20 Apr 2019 04:54:44 +0800 Subject: [PATCH 1313/1635] replate `time::Duration` with `chrono::Duration` and add `max_age_time` method (#789) * feat: replate time::Duration with chrono::Duration * feat: rename max_age method which accepts `Duration` to max_age_time and add new max_age method accepting isize of seconds * feat: replace `time:Duration` with `chrono:Duration` in repo `actix-http` --- Cargo.toml | 7 ++-- actix-http/Cargo.toml | 1 + actix-http/src/cookie/builder.rs | 7 ++-- actix-http/src/cookie/jar.rs | 8 ++-- actix-http/src/cookie/mod.rs | 10 +++-- actix-http/src/cookie/parse.rs | 5 ++- src/middleware/identity.rs | 63 ++++++++++++++++++++++++++++++-- 7 files changed, 82 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 229cb6dc..911accbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" +[package.metadata.docs.rs] +features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] + [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } codecov = { repository = "actix/actix-web", branch = "master", service = "github" } @@ -37,9 +40,6 @@ members = [ "test-server", ] -[package.metadata.docs.rs] -features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] - [features] default = ["brotli", "flate2-zlib", "secure-cookies", "client"] @@ -96,6 +96,7 @@ url = { version="1.7", features=["query_encoding"] } # ssl support openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } +chrono = "0.4.6" [dev-dependencies] actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 575a08a0..d5a65b7b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -93,6 +93,7 @@ flate2 = { version="1.0.7", optional = true, default-features = false } # optional deps failure = { version = "0.1.5", optional = true } openssl = { version="0.10", optional = true } +chrono = "0.4.6" [dev-dependencies] actix-rt = "0.2.2" diff --git a/actix-http/src/cookie/builder.rs b/actix-http/src/cookie/builder.rs index 2635572a..71c9bd19 100644 --- a/actix-http/src/cookie/builder.rs +++ b/actix-http/src/cookie/builder.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; -use time::{Duration, Tm}; +use time::Tm; +use chrono::Duration; use super::{Cookie, SameSite}; @@ -16,7 +17,7 @@ use super::{Cookie, SameSite}; /// /// ```rust /// use actix_http::cookie::Cookie; -/// use time::Duration; +/// use chrono::Duration; /// /// # fn main() { /// let cookie: Cookie = Cookie::build("name", "value") @@ -200,7 +201,7 @@ impl CookieBuilder { /// /// ```rust /// use actix_http::cookie::Cookie; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let c = Cookie::build("foo", "bar") diff --git a/actix-http/src/cookie/jar.rs b/actix-http/src/cookie/jar.rs index b60d73fe..8b67c37d 100644 --- a/actix-http/src/cookie/jar.rs +++ b/actix-http/src/cookie/jar.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::mem::replace; -use time::{self, Duration}; +use chrono::Duration; use super::delta::DeltaCookie; use super::Cookie; @@ -188,7 +188,7 @@ impl CookieJar { /// /// ```rust /// use actix_http::cookie::{CookieJar, Cookie}; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let mut jar = CookieJar::new(); @@ -241,7 +241,7 @@ impl CookieJar { /// /// ```rust /// use actix_http::cookie::{CookieJar, Cookie}; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let mut jar = CookieJar::new(); @@ -538,7 +538,7 @@ mod test { #[cfg(feature = "secure-cookies")] fn delta() { use std::collections::HashMap; - use time::Duration; + use chrono::Duration; let mut c = CookieJar::new(); diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index 0f5f4548..eef41a12 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -66,7 +66,8 @@ use std::fmt; use std::str::FromStr; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; -use time::{Duration, Tm}; +use time::Tm; +use chrono::Duration; pub use self::builder::CookieBuilder; pub use self::draft::*; @@ -624,7 +625,7 @@ impl<'c> Cookie<'c> { /// /// ```rust /// use actix_http::cookie::Cookie; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let mut c = Cookie::new("name", "value"); @@ -703,7 +704,7 @@ impl<'c> Cookie<'c> { /// /// ```rust /// use actix_http::cookie::Cookie; - /// use time::Duration; + /// use chrono::Duration; /// /// # fn main() { /// let mut c = Cookie::new("foo", "bar"); @@ -977,7 +978,8 @@ impl<'a, 'b> PartialEq> for Cookie<'a> { #[cfg(test)] mod tests { use super::{Cookie, SameSite}; - use time::{strptime, Duration}; + use time::strptime; + use chrono::Duration; #[test] fn format() { diff --git a/actix-http/src/cookie/parse.rs b/actix-http/src/cookie/parse.rs index ebbd6ffc..cc042733 100644 --- a/actix-http/src/cookie/parse.rs +++ b/actix-http/src/cookie/parse.rs @@ -6,7 +6,7 @@ use std::fmt; use std::str::Utf8Error; use percent_encoding::percent_decode; -use time::{self, Duration}; +use chrono::Duration; use super::{Cookie, CookieStr, SameSite}; @@ -220,7 +220,8 @@ where #[cfg(test)] mod tests { use super::{Cookie, SameSite}; - use time::{strptime, Duration}; + use time::strptime; + use chrono::Duration; macro_rules! assert_eq_parse { ($string:expr, $expected:expr) => { diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 6027aaa7..bf739636 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -53,7 +53,7 @@ use std::rc::Rc; use actix_service::{Service, Transform}; use futures::future::{ok, Either, FutureResult}; use futures::{Future, IntoFuture, Poll}; -use time::Duration; +use chrono::Duration; use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::error::{Error, Result}; @@ -427,11 +427,16 @@ impl CookieIdentityPolicy { self } - /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy { + /// Sets the `max-age` field in the session cookie being built with `chrono::Duration`. + pub fn max_age_time(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); self } + /// Sets the `max-age` field in the session cookie being built with given number of seconds. + pub fn max_age(mut self, seconds: isize) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().max_age = Some(Duration::seconds(seconds as i64)); + self + } /// Sets the `same_site` field in the session cookie being built. pub fn same_site(mut self, same_site: SameSite) -> Self { @@ -525,4 +530,56 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); assert!(resp.headers().contains_key(header::SET_COOKIE)) } + + #[test] + fn test_identity_max_age_time() { + let duration = Duration::days(1); + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&[0; 32]) + .domain("www.rust-lang.org") + .name("actix_auth") + .path("/") + .max_age_time(duration) + .secure(true), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })) + ); + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + let c = resp.response().cookies().next().unwrap().to_owned(); + assert_eq!(duration, c.max_age().unwrap()); + } + + #[test] + fn test_identity_max_age() { + let seconds = 60isize; + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&[0; 32]) + .domain("www.rust-lang.org") + .name("actix_auth") + .path("/") + .max_age(seconds) + .secure(true), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })) + ); + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + let c = resp.response().cookies().next().unwrap().to_owned(); + assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); + } } From a3844c1bfd031c763b239de5e3ce6e337dee4b5a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 13:55:36 -0700 Subject: [PATCH 1314/1635] update version --- Cargo.toml | 2 +- MIGRATION.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 911accbe..8003956f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-alpha.6" +version = "1.0.0-beta.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" diff --git a/MIGRATION.md b/MIGRATION.md index 5953452d..9d433ed0 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -128,7 +128,7 @@ ``` -* AsyncResponder is removed. +* AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. instead of From 7292d0b6961a9f1ea6f47472586b968b3b28b382 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 17:23:17 -0700 Subject: [PATCH 1315/1635] drop chrono and use i64 for max age --- CHANGES.md | 2 ++ Cargo.toml | 3 +-- actix-http/CHANGES.md | 9 +++++++++ actix-http/Cargo.toml | 2 +- actix-http/src/cookie/builder.rs | 29 ++++++++++++++++++++++++----- actix-http/src/cookie/jar.rs | 2 +- actix-http/src/cookie/mod.rs | 7 ++----- actix-http/src/cookie/parse.rs | 8 +++----- actix-http/src/response.rs | 2 +- actix-session/CHANGES.md | 3 +++ actix-session/Cargo.toml | 2 +- actix-session/src/cookie.rs | 14 +++++++++----- src/middleware/identity.rs | 18 +++++++++--------- src/middleware/normalize.rs | 5 ++--- src/test.rs | 5 ++--- 15 files changed, 70 insertions(+), 41 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c37cb51c..573d287c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,8 @@ * Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. +* `CookieIdentityPolicy::max_age()` accepts value in seconds + ### Fixed * Fixed `TestRequest::app_data()` diff --git a/Cargo.toml b/Cargo.toml index 8003956f..cfc3d59f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,13 +90,12 @@ regex = "1.0" serde = { version = "1.0", features=["derive"] } serde_json = "1.0" serde_urlencoded = "0.5.3" -time = "0.1" +time = "0.1.42" url = { version="1.7", features=["query_encoding"] } # ssl support openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } -chrono = "0.4.6" [dev-dependencies] actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3f2ccd4e..a82b864b 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,14 @@ # Changes +## [0.1.1] - 2019-04-19 + +### Changes + +* Cookie::max_age() accepts value in seconds + +* Cookie::max_age_time() accepts value in time::Duration + + ## [0.1.0] - 2019-04-16 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d5a65b7b..b94e12d8 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -77,7 +77,7 @@ serde_json = "1.0" sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.5.5" -time = "0.1" +time = "0.1.42" tokio-tcp = "0.1.3" tokio-timer = "0.2.8" tokio-current-thread = "0.1" diff --git a/actix-http/src/cookie/builder.rs b/actix-http/src/cookie/builder.rs index 71c9bd19..efeddbb6 100644 --- a/actix-http/src/cookie/builder.rs +++ b/actix-http/src/cookie/builder.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; -use time::Tm; use chrono::Duration; +use time::Tm; use super::{Cookie, SameSite}; @@ -17,7 +17,6 @@ use super::{Cookie, SameSite}; /// /// ```rust /// use actix_http::cookie::Cookie; -/// use chrono::Duration; /// /// # fn main() { /// let cookie: Cookie = Cookie::build("name", "value") @@ -25,7 +24,7 @@ use super::{Cookie, SameSite}; /// .path("/") /// .secure(true) /// .http_only(true) -/// .max_age(Duration::days(1)) +/// .max_age(84600) /// .finish(); /// # } /// ``` @@ -80,6 +79,26 @@ impl CookieBuilder { self } + /// Sets the `max_age` field in seconds in the cookie being built. + /// + /// # Example + /// + /// ```rust + /// use actix_http::cookie::Cookie; + /// + /// # fn main() { + /// let c = Cookie::build("foo", "bar") + /// .max_age(1800) + /// .finish(); + /// + /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); + /// # } + /// ``` + #[inline] + pub fn max_age(self, seconds: i64) -> CookieBuilder { + self.max_age_time(Duration::seconds(seconds)) + } + /// Sets the `max_age` field in the cookie being built. /// /// # Example @@ -89,14 +108,14 @@ impl CookieBuilder { /// /// # fn main() { /// let c = Cookie::build("foo", "bar") - /// .max_age(time::Duration::minutes(30)) + /// .max_age_time(time::Duration::minutes(30)) /// .finish(); /// /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); /// # } /// ``` #[inline] - pub fn max_age(mut self, value: Duration) -> CookieBuilder { + pub fn max_age_time(mut self, value: Duration) -> CookieBuilder { self.cookie.set_max_age(value); self } diff --git a/actix-http/src/cookie/jar.rs b/actix-http/src/cookie/jar.rs index 8b67c37d..cc67536c 100644 --- a/actix-http/src/cookie/jar.rs +++ b/actix-http/src/cookie/jar.rs @@ -537,8 +537,8 @@ mod test { #[test] #[cfg(feature = "secure-cookies")] fn delta() { - use std::collections::HashMap; use chrono::Duration; + use std::collections::HashMap; let mut c = CookieJar::new(); diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index eef41a12..ddcb12bb 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -65,9 +65,9 @@ use std::borrow::Cow; use std::fmt; use std::str::FromStr; +use chrono::Duration; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use time::Tm; -use chrono::Duration; pub use self::builder::CookieBuilder; pub use self::draft::*; @@ -979,7 +979,6 @@ impl<'a, 'b> PartialEq> for Cookie<'a> { mod tests { use super::{Cookie, SameSite}; use time::strptime; - use chrono::Duration; #[test] fn format() { @@ -989,9 +988,7 @@ mod tests { let cookie = Cookie::build("foo", "bar").http_only(true).finish(); assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly"); - let cookie = Cookie::build("foo", "bar") - .max_age(Duration::seconds(10)) - .finish(); + let cookie = Cookie::build("foo", "bar").max_age(10).finish(); assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10"); let cookie = Cookie::build("foo", "bar").secure(true).finish(); diff --git a/actix-http/src/cookie/parse.rs b/actix-http/src/cookie/parse.rs index cc042733..42a2c1fc 100644 --- a/actix-http/src/cookie/parse.rs +++ b/actix-http/src/cookie/parse.rs @@ -5,8 +5,8 @@ use std::error::Error; use std::fmt; use std::str::Utf8Error; -use percent_encoding::percent_decode; use chrono::Duration; +use percent_encoding::percent_decode; use super::{Cookie, CookieStr, SameSite}; @@ -220,8 +220,8 @@ where #[cfg(test)] mod tests { use super::{Cookie, SameSite}; - use time::strptime; use chrono::Duration; + use time::strptime; macro_rules! assert_eq_parse { ($string:expr, $expected:expr) => { @@ -419,9 +419,7 @@ mod tests { #[test] fn do_not_panic_on_large_max_ages() { let max_seconds = Duration::max_value().num_seconds(); - let expected = Cookie::build("foo", "bar") - .max_age(Duration::seconds(max_seconds)) - .finish(); + let expected = Cookie::build("foo", "bar").max_age(max_seconds).finish(); assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected); } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 6125ae1c..fd51e54c 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -860,7 +860,7 @@ mod tests { .domain("www.rust-lang.org") .path("/test") .http_only(true) - .max_age(time::Duration::days(1)) + .max_age_time(time::Duration::days(1)) .finish(), ) .del_cookie(&cookies[1]) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 54ea66d9..dfa3033c 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,8 @@ # Changes + +* `CookieSession::max_age()` accepts value in seconds + ## [0.1.0-alpha.6] - 2019-04-14 * Update actix-web alpha.6 diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 058fc706..e21bb732 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -32,7 +32,7 @@ futures = "0.1.25" hashbrown = "0.2.0" serde = "1.0" serde_json = "1.0" -time = "0.1" +time = "0.1.42" [dev-dependencies] actix-rt = "0.2.2" diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 4f7614dc..ac08d114 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -17,7 +17,6 @@ use std::collections::HashMap; use std::rc::Rc; -use std::time::Duration; use actix_service::{Service, Transform}; use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; @@ -57,7 +56,7 @@ struct CookieSessionInner { domain: Option, secure: bool, http_only: bool, - max_age: Option, + max_age: Option, same_site: Option, } @@ -98,7 +97,7 @@ impl CookieSessionInner { } if let Some(max_age) = self.max_age { - cookie.set_max_age(time::Duration::from_std(max_age).unwrap()); + cookie.set_max_age(max_age); } if let Some(same_site) = self.same_site { @@ -250,7 +249,12 @@ impl CookieSession { } /// Sets the `max-age` field in the session cookie being built. - pub fn max_age(mut self, value: Duration) -> CookieSession { + pub fn max_age(self, seconds: i64) -> CookieSession { + self.max_age_time(time::Duration::seconds(seconds)) + } + + /// Sets the `max-age` field in the session cookie being built. + pub fn max_age_time(mut self, value: time::Duration) -> CookieSession { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); self } @@ -390,7 +394,7 @@ mod tests { .domain("localhost") .http_only(true) .same_site(SameSite::Lax) - .max_age(Duration::from_secs(100)), + .max_age(100), ) .service(web::resource("/").to(|ses: Session| { let _ = ses.set("counter", 100); diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index bf739636..ba03366f 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -53,7 +53,7 @@ use std::rc::Rc; use actix_service::{Service, Transform}; use futures::future::{ok, Either, FutureResult}; use futures::{Future, IntoFuture, Poll}; -use chrono::Duration; +use time::Duration; use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::error::{Error, Result}; @@ -427,16 +427,16 @@ impl CookieIdentityPolicy { self } + /// Sets the `max-age` field in the session cookie being built with given number of seconds. + pub fn max_age(self, seconds: i64) -> CookieIdentityPolicy { + self.max_age_time(Duration::seconds(seconds)) + } + /// Sets the `max-age` field in the session cookie being built with `chrono::Duration`. pub fn max_age_time(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); self } - /// Sets the `max-age` field in the session cookie being built with given number of seconds. - pub fn max_age(mut self, seconds: isize) -> CookieIdentityPolicy { - Rc::get_mut(&mut self.0).unwrap().max_age = Some(Duration::seconds(seconds as i64)); - self - } /// Sets the `same_site` field in the session cookie being built. pub fn same_site(mut self, same_site: SameSite) -> Self { @@ -547,7 +547,7 @@ mod tests { .service(web::resource("/login").to(|id: Identity| { id.remember("test".to_string()); HttpResponse::Ok() - })) + })), ); let resp = test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); @@ -559,7 +559,7 @@ mod tests { #[test] fn test_identity_max_age() { - let seconds = 60isize; + let seconds = 60; let mut srv = test::init_service( App::new() .wrap(IdentityService::new( @@ -573,7 +573,7 @@ mod tests { .service(web::resource("/login").to(|id: Identity| { id.remember("test".to_string()); HttpResponse::Ok() - })) + })), ); let resp = test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 4b0afe7a..060331a6 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,8 +1,8 @@ //! `Middleware` to normalize request's URI -use regex::Regex; use actix_service::{Service, Transform}; use futures::future::{self, FutureResult}; +use regex::Regex; use crate::service::{ServiceRequest, ServiceResponse}; @@ -28,7 +28,7 @@ where fn new_transform(&self, service: S) -> Self::Future { future::ok(NormalizePathNormalization { service, - merge_slash: Regex::new("//+").unwrap() + merge_slash: Regex::new("//+").unwrap(), }) } } @@ -72,7 +72,6 @@ mod tests { use super::*; use crate::dev::ServiceRequest; - use crate::http::header::CONTENT_TYPE; use crate::test::{block_on, TestRequest}; use crate::HttpResponse; diff --git a/src/test.rs b/src/test.rs index a8eed388..d932adfd 100644 --- a/src/test.rs +++ b/src/test.rs @@ -335,12 +335,12 @@ impl TestRequest { pub fn post() -> TestRequest { TestRequest::default().method(Method::POST) } - + /// Create TestRequest and set method to `Method::PUT` pub fn put() -> TestRequest { TestRequest::default().method(Method::PUT) } - + /// Create TestRequest and set method to `Method::PATCH` pub fn patch() -> TestRequest { TestRequest::default().method(Method::PATCH) @@ -521,7 +521,6 @@ mod tests { let result = read_response(&mut app, put_req); assert_eq!(result, Bytes::from_static(b"put!")); - let patch_req = TestRequest::patch() .uri("/index.html") .header(header::CONTENT_TYPE, "application/json") From fc9b14a933f49c40fa43f31335913e6e92a701d7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:03:44 -0700 Subject: [PATCH 1316/1635] allow to specify server address for http and ws requests --- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 2 +- actix-http/src/client/connector.rs | 106 ++++++++++++++--------------- actix-http/src/client/mod.rs | 8 +++ actix-http/src/client/pool.rs | 22 +++--- awc/CHANGES.md | 6 +- awc/Cargo.toml | 2 +- awc/src/builder.rs | 6 +- awc/src/connect.rs | 24 +++++-- awc/src/request.rs | 15 +++- awc/src/ws.rs | 14 +++- 11 files changed, 129 insertions(+), 78 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a82b864b..fc33ff41 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -8,6 +8,8 @@ * Cookie::max_age_time() accepts value in time::Duration +* Allow to specify server address for client connector + ## [0.1.0] - 2019-04-16 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b94e12d8..1f8056b3 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -46,7 +46,7 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.3.6" actix-codec = "0.1.2" -actix-connect = "0.1.4" +actix-connect = "0.1.5" actix-utils = "0.3.5" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index ed6207f9..639afb75 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -14,6 +14,7 @@ use tokio_tcp::TcpStream; use super::connection::Connection; use super::error::ConnectError; use super::pool::{ConnectionPool, Protocol}; +use super::Connect; #[cfg(feature = "ssl")] use openssl::ssl::SslConnector; @@ -177,15 +178,17 @@ where /// its combinator chain. pub fn finish( self, - ) -> impl Service + Clone - { + ) -> impl Service + + Clone { #[cfg(not(feature = "ssl"))] { let connector = TimeoutService::new( self.timeout, - apply_fn(self.connector, |msg: Uri, srv| srv.call(msg.into())) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector, |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -209,26 +212,28 @@ where let ssl_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) - .map_err(ConnectError::from) - .and_then( - OpensslConnector::service(self.ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (sock, Protocol::Http2) - } else { - (sock, Protocol::Http1) - } - }), - ), + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }) + .map_err(ConnectError::from) + .and_then( + OpensslConnector::service(self.ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (sock, Protocol::Http2) + } else { + (sock, Protocol::Http1) + } + }), + ), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -237,9 +242,11 @@ where let tcp_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Uri, srv| srv.call(msg.into())) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }) + .map_err(ConnectError::from) + .map(|stream| (stream.into_parts().0, Protocol::Http1)), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -264,15 +271,6 @@ where } } } - - #[doc(hidden)] - #[deprecated(since = "0.1.0-alpha4", note = "please use `.finish()` method")] - pub fn service( - self, - ) -> impl Service + Clone - { - self.finish() - } } #[cfg(not(feature = "ssl"))] @@ -286,7 +284,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) tcp_pool: ConnectionPool, } @@ -294,7 +292,7 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service + T: Service + Clone, { fn clone(&self) -> Self { @@ -307,9 +305,9 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Uri; + type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = Either< @@ -346,8 +344,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -357,9 +355,9 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service + T1: Service + Clone, - T2: Service + T2: Service + Clone, { fn clone(&self) -> Self { @@ -374,10 +372,10 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { - type Request = Uri; + type Request = Connect; type Response = EitherConnection; type Error = ConnectError; type Future = Either< @@ -392,8 +390,8 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Uri) -> Self::Future { - match req.scheme_str() { + fn call(&mut self, req: Connect) -> Self::Future { + match req.uri.scheme_str() { Some("https") | Some("wss") => { Either::B(Either::B(InnerConnectorResponseB { fut: self.ssl_pool.call(req), @@ -411,7 +409,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -419,7 +417,7 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -437,7 +435,7 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { fut: as Service>::Future, _t: PhantomData, @@ -445,7 +443,7 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index cf526e25..1d10117c 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -1,4 +1,6 @@ //! Http client api +use http::Uri; + mod connection; mod connector; mod error; @@ -10,3 +12,9 @@ pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; pub use self::pool::Protocol; + +#[derive(Clone)] +pub struct Connect { + pub uri: Uri, + pub addr: Option, +} diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 68ac6fbc..7b138dba 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -13,13 +13,14 @@ use futures::unsync::oneshot; use futures::{Async, Future, Poll}; use h2::client::{handshake, Handshake}; use hashbrown::HashMap; -use http::uri::{Authority, Uri}; +use http::uri::Authority; use indexmap::IndexSet; use slab::Slab; use tokio_timer::{sleep, Delay}; use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; +use super::Connect; #[derive(Clone, Copy, PartialEq)] /// Protocol version @@ -48,7 +49,7 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { pub(crate) fn new( connector: T, @@ -87,9 +88,9 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service, { - type Request = Uri; + type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = Either< @@ -101,8 +102,8 @@ where self.0.poll_ready() } - fn call(&mut self, req: Uri) -> Self::Future { - let key = if let Some(authority) = req.authority_part() { + fn call(&mut self, req: Connect) -> Self::Future { + let key = if let Some(authority) = req.uri.authority_part() { authority.clone().into() } else { return Either::A(err(ConnectError::Unresolverd)); @@ -292,7 +293,10 @@ pub(crate) struct Inner { limit: usize, acquired: usize, available: HashMap>>, - waiters: Slab<(Uri, oneshot::Sender, ConnectError>>)>, + waiters: Slab<( + Connect, + oneshot::Sender, ConnectError>>, + )>, waiters_queue: IndexSet<(Key, usize)>, task: AtomicTask, } @@ -331,14 +335,14 @@ where /// connection is not available, wait fn wait_for( &mut self, - connect: Uri, + connect: Connect, ) -> ( oneshot::Receiver, ConnectError>>, usize, ) { let (tx, rx) = oneshot::channel(); - let key: Key = connect.authority_part().unwrap().clone().into(); + let key: Key = connect.uri.authority_part().unwrap().clone().into(); let entry = self.waiters.vacant_entry(); let token = entry.key(); entry.insert((connect, tx)); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index a4f43d29..30fd4a6d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [0.1.1] - 2019-04-xx +## [0.1.1] - 2019-04-19 + +### Added + +* Allow to specify server address for http and ws requests. ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index bbc3d928..b8c4f989 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index ddefed43..c460f135 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -3,8 +3,8 @@ use std::fmt; use std::rc::Rc; use std::time::Duration; -use actix_http::client::{ConnectError, Connection, Connector}; -use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom, Uri}; +use actix_http::client::{Connect, ConnectError, Connection, Connector}; +use actix_http::http::{header, HeaderMap, HeaderName, HttpTryFrom}; use actix_service::Service; use crate::connect::ConnectorWrapper; @@ -40,7 +40,7 @@ impl ClientBuilder { /// Use custom connector service. pub fn connector(mut self, connector: T) -> Self where - T: Service + 'static, + T: Service + 'static, T::Response: Connection, ::Future: 'static, T::Future: 'static, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index bfc9da05..4b564d77 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,10 +1,12 @@ -use std::{fmt, io}; +use std::{fmt, io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::Body; -use actix_http::client::{ConnectError, Connection, SendRequestError}; +use actix_http::client::{ + Connect as ClientConnect, ConnectError, Connection, SendRequestError, +}; use actix_http::h1::ClientCodec; -use actix_http::{http, RequestHead, ResponseHead}; +use actix_http::{RequestHead, ResponseHead}; use actix_service::Service; use futures::{Future, Poll}; @@ -17,12 +19,14 @@ pub(crate) trait Connect { &mut self, head: RequestHead, body: Body, + addr: Option, ) -> Box>; /// Send request, returns Response and Framed fn open_tunnel( &mut self, head: RequestHead, + addr: Option, ) -> Box< Future< Item = (ResponseHead, Framed), @@ -33,7 +37,7 @@ pub(crate) trait Connect { impl Connect for ConnectorWrapper where - T: Service, + T: Service, T::Response: Connection, ::Io: 'static, ::Future: 'static, @@ -44,11 +48,15 @@ where &mut self, head: RequestHead, body: Body, + addr: Option, ) -> Box> { Box::new( self.0 // connect to the host - .call(head.uri.clone()) + .call(ClientConnect { + uri: head.uri.clone(), + addr, + }) .from_err() // send request .and_then(move |connection| connection.send_request(head, body)) @@ -59,6 +67,7 @@ where fn open_tunnel( &mut self, head: RequestHead, + addr: Option, ) -> Box< Future< Item = (ResponseHead, Framed), @@ -68,7 +77,10 @@ where Box::new( self.0 // connect to the host - .call(head.uri.clone()) + .call(ClientConnect { + uri: head.uri.clone(), + addr, + }) .from_err() // send request .and_then(move |connection| connection.open_tunnel(head)) diff --git a/awc/src/request.rs b/awc/src/request.rs index a280dfce..2e603264 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,8 +1,8 @@ -use std::fmt; use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; use std::time::Duration; +use std::{fmt, net}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{err, Either}; @@ -60,6 +60,7 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; pub struct ClientRequest { pub(crate) head: RequestHead, err: Option, + addr: Option, cookies: Option, response_decompress: bool, timeout: Option, @@ -76,6 +77,7 @@ impl ClientRequest { config, head: RequestHead::default(), err: None, + addr: None, cookies: None, timeout: None, response_decompress: true, @@ -97,6 +99,15 @@ impl ClientRequest { self } + /// Set socket address of the server. + /// + /// This address is used for connection. If address is not + /// provided url's host name get resolved. + pub fn address(mut self, addr: net::SocketAddr) -> Self { + self.addr = Some(addr); + self + } + /// Set HTTP method of this request. #[inline] pub fn method(mut self, method: Method) -> Self { @@ -435,7 +446,7 @@ impl ClientRequest { let fut = config .connector .borrow_mut() - .send_request(head, body.into()) + .send_request(head, body.into(), slf.addr) .map(move |res| { res.map_body(|head, payload| { if response_decompress { diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 028330ab..94a90535 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -1,5 +1,6 @@ //! Websockets client use std::fmt::Write as FmtWrite; +use std::net::SocketAddr; use std::rc::Rc; use std::{fmt, str}; @@ -29,6 +30,7 @@ pub struct WebsocketsRequest { err: Option, origin: Option, protocols: Option, + addr: Option, max_size: usize, server_mode: bool, cookies: Option, @@ -55,6 +57,7 @@ impl WebsocketsRequest { head, err, config, + addr: None, origin: None, protocols: None, max_size: 65_536, @@ -63,6 +66,15 @@ impl WebsocketsRequest { } } + /// Set socket address of the server. + /// + /// This address is used for connection. If address is not + /// provided url's host name get resolved. + pub fn address(mut self, addr: SocketAddr) -> Self { + self.addr = Some(addr); + self + } + /// Set supported websocket protocols pub fn protocols(mut self, protos: U) -> Self where @@ -274,7 +286,7 @@ impl WebsocketsRequest { .config .connector .borrow_mut() - .open_tunnel(head) + .open_tunnel(head, self.addr) .from_err() .and_then(move |(head, framed)| { // verify response From 6decfdda1f5da983c07c35260afa36894f2d8151 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:06:34 -0700 Subject: [PATCH 1317/1635] update deps --- awc/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b8c4f989..a254d69c 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.3.6" -actix-http = "0.1.0" +actix-http = "0.1.1" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -56,7 +56,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } -actix-http = { version = "0.1.0", features=["ssl"] } +actix-http = { version = "0.1.1", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.3", features=["ssl"] } From 9f421b81b85b0d9282fc42eefe98e01348d3e732 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:10:53 -0700 Subject: [PATCH 1318/1635] fix non-ssl connector --- actix-http/src/client/connector.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 639afb75..14df2eee 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -319,8 +319,8 @@ mod connect_impl { self.tcp_pool.poll_ready() } - fn call(&mut self, req: Uri) -> Self::Future { - match req.scheme_str() { + fn call(&mut self, req: Connect) -> Self::Future { + match req.uri.scheme_str() { Some("https") | Some("wss") => { Either::B(err(ConnectError::SslIsNotSupported)) } From 5e4e95fb0ae6d66f0d2faafaf67db5184c31ff7d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:13:05 -0700 Subject: [PATCH 1319/1635] update create version --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1f8056b3..ac15667f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From 01b1350dcc2382d11907b5c78228a77eb380619b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Apr 2019 18:16:01 -0700 Subject: [PATCH 1320/1635] update versions --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cfc3d59f..f49884c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,11 +71,11 @@ actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-alpha.6" -actix-http = { version = "0.1.0", features=["fail"] } +actix-http = { version = "0.1.1", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" -awc = { version = "0.1.0", optional = true } +awc = { version = "0.1.1", optional = true } bytes = "0.4" derive_more = "0.14" @@ -98,7 +98,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.0", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.1", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0", features=["ssl"] } actix-files = { version = "0.1.0-alpha.6" } rand = "0.6" From 891f85754761572906b5b0b6db3056a93a1103f3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 20 Apr 2019 11:18:04 -0700 Subject: [PATCH 1321/1635] update changes --- CHANGES.md | 2 ++ src/middleware/mod.rs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 573d287c..ea4cdc5d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,8 @@ * Add `.peer_addr()` #744 +* Add `NormalizePath` middleware + ### Changed * Rename `RouterConfig` to `ServiceConfig` diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 3df92625..5266f7c1 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -6,10 +6,11 @@ pub mod cors; mod defaultheaders; pub mod errhandlers; mod logger; -pub mod normalize; +mod normalize; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; +pub use self::normalize::NormalizePath; #[cfg(feature = "secure-cookies")] pub mod identity; From 7e480ab2f77659e0694f23447b2ecb7f08cea994 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 20 Apr 2019 21:16:51 -0700 Subject: [PATCH 1322/1635] beta.1 release --- CHANGES.md | 2 +- Cargo.toml | 4 ++-- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 6 +++--- actix-session/CHANGES.md | 3 +++ actix-session/Cargo.toml | 6 +++--- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 4 ++-- 8 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ea4cdc5d..eed851a7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.0-beta.1] - 2019-04-xx +## [1.0.0-beta.1] - 2019-04-20 ### Added diff --git a/Cargo.toml b/Cargo.toml index f49884c8..c4e01e9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ actix-service = "0.3.6" actix-utils = "0.3.4" actix-router = "0.1.2" actix-rt = "0.2.2" -actix-web-codegen = "0.1.0-alpha.6" +actix-web-codegen = "0.1.0-beta.1" actix-http = { version = "0.1.1", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" @@ -100,7 +100,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.1.1", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0", features=["ssl"] } -actix-files = { version = "0.1.0-alpha.6" } +actix-files = { version = "0.1.0-beta.1" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index ae22fca1..05a5e580 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-beta.1] - 2019-04-20 + +* Update actix-web to beta.1 + ## [0.1.0-alpha.6] - 2019-04-14 * Update actix-web to alpha6 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index fa9d6739..8d242a72 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-alpha.6" +version = "0.1.0-betsa.1" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.6" +actix-web = "1.0.0-beta.1" actix-service = "0.3.4" bitflags = "1" bytes = "0.4" @@ -31,4 +31,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } +actix-web = { version = "1.0.0-beta.1", features=["ssl"] } diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index dfa3033c..a60d1e66 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,8 @@ # Changes +## [0.1.0-beta.1] - 2019-04-20 + +* Update actix-web to beta.1 * `CookieSession::max_age()` accepts value in seconds diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index e21bb732..83f9807f 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-alpha.6" +version = "0.1.0-beta.1" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,12 +24,12 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-alpha.6" +actix-web = "1.0.0-beta.1" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" futures = "0.1.25" -hashbrown = "0.2.0" +hashbrown = "0.2.2" serde = "1.0" serde_json = "1.0" time = "0.1.42" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 2ce5bd7d..3173ff1f 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-beta.1] - 2019-04-20 + +* Gen code for actix-web 1.0.0-beta.1 + ## [0.1.0-alpha.6] - 2019-04-14 * Gen code for actix-web 1.0.0-alpha.6 diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 13928f67..4108d879 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.1.0-alpha.6" +version = "0.1.0-beta.1" description = "Actix web proc macros" readme = "README.md" authors = ["Nikolay Kim "] @@ -17,6 +17,6 @@ syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] actix-web = { version = "1.0.0-alpha.6" } -actix-http = { version = "0.1.0", features=["ssl"] } +actix-http = { version = "0.1.1", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] } futures = { version = "0.1" } From f0789aad055065f9e556a0ca146a301dc3d1b3bb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 21 Apr 2019 09:03:46 -0700 Subject: [PATCH 1323/1635] update dep versions --- actix-files/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 8d242a72..5e37fc09 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-betsa.1" +version = "0.1.0-beta.1" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 3e67c231..22fdf613 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,8 +19,8 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0" -actix-web = "1.0.0-alpha.5" -actix-http = "0.1.0" +actix-web = "1.0.0-beta.1" +actix-http = "0.1.1" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" From 895e409d57178666aeb0faff97458052ab6d68ca Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 21 Apr 2019 15:41:01 -0700 Subject: [PATCH 1324/1635] Optimize multipart handling #634, #769 --- actix-multipart/CHANGES.md | 4 +- actix-multipart/Cargo.toml | 8 +- actix-multipart/src/server.rs | 140 ++++++++++++++++++++-------------- 3 files changed, 90 insertions(+), 62 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index fec3e50f..9f8fa052 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,7 +1,9 @@ # Changes -## [0.1.0-alpha.1] - 2019-04-xx +## [0.1.0-beta.1] - 2019-04-21 * Do not support nested multipart * Split multipart support to separate crate + +* Optimize multipart handling #634, #769 \ No newline at end of file diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 58ab4523..8e1714e7 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.0-alpha.1" +version = "0.1.0-beta.1" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" @@ -18,8 +18,8 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-alpha.6" -actix-service = "0.3.4" +actix-web = "1.0.0-beta.1" +actix-service = "0.3.6" bytes = "0.4" derive_more = "0.14" httparse = "1.3" @@ -31,4 +31,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "0.2.2" -actix-http = "0.1.0" \ No newline at end of file +actix-http = "0.1.1" \ No newline at end of file diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c651fae5..59ed5599 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -168,7 +168,7 @@ impl InnerMultipart { match payload.readline() { None => { if payload.eof { - Err(MultipartError::Incomplete) + Ok(Some(true)) } else { Ok(None) } @@ -201,8 +201,7 @@ impl InnerMultipart { match payload.readline() { Some(chunk) => { if chunk.is_empty() { - //ValueError("Could not find starting boundary %r" - //% (self._boundary)) + return Err(MultipartError::Boundary); } if chunk.len() < boundary.len() { continue; @@ -505,47 +504,73 @@ impl InnerField { payload: &mut PayloadBuffer, boundary: &str, ) -> Poll, MultipartError> { - match payload.read_until(b"\r") { - None => { - if payload.eof { - Err(MultipartError::Incomplete) + let mut pos = 0; + + let len = payload.buf.len(); + if len == 0 { + return Ok(Async::NotReady); + } + + // check boundary + if len > 4 && payload.buf[0] == b'\r' { + let b_len = if &payload.buf[..2] == b"\r\n" && &payload.buf[2..4] == b"--" { + Some(4) + } else if &payload.buf[1..3] == b"--" { + Some(3) + } else { + None + }; + + if let Some(b_len) = b_len { + let b_size = boundary.len() + b_len; + if len < b_size { + return Ok(Async::NotReady); } else { - Ok(Async::NotReady) + if &payload.buf[b_len..b_size] == boundary.as_bytes() { + // found boundary + payload.buf.split_to(b_size); + return Ok(Async::Ready(None)); + } else { + pos = b_size; + } } } - Some(mut chunk) => { - if chunk.len() == 1 { - payload.unprocessed(chunk); - match payload.read_exact(boundary.len() + 4) { - None => { - if payload.eof { - Err(MultipartError::Incomplete) - } else { - Ok(Async::NotReady) - } - } - Some(mut chunk) => { - if &chunk[..2] == b"\r\n" - && &chunk[2..4] == b"--" - && &chunk[4..] == boundary.as_bytes() - { - payload.unprocessed(chunk); - Ok(Async::Ready(None)) - } else { - // \r might be part of data stream - let ch = chunk.split_to(1); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) - } - } + } + + loop { + return if let Some(idx) = twoway::find_bytes(&payload.buf[pos..], b"\r") { + let cur = pos + idx; + + // check if we have enough data for boundary detection + if cur + 4 > len { + if cur > 0 { + Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze()))) + } else { + Ok(Async::NotReady) } } else { - let to = chunk.len() - 1; - let ch = chunk.split_to(to); - payload.unprocessed(chunk); - Ok(Async::Ready(Some(ch))) + // check boundary + if (&payload.buf[cur..cur + 2] == b"\r\n" + && &payload.buf[cur + 2..cur + 4] == b"--") + || (&payload.buf[cur..cur + 1] == b"\r" + && &payload.buf[cur + 1..cur + 3] == b"--") + { + if cur != 0 { + // return buffer + Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze()))) + } else { + pos = cur + 1; + continue; + } + } else { + // not boundary + pos = cur + 1; + continue; + } } - } + } else { + return Ok(Async::Ready(Some(payload.buf.take().freeze()))); + }; } } @@ -555,26 +580,27 @@ impl InnerField { } let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { - let res = if let Some(ref mut len) = self.length { - InnerField::read_len(payload, len)? - } else { - InnerField::read_stream(payload, &self.boundary)? - }; + if !self.eof { + let res = if let Some(ref mut len) = self.length { + InnerField::read_len(payload, len)? + } else { + InnerField::read_stream(payload, &self.boundary)? + }; - match res { - Async::NotReady => Async::NotReady, - Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)), - Async::Ready(None) => { - self.eof = true; - match payload.readline() { - None => Async::Ready(None), - Some(line) => { - if line.as_ref() != b"\r\n" { - log::warn!("multipart field did not read all the data or it is malformed"); - } - Async::Ready(None) - } + match res { + Async::NotReady => return Ok(Async::NotReady), + Async::Ready(Some(bytes)) => return Ok(Async::Ready(Some(bytes))), + Async::Ready(None) => self.eof = true, + } + } + + match payload.readline() { + None => Async::Ready(None), + Some(line) => { + if line.as_ref() != b"\r\n" { + log::warn!("multipart field did not read all the data or it is malformed"); } + Async::Ready(None) } } } else { @@ -704,7 +730,7 @@ impl PayloadBuffer { } /// Read exact number of bytes - #[inline] + #[cfg(test)] fn read_exact(&mut self, size: usize) -> Option { if size <= self.buf.len() { Some(self.buf.split_to(size).freeze()) From d00c9bb8446ee82a9fbad884c202cf9505c6ccc5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 21 Apr 2019 16:14:09 -0700 Subject: [PATCH 1325/1635] do not consume boundary --- actix-multipart/src/server.rs | 73 ++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 59ed5599..82b0b5ac 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -528,7 +528,6 @@ impl InnerField { } else { if &payload.buf[b_len..b_size] == boundary.as_bytes() { // found boundary - payload.buf.split_to(b_size); return Ok(Async::Ready(None)); } else { pos = b_size; @@ -901,6 +900,78 @@ mod tests { }); } + #[test] + fn test_stream() { + run_on(|| { + let (sender, payload) = create_stream(); + + let bytes = Bytes::from( + "testasdadsad\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ + Content-Type: text/plain; charset=utf-8\r\n\r\n\ + test\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Type: text/plain; charset=utf-8\r\n\r\n\ + data\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n", + ); + sender.unbounded_send(Ok(bytes)).unwrap(); + + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static( + "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", + ), + ); + + let mut multipart = Multipart::new(&headers, payload); + match multipart.poll().unwrap() { + Async::Ready(Some(mut field)) => { + let cd = field.content_disposition().unwrap(); + assert_eq!(cd.disposition, DispositionType::FormData); + assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); + + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); + + match field.poll().unwrap() { + Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), + _ => unreachable!(), + } + match field.poll().unwrap() { + Async::Ready(None) => (), + _ => unreachable!(), + } + } + _ => unreachable!(), + } + + match multipart.poll().unwrap() { + Async::Ready(Some(mut field)) => { + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); + + match field.poll() { + Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"), + _ => unreachable!(), + } + match field.poll() { + Ok(Async::Ready(None)) => (), + _ => unreachable!(), + } + } + _ => unreachable!(), + } + + match multipart.poll().unwrap() { + Async::Ready(None) => (), + _ => unreachable!(), + } + }); + } + #[test] fn test_basic() { run_on(|| { From 48bee5508789ad92a78162b8e1691c1ecd3de616 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Apr 2019 14:22:08 -0700 Subject: [PATCH 1326/1635] .to_async() handler can return Responder type #792 --- CHANGES.md | 9 +++++++++ src/handler.rs | 50 ++++++++++++++++++++++++++++++++++++------------- src/resource.rs | 2 +- src/route.rs | 37 ++++++++++++++++++++++++++++-------- src/test.rs | 40 +++++++++++++++++++++++++++++++++++++++ src/web.rs | 4 ++-- 6 files changed, 118 insertions(+), 24 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index eed851a7..6f0a2d42 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,14 @@ # Changes +### Added + +* Add helper functions for reading response body `test::read_body()` + +### Changed + +* `.to_async()` handler can return `Responder` type #792 + + ## [1.0.0-beta.1] - 2019-04-20 ### Added diff --git a/src/handler.rs b/src/handler.rs index f328cd25..850c0c92 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -124,7 +124,7 @@ where pub trait AsyncFactory: Clone + 'static where R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { fn call(&self, param: T) -> R; @@ -134,7 +134,7 @@ impl AsyncFactory<(), R> for F where F: Fn() -> R + Clone + 'static, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { fn call(&self, _: ()) -> R { @@ -147,7 +147,7 @@ pub struct AsyncHandler where F: AsyncFactory, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { hnd: F, @@ -158,7 +158,7 @@ impl AsyncHandler where F: AsyncFactory, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { pub fn new(hnd: F) -> Self { @@ -173,7 +173,7 @@ impl Clone for AsyncHandler where F: AsyncFactory, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { fn clone(&self) -> Self { @@ -188,7 +188,7 @@ impl Service for AsyncHandler where F: AsyncFactory, R: IntoFuture, - R::Item: Into, + R::Item: Responder, R::Error: Into, { type Request = (T, HttpRequest); @@ -203,32 +203,56 @@ where fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { AsyncHandlerServiceResponse { fut: self.hnd.call(param).into_future(), + fut2: None, req: Some(req), } } } #[doc(hidden)] -pub struct AsyncHandlerServiceResponse { +pub struct AsyncHandlerServiceResponse +where + T: Future, + T::Item: Responder, +{ fut: T, + fut2: Option<<::Future as IntoFuture>::Future>, req: Option, } impl Future for AsyncHandlerServiceResponse where T: Future, - T::Item: Into, + T::Item: Responder, T::Error: Into, { type Item = ServiceResponse; type Error = Void; fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut2 { + return match fut.poll() { + Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( + self.req.take().unwrap(), + res, + ))), + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => { + let res: Response = e.into().into(); + Ok(Async::Ready(ServiceResponse::new( + self.req.take().unwrap(), + res, + ))) + } + }; + } + match self.fut.poll() { - Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( - self.req.take().unwrap(), - res.into(), - ))), + Ok(Async::Ready(res)) => { + self.fut2 = + Some(res.respond_to(self.req.as_ref().unwrap()).into_future()); + return self.poll(); + } Ok(Async::NotReady) => Ok(Async::NotReady), Err(e) => { let res: Response = e.into().into(); @@ -357,7 +381,7 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { impl AsyncFactory<($($T,)+), Res> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, Res: IntoFuture, - Res::Item: Into, + Res::Item: Responder, Res::Error: Into, { fn call(&self, param: ($($T,)+)) -> Res { diff --git a/src/resource.rs b/src/resource.rs index 1f1e6e15..03c614a9 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -217,7 +217,7 @@ where F: AsyncFactory, I: FromRequest + 'static, R: IntoFuture + 'static, - R::Item: Into, + R::Item: Responder, R::Error: Into, { self.routes.push(Route::new().to_async(handler)); diff --git a/src/route.rs b/src/route.rs index 7b9f36a6..8c97d772 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; -use actix_http::{http::Method, Error, Extensions, Response}; +use actix_http::{http::Method, Error, Extensions}; use actix_service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; @@ -278,7 +278,7 @@ impl Route { F: AsyncFactory, T: FromRequest + 'static, R: IntoFuture + 'static, - R::Item: Into, + R::Item: Responder, R::Error: Into, { self.service = Box::new(RouteNewService::new(Extract::new( @@ -418,18 +418,25 @@ where mod tests { use std::time::Duration; + use bytes::Bytes; use futures::Future; + use serde_derive::Serialize; use tokio_timer::sleep; use crate::http::{Method, StatusCode}; - use crate::test::{call_service, init_service, TestRequest}; + use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{error, web, App, HttpResponse}; + #[derive(Serialize, PartialEq, Debug)] + struct MyObject { + name: String, + } + #[test] fn test_route() { - let mut srv = - init_service( - App::new().service( + let mut srv = init_service( + App::new() + .service( web::resource("/test") .route(web::get().to(|| HttpResponse::Ok())) .route(web::put().to(|| { @@ -444,8 +451,15 @@ mod tests { Err::(error::ErrorBadRequest("err")) }) })), - ), - ); + ) + .service(web::resource("/json").route(web::get().to_async(|| { + sleep(Duration::from_millis(25)).then(|_| { + Ok::<_, crate::Error>(web::Json(MyObject { + name: "test".to_string(), + })) + }) + }))), + ); let req = TestRequest::with_uri("/test") .method(Method::GET) @@ -476,5 +490,12 @@ mod tests { .to_request(); let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let req = TestRequest::with_uri("/json").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + + let body = read_body(resp); + assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); } } diff --git a/src/test.rs b/src/test.rs index d932adfd..1f3a2427 100644 --- a/src/test.rs +++ b/src/test.rs @@ -193,6 +193,46 @@ where .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) } +/// Helper function that returns a response body of a ServiceResponse. +/// This function blocks the current thread until futures complete. +/// +/// ```rust +/// use actix_web::{test, web, App, HttpResponse, http::header}; +/// use bytes::Bytes; +/// +/// #[test] +/// fn test_index() { +/// let mut app = test::init_service( +/// App::new().service( +/// web::resource("/index.html") +/// .route(web::post().to( +/// || HttpResponse::Ok().body("welcome!"))))); +/// +/// let req = test::TestRequest::post() +/// .uri("/index.html") +/// .header(header::CONTENT_TYPE, "application/json") +/// .to_request(); +/// +/// let resp = call_service(&mut srv, req); +/// let result = test::read_body(resp); +/// assert_eq!(result, Bytes::from_static(b"welcome!")); +/// } +/// ``` +pub fn read_body(mut res: ServiceResponse) -> Bytes +where + B: MessageBody, +{ + block_on(run_on(move || { + res.take_body() + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, Error>(body) + }) + .map(|body: BytesMut| body.freeze()) + })) + .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) +} + /// Helper function that returns a deserialized response body of a TestRequest /// This function blocks the current thread until futures complete. /// diff --git a/src/web.rs b/src/web.rs index 079dec51..73314449 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,5 +1,5 @@ //! Essentials helper functions and types for application registration. -use actix_http::{http::Method, Response}; +use actix_http::http::Method; use futures::{Future, IntoFuture}; pub use actix_http::Response as HttpResponse; @@ -268,7 +268,7 @@ where F: AsyncFactory, I: FromRequest + 'static, R: IntoFuture + 'static, - R::Item: Into, + R::Item: Responder, R::Error: Into, { Route::new().to_async(handler) From 3532602299f855ba1b9de291db9a8128fb72cb0f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Apr 2019 21:22:17 -0700 Subject: [PATCH 1327/1635] Added support for remainder match (i.e /path/{tail}*) --- CHANGES.md | 3 +++ Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6f0a2d42..f4fdd6af 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ * Add helper functions for reading response body `test::read_body()` +* Added support for `remainder match` (i.e "/path/{tail}*") + + ### Changed * `.to_async()` handler can return `Responder` type #792 diff --git a/Cargo.toml b/Cargo.toml index c4e01e9f..74fe78b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ rust-tls = ["rustls", "actix-server/rust-tls"] actix-codec = "0.1.2" actix-service = "0.3.6" actix-utils = "0.3.4" -actix-router = "0.1.2" +actix-router = "0.1.3" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" actix-http = { version = "0.1.1", features=["fail"] } From 5d531989e70d6279ea97e7dead7a1feae3240abc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 09:42:19 -0700 Subject: [PATCH 1328/1635] Fix BorrowMutError panic in client connector #793 --- actix-http/CHANGES.md | 7 +++++++ actix-http/src/client/pool.rs | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index fc33ff41..2edcceeb 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.2] - 2019-04-23 + +### Fixed + +* Fix BorrowMutError panic in client connector #793 + + ## [0.1.1] - 2019-04-19 ### Changes diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 7b138dba..1164205e 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -113,31 +113,31 @@ where match self.1.as_ref().borrow_mut().acquire(&key) { Acquire::Acquired(io, created) => { // use existing connection - Either::A(ok(IoConnection::new( + return Either::A(ok(IoConnection::new( io, created, Some(Acquired(key, Some(self.1.clone()))), - ))) - } - Acquire::NotAvailable => { - // connection is not available, wait - let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req); - Either::B(Either::A(WaitForConnection { - rx, - key, - token, - inner: Some(self.1.clone()), - })) + ))); } Acquire::Available => { // open new connection - Either::B(Either::B(OpenConnection::new( + return Either::B(Either::B(OpenConnection::new( key, self.1.clone(), self.0.call(req), - ))) + ))); } + _ => (), } + + // connection is not available, wait + let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req); + Either::B(Either::A(WaitForConnection { + rx, + key, + token, + inner: Some(self.1.clone()), + })) } } From 5f6a1a82492d6fbc1915a4b0f98c7bbfb828e008 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 09:45:39 -0700 Subject: [PATCH 1329/1635] update version --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ac15667f..17e99bca 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.1" +version = "0.1.2" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From d2b0afd859e61f48f1057f37d09661afa1be1c36 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 14:57:03 -0700 Subject: [PATCH 1330/1635] Fix http client pool and wait queue management --- Cargo.toml | 4 +- actix-http/CHANGES.md | 9 ++ actix-http/src/client/connector.rs | 44 +++++-- actix-http/src/client/h1proto.rs | 2 +- actix-http/src/client/pool.rs | 205 +++++++++++++++++++++++++---- awc/Cargo.toml | 6 +- awc/tests/test_client.rs | 201 ++++++++++++++++++++++++++-- test-server/src/lib.rs | 11 ++ 8 files changed, 430 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 74fe78b4..a886b5fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ actix-utils = "0.3.4" actix-router = "0.1.3" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" -actix-http = { version = "0.1.1", features=["fail"] } +actix-http = { version = "0.1.2", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" @@ -98,7 +98,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.1", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.2", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.0", features=["ssl"] } actix-files = { version = "0.1.0-beta.1" } rand = "0.6" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2edcceeb..37d0eec6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,14 @@ # Changes +## [0.1.3] - 2019-04-23 + +### Fixed + +* Fix http client pool management + +* Fix http client wait queue management #794 + + ## [0.1.2] - 2019-04-23 ### Fixed diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 14df2eee..0241e847 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -114,7 +114,8 @@ where Request = TcpConnect, Response = TcpConnection, Error = actix_connect::ConnectError, - > + Clone, + > + Clone + + 'static, { /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. /// Set to 1 second by default. @@ -284,7 +285,9 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { pub(crate) tcp_pool: ConnectionPool, } @@ -293,7 +296,8 @@ mod connect_impl { where Io: AsyncRead + AsyncWrite + 'static, T: Service - + Clone, + + Clone + + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -305,7 +309,9 @@ mod connect_impl { impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { type Request = Connect; type Response = IoConnection; @@ -356,9 +362,11 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, T1: Service - + Clone, + + Clone + + 'static, T2: Service - + Clone, + + Clone + + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -372,8 +380,12 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, - T1: Service, - T2: Service, + T1: Service + + Clone + + 'static, + T2: Service + + Clone + + 'static, { type Request = Connect; type Response = EitherConnection; @@ -409,7 +421,9 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { fut: as Service>::Future, _t: PhantomData, @@ -417,7 +431,9 @@ mod connect_impl { impl Future for InnerConnectorResponseA where - T: Service, + T: Service + + Clone + + 'static, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { @@ -435,7 +451,9 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { fut: as Service>::Future, _t: PhantomData, @@ -443,7 +461,9 @@ mod connect_impl { impl Future for InnerConnectorResponseB where - T: Service, + T: Service + + Clone + + 'static, Io1: AsyncRead + AsyncWrite + 'static, Io2: AsyncRead + AsyncWrite + 'static, { diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index becc0752..97ed3bbc 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -274,7 +274,7 @@ impl Stream for PlStream { Ok(Async::Ready(Some(chunk))) } else { let framed = self.framed.take().unwrap(); - let force_close = framed.get_codec().keepalive(); + let force_close = !framed.get_codec().keepalive(); release_connection(framed, force_close); Ok(Async::Ready(None)) } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 1164205e..8dedf72f 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -49,7 +49,9 @@ pub(crate) struct ConnectionPool( impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { pub(crate) fn new( connector: T, @@ -69,7 +71,7 @@ where waiters: Slab::new(), waiters_queue: IndexSet::new(), available: HashMap::new(), - task: AtomicTask::new(), + task: None, })), ) } @@ -88,7 +90,9 @@ where impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, - T: Service, + T: Service + + Clone + + 'static, { type Request = Connect; type Response = IoConnection; @@ -131,7 +135,17 @@ where } // connection is not available, wait - let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req); + let (rx, token, support) = self.1.as_ref().borrow_mut().wait_for(req); + + // start support future + if !support { + self.1.as_ref().borrow_mut().task = Some(AtomicTask::new()); + tokio_current_thread::spawn(ConnectorPoolSupport { + connector: self.0.clone(), + inner: self.1.clone(), + }) + } + Either::B(Either::A(WaitForConnection { rx, key, @@ -245,7 +259,7 @@ where Ok(Async::Ready(IoConnection::new( ConnectionType::H2(snd), Instant::now(), - Some(Acquired(self.key.clone(), self.inner.clone())), + Some(Acquired(self.key.clone(), self.inner.take())), ))) } Ok(Async::NotReady) => Ok(Async::NotReady), @@ -256,12 +270,11 @@ where match self.fut.poll() { Err(err) => Err(err), Ok(Async::Ready((io, proto))) => { - let _ = self.inner.take(); if proto == Protocol::Http1 { Ok(Async::Ready(IoConnection::new( ConnectionType::H1(io), Instant::now(), - Some(Acquired(self.key.clone(), self.inner.clone())), + Some(Acquired(self.key.clone(), self.inner.take())), ))) } else { self.h2 = Some(handshake(io)); @@ -279,7 +292,6 @@ enum Acquire { NotAvailable, } -// #[derive(Debug)] struct AvailableConnection { io: ConnectionType, used: Instant, @@ -298,7 +310,7 @@ pub(crate) struct Inner { oneshot::Sender, ConnectError>>, )>, waiters_queue: IndexSet<(Key, usize)>, - task: AtomicTask, + task: Option, } impl Inner { @@ -314,18 +326,6 @@ impl Inner { self.waiters.remove(token); self.waiters_queue.remove(&(key.clone(), token)); } - - fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { - self.acquired -= 1; - self.available - .entry(key.clone()) - .or_insert_with(VecDeque::new) - .push_back(AvailableConnection { - io, - created, - used: Instant::now(), - }); - } } impl Inner @@ -339,6 +339,7 @@ where ) -> ( oneshot::Receiver, ConnectError>>, usize, + bool, ) { let (tx, rx) = oneshot::channel(); @@ -346,8 +347,9 @@ where let entry = self.waiters.vacant_entry(); let token = entry.key(); entry.insert((connect, tx)); - assert!(!self.waiters_queue.insert((key, token))); - (rx, token) + assert!(self.waiters_queue.insert((key, token))); + + (rx, token, self.task.is_some()) } fn acquire(&mut self, key: &Key) -> Acquire { @@ -400,6 +402,19 @@ where Acquire::Available } + fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { + self.acquired -= 1; + self.available + .entry(key.clone()) + .or_insert_with(VecDeque::new) + .push_back(AvailableConnection { + io, + created, + used: Instant::now(), + }); + self.check_availibility(); + } + fn release_close(&mut self, io: ConnectionType) { self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { @@ -407,11 +422,12 @@ where tokio_current_thread::spawn(CloseConnection::new(io, timeout)) } } + self.check_availibility(); } fn check_availibility(&self) { if !self.waiters_queue.is_empty() && self.acquired < self.limit { - self.task.notify() + self.task.as_ref().map(|t| t.notify()); } } } @@ -451,6 +467,147 @@ where } } +struct ConnectorPoolSupport +where + Io: AsyncRead + AsyncWrite + 'static, +{ + connector: T, + inner: Rc>>, +} + +impl Future for ConnectorPoolSupport +where + Io: AsyncRead + AsyncWrite + 'static, + T: Service, + T::Future: 'static, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let mut inner = self.inner.as_ref().borrow_mut(); + inner.task.as_ref().unwrap().register(); + + // check waiters + loop { + let (key, token) = { + if let Some((key, token)) = inner.waiters_queue.get_index(0) { + (key.clone(), *token) + } else { + break; + } + }; + match inner.acquire(&key) { + Acquire::NotAvailable => break, + Acquire::Acquired(io, created) => { + let (_, tx) = inner.waiters.remove(token); + if let Err(conn) = tx.send(Ok(IoConnection::new( + io, + created, + Some(Acquired(key.clone(), Some(self.inner.clone()))), + ))) { + let (io, created) = conn.unwrap().into_inner(); + inner.release_conn(&key, io, created); + } + } + Acquire::Available => { + let (connect, tx) = inner.waiters.remove(token); + OpenWaitingConnection::spawn( + key.clone(), + tx, + self.inner.clone(), + self.connector.call(connect), + ); + } + } + let _ = inner.waiters_queue.swap_remove_index(0); + } + + Ok(Async::NotReady) + } +} + +struct OpenWaitingConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fut: F, + key: Key, + h2: Option>, + rx: Option, ConnectError>>>, + inner: Option>>>, +} + +impl OpenWaitingConnection +where + F: Future + 'static, + Io: AsyncRead + AsyncWrite + 'static, +{ + fn spawn( + key: Key, + rx: oneshot::Sender, ConnectError>>, + inner: Rc>>, + fut: F, + ) { + tokio_current_thread::spawn(OpenWaitingConnection { + key, + fut, + h2: None, + rx: Some(rx), + inner: Some(inner), + }) + } +} + +impl Drop for OpenWaitingConnection +where + Io: AsyncRead + AsyncWrite + 'static, +{ + fn drop(&mut self) { + if let Some(inner) = self.inner.take() { + let mut inner = inner.as_ref().borrow_mut(); + inner.release(); + inner.check_availibility(); + } + } +} + +impl Future for OpenWaitingConnection +where + F: Future, + Io: AsyncRead + AsyncWrite, +{ + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + match self.fut.poll() { + Err(err) => { + let _ = self.inner.take(); + if let Some(rx) = self.rx.take() { + let _ = rx.send(Err(err)); + } + Err(()) + } + Ok(Async::Ready((io, proto))) => { + if proto == Protocol::Http1 { + let rx = self.rx.take().unwrap(); + let _ = rx.send(Ok(IoConnection::new( + ConnectionType::H1(io), + Instant::now(), + Some(Acquired(self.key.clone(), self.inner.take())), + ))); + Ok(Async::Ready(())) + } else { + self.h2 = Some(handshake(io)); + self.poll() + } + } + Ok(Async::NotReady) => Ok(Async::NotReady), + } + } +} + pub(crate) struct Acquired(Key, Option>>>); impl Acquired diff --git a/awc/Cargo.toml b/awc/Cargo.toml index a254d69c..e6018f44 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.3.6" -actix-http = "0.1.1" +actix-http = "0.1.2" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -55,8 +55,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-alpha.6", features=["ssl"] } -actix-http = { version = "0.1.1", features=["ssl"] } +actix-web = { version = "1.0.0-beta.1", features=["ssl"] } +actix-http = { version = "0.1.2", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.3", features=["ssl"] } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index afccdff8..d1139fdc 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; use std::io::{Read, Write}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; use std::time::Duration; use brotli2::write::BrotliEncoder; @@ -7,11 +9,12 @@ use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures::future::Future; +use futures::Future; use rand::Rng; use actix_http::HttpService; use actix_http_test::TestServer; +use actix_service::{fn_service, NewService}; use actix_web::http::Cookie; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; @@ -144,17 +147,195 @@ fn test_timeout_override() { } #[test] -fn test_connection_close() { - let mut srv = TestServer::new(|| { - HttpService::new( - App::new().service(web::resource("/").to(|| HttpResponse::Ok())), - ) +fn test_connection_reuse() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), + )) }); - let res = srv - .block_on(awc::Client::new().get(srv.url("/")).force_close().send()) - .unwrap(); - assert!(res.status().is_success()); + let client = awc::Client::default(); + + // req 1 + let request = client.get(srv.url("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.url("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_connection_force_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), + )) + }); + + let client = awc::Client::default(); + + // req 1 + let request = client.get(srv.url("/")).force_close().send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.url("/")).force_close(); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); +} + +#[test] +fn test_connection_server_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().finish())), + ), + )) + }); + + let client = awc::Client::default(); + + // req 1 + let request = client.get(srv.url("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.url("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); +} + +#[test] +fn test_connection_wait_queue() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + ))) + }); + + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); + + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = srv.execute(move || { + let mut fut = req2.send(); + assert!(fut.poll().unwrap().is_not_ready()); + fut + }); + + // read response 1 + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + // req 2 + let response = srv.block_on(req2_fut).unwrap(); + assert!(response.status().is_success()); + + // two connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} + +#[test] +fn test_connection_wait_queue_force_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), + ), + )) + }); + + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); + + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = srv.execute(move || { + let mut fut = req2.send(); + assert!(fut.poll().unwrap().is_not_ready()); + fut + }); + + // read response 1 + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + // req 2 + let response = srv.block_on(req2_fut).unwrap(); + assert!(response.status().is_success()); + + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); } #[test] diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 37abe129..42d07549 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -124,6 +124,7 @@ impl TestServer { |e| log::error!("Can not set alpn protocol: {:?}", e), ); Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(500)) .ssl(builder.build()) .finish() @@ -131,6 +132,7 @@ impl TestServer { #[cfg(not(feature = "ssl"))] { Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(500)) .finish() } @@ -163,6 +165,15 @@ impl TestServerRuntime { self.rt.block_on(fut) } + /// Execute future on current core + pub fn block_on_fn(&mut self, f: F) -> Result + where + F: FnOnce() -> R, + R: Future, + { + self.rt.block_on(lazy(|| f())) + } + /// Execute function on current core pub fn execute(&mut self, fut: F) -> R where From 9702b2d88edec888ec88bdfa4736bc0c99971860 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 15:06:30 -0700 Subject: [PATCH 1331/1635] add client h2 reuse test --- actix-http/Cargo.toml | 2 +- awc/tests/test_client.rs | 81 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 17e99bca..438754b3 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.2" +version = "0.1.3" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index d1139fdc..7e2dc6ba 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -12,10 +12,11 @@ use flate2::Compression; use futures::Future; use rand::Rng; +use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; use actix_http_test::TestServer; use actix_service::{fn_service, NewService}; -use actix_web::http::Cookie; +use actix_web::http::{Cookie, Version}; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; use awc::error::SendRequestError; @@ -42,6 +43,30 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; +#[cfg(feature = "ssl")] +fn ssl_acceptor( +) -> std::io::Result> { + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("../tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("../tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(openssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) +} + #[test] fn test_simple() { let mut srv = @@ -178,6 +203,60 @@ fn test_connection_reuse() { assert_eq!(num.load(Ordering::Relaxed), 1); } +#[cfg(feature = "ssl")] +#[test] +fn test_connection_reuse_h2() { + let openssl = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + fn_service(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) + .map_err(|_| ()), + ) + }); + + // disable ssl verification + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + + let client = awc::Client::build() + .connector(awc::Connector::new().ssl(builder.build()).finish()) + .finish(); + + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.surl("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} + #[test] fn test_connection_force_close() { let num = Arc::new(AtomicUsize::new(0)); From 898ef570803c983aaf92e2d3a0ecf7e5d278cbb6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Apr 2019 21:21:49 -0700 Subject: [PATCH 1332/1635] Fix async web::Data factory handling --- CHANGES.md | 5 +++++ src/app.rs | 27 +++++++++++++++++++++++++-- src/app_service.rs | 2 +- src/config.rs | 41 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f4fdd6af..56536226 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,11 @@ * `.to_async()` handler can return `Responder` type #792 +### Fixed + +* Fix async web::Data factory handling + + ## [1.0.0-beta.1] - 2019-04-20 ### Added diff --git a/src/app.rs b/src/app.rs index c0bbc8b2..bb6d2aef 100644 --- a/src/app.rs +++ b/src/app.rs @@ -431,13 +431,14 @@ where #[cfg(test)] mod tests { use actix_service::Service; + use bytes::Bytes; use futures::{Future, IntoFuture}; use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_service, init_service, TestRequest}; - use crate::{web, Error, HttpResponse}; + use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; + use crate::{web, Error, HttpRequest, HttpResponse}; #[test] fn test_default_resource() { @@ -598,4 +599,26 @@ mod tests { HeaderValue::from_static("0001") ); } + + #[test] + fn test_external_resource() { + let mut srv = init_service( + App::new() + .external_resource("youtube", "https://youtube.com/watch/{video_id}") + .route( + "/test", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["12345"]).unwrap() + )) + }), + ), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp); + assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); + } } diff --git a/src/app_service.rs b/src/app_service.rs index 63bf84e7..7229a230 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -162,7 +162,7 @@ where } } - if self.endpoint.is_some() { + if self.endpoint.is_some() && self.data.is_empty() { Ok(Async::Ready(AppInitService { service: self.endpoint.take().unwrap(), rmap: self.rmap.clone(), diff --git a/src/config.rs b/src/config.rs index a8caba4d..4c4bfa22 100644 --- a/src/config.rs +++ b/src/config.rs @@ -253,11 +253,14 @@ impl ServiceConfig { #[cfg(test)] mod tests { use actix_service::Service; + use bytes::Bytes; + use futures::Future; + use tokio_timer::sleep; use super::*; use crate::http::{Method, StatusCode}; - use crate::test::{block_on, call_service, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; + use crate::{web, App, HttpRequest, HttpResponse}; #[test] fn test_data() { @@ -277,7 +280,12 @@ mod tests { #[test] fn test_data_factory() { let cfg = |cfg: &mut ServiceConfig| { - cfg.data_factory(|| Ok::<_, ()>(10usize)); + cfg.data_factory(|| { + sleep(std::time::Duration::from_millis(50)).then(|_| { + println!("READY"); + Ok::<_, ()>(10usize) + }) + }); }; let mut srv = @@ -301,6 +309,33 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + #[test] + fn test_external_resource() { + let mut srv = init_service( + App::new() + .configure(|cfg| { + cfg.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + }) + .route( + "/test", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["12345"]).unwrap() + )) + }), + ), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp); + assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); + } + #[test] fn test_service() { let mut srv = init_service(App::new().configure(|cfg| { From 42644dac3f2827ac290d9f1e64591c35dbce395f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 07:31:33 -0700 Subject: [PATCH 1333/1635] prepare actix-http-test release --- Cargo.toml | 2 +- test-server/CHANGES.md | 5 +++++ test-server/Cargo.toml | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a886b5fc..f4720a1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.1.2", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.1.0", features=["ssl"] } +actix-http-test = { version = "0.1.1", features=["ssl"] } actix-files = { version = "0.1.0-beta.1" } rand = "0.6" env_logger = "0.6" diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index cec01fde..700b3aa1 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.1] - 2019-04-24 + +* Always make new connection for http client + + ## [0.1.0] - 2019-04-16 * No changes diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 657dd261..906c9d38 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.2" actix-service = "0.3.6" actix-server = "0.4.3" actix-utils = "0.3.5" -awc = "0.1.0" +awc = "0.1.1" base64 = "0.10" bytes = "0.4" @@ -55,5 +55,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-alpha.5" -actix-http = "0.1.0" +actix-web = "1.0.0-beta.1" +actix-http = "0.1.2" From 679d1cd51360f62fe5f0084893591b6003671091 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 10:25:46 -0700 Subject: [PATCH 1334/1635] allow to override responder's status code and headers --- CHANGES.md | 2 + src/responder.rs | 193 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 193 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 56536226..81be2a34 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ### Added +* Extend `Responder` trait, allow to override status code and headers. + * Add helper functions for reading response body `test::read_body()` * Added support for `remainder match` (i.e "/path/{tail}*") diff --git a/src/responder.rs b/src/responder.rs index 103009e7..f7f2a8b3 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,8 +1,12 @@ use actix_http::error::InternalError; -use actix_http::{http::StatusCode, Error, Response, ResponseBuilder}; +use actix_http::http::{ + header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, HttpTryFrom, + StatusCode, +}; +use actix_http::{Error, Response, ResponseBuilder}; use bytes::{Bytes, BytesMut}; use futures::future::{err, ok, Either as EitherFuture, FutureResult}; -use futures::{Future, IntoFuture, Poll}; +use futures::{try_ready, Async, Future, IntoFuture, Poll}; use crate::request::HttpRequest; @@ -18,6 +22,51 @@ pub trait Responder { /// Convert itself to `AsyncResult` or `Error`. fn respond_to(self, req: &HttpRequest) -> Self::Future; + + /// Override a status code for a responder. + /// + /// ```rust + /// use actix_web::{HttpRequest, Responder, http::StatusCode}; + /// + /// fn index(req: HttpRequest) -> impl Responder { + /// "Welcome!".with_status(StatusCode::OK) + /// } + /// # fn main() {} + /// ``` + fn with_status(self, status: StatusCode) -> CustomResponder + where + Self: Sized, + { + CustomResponder::new(self).with_status(status) + } + + /// Add extra header to the responder's response. + /// + /// ```rust + /// use actix_web::{web, HttpRequest, Responder}; + /// use serde::Serialize; + /// + /// #[derive(Serialize)] + /// struct MyObj { + /// name: String, + /// } + /// + /// fn index(req: HttpRequest) -> impl Responder { + /// web::Json( + /// MyObj{name: "Name".to_string()} + /// ) + /// .with_header("x-version", "1.2.3") + /// } + /// # fn main() {} + /// ``` + fn with_header(self, key: K, value: V) -> CustomResponder + where + Self: Sized, + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + CustomResponder::new(self).with_header(key, value) + } } impl Responder for Response { @@ -154,6 +203,117 @@ impl Responder for BytesMut { } } +/// Allows to override status code and headers for a responder. +pub struct CustomResponder { + responder: T, + status: Option, + headers: Option, + error: Option, +} + +impl CustomResponder { + fn new(responder: T) -> Self { + CustomResponder { + responder, + status: None, + headers: None, + error: None, + } + } + + /// Override a status code for the responder's response. + /// + /// ```rust + /// use actix_web::{HttpRequest, Responder, http::StatusCode}; + /// + /// fn index(req: HttpRequest) -> impl Responder { + /// "Welcome!".with_status(StatusCode::OK) + /// } + /// # fn main() {} + /// ``` + pub fn with_status(mut self, status: StatusCode) -> Self { + self.status = Some(status); + self + } + + /// Add extra header to the responder's response. + /// + /// ```rust + /// use actix_web::{web, HttpRequest, Responder}; + /// use serde::Serialize; + /// + /// #[derive(Serialize)] + /// struct MyObj { + /// name: String, + /// } + /// + /// fn index(req: HttpRequest) -> impl Responder { + /// web::Json( + /// MyObj{name: "Name".to_string()} + /// ) + /// .with_header("x-version", "1.2.3") + /// } + /// # fn main() {} + /// ``` + pub fn with_header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + if self.headers.is_none() { + self.headers = Some(HeaderMap::new()); + } + + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => { + self.headers.as_mut().unwrap().append(key, value); + } + Err(e) => self.error = Some(e.into()), + }, + Err(e) => self.error = Some(e.into()), + }; + self + } +} + +impl Responder for CustomResponder { + type Error = T::Error; + type Future = CustomResponderFut; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + CustomResponderFut { + fut: self.responder.respond_to(req).into_future(), + status: self.status, + headers: self.headers, + } + } +} + +pub struct CustomResponderFut { + fut: ::Future, + status: Option, + headers: Option, +} + +impl Future for CustomResponderFut { + type Item = Response; + type Error = T::Error; + + fn poll(&mut self) -> Poll { + let mut res = try_ready!(self.fut.poll()); + if let Some(status) = self.status { + *res.status_mut() = status; + } + if let Some(ref headers) = self.headers { + for (k, v) in headers { + res.headers_mut().insert(k.clone(), v.clone()); + } + } + Ok(Async::Ready(res)) + } +} + /// Combines two different responder types into a single type /// /// ```rust @@ -435,4 +595,33 @@ pub(crate) mod tests { ); assert!(res.is_err()); } + + #[test] + fn test_custom_responder() { + let req = TestRequest::default().to_http_request(); + let res = block_on( + "test" + .to_string() + .with_status(StatusCode::BAD_REQUEST) + .respond_to(&req), + ) + .unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!(res.body().bin_ref(), b"test"); + + let res = block_on( + "test" + .to_string() + .with_header("content-type", "json") + .respond_to(&req), + ) + .unwrap(); + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); + } } From 64f603b0766e52e21b1bf9bacdf8e2e250c54f31 Mon Sep 17 00:00:00 2001 From: Peter Ding Date: Thu, 25 Apr 2019 01:48:49 +0800 Subject: [PATCH 1335/1635] Support to set header names of `ClientRequest` as Camel-Case (#713) * Support to set header names of `ClientRequest` as Camel-Case This is the case for supporting to request for servers which don't perfectly implement the `RFC 7230`. It is important for an app which uses `ClientRequest` as core part. * Add field `upper_camel_case_headers` to `ClientRequest`. * Add function `set_upper_camel_case_headers` to `ClientRequest` and `ClientRequestBuilder` to set field `upper_camel_case_headers`. * Add trait `client::writer::UpperCamelCaseHeader` for `http::header::HeaderName`, let it can be converted to Camel-Case then writed to buffer. * Add test `test_client::test_upper_camel_case_headers`. * Support upper Camel-Case headers * [actix-http] Add field `upper_camel_case_headers` for `RequestHead` * [actix-http] Add code for `MessageType` to support upper camel case * [awc] Add functions for `ClientRequest` to set upper camel case * Use `Flags::CAMEL_CASE` for upper camel case of headers --- actix-http/src/h1/encoder.rs | 36 ++++++++++++++++++++++++++++++++++++ actix-http/src/message.rs | 18 ++++++++++++++++++ awc/src/request.rs | 14 ++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 8f98fe67..177661b5 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -43,6 +43,10 @@ pub(crate) trait MessageType: Sized { fn headers(&self) -> &HeaderMap; + fn upper_camel_case(&self) -> bool { + false + } + fn chunked(&self) -> bool; fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>; @@ -221,6 +225,10 @@ impl MessageType for RequestHead { self.chunked() } + fn upper_camel_case(&self) -> bool { + self.upper_camel_case_headers() + } + fn headers(&self) -> &HeaderMap { &self.headers } @@ -418,6 +426,34 @@ impl<'a> io::Write for Writer<'a> { } } +fn write_upper_camel_case(value: &[u8], buffer: &mut [u8]) { + let mut index = 0; + let key = value; + let mut key_iter = key.iter(); + + if let Some(c) = key_iter.next() { + if *c >= b'a' && *c <= b'z' { + buffer[index] = *c ^ b' '; + index += 1; + } + } else { + return; + } + + while let Some(c) = key_iter.next() { + buffer[index] = *c; + index += 1; + if *c == b'-' { + if let Some(c) = key_iter.next() { + if *c >= b'a' && *c <= b'z' { + buffer[index] = *c ^ b' '; + index += 1; + } + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 7f2dc603..f3129c75 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -27,6 +27,7 @@ bitflags! { const UPGRADE = 0b0000_0100; const EXPECT = 0b0000_1000; const NO_CHUNKING = 0b0001_0000; + const CAMEL_CASE = 0b0010_0000; } } @@ -97,6 +98,23 @@ impl RequestHead { &mut self.headers } + /// Is to uppercase headers with Camel-Case. + /// Befault is `false` + #[inline] + pub fn upper_camel_case_headers(&self) -> bool { + self.flags.contains(Flags::CAMEL_CASE) + } + + /// Set `true` to send headers which are uppercased with Camel-Case. + #[inline] + pub fn set_upper_camel_case_headers(&mut self, val: bool) { + if val { + self.flags.insert(Flags::CAMEL_CASE); + } else { + self.flags.remove(Flags::CAMEL_CASE); + } + } + #[inline] /// Set connection type of the message pub fn set_connection_type(&mut self, ctype: ConnectionType) { diff --git a/awc/src/request.rs b/awc/src/request.rs index 2e603264..d99a9418 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -235,6 +235,20 @@ impl ClientRequest { self } + /// Is to uppercase headers with Camel-Case. + /// Befault is `false` + #[inline] + pub fn upper_camel_case_headers(&self) -> bool { + self.head.upper_camel_case_headers() + } + + /// Set `true` to send headers which are uppercased with Camel-Case. + #[inline] + pub fn set_upper_camel_case_headers(&mut self, value: bool) -> &mut Self { + self.head.set_upper_camel_case_headers(value); + self + } + /// Force close connection instead of returning it back to connections pool. /// This setting affect only http/1 connections. #[inline] From 2e19f572ee9b4270508408fd9521dffb22eb773e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 11:27:57 -0700 Subject: [PATCH 1336/1635] add tests for camel case headers rendering --- actix-http/CHANGES.md | 5 ++ actix-http/src/h1/encoder.rs | 117 ++++++++++++++++++++++++++++---- actix-http/src/message.rs | 4 +- actix-http/tests/test_client.rs | 2 - awc/CHANGES.md | 4 ++ awc/src/request.rs | 13 +--- awc/tests/test_client.rs | 4 ++ 7 files changed, 123 insertions(+), 26 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 37d0eec6..c51b421c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,10 @@ # Changes +### Added + +* Allow to render h1 request headers in `Camel-Case` + + ## [0.1.3] - 2019-04-23 ### Fixed diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 177661b5..60bf2262 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -43,7 +43,7 @@ pub(crate) trait MessageType: Sized { fn headers(&self) -> &HeaderMap; - fn upper_camel_case(&self) -> bool { + fn camel_case(&self) -> bool { false } @@ -61,6 +61,7 @@ pub(crate) trait MessageType: Sized { ) -> io::Result<()> { let chunked = self.chunked(); let mut skip_len = length != BodySize::Stream; + let camel_case = self.camel_case(); // Content length if let Some(status) = self.status() { @@ -78,18 +79,30 @@ pub(crate) trait MessageType: Sized { match length { BodySize::Stream => { if chunked { - dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n") + if camel_case { + dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n") + } else { + dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n") + } } else { skip_len = false; dst.put_slice(b"\r\n"); } } BodySize::Empty => { - dst.put_slice(b"\r\ncontent-length: 0\r\n"); + if camel_case { + dst.put_slice(b"\r\nContent-Length: 0\r\n"); + } else { + dst.put_slice(b"\r\ncontent-length: 0\r\n"); + } } BodySize::Sized(len) => helpers::write_content_length(len, dst), BodySize::Sized64(len) => { - dst.put_slice(b"\r\ncontent-length: "); + if camel_case { + dst.put_slice(b"\r\nContent-Length: "); + } else { + dst.put_slice(b"\r\ncontent-length: "); + } write!(dst.writer(), "{}\r\n", len)?; } BodySize::None => dst.put_slice(b"\r\n"), @@ -99,10 +112,18 @@ pub(crate) trait MessageType: Sized { match ctype { ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"), ConnectionType::KeepAlive if version < Version::HTTP_11 => { - dst.put_slice(b"connection: keep-alive\r\n") + if camel_case { + dst.put_slice(b"Connection: keep-alive\r\n") + } else { + dst.put_slice(b"connection: keep-alive\r\n") + } } ConnectionType::Close if version >= Version::HTTP_11 => { - dst.put_slice(b"connection: close\r\n") + if camel_case { + dst.put_slice(b"Connection: close\r\n") + } else { + dst.put_slice(b"connection: close\r\n") + } } _ => (), } @@ -137,7 +158,12 @@ pub(crate) trait MessageType: Sized { buf = &mut *(dst.bytes_mut() as *mut _); } } - buf[pos..pos + k.len()].copy_from_slice(k); + // use upper Camel-Case + if camel_case { + write_camel_case(k, &mut buf[pos..pos + k.len()]); + } else { + buf[pos..pos + k.len()].copy_from_slice(k); + } pos += k.len(); buf[pos..pos + 2].copy_from_slice(b": "); pos += 2; @@ -162,7 +188,12 @@ pub(crate) trait MessageType: Sized { buf = &mut *(dst.bytes_mut() as *mut _); } } - buf[pos..pos + k.len()].copy_from_slice(k); + // use upper Camel-Case + if camel_case { + write_camel_case(k, &mut buf[pos..pos + k.len()]); + } else { + buf[pos..pos + k.len()].copy_from_slice(k); + } pos += k.len(); buf[pos..pos + 2].copy_from_slice(b": "); pos += 2; @@ -225,8 +256,8 @@ impl MessageType for RequestHead { self.chunked() } - fn upper_camel_case(&self) -> bool { - self.upper_camel_case_headers() + fn camel_case(&self) -> bool { + RequestHead::camel_case_headers(self) } fn headers(&self) -> &HeaderMap { @@ -426,7 +457,7 @@ impl<'a> io::Write for Writer<'a> { } } -fn write_upper_camel_case(value: &[u8], buffer: &mut [u8]) { +fn write_camel_case(value: &[u8], buffer: &mut [u8]) { let mut index = 0; let key = value; let mut key_iter = key.iter(); @@ -456,9 +487,11 @@ fn write_upper_camel_case(value: &[u8], buffer: &mut [u8]) { #[cfg(test)] mod tests { - use super::*; use bytes::Bytes; + use super::*; + use crate::http::header::{HeaderValue, CONTENT_TYPE}; + #[test] fn test_chunked_te() { let mut bytes = BytesMut::new(); @@ -472,4 +505,64 @@ mod tests { Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") ); } + + #[test] + fn test_camel_case() { + let mut bytes = BytesMut::with_capacity(2048); + let mut head = RequestHead::default(); + head.set_camel_case_headers(true); + head.headers.insert(DATE, HeaderValue::from_static("date")); + head.headers + .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); + + let _ = head.encode_headers( + &mut bytes, + Version::HTTP_11, + BodySize::Empty, + ConnectionType::Close, + &ServiceConfig::default(), + ); + assert_eq!( + bytes.take().freeze(), + Bytes::from_static(b"\r\nContent-Length: 0\r\nConnection: close\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") + ); + + let _ = head.encode_headers( + &mut bytes, + Version::HTTP_11, + BodySize::Stream, + ConnectionType::KeepAlive, + &ServiceConfig::default(), + ); + assert_eq!( + bytes.take().freeze(), + Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") + ); + + let _ = head.encode_headers( + &mut bytes, + Version::HTTP_11, + BodySize::Sized64(100), + ConnectionType::KeepAlive, + &ServiceConfig::default(), + ); + assert_eq!( + bytes.take().freeze(), + Bytes::from_static(b"\r\nContent-Length: 100\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") + ); + + head.headers + .append(CONTENT_TYPE, HeaderValue::from_static("xml")); + let _ = head.encode_headers( + &mut bytes, + Version::HTTP_11, + BodySize::Stream, + ConnectionType::KeepAlive, + &ServiceConfig::default(), + ); + assert_eq!( + bytes.take().freeze(), + Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: xml\r\nContent-Type: plain/text\r\n\r\n") + ); + } } diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index f3129c75..c279aaeb 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -101,13 +101,13 @@ impl RequestHead { /// Is to uppercase headers with Camel-Case. /// Befault is `false` #[inline] - pub fn upper_camel_case_headers(&self) -> bool { + pub fn camel_case_headers(&self) -> bool { self.flags.contains(Flags::CAMEL_CASE) } /// Set `true` to send headers which are uppercased with Camel-Case. #[inline] - pub fn set_upper_camel_case_headers(&mut self, val: bool) { + pub fn set_camel_case_headers(&mut self, val: bool) { if val { self.flags.insert(Flags::CAMEL_CASE); } else { diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 6d382478..a4f1569c 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -59,9 +59,7 @@ fn test_connection_close() { .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) .map(|_| ()) }); - println!("REQ: {:?}", srv.get("/").force_close()); let response = srv.block_on(srv.get("/").force_close().send()).unwrap(); - println!("RES: {:?}", response); assert!(response.status().is_success()); } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 30fd4a6d..10ab87bd 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,9 @@ # Changes +### Added + +* Allow to send headers in `Camel-Case` form. + ## [0.1.1] - 2019-04-19 ### Added diff --git a/awc/src/request.rs b/awc/src/request.rs index d99a9418..5c09df81 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -235,17 +235,10 @@ impl ClientRequest { self } - /// Is to uppercase headers with Camel-Case. - /// Befault is `false` + /// Send headers in `Camel-Case` form. #[inline] - pub fn upper_camel_case_headers(&self) -> bool { - self.head.upper_camel_case_headers() - } - - /// Set `true` to send headers which are uppercased with Camel-Case. - #[inline] - pub fn set_upper_camel_case_headers(&mut self, value: bool) -> &mut Self { - self.head.set_upper_camel_case_headers(value); + pub fn camel_case(mut self) -> Self { + self.head.set_camel_case_headers(true); self } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 7e2dc6ba..94684dd9 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -90,6 +90,10 @@ fn test_simple() { // read response let bytes = srv.block_on(response.body()).unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + + // camel case + let response = srv.block_on(srv.post("/").camel_case().send()).unwrap(); + assert!(response.status().is_success()); } #[test] From f429d3319fe7362b17d996ecca5e5996e6d741c7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 11:57:40 -0700 Subject: [PATCH 1337/1635] Read until eof for http/1.0 responses #771 --- actix-http/CHANGES.md | 4 ++++ actix-http/src/h1/decoder.rs | 22 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c51b421c..78245692 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,10 @@ * Allow to render h1 request headers in `Camel-Case` +### Fixed + +* Read until eof for http/1.0 responses #771 + ## [0.1.3] - 2019-04-23 diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 411649fc..12419d66 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -300,7 +300,13 @@ impl MessageType for ResponseHead { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); return Err(ParseError::TooLarge); } else { - PayloadType::None + // for HTTP/1.0 read to eof and close connection + if msg.version == Version::HTTP_10 { + msg.set_connection_type(ConnectionType::Close); + PayloadType::Payload(PayloadDecoder::eof()) + } else { + PayloadType::None + } }; Ok(Some((msg, decoder))) @@ -331,7 +337,7 @@ impl HeaderIndex { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] /// Http payload item pub enum PayloadItem { Chunk(Bytes), @@ -1191,4 +1197,16 @@ mod tests { let msg = pl.decode(&mut buf).unwrap().unwrap(); assert!(msg.eof()); } + + #[test] + fn test_response_http10_read_until_eof() { + let mut buf = BytesMut::from(&"HTTP/1.0 200 Ok\r\n\r\ntest data"[..]); + + let mut reader = MessageDecoder::::default(); + let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); + let mut pl = pl.unwrap(); + + let chunk = pl.decode(&mut buf).unwrap().unwrap(); + assert_eq!(chunk, PayloadItem::Chunk(Bytes::from_static(b"test data"))); + } } From 60fa0d5427c8f15a05a33502426924c4ad6a8051 Mon Sep 17 00:00:00 2001 From: Maciej Piechotka Date: Wed, 24 Apr 2019 12:49:56 -0700 Subject: [PATCH 1338/1635] Store visit and login timestamp in the identity cookie (#502) This allows to verify time of login or last visit and therfore limiting the danger of leaked cookies. --- src/middleware/identity.rs | 436 ++++++++++++++++++++++++++++++++----- 1 file changed, 381 insertions(+), 55 deletions(-) diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index ba03366f..7a7604d6 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -49,10 +49,12 @@ //! ``` use std::cell::RefCell; use std::rc::Rc; +use std::time::SystemTime; use actix_service::{Service, Transform}; use futures::future::{ok, Either, FutureResult}; use futures::{Future, IntoFuture, Poll}; +use serde::{Deserialize, Serialize}; use time::Duration; use crate::cookie::{Cookie, CookieJar, Key, SameSite}; @@ -284,84 +286,133 @@ where struct CookieIdentityInner { key: Key, + key_v2: Key, name: String, path: String, domain: Option, secure: bool, max_age: Option, same_site: Option, + visit_deadline: Option, + login_deadline: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +struct CookieValue { + identity: String, + #[serde(skip_serializing_if = "Option::is_none")] + login_timestamp: Option, + #[serde(skip_serializing_if = "Option::is_none")] + visit_timestamp: Option, +} + +#[derive(Debug)] +struct CookieIdentityExtention { + login_timestamp: Option } impl CookieIdentityInner { fn new(key: &[u8]) -> CookieIdentityInner { + let key_v2: Vec = key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); CookieIdentityInner { key: Key::from_master(key), + key_v2: Key::from_master(&key_v2), name: "actix-identity".to_owned(), path: "/".to_owned(), domain: None, secure: true, max_age: None, same_site: None, + visit_deadline: None, + login_deadline: None, } } fn set_cookie( &self, resp: &mut ServiceResponse, - id: Option, + value: Option, ) -> Result<()> { - let some = id.is_some(); - { - let id = id.unwrap_or_else(String::new); - let mut cookie = Cookie::new(self.name.clone(), id); - cookie.set_path(self.path.clone()); - cookie.set_secure(self.secure); - cookie.set_http_only(true); + let add_cookie = value.is_some(); + let val = value.map(|val| if !self.legacy_supported() { + serde_json::to_string(&val) + } else { + Ok(val.identity) + }); + let mut cookie = Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?); + cookie.set_path(self.path.clone()); + cookie.set_secure(self.secure); + cookie.set_http_only(true); - if let Some(ref domain) = self.domain { - cookie.set_domain(domain.clone()); - } - - if let Some(max_age) = self.max_age { - cookie.set_max_age(max_age); - } - - if let Some(same_site) = self.same_site { - cookie.set_same_site(same_site); - } - - let mut jar = CookieJar::new(); - if some { - jar.private(&self.key).add(cookie); - } else { - jar.add_original(cookie.clone()); - jar.private(&self.key).remove(cookie); - } - - for cookie in jar.delta() { - let val = HeaderValue::from_str(&cookie.to_string())?; - resp.headers_mut().append(header::SET_COOKIE, val); - } + if let Some(ref domain) = self.domain { + cookie.set_domain(domain.clone()); } + if let Some(max_age) = self.max_age { + cookie.set_max_age(max_age); + } + + if let Some(same_site) = self.same_site { + cookie.set_same_site(same_site); + } + + let mut jar = CookieJar::new(); + let key = if self.legacy_supported() {&self.key} else {&self.key_v2}; + if add_cookie { + jar.private(&key).add(cookie); + } else { + jar.add_original(cookie.clone()); + jar.private(&key).remove(cookie); + } + for cookie in jar.delta() { + let val = HeaderValue::from_str(&cookie.to_string())?; + resp.headers_mut().append(header::SET_COOKIE, val); + } Ok(()) } - fn load(&self, req: &ServiceRequest) -> Option { - if let Ok(cookies) = req.cookies() { - for cookie in cookies.iter() { - if cookie.name() == self.name { - let mut jar = CookieJar::new(); - jar.add_original(cookie.clone()); + fn load(&self, req: &ServiceRequest) -> Option { + let cookie = req.cookie(&self.name)?; + let mut jar = CookieJar::new(); + jar.add_original(cookie.clone()); + let res = if self.legacy_supported() { + jar.private(&self.key).get(&self.name).map(|n| CookieValue { + identity: n.value().to_string(), + login_timestamp: None, + visit_timestamp: None + }) + } else { + None + }; + res.or_else(|| jar.private(&self.key_v2).get(&self.name).and_then(|c| self.parse(c))) + } - let cookie_opt = jar.private(&self.key).get(&self.name); - if let Some(cookie) = cookie_opt { - return Some(cookie.value().into()); - } - } + fn parse(&self, cookie: Cookie) -> Option { + let value: CookieValue = serde_json::from_str(cookie.value()).ok()?; + let now = SystemTime::now(); + if let Some(visit_deadline) = self.visit_deadline { + if now.duration_since(value.visit_timestamp?).ok()? > visit_deadline.to_std().ok()? { + return None; } } - None + if let Some(login_deadline) = self.login_deadline { + if now.duration_since(value.login_timestamp?).ok()? > login_deadline.to_std().ok()? { + return None; + } + } + Some(value) + } + + fn legacy_supported(&self) -> bool { + self.visit_deadline.is_none() && self.login_deadline.is_none() + } + + fn always_update_cookie(&self) -> bool { + self.visit_deadline.is_some() + } + + fn requires_oob_data(&self) -> bool { + self.login_deadline.is_some() } } @@ -443,6 +494,18 @@ impl CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site); self } + + /// Accepts only users whose cookie has been seen before the given deadline + pub fn visit_deadline(mut self, value: Duration) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().visit_deadline = Some(value); + self + } + + /// Accepts only users which has been authenticated before the given deadline + pub fn login_deadline(mut self, value: Duration) -> CookieIdentityPolicy { + Rc::get_mut(&mut self.0).unwrap().login_deadline = Some(value); + self + } } impl IdentityPolicy for CookieIdentityPolicy { @@ -450,7 +513,12 @@ impl IdentityPolicy for CookieIdentityPolicy { type ResponseFuture = Result<(), Error>; fn from_request(&self, req: &mut ServiceRequest) -> Self::Future { - Ok(self.0.load(req)) + Ok(self.0.load(req).map(|CookieValue {identity, login_timestamp, ..}| { + if self.0.requires_oob_data() { + req.extensions_mut().insert(CookieIdentityExtention { login_timestamp }); + } + identity + })) } fn to_response( @@ -459,9 +527,28 @@ impl IdentityPolicy for CookieIdentityPolicy { changed: bool, res: &mut ServiceResponse, ) -> Self::ResponseFuture { - if changed { - let _ = self.0.set_cookie(res, id); - } + let _ = if changed { + let login_timestamp = SystemTime::now(); + self.0.set_cookie(res, id.map(|identity| CookieValue { + identity, + login_timestamp: self.0.login_deadline.map(|_| login_timestamp), + visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp) + })) + } else if self.0.always_update_cookie() && id.is_some() { + let visit_timestamp = SystemTime::now(); + let mut login_timestamp = None; + if self.0.requires_oob_data() { + let CookieIdentityExtention { login_timestamp: lt } = res.request().extensions_mut().remove().unwrap(); + login_timestamp = lt; + } + self.0.set_cookie(res, Some(CookieValue { + identity: id.unwrap(), + login_timestamp, + visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp) + })) + } else { + Ok(()) + }; Ok(()) } } @@ -473,14 +560,20 @@ mod tests { use crate::test::{self, TestRequest}; use crate::{web, App, HttpResponse}; + use std::borrow::Borrow; + + const COOKIE_KEY_MASTER: [u8; 32] = [0; 32]; + const COOKIE_NAME: &'static str = "actix_auth"; + const COOKIE_LOGIN: &'static str = "test"; + #[test] fn test_identity() { let mut srv = test::init_service( App::new() .wrap(IdentityService::new( - CookieIdentityPolicy::new(&[0; 32]) + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) .domain("www.rust-lang.org") - .name("actix_auth") + .name(COOKIE_NAME) .path("/") .secure(true), )) @@ -492,7 +585,7 @@ mod tests { } })) .service(web::resource("/login").to(|id: Identity| { - id.remember("test".to_string()); + id.remember(COOKIE_LOGIN.to_string()); HttpResponse::Ok() })) .service(web::resource("/logout").to(|id: Identity| { @@ -537,9 +630,9 @@ mod tests { let mut srv = test::init_service( App::new() .wrap(IdentityService::new( - CookieIdentityPolicy::new(&[0; 32]) + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) .domain("www.rust-lang.org") - .name("actix_auth") + .name(COOKIE_NAME) .path("/") .max_age_time(duration) .secure(true), @@ -563,9 +656,9 @@ mod tests { let mut srv = test::init_service( App::new() .wrap(IdentityService::new( - CookieIdentityPolicy::new(&[0; 32]) + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) .domain("www.rust-lang.org") - .name("actix_auth") + .name(COOKIE_NAME) .path("/") .max_age(seconds) .secure(true), @@ -582,4 +675,237 @@ mod tests { let c = resp.response().cookies().next().unwrap().to_owned(); assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); } + + fn create_identity_server CookieIdentityPolicy + Sync + Send + Clone + 'static>(f: F) -> impl actix_service::Service, Error = actix_http::Error> { + test::init_service( + App::new() + .wrap(IdentityService::new(f(CookieIdentityPolicy::new(&COOKIE_KEY_MASTER).secure(false).name(COOKIE_NAME)))) + .service(web::resource("/").to(|id: Identity| { + let identity = id.identity(); + if identity.is_none() { + id.remember(COOKIE_LOGIN.to_string()) + } + web::Json(identity) + })) + ) + } + + fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> { + let mut jar = CookieJar::new(); + jar.private(&Key::from_master(&COOKIE_KEY_MASTER)).add(Cookie::new(COOKIE_NAME, identity)); + jar.get(COOKIE_NAME).unwrap().clone() + } + + fn login_cookie(identity: &'static str, login_timestamp: Option, visit_timestamp: Option) -> Cookie<'static> { + let mut jar = CookieJar::new(); + let key: Vec = COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); + jar.private(&Key::from_master(&key)).add(Cookie::new(COOKIE_NAME, serde_json::to_string(&CookieValue { + identity: identity.to_string(), + login_timestamp, + visit_timestamp + }).unwrap())); + jar.get(COOKIE_NAME).unwrap().clone() + } + + fn assert_logged_in(response: &mut ServiceResponse, identity: Option<&str>) { + use bytes::BytesMut; + use futures::Stream; + let bytes = + test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { + b.extend(c); + Ok::<_, Error>(b) + })) + .unwrap(); + let resp: Option = serde_json::from_slice(&bytes[..]).unwrap(); + assert_eq!(resp.as_ref().map(|s| s.borrow()), identity); + } + + fn assert_legacy_login_cookie(response: &mut ServiceResponse, identity: &str) { + let mut cookies = CookieJar::new(); + for cookie in response.headers().get_all(header::SET_COOKIE) { + cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap()); + } + let cookie = cookies.private(&Key::from_master(&COOKIE_KEY_MASTER)).get(COOKIE_NAME).unwrap(); + assert_eq!(cookie.value(), identity); + } + + enum LoginTimestampCheck { + NoTimestamp, + NewTimestamp, + OldTimestamp(SystemTime) + } + + enum VisitTimeStampCheck { + NoTimestamp, + NewTimestamp + } + + fn assert_login_cookie(response: &mut ServiceResponse, identity: &str, login_timestamp: LoginTimestampCheck, visit_timestamp: VisitTimeStampCheck) { + let mut cookies = CookieJar::new(); + for cookie in response.headers().get_all(header::SET_COOKIE) { + cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap()); + } + let key: Vec = COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); + let cookie = cookies.private(&Key::from_master(&key)).get(COOKIE_NAME).unwrap(); + let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap(); + assert_eq!(cv.identity, identity); + let now = SystemTime::now(); + let t30sec_ago = now - Duration::seconds(30).to_std().unwrap(); + match login_timestamp { + LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None), + LoginTimestampCheck::NewTimestamp => assert!(t30sec_ago <= cv.login_timestamp.unwrap() && cv.login_timestamp.unwrap() <= now), + LoginTimestampCheck::OldTimestamp(old_timestamp) => assert_eq!(cv.login_timestamp, Some(old_timestamp)) + } + match visit_timestamp { + VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None), + VisitTimeStampCheck::NewTimestamp => assert!(t30sec_ago <= cv.visit_timestamp.unwrap() && cv.visit_timestamp.unwrap() <= now) + } + } + + fn assert_no_login_cookie(response: &mut ServiceResponse) { + let mut cookies = CookieJar::new(); + for cookie in response.headers().get_all(header::SET_COOKIE) { + cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap()); + } + assert!(cookies.get(COOKIE_NAME).is_none()); + } + + #[test] + fn test_identity_legacy_cookie_is_set() { + let mut srv = create_identity_server(|c| c); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .to_request() + ); + assert_logged_in(&mut resp, None); + assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN); + } + + #[test] + fn test_identity_legacy_cookie_works() { + let mut srv = create_identity_server(|c| c); + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); + assert_no_login_cookie(&mut resp); + } + + #[test] + fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() { + let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, None); + assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp); + } + + #[test] + fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() { + let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, None); + assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp); + } + + #[test] + fn test_identity_cookie_rejected_if_login_timestamp_needed() { + let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); + let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now())); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, None); + assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp); + } + + #[test] + fn test_identity_cookie_rejected_if_visit_timestamp_needed() { + let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); + let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, None); + assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp); + } + + #[test] + fn test_identity_cookie_rejected_if_login_timestamp_too_old() { + let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); + let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), None); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, None); + assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp); + } + + #[test] + fn test_identity_cookie_rejected_if_visit_timestamp_too_old() { + let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); + let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now() - Duration::days(180).to_std().unwrap())); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, None); + assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp); + } + + #[test] + fn test_identity_cookie_not_updated_on_login_deadline() { + let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); + let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); + assert_no_login_cookie(&mut resp); + } + + #[test] + fn test_identity_cookie_updated_on_visit_deadline() { + let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90)).login_deadline(Duration::days(90))); + let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap(); + let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp)); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request() + ); + assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); + assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::OldTimestamp(timestamp), VisitTimeStampCheck::NewTimestamp); + } } From 2bc937f6c3e828319ca79277ff06d6ea23962fef Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 12:50:44 -0700 Subject: [PATCH 1339/1635] prepare release --- actix-http/CHANGES.md | 2 ++ actix-http/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 78245692..d1a043d8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## [0.1.4] - 2019-04-24 + ### Added * Allow to render h1 request headers in `Camel-Case` diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 438754b3..9d044c64 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.3" +version = "0.1.4" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From 5426413cb6349219671f972ad2293331c91877cc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 13:00:30 -0700 Subject: [PATCH 1340/1635] update dependencies --- CHANGES.md | 2 ++ Cargo.toml | 7 ++++--- awc/CHANGES.md | 1 + awc/Cargo.toml | 6 +++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 81be2a34..2b637a73 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## [1.0.0-beta.2] - 2019-04-24 + ### Added * Extend `Responder` trait, allow to override status code and headers. diff --git a/Cargo.toml b/Cargo.toml index f4720a1c..d1855b22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-beta.1" +version = "1.0.0-beta.2" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -71,10 +71,11 @@ actix-utils = "0.3.4" actix-router = "0.1.3" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" -actix-http = { version = "0.1.2", features=["fail"] } +actix-http = { version = "0.1.4", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" +actix = { version = "0.8.1", features=["http"], optional = true } awc = { version = "0.1.1", optional = true } bytes = "0.4" @@ -98,7 +99,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.2", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.4", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.1", features=["ssl"] } actix-files = { version = "0.1.0-beta.1" } rand = "0.6" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 10ab87bd..124efc36 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -4,6 +4,7 @@ * Allow to send headers in `Camel-Case` form. + ## [0.1.1] - 2019-04-19 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e6018f44..8f64a3c6 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.3.6" -actix-http = "0.1.2" +actix-http = "0.1.4" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -56,8 +56,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" actix-web = { version = "1.0.0-beta.1", features=["ssl"] } -actix-http = { version = "0.1.2", features=["ssl"] } -actix-http-test = { version = "0.1.0", features=["ssl"] } +actix-http = { version = "0.1.4", features=["ssl"] } +actix-http-test = { version = "0.1.1", features=["ssl"] } actix-utils = "0.3.4" actix-server = { version = "0.4.3", features=["ssl"] } brotli2 = { version="0.3.2" } From 7300002226f46b9115f3db2a7ee28491dd664b0a Mon Sep 17 00:00:00 2001 From: Darin Date: Wed, 24 Apr 2019 16:21:42 -0400 Subject: [PATCH 1341/1635] grammar fixes (#796) --- src/responder.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/responder.rs b/src/responder.rs index f7f2a8b3..47a8800e 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -23,7 +23,7 @@ pub trait Responder { /// Convert itself to `AsyncResult` or `Error`. fn respond_to(self, req: &HttpRequest) -> Self::Future; - /// Override a status code for a responder. + /// Override a status code for a Responder. /// /// ```rust /// use actix_web::{HttpRequest, Responder, http::StatusCode}; @@ -40,7 +40,7 @@ pub trait Responder { CustomResponder::new(self).with_status(status) } - /// Add extra header to the responder's response. + /// Add header to the Responder's response. /// /// ```rust /// use actix_web::{web, HttpRequest, Responder}; @@ -221,7 +221,7 @@ impl CustomResponder { } } - /// Override a status code for the responder's response. + /// Override a status code for the Responder's response. /// /// ```rust /// use actix_web::{HttpRequest, Responder, http::StatusCode}; @@ -236,7 +236,7 @@ impl CustomResponder { self } - /// Add extra header to the responder's response. + /// Add header to the Responder's response. /// /// ```rust /// use actix_web::{web, HttpRequest, Responder}; From 3b3dbb4f40d7e1b6eb0bf23ed766cf075bdd6ffb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 15:29:15 -0700 Subject: [PATCH 1342/1635] add raw services support --- CHANGES.md | 6 +- src/lib.rs | 4 +- src/middleware/identity.rs | 268 +++++++++++++++++++++++++++---------- src/service.rs | 135 ++++++++++++++++++- src/web.rs | 23 ++++ 5 files changed, 356 insertions(+), 80 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2b637a73..003d7721 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,18 +4,18 @@ ### Added -* Extend `Responder` trait, allow to override status code and headers. +* Add raw services support via `web::service()` * Add helper functions for reading response body `test::read_body()` -* Added support for `remainder match` (i.e "/path/{tail}*") +* Add support for `remainder match` (i.e "/path/{tail}*") +* Extend `Responder` trait, allow to override status code and headers. ### Changed * `.to_async()` handler can return `Responder` type #792 - ### Fixed * Fix async web::Data factory handling diff --git a/src/lib.rs b/src/lib.rs index 6abf37c1..b578d87c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,7 +136,9 @@ pub mod dev { pub use crate::config::{AppConfig, AppService}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; - pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse}; + pub use crate::service::{ + HttpServiceFactory, ServiceRequest, ServiceResponse, WebService, + }; pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 7a7604d6..5e46bda2 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -308,12 +308,13 @@ struct CookieValue { #[derive(Debug)] struct CookieIdentityExtention { - login_timestamp: Option + login_timestamp: Option, } impl CookieIdentityInner { fn new(key: &[u8]) -> CookieIdentityInner { - let key_v2: Vec = key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); + let key_v2: Vec = + key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); CookieIdentityInner { key: Key::from_master(key), key_v2: Key::from_master(&key_v2), @@ -334,12 +335,15 @@ impl CookieIdentityInner { value: Option, ) -> Result<()> { let add_cookie = value.is_some(); - let val = value.map(|val| if !self.legacy_supported() { - serde_json::to_string(&val) - } else { - Ok(val.identity) + let val = value.map(|val| { + if !self.legacy_supported() { + serde_json::to_string(&val) + } else { + Ok(val.identity) + } }); - let mut cookie = Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?); + let mut cookie = + Cookie::new(self.name.clone(), val.unwrap_or_else(|| Ok(String::new()))?); cookie.set_path(self.path.clone()); cookie.set_secure(self.secure); cookie.set_http_only(true); @@ -357,7 +361,11 @@ impl CookieIdentityInner { } let mut jar = CookieJar::new(); - let key = if self.legacy_supported() {&self.key} else {&self.key_v2}; + let key = if self.legacy_supported() { + &self.key + } else { + &self.key_v2 + }; if add_cookie { jar.private(&key).add(cookie); } else { @@ -379,24 +387,32 @@ impl CookieIdentityInner { jar.private(&self.key).get(&self.name).map(|n| CookieValue { identity: n.value().to_string(), login_timestamp: None, - visit_timestamp: None + visit_timestamp: None, }) } else { None }; - res.or_else(|| jar.private(&self.key_v2).get(&self.name).and_then(|c| self.parse(c))) + res.or_else(|| { + jar.private(&self.key_v2) + .get(&self.name) + .and_then(|c| self.parse(c)) + }) } fn parse(&self, cookie: Cookie) -> Option { let value: CookieValue = serde_json::from_str(cookie.value()).ok()?; let now = SystemTime::now(); if let Some(visit_deadline) = self.visit_deadline { - if now.duration_since(value.visit_timestamp?).ok()? > visit_deadline.to_std().ok()? { + if now.duration_since(value.visit_timestamp?).ok()? + > visit_deadline.to_std().ok()? + { return None; } } if let Some(login_deadline) = self.login_deadline { - if now.duration_since(value.login_timestamp?).ok()? > login_deadline.to_std().ok()? { + if now.duration_since(value.login_timestamp?).ok()? + > login_deadline.to_std().ok()? + { return None; } } @@ -513,12 +529,19 @@ impl IdentityPolicy for CookieIdentityPolicy { type ResponseFuture = Result<(), Error>; fn from_request(&self, req: &mut ServiceRequest) -> Self::Future { - Ok(self.0.load(req).map(|CookieValue {identity, login_timestamp, ..}| { - if self.0.requires_oob_data() { - req.extensions_mut().insert(CookieIdentityExtention { login_timestamp }); - } - identity - })) + Ok(self.0.load(req).map( + |CookieValue { + identity, + login_timestamp, + .. + }| { + if self.0.requires_oob_data() { + req.extensions_mut() + .insert(CookieIdentityExtention { login_timestamp }); + } + identity + }, + )) } fn to_response( @@ -529,23 +552,31 @@ impl IdentityPolicy for CookieIdentityPolicy { ) -> Self::ResponseFuture { let _ = if changed { let login_timestamp = SystemTime::now(); - self.0.set_cookie(res, id.map(|identity| CookieValue { - identity, - login_timestamp: self.0.login_deadline.map(|_| login_timestamp), - visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp) - })) + self.0.set_cookie( + res, + id.map(|identity| CookieValue { + identity, + login_timestamp: self.0.login_deadline.map(|_| login_timestamp), + visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp), + }), + ) } else if self.0.always_update_cookie() && id.is_some() { let visit_timestamp = SystemTime::now(); let mut login_timestamp = None; if self.0.requires_oob_data() { - let CookieIdentityExtention { login_timestamp: lt } = res.request().extensions_mut().remove().unwrap(); + let CookieIdentityExtention { + login_timestamp: lt, + } = res.request().extensions_mut().remove().unwrap(); login_timestamp = lt; } - self.0.set_cookie(res, Some(CookieValue { - identity: id.unwrap(), - login_timestamp, - visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp) - })) + self.0.set_cookie( + res, + Some(CookieValue { + identity: id.unwrap(), + login_timestamp, + visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp), + }), + ) } else { Ok(()) }; @@ -676,34 +707,59 @@ mod tests { assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); } - fn create_identity_server CookieIdentityPolicy + Sync + Send + Clone + 'static>(f: F) -> impl actix_service::Service, Error = actix_http::Error> { + fn create_identity_server< + F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static, + >( + f: F, + ) -> impl actix_service::Service< + Request = actix_http::Request, + Response = ServiceResponse, + Error = actix_http::Error, + > { test::init_service( App::new() - .wrap(IdentityService::new(f(CookieIdentityPolicy::new(&COOKIE_KEY_MASTER).secure(false).name(COOKIE_NAME)))) + .wrap(IdentityService::new(f(CookieIdentityPolicy::new( + &COOKIE_KEY_MASTER, + ) + .secure(false) + .name(COOKIE_NAME)))) .service(web::resource("/").to(|id: Identity| { let identity = id.identity(); if identity.is_none() { id.remember(COOKIE_LOGIN.to_string()) } web::Json(identity) - })) + })), ) } fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> { let mut jar = CookieJar::new(); - jar.private(&Key::from_master(&COOKIE_KEY_MASTER)).add(Cookie::new(COOKIE_NAME, identity)); + jar.private(&Key::from_master(&COOKIE_KEY_MASTER)) + .add(Cookie::new(COOKIE_NAME, identity)); jar.get(COOKIE_NAME).unwrap().clone() } - fn login_cookie(identity: &'static str, login_timestamp: Option, visit_timestamp: Option) -> Cookie<'static> { + fn login_cookie( + identity: &'static str, + login_timestamp: Option, + visit_timestamp: Option, + ) -> Cookie<'static> { let mut jar = CookieJar::new(); - let key: Vec = COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); - jar.private(&Key::from_master(&key)).add(Cookie::new(COOKIE_NAME, serde_json::to_string(&CookieValue { - identity: identity.to_string(), - login_timestamp, - visit_timestamp - }).unwrap())); + let key: Vec = COOKIE_KEY_MASTER + .iter() + .chain([1, 0, 0, 0].iter()) + .map(|e| *e) + .collect(); + jar.private(&Key::from_master(&key)).add(Cookie::new( + COOKIE_NAME, + serde_json::to_string(&CookieValue { + identity: identity.to_string(), + login_timestamp, + visit_timestamp, + }) + .unwrap(), + )); jar.get(COOKIE_NAME).unwrap().clone() } @@ -725,40 +781,63 @@ mod tests { for cookie in response.headers().get_all(header::SET_COOKIE) { cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap()); } - let cookie = cookies.private(&Key::from_master(&COOKIE_KEY_MASTER)).get(COOKIE_NAME).unwrap(); + let cookie = cookies + .private(&Key::from_master(&COOKIE_KEY_MASTER)) + .get(COOKIE_NAME) + .unwrap(); assert_eq!(cookie.value(), identity); } enum LoginTimestampCheck { NoTimestamp, NewTimestamp, - OldTimestamp(SystemTime) + OldTimestamp(SystemTime), } enum VisitTimeStampCheck { NoTimestamp, - NewTimestamp + NewTimestamp, } - fn assert_login_cookie(response: &mut ServiceResponse, identity: &str, login_timestamp: LoginTimestampCheck, visit_timestamp: VisitTimeStampCheck) { + fn assert_login_cookie( + response: &mut ServiceResponse, + identity: &str, + login_timestamp: LoginTimestampCheck, + visit_timestamp: VisitTimeStampCheck, + ) { let mut cookies = CookieJar::new(); for cookie in response.headers().get_all(header::SET_COOKIE) { cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap()); } - let key: Vec = COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); - let cookie = cookies.private(&Key::from_master(&key)).get(COOKIE_NAME).unwrap(); + let key: Vec = COOKIE_KEY_MASTER + .iter() + .chain([1, 0, 0, 0].iter()) + .map(|e| *e) + .collect(); + let cookie = cookies + .private(&Key::from_master(&key)) + .get(COOKIE_NAME) + .unwrap(); let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap(); assert_eq!(cv.identity, identity); let now = SystemTime::now(); let t30sec_ago = now - Duration::seconds(30).to_std().unwrap(); match login_timestamp { LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None), - LoginTimestampCheck::NewTimestamp => assert!(t30sec_ago <= cv.login_timestamp.unwrap() && cv.login_timestamp.unwrap() <= now), - LoginTimestampCheck::OldTimestamp(old_timestamp) => assert_eq!(cv.login_timestamp, Some(old_timestamp)) + LoginTimestampCheck::NewTimestamp => assert!( + t30sec_ago <= cv.login_timestamp.unwrap() + && cv.login_timestamp.unwrap() <= now + ), + LoginTimestampCheck::OldTimestamp(old_timestamp) => { + assert_eq!(cv.login_timestamp, Some(old_timestamp)) + } } match visit_timestamp { VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None), - VisitTimeStampCheck::NewTimestamp => assert!(t30sec_ago <= cv.visit_timestamp.unwrap() && cv.visit_timestamp.unwrap() <= now) + VisitTimeStampCheck::NewTimestamp => assert!( + t30sec_ago <= cv.visit_timestamp.unwrap() + && cv.visit_timestamp.unwrap() <= now + ), } } @@ -773,11 +852,8 @@ mod tests { #[test] fn test_identity_legacy_cookie_is_set() { let mut srv = create_identity_server(|c| c); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .to_request() - ); + let mut resp = + test::call_service(&mut srv, TestRequest::with_uri("/").to_request()); assert_logged_in(&mut resp, None); assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN); } @@ -790,7 +866,7 @@ mod tests { &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); assert_no_login_cookie(&mut resp); @@ -804,10 +880,15 @@ mod tests { &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, None); - assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp); + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); } #[test] @@ -818,10 +899,15 @@ mod tests { &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, None); - assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp); + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); } #[test] @@ -832,10 +918,15 @@ mod tests { &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, None); - assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp); + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); } #[test] @@ -846,38 +937,61 @@ mod tests { &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, None); - assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp); + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); } #[test] fn test_identity_cookie_rejected_if_login_timestamp_too_old() { let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); - let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), None); + let cookie = login_cookie( + COOKIE_LOGIN, + Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), + None, + ); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, None); - assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NewTimestamp, VisitTimeStampCheck::NoTimestamp); + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); } #[test] fn test_identity_cookie_rejected_if_visit_timestamp_too_old() { let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); - let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now() - Duration::days(180).to_std().unwrap())); + let cookie = login_cookie( + COOKIE_LOGIN, + None, + Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), + ); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, None); - assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::NoTimestamp, VisitTimeStampCheck::NewTimestamp); + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); } #[test] @@ -888,7 +1002,7 @@ mod tests { &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); assert_no_login_cookie(&mut resp); @@ -896,16 +1010,24 @@ mod tests { #[test] fn test_identity_cookie_updated_on_visit_deadline() { - let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90)).login_deadline(Duration::days(90))); + let mut srv = create_identity_server(|c| { + c.visit_deadline(Duration::days(90)) + .login_deadline(Duration::days(90)) + }); let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap(); let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp)); let mut resp = test::call_service( &mut srv, TestRequest::with_uri("/") .cookie(cookie.clone()) - .to_request() + .to_request(), ); assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); - assert_login_cookie(&mut resp, COOKIE_LOGIN, LoginTimestampCheck::OldTimestamp(timestamp), VisitTimeStampCheck::NewTimestamp); + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::OldTimestamp(timestamp), + VisitTimeStampCheck::NewTimestamp, + ); } } diff --git a/src/service.rs b/src/service.rs index 396daab4..7fbbf013 100644 --- a/src/service.rs +++ b/src/service.rs @@ -7,11 +7,14 @@ use actix_http::{ Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, }; -use actix_router::{Path, Resource, Url}; +use actix_router::{Path, Resource, ResourceDef, Url}; +use actix_service::{IntoNewService, NewService}; use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::{AppConfig, AppService}; use crate::data::Data; +use crate::dev::insert_slash; +use crate::guard::Guard; use crate::info::ConnectionInfo; use crate::request::HttpRequest; @@ -380,10 +383,136 @@ impl fmt::Debug for ServiceResponse { } } +pub struct WebService { + rdef: String, + name: Option, + guards: Vec>, +} + +impl WebService { + /// Create new `WebService` instance. + pub fn new(path: &str) -> Self { + WebService { + rdef: path.to_string(), + name: None, + guards: Vec::new(), + } + } + + /// Set service name. + /// + /// Name is used for url generation. + pub fn name(mut self, name: &str) -> Self { + self.name = Some(name.to_string()); + self + } + + /// Add match guard to a web service. + /// + /// ```rust + /// use actix_web::{web, guard, dev, App, HttpResponse}; + /// + /// fn index(req: dev::ServiceRequest) -> dev::ServiceResponse { + /// req.into_response(HttpResponse::Ok().finish()) + /// } + /// + /// fn main() { + /// let app = App::new() + /// .service( + /// web::service("/app") + /// .guard(guard::Header("content-type", "text/plain")) + /// .finish(index) + /// ); + /// } + /// ``` + pub fn guard(mut self, guard: G) -> Self { + self.guards.push(Box::new(guard)); + self + } + + /// Set a service factory implementation and generate web service. + pub fn finish(self, service: F) -> impl HttpServiceFactory + where + F: IntoNewService, + T: NewService< + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, + { + WebServiceImpl { + srv: service.into_new_service(), + rdef: self.rdef, + name: self.name, + guards: self.guards, + } + } +} + +struct WebServiceImpl { + srv: T, + rdef: String, + name: Option, + guards: Vec>, +} + +impl HttpServiceFactory for WebServiceImpl +where + T: NewService< + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, +{ + fn register(mut self, config: &mut AppService) { + let guards = if self.guards.is_empty() { + None + } else { + Some(std::mem::replace(&mut self.guards, Vec::new())) + }; + + let mut rdef = if config.is_root() || !self.rdef.is_empty() { + ResourceDef::new(&insert_slash(&self.rdef)) + } else { + ResourceDef::new(&self.rdef) + }; + if let Some(ref name) = self.name { + *rdef.name_mut() = name.clone(); + } + config.register_service(rdef, guards, self.srv, None) + } +} + #[cfg(test)] mod tests { - use crate::test::TestRequest; - use crate::HttpResponse; + use super::*; + use crate::test::{call_service, init_service, TestRequest}; + use crate::{guard, http, web, App, HttpResponse}; + + #[test] + fn test_service() { + let mut srv = init_service( + App::new().service(web::service("/test").name("test").finish( + |req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()), + )), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), http::StatusCode::OK); + + let mut srv = init_service( + App::new().service(web::service("/test").guard(guard::Get()).finish( + |req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()), + )), + ); + let req = TestRequest::with_uri("/test") + .method(http::Method::PUT) + .to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); + } #[test] fn test_fmt_debug() { diff --git a/src/web.rs b/src/web.rs index 73314449..1ecebe77 100644 --- a/src/web.rs +++ b/src/web.rs @@ -12,6 +12,7 @@ use crate::resource::Resource; use crate::responder::Responder; use crate::route::Route; use crate::scope::Scope; +use crate::service::WebService; pub use crate::config::ServiceConfig; pub use crate::data::{Data, RouteData}; @@ -274,6 +275,28 @@ where Route::new().to_async(handler) } +/// Create raw service for a specific path. +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::{dev, web, guard, App, HttpResponse}; +/// +/// fn my_service(req: dev::ServiceRequest) -> dev::ServiceResponse { +/// req.into_response(HttpResponse::Ok().finish()) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::service("/users/*") +/// .guard(guard::Header("content-type", "text/plain")) +/// .finish(my_service) +/// ); +/// } +/// ``` +pub fn service(path: &str) -> WebService { + WebService::new(path) +} + /// Execute blocking function on a thread pool, returns future that resolves /// to result of the function execution. pub fn block(f: F) -> impl Future> From cba78e06aeb287feac6762eb1b5023d985781b01 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 24 Apr 2019 15:42:34 -0700 Subject: [PATCH 1343/1635] update changes --- CHANGES.md | 2 ++ src/middleware/identity.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 003d7721..08a60d33 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ * Extend `Responder` trait, allow to override status code and headers. +* Store visit and login timestamp in the identity cookie #502 + ### Changed * `.to_async()` handler can return `Responder` type #792 diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 5e46bda2..65b5309b 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -512,12 +512,16 @@ impl CookieIdentityPolicy { } /// Accepts only users whose cookie has been seen before the given deadline + /// + /// By default visit deadline is disabled. pub fn visit_deadline(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().visit_deadline = Some(value); self } /// Accepts only users which has been authenticated before the given deadline + /// + /// By default login deadline is disabled. pub fn login_deadline(mut self, value: Duration) -> CookieIdentityPolicy { Rc::get_mut(&mut self.0).unwrap().login_deadline = Some(value); self From 70a4c36496c065e95db3d592e469e4b5e85fe997 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 25 Apr 2019 11:14:32 -0700 Subject: [PATCH 1344/1635] use Error explicitly --- src/middleware/compress.rs | 14 +++++++------- src/middleware/cors.rs | 27 +++++++++++++-------------- src/middleware/defaultheaders.rs | 9 +++++---- src/middleware/errhandlers.rs | 2 -- src/middleware/identity.rs | 12 ++++++------ src/middleware/logger.rs | 12 ++++++------ src/middleware/normalize.rs | 9 +++++---- src/scope.rs | 2 +- 8 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index d5c4082e..86665d82 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use actix_http::body::MessageBody; use actix_http::encoding::Encoder; use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; -use actix_http::{Response, ResponseBuilder}; +use actix_http::{Error, Response, ResponseBuilder}; use actix_service::{Service, Transform}; use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; @@ -71,11 +71,11 @@ impl Default for Compress { impl Transform for Compress where B: MessageBody, - S: Service>, + S: Service, Error = Error>, { type Request = ServiceRequest; type Response = ServiceResponse>; - type Error = S::Error; + type Error = Error; type InitError = (); type Transform = CompressMiddleware; type Future = FutureResult; @@ -96,11 +96,11 @@ pub struct CompressMiddleware { impl Service for CompressMiddleware where B: MessageBody, - S: Service>, + S: Service, Error = Error>, { type Request = ServiceRequest; type Response = ServiceResponse>; - type Error = S::Error; + type Error = Error; type Future = CompressResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -141,10 +141,10 @@ where impl Future for CompressResponse where B: MessageBody, - S: Service>, + S: Service, Error = Error>, { type Item = ServiceResponse>; - type Error = S::Error; + type Error = Error; fn poll(&mut self) -> Poll { let resp = futures::try_ready!(self.fut.poll()); diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 12cd0b83..6e2ec9d0 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -47,7 +47,7 @@ use futures::future::{ok, Either, Future, FutureResult}; use futures::Poll; use crate::dev::RequestHead; -use crate::error::{ResponseError, Result}; +use crate::error::{Error, ResponseError, Result}; use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri}; use crate::service::{ServiceRequest, ServiceResponse}; @@ -477,9 +477,8 @@ fn cors<'a>( impl IntoTransform for Cors where - S: Service>, + S: Service, Error = Error>, S::Future: 'static, - S::Error: 'static, B: 'static, { fn into_transform(self) -> CorsFactory { @@ -539,14 +538,13 @@ pub struct CorsFactory { impl Transform for CorsFactory where - S: Service>, + S: Service, Error = Error>, S::Future: 'static, - S::Error: 'static, B: 'static, { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = S::Error; + type Error = Error; type InitError = (); type Transform = CorsMiddleware; type Future = FutureResult; @@ -680,17 +678,16 @@ impl Inner { impl Service for CorsMiddleware where - S: Service>, + S: Service, Error = Error>, S::Future: 'static, - S::Error: 'static, B: 'static, { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = S::Error; + type Error = Error; type Future = Either< - FutureResult, - Either>>, + FutureResult, + Either>>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -820,10 +817,12 @@ mod tests { impl Cors { fn finish(self, srv: S) -> CorsMiddleware where - S: Service> - + 'static, + S: Service< + Request = ServiceRequest, + Response = ServiceResponse, + Error = Error, + > + 'static, S::Future: 'static, - S::Error: 'static, B: 'static, { block_on( diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index c0e62e28..8b92b530 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -8,6 +8,7 @@ use futures::{Future, Poll}; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use crate::http::{HeaderMap, HttpTryFrom}; use crate::service::{ServiceRequest, ServiceResponse}; +use crate::Error; /// `Middleware` for setting default response headers. /// @@ -87,12 +88,12 @@ impl DefaultHeaders { impl Transform for DefaultHeaders where - S: Service>, + S: Service, Error = Error>, S::Future: 'static, { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = S::Error; + type Error = Error; type InitError = (); type Transform = DefaultHeadersMiddleware; type Future = FutureResult; @@ -112,12 +113,12 @@ pub struct DefaultHeadersMiddleware { impl Service for DefaultHeadersMiddleware where - S: Service>, + S: Service, Error = Error>, S::Future: 'static, { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = S::Error; + type Error = Error; type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index aa36b6a4..acc6783f 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -85,7 +85,6 @@ impl Transform for ErrorHandlers where S: Service, Error = Error>, S::Future: 'static, - S::Error: 'static, B: 'static, { type Request = ServiceRequest; @@ -113,7 +112,6 @@ impl Service for ErrorHandlersMiddleware where S: Service, Error = Error>, S::Future: 'static, - S::Error: 'static, B: 'static, { type Request = ServiceRequest; diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 65b5309b..82ae0154 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -203,15 +203,15 @@ impl IdentityService { impl Transform for IdentityService where - S: Service> + 'static, + S: Service, Error = Error> + + 'static, S::Future: 'static, - S::Error: 'static, T: IdentityPolicy, B: 'static, { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = S::Error; + type Error = Error; type InitError = (); type Transform = IdentityServiceMiddleware; type Future = FutureResult; @@ -233,14 +233,14 @@ pub struct IdentityServiceMiddleware { impl Service for IdentityServiceMiddleware where B: 'static, - S: Service> + 'static, + S: Service, Error = Error> + + 'static, S::Future: 'static, - S::Error: 'static, T: IdentityPolicy, { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = S::Error; + type Error = Error; type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 43893bc0..3e3fb05f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -116,12 +116,12 @@ impl Default for Logger { impl Transform for Logger where - S: Service>, + S: Service, Error = Error>, B: MessageBody, { type Request = ServiceRequest; type Response = ServiceResponse>; - type Error = S::Error; + type Error = Error; type InitError = (); type Transform = LoggerMiddleware; type Future = FutureResult; @@ -142,12 +142,12 @@ pub struct LoggerMiddleware { impl Service for LoggerMiddleware where - S: Service>, + S: Service, Error = Error>, B: MessageBody, { type Request = ServiceRequest; type Response = ServiceResponse>; - type Error = S::Error; + type Error = Error; type Future = LoggerResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -194,10 +194,10 @@ where impl Future for LoggerResponse where B: MessageBody, - S: Service>, + S: Service, Error = Error>, { type Item = ServiceResponse>; - type Error = S::Error; + type Error = Error; fn poll(&mut self) -> Poll { let res = futures::try_ready!(self.fut.poll()); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 060331a6..7289e8d9 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -5,6 +5,7 @@ use futures::future::{self, FutureResult}; use regex::Regex; use crate::service::{ServiceRequest, ServiceResponse}; +use crate::Error; #[derive(Default, Clone, Copy)] /// `Middleware` to normalize request's URI in place @@ -16,11 +17,11 @@ pub struct NormalizePath; impl Transform for NormalizePath where - S: Service, + S: Service, { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = S::Error; + type Error = Error; type InitError = (); type Transform = NormalizePathNormalization; type Future = FutureResult; @@ -40,11 +41,11 @@ pub struct NormalizePathNormalization { impl Service for NormalizePathNormalization where - S: Service, + S: Service, { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = S::Error; + type Error = Error; type Future = S::Future; fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> { diff --git a/src/scope.rs b/src/scope.rs index 81bf84d2..d048d143 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -247,7 +247,7 @@ where /// Registers middleware, in the form of a closure, that runs during inbound /// processing in the request lifecycle (request -> response), modifying - /// request as necessary, across all requests managed by the *Scope*. + /// request as necessary, across all requests managed by the *Scope*. /// Scope-level middleware is more limited in what it can modify, relative /// to Route or Application level middleware, in that Scope-level middleware /// can not modify ServiceResponse. From ffd2c04cd385fdb8bc215e04a45a42e4ff58cd31 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 28 Apr 2019 09:08:51 -0700 Subject: [PATCH 1345/1635] Add helper trait UserSession which allows to get session for ServiceRequest and HttpRequest --- actix-session/CHANGES.md | 4 ++++ actix-session/src/lib.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index a60d1e66..8e06a562 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-beta.2] - 2019-04-28 + +* Add helper trait `UserSession` which allows to get session for ServiceRequest and HttpRequest + ## [0.1.0-beta.1] - 2019-04-20 * Update actix-web to beta.1 diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index b8202964..0e783144 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -79,6 +79,23 @@ pub use crate::cookie::CookieSession; /// ``` pub struct Session(Rc>); +/// Helper trait that allows to get session +pub trait UserSession { + fn get_session(&mut self) -> Session; +} + +impl UserSession for HttpRequest { + fn get_session(&mut self) -> Session { + Session::get_session(&mut *self.extensions_mut()) + } +} + +impl UserSession for ServiceRequest { + fn get_session(&mut self) -> Session { + Session::get_session(&mut *self.extensions_mut()) + } +} + #[derive(Default)] struct SessionInner { state: HashMap, @@ -208,4 +225,18 @@ mod tests { let changes: Vec<_> = Session::get_changes(&mut res).unwrap().collect(); assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]); } + + #[test] + fn get_session() { + let mut req = test::TestRequest::default().to_srv_request(); + + Session::set_session( + vec![("key".to_string(), "\"value\"".to_string())].into_iter(), + &mut req, + ); + + let session = req.get_session(); + let res = session.get::("key").unwrap(); + assert_eq!(res, Some("value".to_string())); + } } From 8db6b48a7612b2f170e24d03a1733b515b149d10 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 28 Apr 2019 09:09:18 -0700 Subject: [PATCH 1346/1635] update version --- actix-session/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 83f9807f..e13ff5c6 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,7 +24,7 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-beta.1" +actix-web = "1.0.0-beta.2" actix-service = "0.3.4" bytes = "0.4" derive_more = "0.14" From b51b5b763c9c861fe8d9c55f4fa81f12c0e2875f Mon Sep 17 00:00:00 2001 From: Darin Date: Mon, 29 Apr 2019 12:14:36 -0400 Subject: [PATCH 1347/1635] added clarification to docs regarding middleware processing sequence, added delete method to TestRequest (#799) * added clarification to docs regarding middleware processing sequnce * added delete method to TestRequest, doc, and test --- src/app.rs | 10 +++++++++- src/test.rs | 14 +++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index bb6d2aef..b478b6c0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -301,7 +301,15 @@ where /// lifecycle (request -> response), modifying request/response as /// necessary, across all requests managed by the *Application*. /// - /// Use middleware when you need to read or modify *every* request or response in some way. + /// Use middleware when you need to read or modify *every* request or + /// response in some way. + /// + /// Notice that the keyword for registering middleware is `wrap`. As you + /// register middleware using `wrap` in the App builder, imagine wrapping + /// layers around an inner App. The first middleware layer exposed to a + /// Request is the outermost layer-- the *last* registered in + /// the builder chain. Consequently, the *first* middleware registered + /// in the builder chain is the *last* to execute during request processing. /// /// ```rust /// use actix_service::Service; diff --git a/src/test.rs b/src/test.rs index 1f3a2427..6bdc3ce3 100644 --- a/src/test.rs +++ b/src/test.rs @@ -386,6 +386,11 @@ impl TestRequest { TestRequest::default().method(Method::PATCH) } + /// Create TestRequest and set method to `Method::DELETE` + pub fn delete() -> TestRequest { + TestRequest::default().method(Method::DELETE) + } + /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { self.req.version(ver); @@ -549,7 +554,8 @@ mod tests { App::new().service( web::resource("/index.html") .route(web::put().to(|| HttpResponse::Ok().body("put!"))) - .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))), + .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))) + .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))) ), ); @@ -568,6 +574,12 @@ mod tests { let result = read_response(&mut app, patch_req); assert_eq!(result, Bytes::from_static(b"patch!")); + + let delete_req = TestRequest::delete() + .uri("/index.html") + .to_request(); + let result = read_response(&mut app, delete_req); + assert_eq!(result, Bytes::from_static(b"delete!")); } #[test] From 29a841529f42e12305fcf6496911420e2562b315 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 29 Apr 2019 09:26:12 -0700 Subject: [PATCH 1348/1635] Allow to construct Data instances to avoid double Arc for Send + Sync types. --- CHANGES.md | 5 +++++ src/app.rs | 10 +++++----- src/data.rs | 31 +++++++++++++++++++++++-------- src/test.rs | 6 ++---- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 08a60d33..53ff98cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +### Changed + +* Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types. + + ## [1.0.0-beta.2] - 2019-04-24 ### Added diff --git a/src/app.rs b/src/app.rs index b478b6c0..0e306a00 100644 --- a/src/app.rs +++ b/src/app.rs @@ -95,8 +95,8 @@ where /// web::get().to(index))); /// } /// ``` - pub fn data(mut self, data: S) -> Self { - self.data.push(Box::new(Data::new(data))); + pub fn data> + 'static>(mut self, data: U) -> Self { + self.data.push(Box::new(data.into())); self } @@ -301,14 +301,14 @@ where /// lifecycle (request -> response), modifying request/response as /// necessary, across all requests managed by the *Application*. /// - /// Use middleware when you need to read or modify *every* request or + /// Use middleware when you need to read or modify *every* request or /// response in some way. /// - /// Notice that the keyword for registering middleware is `wrap`. As you + /// Notice that the keyword for registering middleware is `wrap`. As you /// register middleware using `wrap` in the App builder, imagine wrapping /// layers around an inner App. The first middleware layer exposed to a /// Request is the outermost layer-- the *last* registered in - /// the builder chain. Consequently, the *first* middleware registered + /// the builder chain. Consequently, the *first* middleware registered /// in the builder chain is the *last* to execute during request processing. /// /// ```rust diff --git a/src/data.rs b/src/data.rs index 0c896fcc..c77ba3e2 100644 --- a/src/data.rs +++ b/src/data.rs @@ -33,29 +33,33 @@ pub(crate) trait DataFactoryResult { /// instance for each thread, thus application data must be constructed /// multiple times. If you want to share data between different /// threads, a shareable object should be used, e.g. `Send + Sync`. Application -/// data does not need to be `Send` or `Sync`. Internally `Data` instance -/// uses `Arc`. +/// data does not need to be `Send` or `Sync`. Internally `Data` type +/// uses `Arc`. if your data implements `Send` + `Sync` traits you can +/// use `web::Data::new()` and avoid double `Arc`. /// /// If route data is not set for a handler, using `Data` extractor would /// cause *Internal Server Error* response. /// /// ```rust -/// use std::cell::Cell; +/// use std::sync::Mutex; /// use actix_web::{web, App}; /// /// struct MyData { -/// counter: Cell, +/// counter: usize, /// } /// /// /// Use `Data` extractor to access data in handler. -/// fn index(data: web::Data) { -/// data.counter.set(data.counter.get() + 1); +/// fn index(data: web::Data>) { +/// let mut data = data.lock().unwrap(); +/// data.counter += 1; /// } /// /// fn main() { +/// let data = web::Data::new(Mutex::new(MyData{ counter: 0 })); +/// /// let app = App::new() /// // Store `MyData` in application storage. -/// .data(MyData{ counter: Cell::new(0) }) +/// .data(data.clone()) /// .service( /// web::resource("/index.html").route( /// web::get().to(index))); @@ -65,7 +69,12 @@ pub(crate) trait DataFactoryResult { pub struct Data(Arc); impl Data { - pub(crate) fn new(state: T) -> Data { + /// Create new `Data` instance. + /// + /// Internally `Data` type uses `Arc`. if your data implements + /// `Send` + `Sync` traits you can use `web::Data::new()` and + /// avoid double `Arc`. + pub fn new(state: T) -> Data { Data(Arc::new(state)) } @@ -89,6 +98,12 @@ impl Clone for Data { } } +impl From for Data { + fn from(data: T) -> Self { + Data::new(data) + } +} + impl FromRequest for Data { type Config = (); type Error = Error; diff --git a/src/test.rs b/src/test.rs index 6bdc3ce3..dc55df63 100644 --- a/src/test.rs +++ b/src/test.rs @@ -555,7 +555,7 @@ mod tests { web::resource("/index.html") .route(web::put().to(|| HttpResponse::Ok().body("put!"))) .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))) - .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))) + .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))), ), ); @@ -575,9 +575,7 @@ mod tests { let result = read_response(&mut app, patch_req); assert_eq!(result, Bytes::from_static(b"patch!")); - let delete_req = TestRequest::delete() - .uri("/index.html") - .to_request(); + let delete_req = TestRequest::delete().uri("/index.html").to_request(); let result = read_response(&mut app, delete_req); assert_eq!(result, Bytes::from_static(b"delete!")); } From f4b4875cb12d1bd7c880b76258d1f80b376c70da Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 29 Apr 2019 09:34:14 -0700 Subject: [PATCH 1349/1635] Add helper function for executing futures test::block_fn() --- CHANGES.md | 5 +++++ src/app.rs | 6 ++++-- src/test.rs | 25 +++++++++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 53ff98cc..3b7a30e2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +### Added + +* Add helper function for executing futures `test::block_fn()` + + ### Changed * Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types. diff --git a/src/app.rs b/src/app.rs index 0e306a00..7e5cd394 100644 --- a/src/app.rs +++ b/src/app.rs @@ -445,7 +445,9 @@ mod tests { use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; + use crate::test::{ + block_fn, block_on, call_service, init_service, read_body, TestRequest, + }; use crate::{web, Error, HttpRequest, HttpResponse}; #[test] @@ -454,7 +456,7 @@ mod tests { App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), ); let req = TestRequest::with_uri("/test").to_request(); - let resp = block_on(srv.call(req)).unwrap(); + let resp = block_fn(|| srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/blah").to_request(); diff --git a/src/test.rs b/src/test.rs index dc55df63..2fc3e2a7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -12,10 +12,8 @@ use actix_rt::Runtime; use actix_server_config::ServerConfig; use actix_service::{FnService, IntoNewService, NewService, Service}; use bytes::{Bytes, BytesMut}; -use futures::{ - future::{lazy, ok, Future}, - stream::Stream, -}; +use futures::future::{lazy, ok, Future, IntoFuture}; +use futures::Stream; use serde::de::DeserializeOwned; use serde_json; @@ -52,6 +50,25 @@ where RT.with(move |rt| rt.borrow_mut().block_on(f)) } +/// Runs the provided function, blocking the current thread until the resul +/// future completes. +/// +/// This function can be used to synchronously block the current thread +/// until the provided `future` has resolved either successfully or with an +/// error. The result of the future is then returned from this function +/// call. +/// +/// Note that this function is intended to be used only for testing purpose. +/// This function panics on nested call. +pub fn block_fn(f: F) -> Result +where + F: FnOnce() -> R, + R: IntoFuture, +{ + RT.with(move |rt| rt.borrow_mut().block_on(f().into_future())) +} + +#[doc(hidden)] /// Runs the provided function, with runtime enabled. /// /// Note that this function is intended to be used only for testing purpose. From d2c17910670cbc704fc6ac6e16960602666905cb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 29 Apr 2019 09:45:37 -0700 Subject: [PATCH 1350/1635] add async handler test with blocking call --- CHANGES.md | 1 - src/test.rs | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 3b7a30e2..84eb12df 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,6 @@ * Add helper function for executing futures `test::block_fn()` - ### Changed * Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types. diff --git a/src/test.rs b/src/test.rs index 2fc3e2a7..60a4a384 100644 --- a/src/test.rs +++ b/src/test.rs @@ -640,4 +640,24 @@ mod tests { let result: Person = read_response_json(&mut app, req); assert_eq!(&result.id, "12345"); } + + #[test] + fn test_async_with_block() { + fn async_with_block() -> impl Future { + web::block(move || Some(4).ok_or("wrong")).then(|res| match res { + Ok(value) => HttpResponse::Ok() + .content_type("text/plain") + .body(format!("Async with block value: {}", value)), + Err(_) => panic!("Unexpected"), + }) + } + + let mut app = init_service( + App::new().service(web::resource("/index.html").to_async(async_with_block)), + ); + + let req = TestRequest::post().uri("/index.html").to_request(); + let res = block_on(app.call(req)).unwrap(); + assert!(res.status().is_success()); + } } From f4e1205cbb48b0e73f8ef47f79f527256fa01d8b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 29 Apr 2019 10:14:08 -0700 Subject: [PATCH 1351/1635] fix reactor drop panic --- src/test.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/test.rs b/src/test.rs index 60a4a384..87b33e99 100644 --- a/src/test.rs +++ b/src/test.rs @@ -28,11 +28,25 @@ use crate::service::{ServiceRequest, ServiceResponse}; use crate::{Error, HttpRequest, HttpResponse}; thread_local! { - static RT: RefCell = { - RefCell::new(Runtime::new().unwrap()) + static RT: RefCell = { + RefCell::new(Inner(Some(Runtime::new().unwrap()))) }; } +struct Inner(Option); + +impl Inner { + fn get_mut(&mut self) -> &mut Runtime { + self.0.as_mut().unwrap() + } +} + +impl Drop for Inner { + fn drop(&mut self) { + std::mem::forget(self.0.take().unwrap()) + } +} + /// Runs the provided future, blocking the current thread until the future /// completes. /// @@ -47,7 +61,7 @@ pub fn block_on(f: F) -> Result where F: Future, { - RT.with(move |rt| rt.borrow_mut().block_on(f)) + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f)) } /// Runs the provided function, blocking the current thread until the resul @@ -65,7 +79,7 @@ where F: FnOnce() -> R, R: IntoFuture, { - RT.with(move |rt| rt.borrow_mut().block_on(f().into_future())) + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(|| f()))) } #[doc(hidden)] @@ -77,8 +91,12 @@ pub fn run_on(f: F) -> R where F: FnOnce() -> R, { - RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f())))) - .unwrap() + RT.with(move |rt| { + rt.borrow_mut() + .get_mut() + .block_on(lazy(|| Ok::<_, ()>(f()))) + }) + .unwrap() } /// Create service that always responds with `HttpResponse::Ok()` @@ -657,7 +675,7 @@ mod tests { ); let req = TestRequest::post().uri("/index.html").to_request(); - let res = block_on(app.call(req)).unwrap(); + let res = block_fn(|| app.call(req)).unwrap(); assert!(res.status().is_success()); } } From 94a0d1a6bc0140a60a48b89dff1eca000b1d492a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 29 Apr 2019 18:42:21 -0700 Subject: [PATCH 1352/1635] remove old api doc refs --- src/test.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/test.rs b/src/test.rs index 87b33e99..7d332118 100644 --- a/src/test.rs +++ b/src/test.rs @@ -59,9 +59,9 @@ impl Drop for Inner { /// This function panics on nested call. pub fn block_on(f: F) -> Result where - F: Future, + F: IntoFuture, { - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f)) + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f.into_future())) } /// Runs the provided function, blocking the current thread until the resul @@ -330,12 +330,11 @@ where /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. /// You can generate various types of request via TestRequest's methods: /// * `TestRequest::to_request` creates `actix_http::Request` instance. -/// * `TestRequest::to_service` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. -/// * `TestRequest::to_from` creates `ServiceFromRequest` instance, which is used for testing extractors. +/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. +/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. /// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// /// ```rust -/// # use futures::IntoFuture; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; /// @@ -352,11 +351,11 @@ where /// let req = test::TestRequest::with_header("content-type", "text/plain") /// .to_http_request(); /// -/// let resp = test::block_on(index(req).into_future()).unwrap(); +/// let resp = test::block_on(index(req)).unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); -/// let resp = test::block_on(index(req).into_future()).unwrap(); +/// let resp = test::block_on(index(req)).unwrap(); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` From 24bd5b13447dc94829795c3fa38bed3a98c7759a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 29 Apr 2019 20:47:21 -0700 Subject: [PATCH 1353/1635] update readmes --- actix-files/README.md | 8 ++++++++ actix-framed/README.md | 7 +++++++ actix-multipart/README.md | 7 +++++++ actix-session/README.md | 8 ++++++++ actix-web-actors/README.md | 7 +++++++ awc/Cargo.toml | 5 ++++- test-server/README.md | 8 ++++++++ 7 files changed, 49 insertions(+), 1 deletion(-) diff --git a/actix-files/README.md b/actix-files/README.md index 5b133f57..9585e67a 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -1 +1,9 @@ # Static files support for actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-files)](https://crates.io/crates/actix-files) [![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) + +## Documentation & community resources + +* [User Guide](https://actix.rs/docs/) +* [API Documentation](https://docs.rs/actix-files/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-files](https://crates.io/crates/actix-files) +* Minimum supported Rust version: 1.33 or later diff --git a/actix-framed/README.md b/actix-framed/README.md index f56ae145..1714b364 100644 --- a/actix-framed/README.md +++ b/actix-framed/README.md @@ -1 +1,8 @@ # Framed app for actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-framed)](https://crates.io/crates/actix-framed) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Documentation & community resources + +* [API Documentation](https://docs.rs/actix-framed/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-framed](https://crates.io/crates/actix-framed) +* Minimum supported Rust version: 1.33 or later diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 2739ff3d..ac0d0564 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -1 +1,8 @@ # Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Documentation & community resources + +* [API Documentation](https://docs.rs/actix-multipart/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-multipart](https://crates.io/crates/actix-multipart) +* Minimum supported Rust version: 1.33 or later diff --git a/actix-session/README.md b/actix-session/README.md index 504fe150..7d683041 100644 --- a/actix-session/README.md +++ b/actix-session/README.md @@ -1 +1,9 @@ # Session for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-session)](https://crates.io/crates/actix-session) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Documentation & community resources + +* [User Guide](https://actix.rs/docs/) +* [API Documentation](https://docs.rs/actix-session/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-session](https://crates.io/crates/actix-session) +* Minimum supported Rust version: 1.33 or later diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index c7099038..6ff7ac67 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -1 +1,8 @@ Actix actors support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-actors)](https://crates.io/crates/actix-web-actors) [![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) + +## Documentation & community resources + +* [API Documentation](https://docs.rs/actix-web-actors/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-web-actors](https://crates.io/crates/actix-web-actors) +* Minimum supported Rust version: 1.33 or later diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8f64a3c6..f061351d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -8,10 +8,13 @@ keywords = ["actix", "http", "framework", "async", "web"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/awc/" +categories = ["network-programming", "asynchronous", + "web-programming::http-client", + "web-programming::websocket"] license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] -workspace = ".." edition = "2018" +workspace = ".." [lib] name = "awc" diff --git a/test-server/README.md b/test-server/README.md index 596dddf8..e4065012 100644 --- a/test-server/README.md +++ b/test-server/README.md @@ -1 +1,9 @@ # Actix http test server [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-http-test)](https://crates.io/crates/actix-http-test) [![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) + +## Documentation & community resources + +* [User Guide](https://actix.rs/docs/) +* [API Documentation](https://docs.rs/actix-http-test/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-http-test](https://crates.io/crates/actix-http-test) +* Minimum supported Rust version: 1.33 or later From 87284f0951a65693189e64cacb12d4616bb13458 Mon Sep 17 00:00:00 2001 From: Douman Date: Wed, 1 May 2019 21:47:51 +0300 Subject: [PATCH 1354/1635] Add doctest to verify NormalizePath middleware (#809) --- src/middleware/normalize.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 7289e8d9..a92ba5ad 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -13,14 +13,30 @@ use crate::Error; /// Performs following: /// /// - Merges multiple slashes into one. +/// +/// ```rust +/// use actix_web::{web, http, middleware, App, HttpResponse}; +/// +/// fn main() { +/// let app = App::new() +/// .wrap(middleware::NormalizePath) +/// .service( +/// web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// } +/// ``` + pub struct NormalizePath; -impl Transform for NormalizePath +impl Transform for NormalizePath where - S: Service, + S: Service, Error = Error>, + S::Future: 'static, { type Request = ServiceRequest; - type Response = ServiceResponse; + type Response = ServiceResponse; type Error = Error; type InitError = (); type Transform = NormalizePathNormalization; @@ -39,12 +55,13 @@ pub struct NormalizePathNormalization { merge_slash: Regex, } -impl Service for NormalizePathNormalization +impl Service for NormalizePathNormalization where - S: Service, + S: Service, Error = Error>, + S::Future: 'static, { type Request = ServiceRequest; - type Response = ServiceResponse; + type Response = ServiceResponse; type Error = Error; type Future = S::Future; From 6b34909537a19e19f296aa52706449b3b472e2c3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 1 May 2019 12:40:56 -0700 Subject: [PATCH 1355/1635] Fix NormalizePath middleware impl #806 --- CHANGES.md | 4 ++++ src/middleware/normalize.rs | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 84eb12df..b2a9e7e4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,10 @@ * Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types. +### Fixed + +* Fix `NormalizePath` middleware impl #806 + ## [1.0.0-beta.2] - 2019-04-24 diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index a92ba5ad..a86e2b9c 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,6 +1,8 @@ //! `Middleware` to normalize request's URI +use actix_http::http::{HttpTryFrom, PathAndQuery, Uri}; use actix_service::{Service, Transform}; +use bytes::Bytes; use futures::future::{self, FutureResult}; use regex::Regex; @@ -77,7 +79,19 @@ where let path = self.merge_slash.replace_all(path, "/"); if original_len != path.len() { - head.uri = path.parse().unwrap(); + let mut parts = head.uri.clone().into_parts(); + let pq = parts.path_and_query.as_ref().unwrap(); + + let path = if let Some(q) = pq.query() { + Bytes::from(format!("{}?{}", path, q)) + } else { + Bytes::from(path.as_ref()) + }; + parts.path_and_query = Some(PathAndQuery::try_from(path).unwrap()); + + let uri = Uri::from_parts(parts).unwrap(); + req.match_info_mut().get_mut().update(&uri); + req.head_mut().uri = uri; } self.service.call(req) @@ -90,8 +104,21 @@ mod tests { use super::*; use crate::dev::ServiceRequest; - use crate::test::{block_on, TestRequest}; - use crate::HttpResponse; + use crate::test::{block_on, call_service, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[test] + fn test_wrap() { + let mut app = init_service( + App::new() + .wrap(NormalizePath::default()) + .service(web::resource("/v1/something/").to(|| HttpResponse::Ok())), + ); + + let req = TestRequest::with_uri("/v1//something////").to_request(); + let res = call_service(&mut app, req); + assert!(res.status().is_success()); + } #[test] fn test_in_place_normalization() { From 4f1c6d1bb7a805b869cd9d14ffa4a353ecd42132 Mon Sep 17 00:00:00 2001 From: Max Bo Date: Fri, 3 May 2019 02:26:51 +1000 Subject: [PATCH 1356/1635] Update MIGRATION.md (#811) --- MIGRATION.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 9d433ed0..c7932b60 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -164,7 +164,7 @@ * `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead. - instead if + instead of ```rust fn index(req: &HttpRequest) -> Responder { @@ -173,6 +173,7 @@ ... }) } + ``` use From f27beab016de18577de0818a5802829b31da96b1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 2 May 2019 09:30:00 -0700 Subject: [PATCH 1357/1635] fix case for transfer-encoding header name --- actix-http/src/h1/encoder.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 60bf2262..61ca48b1 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -82,7 +82,7 @@ pub(crate) trait MessageType: Sized { if camel_case { dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n") } else { - dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n") + dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n") } } else { skip_len = false; @@ -564,5 +564,18 @@ mod tests { bytes.take().freeze(), Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: xml\r\nContent-Type: plain/text\r\n\r\n") ); + + head.set_camel_case_headers(false); + let _ = head.encode_headers( + &mut bytes, + Version::HTTP_11, + BodySize::Stream, + ConnectionType::KeepAlive, + &ServiceConfig::default(), + ); + assert_eq!( + bytes.take().freeze(), + Bytes::from_static(b"\r\ntransfer-encoding: chunked\r\ndate: date\r\ncontent-type: xml\r\ncontent-type: plain/text\r\n\r\n") + ); } } From 337c2febe34e5d3781076d348c1d732e3035f49b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 2 May 2019 09:49:10 -0700 Subject: [PATCH 1358/1635] add more tests --- actix-web-codegen/tests/test_macro.rs | 31 +++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index dd105785..9b9ec6f3 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,7 +1,7 @@ use actix_http::HttpService; use actix_http_test::TestServer; use actix_web::{http, App, HttpResponse, Responder}; -use actix_web_codegen::get; +use actix_web_codegen::{get, post, put}; use futures::{future, Future}; #[get("/test")] @@ -9,6 +9,16 @@ fn test() -> impl Responder { HttpResponse::Ok() } +#[put("/test")] +fn put_test() -> impl Responder { + HttpResponse::Created() +} + +#[post("/test")] +fn post_test() -> impl Responder { + HttpResponse::NoContent() +} + #[get("/test")] fn auto_async() -> impl Future { future::ok(HttpResponse::Ok().finish()) @@ -21,11 +31,28 @@ fn auto_sync() -> impl Future { #[test] fn test_body() { - let mut srv = TestServer::new(|| HttpService::new(App::new().service(test))); + let mut srv = TestServer::new(|| { + HttpService::new( + App::new() + .service(post_test) + .service(put_test) + .service(test), + ) + }); let request = srv.request(http::Method::GET, srv.url("/test")); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); + let request = srv.request(http::Method::PUT, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.status(), http::StatusCode::CREATED); + + let request = srv.request(http::Method::POST, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); + let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_sync))); let request = srv.request(http::Method::GET, srv.url("/test")); let response = srv.block_on(request.send()).unwrap(); From 6e00eef63a58ad8d9dbf4337f68ce075309b8fbe Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Fri, 3 May 2019 18:30:00 -0300 Subject: [PATCH 1359/1635] awc: Fix typo on ResponseError documentation (#815) * awc: Fix typo on ResponseError documentation Signed-off-by: Otavio Salvador * http: Fix typo on ResponseError documentation Signed-off-by: Otavio Salvador * http: Expand type names for openssl related errors documentation Signed-off-by: Otavio Salvador --- actix-http/src/error.rs | 6 +++--- awc/src/error.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 1768c954..4913c3d9 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -137,7 +137,7 @@ impl ResponseError for TimeoutError { #[display(fmt = "UnknownError")] struct UnitError; -/// `InternalServerError` for `JsonError` +/// `InternalServerError` for `UnitError` impl ResponseError for UnitError {} /// `InternalServerError` for `JsonError` @@ -150,11 +150,11 @@ impl ResponseError for FormError {} impl ResponseError for TimerError {} #[cfg(feature = "ssl")] -/// `InternalServerError` for `SslError` +/// `InternalServerError` for `openssl::ssl::Error` impl ResponseError for openssl::ssl::Error {} #[cfg(feature = "ssl")] -/// `InternalServerError` for `SslError` +/// `InternalServerError` for `openssl::ssl::HandshakeError` impl ResponseError for openssl::ssl::HandshakeError {} /// Return `BAD_REQUEST` for `de::value::Error` diff --git a/awc/src/error.rs b/awc/src/error.rs index 20654bdf..f78355c6 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -65,7 +65,7 @@ pub enum JsonPayloadError { Payload(PayloadError), } -/// Return `InternlaServerError` for `JsonPayloadError` +/// Return `InternalServerError` for `JsonPayloadError` impl ResponseError for JsonPayloadError { fn error_response(&self) -> Response { Response::new(StatusCode::INTERNAL_SERVER_ERROR) From fc19ce41c42ded0143c999d94ee6cb917641b98e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 3 May 2019 15:26:34 -0700 Subject: [PATCH 1360/1635] Clean up response extensions in response pool #817 --- actix-http/CHANGES.md | 7 +++++++ actix-http/src/message.rs | 1 + 2 files changed, 8 insertions(+) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d1a043d8..69abcfba 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.1.5] - 2019-05-xx + +### Fixed + +* Clean up response extensions in response pool #817 + + ## [0.1.4] - 2019-04-24 ### Added diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index c279aaeb..f3c01a12 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -449,6 +449,7 @@ impl BoxedResponsePool { fn release(&self, msg: Box) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { + msg.extensions.borrow_mut().clear(); v.push(msg); } } From 7ef4f5ac0b69463e2d616c68f82f5b65c958ed28 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 5 May 2019 01:41:37 +1000 Subject: [PATCH 1361/1635] Make request headers optional in CORS preflight (#816) --- src/middleware/cors.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 6e2ec9d0..bd57c66a 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -81,13 +81,6 @@ pub enum CorsError { fmt = "The request header `Access-Control-Request-Headers` has an invalid value" )] BadRequestHeaders, - /// The request header `Access-Control-Request-Headers` is required but is - /// missing. - #[display( - fmt = "The request header `Access-Control-Request-Headers` is required but is - missing" - )] - MissingRequestHeaders, /// Origin is not allowed to make this request #[display(fmt = "Origin is not allowed to make this request")] OriginNotAllowed, @@ -661,15 +654,18 @@ impl Inner { Err(_) => return Err(CorsError::BadRequestHeaders), }; } - - if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) { - return Err(CorsError::HeadersNotAllowed); + // `Access-Control-Request-Headers` must contain 1 or more + // `field-name`. + if !hdrs.is_empty() { + if !hdrs.is_subset(allowed_headers) { + return Err(CorsError::HeadersNotAllowed); + } + return Ok(()); } - return Ok(()); } Err(CorsError::BadRequestHeaders) } else { - Err(CorsError::MissingRequestHeaders) + return Ok(()); } } } @@ -874,6 +870,10 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) + .header( + header::ACCESS_CONTROL_REQUEST_HEADERS, + "X-Not-Allowed", + ) .to_srv_request(); assert!(cors.inner.validate_allowed_method(req.head()).is_err()); @@ -887,7 +887,7 @@ mod tests { .to_srv_request(); assert!(cors.inner.validate_allowed_method(req.head()).is_err()); - assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); + assert!(cors.inner.validate_allowed_headers(req.head()).is_ok()); let req = TestRequest::with_header("Origin", "https://www.example.com") .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") From 01cfcf3b758220f763ab4cbf3a3615ea8543c05f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 4 May 2019 08:42:27 -0700 Subject: [PATCH 1362/1635] update changes --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b2a9e7e4..1d31b351 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ ### Changed +* CORS handling without headers #702 + * Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types. ### Fixed From fa78da81569f23accfe7b293be0c022fd01bbdb3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 4 May 2019 19:43:49 -0700 Subject: [PATCH 1363/1635] unify route and app data, it allows to provide global extractor config #775 --- CHANGES.md | 12 +++ src/app.rs | 51 +++++------ src/app_service.rs | 48 ++++++----- src/config.rs | 130 +++++++++++++--------------- src/data.rs | 189 ++++++++--------------------------------- src/extract.rs | 2 +- src/handler.rs | 13 +-- src/middleware/cors.rs | 5 +- src/request.rs | 35 ++++---- src/resource.rs | 56 +++++++++++- src/route.rs | 66 ++------------ src/scope.rs | 1 + src/service.rs | 9 +- src/test.rs | 42 +++++---- src/types/form.rs | 13 ++- src/types/json.rs | 29 +++---- src/types/payload.rs | 15 ++-- src/web.rs | 2 +- 18 files changed, 292 insertions(+), 426 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1d31b351..cfd7a6df 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,19 @@ # Changes +## [1.0.0-beta.3] - 2019-05-04 + ### Added * Add helper function for executing futures `test::block_fn()` ### Changed +* Extractor configuration could be registered with `App::data()` + or with `Resource::data()` #775 + +* Route data is unified with app data, `Route::data()` moved to resource + level to `Resource::data()` + * CORS handling without headers #702 * Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types. @@ -14,6 +22,10 @@ * Fix `NormalizePath` middleware impl #806 +### Deleted + +* `App::data_factory()` is deleted. + ## [1.0.0-beta.2] - 2019-04-24 diff --git a/src/app.rs b/src/app.rs index 7e5cd394..bac71250 100644 --- a/src/app.rs +++ b/src/app.rs @@ -100,19 +100,6 @@ where self } - /// Set application data factory. This function is - /// similar to `.data()` but it accepts data factory. Data object get - /// constructed asynchronously during application initialization. - pub fn data_factory(mut self, data: F) -> Self - where - F: Fn() -> R + 'static, - R: IntoFuture + 'static, - R::Error: std::fmt::Debug, - { - self.data.push(Box::new(data)); - self - } - /// Run external configuration as part of the application building /// process /// @@ -425,9 +412,9 @@ where { fn into_new_service(self) -> AppInit { AppInit { - data: self.data, + data: Rc::new(self.data), endpoint: self.endpoint, - services: RefCell::new(self.services), + services: Rc::new(RefCell::new(self.services)), external: RefCell::new(self.external), default: self.default, factory_ref: self.factory_ref, @@ -493,24 +480,24 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); } - #[test] - fn test_data_factory() { - let mut srv = - init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + // #[test] + // fn test_data_factory() { + // let mut srv = + // init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( + // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + // )); + // let req = TestRequest::default().to_request(); + // let resp = block_on(srv.call(req)).unwrap(); + // assert_eq!(resp.status(), StatusCode::OK); - let mut srv = - init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } + // let mut srv = + // init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( + // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + // )); + // let req = TestRequest::default().to_request(); + // let resp = block_on(srv.call(req)).unwrap(); + // assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + // } fn md( req: ServiceRequest, diff --git a/src/app_service.rs b/src/app_service.rs index 7229a230..e2f91842 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; -use actix_http::{Request, Response}; +use actix_http::{Extensions, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService, BoxedService}; @@ -11,7 +11,7 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll}; use crate::config::{AppConfig, AppService}; -use crate::data::{DataFactory, DataFactoryResult}; +use crate::data::DataFactory; use crate::error::Error; use crate::guard::Guard; use crate::request::{HttpRequest, HttpRequestPool}; @@ -38,9 +38,9 @@ where >, { pub(crate) endpoint: T, - pub(crate) data: Vec>, + pub(crate) data: Rc>>, pub(crate) config: RefCell, - pub(crate) services: RefCell>>, + pub(crate) services: Rc>>>, pub(crate) default: Option>, pub(crate) factory_ref: Rc>>, pub(crate) external: RefCell>, @@ -70,6 +70,7 @@ where }))) }); + // App config { let mut c = self.config.borrow_mut(); let loc_cfg = Rc::get_mut(&mut c.0).unwrap(); @@ -77,7 +78,11 @@ where loc_cfg.addr = cfg.local_addr(); } - let mut config = AppService::new(self.config.borrow().clone(), default.clone()); + let mut config = AppService::new( + self.config.borrow().clone(), + default.clone(), + self.data.clone(), + ); // register services std::mem::replace(&mut *self.services.borrow_mut(), Vec::new()) @@ -86,12 +91,13 @@ where let mut rmap = ResourceMap::new(ResourceDef::new("")); + let (config, services) = config.into_services(); + // complete pipeline creation *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { default, services: Rc::new( - config - .into_services() + services .into_iter() .map(|(mut rdef, srv, guards, nested)| { rmap.add(&mut rdef, nested); @@ -110,11 +116,17 @@ where let rmap = Rc::new(rmap); rmap.finish(rmap.clone()); + // create app data container + let mut data = Extensions::new(); + for f in self.data.iter() { + f.create(&mut data); + } + AppInitResult { endpoint: None, endpoint_fut: self.endpoint.new_service(&()), - data: self.data.iter().map(|s| s.construct()).collect(), - config: self.config.borrow().clone(), + data: Rc::new(data), + config, rmap, _t: PhantomData, } @@ -128,8 +140,8 @@ where endpoint: Option, endpoint_fut: T::Future, rmap: Rc, - data: Vec>, config: AppConfig, + data: Rc, _t: PhantomData, } @@ -146,27 +158,18 @@ where type Error = T::InitError; fn poll(&mut self) -> Poll { - let mut idx = 0; - let mut extensions = self.config.0.extensions.borrow_mut(); - while idx < self.data.len() { - if let Async::Ready(_) = self.data[idx].poll_result(&mut extensions)? { - self.data.remove(idx); - } else { - idx += 1; - } - } - if self.endpoint.is_none() { if let Async::Ready(srv) = self.endpoint_fut.poll()? { self.endpoint = Some(srv); } } - if self.endpoint.is_some() && self.data.is_empty() { + if self.endpoint.is_some() { Ok(Async::Ready(AppInitService { service: self.endpoint.take().unwrap(), rmap: self.rmap.clone(), config: self.config.clone(), + data: self.data.clone(), pool: HttpRequestPool::create(), })) } else { @@ -183,6 +186,7 @@ where service: T, rmap: Rc, config: AppConfig, + data: Rc, pool: &'static HttpRequestPool, } @@ -207,6 +211,7 @@ where inner.path.get_mut().update(&head.uri); inner.path.reset(); inner.head = head; + inner.app_data = self.data.clone(); req } else { HttpRequest::new( @@ -214,6 +219,7 @@ where head, self.rmap.clone(), self.config.clone(), + self.data.clone(), self.pool, ) }; diff --git a/src/config.rs b/src/config.rs index 4c4bfa22..e4e390b1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,9 @@ -use std::cell::{Ref, RefCell}; use std::net::SocketAddr; use std::rc::Rc; use actix_http::Extensions; use actix_router::ResourceDef; use actix_service::{boxed, IntoNewService, NewService}; -use futures::IntoFuture; use crate::data::{Data, DataFactory}; use crate::error::Error; @@ -33,14 +31,20 @@ pub struct AppService { Option, Option>, )>, + route_data: Rc>>, } impl AppService { /// Crate server settings instance - pub(crate) fn new(config: AppConfig, default: Rc) -> Self { + pub(crate) fn new( + config: AppConfig, + default: Rc, + route_data: Rc>>, + ) -> Self { AppService { config, default, + route_data, root: true, services: Vec::new(), } @@ -53,13 +57,16 @@ impl AppService { pub(crate) fn into_services( self, - ) -> Vec<( - ResourceDef, - HttpNewService, - Option, - Option>, - )> { - self.services + ) -> ( + AppConfig, + Vec<( + ResourceDef, + HttpNewService, + Option, + Option>, + )>, + ) { + (self.config, self.services) } pub(crate) fn clone_config(&self) -> Self { @@ -68,6 +75,7 @@ impl AppService { default: self.default.clone(), services: Vec::new(), root: false, + route_data: self.route_data.clone(), } } @@ -81,6 +89,15 @@ impl AppService { self.default.clone() } + /// Set global route data + pub fn set_route_data(&self, extensions: &mut Extensions) -> bool { + for f in self.route_data.iter() { + f.create(extensions); + } + !self.route_data.is_empty() + } + + /// Register http service pub fn register_service( &mut self, rdef: ResourceDef, @@ -133,24 +150,12 @@ impl AppConfig { pub fn local_addr(&self) -> SocketAddr { self.0.addr } - - /// Resource map - pub fn rmap(&self) -> &ResourceMap { - &self.0.rmap - } - - /// Application extensions - pub fn extensions(&self) -> Ref { - self.0.extensions.borrow() - } } pub(crate) struct AppConfigInner { pub(crate) secure: bool, pub(crate) host: String, pub(crate) addr: SocketAddr, - pub(crate) rmap: ResourceMap, - pub(crate) extensions: RefCell, } impl Default for AppConfigInner { @@ -159,8 +164,6 @@ impl Default for AppConfigInner { secure: false, addr: "127.0.0.1:8080".parse().unwrap(), host: "localhost:8080".to_owned(), - rmap: ResourceMap::new(ResourceDef::new("")), - extensions: RefCell::new(Extensions::new()), } } } @@ -188,23 +191,8 @@ impl ServiceConfig { /// by using `Data` extractor where `T` is data type. /// /// This is same as `App::data()` method. - pub fn data(&mut self, data: S) -> &mut Self { - self.data.push(Box::new(Data::new(data))); - self - } - - /// Set application data factory. This function is - /// similar to `.data()` but it accepts data factory. Data object get - /// constructed asynchronously during application initialization. - /// - /// This is same as `App::data_dactory()` method. - pub fn data_factory(&mut self, data: F) -> &mut Self - where - F: Fn() -> R + 'static, - R: IntoFuture + 'static, - R::Error: std::fmt::Debug, - { - self.data.push(Box::new(data)); + pub fn data> + 'static>(&mut self, data: S) -> &mut Self { + self.data.push(Box::new(data.into())); self } @@ -254,8 +242,6 @@ impl ServiceConfig { mod tests { use actix_service::Service; use bytes::Bytes; - use futures::Future; - use tokio_timer::sleep; use super::*; use crate::http::{Method, StatusCode}; @@ -277,37 +263,37 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_data_factory() { - let cfg = |cfg: &mut ServiceConfig| { - cfg.data_factory(|| { - sleep(std::time::Duration::from_millis(50)).then(|_| { - println!("READY"); - Ok::<_, ()>(10usize) - }) - }); - }; + // #[test] + // fn test_data_factory() { + // let cfg = |cfg: &mut ServiceConfig| { + // cfg.data_factory(|| { + // sleep(std::time::Duration::from_millis(50)).then(|_| { + // println!("READY"); + // Ok::<_, ()>(10usize) + // }) + // }); + // }; - let mut srv = - init_service(App::new().configure(cfg).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + // let mut srv = + // init_service(App::new().configure(cfg).service( + // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + // )); + // let req = TestRequest::default().to_request(); + // let resp = block_on(srv.call(req)).unwrap(); + // assert_eq!(resp.status(), StatusCode::OK); - let cfg2 = |cfg: &mut ServiceConfig| { - cfg.data_factory(|| Ok::<_, ()>(10u32)); - }; - let mut srv = init_service( - App::new() - .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) - .configure(cfg2), - ); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } + // let cfg2 = |cfg: &mut ServiceConfig| { + // cfg.data_factory(|| Ok::<_, ()>(10u32)); + // }; + // let mut srv = init_service( + // App::new() + // .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) + // .configure(cfg2), + // ); + // let req = TestRequest::default().to_request(); + // let resp = block_on(srv.call(req)).unwrap(); + // assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + // } #[test] fn test_external_resource() { diff --git a/src/data.rs b/src/data.rs index c77ba3e2..f23bfff7 100644 --- a/src/data.rs +++ b/src/data.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; -use futures::{Async, Future, IntoFuture, Poll}; use crate::dev::Payload; use crate::extract::FromRequest; @@ -11,11 +10,7 @@ use crate::request::HttpRequest; /// Application data factory pub(crate) trait DataFactory { - fn construct(&self) -> Box; -} - -pub(crate) trait DataFactoryResult { - fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()>; + fn create(&self, extensions: &mut Extensions) -> bool; } /// Application data. @@ -111,8 +106,8 @@ impl FromRequest for Data { #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - if let Some(st) = req.app_config().extensions().get::>() { - Ok(st.clone()) + if let Some(st) = req.get_app_data::() { + Ok(st) } else { log::debug!( "Failed to construct App-level Data extractor. \ @@ -127,142 +122,12 @@ impl FromRequest for Data { } impl DataFactory for Data { - fn construct(&self) -> Box { - Box::new(DataFut { st: self.clone() }) - } -} - -struct DataFut { - st: Data, -} - -impl DataFactoryResult for DataFut { - fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> { - extensions.insert(self.st.clone()); - Ok(Async::Ready(())) - } -} - -impl DataFactory for F -where - F: Fn() -> Out + 'static, - Out: IntoFuture + 'static, - Out::Error: std::fmt::Debug, -{ - fn construct(&self) -> Box { - Box::new(DataFactoryFut { - fut: (*self)().into_future(), - }) - } -} - -struct DataFactoryFut -where - F: Future, - F::Error: std::fmt::Debug, -{ - fut: F, -} - -impl DataFactoryResult for DataFactoryFut -where - F: Future, - F::Error: std::fmt::Debug, -{ - fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()> { - match self.fut.poll() { - Ok(Async::Ready(s)) => { - extensions.insert(Data::new(s)); - Ok(Async::Ready(())) - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { - log::error!("Can not construct application state: {:?}", e); - Err(()) - } - } - } -} - -/// Route data. -/// -/// Route data is an arbitrary data attached to specific route. -/// Route data could be added to route during route configuration process -/// with `Route::data()` method. Route data is also used as an extractor -/// configuration storage. Route data could be accessed in handler -/// via `RouteData` extractor. -/// -/// If route data is not set for a handler, using `RouteData` extractor -/// would cause *Internal Server Error* response. -/// -/// ```rust -/// # use std::cell::Cell; -/// use actix_web::{web, App}; -/// -/// struct MyData { -/// counter: Cell, -/// } -/// -/// /// Use `RouteData` extractor to access data in handler. -/// fn index(data: web::RouteData) { -/// data.counter.set(data.counter.get() + 1); -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get() -/// // Store `MyData` in route storage -/// .data(MyData{ counter: Cell::new(0) }) -/// // Route data could be used as extractor configuration storage, -/// // limit size of the payload -/// .data(web::PayloadConfig::new(4096)) -/// // register handler -/// .to(index) -/// )); -/// } -/// ``` -pub struct RouteData(Arc); - -impl RouteData { - pub(crate) fn new(state: T) -> RouteData { - RouteData(Arc::new(state)) - } - - /// Get referecnce to inner data object. - pub fn get_ref(&self) -> &T { - self.0.as_ref() - } -} - -impl Deref for RouteData { - type Target = T; - - fn deref(&self) -> &T { - self.0.as_ref() - } -} - -impl Clone for RouteData { - fn clone(&self) -> RouteData { - RouteData(self.0.clone()) - } -} - -impl FromRequest for RouteData { - type Config = (); - type Error = Error; - type Future = Result; - - #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - if let Some(st) = req.route_data::() { - Ok(st.clone()) + fn create(&self, extensions: &mut Extensions) -> bool { + if !extensions.contains::>() { + let _ = extensions.insert(Data(self.0.clone())); + true } else { - log::debug!("Failed to construct Route-level Data extractor"); - Err(ErrorInternalServerError( - "Route data is not configured, to configure use Route::data()", - )) + false } } } @@ -297,12 +162,13 @@ mod tests { #[test] fn test_route_data_extractor() { - let mut srv = init_service(App::new().service(web::resource("/").route( - web::get().data(10usize).to(|data: web::RouteData| { - let _ = data.clone(); - HttpResponse::Ok() - }), - ))); + let mut srv = + init_service(App::new().service(web::resource("/").data(10usize).route( + web::get().to(|data: web::Data| { + let _ = data.clone(); + HttpResponse::Ok() + }), + ))); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -311,15 +177,30 @@ mod tests { // different type let mut srv = init_service( App::new().service( - web::resource("/").route( - web::get() - .data(10u32) - .to(|_: web::RouteData| HttpResponse::Ok()), - ), + web::resource("/") + .data(10u32) + .route(web::get().to(|_: web::Data| HttpResponse::Ok())), ), ); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + + #[test] + fn test_override_data() { + let mut srv = init_service(App::new().data(1usize).service( + web::resource("/").data(10usize).route(web::get().to( + |data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }, + )), + )); + + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } } diff --git a/src/extract.rs b/src/extract.rs index 6d414fbc..17b5cb40 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -283,7 +283,7 @@ mod tests { header::CONTENT_TYPE, "application/x-www-form-urlencoded", ) - .route_data(FormConfig::default().limit(4096)) + .data(FormConfig::default().limit(4096)) .to_http_parts(); let r = block_on(Option::>::from_request(&req, &mut pl)).unwrap(); diff --git a/src/handler.rs b/src/handler.rs index 850c0c92..245aba9d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,8 +1,6 @@ -use std::cell::RefCell; use std::marker::PhantomData; -use std::rc::Rc; -use actix_http::{Error, Extensions, Payload, Response}; +use actix_http::{Error, Payload, Response}; use actix_service::{NewService, Service, Void}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; @@ -267,15 +265,13 @@ where /// Extract arguments from request pub struct Extract { - config: Rc>>>, service: S, _t: PhantomData, } impl Extract { - pub fn new(config: Rc>>>, service: S) -> Self { + pub fn new(service: S) -> Self { Extract { - config, service, _t: PhantomData, } @@ -297,14 +293,12 @@ where fn new_service(&self, _: &()) -> Self::Future { ok(ExtractService { _t: PhantomData, - config: self.config.borrow().clone(), service: self.service.clone(), }) } } pub struct ExtractService { - config: Option>, service: S, _t: PhantomData, } @@ -324,8 +318,7 @@ where } fn call(&mut self, req: ServiceRequest) -> Self::Future { - let (mut req, mut payload) = req.into_parts(); - req.set_route_data(self.config.clone()); + let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload).into_future(); ExtractResponse { diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index bd57c66a..bb4fd567 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -870,10 +870,7 @@ mod tests { let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "X-Not-Allowed", - ) + .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed") .to_srv_request(); assert!(cors.inner.validate_allowed_method(req.head()).is_err()); diff --git a/src/request.rs b/src/request.rs index ad5b2488..7b3ab04a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -7,7 +7,7 @@ use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; use crate::config::AppConfig; -use crate::data::{Data, RouteData}; +use crate::data::Data; use crate::error::UrlGenerationError; use crate::extract::FromRequest; use crate::info::ConnectionInfo; @@ -20,9 +20,9 @@ pub struct HttpRequest(pub(crate) Rc); pub(crate) struct HttpRequestInner { pub(crate) head: Message, pub(crate) path: Path, + pub(crate) app_data: Rc, rmap: Rc, config: AppConfig, - route_data: Option>, pool: &'static HttpRequestPool, } @@ -33,6 +33,7 @@ impl HttpRequest { head: Message, rmap: Rc, config: AppConfig, + app_data: Rc, pool: &'static HttpRequestPool, ) -> HttpRequest { HttpRequest(Rc::new(HttpRequestInner { @@ -40,8 +41,8 @@ impl HttpRequest { path, rmap, config, + app_data, pool, - route_data: None, })) } } @@ -195,27 +196,23 @@ impl HttpRequest { /// Get an application data stored with `App::data()` method during /// application configuration. - pub fn app_data(&self) -> Option> { - if let Some(st) = self.0.config.extensions().get::>() { + pub fn app_data(&self) -> Option<&T> { + if let Some(st) = self.0.app_data.get::>() { + Some(&st) + } else { + None + } + } + + /// Get an application data stored with `App::data()` method during + /// application configuration. + pub fn get_app_data(&self) -> Option> { + if let Some(st) = self.0.app_data.get::>() { Some(st.clone()) } else { None } } - - /// Load route data. Route data could be set during - /// route configuration with `Route::data()` method. - pub fn route_data(&self) -> Option<&RouteData> { - if let Some(ref ext) = self.0.route_data { - ext.get::>() - } else { - None - } - } - - pub(crate) fn set_route_data(&mut self, data: Option>) { - Rc::get_mut(&mut self.0).unwrap().route_data = data; - } } impl HttpMessage for HttpRequest { diff --git a/src/resource.rs b/src/resource.rs index 03c614a9..8bafc0fc 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::fmt; use std::rc::Rc; -use actix_http::{Error, Response}; +use actix_http::{Error, Extensions, Response}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform, @@ -10,6 +10,7 @@ use actix_service::{ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; use crate::guard::Guard; @@ -48,6 +49,7 @@ pub struct Resource { rdef: String, name: Option, routes: Vec, + data: Option, guards: Vec>, default: Rc>>>, factory_ref: Rc>>, @@ -64,6 +66,7 @@ impl Resource { endpoint: ResourceEndpoint::new(fref.clone()), factory_ref: fref, guards: Vec::new(), + data: None, default: Rc::new(RefCell::new(None)), } } @@ -154,7 +157,42 @@ where /// # fn delete_handler() {} /// ``` pub fn route(mut self, route: Route) -> Self { - self.routes.push(route.finish()); + self.routes.push(route); + self + } + + /// Provide resource specific data. This method allows to add extractor + /// configuration or specific state available via `Data` extractor. + /// Provided data is available for all routes registered for the current resource. + /// Resource data overrides data registered by `App::data()` method. + /// + /// ```rust + /// use actix_web::{web, App, FromRequest}; + /// + /// /// extract text data from request + /// fn index(body: String) -> String { + /// format!("Body {}!", body) + /// } + /// + /// fn main() { + /// let app = App::new().service( + /// web::resource("/index.html") + /// // limit size of the payload + /// .data(String::configure(|cfg| { + /// cfg.limit(4096) + /// })) + /// .route( + /// web::get() + /// // register handler + /// .to(index) + /// )); + /// } + /// ``` + pub fn data(mut self, data: U) -> Self { + if self.data.is_none() { + self.data = Some(Extensions::new()); + } + self.data.as_mut().unwrap().insert(Data::new(data)); self } @@ -260,6 +298,7 @@ where guards: self.guards, routes: self.routes, default: self.default, + data: self.data, factory_ref: self.factory_ref, } } @@ -361,6 +400,10 @@ where if let Some(ref name) = self.name { *rdef.name_mut() = name.clone(); } + // custom app data storage + if let Some(ref mut ext) = self.data { + config.set_route_data(ext); + } config.register_service(rdef, guards, self, None) } } @@ -377,6 +420,7 @@ where fn into_new_service(self) -> T { *self.factory_ref.borrow_mut() = Some(ResourceFactory { routes: self.routes, + data: self.data.map(|data| Rc::new(data)), default: self.default, }); @@ -386,6 +430,7 @@ where pub struct ResourceFactory { routes: Vec, + data: Option>, default: Rc>>>, } @@ -410,6 +455,7 @@ impl NewService for ResourceFactory { .iter() .map(|route| CreateRouteServiceItem::Future(route.new_service(&()))) .collect(), + data: self.data.clone(), default: None, default_fut, } @@ -423,6 +469,7 @@ enum CreateRouteServiceItem { pub struct CreateResourceService { fut: Vec, + data: Option>, default: Option, default_fut: Option>>, } @@ -467,6 +514,7 @@ impl Future for CreateResourceService { .collect(); Ok(Async::Ready(ResourceService { routes, + data: self.data.clone(), default: self.default.take(), })) } else { @@ -477,6 +525,7 @@ impl Future for CreateResourceService { pub struct ResourceService { routes: Vec, + data: Option>, default: Option, } @@ -496,6 +545,9 @@ impl Service for ResourceService { fn call(&mut self, mut req: ServiceRequest) -> Self::Future { for route in self.routes.iter_mut() { if route.check(&mut req) { + if let Some(ref data) = self.data { + req.set_data_container(data.clone()); + } return route.call(req); } } diff --git a/src/route.rs b/src/route.rs index 8c97d772..62f030c7 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,12 +1,10 @@ -use std::cell::RefCell; use std::rc::Rc; -use actix_http::{http::Method, Error, Extensions}; +use actix_http::{http::Method, Error}; use actix_service::{NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::data::RouteData; use crate::extract::FromRequest; use crate::guard::{self, Guard}; use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; @@ -44,30 +42,19 @@ type BoxedRouteNewService = Box< pub struct Route { service: BoxedRouteNewService, guards: Rc>>, - data: Option, - data_ref: Rc>>>, } impl Route { /// Create new route which matches any request. pub fn new() -> Route { - let data_ref = Rc::new(RefCell::new(None)); Route { - service: Box::new(RouteNewService::new(Extract::new( - data_ref.clone(), - Handler::new(|| HttpResponse::NotFound()), - ))), + service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| { + HttpResponse::NotFound() + })))), guards: Rc::new(Vec::new()), - data: None, - data_ref, } } - pub(crate) fn finish(mut self) -> Self { - *self.data_ref.borrow_mut() = self.data.take().map(Rc::new); - self - } - pub(crate) fn take_guards(&mut self) -> Vec> { std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new()) } @@ -239,10 +226,8 @@ impl Route { T: FromRequest + 'static, R: Responder + 'static, { - self.service = Box::new(RouteNewService::new(Extract::new( - self.data_ref.clone(), - Handler::new(handler), - ))); + self.service = + Box::new(RouteNewService::new(Extract::new(Handler::new(handler)))); self } @@ -281,42 +266,9 @@ impl Route { R::Item: Responder, R::Error: Into, { - self.service = Box::new(RouteNewService::new(Extract::new( - self.data_ref.clone(), - AsyncHandler::new(handler), - ))); - self - } - - /// Provide route specific data. This method allows to add extractor - /// configuration or specific state available via `RouteData` extractor. - /// - /// ```rust - /// use actix_web::{web, App, FromRequest}; - /// - /// /// extract text data from request - /// fn index(body: String) -> String { - /// format!("Body {}!", body) - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/index.html").route( - /// web::get() - /// // limit size of the payload - /// .data(String::configure(|cfg| { - /// cfg.limit(4096) - /// })) - /// // register handler - /// .to(index) - /// )); - /// } - /// ``` - pub fn data(mut self, data: T) -> Self { - if self.data.is_none() { - self.data = Some(Extensions::new()); - } - self.data.as_mut().unwrap().insert(RouteData::new(data)); + self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new( + handler, + )))); self } } diff --git a/src/scope.rs b/src/scope.rs index d048d143..ada53334 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -322,6 +322,7 @@ where default: self.default.clone(), services: Rc::new( cfg.into_services() + .1 .into_iter() .map(|(mut rdef, srv, guards, nested)| { rmap.add(&mut rdef, nested); diff --git a/src/service.rs b/src/service.rs index 7fbbf013..f35ea89f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,5 @@ use std::cell::{Ref, RefMut}; +use std::rc::Rc; use std::{fmt, net}; use actix_http::body::{Body, MessageBody, ResponseBody}; @@ -180,12 +181,18 @@ impl ServiceRequest { /// Get an application data stored with `App::data()` method during /// application configuration. pub fn app_data(&self) -> Option> { - if let Some(st) = self.req.app_config().extensions().get::>() { + if let Some(st) = self.req.0.app_data.get::>() { Some(st.clone()) } else { None } } + + #[doc(hidden)] + /// Set new app data container + pub fn set_data_container(&mut self, extensions: Rc) { + Rc::get_mut(&mut self.req.0).unwrap().app_data = extensions; + } } impl Resource for ServiceRequest { diff --git a/src/test.rs b/src/test.rs index 7d332118..66b380e8 100644 --- a/src/test.rs +++ b/src/test.rs @@ -2,11 +2,10 @@ use std::cell::RefCell; use std::rc::Rc; -use actix_http::cookie::Cookie; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{Extensions, Request}; +use actix_http::{cookie::Cookie, Extensions, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; use actix_server_config::ServerConfig; @@ -20,7 +19,7 @@ use serde_json; pub use actix_http::test::TestBuffer; use crate::config::{AppConfig, AppConfigInner}; -use crate::data::{Data, RouteData}; +use crate::data::Data; use crate::dev::{Body, MessageBody, Payload}; use crate::request::HttpRequestPool; use crate::rmap::ResourceMap; @@ -363,8 +362,8 @@ pub struct TestRequest { req: HttpTestRequest, rmap: ResourceMap, config: AppConfigInner, - route_data: Extensions, path: Path, + app_data: Extensions, } impl Default for TestRequest { @@ -373,8 +372,8 @@ impl Default for TestRequest { req: HttpTestRequest::default(), rmap: ResourceMap::new(ResourceDef::new("")), config: AppConfigInner::default(), - route_data: Extensions::new(), path: Path::new(Url::new(Uri::default())), + app_data: Extensions::new(), } } } @@ -479,15 +478,8 @@ impl TestRequest { /// Set application data. This is equivalent of `App::data()` method /// for testing purpose. - pub fn app_data(self, data: T) -> Self { - self.config.extensions.borrow_mut().insert(Data::new(data)); - self - } - - /// Set route data. This is equivalent of `Route::data()` method - /// for testing purpose. - pub fn route_data(mut self, data: T) -> Self { - self.route_data.insert(RouteData::new(data)); + pub fn data(mut self, data: T) -> Self { + self.app_data.insert(Data::new(data)); self } @@ -513,6 +505,7 @@ impl TestRequest { head, Rc::new(self.rmap), AppConfig::new(self.config), + Rc::new(self.app_data), HttpRequestPool::create(), ); @@ -529,15 +522,14 @@ impl TestRequest { let (head, _) = self.req.finish().into_parts(); self.path.get_mut().update(&head.uri); - let mut req = HttpRequest::new( + HttpRequest::new( self.path, head, Rc::new(self.rmap), AppConfig::new(self.config), + Rc::new(self.app_data), HttpRequestPool::create(), - ); - req.set_route_data(Some(Rc::new(self.route_data))); - req + ) } /// Complete request creation and generate `HttpRequest` and `Payload` instances @@ -545,14 +537,15 @@ impl TestRequest { let (head, payload) = self.req.finish().into_parts(); self.path.get_mut().update(&head.uri); - let mut req = HttpRequest::new( + let req = HttpRequest::new( self.path, head, Rc::new(self.rmap), AppConfig::new(self.config), + Rc::new(self.app_data), HttpRequestPool::create(), ); - req.set_route_data(Some(Rc::new(self.route_data))); + (req, payload) } } @@ -571,15 +564,20 @@ mod tests { .version(Version::HTTP_2) .set(header::Date(SystemTime::now().into())) .param("test", "123") - .app_data(10u32) + .data(10u32) .to_http_request(); assert!(req.headers().contains_key(header::CONTENT_TYPE)); assert!(req.headers().contains_key(header::DATE)); assert_eq!(&req.match_info()["test"], "123"); assert_eq!(req.version(), Version::HTTP_2); - let data = req.app_data::().unwrap(); + let data = req.get_app_data::().unwrap(); + assert!(req.get_app_data::().is_none()); assert_eq!(*data, 10); assert_eq!(*data.get_ref(), 10); + + assert!(req.app_data::().is_none()); + let data = req.app_data::().unwrap(); + assert_eq!(*data, 10); } #[test] diff --git a/src/types/form.rs b/src/types/form.rs index e8f78c49..0bc6a030 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -81,7 +81,7 @@ where fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .route_data::() + .app_data::() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((16384, None)); @@ -132,12 +132,11 @@ impl fmt::Display for Form { /// fn main() { /// let app = App::new().service( /// web::resource("/index.html") -/// .route(web::get() -/// // change `Form` extractor configuration -/// .data( -/// web::Form::::configure(|cfg| cfg.limit(4097)) -/// ) -/// .to(index)) +/// // change `Form` extractor configuration +/// .data( +/// web::Form::::configure(|cfg| cfg.limit(4097)) +/// ) +/// .route(web::get().to(index)) /// ); /// } /// ``` diff --git a/src/types/json.rs b/src/types/json.rs index 3543975a..73614d87 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -176,7 +176,7 @@ where fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .route_data::() + .app_data::() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((32768, None)); @@ -220,17 +220,16 @@ where /// /// fn main() { /// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::post().data( -/// // change json extractor configuration -/// web::Json::::configure(|cfg| { -/// cfg.limit(4096) -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }) -/// })) -/// .to(index)) +/// web::resource("/index.html").data( +/// // change json extractor configuration +/// web::Json::::configure(|cfg| { +/// cfg.limit(4096) +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// }) +/// })) +/// .route(web::post().to(index)) /// ); /// } /// ``` @@ -431,7 +430,7 @@ mod tests { header::HeaderValue::from_static("16"), ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .route_data(JsonConfig::default().limit(10).error_handler(|err, _| { + .data(JsonConfig::default().limit(10).error_handler(|err, _| { let msg = MyObject { name: "invalid request".to_string(), }; @@ -483,7 +482,7 @@ mod tests { header::HeaderValue::from_static("16"), ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .route_data(JsonConfig::default().limit(10)) + .data(JsonConfig::default().limit(10)) .to_http_parts(); let s = block_on(Json::::from_request(&req, &mut pl)); @@ -500,7 +499,7 @@ mod tests { header::HeaderValue::from_static("16"), ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .route_data( + .data( JsonConfig::default() .limit(10) .error_handler(|_, _| JsonPayloadError::ContentType.into()), diff --git a/src/types/payload.rs b/src/types/payload.rs index ca4b5de6..8e4dd703 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -130,7 +130,7 @@ impl FromRequest for Bytes { #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { let mut tmp; - let cfg = if let Some(cfg) = req.route_data::() { + let cfg = if let Some(cfg) = req.app_data::() { cfg } else { tmp = PayloadConfig::default(); @@ -167,12 +167,11 @@ impl FromRequest for Bytes { /// /// fn main() { /// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get() -/// .data(String::configure(|cfg| { // <- limit size of the payload -/// cfg.limit(4096) -/// })) -/// .to(index)) // <- register handler with extractor params +/// web::resource("/index.html") +/// .data(String::configure(|cfg| { // <- limit size of the payload +/// cfg.limit(4096) +/// })) +/// .route(web::get().to(index)) // <- register handler with extractor params /// ); /// } /// ``` @@ -185,7 +184,7 @@ impl FromRequest for String { #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { let mut tmp; - let cfg = if let Some(cfg) = req.route_data::() { + let cfg = if let Some(cfg) = req.app_data::() { cfg } else { tmp = PayloadConfig::default(); diff --git a/src/web.rs b/src/web.rs index 1ecebe77..5669a1e8 100644 --- a/src/web.rs +++ b/src/web.rs @@ -15,7 +15,7 @@ use crate::scope::Scope; use crate::service::WebService; pub use crate::config::ServiceConfig; -pub use crate::data::{Data, RouteData}; +pub use crate::data::Data; pub use crate::request::HttpRequest; pub use crate::types::*; From 3d1af19080d6d2e2b20ba8b435d09584b72cfc5d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 4 May 2019 19:51:00 -0700 Subject: [PATCH 1364/1635] prepare actix-http release --- actix-files/src/named.rs | 1 + actix-http/CHANGES.md | 2 +- actix-http/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index c506c02f..41a7cf1f 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -66,6 +66,7 @@ impl NamedFile { /// let mut file = File::create("foo.txt")?; /// file.write_all(b"Hello, world!")?; /// let named_file = NamedFile::from_file(file, "bar.txt")?; + /// # std::fs::remove_file("foo.txt"); /// Ok(()) /// } /// ``` diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 69abcfba..872a481d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.5] - 2019-05-xx +## [0.1.5] - 2019-05-04 ### Fixed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9d044c64..bcc9b456 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.4" +version = "0.1.5" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From 005c055a7f2feaee7c96a7cdf6936091d95575f0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 4 May 2019 20:05:20 -0700 Subject: [PATCH 1365/1635] prepare actix-web release --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d1855b22..d880ac88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -71,7 +71,7 @@ actix-utils = "0.3.4" actix-router = "0.1.3" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" -actix-http = { version = "0.1.4", features=["fail"] } +actix-http = { version = "0.1.5", features=["fail"] } actix-server = "0.4.3" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" @@ -99,7 +99,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.4", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.1.5", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.1", features=["ssl"] } actix-files = { version = "0.1.0-beta.1" } rand = "0.6" From 33b4c055570a8ecad567797ea6187919109862db Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 4 May 2019 22:18:02 -0700 Subject: [PATCH 1366/1635] add payload stream migration entry --- Cargo.toml | 2 +- MIGRATION.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d880ac88..a8714846 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ hashbrown = "0.2.2" log = "0.4" mime = "0.3" net2 = "0.2.33" -parking_lot = "0.7" +parking_lot = "0.8" regex = "1.0" serde = { version = "1.0", features=["derive"] } serde_json = "1.0" diff --git a/MIGRATION.md b/MIGRATION.md index c7932b60..a07a6508 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -92,6 +92,36 @@ App.new().service(web::resource("/welcome").to(welcome)) ``` +* `HttpRequest` does not provide access to request's payload stream. + + instead of + + ```rust +fn index(req: &HttpRequest) -> Box> { + req + .payload() + .from_err() + .fold((), |_, chunk| { + ... + }) + .map(|_| HttpResponse::Ok().finish()) + .responder() +} + ``` + + use `Payload` extractor + + ```rust +fn index(stream: web::Payload) -> impl Future { + stream + .from_err() + .fold((), |_, chunk| { + ... + }) + .map(|_| HttpResponse::Ok().finish()) +} + ``` + * `State` is now `Data`. You register Data during the App initialization process and then access it from handlers either using a Data extractor or using HttpRequest's api. From a17ff492a1840535e36ae048a455a19e748b9f34 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 4 May 2019 22:18:59 -0700 Subject: [PATCH 1367/1635] fix formatting --- MIGRATION.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index a07a6508..73669ddb 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -97,7 +97,7 @@ instead of ```rust -fn index(req: &HttpRequest) -> Box> { + fn index(req: &HttpRequest) -> Box> { req .payload() .from_err() @@ -106,21 +106,21 @@ fn index(req: &HttpRequest) -> Box> { }) .map(|_| HttpResponse::Ok().finish()) .responder() -} - ``` + } + ``` - use `Payload` extractor + use `Payload` extractor ```rust -fn index(stream: web::Payload) -> impl Future { - stream + fn index(stream: web::Payload) -> impl Future { + stream .from_err() .fold((), |_, chunk| { ... }) .map(|_| HttpResponse::Ok().finish()) -} - ``` + } + ``` * `State` is now `Data`. You register Data during the App initialization process and then access it from handlers either using a Data extractor or using From a77b0b054a00f24bf1f3cc3637d919702ddc1602 Mon Sep 17 00:00:00 2001 From: Nikolai Vazquez Date: Fri, 10 May 2019 23:44:49 +0200 Subject: [PATCH 1368/1635] Make `App::configure` take an `FnOnce` (#825) --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index bac71250..eb14d46f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -128,7 +128,7 @@ where /// ``` pub fn configure(mut self, f: F) -> Self where - F: Fn(&mut ServiceConfig), + F: FnOnce(&mut ServiceConfig), { let mut cfg = ServiceConfig::new(); f(&mut cfg); From 4066375737c5c7669c4a441bbc4aeb48f46097af Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 10 May 2019 14:45:30 -0700 Subject: [PATCH 1369/1635] Update CHANGES.md --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index cfd7a6df..ce7da6d2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +### Changes + +* `App::configure` take an `FnOnce` instead of `Fn` + + ## [1.0.0-beta.3] - 2019-05-04 ### Added From df08baf67f166d2d75118b859f1049b01944daf4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 08:34:51 -0700 Subject: [PATCH 1370/1635] update actix-net dependencies --- Cargo.toml | 13 +++--- actix-files/Cargo.toml | 4 +- actix-files/src/lib.rs | 2 + actix-framed/Cargo.toml | 5 ++- actix-framed/src/app.rs | 7 +++- actix-framed/src/helpers.rs | 4 +- actix-framed/src/route.rs | 1 + actix-framed/src/service.rs | 24 ++++++----- actix-http/CHANGES.md | 9 ++++ actix-http/Cargo.toml | 10 ++--- actix-http/src/builder.rs | 26 ++++++++---- actix-http/src/h1/expect.rs | 4 +- actix-http/src/h1/service.rs | 35 +++++++++------- actix-http/src/h1/upgrade.rs | 4 +- actix-http/src/h2/service.rs | 18 ++++---- actix-http/src/service.rs | 46 +++++++++++---------- actix-http/tests/test_server.rs | 10 ++--- actix-multipart/Cargo.toml | 2 +- actix-session/Cargo.toml | 2 +- awc/Cargo.toml | 4 +- awc/src/builder.rs | 71 +++++++++++++++----------------- awc/src/response.rs | 3 +- awc/src/test.rs | 29 ------------- awc/src/ws.rs | 49 +++++++++++----------- awc/tests/test_client.rs | 14 +++---- awc/tests/test_ws.rs | 70 +++++++------------------------ src/app.rs | 8 +++- src/app_service.rs | 12 ++++-- src/config.rs | 1 + src/handler.rs | 32 +++++++++----- src/middleware/cors.rs | 12 +++--- src/middleware/defaultheaders.rs | 20 +++++---- src/middleware/errhandlers.rs | 14 +++---- src/middleware/logger.rs | 8 ++-- src/middleware/normalize.rs | 16 +++---- src/resource.rs | 8 ++++ src/route.rs | 5 +++ src/scope.rs | 11 ++++- src/server.rs | 12 +++--- src/service.rs | 2 + src/test.rs | 9 ++-- test-server/Cargo.toml | 6 +-- test-server/src/lib.rs | 40 +++++++++++++----- 43 files changed, 361 insertions(+), 321 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8714846..f4e2f79e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,13 +66,13 @@ rust-tls = ["rustls", "actix-server/rust-tls"] [dependencies] actix-codec = "0.1.2" -actix-service = "0.3.6" -actix-utils = "0.3.4" +actix-service = "0.4.0" +actix-utils = "0.4.0" actix-router = "0.1.3" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" actix-http = { version = "0.1.5", features=["fail"] } -actix-server = "0.4.3" +actix-server = "0.5.0" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" actix = { version = "0.8.1", features=["http"], optional = true } @@ -101,7 +101,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.1.5", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.1", features=["ssl"] } -actix-files = { version = "0.1.0-beta.1" } +actix-files = { version = "0.1.0-betsa.1" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" @@ -121,5 +121,8 @@ actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } actix-web-actors = { path = "actix-web-actors" } actix-session = { path = "actix-session" } -actix-files = { path = "actix-files" } awc = { path = "awc" } + +actix-files = { path = "actix-files" } +actix-framed = { path = "actix-framed" } +actix-multipart = { path = "actix-multipart" } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 5e37fc09..a0cbba36 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-beta.1" +version = "0.1.0-betsa.1" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = "1.0.0-beta.1" -actix-service = "0.3.4" +actix-service = "0.4.0" bitflags = "1" bytes = "0.4" futures = "0.1.25" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 4038a548..301d9d81 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -334,6 +334,7 @@ impl Files { where F: IntoNewService, U: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -363,6 +364,7 @@ impl HttpServiceFactory for Files { } impl NewService for Files { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index f0622fd9..98551536 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -21,11 +21,12 @@ path = "src/lib.rs" [dependencies] actix-codec = "0.1.2" -actix-service = "0.3.6" -actix-utils = "0.3.4" +actix-service = "0.4.0" +actix-utils = "0.4.0" actix-router = "0.1.2" actix-rt = "0.2.2" actix-http = "0.1.0" +actix-server-config = "0.1.1" bytes = "0.4" futures = "0.1.25" diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs index 20bc2f77..297796bd 100644 --- a/actix-framed/src/app.rs +++ b/actix-framed/src/app.rs @@ -4,6 +4,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::h1::{Codec, SendResponse}; use actix_http::{Error, Request, Response}; use actix_router::{Path, Router, Url}; +use actix_server_config::ServerConfig; use actix_service::{IntoNewService, NewService, Service}; use actix_utils::cloneable::CloneableService; use futures::{Async, Future, Poll}; @@ -49,6 +50,7 @@ impl FramedApp { where U: HttpServiceFactory, U::Factory: NewService< + Config = (), Request = FramedRequest, Response = (), Error = Error, @@ -88,11 +90,12 @@ pub struct FramedAppFactory { services: Rc>)>>, } -impl NewService for FramedAppFactory +impl NewService for FramedAppFactory where T: AsyncRead + AsyncWrite + 'static, S: 'static, { + type Config = ServerConfig; type Request = (Request, Framed); type Response = (); type Error = Error; @@ -100,7 +103,7 @@ where type Service = CloneableService>; type Future = CreateService; - fn new_service(&self, _: &C) -> Self::Future { + fn new_service(&self, _: &ServerConfig) -> Self::Future { CreateService { fut: self .services diff --git a/actix-framed/src/helpers.rs b/actix-framed/src/helpers.rs index c2c7dbd8..944b729d 100644 --- a/actix-framed/src/helpers.rs +++ b/actix-framed/src/helpers.rs @@ -13,6 +13,7 @@ pub(crate) type BoxedHttpService = Box< pub(crate) type BoxedHttpNewService = Box< NewService< + Config = (), Request = Req, Response = (), Error = Error, @@ -39,12 +40,13 @@ where impl NewService for HttpNewService where - T: NewService, + T: NewService, T::Request: 'static, T::Future: 'static, T::Service: Service>> + 'static, ::Future: 'static, { + type Config = (); type Request = T::Request; type Response = (); type Error = Error; diff --git a/actix-framed/src/route.rs b/actix-framed/src/route.rs index c8d9d432..c50401d6 100644 --- a/actix-framed/src/route.rs +++ b/actix-framed/src/route.rs @@ -106,6 +106,7 @@ where R::Future: 'static, R::Error: fmt::Display, { + type Config = (); type Request = FramedRequest; type Response = (); type Error = Error; diff --git a/actix-framed/src/service.rs b/actix-framed/src/service.rs index 6e5c7a54..fbbc9fbe 100644 --- a/actix-framed/src/service.rs +++ b/actix-framed/src/service.rs @@ -12,22 +12,23 @@ use futures::{Async, Future, IntoFuture, Poll, Sink}; /// Service that verifies incoming request if it is valid websocket /// upgrade request. In case of error returns `HandshakeError` -pub struct VerifyWebSockets { - _t: PhantomData, +pub struct VerifyWebSockets { + _t: PhantomData<(T, C)>, } -impl Default for VerifyWebSockets { +impl Default for VerifyWebSockets { fn default() -> Self { VerifyWebSockets { _t: PhantomData } } } -impl NewService for VerifyWebSockets { +impl NewService for VerifyWebSockets { + type Config = C; type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); - type Service = VerifyWebSockets; + type Service = VerifyWebSockets; type Future = FutureResult; fn new_service(&self, _: &C) -> Self::Future { @@ -35,7 +36,7 @@ impl NewService for VerifyWebSockets { } } -impl Service for VerifyWebSockets { +impl Service for VerifyWebSockets { type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); @@ -54,9 +55,9 @@ impl Service for VerifyWebSockets { } /// Send http/1 error response -pub struct SendError(PhantomData<(T, R, E)>); +pub struct SendError(PhantomData<(T, R, E, C)>); -impl Default for SendError +impl Default for SendError where T: AsyncRead + AsyncWrite, E: ResponseError, @@ -66,17 +67,18 @@ where } } -impl NewService for SendError +impl NewService for SendError where T: AsyncRead + AsyncWrite + 'static, R: 'static, E: ResponseError + 'static, { + type Config = C; type Request = Result)>; type Response = R; type Error = (E, Framed); type InitError = (); - type Service = SendError; + type Service = SendError; type Future = FutureResult; fn new_service(&self, _: &C) -> Self::Future { @@ -84,7 +86,7 @@ where } } -impl Service for SendError +impl Service for SendError where T: AsyncRead + AsyncWrite + 'static, R: 'static, diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 872a481d..f1c119d5 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,14 @@ # Changes +## [0.2.0] - 2019-xx-xx + +### Changed + +* Update actix-service to 0.4 + +* Expect and upgrade services accept `ServerConfig` config. + + ## [0.1.5] - 2019-05-04 ### Fixed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index bcc9b456..eaac7c09 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -44,10 +44,10 @@ fail = ["failure"] secure-cookies = ["ring"] [dependencies] -actix-service = "0.3.6" +actix-service = "0.4.0" actix-codec = "0.1.2" -actix-connect = "0.1.5" -actix-utils = "0.3.5" +actix-connect = "0.2.0" +actix-utils = "0.4.0" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" @@ -97,8 +97,8 @@ chrono = "0.4.6" [dev-dependencies] actix-rt = "0.2.2" -actix-server = { version = "0.4.3", features=["ssl"] } -actix-connect = { version = "0.1.4", features=["ssl"] } +actix-server = { version = "0.5.0", features=["ssl"] } +actix-connect = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "0.1.0", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 56f144bd..b1b193a9 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -29,7 +29,7 @@ pub struct HttpServiceBuilder> { impl HttpServiceBuilder> where - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, { @@ -48,13 +48,17 @@ where impl HttpServiceBuilder where - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, - X: NewService, + X: NewService, X::Error: Into, X::InitError: fmt::Debug, - U: NewService), Response = ()>, + U: NewService< + Config = SrvConfig, + Request = (Request, Framed), + Response = (), + >, U::Error: fmt::Display, U::InitError: fmt::Debug, { @@ -101,7 +105,7 @@ where pub fn expect(self, expect: F) -> HttpServiceBuilder where F: IntoNewService, - X1: NewService, + X1: NewService, X1::Error: Into, X1::InitError: fmt::Debug, { @@ -122,7 +126,11 @@ where pub fn upgrade(self, upgrade: F) -> HttpServiceBuilder where F: IntoNewService, - U1: NewService), Response = ()>, + U1: NewService< + Config = SrvConfig, + Request = (Request, Framed), + Response = (), + >, U1::Error: fmt::Display, U1::InitError: fmt::Debug, { @@ -140,7 +148,7 @@ where pub fn h1(self, service: F) -> H1Service where B: MessageBody + 'static, - F: IntoNewService, + F: IntoNewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -159,7 +167,7 @@ where pub fn h2(self, service: F) -> H2Service where B: MessageBody + 'static, - F: IntoNewService, + F: IntoNewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -177,7 +185,7 @@ where pub fn finish(self, service: F) -> HttpService where B: MessageBody + 'static, - F: IntoNewService, + F: IntoNewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 86fcb2cc..32b6bd9c 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,3 +1,4 @@ +use actix_server_config::ServerConfig; use actix_service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{Async, Poll}; @@ -8,6 +9,7 @@ use crate::request::Request; pub struct ExpectHandler; impl NewService for ExpectHandler { + type Config = ServerConfig; type Request = Request; type Response = Request; type Error = Error; @@ -15,7 +17,7 @@ impl NewService for ExpectHandler { type InitError = Error; type Future = FutureResult; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, _: &ServerConfig) -> Self::Future { ok(ExpectHandler) } } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index ecf6c8b9..2c0a48eb 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -29,14 +29,14 @@ pub struct H1Service> { impl H1Service where - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, { /// Create new `HttpService` instance with default config. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { @@ -49,10 +49,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( - cfg: ServiceConfig, - service: F, - ) -> Self { + pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { H1Service { cfg, srv: service.into_new_service(), @@ -65,7 +62,7 @@ where impl H1Service where - S: NewService, + S: NewService, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, @@ -102,21 +99,26 @@ where } } -impl NewService for H1Service +impl NewService for H1Service where T: IoStream, - S: NewService, + S: NewService, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, - X: NewService, + X: NewService, X::Error: Into, X::InitError: fmt::Debug, - U: NewService), Response = ()>, + U: NewService< + Config = SrvConfig, + Request = (Request, Framed), + Response = (), + >, U::Error: fmt::Display, U::InitError: fmt::Debug, { + type Config = SrvConfig; type Request = Io; type Response = (); type Error = DispatchError; @@ -127,8 +129,8 @@ where fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H1ServiceResponse { fut: self.srv.new_service(cfg).into_future(), - fut_ex: Some(self.expect.new_service(&())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())), + fut_ex: Some(self.expect.new_service(cfg)), + fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), expect: None, upgrade: None, cfg: Some(self.cfg.clone()), @@ -140,7 +142,7 @@ where #[doc(hidden)] pub struct H1ServiceResponse where - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, X: NewService, @@ -162,7 +164,7 @@ where impl Future for H1ServiceResponse where T: IoStream, - S: NewService, + S: NewService, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, @@ -320,10 +322,11 @@ where } } -impl NewService for OneRequest +impl NewService for OneRequest where T: IoStream, { + type Config = SrvConfig; type Request = Io; type Response = (Request, Framed); type Error = ParseError; diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 0d0164fe..0278f23e 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; use actix_codec::Framed; +use actix_server_config::ServerConfig; use actix_service::{NewService, Service}; use futures::future::FutureResult; use futures::{Async, Poll}; @@ -12,6 +13,7 @@ use crate::request::Request; pub struct UpgradeHandler(PhantomData); impl NewService for UpgradeHandler { + type Config = ServerConfig; type Request = (Request, Framed); type Response = (); type Error = Error; @@ -19,7 +21,7 @@ impl NewService for UpgradeHandler { type InitError = Error; type Future = FutureResult; - fn new_service(&self, _: &()) -> Self::Future { + fn new_service(&self, _: &ServerConfig) -> Self::Future { unimplemented!() } } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 42b8d8d8..b4191f03 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -31,14 +31,14 @@ pub struct H2Service { impl H2Service where - S: NewService, + S: NewService, S::Error: Into, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H2Service { @@ -49,10 +49,7 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>( - cfg: ServiceConfig, - service: F, - ) -> Self { + pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { H2Service { cfg, srv: service.into_new_service(), @@ -61,15 +58,16 @@ where } } -impl NewService for H2Service +impl NewService for H2Service where T: IoStream, - S: NewService, + S: NewService, S::Error: Into, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, { + type Config = SrvConfig; type Request = Io; type Response = (); type Error = DispatchError; @@ -87,7 +85,7 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse, B> { +pub struct H2ServiceResponse { fut: ::Future, cfg: Option, _t: PhantomData<(T, P, B)>, @@ -96,7 +94,7 @@ pub struct H2ServiceResponse, impl Future for H2ServiceResponse where T: IoStream, - S: NewService, + S: NewService, S::Error: Into, S::Response: Into>, ::Future: 'static, diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index dd3af1db..b762f3cb 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -30,7 +30,7 @@ pub struct HttpService HttpService where - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -45,7 +45,7 @@ where impl HttpService where - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -53,7 +53,7 @@ where B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); HttpService { @@ -66,7 +66,7 @@ where } /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -82,7 +82,7 @@ where impl HttpService where - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -95,7 +95,7 @@ where /// request will be forwarded to main service. pub fn expect(self, expect: X1) -> HttpService where - X1: NewService, + X1: NewService, X1::Error: Into, X1::InitError: fmt::Debug, { @@ -114,7 +114,11 @@ where /// and this service get called with original request and framed object. pub fn upgrade(self, upgrade: Option) -> HttpService where - U1: NewService), Response = ()>, + U1: NewService< + Config = SrvConfig, + Request = (Request, Framed), + Response = (), + >, U1::Error: fmt::Display, U1::InitError: fmt::Debug, { @@ -128,22 +132,27 @@ where } } -impl NewService for HttpService +impl NewService for HttpService where T: IoStream, - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, ::Future: 'static, B: MessageBody + 'static, - X: NewService, + X: NewService, X::Error: Into, X::InitError: fmt::Debug, - U: NewService), Response = ()>, + U: NewService< + Config = SrvConfig, + Request = (Request, Framed), + Response = (), + >, U::Error: fmt::Display, U::InitError: fmt::Debug, { + type Config = SrvConfig; type Request = ServerIo; type Response = (); type Error = DispatchError; @@ -154,8 +163,8 @@ where fn new_service(&self, cfg: &SrvConfig) -> Self::Future { HttpServiceResponse { fut: self.srv.new_service(cfg).into_future(), - fut_ex: Some(self.expect.new_service(&())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())), + fut_ex: Some(self.expect.new_service(cfg)), + fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), expect: None, upgrade: None, cfg: Some(self.cfg.clone()), @@ -165,14 +174,7 @@ where } #[doc(hidden)] -pub struct HttpServiceResponse< - T, - P, - S: NewService, - B, - X: NewService, - U: NewService, -> { +pub struct HttpServiceResponse { fut: S::Future, fut_ex: Option, fut_upg: Option, @@ -185,7 +187,7 @@ pub struct HttpServiceResponse< impl Future for HttpServiceResponse where T: IoStream, - S: NewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 4b56e4b2..d0c5e352 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -5,7 +5,7 @@ use std::{net, thread}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; -use actix_service::{fn_cfg_factory, fn_service, NewService}; +use actix_service::{new_service_cfg, service_fn, NewService}; use bytes::{Bytes, BytesMut}; use futures::future::{self, ok, Future}; use futures::stream::{once, Stream}; @@ -163,7 +163,7 @@ fn test_h2_body() -> std::io::Result<()> { fn test_expect_continue() { let srv = TestServer::new(|| { HttpService::build() - .expect(fn_service(|req: Request| { + .expect(service_fn(|req: Request| { if req.head().uri.query() == Some("yes=") { Ok(req) } else { @@ -190,7 +190,7 @@ fn test_expect_continue() { fn test_expect_continue_h1() { let srv = TestServer::new(|| { HttpService::build() - .expect(fn_service(|req: Request| { + .expect(service_fn(|req: Request| { sleep(Duration::from_millis(20)).then(move |_| { if req.head().uri.query() == Some("yes=") { Ok(req) @@ -912,7 +912,7 @@ fn test_h1_body_chunked_implicit() { #[test] fn test_h1_response_http_error_handling() { let mut srv = TestServer::new(|| { - HttpService::build().h1(fn_cfg_factory(|_: &ServerConfig| { + HttpService::build().h1(new_service_cfg(|_: &ServerConfig| { Ok::<_, ()>(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( @@ -943,7 +943,7 @@ fn test_h2_response_http_error_handling() { .map_err(|e| println!("Openssl error: {}", e)) .and_then( HttpService::build() - .h2(fn_cfg_factory(|_: &ServerConfig| { + .h2(new_service_cfg(|_: &ServerConfig| { Ok::<_, ()>(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 8e1714e7..5d0b6fad 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = "1.0.0-beta.1" -actix-service = "0.3.6" +actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" httparse = "1.3" diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index e13ff5c6..fe2054c9 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -25,7 +25,7 @@ cookie-session = ["actix-web/secure-cookies"] [dependencies] actix-web = "1.0.0-beta.2" -actix-service = "0.3.4" +actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" futures = "0.1.25" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f061351d..2b51a752 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -40,7 +40,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" -actix-service = "0.3.6" +actix-service = "0.4.0" actix-http = "0.1.4" base64 = "0.10.1" bytes = "0.4" @@ -62,7 +62,7 @@ actix-web = { version = "1.0.0-beta.1", features=["ssl"] } actix-http = { version = "0.1.4", features=["ssl"] } actix-http-test = { version = "0.1.1", features=["ssl"] } actix-utils = "0.3.4" -actix-server = { version = "0.4.3", features=["ssl"] } +actix-server = { version = "0.5.0", features=["ssl"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index c460f135..2bc52a43 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -140,51 +140,46 @@ impl ClientBuilder { #[cfg(test)] mod tests { use super::*; - use crate::test; #[test] fn client_basic_auth() { - test::run_on(|| { - let client = ClientBuilder::new().basic_auth("username", Some("password")); - assert_eq!( - client - .config - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - ); + let client = ClientBuilder::new().basic_auth("username", Some("password")); + assert_eq!( + client + .config + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + ); - let client = ClientBuilder::new().basic_auth("username", None); - assert_eq!( - client - .config - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU=" - ); - }); + let client = ClientBuilder::new().basic_auth("username", None); + assert_eq!( + client + .config + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Basic dXNlcm5hbWU=" + ); } #[test] fn client_bearer_auth() { - test::run_on(|| { - let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n"); - assert_eq!( - client - .config - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Bearer someS3cr3tAutht0k3n" - ); - }) + let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n"); + assert_eq!( + client + .config + .headers + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap(), + "Bearer someS3cr3tAutht0k3n" + ); } } diff --git a/awc/src/response.rs b/awc/src/response.rs index b9b007b8..d186526d 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -347,10 +347,11 @@ where #[cfg(test)] mod tests { use super::*; + use actix_http_test::block_on; use futures::Async; use serde::{Deserialize, Serialize}; - use crate::{http::header, test::block_on, test::TestResponse}; + use crate::{http::header, test::TestResponse}; #[test] fn test_body() { diff --git a/awc/src/test.rs b/awc/src/test.rs index 8df21e8f..f2c513ba 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -6,39 +6,10 @@ use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; use actix_http::http::{HeaderName, HttpTryFrom, StatusCode, Version}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; -#[cfg(test)] -use futures::Future; use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::ClientResponse; -#[cfg(test)] -thread_local! { - static RT: std::cell::RefCell = { - std::cell::RefCell::new(actix_rt::Runtime::new().unwrap()) - }; -} - -#[cfg(test)] -pub(crate) fn run_on(f: F) -> R -where - F: Fn() -> R, -{ - RT.with(move |rt| { - rt.borrow_mut() - .block_on(futures::future::lazy(|| Ok::<_, ()>(f()))) - }) - .unwrap() -} - -#[cfg(test)] -pub(crate) fn block_on(f: F) -> Result -where - F: Future, -{ - RT.with(move |rt| rt.borrow_mut().block_on(f)) -} - /// Test `ClientResponse` builder pub struct TestResponse { head: ResponseHead, diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 94a90535..d3e06d3d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -318,7 +318,9 @@ impl WebsocketsRequest { } } else { log::trace!("Invalid connection header: {:?}", conn); - return Err(WsClientError::InvalidConnectionHeader(conn.clone())); + return Err(WsClientError::InvalidConnectionHeader( + conn.clone(), + )); } } else { log::trace!("Missing connection header"); @@ -462,29 +464,28 @@ mod tests { #[test] fn basics() { - actix_http_test::run_on(|| { - let req = Client::new() - .ws("http://localhost/") - .origin("test-origin") - .max_frame_size(100) - .server_mode() - .protocols(&["v1", "v2"]) - .set_header_if_none(header::CONTENT_TYPE, "json") - .set_header_if_none(header::CONTENT_TYPE, "text") - .cookie(Cookie::build("cookie1", "value1").finish()); - assert_eq!( - req.origin.as_ref().unwrap().to_str().unwrap(), - "test-origin" - ); - assert_eq!(req.max_size, 100); - assert_eq!(req.server_mode, true); - assert_eq!(req.protocols, Some("v1,v2".to_string())); - assert_eq!( - req.head.headers.get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("json") - ); - let _ = req.connect(); - }); + let req = Client::new() + .ws("http://localhost/") + .origin("test-origin") + .max_frame_size(100) + .server_mode() + .protocols(&["v1", "v2"]) + .set_header_if_none(header::CONTENT_TYPE, "json") + .set_header_if_none(header::CONTENT_TYPE, "text") + .cookie(Cookie::build("cookie1", "value1").finish()); + assert_eq!( + req.origin.as_ref().unwrap().to_str().unwrap(), + "test-origin" + ); + assert_eq!(req.max_size, 100); + assert_eq!(req.server_mode, true); + assert_eq!(req.protocols, Some("v1,v2".to_string())); + assert_eq!( + req.head.headers.get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("json") + ); + + let _ = actix_http_test::block_fn(move || req.connect()); assert!(Client::new().ws("/").connect().poll().is_err()); assert!(Client::new().ws("http:///test").connect().poll().is_err()); diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 94684dd9..698481e3 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -15,7 +15,7 @@ use rand::Rng; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; use actix_http_test::TestServer; -use actix_service::{fn_service, NewService}; +use actix_service::{service_fn, NewService}; use actix_web::http::{Cookie, Version}; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; @@ -182,7 +182,7 @@ fn test_connection_reuse() { let mut srv = TestServer::new(move || { let num2 = num2.clone(); - fn_service(move |io| { + service_fn(move |io| { num2.fetch_add(1, Ordering::Relaxed); Ok(io) }) @@ -216,7 +216,7 @@ fn test_connection_reuse_h2() { let mut srv = TestServer::new(move || { let num2 = num2.clone(); - fn_service(move |io| { + service_fn(move |io| { num2.fetch_add(1, Ordering::Relaxed); Ok(io) }) @@ -268,7 +268,7 @@ fn test_connection_force_close() { let mut srv = TestServer::new(move || { let num2 = num2.clone(); - fn_service(move |io| { + service_fn(move |io| { num2.fetch_add(1, Ordering::Relaxed); Ok(io) }) @@ -300,7 +300,7 @@ fn test_connection_server_close() { let mut srv = TestServer::new(move || { let num2 = num2.clone(); - fn_service(move |io| { + service_fn(move |io| { num2.fetch_add(1, Ordering::Relaxed); Ok(io) }) @@ -335,7 +335,7 @@ fn test_connection_wait_queue() { let mut srv = TestServer::new(move || { let num2 = num2.clone(); - fn_service(move |io| { + service_fn(move |io| { num2.fetch_add(1, Ordering::Relaxed); Ok(io) }) @@ -380,7 +380,7 @@ fn test_connection_wait_queue_force_close() { let mut srv = TestServer::new(move || { let num2 = num2.clone(); - fn_service(move |io| { + service_fn(move |io| { num2.fetch_add(1, Ordering::Relaxed); Ok(io) }) diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 4fc9f4bd..5abf9635 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -1,17 +1,11 @@ use std::io; use actix_codec::Framed; +use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; use actix_http_test::TestServer; -use actix_server::Io; -use actix_service::{fn_service, NewService}; -use actix_utils::framed::IntoFramed; -use actix_utils::stream::TakeItem; use bytes::{Bytes, BytesMut}; -use futures::future::{ok, Either}; +use futures::future::ok; use futures::{Future, Sink, Stream}; -use tokio_tcp::TcpStream; - -use actix_http::{body::BodySize, h1, ws, Request, ResponseError, ServiceConfig}; fn ws_service(req: ws::Frame) -> impl Future { match req { @@ -37,52 +31,20 @@ fn ws_service(req: ws::Frame) -> impl Future| Ok(io.into_parts().0)) - .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default()))) - .and_then(TakeItem::new().map_err(|_| ())) - .and_then( - |(req, framed): (Option>, Framed<_, _>)| { - // validate request - if let Some(h1::Message::Item(req)) = req { - match ws::verify_handshake(req.head()) { - Err(e) => { - // validation failed - let res = e.error_response(); - Either::A( - framed - .send(h1::Message::Item(( - res.drop_body(), - BodySize::Empty, - ))) - .map_err(|_| ()) - .map(|_| ()), - ) - } - Ok(_) => { - let res = ws::handshake_response(req.head()).finish(); - Either::B( - // send handshake response - framed - .send(h1::Message::Item(( - res.drop_body(), - BodySize::None, - ))) - .map_err(|_| ()) - .and_then(|framed| { - // start websocket service - let framed = - framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_service) - .map_err(|_| ()) - }), - ) - } - } - } else { - panic!() - } - }, - ) + HttpService::build() + .upgrade(|(req, framed): (Request, Framed<_, _>)| { + let res = ws::handshake_response(req.head()).finish(); + // send handshake response + framed + .send(h1::Message::Item((res.drop_body(), BodySize::None))) + .map_err(|e: io::Error| e.into()) + .and_then(|framed| { + // start websocket service + let framed = framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_service) + }) + }) + .finish(|_| ok::<_, Error>(Response::NotFound())) }); // client service diff --git a/src/app.rs b/src/app.rs index eb14d46f..cc04630a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,6 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody}; -use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ apply_transform, IntoNewService, IntoTransform, NewService, Transform, @@ -59,6 +58,7 @@ impl App where B: MessageBody, T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -234,6 +234,7 @@ where where F: IntoNewService, U: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -319,6 +320,7 @@ where mw: F, ) -> App< impl NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -384,6 +386,7 @@ where mw: F, ) -> App< impl NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -400,10 +403,11 @@ where } } -impl IntoNewService, ServerConfig> for App +impl IntoNewService> for App where B: MessageBody, T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, diff --git a/src/app_service.rs b/src/app_service.rs index e2f91842..f3438984 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -6,7 +6,7 @@ use actix_http::{Extensions, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService, BoxedService}; -use actix_service::{fn_service, NewService, Service}; +use actix_service::{service_fn, NewService, Service}; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll}; @@ -31,6 +31,7 @@ type BoxedResponse = Either< pub struct AppInit where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -46,15 +47,17 @@ where pub(crate) external: RefCell>, } -impl NewService for AppInit +impl NewService for AppInit where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, { + type Config = ServerConfig; type Request = Request; type Response = ServiceResponse; type Error = T::Error; @@ -65,7 +68,7 @@ where fn new_service(&self, cfg: &ServerConfig) -> Self::Future { // update resource default service let default = self.default.clone().unwrap_or_else(|| { - Rc::new(boxed::new_service(fn_service(|req: ServiceRequest| { + Rc::new(boxed::new_service(service_fn(|req: ServiceRequest| { Ok(req.into_response(Response::NotFound().finish())) }))) }); @@ -148,6 +151,7 @@ where impl Future for AppInitResult where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -233,6 +237,7 @@ pub struct AppRoutingFactory { } impl NewService for AppRoutingFactory { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; @@ -391,6 +396,7 @@ impl AppEntry { } impl NewService for AppEntry { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; diff --git a/src/config.rs b/src/config.rs index e4e390b1..62fd01be 100644 --- a/src/config.rs +++ b/src/config.rs @@ -107,6 +107,7 @@ impl AppService { ) where F: IntoNewService, S: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, diff --git a/src/handler.rs b/src/handler.rs index 245aba9d..b53d1638 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,7 +1,8 @@ +use std::convert::Infallible; use std::marker::PhantomData; use actix_http::{Error, Payload, Response}; -use actix_service::{NewService, Service, Void}; +use actix_service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; @@ -71,7 +72,7 @@ where { type Request = (T, HttpRequest); type Response = ServiceResponse; - type Error = Void; + type Error = Infallible; type Future = HandlerServiceResponse<::Future>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -98,7 +99,7 @@ where T::Error: Into, { type Item = ServiceResponse; - type Error = Void; + type Error = Infallible; fn poll(&mut self) -> Poll { match self.fut.poll() { @@ -191,7 +192,7 @@ where { type Request = (T, HttpRequest); type Response = ServiceResponse; - type Error = Void; + type Error = Infallible; type Future = AsyncHandlerServiceResponse; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -225,7 +226,7 @@ where T::Error: Into, { type Item = ServiceResponse; - type Error = Void; + type Error = Infallible; fn poll(&mut self) -> Poll { if let Some(ref mut fut) = self.fut2 { @@ -280,9 +281,13 @@ impl Extract { impl NewService for Extract where - S: Service - + Clone, + S: Service< + Request = (T, HttpRequest), + Response = ServiceResponse, + Error = Infallible, + > + Clone, { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = (Error, ServiceRequest); @@ -305,8 +310,11 @@ pub struct ExtractService { impl Service for ExtractService where - S: Service - + Clone, + S: Service< + Request = (T, HttpRequest), + Response = ServiceResponse, + Error = Infallible, + > + Clone, { type Request = ServiceRequest; type Response = ServiceResponse; @@ -339,7 +347,11 @@ pub struct ExtractResponse { impl Future for ExtractResponse where - S: Service, + S: Service< + Request = (T, HttpRequest), + Response = ServiceResponse, + Error = Infallible, + >, { type Item = ServiceResponse; type Error = (Error, ServiceRequest); diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index bb4fd567..f731f49b 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -805,14 +805,15 @@ where #[cfg(test)] mod tests { - use actix_service::{FnService, Transform}; + use actix_service::{IntoService, Transform}; use super::*; use crate::test::{self, block_on, TestRequest}; impl Cors { - fn finish(self, srv: S) -> CorsMiddleware + fn finish(self, srv: F) -> CorsMiddleware where + F: IntoService, S: Service< Request = ServiceRequest, Response = ServiceResponse, @@ -822,7 +823,8 @@ mod tests { B: 'static, { block_on( - IntoTransform::::into_transform(self).new_transform(srv), + IntoTransform::::into_transform(self) + .new_transform(srv.into_service()), ) .unwrap() } @@ -1063,11 +1065,11 @@ mod tests { .allowed_headers(exposed_headers.clone()) .expose_headers(exposed_headers.clone()) .allowed_header(header::CONTENT_TYPE) - .finish(FnService::new(move |req: ServiceRequest| { + .finish(|req: ServiceRequest| { req.into_response( HttpResponse::Ok().header(header::VARY, "Accept").finish(), ) - })); + }); let req = TestRequest::with_header("Origin", "https://www.example.com") .method(Method::OPTIONS) .to_srv_request(); diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 8b92b530..bddcdd55 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -150,7 +150,7 @@ where #[cfg(test)] mod tests { - use actix_service::FnService; + use actix_service::IntoService; use super::*; use crate::dev::ServiceRequest; @@ -172,13 +172,13 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); let req = TestRequest::default().to_srv_request(); - let srv = FnService::new(|req: ServiceRequest| { + let srv = |req: ServiceRequest| { req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) - }); + }; let mut mw = block_on( DefaultHeaders::new() .header(CONTENT_TYPE, "0001") - .new_transform(srv), + .new_transform(srv.into_service()), ) .unwrap(); let resp = block_on(mw.call(req)).unwrap(); @@ -187,11 +187,13 @@ mod tests { #[test] fn test_content_type() { - let srv = FnService::new(|req: ServiceRequest| { - req.into_response(HttpResponse::Ok().finish()) - }); - let mut mw = - block_on(DefaultHeaders::new().content_type().new_transform(srv)).unwrap(); + let srv = |req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()); + let mut mw = block_on( + DefaultHeaders::new() + .content_type() + .new_transform(srv.into_service()), + ) + .unwrap(); let req = TestRequest::default().to_srv_request(); let resp = block_on(mw.call(req)).unwrap(); diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index acc6783f..ac166e0e 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -142,7 +142,7 @@ where #[cfg(test)] mod tests { - use actix_service::FnService; + use actix_service::IntoService; use futures::future::ok; use super::*; @@ -159,14 +159,14 @@ mod tests { #[test] fn test_handler() { - let srv = FnService::new(|req: ServiceRequest| { + let srv = |req: ServiceRequest| { req.into_response(HttpResponse::InternalServerError().finish()) - }); + }; let mut mw = test::block_on( ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) - .new_transform(srv), + .new_transform(srv.into_service()), ) .unwrap(); @@ -185,14 +185,14 @@ mod tests { #[test] fn test_handler_async() { - let srv = FnService::new(|req: ServiceRequest| { + let srv = |req: ServiceRequest| { req.into_response(HttpResponse::InternalServerError().finish()) - }); + }; let mut mw = test::block_on( ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) - .new_transform(srv), + .new_transform(srv.into_service()), ) .unwrap(); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 3e3fb05f..5d0b615e 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -457,7 +457,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> { #[cfg(test)] mod tests { - use actix_service::{FnService, Service, Transform}; + use actix_service::{IntoService, Service, Transform}; use super::*; use crate::http::{header, StatusCode}; @@ -465,16 +465,16 @@ mod tests { #[test] fn test_logger() { - let srv = FnService::new(|req: ServiceRequest| { + let srv = |req: ServiceRequest| { req.into_response( HttpResponse::build(StatusCode::OK) .header("X-Test", "ttt") .finish(), ) - }); + }; let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - let mut srv = block_on(logger.new_transform(srv)).unwrap(); + let mut srv = block_on(logger.new_transform(srv.into_service())).unwrap(); let req = TestRequest::with_header( header::USER_AGENT, diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index a86e2b9c..427f954f 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -100,7 +100,7 @@ where #[cfg(test)] mod tests { - use actix_service::FnService; + use actix_service::IntoService; use super::*; use crate::dev::ServiceRequest; @@ -122,12 +122,13 @@ mod tests { #[test] fn test_in_place_normalization() { - let srv = FnService::new(|req: ServiceRequest| { + let srv = |req: ServiceRequest| { assert_eq!("/v1/something/", req.path()); req.into_response(HttpResponse::Ok().finish()) - }); + }; - let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap(); + let mut normalize = + block_on(NormalizePath.new_transform(srv.into_service())).unwrap(); let req = TestRequest::with_uri("/v1//something////").to_srv_request(); let res = block_on(normalize.call(req)).unwrap(); @@ -138,12 +139,13 @@ mod tests { fn should_normalize_nothing() { const URI: &str = "/v1/something/"; - let srv = FnService::new(|req: ServiceRequest| { + let srv = |req: ServiceRequest| { assert_eq!(URI, req.path()); req.into_response(HttpResponse::Ok().finish()) - }); + }; - let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap(); + let mut normalize = + block_on(NormalizePath.new_transform(srv.into_service())).unwrap(); let req = TestRequest::with_uri(URI).to_srv_request(); let res = block_on(normalize.call(req)).unwrap(); diff --git a/src/resource.rs b/src/resource.rs index 8bafc0fc..2040a1bb 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -75,6 +75,7 @@ impl Resource { impl Resource where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -274,6 +275,7 @@ where mw: F, ) -> Resource< impl NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -340,6 +342,7 @@ where mw: F, ) -> Resource< impl NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -360,6 +363,7 @@ where where F: IntoNewService, U: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -380,6 +384,7 @@ where impl HttpServiceFactory for Resource where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -411,6 +416,7 @@ where impl IntoNewService for Resource where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -435,6 +441,7 @@ pub struct ResourceFactory { } impl NewService for ResourceFactory { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; @@ -575,6 +582,7 @@ impl ResourceEndpoint { } impl NewService for ResourceEndpoint { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; diff --git a/src/route.rs b/src/route.rs index 62f030c7..660b8200 100644 --- a/src/route.rs +++ b/src/route.rs @@ -26,6 +26,7 @@ type BoxedRouteService = Box< type BoxedRouteNewService = Box< NewService< + Config = (), Request = Req, Response = Res, Error = Error, @@ -61,6 +62,7 @@ impl Route { } impl NewService for Route { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; @@ -283,6 +285,7 @@ where impl RouteNewService where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = (Error, ServiceRequest), @@ -299,6 +302,7 @@ where impl NewService for RouteNewService where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = (Error, ServiceRequest), @@ -307,6 +311,7 @@ where T::Service: 'static, ::Future: 'static, { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; diff --git a/src/scope.rs b/src/scope.rs index ada53334..59d3c673 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -6,7 +6,7 @@ use actix_http::Response; use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform, + apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform, }; use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, IntoFuture, Poll}; @@ -85,6 +85,7 @@ impl Scope { impl Scope where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -188,6 +189,7 @@ where where F: IntoNewService, U: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -218,6 +220,7 @@ where mw: F, ) -> Scope< impl NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -234,7 +237,7 @@ where >, F: IntoTransform, { - let endpoint = ApplyTransform::new(mw, self.endpoint); + let endpoint = apply_transform(mw, self.endpoint); Scope { endpoint, rdef: self.rdef, @@ -280,6 +283,7 @@ where mw: F, ) -> Scope< impl NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -297,6 +301,7 @@ where impl HttpServiceFactory for Scope where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -355,6 +360,7 @@ pub struct ScopeFactory { } impl NewService for ScopeFactory { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; @@ -515,6 +521,7 @@ impl ScopeEndpoint { } impl NewService for ScopeEndpoint { + type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; diff --git a/src/server.rs b/src/server.rs index efc70773..3cb13997 100644 --- a/src/server.rs +++ b/src/server.rs @@ -51,8 +51,8 @@ struct Config { pub struct HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -71,8 +71,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -442,8 +442,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoNewService, + S: NewService, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, diff --git a/src/service.rs b/src/service.rs index f35ea89f..eee8b0ad 100644 --- a/src/service.rs +++ b/src/service.rs @@ -442,6 +442,7 @@ impl WebService { where F: IntoNewService, T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, @@ -467,6 +468,7 @@ struct WebServiceImpl { impl HttpServiceFactory for WebServiceImpl where T: NewService< + Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, diff --git a/src/test.rs b/src/test.rs index 66b380e8..3b3aac67 100644 --- a/src/test.rs +++ b/src/test.rs @@ -9,7 +9,7 @@ use actix_http::{cookie::Cookie, Extensions, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_rt::Runtime; use actix_server_config::ServerConfig; -use actix_service::{FnService, IntoNewService, NewService, Service}; +use actix_service::{IntoNewService, IntoService, NewService, Service}; use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Future, IntoFuture}; use futures::Stream; @@ -110,9 +110,10 @@ pub fn default_service( status_code: StatusCode, ) -> impl Service, Error = Error> { - FnService::new(move |req: ServiceRequest| { + (move |req: ServiceRequest| { req.into_response(HttpResponse::build(status_code).finish()) }) + .into_service() } /// This method accepts application builder instance, and constructs @@ -141,9 +142,9 @@ pub fn init_service( app: R, ) -> impl Service, Error = E> where - R: IntoNewService, + R: IntoNewService, S: NewService< - ServerConfig, + Config = ServerConfig, Request = Request, Response = ServiceResponse, Error = E, diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 906c9d38..cd5cc505 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -32,9 +32,9 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.2" actix-rt = "0.2.2" -actix-service = "0.3.6" -actix-server = "0.4.3" -actix-utils = "0.3.5" +actix-service = "0.4.0" +actix-server = "0.5.0" +actix-utils = "0.4.0" awc = "0.1.1" base64 = "0.10" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 42d07549..b8f5934a 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -9,16 +9,30 @@ use actix_server::{Server, StreamServiceFactory}; use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; use bytes::Bytes; use futures::future::lazy; -use futures::{Future, Stream}; +use futures::{Future, IntoFuture, Stream}; use http::Method; use net2::TcpBuilder; thread_local! { - static RT: RefCell = { - RefCell::new(Runtime::new().unwrap()) + static RT: RefCell = { + RefCell::new(Inner(Some(Runtime::new().unwrap()))) }; } +struct Inner(Option); + +impl Inner { + fn get_mut(&mut self) -> &mut Runtime { + self.0.as_mut().unwrap() + } +} + +impl Drop for Inner { + fn drop(&mut self) { + std::mem::forget(self.0.take().unwrap()) + } +} + /// Runs the provided future, blocking the current thread until the future /// completes. /// @@ -31,21 +45,27 @@ thread_local! { /// This function panics on nested call. pub fn block_on(f: F) -> Result where - F: Future, + F: IntoFuture, { - RT.with(move |rt| rt.borrow_mut().block_on(f)) + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f.into_future())) } -/// Runs the provided function, with runtime enabled. +/// Runs the provided function, blocking the current thread until the resul +/// future completes. +/// +/// This function can be used to synchronously block the current thread +/// until the provided `future` has resolved either successfully or with an +/// error. The result of the future is then returned from this function +/// call. /// /// Note that this function is intended to be used only for testing purpose. /// This function panics on nested call. -pub fn run_on(f: F) -> R +pub fn block_fn(f: F) -> Result where - F: Fn() -> R, + F: FnOnce() -> R, + R: IntoFuture, { - RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f())))) - .unwrap() + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(|| f()))) } /// The `TestServer` type. From 45c05978b0c2fdbe1f8b60708f54d472ba25b41b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 09:42:05 -0700 Subject: [PATCH 1371/1635] Allow to set/override app data on scope level --- CHANGES.md | 6 ++++ src/app.rs | 4 +-- src/config.rs | 18 ++++++------ src/data.rs | 7 +---- src/resource.rs | 2 +- src/scope.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 90 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ce7da6d2..aa42900d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [1.0.0-beta.4] - 2019-05-xx + +### Add + +* Allow to set/override app data on scope level + ### Changes * `App::configure` take an `FnOnce` instead of `Fn` diff --git a/src/app.rs b/src/app.rs index cc04630a..1568d5fc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -95,8 +95,8 @@ where /// web::get().to(index))); /// } /// ``` - pub fn data> + 'static>(mut self, data: U) -> Self { - self.data.push(Box::new(data.into())); + pub fn data(mut self, data: U) -> Self { + self.data.push(Box::new(Data::new(data))); self } diff --git a/src/config.rs b/src/config.rs index 62fd01be..bc33da9d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,7 +31,7 @@ pub struct AppService { Option, Option>, )>, - route_data: Rc>>, + service_data: Rc>>, } impl AppService { @@ -39,12 +39,12 @@ impl AppService { pub(crate) fn new( config: AppConfig, default: Rc, - route_data: Rc>>, + service_data: Rc>>, ) -> Self { AppService { config, default, - route_data, + service_data, root: true, services: Vec::new(), } @@ -75,7 +75,7 @@ impl AppService { default: self.default.clone(), services: Vec::new(), root: false, - route_data: self.route_data.clone(), + service_data: self.service_data.clone(), } } @@ -90,11 +90,11 @@ impl AppService { } /// Set global route data - pub fn set_route_data(&self, extensions: &mut Extensions) -> bool { - for f in self.route_data.iter() { + pub fn set_service_data(&self, extensions: &mut Extensions) -> bool { + for f in self.service_data.iter() { f.create(extensions); } - !self.route_data.is_empty() + !self.service_data.is_empty() } /// Register http service @@ -192,8 +192,8 @@ impl ServiceConfig { /// by using `Data` extractor where `T` is data type. /// /// This is same as `App::data()` method. - pub fn data> + 'static>(&mut self, data: S) -> &mut Self { - self.data.push(Box::new(data.into())); + pub fn data(&mut self, data: S) -> &mut Self { + self.data.push(Box::new(Data::new(data))); self } diff --git a/src/data.rs b/src/data.rs index f23bfff7..5cb636f6 100644 --- a/src/data.rs +++ b/src/data.rs @@ -93,12 +93,6 @@ impl Clone for Data { } } -impl From for Data { - fn from(data: T) -> Self { - Data::new(data) - } -} - impl FromRequest for Data { type Config = (); type Error = Error; @@ -135,6 +129,7 @@ impl DataFactory for Data { #[cfg(test)] mod tests { use actix_service::Service; + use std::sync::Mutex; use crate::http::StatusCode; use crate::test::{block_on, init_service, TestRequest}; diff --git a/src/resource.rs b/src/resource.rs index 2040a1bb..7f76e0f5 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -407,7 +407,7 @@ where } // custom app data storage if let Some(ref mut ext) = self.data { - config.set_route_data(ext); + config.set_service_data(ext); } config.register_service(rdef, guards, self, None) } diff --git a/src/scope.rs b/src/scope.rs index 59d3c673..84f34dae 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::fmt; use std::rc::Rc; -use actix_http::Response; +use actix_http::{Extensions, Response}; use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ @@ -11,6 +11,7 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, IntoFuture, Poll}; +use crate::data::Data; use crate::dev::{AppService, HttpServiceFactory}; use crate::error::Error; use crate::guard::Guard; @@ -61,6 +62,7 @@ type BoxedResponse = Either< pub struct Scope { endpoint: T, rdef: String, + data: Option, services: Vec>, guards: Vec>, default: Rc>>>, @@ -74,6 +76,7 @@ impl Scope { Scope { endpoint: ScopeEndpoint::new(fref.clone()), rdef: path.to_string(), + data: None, guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), @@ -117,6 +120,39 @@ where self } + /// Set or override application data. Application data could be accessed + /// by using `Data` extractor where `T` is data type. + /// + /// ```rust + /// use std::cell::Cell; + /// use actix_web::{web, App}; + /// + /// struct MyData { + /// counter: Cell, + /// } + /// + /// fn index(data: web::Data) { + /// data.counter.set(data.counter.get() + 1); + /// } + /// + /// fn main() { + /// let app = App::new().service( + /// web::scope("/app") + /// .data(MyData{ counter: Cell::new(0) }) + /// .service( + /// web::resource("/index.html").route( + /// web::get().to(index))) + /// ); + /// } + /// ``` + pub fn data(mut self, data: U) -> Self { + if self.data.is_none() { + self.data = Some(Extensions::new()); + } + self.data.as_mut().unwrap().insert(Data::new(data)); + self + } + /// Register http service. /// /// This is similar to `App's` service registration. @@ -241,6 +277,7 @@ where Scope { endpoint, rdef: self.rdef, + data: self.data, guards: self.guards, services: self.services, default: self.default, @@ -308,7 +345,7 @@ where InitError = (), > + 'static, { - fn register(self, config: &mut AppService) { + fn register(mut self, config: &mut AppService) { // update default resource if needed if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); @@ -322,8 +359,14 @@ where let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); + // custom app data storage + if let Some(ref mut ext) = self.data { + config.set_service_data(ext); + } + // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { + data: self.data.take().map(|data| Rc::new(data)), default: self.default.clone(), services: Rc::new( cfg.into_services() @@ -355,6 +398,7 @@ where } pub struct ScopeFactory { + data: Option>, services: Rc>)>>, default: Rc>>>, } @@ -388,6 +432,7 @@ impl NewService for ScopeFactory { }) .collect(), default: None, + data: self.data.clone(), default_fut, } } @@ -397,6 +442,7 @@ impl NewService for ScopeFactory { #[doc(hidden)] pub struct ScopeFactoryResponse { fut: Vec, + data: Option>, default: Option, default_fut: Option>>, } @@ -460,6 +506,7 @@ impl Future for ScopeFactoryResponse { router }); Ok(Async::Ready(ScopeService { + data: self.data.clone(), router: router.finish(), default: self.default.take(), _ready: None, @@ -471,6 +518,7 @@ impl Future for ScopeFactoryResponse { } pub struct ScopeService { + data: Option>, router: Router>>, default: Option, _ready: Option<(ServiceRequest, ResourceInfo)>, @@ -499,6 +547,9 @@ impl Service for ScopeService { }); if let Some((srv, _info)) = res { + if let Some(ref data) = self.data { + req.set_data_container(data.clone()); + } Either::A(srv.call(req)) } else if let Some(ref mut default) = self.default { Either::A(default.call(req)) @@ -953,4 +1004,22 @@ mod tests { HeaderValue::from_static("0001") ); } + + #[test] + fn test_override_data() { + let mut srv = init_service(App::new().data(1usize).service( + web::scope("app").data(10usize).route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + )); + + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + } } From 07b9707ca100df7730d20572163a668f833da284 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 09:56:55 -0700 Subject: [PATCH 1372/1635] prepare actix-http release --- Cargo.toml | 2 +- actix-http/CHANGES.md | 6 +++++- actix-http/Cargo.toml | 6 +++--- actix-session/Cargo.toml | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4e2f79e..1bd47e43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ bytes = "0.4" derive_more = "0.14" encoding = "0.2" futures = "0.1" -hashbrown = "0.2.2" +hashbrown = "0.3.0" log = "0.4" mime = "0.3" net2 = "0.2.33" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f1c119d5..a6653f62 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.2.0] - 2019-xx-xx +## [0.2.0] - 2019-05-xx ### Changed @@ -8,6 +8,10 @@ * Expect and upgrade services accept `ServerConfig` config. +### Deleted + +* `OneRequest` service + ## [0.1.5] - 2019-05-04 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index eaac7c09..1f0e1268 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.1.5" +version = "0.2.0" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -59,8 +59,8 @@ copyless = "0.1.2" derive_more = "0.14" either = "1.5.2" encoding = "0.2" -futures = "0.1" -hashbrown = "0.2.2" +futures = "0.1.25" +hashbrown = "0.3.0" h2 = "0.1.16" http = "0.1.17" httparse = "1.3" diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index fe2054c9..4af0fc29 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -29,7 +29,7 @@ actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" futures = "0.1.25" -hashbrown = "0.2.2" +hashbrown = "0.3.0" serde = "1.0" serde_json = "1.0" time = "0.1.42" From beae9ca0f77a7ff381df0cb831b62fcc601127be Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 09:57:16 -0700 Subject: [PATCH 1373/1635] update changes --- actix-http/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a6653f62..61c211b2 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.2.0] - 2019-05-xx +## [0.2.0] - 2019-05-12 ### Changed From 07c9eec8039547e97df91d424e376b299002cbb5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 10:04:38 -0700 Subject: [PATCH 1374/1635] prepare awc release --- Cargo.toml | 4 ++-- awc/CHANGES.md | 6 ++++++ awc/Cargo.toml | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1bd47e43..6c0433d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ actix-utils = "0.4.0" actix-router = "0.1.3" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" -actix-http = { version = "0.1.5", features=["fail"] } +actix-http = { version = "0.2.0", features=["fail"] } actix-server = "0.5.0" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" @@ -99,7 +99,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "^0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.1.5", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.2.0", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.1.1", features=["ssl"] } actix-files = { version = "0.1.0-betsa.1" } rand = "0.6" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 124efc36..f36b0bb3 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,9 +1,15 @@ # Changes +## [0.2.0] - 2019-05-12 + ### Added * Allow to send headers in `Camel-Case` form. +### Changed + +* Upgrade actix-http dependency. + ## [0.1.1] - 2019-04-19 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 2b51a752..b92d62f0 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.1.1" +version = "0.2.0" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -41,7 +41,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.0" -actix-http = "0.1.4" +actix-http = "0.2.0" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -59,9 +59,9 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" actix-web = { version = "1.0.0-beta.1", features=["ssl"] } -actix-http = { version = "0.1.4", features=["ssl"] } +actix-http = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "0.1.1", features=["ssl"] } -actix-utils = "0.3.4" +actix-utils = "0.4.0" actix-server = { version = "0.5.0", features=["ssl"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } From e9cbcbaf03128b9932a58f5ed00441efad669701 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 10:18:02 -0700 Subject: [PATCH 1375/1635] update dependencies --- Cargo.toml | 5 ++--- actix-framed/Cargo.toml | 10 +++++----- actix-framed/changes.md | 5 +++++ actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 4 ++-- actix-web-codegen/Cargo.toml | 4 ++-- awc/Cargo.toml | 2 +- src/data.rs | 1 - test-server/CHANGES.md | 4 ++++ test-server/Cargo.toml | 6 +++--- 11 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c0433d5..08e5d55a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ actix-server = "0.5.0" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" actix = { version = "0.8.1", features=["http"], optional = true } -awc = { version = "0.1.1", optional = true } +awc = { version = "0.2.0", optional = true } bytes = "0.4" derive_more = "0.14" @@ -100,7 +100,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.2.0", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.1.1", features=["ssl"] } +actix-http-test = { version = "0.2.0", features=["ssl"] } actix-files = { version = "0.1.0-betsa.1" } rand = "0.6" env_logger = "0.6" @@ -124,5 +124,4 @@ actix-session = { path = "actix-session" } awc = { path = "awc" } actix-files = { path = "actix-files" } -actix-framed = { path = "actix-framed" } actix-multipart = { path = "actix-multipart" } diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index 98551536..c2ab26fa 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-framed" -version = "0.1.0" +version = "0.2.0" authors = ["Nikolay Kim "] description = "Actix framed app server" readme = "README.md" @@ -25,7 +25,7 @@ actix-service = "0.4.0" actix-utils = "0.4.0" actix-router = "0.1.2" actix-rt = "0.2.2" -actix-http = "0.1.0" +actix-http = "0.2.0" actix-server-config = "0.1.1" bytes = "0.4" @@ -33,6 +33,6 @@ futures = "0.1.25" log = "0.4" [dev-dependencies] -actix-server = { version = "0.4.3", features=["ssl"] } -actix-connect = { version = "0.1.4", features=["ssl"] } -actix-http-test = { version = "0.1.0", features=["ssl"] } +actix-server = { version = "0.5.0", features=["ssl"] } +actix-connect = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.0", features=["ssl"] } diff --git a/actix-framed/changes.md b/actix-framed/changes.md index 9cef3c05..9f16c790 100644 --- a/actix-framed/changes.md +++ b/actix-framed/changes.md @@ -1,5 +1,10 @@ # Changes +## [0.2.0] - 2019-05-12 + +* Update dependencies + + ## [0.1.0] - 2019-04-16 * Update tests diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1f0e1268..fabf7fe9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -99,7 +99,7 @@ chrono = "0.4.6" actix-rt = "0.2.2" actix-server = { version = "0.5.0", features=["ssl"] } actix-connect = { version = "0.2.0", features=["ssl"] } -actix-http-test = { version = "0.1.0", features=["ssl"] } +actix-http-test = { version = "0.2.0", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 5d0b6fad..6e3f6dc7 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -31,4 +31,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "0.2.2" -actix-http = "0.1.1" \ No newline at end of file +actix-http = "0.2.0" \ No newline at end of file diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 22fdf613..f2309d19 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -20,11 +20,11 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0" actix-web = "1.0.0-beta.1" -actix-http = "0.1.1" +actix-http = "0.2.0" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" [dev-dependencies] env_logger = "0.6" -actix-http-test = { version = "0.1.0", features=["ssl"] } +actix-http-test = { version = "0.2.0", features=["ssl"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 4108d879..69612183 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -17,6 +17,6 @@ syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] actix-web = { version = "1.0.0-alpha.6" } -actix-http = { version = "0.1.1", features=["ssl"] } -actix-http-test = { version = "0.1.0", features=["ssl"] } +actix-http = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.0", features=["ssl"] } futures = { version = "0.1" } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b92d62f0..20cee12e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,7 +60,7 @@ openssl = { version="0.10", optional = true } actix-rt = "0.2.2" actix-web = { version = "1.0.0-beta.1", features=["ssl"] } actix-http = { version = "0.2.0", features=["ssl"] } -actix-http-test = { version = "0.1.1", features=["ssl"] } +actix-http-test = { version = "0.2.0", features=["ssl"] } actix-utils = "0.4.0" actix-server = { version = "0.5.0", features=["ssl"] } brotli2 = { version="0.3.2" } diff --git a/src/data.rs b/src/data.rs index 5cb636f6..1328c4ef 100644 --- a/src/data.rs +++ b/src/data.rs @@ -129,7 +129,6 @@ impl DataFactory for Data { #[cfg(test)] mod tests { use actix_service::Service; - use std::sync::Mutex; use crate::http::StatusCode; use crate::test::{block_on, init_service, TestRequest}; diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 700b3aa1..8704a64c 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.2.0] - 2019-05-12 + +* Update awc and actix-http deps + ## [0.1.1] - 2019-04-24 * Always make new connection for http client diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index cd5cc505..13817d50 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.1.1" +version = "0.2.0" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.2" actix-service = "0.4.0" actix-server = "0.5.0" actix-utils = "0.4.0" -awc = "0.1.1" +awc = "0.2.0" base64 = "0.10" bytes = "0.4" @@ -56,4 +56,4 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-web = "1.0.0-beta.1" -actix-http = "0.1.2" +actix-http = "0.2.0" From 1ca58e876b16db57757544af5f4b9fc209959da8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 10:49:21 -0700 Subject: [PATCH 1376/1635] prepare beta4 release --- CHANGES.md | 4 +++- Cargo.toml | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index aa42900d..dedf0758 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.0-beta.4] - 2019-05-xx +## [1.0.0-beta.4] - 2019-05-12 ### Add @@ -10,6 +10,8 @@ * `App::configure` take an `FnOnce` instead of `Fn` +* Upgrade actix-net crates + ## [1.0.0-beta.3] - 2019-05-04 diff --git a/Cargo.toml b/Cargo.toml index 08e5d55a..23b38827 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-beta.3" +version = "1.0.0-beta.4" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -121,7 +121,6 @@ actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } actix-web-actors = { path = "actix-web-actors" } actix-session = { path = "actix-session" } -awc = { path = "awc" } - actix-files = { path = "actix-files" } actix-multipart = { path = "actix-multipart" } +awc = { path = "awc" } From 3bb081852ce9deae03b0ce62f056841a1cd83bbb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 10:53:21 -0700 Subject: [PATCH 1377/1635] prep actix-session release --- actix-session/CHANGES.md | 4 ++++ actix-session/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 8e06a562..5f2bdadd 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-beta.4] - 2019-05-12 + +* Use actix-web 1.0.0-beta.4 + ## [0.1.0-beta.2] - 2019-04-28 * Add helper trait `UserSession` which allows to get session for ServiceRequest and HttpRequest diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 4af0fc29..f74d23f0 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-beta.2" +version = "0.1.0-beta.4" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,7 +24,7 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-beta.2" +actix-web = "1.0.0-beta.4" actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" From 36d017dcc6b27c1442a2d013b588d3119f692957 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 11:41:43 -0700 Subject: [PATCH 1378/1635] update deps --- Cargo.toml | 2 +- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 6 +++--- actix-web-actors/Cargo.toml | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23b38827..513c9add 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ rustls = { version = "^0.15", optional = true } [dev-dependencies] actix-http = { version = "0.2.0", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } -actix-files = { version = "0.1.0-betsa.1" } +actix-files = { version = "0.1.0-beta.4" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 05a5e580..6b4ab57b 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0-beta.4] - 2019-05-12 + +* Update actix-web to beta.4 + ## [0.1.0-beta.1] - 2019-04-20 * Update actix-web to beta.1 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index a0cbba36..c75b3515 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-betsa.1" +version = "0.1.0-beta.4" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-beta.1" +actix-web = "1.0.0-beta.4" actix-service = "0.4.0" bitflags = "1" bytes = "0.4" @@ -31,4 +31,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0-beta.1", features=["ssl"] } +actix-web = { version = "1.0.0-beta.4", features=["ssl"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index f2309d19..6d87b5ef 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.0-alpha.3" +version = "1.0.0-beta.4" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0" -actix-web = "1.0.0-beta.1" +actix-web = "1.0.0-beta.4" actix-http = "0.2.0" actix-codec = "0.1.2" bytes = "0.4" From 2350a2dc68ad2093fa4b95ff26a47bab86d54868 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 11:43:05 -0700 Subject: [PATCH 1379/1635] Handle cancellation of uploads #834 #736 --- actix-multipart/CHANGES.md | 6 ++++++ actix-multipart/Cargo.toml | 4 ++-- actix-multipart/src/error.rs | 3 +++ actix-multipart/src/server.rs | 17 ++++++++++++++--- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 9f8fa052..5ee1d620 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.1.0-beta.4] - 2019-05-12 + +* Handle cancellation of uploads #834 #736 + +* Upgrade to actix-web 1.0.0-beta.4 + ## [0.1.0-beta.1] - 2019-04-21 * Do not support nested multipart diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 6e3f6dc7..e88c642b 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.0-beta.1" +version = "0.1.0-beta.4" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-beta.1" +actix-web = "1.0.0-beta.4" actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs index 99558585..32c740a1 100644 --- a/actix-multipart/src/error.rs +++ b/actix-multipart/src/error.rs @@ -28,6 +28,9 @@ pub enum MultipartError { /// Payload error #[display(fmt = "{}", _0)] Payload(PayloadError), + /// Not consumed + #[display(fmt = "Multipart stream is not consumed")] + NotConsumed, } /// Return `BadRequest` for `MultipartError` diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 82b0b5ac..7d746ea2 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1,5 +1,5 @@ //! Multipart payload support -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{Cell, RefCell, UnsafeCell}; use std::marker::PhantomData; use std::rc::Rc; use std::{cmp, fmt}; @@ -116,6 +116,8 @@ impl Stream for Multipart { payload.poll_stream()?; } inner.poll(&self.safety) + } else if !self.safety.is_clean() { + Err(MultipartError::NotConsumed) } else { Ok(Async::NotReady) } @@ -415,6 +417,8 @@ impl Stream for Field { } inner.poll(&self.safety) + } else if !self.safety.is_clean() { + return Err(MultipartError::NotConsumed); } else { Ok(Async::NotReady) } @@ -655,6 +659,7 @@ struct Safety { task: Option, level: usize, payload: Rc>, + clean: Rc>, } impl Safety { @@ -663,12 +668,17 @@ impl Safety { Safety { task: None, level: Rc::strong_count(&payload), + clean: Rc::new(Cell::new(true)), payload, } } fn current(&self) -> bool { - Rc::strong_count(&self.payload) == self.level + Rc::strong_count(&self.payload) == self.level && self.clean.get() + } + + fn is_clean(&self) -> bool { + self.clean.get() } } @@ -678,6 +688,7 @@ impl Clone for Safety { Safety { task: Some(current_task()), level: Rc::strong_count(&payload), + clean: self.clean.clone(), payload, } } @@ -687,7 +698,7 @@ impl Drop for Safety { fn drop(&mut self) { // parent task is dead if Rc::strong_count(&self.payload) != self.level { - panic!("Safety get dropped but it is not from top-most task"); + self.clean.set(true); } if let Some(task) = self.task.take() { task.notify() From 86b569e3206a39c29c15d10906669d39a1f9fb79 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 11:56:01 -0700 Subject: [PATCH 1380/1635] version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 513c9add..9c670ab1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-beta.4" +version = "1.0.0-beta.5" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 5a90e33bcccd893de499ff73b4d53c8273570555 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 12:01:24 -0700 Subject: [PATCH 1381/1635] update deps --- actix-files/Cargo.toml | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-session/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- test-server/Cargo.toml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c75b3515..6b065758 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-beta.4" +actix-web = "1.0.0-beta.5" actix-service = "0.4.0" bitflags = "1" bytes = "0.4" @@ -31,4 +31,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0-beta.4", features=["ssl"] } +actix-web = { version = "1.0.0-beta.5", features=["ssl"] } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e88c642b..7d19ef9f 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -18,7 +18,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-beta.4" +actix-web = "1.0.0-beta.5" actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index f74d23f0..5bebb9a1 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -24,7 +24,7 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-beta.4" +actix-web = "1.0.0-beta.5" actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 6d87b5ef..fe24d4c3 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix = "0.8.0" -actix-web = "1.0.0-beta.4" +actix-web = "1.0.0-beta.5" actix-http = "0.2.0" actix-codec = "0.1.2" bytes = "0.4" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 69612183..7ca7912f 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -16,7 +16,7 @@ quote = "0.6" syn = { version = "0.15", features = ["full", "parsing"] } [dev-dependencies] -actix-web = { version = "1.0.0-alpha.6" } +actix-web = { version = "1.0.0-beta.5" } actix-http = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } futures = { version = "0.1" } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 20cee12e..2112185c 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -58,7 +58,7 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-beta.1", features=["ssl"] } +actix-web = { version = "1.0.0-beta.4", features=["ssl"] } actix-http = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } actix-utils = "0.4.0" diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 13817d50..8567b745 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -55,5 +55,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-beta.1" +actix-web = "1.0.0-beta.4" actix-http = "0.2.0" From 6c3d8b87384c1e375c4b1eb5dc4a27d839942a9d Mon Sep 17 00:00:00 2001 From: Davide Di Carlo Date: Mon, 13 May 2019 05:04:08 +0200 Subject: [PATCH 1382/1635] Make JsonConfig send (#830) * replace Rc with Arc * add Send trait requirement for Fn in JsonConfig error handler * add Sync trait requirement for Fn in JsonConfig error handler * use associated type inside JsonConfig * fix lint: members in the impl has the same order in the trait * Update CHANGES.md --- CHANGES.md | 6 ++++-- src/types/json.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dedf0758..67a97f14 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +### Changes + +* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. + ## [1.0.0-beta.4] - 2019-05-12 ### Add @@ -9,10 +13,8 @@ ### Changes * `App::configure` take an `FnOnce` instead of `Fn` - * Upgrade actix-net crates - ## [1.0.0-beta.3] - 2019-05-04 ### Added diff --git a/src/types/json.rs b/src/types/json.rs index 73614d87..4e827942 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -1,6 +1,6 @@ //! Json extractor/responder -use std::rc::Rc; +use std::sync::Arc; use std::{fmt, ops}; use bytes::BytesMut; @@ -168,15 +168,15 @@ impl FromRequest for Json where T: DeserializeOwned + 'static, { - type Config = JsonConfig; type Error = Error; type Future = Box>; + type Config = JsonConfig; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .app_data::() + .app_data::() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((32768, None)); @@ -236,7 +236,7 @@ where #[derive(Clone)] pub struct JsonConfig { limit: usize, - ehandler: Option Error>>, + ehandler: Option Error + Send + Sync>>, } impl JsonConfig { @@ -249,9 +249,9 @@ impl JsonConfig { /// Set custom error handler pub fn error_handler(mut self, f: F) -> Self where - F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, + F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, { - self.ehandler = Some(Rc::new(f)); + self.ehandler = Some(Arc::new(f)); self } } From f8af3b86e51796727efd28ad51c3f1f6dc9a51bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 14 May 2019 08:48:11 -0700 Subject: [PATCH 1383/1635] export set_date --- actix-http/src/config.rs | 3 ++- actix-http/src/h1/codec.rs | 7 ++++++- actix-http/src/h1/dispatcher.rs | 9 +++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index f7d7f5f9..aba50a81 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -158,7 +158,8 @@ impl ServiceConfig { self.0.timer.now() } - pub(crate) fn set_date(&self, dst: &mut BytesMut) { + #[doc(hidden)] + pub fn set_date(&self, dst: &mut BytesMut) { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(b"date: "); buf[6..35].copy_from_slice(&self.0.timer.date().bytes); diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 1e1e1602..22c7ed23 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -31,7 +31,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; /// HTTP/1 Codec pub struct Codec { - pub(crate) config: ServiceConfig, + config: ServiceConfig, decoder: decoder::MessageDecoder, payload: Option, version: Version, @@ -104,6 +104,11 @@ impl Codec { MessageType::Payload } } + + #[inline] + pub fn config(&self) -> &ServiceConfig { + &self.config + } } impl Decoder for Codec { diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 75846683..ec717ae0 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -567,7 +567,7 @@ where } if updated && self.ka_timer.is_some() { - if let Some(expire) = self.codec.config.keep_alive_expire() { + if let Some(expire) = self.codec.config().keep_alive_expire() { self.ka_expire = expire; } } @@ -579,7 +579,7 @@ where if self.ka_timer.is_none() { // shutdown timeout if self.flags.contains(Flags::SHUTDOWN) { - if let Some(interval) = self.codec.config.client_disconnect_timer() { + if let Some(interval) = self.codec.config().client_disconnect_timer() { self.ka_timer = Some(Delay::new(interval)); } else { self.flags.insert(Flags::READ_DISCONNECT); @@ -607,7 +607,7 @@ where // start shutdown timer if let Some(deadline) = - self.codec.config.client_disconnect_timer() + self.codec.config().client_disconnect_timer() { if let Some(timer) = self.ka_timer.as_mut() { timer.reset(deadline); @@ -632,7 +632,8 @@ where self.flags.insert(Flags::STARTED | Flags::SHUTDOWN); self.state = State::None; } - } else if let Some(deadline) = self.codec.config.keep_alive_expire() + } else if let Some(deadline) = + self.codec.config().keep_alive_expire() { if let Some(timer) = self.ka_timer.as_mut() { timer.reset(deadline); From bba90d7f2276d0e636ccb04fd7a11ffde30dbe76 Mon Sep 17 00:00:00 2001 From: Davide Di Carlo Date: Tue, 14 May 2019 22:54:30 +0200 Subject: [PATCH 1384/1635] Query config (#839) * add QueryConfig * expose QueryConfig in web module * fmt * use associated type for QueryConfig * update CHANGES.md --- CHANGES.md | 4 ++ src/error.rs | 24 ++++++++++++ src/types/mod.rs | 2 +- src/types/query.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 120 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 67a97f14..2e4d23ba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +### Add + +* `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. + ### Changes * `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. diff --git a/src/error.rs b/src/error.rs index e9e225f2..a3062b58 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ use url::ParseError as UrlParseError; use crate::http::StatusCode; use crate::HttpResponse; +use serde_urlencoded::de; /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, From)] @@ -91,6 +92,23 @@ impl ResponseError for JsonPayloadError { } } +/// A set of errors that can occur during parsing query strings +#[derive(Debug, Display, From)] +pub enum QueryPayloadError { + /// Deserialize error + #[display(fmt = "Query deserialize error: {}", _0)] + Deserialize(de::Error), +} + +/// Return `BadRequest` for `QueryPayloadError` +impl ResponseError for QueryPayloadError { + fn error_response(&self) -> HttpResponse { + match *self { + QueryPayloadError::Deserialize(_) => HttpResponse::new(StatusCode::BAD_REQUEST), + } + } +} + /// Error type returned when reading body as lines. #[derive(From, Display, Debug)] pub enum ReadlinesError { @@ -143,6 +161,12 @@ mod tests { assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } + #[test] + fn test_query_payload_error() { + let resp: HttpResponse = QueryPayloadError::Deserialize(serde_urlencoded::from_str::("bad query").unwrap_err()).error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + #[test] fn test_readlines_error() { let resp: HttpResponse = ReadlinesError::LimitOverflow.error_response(); diff --git a/src/types/mod.rs b/src/types/mod.rs index 30ee7309..d01d597b 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -11,4 +11,4 @@ pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; pub use self::path::Path; pub use self::payload::{Payload, PayloadConfig}; -pub use self::query::Query; +pub use self::query::{Query, QueryConfig}; diff --git a/src/types/query.rs b/src/types/query.rs index f9f545d6..24225dad 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -1,5 +1,6 @@ //! Query extractor +use std::sync::Arc; use std::{fmt, ops}; use actix_http::error::Error; @@ -9,6 +10,7 @@ use serde_urlencoded; use crate::dev::Payload; use crate::extract::FromRequest; use crate::request::HttpRequest; +use crate::error::QueryPayloadError; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from from the request's query. @@ -115,32 +117,103 @@ impl FromRequest for Query where T: de::DeserializeOwned, { - type Config = (); type Error = Error; type Future = Result; + type Config = QueryConfig; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let error_handler = req + .app_data::() + .map(|c| c.ehandler.clone()) + .unwrap_or(None); + serde_urlencoded::from_str::(req.query_string()) .map(|val| Ok(Query(val))) - .unwrap_or_else(|e| { + .unwrap_or_else(move |e| { + let e = QueryPayloadError::Deserialize(e); + log::debug!( "Failed during Query extractor deserialization. \ Request path: {:?}", req.path() ); - Err(e.into()) + + let e = if let Some(error_handler) = error_handler { + (error_handler)(e, req) + } else { + e.into() + }; + + Err(e) }) } } +/// Query extractor configuration +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{error, web, App, FromRequest, HttpResponse}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's querystring +/// fn index(info: web::Query) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").data( +/// // change query extractor configuration +/// web::Query::::configure(|cfg| { +/// cfg.error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// }) +/// })) +/// .route(web::post().to(index)) +/// ); +/// } +/// ``` +#[derive(Clone)] +pub struct QueryConfig { + ehandler: Option Error + Send + Sync>>, +} + +impl QueryConfig { + /// Set custom error handler + pub fn error_handler(mut self, f: F) -> Self + where + F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, + { + self.ehandler = Some(Arc::new(f)); + self + } +} + +impl Default for QueryConfig { + fn default() -> Self { + QueryConfig { + ehandler: None, + } + } +} + #[cfg(test)] mod tests { use derive_more::Display; use serde_derive::Deserialize; + use actix_http::http::StatusCode; use super::*; use crate::test::TestRequest; + use crate::error::InternalError; + use crate::HttpResponse; #[derive(Deserialize, Debug, Display)] struct Id { @@ -164,4 +237,19 @@ mod tests { let s = s.into_inner(); assert_eq!(s.id, "test1"); } + + #[test] + fn test_custom_error_responder() { + let req = TestRequest::with_uri("/name/user1/") + .data(QueryConfig::default().error_handler(|e, _| { + let resp = HttpResponse::UnprocessableEntity().finish(); + InternalError::from_response(e, resp).into() + })).to_srv_request(); + + let (req, mut pl) = req.into_parts(); + let query = Query::::from_request(&req, &mut pl); + + assert!(query.is_err()); + assert_eq!(query.unwrap_err().as_response_error().error_response().status(), StatusCode::UNPROCESSABLE_ENTITY); + } } From 80f4ef9aac8d940e8a3261c37e007bd1c9ffcd32 Mon Sep 17 00:00:00 2001 From: Glade Miller Date: Wed, 15 May 2019 10:21:07 -0600 Subject: [PATCH 1385/1635] When using codegen with paths that have parameters then only the first endpoint resolves (#842) --- actix-web-codegen/tests/test_macro.rs | 43 +++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 9b9ec6f3..cd58d0b0 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,7 +1,7 @@ use actix_http::HttpService; use actix_http_test::TestServer; -use actix_web::{http, App, HttpResponse, Responder}; -use actix_web_codegen::{get, post, put}; +use actix_web::{http, App, HttpResponse, Responder, web::{Path}}; +use actix_web_codegen::{get, post, put, delete}; use futures::{future, Future}; #[get("/test")] @@ -29,6 +29,45 @@ fn auto_sync() -> impl Future { future::ok(HttpResponse::Ok().finish()) } +#[put("/test/{param}")] +fn put_param_test(_: Path) -> impl Responder { + HttpResponse::Created() +} + +#[delete("/test/{param}")] +fn delete_param_test(_: Path) -> impl Responder { + HttpResponse::NoContent() +} + +#[get("/test/{param}")] +fn get_param_test(_: Path) -> impl Responder { + HttpResponse::Ok() +} + +#[test] +fn test_params() { + let mut srv = TestServer::new(|| { + HttpService::new( + App::new() + .service(get_param_test) + .service(put_param_test) + .service(delete_param_test), + ) + }); + + let request = srv.request(http::Method::GET, srv.url("/test/it")); + let response = srv.block_on(request.send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::OK); + + let request = srv.request(http::Method::PUT, srv.url("/test/it")); + let response = srv.block_on(request.send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::CREATED); + + let request = srv.request(http::Method::DELETE, srv.url("/test/it")); + let response = srv.block_on(request.send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); +} + #[test] fn test_body() { let mut srv = TestServer::new(|| { From e1ff3bf8fa04bdd7b309385e38db0dcb24cd6a76 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 15 May 2019 10:31:40 -0700 Subject: [PATCH 1386/1635] fix resource match with params #841 --- CHANGES.md | 7 +++++ Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/tests/test_macro.rs | 4 +-- src/error.rs | 9 ++++-- src/resource.rs | 43 ++++++++++++++++++++++++++- src/types/query.rs | 26 +++++++++------- 8 files changed, 77 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2e4d23ba..2e41312a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## [1.0.0-rc.1] - 2019-05-xx + ### Add * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. @@ -8,6 +10,11 @@ * `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. +### Fixed + +* Codegen with parameters in the path only resolves the first registered endpoint #841 + + ## [1.0.0-beta.4] - 2019-05-12 ### Add diff --git a/Cargo.toml b/Cargo.toml index 9c670ab1..8d8dd980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ rust-tls = ["rustls", "actix-server/rust-tls"] actix-codec = "0.1.2" actix-service = "0.4.0" actix-utils = "0.4.0" -actix-router = "0.1.3" +actix-router = "0.1.5" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" actix-http = { version = "0.2.0", features=["fail"] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index fe24d4c3..0341641a 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.8.0" +actix = "0.8.2" actix-web = "1.0.0-beta.5" actix-http = "0.2.0" actix-codec = "0.1.2" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 7ca7912f..5ca9f416 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.1.0-beta.1" +version = "0.1.0" description = "Actix web proc macros" readme = "README.md" authors = ["Nikolay Kim "] diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index cd58d0b0..cd899d48 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,7 +1,7 @@ use actix_http::HttpService; use actix_http_test::TestServer; -use actix_web::{http, App, HttpResponse, Responder, web::{Path}}; -use actix_web_codegen::{get, post, put, delete}; +use actix_web::{http, web::Path, App, HttpResponse, Responder}; +use actix_web_codegen::{delete, get, post, put}; use futures::{future, Future}; #[get("/test")] diff --git a/src/error.rs b/src/error.rs index a3062b58..e1cc7984 100644 --- a/src/error.rs +++ b/src/error.rs @@ -104,7 +104,9 @@ pub enum QueryPayloadError { impl ResponseError for QueryPayloadError { fn error_response(&self) -> HttpResponse { match *self { - QueryPayloadError::Deserialize(_) => HttpResponse::new(StatusCode::BAD_REQUEST), + QueryPayloadError::Deserialize(_) => { + HttpResponse::new(StatusCode::BAD_REQUEST) + } } } } @@ -163,7 +165,10 @@ mod tests { #[test] fn test_query_payload_error() { - let resp: HttpResponse = QueryPayloadError::Deserialize(serde_urlencoded::from_str::("bad query").unwrap_err()).error_response(); + let resp: HttpResponse = QueryPayloadError::Deserialize( + serde_urlencoded::from_str::("bad query").unwrap_err(), + ) + .error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } diff --git a/src/resource.rs b/src/resource.rs index 7f76e0f5..ad08a15f 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -606,7 +606,7 @@ mod tests { use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::test::{call_service, init_service, TestRequest}; - use crate::{web, App, Error, HttpResponse}; + use crate::{guard, web, App, Error, HttpResponse}; fn md( req: ServiceRequest, @@ -723,4 +723,45 @@ mod tests { let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } + + #[test] + fn test_resource_guards() { + let mut srv = init_service( + App::new() + .service( + web::resource("/test/{p}") + .guard(guard::Get()) + .to(|| HttpResponse::Ok()), + ) + .service( + web::resource("/test/{p}") + .guard(guard::Put()) + .to(|| HttpResponse::Created()), + ) + .service( + web::resource("/test/{p}") + .guard(guard::Delete()) + .to(|| HttpResponse::NoContent()), + ), + ); + + let req = TestRequest::with_uri("/test/it") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test/it") + .method(Method::PUT) + .to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::CREATED); + + let req = TestRequest::with_uri("/test/it") + .method(Method::DELETE) + .to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::NO_CONTENT); + } + } diff --git a/src/types/query.rs b/src/types/query.rs index 24225dad..7a91c421 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -8,9 +8,9 @@ use serde::de; use serde_urlencoded; use crate::dev::Payload; +use crate::error::QueryPayloadError; use crate::extract::FromRequest; use crate::request::HttpRequest; -use crate::error::QueryPayloadError; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from from the request's query. @@ -188,8 +188,8 @@ pub struct QueryConfig { impl QueryConfig { /// Set custom error handler pub fn error_handler(mut self, f: F) -> Self - where - F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, + where + F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, { self.ehandler = Some(Arc::new(f)); self @@ -198,21 +198,19 @@ impl QueryConfig { impl Default for QueryConfig { fn default() -> Self { - QueryConfig { - ehandler: None, - } + QueryConfig { ehandler: None } } } #[cfg(test)] mod tests { + use actix_http::http::StatusCode; use derive_more::Display; use serde_derive::Deserialize; - use actix_http::http::StatusCode; use super::*; - use crate::test::TestRequest; use crate::error::InternalError; + use crate::test::TestRequest; use crate::HttpResponse; #[derive(Deserialize, Debug, Display)] @@ -244,12 +242,20 @@ mod tests { .data(QueryConfig::default().error_handler(|e, _| { let resp = HttpResponse::UnprocessableEntity().finish(); InternalError::from_response(e, resp).into() - })).to_srv_request(); + })) + .to_srv_request(); let (req, mut pl) = req.into_parts(); let query = Query::::from_request(&req, &mut pl); assert!(query.is_err()); - assert_eq!(query.unwrap_err().as_response_error().error_response().status(), StatusCode::UNPROCESSABLE_ENTITY); + assert_eq!( + query + .unwrap_err() + .as_response_error() + .error_response() + .status(), + StatusCode::UNPROCESSABLE_ENTITY + ); } } From 4b215e08395e8931ed771bd7a8ef959353610853 Mon Sep 17 00:00:00 2001 From: Miles Granger Date: Fri, 17 May 2019 22:10:46 +0200 Subject: [PATCH 1387/1635] Support Query::from_query() (#846) --- CHANGES.md | 1 + src/types/query.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 2e41312a..611dd0c5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Add +* Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. ### Changes diff --git a/src/types/query.rs b/src/types/query.rs index 7a91c421..2c07edfb 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -52,6 +52,16 @@ impl Query { pub fn into_inner(self) -> T { self.0 } + + /// Get query parameters from the path + pub fn from_query(query_str: &str) -> Result + where + T: de::DeserializeOwned, + { + serde_urlencoded::from_str::(query_str) + .map(|val| Ok(Query(val))) + .unwrap_or_else(move |e| Err(QueryPayloadError::Deserialize(e))) + } } impl ops::Deref for Query { @@ -218,6 +228,22 @@ mod tests { id: String, } + #[test] + fn test_service_request_extract() { + let req = TestRequest::with_uri("/name/user1/").to_srv_request(); + assert!(Query::::from_query(&req.query_string()).is_err()); + + let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + let mut s = Query::::from_query(&req.query_string()).unwrap(); + + assert_eq!(s.id, "test"); + assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + + s.id = "test1".to_string(); + let s = s.into_inner(); + assert_eq!(s.id, "test1"); + } + #[test] fn test_request_extract() { let req = TestRequest::with_uri("/name/user1/").to_srv_request(); From e8c86268784bd6e3af10eab05b69b2f54a5be2fc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 18 May 2019 09:54:23 -0700 Subject: [PATCH 1388/1635] update deps --- CHANGES.md | 3 ++- Cargo.toml | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 611dd0c5..e2aab249 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.0-rc.1] - 2019-05-xx +## [1.0.0-rc] - 2019-05-xx ### Add @@ -27,6 +27,7 @@ * `App::configure` take an `FnOnce` instead of `Fn` * Upgrade actix-net crates + ## [1.0.0-beta.3] - 2019-05-04 ### Added diff --git a/Cargo.toml b/Cargo.toml index 8d8dd980..74022c51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-beta.5" +version = "1.0.0-rc" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -41,7 +41,7 @@ members = [ ] [features] -default = ["brotli", "flate2-zlib", "secure-cookies", "client"] +default = ["brotli", "flate2-zlib", "secure-cookies", "client", "fail"] # http client client = ["awc"] @@ -58,6 +58,8 @@ flate2-rust = ["actix-http/flate2-rust"] # sessions feature, session require "ring" crate and c compiler secure-cookies = ["actix-http/secure-cookies"] +fail = ["actix-http/fail"] + # openssl ssl = ["openssl", "actix-server/ssl", "awc/ssl"] @@ -67,21 +69,20 @@ rust-tls = ["rustls", "actix-server/rust-tls"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.0" -actix-utils = "0.4.0" +actix-utils = "0.4.1" actix-router = "0.1.5" actix-rt = "0.2.2" actix-web-codegen = "0.1.0-beta.1" -actix-http = { version = "0.2.0", features=["fail"] } +actix-http = "0.2.0" actix-server = "0.5.0" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" -actix = { version = "0.8.1", features=["http"], optional = true } awc = { version = "0.2.0", optional = true } bytes = "0.4" derive_more = "0.14" encoding = "0.2" -futures = "0.1" +futures = "0.1.25" hashbrown = "0.3.0" log = "0.4" mime = "0.3" @@ -96,7 +97,7 @@ url = { version="1.7", features=["query_encoding"] } # ssl support openssl = { version="0.10", optional = true } -rustls = { version = "^0.15", optional = true } +rustls = { version = "0.15", optional = true } [dev-dependencies] actix-http = { version = "0.2.0", features=["ssl", "brotli", "flate2-zlib"] } From cbe022617763c22698561b2546a10897158edf12 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 18 May 2019 10:47:08 -0700 Subject: [PATCH 1389/1635] update changes --- actix-web-codegen/CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 3173ff1f..4f2d1c86 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0] - 2019-05-18 + +* Release + ## [0.1.0-beta.1] - 2019-04-20 * Gen code for actix-web 1.0.0-beta.1 From 0dda4b06ea30ea202758eae584867154ed9da02b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 18 May 2019 10:49:59 -0700 Subject: [PATCH 1390/1635] prepare release --- CHANGES.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e2aab249..85e63536 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.0-rc] - 2019-05-xx +## [1.0.0-rc] - 2019-05-18 ### Add diff --git a/Cargo.toml b/Cargo.toml index 74022c51..f52a492a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ actix-service = "0.4.0" actix-utils = "0.4.1" actix-router = "0.1.5" actix-rt = "0.2.2" -actix-web-codegen = "0.1.0-beta.1" +actix-web-codegen = "0.1.0" actix-http = "0.2.0" actix-server = "0.5.0" actix-server-config = "0.1.1" From e857ab1f81a537b192fd9074372e246b530dd151 Mon Sep 17 00:00:00 2001 From: Herbert Jones Date: Sat, 18 May 2019 12:50:35 -0500 Subject: [PATCH 1391/1635] HttpServer::shutdown_timeout u16 to u64 (#849) Increase maximum graceful shutdown time from 18 hours. For issue #848. --- src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index 3cb13997..629fc8bd 100644 --- a/src/server.rs +++ b/src/server.rs @@ -207,7 +207,7 @@ where /// dropped. /// /// By default shutdown timeout sets to 30 seconds. - pub fn shutdown_timeout(mut self, sec: u16) -> Self { + pub fn shutdown_timeout(mut self, sec: u64) -> Self { self.builder = Some(self.builder.take().unwrap().shutdown_timeout(sec)); self } From dea0e0a721c68324cf6aba5cc8429d395a91c334 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 18 May 2019 11:00:33 -0700 Subject: [PATCH 1392/1635] update actix-server dep --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f52a492a..0bf6e7c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ actix-router = "0.1.5" actix-rt = "0.2.2" actix-web-codegen = "0.1.0" actix-http = "0.2.0" -actix-server = "0.5.0" +actix-server = "0.5.1" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" awc = { version = "0.2.0", optional = true } From 0843bce7ba2d1e48f76df5f88d4043f08d8ea91d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 18 May 2019 11:15:58 -0700 Subject: [PATCH 1393/1635] prepare actix-multipart --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 5ee1d620..3a959890 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0] - 2019-05-18 + +* Release + ## [0.1.0-beta.4] - 2019-05-12 * Handle cancellation of uploads #834 #736 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 7d19ef9f..ca1ff9c9 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.0-beta.4" +version = "0.1.0" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-beta.5" +actix-web = "1.0.0-rc" actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" From 8ff56d7cd56c5de91ada5f2a7d02ea9392cf1816 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 18 May 2019 11:20:09 -0700 Subject: [PATCH 1394/1635] prepare actix-session release --- actix-session/CHANGES.md | 4 ++++ actix-session/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 5f2bdadd..10727ae3 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.0] - 2019-05-18 + +* Use actix-web 1.0.0-rc + ## [0.1.0-beta.4] - 2019-05-12 * Use actix-web 1.0.0-beta.4 diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 5bebb9a1..b0ef1e2b 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0-beta.4" +version = "0.1.0" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,7 +24,7 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-beta.5" +actix-web = "1.0.0-rc" actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" From 5826f39dbe15791a171fa8ea15d675e98803297a Mon Sep 17 00:00:00 2001 From: Harry Stern Date: Sat, 18 May 2019 22:36:28 -0400 Subject: [PATCH 1395/1635] Add `set_json` method to TestRequest (#851) - Takes a type which implements serde::Serialize, serializes it to JSON, and sets it as the payload. The content-type is also set to JSON. --- CHANGES.md | 2 ++ src/test.rs | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 85e63536..cc18e068 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. +* Add `test::TestRequest::set_json` convenience method to automatically + serialize data and set header in test requests. ### Changes diff --git a/src/test.rs b/src/test.rs index 3b3aac67..979a8bc4 100644 --- a/src/test.rs +++ b/src/test.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::rc::Rc; -use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; +use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, Extensions, Request}; @@ -13,6 +13,7 @@ use actix_service::{IntoNewService, IntoService, NewService, Service}; use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Future, IntoFuture}; use futures::Stream; +use serde::Serialize; use serde::de::DeserializeOwned; use serde_json; @@ -477,6 +478,15 @@ impl TestRequest { self } + /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is + /// set to `application/json`. + pub fn set_json(mut self, data: &T) -> Self { + let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json"); + self.req.set_payload(bytes); + self.req.set(ContentType::json()); + self + } + /// Set application data. This is equivalent of `App::data()` method /// for testing purpose. pub fn data(mut self, data: T) -> Self { @@ -553,6 +563,7 @@ impl TestRequest { #[cfg(test)] mod tests { + use actix_http::httpmessage::HttpMessage; use serde::{Deserialize, Serialize}; use std::time::SystemTime; @@ -657,6 +668,28 @@ mod tests { assert_eq!(&result.id, "12345"); } + #[test] + fn test_request_response_json() { + let mut app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| { + HttpResponse::Ok().json(person.into_inner()) + }), + ))); + + let payload = Person {id: "12345".to_string(), name: "User name".to_string() }; + + let req = TestRequest::post() + .uri("/people") + .set_json(&payload) + .to_request(); + + assert_eq!(req.content_type(), "application/json"); + + let result: Person = read_response_json(&mut app, req); + assert_eq!(&result.id, "12345"); + assert_eq!(&result.name, "User name"); + } + #[test] fn test_async_with_block() { fn async_with_block() -> impl Future { From fc85ae401486ba013865c62e6b4e867e4c0ffb75 Mon Sep 17 00:00:00 2001 From: Aliaksandr Rahalevich Date: Tue, 21 May 2019 10:43:18 -0700 Subject: [PATCH 1396/1635] small documentation fix (#856) --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 979a8bc4..a9b1d3b7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -249,7 +249,7 @@ where /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let resp = call_service(&mut srv, req); +/// let resp = test::call_service(&mut app, req); /// let result = test::read_body(resp); /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } From 12842871fe2d848af62da6418022bac931350a28 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 22 May 2019 11:18:33 -0700 Subject: [PATCH 1397/1635] Clear http requests pool on app service drop #860 --- CHANGES.md | 14 ++++++++++++-- src/app_service.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++- src/request.rs | 4 ++++ src/test.rs | 10 +++++++--- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cc18e068..16ac0d12 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,13 +1,23 @@ # Changes +## [1.0.0] - 2019-05-xx + +### Add + +* Add `test::TestRequest::set_json()` convenience method to automatically + serialize data and set header in test requests. + +### Fixed + +* Clear http requests pool on app service drop #860 + + ## [1.0.0-rc] - 2019-05-18 ### Add * Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. -* Add `test::TestRequest::set_json` convenience method to automatically - serialize data and set header in test requests. ### Changes diff --git a/src/app_service.rs b/src/app_service.rs index f3438984..1a4a22b8 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -183,7 +183,7 @@ where } /// Service to convert `Request` to a `ServiceRequest` -pub struct AppInitService +pub struct AppInitService where T: Service, Error = Error>, { @@ -231,6 +231,16 @@ where } } +impl Drop for AppInitService +where + T: Service, Error = Error>, +{ + fn drop(&mut self) { + self.pool.clear(); + println!("DROP: APP-INIT-ENTRY"); + } +} + pub struct AppRoutingFactory { services: Rc>)>>, default: Rc, @@ -408,3 +418,38 @@ impl NewService for AppEntry { self.factory.borrow_mut().as_mut().unwrap().new_service(&()) } } + +#[cfg(test)] +mod tests { + use actix_service::Service; + use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }; + + use crate::{test, web, App, HttpResponse}; + + struct DropData(Arc); + + impl Drop for DropData { + fn drop(&mut self) { + self.0.store(true, Ordering::Relaxed); + println!("Dropping!"); + } + } + + #[test] + fn drop_data() { + let data = Arc::new(AtomicBool::new(false)); + { + let mut app = test::init_service( + App::new() + .data(DropData(data.clone())) + .service(web::resource("/test").to(|| HttpResponse::Ok())), + ); + let req = test::TestRequest::with_uri("/test").to_request(); + let resp = test::block_on(app.call(req)).unwrap(); + } + assert!(data.load(Ordering::Relaxed)); + } +} diff --git a/src/request.rs b/src/request.rs index 7b3ab04a..c6d14b50 100644 --- a/src/request.rs +++ b/src/request.rs @@ -325,6 +325,10 @@ impl HttpRequestPool { None } } + + pub(crate) fn clear(&self) { + self.0.borrow_mut().clear() + } } #[cfg(test)] diff --git a/src/test.rs b/src/test.rs index 979a8bc4..7a909fbd 100644 --- a/src/test.rs +++ b/src/test.rs @@ -13,8 +13,8 @@ use actix_service::{IntoNewService, IntoService, NewService, Service}; use bytes::{Bytes, BytesMut}; use futures::future::{lazy, ok, Future, IntoFuture}; use futures::Stream; -use serde::Serialize; use serde::de::DeserializeOwned; +use serde::Serialize; use serde_json; pub use actix_http::test::TestBuffer; @@ -481,7 +481,8 @@ impl TestRequest { /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is /// set to `application/json`. pub fn set_json(mut self, data: &T) -> Self { - let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json"); + let bytes = + serde_json::to_string(data).expect("Failed to serialize test data to json"); self.req.set_payload(bytes); self.req.set(ContentType::json()); self @@ -676,7 +677,10 @@ mod tests { }), ))); - let payload = Person {id: "12345".to_string(), name: "User name".to_string() }; + let payload = Person { + id: "12345".to_string(), + name: "User name".to_string(), + }; let req = TestRequest::post() .uri("/people") From 7746e785c19c0e8d4c605f9bf44d68c6c469c035 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 22 May 2019 11:20:37 -0700 Subject: [PATCH 1398/1635] re-export Service and Transform traits --- src/app_service.rs | 2 +- src/lib.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app_service.rs b/src/app_service.rs index 1a4a22b8..c483a119 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -448,7 +448,7 @@ mod tests { .service(web::resource("/test").to(|| HttpResponse::Ok())), ); let req = test::TestRequest::with_uri("/test").to_request(); - let resp = test::block_on(app.call(req)).unwrap(); + let _ = test::block_on(app.call(req)).unwrap(); } assert!(data.load(Ordering::Relaxed)); } diff --git a/src/lib.rs b/src/lib.rs index b578d87c..f84dbd5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,6 +151,7 @@ pub mod dev { }; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; + pub use actix_service::{Service, Transform}; pub(crate) fn insert_slash(path: &str) -> String { let mut path = path.to_owned(); From d3e807f6e94777d35d1b16b2759a7c7a8b5b39cc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 22 May 2019 11:49:27 -0700 Subject: [PATCH 1399/1635] move Payload to inner http request --- src/app_service.rs | 4 +++- src/handler.rs | 11 +++++------ src/request.rs | 3 +++ src/service.rs | 43 ++++++++++++++++++++++--------------------- src/test.rs | 11 ++++++----- 5 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index c483a119..88e97de1 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -215,19 +215,21 @@ where inner.path.get_mut().update(&head.uri); inner.path.reset(); inner.head = head; + inner.payload = payload; inner.app_data = self.data.clone(); req } else { HttpRequest::new( Path::new(Url::new(head.uri.clone())), head, + payload, self.rmap.clone(), self.config.clone(), self.data.clone(), self.pool, ) }; - self.service.call(ServiceRequest::from_parts(req, payload)) + self.service.call(ServiceRequest::new(req)) } } diff --git a/src/handler.rs b/src/handler.rs index b53d1638..bd0b3551 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,7 +1,7 @@ use std::convert::Infallible; use std::marker::PhantomData; -use actix_http::{Error, Payload, Response}; +use actix_http::{Error, Response}; use actix_service::{NewService, Service}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; @@ -331,15 +331,15 @@ where ExtractResponse { fut, + req, fut_s: None, - req: Some((req, payload)), service: self.service.clone(), } } } pub struct ExtractResponse { - req: Option<(HttpRequest, Payload)>, + req: HttpRequest, service: S, fut: ::Future, fut_s: Option, @@ -362,12 +362,11 @@ where } let item = try_ready!(self.fut.poll().map_err(|e| { - let (req, payload) = self.req.take().unwrap(); - let req = ServiceRequest::from_parts(req, payload); + let req = ServiceRequest::new(self.req.clone()); (e.into(), req) })); - self.fut_s = Some(self.service.call((item, self.req.take().unwrap().0))); + self.fut_s = Some(self.service.call((item, self.req.clone()))); self.poll() } } diff --git a/src/request.rs b/src/request.rs index c6d14b50..07aac8cf 100644 --- a/src/request.rs +++ b/src/request.rs @@ -20,6 +20,7 @@ pub struct HttpRequest(pub(crate) Rc); pub(crate) struct HttpRequestInner { pub(crate) head: Message, pub(crate) path: Path, + pub(crate) payload: Payload, pub(crate) app_data: Rc, rmap: Rc, config: AppConfig, @@ -31,6 +32,7 @@ impl HttpRequest { pub(crate) fn new( path: Path, head: Message, + payload: Payload, rmap: Rc, config: AppConfig, app_data: Rc, @@ -39,6 +41,7 @@ impl HttpRequest { HttpRequest(Rc::new(HttpRequestInner { head, path, + payload, rmap, config, app_data, diff --git a/src/service.rs b/src/service.rs index eee8b0ad..f4f1a205 100644 --- a/src/service.rs +++ b/src/service.rs @@ -50,45 +50,46 @@ where } } -pub struct ServiceRequest { - req: HttpRequest, - payload: Payload, -} +/// An service http request +/// +/// ServiceRequest allows mutable access to request's internal structures +pub struct ServiceRequest(HttpRequest); impl ServiceRequest { - /// Construct service request from parts - pub(crate) fn from_parts(req: HttpRequest, payload: Payload) -> Self { - ServiceRequest { req, payload } + /// Construct service request + pub(crate) fn new(req: HttpRequest) -> Self { + ServiceRequest(req) } /// Deconstruct request into parts - pub fn into_parts(self) -> (HttpRequest, Payload) { - (self.req, self.payload) + pub fn into_parts(mut self) -> (HttpRequest, Payload) { + let pl = Rc::get_mut(&mut (self.0).0).unwrap().payload.take(); + (self.0, pl) } /// Create service response #[inline] pub fn into_response>>(self, res: R) -> ServiceResponse { - ServiceResponse::new(self.req, res.into()) + ServiceResponse::new(self.0, res.into()) } /// Create service response for error #[inline] pub fn error_response>(self, err: E) -> ServiceResponse { let res: Response = err.into().into(); - ServiceResponse::new(self.req, res.into_body()) + ServiceResponse::new(self.0, res.into_body()) } /// This method returns reference to the request head #[inline] pub fn head(&self) -> &RequestHead { - &self.req.head() + &self.0.head() } /// This method returns reference to the request head #[inline] pub fn head_mut(&mut self) -> &mut RequestHead { - self.req.head_mut() + self.0.head_mut() } /// Request's uri. @@ -164,24 +165,24 @@ impl ServiceRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Path { - self.req.match_info() + self.0.match_info() } #[inline] pub fn match_info_mut(&mut self) -> &mut Path { - self.req.match_info_mut() + self.0.match_info_mut() } /// Service configuration #[inline] pub fn app_config(&self) -> &AppConfig { - self.req.app_config() + self.0.app_config() } /// Get an application data stored with `App::data()` method during /// application configuration. pub fn app_data(&self) -> Option> { - if let Some(st) = self.req.0.app_data.get::>() { + if let Some(st) = (self.0).0.app_data.get::>() { Some(st.clone()) } else { None @@ -191,7 +192,7 @@ impl ServiceRequest { #[doc(hidden)] /// Set new app data container pub fn set_data_container(&mut self, extensions: Rc) { - Rc::get_mut(&mut self.req.0).unwrap().app_data = extensions; + Rc::get_mut(&mut (self.0).0).unwrap().app_data = extensions; } } @@ -213,18 +214,18 @@ impl HttpMessage for ServiceRequest { /// Request extensions #[inline] fn extensions(&self) -> Ref { - self.req.extensions() + self.0.extensions() } /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut { - self.req.extensions_mut() + self.0.extensions_mut() } #[inline] fn take_payload(&mut self) -> Payload { - std::mem::replace(&mut self.payload, Payload::None) + Rc::get_mut(&mut (self.0).0).unwrap().payload.take() } } diff --git a/src/test.rs b/src/test.rs index dc17e922..89c1a126 100644 --- a/src/test.rs +++ b/src/test.rs @@ -512,16 +512,15 @@ impl TestRequest { let (head, payload) = self.req.finish().into_parts(); self.path.get_mut().update(&head.uri); - let req = HttpRequest::new( + ServiceRequest::new(HttpRequest::new( self.path, head, + payload, Rc::new(self.rmap), AppConfig::new(self.config), Rc::new(self.app_data), HttpRequestPool::create(), - ); - - ServiceRequest::from_parts(req, payload) + )) } /// Complete request creation and generate `ServiceResponse` instance @@ -531,12 +530,13 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { - let (head, _) = self.req.finish().into_parts(); + let (head, payload) = self.req.finish().into_parts(); self.path.get_mut().update(&head.uri); HttpRequest::new( self.path, head, + payload, Rc::new(self.rmap), AppConfig::new(self.config), Rc::new(self.app_data), @@ -552,6 +552,7 @@ impl TestRequest { let req = HttpRequest::new( self.path, head, + Payload::None, Rc::new(self.rmap), AppConfig::new(self.config), Rc::new(self.app_data), From babf48c550bcd27bded364caec7d0350846efba9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 22 May 2019 21:21:12 -0700 Subject: [PATCH 1400/1635] fix NamedFile last-modified check #820 --- actix-files/CHANGES.md | 5 +++++ actix-files/src/named.rs | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 6b4ab57b..e8457f42 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.0] - 2019-05-xx + +* NamedFile last-modified check always fails due to nano-seconds + in file modified date #820 + ## [0.1.0-beta.4] - 2019-05-12 * Update actix-web to beta.4 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 41a7cf1f..2298e35a 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -337,7 +337,12 @@ impl Responder for NamedFile { } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = (last_modified, req.get_header()) { - m > since + let t1: SystemTime = m.clone().into(); + let t2: SystemTime = since.clone().into(); + match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { + (Ok(t1), Ok(t2)) => t1 > t2, + _ => false, + } } else { false }; @@ -350,7 +355,12 @@ impl Responder for NamedFile { } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = (last_modified, req.get_header()) { - m <= since + let t1: SystemTime = m.clone().into(); + let t2: SystemTime = since.clone().into(); + match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { + (Ok(t1), Ok(t2)) => t1 <= t2, + _ => false, + } } else { false }; From ded1e86e7eb0799b9a31401e00f50ab1206a2abe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 22 May 2019 21:25:51 -0700 Subject: [PATCH 1401/1635] Add ServiceRequest::set_payload() method --- CHANGES.md | 2 ++ src/service.rs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 16ac0d12..6cd109a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Add +* Add `ServiceRequest::set_payload()` method. + * Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. diff --git a/src/service.rs b/src/service.rs index f4f1a205..722813a9 100644 --- a/src/service.rs +++ b/src/service.rs @@ -189,6 +189,11 @@ impl ServiceRequest { } } + /// Set request payload. + pub fn set_payload(&mut self, payload: Payload) { + Rc::get_mut(&mut (self.0).0).unwrap().payload = payload; + } + #[doc(hidden)] /// Set new app data container pub fn set_data_container(&mut self, extensions: Rc) { From 801cc2ed5d20cf9288d581c68cee91a3b29efc77 Mon Sep 17 00:00:00 2001 From: Vlad Frolov Date: Thu, 23 May 2019 15:21:02 +0300 Subject: [PATCH 1402/1635] Cleaned unnecessary Option<_> around ServerBuilder in server.rs/HttpServer (#863) --- CHANGES.md | 4 ++++ src/server.rs | 61 ++++++++++++++++++++++++--------------------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6cd109a0..5974ee69 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,10 @@ * Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. +### Changes + +* Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 + ### Fixed * Clear http requests pool on app service drop #860 diff --git a/src/server.rs b/src/server.rs index 629fc8bd..353f29ba 100644 --- a/src/server.rs +++ b/src/server.rs @@ -64,7 +64,7 @@ where config: Arc>, backlog: i32, sockets: Vec, - builder: Option, + builder: ServerBuilder, _t: PhantomData<(S, B)>, } @@ -91,7 +91,7 @@ where })), backlog: 1024, sockets: Vec::new(), - builder: Some(ServerBuilder::default()), + builder: ServerBuilder::default(), _t: PhantomData, } } @@ -101,7 +101,7 @@ where /// By default http server uses number of available logical cpu as threads /// count. pub fn workers(mut self, num: usize) -> Self { - self.builder = Some(self.builder.take().unwrap().workers(num)); + self.builder = self.builder.workers(num); self } @@ -117,7 +117,7 @@ where /// This method should be called before `bind()` method call. pub fn backlog(mut self, backlog: i32) -> Self { self.backlog = backlog; - self.builder = Some(self.builder.take().unwrap().backlog(backlog)); + self.builder = self.builder.backlog(backlog); self } @@ -128,7 +128,7 @@ where /// /// By default max connections is set to a 25k. pub fn maxconn(mut self, num: usize) -> Self { - self.builder = Some(self.builder.take().unwrap().maxconn(num)); + self.builder = self.builder.maxconn(num); self } @@ -139,7 +139,7 @@ where /// /// By default max connections is set to a 256. pub fn maxconnrate(mut self, num: usize) -> Self { - self.builder = Some(self.builder.take().unwrap().maxconnrate(num)); + self.builder = self.builder.maxconnrate(num); self } @@ -190,13 +190,13 @@ where /// Stop actix system. pub fn system_exit(mut self) -> Self { - self.builder = Some(self.builder.take().unwrap().system_exit()); + self.builder = self.builder.system_exit(); self } /// Disable signal handling pub fn disable_signals(mut self) -> Self { - self.builder = Some(self.builder.take().unwrap().disable_signals()); + self.builder = self.builder.disable_signals(); self } @@ -208,7 +208,7 @@ where /// /// By default shutdown timeout sets to 30 seconds. pub fn shutdown_timeout(mut self, sec: u64) -> Self { - self.builder = Some(self.builder.take().unwrap().shutdown_timeout(sec)); + self.builder = self.builder.shutdown_timeout(sec); self } @@ -240,7 +240,7 @@ where scheme: "http", }); - self.builder = Some(self.builder.take().unwrap().listen( + self.builder = self.builder.listen( format!("actix-web-service-{}", addr), lst, move || { @@ -250,8 +250,7 @@ where .client_timeout(c.client_timeout) .finish(factory()) }, - )?); - + )?; Ok(self) } @@ -260,20 +259,19 @@ where /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn listen_ssl( - mut self, + self, lst: net::TcpListener, builder: SslAcceptorBuilder, ) -> io::Result { - self.listen_ssl_inner(lst, openssl_acceptor(builder)?)?; - Ok(self) + self.listen_ssl_inner(lst, openssl_acceptor(builder)?) } #[cfg(feature = "ssl")] fn listen_ssl_inner( - &mut self, + mut self, lst: net::TcpListener, acceptor: SslAcceptor, - ) -> io::Result<()> { + ) -> io::Result { use actix_server::ssl::{OpensslAcceptor, SslError}; let acceptor = OpensslAcceptor::new(acceptor); @@ -285,7 +283,7 @@ where scheme: "https", }); - self.builder = Some(self.builder.take().unwrap().listen( + self.builder = self.builder.listen( format!("actix-web-service-{}", addr), lst, move || { @@ -300,8 +298,8 @@ where .map_init_err(|_| ()), ) }, - )?); - Ok(()) + )?; + Ok(self) } #[cfg(feature = "rust-tls")] @@ -309,20 +307,19 @@ where /// /// This method sets alpn protocols to "h2" and "http/1.1" pub fn listen_rustls( - mut self, + self, lst: net::TcpListener, config: RustlsServerConfig, ) -> io::Result { - self.listen_rustls_inner(lst, config)?; - Ok(self) + self.listen_rustls_inner(lst, config) } #[cfg(feature = "rust-tls")] fn listen_rustls_inner( - &mut self, + mut self, lst: net::TcpListener, mut config: RustlsServerConfig, - ) -> io::Result<()> { + ) -> io::Result { use actix_server::ssl::{RustlsAcceptor, SslError}; let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; @@ -337,7 +334,7 @@ where scheme: "https", }); - self.builder = Some(self.builder.take().unwrap().listen( + self.builder = self.builder.listen( format!("actix-web-service-{}", addr), lst, move || { @@ -352,8 +349,8 @@ where .map_init_err(|_| ()), ) }, - )?); - Ok(()) + )?; + Ok(self) } /// The socket address to bind @@ -416,7 +413,7 @@ where let acceptor = openssl_acceptor(builder)?; for lst in sockets { - self.listen_ssl_inner(lst, acceptor.clone())?; + self = self.listen_ssl_inner(lst, acceptor.clone())?; } Ok(self) @@ -433,7 +430,7 @@ where ) -> io::Result { let sockets = self.bind2(addr)?; for lst in sockets { - self.listen_rustls_inner(lst, config.clone())?; + self = self.listen_rustls_inner(lst, config.clone())?; } Ok(self) } @@ -473,8 +470,8 @@ where /// sys.run() // <- Run actix system, this method starts all async processes /// } /// ``` - pub fn start(mut self) -> Server { - self.builder.take().unwrap().start() + pub fn start(self) -> Server { + self.builder.start() } /// Spawn new thread and start listening for incoming connections. From 6db625f55b17725a4ba8d6c336e384bcb5e68b01 Mon Sep 17 00:00:00 2001 From: Miles Granger Date: Sat, 25 May 2019 10:52:23 +0200 Subject: [PATCH 1403/1635] Update actix-web dep to 1.0.0-rc (#864) --- actix-files/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6b065758..de79946a 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-beta.5" +actix-web = "1.0.0-rc" actix-service = "0.4.0" bitflags = "1" bytes = "0.4" From 35eb378585087a9db5e1eaa6206b0864e7a42883 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 25 May 2019 02:02:28 -0700 Subject: [PATCH 1404/1635] prepare actix-files release --- actix-files/CHANGES.md | 2 +- actix-files/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index e8457f42..021f8dc6 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.0] - 2019-05-xx +## [0.1.0] - 2019-05-25 * NamedFile last-modified check always fails due to nano-seconds in file modified date #820 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index de79946a..9ffbf0e7 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0-beta.4" +version = "0.1.0" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -31,4 +31,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0-beta.5", features=["ssl"] } +actix-web = { version = "1.0.0-rc", features=["ssl"] } From 3f196f469d115af8053ae4ca99f3001eae5eeb81 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 25 May 2019 02:13:04 -0700 Subject: [PATCH 1405/1635] update version --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0bf6e7c9..815aeb5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0-rc" +version = "1.0.0" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -102,7 +102,7 @@ rustls = { version = "0.15", optional = true } [dev-dependencies] actix-http = { version = "0.2.0", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } -actix-files = { version = "0.1.0-beta.4" } +actix-files = { version = "0.1.0" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" From 7f12b754e9aabc0cbfd02eaee9512bc19ed20bb4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 25 May 2019 03:07:40 -0700 Subject: [PATCH 1406/1635] Handle socket read disconnect --- actix-http/CHANGES.md | 7 +++++++ actix-http/Cargo.toml | 4 ++-- actix-http/src/h1/dispatcher.rs | 8 +++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 61c211b2..b3d8604c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.2.1] - 2019-05-2 + +### Fixed + +* Handle socket read disconnect + + ## [0.2.0] - 2019-05-12 ### Changed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index fabf7fe9..b4bfabec 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.0" +version = "0.2.1" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -47,7 +47,7 @@ secure-cookies = ["ring"] actix-service = "0.4.0" actix-codec = "0.1.2" actix-connect = "0.2.0" -actix-utils = "0.4.0" +actix-utils = "0.4.1" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index ec717ae0..131811a9 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -583,6 +583,9 @@ where self.ka_timer = Some(Delay::new(interval)); } else { self.flags.insert(Flags::READ_DISCONNECT); + if let Some(mut payload) = self.payload.take() { + payload.set_error(PayloadError::Incomplete(None)); + } return Ok(()); } } else { @@ -694,7 +697,10 @@ where if let Some(true) = read_available(&mut inner.io, &mut inner.read_buf)? { - inner.flags.insert(Flags::READ_DISCONNECT) + inner.flags.insert(Flags::READ_DISCONNECT); + if let Some(mut payload) = inner.payload.take() { + payload.feed_eof(); + } } } From aa626a1e7281c22aebadbc296e2f8510eb1965dd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 25 May 2019 03:16:46 -0700 Subject: [PATCH 1407/1635] handle disconnects --- actix-multipart/CHANGES.md | 6 +++- actix-multipart/Cargo.toml | 2 +- actix-multipart/src/server.rs | 64 ++++++++++++++++++++++------------- 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 3a959890..cf32859e 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,12 +1,16 @@ # Changes +## [0.1.1] - 2019-05-25 + +* Fix disconnect handling #834 + ## [0.1.0] - 2019-05-18 * Release ## [0.1.0-beta.4] - 2019-05-12 -* Handle cancellation of uploads #834 #736 +* Handle cancellation of uploads #736 * Upgrade to actix-web 1.0.0-beta.4 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index ca1ff9c9..fe63d536 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 7d746ea2..8245d241 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -128,7 +128,7 @@ impl InnerMultipart { fn read_headers( payload: &mut PayloadBuffer, ) -> Result, MultipartError> { - match payload.read_until(b"\r\n\r\n") { + match payload.read_until(b"\r\n\r\n")? { None => { if payload.eof { Err(MultipartError::Incomplete) @@ -167,7 +167,7 @@ impl InnerMultipart { boundary: &str, ) -> Result, MultipartError> { // TODO: need to read epilogue - match payload.readline() { + match payload.readline()? { None => { if payload.eof { Ok(Some(true)) @@ -200,7 +200,7 @@ impl InnerMultipart { ) -> Result, MultipartError> { let mut eof = false; loop { - match payload.readline() { + match payload.readline()? { Some(chunk) => { if chunk.is_empty() { return Err(MultipartError::Boundary); @@ -481,7 +481,7 @@ impl InnerField { if *size == 0 { Ok(Async::Ready(None)) } else { - match payload.read_max(*size) { + match payload.read_max(*size)? { Some(mut chunk) => { let len = cmp::min(chunk.len() as u64, *size); *size -= len; @@ -512,7 +512,11 @@ impl InnerField { let len = payload.buf.len(); if len == 0 { - return Ok(Async::NotReady); + return if payload.eof { + Err(MultipartError::Incomplete) + } else { + Ok(Async::NotReady) + }; } // check boundary @@ -597,7 +601,7 @@ impl InnerField { } } - match payload.readline() { + match payload.readline()? { None => Async::Ready(None), Some(line) => { if line.as_ref() != b"\r\n" { @@ -749,23 +753,31 @@ impl PayloadBuffer { } } - fn read_max(&mut self, size: u64) -> Option { + fn read_max(&mut self, size: u64) -> Result, MultipartError> { if !self.buf.is_empty() { let size = std::cmp::min(self.buf.len() as u64, size) as usize; - Some(self.buf.split_to(size).freeze()) + Ok(Some(self.buf.split_to(size).freeze())) + } else if self.eof { + Err(MultipartError::Incomplete) } else { - None + Ok(None) } } /// Read until specified ending - pub fn read_until(&mut self, line: &[u8]) -> Option { - twoway::find_bytes(&self.buf, line) - .map(|idx| self.buf.split_to(idx + line.len()).freeze()) + pub fn read_until(&mut self, line: &[u8]) -> Result, MultipartError> { + let res = twoway::find_bytes(&self.buf, line) + .map(|idx| self.buf.split_to(idx + line.len()).freeze()); + + if res.is_none() && self.eof { + Err(MultipartError::Incomplete) + } else { + Ok(res) + } } /// Read bytes until new line delimiter - pub fn readline(&mut self) -> Option { + pub fn readline(&mut self) -> Result, MultipartError> { self.read_until(b"\n") } @@ -991,7 +1003,7 @@ mod tests { assert_eq!(payload.buf.len(), 0); payload.poll_stream().unwrap(); - assert_eq!(None, payload.read_max(1)); + assert_eq!(None, payload.read_max(1).unwrap()); }) } @@ -1001,14 +1013,14 @@ mod tests { let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); - assert_eq!(None, payload.read_max(4)); + assert_eq!(None, payload.read_max(4).unwrap()); sender.feed_data(Bytes::from("data")); sender.feed_eof(); payload.poll_stream().unwrap(); - assert_eq!(Some(Bytes::from("data")), payload.read_max(4)); + assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap()); assert_eq!(payload.buf.len(), 0); - assert_eq!(None, payload.read_max(1)); + assert!(payload.read_max(1).is_err()); assert!(payload.eof); }) } @@ -1018,7 +1030,7 @@ mod tests { run_on(|| { let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); - assert_eq!(None, payload.read_max(1)); + assert_eq!(None, payload.read_max(1).unwrap()); sender.set_error(PayloadError::Incomplete(None)); payload.poll_stream().err().unwrap(); }) @@ -1035,10 +1047,10 @@ mod tests { payload.poll_stream().unwrap(); assert_eq!(payload.buf.len(), 10); - assert_eq!(Some(Bytes::from("line1")), payload.read_max(5)); + assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap()); assert_eq!(payload.buf.len(), 5); - assert_eq!(Some(Bytes::from("line2")), payload.read_max(5)); + assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap()); assert_eq!(payload.buf.len(), 0); }) } @@ -1069,16 +1081,22 @@ mod tests { let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); - assert_eq!(None, payload.read_until(b"ne")); + assert_eq!(None, payload.read_until(b"ne").unwrap()); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); payload.poll_stream().unwrap(); - assert_eq!(Some(Bytes::from("line")), payload.read_until(b"ne")); + assert_eq!( + Some(Bytes::from("line")), + payload.read_until(b"ne").unwrap() + ); assert_eq!(payload.buf.len(), 6); - assert_eq!(Some(Bytes::from("1line2")), payload.read_until(b"2")); + assert_eq!( + Some(Bytes::from("1line2")), + payload.read_until(b"2").unwrap() + ); assert_eq!(payload.buf.len(), 0); }) } From 1eb89b83751b14267d935aae0d82af565c2bc9d1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 25 May 2019 03:16:53 -0700 Subject: [PATCH 1408/1635] remove debug prints --- actix-http/CHANGES.md | 2 +- src/app_service.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index b3d8604c..041596c3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.2.1] - 2019-05-2 +## [0.2.1] - 2019-05-25 ### Fixed diff --git a/src/app_service.rs b/src/app_service.rs index 88e97de1..5a9731bf 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -239,7 +239,6 @@ where { fn drop(&mut self) { self.pool.clear(); - println!("DROP: APP-INIT-ENTRY"); } } @@ -436,7 +435,6 @@ mod tests { impl Drop for DropData { fn drop(&mut self) { self.0.store(true, Ordering::Relaxed); - println!("Dropping!"); } } From a614be7cb5c603523b0b509e2a30cde27bc58392 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 29 May 2019 18:37:42 +0200 Subject: [PATCH 1409/1635] Don't DISCONNECT from stream when reader is empty (#870) * Don't DISCONNECT from stream when reader is empty * Fix chunked transfer: poll_request before closing stream + Test --- actix-http/src/h1/dispatcher.rs | 22 +++++++------- actix-http/tests/test_server.rs | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 131811a9..b7b9db2d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -693,18 +693,20 @@ where } } else { // read socket into a buf - if !inner.flags.contains(Flags::READ_DISCONNECT) { - if let Some(true) = - read_available(&mut inner.io, &mut inner.read_buf)? - { - inner.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = inner.payload.take() { - payload.feed_eof(); - } - } - } + let should_disconnect = if !inner.flags.contains(Flags::READ_DISCONNECT) { + read_available(&mut inner.io, &mut inner.read_buf)? + } else { + None + }; inner.poll_request()?; + if let Some(true) = should_disconnect { + inner.flags.insert(Flags::READ_DISCONNECT); + if let Some(mut payload) = inner.payload.take() { + payload.feed_eof(); + } + }; + loop { if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE { inner.write_buf.reserve(HW_BUFFER_SIZE); diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index d0c5e352..a299f58d 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -9,6 +9,7 @@ use actix_service::{new_service_cfg, service_fn, NewService}; use bytes::{Bytes, BytesMut}; use futures::future::{self, ok, Future}; use futures::stream::{once, Stream}; +use regex::Regex; use tokio_timer::sleep; use actix_http::body::Body; @@ -215,6 +216,56 @@ fn test_expect_continue_h1() { assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); } +#[test] +fn test_chunked_payload() { + let chunk_sizes = vec![ 32768, 32, 32768 ]; + let total_size: usize = chunk_sizes.iter().sum(); + + let srv = TestServer::new(|| { + HttpService::build() + .h1(|mut request: Request| { + request.take_payload() + .map_err(|e| panic!(format!("Error reading payload: {}", e))) + .fold(0usize, |acc, chunk| { + future::ok::<_, ()>(acc + chunk.len()) + }) + .map(|req_size| { + Response::Ok().body(format!("size={}", req_size)) + }) + }) + }); + + let returned_size = { + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); + + for chunk_size in chunk_sizes.iter() { + let mut bytes = Vec::new(); + let random_bytes: Vec = (0..*chunk_size).map(|_| rand::random::()).collect(); + + bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes()); + bytes.extend(&random_bytes[..]); + bytes.extend(b"\r\n"); + let _ = stream.write_all(&bytes); + } + + let _ = stream.write_all(b"0\r\n\r\n"); + stream.shutdown(net::Shutdown::Write).unwrap(); + + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + + let re = Regex::new(r"size=(\d+)").unwrap(); + let size: usize = match re.captures(&data) { + Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), + None => panic!(format!("Failed to find size in HTTP Response: {}", data)), + }; + size + }; + + assert_eq!(returned_size, total_size); +} + #[test] fn test_slow_request() { let srv = TestServer::new(|| { From fe781345d5eb0c491039a4250a8a4862898a19b0 Mon Sep 17 00:00:00 2001 From: octave99 <36263355+octave99@users.noreply.github.com> Date: Wed, 29 May 2019 16:47:04 +0000 Subject: [PATCH 1410/1635] Add Migration steps for Custom Error (#869) Adds migration steps for custom error in 1.0 --- MIGRATION.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 73669ddb..1736ee65 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -238,6 +238,17 @@ * Actors support have been moved to `actix-web-actors` crate +* Custom Error + + Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller. + + Simplest migration from 0.7 to 1.0 shall include below method to the custom implementation of ResponseError: + + ```rust + fn render_response(&self) -> HttpResponse { + self.error_response() + } + ``` ## 0.7.15 From 21418c7414d007372615dcadd14bf17d93d5858a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 29 May 2019 16:15:12 -0700 Subject: [PATCH 1411/1635] prep actix-http release --- actix-http/CHANGES.md | 7 +++++++ actix-http/Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 041596c3..edc075b9 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.2.2] - 2019-05-29 + +### Fixed + +* Parse incoming stream before closing stream on disconnect #868 + + ## [0.2.1] - 2019-05-25 ### Fixed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b4bfabec..187d84da 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.1" +version = "0.2.2" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From c2d7db7e069568df0bb9c0fbf69d30e7dd875683 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 29 May 2019 16:22:57 -0700 Subject: [PATCH 1412/1635] prepare actix-web-actors release --- Cargo.toml | 4 ++-- actix-http/Cargo.toml | 2 +- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 8 ++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 815aeb5a..82927765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ actix-utils = "0.4.1" actix-router = "0.1.5" actix-rt = "0.2.2" actix-web-codegen = "0.1.0" -actix-http = "0.2.0" +actix-http = "0.2.2" actix-server = "0.5.1" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" @@ -100,7 +100,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.2.0", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.2.2", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } actix-files = { version = "0.1.0" } rand = "0.6" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 187d84da..6a020eae 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -81,7 +81,7 @@ time = "0.1.42" tokio-tcp = "0.1.3" tokio-timer = "0.2.8" tokio-current-thread = "0.1" -trust-dns-resolver = { version="0.11.0", default-features = false } +trust-dns-resolver = { version="0.11.1", default-features = false } # for secure cookie ring = { version = "0.14.6", optional = true } diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 34592aaf..89b4be81 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [1.0.0] - 2019-05-29 + +* Update actix-http and actix-web + ## [0.1.0-alpha.3] - 2019-04-02 * Update actix-http and actix-web diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 0341641a..565b53a5 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.0-beta.4" +version = "1.0.0" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -18,9 +18,9 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.8.2" -actix-web = "1.0.0-beta.5" -actix-http = "0.2.0" +actix = "0.8.3" +actix-web = "1.0.0-rc" +actix-http = "0.2.2" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" From f1764bba435e525df5cc06ea0c3972c7317e7078 Mon Sep 17 00:00:00 2001 From: Mohab Usama Date: Fri, 31 May 2019 10:09:21 +0200 Subject: [PATCH 1413/1635] Fix Logger time format (use rfc3339) (#867) * Fix Logger time format (use rfc3339) * Update change log --- CHANGES.md | 2 ++ src/middleware/logger.rs | 32 +++++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5974ee69..0dc7be27 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,8 @@ ### Fixed +* Fix Logger request time format, and use rfc3339. #867 + * Clear http requests pool on app service drop #860 diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 5d0b615e..d47e4502 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -53,7 +53,7 @@ use crate::HttpResponse; /// /// `%a` Remote IP-address (IP-address of proxy if using reverse proxy) /// -/// `%t` Time when the request was started to process +/// `%t` Time when the request was started to process (in rfc3339 format) /// /// `%r` First line of request /// @@ -417,10 +417,7 @@ impl FormatText { } FormatText::UrlPath => *self = FormatText::Str(format!("{}", req.path())), FormatText::RequestTime => { - *self = FormatText::Str(format!( - "{:?}", - now.strftime("[%d/%b/%Y:%H:%M:%S %z]").unwrap() - )) + *self = FormatText::Str(format!("{}", now.rfc3339())) } FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { @@ -547,4 +544,29 @@ mod tests { assert!(s.contains("200 1024")); assert!(s.contains("ACTIX-WEB")); } + + #[test] + fn test_request_time_format() { + let mut format = Format::new("%t"); + let req = TestRequest::default().to_srv_request(); + + let now = time::now(); + for unit in &mut format.0 { + unit.render_request(now, &req); + } + + let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); + for unit in &mut format.0 { + unit.render_response(&resp); + } + + let render = |fmt: &mut Formatter| { + for unit in &format.0 { + unit.render(fmt, 1024, now)?; + } + Ok(()) + }; + let s = format!("{}", FormatDisplay(&render)); + assert!(s.contains(&format!("{}", now.rfc3339()))); + } } From 7753b9da6dcb21ed7e7a56022869208447680319 Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Sat, 1 Jun 2019 10:13:45 +0200 Subject: [PATCH 1414/1635] web-codegen: Add extra-traits to syn features (#879) ```rust error[E0277]: `syn::attr::NestedMeta` doesn't implement `std::fmt::Debug` --> src/route.rs:149:57 | 149 | attr => panic!("Unknown attribute{:?}", attr), | ^^^^ `syn::attr::NestedMeta` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` | = help: the trait `std::fmt::Debug` is not implemented for `syn::attr::NestedMeta` = note: required because of the requirements on the impl of `std::fmt::Debug` for `&syn::attr::NestedMeta` = note: required by `std::fmt::Debug::fmt` ``` --- actix-web-codegen/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 5ca9f416..8b9f8d1b 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -13,7 +13,7 @@ proc-macro = true [dependencies] quote = "0.6" -syn = { version = "0.15", features = ["full", "parsing"] } +syn = { version = "0.15", features = ["full", "parsing", "extra-traits"] } [dev-dependencies] actix-web = { version = "1.0.0-beta.5" } From 29a0fe76d5d6b4ba778fe92f15e51be7e8edd117 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 1 Jun 2019 17:21:22 +0600 Subject: [PATCH 1415/1635] prepare actix-web-codegen release --- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 10 +++++----- actix-web-codegen/LICENSE-APACHE | 1 + actix-web-codegen/LICENSE-MIT | 1 + 4 files changed, 11 insertions(+), 5 deletions(-) create mode 120000 actix-web-codegen/LICENSE-APACHE create mode 120000 actix-web-codegen/LICENSE-MIT diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 4f2d1c86..ac186111 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.1] - 2019-06-01 + +* Add syn "extra-traits" feature + ## [0.1.0] - 2019-05-18 * Release diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 8b9f8d1b..5557441c 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.1.0" +version = "0.1.1" description = "Actix web proc macros" readme = "README.md" authors = ["Nikolay Kim "] @@ -12,11 +12,11 @@ workspace = ".." proc-macro = true [dependencies] -quote = "0.6" -syn = { version = "0.15", features = ["full", "parsing", "extra-traits"] } +quote = "0.6.12" +syn = { version = "0.15.34", features = ["full", "parsing", "extra-traits"] } [dev-dependencies] -actix-web = { version = "1.0.0-beta.5" } -actix-http = { version = "0.2.0", features=["ssl"] } +actix-web = { version = "1.0.0-rc" } +actix-http = { version = "0.2.2", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } futures = { version = "0.1" } diff --git a/actix-web-codegen/LICENSE-APACHE b/actix-web-codegen/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/actix-web-codegen/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-web-codegen/LICENSE-MIT b/actix-web-codegen/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/actix-web-codegen/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file From a1b40f431481e0f41a73f4808c33241708abeb9f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 1 Jun 2019 17:25:29 +0600 Subject: [PATCH 1416/1635] add license files --- Cargo.toml | 2 +- actix-files/LICENSE-APACHE | 1 + actix-files/LICENSE-MIT | 1 + actix-multipart/LICENSE-APACHE | 1 + actix-multipart/LICENSE-MIT | 1 + actix-session/LICENSE-APACHE | 1 + actix-session/LICENSE-MIT | 1 + actix-web-actors/LICENSE-APACHE | 1 + actix-web-actors/LICENSE-MIT | 1 + awc/LICENSE-APACHE | 1 + awc/LICENSE-MIT | 1 + test-server/LICENSE-APACHE | 1 + test-server/LICENSE-MIT | 1 + 13 files changed, 13 insertions(+), 1 deletion(-) create mode 120000 actix-files/LICENSE-APACHE create mode 120000 actix-files/LICENSE-MIT create mode 120000 actix-multipart/LICENSE-APACHE create mode 120000 actix-multipart/LICENSE-MIT create mode 120000 actix-session/LICENSE-APACHE create mode 120000 actix-session/LICENSE-MIT create mode 120000 actix-web-actors/LICENSE-APACHE create mode 120000 actix-web-actors/LICENSE-MIT create mode 120000 awc/LICENSE-APACHE create mode 120000 awc/LICENSE-MIT create mode 120000 test-server/LICENSE-APACHE create mode 120000 test-server/LICENSE-MIT diff --git a/Cargo.toml b/Cargo.toml index 82927765..e8fdb136 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ actix-service = "0.4.0" actix-utils = "0.4.1" actix-router = "0.1.5" actix-rt = "0.2.2" -actix-web-codegen = "0.1.0" +actix-web-codegen = "0.1.1" actix-http = "0.2.2" actix-server = "0.5.1" actix-server-config = "0.1.1" diff --git a/actix-files/LICENSE-APACHE b/actix-files/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/actix-files/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-files/LICENSE-MIT b/actix-files/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/actix-files/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-multipart/LICENSE-APACHE b/actix-multipart/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/actix-multipart/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-multipart/LICENSE-MIT b/actix-multipart/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/actix-multipart/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-session/LICENSE-APACHE b/actix-session/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/actix-session/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-session/LICENSE-MIT b/actix-session/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/actix-session/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-web-actors/LICENSE-APACHE b/actix-web-actors/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/actix-web-actors/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-web-actors/LICENSE-MIT b/actix-web-actors/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/actix-web-actors/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/awc/LICENSE-APACHE b/awc/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/awc/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/awc/LICENSE-MIT b/awc/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/awc/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/test-server/LICENSE-APACHE b/test-server/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/test-server/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/test-server/LICENSE-MIT b/test-server/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/test-server/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file From 666756bfbe1a6ed5e17cee567320f603af7b3cf9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 1 Jun 2019 17:57:25 +0600 Subject: [PATCH 1417/1635] body helpers --- actix-http/CHANGES.md | 10 +++++++++ actix-http/src/body.rs | 25 +++++++++++++++++++--- actix-http/src/response.rs | 19 +++++++++++++++++ actix-http/tests/test_server.rs | 37 +++++++++++++++------------------ 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index edc075b9..e677c3fa 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,15 @@ # Changes +### Added + +* Debug impl for ResponseBuilder + +* From SizedStream and BodyStream for Body + +### Changed + +* SizedStream accepts u64 + ## [0.2.2] - 2019-05-29 ### Fixed diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 0652dd27..e728cdb9 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -234,6 +234,25 @@ impl From for Body { } } +impl From> for Body +where + S: Stream + 'static, +{ + fn from(s: SizedStream) -> Body { + Body::from_message(s) + } +} + +impl From> for Body +where + S: Stream + 'static, + E: Into + 'static, +{ + fn from(s: BodyStream) -> Body { + Body::from_message(s) + } +} + impl MessageBody for Bytes { fn size(&self) -> BodySize { BodySize::Sized(self.len()) @@ -366,7 +385,7 @@ where /// Type represent streaming body. This body implementation should be used /// if total size of stream is known. Data get sent as is without using transfer encoding. pub struct SizedStream { - size: usize, + size: u64, stream: S, } @@ -374,7 +393,7 @@ impl SizedStream where S: Stream, { - pub fn new(size: usize, stream: S) -> Self { + pub fn new(size: u64, stream: S) -> Self { SizedStream { size, stream } } } @@ -384,7 +403,7 @@ where S: Stream, { fn size(&self) -> BodySize { - BodySize::Sized(self.size) + BodySize::Sized64(self.size) } fn poll_next(&mut self) -> Poll, Error> { diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index fd51e54c..ce986a47 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -764,6 +764,25 @@ impl IntoFuture for ResponseBuilder { } } +impl fmt::Debug for ResponseBuilder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let head = self.head.as_ref().unwrap(); + + let res = writeln!( + f, + "\nResponseBuilder {:?} {}{}", + head.version, + head.status, + head.reason.unwrap_or(""), + ); + let _ = writeln!(f, " headers:"); + for (key, val) in head.headers.iter() { + let _ = writeln!(f, " {:?}: {:?}", key, val); + } + res + } +} + /// Helper converters impl, E: Into> From> for Response { fn from(res: Result) -> Self { diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index a299f58d..4a679f4b 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -12,7 +12,6 @@ use futures::stream::{once, Stream}; use regex::Regex; use tokio_timer::sleep; -use actix_http::body::Body; use actix_http::error::PayloadError; use actix_http::{ body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, @@ -218,30 +217,28 @@ fn test_expect_continue_h1() { #[test] fn test_chunked_payload() { - let chunk_sizes = vec![ 32768, 32, 32768 ]; + let chunk_sizes = vec![32768, 32, 32768]; let total_size: usize = chunk_sizes.iter().sum(); let srv = TestServer::new(|| { - HttpService::build() - .h1(|mut request: Request| { - request.take_payload() - .map_err(|e| panic!(format!("Error reading payload: {}", e))) - .fold(0usize, |acc, chunk| { - future::ok::<_, ()>(acc + chunk.len()) - }) - .map(|req_size| { - Response::Ok().body(format!("size={}", req_size)) - }) - }) + HttpService::build().h1(|mut request: Request| { + request + .take_payload() + .map_err(|e| panic!(format!("Error reading payload: {}", e))) + .fold(0usize, |acc, chunk| future::ok::<_, ()>(acc + chunk.len())) + .map(|req_size| Response::Ok().body(format!("size={}", req_size))) + }) }); let returned_size = { let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); + let _ = stream + .write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); for chunk_size in chunk_sizes.iter() { let mut bytes = Vec::new(); - let random_bytes: Vec = (0..*chunk_size).map(|_| rand::random::()).collect(); + let random_bytes: Vec = + (0..*chunk_size).map(|_| rand::random::()).collect(); bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes()); bytes.extend(&random_bytes[..]); @@ -826,8 +823,7 @@ fn test_h1_body_length() { HttpService::build().h1(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( - Response::Ok() - .body(Body::from_message(body::SizedStream::new(STR.len(), body))), + Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), ) }) }); @@ -852,9 +848,10 @@ fn test_h2_body_length() { HttpService::build() .h2(|_| { let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().body(Body::from_message( - body::SizedStream::new(STR.len(), body), - ))) + ok::<_, ()>( + Response::Ok() + .body(body::SizedStream::new(STR.len() as u64, body)), + ) }) .map_err(|_| ()), ) From 15cdc680f6f1b1ba6454974838f6022e7e20b3e3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 1 Jun 2019 17:57:40 +0600 Subject: [PATCH 1418/1635] Static files are incorrectly served as both chunked and with length #812 --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 1 + actix-files/src/named.rs | 3 ++- src/lib.rs | 4 ++-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 021f8dc6..c7749457 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.1] - 2019-06-01 + +* Static files are incorrectly served as both chunked and with length #812 + ## [0.1.0] - 2019-05-25 * NamedFile last-modified check always fails due to nano-seconds diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 9ffbf0e7..fa48fff0 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,6 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = "1.0.0-rc" +actix-http = "0.2.2" actix-service = "0.4.0" bitflags = "1" bytes = "0.4" diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 2298e35a..29e9eee4 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -11,6 +11,7 @@ use bitflags::bitflags; use mime; use mime_guess::guess_mime_type; +use actix_http::body::SizedStream; use actix_web::http::header::{ self, ContentDisposition, DispositionParam, DispositionType, }; @@ -434,7 +435,7 @@ impl Responder for NamedFile { if offset != 0 || length != self.md.len() { return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); }; - Ok(resp.streaming(reader)) + Ok(resp.body(SizedStream::new(length, reader))) } } } diff --git a/src/lib.rs b/src/lib.rs index f84dbd5b..0e4a421e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,7 +111,7 @@ pub use actix_web_codegen::*; // re-export for convenience pub use actix_http::Response as HttpResponse; -pub use actix_http::{cookie, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{body, cookie, http, Error, HttpMessage, ResponseError, Result}; pub use crate::app::App; pub use crate::extract::FromRequest; @@ -143,7 +143,7 @@ pub mod dev { pub use crate::types::json::JsonBody; pub use crate::types::readlines::Readlines; - pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody}; + pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ From 24180f9014e8e041e2e4d711c4f92a16be1751bf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 2 Jun 2019 12:58:37 +0600 Subject: [PATCH 1419/1635] Fix boundary parsing #876 --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 4 ++-- actix-multipart/src/server.rs | 4 +--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index cf32859e..751bc126 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.2] - 2019-06-02 + +* Fix boundary parsing #876 + ## [0.1.1] - 2019-05-25 * Fix disconnect handling #834 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index fe63d536..9bb1179d 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.1" +version = "0.1.2" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" @@ -31,4 +31,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "0.2.2" -actix-http = "0.2.0" \ No newline at end of file +actix-http = "0.2.2" \ No newline at end of file diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 8245d241..e1a5543d 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -537,8 +537,6 @@ impl InnerField { if &payload.buf[b_len..b_size] == boundary.as_bytes() { // found boundary return Ok(Async::Ready(None)); - } else { - pos = b_size; } } } @@ -576,7 +574,7 @@ impl InnerField { } } } else { - return Ok(Async::Ready(Some(payload.buf.take().freeze()))); + Ok(Async::Ready(Some(payload.buf.take().freeze()))) }; } } From b1cfbdcf7a4f391ec93aabdd2f81fd24b87517bb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 2 Jun 2019 13:05:22 +0600 Subject: [PATCH 1420/1635] prepare actix-http release --- actix-http/CHANGES.md | 5 ++++- actix-http/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e677c3fa..d0c75da7 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,7 @@ # Changes +## [0.2.3] - 2019-06-02 + ### Added * Debug impl for ResponseBuilder @@ -8,7 +10,8 @@ ### Changed -* SizedStream accepts u64 +* SizedStream uses u64 + ## [0.2.2] - 2019-05-29 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6a020eae..1847a5ba 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.2" +version = "0.2.3" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From 6d2e190c8e6f5f8304cfc08e4fbc7d07567b7dfa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 2 Jun 2019 13:09:21 +0600 Subject: [PATCH 1421/1635] prepare actix-files release --- actix-files/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index fa48fff0..9d6b0f48 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = "1.0.0-rc" -actix-http = "0.2.2" +actix-http = "0.2.3" actix-service = "0.4.0" bitflags = "1" bytes = "0.4" From a780ea10e9f225b4620b2e7e8d72c553c1e45554 Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Mon, 3 Jun 2019 06:30:30 +0200 Subject: [PATCH 1422/1635] Guard cookie mod by cookie-session feature (#883) Signed-off-by: Igor Gnatenko --- actix-session/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 0e783144..fb316f39 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -52,7 +52,9 @@ use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; +#[cfg(feature = "cookie-session")] mod cookie; +#[cfg(feature = "cookie-session")] pub use crate::cookie::CookieSession; /// The high-level interface you use to modify session data. From 4a179d1ae12d39b409a48d45b5de6b6f10db8474 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 3 Jun 2019 10:52:43 +0600 Subject: [PATCH 1423/1635] prepare actix-session release --- actix-session/CHANGES.md | 4 ++++ actix-session/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 10727ae3..10aea870 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.1] - 2019-06-03 + +* Fix optional cookie session support + ## [0.1.0] - 2019-05-18 * Use actix-web 1.0.0-rc diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index b0ef1e2b..1101ceff 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.0" +version = "0.1.1" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" From 1fce4876f38b9f2ab276bac2c4be7a0762ce8ac2 Mon Sep 17 00:00:00 2001 From: Denys Vitali Date: Mon, 3 Jun 2019 19:12:37 +0200 Subject: [PATCH 1424/1635] Scope configuration (#880) * WIP: Scope configuarion * Extensions: Fix into_iter() * Scope: Fix tests * Add ScopeConfig to web Committing from mobile, if this doesn't look good it's because I haven't tested it... * Scope Config: Use ServiceConfig instead * Scope: Switch to ServiceConfig in doc * ScopeConfig: Remove unnecessary changes, handle the case when data is empty * ScopeConfig: Remove changes from actix-http --- src/config.rs | 2 +- src/scope.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index bc33da9d..8de43f36 100644 --- a/src/config.rs +++ b/src/config.rs @@ -188,7 +188,7 @@ impl ServiceConfig { } } - /// Set application data. Applicatin data could be accessed + /// Set application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. /// /// This is same as `App::data()` method. diff --git a/src/scope.rs b/src/scope.rs index 84f34dae..e9b60c6e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -21,6 +21,7 @@ use crate::route::Route; use crate::service::{ ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; +use crate::config::ServiceConfig; type Guards = Vec>; type HttpService = BoxedService; @@ -83,6 +84,56 @@ impl Scope { factory_ref: fref, } } + + + /// Run external configuration as part of the scope building + /// process + /// + /// This function is useful for moving parts of configuration to a + /// different module or even library. For example, + /// some of the resource's configuration could be moved to different module. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, middleware, App, HttpResponse}; + /// + /// // this function could be located in different module + /// fn config(cfg: &mut web::ServiceConfig) { + /// cfg.service(web::resource("/test") + /// .route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .service( + /// web::scope("/api") + /// .configure(config) + /// ) + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); + /// } + /// ``` + pub fn configure(mut self, f: F) -> Self + where + F: FnOnce(&mut ServiceConfig), + { + let mut cfg = ServiceConfig::new(); + f(&mut cfg); + self.services.extend(cfg.services); + + if !cfg.data.is_empty() { + let mut data = self.data.unwrap_or(Extensions::new()); + + for value in cfg.data.iter() { + value.create(&mut data); + } + + self.data = Some(data); + } + self + } } impl Scope @@ -1022,4 +1073,40 @@ mod tests { let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); } + + #[test] + fn test_scope_config() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .configure(|s|{ + s.route("/path1", web::get().to(||HttpResponse::Ok())); + }) + ), + ); + + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[test] + fn test_scope_config_2() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .configure(|s|{ + s.service( + web::scope("/v1") + .configure(|s|{ + s.route("/", web::get().to(||HttpResponse::Ok())); + })); + }) + ), + ); + + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } } From 0e138e111f439ee447a57ef5f1a19203520691d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 3 Jun 2019 23:41:32 +0600 Subject: [PATCH 1425/1635] add external resource support on scope level --- CHANGES.md | 4 +- Cargo.toml | 2 +- src/rmap.rs | 4 +- src/scope.rs | 170 +++++++++++++++++++++++++++++---------------------- 4 files changed, 105 insertions(+), 75 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0dc7be27..b5078d53 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,11 @@ # Changes -## [1.0.0] - 2019-05-xx +## [1.0.0] - 2019-06-xx ### Add +* Add `Scope::configure()` method. + * Add `ServiceRequest::set_payload()` method. * Add `test::TestRequest::set_json()` convenience method to automatically diff --git a/Cargo.toml b/Cargo.toml index e8fdb136..e689fab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ rustls = { version = "0.15", optional = true } [dev-dependencies] actix-http = { version = "0.2.2", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } -actix-files = { version = "0.1.0" } +actix-files = { version = "0.1.1" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/src/rmap.rs b/src/rmap.rs index 35fe8ee3..6a543b75 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -122,7 +122,9 @@ impl ResourceMap { I: AsRef, { if let Some(pattern) = self.named.get(name) { - self.fill_root(path, elements)?; + if pattern.pattern().starts_with("/") { + self.fill_root(path, elements)?; + } if pattern.resource_path(path, elements) { Ok(Some(())) } else { diff --git a/src/scope.rs b/src/scope.rs index e9b60c6e..ad97fcb6 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -11,6 +11,7 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, IntoFuture, Poll}; +use crate::config::ServiceConfig; use crate::data::Data; use crate::dev::{AppService, HttpServiceFactory}; use crate::error::Error; @@ -21,7 +22,6 @@ use crate::route::Route; use crate::service::{ ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; -use crate::config::ServiceConfig; type Guards = Vec>; type HttpService = BoxedService; @@ -67,6 +67,7 @@ pub struct Scope { services: Vec>, guards: Vec>, default: Rc>>>, + external: Vec, factory_ref: Rc>>, } @@ -81,59 +82,10 @@ impl Scope { guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), + external: Vec::new(), factory_ref: fref, } } - - - /// Run external configuration as part of the scope building - /// process - /// - /// This function is useful for moving parts of configuration to a - /// different module or even library. For example, - /// some of the resource's configuration could be moved to different module. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, middleware, App, HttpResponse}; - /// - /// // this function could be located in different module - /// fn config(cfg: &mut web::ServiceConfig) { - /// cfg.service(web::resource("/test") - /// .route(web::get().to(|| HttpResponse::Ok())) - /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } - /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .service( - /// web::scope("/api") - /// .configure(config) - /// ) - /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); - /// } - /// ``` - pub fn configure(mut self, f: F) -> Self - where - F: FnOnce(&mut ServiceConfig), - { - let mut cfg = ServiceConfig::new(); - f(&mut cfg); - self.services.extend(cfg.services); - - if !cfg.data.is_empty() { - let mut data = self.data.unwrap_or(Extensions::new()); - - for value in cfg.data.iter() { - value.create(&mut data); - } - - self.data = Some(data); - } - self - } } impl Scope @@ -204,6 +156,56 @@ where self } + /// Run external configuration as part of the scope building + /// process + /// + /// This function is useful for moving parts of configuration to a + /// different module or even library. For example, + /// some of the resource's configuration could be moved to different module. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, middleware, App, HttpResponse}; + /// + /// // this function could be located in different module + /// fn config(cfg: &mut web::ServiceConfig) { + /// cfg.service(web::resource("/test") + /// .route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap(middleware::Logger::default()) + /// .service( + /// web::scope("/api") + /// .configure(config) + /// ) + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); + /// } + /// ``` + pub fn configure(mut self, f: F) -> Self + where + F: FnOnce(&mut ServiceConfig), + { + let mut cfg = ServiceConfig::new(); + f(&mut cfg); + self.services.extend(cfg.services); + self.external.extend(cfg.external); + + if !cfg.data.is_empty() { + let mut data = self.data.unwrap_or_else(|| Extensions::new()); + + for value in cfg.data.iter() { + value.create(&mut data); + } + + self.data = Some(data); + } + self + } + /// Register http service. /// /// This is similar to `App's` service registration. @@ -332,6 +334,7 @@ where guards: self.guards, services: self.services, default: self.default, + external: self.external, factory_ref: self.factory_ref, } } @@ -410,6 +413,11 @@ where let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); + // external resources + for mut rdef in std::mem::replace(&mut self.external, Vec::new()) { + rmap.add(&mut rdef, None); + } + // custom app data storage if let Some(ref mut ext) = self.data { config.set_service_data(ext); @@ -645,7 +653,7 @@ mod tests { use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_service, init_service, TestRequest}; + use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; #[test] @@ -1076,14 +1084,10 @@ mod tests { #[test] fn test_scope_config() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .configure(|s|{ - s.route("/path1", web::get().to(||HttpResponse::Ok())); - }) - ), - ); + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.route("/path1", web::get().to(|| HttpResponse::Ok())); + }))); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -1092,21 +1096,43 @@ mod tests { #[test] fn test_scope_config_2() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .configure(|s|{ - s.service( - web::scope("/v1") - .configure(|s|{ - s.route("/", web::get().to(||HttpResponse::Ok())); - })); - }) - ), - ); + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.route("/", web::get().to(|| HttpResponse::Ok())); + })); + }))); let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); } + + #[test] + fn test_url_for_external() { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + s.route( + "/", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["xxxxxx"]).unwrap().as_str() + )) + }), + ); + })); + }))); + + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp); + assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); + } } From cf217d35a895a2ed98c4f5fcb8ddd9fd58596c5a Mon Sep 17 00:00:00 2001 From: Glade Miller Date: Tue, 4 Jun 2019 10:30:43 -0600 Subject: [PATCH 1426/1635] Added HEAD, CONNECT, OPTIONS and TRACE to the codegen (#886) * Added HEAD, CONNECT, OPTIONS and TRACE to the codegen * Add new macros to use statement * Add patch to supported codegen http methods * Update CHANGES.md Added head, options, trace, connect and patch codegen changes to CHANGES.md --- CHANGES.md | 2 + actix-web-codegen/src/lib.rs | 65 +++++++++++++++++++++++++++ actix-web-codegen/src/route.rs | 10 +++++ actix-web-codegen/tests/test_macro.rs | 52 ++++++++++++++++++++- 4 files changed, 128 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b5078d53..1ed434b7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ * Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. + +* Add codegen now supports head, options, trace, connect and patch http methods ### Changes diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 70cde90e..99abbb6a 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -11,6 +11,11 @@ //! - [post](attr.post.html) //! - [put](attr.put.html) //! - [delete](attr.delete.html) +//! - [head](attr.head.html) +//! - [connect](attr.connect.html) +//! - [options](attr.options.html) +//! - [trace](attr.trace.html) +//! - [patch](attr.patch.html) //! //! ### Attributes: //! @@ -92,3 +97,63 @@ pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { let gen = route::Args::new(&args, input, route::GuardType::Delete); gen.generate() } + +/// Creates route handler with `HEAD` method guard. +/// +/// Syntax: `#[head("path"[, attributes])]` +/// +/// Attributes are the same as in [head](attr.head.html) +#[proc_macro_attribute] +pub fn head(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + let gen = route::Args::new(&args, input, route::GuardType::Head); + gen.generate() +} + +/// Creates route handler with `CONNECT` method guard. +/// +/// Syntax: `#[connect("path"[, attributes])]` +/// +/// Attributes are the same as in [connect](attr.connect.html) +#[proc_macro_attribute] +pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + let gen = route::Args::new(&args, input, route::GuardType::Connect); + gen.generate() +} + +/// Creates route handler with `OPTIONS` method guard. +/// +/// Syntax: `#[options("path"[, attributes])]` +/// +/// Attributes are the same as in [options](attr.options.html) +#[proc_macro_attribute] +pub fn options(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + let gen = route::Args::new(&args, input, route::GuardType::Options); + gen.generate() +} + +/// Creates route handler with `TRACE` method guard. +/// +/// Syntax: `#[trace("path"[, attributes])]` +/// +/// Attributes are the same as in [trace](attr.trace.html) +#[proc_macro_attribute] +pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + let gen = route::Args::new(&args, input, route::GuardType::Trace); + gen.generate() +} + +/// Creates route handler with `PATCH` method guard. +/// +/// Syntax: `#[patch("path"[, attributes])]` +/// +/// Attributes are the same as in [patch](attr.patch.html) +#[proc_macro_attribute] +pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as syn::AttributeArgs); + let gen = route::Args::new(&args, input, route::GuardType::Patch); + gen.generate() +} \ No newline at end of file diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 1a5f7929..3b890c1c 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -25,6 +25,11 @@ pub enum GuardType { Post, Put, Delete, + Head, + Connect, + Options, + Trace, + Patch } impl fmt::Display for GuardType { @@ -34,6 +39,11 @@ impl fmt::Display for GuardType { &GuardType::Post => write!(f, "Post"), &GuardType::Put => write!(f, "Put"), &GuardType::Delete => write!(f, "Delete"), + &GuardType::Head => write!(f, "Head"), + &GuardType::Connect => write!(f, "Connect"), + &GuardType::Options => write!(f, "Options"), + &GuardType::Trace => write!(f, "Trace"), + &GuardType::Patch => write!(f, "Patch"), } } } diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index cd899d48..71872887 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,7 +1,7 @@ use actix_http::HttpService; use actix_http_test::TestServer; use actix_web::{http, web::Path, App, HttpResponse, Responder}; -use actix_web_codegen::{delete, get, post, put}; +use actix_web_codegen::{delete, get, post, put, patch, head, connect, options, trace}; use futures::{future, Future}; #[get("/test")] @@ -14,11 +14,36 @@ fn put_test() -> impl Responder { HttpResponse::Created() } +#[patch("/test")] +fn patch_test() -> impl Responder { + HttpResponse::Ok() +} + #[post("/test")] fn post_test() -> impl Responder { HttpResponse::NoContent() } +#[head("/test")] +fn head_test() -> impl Responder { + HttpResponse::Ok() +} + +#[connect("/test")] +fn connect_test() -> impl Responder { + HttpResponse::Ok() +} + +#[options("/test")] +fn options_test() -> impl Responder { + HttpResponse::Ok() +} + +#[trace("/test")] +fn trace_test() -> impl Responder { + HttpResponse::Ok() +} + #[get("/test")] fn auto_async() -> impl Future { future::ok(HttpResponse::Ok().finish()) @@ -75,6 +100,11 @@ fn test_body() { App::new() .service(post_test) .service(put_test) + .service(head_test) + .service(connect_test) + .service(options_test) + .service(trace_test) + .service(patch_test) .service(test), ) }); @@ -82,6 +112,26 @@ fn test_body() { let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); + let request = srv.request(http::Method::HEAD, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::CONNECT, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::OPTIONS, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::TRACE, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + + let request = srv.request(http::Method::PATCH, srv.url("/test")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + let request = srv.request(http::Method::PUT, srv.url("/test")); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); From a771540b16d9441a3a69f7e39fb61d26fc1698ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 4 Jun 2019 22:33:43 +0600 Subject: [PATCH 1427/1635] prepare actix-web-codegen release --- CHANGES.md | 4 ++-- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1ed434b7..09c39877 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,8 +10,8 @@ * Add `test::TestRequest::set_json()` convenience method to automatically serialize data and set header in test requests. - -* Add codegen now supports head, options, trace, connect and patch http methods + +* Add macros for head, options, trace, connect and patch http methods ### Changes diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index ac186111..7cc0c164 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.2] - 2019-06-04 + +* Add macros for head, options, trace, connect and patch http methods + ## [0.1.1] - 2019-06-01 * Add syn "extra-traits" feature diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 5557441c..23e9a432 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.1.1" +version = "0.1.2" description = "Actix web proc macros" readme = "README.md" authors = ["Nikolay Kim "] From 38f04b75a798434eb64ec0df27595fcfac4a43a7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 4 Jun 2019 22:36:10 +0600 Subject: [PATCH 1428/1635] update deps --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e689fab0..f1890765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,8 +72,8 @@ actix-service = "0.4.0" actix-utils = "0.4.1" actix-router = "0.1.5" actix-rt = "0.2.2" -actix-web-codegen = "0.1.1" -actix-http = "0.2.2" +actix-web-codegen = "0.1.2" +actix-http = "0.2.3" actix-server = "0.5.1" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" @@ -100,7 +100,7 @@ openssl = { version="0.10", optional = true } rustls = { version = "0.15", optional = true } [dev-dependencies] -actix-http = { version = "0.2.2", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.2.3", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } actix-files = { version = "0.1.1" } rand = "0.6" From a342b1289dfa163bd09da4c30c79154c05c9e74d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 5 Jun 2019 08:13:43 +0600 Subject: [PATCH 1429/1635] prep awc release --- Cargo.toml | 2 +- awc/CHANGES.md | 6 ++++++ awc/Cargo.toml | 12 ++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f1890765..cd0e9458 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ actix-web-codegen = "0.1.2" actix-http = "0.2.3" actix-server = "0.5.1" actix-server-config = "0.1.1" -actix-threadpool = "0.1.0" +actix-threadpool = "0.1.1" awc = { version = "0.2.0", optional = true } bytes = "0.4" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index f36b0bb3..b5f64dd3 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.2.1] - 2019-06-05 + +### Added + +* Add license files + ## [0.2.0] - 2019-05-12 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 2112185c..cad52033 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.0" +version = "0.2.1" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -41,7 +41,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.0" -actix-http = "0.2.0" +actix-http = "0.2.3" base64 = "0.10.1" bytes = "0.4" derive_more = "0.14" @@ -58,11 +58,11 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-beta.4", features=["ssl"] } -actix-http = { version = "0.2.0", features=["ssl"] } +actix-web = { version = "1.0.0-rc", features=["ssl"] } +actix-http = { version = "0.2.3", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } -actix-utils = "0.4.0" -actix-server = { version = "0.5.0", features=["ssl"] } +actix-utils = "0.4.1" +actix-server = { version = "0.5.1", features=["ssl"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" From ae64475d988b7831464dfb9cbfc8d5957e990f1f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 5 Jun 2019 08:27:25 +0600 Subject: [PATCH 1430/1635] test-server release --- CHANGES.md | 2 +- test-server/CHANGES.md | 4 ++++ test-server/Cargo.toml | 14 +++++++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 09c39877..07991142 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.0] - 2019-06-xx +## [1.0.0] - 2019-06-05 ### Add diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 8704a64c..a3193790 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.2.1] - 2019-06-05 + +* Add license files + ## [0.2.0] - 2019-05-12 * Update awc and actix-http deps diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 8567b745..37e2b044 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.2.0" +version = "0.2.1" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -32,10 +32,10 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.2" actix-rt = "0.2.2" -actix-service = "0.4.0" -actix-server = "0.5.0" -actix-utils = "0.4.0" -awc = "0.2.0" +actix-service = "0.4.1" +actix-server = "0.5.1" +actix-utils = "0.4.1" +awc = "0.2.1" base64 = "0.10" bytes = "0.4" @@ -55,5 +55,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-beta.4" -actix-http = "0.2.0" +actix-web = "1.0.0-rc" +actix-http = "0.2.3" From a548b69679ebec4be9bb280e9e1fd88707d6790d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 5 Jun 2019 08:43:13 +0600 Subject: [PATCH 1431/1635] fmt --- actix-http/src/h1/dispatcher.rs | 11 ++++++----- actix-web-codegen/src/lib.rs | 2 +- actix-web-codegen/src/route.rs | 2 +- actix-web-codegen/tests/test_macro.rs | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index b7b9db2d..220984f8 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -693,11 +693,12 @@ where } } else { // read socket into a buf - let should_disconnect = if !inner.flags.contains(Flags::READ_DISCONNECT) { - read_available(&mut inner.io, &mut inner.read_buf)? - } else { - None - }; + let should_disconnect = + if !inner.flags.contains(Flags::READ_DISCONNECT) { + read_available(&mut inner.io, &mut inner.read_buf)? + } else { + None + }; inner.poll_request()?; if let Some(true) = should_disconnect { diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 99abbb6a..b3ae7dd9 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -156,4 +156,4 @@ pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); let gen = route::Args::new(&args, input, route::GuardType::Patch); gen.generate() -} \ No newline at end of file +} diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 3b890c1c..268adecb 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -29,7 +29,7 @@ pub enum GuardType { Connect, Options, Trace, - Patch + Patch, } impl fmt::Display for GuardType { diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 71872887..f02b82f0 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,7 +1,7 @@ use actix_http::HttpService; use actix_http_test::TestServer; use actix_web::{http, web::Path, App, HttpResponse, Responder}; -use actix_web_codegen::{delete, get, post, put, patch, head, connect, options, trace}; +use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, trace}; use futures::{future, Future}; #[get("/test")] From d9a62c4bbf9091a39aa5df05ae08a6a0a8e149ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 5 Jun 2019 08:43:39 +0600 Subject: [PATCH 1432/1635] add App::register_data() --- src/app.rs | 7 +++++++ src/data.rs | 23 ++++++++++++++++++++++- test-server/Cargo.toml | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1568d5fc..4f8b283e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -100,6 +100,13 @@ where self } + /// Set application data. Application data could be accessed + /// by using `Data` extractor where `T` is data type. + pub fn register_data(mut self, data: Data) -> Self { + self.data.push(Box::new(data)); + self + } + /// Run external configuration as part of the application building /// process /// diff --git a/src/data.rs b/src/data.rs index 1328c4ef..9fd8b67f 100644 --- a/src/data.rs +++ b/src/data.rs @@ -54,7 +54,7 @@ pub(crate) trait DataFactory { /// /// let app = App::new() /// // Store `MyData` in application storage. -/// .data(data.clone()) +/// .register_data(data.clone()) /// .service( /// web::resource("/index.html").route( /// web::get().to(index))); @@ -130,6 +130,7 @@ impl DataFactory for Data { mod tests { use actix_service::Service; + use super::*; use crate::http::StatusCode; use crate::test::{block_on, init_service, TestRequest}; use crate::{web, App, HttpResponse}; @@ -154,6 +155,26 @@ mod tests { assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + #[test] + fn test_register_data_extractor() { + let mut srv = + init_service(App::new().register_data(Data::new(10usize)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let mut srv = + init_service(App::new().register_data(Data::new(10u32)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } + #[test] fn test_route_data_extractor() { let mut srv = diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 37e2b044..a8f4425b 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -32,7 +32,7 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.2" actix-rt = "0.2.2" -actix-service = "0.4.1" +actix-service = "0.4.0" actix-server = "0.5.1" actix-utils = "0.4.1" awc = "0.2.1" From e399e01a22b8a848ecbbc21c362878cd59a4342e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 5 Jun 2019 09:02:44 +0600 Subject: [PATCH 1433/1635] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fc8f78b8..cae737b6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * Multipart streams * Static assets * SSL support with OpenSSL or Rustls -* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/)) +* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Supports [Actix actor framework](https://github.com/actix/actix) @@ -22,7 +22,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.32 or later +* Minimum supported Rust version: 1.34 or later ## Example From 53e2f8090f3afbb5ae21ff6c89f8e9cb33668c93 Mon Sep 17 00:00:00 2001 From: Stefano Probst Date: Thu, 6 Jun 2019 07:14:56 +0200 Subject: [PATCH 1434/1635] Mark default enabled package features in the docs (#890) --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e4a421e..f0bf01bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,15 +66,15 @@ //! //! ## Package feature //! -//! * `client` - enables http client +//! * `client` - enables http client (default enabled) //! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` //! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` //! * `secure-cookies` - enables secure cookies support, includes `ring` crate as -//! dependency +//! dependency (default enabled) //! * `brotli` - enables `brotli` compression support, requires `c` -//! compiler +//! compiler (default enabled) //! * `flate2-zlib` - enables `gzip`, `deflate` compression support, requires -//! `c` compiler +//! `c` compiler (default enabled) //! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. //! From bfbac4f875f656dc83c3227d4fb85a4e5887751a Mon Sep 17 00:00:00 2001 From: simlay Date: Thu, 6 Jun 2019 20:34:30 -0700 Subject: [PATCH 1435/1635] Upgraded actix-web dependency and set default-features to false (#900) --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c7749457..862a5944 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.2] - 2019-06-06 + +* Fix ring dependency from actix-web default features for #741. + ## [0.1.1] - 2019-06-01 * Static files are incorrectly served as both chunked and with length #812 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 9d6b0f48..b1428a22 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.1" +version = "0.1.2" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-rc" +actix-web = { version = "1.0.0", default-features = false } actix-http = "0.2.3" actix-service = "0.4.0" bitflags = "1" @@ -32,4 +32,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0-rc", features=["ssl"] } +actix-web = { version = "1.0.0", features=["ssl"] } From c4b7980b4f4e31a4f881c4ff812873661c4a812c Mon Sep 17 00:00:00 2001 From: simlay Date: Thu, 6 Jun 2019 20:34:56 -0700 Subject: [PATCH 1436/1635] Upgraded actix-web dependency and set default-features to false (#895) --- actix-multipart/CHANGES.md | 4 ++++ actix-multipart/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 751bc126..b0d8f285 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.3] - 2019-06-06 + +* Fix ring dependency from actix-web default features for #741. + ## [0.1.2] - 2019-06-02 * Fix boundary parsing #876 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 9bb1179d..d377be1f 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.2" +version = "0.1.3" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0-rc" +actix-web = { version = "1.0.0", default-features = false } actix-service = "0.4.0" bytes = "0.4" derive_more = "0.14" From ee769832cf5b8dfafbd2543bdb46c949c670f05c Mon Sep 17 00:00:00 2001 From: Bob Date: Wed, 12 Jun 2019 11:26:46 +0800 Subject: [PATCH 1437/1635] get_identity from HttpMessage (#908) * get_identity from HttpMessage * more doc for RequestIdentity --- CHANGES.md | 11 +++++++++++ src/middleware/identity.rs | 34 ++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 07991142..3e926525 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # Changes +## [1.0.x] - 2019-xx-xx + +### Add + +* Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. + +### Changes + +### Fixed + + ## [1.0.0] - 2019-06-05 ### Add diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 82ae0154..304df9eb 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -61,7 +61,10 @@ use crate::cookie::{Cookie, CookieJar, Key, SameSite}; use crate::error::{Error, Result}; use crate::http::header::{self, HeaderValue}; use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{dev::Payload, FromRequest, HttpMessage, HttpRequest}; +use crate::{ + dev::{Extensions, Payload}, + FromRequest, HttpMessage, HttpRequest, +}; /// The extractor type to obtain your identity from a request. /// @@ -96,11 +99,7 @@ impl Identity { /// Return the claimed identity of the user associated request or /// ``None`` if no identity can be found associated with the request. pub fn identity(&self) -> Option { - if let Some(id) = self.0.extensions().get::() { - id.id.clone() - } else { - None - } + Identity::get_identity(&self.0.extensions()) } /// Remember identity. @@ -119,6 +118,14 @@ impl Identity { id.changed = true; } } + + fn get_identity(extensions: &Extensions) -> Option { + if let Some(id) = extensions.get::() { + id.id.clone() + } else { + None + } + } } struct IdentityItem { @@ -126,6 +133,21 @@ struct IdentityItem { changed: bool, } +/// Helper trait that allows to get Identity. +/// It could be used in middleware but identity policy must be set before any other middleware that needs identity +pub trait RequestIdentity { + fn get_identity(&self) -> Option; +} + +impl RequestIdentity for T +where + T: HttpMessage, +{ + fn get_identity(&self) -> Option { + Identity::get_identity(&self.extensions()) + } +} + /// Extractor implementation for Identity type. /// /// ```rust From ff724e239db50210a9913de6e214be214f41befa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 12 Jun 2019 15:52:48 +0600 Subject: [PATCH 1438/1635] move identity service separate crate --- CHANGES.md | 6 ++- Cargo.toml | 5 ++- MIGRATION.md | 17 +++++++ actix-identity/CHANGES.md | 5 +++ actix-identity/Cargo.toml | 29 ++++++++++++ actix-identity/LICENSE-APACHE | 1 + actix-identity/LICENSE-MIT | 1 + actix-identity/README.md | 9 ++++ .../identity.rs => actix-identity/src/lib.rs | 45 +++++++++---------- awc/src/lib.rs | 1 + src/lib.rs | 2 +- src/middleware/mod.rs | 3 -- 12 files changed, 92 insertions(+), 32 deletions(-) create mode 100644 actix-identity/CHANGES.md create mode 100644 actix-identity/Cargo.toml create mode 120000 actix-identity/LICENSE-APACHE create mode 120000 actix-identity/LICENSE-MIT create mode 100644 actix-identity/README.md rename src/middleware/identity.rs => actix-identity/src/lib.rs (97%) diff --git a/CHANGES.md b/CHANGES.md index 3e926525..231cb133 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.x] - 2019-xx-xx +## [1.0.1] - 2019-06-xx ### Add @@ -8,7 +8,9 @@ ### Changes -### Fixed +* Disable default feature `secure-cookies`. + +* Move identity middleware to `actix-identity` crate. ## [1.0.0] - 2019-06-05 diff --git a/Cargo.toml b/Cargo.toml index cd0e9458..08eb7cd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "actix-files", "actix-framed", "actix-session", + "actix-identity", "actix-multipart", "actix-web-actors", "actix-web-codegen", @@ -41,7 +42,7 @@ members = [ ] [features] -default = ["brotli", "flate2-zlib", "secure-cookies", "client", "fail"] +default = ["brotli", "flate2-zlib", "client", "fail"] # http client client = ["awc"] @@ -77,7 +78,7 @@ actix-http = "0.2.3" actix-server = "0.5.1" actix-server-config = "0.1.1" actix-threadpool = "0.1.1" -awc = { version = "0.2.0", optional = true } +awc = { version = "0.2.1", optional = true } bytes = "0.4" derive_more = "0.14" diff --git a/MIGRATION.md b/MIGRATION.md index 1736ee65..8b5d7dd4 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,20 @@ +## 1.0.1 + +* Identity middleware has been moved to `actix-identity` crate + + instead of + + ```rust + use actix_web::middleware::identity::{Identity, CookieIdentityPolicy, IdentityService}; + ``` + + use + + ```rust + use actix_identity::{Identity, CookieIdentityPolicy, IdentityService}; + ``` + + ## 1.0 * Resource registration. 1.0 version uses generalized resource diff --git a/actix-identity/CHANGES.md b/actix-identity/CHANGES.md new file mode 100644 index 00000000..74a20405 --- /dev/null +++ b/actix-identity/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0] - 2019-06-xx + +* Move identity middleware to separate crate diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml new file mode 100644 index 00000000..3b1b9086 --- /dev/null +++ b/actix-identity/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "actix-identity" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Identity service for actix web framework." +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://docs.rs/actix-identity/" +license = "MIT/Apache-2.0" +edition = "2018" +workspace = ".." + +[lib] +name = "actix_identity" +path = "src/lib.rs" + +[dependencies] +actix-web = { version = "1.0.0", default-features = false, features = ["secure-cookies"] } +actix-service = "0.4.0" +futures = "0.1.25" +serde = "1.0" +serde_json = "1.0" +time = "0.1.42" + +[dev-dependencies] +actix-rt = "0.2.2" +actix-http = "0.2.3" diff --git a/actix-identity/LICENSE-APACHE b/actix-identity/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/actix-identity/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-identity/LICENSE-MIT b/actix-identity/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/actix-identity/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-identity/README.md b/actix-identity/README.md new file mode 100644 index 00000000..60b615c7 --- /dev/null +++ b/actix-identity/README.md @@ -0,0 +1,9 @@ +# Identity service for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-identity)](https://crates.io/crates/actix-identity) [![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) + +## Documentation & community resources + +* [User Guide](https://actix.rs/docs/) +* [API Documentation](https://docs.rs/actix-identity/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-session](https://crates.io/crates/actix-identity) +* Minimum supported Rust version: 1.34 or later diff --git a/src/middleware/identity.rs b/actix-identity/src/lib.rs similarity index 97% rename from src/middleware/identity.rs rename to actix-identity/src/lib.rs index 304df9eb..6664df67 100644 --- a/src/middleware/identity.rs +++ b/actix-identity/src/lib.rs @@ -10,12 +10,11 @@ //! uses cookies as identity storage. //! //! To access current request identity -//! [**Identity**](trait.Identity.html) extractor should be used. +//! [**Identity**](struct.Identity.html) extractor should be used. //! //! ```rust -//! use actix_web::middleware::identity::Identity; -//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; //! use actix_web::*; +//! use actix_identity::{Identity, CookieIdentityPolicy, IdentityService}; //! //! fn index(id: Identity) -> String { //! // access request identity @@ -39,7 +38,7 @@ //! fn main() { //! let app = App::new().wrap(IdentityService::new( //! // <- create identity middleware -//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend +//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie identity policy //! .name("auth-cookie") //! .secure(false))) //! .service(web::resource("/index.html").to(index)) @@ -57,20 +56,17 @@ use futures::{Future, IntoFuture, Poll}; use serde::{Deserialize, Serialize}; use time::Duration; -use crate::cookie::{Cookie, CookieJar, Key, SameSite}; -use crate::error::{Error, Result}; -use crate::http::header::{self, HeaderValue}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{ - dev::{Extensions, Payload}, - FromRequest, HttpMessage, HttpRequest, -}; +use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; +use actix_web::dev::{Extensions, Payload, ServiceRequest, ServiceResponse}; +use actix_web::error::{Error, Result}; +use actix_web::http::header::{self, HeaderValue}; +use actix_web::{FromRequest, HttpMessage, HttpRequest}; /// The extractor type to obtain your identity from a request. /// /// ```rust /// use actix_web::*; -/// use actix_web::middleware::identity::Identity; +/// use actix_identity::Identity; /// /// fn index(id: Identity) -> Result { /// // access request identity @@ -134,7 +130,9 @@ struct IdentityItem { } /// Helper trait that allows to get Identity. +/// /// It could be used in middleware but identity policy must be set before any other middleware that needs identity +/// RequestIdentity is implemented both for `ServiceRequest` and `HttpRequest`. pub trait RequestIdentity { fn get_identity(&self) -> Option; } @@ -152,7 +150,7 @@ where /// /// ```rust /// # use actix_web::*; -/// use actix_web::middleware::identity::Identity; +/// use actix_identity::Identity; /// /// fn index(id: Identity) -> String { /// // access request identity @@ -199,7 +197,7 @@ pub trait IdentityPolicy: Sized + 'static { /// /// ```rust /// use actix_web::App; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; +/// use actix_identity::{CookieIdentityPolicy, IdentityService}; /// /// fn main() { /// let app = App::new().wrap(IdentityService::new( @@ -464,9 +462,8 @@ impl CookieIdentityInner { /// # Example /// /// ```rust -/// # extern crate actix_web; -/// use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; /// use actix_web::App; +/// use actix_identity::{CookieIdentityPolicy, IdentityService}; /// /// fn main() { /// let app = App::new().wrap(IdentityService::new( @@ -612,13 +609,13 @@ impl IdentityPolicy for CookieIdentityPolicy { #[cfg(test)] mod tests { - use super::*; - use crate::http::StatusCode; - use crate::test::{self, TestRequest}; - use crate::{web, App, HttpResponse}; - use std::borrow::Borrow; + use super::*; + use actix_web::http::StatusCode; + use actix_web::test::{self, TestRequest}; + use actix_web::{web, App, Error, HttpResponse}; + const COOKIE_KEY_MASTER: [u8; 32] = [0; 32]; const COOKIE_NAME: &'static str = "actix_auth"; const COOKIE_LOGIN: &'static str = "test"; @@ -739,8 +736,8 @@ mod tests { f: F, ) -> impl actix_service::Service< Request = actix_http::Request, - Response = ServiceResponse, - Error = actix_http::Error, + Response = ServiceResponse, + Error = Error, > { test::init_service( App::new() diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 8c1bc80b..9fbda8aa 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -38,6 +38,7 @@ pub mod test; pub mod ws; pub use self::builder::ClientBuilder; +pub use self::connect::BoxedSocket; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; diff --git a/src/lib.rs b/src/lib.rs index f0bf01bc..fffbc2f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,7 @@ //! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` //! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` //! * `secure-cookies` - enables secure cookies support, includes `ring` crate as -//! dependency (default enabled) +//! dependency //! * `brotli` - enables `brotli` compression support, requires `c` //! compiler (default enabled) //! * `flate2-zlib` - enables `gzip`, `deflate` compression support, requires diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 5266f7c1..99c6cb45 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -11,6 +11,3 @@ mod normalize; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; pub use self::normalize::NormalizePath; - -#[cfg(feature = "secure-cookies")] -pub mod identity; From 2ffda29f9bf56cb357cca7480cd29e6e743e59d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 12 Jun 2019 16:15:06 +0600 Subject: [PATCH 1439/1635] Allow to test an app that uses async actors #897 --- CHANGES.md | 2 ++ Cargo.toml | 3 ++- MIGRATION.md | 2 +- src/test.rs | 46 ++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 231cb133..14400add 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ * Move identity middleware to `actix-identity` crate. +* Allow to test an app that uses async actors #897 + ## [1.0.0] - 2019-06-05 diff --git a/Cargo.toml b/Cargo.toml index 08eb7cd9..871ed745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,8 @@ rustls = { version = "0.15", optional = true } [dev-dependencies] actix-http = { version = "0.2.3", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } -actix-files = { version = "0.1.1" } +actix-files = "0.1.1" +actix = { version = "0.8.3" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" diff --git a/MIGRATION.md b/MIGRATION.md index 8b5d7dd4..a2591a1d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -15,7 +15,7 @@ ``` -## 1.0 +## 1.0.0 * Resource registration. 1.0 version uses generalized resource registration via `.service()` method. diff --git a/src/test.rs b/src/test.rs index 89c1a126..5ab417bd 100644 --- a/src/test.rs +++ b/src/test.rs @@ -7,7 +7,7 @@ use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, Extensions, Request}; use actix_router::{Path, ResourceDef, Url}; -use actix_rt::Runtime; +use actix_rt::{System, SystemRunner}; use actix_server_config::ServerConfig; use actix_service::{IntoNewService, IntoService, NewService, Service}; use bytes::{Bytes, BytesMut}; @@ -29,14 +29,14 @@ use crate::{Error, HttpRequest, HttpResponse}; thread_local! { static RT: RefCell = { - RefCell::new(Inner(Some(Runtime::new().unwrap()))) + RefCell::new(Inner(Some(System::builder().build()))) }; } -struct Inner(Option); +struct Inner(Option); impl Inner { - fn get_mut(&mut self) -> &mut Runtime { + fn get_mut(&mut self) -> &mut SystemRunner { self.0.as_mut().unwrap() } } @@ -714,4 +714,42 @@ mod tests { let res = block_fn(|| app.call(req)).unwrap(); assert!(res.status().is_success()); } + + #[test] + fn test_actor() { + use actix::Actor; + + struct MyActor; + + struct Num(usize); + impl actix::Message for Num { + type Result = usize; + } + impl actix::Actor for MyActor { + type Context = actix::Context; + } + impl actix::Handler for MyActor { + type Result = usize; + fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result { + msg.0 + } + } + + let addr = run_on(|| MyActor.start()); + let mut app = init_service(App::new().service( + web::resource("/index.html").to_async(move || { + addr.send(Num(1)).from_err().and_then(|res| { + if res == 1 { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }) + }), + )); + + let req = TestRequest::post().uri("/index.html").to_request(); + let res = block_fn(|| app.call(req)).unwrap(); + assert!(res.status().is_success()); + } } From 7450ae37a7dbf87b78094e73955ed1f2590d7de2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 12 Jun 2019 16:45:05 +0600 Subject: [PATCH 1440/1635] Re-apply patch from #637 #894 --- CHANGES.md | 2 + actix-identity/Cargo.toml | 1 + src/types/json.rs | 100 ++++++++++++++++++++++++++++++++++---- 3 files changed, 93 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 14400add..1794a812 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ * Allow to test an app that uses async actors #897 +* Re-apply patch from #637 #894 + ## [1.0.0] - 2019-06-05 diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index 3b1b9086..e645275a 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -27,3 +27,4 @@ time = "0.1.42" [dev-dependencies] actix-rt = "0.2.2" actix-http = "0.2.3" +bytes = "0.4" \ No newline at end of file diff --git a/src/types/json.rs b/src/types/json.rs index 4e827942..0789fb61 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -175,15 +175,15 @@ where #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req2 = req.clone(); - let (limit, err) = req + let (limit, err, ctype) = req .app_data::() - .map(|c| (c.limit, c.ehandler.clone())) - .unwrap_or((32768, None)); + .map(|c| (c.limit, c.ehandler.clone(), c.content_type.clone())) + .unwrap_or((32768, None, None)); let path = req.path().to_string(); Box::new( - JsonBody::new(req, payload) + JsonBody::new(req, payload, ctype) .limit(limit) .map_err(move |e| { log::debug!( @@ -224,6 +224,9 @@ where /// // change json extractor configuration /// web::Json::::configure(|cfg| { /// cfg.limit(4096) +/// .content_type(|mime| { // <- accept text/plain content type +/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN +/// }) /// .error_handler(|err, req| { // <- create custom error response /// error::InternalError::from_response( /// err, HttpResponse::Conflict().finish()).into() @@ -237,6 +240,7 @@ where pub struct JsonConfig { limit: usize, ehandler: Option Error + Send + Sync>>, + content_type: Option bool + Send + Sync>>, } impl JsonConfig { @@ -254,6 +258,15 @@ impl JsonConfig { self.ehandler = Some(Arc::new(f)); self } + + /// Set predicate for allowed content types + pub fn content_type(mut self, predicate: F) -> Self + where + F: Fn(mime::Mime) -> bool + Send + Sync + 'static, + { + self.content_type = Some(Arc::new(predicate)); + self + } } impl Default for JsonConfig { @@ -261,6 +274,7 @@ impl Default for JsonConfig { JsonConfig { limit: 32768, ehandler: None, + content_type: None, } } } @@ -271,6 +285,7 @@ impl Default for JsonConfig { /// Returns error: /// /// * content type is not `application/json` +/// (unless specified in [`JsonConfig`](struct.JsonConfig.html)) /// * content length is greater than 256k pub struct JsonBody { limit: usize, @@ -285,13 +300,20 @@ where U: DeserializeOwned + 'static, { /// Create `JsonBody` for request. - pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self { + pub fn new( + req: &HttpRequest, + payload: &mut Payload, + ctype: Option bool + Send + Sync>>, + ) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) + mime.subtype() == mime::JSON + || mime.suffix() == Some(mime::JSON) + || ctype.as_ref().map_or(false, |predicate| predicate(mime)) } else { false }; + if !json { return JsonBody { limit: 262_144, @@ -512,7 +534,7 @@ mod tests { #[test] fn test_json_body() { let (req, mut pl) = TestRequest::default().to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl, None)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() @@ -521,7 +543,7 @@ mod tests { header::HeaderValue::from_static("application/text"), ) .to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl, None)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() @@ -535,7 +557,7 @@ mod tests { ) .to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl).limit(100)); + let json = block_on(JsonBody::::new(&req, &mut pl, None).limit(100)); assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); let (req, mut pl) = TestRequest::default() @@ -550,7 +572,7 @@ mod tests { .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl)); + let json = block_on(JsonBody::::new(&req, &mut pl, None)); assert_eq!( json.ok().unwrap(), MyObject { @@ -558,4 +580,62 @@ mod tests { } ); } + + #[test] + fn test_with_json_and_bad_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().limit(4096)) + .to_http_parts(); + + let s = block_on(Json::::from_request(&req, &mut pl)); + assert!(s.is_err()) + } + + #[test] + fn test_with_json_and_good_custom_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); + + let s = block_on(Json::::from_request(&req, &mut pl)); + assert!(s.is_ok()) + } + + #[test] + fn test_with_json_and_bad_custom_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); + + let s = block_on(Json::::from_request(&req, &mut pl)); + assert!(s.is_err()) + } } From 36e6f0cb4b96bcec8c8f4dd44ab623cb9866b631 Mon Sep 17 00:00:00 2001 From: Aliaksandr Rahalevich Date: Wed, 12 Jun 2019 03:47:00 -0700 Subject: [PATCH 1441/1635] add "put" and "sput" methods for test server (#909) --- test-server/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index b8f5934a..1fbaa6c7 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -255,6 +255,16 @@ impl TestServerRuntime { self.client.head(self.surl(path.as_ref()).as_str()) } + /// Create `PUT` request + pub fn put>(&self, path: S) -> ClientRequest { + self.client.put(self.url(path.as_ref()).as_str()) + } + + /// Create https `PUT` request + pub fn sput>(&self, path: S) -> ClientRequest { + self.client.put(self.surl(path.as_ref()).as_str()) + } + /// Connect to test http server pub fn request>(&self, method: Method, path: S) -> ClientRequest { self.client.request(method, path.as_ref()) From 13e618b128e3f80d9fe031af8dc787116cbe6391 Mon Sep 17 00:00:00 2001 From: Lucas Berezy Date: Wed, 12 Jun 2019 20:49:56 +1000 Subject: [PATCH 1442/1635] Added initial support for PathConfig, allows setting custom error handler. (#903) --- src/error.rs | 19 +++++++++++ src/types/mod.rs | 2 +- src/types/path.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index e1cc7984..d8e2b1d0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -92,6 +92,25 @@ impl ResponseError for JsonPayloadError { } } +/// A set of errors that can occur during parsing request paths +#[derive(Debug, Display, From)] +pub enum PathPayloadError { + /// Deserialize error + #[display(fmt = "Path deserialize error: {}", _0)] + Deserialize(de::Error), +} + +/// Return `BadRequest` for `PathPayloadError` +impl ResponseError for PathPayloadError { + fn error_response(&self) -> HttpResponse { + match *self { + PathPayloadError::Deserialize(_) => { + HttpResponse::new(StatusCode::BAD_REQUEST) + } + } + } +} + /// A set of errors that can occur during parsing query strings #[derive(Debug, Display, From)] pub enum QueryPayloadError { diff --git a/src/types/mod.rs b/src/types/mod.rs index d01d597b..43a189e2 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -9,6 +9,6 @@ pub(crate) mod readlines; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; -pub use self::path::Path; +pub use self::path::{Path, PathConfig}; pub use self::payload::{Payload, PayloadConfig}; pub use self::query::{Query, QueryConfig}; diff --git a/src/types/path.rs b/src/types/path.rs index 5f0a05af..4f1d3d54 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -1,5 +1,6 @@ //! Path extractor +use std::sync::Arc; use std::{fmt, ops}; use actix_http::error::{Error, ErrorNotFound}; @@ -7,6 +8,7 @@ use actix_router::PathDeserializer; use serde::de; use crate::dev::Payload; +use crate::error::PathPayloadError; use crate::request::HttpRequest; use crate::FromRequest; @@ -156,15 +158,89 @@ impl FromRequest for Path where T: de::DeserializeOwned, { - type Config = (); type Error = Error; type Future = Result; + type Config = PathConfig; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let error_handler = req + .app_data::() + .map(|c| c.ehandler.clone()) + .unwrap_or(None); + de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) .map(|inner| Path { inner }) - .map_err(ErrorNotFound) + .map_err(move |e| { + log::debug!( + "Failed during Path extractor deserialization. \ + Request path: {:?}", + req.path() + ); + if let Some(error_handler) = error_handler { + let e = PathPayloadError::Deserialize(e); + (error_handler)(e, req) + } else { + ErrorNotFound(e) + } + }) + } +} + +/// Path extractor configuration +/// +/// ```rust +// #[macro_use] +// extern crate serde_derive; +// use actix_web::web::PathConfig; +// use actix_web::{error, web, App, FromRequest, HttpResponse}; + +// #[derive(Deserialize, Debug)] +// enum Folder { +// #[serde(rename = "inbox")] +// Inbox, +// #[serde(rename = "outbox")] +// Outbox, +// } + +// /// deserialize `Info` from request's path +// fn index(folder: web::Path) -> String { +// format!("Selected folder: {}!", folder) +// } + +// fn main() { +// let app = App::new().service( +// web::resource("messages/{folder}") +// .data(PathConfig::default().error_handler(|err, req| { +// error::InternalError::from_response( +// err, +// HttpResponse::Conflict().finish(), +// ) +// .into() +// })) +// .route(web::post().to(index)), +// ); +// } +/// ``` +#[derive(Clone)] +pub struct PathConfig { + ehandler: Option Error + Send + Sync>>, +} + +impl PathConfig { + /// Set custom error handler + pub fn error_handler(mut self, f: F) -> Self + where + F: Fn(PathPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, + { + self.ehandler = Some(Arc::new(f)); + self + } +} + +impl Default for PathConfig { + fn default() -> Self { + PathConfig { ehandler: None } } } From e7ba67e1a8ef8fac448357e8aceffc703b401e6b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 12 Jun 2019 17:02:45 +0600 Subject: [PATCH 1443/1635] rename PathPayloadError and test for path config --- CHANGES.md | 2 ++ src/error.rs | 10 +++--- src/types/path.rs | 88 ++++++++++++++++++++++++++++------------------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1794a812..5f3d519a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Add +* Add support for PathConfig #903 + * Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. ### Changes diff --git a/src/error.rs b/src/error.rs index d8e2b1d0..9f31582e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -94,19 +94,17 @@ impl ResponseError for JsonPayloadError { /// A set of errors that can occur during parsing request paths #[derive(Debug, Display, From)] -pub enum PathPayloadError { +pub enum PathError { /// Deserialize error #[display(fmt = "Path deserialize error: {}", _0)] Deserialize(de::Error), } -/// Return `BadRequest` for `PathPayloadError` -impl ResponseError for PathPayloadError { +/// Return `BadRequest` for `PathError` +impl ResponseError for PathError { fn error_response(&self) -> HttpResponse { match *self { - PathPayloadError::Deserialize(_) => { - HttpResponse::new(StatusCode::BAD_REQUEST) - } + PathError::Deserialize(_) => HttpResponse::new(StatusCode::BAD_REQUEST), } } } diff --git a/src/types/path.rs b/src/types/path.rs index 4f1d3d54..2c37a49f 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -8,7 +8,7 @@ use actix_router::PathDeserializer; use serde::de; use crate::dev::Payload; -use crate::error::PathPayloadError; +use crate::error::PathError; use crate::request::HttpRequest; use crate::FromRequest; @@ -178,7 +178,7 @@ where req.path() ); if let Some(error_handler) = error_handler { - let e = PathPayloadError::Deserialize(e); + let e = PathError::Deserialize(e); (error_handler)(e, req) } else { ErrorNotFound(e) @@ -190,48 +190,48 @@ where /// Path extractor configuration /// /// ```rust -// #[macro_use] -// extern crate serde_derive; -// use actix_web::web::PathConfig; -// use actix_web::{error, web, App, FromRequest, HttpResponse}; - -// #[derive(Deserialize, Debug)] -// enum Folder { -// #[serde(rename = "inbox")] -// Inbox, -// #[serde(rename = "outbox")] -// Outbox, -// } - -// /// deserialize `Info` from request's path -// fn index(folder: web::Path) -> String { -// format!("Selected folder: {}!", folder) -// } - -// fn main() { -// let app = App::new().service( -// web::resource("messages/{folder}") -// .data(PathConfig::default().error_handler(|err, req| { -// error::InternalError::from_response( -// err, -// HttpResponse::Conflict().finish(), -// ) -// .into() -// })) -// .route(web::post().to(index)), -// ); -// } +/// # #[macro_use] +/// # extern crate serde_derive; +/// use actix_web::web::PathConfig; +/// use actix_web::{error, web, App, FromRequest, HttpResponse}; +/// +/// #[derive(Deserialize, Debug)] +/// enum Folder { +/// #[serde(rename = "inbox")] +/// Inbox, +/// #[serde(rename = "outbox")] +/// Outbox, +/// } +/// +/// // deserialize `Info` from request's path +/// fn index(folder: web::Path) -> String { +/// format!("Selected folder: {}!", folder) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/messages/{folder}") +/// .data(PathConfig::default().error_handler(|err, req| { +/// error::InternalError::from_response( +/// err, +/// HttpResponse::Conflict().finish(), +/// ) +/// .into() +/// })) +/// .route(web::post().to(index)), +/// ); +/// } /// ``` #[derive(Clone)] pub struct PathConfig { - ehandler: Option Error + Send + Sync>>, + ehandler: Option Error + Send + Sync>>, } impl PathConfig { /// Set custom error handler pub fn error_handler(mut self, f: F) -> Self where - F: Fn(PathPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, + F: Fn(PathError, &HttpRequest) -> Error + Send + Sync + 'static, { self.ehandler = Some(Arc::new(f)); self @@ -252,6 +252,7 @@ mod tests { use super::*; use crate::test::{block_on, TestRequest}; + use crate::{error, http, HttpResponse}; #[derive(Deserialize, Debug, Display)] #[display(fmt = "MyStruct({}, {})", key, value)] @@ -347,4 +348,21 @@ mod tests { assert_eq!(res[1], "32".to_owned()); } + #[test] + fn test_custom_err_handler() { + let (req, mut pl) = TestRequest::with_uri("/name/user1/") + .data(PathConfig::default().error_handler(|err, _| { + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish(), + ) + .into() + })) + .to_http_parts(); + + let s = block_on(Path::<(usize,)>::from_request(&req, &mut pl)).unwrap_err(); + let res: HttpResponse = s.into(); + + assert_eq!(res.status(), http::StatusCode::CONFLICT); + } } From 959eef05ae2bb1059162df728c9747cd7998fe4b Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 12 Jun 2019 08:03:27 -0400 Subject: [PATCH 1444/1635] updated actix-session to support login and logout functionality (renew and purge) --- actix-session/src/lib.rs | 62 ++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index fb316f39..30d71552 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -98,10 +98,23 @@ impl UserSession for ServiceRequest { } } +#[derive(PartialEq, Clone, Debug)] +pub enum SessionStatus { + Changed, + Purged, + Renewed, + Unchanged +} +impl Default for SessionStatus { + fn default() -> SessionStatus { + SessionStatus::Unchanged + } +} + #[derive(Default)] struct SessionInner { state: HashMap, - changed: bool, + pub status: SessionStatus, } impl Session { @@ -117,7 +130,7 @@ impl Session { /// Set a `value` from the session. pub fn set(&self, key: &str, value: T) -> Result<(), Error> { let mut inner = self.0.borrow_mut(); - inner.changed = true; + inner.status = SessionStatus::Changed; inner .state .insert(key.to_owned(), serde_json::to_string(&value)?); @@ -127,17 +140,30 @@ impl Session { /// Remove value from the session. pub fn remove(&self, key: &str) { let mut inner = self.0.borrow_mut(); - inner.changed = true; + inner.status = SessionStatus::Changed; inner.state.remove(key); } /// Clear the session. pub fn clear(&self) { let mut inner = self.0.borrow_mut(); - inner.changed = true; + inner.status = SessionStatus::Changed; inner.state.clear() } + /// Removes session, both client and server side. + pub fn purge(&self) { + let mut inner = self.0.borrow_mut(); + inner.status = SessionStatus::Purged; + inner.state.clear(); + } + + /// Renews the session key, assigning existing session state to new key. + pub fn renew(&self) { + let mut inner = self.0.borrow_mut(); + inner.status = SessionStatus::Renewed; + } + pub fn set_session( data: impl Iterator, req: &mut ServiceRequest, @@ -149,7 +175,7 @@ impl Session { pub fn get_changes( res: &mut ServiceResponse, - ) -> Option> { + ) -> (SessionStatus, Option>) { if let Some(s_impl) = res .request() .extensions() @@ -157,9 +183,9 @@ impl Session { { let state = std::mem::replace(&mut s_impl.borrow_mut().state, HashMap::new()); - Some(state.into_iter()) + (s_impl.borrow().status.clone(), Some(state.into_iter())) } else { - None + (SessionStatus::Unchanged, None) } } @@ -224,7 +250,8 @@ mod tests { session.remove("key"); let mut res = req.into_response(HttpResponse::Ok().finish()); - let changes: Vec<_> = Session::get_changes(&mut res).unwrap().collect(); + let (_status, state) = Session::get_changes(&mut res); + let changes: Vec<_> = state.unwrap().collect(); assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]); } @@ -241,4 +268,23 @@ mod tests { let res = session.get::("key").unwrap(); assert_eq!(res, Some("value".to_string())); } + + #[test] + fn purge_session() { + let mut req = test::TestRequest::default().to_srv_request(); + let session = Session::get_session(&mut *req.extensions_mut()); + assert_eq!(session.0.borrow().status, SessionStatus::Unchanged); + session.purge(); + assert_eq!(session.0.borrow().status, SessionStatus::Purged); + } + + + #[test] + fn renew_session() { + let mut req = test::TestRequest::default().to_srv_request(); + let session = Session::get_session(&mut *req.extensions_mut()); + assert_eq!(session.0.borrow().status, SessionStatus::Unchanged); + session.renew(); + assert_eq!(session.0.borrow().status, SessionStatus::Renewed); + } } From 65732197b8f1e3abdf8847edcbe24ca70f8a21b5 Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 12 Jun 2019 10:11:38 -0400 Subject: [PATCH 1445/1635] modified so as to consider unanticipated state changes --- actix-session/src/lib.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 30d71552..aaf0ab02 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -130,25 +130,31 @@ impl Session { /// Set a `value` from the session. pub fn set(&self, key: &str, value: T) -> Result<(), Error> { let mut inner = self.0.borrow_mut(); - inner.status = SessionStatus::Changed; - inner - .state - .insert(key.to_owned(), serde_json::to_string(&value)?); + if inner.status != SessionStatus::Purged { + inner.status = SessionStatus::Changed; + inner + .state + .insert(key.to_owned(), serde_json::to_string(&value)?); + } Ok(()) } /// Remove value from the session. pub fn remove(&self, key: &str) { let mut inner = self.0.borrow_mut(); - inner.status = SessionStatus::Changed; - inner.state.remove(key); + if inner.status != SessionStatus::Purged { + inner.status = SessionStatus::Changed; + inner.state.remove(key); + } } /// Clear the session. pub fn clear(&self) { let mut inner = self.0.borrow_mut(); - inner.status = SessionStatus::Changed; - inner.state.clear() + if inner.status != SessionStatus::Purged { + inner.status = SessionStatus::Changed; + inner.state.clear() + } } /// Removes session, both client and server side. @@ -161,7 +167,9 @@ impl Session { /// Renews the session key, assigning existing session state to new key. pub fn renew(&self) { let mut inner = self.0.borrow_mut(); - inner.status = SessionStatus::Renewed; + if inner.status != SessionStatus::Purged { + inner.status = SessionStatus::Renewed; + } } pub fn set_session( From c8118e841135594ef46d2f3662872d40378d3b3d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 12 Jun 2019 20:12:15 +0600 Subject: [PATCH 1446/1635] fix path doc tests --- src/types/path.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/path.rs b/src/types/path.rs index 2c37a49f..2a6ce287 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -205,7 +205,7 @@ where /// /// // deserialize `Info` from request's path /// fn index(folder: web::Path) -> String { -/// format!("Selected folder: {}!", folder) +/// format!("Selected folder: {:?}!", folder) /// } /// /// fn main() { From bf48798bcec35580c408d190b50cc2e42526f1a2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 13 Jun 2019 15:27:21 +0600 Subject: [PATCH 1447/1635] Content-Length is 0 for NamedFile HEAD request #914 --- actix-files/CHANGES.md | 6 ++++-- actix-files/src/lib.rs | 23 +++++++++++++++++++++++ actix-files/src/named.rs | 26 +++++++++++--------------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 862a5944..e79d8096 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,10 @@ # Changes -## [0.1.2] - 2019-06-06 +## [0.1.2] - 2019-06-13 -* Fix ring dependency from actix-web default features for #741. +* Content-Length is 0 for NamedFile HEAD request #914 + +* Fix ring dependency from actix-web default features for #741 ## [0.1.1] - 2019-06-01 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 301d9d81..9f526f3f 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -926,6 +926,29 @@ mod tests { assert_eq!(bytes.freeze(), data); } + #[test] + fn test_head_content_length_headers() { + let mut srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("tests/test.binary")), + ); + + // Valid range header + let request = TestRequest::default() + .method(Method::HEAD) + .uri("/t%65st/tests/test.binary") + .to_request(); + let response = test::call_service(&mut srv, request); + + let contentlength = response + .headers() + .get(header::CONTENT_LENGTH) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentlength, "100"); + } + #[test] fn test_static_files_with_spaces() { let mut srv = test::init_service( diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 29e9eee4..3ece7c21 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -422,20 +422,16 @@ impl Responder for NamedFile { return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); } - if *req.method() == Method::HEAD { - Ok(resp.finish()) - } else { - let reader = ChunkedReadFile { - offset, - size: length, - file: Some(self.file), - fut: None, - counter: 0, - }; - if offset != 0 || length != self.md.len() { - return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); - }; - Ok(resp.body(SizedStream::new(length, reader))) - } + let reader = ChunkedReadFile { + offset, + size: length, + file: Some(self.file), + fut: None, + counter: 0, + }; + if offset != 0 || length != self.md.len() { + return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); + }; + Ok(resp.body(SizedStream::new(length, reader))) } } From ca4ed0932e880bdd44dd971a5248a5867679305e Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 13 Jun 2019 08:59:59 -0400 Subject: [PATCH 1448/1635] made Session::get_session public --- actix-session/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index aaf0ab02..beee6b07 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -197,7 +197,7 @@ impl Session { } } - fn get_session(extensions: &mut Extensions) -> Session { + pub fn get_session(extensions: &mut Extensions) -> Session { if let Some(s_impl) = extensions.get::>>() { return Session(Rc::clone(&s_impl)); } From 32a66a99bf71d621e55f4bc83100ba3d56f2912a Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 13 Jun 2019 09:19:03 -0400 Subject: [PATCH 1449/1635] reverting change to get_session due to side effects --- actix-session/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index beee6b07..aaf0ab02 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -197,7 +197,7 @@ impl Session { } } - pub fn get_session(extensions: &mut Extensions) -> Session { + fn get_session(extensions: &mut Extensions) -> Session { if let Some(s_impl) = extensions.get::>>() { return Session(Rc::clone(&s_impl)); } From cd323f2ff1da9129e0d0ba63131f85f6e433cbb2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Jun 2019 09:34:16 +0600 Subject: [PATCH 1450/1635] Move cors middleware to actix-cors crate --- CHANGES.md | 4 ++- Cargo.toml | 3 ++ actix-cors/CHANGES.md | 5 ++++ actix-cors/Cargo.toml | 28 +++++++++++++++++++ actix-cors/LICENSE-APACHE | 1 + actix-cors/LICENSE-MIT | 1 + actix-cors/README.md | 9 ++++++ .../cors.rs => actix-cors/src/lib.rs | 20 ++++++------- src/middleware/mod.rs | 4 ++- 9 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 actix-cors/CHANGES.md create mode 100644 actix-cors/Cargo.toml create mode 120000 actix-cors/LICENSE-APACHE create mode 120000 actix-cors/LICENSE-MIT create mode 100644 actix-cors/README.md rename src/middleware/cors.rs => actix-cors/src/lib.rs (98%) diff --git a/CHANGES.md b/CHANGES.md index 5f3d519a..1f34b797 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,10 +10,12 @@ ### Changes -* Disable default feature `secure-cookies`. +* Move cors middleware to `actix-cors` crate. * Move identity middleware to `actix-identity` crate. +* Disable default feature `secure-cookies`. + * Allow to test an app that uses async actors #897 * Re-apply patch from #637 #894 diff --git a/Cargo.toml b/Cargo.toml index 871ed745..56a42bf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ ".", "awc", "actix-http", + "actix-cors", "actix-files", "actix-framed", "actix-session", @@ -80,6 +81,8 @@ actix-server-config = "0.1.1" actix-threadpool = "0.1.1" awc = { version = "0.2.1", optional = true } +# actix-cors = "0.1."{ path="./actix-cors" } + bytes = "0.4" derive_more = "0.14" encoding = "0.2" diff --git a/actix-cors/CHANGES.md b/actix-cors/CHANGES.md new file mode 100644 index 00000000..1e842f37 --- /dev/null +++ b/actix-cors/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## [0.1.0] - 2019-06-15 + +* Move cors middleware to separate crate diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml new file mode 100644 index 00000000..a62cc664 --- /dev/null +++ b/actix-cors/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "actix-cors" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Cross-origin resource sharing (CORS) for Actix applications." +readme = "README.md" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" +documentation = "https://docs.rs/actix-cors/" +license = "MIT/Apache-2.0" +edition = "2018" +workspace = ".." + +[lib] +name = "actix_cors" +path = "src/lib.rs" + +[dependencies] +actix-web = "1.0.0" +actix-service = "0.4.0" +derive_more = "0.14.1" +futures = "0.1.25" + +[dev-dependencies] +actix-rt = "0.2.2" +#actix-http = "0.2.3" +#bytes = "0.4" \ No newline at end of file diff --git a/actix-cors/LICENSE-APACHE b/actix-cors/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/actix-cors/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-cors/LICENSE-MIT b/actix-cors/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/actix-cors/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-cors/README.md b/actix-cors/README.md new file mode 100644 index 00000000..60b615c7 --- /dev/null +++ b/actix-cors/README.md @@ -0,0 +1,9 @@ +# Identity service for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-identity)](https://crates.io/crates/actix-identity) [![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) + +## Documentation & community resources + +* [User Guide](https://actix.rs/docs/) +* [API Documentation](https://docs.rs/actix-identity/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-session](https://crates.io/crates/actix-identity) +* Minimum supported Rust version: 1.34 or later diff --git a/src/middleware/cors.rs b/actix-cors/src/lib.rs similarity index 98% rename from src/middleware/cors.rs rename to actix-cors/src/lib.rs index f731f49b..5d0d013e 100644 --- a/src/middleware/cors.rs +++ b/actix-cors/src/lib.rs @@ -7,7 +7,7 @@ //! # Example //! //! ```rust -//! use actix_web::middleware::cors::Cors; +//! use actix_cors::Cors; //! use actix_web::{http, web, App, HttpRequest, HttpResponse, HttpServer}; //! //! fn index(req: HttpRequest) -> &'static str { @@ -42,17 +42,15 @@ use std::iter::FromIterator; use std::rc::Rc; use actix_service::{IntoTransform, Service, Transform}; +use actix_web::dev::{RequestHead, ServiceRequest, ServiceResponse}; +use actix_web::error::{Error, ResponseError, Result}; +use actix_web::http::header::{self, HeaderName, HeaderValue}; +use actix_web::http::{self, HttpTryFrom, Method, StatusCode, Uri}; +use actix_web::HttpResponse; use derive_more::Display; use futures::future::{ok, Either, Future, FutureResult}; use futures::Poll; -use crate::dev::RequestHead; -use crate::error::{Error, ResponseError, Result}; -use crate::http::header::{self, HeaderName, HeaderValue}; -use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::HttpResponse; - /// A set of errors that can occur during processing CORS #[derive(Debug, Display)] pub enum CorsError { @@ -152,11 +150,11 @@ impl AllOrSome { /// # Example /// /// ```rust +/// use actix_cors::Cors; /// use actix_web::http::header; -/// use actix_web::middleware::cors; /// /// # fn main() { -/// let cors = cors::Cors::new() +/// let cors = Cors::new() /// .allowed_origin("https://www.rust-lang.org/") /// .allowed_methods(vec!["GET", "POST"]) /// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) @@ -806,9 +804,9 @@ where #[cfg(test)] mod tests { use actix_service::{IntoService, Transform}; + use actix_web::test::{self, block_on, TestRequest}; use super::*; - use crate::test::{self, block_on, TestRequest}; impl Cors { fn finish(self, srv: F) -> CorsMiddleware diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 99c6cb45..f0b90e77 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -2,7 +2,6 @@ mod compress; pub use self::compress::{BodyEncoding, Compress}; -pub mod cors; mod defaultheaders; pub mod errhandlers; mod logger; @@ -11,3 +10,6 @@ mod normalize; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; pub use self::normalize::NormalizePath; + +// +// use actix_cors as cors; From d7ec241fd081c625551ef07a1039b322935b5821 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Jun 2019 21:47:06 +0600 Subject: [PATCH 1451/1635] re-export identity and cors middleware --- Cargo.toml | 16 ++++++++++------ actix-cors/Cargo.toml | 9 ++------- src/app.rs | 3 +-- src/middleware/mod.rs | 15 +++++++++++++-- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56a42bf9..8531a93a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ members = [ ] [features] -default = ["brotli", "flate2-zlib", "client", "fail"] +default = ["brotli", "flate2-zlib", "client", "fail", "depracated"] # http client client = ["awc"] @@ -68,6 +68,9 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] # rustls rust-tls = ["rustls", "actix-server/rust-tls"] +# deprecated middlewares +depracated = ["actix-cors", "actix-identity"] + [dependencies] actix-codec = "0.1.2" actix-service = "0.4.0" @@ -81,13 +84,15 @@ actix-server-config = "0.1.1" actix-threadpool = "0.1.1" awc = { version = "0.2.1", optional = true } -# actix-cors = "0.1."{ path="./actix-cors" } +# deprecated middlewares +actix-cors = { version = "0.1.0", optional = true } +actix-identity = { version = "0.1.0", optional = true } bytes = "0.4" derive_more = "0.14" encoding = "0.2" futures = "0.1.25" -hashbrown = "0.3.0" +hashbrown = "0.3.1" log = "0.4" mime = "0.3" net2 = "0.2.33" @@ -104,10 +109,9 @@ openssl = { version="0.10", optional = true } rustls = { version = "0.15", optional = true } [dev-dependencies] +actix = { version = "0.8.3" } actix-http = { version = "0.2.3", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } -actix-files = "0.1.1" -actix = { version = "0.8.3" } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" @@ -121,7 +125,7 @@ opt-level = 3 codegen-units = 1 [patch.crates-io] -actix-web = { path = "." } +# actix-web = { path = "." } actix-http = { path = "actix-http" } actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml index a62cc664..98ed67a2 100644 --- a/actix-cors/Cargo.toml +++ b/actix-cors/Cargo.toml @@ -4,13 +4,13 @@ version = "0.1.0" authors = ["Nikolay Kim "] description = "Cross-origin resource sharing (CORS) for Actix applications." readme = "README.md" -keywords = ["http", "web", "framework", "async", "futures"] +keywords = ["web", "framework"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-cors/" license = "MIT/Apache-2.0" edition = "2018" -workspace = ".." +#workspace = ".." [lib] name = "actix_cors" @@ -21,8 +21,3 @@ actix-web = "1.0.0" actix-service = "0.4.0" derive_more = "0.14.1" futures = "0.1.25" - -[dev-dependencies] -actix-rt = "0.2.2" -#actix-http = "0.2.3" -#bytes = "0.4" \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 4f8b283e..897b3645 100644 --- a/src/app.rs +++ b/src/app.rs @@ -225,7 +225,6 @@ where /// It is also possible to use static files as default service. /// /// ```rust - /// use actix_files::Files; /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { @@ -233,7 +232,7 @@ where /// .service( /// web::resource("/index.html").to(|| HttpResponse::Ok())) /// .default_service( - /// Files::new("", "./static") + /// web::to(|| HttpResponse::NotFound()) /// ); /// } /// ``` diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index f0b90e77..c2001e00 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -11,5 +11,16 @@ pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; pub use self::normalize::NormalizePath; -// -// use actix_cors as cors; +#[cfg(feature = "deprecated")] +#[deprecated( + since = "1.0.1", + note = "please use `actix_cors` instead. support will be removed in actix-web 1.0.2" +)] +pub use actix_cors as cors; + +#[cfg(feature = "deprecated")] +#[deprecated( + since = "1.0.1", + note = "please use `actix_identity` instead. support will be removed in actix-web 1.0.2" +)] +pub use actix_identity as identity; From d293ae2a691fb6f9b9981346643e56932114d2da Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Jun 2019 22:12:20 +0600 Subject: [PATCH 1452/1635] fix nested resource map registration #915 --- CHANGES.md | 4 ++++ src/rmap.rs | 3 ++- src/scope.rs | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1f34b797..87729eb6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +20,10 @@ * Re-apply patch from #637 #894 +### Fixed + +* HttpRequest::url_for is broken with nested scopes #915 + ## [1.0.0] - 2019-06-05 diff --git a/src/rmap.rs b/src/rmap.rs index 6a543b75..cad62dca 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -38,7 +38,8 @@ impl ResourceMap { pub(crate) fn finish(&self, current: Rc) { for (_, nested) in &self.patterns { if let Some(ref nested) = nested { - *nested.parent.borrow_mut() = Some(current.clone()) + *nested.parent.borrow_mut() = Some(current.clone()); + nested.finish(nested.clone()); } } } diff --git a/src/scope.rs b/src/scope.rs index ad97fcb6..400da668 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1135,4 +1135,24 @@ mod tests { let body = read_body(resp); assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); } + + #[test] + fn test_url_for_nested() { + let mut srv = init_service(App::new().service(web::scope("/a").service( + web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( + web::get().to(|req: HttpRequest| { + HttpResponse::Ok() + .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) + }), + )), + ))); + let req = TestRequest::with_uri("/a/b/c/test").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp); + assert_eq!( + body, + Bytes::from_static(b"http://localhost:8080/a/b/c/12345") + ); + } } From eaa371db8b6a2d1637c4cc31855fe029b576b6a6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 15 Jun 2019 22:20:46 +0600 Subject: [PATCH 1453/1635] update migration --- MIGRATION.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index a2591a1d..5273a013 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,19 @@ ## 1.0.1 +* Cors middleware has been moved to `actix-cors` crate + + instead of + + ```rust + use actix_web::middleware::cors::Cors; + ``` + + use + + ```rust + use actix_cors::Cors; + ``` + * Identity middleware has been moved to `actix-identity` crate instead of From 7c0f57084559ce9c97fb09a49047e8c9059551f8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 16 Jun 2019 21:54:17 +0600 Subject: [PATCH 1454/1635] Do not compress NoContent (204) responses #918 --- Cargo.toml | 4 ++-- actix-http/CHANGES.md | 7 +++++++ actix-http/Cargo.toml | 4 ++-- actix-http/src/encoding/encoder.rs | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8531a93a..a1863568 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,10 +89,10 @@ actix-cors = { version = "0.1.0", optional = true } actix-identity = { version = "0.1.0", optional = true } bytes = "0.4" -derive_more = "0.14" +derive_more = "0.15.0" encoding = "0.2" futures = "0.1.25" -hashbrown = "0.3.1" +hashbrown = "0.5.0" log = "0.4" mime = "0.3" net2 = "0.2.33" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d0c75da7..93c35289 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.2.4] - 2019-06-16 + +### Fixed + +* Do not compress NoContent (204) responses #918 + + ## [0.2.3] - 2019-06-02 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1847a5ba..4411bdf6 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -56,11 +56,11 @@ bitflags = "1.0" bytes = "0.4" byteorder = "1.2" copyless = "0.1.2" -derive_more = "0.14" +derive_more = "0.15.0" either = "1.5.2" encoding = "0.2" futures = "0.1.25" -hashbrown = "0.3.0" +hashbrown = "0.5.0" h2 = "0.1.16" http = "0.1.17" httparse = "1.3" diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index aabce292..d793eb4c 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -33,6 +33,7 @@ impl Encoder { ) -> ResponseBody> { let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) || head.status == StatusCode::SWITCHING_PROTOCOLS + || head.status == StatusCode::NO_CONTENT || encoding == ContentEncoding::Identity || encoding == ContentEncoding::Auto); @@ -122,6 +123,7 @@ impl MessageBody for Encoder { Async::NotReady => return Ok(Async::NotReady), Async::Ready(Some(chunk)) => { if let Some(mut encoder) = self.encoder.take() { + self.encoded += chunk.len(); if chunk.len() < INPLACE { encoder.write(&chunk)?; let chunk = encoder.take(); From d2b6502c7a743bed3f10eea269aa4031930a1972 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 16 Jun 2019 21:59:22 +0600 Subject: [PATCH 1455/1635] prepare actix-http release --- actix-http/Cargo.toml | 2 +- actix-http/src/encoding/encoder.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 4411bdf6..2da41013 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.3" +version = "0.2.4" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index d793eb4c..fa95d798 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -123,7 +123,6 @@ impl MessageBody for Encoder { Async::NotReady => return Ok(Async::NotReady), Async::Ready(Some(chunk)) => { if let Some(mut encoder) = self.encoder.take() { - self.encoded += chunk.len(); if chunk.len() < INPLACE { encoder.write(&chunk)?; let chunk = encoder.take(); From 686e5f1595b4a740d695d94c23cd5c8bb3b35872 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 16 Jun 2019 22:10:22 +0600 Subject: [PATCH 1456/1635] update deps --- Cargo.toml | 6 +++--- actix-files/Cargo.toml | 6 +++--- actix-multipart/Cargo.toml | 6 +++--- actix-session/Cargo.toml | 8 ++++---- actix-web-actors/Cargo.toml | 4 ++-- actix-web-codegen/Cargo.toml | 4 ++-- awc/Cargo.toml | 10 +++++----- test-server/CHANGES.md | 4 ++++ test-server/Cargo.toml | 8 ++++---- 9 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1863568..a4c37cfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,12 +73,12 @@ depracated = ["actix-cors", "actix-identity"] [dependencies] actix-codec = "0.1.2" -actix-service = "0.4.0" +actix-service = "0.4.1" actix-utils = "0.4.1" actix-router = "0.1.5" actix-rt = "0.2.2" actix-web-codegen = "0.1.2" -actix-http = "0.2.3" +actix-http = "0.2.4" actix-server = "0.5.1" actix-server-config = "0.1.1" actix-threadpool = "0.1.1" @@ -110,7 +110,7 @@ rustls = { version = "0.15", optional = true } [dev-dependencies] actix = { version = "0.8.3" } -actix-http = { version = "0.2.3", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.2.4", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.0", features=["ssl"] } rand = "0.6" env_logger = "0.6" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b1428a22..9df93834 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,12 +19,12 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "1.0.0", default-features = false } -actix-http = "0.2.3" -actix-service = "0.4.0" +actix-http = "0.2.4" +actix-service = "0.4.1" bitflags = "1" bytes = "0.4" futures = "0.1.25" -derive_more = "0.14" +derive_more = "0.15.0" log = "0.4" mime = "0.3" mime_guess = "2.0.0-alpha" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index d377be1f..b26681e2 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -19,9 +19,9 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "1.0.0", default-features = false } -actix-service = "0.4.0" +actix-service = "0.4.1" bytes = "0.4" -derive_more = "0.14" +derive_more = "0.15.0" httparse = "1.3" futures = "0.1.25" log = "0.4" @@ -31,4 +31,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "0.2.2" -actix-http = "0.2.2" \ No newline at end of file +actix-http = "0.2.4" \ No newline at end of file diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 1101ceff..4c1d6657 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -24,12 +24,12 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0-rc" -actix-service = "0.4.0" +actix-web = "1.0.0" +actix-service = "0.4.1" bytes = "0.4" -derive_more = "0.14" +derive_more = "0.15.0" futures = "0.1.25" -hashbrown = "0.3.0" +hashbrown = "0.5.0" serde = "1.0" serde_json = "1.0" time = "0.1.42" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 565b53a5..90d0a00f 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,8 +19,8 @@ path = "src/lib.rs" [dependencies] actix = "0.8.3" -actix-web = "1.0.0-rc" -actix-http = "0.2.2" +actix-web = "1.0.0" +actix-http = "0.2.4" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 23e9a432..29abb489 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -16,7 +16,7 @@ quote = "0.6.12" syn = { version = "0.15.34", features = ["full", "parsing", "extra-traits"] } [dev-dependencies] -actix-web = { version = "1.0.0-rc" } -actix-http = { version = "0.2.2", features=["ssl"] } +actix-web = { version = "1.0.0" } +actix-http = { version = "0.2.4", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } futures = { version = "0.1" } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index cad52033..d0629f4f 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -40,11 +40,11 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" -actix-service = "0.4.0" -actix-http = "0.2.3" +actix-service = "0.4.1" +actix-http = "0.2.4" base64 = "0.10.1" bytes = "0.4" -derive_more = "0.14" +derive_more = "0.15.0" futures = "0.1.25" log =" 0.4" mime = "0.3" @@ -58,8 +58,8 @@ openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0-rc", features=["ssl"] } -actix-http = { version = "0.2.3", features=["ssl"] } +actix-web = { version = "1.0.0", features=["ssl"] } +actix-http = { version = "0.2.4", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } actix-utils = "0.4.1" actix-server = { version = "0.5.1", features=["ssl"] } diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index a3193790..e7292c0e 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.2.2] - 2019-06-16 + +* Add .put() and .sput() methods + ## [0.2.1] - 2019-06-05 * Add license files diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index a8f4425b..4231b17b 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.2.1" +version = "0.2.2" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -32,7 +32,7 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] [dependencies] actix-codec = "0.1.2" actix-rt = "0.2.2" -actix-service = "0.4.0" +actix-service = "0.4.1" actix-server = "0.5.1" actix-utils = "0.4.1" awc = "0.2.1" @@ -55,5 +55,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0-rc" -actix-http = "0.2.3" +actix-web = "1.0.0" +actix-http = "0.2.4" From acda1c075a000a8b94f04b25b8d3668954d7938e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 17 Jun 2019 12:23:30 +0600 Subject: [PATCH 1457/1635] prepare actix-web release --- CHANGES.md | 2 +- Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 87729eb6..38526413 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.1] - 2019-06-xx +## [1.0.1] - 2019-06-17 ### Add diff --git a/Cargo.toml b/Cargo.toml index a4c37cfa..d8a143d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.0" +version = "1.0.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -111,7 +111,7 @@ rustls = { version = "0.15", optional = true } [dev-dependencies] actix = { version = "0.8.3" } actix-http = { version = "0.2.4", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.2", features=["ssl"] } rand = "0.6" env_logger = "0.6" serde_derive = "1.0" From 546a8a58db0e9f985310ae1ed56878a089f7ba09 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 17 Jun 2019 12:33:00 +0600 Subject: [PATCH 1458/1635] remove cors and identity middlewares --- CHANGES.md | 9 +++++++++ Cargo.toml | 11 ++--------- src/middleware/mod.rs | 14 -------------- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 38526413..9f899ea9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,14 @@ # Changes +## [1.0.2] - 2019-06-17 + +### Changes + +* Move cors middleware to `actix-cors` crate. + +* Move identity middleware to `actix-identity` crate. + + ## [1.0.1] - 2019-06-17 ### Add diff --git a/Cargo.toml b/Cargo.toml index d8a143d6..996d9470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ members = [ ] [features] -default = ["brotli", "flate2-zlib", "client", "fail", "depracated"] +default = ["brotli", "flate2-zlib", "client", "fail"] # http client client = ["awc"] @@ -68,9 +68,6 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] # rustls rust-tls = ["rustls", "actix-server/rust-tls"] -# deprecated middlewares -depracated = ["actix-cors", "actix-identity"] - [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" @@ -84,10 +81,6 @@ actix-server-config = "0.1.1" actix-threadpool = "0.1.1" awc = { version = "0.2.1", optional = true } -# deprecated middlewares -actix-cors = { version = "0.1.0", optional = true } -actix-identity = { version = "0.1.0", optional = true } - bytes = "0.4" derive_more = "0.15.0" encoding = "0.2" @@ -125,7 +118,7 @@ opt-level = 3 codegen-units = 1 [patch.crates-io] -# actix-web = { path = "." } +actix-web = { path = "." } actix-http = { path = "actix-http" } actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index c2001e00..814993f0 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -10,17 +10,3 @@ mod normalize; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; pub use self::normalize::NormalizePath; - -#[cfg(feature = "deprecated")] -#[deprecated( - since = "1.0.1", - note = "please use `actix_cors` instead. support will be removed in actix-web 1.0.2" -)] -pub use actix_cors as cors; - -#[cfg(feature = "deprecated")] -#[deprecated( - since = "1.0.1", - note = "please use `actix_identity` instead. support will be removed in actix-web 1.0.2" -)] -pub use actix_identity as identity; From ad0e6f73b3edd666fced6c29e8dd74f3991dab66 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 17 Jun 2019 12:35:00 +0600 Subject: [PATCH 1459/1635] update version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 996d9470..9f7f8776 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.1" +version = "1.0.2" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From d7780d53c929fcd650710c35d9d276aeab822af3 Mon Sep 17 00:00:00 2001 From: Joe Roberts Date: Tue, 18 Jun 2019 02:27:23 +0100 Subject: [PATCH 1460/1635] Fix typo in `actix_web::web::Data::get_ref docstring` (#921) --- src/data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data.rs b/src/data.rs index 9fd8b67f..bd166b79 100644 --- a/src/data.rs +++ b/src/data.rs @@ -73,7 +73,7 @@ impl Data { Data(Arc::new(state)) } - /// Get referecnce to inner app data. + /// Get reference to inner app data. pub fn get_ref(&self) -> &T { self.0.as_ref() } From 313ac4876586472ca070cabc647d164971688b76 Mon Sep 17 00:00:00 2001 From: messense Date: Tue, 18 Jun 2019 14:43:25 +0800 Subject: [PATCH 1461/1635] Use encoding_rs crate instead of unmaintained encoding crate (#922) * Use encoding_rs crate instead of unmaintained encoding crate * Update changelog --- CHANGES.md | 16 +++++++++++----- Cargo.toml | 2 +- actix-http/CHANGES.md | 8 +++++++- actix-http/Cargo.toml | 2 +- actix-http/src/httpmessage.rs | 15 +++++++-------- src/types/form.rs | 13 ++++++------- src/types/payload.rs | 11 +++++------ src/types/readlines.rs | 35 +++++++++++++++++++---------------- 8 files changed, 57 insertions(+), 45 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9f899ea9..a2071310 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,14 @@ # Changes +## [1.0.3] - unreleased + +### Changed + +* Use `encoding_rs` crate instead of unmaintained `encoding` crate + ## [1.0.2] - 2019-06-17 -### Changes +### Changed * Move cors middleware to `actix-cors` crate. @@ -17,7 +23,7 @@ * Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. -### Changes +### Changed * Move cors middleware to `actix-cors` crate. @@ -47,7 +53,7 @@ * Add macros for head, options, trace, connect and patch http methods -### Changes +### Changed * Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 @@ -65,7 +71,7 @@ * Add `Query::from_query()` to extract parameters from a query string. #846 * `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. -### Changes +### Changed * `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. @@ -80,7 +86,7 @@ * Allow to set/override app data on scope level -### Changes +### Changed * `App::configure` take an `FnOnce` instead of `Fn` * Upgrade actix-net crates diff --git a/Cargo.toml b/Cargo.toml index 9f7f8776..4f8cd745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ awc = { version = "0.2.1", optional = true } bytes = "0.4" derive_more = "0.15.0" -encoding = "0.2" +encoding_rs = "0.8" futures = "0.1.25" hashbrown = "0.5.0" log = "0.4" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 93c35289..891967f1 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.2.5] - unreleased + +### Changed + +* Use `encoding_rs` crate instead of unmaintained `encoding` crate + ## [0.2.4] - 2019-06-16 ### Fixed @@ -83,7 +89,7 @@ ## [0.1.1] - 2019-04-19 -### Changes +### Changed * Cookie::max_age() accepts value in seconds diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2da41013..c3930a7a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -58,7 +58,7 @@ byteorder = "1.2" copyless = "0.1.2" derive_more = "0.15.0" either = "1.5.2" -encoding = "0.2" +encoding_rs = "0.8" futures = "0.1.25" hashbrown = "0.5.0" h2 = "0.1.16" diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/httpmessage.rs index 1534973a..05d668c1 100644 --- a/actix-http/src/httpmessage.rs +++ b/actix-http/src/httpmessage.rs @@ -1,9 +1,7 @@ use std::cell::{Ref, RefMut}; use std::str; -use encoding::all::UTF_8; -use encoding::label::encoding_from_whatwg_label; -use encoding::EncodingRef; +use encoding_rs::{Encoding, UTF_8}; use http::header; use mime::Mime; @@ -59,10 +57,12 @@ pub trait HttpMessage: Sized { /// Get content type encoding /// /// UTF-8 is used by default, If request charset is not set. - fn encoding(&self) -> Result { + fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> { if let Some(mime_type) = self.mime_type()? { if let Some(charset) = mime_type.get_param("charset") { - if let Some(enc) = encoding_from_whatwg_label(charset.as_str()) { + if let Some(enc) = + Encoding::for_label_no_replacement(charset.as_str().as_bytes()) + { Ok(enc) } else { Err(ContentTypeError::UnknownEncoding) @@ -166,8 +166,7 @@ where #[cfg(test)] mod tests { use bytes::Bytes; - use encoding::all::ISO_8859_2; - use encoding::Encoding; + use encoding_rs::ISO_8859_2; use mime; use super::*; @@ -223,7 +222,7 @@ mod tests { "application/json; charset=ISO-8859-2", ) .finish(); - assert_eq!(ISO_8859_2.name(), req.encoding().unwrap().name()); + assert_eq!(ISO_8859_2, req.encoding().unwrap()); } #[test] diff --git a/src/types/form.rs b/src/types/form.rs index 0bc6a030..32d0edb6 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -5,9 +5,7 @@ use std::{fmt, ops}; use actix_http::{Error, HttpMessage, Payload}; use bytes::BytesMut; -use encoding::all::UTF_8; -use encoding::types::{DecoderTrap, Encoding}; -use encoding::EncodingRef; +use encoding_rs::{Encoding, UTF_8}; use futures::{Future, Poll, Stream}; use serde::de::DeserializeOwned; @@ -187,7 +185,7 @@ pub struct UrlEncoded { stream: Option>, limit: usize, length: Option, - encoding: EncodingRef, + encoding: &'static Encoding, err: Option, fut: Option>>, } @@ -286,13 +284,14 @@ where } }) .and_then(move |body| { - if (encoding as *const Encoding) == UTF_8 { + if encoding == UTF_8 { serde_urlencoded::from_bytes::(&body) .map_err(|_| UrlencodedError::Parse) } else { let body = encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| UrlencodedError::Parse)?; + .decode_without_bom_handling_and_without_replacement(&body) + .map(|s| s.into_owned()) + .ok_or(UrlencodedError::Parse)?; serde_urlencoded::from_str::(&body) .map_err(|_| UrlencodedError::Parse) } diff --git a/src/types/payload.rs b/src/types/payload.rs index 8e4dd703..a8e85e4f 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -4,8 +4,7 @@ use std::str; use actix_http::error::{Error, ErrorBadRequest, PayloadError}; use actix_http::HttpMessage; use bytes::{Bytes, BytesMut}; -use encoding::all::UTF_8; -use encoding::types::{DecoderTrap, Encoding}; +use encoding_rs::UTF_8; use futures::future::{err, Either, FutureResult}; use futures::{Future, Poll, Stream}; use mime::Mime; @@ -208,15 +207,15 @@ impl FromRequest for String { .limit(limit) .from_err() .and_then(move |body| { - let enc: *const Encoding = encoding as *const Encoding; - if enc == UTF_8 { + if encoding == UTF_8 { Ok(str::from_utf8(body.as_ref()) .map_err(|_| ErrorBadRequest("Can not decode body"))? .to_owned()) } else { Ok(encoding - .decode(&body, DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))?) + .decode_without_bom_handling_and_without_replacement(&body) + .map(|s| s.into_owned()) + .ok_or_else(|| ErrorBadRequest("Can not decode body"))?) } }), )) diff --git a/src/types/readlines.rs b/src/types/readlines.rs index c23b8443..cea63e43 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -1,9 +1,8 @@ +use std::borrow::Cow; use std::str; use bytes::{Bytes, BytesMut}; -use encoding::all::UTF_8; -use encoding::types::{DecoderTrap, Encoding}; -use encoding::EncodingRef; +use encoding_rs::{Encoding, UTF_8}; use futures::{Async, Poll, Stream}; use crate::dev::Payload; @@ -16,7 +15,7 @@ pub struct Readlines { buff: BytesMut, limit: usize, checked_buff: bool, - encoding: EncodingRef, + encoding: &'static Encoding, err: Option, } @@ -87,15 +86,17 @@ where if ind + 1 > self.limit { return Err(ReadlinesError::LimitOverflow); } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { + let line = if self.encoding == UTF_8 { str::from_utf8(&self.buff.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { self.encoding - .decode(&self.buff.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? + .decode_without_bom_handling_and_without_replacement( + &self.buff.split_to(ind + 1), + ) + .map(Cow::into_owned) + .ok_or(ReadlinesError::EncodingError)? }; return Ok(Async::Ready(Some(line))); } @@ -117,15 +118,17 @@ where if ind + 1 > self.limit { return Err(ReadlinesError::LimitOverflow); } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { + let line = if self.encoding == UTF_8 { str::from_utf8(&bytes.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { self.encoding - .decode(&bytes.split_to(ind + 1), DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? + .decode_without_bom_handling_and_without_replacement( + &bytes.split_to(ind + 1), + ) + .map(Cow::into_owned) + .ok_or(ReadlinesError::EncodingError)? }; // extend buffer with rest of the bytes; self.buff.extend_from_slice(&bytes); @@ -143,15 +146,15 @@ where if self.buff.len() > self.limit { return Err(ReadlinesError::LimitOverflow); } - let enc: *const Encoding = self.encoding as *const Encoding; - let line = if enc == UTF_8 { + let line = if self.encoding == UTF_8 { str::from_utf8(&self.buff) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { self.encoding - .decode(&self.buff, DecoderTrap::Strict) - .map_err(|_| ReadlinesError::EncodingError)? + .decode_without_bom_handling_and_without_replacement(&self.buff) + .map(Cow::into_owned) + .ok_or(ReadlinesError::EncodingError)? }; self.buff.clear(); Ok(Async::Ready(Some(line))) From 47fab0e393802030808c842565c918c06c22c278 Mon Sep 17 00:00:00 2001 From: messense Date: Wed, 19 Jun 2019 18:41:42 +0800 Subject: [PATCH 1462/1635] Bump derive_more crate version to 0.15.0 in actix-cors (#927) --- actix-cors/CHANGES.md | 4 ++++ actix-cors/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/actix-cors/CHANGES.md b/actix-cors/CHANGES.md index 1e842f37..10e408ed 100644 --- a/actix-cors/CHANGES.md +++ b/actix-cors/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.1] - unreleased + +* Bump `derive_more` crate version to 0.15.0 + ## [0.1.0] - 2019-06-15 * Move cors middleware to separate crate diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml index 98ed67a2..091c9404 100644 --- a/actix-cors/Cargo.toml +++ b/actix-cors/Cargo.toml @@ -19,5 +19,5 @@ path = "src/lib.rs" [dependencies] actix-web = "1.0.0" actix-service = "0.4.0" -derive_more = "0.14.1" +derive_more = "0.15.0" futures = "0.1.25" From 1a24ff871728bacaf4853c4000a9d2be665abd56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Vis=C3=A9e?= Date: Fri, 21 Jun 2019 09:06:29 +0200 Subject: [PATCH 1463/1635] Add builder function for HTTP 429 Too Many Requests status (#931) --- actix-http/src/httpcodes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/httpcodes.rs index e7eda2da..3cac35eb 100644 --- a/actix-http/src/httpcodes.rs +++ b/actix-http/src/httpcodes.rs @@ -61,6 +61,7 @@ impl Response { STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); + STATIC_RESP!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); From b948f74b540c70036cef60d16cba554ffb71f0cd Mon Sep 17 00:00:00 2001 From: Dustin Bensing Date: Mon, 24 Jun 2019 03:16:04 +0200 Subject: [PATCH 1464/1635] Extractor configuration Migration (#937) added guide for Extractor configuration in MIGRATION.md --- MIGRATION.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 5273a013..2f0f369a 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -31,6 +31,64 @@ ## 1.0.0 +* Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration + + instead of + + ```rust + + #[derive(Default)] + struct ExtractorConfig { + config: String, + } + + impl FromRequest for YourExtractor { + type Config = ExtractorConfig; + type Result = Result; + + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + println!("use the config: {:?}", cfg.config); + ... + } + } + + App::new().resource("/route_with_config", |r| { + r.post().with_config(handler_fn, |cfg| { + cfg.0.config = "test".to_string(); + }) + }) + + ``` + + use the HttpRequest to get the configuration like any other `Data` with `req.app_data::()` and set it with the `data()` method on the `resource` + + ```rust + #[derive(Default)] + struct ExtractorConfig { + config: String, + } + + impl FromRequest for YourExtractor { + type Error = Error; + type Future = Result; + type Config = ExtractorConfig; + + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { + let cfg = req.app_data::(); + println!("config data?: {:?}", cfg.unwrap().role); + ... + } + } + + App::new().service( + resource("/route_with_config") + .data(ExtractorConfig { + config: "test".to_string(), + }) + .route(post().to(handler_fn)), + ) + ``` + * Resource registration. 1.0 version uses generalized resource registration via `.service()` method. From fa7e0fe6df27c98da0117384b1e9ac5864b736d1 Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 24 Jun 2019 18:40:14 -0400 Subject: [PATCH 1465/1635] updated cookie.rs req to get_changes --- actix-session/src/cookie.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index ac08d114..55904f5b 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -308,7 +308,7 @@ where Session::set_session(state.into_iter(), &mut req); Box::new(self.service.call(req).map(move |mut res| { - if let Some(state) = Session::get_changes(&mut res) { + if let (_status, Some(state)) = Session::get_changes(&mut res) { res.checked_expr(|res| inner.set_cookie(res, state)) } else { res From c0c71f82c00fdac964bcf588c2ea49c4c18d5de7 Mon Sep 17 00:00:00 2001 From: Cameron Dershem Date: Tue, 25 Jun 2019 13:23:36 -0400 Subject: [PATCH 1466/1635] Fixes typo. (#940) Small typo fix. --- src/resource.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resource.rs b/src/resource.rs index ad08a15f..c2691eeb 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -27,7 +27,7 @@ type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error /// Resource in turn has at least one route. /// Route consists of an handlers objects and list of guards /// (objects that implement `Guard` trait). -/// Resources and rouets uses builder-like pattern for configuration. +/// Resources and routes uses builder-like pattern for configuration. /// During request handling, resource object iterate through all routes /// and check guards for specific route, if request matches all /// guards, route considered matched and route handler get called. From af9fb5d1908c425c522d54214291e6a80604e5d7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Jun 2019 10:43:52 +0600 Subject: [PATCH 1467/1635] Support asynchronous data factories #850 --- CHANGES.md | 9 +++++-- src/app.rs | 66 +++++++++++++++++++++++++++++++++------------- src/app_service.rs | 44 +++++++++++++++++++++++-------- 3 files changed, 88 insertions(+), 31 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a2071310..dcefdec5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,10 +2,15 @@ ## [1.0.3] - unreleased +### Added + +* Support asynchronous data factories #850 + ### Changed * Use `encoding_rs` crate instead of unmaintained `encoding` crate + ## [1.0.2] - 2019-06-17 ### Changed @@ -17,7 +22,7 @@ ## [1.0.1] - 2019-06-17 -### Add +### Added * Add support for PathConfig #903 @@ -42,7 +47,7 @@ ## [1.0.0] - 2019-06-05 -### Add +### Added * Add `Scope::configure()` method. diff --git a/src/app.rs b/src/app.rs index 897b3645..3b063a84 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,7 +8,7 @@ use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ apply_transform, IntoNewService, IntoTransform, NewService, Transform, }; -use futures::IntoFuture; +use futures::{Future, IntoFuture}; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; use crate::config::{AppConfig, AppConfigInner, ServiceConfig}; @@ -23,6 +23,7 @@ use crate::service::{ }; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; +type FnDataFactory = Box Box, Error = ()>>>; /// Application builder - structure that follows the builder pattern /// for building application instances. @@ -32,6 +33,7 @@ pub struct App { default: Option>, factory_ref: Rc>>, data: Vec>, + data_factories: Vec, config: AppConfigInner, external: Vec, _t: PhantomData<(B)>, @@ -44,6 +46,7 @@ impl App { App { endpoint: AppEntry::new(fref.clone()), data: Vec::new(), + data_factories: Vec::new(), services: Vec::new(), default: None, factory_ref: fref, @@ -100,6 +103,31 @@ where self } + /// Set application data factory. This function is + /// similar to `.data()` but it accepts data factory. Data object get + /// constructed asynchronously during application initialization. + pub fn data_factory(mut self, data: F) -> Self + where + F: Fn() -> Out + 'static, + Out: IntoFuture + 'static, + Out::Error: std::fmt::Debug, + { + self.data_factories.push(Box::new(move || { + Box::new( + data() + .into_future() + .map_err(|e| { + log::error!("Can not construct data instance: {:?}", e); + }) + .map(|data| { + let data: Box = Box::new(Data::new(data)); + data + }), + ) + })); + self + } + /// Set application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. pub fn register_data(mut self, data: Data) -> Self { @@ -349,6 +377,7 @@ where App { endpoint, data: self.data, + data_factories: self.data_factories, services: self.services, default: self.default, factory_ref: self.factory_ref, @@ -423,6 +452,7 @@ where fn into_new_service(self) -> AppInit { AppInit { data: Rc::new(self.data), + data_factories: Rc::new(self.data_factories), endpoint: self.endpoint, services: Rc::new(RefCell::new(self.services)), external: RefCell::new(self.external), @@ -490,24 +520,24 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); } - // #[test] - // fn test_data_factory() { - // let mut srv = - // init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( - // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - // )); - // let req = TestRequest::default().to_request(); - // let resp = block_on(srv.call(req)).unwrap(); - // assert_eq!(resp.status(), StatusCode::OK); + #[test] + fn test_data_factory() { + let mut srv = + init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - // let mut srv = - // init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( - // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - // )); - // let req = TestRequest::default().to_request(); - // let resp = block_on(srv.call(req)).unwrap(); - // assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - // } + let mut srv = + init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } fn md( req: ServiceRequest, diff --git a/src/app_service.rs b/src/app_service.rs index 5a9731bf..8ab9b352 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -25,6 +25,7 @@ type BoxedResponse = Either< FutureResult, Box>, >; +type FnDataFactory = Box Box, Error = ()>>>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. @@ -40,6 +41,7 @@ where { pub(crate) endpoint: T, pub(crate) data: Rc>>, + pub(crate) data_factories: Rc>, pub(crate) config: RefCell, pub(crate) services: Rc>>>, pub(crate) default: Option>, @@ -119,16 +121,12 @@ where let rmap = Rc::new(rmap); rmap.finish(rmap.clone()); - // create app data container - let mut data = Extensions::new(); - for f in self.data.iter() { - f.create(&mut data); - } - AppInitResult { endpoint: None, endpoint_fut: self.endpoint.new_service(&()), - data: Rc::new(data), + data: self.data.clone(), + data_factories: Vec::new(), + data_factories_fut: self.data_factories.iter().map(|f| f()).collect(), config, rmap, _t: PhantomData, @@ -144,7 +142,9 @@ where endpoint_fut: T::Future, rmap: Rc, config: AppConfig, - data: Rc, + data: Rc>>, + data_factories: Vec>, + data_factories_fut: Vec, Error = ()>>>, _t: PhantomData, } @@ -159,21 +159,43 @@ where >, { type Item = AppInitService; - type Error = T::InitError; + type Error = (); fn poll(&mut self) -> Poll { + // async data factories + let mut idx = 0; + while idx < self.data_factories_fut.len() { + match self.data_factories_fut[idx].poll()? { + Async::Ready(f) => { + self.data_factories.push(f); + self.data_factories_fut.remove(idx); + } + Async::NotReady => idx += 1, + } + } + if self.endpoint.is_none() { if let Async::Ready(srv) = self.endpoint_fut.poll()? { self.endpoint = Some(srv); } } - if self.endpoint.is_some() { + if self.endpoint.is_some() && self.data_factories_fut.is_empty() { + // create app data container + let mut data = Extensions::new(); + for f in self.data.iter() { + f.create(&mut data); + } + + for f in &self.data_factories { + f.create(&mut data); + } + Ok(Async::Ready(AppInitService { service: self.endpoint.take().unwrap(), rmap: self.rmap.clone(), config: self.config.clone(), - data: self.data.clone(), + data: Rc::new(data), pool: HttpRequestPool::create(), })) } else { From 44bb79cd07af726c23825fd19a0f028ba3fd3a80 Mon Sep 17 00:00:00 2001 From: messense Date: Fri, 28 Jun 2019 12:44:53 +0800 Subject: [PATCH 1468/1635] Call req.path() on Json extractor error only (#945) * Call req.path() on Json extractor error only * Cleanup len parse code --- src/types/json.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/types/json.rs b/src/types/json.rs index 0789fb61..de0ffb54 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -180,16 +180,14 @@ where .map(|c| (c.limit, c.ehandler.clone(), c.content_type.clone())) .unwrap_or((32768, None, None)); - let path = req.path().to_string(); - Box::new( JsonBody::new(req, payload, ctype) .limit(limit) .map_err(move |e| { log::debug!( "Failed to deserialize Json from payload. \ - Request path: {:?}", - path + Request path: {}", + req2.path() ); if let Some(err) = err { (*err)(e, &req2) @@ -324,14 +322,11 @@ where }; } - let mut len = None; - if let Some(l) = req.headers().get(&CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } - } - } + let len = req + .headers() + .get(&CONTENT_LENGTH) + .and_then(|l| l.to_str().ok()) + .and_then(|s| s.parse::().ok()); let payload = Decompress::from_headers(payload.take(), req.headers()); JsonBody { From 768859513a5b3eeb1741ab7d5180c0c93eabfafe Mon Sep 17 00:00:00 2001 From: anthonyjchriste Date: Thu, 27 Jun 2019 18:49:03 -1000 Subject: [PATCH 1469/1635] Expose the max limit for payload sizes in Websocket Actors. #925 (#933) * Expose the max limit for payload sizes in Websocket Actors. * Revert to previous not-formatted code. * Implement WebsocketContext::with_codec and make Codec Copy and Clone. * Fix formatting. * Fix formatting. --- actix-http/src/ws/codec.rs | 2 +- actix-web-actors/src/ws.rs | 33 +++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index ad599ffa..9891bfa6 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -37,7 +37,7 @@ pub enum Frame { Close(Option), } -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] /// WebSockets protocol codec pub struct Codec { max_size: usize, diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 0ef3c916..16f475a7 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -177,9 +177,26 @@ where inner: ContextParts::new(mb.sender_producer()), messages: VecDeque::new(), }; - ctx.add_stream(WsStream::new(stream)); + ctx.add_stream(WsStream::new(stream, Codec::new())); - WebsocketContextFut::new(ctx, actor, mb) + WebsocketContextFut::new(ctx, actor, mb, Codec::new()) + } + + #[inline] + /// Create a new Websocket context from a request, an actor, and a codec + pub fn with_codec(actor: A, stream: S, codec: Codec) -> impl Stream + where + A: StreamHandler, + S: Stream + 'static, + { + let mb = Mailbox::default(); + let mut ctx = WebsocketContext { + inner: ContextParts::new(mb.sender_producer()), + messages: VecDeque::new(), + }; + ctx.add_stream(WsStream::new(stream, codec)); + + WebsocketContextFut::new(ctx, actor, mb, codec) } /// Create a new Websocket context @@ -197,11 +214,11 @@ where inner: ContextParts::new(mb.sender_producer()), messages: VecDeque::new(), }; - ctx.add_stream(WsStream::new(stream)); + ctx.add_stream(WsStream::new(stream, Codec::new())); let act = f(&mut ctx); - WebsocketContextFut::new(ctx, act, mb) + WebsocketContextFut::new(ctx, act, mb, Codec::new()) } } @@ -288,11 +305,11 @@ impl WebsocketContextFut where A: Actor>, { - fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox) -> Self { + fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox, codec: Codec) -> Self { let fut = ContextFut::new(ctx, act, mailbox); WebsocketContextFut { fut, - encoder: Codec::new(), + encoder: codec, buf: BytesMut::new(), closed: false, } @@ -353,10 +370,10 @@ impl WsStream where S: Stream, { - fn new(stream: S) -> Self { + fn new(stream: S, codec: Codec) -> Self { Self { stream, - decoder: Codec::new(), + decoder: codec, buf: BytesMut::new(), closed: false, } From 596483ff55b6aceba1d78adec8a27b347130a789 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Jun 2019 10:54:23 +0600 Subject: [PATCH 1470/1635] prepare actix-web-actors release --- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 4 ++-- actix-web-actors/src/ws.rs | 12 ++++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 89b4be81..115af87b 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [1.0.1] - 2019-06-28 + +* Allow to use custom ws codec with `WebsocketContext` #925 + ## [1.0.0] - 2019-05-29 * Update actix-http and actix-web diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 90d0a00f..864d8d95 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.0" +version = "1.0.1" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix = "0.8.3" -actix-web = "1.0.0" +actix-web = "1.0.2" actix-http = "0.2.4" actix-codec = "0.1.2" bytes = "0.4" diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 16f475a7..fece826d 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -184,10 +184,14 @@ where #[inline] /// Create a new Websocket context from a request, an actor, and a codec - pub fn with_codec(actor: A, stream: S, codec: Codec) -> impl Stream - where - A: StreamHandler, - S: Stream + 'static, + pub fn with_codec( + actor: A, + stream: S, + codec: Codec, + ) -> impl Stream + where + A: StreamHandler, + S: Stream + 'static, { let mb = Mailbox::default(); let mut ctx = WebsocketContext { From a3a78ac6fb50c17b73f9d4ac6cac816ceae68bb3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Jun 2019 11:42:20 +0600 Subject: [PATCH 1471/1635] Do not set Content-Length header, let actix-http set it #930 --- actix-files/CHANGES.md | 5 +++++ actix-files/Cargo.toml | 6 ++--- actix-files/src/lib.rs | 48 ++++++++++++++++++++-------------------- actix-files/src/named.rs | 2 -- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index e79d8096..2f98e15c 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.3] - 2019-06-28 + +* Do not set `Content-Length` header, let actix-http set it #930 + + ## [0.1.2] - 2019-06-13 * Content-Length is 0 for NamedFile HEAD request #914 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 9df93834..c9d9cfec 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.2" +version = "0.1.3" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "1.0.0", default-features = false } +actix-web = { version = "1.0.2", default-features = false } actix-http = "0.2.4" actix-service = "0.4.1" bitflags = "1" @@ -32,4 +32,4 @@ percent-encoding = "1.0" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.0", features=["ssl"] } +actix-web = { version = "1.0.2", features=["ssl"] } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 9f526f3f..8e87f7d8 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -855,6 +855,8 @@ mod tests { #[test] fn test_named_file_content_length_headers() { + use actix_web::body::{MessageBody, ResponseBody}; + let mut srv = test::init_service( App::new().service(Files::new("test", ".").index_file("tests/test.binary")), ); @@ -866,14 +868,13 @@ mod tests { .to_request(); let response = test::call_service(&mut srv, request); - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "11"); + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "11"); // Invalid range header let request = TestRequest::get() @@ -890,14 +891,13 @@ mod tests { .to_request(); let response = test::call_service(&mut srv, request); - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "100"); + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "100"); // chunked let request = TestRequest::get() @@ -939,14 +939,14 @@ mod tests { .to_request(); let response = test::call_service(&mut srv, request); - let contentlength = response - .headers() - .get(header::CONTENT_LENGTH) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentlength, "100"); + // TODO: fix check + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "100"); } #[test] diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 3ece7c21..6b948da8 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -414,8 +414,6 @@ impl Responder for NamedFile { }; }; - resp.header(header::CONTENT_LENGTH, format!("{}", length)); - if precondition_failed { return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); } else if not_modified { From cac162aed765431d1e405f7aeb276425bd62031a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Jun 2019 12:34:43 +0600 Subject: [PATCH 1472/1635] update actix-http changes --- .travis.yml | 2 +- actix-http/CHANGES.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2dea00c5..5f7d01a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: before_cache: | if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin fi # Add clippy diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 891967f1..6dea516d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,7 +4,10 @@ ### Changed -* Use `encoding_rs` crate instead of unmaintained `encoding` crate +* Use `encoding_rs` crate instead of unmaintained `encoding` crate + +* Add `Copy` and `Clone` impls for `ws::Codec` + ## [0.2.4] - 2019-06-16 From d286ccb4f5a86eca12c65b1632506a8bd8b37d19 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Jun 2019 14:34:26 +0600 Subject: [PATCH 1473/1635] Add on-connect callback #946 --- actix-http/CHANGES.md | 6 ++++- actix-http/Cargo.toml | 6 ++--- actix-http/src/builder.rs | 21 +++++++++++++++ actix-http/src/h1/dispatcher.rs | 17 ++++++++++-- actix-http/src/h1/service.rs | 33 ++++++++++++++++++++++- actix-http/src/h2/dispatcher.rs | 6 ++++- actix-http/src/h2/service.rs | 36 ++++++++++++++++++++++++-- actix-http/src/helpers.rs | 14 ++++++++++ actix-http/src/service.rs | 46 ++++++++++++++++++++++++++++++--- 9 files changed, 171 insertions(+), 14 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6dea516d..636cbedf 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## [0.2.5] - unreleased +## [0.2.5] - 2019-06-28 + +### Added + +* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 ### Changed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c3930a7a..afbf0a48 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.4" +version = "0.2.5" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -44,10 +44,10 @@ fail = ["failure"] secure-cookies = ["ring"] [dependencies] -actix-service = "0.4.0" +actix-service = "0.4.1" actix-codec = "0.1.2" actix-connect = "0.2.0" -actix-utils = "0.4.1" +actix-utils = "0.4.2" actix-server-config = "0.1.1" actix-threadpool = "0.1.0" diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index b1b193a9..b6967d94 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -1,5 +1,6 @@ use std::fmt; use std::marker::PhantomData; +use std::rc::Rc; use actix_codec::Framed; use actix_server_config::ServerConfig as SrvConfig; @@ -10,6 +11,7 @@ use crate::config::{KeepAlive, ServiceConfig}; use crate::error::Error; use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler}; use crate::h2::H2Service; +use crate::helpers::{Data, DataFactory}; use crate::request::Request; use crate::response::Response; use crate::service::HttpService; @@ -24,6 +26,7 @@ pub struct HttpServiceBuilder> { client_disconnect: u64, expect: X, upgrade: Option, + on_connect: Option Box>>, _t: PhantomData<(T, S)>, } @@ -41,6 +44,7 @@ where client_disconnect: 0, expect: ExpectHandler, upgrade: None, + on_connect: None, _t: PhantomData, } } @@ -115,6 +119,7 @@ where client_disconnect: self.client_disconnect, expect: expect.into_new_service(), upgrade: self.upgrade, + on_connect: self.on_connect, _t: PhantomData, } } @@ -140,10 +145,24 @@ where client_disconnect: self.client_disconnect, expect: self.expect, upgrade: Some(upgrade.into_new_service()), + on_connect: self.on_connect, _t: PhantomData, } } + /// Set on-connect callback. + /// + /// It get called once per connection and result of the call + /// get stored to the request's extensions. + pub fn on_connect(mut self, f: F) -> Self + where + F: Fn(&T) -> I + 'static, + I: Clone + 'static, + { + self.on_connect = Some(Rc::new(move |io| Box::new(Data(f(io))))); + self + } + /// Finish service configuration and create *http service* for HTTP/1 protocol. pub fn h1(self, service: F) -> H1Service where @@ -161,6 +180,7 @@ where H1Service::with_config(cfg, service.into_new_service()) .expect(self.expect) .upgrade(self.upgrade) + .on_connect(self.on_connect) } /// Finish service configuration and create *http service* for HTTP/2 protocol. @@ -199,5 +219,6 @@ where HttpService::with_config(cfg, service.into_new_service()) .expect(self.expect) .upgrade(self.upgrade) + .on_connect(self.on_connect) } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 220984f8..91990d05 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -16,6 +16,8 @@ use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; +use crate::helpers::DataFactory; +use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::Response; @@ -81,6 +83,7 @@ where service: CloneableService, expect: CloneableService, upgrade: Option>, + on_connect: Option>, flags: Flags, peer_addr: Option, error: Option, @@ -174,12 +177,13 @@ where U::Error: fmt::Display, { /// Create http/1 dispatcher. - pub fn new( + pub(crate) fn new( stream: T, config: ServiceConfig, service: CloneableService, expect: CloneableService, upgrade: Option>, + on_connect: Option>, ) -> Self { Dispatcher::with_timeout( stream, @@ -190,11 +194,12 @@ where service, expect, upgrade, + on_connect, ) } /// Create http/1 dispatcher with slow request timeout. - pub fn with_timeout( + pub(crate) fn with_timeout( io: T, codec: Codec, config: ServiceConfig, @@ -203,6 +208,7 @@ where service: CloneableService, expect: CloneableService, upgrade: Option>, + on_connect: Option>, ) -> Self { let keepalive = config.keep_alive_enabled(); let flags = if keepalive { @@ -234,6 +240,7 @@ where service, expect, upgrade, + on_connect, flags, ka_expire, ka_timer, @@ -495,6 +502,11 @@ where let pl = self.codec.message_type(); req.head_mut().peer_addr = self.peer_addr; + // on_connect data + if let Some(ref on_connect) = self.on_connect { + on_connect.set(&mut req.extensions_mut()); + } + if pl == MessageType::Stream && self.upgrade.is_some() { self.messages.push_back(DispatcherMessage::Upgrade(req)); break; @@ -851,6 +863,7 @@ mod tests { ), CloneableService::new(ExpectHandler), None, + None, ); assert!(h1.poll().is_err()); diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 2c0a48eb..192d1b59 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,5 +1,6 @@ use std::fmt; use std::marker::PhantomData; +use std::rc::Rc; use actix_codec::Framed; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; @@ -11,6 +12,7 @@ use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error, ParseError}; +use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; @@ -24,6 +26,7 @@ pub struct H1Service> { cfg: ServiceConfig, expect: X, upgrade: Option, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -44,6 +47,7 @@ where srv: service.into_new_service(), expect: ExpectHandler, upgrade: None, + on_connect: None, _t: PhantomData, } } @@ -55,6 +59,7 @@ where srv: service.into_new_service(), expect: ExpectHandler, upgrade: None, + on_connect: None, _t: PhantomData, } } @@ -79,6 +84,7 @@ where cfg: self.cfg, srv: self.srv, upgrade: self.upgrade, + on_connect: self.on_connect, _t: PhantomData, } } @@ -94,9 +100,19 @@ where cfg: self.cfg, srv: self.srv, expect: self.expect, + on_connect: self.on_connect, _t: PhantomData, } } + + /// Set on connect callback. + pub(crate) fn on_connect( + mut self, + f: Option Box>>, + ) -> Self { + self.on_connect = f; + self + } } impl NewService for H1Service @@ -133,6 +149,7 @@ where fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), expect: None, upgrade: None, + on_connect: self.on_connect.clone(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -157,6 +174,7 @@ where fut_upg: Option, expect: Option, upgrade: Option, + on_connect: Option Box>>, cfg: Option, _t: PhantomData<(T, P, B)>, } @@ -205,6 +223,7 @@ where service, self.expect.take().unwrap(), self.upgrade.take(), + self.on_connect.clone(), ))) } } @@ -214,6 +233,7 @@ pub struct H1ServiceHandler { srv: CloneableService, expect: CloneableService, upgrade: Option>, + on_connect: Option Box>>, cfg: ServiceConfig, _t: PhantomData<(T, P, B)>, } @@ -234,12 +254,14 @@ where srv: S, expect: X, upgrade: Option, + on_connect: Option Box>>, ) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), expect: CloneableService::new(expect), upgrade: upgrade.map(|s| CloneableService::new(s)), cfg, + on_connect, _t: PhantomData, } } @@ -292,12 +314,21 @@ where } fn call(&mut self, req: Self::Request) -> Self::Future { + let io = req.into_parts().0; + + let on_connect = if let Some(ref on_connect) = self.on_connect { + Some(on_connect(&io)) + } else { + None + }; + Dispatcher::new( - req.into_parts().0, + io, self.cfg.clone(), self.srv.clone(), self.expect.clone(), self.upgrade.clone(), + on_connect, ) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index e66ff63c..48d32993 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -22,6 +22,7 @@ use tokio_timer::Delay; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; +use crate::helpers::DataFactory; use crate::message::ResponseHead; use crate::payload::Payload; use crate::request::Request; @@ -33,6 +34,7 @@ const CHUNK_SIZE: usize = 16_384; pub struct Dispatcher, B: MessageBody> { service: CloneableService, connection: Connection, + on_connect: Option>, config: ServiceConfig, peer_addr: Option, ka_expire: Instant, @@ -49,9 +51,10 @@ where S::Response: Into>, B: MessageBody + 'static, { - pub fn new( + pub(crate) fn new( service: CloneableService, connection: Connection, + on_connect: Option>, config: ServiceConfig, timeout: Option, peer_addr: Option, @@ -77,6 +80,7 @@ where config, peer_addr, connection, + on_connect, ka_expire, ka_timer, _t: PhantomData, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index b4191f03..efc400da 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; use std::marker::PhantomData; -use std::{io, net}; +use std::{io, net, rc}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; @@ -16,6 +16,7 @@ use log::error; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error, ParseError, ResponseError}; +use crate::helpers::DataFactory; use crate::payload::Payload; use crate::request::Request; use crate::response::Response; @@ -26,6 +27,7 @@ use super::dispatcher::Dispatcher; pub struct H2Service { srv: S, cfg: ServiceConfig, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -43,6 +45,7 @@ where H2Service { cfg, + on_connect: None, srv: service.into_new_service(), _t: PhantomData, } @@ -52,10 +55,20 @@ where pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { H2Service { cfg, + on_connect: None, srv: service.into_new_service(), _t: PhantomData, } } + + /// Set on connect callback. + pub(crate) fn on_connect( + mut self, + f: Option Box>>, + ) -> Self { + self.on_connect = f; + self + } } impl NewService for H2Service @@ -79,6 +92,7 @@ where H2ServiceResponse { fut: self.srv.new_service(cfg).into_future(), cfg: Some(self.cfg.clone()), + on_connect: self.on_connect.clone(), _t: PhantomData, } } @@ -88,6 +102,7 @@ where pub struct H2ServiceResponse { fut: ::Future, cfg: Option, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -107,6 +122,7 @@ where let service = try_ready!(self.fut.poll()); Ok(Async::Ready(H2ServiceHandler::new( self.cfg.take().unwrap(), + self.on_connect.clone(), service, ))) } @@ -116,6 +132,7 @@ where pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -127,9 +144,14 @@ where S::Response: Into>, B: MessageBody + 'static, { - fn new(cfg: ServiceConfig, srv: S) -> H2ServiceHandler { + fn new( + cfg: ServiceConfig, + on_connect: Option Box>>, + srv: S, + ) -> H2ServiceHandler { H2ServiceHandler { cfg, + on_connect, srv: CloneableService::new(srv), _t: PhantomData, } @@ -161,11 +183,18 @@ where fn call(&mut self, req: Self::Request) -> Self::Future { let io = req.into_parts().0; let peer_addr = io.peer_addr(); + let on_connect = if let Some(ref on_connect) = self.on_connect { + Some(on_connect(&io)) + } else { + None + }; + H2ServiceHandlerResponse { state: State::Handshake( Some(self.srv.clone()), Some(self.cfg.clone()), peer_addr, + on_connect, server::handshake(io), ), } @@ -181,6 +210,7 @@ where Option>, Option, Option, + Option>, Handshake, ), } @@ -216,12 +246,14 @@ where ref mut srv, ref mut config, ref peer_addr, + ref mut on_connect, ref mut handshake, ) => match handshake.poll() { Ok(Async::Ready(conn)) => { self.state = State::Incoming(Dispatcher::new( srv.take().unwrap(), conn, + on_connect.take(), config.take().unwrap(), None, peer_addr.clone(), diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index e8dbcd82..e4583ee3 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -3,6 +3,8 @@ use std::{io, mem, ptr, slice}; use bytes::{BufMut, BytesMut}; use http::Version; +use crate::extensions::Extensions; + const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ 2021222324252627282930313233343536373839\ 4041424344454647484950515253545556575859\ @@ -180,6 +182,18 @@ impl<'a> io::Write for Writer<'a> { } } +pub(crate) trait DataFactory { + fn set(&self, ext: &mut Extensions); +} + +pub(crate) struct Data(pub(crate) T); + +impl DataFactory for Data { + fn set(&self, ext: &mut Extensions) { + ext.insert(self.0.clone()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index b762f3cb..1ac01880 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,5 +1,5 @@ use std::marker::PhantomData; -use std::{fmt, io, net}; +use std::{fmt, io, net, rc}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::{ @@ -15,6 +15,7 @@ use crate::body::MessageBody; use crate::builder::HttpServiceBuilder; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error}; +use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; use crate::{h1, h2::Dispatcher}; @@ -25,6 +26,7 @@ pub struct HttpService, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -61,6 +63,7 @@ where srv: service.into_new_service(), expect: h1::ExpectHandler, upgrade: None, + on_connect: None, _t: PhantomData, } } @@ -75,6 +78,7 @@ where srv: service.into_new_service(), expect: h1::ExpectHandler, upgrade: None, + on_connect: None, _t: PhantomData, } } @@ -104,6 +108,7 @@ where cfg: self.cfg, srv: self.srv, upgrade: self.upgrade, + on_connect: self.on_connect, _t: PhantomData, } } @@ -127,9 +132,19 @@ where cfg: self.cfg, srv: self.srv, expect: self.expect, + on_connect: self.on_connect, _t: PhantomData, } } + + /// Set on connect callback. + pub(crate) fn on_connect( + mut self, + f: Option Box>>, + ) -> Self { + self.on_connect = f; + self + } } impl NewService for HttpService @@ -167,6 +182,7 @@ where fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), expect: None, upgrade: None, + on_connect: self.on_connect.clone(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -180,6 +196,7 @@ pub struct HttpServiceResponse, expect: Option, upgrade: Option, + on_connect: Option Box>>, cfg: Option, _t: PhantomData<(T, P, B)>, } @@ -229,6 +246,7 @@ where service, self.expect.take().unwrap(), self.upgrade.take(), + self.on_connect.clone(), ))) } } @@ -239,6 +257,7 @@ pub struct HttpServiceHandler { expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, + on_connect: Option Box>>, _t: PhantomData<(T, P, B, X)>, } @@ -259,9 +278,11 @@ where srv: S, expect: X, upgrade: Option, + on_connect: Option Box>>, ) -> HttpServiceHandler { HttpServiceHandler { cfg, + on_connect, srv: CloneableService::new(srv), expect: CloneableService::new(expect), upgrade: upgrade.map(|s| CloneableService::new(s)), @@ -319,6 +340,13 @@ where fn call(&mut self, req: Self::Request) -> Self::Future { let (io, _, proto) = req.into_parts(); + + let on_connect = if let Some(ref on_connect) = self.on_connect { + Some(on_connect(&io)) + } else { + None + }; + match proto { Protocol::Http2 => { let peer_addr = io.peer_addr(); @@ -332,6 +360,7 @@ where self.cfg.clone(), self.srv.clone(), peer_addr, + on_connect, ))), } } @@ -342,6 +371,7 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), + on_connect, )), }, _ => HttpServiceHandlerResponse { @@ -352,6 +382,7 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), + on_connect, ))), }, } @@ -380,6 +411,7 @@ where CloneableService, CloneableService, Option>, + Option>, )>, ), Handshake( @@ -388,6 +420,7 @@ where ServiceConfig, CloneableService, Option, + Option>, )>, ), } @@ -448,7 +481,8 @@ where } else { panic!() } - let (io, buf, cfg, srv, expect, upgrade) = data.take().unwrap(); + let (io, buf, cfg, srv, expect, upgrade, on_connect) = + data.take().unwrap(); if buf[..14] == HTTP2_PREFACE[..] { let peer_addr = io.peer_addr(); let io = Io { @@ -460,6 +494,7 @@ where cfg, srv, peer_addr, + on_connect, ))); } else { self.state = State::H1(h1::Dispatcher::with_timeout( @@ -471,6 +506,7 @@ where srv, expect, upgrade, + on_connect, )) } self.poll() @@ -488,8 +524,10 @@ where } else { panic!() }; - let (_, cfg, srv, peer_addr) = data.take().unwrap(); - self.state = State::H2(Dispatcher::new(srv, conn, cfg, None, peer_addr)); + let (_, cfg, srv, peer_addr, on_connect) = data.take().unwrap(); + self.state = State::H2(Dispatcher::new( + srv, conn, on_connect, cfg, None, peer_addr, + )); self.poll() } } From b77ed193f79e1d5ad70cc34e479e12c315c5e98e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Jun 2019 14:36:20 +0600 Subject: [PATCH 1474/1635] prepare actix-web release --- CHANGES.md | 2 +- Cargo.toml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dcefdec5..6c7a8b31 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.3] - unreleased +## [1.0.3] - 2019-06-28 ### Added diff --git a/Cargo.toml b/Cargo.toml index 4f8cd745..4e492e19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.2" +version = "1.0.3" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -71,11 +71,11 @@ rust-tls = ["rustls", "actix-server/rust-tls"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-utils = "0.4.1" +actix-utils = "0.4.2" actix-router = "0.1.5" -actix-rt = "0.2.2" +actix-rt = "0.2.3" actix-web-codegen = "0.1.2" -actix-http = "0.2.4" +actix-http = "0.2.5" actix-server = "0.5.1" actix-server-config = "0.1.1" actix-threadpool = "0.1.1" @@ -103,7 +103,7 @@ rustls = { version = "0.15", optional = true } [dev-dependencies] actix = { version = "0.8.3" } -actix-http = { version = "0.2.4", features=["ssl", "brotli", "flate2-zlib"] } +actix-http = { version = "0.2.5", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.2", features=["ssl"] } rand = "0.6" env_logger = "0.6" From 12b51748503d9802a51814bd49c032ec1e7344ce Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 28 Jun 2019 14:46:26 +0600 Subject: [PATCH 1475/1635] update deps --- actix-web-actors/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 864d8d95..eb5fb111 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,8 +19,8 @@ path = "src/lib.rs" [dependencies] actix = "0.8.3" -actix-web = "1.0.2" -actix-http = "0.2.4" +actix-web = "1.0.3" +actix-http = "0.2.5" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" From 37f4ce8604f3a3cc8cb789d17ee5b4aac8b5111c Mon Sep 17 00:00:00 2001 From: Cameron Dershem Date: Sat, 29 Jun 2019 00:38:16 -0400 Subject: [PATCH 1476/1635] Fixes typo in docs. (#948) Small typo in docs. --- src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 5ab417bd..208360a2 100644 --- a/src/test.rs +++ b/src/test.rs @@ -64,7 +64,7 @@ where RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f.into_future())) } -/// Runs the provided function, blocking the current thread until the resul +/// Runs the provided function, blocking the current thread until the result /// future completes. /// /// This function can be used to synchronously block the current thread From 0e05b37082fa57444858b46e1a8b57b731777f1c Mon Sep 17 00:00:00 2001 From: dowwie Date: Sat, 29 Jun 2019 14:24:02 -0400 Subject: [PATCH 1477/1635] updated cookie session to update on change --- actix-session/src/cookie.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 55904f5b..de1faea9 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -28,7 +28,7 @@ use futures::future::{ok, Future, FutureResult}; use futures::Poll; use serde_json::error::Error as JsonError; -use crate::Session; +use crate::{Session, SessionStatus}; /// Errors that can occur during handling cookie session #[derive(Debug, From, Display)] @@ -308,10 +308,10 @@ where Session::set_session(state.into_iter(), &mut req); Box::new(self.service.call(req).map(move |mut res| { - if let (_status, Some(state)) = Session::get_changes(&mut res) { - res.checked_expr(|res| inner.set_cookie(res, state)) - } else { - res + match Session::get_changes(&mut res) { + (SessionStatus::Changed, Some(state)) => + res.checked_expr(|res| inner.set_cookie(res, state)), + _ => res } })) } From 5901dfee1a1ed553cab216ad480ff784baa7435f Mon Sep 17 00:00:00 2001 From: Sindre Johansen Date: Sun, 30 Jun 2019 17:30:04 +0200 Subject: [PATCH 1478/1635] Fix link to actix-cors (#950) --- actix-cors/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-cors/README.md b/actix-cors/README.md index 60b615c7..980d98ca 100644 --- a/actix-cors/README.md +++ b/actix-cors/README.md @@ -3,7 +3,7 @@ ## Documentation & community resources * [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/actix-identity/) +* [API Documentation](https://docs.rs/actix-cors/) * [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-session](https://crates.io/crates/actix-identity) +* Cargo package: [actix-cors](https://crates.io/crates/actix-cors) * Minimum supported Rust version: 1.34 or later From d2eb1edac33f0617ca5d9869258f678365428803 Mon Sep 17 00:00:00 2001 From: Alec Moskvin Date: Sun, 30 Jun 2019 23:34:42 -0400 Subject: [PATCH 1479/1635] Actix-web client: Always append a colon after username in basic auth (#949) * Always append a colon after username in basic auth * Update CHANGES.md --- awc/CHANGES.md | 7 +++++++ awc/src/builder.rs | 4 ++-- awc/src/request.rs | 4 ++-- awc/src/ws.rs | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index b5f64dd3..fd5d0190 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.2.2] - TBD + +### Changed + +* Always append a colon after username in basic auth + + ## [0.2.1] - 2019-06-05 ### Added diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 2bc52a43..a58265c5 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -115,7 +115,7 @@ impl ClientBuilder { { let auth = match password { Some(password) => format!("{}:{}", username, password), - None => format!("{}", username), + None => format!("{}:", username), }; self.header( header::AUTHORIZATION, @@ -164,7 +164,7 @@ mod tests { .unwrap() .to_str() .unwrap(), - "Basic dXNlcm5hbWU=" + "Basic dXNlcm5hbWU6" ); } diff --git a/awc/src/request.rs b/awc/src/request.rs index 5c09df81..36cd6fcf 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -280,7 +280,7 @@ impl ClientRequest { { let auth = match password { Some(password) => format!("{}:{}", username, password), - None => format!("{}", username), + None => format!("{}:", username), }; self.header( header::AUTHORIZATION, @@ -664,7 +664,7 @@ mod tests { .unwrap() .to_str() .unwrap(), - "Basic dXNlcm5hbWU=" + "Basic dXNlcm5hbWU6" ); } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index d3e06d3d..95bf6ef7 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -195,7 +195,7 @@ impl WebsocketsRequest { { let auth = match password { Some(password) => format!("{}:{}", username, password), - None => format!("{}", username), + None => format!("{}:", username), }; self.header(AUTHORIZATION, format!("Basic {}", base64::encode(&auth))) } @@ -443,7 +443,7 @@ mod tests { .unwrap() .to_str() .unwrap(), - "Basic dXNlcm5hbWU=" + "Basic dXNlcm5hbWU6" ); } From dbab55dd6b971c97684d7deca23f327a3a2694f6 Mon Sep 17 00:00:00 2001 From: messense Date: Mon, 1 Jul 2019 11:37:03 +0800 Subject: [PATCH 1480/1635] Bump rand crate version to 0.7 (#951) --- CHANGES.md | 6 ++++++ Cargo.toml | 2 +- actix-http/CHANGES.md | 6 ++++++ actix-http/Cargo.toml | 2 +- awc/CHANGES.md | 2 ++ awc/Cargo.toml | 6 +++--- 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6c7a8b31..641e09bd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [1.0.4] - TBD + +### Changed + +* Upgrade `rand` dependency version to 0.7 + ## [1.0.3] - 2019-06-28 ### Added diff --git a/Cargo.toml b/Cargo.toml index 4e492e19..57676acc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ rustls = { version = "0.15", optional = true } actix = { version = "0.8.3" } actix-http = { version = "0.2.5", features=["ssl", "brotli", "flate2-zlib"] } actix-http-test = { version = "0.2.2", features=["ssl"] } -rand = "0.6" +rand = "0.7" env_logger = "0.6" serde_derive = "1.0" tokio-timer = "0.2.8" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 636cbedf..51679326 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.2.6] - TBD + +### Changed + +* Upgrade `rand` dependency version to 0.7 + ## [0.2.5] - 2019-06-28 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index afbf0a48..b4d5e206 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -70,7 +70,7 @@ language-tags = "0.2" log = "0.4" mime = "0.3" percent-encoding = "1.0" -rand = "0.6" +rand = "0.7" regex = "1.0" serde = "1.0" serde_json = "1.0" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index fd5d0190..3020eb2f 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -6,6 +6,8 @@ * Always append a colon after username in basic auth +* Upgrade `rand` dependency version to 0.7 + ## [0.2.1] - 2019-06-05 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d0629f4f..ecc9b949 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -49,7 +49,7 @@ futures = "0.1.25" log =" 0.4" mime = "0.3" percent-encoding = "1.0" -rand = "0.6" +rand = "0.7" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.5.3" @@ -66,5 +66,5 @@ actix-server = { version = "0.5.1", features=["ssl"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" -rand = "0.6" -tokio-tcp = "0.1" \ No newline at end of file +rand = "0.7" +tokio-tcp = "0.1" From a0a469fe8541466d38a4d8c963c505a486f4fb93 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Jul 2019 11:33:11 +0600 Subject: [PATCH 1481/1635] disable travis cargo cache --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5f7d01a3..97a05cc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ sudo: required dist: trusty cache: - cargo: true + # cargo: true apt: true matrix: From a28b7139e6bd2dedeeb24d839aeaebf5276cc8ca Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 1 Jul 2019 11:34:57 +0600 Subject: [PATCH 1482/1635] prepare awc release --- awc/CHANGES.md | 2 +- awc/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3020eb2f..602f9a3b 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.2.2] - TBD +## [0.2.2] - 2019-07-01 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ecc9b949..234662e9 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.1" +version = "0.2.2" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" From 099a8ff7d82184512ae5862ca0e6d088a8838367 Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 1 Jul 2019 15:26:19 -0400 Subject: [PATCH 1483/1635] updated session cookie to support login, logout, changes --- actix-session/src/cookie.rs | 42 ++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index de1faea9..45f24817 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -119,7 +119,21 @@ impl CookieSessionInner { Ok(()) } - fn load(&self, req: &ServiceRequest) -> HashMap { + /// invalidates session cookie + fn remove_cookie(&self, res: &mut ServiceResponse) + -> Result<(), Error> { + let mut cookie = Cookie::named(self.name.clone()); + cookie.set_value(""); + cookie.set_max_age(time::Duration::seconds(0)); + cookie.set_expires(time::now() - time::Duration::days(365)); + + let val = HeaderValue::from_str(&cookie.to_string())?; + res.headers_mut().append(SET_COOKIE, val); + + Ok(()) + } + + fn load(&self, req: &ServiceRequest) -> (bool, HashMap) { if let Ok(cookies) = req.cookies() { for cookie in cookies.iter() { if cookie.name() == self.name { @@ -134,13 +148,13 @@ impl CookieSessionInner { }; if let Some(cookie) = cookie_opt { if let Ok(val) = serde_json::from_str(cookie.value()) { - return val; + return (false, val); } } } } } - HashMap::new() + (true, HashMap::new()) } } @@ -302,15 +316,33 @@ where self.service.poll_ready() } + /// On first request, a new session cookie is returned in response, regardless + /// of whether any session state is set. With subsequent requests, if the + /// session state changes, then set-cookie is returned in response. As + /// a user logs out, call session.purge() to set SessionStatus accordingly + /// and this will trigger removal of the session cookie in the response. fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); - let state = self.inner.load(&req); + let (is_new, state) = self.inner.load(&req); Session::set_session(state.into_iter(), &mut req); Box::new(self.service.call(req).map(move |mut res| { match Session::get_changes(&mut res) { - (SessionStatus::Changed, Some(state)) => + (SessionStatus::Changed, Some(state)) + | (SessionStatus::Renewed, Some(state)) => res.checked_expr(|res| inner.set_cookie(res, state)), + (SessionStatus::Unchanged, _) => + // set a new session cookie upon first request (new client) + if is_new { + let state: HashMap = HashMap::new(); + res.checked_expr(|res| inner.set_cookie(res, state.into_iter())) + } else { + res + }, + (SessionStatus::Purged, _) => { + inner.remove_cookie(&mut res); + res + }, _ => res } })) From 5bf5b0acd2b60c5d56ab60b0d33523b9952916c1 Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 3 Jul 2019 07:46:46 -0400 Subject: [PATCH 1484/1635] updated CHANGES with info about actix-session update --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a2071310..243dc827 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,9 @@ ### Changed * Use `encoding_rs` crate instead of unmaintained `encoding` crate +* Enhanced ``actix-session`` to facilitate state changes. Use ``Session.renew()`` + at successful login to cycle a session (new key, cookie). Use ``Session.purge()`` + at logout to invalid a session cookie (and remove from redis cache, if applicable). ## [1.0.2] - 2019-06-17 From dabc4fe00b1b2cf149793fae40440e36a6e5e95f Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 3 Jul 2019 07:50:11 -0400 Subject: [PATCH 1485/1635] updated actix-session/CHANGES with info --- actix-session/CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 10aea870..ec1606de 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.2.0] - 2019-07-03 +* Enhanced ``actix-session`` to facilitate state changes. Use ``Session.renew()`` + at successful login to cycle a session (new key, cookie). Use ``Session.purge()`` + at logout to invalid a session cookie (and remove from redis cache, if applicable). + ## [0.1.1] - 2019-06-03 * Fix optional cookie session support From 2d424957fb4d4feea0e3c104b84f60d4ac0af2cf Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 3 Jul 2019 07:50:45 -0400 Subject: [PATCH 1486/1635] updated version in Cargo to 0.2 --- actix-session/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 4c1d6657..d973661e 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.1.1" +version = "0.2.0" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" From 1fdd77bffac9f16d780eff9aaefc23889eeae513 Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 3 Jul 2019 07:56:50 -0400 Subject: [PATCH 1487/1635] reworded session info in CHANGES --- CHANGES.md | 5 +++-- actix-session/CHANGES.md | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 243dc827..5937cf06 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,8 +6,9 @@ * Use `encoding_rs` crate instead of unmaintained `encoding` crate * Enhanced ``actix-session`` to facilitate state changes. Use ``Session.renew()`` - at successful login to cycle a session (new key, cookie). Use ``Session.purge()`` - at logout to invalid a session cookie (and remove from redis cache, if applicable). + at successful login to cycle a session (new key/cookie but keeps state). + Use ``Session.purge()`` at logout to invalid a session cookie (and remove + from redis cache, if applicable). ## [1.0.2] - 2019-06-17 diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index ec1606de..92748505 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -2,8 +2,10 @@ ## [0.2.0] - 2019-07-03 * Enhanced ``actix-session`` to facilitate state changes. Use ``Session.renew()`` - at successful login to cycle a session (new key, cookie). Use ``Session.purge()`` - at logout to invalid a session cookie (and remove from redis cache, if applicable). + at successful login to cycle a session (new key/cookie but keeps state). + Use ``Session.purge()`` at logout to invalid a session cookie (and remove + from redis cache, if applicable). + ## [0.1.1] - 2019-06-03 From 7596ab69e0b0a40a0c07627a55a2f2a47c8ecc6a Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 3 Jul 2019 08:55:29 -0400 Subject: [PATCH 1488/1635] reverted actix-web/CHANGES.md --- CHANGES.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5937cf06..a2071310 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,10 +5,6 @@ ### Changed * Use `encoding_rs` crate instead of unmaintained `encoding` crate -* Enhanced ``actix-session`` to facilitate state changes. Use ``Session.renew()`` - at successful login to cycle a session (new key/cookie but keeps state). - Use ``Session.purge()`` at logout to invalid a session cookie (and remove - from redis cache, if applicable). ## [1.0.2] - 2019-06-17 From 0d8a4304a922b8f2da0fd50579dc2cc3dacc3ded Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 5 Jul 2019 17:46:55 +0300 Subject: [PATCH 1489/1635] Drop a duplicated word (#958) --- src/types/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/query.rs b/src/types/query.rs index 2c07edfb..17240c0b 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -13,7 +13,7 @@ use crate::extract::FromRequest; use crate::request::HttpRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from from the request's query. +/// Extract typed information from the request's query. /// /// ## Example /// @@ -90,7 +90,7 @@ impl fmt::Display for Query { } } -/// Extract typed information from from the request's query. +/// Extract typed information from the request's query. /// /// ## Example /// From e1fcd203f8f29b8e2c2cc38a9862c5c52a466af4 Mon Sep 17 00:00:00 2001 From: Jeff Muizelaar Date: Mon, 8 Jul 2019 05:48:20 -0400 Subject: [PATCH 1490/1635] Update the copyless version to 0.1.4 (#956) < 0.1.4 failed to check for null when doing allocations which could lead to null dereferences. --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b4d5e206..5db7a6ca 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -55,7 +55,7 @@ base64 = "0.10" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" -copyless = "0.1.2" +copyless = "0.1.4" derive_more = "0.15.0" either = "1.5.2" encoding_rs = "0.8" From f410f3330fb771e8d51b7448ea2b0d3981d95891 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 8 Jul 2019 23:25:51 +0600 Subject: [PATCH 1491/1635] prepare actix-session release --- actix-session/CHANGES.md | 6 +++--- actix-session/README.md | 2 +- actix-session/src/cookie.rs | 24 +++++++++++++----------- actix-session/src/lib.rs | 8 +++++--- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 92748505..d85f6d5f 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -1,12 +1,12 @@ # Changes -## [0.2.0] - 2019-07-03 +## [0.2.0] - 2019-07-08 + * Enhanced ``actix-session`` to facilitate state changes. Use ``Session.renew()`` at successful login to cycle a session (new key/cookie but keeps state). - Use ``Session.purge()`` at logout to invalid a session cookie (and remove + Use ``Session.purge()`` at logout to invalid a session cookie (and remove from redis cache, if applicable). - ## [0.1.1] - 2019-06-03 * Fix optional cookie session support diff --git a/actix-session/README.md b/actix-session/README.md index 7d683041..0aee756f 100644 --- a/actix-session/README.md +++ b/actix-session/README.md @@ -6,4 +6,4 @@ * [API Documentation](https://docs.rs/actix-session/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-session](https://crates.io/crates/actix-session) -* Minimum supported Rust version: 1.33 or later +* Minimum supported Rust version: 1.34 or later diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 45f24817..8627ce4c 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -120,8 +120,7 @@ impl CookieSessionInner { } /// invalidates session cookie - fn remove_cookie(&self, res: &mut ServiceResponse) - -> Result<(), Error> { + fn remove_cookie(&self, res: &mut ServiceResponse) -> Result<(), Error> { let mut cookie = Cookie::named(self.name.clone()); cookie.set_value(""); cookie.set_max_age(time::Duration::seconds(0)); @@ -317,7 +316,7 @@ where } /// On first request, a new session cookie is returned in response, regardless - /// of whether any session state is set. With subsequent requests, if the + /// of whether any session state is set. With subsequent requests, if the /// session state changes, then set-cookie is returned in response. As /// a user logs out, call session.purge() to set SessionStatus accordingly /// and this will trigger removal of the session cookie in the response. @@ -329,21 +328,24 @@ where Box::new(self.service.call(req).map(move |mut res| { match Session::get_changes(&mut res) { (SessionStatus::Changed, Some(state)) - | (SessionStatus::Renewed, Some(state)) => - res.checked_expr(|res| inner.set_cookie(res, state)), + | (SessionStatus::Renewed, Some(state)) => { + res.checked_expr(|res| inner.set_cookie(res, state)) + } (SessionStatus::Unchanged, _) => - // set a new session cookie upon first request (new client) + // set a new session cookie upon first request (new client) + { if is_new { let state: HashMap = HashMap::new(); res.checked_expr(|res| inner.set_cookie(res, state.into_iter())) } else { res - }, + } + } (SessionStatus::Purged, _) => { - inner.remove_cookie(&mut res); - res - }, - _ => res + inner.remove_cookie(&mut res); + res + } + _ => res, } })) } diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index aaf0ab02..27ad2b87 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -103,7 +103,7 @@ pub enum SessionStatus { Changed, Purged, Renewed, - Unchanged + Unchanged, } impl Default for SessionStatus { fn default() -> SessionStatus { @@ -183,7 +183,10 @@ impl Session { pub fn get_changes( res: &mut ServiceResponse, - ) -> (SessionStatus, Option>) { + ) -> ( + SessionStatus, + Option>, + ) { if let Some(s_impl) = res .request() .extensions() @@ -286,7 +289,6 @@ mod tests { assert_eq!(session.0.borrow().status, SessionStatus::Purged); } - #[test] fn renew_session() { let mut req = test::TestRequest::default().to_srv_request(); From 69456991f6d19e056e13b5f7572f9ecbb959d802 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 11 Jul 2019 14:40:37 +0600 Subject: [PATCH 1492/1635] update api doc example for client and add panic info for connection_info --- awc/src/lib.rs | 2 +- src/request.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 9fbda8aa..45231326 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,7 +1,7 @@ //! An HTTP Client //! //! ```rust -//! # use futures::future::{Future, lazy}; +//! use futures::future::{lazy, Future}; //! use actix_rt::System; //! use awc::Client; //! diff --git a/src/request.rs b/src/request.rs index 07aac8cf..d0d24f4f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -186,6 +186,9 @@ impl HttpRequest { } /// Get *ConnectionInfo* for the current request. + /// + /// This method panics if request's extensions container is already + /// borrowed. #[inline] pub fn connection_info(&self) -> Ref { ConnectionInfo::get(self.head(), &*self.app_config()) From b1143168e53a65824b23b6cfff6d2db7921846ef Mon Sep 17 00:00:00 2001 From: messense Date: Thu, 11 Jul 2019 16:42:58 +0800 Subject: [PATCH 1493/1635] Impl Responder for (T, StatusCode) where T: Responder (#954) --- CHANGES.md | 4 ++++ src/responder.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 641e09bd..0b3f3e0c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,10 @@ ## [1.0.4] - TBD +### Added + +* Add `Responder` impl for `(T, StatusCode) where T: Responder` + ### Changed * Upgrade `rand` dependency version to 0.7 diff --git a/src/responder.rs b/src/responder.rs index 47a8800e..6bd25e6e 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -137,6 +137,22 @@ impl Responder for () { } } +impl Responder for (T, StatusCode) +where + T: Responder, +{ + type Error = T::Error; + type Future = CustomResponderFut; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + CustomResponderFut { + fut: self.0.respond_to(req).into_future(), + status: Some(self.1), + headers: None, + } + } +} + impl Responder for &'static str { type Error = Error; type Future = FutureResult; @@ -624,4 +640,29 @@ pub(crate) mod tests { HeaderValue::from_static("json") ); } + + #[test] + fn test_tuple_responder_with_status_code() { + let req = TestRequest::default().to_http_request(); + let res = block_on( + ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req) + ) + .unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!(res.body().bin_ref(), b"test"); + + let req = TestRequest::default().to_http_request(); + let res = block_on( + ("test".to_string(), StatusCode::OK) + .with_header("content-type", "json") + .respond_to(&req) + ) + .unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); + } } From 8d17c8651f5f1eb721ffe2ba550512aa7241ef1d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 11 Jul 2019 14:45:58 +0600 Subject: [PATCH 1494/1635] update bench link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cae737b6..e533c848 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ You may consider checking out ## Benchmarks -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) +* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r18) ## License From 6f71409355d6c51501dfdad2deb2efac13a8174f Mon Sep 17 00:00:00 2001 From: Andrea Corradi Date: Tue, 16 Jul 2019 06:19:28 +0200 Subject: [PATCH 1495/1635] Add DELETE, PATCH, OPTIONS methods to TestServerRunner (#973) --- CHANGES.md | 1 + test-server/src/lib.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0b3f3e0c..0943fbdc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### Added * Add `Responder` impl for `(T, StatusCode) where T: Responder` +* Add `delete`, `options`, `patch` methods to `TestServerRunner` ### Changed diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 1fbaa6c7..c49026fa 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -265,6 +265,36 @@ impl TestServerRuntime { self.client.put(self.surl(path.as_ref()).as_str()) } + /// Create `PATCH` request + pub fn patch>(&self, path: S) -> ClientRequest { + self.client.patch(self.url(path.as_ref()).as_str()) + } + + /// Create https `PATCH` request + pub fn spatch>(&self, path: S) -> ClientRequest { + self.client.patch(self.surl(path.as_ref()).as_str()) + } + + /// Create `DELETE` request + pub fn delete>(&self, path: S) -> ClientRequest { + self.client.delete(self.url(path.as_ref()).as_str()) + } + + /// Create https `DELETE` request + pub fn sdelete>(&self, path: S) -> ClientRequest { + self.client.delete(self.surl(path.as_ref()).as_str()) + } + + /// Create `OPTIONS` request + pub fn options>(&self, path: S) -> ClientRequest { + self.client.options(self.url(path.as_ref()).as_str()) + } + + /// Create https `OPTIONS` request + pub fn soptions>(&self, path: S) -> ClientRequest { + self.client.options(self.surl(path.as_ref()).as_str()) + } + /// Connect to test http server pub fn request>(&self, method: Method, path: S) -> ClientRequest { self.client.request(method, path.as_ref()) From c45728ac01743aed6f12308aa2d1c3ef32aa2b19 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 16 Jul 2019 10:21:52 +0600 Subject: [PATCH 1496/1635] prep test server release --- CHANGES.md | 1 - src/responder.rs | 9 ++++----- test-server/CHANGES.md | 4 ++++ test-server/Cargo.toml | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0943fbdc..0b3f3e0c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,6 @@ ### Added * Add `Responder` impl for `(T, StatusCode) where T: Responder` -* Add `delete`, `options`, `patch` methods to `TestServerRunner` ### Changed diff --git a/src/responder.rs b/src/responder.rs index 6bd25e6e..39927c78 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -644,10 +644,9 @@ pub(crate) mod tests { #[test] fn test_tuple_responder_with_status_code() { let req = TestRequest::default().to_http_request(); - let res = block_on( - ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req) - ) - .unwrap(); + let res = + block_on(("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req)) + .unwrap(); assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.body().bin_ref(), b"test"); @@ -655,7 +654,7 @@ pub(crate) mod tests { let res = block_on( ("test".to_string(), StatusCode::OK) .with_header("content-type", "json") - .respond_to(&req) + .respond_to(&req), ) .unwrap(); assert_eq!(res.status(), StatusCode::OK); diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index e7292c0e..f3e98010 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.2.3] - 2019-07-16 + +* Add `delete`, `options`, `patch` methods to `TestServerRunner` + ## [0.2.2] - 2019-06-16 * Add .put() and .sput() methods diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 4231b17b..445ed33e 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.2.2" +version = "0.2.3" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.2" actix-service = "0.4.1" actix-server = "0.5.1" actix-utils = "0.4.1" -awc = "0.2.1" +awc = "0.2.2" base64 = "0.10" bytes = "0.4" From c65dbaf88ed6172484d10e18473aa27d7fcf7338 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Jul 2019 11:33:05 +0600 Subject: [PATCH 1497/1635] expose app's ResourceMap via resource_map method --- CHANGES.md | 4 ++++ src/request.rs | 6 ++++++ src/service.rs | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0b3f3e0c..11158925 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,10 +6,14 @@ * Add `Responder` impl for `(T, StatusCode) where T: Responder` +* Allow to access app's resource map via + `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. + ### Changed * Upgrade `rand` dependency version to 0.7 + ## [1.0.3] - 2019-06-28 ### Added diff --git a/src/request.rs b/src/request.rs index d0d24f4f..0fc0647f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -174,6 +174,12 @@ impl HttpRequest { self.url_for(name, &NO_PARAMS) } + #[inline] + /// Get a reference to a `ResourceMap` of current application. + pub fn resource_map(&self) -> &ResourceMap { + &self.0.rmap + } + /// Peer socket address /// /// Peer address is actual socket address, if proxy is used in front of diff --git a/src/service.rs b/src/service.rs index 722813a9..5863a100 100644 --- a/src/service.rs +++ b/src/service.rs @@ -18,6 +18,7 @@ use crate::dev::insert_slash; use crate::guard::Guard; use crate::info::ConnectionInfo; use crate::request::HttpRequest; +use crate::rmap::ResourceMap; pub trait HttpServiceFactory { fn register(self, config: &mut AppService); @@ -169,10 +170,17 @@ impl ServiceRequest { } #[inline] + /// Get a mutable reference to the Path parameters. pub fn match_info_mut(&mut self) -> &mut Path { self.0.match_info_mut() } + #[inline] + /// Get a reference to a `ResourceMap` of current application. + pub fn resource_map(&self) -> &ResourceMap { + self.0.resource_map() + } + /// Service configuration #[inline] pub fn app_config(&self) -> &AppConfig { From 7b1dcaffda60648b77535c2cec34946ab57cb0ed Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Jul 2019 11:44:39 +0600 Subject: [PATCH 1498/1635] cleanup deprecation warning for Box --- actix-cors/src/lib.rs | 2 +- actix-files/src/lib.rs | 8 ++++---- actix-framed/src/app.rs | 6 +++--- actix-framed/src/helpers.rs | 14 +++++++------- actix-framed/src/route.rs | 2 +- actix-http/src/client/connection.rs | 6 ++++-- actix-identity/src/lib.rs | 2 +- actix-session/src/cookie.rs | 2 +- awc/src/connect.rs | 4 ++-- src/app_service.rs | 6 +++--- src/extract.rs | 4 ++-- src/middleware/defaultheaders.rs | 2 +- src/middleware/errhandlers.rs | 4 ++-- src/resource.rs | 6 +++--- src/responder.rs | 6 +++--- src/route.rs | 15 ++++++++------- src/scope.rs | 6 +++--- src/types/form.rs | 4 ++-- src/types/json.rs | 4 ++-- src/types/payload.rs | 10 ++++++---- 20 files changed, 59 insertions(+), 54 deletions(-) diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index 5d0d013e..ea1eb383 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -681,7 +681,7 @@ where type Error = Error; type Future = Either< FutureResult, - Either>>, + Either>>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 8e87f7d8..82abb999 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -50,7 +50,7 @@ pub struct ChunkedReadFile { size: u64, offset: u64, file: Option, - fut: Option>>>, + fut: Option>>>, counter: u64, } @@ -370,7 +370,7 @@ impl NewService for Files { type Error = Error; type Service = FilesService; type InitError = (); - type Future = Box>; + type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { let mut srv = FilesService { @@ -416,7 +416,7 @@ impl FilesService { req: ServiceRequest, ) -> Either< FutureResult, - Box>, + Box>, > { log::debug!("Files: Failed to handle {}: {}", req.path(), e); if let Some(ref mut default) = self.default { @@ -433,7 +433,7 @@ impl Service for FilesService { type Error = Error; type Future = Either< FutureResult, - Box>, + Box>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs index 297796bd..a9d73a25 100644 --- a/actix-framed/src/app.rs +++ b/actix-framed/src/app.rs @@ -13,7 +13,7 @@ use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService}; use crate::request::FramedRequest; use crate::state::State; -type BoxedResponse = Box>; +type BoxedResponse = Box>; pub trait HttpServiceFactory { type Factory: NewService; @@ -61,7 +61,7 @@ impl FramedApp { Request = FramedRequest, Response = (), Error = Error, - Future = Box>, + Future = Box>, >, { let path = factory.path().to_string(); @@ -129,7 +129,7 @@ pub struct CreateService { enum CreateServiceItem { Future( Option, - Box>, Error = ()>>, + Box>, Error = ()>>, ), Service(String, BoxedHttpService>), } diff --git a/actix-framed/src/helpers.rs b/actix-framed/src/helpers.rs index 944b729d..5e84ad88 100644 --- a/actix-framed/src/helpers.rs +++ b/actix-framed/src/helpers.rs @@ -7,7 +7,7 @@ pub(crate) type BoxedHttpService = Box< Request = Req, Response = (), Error = Error, - Future = Box>, + Future = Box>, >, >; @@ -19,7 +19,7 @@ pub(crate) type BoxedHttpNewService = Box< Error = Error, InitError = (), Service = BoxedHttpService, - Future = Box, Error = ()>>, + Future = Box, Error = ()>>, >, >; @@ -30,7 +30,7 @@ where T: NewService, T::Response: 'static, T::Future: 'static, - T::Service: Service>> + 'static, + T::Service: Service>> + 'static, ::Future: 'static, { pub fn new(service: T) -> Self { @@ -43,7 +43,7 @@ where T: NewService, T::Request: 'static, T::Future: 'static, - T::Service: Service>> + 'static, + T::Service: Service>> + 'static, ::Future: 'static, { type Config = (); @@ -52,7 +52,7 @@ where type Error = Error; type InitError = (); type Service = BoxedHttpService; - type Future = Box>; + type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { @@ -70,7 +70,7 @@ impl Service for HttpServiceWrapper where T: Service< Response = (), - Future = Box>, + Future = Box>, Error = Error, >, T::Request: 'static, @@ -78,7 +78,7 @@ where type Request = T::Request; type Response = (); type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() diff --git a/actix-framed/src/route.rs b/actix-framed/src/route.rs index c50401d6..5beb2416 100644 --- a/actix-framed/src/route.rs +++ b/actix-framed/src/route.rs @@ -140,7 +140,7 @@ where type Request = FramedRequest; type Response = (); type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 9354fca4..2f3103d4 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -94,7 +94,8 @@ where T: AsyncRead + AsyncWrite + 'static, { type Io = T; - type Future = Box>; + type Future = + Box>; fn protocol(&self) -> Protocol { match self.io { @@ -169,7 +170,8 @@ where B: AsyncRead + AsyncWrite + 'static, { type Io = EitherIo; - type Future = Box>; + type Future = + Box>; fn protocol(&self) -> Protocol { match self { diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 6664df67..fe7216a0 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -261,7 +261,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.borrow_mut().poll_ready() diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 8627ce4c..87fc0b16 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -309,7 +309,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 4b564d77..8344abbd 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -20,7 +20,7 @@ pub(crate) trait Connect { head: RequestHead, body: Body, addr: Option, - ) -> Box>; + ) -> Box>; /// Send request, returns Response and Framed fn open_tunnel( @@ -49,7 +49,7 @@ where head: RequestHead, body: Body, addr: Option, - ) -> Box> { + ) -> Box> { Box::new( self.0 // connect to the host diff --git a/src/app_service.rs b/src/app_service.rs index 8ab9b352..6012dcda 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -23,7 +23,7 @@ type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = Either< FutureResult, - Box>, + Box>, >; type FnDataFactory = Box Box, Error = ()>>>; @@ -297,14 +297,14 @@ impl NewService for AppRoutingFactory { } } -type HttpServiceFut = Box>; +type HttpServiceFut = Box>; /// Create app service #[doc(hidden)] pub struct AppRoutingFactoryResponse { fut: Vec, default: Option, - default_fut: Option>>, + default_fut: Option>>, } enum CreateAppRoutingItem { diff --git a/src/extract.rs b/src/extract.rs index 17b5cb40..1687973a 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -94,7 +94,7 @@ where { type Config = T::Config; type Error = Error; - type Future = Box, Error = Error>>; + type Future = Box, Error = Error>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { @@ -165,7 +165,7 @@ where { type Config = T::Config; type Error = Error; - type Future = Box, Error = Error>>; + type Future = Box, Error = Error>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index bddcdd55..ab2d36c2 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -119,7 +119,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index ac166e0e..afe7c72f 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -15,7 +15,7 @@ pub enum ErrorHandlerResponse { /// New http response got generated Response(ServiceResponse), /// Result is a future that resolves to a new http response - Future(Box, Error = Error>>), + Future(Box, Error = Error>>), } type ErrorHandler = Fn(ServiceResponse) -> Result>; @@ -117,7 +117,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() diff --git a/src/resource.rs b/src/resource.rs index c2691eeb..d09beb27 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -245,7 +245,7 @@ where /// ```rust /// # use actix_web::*; /// # use futures::future::Future; - /// # fn index(req: HttpRequest) -> Box> { + /// # fn index(req: HttpRequest) -> Box> { /// # unimplemented!() /// # } /// App::new().service(web::resource("/").route(web::route().to_async(index))); @@ -478,7 +478,7 @@ pub struct CreateResourceService { fut: Vec, data: Option>, default: Option, - default_fut: Option>>, + default_fut: Option>>, } impl Future for CreateResourceService { @@ -542,7 +542,7 @@ impl Service for ResourceService { type Error = Error; type Future = Either< FutureResult, - Box>, + Box>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/responder.rs b/src/responder.rs index 39927c78..4988ad5b 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -337,7 +337,7 @@ impl Future for CustomResponderFut { /// use actix_web::{Either, Error, HttpResponse}; /// /// type RegisterResult = -/// Either>>; +/// Either>>; /// /// fn index() -> RegisterResult { /// if is_a_variant() { @@ -411,13 +411,13 @@ where } } -impl Responder for Box> +impl Responder for Box> where I: Responder + 'static, E: Into + 'static, { type Error = Error; - type Future = Box>; + type Future = Box>; #[inline] fn respond_to(self, req: &HttpRequest) -> Self::Future { diff --git a/src/route.rs b/src/route.rs index 660b8200..591175d1 100644 --- a/src/route.rs +++ b/src/route.rs @@ -19,7 +19,7 @@ type BoxedRouteService = Box< Error = Error, Future = Either< FutureResult, - Box>, + Box>, >, >, >; @@ -32,7 +32,7 @@ type BoxedRouteNewService = Box< Error = Error, InitError = (), Service = BoxedRouteService, - Future = Box, Error = ()>>, + Future = Box, Error = ()>>, >, >; @@ -78,8 +78,9 @@ impl NewService for Route { } } -type RouteFuture = - Box, Error = ()>>; +type RouteFuture = Box< + dyn Future, Error = ()>, +>; pub struct CreateRouteService { fut: RouteFuture, @@ -123,7 +124,7 @@ impl Service for RouteService { type Error = Error; type Future = Either< FutureResult, - Box>, + Box>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -317,7 +318,7 @@ where type Error = Error; type InitError = (); type Service = BoxedRouteService; - type Future = Box>; + type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { Box::new( @@ -351,7 +352,7 @@ where type Error = Error; type Future = Either< FutureResult, - Box>, + Box>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/scope.rs b/src/scope.rs index 400da668..99afd7d1 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -28,7 +28,7 @@ type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = Either< FutureResult, - Box>, + Box>, >; /// Resources scope. @@ -503,10 +503,10 @@ pub struct ScopeFactoryResponse { fut: Vec, data: Option>, default: Option, - default_fut: Option>>, + default_fut: Option>>, } -type HttpServiceFut = Box>; +type HttpServiceFut = Box>; enum CreateScopeServiceItem { Future(Option, Option, HttpServiceFut), diff --git a/src/types/form.rs b/src/types/form.rs index 32d0edb6..e61145b0 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -73,7 +73,7 @@ where { type Config = FormConfig; type Error = Error; - type Future = Box>; + type Future = Box>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { @@ -187,7 +187,7 @@ pub struct UrlEncoded { length: Option, encoding: &'static Encoding, err: Option, - fut: Option>>, + fut: Option>>, } impl UrlEncoded { diff --git a/src/types/json.rs b/src/types/json.rs index de0ffb54..f309a3c5 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -169,7 +169,7 @@ where T: DeserializeOwned + 'static, { type Error = Error; - type Future = Box>; + type Future = Box>; type Config = JsonConfig; #[inline] @@ -290,7 +290,7 @@ pub struct JsonBody { length: Option, stream: Option>, err: Option, - fut: Option>>, + fut: Option>>, } impl JsonBody diff --git a/src/types/payload.rs b/src/types/payload.rs index a8e85e4f..8a634b4c 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -124,7 +124,7 @@ impl FromRequest for Bytes { type Config = PayloadConfig; type Error = Error; type Future = - Either>, FutureResult>; + Either>, FutureResult>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -177,8 +177,10 @@ impl FromRequest for Bytes { impl FromRequest for String { type Config = PayloadConfig; type Error = Error; - type Future = - Either>, FutureResult>; + type Future = Either< + Box>, + FutureResult, + >; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -291,7 +293,7 @@ pub struct HttpMessageBody { length: Option, stream: Option>, err: Option, - fut: Option>>, + fut: Option>>, } impl HttpMessageBody { From c01611d8b51ddc57841dce48270eaa66f0ab9030 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Jul 2019 12:07:12 +0600 Subject: [PATCH 1499/1635] prepare actix-web release --- CHANGES.md | 2 +- Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 11158925..cb5f5112 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.4] - TBD +## [1.0.4] - 2019-07-17 ### Added diff --git a/Cargo.toml b/Cargo.toml index 57676acc..d781422e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.3" +version = "1.0.4" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -71,9 +71,9 @@ rust-tls = ["rustls", "actix-server/rust-tls"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-utils = "0.4.2" +actix-utils = "0.4.3" actix-router = "0.1.5" -actix-rt = "0.2.3" +actix-rt = "0.2.4" actix-web-codegen = "0.1.2" actix-http = "0.2.5" actix-server = "0.5.1" From 32718b7e3101e6d1507f883318dcc050890eda37 Mon Sep 17 00:00:00 2001 From: Ravi Shankar Date: Wed, 17 Jul 2019 12:28:42 +0530 Subject: [PATCH 1500/1635] Expose factory traits and some clippy fixes (#983) --- src/data.rs | 2 +- src/handler.rs | 2 +- src/info.rs | 2 +- src/lib.rs | 2 ++ src/middleware/compress.rs | 1 + src/middleware/defaultheaders.rs | 1 + src/middleware/logger.rs | 4 ++-- src/resource.rs | 2 +- src/rmap.rs | 2 +- src/scope.rs | 4 ++-- src/types/form.rs | 1 + src/types/json.rs | 1 + src/types/payload.rs | 1 + 13 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/data.rs b/src/data.rs index bd166b79..3461d24f 100644 --- a/src/data.rs +++ b/src/data.rs @@ -118,7 +118,7 @@ impl FromRequest for Data { impl DataFactory for Data { fn create(&self, extensions: &mut Extensions) -> bool { if !extensions.contains::>() { - let _ = extensions.insert(Data(self.0.clone())); + extensions.insert(Data(self.0.clone())); true } else { false diff --git a/src/handler.rs b/src/handler.rs index bd0b3551..078abbf1 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -250,7 +250,7 @@ where Ok(Async::Ready(res)) => { self.fut2 = Some(res.respond_to(self.req.as_ref().unwrap()).into_future()); - return self.poll(); + self.poll() } Ok(Async::NotReady) => Ok(Async::NotReady), Err(e) => { diff --git a/src/info.rs b/src/info.rs index e9b37587..a6a7d728 100644 --- a/src/info.rs +++ b/src/info.rs @@ -25,7 +25,7 @@ impl ConnectionInfo { Ref::map(req.extensions(), |e| e.get().unwrap()) } - #[allow(clippy::cyclomatic_complexity)] + #[allow(clippy::cyclomatic_complexity, clippy::cognitive_complexity, clippy::borrow_interior_mutable_const)] fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { let mut host = None; let mut scheme = None; diff --git a/src/lib.rs b/src/lib.rs index fffbc2f5..345987ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,6 +134,8 @@ pub mod dev { //! ``` pub use crate::config::{AppConfig, AppService}; + #[doc(hidden)] + pub use crate::handler::{AsyncFactory, Factory}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; pub use crate::service::{ diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 86665d82..c9d2107f 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -107,6 +107,7 @@ where self.service.poll_ready() } + #[allow(clippy::borrow_interior_mutable_const)] fn call(&mut self, req: ServiceRequest) -> Self::Future { // negotiate content-encoding let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index ab2d36c2..a353f5fc 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -125,6 +125,7 @@ where self.service.poll_ready() } + #[allow(clippy::borrow_interior_mutable_const)] fn call(&mut self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d47e4502..24aabb95 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -415,9 +415,9 @@ impl FormatText { )) }; } - FormatText::UrlPath => *self = FormatText::Str(format!("{}", req.path())), + FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()), FormatText::RequestTime => { - *self = FormatText::Str(format!("{}", now.rfc3339())) + *self = FormatText::Str(now.rfc3339().to_string()) } FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { diff --git a/src/resource.rs b/src/resource.rs index d09beb27..0d66aa84 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -426,7 +426,7 @@ where fn into_new_service(self) -> T { *self.factory_ref.borrow_mut() = Some(ResourceFactory { routes: self.routes, - data: self.data.map(|data| Rc::new(data)), + data: self.data.map(Rc::new), default: self.default, }); diff --git a/src/rmap.rs b/src/rmap.rs index cad62dca..42ddb134 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -123,7 +123,7 @@ impl ResourceMap { I: AsRef, { if let Some(pattern) = self.named.get(name) { - if pattern.pattern().starts_with("/") { + if pattern.pattern().starts_with('/') { self.fill_root(path, elements)?; } if pattern.resource_path(path, elements) { diff --git a/src/scope.rs b/src/scope.rs index 99afd7d1..714f0354 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -195,7 +195,7 @@ where self.external.extend(cfg.external); if !cfg.data.is_empty() { - let mut data = self.data.unwrap_or_else(|| Extensions::new()); + let mut data = self.data.unwrap_or_else(Extensions::new); for value in cfg.data.iter() { value.create(&mut data); @@ -425,7 +425,7 @@ where // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { - data: self.data.take().map(|data| Rc::new(data)), + data: self.data.take().map(Rc::new), default: self.default.clone(), services: Rc::new( cfg.into_services() diff --git a/src/types/form.rs b/src/types/form.rs index e61145b0..ac202b17 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -192,6 +192,7 @@ pub struct UrlEncoded { impl UrlEncoded { /// Create a new future to URL encode a request + #[allow(clippy::borrow_interior_mutable_const)] pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { diff --git a/src/types/json.rs b/src/types/json.rs index f309a3c5..70feef8d 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -298,6 +298,7 @@ where U: DeserializeOwned + 'static, { /// Create `JsonBody` for request. + #[allow(clippy::borrow_interior_mutable_const)] pub fn new( req: &HttpRequest, payload: &mut Payload, diff --git a/src/types/payload.rs b/src/types/payload.rs index 8a634b4c..34c3e298 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -298,6 +298,7 @@ pub struct HttpMessageBody { impl HttpMessageBody { /// Create `MessageBody` for request. + #[allow(clippy::borrow_interior_mutable_const)] pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody { let mut len = None; if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { From baaa7b3fbb61cec96d9e0255d4b798334fb4518c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Jul 2019 13:55:44 +0600 Subject: [PATCH 1501/1635] Replace ClonableService with local copy --- Cargo.toml | 2 +- actix-http/CHANGES.md | 5 ++- actix-http/Cargo.toml | 6 +-- actix-http/src/client/connector.rs | 1 + actix-http/src/client/pool.rs | 4 +- actix-http/src/cloneable.rs | 42 +++++++++++++++++++ actix-http/src/cookie/mod.rs | 1 + actix-http/src/cookie/secure/key.rs | 2 +- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/service.rs | 4 +- actix-http/src/h2/dispatcher.rs | 2 +- actix-http/src/h2/service.rs | 4 +- .../src/header/common/content_disposition.rs | 7 +++- actix-http/src/lib.rs | 5 ++- actix-http/src/message.rs | 1 + actix-http/src/service.rs | 4 +- actix-http/src/ws/proto.rs | 5 +-- src/info.rs | 6 ++- 18 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 actix-http/src/cloneable.rs diff --git a/Cargo.toml b/Cargo.toml index d781422e..470644e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ rust-tls = ["rustls", "actix-server/rust-tls"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-utils = "0.4.3" +actix-utils = "0.4.4" actix-router = "0.1.5" actix-rt = "0.2.4" actix-web-codegen = "0.1.2" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 51679326..84033531 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,11 +1,14 @@ # Changes -## [0.2.6] - TBD +## [0.2.6] - 2019-07-17 ### Changed +* Replace `ClonableService` with local copy + * Upgrade `rand` dependency version to 0.7 + ## [0.2.5] - 2019-06-28 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 5db7a6ca..e922c86d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -46,10 +46,10 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.4.1" actix-codec = "0.1.2" -actix-connect = "0.2.0" -actix-utils = "0.4.2" +actix-connect = "0.2.1" +actix-utils = "0.4.4" actix-server-config = "0.1.1" -actix-threadpool = "0.1.0" +actix-threadpool = "0.1.1" base64 = "0.10" bitflags = "1.0" diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 0241e847..cd3ae3d9 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -47,6 +47,7 @@ pub struct Connector { } impl Connector<(), ()> { + #[allow(clippy::new_ret_no_self)] pub fn new() -> Connector< impl Service< Request = TcpConnect, diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 8dedf72f..4739141d 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -427,7 +427,9 @@ where fn check_availibility(&self) { if !self.waiters_queue.is_empty() && self.acquired < self.limit { - self.task.as_ref().map(|t| t.notify()); + if let Some(t) = self.task.as_ref() { + t.notify() + } } } } diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs new file mode 100644 index 00000000..ffc1d061 --- /dev/null +++ b/actix-http/src/cloneable.rs @@ -0,0 +1,42 @@ +use std::cell::UnsafeCell; +use std::rc::Rc; + +use actix_service::Service; +use futures::Poll; + +#[doc(hidden)] +/// Service that allows to turn non-clone service to a service with `Clone` impl +pub(crate) struct CloneableService(Rc>); + +impl CloneableService { + pub(crate) fn new(service: T) -> Self + where + T: Service, + { + Self(Rc::new(UnsafeCell::new(service))) + } +} + +impl Clone for CloneableService { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Service for CloneableService +where + T: Service, +{ + type Request = T::Request; + type Response = T::Response; + type Error = T::Error; + type Future = T::Future; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + unsafe { &mut *self.0.as_ref().get() }.poll_ready() + } + + fn call(&mut self, req: T::Request) -> Self::Future { + unsafe { &mut *self.0.as_ref().get() }.call(req) + } +} diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index ddcb12bb..f576a452 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -104,6 +104,7 @@ impl CookieStr { } } + #[allow(clippy::ptr_arg)] fn to_raw_str<'s, 'c: 's>(&'s self, string: &'s Cow<'c, str>) -> Option<&'c str> { match *self { CookieStr::Indexed(i, j) => match *string { diff --git a/actix-http/src/cookie/secure/key.rs b/actix-http/src/cookie/secure/key.rs index 4e74f6e7..8435ce3a 100644 --- a/actix-http/src/cookie/secure/key.rs +++ b/actix-http/src/cookie/secure/key.rs @@ -7,7 +7,7 @@ use super::private::KEY_LEN as PRIVATE_KEY_LEN; use super::signed::KEY_LEN as SIGNED_KEY_LEN; static HKDF_DIGEST: &'static Algorithm = &SHA256; -const KEYS_INFO: &'static str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; +const KEYS_INFO: &str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; /// A cryptographic master key for use with `Signed` and/or `Private` jars. /// diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 91990d05..5e9c0b53 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -5,7 +5,6 @@ use std::{fmt, io, net}; use actix_codec::{Decoder, Encoder, Framed, FramedParts}; use actix_server_config::IoStream; use actix_service::Service; -use actix_utils::cloneable::CloneableService; use bitflags::bitflags; use bytes::{BufMut, BytesMut}; use futures::{Async, Future, Poll}; @@ -13,6 +12,7 @@ use log::{error, trace}; use tokio_timer::Delay; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; +use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 192d1b59..108b7079 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -5,11 +5,11 @@ use std::rc::Rc; use actix_codec::Framed; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use crate::body::MessageBody; +use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error, ParseError}; use crate::helpers::DataFactory; @@ -259,7 +259,7 @@ where H1ServiceHandler { srv: CloneableService::new(srv), expect: CloneableService::new(expect), - upgrade: upgrade.map(|s| CloneableService::new(s)), + upgrade: upgrade.map(CloneableService::new), cfg, on_connect, _t: PhantomData, diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 48d32993..2bd7940d 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -6,7 +6,6 @@ use std::{fmt, mem, net}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_server_config::IoStream; use actix_service::Service; -use actix_utils::cloneable::CloneableService; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; @@ -20,6 +19,7 @@ use log::{debug, error, trace}; use tokio_timer::Delay; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; +use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::helpers::DataFactory; diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index efc400da..487d5b6a 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -5,7 +5,6 @@ use std::{io, net, rc}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; use bytes::Bytes; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; @@ -14,6 +13,7 @@ use h2::RecvStream; use log::error; use crate::body::MessageBody; +use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error, ParseError, ResponseError}; use crate::helpers::DataFactory; @@ -256,7 +256,7 @@ where on_connect.take(), config.take().unwrap(), None, - peer_addr.clone(), + *peer_addr, )); self.poll() } diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index badf307a..14fcc351 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -70,6 +70,7 @@ impl<'a> From<&'a str> for DispositionType { /// assert_eq!(param.as_filename().unwrap(), "sample.txt"); /// ``` #[derive(Clone, Debug, PartialEq)] +#[allow(clippy::large_enum_variant)] pub enum DispositionParam { /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from /// the form. @@ -719,8 +720,10 @@ mod tests { }; assert_eq!(a, b); - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx\"").unwrap(); + let a = HeaderValue::from_str( + "form-data; name=upload; filename=\"余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx\"", + ) + .unwrap(); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::FormData, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index ac085eae..6b8874b2 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,8 +1,10 @@ //! Basic http primitives for actix-net framework. #![allow( clippy::type_complexity, + clippy::too_many_arguments, clippy::new_without_default, - clippy::borrow_interior_mutable_const + clippy::borrow_interior_mutable_const, + clippy::write_with_newline )] #[macro_use] @@ -11,6 +13,7 @@ extern crate log; pub mod body; mod builder; pub mod client; +mod cloneable; mod config; pub mod encoding; mod extensions; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index f3c01a12..cf23a401 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -385,6 +385,7 @@ impl Drop for BoxedResponseHead { pub struct MessagePool(RefCell>>); #[doc(hidden)] +#[allow(clippy::vec_box)] /// Request's objects pool pub struct BoxedResponsePool(RefCell>>); diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 1ac01880..d37d377e 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -6,13 +6,13 @@ use actix_server_config::{ Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig, }; use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; use h2::server::{self, Handshake}; use crate::body::MessageBody; use crate::builder::HttpServiceBuilder; +use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error}; use crate::helpers::DataFactory; @@ -285,7 +285,7 @@ where on_connect, srv: CloneableService::new(srv), expect: CloneableService::new(expect), - upgrade: upgrade.map(|s| CloneableService::new(s)), + upgrade: upgrade.map(CloneableService::new), _t: PhantomData, } } diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index eef87474..9b51b922 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -47,10 +47,7 @@ impl Into for OpCode { Ping => 9, Pong => 10, Bad => { - debug_assert!( - false, - "Attempted to convert invalid opcode to u8. This is a bug." - ); + log::error!("Attempted to convert invalid opcode to u8. This is a bug."); 8 // if this somehow happens, a close frame will help us tear down quickly } } diff --git a/src/info.rs b/src/info.rs index a6a7d728..0caa9683 100644 --- a/src/info.rs +++ b/src/info.rs @@ -25,7 +25,11 @@ impl ConnectionInfo { Ref::map(req.extensions(), |e| e.get().unwrap()) } - #[allow(clippy::cyclomatic_complexity, clippy::cognitive_complexity, clippy::borrow_interior_mutable_const)] + #[allow( + clippy::cyclomatic_complexity, + clippy::cognitive_complexity, + clippy::borrow_interior_mutable_const + )] fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { let mut host = None; let mut scheme = None; From ef3e1037a86f2d6d2176e9f60847fe3a1d7c84cd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Jul 2019 14:18:26 +0600 Subject: [PATCH 1502/1635] bump version --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e922c86d..ae12cbc4 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.5" +version = "0.2.6" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From 4092c7f326fa18c65b40ae703c68dfa4128be92d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Jul 2019 15:08:30 +0600 Subject: [PATCH 1503/1635] clippy warnings --- actix-cors/src/lib.rs | 2 + actix-files/src/lib.rs | 4 +- actix-files/src/named.rs | 8 ++-- actix-framed/src/lib.rs | 6 +++ actix-identity/src/lib.rs | 12 +++--- actix-multipart/src/lib.rs | 2 + actix-multipart/src/server.rs | 12 +++--- actix-session/src/cookie.rs | 2 +- actix-web-actors/src/lib.rs | 1 + actix-web-actors/src/ws.rs | 2 +- actix-web-codegen/src/route.rs | 65 ++++++++++++++------------------ awc/src/builder.rs | 6 +++ awc/src/lib.rs | 1 + awc/src/request.rs | 20 +++------- awc/src/test.rs | 2 +- awc/src/ws.rs | 2 +- src/info.rs | 6 +-- src/lib.rs | 1 + src/middleware/compress.rs | 1 - src/middleware/defaultheaders.rs | 1 - src/server.rs | 8 ++-- src/test.rs | 2 +- src/types/form.rs | 1 - src/types/json.rs | 1 - src/types/payload.rs | 1 - test-server/src/lib.rs | 5 ++- 26 files changed, 84 insertions(+), 90 deletions(-) diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index ea1eb383..d9a44d0b 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_interior_mutable_const, clippy::type_complexity)] //! Cross-origin resource sharing (CORS) for Actix applications //! //! CORS middleware could be used with application and with resource. @@ -162,6 +163,7 @@ impl AllOrSome { /// .max_age(3600); /// # } /// ``` +#[derive(Default)] pub struct Cors { cors: Option, methods: bool, diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 82abb999..c870c9dd 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::borrow_interior_mutable_const, clippy::type_complexity)] + //! Static files support use std::cell::RefCell; use std::fmt::Write; @@ -57,7 +59,7 @@ pub struct ChunkedReadFile { fn handle_error(err: BlockingError) -> Error { match err { BlockingError::Error(err) => err.into(), - BlockingError::Canceled => ErrorInternalServerError("Unexpected error").into(), + BlockingError::Canceled => ErrorInternalServerError("Unexpected error"), } } diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 6b948da8..3273a4d6 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -24,8 +24,8 @@ use crate::ChunkedReadFile; bitflags! { pub(crate) struct Flags: u32 { - const ETAG = 0b00000001; - const LAST_MD = 0b00000010; + const ETAG = 0b0000_0001; + const LAST_MD = 0b0000_0010; } } @@ -311,8 +311,8 @@ impl Responder for NamedFile { return Ok(resp.streaming(reader)); } - match req.method() { - &Method::HEAD | &Method::GET => (), + match *req.method() { + Method::HEAD | Method::GET => (), _ => { return Ok(HttpResponse::MethodNotAllowed() .header(header::CONTENT_TYPE, "text/plain") diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs index c6405e20..5e72ba5b 100644 --- a/actix-framed/src/lib.rs +++ b/actix-framed/src/lib.rs @@ -1,3 +1,9 @@ +#![allow( + clippy::type_complexity, + clippy::new_without_default, + dead_code, + deprecated +)] mod app; mod helpers; mod request; diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index fe7216a0..0ae58fe8 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -333,8 +333,7 @@ struct CookieIdentityExtention { impl CookieIdentityInner { fn new(key: &[u8]) -> CookieIdentityInner { - let key_v2: Vec = - key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); + let key_v2: Vec = key.iter().chain([1, 0, 0, 0].iter()).cloned().collect(); CookieIdentityInner { key: Key::from_master(key), key_v2: Key::from_master(&key_v2), @@ -585,13 +584,14 @@ impl IdentityPolicy for CookieIdentityPolicy { ) } else if self.0.always_update_cookie() && id.is_some() { let visit_timestamp = SystemTime::now(); - let mut login_timestamp = None; - if self.0.requires_oob_data() { + let login_timestamp = if self.0.requires_oob_data() { let CookieIdentityExtention { login_timestamp: lt, } = res.request().extensions_mut().remove().unwrap(); - login_timestamp = lt; - } + lt + } else { + None + }; self.0.set_cookie( res, Some(CookieValue { diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs index d8f365b2..43eb048c 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::borrow_interior_mutable_const)] + mod error; mod extractor; mod server; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index e1a5543d..c28a8d6a 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -418,7 +418,7 @@ impl Stream for Field { inner.poll(&self.safety) } else if !self.safety.is_clean() { - return Err(MultipartError::NotConsumed); + Err(MultipartError::NotConsumed) } else { Ok(Async::NotReady) } @@ -533,11 +533,9 @@ impl InnerField { let b_size = boundary.len() + b_len; if len < b_size { return Ok(Async::NotReady); - } else { - if &payload.buf[b_len..b_size] == boundary.as_bytes() { - // found boundary - return Ok(Async::Ready(None)); - } + } else if &payload.buf[b_len..b_size] == boundary.as_bytes() { + // found boundary + return Ok(Async::Ready(None)); } } } @@ -557,7 +555,7 @@ impl InnerField { // check boundary if (&payload.buf[cur..cur + 2] == b"\r\n" && &payload.buf[cur + 2..cur + 4] == b"--") - || (&payload.buf[cur..cur + 1] == b"\r" + || (&payload.buf[cur..=cur] == b"\r" && &payload.buf[cur + 1..cur + 3] == b"--") { if cur != 0 { diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 87fc0b16..19273778 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -342,7 +342,7 @@ where } } (SessionStatus::Purged, _) => { - inner.remove_cookie(&mut res); + let _ = inner.remove_cookie(&mut res); res } _ => res, diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 5b64d7e0..6360917c 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_interior_mutable_const)] //! Actix actors integration for Actix web framework mod context; pub mod ws; diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index fece826d..08d8b108 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -435,7 +435,7 @@ where } } Frame::Binary(data) => Message::Binary( - data.map(|b| b.freeze()).unwrap_or_else(|| Bytes::new()), + data.map(|b| b.freeze()).unwrap_or_else(Bytes::new), ), Frame::Ping(s) => Message::Ping(s), Frame::Pong(s) => Message::Pong(s), diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 268adecb..5215f60c 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -12,9 +12,9 @@ enum ResourceType { impl fmt::Display for ResourceType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &ResourceType::Async => write!(f, "to_async"), - &ResourceType::Sync => write!(f, "to"), + match *self { + ResourceType::Async => write!(f, "to_async"), + ResourceType::Sync => write!(f, "to"), } } } @@ -34,16 +34,16 @@ pub enum GuardType { impl fmt::Display for GuardType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &GuardType::Get => write!(f, "Get"), - &GuardType::Post => write!(f, "Post"), - &GuardType::Put => write!(f, "Put"), - &GuardType::Delete => write!(f, "Delete"), - &GuardType::Head => write!(f, "Head"), - &GuardType::Connect => write!(f, "Connect"), - &GuardType::Options => write!(f, "Options"), - &GuardType::Trace => write!(f, "Trace"), - &GuardType::Patch => write!(f, "Patch"), + match *self { + GuardType::Get => write!(f, "Get"), + GuardType::Post => write!(f, "Post"), + GuardType::Put => write!(f, "Put"), + GuardType::Delete => write!(f, "Delete"), + GuardType::Head => write!(f, "Head"), + GuardType::Connect => write!(f, "Connect"), + GuardType::Options => write!(f, "Options"), + GuardType::Trace => write!(f, "Trace"), + GuardType::Patch => write!(f, "Patch"), } } } @@ -92,37 +92,27 @@ impl actix_web::dev::HttpServiceFactory for {name} {{ fn guess_resource_type(typ: &syn::Type) -> ResourceType { let mut guess = ResourceType::Sync; - match typ { - syn::Type::ImplTrait(typ) => { - for bound in typ.bounds.iter() { - match bound { - syn::TypeParamBound::Trait(bound) => { - for bound in bound.path.segments.iter() { - if bound.ident == "Future" { - guess = ResourceType::Async; - break; - } else if bound.ident == "Responder" { - guess = ResourceType::Sync; - break; - } - } + if let syn::Type::ImplTrait(typ) = typ { + for bound in typ.bounds.iter() { + if let syn::TypeParamBound::Trait(bound) = bound { + for bound in bound.path.segments.iter() { + if bound.ident == "Future" { + guess = ResourceType::Async; + break; + } else if bound.ident == "Responder" { + guess = ResourceType::Sync; + break; } - _ => (), } } } - _ => (), } guess } impl Args { - pub fn new( - args: &Vec, - input: TokenStream, - guard: GuardType, - ) -> Self { + pub fn new(args: &[syn::NestedMeta], input: TokenStream, guard: GuardType) -> Self { if args.is_empty() { panic!( "invalid server definition, expected: #[{}(\"some path\")]", @@ -164,9 +154,10 @@ impl Args { ResourceType::Async } else { match ast.decl.output { - syn::ReturnType::Default => { - panic!("Function {} has no return type. Cannot be used as handler") - } + syn::ReturnType::Default => panic!( + "Function {} has no return type. Cannot be used as handler", + name + ), syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), } }; diff --git a/awc/src/builder.rs b/awc/src/builder.rs index a58265c5..463f4030 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -21,6 +21,12 @@ pub struct ClientBuilder { max_redirects: usize, } +impl Default for ClientBuilder { + fn default() -> Self { + Self::new() + } +} + impl ClientBuilder { pub fn new() -> Self { ClientBuilder { diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 45231326..da63bbd9 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_interior_mutable_const)] //! An HTTP Client //! //! ```rust diff --git a/awc/src/request.rs b/awc/src/request.rs index 36cd6fcf..0e544589 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -185,9 +185,7 @@ impl ClientRequest { { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { - Ok(value) => { - let _ = self.head.headers.append(key, value); - } + Ok(value) => self.head.headers.append(key, value), Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), @@ -203,9 +201,7 @@ impl ClientRequest { { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { - Ok(value) => { - let _ = self.head.headers.insert(key, value); - } + Ok(value) => self.head.headers.insert(key, value), Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), @@ -223,9 +219,7 @@ impl ClientRequest { Ok(key) => { if !self.head.headers.contains_key(&key) { match value.try_into() { - Ok(value) => { - let _ = self.head.headers.insert(key, value); - } + Ok(value) => self.head.headers.insert(key, value), Err(e) => self.err = Some(e.into()), } } @@ -257,9 +251,7 @@ impl ClientRequest { HeaderValue: HttpTryFrom, { match HeaderValue::try_from(value) { - Ok(value) => { - let _ = self.head.headers.insert(header::CONTENT_TYPE, value); - } + Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value), Err(e) => self.err = Some(e.into()), } self @@ -321,7 +313,7 @@ impl ClientRequest { /// })); /// } /// ``` - pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { + pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); jar.add(cookie.into_owned()); @@ -465,7 +457,7 @@ impl ClientRequest { }); // set request timeout - if let Some(timeout) = slf.timeout.or_else(|| config.timeout.clone()) { + if let Some(timeout) = slf.timeout.or_else(|| config.timeout) { Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { if let Some(e) = e.into_inner() { e diff --git a/awc/src/test.rs b/awc/src/test.rs index f2c513ba..b852adb2 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -68,7 +68,7 @@ impl TestResponse { } /// Set cookie for this response - pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { self.cookies.add(cookie.into_owned()); self } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 95bf6ef7..27f454ed 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -90,7 +90,7 @@ impl WebsocketsRequest { } /// Set a cookie - pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { + pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); jar.add(cookie.into_owned()); diff --git a/src/info.rs b/src/info.rs index 0caa9683..ba59605d 100644 --- a/src/info.rs +++ b/src/info.rs @@ -25,11 +25,7 @@ impl ConnectionInfo { Ref::map(req.extensions(), |e| e.get().unwrap()) } - #[allow( - clippy::cyclomatic_complexity, - clippy::cognitive_complexity, - clippy::borrow_interior_mutable_const - )] + #[allow(clippy::cognitive_complexity)] fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { let mut host = None; let mut scheme = None; diff --git a/src/lib.rs b/src/lib.rs index 345987ff..857bc294 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_interior_mutable_const)] //! Actix web is a small, pragmatic, and extremely fast web framework //! for Rust. //! diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index c9d2107f..86665d82 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -107,7 +107,6 @@ where self.service.poll_ready() } - #[allow(clippy::borrow_interior_mutable_const)] fn call(&mut self, req: ServiceRequest) -> Self::Future { // negotiate content-encoding let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index a353f5fc..ab2d36c2 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -125,7 +125,6 @@ where self.service.poll_ready() } - #[allow(clippy::borrow_interior_mutable_const)] fn call(&mut self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); diff --git a/src/server.rs b/src/server.rs index 353f29ba..43236fa3 100644 --- a/src/server.rs +++ b/src/server.rs @@ -288,13 +288,13 @@ where lst, move || { let c = cfg.lock(); - acceptor.clone().map_err(|e| SslError::Ssl(e)).and_then( + acceptor.clone().map_err(SslError::Ssl).and_then( HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) .client_disconnect(c.client_shutdown) .finish(factory()) - .map_err(|e| SslError::Service(e)) + .map_err(SslError::Service) .map_init_err(|_| ()), ) }, @@ -339,13 +339,13 @@ where lst, move || { let c = cfg.lock(); - acceptor.clone().map_err(|e| SslError::Ssl(e)).and_then( + acceptor.clone().map_err(SslError::Ssl).and_then( HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) .client_disconnect(c.client_shutdown) .finish(factory()) - .map_err(|e| SslError::Service(e)) + .map_err(SslError::Service) .map_init_err(|_| ()), ) }, diff --git a/src/test.rs b/src/test.rs index 208360a2..562fdf43 100644 --- a/src/test.rs +++ b/src/test.rs @@ -79,7 +79,7 @@ where F: FnOnce() -> R, R: IntoFuture, { - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(|| f()))) + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(f))) } #[doc(hidden)] diff --git a/src/types/form.rs b/src/types/form.rs index ac202b17..e61145b0 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -192,7 +192,6 @@ pub struct UrlEncoded { impl UrlEncoded { /// Create a new future to URL encode a request - #[allow(clippy::borrow_interior_mutable_const)] pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { diff --git a/src/types/json.rs b/src/types/json.rs index 70feef8d..f309a3c5 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -298,7 +298,6 @@ where U: DeserializeOwned + 'static, { /// Create `JsonBody` for request. - #[allow(clippy::borrow_interior_mutable_const)] pub fn new( req: &HttpRequest, payload: &mut Payload, diff --git a/src/types/payload.rs b/src/types/payload.rs index 34c3e298..8a634b4c 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -298,7 +298,6 @@ pub struct HttpMessageBody { impl HttpMessageBody { /// Create `MessageBody` for request. - #[allow(clippy::borrow_interior_mutable_const)] pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody { let mut len = None; if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index c49026fa..38405cd5 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -65,7 +65,7 @@ where F: FnOnce() -> R, R: IntoFuture, { - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(|| f()))) + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(f))) } /// The `TestServer` type. @@ -107,6 +107,7 @@ pub struct TestServerRuntime { } impl TestServer { + #[allow(clippy::new_ret_no_self)] /// Start new test server with application factory pub fn new(factory: F) -> TestServerRuntime { let (tx, rx) = mpsc::channel(); @@ -191,7 +192,7 @@ impl TestServerRuntime { F: FnOnce() -> R, R: Future, { - self.rt.block_on(lazy(|| f())) + self.rt.block_on(lazy(f)) } /// Execute function on current core From 2a2d7f57687b635262b426f873702562b5395005 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 17 Jul 2019 15:48:37 +0600 Subject: [PATCH 1504/1635] nightly clippy warnings --- Cargo.toml | 4 ++-- actix-cors/src/lib.rs | 6 +++--- actix-files/src/lib.rs | 16 ++++++++-------- actix-files/src/range.rs | 2 +- actix-framed/src/helpers.rs | 4 ++-- actix-http/src/builder.rs | 2 +- actix-http/src/client/connection.rs | 4 ++-- actix-http/src/cookie/secure/key.rs | 2 +- actix-http/src/cookie/secure/private.rs | 2 +- actix-http/src/cookie/secure/signed.rs | 2 +- actix-http/src/error.rs | 4 ++-- actix-http/src/extensions.rs | 8 ++++---- actix-http/src/h1/decoder.rs | 6 +++--- actix-http/src/h1/service.rs | 10 +++++----- actix-http/src/h2/service.rs | 10 +++++----- actix-http/src/service.rs | 10 +++++----- actix-http/src/ws/proto.rs | 2 +- actix-identity/src/lib.rs | 4 ++-- actix-session/src/lib.rs | 4 ++-- awc/src/connect.rs | 16 ++++++++-------- src/app.rs | 7 ++++--- src/app_service.rs | 15 ++++++++------- src/config.rs | 12 ++++++------ src/guard.rs | 6 +++--- src/middleware/errhandlers.rs | 2 +- src/middleware/logger.rs | 4 +++- src/resource.rs | 4 ++-- src/route.rs | 12 ++++++------ src/scope.rs | 8 ++++---- src/service.rs | 4 ++-- src/types/form.rs | 2 +- src/types/path.rs | 2 +- src/types/payload.rs | 4 ++-- src/types/query.rs | 3 ++- 34 files changed, 104 insertions(+), 99 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 470644e0..866a7628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ actix-utils = "0.4.4" actix-router = "0.1.5" actix-rt = "0.2.4" actix-web-codegen = "0.1.2" -actix-http = "0.2.5" +actix-http = "0.2.6" actix-server = "0.5.1" actix-server-config = "0.1.1" actix-threadpool = "0.1.1" @@ -89,7 +89,7 @@ hashbrown = "0.5.0" log = "0.4" mime = "0.3" net2 = "0.2.33" -parking_lot = "0.8" +parking_lot = "0.9" regex = "1.0" serde = { version = "1.0", features=["derive"] } serde_json = "1.0" diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index d9a44d0b..c76bae92 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -587,10 +587,10 @@ impl Inner { } Err(CorsError::BadOrigin) } else { - return match self.origins { + match self.origins { AllOrSome::All => Ok(()), _ => Err(CorsError::MissingOrigin), - }; + } } } @@ -665,7 +665,7 @@ impl Inner { } Err(CorsError::BadRequestHeaders) } else { - return Ok(()); + Ok(()) } } } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index c870c9dd..cf07153f 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -107,7 +107,7 @@ impl Stream for ChunkedReadFile { } type DirectoryRenderer = - Fn(&Directory, &HttpRequest) -> Result; + dyn Fn(&Directory, &HttpRequest) -> Result; /// A directory; responds with the generated directory listing. #[derive(Debug)] @@ -211,7 +211,7 @@ fn directory_listing( )) } -type MimeOverride = Fn(&mime::Name) -> DispositionType; +type MimeOverride = dyn Fn(&mime::Name) -> DispositionType; /// Static files handling /// @@ -475,7 +475,7 @@ impl Service for FilesService { Err(e) => ServiceResponse::from_err(e, req), })) } - Err(e) => return self.handle_err(e, req), + Err(e) => self.handle_err(e, req), } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); @@ -483,7 +483,7 @@ impl Service for FilesService { let x = (self.renderer)(&dir, &req); match x { Ok(resp) => Either::A(ok(resp)), - Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req))), + Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))), } } else { Either::A(ok(ServiceResponse::from_err( @@ -857,7 +857,7 @@ mod tests { #[test] fn test_named_file_content_length_headers() { - use actix_web::body::{MessageBody, ResponseBody}; + // use actix_web::body::{MessageBody, ResponseBody}; let mut srv = test::init_service( App::new().service(Files::new("test", ".").index_file("tests/test.binary")), @@ -868,7 +868,7 @@ mod tests { .uri("/t%65st/tests/test.binary") .header(header::RANGE, "bytes=10-20") .to_request(); - let response = test::call_service(&mut srv, request); + let _response = test::call_service(&mut srv, request); // let contentlength = response // .headers() @@ -891,7 +891,7 @@ mod tests { .uri("/t%65st/tests/test.binary") // .no_default_headers() .to_request(); - let response = test::call_service(&mut srv, request); + let _response = test::call_service(&mut srv, request); // let contentlength = response // .headers() @@ -939,7 +939,7 @@ mod tests { .method(Method::HEAD) .uri("/t%65st/tests/test.binary") .to_request(); - let response = test::call_service(&mut srv, request); + let _response = test::call_service(&mut srv, request); // TODO: fix check // let contentlength = response diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index d97a35e7..47673b0b 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -5,7 +5,7 @@ pub struct HttpRange { pub length: u64, } -static PREFIX: &'static str = "bytes="; +static PREFIX: &str = "bytes="; const PREFIX_LEN: usize = 6; impl HttpRange { diff --git a/actix-framed/src/helpers.rs b/actix-framed/src/helpers.rs index 5e84ad88..b343301f 100644 --- a/actix-framed/src/helpers.rs +++ b/actix-framed/src/helpers.rs @@ -3,7 +3,7 @@ use actix_service::{NewService, Service}; use futures::{Future, Poll}; pub(crate) type BoxedHttpService = Box< - Service< + dyn Service< Request = Req, Response = (), Error = Error, @@ -12,7 +12,7 @@ pub(crate) type BoxedHttpService = Box< >; pub(crate) type BoxedHttpNewService = Box< - NewService< + dyn NewService< Config = (), Request = Req, Response = (), diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index b6967d94..bab0f5e1 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -26,7 +26,7 @@ pub struct HttpServiceBuilder> { client_disconnect: u64, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, S)>, } diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 2f3103d4..36913c5f 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -130,7 +130,7 @@ where type TunnelFuture = Either< Box< - Future< + dyn Future< Item = (ResponseHead, Framed), Error = SendRequestError, >, @@ -192,7 +192,7 @@ where } type TunnelFuture = Box< - Future< + dyn Future< Item = (ResponseHead, Framed), Error = SendRequestError, >, diff --git a/actix-http/src/cookie/secure/key.rs b/actix-http/src/cookie/secure/key.rs index 8435ce3a..39575c93 100644 --- a/actix-http/src/cookie/secure/key.rs +++ b/actix-http/src/cookie/secure/key.rs @@ -6,7 +6,7 @@ use ring::rand::{SecureRandom, SystemRandom}; use super::private::KEY_LEN as PRIVATE_KEY_LEN; use super::signed::KEY_LEN as SIGNED_KEY_LEN; -static HKDF_DIGEST: &'static Algorithm = &SHA256; +static HKDF_DIGEST: &Algorithm = &SHA256; const KEYS_INFO: &str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; /// A cryptographic master key for use with `Signed` and/or `Private` jars. diff --git a/actix-http/src/cookie/secure/private.rs b/actix-http/src/cookie/secure/private.rs index e5974376..eb8e9beb 100644 --- a/actix-http/src/cookie/secure/private.rs +++ b/actix-http/src/cookie/secure/private.rs @@ -10,7 +10,7 @@ use crate::cookie::{Cookie, CookieJar}; // Keep these in sync, and keep the key len synced with the `private` docs as // well as the `KEYS_INFO` const in secure::Key. -static ALGO: &'static Algorithm = &AES_256_GCM; +static ALGO: &Algorithm = &AES_256_GCM; const NONCE_LEN: usize = 12; pub const KEY_LEN: usize = 32; diff --git a/actix-http/src/cookie/secure/signed.rs b/actix-http/src/cookie/secure/signed.rs index 1b1799cf..36a277cd 100644 --- a/actix-http/src/cookie/secure/signed.rs +++ b/actix-http/src/cookie/secure/signed.rs @@ -6,7 +6,7 @@ use crate::cookie::{Cookie, CookieJar}; // Keep these in sync, and keep the key len synced with the `signed` docs as // well as the `KEYS_INFO` const in secure::Key. -static HMAC_DIGEST: &'static Algorithm = &SHA256; +static HMAC_DIGEST: &Algorithm = &SHA256; const BASE64_DIGEST_LEN: usize = 44; pub const KEY_LEN: usize = 32; diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 4913c3d9..368ee6dd 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -43,12 +43,12 @@ pub type Result = result::Result; /// if you have access to an actix `Error` you can always get a /// `ResponseError` reference from it. pub struct Error { - cause: Box, + cause: Box, } impl Error { /// Returns the reference to the underlying `ResponseError`. - pub fn as_response_error(&self) -> &ResponseError { + pub fn as_response_error(&self) -> &dyn ResponseError { self.cause.as_ref() } } diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 148e4c18..c6266f56 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -6,7 +6,7 @@ use hashbrown::HashMap; #[derive(Default)] /// A type map of request extensions. pub struct Extensions { - map: HashMap>, + map: HashMap>, } impl Extensions { @@ -35,14 +35,14 @@ impl Extensions { pub fn get(&self) -> Option<&T> { self.map .get(&TypeId::of::()) - .and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref()) + .and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref()) } /// Get a mutable reference to a type previously inserted on this `Extensions`. pub fn get_mut(&mut self) -> Option<&mut T> { self.map .get_mut(&TypeId::of::()) - .and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut()) + .and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut()) } /// Remove a type from this `Extensions`. @@ -50,7 +50,7 @@ impl Extensions { /// If a extension of this type existed, it will be returned. pub fn remove(&mut self) -> Option { self.map.remove(&TypeId::of::()).and_then(|boxed| { - (boxed as Box) + (boxed as Box) .downcast() .ok() .map(|boxed| *boxed) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 12419d66..c7ef065a 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -502,15 +502,15 @@ impl ChunkedState { fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll { let radix = 16; match byte!(rdr) { - b @ b'0'...b'9' => { + b @ b'0'..=b'9' => { *size *= radix; *size += u64::from(b - b'0'); } - b @ b'a'...b'f' => { + b @ b'a'..=b'f' => { *size *= radix; *size += u64::from(b + 10 - b'a'); } - b @ b'A'...b'F' => { + b @ b'A'..=b'F' => { *size *= radix; *size += u64::from(b + 10 - b'A'); } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 108b7079..89bf08e9 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -26,7 +26,7 @@ pub struct H1Service> { cfg: ServiceConfig, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -108,7 +108,7 @@ where /// Set on connect callback. pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self @@ -174,7 +174,7 @@ where fut_upg: Option, expect: Option, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, cfg: Option, _t: PhantomData<(T, P, B)>, } @@ -233,7 +233,7 @@ pub struct H1ServiceHandler { srv: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option Box>>, + on_connect: Option Box>>, cfg: ServiceConfig, _t: PhantomData<(T, P, B)>, } @@ -254,7 +254,7 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, ) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 487d5b6a..e894cf66 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -27,7 +27,7 @@ use super::dispatcher::Dispatcher; pub struct H2Service { srv: S, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -64,7 +64,7 @@ where /// Set on connect callback. pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self @@ -102,7 +102,7 @@ where pub struct H2ServiceResponse { fut: ::Future, cfg: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -132,7 +132,7 @@ where pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -146,7 +146,7 @@ where { fn new( cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, srv: S, ) -> H2ServiceHandler { H2ServiceHandler { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index d37d377e..be021b25 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -26,7 +26,7 @@ pub struct HttpService, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -140,7 +140,7 @@ where /// Set on connect callback. pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self @@ -196,7 +196,7 @@ pub struct HttpServiceResponse, expect: Option, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, cfg: Option, _t: PhantomData<(T, P, B)>, } @@ -257,7 +257,7 @@ pub struct HttpServiceHandler { expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B, X)>, } @@ -278,7 +278,7 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, ) -> HttpServiceHandler { HttpServiceHandler { cfg, diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 9b51b922..e14651a5 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -203,7 +203,7 @@ impl> From<(CloseCode, T)> for CloseReason { } } -static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +static WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // TODO: hash is always same size, we dont need String pub fn hash_key(key: &[u8]) -> String { diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 0ae58fe8..7216104e 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -283,7 +283,7 @@ where res.request().extensions_mut().remove::(); if let Some(id) = id { - return Either::A( + Either::A( backend .to_response(id.id, id.changed, &mut res) .into_future() @@ -291,7 +291,7 @@ where Ok(_) => Ok(res), Err(e) => Ok(res.error_response(e)), }), - ); + ) } else { Either::B(ok(res)) } diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 27ad2b87..2e9e5171 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -282,7 +282,7 @@ mod tests { #[test] fn purge_session() { - let mut req = test::TestRequest::default().to_srv_request(); + let req = test::TestRequest::default().to_srv_request(); let session = Session::get_session(&mut *req.extensions_mut()); assert_eq!(session.0.borrow().status, SessionStatus::Unchanged); session.purge(); @@ -291,7 +291,7 @@ mod tests { #[test] fn renew_session() { - let mut req = test::TestRequest::default().to_srv_request(); + let req = test::TestRequest::default().to_srv_request(); let session = Session::get_session(&mut *req.extensions_mut()); assert_eq!(session.0.borrow().status, SessionStatus::Unchanged); session.renew(); diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 8344abbd..04f08ecd 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -28,7 +28,7 @@ pub(crate) trait Connect { head: RequestHead, addr: Option, ) -> Box< - Future< + dyn Future< Item = (ResponseHead, Framed), Error = SendRequestError, >, @@ -69,7 +69,7 @@ where head: RequestHead, addr: Option, ) -> Box< - Future< + dyn Future< Item = (ResponseHead, Framed), Error = SendRequestError, >, @@ -93,21 +93,21 @@ where } trait AsyncSocket { - fn as_read(&self) -> &AsyncRead; - fn as_read_mut(&mut self) -> &mut AsyncRead; - fn as_write(&mut self) -> &mut AsyncWrite; + fn as_read(&self) -> &dyn AsyncRead; + fn as_read_mut(&mut self) -> &mut dyn AsyncRead; + fn as_write(&mut self) -> &mut dyn AsyncWrite; } struct Socket(T); impl AsyncSocket for Socket { - fn as_read(&self) -> &AsyncRead { + fn as_read(&self) -> &dyn AsyncRead { &self.0 } - fn as_read_mut(&mut self) -> &mut AsyncRead { + fn as_read_mut(&mut self) -> &mut dyn AsyncRead { &mut self.0 } - fn as_write(&mut self) -> &mut AsyncWrite { + fn as_write(&mut self) -> &mut dyn AsyncWrite { &mut self.0 } } diff --git a/src/app.rs b/src/app.rs index 3b063a84..f93859c7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -23,16 +23,17 @@ use crate::service::{ }; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; -type FnDataFactory = Box Box, Error = ()>>>; +type FnDataFactory = + Box Box, Error = ()>>>; /// Application builder - structure that follows the builder pattern /// for building application instances. pub struct App { endpoint: T, - services: Vec>, + services: Vec>, default: Option>, factory_ref: Rc>>, - data: Vec>, + data: Vec>, data_factories: Vec, config: AppConfigInner, external: Vec, diff --git a/src/app_service.rs b/src/app_service.rs index 6012dcda..736c3501 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -18,14 +18,15 @@ use crate::request::{HttpRequest, HttpRequestPool}; use crate::rmap::ResourceMap; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; -type Guards = Vec>; +type Guards = Vec>; type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = Either< FutureResult, Box>, >; -type FnDataFactory = Box Box, Error = ()>>>; +type FnDataFactory = + Box Box, Error = ()>>>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. @@ -40,10 +41,10 @@ where >, { pub(crate) endpoint: T, - pub(crate) data: Rc>>, + pub(crate) data: Rc>>, pub(crate) data_factories: Rc>, pub(crate) config: RefCell, - pub(crate) services: Rc>>>, + pub(crate) services: Rc>>>, pub(crate) default: Option>, pub(crate) factory_ref: Rc>>, pub(crate) external: RefCell>, @@ -142,9 +143,9 @@ where endpoint_fut: T::Future, rmap: Rc, config: AppConfig, - data: Rc>>, - data_factories: Vec>, - data_factories_fut: Vec, Error = ()>>>, + data: Rc>>, + data_factories: Vec>, + data_factories_fut: Vec, Error = ()>>>, _t: PhantomData, } diff --git a/src/config.rs b/src/config.rs index 8de43f36..bbbb3bb0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,7 +16,7 @@ use crate::service::{ ServiceResponse, }; -type Guards = Vec>; +type Guards = Vec>; type HttpNewService = boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; @@ -31,7 +31,7 @@ pub struct AppService { Option, Option>, )>, - service_data: Rc>>, + service_data: Rc>>, } impl AppService { @@ -39,7 +39,7 @@ impl AppService { pub(crate) fn new( config: AppConfig, default: Rc, - service_data: Rc>>, + service_data: Rc>>, ) -> Self { AppService { config, @@ -101,7 +101,7 @@ impl AppService { pub fn register_service( &mut self, rdef: ResourceDef, - guards: Option>>, + guards: Option>>, service: F, nested: Option>, ) where @@ -174,8 +174,8 @@ impl Default for AppConfigInner { /// to set of external methods. This could help with /// modularization of big application configuration. pub struct ServiceConfig { - pub(crate) services: Vec>, - pub(crate) data: Vec>, + pub(crate) services: Vec>, + pub(crate) data: Vec>, pub(crate) external: Vec, } diff --git a/src/guard.rs b/src/guard.rs index 0990e876..6522a984 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -100,7 +100,7 @@ pub fn Any(guard: F) -> AnyGuard { } /// Matches if any of supplied guards matche. -pub struct AnyGuard(Vec>); +pub struct AnyGuard(Vec>); impl AnyGuard { /// Add guard to a list of guards to check @@ -140,7 +140,7 @@ pub fn All(guard: F) -> AllGuard { } /// Matches if all of supplied guards. -pub struct AllGuard(Vec>); +pub struct AllGuard(Vec>); impl AllGuard { /// Add new guard to the list of guards to check @@ -167,7 +167,7 @@ pub fn Not(guard: F) -> NotGuard { } #[doc(hidden)] -pub struct NotGuard(Box); +pub struct NotGuard(Box); impl Guard for NotGuard { fn check(&self, request: &RequestHead) -> bool { diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index afe7c72f..5f73d4d7 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -18,7 +18,7 @@ pub enum ErrorHandlerResponse { Future(Box, Error = Error>>), } -type ErrorHandler = Fn(ServiceResponse) -> Result>; +type ErrorHandler = dyn Fn(ServiceResponse) -> Result>; /// `Middleware` for allowing custom handlers for responses. /// diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 24aabb95..9e332fb8 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -444,7 +444,9 @@ impl FormatText { } } -pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>); +pub(crate) struct FormatDisplay<'a>( + &'a dyn Fn(&mut Formatter) -> Result<(), fmt::Error>, +); impl<'a> fmt::Display for FormatDisplay<'a> { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { diff --git a/src/resource.rs b/src/resource.rs index 0d66aa84..0af43a42 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -50,7 +50,7 @@ pub struct Resource { name: Option, routes: Vec, data: Option, - guards: Vec>, + guards: Vec>, default: Rc>>>, factory_ref: Rc>>, } @@ -118,7 +118,7 @@ where self } - pub(crate) fn add_guards(mut self, guards: Vec>) -> Self { + pub(crate) fn add_guards(mut self, guards: Vec>) -> Self { self.guards.extend(guards); self } diff --git a/src/route.rs b/src/route.rs index 591175d1..f4d30363 100644 --- a/src/route.rs +++ b/src/route.rs @@ -13,7 +13,7 @@ use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; type BoxedRouteService = Box< - Service< + dyn Service< Request = Req, Response = Res, Error = Error, @@ -25,7 +25,7 @@ type BoxedRouteService = Box< >; type BoxedRouteNewService = Box< - NewService< + dyn NewService< Config = (), Request = Req, Response = Res, @@ -42,7 +42,7 @@ type BoxedRouteNewService = Box< /// If handler is not explicitly set, default *404 Not Found* handler is used. pub struct Route { service: BoxedRouteNewService, - guards: Rc>>, + guards: Rc>>, } impl Route { @@ -56,7 +56,7 @@ impl Route { } } - pub(crate) fn take_guards(&mut self) -> Vec> { + pub(crate) fn take_guards(&mut self) -> Vec> { std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new()) } } @@ -84,7 +84,7 @@ type RouteFuture = Box< pub struct CreateRouteService { fut: RouteFuture, - guards: Rc>>, + guards: Rc>>, } impl Future for CreateRouteService { @@ -104,7 +104,7 @@ impl Future for CreateRouteService { pub struct RouteService { service: BoxedRouteService, - guards: Rc>>, + guards: Rc>>, } impl RouteService { diff --git a/src/scope.rs b/src/scope.rs index 714f0354..760fee47 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -23,7 +23,7 @@ use crate::service::{ ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; -type Guards = Vec>; +type Guards = Vec>; type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = Either< @@ -64,8 +64,8 @@ pub struct Scope { endpoint: T, rdef: String, data: Option, - services: Vec>, - guards: Vec>, + services: Vec>, + guards: Vec>, default: Rc>>>, external: Vec, factory_ref: Rc>>, @@ -578,7 +578,7 @@ impl Future for ScopeFactoryResponse { pub struct ScopeService { data: Option>, - router: Router>>, + router: Router>>, default: Option, _ready: Option<(ServiceRequest, ResourceInfo)>, } diff --git a/src/service.rs b/src/service.rs index 5863a100..1d475cf1 100644 --- a/src/service.rs +++ b/src/service.rs @@ -407,7 +407,7 @@ impl fmt::Debug for ServiceResponse { pub struct WebService { rdef: String, name: Option, - guards: Vec>, + guards: Vec>, } impl WebService { @@ -476,7 +476,7 @@ struct WebServiceImpl { srv: T, rdef: String, name: Option, - guards: Vec>, + guards: Vec>, } impl HttpServiceFactory for WebServiceImpl diff --git a/src/types/form.rs b/src/types/form.rs index e61145b0..42e6363f 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -141,7 +141,7 @@ impl fmt::Display for Form { #[derive(Clone)] pub struct FormConfig { limit: usize, - ehandler: Option Error>>, + ehandler: Option Error>>, } impl FormConfig { diff --git a/src/types/path.rs b/src/types/path.rs index 2a6ce287..a4657576 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -224,7 +224,7 @@ where /// ``` #[derive(Clone)] pub struct PathConfig { - ehandler: Option Error + Send + Sync>>, + ehandler: Option Error + Send + Sync>>, } impl PathConfig { diff --git a/src/types/payload.rs b/src/types/payload.rs index 8a634b4c..f33e2e5f 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -128,7 +128,7 @@ impl FromRequest for Bytes { #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - let mut tmp; + let tmp; let cfg = if let Some(cfg) = req.app_data::() { cfg } else { @@ -184,7 +184,7 @@ impl FromRequest for String { #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - let mut tmp; + let tmp; let cfg = if let Some(cfg) = req.app_data::() { cfg } else { diff --git a/src/types/query.rs b/src/types/query.rs index 17240c0b..2ad7106d 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -192,7 +192,8 @@ where /// ``` #[derive(Clone)] pub struct QueryConfig { - ehandler: Option Error + Send + Sync>>, + ehandler: + Option Error + Send + Sync>>, } impl QueryConfig { From b36fdc46db7b606615c417d538059d30427b82fd Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 17 Jul 2019 18:45:17 -0400 Subject: [PATCH 1505/1635] Remove several usages of 'unsafe' (#968) * Replace UnsafeCell in DateServiceInner with Cell The previous API was extremely dangerous - calling `get_ref()` followed by `reset()` would trigger instant UB, without requiring any `unsafe` blocks in the caller. By making DateInner `Copy`, we can use a normal `Cell` instead of an `UnsafeCell`. This makes it impossible to cause UB (or even panic) with the API. * Split unsafe block HttpServiceHandlerResponse Also add explanation of the safety of the usage of `unsafe` * Replace UnsafeCell with RefCell in PayloadRef This ensures that a mistake in the usage of 'get_mut' will cause a panic, not undefined behavior. --- actix-http/src/config.rs | 24 ++++++++++++------------ actix-http/src/service.rs | 22 ++++++++++++---------- actix-multipart/src/server.rs | 29 +++++++++++++---------------- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index aba50a81..7d0e2763 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -1,4 +1,4 @@ -use std::cell::UnsafeCell; +use std::cell::Cell; use std::fmt; use std::fmt::Write; use std::rc::Rc; @@ -172,6 +172,7 @@ impl ServiceConfig { } } +#[derive(Copy, Clone)] struct Date { bytes: [u8; DATE_VALUE_LENGTH], pos: usize, @@ -205,28 +206,28 @@ impl fmt::Write for Date { struct DateService(Rc); struct DateServiceInner { - current: UnsafeCell>, + current: Cell>, } impl DateServiceInner { fn new() -> Self { DateServiceInner { - current: UnsafeCell::new(None), + current: Cell::new(None), } } - fn get_ref(&self) -> &Option<(Date, Instant)> { - unsafe { &*self.current.get() } + fn get(&self) -> Option<(Date, Instant)> { + self.current.get() } fn reset(&self) { - unsafe { (&mut *self.current.get()).take() }; + self.current.set(None); } fn update(&self) { let now = Instant::now(); let date = Date::new(); - *(unsafe { &mut *self.current.get() }) = Some((date, now)); + self.current.set(Some((date, now))); } } @@ -236,7 +237,7 @@ impl DateService { } fn check_date(&self) { - if self.0.get_ref().is_none() { + if self.0.get().is_none() { self.0.update(); // periodic date update @@ -252,14 +253,13 @@ impl DateService { fn now(&self) -> Instant { self.check_date(); - self.0.get_ref().as_ref().unwrap().1 + self.0.get().unwrap().1 } - fn date(&self) -> &Date { + fn date(&self) -> Date { self.check_date(); - let item = self.0.get_ref().as_ref().unwrap(); - &item.0 + self.0.get().unwrap().0 } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index be021b25..09b8077b 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -466,16 +466,18 @@ where State::Unknown(ref mut data) => { if let Some(ref mut item) = data { loop { - unsafe { - let b = item.1.bytes_mut(); - let n = try_ready!(item.0.poll_read(b)); - if n == 0 { - return Ok(Async::Ready(())); - } - item.1.advance_mut(n); - if item.1.len() >= HTTP2_PREFACE.len() { - break; - } + // Safety - we only write to the returned slice. + let b = unsafe { item.1.bytes_mut() }; + let n = try_ready!(item.0.poll_read(b)); + if n == 0 { + return Ok(Async::Ready(())); + } + // Safety - we know that 'n' bytes have + // been initialized via the contract of + // 'poll_read' + unsafe { item.1.advance_mut(n) }; + if item.1.len() >= HTTP2_PREFACE.len() { + break; } } } else { diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index c28a8d6a..70eb61cb 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1,5 +1,5 @@ //! Multipart payload support -use std::cell::{Cell, RefCell, UnsafeCell}; +use std::cell::{Cell, RefCell, RefMut}; use std::marker::PhantomData; use std::rc::Rc; use std::{cmp, fmt}; @@ -112,7 +112,7 @@ impl Stream for Multipart { Err(err) } else if self.safety.current() { let mut inner = self.inner.as_mut().unwrap().borrow_mut(); - if let Some(payload) = inner.payload.get_mut(&self.safety) { + if let Some(mut payload) = inner.payload.get_mut(&self.safety) { payload.poll_stream()?; } inner.poll(&self.safety) @@ -265,12 +265,12 @@ impl InnerMultipart { } } - let headers = if let Some(payload) = self.payload.get_mut(safety) { + let headers = if let Some(mut payload) = self.payload.get_mut(safety) { match self.state { // read until first boundary InnerState::FirstBoundary => { match InnerMultipart::skip_until_boundary( - payload, + &mut *payload, &self.boundary, )? { Some(eof) => { @@ -286,7 +286,7 @@ impl InnerMultipart { } // read boundary InnerState::Boundary => { - match InnerMultipart::read_boundary(payload, &self.boundary)? { + match InnerMultipart::read_boundary(&mut *payload, &self.boundary)? { None => return Ok(Async::NotReady), Some(eof) => { if eof { @@ -303,7 +303,7 @@ impl InnerMultipart { // read field headers for next field if self.state == InnerState::Headers { - if let Some(headers) = InnerMultipart::read_headers(payload)? { + if let Some(headers) = InnerMultipart::read_headers(&mut *payload)? { self.state = InnerState::Boundary; headers } else { @@ -411,7 +411,7 @@ impl Stream for Field { fn poll(&mut self) -> Poll, Self::Error> { if self.safety.current() { let mut inner = self.inner.borrow_mut(); - if let Some(payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) + if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) { payload.poll_stream()?; } @@ -582,12 +582,12 @@ impl InnerField { return Ok(Async::Ready(None)); } - let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { + let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) { if !self.eof { let res = if let Some(ref mut len) = self.length { - InnerField::read_len(payload, len)? + InnerField::read_len(&mut *payload, len)? } else { - InnerField::read_stream(payload, &self.boundary)? + InnerField::read_stream(&mut *payload, &self.boundary)? }; match res { @@ -618,7 +618,7 @@ impl InnerField { } struct PayloadRef { - payload: Rc>, + payload: Rc>, } impl PayloadRef { @@ -628,15 +628,12 @@ impl PayloadRef { } } - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer> + fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option> where 'a: 'b, { - // Unsafe: Invariant is inforced by Safety Safety is used as ref counter, - // only top most ref can have mutable access to payload. if s.current() { - let payload: &mut PayloadBuffer = unsafe { &mut *self.payload.get() }; - Some(payload) + Some(self.payload.borrow_mut()) } else { None } From d03296237eb6cbfa019355eca5747032b0836539 Mon Sep 17 00:00:00 2001 From: Rotem Yaari Date: Thu, 18 Jul 2019 11:31:18 +0300 Subject: [PATCH 1506/1635] Log error results in Logger middleware (closes #938) (#984) * Log error results in Logger middleware (closes #938) * Log internal server errors with an ERROR log level * Logger middleware: don't log 500 internal server errors, as Actix now logs them always * Changelog --- CHANGES.md | 11 +++++++++++ actix-http/src/response.rs | 3 +++ src/middleware/logger.rs | 9 ++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cb5f5112..d64a2239 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # Changes +## [1.0.5] - ? + +### Added + +* Actix now logs errors resulting in "internal server error" responses always, with the `error` + logging level + +### Fixed + +* Restored logging of errors through the `Logger` middleware + ## [1.0.4] - 2019-07-17 ### Added diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index ce986a47..124bf5f9 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -52,6 +52,9 @@ impl Response { #[inline] pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().render_response(); + if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { + error!("Internal Server Error: {:?}", error); + } resp.error = Some(error); resp } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 9e332fb8..f450f048 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -9,12 +9,13 @@ use actix_service::{Service, Transform}; use bytes::Bytes; use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; +use log::debug; use regex::Regex; use time; use crate::dev::{BodySize, MessageBody, ResponseBody}; use crate::error::{Error, Result}; -use crate::http::{HeaderName, HttpTryFrom}; +use crate::http::{HeaderName, HttpTryFrom, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -202,6 +203,12 @@ where fn poll(&mut self) -> Poll { let res = futures::try_ready!(self.fut.poll()); + if let Some(error) = res.response().error() { + if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR { + debug!("Error in response: {:?}", error); + } + } + if let Some(ref mut format) = self.format { for unit in &mut format.0 { unit.render_response(res.response()); From fbdda8acb191dcaaa02f582ed8fce0b9bb96a51e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Jul 2019 17:24:12 +0600 Subject: [PATCH 1507/1635] Unix domain sockets (HttpServer::bind_uds) #92 --- CHANGES.md | 5 +++- Cargo.toml | 9 ++++--- actix-multipart/src/server.rs | 11 +++++--- examples/uds.rs | 49 +++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/server.rs | 32 +++++++++++++++++++++++ test-server/CHANGES.md | 4 +++ test-server/Cargo.toml | 4 +-- test-server/src/lib.rs | 3 ++- 9 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 examples/uds.rs diff --git a/CHANGES.md b/CHANGES.md index d64a2239..80abfc59 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,11 @@ # Changes -## [1.0.5] - ? +## [1.0.5] - 2019-07-xx ### Added +* Unix domain sockets (HttpServer::bind_uds) #92 + * Actix now logs errors resulting in "internal server error" responses always, with the `error` logging level @@ -11,6 +13,7 @@ * Restored logging of errors through the `Logger` middleware + ## [1.0.4] - 2019-07-17 ### Added diff --git a/Cargo.toml b/Cargo.toml index 866a7628..40817a1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" [package.metadata.docs.rs] -features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] +features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls", "uds"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } @@ -68,6 +68,9 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] # rustls rust-tls = ["rustls", "actix-server/rust-tls"] +# unix domain sockets support +uds = ["actix-server/uds"] + [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" @@ -76,8 +79,8 @@ actix-router = "0.1.5" actix-rt = "0.2.4" actix-web-codegen = "0.1.2" actix-http = "0.2.6" -actix-server = "0.5.1" -actix-server-config = "0.1.1" +actix-server = "0.6.0" +actix-server-config = "0.1.2" actix-threadpool = "0.1.1" awc = { version = "0.2.1", optional = true } diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 70eb61cb..e2111bb7 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -286,7 +286,10 @@ impl InnerMultipart { } // read boundary InnerState::Boundary => { - match InnerMultipart::read_boundary(&mut *payload, &self.boundary)? { + match InnerMultipart::read_boundary( + &mut *payload, + &self.boundary, + )? { None => return Ok(Async::NotReady), Some(eof) => { if eof { @@ -411,7 +414,8 @@ impl Stream for Field { fn poll(&mut self) -> Poll, Self::Error> { if self.safety.current() { let mut inner = self.inner.borrow_mut(); - if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) + if let Some(mut payload) = + inner.payload.as_ref().unwrap().get_mut(&self.safety) { payload.poll_stream()?; } @@ -582,7 +586,8 @@ impl InnerField { return Ok(Async::Ready(None)); } - let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) { + let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) + { if !self.eof { let res = if let Some(ref mut len) = self.length { InnerField::read_len(&mut *payload, len)? diff --git a/examples/uds.rs b/examples/uds.rs new file mode 100644 index 00000000..4d6eca40 --- /dev/null +++ b/examples/uds.rs @@ -0,0 +1,49 @@ +use futures::IntoFuture; + +use actix_web::{ + get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, +}; + +#[get("/resource1/{name}/index.html")] +fn index(req: HttpRequest, name: web::Path) -> String { + println!("REQ: {:?}", req); + format!("Hello: {}!\r\n", name) +} + +fn index_async(req: HttpRequest) -> impl IntoFuture { + println!("REQ: {:?}", req); + Ok("Hello world!\r\n") +} + +#[get("/")] +fn no_params() -> &'static str { + "Hello world!\r\n" +} + +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); + env_logger::init(); + + HttpServer::new(|| { + App::new() + .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) + .wrap(middleware::Compress::default()) + .wrap(middleware::Logger::default()) + .service(index) + .service(no_params) + .service( + web::resource("/resource2/index.html") + .wrap( + middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), + ) + .default_service( + web::route().to(|| HttpResponse::MethodNotAllowed()), + ) + .route(web::get().to_async(index_async)), + ) + .service(web::resource("/test1.html").to(|| "Test\r\n")) + }) + .bind_uds("/Users/fafhrd91/uds-test")? + .workers(1) + .run() +} diff --git a/src/lib.rs b/src/lib.rs index 857bc294..c7cdf147 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ //! `c` compiler (default enabled) //! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. +//! * `uds` - Unix domain support, enables `HttpServer::bind_uds()` method. //! #![allow(clippy::type_complexity, clippy::new_without_default)] diff --git a/src/server.rs b/src/server.rs index 43236fa3..aa654e57 100644 --- a/src/server.rs +++ b/src/server.rs @@ -434,6 +434,38 @@ where } Ok(self) } + + #[cfg(feature = "uds")] + /// Start listening for incoming unix domain connections. + /// + /// This method is available with `uds` feature. + pub fn bind_uds(mut self, addr: A) -> io::Result + where + A: AsRef, + { + let cfg = self.config.clone(); + let factory = self.factory.clone(); + self.sockets.push(Socket { + scheme: "http", + addr: net::SocketAddr::new( + net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), + 8080, + ), + }); + + self.builder = self.builder.bind_uds( + format!("actix-web-service-{:?}", addr.as_ref()), + addr, + move || { + let c = cfg.lock(); + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .finish(factory()) + }, + )?; + Ok(self) + } } impl HttpServer diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index f3e98010..33314421 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.2.4] - 2019-07-18 + +* Update actix-server to 0.6 + ## [0.2.3] - 2019-07-16 * Add `delete`, `options`, `patch` methods to `TestServerRunner` diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 445ed33e..512f65e1 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.2.3" +version = "0.2.4" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -33,7 +33,7 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] actix-codec = "0.1.2" actix-rt = "0.2.2" actix-service = "0.4.1" -actix-server = "0.5.1" +actix-server = "0.6.0" actix-utils = "0.4.1" awc = "0.2.2" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 38405cd5..ce73e181 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -12,6 +12,7 @@ use futures::future::lazy; use futures::{Future, IntoFuture, Stream}; use http::Method; use net2::TcpBuilder; +use tokio_tcp::TcpStream; thread_local! { static RT: RefCell = { @@ -109,7 +110,7 @@ pub struct TestServerRuntime { impl TestServer { #[allow(clippy::new_ret_no_self)] /// Start new test server with application factory - pub fn new(factory: F) -> TestServerRuntime { + pub fn new>(factory: F) -> TestServerRuntime { let (tx, rx) = mpsc::channel(); // run server in separate thread From 29098f8397ff8ec8b8d615d72c5a08bec08fec4d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 18 Jul 2019 13:25:50 +0200 Subject: [PATCH 1508/1635] Add support for downcasting response errors (#986) * Add support for downcasting response errors * Added test for error casting --- actix-http/src/error.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 368ee6dd..c4d663ad 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -4,6 +4,7 @@ use std::io::Write; use std::str::Utf8Error; use std::string::FromUtf8Error; use std::{fmt, io, result}; +use std::any::TypeId; pub use actix_threadpool::BlockingError; use actix_utils::timeout::TimeoutError; @@ -51,6 +52,11 @@ impl Error { pub fn as_response_error(&self) -> &dyn ResponseError { self.cause.as_ref() } + + /// Similar to `as_response_error` but downcasts. + pub fn as_error(&self) -> Option<&T> { + ResponseError::downcast_ref(self.cause.as_ref()) + } } /// Error that can be converted to `Response` @@ -73,6 +79,22 @@ pub trait ResponseError: fmt::Debug + fmt::Display { ); resp.set_body(Body::from(buf)) } + + #[doc(hidden)] + fn __private_get_type_id__(&self) -> TypeId where Self: 'static { + TypeId::of::() + } +} + +impl ResponseError + 'static { + /// Downcasts a response error to a specific type. + pub fn downcast_ref(&self) -> Option<&T> { + if self.__private_get_type_id__() == TypeId::of::() { + unsafe { Some(&*(self as *const ResponseError as *const T)) } + } else { + None + } + } } impl fmt::Display for Error { @@ -1044,6 +1066,16 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } + #[test] + fn test_error_casting() { + let err = PayloadError::Overflow; + let resp_err: &ResponseError = &err; + let err = resp_err.downcast_ref::().unwrap(); + assert_eq!(err.to_string(), "A payload reached size limit."); + let not_err = resp_err.downcast_ref::(); + assert!(not_err.is_none()); + } + #[test] fn test_error_helpers() { let r: Response = ErrorBadRequest("err").into(); From 9c3789cbd0c607d58f024afd2e51dc2f6a1198d5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Jul 2019 17:37:41 +0600 Subject: [PATCH 1509/1635] revert DateServiceInner changes --- actix-http/CHANGES.md | 7 +++++++ actix-http/src/config.rs | 31 +++++++++++++++---------------- actix-http/src/error.rs | 7 +++++-- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 84033531..cc695fa6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.2.7] - 2019-07-18 + +### Added + +* Add support for downcasting response errors #986 + + ## [0.2.6] - 2019-07-17 ### Changed diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 7d0e2763..bdfecef3 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -1,4 +1,4 @@ -use std::cell::Cell; +use std::cell::UnsafeCell; use std::fmt; use std::fmt::Write; use std::rc::Rc; @@ -162,13 +162,17 @@ impl ServiceConfig { pub fn set_date(&self, dst: &mut BytesMut) { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(&self.0.timer.date().bytes); + self.0 + .timer + .set_date(|date| buf[6..35].copy_from_slice(&date.bytes)); buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { - dst.extend_from_slice(&self.0.timer.date().bytes); + self.0 + .timer + .set_date(|date| dst.extend_from_slice(&date.bytes)); } } @@ -206,28 +210,24 @@ impl fmt::Write for Date { struct DateService(Rc); struct DateServiceInner { - current: Cell>, + current: UnsafeCell>, } impl DateServiceInner { fn new() -> Self { DateServiceInner { - current: Cell::new(None), + current: UnsafeCell::new(None), } } - fn get(&self) -> Option<(Date, Instant)> { - self.current.get() - } - fn reset(&self) { - self.current.set(None); + unsafe { (&mut *self.current.get()).take() }; } fn update(&self) { let now = Instant::now(); let date = Date::new(); - self.current.set(Some((date, now))); + *(unsafe { &mut *self.current.get() }) = Some((date, now)); } } @@ -237,7 +237,7 @@ impl DateService { } fn check_date(&self) { - if self.0.get().is_none() { + if unsafe { (&*self.0.current.get()).is_none() } { self.0.update(); // periodic date update @@ -253,13 +253,12 @@ impl DateService { fn now(&self) -> Instant { self.check_date(); - self.0.get().unwrap().1 + unsafe { (&*self.0.current.get()).as_ref().unwrap().1 } } - fn date(&self) -> Date { + fn set_date(&self, mut f: F) { self.check_date(); - - self.0.get().unwrap().0 + f(&unsafe { (&*self.0.current.get()).as_ref().unwrap().0 }) } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index c4d663ad..cbb009a7 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,10 +1,10 @@ //! Error and Result module +use std::any::TypeId; use std::cell::RefCell; use std::io::Write; use std::str::Utf8Error; use std::string::FromUtf8Error; use std::{fmt, io, result}; -use std::any::TypeId; pub use actix_threadpool::BlockingError; use actix_utils::timeout::TimeoutError; @@ -81,7 +81,10 @@ pub trait ResponseError: fmt::Debug + fmt::Display { } #[doc(hidden)] - fn __private_get_type_id__(&self) -> TypeId where Self: 'static { + fn __private_get_type_id__(&self) -> TypeId + where + Self: 'static, + { TypeId::of::() } } From b6ff786ed361cb2d1c4111629dd9c96e697a0cfe Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Jul 2019 17:50:10 +0600 Subject: [PATCH 1510/1635] update dependencies --- Cargo.toml | 6 +++--- actix-framed/Cargo.toml | 6 +++--- actix-http/Cargo.toml | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 40817a1f..f2ed9160 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.4" +version = "1.0.5" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -82,7 +82,7 @@ actix-http = "0.2.6" actix-server = "0.6.0" actix-server-config = "0.1.2" actix-threadpool = "0.1.1" -awc = { version = "0.2.1", optional = true } +awc = { version = "0.2.2", optional = true } bytes = "0.4" derive_more = "0.15.0" @@ -107,7 +107,7 @@ rustls = { version = "0.15", optional = true } [dev-dependencies] actix = { version = "0.8.3" } actix-http = { version = "0.2.5", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.2.2", features=["ssl"] } +actix-http-test = { version = "0.2.4", features=["ssl"] } rand = "0.7" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index c2ab26fa..5fbd262d 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -26,13 +26,13 @@ actix-utils = "0.4.0" actix-router = "0.1.2" actix-rt = "0.2.2" actix-http = "0.2.0" -actix-server-config = "0.1.1" +actix-server-config = "0.1.2" bytes = "0.4" futures = "0.1.25" log = "0.4" [dev-dependencies] -actix-server = { version = "0.5.0", features=["ssl"] } +actix-server = { version = "0.6.0", features=["ssl"] } actix-connect = { version = "0.2.0", features=["ssl"] } -actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.4", features=["ssl"] } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ae12cbc4..0cbf5867 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.6" +version = "0.2.7" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -48,7 +48,7 @@ actix-service = "0.4.1" actix-codec = "0.1.2" actix-connect = "0.2.1" actix-utils = "0.4.4" -actix-server-config = "0.1.1" +actix-server-config = "0.1.2" actix-threadpool = "0.1.1" base64 = "0.10" @@ -97,9 +97,9 @@ chrono = "0.4.6" [dev-dependencies] actix-rt = "0.2.2" -actix-server = { version = "0.5.0", features=["ssl"] } +actix-server = { version = "0.6.0", features=["ssl"] } actix-connect = { version = "0.2.0", features=["ssl"] } -actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.4", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } From 6b7df6b242c1cf844ff95b26d963eb275b3d0ada Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 18 Jul 2019 17:51:51 +0600 Subject: [PATCH 1511/1635] prep actix-web release --- CHANGES.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 80abfc59..754c67f2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.5] - 2019-07-xx +## [1.0.5] - 2019-07-18 ### Added diff --git a/Cargo.toml b/Cargo.toml index f2ed9160..538ea0d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ actix-utils = "0.4.4" actix-router = "0.1.5" actix-rt = "0.2.4" actix-web-codegen = "0.1.2" -actix-http = "0.2.6" +actix-http = "0.2.7" actix-server = "0.6.0" actix-server-config = "0.1.2" actix-threadpool = "0.1.1" From 3650f6d7b888c823c2af60c507904e4700c77b3a Mon Sep 17 00:00:00 2001 From: Anton Lazarev Date: Thu, 18 Jul 2019 21:28:43 -0700 Subject: [PATCH 1512/1635] Re-implement Host predicate (#989) * update HostGuard implementation * update/add tests for new HostGuard implementation --- src/guard.rs | 166 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 116 insertions(+), 50 deletions(-) diff --git a/src/guard.rs b/src/guard.rs index 6522a984..6fd6d1d2 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -26,7 +26,7 @@ //! ``` #![allow(non_snake_case)] -use actix_http::http::{self, header, HttpTryFrom}; +use actix_http::http::{self, header, HttpTryFrom, uri::Uri}; use actix_http::RequestHead; /// Trait defines resource guards. Guards are used for routes selection. @@ -256,45 +256,68 @@ impl Guard for HeaderGuard { } } -// /// Return predicate that matches if request contains specified Host name. -// /// -// /// ```rust,ignore -// /// # extern crate actix_web; -// /// use actix_web::{pred, App, HttpResponse}; -// /// -// /// fn main() { -// /// App::new().resource("/index.html", |r| { -// /// r.route() -// /// .guard(pred::Host("www.rust-lang.org")) -// /// .f(|_| HttpResponse::MethodNotAllowed()) -// /// }); -// /// } -// /// ``` -// pub fn Host>(host: H) -> HostGuard { -// HostGuard(host.as_ref().to_string(), None) -// } +/// Return predicate that matches if request contains specified Host name. +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// use actix_web::{guard::Host, App, HttpResponse}; +/// +/// fn main() { +/// App::new().resource("/index.html", |r| { +/// r.route() +/// .guard(Host("www.rust-lang.org")) +/// .f(|_| HttpResponse::MethodNotAllowed()) +/// }); +/// } +/// ``` +pub fn Host>(host: H) -> HostGuard { + HostGuard(host.as_ref().to_string(), None) +} -// #[doc(hidden)] -// pub struct HostGuard(String, Option); +fn get_host_uri(req: &RequestHead) -> Option { + use core::str::FromStr; + let host_value = req.headers.get(header::HOST)?; + let host = host_value.to_str().ok()?; + let uri = Uri::from_str(host).ok()?; + Some(uri) +} -// impl HostGuard { -// /// Set reuest scheme to match -// pub fn scheme>(&mut self, scheme: H) { -// self.1 = Some(scheme.as_ref().to_string()) -// } -// } +#[doc(hidden)] +pub struct HostGuard(String, Option); -// impl Guard for HostGuard { -// fn check(&self, _req: &RequestHead) -> bool { -// // let info = req.connection_info(); -// // if let Some(ref scheme) = self.1 { -// // self.0 == info.host() && scheme == info.scheme() -// // } else { -// // self.0 == info.host() -// // } -// false -// } -// } +impl HostGuard { + /// Set request scheme to match + pub fn scheme>(mut self, scheme: H) -> HostGuard { + self.1 = Some(scheme.as_ref().to_string()); + self + } +} + +impl Guard for HostGuard { + fn check(&self, req: &RequestHead) -> bool { + let req_host_uri = if let Some(uri) = get_host_uri(req) { + uri + } else { + return false; + }; + + if let Some(uri_host) = req_host_uri.host() { + if self.0 != uri_host { + return false; + } + } else { + return false; + } + + if let Some(ref scheme) = self.1 { + if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() { + return scheme == req_host_uri_scheme; + } + } + + true + } +} #[cfg(test)] mod tests { @@ -318,21 +341,64 @@ mod tests { assert!(!pred.check(req.head())); } - // #[test] - // fn test_host() { - // let req = TestServiceRequest::default() - // .header( - // header::HOST, - // header::HeaderValue::from_static("www.rust-lang.org"), - // ) - // .request(); + #[test] + fn test_host() { + let req = TestRequest::default() + .header( + header::HOST, + header::HeaderValue::from_static("www.rust-lang.org"), + ) + .to_http_request(); - // let pred = Host("www.rust-lang.org"); - // assert!(pred.check(&req)); + let pred = Host("www.rust-lang.org"); + assert!(pred.check(req.head())); - // let pred = Host("localhost"); - // assert!(!pred.check(&req)); - // } + let pred = Host("www.rust-lang.org").scheme("https"); + assert!(pred.check(req.head())); + + let pred = Host("blog.rust-lang.org"); + assert!(!pred.check(req.head())); + + let pred = Host("blog.rust-lang.org").scheme("https"); + assert!(!pred.check(req.head())); + + let pred = Host("crates.io"); + assert!(!pred.check(req.head())); + + let pred = Host("localhost"); + assert!(!pred.check(req.head())); + } + + #[test] + fn test_host_scheme() { + let req = TestRequest::default() + .header( + header::HOST, + header::HeaderValue::from_static("https://www.rust-lang.org"), + ) + .to_http_request(); + + let pred = Host("www.rust-lang.org").scheme("https"); + assert!(pred.check(req.head())); + + let pred = Host("www.rust-lang.org"); + assert!(pred.check(req.head())); + + let pred = Host("www.rust-lang.org").scheme("http"); + assert!(!pred.check(req.head())); + + let pred = Host("blog.rust-lang.org"); + assert!(!pred.check(req.head())); + + let pred = Host("blog.rust-lang.org").scheme("https"); + assert!(!pred.check(req.head())); + + let pred = Host("crates.io").scheme("https"); + assert!(!pred.check(req.head())); + + let pred = Host("localhost"); + assert!(!pred.check(req.head())); + } #[test] fn test_methods() { From cccd82965653e951854ee293235ded2fb09a3baa Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Jul 2019 11:07:52 +0600 Subject: [PATCH 1513/1635] update changes --- CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 754c67f2..bc022e38 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [1.0.6] - 2019-xx-xx + +### Added + +* Re-implement Host predicate (#989) + + ## [1.0.5] - 2019-07-18 ### Added From c808364c072f0b1e5fefc59229facce6ef3595c1 Mon Sep 17 00:00:00 2001 From: jesskfullwood <38404589+jesskfullwood@users.noreply.github.com> Date: Fri, 19 Jul 2019 10:47:44 +0100 Subject: [PATCH 1514/1635] make Query payload public (#991) --- CHANGES.md | 4 ++++ src/types/query.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bc022e38..d56e5ce0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ * Re-implement Host predicate (#989) +### Changed + +* `Query` payload made `pub`. Allows user to pattern-match the payload. + ## [1.0.5] - 2019-07-18 diff --git a/src/types/query.rs b/src/types/query.rs index 2ad7106d..d00f4600 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -36,7 +36,7 @@ use crate::request::HttpRequest; /// // Use `Query` extractor for query information. /// // This handler get called only if request's query contains `username` field /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: web::Query) -> String { +/// fn index(web::Query(info): web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } /// @@ -45,7 +45,7 @@ use crate::request::HttpRequest; /// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` -pub struct Query(T); +pub struct Query(pub T); impl Query { /// Deconstruct to a inner value From f8320fedd83e159ed78789815ad0765919d50284 Mon Sep 17 00:00:00 2001 From: jesskfullwood <38404589+jesskfullwood@users.noreply.github.com> Date: Fri, 19 Jul 2019 12:37:49 +0100 Subject: [PATCH 1515/1635] add note about Query decoding (#992) --- src/types/query.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/types/query.rs b/src/types/query.rs index d00f4600..60b07085 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -12,9 +12,12 @@ use crate::error::QueryPayloadError; use crate::extract::FromRequest; use crate::request::HttpRequest; -#[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's query. /// +/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot +/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs. +/// Attempts to do so will *fail at runtime*. +/// /// ## Example /// /// ```rust @@ -33,9 +36,9 @@ use crate::request::HttpRequest; /// response_type: ResponseType, /// } /// -/// // Use `Query` extractor for query information. -/// // This handler get called only if request's query contains `username` field -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` +/// // Use `Query` extractor for query information (and destructure it within the signature). +/// // This handler gets called only if the request's query string contains a `username` field. +/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`. /// fn index(web::Query(info): web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } @@ -45,6 +48,7 @@ use crate::request::HttpRequest; /// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` +#[derive(PartialEq, Eq, PartialOrd, Ord)] pub struct Query(pub T); impl Query { @@ -162,6 +166,8 @@ where /// Query extractor configuration /// +/// ## Example +/// /// ```rust /// #[macro_use] extern crate serde_derive; /// use actix_web::{error, web, App, FromRequest, HttpResponse}; From 941241c5f097260f552dd988fa5292d872c823d9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 20 Jul 2019 10:50:36 +0600 Subject: [PATCH 1516/1635] Remove unneeded actix-utils dependency --- actix-framed/Cargo.toml | 8 ++++---- actix-framed/changes.md | 5 +++++ actix-framed/src/app.rs | 9 ++++----- actix-framed/src/lib.rs | 7 +------ src/guard.rs | 2 +- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index 5fbd262d..321041c7 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-framed" -version = "0.2.0" +version = "0.2.1" authors = ["Nikolay Kim "] description = "Actix framed app server" readme = "README.md" @@ -21,11 +21,10 @@ path = "src/lib.rs" [dependencies] actix-codec = "0.1.2" -actix-service = "0.4.0" -actix-utils = "0.4.0" +actix-service = "0.4.1" actix-router = "0.1.2" actix-rt = "0.2.2" -actix-http = "0.2.0" +actix-http = "0.2.7" actix-server-config = "0.1.2" bytes = "0.4" @@ -36,3 +35,4 @@ log = "0.4" actix-server = { version = "0.6.0", features=["ssl"] } actix-connect = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "0.2.4", features=["ssl"] } +actix-utils = "0.4.4" diff --git a/actix-framed/changes.md b/actix-framed/changes.md index 9f16c790..6e67e00d 100644 --- a/actix-framed/changes.md +++ b/actix-framed/changes.md @@ -1,5 +1,10 @@ # Changes +## [0.2.1] - 2019-07-20 + +* Remove unneeded actix-utils dependency + + ## [0.2.0] - 2019-05-12 * Update dependencies diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs index a9d73a25..ad5b1ec2 100644 --- a/actix-framed/src/app.rs +++ b/actix-framed/src/app.rs @@ -6,7 +6,6 @@ use actix_http::{Error, Request, Response}; use actix_router::{Path, Router, Url}; use actix_server_config::ServerConfig; use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; use futures::{Async, Future, Poll}; use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService}; @@ -100,7 +99,7 @@ where type Response = (); type Error = Error; type InitError = (); - type Service = CloneableService>; + type Service = FramedAppService; type Future = CreateService; fn new_service(&self, _: &ServerConfig) -> Self::Future { @@ -138,7 +137,7 @@ impl Future for CreateService where T: AsyncRead + AsyncWrite, { - type Item = CloneableService>; + type Item = FramedAppService; type Error = (); fn poll(&mut self) -> Poll { @@ -177,10 +176,10 @@ where } router }); - Ok(Async::Ready(CloneableService::new(FramedAppService { + Ok(Async::Ready(FramedAppService { router: router.finish(), state: self.state.clone(), - }))) + })) } else { Ok(Async::NotReady) } diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs index 5e72ba5b..250533f3 100644 --- a/actix-framed/src/lib.rs +++ b/actix-framed/src/lib.rs @@ -1,9 +1,4 @@ -#![allow( - clippy::type_complexity, - clippy::new_without_default, - dead_code, - deprecated -)] +#![allow(clippy::type_complexity, clippy::new_without_default, dead_code)] mod app; mod helpers; mod request; diff --git a/src/guard.rs b/src/guard.rs index 6fd6d1d2..e0b4055b 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -26,7 +26,7 @@ //! ``` #![allow(non_snake_case)] -use actix_http::http::{self, header, HttpTryFrom, uri::Uri}; +use actix_http::http::{self, header, uri::Uri, HttpTryFrom}; use actix_http::RequestHead; /// Trait defines resource guards. Guards are used for routes selection. From e53e9c8ba3b7d0bed00b576344f7be68a27b8580 Mon Sep 17 00:00:00 2001 From: naerbnic Date: Fri, 19 Jul 2019 22:17:58 -0700 Subject: [PATCH 1517/1635] Add the start_with_addr() function, to obtain an addr to the target websocket actor (#988) --- actix-web-actors/CHANGES.md | 3 +++ actix-web-actors/src/ws.rs | 47 ++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 115af87b..7c035fdd 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -4,6 +4,9 @@ * Allow to use custom ws codec with `WebsocketContext` #925 +* Add `ws::start_with_addr()`, returning the address of the created actor, along + with the `HttpResponse`. + ## [1.0.0] - 2019-05-29 * Update actix-http and actix-web diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 08d8b108..ac08e396 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -35,6 +35,31 @@ where Ok(res.streaming(WebsocketContext::create(actor, stream))) } +/// Do websocket handshake and start ws actor. +/// +/// `req` is an HTTP Request that should be requesting a websocket protocol +/// change. `stream` should be a `Bytes` stream (such as +/// `actix_web::web::Payload`) that contains a stream of the body request. +/// +/// If there is a problem with the handshake, an error is returned. +/// +/// If successful, returns a pair where the first item is an address for the +/// created actor and the second item is the response that should be returned +/// from the websocket request. +pub fn start_with_addr( + actor: A, + req: &HttpRequest, + stream: T, +) -> Result<(Addr, HttpResponse), Error> +where + A: Actor> + StreamHandler, + T: Stream + 'static, +{ + let mut res = handshake(req)?; + let (addr, out_stream) = WebsocketContext::create_with_addr(actor, stream); + Ok((addr, res.streaming(out_stream))) +} + /// Prepare `WebSocket` handshake response. /// /// This function returns handshake `HttpResponse`, ready to send to peer. @@ -168,6 +193,24 @@ where #[inline] /// Create a new Websocket context from a request and an actor pub fn create(actor: A, stream: S) -> impl Stream + where + A: StreamHandler, + S: Stream + 'static, + { + let (_, stream) = WebsocketContext::create_with_addr(actor, stream); + stream + } + + #[inline] + /// Create a new Websocket context from a request and an actor. + /// + /// Returns a pair, where the first item is an addr for the created actor, + /// and the second item is a stream intended to be set as part of the + /// response via `HttpResponseBuilder::streaming()`. + pub fn create_with_addr( + actor: A, + stream: S, + ) -> (Addr, impl Stream) where A: StreamHandler, S: Stream + 'static, @@ -179,7 +222,9 @@ where }; ctx.add_stream(WsStream::new(stream, Codec::new())); - WebsocketContextFut::new(ctx, actor, mb, Codec::new()) + let addr = ctx.address(); + + (addr, WebsocketContextFut::new(ctx, actor, mb, Codec::new())) } #[inline] From 03ca408e9422946d4863d9d70b82e698e73d8cda Mon Sep 17 00:00:00 2001 From: jairinhohw Date: Sat, 20 Jul 2019 02:22:06 -0300 Subject: [PATCH 1518/1635] add support for specifying protocols on websocket handshake (#835) * add support for specifying protocols on websocket handshake * separated the handshake function with and without protocols changed protocols type from Vec<&str> to [&str] --- actix-web-actors/src/ws.rs | 139 +++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 6 deletions(-) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index ac08e396..e25a7e6e 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -60,15 +60,43 @@ where Ok((addr, res.streaming(out_stream))) } +/// Do websocket handshake and start ws actor. +/// +/// `protocols` is a sequence of known protocols. +pub fn start_with_protocols( + actor: A, + protocols: &[&str], + req: &HttpRequest, + stream: T, +) -> Result +where + A: Actor> + StreamHandler, + T: Stream + 'static, +{ + let mut res = handshake_with_protocols(req, protocols)?; + Ok(res.streaming(WebsocketContext::create(actor, stream))) +} + +/// Prepare `WebSocket` handshake response. +/// +/// This function returns handshake `HttpResponse`, ready to send to peer. +/// It does not perform any IO. +pub fn handshake(req: &HttpRequest) -> Result { + handshake_with_protocols(req, &[]) +} + /// Prepare `WebSocket` handshake response. /// /// This function returns handshake `HttpResponse`, ready to send to peer. /// It does not perform any IO. /// -// /// `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 { +/// `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_with_protocols( + req: &HttpRequest, + protocols: &[&str], +) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); @@ -117,11 +145,28 @@ pub fn handshake(req: &HttpRequest) -> Result Date: Sat, 20 Jul 2019 11:27:21 +0600 Subject: [PATCH 1519/1635] update changes --- actix-web-actors/CHANGES.md | 10 +++++++--- actix-web-actors/Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 7c035fdd..0d1df7e5 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,12 +1,16 @@ # Changes +## [1.0.2] - 2019-07-20 + +* Add `ws::start_with_addr()`, returning the address of the created actor, along + with the `HttpResponse`. + +* Add support for specifying protocols on websocket handshake #835 + ## [1.0.1] - 2019-06-28 * Allow to use custom ws codec with `WebsocketContext` #925 -* Add `ws::start_with_addr()`, returning the address of the created actor, along - with the `HttpResponse`. - ## [1.0.0] - 2019-05-29 * Update actix-http and actix-web diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index eb5fb111..356109da 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.1" +version = "1.0.2" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -27,4 +27,4 @@ futures = "0.1.25" [dev-dependencies] env_logger = "0.6" -actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.4", features=["ssl"] } From 7bca1f7d8de013212fd35aff9f2f89162a023f40 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 20 Jul 2019 11:43:49 +0600 Subject: [PATCH 1520/1635] Allow to disable Content-Disposition header #686 --- actix-files/CHANGES.md | 5 +++++ actix-files/src/lib.rs | 44 ++++++++++++++++++++++++++++++++++++---- actix-files/src/named.rs | 43 ++++++++++++++++++++++++++------------- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 2f98e15c..916d579f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [0.1.4] - 2019-07-20 + +* Allow to disable `Content-Disposition` header #686 + + ## [0.1.3] - 2019-06-28 * Do not set `Content-Length` header, let actix-http set it #930 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index cf07153f..2eab6405 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -314,23 +314,32 @@ impl Files { } #[inline] - ///Specifies whether to use ETag or not. + /// Specifies whether to use ETag or not. /// - ///Default is true. + /// Default is true. pub fn use_etag(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::ETAG, value); self } #[inline] - ///Specifies whether to use Last-Modified or not. + /// Specifies whether to use Last-Modified or not. /// - ///Default is true. + /// Default is true. pub fn use_last_modified(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::LAST_MD, value); self } + /// Disable `Content-Disposition` header. + /// + /// By default Content-Disposition` header is enabled. + #[inline] + pub fn disable_content_disposition(mut self) -> Self { + self.file_flags.remove(named::Flags::CONTENT_DISPOSITION); + self + } + /// Sets default handler which is used when no matched file could be found. pub fn default_handler(mut self, f: F) -> Self where @@ -638,6 +647,33 @@ mod tests { ); } + #[test] + fn test_named_file_content_disposition() { + 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 req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + + let file = NamedFile::open("Cargo.toml") + .unwrap() + .disable_content_disposition(); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); + } + #[test] fn test_named_file_set_content_type() { let mut file = NamedFile::open("Cargo.toml") diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 3273a4d6..4c80e1d9 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -23,9 +23,10 @@ use crate::range::HttpRange; use crate::ChunkedReadFile; bitflags! { - pub(crate) struct Flags: u32 { + pub(crate) struct Flags: u8 { const ETAG = 0b0000_0001; const LAST_MD = 0b0000_0010; + const CONTENT_DISPOSITION = 0b0000_0100; } } @@ -40,13 +41,13 @@ impl Default for Flags { pub struct NamedFile { path: PathBuf, file: File, + modified: Option, + pub(crate) md: Metadata, + pub(crate) flags: Flags, + pub(crate) status_code: StatusCode, pub(crate) content_type: mime::Mime, pub(crate) content_disposition: header::ContentDisposition, - pub(crate) md: Metadata, - modified: Option, pub(crate) encoding: Option, - pub(crate) status_code: StatusCode, - pub(crate) flags: Flags, } impl NamedFile { @@ -172,11 +173,21 @@ impl NamedFile { /// sent to the peer. By default the disposition is `inline` for text, /// image, and video content types, and `attachment` otherwise, and /// the filename is taken from the path provided in the `open` method - /// after converting it to UTF-8 using + /// after converting it to UTF-8 using. /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). #[inline] pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { self.content_disposition = cd; + self.flags.insert(Flags::CONTENT_DISPOSITION); + self + } + + /// Disable `Content-Disposition` header. + /// + /// By default Content-Disposition` header is enabled. + #[inline] + pub fn disable_content_disposition(mut self) -> Self { + self.flags.remove(Flags::CONTENT_DISPOSITION); self } @@ -294,10 +305,12 @@ impl Responder for NamedFile { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); + .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { + res.header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); + }); if let Some(current_encoding) = self.encoding { resp.encoding(current_encoding); } @@ -368,10 +381,12 @@ impl Responder for NamedFile { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); + .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { + res.header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); + }); // default compressing if let Some(current_encoding) = self.encoding { resp.encoding(current_encoding); From c96068e78df609bc1bc01a41caae1237ed5e0d54 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 20 Jul 2019 11:46:21 +0600 Subject: [PATCH 1521/1635] bump version --- actix-files/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c9d9cfec..307e7906 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.3" +version = "0.1.4" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" From 8f48ed2597ffdf5f372c40d31c905dc3976078dd Mon Sep 17 00:00:00 2001 From: jesskfulwood Date: Sat, 20 Jul 2019 12:55:52 +0100 Subject: [PATCH 1522/1635] impl Responder for Form --- src/types/form.rs | 115 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 16 deletions(-) diff --git a/src/types/form.rs b/src/types/form.rs index 42e6363f..ec6e6cd0 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -3,20 +3,29 @@ use std::rc::Rc; use std::{fmt, ops}; -use actix_http::{Error, HttpMessage, Payload}; +use actix_http::{Error, HttpMessage, Payload, Response}; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; use futures::{Future, Poll, Stream}; use serde::de::DeserializeOwned; +use serde::Serialize; use crate::dev::Decompress; use crate::error::UrlencodedError; use crate::extract::FromRequest; -use crate::http::header::CONTENT_LENGTH; +use crate::http::{ + header::{ContentType, CONTENT_LENGTH}, + StatusCode, +}; use crate::request::HttpRequest; +use crate::responder::Responder; -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's body. +/// Form data helper (`application/x-www-form-urlencoded`) +/// +/// Can be use to extract url-encoded data from the request body, +/// or send url-encoded data as the response. +/// +/// ## Extract /// /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. @@ -24,8 +33,7 @@ use crate::request::HttpRequest; /// [**FormConfig**](struct.FormConfig.html) allows to configure extraction /// process. /// -/// ## Example -/// +/// ### Example /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; @@ -44,6 +52,36 @@ use crate::request::HttpRequest; /// } /// # fn main() {} /// ``` +/// +/// ## Respond +/// +/// The `Form` type also allows you to respond with well-formed url-encoded data: +/// simply return a value of type Form where T is the type to be url-encoded. +/// The type must implement `serde::Serialize`; +/// +/// ### Example +/// ```rust +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// # +/// #[derive(Serialize)] +/// struct SomeForm { +/// name: String, +/// age: u8 +/// } +/// +/// // Will return a 200 response with header +/// // `Content-Type: application/x-www-form-urlencoded` +/// // and body "name=actix&age=123" +/// fn index() -> web::Form { +/// web::Form(SomeForm { +/// name: "actix".into(), +/// age: 123 +/// }) +/// } +/// # fn main() {} +/// ``` +#[derive(PartialEq, Eq, PartialOrd, Ord)] pub struct Form(pub T); impl Form { @@ -110,6 +148,22 @@ impl fmt::Display for Form { } } +impl Responder for Form { + type Error = Error; + type Future = Result; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + let body = match serde_urlencoded::to_string(&self.0) { + Ok(body) => body, + Err(e) => return Err(e.into()), + }; + + Ok(Response::build(StatusCode::OK) + .set(ContentType::form_url_encoded()) + .body(body)) + } +} + /// Form extractor configuration /// /// ```rust @@ -304,15 +358,16 @@ where #[cfg(test)] mod tests { use bytes::Bytes; - use serde::Deserialize; + use serde::{Deserialize, Serialize}; use super::*; - use crate::http::header::CONTENT_TYPE; + use crate::http::header::{HeaderValue, CONTENT_TYPE}; use crate::test::{block_on, TestRequest}; - #[derive(Deserialize, Debug, PartialEq)] + #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Info { hello: String, + counter: i64, } #[test] @@ -320,11 +375,17 @@ mod tests { let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) .to_http_parts(); - let s = block_on(Form::::from_request(&req, &mut pl)).unwrap(); - assert_eq!(s.hello, "world"); + let Form(s) = block_on(Form::::from_request(&req, &mut pl)).unwrap(); + assert_eq!( + s, + Info { + hello: "world".into(), + counter: 123 + } + ); } fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { @@ -373,14 +434,15 @@ mod tests { let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) .to_http_parts(); let info = block_on(UrlEncoded::::new(&req, &mut pl)).unwrap(); assert_eq!( info, Info { - hello: "world".to_owned() + hello: "world".to_owned(), + counter: 123 } ); @@ -389,15 +451,36 @@ mod tests { "application/x-www-form-urlencoded; charset=utf-8", ) .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) .to_http_parts(); let info = block_on(UrlEncoded::::new(&req, &mut pl)).unwrap(); assert_eq!( info, Info { - hello: "world".to_owned() + hello: "world".to_owned(), + counter: 123 } ); } + + #[test] + fn test_responder() { + let req = TestRequest::default().to_http_request(); + + let form = Form(Info { + hello: "world".to_string(), + counter: 123, + }); + let resp = form.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/x-www-form-urlencoded") + ); + + use crate::responder::tests::BodyTest; + assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); + } + } From b0b462581be1dc40c00f7bccf301d17c4433004a Mon Sep 17 00:00:00 2001 From: jesskfulwood Date: Sat, 20 Jul 2019 12:59:10 +0100 Subject: [PATCH 1523/1635] update CHANGES.md for Form impl Responder --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index d56e5ce0..b3a0c86c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Re-implement Host predicate (#989) +* Form immplements Responder, returning a `application/x-www-form-urlencoded` response + ### Changed * `Query` payload made `pub`. Allows user to pattern-match the payload. From f3751d83f87d56d7d8b3e1ebea5f7931da7beec2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jul 2019 11:35:00 +0600 Subject: [PATCH 1524/1635] Modify response body only if encoder is not None #997 --- actix-http/CHANGES.md | 6 ++++++ actix-http/src/encoding/encoder.rs | 32 ++++++++++++++++-------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index cc695fa6..4b53161a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.2.8] - 2019-07-xx + +### Fixed + +* Invalid response with compression middleware enabled, but compression-related features disabled #997 + ## [0.2.7] - 2019-07-18 ### Added diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index fa95d798..58d8a2d9 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -54,22 +54,24 @@ impl Encoder { }; if can_encode { - update_head(encoding, head); - head.no_chunking(false); - ResponseBody::Body(Encoder { - body, - eof: false, - fut: None, - encoder: ContentEncoder::encoder(encoding), - }) - } else { - ResponseBody::Body(Encoder { - body, - eof: false, - fut: None, - encoder: None, - }) + // Modify response body only if encoder is not None + if let Some(enc) = ContentEncoder::encoder(encoding) { + update_head(encoding, head); + head.no_chunking(false); + return ResponseBody::Body(Encoder { + body, + eof: false, + fut: None, + encoder: Some(enc), + }); + } } + ResponseBody::Body(Encoder { + body, + eof: false, + fut: None, + encoder: None, + }) } } From 52372fcbea371c84bf2bf20f17a72577f7841d17 Mon Sep 17 00:00:00 2001 From: erikdesjardins Date: Mon, 22 Jul 2019 20:41:59 -0400 Subject: [PATCH 1525/1635] actix-files: "Specified path is not a directory" error now includes the path (#1004) --- actix-files/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 2eab6405..816fd92a 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -261,7 +261,7 @@ impl Files { pub fn new>(path: &str, dir: T) -> Files { let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new()); if !dir.is_dir() { - log::error!("Specified path is not a directory"); + log::error!("Specified path is not a directory: {:?}", dir); } Files { From 6f2049ba9bc7303d6760366ca24417e1632e655b Mon Sep 17 00:00:00 2001 From: Cyril Plisko Date: Thu, 25 Jul 2019 13:06:23 +0300 Subject: [PATCH 1526/1635] Fix typo --- src/config.rs | 2 +- src/server.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index bbbb3bb0..63fd31d2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -133,7 +133,7 @@ impl AppConfig { /// Set server host name. /// - /// Host name is used by application router aa a hostname for url + /// Host name is used by application router as a hostname for url /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. /// html#method.host) documentation for more information. /// diff --git a/src/server.rs b/src/server.rs index aa654e57..d1a019a1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -180,7 +180,7 @@ where /// Set server host name. /// - /// Host name is used by application router aa a hostname for url + /// Host name is used by application router as a hostname for url /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. /// html#method.host) documentation for more information. pub fn server_hostname>(mut self, val: T) -> Self { From 81ab37f23591e2404443d5cc260dca55abfa7cd5 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Mon, 29 Jul 2019 06:10:33 +0200 Subject: [PATCH 1527/1635] Fix two dyn warnings (#1015) --- actix-http/src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index cbb009a7..dcbc3cc9 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -89,11 +89,11 @@ pub trait ResponseError: fmt::Debug + fmt::Display { } } -impl ResponseError + 'static { +impl dyn ResponseError + 'static { /// Downcasts a response error to a specific type. pub fn downcast_ref(&self) -> Option<&T> { if self.__private_get_type_id__() == TypeId::of::() { - unsafe { Some(&*(self as *const ResponseError as *const T)) } + unsafe { Some(&*(self as *const dyn ResponseError as *const T)) } } else { None } From 511026cab01136d6e94f8fb234b66889b5cf27ef Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Mon, 29 Jul 2019 06:11:23 +0200 Subject: [PATCH 1528/1635] Allow HeaderMap to be cloned (#1014) * Allow HeaderMap to be cloned * Add entry to changelog --- actix-http/CHANGES.md | 4 ++++ actix-http/src/header/map.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 4b53161a..cffdd7af 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,10 @@ ## [0.2.8] - 2019-07-xx +### Changed + +* Add `Clone` impl for `HeaderMap` + ### Fixed * Invalid response with compression middleware enabled, but compression-related features disabled #997 diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 694aed02..f2f1ba51 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -9,12 +9,12 @@ use http::HttpTryFrom; /// `HeaderMap` is an multimap of [`HeaderName`] to values. /// /// [`HeaderName`]: struct.HeaderName.html -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HeaderMap { pub(crate) inner: HashMap, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) enum Value { One(HeaderValue), Multi(Vec), From 7674f1173cd3af8466c62a240619b146382acfd6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 30 Jul 2019 08:00:46 -0700 Subject: [PATCH 1529/1635] fix awc client panic #1016 --- actix-http/CHANGES.md | 3 +++ actix-http/src/client/pool.rs | 21 ++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 4b53161a..3024703d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,8 +4,11 @@ ### Fixed +* awc client panic #1016 + * Invalid response with compression middleware enabled, but compression-related features disabled #997 + ## [0.2.7] - 2019-07-18 ### Added diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 4739141d..dbd8f202 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -305,10 +305,12 @@ pub(crate) struct Inner { limit: usize, acquired: usize, available: HashMap>>, - waiters: Slab<( - Connect, - oneshot::Sender, ConnectError>>, - )>, + waiters: Slab< + Option<( + Connect, + oneshot::Sender, ConnectError>>, + )>, + >, waiters_queue: IndexSet<(Key, usize)>, task: Option, } @@ -346,7 +348,7 @@ where let key: Key = connect.uri.authority_part().unwrap().clone().into(); let entry = self.waiters.vacant_entry(); let token = entry.key(); - entry.insert((connect, tx)); + entry.insert(Some((connect, tx))); assert!(self.waiters_queue.insert((key, token))); (rx, token, self.task.is_some()) @@ -499,10 +501,14 @@ where break; } }; + if inner.waiters.get(token).unwrap().is_none() { + continue; + } + match inner.acquire(&key) { Acquire::NotAvailable => break, Acquire::Acquired(io, created) => { - let (_, tx) = inner.waiters.remove(token); + let tx = inner.waiters.get_mut(token).unwrap().take().unwrap().1; if let Err(conn) = tx.send(Ok(IoConnection::new( io, created, @@ -513,7 +519,8 @@ where } } Acquire::Available => { - let (connect, tx) = inner.waiters.remove(token); + let (connect, tx) = + inner.waiters.get_mut(token).unwrap().take().unwrap(); OpenWaitingConnection::spawn( key.clone(), tx, From 0d9ea41047902dea0d726f588394b48a66e80e0c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 31 Jul 2019 06:49:46 -0700 Subject: [PATCH 1530/1635] update min rust version --- README.md | 2 +- src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e533c848..99b7b176 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.34 or later +* Minimum supported Rust version: 1.36 or later ## Example diff --git a/src/lib.rs b/src/lib.rs index c7cdf147..60c34489 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,9 +61,9 @@ //! * Configurable request routing //! * Multipart streams //! * SSL support with OpenSSL or `native-tls` -//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) +//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`) //! * Supports [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.31 or later +//! * Supported Rust version: 1.36 or later //! //! ## Package feature //! From cb19ebfe0c1bdbe69bdc71fdf77a7ccf26b065d8 Mon Sep 17 00:00:00 2001 From: Marat Safin Date: Wed, 31 Jul 2019 23:02:56 +0300 Subject: [PATCH 1531/1635] add rustls support for actix-http and awc (#998) * add rustls support for actix-http and awc * fix features conflict * remove unnecessary duplication * test server with rust-tls * fix * test rustls * awc rustls test * format * tests * fix dependencies * fixes and add changes * remove test-server and Cargo.toml dev-dependencies changes * cargo fmt --- Cargo.toml | 6 +- actix-http/CHANGES.md | 1 + actix-http/Cargo.toml | 7 +- actix-http/src/client/connector.rs | 117 +++++-- actix-http/tests/cert.pem | 20 -- actix-http/tests/key.pem | 27 -- actix-http/tests/test_rustls_server.rs | 462 +++++++++++++++++++++++++ actix-http/tests/test_server.rs | 451 +----------------------- actix-http/tests/test_ssl_server.rs | 455 ++++++++++++++++++++++++ actix-web-codegen/tests/test_macro.rs | 1 - awc/CHANGES.md | 4 + awc/Cargo.toml | 12 +- awc/tests/test_client.rs | 81 +---- awc/tests/test_rustls_client.rs | 96 +++++ awc/tests/test_ssl_client.rs | 86 +++++ examples/uds.rs | 4 + test-server/Cargo.toml | 1 + test-server/src/lib.rs | 12 +- tests/cert.pem | 48 ++- tests/key.pem | 79 +++-- tests/test_server.rs | 21 +- 21 files changed, 1317 insertions(+), 674 deletions(-) delete mode 100644 actix-http/tests/cert.pem delete mode 100644 actix-http/tests/key.pem create mode 100644 actix-http/tests/test_rustls_server.rs create mode 100644 actix-http/tests/test_ssl_server.rs create mode 100644 awc/tests/test_rustls_client.rs create mode 100644 awc/tests/test_ssl_client.rs diff --git a/Cargo.toml b/Cargo.toml index 538ea0d3..9143f2fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,9 +105,9 @@ openssl = { version="0.10", optional = true } rustls = { version = "0.15", optional = true } [dev-dependencies] -actix = { version = "0.8.3" } -actix-http = { version = "0.2.5", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.2.4", features=["ssl"] } +actix = "0.8.3" +actix-connect = "0.2.2" +actix-http-test = "0.2.4" rand = "0.7" env_logger = "0.6" serde_derive = "1.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 94ea543b..b14c5784 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -5,6 +5,7 @@ ### Changed * Add `Clone` impl for `HeaderMap` +* Add `rustls` support ### Fixed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0cbf5867..ad626eb1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.7" +version = "0.2.8" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -27,6 +27,7 @@ default = [] # openssl ssl = ["openssl", "actix-connect/ssl"] +rust-tls = ["rustls", "webpki-roots", "actix-connect/rust-tls"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -46,7 +47,7 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.4.1" actix-codec = "0.1.2" -actix-connect = "0.2.1" +actix-connect = "0.2.2" actix-utils = "0.4.4" actix-server-config = "0.1.2" actix-threadpool = "0.1.1" @@ -93,6 +94,8 @@ flate2 = { version="1.0.7", optional = true, default-features = false } # optional deps failure = { version = "0.1.5", optional = true } openssl = { version="0.10", optional = true } +rustls = { version = "0.15.2", optional = true } +webpki-roots = { version = "0.16", optional = true } chrono = "0.4.6" [dev-dependencies] diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index cd3ae3d9..7d6fca25 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -17,9 +17,21 @@ use super::pool::{ConnectionPool, Protocol}; use super::Connect; #[cfg(feature = "ssl")] -use openssl::ssl::SslConnector; +use openssl::ssl::SslConnector as OpensslConnector; -#[cfg(not(feature = "ssl"))] +#[cfg(feature = "rust-tls")] +use rustls::ClientConfig; +#[cfg(feature = "rust-tls")] +use std::sync::Arc; + +#[cfg(any(feature = "ssl", feature = "rust-tls"))] +enum SslConnector { + #[cfg(feature = "ssl")] + Openssl(OpensslConnector), + #[cfg(feature = "rust-tls")] + Rustls(Arc), +} +#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] type SslConnector = (); /// Manages http client network connectivity @@ -46,6 +58,9 @@ pub struct Connector { _t: PhantomData, } +trait Io: AsyncRead + AsyncWrite {} +impl Io for T {} + impl Connector<(), ()> { #[allow(clippy::new_ret_no_self)] pub fn new() -> Connector< @@ -61,13 +76,23 @@ impl Connector<(), ()> { { use openssl::ssl::SslMethod; - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); + let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); let _ = ssl .set_alpn_protos(b"\x02h2\x08http/1.1") .map_err(|e| error!("Can not set alpn protocol: {:?}", e)); - ssl.build() + SslConnector::Openssl(ssl.build()) } - #[cfg(not(feature = "ssl"))] + #[cfg(all(not(feature = "ssl"), feature = "rust-tls"))] + { + let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + let mut config = ClientConfig::new(); + config.set_protocols(&protos); + config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + SslConnector::Rustls(Arc::new(config)) + } + #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] {} }; @@ -127,8 +152,14 @@ where #[cfg(feature = "ssl")] /// Use custom `SslConnector` instance. - pub fn ssl(mut self, connector: SslConnector) -> Self { - self.ssl = connector; + pub fn ssl(mut self, connector: OpensslConnector) -> Self { + self.ssl = SslConnector::Openssl(connector); + self + } + + #[cfg(feature = "rust-tls")] + pub fn rustls(mut self, connector: Arc) -> Self { + self.ssl = SslConnector::Rustls(connector); self } @@ -182,7 +213,7 @@ where self, ) -> impl Service + Clone { - #[cfg(not(feature = "ssl"))] + #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] { let connector = TimeoutService::new( self.timeout, @@ -207,10 +238,16 @@ where ), } } - #[cfg(feature = "ssl")] + #[cfg(any(feature = "ssl", feature = "rust-tls"))] { const H2: &[u8] = b"h2"; + #[cfg(feature = "ssl")] use actix_connect::ssl::OpensslConnector; + #[cfg(feature = "rust-tls")] + use actix_connect::ssl::RustlsConnector; + use actix_service::boxed::service; + #[cfg(feature = "rust-tls")] + use rustls::Session; let ssl_service = TimeoutService::new( self.timeout, @@ -218,24 +255,46 @@ where srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) }) .map_err(ConnectError::from) - .and_then( - OpensslConnector::service(self.ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (sock, Protocol::Http2) - } else { - (sock, Protocol::Http1) - } - }), - ), + .and_then(match self.ssl { + #[cfg(feature = "ssl")] + SslConnector::Openssl(ssl) => service( + OpensslConnector::service(ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock) as Box, Protocol::Http2) + } else { + (Box::new(sock) as Box, Protocol::Http1) + } + }), + ), + #[cfg(feature = "rust-tls")] + SslConnector::Rustls(ssl) => service( + RustlsConnector::service(ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .1 + .get_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock) as Box, Protocol::Http2) + } else { + (Box::new(sock) as Box, Protocol::Http1) + } + }), + ), + }), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -275,7 +334,7 @@ where } } -#[cfg(not(feature = "ssl"))] +#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] mod connect_impl { use futures::future::{err, Either, FutureResult}; use futures::Poll; @@ -337,7 +396,7 @@ mod connect_impl { } } -#[cfg(feature = "ssl")] +#[cfg(any(feature = "ssl", feature = "rust-tls"))] mod connect_impl { use std::marker::PhantomData; diff --git a/actix-http/tests/cert.pem b/actix-http/tests/cert.pem deleted file mode 100644 index eafad524..00000000 --- a/actix-http/tests/cert.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDPjCCAiYCCQCmkoCBehOyYTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww -CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xOTAzMjky -MzE5MDlaFw0yMDAzMjgyMzE5MDlaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD -QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY -MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmmbt8r -YNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2ZBro -AuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo4YI4 -xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN124x -giFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ+K/Y -p/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABMA0GCSqGSIb3DQEBCwUAA4IB -AQAEWn3WAwAbd64f5jo2w4076s2qFiCJjPWoxO6bO75FgFFtw/NNev8pxGVw1ehg -HiTO6VRYolL5S/RKOchjA83AcDEBjgf8fKtvTmE9kxZSUIo4kIvv8V9ZM72gJhDN -8D/lXduTZ9JMwLOa1NUB8/I6CbaU3VzWkfodArKKpQF3M+LLgK03i12PD0KPQ5zv -bwaNoQo6cTmPNIdsVZETRvPqONiCUaQV57G74dGtjeirCh/DO5EYRtb1thgS7TGm -+Xg8OC5vZ6g0+xsrSqDBmWNtlI7S3bsL5C3LIEOOAL1ZJHRy2KvIGQ9ipb3XjnKS -N7/wlQduRyPH7oaD/o4xf5Gt ------END CERTIFICATE----- diff --git a/actix-http/tests/key.pem b/actix-http/tests/key.pem deleted file mode 100644 index 2afbf549..00000000 --- a/actix-http/tests/key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmm -bt8rYNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2 -ZBroAuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo -4YI4xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN -124xgiFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ -+K/Yp/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABAoIBAQC4lzyQd+ITEbi+ -dTxJuQj94hgHB1htgKqU888SLI5F9nP6n67y9hb5N9WygSp6UWbGqYTFYwxlPMKr -22p2WjL5NTsTcm+XdIKQZW/3y06Mn4qFefsT9XURaZriCjihfU2BRaCCNARSUzwd -ZH4I6n9mM7KaH71aa7v6ZVoahE9tXPR6hM+SHQEySW4pWkEu98VpNNeIt6vP7WF9 -ONGbRa+0En4xgkuaxem2ZYa/GZFFtdQRkroNMhIRlfcPpkjy8DCc8E5RAkOzKC3O -lnxQwt+tdNNkGZz02ed2hx/YHPwFYy76y6hK5dxq74iKIaOc8U5t0HjB1zVfwiR0 -5mcxMncxAoGBAP+RivwXZ4FcxDY1uyziF+rwlC/1RujQFEWXIxsXCnba5DH3yKul -iKEIZPZtGhpsnQe367lcXcn7tztuoVjpAnk5L+hQY64tLwYbHeRcOMJ75C2y8FFC -NeG5sQsrk3IU1+jhGvrbE7UgOeAuWJmv0M1vPNB/+hGoZBW5W5uU1x89AoGBANtA -AhLtAcqQ/Qh2SpVhLljh7U85Be9tbCGua09clkYFzh3bcoBolXKH18Veww0TP0yF -0748CKw1A+ITbTVFV+vKvi4jzIxS7mr4wYtVCMssbttQN7y3l30IDxJwa9j3zTJx -IUn5OMMLv1JyitLId8HdOy1AdU3MkpJzdLyi1mFfAoGBAL3kL4fGABM/kU7SN6RO -zgS0AvdrYOeljBp1BRGg2hab58g02vamxVEZgqMTR7zwjPDqOIz+03U7wda4Ccyd -PUhDNJSB/r6xNepshZZi642eLlnCRguqjYyNw72QADtYv2B6uehAlXEUY8xtw0lW -OGgcSeyF2pH6M3tswWNlgT3lAoGAQ/BttBelOnP7NKgTLH7Usc4wjyAIaszpePZn -Ykw6dLBP0oixzoCZ7seRYSOgJWkVcEz39Db+KP60mVWTvbIjMHm+vOVy+Pip0JQM -xXQwKWU3ZNZSrzPkyWW55ejYQn9nIn5T5mxH3ojBXHcJ9Y8RLQ20zKzwrI77zE3i -mqGK9NkCgYEAq3dzHI0DGAJrR19sWl2LcqI19sj5a91tHx4cl1dJXS/iApOLLieU -zyUGkwfsqjHPAZ7GacICeBojIn/7KdPdlSKAbGVAU3d4qzvFS0qmWzObplBz3niT -Xnep2XLaVXqwlFJZZ6AHeKzYmMH0d0raiou2bpEUBqYizy2fi3NI4mA= ------END RSA PRIVATE KEY----- diff --git a/actix-http/tests/test_rustls_server.rs b/actix-http/tests/test_rustls_server.rs new file mode 100644 index 00000000..32b33fce --- /dev/null +++ b/actix-http/tests/test_rustls_server.rs @@ -0,0 +1,462 @@ +#![cfg(feature = "rust-tls")] +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::error::PayloadError; +use actix_http::http::header::{self, HeaderName, HeaderValue}; +use actix_http::http::{Method, StatusCode, Version}; +use actix_http::{body, error, Error, HttpService, Request, Response}; +use actix_http_test::TestServer; +use actix_server::ssl::RustlsAcceptor; +use actix_server_config::ServerConfig; +use actix_service::{new_service_cfg, NewService}; + +use bytes::{Bytes, BytesMut}; +use futures::future::{self, ok, Future}; +use futures::stream::{once, Stream}; +use rustls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + NoClientAuth, ServerConfig as RustlsServerConfig, +}; + +use std::fs::File; +use std::io::{BufReader, Result}; + +fn load_body(stream: S) -> impl Future +where + S: Stream, +{ + stream.fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) +} + +fn ssl_acceptor() -> Result> { + // load ssl keys + let mut config = RustlsServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + let protos = vec![b"h2".to_vec()]; + config.set_protocols(&protos); + Ok(RustlsAcceptor::new(config)) +} + +#[test] +fn test_h2() -> Result<()> { + let rustls = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + Ok(()) +} + +#[test] +fn test_h2_1() -> Result<()> { + let rustls = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), Version::HTTP_2); + future::ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + Ok(()) +} + +#[test] +fn test_h2_body() -> Result<()> { + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let rustls = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|mut req: Request<_>| { + load_body(req.take_payload()) + .and_then(|body| Ok(Response::Ok().body(body))) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); + assert!(response.status().is_success()); + + let body = srv.load_body(response).unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) +} + +#[test] +fn test_h2_content_length() { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + { + for i in 0..4 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} + +#[test] +fn test_h2_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + let data = data.clone(); + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build().h2(move |_| { + let mut config = Response::Ok(); + for idx in 0..90 { + config.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(config.body(data.clone())) + }).map_err(|_| ())) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from(data2)); +} + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_h2_body2() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_head_empty() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_head_binary() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_head_binary2() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[test] +fn test_h2_body_length() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_body_chunked_explicit() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.load_body(response).unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_response_http_error_handling() { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(new_service_cfg(|_: &ServerConfig| { + Ok::<_, ()>(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }) + })) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); +} + +#[test] +fn test_h2_service_error() { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| Err::(error::ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 4a679f4b..a74fbb15 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -2,32 +2,19 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; -use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; use actix_service::{new_service_cfg, service_fn, NewService}; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use futures::future::{self, ok, Future}; use futures::stream::{once, Stream}; use regex::Regex; use tokio_timer::sleep; -use actix_http::error::PayloadError; use actix_http::{ body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, }; -#[cfg(feature = "ssl")] -fn load_body(stream: S) -> impl Future -where - S: Stream, -{ - stream.fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, PayloadError>(body) - }) -} - #[test] fn test_h1() { let mut srv = TestServer::new(|| { @@ -64,101 +51,6 @@ fn test_h1_2() { assert!(response.status().is_success()); } -#[cfg(feature = "ssl")] -fn ssl_acceptor( -) -> std::io::Result> { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(openssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2")?; - Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) -} - -#[cfg(feature = "ssl")] -#[test] -fn test_h2() -> std::io::Result<()> { - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[cfg(feature = "ssl")] -#[test] -fn test_h2_1() -> std::io::Result<()> { - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), http::Version::HTTP_2); - future::ok::<_, Error>(Response::Ok().finish()) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[cfg(feature = "ssl")] -#[test] -fn test_h2_body() -> std::io::Result<()> { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|mut req: Request<_>| { - load_body(req.take_payload()) - .and_then(|body| Ok(Response::Ok().body(body))) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); - assert!(response.status().is_success()); - - let body = srv.load_body(response).unwrap(); - assert_eq!(&body, data.as_bytes()); - Ok(()) -} - #[test] fn test_expect_continue() { let srv = TestServer::new(|| { @@ -457,65 +349,6 @@ fn test_content_length() { } } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_content_length() { - use actix_http::http::{ - header::{HeaderName, HeaderValue}, - StatusCode, - }; - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }) - .map_err(|_| ()), - ) - }); - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - - { - for i in 0..4 { - let req = srv - .request(http::Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), None); - - let req = srv - .request(http::Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv - .request(http::Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } - } -} - #[test] fn test_h1_headers() { let data = STR.repeat(10); @@ -555,51 +388,6 @@ fn test_h1_headers() { assert_eq!(bytes, Bytes::from(data2)); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_headers() { - let data = STR.repeat(10); - let data2 = data.clone(); - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - let data = data.clone(); - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build().h2(move |_| { - let mut builder = Response::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - future::ok::<_, ()>(builder.body(data.clone())) - }).map_err(|_| ())) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from(data2)); -} - const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -636,29 +424,6 @@ fn test_h1_body() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_body2() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - #[test] fn test_h1_head_empty() { let mut srv = TestServer::new(|| { @@ -681,38 +446,6 @@ fn test_h1_head_empty() { assert!(bytes.is_empty()); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_head_empty() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), http::Version::HTTP_2); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} - #[test] fn test_h1_head_binary() { let mut srv = TestServer::new(|| { @@ -737,41 +470,6 @@ fn test_h1_head_binary() { assert!(bytes.is_empty()); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_head_binary() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - ok::<_, ()>( - Response::Ok().content_length(STR.len() as u64).body(STR), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} - #[test] fn test_h1_head_binary2() { let mut srv = TestServer::new(|| { @@ -790,33 +488,6 @@ fn test_h1_head_binary2() { } } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_head_binary2() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - #[test] fn test_h1_body_length() { let mut srv = TestServer::new(|| { @@ -836,35 +507,6 @@ fn test_h1_body_length() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_body_length() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - #[test] fn test_h1_body_chunked_explicit() { let mut srv = TestServer::new(|| { @@ -897,40 +539,6 @@ fn test_h1_body_chunked_explicit() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_body_chunked_explicit() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - let body = - once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); - - // read response - let bytes = srv.load_body(response).unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - #[test] fn test_h1_body_chunked_implicit() { let mut srv = TestServer::new(|| { @@ -980,39 +588,6 @@ fn test_h1_response_http_error_handling() { assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_response_http_error_handling() { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(new_service_cfg(|_: &ServerConfig| { - Ok::<_, ()>(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) - }) - })) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); -} - #[test] fn test_h1_service_error() { let mut srv = TestServer::new(|| { @@ -1027,27 +602,3 @@ fn test_h1_service_error() { let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(b"error")); } - -#[cfg(feature = "ssl")] -#[test] -fn test_h2_service_error() { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| Err::(error::ErrorBadRequest("error"))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} diff --git a/actix-http/tests/test_ssl_server.rs b/actix-http/tests/test_ssl_server.rs new file mode 100644 index 00000000..0b85f33f --- /dev/null +++ b/actix-http/tests/test_ssl_server.rs @@ -0,0 +1,455 @@ +#![cfg(feature = "ssl")] +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::error::{ErrorBadRequest, PayloadError}; +use actix_http::http::header::{self, HeaderName, HeaderValue}; +use actix_http::http::{Method, StatusCode, Version}; +use actix_http::{body, Error, HttpService, Request, Response}; +use actix_http_test::TestServer; +use actix_server::ssl::OpensslAcceptor; +use actix_server_config::ServerConfig; +use actix_service::{new_service_cfg, NewService}; + +use bytes::{Bytes, BytesMut}; +use futures::future::{ok, Future}; +use futures::stream::{once, Stream}; +use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; +use std::io::Result; + +fn load_body(stream: S) -> impl Future +where + S: Stream, +{ + stream.fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) +} + +fn ssl_acceptor() -> Result> { + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("../tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("../tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(OpensslAcceptor::new(builder.build())) +} + +#[test] +fn test_h2() -> Result<()> { + let openssl = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + Ok(()) +} + +#[test] +fn test_h2_1() -> Result<()> { + let openssl = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), Version::HTTP_2); + ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + Ok(()) +} + +#[test] +fn test_h2_body() -> Result<()> { + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let openssl = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|mut req: Request<_>| { + load_body(req.take_payload()) + .and_then(|body| Ok(Response::Ok().body(body))) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); + assert!(response.status().is_success()); + + let body = srv.load_body(response).unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) +} + +#[test] +fn test_h2_content_length() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + { + for i in 0..4 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} + +#[test] +fn test_h2_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + let data = data.clone(); + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build().h2(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + ok::<_, ()>(builder.body(data.clone())) + }).map_err(|_| ())) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from(data2)); +} + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_h2_body2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_head_empty() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_head_binary() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_head_binary2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[test] +fn test_h2_body_length() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_body_chunked_explicit() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.load_body(response).unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_response_http_error_handling() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(new_service_cfg(|_: &ServerConfig| { + Ok::<_, ()>(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }) + })) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); +} + +#[test] +fn test_h2_service_error() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| Err::(ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index f02b82f0..8ecc81dc 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -142,7 +142,6 @@ fn test_body() { assert!(response.status().is_success()); assert_eq!(response.status(), http::StatusCode::NO_CONTENT); - let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_sync))); let request = srv.request(http::Method::GET, srv.url("/test")); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 602f9a3b..d9b6db41 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.2.3] - 2019-07-xx + +* Add `rustls` support + ## [0.2.2] - 2019-07-01 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 234662e9..84baf4fb 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.2" +version = "0.2.3" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -29,6 +29,9 @@ default = ["brotli", "flate2-zlib"] # openssl ssl = ["openssl", "actix-http/ssl"] +# rustls +rust-tls = ["rustls", "actix-http/rust-tls"] + # brotli encoding, requires c compiler brotli = ["actix-http/brotli"] @@ -41,7 +44,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-http = "0.2.4" +actix-http = "0.2.8" base64 = "0.10.1" bytes = "0.4" derive_more = "0.15.0" @@ -55,6 +58,7 @@ serde_json = "1.0" serde_urlencoded = "0.5.3" tokio-timer = "0.2.8" openssl = { version="0.10", optional = true } +rustls = { version = "0.15.2", optional = true } [dev-dependencies] actix-rt = "0.2.2" @@ -62,9 +66,11 @@ actix-web = { version = "1.0.0", features=["ssl"] } actix-http = { version = "0.2.4", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } actix-utils = "0.4.1" -actix-server = { version = "0.5.1", features=["ssl"] } +actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" rand = "0.7" tokio-tcp = "0.1" +webpki = "0.19" +rustls = { version = "0.15.2", features = ["dangerous_configuration"] } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 698481e3..cb38c731 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -12,11 +12,10 @@ use flate2::Compression; use futures::Future; use rand::Rng; -use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; use actix_http_test::TestServer; use actix_service::{service_fn, NewService}; -use actix_web::http::{Cookie, Version}; +use actix_web::http::Cookie; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; use awc::error::SendRequestError; @@ -43,30 +42,6 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[cfg(feature = "ssl")] -fn ssl_acceptor( -) -> std::io::Result> { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(openssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2")?; - Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) -} - #[test] fn test_simple() { let mut srv = @@ -207,60 +182,6 @@ fn test_connection_reuse() { assert_eq!(num.load(Ordering::Relaxed), 1); } -#[cfg(feature = "ssl")] -#[test] -fn test_connection_reuse_h2() { - let openssl = ssl_acceptor().unwrap(); - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) - .map_err(|_| ()), - ) - }); - - // disable ssl verification - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - - let client = awc::Client::build() - .connector(awc::Connector::new().ssl(builder.build()).finish()) - .finish(); - - // req 1 - let request = client.get(srv.surl("/")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req = client.post(srv.surl("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); - - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); -} - #[test] fn test_connection_force_close() { let num = Arc::new(AtomicUsize::new(0)); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs new file mode 100644 index 00000000..e65e4e87 --- /dev/null +++ b/awc/tests/test_rustls_client.rs @@ -0,0 +1,96 @@ +#![cfg(feature = "rust-tls")] +use rustls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + ClientConfig, NoClientAuth, +}; + +use std::fs::File; +use std::io::{BufReader, Result}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::HttpService; +use actix_http_test::TestServer; +use actix_server::ssl::RustlsAcceptor; +use actix_service::{service_fn, NewService}; +use actix_web::http::Version; +use actix_web::{web, App, HttpResponse}; + +fn ssl_acceptor() -> Result> { + use rustls::ServerConfig; + // load ssl keys + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + let protos = vec![b"h2".to_vec()]; + config.set_protocols(&protos); + Ok(RustlsAcceptor::new(config)) +} + +mod danger { + pub struct NoCertificateVerification {} + + impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp: &[u8], + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } + } +} + +#[test] +fn test_connection_reuse_h2() { + let rustls = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) + .map_err(|_| ()), + ) + }); + + // disable ssl verification + let mut config = ClientConfig::new(); + let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + config.set_protocols(&protos); + config + .dangerous() + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + + let client = awc::Client::build() + .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) + .finish(); + + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.surl("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs new file mode 100644 index 00000000..e6b0101b --- /dev/null +++ b/awc/tests/test_ssl_client.rs @@ -0,0 +1,86 @@ +#![cfg(feature = "ssl")] +use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; + +use std::io::Result; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::HttpService; +use actix_http_test::TestServer; +use actix_server::ssl::OpensslAcceptor; +use actix_service::{service_fn, NewService}; +use actix_web::http::Version; +use actix_web::{web, App, HttpResponse}; + +fn ssl_acceptor() -> Result> { + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("../tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("../tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(openssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) +} + +#[test] +fn test_connection_reuse_h2() { + let openssl = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) + .map_err(|_| ()), + ) + }); + + // disable ssl verification + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + + let client = awc::Client::build() + .connector(awc::Connector::new().ssl(builder.build()).finish()) + .finish(); + + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.surl("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} diff --git a/examples/uds.rs b/examples/uds.rs index 4d6eca40..9dc82903 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -20,6 +20,7 @@ fn no_params() -> &'static str { "Hello world!\r\n" } +#[cfg(feature = "uds")] fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init(); @@ -47,3 +48,6 @@ fn main() -> std::io::Result<()> { .workers(1) .run() } + +#[cfg(not(feature = "uds"))] +fn main() {} diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 512f65e1..fff96893 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -36,6 +36,7 @@ actix-service = "0.4.1" actix-server = "0.6.0" actix-utils = "0.4.1" awc = "0.2.2" +actix-connect = "0.2.2" base64 = "0.10" bytes = "0.4" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index ce73e181..aba53980 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -163,6 +163,10 @@ impl TestServer { Ok::(Client::build().connector(connector).finish()) })) .unwrap(); + rt.block_on(lazy( + || Ok::<_, ()>(actix_connect::start_default_resolver()), + )) + .unwrap(); System::set_current(system); TestServerRuntime { addr, rt, client } } @@ -212,18 +216,18 @@ impl TestServerRuntime { /// Construct test server url pub fn url(&self, uri: &str) -> String { if uri.starts_with('/') { - format!("http://127.0.0.1:{}{}", self.addr.port(), uri) + format!("http://localhost:{}{}", self.addr.port(), uri) } else { - format!("http://127.0.0.1:{}/{}", self.addr.port(), uri) + format!("http://localhost:{}/{}", self.addr.port(), uri) } } /// Construct test https server url pub fn surl(&self, uri: &str) -> String { if uri.starts_with('/') { - format!("https://127.0.0.1:{}{}", self.addr.port(), uri) + format!("https://localhost:{}{}", self.addr.port(), uri) } else { - format!("https://127.0.0.1:{}/{}", self.addr.port(), uri) + format!("https://localhost:{}/{}", self.addr.port(), uri) } } diff --git a/tests/cert.pem b/tests/cert.pem index eafad524..f9bb0508 100644 --- a/tests/cert.pem +++ b/tests/cert.pem @@ -1,20 +1,32 @@ -----BEGIN CERTIFICATE----- -MIIDPjCCAiYCCQCmkoCBehOyYTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww -CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xOTAzMjky -MzE5MDlaFw0yMDAzMjgyMzE5MDlaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD -QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY -MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmmbt8r -YNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2ZBro -AuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo4YI4 -xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN124x -giFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ+K/Y -p/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABMA0GCSqGSIb3DQEBCwUAA4IB -AQAEWn3WAwAbd64f5jo2w4076s2qFiCJjPWoxO6bO75FgFFtw/NNev8pxGVw1ehg -HiTO6VRYolL5S/RKOchjA83AcDEBjgf8fKtvTmE9kxZSUIo4kIvv8V9ZM72gJhDN -8D/lXduTZ9JMwLOa1NUB8/I6CbaU3VzWkfodArKKpQF3M+LLgK03i12PD0KPQ5zv -bwaNoQo6cTmPNIdsVZETRvPqONiCUaQV57G74dGtjeirCh/DO5EYRtb1thgS7TGm -+Xg8OC5vZ6g0+xsrSqDBmWNtlI7S3bsL5C3LIEOOAL1ZJHRy2KvIGQ9ipb3XjnKS -N7/wlQduRyPH7oaD/o4xf5Gt +MIIFfjCCA2agAwIBAgIJAOIBvp/w68KrMA0GCSqGSIb3DQEBCwUAMGsxCzAJBgNV +BAYTAlJVMRkwFwYDVQQIDBBTYWludC1QZXRlcnNidXJnMRkwFwYDVQQHDBBTYWlu +dC1QZXRlcnNidXJnMRIwEAYDVQQKDAlLdXBpYmlsZXQxEjAQBgNVBAMMCWxvY2Fs +aG9zdDAgFw0xOTA3MjcxODIzMTJaGA8zMDE5MDcyNzE4MjMxMlowazELMAkGA1UE +BhMCUlUxGTAXBgNVBAgMEFNhaW50LVBldGVyc2J1cmcxGTAXBgNVBAcMEFNhaW50 +LVBldGVyc2J1cmcxEjAQBgNVBAoMCUt1cGliaWxldDESMBAGA1UEAwwJbG9jYWxo +b3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuiQZzTO3gRRPr6ZH +wcmKqkoXig9taCCqx72Qvb9tvCLhQLE1dDPZV8I/r8bx+mM4Yz3r0Hm5LxTIhCM9 +p3/abuiJAZENC/VkxgFzBGg7KGLSFmzU+A8Ft+2mrKmj5MpIPBCxDeVg80TCQOJy +hj+NU3PpBo9nxTgxWNWO6X+ZovZohdp78fYLLtns8rxjug3FVzdPrrLnBvihkGlq +gfImkh+vZxMTj1OgtxyCOhdbO4Ol4jCbn7a5yIw+iixHOEgBQfTQopRP7z1PEUV2 +WIy2VEGzvQDlj2OyzH86T1IOFV5rz5MjdZuW0qNzeS0w3Jzgp/olSbIZLhGAaIk0 +gN7y9XvSHqs7rO0wW+467ico7+uP1ScGgPgJA5fGu7ahp7F7G3ZSoAqAcS60wYsX +kStoA3RWAuqste6aChv1tlgTt+Rhk8qjGhuh0ng2qVnTGyo2T3OCHB/c47Bcsp6L +xiyTCnQIPH3fh2iO/SC7gPw3jihPMCAQZYlkC3MhMk974rn2zs9cKtJ8ubnG2m5F +VFVYmduRqS/YQS/O802jVCFdc8KDmoeAYNuHzgRZjQv9018UUeW3jtWKnopJnWs5 +ae9pbtmYeOtc7OovOxT7J2AaVfUkHRhmlqWZMzEJTcZws0fRPTZDifFJ5LFWbZsC +zW4tCKBKvYM9eAnbb+abiHXlY1MCAwEAAaMjMCEwHwYDVR0RBBgwFoIJbG9jYWxo +b3N0ggkxMjcuMC4wLjEwDQYJKoZIhvcNAQELBQADggIBAC1EU4SVCfUKc7JbhYRf +P87F+8e13bBTLxevJdnTCH3Xw2AN8UPmwQ2vv9Mv2FMulMBQ7yLnQLGtgGUua2OE +XO+EdBBEKnglo9cwXGzU6qHhaiCeXZDM8s53qOOrD42XsDsY0nOoFYqDLW3WixP9 +f1fWbcEf6+ktlvqi/1/3R6QtQR+6LS43enbsYHq8aAP60NrpXxdXxEoUwW6Z/sje +XAQluH8jzledwJcY8bXRskAHZlE4kGlOVuGgnyI3BXyLiwB4g9smFzYIs98iAGmV +7ZBaR5IIiRCtoKBG+SngM7Log0bHphvFPjDDvgqWYiWaOHboYM60Y2Z/gRbcjuMU +WZX64jw29fa8UPFdtGTupt+iuO7iXnHnm0lBBK36rVdOvsZup76p6L4BXmFsRmFK +qJ2Zd8uWNPDq80Am0mYaAqENuIANHHJXX38SesC+QO+G2JZt6vCwkGk/Qune4GIg +1GwhvsDRfTQopSxg1rdPwPM7HWeTfUGHZ34B5p/iILA3o6PfYQU8fNAWIsCDkRX2 +MrgDgCnLZxKb6pjR4DYNAdPwkxyMFACZ2T46z6WvLWFlnkK5nbZoqsOsp+GJHole +llafhrelXEzt3zFR0q4zGcqheJDI+Wy+fBy3XawgAc4eN0T2UCzL/jKxKgzlzSU3 ++xh1SDNjFLRd6sGzZHPMgXN0 -----END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem index 2afbf549..70153c8a 100644 --- a/tests/key.pem +++ b/tests/key.pem @@ -1,27 +1,52 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmm -bt8rYNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2 -ZBroAuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo -4YI4xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN -124xgiFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ -+K/Yp/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABAoIBAQC4lzyQd+ITEbi+ -dTxJuQj94hgHB1htgKqU888SLI5F9nP6n67y9hb5N9WygSp6UWbGqYTFYwxlPMKr -22p2WjL5NTsTcm+XdIKQZW/3y06Mn4qFefsT9XURaZriCjihfU2BRaCCNARSUzwd -ZH4I6n9mM7KaH71aa7v6ZVoahE9tXPR6hM+SHQEySW4pWkEu98VpNNeIt6vP7WF9 -ONGbRa+0En4xgkuaxem2ZYa/GZFFtdQRkroNMhIRlfcPpkjy8DCc8E5RAkOzKC3O -lnxQwt+tdNNkGZz02ed2hx/YHPwFYy76y6hK5dxq74iKIaOc8U5t0HjB1zVfwiR0 -5mcxMncxAoGBAP+RivwXZ4FcxDY1uyziF+rwlC/1RujQFEWXIxsXCnba5DH3yKul -iKEIZPZtGhpsnQe367lcXcn7tztuoVjpAnk5L+hQY64tLwYbHeRcOMJ75C2y8FFC -NeG5sQsrk3IU1+jhGvrbE7UgOeAuWJmv0M1vPNB/+hGoZBW5W5uU1x89AoGBANtA -AhLtAcqQ/Qh2SpVhLljh7U85Be9tbCGua09clkYFzh3bcoBolXKH18Veww0TP0yF -0748CKw1A+ITbTVFV+vKvi4jzIxS7mr4wYtVCMssbttQN7y3l30IDxJwa9j3zTJx -IUn5OMMLv1JyitLId8HdOy1AdU3MkpJzdLyi1mFfAoGBAL3kL4fGABM/kU7SN6RO -zgS0AvdrYOeljBp1BRGg2hab58g02vamxVEZgqMTR7zwjPDqOIz+03U7wda4Ccyd -PUhDNJSB/r6xNepshZZi642eLlnCRguqjYyNw72QADtYv2B6uehAlXEUY8xtw0lW -OGgcSeyF2pH6M3tswWNlgT3lAoGAQ/BttBelOnP7NKgTLH7Usc4wjyAIaszpePZn -Ykw6dLBP0oixzoCZ7seRYSOgJWkVcEz39Db+KP60mVWTvbIjMHm+vOVy+Pip0JQM -xXQwKWU3ZNZSrzPkyWW55ejYQn9nIn5T5mxH3ojBXHcJ9Y8RLQ20zKzwrI77zE3i -mqGK9NkCgYEAq3dzHI0DGAJrR19sWl2LcqI19sj5a91tHx4cl1dJXS/iApOLLieU -zyUGkwfsqjHPAZ7GacICeBojIn/7KdPdlSKAbGVAU3d4qzvFS0qmWzObplBz3niT -Xnep2XLaVXqwlFJZZ6AHeKzYmMH0d0raiou2bpEUBqYizy2fi3NI4mA= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC6JBnNM7eBFE+v +pkfByYqqSheKD21oIKrHvZC9v228IuFAsTV0M9lXwj+vxvH6YzhjPevQebkvFMiE +Iz2nf9pu6IkBkQ0L9WTGAXMEaDsoYtIWbNT4DwW37aasqaPkykg8ELEN5WDzRMJA +4nKGP41Tc+kGj2fFODFY1Y7pf5mi9miF2nvx9gsu2ezyvGO6DcVXN0+usucG+KGQ +aWqB8iaSH69nExOPU6C3HII6F1s7g6XiMJuftrnIjD6KLEc4SAFB9NCilE/vPU8R +RXZYjLZUQbO9AOWPY7LMfzpPUg4VXmvPkyN1m5bSo3N5LTDcnOCn+iVJshkuEYBo +iTSA3vL1e9Ieqzus7TBb7jruJyjv64/VJwaA+AkDl8a7tqGnsXsbdlKgCoBxLrTB +ixeRK2gDdFYC6qy17poKG/W2WBO35GGTyqMaG6HSeDapWdMbKjZPc4IcH9zjsFyy +novGLJMKdAg8fd+HaI79ILuA/DeOKE8wIBBliWQLcyEyT3viufbOz1wq0ny5ucba +bkVUVViZ25GpL9hBL87zTaNUIV1zwoOah4Bg24fOBFmNC/3TXxRR5beO1Yqeikmd +azlp72lu2Zh461zs6i87FPsnYBpV9SQdGGaWpZkzMQlNxnCzR9E9NkOJ8UnksVZt +mwLNbi0IoEq9gz14Cdtv5puIdeVjUwIDAQABAoICAQCZVVezw+BsAjFKPi1qIv2J +HZOadO7pEc/czflHdUON8SWgxtmDqZpmQmt3/ugiHE284qs4hqzXbcVnpCgLrLRh +HEiP887NhQ3IVjVK8hmZQR5SvsAIv0c0ph3gqbWKqF8sq4tOKR/eBUwHawJwODXR +AvB4KPWQbqOny/P3wNbseRLNAJeNT+MSaw5XPnzgLKvdFoEbJeBNy847Sbsk5DaF +tHgm7n30WS1Q6bkU5VyP//hMBUKNJFaSL4TtCWB5qkbu8B5VbtsR9m0FizTb6L3h +VmYbUXvIzJXjAwMjiDJ1w9wHl+tj3BE33tEmhuVzNf+SH+tLc9xuKJighDWt2vpD +eTpZ1qest26ANLOmNXWVCVTGpcWvOu5yhG/P7En10EzjFruMfHAFdwLm1gMx1rlR +9fyNAk/0ROJ+5BUtuWgDiyytS5f2T9KGiOHni7UbBIkv0CV2H6VL39Twxf+3OHnx +JJ7OWZ8DRuLM/EJfN3C1+3eDsXOvcdvbo2TFBmCCl4Pa2pm4k3g2NBfxy/zSYWIh +ccGPZorFKNMUi29U0Ool6fxeVflbll570pWVBLAB31HdkLSESv9h+2j/IiEJcJXj +nzl2RtYB0Uxzk6SjO0z4WXjz/SXg5tQQkm/dx8kM8TvHICFq68AEnw8t9Hagsdxs +v5jNyOEeI1I5gPgZmKuboQKCAQEA7Hw6s8Xc3UtNaywMcK1Eb1O+kwRdztgtm0uE +uqsHWmGqbBxXN4jiVLh3dILIXFuVbvDSsSZtPLhOj1wqxgsTHg93b4BtvowyNBgo +X4tErMu7/6NRael/hfOEdtpfv2gV+0eQa+8KKqYJPbqpMz/r5L/3RaxS3iXkj3QM +6oC4+cRuwy/flPMIpxhDriH5yjfiMOdDsi3ZfMTJu/58DTrKV7WkJxQZmha4EoZn +IiXeRhzo+2ukMDWrr3GGPyDfjd/NB7rmY8QBdmhB5NSk+6B66JCNTIbKka/pichS +36bwSYFNji4NaHUUlYDUjfKoTNuQMEZknMGhc/433ADO7s17iQKCAQEAyYBYVG7/ +LE2IkvQy9Nwly5tRCNlSvSkloz7PUwRbzG5uF5mweWEa8YECJe9/vrFXvyBW+NR8 +XABFn4eG0POTR9nyb4n2nUlqiGugDIPgkrKCkJws5InifITZ/+Viocd4YZL5UwCU +R1/kMf0UjK2iJjWEeTPS6RmwRI2Iu7kym9BzphDyNYBQSbUE/f+4hNP6nUT/h09c +VH4/sUhubSgVKeK4onOci8bKitAkwVBYCYSyhuBCeCu8fTk2hVRWviRaJPVq2PMB +LHw1FCcfJLIPJG6MZpFAPkMQxpiewdggXIgi46ZlZcsNXEJ81ocT4GU2j+ArQXCf +lgEycyD3mx4k+wKCAQBGneohmKoVYtEheavVUcgnvkggOqOQirlDsE9YNo4hjRyI +4AWjTbrYNaVmI0+VVLvQvxULVUA1a4v5/zm+nbv9s/ykTSN4TQEI0VXtAfdl6gif +k7NR/ynXZBpgK2GAFKLLwFj+Agl1JtOHnV+9MA9O5Yv/QDAWqhYQSEU7GWkjHGc+ +3eLT5ablzrcXHooqunlOxSBP6qURPupGuv1sLewSOOllyfjDLJmW3o+ZgNlY8nUX +7tK+mqhD4ZCG9VgMU5I0BrmZfQQ6yXMz09PYV9mb7N5kxbNjwbXpMOqeYolKSdRQ +6quST7Pv2OKf6KAdI0txPvP4Y1HFA1rG1W71nGKRAoIBAHlDU+T8J3Rx9I77hu70 +zYoKnmnE35YW/R+Q3RQIu3X7vyVUyG9DkQNlr/VEfIw2Dahnve9hcLWtNDkdRnTZ +IPlMoCmfzVo6pHIU0uy1MKEX7Js6YYnnsPVevhLR6NmTQU73NDRPVOzfOGUc+RDw +LXTxIBgQqAy/+ORIiNDwUxSSDgcSi7DG14qD9c0l59WH/HpI276Cc/4lPA9kl4/5 +X0MlvheFm+BCcgG34Wa1A0Y3JXkl3NqU94oktDro1or3NYioaPTGyR4MYaUPJh7f +SV2TacsP/ql5ks7xahkeB9un0ddOfBcWa6PqH1a7U6rnPj63mVB4hpGvhrziSiB/ +s6ECggEAOp2P4Yd9Vm9/CptxS50HFF4adyLscAtsDd3S2hIAXhDovcPbvRek4pLQ +idPhHlRAfqrEztnhaVAmCK9HlhgthtiQGQX62YI4CS4QL2IhzDFo3M1a2snjFEdl +QuFk3XI7kQ0Yp8BLLG7T436JUrUkCXc4gQX2uRNut+ff34RIR2CjcQQjChxuHVeG +sP/3xFFj8OSs7ZoSPbmDBLrMOl64YHwezQUNAZiRYiaGbFiY0QUV6dHq8qX/qE1h +a/0Rq+gTqObDST0TqhMzI8V/i7R8SwVcD5ODHaZp5I2N2P/hV5OWY7ghQXhh89WM +o21xtGh0nP2Fq1TC6jFO+9cpbK8jNA== +-----END PRIVATE KEY----- diff --git a/tests/test_server.rs b/tests/test_server.rs index 33c18b00..1623d2ef 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -16,6 +16,7 @@ use flate2::Compression; use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; +use actix_connect::start_default_resolver; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{dev, http, test, web, App, HttpResponse, HttpServer}; @@ -782,7 +783,7 @@ fn test_brotli_encoding_large() { #[test] fn test_reading_deflate_encoding_large_random_ssl() { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, rsa_private_keys}; + use rustls::internal::pemfile::{certs, pkcs8_private_keys}; use rustls::{NoClientAuth, ServerConfig}; use std::fs::File; use std::io::BufReader; @@ -803,7 +804,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); let srv = HttpServer::new(|| { @@ -823,6 +824,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { let _ = sys.run(); }); let (srv, _sys) = rx.recv().unwrap(); + test::block_on(futures::lazy(|| Ok::<_, ()>(start_default_resolver()))).unwrap(); let client = test::run_on(|| { let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); @@ -844,19 +846,18 @@ fn test_reading_deflate_encoding_large_random_ssl() { let enc = e.finish().unwrap(); // client request - let _req = client - .post(format!("https://{}/", addr)) + let req = client + .post(format!("https://localhost:{}/", addr.port())) .header(http::header::CONTENT_ENCODING, "deflate") .send_body(enc); - // TODO: fix - // let response = test::block_on(req).unwrap(); - // assert!(response.status().is_success()); + let mut response = test::block_on(req).unwrap(); + assert!(response.status().is_success()); // read response - // let bytes = test::block_on(response.body()).unwrap(); - // assert_eq!(bytes.len(), data.len()); - // assert_eq!(bytes, Bytes::from(data)); + let bytes = test::block_on(response.body()).unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); // stop let _ = srv.stop(false); From 0d15861e234a5aa8da4063df07f6effc33709eda Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 1 Aug 2019 15:26:30 -0700 Subject: [PATCH 1532/1635] prepare actix-http release --- actix-http/CHANGES.md | 7 ++++--- actix-http/Cargo.toml | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index b14c5784..d7cd42f6 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,11 +1,12 @@ # Changes -## [0.2.8] - 2019-07-xx +## [0.2.8] - 2019-08-01 -### Changed +### Added + +* Add `rustls` support * Add `Clone` impl for `HeaderMap` -* Add `rustls` support ### Fixed diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ad626eb1..f54fc9a2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -27,6 +27,8 @@ default = [] # openssl ssl = ["openssl", "actix-connect/ssl"] + +# rustls support rust-tls = ["rustls", "webpki-roots", "actix-connect/rust-tls"] # brotli encoding, requires c compiler From cf1a60cb3a6e954e80eec8dd635114613ac41251 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 1 Aug 2019 15:41:14 -0700 Subject: [PATCH 1533/1635] prepare awc release --- .travis.yml | 2 ++ actix-http/Cargo.toml | 2 +- awc/CHANGES.md | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 97a05cc9..08db4d0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,8 @@ script: - cargo update - cargo check --all --no-default-features - cargo test --all-features --all -- --nocapture + - cd actix-http; cargo test --no-default-features --features="rust-tls" -- --nocapture; cd .. + - cd awc; cargo test --no-default-features --features="rust-tls" -- --nocapture; cd .. # Upload docs after_success: diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f54fc9a2..50b52d12 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -102,7 +102,7 @@ chrono = "0.4.6" [dev-dependencies] actix-rt = "0.2.2" -actix-server = { version = "0.6.0", features=["ssl"] } +actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } actix-connect = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "0.2.4", features=["ssl"] } env_logger = "0.6" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index d9b6db41..33d47fff 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,9 +1,12 @@ # Changes -## [0.2.3] - 2019-07-xx +## [0.2.3] - 2019-08-01 + +### Added * Add `rustls` support + ## [0.2.2] - 2019-07-01 ### Changed From 0b9e6922985b1c3c69a89c04ca61f469dc34ad51 Mon Sep 17 00:00:00 2001 From: Lukas Lueg Date: Tue, 6 Aug 2019 18:32:22 +0200 Subject: [PATCH 1534/1635] Remove byteorder-dependency --- actix-http/Cargo.toml | 1 - actix-http/src/ws/frame.rs | 22 +++++++++++----------- actix-http/src/ws/mask.rs | 3 +-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 50b52d12..5cf5cafc 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -57,7 +57,6 @@ actix-threadpool = "0.1.1" base64 = "0.10" bitflags = "1.0" bytes = "0.4" -byteorder = "1.2" copyless = "0.1.4" derive_more = "0.15.0" either = "1.5.2" diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 652746b8..46e9f36d 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -1,4 +1,5 @@ -use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; +use std::convert::TryFrom; + use bytes::{BufMut, Bytes, BytesMut}; use log::debug; use rand; @@ -48,14 +49,16 @@ impl Parser { if chunk_len < 4 { return Ok(None); } - let len = NetworkEndian::read_uint(&src[idx..], 2) as usize; + let len = usize::from(u16::from_be_bytes( + TryFrom::try_from(&src[idx..idx + 2]).unwrap(), + )); idx += 2; len } else if len == 127 { if chunk_len < 10 { return Ok(None); } - let len = NetworkEndian::read_uint(&src[idx..], 8); + let len = u64::from_be_bytes(TryFrom::try_from(&src[idx..idx + 8]).unwrap()); if len > max_size as u64 { return Err(ProtocolError::Overflow); } @@ -75,10 +78,10 @@ impl Parser { return Ok(None); } - let mask: &[u8] = &src[idx..idx + 4]; - let mask_u32 = LittleEndian::read_u32(mask); + let mask = + u32::from_le_bytes(TryFrom::try_from(&src[idx..idx + 4]).unwrap()); idx += 4; - Some(mask_u32) + Some(mask) } else { None }; @@ -137,7 +140,7 @@ impl Parser { /// Parse the payload of a close frame. pub fn parse_close_payload(payload: &[u8]) -> Option { if payload.len() >= 2 { - let raw_code = NetworkEndian::read_u16(payload); + let raw_code = u16::from_be_bytes(TryFrom::try_from(&payload[..2]).unwrap()); let code = CloseCode::from(raw_code); let description = if payload.len() > 2 { Some(String::from_utf8_lossy(&payload[2..]).into()) @@ -201,10 +204,7 @@ impl Parser { let payload = match reason { None => Vec::new(), Some(reason) => { - let mut code_bytes = [0; 2]; - NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); - - let mut payload = Vec::from(&code_bytes[..]); + let mut payload = Into::::into(reason.code).to_be_bytes().to_vec(); if let Some(description) = reason.description { payload.extend(description.as_bytes()); } diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index 15737541..9f730403 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -105,7 +105,6 @@ fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { #[cfg(test)] mod tests { use super::apply_mask; - use byteorder::{ByteOrder, LittleEndian}; /// A safe unoptimized mask application. fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { @@ -117,7 +116,7 @@ mod tests { #[test] fn test_apply_mask() { let mask = [0x6d, 0xb6, 0xb2, 0x80]; - let mask_u32: u32 = LittleEndian::read_u32(&mask); + let mask_u32 = u32::from_le_bytes(mask); let unmasked = vec![ 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, From b70de5b99125479a718c00e99671c91c4669dcda Mon Sep 17 00:00:00 2001 From: Lukas Lueg Date: Wed, 7 Aug 2019 16:43:03 +0200 Subject: [PATCH 1535/1635] Update CHANGES.md --- actix-http/CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d7cd42f6..ab81bbcd 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.2.9] - 2019-08-xx + +### Changed + +* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation + + ## [0.2.8] - 2019-08-01 ### Added From 0ee69671bac0dba161d01d7a9435789476f9bdf6 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 12 Aug 2019 04:00:13 +0900 Subject: [PATCH 1536/1635] Update nightly to 2019-08-10 (#1028) --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 08db4d0a..82db86a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ matrix: include: - rust: stable - rust: beta - - rust: nightly-2019-04-02 + - rust: nightly-2019-08-10 allow_failures: - - rust: nightly-2019-04-02 + - rust: nightly-2019-08-10 env: global: @@ -25,7 +25,7 @@ before_install: - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-08-10" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin fi @@ -51,7 +51,7 @@ after_success: echo "Uploaded documentation" fi - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-08-10" ]]; then taskset -c 0 cargo tarpaulin --out Xml --all --all-features bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" From dbe4c9ffb5ccbb74105a22f9dc62b0846468f24d Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 12 Aug 2019 05:43:29 +0900 Subject: [PATCH 1537/1635] Replace deprecated methods in actix_files (#1027) * Bump up mime_guess to 2.0.1 * Replace deprecated methods * Update CHANGE.md --- actix-files/CHANGES.md | 4 ++++ actix-files/Cargo.toml | 2 +- actix-files/src/lib.rs | 4 ++-- actix-files/src/named.rs | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 916d579f..8fdca4bc 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.5] - unreleased + +* Bump up `mime_guess` crate version to 2.0.1 + ## [0.1.4] - 2019-07-20 * Allow to disable `Content-Disposition` header #686 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 307e7906..ee2121ff 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -27,7 +27,7 @@ futures = "0.1.25" derive_more = "0.15.0" log = "0.4" mime = "0.3" -mime_guess = "2.0.0-alpha" +mime_guess = "2.0.1" percent-encoding = "1.0" v_htmlescape = "0.4" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 816fd92a..09642046 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -22,7 +22,7 @@ use bytes::Bytes; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Stream}; use mime; -use mime_guess::get_mime_type; +use mime_guess::from_ext; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use v_htmlescape::escape as escape_html_entity; @@ -42,7 +42,7 @@ type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error /// the type `application/octet-stream`. #[inline] pub fn file_extension_to_mime(ext: &str) -> mime::Mime { - get_mime_type(ext) + from_ext(ext).first_or_octet_stream() } #[doc(hidden)] diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 4c80e1d9..f548a7a1 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -9,7 +9,7 @@ use std::os::unix::fs::MetadataExt; use bitflags::bitflags; use mime; -use mime_guess::guess_mime_type; +use mime_guess::from_path; use actix_http::body::SizedStream; use actix_web::http::header::{ @@ -88,7 +88,7 @@ impl NamedFile { } }; - let ct = guess_mime_type(&path); + let ct = from_path(&path).first_or_octet_stream(); let disposition_type = match ct.type_() { mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, _ => DispositionType::Attachment, From 915010e7338c4c119ff387296185aff8f177ca55 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 13 Aug 2019 14:55:04 +0200 Subject: [PATCH 1538/1635] Fixes a bug in OpenWaitingConnection where the h2 flow would panic a future (#1031) --- actix-http/src/client/pool.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index dbd8f202..24a18739 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -590,6 +590,29 @@ where type Error = (); fn poll(&mut self) -> Poll { + if let Some(ref mut h2) = self.h2 { + return match h2.poll() { + Ok(Async::Ready((snd, connection))) => { + tokio_current_thread::spawn(connection.map_err(|_| ())); + let rx = self.rx.take().unwrap(); + let _ = rx.send(Ok(IoConnection::new( + ConnectionType::H2(snd), + Instant::now(), + Some(Acquired(self.key.clone(), self.inner.take())), + ))); + Ok(Async::Ready(())) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => { + let _ = self.inner.take(); + if let Some(rx) = self.rx.take() { + let _ = rx.send(Err(ConnectError::H2(err))); + } + Err(()) + } + }; + } + match self.fut.poll() { Err(err) => { let _ = self.inner.take(); From 192dfff6805a9360ee1858419f98f98066ff87c0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 13 Aug 2019 15:20:29 +0200 Subject: [PATCH 1539/1635] prepare actix-http 0.2.9 release --- actix-http/CHANGES.md | 6 +++++- actix-http/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ab81bbcd..8a8e8545 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,11 +1,15 @@ # Changes -## [0.2.9] - 2019-08-xx +## [0.2.9] - 2019-08-13 ### Changed * Dropped the `byteorder`-dependency in favor of `stdlib`-implementation +### Fixed + +* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) + ## [0.2.8] - 2019-08-01 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 5cf5cafc..b64d5501 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.8" +version = "0.2.9" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From 55179d6ab2c602617d5c1391728d041223ae8358 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 13 Aug 2019 10:48:11 -0700 Subject: [PATCH 1540/1635] update dependencies --- CHANGES.md | 2 ++ Cargo.toml | 2 +- actix-files/CHANGES.md | 2 ++ actix-files/Cargo.toml | 2 +- actix-files/src/lib.rs | 4 +-- actix-http/CHANGES.md | 4 +++ actix-http/Cargo.toml | 4 +-- actix-http/src/cookie/mod.rs | 25 ++++++++++++++++--- actix-http/src/header/mod.rs | 47 +++++++++++++++++++++--------------- actix-http/src/test.rs | 8 +++--- awc/CHANGES.md | 9 +++++++ awc/Cargo.toml | 4 +-- awc/src/request.rs | 8 +++--- awc/src/test.rs | 8 +++--- awc/src/ws.rs | 7 +++--- test-server/CHANGES.md | 6 +++++ test-server/Cargo.toml | 2 +- 17 files changed, 97 insertions(+), 47 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b3a0c86c..62eb0ef9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ * `Query` payload made `pub`. Allows user to pattern-match the payload. +* Update serde_urlencoded to "0.6.1" + ## [1.0.5] - 2019-07-18 diff --git a/Cargo.toml b/Cargo.toml index 9143f2fe..49889def 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,7 @@ parking_lot = "0.9" regex = "1.0" serde = { version = "1.0", features=["derive"] } serde_json = "1.0" -serde_urlencoded = "0.5.3" +serde_urlencoded = "0.6.1" time = "0.1.42" url = { version="1.7", features=["query_encoding"] } diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 8fdca4bc..49ecdbff 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -4,6 +4,8 @@ * Bump up `mime_guess` crate version to 2.0.1 +* Bump up `percent-encoding` crate version to 2.1 + ## [0.1.4] - 2019-07-20 * Allow to disable `Content-Disposition` header #686 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index ee2121ff..a25ce17b 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -28,7 +28,7 @@ derive_more = "0.15.0" log = "0.4" mime = "0.3" mime_guess = "2.0.1" -percent-encoding = "1.0" +percent-encoding = "2.1" v_htmlescape = "0.4" [dev-dependencies] diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 09642046..c99d3265 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -23,7 +23,7 @@ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Stream}; use mime; use mime_guess::from_ext; -use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; +use percent_encoding::{utf8_percent_encode, CONTROLS}; use v_htmlescape::escape as escape_html_entity; mod error; @@ -144,7 +144,7 @@ impl Directory { // show file url as relative to static path macro_rules! encode_file_url { ($path:ident) => { - utf8_percent_encode(&$path.to_string_lossy(), DEFAULT_ENCODE_SET) + utf8_percent_encode(&$path.to_string_lossy(), CONTROLS) }; } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 8a8e8545..c8d1b2ae 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,10 @@ * Dropped the `byteorder`-dependency in favor of `stdlib`-implementation +* Update percent-encoding to 2.1 + +* Update serde_urlencoded to 0.6.1 + ### Fixed * Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b64d5501..79d7117b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -71,14 +71,14 @@ lazy_static = "1.0" language-tags = "0.2" log = "0.4" mime = "0.3" -percent-encoding = "1.0" +percent-encoding = "2.1" rand = "0.7" regex = "1.0" serde = "1.0" serde_json = "1.0" sha1 = "0.6" slab = "0.4" -serde_urlencoded = "0.5.5" +serde_urlencoded = "0.6.1" time = "0.1.42" tokio-tcp = "0.1.3" tokio-timer = "0.2.8" diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index f576a452..db821142 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -66,7 +66,7 @@ use std::fmt; use std::str::FromStr; use chrono::Duration; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::{percent_encode, AsciiSet, CONTROLS}; use time::Tm; pub use self::builder::CookieBuilder; @@ -75,6 +75,25 @@ pub use self::jar::{CookieJar, Delta, Iter}; use self::parse::parse_cookie; pub use self::parse::ParseError; +/// https://url.spec.whatwg.org/#fragment-percent-encode-set +const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); + +/// https://url.spec.whatwg.org/#path-percent-encode-set +const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}'); + +/// https://url.spec.whatwg.org/#userinfo-percent-encode-set +pub const USERINFO: &AsciiSet = &PATH + .add(b'/') + .add(b':') + .add(b';') + .add(b'=') + .add(b'@') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^') + .add(b'|'); + #[derive(Debug, Clone)] enum CookieStr { /// An string derived from indexes (start, end). @@ -910,8 +929,8 @@ pub struct EncodedCookie<'a, 'c: 'a>(&'a Cookie<'c>); impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Percent-encode the name and value. - let name = percent_encode(self.0.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(self.0.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(self.0.name().as_bytes(), USERINFO); + let value = percent_encode(self.0.value().as_bytes(), USERINFO); // Write out the name/value pair and the cookie's parameters. write!(f, "{}={}", name, value)?; diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 62018347..37cf9450 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -6,6 +6,7 @@ use std::{fmt, str::FromStr}; use bytes::{Bytes, BytesMut}; use http::Error as HttpError; use mime::Mime; +use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; @@ -361,10 +362,8 @@ pub fn parse_extended_value( impl fmt::Display for ExtendedValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let encoded_value = percent_encoding::percent_encode( - &self.value[..], - self::percent_encoding_http::HTTP_VALUE, - ); + let encoded_value = + percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); if let Some(ref lang) = self.language_tag { write!(f, "{}'{}'{}", self.charset, lang, encoded_value) } else { @@ -378,8 +377,7 @@ impl fmt::Display for ExtendedValue { /// /// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = - percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); fmt::Display::fmt(&encoded, f) } @@ -394,20 +392,29 @@ impl From for HeaderMap { } } -mod percent_encoding_http { - use percent_encoding::{self, define_encode_set}; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} +// This encode set is used for HTTP header values and is defined at +// https://tools.ietf.org/html/rfc5987#section-3.2 +pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'%') + .add(b'\'') + .add(b'(') + .add(b')') + .add(b'*') + .add(b',') + .add(b'/') + .add(b':') + .add(b';') + .add(b'<') + .add(b'-') + .add(b'>') + .add(b'?') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'{') + .add(b'}'); #[cfg(test)] mod tests { diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index b4344a67..ce81a54d 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -9,9 +9,9 @@ use bytes::{Buf, Bytes, BytesMut}; use futures::{Async, Poll}; use http::header::{self, HeaderName, HeaderValue}; use http::{HttpTryFrom, Method, Uri, Version}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::percent_encode; -use crate::cookie::{Cookie, CookieJar}; +use crate::cookie::{Cookie, CookieJar, USERINFO}; use crate::header::HeaderMap; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; @@ -166,8 +166,8 @@ impl TestRequest { let mut cookie = String::new(); for c in inner.cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(c.name().as_bytes(), USERINFO); + let value = percent_encode(c.value().as_bytes(), USERINFO); let _ = write!(&mut cookie, "; {}={}", name, value); } if !cookie.is_empty() { diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 33d47fff..5e012def 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,14 @@ # Changes +## [0.2.4] - 2019-xx-xx + +### Changed + +* Update percent-encoding to "2.1" + +* Update serde_urlencoded to "0.6.1" + + ## [0.2.3] - 2019-08-01 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 84baf4fb..42534cb1 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -51,11 +51,11 @@ derive_more = "0.15.0" futures = "0.1.25" log =" 0.4" mime = "0.3" -percent-encoding = "1.0" +percent-encoding = "2.1" rand = "0.7" serde = "1.0" serde_json = "1.0" -serde_urlencoded = "0.5.3" +serde_urlencoded = "0.6.1" tokio-timer = "0.2.8" openssl = { version="0.10", optional = true } rustls = { version = "0.15.2", optional = true } diff --git a/awc/src/request.rs b/awc/src/request.rs index 0e544589..43715785 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -7,13 +7,13 @@ use std::{fmt, net}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{err, Either}; use futures::{Future, Stream}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::percent_encode; use serde::Serialize; use serde_json; use tokio_timer::Timeout; use actix_http::body::{Body, BodyStream}; -use actix_http::cookie::{Cookie, CookieJar}; +use actix_http::cookie::{Cookie, CookieJar, USERINFO}; use actix_http::encoding::Decoder; use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; use actix_http::http::{ @@ -399,8 +399,8 @@ impl ClientRequest { if let Some(ref mut jar) = self.cookies { let mut cookie = String::new(); for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(c.name().as_bytes(), USERINFO); + let value = percent_encode(c.value().as_bytes(), USERINFO); let _ = write!(&mut cookie, "; {}={}", name, value); } self.head.headers.insert( diff --git a/awc/src/test.rs b/awc/src/test.rs index b852adb2..641ecaa8 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,12 +1,12 @@ //! Test helpers for actix http client to use during testing. use std::fmt::Write as FmtWrite; -use actix_http::cookie::{Cookie, CookieJar}; +use actix_http::cookie::{Cookie, CookieJar, USERINFO}; use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; use actix_http::http::{HeaderName, HttpTryFrom, StatusCode, Version}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::percent_encode; use crate::ClientResponse; @@ -87,8 +87,8 @@ impl TestResponse { let mut cookie = String::new(); for c in self.cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(c.name().as_bytes(), USERINFO); + let value = percent_encode(c.value().as_bytes(), USERINFO); let _ = write!(&mut cookie, "; {}={}", name, value); } if !cookie.is_empty() { diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 27f454ed..72c9a38b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -8,9 +8,10 @@ use actix_codec::Framed; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use futures::future::{err, Either, Future}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::percent_encode; use tokio_timer::Timeout; +use actix_http::cookie::USERINFO; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; use crate::connect::BoxedSocket; @@ -236,8 +237,8 @@ impl WebsocketsRequest { if let Some(ref mut jar) = self.cookies { let mut cookie = String::new(); for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(c.name().as_bytes(), USERINFO); + let value = percent_encode(c.value().as_bytes(), USERINFO); let _ = write!(&mut cookie, "; {}={}", name, value); } self.head.headers.insert( diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 33314421..c3fe5b28 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,11 @@ # Changes + +### Changed + +* Update serde_urlencoded to "0.6.1" + + ## [0.2.4] - 2019-07-18 * Update actix-server to 0.6 diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index fff96893..22809c06 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -49,7 +49,7 @@ serde = "1.0" serde_json = "1.0" sha1 = "0.6" slab = "0.4" -serde_urlencoded = "0.5.3" +serde_urlencoded = "0.6.1" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" From b1cb72d08803a6cb6d53dc2ed23a0dede058bff1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 13 Aug 2019 11:03:24 -0700 Subject: [PATCH 1541/1635] update url crate --- CHANGES.md | 4 +++- Cargo.toml | 4 ++-- actix-files/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 62eb0ef9..3aadc8f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,7 +12,9 @@ * `Query` payload made `pub`. Allows user to pattern-match the payload. -* Update serde_urlencoded to "0.6.1" +* Update serde_urlencoded to 0.6.1 + +* Update url to 2.1 ## [1.0.5] - 2019-07-18 diff --git a/Cargo.toml b/Cargo.toml index 49889def..3e4d6fde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ actix-utils = "0.4.4" actix-router = "0.1.5" actix-rt = "0.2.4" actix-web-codegen = "0.1.2" -actix-http = "0.2.7" +actix-http = "0.2.9" actix-server = "0.6.0" actix-server-config = "0.1.2" actix-threadpool = "0.1.1" @@ -98,7 +98,7 @@ serde = { version = "1.0", features=["derive"] } serde_json = "1.0" serde_urlencoded = "0.6.1" time = "0.1.42" -url = { version="1.7", features=["query_encoding"] } +url = "2.1" # ssl support openssl = { version="0.10", optional = true } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index a25ce17b..8f36cddc 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "1.0.2", default-features = false } -actix-http = "0.2.4" +actix-http = "0.2.9" actix-service = "0.4.1" bitflags = "1" bytes = "0.4" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 42534cb1..eb81cbdd 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -44,7 +44,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-http = "0.2.8" +actix-http = "0.2.9" base64 = "0.10.1" bytes = "0.4" derive_more = "0.15.0" From 5d248cad89758986bc27ada8744084202debd4cd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 13 Aug 2019 12:28:05 -0700 Subject: [PATCH 1542/1635] prep release --- awc/CHANGES.md | 2 +- awc/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 5e012def..5edfc5e3 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.2.4] - 2019-xx-xx +## [0.2.4] - 2019-08-13 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index eb81cbdd..7f42501e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.3" +version = "0.2.4" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" From 979c4d44f4626e4346714753106d2e94c9b845d1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 13 Aug 2019 12:41:26 -0700 Subject: [PATCH 1543/1635] update awc dep --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3e4d6fde..7c630cc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ actix-http = "0.2.9" actix-server = "0.6.0" actix-server-config = "0.1.2" actix-threadpool = "0.1.1" -awc = { version = "0.2.2", optional = true } +awc = { version = "0.2.4", optional = true } bytes = "0.4" derive_more = "0.15.0" From 87b71624734b57f416c234ee8a24b422a2a21b44 Mon Sep 17 00:00:00 2001 From: Roberto Huertas Date: Fri, 16 Aug 2019 02:21:30 +0200 Subject: [PATCH 1544/1635] chore(readme): fix copy paste error (#1040) Fix actix-cors README --- actix-cors/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-cors/README.md b/actix-cors/README.md index 980d98ca..a77f6c6d 100644 --- a/actix-cors/README.md +++ b/actix-cors/README.md @@ -1,4 +1,4 @@ -# Identity service for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-identity)](https://crates.io/crates/actix-identity) [![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) +# Cors Middleware for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-cors)](https://crates.io/crates/actix-cors) [![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) ## Documentation & community resources From 23d768a77b42c5df45ef76bbc1f84cfee62ee09c Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sat, 17 Aug 2019 02:45:44 +0900 Subject: [PATCH 1545/1635] Add explicit `dyn`s (#1041) * Add explicit `dyn`s * Remove unnecessary lines --- actix-http/src/client/connector.rs | 8 ++++---- actix-http/src/error.rs | 2 +- src/middleware/normalize.rs | 1 - src/resource.rs | 1 - src/types/form.rs | 1 - 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 7d6fca25..98e8618c 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -269,9 +269,9 @@ where .map(|protos| protos.windows(2).any(|w| w == H2)) .unwrap_or(false); if h2 { - (Box::new(sock) as Box, Protocol::Http2) + (Box::new(sock) as Box, Protocol::Http2) } else { - (Box::new(sock) as Box, Protocol::Http1) + (Box::new(sock) as Box, Protocol::Http1) } }), ), @@ -288,9 +288,9 @@ where .map(|protos| protos.windows(2).any(|w| w == H2)) .unwrap_or(false); if h2 { - (Box::new(sock) as Box, Protocol::Http2) + (Box::new(sock) as Box, Protocol::Http2) } else { - (Box::new(sock) as Box, Protocol::Http1) + (Box::new(sock) as Box, Protocol::Http1) } }), ), diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index dcbc3cc9..2c01c86d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1072,7 +1072,7 @@ mod tests { #[test] fn test_error_casting() { let err = PayloadError::Overflow; - let resp_err: &ResponseError = &err; + let resp_err: &dyn ResponseError = &err; let err = resp_err.downcast_ref::().unwrap(); assert_eq!(err.to_string(), "A payload reached size limit."); let not_err = resp_err.downcast_ref::(); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 427f954f..9cfbefb3 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -151,5 +151,4 @@ mod tests { let res = block_on(normalize.call(req)).unwrap(); assert!(res.status().is_success()); } - } diff --git a/src/resource.rs b/src/resource.rs index 0af43a42..b872049d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -763,5 +763,4 @@ mod tests { let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::NO_CONTENT); } - } diff --git a/src/types/form.rs b/src/types/form.rs index ec6e6cd0..9ab98b17 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -482,5 +482,4 @@ mod tests { use crate::responder::tests::BodyTest; assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); } - } From 61e492e7e31fa1543f475b3cde465c89cc77f3b7 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sun, 18 Aug 2019 10:39:22 +0900 Subject: [PATCH 1546/1635] Prepare actix-multipart 0.1.3 release --- actix-multipart/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index b0d8f285..27333f4c 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.3] - 2019-06-06 +## [0.1.3] - 2019-08-18 * Fix ring dependency from actix-web default features for #741. From a07cdd6533cb1e29c730da5811ba6d9928e3bbc9 Mon Sep 17 00:00:00 2001 From: Erlend Langseth Date: Fri, 23 Aug 2019 17:02:03 +0200 Subject: [PATCH 1547/1635] Data::into_inner --- CHANGES.md | 2 ++ src/data.rs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3aadc8f1..19232346 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ * Form immplements Responder, returning a `application/x-www-form-urlencoded` response +* Add `into_inner` to `Data` + ### Changed * `Query` payload made `pub`. Allows user to pattern-match the payload. diff --git a/src/data.rs b/src/data.rs index 3461d24f..14e293bc 100644 --- a/src/data.rs +++ b/src/data.rs @@ -77,6 +77,11 @@ impl Data { pub fn get_ref(&self) -> &T { self.0.as_ref() } + + /// Convert to the internal Arc + pub fn into_inner(self) -> Arc { + self.0 + } } impl Deref for Data { From c19313790547c7b7a3ea42d0a16b9fcfec208f05 Mon Sep 17 00:00:00 2001 From: Leland Jansen Date: Wed, 28 Aug 2019 08:32:17 -0700 Subject: [PATCH 1548/1635] actix_web::test::TestRequest::set_form (#1058) --- CHANGES.md | 3 +++ src/test.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 19232346..aeb270b9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,9 @@ * Add `into_inner` to `Data` +* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set + the header in test requests. + ### Changed * `Query` payload made `pub`. Allows user to pattern-match the payload. diff --git a/src/test.rs b/src/test.rs index 562fdf43..903679ca 100644 --- a/src/test.rs +++ b/src/test.rs @@ -478,6 +478,16 @@ impl TestRequest { self } + /// Serialize `data` to a URL encoded form and set it as the request payload. The `Content-Type` + /// header is set to `application/x-www-form-urlencoded`. + pub fn set_form(mut self, data: &T) -> Self { + let bytes = serde_urlencoded::to_string(data) + .expect("Failed to serialize test data as a urlencoded form"); + self.req.set_payload(bytes); + self.req.set(ContentType::form_url_encoded()); + self + } + /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is /// set to `application/json`. pub fn set_json(mut self, data: &T) -> Self { @@ -670,6 +680,31 @@ mod tests { assert_eq!(&result.id, "12345"); } + #[test] + fn test_request_response_form() { + let mut app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Form| { + HttpResponse::Ok().json(person.into_inner()) + }), + ))); + + let payload = Person { + id: "12345".to_string(), + name: "User name".to_string(), + }; + + let req = TestRequest::post() + .uri("/people") + .set_form(&payload) + .to_request(); + + assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); + + let result: Person = read_response_json(&mut app, req); + assert_eq!(&result.id, "12345"); + assert_eq!(&result.name, "User name"); + } + #[test] fn test_request_response_json() { let mut app = init_service(App::new().service(web::resource("/people").route( From 98bf8ab0984356a78c15b2a02d8974e0063867eb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Aug 2019 21:40:24 +0600 Subject: [PATCH 1549/1635] enable rust-tls feature for actix_web::client #1045 --- CHANGES.md | 4 +++- Cargo.toml | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index aeb270b9..158c8dc9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.6] - 2019-xx-xx +## [1.0.6] - 2019-08-28 ### Added @@ -17,6 +17,8 @@ * `Query` payload made `pub`. Allows user to pattern-match the payload. +* Enable `rust-tls` feature for client #1045 + * Update serde_urlencoded to 0.6.1 * Update url to 2.1 diff --git a/Cargo.toml b/Cargo.toml index 7c630cc7..5a73bf26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.5" +version = "1.0.6" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -66,7 +66,7 @@ fail = ["actix-http/fail"] ssl = ["openssl", "actix-server/ssl", "awc/ssl"] # rustls -rust-tls = ["rustls", "actix-server/rust-tls"] +rust-tls = ["rustls", "actix-server/rust-tls", "awc/rust-tls"] # unix domain sockets support uds = ["actix-server/uds"] From 616981ecf9a5be5de467f9d9753ca47aa44c9d21 Mon Sep 17 00:00:00 2001 From: Philip Jenvey Date: Wed, 28 Aug 2019 20:35:05 -0700 Subject: [PATCH 1550/1635] clear extensions before reclaiming HttpRequests in their pool (#1063) Issue #1062 --- src/request.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/request.rs b/src/request.rs index 0fc0647f..ac9b9933 100644 --- a/src/request.rs +++ b/src/request.rs @@ -259,6 +259,7 @@ impl Drop for HttpRequest { if Rc::strong_count(&self.0) == 1 { let v = &mut self.0.pool.0.borrow_mut(); if v.len() < 128 { + self.extensions_mut().clear(); v.push(self.0.clone()); } } @@ -494,4 +495,36 @@ mod tests { let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } + + #[test] + fn test_extensions_dropped() { + struct Tracker { + pub dropped: bool, + } + struct Foo { + tracker: Rc>, + } + impl Drop for Foo { + fn drop(&mut self) { + self.tracker.borrow_mut().dropped = true; + } + } + + let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); + { + let tracker2 = Rc::clone(&tracker); + let mut srv = init_service(App::new().data(10u32).service( + web::resource("/").to(move |req: HttpRequest| { + req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2) }); + HttpResponse::Ok() + }), + )); + + let req = TestRequest::default().to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + } + + assert!(tracker.borrow().dropped); + } } From bae29897d695f70860ab0134fea1f88180560bae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 29 Aug 2019 09:36:16 +0600 Subject: [PATCH 1551/1635] prep actix-web release --- CHANGES.md | 7 +++++++ Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 158c8dc9..5f8f489f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [1.0.7] - 2019-08-29 + +### Fixed + +* Request Extensions leak #1062 + + ## [1.0.6] - 2019-08-28 ### Added diff --git a/Cargo.toml b/Cargo.toml index 5a73bf26..c2d3b0d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.6" +version = "1.0.7" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" From 63ddd30ee44c710721b53c4d349c5d65655f217e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 1 Sep 2019 13:15:02 +0600 Subject: [PATCH 1552/1635] on_connect result isnt added to request extensions for http2 requests #1009 --- actix-http/CHANGES.md | 7 ++++++ actix-http/src/builder.rs | 1 + actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h2/dispatcher.rs | 7 ++++++ actix-http/tests/test_server.rs | 16 ++++++++++++++ actix-http/tests/test_ssl_server.rs | 33 +++++++++++++++++++++++++---- src/request.rs | 4 +++- 7 files changed, 64 insertions(+), 6 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c8d1b2ae..c7cdcf0a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.2.10] - 2019-09-xx + +### Fixed + +* on_connect result isn't added to request extensions for http2 requests #1009 + + ## [0.2.9] - 2019-08-13 ### Changed diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index bab0f5e1..cd23b726 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -199,6 +199,7 @@ where self.client_disconnect, ); H2Service::with_config(cfg, service.into_new_service()) + .on_connect(self.on_connect) } /// Finish service configuration and create `HttpService` instance. diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 5e9c0b53..c82eb4ac 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -502,7 +502,7 @@ where let pl = self.codec.message_type(); req.head_mut().peer_addr = self.peer_addr; - // on_connect data + // set on_connect data if let Some(ref on_connect) = self.on_connect { on_connect.set(&mut req.extensions_mut()); } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 2bd7940d..69c620e6 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -23,6 +23,7 @@ use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::helpers::DataFactory; +use crate::httpmessage::HttpMessage; use crate::message::ResponseHead; use crate::payload::Payload; use crate::request::Request; @@ -122,6 +123,12 @@ where head.version = parts.version; head.headers = parts.headers.into(); head.peer_addr = self.peer_addr; + + // set on_connect data + if let Some(ref on_connect) = self.on_connect { + on_connect.set(&mut req.extensions_mut()); + } + tokio_current_thread::spawn(ServiceResponse:: { state: ServiceResponseState::ServiceCall( self.service.call(req), diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index a74fbb15..a31e4ac8 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -11,6 +11,7 @@ use futures::stream::{once, Stream}; use regex::Regex; use tokio_timer::sleep; +use actix_http::httpmessage::HttpMessage; use actix_http::{ body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, }; @@ -602,3 +603,18 @@ fn test_h1_service_error() { let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(b"error")); } + +#[test] +fn test_h1_on_connect() { + let mut srv = TestServer::new(|| { + HttpService::build() + .on_connect(|_| 10usize) + .h1(|req: Request| { + assert!(req.extensions().contains::()); + future::ok::<_, ()>(Response::Ok().finish()) + }) + }); + + let response = srv.block_on(srv.get("/").send()).unwrap(); + assert!(response.status().is_success()); +} diff --git a/actix-http/tests/test_ssl_server.rs b/actix-http/tests/test_ssl_server.rs index 0b85f33f..f0c82870 100644 --- a/actix-http/tests/test_ssl_server.rs +++ b/actix-http/tests/test_ssl_server.rs @@ -1,9 +1,5 @@ #![cfg(feature = "ssl")] use actix_codec::{AsyncRead, AsyncWrite}; -use actix_http::error::{ErrorBadRequest, PayloadError}; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::{body, Error, HttpService, Request, Response}; use actix_http_test::TestServer; use actix_server::ssl::OpensslAcceptor; use actix_server_config::ServerConfig; @@ -15,6 +11,12 @@ use futures::stream::{once, Stream}; use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; use std::io::Result; +use actix_http::error::{ErrorBadRequest, PayloadError}; +use actix_http::http::header::{self, HeaderName, HeaderValue}; +use actix_http::http::{Method, StatusCode, Version}; +use actix_http::httpmessage::HttpMessage; +use actix_http::{body, Error, HttpService, Request, Response}; + fn load_body(stream: S) -> impl Future where S: Stream, @@ -453,3 +455,26 @@ fn test_h2_service_error() { let bytes = srv.load_body(response).unwrap(); assert!(bytes.is_empty()); } + +#[test] +fn test_h2_on_connect() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .on_connect(|_| 10usize) + .h2(|req: Request| { + assert!(req.extensions().contains::()); + ok::<_, ()>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); +} diff --git a/src/request.rs b/src/request.rs index ac9b9933..6d9d26e8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -515,7 +515,9 @@ mod tests { let tracker2 = Rc::clone(&tracker); let mut srv = init_service(App::new().data(10u32).service( web::resource("/").to(move |req: HttpRequest| { - req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2) }); + req.extensions_mut().insert(Foo { + tracker: Rc::clone(&tracker2), + }); HttpResponse::Ok() }), )); From c9400456f665fb30d61031289570aafae01b1b61 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Sep 2019 15:20:28 -0700 Subject: [PATCH 1553/1635] update actix-connect ver --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 79d7117b..290a8fba 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -49,7 +49,7 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.4.1" actix-codec = "0.1.2" -actix-connect = "0.2.2" +actix-connect = "0.2.4" actix-utils = "0.4.4" actix-server-config = "0.1.2" actix-threadpool = "0.1.1" From 8a9fcddb3cad417bba5119111e044bb6ff11771d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=BAeen?= <3han5chou7@gmail.com> Date: Mon, 9 Sep 2019 15:26:38 +0900 Subject: [PATCH 1554/1635] Condition middleware (#1075) * add condition middleware * write tests * update changes * Update src/middleware/condition.rs Co-Authored-By: Yuki Okushi * Update src/middleware/condition.rs Co-Authored-By: Yuki Okushi * Update src/middleware/condition.rs Co-Authored-By: Yuki Okushi * Update src/middleware/condition.rs Co-Authored-By: Yuki Okushi --- CHANGES.md | 6 ++ src/middleware/condition.rs | 143 ++++++++++++++++++++++++++++++++++++ src/middleware/mod.rs | 2 + 3 files changed, 151 insertions(+) create mode 100644 src/middleware/condition.rs diff --git a/CHANGES.md b/CHANGES.md index 5f8f489f..57304d08 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,10 @@ # Changes +## not released yet + +### Added + +* Add `middleware::Conditon` that conditionally enables another middleware + ## [1.0.7] - 2019-08-29 diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs new file mode 100644 index 00000000..ddc5fdd4 --- /dev/null +++ b/src/middleware/condition.rs @@ -0,0 +1,143 @@ +//! `Middleware` for conditionally enables another middleware. +use actix_service::{Service, Transform}; +use futures::future::{ok, Either, FutureResult, Map}; +use futures::{Future, Poll}; + +/// `Middleware` for conditionally enables another middleware. +/// The controled middleware must not change the `Service` interfaces. +/// This means you cannot control such middlewares like `Logger` or `Compress`. +/// +/// ## Usage +/// +/// ```rust +/// use actix_web::middleware::{Condition, NormalizePath}; +/// use actix_web::App; +/// +/// fn main() { +/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into()); +/// let app = App::new() +/// .wrap(Condition::new(enable_normalize, NormalizePath)); +/// } +/// ``` +pub struct Condition { + trans: T, + enable: bool, +} + +impl Condition { + pub fn new(enable: bool, trans: T) -> Self { + Self { trans, enable } + } +} + +impl Transform for Condition +where + S: Service, + T: Transform, +{ + type Request = S::Request; + type Response = S::Response; + type Error = S::Error; + type InitError = T::InitError; + type Transform = ConditionMiddleware; + type Future = Either< + Map Self::Transform>, + FutureResult, + >; + + fn new_transform(&self, service: S) -> Self::Future { + if self.enable { + let f = self + .trans + .new_transform(service) + .map(ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform); + Either::A(f) + } else { + Either::B(ok(ConditionMiddleware::Disable(service))) + } + } +} + +pub enum ConditionMiddleware { + Enable(E), + Disable(D), +} + +impl Service for ConditionMiddleware +where + E: Service, + D: Service, +{ + type Request = E::Request; + type Response = E::Response; + type Error = E::Error; + type Future = Either; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + use ConditionMiddleware::*; + match self { + Enable(service) => service.poll_ready(), + Disable(service) => service.poll_ready(), + } + } + + fn call(&mut self, req: E::Request) -> Self::Future { + use ConditionMiddleware::*; + match self { + Enable(service) => Either::A(service.call(req)), + Disable(service) => Either::B(service.call(req)), + } + } +} + +#[cfg(test)] +mod tests { + use actix_service::IntoService; + + use super::*; + use crate::dev::{ServiceRequest, ServiceResponse}; + use crate::error::Result; + use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; + use crate::middleware::errhandlers::*; + use crate::test::{self, TestRequest}; + use crate::HttpResponse; + + fn render_500(mut res: ServiceResponse) -> Result> { + res.response_mut() + .headers_mut() + .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); + Ok(ErrorHandlerResponse::Response(res)) + } + + #[test] + fn test_handler_enabled() { + let srv = |req: ServiceRequest| { + req.into_response(HttpResponse::InternalServerError().finish()) + }; + + let mw = + ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + + let mut mw = + test::block_on(Condition::new(true, mw).new_transform(srv.into_service())) + .unwrap(); + let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request()); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + } + #[test] + fn test_handler_disabled() { + let srv = |req: ServiceRequest| { + req.into_response(HttpResponse::InternalServerError().finish()) + }; + + let mw = + ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + + let mut mw = + test::block_on(Condition::new(false, mw).new_transform(srv.into_service())) + .unwrap(); + + let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request()); + assert_eq!(resp.headers().get(CONTENT_TYPE), None); + } +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 814993f0..311d0ee9 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -6,7 +6,9 @@ mod defaultheaders; pub mod errhandlers; mod logger; mod normalize; +mod condition; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; pub use self::normalize::NormalizePath; +pub use self::condition::Condition; From 8d61fe09257a2439a00c737728f6a26a5111c1cb Mon Sep 17 00:00:00 2001 From: Eugene Bulkin Date: Mon, 9 Sep 2019 01:27:13 -0500 Subject: [PATCH 1555/1635] Ensure that awc::ws::WebsocketsRequest sets the Host header (#1070) * Ensure that awc::ws::WebsocketsRequest sets the Host header before connecting. * Make sure to check if headers already have a HOST value before setting * Update CHANGES.md to reflect WebSocket client update. --- awc/CHANGES.md | 6 ++++++ awc/src/ws.rs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 5edfc5e3..5442e9db 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [0.2.5] - 2019-09-06 + +### Changed + +* Ensure that the `Host` header is set when initiating a WebSocket client connection. + ## [0.2.4] - 2019-08-13 ### Changed diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 72c9a38b..67be9e9d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -233,6 +233,10 @@ impl WebsocketsRequest { return Either::A(err(InvalidUrl::UnknownScheme.into())); } + if !self.head.headers.contains_key(header::HOST) { + self.head.headers.insert(header::HOST, HeaderValue::from_str(uri.host().unwrap()).unwrap()); + } + // set cookies if let Some(ref mut jar) = self.cookies { let mut cookie = String::new(); From 1d96ae9bc372a9bb857cdb105fc89314e4ddc6c8 Mon Sep 17 00:00:00 2001 From: Jeffrey Shen Date: Mon, 9 Sep 2019 17:58:00 +1000 Subject: [PATCH 1556/1635] actix-multipart: Correctly parse multipart body which does not end in CRLF (#1042) * Correctly parse multipart body which does not end in CRLF * Add in an eof guard for extra safety --- actix-multipart/CHANGES.md | 3 + actix-multipart/src/server.rs | 118 ++++++++++++++++++++-------------- 2 files changed, 73 insertions(+), 48 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 27333f4c..365dca28 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,4 +1,7 @@ # Changes +## [0.1.4] - 2019-xx-xx + +* Multipart handling now parses requests which do not end in CRLF #1038 ## [0.1.3] - 2019-08-18 diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index e2111bb7..3312a580 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -167,7 +167,7 @@ impl InnerMultipart { boundary: &str, ) -> Result, MultipartError> { // TODO: need to read epilogue - match payload.readline()? { + match payload.readline_or_eof()? { None => { if payload.eof { Ok(Some(true)) @@ -176,16 +176,15 @@ impl InnerMultipart { } } Some(chunk) => { - if chunk.len() == boundary.len() + 4 - && &chunk[..2] == b"--" - && &chunk[2..boundary.len() + 2] == boundary.as_bytes() - { + if chunk.len() < boundary.len() + 4 + || &chunk[..2] != b"--" + || &chunk[2..boundary.len() + 2] != boundary.as_bytes() { + Err(MultipartError::Boundary) + } else if &chunk[boundary.len() + 2..] == b"\r\n" { Ok(Some(false)) - } else if chunk.len() == boundary.len() + 6 - && &chunk[..2] == b"--" - && &chunk[2..boundary.len() + 2] == boundary.as_bytes() - && &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" - { + } else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" + && (chunk.len() == boundary.len() + 4 + || &chunk[boundary.len() + 4..] == b"\r\n") { Ok(Some(true)) } else { Err(MultipartError::Boundary) @@ -779,6 +778,14 @@ impl PayloadBuffer { self.read_until(b"\n") } + /// Read bytes until new line delimiter or eof + pub fn readline_or_eof(&mut self) -> Result, MultipartError> { + match self.readline() { + Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.take().freeze())), + line => line + } + } + /// Put unprocessed data back to the buffer pub fn unprocessed(&mut self, data: Bytes) { let buf = BytesMut::from(data); @@ -849,32 +856,65 @@ mod tests { (tx, rx.map_err(|_| panic!()).and_then(|res| res)) } + fn create_simple_request_with_header() -> (Bytes, HeaderMap) { + let bytes = Bytes::from( + "testasdadsad\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ + Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ + test\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ + data\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n" + ); + let mut headers = HeaderMap::new(); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static( + "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", + ), + ); + (bytes, headers) + } + + #[test] + fn test_multipart_no_end_crlf() { + run_on(|| { + let (sender, payload) = create_stream(); + let (bytes, headers) = create_simple_request_with_header(); + let bytes_stripped = bytes.slice_to(bytes.len()); // strip crlf + + sender.unbounded_send(Ok(bytes_stripped)).unwrap(); + drop(sender); // eof + + let mut multipart = Multipart::new(&headers, payload); + + match multipart.poll().unwrap() { + Async::Ready(Some(_)) => (), + _ => unreachable!(), + } + + match multipart.poll().unwrap() { + Async::Ready(Some(_)) => (), + _ => unreachable!(), + } + + match multipart.poll().unwrap() { + Async::Ready(None) => (), + _ => unreachable!(), + } + }) + } + #[test] fn test_multipart() { run_on(|| { let (sender, payload) = create_stream(); + let (bytes, headers) = create_simple_request_with_header(); - let bytes = Bytes::from( - "testasdadsad\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - test\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - data\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n", - ); sender.unbounded_send(Ok(bytes)).unwrap(); - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static( - "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", - ), - ); - let mut multipart = Multipart::new(&headers, payload); match multipart.poll().unwrap() { Async::Ready(Some(mut field)) => { @@ -925,28 +965,10 @@ mod tests { fn test_stream() { run_on(|| { let (sender, payload) = create_stream(); + let (bytes, headers) = create_simple_request_with_header(); - let bytes = Bytes::from( - "testasdadsad\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ - Content-Type: text/plain; charset=utf-8\r\n\r\n\ - test\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Type: text/plain; charset=utf-8\r\n\r\n\ - data\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n", - ); sender.unbounded_send(Ok(bytes)).unwrap(); - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static( - "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", - ), - ); - let mut multipart = Multipart::new(&headers, payload); match multipart.poll().unwrap() { Async::Ready(Some(mut field)) => { From 5e8f1c338c2a45544359d03ce6ea5a3b0c0a3cfb Mon Sep 17 00:00:00 2001 From: Ronald Chan Date: Mon, 9 Sep 2019 18:24:57 +0800 Subject: [PATCH 1557/1635] fix h2 not using error response (#1080) * fix h2 not using error response * add fix change log * fix h2 service error tests --- CHANGES.md | 3 +++ actix-http/src/h2/dispatcher.rs | 4 ++-- actix-http/tests/test_rustls_server.rs | 4 ++-- actix-http/tests/test_ssl_server.rs | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 57304d08..f37f8b46 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,9 @@ * Add `middleware::Conditon` that conditionally enables another middleware +### Fixed + +* h2 will use error response #1080 ## [1.0.7] - 2019-08-29 diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 69c620e6..888f9065 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -257,8 +257,8 @@ where } } Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_e) => { - let res: Response = Response::InternalServerError().finish(); + Err(e) => { + let res: Response = e.into().into(); let (res, body) = res.replace_body(()); let mut send = send.take().unwrap(); diff --git a/actix-http/tests/test_rustls_server.rs b/actix-http/tests/test_rustls_server.rs index 32b33fce..b74fd07b 100644 --- a/actix-http/tests/test_rustls_server.rs +++ b/actix-http/tests/test_rustls_server.rs @@ -454,9 +454,9 @@ fn test_h2_service_error() { }); let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); // read response let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); + assert_eq!(bytes, Bytes::from_static(b"error")); } diff --git a/actix-http/tests/test_ssl_server.rs b/actix-http/tests/test_ssl_server.rs index f0c82870..897d92b3 100644 --- a/actix-http/tests/test_ssl_server.rs +++ b/actix-http/tests/test_ssl_server.rs @@ -449,11 +449,11 @@ fn test_h2_service_error() { }); let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); // read response let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); + assert_eq!(bytes, Bytes::from_static(b"error")); } #[test] From 8873e9b39ed776249e756195e46a5f295760146c Mon Sep 17 00:00:00 2001 From: Dmitry Pypin Date: Mon, 9 Sep 2019 21:29:32 -0700 Subject: [PATCH 1558/1635] Added FrozenClientRequest for easier retrying HTTP calls (#1064) * Initial commit * Added extra_headers * Added freeze() method to ClientRequest which produces a 'read-only' copy of a request suitable for retrying the send operation * Additional methods for FrozenClientRequest * Fix * Increased crates versions * Fixed a unit test. Added one more unit test. * Added RequestHeaderWrapper * Small fixes * Renamed RequestHeadWrapper->RequestHeadType * Updated CHANGES.md files * Small fix * Small changes * Removed *_extra methods from Connection trait * Added FrozenSendBuilder * Added FrozenSendBuilder * Minor fix * Replaced impl Future with concrete Future implementation * Small renaming * Renamed Send->SendBody --- actix-http/CHANGES.md | 7 + actix-http/src/client/connection.rs | 26 +- actix-http/src/client/error.rs | 20 + actix-http/src/client/h1proto.rs | 29 +- actix-http/src/client/h2proto.rs | 26 +- actix-http/src/client/mod.rs | 2 +- actix-http/src/h1/client.rs | 18 +- actix-http/src/h1/encoder.rs | 90 +++- actix-http/src/lib.rs | 2 +- actix-http/src/message.rs | 30 ++ awc/CHANGES.md | 8 + awc/src/connect.rs | 78 +++- awc/src/error.rs | 2 +- awc/src/request.rs | 674 +++++++++++++++++++++++----- 14 files changed, 828 insertions(+), 184 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c7cdcf0a..80da691c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## + +### Added + +* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` + + ## [0.2.10] - 2019-09-xx ### Fixed diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 36913c5f..d2b94b3e 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -8,7 +8,7 @@ use h2::client::SendRequest; use crate::body::MessageBody; use crate::h1::ClientCodec; -use crate::message::{RequestHead, ResponseHead}; +use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; use super::error::SendRequestError; @@ -27,9 +27,9 @@ pub trait Connection { fn protocol(&self) -> Protocol; /// Send request and body - fn send_request( + fn send_request>( self, - head: RequestHead, + head: H, body: B, ) -> Self::Future; @@ -39,7 +39,7 @@ pub trait Connection { >; /// Send request, returns Response and Framed - fn open_tunnel(self, head: RequestHead) -> Self::TunnelFuture; + fn open_tunnel>(self, head: H) -> Self::TunnelFuture; } pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { @@ -105,22 +105,22 @@ where } } - fn send_request( + fn send_request>( mut self, - head: RequestHead, + head: H, body: B, ) -> Self::Future { match self.io.take().unwrap() { ConnectionType::H1(io) => Box::new(h1proto::send_request( io, - head, + head.into(), body, self.created, self.pool, )), ConnectionType::H2(io) => Box::new(h2proto::send_request( io, - head, + head.into(), body, self.created, self.pool, @@ -139,10 +139,10 @@ where >; /// Send request, returns Response and Framed - fn open_tunnel(mut self, head: RequestHead) -> Self::TunnelFuture { + fn open_tunnel>(mut self, head: H) -> Self::TunnelFuture { match self.io.take().unwrap() { ConnectionType::H1(io) => { - Either::A(Box::new(h1proto::open_tunnel(io, head))) + Either::A(Box::new(h1proto::open_tunnel(io, head.into()))) } ConnectionType::H2(io) => { if let Some(mut pool) = self.pool.take() { @@ -180,9 +180,9 @@ where } } - fn send_request( + fn send_request>( self, - head: RequestHead, + head: H, body: RB, ) -> Self::Future { match self { @@ -199,7 +199,7 @@ where >; /// Send request, returns Response and Framed - fn open_tunnel(self, head: RequestHead) -> Self::TunnelFuture { + fn open_tunnel>(self, head: H) -> Self::TunnelFuture { match self { EitherConnection::A(con) => Box::new( con.open_tunnel(head) diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index fc4b5b72..40aef2cc 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -128,3 +128,23 @@ impl ResponseError for SendRequestError { .into() } } + +/// A set of errors that can occur during freezing a request +#[derive(Debug, Display, From)] +pub enum FreezeRequestError { + /// Invalid URL + #[display(fmt = "Invalid URL: {}", _0)] + Url(InvalidUrl), + /// Http error + #[display(fmt = "{}", _0)] + Http(HttpError), +} + +impl From for SendRequestError { + fn from(e: FreezeRequestError) -> Self { + match e { + FreezeRequestError::Url(e) => e.into(), + FreezeRequestError::Http(e) => e.into(), + } + } +} \ No newline at end of file diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 97ed3bbc..fa920ab9 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -9,8 +9,9 @@ use futures::{Async, Future, Poll, Sink, Stream}; use crate::error::PayloadError; use crate::h1; use crate::http::header::{IntoHeaderValue, HOST}; -use crate::message::{RequestHead, ResponseHead}; +use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; +use crate::header::HeaderMap; use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectError, SendRequestError}; @@ -19,7 +20,7 @@ use crate::body::{BodySize, MessageBody}; pub(crate) fn send_request( io: T, - mut head: RequestHead, + mut head: RequestHeadType, body: B, created: time::Instant, pool: Option>, @@ -29,21 +30,29 @@ where B: MessageBody, { // set request host header - if !head.headers.contains_key(HOST) { - if let Some(host) = head.uri.host() { + if !head.as_ref().headers.contains_key(HOST) && !head.extra_headers().iter().any(|h| h.contains_key(HOST)) { + if let Some(host) = head.as_ref().uri.host() { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - let _ = match head.uri.port_u16() { + let _ = match head.as_ref().uri.port_u16() { None | Some(80) | Some(443) => write!(wrt, "{}", host), Some(port) => write!(wrt, "{}:{}", host, port), }; match wrt.get_mut().take().freeze().try_into() { Ok(value) => { - head.headers.insert(HOST, value); + match head { + RequestHeadType::Owned(ref mut head) => { + head.headers.insert(HOST, value) + }, + RequestHeadType::Rc(_, ref mut extra_headers) => { + let headers = extra_headers.get_or_insert(HeaderMap::new()); + headers.insert(HOST, value) + }, + } } Err(e) => { - log::error!("Can not set HOST header {}", e); + log::error!("Can not set HOST header {}", e) } } } @@ -57,7 +66,7 @@ where let len = body.size(); - // create Framed and send reqest + // create Framed and send request Framed::new(io, h1::ClientCodec::default()) .send((head, len).into()) .from_err() @@ -95,12 +104,12 @@ where pub(crate) fn open_tunnel( io: T, - head: RequestHead, + head: RequestHeadType, ) -> impl Future), Error = SendRequestError> where T: AsyncRead + AsyncWrite + 'static, { - // create Framed and send reqest + // create Framed and send request Framed::new(io, h1::ClientCodec::default()) .send((head, BodySize::None).into()) .from_err() diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 91240268..2993d89d 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -9,8 +9,9 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; use crate::body::{BodySize, MessageBody}; -use crate::message::{RequestHead, ResponseHead}; +use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; +use crate::header::HeaderMap; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; @@ -18,7 +19,7 @@ use super::pool::Acquired; pub(crate) fn send_request( io: SendRequest, - head: RequestHead, + head: RequestHeadType, body: B, created: time::Instant, pool: Option>, @@ -28,7 +29,7 @@ where B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.size()); - let head_req = head.method == Method::HEAD; + let head_req = head.as_ref().method == Method::HEAD; let length = body.size(); let eof = match length { BodySize::None | BodySize::Empty | BodySize::Sized(0) => true, @@ -39,8 +40,8 @@ where .map_err(SendRequestError::from) .and_then(move |mut io| { let mut req = Request::new(()); - *req.uri_mut() = head.uri; - *req.method_mut() = head.method; + *req.uri_mut() = head.as_ref().uri.clone(); + *req.method_mut() = head.as_ref().method.clone(); *req.version_mut() = Version::HTTP_2; let mut skip_len = true; @@ -66,8 +67,21 @@ where ), }; + // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. + let (head, extra_headers) = match head { + RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()), + RequestHeadType::Rc(head, extra_headers) => (RequestHeadType::Rc(head, None), extra_headers.unwrap_or(HeaderMap::new())), + }; + + // merging headers from head and extra headers. + let headers = head.as_ref().headers.iter() + .filter(|(name, _)| { + !extra_headers.contains_key(*name) + }) + .chain(extra_headers.iter()); + // copy headers - for (key, value) in head.headers.iter() { + for (key, value) in headers { match *key { CONNECTION | TRANSFER_ENCODING => continue, // http2 specific CONTENT_LENGTH if skip_len => continue, diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index 1d10117c..04427ce4 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -10,7 +10,7 @@ mod pool; pub use self::connection::Connection; pub use self::connector::Connector; -pub use self::error::{ConnectError, InvalidUrl, SendRequestError}; +pub use self::error::{ConnectError, InvalidUrl, SendRequestError, FreezeRequestError}; pub use self::pool::Protocol; #[derive(Clone)] diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index f93bc496..c0bbcc69 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -1,5 +1,6 @@ #![allow(unused_imports, unused_variables, dead_code)] use std::io::{self, Write}; +use std::rc::Rc; use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; @@ -16,7 +17,8 @@ use crate::body::BodySize; use crate::config::ServiceConfig; use crate::error::{ParseError, PayloadError}; use crate::helpers; -use crate::message::{ConnectionType, Head, MessagePool, RequestHead, ResponseHead}; +use crate::message::{ConnectionType, Head, MessagePool, RequestHead, RequestHeadType, ResponseHead}; +use crate::header::HeaderMap; bitflags! { struct Flags: u8 { @@ -48,7 +50,7 @@ struct ClientCodecInner { // encoder part flags: Flags, headers_size: u32, - encoder: encoder::MessageEncoder, + encoder: encoder::MessageEncoder, } impl Default for ClientCodec { @@ -183,7 +185,7 @@ impl Decoder for ClientPayloadCodec { } impl Encoder for ClientCodec { - type Item = Message<(RequestHead, BodySize)>; + type Item = Message<(RequestHeadType, BodySize)>; type Error = io::Error; fn encode( @@ -192,13 +194,13 @@ impl Encoder for ClientCodec { dst: &mut BytesMut, ) -> Result<(), Self::Error> { match item { - Message::Item((mut msg, length)) => { + Message::Item((mut head, length)) => { let inner = &mut self.inner; - inner.version = msg.version; - inner.flags.set(Flags::HEAD, msg.method == Method::HEAD); + inner.version = head.as_ref().version; + inner.flags.set(Flags::HEAD, head.as_ref().method == Method::HEAD); // connection status - inner.ctype = match msg.connection_type() { + inner.ctype = match head.as_ref().connection_type() { ConnectionType::KeepAlive => { if inner.flags.contains(Flags::KEEPALIVE_ENABLED) { ConnectionType::KeepAlive @@ -212,7 +214,7 @@ impl Encoder for ClientCodec { inner.encoder.encode( dst, - &mut msg, + &mut head, false, false, inner.version, diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 61ca48b1..380dfe32 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -4,6 +4,7 @@ use std::io::Write; use std::marker::PhantomData; use std::str::FromStr; use std::{cmp, fmt, io, mem}; +use std::rc::Rc; use bytes::{BufMut, Bytes, BytesMut}; @@ -15,7 +16,7 @@ use crate::http::header::{ HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, }; use crate::http::{HeaderMap, Method, StatusCode, Version}; -use crate::message::{ConnectionType, Head, RequestHead, ResponseHead}; +use crate::message::{ConnectionType, Head, RequestHead, ResponseHead, RequestHeadType}; use crate::request::Request; use crate::response::Response; @@ -43,6 +44,8 @@ pub(crate) trait MessageType: Sized { fn headers(&self) -> &HeaderMap; + fn extra_headers(&self) -> Option<&HeaderMap>; + fn camel_case(&self) -> bool { false } @@ -128,12 +131,21 @@ pub(crate) trait MessageType: Sized { _ => (), } + // merging headers from head and extra headers. HeaderMap::new() does not allocate. + let empty_headers = HeaderMap::new(); + let extra_headers = self.extra_headers().unwrap_or(&empty_headers); + let headers = self.headers().inner.iter() + .filter(|(name, _)| { + !extra_headers.contains_key(*name) + }) + .chain(extra_headers.inner.iter()); + // write headers let mut pos = 0; let mut has_date = false; let mut remaining = dst.remaining_mut(); let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) }; - for (key, value) in self.headers().inner.iter() { + for (key, value) in headers { match *key { CONNECTION => continue, TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, @@ -235,6 +247,10 @@ impl MessageType for Response<()> { &self.head().headers } + fn extra_headers(&self) -> Option<&HeaderMap> { + None + } + fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { let head = self.head(); let reason = head.reason().as_bytes(); @@ -247,31 +263,36 @@ impl MessageType for Response<()> { } } -impl MessageType for RequestHead { +impl MessageType for RequestHeadType { fn status(&self) -> Option { None } fn chunked(&self) -> bool { - self.chunked() + self.as_ref().chunked() } fn camel_case(&self) -> bool { - RequestHead::camel_case_headers(self) + self.as_ref().camel_case_headers() } fn headers(&self) -> &HeaderMap { - &self.headers + self.as_ref().headers() + } + + fn extra_headers(&self) -> Option<&HeaderMap> { + self.extra_headers() } fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { - dst.reserve(256 + self.headers.len() * AVERAGE_HEADER_SIZE); + let head = self.as_ref(); + dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE); write!( Writer(dst), "{} {} {}", - self.method, - self.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), - match self.version { + head.method, + head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), + match head.version { Version::HTTP_09 => "HTTP/0.9", Version::HTTP_10 => "HTTP/1.0", Version::HTTP_11 => "HTTP/1.1", @@ -488,9 +509,11 @@ fn write_camel_case(value: &[u8], buffer: &mut [u8]) { #[cfg(test)] mod tests { use bytes::Bytes; + //use std::rc::Rc; use super::*; use crate::http::header::{HeaderValue, CONTENT_TYPE}; + use http::header::AUTHORIZATION; #[test] fn test_chunked_te() { @@ -515,6 +538,8 @@ mod tests { head.headers .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); + let mut head = RequestHeadType::Owned(head); + let _ = head.encode_headers( &mut bytes, Version::HTTP_11, @@ -551,21 +576,16 @@ mod tests { Bytes::from_static(b"\r\nContent-Length: 100\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") ); + let mut head = RequestHead::default(); + head.set_camel_case_headers(false); + head.headers.insert(DATE, HeaderValue::from_static("date")); + head.headers + .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); head.headers .append(CONTENT_TYPE, HeaderValue::from_static("xml")); - let _ = head.encode_headers( - &mut bytes, - Version::HTTP_11, - BodySize::Stream, - ConnectionType::KeepAlive, - &ServiceConfig::default(), - ); - assert_eq!( - bytes.take().freeze(), - Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: xml\r\nContent-Type: plain/text\r\n\r\n") - ); - head.set_camel_case_headers(false); + let mut head = RequestHeadType::Owned(head); + let _ = head.encode_headers( &mut bytes, Version::HTTP_11, @@ -578,4 +598,30 @@ mod tests { Bytes::from_static(b"\r\ntransfer-encoding: chunked\r\ndate: date\r\ncontent-type: xml\r\ncontent-type: plain/text\r\n\r\n") ); } + + #[test] + fn test_extra_headers() { + let mut bytes = BytesMut::with_capacity(2048); + + let mut head = RequestHead::default(); + head.headers.insert(AUTHORIZATION, HeaderValue::from_static("some authorization")); + + let mut extra_headers = HeaderMap::new(); + extra_headers.insert(AUTHORIZATION,HeaderValue::from_static("another authorization")); + extra_headers.insert(DATE, HeaderValue::from_static("date")); + + let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers)); + + let _ = head.encode_headers( + &mut bytes, + Version::HTTP_11, + BodySize::Empty, + ConnectionType::Close, + &ServiceConfig::default(), + ); + assert_eq!( + bytes.take().freeze(), + Bytes::from_static(b"\r\ncontent-length: 0\r\nconnection: close\r\nauthorization: another authorization\r\ndate: date\r\n\r\n") + ); + } } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 6b8874b2..b57fdddc 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -39,7 +39,7 @@ pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; pub use self::httpmessage::HttpMessage; -pub use self::message::{Message, RequestHead, ResponseHead}; +pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; pub use self::response::{Response, ResponseBuilder}; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index cf23a401..316df261 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -181,6 +181,36 @@ impl RequestHead { } } +#[derive(Debug)] +pub enum RequestHeadType { + Owned(RequestHead), + Rc(Rc, Option), +} + +impl RequestHeadType { + pub fn extra_headers(&self) -> Option<&HeaderMap> { + match self { + RequestHeadType::Owned(_) => None, + RequestHeadType::Rc(_, headers) => headers.as_ref(), + } + } +} + +impl AsRef for RequestHeadType { + fn as_ref(&self) -> &RequestHead { + match self { + RequestHeadType::Owned(head) => &head, + RequestHeadType::Rc(head, _) => head.as_ref(), + } + } +} + +impl From for RequestHeadType { + fn from(head: RequestHead) -> Self { + RequestHeadType::Owned(head) + } +} + #[derive(Debug)] pub struct ResponseHead { pub version: Version, diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 5442e9db..27962a6f 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,11 +1,19 @@ # Changes +## + +### Added + +* Add `FrozenClientRequest` to support retries for sending HTTP requests + + ## [0.2.5] - 2019-09-06 ### Changed * Ensure that the `Host` header is set when initiating a WebSocket client connection. + ## [0.2.4] - 2019-08-13 ### Changed diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 04f08ecd..82fd6a75 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,4 +1,5 @@ use std::{fmt, io, net}; +use std::rc::Rc; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::Body; @@ -6,7 +7,8 @@ use actix_http::client::{ Connect as ClientConnect, ConnectError, Connection, SendRequestError, }; use actix_http::h1::ClientCodec; -use actix_http::{RequestHead, ResponseHead}; +use actix_http::{RequestHead, RequestHeadType, ResponseHead}; +use actix_http::http::HeaderMap; use actix_service::Service; use futures::{Future, Poll}; @@ -22,6 +24,14 @@ pub(crate) trait Connect { addr: Option, ) -> Box>; + fn send_request_extra( + &mut self, + head: Rc, + extra_headers: Option, + body: Body, + addr: Option, + ) -> Box>; + /// Send request, returns Response and Framed fn open_tunnel( &mut self, @@ -33,6 +43,19 @@ pub(crate) trait Connect { Error = SendRequestError, >, >; + + /// Send request and extra headers, returns Response and Framed + fn open_tunnel_extra( + &mut self, + head: Rc, + extra_headers: Option, + addr: Option, + ) -> Box< + dyn Future< + Item = (ResponseHead, Framed), + Error = SendRequestError, + >, + >; } impl Connect for ConnectorWrapper @@ -59,7 +82,28 @@ where }) .from_err() // send request - .and_then(move |connection| connection.send_request(head, body)) + .and_then(move |connection| connection.send_request(RequestHeadType::from(head), body)) + .map(|(head, payload)| ClientResponse::new(head, payload)), + ) + } + + fn send_request_extra( + &mut self, + head: Rc, + extra_headers: Option, + body: Body, + addr: Option, + ) -> Box> { + Box::new( + self.0 + // connect to the host + .call(ClientConnect { + uri: head.uri.clone(), + addr, + }) + .from_err() + // send request + .and_then(move |connection| connection.send_request(RequestHeadType::Rc(head, extra_headers), body)) .map(|(head, payload)| ClientResponse::new(head, payload)), ) } @@ -83,7 +127,35 @@ where }) .from_err() // send request - .and_then(move |connection| connection.open_tunnel(head)) + .and_then(move |connection| connection.open_tunnel(RequestHeadType::from(head))) + .map(|(head, framed)| { + let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); + (head, framed) + }), + ) + } + + fn open_tunnel_extra( + &mut self, + head: Rc, + extra_headers: Option, + addr: Option, + ) -> Box< + dyn Future< + Item = (ResponseHead, Framed), + Error = SendRequestError, + >, + > { + Box::new( + self.0 + // connect to the host + .call(ClientConnect { + uri: head.uri.clone(), + addr, + }) + .from_err() + // send request + .and_then(move |connection| connection.open_tunnel(RequestHeadType::Rc(head, extra_headers))) .map(|(head, framed)| { let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); (head, framed) diff --git a/awc/src/error.rs b/awc/src/error.rs index f78355c6..4eb92900 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,5 +1,5 @@ //! Http client errors -pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError}; +pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError, FreezeRequestError}; pub use actix_http::error::PayloadError; pub use actix_http::ws::HandshakeError as WsHandshakeError; pub use actix_http::ws::ProtocolError as WsProtocolError; diff --git a/awc/src/request.rs b/awc/src/request.rs index 43715785..4dd07c5d 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,16 +1,16 @@ use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; -use std::time::Duration; +use std::time::{Duration, Instant}; use std::{fmt, net}; use bytes::{BufMut, Bytes, BytesMut}; -use futures::future::{err, Either}; -use futures::{Future, Stream}; +use futures::{Async, Future, Poll, Stream, try_ready}; use percent_encoding::percent_encode; use serde::Serialize; use serde_json; -use tokio_timer::Timeout; +use tokio_timer::Delay; +use derive_more::From; use actix_http::body::{Body, BodyStream}; use actix_http::cookie::{Cookie, CookieJar, USERINFO}; @@ -20,9 +20,9 @@ use actix_http::http::{ uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use actix_http::{Error, Payload, RequestHead}; +use actix_http::{Error, Payload, PayloadStream, RequestHead}; -use crate::error::{InvalidUrl, PayloadError, SendRequestError}; +use crate::error::{InvalidUrl, SendRequestError, FreezeRequestError}; use crate::response::ClientResponse; use crate::ClientConfig; @@ -99,6 +99,11 @@ impl ClientRequest { self } + /// Get HTTP URI of request + pub fn get_uri(&self) -> &Uri { + &self.head.uri + } + /// Set socket address of the server. /// /// This address is used for connection. If address is not @@ -115,6 +120,11 @@ impl ClientRequest { self } + /// Get HTTP method of this request + pub fn get_method(&self) -> &Method { + &self.head.method + } + #[doc(hidden)] /// Set HTTP version of this request. /// @@ -365,34 +375,122 @@ impl ClientRequest { } } + pub fn freeze(self) -> Result { + let slf = match self.prep_for_sending() { + Ok(slf) => slf, + Err(e) => return Err(e.into()), + }; + + let request = FrozenClientRequest { + head: Rc::new(slf.head), + addr: slf.addr, + response_decompress: slf.response_decompress, + timeout: slf.timeout, + config: slf.config, + }; + + Ok(request) + } + /// Complete request construction and send body. pub fn send_body( - mut self, + self, body: B, - ) -> impl Future< - Item = ClientResponse>, - Error = SendRequestError, - > + ) -> SendBody where B: Into, { - if let Some(e) = self.err.take() { - return Either::A(err(e.into())); + let slf = match self.prep_for_sending() { + Ok(slf) => slf, + Err(e) => return e.into(), + }; + + RequestSender::Owned(slf.head) + .send_body(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), body) + } + + /// Set a JSON body and generate `ClientRequest` + pub fn send_json( + self, + value: &T, + ) -> SendBody + { + let slf = match self.prep_for_sending() { + Ok(slf) => slf, + Err(e) => return e.into(), + }; + + RequestSender::Owned(slf.head) + .send_json(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), value) + } + + /// Set a urlencoded body and generate `ClientRequest` + /// + /// `ClientRequestBuilder` can not be used after this call. + pub fn send_form( + self, + value: &T, + ) -> SendBody + { + let slf = match self.prep_for_sending() { + Ok(slf) => slf, + Err(e) => return e.into(), + }; + + RequestSender::Owned(slf.head) + .send_form(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), value) + } + + /// Set an streaming body and generate `ClientRequest`. + pub fn send_stream( + self, + stream: S, + ) -> SendBody + where + S: Stream + 'static, + E: Into + 'static, + { + let slf = match self.prep_for_sending() { + Ok(slf) => slf, + Err(e) => return e.into(), + }; + + RequestSender::Owned(slf.head) + .send_stream(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), stream) + } + + /// Set an empty body and generate `ClientRequest`. + pub fn send( + self, + ) -> SendBody + { + let slf = match self.prep_for_sending() { + Ok(slf) => slf, + Err(e) => return e.into(), + }; + + RequestSender::Owned(slf.head) + .send(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref()) + } + + fn prep_for_sending(mut self) -> Result { + if let Some(e) = self.err { + return Err(e.into()); } // validate uri let uri = &self.head.uri; if uri.host().is_none() { - return Either::A(err(InvalidUrl::MissingHost.into())); + return Err(InvalidUrl::MissingHost.into()); } else if uri.scheme_part().is_none() { - return Either::A(err(InvalidUrl::MissingScheme.into())); + return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme_part() { match scheme.as_str() { "http" | "ws" | "https" | "wss" => (), - _ => return Either::A(err(InvalidUrl::UnknownScheme.into())), + _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { - return Either::A(err(InvalidUrl::UnknownScheme.into())); + return Err(InvalidUrl::UnknownScheme.into()); } // set cookies @@ -430,112 +528,15 @@ impl ClientRequest { slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) } else { #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] - { - slf = slf - .set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") - } + { + slf = slf + .set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + } }; } } - let head = slf.head; - let config = slf.config.as_ref(); - let response_decompress = slf.response_decompress; - - let fut = config - .connector - .borrow_mut() - .send_request(head, body.into(), slf.addr) - .map(move |res| { - res.map_body(|head, payload| { - if response_decompress { - Payload::Stream(Decoder::from_headers(payload, &head.headers)) - } else { - Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) - } - }) - }); - - // set request timeout - if let Some(timeout) = slf.timeout.or_else(|| config.timeout) { - Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { - if let Some(e) = e.into_inner() { - e - } else { - SendRequestError::Timeout - } - }))) - } else { - Either::B(Either::B(fut)) - } - } - - /// Set a JSON body and generate `ClientRequest` - pub fn send_json( - self, - value: &T, - ) -> impl Future< - Item = ClientResponse>, - Error = SendRequestError, - > { - let body = match serde_json::to_string(value) { - Ok(body) => body, - Err(e) => return Either::A(err(Error::from(e).into())), - }; - - // set content-type - let slf = self.set_header_if_none(header::CONTENT_TYPE, "application/json"); - - Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) - } - - /// Set a urlencoded body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn send_form( - self, - value: &T, - ) -> impl Future< - Item = ClientResponse>, - Error = SendRequestError, - > { - let body = match serde_urlencoded::to_string(value) { - Ok(body) => body, - Err(e) => return Either::A(err(Error::from(e).into())), - }; - - // set content-type - let slf = self.set_header_if_none( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ); - - Either::B(slf.send_body(Body::Bytes(Bytes::from(body)))) - } - - /// Set an streaming body and generate `ClientRequest`. - pub fn send_stream( - self, - stream: S, - ) -> impl Future< - Item = ClientResponse>, - Error = SendRequestError, - > - where - S: Stream + 'static, - E: Into + 'static, - { - self.send_body(Body::from_message(BodyStream::new(stream))) - } - - /// Set an empty body and generate `ClientRequest`. - pub fn send( - self, - ) -> impl Future< - Item = ClientResponse>, - Error = SendRequestError, - > { - self.send_body(Body::Empty) + Ok(slf) } } @@ -554,6 +555,441 @@ impl fmt::Debug for ClientRequest { } } +#[derive(Clone)] +pub struct FrozenClientRequest { + pub(crate) head: Rc, + pub(crate) addr: Option, + pub(crate) response_decompress: bool, + pub(crate) timeout: Option, + pub(crate) config: Rc, +} + +impl FrozenClientRequest { + /// Get HTTP URI of request + pub fn get_uri(&self) -> &Uri { + &self.head.uri + } + + /// Get HTTP method of this request + pub fn get_method(&self) -> &Method { + &self.head.method + } + + /// Returns request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head.headers + } + + /// Send a body. + pub fn send_body( + &self, + body: B, + ) -> SendBody + where + B: Into, + { + RequestSender::Rc(self.head.clone(), None) + .send_body(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), body) + } + + /// Send a json body. + pub fn send_json( + &self, + value: &T, + ) -> SendBody + { + RequestSender::Rc(self.head.clone(), None) + .send_json(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), value) + } + + /// Send an urlencoded body. + pub fn send_form( + &self, + value: &T, + ) -> SendBody + { + RequestSender::Rc(self.head.clone(), None) + .send_form(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), value) + } + + /// Send a streaming body. + pub fn send_stream( + &self, + stream: S, + ) -> SendBody + where + S: Stream + 'static, + E: Into + 'static, + { + RequestSender::Rc(self.head.clone(), None) + .send_stream(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), stream) + } + + /// Send an empty body. + pub fn send( + &self, + ) -> SendBody + { + RequestSender::Rc(self.head.clone(), None) + .send(self.addr, self.response_decompress, self.timeout, self.config.as_ref()) + } + + /// Create a `FrozenSendBuilder` with extra headers + pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder { + FrozenSendBuilder::new(self.clone(), extra_headers) + } + + /// Create a `FrozenSendBuilder` with an extra header + pub fn extra_header(&self, key: K, value: V) -> FrozenSendBuilder + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + self.extra_headers(HeaderMap::new()).extra_header(key, value) + } +} + +pub struct FrozenSendBuilder { + req: FrozenClientRequest, + extra_headers: HeaderMap, + err: Option, +} + +impl FrozenSendBuilder { + pub(crate) fn new(req: FrozenClientRequest, extra_headers: HeaderMap) -> Self { + Self { + req, + extra_headers, + err: None, + } + } + + /// Insert a header, it overrides existing header in `FrozenClientRequest`. + pub fn extra_header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => self.extra_headers.insert(key, value), + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Complete request construction and send a body. + pub fn send_body( + self, + body: B, + ) -> SendBody + where + B: Into, + { + if let Some(e) = self.err { + return e.into() + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)) + .send_body(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), body) + } + + /// Complete request construction and send a json body. + pub fn send_json( + self, + value: &T, + ) -> SendBody + { + if let Some(e) = self.err { + return e.into() + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)) + .send_json(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), value) + } + + /// Complete request construction and send an urlencoded body. + pub fn send_form( + self, + value: &T, + ) -> SendBody + { + if let Some(e) = self.err { + return e.into() + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)) + .send_form(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), value) + } + + /// Complete request construction and send a streaming body. + pub fn send_stream( + self, + stream: S, + ) -> SendBody + where + S: Stream + 'static, + E: Into + 'static, + { + if let Some(e) = self.err { + return e.into() + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)) + .send_stream(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), stream) + } + + /// Complete request construction and send an empty body. + pub fn send( + self, + ) -> SendBody + { + if let Some(e) = self.err { + return e.into() + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)) + .send(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref()) + } +} + +#[derive(Debug, From)] +enum PrepForSendingError { + Url(InvalidUrl), + Http(HttpError), +} + +impl Into for PrepForSendingError { + fn into(self) -> FreezeRequestError { + match self { + PrepForSendingError::Url(e) => FreezeRequestError::Url(e), + PrepForSendingError::Http(e) => FreezeRequestError::Http(e), + } + } +} + +impl Into for PrepForSendingError { + fn into(self) -> SendRequestError { + match self { + PrepForSendingError::Url(e) => SendRequestError::Url(e), + PrepForSendingError::Http(e) => SendRequestError::Http(e), + } + } +} + +pub enum SendBody +{ + Fut(Box>, Option, bool), + Err(Option), +} + +impl SendBody +{ + pub fn new( + send: Box>, + response_decompress: bool, + timeout: Option, + ) -> SendBody + { + let delay = timeout.map(|t| Delay::new(Instant::now() + t)); + SendBody::Fut(send, delay, response_decompress) + } +} + +impl Future for SendBody +{ + type Item = ClientResponse>>; + type Error = SendRequestError; + + fn poll(&mut self) -> Poll { + match self { + SendBody::Fut(send, delay, response_decompress) => { + if delay.is_some() { + match delay.poll() { + Ok(Async::NotReady) => (), + _ => return Err(SendRequestError::Timeout), + } + } + + let res = try_ready!(send.poll()) + .map_body(|head, payload| { + if *response_decompress { + Payload::Stream(Decoder::from_headers(payload, &head.headers)) + } else { + Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) + } + }); + + Ok(Async::Ready(res)) + }, + SendBody::Err(ref mut e) => { + match e.take() { + Some(e) => Err(e.into()), + None => panic!("Attempting to call completed future"), + } + } + } + } +} + + +impl From for SendBody +{ + fn from(e: SendRequestError) -> Self { + SendBody::Err(Some(e)) + } +} + +impl From for SendBody +{ + fn from(e: Error) -> Self { + SendBody::Err(Some(e.into())) + } +} + +impl From for SendBody +{ + fn from(e: HttpError) -> Self { + SendBody::Err(Some(e.into())) + } +} + +impl From for SendBody +{ + fn from(e: PrepForSendingError) -> Self { + SendBody::Err(Some(e.into())) + } +} + +#[derive(Debug)] +enum RequestSender { + Owned(RequestHead), + Rc(Rc, Option), +} + +impl RequestSender { + pub fn send_body( + self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + body: B, + ) -> SendBody + where + B: Into, + { + let mut connector = config.connector.borrow_mut(); + + let fut = match self { + RequestSender::Owned(head) => connector.send_request(head, body.into(), addr), + RequestSender::Rc(head, extra_headers) => connector.send_request_extra(head, extra_headers, body.into(), addr), + }; + + SendBody::new(fut, response_decompress, timeout.or_else(|| config.timeout.clone())) + } + + pub fn send_json( + mut self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + value: &T, + ) -> SendBody + { + let body = match serde_json::to_string(value) { + Ok(body) => body, + Err(e) => return Error::from(e).into(), + }; + + if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { + return e.into(); + } + + self.send_body(addr, response_decompress, timeout, config, Body::Bytes(Bytes::from(body))) + } + + pub fn send_form( + mut self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + value: &T, + ) -> SendBody + { + let body = match serde_urlencoded::to_string(value) { + Ok(body) => body, + Err(e) => return Error::from(e).into(), + }; + + // set content-type + if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded") { + return e.into(); + } + + self.send_body(addr, response_decompress, timeout, config, Body::Bytes(Bytes::from(body))) + } + + pub fn send_stream( + self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + stream: S, + ) -> SendBody + where + S: Stream + 'static, + E: Into + 'static, + { + self.send_body(addr, response_decompress, timeout, config, Body::from_message(BodyStream::new(stream))) + } + + pub fn send( + self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + ) -> SendBody + { + self.send_body(addr, response_decompress, timeout, config, Body::Empty) + } + + fn set_header_if_none(&mut self, key: HeaderName, value: V) -> Result<(), HttpError> + where + V: IntoHeaderValue, + { + match self { + RequestSender::Owned(head) => { + if !head.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => head.headers.insert(key, value), + Err(e) => return Err(e.into()), + } + } + }, + RequestSender::Rc(head, extra_headers) => { + if !head.headers.contains_key(&key) && !extra_headers.iter().any(|h| h.contains_key(&key)) { + match value.try_into(){ + Ok(v) => { + let h = extra_headers.get_or_insert(HeaderMap::new()); + h.insert(key, v) + }, + Err(e) => return Err(e.into()), + }; + } + } + } + + Ok(()) + } +} + #[cfg(test)] mod tests { use std::time::SystemTime; From 043f763c51675b0d4bc0d8a66e8a6883e155bdd8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Sep 2019 20:07:39 +0600 Subject: [PATCH 1559/1635] prepare actix-http release --- actix-http/CHANGES.md | 5 +---- actix-http/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 80da691c..d603cde7 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,14 +1,11 @@ # Changes -## +## [0.2.11] - 2019-09-11 ### Added * Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` - -## [0.2.10] - 2019-09-xx - ### Fixed * on_connect result isn't added to request extensions for http2 requests #1009 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 290a8fba..3019b289 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.9" +version = "0.2.10" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" From 71f8577713791a6991a2e7120077d52347d38a55 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 11 Sep 2019 20:13:28 +0600 Subject: [PATCH 1560/1635] prepare awc release --- awc/CHANGES.md | 5 +---- awc/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 27962a6f..4a52a9df 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,14 +1,11 @@ # Changes -## +## [0.2.5] - 2019-09-11 ### Added * Add `FrozenClientRequest` to support retries for sending HTTP requests - -## [0.2.5] - 2019-09-06 - ### Changed * Ensure that the `Host` header is set when initiating a WebSocket client connection. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 7f42501e..3d15c943 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.4" +version = "0.2.5" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -44,7 +44,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-http = "0.2.9" +actix-http = "0.2.10" base64 = "0.10.1" bytes = "0.4" derive_more = "0.15.0" @@ -63,7 +63,7 @@ rustls = { version = "0.15.2", optional = true } [dev-dependencies] actix-rt = "0.2.2" actix-web = { version = "1.0.0", features=["ssl"] } -actix-http = { version = "0.2.4", features=["ssl"] } +actix-http = { version = "0.2.10", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } actix-utils = "0.4.1" actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } From 45d2fd429928d243d80a6bce246d2542f9e8cde7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Sep 2019 10:40:56 +0600 Subject: [PATCH 1561/1635] export frozen request related types; refactor code layout --- awc/CHANGES.md | 7 + awc/src/frozen.rs | 235 ++++++++++++++++++++ awc/src/lib.rs | 4 + awc/src/request.rs | 535 +++++---------------------------------------- awc/src/sender.rs | 282 ++++++++++++++++++++++++ 5 files changed, 581 insertions(+), 482 deletions(-) create mode 100644 awc/src/frozen.rs create mode 100644 awc/src/sender.rs diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 4a52a9df..94ad65ff 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [0.2.6] - 2019-09-12 + +### Added + +* Export frozen request related types. + + ## [0.2.5] - 2019-09-11 ### Added diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs new file mode 100644 index 00000000..d9f65d43 --- /dev/null +++ b/awc/src/frozen.rs @@ -0,0 +1,235 @@ +use std::net; +use std::rc::Rc; +use std::time::Duration; + +use bytes::Bytes; +use futures::Stream; +use serde::Serialize; + +use actix_http::body::Body; +use actix_http::http::header::IntoHeaderValue; +use actix_http::http::{ + Error as HttpError, HeaderMap, HeaderName, HttpTryFrom, Method, Uri, +}; +use actix_http::{Error, RequestHead}; + +use crate::sender::{RequestSender, SendClientRequest}; +use crate::ClientConfig; + +/// `FrozenClientRequest` struct represents clonable client request. +/// It could be used to send same request multiple times. +#[derive(Clone)] +pub struct FrozenClientRequest { + pub(crate) head: Rc, + pub(crate) addr: Option, + pub(crate) response_decompress: bool, + pub(crate) timeout: Option, + pub(crate) config: Rc, +} + +impl FrozenClientRequest { + /// Get HTTP URI of request + pub fn get_uri(&self) -> &Uri { + &self.head.uri + } + + /// Get HTTP method of this request + pub fn get_method(&self) -> &Method { + &self.head.method + } + + /// Returns request's headers. + pub fn headers(&self) -> &HeaderMap { + &self.head.headers + } + + /// Send a body. + pub fn send_body(&self, body: B) -> SendClientRequest + where + B: Into, + { + RequestSender::Rc(self.head.clone(), None).send_body( + self.addr, + self.response_decompress, + self.timeout, + self.config.as_ref(), + body, + ) + } + + /// Send a json body. + pub fn send_json(&self, value: &T) -> SendClientRequest { + RequestSender::Rc(self.head.clone(), None).send_json( + self.addr, + self.response_decompress, + self.timeout, + self.config.as_ref(), + value, + ) + } + + /// Send an urlencoded body. + pub fn send_form(&self, value: &T) -> SendClientRequest { + RequestSender::Rc(self.head.clone(), None).send_form( + self.addr, + self.response_decompress, + self.timeout, + self.config.as_ref(), + value, + ) + } + + /// Send a streaming body. + pub fn send_stream(&self, stream: S) -> SendClientRequest + where + S: Stream + 'static, + E: Into + 'static, + { + RequestSender::Rc(self.head.clone(), None).send_stream( + self.addr, + self.response_decompress, + self.timeout, + self.config.as_ref(), + stream, + ) + } + + /// Send an empty body. + pub fn send(&self) -> SendClientRequest { + RequestSender::Rc(self.head.clone(), None).send( + self.addr, + self.response_decompress, + self.timeout, + self.config.as_ref(), + ) + } + + /// Create a `FrozenSendBuilder` with extra headers + pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder { + FrozenSendBuilder::new(self.clone(), extra_headers) + } + + /// Create a `FrozenSendBuilder` with an extra header + pub fn extra_header(&self, key: K, value: V) -> FrozenSendBuilder + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + self.extra_headers(HeaderMap::new()) + .extra_header(key, value) + } +} + +/// Builder that allows to modify extra headers. +pub struct FrozenSendBuilder { + req: FrozenClientRequest, + extra_headers: HeaderMap, + err: Option, +} + +impl FrozenSendBuilder { + pub(crate) fn new(req: FrozenClientRequest, extra_headers: HeaderMap) -> Self { + Self { + req, + extra_headers, + err: None, + } + } + + /// Insert a header, it overrides existing header in `FrozenClientRequest`. + pub fn extra_header(mut self, key: K, value: V) -> Self + where + HeaderName: HttpTryFrom, + V: IntoHeaderValue, + { + match HeaderName::try_from(key) { + Ok(key) => match value.try_into() { + Ok(value) => self.extra_headers.insert(key, value), + Err(e) => self.err = Some(e.into()), + }, + Err(e) => self.err = Some(e.into()), + } + self + } + + /// Complete request construction and send a body. + pub fn send_body(self, body: B) -> SendClientRequest + where + B: Into, + { + if let Some(e) = self.err { + return e.into(); + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_body( + self.req.addr, + self.req.response_decompress, + self.req.timeout, + self.req.config.as_ref(), + body, + ) + } + + /// Complete request construction and send a json body. + pub fn send_json(self, value: &T) -> SendClientRequest { + if let Some(e) = self.err { + return e.into(); + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_json( + self.req.addr, + self.req.response_decompress, + self.req.timeout, + self.req.config.as_ref(), + value, + ) + } + + /// Complete request construction and send an urlencoded body. + pub fn send_form(self, value: &T) -> SendClientRequest { + if let Some(e) = self.err { + return e.into(); + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_form( + self.req.addr, + self.req.response_decompress, + self.req.timeout, + self.req.config.as_ref(), + value, + ) + } + + /// Complete request construction and send a streaming body. + pub fn send_stream(self, stream: S) -> SendClientRequest + where + S: Stream + 'static, + E: Into + 'static, + { + if let Some(e) = self.err { + return e.into(); + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_stream( + self.req.addr, + self.req.response_decompress, + self.req.timeout, + self.req.config.as_ref(), + stream, + ) + } + + /// Complete request construction and send an empty body. + pub fn send(self) -> SendClientRequest { + if let Some(e) = self.err { + return e.into(); + } + + RequestSender::Rc(self.req.head, Some(self.extra_headers)).send( + self.req.addr, + self.req.response_decompress, + self.req.timeout, + self.req.config.as_ref(), + ) + } +} diff --git a/awc/src/lib.rs b/awc/src/lib.rs index da63bbd9..58c9056b 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -33,15 +33,19 @@ use actix_http::RequestHead; mod builder; mod connect; pub mod error; +mod frozen; mod request; mod response; +mod sender; pub mod test; pub mod ws; pub use self::builder::ClientBuilder; pub use self::connect::BoxedSocket; +pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; +pub use self::sender::SendClientRequest; use self::connect::{Connect, ConnectorWrapper}; diff --git a/awc/src/request.rs b/awc/src/request.rs index 4dd07c5d..d597a163 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -1,29 +1,26 @@ use std::fmt::Write as FmtWrite; use std::io::Write; use std::rc::Rc; -use std::time::{Duration, Instant}; +use std::time::Duration; use std::{fmt, net}; use bytes::{BufMut, Bytes, BytesMut}; -use futures::{Async, Future, Poll, Stream, try_ready}; +use futures::Stream; use percent_encoding::percent_encode; use serde::Serialize; -use serde_json; -use tokio_timer::Delay; -use derive_more::From; -use actix_http::body::{Body, BodyStream}; +use actix_http::body::Body; use actix_http::cookie::{Cookie, CookieJar, USERINFO}; -use actix_http::encoding::Decoder; -use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; +use actix_http::http::header::{self, Header, IntoHeaderValue}; use actix_http::http::{ uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, HttpTryFrom, Method, Uri, Version, }; -use actix_http::{Error, Payload, PayloadStream, RequestHead}; +use actix_http::{Error, RequestHead}; -use crate::error::{InvalidUrl, SendRequestError, FreezeRequestError}; -use crate::response::ClientResponse; +use crate::error::{FreezeRequestError, InvalidUrl}; +use crate::frozen::FrozenClientRequest; +use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; use crate::ClientConfig; #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] @@ -375,6 +372,8 @@ impl ClientRequest { } } + /// Freeze request builder and construct `FrozenClientRequest`, + /// which could be used for sending same request multiple times. pub fn freeze(self) -> Result { let slf = match self.prep_for_sending() { Ok(slf) => slf, @@ -393,10 +392,7 @@ impl ClientRequest { } /// Complete request construction and send body. - pub fn send_body( - self, - body: B, - ) -> SendBody + pub fn send_body(self, body: B) -> SendClientRequest where B: Into, { @@ -405,47 +401,51 @@ impl ClientRequest { Err(e) => return e.into(), }; - RequestSender::Owned(slf.head) - .send_body(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), body) + RequestSender::Owned(slf.head).send_body( + slf.addr, + slf.response_decompress, + slf.timeout, + slf.config.as_ref(), + body, + ) } /// Set a JSON body and generate `ClientRequest` - pub fn send_json( - self, - value: &T, - ) -> SendBody - { + pub fn send_json(self, value: &T) -> SendClientRequest { let slf = match self.prep_for_sending() { Ok(slf) => slf, Err(e) => return e.into(), }; - RequestSender::Owned(slf.head) - .send_json(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), value) + RequestSender::Owned(slf.head).send_json( + slf.addr, + slf.response_decompress, + slf.timeout, + slf.config.as_ref(), + value, + ) } /// Set a urlencoded body and generate `ClientRequest` /// /// `ClientRequestBuilder` can not be used after this call. - pub fn send_form( - self, - value: &T, - ) -> SendBody - { + pub fn send_form(self, value: &T) -> SendClientRequest { let slf = match self.prep_for_sending() { Ok(slf) => slf, Err(e) => return e.into(), }; - RequestSender::Owned(slf.head) - .send_form(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), value) + RequestSender::Owned(slf.head).send_form( + slf.addr, + slf.response_decompress, + slf.timeout, + slf.config.as_ref(), + value, + ) } /// Set an streaming body and generate `ClientRequest`. - pub fn send_stream( - self, - stream: S, - ) -> SendBody + pub fn send_stream(self, stream: S) -> SendClientRequest where S: Stream + 'static, E: Into + 'static, @@ -455,22 +455,28 @@ impl ClientRequest { Err(e) => return e.into(), }; - RequestSender::Owned(slf.head) - .send_stream(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), stream) + RequestSender::Owned(slf.head).send_stream( + slf.addr, + slf.response_decompress, + slf.timeout, + slf.config.as_ref(), + stream, + ) } /// Set an empty body and generate `ClientRequest`. - pub fn send( - self, - ) -> SendBody - { + pub fn send(self) -> SendClientRequest { let slf = match self.prep_for_sending() { Ok(slf) => slf, Err(e) => return e.into(), }; - RequestSender::Owned(slf.head) - .send(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref()) + RequestSender::Owned(slf.head).send( + slf.addr, + slf.response_decompress, + slf.timeout, + slf.config.as_ref(), + ) } fn prep_for_sending(mut self) -> Result { @@ -528,10 +534,10 @@ impl ClientRequest { slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) } else { #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] - { - slf = slf - .set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") - } + { + slf = slf + .set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + } }; } } @@ -555,441 +561,6 @@ impl fmt::Debug for ClientRequest { } } -#[derive(Clone)] -pub struct FrozenClientRequest { - pub(crate) head: Rc, - pub(crate) addr: Option, - pub(crate) response_decompress: bool, - pub(crate) timeout: Option, - pub(crate) config: Rc, -} - -impl FrozenClientRequest { - /// Get HTTP URI of request - pub fn get_uri(&self) -> &Uri { - &self.head.uri - } - - /// Get HTTP method of this request - pub fn get_method(&self) -> &Method { - &self.head.method - } - - /// Returns request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - /// Send a body. - pub fn send_body( - &self, - body: B, - ) -> SendBody - where - B: Into, - { - RequestSender::Rc(self.head.clone(), None) - .send_body(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), body) - } - - /// Send a json body. - pub fn send_json( - &self, - value: &T, - ) -> SendBody - { - RequestSender::Rc(self.head.clone(), None) - .send_json(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), value) - } - - /// Send an urlencoded body. - pub fn send_form( - &self, - value: &T, - ) -> SendBody - { - RequestSender::Rc(self.head.clone(), None) - .send_form(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), value) - } - - /// Send a streaming body. - pub fn send_stream( - &self, - stream: S, - ) -> SendBody - where - S: Stream + 'static, - E: Into + 'static, - { - RequestSender::Rc(self.head.clone(), None) - .send_stream(self.addr, self.response_decompress, self.timeout, self.config.as_ref(), stream) - } - - /// Send an empty body. - pub fn send( - &self, - ) -> SendBody - { - RequestSender::Rc(self.head.clone(), None) - .send(self.addr, self.response_decompress, self.timeout, self.config.as_ref()) - } - - /// Create a `FrozenSendBuilder` with extra headers - pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder { - FrozenSendBuilder::new(self.clone(), extra_headers) - } - - /// Create a `FrozenSendBuilder` with an extra header - pub fn extra_header(&self, key: K, value: V) -> FrozenSendBuilder - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - self.extra_headers(HeaderMap::new()).extra_header(key, value) - } -} - -pub struct FrozenSendBuilder { - req: FrozenClientRequest, - extra_headers: HeaderMap, - err: Option, -} - -impl FrozenSendBuilder { - pub(crate) fn new(req: FrozenClientRequest, extra_headers: HeaderMap) -> Self { - Self { - req, - extra_headers, - err: None, - } - } - - /// Insert a header, it overrides existing header in `FrozenClientRequest`. - pub fn extra_header(mut self, key: K, value: V) -> Self - where - HeaderName: HttpTryFrom, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => self.extra_headers.insert(key, value), - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Complete request construction and send a body. - pub fn send_body( - self, - body: B, - ) -> SendBody - where - B: Into, - { - if let Some(e) = self.err { - return e.into() - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)) - .send_body(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), body) - } - - /// Complete request construction and send a json body. - pub fn send_json( - self, - value: &T, - ) -> SendBody - { - if let Some(e) = self.err { - return e.into() - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)) - .send_json(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), value) - } - - /// Complete request construction and send an urlencoded body. - pub fn send_form( - self, - value: &T, - ) -> SendBody - { - if let Some(e) = self.err { - return e.into() - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)) - .send_form(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), value) - } - - /// Complete request construction and send a streaming body. - pub fn send_stream( - self, - stream: S, - ) -> SendBody - where - S: Stream + 'static, - E: Into + 'static, - { - if let Some(e) = self.err { - return e.into() - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)) - .send_stream(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref(), stream) - } - - /// Complete request construction and send an empty body. - pub fn send( - self, - ) -> SendBody - { - if let Some(e) = self.err { - return e.into() - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)) - .send(self.req.addr, self.req.response_decompress, self.req.timeout, self.req.config.as_ref()) - } -} - -#[derive(Debug, From)] -enum PrepForSendingError { - Url(InvalidUrl), - Http(HttpError), -} - -impl Into for PrepForSendingError { - fn into(self) -> FreezeRequestError { - match self { - PrepForSendingError::Url(e) => FreezeRequestError::Url(e), - PrepForSendingError::Http(e) => FreezeRequestError::Http(e), - } - } -} - -impl Into for PrepForSendingError { - fn into(self) -> SendRequestError { - match self { - PrepForSendingError::Url(e) => SendRequestError::Url(e), - PrepForSendingError::Http(e) => SendRequestError::Http(e), - } - } -} - -pub enum SendBody -{ - Fut(Box>, Option, bool), - Err(Option), -} - -impl SendBody -{ - pub fn new( - send: Box>, - response_decompress: bool, - timeout: Option, - ) -> SendBody - { - let delay = timeout.map(|t| Delay::new(Instant::now() + t)); - SendBody::Fut(send, delay, response_decompress) - } -} - -impl Future for SendBody -{ - type Item = ClientResponse>>; - type Error = SendRequestError; - - fn poll(&mut self) -> Poll { - match self { - SendBody::Fut(send, delay, response_decompress) => { - if delay.is_some() { - match delay.poll() { - Ok(Async::NotReady) => (), - _ => return Err(SendRequestError::Timeout), - } - } - - let res = try_ready!(send.poll()) - .map_body(|head, payload| { - if *response_decompress { - Payload::Stream(Decoder::from_headers(payload, &head.headers)) - } else { - Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) - } - }); - - Ok(Async::Ready(res)) - }, - SendBody::Err(ref mut e) => { - match e.take() { - Some(e) => Err(e.into()), - None => panic!("Attempting to call completed future"), - } - } - } - } -} - - -impl From for SendBody -{ - fn from(e: SendRequestError) -> Self { - SendBody::Err(Some(e)) - } -} - -impl From for SendBody -{ - fn from(e: Error) -> Self { - SendBody::Err(Some(e.into())) - } -} - -impl From for SendBody -{ - fn from(e: HttpError) -> Self { - SendBody::Err(Some(e.into())) - } -} - -impl From for SendBody -{ - fn from(e: PrepForSendingError) -> Self { - SendBody::Err(Some(e.into())) - } -} - -#[derive(Debug)] -enum RequestSender { - Owned(RequestHead), - Rc(Rc, Option), -} - -impl RequestSender { - pub fn send_body( - self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - body: B, - ) -> SendBody - where - B: Into, - { - let mut connector = config.connector.borrow_mut(); - - let fut = match self { - RequestSender::Owned(head) => connector.send_request(head, body.into(), addr), - RequestSender::Rc(head, extra_headers) => connector.send_request_extra(head, extra_headers, body.into(), addr), - }; - - SendBody::new(fut, response_decompress, timeout.or_else(|| config.timeout.clone())) - } - - pub fn send_json( - mut self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - value: &T, - ) -> SendBody - { - let body = match serde_json::to_string(value) { - Ok(body) => body, - Err(e) => return Error::from(e).into(), - }; - - if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { - return e.into(); - } - - self.send_body(addr, response_decompress, timeout, config, Body::Bytes(Bytes::from(body))) - } - - pub fn send_form( - mut self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - value: &T, - ) -> SendBody - { - let body = match serde_urlencoded::to_string(value) { - Ok(body) => body, - Err(e) => return Error::from(e).into(), - }; - - // set content-type - if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded") { - return e.into(); - } - - self.send_body(addr, response_decompress, timeout, config, Body::Bytes(Bytes::from(body))) - } - - pub fn send_stream( - self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - stream: S, - ) -> SendBody - where - S: Stream + 'static, - E: Into + 'static, - { - self.send_body(addr, response_decompress, timeout, config, Body::from_message(BodyStream::new(stream))) - } - - pub fn send( - self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - ) -> SendBody - { - self.send_body(addr, response_decompress, timeout, config, Body::Empty) - } - - fn set_header_if_none(&mut self, key: HeaderName, value: V) -> Result<(), HttpError> - where - V: IntoHeaderValue, - { - match self { - RequestSender::Owned(head) => { - if !head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => head.headers.insert(key, value), - Err(e) => return Err(e.into()), - } - } - }, - RequestSender::Rc(head, extra_headers) => { - if !head.headers.contains_key(&key) && !extra_headers.iter().any(|h| h.contains_key(&key)) { - match value.try_into(){ - Ok(v) => { - let h = extra_headers.get_or_insert(HeaderMap::new()); - h.insert(key, v) - }, - Err(e) => return Err(e.into()), - }; - } - } - } - - Ok(()) - } -} - #[cfg(test)] mod tests { use std::time::SystemTime; diff --git a/awc/src/sender.rs b/awc/src/sender.rs new file mode 100644 index 00000000..c8e169cb --- /dev/null +++ b/awc/src/sender.rs @@ -0,0 +1,282 @@ +use std::net; +use std::rc::Rc; +use std::time::{Duration, Instant}; + +use bytes::Bytes; +use derive_more::From; +use futures::{try_ready, Async, Future, Poll, Stream}; +use serde::Serialize; +use serde_json; +use tokio_timer::Delay; + +use actix_http::body::{Body, BodyStream}; +use actix_http::encoding::Decoder; +use actix_http::http::header::{self, ContentEncoding, IntoHeaderValue}; +use actix_http::http::{Error as HttpError, HeaderMap, HeaderName}; +use actix_http::{Error, Payload, PayloadStream, RequestHead}; + +use crate::error::{FreezeRequestError, InvalidUrl, SendRequestError}; +use crate::response::ClientResponse; +use crate::ClientConfig; + +#[derive(Debug, From)] +pub(crate) enum PrepForSendingError { + Url(InvalidUrl), + Http(HttpError), +} + +impl Into for PrepForSendingError { + fn into(self) -> FreezeRequestError { + match self { + PrepForSendingError::Url(e) => FreezeRequestError::Url(e), + PrepForSendingError::Http(e) => FreezeRequestError::Http(e), + } + } +} + +impl Into for PrepForSendingError { + fn into(self) -> SendRequestError { + match self { + PrepForSendingError::Url(e) => SendRequestError::Url(e), + PrepForSendingError::Http(e) => SendRequestError::Http(e), + } + } +} + +/// Future that sends request's payload and resolves to a server response. +#[must_use = "futures do nothing unless polled"] +pub enum SendClientRequest { + Fut( + Box>, + Option, + bool, + ), + Err(Option), +} + +impl SendClientRequest { + pub(crate) fn new( + send: Box>, + response_decompress: bool, + timeout: Option, + ) -> SendClientRequest { + let delay = timeout.map(|t| Delay::new(Instant::now() + t)); + SendClientRequest::Fut(send, delay, response_decompress) + } +} + +impl Future for SendClientRequest { + type Item = ClientResponse>>; + type Error = SendRequestError; + + fn poll(&mut self) -> Poll { + match self { + SendClientRequest::Fut(send, delay, response_decompress) => { + if delay.is_some() { + match delay.poll() { + Ok(Async::NotReady) => (), + _ => return Err(SendRequestError::Timeout), + } + } + + let res = try_ready!(send.poll()).map_body(|head, payload| { + if *response_decompress { + Payload::Stream(Decoder::from_headers(payload, &head.headers)) + } else { + Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) + } + }); + + Ok(Async::Ready(res)) + } + SendClientRequest::Err(ref mut e) => match e.take() { + Some(e) => Err(e.into()), + None => panic!("Attempting to call completed future"), + }, + } + } +} + +impl From for SendClientRequest { + fn from(e: SendRequestError) -> Self { + SendClientRequest::Err(Some(e)) + } +} + +impl From for SendClientRequest { + fn from(e: Error) -> Self { + SendClientRequest::Err(Some(e.into())) + } +} + +impl From for SendClientRequest { + fn from(e: HttpError) -> Self { + SendClientRequest::Err(Some(e.into())) + } +} + +impl From for SendClientRequest { + fn from(e: PrepForSendingError) -> Self { + SendClientRequest::Err(Some(e.into())) + } +} + +#[derive(Debug)] +pub(crate) enum RequestSender { + Owned(RequestHead), + Rc(Rc, Option), +} + +impl RequestSender { + pub(crate) fn send_body( + self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + body: B, + ) -> SendClientRequest + where + B: Into, + { + let mut connector = config.connector.borrow_mut(); + + let fut = match self { + RequestSender::Owned(head) => { + connector.send_request(head, body.into(), addr) + } + RequestSender::Rc(head, extra_headers) => { + connector.send_request_extra(head, extra_headers, body.into(), addr) + } + }; + + SendClientRequest::new( + fut, + response_decompress, + timeout.or_else(|| config.timeout.clone()), + ) + } + + pub(crate) fn send_json( + mut self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + value: &T, + ) -> SendClientRequest { + let body = match serde_json::to_string(value) { + Ok(body) => body, + Err(e) => return Error::from(e).into(), + }; + + if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") + { + return e.into(); + } + + self.send_body( + addr, + response_decompress, + timeout, + config, + Body::Bytes(Bytes::from(body)), + ) + } + + pub(crate) fn send_form( + mut self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + value: &T, + ) -> SendClientRequest { + let body = match serde_urlencoded::to_string(value) { + Ok(body) => body, + Err(e) => return Error::from(e).into(), + }; + + // set content-type + if let Err(e) = self.set_header_if_none( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) { + return e.into(); + } + + self.send_body( + addr, + response_decompress, + timeout, + config, + Body::Bytes(Bytes::from(body)), + ) + } + + pub(crate) fn send_stream( + self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + stream: S, + ) -> SendClientRequest + where + S: Stream + 'static, + E: Into + 'static, + { + self.send_body( + addr, + response_decompress, + timeout, + config, + Body::from_message(BodyStream::new(stream)), + ) + } + + pub(crate) fn send( + self, + addr: Option, + response_decompress: bool, + timeout: Option, + config: &ClientConfig, + ) -> SendClientRequest { + self.send_body(addr, response_decompress, timeout, config, Body::Empty) + } + + fn set_header_if_none( + &mut self, + key: HeaderName, + value: V, + ) -> Result<(), HttpError> + where + V: IntoHeaderValue, + { + match self { + RequestSender::Owned(head) => { + if !head.headers.contains_key(&key) { + match value.try_into() { + Ok(value) => head.headers.insert(key, value), + Err(e) => return Err(e.into()), + } + } + } + RequestSender::Rc(head, extra_headers) => { + if !head.headers.contains_key(&key) + && !extra_headers.iter().any(|h| h.contains_key(&key)) + { + match value.try_into() { + Ok(v) => { + let h = extra_headers.get_or_insert(HeaderMap::new()); + h.insert(key, v) + } + Err(e) => return Err(e.into()), + }; + } + } + } + + Ok(()) + } +} From 60b7aebd0a6de57cc480e8fdc9a755743654bde1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Sep 2019 21:52:46 +0600 Subject: [PATCH 1562/1635] fmt & clippy --- actix-http/Cargo.toml | 2 +- actix-http/src/client/error.rs | 2 +- actix-http/src/client/h1proto.rs | 28 +++++++++++++--------------- actix-http/src/client/h2proto.rs | 20 +++++++++++++------- actix-http/src/client/mod.rs | 2 +- actix-http/src/client/pool.rs | 2 +- actix-http/src/h1/client.rs | 10 +++++++--- actix-http/src/h1/encoder.rs | 23 +++++++++++++++-------- actix-multipart/src/server.rs | 28 ++++++++++++++++------------ awc/src/connect.rs | 21 +++++++++++++++------ awc/src/error.rs | 4 +++- awc/src/sender.rs | 4 ++-- awc/src/ws.rs | 5 ++++- src/app_service.rs | 2 +- src/middleware/mod.rs | 4 ++-- 15 files changed, 95 insertions(+), 62 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3019b289..cc7c885e 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -66,7 +66,7 @@ hashbrown = "0.5.0" h2 = "0.1.16" http = "0.1.17" httparse = "1.3" -indexmap = "1.0" +indexmap = "1.2" lazy_static = "1.0" language-tags = "0.2" log = "0.4" diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 40aef2cc..0ac5f30f 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -147,4 +147,4 @@ impl From for SendRequestError { FreezeRequestError::Http(e) => e.into(), } } -} \ No newline at end of file +} diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index fa920ab9..b078c6a6 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -8,10 +8,10 @@ use futures::{Async, Future, Poll, Sink, Stream}; use crate::error::PayloadError; use crate::h1; +use crate::header::HeaderMap; use crate::http::header::{IntoHeaderValue, HOST}; use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::{Payload, PayloadStream}; -use crate::header::HeaderMap; use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::error::{ConnectError, SendRequestError}; @@ -30,7 +30,9 @@ where B: MessageBody, { // set request host header - if !head.as_ref().headers.contains_key(HOST) && !head.extra_headers().iter().any(|h| h.contains_key(HOST)) { + if !head.as_ref().headers.contains_key(HOST) + && !head.extra_headers().iter().any(|h| h.contains_key(HOST)) + { if let Some(host) = head.as_ref().uri.host() { let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); @@ -40,20 +42,16 @@ where }; match wrt.get_mut().take().freeze().try_into() { - Ok(value) => { - match head { - RequestHeadType::Owned(ref mut head) => { - head.headers.insert(HOST, value) - }, - RequestHeadType::Rc(_, ref mut extra_headers) => { - let headers = extra_headers.get_or_insert(HeaderMap::new()); - headers.insert(HOST, value) - }, + Ok(value) => match head { + RequestHeadType::Owned(ref mut head) => { + head.headers.insert(HOST, value) } - } - Err(e) => { - log::error!("Can not set HOST header {}", e) - } + RequestHeadType::Rc(_, ref mut extra_headers) => { + let headers = extra_headers.get_or_insert(HeaderMap::new()); + headers.insert(HOST, value) + } + }, + Err(e) => log::error!("Can not set HOST header {}", e), } } } diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 2993d89d..5744a154 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -9,9 +9,9 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; use crate::body::{BodySize, MessageBody}; +use crate::header::HeaderMap; use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; -use crate::header::HeaderMap; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; @@ -69,15 +69,21 @@ where // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. let (head, extra_headers) = match head { - RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()), - RequestHeadType::Rc(head, extra_headers) => (RequestHeadType::Rc(head, None), extra_headers.unwrap_or(HeaderMap::new())), + RequestHeadType::Owned(head) => { + (RequestHeadType::Owned(head), HeaderMap::new()) + } + RequestHeadType::Rc(head, extra_headers) => ( + RequestHeadType::Rc(head, None), + extra_headers.unwrap_or_else(HeaderMap::new), + ), }; // merging headers from head and extra headers. - let headers = head.as_ref().headers.iter() - .filter(|(name, _)| { - !extra_headers.contains_key(*name) - }) + let headers = head + .as_ref() + .headers + .iter() + .filter(|(name, _)| !extra_headers.contains_key(*name)) .chain(extra_headers.iter()); // copy headers diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index 04427ce4..a45aebcd 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -10,7 +10,7 @@ mod pool; pub use self::connection::Connection; pub use self::connector::Connector; -pub use self::error::{ConnectError, InvalidUrl, SendRequestError, FreezeRequestError}; +pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use self::pool::Protocol; #[derive(Clone)] diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 24a18739..a3522ff8 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -326,7 +326,7 @@ impl Inner { fn release_waiter(&mut self, key: &Key, token: usize) { self.waiters.remove(token); - self.waiters_queue.remove(&(key.clone(), token)); + let _ = self.waiters_queue.shift_remove(&(key.clone(), token)); } } diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index c0bbcc69..bea629c4 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -16,9 +16,11 @@ use super::{Message, MessageType}; use crate::body::BodySize; use crate::config::ServiceConfig; use crate::error::{ParseError, PayloadError}; -use crate::helpers; -use crate::message::{ConnectionType, Head, MessagePool, RequestHead, RequestHeadType, ResponseHead}; use crate::header::HeaderMap; +use crate::helpers; +use crate::message::{ + ConnectionType, Head, MessagePool, RequestHead, RequestHeadType, ResponseHead, +}; bitflags! { struct Flags: u8 { @@ -197,7 +199,9 @@ impl Encoder for ClientCodec { Message::Item((mut head, length)) => { let inner = &mut self.inner; inner.version = head.as_ref().version; - inner.flags.set(Flags::HEAD, head.as_ref().method == Method::HEAD); + inner + .flags + .set(Flags::HEAD, head.as_ref().method == Method::HEAD); // connection status inner.ctype = match head.as_ref().connection_type() { diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 380dfe32..51ea497e 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -2,9 +2,9 @@ use std::fmt::Write as FmtWrite; use std::io::Write; use std::marker::PhantomData; +use std::rc::Rc; use std::str::FromStr; use std::{cmp, fmt, io, mem}; -use std::rc::Rc; use bytes::{BufMut, Bytes, BytesMut}; @@ -16,7 +16,7 @@ use crate::http::header::{ HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, }; use crate::http::{HeaderMap, Method, StatusCode, Version}; -use crate::message::{ConnectionType, Head, RequestHead, ResponseHead, RequestHeadType}; +use crate::message::{ConnectionType, Head, RequestHead, RequestHeadType, ResponseHead}; use crate::request::Request; use crate::response::Response; @@ -134,10 +134,11 @@ pub(crate) trait MessageType: Sized { // merging headers from head and extra headers. HeaderMap::new() does not allocate. let empty_headers = HeaderMap::new(); let extra_headers = self.extra_headers().unwrap_or(&empty_headers); - let headers = self.headers().inner.iter() - .filter(|(name, _)| { - !extra_headers.contains_key(*name) - }) + let headers = self + .headers() + .inner + .iter() + .filter(|(name, _)| !extra_headers.contains_key(*name)) .chain(extra_headers.inner.iter()); // write headers @@ -604,10 +605,16 @@ mod tests { let mut bytes = BytesMut::with_capacity(2048); let mut head = RequestHead::default(); - head.headers.insert(AUTHORIZATION, HeaderValue::from_static("some authorization")); + head.headers.insert( + AUTHORIZATION, + HeaderValue::from_static("some authorization"), + ); let mut extra_headers = HeaderMap::new(); - extra_headers.insert(AUTHORIZATION,HeaderValue::from_static("another authorization")); + extra_headers.insert( + AUTHORIZATION, + HeaderValue::from_static("another authorization"), + ); extra_headers.insert(DATE, HeaderValue::from_static("date")); let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers)); diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 3312a580..a7c787f4 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -178,13 +178,15 @@ impl InnerMultipart { Some(chunk) => { if chunk.len() < boundary.len() + 4 || &chunk[..2] != b"--" - || &chunk[2..boundary.len() + 2] != boundary.as_bytes() { + || &chunk[2..boundary.len() + 2] != boundary.as_bytes() + { Err(MultipartError::Boundary) } else if &chunk[boundary.len() + 2..] == b"\r\n" { Ok(Some(false)) } else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" && (chunk.len() == boundary.len() + 4 - || &chunk[boundary.len() + 4..] == b"\r\n") { + || &chunk[boundary.len() + 4..] == b"\r\n") + { Ok(Some(true)) } else { Err(MultipartError::Boundary) @@ -781,8 +783,10 @@ impl PayloadBuffer { /// Read bytes until new line delimiter or eof pub fn readline_or_eof(&mut self) -> Result, MultipartError> { match self.readline() { - Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.take().freeze())), - line => line + Err(MultipartError::Incomplete) if self.eof => { + Ok(Some(self.buf.take().freeze())) + } + line => line, } } @@ -859,14 +863,14 @@ mod tests { fn create_simple_request_with_header() -> (Bytes, HeaderMap) { let bytes = Bytes::from( "testasdadsad\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - test\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - data\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n" + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ + Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ + test\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ + Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ + data\r\n\ + --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n", ); let mut headers = HeaderMap::new(); headers.insert( diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 82fd6a75..97864d30 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,5 +1,5 @@ -use std::{fmt, io, net}; use std::rc::Rc; +use std::{fmt, io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::Body; @@ -7,8 +7,8 @@ use actix_http::client::{ Connect as ClientConnect, ConnectError, Connection, SendRequestError, }; use actix_http::h1::ClientCodec; -use actix_http::{RequestHead, RequestHeadType, ResponseHead}; use actix_http::http::HeaderMap; +use actix_http::{RequestHead, RequestHeadType, ResponseHead}; use actix_service::Service; use futures::{Future, Poll}; @@ -82,7 +82,9 @@ where }) .from_err() // send request - .and_then(move |connection| connection.send_request(RequestHeadType::from(head), body)) + .and_then(move |connection| { + connection.send_request(RequestHeadType::from(head), body) + }) .map(|(head, payload)| ClientResponse::new(head, payload)), ) } @@ -103,7 +105,10 @@ where }) .from_err() // send request - .and_then(move |connection| connection.send_request(RequestHeadType::Rc(head, extra_headers), body)) + .and_then(move |connection| { + connection + .send_request(RequestHeadType::Rc(head, extra_headers), body) + }) .map(|(head, payload)| ClientResponse::new(head, payload)), ) } @@ -127,7 +132,9 @@ where }) .from_err() // send request - .and_then(move |connection| connection.open_tunnel(RequestHeadType::from(head))) + .and_then(move |connection| { + connection.open_tunnel(RequestHeadType::from(head)) + }) .map(|(head, framed)| { let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); (head, framed) @@ -155,7 +162,9 @@ where }) .from_err() // send request - .and_then(move |connection| connection.open_tunnel(RequestHeadType::Rc(head, extra_headers))) + .and_then(move |connection| { + connection.open_tunnel(RequestHeadType::Rc(head, extra_headers)) + }) .map(|(head, framed)| { let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); (head, framed) diff --git a/awc/src/error.rs b/awc/src/error.rs index 4eb92900..eb8d03e2 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,5 +1,7 @@ //! Http client errors -pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError, FreezeRequestError}; +pub use actix_http::client::{ + ConnectError, FreezeRequestError, InvalidUrl, SendRequestError, +}; pub use actix_http::error::PayloadError; pub use actix_http::ws::HandshakeError as WsHandshakeError; pub use actix_http::ws::ProtocolError as WsProtocolError; diff --git a/awc/src/sender.rs b/awc/src/sender.rs index c8e169cb..95109b92 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -90,7 +90,7 @@ impl Future for SendClientRequest { Ok(Async::Ready(res)) } SendClientRequest::Err(ref mut e) => match e.take() { - Some(e) => Err(e.into()), + Some(e) => Err(e), None => panic!("Attempting to call completed future"), }, } @@ -153,7 +153,7 @@ impl RequestSender { SendClientRequest::new( fut, response_decompress, - timeout.or_else(|| config.timeout.clone()), + timeout.or_else(|| config.timeout), ) } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 67be9e9d..77cbc7ca 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -234,7 +234,10 @@ impl WebsocketsRequest { } if !self.head.headers.contains_key(header::HOST) { - self.head.headers.insert(header::HOST, HeaderValue::from_str(uri.host().unwrap()).unwrap()); + self.head.headers.insert( + header::HOST, + HeaderValue::from_str(uri.host().unwrap()).unwrap(), + ); } // set cookies diff --git a/src/app_service.rs b/src/app_service.rs index 736c3501..513b4aa4 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -169,7 +169,7 @@ where match self.data_factories_fut[idx].poll()? { Async::Ready(f) => { self.data_factories.push(f); - self.data_factories_fut.remove(idx); + let _ = self.data_factories_fut.remove(idx); } Async::NotReady => idx += 1, } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 311d0ee9..84e0758b 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -2,13 +2,13 @@ mod compress; pub use self::compress::{BodyEncoding, Compress}; +mod condition; mod defaultheaders; pub mod errhandlers; mod logger; mod normalize; -mod condition; +pub use self::condition::Condition; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; pub use self::normalize::NormalizePath; -pub use self::condition::Condition; From e35d930ef9d2c7c33a36cd9760f44b0b3bb2d6f5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Sep 2019 21:58:08 +0600 Subject: [PATCH 1563/1635] prepare releases --- actix-multipart/CHANGES.md | 3 ++- actix-multipart/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 365dca28..ca61176c 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,5 +1,6 @@ # Changes -## [0.1.4] - 2019-xx-xx + +## [0.1.4] - 2019-09-12 * Multipart handling now parses requests which do not end in CRLF #1038 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index b26681e2..2168c259 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.3" +version = "0.1.4" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 3d15c943..3a86193c 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.5" +version = "0.2.6" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" From a32573bb58727059afa470bf5d596b03f6616b7e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 13 Sep 2019 11:56:24 +0600 Subject: [PATCH 1564/1635] Allow to re-construct ServiceRequest from HttpRequest and Payload #1088 --- CHANGES.md | 8 +++---- actix-http/CHANGES.md | 2 ++ src/service.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f37f8b46..2a2e2e41 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,13 +1,13 @@ # Changes -## not released yet + +## [1.0.8] - 2019-09-xx ### Added -* Add `middleware::Conditon` that conditionally enables another middleware +* Add `middleware::Conditon` that conditionally enables another middleware -### Fixed +* Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` -* h2 will use error response #1080 ## [1.0.7] - 2019-08-29 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d603cde7..84983937 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -8,6 +8,8 @@ ### Fixed +* h2 will use error response #1080 + * on_connect result isn't added to request extensions for http2 requests #1009 diff --git a/src/service.rs b/src/service.rs index 1d475cf1..8b94dd28 100644 --- a/src/service.rs +++ b/src/service.rs @@ -68,6 +68,34 @@ impl ServiceRequest { (self.0, pl) } + /// Construct request from parts. + /// + /// `ServiceRequest` can be re-constructed only if `req` hasnt been cloned. + pub fn from_parts( + mut req: HttpRequest, + pl: Payload, + ) -> Result { + if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 { + Rc::get_mut(&mut req.0).unwrap().payload = pl; + Ok(ServiceRequest(req)) + } else { + Err((req, pl)) + } + } + + /// Construct request from request. + /// + /// `HttpRequest` implements `Clone` trait via `Rc` type. `ServiceRequest` + /// can be re-constructed only if rc's strong pointers count eq 1 and + /// weak pointers count is 0. + pub fn from_request(req: HttpRequest) -> Result { + if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 { + Ok(ServiceRequest(req)) + } else { + Err(req) + } + } + /// Create service response #[inline] pub fn into_response>>(self, res: R) -> ServiceResponse { @@ -514,6 +542,27 @@ mod tests { use crate::test::{call_service, init_service, TestRequest}; use crate::{guard, http, web, App, HttpResponse}; + #[test] + fn test_service_request() { + let req = TestRequest::default().to_srv_request(); + let (r, pl) = req.into_parts(); + assert!(ServiceRequest::from_parts(r, pl).is_ok()); + + let req = TestRequest::default().to_srv_request(); + let (r, pl) = req.into_parts(); + let _r2 = r.clone(); + assert!(ServiceRequest::from_parts(r, pl).is_err()); + + let req = TestRequest::default().to_srv_request(); + let (r, _pl) = req.into_parts(); + assert!(ServiceRequest::from_request(r).is_ok()); + + let req = TestRequest::default().to_srv_request(); + let (r, _pl) = req.into_parts(); + let _r2 = r.clone(); + assert!(ServiceRequest::from_request(r).is_err()); + } + #[test] fn test_service() { let mut srv = init_service( From c1f99e0775b986d57244e4ef20faa8982f0dac88 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 16 Sep 2019 07:52:23 +0900 Subject: [PATCH 1565/1635] Remove `mem::uninitialized()` (#1090) --- actix-http/src/client/connector.rs | 2 +- actix-http/src/h1/decoder.rs | 13 ++++++++----- actix-http/src/helpers.rs | 2 +- actix-http/src/test.rs | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 98e8618c..d92441f2 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -212,7 +212,7 @@ where pub fn finish( self, ) -> impl Service - + Clone { + + Clone { #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] { let connector = TimeoutService::new( diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index c7ef065a..ce113a14 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -1,5 +1,6 @@ +use std::io; use std::marker::PhantomData; -use std::{io, mem}; +use std::mem::MaybeUninit; use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; @@ -186,11 +187,12 @@ impl MessageType for Request { fn decode(src: &mut BytesMut) -> Result, ParseError> { // Unsafe: we read only this data only after httparse parses headers into. // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; + let mut headers: [HeaderIndex; MAX_HEADERS] = + unsafe { MaybeUninit::uninit().assume_init() }; let (len, method, uri, ver, h_len) = { let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; + unsafe { MaybeUninit::uninit().assume_init() }; let mut req = httparse::Request::new(&mut parsed); match req.parse(src)? { @@ -260,11 +262,12 @@ impl MessageType for ResponseHead { fn decode(src: &mut BytesMut) -> Result, ParseError> { // Unsafe: we read only this data only after httparse parses headers into. // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() }; + let mut headers: [HeaderIndex; MAX_HEADERS] = + unsafe { MaybeUninit::uninit().assume_init() }; let (len, ver, status, h_len) = { let mut parsed: [httparse::Header; MAX_HEADERS] = - unsafe { mem::uninitialized() }; + unsafe { MaybeUninit::uninit().assume_init() }; let mut res = httparse::Response::new(&mut parsed); match res.parse(src)? { diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index e4583ee3..84403d8f 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -115,7 +115,7 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) { pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { let mut curr: isize = 39; - let mut buf: [u8; 41] = unsafe { mem::uninitialized() }; + let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() }; buf[39] = b'\r'; buf[40] = b'\n'; let buf_ptr = buf.as_mut_ptr(); diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index ce81a54d..ed5b81a3 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -150,7 +150,7 @@ impl TestRequest { /// Complete request creation and generate `Request` instance pub fn finish(&mut self) -> Request { - let inner = self.0.take().expect("cannot reuse test request builder");; + let inner = self.0.take().expect("cannot reuse test request builder"); let mut req = if let Some(pl) = inner.payload { Request::with_payload(pl) From 7c9f9afc46d2bf1c0a69826b2de41fa4f59f97a9 Mon Sep 17 00:00:00 2001 From: nWacky <38620459+nWacky@users.noreply.github.com> Date: Tue, 17 Sep 2019 03:57:39 +0300 Subject: [PATCH 1566/1635] Add ability to use `Infallible` as `HttpResponse` error type (#1093) * Add `std::convert::Infallible` implementantion for `ResponseError` * Add from `std::convert::Infallible` to `Error` * Remove `ResponseError` implementantion for `Infallible` * Remove useless docs * Better comment * Update changelog * Update actix_http::changelog --- actix-http/CHANGES.md | 3 +++ actix-http/src/error.rs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 84983937..6820626f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,9 @@ * Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` +* Allow to use `std::convert::Infallible` as `actix_http::error::Error` + + ### Fixed * h2 will use error response #1080 diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 2c01c86d..90c35e48 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -132,6 +132,14 @@ impl std::error::Error for Error { } } +impl From for Error { + fn from(_: std::convert::Infallible) -> Self { + // `std::convert::Infallible` indicates an error + // that will never happen + unreachable!() + } +} + /// Convert `Error` to a `Response` instance impl From for Response { fn from(err: Error) -> Self { From 32a1c365975acffb25959f5887a6530396788504 Mon Sep 17 00:00:00 2001 From: Jos van den Oever Date: Tue, 17 Sep 2019 02:58:04 +0200 Subject: [PATCH 1567/1635] Make UrlencodedError::Overflow more informative (#1089) --- CHANGES.md | 1 + src/error.rs | 13 +++++++++---- src/types/form.rs | 16 +++++++++++----- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2a2e2e41..95f8b75e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ * Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` +* Make UrlEncodedError::Overflow more informativve ## [1.0.7] - 2019-08-29 diff --git a/src/error.rs b/src/error.rs index 9f31582e..a60276a7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,8 +32,12 @@ pub enum UrlencodedError { #[display(fmt = "Can not decode chunked transfer encoding")] Chunked, /// Payload size is bigger than allowed. (default: 256kB) - #[display(fmt = "Urlencoded payload size is bigger than allowed (default: 256kB)")] - Overflow, + #[display( + fmt = "Urlencoded payload size is bigger ({} bytes) than allowed (default: {} bytes)", + size, + limit + )] + Overflow { size: usize, limit: usize }, /// Payload size is now known #[display(fmt = "Payload size is now known")] UnknownLength, @@ -52,7 +56,7 @@ pub enum UrlencodedError { impl ResponseError for UrlencodedError { fn error_response(&self) -> HttpResponse { match *self { - UrlencodedError::Overflow => { + UrlencodedError::Overflow { .. } => { HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) } UrlencodedError::UnknownLength => { @@ -164,7 +168,8 @@ mod tests { #[test] fn test_urlencoded_error() { - let resp: HttpResponse = UrlencodedError::Overflow.error_response(); + let resp: HttpResponse = + UrlencodedError::Overflow { size: 0, limit: 0 }.error_response(); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); let resp: HttpResponse = UrlencodedError::UnknownLength.error_response(); assert_eq!(resp.status(), StatusCode::LENGTH_REQUIRED); diff --git a/src/types/form.rs b/src/types/form.rs index 9ab98b17..3bc067ab 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -318,7 +318,7 @@ where let limit = self.limit; if let Some(len) = self.length.take() { if len > limit { - return Err(UrlencodedError::Overflow); + return Err(UrlencodedError::Overflow { size: len, limit }); } } @@ -331,7 +331,10 @@ where .from_err() .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow) + Err(UrlencodedError::Overflow { + size: body.len() + chunk.len(), + limit, + }) } else { body.extend_from_slice(&chunk); Ok(body) @@ -390,8 +393,8 @@ mod tests { fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { match err { - UrlencodedError::Overflow => match other { - UrlencodedError::Overflow => true, + UrlencodedError::Overflow { .. } => match other { + UrlencodedError::Overflow { .. } => true, _ => false, }, UrlencodedError::UnknownLength => match other { @@ -420,7 +423,10 @@ mod tests { .header(CONTENT_LENGTH, "1000000") .to_http_parts(); let info = block_on(UrlEncoded::::new(&req, &mut pl)); - assert!(eq(info.err().unwrap(), UrlencodedError::Overflow)); + assert!(eq( + info.err().unwrap(), + UrlencodedError::Overflow { size: 0, limit: 0 } + )); let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") .header(CONTENT_LENGTH, "10") From e4503046de1263148e1b56394144b1828bbfdac0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 17 Sep 2019 21:45:06 +0600 Subject: [PATCH 1568/1635] Do not override current System --- test-server/CHANGES.md | 5 +++ test-server/Cargo.toml | 8 ++--- test-server/src/lib.rs | 81 +++++++++++++++++++++--------------------- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index c3fe5b28..798dbf50 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,10 +1,15 @@ # Changes +## [0.2.5] - 2019-0917 ### Changed * Update serde_urlencoded to "0.6.1" +### Fixed + +* Do not override current `System` + ## [0.2.4] - 2019-07-18 diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 22809c06..77301b0a 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.2.4" +version = "0.2.5" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -35,7 +35,7 @@ actix-rt = "0.2.2" actix-service = "0.4.1" actix-server = "0.6.0" actix-utils = "0.4.1" -awc = "0.2.2" +awc = "0.2.6" actix-connect = "0.2.2" base64 = "0.10" @@ -56,5 +56,5 @@ tokio-timer = "0.2" openssl = { version="0.10", optional = true } [dev-dependencies] -actix-web = "1.0.0" -actix-http = "0.2.4" +actix-web = "1.0.7" +actix-http = "0.2.9" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index aba53980..a2366bf4 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -103,8 +103,8 @@ pub struct TestServer; /// Test server controller pub struct TestServerRuntime { addr: net::SocketAddr, - rt: Runtime, client: Client, + system: System, } impl TestServer { @@ -130,45 +130,47 @@ impl TestServer { }); let (system, addr) = rx.recv().unwrap(); - let mut rt = Runtime::new().unwrap(); - let client = rt - .block_on(lazy(move || { - let connector = { - #[cfg(feature = "ssl")] - { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + let client = block_on(lazy(move || { + let connector = { + #[cfg(feature = "ssl")] + { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - let mut builder = - SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").map_err( - |e| log::error!("Can not set alpn protocol: {:?}", e), - ); - Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(500)) - .ssl(builder.build()) - .finish() - } - #[cfg(not(feature = "ssl"))] - { - Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(500)) - .finish() - } - }; + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) + .timeout(time::Duration::from_millis(500)) + .ssl(builder.build()) + .finish() + } + #[cfg(not(feature = "ssl"))] + { + Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) + .timeout(time::Duration::from_millis(500)) + .finish() + } + }; - Ok::(Client::build().connector(connector).finish()) - })) - .unwrap(); - rt.block_on(lazy( + Ok::(Client::build().connector(connector).finish()) + })) + .unwrap(); + + block_on(lazy( || Ok::<_, ()>(actix_connect::start_default_resolver()), )) .unwrap(); - System::set_current(system); - TestServerRuntime { addr, rt, client } + + TestServerRuntime { + addr, + client, + system, + } } /// Get first available unused address @@ -188,7 +190,7 @@ impl TestServerRuntime { where F: Future, { - self.rt.block_on(fut) + block_on(fut) } /// Execute future on current core @@ -197,7 +199,7 @@ impl TestServerRuntime { F: FnOnce() -> R, R: Future, { - self.rt.block_on(lazy(f)) + block_on(lazy(f)) } /// Execute function on current core @@ -205,7 +207,7 @@ impl TestServerRuntime { where F: FnOnce() -> R, { - self.rt.block_on(lazy(|| Ok::<_, ()>(fut()))).unwrap() + block_on(lazy(|| Ok::<_, ()>(fut()))).unwrap() } /// Construct test server url @@ -324,8 +326,7 @@ impl TestServerRuntime { { let url = self.url(path); let connect = self.client.ws(url).connect(); - self.rt - .block_on(lazy(move || connect.map(|(_, framed)| framed))) + block_on(lazy(move || connect.map(|(_, framed)| framed))) } /// Connect to a websocket server @@ -338,7 +339,7 @@ impl TestServerRuntime { /// Stop http server fn stop(&mut self) { - System::current().stop(); + self.system.stop(); } } From 58c7065f08dddb2a9370eee468a10aa29d8cd641 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Tue, 17 Sep 2019 17:36:39 -0700 Subject: [PATCH 1569/1635] Implement `register_data` method on `Resource` and `Scope`. (#1094) * Implement `register_data` method on `Resource` and `Scope`. * Split Scope::register_data tests out from Scope::data tests. * CHANGES.md: Mention {Scope,Resource}::register_data. --- CHANGES.md | 10 +++++++++- src/resource.rs | 40 ++++++++++++++++++++++++++++++++++++++-- src/scope.rs | 31 +++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 95f8b75e..ead84ac8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,18 @@ # Changes +## not released yet + +### Added + +* Add `Scope::register_data` and `Resource::register_data` methods, parallel to + `App::register_data`. ## [1.0.8] - 2019-09-xx ### Added -* Add `middleware::Conditon` that conditionally enables another middleware +* Add `middleware::Condition` that conditionally enables another middleware + +### Fixed * Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` diff --git a/src/resource.rs b/src/resource.rs index b872049d..b711fc32 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -189,11 +189,21 @@ where /// )); /// } /// ``` - pub fn data(mut self, data: U) -> Self { + pub fn data(self, data: U) -> Self { + self.register_data(Data::new(data)) + } + + /// Set or override application data. + /// + /// This method has the same effect as [`Resource::data`](#method.data), + /// except that instead of taking a value of some type `T`, it expects a + /// value of type `Data`. Use a `Data` extractor to retrieve its + /// value. + pub fn register_data(mut self, data: Data) -> Self { if self.data.is_none() { self.data = Some(Extensions::new()); } - self.data.as_mut().unwrap().insert(Data::new(data)); + self.data.as_mut().unwrap().insert(data); self } @@ -763,4 +773,30 @@ mod tests { let resp = call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::NO_CONTENT); } + + #[test] + fn test_data() { + let mut srv = init_service( + App::new() + .data(1.0f64) + .data(1usize) + .register_data(web::Data::new('-')) + .service( + web::resource("/test") + .data(10usize) + .register_data(web::Data::new('*')) + .guard(guard::Get()) + .to(|data1: web::Data, data2: web::Data, data3: web::Data| { + assert_eq!(*data1, 10); + assert_eq!(*data2, '*'); + assert_eq!(*data3, 1.0); + HttpResponse::Ok() + }), + ) + ); + + let req = TestRequest::get().uri("/test").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + } } diff --git a/src/scope.rs b/src/scope.rs index 760fee47..06ebbd94 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -148,11 +148,20 @@ where /// ); /// } /// ``` - pub fn data(mut self, data: U) -> Self { + pub fn data(self, data: U) -> Self { + self.register_data(Data::new(data)) + } + + /// Set or override application data. + /// + /// This method has the same effect as [`Scope::data`](#method.data), except + /// that instead of taking a value of some type `T`, it expects a value of + /// type `Data`. Use a `Data` extractor to retrieve its value. + pub fn register_data(mut self, data: Data) -> Self { if self.data.is_none() { self.data = Some(Extensions::new()); } - self.data.as_mut().unwrap().insert(Data::new(data)); + self.data.as_mut().unwrap().insert(data); self } @@ -1082,6 +1091,24 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } + #[test] + fn test_override_register_data() { + let mut srv = init_service(App::new().register_data(web::Data::new(1usize)).service( + web::scope("app").register_data(web::Data::new(10usize)).route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + )); + + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + } + #[test] fn test_scope_config() { let mut srv = From aa39b8ca6fe7160446cc1adbd943c63014413e30 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Wed, 25 Sep 2019 11:33:52 +0800 Subject: [PATCH 1570/1635] Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() (#1096) * Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() * Update actix-http/CHANGES.md --- actix-http/CHANGES.md | 7 +++++++ actix-http/src/body.rs | 19 +++++++++++++++++++ actix-http/src/response.rs | 8 ++++++++ 3 files changed, 34 insertions(+) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6820626f..acba0796 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## Not released yet + +### Added + +* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() + + ## [0.2.11] - 2019-09-11 ### Added diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index e728cdb9..b761738e 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -234,6 +234,12 @@ impl From for Body { } } +impl From for Body { + fn from(v: serde_json::Value) -> Body { + Body::Bytes(v.to_string().into()) + } +} + impl From> for Body where S: Stream + 'static, @@ -548,4 +554,17 @@ mod tests { assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1")); } + + #[test] + fn test_serde_json() { + use serde_json::json; + assert_eq!( + Body::from(serde_json::Value::String("test".into())).size(), + BodySize::Sized(6) + ); + assert_eq!( + Body::from(json!({"test-key":"test-value"})).size(), + BodySize::Sized(25) + ); + } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 124bf5f9..5b0b3bc8 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -992,6 +992,14 @@ mod tests { assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); } + #[test] + fn test_serde_json_in_body() { + use serde_json::json; + let resp = + Response::build(StatusCode::OK).body(json!({"test-key":"test-value"})); + assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#); + } + #[test] fn test_into_response() { let resp: Response = "test".into(); From d9af8f66ba7ab645f160ee195136fcd1147cea99 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 25 Sep 2019 10:28:41 +0600 Subject: [PATCH 1571/1635] Use actix-testing for testing utils --- CHANGES.md | 14 +++--- Cargo.toml | 3 +- actix-http/src/client/connector.rs | 2 +- src/resource.rs | 18 ++++--- src/scope.rs | 22 +++++---- src/test.rs | 77 +----------------------------- 6 files changed, 36 insertions(+), 100 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ead84ac8..c27c8865 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,23 +1,23 @@ # Changes -## not released yet + +## [1.0.8] - 2019-09-xx ### Added * Add `Scope::register_data` and `Resource::register_data` methods, parallel to `App::register_data`. -## [1.0.8] - 2019-09-xx - -### Added - * Add `middleware::Condition` that conditionally enables another middleware -### Fixed - * Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` +### Changed + * Make UrlEncodedError::Overflow more informativve +* Use actix-testing for testing utils + + ## [1.0.7] - 2019-08-29 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index c2d3b0d2..b0d34e1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.7" +version = "1.0.8" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -81,6 +81,7 @@ actix-web-codegen = "0.1.2" actix-http = "0.2.9" actix-server = "0.6.0" actix-server-config = "0.1.2" +actix-testing = "0.1.0" actix-threadpool = "0.1.1" awc = { version = "0.2.4", optional = true } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index d92441f2..98e8618c 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -212,7 +212,7 @@ where pub fn finish( self, ) -> impl Service - + Clone { + + Clone { #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] { let connector = TimeoutService::new( diff --git a/src/resource.rs b/src/resource.rs index b711fc32..3ee0167a 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -786,13 +786,17 @@ mod tests { .data(10usize) .register_data(web::Data::new('*')) .guard(guard::Get()) - .to(|data1: web::Data, data2: web::Data, data3: web::Data| { - assert_eq!(*data1, 10); - assert_eq!(*data2, '*'); - assert_eq!(*data3, 1.0); - HttpResponse::Ok() - }), - ) + .to( + |data1: web::Data, + data2: web::Data, + data3: web::Data| { + assert_eq!(*data1, 10); + assert_eq!(*data2, '*'); + assert_eq!(*data3, 1.0); + HttpResponse::Ok() + }, + ), + ), ); let req = TestRequest::get().uri("/test").to_request(); diff --git a/src/scope.rs b/src/scope.rs index 06ebbd94..c152bc33 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1093,16 +1093,20 @@ mod tests { #[test] fn test_override_register_data() { - let mut srv = init_service(App::new().register_data(web::Data::new(1usize)).service( - web::scope("app").register_data(web::Data::new(10usize)).route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), + let mut srv = init_service( + App::new().register_data(web::Data::new(1usize)).service( + web::scope("app") + .register_data(web::Data::new(10usize)) + .route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), ), - )); + ); let req = TestRequest::with_uri("/app/t").to_request(); let resp = call_service(&mut srv, req); diff --git a/src/test.rs b/src/test.rs index 903679ca..6563253c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,5 +1,4 @@ //! Various helpers for Actix applications to use during testing. -use std::cell::RefCell; use std::rc::Rc; use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue}; @@ -7,17 +6,17 @@ use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, Extensions, Request}; use actix_router::{Path, ResourceDef, Url}; -use actix_rt::{System, SystemRunner}; use actix_server_config::ServerConfig; use actix_service::{IntoNewService, IntoService, NewService, Service}; use bytes::{Bytes, BytesMut}; -use futures::future::{lazy, ok, Future, IntoFuture}; +use futures::future::{ok, Future}; use futures::Stream; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; pub use actix_http::test::TestBuffer; +pub use actix_testing::{block_fn, block_on, run_on}; use crate::config::{AppConfig, AppConfigInner}; use crate::data::Data; @@ -27,78 +26,6 @@ use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; use crate::{Error, HttpRequest, HttpResponse}; -thread_local! { - static RT: RefCell = { - RefCell::new(Inner(Some(System::builder().build()))) - }; -} - -struct Inner(Option); - -impl Inner { - fn get_mut(&mut self) -> &mut SystemRunner { - self.0.as_mut().unwrap() - } -} - -impl Drop for Inner { - fn drop(&mut self) { - std::mem::forget(self.0.take().unwrap()) - } -} - -/// Runs the provided future, blocking the current thread until the future -/// completes. -/// -/// This function can be used to synchronously block the current thread -/// until the provided `future` has resolved either successfully or with an -/// error. The result of the future is then returned from this function -/// call. -/// -/// Note that this function is intended to be used only for testing purpose. -/// This function panics on nested call. -pub fn block_on(f: F) -> Result -where - F: IntoFuture, -{ - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f.into_future())) -} - -/// Runs the provided function, blocking the current thread until the result -/// future completes. -/// -/// This function can be used to synchronously block the current thread -/// until the provided `future` has resolved either successfully or with an -/// error. The result of the future is then returned from this function -/// call. -/// -/// Note that this function is intended to be used only for testing purpose. -/// This function panics on nested call. -pub fn block_fn(f: F) -> Result -where - F: FnOnce() -> R, - R: IntoFuture, -{ - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(f))) -} - -#[doc(hidden)] -/// Runs the provided function, with runtime enabled. -/// -/// Note that this function is intended to be used only for testing purpose. -/// This function panics on nested call. -pub fn run_on(f: F) -> R -where - F: FnOnce() -> R, -{ - RT.with(move |rt| { - rt.borrow_mut() - .get_mut() - .block_on(lazy(|| Ok::<_, ()>(f()))) - }) - .unwrap() -} - /// Create service that always responds with `HttpResponse::Ok()` pub fn ok_service( ) -> impl Service, Error = Error> From 23f04c4f38ee3ff47eafb2f26338e2e6561f591e Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Wed, 25 Sep 2019 08:50:45 +0200 Subject: [PATCH 1572/1635] Add remaining getter methods from private head field --- awc/src/request.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/awc/src/request.rs b/awc/src/request.rs index d597a163..a90cf60b 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -96,7 +96,7 @@ impl ClientRequest { self } - /// Get HTTP URI of request + /// Get HTTP URI of request. pub fn get_uri(&self) -> &Uri { &self.head.uri } @@ -132,6 +132,16 @@ impl ClientRequest { self } + /// Get HTTP version of this request. + pub fn get_version(&self) -> &Version { + &self.head.version + } + + /// Get peer address of this request. + pub fn get_peer_addr(&self) -> &Option { + &self.head.peer_addr + } + #[inline] /// Returns request's headers. pub fn headers(&self) -> &HeaderMap { From c659c33919c4880dbe3d220773f20fc6c5b58070 Mon Sep 17 00:00:00 2001 From: karlri <49443488+karlri@users.noreply.github.com> Date: Wed, 25 Sep 2019 11:16:51 +0200 Subject: [PATCH 1573/1635] Feature uds: Add listen_uds to ServerBuilder (#1085) Allows using an existing Unix Listener instead of binding to a path. Useful for when running as a daemon under systemd. Change-Id: I54a0e78c321d8b7a9ded381083217af590e9a7fa --- CHANGES.md | 3 +++ Cargo.toml | 2 +- src/server.rs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c27c8865..86e5e8a4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,9 @@ * Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` +* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, + which is useful for example with systemd. + ### Changed * Make UrlEncodedError::Overflow more informativve diff --git a/Cargo.toml b/Cargo.toml index b0d34e1c..5a01c442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ actix-router = "0.1.5" actix-rt = "0.2.4" actix-web-codegen = "0.1.2" actix-http = "0.2.9" -actix-server = "0.6.0" +actix-server = "0.6.1" actix-server-config = "0.1.2" actix-testing = "0.1.0" actix-threadpool = "0.1.1" diff --git a/src/server.rs b/src/server.rs index d1a019a1..51492eb0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -435,6 +435,37 @@ where Ok(self) } + #[cfg(feature = "uds")] + /// Start listening for unix domain connections on existing listener. + /// + /// This method is available with `uds` feature. + pub fn listen_uds( + mut self, + lst: std::os::unix::net::UnixListener, + ) -> io::Result { + let cfg = self.config.clone(); + let factory = self.factory.clone(); + // todo duplicated: + self.sockets.push(Socket { + scheme: "http", + addr: net::SocketAddr::new( + net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), + 8080, + ), + }); + + let addr = format!("actix-web-service-{:?}", lst.local_addr()?); + + self.builder = self.builder.listen_uds(addr, lst, move || { + let c = cfg.lock(); + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .finish(factory()) + })?; + Ok(self) + } + #[cfg(feature = "uds")] /// Start listening for incoming unix domain connections. /// From 3d4e45a0e56979ba2f316e752e399f1ccb35154f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 25 Sep 2019 15:30:20 +0600 Subject: [PATCH 1574/1635] prepare release --- awc/CHANGES.md | 8 ++++++++ awc/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 94ad65ff..3ea1790b 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,13 @@ # Changes + +## [0.2.7] - 2019-09-25 + +### Added + +* Add remaining getter methods from private head field #1101 + + ## [0.2.6] - 2019-09-12 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 3a86193c..6f0f63f9 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.6" +version = "0.2.7" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" From 3ff01a9fc487d729781c4206ab3c9ea920c8c23b Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Wed, 25 Sep 2019 11:35:28 +0200 Subject: [PATCH 1575/1635] Add changelog entry for #1101 (#1102) --- awc/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3ea1790b..6f8fe2db 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -5,7 +5,7 @@ ### Added -* Add remaining getter methods from private head field #1101 +* Remaining getter methods for `ClientRequest`'s private `head` field #1101 ## [0.2.6] - 2019-09-12 From 4f3e97fff800267c6c21440540d6c01a4f297538 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 25 Sep 2019 15:39:09 +0600 Subject: [PATCH 1576/1635] prepare actix-web release --- CHANGES.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 86e5e8a4..b984e68c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.8] - 2019-09-xx +## [1.0.8] - 2019-09-25 ### Added diff --git a/Cargo.toml b/Cargo.toml index 5a01c442..35ca28b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ actix-server = "0.6.1" actix-server-config = "0.1.2" actix-testing = "0.1.0" actix-threadpool = "0.1.1" -awc = { version = "0.2.4", optional = true } +awc = { version = "0.2.7", optional = true } bytes = "0.4" derive_more = "0.15.0" From 5169d306ae5311b0955b8927ad0fbdbdb348df1d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 27 Sep 2019 07:03:12 +0600 Subject: [PATCH 1577/1635] update ConnectionInfo.remote() doc string --- actix-http/CHANGES.md | 2 +- src/info.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index acba0796..06756033 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,7 +7,7 @@ * Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() -## [0.2.11] - 2019-09-11 +## [0.2.10] - 2019-09-11 ### Added diff --git a/src/info.rs b/src/info.rs index ba59605d..61914516 100644 --- a/src/info.rs +++ b/src/info.rs @@ -155,9 +155,9 @@ impl ConnectionInfo { &self.host } - /// Remote IP of client initiated HTTP request. + /// Remote socket addr of client initiated HTTP request. /// - /// The IP is resolved through the following headers, in this order: + /// The addr is resolved through the following headers, in this order: /// /// - Forwarded /// - X-Forwarded-For From f81ae37677e36eba58ff059466efb5c66eb89824 Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Tue, 1 Oct 2019 11:05:38 +0300 Subject: [PATCH 1578/1635] Add From for crate::dev::Payload (#1110) * Add From for crate::dev::Payload * Make dev::Payload field of Payload public and add into_inner method * Add changelog entry --- CHANGES.md | 6 ++++++ src/types/payload.rs | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b984e68c..4ff7d1e6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## [1.0.9] - 2019-xx-xx + +### Added + +* Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) + ## [1.0.8] - 2019-09-25 ### Added diff --git a/src/types/payload.rs b/src/types/payload.rs index f33e2e5f..8fc5f093 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -43,7 +43,14 @@ use crate::request::HttpRequest; /// ); /// } /// ``` -pub struct Payload(crate::dev::Payload); +pub struct Payload(pub crate::dev::Payload); + +impl Payload { + /// Deconstruct to a inner value + pub fn into_inner(self) -> crate::dev::Payload { + self.0 + } +} impl Stream for Payload { type Item = Bytes; From fba31d4e0aa87f1e7f8c59f00079b6ba1aa534cc Mon Sep 17 00:00:00 2001 From: Zac Pullar-Strecker Date: Wed, 2 Oct 2019 16:48:25 +1300 Subject: [PATCH 1579/1635] Expose ContentDisposition in actix-multipart to fix broken doc link (#1114) * Expose ContentDisposition in actix-multipart to fix broken doc link * Revert "Expose ContentDisposition in actix-multipart to fix broken doc link" This reverts commit e90d71d16cb552cd3e1745646fabcc48e0b4e379. * Unhide actix-http::header::common docs These types are used in other exported documented interfaces and create broken links if not documented. See `actix_multipart::Field.content_disposition` --- actix-http/src/header/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 37cf9450..59cbb11c 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -16,7 +16,6 @@ use crate::httpmessage::HttpMessage; mod common; pub(crate) mod map; mod shared; -#[doc(hidden)] pub use self::common::*; #[doc(hidden)] pub use self::shared::*; From 15d3c1ae816b81ef6ff0b5aa284cbc909dfd39e3 Mon Sep 17 00:00:00 2001 From: Koen Hoeijmakers Date: Mon, 7 Oct 2019 05:05:17 +0200 Subject: [PATCH 1580/1635] Update docs of guard.rs (#1116) * Update guard.rs --- src/guard.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/guard.rs b/src/guard.rs index e0b4055b..c6019258 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -1,16 +1,16 @@ //! Route match guards. //! -//! Guards are one of the way how actix-web router chooses -//! handler service. In essence it just function that accepts -//! reference to a `RequestHead` instance and returns boolean. +//! Guards are one of the ways how actix-web router chooses a +//! handler service. In essence it is just a function that accepts a +//! reference to a `RequestHead` instance and returns a boolean. //! It is possible to add guards to *scopes*, *resources* //! and *routes*. Actix provide several guards by default, like various //! http methods, header, etc. To become a guard, type must implement `Guard` //! trait. Simple functions coulds guards as well. //! -//! Guard can not modify request object. But it is possible to -//! to store extra attributes on a request by using `Extensions` container. -//! Extensions container available via `RequestHead::extensions()` method. +//! Guards can not modify the request object. But it is possible +//! to store extra attributes on a request by using the `Extensions` container. +//! Extensions containers are available via the `RequestHead::extensions()` method. //! //! ```rust //! use actix_web::{web, http, dev, guard, App, HttpResponse}; @@ -29,11 +29,11 @@ use actix_http::http::{self, header, uri::Uri, HttpTryFrom}; use actix_http::RequestHead; -/// Trait defines resource guards. Guards are used for routes selection. +/// Trait defines resource guards. Guards are used for route selection. /// -/// Guard can not modify request object. But it is possible to -/// to store extra attributes on request by using `Extensions` container, -/// Extensions container available via `RequestHead::extensions()` method. +/// Guards can not modify the request object. But it is possible +/// to store extra attributes on a request by using the `Extensions` container. +/// Extensions containers are available via the `RequestHead::extensions()` method. pub trait Guard { /// Check if request matches predicate fn check(&self, request: &RequestHead) -> bool; From f089cf185b64cf1f337b47d814a9b6882e7f0d42 Mon Sep 17 00:00:00 2001 From: SuperHacker-liuan <30787037+SuperHacker-liuan@users.noreply.github.com> Date: Mon, 7 Oct 2019 12:56:24 +0800 Subject: [PATCH 1581/1635] Let ResponseError render w/ 'text/plain; charset=utf-8' header (#1118) (#1119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Let ResponseError render w/ 'text/plain; charset=utf-8' header (#1118) Trait ResponseError originally render Error messages with header `text/plain` , which causes browsers (i.e. Firefox 70.0) with Non-English locale unable to render UTF-8 responses with non-English characters correctly. i.e. emoji. This fix solved this problem by specifying the charset of `text/plain` as utf-8, which is the default charset in rust. Before actix-web consider to support other charsets, this hotfix is enough. Test case: fn test() -> Result { Err(actix_web::error::ErrorForbidden("😋test")) } * Update actix-http/CHANGES.md for #1118 --- actix-http/CHANGES.md | 4 ++++ actix-http/src/error.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 06756033..624aca5e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,10 @@ * Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() +### Fixed + +* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118 + ## [0.2.10] - 2019-09-11 diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 90c35e48..cd9613d2 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -75,7 +75,7 @@ pub trait ResponseError: fmt::Debug + fmt::Display { let _ = write!(Writer(&mut buf), "{}", self); resp.headers_mut().insert( header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), + header::HeaderValue::from_static("text/plain; charset=utf-8"), ); resp.set_body(Body::from(buf)) } @@ -536,7 +536,7 @@ where let _ = write!(Writer(&mut buf), "{}", self); res.headers_mut().insert( header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), + header::HeaderValue::from_static("text/plain; charset=utf-8"), ); res.set_body(Body::from(buf)) } From 0f09415469843eea4000dc48085101dcf8d75e9b Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Mon, 7 Oct 2019 08:29:11 +0300 Subject: [PATCH 1582/1635] Convert documentation examples to Rust 2018 edition (#1120) * Convert types::query examples to rust-2018 edition * Convert types::json examples to rust-2018 edition * Convert types::path examples to rust-2018 edition * Convert types::form examples to rust-2018 edition * Convert rest of the examples to rust-2018 edition. --- src/extract.rs | 4 ++-- src/request.rs | 2 +- src/route.rs | 4 ++-- src/types/form.rs | 13 ++++++------- src/types/json.rs | 12 ++++++------ src/types/path.rs | 7 +++---- src/types/query.rs | 6 +++--- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 1687973a..42563731 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -46,9 +46,9 @@ pub trait FromRequest: Sized { /// ## Example /// /// ```rust -/// # #[macro_use] extern crate serde_derive; /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; +/// use serde_derive::Deserialize; /// use rand; /// /// #[derive(Debug, Deserialize)] @@ -119,9 +119,9 @@ where /// ## Example /// /// ```rust -/// # #[macro_use] extern crate serde_derive; /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; +/// use serde_derive::Deserialize; /// use rand; /// /// #[derive(Debug, Deserialize)] diff --git a/src/request.rs b/src/request.rs index 6d9d26e8..ea27e303 100644 --- a/src/request.rs +++ b/src/request.rs @@ -271,8 +271,8 @@ impl Drop for HttpRequest { /// ## Example /// /// ```rust -/// # #[macro_use] extern crate serde_derive; /// use actix_web::{web, App, HttpRequest}; +/// use serde_derive::Deserialize; /// /// /// extract `Thing` from request /// fn index(req: HttpRequest) -> String { diff --git a/src/route.rs b/src/route.rs index f4d30363..35b84294 100644 --- a/src/route.rs +++ b/src/route.rs @@ -178,8 +178,8 @@ impl Route { /// Set handler function, use request extractors for parameters. /// /// ```rust - /// #[macro_use] extern crate serde_derive; /// use actix_web::{web, http, App}; + /// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -239,9 +239,9 @@ impl Route { /// /// ```rust /// # use futures::future::ok; - /// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App, Error}; /// use futures::Future; + /// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { diff --git a/src/types/form.rs b/src/types/form.rs index 3bc067ab..c727ce0e 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -35,9 +35,8 @@ use crate::responder::Responder; /// /// ### Example /// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{web, App}; +/// use actix_web::web; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct FormData { @@ -61,9 +60,9 @@ use crate::responder::Responder; /// /// ### Example /// ```rust -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// # +/// use actix_web::*; +/// use serde_derive::Serialize; +/// /// #[derive(Serialize)] /// struct SomeForm { /// name: String, @@ -167,8 +166,8 @@ impl Responder for Form { /// Form extractor configuration /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App, FromRequest, Result}; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct FormData { diff --git a/src/types/json.rs b/src/types/json.rs index f309a3c5..e80d0a45 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -33,8 +33,8 @@ use crate::responder::Responder; /// ## Example /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App}; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -60,9 +60,9 @@ use crate::responder::Responder; /// trait from *serde*. /// /// ```rust -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// # +/// use actix_web::*; +/// use serde_derive::Serialize; +/// /// #[derive(Serialize)] /// struct MyObj { /// name: String, @@ -144,8 +144,8 @@ impl Responder for Json { /// ## Example /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App}; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -203,8 +203,8 @@ where /// Json extractor configuration /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{error, web, App, FromRequest, HttpResponse}; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { diff --git a/src/types/path.rs b/src/types/path.rs index a4657576..fa7c6e11 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -39,8 +39,8 @@ use crate::FromRequest; /// implements `Deserialize` trait from *serde*. /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App, Error}; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -134,8 +134,8 @@ impl fmt::Display for Path { /// implements `Deserialize` trait from *serde*. /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App, Error}; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -190,10 +190,9 @@ where /// Path extractor configuration /// /// ```rust -/// # #[macro_use] -/// # extern crate serde_derive; /// use actix_web::web::PathConfig; /// use actix_web::{error, web, App, FromRequest, HttpResponse}; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize, Debug)] /// enum Folder { diff --git a/src/types/query.rs b/src/types/query.rs index 60b07085..817b2ed7 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -21,8 +21,8 @@ use crate::request::HttpRequest; /// ## Example /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App}; +/// use serde_derive::Deserialize; /// /// #[derive(Debug, Deserialize)] /// pub enum ResponseType { @@ -99,8 +99,8 @@ impl fmt::Display for Query { /// ## Example /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App}; +/// use serde_derive::Deserialize; /// /// #[derive(Debug, Deserialize)] /// pub enum ResponseType { @@ -169,8 +169,8 @@ where /// ## Example /// /// ```rust -/// #[macro_use] extern crate serde_derive; /// use actix_web::{error, web, App, FromRequest, HttpResponse}; +/// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { From 4de2e8a8983f96937d3c7d660ec9bd76951b5ebc Mon Sep 17 00:00:00 2001 From: Naim A <227396+naim94a@users.noreply.github.com> Date: Tue, 8 Oct 2019 07:09:40 +0300 Subject: [PATCH 1583/1635] [actix-files] Allow user defined guards for NamedFile (actix#1113) (#1115) * [actix-files] remove request method checks from NamedFile * [actix-files] added custom guard checks to FilesService * [actix-files] modify method check tests (NamedFile -> Files) * [actix-files] add test for custom guards in Files * [actix-files] update changelog --- actix-files/CHANGES.md | 2 ++ actix-files/src/lib.rs | 77 +++++++++++++++++++++++++++++++++++----- actix-files/src/named.rs | 12 +------ 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 49ecdbff..2421890d 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -6,6 +6,8 @@ * Bump up `percent-encoding` crate version to 2.1 +* Allow user defined request guards for `Files` #1113 + ## [0.1.4] - 2019-07-20 * Allow to disable `Content-Disposition` header #686 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index c99d3265..7fc3c45c 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -15,8 +15,10 @@ use actix_web::dev::{ AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest, ServiceResponse, }; +use actix_web::guard::Guard; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; -use actix_web::http::header::DispositionType; +use actix_web::http::header::{self, DispositionType}; +use actix_web::http::Method; use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; use bytes::Bytes; use futures::future::{ok, Either, FutureResult}; @@ -235,6 +237,7 @@ pub struct Files { renderer: Rc, mime_override: Option>, file_flags: named::Flags, + guards: Option>>, } impl Clone for Files { @@ -248,6 +251,7 @@ impl Clone for Files { file_flags: self.file_flags, path: self.path.clone(), mime_override: self.mime_override.clone(), + guards: self.guards.clone(), } } } @@ -273,6 +277,7 @@ impl Files { renderer: Rc::new(directory_listing), mime_override: None, file_flags: named::Flags::default(), + guards: None, } } @@ -331,6 +336,15 @@ impl Files { self } + /// Specifies custom guards to use for directory listings and files. + /// + /// Default behaviour allows GET and HEAD. + #[inline] + pub fn use_guards(mut self, guards: G) -> Self { + self.guards = Some(Rc::new(Box::new(guards))); + self + } + /// Disable `Content-Disposition` header. /// /// By default Content-Disposition` header is enabled. @@ -392,6 +406,7 @@ impl NewService for Files { renderer: self.renderer.clone(), mime_override: self.mime_override.clone(), file_flags: self.file_flags, + guards: self.guards.clone(), }; if let Some(ref default) = *self.default.borrow() { @@ -418,6 +433,7 @@ pub struct FilesService { renderer: Rc, mime_override: Option>, file_flags: named::Flags, + guards: Option>>, } impl FilesService { @@ -454,6 +470,25 @@ impl Service for FilesService { fn call(&mut self, req: ServiceRequest) -> Self::Future { // let (req, pl) = req.into_parts(); + let is_method_valid = if let Some(guard) = &self.guards { + // execute user defined guards + (**guard).check(req.head()) + } else { + // default behaviour + match *req.method() { + Method::HEAD | Method::GET => true, + _ => false, + } + }; + + if !is_method_valid { + return Either::A(ok(req.into_response( + actix_web::HttpResponse::MethodNotAllowed() + .header(header::CONTENT_TYPE, "text/plain") + .body("Request did not meet this resource's requirements.") + ))); + } + let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { Ok(item) => item, Err(e) => return Either::A(ok(req.error_response(e))), @@ -576,6 +611,7 @@ mod tests { use bytes::BytesMut; use super::*; + use actix_web::guard; use actix_web::http::header::{ self, ContentDisposition, DispositionParam, DispositionType, }; @@ -1010,20 +1046,45 @@ mod tests { } #[test] - fn test_named_file_not_allowed() { - let file = NamedFile::open("Cargo.toml").unwrap(); + fn test_files_not_allowed() { + let mut srv = test::init_service( + App::new().service(Files::new("/", ".")), + ); + let req = TestRequest::default() + .uri("/Cargo.toml") .method(Method::POST) - .to_http_request(); - let resp = file.respond_to(&req).unwrap(); + .to_request(); + + let resp = test::call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let file = NamedFile::open("Cargo.toml").unwrap(); - let req = TestRequest::default().method(Method::PUT).to_http_request(); - let resp = file.respond_to(&req).unwrap(); + let mut srv = test::init_service( + App::new().service(Files::new("/", ".")), + ); + let req = TestRequest::default().method(Method::PUT).uri("/Cargo.toml").to_request(); + let resp = test::call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } + #[test] + fn test_files_guards() { + let mut srv = test::init_service( + App::new().service( + Files::new("/", ".") + .use_guards(guard::Post()) + ), + ); + + let req = TestRequest::default() + .uri("/Cargo.toml") + .method(Method::POST) + .to_request(); + + let resp = test::call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + } + #[test] fn test_named_file_content_encoding() { let mut srv = test::init_service(App::new().wrap(Compress::default()).service( diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index f548a7a1..ca1a909a 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -15,7 +15,7 @@ use actix_http::body::SizedStream; use actix_web::http::header::{ self, ContentDisposition, DispositionParam, DispositionType, }; -use actix_web::http::{ContentEncoding, Method, StatusCode}; +use actix_web::http::{ContentEncoding, StatusCode}; use actix_web::middleware::BodyEncoding; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; @@ -324,16 +324,6 @@ impl Responder for NamedFile { return Ok(resp.streaming(reader)); } - match *req.method() { - Method::HEAD | Method::GET => (), - _ => { - return Ok(HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")); - } - } - let etag = if self.flags.contains(Flags::ETAG) { self.etag() } else { From a464ffc23daea1e0bc02bfea9622ae7bf33a2c12 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 Oct 2019 10:13:16 +0600 Subject: [PATCH 1584/1635] prepare actix-files release --- actix-files/CHANGES.md | 3 ++- actix-files/Cargo.toml | 6 +++--- actix-files/src/lib.rs | 22 +++++++++------------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 2421890d..5999f276 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.5] - unreleased +## [0.1.5] - 2019-10-08 * Bump up `mime_guess` crate version to 2.0.1 @@ -8,6 +8,7 @@ * Allow user defined request guards for `Files` #1113 + ## [0.1.4] - 2019-07-20 * Allow to disable `Content-Disposition` header #686 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 8f36cddc..971db792 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.4" +version = "0.1.5" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,7 +18,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "1.0.2", default-features = false } +actix-web = { version = "1.0.8", default-features = false } actix-http = "0.2.9" actix-service = "0.4.1" bitflags = "1" @@ -32,4 +32,4 @@ percent-encoding = "2.1" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.2", features=["ssl"] } +actix-web = { version = "1.0.8", features=["ssl"] } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 7fc3c45c..1cc26629 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -15,8 +15,8 @@ use actix_web::dev::{ AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest, ServiceResponse, }; -use actix_web::guard::Guard; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; +use actix_web::guard::Guard; use actix_web::http::header::{self, DispositionType}; use actix_web::http::Method; use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; @@ -485,7 +485,7 @@ impl Service for FilesService { return Either::A(ok(req.into_response( actix_web::HttpResponse::MethodNotAllowed() .header(header::CONTENT_TYPE, "text/plain") - .body("Request did not meet this resource's requirements.") + .body("Request did not meet this resource's requirements."), ))); } @@ -1047,9 +1047,7 @@ mod tests { #[test] fn test_files_not_allowed() { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".")), - ); + let mut srv = test::init_service(App::new().service(Files::new("/", "."))); let req = TestRequest::default() .uri("/Cargo.toml") @@ -1059,10 +1057,11 @@ mod tests { let resp = test::call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let mut srv = test::init_service( - App::new().service(Files::new("/", ".")), - ); - let req = TestRequest::default().method(Method::PUT).uri("/Cargo.toml").to_request(); + let mut srv = test::init_service(App::new().service(Files::new("/", "."))); + let req = TestRequest::default() + .method(Method::PUT) + .uri("/Cargo.toml") + .to_request(); let resp = test::call_service(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } @@ -1070,10 +1069,7 @@ mod tests { #[test] fn test_files_guards() { let mut srv = test::init_service( - App::new().service( - Files::new("/", ".") - .use_guards(guard::Post()) - ), + App::new().service(Files::new("/", ".").use_guards(guard::Post())), ); let req = TestRequest::default() From cc0b4be5b7efa51f58f7b97ec67c57d60c1f2849 Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 9 Oct 2019 09:11:55 -0400 Subject: [PATCH 1585/1635] Fix typo in response.rs body() comment (#1126) Fixes https://github.com/actix/actix-web/issues/1125 --- actix-http/src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 5b0b3bc8..a1541b53 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -194,7 +194,7 @@ impl Response { self.head.extensions.borrow_mut() } - /// Get body os this response + /// Get body of this response #[inline] pub fn body(&self) -> &ResponseBody { &self.body From effa96f5e4697d7dad7fd73f68b4cca055fa3f25 Mon Sep 17 00:00:00 2001 From: MaySantucci Date: Sat, 12 Oct 2019 02:45:12 +0200 Subject: [PATCH 1586/1635] Removed httpcode 'MovedPermanenty'. (#1128) --- actix-http/src/httpcodes.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/httpcodes.rs index 3cac35eb..0c7f23fc 100644 --- a/actix-http/src/httpcodes.rs +++ b/actix-http/src/httpcodes.rs @@ -29,7 +29,6 @@ impl Response { STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); - STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY); STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); STATIC_RESP!(Found, StatusCode::FOUND); STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); From a48e616def6463826a47e20f84fdfaa43266d82d Mon Sep 17 00:00:00 2001 From: Roberto Huertas Date: Mon, 14 Oct 2019 17:23:15 +0200 Subject: [PATCH 1587/1635] feat(files): add possibility to redirect to slash-ended path (#1134) When accessing to a folder without a final slash, the index file will be loaded ok, but if it has references (like a css or an image in an html file) these resources won't be loaded correctly if they are using relative paths. In order to solve this, this PR adds the possibility to detect folders without a final slash and make a 302 redirect to mitigate this issue. The behavior is off by default. We're adding a new method called `redirect_to_slash_directory` which can be used to enable this behavior. --- actix-files/CHANGES.md | 9 +++---- actix-files/src/lib.rs | 53 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 5999f276..5eb4e9a6 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.6] - TBD + +* Add option to redirect to a slash-ended path `Files` #1132 + ## [0.1.5] - 2019-10-08 * Bump up `mime_guess` crate version to 2.0.1 @@ -7,18 +11,15 @@ * Bump up `percent-encoding` crate version to 2.1 * Allow user defined request guards for `Files` #1113 - - + ## [0.1.4] - 2019-07-20 * Allow to disable `Content-Disposition` header #686 - ## [0.1.3] - 2019-06-28 * Do not set `Content-Length` header, let actix-http set it #930 - ## [0.1.2] - 2019-06-13 * Content-Length is 0 for NamedFile HEAD request #914 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 1cc26629..61674ca3 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -233,6 +233,7 @@ pub struct Files { directory: PathBuf, index: Option, show_index: bool, + redirect_to_slash: bool, default: Rc>>>, renderer: Rc, mime_override: Option>, @@ -246,6 +247,7 @@ impl Clone for Files { directory: self.directory.clone(), index: self.index.clone(), show_index: self.show_index, + redirect_to_slash: self.redirect_to_slash, default: self.default.clone(), renderer: self.renderer.clone(), file_flags: self.file_flags, @@ -273,6 +275,7 @@ impl Files { directory: dir, index: None, show_index: false, + redirect_to_slash: false, default: Rc::new(RefCell::new(None)), renderer: Rc::new(directory_listing), mime_override: None, @@ -289,6 +292,14 @@ impl Files { self } + /// Redirects to a slash-ended path when browsing a directory. + /// + /// By default never redirect. + pub fn redirect_to_slash_directory(mut self) -> Self { + self.redirect_to_slash = true; + self + } + /// Set custom directory renderer pub fn files_listing_renderer(mut self, f: F) -> Self where @@ -389,10 +400,10 @@ impl HttpServiceFactory for Files { } impl NewService for Files { - type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; + type Config = (); type Service = FilesService; type InitError = (); type Future = Box>; @@ -402,6 +413,7 @@ impl NewService for Files { directory: self.directory.clone(), index: self.index.clone(), show_index: self.show_index, + redirect_to_slash: self.redirect_to_slash, default: None, renderer: self.renderer.clone(), mime_override: self.mime_override.clone(), @@ -429,6 +441,7 @@ pub struct FilesService { directory: PathBuf, index: Option, show_index: bool, + redirect_to_slash: bool, default: Option, renderer: Rc, mime_override: Option>, @@ -502,6 +515,16 @@ impl Service for FilesService { if path.is_dir() { if let Some(ref redir_index) = self.index { + if self.redirect_to_slash && !req.path().ends_with('/') { + let redirect_to = format!("{}/", req.path()); + return Either::A(ok(req.into_response( + HttpResponse::Found() + .header(header::LOCATION, redirect_to) + .body("") + .into_body(), + ))); + } + let path = path.join(redir_index); match NamedFile::open(path) { @@ -1169,6 +1192,34 @@ mod tests { assert!(format!("{:?}", bytes).contains("/tests/test.png")); } + #[test] + fn test_redirect_to_slash_directory() { + // should not redirect if no index + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").redirect_to_slash_directory()), + ); + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + // should redirect if index present + let mut srv = test::init_service( + App::new().service( + Files::new("/", ".") + .index_file("test.png") + .redirect_to_slash_directory(), + ), + ); + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::FOUND); + + // should not redirect if the path is wrong + let req = TestRequest::with_uri("/not_existing").to_request(); + let resp = test::call_service(&mut srv, req); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + #[test] fn test_static_files_bad_directory() { let _st: Files = Files::new("/", "missing"); From 062e51e8ce6c2bfb34044e62682df7c8fa88f65a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 14 Oct 2019 21:26:26 +0600 Subject: [PATCH 1588/1635] prep actix-file release --- actix-files/CHANGES.md | 4 ++-- actix-files/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 5eb4e9a6..d6825c61 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [0.1.6] - TBD +## [0.1.6] - 2019-10-14 * Add option to redirect to a slash-ended path `Files` #1132 @@ -11,7 +11,7 @@ * Bump up `percent-encoding` crate version to 2.1 * Allow user defined request guards for `Files` #1113 - + ## [0.1.4] - 2019-07-20 * Allow to disable `Content-Disposition` header #686 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 971db792..9695cebe 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.5" +version = "0.1.6" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" From 967f9654051a5bd73941a5f21441aadb23cc3b90 Mon Sep 17 00:00:00 2001 From: DanSnow Date: Mon, 14 Oct 2019 23:34:17 +0800 Subject: [PATCH 1589/1635] Update `syn` & `quote` to 1.0 (#1133) * chore(actix-web-codegen): Upgrade syn and quote to 1.0 * feat(actix-web-codegen): Generate better error message * doc(actix-web-codegen): Update CHANGES.md * fix: Build with stable rust --- actix-web-codegen/CHANGES.md | 5 + actix-web-codegen/Cargo.toml | 5 +- actix-web-codegen/src/lib.rs | 45 ++++-- actix-web-codegen/src/route.rs | 242 ++++++++++++++++++--------------- 4 files changed, 178 insertions(+), 119 deletions(-) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 7cc0c164..d57bd5c6 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [UNRELEASE] + +* Bump up `syn` & `quote` to 1.0 +* Provide better error message + ## [0.1.2] - 2019-06-04 * Add macros for head, options, trace, connect and patch http methods diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 29abb489..585d4970 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -12,8 +12,9 @@ workspace = ".." proc-macro = true [dependencies] -quote = "0.6.12" -syn = { version = "0.15.34", features = ["full", "parsing", "extra-traits"] } +quote = "1" +syn = { version = "1", features = ["full", "parsing"] } +proc-macro2 = "1" [dev-dependencies] actix-web = { version = "1.0.0" } diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index b3ae7dd9..88fa4dfd 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -58,7 +58,10 @@ use syn::parse_macro_input; #[proc_macro_attribute] pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Get); + let gen = match route::Route::new(args, input, route::GuardType::Get) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } @@ -70,7 +73,10 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Post); + let gen = match route::Route::new(args, input, route::GuardType::Post) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } @@ -82,7 +88,10 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Put); + let gen = match route::Route::new(args, input, route::GuardType::Put) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } @@ -94,7 +103,10 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Delete); + let gen = match route::Route::new(args, input, route::GuardType::Delete) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } @@ -106,7 +118,10 @@ pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn head(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Head); + let gen = match route::Route::new(args, input, route::GuardType::Head) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } @@ -118,7 +133,10 @@ pub fn head(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Connect); + let gen = match route::Route::new(args, input, route::GuardType::Connect) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } @@ -130,7 +148,10 @@ pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn options(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Options); + let gen = match route::Route::new(args, input, route::GuardType::Options) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } @@ -142,7 +163,10 @@ pub fn options(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Trace); + let gen = match route::Route::new(args, input, route::GuardType::Trace) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } @@ -154,6 +178,9 @@ pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = route::Args::new(&args, input, route::GuardType::Patch); + let gen = match route::Route::new(args, input, route::GuardType::Patch) { + Ok(gen) => gen, + Err(err) => return err.to_compile_error().into(), + }; gen.generate() } diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 5215f60c..e792a7f0 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -1,21 +1,23 @@ extern crate proc_macro; -use std::fmt; - use proc_macro::TokenStream; -use quote::quote; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens, TokenStreamExt}; +use syn::{AttributeArgs, Ident, NestedMeta}; enum ResourceType { Async, Sync, } -impl fmt::Display for ResourceType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ResourceType::Async => write!(f, "to_async"), - ResourceType::Sync => write!(f, "to"), - } +impl ToTokens for ResourceType { + fn to_tokens(&self, stream: &mut TokenStream2) { + let ident = match self { + ResourceType::Async => "to_async", + ResourceType::Sync => "to", + }; + let ident = Ident::new(ident, Span::call_site()); + stream.append(ident); } } @@ -32,63 +34,89 @@ pub enum GuardType { Patch, } -impl fmt::Display for GuardType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - GuardType::Get => write!(f, "Get"), - GuardType::Post => write!(f, "Post"), - GuardType::Put => write!(f, "Put"), - GuardType::Delete => write!(f, "Delete"), - GuardType::Head => write!(f, "Head"), - GuardType::Connect => write!(f, "Connect"), - GuardType::Options => write!(f, "Options"), - GuardType::Trace => write!(f, "Trace"), - GuardType::Patch => write!(f, "Patch"), +impl GuardType { + fn as_str(&self) -> &'static str { + match self { + GuardType::Get => "Get", + GuardType::Post => "Post", + GuardType::Put => "Put", + GuardType::Delete => "Delete", + GuardType::Head => "Head", + GuardType::Connect => "Connect", + GuardType::Options => "Options", + GuardType::Trace => "Trace", + GuardType::Patch => "Patch", } } } -pub struct Args { - name: syn::Ident, - path: String, - ast: syn::ItemFn, - resource_type: ResourceType, - pub guard: GuardType, - pub extra_guards: Vec, +impl ToTokens for GuardType { + fn to_tokens(&self, stream: &mut TokenStream2) { + let ident = self.as_str(); + let ident = Ident::new(ident, Span::call_site()); + stream.append(ident); + } } -impl fmt::Display for Args { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let ast = &self.ast; - let guards = format!(".guard(actix_web::guard::{}())", self.guard); - let guards = self.extra_guards.iter().fold(guards, |acc, val| { - format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val) - }); +struct Args { + path: syn::LitStr, + guards: Vec, +} - write!( - f, - " -#[allow(non_camel_case_types)] -pub struct {name}; - -impl actix_web::dev::HttpServiceFactory for {name} {{ - fn register(self, config: &mut actix_web::dev::AppService) {{ - {ast} - - let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name}); - - actix_web::dev::HttpServiceFactory::register(resource, config) - }} -}}", - name = self.name, - ast = quote!(#ast), - path = self.path, - guards = guards, - to = self.resource_type - ) +impl Args { + fn new(args: AttributeArgs) -> syn::Result { + let mut path = None; + let mut guards = Vec::new(); + for arg in args { + match arg { + NestedMeta::Lit(syn::Lit::Str(lit)) => match path { + None => { + path = Some(lit); + } + _ => { + return Err(syn::Error::new_spanned( + lit, + "Multiple paths specified! Should be only one!", + )); + } + }, + NestedMeta::Meta(syn::Meta::NameValue(nv)) => { + if nv.path.is_ident("guard") { + if let syn::Lit::Str(lit) = nv.lit { + guards.push(Ident::new(&lit.value(), Span::call_site())); + } else { + return Err(syn::Error::new_spanned( + nv.lit, + "Attribute guard expects literal string!", + )); + } + } else { + return Err(syn::Error::new_spanned( + nv.path, + "Unknown attribute key is specified. Allowed: guard", + )); + } + } + arg => { + return Err(syn::Error::new_spanned(arg, "Unknown attribute")); + } + } + } + Ok(Args { + path: path.unwrap(), + guards, + }) } } +pub struct Route { + name: syn::Ident, + args: Args, + ast: syn::ItemFn, + resource_type: ResourceType, + guard: GuardType, +} + fn guess_resource_type(typ: &syn::Type) -> ResourceType { let mut guess = ResourceType::Sync; @@ -111,75 +139,73 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType { guess } -impl Args { - pub fn new(args: &[syn::NestedMeta], input: TokenStream, guard: GuardType) -> Self { +impl Route { + pub fn new( + args: AttributeArgs, + input: TokenStream, + guard: GuardType, + ) -> syn::Result { if args.is_empty() { - panic!( - "invalid server definition, expected: #[{}(\"some path\")]", - guard - ); + return Err(syn::Error::new( + Span::call_site(), + format!( + r#"invalid server definition, expected #[{}("")]"#, + guard.as_str().to_ascii_lowercase() + ), + )); } + let ast: syn::ItemFn = syn::parse(input)?; + let name = ast.sig.ident.clone(); - let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function"); - let name = ast.ident.clone(); + let args = Args::new(args)?; - let mut extra_guards = Vec::new(); - let mut path = None; - for arg in args { - match arg { - syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => { - if path.is_some() { - panic!("Multiple paths specified! Should be only one!") - } - let fname = quote!(#fname).to_string(); - path = Some(fname.as_str()[1..fname.len() - 1].to_owned()) - } - syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => { - match ident.ident.to_string().to_lowercase().as_str() { - "guard" => match ident.lit { - syn::Lit::Str(ref text) => extra_guards.push(text.value()), - _ => panic!("Attribute guard expects literal string!"), - }, - attr => panic!( - "Unknown attribute key is specified: {}. Allowed: guard", - attr - ), - } - } - attr => panic!("Unknown attribute{:?}", attr), - } - } - - let resource_type = if ast.asyncness.is_some() { + let resource_type = if ast.sig.asyncness.is_some() { ResourceType::Async } else { - match ast.decl.output { - syn::ReturnType::Default => panic!( - "Function {} has no return type. Cannot be used as handler", - name - ), + match ast.sig.output { + syn::ReturnType::Default => { + return Err(syn::Error::new_spanned( + ast, + "Function has no return type. Cannot be used as handler", + )); + } syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), } }; - let path = path.unwrap(); - - Self { + Ok(Self { name, - path, + args, ast, resource_type, guard, - extra_guards, - } + }) } pub fn generate(&self) -> TokenStream { - let text = self.to_string(); + let name = &self.name; + let guard = &self.guard; + let ast = &self.ast; + let path = &self.args.path; + let extra_guards = &self.args.guards; + let resource_type = &self.resource_type; + let stream = quote! { + #[allow(non_camel_case_types)] + pub struct #name; - match text.parse() { - Ok(res) => res, - Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text), - } + impl actix_web::dev::HttpServiceFactory for #name { + fn register(self, config: &mut actix_web::dev::AppService) { + #ast + + let resource = actix_web::Resource::new(#path) + .guard(actix_web::guard::#guard()) + #(.guard(actix_web::guard::fn_guard(#extra_guards)))* + .#resource_type(#name); + + actix_web::dev::HttpServiceFactory::register(resource, config) + } + } + }; + stream.into() } } From 1ca9d87f0a4dc97cf9a427debc65c2c384b8110e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 14 Oct 2019 21:35:53 +0600 Subject: [PATCH 1590/1635] prep actix-web-codegen release --- actix-web-codegen/CHANGES.md | 3 ++- actix-web-codegen/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index d57bd5c6..2beea62c 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,8 +1,9 @@ # Changes -## [UNRELEASE] +## [0.1.3] - 2019-10-14 * Bump up `syn` & `quote` to 1.0 + * Provide better error message ## [0.1.2] - 2019-06-04 diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 585d4970..981e0032 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.1.2" +version = "0.1.3" description = "Actix web proc macros" readme = "README.md" authors = ["Nikolay Kim "] From ace98e3a1e62cdfac4c21e22955c05157e373d35 Mon Sep 17 00:00:00 2001 From: Anton Lazarev Date: Mon, 14 Oct 2019 16:05:54 -0700 Subject: [PATCH 1591/1635] support Host guards when Host header is unset (#1129) --- CHANGES.md | 4 ++++ src/guard.rs | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4ff7d1e6..689ab13d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ * Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) +### Changed + +* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) + ## [1.0.8] - 2019-09-25 ### Added diff --git a/src/guard.rs b/src/guard.rs index c6019258..aad19c8f 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -276,10 +276,11 @@ pub fn Host>(host: H) -> HostGuard { fn get_host_uri(req: &RequestHead) -> Option { use core::str::FromStr; - let host_value = req.headers.get(header::HOST)?; - let host = host_value.to_str().ok()?; - let uri = Uri::from_str(host).ok()?; - Some(uri) + req.headers.get(header::HOST) + .and_then(|host_value| host_value.to_str().ok()) + .or_else(|| req.uri.host()) + .map(|host: &str| Uri::from_str(host).ok()) + .and_then(|host_success| host_success) } #[doc(hidden)] @@ -400,6 +401,31 @@ mod tests { assert!(!pred.check(req.head())); } + #[test] + fn test_host_without_header() { + let req = TestRequest::default() + .uri("www.rust-lang.org") + .to_http_request(); + + let pred = Host("www.rust-lang.org"); + assert!(pred.check(req.head())); + + let pred = Host("www.rust-lang.org").scheme("https"); + assert!(pred.check(req.head())); + + let pred = Host("blog.rust-lang.org"); + assert!(!pred.check(req.head())); + + let pred = Host("blog.rust-lang.org").scheme("https"); + assert!(!pred.check(req.head())); + + let pred = Host("crates.io"); + assert!(!pred.check(req.head())); + + let pred = Host("localhost"); + assert!(!pred.check(req.head())); + } + #[test] fn test_methods() { let req = TestRequest::default().to_http_request(); From f0612f757001dde1509892f386d5d3033194f540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathas=20Concei=C3=A7=C3=A3o?= Date: Sat, 26 Oct 2019 02:27:14 -0300 Subject: [PATCH 1592/1635] awc: Add support for setting query from Serialize type for client request (#1130) Signed-off-by: Jonathas-Conceicao --- awc/CHANGES.md | 3 +++ awc/src/request.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 6f8fe2db..9b8e27c9 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,8 @@ # Changes +## [0.2.8] - 2019-10-24 + +* Add support for setting query from Serialize type for client request. ## [0.2.7] - 2019-09-25 diff --git a/awc/src/request.rs b/awc/src/request.rs index a90cf60b..6ff68ae6 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -382,6 +382,27 @@ impl ClientRequest { } } + /// Sets the query part of the request + pub fn query( + mut self, + query: &T, + ) -> Result { + let mut parts = self.head.uri.clone().into_parts(); + + if let Some(path_and_query) = parts.path_and_query { + let query = serde_urlencoded::to_string(query)?; + let path = path_and_query.path(); + parts.path_and_query = format!("{}?{}", path, query).parse().ok(); + + match Uri::from_parts(parts) { + Ok(uri) => self.head.uri = uri, + Err(e) => self.err = Some(e.into()), + } + } + + Ok(self) + } + /// Freeze request builder and construct `FrozenClientRequest`, /// which could be used for sending same request multiple times. pub fn freeze(self) -> Result { @@ -690,4 +711,13 @@ mod tests { "Bearer someS3cr3tAutht0k3n" ); } + + #[test] + fn client_query() { + let req = Client::new() + .get("/") + .query(&[("key1", "val1"), ("key2", "val2")]) + .unwrap(); + assert_eq!(req.get_uri().query().unwrap(), "key1=val1&key2=val2"); + } } From edcde6707657d3e8bdd1df21533fe80aa235f0dd Mon Sep 17 00:00:00 2001 From: Hung-I Wang Date: Wed, 6 Nov 2019 22:08:37 +0800 Subject: [PATCH 1593/1635] Fix escaping/encoding problems in Content-Disposition header (#1151) * Fix filename encoding in Content-Disposition of acitx_files::NamedFile * Add more comments on how to use Content-Disposition header properly & Fix some trivial problems * Improve Content-Disposition filename(*) parameters of actix_files::NamedFile * Tweak Content-Disposition parse to accept empty param value in quoted-string * Fix typos in comments in .../content_disposition.rs (pointed out by @JohnTitor) * Update CHANGES.md * Update CHANGES.md again --- CHANGES.md | 1 + actix-files/src/lib.rs | 25 ++++++ actix-files/src/named.rs | 13 ++- .../src/header/common/content_disposition.rs | 90 +++++++++++++++++-- 4 files changed, 119 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 689ab13d..dcb57630 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### Added * Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) +* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) ### Changed diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 61674ca3..16f40a20 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -733,6 +733,31 @@ mod tests { assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); } + #[test] + fn test_named_file_non_ascii_file_name() { + let mut file = + NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml") + .unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml" + ); + } + #[test] fn test_named_file_set_content_type() { let mut file = NamedFile::open("Cargo.toml") diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index ca1a909a..955982ca 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -13,7 +13,7 @@ use mime_guess::from_path; use actix_http::body::SizedStream; use actix_web::http::header::{ - self, ContentDisposition, DispositionParam, DispositionType, + self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, }; use actix_web::http::{ContentEncoding, StatusCode}; use actix_web::middleware::BodyEncoding; @@ -93,9 +93,18 @@ impl NamedFile { mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, _ => DispositionType::Attachment, }; + let mut parameters = + vec![DispositionParam::Filename(String::from(filename.as_ref()))]; + if !filename.is_ascii() { + parameters.push(DispositionParam::FilenameExt(ExtendedValue { + charset: Charset::Ext(String::from("UTF-8")), + language_tag: None, + value: filename.into_owned().into_bytes(), + })) + } let cd = ContentDisposition { disposition: disposition_type, - parameters: vec![DispositionParam::Filename(filename.into_owned())], + parameters: parameters, }; (ct, cd) }; diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 14fcc351..b2b6f34d 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -76,6 +76,11 @@ pub enum DispositionParam { /// the form. Name(String), /// A plain file name. + /// + /// It is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any + /// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where + /// [`FilenameExt`](DispositionParam::FilenameExt) with charset UTF-8 may be used instead + /// in case there are Unicode characters in file names. Filename(String), /// An extended file name. It must not exist for `ContentType::Formdata` according to /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). @@ -220,7 +225,16 @@ impl DispositionParam { /// ext-token = /// ``` /// -/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within +/// # Note +/// +/// filename is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any +/// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where +/// filename* with charset UTF-8 may be used instead in case there are Unicode characters in file +/// names. +/// filename is [acceptable](https://tools.ietf.org/html/rfc7578#section-4.2) to be UTF-8 encoded +/// directly in a *Content-Disposition* header for *multipart/form-data*, though. +/// +/// filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within /// *multipart/form-data*. /// /// # Example @@ -251,6 +265,22 @@ impl DispositionParam { /// }; /// assert_eq!(cd2.get_name(), Some("file")); // field name /// assert_eq!(cd2.get_filename(), Some("bill.odt")); +/// +/// // HTTP response header with Unicode characters in file names +/// let cd3 = ContentDisposition { +/// disposition: DispositionType::Attachment, +/// parameters: vec![ +/// DispositionParam::FilenameExt(ExtendedValue { +/// charset: Charset::Ext(String::from("UTF-8")), +/// language_tag: None, +/// value: String::from("\u{1f600}.svg").into_bytes(), +/// }), +/// // fallback for better compatibility +/// DispositionParam::Filename(String::from("Grinning-Face-Emoji.svg")) +/// ], +/// }; +/// assert_eq!(cd3.get_filename_ext().map(|ev| ev.value.as_ref()), +/// Some("\u{1f600}.svg".as_bytes())); /// ``` /// /// # WARN @@ -333,15 +363,17 @@ impl ContentDisposition { // token: won't contains semicolon according to RFC 2616 Section 2.2 let (token, new_left) = split_once_and_trim(left, ';'); left = new_left; + if token.is_empty() { + // quoted-string can be empty, but token cannot be empty + return Err(crate::error::ParseError::Header); + } token.to_owned() }; - if value.is_empty() { - return Err(crate::error::ParseError::Header); - } let param = if param_name.eq_ignore_ascii_case("name") { DispositionParam::Name(value) } else if param_name.eq_ignore_ascii_case("filename") { + // See also comments in test_from_raw_uncessary_percent_decode. DispositionParam::Filename(value) } else { DispositionParam::Unknown(param_name.to_owned(), value) @@ -466,11 +498,40 @@ impl fmt::Display for DispositionType { impl fmt::Display for DispositionParam { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and + // All ASCII control characters (0-30, 127) including horizontal tab, double quote, and // backslash should be escaped in quoted-string (i.e. "foobar"). - // Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . + // Ref: RFC6266 S4.1 -> RFC2616 S3.6 + // filename-parm = "filename" "=" value + // value = token | quoted-string + // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + // qdtext = > + // quoted-pair = "\" CHAR + // TEXT = + // LWS = [CRLF] 1*( SP | HT ) + // OCTET = + // CHAR = + // CTL = + // + // Ref: RFC7578 S4.2 -> RFC2183 S2 -> RFC2045 S5.1 + // parameter := attribute "=" value + // attribute := token + // ; Matching of attributes + // ; is ALWAYS case-insensitive. + // value := token / quoted-string + // token := 1* + // tspecials := "(" / ")" / "<" / ">" / "@" / + // "," / ";" / ":" / "\" / <"> + // "/" / "[" / "]" / "?" / "=" + // ; Must be in quoted-string, + // ; to use within parameter values + // + // + // See also comments in test_from_raw_uncessary_percent_decode. lazy_static! { - static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); + static ref RE: Regex = Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap(); } match self { DispositionParam::Name(ref value) => write!(f, "name={}", value), @@ -774,8 +835,18 @@ mod tests { #[test] fn test_from_raw_uncessary_percent_decode() { + // In fact, RFC7578 (multipart/form-data) Section 2 and 4.2 suggests that filename with + // non-ASCII characters MAY be percent-encoded. + // On the contrary, RFC6266 or other RFCs related to Content-Disposition response header + // do not mention such percent-encoding. + // So, it appears to be undecidable whether to percent-decode or not without + // knowing the usage scenario (multipart/form-data v.s. HTTP response header) and + // inevitable to unnecessarily percent-decode filename with %XX in the former scenario. + // Fortunately, it seems that almost all mainstream browsers just send UTF-8 encoded file + // names in quoted-string format (tested on Edge, IE11, Chrome and Firefox) without + // percent-encoding. So we do not bother to attempt to percent-decode. let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! + "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", ); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { @@ -811,6 +882,9 @@ mod tests { let a = HeaderValue::from_static("inline; filename= "); assert!(ContentDisposition::from_raw(&a).is_err()); + + let a = HeaderValue::from_static("inline; filename=\"\""); + assert!(ContentDisposition::from_raw(&a).expect("parse cd").get_filename().expect("filename").is_empty()); } #[test] From 61b38e8d0df8ae5b4db59ff1d72ca65fbd75b8a2 Mon Sep 17 00:00:00 2001 From: Erlend Langseth <3rlendhl@gmail.com> Date: Wed, 6 Nov 2019 15:09:22 +0100 Subject: [PATCH 1594/1635] Increase timeouts in test-server (#1153) --- test-server/CHANGES.md | 1 + test-server/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 798dbf50..57068fe9 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -5,6 +5,7 @@ ### Changed * Update serde_urlencoded to "0.6.1" +* Increase TestServerRuntime timeouts from 500ms to 3000ms ### Fixed diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index a2366bf4..ebdec688 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -144,7 +144,7 @@ impl TestServer { .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); Connector::new() .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(500)) + .timeout(time::Duration::from_millis(3000)) .ssl(builder.build()) .finish() } @@ -152,7 +152,7 @@ impl TestServer { { Connector::new() .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(500)) + .timeout(time::Duration::from_millis(3000)) .finish() } }; From 885ff7396e792403990f713df1cd3bfd0b018059 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Nov 2019 10:35:13 -0800 Subject: [PATCH 1595/1635] prepare actox-http release --- CHANGES.md | 1 - Cargo.toml | 2 +- actix-files/CHANGES.md | 4 ++++ actix-http/CHANGES.md | 9 +++++---- actix-http/Cargo.toml | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dcb57630..689ab13d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,6 @@ ### Added * Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) -* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) ### Changed diff --git a/Cargo.toml b/Cargo.toml index 35ca28b2..96b015e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ bytes = "0.4" derive_more = "0.15.0" encoding_rs = "0.8" futures = "0.1.25" -hashbrown = "0.5.0" +hashbrown = "0.6.3" log = "0.4" mime = "0.3" net2 = "0.2.33" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index d6825c61..5ec56593 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.1.7] - 2019-11-06 + +* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) + ## [0.1.6] - 2019-10-14 * Add option to redirect to a slash-ended path `Files` #1132 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 624aca5e..4cb5644c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,11 +1,15 @@ # Changes -## Not released yet +## [0.2.11] - 2019-11-06 ### Added * Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() +* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) + +* Allow to use `std::convert::Infallible` as `actix_http::error::Error` + ### Fixed * To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118 @@ -17,9 +21,6 @@ * Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` -* Allow to use `std::convert::Infallible` as `actix_http::error::Error` - - ### Fixed * h2 will use error response #1080 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index cc7c885e..ee0ded59 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.10" +version = "0.2.11" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -62,7 +62,7 @@ derive_more = "0.15.0" either = "1.5.2" encoding_rs = "0.8" futures = "0.1.25" -hashbrown = "0.5.0" +hashbrown = "0.6.3" h2 = "0.1.16" http = "0.1.17" httparse = "1.3" From f7f410d033c8d34295892a7d52a4a6dc51ef2e77 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Nov 2019 11:20:47 -0800 Subject: [PATCH 1596/1635] fix test order dep --- actix-http/src/h1/encoder.rs | 44 +++++++++++++++++++----------------- src/guard.rs | 3 ++- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 51ea497e..6396f3b5 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -548,10 +548,11 @@ mod tests { ConnectionType::Close, &ServiceConfig::default(), ); - assert_eq!( - bytes.take().freeze(), - Bytes::from_static(b"\r\nContent-Length: 0\r\nConnection: close\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") - ); + let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); + assert!(data.contains("Content-Length: 0\r\n")); + assert!(data.contains("Connection: close\r\n")); + assert!(data.contains("Content-Type: plain/text\r\n")); + assert!(data.contains("Date: date\r\n")); let _ = head.encode_headers( &mut bytes, @@ -560,10 +561,10 @@ mod tests { ConnectionType::KeepAlive, &ServiceConfig::default(), ); - assert_eq!( - bytes.take().freeze(), - Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") - ); + let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); + assert!(data.contains("Transfer-Encoding: chunked\r\n")); + assert!(data.contains("Content-Type: plain/text\r\n")); + assert!(data.contains("Date: date\r\n")); let _ = head.encode_headers( &mut bytes, @@ -572,10 +573,10 @@ mod tests { ConnectionType::KeepAlive, &ServiceConfig::default(), ); - assert_eq!( - bytes.take().freeze(), - Bytes::from_static(b"\r\nContent-Length: 100\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n") - ); + let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); + assert!(data.contains("Content-Length: 100\r\n")); + assert!(data.contains("Content-Type: plain/text\r\n")); + assert!(data.contains("Date: date\r\n")); let mut head = RequestHead::default(); head.set_camel_case_headers(false); @@ -586,7 +587,6 @@ mod tests { .append(CONTENT_TYPE, HeaderValue::from_static("xml")); let mut head = RequestHeadType::Owned(head); - let _ = head.encode_headers( &mut bytes, Version::HTTP_11, @@ -594,10 +594,11 @@ mod tests { ConnectionType::KeepAlive, &ServiceConfig::default(), ); - assert_eq!( - bytes.take().freeze(), - Bytes::from_static(b"\r\ntransfer-encoding: chunked\r\ndate: date\r\ncontent-type: xml\r\ncontent-type: plain/text\r\n\r\n") - ); + let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); + assert!(data.contains("transfer-encoding: chunked\r\n")); + assert!(data.contains("content-type: xml\r\n")); + assert!(data.contains("content-type: plain/text\r\n")); + assert!(data.contains("date: date\r\n")); } #[test] @@ -626,9 +627,10 @@ mod tests { ConnectionType::Close, &ServiceConfig::default(), ); - assert_eq!( - bytes.take().freeze(), - Bytes::from_static(b"\r\ncontent-length: 0\r\nconnection: close\r\nauthorization: another authorization\r\ndate: date\r\n\r\n") - ); + let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap(); + assert!(data.contains("content-length: 0\r\n")); + assert!(data.contains("connection: close\r\n")); + assert!(data.contains("authorization: another authorization\r\n")); + assert!(data.contains("date: date\r\n")); } } diff --git a/src/guard.rs b/src/guard.rs index aad19c8f..3db525f9 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -276,7 +276,8 @@ pub fn Host>(host: H) -> HostGuard { fn get_host_uri(req: &RequestHead) -> Option { use core::str::FromStr; - req.headers.get(header::HOST) + req.headers + .get(header::HOST) .and_then(|host_value| host_value.to_str().ok()) .or_else(|| req.uri.host()) .map(|host: &str| Uri::from_str(host).ok()) From b2934ad8d2c3315e0eae85634a209a17e7b4a6af Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Nov 2019 11:25:26 -0800 Subject: [PATCH 1597/1635] prep actix-file release --- actix-files/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 9695cebe..1bc063e5 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.6" +version = "0.1.7" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "1.0.8", default-features = false } -actix-http = "0.2.9" +actix-http = "0.2.11" actix-service = "0.4.1" bitflags = "1" bytes = "0.4" From fba02fdd8cde150615772c7fde8d3dd0811bef06 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Nov 2019 11:33:25 -0800 Subject: [PATCH 1598/1635] prep awc release --- awc/CHANGES.md | 3 ++- awc/Cargo.toml | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9b8e27c9..89423f80 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,9 +1,10 @@ # Changes -## [0.2.8] - 2019-10-24 +## [0.2.8] - 2019-11-06 * Add support for setting query from Serialize type for client request. + ## [0.2.7] - 2019-09-25 ### Added diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 6f0f63f9..4b0e612b 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.7" +version = "0.2.8" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -44,7 +44,7 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-http = "0.2.10" +actix-http = "0.2.11" base64 = "0.10.1" bytes = "0.4" derive_more = "0.15.0" @@ -62,8 +62,8 @@ rustls = { version = "0.15.2", optional = true } [dev-dependencies] actix-rt = "0.2.2" -actix-web = { version = "1.0.0", features=["ssl"] } -actix-http = { version = "0.2.10", features=["ssl"] } +actix-web = { version = "1.0.8", features=["ssl"] } +actix-http = { version = "0.2.11", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } actix-utils = "0.4.1" actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } From 88110ed268a3e46ccce610486abfb2cf8f39fbde Mon Sep 17 00:00:00 2001 From: Feiko Nanninga Date: Thu, 14 Nov 2019 03:32:47 +0100 Subject: [PATCH 1599/1635] Add security note to ConnectionInfo::remote() (#1158) --- src/info.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/info.rs b/src/info.rs index 61914516..a9c3e4ee 100644 --- a/src/info.rs +++ b/src/info.rs @@ -162,6 +162,12 @@ impl ConnectionInfo { /// - Forwarded /// - X-Forwarded-For /// - peer name of opened socket + /// + /// # Security + /// Do not use this function for security purposes, unless you can ensure the Forwarded and + /// X-Forwarded-For headers cannot be spoofed by the client. If you want the client's socket + /// address explicitly, use + /// [`HttpRequest::peer_addr()`](../web/struct.HttpRequest.html#method.peer_addr) instead. #[inline] pub fn remote(&self) -> Option<&str> { if let Some(ref r) = self.remote { From 0212c618c6594de8c44df02677a2f607288cd0c5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Nov 2019 08:55:37 +0600 Subject: [PATCH 1600/1635] prepare actix-web release --- CHANGES.md | 3 ++- Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 689ab13d..bb17a7ef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## [1.0.9] - 2019-xx-xx +## [1.0.9] - 2019-11-14 ### Added @@ -10,6 +10,7 @@ * Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) + ## [1.0.8] - 2019-09-25 ### Added diff --git a/Cargo.toml b/Cargo.toml index 96b015e1..54e4b237 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.8" +version = "1.0.9" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -78,7 +78,7 @@ actix-utils = "0.4.4" actix-router = "0.1.5" actix-rt = "0.2.4" actix-web-codegen = "0.1.2" -actix-http = "0.2.9" +actix-http = "0.2.11" actix-server = "0.6.1" actix-server-config = "0.1.2" actix-testing = "0.1.0" From 5cb2d500d1289fe66c4fa8b4a10975c0f2399b46 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 14 Nov 2019 08:58:24 +0600 Subject: [PATCH 1601/1635] update actix-web-actors --- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 0d1df7e5..c1417c9c 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [1.0.3] - 2019-11-14 + +* Update actix-web and actix-http dependencies + ## [1.0.2] - 2019-07-20 * Add `ws::start_with_addr()`, returning the address of the created actor, along diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 356109da..d5a6ce2c 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.2" +version = "1.0.3" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -19,8 +19,8 @@ path = "src/lib.rs" [dependencies] actix = "0.8.3" -actix-web = "1.0.3" -actix-http = "0.2.5" +actix-web = "1.0.9" +actix-http = "0.2.11" actix-codec = "0.1.2" bytes = "0.4" futures = "0.1.25" From 8cba1170e6064ba6754abec97776fd25038768f7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 15 Nov 2019 15:54:11 +0600 Subject: [PATCH 1602/1635] make actix-http compile with std::future --- Cargo.toml | 54 +++-- actix-http/Cargo.toml | 69 +++--- actix-http/src/body.rs | 110 +++++----- actix-http/src/builder.rs | 83 ++++--- actix-http/src/client/connector.rs | 9 +- actix-http/src/client/h1proto.rs | 5 +- actix-http/src/client/h2proto.rs | 4 +- actix-http/src/client/pool.rs | 26 +-- actix-http/src/cloneable.rs | 6 +- actix-http/src/config.rs | 24 +-- actix-http/src/encoding/decoder.rs | 52 +++-- actix-http/src/encoding/encoder.rs | 42 ++-- actix-http/src/error.rs | 37 ++-- actix-http/src/h1/decoder.rs | 104 ++++----- actix-http/src/h1/dispatcher.rs | 226 +++++++++++-------- actix-http/src/h1/expect.rs | 19 +- actix-http/src/h1/payload.rs | 78 +++---- actix-http/src/h1/service.rs | 207 +++++++++++------- actix-http/src/h1/upgrade.rs | 18 +- actix-http/src/h1/utils.rs | 47 ++-- actix-http/src/h2/dispatcher.rs | 191 +++++++++------- actix-http/src/h2/mod.rs | 29 +-- actix-http/src/h2/service.rs | 129 ++++++----- actix-http/src/lib.rs | 9 +- actix-http/src/payload.rs | 25 ++- actix-http/src/response.rs | 38 ++-- actix-http/src/service.rs | 336 +++++++++++++++++++---------- actix-http/src/test.rs | 21 +- 28 files changed, 1176 insertions(+), 822 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54e4b237..ab812d1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,19 +28,20 @@ path = "src/lib.rs" [workspace] members = [ - ".", - "awc", - "actix-http", - "actix-cors", - "actix-files", - "actix-framed", - "actix-session", - "actix-identity", - "actix-multipart", - "actix-web-actors", - "actix-web-codegen", - "test-server", +# ".", +# "awc", +# #"actix-http", +# "actix-cors", +# "actix-files", +# "actix-framed", +# "actix-session", +# "actix-identity", +# "actix-multipart", +# "actix-web-actors", +# "actix-web-codegen", +# "test-server", ] +exclude = ["actix-http"] [features] default = ["brotli", "flate2-zlib", "client", "fail"] @@ -122,12 +123,23 @@ opt-level = 3 codegen-units = 1 [patch.crates-io] -actix-web = { path = "." } -actix-http = { path = "actix-http" } -actix-http-test = { path = "test-server" } -actix-web-codegen = { path = "actix-web-codegen" } -actix-web-actors = { path = "actix-web-actors" } -actix-session = { path = "actix-session" } -actix-files = { path = "actix-files" } -actix-multipart = { path = "actix-multipart" } -awc = { path = "awc" } +# actix-web = { path = "." } +# actix-http = { path = "actix-http" } +# actix-http-test = { path = "test-server" } +# actix-web-codegen = { path = "actix-web-codegen" } +# actix-web-actors = { path = "actix-web-actors" } +# actix-session = { path = "actix-session" } +# actix-files = { path = "actix-files" } +# actix-multipart = { path = "actix-multipart" } +# awc = { path = "awc" } + +actix-codec = { path = "../actix-net/actix-codec" } +actix-connect = { path = "../actix-net/actix-connect" } +actix-ioframe = { path = "../actix-net/actix-ioframe" } +actix-rt = { path = "../actix-net/actix-rt" } +actix-server = { path = "../actix-net/actix-server" } +actix-server-config = { path = "../actix-net/actix-server-config" } +actix-service = { path = "../actix-net/actix-service" } +actix-testing = { path = "../actix-net/actix-testing" } +actix-threadpool = { path = "../actix-net/actix-threadpool" } +actix-utils = { path = "../actix-net/actix-utils" } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ee0ded59..1cc5e43a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.11" +version = "0.3.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -13,10 +13,11 @@ categories = ["network-programming", "asynchronous", "web-programming::websocket"] license = "MIT/Apache-2.0" edition = "2018" -workspace = ".." + +# workspace = ".." [package.metadata.docs.rs] -features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"] +features = ["openssl", "fail", "brotli", "flate2-zlib", "secure-cookies"] [lib] name = "actix_http" @@ -26,10 +27,10 @@ path = "src/lib.rs" default = [] # openssl -ssl = ["openssl", "actix-connect/ssl"] +openssl = ["open-ssl", "actix-connect/openssl"] # rustls support -rust-tls = ["rustls", "webpki-roots", "actix-connect/rust-tls"] +rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -47,23 +48,24 @@ fail = ["failure"] secure-cookies = ["ring"] [dependencies] -actix-service = "0.4.1" -actix-codec = "0.1.2" -actix-connect = "0.2.4" -actix-utils = "0.4.4" -actix-server-config = "0.1.2" -actix-threadpool = "0.1.1" +actix-service = "1.0.0-alpha.1" +actix-codec = "0.2.0-alpha.1" +actix-connect = "1.0.0-alpha.1" +actix-utils = "0.5.0-alpha.1" +actix-server-config = "0.3.0-alpha.1" +actix-threadpool = "0.2.0-alpha.1" base64 = "0.10" bitflags = "1.0" bytes = "0.4" copyless = "0.1.4" +chrono = "0.4.6" derive_more = "0.15.0" either = "1.5.2" encoding_rs = "0.8" -futures = "0.1.25" +futures = "0.3.1" hashbrown = "0.6.3" -h2 = "0.1.16" +h2 = "0.2.0-alpha.3" http = "0.1.17" httparse = "1.3" indexmap = "1.2" @@ -80,13 +82,16 @@ sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.6.1" time = "0.1.42" -tokio-tcp = "0.1.3" -tokio-timer = "0.2.8" -tokio-current-thread = "0.1" -trust-dns-resolver = { version="0.11.1", default-features = false } + +tokio = "=0.2.0-alpha.6" +tokio-io = "=0.2.0-alpha.6" +tokio-net = "=0.2.0-alpha.6" +tokio-timer = "0.3.0-alpha.6" +tokio-executor = "=0.2.0-alpha.6" +trust-dns-resolver = { version="0.18.0-alpha.1", default-features = false } # for secure cookie -ring = { version = "0.14.6", optional = true } +ring = { version = "0.16.9", optional = true } # compression brotli2 = { version="0.3.2", optional = true } @@ -94,17 +99,25 @@ flate2 = { version="1.0.7", optional = true, default-features = false } # optional deps failure = { version = "0.1.5", optional = true } -openssl = { version="0.10", optional = true } -rustls = { version = "0.15.2", optional = true } -webpki-roots = { version = "0.16", optional = true } -chrono = "0.4.6" +open-ssl = { version="0.10", package="openssl", optional = true } +rust-tls = { version = "0.16.0", package="rustls", optional = true } +webpki-roots = { version = "0.18", optional = true } [dev-dependencies] -actix-rt = "0.2.2" -actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } -actix-connect = { version = "0.2.0", features=["ssl"] } -actix-http-test = { version = "0.2.4", features=["ssl"] } +actix-rt = "1.0.0-alpha.1" +actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } +actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } +#actix-http-test = { version = "0.2.4", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" -openssl = { version="0.10" } -tokio-tcp = "0.1" +open-ssl = { version="0.10", package="openssl" } + +[patch.crates-io] +actix-codec = { path = "../../actix-net/actix-codec" } +actix-connect = { path = "../../actix-net/actix-connect" } +actix-rt = { path = "../../actix-net/actix-rt" } +actix-server = { path = "../../actix-net/actix-server" } +actix-server-config = { path = "../../actix-net/actix-server-config" } +actix-service = { path = "../../actix-net/actix-service" } +actix-threadpool = { path = "../../actix-net/actix-threadpool" } +actix-utils = { path = "../../actix-net/actix-utils" } diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index b761738e..7b86bfb1 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -1,8 +1,10 @@ use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll, Stream}; +use futures::Stream; use crate::error::Error; @@ -29,10 +31,10 @@ impl BodySize { } /// Type that provides this trait can be streamed to a peer. -pub trait MessageBody { +pub trait MessageBody: Unpin { fn size(&self) -> BodySize; - fn poll_next(&mut self) -> Poll, Error>; + fn poll_next(&mut self, cx: &mut Context) -> Poll>>; } impl MessageBody for () { @@ -40,8 +42,8 @@ impl MessageBody for () { BodySize::Empty } - fn poll_next(&mut self) -> Poll, Error> { - Ok(Async::Ready(None)) + fn poll_next(&mut self, _: &mut Context) -> Poll>> { + Poll::Ready(None) } } @@ -50,8 +52,8 @@ impl MessageBody for Box { self.as_ref().size() } - fn poll_next(&mut self) -> Poll, Error> { - self.as_mut().poll_next() + fn poll_next(&mut self, cx: &mut Context) -> Poll>> { + self.as_mut().poll_next(cx) } } @@ -93,20 +95,19 @@ impl MessageBody for ResponseBody { } } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, cx: &mut Context) -> Poll>> { match self { - ResponseBody::Body(ref mut body) => body.poll_next(), - ResponseBody::Other(ref mut body) => body.poll_next(), + ResponseBody::Body(ref mut body) => body.poll_next(cx), + ResponseBody::Other(ref mut body) => body.poll_next(cx), } } } impl Stream for ResponseBody { - type Item = Bytes; - type Error = Error; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { - self.poll_next() + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.get_mut().poll_next(cx) } } @@ -144,19 +145,19 @@ impl MessageBody for Body { } } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, cx: &mut Context) -> Poll>> { match self { - Body::None => Ok(Async::Ready(None)), - Body::Empty => Ok(Async::Ready(None)), + Body::None => Poll::Ready(None), + Body::Empty => Poll::Ready(None), Body::Bytes(ref mut bin) => { let len = bin.len(); if len == 0 { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { - Ok(Async::Ready(Some(mem::replace(bin, Bytes::new())))) + Poll::Ready(Some(Ok(mem::replace(bin, Bytes::new())))) } } - Body::Message(ref mut body) => body.poll_next(), + Body::Message(ref mut body) => body.poll_next(cx), } } } @@ -242,7 +243,7 @@ impl From for Body { impl From> for Body where - S: Stream + 'static, + S: Stream> + Unpin + 'static, { fn from(s: SizedStream) -> Body { Body::from_message(s) @@ -251,8 +252,8 @@ where impl From> for Body where - S: Stream + 'static, - E: Into + 'static, + S: Stream> + Unpin + 'static, + E: Into + Unpin + 'static, { fn from(s: BodyStream) -> Body { Body::from_message(s) @@ -264,11 +265,11 @@ impl MessageBody for Bytes { BodySize::Sized(self.len()) } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, _: &mut Context) -> Poll>> { if self.is_empty() { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { - Ok(Async::Ready(Some(mem::replace(self, Bytes::new())))) + Poll::Ready(Some(Ok(mem::replace(self, Bytes::new())))) } } } @@ -278,13 +279,11 @@ impl MessageBody for BytesMut { BodySize::Sized(self.len()) } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, _: &mut Context) -> Poll>> { if self.is_empty() { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { - Ok(Async::Ready(Some( - mem::replace(self, BytesMut::new()).freeze(), - ))) + Poll::Ready(Some(Ok(mem::replace(self, BytesMut::new()).freeze()))) } } } @@ -294,11 +293,11 @@ impl MessageBody for &'static str { BodySize::Sized(self.len()) } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, _: &mut Context) -> Poll>> { if self.is_empty() { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { - Ok(Async::Ready(Some(Bytes::from_static( + Poll::Ready(Some(Ok(Bytes::from_static( mem::replace(self, "").as_ref(), )))) } @@ -310,13 +309,11 @@ impl MessageBody for &'static [u8] { BodySize::Sized(self.len()) } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, _: &mut Context) -> Poll>> { if self.is_empty() { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { - Ok(Async::Ready(Some(Bytes::from_static(mem::replace( - self, b"", - ))))) + Poll::Ready(Some(Ok(Bytes::from_static(mem::replace(self, b""))))) } } } @@ -326,14 +323,11 @@ impl MessageBody for Vec { BodySize::Sized(self.len()) } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, _: &mut Context) -> Poll>> { if self.is_empty() { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { - Ok(Async::Ready(Some(Bytes::from(mem::replace( - self, - Vec::new(), - ))))) + Poll::Ready(Some(Ok(Bytes::from(mem::replace(self, Vec::new()))))) } } } @@ -343,11 +337,11 @@ impl MessageBody for String { BodySize::Sized(self.len()) } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, _: &mut Context) -> Poll>> { if self.is_empty() { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { - Ok(Async::Ready(Some(Bytes::from( + Poll::Ready(Some(Ok(Bytes::from( mem::replace(self, String::new()).into_bytes(), )))) } @@ -363,7 +357,7 @@ pub struct BodyStream { impl BodyStream where - S: Stream, + S: Stream>, E: Into, { pub fn new(stream: S) -> Self { @@ -376,15 +370,17 @@ where impl MessageBody for BodyStream where - S: Stream, - E: Into, + S: Stream> + Unpin, + E: Into + Unpin, { fn size(&self) -> BodySize { BodySize::Stream } - fn poll_next(&mut self) -> Poll, Error> { - self.stream.poll().map_err(std::convert::Into::into) + fn poll_next(&mut self, cx: &mut Context) -> Poll>> { + Pin::new(&mut self.stream) + .poll_next(cx) + .map(|res| res.map(|res| res.map_err(std::convert::Into::into))) } } @@ -397,7 +393,7 @@ pub struct SizedStream { impl SizedStream where - S: Stream, + S: Stream>, { pub fn new(size: u64, stream: S) -> Self { SizedStream { size, stream } @@ -406,14 +402,14 @@ where impl MessageBody for SizedStream where - S: Stream, + S: Stream> + Unpin, { fn size(&self) -> BodySize { BodySize::Sized64(self.size) } - fn poll_next(&mut self) -> Poll, Error> { - self.stream.poll() + fn poll_next(&mut self, cx: &mut Context) -> Poll>> { + Pin::new(&mut self.stream).poll_next(cx) } } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index cd23b726..8997d720 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use actix_codec::Framed; use actix_server_config::ServerConfig as SrvConfig; -use actix_service::{IntoNewService, NewService, Service}; +use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use crate::body::MessageBody; use crate::config::{KeepAlive, ServiceConfig}; @@ -32,9 +32,12 @@ pub struct HttpServiceBuilder> { impl HttpServiceBuilder> where - S: NewService, - S::Error: Into, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, { /// Create instance of `ServiceConfigBuilder` pub fn new() -> Self { @@ -52,19 +55,28 @@ where impl HttpServiceBuilder where - S: NewService, - S::Error: Into, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - X: NewService, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: NewService< + X::Future: Unpin, + X::Service: Unpin, + ::Future: Unpin + 'static, + U: ServiceFactory< Config = SrvConfig, Request = (Request, Framed), Response = (), >, U::Error: fmt::Display, U::InitError: fmt::Debug, + U::Future: Unpin, + U::Service: Unpin, + ::Future: Unpin + 'static, { /// Set server keep-alive setting. /// @@ -108,16 +120,19 @@ where /// request will be forwarded to main service. pub fn expect(self, expect: F) -> HttpServiceBuilder where - F: IntoNewService, - X1: NewService, + F: IntoServiceFactory, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, + X1::Future: Unpin, + X1::Service: Unpin, + ::Future: Unpin + 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, client_timeout: self.client_timeout, client_disconnect: self.client_disconnect, - expect: expect.into_new_service(), + expect: expect.into_factory(), upgrade: self.upgrade, on_connect: self.on_connect, _t: PhantomData, @@ -130,21 +145,24 @@ where /// and this service get called with original request and framed object. pub fn upgrade(self, upgrade: F) -> HttpServiceBuilder where - F: IntoNewService, - U1: NewService< + F: IntoServiceFactory, + U1: ServiceFactory< Config = SrvConfig, Request = (Request, Framed), Response = (), >, U1::Error: fmt::Display, U1::InitError: fmt::Debug, + U1::Future: Unpin, + U1::Service: Unpin, + ::Future: Unpin + 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, client_timeout: self.client_timeout, client_disconnect: self.client_disconnect, expect: self.expect, - upgrade: Some(upgrade.into_new_service()), + upgrade: Some(upgrade.into_factory()), on_connect: self.on_connect, _t: PhantomData, } @@ -167,17 +185,21 @@ where pub fn h1(self, service: F) -> H1Service where B: MessageBody + 'static, - F: IntoNewService, - S::Error: Into, + F: IntoServiceFactory, + S::Future: Unpin, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - S::Response: Into>, + S::Response: Into> + Unpin + 'static, + S::Service: Unpin, + ::Future: Unpin + 'static, + P: Unpin, { let cfg = ServiceConfig::new( self.keep_alive, self.client_timeout, self.client_disconnect, ); - H1Service::with_config(cfg, service.into_new_service()) + H1Service::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) .on_connect(self.on_connect) @@ -187,37 +209,42 @@ where pub fn h2(self, service: F) -> H2Service where B: MessageBody + 'static, - F: IntoNewService, - S::Error: Into, + F: IntoServiceFactory, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - S::Response: Into>, - ::Future: 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, + P: Unpin, { let cfg = ServiceConfig::new( self.keep_alive, self.client_timeout, self.client_disconnect, ); - H2Service::with_config(cfg, service.into_new_service()) - .on_connect(self.on_connect) + H2Service::with_config(cfg, service.into_factory()).on_connect(self.on_connect) } /// Finish service configuration and create `HttpService` instance. pub fn finish(self, service: F) -> HttpService where B: MessageBody + 'static, - F: IntoNewService, - S::Error: Into, + F: IntoServiceFactory, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - S::Response: Into>, - ::Future: 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, + P: Unpin, { let cfg = ServiceConfig::new( self.keep_alive, self.client_timeout, self.client_disconnect, ); - HttpService::with_config(cfg, service.into_new_service()) + HttpService::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) .on_connect(self.on_connect) diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 98e8618c..4ae28ba6 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -1,15 +1,18 @@ use std::fmt; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; use actix_connect::{ default_connector, Connect as TcpConnect, Connection as TcpConnection, }; -use actix_service::{apply_fn, Service, ServiceExt}; +use actix_service::{apply_fn, Service}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use http::Uri; -use tokio_tcp::TcpStream; +use tokio_net::tcp::TcpStream; use super::connection::Connection; use super::error::ConnectError; @@ -212,7 +215,7 @@ where pub fn finish( self, ) -> impl Service - + Clone { + + Clone { #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] { let connector = TimeoutService::new( diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index b078c6a6..14984253 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -1,10 +1,13 @@ +use std::future::Future; use std::io::Write; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{ok, Either}; -use futures::{Async, Future, Poll, Sink, Stream}; +use futures::{Sink, Stream}; use crate::error::PayloadError; use crate::h1; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 5744a154..50d74fe1 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,9 +1,11 @@ +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; use futures::future::{err, Either}; -use futures::{Async, Future, Poll}; use h2::{client::SendRequest, SendStream}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index a3522ff8..4d02e0a1 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -1,22 +1,24 @@ use std::cell::RefCell; use std::collections::VecDeque; +use std::future::Future; use std::io; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; +use actix_utils::oneshot; +use actix_utils::task::LocalWaker; use bytes::Bytes; use futures::future::{err, ok, Either, FutureResult}; -use futures::task::AtomicTask; -use futures::unsync::oneshot; -use futures::{Async, Future, Poll}; use h2::client::{handshake, Handshake}; use hashbrown::HashMap; use http::uri::Authority; use indexmap::IndexSet; use slab::Slab; -use tokio_timer::{sleep, Delay}; +use tokio_timer::{delay_for, Delay}; use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; @@ -140,7 +142,7 @@ where // start support future if !support { self.1.as_ref().borrow_mut().task = Some(AtomicTask::new()); - tokio_current_thread::spawn(ConnectorPoolSupport { + tokio_executor::current_thread::spawn(ConnectorPoolSupport { connector: self.0.clone(), inner: self.1.clone(), }) @@ -255,7 +257,7 @@ where if let Some(ref mut h2) = self.h2 { return match h2.poll() { Ok(Async::Ready((snd, connection))) => { - tokio_current_thread::spawn(connection.map_err(|_| ())); + tokio_executor::current_thread::spawn(connection.map_err(|_| ())); Ok(Async::Ready(IoConnection::new( ConnectionType::H2(snd), Instant::now(), @@ -373,7 +375,7 @@ where { if let Some(timeout) = self.disconnect_timeout { if let ConnectionType::H1(io) = conn.io { - tokio_current_thread::spawn(CloseConnection::new( + tokio_executor::current_thread::spawn(CloseConnection::new( io, timeout, )) } @@ -387,7 +389,7 @@ where Ok(n) if n > 0 => { if let Some(timeout) = self.disconnect_timeout { if let ConnectionType::H1(io) = io { - tokio_current_thread::spawn( + tokio_executor::current_thread::spawn( CloseConnection::new(io, timeout), ) } @@ -421,7 +423,7 @@ where self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { if let ConnectionType::H1(io) = io { - tokio_current_thread::spawn(CloseConnection::new(io, timeout)) + tokio_executor::current_thread::spawn(CloseConnection::new(io, timeout)) } } self.check_availibility(); @@ -448,7 +450,7 @@ where fn new(io: T, timeout: Duration) -> Self { CloseConnection { io, - timeout: sleep(timeout), + timeout: delay_for(timeout), } } } @@ -558,7 +560,7 @@ where inner: Rc>>, fut: F, ) { - tokio_current_thread::spawn(OpenWaitingConnection { + tokio_executor::current_thread::spawn(OpenWaitingConnection { key, fut, h2: None, @@ -593,7 +595,7 @@ where if let Some(ref mut h2) = self.h2 { return match h2.poll() { Ok(Async::Ready((snd, connection))) => { - tokio_current_thread::spawn(connection.map_err(|_| ())); + tokio_executor::current_thread::spawn(connection.map_err(|_| ())); let rx = self.rx.take().unwrap(); let _ = rx.send(Ok(IoConnection::new( ConnectionType::H2(snd), diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs index ffc1d061..18869c66 100644 --- a/actix-http/src/cloneable.rs +++ b/actix-http/src/cloneable.rs @@ -1,8 +1,8 @@ use std::cell::UnsafeCell; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_service::Service; -use futures::Poll; #[doc(hidden)] /// Service that allows to turn non-clone service to a service with `Clone` impl @@ -32,8 +32,8 @@ where type Error = T::Error; type Future = T::Future; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - unsafe { &mut *self.0.as_ref().get() }.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + unsafe { &mut *self.0.as_ref().get() }.poll_ready(cx) } fn call(&mut self, req: T::Request) -> Self::Future { diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index bdfecef3..a2dab8f0 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -5,9 +5,9 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use bytes::BytesMut; -use futures::{future, Future}; +use futures::{future, Future, FutureExt}; use time; -use tokio_timer::{sleep, Delay}; +use tokio_timer::{delay, delay_for, Delay}; // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; @@ -104,10 +104,10 @@ impl ServiceConfig { #[inline] /// Client timeout for first request. pub fn client_timer(&self) -> Option { - let delay = self.0.client_timeout; - if delay != 0 { - Some(Delay::new( - self.0.timer.now() + Duration::from_millis(delay), + let delay_time = self.0.client_timeout; + if delay_time != 0 { + Some(delay( + self.0.timer.now() + Duration::from_millis(delay_time), )) } else { None @@ -138,7 +138,7 @@ impl ServiceConfig { /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(Delay::new(self.0.timer.now() + ka)) + Some(delay(self.0.timer.now() + ka)) } else { None } @@ -242,12 +242,12 @@ impl DateService { // periodic date update let s = self.clone(); - tokio_current_thread::spawn(sleep(Duration::from_millis(500)).then( - move |_| { + tokio_executor::current_thread::spawn( + delay_for(Duration::from_millis(500)).then(move |_| { s.0.reset(); - future::ok(()) - }, - )); + future::ready(()) + }), + ); } } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 4b56a1b6..1e51e8b5 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -1,4 +1,7 @@ +use std::future::Future; use std::io::{self, Write}; +use std::pin::Pin; +use std::task::{Context, Poll}; use actix_threadpool::{run, CpuFuture}; #[cfg(feature = "brotli")] @@ -6,7 +9,7 @@ use brotli2::write::BrotliDecoder; use bytes::Bytes; #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] use flate2::write::{GzDecoder, ZlibDecoder}; -use futures::{try_ready, Async, Future, Poll, Stream}; +use futures::{ready, Stream}; use super::Writer; use crate::error::PayloadError; @@ -18,12 +21,12 @@ pub struct Decoder { decoder: Option, stream: S, eof: bool, - fut: Option, ContentDecoder), io::Error>>, + fut: Option, ContentDecoder), io::Error>>>, } impl Decoder where - S: Stream, + S: Stream>, { /// Construct a decoder. #[inline] @@ -71,34 +74,41 @@ where impl Stream for Decoder where - S: Stream, + S: Stream> + Unpin, { - type Item = Bytes; - type Error = PayloadError; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll> { loop { if let Some(ref mut fut) = self.fut { - let (chunk, decoder) = try_ready!(fut.poll()); + let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) { + Ok(Ok(item)) => item, + Ok(Err(e)) => return Poll::Ready(Some(Err(e.into()))), + Err(e) => return Poll::Ready(Some(Err(e.into()))), + }; self.decoder = Some(decoder); self.fut.take(); if let Some(chunk) = chunk { - return Ok(Async::Ready(Some(chunk))); + return Poll::Ready(Some(Ok(chunk))); } } if self.eof { - return Ok(Async::Ready(None)); + return Poll::Ready(None); } - match self.stream.poll()? { - Async::Ready(Some(chunk)) => { + match Pin::new(&mut self.stream).poll_next(cx) { + Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))), + Poll::Ready(Some(Ok(chunk))) => { if let Some(mut decoder) = self.decoder.take() { if chunk.len() < INPLACE { let chunk = decoder.feed_data(chunk)?; self.decoder = Some(decoder); if let Some(chunk) = chunk { - return Ok(Async::Ready(Some(chunk))); + return Poll::Ready(Some(Ok(chunk))); } } else { self.fut = Some(run(move || { @@ -108,21 +118,25 @@ where } continue; } else { - return Ok(Async::Ready(Some(chunk))); + return Poll::Ready(Some(Ok(chunk))); } } - Async::Ready(None) => { + Poll::Ready(None) => { self.eof = true; return if let Some(mut decoder) = self.decoder.take() { - Ok(Async::Ready(decoder.feed_eof()?)) + match decoder.feed_eof() { + Ok(Some(res)) => Poll::Ready(Some(Ok(res))), + Ok(None) => Poll::Ready(None), + Err(err) => Poll::Ready(Some(Err(err.into()))), + } } else { - Ok(Async::Ready(None)) + Poll::Ready(None) }; } - Async::NotReady => break, + Poll::Pending => break, } } - Ok(Async::NotReady) + Poll::Pending } } diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 58d8a2d9..295d99a2 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -1,5 +1,8 @@ //! Stream encoder +use std::future::Future; use std::io::{self, Write}; +use std::pin::Pin; +use std::task::{Context, Poll}; use actix_threadpool::{run, CpuFuture}; #[cfg(feature = "brotli")] @@ -7,7 +10,6 @@ use brotli2::write::BrotliEncoder; use bytes::Bytes; #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] use flate2::write::{GzEncoder, ZlibEncoder}; -use futures::{Async, Future, Poll}; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; @@ -22,7 +24,7 @@ pub struct Encoder { eof: bool, body: EncoderBody, encoder: Option, - fut: Option>, + fut: Option>>, } impl Encoder { @@ -94,43 +96,46 @@ impl MessageBody for Encoder { } } - fn poll_next(&mut self) -> Poll, Error> { + fn poll_next(&mut self, cx: &mut Context) -> Poll>> { loop { if self.eof { - return Ok(Async::Ready(None)); + return Poll::Ready(None); } if let Some(ref mut fut) = self.fut { - let mut encoder = futures::try_ready!(fut.poll()); + let mut encoder = match futures::ready!(Pin::new(fut).poll(cx)) { + Ok(Ok(item)) => item, + Ok(Err(e)) => return Poll::Ready(Some(Err(e.into()))), + Err(e) => return Poll::Ready(Some(Err(e.into()))), + }; let chunk = encoder.take(); self.encoder = Some(encoder); self.fut.take(); if !chunk.is_empty() { - return Ok(Async::Ready(Some(chunk))); + return Poll::Ready(Some(Ok(chunk))); } } let result = match self.body { EncoderBody::Bytes(ref mut b) => { if b.is_empty() { - Async::Ready(None) + Poll::Ready(None) } else { - Async::Ready(Some(std::mem::replace(b, Bytes::new()))) + Poll::Ready(Some(Ok(std::mem::replace(b, Bytes::new())))) } } - EncoderBody::Stream(ref mut b) => b.poll_next()?, - EncoderBody::BoxedStream(ref mut b) => b.poll_next()?, + EncoderBody::Stream(ref mut b) => b.poll_next(cx), + EncoderBody::BoxedStream(ref mut b) => b.poll_next(cx), }; match result { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(chunk)) => { + Poll::Ready(Some(Ok(chunk))) => { if let Some(mut encoder) = self.encoder.take() { if chunk.len() < INPLACE { encoder.write(&chunk)?; let chunk = encoder.take(); self.encoder = Some(encoder); if !chunk.is_empty() { - return Ok(Async::Ready(Some(chunk))); + return Poll::Ready(Some(Ok(chunk))); } } else { self.fut = Some(run(move || { @@ -139,22 +144,23 @@ impl MessageBody for Encoder { })); } } else { - return Ok(Async::Ready(Some(chunk))); + return Poll::Ready(Some(Ok(chunk))); } } - Async::Ready(None) => { + Poll::Ready(None) => { if let Some(encoder) = self.encoder.take() { let chunk = encoder.finish()?; if chunk.is_empty() { - return Ok(Async::Ready(None)); + return Poll::Ready(None); } else { self.eof = true; - return Ok(Async::Ready(Some(chunk))); + return Poll::Ready(Some(Ok(chunk))); } } else { - return Ok(Async::Ready(None)); + return Poll::Ready(None); } } + val => return val, } } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index cd9613d2..82027dbe 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -6,11 +6,10 @@ use std::str::Utf8Error; use std::string::FromUtf8Error; use std::{fmt, io, result}; -pub use actix_threadpool::BlockingError; use actix_utils::timeout::TimeoutError; use bytes::BytesMut; use derive_more::{Display, From}; -use futures::Canceled; +use futures::channel::oneshot::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; use httparse; @@ -197,8 +196,8 @@ impl ResponseError for DeError { } } -/// `InternalServerError` for `BlockingError` -impl ResponseError for BlockingError {} +/// `InternalServerError` for `Canceled` +impl ResponseError for Canceled {} /// Return `BAD_REQUEST` for `Utf8Error` impl ResponseError for Utf8Error { @@ -236,9 +235,6 @@ impl ResponseError for header::InvalidHeaderValueBytes { } } -/// `InternalServerError` for `futures::Canceled` -impl ResponseError for Canceled {} - /// A set of errors that can occur during parsing HTTP streams #[derive(Debug, Display)] pub enum ParseError { @@ -365,15 +361,12 @@ impl From for PayloadError { } } -impl From> for PayloadError { - fn from(err: BlockingError) -> Self { - match err { - BlockingError::Error(e) => PayloadError::Io(e), - BlockingError::Canceled => PayloadError::Io(io::Error::new( - io::ErrorKind::Other, - "Thread pool is gone", - )), - } +impl From for PayloadError { + fn from(_: Canceled) -> Self { + PayloadError::Io(io::Error::new( + io::ErrorKind::Other, + "Operation is canceled", + )) } } @@ -390,12 +383,12 @@ impl ResponseError for PayloadError { } } -/// Return `BadRequest` for `cookie::ParseError` -impl ResponseError for crate::cookie::ParseError { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) - } -} +// /// Return `BadRequest` for `cookie::ParseError` +// impl ResponseError for crate::cookie::ParseError { +// fn error_response(&self) -> Response { +// Response::new(StatusCode::BAD_REQUEST) +// } +// } #[derive(Debug, Display, From)] /// A set of errors that can occur during dispatching http requests diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index ce113a14..272270ca 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -1,10 +1,12 @@ +use std::future::Future; use std::io; use std::marker::PhantomData; use std::mem::MaybeUninit; +use std::pin::Pin; +use std::task::{Context, Poll}; use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; -use futures::{Async, Poll}; use http::header::{HeaderName, HeaderValue}; use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version}; use httparse; @@ -442,9 +444,10 @@ impl Decoder for PayloadDecoder { loop { let mut buf = None; // advances the chunked state - *state = match state.step(src, size, &mut buf)? { - Async::NotReady => return Ok(None), - Async::Ready(state) => state, + *state = match state.step(src, size, &mut buf) { + Poll::Pending => return Ok(None), + Poll::Ready(Ok(state)) => state, + Poll::Ready(Err(e)) => return Err(e), }; if *state == ChunkedState::End { trace!("End of chunked stream"); @@ -476,7 +479,7 @@ macro_rules! byte ( $rdr.split_to(1); b } else { - return Ok(Async::NotReady) + return Poll::Pending } }) ); @@ -487,7 +490,7 @@ impl ChunkedState { body: &mut BytesMut, size: &mut u64, buf: &mut Option, - ) -> Poll { + ) -> Poll> { use self::ChunkedState::*; match *self { Size => ChunkedState::read_size(body, size), @@ -499,10 +502,14 @@ impl ChunkedState { BodyLf => ChunkedState::read_body_lf(body), EndCr => ChunkedState::read_end_cr(body), EndLf => ChunkedState::read_end_lf(body), - End => Ok(Async::Ready(ChunkedState::End)), + End => Poll::Ready(Ok(ChunkedState::End)), } } - fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll { + + fn read_size( + rdr: &mut BytesMut, + size: &mut u64, + ) -> Poll> { let radix = 16; match byte!(rdr) { b @ b'0'..=b'9' => { @@ -517,48 +524,49 @@ impl ChunkedState { *size *= radix; *size += u64::from(b + 10 - b'A'); } - b'\t' | b' ' => return Ok(Async::Ready(ChunkedState::SizeLws)), - b';' => return Ok(Async::Ready(ChunkedState::Extension)), - b'\r' => return Ok(Async::Ready(ChunkedState::SizeLf)), + b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)), + b';' => return Poll::Ready(Ok(ChunkedState::Extension)), + b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)), _ => { - return Err(io::Error::new( + return Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk size line: Invalid Size", - )); + ))); } } - Ok(Async::Ready(ChunkedState::Size)) + Poll::Ready(Ok(ChunkedState::Size)) } - fn read_size_lws(rdr: &mut BytesMut) -> Poll { + + fn read_size_lws(rdr: &mut BytesMut) -> Poll> { trace!("read_size_lws"); match byte!(rdr) { // LWS can follow the chunk size, but no more digits can come - b'\t' | b' ' => Ok(Async::Ready(ChunkedState::SizeLws)), - b';' => Ok(Async::Ready(ChunkedState::Extension)), - b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), - _ => Err(io::Error::new( + b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)), + b';' => Poll::Ready(Ok(ChunkedState::Extension)), + b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), + _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk size linear white space", - )), + ))), } } - fn read_extension(rdr: &mut BytesMut) -> Poll { + fn read_extension(rdr: &mut BytesMut) -> Poll> { match byte!(rdr) { - b'\r' => Ok(Async::Ready(ChunkedState::SizeLf)), - _ => Ok(Async::Ready(ChunkedState::Extension)), // no supported extensions + b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), + _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions } } fn read_size_lf( rdr: &mut BytesMut, size: &mut u64, - ) -> Poll { + ) -> Poll> { match byte!(rdr) { - b'\n' if *size > 0 => Ok(Async::Ready(ChunkedState::Body)), - b'\n' if *size == 0 => Ok(Async::Ready(ChunkedState::EndCr)), - _ => Err(io::Error::new( + b'\n' if *size > 0 => Poll::Ready(Ok(ChunkedState::Body)), + b'\n' if *size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), + _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk size LF", - )), + ))), } } @@ -566,12 +574,12 @@ impl ChunkedState { rdr: &mut BytesMut, rem: &mut u64, buf: &mut Option, - ) -> Poll { + ) -> Poll> { trace!("Chunked read, remaining={:?}", rem); let len = rdr.len() as u64; if len == 0 { - Ok(Async::Ready(ChunkedState::Body)) + Poll::Ready(Ok(ChunkedState::Body)) } else { let slice; if *rem > len { @@ -583,47 +591,47 @@ impl ChunkedState { } *buf = Some(slice); if *rem > 0 { - Ok(Async::Ready(ChunkedState::Body)) + Poll::Ready(Ok(ChunkedState::Body)) } else { - Ok(Async::Ready(ChunkedState::BodyCr)) + Poll::Ready(Ok(ChunkedState::BodyCr)) } } } - fn read_body_cr(rdr: &mut BytesMut) -> Poll { + fn read_body_cr(rdr: &mut BytesMut) -> Poll> { match byte!(rdr) { - b'\r' => Ok(Async::Ready(ChunkedState::BodyLf)), - _ => Err(io::Error::new( + b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)), + _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk body CR", - )), + ))), } } - fn read_body_lf(rdr: &mut BytesMut) -> Poll { + fn read_body_lf(rdr: &mut BytesMut) -> Poll> { match byte!(rdr) { - b'\n' => Ok(Async::Ready(ChunkedState::Size)), - _ => Err(io::Error::new( + b'\n' => Poll::Ready(Ok(ChunkedState::Size)), + _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk body LF", - )), + ))), } } - fn read_end_cr(rdr: &mut BytesMut) -> Poll { + fn read_end_cr(rdr: &mut BytesMut) -> Poll> { match byte!(rdr) { - b'\r' => Ok(Async::Ready(ChunkedState::EndLf)), - _ => Err(io::Error::new( + b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)), + _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk end CR", - )), + ))), } } - fn read_end_lf(rdr: &mut BytesMut) -> Poll { + fn read_end_lf(rdr: &mut BytesMut) -> Poll> { match byte!(rdr) { - b'\n' => Ok(Async::Ready(ChunkedState::End)), - _ => Err(io::Error::new( + b'\n' => Poll::Ready(Ok(ChunkedState::End)), + _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk end LF", - )), + ))), } } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index c82eb4ac..16e36447 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,15 +1,17 @@ use std::collections::VecDeque; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Instant; -use std::{fmt, io, net}; +use std::{fmt, io, io::Write, net}; -use actix_codec::{Decoder, Encoder, Framed, FramedParts}; +use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; use actix_server_config::IoStream; use actix_service::Service; use bitflags::bitflags; use bytes::{BufMut, BytesMut}; -use futures::{Async, Future, Poll}; use log::{error, trace}; -use tokio_timer::Delay; +use tokio_timer::{delay, Delay}; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::cloneable::CloneableService; @@ -46,11 +48,14 @@ pub struct Dispatcher where S: Service, S::Error: Into, + S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, + X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, + U::Future: Unpin, { inner: DispatcherState, } @@ -59,11 +64,14 @@ enum DispatcherState where S: Service, S::Error: Into, + S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, + X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, + U::Future: Unpin, { Normal(InnerDispatcher), Upgrade(U::Future), @@ -74,11 +82,14 @@ struct InnerDispatcher where S: Service, S::Error: Into, + S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, + X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, + U::Future: Unpin, { service: CloneableService, expect: CloneableService, @@ -170,11 +181,14 @@ where S: Service, S::Error: Into, S::Response: Into>, + S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, + X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, + U::Future: Unpin, { /// Create http/1 dispatcher. pub(crate) fn new( @@ -255,20 +269,23 @@ where S: Service, S::Error: Into, S::Response: Into>, + S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, + X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, + U::Future: Unpin, { - fn can_read(&self) -> bool { + fn can_read(&self, cx: &mut Context) -> bool { if self .flags .intersects(Flags::READ_DISCONNECT | Flags::UPGRADE) { false } else if let Some(ref info) = self.payload { - info.need_read() == PayloadStatus::Read + info.need_read(cx) == PayloadStatus::Read } else { true } @@ -287,7 +304,7 @@ where /// /// true - got whouldblock /// false - didnt get whouldblock - fn poll_flush(&mut self) -> Result { + fn poll_flush(&mut self, cx: &mut Context) -> Result { if self.write_buf.is_empty() { return Ok(false); } @@ -295,23 +312,23 @@ where let len = self.write_buf.len(); let mut written = 0; while written < len { - match self.io.write(&self.write_buf[written..]) { - Ok(0) => { + match Pin::new(&mut self.io).poll_write(cx, &self.write_buf[written..]) { + Poll::Ready(Ok(0)) => { return Err(DispatchError::Io(io::Error::new( io::ErrorKind::WriteZero, "", ))); } - Ok(n) => { + Poll::Ready(Ok(n)) => { written += n; } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + Poll::Pending => { if written > 0 { let _ = self.write_buf.split_to(written); } return Ok(true); } - Err(err) => return Err(DispatchError::Io(err)), + Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)), } } if written > 0 { @@ -350,12 +367,15 @@ where .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); } - fn poll_response(&mut self) -> Result { + fn poll_response( + &mut self, + cx: &mut Context, + ) -> Result { loop { let state = match self.state { State::None => match self.messages.pop_front() { Some(DispatcherMessage::Item(req)) => { - Some(self.handle_request(req)?) + Some(self.handle_request(req, cx)?) } Some(DispatcherMessage::Error(res)) => { Some(self.send_response(res, ResponseBody::Other(Body::Empty))?) @@ -365,54 +385,54 @@ where } None => None, }, - State::ExpectCall(ref mut fut) => match fut.poll() { - Ok(Async::Ready(req)) => { + State::ExpectCall(ref mut fut) => match Pin::new(fut).poll(cx) { + Poll::Ready(Ok(req)) => { self.send_continue(); self.state = State::ServiceCall(self.service.call(req)); continue; } - Ok(Async::NotReady) => None, - Err(e) => { + Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); Some(self.send_response(res, body.into_body())?) } + Poll::Pending => None, }, - State::ServiceCall(ref mut fut) => match fut.poll() { - Ok(Async::Ready(res)) => { + State::ServiceCall(ref mut fut) => match Pin::new(fut).poll(cx) { + Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); self.state = self.send_response(res, body)?; continue; } - Ok(Async::NotReady) => None, - Err(e) => { + Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); Some(self.send_response(res, body.into_body())?) } + Poll::Pending => None, }, State::SendPayload(ref mut stream) => { loop { if self.write_buf.len() < HW_BUFFER_SIZE { - match stream - .poll_next() - .map_err(|_| DispatchError::Unknown)? - { - Async::Ready(Some(item)) => { + match stream.poll_next(cx) { + Poll::Ready(Some(Ok(item))) => { self.codec.encode( Message::Chunk(Some(item)), &mut self.write_buf, )?; continue; } - Async::Ready(None) => { + Poll::Ready(None) => { self.codec.encode( Message::Chunk(None), &mut self.write_buf, )?; self.state = State::None; } - Async::NotReady => return Ok(PollResponse::DoNothing), + Poll::Ready(Some(Err(_))) => { + return Err(DispatchError::Unknown) + } + Poll::Pending => return Ok(PollResponse::DoNothing), } } else { return Ok(PollResponse::DrainWriteBuf); @@ -433,7 +453,7 @@ where // if read-backpressure is enabled and we consumed some data. // we may read more data and retry if self.state.is_call() { - if self.poll_request()? { + if self.poll_request(cx)? { continue; } } else if !self.messages.is_empty() { @@ -446,17 +466,21 @@ where Ok(PollResponse::DoNothing) } - fn handle_request(&mut self, req: Request) -> Result, DispatchError> { + fn handle_request( + &mut self, + req: Request, + cx: &mut Context, + ) -> Result, DispatchError> { // Handle `EXPECT: 100-Continue` header let req = if req.head().expect() { let mut task = self.expect.call(req); - match task.poll() { - Ok(Async::Ready(req)) => { + match Pin::new(&mut task).poll(cx) { + Poll::Ready(Ok(req)) => { self.send_continue(); req } - Ok(Async::NotReady) => return Ok(State::ExpectCall(task)), - Err(e) => { + Poll::Pending => return Ok(State::ExpectCall(task)), + Poll::Ready(Err(e)) => { let e = e.into(); let res: Response = e.into(); let (res, body) = res.replace_body(()); @@ -469,13 +493,13 @@ where // Call service let mut task = self.service.call(req); - match task.poll() { - Ok(Async::Ready(res)) => { + match Pin::new(&mut task).poll(cx) { + Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); self.send_response(res, body) } - Ok(Async::NotReady) => Ok(State::ServiceCall(task)), - Err(e) => { + Poll::Pending => Ok(State::ServiceCall(task)), + Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); self.send_response(res, body.into_body()) @@ -484,9 +508,12 @@ where } /// Process one incoming requests - pub(self) fn poll_request(&mut self) -> Result { + pub(self) fn poll_request( + &mut self, + cx: &mut Context, + ) -> Result { // limit a mount of non processed requests - if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read() { + if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) { return Ok(false); } @@ -521,7 +548,7 @@ where // handle request early if self.state.is_empty() { - self.state = self.handle_request(req)?; + self.state = self.handle_request(req, cx)?; } else { self.messages.push_back(DispatcherMessage::Item(req)); } @@ -587,12 +614,12 @@ where } /// keep-alive timer - fn poll_keepalive(&mut self) -> Result<(), DispatchError> { + fn poll_keepalive(&mut self, cx: &mut Context) -> Result<(), DispatchError> { if self.ka_timer.is_none() { // shutdown timeout if self.flags.contains(Flags::SHUTDOWN) { if let Some(interval) = self.codec.config().client_disconnect_timer() { - self.ka_timer = Some(Delay::new(interval)); + self.ka_timer = Some(delay(interval)); } else { self.flags.insert(Flags::READ_DISCONNECT); if let Some(mut payload) = self.payload.take() { @@ -605,11 +632,8 @@ where } } - match self.ka_timer.as_mut().unwrap().poll().map_err(|e| { - error!("Timer error {:?}", e); - DispatchError::Unknown - })? { - Async::Ready(_) => { + match Pin::new(&mut self.ka_timer.as_mut().unwrap()).poll(cx) { + Poll::Ready(()) => { // if we get timeout during shutdown, drop connection if self.flags.contains(Flags::SHUTDOWN) { return Err(DispatchError::DisconnectTimeout); @@ -624,9 +648,9 @@ where if let Some(deadline) = self.codec.config().client_disconnect_timer() { - if let Some(timer) = self.ka_timer.as_mut() { + if let Some(mut timer) = self.ka_timer.as_mut() { timer.reset(deadline); - let _ = timer.poll(); + let _ = Pin::new(&mut timer).poll(cx); } } else { // no shutdown timeout, drop socket @@ -650,17 +674,17 @@ where } else if let Some(deadline) = self.codec.config().keep_alive_expire() { - if let Some(timer) = self.ka_timer.as_mut() { + if let Some(mut timer) = self.ka_timer.as_mut() { timer.reset(deadline); - let _ = timer.poll(); + let _ = Pin::new(&mut timer).poll(cx); } } - } else if let Some(timer) = self.ka_timer.as_mut() { + } else if let Some(mut timer) = self.ka_timer.as_mut() { timer.reset(self.ka_expire); - let _ = timer.poll(); + let _ = Pin::new(&mut timer).poll(cx); } } - Async::NotReady => (), + Poll::Pending => (), } Ok(()) @@ -673,33 +697,37 @@ where S: Service, S::Error: Into, S::Response: Into>, + S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, + X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, + U::Future: Unpin, { - type Item = (); - type Error = DispatchError; + type Output = Result<(), DispatchError>; #[inline] - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { match self.inner { DispatcherState::Normal(ref mut inner) => { - inner.poll_keepalive()?; + inner.poll_keepalive(cx)?; if inner.flags.contains(Flags::SHUTDOWN) { if inner.flags.contains(Flags::WRITE_DISCONNECT) { - Ok(Async::Ready(())) + Poll::Ready(Ok(())) } else { // flush buffer - inner.poll_flush()?; + inner.poll_flush(cx)?; if !inner.write_buf.is_empty() { - Ok(Async::NotReady) + Poll::Pending } else { - match inner.io.shutdown()? { - Async::Ready(_) => Ok(Async::Ready(())), - Async::NotReady => Ok(Async::NotReady), + match Pin::new(&mut inner.io).poll_shutdown(cx) { + Poll::Ready(res) => { + Poll::Ready(res.map_err(DispatchError::from)) + } + Poll::Pending => Poll::Pending, } } } @@ -707,12 +735,12 @@ where // read socket into a buf let should_disconnect = if !inner.flags.contains(Flags::READ_DISCONNECT) { - read_available(&mut inner.io, &mut inner.read_buf)? + read_available(cx, &mut inner.io, &mut inner.read_buf)? } else { None }; - inner.poll_request()?; + inner.poll_request(cx)?; if let Some(true) = should_disconnect { inner.flags.insert(Flags::READ_DISCONNECT); if let Some(mut payload) = inner.payload.take() { @@ -724,7 +752,7 @@ where if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE { inner.write_buf.reserve(HW_BUFFER_SIZE); } - let result = inner.poll_response()?; + let result = inner.poll_response(cx)?; let drain = result == PollResponse::DrainWriteBuf; // switch to upgrade handler @@ -742,7 +770,7 @@ where self.inner = DispatcherState::Upgrade( inner.upgrade.unwrap().call((req, framed)), ); - return self.poll(); + return self.poll(cx); } else { panic!() } @@ -751,14 +779,14 @@ where // we didnt get WouldBlock from write operation, // so data get written to kernel completely (OSX) // and we have to write again otherwise response can get stuck - if inner.poll_flush()? || !drain { + if inner.poll_flush(cx)? || !drain { break; } } // client is gone if inner.flags.contains(Flags::WRITE_DISCONNECT) { - return Ok(Async::Ready(())); + return Poll::Ready(Ok(())); } let is_empty = inner.state.is_empty(); @@ -771,38 +799,44 @@ where // keep-alive and stream errors if is_empty && inner.write_buf.is_empty() { if let Some(err) = inner.error.take() { - Err(err) + Poll::Ready(Err(err)) } // disconnect if keep-alive is not enabled else if inner.flags.contains(Flags::STARTED) && !inner.flags.intersects(Flags::KEEPALIVE) { inner.flags.insert(Flags::SHUTDOWN); - self.poll() + self.poll(cx) } // disconnect if shutdown else if inner.flags.contains(Flags::SHUTDOWN) { - self.poll() + self.poll(cx) } else { - Ok(Async::NotReady) + Poll::Pending } } else { - Ok(Async::NotReady) + Poll::Pending } } } - DispatcherState::Upgrade(ref mut fut) => fut.poll().map_err(|e| { - error!("Upgrade handler error: {}", e); - DispatchError::Upgrade - }), + DispatcherState::Upgrade(ref mut fut) => { + Pin::new(fut).poll(cx).map_err(|e| { + error!("Upgrade handler error: {}", e); + DispatchError::Upgrade + }) + } DispatcherState::None => panic!(), } } } -fn read_available(io: &mut T, buf: &mut BytesMut) -> Result, io::Error> +fn read_available( + cx: &mut Context, + io: &mut T, + buf: &mut BytesMut, +) -> Result, io::Error> where - T: io::Read, + T: AsyncRead + Unpin, { let mut read_some = false; loop { @@ -810,19 +844,18 @@ where buf.reserve(HW_BUFFER_SIZE); } - let read = unsafe { io.read(buf.bytes_mut()) }; - match read { - Ok(n) => { + match read(cx, io, buf) { + Poll::Pending => { + return if read_some { Ok(Some(false)) } else { Ok(None) }; + } + Poll::Ready(Ok(n)) => { if n == 0 { return Ok(Some(true)); } else { read_some = true; - unsafe { - buf.advance_mut(n); - } } } - Err(e) => { + Poll::Ready(Err(e)) => { return if e.kind() == io::ErrorKind::WouldBlock { if read_some { Ok(Some(false)) @@ -833,12 +866,23 @@ where Ok(Some(true)) } else { Err(e) - }; + } } } } } +fn read( + cx: &mut Context, + io: &mut T, + buf: &mut BytesMut, +) -> Poll> +where + T: AsyncRead + Unpin, +{ + Pin::new(io).poll_read_buf(cx, buf) +} + #[cfg(test)] mod tests { use actix_service::IntoService; diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 32b6bd9c..79831eae 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,21 +1,24 @@ +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + use actix_server_config::ServerConfig; -use actix_service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Poll}; +use actix_service::{Service, ServiceFactory}; +use futures::future::{ok, Ready}; use crate::error::Error; use crate::request::Request; pub struct ExpectHandler; -impl NewService for ExpectHandler { +impl ServiceFactory for ExpectHandler { type Config = ServerConfig; type Request = Request; type Response = Request; type Error = Error; type Service = ExpectHandler; type InitError = Error; - type Future = FutureResult; + type Future = Ready>; fn new_service(&self, _: &ServerConfig) -> Self::Future { ok(ExpectHandler) @@ -26,10 +29,10 @@ impl Service for ExpectHandler { type Request = Request; type Response = Request; type Error = Error; - type Future = FutureResult; + type Future = Ready>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, req: Request) -> Self::Future { diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 28acb64b..036138f9 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -1,12 +1,14 @@ //! Payload stream use std::cell::RefCell; use std::collections::VecDeque; +use std::future::Future; +use std::pin::Pin; use std::rc::{Rc, Weak}; +use std::task::{Context, Poll}; +use actix_utils::task::LocalWaker; use bytes::Bytes; -use futures::task::current as current_task; -use futures::task::Task; -use futures::{Async, Poll, Stream}; +use futures::Stream; use crate::error::PayloadError; @@ -77,15 +79,24 @@ impl Payload { pub fn unread_data(&mut self, data: Bytes) { self.inner.borrow_mut().unread_data(data); } + + #[inline] + pub fn readany( + &mut self, + cx: &mut Context, + ) -> Poll>> { + self.inner.borrow_mut().readany(cx) + } } impl Stream for Payload { - type Item = Bytes; - type Error = PayloadError; + type Item = Result; - #[inline] - fn poll(&mut self) -> Poll, PayloadError> { - self.inner.borrow_mut().readany() + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll>> { + self.inner.borrow_mut().readany(cx) } } @@ -117,19 +128,14 @@ impl PayloadSender { } #[inline] - pub fn need_read(&self) -> PayloadStatus { + pub fn need_read(&self, cx: &mut Context) -> PayloadStatus { // we check need_read only if Payload (other side) is alive, // otherwise always return true (consume payload) if let Some(shared) = self.inner.upgrade() { if shared.borrow().need_read { PayloadStatus::Read } else { - #[cfg(not(test))] - { - if shared.borrow_mut().io_task.is_none() { - shared.borrow_mut().io_task = Some(current_task()); - } - } + shared.borrow_mut().io_task.register(cx.waker()); PayloadStatus::Pause } } else { @@ -145,8 +151,8 @@ struct Inner { err: Option, need_read: bool, items: VecDeque, - task: Option, - io_task: Option, + task: LocalWaker, + io_task: LocalWaker, } impl Inner { @@ -157,8 +163,8 @@ impl Inner { err: None, items: VecDeque::new(), need_read: true, - task: None, - io_task: None, + task: LocalWaker::new(), + io_task: LocalWaker::new(), } } @@ -178,7 +184,7 @@ impl Inner { self.items.push_back(data); self.need_read = self.len < MAX_BUFFER_SIZE; if let Some(task) = self.task.take() { - task.notify() + task.wake() } } @@ -187,34 +193,28 @@ impl Inner { self.len } - fn readany(&mut self) -> Poll, PayloadError> { + fn readany( + &mut self, + cx: &mut Context, + ) -> Poll>> { if let Some(data) = self.items.pop_front() { self.len -= data.len(); self.need_read = self.len < MAX_BUFFER_SIZE; - if self.need_read && self.task.is_none() && !self.eof { - self.task = Some(current_task()); + if self.need_read && !self.eof { + self.task.register(cx.waker()); } - if let Some(task) = self.io_task.take() { - task.notify() - } - Ok(Async::Ready(Some(data))) + self.io_task.wake(); + Poll::Ready(Some(Ok(data))) } else if let Some(err) = self.err.take() { - Err(err) + Poll::Ready(Some(Err(err))) } else if self.eof { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { self.need_read = true; - #[cfg(not(test))] - { - if self.task.is_none() { - self.task = Some(current_task()); - } - if let Some(task) = self.io_task.take() { - task.notify() - } - } - Ok(Async::NotReady) + self.task.register(cx.waker()); + self.io_task.wake(); + Poll::Pending } } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 89bf08e9..95596af7 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,12 +1,15 @@ use std::fmt; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_codec::Framed; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; -use actix_service::{IntoNewService, NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; +use actix_service::{IntoServiceFactory, Service, ServiceFactory}; +use futures::future::{ok, Ready}; +use futures::{ready, Stream}; use crate::body::MessageBody; use crate::cloneable::CloneableService; @@ -20,7 +23,7 @@ use super::codec::Codec; use super::dispatcher::Dispatcher; use super::{ExpectHandler, Message, UpgradeHandler}; -/// `NewService` implementation for HTTP1 transport +/// `ServiceFactory` implementation for HTTP1 transport pub struct H1Service> { srv: S, cfg: ServiceConfig, @@ -32,19 +35,23 @@ pub struct H1Service> { impl H1Service where - S: NewService, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin, B: MessageBody, + P: Unpin, { /// Create new `HttpService` instance with default config. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H1Service { cfg, - srv: service.into_new_service(), + srv: service.into_factory(), expect: ExpectHandler, upgrade: None, on_connect: None, @@ -53,10 +60,13 @@ where } /// Create new `HttpService` instance with config. - pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { + pub fn with_config>( + cfg: ServiceConfig, + service: F, + ) -> Self { H1Service { cfg, - srv: service.into_new_service(), + srv: service.into_factory(), expect: ExpectHandler, upgrade: None, on_connect: None, @@ -67,17 +77,24 @@ where impl H1Service where - S: NewService, + S: ServiceFactory, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin, B: MessageBody, + P: Unpin, { pub fn expect(self, expect: X1) -> H1Service where - X1: NewService, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, + X1::Future: Unpin, + X1::Service: Unpin, + ::Future: Unpin, { H1Service { expect, @@ -91,9 +108,12 @@ where pub fn upgrade(self, upgrade: Option) -> H1Service where - U1: NewService), Response = ()>, + U1: ServiceFactory), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, + U1::Future: Unpin, + U1::Service: Unpin, + ::Future: Unpin, { H1Service { upgrade, @@ -115,24 +135,35 @@ where } } -impl NewService for H1Service +impl ServiceFactory for H1Service where T: IoStream, - S: NewService, + S: ServiceFactory, + S::Service: Unpin, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin, B: MessageBody, - X: NewService, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: NewService< + X::Future: Unpin, + X::Service: Unpin, + ::Future: Unpin, + U: ServiceFactory< Config = SrvConfig, Request = (Request, Framed), Response = (), >, U::Error: fmt::Display, U::InitError: fmt::Debug, + U::Future: Unpin, + U::Service: Unpin, + ::Future: Unpin, + P: Unpin, { type Config = SrvConfig; type Request = Io; @@ -144,7 +175,7 @@ where fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H1ServiceResponse { - fut: self.srv.new_service(cfg).into_future(), + fut: self.srv.new_service(cfg), fut_ex: Some(self.expect.new_service(cfg)), fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), expect: None, @@ -159,15 +190,25 @@ where #[doc(hidden)] pub struct H1ServiceResponse where - S: NewService, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, - X: NewService, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: NewService), Response = ()>, + X::Future: Unpin, + X::Service: Unpin, + ::Future: Unpin, + U: ServiceFactory), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, + U::Future: Unpin, + U::Service: Unpin, + ::Future: Unpin, + P: Unpin, { fut: S::Future, fut_ex: Option, @@ -182,49 +223,63 @@ where impl Future for H1ServiceResponse where T: IoStream, - S: NewService, + S: ServiceFactory, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin, B: MessageBody, - X: NewService, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: NewService), Response = ()>, + X::Future: Unpin, + X::Service: Unpin, + ::Future: Unpin, + U: ServiceFactory), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, + U::Future: Unpin, + U::Service: Unpin, + ::Future: Unpin, + P: Unpin, { - type Item = H1ServiceHandler; - type Error = (); + type Output = + Result, ()>; - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut_ex { - let expect = try_ready!(fut - .poll() - .map_err(|e| log::error!("Init http service error: {:?}", e))); - self.expect = Some(expect); - self.fut_ex.take(); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + if let Some(ref mut fut) = this.fut_ex { + let expect = ready!(Pin::new(fut) + .poll(cx) + .map_err(|e| log::error!("Init http service error: {:?}", e)))?; + this.expect = Some(expect); + this.fut_ex.take(); } - if let Some(ref mut fut) = self.fut_upg { - let upgrade = try_ready!(fut - .poll() - .map_err(|e| log::error!("Init http service error: {:?}", e))); - self.upgrade = Some(upgrade); - self.fut_ex.take(); + if let Some(ref mut fut) = this.fut_upg { + let upgrade = ready!(Pin::new(fut) + .poll(cx) + .map_err(|e| log::error!("Init http service error: {:?}", e)))?; + this.upgrade = Some(upgrade); + this.fut_ex.take(); } - let service = try_ready!(self - .fut - .poll() + let result = ready!(Pin::new(&mut this.fut) + .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e))); - Ok(Async::Ready(H1ServiceHandler::new( - self.cfg.take().unwrap(), - service, - self.expect.take().unwrap(), - self.upgrade.take(), - self.on_connect.clone(), - ))) + + Poll::Ready(result.map(|service| { + H1ServiceHandler::new( + this.cfg.take().unwrap(), + service, + this.expect.take().unwrap(), + this.upgrade.take(), + this.on_connect.clone(), + ) + })) } } @@ -240,14 +295,18 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service, + S: Service + Unpin, S::Error: Into, S::Response: Into>, + S::Future: Unpin, B: MessageBody, - X: Service, + X: Service + Unpin, + X::Future: Unpin, X::Error: Into, - U: Service), Response = ()>, + U: Service), Response = ()> + Unpin, + U::Future: Unpin, U::Error: fmt::Display, + P: Unpin, { fn new( cfg: ServiceConfig, @@ -270,24 +329,28 @@ where impl Service for H1ServiceHandler where T: IoStream, - S: Service, + S: Service + Unpin, S::Error: Into, S::Response: Into>, + S::Future: Unpin, B: MessageBody, - X: Service, + X: Service + Unpin, X::Error: Into, - U: Service), Response = ()>, + X::Future: Unpin, + U: Service), Response = ()> + Unpin, U::Error: fmt::Display, + U::Future: Unpin, + P: Unpin, { type Request = Io; type Response = (); type Error = DispatchError; type Future = Dispatcher; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { let ready = self .expect - .poll_ready() + .poll_ready(cx) .map_err(|e| { let e = e.into(); log::error!("Http service readiness error: {:?}", e); @@ -297,7 +360,7 @@ where let ready = self .srv - .poll_ready() + .poll_ready(cx) .map_err(|e| { let e = e.into(); log::error!("Http service readiness error: {:?}", e); @@ -307,9 +370,9 @@ where && ready; if ready { - Ok(Async::Ready(())) + Poll::Ready(Ok(())) } else { - Ok(Async::NotReady) + Poll::Pending } } @@ -333,7 +396,7 @@ where } } -/// `NewService` implementation for `OneRequestService` service +/// `ServiceFactory` implementation for `OneRequestService` service #[derive(Default)] pub struct OneRequest { config: ServiceConfig, @@ -353,7 +416,7 @@ where } } -impl NewService for OneRequest +impl ServiceFactory for OneRequest where T: IoStream, { @@ -363,7 +426,7 @@ where type Error = ParseError; type InitError = (); type Service = OneRequestService; - type Future = FutureResult; + type Future = Ready>; fn new_service(&self, _: &SrvConfig) -> Self::Future { ok(OneRequestService { @@ -389,8 +452,8 @@ where type Error = ParseError; type Future = OneRequestServiceResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, req: Self::Request) -> Self::Future { @@ -415,19 +478,19 @@ impl Future for OneRequestServiceResponse where T: IoStream, { - type Item = (Request, Framed); - type Error = ParseError; + type Output = Result<(Request, Framed), ParseError>; - fn poll(&mut self) -> Poll { - match self.framed.as_mut().unwrap().poll()? { - Async::Ready(Some(req)) => match req { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + match self.framed.as_mut().unwrap().next_item(cx) { + Poll::Ready(Some(Ok(req))) => match req { Message::Item(req) => { - Ok(Async::Ready((req, self.framed.take().unwrap()))) + Poll::Ready(Ok((req, self.framed.take().unwrap()))) } Message::Chunk(_) => unreachable!("Something is wrong"), }, - Async::Ready(None) => Err(ParseError::Incomplete), - Async::NotReady => Ok(Async::NotReady), + Poll::Ready(Some(Err(err))) => Poll::Ready(Err(err)), + Poll::Ready(None) => Poll::Ready(Err(ParseError::Incomplete)), + Poll::Pending => Poll::Pending, } } } diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 0278f23e..43ab53d0 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,10 +1,12 @@ +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use actix_codec::Framed; use actix_server_config::ServerConfig; -use actix_service::{NewService, Service}; -use futures::future::FutureResult; -use futures::{Async, Poll}; +use actix_service::{Service, ServiceFactory}; +use futures::future::Ready; use crate::error::Error; use crate::h1::Codec; @@ -12,14 +14,14 @@ use crate::request::Request; pub struct UpgradeHandler(PhantomData); -impl NewService for UpgradeHandler { +impl ServiceFactory for UpgradeHandler { type Config = ServerConfig; type Request = (Request, Framed); type Response = (); type Error = Error; type Service = UpgradeHandler; type InitError = Error; - type Future = FutureResult; + type Future = Ready>; fn new_service(&self, _: &ServerConfig) -> Self::Future { unimplemented!() @@ -30,10 +32,10 @@ impl Service for UpgradeHandler { type Request = (Request, Framed); type Response = (); type Error = Error; - type Future = FutureResult; + type Future = Ready>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, _: Self::Request) -> Self::Future { diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index fdc4cf0b..bc6914d3 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -1,5 +1,9 @@ +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use futures::{Async, Future, Poll, Sink}; +use futures::Sink; use crate::body::{BodySize, MessageBody, ResponseBody}; use crate::error::Error; @@ -30,63 +34,64 @@ where impl Future for SendResponse where - T: AsyncRead + AsyncWrite, + T: AsyncRead + AsyncWrite + Unpin, B: MessageBody, { - type Item = Framed; - type Error = Error; + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); - fn poll(&mut self) -> Poll { loop { - let mut body_ready = self.body.is_some(); - let framed = self.framed.as_mut().unwrap(); + let mut body_ready = this.body.is_some(); + let framed = this.framed.as_mut().unwrap(); // send body - if self.res.is_none() && self.body.is_some() { - while body_ready && self.body.is_some() && !framed.is_write_buf_full() { - match self.body.as_mut().unwrap().poll_next()? { - Async::Ready(item) => { + if this.res.is_none() && this.body.is_some() { + while body_ready && this.body.is_some() && !framed.is_write_buf_full() { + match this.body.as_mut().unwrap().poll_next(cx)? { + Poll::Ready(item) => { // body is done if item.is_none() { - let _ = self.body.take(); + let _ = this.body.take(); } framed.force_send(Message::Chunk(item))?; } - Async::NotReady => body_ready = false, + Poll::Pending => body_ready = false, } } } // flush write buffer if !framed.is_write_buf_empty() { - match framed.poll_complete()? { - Async::Ready(_) => { + match framed.flush(cx)? { + Poll::Ready(_) => { if body_ready { continue; } else { - return Ok(Async::NotReady); + return Poll::Pending; } } - Async::NotReady => return Ok(Async::NotReady), + Poll::Pending => return Poll::Pending, } } // send response - if let Some(res) = self.res.take() { + if let Some(res) = this.res.take() { framed.force_send(res)?; continue; } - if self.body.is_some() { + if this.body.is_some() { if body_ready { continue; } else { - return Ok(Async::NotReady); + return Poll::Pending; } } else { break; } } - Ok(Async::Ready(self.framed.take().unwrap())) + Poll::Ready(Ok(this.framed.take().unwrap())) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 888f9065..2a44c83f 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,5 +1,8 @@ use std::collections::VecDeque; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Instant; use std::{fmt, mem, net}; @@ -8,7 +11,7 @@ use actix_server_config::IoStream; use actix_service::Service; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; -use futures::{try_ready, Async, Future, Poll, Sink, Stream}; +use futures::{ready, Sink, Stream}; use h2::server::{Connection, SendResponse}; use h2::{RecvStream, SendStream}; use http::header::{ @@ -43,13 +46,24 @@ pub struct Dispatcher, B: MessageBody _t: PhantomData, } +impl Unpin for Dispatcher +where + T: IoStream, + S: Service, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, + B: MessageBody + 'static, +{ +} + impl Dispatcher where T: IoStream, S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody + 'static, { pub(crate) fn new( @@ -93,61 +107,75 @@ impl Future for Dispatcher where T: IoStream, S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody + 'static, { - type Item = (); - type Error = DispatchError; + type Output = Result<(), DispatchError>; #[inline] - fn poll(&mut self) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + loop { - match self.connection.poll()? { - Async::Ready(None) => return Ok(Async::Ready(())), - Async::Ready(Some((req, res))) => { + match Pin::new(&mut this.connection).poll_accept(cx) { + Poll::Ready(None) => return Poll::Ready(Ok(())), + Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), + Poll::Ready(Some(Ok((req, res)))) => { // update keep-alive expire - if self.ka_timer.is_some() { - if let Some(expire) = self.config.keep_alive_expire() { - self.ka_expire = expire; + if this.ka_timer.is_some() { + if let Some(expire) = this.config.keep_alive_expire() { + this.ka_expire = expire; } } let (parts, body) = req.into_parts(); - let mut req = Request::with_payload(body.into()); + // let b: () = body; + let mut req = Request::with_payload(Payload::< + crate::payload::PayloadStream, + >::H2( + crate::h2::Payload::new(body) + )); let head = &mut req.head_mut(); head.uri = parts.uri; head.method = parts.method; head.version = parts.version; head.headers = parts.headers.into(); - head.peer_addr = self.peer_addr; + head.peer_addr = this.peer_addr; // set on_connect data - if let Some(ref on_connect) = self.on_connect { + if let Some(ref on_connect) = this.on_connect { on_connect.set(&mut req.extensions_mut()); } - tokio_current_thread::spawn(ServiceResponse:: { - state: ServiceResponseState::ServiceCall( - self.service.call(req), - Some(res), - ), - config: self.config.clone(), - buffer: None, - }) + // tokio_executor::current_thread::spawn(ServiceResponse::< + // S::Future, + // S::Response, + // S::Error, + // B, + // > { + // state: ServiceResponseState::ServiceCall( + // this.service.call(req), + // Some(res), + // ), + // config: this.config.clone(), + // buffer: None, + // _t: PhantomData, + // }); } - Async::NotReady => return Ok(Async::NotReady), + Poll::Pending => return Poll::Pending, } } } } -struct ServiceResponse { +struct ServiceResponse { state: ServiceResponseState, config: ServiceConfig, buffer: Option, + _t: PhantomData<(I, E)>, } enum ServiceResponseState { @@ -155,11 +183,11 @@ enum ServiceResponseState { SendPayload(SendStream, ResponseBody), } -impl ServiceResponse +impl ServiceResponse where - F: Future, - F::Error: Into, - F::Item: Into>, + F: Future> + Unpin, + E: Into + Unpin + 'static, + I: Into> + Unpin + 'static, B: MessageBody + 'static, { fn prepare_response( @@ -223,109 +251,116 @@ where } } -impl Future for ServiceResponse +impl Future for ServiceResponse where - F: Future, - F::Error: Into, - F::Item: Into>, + F: Future> + Unpin, + E: Into + Unpin + 'static, + I: Into> + Unpin + 'static, B: MessageBody + 'static, { - type Item = (); - type Error = (); + type Output = (); - fn poll(&mut self) -> Poll { - match self.state { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + match this.state { ServiceResponseState::ServiceCall(ref mut call, ref mut send) => { - match call.poll() { - Ok(Async::Ready(res)) => { + match Pin::new(call).poll(cx) { + Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); let mut send = send.take().unwrap(); let mut size = body.size(); - let h2_res = self.prepare_response(res.head(), &mut size); + let h2_res = this.prepare_response(res.head(), &mut size); - let stream = - send.send_response(h2_res, size.is_eof()).map_err(|e| { + let stream = match send.send_response(h2_res, size.is_eof()) { + Err(e) => { trace!("Error sending h2 response: {:?}", e); - })?; + return Poll::Ready(()); + } + Ok(stream) => stream, + }; if size.is_eof() { - Ok(Async::Ready(())) + Poll::Ready(()) } else { - self.state = ServiceResponseState::SendPayload(stream, body); - self.poll() + this.state = ServiceResponseState::SendPayload(stream, body); + Pin::new(this).poll(cx) } } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); let mut send = send.take().unwrap(); let mut size = body.size(); - let h2_res = self.prepare_response(res.head(), &mut size); + let h2_res = this.prepare_response(res.head(), &mut size); - let stream = - send.send_response(h2_res, size.is_eof()).map_err(|e| { + let stream = match send.send_response(h2_res, size.is_eof()) { + Err(e) => { trace!("Error sending h2 response: {:?}", e); - })?; + return Poll::Ready(()); + } + Ok(stream) => stream, + }; if size.is_eof() { - Ok(Async::Ready(())) + Poll::Ready(()) } else { - self.state = ServiceResponseState::SendPayload( + this.state = ServiceResponseState::SendPayload( stream, body.into_body(), ); - self.poll() + Pin::new(this).poll(cx) } } } } ServiceResponseState::SendPayload(ref mut stream, ref mut body) => loop { loop { - if let Some(ref mut buffer) = self.buffer { - match stream.poll_capacity().map_err(|e| warn!("{:?}", e))? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(None) => return Ok(Async::Ready(())), - Async::Ready(Some(cap)) => { + if let Some(ref mut buffer) = this.buffer { + match stream.poll_capacity(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => return Poll::Ready(()), + Poll::Ready(Some(Ok(cap))) => { let len = buffer.len(); let bytes = buffer.split_to(std::cmp::min(cap, len)); if let Err(e) = stream.send_data(bytes, false) { warn!("{:?}", e); - return Err(()); + return Poll::Ready(()); } else if !buffer.is_empty() { let cap = std::cmp::min(buffer.len(), CHUNK_SIZE); stream.reserve_capacity(cap); } else { - self.buffer.take(); + this.buffer.take(); } } + Poll::Ready(Some(Err(e))) => { + warn!("{:?}", e); + return Poll::Ready(()); + } } } else { - match body.poll_next() { - Ok(Async::NotReady) => { - return Ok(Async::NotReady); - } - Ok(Async::Ready(None)) => { + match body.poll_next(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => { if let Err(e) = stream.send_data(Bytes::new(), true) { warn!("{:?}", e); - return Err(()); - } else { - return Ok(Async::Ready(())); } + return Poll::Ready(()); } - Ok(Async::Ready(Some(chunk))) => { + Poll::Ready(Some(Ok(chunk))) => { stream.reserve_capacity(std::cmp::min( chunk.len(), CHUNK_SIZE, )); - self.buffer = Some(chunk); + this.buffer = Some(chunk); } - Err(e) => { + Poll::Ready(Some(Err(e))) => { error!("Response payload stream error: {:?}", e); - return Err(()); + return Poll::Ready(()); } } } diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index c5972123..9c902f18 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -1,9 +1,11 @@ #![allow(dead_code, unused_imports)] - use std::fmt; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; use bytes::Bytes; -use futures::{Async, Poll, Stream}; +use futures::Stream; use h2::RecvStream; mod dispatcher; @@ -25,22 +27,23 @@ impl Payload { } impl Stream for Payload { - type Item = Bytes; - type Error = PayloadError; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { - match self.pl.poll() { - Ok(Async::Ready(Some(chunk))) => { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let this = self.get_mut(); + + match Pin::new(&mut this.pl).poll_data(cx) { + Poll::Ready(Some(Ok(chunk))) => { let len = chunk.len(); - if let Err(err) = self.pl.release_capacity().release_capacity(len) { - Err(err.into()) + if let Err(err) = this.pl.release_capacity().release_capacity(len) { + Poll::Ready(Some(Err(err.into()))) } else { - Ok(Async::Ready(Some(chunk))) + Poll::Ready(Some(Ok(chunk))) } } - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => Err(err.into()), + Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))), + Poll::Pending => Poll::Pending, + Poll::Ready(None) => Poll::Ready(None), } } } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index e894cf66..559c9930 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,13 +1,16 @@ use std::fmt::Debug; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{io, net, rc}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; -use actix_service::{IntoNewService, NewService, Service}; +use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use bytes::Bytes; -use futures::future::{ok, FutureResult}; -use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; +use futures::future::{ok, Ready}; +use futures::{ready, Stream}; use h2::server::{self, Connection, Handshake}; use h2::RecvStream; use log::error; @@ -23,7 +26,7 @@ use crate::response::Response; use super::dispatcher::Dispatcher; -/// `NewService` implementation for HTTP2 transport +/// `ServiceFactory` implementation for HTTP2 transport pub struct H2Service { srv: S, cfg: ServiceConfig, @@ -33,30 +36,35 @@ pub struct H2Service { impl H2Service where - S: NewService, - S::Error: Into, - S::Response: Into>, - ::Future: 'static, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + ::Future: Unpin + 'static, B: MessageBody + 'static, + P: Unpin, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); H2Service { cfg, on_connect: None, - srv: service.into_new_service(), + srv: service.into_factory(), _t: PhantomData, } } /// Create new `HttpService` instance with config. - pub fn with_config>(cfg: ServiceConfig, service: F) -> Self { + pub fn with_config>( + cfg: ServiceConfig, + service: F, + ) -> Self { H2Service { cfg, on_connect: None, - srv: service.into_new_service(), + srv: service.into_factory(), _t: PhantomData, } } @@ -71,14 +79,16 @@ where } } -impl NewService for H2Service +impl ServiceFactory for H2Service where T: IoStream, - S: NewService, - S::Error: Into, - S::Response: Into>, - ::Future: 'static, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + ::Future: Unpin + 'static, B: MessageBody + 'static, + P: Unpin, { type Config = SrvConfig; type Request = Io; @@ -90,7 +100,7 @@ where fn new_service(&self, cfg: &SrvConfig) -> Self::Future { H2ServiceResponse { - fut: self.srv.new_service(cfg).into_future(), + fut: self.srv.new_service(cfg), cfg: Some(self.cfg.clone()), on_connect: self.on_connect.clone(), _t: PhantomData, @@ -99,8 +109,8 @@ where } #[doc(hidden)] -pub struct H2ServiceResponse { - fut: ::Future, +pub struct H2ServiceResponse { + fut: S::Future, cfg: Option, on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, @@ -109,22 +119,26 @@ pub struct H2ServiceResponse { impl Future for H2ServiceResponse where T: IoStream, - S: NewService, - S::Error: Into, - S::Response: Into>, - ::Future: 'static, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + ::Future: Unpin + 'static, B: MessageBody + 'static, + P: Unpin, { - type Item = H2ServiceHandler; - type Error = S::InitError; + type Output = Result, S::InitError>; - fn poll(&mut self) -> Poll { - let service = try_ready!(self.fut.poll()); - Ok(Async::Ready(H2ServiceHandler::new( - self.cfg.take().unwrap(), - self.on_connect.clone(), - service, - ))) + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + Poll::Ready(ready!(Pin::new(&mut this.fut).poll(cx)).map(|service| { + H2ServiceHandler::new( + this.cfg.take().unwrap(), + this.on_connect.clone(), + service, + ) + })) } } @@ -139,10 +153,11 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody + 'static, + P: Unpin, { fn new( cfg: ServiceConfig, @@ -162,18 +177,19 @@ impl Service for H2ServiceHandler where T: IoStream, S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody + 'static, + P: Unpin, { type Request = Io; type Response = (); type Error = DispatchError; type Future = H2ServiceHandlerResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.srv.poll_ready().map_err(|e| { + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.srv.poll_ready(cx).map_err(|e| { let e = e.into(); error!("Service readiness error: {:?}", e); DispatchError::Service(e) @@ -219,9 +235,9 @@ pub struct H2ServiceHandlerResponse where T: IoStream, S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody + 'static, { state: State, @@ -231,25 +247,24 @@ impl Future for H2ServiceHandlerResponse where T: IoStream, S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody, { - type Item = (); - type Error = DispatchError; + type Output = Result<(), DispatchError>; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { match self.state { - State::Incoming(ref mut disp) => disp.poll(), + State::Incoming(ref mut disp) => Pin::new(disp).poll(cx), State::Handshake( ref mut srv, ref mut config, ref peer_addr, ref mut on_connect, ref mut handshake, - ) => match handshake.poll() { - Ok(Async::Ready(conn)) => { + ) => match Pin::new(handshake).poll(cx) { + Poll::Ready(Ok(conn)) => { self.state = State::Incoming(Dispatcher::new( srv.take().unwrap(), conn, @@ -258,13 +273,13 @@ where None, *peer_addr, )); - self.poll() + self.poll(cx) } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => { + Poll::Ready(Err(err)) => { trace!("H2 handshake error: {}", err); - Err(err.into()) + Poll::Ready(Err(err.into())) } + Poll::Pending => Poll::Pending, }, } } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index b57fdddc..cf528aee 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -4,7 +4,8 @@ clippy::too_many_arguments, clippy::new_without_default, clippy::borrow_interior_mutable_const, - clippy::write_with_newline + clippy::write_with_newline, + unused_imports )] #[macro_use] @@ -12,7 +13,7 @@ extern crate log; pub mod body; mod builder; -pub mod client; +// pub mod client; mod cloneable; mod config; pub mod encoding; @@ -31,8 +32,8 @@ pub mod cookie; pub mod error; pub mod h1; pub mod h2; -pub mod test; -pub mod ws; +// pub mod test; +// pub mod ws; pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 0ce20970..f2cc6414 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -1,11 +1,15 @@ +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + use bytes::Bytes; -use futures::{Async, Poll, Stream}; +use futures::Stream; use h2::RecvStream; use crate::error::PayloadError; /// Type represent boxed payload -pub type PayloadStream = Box>; +pub type PayloadStream = Pin>>>; /// Type represent streaming payload pub enum Payload { @@ -48,18 +52,17 @@ impl Payload { impl Stream for Payload where - S: Stream, + S: Stream> + Unpin, { - type Item = Bytes; - type Error = PayloadError; + type Item = Result; #[inline] - fn poll(&mut self) -> Poll, Self::Error> { - match self { - Payload::None => Ok(Async::Ready(None)), - Payload::H1(ref mut pl) => pl.poll(), - Payload::H2(ref mut pl) => pl.poll(), - Payload::Stream(ref mut pl) => pl.poll(), + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + match self.get_mut() { + Payload::None => Poll::Ready(None), + Payload::H1(ref mut pl) => pl.readany(cx), + Payload::H2(ref mut pl) => Pin::new(pl).poll_next(cx), + Payload::Stream(ref mut pl) => Pin::new(pl).poll_next(cx), } } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index a1541b53..5b3d17cb 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -4,7 +4,7 @@ use std::io::Write; use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; -use futures::future::{ok, FutureResult, IntoFuture}; +use futures::future::{ok, Ready}; use futures::Stream; use serde::Serialize; use serde_json; @@ -280,15 +280,15 @@ impl fmt::Debug for Response { } } -impl IntoFuture for Response { - type Item = Response; - type Error = Error; - type Future = FutureResult; +// impl IntoFuture for Response { +// type Item = Response; +// type Error = Error; +// type Future = FutureResult; - fn into_future(self) -> Self::Future { - ok(self) - } -} +// fn into_future(self) -> Self::Future { +// ok(self) +// } +// } pub struct CookieIter<'a> { iter: header::GetAll<'a>, @@ -635,8 +635,8 @@ impl ResponseBuilder { /// `ResponseBuilder` can not be used after this call. pub fn streaming(&mut self, stream: S) -> Response where - S: Stream + 'static, - E: Into + 'static, + S: Stream> + Unpin + 'static, + E: Into + Unpin + 'static, { self.body(Body::from_message(BodyStream::new(stream))) } @@ -757,15 +757,15 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } } -impl IntoFuture for ResponseBuilder { - type Item = Response; - type Error = Error; - type Future = FutureResult; +// impl IntoFuture for ResponseBuilder { +// type Item = Response; +// type Error = Error; +// type Future = FutureResult; - fn into_future(mut self) -> Self::Future { - ok(self.finish()) - } -} +// fn into_future(mut self) -> Self::Future { +// ok(self.finish()) +// } +// } impl fmt::Debug for ResponseBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 09b8077b..65a0c7bd 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,13 +1,15 @@ use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{fmt, io, net, rc}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::{ Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig, }; -use actix_service::{IntoNewService, NewService, Service}; +use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use bytes::{Buf, BufMut, Bytes, BytesMut}; -use futures::{try_ready, Async, Future, IntoFuture, Poll}; +use futures::{ready, Future}; use h2::server::{self, Handshake}; use crate::body::MessageBody; @@ -20,7 +22,7 @@ use crate::request::Request; use crate::response::Response; use crate::{h1, h2::Dispatcher}; -/// `NewService` HTTP1.1/HTTP2 transport implementation +/// `ServiceFactory` HTTP1.1/HTTP2 transport implementation pub struct HttpService> { srv: S, cfg: ServiceConfig, @@ -32,11 +34,13 @@ pub struct HttpService HttpService where - S: NewService, - S::Error: Into, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - S::Response: Into>, - ::Future: 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, B: MessageBody + 'static, { /// Create builder for `HttpService` instance. @@ -47,20 +51,23 @@ where impl HttpService where - S: NewService, - S::Error: Into, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - S::Response: Into>, - ::Future: 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, B: MessageBody + 'static, + P: Unpin, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0); HttpService { cfg, - srv: service.into_new_service(), + srv: service.into_factory(), expect: h1::ExpectHandler, upgrade: None, on_connect: None, @@ -69,13 +76,13 @@ where } /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { HttpService { cfg, - srv: service.into_new_service(), + srv: service.into_factory(), expect: h1::ExpectHandler, upgrade: None, on_connect: None, @@ -86,11 +93,15 @@ where impl HttpService where - S: NewService, - S::Error: Into, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - S::Response: Into>, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, B: MessageBody, + P: Unpin, { /// Provide service for `EXPECT: 100-Continue` support. /// @@ -99,9 +110,12 @@ where /// request will be forwarded to main service. pub fn expect(self, expect: X1) -> HttpService where - X1: NewService, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, + X1::Future: Unpin, + X1::Service: Unpin, + ::Future: Unpin + 'static, { HttpService { expect, @@ -119,13 +133,16 @@ where /// and this service get called with original request and framed object. pub fn upgrade(self, upgrade: Option) -> HttpService where - U1: NewService< + U1: ServiceFactory< Config = SrvConfig, Request = (Request, Framed), Response = (), >, U1::Error: fmt::Display, U1::InitError: fmt::Debug, + U1::Future: Unpin, + U1::Service: Unpin, + ::Future: Unpin + 'static, { HttpService { upgrade, @@ -147,25 +164,35 @@ where } } -impl NewService for HttpService +impl ServiceFactory for HttpService where - T: IoStream, - S: NewService, - S::Error: Into, + T: IoStream + Unpin, + S: ServiceFactory, + S::Service: Unpin, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - S::Response: Into>, - ::Future: 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, B: MessageBody + 'static, - X: NewService, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: NewService< + X::Future: Unpin, + X::Service: Unpin, + ::Future: Unpin + 'static, + U: ServiceFactory< Config = SrvConfig, Request = (Request, Framed), Response = (), >, U::Error: fmt::Display, U::InitError: fmt::Debug, + U::Future: Unpin, + U::Service: Unpin, + ::Future: Unpin + 'static, + P: Unpin, { type Config = SrvConfig; type Request = ServerIo; @@ -177,7 +204,7 @@ where fn new_service(&self, cfg: &SrvConfig) -> Self::Future { HttpServiceResponse { - fut: self.srv.new_service(cfg).into_future(), + fut: self.srv.new_service(cfg), fut_ex: Some(self.expect.new_service(cfg)), fut_upg: self.upgrade.as_ref().map(|f| f.new_service(cfg)), expect: None, @@ -190,7 +217,14 @@ where } #[doc(hidden)] -pub struct HttpServiceResponse { +pub struct HttpServiceResponse< + T, + P, + S: ServiceFactory, + B, + X: ServiceFactory, + U: ServiceFactory, +> { fut: S::Future, fut_ex: Option, fut_upg: Option, @@ -204,50 +238,62 @@ pub struct HttpServiceResponse Future for HttpServiceResponse where T: IoStream, - S: NewService, - S::Error: Into, + S: ServiceFactory, + S::Error: Into + Unpin + 'static, S::InitError: fmt::Debug, - S::Response: Into>, - ::Future: 'static, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, + S::Service: Unpin, + ::Future: Unpin + 'static, B: MessageBody + 'static, - X: NewService, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: NewService), Response = ()>, + X::Future: Unpin, + X::Service: Unpin, + ::Future: Unpin + 'static, + U: ServiceFactory), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, + U::Future: Unpin, + U::Service: Unpin, + ::Future: Unpin + 'static, + P: Unpin, { - type Item = HttpServiceHandler; - type Error = (); + type Output = + Result, ()>; - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut_ex { - let expect = try_ready!(fut - .poll() - .map_err(|e| log::error!("Init http service error: {:?}", e))); - self.expect = Some(expect); - self.fut_ex.take(); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + if let Some(ref mut fut) = this.fut_ex { + let expect = ready!(Pin::new(fut) + .poll(cx) + .map_err(|e| log::error!("Init http service error: {:?}", e)))?; + this.expect = Some(expect); + this.fut_ex.take(); } - if let Some(ref mut fut) = self.fut_upg { - let upgrade = try_ready!(fut - .poll() - .map_err(|e| log::error!("Init http service error: {:?}", e))); - self.upgrade = Some(upgrade); - self.fut_ex.take(); + if let Some(ref mut fut) = this.fut_upg { + let upgrade = ready!(Pin::new(fut) + .poll(cx) + .map_err(|e| log::error!("Init http service error: {:?}", e)))?; + this.upgrade = Some(upgrade); + this.fut_ex.take(); } - let service = try_ready!(self - .fut - .poll() + let result = ready!(Pin::new(&mut this.fut) + .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e))); - Ok(Async::Ready(HttpServiceHandler::new( - self.cfg.take().unwrap(), - service, - self.expect.take().unwrap(), - self.upgrade.take(), - self.on_connect.clone(), - ))) + Poll::Ready(result.map(|service| { + HttpServiceHandler::new( + this.cfg.take().unwrap(), + service, + this.expect.take().unwrap(), + this.upgrade.take(), + this.on_connect.clone(), + ) + })) } } @@ -263,15 +309,19 @@ pub struct HttpServiceHandler { impl HttpServiceHandler where - S: Service, - S::Error: Into, + S: Service + Unpin, + S::Error: Into + Unpin + 'static, S::Future: 'static, - S::Response: Into>, + S::Response: Into> + Unpin + 'static, + S::Future: Unpin, B: MessageBody + 'static, - X: Service, + X: Service + Unpin, + X::Future: Unpin, X::Error: Into, - U: Service), Response = ()>, + U: Service), Response = ()> + Unpin, + U::Future: Unpin, U::Error: fmt::Display, + P: Unpin, { fn new( cfg: ServiceConfig, @@ -293,26 +343,29 @@ where impl Service for HttpServiceHandler where - T: IoStream, - S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + T: IoStream + Unpin, + S: Service + Unpin, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody + 'static, - X: Service, + X: Service + Unpin, X::Error: Into, - U: Service), Response = ()>, + X::Future: Unpin, + U: Service), Response = ()> + Unpin, U::Error: fmt::Display, + U::Future: Unpin, + P: Unpin, { type Request = ServerIo; type Response = (); type Error = DispatchError; type Future = HttpServiceHandlerResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { let ready = self .expect - .poll_ready() + .poll_ready(cx) .map_err(|e| { let e = e.into(); log::error!("Http service readiness error: {:?}", e); @@ -322,7 +375,7 @@ where let ready = self .srv - .poll_ready() + .poll_ready(cx) .map_err(|e| { let e = e.into(); log::error!("Http service readiness error: {:?}", e); @@ -332,9 +385,9 @@ where && ready; if ready { - Ok(Async::Ready(())) + Poll::Ready(Ok(())) } else { - Ok(Async::NotReady) + Poll::Pending } } @@ -391,15 +444,17 @@ where enum State where - S: Service, - S::Future: 'static, + S: Service + Unpin, + S::Future: Unpin + 'static, S::Error: Into, - T: IoStream, + T: IoStream + Unpin, B: MessageBody, - X: Service, + X: Service + Unpin, X::Error: Into, - U: Service), Response = ()>, + X::Future: Unpin, + U: Service), Response = ()> + Unpin, U::Error: fmt::Display, + U::Future: Unpin, { H1(h1::Dispatcher), H2(Dispatcher, S, B>), @@ -427,16 +482,18 @@ where pub struct HttpServiceHandlerResponse where - T: IoStream, - S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + T: IoStream + Unpin, + S: Service + Unpin, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody + 'static, - X: Service, + X: Service + Unpin, X::Error: Into, - U: Service), Response = ()>, + X::Future: Unpin, + U: Service), Response = ()> + Unpin, U::Error: fmt::Display, + U::Future: Unpin, { state: State, } @@ -445,32 +502,33 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; impl Future for HttpServiceHandlerResponse where - T: IoStream, - S: Service, - S::Error: Into, - S::Future: 'static, - S::Response: Into>, + T: IoStream + Unpin, + S: Service + Unpin, + S::Error: Into + Unpin + 'static, + S::Future: Unpin + 'static, + S::Response: Into> + Unpin + 'static, B: MessageBody, - X: Service, + X: Service + Unpin, + X::Future: Unpin, X::Error: Into, - U: Service), Response = ()>, + U: Service), Response = ()> + Unpin, + U::Future: Unpin, U::Error: fmt::Display, { - type Item = (); - type Error = DispatchError; + type Output = Result<(), DispatchError>; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { match self.state { - State::H1(ref mut disp) => disp.poll(), - State::H2(ref mut disp) => disp.poll(), + State::H1(ref mut disp) => Pin::new(disp).poll(cx), + State::H2(ref mut disp) => Pin::new(disp).poll(cx), State::Unknown(ref mut data) => { if let Some(ref mut item) = data { loop { // Safety - we only write to the returned slice. let b = unsafe { item.1.bytes_mut() }; - let n = try_ready!(item.0.poll_read(b)); + let n = ready!(Pin::new(&mut item.0).poll_read(cx, b))?; if n == 0 { - return Ok(Async::Ready(())); + return Poll::Ready(Ok(())); } // Safety - we know that 'n' bytes have // been initialized via the contract of @@ -511,17 +569,17 @@ where on_connect, )) } - self.poll() + self.poll(cx) } State::Handshake(ref mut data) => { let conn = if let Some(ref mut item) = data { - match item.0.poll() { - Ok(Async::Ready(conn)) => conn, - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => { + match Pin::new(&mut item.0).poll(cx) { + Poll::Ready(Ok(conn)) => conn, + Poll::Ready(Err(err)) => { trace!("H2 handshake error: {}", err); - return Err(err.into()); + return Poll::Ready(Err(err.into())); } + Poll::Pending => return Poll::Pending, } } else { panic!() @@ -530,7 +588,7 @@ where self.state = State::H2(Dispatcher::new( srv, conn, on_connect, cfg, None, peer_addr, )); - self.poll() + self.poll(cx) } } } @@ -542,6 +600,8 @@ struct Io { inner: T, } +impl Unpin for Io {} + impl io::Read for Io { fn read(&mut self, buf: &mut [u8]) -> io::Result { if let Some(mut bytes) = self.unread.take() { @@ -567,22 +627,62 @@ impl io::Write for Io { } } -impl AsyncRead for Io { +impl AsyncRead for Io { + // unsafe fn initializer(&self) -> io::Initializer { + // self.get_mut().inner.initializer() + // } + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { self.inner.prepare_uninitialized_buffer(buf) } + + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Pin::new(&mut self.get_mut().inner).poll_read(cx, buf) + } + + // fn poll_read_vectored( + // self: Pin<&mut Self>, + // cx: &mut Context<'_>, + // bufs: &mut [io::IoSliceMut<'_>], + // ) -> Poll> { + // self.get_mut().inner.poll_read_vectored(cx, bufs) + // } } -impl AsyncWrite for Io { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.inner.shutdown() +impl tokio_io::AsyncWrite for Io { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.get_mut().inner).poll_write(cx, buf) } - fn write_buf(&mut self, buf: &mut B) -> Poll { - self.inner.write_buf(buf) + + // fn poll_write_vectored( + // self: Pin<&mut Self>, + // cx: &mut Context<'_>, + // bufs: &[io::IoSlice<'_>], + // ) -> Poll> { + // self.get_mut().inner.poll_write_vectored(cx, bufs) + // } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().inner).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.get_mut().inner).poll_shutdown(cx) } } -impl IoStream for Io { +impl actix_server_config::IoStream for Io { #[inline] fn peer_addr(&self) -> Option { self.inner.peer_addr() diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index ed5b81a3..817bf480 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -1,12 +1,13 @@ //! Test Various helpers for Actix applications to use during testing. use std::fmt::Write as FmtWrite; use std::io; +use std::pin::Pin; use std::str::FromStr; +use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_server_config::IoStream; use bytes::{Buf, Bytes, BytesMut}; -use futures::{Async, Poll}; use http::header::{self, HeaderName, HeaderValue}; use http::{HttpTryFrom, Method, Uri, Version}; use percent_encoding::percent_encode; @@ -244,16 +245,16 @@ impl io::Write for TestBuffer { } } -impl AsyncRead for TestBuffer {} +// impl AsyncRead for TestBuffer {} -impl AsyncWrite for TestBuffer { - fn shutdown(&mut self) -> Poll<(), io::Error> { - Ok(Async::Ready(())) - } - fn write_buf(&mut self, _: &mut B) -> Poll { - Ok(Async::NotReady) - } -} +// impl AsyncWrite for TestBuffer { +// fn shutdown(&mut self) -> Poll<(), io::Error> { +// Ok(Async::Ready(())) +// } +// fn write_buf(&mut self, _: &mut B) -> Poll { +// Ok(Async::NotReady) +// } +// } impl IoStream for TestBuffer { fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { From 9e95efcc1604e88825c9fdfca19378b911b28f40 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Nov 2019 18:42:27 +0600 Subject: [PATCH 1603/1635] migrate client to std::future --- actix-http/src/client/connection.rs | 177 ++++++------ actix-http/src/client/connector.rs | 177 +++++++----- actix-http/src/client/h1proto.rs | 283 +++++++++---------- actix-http/src/client/h2proto.rs | 254 ++++++++--------- actix-http/src/client/pool.rs | 408 +++++++++++++--------------- actix-http/src/h1/utils.rs | 4 +- actix-http/src/lib.rs | 6 +- actix-http/src/test.rs | 37 ++- actix-http/src/ws/transport.rs | 26 +- 9 files changed, 674 insertions(+), 698 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index d2b94b3e..70ffff6c 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,9 +1,10 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{fmt, io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use bytes::{Buf, Bytes}; -use futures::future::{err, Either, Future, FutureResult}; -use futures::Poll; +use futures::future::{err, Either, Future, FutureExt, LocalBoxFuture, Ready}; use h2::client::SendRequest; use crate::body::MessageBody; @@ -22,7 +23,7 @@ pub(crate) enum ConnectionType { pub trait Connection { type Io: AsyncRead + AsyncWrite; - type Future: Future; + type Future: Future>; fn protocol(&self) -> Protocol; @@ -34,15 +35,16 @@ pub trait Connection { ) -> Self::Future; type TunnelFuture: Future< - Item = (ResponseHead, Framed), - Error = SendRequestError, + Output = Result<(ResponseHead, Framed), SendRequestError>, >; /// Send request, returns Response and Framed fn open_tunnel>(self, head: H) -> Self::TunnelFuture; } -pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { +pub(crate) trait ConnectionLifetime: + AsyncRead + AsyncWrite + Unpin + 'static +{ /// Close connection fn close(&mut self); @@ -91,11 +93,11 @@ impl IoConnection { impl Connection for IoConnection where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, { type Io = T; type Future = - Box>; + LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; fn protocol(&self) -> Protocol { match self.io { @@ -111,38 +113,30 @@ where body: B, ) -> Self::Future { match self.io.take().unwrap() { - ConnectionType::H1(io) => Box::new(h1proto::send_request( - io, - head.into(), - body, - self.created, - self.pool, - )), - ConnectionType::H2(io) => Box::new(h2proto::send_request( - io, - head.into(), - body, - self.created, - self.pool, - )), + ConnectionType::H1(io) => { + h1proto::send_request(io, head.into(), body, self.created, self.pool) + .boxed_local() + } + ConnectionType::H2(io) => { + h2proto::send_request(io, head.into(), body, self.created, self.pool) + .boxed_local() + } } } type TunnelFuture = Either< - Box< - dyn Future< - Item = (ResponseHead, Framed), - Error = SendRequestError, - >, + LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, >, - FutureResult<(ResponseHead, Framed), SendRequestError>, + Ready), SendRequestError>>, >; /// Send request, returns Response and Framed fn open_tunnel>(mut self, head: H) -> Self::TunnelFuture { match self.io.take().unwrap() { ConnectionType::H1(io) => { - Either::A(Box::new(h1proto::open_tunnel(io, head.into()))) + Either::Left(h1proto::open_tunnel(io, head.into()).boxed_local()) } ConnectionType::H2(io) => { if let Some(mut pool) = self.pool.take() { @@ -152,7 +146,7 @@ where None, )); } - Either::B(err(SendRequestError::TunnelNotSupported)) + Either::Right(err(SendRequestError::TunnelNotSupported)) } } } @@ -166,12 +160,12 @@ pub(crate) enum EitherConnection { impl Connection for EitherConnection where - A: AsyncRead + AsyncWrite + 'static, - B: AsyncRead + AsyncWrite + 'static, + A: AsyncRead + AsyncWrite + Unpin + 'static, + B: AsyncRead + AsyncWrite + Unpin + 'static, { type Io = EitherIo; type Future = - Box>; + LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; fn protocol(&self) -> Protocol { match self { @@ -191,24 +185,22 @@ where } } - type TunnelFuture = Box< - dyn Future< - Item = (ResponseHead, Framed), - Error = SendRequestError, - >, + type TunnelFuture = LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, >; /// Send request, returns Response and Framed fn open_tunnel>(self, head: H) -> Self::TunnelFuture { match self { - EitherConnection::A(con) => Box::new( - con.open_tunnel(head) - .map(|(head, framed)| (head, framed.map_io(EitherIo::A))), - ), - EitherConnection::B(con) => Box::new( - con.open_tunnel(head) - .map(|(head, framed)| (head, framed.map_io(EitherIo::B))), - ), + EitherConnection::A(con) => con + .open_tunnel(head) + .map(|res| res.map(|(head, framed)| (head, framed.map_io(EitherIo::A)))) + .boxed_local(), + EitherConnection::B(con) => con + .open_tunnel(head) + .map(|res| res.map(|(head, framed)| (head, framed.map_io(EitherIo::B)))) + .boxed_local(), } } } @@ -218,24 +210,22 @@ pub enum EitherIo { B(B), } -impl io::Read for EitherIo -where - A: io::Read, - B: io::Read, -{ - fn read(&mut self, buf: &mut [u8]) -> io::Result { - match self { - EitherIo::A(ref mut val) => val.read(buf), - EitherIo::B(ref mut val) => val.read(buf), - } - } -} - impl AsyncRead for EitherIo where - A: AsyncRead, - B: AsyncRead, + A: AsyncRead + Unpin, + B: AsyncRead + Unpin, { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + match self.get_mut() { + EitherIo::A(ref mut val) => Pin::new(val).poll_read(cx, buf), + EitherIo::B(ref mut val) => Pin::new(val).poll_read(cx, buf), + } + } + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { match self { EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf), @@ -244,45 +234,50 @@ where } } -impl io::Write for EitherIo -where - A: io::Write, - B: io::Write, -{ - fn write(&mut self, buf: &[u8]) -> io::Result { - match self { - EitherIo::A(ref mut val) => val.write(buf), - EitherIo::B(ref mut val) => val.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match self { - EitherIo::A(ref mut val) => val.flush(), - EitherIo::B(ref mut val) => val.flush(), - } - } -} - impl AsyncWrite for EitherIo where - A: AsyncWrite, - B: AsyncWrite, + A: AsyncWrite + Unpin, + B: AsyncWrite + Unpin, { - fn shutdown(&mut self) -> Poll<(), io::Error> { - match self { - EitherIo::A(ref mut val) => val.shutdown(), - EitherIo::B(ref mut val) => val.shutdown(), + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &[u8], + ) -> Poll> { + match self.get_mut() { + EitherIo::A(ref mut val) => Pin::new(val).poll_write(cx, buf), + EitherIo::B(ref mut val) => Pin::new(val).poll_write(cx, buf), } } - fn write_buf(&mut self, buf: &mut U) -> Poll + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + EitherIo::A(ref mut val) => Pin::new(val).poll_flush(cx), + EitherIo::B(ref mut val) => Pin::new(val).poll_flush(cx), + } + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + match self.get_mut() { + EitherIo::A(ref mut val) => Pin::new(val).poll_shutdown(cx), + EitherIo::B(ref mut val) => Pin::new(val).poll_shutdown(cx), + } + } + + fn poll_write_buf( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut U, + ) -> Poll> where Self: Sized, { - match self { - EitherIo::A(ref mut val) => val.write_buf(buf), - EitherIo::B(ref mut val) => val.write_buf(buf), + match self.get_mut() { + EitherIo::A(ref mut val) => Pin::new(val).poll_write_buf(cx, buf), + EitherIo::B(ref mut val) => Pin::new(val).poll_write_buf(cx, buf), } } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 4ae28ba6..7421cb02 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -11,6 +11,7 @@ use actix_connect::{ }; use actix_service::{apply_fn, Service}; use actix_utils::timeout::{TimeoutError, TimeoutService}; +use futures::future::Ready; use http::Uri; use tokio_net::tcp::TcpStream; @@ -116,12 +117,13 @@ impl Connector { /// Use custom connector. pub fn connector(self, connector: T1) -> Connector where - U1: AsyncRead + AsyncWrite + fmt::Debug, + U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug, T1: Service< Request = TcpConnect, Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, + T1::Future: Unpin, { Connector { connector, @@ -138,13 +140,12 @@ impl Connector { impl Connector where - U: AsyncRead + AsyncWrite + fmt::Debug + 'static, + U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, T: Service< Request = TcpConnect, Response = TcpConnection, Error = actix_connect::ConnectError, - > + Clone - + 'static, + > + 'static, { /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. /// Set to 1 second by default. @@ -220,7 +221,7 @@ where { let connector = TimeoutService::new( self.timeout, - apply_fn(self.connector, |msg: Connect, srv| { + apply_fn(UnpinWrapper(self.connector), |msg: Connect, srv| { srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) }) .map_err(ConnectError::from) @@ -337,10 +338,48 @@ where } } +#[derive(Clone)] +struct UnpinWrapper(T); + +impl Unpin for UnpinWrapper {} + +impl Service for UnpinWrapper { + type Request = T::Request; + type Response = T::Response; + type Error = T::Error; + type Future = UnpinWrapperFut; + + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.0.poll_ready(cx) + } + + fn call(&mut self, req: T::Request) -> Self::Future { + UnpinWrapperFut { + fut: self.0.call(req), + } + } +} + +struct UnpinWrapperFut { + fut: T::Future, +} + +impl Unpin for UnpinWrapperFut {} + +impl Future for UnpinWrapperFut { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + unsafe { Pin::new_unchecked(&mut self.get_mut().fut) }.poll(cx) + } +} + #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] mod connect_impl { - use futures::future::{err, Either, FutureResult}; - use futures::Poll; + use std::task::{Context, Poll}; + + use futures::future::{err, Either, Ready}; + use futures::ready; use super::*; use crate::client::connection::IoConnection; @@ -349,7 +388,7 @@ mod connect_impl { where Io: AsyncRead + AsyncWrite + 'static, T: Service - + Clone + + Unpin + 'static, { pub(crate) tcp_pool: ConnectionPool, @@ -359,7 +398,7 @@ mod connect_impl { where Io: AsyncRead + AsyncWrite + 'static, T: Service - + Clone + + Unpin + 'static, { fn clone(&self) -> Self { @@ -371,29 +410,30 @@ mod connect_impl { impl Service for InnerConnector where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Clone + + Unpin + 'static, + T::Future: Unpin, { type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = Either< as Service>::Future, - FutureResult, ConnectError>, + Ready, ConnectError>>, >; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.tcp_pool.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.tcp_pool.poll_ready(cx) } fn call(&mut self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => { - Either::B(err(ConnectError::SslIsNotSupported)) + Either::Right(err(ConnectError::SslIsNotSupported)) } - _ => Either::A(self.tcp_pool.call(req)), + _ => Either::Left(self.tcp_pool.call(req)), } } } @@ -403,18 +443,20 @@ mod connect_impl { mod connect_impl { use std::marker::PhantomData; - use futures::future::{Either, FutureResult}; - use futures::{Async, Future, Poll}; + use futures::future::Either; + use futures::ready; use super::*; use crate::client::connection::EitherConnection; pub(crate) struct InnerConnector where - Io1: AsyncRead + AsyncWrite + 'static, - Io2: AsyncRead + AsyncWrite + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, T1: Service, T2: Service, + T1::Future: Unpin, + T2::Future: Unpin, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -422,14 +464,16 @@ mod connect_impl { impl Clone for InnerConnector where - Io1: AsyncRead + AsyncWrite + 'static, - Io2: AsyncRead + AsyncWrite + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, T1: Service - + Clone + + Unpin + 'static, T2: Service - + Clone + + Unpin + 'static, + T1::Future: Unpin, + T2::Future: Unpin, { fn clone(&self) -> Self { InnerConnector { @@ -441,52 +485,50 @@ mod connect_impl { impl Service for InnerConnector where - Io1: AsyncRead + AsyncWrite + 'static, - Io2: AsyncRead + AsyncWrite + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, T1: Service - + Clone + + Unpin + 'static, T2: Service - + Clone + + Unpin + 'static, + T1::Future: Unpin, + T2::Future: Unpin, { type Request = Connect; type Response = EitherConnection; type Error = ConnectError; type Future = Either< - FutureResult, - Either< - InnerConnectorResponseA, - InnerConnectorResponseB, - >, + InnerConnectorResponseA, + InnerConnectorResponseB, >; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.tcp_pool.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.tcp_pool.poll_ready(cx) } fn call(&mut self, req: Connect) -> Self::Future { match req.uri.scheme_str() { - Some("https") | Some("wss") => { - Either::B(Either::B(InnerConnectorResponseB { - fut: self.ssl_pool.call(req), - _t: PhantomData, - })) - } - _ => Either::B(Either::A(InnerConnectorResponseA { + Some("https") | Some("wss") => Either::B(InnerConnectorResponseB { + fut: self.ssl_pool.call(req), + _t: PhantomData, + }), + _ => Either::A(InnerConnectorResponseA { fut: self.tcp_pool.call(req), _t: PhantomData, - })), + }), } } } pub(crate) struct InnerConnectorResponseA where - Io1: AsyncRead + AsyncWrite + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Clone + + Unpin + 'static, + T::Future: Unpin, { fut: as Service>::Future, _t: PhantomData, @@ -495,28 +537,29 @@ mod connect_impl { impl Future for InnerConnectorResponseA where T: Service - + Clone + + Unpin + 'static, - Io1: AsyncRead + AsyncWrite + 'static, - Io2: AsyncRead + AsyncWrite + 'static, + T::Future: Unpin, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, { - type Item = EitherConnection; - type Error = ConnectError; + type Output = Result, ConnectError>; - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(res) => Ok(Async::Ready(EitherConnection::A(res))), - } + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + Poll::Ready( + ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) + .map(|res| EitherConnection::A(res)), + ) } } pub(crate) struct InnerConnectorResponseB where - Io2: AsyncRead + AsyncWrite + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Clone + + Unpin + 'static, + T::Future: Unpin, { fut: as Service>::Future, _t: PhantomData, @@ -525,19 +568,19 @@ mod connect_impl { impl Future for InnerConnectorResponseB where T: Service - + Clone + + Unpin + 'static, - Io1: AsyncRead + AsyncWrite + 'static, - Io2: AsyncRead + AsyncWrite + 'static, + T::Future: Unpin, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, { - type Item = EitherConnection; - type Error = ConnectError; + type Output = Result, ConnectError>; - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(res) => Ok(Async::Ready(EitherConnection::B(res))), - } + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + Poll::Ready( + ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) + .map(|res| EitherConnection::B(res)), + ) } } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 14984253..f8902a0e 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -6,8 +6,8 @@ use std::{io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use bytes::{BufMut, Bytes, BytesMut}; -use futures::future::{ok, Either}; -use futures::{Sink, Stream}; +use futures::future::{ok, poll_fn, Either}; +use futures::{Sink, SinkExt, Stream, StreamExt}; use crate::error::PayloadError; use crate::h1; @@ -21,15 +21,15 @@ use super::error::{ConnectError, SendRequestError}; use super::pool::Acquired; use crate::body::{BodySize, MessageBody}; -pub(crate) fn send_request( +pub(crate) async fn send_request( io: T, mut head: RequestHeadType, body: B, created: time::Instant, pool: Option>, -) -> impl Future +) -> Result<(ResponseHead, Payload), SendRequestError> where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, B: MessageBody, { // set request host header @@ -65,68 +65,98 @@ where io: Some(io), }; - let len = body.size(); - // create Framed and send request - Framed::new(io, h1::ClientCodec::default()) - .send((head, len).into()) - .from_err() - // send request body - .and_then(move |framed| match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => { - Either::A(ok(framed)) - } - _ => Either::B(SendBody::new(body, framed)), - }) - // read response and init read body - .and_then(|framed| { - framed - .into_future() - .map_err(|(e, _)| SendRequestError::from(e)) - .and_then(|(item, framed)| { - if let Some(res) = item { - match framed.get_codec().message_type() { - h1::MessageType::None => { - let force_close = !framed.get_codec().keepalive(); - release_connection(framed, force_close); - Ok((res, Payload::None)) - } - _ => { - let pl: PayloadStream = Box::new(PlStream::new(framed)); - Ok((res, pl.into())) - } - } - } else { - Err(ConnectError::Disconnected.into()) - } - }) - }) + let mut framed = Framed::new(io, h1::ClientCodec::default()); + framed.send((head, body.size()).into()).await?; + + // send request body + match body.size() { + BodySize::None | BodySize::Empty | BodySize::Sized(0) => (), + _ => send_body(body, &mut framed).await?, + }; + + // read response and init read body + let (head, framed) = if let (Some(result), framed) = framed.into_future().await { + let item = result.map_err(SendRequestError::from)?; + (item, framed) + } else { + return Err(SendRequestError::from(ConnectError::Disconnected)); + }; + + match framed.get_codec().message_type() { + h1::MessageType::None => { + let force_close = !framed.get_codec().keepalive(); + release_connection(framed, force_close); + Ok((head, Payload::None)) + } + _ => { + let pl: PayloadStream = PlStream::new(framed).boxed_local(); + Ok((head, pl.into())) + } + } } -pub(crate) fn open_tunnel( +pub(crate) async fn open_tunnel( io: T, head: RequestHeadType, -) -> impl Future), Error = SendRequestError> +) -> Result<(ResponseHead, Framed), SendRequestError> where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, { // create Framed and send request - Framed::new(io, h1::ClientCodec::default()) - .send((head, BodySize::None).into()) - .from_err() - // read response - .and_then(|framed| { - framed - .into_future() - .map_err(|(e, _)| SendRequestError::from(e)) - .and_then(|(head, framed)| { - if let Some(head) = head { - Ok((head, framed)) + let mut framed = Framed::new(io, h1::ClientCodec::default()); + framed.send((head, BodySize::None).into()).await?; + + // read response + if let (Some(result), framed) = framed.into_future().await { + let head = result.map_err(SendRequestError::from)?; + Ok((head, framed)) + } else { + Err(SendRequestError::from(ConnectError::Disconnected)) + } +} + +/// send request body to the peer +pub(crate) async fn send_body( + mut body: B, + framed: &mut Framed, +) -> Result<(), SendRequestError> +where + I: ConnectionLifetime, + B: MessageBody, +{ + let mut eof = false; + while !eof { + while !eof && !framed.is_write_buf_full() { + match poll_fn(|cx| body.poll_next(cx)).await { + Some(result) => { + framed.write(h1::Message::Chunk(Some(result?)))?; + } + None => { + eof = true; + framed.write(h1::Message::Chunk(None))?; + } + } + } + + if !framed.is_write_buf_empty() { + poll_fn(|cx| match framed.flush(cx) { + Poll::Ready(Ok(_)) => Poll::Ready(Ok(())), + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => { + if !framed.is_write_buf_full() { + Poll::Ready(Ok(())) } else { - Err(SendRequestError::from(ConnectError::Disconnected)) + Poll::Pending } - }) - }) + } + }) + .await?; + } + } + + SinkExt::flush(framed).await?; + Ok(()) } #[doc(hidden)] @@ -137,7 +167,10 @@ pub struct H1Connection { pool: Option>, } -impl ConnectionLifetime for H1Connection { +impl ConnectionLifetime for H1Connection +where + T: AsyncRead + AsyncWrite + Unpin + 'static, +{ /// Close connection fn close(&mut self) { if let Some(mut pool) = self.pool.take() { @@ -165,98 +198,41 @@ impl ConnectionLifetime for H1Connection } } -impl io::Read for H1Connection { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.io.as_mut().unwrap().read(buf) +impl AsyncRead for H1Connection { + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf) + } + + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf) } } -impl AsyncRead for H1Connection {} - -impl io::Write for H1Connection { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.io.as_mut().unwrap().write(buf) +impl AsyncWrite for H1Connection { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.io.as_mut().unwrap()).poll_write(cx, buf) } - fn flush(&mut self) -> io::Result<()> { - self.io.as_mut().unwrap().flush() + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(self.io.as_mut().unwrap()).poll_flush(cx) } -} -impl AsyncWrite for H1Connection { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.io.as_mut().unwrap().shutdown() - } -} - -/// Future responsible for sending request body to the peer -pub(crate) struct SendBody { - body: Option, - framed: Option>, - flushed: bool, -} - -impl SendBody -where - I: AsyncRead + AsyncWrite + 'static, - B: MessageBody, -{ - pub(crate) fn new(body: B, framed: Framed) -> Self { - SendBody { - body: Some(body), - framed: Some(framed), - flushed: true, - } - } -} - -impl Future for SendBody -where - I: ConnectionLifetime, - B: MessageBody, -{ - type Item = Framed; - type Error = SendRequestError; - - fn poll(&mut self) -> Poll { - let mut body_ready = true; - loop { - while body_ready - && self.body.is_some() - && !self.framed.as_ref().unwrap().is_write_buf_full() - { - match self.body.as_mut().unwrap().poll_next()? { - Async::Ready(item) => { - // check if body is done - if item.is_none() { - let _ = self.body.take(); - } - self.flushed = false; - self.framed - .as_mut() - .unwrap() - .force_send(h1::Message::Chunk(item))?; - break; - } - Async::NotReady => body_ready = false, - } - } - - if !self.flushed { - match self.framed.as_mut().unwrap().poll_complete()? { - Async::Ready(_) => { - self.flushed = true; - continue; - } - Async::NotReady => return Ok(Async::NotReady), - } - } - - if self.body.is_none() { - return Ok(Async::Ready(self.framed.take().unwrap())); - } - return Ok(Async::NotReady); - } + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll> { + Pin::new(self.io.as_mut().unwrap()).poll_shutdown(cx) } } @@ -273,23 +249,24 @@ impl PlStream { } impl Stream for PlStream { - type Item = Bytes; - type Error = PayloadError; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { - match self.framed.as_mut().unwrap().poll()? { - Async::NotReady => Ok(Async::NotReady), - Async::Ready(Some(chunk)) => { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let this = self.get_mut(); + + match this.framed.as_mut().unwrap().next_item(cx)? { + Poll::Pending => Poll::Pending, + Poll::Ready(Some(chunk)) => { if let Some(chunk) = chunk { - Ok(Async::Ready(Some(chunk))) + Poll::Ready(Some(Ok(chunk))) } else { - let framed = self.framed.take().unwrap(); + let framed = this.framed.take().unwrap(); let force_close = !framed.get_codec().keepalive(); release_connection(framed, force_close); - Ok(Async::Ready(None)) + Poll::Ready(None) } } - Async::Ready(None) => Ok(Async::Ready(None)), + Poll::Ready(None) => Poll::Ready(None), } } } diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 50d74fe1..25299fd6 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -5,7 +5,7 @@ use std::time; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; -use futures::future::{err, Either}; +use futures::future::{err, poll_fn, Either}; use h2::{client::SendRequest, SendStream}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; @@ -19,15 +19,15 @@ use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; use super::pool::Acquired; -pub(crate) fn send_request( - io: SendRequest, +pub(crate) async fn send_request( + mut io: SendRequest, head: RequestHeadType, body: B, created: time::Instant, pool: Option>, -) -> impl Future +) -> Result<(ResponseHead, Payload), SendRequestError> where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.size()); @@ -38,158 +38,138 @@ where _ => false, }; - io.ready() - .map_err(SendRequestError::from) - .and_then(move |mut io| { - let mut req = Request::new(()); - *req.uri_mut() = head.as_ref().uri.clone(); - *req.method_mut() = head.as_ref().method.clone(); - *req.version_mut() = Version::HTTP_2; + let mut req = Request::new(()); + *req.uri_mut() = head.as_ref().uri.clone(); + *req.method_mut() = head.as_ref().method.clone(); + *req.version_mut() = Version::HTTP_2; - let mut skip_len = true; - // let mut has_date = false; + let mut skip_len = true; + // let mut has_date = false; - // Content length - let _ = match length { - BodySize::None => None, - BodySize::Stream => { - skip_len = false; - None - } - BodySize::Empty => req - .headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodySize::Sized(len) => req.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), - BodySize::Sized64(len) => req.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), - }; + // Content length + let _ = match length { + BodySize::None => None, + BodySize::Stream => { + skip_len = false; + None + } + BodySize::Empty => req + .headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), + BodySize::Sized(len) => req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + BodySize::Sized64(len) => req.headers_mut().insert( + CONTENT_LENGTH, + HeaderValue::try_from(format!("{}", len)).unwrap(), + ), + }; - // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. - let (head, extra_headers) = match head { - RequestHeadType::Owned(head) => { - (RequestHeadType::Owned(head), HeaderMap::new()) - } - RequestHeadType::Rc(head, extra_headers) => ( - RequestHeadType::Rc(head, None), - extra_headers.unwrap_or_else(HeaderMap::new), - ), - }; + // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. + let (head, extra_headers) = match head { + RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()), + RequestHeadType::Rc(head, extra_headers) => ( + RequestHeadType::Rc(head, None), + extra_headers.unwrap_or_else(HeaderMap::new), + ), + }; - // merging headers from head and extra headers. - let headers = head - .as_ref() - .headers - .iter() - .filter(|(name, _)| !extra_headers.contains_key(*name)) - .chain(extra_headers.iter()); + // merging headers from head and extra headers. + let headers = head + .as_ref() + .headers + .iter() + .filter(|(name, _)| !extra_headers.contains_key(*name)) + .chain(extra_headers.iter()); - // copy headers - for (key, value) in headers { - match *key { - CONNECTION | TRANSFER_ENCODING => continue, // http2 specific - CONTENT_LENGTH if skip_len => continue, - // DATE => has_date = true, - _ => (), - } - req.headers_mut().append(key, value.clone()); + // copy headers + for (key, value) in headers { + match *key { + CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + CONTENT_LENGTH if skip_len => continue, + // DATE => has_date = true, + _ => (), + } + req.headers_mut().append(key, value.clone()); + } + + let res = poll_fn(|cx| io.poll_ready(cx)).await; + if let Err(e) = res { + release(io, pool, created, e.is_io()); + return Err(SendRequestError::from(e)); + } + + let resp = match io.send_request(req, eof) { + Ok((fut, send)) => { + release(io, pool, created, false); + + if !eof { + send_body(body, send).await?; } + fut.await.map_err(SendRequestError::from)? + } + Err(e) => { + release(io, pool, created, e.is_io()); + return Err(e.into()); + } + }; - match io.send_request(req, eof) { - Ok((res, send)) => { - release(io, pool, created, false); + let (parts, body) = resp.into_parts(); + let payload = if head_req { Payload::None } else { body.into() }; - if !eof { - Either::A(Either::B( - SendBody { - body, - send, - buf: None, - } - .and_then(move |_| res.map_err(SendRequestError::from)), - )) - } else { - Either::B(res.map_err(SendRequestError::from)) - } - } - Err(e) => { - release(io, pool, created, e.is_io()); - Either::A(Either::A(err(e.into()))) - } - } - }) - .and_then(move |resp| { - let (parts, body) = resp.into_parts(); - let payload = if head_req { Payload::None } else { body.into() }; - - let mut head = ResponseHead::new(parts.status); - head.version = parts.version; - head.headers = parts.headers.into(); - Ok((head, payload)) - }) - .from_err() + let mut head = ResponseHead::new(parts.status); + head.version = parts.version; + head.headers = parts.headers.into(); + Ok((head, payload)) } -struct SendBody { - body: B, - send: SendStream, - buf: Option, -} - -impl Future for SendBody { - type Item = (); - type Error = SendRequestError; - - fn poll(&mut self) -> Poll { - loop { - if self.buf.is_none() { - match self.body.poll_next() { - Ok(Async::Ready(Some(buf))) => { - self.send.reserve_capacity(buf.len()); - self.buf = Some(buf); - } - Ok(Async::Ready(None)) => { - if let Err(e) = self.send.send_data(Bytes::new(), true) { - return Err(e.into()); - } - self.send.reserve_capacity(0); - return Ok(Async::Ready(())); - } - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(e) => return Err(e.into()), +async fn send_body( + mut body: B, + mut send: SendStream, +) -> Result<(), SendRequestError> { + let mut buf = None; + loop { + if buf.is_none() { + match poll_fn(|cx| body.poll_next(cx)).await { + Some(Ok(b)) => { + send.reserve_capacity(b.len()); + buf = Some(b); } - } - - match self.send.poll_capacity() { - Ok(Async::NotReady) => return Ok(Async::NotReady), - Ok(Async::Ready(None)) => return Ok(Async::Ready(())), - Ok(Async::Ready(Some(cap))) => { - let mut buf = self.buf.take().unwrap(); - let len = buf.len(); - let bytes = buf.split_to(std::cmp::min(cap, len)); - - if let Err(e) = self.send.send_data(bytes, false) { + Some(Err(e)) => return Err(e.into()), + None => { + if let Err(e) = send.send_data(Bytes::new(), true) { return Err(e.into()); - } else { - if !buf.is_empty() { - self.send.reserve_capacity(buf.len()); - self.buf = Some(buf); - } - continue; } + send.reserve_capacity(0); + return Ok(()); } - Err(e) => return Err(e.into()), } } + + match poll_fn(|cx| send.poll_capacity(cx)).await { + None => return Ok(()), + Some(Ok(cap)) => { + let b = buf.as_mut().unwrap(); + let len = b.len(); + let bytes = b.split_to(std::cmp::min(cap, len)); + + if let Err(e) = send.send_data(bytes, false) { + return Err(e.into()); + } else { + if !b.is_empty() { + send.reserve_capacity(b.len()); + } + continue; + } + } + Some(Err(e)) => return Err(e.into()), + } } } // release SendRequest object -fn release( +fn release( io: SendRequest, pool: Option>, created: time::Instant, diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 4d02e0a1..ee4c4ab9 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -9,11 +9,10 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_service::Service; -use actix_utils::oneshot; -use actix_utils::task::LocalWaker; +use actix_utils::{oneshot, task::LocalWaker}; use bytes::Bytes; -use futures::future::{err, ok, Either, FutureResult}; -use h2::client::{handshake, Handshake}; +use futures::future::{err, ok, poll_fn, Either, FutureExt, LocalBoxFuture, Ready}; +use h2::client::{handshake, Connection, SendRequest}; use hashbrown::HashMap; use http::uri::Authority; use indexmap::IndexSet; @@ -43,17 +42,15 @@ impl From for Key { } /// Connections pool -pub(crate) struct ConnectionPool( - T, - Rc>>, -); +pub(crate) struct ConnectionPool(Rc>, Rc>>); impl ConnectionPool where Io: AsyncRead + AsyncWrite + 'static, T: Service - + Clone + + Unpin + 'static, + T::Future: Unpin, { pub(crate) fn new( connector: T, @@ -63,7 +60,7 @@ where limit: usize, ) -> Self { ConnectionPool( - connector, + Rc::new(RefCell::new(connector)), Rc::new(RefCell::new(Inner { conn_lifetime, conn_keep_alive, @@ -73,7 +70,7 @@ where waiters: Slab::new(), waiters_queue: IndexSet::new(), available: HashMap::new(), - task: None, + waker: LocalWaker::new(), })), ) } @@ -81,8 +78,7 @@ where impl Clone for ConnectionPool where - T: Clone, - Io: AsyncRead + AsyncWrite + 'static, + Io: 'static, { fn clone(&self) -> Self { ConnectionPool(self.0.clone(), self.1.clone()) @@ -91,86 +87,118 @@ where impl Service for ConnectionPool where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Clone + + Unpin + 'static, + T::Future: Unpin, { type Request = Connect; type Response = IoConnection; type Error = ConnectError; - type Future = Either< - FutureResult, - Either, OpenConnection>, - >; + type Future = LocalBoxFuture<'static, Result, ConnectError>>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.0.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.0.poll_ready(cx) } fn call(&mut self, req: Connect) -> Self::Future { - let key = if let Some(authority) = req.uri.authority_part() { - authority.clone().into() - } else { - return Either::A(err(ConnectError::Unresolverd)); + // start support future + tokio_executor::current_thread::spawn(ConnectorPoolSupport { + connector: self.0.clone(), + inner: self.1.clone(), + }); + + let mut connector = self.0.clone(); + let inner = self.1.clone(); + + let fut = async move { + let key = if let Some(authority) = req.uri.authority_part() { + authority.clone().into() + } else { + return Err(ConnectError::Unresolverd); + }; + + // acquire connection + match poll_fn(|cx| Poll::Ready(inner.borrow_mut().acquire(&key, cx))).await { + Acquire::Acquired(io, created) => { + // use existing connection + return Ok(IoConnection::new( + io, + created, + Some(Acquired(key, Some(inner))), + )); + } + Acquire::Available => { + // open tcp connection + let (io, proto) = connector.call(req).await?; + + let guard = OpenGuard::new(key, inner); + + if proto == Protocol::Http1 { + Ok(IoConnection::new( + ConnectionType::H1(io), + Instant::now(), + Some(guard.consume()), + )) + } else { + let (snd, connection) = handshake(io).await?; + tokio_executor::current_thread::spawn(connection.map(|_| ())); + Ok(IoConnection::new( + ConnectionType::H2(snd), + Instant::now(), + Some(guard.consume()), + )) + } + } + _ => { + // connection is not available, wait + let (rx, token) = inner.borrow_mut().wait_for(req); + + let guard = WaiterGuard::new(key, token, inner); + let res = match rx.await { + Err(_) => Err(ConnectError::Disconnected), + Ok(res) => res, + }; + guard.consume(); + res + } + } }; - // acquire connection - match self.1.as_ref().borrow_mut().acquire(&key) { - Acquire::Acquired(io, created) => { - // use existing connection - return Either::A(ok(IoConnection::new( - io, - created, - Some(Acquired(key, Some(self.1.clone()))), - ))); - } - Acquire::Available => { - // open new connection - return Either::B(Either::B(OpenConnection::new( - key, - self.1.clone(), - self.0.call(req), - ))); - } - _ => (), - } - - // connection is not available, wait - let (rx, token, support) = self.1.as_ref().borrow_mut().wait_for(req); - - // start support future - if !support { - self.1.as_ref().borrow_mut().task = Some(AtomicTask::new()); - tokio_executor::current_thread::spawn(ConnectorPoolSupport { - connector: self.0.clone(), - inner: self.1.clone(), - }) - } - - Either::B(Either::A(WaitForConnection { - rx, - key, - token, - inner: Some(self.1.clone()), - })) + fut.boxed_local() } } -#[doc(hidden)] -pub struct WaitForConnection +struct WaiterGuard where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { key: Key, token: usize, - rx: oneshot::Receiver, ConnectError>>, inner: Option>>>, } -impl Drop for WaitForConnection +impl WaiterGuard where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, +{ + fn new(key: Key, token: usize, inner: Rc>>) -> Self { + Self { + key, + token, + inner: Some(inner), + } + } + + fn consume(mut self) { + let _ = self.inner.take(); + } +} + +impl Drop for WaiterGuard +where + Io: AsyncRead + AsyncWrite + Unpin + 'static, { fn drop(&mut self) { if let Some(i) = self.inner.take() { @@ -181,113 +209,43 @@ where } } -impl Future for WaitForConnection +struct OpenGuard where - Io: AsyncRead + AsyncWrite, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { - type Item = IoConnection; - type Error = ConnectError; - - fn poll(&mut self) -> Poll { - match self.rx.poll() { - Ok(Async::Ready(item)) => match item { - Err(err) => Err(err), - Ok(conn) => { - let _ = self.inner.take(); - Ok(Async::Ready(conn)) - } - }, - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => { - let _ = self.inner.take(); - Err(ConnectError::Disconnected) - } - } - } -} - -#[doc(hidden)] -pub struct OpenConnection -where - Io: AsyncRead + AsyncWrite + 'static, -{ - fut: F, key: Key, - h2: Option>, inner: Option>>>, } -impl OpenConnection +impl OpenGuard where - F: Future, - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { - fn new(key: Key, inner: Rc>>, fut: F) -> Self { - OpenConnection { + fn new(key: Key, inner: Rc>>) -> Self { + Self { key, - fut, inner: Some(inner), - h2: None, } } + + fn consume(mut self) -> Acquired { + Acquired(self.key.clone(), self.inner.take()) + } } -impl Drop for OpenConnection +impl Drop for OpenGuard where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { fn drop(&mut self) { - if let Some(inner) = self.inner.take() { - let mut inner = inner.as_ref().borrow_mut(); + if let Some(i) = self.inner.take() { + let mut inner = i.as_ref().borrow_mut(); inner.release(); inner.check_availibility(); } } } -impl Future for OpenConnection -where - F: Future, - Io: AsyncRead + AsyncWrite, -{ - type Item = IoConnection; - type Error = ConnectError; - - fn poll(&mut self) -> Poll { - if let Some(ref mut h2) = self.h2 { - return match h2.poll() { - Ok(Async::Ready((snd, connection))) => { - tokio_executor::current_thread::spawn(connection.map_err(|_| ())); - Ok(Async::Ready(IoConnection::new( - ConnectionType::H2(snd), - Instant::now(), - Some(Acquired(self.key.clone(), self.inner.take())), - ))) - } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => Err(e.into()), - }; - } - - match self.fut.poll() { - Err(err) => Err(err), - Ok(Async::Ready((io, proto))) => { - if proto == Protocol::Http1 { - Ok(Async::Ready(IoConnection::new( - ConnectionType::H1(io), - Instant::now(), - Some(Acquired(self.key.clone(), self.inner.take())), - ))) - } else { - self.h2 = Some(handshake(io)); - self.poll() - } - } - Ok(Async::NotReady) => Ok(Async::NotReady), - } - } -} - enum Acquire { Acquired(ConnectionType, Instant), Available, @@ -314,7 +272,7 @@ pub(crate) struct Inner { )>, >, waiters_queue: IndexSet<(Key, usize)>, - task: Option, + waker: LocalWaker, } impl Inner { @@ -334,7 +292,7 @@ impl Inner { impl Inner where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { /// connection is not available, wait fn wait_for( @@ -343,7 +301,6 @@ where ) -> ( oneshot::Receiver, ConnectError>>, usize, - bool, ) { let (tx, rx) = oneshot::channel(); @@ -353,10 +310,10 @@ where entry.insert(Some((connect, tx))); assert!(self.waiters_queue.insert((key, token))); - (rx, token, self.task.is_some()) + (rx, token) } - fn acquire(&mut self, key: &Key) -> Acquire { + fn acquire(&mut self, key: &Key, cx: &mut Context) -> Acquire { // check limits if self.limit > 0 && self.acquired >= self.limit { return Acquire::NotAvailable; @@ -384,9 +341,9 @@ where let mut io = conn.io; let mut buf = [0; 2]; if let ConnectionType::H1(ref mut s) = io { - match s.read(&mut buf) { - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (), - Ok(n) if n > 0 => { + match Pin::new(s).poll_read(cx, &mut buf) { + Poll::Pending => (), + Poll::Ready(Ok(n)) if n > 0 => { if let Some(timeout) = self.disconnect_timeout { if let ConnectionType::H1(io) = io { tokio_executor::current_thread::spawn( @@ -396,7 +353,7 @@ where } continue; } - Ok(_) | Err(_) => continue, + _ => continue, } } return Acquire::Acquired(io, conn.created); @@ -431,9 +388,7 @@ where fn check_availibility(&self) { if !self.waiters_queue.is_empty() && self.acquired < self.limit { - if let Some(t) = self.task.as_ref() { - t.notify() - } + self.waker.wake(); } } } @@ -457,17 +412,16 @@ where impl Future for CloseConnection where - T: AsyncWrite, + T: AsyncWrite + Unpin, { - type Item = (); - type Error = (); + type Output = (); - fn poll(&mut self) -> Poll<(), ()> { - match self.timeout.poll() { - Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), - Ok(Async::NotReady) => match self.io.shutdown() { - Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), - Ok(Async::NotReady) => Ok(Async::NotReady), + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { + match Pin::new(&mut self.timeout).poll(cx) { + Poll::Ready(_) => Poll::Ready(()), + Poll::Pending => match Pin::new(&mut self.io).poll_shutdown(cx) { + Poll::Ready(_) => Poll::Ready(()), + Poll::Pending => Poll::Pending, }, } } @@ -483,16 +437,18 @@ where impl Future for ConnectorPoolSupport where - Io: AsyncRead + AsyncWrite + 'static, - T: Service, - T::Future: 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, + T: Service + + Unpin, + T::Future: Unpin + 'static, { - type Item = (); - type Error = (); + type Output = (); - fn poll(&mut self) -> Poll { - let mut inner = self.inner.as_ref().borrow_mut(); - inner.task.as_ref().unwrap().register(); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + let mut inner = this.inner.as_ref().borrow_mut(); + inner.waker.register(cx.waker()); // check waiters loop { @@ -507,14 +463,14 @@ where continue; } - match inner.acquire(&key) { + match inner.acquire(&key, cx) { Acquire::NotAvailable => break, Acquire::Acquired(io, created) => { let tx = inner.waiters.get_mut(token).unwrap().take().unwrap().1; if let Err(conn) = tx.send(Ok(IoConnection::new( io, created, - Some(Acquired(key.clone(), Some(self.inner.clone()))), + Some(Acquired(key.clone(), Some(this.inner.clone()))), ))) { let (io, created) = conn.unwrap().into_inner(); inner.release_conn(&key, io, created); @@ -526,33 +482,38 @@ where OpenWaitingConnection::spawn( key.clone(), tx, - self.inner.clone(), - self.connector.call(connect), + this.inner.clone(), + this.connector.call(connect), ); } } let _ = inner.waiters_queue.swap_remove_index(0); } - Ok(Async::NotReady) + Poll::Pending } } struct OpenWaitingConnection where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { fut: F, key: Key, - h2: Option>, + h2: Option< + LocalBoxFuture< + 'static, + Result<(SendRequest, Connection), h2::Error>, + >, + >, rx: Option, ConnectError>>>, inner: Option>>>, } impl OpenWaitingConnection where - F: Future + 'static, - Io: AsyncRead + AsyncWrite + 'static, + F: Future> + Unpin + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { fn spawn( key: Key, @@ -572,7 +533,7 @@ where impl Drop for OpenWaitingConnection where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { fn drop(&mut self) { if let Some(inner) = self.inner.take() { @@ -585,59 +546,60 @@ where impl Future for OpenWaitingConnection where - F: Future, - Io: AsyncRead + AsyncWrite, + F: Future> + Unpin, + Io: AsyncRead + AsyncWrite + Unpin, { - type Item = (); - type Error = (); + type Output = (); - fn poll(&mut self) -> Poll { - if let Some(ref mut h2) = self.h2 { - return match h2.poll() { - Ok(Async::Ready((snd, connection))) => { - tokio_executor::current_thread::spawn(connection.map_err(|_| ())); - let rx = self.rx.take().unwrap(); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + if let Some(ref mut h2) = this.h2 { + return match Pin::new(h2).poll(cx) { + Poll::Ready(Ok((snd, connection))) => { + tokio_executor::current_thread::spawn(connection.map(|_| ())); + let rx = this.rx.take().unwrap(); let _ = rx.send(Ok(IoConnection::new( ConnectionType::H2(snd), Instant::now(), - Some(Acquired(self.key.clone(), self.inner.take())), + Some(Acquired(this.key.clone(), this.inner.take())), ))); - Ok(Async::Ready(())) + Poll::Ready(()) } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(err) => { - let _ = self.inner.take(); - if let Some(rx) = self.rx.take() { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => { + let _ = this.inner.take(); + if let Some(rx) = this.rx.take() { let _ = rx.send(Err(ConnectError::H2(err))); } - Err(()) + Poll::Ready(()) } }; } - match self.fut.poll() { - Err(err) => { - let _ = self.inner.take(); - if let Some(rx) = self.rx.take() { + match Pin::new(&mut this.fut).poll(cx) { + Poll::Ready(Err(err)) => { + let _ = this.inner.take(); + if let Some(rx) = this.rx.take() { let _ = rx.send(Err(err)); } - Err(()) + Poll::Ready(()) } - Ok(Async::Ready((io, proto))) => { + Poll::Ready(Ok((io, proto))) => { if proto == Protocol::Http1 { - let rx = self.rx.take().unwrap(); + let rx = this.rx.take().unwrap(); let _ = rx.send(Ok(IoConnection::new( ConnectionType::H1(io), Instant::now(), - Some(Acquired(self.key.clone(), self.inner.take())), + Some(Acquired(this.key.clone(), this.inner.take())), ))); - Ok(Async::Ready(())) + Poll::Ready(()) } else { - self.h2 = Some(handshake(io)); - self.poll() + this.h2 = Some(handshake(io).boxed_local()); + Pin::new(this).poll(cx) } } - Ok(Async::NotReady) => Ok(Async::NotReady), + Poll::Pending => Poll::Pending, } } } @@ -646,7 +608,7 @@ pub(crate) struct Acquired(Key, Option>>>); impl Acquired where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, { pub(crate) fn close(&mut self, conn: IoConnection) { if let Some(inner) = self.1.take() { diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index bc6914d3..a992089c 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -55,7 +55,7 @@ where if item.is_none() { let _ = this.body.take(); } - framed.force_send(Message::Chunk(item))?; + framed.write(Message::Chunk(item))?; } Poll::Pending => body_ready = false, } @@ -78,7 +78,7 @@ where // send response if let Some(res) = this.res.take() { - framed.force_send(res)?; + framed.write(res)?; continue; } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index cf528aee..4d17347d 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -13,7 +13,7 @@ extern crate log; pub mod body; mod builder; -// pub mod client; +pub mod client; mod cloneable; mod config; pub mod encoding; @@ -32,8 +32,8 @@ pub mod cookie; pub mod error; pub mod h1; pub mod h2; -// pub mod test; -// pub mod ws; +pub mod test; +pub mod ws; pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 817bf480..26f2c223 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -1,6 +1,6 @@ //! Test Various helpers for Actix applications to use during testing. use std::fmt::Write as FmtWrite; -use std::io; +use std::io::{self, Read, Write}; use std::pin::Pin; use std::str::FromStr; use std::task::{Context, Poll}; @@ -245,16 +245,33 @@ impl io::Write for TestBuffer { } } -// impl AsyncRead for TestBuffer {} +impl AsyncRead for TestBuffer { + fn poll_read( + self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Poll::Ready(self.get_mut().read(buf)) + } +} -// impl AsyncWrite for TestBuffer { -// fn shutdown(&mut self) -> Poll<(), io::Error> { -// Ok(Async::Ready(())) -// } -// fn write_buf(&mut self, _: &mut B) -> Poll { -// Ok(Async::NotReady) -// } -// } +impl AsyncWrite for TestBuffer { + fn poll_write( + self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(self.get_mut().write(buf)) + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} impl IoStream for TestBuffer { fn set_nodelay(&mut self, _nodelay: bool) -> io::Result<()> { diff --git a/actix-http/src/ws/transport.rs b/actix-http/src/ws/transport.rs index da7782be..c55e2eeb 100644 --- a/actix-http/src/ws/transport.rs +++ b/actix-http/src/ws/transport.rs @@ -1,24 +1,27 @@ +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_service::{IntoService, Service}; use actix_utils::framed::{FramedTransport, FramedTransportError}; -use futures::{Future, Poll}; use super::{Codec, Frame, Message}; pub struct Transport where S: Service + 'static, - T: AsyncRead + AsyncWrite, + T: AsyncRead + AsyncWrite + Unpin, { inner: FramedTransport, } impl Transport where - T: AsyncRead + AsyncWrite, - S: Service, + T: AsyncRead + AsyncWrite + Unpin, + S: Service + Unpin, S::Future: 'static, - S::Error: 'static, + S::Error: Unpin + 'static, { pub fn new>(io: T, service: F) -> Self { Transport { @@ -35,15 +38,14 @@ where impl Future for Transport where - T: AsyncRead + AsyncWrite, - S: Service, + T: AsyncRead + AsyncWrite + Unpin, + S: Service + Unpin, S::Future: 'static, - S::Error: 'static, + S::Error: Unpin + 'static, { - type Item = (); - type Error = FramedTransportError; + type Output = Result<(), FramedTransportError>; - fn poll(&mut self) -> Poll { - self.inner.poll() + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + Pin::new(&mut self.inner).poll(cx) } } From a6a2d2f444fc9c7c744c44a7ccf53aab6707c1e5 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 18 Nov 2019 20:40:10 +0600 Subject: [PATCH 1604/1635] update ssl impls --- actix-http/Cargo.toml | 4 +- actix-http/src/client/connector.rs | 126 +++++++++++++----------- actix-http/src/cookie/secure/key.rs | 47 +++++---- actix-http/src/cookie/secure/private.rs | 38 ++++--- actix-http/src/cookie/secure/signed.rs | 9 +- actix-http/src/h1/payload.rs | 11 +-- actix-http/tests/test_server.rs | 35 ++++--- 7 files changed, 150 insertions(+), 120 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1cc5e43a..742938d5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -27,7 +27,7 @@ path = "src/lib.rs" default = [] # openssl -openssl = ["open-ssl", "actix-connect/openssl"] +openssl = ["open-ssl", "actix-connect/openssl", "tokio-openssl"] # rustls support rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] @@ -100,6 +100,8 @@ flate2 = { version="1.0.7", optional = true, default-features = false } # optional deps failure = { version = "0.1.5", optional = true } open-ssl = { version="0.10", package="openssl", optional = true } +tokio-openssl = { version = "0.4.0-alpha.6", optional = true } + rust-tls = { version = "0.16.0", package="rustls", optional = true } webpki-roots = { version = "0.18", optional = true } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 7421cb02..45625ca9 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -20,22 +20,22 @@ use super::error::ConnectError; use super::pool::{ConnectionPool, Protocol}; use super::Connect; -#[cfg(feature = "ssl")] -use openssl::ssl::SslConnector as OpensslConnector; +#[cfg(feature = "openssl")] +use open_ssl::ssl::SslConnector as OpensslConnector; -#[cfg(feature = "rust-tls")] -use rustls::ClientConfig; -#[cfg(feature = "rust-tls")] +#[cfg(feature = "rustls")] +use rust_tls::ClientConfig; +#[cfg(feature = "rustls")] use std::sync::Arc; -#[cfg(any(feature = "ssl", feature = "rust-tls"))] +#[cfg(any(feature = "openssl", feature = "rustls"))] enum SslConnector { - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] Openssl(OpensslConnector), - #[cfg(feature = "rust-tls")] + #[cfg(feature = "rustls")] Rustls(Arc), } -#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] +#[cfg(not(any(feature = "openssl", feature = "rustls")))] type SslConnector = (); /// Manages http client network connectivity @@ -76,9 +76,9 @@ impl Connector<(), ()> { TcpStream, > { let ssl = { - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] { - use openssl::ssl::SslMethod; + use open_ssl::ssl::SslMethod; let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); let _ = ssl @@ -86,7 +86,7 @@ impl Connector<(), ()> { .map_err(|e| error!("Can not set alpn protocol: {:?}", e)); SslConnector::Openssl(ssl.build()) } - #[cfg(all(not(feature = "ssl"), feature = "rust-tls"))] + #[cfg(all(not(feature = "openssl"), feature = "rustls"))] { let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let mut config = ClientConfig::new(); @@ -96,7 +96,7 @@ impl Connector<(), ()> { .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); SslConnector::Rustls(Arc::new(config)) } - #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] + #[cfg(not(any(feature = "openssl", feature = "rustls")))] {} }; @@ -145,7 +145,8 @@ where Request = TcpConnect, Response = TcpConnection, Error = actix_connect::ConnectError, - > + 'static, + > + Clone + + 'static, { /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. /// Set to 1 second by default. @@ -154,14 +155,14 @@ where self } - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] /// Use custom `SslConnector` instance. pub fn ssl(mut self, connector: OpensslConnector) -> Self { self.ssl = SslConnector::Openssl(connector); self } - #[cfg(feature = "rust-tls")] + #[cfg(feature = "rustls")] pub fn rustls(mut self, connector: Arc) -> Self { self.ssl = SslConnector::Rustls(connector); self @@ -217,7 +218,7 @@ where self, ) -> impl Service + Clone { - #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] + #[cfg(not(any(feature = "openssl", feature = "rustls")))] { let connector = TimeoutService::new( self.timeout, @@ -242,46 +243,49 @@ where ), } } - #[cfg(any(feature = "ssl", feature = "rust-tls"))] + #[cfg(any(feature = "openssl", feature = "rustls"))] { const H2: &[u8] = b"h2"; - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] use actix_connect::ssl::OpensslConnector; - #[cfg(feature = "rust-tls")] + #[cfg(feature = "rustls")] use actix_connect::ssl::RustlsConnector; - use actix_service::boxed::service; - #[cfg(feature = "rust-tls")] - use rustls::Session; + use actix_service::{boxed::service, pipeline}; + #[cfg(feature = "rustls")] + use rust_tls::Session; let ssl_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) - }) - .map_err(ConnectError::from) + pipeline( + apply_fn( + UnpinWrapper(self.connector.clone()), + |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }, + ) + .map_err(ConnectError::from), + ) .and_then(match self.ssl { - #[cfg(feature = "ssl")] - SslConnector::Openssl(ssl) => service( - OpensslConnector::service(ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (Box::new(sock) as Box, Protocol::Http2) - } else { - (Box::new(sock) as Box, Protocol::Http1) - } - }), - ), - #[cfg(feature = "rust-tls")] + #[cfg(feature = "openssl")] + SslConnector::Openssl(ssl) => OpensslConnector::service(ssl) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock) as Box, Protocol::Http2) + } else { + (Box::new(sock) as Box, Protocol::Http1) + } + }) + .map_err(ConnectError::from), + + #[cfg(feature = "rustls")] SslConnector::Rustls(ssl) => service( - RustlsConnector::service(ssl) + UnpinWrapper(RustlsConnector::service(ssl)) .map_err(ConnectError::from) .map(|stream| { let sock = stream.into_parts().0; @@ -292,9 +296,15 @@ where .map(|protos| protos.windows(2).any(|w| w == H2)) .unwrap_or(false); if h2 { - (Box::new(sock) as Box, Protocol::Http2) + ( + Box::new(sock) as Box, + Protocol::Http2, + ) } else { - (Box::new(sock) as Box, Protocol::Http1) + ( + Box::new(sock) as Box, + Protocol::Http1, + ) } }), ), @@ -307,7 +317,7 @@ where let tcp_service = TimeoutService::new( self.timeout, - apply_fn(self.connector.clone(), |msg: Connect, srv| { + apply_fn(UnpinWrapper(self.connector), |msg: Connect, srv| { srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) }) .map_err(ConnectError::from) @@ -339,11 +349,11 @@ where } #[derive(Clone)] -struct UnpinWrapper(T); +struct UnpinWrapper(T); -impl Unpin for UnpinWrapper {} +impl Unpin for UnpinWrapper {} -impl Service for UnpinWrapper { +impl Service for UnpinWrapper { type Request = T::Request; type Response = T::Response; type Error = T::Error; @@ -374,7 +384,7 @@ impl Future for UnpinWrapperFut { } } -#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] +#[cfg(not(any(feature = "openssl", feature = "rustls")))] mod connect_impl { use std::task::{Context, Poll}; @@ -439,7 +449,7 @@ mod connect_impl { } } -#[cfg(any(feature = "ssl", feature = "rust-tls"))] +#[cfg(any(feature = "openssl", feature = "rustls"))] mod connect_impl { use std::marker::PhantomData; @@ -510,11 +520,11 @@ mod connect_impl { fn call(&mut self, req: Connect) -> Self::Future { match req.uri.scheme_str() { - Some("https") | Some("wss") => Either::B(InnerConnectorResponseB { + Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { fut: self.ssl_pool.call(req), _t: PhantomData, }), - _ => Either::A(InnerConnectorResponseA { + _ => Either::Left(InnerConnectorResponseA { fut: self.tcp_pool.call(req), _t: PhantomData, }), diff --git a/actix-http/src/cookie/secure/key.rs b/actix-http/src/cookie/secure/key.rs index 39575c93..95058ed8 100644 --- a/actix-http/src/cookie/secure/key.rs +++ b/actix-http/src/cookie/secure/key.rs @@ -1,13 +1,12 @@ -use ring::digest::{Algorithm, SHA256}; -use ring::hkdf::expand; -use ring::hmac::SigningKey; +use ring::hkdf::{Algorithm, KeyType, Prk, HKDF_SHA256}; +use ring::hmac; use ring::rand::{SecureRandom, SystemRandom}; use super::private::KEY_LEN as PRIVATE_KEY_LEN; use super::signed::KEY_LEN as SIGNED_KEY_LEN; -static HKDF_DIGEST: &Algorithm = &SHA256; -const KEYS_INFO: &str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; +static HKDF_DIGEST: Algorithm = HKDF_SHA256; +const KEYS_INFO: &[&[u8]] = &[b"COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"]; /// A cryptographic master key for use with `Signed` and/or `Private` jars. /// @@ -25,6 +24,13 @@ pub struct Key { encryption_key: [u8; PRIVATE_KEY_LEN], } +impl KeyType for &Key { + #[inline] + fn len(&self) -> usize { + SIGNED_KEY_LEN + PRIVATE_KEY_LEN + } +} + impl Key { /// Derives new signing/encryption keys from a master key. /// @@ -56,21 +62,26 @@ impl Key { ); } - // Expand the user's key into two. - let prk = SigningKey::new(HKDF_DIGEST, key); + // An empty `Key` structure; will be filled in with HKDF derived keys. + let mut output_key = Key { + signing_key: [0; SIGNED_KEY_LEN], + encryption_key: [0; PRIVATE_KEY_LEN], + }; + + // Expand the master key into two HKDF generated keys. let mut both_keys = [0; SIGNED_KEY_LEN + PRIVATE_KEY_LEN]; - expand(&prk, KEYS_INFO.as_bytes(), &mut both_keys); + let prk = Prk::new_less_safe(HKDF_DIGEST, key); + let okm = prk.expand(KEYS_INFO, &output_key).expect("okm expand"); + okm.fill(&mut both_keys).expect("fill keys"); - // Copy the keys into their respective arrays. - let mut signing_key = [0; SIGNED_KEY_LEN]; - let mut encryption_key = [0; PRIVATE_KEY_LEN]; - signing_key.copy_from_slice(&both_keys[..SIGNED_KEY_LEN]); - encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]); - - Key { - signing_key, - encryption_key, - } + // Copy the key parts into their respective fields. + output_key + .signing_key + .copy_from_slice(&both_keys[..SIGNED_KEY_LEN]); + output_key + .encryption_key + .copy_from_slice(&both_keys[SIGNED_KEY_LEN..]); + output_key } /// Generates signing/encryption keys from a secure, random source. Keys are diff --git a/actix-http/src/cookie/secure/private.rs b/actix-http/src/cookie/secure/private.rs index eb8e9beb..6c16e94e 100644 --- a/actix-http/src/cookie/secure/private.rs +++ b/actix-http/src/cookie/secure/private.rs @@ -1,8 +1,8 @@ use std::str; use log::warn; -use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM}; -use ring::aead::{OpeningKey, SealingKey}; +use ring::aead::{Aad, Algorithm, Nonce, AES_256_GCM}; +use ring::aead::{LessSafeKey, UnboundKey}; use ring::rand::{SecureRandom, SystemRandom}; use super::Key; @@ -10,7 +10,7 @@ use crate::cookie::{Cookie, CookieJar}; // Keep these in sync, and keep the key len synced with the `private` docs as // well as the `KEYS_INFO` const in secure::Key. -static ALGO: &Algorithm = &AES_256_GCM; +static ALGO: &'static Algorithm = &AES_256_GCM; const NONCE_LEN: usize = 12; pub const KEY_LEN: usize = 32; @@ -53,11 +53,14 @@ impl<'a> PrivateJar<'a> { } let ad = Aad::from(name.as_bytes()); - let key = OpeningKey::new(ALGO, &self.key).expect("opening key"); - let (nonce, sealed) = data.split_at_mut(NONCE_LEN); + let key = LessSafeKey::new( + UnboundKey::new(&ALGO, &self.key).expect("matching key length"), + ); + let (nonce, mut sealed) = data.split_at_mut(NONCE_LEN); let nonce = Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`"); - let unsealed = open_in_place(&key, nonce, ad, 0, sealed) + let unsealed = key + .open_in_place(nonce, ad, &mut sealed) .map_err(|_| "invalid key/nonce/value: bad seal")?; if let Ok(unsealed_utf8) = str::from_utf8(unsealed) { @@ -196,30 +199,33 @@ Please change it as soon as possible." fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec { // Create the `SealingKey` structure. - let key = SealingKey::new(ALGO, key).expect("sealing key creation"); + let unbound = UnboundKey::new(&ALGO, key).expect("matching key length"); + let key = LessSafeKey::new(unbound); // Create a vec to hold the [nonce | cookie value | overhead]. - let overhead = ALGO.tag_len(); - let mut data = vec![0; NONCE_LEN + value.len() + overhead]; + let mut data = vec![0; NONCE_LEN + value.len() + ALGO.tag_len()]; // Randomly generate the nonce, then copy the cookie value as input. let (nonce, in_out) = data.split_at_mut(NONCE_LEN); + let (in_out, tag) = in_out.split_at_mut(value.len()); + in_out.copy_from_slice(value); + + // Randomly generate the nonce into the nonce piece. SystemRandom::new() .fill(nonce) .expect("couldn't random fill nonce"); - in_out[..value.len()].copy_from_slice(value); - let nonce = - Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`"); + let nonce = Nonce::try_assume_unique_for_key(nonce).expect("invalid `nonce` length"); // Use cookie's name as associated data to prevent value swapping. let ad = Aad::from(name); + let ad_tag = key + .seal_in_place_separate_tag(nonce, ad, in_out) + .expect("in-place seal"); - // Perform the actual sealing operation and get the output length. - let output_len = - seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal"); + // Copy the tag into the tag piece. + tag.copy_from_slice(ad_tag.as_ref()); // Remove the overhead and return the sealed content. - data.truncate(NONCE_LEN + output_len); data } diff --git a/actix-http/src/cookie/secure/signed.rs b/actix-http/src/cookie/secure/signed.rs index 36a277cd..3fcd2cd8 100644 --- a/actix-http/src/cookie/secure/signed.rs +++ b/actix-http/src/cookie/secure/signed.rs @@ -1,12 +1,11 @@ -use ring::digest::{Algorithm, SHA256}; -use ring::hmac::{sign, verify_with_own_key as verify, SigningKey}; +use ring::hmac::{self, sign, verify}; use super::Key; use crate::cookie::{Cookie, CookieJar}; // Keep these in sync, and keep the key len synced with the `signed` docs as // well as the `KEYS_INFO` const in secure::Key. -static HMAC_DIGEST: &Algorithm = &SHA256; +static HMAC_DIGEST: hmac::Algorithm = hmac::HMAC_SHA256; const BASE64_DIGEST_LEN: usize = 44; pub const KEY_LEN: usize = 32; @@ -21,7 +20,7 @@ pub const KEY_LEN: usize = 32; /// This type is only available when the `secure` feature is enabled. pub struct SignedJar<'a> { parent: &'a mut CookieJar, - key: SigningKey, + key: hmac::Key, } impl<'a> SignedJar<'a> { @@ -32,7 +31,7 @@ impl<'a> SignedJar<'a> { pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> { SignedJar { parent, - key: SigningKey::new(HMAC_DIGEST, key.signing()), + key: hmac::Key::new(HMAC_DIGEST, key.signing()), } } diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 036138f9..20ff830e 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -234,7 +234,7 @@ mod tests { fn test_unread_data() { Runtime::new() .unwrap() - .block_on(lazy(|| { + .block_on(async { let (_, mut payload) = Payload::create(false); payload.unread_data(Bytes::from("data")); @@ -242,13 +242,12 @@ mod tests { assert_eq!(payload.len(), 4); assert_eq!( - Async::Ready(Some(Bytes::from("data"))), - payload.poll().ok().unwrap() + Poll::Ready(Some(Bytes::from("data"))), + payload.next_item().await.ok().unwrap() ); - let res: Result<(), ()> = Ok(()); - result(res) - })) + result(()) + }) .unwrap(); } } diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index a31e4ac8..51ee9f2d 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -4,10 +4,10 @@ use std::{net, thread}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; -use actix_service::{new_service_cfg, service_fn, NewService}; +use actix_service::{factory_fn_cfg, pipeline, service_fn, ServiceFactory}; use bytes::Bytes; -use futures::future::{self, ok, Future}; -use futures::stream::{once, Stream}; +use futures::future::{self, err, ok, ready, Future, FutureExt}; +use futures::stream::{once, Stream, StreamExt}; use regex::Regex; use tokio_timer::sleep; @@ -58,9 +58,9 @@ fn test_expect_continue() { HttpService::build() .expect(service_fn(|req: Request| { if req.head().uri.query() == Some("yes=") { - Ok(req) + ok(req) } else { - Err(error::ErrorPreconditionFailed("error")) + err(error::ErrorPreconditionFailed("error")) } })) .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) @@ -117,9 +117,12 @@ fn test_chunked_payload() { HttpService::build().h1(|mut request: Request| { request .take_payload() - .map_err(|e| panic!(format!("Error reading payload: {}", e))) - .fold(0usize, |acc, chunk| future::ok::<_, ()>(acc + chunk.len())) - .map(|req_size| Response::Ok().body(format!("size={}", req_size))) + .map(|res| match res { + Ok(pl) => pl, + Err(e) => panic!(format!("Error reading payload: {}", e)), + }) + .fold(0usize, |acc, chunk| ready(acc + chunk.len())) + .map(|req_size| ok(Response::Ok().body(format!("size={}", req_size)))) }) }); @@ -414,7 +417,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_body() { let mut srv = TestServer::new(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); let response = srv.block_on(srv.get("/").send()).unwrap(); @@ -493,7 +496,7 @@ fn test_h1_head_binary2() { fn test_h1_body_length() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); + let body = once(ok(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), ) @@ -512,7 +515,7 @@ fn test_h1_body_length() { fn test_h1_body_chunked_explicit() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { - let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() .header(header::TRANSFER_ENCODING, "chunked") @@ -544,7 +547,7 @@ fn test_h1_body_chunked_explicit() { fn test_h1_body_chunked_implicit() { let mut srv = TestServer::new(|| { HttpService::build().h1(|_| { - let body = once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>(Response::Ok().streaming(body)) }) }); @@ -569,15 +572,15 @@ fn test_h1_body_chunked_implicit() { #[test] fn test_h1_response_http_error_handling() { let mut srv = TestServer::new(|| { - HttpService::build().h1(new_service_cfg(|_: &ServerConfig| { - Ok::<_, ()>(|_| { + HttpService::build().h1(factory_fn_cfg(|_: &ServerConfig| { + ok::<_, ()>(pipeline(|_| { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() .header(http::header::CONTENT_TYPE, broken_header) .body(STR), ) - }) + })) })) }); @@ -593,7 +596,7 @@ fn test_h1_response_http_error_handling() { fn test_h1_service_error() { let mut srv = TestServer::new(|| { HttpService::build() - .h1(|_| Err::(error::ErrorBadRequest("error"))) + .h1(|_| future::err::(error::ErrorBadRequest("error"))) }); let response = srv.block_on(srv.get("/").send()).unwrap(); From 5ab29b2e62fca5159d93e6deea1e4a48bfa1bcad Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Nov 2019 09:55:17 +0600 Subject: [PATCH 1605/1635] migrate awc and test-server to std::future --- Cargo.toml | 2 +- actix-http/src/body.rs | 11 +- actix-http/src/client/connection.rs | 2 +- awc/Cargo.toml | 53 ++++--- awc/src/connect.rs | 230 ++++++++++++++-------------- awc/src/frozen.rs | 4 +- awc/src/request.rs | 2 +- awc/src/response.rs | 89 ++++++----- awc/src/sender.rs | 56 ++++--- awc/src/ws.rs | 169 ++++++++++---------- test-server/Cargo.toml | 47 ++++-- test-server/src/lib.rs | 108 ++----------- 12 files changed, 376 insertions(+), 397 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab812d1b..0b5c4f3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ members = [ # "actix-web-codegen", # "test-server", ] -exclude = ["actix-http"] +exclude = ["awc", "actix-http", "test-server"] [features] default = ["brotli", "flate2-zlib", "client", "fail"] diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 7b86bfb1..44e76ae0 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -253,7 +253,7 @@ where impl From> for Body where S: Stream> + Unpin + 'static, - E: Into + Unpin + 'static, + E: Into + 'static, { fn from(s: BodyStream) -> Body { Body::from_message(s) @@ -368,10 +368,17 @@ where } } +impl Unpin for BodyStream +where + S: Stream> + Unpin, + E: Into, +{ +} + impl MessageBody for BodyStream where S: Stream> + Unpin, - E: Into + Unpin, + E: Into, { fn size(&self) -> BodySize { BodySize::Stream diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 70ffff6c..0901fdb2 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -22,7 +22,7 @@ pub(crate) enum ConnectionType { } pub trait Connection { - type Io: AsyncRead + AsyncWrite; + type Io: AsyncRead + AsyncWrite + Unpin; type Future: Future>; fn protocol(&self) -> Protocol; diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 4b0e612b..ea181237 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.8" +version = "0.3.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -14,23 +14,23 @@ categories = ["network-programming", "asynchronous", license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" -workspace = ".." +# workspace = ".." [lib] name = "awc" path = "src/lib.rs" [package.metadata.docs.rs] -features = ["ssl", "brotli", "flate2-zlib"] +features = ["openssl", "brotli", "flate2-zlib"] [features] default = ["brotli", "flate2-zlib"] # openssl -ssl = ["openssl", "actix-http/ssl"] +openssl = ["open-ssl", "actix-http/openssl"] # rustls -rust-tls = ["rustls", "actix-http/rust-tls"] +rustls = ["rust-tls", "actix-http/rustls"] # brotli encoding, requires c compiler brotli = ["actix-http/brotli"] @@ -42,13 +42,14 @@ flate2-zlib = ["actix-http/flate2-zlib"] flate2-rust = ["actix-http/flate2-rust"] [dependencies] -actix-codec = "0.1.2" -actix-service = "0.4.1" -actix-http = "0.2.11" +actix-codec = "0.2.0-alpha.1" +actix-service = "1.0.0-alpha.1" +actix-http = "0.3.0-alpha.1" + base64 = "0.10.1" bytes = "0.4" derive_more = "0.15.0" -futures = "0.1.25" +futures = "0.3.1" log =" 0.4" mime = "0.3" percent-encoding = "2.1" @@ -56,21 +57,33 @@ rand = "0.7" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.6.1" -tokio-timer = "0.2.8" -openssl = { version="0.10", optional = true } -rustls = { version = "0.15.2", optional = true } +tokio-timer = "0.3.0-alpha.6" +open-ssl = { version="0.10", package="openssl", optional = true } +rust-tls = { version = "0.16.0", package="rustls", optional = true } [dev-dependencies] -actix-rt = "0.2.2" -actix-web = { version = "1.0.8", features=["ssl"] } -actix-http = { version = "0.2.11", features=["ssl"] } -actix-http-test = { version = "0.2.0", features=["ssl"] } -actix-utils = "0.4.1" -actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } +actix-rt = "1.0.0-alpha.1" +#actix-web = { version = "1.0.8", features=["ssl"] } +actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } +#actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-utils = "0.5.0-alpha.1" +actix-server = { version = "0.8.0-alpha.1", features=["openssl", "rustls"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" rand = "0.7" tokio-tcp = "0.1" -webpki = "0.19" -rustls = { version = "0.15.2", features = ["dangerous_configuration"] } +webpki = { version = "0.21" } +rus-tls = { version = "0.16.0", package="rustls", features = ["dangerous_configuration"] } + +[patch.crates-io] +actix-http = { path = "../actix-http" } + +actix-codec = { path = "../../actix-net/actix-codec" } +actix-connect = { path = "../../actix-net/actix-connect" } +actix-rt = { path = "../../actix-net/actix-rt" } +actix-server = { path = "../../actix-net/actix-server" } +actix-server-config = { path = "../../actix-net/actix-server-config" } +actix-service = { path = "../../actix-net/actix-service" } +actix-threadpool = { path = "../../actix-net/actix-threadpool" } +actix-utils = { path = "../../actix-net/actix-utils" } diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 97864d30..cc92fdbb 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,4 +1,6 @@ +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use std::{fmt, io, net}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; @@ -10,7 +12,7 @@ use actix_http::h1::ClientCodec; use actix_http::http::HeaderMap; use actix_http::{RequestHead, RequestHeadType, ResponseHead}; use actix_service::Service; -use futures::{Future, Poll}; +use futures::future::{FutureExt, LocalBoxFuture}; use crate::response::ClientResponse; @@ -22,7 +24,7 @@ pub(crate) trait Connect { head: RequestHead, body: Body, addr: Option, - ) -> Box>; + ) -> LocalBoxFuture<'static, Result>; fn send_request_extra( &mut self, @@ -30,18 +32,16 @@ pub(crate) trait Connect { extra_headers: Option, body: Body, addr: Option, - ) -> Box>; + ) -> LocalBoxFuture<'static, Result>; /// Send request, returns Response and Framed fn open_tunnel( &mut self, head: RequestHead, addr: Option, - ) -> Box< - dyn Future< - Item = (ResponseHead, Framed), - Error = SendRequestError, - >, + ) -> LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, >; /// Send request and extra headers, returns Response and Framed @@ -50,11 +50,9 @@ pub(crate) trait Connect { head: Rc, extra_headers: Option, addr: Option, - ) -> Box< - dyn Future< - Item = (ResponseHead, Framed), - Error = SendRequestError, - >, + ) -> LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, >; } @@ -72,21 +70,23 @@ where head: RequestHead, body: Body, addr: Option, - ) -> Box> { - Box::new( - self.0 - // connect to the host - .call(ClientConnect { - uri: head.uri.clone(), - addr, - }) - .from_err() - // send request - .and_then(move |connection| { - connection.send_request(RequestHeadType::from(head), body) - }) - .map(|(head, payload)| ClientResponse::new(head, payload)), - ) + ) -> LocalBoxFuture<'static, Result> { + // connect to the host + let fut = self.0.call(ClientConnect { + uri: head.uri.clone(), + addr, + }); + + async move { + let connection = fut.await?; + + // send request + connection + .send_request(RequestHeadType::from(head), body) + .await + .map(|(head, payload)| ClientResponse::new(head, payload)) + } + .boxed_local() } fn send_request_extra( @@ -95,51 +95,51 @@ where extra_headers: Option, body: Body, addr: Option, - ) -> Box> { - Box::new( - self.0 - // connect to the host - .call(ClientConnect { - uri: head.uri.clone(), - addr, - }) - .from_err() - // send request - .and_then(move |connection| { - connection - .send_request(RequestHeadType::Rc(head, extra_headers), body) - }) - .map(|(head, payload)| ClientResponse::new(head, payload)), - ) + ) -> LocalBoxFuture<'static, Result> { + // connect to the host + let fut = self.0.call(ClientConnect { + uri: head.uri.clone(), + addr, + }); + + async move { + let connection = fut.await?; + + // send request + let (head, payload) = connection + .send_request(RequestHeadType::Rc(head, extra_headers), body) + .await?; + + Ok(ClientResponse::new(head, payload)) + } + .boxed_local() } fn open_tunnel( &mut self, head: RequestHead, addr: Option, - ) -> Box< - dyn Future< - Item = (ResponseHead, Framed), - Error = SendRequestError, - >, + ) -> LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, > { - Box::new( - self.0 - // connect to the host - .call(ClientConnect { - uri: head.uri.clone(), - addr, - }) - .from_err() - // send request - .and_then(move |connection| { - connection.open_tunnel(RequestHeadType::from(head)) - }) - .map(|(head, framed)| { - let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); - (head, framed) - }), - ) + // connect to the host + let fut = self.0.call(ClientConnect { + uri: head.uri.clone(), + addr, + }); + + async move { + let connection = fut.await?; + + // send request + let (head, framed) = + connection.open_tunnel(RequestHeadType::from(head)).await?; + + let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); + Ok((head, framed)) + } + .boxed_local() } fn open_tunnel_extra( @@ -147,48 +147,47 @@ where head: Rc, extra_headers: Option, addr: Option, - ) -> Box< - dyn Future< - Item = (ResponseHead, Framed), - Error = SendRequestError, - >, + ) -> LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, > { - Box::new( - self.0 - // connect to the host - .call(ClientConnect { - uri: head.uri.clone(), - addr, - }) - .from_err() - // send request - .and_then(move |connection| { - connection.open_tunnel(RequestHeadType::Rc(head, extra_headers)) - }) - .map(|(head, framed)| { - let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); - (head, framed) - }), - ) + // connect to the host + let fut = self.0.call(ClientConnect { + uri: head.uri.clone(), + addr, + }); + + async move { + let connection = fut.await?; + + // send request + let (head, framed) = connection + .open_tunnel(RequestHeadType::Rc(head, extra_headers)) + .await?; + + let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); + Ok((head, framed)) + } + .boxed_local() } } trait AsyncSocket { - fn as_read(&self) -> &dyn AsyncRead; - fn as_read_mut(&mut self) -> &mut dyn AsyncRead; - fn as_write(&mut self) -> &mut dyn AsyncWrite; + fn as_read(&self) -> &(dyn AsyncRead + Unpin); + fn as_read_mut(&mut self) -> &mut (dyn AsyncRead + Unpin); + fn as_write(&mut self) -> &mut (dyn AsyncWrite + Unpin); } -struct Socket(T); +struct Socket(T); -impl AsyncSocket for Socket { - fn as_read(&self) -> &dyn AsyncRead { +impl AsyncSocket for Socket { + fn as_read(&self) -> &(dyn AsyncRead + Unpin) { &self.0 } - fn as_read_mut(&mut self) -> &mut dyn AsyncRead { + fn as_read_mut(&mut self) -> &mut (dyn AsyncRead + Unpin) { &mut self.0 } - fn as_write(&mut self) -> &mut dyn AsyncWrite { + fn as_write(&mut self) -> &mut (dyn AsyncWrite + Unpin) { &mut self.0 } } @@ -201,30 +200,37 @@ impl fmt::Debug for BoxedSocket { } } -impl io::Read for BoxedSocket { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.as_read_mut().read(buf) - } -} - impl AsyncRead for BoxedSocket { unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { self.0.as_read().prepare_uninitialized_buffer(buf) } -} -impl io::Write for BoxedSocket { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.as_write().write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.as_write().flush() + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf) } } impl AsyncWrite for BoxedSocket { - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.0.as_write().shutdown() + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(self.get_mut().0.as_write()).poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(self.get_mut().0.as_write()).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx) } } diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index d9f65d43..61ba87aa 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -82,7 +82,7 @@ impl FrozenClientRequest { /// Send a streaming body. pub fn send_stream(&self, stream: S) -> SendClientRequest where - S: Stream + 'static, + S: Stream> + Unpin + 'static, E: Into + 'static, { RequestSender::Rc(self.head.clone(), None).send_stream( @@ -203,7 +203,7 @@ impl FrozenSendBuilder { /// Complete request construction and send a streaming body. pub fn send_stream(self, stream: S) -> SendClientRequest where - S: Stream + 'static, + S: Stream> + Unpin + 'static, E: Into + 'static, { if let Some(e) = self.err { diff --git a/awc/src/request.rs b/awc/src/request.rs index 6ff68ae6..83123443 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -478,7 +478,7 @@ impl ClientRequest { /// Set an streaming body and generate `ClientRequest`. pub fn send_stream(self, stream: S) -> SendClientRequest where - S: Stream + 'static, + S: Stream> + Unpin + 'static, E: Into + 'static, { let slf = match self.prep_for_sending() { diff --git a/awc/src/response.rs b/awc/src/response.rs index d186526d..a7b08eed 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -1,9 +1,11 @@ use std::cell::{Ref, RefMut}; use std::fmt; use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use bytes::{Bytes, BytesMut}; -use futures::{Async, Future, Poll, Stream}; +use futures::{ready, Future, Stream}; use actix_http::cookie::Cookie; use actix_http::error::{CookieParseError, PayloadError}; @@ -104,7 +106,7 @@ impl ClientResponse { impl ClientResponse where - S: Stream, + S: Stream>, { /// Loads http response's body. pub fn body(&mut self) -> MessageBody { @@ -125,13 +127,12 @@ where impl Stream for ClientResponse where - S: Stream, + S: Stream> + Unpin, { - type Item = Bytes; - type Error = PayloadError; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { - self.payload.poll() + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + Pin::new(&mut self.get_mut().payload).poll_next(cx) } } @@ -155,7 +156,7 @@ pub struct MessageBody { impl MessageBody where - S: Stream, + S: Stream>, { /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { @@ -198,23 +199,24 @@ where impl Future for MessageBody where - S: Stream, + S: Stream> + Unpin, { - type Item = Bytes; - type Error = PayloadError; + type Output = Result; - fn poll(&mut self) -> Poll { - if let Some(err) = self.err.take() { - return Err(err); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + if let Some(err) = this.err.take() { + return Poll::Ready(Err(err)); } - if let Some(len) = self.length.take() { - if len > self.fut.as_ref().unwrap().limit { - return Err(PayloadError::Overflow); + if let Some(len) = this.length.take() { + if len > this.fut.as_ref().unwrap().limit { + return Poll::Ready(Err(PayloadError::Overflow)); } } - self.fut.as_mut().unwrap().poll() + Pin::new(&mut this.fut.as_mut().unwrap()).poll(cx) } } @@ -233,7 +235,7 @@ pub struct JsonBody { impl JsonBody where - S: Stream, + S: Stream>, U: DeserializeOwned, { /// Create `JsonBody` for request. @@ -279,27 +281,35 @@ where } } -impl Future for JsonBody +impl Unpin for JsonBody where - T: Stream, + T: Stream> + Unpin, U: DeserializeOwned, { - type Item = U; - type Error = JsonPayloadError; +} - fn poll(&mut self) -> Poll { +impl Future for JsonBody +where + T: Stream> + Unpin, + U: DeserializeOwned, +{ + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { if let Some(err) = self.err.take() { - return Err(err); + return Poll::Ready(Err(err)); } if let Some(len) = self.length.take() { if len > self.fut.as_ref().unwrap().limit { - return Err(JsonPayloadError::Payload(PayloadError::Overflow)); + return Poll::Ready(Err(JsonPayloadError::Payload( + PayloadError::Overflow, + ))); } } - let body = futures::try_ready!(self.fut.as_mut().unwrap().poll()); - Ok(Async::Ready(serde_json::from_slice::(&body)?)) + let body = ready!(Pin::new(&mut self.get_mut().fut.as_mut().unwrap()).poll(cx))?; + Poll::Ready(serde_json::from_slice::(&body).map_err(JsonPayloadError::from)) } } @@ -321,24 +331,25 @@ impl ReadBody { impl Future for ReadBody where - S: Stream, + S: Stream> + Unpin, { - type Item = Bytes; - type Error = PayloadError; + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); - fn poll(&mut self) -> Poll { loop { - return match self.stream.poll()? { - Async::Ready(Some(chunk)) => { - if (self.buf.len() + chunk.len()) > self.limit { - Err(PayloadError::Overflow) + return match Pin::new(&mut this.stream).poll_next(cx)? { + Poll::Ready(Some(chunk)) => { + if (this.buf.len() + chunk.len()) > this.limit { + Poll::Ready(Err(PayloadError::Overflow)) } else { - self.buf.extend_from_slice(&chunk); + this.buf.extend_from_slice(&chunk); continue; } } - Async::Ready(None) => Ok(Async::Ready(self.buf.take().freeze())), - Async::NotReady => Ok(Async::NotReady), + Poll::Ready(None) => Poll::Ready(Ok(this.buf.take().freeze())), + Poll::Pending => Poll::Pending, }; } } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 95109b92..f7461113 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -1,13 +1,15 @@ use std::net; +use std::pin::Pin; use std::rc::Rc; -use std::time::{Duration, Instant}; +use std::task::{Context, Poll}; +use std::time::Duration; use bytes::Bytes; use derive_more::From; -use futures::{try_ready, Async, Future, Poll, Stream}; +use futures::{future::LocalBoxFuture, ready, Future, Stream}; use serde::Serialize; use serde_json; -use tokio_timer::Delay; +use tokio_timer::{delay_for, Delay}; use actix_http::body::{Body, BodyStream}; use actix_http::encoding::Decoder; @@ -47,7 +49,7 @@ impl Into for PrepForSendingError { #[must_use = "futures do nothing unless polled"] pub enum SendClientRequest { Fut( - Box>, + LocalBoxFuture<'static, Result>, Option, bool, ), @@ -56,41 +58,51 @@ pub enum SendClientRequest { impl SendClientRequest { pub(crate) fn new( - send: Box>, + send: LocalBoxFuture<'static, Result>, response_decompress: bool, timeout: Option, ) -> SendClientRequest { - let delay = timeout.map(|t| Delay::new(Instant::now() + t)); + let delay = timeout.map(|t| delay_for(t)); SendClientRequest::Fut(send, delay, response_decompress) } } impl Future for SendClientRequest { - type Item = ClientResponse>>; - type Error = SendRequestError; + type Output = + Result>>, SendRequestError>; - fn poll(&mut self) -> Poll { - match self { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.get_mut(); + + match this { SendClientRequest::Fut(send, delay, response_decompress) => { if delay.is_some() { - match delay.poll() { - Ok(Async::NotReady) => (), - _ => return Err(SendRequestError::Timeout), + match Pin::new(delay.as_mut().unwrap()).poll(cx) { + Poll::Pending => (), + _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } - let res = try_ready!(send.poll()).map_body(|head, payload| { - if *response_decompress { - Payload::Stream(Decoder::from_headers(payload, &head.headers)) - } else { - Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) - } + let res = ready!(Pin::new(send).poll(cx)).map(|res| { + res.map_body(|head, payload| { + if *response_decompress { + Payload::Stream(Decoder::from_headers( + payload, + &head.headers, + )) + } else { + Payload::Stream(Decoder::new( + payload, + ContentEncoding::Identity, + )) + } + }) }); - Ok(Async::Ready(res)) + Poll::Ready(res) } SendClientRequest::Err(ref mut e) => match e.take() { - Some(e) => Err(e), + Some(e) => Poll::Ready(Err(e)), None => panic!("Attempting to call completed future"), }, } @@ -223,7 +235,7 @@ impl RequestSender { stream: S, ) -> SendClientRequest where - S: Stream + 'static, + S: Stream> + Unpin + 'static, E: Into + 'static, { self.send_body( diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 77cbc7ca..979a382a 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -7,7 +7,6 @@ use std::{fmt, str}; use actix_codec::Framed; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; -use futures::future::{err, Either, Future}; use percent_encoding::percent_encode; use tokio_timer::Timeout; @@ -210,27 +209,26 @@ impl WebsocketsRequest { } /// Complete request construction and connect to a websockets server. - pub fn connect( + pub async fn connect( mut self, - ) -> impl Future), Error = WsClientError> - { + ) -> Result<(ClientResponse, Framed), WsClientError> { if let Some(e) = self.err.take() { - return Either::A(err(e.into())); + return Err(e.into()); } // validate uri let uri = &self.head.uri; if uri.host().is_none() { - return Either::A(err(InvalidUrl::MissingHost.into())); + return Err(InvalidUrl::MissingHost.into()); } else if uri.scheme_part().is_none() { - return Either::A(err(InvalidUrl::MissingScheme.into())); + return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme_part() { match scheme.as_str() { "http" | "ws" | "https" | "wss" => (), - _ => return Either::A(err(InvalidUrl::UnknownScheme.into())), + _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { - return Either::A(err(InvalidUrl::UnknownScheme.into())); + return Err(InvalidUrl::UnknownScheme.into()); } if !self.head.headers.contains_key(header::HOST) { @@ -294,90 +292,83 @@ impl WebsocketsRequest { .config .connector .borrow_mut() - .open_tunnel(head, self.addr) - .from_err() - .and_then(move |(head, framed)| { - // verify response - if head.status != StatusCode::SWITCHING_PROTOCOLS { - return Err(WsClientError::InvalidResponseStatus(head.status)); - } - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - log::trace!("Invalid upgrade header"); - return Err(WsClientError::InvalidUpgradeHeader); - } - // Check for "CONNECTION" header - if let Some(conn) = head.headers.get(&header::CONNECTION) { - if let Ok(s) = conn.to_str() { - if !s.to_ascii_lowercase().contains("upgrade") { - log::trace!("Invalid connection header: {}", s); - return Err(WsClientError::InvalidConnectionHeader( - conn.clone(), - )); - } - } else { - log::trace!("Invalid connection header: {:?}", conn); - return Err(WsClientError::InvalidConnectionHeader( - conn.clone(), - )); - } - } else { - log::trace!("Missing connection header"); - return Err(WsClientError::MissingConnectionHeader); - } - - if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { - let encoded = ws::hash_key(key.as_ref()); - if hdr_key.as_bytes() != encoded.as_bytes() { - log::trace!( - "Invalid challenge response: expected: {} received: {:?}", - encoded, - key - ); - return Err(WsClientError::InvalidChallengeResponse( - encoded, - hdr_key.clone(), - )); - } - } else { - log::trace!("Missing SEC-WEBSOCKET-ACCEPT header"); - return Err(WsClientError::MissingWebSocketAcceptHeader); - }; - - // response and ws framed - Ok(( - ClientResponse::new(head, Payload::None), - framed.map_codec(|_| { - if server_mode { - ws::Codec::new().max_size(max_size) - } else { - ws::Codec::new().max_size(max_size).client_mode() - } - }), - )) - }); + .open_tunnel(head, self.addr); // set request timeout - if let Some(timeout) = self.config.timeout { - Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| { - if let Some(e) = e.into_inner() { - e - } else { - SendRequestError::Timeout.into() - } - }))) + let (head, framed) = if let Some(timeout) = self.config.timeout { + Timeout::new(fut, timeout) + .await + .map_err(|_| SendRequestError::Timeout.into()) + .and_then(|res| res)? } else { - Either::B(Either::B(fut)) + fut.await? + }; + + // verify response + if head.status != StatusCode::SWITCHING_PROTOCOLS { + return Err(WsClientError::InvalidResponseStatus(head.status)); } + + // Check for "UPGRADE" to websocket header + let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) { + if let Ok(s) = hdr.to_str() { + s.to_ascii_lowercase().contains("websocket") + } else { + false + } + } else { + false + }; + if !has_hdr { + log::trace!("Invalid upgrade header"); + return Err(WsClientError::InvalidUpgradeHeader); + } + + // Check for "CONNECTION" header + if let Some(conn) = head.headers.get(&header::CONNECTION) { + if let Ok(s) = conn.to_str() { + if !s.to_ascii_lowercase().contains("upgrade") { + log::trace!("Invalid connection header: {}", s); + return Err(WsClientError::InvalidConnectionHeader(conn.clone())); + } + } else { + log::trace!("Invalid connection header: {:?}", conn); + return Err(WsClientError::InvalidConnectionHeader(conn.clone())); + } + } else { + log::trace!("Missing connection header"); + return Err(WsClientError::MissingConnectionHeader); + } + + if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { + let encoded = ws::hash_key(key.as_ref()); + if hdr_key.as_bytes() != encoded.as_bytes() { + log::trace!( + "Invalid challenge response: expected: {} received: {:?}", + encoded, + key + ); + return Err(WsClientError::InvalidChallengeResponse( + encoded, + hdr_key.clone(), + )); + } + } else { + log::trace!("Missing SEC-WEBSOCKET-ACCEPT header"); + return Err(WsClientError::MissingWebSocketAcceptHeader); + }; + + // response and ws framed + Ok(( + ClientResponse::new(head, Payload::None), + framed.map_codec(|_| { + if server_mode { + ws::Codec::new().max_size(max_size) + } else { + ws::Codec::new().max_size(max_size).client_mode() + } + }), + )) } } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 77301b0a..35bf1a0e 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -14,7 +14,7 @@ categories = ["network-programming", "asynchronous", license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" -workspace = ".." +# workspace = ".." [package.metadata.docs.rs] features = [] @@ -27,20 +27,22 @@ path = "src/lib.rs" default = [] # openssl -ssl = ["openssl", "actix-server/ssl", "awc/ssl"] +openssl = ["open-ssl", "actix-server/openssl", "awc/openssl"] [dependencies] -actix-codec = "0.1.2" -actix-rt = "0.2.2" -actix-service = "0.4.1" -actix-server = "0.6.0" -actix-utils = "0.4.1" -awc = "0.2.6" -actix-connect = "0.2.2" +actix-service = "1.0.0-alpha.1" +actix-codec = "0.2.0-alpha.1" +actix-connect = "1.0.0-alpha.1" +actix-utils = "0.5.0-alpha.1" +actix-rt = "1.0.0-alpha.1" +actix-server = "0.8.0-alpha.1" +actix-server-config = "0.3.0-alpha.1" +actix-testing = "0.3.0-alpha.1" +awc = "0.3.0-alpha.1" base64 = "0.10" bytes = "0.4" -futures = "0.1" +futures = "0.3.1" http = "0.1.8" log = "0.4" env_logger = "0.6" @@ -51,10 +53,25 @@ sha1 = "0.6" slab = "0.4" serde_urlencoded = "0.6.1" time = "0.1" -tokio-tcp = "0.1" -tokio-timer = "0.2" -openssl = { version="0.10", optional = true } +tokio-net = "0.2.0-alpha.6" +tokio-timer = "0.3.0-alpha.6" + +open-ssl = { version="0.10", package="openssl", optional = true } [dev-dependencies] -actix-web = "1.0.7" -actix-http = "0.2.9" +#actix-web = "1.0.7" +actix-http = "0.3.0-alpha.1" + +[patch.crates-io] +actix-http = { path = "../actix-http" } +awc = { path = "../awc" } + +actix-codec = { path = "../../actix-net/actix-codec" } +actix-connect = { path = "../../actix-net/actix-connect" } +actix-rt = { path = "../../actix-net/actix-rt" } +actix-server = { path = "../../actix-net/actix-server" } +actix-server-config = { path = "../../actix-net/actix-server-config" } +actix-service = { path = "../../actix-net/actix-service" } +actix-testing = { path = "../../actix-net/actix-testing" } +actix-threadpool = { path = "../../actix-net/actix-threadpool" } +actix-utils = { path = "../../actix-net/actix-utils" } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index ebdec688..17acfe29 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -1,73 +1,18 @@ //! Various helpers for Actix applications to use during testing. -use std::cell::RefCell; use std::sync::mpsc; use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_rt::{Runtime, System}; -use actix_server::{Server, StreamServiceFactory}; +use actix_rt::{System}; +use actix_server::{Server, ServiceFactory}; use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; use bytes::Bytes; -use futures::future::lazy; -use futures::{Future, IntoFuture, Stream}; +use futures::{Stream, future::lazy}; use http::Method; use net2::TcpBuilder; -use tokio_tcp::TcpStream; +use tokio_net::tcp::TcpStream; -thread_local! { - static RT: RefCell = { - RefCell::new(Inner(Some(Runtime::new().unwrap()))) - }; -} - -struct Inner(Option); - -impl Inner { - fn get_mut(&mut self) -> &mut Runtime { - self.0.as_mut().unwrap() - } -} - -impl Drop for Inner { - fn drop(&mut self) { - std::mem::forget(self.0.take().unwrap()) - } -} - -/// Runs the provided future, blocking the current thread until the future -/// completes. -/// -/// This function can be used to synchronously block the current thread -/// until the provided `future` has resolved either successfully or with an -/// error. The result of the future is then returned from this function -/// call. -/// -/// Note that this function is intended to be used only for testing purpose. -/// This function panics on nested call. -pub fn block_on(f: F) -> Result -where - F: IntoFuture, -{ - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f.into_future())) -} - -/// Runs the provided function, blocking the current thread until the resul -/// future completes. -/// -/// This function can be used to synchronously block the current thread -/// until the provided `future` has resolved either successfully or with an -/// error. The result of the future is then returned from this function -/// call. -/// -/// Note that this function is intended to be used only for testing purpose. -/// This function panics on nested call. -pub fn block_fn(f: F) -> Result -where - F: FnOnce() -> R, - R: IntoFuture, -{ - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(f))) -} +pub use actix_testing::*; /// The `TestServer` type. /// @@ -110,7 +55,7 @@ pub struct TestServerRuntime { impl TestServer { #[allow(clippy::new_ret_no_self)] /// Start new test server with application factory - pub fn new>(factory: F) -> TestServerRuntime { + pub fn new>(factory: F) -> TestServerRuntime { let (tx, rx) = mpsc::channel(); // run server in separate thread @@ -131,7 +76,7 @@ impl TestServer { let (system, addr) = rx.recv().unwrap(); - let client = block_on(lazy(move || { + let client = block_on(lazy(move |_| { let connector = { #[cfg(feature = "ssl")] { @@ -161,9 +106,9 @@ impl TestServer { })) .unwrap(); - block_on(lazy( - || Ok::<_, ()>(actix_connect::start_default_resolver()), - )) + block_on(lazy(|_| { + Ok::<_, ()>(actix_connect::start_default_resolver()) + })) .unwrap(); TestServerRuntime { @@ -185,31 +130,6 @@ impl TestServer { } impl TestServerRuntime { - /// Execute future on current core - pub fn block_on(&mut self, fut: F) -> Result - where - F: Future, - { - block_on(fut) - } - - /// Execute future on current core - pub fn block_on_fn(&mut self, f: F) -> Result - where - F: FnOnce() -> R, - R: Future, - { - block_on(lazy(f)) - } - - /// Execute function on current core - pub fn execute(&mut self, fut: F) -> R - where - F: FnOnce() -> R, - { - block_on(lazy(|| Ok::<_, ()>(fut()))).unwrap() - } - /// Construct test server url pub fn addr(&self) -> net::SocketAddr { self.addr @@ -313,9 +233,9 @@ impl TestServerRuntime { mut response: ClientResponse, ) -> Result where - S: Stream + 'static, + S: Stream> + Unpin + 'static, { - self.block_on(response.body().limit(10_485_760)) + block_on(response.body().limit(10_485_760)) } /// Connect to websocket server at a given path @@ -326,7 +246,9 @@ impl TestServerRuntime { { let url = self.url(path); let connect = self.client.ws(url).connect(); - block_on(lazy(move || connect.map(|(_, framed)| framed))) + block_on(async move { + connect.await.map(|(_, framed)| framed) + }) } /// Connect to a websocket server From 687884fb940e7d4bf535f718cc3a0bab907b27ef Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Nov 2019 11:08:03 +0600 Subject: [PATCH 1606/1635] update test-server tests --- actix-http/Cargo.toml | 7 ++- actix-http/src/client/h1proto.rs | 3 +- actix-http/src/h1/dispatcher.rs | 10 ++--- actix-http/src/h2/dispatcher.rs | 2 +- actix-http/tests/test_server.rs | 76 +++++++++++++++----------------- awc/Cargo.toml | 4 +- test-server/Cargo.toml | 2 +- test-server/src/lib.rs | 2 +- 8 files changed, 54 insertions(+), 52 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 742938d5..36f3cef4 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -109,17 +109,22 @@ webpki-roots = { version = "0.18", optional = true } actix-rt = "1.0.0-alpha.1" actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } -#actix-http-test = { version = "0.2.4", features=["ssl"] } +actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } env_logger = "0.6" serde_derive = "1.0" open-ssl = { version="0.10", package="openssl" } [patch.crates-io] +awc = { path = "../awc" } +actix-http = { path = "." } +actix-http-test = { path = "../test-server" } + actix-codec = { path = "../../actix-net/actix-codec" } actix-connect = { path = "../../actix-net/actix-connect" } actix-rt = { path = "../../actix-net/actix-rt" } actix-server = { path = "../../actix-net/actix-server" } actix-server-config = { path = "../../actix-net/actix-server-config" } actix-service = { path = "../../actix-net/actix-service" } +actix-testing = { path = "../../actix-net/actix-testing" } actix-threadpool = { path = "../../actix-net/actix-threadpool" } actix-utils = { path = "../../actix-net/actix-utils" } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index f8902a0e..041a3685 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -76,7 +76,8 @@ where }; // read response and init read body - let (head, framed) = if let (Some(result), framed) = framed.into_future().await { + let res = framed.into_future().await; + let (head, framed) = if let (Some(result), framed) = res { let item = result.map_err(SendRequestError::from)?; (item, framed) } else { diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 16e36447..a06b997e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -331,12 +331,10 @@ where Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)), } } - if written > 0 { - if written == self.write_buf.len() { - unsafe { self.write_buf.set_len(0) } - } else { - let _ = self.write_buf.split_to(written); - } + if written == self.write_buf.len() { + unsafe { self.write_buf.set_len(0) } + } else { + let _ = self.write_buf.split_to(written); } Ok(false) } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 2a44c83f..9b5b7e83 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -122,7 +122,7 @@ where match Pin::new(&mut this.connection).poll_accept(cx) { Poll::Ready(None) => return Poll::Ready(Ok(())), Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), - Poll::Ready(Some(Ok((req, res)))) => { + Poll::Ready(Some(Ok((req, _)))) => { // update keep-alive expire if this.ka_timer.is_some() { if let Some(expire) = this.config.keep_alive_expire() { diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 51ee9f2d..ef861b30 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -2,14 +2,14 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; -use actix_http_test::TestServer; +use actix_http_test::{block_fn, TestServer}; use actix_server_config::ServerConfig; use actix_service::{factory_fn_cfg, pipeline, service_fn, ServiceFactory}; use bytes::Bytes; -use futures::future::{self, err, ok, ready, Future, FutureExt}; -use futures::stream::{once, Stream, StreamExt}; +use futures::future::{self, err, ok, ready, FutureExt}; +use futures::stream::{once, StreamExt}; use regex::Regex; -use tokio_timer::sleep; +use tokio_timer::delay_for; use actix_http::httpmessage::HttpMessage; use actix_http::{ @@ -18,7 +18,7 @@ use actix_http::{ #[test] fn test_h1() { - let mut srv = TestServer::new(|| { + let srv = TestServer::new(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) @@ -29,13 +29,13 @@ fn test_h1() { }) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert!(response.status().is_success()); } #[test] fn test_h1_2() { - let mut srv = TestServer::new(|| { + let srv = TestServer::new(|| { HttpService::build() .keep_alive(KeepAlive::Disabled) .client_timeout(1000) @@ -48,7 +48,7 @@ fn test_h1_2() { .map(|_| ()) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert!(response.status().is_success()); } @@ -84,11 +84,11 @@ fn test_expect_continue_h1() { let srv = TestServer::new(|| { HttpService::build() .expect(service_fn(|req: Request| { - sleep(Duration::from_millis(20)).then(move |_| { + delay_for(Duration::from_millis(20)).then(move |_| { if req.head().uri.query() == Some("yes=") { - Ok(req) + ok(req) } else { - Err(error::ErrorPreconditionFailed("error")) + err(error::ErrorPreconditionFailed("error")) } }) })) @@ -114,7 +114,7 @@ fn test_chunked_payload() { let total_size: usize = chunk_sizes.iter().sum(); let srv = TestServer::new(|| { - HttpService::build().h1(|mut request: Request| { + HttpService::build().h1(service_fn(|mut request: Request| { request .take_payload() .map(|res| match res { @@ -122,8 +122,10 @@ fn test_chunked_payload() { Err(e) => panic!(format!("Error reading payload: {}", e)), }) .fold(0usize, |acc, chunk| ready(acc + chunk.len())) - .map(|req_size| ok(Response::Ok().body(format!("size={}", req_size)))) - }) + .map(|req_size| { + Ok::<_, Error>(Response::Ok().body(format!("size={}", req_size))) + }) + })) }); let returned_size = { @@ -310,7 +312,7 @@ fn test_content_length() { StatusCode, }; - let mut srv = TestServer::new(|| { + let srv = TestServer::new(|| { HttpService::build().h1(|req: Request| { let indx: usize = req.uri().path()[1..].parse().unwrap(); let statuses = [ @@ -330,24 +332,18 @@ fn test_content_length() { { for i in 0..4 { - let req = srv - .request(http::Method::GET, srv.url(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); + let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); + let response = block_fn(move || req.send()).unwrap(); assert_eq!(response.headers().get(&header), None); - let req = srv - .request(http::Method::HEAD, srv.url(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); + let req = srv.request(http::Method::HEAD, srv.url(&format!("/{}", i))); + let response = block_fn(move || req.send()).unwrap(); assert_eq!(response.headers().get(&header), None); } for i in 4..6 { - let req = srv - .request(http::Method::GET, srv.url(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); + let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); + let response = block_fn(move || req.send()).unwrap(); assert_eq!(response.headers().get(&header), Some(&value)); } } @@ -384,7 +380,7 @@ fn test_h1_headers() { }) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -420,7 +416,7 @@ fn test_h1_body() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -434,7 +430,7 @@ fn test_h1_head_empty() { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.head("/").send()).unwrap(); + let response = block_fn(|| srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -458,7 +454,7 @@ fn test_h1_head_binary() { }) }); - let response = srv.block_on(srv.head("/").send()).unwrap(); + let response = block_fn(|| srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -476,11 +472,11 @@ fn test_h1_head_binary() { #[test] fn test_h1_head_binary2() { - let mut srv = TestServer::new(|| { + let srv = TestServer::new(|| { HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) }); - let response = srv.block_on(srv.head("/").send()).unwrap(); + let response = block_fn(|| srv.head("/").send()).unwrap(); assert!(response.status().is_success()); { @@ -503,7 +499,7 @@ fn test_h1_body_length() { }) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert!(response.status().is_success()); // read response @@ -524,7 +520,7 @@ fn test_h1_body_chunked_explicit() { }) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -552,7 +548,7 @@ fn test_h1_body_chunked_implicit() { }) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert!(response.status().is_success()); assert_eq!( response @@ -584,7 +580,7 @@ fn test_h1_response_http_error_handling() { })) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); // read response @@ -599,7 +595,7 @@ fn test_h1_service_error() { .h1(|_| future::err::(error::ErrorBadRequest("error"))) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); // read response @@ -609,7 +605,7 @@ fn test_h1_service_error() { #[test] fn test_h1_on_connect() { - let mut srv = TestServer::new(|| { + let srv = TestServer::new(|| { HttpService::build() .on_connect(|_| 10usize) .h1(|req: Request| { @@ -618,6 +614,6 @@ fn test_h1_on_connect() { }) }); - let response = srv.block_on(srv.get("/").send()).unwrap(); + let response = block_fn(|| srv.get("/").send()).unwrap(); assert!(response.status().is_success()); } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index ea181237..f8f9a7eb 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -65,7 +65,7 @@ rust-tls = { version = "0.16.0", package="rustls", optional = true } actix-rt = "1.0.0-alpha.1" #actix-web = { version = "1.0.8", features=["ssl"] } actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } -#actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } actix-utils = "0.5.0-alpha.1" actix-server = { version = "0.8.0-alpha.1", features=["openssl", "rustls"] } brotli2 = { version="0.3.2" } @@ -77,7 +77,9 @@ webpki = { version = "0.21" } rus-tls = { version = "0.16.0", package="rustls", features = ["dangerous_configuration"] } [patch.crates-io] +awc = { path = "." } actix-http = { path = "../actix-http" } +actix-http-test = { path = "../test-server" } actix-codec = { path = "../../actix-net/actix-codec" } actix-connect = { path = "../../actix-net/actix-connect" } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 35bf1a0e..3333e048 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.2.5" +version = "0.3.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 17acfe29..4ba123aa 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -109,7 +109,7 @@ impl TestServer { block_on(lazy(|_| { Ok::<_, ()>(actix_connect::start_default_resolver()) })) - .unwrap(); + .unwrap(); TestServerRuntime { addr, From 1ffa7d18d37e293e7659562d77059b44de38d122 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Nov 2019 18:54:19 +0600 Subject: [PATCH 1607/1635] drop unpin constraint --- actix-http/Cargo.toml | 3 +- actix-http/examples/echo.rs | 34 +- actix-http/examples/echo2.rs | 29 +- actix-http/src/body.rs | 73 ++- actix-http/src/builder.rs | 55 +- actix-http/src/client/connection.rs | 60 +- actix-http/src/client/connector.rs | 129 +--- actix-http/src/client/error.rs | 8 +- actix-http/src/client/pool.rs | 35 +- actix-http/src/config.rs | 2 +- actix-http/src/error.rs | 20 +- actix-http/src/h1/dispatcher.rs | 103 +-- actix-http/src/h1/payload.rs | 27 +- actix-http/src/h1/service.rs | 95 +-- actix-http/src/h1/utils.rs | 3 +- actix-http/src/h2/dispatcher.rs | 93 ++- actix-http/src/h2/service.rs | 59 +- actix-http/src/request.rs | 5 + actix-http/src/response.rs | 4 +- actix-http/src/service.rs | 262 ++++---- actix-http/src/ws/transport.rs | 14 +- actix-http/tests/test_client.rs | 88 +-- actix-http/tests/test_openssl.rs | 545 ++++++++++++++++ actix-http/tests/test_rustls.rs | 474 ++++++++++++++ actix-http/tests/test_rustls_server.rs | 462 ------------- actix-http/tests/test_server.rs | 855 +++++++++++++------------ actix-http/tests/test_ssl_server.rs | 480 -------------- actix-http/tests/test_ws.rs | 100 +-- test-server/src/lib.rs | 37 +- tests/cert.pem | 47 +- tests/key.pem | 76 +-- 31 files changed, 2136 insertions(+), 2141 deletions(-) create mode 100644 actix-http/tests/test_openssl.rs create mode 100644 actix-http/tests/test_rustls.rs delete mode 100644 actix-http/tests/test_rustls_server.rs delete mode 100644 actix-http/tests/test_ssl_server.rs diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 36f3cef4..edc93f09 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -74,6 +74,7 @@ language-tags = "0.2" log = "0.4" mime = "0.3" percent-encoding = "2.1" +pin-project = "0.4.5" rand = "0.7" regex = "1.0" serde = "1.0" @@ -107,7 +108,7 @@ webpki-roots = { version = "0.18", optional = true } [dev-dependencies] actix-rt = "1.0.0-alpha.1" -actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } +actix-server = { version = "0.8.0-alpha.1", features=["openssl", "rustls"] } actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } env_logger = "0.6" diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index c36292c4..ba81020c 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -1,9 +1,9 @@ use std::{env, io}; -use actix_http::{error::PayloadError, HttpService, Request, Response}; +use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; -use futures::{Future, Stream}; +use futures::StreamExt; use http::header::HeaderValue; use log::info; @@ -17,20 +17,22 @@ fn main() -> io::Result<()> { .client_timeout(1000) .client_disconnect(1000) .finish(|mut req: Request| { - req.take_payload() - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, PayloadError>(body) - }) - .and_then(|bytes| { - info!("request body: {:?}", bytes); - let mut res = Response::Ok(); - res.header( - "x-head", - HeaderValue::from_static("dummy value!"), - ); - Ok(res.body(bytes)) - }) + async move { + let mut body = BytesMut::new(); + while let Some(item) = req.payload().next().await { + body.extend_from_slice(&item?); + } + + info!("request body: {:?}", body); + Ok::<_, Error>( + Response::Ok() + .header( + "x-head", + HeaderValue::from_static("dummy value!"), + ) + .body(body), + ) + } }) })? .run() diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index b239796b..3776c7d5 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -1,25 +1,22 @@ use std::{env, io}; use actix_http::http::HeaderValue; -use actix_http::{error::PayloadError, Error, HttpService, Request, Response}; +use actix_http::{Error, HttpService, Request, Response}; use actix_server::Server; use bytes::BytesMut; -use futures::{Future, Stream}; +use futures::StreamExt; use log::info; -fn handle_request(mut req: Request) -> impl Future { - req.take_payload() - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, PayloadError>(body) - }) - .from_err() - .and_then(|bytes| { - info!("request body: {:?}", bytes); - let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); - Ok(res.body(bytes)) - }) +async fn handle_request(mut req: Request) -> Result { + let mut body = BytesMut::new(); + while let Some(item) = req.payload().next().await { + body.extend_from_slice(&item?) + } + + info!("request body: {:?}", body); + Ok(Response::Ok() + .header("x-head", HeaderValue::from_static("dummy value!")) + .body(body)) } fn main() -> io::Result<()> { @@ -28,7 +25,7 @@ fn main() -> io::Result<()> { Server::build() .bind("echo", "127.0.0.1:8080", || { - HttpService::build().finish(|_req: Request| handle_request(_req)) + HttpService::build().finish(handle_request) })? .run() } diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 44e76ae0..1d3a43fe 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -5,6 +5,7 @@ use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; use futures::Stream; +use pin_project::{pin_project, project}; use crate::error::Error; @@ -31,7 +32,7 @@ impl BodySize { } /// Type that provides this trait can be streamed to a peer. -pub trait MessageBody: Unpin { +pub trait MessageBody { fn size(&self) -> BodySize; fn poll_next(&mut self, cx: &mut Context) -> Poll>>; @@ -57,6 +58,7 @@ impl MessageBody for Box { } } +#[pin_project] pub enum ResponseBody { Body(B), Other(Body), @@ -106,8 +108,13 @@ impl MessageBody for ResponseBody { impl Stream for ResponseBody { type Item = Result; + #[project] fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - self.get_mut().poll_next(cx) + #[project] + match self.project() { + ResponseBody::Body(ref mut body) => body.poll_next(cx), + ResponseBody::Other(ref mut body) => body.poll_next(cx), + } } } @@ -243,7 +250,7 @@ impl From for Body { impl From> for Body where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, { fn from(s: SizedStream) -> Body { Body::from_message(s) @@ -252,7 +259,7 @@ where impl From> for Body where - S: Stream> + Unpin + 'static, + S: Stream> + 'static, E: Into + 'static, { fn from(s: BodyStream) -> Body { @@ -350,7 +357,9 @@ impl MessageBody for String { /// Type represent streaming body. /// Response does not contain `content-length` header and appropriate transfer encoding is used. +#[pin_project] pub struct BodyStream { + #[pin] stream: S, _t: PhantomData, } @@ -368,16 +377,9 @@ where } } -impl Unpin for BodyStream -where - S: Stream> + Unpin, - E: Into, -{ -} - impl MessageBody for BodyStream where - S: Stream> + Unpin, + S: Stream>, E: Into, { fn size(&self) -> BodySize { @@ -385,7 +387,9 @@ where } fn poll_next(&mut self, cx: &mut Context) -> Poll>> { - Pin::new(&mut self.stream) + unsafe { Pin::new_unchecked(self) } + .project() + .stream .poll_next(cx) .map(|res| res.map(|res| res.map_err(std::convert::Into::into))) } @@ -393,8 +397,10 @@ where /// Type represent streaming body. This body implementation should be used /// if total size of stream is known. Data get sent as is without using transfer encoding. +#[pin_project] pub struct SizedStream { size: u64, + #[pin] stream: S, } @@ -409,20 +415,25 @@ where impl MessageBody for SizedStream where - S: Stream> + Unpin, + S: Stream>, { fn size(&self) -> BodySize { BodySize::Sized64(self.size) } fn poll_next(&mut self, cx: &mut Context) -> Poll>> { - Pin::new(&mut self.stream).poll_next(cx) + unsafe { Pin::new_unchecked(self) } + .project() + .stream + .poll_next(cx) } } #[cfg(test)] mod tests { use super::*; + use actix_http_test::block_on; + use futures::future::{lazy, poll_fn}; impl Body { pub(crate) fn get_ref(&self) -> &[u8] { @@ -450,8 +461,8 @@ mod tests { assert_eq!("test".size(), BodySize::Sized(4)); assert_eq!( - "test".poll_next().unwrap(), - Async::Ready(Some(Bytes::from("test"))) + block_on(poll_fn(|cx| "test".poll_next(cx))).unwrap().ok(), + Some(Bytes::from("test")) ); } @@ -467,8 +478,10 @@ mod tests { assert_eq!((&b"test"[..]).size(), BodySize::Sized(4)); assert_eq!( - (&b"test"[..]).poll_next().unwrap(), - Async::Ready(Some(Bytes::from("test"))) + block_on(poll_fn(|cx| (&b"test"[..]).poll_next(cx))) + .unwrap() + .ok(), + Some(Bytes::from("test")) ); } @@ -479,8 +492,10 @@ mod tests { assert_eq!(Vec::from("test").size(), BodySize::Sized(4)); assert_eq!( - Vec::from("test").poll_next().unwrap(), - Async::Ready(Some(Bytes::from("test"))) + block_on(poll_fn(|cx| Vec::from("test").poll_next(cx))) + .unwrap() + .ok(), + Some(Bytes::from("test")) ); } @@ -492,8 +507,8 @@ mod tests { assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!( - b.poll_next().unwrap(), - Async::Ready(Some(Bytes::from("test"))) + block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), + Some(Bytes::from("test")) ); } @@ -505,8 +520,8 @@ mod tests { assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!( - b.poll_next().unwrap(), - Async::Ready(Some(Bytes::from("test"))) + block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), + Some(Bytes::from("test")) ); } @@ -520,22 +535,22 @@ mod tests { assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!( - b.poll_next().unwrap(), - Async::Ready(Some(Bytes::from("test"))) + block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), + Some(Bytes::from("test")) ); } #[test] fn test_unit() { assert_eq!(().size(), BodySize::Empty); - assert_eq!(().poll_next().unwrap(), Async::Ready(None)); + assert!(block_on(poll_fn(|cx| ().poll_next(cx))).is_none()); } #[test] fn test_box() { let mut val = Box::new(()); assert_eq!(val.size(), BodySize::Empty); - assert_eq!(val.poll_next().unwrap(), Async::Ready(None)); + assert!(block_on(poll_fn(|cx| val.poll_next(cx))).is_none()); } #[test] diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 8997d720..8fa35fea 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -33,11 +33,9 @@ pub struct HttpServiceBuilder> { impl HttpServiceBuilder> where S: ServiceFactory, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, { /// Create instance of `ServiceConfigBuilder` pub fn new() -> Self { @@ -56,17 +54,13 @@ where impl HttpServiceBuilder where S: ServiceFactory, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - X::Future: Unpin, - X::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, U: ServiceFactory< Config = SrvConfig, Request = (Request, Framed), @@ -74,9 +68,7 @@ where >, U::Error: fmt::Display, U::InitError: fmt::Debug, - U::Future: Unpin, - U::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, { /// Set server keep-alive setting. /// @@ -124,9 +116,7 @@ where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - X1::Future: Unpin, - X1::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -153,9 +143,7 @@ where >, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - U1::Future: Unpin, - U1::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -186,13 +174,10 @@ where where B: MessageBody + 'static, F: IntoServiceFactory, - S::Future: Unpin, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + Unpin + 'static, - S::Service: Unpin, - ::Future: Unpin + 'static, - P: Unpin, + S::Response: Into> + 'static, + ::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, @@ -210,13 +195,10 @@ where where B: MessageBody + 'static, F: IntoServiceFactory, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, - P: Unpin, + S::Response: Into> + 'static, + ::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, @@ -231,13 +213,10 @@ where where B: MessageBody + 'static, F: IntoServiceFactory, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, - P: Unpin, + S::Response: Into> + 'static, + ::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 0901fdb2..75d393b1 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -6,6 +6,7 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use bytes::{Buf, Bytes}; use futures::future::{err, Either, Future, FutureExt, LocalBoxFuture, Ready}; use h2::client::SendRequest; +use pin_project::{pin_project, project}; use crate::body::MessageBody; use crate::h1::ClientCodec; @@ -42,9 +43,7 @@ pub trait Connection { fn open_tunnel>(self, head: H) -> Self::TunnelFuture; } -pub(crate) trait ConnectionLifetime: - AsyncRead + AsyncWrite + Unpin + 'static -{ +pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { /// Close connection fn close(&mut self); @@ -73,7 +72,7 @@ where } } -impl IoConnection { +impl IoConnection { pub(crate) fn new( io: ConnectionType, created: time::Instant, @@ -205,24 +204,27 @@ where } } +#[pin_project] pub enum EitherIo { - A(A), - B(B), + A(#[pin] A), + B(#[pin] B), } impl AsyncRead for EitherIo where - A: AsyncRead + Unpin, - B: AsyncRead + Unpin, + A: AsyncRead, + B: AsyncRead, { + #[project] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - match self.get_mut() { - EitherIo::A(ref mut val) => Pin::new(val).poll_read(cx, buf), - EitherIo::B(ref mut val) => Pin::new(val).poll_read(cx, buf), + #[project] + match self.project() { + EitherIo::A(val) => val.poll_read(cx, buf), + EitherIo::B(val) => val.poll_read(cx, buf), } } @@ -236,37 +238,44 @@ where impl AsyncWrite for EitherIo where - A: AsyncWrite + Unpin, - B: AsyncWrite + Unpin, + A: AsyncWrite, + B: AsyncWrite, { + #[project] fn poll_write( self: Pin<&mut Self>, cx: &mut Context, buf: &[u8], ) -> Poll> { - match self.get_mut() { - EitherIo::A(ref mut val) => Pin::new(val).poll_write(cx, buf), - EitherIo::B(ref mut val) => Pin::new(val).poll_write(cx, buf), + #[project] + match self.project() { + EitherIo::A(val) => val.poll_write(cx, buf), + EitherIo::B(val) => val.poll_write(cx, buf), } } + #[project] fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - EitherIo::A(ref mut val) => Pin::new(val).poll_flush(cx), - EitherIo::B(ref mut val) => Pin::new(val).poll_flush(cx), + #[project] + match self.project() { + EitherIo::A(val) => val.poll_flush(cx), + EitherIo::B(val) => val.poll_flush(cx), } } + #[project] fn poll_shutdown( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - match self.get_mut() { - EitherIo::A(ref mut val) => Pin::new(val).poll_shutdown(cx), - EitherIo::B(ref mut val) => Pin::new(val).poll_shutdown(cx), + #[project] + match self.project() { + EitherIo::A(val) => val.poll_shutdown(cx), + EitherIo::B(val) => val.poll_shutdown(cx), } } + #[project] fn poll_write_buf( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -275,9 +284,10 @@ where where Self: Sized, { - match self.get_mut() { - EitherIo::A(ref mut val) => Pin::new(val).poll_write_buf(cx, buf), - EitherIo::B(ref mut val) => Pin::new(val).poll_write_buf(cx, buf), + #[project] + match self.project() { + EitherIo::A(val) => val.poll_write_buf(cx, buf), + EitherIo::B(val) => val.poll_write_buf(cx, buf), } } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 45625ca9..1895f530 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -62,8 +62,8 @@ pub struct Connector { _t: PhantomData, } -trait Io: AsyncRead + AsyncWrite {} -impl Io for T {} +trait Io: AsyncRead + AsyncWrite + Unpin {} +impl Io for T {} impl Connector<(), ()> { #[allow(clippy::new_ret_no_self)] @@ -123,7 +123,6 @@ impl Connector { Response = TcpConnection, Error = actix_connect::ConnectError, > + Clone, - T1::Future: Unpin, { Connector { connector, @@ -222,7 +221,7 @@ where { let connector = TimeoutService::new( self.timeout, - apply_fn(UnpinWrapper(self.connector), |msg: Connect, srv| { + apply_fn(self.connector, |msg: Connect, srv| { srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) }) .map_err(ConnectError::from) @@ -257,35 +256,33 @@ where let ssl_service = TimeoutService::new( self.timeout, pipeline( - apply_fn( - UnpinWrapper(self.connector.clone()), - |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) - }, - ) + apply_fn(self.connector.clone(), |msg: Connect, srv| { + srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) + }) .map_err(ConnectError::from), ) .and_then(match self.ssl { #[cfg(feature = "openssl")] - SslConnector::Openssl(ssl) => OpensslConnector::service(ssl) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (Box::new(sock) as Box, Protocol::Http2) - } else { - (Box::new(sock) as Box, Protocol::Http1) - } - }) - .map_err(ConnectError::from), - + SslConnector::Openssl(ssl) => service( + OpensslConnector::service(ssl) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock) as Box, Protocol::Http2) + } else { + (Box::new(sock) as Box, Protocol::Http1) + } + }) + .map_err(ConnectError::from), + ), #[cfg(feature = "rustls")] SslConnector::Rustls(ssl) => service( - UnpinWrapper(RustlsConnector::service(ssl)) + RustlsConnector::service(ssl) .map_err(ConnectError::from) .map(|stream| { let sock = stream.into_parts().0; @@ -296,15 +293,9 @@ where .map(|protos| protos.windows(2).any(|w| w == H2)) .unwrap_or(false); if h2 { - ( - Box::new(sock) as Box, - Protocol::Http2, - ) + (Box::new(sock) as Box, Protocol::Http2) } else { - ( - Box::new(sock) as Box, - Protocol::Http1, - ) + (Box::new(sock) as Box, Protocol::Http1) } }), ), @@ -317,7 +308,7 @@ where let tcp_service = TimeoutService::new( self.timeout, - apply_fn(UnpinWrapper(self.connector), |msg: Connect, srv| { + apply_fn(self.connector, |msg: Connect, srv| { srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) }) .map_err(ConnectError::from) @@ -348,42 +339,6 @@ where } } -#[derive(Clone)] -struct UnpinWrapper(T); - -impl Unpin for UnpinWrapper {} - -impl Service for UnpinWrapper { - type Request = T::Request; - type Response = T::Response; - type Error = T::Error; - type Future = UnpinWrapperFut; - - fn poll_ready(&mut self, cx: &mut Context) -> Poll> { - self.0.poll_ready(cx) - } - - fn call(&mut self, req: T::Request) -> Self::Future { - UnpinWrapperFut { - fut: self.0.call(req), - } - } -} - -struct UnpinWrapperFut { - fut: T::Future, -} - -impl Unpin for UnpinWrapperFut {} - -impl Future for UnpinWrapperFut { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - unsafe { Pin::new_unchecked(&mut self.get_mut().fut) }.poll(cx) - } -} - #[cfg(not(any(feature = "openssl", feature = "rustls")))] mod connect_impl { use std::task::{Context, Poll}; @@ -396,9 +351,8 @@ mod connect_impl { pub(crate) struct InnerConnector where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Unpin + 'static, { pub(crate) tcp_pool: ConnectionPool, @@ -406,9 +360,8 @@ mod connect_impl { impl Clone for InnerConnector where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Unpin + 'static, { fn clone(&self) -> Self { @@ -422,9 +375,7 @@ mod connect_impl { where Io: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Unpin + 'static, - T::Future: Unpin, { type Request = Connect; type Response = IoConnection; @@ -465,8 +416,6 @@ mod connect_impl { Io2: AsyncRead + AsyncWrite + Unpin + 'static, T1: Service, T2: Service, - T1::Future: Unpin, - T2::Future: Unpin, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -477,13 +426,9 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, T1: Service - + Unpin + 'static, T2: Service - + Unpin + 'static, - T1::Future: Unpin, - T2::Future: Unpin, { fn clone(&self) -> Self { InnerConnector { @@ -498,13 +443,9 @@ mod connect_impl { Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, T1: Service - + Unpin + 'static, T2: Service - + Unpin + 'static, - T1::Future: Unpin, - T2::Future: Unpin, { type Request = Connect; type Response = EitherConnection; @@ -532,14 +473,14 @@ mod connect_impl { } } + #[pin_project::pin_project] pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Unpin + 'static, - T::Future: Unpin, { + #[pin] fut: as Service>::Future, _t: PhantomData, } @@ -547,9 +488,7 @@ mod connect_impl { impl Future for InnerConnectorResponseA where T: Service - + Unpin + 'static, - T::Future: Unpin, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, { @@ -563,14 +502,14 @@ mod connect_impl { } } + #[pin_project::pin_project] pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Unpin + 'static, - T::Future: Unpin, { + #[pin] fut: as Service>::Future, _t: PhantomData, } @@ -578,9 +517,7 @@ mod connect_impl { impl Future for InnerConnectorResponseB where T: Service - + Unpin + 'static, - T::Future: Unpin, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, { diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 0ac5f30f..75f7935f 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -3,8 +3,8 @@ use std::io; use derive_more::{Display, From}; use trust_dns_resolver::error::ResolveError; -#[cfg(feature = "ssl")] -use openssl::ssl::{Error as SslError, HandshakeError}; +#[cfg(feature = "openssl")] +use open_ssl::ssl::{Error as SslError, HandshakeError}; use crate::error::{Error, ParseError, ResponseError}; use crate::http::Error as HttpError; @@ -18,7 +18,7 @@ pub enum ConnectError { SslIsNotSupported, /// SSL error - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] #[display(fmt = "{}", _0)] SslError(SslError), @@ -63,7 +63,7 @@ impl From for ConnectError { } } -#[cfg(feature = "ssl")] +#[cfg(feature = "openssl")] impl From> for ConnectError { fn from(err: HandshakeError) -> ConnectError { match err { diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index ee4c4ab9..1952dca5 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -46,11 +46,9 @@ pub(crate) struct ConnectionPool(Rc>, Rc ConnectionPool where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Unpin + 'static, - T::Future: Unpin, { pub(crate) fn new( connector: T, @@ -89,9 +87,7 @@ impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + Unpin + 'static, T: Service - + Unpin + 'static, - T::Future: Unpin, { type Request = Connect; type Response = IoConnection; @@ -400,7 +396,7 @@ struct CloseConnection { impl CloseConnection where - T: AsyncWrite, + T: AsyncWrite + Unpin, { fn new(io: T, timeout: Duration) -> Self { CloseConnection { @@ -416,10 +412,12 @@ where { type Output = (); - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { - match Pin::new(&mut self.timeout).poll(cx) { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { + let this = self.get_mut(); + + match Pin::new(&mut this.timeout).poll(cx) { Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => match Pin::new(&mut self.io).poll_shutdown(cx) { + Poll::Pending => match Pin::new(&mut this.io).poll_shutdown(cx) { Poll::Ready(_) => Poll::Ready(()), Poll::Pending => Poll::Pending, }, @@ -429,7 +427,7 @@ where struct ConnectorPoolSupport where - Io: AsyncRead + AsyncWrite + 'static, + Io: AsyncRead + AsyncWrite + Unpin + 'static, { connector: T, inner: Rc>>, @@ -438,14 +436,13 @@ where impl Future for ConnectorPoolSupport where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + Unpin, - T::Future: Unpin + 'static, + T: Service, + T::Future: 'static, { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.get_mut(); + let this = unsafe { self.get_unchecked_mut() }; let mut inner = this.inner.as_ref().borrow_mut(); inner.waker.register(cx.waker()); @@ -512,7 +509,7 @@ where impl OpenWaitingConnection where - F: Future> + Unpin + 'static, + F: Future> + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static, { fn spawn( @@ -546,13 +543,13 @@ where impl Future for OpenWaitingConnection where - F: Future> + Unpin, + F: Future>, Io: AsyncRead + AsyncWrite + Unpin, { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.get_mut(); + let this = unsafe { self.get_unchecked_mut() }; if let Some(ref mut h2) = this.h2 { return match Pin::new(h2).poll(cx) { @@ -577,7 +574,7 @@ where }; } - match Pin::new(&mut this.fut).poll(cx) { + match unsafe { Pin::new_unchecked(&mut this.fut) }.poll(cx) { Poll::Ready(Err(err)) => { let _ = this.inner.take(); if let Some(rx) = this.rx.take() { @@ -596,7 +593,7 @@ where Poll::Ready(()) } else { this.h2 = Some(handshake(io).boxed_local()); - Pin::new(this).poll(cx) + unsafe { Pin::new_unchecked(this) }.poll(cx) } } Poll::Pending => Poll::Pending, diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index a2dab8f0..488e4d98 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -277,7 +277,7 @@ mod tests { fn test_date() { let mut rt = System::new("test"); - let _ = rt.block_on(future::lazy(|| { + let _ = rt.block_on(future::lazy(|_| { let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); settings.set_date(&mut buf1); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 82027dbe..2a683399 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -181,13 +181,13 @@ impl ResponseError for FormError {} /// `InternalServerError` for `TimerError` impl ResponseError for TimerError {} -#[cfg(feature = "ssl")] +#[cfg(feature = "openssl")] /// `InternalServerError` for `openssl::ssl::Error` -impl ResponseError for openssl::ssl::Error {} +impl ResponseError for open_ssl::ssl::Error {} -#[cfg(feature = "ssl")] +#[cfg(feature = "openssl")] /// `InternalServerError` for `openssl::ssl::HandshakeError` -impl ResponseError for openssl::ssl::HandshakeError {} +impl ResponseError for open_ssl::ssl::HandshakeError {} /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { @@ -383,12 +383,12 @@ impl ResponseError for PayloadError { } } -// /// Return `BadRequest` for `cookie::ParseError` -// impl ResponseError for crate::cookie::ParseError { -// fn error_response(&self) -> Response { -// Response::new(StatusCode::BAD_REQUEST) -// } -// } +/// Return `BadRequest` for `cookie::ParseError` +impl ResponseError for crate::cookie::ParseError { + fn error_response(&self) -> Response { + Response::new(StatusCode::BAD_REQUEST) + } +} #[derive(Debug, Display, From)] /// A set of errors that can occur during dispatching http requests diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index a06b997e..8c089602 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -48,14 +48,11 @@ pub struct Dispatcher where S: Service, S::Error: Into, - S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, - X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, { inner: DispatcherState, } @@ -64,14 +61,11 @@ enum DispatcherState where S: Service, S::Error: Into, - S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, - X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, { Normal(InnerDispatcher), Upgrade(U::Future), @@ -82,14 +76,11 @@ struct InnerDispatcher where S: Service, S::Error: Into, - S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, - X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, { service: CloneableService, expect: CloneableService, @@ -181,14 +172,11 @@ where S: Service, S::Error: Into, S::Response: Into>, - S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, - X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, { /// Create http/1 dispatcher. pub(crate) fn new( @@ -269,14 +257,11 @@ where S: Service, S::Error: Into, S::Response: Into>, - S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, - X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, { fn can_read(&self, cx: &mut Context) -> bool { if self @@ -312,7 +297,9 @@ where let len = self.write_buf.len(); let mut written = 0; while written < len { - match Pin::new(&mut self.io).poll_write(cx, &self.write_buf[written..]) { + match unsafe { Pin::new_unchecked(&mut self.io) } + .poll_write(cx, &self.write_buf[written..]) + { Poll::Ready(Ok(0)) => { return Err(DispatchError::Io(io::Error::new( io::ErrorKind::WriteZero, @@ -383,32 +370,36 @@ where } None => None, }, - State::ExpectCall(ref mut fut) => match Pin::new(fut).poll(cx) { - Poll::Ready(Ok(req)) => { - self.send_continue(); - self.state = State::ServiceCall(self.service.call(req)); - continue; + State::ExpectCall(ref mut fut) => { + match unsafe { Pin::new_unchecked(fut) }.poll(cx) { + Poll::Ready(Ok(req)) => { + self.send_continue(); + self.state = State::ServiceCall(self.service.call(req)); + continue; + } + Poll::Ready(Err(e)) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + Some(self.send_response(res, body.into_body())?) + } + Poll::Pending => None, } - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - Some(self.send_response(res, body.into_body())?) + } + State::ServiceCall(ref mut fut) => { + match unsafe { Pin::new_unchecked(fut) }.poll(cx) { + Poll::Ready(Ok(res)) => { + let (res, body) = res.into().replace_body(()); + self.state = self.send_response(res, body)?; + continue; + } + Poll::Ready(Err(e)) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + Some(self.send_response(res, body.into_body())?) + } + Poll::Pending => None, } - Poll::Pending => None, - }, - State::ServiceCall(ref mut fut) => match Pin::new(fut).poll(cx) { - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); - self.state = self.send_response(res, body)?; - continue; - } - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - Some(self.send_response(res, body.into_body())?) - } - Poll::Pending => None, - }, + } State::SendPayload(ref mut stream) => { loop { if self.write_buf.len() < HW_BUFFER_SIZE { @@ -472,7 +463,7 @@ where // Handle `EXPECT: 100-Continue` header let req = if req.head().expect() { let mut task = self.expect.call(req); - match Pin::new(&mut task).poll(cx) { + match unsafe { Pin::new_unchecked(&mut task) }.poll(cx) { Poll::Ready(Ok(req)) => { self.send_continue(); req @@ -491,7 +482,7 @@ where // Call service let mut task = self.service.call(req); - match Pin::new(&mut task).poll(cx) { + match unsafe { Pin::new_unchecked(&mut task) }.poll(cx) { Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); self.send_response(res, body) @@ -689,26 +680,37 @@ where } } +impl Unpin for Dispatcher +where + T: IoStream, + S: Service, + S::Error: Into, + S::Response: Into>, + B: MessageBody, + X: Service, + X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, +{ +} + impl Future for Dispatcher where T: IoStream, S: Service, S::Error: Into, S::Response: Into>, - S::Future: Unpin, B: MessageBody, X: Service, X::Error: Into, - X::Future: Unpin, U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, { type Output = Result<(), DispatchError>; #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - match self.inner { + match self.as_mut().inner { DispatcherState::Normal(ref mut inner) => { inner.poll_keepalive(cx)?; @@ -818,7 +820,7 @@ where } } DispatcherState::Upgrade(ref mut fut) => { - Pin::new(fut).poll(cx).map_err(|e| { + unsafe { Pin::new_unchecked(fut) }.poll(cx).map_err(|e| { error!("Upgrade handler error: {}", e); DispatchError::Upgrade }) @@ -894,7 +896,7 @@ mod tests { #[test] fn test_req_parse_err() { let mut sys = actix_rt::System::new("test"); - let _ = sys.block_on(lazy(|| { + let _ = sys.block_on(lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -907,7 +909,10 @@ mod tests { None, None, ); - assert!(h1.poll().is_err()); + match Pin::new(&mut h1).poll(cx) { + Poll::Pending => panic!(), + Poll::Ready(res) => assert!(res.is_err()), + } if let DispatcherState::Normal(ref inner) = h1.inner { assert!(inner.flags.contains(Flags::READ_DISCONNECT)); diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 20ff830e..2b52cfd8 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -228,26 +228,23 @@ impl Inner { mod tests { use super::*; use actix_rt::Runtime; - use futures::future::{lazy, result}; + use futures::future::{poll_fn, ready}; #[test] fn test_unread_data() { - Runtime::new() - .unwrap() - .block_on(async { - let (_, mut payload) = Payload::create(false); + Runtime::new().unwrap().block_on(async { + let (_, mut payload) = Payload::create(false); - payload.unread_data(Bytes::from("data")); - assert!(!payload.is_empty()); - assert_eq!(payload.len(), 4); + payload.unread_data(Bytes::from("data")); + assert!(!payload.is_empty()); + assert_eq!(payload.len(), 4); - assert_eq!( - Poll::Ready(Some(Bytes::from("data"))), - payload.next_item().await.ok().unwrap() - ); + assert_eq!( + Bytes::from("data"), + poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap() + ); - result(()) - }) - .unwrap(); + ready(()) + }); } } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 95596af7..ce8ff662 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -39,11 +39,7 @@ where S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin, B: MessageBody, - P: Unpin, { /// Create new `HttpService` instance with default config. pub fn new>(service: F) -> Self { @@ -81,20 +77,13 @@ where S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin, B: MessageBody, - P: Unpin, { pub fn expect(self, expect: X1) -> H1Service where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - X1::Future: Unpin, - X1::Service: Unpin, - ::Future: Unpin, { H1Service { expect, @@ -111,9 +100,6 @@ where U1: ServiceFactory), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - U1::Future: Unpin, - U1::Service: Unpin, - ::Future: Unpin, { H1Service { upgrade, @@ -139,20 +125,13 @@ impl ServiceFactory for H1Service where T: IoStream, S: ServiceFactory, - S::Service: Unpin, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin, B: MessageBody, X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - X::Future: Unpin, - X::Service: Unpin, - ::Future: Unpin, U: ServiceFactory< Config = SrvConfig, Request = (Request, Framed), @@ -160,10 +139,6 @@ where >, U::Error: fmt::Display, U::InitError: fmt::Debug, - U::Future: Unpin, - U::Service: Unpin, - ::Future: Unpin, - P: Unpin, { type Config = SrvConfig; type Request = Io; @@ -188,30 +163,24 @@ where } #[doc(hidden)] +#[pin_project::pin_project] pub struct H1ServiceResponse where S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin, X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - X::Future: Unpin, - X::Service: Unpin, - ::Future: Unpin, U: ServiceFactory), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - U::Future: Unpin, - U::Service: Unpin, - ::Future: Unpin, - P: Unpin, { + #[pin] fut: S::Future, + #[pin] fut_ex: Option, + #[pin] fut_upg: Option, expect: Option, upgrade: Option, @@ -227,51 +196,45 @@ where S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin, B: MessageBody, X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - X::Future: Unpin, - X::Service: Unpin, - ::Future: Unpin, U: ServiceFactory), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - U::Future: Unpin, - U::Service: Unpin, - ::Future: Unpin, - P: Unpin, { type Output = Result, ()>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.get_mut(); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let mut this = self.as_mut().project(); - if let Some(ref mut fut) = this.fut_ex { - let expect = ready!(Pin::new(fut) + if let Some(fut) = this.fut_ex.as_pin_mut() { + let expect = ready!(fut .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this.expect = Some(expect); - this.fut_ex.take(); + this = self.as_mut().project(); + *this.expect = Some(expect); + this.fut_ex.set(None); } - if let Some(ref mut fut) = this.fut_upg { - let upgrade = ready!(Pin::new(fut) + if let Some(fut) = this.fut_upg.as_pin_mut() { + let upgrade = ready!(fut .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this.upgrade = Some(upgrade); - this.fut_ex.take(); + this = self.as_mut().project(); + *this.upgrade = Some(upgrade); + this.fut_ex.set(None); } - let result = ready!(Pin::new(&mut this.fut) + let result = ready!(this + .fut .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e))); Poll::Ready(result.map(|service| { + let this = self.as_mut().project(); H1ServiceHandler::new( this.cfg.take().unwrap(), service, @@ -295,18 +258,14 @@ pub struct H1ServiceHandler { impl H1ServiceHandler where - S: Service + Unpin, + S: Service, S::Error: Into, S::Response: Into>, - S::Future: Unpin, B: MessageBody, - X: Service + Unpin, - X::Future: Unpin, + X: Service, X::Error: Into, - U: Service), Response = ()> + Unpin, - U::Future: Unpin, + U: Service), Response = ()>, U::Error: fmt::Display, - P: Unpin, { fn new( cfg: ServiceConfig, @@ -329,18 +288,14 @@ where impl Service for H1ServiceHandler where T: IoStream, - S: Service + Unpin, + S: Service, S::Error: Into, S::Response: Into>, - S::Future: Unpin, B: MessageBody, - X: Service + Unpin, + X: Service, X::Error: Into, - X::Future: Unpin, - U: Service), Response = ()> + Unpin, + U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, - P: Unpin, { type Request = Io; type Response = (); diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index a992089c..7057bf1c 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -11,6 +11,7 @@ use crate::h1::{Codec, Message}; use crate::response::Response; /// Send http/1 response +#[pin_project::pin_project] pub struct SendResponse { res: Option, BodySize)>>, body: Option>, @@ -34,7 +35,7 @@ where impl Future for SendResponse where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite, B: MessageBody, { type Output = Result, Error>; diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 9b5b7e83..96775b98 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -35,6 +35,7 @@ use crate::response::Response; const CHUNK_SIZE: usize = 16_384; /// Dispatcher for HTTP/2 protocol +#[pin_project::pin_project] pub struct Dispatcher, B: MessageBody> { service: CloneableService, connection: Connection, @@ -46,24 +47,13 @@ pub struct Dispatcher, B: MessageBody _t: PhantomData, } -impl Unpin for Dispatcher -where - T: IoStream, - S: Service, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, - B: MessageBody + 'static, -{ -} - impl Dispatcher where T: IoStream, S: Service, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody + 'static, { pub(crate) fn new( @@ -107,9 +97,9 @@ impl Future for Dispatcher where T: IoStream, S: Service, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody + 'static, { type Output = Result<(), DispatchError>; @@ -122,7 +112,7 @@ where match Pin::new(&mut this.connection).poll_accept(cx) { Poll::Ready(None) => return Poll::Ready(Ok(())), Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), - Poll::Ready(Some(Ok((req, _)))) => { + Poll::Ready(Some(Ok((req, res)))) => { // update keep-alive expire if this.ka_timer.is_some() { if let Some(expire) = this.config.keep_alive_expire() { @@ -131,7 +121,6 @@ where } let (parts, body) = req.into_parts(); - // let b: () = body; let mut req = Request::with_payload(Payload::< crate::payload::PayloadStream, >::H2( @@ -150,20 +139,20 @@ where on_connect.set(&mut req.extensions_mut()); } - // tokio_executor::current_thread::spawn(ServiceResponse::< - // S::Future, - // S::Response, - // S::Error, - // B, - // > { - // state: ServiceResponseState::ServiceCall( - // this.service.call(req), - // Some(res), - // ), - // config: this.config.clone(), - // buffer: None, - // _t: PhantomData, - // }); + tokio_executor::current_thread::spawn(ServiceResponse::< + S::Future, + S::Response, + S::Error, + B, + > { + state: ServiceResponseState::ServiceCall( + this.service.call(req), + Some(res), + ), + config: this.config.clone(), + buffer: None, + _t: PhantomData, + }); } Poll::Pending => return Poll::Pending, } @@ -171,6 +160,7 @@ where } } +#[pin_project::pin_project] struct ServiceResponse { state: ServiceResponseState, config: ServiceConfig, @@ -185,9 +175,9 @@ enum ServiceResponseState { impl ServiceResponse where - F: Future> + Unpin, - E: Into + Unpin + 'static, - I: Into> + Unpin + 'static, + F: Future>, + E: Into + 'static, + I: Into> + 'static, B: MessageBody + 'static, { fn prepare_response( @@ -253,25 +243,27 @@ where impl Future for ServiceResponse where - F: Future> + Unpin, - E: Into + Unpin + 'static, - I: Into> + Unpin + 'static, + F: Future>, + E: Into + 'static, + I: Into> + 'static, B: MessageBody + 'static, { type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.get_mut(); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let mut this = self.as_mut().project(); match this.state { ServiceResponseState::ServiceCall(ref mut call, ref mut send) => { - match Pin::new(call).poll(cx) { + match unsafe { Pin::new_unchecked(call) }.poll(cx) { Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); let mut send = send.take().unwrap(); let mut size = body.size(); - let h2_res = this.prepare_response(res.head(), &mut size); + let h2_res = + self.as_mut().prepare_response(res.head(), &mut size); + this = self.as_mut().project(); let stream = match send.send_response(h2_res, size.is_eof()) { Err(e) => { @@ -284,8 +276,9 @@ where if size.is_eof() { Poll::Ready(()) } else { - this.state = ServiceResponseState::SendPayload(stream, body); - Pin::new(this).poll(cx) + *this.state = + ServiceResponseState::SendPayload(stream, body); + self.poll(cx) } } Poll::Pending => Poll::Pending, @@ -295,7 +288,9 @@ where let mut send = send.take().unwrap(); let mut size = body.size(); - let h2_res = this.prepare_response(res.head(), &mut size); + let h2_res = + self.as_mut().prepare_response(res.head(), &mut size); + this = self.as_mut().project(); let stream = match send.send_response(h2_res, size.is_eof()) { Err(e) => { @@ -308,11 +303,11 @@ where if size.is_eof() { Poll::Ready(()) } else { - this.state = ServiceResponseState::SendPayload( + *this.state = ServiceResponseState::SendPayload( stream, body.into_body(), ); - Pin::new(this).poll(cx) + self.poll(cx) } } } @@ -356,7 +351,7 @@ where chunk.len(), CHUNK_SIZE, )); - this.buffer = Some(chunk); + *this.buffer = Some(chunk); } Poll::Ready(Some(Err(e))) => { error!("Response payload stream error: {:?}", e); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 559c9930..860a61f7 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -37,12 +37,10 @@ pub struct H2Service { impl H2Service where S: ServiceFactory, - S::Error: Into + Unpin + 'static, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - ::Future: Unpin + 'static, + S::Error: Into + 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody + 'static, - P: Unpin, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -83,12 +81,10 @@ impl ServiceFactory for H2Service where T: IoStream, S: ServiceFactory, - S::Error: Into + Unpin + 'static, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - ::Future: Unpin + 'static, + S::Error: Into + 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody + 'static, - P: Unpin, { type Config = SrvConfig; type Request = Io; @@ -109,7 +105,9 @@ where } #[doc(hidden)] +#[pin_project::pin_project] pub struct H2ServiceResponse { + #[pin] fut: S::Future, cfg: Option, on_connect: Option Box>>, @@ -120,19 +118,18 @@ impl Future for H2ServiceResponse where T: IoStream, S: ServiceFactory, - S::Error: Into + Unpin + 'static, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - ::Future: Unpin + 'static, + S::Error: Into + 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody + 'static, - P: Unpin, { type Output = Result, S::InitError>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.get_mut(); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.as_mut().project(); - Poll::Ready(ready!(Pin::new(&mut this.fut).poll(cx)).map(|service| { + Poll::Ready(ready!(this.fut.poll(cx)).map(|service| { + let this = self.as_mut().project(); H2ServiceHandler::new( this.cfg.take().unwrap(), this.on_connect.clone(), @@ -153,11 +150,10 @@ pub struct H2ServiceHandler { impl H2ServiceHandler where S: Service, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody + 'static, - P: Unpin, { fn new( cfg: ServiceConfig, @@ -177,11 +173,10 @@ impl Service for H2ServiceHandler where T: IoStream, S: Service, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody + 'static, - P: Unpin, { type Request = Io; type Response = (); @@ -235,9 +230,9 @@ pub struct H2ServiceHandlerResponse where T: IoStream, S: Service, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody + 'static, { state: State, @@ -247,9 +242,9 @@ impl Future for H2ServiceHandlerResponse where T: IoStream, S: Service, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody, { type Output = Result<(), DispatchError>; diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index e9252a82..0afa45cb 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -80,6 +80,11 @@ impl

    Request

    { ) } + /// Get request's payload + pub fn payload(&mut self) -> &mut Payload

    { + &mut self.payload + } + /// Get request's payload pub fn take_payload(&mut self) -> Payload

    { std::mem::replace(&mut self.payload, Payload::None) diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 5b3d17cb..d05505d8 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -635,8 +635,8 @@ impl ResponseBuilder { /// `ResponseBuilder` can not be used after this call. pub fn streaming(&mut self, stream: S) -> Response where - S: Stream> + Unpin + 'static, - E: Into + Unpin + 'static, + S: Stream> + 'static, + E: Into + 'static, { self.body(Body::from_message(BodyStream::new(stream))) } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 65a0c7bd..e18b1013 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -11,6 +11,7 @@ use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::{ready, Future}; use h2::server::{self, Handshake}; +use pin_project::{pin_project, project}; use crate::body::MessageBody; use crate::builder::HttpServiceBuilder; @@ -35,12 +36,10 @@ pub struct HttpService HttpService where S: ServiceFactory, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody + 'static, { /// Create builder for `HttpService` instance. @@ -52,14 +51,11 @@ where impl HttpService where S: ServiceFactory, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody + 'static, - P: Unpin, { /// Create new `HttpService` instance. pub fn new>(service: F) -> Self { @@ -94,14 +90,11 @@ where impl HttpService where S: ServiceFactory, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody, - P: Unpin, { /// Provide service for `EXPECT: 100-Continue` support. /// @@ -113,9 +106,7 @@ where X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - X1::Future: Unpin, - X1::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, { HttpService { expect, @@ -140,9 +131,7 @@ where >, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - U1::Future: Unpin, - U1::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, { HttpService { upgrade, @@ -166,22 +155,17 @@ where impl ServiceFactory for HttpService where - T: IoStream + Unpin, + T: IoStream, S: ServiceFactory, - S::Service: Unpin, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - X::Future: Unpin, - X::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, U: ServiceFactory< Config = SrvConfig, Request = (Request, Framed), @@ -189,10 +173,7 @@ where >, U::Error: fmt::Display, U::InitError: fmt::Debug, - U::Future: Unpin, - U::Service: Unpin, - ::Future: Unpin + 'static, - P: Unpin, + ::Future: 'static, { type Config = SrvConfig; type Request = ServerIo; @@ -217,6 +198,7 @@ where } #[doc(hidden)] +#[pin_project] pub struct HttpServiceResponse< T, P, @@ -225,8 +207,11 @@ pub struct HttpServiceResponse< X: ServiceFactory, U: ServiceFactory, > { + #[pin] fut: S::Future, + #[pin] fut_ex: Option, + #[pin] fut_upg: Option, expect: Option, upgrade: Option, @@ -239,53 +224,50 @@ impl Future for HttpServiceResponse where T: IoStream, S: ServiceFactory, - S::Error: Into + Unpin + 'static, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, - S::Service: Unpin, - ::Future: Unpin + 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody + 'static, X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - X::Future: Unpin, - X::Service: Unpin, - ::Future: Unpin + 'static, + ::Future: 'static, U: ServiceFactory), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - U::Future: Unpin, - U::Service: Unpin, - ::Future: Unpin + 'static, - P: Unpin, + ::Future: 'static, { type Output = Result, ()>; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.get_mut(); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let mut this = self.as_mut().project(); - if let Some(ref mut fut) = this.fut_ex { - let expect = ready!(Pin::new(fut) + if let Some(fut) = this.fut_ex.as_pin_mut() { + let expect = ready!(fut .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this.expect = Some(expect); - this.fut_ex.take(); + this = self.as_mut().project(); + *this.expect = Some(expect); + this.fut_ex.set(None); } - if let Some(ref mut fut) = this.fut_upg { - let upgrade = ready!(Pin::new(fut) + if let Some(fut) = this.fut_upg.as_pin_mut() { + let upgrade = ready!(fut .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this.upgrade = Some(upgrade); - this.fut_ex.take(); + this = self.as_mut().project(); + *this.upgrade = Some(upgrade); + this.fut_ex.set(None); } - let result = ready!(Pin::new(&mut this.fut) + let result = ready!(this + .fut .poll(cx) .map_err(|e| log::error!("Init http service error: {:?}", e))); Poll::Ready(result.map(|service| { + let this = self.as_mut().project(); HttpServiceHandler::new( this.cfg.take().unwrap(), service, @@ -309,19 +291,15 @@ pub struct HttpServiceHandler { impl HttpServiceHandler where - S: Service + Unpin, - S::Error: Into + Unpin + 'static, + S: Service, + S::Error: Into + 'static, S::Future: 'static, - S::Response: Into> + Unpin + 'static, - S::Future: Unpin, + S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service + Unpin, - X::Future: Unpin, + X: Service, X::Error: Into, - U: Service), Response = ()> + Unpin, - U::Future: Unpin, + U: Service), Response = ()>, U::Error: fmt::Display, - P: Unpin, { fn new( cfg: ServiceConfig, @@ -343,19 +321,16 @@ where impl Service for HttpServiceHandler where - T: IoStream + Unpin, - S: Service + Unpin, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + T: IoStream, + S: Service, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service + Unpin, + X: Service, X::Error: Into, - X::Future: Unpin, - U: Service), Response = ()> + Unpin, + U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, - P: Unpin, { type Request = ServerIo; type Response = (); @@ -442,22 +417,21 @@ where } } +#[pin_project] enum State where - S: Service + Unpin, - S::Future: Unpin + 'static, + S: Service, + S::Future: 'static, S::Error: Into, - T: IoStream + Unpin, + T: IoStream, B: MessageBody, - X: Service + Unpin, + X: Service, X::Error: Into, - X::Future: Unpin, - U: Service), Response = ()> + Unpin, + U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, { - H1(h1::Dispatcher), - H2(Dispatcher, S, B>), + H1(#[pin] h1::Dispatcher), + H2(#[pin] Dispatcher, S, B>), Unknown( Option<( T, @@ -480,21 +454,21 @@ where ), } +#[pin_project] pub struct HttpServiceHandlerResponse where - T: IoStream + Unpin, - S: Service + Unpin, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + T: IoStream, + S: Service, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service + Unpin, + X: Service, X::Error: Into, - X::Future: Unpin, - U: Service), Response = ()> + Unpin, + U: Service), Response = ()>, U::Error: fmt::Display, - U::Future: Unpin, { + #[pin] state: State, } @@ -502,25 +476,45 @@ const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0"; impl Future for HttpServiceHandlerResponse where - T: IoStream + Unpin, - S: Service + Unpin, - S::Error: Into + Unpin + 'static, - S::Future: Unpin + 'static, - S::Response: Into> + Unpin + 'static, + T: IoStream, + S: Service, + S::Error: Into + 'static, + S::Future: 'static, + S::Response: Into> + 'static, B: MessageBody, - X: Service + Unpin, - X::Future: Unpin, + X: Service, X::Error: Into, - U: Service), Response = ()> + Unpin, - U::Future: Unpin, + U: Service), Response = ()>, U::Error: fmt::Display, { type Output = Result<(), DispatchError>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - match self.state { - State::H1(ref mut disp) => Pin::new(disp).poll(cx), - State::H2(ref mut disp) => Pin::new(disp).poll(cx), + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + self.project().state.poll(cx) + } +} + +impl State +where + T: IoStream, + S: Service, + S::Error: Into + 'static, + S::Response: Into> + 'static, + B: MessageBody + 'static, + X: Service, + X::Error: Into, + U: Service), Response = ()>, + U::Error: fmt::Display, +{ + #[project] + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll> { + #[project] + match self.as_mut().project() { + State::H1(disp) => disp.poll(cx), + State::H2(disp) => disp.poll(cx), State::Unknown(ref mut data) => { if let Some(ref mut item) = data { loop { @@ -549,15 +543,15 @@ where inner: io, unread: Some(buf), }; - self.state = State::Handshake(Some(( + self.set(State::Handshake(Some(( server::handshake(io), cfg, srv, peer_addr, on_connect, - ))); + )))); } else { - self.state = State::H1(h1::Dispatcher::with_timeout( + self.set(State::H1(h1::Dispatcher::with_timeout( io, h1::Codec::new(cfg.clone()), cfg, @@ -567,7 +561,7 @@ where expect, upgrade, on_connect, - )) + ))) } self.poll(cx) } @@ -585,9 +579,9 @@ where panic!() }; let (_, cfg, srv, peer_addr, on_connect) = data.take().unwrap(); - self.state = State::H2(Dispatcher::new( + self.set(State::H2(Dispatcher::new( srv, conn, on_connect, cfg, None, peer_addr, - )); + ))); self.poll(cx) } } @@ -595,13 +589,13 @@ where } /// Wrapper for `AsyncRead + AsyncWrite` types +#[pin_project::pin_project] struct Io { unread: Option, + #[pin] inner: T, } -impl Unpin for Io {} - impl io::Read for Io { fn read(&mut self, buf: &mut [u8]) -> io::Result { if let Some(mut bytes) = self.unread.take() { @@ -627,7 +621,7 @@ impl io::Write for Io { } } -impl AsyncRead for Io { +impl AsyncRead for Io { // unsafe fn initializer(&self) -> io::Initializer { // self.get_mut().inner.initializer() // } @@ -641,7 +635,19 @@ impl AsyncRead for Io { cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - Pin::new(&mut self.get_mut().inner).poll_read(cx, buf) + let this = self.project(); + + if let Some(mut bytes) = this.unread.take() { + let size = std::cmp::min(buf.len(), bytes.len()); + buf[..size].copy_from_slice(&bytes[..size]); + if bytes.len() > size { + bytes.split_to(size); + *this.unread = Some(bytes); + } + Poll::Ready(Ok(size)) + } else { + this.inner.poll_read(cx, buf) + } } // fn poll_read_vectored( @@ -653,32 +659,24 @@ impl AsyncRead for Io { // } } -impl tokio_io::AsyncWrite for Io { +impl tokio_io::AsyncWrite for Io { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - Pin::new(&mut self.get_mut().inner).poll_write(cx, buf) + self.project().inner.poll_write(cx, buf) } - // fn poll_write_vectored( - // self: Pin<&mut Self>, - // cx: &mut Context<'_>, - // bufs: &[io::IoSlice<'_>], - // ) -> Poll> { - // self.get_mut().inner.poll_write_vectored(cx, bufs) - // } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.get_mut().inner).poll_flush(cx) + self.project().inner.poll_flush(cx) } fn poll_shutdown( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - Pin::new(&mut self.get_mut().inner).poll_shutdown(cx) + self.project().inner.poll_shutdown(cx) } } diff --git a/actix-http/src/ws/transport.rs b/actix-http/src/ws/transport.rs index c55e2eeb..58ba3160 100644 --- a/actix-http/src/ws/transport.rs +++ b/actix-http/src/ws/transport.rs @@ -11,17 +11,17 @@ use super::{Codec, Frame, Message}; pub struct Transport where S: Service + 'static, - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite, { inner: FramedTransport, } impl Transport where - T: AsyncRead + AsyncWrite + Unpin, - S: Service + Unpin, + T: AsyncRead + AsyncWrite, + S: Service, S::Future: 'static, - S::Error: Unpin + 'static, + S::Error: 'static, { pub fn new>(io: T, service: F) -> Self { Transport { @@ -38,10 +38,10 @@ where impl Future for Transport where - T: AsyncRead + AsyncWrite + Unpin, - S: Service + Unpin, + T: AsyncRead + AsyncWrite, + S: Service, S::Future: 'static, - S::Error: Unpin + 'static, + S::Error: 'static, { type Output = Result<(), FramedTransportError>; diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index a4f1569c..05248966 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,9 +1,9 @@ -use actix_service::NewService; +use actix_service::ServiceFactory; use bytes::Bytes; use futures::future::{self, ok}; use actix_http::{http, HttpService, Request, Response}; -use actix_http_test::TestServer; +use actix_http_test::{block_on, TestServer}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -29,55 +29,63 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_v2() { - env_logger::init(); - let mut srv = TestServer::new(move || { - HttpService::build().finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - }); - let response = srv.block_on(srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + block_on(async { + let srv = TestServer::start(move || { + HttpService::build() + .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + }); - let request = srv.get("/").header("x-test", "111").send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + let request = srv.get("/").header("x-test", "111").send(); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - let response = srv.block_on(srv.post("/").send()).unwrap(); - assert!(response.status().is_success()); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + let mut response = srv.post("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_connection_close() { - let mut srv = TestServer::new(move || { - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) - }); - let response = srv.block_on(srv.get("/").force_close().send()).unwrap(); - assert!(response.status().is_success()); + block_on(async { + let srv = TestServer::start(move || { + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) + }); + + let response = srv.get("/").force_close().send().await.unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_with_query_parameter() { - let mut srv = TestServer::new(move || { - HttpService::build() - .finish(|req: Request| { - if req.uri().query().unwrap().contains("qp=") { - ok::<_, ()>(Response::Ok().finish()) - } else { - ok::<_, ()>(Response::BadRequest().finish()) - } - }) - .map(|_| ()) - }); + block_on(async { + let srv = TestServer::start(move || { + HttpService::build() + .finish(|req: Request| { + if req.uri().query().unwrap().contains("qp=") { + ok::<_, ()>(Response::Ok().finish()) + } else { + ok::<_, ()>(Response::BadRequest().finish()) + } + }) + .map(|_| ()) + }); - let request = srv.request(http::Method::GET, srv.url("/?qp=5")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::GET, srv.url("/?qp=5")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + }) } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs new file mode 100644 index 00000000..7eaa8e2a --- /dev/null +++ b/actix-http/tests/test_openssl.rs @@ -0,0 +1,545 @@ +#![cfg(feature = "openssl")] +use std::io; + +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http_test::{block_on, TestServer}; +use actix_server::ssl::OpensslAcceptor; +use actix_server_config::ServerConfig; +use actix_service::{factory_fn_cfg, pipeline_factory, service_fn2, ServiceFactory}; + +use bytes::{Bytes, BytesMut}; +use futures::future::{err, ok, ready}; +use futures::stream::{once, Stream, StreamExt}; +use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; + +use actix_http::error::{ErrorBadRequest, PayloadError}; +use actix_http::http::header::{self, HeaderName, HeaderValue}; +use actix_http::http::{Method, StatusCode, Version}; +use actix_http::httpmessage::HttpMessage; +use actix_http::{body, Error, HttpService, Request, Response}; + +async fn load_body(stream: S) -> Result +where + S: Stream>, +{ + let body = stream + .map(|res| match res { + Ok(chunk) => chunk, + Err(_) => panic!(), + }) + .fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + ready(body) + }) + .await; + + Ok(body) +} + +fn ssl_acceptor() -> io::Result> { + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("../tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("../tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(OpensslAcceptor::new(builder.build())) +} + +#[test] +fn test_h2() -> io::Result<()> { + block_on(async { + let openssl = ssl_acceptor()?; + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + Ok(()) + }) +} + +#[test] +fn test_h2_1() -> io::Result<()> { + block_on(async { + let openssl = ssl_acceptor()?; + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), Version::HTTP_2); + ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + Ok(()) + }) +} + +#[test] +fn test_h2_body() -> io::Result<()> { + block_on(async { + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let openssl = ssl_acceptor()?; + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|mut req: Request<_>| { + async move { + let body = load_body(req.take_payload()).await?; + Ok::<_, Error>(Response::Ok().body(body)) + } + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send_body(data.clone()).await.unwrap(); + assert!(response.status().is_success()); + + let body = srv.load_body(response).await.unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) + }) +} + +#[test] +fn test_h2_content_length() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + { + for i in 0..4 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), None); + + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } + }) +} + +#[test] +fn test_h2_headers() { + block_on(async { + let data = STR.repeat(10); + let data2 = data.clone(); + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::start(move || { + let data = data.clone(); + pipeline_factory(openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e))) + .and_then( + HttpService::build().h2(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + ok::<_, ()>(builder.body(data.clone())) + }).map_err(|_| ())) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from(data2)); + }) +} + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_h2_body2() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) +} + +#[test] +fn test_h2_head_empty() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); + }) +} + +#[test] +fn test_h2_head_binary() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); + }) +} + +#[test] +fn test_h2_head_binary2() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + }) +} + +#[test] +fn test_h2_body_length() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) +} + +#[test] +fn test_h2_body_chunked_explicit() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) +} + +#[test] +fn test_h2_response_http_error_handling() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(factory_fn_cfg(|_: &ServerConfig| { + ok::<_, ()>(service_fn2(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(header::CONTENT_TYPE, broken_header) + .body(STR), + ) + })) + })) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); + }) +} + +#[test] +fn test_h2_service_error() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| err::(ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"error")); + }) +} + +#[test] +fn test_h2_on_connect() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .on_connect(|_| 10usize) + .h2(|req: Request| { + assert!(req.extensions().contains::()); + ok::<_, ()>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + }) +} diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs new file mode 100644 index 00000000..c36d0579 --- /dev/null +++ b/actix-http/tests/test_rustls.rs @@ -0,0 +1,474 @@ +#![cfg(feature = "rustls")] +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::error::PayloadError; +use actix_http::http::header::{self, HeaderName, HeaderValue}; +use actix_http::http::{Method, StatusCode, Version}; +use actix_http::{body, error, Error, HttpService, Request, Response}; +use actix_http_test::{block_on, TestServer}; +use actix_server::ssl::RustlsAcceptor; +use actix_server_config::ServerConfig; +use actix_service::{factory_fn_cfg, pipeline_factory, service_fn2, ServiceFactory}; + +use bytes::{Bytes, BytesMut}; +use futures::future::{self, err, ok}; +use futures::stream::{once, Stream, StreamExt}; +use rust_tls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + NoClientAuth, ServerConfig as RustlsServerConfig, +}; + +use std::fs::File; +use std::io::{self, BufReader}; + +async fn load_body(mut stream: S) -> Result +where + S: Stream> + Unpin, +{ + let mut body = BytesMut::new(); + while let Some(item) = stream.next().await { + body.extend_from_slice(&item?) + } + Ok(body) +} + +fn ssl_acceptor() -> io::Result> { + // load ssl keys + let mut config = RustlsServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + let protos = vec![b"h2".to_vec()]; + config.set_protocols(&protos); + Ok(RustlsAcceptor::new(config)) +} + +#[test] +fn test_h2() -> io::Result<()> { + block_on(async { + let rustls = ssl_acceptor()?; + let srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + Ok(()) + }) +} + +#[test] +fn test_h2_1() -> io::Result<()> { + block_on(async { + let rustls = ssl_acceptor()?; + let srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), Version::HTTP_2); + future::ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + Ok(()) + }) +} + +#[test] +fn test_h2_body1() -> io::Result<()> { + block_on(async { + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let rustls = ssl_acceptor()?; + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|mut req: Request<_>| { + async move { + let body = load_body(req.take_payload()).await?; + Ok::<_, Error>(Response::Ok().body(body)) + } + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send_body(data.clone()).await.unwrap(); + assert!(response.status().is_success()); + + let body = srv.load_body(response).await.unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) + }) +} + +#[test] +fn test_h2_content_length() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + + let srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + { + for i in 0..4 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), None); + + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } + }) +} + +#[test] +fn test_h2_headers() { + block_on(async { + let data = STR.repeat(10); + let data2 = data.clone(); + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::start(move || { + let data = data.clone(); + pipeline_factory(rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build().h2(move |_| { + let mut config = Response::Ok(); + for idx in 0..90 { + config.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(config.body(data.clone())) + }).map_err(|_| ())) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from(data2)); + }) +} + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_h2_body2() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) +} + +#[test] +fn test_h2_head_empty() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); + }) +} + +#[test] +fn test_h2_head_binary() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok() + .content_length(STR.len() as u64) + .body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); + }) +} + +#[test] +fn test_h2_head_binary2() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + let srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + }) +} + +#[test] +fn test_h2_body_length() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok().body(body::SizedStream::new( + STR.len() as u64, + body, + )), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) +} + +#[test] +fn test_h2_body_chunked_explicit() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) +} + +#[test] +fn test_h2_response_http_error_handling() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(factory_fn_cfg(|_: &ServerConfig| { + ok::<_, ()>(service_fn2(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header( + http::header::CONTENT_TYPE, + broken_header, + ) + .body(STR), + ) + })) + })) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); + }) +} + +#[test] +fn test_h2_service_error() { + block_on(async { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| err::(error::ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"error")); + }) +} diff --git a/actix-http/tests/test_rustls_server.rs b/actix-http/tests/test_rustls_server.rs deleted file mode 100644 index b74fd07b..00000000 --- a/actix-http/tests/test_rustls_server.rs +++ /dev/null @@ -1,462 +0,0 @@ -#![cfg(feature = "rust-tls")] -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_http::error::PayloadError; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::{body, error, Error, HttpService, Request, Response}; -use actix_http_test::TestServer; -use actix_server::ssl::RustlsAcceptor; -use actix_server_config::ServerConfig; -use actix_service::{new_service_cfg, NewService}; - -use bytes::{Bytes, BytesMut}; -use futures::future::{self, ok, Future}; -use futures::stream::{once, Stream}; -use rustls::{ - internal::pemfile::{certs, pkcs8_private_keys}, - NoClientAuth, ServerConfig as RustlsServerConfig, -}; - -use std::fs::File; -use std::io::{BufReader, Result}; - -fn load_body(stream: S) -> impl Future -where - S: Stream, -{ - stream.fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, PayloadError>(body) - }) -} - -fn ssl_acceptor() -> Result> { - // load ssl keys - let mut config = RustlsServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let protos = vec![b"h2".to_vec()]; - config.set_protocols(&protos); - Ok(RustlsAcceptor::new(config)) -} - -#[test] -fn test_h2() -> Result<()> { - let rustls = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[test] -fn test_h2_1() -> Result<()> { - let rustls = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), Version::HTTP_2); - future::ok::<_, Error>(Response::Ok().finish()) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[test] -fn test_h2_body() -> Result<()> { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); - let rustls = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|mut req: Request<_>| { - load_body(req.take_payload()) - .and_then(|body| Ok(Response::Ok().body(body))) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); - assert!(response.status().is_success()); - - let body = srv.load_body(response).unwrap(); - assert_eq!(&body, data.as_bytes()); - Ok(()) -} - -#[test] -fn test_h2_content_length() { - let rustls = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }) - .map_err(|_| ()), - ) - }); - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - - { - for i in 0..4 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), None); - - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } - } -} - -#[test] -fn test_h2_headers() { - let data = STR.repeat(10); - let data2 = data.clone(); - let rustls = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - let data = data.clone(); - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build().h2(move |_| { - let mut config = Response::Ok(); - for idx in 0..90 { - config.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - future::ok::<_, ()>(config.body(data.clone())) - }).map_err(|_| ())) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from(data2)); -} - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[test] -fn test_h2_body2() { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_h2_head_empty() { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_h2_head_binary() { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - ok::<_, ()>( - Response::Ok().content_length(STR.len() as u64).body(STR), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_h2_head_binary2() { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[test] -fn test_h2_body_length() { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_h2_body_chunked_explicit() { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - let body = - once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); - - // read response - let bytes = srv.load_body(response).unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_h2_response_http_error_handling() { - let rustls = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(new_service_cfg(|_: &ServerConfig| { - Ok::<_, ()>(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) - }) - })) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); -} - -#[test] -fn test_h2_service_error() { - let rustls = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - rustls - .clone() - .map_err(|e| println!("Rustls error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| Err::(error::ErrorBadRequest("error"))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); -} diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index ef861b30..c37e8fad 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -2,7 +2,7 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; -use actix_http_test::{block_fn, TestServer}; +use actix_http_test::{block_on, TestServer}; use actix_server_config::ServerConfig; use actix_service::{factory_fn_cfg, pipeline, service_fn, ServiceFactory}; use bytes::Bytes; @@ -18,345 +18,379 @@ use actix_http::{ #[test] fn test_h1() { - let srv = TestServer::new(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .h1(|req: Request| { - assert!(req.peer_addr().is_some()); - future::ok::<_, ()>(Response::Ok().finish()) - }) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .h1(|req: Request| { + assert!(req.peer_addr().is_some()); + future::ok::<_, ()>(Response::Ok().finish()) + }) + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_h1_2() { - let srv = TestServer::new(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), http::Version::HTTP_11); - future::ok::<_, ()>(Response::Ok().finish()) - }) - .map(|_| ()) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), http::Version::HTTP_11); + future::ok::<_, ()>(Response::Ok().finish()) + }) + .map(|_| ()) + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_expect_continue() { - let srv = TestServer::new(|| { - HttpService::build() - .expect(service_fn(|req: Request| { - if req.head().uri.query() == Some("yes=") { - ok(req) - } else { - err(error::ErrorPreconditionFailed("error")) - } - })) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); -} - -#[test] -fn test_expect_continue_h1() { - let srv = TestServer::new(|| { - HttpService::build() - .expect(service_fn(|req: Request| { - delay_for(Duration::from_millis(20)).then(move |_| { + block_on(async { + let srv = TestServer::start(|| { + HttpService::build() + .expect(service_fn(|req: Request| { if req.head().uri.query() == Some("yes=") { ok(req) } else { err(error::ErrorPreconditionFailed("error")) } - }) - })) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + })) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = + stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); + }) +} + +#[test] +fn test_expect_continue_h1() { + block_on(async { + let srv = TestServer::start(|| { + HttpService::build() + .expect(service_fn(|req: Request| { + delay_for(Duration::from_millis(20)).then(move |_| { + if req.head().uri.query() == Some("yes=") { + ok(req) + } else { + err(error::ErrorPreconditionFailed("error")) + } + }) + })) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = + stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); + }) } #[test] fn test_chunked_payload() { - let chunk_sizes = vec![32768, 32, 32768]; - let total_size: usize = chunk_sizes.iter().sum(); + block_on(async { + let chunk_sizes = vec![32768, 32, 32768]; + let total_size: usize = chunk_sizes.iter().sum(); - let srv = TestServer::new(|| { - HttpService::build().h1(service_fn(|mut request: Request| { - request - .take_payload() - .map(|res| match res { - Ok(pl) => pl, - Err(e) => panic!(format!("Error reading payload: {}", e)), - }) - .fold(0usize, |acc, chunk| ready(acc + chunk.len())) - .map(|req_size| { - Ok::<_, Error>(Response::Ok().body(format!("size={}", req_size))) - }) - })) - }); + let srv = TestServer::start(|| { + HttpService::build().h1(service_fn(|mut request: Request| { + request + .take_payload() + .map(|res| match res { + Ok(pl) => pl, + Err(e) => panic!(format!("Error reading payload: {}", e)), + }) + .fold(0usize, |acc, chunk| ready(acc + chunk.len())) + .map(|req_size| { + Ok::<_, Error>(Response::Ok().body(format!("size={}", req_size))) + }) + })) + }); - let returned_size = { - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream - .write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); + let returned_size = { + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream + .write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); - for chunk_size in chunk_sizes.iter() { - let mut bytes = Vec::new(); - let random_bytes: Vec = - (0..*chunk_size).map(|_| rand::random::()).collect(); + for chunk_size in chunk_sizes.iter() { + let mut bytes = Vec::new(); + let random_bytes: Vec = + (0..*chunk_size).map(|_| rand::random::()).collect(); - bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes()); - bytes.extend(&random_bytes[..]); - bytes.extend(b"\r\n"); - let _ = stream.write_all(&bytes); - } + bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes()); + bytes.extend(&random_bytes[..]); + bytes.extend(b"\r\n"); + let _ = stream.write_all(&bytes); + } - let _ = stream.write_all(b"0\r\n\r\n"); - stream.shutdown(net::Shutdown::Write).unwrap(); + let _ = stream.write_all(b"0\r\n\r\n"); + stream.shutdown(net::Shutdown::Write).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); - let re = Regex::new(r"size=(\d+)").unwrap(); - let size: usize = match re.captures(&data) { - Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), - None => panic!(format!("Failed to find size in HTTP Response: {}", data)), + let re = Regex::new(r"size=(\d+)").unwrap(); + let size: usize = match re.captures(&data) { + Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), + None => { + panic!(format!("Failed to find size in HTTP Response: {}", data)) + } + }; + size }; - size - }; - assert_eq!(returned_size, total_size); + assert_eq!(returned_size, total_size); + }) } #[test] fn test_slow_request() { - let srv = TestServer::new(|| { - HttpService::build() - .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build() + .client_timeout(100) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + }) } #[test] fn test_http1_malformed_request() { - let srv = TestServer::new(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 400 Bad Request")); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 400 Bad Request")); + }) } #[test] fn test_http1_keepalive() { - let srv = TestServer::new(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + }) } #[test] fn test_http1_keepalive_timeout() { - let srv = TestServer::new(|| { - HttpService::build() - .keep_alive(1) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build() + .keep_alive(1) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - thread::sleep(Duration::from_millis(1100)); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + thread::sleep(Duration::from_millis(1100)); - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); + }) } #[test] fn test_http1_keepalive_close() { - let srv = TestServer::new(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = - stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream + .write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); + }) } #[test] fn test_http10_keepalive_default_close() { - let srv = TestServer::new(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); + }) } #[test] fn test_http10_keepalive() { - let srv = TestServer::new(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream - .write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all( + b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n", + ); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); + }) } #[test] fn test_http1_keepalive_disabled() { - let srv = TestServer::new(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build() + .keep_alive(KeepAlive::Disabled) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); + }) } #[test] fn test_content_length() { - use actix_http::http::{ - header::{HeaderName, HeaderValue}, - StatusCode, - }; + block_on(async { + use actix_http::http::{ + header::{HeaderName, HeaderValue}, + StatusCode, + }; - let srv = TestServer::new(|| { - HttpService::build().h1(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }) - }); + let srv = TestServer::start(|| { + HttpService::build().h1(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }) + }); - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); - { - for i in 0..4 { - let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); - let response = block_fn(move || req.send()).unwrap(); - assert_eq!(response.headers().get(&header), None); + { + for i in 0..4 { + let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); + let response = req.send().await.unwrap(); + assert_eq!(response.headers().get(&header), None); - let req = srv.request(http::Method::HEAD, srv.url(&format!("/{}", i))); - let response = block_fn(move || req.send()).unwrap(); - assert_eq!(response.headers().get(&header), None); + let req = srv.request(http::Method::HEAD, srv.url(&format!("/{}", i))); + let response = req.send().await.unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); + let response = req.send().await.unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } } - - for i in 4..6 { - let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); - let response = block_fn(move || req.send()).unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } - } + }) } #[test] fn test_h1_headers() { - let data = STR.repeat(10); - let data2 = data.clone(); + block_on(async { + let data = STR.repeat(10); + let data2 = data.clone(); - let mut srv = TestServer::new(move || { - let data = data.clone(); - HttpService::build().h1(move |_| { + let mut srv = TestServer::start(move || { + let data = data.clone(); + HttpService::build().h1(move |_| { let mut builder = Response::Ok(); for idx in 0..90 { builder.header( @@ -378,14 +412,15 @@ fn test_h1_headers() { } future::ok::<_, ()>(builder.body(data.clone())) }) - }); + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from(data2)); + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from(data2)); + }) } const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -412,208 +447,228 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_h1_body() { - let mut srv = TestServer::new(|| { - HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_h1_head_empty() { - let mut srv = TestServer::new(|| { - HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + }); - let response = block_fn(|| srv.head("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); + }) } #[test] fn test_h1_head_binary() { - let mut srv = TestServer::new(|| { - HttpService::build().h1(|_| { - ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) - }) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| { + ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) + }) + }); - let response = block_fn(|| srv.head("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); + }) } #[test] fn test_h1_head_binary2() { - let srv = TestServer::new(|| { - HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + }); - let response = block_fn(|| srv.head("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + }) } #[test] fn test_h1_body_length() { - let mut srv = TestServer::new(|| { - HttpService::build().h1(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| { + let body = once(ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_h1_body_chunked_explicit() { - let mut srv = TestServer::new(|| { - HttpService::build().h1(|_| { - let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| { + let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "chunked" - ); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!( + response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "chunked" + ); - // read response - let bytes = srv.load_body(response).unwrap(); + // read response + let bytes = srv.load_body(response).await.unwrap(); - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_h1_body_chunked_implicit() { - let mut srv = TestServer::new(|| { - HttpService::build().h1(|_| { - let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) - }) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| { + let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) + }) + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "chunked" - ); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!( + response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "chunked" + ); - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_h1_response_http_error_handling() { - let mut srv = TestServer::new(|| { - HttpService::build().h1(factory_fn_cfg(|_: &ServerConfig| { - ok::<_, ()>(pipeline(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build().h1(factory_fn_cfg(|_: &ServerConfig| { + ok::<_, ()>(pipeline(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + })) })) - })) - }); + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + let response = srv.get("/").send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); + }) } #[test] fn test_h1_service_error() { - let mut srv = TestServer::new(|| { - HttpService::build() - .h1(|_| future::err::(error::ErrorBadRequest("error"))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build() + .h1(|_| future::err::(error::ErrorBadRequest("error"))) + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + let response = srv.get("/").send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"error")); + }) } #[test] fn test_h1_on_connect() { - let srv = TestServer::new(|| { - HttpService::build() - .on_connect(|_| 10usize) - .h1(|req: Request| { - assert!(req.extensions().contains::()); - future::ok::<_, ()>(Response::Ok().finish()) - }) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::build() + .on_connect(|_| 10usize) + .h1(|req: Request| { + assert!(req.extensions().contains::()); + future::ok::<_, ()>(Response::Ok().finish()) + }) + }); - let response = block_fn(|| srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + }) } diff --git a/actix-http/tests/test_ssl_server.rs b/actix-http/tests/test_ssl_server.rs deleted file mode 100644 index 897d92b3..00000000 --- a/actix-http/tests/test_ssl_server.rs +++ /dev/null @@ -1,480 +0,0 @@ -#![cfg(feature = "ssl")] -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_http_test::TestServer; -use actix_server::ssl::OpensslAcceptor; -use actix_server_config::ServerConfig; -use actix_service::{new_service_cfg, NewService}; - -use bytes::{Bytes, BytesMut}; -use futures::future::{ok, Future}; -use futures::stream::{once, Stream}; -use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; -use std::io::Result; - -use actix_http::error::{ErrorBadRequest, PayloadError}; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::httpmessage::HttpMessage; -use actix_http::{body, Error, HttpService, Request, Response}; - -fn load_body(stream: S) -> impl Future -where - S: Stream, -{ - stream.fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, PayloadError>(body) - }) -} - -fn ssl_acceptor() -> Result> { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2")?; - Ok(OpensslAcceptor::new(builder.build())) -} - -#[test] -fn test_h2() -> Result<()> { - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| ok::<_, Error>(Response::Ok().finish())) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[test] -fn test_h2_1() -> Result<()> { - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), Version::HTTP_2); - ok::<_, Error>(Response::Ok().finish()) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[test] -fn test_h2_body() -> Result<()> { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|mut req: Request<_>| { - load_body(req.take_payload()) - .and_then(|body| Ok(Response::Ok().body(body))) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); - assert!(response.status().is_success()); - - let body = srv.load_body(response).unwrap(); - assert_eq!(&body, data.as_bytes()); - Ok(()) -} - -#[test] -fn test_h2_content_length() { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - ok::<_, ()>(Response::new(statuses[indx])) - }) - .map_err(|_| ()), - ) - }); - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - - { - for i in 0..4 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), None); - - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } - } -} - -#[test] -fn test_h2_headers() { - let data = STR.repeat(10); - let data2 = data.clone(); - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - let data = data.clone(); - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build().h2(move |_| { - let mut builder = Response::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - ok::<_, ()>(builder.body(data.clone())) - }).map_err(|_| ())) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from(data2)); -} - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[test] -fn test_h2_body2() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_h2_head_empty() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); - - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_h2_head_binary() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - ok::<_, ()>( - Response::Ok().content_length(STR.len() as u64).body(STR), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} - -#[test] -fn test_h2_head_binary2() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[test] -fn test_h2_body_length() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_h2_body_chunked_explicit() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - let body = - once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); - - // read response - let bytes = srv.load_body(response).unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[test] -fn test_h2_response_http_error_handling() { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(new_service_cfg(|_: &ServerConfig| { - Ok::<_, ()>(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(header::CONTENT_TYPE, broken_header) - .body(STR), - ) - }) - })) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); -} - -#[test] -fn test_h2_service_error() { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| Err::(ErrorBadRequest("error"))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); -} - -#[test] -fn test_h2_on_connect() { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .on_connect(|_| 10usize) - .h2(|req: Request| { - assert!(req.extensions().contains::()); - ok::<_, ()>(Response::Ok().finish()) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); -} diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 65a4d094..74c1cb40 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -1,26 +1,27 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; -use actix_http_test::TestServer; +use actix_http_test::{block_on, TestServer}; use actix_utils::framed::FramedTransport; use bytes::{Bytes, BytesMut}; -use futures::future::{self, ok}; -use futures::{Future, Sink, Stream}; +use futures::future; +use futures::{SinkExt, StreamExt}; -fn ws_service( - (req, framed): (Request, Framed), -) -> impl Future { +async fn ws_service( + (req, mut framed): (Request, Framed), +) -> Result<(), Error> { let res = ws::handshake(req.head()).unwrap().message_body(()); framed .send((res, body::BodySize::None).into()) + .await + .unwrap(); + + FramedTransport::new(framed.into_framed(ws::Codec::new()), service) + .await .map_err(|_| panic!()) - .and_then(|framed| { - FramedTransport::new(framed.into_framed(ws::Codec::new()), service) - .map_err(|_| panic!()) - }) } -fn service(msg: ws::Frame) -> impl Future { +async fn service(msg: ws::Frame) -> Result { let msg = match msg { ws::Frame::Ping(msg) => ws::Message::Pong(msg), ws::Frame::Text(text) => { @@ -30,47 +31,56 @@ fn service(msg: ws::Frame) -> impl Future { ws::Frame::Close(reason) => ws::Message::Close(reason), _ => panic!(), }; - ok(msg) + Ok(msg) } #[test] fn test_simple() { - let mut srv = TestServer::new(|| { - HttpService::build() - .upgrade(ws_service) - .finish(|_| future::ok::<_, ()>(Response::NotFound())) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build() + .upgrade(actix_service::service_fn(ws_service)) + .finish(|_| future::ok::<_, ()>(Response::NotFound())) + }); - // client service - let framed = srv.ws().unwrap(); - let framed = srv - .block_on(framed.send(ws::Message::Text("text".to_string()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + // client service + let mut framed = srv.ws().await.unwrap(); + framed + .send(ws::Message::Text("text".to_string())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Text(Some(BytesMut::from("text"))) + ); - let framed = srv - .block_on(framed.send(ws::Message::Binary("text".into()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!( - item, - Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) - ); + framed + .send(ws::Message::Binary("text".into())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) + ); - let framed = srv - .block_on(framed.send(ws::Message::Ping("text".into()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + framed.send(ws::Message::Ping("text".into())).await.unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Pong("text".to_string().into()) + ); - let framed = srv - .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) - .unwrap(); + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); - let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!( - item, - Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) - ); + let (item, _framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Close(Some(ws::CloseCode::Normal.into())) + ); + }) } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 4ba123aa..bf6558b5 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -7,7 +7,7 @@ use actix_rt::{System}; use actix_server::{Server, ServiceFactory}; use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; use bytes::Bytes; -use futures::{Stream, future::lazy}; +use futures::Stream; use http::Method; use net2::TcpBuilder; use tokio_net::tcp::TcpStream; @@ -55,7 +55,7 @@ pub struct TestServerRuntime { impl TestServer { #[allow(clippy::new_ret_no_self)] /// Start new test server with application factory - pub fn new>(factory: F) -> TestServerRuntime { + pub fn start>(factory: F) -> TestServerRuntime { let (tx, rx) = mpsc::channel(); // run server in separate thread @@ -76,11 +76,11 @@ impl TestServer { let (system, addr) = rx.recv().unwrap(); - let client = block_on(lazy(move |_| { + let client = { let connector = { - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); @@ -93,7 +93,7 @@ impl TestServer { .ssl(builder.build()) .finish() } - #[cfg(not(feature = "ssl"))] + #[cfg(not(feature = "openssl"))] { Connector::new() .conn_lifetime(time::Duration::from_secs(0)) @@ -102,14 +102,9 @@ impl TestServer { } }; - Ok::(Client::build().connector(connector).finish()) - })) - .unwrap(); - - block_on(lazy(|_| { - Ok::<_, ()>(actix_connect::start_default_resolver()) - })) - .unwrap(); + Client::build().connector(connector).finish() + }; + actix_connect::start_default_resolver(); TestServerRuntime { addr, @@ -228,35 +223,33 @@ impl TestServerRuntime { self.client.request(method, path.as_ref()) } - pub fn load_body( + pub async fn load_body( &mut self, mut response: ClientResponse, ) -> Result where S: Stream> + Unpin + 'static, { - block_on(response.body().limit(10_485_760)) + response.body().limit(10_485_760).await } /// Connect to websocket server at a given path - pub fn ws_at( + pub async fn ws_at( &mut self, path: &str, ) -> Result, awc::error::WsClientError> { let url = self.url(path); let connect = self.client.ws(url).connect(); - block_on(async move { - connect.await.map(|(_, framed)| framed) - }) + connect.await.map(|(_, framed)| framed) } /// Connect to a websocket server - pub fn ws( + pub async fn ws( &mut self, ) -> Result, awc::error::WsClientError> { - self.ws_at("/") + self.ws_at("/").await } /// Stop http server diff --git a/tests/cert.pem b/tests/cert.pem index f9bb0508..0eeb6721 100644 --- a/tests/cert.pem +++ b/tests/cert.pem @@ -1,32 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIFfjCCA2agAwIBAgIJAOIBvp/w68KrMA0GCSqGSIb3DQEBCwUAMGsxCzAJBgNV -BAYTAlJVMRkwFwYDVQQIDBBTYWludC1QZXRlcnNidXJnMRkwFwYDVQQHDBBTYWlu -dC1QZXRlcnNidXJnMRIwEAYDVQQKDAlLdXBpYmlsZXQxEjAQBgNVBAMMCWxvY2Fs -aG9zdDAgFw0xOTA3MjcxODIzMTJaGA8zMDE5MDcyNzE4MjMxMlowazELMAkGA1UE -BhMCUlUxGTAXBgNVBAgMEFNhaW50LVBldGVyc2J1cmcxGTAXBgNVBAcMEFNhaW50 -LVBldGVyc2J1cmcxEjAQBgNVBAoMCUt1cGliaWxldDESMBAGA1UEAwwJbG9jYWxo -b3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuiQZzTO3gRRPr6ZH -wcmKqkoXig9taCCqx72Qvb9tvCLhQLE1dDPZV8I/r8bx+mM4Yz3r0Hm5LxTIhCM9 -p3/abuiJAZENC/VkxgFzBGg7KGLSFmzU+A8Ft+2mrKmj5MpIPBCxDeVg80TCQOJy -hj+NU3PpBo9nxTgxWNWO6X+ZovZohdp78fYLLtns8rxjug3FVzdPrrLnBvihkGlq -gfImkh+vZxMTj1OgtxyCOhdbO4Ol4jCbn7a5yIw+iixHOEgBQfTQopRP7z1PEUV2 -WIy2VEGzvQDlj2OyzH86T1IOFV5rz5MjdZuW0qNzeS0w3Jzgp/olSbIZLhGAaIk0 -gN7y9XvSHqs7rO0wW+467ico7+uP1ScGgPgJA5fGu7ahp7F7G3ZSoAqAcS60wYsX -kStoA3RWAuqste6aChv1tlgTt+Rhk8qjGhuh0ng2qVnTGyo2T3OCHB/c47Bcsp6L -xiyTCnQIPH3fh2iO/SC7gPw3jihPMCAQZYlkC3MhMk974rn2zs9cKtJ8ubnG2m5F -VFVYmduRqS/YQS/O802jVCFdc8KDmoeAYNuHzgRZjQv9018UUeW3jtWKnopJnWs5 -ae9pbtmYeOtc7OovOxT7J2AaVfUkHRhmlqWZMzEJTcZws0fRPTZDifFJ5LFWbZsC -zW4tCKBKvYM9eAnbb+abiHXlY1MCAwEAAaMjMCEwHwYDVR0RBBgwFoIJbG9jYWxo -b3N0ggkxMjcuMC4wLjEwDQYJKoZIhvcNAQELBQADggIBAC1EU4SVCfUKc7JbhYRf -P87F+8e13bBTLxevJdnTCH3Xw2AN8UPmwQ2vv9Mv2FMulMBQ7yLnQLGtgGUua2OE -XO+EdBBEKnglo9cwXGzU6qHhaiCeXZDM8s53qOOrD42XsDsY0nOoFYqDLW3WixP9 -f1fWbcEf6+ktlvqi/1/3R6QtQR+6LS43enbsYHq8aAP60NrpXxdXxEoUwW6Z/sje -XAQluH8jzledwJcY8bXRskAHZlE4kGlOVuGgnyI3BXyLiwB4g9smFzYIs98iAGmV -7ZBaR5IIiRCtoKBG+SngM7Log0bHphvFPjDDvgqWYiWaOHboYM60Y2Z/gRbcjuMU -WZX64jw29fa8UPFdtGTupt+iuO7iXnHnm0lBBK36rVdOvsZup76p6L4BXmFsRmFK -qJ2Zd8uWNPDq80Am0mYaAqENuIANHHJXX38SesC+QO+G2JZt6vCwkGk/Qune4GIg -1GwhvsDRfTQopSxg1rdPwPM7HWeTfUGHZ34B5p/iILA3o6PfYQU8fNAWIsCDkRX2 -MrgDgCnLZxKb6pjR4DYNAdPwkxyMFACZ2T46z6WvLWFlnkK5nbZoqsOsp+GJHole -llafhrelXEzt3zFR0q4zGcqheJDI+Wy+fBy3XawgAc4eN0T2UCzL/jKxKgzlzSU3 -+xh1SDNjFLRd6sGzZHPMgXN0 +MIIDEDCCAfgCCQCQdmIZc/Ib/jANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQGEwJ1 +czELMAkGA1UECAwCY2ExCzAJBgNVBAcMAnNmMSEwHwYJKoZIhvcNAQkBFhJmYWZo +cmQ5MUBnbWFpbC5jb20wHhcNMTkxMTE5MTEwNjU1WhcNMjkxMTE2MTEwNjU1WjBK +MQswCQYDVQQGEwJ1czELMAkGA1UECAwCY2ExCzAJBgNVBAcMAnNmMSEwHwYJKoZI +hvcNAQkBFhJmYWZocmQ5MUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDcnaz12CKzUL7248V7Axhms/O9UQXfAdw0yolEfC3P5jADa/1C ++kLWKjAc2coqDSbGsrsR6KiH2g06Kunx+tSGqUO+Sct7HEehmxndiSwx/hfMWezy +XRe/olcHFTeCk/Tllz4xGEplhPua6GLhJygLOhAMiV8cwCYrgyPqsDduExLDFCqc +K2xntIPreumXpiE3QY4+MWyteiJko4IWDFf/UwwsdCY5MlFfw1F/Uv9vz7FfOfvu +GccHd/ex8cOwotUqd6emZb+0bVE24Sv8U+yLnHIVx/tOkxgMAnJEpAnf2G3Wp3zU +b2GJosbmfGaf+xTfnGGhTLLL7kCtva+NvZr5AgMBAAEwDQYJKoZIhvcNAQELBQAD +ggEBANftoL8zDGrjCwWvct8kOOqset2ukK8vjIGwfm88CKsy0IfSochNz2qeIu9R +ZuO7c0pfjmRkir9ZQdq9vXgG3ccL9UstFsferPH9W3YJ83kgXg3fa0EmCiN/0hwz +6Ij1ZBiN1j3+d6+PJPgyYFNu2nGwox5mJ9+aRAGe0/9c63PEOY8P2TI4HsiPmYSl +fFR8k/03vr6e+rTKW85BgctjvYKe/TnFxeCQ7dZ+na7vlEtch4tNmy6O/vEk2kCt +5jW0DUxhmRsv2wGmfFRI0+LotHjoXQQZi6nN5aGL3odaGF3gYwIVlZNd3AdkwDQz +BzG0ZwXuDDV9bSs3MfWEWcy4xuU= -----END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem index 70153c8a..a6d30816 100644 --- a/tests/key.pem +++ b/tests/key.pem @@ -1,52 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC6JBnNM7eBFE+v -pkfByYqqSheKD21oIKrHvZC9v228IuFAsTV0M9lXwj+vxvH6YzhjPevQebkvFMiE -Iz2nf9pu6IkBkQ0L9WTGAXMEaDsoYtIWbNT4DwW37aasqaPkykg8ELEN5WDzRMJA -4nKGP41Tc+kGj2fFODFY1Y7pf5mi9miF2nvx9gsu2ezyvGO6DcVXN0+usucG+KGQ -aWqB8iaSH69nExOPU6C3HII6F1s7g6XiMJuftrnIjD6KLEc4SAFB9NCilE/vPU8R -RXZYjLZUQbO9AOWPY7LMfzpPUg4VXmvPkyN1m5bSo3N5LTDcnOCn+iVJshkuEYBo -iTSA3vL1e9Ieqzus7TBb7jruJyjv64/VJwaA+AkDl8a7tqGnsXsbdlKgCoBxLrTB -ixeRK2gDdFYC6qy17poKG/W2WBO35GGTyqMaG6HSeDapWdMbKjZPc4IcH9zjsFyy -novGLJMKdAg8fd+HaI79ILuA/DeOKE8wIBBliWQLcyEyT3viufbOz1wq0ny5ucba -bkVUVViZ25GpL9hBL87zTaNUIV1zwoOah4Bg24fOBFmNC/3TXxRR5beO1Yqeikmd -azlp72lu2Zh461zs6i87FPsnYBpV9SQdGGaWpZkzMQlNxnCzR9E9NkOJ8UnksVZt -mwLNbi0IoEq9gz14Cdtv5puIdeVjUwIDAQABAoICAQCZVVezw+BsAjFKPi1qIv2J -HZOadO7pEc/czflHdUON8SWgxtmDqZpmQmt3/ugiHE284qs4hqzXbcVnpCgLrLRh -HEiP887NhQ3IVjVK8hmZQR5SvsAIv0c0ph3gqbWKqF8sq4tOKR/eBUwHawJwODXR -AvB4KPWQbqOny/P3wNbseRLNAJeNT+MSaw5XPnzgLKvdFoEbJeBNy847Sbsk5DaF -tHgm7n30WS1Q6bkU5VyP//hMBUKNJFaSL4TtCWB5qkbu8B5VbtsR9m0FizTb6L3h -VmYbUXvIzJXjAwMjiDJ1w9wHl+tj3BE33tEmhuVzNf+SH+tLc9xuKJighDWt2vpD -eTpZ1qest26ANLOmNXWVCVTGpcWvOu5yhG/P7En10EzjFruMfHAFdwLm1gMx1rlR -9fyNAk/0ROJ+5BUtuWgDiyytS5f2T9KGiOHni7UbBIkv0CV2H6VL39Twxf+3OHnx -JJ7OWZ8DRuLM/EJfN3C1+3eDsXOvcdvbo2TFBmCCl4Pa2pm4k3g2NBfxy/zSYWIh -ccGPZorFKNMUi29U0Ool6fxeVflbll570pWVBLAB31HdkLSESv9h+2j/IiEJcJXj -nzl2RtYB0Uxzk6SjO0z4WXjz/SXg5tQQkm/dx8kM8TvHICFq68AEnw8t9Hagsdxs -v5jNyOEeI1I5gPgZmKuboQKCAQEA7Hw6s8Xc3UtNaywMcK1Eb1O+kwRdztgtm0uE -uqsHWmGqbBxXN4jiVLh3dILIXFuVbvDSsSZtPLhOj1wqxgsTHg93b4BtvowyNBgo -X4tErMu7/6NRael/hfOEdtpfv2gV+0eQa+8KKqYJPbqpMz/r5L/3RaxS3iXkj3QM -6oC4+cRuwy/flPMIpxhDriH5yjfiMOdDsi3ZfMTJu/58DTrKV7WkJxQZmha4EoZn -IiXeRhzo+2ukMDWrr3GGPyDfjd/NB7rmY8QBdmhB5NSk+6B66JCNTIbKka/pichS -36bwSYFNji4NaHUUlYDUjfKoTNuQMEZknMGhc/433ADO7s17iQKCAQEAyYBYVG7/ -LE2IkvQy9Nwly5tRCNlSvSkloz7PUwRbzG5uF5mweWEa8YECJe9/vrFXvyBW+NR8 -XABFn4eG0POTR9nyb4n2nUlqiGugDIPgkrKCkJws5InifITZ/+Viocd4YZL5UwCU -R1/kMf0UjK2iJjWEeTPS6RmwRI2Iu7kym9BzphDyNYBQSbUE/f+4hNP6nUT/h09c -VH4/sUhubSgVKeK4onOci8bKitAkwVBYCYSyhuBCeCu8fTk2hVRWviRaJPVq2PMB -LHw1FCcfJLIPJG6MZpFAPkMQxpiewdggXIgi46ZlZcsNXEJ81ocT4GU2j+ArQXCf -lgEycyD3mx4k+wKCAQBGneohmKoVYtEheavVUcgnvkggOqOQirlDsE9YNo4hjRyI -4AWjTbrYNaVmI0+VVLvQvxULVUA1a4v5/zm+nbv9s/ykTSN4TQEI0VXtAfdl6gif -k7NR/ynXZBpgK2GAFKLLwFj+Agl1JtOHnV+9MA9O5Yv/QDAWqhYQSEU7GWkjHGc+ -3eLT5ablzrcXHooqunlOxSBP6qURPupGuv1sLewSOOllyfjDLJmW3o+ZgNlY8nUX -7tK+mqhD4ZCG9VgMU5I0BrmZfQQ6yXMz09PYV9mb7N5kxbNjwbXpMOqeYolKSdRQ -6quST7Pv2OKf6KAdI0txPvP4Y1HFA1rG1W71nGKRAoIBAHlDU+T8J3Rx9I77hu70 -zYoKnmnE35YW/R+Q3RQIu3X7vyVUyG9DkQNlr/VEfIw2Dahnve9hcLWtNDkdRnTZ -IPlMoCmfzVo6pHIU0uy1MKEX7Js6YYnnsPVevhLR6NmTQU73NDRPVOzfOGUc+RDw -LXTxIBgQqAy/+ORIiNDwUxSSDgcSi7DG14qD9c0l59WH/HpI276Cc/4lPA9kl4/5 -X0MlvheFm+BCcgG34Wa1A0Y3JXkl3NqU94oktDro1or3NYioaPTGyR4MYaUPJh7f -SV2TacsP/ql5ks7xahkeB9un0ddOfBcWa6PqH1a7U6rnPj63mVB4hpGvhrziSiB/ -s6ECggEAOp2P4Yd9Vm9/CptxS50HFF4adyLscAtsDd3S2hIAXhDovcPbvRek4pLQ -idPhHlRAfqrEztnhaVAmCK9HlhgthtiQGQX62YI4CS4QL2IhzDFo3M1a2snjFEdl -QuFk3XI7kQ0Yp8BLLG7T436JUrUkCXc4gQX2uRNut+ff34RIR2CjcQQjChxuHVeG -sP/3xFFj8OSs7ZoSPbmDBLrMOl64YHwezQUNAZiRYiaGbFiY0QUV6dHq8qX/qE1h -a/0Rq+gTqObDST0TqhMzI8V/i7R8SwVcD5ODHaZp5I2N2P/hV5OWY7ghQXhh89WM -o21xtGh0nP2Fq1TC6jFO+9cpbK8jNA== +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcnaz12CKzUL72 +48V7Axhms/O9UQXfAdw0yolEfC3P5jADa/1C+kLWKjAc2coqDSbGsrsR6KiH2g06 +Kunx+tSGqUO+Sct7HEehmxndiSwx/hfMWezyXRe/olcHFTeCk/Tllz4xGEplhPua +6GLhJygLOhAMiV8cwCYrgyPqsDduExLDFCqcK2xntIPreumXpiE3QY4+MWyteiJk +o4IWDFf/UwwsdCY5MlFfw1F/Uv9vz7FfOfvuGccHd/ex8cOwotUqd6emZb+0bVE2 +4Sv8U+yLnHIVx/tOkxgMAnJEpAnf2G3Wp3zUb2GJosbmfGaf+xTfnGGhTLLL7kCt +va+NvZr5AgMBAAECggEBAKoU0UwzVgVCQgca8Jt2dnBvWYDhnxIfYAI/BvaKedMm +1ms87OKfB7oOiksjyI0E2JklH72dzZf2jm4CuZt5UjGC+xwPzlTaJ4s6hQVbBHyC +NRyxU1BCXtW5tThbrhD4OjxqjmLRJEIB9OunLtwAEQoeuFLB8Va7+HFhR+Zd9k3f +7aVA93pC5A50NRbZlke4miJ3Q8n7ZF0+UmxkBfm3fbqLk7aMWkoEKwLLTadjRlu1 +bBp0YDStX66I/p1kujqBOdh6VpPvxFOa1sV9pq0jeiGc9YfSkzRSKzIn8GoyviFB +fHeszQdNlcnrSDSNnMABAw+ZpxUO7SCaftjwejEmKZUCgYEA+TY43VpmV95eY7eo +WKwGepiHE0fwQLuKGELmZdZI80tFi73oZMuiB5WzwmkaKGcJmm7KGE9KEvHQCo9j +xvmktBR0VEZH8pmVfun+4h6+0H7m/NKMBBeOyv/IK8jBgHjkkB6e6nmeR7CqTxCw +tf9tbajl1QN8gNzXZSjBDT/lanMCgYEA4qANOKOSiEARtgwyXQeeSJcM2uPv6zF3 +ffM7vjSedtuEOHUSVeyBP/W8KDt7zyPppO/WNbURHS+HV0maS9yyj6zpVS2HGmbs +3fetswsQ+zYVdokW89x4oc2z4XOGHd1LcSlyhRwPt0u2g1E9L0irwTQLWU0npFmG +PRf7sN9+LeMCgYAGkDUDL2ROoB6gRa/7Vdx90hKMoXJkYgwLA4gJ2pDlR3A3c/Lw +5KQJyxmG3zm/IqeQF6be6QesZA30mT4peV2rGHbP2WH/s6fKReNelSy1VQJEWk8x +tGUgV4gwDwN5nLV4TjYlOrq+bJqvpmLhCC8bmj0jVQosYqSRl3cuICasnQKBgGlV +VO/Xb1su1EyWPK5qxRIeSxZOTYw2sMB01nbgxCqge0M2fvA6/hQ5ZlwY0cIEgits +YlcSMsMq/TAAANxz1vbaupUhlSMbZcsBvNV0Nk9c4vr2Wxm7hsJF9u66IEMvQUp2 +pkjiMxfR9CHzF4orr9EcHI5EQ0Grbq5kwFKEfoRbAoGAcWoFPILeJOlp2yW/Ds3E +g2fQdI9BAamtEZEaslJmZMmsDTg5ACPcDkOSFEQIaJ7wLPXeZy74FVk/NrY5F8Gz +bjX9OD/xzwp852yW5L9r62vYJakAlXef5jI6CFdYKDDCcarU0S7W5k6kq9n+wrBR +i1NklYmUAMr2q59uJA5zsic= -----END PRIVATE KEY----- From d081e573167cc139debb00e78333310a18b2cdf2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 19 Nov 2019 19:38:42 +0600 Subject: [PATCH 1608/1635] fix h2 client send body --- actix-http/src/client/h2proto.rs | 2 ++ actix-http/src/request.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 25299fd6..1647abf8 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -159,6 +159,8 @@ async fn send_body( } else { if !b.is_empty() { send.reserve_capacity(b.len()); + } else { + buf = None; } continue; } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 0afa45cb..77ece01c 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -204,7 +204,6 @@ mod tests { assert_eq!(req.uri().query(), Some("q=1")); let s = format!("{:?}", req); - println!("T: {:?}", s); assert!(s.contains("Request HTTP/1.1 GET:/index.html")); } } From 3127dd4db62175e9960b879f7621415b755d9e06 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 20 Nov 2019 23:33:22 +0600 Subject: [PATCH 1609/1635] migrate actix-web to std::future --- Cargo.toml | 88 ++- actix-cors/Cargo.toml | 10 +- actix-files/Cargo.toml | 12 +- actix-files/src/lib.rs | 5 +- actix-framed/Cargo.toml | 20 +- actix-http/Cargo.toml | 18 +- actix-http/src/builder.rs | 7 +- actix-http/src/h2/dispatcher.rs | 20 +- actix-identity/Cargo.toml | 12 +- actix-multipart/Cargo.toml | 12 +- actix-session/Cargo.toml | 12 +- awc/Cargo.toml | 16 +- awc/tests/test_client.rs | 918 ++++++++++++----------- examples/basic.rs | 6 +- examples/client.rs | 35 +- examples/uds.rs | 8 +- src/app.rs | 414 ++++++----- src/app_service.rs | 131 ++-- src/config.rs | 119 +-- src/data.rs | 139 ++-- src/extract.rs | 101 +-- src/handler.rs | 239 +++--- src/lib.rs | 30 +- src/middleware/compress.rs | 43 +- src/middleware/defaultheaders.rs | 92 ++- src/middleware/logger.rs | 52 +- src/middleware/mod.rs | 10 +- src/request.rs | 114 +-- src/resource.rs | 441 ++++++----- src/responder.rs | 438 +++++------ src/route.rs | 245 +++--- src/scope.rs | 923 +++++++++++++---------- src/server.rs | 59 +- src/service.rs | 80 +- src/test.rs | 420 ++++++----- src/types/form.rs | 252 ++++--- src/types/json.rs | 443 +++++------ src/types/path.rs | 185 ++--- src/types/payload.rs | 158 ++-- src/types/query.rs | 69 +- src/types/readlines.rs | 138 ++-- src/web.rs | 35 +- test-server/Cargo.toml | 16 +- test-server/src/lib.rs | 2 +- tests/test_httpserver.rs | 70 +- tests/test_server.rs | 1197 ++++++++++++++++-------------- 46 files changed, 4134 insertions(+), 3720 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0b5c4f3d..b1aa7952 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.9" +version = "2.0.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -16,7 +16,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" [package.metadata.docs.rs] -features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls", "uds"] +features = ["openssl", "rustls", "brotli", "flate2-zlib", "secure-cookies", "client"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } @@ -28,20 +28,19 @@ path = "src/lib.rs" [workspace] members = [ -# ".", -# "awc", -# #"actix-http", -# "actix-cors", -# "actix-files", -# "actix-framed", -# "actix-session", -# "actix-identity", -# "actix-multipart", -# "actix-web-actors", -# "actix-web-codegen", -# "test-server", + ".", + "awc", + "actix-http", + "actix-cors", + "actix-files", + "actix-framed", + "actix-session", + "actix-identity", + "actix-multipart", + "actix-web-actors", + "actix-web-codegen", + "test-server", ] -exclude = ["awc", "actix-http", "test-server"] [features] default = ["brotli", "flate2-zlib", "client", "fail"] @@ -64,37 +63,35 @@ secure-cookies = ["actix-http/secure-cookies"] fail = ["actix-http/fail"] # openssl -ssl = ["openssl", "actix-server/ssl", "awc/ssl"] +openssl = ["open-ssl", "actix-server/openssl", "awc/openssl"] # rustls -rust-tls = ["rustls", "actix-server/rust-tls", "awc/rust-tls"] - -# unix domain sockets support -uds = ["actix-server/uds"] +rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"] [dependencies] -actix-codec = "0.1.2" -actix-service = "0.4.1" -actix-utils = "0.4.4" +actix-codec = "0.2.0-alpha.1" +actix-service = "1.0.0-alpha.1" +actix-utils = "0.5.0-alpha.1" actix-router = "0.1.5" -actix-rt = "0.2.4" +actix-rt = "1.0.0-alpha.1" actix-web-codegen = "0.1.2" -actix-http = "0.2.11" -actix-server = "0.6.1" -actix-server-config = "0.1.2" -actix-testing = "0.1.0" -actix-threadpool = "0.1.1" -awc = { version = "0.2.7", optional = true } +actix-http = "0.3.0-alpha.1" +actix-server = "0.8.0-alpha.1" +actix-server-config = "0.3.0-alpha.1" +actix-testing = "0.3.0-alpha.1" +actix-threadpool = "0.2.0-alpha.1" +awc = { version = "0.3.0-alpha.1", optional = true } bytes = "0.4" derive_more = "0.15.0" encoding_rs = "0.8" -futures = "0.1.25" +futures = "0.3.1" hashbrown = "0.6.3" log = "0.4" mime = "0.3" net2 = "0.2.33" parking_lot = "0.9" +pin-project = "0.4.5" regex = "1.0" serde = { version = "1.0", features=["derive"] } serde_json = "1.0" @@ -103,17 +100,17 @@ time = "0.1.42" url = "2.1" # ssl support -openssl = { version="0.10", optional = true } -rustls = { version = "0.15", optional = true } +open-ssl = { version="0.10", package="openssl", optional = true } +rust-tls = { version = "0.16", package="rustls", optional = true } [dev-dependencies] -actix = "0.8.3" -actix-connect = "0.2.2" -actix-http-test = "0.2.4" +# actix = "0.8.3" +actix-connect = "0.3.0-alpha.1" +actix-http-test = "0.3.0-alpha.1" rand = "0.7" env_logger = "0.6" serde_derive = "1.0" -tokio-timer = "0.2.8" +tokio-timer = "0.3.0-alpha.6" brotli2 = "0.3.2" flate2 = "1.0.2" @@ -123,19 +120,18 @@ opt-level = 3 codegen-units = 1 [patch.crates-io] -# actix-web = { path = "." } -# actix-http = { path = "actix-http" } -# actix-http-test = { path = "test-server" } -# actix-web-codegen = { path = "actix-web-codegen" } +actix-web = { path = "." } +actix-http = { path = "actix-http" } +actix-http-test = { path = "test-server" } +actix-web-codegen = { path = "actix-web-codegen" } # actix-web-actors = { path = "actix-web-actors" } -# actix-session = { path = "actix-session" } -# actix-files = { path = "actix-files" } -# actix-multipart = { path = "actix-multipart" } -# awc = { path = "awc" } +actix-session = { path = "actix-session" } +actix-files = { path = "actix-files" } +actix-multipart = { path = "actix-multipart" } +awc = { path = "awc" } actix-codec = { path = "../actix-net/actix-codec" } actix-connect = { path = "../actix-net/actix-connect" } -actix-ioframe = { path = "../actix-net/actix-ioframe" } actix-rt = { path = "../actix-net/actix-rt" } actix-server = { path = "../actix-net/actix-server" } actix-server-config = { path = "../actix-net/actix-server-config" } diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml index 091c9404..57aa5833 100644 --- a/actix-cors/Cargo.toml +++ b/actix-cors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-cors" -version = "0.1.0" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] description = "Cross-origin resource sharing (CORS) for Actix applications." readme = "README.md" @@ -10,14 +10,14 @@ repository = "https://github.com/actix/actix-web.git" documentation = "https://docs.rs/actix-cors/" license = "MIT/Apache-2.0" edition = "2018" -#workspace = ".." +workspace = ".." [lib] name = "actix_cors" path = "src/lib.rs" [dependencies] -actix-web = "1.0.0" -actix-service = "0.4.0" +actix-web = "2.0.0-alpha.1" +actix-service = "1.0.0-alpha.1" derive_more = "0.15.0" -futures = "0.1.25" +futures = "0.3.1" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 1bc063e5..6e33bb41 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.7" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -18,12 +18,12 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "1.0.8", default-features = false } -actix-http = "0.2.11" -actix-service = "0.4.1" +actix-web = { version = "2.0.0-alpha.1", default-features = false } +actix-http = "0.3.0-alpha.1" +actix-service = "1.0.0-alpha.1" bitflags = "1" bytes = "0.4" -futures = "0.1.25" +futures = "0.3.1" derive_more = "0.15.0" log = "0.4" mime = "0.3" @@ -32,4 +32,4 @@ percent-encoding = "2.1" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.8", features=["ssl"] } +actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 16f40a20..8df7a6aa 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -303,9 +303,8 @@ impl Files { /// Set custom directory renderer pub fn files_listing_renderer(mut self, f: F) -> Self where - for<'r, 's> F: - Fn(&'r Directory, &'s HttpRequest) -> Result - + 'static, + for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result + + 'static, { self.renderer = Rc::new(f); self diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index 321041c7..9d32ebed 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-framed" -version = "0.2.1" +version = "0.3.0-alpha.1" authors = ["Nikolay Kim "] description = "Actix framed app server" readme = "README.md" @@ -20,19 +20,19 @@ name = "actix_framed" path = "src/lib.rs" [dependencies] -actix-codec = "0.1.2" -actix-service = "0.4.1" +actix-codec = "0.2.0-alpha.1" +actix-service = "1.0.0-alpha.1" actix-router = "0.1.2" -actix-rt = "0.2.2" -actix-http = "0.2.7" -actix-server-config = "0.1.2" +actix-rt = "1.0.0-alpha.1" +actix-http = "0.3.0-alpha.1" +actix-server-config = "0.2.0-alpha.1" bytes = "0.4" futures = "0.1.25" log = "0.4" [dev-dependencies] -actix-server = { version = "0.6.0", features=["ssl"] } -actix-connect = { version = "0.2.0", features=["ssl"] } -actix-http-test = { version = "0.2.4", features=["ssl"] } -actix-utils = "0.4.4" +actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } +actix-connect = { version = "0.3.0-alpha.1", features=["openssl"] } +actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } +actix-utils = "0.5.0-alpha.1" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index edc93f09..32af97ad 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -13,8 +13,7 @@ categories = ["network-programming", "asynchronous", "web-programming::websocket"] license = "MIT/Apache-2.0" edition = "2018" - -# workspace = ".." +workspace = ".." [package.metadata.docs.rs] features = ["openssl", "fail", "brotli", "flate2-zlib", "secure-cookies"] @@ -114,18 +113,3 @@ actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } env_logger = "0.6" serde_derive = "1.0" open-ssl = { version="0.10", package="openssl" } - -[patch.crates-io] -awc = { path = "../awc" } -actix-http = { path = "." } -actix-http-test = { path = "../test-server" } - -actix-codec = { path = "../../actix-net/actix-codec" } -actix-connect = { path = "../../actix-net/actix-connect" } -actix-rt = { path = "../../actix-net/actix-rt" } -actix-server = { path = "../../actix-net/actix-server" } -actix-server-config = { path = "../../actix-net/actix-server-config" } -actix-service = { path = "../../actix-net/actix-service" } -actix-testing = { path = "../../actix-net/actix-testing" } -actix-threadpool = { path = "../../actix-net/actix-threadpool" } -actix-utils = { path = "../../actix-net/actix-utils" } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 8fa35fea..7e1dae58 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -172,12 +172,11 @@ where /// Finish service configuration and create *http service* for HTTP/1 protocol. pub fn h1(self, service: F) -> H1Service where - B: MessageBody + 'static, + B: MessageBody, F: IntoServiceFactory, - S::Error: Into + 'static, + S::Error: Into, S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, + S::Response: Into>, { let cfg = ServiceConfig::new( self.keep_alive, diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 96775b98..1a52a60f 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -51,10 +51,10 @@ impl Dispatcher where T: IoStream, S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, + S::Error: Into, + // S::Future: 'static, + S::Response: Into>, + B: MessageBody, { pub(crate) fn new( service: CloneableService, @@ -176,9 +176,9 @@ enum ServiceResponseState { impl ServiceResponse where F: Future>, - E: Into + 'static, - I: Into> + 'static, - B: MessageBody + 'static, + E: Into, + I: Into>, + B: MessageBody, { fn prepare_response( &self, @@ -244,9 +244,9 @@ where impl Future for ServiceResponse where F: Future>, - E: Into + 'static, - I: Into> + 'static, - B: MessageBody + 'static, + E: Into, + I: Into>, + B: MessageBody, { type Output = (); diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index e645275a..d05b3768 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-identity" -version = "0.1.0" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] description = "Identity service for actix web framework." readme = "README.md" @@ -17,14 +17,14 @@ name = "actix_identity" path = "src/lib.rs" [dependencies] -actix-web = { version = "1.0.0", default-features = false, features = ["secure-cookies"] } -actix-service = "0.4.0" -futures = "0.1.25" +actix-web = { version = "2.0.0-alpha.1", default-features = false, features = ["secure-cookies"] } +actix-service = "1.0.0-alpha.1" +futures = "0.3.1" serde = "1.0" serde_json = "1.0" time = "0.1.42" [dev-dependencies] -actix-rt = "0.2.2" -actix-http = "0.2.3" +actix-rt = "1.0.0-alpha.1" +actix-http = "0.3.0-alpha.1" bytes = "0.4" \ No newline at end of file diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 2168c259..804d1bb6 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.1.4" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" @@ -18,17 +18,17 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "1.0.0", default-features = false } -actix-service = "0.4.1" +actix-web = { version = "2.0.0-alpha.1", default-features = false } +actix-service = "1.0.0-alpha.1" bytes = "0.4" derive_more = "0.15.0" httparse = "1.3" -futures = "0.1.25" +futures = "0.3.1" log = "0.4" mime = "0.3" time = "0.1" twoway = "0.2" [dev-dependencies] -actix-rt = "0.2.2" -actix-http = "0.2.4" \ No newline at end of file +actix-rt = "1.0.0-alpha.1" +actix-http = "0.3.0-alpha.1" \ No newline at end of file diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index d973661e..3ce2a8b4 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.2.0" +version = "0.3.0-alpha.1" authors = ["Nikolay Kim "] description = "Session for actix web framework." readme = "README.md" @@ -24,15 +24,15 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.0" -actix-service = "0.4.1" +actix-web = "2.0.0-alpha.1" +actix-service = "1.0.0-alpha.1" bytes = "0.4" derive_more = "0.15.0" -futures = "0.1.25" -hashbrown = "0.5.0" +futures = "0.3.1" +hashbrown = "0.6.3" serde = "1.0" serde_json = "1.0" time = "0.1.42" [dev-dependencies] -actix-rt = "0.2.2" +actix-rt = "1.0.0-alpha.1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f8f9a7eb..70d89d5d 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -63,7 +63,7 @@ rust-tls = { version = "0.16.0", package="rustls", optional = true } [dev-dependencies] actix-rt = "1.0.0-alpha.1" -#actix-web = { version = "1.0.8", features=["ssl"] } +actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } actix-utils = "0.5.0-alpha.1" @@ -75,17 +75,3 @@ rand = "0.7" tokio-tcp = "0.1" webpki = { version = "0.21" } rus-tls = { version = "0.16.0", package="rustls", features = ["dangerous_configuration"] } - -[patch.crates-io] -awc = { path = "." } -actix-http = { path = "../actix-http" } -actix-http-test = { path = "../test-server" } - -actix-codec = { path = "../../actix-net/actix-codec" } -actix-connect = { path = "../../actix-net/actix-connect" } -actix-rt = { path = "../../actix-net/actix-rt" } -actix-server = { path = "../../actix-net/actix-server" } -actix-server-config = { path = "../../actix-net/actix-server-config" } -actix-service = { path = "../../actix-net/actix-service" } -actix-threadpool = { path = "../../actix-net/actix-threadpool" } -actix-utils = { path = "../../actix-net/actix-utils" } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index cb38c731..8d7bc227 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -13,8 +13,8 @@ use futures::Future; use rand::Rng; use actix_http::HttpService; -use actix_http_test::TestServer; -use actix_service::{service_fn, NewService}; +use actix_http_test::{bloxk_on, TestServer}; +use actix_service::{service_fn, ServiceFactory}; use actix_web::http::Cookie; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; @@ -44,459 +44,497 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_simple() { - let mut srv = - TestServer::new(|| { + block_on(async { + let mut srv = TestServer::start(|| { HttpService::new(App::new().service( web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), )) }); - let request = srv.get("/").header("x-test", "111").send(); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv.get("/").header("x-test", "111").send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let mut response = srv.block_on(srv.post("/").send()).unwrap(); - assert!(response.status().is_success()); + let mut response = srv.block_on(srv.post("/").send()).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // camel case - let response = srv.block_on(srv.post("/").camel_case().send()).unwrap(); - assert!(response.status().is_success()); + // camel case + let response = srv.block_on(srv.post("/").camel_case().send()).unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_json() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service( - web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), - )) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|_: web::Json| HttpResponse::Ok())), + ), + ) + }); - let request = srv - .get("/") - .header("x-test", "111") - .send_json(&"TEST".to_string()); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .get("/") + .header("x-test", "111") + .send_json(&"TEST".to_string()); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_form() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |_: web::Form>| HttpResponse::Ok(), - )))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |_: web::Form>| HttpResponse::Ok(), + )))) + }); - let mut data = HashMap::new(); - let _ = data.insert("key".to_string(), "TEST".to_string()); + let mut data = HashMap::new(); + let _ = data.insert("key".to_string(), "TEST".to_string()); - let request = srv.get("/").header("x-test", "111").send_form(&data); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv.get("/").header("x-test", "111").send_form(&data); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_timeout() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to_async( - || { - tokio_timer::sleep(Duration::from_millis(200)) - .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) - }, - )))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route( + web::to_async(|| { + tokio_timer::sleep(Duration::from_millis(200)) + .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) + }), + ))) + }); - let client = srv.execute(|| { - awc::Client::build() - .timeout(Duration::from_millis(50)) - .finish() - }); - let request = client.get(srv.url("/")).send(); - match srv.block_on(request) { - Err(SendRequestError::Timeout) => (), - _ => panic!(), - } + let client = srv.execute(|| { + awc::Client::build() + .timeout(Duration::from_millis(50)) + .finish() + }); + let request = client.get(srv.url("/")).send(); + match srv.block_on(request) { + Err(SendRequestError::Timeout) => (), + _ => panic!(), + } + }) } #[test] fn test_timeout_override() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to_async( - || { - tokio_timer::sleep(Duration::from_millis(200)) - .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) - }, - )))) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route( + web::to_async(|| { + tokio_timer::sleep(Duration::from_millis(200)) + .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) + }), + ))) + }); - let client = awc::Client::build() - .timeout(Duration::from_millis(50000)) - .finish(); - let request = client - .get(srv.url("/")) - .timeout(Duration::from_millis(50)) - .send(); - match srv.block_on(request) { - Err(SendRequestError::Timeout) => (), - _ => panic!(), - } + let client = awc::Client::build() + .timeout(Duration::from_millis(50000)) + .finish(); + let request = client + .get(srv.url("/")) + .timeout(Duration::from_millis(50)) + .send(); + match srv.block_on(request) { + Err(SendRequestError::Timeout) => (), + _ => panic!(), + } + }) } #[test] fn test_connection_reuse() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new( - App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), - )) - }); + let mut srv = TestServer::start(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok())), + ))) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); + }) } #[test] fn test_connection_force_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new( - App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), - )) - }); + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok())), + ))) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).force_close().send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).force_close().send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")).force_close(); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")).force_close(); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); + }) } #[test] fn test_connection_server_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().finish())), - ), - )) - }); + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().finish())), + ), + )) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); + }) } #[test] fn test_connection_wait_queue() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new(App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), - ))) - }); + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + ))) + }); - let client = awc::Client::build() - .connector(awc::Connector::new().limit(1).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); - // req 1 - let request = client.get(srv.url("/")).send(); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req2 = client.post(srv.url("/")); - let req2_fut = srv.execute(move || { - let mut fut = req2.send(); - assert!(fut.poll().unwrap().is_not_ready()); - fut - }); + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = srv.execute(move || { + let mut fut = req2.send(); + assert!(fut.poll().unwrap().is_not_ready()); + fut + }); - // read response 1 - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response 1 + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // req 2 - let response = srv.block_on(req2_fut).unwrap(); - assert!(response.status().is_success()); + // req 2 + let response = srv.block_on(req2_fut).unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 1); + // two connection + assert_eq!(num.load(Ordering::Relaxed), 1); + }) } #[test] fn test_connection_wait_queue_force_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), - ), - )) - }); + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), + ), + )) + }); - let client = awc::Client::build() - .connector(awc::Connector::new().limit(1).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); - // req 1 - let request = client.get(srv.url("/")).send(); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); - // req 2 - let req2 = client.post(srv.url("/")); - let req2_fut = srv.execute(move || { - let mut fut = req2.send(); - assert!(fut.poll().unwrap().is_not_ready()); - fut - }); + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = srv.execute(move || { + let mut fut = req2.send(); + assert!(fut.poll().unwrap().is_not_ready()); + fut + }); - // read response 1 - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response 1 + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // req 2 - let response = srv.block_on(req2_fut).unwrap(); - assert!(response.status().is_success()); + // req 2 + let response = srv.block_on(req2_fut).unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); + }) } #[test] fn test_with_query_parameter() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").to( - |req: HttpRequest| { - if req.query_string().contains("qp") { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }, - ))) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").to( + |req: HttpRequest| { + if req.query_string().contains("qp") { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }, + ))) + }); - let res = srv - .block_on(awc::Client::new().get(srv.url("/?qp=5")).send()) - .unwrap(); - assert!(res.status().is_success()); + let res = srv + .block_on(awc::Client::new().get(srv.url("/?qp=5")).send()) + .unwrap(); + assert!(res.status().is_success()); + }) } #[test] fn test_no_decompress() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().wrap(Compress::default()).service( - web::resource("/").route(web::to(|| { - let mut res = HttpResponse::Ok().body(STR); - res.encoding(header::ContentEncoding::Gzip); - res - })), - )) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().wrap(Compress::default()).service( + web::resource("/").route(web::to(|| { + let mut res = HttpResponse::Ok().body(STR); + res.encoding(header::ContentEncoding::Gzip); + res + })), + )) + }); - let mut res = srv - .block_on(awc::Client::new().get(srv.url("/")).no_decompress().send()) - .unwrap(); - assert!(res.status().is_success()); + let mut res = srv + .block_on(awc::Client::new().get(srv.url("/")).no_decompress().send()) + .unwrap(); + assert!(res.status().is_success()); - // read response - let bytes = srv.block_on(res.body()).unwrap(); + // read response + let bytes = srv.block_on(res.body()).unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - // POST - let mut res = srv - .block_on(awc::Client::new().post(srv.url("/")).no_decompress().send()) - .unwrap(); - assert!(res.status().is_success()); + // POST + let mut res = srv + .block_on(awc::Client::new().post(srv.url("/")).no_decompress().send()) + .unwrap(); + assert!(res.status().is_success()); - let bytes = srv.block_on(res.body()).unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + let bytes = srv.block_on(res.body()).unwrap(); + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_client_gzip_encoding() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to(|| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let data = e.finish().unwrap(); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + || { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - })))) - }); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + }, + )))) + }); - // client request - let mut response = srv.block_on(srv.post("/").send()).unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.block_on(srv.post("/").send()).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_client_gzip_encoding_large() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to(|| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.repeat(10).as_ref()).unwrap(); - let data = e.finish().unwrap(); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + || { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.repeat(10).as_ref()).unwrap(); + let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - })))) - }); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + }, + )))) + }); - // client request - let mut response = srv.block_on(srv.post("/").send()).unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.block_on(srv.post("/").send()).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(STR.repeat(10))); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(STR.repeat(10))); + }) } #[test] fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(100_000) - .collect::(); + block_on(async { + let data = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(100_000) + .collect::(); - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |data: Bytes| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - }, - )))) - }); + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |data: Bytes| { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(&data).unwrap(); + let data = e.finish().unwrap(); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + }, + )))) + }); - // client request - let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] fn test_client_brotli_encoding() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |data: Bytes| { - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "br") - .body(data) - }, - )))) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |data: Bytes| { + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(&data).unwrap(); + let data = e.finish().unwrap(); + HttpResponse::Ok() + .header("content-encoding", "br") + .body(data) + }, + )))) + }); - // client request - let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = srv.block_on(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } // #[test] @@ -644,65 +682,67 @@ fn test_client_brotli_encoding() { #[test] fn test_client_cookie_handling() { - fn err() -> Error { - use std::io::{Error as IoError, ErrorKind}; - // stub some generic error - Error::from(IoError::from(ErrorKind::NotFound)) - } - let cookie1 = Cookie::build("cookie1", "value1").finish(); - let cookie2 = Cookie::build("cookie2", "value2") - .domain("www.example.org") - .path("/") - .secure(true) - .http_only(true) - .finish(); - // Q: are all these clones really necessary? A: Yes, possibly - let cookie1b = cookie1.clone(); - let cookie2b = cookie2.clone(); + block_on(async { + fn err() -> Error { + use std::io::{Error as IoError, ErrorKind}; + // stub some generic error + Error::from(IoError::from(ErrorKind::NotFound)) + } + let cookie1 = Cookie::build("cookie1", "value1").finish(); + let cookie2 = Cookie::build("cookie2", "value2") + .domain("www.example.org") + .path("/") + .secure(true) + .http_only(true) + .finish(); + // Q: are all these clones really necessary? A: Yes, possibly + let cookie1b = cookie1.clone(); + let cookie2b = cookie2.clone(); - let mut srv = TestServer::new(move || { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); + let mut srv = TestServer::new(move || { + let cookie1 = cookie1b.clone(); + let cookie2 = cookie2b.clone(); - HttpService::new(App::new().route( - "/", - web::to(move |req: HttpRequest| { - // Check cookies were sent correctly - req.cookie("cookie1") - .ok_or_else(err) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(err()) - } - }) - .and_then(|()| req.cookie("cookie2").ok_or_else(err)) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(err()) - } - }) - // Send some cookies back - .map(|_| { - HttpResponse::Ok() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - }) - }), - )) - }); + HttpService::new(App::new().route( + "/", + web::to(move |req: HttpRequest| { + // Check cookies were sent correctly + req.cookie("cookie1") + .ok_or_else(err) + .and_then(|c1| { + if c1.value() == "value1" { + Ok(()) + } else { + Err(err()) + } + }) + .and_then(|()| req.cookie("cookie2").ok_or_else(err)) + .and_then(|c2| { + if c2.value() == "value2" { + Ok(()) + } else { + Err(err()) + } + }) + // Send some cookies back + .map(|_| { + HttpResponse::Ok() + .cookie(cookie1.clone()) + .cookie(cookie2.clone()) + .finish() + }) + }), + )) + }); - let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, cookie1); - let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, cookie2); + let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + let c1 = response.cookie("cookie1").expect("Missing cookie1"); + assert_eq!(c1, cookie1); + let c2 = response.cookie("cookie2").expect("Missing cookie2"); + assert_eq!(c2, cookie2); + }) } // #[test] @@ -737,56 +777,60 @@ fn test_client_cookie_handling() { #[test] fn client_basic_auth() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().route( - "/", - web::to(|req: HttpRequest| { - if req - .headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().route( + "/", + web::to(|req: HttpRequest| { + if req + .headers() + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap() + == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + }); - // set authorization header to Basic - let request = srv.get("/").basic_auth("username", Some("password")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + // set authorization header to Basic + let request = srv.get("/").basic_auth("username", Some("password")); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn client_bearer_auth() { - let mut srv = TestServer::new(|| { - HttpService::new(App::new().route( - "/", - web::to(|req: HttpRequest| { - if req - .headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - == "Bearer someS3cr3tAutht0k3n" - { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - }); + block_on(async { + let mut srv = TestServer::new(|| { + HttpService::new(App::new().route( + "/", + web::to(|req: HttpRequest| { + if req + .headers() + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap() + == "Bearer someS3cr3tAutht0k3n" + { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + }); - // set authorization header to Bearer - let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + // set authorization header to Bearer + let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); + let response = srv.block_on(request.send()).unwrap(); + assert!(response.status().is_success()); + }) } diff --git a/examples/basic.rs b/examples/basic.rs index 46440d70..76c97732 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,5 +1,3 @@ -use futures::IntoFuture; - use actix_web::{ get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, }; @@ -10,7 +8,7 @@ fn index(req: HttpRequest, name: web::Path) -> String { format!("Hello: {}!\r\n", name) } -fn index_async(req: HttpRequest) -> impl IntoFuture { +async fn index_async(req: HttpRequest) -> Result<&'static str, Error> { println!("REQ: {:?}", req); Ok("Hello world!\r\n") } @@ -28,7 +26,7 @@ fn main() -> std::io::Result<()> { App::new() .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .wrap(middleware::Compress::default()) - .wrap(middleware::Logger::default()) + // .wrap(middleware::Logger::default()) .service(index) .service(no_params) .service( diff --git a/examples/client.rs b/examples/client.rs index 8a75fd30..90a362fe 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -1,26 +1,27 @@ use actix_http::Error; use actix_rt::System; -use futures::{future::lazy, Future}; fn main() -> Result<(), Error> { std::env::set_var("RUST_LOG", "actix_http=trace"); env_logger::init(); - System::new("test").block_on(lazy(|| { - awc::Client::new() - .get("https://www.rust-lang.org/") // <- Create request builder - .header("User-Agent", "Actix-web") - .send() // <- Send http request - .from_err() - .and_then(|mut response| { - // <- server http response - println!("Response: {:?}", response); + System::new("test").block_on(async { + let client = awc::Client::new(); - // read response body - response - .body() - .from_err() - .map(|body| println!("Downloaded: {:?} bytes", body.len())) - }) - })) + // Create request builder, configure request and send + let mut response = client + .get("https://www.rust-lang.org/") + .header("User-Agent", "Actix-web") + .send() + .await?; + + // server http response + println!("Response: {:?}", response); + + // read response body + let body = response.body().await?; + println!("Downloaded: {:?} bytes", body.len()); + + Ok(()) + }) } diff --git a/examples/uds.rs b/examples/uds.rs index 9dc82903..7da41a2c 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -1,5 +1,3 @@ -use futures::IntoFuture; - use actix_web::{ get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, }; @@ -10,7 +8,7 @@ fn index(req: HttpRequest, name: web::Path) -> String { format!("Hello: {}!\r\n", name) } -fn index_async(req: HttpRequest) -> impl IntoFuture { +async fn index_async(req: HttpRequest) -> Result<&'static str, Error> { println!("REQ: {:?}", req); Ok("Hello world!\r\n") } @@ -29,7 +27,7 @@ fn main() -> std::io::Result<()> { App::new() .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .wrap(middleware::Compress::default()) - .wrap(middleware::Logger::default()) + // .wrap(middleware::Logger::default()) .service(index) .service(no_params) .service( @@ -38,7 +36,7 @@ fn main() -> std::io::Result<()> { middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), ) .default_service( - web::route().to(|| HttpResponse::MethodNotAllowed()), + web::route().to(|| ok(HttpResponse::MethodNotAllowed())), ) .route(web::get().to_async(index_async)), ) diff --git a/src/app.rs b/src/app.rs index f93859c7..28825660 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,14 +1,17 @@ use std::cell::RefCell; use std::fmt; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_http::body::{Body, MessageBody}; use actix_service::boxed::{self, BoxedNewService}; use actix_service::{ - apply_transform, IntoNewService, IntoTransform, NewService, Transform, + apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, Transform, }; -use futures::{Future, IntoFuture}; +use futures::future::{FutureExt, LocalBoxFuture}; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; use crate::config::{AppConfig, AppConfigInner, ServiceConfig}; @@ -18,19 +21,19 @@ use crate::error::Error; use crate::resource::Resource; use crate::route::Route; use crate::service::{ - HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, + AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type FnDataFactory = - Box Box, Error = ()>>>; + Box LocalBoxFuture<'static, Result, ()>>>; /// Application builder - structure that follows the builder pattern /// for building application instances. pub struct App { endpoint: T, - services: Vec>, + services: Vec>, default: Option>, factory_ref: Rc>>, data: Vec>, @@ -61,7 +64,7 @@ impl App { impl App where B: MessageBody, - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -107,24 +110,30 @@ where /// Set application data factory. This function is /// similar to `.data()` but it accepts data factory. Data object get /// constructed asynchronously during application initialization. - pub fn data_factory(mut self, data: F) -> Self + pub fn data_factory(mut self, data: F) -> Self where F: Fn() -> Out + 'static, - Out: IntoFuture + 'static, - Out::Error: std::fmt::Debug, + Out: Future> + 'static, + D: 'static, + E: std::fmt::Debug, { self.data_factories.push(Box::new(move || { - Box::new( - data() - .into_future() - .map_err(|e| { - log::error!("Can not construct data instance: {:?}", e); - }) - .map(|data| { - let data: Box = Box::new(Data::new(data)); - data - }), - ) + { + let fut = data(); + async move { + match fut.await { + Err(e) => { + log::error!("Can not construct data instance: {:?}", e); + Err(()) + } + Ok(data) => { + let data: Box = Box::new(Data::new(data)); + Ok(data) + } + } + } + } + .boxed_local() })); self } @@ -267,8 +276,8 @@ where /// ``` pub fn default_service(mut self, f: F) -> Self where - F: IntoNewService, - U: NewService< + F: IntoServiceFactory, + U: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -277,11 +286,9 @@ where U::InitError: fmt::Debug, { // create and configure default resource - self.default = Some(Rc::new(boxed::new_service( - f.into_new_service().map_init_err(|e| { - log::error!("Can not construct default service: {:?}", e) - }), - ))); + self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err( + |e| log::error!("Can not construct default service: {:?}", e), + )))); self } @@ -350,11 +357,11 @@ where /// .route("/index.html", web::get().to(index)); /// } /// ``` - pub fn wrap( + pub fn wrap( self, - mw: F, + mw: M, ) -> App< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -372,11 +379,9 @@ where InitError = (), >, B1: MessageBody, - F: IntoTransform, { - let endpoint = apply_transform(mw, self.endpoint); App { - endpoint, + endpoint: apply(mw, self.endpoint), data: self.data, data_factories: self.data_factories, services: self.services, @@ -407,13 +412,16 @@ where /// /// fn main() { /// let app = App::new() - /// .wrap_fn(|req, srv| - /// srv.call(req).map(|mut res| { + /// .wrap_fn(|req, srv| { + /// let fut = srv.call(req); + /// async { + /// let mut res = fut.await?; /// res.headers_mut().insert( /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), /// ); - /// res - /// })) + /// Ok(res) + /// } + /// }) /// .route("/index.html", web::get().to(index)); /// } /// ``` @@ -421,7 +429,7 @@ where self, mw: F, ) -> App< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -433,16 +441,26 @@ where where B1: MessageBody, F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, - R: IntoFuture, Error = Error>, + R: Future, Error>>, { - self.wrap(mw) + App { + endpoint: apply_fn_factory(self.endpoint, mw), + data: self.data, + data_factories: self.data_factories, + services: self.services, + default: self.default, + factory_ref: self.factory_ref, + config: self.config, + external: self.external, + _t: PhantomData, + } } } -impl IntoNewService> for App +impl IntoServiceFactory> for App where B: MessageBody, - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -450,7 +468,7 @@ where InitError = (), >, { - fn into_new_service(self) -> AppInit { + fn into_factory(self) -> AppInit { AppInit { data: Rc::new(self.data), data_factories: Rc::new(self.data_factories), @@ -468,82 +486,89 @@ where mod tests { use actix_service::Service; use bytes::Bytes; - use futures::{Future, IntoFuture}; + use futures::future::{ok, Future}; use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::middleware::DefaultHeaders; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{ - block_fn, block_on, call_service, init_service, read_body, TestRequest, - }; + use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; use crate::{web, Error, HttpRequest, HttpResponse}; #[test] fn test_default_resource() { - let mut srv = init_service( - App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = block_fn(|| srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + block_on(async { + let mut srv = init_service( + App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/blah").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/blah").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = init_service( - App::new() - .service(web::resource("/test").to(|| HttpResponse::Ok())) - .service( - web::resource("/test2") - .default_service(|r: ServiceRequest| { - r.into_response(HttpResponse::Created()) - }) - .route(web::get().to(|| HttpResponse::Ok())), - ) - .default_service(|r: ServiceRequest| { - r.into_response(HttpResponse::MethodNotAllowed()) - }), - ); + let mut srv = init_service( + App::new() + .service(web::resource("/test").to(|| HttpResponse::Ok())) + .service( + web::resource("/test2") + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::Created())) + }) + .route(web::get().to(|| HttpResponse::Ok())), + ) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::MethodNotAllowed())) + }), + ) + .await; - let req = TestRequest::with_uri("/blah").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/blah").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let req = TestRequest::with_uri("/test2").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/test2") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/test2") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_data_factory() { - let mut srv = - init_service(App::new().data_factory(|| Ok::<_, ()>(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + block_on(async { + let mut srv = + init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let mut srv = - init_service(App::new().data_factory(|| Ok::<_, ()>(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + let mut srv = + init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + }) } fn md( req: ServiceRequest, srv: &mut S, - ) -> impl IntoFuture, Error = Error> + ) -> impl Future, Error>> where S: Service< Request = ServiceRequest, @@ -551,112 +576,141 @@ mod tests { Error = Error, >, { - srv.call(req).map(|mut res| { + let fut = srv.call(req); + async move { + let mut res = fut.await?; res.headers_mut() .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); - res - }) + Ok(res) + } } #[test] fn test_wrap() { - let mut srv = init_service( - App::new() - .wrap(md) - .route("/test", web::get().to(|| HttpResponse::Ok())), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); + block_on(async { + let mut srv = + init_service( + App::new() + .wrap(DefaultHeaders::new().header( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + )) + .route("/test", web::get().to(|| HttpResponse::Ok())), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + }) } #[test] fn test_router_wrap() { - let mut srv = init_service( - App::new() - .route("/test", web::get().to(|| HttpResponse::Ok())) - .wrap(md), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); + block_on(async { + let mut srv = + init_service( + App::new() + .route("/test", web::get().to(|| HttpResponse::Ok())) + .wrap(DefaultHeaders::new().header( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + )), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + }) } #[test] fn test_wrap_fn() { - let mut srv = init_service( - App::new() - .wrap_fn(|req, srv| { - srv.call(req).map(|mut res| { - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - res + block_on(async { + let mut srv = init_service( + App::new() + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async move { + let mut res = fut.await?; + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + Ok(res) + } }) - }) - .service(web::resource("/test").to(|| HttpResponse::Ok())), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); + .service(web::resource("/test").to(|| HttpResponse::Ok())), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + }) } #[test] fn test_router_wrap_fn() { - let mut srv = init_service( - App::new() - .route("/test", web::get().to(|| HttpResponse::Ok())) - .wrap_fn(|req, srv| { - srv.call(req).map(|mut res| { - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - res - }) - }), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); + block_on(async { + let mut srv = init_service( + App::new() + .route("/test", web::get().to(|| HttpResponse::Ok())) + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { + let mut res = fut.await?; + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + Ok(res) + } + }), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + }) } #[test] fn test_external_resource() { - let mut srv = init_service( - App::new() - .external_resource("youtube", "https://youtube.com/watch/{video_id}") - .route( - "/test", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["12345"]).unwrap() - )) - }), - ), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp); - assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); + block_on(async { + let mut srv = init_service( + App::new() + .external_resource("youtube", "https://youtube.com/watch/{video_id}") + .route( + "/test", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["12345"]).unwrap() + )) + }), + ), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); + }) } } diff --git a/src/app_service.rs b/src/app_service.rs index 513b4aa4..7407ee2f 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,14 +1,16 @@ use std::cell::RefCell; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_http::{Extensions, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_server_config::ServerConfig; use actix_service::boxed::{self, BoxedNewService, BoxedService}; -use actix_service::{service_fn, NewService, Service}; -use futures::future::{ok, Either, FutureResult}; -use futures::{Async, Future, Poll}; +use actix_service::{service_fn, Service, ServiceFactory}; +use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; use crate::config::{AppConfig, AppService}; use crate::data::DataFactory; @@ -16,23 +18,20 @@ use crate::error::Error; use crate::guard::Guard; use crate::request::{HttpRequest, HttpRequestPool}; use crate::rmap::ResourceMap; -use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; +use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse}; type Guards = Vec>; type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxedResponse = Either< - FutureResult, - Box>, ->; +type BoxedResponse = LocalBoxFuture<'static, Result>; type FnDataFactory = - Box Box, Error = ()>>>; + Box LocalBoxFuture<'static, Result, ()>>>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. pub struct AppInit where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -44,15 +43,15 @@ where pub(crate) data: Rc>>, pub(crate) data_factories: Rc>, pub(crate) config: RefCell, - pub(crate) services: Rc>>>, + pub(crate) services: Rc>>>, pub(crate) default: Option>, pub(crate) factory_ref: Rc>>, pub(crate) external: RefCell>, } -impl NewService for AppInit +impl ServiceFactory for AppInit where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -71,8 +70,8 @@ where fn new_service(&self, cfg: &ServerConfig) -> Self::Future { // update resource default service let default = self.default.clone().unwrap_or_else(|| { - Rc::new(boxed::new_service(service_fn(|req: ServiceRequest| { - Ok(req.into_response(Response::NotFound().finish())) + Rc::new(boxed::factory(service_fn(|req: ServiceRequest| { + ok(req.into_response(Response::NotFound().finish())) }))) }); @@ -135,23 +134,25 @@ where } } +#[pin_project::pin_project] pub struct AppInitResult where - T: NewService, + T: ServiceFactory, { endpoint: Option, + #[pin] endpoint_fut: T::Future, rmap: Rc, config: AppConfig, data: Rc>>, data_factories: Vec>, - data_factories_fut: Vec, Error = ()>>>, + data_factories_fut: Vec, ()>>>, _t: PhantomData, } impl Future for AppInitResult where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -159,48 +160,49 @@ where InitError = (), >, { - type Item = AppInitService; - type Error = (); + type Output = Result, ()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); - fn poll(&mut self) -> Poll { // async data factories let mut idx = 0; - while idx < self.data_factories_fut.len() { - match self.data_factories_fut[idx].poll()? { - Async::Ready(f) => { - self.data_factories.push(f); - let _ = self.data_factories_fut.remove(idx); + while idx < this.data_factories_fut.len() { + match Pin::new(&mut this.data_factories_fut[idx]).poll(cx)? { + Poll::Ready(f) => { + this.data_factories.push(f); + let _ = this.data_factories_fut.remove(idx); } - Async::NotReady => idx += 1, + Poll::Pending => idx += 1, } } - if self.endpoint.is_none() { - if let Async::Ready(srv) = self.endpoint_fut.poll()? { - self.endpoint = Some(srv); + if this.endpoint.is_none() { + if let Poll::Ready(srv) = this.endpoint_fut.poll(cx)? { + *this.endpoint = Some(srv); } } - if self.endpoint.is_some() && self.data_factories_fut.is_empty() { + if this.endpoint.is_some() && this.data_factories_fut.is_empty() { // create app data container let mut data = Extensions::new(); - for f in self.data.iter() { + for f in this.data.iter() { f.create(&mut data); } - for f in &self.data_factories { + for f in this.data_factories.iter() { f.create(&mut data); } - Ok(Async::Ready(AppInitService { - service: self.endpoint.take().unwrap(), - rmap: self.rmap.clone(), - config: self.config.clone(), + Poll::Ready(Ok(AppInitService { + service: this.endpoint.take().unwrap(), + rmap: this.rmap.clone(), + config: this.config.clone(), data: Rc::new(data), pool: HttpRequestPool::create(), })) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -226,8 +228,8 @@ where type Error = T::Error; type Future = T::Future; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: Request) -> Self::Future { @@ -270,7 +272,7 @@ pub struct AppRoutingFactory { default: Rc, } -impl NewService for AppRoutingFactory { +impl ServiceFactory for AppRoutingFactory { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -288,7 +290,7 @@ impl NewService for AppRoutingFactory { CreateAppRoutingItem::Future( Some(path.clone()), guards.borrow_mut().take(), - service.new_service(&()), + service.new_service(&()).boxed_local(), ) }) .collect(), @@ -298,14 +300,14 @@ impl NewService for AppRoutingFactory { } } -type HttpServiceFut = Box>; +type HttpServiceFut = LocalBoxFuture<'static, Result>; /// Create app service #[doc(hidden)] pub struct AppRoutingFactoryResponse { fut: Vec, default: Option, - default_fut: Option>>, + default_fut: Option>>, } enum CreateAppRoutingItem { @@ -314,16 +316,15 @@ enum CreateAppRoutingItem { } impl Future for AppRoutingFactoryResponse { - type Item = AppRouting; - type Error = (); + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let mut done = true; if let Some(ref mut fut) = self.default_fut { - match fut.poll()? { - Async::Ready(default) => self.default = Some(default), - Async::NotReady => done = false, + match Pin::new(fut).poll(cx)? { + Poll::Ready(default) => self.default = Some(default), + Poll::Pending => done = false, } } @@ -334,11 +335,12 @@ impl Future for AppRoutingFactoryResponse { ref mut path, ref mut guards, ref mut fut, - ) => match fut.poll()? { - Async::Ready(service) => { + ) => match Pin::new(fut).poll(cx) { + Poll::Ready(Ok(service)) => { Some((path.take().unwrap(), guards.take(), service)) } - Async::NotReady => { + Poll::Ready(Err(_)) => return Poll::Ready(Err(())), + Poll::Pending => { done = false; None } @@ -364,13 +366,13 @@ impl Future for AppRoutingFactoryResponse { } router }); - Ok(Async::Ready(AppRouting { + Poll::Ready(Ok(AppRouting { ready: None, router: router.finish(), default: self.default.take(), })) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -387,11 +389,11 @@ impl Service for AppRouting { type Error = Error; type Future = BoxedResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { + fn poll_ready(&mut self, _: &mut Context) -> Poll> { if self.ready.is_none() { - Ok(Async::Ready(())) + Poll::Ready(Ok(())) } else { - Ok(Async::NotReady) + Poll::Pending } } @@ -413,7 +415,7 @@ impl Service for AppRouting { default.call(req) } else { let req = req.into_parts().0; - Either::A(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + ok(ServiceResponse::new(req, Response::NotFound().finish())).boxed_local() } } } @@ -429,7 +431,7 @@ impl AppEntry { } } -impl NewService for AppEntry { +impl ServiceFactory for AppEntry { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -464,15 +466,16 @@ mod tests { #[test] fn drop_data() { let data = Arc::new(AtomicBool::new(false)); - { + test::block_on(async { let mut app = test::init_service( App::new() .data(DropData(data.clone())) .service(web::resource("/test").to(|| HttpResponse::Ok())), - ); + ) + .await; let req = test::TestRequest::with_uri("/test").to_request(); - let _ = test::block_on(app.call(req)).unwrap(); - } + let _ = app.call(req).await.unwrap(); + }); assert!(data.load(Ordering::Relaxed)); } } diff --git a/src/config.rs b/src/config.rs index 63fd31d2..3ce18f98 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use actix_http::Extensions; use actix_router::ResourceDef; -use actix_service::{boxed, IntoNewService, NewService}; +use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; use crate::data::{Data, DataFactory}; use crate::error::Error; @@ -12,7 +12,7 @@ use crate::resource::Resource; use crate::rmap::ResourceMap; use crate::route::Route; use crate::service::{ - HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, + AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; @@ -102,11 +102,11 @@ impl AppService { &mut self, rdef: ResourceDef, guards: Option>>, - service: F, + factory: F, nested: Option>, ) where - F: IntoNewService, - S: NewService< + F: IntoServiceFactory, + S: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -116,7 +116,7 @@ impl AppService { { self.services.push(( rdef, - boxed::new_service(service.into_new_service()), + boxed::factory(factory.into_factory()), guards, nested, )); @@ -174,7 +174,7 @@ impl Default for AppConfigInner { /// to set of external methods. This could help with /// modularization of big application configuration. pub struct ServiceConfig { - pub(crate) services: Vec>, + pub(crate) services: Vec>, pub(crate) data: Vec>, pub(crate) external: Vec, } @@ -251,17 +251,19 @@ mod tests { #[test] fn test_data() { - let cfg = |cfg: &mut ServiceConfig| { - cfg.data(10usize); - }; + block_on(async { + let cfg = |cfg: &mut ServiceConfig| { + cfg.data(10usize); + }; - let mut srv = - init_service(App::new().configure(cfg).service( + let mut srv = init_service(App::new().configure(cfg).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + )) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } // #[test] @@ -298,50 +300,57 @@ mod tests { #[test] fn test_external_resource() { - let mut srv = init_service( - App::new() - .configure(|cfg| { - cfg.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - }) - .route( - "/test", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["12345"]).unwrap() - )) - }), - ), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp); - assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); + block_on(async { + let mut srv = init_service( + App::new() + .configure(|cfg| { + cfg.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + }) + .route( + "/test", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["12345"]).unwrap() + )) + }), + ), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); + }) } #[test] fn test_service() { - let mut srv = init_service(App::new().configure(|cfg| { - cfg.service( - web::resource("/test").route(web::get().to(|| HttpResponse::Created())), - ) - .route("/index.html", web::get().to(|| HttpResponse::Ok())); - })); + block_on(async { + let mut srv = init_service(App::new().configure(|cfg| { + cfg.service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Created())), + ) + .route("/index.html", web::get().to(|| HttpResponse::Ok())); + })) + .await; - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/index.html") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/index.html") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } } diff --git a/src/data.rs b/src/data.rs index 14e293bc..a11175c1 100644 --- a/src/data.rs +++ b/src/data.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use actix_http::error::{Error, ErrorInternalServerError}; use actix_http::Extensions; +use futures::future::{err, ok, Ready}; use crate::dev::Payload; use crate::extract::FromRequest; @@ -101,19 +102,19 @@ impl Clone for Data { impl FromRequest for Data { type Config = (); type Error = Error; - type Future = Result; + type Future = Ready>; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { if let Some(st) = req.get_app_data::() { - Ok(st) + ok(st) } else { log::debug!( "Failed to construct App-level Data extractor. \ Request path: {:?}", req.path() ); - Err(ErrorInternalServerError( + err(ErrorInternalServerError( "App data is not configured, to configure use App::data()", )) } @@ -142,85 +143,99 @@ mod tests { #[test] fn test_data_extractor() { - let mut srv = - init_service(App::new().data(10usize).service( + block_on(async { + let mut srv = init_service(App::new().data(10usize).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); + )) + .await; - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let mut srv = - init_service(App::new().data(10u32).service( + let mut srv = init_service(App::new().data(10u32).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + )) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + }) } #[test] fn test_register_data_extractor() { - let mut srv = - init_service(App::new().register_data(Data::new(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); + block_on(async { + let mut srv = + init_service(App::new().register_data(Data::new(10usize)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )) + .await; - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let mut srv = - init_service(App::new().register_data(Data::new(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + let mut srv = + init_service(App::new().register_data(Data::new(10u32)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + }) } #[test] fn test_route_data_extractor() { - let mut srv = - init_service(App::new().service(web::resource("/").data(10usize).route( - web::get().to(|data: web::Data| { - let _ = data.clone(); - HttpResponse::Ok() - }), - ))); + block_on(async { + let mut srv = init_service(App::new().service( + web::resource("/").data(10usize).route(web::get().to( + |data: web::Data| { + let _ = data.clone(); + HttpResponse::Ok() + }, + )), + )) + .await; - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - // different type - let mut srv = init_service( - App::new().service( - web::resource("/") - .data(10u32) - .route(web::get().to(|_: web::Data| HttpResponse::Ok())), - ), - ); - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + // different type + let mut srv = init_service( + App::new().service( + web::resource("/") + .data(10u32) + .route(web::get().to(|_: web::Data| HttpResponse::Ok())), + ), + ) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + }) } #[test] fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( - web::resource("/").data(10usize).route(web::get().to( - |data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }, - )), - )); + block_on(async { + let mut srv = init_service(App::new().data(1usize).service( + web::resource("/").data(10usize).route(web::get().to( + |data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }, + )), + )) + .await; - let req = TestRequest::default().to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } } diff --git a/src/extract.rs b/src/extract.rs index 42563731..20a1180e 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,8 +1,10 @@ //! Request extractors +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; use actix_http::error::Error; -use futures::future::ok; -use futures::{future, Async, Future, IntoFuture, Poll}; +use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; use crate::dev::Payload; use crate::request::HttpRequest; @@ -15,7 +17,7 @@ pub trait FromRequest: Sized { type Error: Into; /// Future that resolves to a Self - type Future: IntoFuture; + type Future: Future>; /// Configuration for this extractor type Config: Default + 'static; @@ -48,6 +50,7 @@ pub trait FromRequest: Sized { /// ```rust /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; +/// use futures::future::{ok, err, Ready}; /// use serde_derive::Deserialize; /// use rand; /// @@ -58,14 +61,14 @@ pub trait FromRequest: Sized { /// /// impl FromRequest for Thing { /// type Error = Error; -/// type Future = Result; +/// type Future = Ready>; /// type Config = (); /// /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) +/// ok(Thing { name: "thingy".into() }) /// } else { -/// Err(ErrorBadRequest("no luck")) +/// err(ErrorBadRequest("no luck")) /// } /// /// } @@ -94,21 +97,19 @@ where { type Config = T::Config; type Error = Error; - type Future = Box, Error = Error>>; + type Future = LocalBoxFuture<'static, Result, Error>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - Box::new( - T::from_request(req, payload) - .into_future() - .then(|r| match r { - Ok(v) => future::ok(Some(v)), - Err(e) => { - log::debug!("Error for Option extractor: {}", e.into()); - future::ok(None) - } - }), - ) + T::from_request(req, payload) + .then(|r| match r { + Ok(v) => ok(Some(v)), + Err(e) => { + log::debug!("Error for Option extractor: {}", e.into()); + ok(None) + } + }) + .boxed_local() } } @@ -121,6 +122,7 @@ where /// ```rust /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; +/// use futures::future::{ok, err, Ready}; /// use serde_derive::Deserialize; /// use rand; /// @@ -131,14 +133,14 @@ where /// /// impl FromRequest for Thing { /// type Error = Error; -/// type Future = Result; +/// type Future = Ready>; /// type Config = (); /// /// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { /// if rand::random() { -/// Ok(Thing { name: "thingy".into() }) +/// ok(Thing { name: "thingy".into() }) /// } else { -/// Err(ErrorBadRequest("no luck")) +/// err(ErrorBadRequest("no luck")) /// } /// } /// } @@ -157,26 +159,24 @@ where /// ); /// } /// ``` -impl FromRequest for Result +impl FromRequest for Result where - T: FromRequest, - T::Future: 'static, + T: FromRequest + 'static, T::Error: 'static, + T::Future: 'static, { type Config = T::Config; type Error = Error; - type Future = Box, Error = Error>>; + type Future = LocalBoxFuture<'static, Result, Error>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - Box::new( - T::from_request(req, payload) - .into_future() - .then(|res| match res { - Ok(v) => ok(Ok(v)), - Err(e) => ok(Err(e)), - }), - ) + T::from_request(req, payload) + .then(|res| match res { + Ok(v) => ok(Ok(v)), + Err(e) => ok(Err(e)), + }) + .boxed_local() } } @@ -184,10 +184,10 @@ where impl FromRequest for () { type Config = (); type Error = Error; - type Future = Result<(), Error>; + type Future = Ready>; fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { - Ok(()) + ok(()) } } @@ -204,43 +204,44 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { $fut_type { items: <($(Option<$T>,)+)>::default(), - futs: ($($T::from_request(req, payload).into_future(),)+), + futs: ($($T::from_request(req, payload),)+), } } } #[doc(hidden)] + #[pin_project::pin_project] pub struct $fut_type<$($T: FromRequest),+> { items: ($(Option<$T>,)+), - futs: ($(<$T::Future as futures::IntoFuture>::Future,)+), + futs: ($($T::Future,)+), } impl<$($T: FromRequest),+> Future for $fut_type<$($T),+> { - type Item = ($($T,)+); - type Error = Error; + type Output = Result<($($T,)+), Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); - fn poll(&mut self) -> Poll { let mut ready = true; - $( - if self.items.$n.is_none() { - match self.futs.$n.poll() { - Ok(Async::Ready(item)) => { - self.items.$n = Some(item); + if this.items.$n.is_none() { + match unsafe { Pin::new_unchecked(&mut this.futs.$n) }.poll(cx) { + Poll::Ready(Ok(item)) => { + this.items.$n = Some(item); } - Ok(Async::NotReady) => ready = false, - Err(e) => return Err(e.into()), + Poll::Pending => ready = false, + Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), } } )+ if ready { - Ok(Async::Ready( - ($(self.items.$n.take().unwrap(),)+) + Poll::Ready(Ok( + ($(this.items.$n.take().unwrap(),)+) )) } else { - Ok(Async::NotReady) + Poll::Pending } } } diff --git a/src/handler.rs b/src/handler.rs index 078abbf1..7f5d5294 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,10 +1,14 @@ use std::convert::Infallible; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use actix_http::{Error, Response}; -use actix_service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{try_ready, Async, Future, IntoFuture, Poll}; +use actix_service::{Service, ServiceFactory}; +use futures::future::{ok, Ready}; +use futures::ready; +use pin_project::pin_project; use crate::extract::FromRequest; use crate::request::HttpRequest; @@ -73,14 +77,14 @@ where type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Infallible; - type Future = HandlerServiceResponse<::Future>; + type Future = HandlerServiceResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - let fut = self.hnd.call(param).respond_to(&req).into_future(); + let fut = self.hnd.call(param).respond_to(&req); HandlerServiceResponse { fut, req: Some(req), @@ -88,53 +92,48 @@ where } } -pub struct HandlerServiceResponse { - fut: T, +#[pin_project] +pub struct HandlerServiceResponse { + #[pin] + fut: T::Future, req: Option, } -impl Future for HandlerServiceResponse -where - T: Future, - T::Error: Into, -{ - type Item = ServiceResponse; - type Error = Infallible; +impl Future for HandlerServiceResponse { + type Output = Result; - fn poll(&mut self) -> Poll { - match self.fut.poll() { - Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( - self.req.take().unwrap(), - res, - ))), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); + + match this.fut.poll(cx) { + Poll::Ready(Ok(res)) => { + Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) + } + Poll::Pending => Poll::Pending, + Poll::Ready(Err(e)) => { let res: Response = e.into().into(); - Ok(Async::Ready(ServiceResponse::new( - self.req.take().unwrap(), - res, - ))) + Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) } } } } /// Async handler converter factory -pub trait AsyncFactory: Clone + 'static +pub trait AsyncFactory: Clone + 'static where - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + R: Future>, + O: Responder, + E: Into, { fn call(&self, param: T) -> R; } -impl AsyncFactory<(), R> for F +impl AsyncFactory<(), R, O, E> for F where F: Fn() -> R + Clone + 'static, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + R: Future>, + O: Responder, + E: Into, { fn call(&self, _: ()) -> R { (self)() @@ -142,23 +141,23 @@ where } #[doc(hidden)] -pub struct AsyncHandler +pub struct AsyncHandler where - F: AsyncFactory, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + F: AsyncFactory, + R: Future>, + O: Responder, + E: Into, { hnd: F, - _t: PhantomData<(T, R)>, + _t: PhantomData<(T, R, O, E)>, } -impl AsyncHandler +impl AsyncHandler where - F: AsyncFactory, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + F: AsyncFactory, + R: Future>, + O: Responder, + E: Into, { pub fn new(hnd: F) -> Self { AsyncHandler { @@ -168,12 +167,12 @@ where } } -impl Clone for AsyncHandler +impl Clone for AsyncHandler where - F: AsyncFactory, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + F: AsyncFactory, + R: Future>, + O: Responder, + E: Into, { fn clone(&self) -> Self { AsyncHandler { @@ -183,25 +182,25 @@ where } } -impl Service for AsyncHandler +impl Service for AsyncHandler where - F: AsyncFactory, - R: IntoFuture, - R::Item: Responder, - R::Error: Into, + F: AsyncFactory, + R: Future>, + O: Responder, + E: Into, { type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Infallible; - type Future = AsyncHandlerServiceResponse; + type Future = AsyncHandlerServiceResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { AsyncHandlerServiceResponse { - fut: self.hnd.call(param).into_future(), + fut: self.hnd.call(param), fut2: None, req: Some(req), } @@ -209,56 +208,54 @@ where } #[doc(hidden)] -pub struct AsyncHandlerServiceResponse +#[pin_project] +pub struct AsyncHandlerServiceResponse where - T: Future, - T::Item: Responder, + T: Future>, + R: Responder, + E: Into, { + #[pin] fut: T, - fut2: Option<<::Future as IntoFuture>::Future>, + #[pin] + fut2: Option, req: Option, } -impl Future for AsyncHandlerServiceResponse +impl Future for AsyncHandlerServiceResponse where - T: Future, - T::Item: Responder, - T::Error: Into, + T: Future>, + R: Responder, + E: Into, { - type Item = ServiceResponse; - type Error = Infallible; + type Output = Result; - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut2 { - return match fut.poll() { - Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( - self.req.take().unwrap(), - res, - ))), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.as_mut().project(); + + if let Some(fut) = this.fut2.as_pin_mut() { + return match fut.poll(cx) { + Poll::Ready(Ok(res)) => { + Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) + } + Poll::Pending => Poll::Pending, + Poll::Ready(Err(e)) => { let res: Response = e.into().into(); - Ok(Async::Ready(ServiceResponse::new( - self.req.take().unwrap(), - res, - ))) + Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) } }; } - match self.fut.poll() { - Ok(Async::Ready(res)) => { - self.fut2 = - Some(res.respond_to(self.req.as_ref().unwrap()).into_future()); - self.poll() + match this.fut.poll(cx) { + Poll::Ready(Ok(res)) => { + let fut = res.respond_to(this.req.as_ref().unwrap()); + self.as_mut().project().fut2.set(Some(fut)); + self.poll(cx) } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(e) => { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(e)) => { let res: Response = e.into().into(); - Ok(Async::Ready(ServiceResponse::new( - self.req.take().unwrap(), - res, - ))) + Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) } } } @@ -279,7 +276,7 @@ impl Extract { } } -impl NewService for Extract +impl ServiceFactory for Extract where S: Service< Request = (T, HttpRequest), @@ -293,7 +290,7 @@ where type Error = (Error, ServiceRequest); type InitError = (); type Service = ExtractService; - type Future = FutureResult; + type Future = Ready>; fn new_service(&self, _: &()) -> Self::Future { ok(ExtractService { @@ -321,13 +318,13 @@ where type Error = (Error, ServiceRequest); type Future = ExtractResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); - let fut = T::from_request(&req, &mut payload).into_future(); + let fut = T::from_request(&req, &mut payload); ExtractResponse { fut, @@ -338,10 +335,13 @@ where } } +#[pin_project] pub struct ExtractResponse { req: HttpRequest, service: S, - fut: ::Future, + #[pin] + fut: T::Future, + #[pin] fut_s: Option, } @@ -353,21 +353,26 @@ where Error = Infallible, >, { - type Item = ServiceResponse; - type Error = (Error, ServiceRequest); + type Output = Result; - fn poll(&mut self) -> Poll { - if let Some(ref mut fut) = self.fut_s { - return fut.poll().map_err(|_| panic!()); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.as_mut().project(); + + if let Some(fut) = this.fut_s.as_pin_mut() { + return fut.poll(cx).map_err(|_| panic!()); } - let item = try_ready!(self.fut.poll().map_err(|e| { - let req = ServiceRequest::new(self.req.clone()); - (e.into(), req) - })); - - self.fut_s = Some(self.service.call((item, self.req.clone()))); - self.poll() + match ready!(this.fut.poll(cx)) { + Err(e) => { + let req = ServiceRequest::new(this.req.clone()); + Poll::Ready(Err((e.into(), req))) + } + Ok(item) => { + let fut = Some(this.service.call((item, this.req.clone()))); + self.as_mut().project().fut_s.set(fut); + self.poll(cx) + } + } } } @@ -382,11 +387,11 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { } } - impl AsyncFactory<($($T,)+), Res> for Func + impl AsyncFactory<($($T,)+), Res, O, E1> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, - Res: IntoFuture, - Res::Item: Responder, - Res::Error: Into, + Res: Future>, + O: Responder, + E1: Into, { fn call(&self, param: ($($T,)+)) -> Res { (self)($(param.$n,)+) diff --git a/src/lib.rs b/src/lib.rs index 60c34489..1ae81505 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(clippy::borrow_interior_mutable_const)] +#![allow(clippy::borrow_interior_mutable_const, unused_imports, dead_code)] //! Actix web is a small, pragmatic, and extremely fast web framework //! for Rust. //! @@ -68,8 +68,8 @@ //! ## Package feature //! //! * `client` - enables http client (default enabled) -//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2` -//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2` +//! * `openssl` - enables ssl support via `openssl` crate, supports `http/2` +//! * `rustls` - enables ssl support via `rustls` crate, supports `http/2` //! * `secure-cookies` - enables secure cookies support, includes `ring` crate as //! dependency //! * `brotli` - enables `brotli` compression support, requires `c` @@ -78,7 +78,6 @@ //! `c` compiler (default enabled) //! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. -//! * `uds` - Unix domain support, enables `HttpServer::bind_uds()` method. //! #![allow(clippy::type_complexity, clippy::new_without_default)] @@ -143,9 +142,10 @@ pub mod dev { pub use crate::service::{ HttpServiceFactory, ServiceRequest, ServiceResponse, WebService, }; - pub use crate::types::form::UrlEncoded; - pub use crate::types::json::JsonBody; - pub use crate::types::readlines::Readlines; + + //pub use crate::types::form::UrlEncoded; + //pub use crate::types::json::JsonBody; + //pub use crate::types::readlines::Readlines; pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; pub use actix_http::encoding::Decoder as Decompress; @@ -176,18 +176,16 @@ pub mod client { //! use actix_web::client::Client; //! //! fn main() { - //! System::new("test").block_on(lazy(|| { + //! System::new("test").block_on(async { //! let mut client = Client::default(); //! - //! client.get("http://www.rust-lang.org") // <- Create request builder + //! // Create request builder and send request + //! let response = client.get("http://www.rust-lang.org") //! .header("User-Agent", "Actix-web") - //! .send() // <- Send http request - //! .map_err(|_| ()) - //! .and_then(|response| { // <- server http response - //! println!("Response: {:?}", response); - //! Ok(()) - //! }) - //! })); + //! .send().await; // <- Send http request + //! + //! println!("Response: {:?}", response); + //! }); //! } //! ``` pub use awc::error::{ diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 86665d82..a697deae 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -1,15 +1,18 @@ //! `Middleware` for compressing response body. use std::cmp; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; use std::str::FromStr; +use std::task::{Context, Poll}; use actix_http::body::MessageBody; use actix_http::encoding::Encoder; use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; use actix_http::{Error, Response, ResponseBuilder}; use actix_service::{Service, Transform}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Future, Poll}; +use futures::future::{ok, Ready}; +use pin_project::pin_project; use crate::service::{ServiceRequest, ServiceResponse}; @@ -78,7 +81,7 @@ where type Error = Error; type InitError = (); type Transform = CompressMiddleware; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(CompressMiddleware { @@ -103,8 +106,8 @@ where type Error = Error; type Future = CompressResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { @@ -128,11 +131,13 @@ where } #[doc(hidden)] +#[pin_project] pub struct CompressResponse where S: Service, B: MessageBody, { + #[pin] fut: S::Future, encoding: ContentEncoding, _t: PhantomData<(B)>, @@ -143,21 +148,25 @@ where B: MessageBody, S: Service, Error = Error>, { - type Item = ServiceResponse>; - type Error = Error; + type Output = Result>, Error>; - fn poll(&mut self) -> Poll { - let resp = futures::try_ready!(self.fut.poll()); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); - let enc = if let Some(enc) = resp.response().extensions().get::() { - enc.0 - } else { - self.encoding - }; + match futures::ready!(this.fut.poll(cx)) { + Ok(resp) => { + let enc = if let Some(enc) = resp.response().extensions().get::() { + enc.0 + } else { + *this.encoding + }; - Ok(Async::Ready(resp.map_body(move |head, body| { - Encoder::response(enc, head, body) - }))) + Poll::Ready(Ok( + resp.map_body(move |head, body| Encoder::response(enc, head, body)) + )) + } + Err(e) => Poll::Ready(Err(e)), + } } } diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index ab2d36c2..5c995503 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -1,9 +1,11 @@ //! Middleware for setting default response headers +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures::future::{ok, FutureResult}; -use futures::{Future, Poll}; +use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use crate::http::{HeaderMap, HttpTryFrom}; @@ -96,7 +98,7 @@ where type Error = Error; type InitError = (); type Transform = DefaultHeadersMiddleware; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(DefaultHeadersMiddleware { @@ -119,16 +121,19 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); + let fut = self.service.call(req); + + async move { + let mut res = fut.await?; - Box::new(self.service.call(req).map(move |mut res| { // set response headers for (key, value) in inner.headers.iter() { if !res.headers().contains_key(key) { @@ -142,15 +147,16 @@ where HeaderValue::from_static("application/octet-stream"), ); } - - res - })) + Ok(res) + } + .boxed_local() } } #[cfg(test)] mod tests { use actix_service::IntoService; + use futures::future::ok; use super::*; use crate::dev::ServiceRequest; @@ -160,46 +166,50 @@ mod tests { #[test] fn test_default_headers() { - let mut mw = block_on( - DefaultHeaders::new() + block_on(async { + let mut mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") - .new_transform(ok_service()), - ) - .unwrap(); + .new_transform(ok_service()) + .await + .unwrap(); - let req = TestRequest::default().to_srv_request(); - let resp = block_on(mw.call(req)).unwrap(); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + let req = TestRequest::default().to_srv_request(); + let resp = mw.call(req).await.unwrap(); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let req = TestRequest::default().to_srv_request(); - let srv = |req: ServiceRequest| { - req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish()) - }; - let mut mw = block_on( - DefaultHeaders::new() + let req = TestRequest::default().to_srv_request(); + let srv = |req: ServiceRequest| { + ok(req.into_response( + HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(), + )) + }; + let mut mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") - .new_transform(srv.into_service()), - ) - .unwrap(); - let resp = block_on(mw.call(req)).unwrap(); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); + .new_transform(srv.into_service()) + .await + .unwrap(); + let resp = mw.call(req).await.unwrap(); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); + }) } #[test] fn test_content_type() { - let srv = |req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()); - let mut mw = block_on( - DefaultHeaders::new() + block_on(async { + let srv = + |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); + let mut mw = DefaultHeaders::new() .content_type() - .new_transform(srv.into_service()), - ) - .unwrap(); + .new_transform(srv.into_service()) + .await + .unwrap(); - let req = TestRequest::default().to_srv_request(); - let resp = block_on(mw.call(req)).unwrap(); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); + let req = TestRequest::default().to_srv_request(); + let resp = mw.call(req).await.unwrap(); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + "application/octet-stream" + ); + }) } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index f450f048..45df4bf3 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -2,13 +2,15 @@ use std::collections::HashSet; use std::env; use std::fmt::{self, Display, Formatter}; +use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_service::{Service, Transform}; use bytes::Bytes; -use futures::future::{ok, FutureResult}; -use futures::{Async, Future, Poll}; +use futures::future::{ok, Ready}; use log::debug; use regex::Regex; use time; @@ -125,7 +127,7 @@ where type Error = Error; type InitError = (); type Transform = LoggerMiddleware; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(LoggerMiddleware { @@ -151,8 +153,8 @@ where type Error = Error; type Future = LoggerResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { @@ -181,11 +183,13 @@ where } #[doc(hidden)] +#[pin_project::pin_project] pub struct LoggerResponse where B: MessageBody, S: Service, { + #[pin] fut: S::Future, time: time::Tm, format: Option, @@ -197,11 +201,15 @@ where B: MessageBody, S: Service, Error = Error>, { - type Item = ServiceResponse>; - type Error = Error; + type Output = Result>, Error>; - fn poll(&mut self) -> Poll { - let res = futures::try_ready!(self.fut.poll()); + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); + + let res = match futures::ready!(this.fut.poll(cx)) { + Ok(res) => res, + Err(e) => return Poll::Ready(Err(e)), + }; if let Some(error) = res.response().error() { if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR { @@ -209,18 +217,21 @@ where } } - if let Some(ref mut format) = self.format { + if let Some(ref mut format) = this.format { for unit in &mut format.0 { unit.render_response(res.response()); } } - Ok(Async::Ready(res.map_body(move |_, body| { + let time = *this.time; + let format = this.format.take(); + + Poll::Ready(Ok(res.map_body(move |_, body| { ResponseBody::Body(StreamLog { body, + time, + format, size: 0, - time: self.time, - format: self.format.take(), }) }))) } @@ -252,13 +263,13 @@ impl MessageBody for StreamLog { self.body.size() } - fn poll_next(&mut self) -> Poll, Error> { - match self.body.poll_next()? { - Async::Ready(Some(chunk)) => { + fn poll_next(&mut self, cx: &mut Context) -> Poll>> { + match self.body.poll_next(cx) { + Poll::Ready(Some(Ok(chunk))) => { self.size += chunk.len(); - Ok(Async::Ready(Some(chunk))) + Poll::Ready(Some(Ok(chunk))) } - val => Ok(val), + val => val, } } } @@ -464,6 +475,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> { #[cfg(test)] mod tests { use actix_service::{IntoService, Service, Transform}; + use futures::future::ok; use super::*; use crate::http::{header, StatusCode}; @@ -472,11 +484,11 @@ mod tests { #[test] fn test_logger() { let srv = |req: ServiceRequest| { - req.into_response( + ok(req.into_response( HttpResponse::build(StatusCode::OK) .header("X-Test", "ttt") .finish(), - ) + )) }; let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 84e0758b..30acad15 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -2,13 +2,13 @@ mod compress; pub use self::compress::{BodyEncoding, Compress}; -mod condition; +//mod condition; mod defaultheaders; -pub mod errhandlers; +//pub mod errhandlers; mod logger; -mod normalize; +//mod normalize; -pub use self::condition::Condition; +//pub use self::condition::Condition; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; -pub use self::normalize::NormalizePath; +//pub use self::normalize::NormalizePath; diff --git a/src/request.rs b/src/request.rs index ea27e303..84744af2 100644 --- a/src/request.rs +++ b/src/request.rs @@ -5,6 +5,7 @@ use std::{fmt, net}; use actix_http::http::{HeaderMap, Method, Uri, Version}; use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; use actix_router::{Path, Url}; +use futures::future::{ok, Ready}; use crate::config::AppConfig; use crate::data::Data; @@ -289,11 +290,11 @@ impl Drop for HttpRequest { impl FromRequest for HttpRequest { type Config = (); type Error = Error; - type Future = Result; + type Future = Ready>; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - Ok(req.clone()) + ok(req.clone()) } } @@ -349,7 +350,7 @@ mod tests { use super::*; use crate::dev::{ResourceDef, ResourceMap}; use crate::http::{header, StatusCode}; - use crate::test::{call_service, init_service, TestRequest}; + use crate::test::{block_on, call_service, init_service, TestRequest}; use crate::{web, App, HttpResponse}; #[test] @@ -467,66 +468,73 @@ mod tests { #[test] fn test_app_data() { - let mut srv = init_service(App::new().data(10usize).service( - web::resource("/").to(|req: HttpRequest| { - if req.app_data::().is_some() { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )); + block_on(async { + let mut srv = init_service(App::new().data(10usize).service( + web::resource("/").to(|req: HttpRequest| { + if req.app_data::().is_some() { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + .await; - let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::default().to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let mut srv = init_service(App::new().data(10u32).service( - web::resource("/").to(|req: HttpRequest| { - if req.app_data::().is_some() { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )); + let mut srv = init_service(App::new().data(10u32).service( + web::resource("/").to(|req: HttpRequest| { + if req.app_data::().is_some() { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + .await; - let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::default().to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + }) } #[test] fn test_extensions_dropped() { - struct Tracker { - pub dropped: bool, - } - struct Foo { - tracker: Rc>, - } - impl Drop for Foo { - fn drop(&mut self) { - self.tracker.borrow_mut().dropped = true; + block_on(async { + struct Tracker { + pub dropped: bool, + } + struct Foo { + tracker: Rc>, + } + impl Drop for Foo { + fn drop(&mut self) { + self.tracker.borrow_mut().dropped = true; + } } - } - let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); - { - let tracker2 = Rc::clone(&tracker); - let mut srv = init_service(App::new().data(10u32).service( - web::resource("/").to(move |req: HttpRequest| { - req.extensions_mut().insert(Foo { - tracker: Rc::clone(&tracker2), - }); - HttpResponse::Ok() - }), - )); + let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); + { + let tracker2 = Rc::clone(&tracker); + let mut srv = init_service(App::new().data(10u32).service( + web::resource("/").to(move |req: HttpRequest| { + req.extensions_mut().insert(Foo { + tracker: Rc::clone(&tracker2), + }); + HttpResponse::Ok() + }), + )) + .await; - let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - } + let req = TestRequest::default().to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + } - assert!(tracker.borrow().dropped); + assert!(tracker.borrow().dropped); + }) } } diff --git a/src/resource.rs b/src/resource.rs index 3ee0167a..553d4156 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,14 +1,17 @@ use std::cell::RefCell; use std::fmt; +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_http::{Error, Extensions, Response}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform, + apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; -use futures::future::{ok, Either, FutureResult}; -use futures::{Async, Future, IntoFuture, Poll}; +use futures::future::{ok, Either, LocalBoxFuture, Ready}; +use pin_project::pin_project; use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; @@ -74,7 +77,7 @@ impl Resource { impl Resource where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -243,8 +246,8 @@ where /// use actix_web::*; /// use futures::future::{ok, Future}; /// - /// fn index(req: HttpRequest) -> impl Future { - /// ok(HttpResponse::Ok().finish()) + /// async fn index(req: HttpRequest) -> Result { + /// Ok(HttpResponse::Ok().finish()) /// } /// /// App::new().service(web::resource("/").to_async(index)); @@ -255,19 +258,19 @@ where /// ```rust /// # use actix_web::*; /// # use futures::future::Future; - /// # fn index(req: HttpRequest) -> Box> { + /// # async fn index(req: HttpRequest) -> Result { /// # unimplemented!() /// # } /// App::new().service(web::resource("/").route(web::route().to_async(index))); /// ``` #[allow(clippy::wrong_self_convention)] - pub fn to_async(mut self, handler: F) -> Self + pub fn to_async(mut self, handler: F) -> Self where - F: AsyncFactory, + F: AsyncFactory, I: FromRequest + 'static, - R: IntoFuture + 'static, - R::Item: Responder, - R::Error: Into, + R: Future> + 'static, + O: Responder + 'static, + E: Into + 'static, { self.routes.push(Route::new().to_async(handler)); self @@ -280,11 +283,11 @@ where /// type (i.e modify response's body). /// /// **Note**: middlewares get called in opposite order of middlewares registration. - pub fn wrap( + pub fn wrap( self, - mw: F, + mw: M, ) -> Resource< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -300,11 +303,9 @@ where Error = Error, InitError = (), >, - F: IntoTransform, { - let endpoint = apply_transform(mw, self.endpoint); Resource { - endpoint, + endpoint: apply(mw, self.endpoint), rdef: self.rdef, name: self.name, guards: self.guards, @@ -337,13 +338,16 @@ where /// fn main() { /// let app = App::new().service( /// web::resource("/index.html") - /// .wrap_fn(|req, srv| - /// srv.call(req).map(|mut res| { + /// .wrap_fn(|req, srv| { + /// let fut = srv.call(req); + /// async { + /// let mut res = fut.await?; /// res.headers_mut().insert( /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), /// ); - /// res - /// })) + /// Ok(res) + /// } + /// }) /// .route(web::get().to(index))); /// } /// ``` @@ -351,7 +355,7 @@ where self, mw: F, ) -> Resource< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -361,9 +365,18 @@ where > where F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, - R: IntoFuture, + R: Future>, { - self.wrap(mw) + Resource { + endpoint: apply_fn_factory(self.endpoint, mw), + rdef: self.rdef, + name: self.name, + guards: self.guards, + routes: self.routes, + default: self.default, + data: self.data, + factory_ref: self.factory_ref, + } } /// Default service to be used if no matching route could be found. @@ -371,8 +384,8 @@ where /// default handler from `App` or `Scope`. pub fn default_service(mut self, f: F) -> Self where - F: IntoNewService, - U: NewService< + F: IntoServiceFactory, + U: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -381,8 +394,8 @@ where U::InitError: fmt::Debug, { // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f.into_new_service().map_init_err(|e| { + self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( + f.into_factory().map_init_err(|e| { log::error!("Can not construct default service: {:?}", e) }), ))))); @@ -393,7 +406,7 @@ where impl HttpServiceFactory for Resource where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -423,9 +436,9 @@ where } } -impl IntoNewService for Resource +impl IntoServiceFactory for Resource where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -433,7 +446,7 @@ where InitError = (), >, { - fn into_new_service(self) -> T { + fn into_factory(self) -> T { *self.factory_ref.borrow_mut() = Some(ResourceFactory { routes: self.routes, data: self.data.map(Rc::new), @@ -450,7 +463,7 @@ pub struct ResourceFactory { default: Rc>>>, } -impl NewService for ResourceFactory { +impl ServiceFactory for ResourceFactory { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -488,31 +501,30 @@ pub struct CreateResourceService { fut: Vec, data: Option>, default: Option, - default_fut: Option>>, + default_fut: Option>>, } impl Future for CreateResourceService { - type Item = ResourceService; - type Error = (); + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let mut done = true; if let Some(ref mut fut) = self.default_fut { - match fut.poll()? { - Async::Ready(default) => self.default = Some(default), - Async::NotReady => done = false, + match Pin::new(fut).poll(cx)? { + Poll::Ready(default) => self.default = Some(default), + Poll::Pending => done = false, } } // poll http services for item in &mut self.fut { match item { - CreateRouteServiceItem::Future(ref mut fut) => match fut.poll()? { - Async::Ready(route) => { - *item = CreateRouteServiceItem::Service(route) - } - Async::NotReady => { + CreateRouteServiceItem::Future(ref mut fut) => match Pin::new(fut) + .poll(cx)? + { + Poll::Ready(route) => *item = CreateRouteServiceItem::Service(route), + Poll::Pending => { done = false; } }, @@ -529,13 +541,13 @@ impl Future for CreateResourceService { CreateRouteServiceItem::Future(_) => unreachable!(), }) .collect(); - Ok(Async::Ready(ResourceService { + Poll::Ready(Ok(ResourceService { routes, data: self.data.clone(), default: self.default.take(), })) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -551,12 +563,12 @@ impl Service for ResourceService { type Response = ServiceResponse; type Error = Error; type Future = Either< - FutureResult, - Box>, + Ready>, + LocalBoxFuture<'static, Result>, >; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { @@ -565,14 +577,14 @@ impl Service for ResourceService { if let Some(ref data) = self.data { req.set_data_container(data.clone()); } - return route.call(req); + return Either::Right(route.call(req)); } } if let Some(ref mut default) = self.default { - default.call(req) + Either::Right(default.call(req)) } else { let req = req.into_parts().0; - Either::A(ok(ServiceResponse::new( + Either::Left(ok(ServiceResponse::new( req, Response::MethodNotAllowed().finish(), ))) @@ -591,7 +603,7 @@ impl ResourceEndpoint { } } -impl NewService for ResourceEndpoint { +impl ServiceFactory for ResourceEndpoint { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -610,18 +622,19 @@ mod tests { use std::time::Duration; use actix_service::Service; - use futures::{Future, IntoFuture}; - use tokio_timer::sleep; + use futures::future::{ok, Future}; + use tokio_timer::delay_for; use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::middleware::DefaultHeaders; use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{call_service, init_service, TestRequest}; + use crate::test::{block_on, call_service, init_service, TestRequest}; use crate::{guard, web, App, Error, HttpResponse}; fn md( req: ServiceRequest, srv: &mut S, - ) -> impl IntoFuture, Error = Error> + ) -> impl Future, Error>> where S: Service< Request = ServiceRequest, @@ -629,178 +642,210 @@ mod tests { Error = Error, >, { - srv.call(req).map(|mut res| { + let fut = srv.call(req); + async move { + let mut res = fut.await?; res.headers_mut() .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); - res - }) + Ok(res) + } } #[test] fn test_middleware() { - let mut srv = init_service( - App::new().service( - web::resource("/test") - .name("test") - .wrap(md) - .route(web::get().to(|| HttpResponse::Ok())), - ), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::resource("/test") + .name("test") + .wrap(DefaultHeaders::new().header( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + )) + .route(web::get().to(|| HttpResponse::Ok())), + ), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + }) } #[test] fn test_middleware_fn() { - let mut srv = init_service( - App::new().service( - web::resource("/test") - .wrap_fn(|req, srv| { - srv.call(req).map(|mut res| { - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - res + block_on(async { + let mut srv = init_service( + App::new().service( + web::resource("/test") + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { + fut.await.map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + } }) - }) - .route(web::get().to(|| HttpResponse::Ok())), - ), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); + .route(web::get().to(|| HttpResponse::Ok())), + ), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + }) } #[test] fn test_to_async() { - let mut srv = - init_service(App::new().service(web::resource("/test").to_async(|| { - sleep(Duration::from_millis(100)).then(|_| HttpResponse::Ok()) - }))); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + block_on(async { + let mut srv = init_service(App::new().service( + web::resource("/test").to_async(|| { + async { + delay_for(Duration::from_millis(100)).await; + Ok::<_, Error>(HttpResponse::Ok()) + } + }), + )) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_default_resource() { - let mut srv = init_service( - App::new() - .service( - web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), - ) - .default_service(|r: ServiceRequest| { - r.into_response(HttpResponse::BadRequest()) - }), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let mut srv = init_service( - App::new().service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())) + block_on(async { + let mut srv = init_service( + App::new() + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())), + ) .default_service(|r: ServiceRequest| { - r.into_response(HttpResponse::BadRequest()) + ok(r.into_response(HttpResponse::BadRequest())) }), - ), - ); + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let mut srv = init_service( + App::new().service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::BadRequest())) + }), + ), + ) + .await; + + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + }) } #[test] fn test_resource_guards() { - let mut srv = init_service( - App::new() - .service( - web::resource("/test/{p}") - .guard(guard::Get()) - .to(|| HttpResponse::Ok()), - ) - .service( - web::resource("/test/{p}") - .guard(guard::Put()) - .to(|| HttpResponse::Created()), - ) - .service( - web::resource("/test/{p}") - .guard(guard::Delete()) - .to(|| HttpResponse::NoContent()), - ), - ); + block_on(async { + let mut srv = init_service( + App::new() + .service( + web::resource("/test/{p}") + .guard(guard::Get()) + .to(|| HttpResponse::Ok()), + ) + .service( + web::resource("/test/{p}") + .guard(guard::Put()) + .to(|| HttpResponse::Created()), + ) + .service( + web::resource("/test/{p}") + .guard(guard::Delete()) + .to(|| HttpResponse::NoContent()), + ), + ) + .await; - let req = TestRequest::with_uri("/test/it") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/test/it") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/test/it") - .method(Method::PUT) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/test/it") + .method(Method::PUT) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/test/it") - .method(Method::DELETE) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::NO_CONTENT); + let req = TestRequest::with_uri("/test/it") + .method(Method::DELETE) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NO_CONTENT); + }) } #[test] fn test_data() { - let mut srv = init_service( - App::new() - .data(1.0f64) - .data(1usize) - .register_data(web::Data::new('-')) - .service( - web::resource("/test") - .data(10usize) - .register_data(web::Data::new('*')) - .guard(guard::Get()) - .to( - |data1: web::Data, - data2: web::Data, - data3: web::Data| { - assert_eq!(*data1, 10); - assert_eq!(*data2, '*'); - assert_eq!(*data3, 1.0); - HttpResponse::Ok() - }, - ), - ), - ); + block_on(async { + let mut srv = init_service( + App::new() + .data(1.0f64) + .data(1usize) + .register_data(web::Data::new('-')) + .service( + web::resource("/test") + .data(10usize) + .register_data(web::Data::new('*')) + .guard(guard::Get()) + .to( + |data1: web::Data, + data2: web::Data, + data3: web::Data| { + assert_eq!(*data1, 10); + assert_eq!(*data2, '*'); + assert_eq!(*data3, 1.0); + HttpResponse::Ok() + }, + ), + ), + ) + .await; - let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::get().uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } } diff --git a/src/responder.rs b/src/responder.rs index 4988ad5b..2bb422b2 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,3 +1,8 @@ +use std::future::Future; +use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; + use actix_http::error::InternalError; use actix_http::http::{ header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, HttpTryFrom, @@ -5,8 +10,9 @@ use actix_http::http::{ }; use actix_http::{Error, Response, ResponseBuilder}; use bytes::{Bytes, BytesMut}; -use futures::future::{err, ok, Either as EitherFuture, FutureResult}; -use futures::{try_ready, Async, Future, IntoFuture, Poll}; +use futures::future::{err, ok, Either as EitherFuture, LocalBoxFuture, Ready}; +use futures::ready; +use pin_project::{pin_project, project}; use crate::request::HttpRequest; @@ -18,7 +24,7 @@ pub trait Responder { type Error: Into; /// The future response value. - type Future: IntoFuture; + type Future: Future>; /// Convert itself to `AsyncResult` or `Error`. fn respond_to(self, req: &HttpRequest) -> Self::Future; @@ -71,7 +77,7 @@ pub trait Responder { impl Responder for Response { type Error = Error; - type Future = FutureResult; + type Future = Ready>; #[inline] fn respond_to(self, _: &HttpRequest) -> Self::Future { @@ -84,15 +90,14 @@ where T: Responder, { type Error = T::Error; - type Future = EitherFuture< - ::Future, - FutureResult, - >; + type Future = EitherFuture>>; fn respond_to(self, req: &HttpRequest) -> Self::Future { match self { - Some(t) => EitherFuture::A(t.respond_to(req).into_future()), - None => EitherFuture::B(ok(Response::build(StatusCode::NOT_FOUND).finish())), + Some(t) => EitherFuture::Left(t.respond_to(req)), + None => { + EitherFuture::Right(ok(Response::build(StatusCode::NOT_FOUND).finish())) + } } } } @@ -104,23 +109,21 @@ where { type Error = Error; type Future = EitherFuture< - ResponseFuture<::Future>, - FutureResult, + ResponseFuture, + Ready>, >; fn respond_to(self, req: &HttpRequest) -> Self::Future { match self { - Ok(val) => { - EitherFuture::A(ResponseFuture::new(val.respond_to(req).into_future())) - } - Err(e) => EitherFuture::B(err(e.into())), + Ok(val) => EitherFuture::Left(ResponseFuture::new(val.respond_to(req))), + Err(e) => EitherFuture::Right(err(e.into())), } } } impl Responder for ResponseBuilder { type Error = Error; - type Future = FutureResult; + type Future = Ready>; #[inline] fn respond_to(mut self, _: &HttpRequest) -> Self::Future { @@ -130,7 +133,7 @@ impl Responder for ResponseBuilder { impl Responder for () { type Error = Error; - type Future = FutureResult; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { ok(Response::build(StatusCode::OK).finish()) @@ -146,7 +149,7 @@ where fn respond_to(self, req: &HttpRequest) -> Self::Future { CustomResponderFut { - fut: self.0.respond_to(req).into_future(), + fut: self.0.respond_to(req), status: Some(self.1), headers: None, } @@ -155,7 +158,7 @@ where impl Responder for &'static str { type Error = Error; - type Future = FutureResult; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { ok(Response::build(StatusCode::OK) @@ -166,7 +169,7 @@ impl Responder for &'static str { impl Responder for &'static [u8] { type Error = Error; - type Future = FutureResult; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { ok(Response::build(StatusCode::OK) @@ -177,7 +180,7 @@ impl Responder for &'static [u8] { impl Responder for String { type Error = Error; - type Future = FutureResult; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { ok(Response::build(StatusCode::OK) @@ -188,7 +191,7 @@ impl Responder for String { impl<'a> Responder for &'a String { type Error = Error; - type Future = FutureResult; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { ok(Response::build(StatusCode::OK) @@ -199,7 +202,7 @@ impl<'a> Responder for &'a String { impl Responder for Bytes { type Error = Error; - type Future = FutureResult; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { ok(Response::build(StatusCode::OK) @@ -210,7 +213,7 @@ impl Responder for Bytes { impl Responder for BytesMut { type Error = Error; - type Future = FutureResult; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { ok(Response::build(StatusCode::OK) @@ -299,34 +302,40 @@ impl Responder for CustomResponder { fn respond_to(self, req: &HttpRequest) -> Self::Future { CustomResponderFut { - fut: self.responder.respond_to(req).into_future(), + fut: self.responder.respond_to(req), status: self.status, headers: self.headers, } } } +#[pin_project] pub struct CustomResponderFut { - fut: ::Future, + #[pin] + fut: T::Future, status: Option, headers: Option, } impl Future for CustomResponderFut { - type Item = Response; - type Error = T::Error; + type Output = Result; - fn poll(&mut self) -> Poll { - let mut res = try_ready!(self.fut.poll()); - if let Some(status) = self.status { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); + + let mut res = match ready!(this.fut.poll(cx)) { + Ok(res) => res, + Err(e) => return Poll::Ready(Err(e)), + }; + if let Some(status) = this.status.take() { *res.status_mut() = status; } - if let Some(ref headers) = self.headers { + if let Some(ref headers) = this.headers { for (k, v) in headers { res.headers_mut().insert(k.clone(), v.clone()); } } - Ok(Async::Ready(res)) + Poll::Ready(Ok(res)) } } @@ -336,8 +345,7 @@ impl Future for CustomResponderFut { /// # use futures::future::{ok, Future}; /// use actix_web::{Either, Error, HttpResponse}; /// -/// type RegisterResult = -/// Either>>; +/// type RegisterResult = Either>; /// /// fn index() -> RegisterResult { /// if is_a_variant() { @@ -346,9 +354,9 @@ impl Future for CustomResponderFut { /// } else { /// Either::B( /// // <- Right variant -/// Box::new(ok(HttpResponse::Ok() +/// Ok(HttpResponse::Ok() /// .content_type("text/html") -/// .body("Hello!"))) +/// .body("Hello!")) /// ) /// } /// } @@ -369,97 +377,85 @@ where B: Responder, { type Error = Error; - type Future = EitherResponder< - ::Future, - ::Future, - >; + type Future = EitherResponder; fn respond_to(self, req: &HttpRequest) -> Self::Future { match self { - Either::A(a) => EitherResponder::A(a.respond_to(req).into_future()), - Either::B(b) => EitherResponder::B(b.respond_to(req).into_future()), + Either::A(a) => EitherResponder::A(a.respond_to(req)), + Either::B(b) => EitherResponder::B(b.respond_to(req)), } } } +#[pin_project] pub enum EitherResponder where - A: Future, - A::Error: Into, - B: Future, - B::Error: Into, + A: Responder, + B: Responder, { - A(A), - B(B), + A(#[pin] A::Future), + B(#[pin] B::Future), } impl Future for EitherResponder where - A: Future, - A::Error: Into, - B: Future, - B::Error: Into, + A: Responder, + B: Responder, { - type Item = Response; - type Error = Error; + type Output = Result; - fn poll(&mut self) -> Poll { - match self { - EitherResponder::A(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?), - EitherResponder::B(ref mut fut) => Ok(fut.poll().map_err(|e| e.into())?), + #[project] + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + #[project] + match self.project() { + EitherResponder::A(fut) => { + Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into())) + } + EitherResponder::B(fut) => { + Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into()))) + } } } } -impl Responder for Box> -where - I: Responder + 'static, - E: Into + 'static, -{ - type Error = Error; - type Future = Box>; - - #[inline] - fn respond_to(self, req: &HttpRequest) -> Self::Future { - let req = req.clone(); - Box::new( - self.map_err(|e| e.into()) - .and_then(move |r| ResponseFuture(r.respond_to(&req).into_future())), - ) - } -} - impl Responder for InternalError where T: std::fmt::Debug + std::fmt::Display + 'static, { type Error = Error; - type Future = Result; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { let err: Error = self.into(); - Ok(err.into()) + ok(err.into()) } } -pub struct ResponseFuture(T); +#[pin_project] +pub struct ResponseFuture { + #[pin] + fut: T, + _t: PhantomData, +} -impl ResponseFuture { +impl ResponseFuture { pub fn new(fut: T) -> Self { - ResponseFuture(fut) + ResponseFuture { + fut, + _t: PhantomData, + } } } -impl Future for ResponseFuture +impl Future for ResponseFuture where - T: Future, - T::Error: Into, + T: Future>, + E: Into, { - type Item = Response; - type Error = Error; + type Output = Result; - fn poll(&mut self) -> Poll { - Ok(self.0.poll().map_err(|e| e.into())?) + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + Poll::Ready(ready!(self.project().fut.poll(cx)).map_err(|e| e.into())) } } @@ -476,26 +472,31 @@ pub(crate) mod tests { #[test] fn test_option_responder() { - let mut srv = init_service( - App::new() - .service(web::resource("/none").to(|| -> Option<&'static str> { None })) - .service(web::resource("/some").to(|| Some("some"))), - ); + block_on(async { + let mut srv = init_service( + App::new() + .service( + web::resource("/none").to(|| -> Option<&'static str> { None }), + ) + .service(web::resource("/some").to(|| Some("some"))), + ) + .await; - let req = TestRequest::with_uri("/none").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/none").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/some").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"some")); + let req = TestRequest::with_uri("/some").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"some")); + } + _ => panic!(), } - _ => panic!(), - } + }) } pub(crate) trait BodyTest { @@ -526,142 +527,155 @@ pub(crate) mod tests { #[test] fn test_responder() { - let req = TestRequest::default().to_http_request(); + block_on(async { + let req = TestRequest::default().to_http_request(); - let resp: HttpResponse = block_on(().respond_to(&req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(*resp.body().body(), Body::Empty); + let resp: HttpResponse = ().respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(*resp.body().body(), Body::Empty); - let resp: HttpResponse = block_on("test".respond_to(&req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); + let resp: HttpResponse = "test".respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); - let resp: HttpResponse = block_on(b"test".respond_to(&req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); + let resp: HttpResponse = b"test".respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); - let resp: HttpResponse = block_on("test".to_string().respond_to(&req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); + let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); - let resp: HttpResponse = - block_on((&"test".to_string()).respond_to(&req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); + let resp: HttpResponse = + (&"test".to_string()).respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); - let resp: HttpResponse = - block_on(Bytes::from_static(b"test").respond_to(&req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); + let resp: HttpResponse = + Bytes::from_static(b"test").respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); - let resp: HttpResponse = - block_on(BytesMut::from(b"test".as_ref()).respond_to(&req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - - // InternalError - let resp: HttpResponse = - error::InternalError::new("err", StatusCode::BAD_REQUEST) + let resp: HttpResponse = BytesMut::from(b"test".as_ref()) .respond_to(&req) + .await .unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + + // InternalError + let resp: HttpResponse = + error::InternalError::new("err", StatusCode::BAD_REQUEST) + .respond_to(&req) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + }) } #[test] fn test_result_responder() { - let req = TestRequest::default().to_http_request(); + block_on(async { + let req = TestRequest::default().to_http_request(); - // Result - let resp: HttpResponse = - block_on(Ok::<_, Error>("test".to_string()).respond_to(&req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); + // Result + let resp: HttpResponse = Ok::<_, Error>("test".to_string()) + .respond_to(&req) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); - let res = block_on( - Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) - .respond_to(&req), - ); - assert!(res.is_err()); + let res = Err::(error::InternalError::new( + "err", + StatusCode::BAD_REQUEST, + )) + .respond_to(&req) + .await; + assert!(res.is_err()); + }) } #[test] fn test_custom_responder() { - let req = TestRequest::default().to_http_request(); - let res = block_on( - "test" + block_on(async { + let req = TestRequest::default().to_http_request(); + let res = "test" .to_string() .with_status(StatusCode::BAD_REQUEST) - .respond_to(&req), - ) - .unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(res.body().bin_ref(), b"test"); + .respond_to(&req) + .await + .unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!(res.body().bin_ref(), b"test"); - let res = block_on( - "test" + let res = "test" .to_string() .with_header("content-type", "json") - .respond_to(&req), - ) - .unwrap(); + .respond_to(&req) + .await + .unwrap(); - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.body().bin_ref(), b"test"); - assert_eq!( - res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("json") - ); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); + }) } #[test] fn test_tuple_responder_with_status_code() { - let req = TestRequest::default().to_http_request(); - let res = - block_on(("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req)) + block_on(async { + let req = TestRequest::default().to_http_request(); + let res = ("test".to_string(), StatusCode::BAD_REQUEST) + .respond_to(&req) + .await .unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!(res.body().bin_ref(), b"test"); - let req = TestRequest::default().to_http_request(); - let res = block_on( - ("test".to_string(), StatusCode::OK) + let req = TestRequest::default().to_http_request(); + let res = ("test".to_string(), StatusCode::OK) .with_header("content-type", "json") - .respond_to(&req), - ) - .unwrap(); - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.body().bin_ref(), b"test"); - assert_eq!( - res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("json") - ); + .respond_to(&req) + .await + .unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); + }) } } diff --git a/src/route.rs b/src/route.rs index 35b84294..fb46dbfd 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,9 +1,11 @@ +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_http::{http::Method, Error}; -use actix_service::{NewService, Service}; -use futures::future::{ok, Either, FutureResult}; -use futures::{Async, Future, IntoFuture, Poll}; +use actix_service::{Service, ServiceFactory}; +use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; use crate::extract::FromRequest; use crate::guard::{self, Guard}; @@ -17,22 +19,19 @@ type BoxedRouteService = Box< Request = Req, Response = Res, Error = Error, - Future = Either< - FutureResult, - Box>, - >, + Future = LocalBoxFuture<'static, Result>, >, >; type BoxedRouteNewService = Box< - dyn NewService< + dyn ServiceFactory< Config = (), Request = Req, Response = Res, Error = Error, InitError = (), Service = BoxedRouteService, - Future = Box, Error = ()>>, + Future = LocalBoxFuture<'static, Result, ()>>, >, >; @@ -61,7 +60,7 @@ impl Route { } } -impl NewService for Route { +impl ServiceFactory for Route { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -78,26 +77,30 @@ impl NewService for Route { } } -type RouteFuture = Box< - dyn Future, Error = ()>, +type RouteFuture = LocalBoxFuture< + 'static, + Result, ()>, >; +#[pin_project::pin_project] pub struct CreateRouteService { + #[pin] fut: RouteFuture, guards: Rc>>, } impl Future for CreateRouteService { - type Item = RouteService; - type Error = (); + type Output = Result; - fn poll(&mut self) -> Poll { - match self.fut.poll()? { - Async::Ready(service) => Ok(Async::Ready(RouteService { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); + + match this.fut.poll(cx)? { + Poll::Ready(service) => Poll::Ready(Ok(RouteService { service, - guards: self.guards.clone(), + guards: this.guards.clone(), })), - Async::NotReady => Ok(Async::NotReady), + Poll::Pending => Poll::Pending, } } } @@ -122,17 +125,14 @@ impl Service for RouteService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Either< - FutureResult, - Box>, - >; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { - self.service.call(req) + self.service.call(req).boxed_local() } } @@ -249,8 +249,8 @@ impl Route { /// } /// /// /// extract path info using serde - /// fn index(info: web::Path) -> impl Future { - /// ok("Hello World!") + /// async fn index(info: web::Path) -> Result<&'static str, Error> { + /// Ok("Hello World!") /// } /// /// fn main() { @@ -261,13 +261,13 @@ impl Route { /// } /// ``` #[allow(clippy::wrong_self_convention)] - pub fn to_async(mut self, handler: F) -> Self + pub fn to_async(mut self, handler: F) -> Self where - F: AsyncFactory, + F: AsyncFactory, T: FromRequest + 'static, - R: IntoFuture + 'static, - R::Item: Responder, - R::Error: Into, + R: Future> + 'static, + O: Responder + 'static, + E: Into + 'static, { self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new( handler, @@ -278,14 +278,14 @@ impl Route { struct RouteNewService where - T: NewService, + T: ServiceFactory, { service: T, } impl RouteNewService where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -300,9 +300,9 @@ where } } -impl NewService for RouteNewService +impl ServiceFactory for RouteNewService where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -318,19 +318,20 @@ where type Error = Error; type InitError = (); type Service = BoxedRouteService; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: &()) -> Self::Future { - Box::new( - self.service - .new_service(&()) - .map_err(|_| ()) - .and_then(|service| { + self.service + .new_service(&()) + .map(|result| match result { + Ok(service) => { let service: BoxedRouteService<_, _> = Box::new(RouteServiceWrapper { service }); Ok(service) - }), - ) + } + Err(_) => Err(()), + }) + .boxed_local() } } @@ -350,25 +351,30 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Either< - FutureResult, - Box>, - >; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready().map_err(|(e, _)| e) + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx).map_err(|(e, _)| e) } fn call(&mut self, req: ServiceRequest) -> Self::Future { - let mut fut = self.service.call(req); - match fut.poll() { - Ok(Async::Ready(res)) => Either::A(ok(res)), - Err((e, req)) => Either::A(ok(req.error_response(e))), - Ok(Async::NotReady) => Either::B(Box::new(fut.then(|res| match res { + // let mut fut = self.service.call(req); + self.service + .call(req) + .map(|res| match res { Ok(res) => Ok(res), Err((err, req)) => Ok(req.error_response(err)), - }))), - } + }) + .boxed_local() + + // match fut.poll() { + // Poll::Ready(Ok(res)) => Either::Left(ok(res)), + // Poll::Ready(Err((e, req))) => Either::Left(ok(req.error_response(e))), + // Poll::Pending => Either::Right(Box::new(fut.then(|res| match res { + // Ok(res) => Ok(res), + // Err((err, req)) => Ok(req.error_response(err)), + // }))), + // } } } @@ -379,11 +385,11 @@ mod tests { use bytes::Bytes; use futures::Future; use serde_derive::Serialize; - use tokio_timer::sleep; + use tokio_timer::delay_for; use crate::http::{Method, StatusCode}; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{error, web, App, HttpResponse}; + use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; + use crate::{error, web, App, Error, HttpResponse}; #[derive(Serialize, PartialEq, Debug)] struct MyObject { @@ -392,68 +398,75 @@ mod tests { #[test] fn test_route() { - let mut srv = init_service( - App::new() - .service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())) - .route(web::put().to(|| { - Err::(error::ErrorBadRequest("err")) - })) - .route(web::post().to_async(|| { - sleep(Duration::from_millis(100)) - .then(|_| HttpResponse::Created()) - })) - .route(web::delete().to_async(|| { - sleep(Duration::from_millis(100)).then(|_| { + block_on(async { + let mut srv = init_service( + App::new() + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::put().to(|| { Err::(error::ErrorBadRequest("err")) - }) - })), - ) - .service(web::resource("/json").route(web::get().to_async(|| { - sleep(Duration::from_millis(25)).then(|_| { - Ok::<_, crate::Error>(web::Json(MyObject { - name: "test".to_string(), - })) - }) - }))), - ); + })) + .route(web::post().to_async(|| { + async { + delay_for(Duration::from_millis(100)).await; + Ok::<_, Error>(HttpResponse::Created()) + } + })) + .route(web::delete().to_async(|| { + async { + delay_for(Duration::from_millis(100)).await; + Err::(error::ErrorBadRequest("err")) + } + })), + ) + .service(web::resource("/json").route(web::get().to_async(|| { + async { + delay_for(Duration::from_millis(25)).await; + Ok::<_, Error>(web::Json(MyObject { + name: "test".to_string(), + })) + } + }))), + ) + .await; - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/test") - .method(Method::PUT) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/test") + .method(Method::PUT) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/test") - .method(Method::DELETE) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/test") + .method(Method::DELETE) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/test") + .method(Method::HEAD) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let req = TestRequest::with_uri("/json").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/json").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp); - assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); + }) } } diff --git a/src/scope.rs b/src/scope.rs index c152bc33..2e59352d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,15 +1,16 @@ use std::cell::RefCell; use std::fmt; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_http::{Extensions, Response}; use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform, + apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; -use futures::future::{ok, Either, Future, FutureResult}; -use futures::{Async, IntoFuture, Poll}; +use futures::future::{ok, Either, Future, LocalBoxFuture, Ready}; use crate::config::ServiceConfig; use crate::data::Data; @@ -20,16 +21,13 @@ use crate::resource::Resource; use crate::rmap::ResourceMap; use crate::route::Route; use crate::service::{ - ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, + AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; type Guards = Vec>; type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxedResponse = Either< - FutureResult, - Box>, ->; +type BoxedResponse = LocalBoxFuture<'static, Result>; /// Resources scope. /// @@ -64,7 +62,7 @@ pub struct Scope { endpoint: T, rdef: String, data: Option, - services: Vec>, + services: Vec>, guards: Vec>, default: Rc>>>, external: Vec, @@ -90,7 +88,7 @@ impl Scope { impl Scope where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -285,8 +283,8 @@ where /// If default resource is not registered, app's default resource is being used. pub fn default_service(mut self, f: F) -> Self where - F: IntoNewService, - U: NewService< + F: IntoServiceFactory, + U: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -295,8 +293,8 @@ where U::InitError: fmt::Debug, { // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f.into_new_service().map_init_err(|e| { + self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( + f.into_factory().map_init_err(|e| { log::error!("Can not construct default service: {:?}", e) }), ))))); @@ -313,11 +311,11 @@ where /// ServiceResponse. /// /// Use middleware when you need to read or modify *every* request in some way. - pub fn wrap( + pub fn wrap( self, - mw: F, + mw: M, ) -> Scope< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -333,11 +331,9 @@ where Error = Error, InitError = (), >, - F: IntoTransform, { - let endpoint = apply_transform(mw, self.endpoint); Scope { - endpoint, + endpoint: apply(mw, self.endpoint), rdef: self.rdef, data: self.data, guards: self.guards, @@ -368,13 +364,16 @@ where /// fn main() { /// let app = App::new().service( /// web::scope("/app") - /// .wrap_fn(|req, srv| - /// srv.call(req).map(|mut res| { + /// .wrap_fn(|req, srv| { + /// let fut = srv.call(req); + /// async { + /// let mut res = fut.await?; /// res.headers_mut().insert( /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), /// ); - /// res - /// })) + /// Ok(res) + /// } + /// }) /// .route("/index.html", web::get().to(index))); /// } /// ``` @@ -382,7 +381,7 @@ where self, mw: F, ) -> Scope< - impl NewService< + impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -392,15 +391,24 @@ where > where F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, - R: IntoFuture, + R: Future>, { - self.wrap(mw) + Scope { + endpoint: apply_fn_factory(self.endpoint, mw), + rdef: self.rdef, + data: self.data, + guards: self.guards, + services: self.services, + default: self.default, + external: self.external, + factory_ref: self.factory_ref, + } } } impl HttpServiceFactory for Scope where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -471,7 +479,7 @@ pub struct ScopeFactory { default: Rc>>>, } -impl NewService for ScopeFactory { +impl ServiceFactory for ScopeFactory { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -508,14 +516,15 @@ impl NewService for ScopeFactory { /// Create scope service #[doc(hidden)] +#[pin_project::pin_project] pub struct ScopeFactoryResponse { fut: Vec, data: Option>, default: Option, - default_fut: Option>>, + default_fut: Option>>, } -type HttpServiceFut = Box>; +type HttpServiceFut = LocalBoxFuture<'static, Result>; enum CreateScopeServiceItem { Future(Option, Option, HttpServiceFut), @@ -523,16 +532,15 @@ enum CreateScopeServiceItem { } impl Future for ScopeFactoryResponse { - type Item = ScopeService; - type Error = (); + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let mut done = true; if let Some(ref mut fut) = self.default_fut { - match fut.poll()? { - Async::Ready(default) => self.default = Some(default), - Async::NotReady => done = false, + match Pin::new(fut).poll(cx)? { + Poll::Ready(default) => self.default = Some(default), + Poll::Pending => done = false, } } @@ -543,11 +551,11 @@ impl Future for ScopeFactoryResponse { ref mut path, ref mut guards, ref mut fut, - ) => match fut.poll()? { - Async::Ready(service) => { + ) => match Pin::new(fut).poll(cx)? { + Poll::Ready(service) => { Some((path.take().unwrap(), guards.take(), service)) } - Async::NotReady => { + Poll::Pending => { done = false; None } @@ -573,14 +581,14 @@ impl Future for ScopeFactoryResponse { } router }); - Ok(Async::Ready(ScopeService { + Poll::Ready(Ok(ScopeService { data: self.data.clone(), router: router.finish(), default: self.default.take(), _ready: None, })) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -596,10 +604,10 @@ impl Service for ScopeService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Either>; + type Future = Either>>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { @@ -618,12 +626,12 @@ impl Service for ScopeService { if let Some(ref data) = self.data { req.set_data_container(data.clone()); } - Either::A(srv.call(req)) + Either::Left(srv.call(req)) } else if let Some(ref mut default) = self.default { - Either::A(default.call(req)) + Either::Left(default.call(req)) } else { let req = req.into_parts().0; - Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + Either::Right(ok(ServiceResponse::new(req, Response::NotFound().finish()))) } } } @@ -639,7 +647,7 @@ impl ScopeEndpoint { } } -impl NewService for ScopeEndpoint { +impl ServiceFactory for ScopeEndpoint { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; @@ -657,367 +665,416 @@ impl NewService for ScopeEndpoint { mod tests { use actix_service::Service; use bytes::Bytes; - use futures::{Future, IntoFuture}; + use futures::future::ok; + use futures::Future; use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::middleware::DefaultHeaders; use crate::service::{ServiceRequest, ServiceResponse}; use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; #[test] fn test_scope() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_root() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("").to(|| HttpResponse::Ok())) - .service(web::resource("/").to(|| HttpResponse::Created())), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), + ) + .await; - let req = TestRequest::with_uri("/app").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_scope_root2() { - let mut srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), - )); + block_on(async { + let mut srv = init_service(App::new().service( + web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), + )) + .await; - let req = TestRequest::with_uri("/app").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_root3() { - let mut srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("/").to(|| HttpResponse::Ok())), - )); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app/") + .service(web::resource("/").to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_scope_route() { - let mut srv = init_service( - App::new().service( - web::scope("app") - .route("/path1", web::get().to(|| HttpResponse::Ok())) - .route("/path1", web::delete().to(|| HttpResponse::Ok())), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("app") + .route("/path1", web::get().to(|| HttpResponse::Ok())) + .route("/path1", web::delete().to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_scope_route_without_leading_slash() { - let mut srv = init_service( - App::new().service( - web::scope("app").service( - web::resource("path1") - .route(web::get().to(|| HttpResponse::Ok())) - .route(web::delete().to(|| HttpResponse::Ok())), + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("app").service( + web::resource("path1") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::delete().to(|| HttpResponse::Ok())), + ), ), - ), - ); + ) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + }) } #[test] fn test_scope_guard() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .guard(guard::Get()) - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .guard(guard::Get()) + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/path1") - .method(Method::GET) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1") + .method(Method::GET) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_variable_segment() { - let mut srv = - init_service(App::new().service(web::scope("/ab-{project}").service( - web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }), - ))); + block_on(async { + let mut srv = + init_service(App::new().service(web::scope("/ab-{project}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + }), + ))) + .await; - let req = TestRequest::with_uri("/ab-project1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/ab-project1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project1")); + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project1")); + } + _ => panic!(), } - _ => panic!(), - } - let req = TestRequest::with_uri("/aa-project1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/aa-project1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_nested_scope() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::scope("/t1").service( + block_on(async { + let mut srv = + init_service(App::new().service( + web::scope("/app").service(web::scope("/t1").service( web::resource("/path1").to(|| HttpResponse::Created()), )), - ), - ); + )) + .await; - let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/t1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_nested_scope_no_slash() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::scope("t1").service( + block_on(async { + let mut srv = + init_service(App::new().service( + web::scope("/app").service(web::scope("t1").service( web::resource("/path1").to(|| HttpResponse::Created()), )), - ), - ); + )) + .await; - let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/t1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_nested_scope_root() { - let mut srv = init_service( - App::new().service( - web::scope("/app").service( - web::scope("/t1") - .service(web::resource("").to(|| HttpResponse::Ok())) - .service(web::resource("/").to(|| HttpResponse::Created())), + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), ), - ), - ); + ) + .await; - let req = TestRequest::with_uri("/app/t1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/t1/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/t1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + }) } #[test] fn test_nested_scope_filter() { - let mut srv = init_service( - App::new().service( - web::scope("/app").service( - web::scope("/t1") - .guard(guard::Get()) - .service(web::resource("/path1").to(|| HttpResponse::Ok())), + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") + .guard(guard::Get()) + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), ), - ), - ); + ) + .await; - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::GET) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_nested_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( - web::scope("/{project_id}").service(web::resource("/path1").to( - |r: HttpRequest| { - HttpResponse::Created() - .body(format!("project: {}", &r.match_info()["project_id"])) - }, - )), - ))); + block_on(async { + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project_id}").service(web::resource("/path1").to( + |r: HttpRequest| { + HttpResponse::Created() + .body(format!("project: {}", &r.match_info()["project_id"])) + }, + )), + ))) + .await; - let req = TestRequest::with_uri("/app/project_1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/project_1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project_1")); + } + _ => panic!(), } - _ => panic!(), - } + }) } #[test] fn test_nested2_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( - web::scope("/{project}").service(web::scope("/{id}").service( - web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - }), - )), - ))); + block_on(async { + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project}").service(web::scope("/{id}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Created().body(format!( + "project: {} - {}", + &r.match_info()["project"], + &r.match_info()["id"], + )) + }), + )), + ))) + .await; - let req = TestRequest::with_uri("/app/test/1/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/app/test/1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); + } + _ => panic!(), } - _ => panic!(), - } - let req = TestRequest::with_uri("/app/test/1/path2").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/test/1/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_default_resource() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("/path1").to(|| HttpResponse::Ok())) - .default_service(|r: ServiceRequest| { - r.into_response(HttpResponse::BadRequest()) - }), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::BadRequest())) + }), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path2").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/app/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/path2").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_default_resource_propagation() { - let mut srv = init_service( - App::new() - .service(web::scope("/app1").default_service( - web::resource("").to(|| HttpResponse::BadRequest()), - )) - .service(web::scope("/app2")) - .default_service(|r: ServiceRequest| { - r.into_response(HttpResponse::MethodNotAllowed()) - }), - ); + block_on(async { + let mut srv = init_service( + App::new() + .service(web::scope("/app1").default_service( + web::resource("").to(|| HttpResponse::BadRequest()), + )) + .service(web::scope("/app2")) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::MethodNotAllowed())) + }), + ) + .await; - let req = TestRequest::with_uri("/non-exist").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let req = TestRequest::with_uri("/app1/non-exist").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/app1/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/app2/non-exist").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/app2/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + }) } fn md( req: ServiceRequest, srv: &mut S, - ) -> impl IntoFuture, Error = Error> + ) -> impl Future, Error>> where S: Service< Request = ServiceRequest, @@ -1025,165 +1082,207 @@ mod tests { Error = Error, >, { - srv.call(req).map(|mut res| { + let fut = srv.call(req); + async move { + let mut res = fut.await?; res.headers_mut() .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); - res - }) + Ok(res) + } } #[test] fn test_middleware() { - let mut srv = - init_service(App::new().service(web::scope("app").wrap(md).service( - web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), - ))); - let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("app") + .wrap(DefaultHeaders::new().header( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + )) + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())), + ), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + }) } #[test] fn test_middleware_fn() { - let mut srv = init_service( - App::new().service( - web::scope("app") - .wrap_fn(|req, srv| { - srv.call(req).map(|mut res| { - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - res + block_on(async { + let mut srv = init_service( + App::new().service( + web::scope("app") + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async move { + let mut res = fut.await?; + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + Ok(res) + } }) - }) - .route("/test", web::get().to(|| HttpResponse::Ok())), - ), - ); - let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); + .route("/test", web::get().to(|| HttpResponse::Ok())), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + }) } #[test] fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( - web::scope("app").data(10usize).route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), - ), - )); + block_on(async { + let mut srv = init_service(App::new().data(1usize).service( + web::scope("app").data(10usize).route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + )) + .await; - let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_override_register_data() { - let mut srv = init_service( - App::new().register_data(web::Data::new(1usize)).service( - web::scope("app") - .register_data(web::Data::new(10usize)) - .route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), - ), - ), - ); + block_on(async { + let mut srv = init_service( + App::new().register_data(web::Data::new(1usize)).service( + web::scope("app") + .register_data(web::Data::new(10usize)) + .route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + ), + ) + .await; - let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_config() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.route("/path1", web::get().to(|| HttpResponse::Ok())); - }))); + block_on(async { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.route("/path1", web::get().to(|| HttpResponse::Ok())); + }))) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_scope_config_2() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.route("/", web::get().to(|| HttpResponse::Ok())); - })); - }))); + block_on(async { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.route("/", web::get().to(|| HttpResponse::Ok())); + })); + }))) + .await; - let req = TestRequest::with_uri("/app/v1/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_url_for_external() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - s.route( - "/", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["xxxxxx"]).unwrap().as_str() - )) - }), - ); - })); - }))); + block_on(async { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + s.route( + "/", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["xxxxxx"]) + .unwrap() + .as_str() + )) + }), + ); + })); + }))) + .await; - let req = TestRequest::with_uri("/app/v1/").to_request(); - let resp = block_on(srv.call(req)).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp); - assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); + }) } #[test] fn test_url_for_nested() { - let mut srv = init_service(App::new().service(web::scope("/a").service( - web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( - web::get().to(|req: HttpRequest| { - HttpResponse::Ok() - .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) - }), - )), - ))); - let req = TestRequest::with_uri("/a/b/c/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp); - assert_eq!( - body, - Bytes::from_static(b"http://localhost:8080/a/b/c/12345") - ); + block_on(async { + let mut srv = init_service(App::new().service(web::scope("/a").service( + web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( + web::get().to(|req: HttpRequest| { + HttpResponse::Ok() + .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) + }), + )), + ))) + .await; + + let req = TestRequest::with_uri("/a/b/c/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!( + body, + Bytes::from_static(b"http://localhost:8080/a/b/c/12345") + ); + }) } } diff --git a/src/server.rs b/src/server.rs index 51492eb0..a98d0627 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,15 +6,15 @@ use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Resp use actix_rt::System; use actix_server::{Server, ServerBuilder}; use actix_server_config::ServerConfig; -use actix_service::{IntoNewService, NewService}; +use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use parking_lot::Mutex; use net2::TcpBuilder; -#[cfg(feature = "ssl")] -use openssl::ssl::{SslAcceptor, SslAcceptorBuilder}; -#[cfg(feature = "rust-tls")] -use rustls::ServerConfig as RustlsServerConfig; +#[cfg(feature = "openssl")] +use open_ssl::ssl::{SslAcceptor, SslAcceptorBuilder}; +#[cfg(feature = "rustls")] +use rust_tls::ServerConfig as RustlsServerConfig; struct Socket { scheme: &'static str, @@ -51,12 +51,11 @@ struct Config { pub struct HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, - S::Service: 'static, B: MessageBody, { pub(super) factory: F, @@ -71,12 +70,12 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, - S::Error: Into, + I: IntoServiceFactory, + S: ServiceFactory, + S::Error: Into + 'static, S::InitError: fmt::Debug, - S::Response: Into>, - S::Service: 'static, + S::Response: Into> + 'static, + ::Future: 'static, B: MessageBody + 'static, { /// Create new http server with application factory @@ -254,11 +253,11 @@ where Ok(self) } - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_ssl( + pub fn listen_openssl( self, lst: net::TcpListener, builder: SslAcceptorBuilder, @@ -266,13 +265,14 @@ where self.listen_ssl_inner(lst, openssl_acceptor(builder)?) } - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] fn listen_ssl_inner( mut self, lst: net::TcpListener, acceptor: SslAcceptor, ) -> io::Result { use actix_server::ssl::{OpensslAcceptor, SslError}; + use actix_service::pipeline_factory; let acceptor = OpensslAcceptor::new(acceptor); let factory = self.factory.clone(); @@ -288,7 +288,7 @@ where lst, move || { let c = cfg.lock(); - acceptor.clone().map_err(SslError::Ssl).and_then( + pipeline_factory(acceptor.clone().map_err(SslError::Ssl)).and_then( HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) @@ -302,7 +302,7 @@ where Ok(self) } - #[cfg(feature = "rust-tls")] + #[cfg(feature = "rustls")] /// Use listener for accepting incoming tls connection requests /// /// This method sets alpn protocols to "h2" and "http/1.1" @@ -314,13 +314,14 @@ where self.listen_rustls_inner(lst, config) } - #[cfg(feature = "rust-tls")] + #[cfg(feature = "rustls")] fn listen_rustls_inner( mut self, lst: net::TcpListener, mut config: RustlsServerConfig, ) -> io::Result { use actix_server::ssl::{RustlsAcceptor, SslError}; + use actix_service::pipeline_factory; let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; config.set_protocols(&protos); @@ -339,7 +340,7 @@ where lst, move || { let c = cfg.lock(); - acceptor.clone().map_err(SslError::Ssl).and_then( + pipeline_factory(acceptor.clone().map_err(SslError::Ssl)).and_then( HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) @@ -397,11 +398,11 @@ where } } - #[cfg(feature = "ssl")] + #[cfg(feature = "openssl")] /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_ssl( + pub fn bind_openssl( mut self, addr: A, builder: SslAcceptorBuilder, @@ -419,7 +420,7 @@ where Ok(self) } - #[cfg(feature = "rust-tls")] + #[cfg(feature = "rustls")] /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" @@ -435,7 +436,7 @@ where Ok(self) } - #[cfg(feature = "uds")] + #[cfg(unix)] /// Start listening for unix domain connections on existing listener. /// /// This method is available with `uds` feature. @@ -466,7 +467,7 @@ where Ok(self) } - #[cfg(feature = "uds")] + #[cfg(unix)] /// Start listening for incoming unix domain connections. /// /// This method is available with `uds` feature. @@ -502,8 +503,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoNewService, - S: NewService, + I: IntoServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -577,10 +578,10 @@ fn create_tcp_listener( Ok(builder.listen(backlog)?) } -#[cfg(feature = "ssl")] +#[cfg(feature = "openssl")] /// Configure `SslAcceptorBuilder` with custom server flags. fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result { - use openssl::ssl::AlpnError; + use open_ssl::ssl::AlpnError; builder.set_alpn_select_callback(|_, protos| { const H2: &[u8] = b"\x02h2"; diff --git a/src/service.rs b/src/service.rs index 8b94dd28..9c4e6b4a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -9,8 +9,8 @@ use actix_http::{ ResponseHead, }; use actix_router::{Path, Resource, ResourceDef, Url}; -use actix_service::{IntoNewService, NewService}; -use futures::future::{ok, FutureResult, IntoFuture}; +use actix_service::{IntoServiceFactory, ServiceFactory}; +use futures::future::{ok, Ready}; use crate::config::{AppConfig, AppService}; use crate::data::Data; @@ -24,7 +24,7 @@ pub trait HttpServiceFactory { fn register(self, config: &mut AppService); } -pub(crate) trait ServiceFactory { +pub(crate) trait AppServiceFactory { fn register(&mut self, config: &mut AppService); } @@ -40,7 +40,7 @@ impl ServiceFactoryWrapper { } } -impl ServiceFactory for ServiceFactoryWrapper +impl AppServiceFactory for ServiceFactoryWrapper where T: HttpServiceFactory, { @@ -404,16 +404,6 @@ impl Into> for ServiceResponse { } } -impl IntoFuture for ServiceResponse { - type Item = ServiceResponse; - type Error = Error; - type Future = FutureResult, Error>; - - fn into_future(self) -> Self::Future { - ok(self) - } -} - impl fmt::Debug for ServiceResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = writeln!( @@ -459,10 +449,11 @@ impl WebService { /// Add match guard to a web service. /// /// ```rust - /// use actix_web::{web, guard, dev, App, HttpResponse}; + /// use futures::future::{ok, Ready}; + /// use actix_web::{web, guard, dev, App, Error, HttpResponse}; /// - /// fn index(req: dev::ServiceRequest) -> dev::ServiceResponse { - /// req.into_response(HttpResponse::Ok().finish()) + /// fn index(req: dev::ServiceRequest) -> Ready> { + /// ok(req.into_response(HttpResponse::Ok().finish())) /// } /// /// fn main() { @@ -482,8 +473,8 @@ impl WebService { /// Set a service factory implementation and generate web service. pub fn finish(self, service: F) -> impl HttpServiceFactory where - F: IntoNewService, - T: NewService< + F: IntoServiceFactory, + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -492,7 +483,7 @@ impl WebService { > + 'static, { WebServiceImpl { - srv: service.into_new_service(), + srv: service.into_factory(), rdef: self.rdef, name: self.name, guards: self.guards, @@ -509,7 +500,7 @@ struct WebServiceImpl { impl HttpServiceFactory for WebServiceImpl where - T: NewService< + T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -539,8 +530,9 @@ where #[cfg(test)] mod tests { use super::*; - use crate::test::{call_service, init_service, TestRequest}; + use crate::test::{block_on, init_service, TestRequest}; use crate::{guard, http, web, App, HttpResponse}; + use actix_service::Service; #[test] fn test_service_request() { @@ -565,25 +557,33 @@ mod tests { #[test] fn test_service() { - let mut srv = init_service( - App::new().service(web::service("/test").name("test").finish( - |req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()), - )), - ); - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), http::StatusCode::OK); + block_on(async { + let mut srv = init_service( + App::new().service(web::service("/test").name("test").finish( + |req: ServiceRequest| { + ok(req.into_response(HttpResponse::Ok().finish())) + }, + )), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); - let mut srv = init_service( - App::new().service(web::service("/test").guard(guard::Get()).finish( - |req: ServiceRequest| req.into_response(HttpResponse::Ok().finish()), - )), - ); - let req = TestRequest::with_uri("/test") - .method(http::Method::PUT) - .to_request(); - let resp = call_service(&mut srv, req); - assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); + let mut srv = init_service(App::new().service( + web::service("/test").guard(guard::Get()).finish( + |req: ServiceRequest| { + ok(req.into_response(HttpResponse::Ok().finish())) + }, + ), + )) + .await; + let req = TestRequest::with_uri("/test") + .method(http::Method::PUT) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); + }) } #[test] diff --git a/src/test.rs b/src/test.rs index 6563253c..8cee3bc6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -7,10 +7,10 @@ use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, Extensions, Request}; use actix_router::{Path, ResourceDef, Url}; use actix_server_config::ServerConfig; -use actix_service::{IntoNewService, IntoService, NewService, Service}; +use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use bytes::{Bytes, BytesMut}; -use futures::future::{ok, Future}; -use futures::Stream; +use futures::future::{ok, Future, FutureExt}; +use futures::stream::{Stream, StreamExt}; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; @@ -39,7 +39,7 @@ pub fn default_service( ) -> impl Service, Error = Error> { (move |req: ServiceRequest| { - req.into_response(HttpResponse::build(status_code).finish()) + ok(req.into_response(HttpResponse::build(status_code).finish())) }) .into_service() } @@ -66,12 +66,12 @@ pub fn default_service( /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` -pub fn init_service( +pub async fn init_service( app: R, ) -> impl Service, Error = E> where - R: IntoNewService, - S: NewService< + R: IntoServiceFactory, + S: ServiceFactory< Config = ServerConfig, Request = Request, Response = ServiceResponse, @@ -80,9 +80,8 @@ where S::InitError: std::fmt::Debug, { let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap()); - let srv = app.into_new_service(); - let fut = run_on(move || srv.new_service(&cfg)); - block_on(fut).unwrap() + let srv = app.into_factory(); + srv.new_service(&cfg).await.unwrap() } /// Calls service and waits for response future completion. @@ -106,12 +105,12 @@ where /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` -pub fn call_service(app: &mut S, req: R) -> S::Response +pub async fn call_service(app: &mut S, req: R) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, { - block_on(run_on(move || app.call(req))).unwrap() + app.call(req).await.unwrap() } /// Helper function that returns a response body of a TestRequest @@ -138,22 +137,22 @@ where /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } /// ``` -pub fn read_response(app: &mut S, req: Request) -> Bytes +pub async fn read_response(app: &mut S, req: Request) -> Bytes where S: Service, Error = Error>, B: MessageBody, { - block_on(run_on(move || { - app.call(req).and_then(|mut resp: ServiceResponse| { - resp.take_body() - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, Error>(body) - }) - .map(|body: BytesMut| body.freeze()) - }) - })) - .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) + let mut resp = app + .call(req) + .await + .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")); + + let mut body = resp.take_body(); + let mut bytes = BytesMut::new(); + while let Some(item) = body.next().await { + bytes.extend_from_slice(&item.unwrap()); + } + bytes.freeze() } /// Helper function that returns a response body of a ServiceResponse. @@ -181,19 +180,27 @@ where /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } /// ``` -pub fn read_body(mut res: ServiceResponse) -> Bytes +pub async fn read_body(mut res: ServiceResponse) -> Bytes where B: MessageBody, { - block_on(run_on(move || { - res.take_body() - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, Error>(body) - }) - .map(|body: BytesMut| body.freeze()) - })) - .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) + let mut body = res.take_body(); + let mut bytes = BytesMut::new(); + while let Some(item) = body.next().await { + bytes.extend_from_slice(&item.unwrap()); + } + bytes.freeze() +} + +pub async fn load_stream(mut stream: S) -> Result +where + S: Stream> + Unpin, +{ + let mut data = BytesMut::new(); + while let Some(item) = stream.next().await { + data.extend_from_slice(&item?); + } + Ok(data.freeze()) } /// Helper function that returns a deserialized response body of a TestRequest @@ -230,27 +237,16 @@ where /// let result: Person = test::read_response_json(&mut app, req); /// } /// ``` -pub fn read_response_json(app: &mut S, req: Request) -> T +pub async fn read_response_json(app: &mut S, req: Request) -> T where S: Service, Error = Error>, B: MessageBody, T: DeserializeOwned, { - block_on(run_on(move || { - app.call(req).and_then(|mut resp: ServiceResponse| { - resp.take_body() - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, Error>(body) - }) - .and_then(|body: BytesMut| { - ok(serde_json::from_slice(&body).unwrap_or_else(|_| { - panic!("read_response_json failed during deserialization") - })) - }) - }) - })) - .unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap")) + let body = read_response(app, req).await; + + serde_json::from_slice(&body) + .unwrap_or_else(|_| panic!("read_response_json failed during deserialization")) } /// Test `Request` builder. @@ -511,74 +507,82 @@ mod tests { #[test] fn test_basics() { - let req = TestRequest::with_hdr(header::ContentType::json()) - .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) - .param("test", "123") - .data(10u32) - .to_http_request(); - assert!(req.headers().contains_key(header::CONTENT_TYPE)); - assert!(req.headers().contains_key(header::DATE)); - assert_eq!(&req.match_info()["test"], "123"); - assert_eq!(req.version(), Version::HTTP_2); - let data = req.get_app_data::().unwrap(); - assert!(req.get_app_data::().is_none()); - assert_eq!(*data, 10); - assert_eq!(*data.get_ref(), 10); + block_on(async { + let req = TestRequest::with_hdr(header::ContentType::json()) + .version(Version::HTTP_2) + .set(header::Date(SystemTime::now().into())) + .param("test", "123") + .data(10u32) + .to_http_request(); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); + assert!(req.headers().contains_key(header::DATE)); + assert_eq!(&req.match_info()["test"], "123"); + assert_eq!(req.version(), Version::HTTP_2); + let data = req.get_app_data::().unwrap(); + assert!(req.get_app_data::().is_none()); + assert_eq!(*data, 10); + assert_eq!(*data.get_ref(), 10); - assert!(req.app_data::().is_none()); - let data = req.app_data::().unwrap(); - assert_eq!(*data, 10); + assert!(req.app_data::().is_none()); + let data = req.app_data::().unwrap(); + assert_eq!(*data, 10); + }) } #[test] fn test_request_methods() { - let mut app = init_service( - App::new().service( - web::resource("/index.html") - .route(web::put().to(|| HttpResponse::Ok().body("put!"))) - .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))) - .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))), - ), - ); + block_on(async { + let mut app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::put().to(|| HttpResponse::Ok().body("put!"))) + .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))) + .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))), + ), + ) + .await; - let put_req = TestRequest::put() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); + let put_req = TestRequest::put() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); - let result = read_response(&mut app, put_req); - assert_eq!(result, Bytes::from_static(b"put!")); + let result = read_response(&mut app, put_req).await; + assert_eq!(result, Bytes::from_static(b"put!")); - let patch_req = TestRequest::patch() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); + let patch_req = TestRequest::patch() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); - let result = read_response(&mut app, patch_req); - assert_eq!(result, Bytes::from_static(b"patch!")); + let result = read_response(&mut app, patch_req).await; + assert_eq!(result, Bytes::from_static(b"patch!")); - let delete_req = TestRequest::delete().uri("/index.html").to_request(); - let result = read_response(&mut app, delete_req); - assert_eq!(result, Bytes::from_static(b"delete!")); + let delete_req = TestRequest::delete().uri("/index.html").to_request(); + let result = read_response(&mut app, delete_req).await; + assert_eq!(result, Bytes::from_static(b"delete!")); + }) } #[test] fn test_response() { - let mut app = init_service( - App::new().service( - web::resource("/index.html") - .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), - ), - ); + block_on(async { + let mut app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), + ), + ) + .await; - let req = TestRequest::post() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); + let req = TestRequest::post() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); - let result = read_response(&mut app, req); - assert_eq!(result, Bytes::from_static(b"welcome!")); + let result = read_response(&mut app, req).await; + assert_eq!(result, Bytes::from_static(b"welcome!")); + }) } #[derive(Serialize, Deserialize)] @@ -589,129 +593,147 @@ mod tests { #[test] fn test_response_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - HttpResponse::Ok().json(person.into_inner()) - }), - ))); + block_on(async { + let mut app = + init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| { + HttpResponse::Ok().json(person.into_inner()) + }), + ))) + .await; - let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); + let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); - let req = TestRequest::post() - .uri("/people") - .header(header::CONTENT_TYPE, "application/json") - .set_payload(payload) - .to_request(); + let req = TestRequest::post() + .uri("/people") + .header(header::CONTENT_TYPE, "application/json") + .set_payload(payload) + .to_request(); - let result: Person = read_response_json(&mut app, req); - assert_eq!(&result.id, "12345"); + let result: Person = read_response_json(&mut app, req).await; + assert_eq!(&result.id, "12345"); + }) } #[test] fn test_request_response_form() { - let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Form| { - HttpResponse::Ok().json(person.into_inner()) - }), - ))); + block_on(async { + let mut app = + init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Form| { + HttpResponse::Ok().json(person.into_inner()) + }), + ))) + .await; - let payload = Person { - id: "12345".to_string(), - name: "User name".to_string(), - }; + let payload = Person { + id: "12345".to_string(), + name: "User name".to_string(), + }; - let req = TestRequest::post() - .uri("/people") - .set_form(&payload) - .to_request(); + let req = TestRequest::post() + .uri("/people") + .set_form(&payload) + .to_request(); - assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); + assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); - let result: Person = read_response_json(&mut app, req); - assert_eq!(&result.id, "12345"); - assert_eq!(&result.name, "User name"); + let result: Person = read_response_json(&mut app, req).await; + assert_eq!(&result.id, "12345"); + assert_eq!(&result.name, "User name"); + }) } #[test] fn test_request_response_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - HttpResponse::Ok().json(person.into_inner()) - }), - ))); + block_on(async { + let mut app = + init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| { + HttpResponse::Ok().json(person.into_inner()) + }), + ))) + .await; - let payload = Person { - id: "12345".to_string(), - name: "User name".to_string(), - }; + let payload = Person { + id: "12345".to_string(), + name: "User name".to_string(), + }; - let req = TestRequest::post() - .uri("/people") - .set_json(&payload) - .to_request(); + let req = TestRequest::post() + .uri("/people") + .set_json(&payload) + .to_request(); - assert_eq!(req.content_type(), "application/json"); + assert_eq!(req.content_type(), "application/json"); - let result: Person = read_response_json(&mut app, req); - assert_eq!(&result.id, "12345"); - assert_eq!(&result.name, "User name"); + let result: Person = read_response_json(&mut app, req).await; + assert_eq!(&result.id, "12345"); + assert_eq!(&result.name, "User name"); + }) } #[test] fn test_async_with_block() { - fn async_with_block() -> impl Future { - web::block(move || Some(4).ok_or("wrong")).then(|res| match res { - Ok(value) => HttpResponse::Ok() - .content_type("text/plain") - .body(format!("Async with block value: {}", value)), - Err(_) => panic!("Unexpected"), - }) - } + block_on(async { + async fn async_with_block() -> Result { + let res = web::block(move || Some(4usize).ok_or("wrong")).await; - let mut app = init_service( - App::new().service(web::resource("/index.html").to_async(async_with_block)), - ); - - let req = TestRequest::post().uri("/index.html").to_request(); - let res = block_fn(|| app.call(req)).unwrap(); - assert!(res.status().is_success()); - } - - #[test] - fn test_actor() { - use actix::Actor; - - struct MyActor; - - struct Num(usize); - impl actix::Message for Num { - type Result = usize; - } - impl actix::Actor for MyActor { - type Context = actix::Context; - } - impl actix::Handler for MyActor { - type Result = usize; - fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result { - msg.0 + match res? { + Ok(value) => Ok(HttpResponse::Ok() + .content_type("text/plain") + .body(format!("Async with block value: {}", value))), + Err(_) => panic!("Unexpected"), + } } - } - let addr = run_on(|| MyActor.start()); - let mut app = init_service(App::new().service( - web::resource("/index.html").to_async(move || { - addr.send(Num(1)).from_err().and_then(|res| { - if res == 1 { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }) - }), - )); + let mut app = init_service( + App::new() + .service(web::resource("/index.html").to_async(async_with_block)), + ) + .await; - let req = TestRequest::post().uri("/index.html").to_request(); - let res = block_fn(|| app.call(req)).unwrap(); - assert!(res.status().is_success()); + let req = TestRequest::post().uri("/index.html").to_request(); + let res = app.call(req).await.unwrap(); + assert!(res.status().is_success()); + }) } + + // #[test] + // fn test_actor() { + // use actix::Actor; + + // struct MyActor; + + // struct Num(usize); + // impl actix::Message for Num { + // type Result = usize; + // } + // impl actix::Actor for MyActor { + // type Context = actix::Context; + // } + // impl actix::Handler for MyActor { + // type Result = usize; + // fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result { + // msg.0 + // } + // } + + // let addr = run_on(|| MyActor.start()); + // let mut app = init_service(App::new().service( + // web::resource("/index.html").to_async(move || { + // addr.send(Num(1)).from_err().and_then(|res| { + // if res == 1 { + // HttpResponse::Ok() + // } else { + // HttpResponse::BadRequest() + // } + // }) + // }), + // )); + + // let req = TestRequest::post().uri("/index.html").to_request(); + // let res = block_fn(|| app.call(req)).unwrap(); + // assert!(res.status().is_success()); + // } } diff --git a/src/types/form.rs b/src/types/form.rs index c727ce0e..694fe6db 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -1,12 +1,16 @@ //! Form extractor +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use std::{fmt, ops}; use actix_http::{Error, HttpMessage, Payload, Response}; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures::{Future, Poll, Stream}; +use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; +use futures::{Stream, StreamExt}; use serde::de::DeserializeOwned; use serde::Serialize; @@ -110,7 +114,7 @@ where { type Config = FormConfig; type Error = Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { @@ -120,18 +124,19 @@ where .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((16384, None)); - Box::new( - UrlEncoded::new(req, payload) - .limit(limit) - .map_err(move |e| { + UrlEncoded::new(req, payload) + .limit(limit) + .map(move |res| match res { + Err(e) => { if let Some(err) = err { - (*err)(e, &req2) + Err((*err)(e, &req2)) } else { - e.into() + Err(e.into()) } - }) - .map(Form), - ) + } + Ok(item) => Ok(Form(item)), + }) + .boxed_local() } } @@ -149,15 +154,15 @@ impl fmt::Display for Form { impl Responder for Form { type Error = Error; - type Future = Result; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { let body = match serde_urlencoded::to_string(&self.0) { Ok(body) => body, - Err(e) => return Err(e.into()), + Err(e) => return err(e.into()), }; - Ok(Response::build(StatusCode::OK) + ok(Response::build(StatusCode::OK) .set(ContentType::form_url_encoded()) .body(body)) } @@ -240,7 +245,7 @@ pub struct UrlEncoded { length: Option, encoding: &'static Encoding, err: Option, - fut: Option>>, + fut: Option>>, } impl UrlEncoded { @@ -301,45 +306,45 @@ impl Future for UrlEncoded where U: DeserializeOwned + 'static, { - type Item = U; - type Error = UrlencodedError; + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { if let Some(ref mut fut) = self.fut { - return fut.poll(); + return Pin::new(fut).poll(cx); } if let Some(err) = self.err.take() { - return Err(err); + return Poll::Ready(Err(err)); } // payload size let limit = self.limit; if let Some(len) = self.length.take() { if len > limit { - return Err(UrlencodedError::Overflow { size: len, limit }); + return Poll::Ready(Err(UrlencodedError::Overflow { size: len, limit })); } } // future let encoding = self.encoding; - let fut = self - .stream - .take() - .unwrap() - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(UrlencodedError::Overflow { - size: body.len() + chunk.len(), - limit, - }) - } else { - body.extend_from_slice(&chunk); - Ok(body) + let mut stream = self.stream.take().unwrap(); + + self.fut = Some( + async move { + let mut body = BytesMut::with_capacity(8192); + + while let Some(item) = stream.next().await { + let chunk = item?; + if (body.len() + chunk.len()) > limit { + return Err(UrlencodedError::Overflow { + size: body.len() + chunk.len(), + limit, + }); + } else { + body.extend_from_slice(&chunk); + } } - }) - .and_then(move |body| { + if encoding == UTF_8 { serde_urlencoded::from_bytes::(&body) .map_err(|_| UrlencodedError::Parse) @@ -351,9 +356,10 @@ where serde_urlencoded::from_str::(&body) .map_err(|_| UrlencodedError::Parse) } - }); - self.fut = Some(Box::new(fut)); - self.poll() + } + .boxed_local(), + ); + self.poll(cx) } } @@ -374,20 +380,24 @@ mod tests { #[test] fn test_form() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + block_on(async { + let (req, mut pl) = TestRequest::with_header( + CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); - let Form(s) = block_on(Form::::from_request(&req, &mut pl)).unwrap(); - assert_eq!( - s, - Info { - hello: "world".into(), - counter: 123 - } - ); + let Form(s) = Form::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!( + s, + Info { + hello: "world".into(), + counter: 123 + } + ); + }) } fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { @@ -410,81 +420,93 @@ mod tests { #[test] fn test_urlencoded_error() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "xxxx") - .to_http_parts(); - let info = block_on(UrlEncoded::::new(&req, &mut pl)); - assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); - - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "1000000") - .to_http_parts(); - let info = block_on(UrlEncoded::::new(&req, &mut pl)); - assert!(eq( - info.err().unwrap(), - UrlencodedError::Overflow { size: 0, limit: 0 } - )); - - let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") - .header(CONTENT_LENGTH, "10") + block_on(async { + let (req, mut pl) = TestRequest::with_header( + CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(CONTENT_LENGTH, "xxxx") .to_http_parts(); - let info = block_on(UrlEncoded::::new(&req, &mut pl)); - assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); + let info = UrlEncoded::::new(&req, &mut pl).await; + assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); + + let (req, mut pl) = TestRequest::with_header( + CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(CONTENT_LENGTH, "1000000") + .to_http_parts(); + let info = UrlEncoded::::new(&req, &mut pl).await; + assert!(eq( + info.err().unwrap(), + UrlencodedError::Overflow { size: 0, limit: 0 } + )); + + let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") + .header(CONTENT_LENGTH, "10") + .to_http_parts(); + let info = UrlEncoded::::new(&req, &mut pl).await; + assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); + }) } #[test] fn test_urlencoded() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + block_on(async { + let (req, mut pl) = TestRequest::with_header( + CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); - let info = block_on(UrlEncoded::::new(&req, &mut pl)).unwrap(); - assert_eq!( - info, - Info { - hello: "world".to_owned(), - counter: 123 - } - ); + let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); + assert_eq!( + info, + Info { + hello: "world".to_owned(), + counter: 123 + } + ); - let (req, mut pl) = TestRequest::with_header( - CONTENT_TYPE, - "application/x-www-form-urlencoded; charset=utf-8", - ) - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + let (req, mut pl) = TestRequest::with_header( + CONTENT_TYPE, + "application/x-www-form-urlencoded; charset=utf-8", + ) + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); - let info = block_on(UrlEncoded::::new(&req, &mut pl)).unwrap(); - assert_eq!( - info, - Info { - hello: "world".to_owned(), - counter: 123 - } - ); + let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); + assert_eq!( + info, + Info { + hello: "world".to_owned(), + counter: 123 + } + ); + }) } #[test] fn test_responder() { - let req = TestRequest::default().to_http_request(); + block_on(async { + let req = TestRequest::default().to_http_request(); - let form = Form(Info { - hello: "world".to_string(), - counter: 123, - }); - let resp = form.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/x-www-form-urlencoded") - ); + let form = Form(Info { + hello: "world".to_string(), + counter: 123, + }); + let resp = form.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/x-www-form-urlencoded") + ); - use crate::responder::tests::BodyTest; - assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); + use crate::responder::tests::BodyTest; + assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); + }) } } diff --git a/src/types/json.rs b/src/types/json.rs index e80d0a45..19f8532b 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -1,10 +1,14 @@ //! Json extractor/responder +use std::future::Future; +use std::pin::Pin; use std::sync::Arc; +use std::task::{Context, Poll}; use std::{fmt, ops}; use bytes::BytesMut; -use futures::{Future, Poll, Stream}; +use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; +use futures::{Stream, StreamExt}; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; @@ -118,15 +122,15 @@ where impl Responder for Json { type Error = Error; - type Future = Result; + type Future = Ready>; fn respond_to(self, _: &HttpRequest) -> Self::Future { let body = match serde_json::to_string(&self.0) { Ok(body) => body, - Err(e) => return Err(e.into()), + Err(e) => return err(e.into()), }; - Ok(Response::build(StatusCode::OK) + ok(Response::build(StatusCode::OK) .content_type("application/json") .body(body)) } @@ -169,7 +173,7 @@ where T: DeserializeOwned + 'static, { type Error = Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; type Config = JsonConfig; #[inline] @@ -180,23 +184,24 @@ where .map(|c| (c.limit, c.ehandler.clone(), c.content_type.clone())) .unwrap_or((32768, None, None)); - Box::new( - JsonBody::new(req, payload, ctype) - .limit(limit) - .map_err(move |e| { + JsonBody::new(req, payload, ctype) + .limit(limit) + .map(move |res| match res { + Err(e) => { log::debug!( "Failed to deserialize Json from payload. \ Request path: {}", req2.path() ); if let Some(err) = err { - (*err)(e, &req2) + Err((*err)(e, &req2)) } else { - e.into() + Err(e.into()) } - }) - .map(Json), - ) + } + Ok(data) => Ok(Json(data)), + }) + .boxed_local() } } @@ -290,7 +295,7 @@ pub struct JsonBody { length: Option, stream: Option>, err: Option, - fut: Option>>, + fut: Option>>, } impl JsonBody @@ -349,41 +354,43 @@ impl Future for JsonBody where U: DeserializeOwned + 'static, { - type Item = U; - type Error = JsonPayloadError; + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { if let Some(ref mut fut) = self.fut { - return fut.poll(); + return Pin::new(fut).poll(cx); } if let Some(err) = self.err.take() { - return Err(err); + return Poll::Ready(Err(err)); } let limit = self.limit; if let Some(len) = self.length.take() { if len > limit { - return Err(JsonPayloadError::Overflow); + return Poll::Ready(Err(JsonPayloadError::Overflow)); } } + let mut stream = self.stream.take().unwrap(); - let fut = self - .stream - .take() - .unwrap() - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(JsonPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) + self.fut = Some( + async move { + let mut body = BytesMut::with_capacity(8192); + + while let Some(item) = stream.next().await { + let chunk = item?; + if (body.len() + chunk.len()) > limit { + return Err(JsonPayloadError::Overflow); + } else { + body.extend_from_slice(&chunk); + } } - }) - .and_then(|body| Ok(serde_json::from_slice::(&body)?)); - self.fut = Some(Box::new(fut)); - self.poll() + Ok(serde_json::from_slice::(&body)?) + } + .boxed_local(), + ); + + self.poll(cx) } } @@ -395,7 +402,7 @@ mod tests { use super::*; use crate::error::InternalError; use crate::http::header; - use crate::test::{block_on, TestRequest}; + use crate::test::{block_on, load_stream, TestRequest}; use crate::HttpResponse; #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -419,218 +426,234 @@ mod tests { #[test] fn test_responder() { - let req = TestRequest::default().to_http_request(); + block_on(async { + let req = TestRequest::default().to_http_request(); - let j = Json(MyObject { - name: "test".to_string(), - }); - let resp = j.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("application/json") - ); + let j = Json(MyObject { + name: "test".to_string(), + }); + let resp = j.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("application/json") + ); - use crate::responder::tests::BodyTest; - assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}"); + use crate::responder::tests::BodyTest; + assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}"); + }) } #[test] fn test_custom_error_responder() { - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().limit(10).error_handler(|err, _| { - let msg = MyObject { - name: "invalid request".to_string(), - }; - let resp = HttpResponse::BadRequest() - .body(serde_json::to_string(&msg).unwrap()); - InternalError::from_response(err, resp).into() - })) - .to_http_parts(); + block_on(async { + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().limit(10).error_handler(|err, _| { + let msg = MyObject { + name: "invalid request".to_string(), + }; + let resp = HttpResponse::BadRequest() + .body(serde_json::to_string(&msg).unwrap()); + InternalError::from_response(err, resp).into() + })) + .to_http_parts(); - let s = block_on(Json::::from_request(&req, &mut pl)); - let mut resp = Response::from_error(s.err().unwrap().into()); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let s = Json::::from_request(&req, &mut pl).await; + let mut resp = Response::from_error(s.err().unwrap().into()); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let body = block_on(resp.take_body().concat2()).unwrap(); - let msg: MyObject = serde_json::from_slice(&body).unwrap(); - assert_eq!(msg.name, "invalid request"); + let body = load_stream(resp.take_body()).await.unwrap(); + let msg: MyObject = serde_json::from_slice(&body).unwrap(); + assert_eq!(msg.name, "invalid request"); + }) } #[test] fn test_extract() { - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .to_http_parts(); + block_on(async { + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .to_http_parts(); - let s = block_on(Json::::from_request(&req, &mut pl)).unwrap(); - assert_eq!(s.name, "test"); - assert_eq!( - s.into_inner(), - MyObject { - name: "test".to_string() - } - ); + let s = Json::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(s.name, "test"); + assert_eq!( + s.into_inner(), + MyObject { + name: "test".to_string() + } + ); - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().limit(10)) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().limit(10)) + .to_http_parts(); - let s = block_on(Json::::from_request(&req, &mut pl)); - assert!(format!("{}", s.err().unwrap()) - .contains("Json payload size is bigger than allowed")); + let s = Json::::from_request(&req, &mut pl).await; + assert!(format!("{}", s.err().unwrap()) + .contains("Json payload size is bigger than allowed")); - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data( - JsonConfig::default() - .limit(10) - .error_handler(|_, _| JsonPayloadError::ContentType.into()), - ) - .to_http_parts(); - let s = block_on(Json::::from_request(&req, &mut pl)); - assert!(format!("{}", s.err().unwrap()).contains("Content type error")); + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data( + JsonConfig::default() + .limit(10) + .error_handler(|_, _| JsonPayloadError::ContentType.into()), + ) + .to_http_parts(); + let s = Json::::from_request(&req, &mut pl).await; + assert!(format!("{}", s.err().unwrap()).contains("Content type error")); + }) } #[test] fn test_json_body() { - let (req, mut pl) = TestRequest::default().to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl, None)); - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + block_on(async { + let (req, mut pl) = TestRequest::default().to_http_parts(); + let json = JsonBody::::new(&req, &mut pl, None).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - let (req, mut pl) = TestRequest::default() - .header( + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + ) + .to_http_parts(); + let json = JsonBody::::new(&req, &mut pl, None).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ) + .to_http_parts(); + + let json = JsonBody::::new(&req, &mut pl, None) + .limit(100) + .await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); + + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .to_http_parts(); + + let json = JsonBody::::new(&req, &mut pl, None).await; + assert_eq!( + json.ok().unwrap(), + MyObject { + name: "test".to_owned() + } + ); + }) + } + + #[test] + fn test_with_json_and_bad_content_type() { + block_on(async { + let (req, mut pl) = TestRequest::with_header( header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ) - .to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl, None)); - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ) - .to_http_parts(); - - let json = block_on(JsonBody::::new(&req, &mut pl, None).limit(100)); - assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), + header::HeaderValue::from_static("text/plain"), ) .header( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().limit(4096)) .to_http_parts(); - let json = block_on(JsonBody::::new(&req, &mut pl, None)); - assert_eq!( - json.ok().unwrap(), - MyObject { - name: "test".to_owned() - } - ); - } - - #[test] - fn test_with_json_and_bad_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().limit(4096)) - .to_http_parts(); - - let s = block_on(Json::::from_request(&req, &mut pl)); - assert!(s.is_err()) + let s = Json::::from_request(&req, &mut pl).await; + assert!(s.is_err()) + }) } #[test] fn test_with_json_and_good_custom_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) - .to_http_parts(); + block_on(async { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); - let s = block_on(Json::::from_request(&req, &mut pl)); - assert!(s.is_ok()) + let s = Json::::from_request(&req, &mut pl).await; + assert!(s.is_ok()) + }) } #[test] fn test_with_json_and_bad_custom_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/html"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) - .to_http_parts(); + block_on(async { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); - let s = block_on(Json::::from_request(&req, &mut pl)); - assert!(s.is_err()) + let s = Json::::from_request(&req, &mut pl).await; + assert!(s.is_err()) + }) } } diff --git a/src/types/path.rs b/src/types/path.rs index fa7c6e11..89b9392b 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -5,6 +5,7 @@ use std::{fmt, ops}; use actix_http::error::{Error, ErrorNotFound}; use actix_router::PathDeserializer; +use futures::future::{ready, Ready}; use serde::de; use crate::dev::Payload; @@ -159,7 +160,7 @@ where T: de::DeserializeOwned, { type Error = Error; - type Future = Result; + type Future = Ready>; type Config = PathConfig; #[inline] @@ -169,21 +170,23 @@ where .map(|c| c.ehandler.clone()) .unwrap_or(None); - de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) - .map(|inner| Path { inner }) - .map_err(move |e| { - log::debug!( - "Failed during Path extractor deserialization. \ - Request path: {:?}", - req.path() - ); - if let Some(error_handler) = error_handler { - let e = PathError::Deserialize(e); - (error_handler)(e, req) - } else { - ErrorNotFound(e) - } - }) + ready( + de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) + .map(|inner| Path { inner }) + .map_err(move |e| { + log::debug!( + "Failed during Path extractor deserialization. \ + Request path: {:?}", + req.path() + ); + if let Some(error_handler) = error_handler { + let e = PathError::Deserialize(e); + (error_handler)(e, req) + } else { + ErrorNotFound(e) + } + }), + ) } } @@ -268,100 +271,116 @@ mod tests { #[test] fn test_extract_path_single() { - let resource = ResourceDef::new("/{value}/"); + block_on(async { + let resource = ResourceDef::new("/{value}/"); - let mut req = TestRequest::with_uri("/32/").to_srv_request(); - resource.match_path(req.match_info_mut()); + let mut req = TestRequest::with_uri("/32/").to_srv_request(); + resource.match_path(req.match_info_mut()); - let (req, mut pl) = req.into_parts(); - assert_eq!(*Path::::from_request(&req, &mut pl).unwrap(), 32); - assert!(Path::::from_request(&req, &mut pl).is_err()); + let (req, mut pl) = req.into_parts(); + assert_eq!(*Path::::from_request(&req, &mut pl).await.unwrap(), 32); + assert!(Path::::from_request(&req, &mut pl).await.is_err()); + }) } #[test] fn test_tuple_extract() { - let resource = ResourceDef::new("/{key}/{value}/"); + block_on(async { + let resource = ResourceDef::new("/{key}/{value}/"); - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - resource.match_path(req.match_info_mut()); + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + resource.match_path(req.match_info_mut()); - let (req, mut pl) = req.into_parts(); - let res = - block_on(<(Path<(String, String)>,)>::from_request(&req, &mut pl)).unwrap(); - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); + let (req, mut pl) = req.into_parts(); + let res = <(Path<(String, String)>,)>::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); - let res = block_on( - <(Path<(String, String)>, Path<(String, String)>)>::from_request( + let res = <(Path<(String, String)>, Path<(String, String)>)>::from_request( &req, &mut pl, - ), - ) - .unwrap(); - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - assert_eq!((res.1).0, "name"); - assert_eq!((res.1).1, "user1"); + ) + .await + .unwrap(); + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); + assert_eq!((res.1).0, "name"); + assert_eq!((res.1).1, "user1"); - let () = <()>::from_request(&req, &mut pl).unwrap(); + let () = <()>::from_request(&req, &mut pl).await.unwrap(); + }) } #[test] fn test_request_extract() { - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + block_on(async { + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); - let (req, mut pl) = req.into_parts(); - let mut s = Path::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - s.value = "user2".to_string(); - assert_eq!(s.value, "user2"); - assert_eq!( - format!("{}, {:?}", s, s), - "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" - ); - let s = s.into_inner(); - assert_eq!(s.value, "user2"); + let (req, mut pl) = req.into_parts(); + let mut s = Path::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); + s.value = "user2".to_string(); + assert_eq!(s.value, "user2"); + assert_eq!( + format!("{}, {:?}", s, s), + "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" + ); + let s = s.into_inner(); + assert_eq!(s.value, "user2"); - let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); + let s = Path::<(String, String)>::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); - let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); + let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); - let (req, mut pl) = req.into_parts(); - let s = Path::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); + let (req, mut pl) = req.into_parts(); + let s = Path::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(s.as_ref().key, "name"); + assert_eq!(s.value, 32); - let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); + let s = Path::<(String, u8)>::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); - let res = Path::>::from_request(&req, &mut pl).unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); + let res = Path::>::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(res[0], "name".to_owned()); + assert_eq!(res[1], "32".to_owned()); + }) } #[test] fn test_custom_err_handler() { - let (req, mut pl) = TestRequest::with_uri("/name/user1/") - .data(PathConfig::default().error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ) - .into() - })) - .to_http_parts(); + block_on(async { + let (req, mut pl) = TestRequest::with_uri("/name/user1/") + .data(PathConfig::default().error_handler(|err, _| { + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish(), + ) + .into() + })) + .to_http_parts(); - let s = block_on(Path::<(usize,)>::from_request(&req, &mut pl)).unwrap_err(); - let res: HttpResponse = s.into(); + let s = Path::<(usize,)>::from_request(&req, &mut pl) + .await + .unwrap_err(); + let res: HttpResponse = s.into(); - assert_eq!(res.status(), http::StatusCode::CONFLICT); + assert_eq!(res.status(), http::StatusCode::CONFLICT); + }) } } diff --git a/src/types/payload.rs b/src/types/payload.rs index 8fc5f093..61f7328b 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -1,12 +1,15 @@ //! Payload/Bytes/String extractors +use std::future::Future; +use std::pin::Pin; use std::str; +use std::task::{Context, Poll}; use actix_http::error::{Error, ErrorBadRequest, PayloadError}; use actix_http::HttpMessage; use bytes::{Bytes, BytesMut}; use encoding_rs::UTF_8; -use futures::future::{err, Either, FutureResult}; -use futures::{Future, Poll, Stream}; +use futures::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready}; +use futures::{Stream, StreamExt}; use mime::Mime; use crate::dev; @@ -19,21 +22,19 @@ use crate::request::HttpRequest; /// ## Example /// /// ```rust -/// use futures::{Future, Stream}; +/// use futures::{Future, Stream, StreamExt}; /// use actix_web::{web, error, App, Error, HttpResponse}; /// /// /// extract binary data from request -/// fn index(body: web::Payload) -> impl Future +/// async fn index(mut body: web::Payload) -> Result /// { -/// body.map_err(Error::from) -/// .fold(web::BytesMut::new(), move |mut body, chunk| { -/// body.extend_from_slice(&chunk); -/// Ok::<_, Error>(body) -/// }) -/// .and_then(|body| { -/// format!("Body {:?}!", body); -/// Ok(HttpResponse::Ok().finish()) -/// }) +/// let mut bytes = web::BytesMut::new(); +/// while let Some(item) = body.next().await { +/// bytes.extend_from_slice(&item?); +/// } +/// +/// format!("Body {:?}!", bytes); +/// Ok(HttpResponse::Ok().finish()) /// } /// /// fn main() { @@ -53,12 +54,14 @@ impl Payload { } impl Stream for Payload { - type Item = Bytes; - type Error = PayloadError; + type Item = Result; #[inline] - fn poll(&mut self) -> Poll, PayloadError> { - self.0.poll() + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll> { + Pin::new(&mut self.0).poll_next(cx) } } @@ -67,21 +70,19 @@ impl Stream for Payload { /// ## Example /// /// ```rust -/// use futures::{Future, Stream}; +/// use futures::{Future, Stream, StreamExt}; /// use actix_web::{web, error, App, Error, HttpResponse}; /// /// /// extract binary data from request -/// fn index(body: web::Payload) -> impl Future +/// async fn index(mut body: web::Payload) -> Result /// { -/// body.map_err(Error::from) -/// .fold(web::BytesMut::new(), move |mut body, chunk| { -/// body.extend_from_slice(&chunk); -/// Ok::<_, Error>(body) -/// }) -/// .and_then(|body| { -/// format!("Body {:?}!", body); -/// Ok(HttpResponse::Ok().finish()) -/// }) +/// let mut bytes = web::BytesMut::new(); +/// while let Some(item) = body.next().await { +/// bytes.extend_from_slice(&item?); +/// } +/// +/// format!("Body {:?}!", bytes); +/// Ok(HttpResponse::Ok().finish()) /// } /// /// fn main() { @@ -94,11 +95,11 @@ impl Stream for Payload { impl FromRequest for Payload { type Config = PayloadConfig; type Error = Error; - type Future = Result; + type Future = Ready>; #[inline] fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - Ok(Payload(payload.take())) + ok(Payload(payload.take())) } } @@ -130,8 +131,10 @@ impl FromRequest for Payload { impl FromRequest for Bytes { type Config = PayloadConfig; type Error = Error; - type Future = - Either>, FutureResult>; + type Future = Either< + LocalBoxFuture<'static, Result>, + Ready>, + >; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -144,13 +147,12 @@ impl FromRequest for Bytes { }; if let Err(e) = cfg.check_mimetype(req) { - return Either::B(err(e)); + return Either::Right(err(e)); } let limit = cfg.limit; - Either::A(Box::new( - HttpMessageBody::new(req, payload).limit(limit).from_err(), - )) + let fut = HttpMessageBody::new(req, payload).limit(limit); + Either::Left(async move { Ok(fut.await?) }.boxed_local()) } } @@ -185,8 +187,8 @@ impl FromRequest for String { type Config = PayloadConfig; type Error = Error; type Future = Either< - Box>, - FutureResult, + LocalBoxFuture<'static, Result>, + Ready>, >; #[inline] @@ -201,33 +203,34 @@ impl FromRequest for String { // check content-type if let Err(e) = cfg.check_mimetype(req) { - return Either::B(err(e)); + return Either::Right(err(e)); } // check charset let encoding = match req.encoding() { Ok(enc) => enc, - Err(e) => return Either::B(err(e.into())), + Err(e) => return Either::Right(err(e.into())), }; let limit = cfg.limit; + let fut = HttpMessageBody::new(req, payload).limit(limit); - Either::A(Box::new( - HttpMessageBody::new(req, payload) - .limit(limit) - .from_err() - .and_then(move |body| { - if encoding == UTF_8 { - Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) - } else { - Ok(encoding - .decode_without_bom_handling_and_without_replacement(&body) - .map(|s| s.into_owned()) - .ok_or_else(|| ErrorBadRequest("Can not decode body"))?) - } - }), - )) + Either::Left( + async move { + let body = fut.await?; + + if encoding == UTF_8 { + Ok(str::from_utf8(body.as_ref()) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned()) + } else { + Ok(encoding + .decode_without_bom_handling_and_without_replacement(&body) + .map(|s| s.into_owned()) + .ok_or_else(|| ErrorBadRequest("Can not decode body"))?) + } + } + .boxed_local(), + ) } } /// Payload configuration for request's payload. @@ -300,7 +303,7 @@ pub struct HttpMessageBody { length: Option, stream: Option>, err: Option, - fut: Option>>, + fut: Option>>, } impl HttpMessageBody { @@ -346,42 +349,43 @@ impl HttpMessageBody { } impl Future for HttpMessageBody { - type Item = Bytes; - type Error = PayloadError; + type Output = Result; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { if let Some(ref mut fut) = self.fut { - return fut.poll(); + return Pin::new(fut).poll(cx); } if let Some(err) = self.err.take() { - return Err(err); + return Poll::Ready(Err(err)); } if let Some(len) = self.length.take() { if len > self.limit { - return Err(PayloadError::Overflow); + return Poll::Ready(Err(PayloadError::Overflow)); } } // future let limit = self.limit; - self.fut = Some(Box::new( - self.stream - .take() - .unwrap() - .from_err() - .fold(BytesMut::with_capacity(8192), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(PayloadError::Overflow) + let mut stream = self.stream.take().unwrap(); + self.fut = Some( + async move { + let mut body = BytesMut::with_capacity(8192); + + while let Some(item) = stream.next().await { + let chunk = item?; + if body.len() + chunk.len() > limit { + return Err(PayloadError::Overflow); } else { body.extend_from_slice(&chunk); - Ok(body) } - }) - .map(|body| body.freeze()), - )); - self.poll() + } + Ok(body.freeze()) + } + .boxed_local(), + ); + self.poll(cx) } } diff --git a/src/types/query.rs b/src/types/query.rs index 817b2ed7..8061d723 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use std::{fmt, ops}; use actix_http::error::Error; +use futures::future::{err, ok, Ready}; use serde::de; use serde_urlencoded; @@ -132,7 +133,7 @@ where T: de::DeserializeOwned, { type Error = Error; - type Future = Result; + type Future = Ready>; type Config = QueryConfig; #[inline] @@ -143,7 +144,7 @@ where .unwrap_or(None); serde_urlencoded::from_str::(req.query_string()) - .map(|val| Ok(Query(val))) + .map(|val| ok(Query(val))) .unwrap_or_else(move |e| { let e = QueryPayloadError::Deserialize(e); @@ -159,7 +160,7 @@ where e.into() }; - Err(e) + err(e) }) } } @@ -227,7 +228,7 @@ mod tests { use super::*; use crate::error::InternalError; - use crate::test::TestRequest; + use crate::test::{block_on, TestRequest}; use crate::HttpResponse; #[derive(Deserialize, Debug, Display)] @@ -253,42 +254,46 @@ mod tests { #[test] fn test_request_extract() { - let req = TestRequest::with_uri("/name/user1/").to_srv_request(); - let (req, mut pl) = req.into_parts(); - assert!(Query::::from_request(&req, &mut pl).is_err()); + block_on(async { + let req = TestRequest::with_uri("/name/user1/").to_srv_request(); + let (req, mut pl) = req.into_parts(); + assert!(Query::::from_request(&req, &mut pl).await.is_err()); - let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - let (req, mut pl) = req.into_parts(); + let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + let (req, mut pl) = req.into_parts(); - let mut s = Query::::from_request(&req, &mut pl).unwrap(); - assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + let mut s = Query::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(s.id, "test"); + assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); - s.id = "test1".to_string(); - let s = s.into_inner(); - assert_eq!(s.id, "test1"); + s.id = "test1".to_string(); + let s = s.into_inner(); + assert_eq!(s.id, "test1"); + }) } #[test] fn test_custom_error_responder() { - let req = TestRequest::with_uri("/name/user1/") - .data(QueryConfig::default().error_handler(|e, _| { - let resp = HttpResponse::UnprocessableEntity().finish(); - InternalError::from_response(e, resp).into() - })) - .to_srv_request(); + block_on(async { + let req = TestRequest::with_uri("/name/user1/") + .data(QueryConfig::default().error_handler(|e, _| { + let resp = HttpResponse::UnprocessableEntity().finish(); + InternalError::from_response(e, resp).into() + })) + .to_srv_request(); - let (req, mut pl) = req.into_parts(); - let query = Query::::from_request(&req, &mut pl); + let (req, mut pl) = req.into_parts(); + let query = Query::::from_request(&req, &mut pl).await; - assert!(query.is_err()); - assert_eq!( - query - .unwrap_err() - .as_response_error() - .error_response() - .status(), - StatusCode::UNPROCESSABLE_ENTITY - ); + assert!(query.is_err()); + assert_eq!( + query + .unwrap_err() + .as_response_error() + .error_response() + .status(), + StatusCode::UNPROCESSABLE_ENTITY + ); + }) } } diff --git a/src/types/readlines.rs b/src/types/readlines.rs index cea63e43..e2b3f9c1 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -1,9 +1,13 @@ use std::borrow::Cow; +use std::future::Future; +use std::pin::Pin; use std::str; +use std::task::{Context, Poll}; use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; -use futures::{Async, Poll, Stream}; +use futures::Stream; +use pin_project::pin_project; use crate::dev::Payload; use crate::error::{PayloadError, ReadlinesError}; @@ -22,7 +26,7 @@ pub struct Readlines { impl Readlines where T: HttpMessage, - T::Stream: Stream, + T::Stream: Stream> + Unpin, { /// Create a new stream to read request line by line. pub fn new(req: &mut T) -> Self { @@ -62,20 +66,21 @@ where impl Stream for Readlines where T: HttpMessage, - T::Stream: Stream, + T::Stream: Stream> + Unpin, { - type Item = String; - type Error = ReadlinesError; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { - if let Some(err) = self.err.take() { - return Err(err); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let this = self.get_mut(); + + if let Some(err) = this.err.take() { + return Poll::Ready(Some(Err(err))); } // check if there is a newline in the buffer - if !self.checked_buff { + if !this.checked_buff { let mut found: Option = None; - for (ind, b) in self.buff.iter().enumerate() { + for (ind, b) in this.buff.iter().enumerate() { if *b == b'\n' { found = Some(ind); break; @@ -83,28 +88,28 @@ where } if let Some(ind) = found { // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); + if ind + 1 > this.limit { + return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); } - let line = if self.encoding == UTF_8 { - str::from_utf8(&self.buff.split_to(ind + 1)) + let line = if this.encoding == UTF_8 { + str::from_utf8(&this.buff.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { - self.encoding + this.encoding .decode_without_bom_handling_and_without_replacement( - &self.buff.split_to(ind + 1), + &this.buff.split_to(ind + 1), ) .map(Cow::into_owned) .ok_or(ReadlinesError::EncodingError)? }; - return Ok(Async::Ready(Some(line))); + return Poll::Ready(Some(Ok(line))); } - self.checked_buff = true; + this.checked_buff = true; } // poll req for more bytes - match self.stream.poll() { - Ok(Async::Ready(Some(mut bytes))) => { + match Pin::new(&mut this.stream).poll_next(cx) { + Poll::Ready(Some(Ok(mut bytes))) => { // check if there is a newline in bytes let mut found: Option = None; for (ind, b) in bytes.iter().enumerate() { @@ -115,15 +120,15 @@ where } if let Some(ind) = found { // check if line is longer than limit - if ind + 1 > self.limit { - return Err(ReadlinesError::LimitOverflow); + if ind + 1 > this.limit { + return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); } - let line = if self.encoding == UTF_8 { + let line = if this.encoding == UTF_8 { str::from_utf8(&bytes.split_to(ind + 1)) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { - self.encoding + this.encoding .decode_without_bom_handling_and_without_replacement( &bytes.split_to(ind + 1), ) @@ -131,83 +136,72 @@ where .ok_or(ReadlinesError::EncodingError)? }; // extend buffer with rest of the bytes; - self.buff.extend_from_slice(&bytes); - self.checked_buff = false; - return Ok(Async::Ready(Some(line))); + this.buff.extend_from_slice(&bytes); + this.checked_buff = false; + return Poll::Ready(Some(Ok(line))); } - self.buff.extend_from_slice(&bytes); - Ok(Async::NotReady) + this.buff.extend_from_slice(&bytes); + Poll::Pending } - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(None)) => { - if self.buff.is_empty() { - return Ok(Async::Ready(None)); + Poll::Pending => Poll::Pending, + Poll::Ready(None) => { + if this.buff.is_empty() { + return Poll::Ready(None); } - if self.buff.len() > self.limit { - return Err(ReadlinesError::LimitOverflow); + if this.buff.len() > this.limit { + return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); } - let line = if self.encoding == UTF_8 { - str::from_utf8(&self.buff) + let line = if this.encoding == UTF_8 { + str::from_utf8(&this.buff) .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { - self.encoding - .decode_without_bom_handling_and_without_replacement(&self.buff) + this.encoding + .decode_without_bom_handling_and_without_replacement(&this.buff) .map(Cow::into_owned) .ok_or(ReadlinesError::EncodingError)? }; - self.buff.clear(); - Ok(Async::Ready(Some(line))) + this.buff.clear(); + Poll::Ready(Some(Ok(line))) } - Err(e) => Err(ReadlinesError::from(e)), + Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ReadlinesError::from(e)))), } } } #[cfg(test)] mod tests { + use futures::stream::StreamExt; + use super::*; use crate::test::{block_on, TestRequest}; #[test] fn test_readlines() { - let mut req = TestRequest::default() + block_on(async { + let mut req = TestRequest::default() .set_payload(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ Contrary to popular belief, Lorem Ipsum is not simply random text.", )) .to_request(); - let stream = match block_on(Readlines::new(&mut req).into_future()) { - Ok((Some(s), stream)) => { - assert_eq!( - s, - "Lorem Ipsum is simply dummy text of the printing and typesetting\n" - ); - stream - } - _ => unreachable!("error"), - }; - let stream = match block_on(stream.into_future()) { - Ok((Some(s), stream)) => { - assert_eq!( - s, - "industry. Lorem Ipsum has been the industry's standard dummy\n" - ); - stream - } - _ => unreachable!("error"), - }; + let mut stream = Readlines::new(&mut req); + assert_eq!( + stream.next().await.unwrap().unwrap(), + "Lorem Ipsum is simply dummy text of the printing and typesetting\n" + ); - match block_on(stream.into_future()) { - Ok((Some(s), _)) => { - assert_eq!( - s, - "Contrary to popular belief, Lorem Ipsum is not simply random text." - ); - } - _ => unreachable!("error"), - } + assert_eq!( + stream.next().await.unwrap().unwrap(), + "industry. Lorem Ipsum has been the industry's standard dummy\n" + ); + + assert_eq!( + stream.next().await.unwrap().unwrap(), + "Contrary to popular belief, Lorem Ipsum is not simply random text." + ); + }) } } diff --git a/src/web.rs b/src/web.rs index 5669a1e8..67cfd51a 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,11 +1,12 @@ //! Essentials helper functions and types for application registration. use actix_http::http::Method; -use futures::{Future, IntoFuture}; +use futures::Future; pub use actix_http::Response as HttpResponse; pub use bytes::{Bytes, BytesMut}; +pub use futures::channel::oneshot::Canceled; -use crate::error::{BlockingError, Error}; +use crate::error::Error; use crate::extract::FromRequest; use crate::handler::{AsyncFactory, Factory}; use crate::resource::Resource; @@ -256,21 +257,21 @@ where /// # use futures::future::{ok, Future}; /// use actix_web::{web, App, HttpResponse, Error}; /// -/// fn index() -> impl Future { -/// ok(HttpResponse::Ok().finish()) +/// async fn index() -> Result { +/// Ok(HttpResponse::Ok().finish()) /// } /// /// App::new().service(web::resource("/").route( /// web::to_async(index)) /// ); /// ``` -pub fn to_async(handler: F) -> Route +pub fn to_async(handler: F) -> Route where - F: AsyncFactory, + F: AsyncFactory, I: FromRequest + 'static, - R: IntoFuture + 'static, - R::Item: Responder, - R::Error: Into, + R: Future> + 'static, + O: Responder + 'static, + E: Into + 'static, { Route::new().to_async(handler) } @@ -279,10 +280,11 @@ where /// /// ```rust /// # extern crate actix_web; -/// use actix_web::{dev, web, guard, App, HttpResponse}; +/// use futures::future::{ok, Ready}; +/// use actix_web::{dev, web, guard, App, Error, HttpResponse}; /// -/// fn my_service(req: dev::ServiceRequest) -> dev::ServiceResponse { -/// req.into_response(HttpResponse::Ok().finish()) +/// fn my_service(req: dev::ServiceRequest) -> Ready> { +/// ok(req.into_response(HttpResponse::Ok().finish())) /// } /// /// fn main() { @@ -299,11 +301,10 @@ pub fn service(path: &str) -> WebService { /// Execute blocking function on a thread pool, returns future that resolves /// to result of the function execution. -pub fn block(f: F) -> impl Future> +pub fn block(f: F) -> impl Future> where - F: FnOnce() -> Result + Send + 'static, - I: Send + 'static, - E: Send + std::fmt::Debug + 'static, + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, { - actix_threadpool::run(f).from_err() + actix_threadpool::run(f) } diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 3333e048..e4382029 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -59,19 +59,5 @@ tokio-timer = "0.3.0-alpha.6" open-ssl = { version="0.10", package="openssl", optional = true } [dev-dependencies] -#actix-web = "1.0.7" +actix-web = "2.0.0-alpha.1" actix-http = "0.3.0-alpha.1" - -[patch.crates-io] -actix-http = { path = "../actix-http" } -awc = { path = "../awc" } - -actix-codec = { path = "../../actix-net/actix-codec" } -actix-connect = { path = "../../actix-net/actix-connect" } -actix-rt = { path = "../../actix-net/actix-rt" } -actix-server = { path = "../../actix-net/actix-server" } -actix-server-config = { path = "../../actix-net/actix-server-config" } -actix-service = { path = "../../actix-net/actix-service" } -actix-testing = { path = "../../actix-net/actix-testing" } -actix-threadpool = { path = "../../actix-net/actix-threadpool" } -actix-utils = { path = "../../actix-net/actix-utils" } diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index bf6558b5..0c24ac90 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -3,7 +3,7 @@ use std::sync::mpsc; use std::{net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_rt::{System}; +use actix_rt::System; use actix_server::{Server, ServiceFactory}; use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; use bytes::Bytes; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index c0d2e81c..122f79ba 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -2,8 +2,8 @@ use net2::TcpBuilder; use std::sync::mpsc; use std::{net, thread, time::Duration}; -#[cfg(feature = "ssl")] -use openssl::ssl::SslAcceptorBuilder; +#[cfg(feature = "openssl")] +use open_ssl::ssl::SslAcceptorBuilder; use actix_http::Response; use actix_web::{test, web, App, HttpServer}; @@ -55,22 +55,19 @@ fn test_start() { use actix_http::client; use actix_web::test; - let client = test::run_on(|| { - Ok::<_, ()>( - awc::Client::build() - .connector( - client::Connector::new() - .timeout(Duration::from_millis(100)) - .finish(), - ) - .finish(), - ) - }) - .unwrap(); - let host = format!("http://{}", addr); + test::block_on(async { + let client = awc::Client::build() + .connector( + client::Connector::new() + .timeout(Duration::from_millis(100)) + .finish(), + ) + .finish(); - let response = test::block_on(client.get(host.clone()).send()).unwrap(); - assert!(response.status().is_success()); + let host = format!("http://{}", addr); + let response = client.get(host.clone()).send().await.unwrap(); + assert!(response.status().is_success()); + }); } // stop @@ -80,9 +77,9 @@ fn test_start() { let _ = sys.stop(); } -#[cfg(feature = "ssl")] +#[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); builder @@ -95,7 +92,7 @@ fn ssl_acceptor() -> std::io::Result { } #[test] -#[cfg(feature = "ssl")] +#[cfg(feature = "openssl")] fn test_start_ssl() { let addr = unused_addr(); let (tx, rx) = mpsc::channel(); @@ -113,7 +110,7 @@ fn test_start_ssl() { .shutdown_timeout(1) .system_exit() .disable_signals() - .bind_ssl(format!("{}", addr), builder) + .bind_openssl(format!("{}", addr), builder) .unwrap() .start(); @@ -122,30 +119,27 @@ fn test_start_ssl() { }); let (srv, sys) = rx.recv().unwrap(); - let client = test::run_on(|| { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + test::block_on(async move { + use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); let _ = builder .set_alpn_protos(b"\x02h2\x08http/1.1") .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Ok::<_, ()>( - awc::Client::build() - .connector( - awc::Connector::new() - .ssl(builder.build()) - .timeout(Duration::from_millis(100)) - .finish(), - ) - .finish(), - ) - }) - .unwrap(); - let host = format!("https://{}", addr); + let client = awc::Client::build() + .connector( + awc::Connector::new() + .ssl(builder.build()) + .timeout(Duration::from_millis(100)) + .finish(), + ) + .finish(); - let response = test::block_on(client.get(host.clone()).send()).unwrap(); - assert!(response.status().is_success()); + let host = format!("https://{}", addr); + let response = client.get(host.clone()).send().await.unwrap(); + assert!(response.status().is_success()); + }); // stop let _ = srv.stop(false); diff --git a/tests/test_server.rs b/tests/test_server.rs index 1623d2ef..eeaedec0 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,18 +1,17 @@ use std::io::{Read, Write}; -use std::sync::mpsc; -use std::thread; use actix_http::http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; use actix_http::{h1, Error, HttpService, Response}; -use actix_http_test::TestServer; +use actix_http_test::{block_on, TestServer}; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; use flate2::Compression; +use futures::future::ok; use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; @@ -44,679 +43,721 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_body() { - let mut srv = TestServer::new(|| { - h1::H1Service::new( - App::new() - .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), - ) - }); + block_on(async { + let srv = + TestServer::start(|| { + h1::H1Service::new(App::new().service( + web::resource("/").route(web::to(|| Response::Ok().body(STR))), + )) + }); - let mut response = srv.block_on(srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); + let mut response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip() { - let mut srv = TestServer::new(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), - ) - }); + block_on(async { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/").route(web::to(|| Response::Ok().body(STR))), + ), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip2() { - let mut srv = TestServer::new(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - Response::Ok().body(STR).into_body::() - }))), - ) - }); + block_on(async { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + Response::Ok().body(STR).into_body::() + }))), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_encoding_override() { - let mut srv = TestServer::new(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - Response::Ok().encoding(ContentEncoding::Deflate).body(STR) - }))) - .service(web::resource("/raw").route(web::to(|| { - let body = actix_web::dev::Body::Bytes(STR.into()); - let mut response = - Response::with_body(actix_web::http::StatusCode::OK, body); + block_on(async { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + Response::Ok().encoding(ContentEncoding::Deflate).body(STR) + }))) + .service(web::resource("/raw").route(web::to(|| { + let body = actix_web::dev::Body::Bytes(STR.into()); + let mut response = + Response::with_body(actix_web::http::StatusCode::OK, body); - response.encoding(ContentEncoding::Deflate); + response.encoding(ContentEncoding::Deflate); - response - }))), - ) - }); + response + }))), + ) + }); - // Builder - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "deflate") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + // Builder + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "deflate") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - // Raw Response - let mut response = srv - .block_on( - srv.request(actix_web::http::Method::GET, srv.url("/raw")) - .no_decompress() - .header(ACCEPT_ENCODING, "deflate") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + // Raw Response + let mut response = srv + .request(actix_web::http::Method::GET, srv.url("/raw")) + .no_decompress() + .header(ACCEPT_ENCODING, "deflate") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip_large() { - let data = STR.repeat(10); - let srv_data = data.clone(); + block_on(async { + let data = STR.repeat(10); + let srv_data = data.clone(); - let mut srv = TestServer::new(move || { - let data = srv_data.clone(); - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(data.clone()))), - ), - ) - }); + let srv = TestServer::start(move || { + let data = srv_data.clone(); + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from(data)); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from(data)); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_gzip_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(70_000) - .collect::(); - let srv_data = data.clone(); + block_on(async { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(70_000) + .collect::(); + let srv_data = data.clone(); - let mut srv = TestServer::new(move || { - let data = srv_data.clone(); - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(data.clone()))), - ), - ) - }); + let srv = TestServer::start(move || { + let data = srv_data.clone(); + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(dec.len(), data.len()); - assert_eq!(Bytes::from(dec), Bytes::from(data)); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(dec.len(), data.len()); + assert_eq!(Bytes::from(dec), Bytes::from(data)); + }) } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] #[test] fn test_body_chunked_implicit() { - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::get().to(move || { - Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( - STR.as_ref(), - )))) - }))), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::get().to(move || { + Response::Ok().streaming(once(ok::<_, Error>( + Bytes::from_static(STR.as_ref()), + ))) + }))), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response.headers().get(TRANSFER_ENCODING).unwrap(), - &b"chunked"[..] - ); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); + assert_eq!( + response.headers().get(TRANSFER_ENCODING).unwrap(), + &b"chunked"[..] + ); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(feature = "brotli")] fn test_body_br_streaming() { - let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || { - Response::Ok() - .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) - })), - )) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || { + Response::Ok().streaming(once(ok::<_, Error>( + Bytes::from_static(STR.as_ref()), + ))) + })), + ), + ) + }); - let mut response = srv - .block_on( - srv.get("/") - .header(ACCEPT_ENCODING, "br") - .no_decompress() - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "br") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode br - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode br + let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] fn test_head_binary() { - let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().service(web::resource("/").route( - web::head().to(move || Response::Ok().content_length(100).body(STR)), - ))) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new(App::new().service(web::resource("/").route( + web::head().to(move || Response::Ok().content_length(100).body(STR)), + ))) + }); - let mut response = srv.block_on(srv.head("/").send()).unwrap(); - assert!(response.status().is_success()); + let mut response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response.headers().get(CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response.headers().get(CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert!(bytes.is_empty()); + // read response + let bytes = response.body().await.unwrap(); + assert!(bytes.is_empty()); + }) } #[test] fn test_no_chunking() { - let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().service(web::resource("/").route(web::to( - move || { - Response::Ok() - .no_chunking() - .content_length(STR.len() as u64) - .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) - }, - )))) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new(App::new().service(web::resource("/").route(web::to( + move || { + Response::Ok() + .no_chunking() + .content_length(STR.len() as u64) + .streaming(once(ok::<_, Error>(Bytes::from_static( + STR.as_ref(), + )))) + }, + )))) + }); - let mut response = srv.block_on(srv.get("/").send()).unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(TRANSFER_ENCODING)); + let mut response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(TRANSFER_ENCODING)); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_body_deflate() { - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Deflate)) - .service( - web::resource("/").route(web::to(move || Response::Ok().body(STR))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Deflate)) + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(STR))), + ), + ) + }); - // client request - let mut response = srv - .block_on( - srv.get("/") - .header(ACCEPT_ENCODING, "deflate") - .no_decompress() - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "deflate") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "brotli"))] fn test_body_brotli() { - let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || Response::Ok().body(STR))), - )) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || Response::Ok().body(STR))), + ), + ) + }); - // client request - let mut response = srv - .block_on( - srv.get("/") - .header(ACCEPT_ENCODING, "br") - .no_decompress() - .send(), - ) - .unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "br") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode brotli - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode brotli + let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_encoding() { - let mut srv = TestServer::new(move || { - HttpService::new( - App::new().wrap(Compress::default()).service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + HttpService::new( + App::new().wrap(Compress::default()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_gzip_encoding() { - let mut srv = TestServer::new(move || { - HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_gzip_encoding_large() { - let data = STR.repeat(10); - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(60_000) - .collect::(); + block_on(async { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(60_000) + .collect::(); - let mut srv = TestServer::new(move || { - HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + let srv = TestServer::start(move || { + HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding() { - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding_large() { - let data = STR.repeat(10); - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); + block_on(async { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); + }) } #[test] #[cfg(feature = "brotli")] fn test_brotli_encoding() { - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "br") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "br") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + }) } #[cfg(feature = "brotli")] #[test] fn test_brotli_encoding_large() { - let data = STR.repeat(10); - let mut srv = TestServer::new(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + block_on(async { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "br") - .send_body(enc.clone()); - let mut response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "br") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.block_on(response.body()).unwrap(); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); + }) } // #[cfg(all(feature = "brotli", feature = "ssl"))] @@ -782,85 +823,87 @@ fn test_brotli_encoding_large() { ))] #[test] fn test_reading_deflate_encoding_large_random_ssl() { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, pkcs8_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; + block_on(async { + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use rustls::internal::pemfile::{certs, pkcs8_private_keys}; + use rustls::{NoClientAuth, ServerConfig}; + use std::fs::File; + use std::io::BufReader; - let addr = TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); + let addr = TestServer::unused_addr(); + let (tx, rx) = mpsc::channel(); - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); - thread::spawn(move || { - let sys = actix_rt::System::new("test"); + thread::spawn(move || { + let sys = actix_rt::System::new("test"); - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + // load ssl keys + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - let srv = HttpServer::new(|| { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - Ok::<_, Error>( - HttpResponse::Ok() - .encoding(http::ContentEncoding::Identity) - .body(bytes), + let srv = HttpServer::new(|| { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + Ok::<_, Error>( + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .body(bytes), + ) + }))) + }) + .bind_rustls(addr, config) + .unwrap() + .start(); + + let _ = tx.send((srv, actix_rt::System::current())); + let _ = sys.run(); + }); + let (srv, _sys) = rx.recv().unwrap(); + test::block_on(futures::lazy(|| Ok::<_, ()>(start_default_resolver()))).unwrap(); + let client = test::run_on(|| { + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); + + awc::Client::build() + .connector( + awc::Connector::new() + .timeout(std::time::Duration::from_millis(500)) + .ssl(builder.build()) + .finish(), ) - }))) - }) - .bind_rustls(addr, config) - .unwrap() - .start(); + .finish() + }); - let _ = tx.send((srv, actix_rt::System::current())); - let _ = sys.run(); - }); - let (srv, _sys) = rx.recv().unwrap(); - test::block_on(futures::lazy(|| Ok::<_, ()>(start_default_resolver()))).unwrap(); - let client = test::run_on(|| { - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); + // encode data + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - awc::Client::build() - .connector( - awc::Connector::new() - .timeout(std::time::Duration::from_millis(500)) - .ssl(builder.build()) - .finish(), - ) - .finish() - }); + // client request + let req = client + .post(format!("https://localhost:{}/", addr.port())) + .header(http::header::CONTENT_ENCODING, "deflate") + .send_body(enc); - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut response = test::block_on(req).unwrap(); + assert!(response.status().is_success()); - // client request - let req = client - .post(format!("https://localhost:{}/", addr.port())) - .header(http::header::CONTENT_ENCODING, "deflate") - .send_body(enc); + // read response + let bytes = test::block_on(response.body()).unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); - let mut response = test::block_on(req).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = test::block_on(response.body()).unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); - - // stop - let _ = srv.stop(false); + // stop + let _ = srv.stop(false); + }) } // #[cfg(all(feature = "tls", feature = "ssl"))] @@ -954,7 +997,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { // fn test_server_cookies() { // use actix_web::http; -// let mut srv = test::TestServer::with_factory(|| { +// let srv = test::TestServer::with_factory(|| { // App::new().resource("/", |r| { // r.f(|_| { // HttpResponse::Ok() From b510527a9fd64926aa7577dd17e050dc3976ab9e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 00:35:07 +0600 Subject: [PATCH 1610/1635] update awc tests --- awc/Cargo.toml | 7 +- awc/src/lib.rs | 12 +- awc/src/request.rs | 27 +++-- awc/src/response.rs | 141 +++++++++++----------- awc/src/ws.rs | 56 ++++----- awc/tests/test_client.rs | 202 ++++++++++++++++---------------- awc/tests/test_rustls_client.rs | 145 ++++++++++++----------- awc/tests/test_ssl_client.rs | 96 +++++++-------- awc/tests/test_ws.rs | 101 ++++++++-------- 9 files changed, 404 insertions(+), 383 deletions(-) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 70d89d5d..e085ea09 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -14,7 +14,7 @@ categories = ["network-programming", "asynchronous", license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" -# workspace = ".." +workspace = ".." [lib] name = "awc" @@ -59,7 +59,7 @@ serde_json = "1.0" serde_urlencoded = "0.6.1" tokio-timer = "0.3.0-alpha.6" open-ssl = { version="0.10", package="openssl", optional = true } -rust-tls = { version = "0.16.0", package="rustls", optional = true } +rust-tls = { version = "0.16.0", package="rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] actix-rt = "1.0.0-alpha.1" @@ -67,11 +67,10 @@ actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } actix-utils = "0.5.0-alpha.1" -actix-server = { version = "0.8.0-alpha.1", features=["openssl", "rustls"] } +actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" rand = "0.7" tokio-tcp = "0.1" webpki = { version = "0.21" } -rus-tls = { version = "0.16.0", package="rustls", features = ["dangerous_configuration"] } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 58c9056b..7bbe4219 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -7,18 +7,18 @@ //! use awc::Client; //! //! fn main() { -//! System::new("test").block_on(lazy(|| { +//! System::new("test").block_on(async { //! let mut client = Client::default(); //! //! client.get("http://www.rust-lang.org") // <- Create request builder //! .header("User-Agent", "Actix-web") //! .send() // <- Send http request -//! .map_err(|_| ()) +//! .await //! .and_then(|response| { // <- server http response //! println!("Response: {:?}", response); //! Ok(()) //! }) -//! })); +//! }); //! } //! ``` use std::cell::RefCell; @@ -57,18 +57,18 @@ use self::connect::{Connect, ConnectorWrapper}; /// use awc::Client; /// /// fn main() { -/// System::new("test").block_on(lazy(|| { +/// System::new("test").block_on(async { /// let mut client = Client::default(); /// /// client.get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .send() // <- Send http request -/// .map_err(|_| ()) +/// .await /// .and_then(|response| { // <- server http response /// println!("Response: {:?}", response); /// Ok(()) /// }) -/// })); +/// }); /// } /// ``` #[derive(Clone)] diff --git a/awc/src/request.rs b/awc/src/request.rs index 83123443..5181f190 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -41,17 +41,18 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; /// use actix_rt::System; /// /// fn main() { -/// System::new("test").block_on(lazy(|| { -/// awc::Client::new() +/// System::new("test").block_on(async { +/// let response = awc::Client::new() /// .get("http://www.rust-lang.org") // <- Create request builder /// .header("User-Agent", "Actix-web") /// .send() // <- Send http request -/// .map_err(|_| ()) -/// .and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// Ok(()) +/// .await; +/// +/// response.and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// Ok(()) /// }) -/// })); +/// }); /// } /// ``` pub struct ClientRequest { @@ -158,7 +159,7 @@ impl ClientRequest { /// /// ```rust /// fn main() { - /// # actix_rt::System::new("test").block_on(futures::future::lazy(|| { + /// # actix_rt::System::new("test").block_on(futures::future::lazy(|_| { /// let req = awc::Client::new() /// .get("http://www.rust-lang.org") /// .set(awc::http::header::Date::now()) @@ -186,13 +187,13 @@ impl ClientRequest { /// use awc::{http, Client}; /// /// fn main() { - /// # actix_rt::System::new("test").block_on(futures::future::lazy(|| { + /// # actix_rt::System::new("test").block_on(async { /// let req = Client::new() /// .get("http://www.rust-lang.org") /// .header("X-TEST", "value") /// .header(http::header::CONTENT_TYPE, "application/json"); /// # Ok::<_, ()>(()) - /// # })); + /// # }); /// } /// ``` pub fn header(mut self, key: K, value: V) -> Self @@ -311,7 +312,7 @@ impl ClientRequest { /// # use actix_rt::System; /// # use futures::future::{lazy, Future}; /// fn main() { - /// System::new("test").block_on(lazy(|| { + /// System::new("test").block_on(async { /// awc::Client::new().get("https://www.rust-lang.org") /// .cookie( /// awc::http::Cookie::build("name", "value") @@ -322,12 +323,12 @@ impl ClientRequest { /// .finish(), /// ) /// .send() - /// .map_err(|_| ()) + /// .await /// .and_then(|response| { /// println!("Response: {:?}", response); /// Ok(()) /// }) - /// })); + /// }); /// } /// ``` pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { diff --git a/awc/src/response.rs b/awc/src/response.rs index a7b08eed..5ef8e18b 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -359,41 +359,40 @@ where mod tests { use super::*; use actix_http_test::block_on; - use futures::Async; use serde::{Deserialize, Serialize}; use crate::{http::header, test::TestResponse}; #[test] fn test_body() { - let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); - match req.body().poll().err().unwrap() { - PayloadError::UnknownLength => (), - _ => unreachable!("error"), - } + block_on(async { + let mut req = + TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + match req.body().await.err().unwrap() { + PayloadError::UnknownLength => (), + _ => unreachable!("error"), + } - let mut req = - TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); - match req.body().poll().err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } + let mut req = + TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + match req.body().await.err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } - let mut req = TestResponse::default() - .set_payload(Bytes::from_static(b"test")) - .finish(); - match req.body().poll().ok().unwrap() { - Async::Ready(bytes) => assert_eq!(bytes, Bytes::from_static(b"test")), - _ => unreachable!("error"), - } + let mut req = TestResponse::default() + .set_payload(Bytes::from_static(b"test")) + .finish(); + assert_eq!(req.body().await.ok().unwrap(), Bytes::from_static(b"test")); - let mut req = TestResponse::default() - .set_payload(Bytes::from_static(b"11111111111111")) - .finish(); - match req.body().limit(5).poll().err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } + let mut req = TestResponse::default() + .set_payload(Bytes::from_static(b"11111111111111")) + .finish(); + match req.body().limit(5).await.err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } + }) } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -417,54 +416,56 @@ mod tests { #[test] fn test_json_body() { - let mut req = TestResponse::default().finish(); - let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + block_on(async { + let mut req = TestResponse::default().finish(); + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ) - .finish(); - let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + ) + .finish(); + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ) - .finish(); + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ) + .finish(); - let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100)); - assert!(json_eq( - json.err().unwrap(), - JsonPayloadError::Payload(PayloadError::Overflow) - )); + let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; + assert!(json_eq( + json.err().unwrap(), + JsonPayloadError::Payload(PayloadError::Overflow) + )); - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); - let json = block_on(JsonBody::<_, MyObject>::new(&mut req)); - assert_eq!( - json.ok().unwrap(), - MyObject { - name: "test".to_owned() - } - ); + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert_eq!( + json.ok().unwrap(), + MyObject { + name: "test".to_owned() + } + ); + }) } } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 979a382a..8819b499 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -389,6 +389,8 @@ impl fmt::Debug for WebsocketsRequest { #[cfg(test)] mod tests { + use actix_web::test::block_on; + use super::*; use crate::Client; @@ -463,35 +465,33 @@ mod tests { #[test] fn basics() { - let req = Client::new() - .ws("http://localhost/") - .origin("test-origin") - .max_frame_size(100) - .server_mode() - .protocols(&["v1", "v2"]) - .set_header_if_none(header::CONTENT_TYPE, "json") - .set_header_if_none(header::CONTENT_TYPE, "text") - .cookie(Cookie::build("cookie1", "value1").finish()); - assert_eq!( - req.origin.as_ref().unwrap().to_str().unwrap(), - "test-origin" - ); - assert_eq!(req.max_size, 100); - assert_eq!(req.server_mode, true); - assert_eq!(req.protocols, Some("v1,v2".to_string())); - assert_eq!( - req.head.headers.get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("json") - ); + block_on(async { + let req = Client::new() + .ws("http://localhost/") + .origin("test-origin") + .max_frame_size(100) + .server_mode() + .protocols(&["v1", "v2"]) + .set_header_if_none(header::CONTENT_TYPE, "json") + .set_header_if_none(header::CONTENT_TYPE, "text") + .cookie(Cookie::build("cookie1", "value1").finish()); + assert_eq!( + req.origin.as_ref().unwrap().to_str().unwrap(), + "test-origin" + ); + assert_eq!(req.max_size, 100); + assert_eq!(req.server_mode, true); + assert_eq!(req.protocols, Some("v1,v2".to_string())); + assert_eq!( + req.head.headers.get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("json") + ); - let _ = actix_http_test::block_fn(move || req.connect()); + let _ = req.connect().await; - assert!(Client::new().ws("/").connect().poll().is_err()); - assert!(Client::new().ws("http:///test").connect().poll().is_err()); - assert!(Client::new() - .ws("hmm://test.com/") - .connect() - .poll() - .is_err()); + assert!(Client::new().ws("/").connect().await.is_err()); + assert!(Client::new().ws("http:///test").connect().await.is_err()); + assert!(Client::new().ws("hmm://test.com/").connect().await.is_err()); + }) } } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 8d7bc227..bcedaf64 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -9,12 +9,12 @@ use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures::Future; +use futures::future::ok; use rand::Rng; use actix_http::HttpService; -use actix_http_test::{bloxk_on, TestServer}; -use actix_service::{service_fn, ServiceFactory}; +use actix_http_test::{block_on, TestServer}; +use actix_service::pipeline_factory; use actix_web::http::Cookie; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; @@ -45,29 +45,29 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[test] fn test_simple() { block_on(async { - let mut srv = TestServer::start(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service( web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), )) }); let request = srv.get("/").header("x-test", "111").send(); - let mut response = srv.block_on(request).unwrap(); + let mut response = request.await.unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let mut response = srv.block_on(srv.post("/").send()).unwrap(); + let mut response = srv.post("/").send().await.unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // camel case - let response = srv.block_on(srv.post("/").camel_case().send()).unwrap(); + let response = srv.post("/").camel_case().send().await.unwrap(); assert!(response.status().is_success()); }) } @@ -75,7 +75,7 @@ fn test_simple() { #[test] fn test_json() { block_on(async { - let mut srv = TestServer::start(|| { + let srv = TestServer::start(|| { HttpService::new( App::new().service( web::resource("/") @@ -88,7 +88,7 @@ fn test_json() { .get("/") .header("x-test", "111") .send_json(&"TEST".to_string()); - let response = srv.block_on(request).unwrap(); + let response = request.await.unwrap(); assert!(response.status().is_success()); }) } @@ -96,7 +96,7 @@ fn test_json() { #[test] fn test_form() { block_on(async { - let mut srv = TestServer::start(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service(web::resource("/").route(web::to( |_: web::Form>| HttpResponse::Ok(), )))) @@ -106,7 +106,7 @@ fn test_form() { let _ = data.insert("key".to_string(), "TEST".to_string()); let request = srv.get("/").header("x-test", "111").send_form(&data); - let response = srv.block_on(request).unwrap(); + let response = request.await.unwrap(); assert!(response.status().is_success()); }) } @@ -114,22 +114,22 @@ fn test_form() { #[test] fn test_timeout() { block_on(async { - let mut srv = TestServer::start(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service(web::resource("/").route( web::to_async(|| { - tokio_timer::sleep(Duration::from_millis(200)) - .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) + async { + tokio_timer::delay_for(Duration::from_millis(200)).await; + Ok::<_, Error>(HttpResponse::Ok().body(STR)) + } }), ))) }); - let client = srv.execute(|| { - awc::Client::build() - .timeout(Duration::from_millis(50)) - .finish() - }); + let client = awc::Client::build() + .timeout(Duration::from_millis(50)) + .finish(); let request = client.get(srv.url("/")).send(); - match srv.block_on(request) { + match request.await { Err(SendRequestError::Timeout) => (), _ => panic!(), } @@ -139,11 +139,13 @@ fn test_timeout() { #[test] fn test_timeout_override() { block_on(async { - let mut srv = TestServer::start(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service(web::resource("/").route( web::to_async(|| { - tokio_timer::sleep(Duration::from_millis(200)) - .then(|_| Ok::<_, Error>(HttpResponse::Ok().body(STR))) + async { + tokio_timer::delay_for(Duration::from_millis(200)).await; + Ok::<_, Error>(HttpResponse::Ok().body(STR)) + } }), ))) }); @@ -155,7 +157,7 @@ fn test_timeout_override() { .get(srv.url("/")) .timeout(Duration::from_millis(50)) .send(); - match srv.block_on(request) { + match request.await { Err(SendRequestError::Timeout) => (), _ => panic!(), } @@ -168,11 +170,11 @@ fn test_connection_reuse() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); - let mut srv = TestServer::start(move || { + let srv = TestServer::start(move || { let num2 = num2.clone(); - service_fn(move |io| { + pipeline_factory(move |io| { num2.fetch_add(1, Ordering::Relaxed); - Ok(io) + ok(io) }) .and_then(HttpService::new(App::new().service( web::resource("/").route(web::to(|| HttpResponse::Ok())), @@ -183,12 +185,12 @@ fn test_connection_reuse() { // req 1 let request = client.get(srv.url("/")).send(); - let response = srv.block_on(request).unwrap(); + let response = request.await.unwrap(); assert!(response.status().is_success()); // req 2 let req = client.post(srv.url("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); + let response = req.send().await.unwrap(); assert!(response.status().is_success()); // one connection @@ -202,11 +204,11 @@ fn test_connection_force_close() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); - let mut srv = TestServer::new(move || { + let srv = TestServer::start(move || { let num2 = num2.clone(); - service_fn(move |io| { + pipeline_factory(move |io| { num2.fetch_add(1, Ordering::Relaxed); - Ok(io) + ok(io) }) .and_then(HttpService::new(App::new().service( web::resource("/").route(web::to(|| HttpResponse::Ok())), @@ -217,12 +219,12 @@ fn test_connection_force_close() { // req 1 let request = client.get(srv.url("/")).force_close().send(); - let response = srv.block_on(request).unwrap(); + let response = request.await.unwrap(); assert!(response.status().is_success()); // req 2 let req = client.post(srv.url("/")).force_close(); - let response = srv.block_on_fn(move || req.send()).unwrap(); + let response = req.send().await.unwrap(); assert!(response.status().is_success()); // two connection @@ -236,11 +238,11 @@ fn test_connection_server_close() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); - let mut srv = TestServer::new(move || { + let srv = TestServer::start(move || { let num2 = num2.clone(); - service_fn(move |io| { + pipeline_factory(move |io| { num2.fetch_add(1, Ordering::Relaxed); - Ok(io) + ok(io) }) .and_then(HttpService::new( App::new().service( @@ -254,12 +256,12 @@ fn test_connection_server_close() { // req 1 let request = client.get(srv.url("/")).send(); - let response = srv.block_on(request).unwrap(); + let response = request.await.unwrap(); assert!(response.status().is_success()); // req 2 let req = client.post(srv.url("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); + let response = req.send().await.unwrap(); assert!(response.status().is_success()); // two connection @@ -273,11 +275,11 @@ fn test_connection_wait_queue() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); - let mut srv = TestServer::new(move || { + let srv = TestServer::start(move || { let num2 = num2.clone(); - service_fn(move |io| { + pipeline_factory(move |io| { num2.fetch_add(1, Ordering::Relaxed); - Ok(io) + ok(io) }) .and_then(HttpService::new(App::new().service( web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), @@ -290,23 +292,19 @@ fn test_connection_wait_queue() { // req 1 let request = client.get(srv.url("/")).send(); - let mut response = srv.block_on(request).unwrap(); + let mut response = request.await.unwrap(); assert!(response.status().is_success()); // req 2 let req2 = client.post(srv.url("/")); - let req2_fut = srv.execute(move || { - let mut fut = req2.send(); - assert!(fut.poll().unwrap().is_not_ready()); - fut - }); + let req2_fut = req2.send(); // read response 1 - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // req 2 - let response = srv.block_on(req2_fut).unwrap(); + let response = req2_fut.await.unwrap(); assert!(response.status().is_success()); // two connection @@ -320,11 +318,11 @@ fn test_connection_wait_queue_force_close() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); - let mut srv = TestServer::new(move || { + let srv = TestServer::start(move || { let num2 = num2.clone(); - service_fn(move |io| { + pipeline_factory(move |io| { num2.fetch_add(1, Ordering::Relaxed); - Ok(io) + ok(io) }) .and_then(HttpService::new( App::new().service( @@ -340,23 +338,19 @@ fn test_connection_wait_queue_force_close() { // req 1 let request = client.get(srv.url("/")).send(); - let mut response = srv.block_on(request).unwrap(); + let mut response = request.await.unwrap(); assert!(response.status().is_success()); // req 2 let req2 = client.post(srv.url("/")); - let req2_fut = srv.execute(move || { - let mut fut = req2.send(); - assert!(fut.poll().unwrap().is_not_ready()); - fut - }); + let req2_fut = req2.send(); // read response 1 - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // req 2 - let response = srv.block_on(req2_fut).unwrap(); + let response = req2_fut.await.unwrap(); assert!(response.status().is_success()); // two connection @@ -367,7 +361,7 @@ fn test_connection_wait_queue_force_close() { #[test] fn test_with_query_parameter() { block_on(async { - let mut srv = TestServer::new(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service(web::resource("/").to( |req: HttpRequest| { if req.query_string().contains("qp") { @@ -379,8 +373,10 @@ fn test_with_query_parameter() { ))) }); - let res = srv - .block_on(awc::Client::new().get(srv.url("/?qp=5")).send()) + let res = awc::Client::new() + .get(srv.url("/?qp=5")) + .send() + .await .unwrap(); assert!(res.status().is_success()); }) @@ -389,7 +385,7 @@ fn test_with_query_parameter() { #[test] fn test_no_decompress() { block_on(async { - let mut srv = TestServer::new(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().wrap(Compress::default()).service( web::resource("/").route(web::to(|| { let mut res = HttpResponse::Ok().body(STR); @@ -399,13 +395,16 @@ fn test_no_decompress() { )) }); - let mut res = srv - .block_on(awc::Client::new().get(srv.url("/")).no_decompress().send()) + let mut res = awc::Client::new() + .get(srv.url("/")) + .no_decompress() + .send() + .await .unwrap(); assert!(res.status().is_success()); // read response - let bytes = srv.block_on(res.body()).unwrap(); + let bytes = res.body().await.unwrap(); let mut e = GzDecoder::new(&bytes[..]); let mut dec = Vec::new(); @@ -413,12 +412,15 @@ fn test_no_decompress() { assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); // POST - let mut res = srv - .block_on(awc::Client::new().post(srv.url("/")).no_decompress().send()) + let mut res = awc::Client::new() + .post(srv.url("/")) + .no_decompress() + .send() + .await .unwrap(); assert!(res.status().is_success()); - let bytes = srv.block_on(res.body()).unwrap(); + let bytes = res.body().await.unwrap(); let mut e = GzDecoder::new(&bytes[..]); let mut dec = Vec::new(); e.read_to_end(&mut dec).unwrap(); @@ -429,7 +431,7 @@ fn test_no_decompress() { #[test] fn test_client_gzip_encoding() { block_on(async { - let mut srv = TestServer::new(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service(web::resource("/").route(web::to( || { let mut e = GzEncoder::new(Vec::new(), Compression::default()); @@ -444,11 +446,11 @@ fn test_client_gzip_encoding() { }); // client request - let mut response = srv.block_on(srv.post("/").send()).unwrap(); + let mut response = srv.post("/").send().await.unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); }) } @@ -456,7 +458,7 @@ fn test_client_gzip_encoding() { #[test] fn test_client_gzip_encoding_large() { block_on(async { - let mut srv = TestServer::new(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service(web::resource("/").route(web::to( || { let mut e = GzEncoder::new(Vec::new(), Compression::default()); @@ -471,11 +473,11 @@ fn test_client_gzip_encoding_large() { }); // client request - let mut response = srv.block_on(srv.post("/").send()).unwrap(); + let mut response = srv.post("/").send().await.unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from(STR.repeat(10))); }) } @@ -488,7 +490,7 @@ fn test_client_gzip_encoding_large_random() { .take(100_000) .collect::(); - let mut srv = TestServer::new(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service(web::resource("/").route(web::to( |data: Bytes| { let mut e = GzEncoder::new(Vec::new(), Compression::default()); @@ -502,11 +504,11 @@ fn test_client_gzip_encoding_large_random() { }); // client request - let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap(); + let mut response = srv.post("/").send_body(data.clone()).await.unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from(data)); }) } @@ -514,7 +516,7 @@ fn test_client_gzip_encoding_large_random() { #[test] fn test_client_brotli_encoding() { block_on(async { - let mut srv = TestServer::new(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().service(web::resource("/").route(web::to( |data: Bytes| { let mut e = BrotliEncoder::new(Vec::new(), 5); @@ -528,11 +530,11 @@ fn test_client_brotli_encoding() { }); // client request - let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap(); + let mut response = srv.post("/").send_body(STR).await.unwrap(); assert!(response.status().is_success()); // read response - let bytes = srv.block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes, Bytes::from_static(STR.as_ref())); }) } @@ -544,7 +546,7 @@ fn test_client_brotli_encoding() { // .take(70_000) // .collect::(); -// let mut srv = test::TestServer::new(|app| { +// let srv = test::TestServer::start(|app| { // app.handler(|req: &HttpRequest| { // req.body() // .and_then(move |bytes: Bytes| { @@ -562,11 +564,11 @@ fn test_client_brotli_encoding() { // .content_encoding(http::ContentEncoding::Br) // .body(data.clone()) // .unwrap(); -// let response = srv.execute(request.send()).unwrap(); +// let response = request.send().await.unwrap(); // assert!(response.status().is_success()); // // read response -// let bytes = srv.execute(response.body()).unwrap(); +// let bytes = response.body().await.unwrap(); // assert_eq!(bytes.len(), data.len()); // assert_eq!(bytes, Bytes::from(data)); // } @@ -574,7 +576,7 @@ fn test_client_brotli_encoding() { // #[cfg(feature = "brotli")] // #[test] // fn test_client_deflate_encoding() { -// let mut srv = test::TestServer::new(|app| { +// let srv = test::TestServer::start(|app| { // app.handler(|req: &HttpRequest| { // req.body() // .and_then(|bytes: Bytes| { @@ -607,7 +609,7 @@ fn test_client_brotli_encoding() { // .take(70_000) // .collect::(); -// let mut srv = test::TestServer::new(|app| { +// let srv = test::TestServer::start(|app| { // app.handler(|req: &HttpRequest| { // req.body() // .and_then(|bytes: Bytes| { @@ -635,7 +637,7 @@ fn test_client_brotli_encoding() { // #[test] // fn test_client_streaming_explicit() { -// let mut srv = test::TestServer::new(|app| { +// let srv = test::TestServer::start(|app| { // app.handler(|req: &HttpRequest| { // req.body() // .map_err(Error::from) @@ -662,7 +664,7 @@ fn test_client_brotli_encoding() { // #[test] // fn test_body_streaming_implicit() { -// let mut srv = test::TestServer::new(|app| { +// let srv = test::TestServer::start(|app| { // app.handler(|_| { // let body = once(Ok(Bytes::from_static(STR.as_ref()))); // HttpResponse::Ok() @@ -699,7 +701,7 @@ fn test_client_cookie_handling() { let cookie1b = cookie1.clone(); let cookie2b = cookie2.clone(); - let mut srv = TestServer::new(move || { + let srv = TestServer::start(move || { let cookie1 = cookie1b.clone(); let cookie2 = cookie2b.clone(); @@ -736,7 +738,7 @@ fn test_client_cookie_handling() { }); let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); - let response = srv.block_on(request.send()).unwrap(); + let response = request.send().await.unwrap(); assert!(response.status().is_success()); let c1 = response.cookie("cookie1").expect("Missing cookie1"); assert_eq!(c1, cookie1); @@ -767,18 +769,18 @@ fn test_client_cookie_handling() { // let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) // .finish() // .unwrap(); -// let response = sys.block_on(req.send()).unwrap(); +// let response = req.send().await.unwrap(); // assert!(response.status().is_success()); // // read response -// let bytes = sys.block_on(response.body()).unwrap(); +// let bytes = response.body().await.unwrap(); // assert_eq!(bytes, Bytes::from_static(b"welcome!")); // } #[test] fn client_basic_auth() { block_on(async { - let mut srv = TestServer::new(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().route( "/", web::to(|req: HttpRequest| { @@ -800,7 +802,7 @@ fn client_basic_auth() { // set authorization header to Basic let request = srv.get("/").basic_auth("username", Some("password")); - let response = srv.block_on(request.send()).unwrap(); + let response = request.send().await.unwrap(); assert!(response.status().is_success()); }) } @@ -808,7 +810,7 @@ fn client_basic_auth() { #[test] fn client_bearer_auth() { block_on(async { - let mut srv = TestServer::new(|| { + let srv = TestServer::start(|| { HttpService::new(App::new().route( "/", web::to(|req: HttpRequest| { @@ -830,7 +832,7 @@ fn client_bearer_auth() { // set authorization header to Bearer let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); - let response = srv.block_on(request.send()).unwrap(); + let response = request.send().await.unwrap(); assert!(response.status().is_success()); }) } diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index e65e4e87..bdfd2103 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -1,96 +1,109 @@ -#![cfg(feature = "rust-tls")] -use rustls::{ - internal::pemfile::{certs, pkcs8_private_keys}, - ClientConfig, NoClientAuth, -}; +#![cfg(feature = "rustls")] +use rust_tls::ClientConfig; -use std::fs::File; -use std::io::{BufReader, Result}; +use std::io::Result; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; -use actix_http_test::TestServer; -use actix_server::ssl::RustlsAcceptor; -use actix_service::{service_fn, NewService}; +use actix_http_test::{block_on, TestServer}; +use actix_server::ssl::OpensslAcceptor; +use actix_service::{pipeline_factory, ServiceFactory}; use actix_web::http::Version; use actix_web::{web, App, HttpResponse}; +use futures::future::ok; +use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslVerifyMode}; -fn ssl_acceptor() -> Result> { - use rustls::ServerConfig; +fn ssl_acceptor() -> Result> { // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - let protos = vec![b"h2".to_vec()]; - config.set_protocols(&protos); - Ok(RustlsAcceptor::new(config)) + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder.set_verify_callback(SslVerifyMode::NONE, |_, _| true); + builder + .set_private_key_file("../tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("../tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(open_ssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) } mod danger { pub struct NoCertificateVerification {} - impl rustls::ServerCertVerifier for NoCertificateVerification { + impl rust_tls::ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _roots: &rustls::RootCertStore, - _presented_certs: &[rustls::Certificate], + _roots: &rust_tls::RootCertStore, + _presented_certs: &[rust_tls::Certificate], _dns_name: webpki::DNSNameRef<'_>, _ocsp: &[u8], - ) -> Result { - Ok(rustls::ServerCertVerified::assertion()) + ) -> Result { + Ok(rust_tls::ServerCertVerified::assertion()) } } } -#[test] -fn test_connection_reuse_h2() { - let rustls = ssl_acceptor().unwrap(); - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); +// #[test] +fn _test_connection_reuse_h2() { + block_on(async { + let openssl = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) - .map_err(|_| ()), - ) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok())), + )) + .map_err(|_| ()), + ) + }); - // disable ssl verification - let mut config = ClientConfig::new(); - let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - config.set_protocols(&protos); - config - .dangerous() - .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + // disable ssl verification + let mut config = ClientConfig::new(); + let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + config.set_protocols(&protos); + config + .dangerous() + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); - let client = awc::Client::build() - .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) + .finish(); - // req 1 - let request = client.get(srv.surl("/")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.surl("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); + // req 2 + let req = client.post(srv.surl("/")); + let response = req.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); + }) } diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index e6b0101b..d37dba29 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -1,5 +1,5 @@ -#![cfg(feature = "ssl")] -use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +#![cfg(feature = "openssl")] +use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; use std::io::Result; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -7,11 +7,12 @@ use std::sync::Arc; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; -use actix_http_test::TestServer; +use actix_http_test::{block_on, TestServer}; use actix_server::ssl::OpensslAcceptor; -use actix_service::{service_fn, NewService}; +use actix_service::{pipeline_factory, ServiceFactory}; use actix_web::http::Version; use actix_web::{web, App, HttpResponse}; +use futures::future::ok; fn ssl_acceptor() -> Result> { // load ssl keys @@ -27,7 +28,7 @@ fn ssl_acceptor() -> Result> { if protos.windows(3).any(|window| window == H2) { Ok(b"h2") } else { - Err(openssl::ssl::AlpnError::NOACK) + Err(open_ssl::ssl::AlpnError::NOACK) } }); builder.set_alpn_protos(b"\x02h2")?; @@ -36,51 +37,54 @@ fn ssl_acceptor() -> Result> { #[test] fn test_connection_reuse_h2() { - let openssl = ssl_acceptor().unwrap(); - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); + block_on(async { + let openssl = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) - .map_err(|_| ()), - ) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok())), + )) + .map_err(|_| ()), + ) + }); - // disable ssl verification - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + // disable ssl verification + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - let client = awc::Client::build() - .connector(awc::Connector::new().ssl(builder.build()).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().ssl(builder.build()).finish()) + .finish(); - // req 1 - let request = client.get(srv.surl("/")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.surl("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); + // req 2 + let req = client.post(srv.surl("/")); + let response = req.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); + }) } diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 5abf9635..633e8db5 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -2,81 +2,82 @@ use std::io; use actix_codec::Framed; use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; -use actix_http_test::TestServer; +use actix_http_test::{block_on, TestServer}; use bytes::{Bytes, BytesMut}; use futures::future::ok; -use futures::{Future, Sink, Stream}; +use futures::{SinkExt, StreamExt}; -fn ws_service(req: ws::Frame) -> impl Future { +async fn ws_service(req: ws::Frame) -> Result { match req { - ws::Frame::Ping(msg) => ok(ws::Message::Pong(msg)), + ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)), ws::Frame::Text(text) => { let text = if let Some(pl) = text { String::from_utf8(Vec::from(pl.as_ref())).unwrap() } else { String::new() }; - ok(ws::Message::Text(text)) + Ok(ws::Message::Text(text)) } - ws::Frame::Binary(bin) => ok(ws::Message::Binary( + ws::Frame::Binary(bin) => Ok(ws::Message::Binary( bin.map(|e| e.freeze()) .unwrap_or_else(|| Bytes::from("")) .into(), )), - ws::Frame::Close(reason) => ok(ws::Message::Close(reason)), - _ => ok(ws::Message::Close(None)), + ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)), + _ => Ok(ws::Message::Close(None)), } } #[test] fn test_simple() { - let mut srv = TestServer::new(|| { - HttpService::build() - .upgrade(|(req, framed): (Request, Framed<_, _>)| { - let res = ws::handshake_response(req.head()).finish(); - // send handshake response - framed - .send(h1::Message::Item((res.drop_body(), BodySize::None))) - .map_err(|e: io::Error| e.into()) - .and_then(|framed| { + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build() + .upgrade(|(req, mut framed): (Request, Framed<_, _>)| { + async move { + let res = ws::handshake_response(req.head()).finish(); + // send handshake response + framed + .send(h1::Message::Item((res.drop_body(), BodySize::None))) + .await?; + // start websocket service let framed = framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_service) - }) - }) - .finish(|_| ok::<_, Error>(Response::NotFound())) - }); + ws::Transport::with(framed, ws_service).await + } + }) + .finish(|_| ok::<_, Error>(Response::NotFound())) + }); - // client service - let framed = srv.ws().unwrap(); - let framed = srv - .block_on(framed.send(ws::Message::Text("text".to_string()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + // client service + let mut framed = srv.ws().await.unwrap(); + framed + .send(ws::Message::Text("text".to_string())) + .await + .unwrap(); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, ws::Frame::Text(Some(BytesMut::from("text")))); - let framed = srv - .block_on(framed.send(ws::Message::Binary("text".into()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!( - item, - Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) - ); + framed + .send(ws::Message::Binary("text".into())) + .await + .unwrap(); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!( + item, + ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) + ); - let framed = srv - .block_on(framed.send(ws::Message::Ping("text".into()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + framed.send(ws::Message::Ping("text".into())).await.unwrap(); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, ws::Frame::Pong("text".to_string().into())); - let framed = srv - .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) - .unwrap(); + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); - let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!( - item, - Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) - ); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); + }) } From ff62facc0d82e1410d1abc78d4191386308b13d8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 00:52:38 +0600 Subject: [PATCH 1611/1635] disable unmigrated crates --- Cargo.toml | 48 +++++++++++++++++++++++--------------- actix-cors/Cargo.toml | 4 ++-- actix-files/Cargo.toml | 14 +++++------ actix-framed/Cargo.toml | 18 +++++++------- actix-identity/Cargo.toml | 8 +++---- actix-multipart/Cargo.toml | 10 ++++---- actix-session/Cargo.toml | 8 +++---- test-server/Cargo.toml | 2 +- test-server/src/lib.rs | 28 +++++++++++----------- 9 files changed, 76 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1aa7952..db983bf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,13 +31,13 @@ members = [ ".", "awc", "actix-http", - "actix-cors", - "actix-files", - "actix-framed", - "actix-session", - "actix-identity", - "actix-multipart", - "actix-web-actors", + #"actix-cors", + #"actix-files", + #"actix-framed", + #"actix-session", + #"actix-identity", + #"actix-multipart", + #"actix-web-actors", "actix-web-codegen", "test-server", ] @@ -125,17 +125,27 @@ actix-http = { path = "actix-http" } actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } # actix-web-actors = { path = "actix-web-actors" } -actix-session = { path = "actix-session" } -actix-files = { path = "actix-files" } -actix-multipart = { path = "actix-multipart" } +# actix-session = { path = "actix-session" } +# actix-files = { path = "actix-files" } +# actix-multipart = { path = "actix-multipart" } awc = { path = "awc" } -actix-codec = { path = "../actix-net/actix-codec" } -actix-connect = { path = "../actix-net/actix-connect" } -actix-rt = { path = "../actix-net/actix-rt" } -actix-server = { path = "../actix-net/actix-server" } -actix-server-config = { path = "../actix-net/actix-server-config" } -actix-service = { path = "../actix-net/actix-service" } -actix-testing = { path = "../actix-net/actix-testing" } -actix-threadpool = { path = "../actix-net/actix-threadpool" } -actix-utils = { path = "../actix-net/actix-utils" } +actix-codec = { git = "https://github.com/actix/actix-net.git" } +actix-connect = { git = "https://github.com/actix/actix-net.git" } +actix-rt = { git = "https://github.com/actix/actix-net.git" } +actix-server = { git = "https://github.com/actix/actix-net.git" } +actix-server-config = { git = "https://github.com/actix/actix-net.git" } +actix-service = { git = "https://github.com/actix/actix-net.git" } +actix-testing = { git = "https://github.com/actix/actix-net.git" } +actix-threadpool = { git = "https://github.com/actix/actix-net.git" } +actix-utils = { git = "https://github.com/actix/actix-net.git" } + +# actix-codec = { path = "../actix-net/actix-codec" } +# actix-connect = { path = "../actix-net/actix-connect" } +# actix-rt = { path = "../actix-net/actix-rt" } +# actix-server = { path = "../actix-net/actix-server" } +# actix-server-config = { path = "../actix-net/actix-server-config" } +# actix-service = { path = "../actix-net/actix-service" } +# actix-testing = { path = "../actix-net/actix-testing" } +# actix-threadpool = { path = "../actix-net/actix-threadpool" } +# actix-utils = { path = "../actix-net/actix-utils" } diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml index 57aa5833..56b6fabd 100644 --- a/actix-cors/Cargo.toml +++ b/actix-cors/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_cors" path = "src/lib.rs" [dependencies] -actix-web = "2.0.0-alpha.1" -actix-service = "1.0.0-alpha.1" +actix-web = "1.0.9" +actix-service = "0.4.0" derive_more = "0.15.0" futures = "0.3.1" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6e33bb41..2f75fb50 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.2.0-alpha.1" +version = "0.1.7" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -11,19 +11,19 @@ documentation = "https://docs.rs/actix-files/" categories = ["asynchronous", "web-programming::http-server"] license = "MIT/Apache-2.0" edition = "2018" -workspace = ".." +# workspace = ".." [lib] name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "2.0.0-alpha.1", default-features = false } -actix-http = "0.3.0-alpha.1" -actix-service = "1.0.0-alpha.1" +actix-web = { version = "1.0.9", default-features = false } +actix-http = "0.2.11" +actix-service = "0.4.2" bitflags = "1" bytes = "0.4" -futures = "0.3.1" +futures = "0.1.24" derive_more = "0.15.0" log = "0.4" mime = "0.3" @@ -32,4 +32,4 @@ percent-encoding = "2.1" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } +actix-web = { version = "1.0.9", features=["ssl"] } diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index 9d32ebed..232c6ae6 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -20,19 +20,19 @@ name = "actix_framed" path = "src/lib.rs" [dependencies] -actix-codec = "0.2.0-alpha.1" -actix-service = "1.0.0-alpha.1" +actix-codec = "0.1.2" +actix-service = "0.4.2" actix-router = "0.1.2" -actix-rt = "1.0.0-alpha.1" -actix-http = "0.3.0-alpha.1" -actix-server-config = "0.2.0-alpha.1" +actix-rt = "0.2.2" +actix-http = "0.2.11" +actix-server-config = "0.1.1" bytes = "0.4" futures = "0.1.25" log = "0.4" [dev-dependencies] -actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } -actix-connect = { version = "0.3.0-alpha.1", features=["openssl"] } -actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } -actix-utils = "0.5.0-alpha.1" +actix-server = { version = "0.6.0", features=["openssl"] } +actix-connect = { version = "0.2.0", features=["openssl"] } +actix-http-test = { version = "0.1.0", features=["openssl"] } +actix-utils = "0.4.0" diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index d05b3768..a307007e 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -17,14 +17,14 @@ name = "actix_identity" path = "src/lib.rs" [dependencies] -actix-web = { version = "2.0.0-alpha.1", default-features = false, features = ["secure-cookies"] } -actix-service = "1.0.0-alpha.1" +actix-web = { version = "1.0.9", default-features = false, features = ["secure-cookies"] } +actix-service = "0.4.2" futures = "0.3.1" serde = "1.0" serde_json = "1.0" time = "0.1.42" [dev-dependencies] -actix-rt = "1.0.0-alpha.1" -actix-http = "0.3.0-alpha.1" +actix-rt = "0.2.2" +actix-http = "0.2.11" bytes = "0.4" \ No newline at end of file diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 804d1bb6..aa4e9be2 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -18,17 +18,17 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "2.0.0-alpha.1", default-features = false } -actix-service = "1.0.0-alpha.1" +actix-web = { version = "1.0.9", default-features = false } +actix-service = "0.4.2" bytes = "0.4" derive_more = "0.15.0" httparse = "1.3" -futures = "0.3.1" +futures = "0.1.24" log = "0.4" mime = "0.3" time = "0.1" twoway = "0.2" [dev-dependencies] -actix-rt = "1.0.0-alpha.1" -actix-http = "0.3.0-alpha.1" \ No newline at end of file +actix-rt = "0.2.2" +actix-http = "0.2.11" \ No newline at end of file diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 3ce2a8b4..d2fd5ae5 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -24,15 +24,15 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "2.0.0-alpha.1" -actix-service = "1.0.0-alpha.1" +actix-web = "1.0.9" +actix-service = "0.4.2" bytes = "0.4" derive_more = "0.15.0" -futures = "0.3.1" +futures = "0.1.24" hashbrown = "0.6.3" serde = "1.0" serde_json = "1.0" time = "0.1.42" [dev-dependencies] -actix-rt = "1.0.0-alpha.1" +actix-rt = "0.2.2" diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index e4382029..a2b03ffc 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -14,7 +14,7 @@ categories = ["network-programming", "asynchronous", license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" -# workspace = ".." +workspace = ".." [package.metadata.docs.rs] features = [] diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 0c24ac90..1ec69b10 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -23,24 +23,26 @@ pub use actix_testing::*; /// /// ```rust /// use actix_http::HttpService; -/// use actix_http_test::TestServer; -/// use actix_web::{web, App, HttpResponse}; +/// use actix_http_test::{block_on, TestServer}; +/// use actix_web::{web, App, HttpResponse, Error}; /// -/// fn my_handler() -> HttpResponse { -/// HttpResponse::Ok().into() +/// async fn my_handler() -> Result { +/// Ok(HttpResponse::Ok().into()) /// } /// /// fn main() { -/// let mut srv = TestServer::new( -/// || HttpService::new( -/// App::new().service( -/// web::resource("/").to(my_handler)) -/// ) -/// ); +/// block_on( async { +/// let mut srv = TestServer::start( +/// || HttpService::new( +/// App::new().service( +/// web::resource("/").to_async(my_handler)) +/// ) +/// ); /// -/// let req = srv.get("/"); -/// let response = srv.block_on(req.send()).unwrap(); -/// assert!(response.status().is_success()); +/// let req = srv.get("/"); +/// let response = req.send().await.unwrap(); +/// assert!(response.status().is_success()); +/// }) /// } /// ``` pub struct TestServer; From 3646725cf634331a0f5162c4f3bb63637dc4e34b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 10:31:52 +0600 Subject: [PATCH 1612/1635] migrate actix-identity --- Cargo.toml | 2 +- actix-identity/Cargo.toml | 8 +- actix-identity/src/lib.rs | 625 ++++++++++++++++++++------------------ 3 files changed, 342 insertions(+), 293 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index db983bf6..b80cf3e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ members = [ #"actix-files", #"actix-framed", #"actix-session", - #"actix-identity", + "actix-identity", #"actix-multipart", #"actix-web-actors", "actix-web-codegen", diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index a307007e..d05b3768 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -17,14 +17,14 @@ name = "actix_identity" path = "src/lib.rs" [dependencies] -actix-web = { version = "1.0.9", default-features = false, features = ["secure-cookies"] } -actix-service = "0.4.2" +actix-web = { version = "2.0.0-alpha.1", default-features = false, features = ["secure-cookies"] } +actix-service = "1.0.0-alpha.1" futures = "0.3.1" serde = "1.0" serde_json = "1.0" time = "0.1.42" [dev-dependencies] -actix-rt = "0.2.2" -actix-http = "0.2.11" +actix-rt = "1.0.0-alpha.1" +actix-http = "0.3.0-alpha.1" bytes = "0.4" \ No newline at end of file diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 7216104e..30761d87 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -47,12 +47,13 @@ //! } //! ``` use std::cell::RefCell; +use std::future::Future; use std::rc::Rc; +use std::task::{Context, Poll}; use std::time::SystemTime; use actix_service::{Service, Transform}; -use futures::future::{ok, Either, FutureResult}; -use futures::{Future, IntoFuture, Poll}; +use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; use serde::{Deserialize, Serialize}; use time::Duration; @@ -165,21 +166,21 @@ where impl FromRequest for Identity { type Config = (); type Error = Error; - type Future = Result; + type Future = Ready>; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - Ok(Identity(req.clone())) + ok(Identity(req.clone())) } } /// Identity policy definition. pub trait IdentityPolicy: Sized + 'static { /// The return type of the middleware - type Future: IntoFuture, Error = Error>; + type Future: Future, Error>>; /// The return type of the middleware - type ResponseFuture: IntoFuture; + type ResponseFuture: Future>; /// Parse the session from request and load data from a service identity. fn from_request(&self, request: &mut ServiceRequest) -> Self::Future; @@ -234,7 +235,7 @@ where type Error = Error; type InitError = (); type Transform = IdentityServiceMiddleware; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(IdentityServiceMiddleware { @@ -261,46 +262,39 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.borrow_mut().poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.borrow_mut().poll_ready(cx) } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let srv = self.service.clone(); let backend = self.backend.clone(); + let fut = self.backend.from_request(&mut req); - Box::new( - self.backend.from_request(&mut req).into_future().then( - move |res| match res { - Ok(id) => { - req.extensions_mut() - .insert(IdentityItem { id, changed: false }); + async move { + match fut.await { + Ok(id) => { + req.extensions_mut() + .insert(IdentityItem { id, changed: false }); - Either::A(srv.borrow_mut().call(req).and_then(move |mut res| { - let id = - res.request().extensions_mut().remove::(); + let mut res = srv.borrow_mut().call(req).await?; + let id = res.request().extensions_mut().remove::(); - if let Some(id) = id { - Either::A( - backend - .to_response(id.id, id.changed, &mut res) - .into_future() - .then(move |t| match t { - Ok(_) => Ok(res), - Err(e) => Ok(res.error_response(e)), - }), - ) - } else { - Either::B(ok(res)) - } - })) + if let Some(id) = id { + match backend.to_response(id.id, id.changed, &mut res).await { + Ok(_) => Ok(res), + Err(e) => Ok(res.error_response(e)), + } + } else { + Ok(res) } - Err(err) => Either::B(ok(req.error_response(err))), - }, - ), - ) + } + Err(err) => Ok(req.error_response(err)), + } + } + .boxed_local() } } @@ -547,11 +541,11 @@ impl CookieIdentityPolicy { } impl IdentityPolicy for CookieIdentityPolicy { - type Future = Result, Error>; - type ResponseFuture = Result<(), Error>; + type Future = Ready, Error>>; + type ResponseFuture = Ready>; fn from_request(&self, req: &mut ServiceRequest) -> Self::Future { - Ok(self.0.load(req).map( + ok(self.0.load(req).map( |CookieValue { identity, login_timestamp, @@ -603,7 +597,7 @@ impl IdentityPolicy for CookieIdentityPolicy { } else { Ok(()) }; - Ok(()) + ok(()) } } @@ -613,7 +607,7 @@ mod tests { use super::*; use actix_web::http::StatusCode; - use actix_web::test::{self, TestRequest}; + use actix_web::test::{self, block_on, TestRequest}; use actix_web::{web, App, Error, HttpResponse}; const COOKIE_KEY_MASTER: [u8; 32] = [0; 32]; @@ -622,115 +616,138 @@ mod tests { #[test] fn test_identity() { - let mut srv = test::init_service( - App::new() - .wrap(IdentityService::new( - CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) - .domain("www.rust-lang.org") - .name(COOKIE_NAME) - .path("/") - .secure(true), - )) - .service(web::resource("/index").to(|id: Identity| { - if id.identity().is_some() { - HttpResponse::Created() - } else { + block_on(async { + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) + .domain("www.rust-lang.org") + .name(COOKIE_NAME) + .path("/") + .secure(true), + )) + .service(web::resource("/index").to(|id: Identity| { + if id.identity().is_some() { + HttpResponse::Created() + } else { + HttpResponse::Ok() + } + })) + .service(web::resource("/login").to(|id: Identity| { + id.remember(COOKIE_LOGIN.to_string()); HttpResponse::Ok() - } - })) - .service(web::resource("/login").to(|id: Identity| { - id.remember(COOKIE_LOGIN.to_string()); - HttpResponse::Ok() - })) - .service(web::resource("/logout").to(|id: Identity| { - if id.identity().is_some() { - id.forget(); - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - })), - ); - let resp = - test::call_service(&mut srv, TestRequest::with_uri("/index").to_request()); - assert_eq!(resp.status(), StatusCode::OK); + })) + .service(web::resource("/logout").to(|id: Identity| { + if id.identity().is_some() { + id.forget(); + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + })), + ) + .await; + let resp = test::call_service( + &mut srv, + TestRequest::with_uri("/index").to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); - let resp = - test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); - assert_eq!(resp.status(), StatusCode::OK); - let c = resp.response().cookies().next().unwrap().to_owned(); + let resp = test::call_service( + &mut srv, + TestRequest::with_uri("/login").to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + let c = resp.response().cookies().next().unwrap().to_owned(); - let resp = test::call_service( - &mut srv, - TestRequest::with_uri("/index") - .cookie(c.clone()) - .to_request(), - ); - assert_eq!(resp.status(), StatusCode::CREATED); + let resp = test::call_service( + &mut srv, + TestRequest::with_uri("/index") + .cookie(c.clone()) + .to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::CREATED); - let resp = test::call_service( - &mut srv, - TestRequest::with_uri("/logout") - .cookie(c.clone()) - .to_request(), - ); - assert_eq!(resp.status(), StatusCode::OK); - assert!(resp.headers().contains_key(header::SET_COOKIE)) + let resp = test::call_service( + &mut srv, + TestRequest::with_uri("/logout") + .cookie(c.clone()) + .to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)) + }) } #[test] fn test_identity_max_age_time() { - let duration = Duration::days(1); - let mut srv = test::init_service( - App::new() - .wrap(IdentityService::new( - CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) - .domain("www.rust-lang.org") - .name(COOKIE_NAME) - .path("/") - .max_age_time(duration) - .secure(true), - )) - .service(web::resource("/login").to(|id: Identity| { - id.remember("test".to_string()); - HttpResponse::Ok() - })), - ); - let resp = - test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); - assert_eq!(resp.status(), StatusCode::OK); - assert!(resp.headers().contains_key(header::SET_COOKIE)); - let c = resp.response().cookies().next().unwrap().to_owned(); - assert_eq!(duration, c.max_age().unwrap()); + block_on(async { + let duration = Duration::days(1); + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) + .domain("www.rust-lang.org") + .name(COOKIE_NAME) + .path("/") + .max_age_time(duration) + .secure(true), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })), + ) + .await; + let resp = test::call_service( + &mut srv, + TestRequest::with_uri("/login").to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + let c = resp.response().cookies().next().unwrap().to_owned(); + assert_eq!(duration, c.max_age().unwrap()); + }) } #[test] fn test_identity_max_age() { - let seconds = 60; - let mut srv = test::init_service( - App::new() - .wrap(IdentityService::new( - CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) - .domain("www.rust-lang.org") - .name(COOKIE_NAME) - .path("/") - .max_age(seconds) - .secure(true), - )) - .service(web::resource("/login").to(|id: Identity| { - id.remember("test".to_string()); - HttpResponse::Ok() - })), - ); - let resp = - test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()); - assert_eq!(resp.status(), StatusCode::OK); - assert!(resp.headers().contains_key(header::SET_COOKIE)); - let c = resp.response().cookies().next().unwrap().to_owned(); - assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); + block_on(async { + let seconds = 60; + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) + .domain("www.rust-lang.org") + .name(COOKIE_NAME) + .path("/") + .max_age(seconds) + .secure(true), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })), + ) + .await; + let resp = test::call_service( + &mut srv, + TestRequest::with_uri("/login").to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + let c = resp.response().cookies().next().unwrap().to_owned(); + assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); + }) } - fn create_identity_server< + async fn create_identity_server< F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static, >( f: F, @@ -754,6 +771,7 @@ mod tests { web::Json(identity) })), ) + .await } fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> { @@ -786,15 +804,8 @@ mod tests { jar.get(COOKIE_NAME).unwrap().clone() } - fn assert_logged_in(response: &mut ServiceResponse, identity: Option<&str>) { - use bytes::BytesMut; - use futures::Stream; - let bytes = - test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { - b.extend(c); - Ok::<_, Error>(b) - })) - .unwrap(); + async fn assert_logged_in(response: ServiceResponse, identity: Option<&str>) { + let bytes = test::read_body(response).await; let resp: Option = serde_json::from_slice(&bytes[..]).unwrap(); assert_eq!(resp.as_ref().map(|s| s.borrow()), identity); } @@ -874,183 +885,221 @@ mod tests { #[test] fn test_identity_legacy_cookie_is_set() { - let mut srv = create_identity_server(|c| c); - let mut resp = - test::call_service(&mut srv, TestRequest::with_uri("/").to_request()); - assert_logged_in(&mut resp, None); - assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN); + block_on(async { + let mut srv = create_identity_server(|c| c).await; + let mut resp = + test::call_service(&mut srv, TestRequest::with_uri("/").to_request()) + .await; + assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN); + assert_logged_in(resp, None).await; + }) } #[test] fn test_identity_legacy_cookie_works() { - let mut srv = create_identity_server(|c| c); - let cookie = legacy_login_cookie(COOKIE_LOGIN); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); - assert_no_login_cookie(&mut resp); + block_on(async { + let mut srv = create_identity_server(|c| c).await; + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_no_login_cookie(&mut resp); + assert_logged_in(resp, Some(COOKIE_LOGIN)).await; + }) } #[test] fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() { - let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); - let cookie = legacy_login_cookie(COOKIE_LOGIN); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, None); - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NoTimestamp, - VisitTimeStampCheck::NewTimestamp, - ); + block_on(async { + let mut srv = + create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); + assert_logged_in(resp, None).await; + }) } #[test] fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() { - let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); - let cookie = legacy_login_cookie(COOKIE_LOGIN); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, None); - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NewTimestamp, - VisitTimeStampCheck::NoTimestamp, - ); + block_on(async { + let mut srv = + create_identity_server(|c| c.login_deadline(Duration::days(90))).await; + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); + assert_logged_in(resp, None).await; + }) } #[test] fn test_identity_cookie_rejected_if_login_timestamp_needed() { - let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); - let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now())); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, None); - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NewTimestamp, - VisitTimeStampCheck::NoTimestamp, - ); + block_on(async { + let mut srv = + create_identity_server(|c| c.login_deadline(Duration::days(90))).await; + let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now())); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); + assert_logged_in(resp, None).await; + }) } #[test] fn test_identity_cookie_rejected_if_visit_timestamp_needed() { - let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); - let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, None); - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NoTimestamp, - VisitTimeStampCheck::NewTimestamp, - ); + block_on(async { + let mut srv = + create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; + let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); + assert_logged_in(resp, None).await; + }) } #[test] fn test_identity_cookie_rejected_if_login_timestamp_too_old() { - let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); - let cookie = login_cookie( - COOKIE_LOGIN, - Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), - None, - ); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, None); - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NewTimestamp, - VisitTimeStampCheck::NoTimestamp, - ); + block_on(async { + let mut srv = + create_identity_server(|c| c.login_deadline(Duration::days(90))).await; + let cookie = login_cookie( + COOKIE_LOGIN, + Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), + None, + ); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); + assert_logged_in(resp, None).await; + }) } #[test] fn test_identity_cookie_rejected_if_visit_timestamp_too_old() { - let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90))); - let cookie = login_cookie( - COOKIE_LOGIN, - None, - Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), - ); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, None); - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NoTimestamp, - VisitTimeStampCheck::NewTimestamp, - ); + block_on(async { + let mut srv = + create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; + let cookie = login_cookie( + COOKIE_LOGIN, + None, + Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), + ); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); + assert_logged_in(resp, None).await; + }) } #[test] fn test_identity_cookie_not_updated_on_login_deadline() { - let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90))); - let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); - assert_no_login_cookie(&mut resp); + block_on(async { + let mut srv = + create_identity_server(|c| c.login_deadline(Duration::days(90))).await; + let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_no_login_cookie(&mut resp); + assert_logged_in(resp, Some(COOKIE_LOGIN)).await; + }) } #[test] fn test_identity_cookie_updated_on_visit_deadline() { - let mut srv = create_identity_server(|c| { - c.visit_deadline(Duration::days(90)) - .login_deadline(Duration::days(90)) - }); - let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap(); - let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp)); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ); - assert_logged_in(&mut resp, Some(COOKIE_LOGIN)); - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::OldTimestamp(timestamp), - VisitTimeStampCheck::NewTimestamp, - ); + block_on(async { + let mut srv = create_identity_server(|c| { + c.visit_deadline(Duration::days(90)) + .login_deadline(Duration::days(90)) + }) + .await; + let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap(); + let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp)); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::OldTimestamp(timestamp), + VisitTimeStampCheck::NewTimestamp, + ); + assert_logged_in(resp, Some(COOKIE_LOGIN)).await; + }) } } From 6ac4ac66b96aa73f694d8de7fde2e0a2cffa1af8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 10:54:07 +0600 Subject: [PATCH 1613/1635] migrate actix-cors --- Cargo.toml | 2 +- actix-cors/Cargo.toml | 4 +- actix-cors/src/lib.rs | 722 ++++++++++++++++++++++-------------------- 3 files changed, 383 insertions(+), 345 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b80cf3e6..6827a619 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ members = [ ".", "awc", "actix-http", - #"actix-cors", + "actix-cors", #"actix-files", #"actix-framed", #"actix-session", diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml index 56b6fabd..57aa5833 100644 --- a/actix-cors/Cargo.toml +++ b/actix-cors/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_cors" path = "src/lib.rs" [dependencies] -actix-web = "1.0.9" -actix-service = "0.4.0" +actix-web = "2.0.0-alpha.1" +actix-service = "1.0.0-alpha.1" derive_more = "0.15.0" futures = "0.3.1" diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index c76bae92..40f9fdf9 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -23,7 +23,8 @@ //! .allowed_methods(vec!["GET", "POST"]) //! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) //! .allowed_header(http::header::CONTENT_TYPE) -//! .max_age(3600)) +//! .max_age(3600) +//! .finish()) //! .service( //! web::resource("/index.html") //! .route(web::get().to(index)) @@ -41,16 +42,16 @@ use std::collections::HashSet; use std::iter::FromIterator; use std::rc::Rc; +use std::task::{Context, Poll}; -use actix_service::{IntoTransform, Service, Transform}; +use actix_service::{Service, Transform}; use actix_web::dev::{RequestHead, ServiceRequest, ServiceResponse}; use actix_web::error::{Error, ResponseError, Result}; use actix_web::http::header::{self, HeaderName, HeaderValue}; use actix_web::http::{self, HttpTryFrom, Method, StatusCode, Uri}; use actix_web::HttpResponse; use derive_more::Display; -use futures::future::{ok, Either, Future, FutureResult}; -use futures::Poll; +use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; /// A set of errors that can occur during processing CORS #[derive(Debug, Display)] @@ -456,25 +457,9 @@ impl Cors { } self } -} -fn cors<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut Inner> { - if err.is_some() { - return None; - } - parts.as_mut() -} - -impl IntoTransform for Cors -where - S: Service, Error = Error>, - S::Future: 'static, - B: 'static, -{ - fn into_transform(self) -> CorsFactory { + /// Construct cors middleware + pub fn finish(self) -> CorsFactory { let mut slf = if !self.methods { self.allowed_methods(vec![ Method::GET, @@ -521,6 +506,16 @@ where } } +fn cors<'a>( + parts: &'a mut Option, + err: &Option, +) -> Option<&'a mut Inner> { + if err.is_some() { + return None; + } + parts.as_mut() +} + /// `Middleware` for Cross-origin resource sharing support /// /// The Cors struct contains the settings for CORS requests to be validated and @@ -540,7 +535,7 @@ where type Error = Error; type InitError = (); type Transform = CorsMiddleware; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(CorsMiddleware { @@ -682,12 +677,12 @@ where type Response = ServiceResponse; type Error = Error; type Future = Either< - FutureResult, - Either>>, + Ready>, + LocalBoxFuture<'static, Result>, >; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { @@ -698,7 +693,7 @@ where .and_then(|_| self.inner.validate_allowed_method(req.head())) .and_then(|_| self.inner.validate_allowed_headers(req.head())) { - return Either::A(ok(req.error_response(e))); + return Either::Left(ok(req.error_response(e))); } // allowed headers @@ -751,39 +746,50 @@ where .finish() .into_body(); - Either::A(ok(req.into_response(res))) - } else if req.headers().contains_key(&header::ORIGIN) { - // Only check requests with a origin header. - if let Err(e) = self.inner.validate_origin(req.head()) { - return Either::A(ok(req.error_response(e))); + Either::Left(ok(req.into_response(res))) + } else { + if req.headers().contains_key(&header::ORIGIN) { + // Only check requests with a origin header. + if let Err(e) = self.inner.validate_origin(req.head()) { + return Either::Left(ok(req.error_response(e))); + } } let inner = self.inner.clone(); + let has_origin = req.headers().contains_key(&header::ORIGIN); + let fut = self.service.call(req); - Either::B(Either::B(Box::new(self.service.call(req).and_then( - move |mut res| { - if let Some(origin) = - inner.access_control_allow_origin(res.request().head()) - { - res.headers_mut() - .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); - }; + Either::Right( + async move { + let res = fut.await; - if let Some(ref expose) = inner.expose_hdrs { - res.headers_mut().insert( - header::ACCESS_CONTROL_EXPOSE_HEADERS, - HeaderValue::try_from(expose.as_str()).unwrap(), - ); - } - if inner.supports_credentials { - res.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_CREDENTIALS, - HeaderValue::from_static("true"), - ); - } - if inner.vary_header { - let value = - if let Some(hdr) = res.headers_mut().get(&header::VARY) { + if has_origin { + let mut res = res?; + if let Some(origin) = + inner.access_control_allow_origin(res.request().head()) + { + res.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + origin.clone(), + ); + }; + + if let Some(ref expose) = inner.expose_hdrs { + res.headers_mut().insert( + header::ACCESS_CONTROL_EXPOSE_HEADERS, + HeaderValue::try_from(expose.as_str()).unwrap(), + ); + } + if inner.supports_credentials { + res.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_CREDENTIALS, + HeaderValue::from_static("true"), + ); + } + if inner.vary_header { + let value = if let Some(hdr) = + res.headers_mut().get(&header::VARY) + { let mut val: Vec = Vec::with_capacity(hdr.as_bytes().len() + 8); val.extend(hdr.as_bytes()); @@ -792,159 +798,153 @@ where } else { HeaderValue::from_static("Origin") }; - res.headers_mut().insert(header::VARY, value); + res.headers_mut().insert(header::VARY, value); + } + Ok(res) + } else { + res } - Ok(res) - }, - )))) - } else { - Either::B(Either::A(self.service.call(req))) + } + .boxed_local(), + ) } } } #[cfg(test)] mod tests { - use actix_service::{IntoService, Transform}; + use actix_service::{service_fn2, Transform}; use actix_web::test::{self, block_on, TestRequest}; use super::*; - impl Cors { - fn finish(self, srv: F) -> CorsMiddleware - where - F: IntoService, - S: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - > + 'static, - S::Future: 'static, - B: 'static, - { - block_on( - IntoTransform::::into_transform(self) - .new_transform(srv.into_service()), - ) - .unwrap() - } - } - #[test] #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] fn cors_validates_illegal_allow_credentials() { - let _cors = Cors::new() - .supports_credentials() - .send_wildcard() - .finish(test::ok_service()); + let _cors = Cors::new().supports_credentials().send_wildcard().finish(); } #[test] fn validate_origin_allows_all_origins() { - let mut cors = Cors::new().finish(test::ok_service()); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .to_srv_request(); + block_on(async { + let mut cors = Cors::new() + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!(resp.status(), StatusCode::OK); + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn default() { - let mut cors = - block_on(Cors::default().new_transform(test::ok_service())).unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .to_srv_request(); + block_on(async { + let mut cors = Cors::default() + .new_transform(test::ok_service()) + .await + .unwrap(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!(resp.status(), StatusCode::OK); + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_preflight() { - let mut cors = Cors::new() - .send_wildcard() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) - .allowed_header(header::CONTENT_TYPE) - .finish(test::ok_service()); + block_on(async { + let mut cors = Cors::new() + .send_wildcard() + .max_age(3600) + .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) + .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) + .allowed_header(header::CONTENT_TYPE) + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed") - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed") + .to_srv_request(); - assert!(cors.inner.validate_allowed_method(req.head()).is_err()); - assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); - let resp = test::call_service(&mut cors, req); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + assert!(cors.inner.validate_allowed_method(req.head()).is_err()); + assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") + .method(Method::OPTIONS) + .to_srv_request(); - assert!(cors.inner.validate_allowed_method(req.head()).is_err()); - assert!(cors.inner.validate_allowed_headers(req.head()).is_ok()); + assert!(cors.inner.validate_allowed_method(req.head()).is_err()); + assert!(cors.inner.validate_allowed_headers(req.head()).is_ok()); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ) - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") + .header( + header::ACCESS_CONTROL_REQUEST_HEADERS, + "AUTHORIZATION,ACCEPT", + ) + .method(Method::OPTIONS) + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!( - &b"*"[..], - resp.headers() - .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN) + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"*"[..], + resp.headers() + .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + assert_eq!( + &b"3600"[..], + resp.headers() + .get(&header::ACCESS_CONTROL_MAX_AGE) + .unwrap() + .as_bytes() + ); + let hdr = resp + .headers() + .get(&header::ACCESS_CONTROL_ALLOW_HEADERS) .unwrap() - .as_bytes() - ); - assert_eq!( - &b"3600"[..], - resp.headers() - .get(&header::ACCESS_CONTROL_MAX_AGE) + .to_str() + .unwrap(); + assert!(hdr.contains("authorization")); + assert!(hdr.contains("accept")); + assert!(hdr.contains("content-type")); + + let methods = resp + .headers() + .get(header::ACCESS_CONTROL_ALLOW_METHODS) .unwrap() - .as_bytes() - ); - let hdr = resp - .headers() - .get(&header::ACCESS_CONTROL_ALLOW_HEADERS) - .unwrap() - .to_str() - .unwrap(); - assert!(hdr.contains("authorization")); - assert!(hdr.contains("accept")); - assert!(hdr.contains("content-type")); + .to_str() + .unwrap(); + assert!(methods.contains("POST")); + assert!(methods.contains("GET")); + assert!(methods.contains("OPTIONS")); - let methods = resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_METHODS) - .unwrap() - .to_str() - .unwrap(); - assert!(methods.contains("POST")); - assert!(methods.contains("GET")); - assert!(methods.contains("OPTIONS")); + Rc::get_mut(&mut cors.inner).unwrap().preflight = false; - Rc::get_mut(&mut cors.inner).unwrap().preflight = false; + let req = TestRequest::with_header("Origin", "https://www.example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") + .header( + header::ACCESS_CONTROL_REQUEST_HEADERS, + "AUTHORIZATION,ACCEPT", + ) + .method(Method::OPTIONS) + .to_srv_request(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ) - .method(Method::OPTIONS) - .to_srv_request(); - - let resp = test::call_service(&mut cors, req); - assert_eq!(resp.status(), StatusCode::OK); + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } // #[test] @@ -960,216 +960,254 @@ mod tests { #[test] #[should_panic(expected = "OriginNotAllowed")] fn test_validate_not_allowed_origin() { - let cors = Cors::new() - .allowed_origin("https://www.example.com") - .finish(test::ok_service()); + block_on(async { + let cors = Cors::new() + .allowed_origin("https://www.example.com") + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.unknown.com") - .method(Method::GET) - .to_srv_request(); - cors.inner.validate_origin(req.head()).unwrap(); - cors.inner.validate_allowed_method(req.head()).unwrap(); - cors.inner.validate_allowed_headers(req.head()).unwrap(); + let req = TestRequest::with_header("Origin", "https://www.unknown.com") + .method(Method::GET) + .to_srv_request(); + cors.inner.validate_origin(req.head()).unwrap(); + cors.inner.validate_allowed_method(req.head()).unwrap(); + cors.inner.validate_allowed_headers(req.head()).unwrap(); + }) } #[test] fn test_validate_origin() { - let mut cors = Cors::new() - .allowed_origin("https://www.example.com") - .finish(test::ok_service()); + block_on(async { + let mut cors = Cors::new() + .allowed_origin("https://www.example.com") + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::GET) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::GET) + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!(resp.status(), StatusCode::OK); + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_no_origin_response() { - let mut cors = Cors::new().disable_preflight().finish(test::ok_service()); + block_on(async { + let mut cors = Cors::new() + .disable_preflight() + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::default().method(Method::GET).to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert!(resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_none()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!( - &b"https://www.example.com"[..], - resp.headers() + let req = TestRequest::default().method(Method::GET).to_srv_request(); + let resp = test::call_service(&mut cors, req).await; + assert!(resp + .headers() .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); + .is_none()); + + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .to_srv_request(); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://www.example.com"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + }) } #[test] fn test_response() { - let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let mut cors = Cors::new() - .send_wildcard() - .disable_preflight() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(exposed_headers.clone()) - .expose_headers(exposed_headers.clone()) - .allowed_header(header::CONTENT_TYPE) - .finish(test::ok_service()); + block_on(async { + let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; + let mut cors = Cors::new() + .send_wildcard() + .disable_preflight() + .max_age(3600) + .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) + .allowed_headers(exposed_headers.clone()) + .expose_headers(exposed_headers.clone()) + .allowed_header(header::CONTENT_TYPE) + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!( - &b"*"[..], - resp.headers() + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"*"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + assert_eq!( + &b"Origin"[..], + resp.headers().get(header::VARY).unwrap().as_bytes() + ); + + { + let headers = resp + .headers() + .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) + .unwrap() + .to_str() + .unwrap() + .split(',') + .map(|s| s.trim()) + .collect::>(); + + for h in exposed_headers { + assert!(headers.contains(&h.as_str())); + } + } + + let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; + let mut cors = Cors::new() + .send_wildcard() + .disable_preflight() + .max_age(3600) + .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) + .allowed_headers(exposed_headers.clone()) + .expose_headers(exposed_headers.clone()) + .allowed_header(header::CONTENT_TYPE) + .finish() + .new_transform(service_fn2(|req: ServiceRequest| { + ok(req.into_response( + HttpResponse::Ok().header(header::VARY, "Accept").finish(), + )) + })) + .await + .unwrap(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .to_srv_request(); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"Accept, Origin"[..], + resp.headers().get(header::VARY).unwrap().as_bytes() + ); + + let mut cors = Cors::new() + .disable_vary_header() + .allowed_origin("https://www.example.com") + .allowed_origin("https://www.google.com") + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); + + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") + .to_srv_request(); + let resp = test::call_service(&mut cors, req).await; + + let origins_str = resp + .headers() .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .unwrap() - .as_bytes() - ); - assert_eq!( - &b"Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - { - let headers = resp - .headers() - .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) - .unwrap() .to_str() - .unwrap() - .split(',') - .map(|s| s.trim()) - .collect::>(); + .unwrap(); - for h in exposed_headers { - assert!(headers.contains(&h.as_str())); - } - } - - let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let mut cors = Cors::new() - .send_wildcard() - .disable_preflight() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(exposed_headers.clone()) - .expose_headers(exposed_headers.clone()) - .allowed_header(header::CONTENT_TYPE) - .finish(|req: ServiceRequest| { - req.into_response( - HttpResponse::Ok().header(header::VARY, "Accept").finish(), - ) - }); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!( - &b"Accept, Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - let mut cors = Cors::new() - .disable_vary_header() - .allowed_origin("https://www.example.com") - .allowed_origin("https://www.google.com") - .finish(test::ok_service()); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .to_srv_request(); - let resp = test::call_service(&mut cors, req); - - let origins_str = resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!("https://www.example.com", origins_str); + assert_eq!("https://www.example.com", origins_str); + }) } #[test] fn test_multiple_origins() { - let mut cors = Cors::new() - .allowed_origin("https://example.com") - .allowed_origin("https://example.org") - .allowed_methods(vec![Method::GET]) - .finish(test::ok_service()); + block_on(async { + let mut cors = Cors::new() + .allowed_origin("https://example.com") + .allowed_origin("https://example.org") + .allowed_methods(vec![Method::GET]) + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://example.com") - .method(Method::GET) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://example.com") + .method(Method::GET) + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!( - &b"https://example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://example.com"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); - let req = TestRequest::with_header("Origin", "https://example.org") - .method(Method::GET) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://example.org") + .method(Method::GET) + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!( - &b"https://example.org"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://example.org"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + }) } #[test] fn test_multiple_origins_preflight() { - let mut cors = Cors::new() - .allowed_origin("https://example.com") - .allowed_origin("https://example.org") - .allowed_methods(vec![Method::GET]) - .finish(test::ok_service()); + block_on(async { + let mut cors = Cors::new() + .allowed_origin("https://example.com") + .allowed_origin("https://example.org") + .allowed_methods(vec![Method::GET]) + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") + .method(Method::OPTIONS) + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!( - &b"https://example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://example.com"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); - let req = TestRequest::with_header("Origin", "https://example.org") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://example.org") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") + .method(Method::OPTIONS) + .to_srv_request(); - let resp = test::call_service(&mut cors, req); - assert_eq!( - &b"https://example.org"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://example.org"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + }) } } From 69cadcdedb139f6cdae4604dde128161be6d87cb Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 11:31:31 +0600 Subject: [PATCH 1614/1635] migrate actix-files --- Cargo.toml | 4 +- actix-files/Cargo.toml | 14 +- actix-files/src/lib.rs | 1129 ++++++++++++++++++++------------------ actix-files/src/named.rs | 122 ++-- actix-http/src/error.rs | 2 +- 5 files changed, 664 insertions(+), 607 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6827a619..32918ee4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ members = [ "awc", "actix-http", "actix-cors", - #"actix-files", + "actix-files", #"actix-framed", #"actix-session", "actix-identity", @@ -126,7 +126,7 @@ actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } # actix-web-actors = { path = "actix-web-actors" } # actix-session = { path = "actix-session" } -# actix-files = { path = "actix-files" } +actix-files = { path = "actix-files" } # actix-multipart = { path = "actix-multipart" } awc = { path = "awc" } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 2f75fb50..6e33bb41 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.7" +version = "0.2.0-alpha.1" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -11,19 +11,19 @@ documentation = "https://docs.rs/actix-files/" categories = ["asynchronous", "web-programming::http-server"] license = "MIT/Apache-2.0" edition = "2018" -# workspace = ".." +workspace = ".." [lib] name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "1.0.9", default-features = false } -actix-http = "0.2.11" -actix-service = "0.4.2" +actix-web = { version = "2.0.0-alpha.1", default-features = false } +actix-http = "0.3.0-alpha.1" +actix-service = "1.0.0-alpha.1" bitflags = "1" bytes = "0.4" -futures = "0.1.24" +futures = "0.3.1" derive_more = "0.15.0" log = "0.4" mime = "0.3" @@ -32,4 +32,4 @@ percent-encoding = "2.1" v_htmlescape = "0.4" [dev-dependencies] -actix-web = { version = "1.0.9", features=["ssl"] } +actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 8df7a6aa..72db1695 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -4,25 +4,28 @@ use std::cell::RefCell; use std::fmt::Write; use std::fs::{DirEntry, File}; +use std::future::Future; use std::io::{Read, Seek}; use std::path::{Path, PathBuf}; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use std::{cmp, io}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; -use actix_service::{IntoNewService, NewService, Service}; +use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use actix_web::dev::{ AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest, ServiceResponse, }; -use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; +use actix_web::error::{Canceled, Error, ErrorInternalServerError}; use actix_web::guard::Guard; use actix_web::http::header::{self, DispositionType}; use actix_web::http::Method; -use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; +use actix_web::{web, FromRequest, HttpRequest, HttpResponse}; use bytes::Bytes; -use futures::future::{ok, Either, FutureResult}; -use futures::{Async, Future, Poll, Stream}; +use futures::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready}; +use futures::Stream; use mime; use mime_guess::from_ext; use percent_encoding::{utf8_percent_encode, CONTROLS}; @@ -54,32 +57,34 @@ pub struct ChunkedReadFile { size: u64, offset: u64, file: Option, - fut: Option>>>, + fut: Option< + LocalBoxFuture<'static, Result, Canceled>>, + >, counter: u64, } -fn handle_error(err: BlockingError) -> Error { - match err { - BlockingError::Error(err) => err.into(), - BlockingError::Canceled => ErrorInternalServerError("Unexpected error"), - } -} - impl Stream for ChunkedReadFile { - type Item = Bytes; - type Error = Error; + type Item = Result; - fn poll(&mut self) -> Poll, Error> { - if self.fut.is_some() { - return match self.fut.as_mut().unwrap().poll().map_err(handle_error)? { - Async::Ready((file, bytes)) => { + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll> { + if let Some(ref mut fut) = self.fut { + return match Pin::new(fut).poll(cx) { + Poll::Ready(Err(_)) => Poll::Ready(Some(Err(ErrorInternalServerError( + "Unexpected error", + ) + .into()))), + Poll::Ready(Ok(Ok((file, bytes)))) => { self.fut.take(); self.file = Some(file); self.offset += bytes.len() as u64; self.counter += bytes.len() as u64; - Ok(Async::Ready(Some(bytes))) + Poll::Ready(Some(Ok(bytes))) } - Async::NotReady => Ok(Async::NotReady), + Poll::Ready(Ok(Err(e))) => Poll::Ready(Some(Err(e.into()))), + Poll::Pending => Poll::Pending, }; } @@ -88,22 +93,25 @@ impl Stream for ChunkedReadFile { let counter = self.counter; if size == counter { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { let mut file = self.file.take().expect("Use after completion"); - self.fut = Some(Box::new(web::block(move || { - let max_bytes: usize; - max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; - let nbytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - if nbytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - Ok((file, Bytes::from(buf))) - }))); - self.poll() + self.fut = Some( + web::block(move || { + let max_bytes: usize; + max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; + let mut buf = Vec::with_capacity(max_bytes); + file.seek(io::SeekFrom::Start(offset))?; + let nbytes = + file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; + if nbytes == 0 { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + Ok((file, Bytes::from(buf))) + }) + .boxed_local(), + ); + self.poll_next(cx) } } } @@ -367,8 +375,8 @@ impl Files { /// Sets default handler which is used when no matched file could be found. pub fn default_handler(mut self, f: F) -> Self where - F: IntoNewService, - U: NewService< + F: IntoServiceFactory, + U: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, @@ -376,8 +384,8 @@ impl Files { > + 'static, { // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f.into_new_service().map_init_err(|_| ()), + self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( + f.into_factory().map_init_err(|_| ()), ))))); self @@ -398,14 +406,14 @@ impl HttpServiceFactory for Files { } } -impl NewService for Files { +impl ServiceFactory for Files { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Config = (); type Service = FilesService; type InitError = (); - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: &()) -> Self::Future { let mut srv = FilesService { @@ -421,17 +429,18 @@ impl NewService for Files { }; if let Some(ref default) = *self.default.borrow() { - Box::new( - default - .new_service(&()) - .map(move |default| { + default + .new_service(&()) + .map(move |result| match result { + Ok(default) => { srv.default = Some(default); - srv - }) - .map_err(|_| ()), - ) + Ok(srv) + } + Err(_) => Err(()), + }) + .boxed_local() } else { - Box::new(ok(srv)) + ok(srv).boxed_local() } } } @@ -454,14 +463,14 @@ impl FilesService { e: io::Error, req: ServiceRequest, ) -> Either< - FutureResult, - Box>, + Ready>, + LocalBoxFuture<'static, Result>, > { log::debug!("Files: Failed to handle {}: {}", req.path(), e); if let Some(ref mut default) = self.default { - default.call(req) + Either::Right(default.call(req)) } else { - Either::A(ok(req.error_response(e))) + Either::Left(ok(req.error_response(e))) } } } @@ -471,17 +480,15 @@ impl Service for FilesService { type Response = ServiceResponse; type Error = Error; type Future = Either< - FutureResult, - Box>, + Ready>, + LocalBoxFuture<'static, Result>, >; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, req: ServiceRequest) -> Self::Future { - // let (req, pl) = req.into_parts(); - let is_method_valid = if let Some(guard) = &self.guards { // execute user defined guards (**guard).check(req.head()) @@ -494,7 +501,7 @@ impl Service for FilesService { }; if !is_method_valid { - return Either::A(ok(req.into_response( + return Either::Left(ok(req.into_response( actix_web::HttpResponse::MethodNotAllowed() .header(header::CONTENT_TYPE, "text/plain") .body("Request did not meet this resource's requirements."), @@ -503,7 +510,7 @@ impl Service for FilesService { let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { Ok(item) => item, - Err(e) => return Either::A(ok(req.error_response(e))), + Err(e) => return Either::Left(ok(req.error_response(e))), }; // full filepath @@ -516,7 +523,7 @@ impl Service for FilesService { if let Some(ref redir_index) = self.index { if self.redirect_to_slash && !req.path().ends_with('/') { let redirect_to = format!("{}/", req.path()); - return Either::A(ok(req.into_response( + return Either::Left(ok(req.into_response( HttpResponse::Found() .header(header::LOCATION, redirect_to) .body("") @@ -536,7 +543,7 @@ impl Service for FilesService { named_file.flags = self.file_flags; let (req, _) = req.into_parts(); - Either::A(ok(match named_file.respond_to(&req) { + Either::Left(ok(match named_file.into_response(&req) { Ok(item) => ServiceResponse::new(req, item), Err(e) => ServiceResponse::from_err(e, req), })) @@ -548,11 +555,11 @@ impl Service for FilesService { let (req, _) = req.into_parts(); let x = (self.renderer)(&dir, &req); match x { - Ok(resp) => Either::A(ok(resp)), - Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))), + Ok(resp) => Either::Left(ok(resp)), + Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), } } else { - Either::A(ok(ServiceResponse::from_err( + Either::Left(ok(ServiceResponse::from_err( FilesError::IsDirectory, req.into_parts().0, ))) @@ -568,11 +575,11 @@ impl Service for FilesService { named_file.flags = self.file_flags; let (req, _) = req.into_parts(); - match named_file.respond_to(&req) { + match named_file.into_response(&req) { Ok(item) => { - Either::A(ok(ServiceResponse::new(req.clone(), item))) + Either::Left(ok(ServiceResponse::new(req.clone(), item))) } - Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))), + Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), } } Err(e) => self.handle_err(e, req), @@ -615,11 +622,11 @@ impl PathBufWrp { impl FromRequest for PathBufWrp { type Error = UriSegmentError; - type Future = Result; + type Future = Ready>; type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - PathBufWrp::get_pathbuf(req.match_info().path()) + ready(PathBufWrp::get_pathbuf(req.match_info().path())) } } @@ -630,8 +637,6 @@ mod tests { use std::ops::Add; use std::time::{Duration, SystemTime}; - use bytes::BytesMut; - use super::*; use actix_web::guard; use actix_web::http::header::{ @@ -639,8 +644,8 @@ mod tests { }; use actix_web::http::{Method, StatusCode}; use actix_web::middleware::Compress; - use actix_web::test::{self, TestRequest}; - use actix_web::App; + use actix_web::test::{self, block_on, TestRequest}; + use actix_web::{App, Responder}; #[test] fn test_file_extension_to_mime() { @@ -656,592 +661,643 @@ mod tests { #[test] fn test_if_modified_since_without_if_none_match() { - let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + block_on(async { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = + header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); - let req = TestRequest::default() - .header(header::IF_MODIFIED_SINCE, since) - .to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); + let req = TestRequest::default() + .header(header::IF_MODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); + }) } #[test] fn test_if_modified_since_with_if_none_match() { - let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + block_on(async { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = + header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); - let req = TestRequest::default() - .header(header::IF_NONE_MATCH, "miss_etag") - .header(header::IF_MODIFIED_SINCE, since) - .to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); + let req = TestRequest::default() + .header(header::IF_NONE_MATCH, "miss_etag") + .header(header::IF_MODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); + }) } #[test] fn test_named_file_text() { - 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; - } + block_on(async { + 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 req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + }) } #[test] fn test_named_file_content_disposition() { - 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; - } + block_on(async { + 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 req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); - let file = NamedFile::open("Cargo.toml") - .unwrap() - .disable_content_disposition(); - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); + let file = NamedFile::open("Cargo.toml") + .unwrap() + .disable_content_disposition(); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); + }) } #[test] fn test_named_file_non_ascii_file_name() { - let mut file = - NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml") - .unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + block_on(async { + let mut file = + NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml") + .unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml" ); + }) } #[test] fn test_named_file_set_content_type() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_content_type(mime::TEXT_XML); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + block_on(async { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_content_type(mime::TEXT_XML); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/xml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/xml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + }) } #[test] fn test_named_file_image() { - let mut file = NamedFile::open("tests/test.png").unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + block_on(async { + let mut file = NamedFile::open("tests/test.png").unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"test.png\"" + ); + }) } #[test] fn test_named_file_image_attachment() { - let cd = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename(String::from("test.png"))], - }; - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_content_disposition(cd); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + block_on(async { + let cd = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Filename(String::from("test.png"))], + }; + let mut file = NamedFile::open("tests/test.png") + .unwrap() + .set_content_disposition(cd); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.png\"" + ); + }) } #[test] fn test_named_file_binary() { - let mut file = NamedFile::open("tests/test.binary").unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + block_on(async { + let mut file = NamedFile::open("tests/test.binary").unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.binary\"" - ); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "application/octet-stream" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.binary\"" + ); + }) } #[test] fn test_named_file_status_code_text() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_status_code(StatusCode::NOT_FOUND); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + block_on(async { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_status_code(StatusCode::NOT_FOUND); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] fn test_mime_override() { - fn all_attachment(_: &mime::Name) -> DispositionType { - DispositionType::Attachment - } + block_on(async { + fn all_attachment(_: &mime::Name) -> DispositionType { + DispositionType::Attachment + } - let mut srv = test::init_service( - App::new().service( - Files::new("/", ".") - .mime_override(all_attachment) - .index_file("Cargo.toml"), - ), - ); + let mut srv = test::init_service( + App::new().service( + Files::new("/", ".") + .mime_override(all_attachment) + .index_file("Cargo.toml"), + ), + ) + .await; - let request = TestRequest::get().uri("/").to_request(); - let response = test::call_service(&mut srv, request); - assert_eq!(response.status(), StatusCode::OK); + let request = TestRequest::get().uri("/").to_request(); + let response = test::call_service(&mut srv, request).await; + assert_eq!(response.status(), StatusCode::OK); - let content_disposition = response - .headers() - .get(header::CONTENT_DISPOSITION) - .expect("To have CONTENT_DISPOSITION"); - let content_disposition = content_disposition - .to_str() - .expect("Convert CONTENT_DISPOSITION to str"); - assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); + let content_disposition = response + .headers() + .get(header::CONTENT_DISPOSITION) + .expect("To have CONTENT_DISPOSITION"); + let content_disposition = content_disposition + .to_str() + .expect("Convert CONTENT_DISPOSITION to str"); + assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); + }) } #[test] fn test_named_file_ranges_status_code() { - let mut srv = test::init_service( - App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), - ); + block_on(async { + let mut srv = test::init_service( + App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), + ) + .await; - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=10-20") - .to_request(); - let response = test::call_service(&mut srv, request); - assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/Cargo.toml") + .header(header::RANGE, "bytes=10-20") + .to_request(); + let response = test::call_service(&mut srv, request).await; + assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); - // Invalid range header - let request = TestRequest::get() - .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=1-0") - .to_request(); - let response = test::call_service(&mut srv, request); + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/Cargo.toml") + .header(header::RANGE, "bytes=1-0") + .to_request(); + let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); + assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); + }) } #[test] fn test_named_file_content_range_headers() { - let mut srv = test::init_service( - App::new().service(Files::new("/test", ".").index_file("tests/test.binary")), - ); + block_on(async { + let mut srv = test::init_service( + App::new() + .service(Files::new("/test", ".").index_file("tests/test.binary")), + ) + .await; - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-20") - .to_request(); + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-20") + .to_request(); - let response = test::call_service(&mut srv, request); - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); + let response = test::call_service(&mut srv, request).await; + let contentrange = response + .headers() + .get(header::CONTENT_RANGE) + .unwrap() + .to_str() + .unwrap(); - assert_eq!(contentrange, "bytes 10-20/100"); + assert_eq!(contentrange, "bytes 10-20/100"); - // Invalid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-5") - .to_request(); - let response = test::call_service(&mut srv, request); + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-5") + .to_request(); + let response = test::call_service(&mut srv, request).await; - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); + let contentrange = response + .headers() + .get(header::CONTENT_RANGE) + .unwrap() + .to_str() + .unwrap(); - assert_eq!(contentrange, "bytes */100"); + assert_eq!(contentrange, "bytes */100"); + }) } #[test] fn test_named_file_content_length_headers() { - // use actix_web::body::{MessageBody, ResponseBody}; + block_on(async { + // use actix_web::body::{MessageBody, ResponseBody}; - let mut srv = test::init_service( - App::new().service(Files::new("test", ".").index_file("tests/test.binary")), - ); + let mut srv = test::init_service( + App::new() + .service(Files::new("test", ".").index_file("tests/test.binary")), + ) + .await; - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-20") - .to_request(); - let _response = test::call_service(&mut srv, request); + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-20") + .to_request(); + let _response = test::call_service(&mut srv, request).await; - // let contentlength = response - // .headers() - // .get(header::CONTENT_LENGTH) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(contentlength, "11"); + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "11"); - // Invalid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-8") - .to_request(); - let response = test::call_service(&mut srv, request); - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-8") + .to_request(); + let response = test::call_service(&mut srv, request).await; + assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); - // Without range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - // .no_default_headers() - .to_request(); - let _response = test::call_service(&mut srv, request); + // Without range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + // .no_default_headers() + .to_request(); + let _response = test::call_service(&mut srv, request).await; - // let contentlength = response - // .headers() - // .get(header::CONTENT_LENGTH) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(contentlength, "100"); + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "100"); - // chunked - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .to_request(); - let mut response = test::call_service(&mut srv, request); + // chunked + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .to_request(); + let response = test::call_service(&mut srv, request).await; - // with enabled compression - // { - // let te = response - // .headers() - // .get(header::TRANSFER_ENCODING) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(te, "chunked"); - // } + // with enabled compression + // { + // let te = response + // .headers() + // .get(header::TRANSFER_ENCODING) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(te, "chunked"); + // } - let bytes = - test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { - b.extend(c); - Ok::<_, Error>(b) - })) - .unwrap(); - let data = Bytes::from(fs::read("tests/test.binary").unwrap()); - assert_eq!(bytes.freeze(), data); + let bytes = test::read_body(response).await; + let data = Bytes::from(fs::read("tests/test.binary").unwrap()); + assert_eq!(bytes, data); + }) } #[test] fn test_head_content_length_headers() { - let mut srv = test::init_service( - App::new().service(Files::new("test", ".").index_file("tests/test.binary")), - ); + block_on(async { + let mut srv = test::init_service( + App::new() + .service(Files::new("test", ".").index_file("tests/test.binary")), + ) + .await; - // Valid range header - let request = TestRequest::default() - .method(Method::HEAD) - .uri("/t%65st/tests/test.binary") - .to_request(); - let _response = test::call_service(&mut srv, request); + // Valid range header + let request = TestRequest::default() + .method(Method::HEAD) + .uri("/t%65st/tests/test.binary") + .to_request(); + let _response = test::call_service(&mut srv, request).await; - // TODO: fix check - // let contentlength = response - // .headers() - // .get(header::CONTENT_LENGTH) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(contentlength, "100"); + // TODO: fix check + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "100"); + }) } #[test] fn test_static_files_with_spaces() { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").index_file("Cargo.toml")), - ); - let request = TestRequest::get() - .uri("/tests/test%20space.binary") - .to_request(); - let mut response = test::call_service(&mut srv, request); - assert_eq!(response.status(), StatusCode::OK); + block_on(async { + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").index_file("Cargo.toml")), + ) + .await; + let request = TestRequest::get() + .uri("/tests/test%20space.binary") + .to_request(); + let response = test::call_service(&mut srv, request).await; + assert_eq!(response.status(), StatusCode::OK); - let bytes = - test::block_on(response.take_body().fold(BytesMut::new(), |mut b, c| { - b.extend(c); - Ok::<_, Error>(b) - })) - .unwrap(); - - let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); - assert_eq!(bytes.freeze(), data); + let bytes = test::read_body(response).await; + let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); + assert_eq!(bytes, data); + }) } #[test] fn test_files_not_allowed() { - let mut srv = test::init_service(App::new().service(Files::new("/", "."))); + block_on(async { + let mut srv = + test::init_service(App::new().service(Files::new("/", "."))).await; - let req = TestRequest::default() - .uri("/Cargo.toml") - .method(Method::POST) - .to_request(); + let req = TestRequest::default() + .uri("/Cargo.toml") + .method(Method::POST) + .to_request(); - let resp = test::call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let mut srv = test::init_service(App::new().service(Files::new("/", "."))); - let req = TestRequest::default() - .method(Method::PUT) - .uri("/Cargo.toml") - .to_request(); - let resp = test::call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let mut srv = + test::init_service(App::new().service(Files::new("/", "."))).await; + let req = TestRequest::default() + .method(Method::PUT) + .uri("/Cargo.toml") + .to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + }) } #[test] fn test_files_guards() { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").use_guards(guard::Post())), - ); + block_on(async { + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").use_guards(guard::Post())), + ) + .await; - let req = TestRequest::default() - .uri("/Cargo.toml") - .method(Method::POST) - .to_request(); + let req = TestRequest::default() + .uri("/Cargo.toml") + .method(Method::POST) + .to_request(); - let resp = test::call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::OK); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_named_file_content_encoding() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( - web::resource("/").to(|| { - NamedFile::open("Cargo.toml") - .unwrap() - .set_content_encoding(header::ContentEncoding::Identity) - }), - )); + block_on(async { + let mut srv = + test::init_service(App::new().wrap(Compress::default()).service( + web::resource("/").to(|| { + NamedFile::open("Cargo.toml") + .unwrap() + .set_content_encoding(header::ContentEncoding::Identity) + }), + )) + .await; - let request = TestRequest::get() - .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") - .to_request(); - let res = test::call_service(&mut srv, request); - assert_eq!(res.status(), StatusCode::OK); - assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); + let request = TestRequest::get() + .uri("/") + .header(header::ACCEPT_ENCODING, "gzip") + .to_request(); + let res = test::call_service(&mut srv, request).await; + assert_eq!(res.status(), StatusCode::OK); + assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); + }) } #[test] fn test_named_file_content_encoding_gzip() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( - web::resource("/").to(|| { - NamedFile::open("Cargo.toml") - .unwrap() - .set_content_encoding(header::ContentEncoding::Gzip) - }), - )); + block_on(async { + let mut srv = + test::init_service(App::new().wrap(Compress::default()).service( + web::resource("/").to(|| { + NamedFile::open("Cargo.toml") + .unwrap() + .set_content_encoding(header::ContentEncoding::Gzip) + }), + )) + .await; - let request = TestRequest::get() - .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") - .to_request(); - let res = test::call_service(&mut srv, request); - assert_eq!(res.status(), StatusCode::OK); - assert_eq!( - res.headers() - .get(header::CONTENT_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "gzip" - ); + let request = TestRequest::get() + .uri("/") + .header(header::ACCEPT_ENCODING, "gzip") + .to_request(); + let res = test::call_service(&mut srv, request).await; + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers() + .get(header::CONTENT_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "gzip" + ); + }) } #[test] fn test_named_file_allowed_method() { - let req = TestRequest::default().method(Method::GET).to_http_request(); - let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.respond_to(&req).unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + block_on(async { + let req = TestRequest::default().method(Method::GET).to_http_request(); + let file = NamedFile::open("Cargo.toml").unwrap(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + }) } #[test] fn test_static_files() { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ); - let req = TestRequest::with_uri("/missing").to_request(); + block_on(async { + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").show_files_listing()), + ) + .await; + let req = TestRequest::with_uri("/missing").to_request(); - let resp = test::call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service(App::new().service(Files::new("/", "."))); + let mut srv = + test::init_service(App::new().service(Files::new("/", "."))).await; - let req = TestRequest::default().to_request(); - let resp = test::call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::default().to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ); - let req = TestRequest::with_uri("/tests").to_request(); - let mut resp = test::call_service(&mut srv, req); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/html; charset=utf-8" - ); + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").show_files_listing()), + ) + .await; + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/html; charset=utf-8" + ); - let bytes = - test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| { - b.extend(c); - Ok::<_, Error>(b) - })) - .unwrap(); - assert!(format!("{:?}", bytes).contains("/tests/test.png")); + let bytes = test::read_body(resp).await; + assert!(format!("{:?}", bytes).contains("/tests/test.png")); + }) } #[test] fn test_redirect_to_slash_directory() { - // should not redirect if no index - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").redirect_to_slash_directory()), - ); - let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + block_on(async { + // should not redirect if no index + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").redirect_to_slash_directory()), + ) + .await; + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - // should redirect if index present - let mut srv = test::init_service( - App::new().service( - Files::new("/", ".") - .index_file("test.png") - .redirect_to_slash_directory(), - ), - ); - let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::FOUND); + // should redirect if index present + let mut srv = test::init_service( + App::new().service( + Files::new("/", ".") + .index_file("test.png") + .redirect_to_slash_directory(), + ), + ) + .await; + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::FOUND); - // should not redirect if the path is wrong - let req = TestRequest::with_uri("/not_existing").to_request(); - let resp = test::call_service(&mut srv, req); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + // should not redirect if the path is wrong + let req = TestRequest::with_uri("/not_existing").to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + }) } #[test] @@ -1252,26 +1308,21 @@ mod tests { #[test] fn test_default_handler_file_missing() { - let mut st = test::block_on( - Files::new("/", ".") + block_on(async { + let mut st = Files::new("/", ".") .default_handler(|req: ServiceRequest| { - Ok(req.into_response(HttpResponse::Ok().body("default content"))) + ok(req.into_response(HttpResponse::Ok().body("default content"))) }) - .new_service(&()), - ) - .unwrap(); - let req = TestRequest::with_uri("/missing").to_srv_request(); + .new_service(&()) + .await + .unwrap(); + let req = TestRequest::with_uri("/missing").to_srv_request(); - let mut resp = test::call_service(&mut st, req); - assert_eq!(resp.status(), StatusCode::OK); - let bytes = - test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| { - b.extend(c); - Ok::<_, Error>(b) - })) - .unwrap(); - - assert_eq!(bytes.freeze(), Bytes::from_static(b"default content")); + let resp = test::call_service(&mut st, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let bytes = test::read_body(resp).await; + assert_eq!(bytes, Bytes::from_static(b"default content")); + }) } // #[test] diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 955982ca..0dcbd93b 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -18,6 +18,7 @@ use actix_web::http::header::{ use actix_web::http::{ContentEncoding, StatusCode}; use actix_web::middleware::BodyEncoding; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; +use futures::future::{ready, Ready}; use crate::range::HttpRange; use crate::ChunkedReadFile; @@ -255,62 +256,8 @@ impl NamedFile { pub(crate) fn last_modified(&self) -> Option { self.modified.map(|mtime| mtime.into()) } -} -impl Deref for NamedFile { - type Target = File; - - fn deref(&self) -> &File { - &self.file - } -} - -impl DerefMut for NamedFile { - fn deref_mut(&mut self) -> &mut File { - &mut self.file - } -} - -/// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - None | Some(header::IfMatch::Any) => true, - Some(header::IfMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.strong_eq(some_etag) { - return true; - } - } - } - false - } - } -} - -/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - Some(header::IfNoneMatch::Any) => false, - Some(header::IfNoneMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.weak_eq(some_etag) { - return false; - } - } - } - true - } - None => true, - } -} - -impl Responder for NamedFile { - type Error = Error; - type Future = Result; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { + pub fn into_response(self, req: &HttpRequest) -> Result { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) @@ -442,8 +389,67 @@ impl Responder for NamedFile { counter: 0, }; if offset != 0 || length != self.md.len() { - return Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)); - }; - Ok(resp.body(SizedStream::new(length, reader))) + Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader)) + } else { + Ok(resp.body(SizedStream::new(length, reader))) + } + } +} + +impl Deref for NamedFile { + type Target = File; + + fn deref(&self) -> &File { + &self.file + } +} + +impl DerefMut for NamedFile { + fn deref_mut(&mut self) -> &mut File { + &mut self.file + } +} + +/// Returns true if `req` has no `If-Match` header or one which matches `etag`. +fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { + match req.get_header::() { + None | Some(header::IfMatch::Any) => true, + Some(header::IfMatch::Items(ref items)) => { + if let Some(some_etag) = etag { + for item in items { + if item.strong_eq(some_etag) { + return true; + } + } + } + false + } + } +} + +/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. +fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { + match req.get_header::() { + Some(header::IfNoneMatch::Any) => false, + Some(header::IfNoneMatch::Items(ref items)) => { + if let Some(some_etag) = etag { + for item in items { + if item.weak_eq(some_etag) { + return false; + } + } + } + true + } + None => true, + } +} + +impl Responder for NamedFile { + type Error = Error; + type Future = Ready>; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + ready(self.into_response(req)) } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 2a683399..a725789a 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -9,7 +9,7 @@ use std::{fmt, io, result}; use actix_utils::timeout::TimeoutError; use bytes::BytesMut; use derive_more::{Display, From}; -use futures::channel::oneshot::Canceled; +pub use futures::channel::oneshot::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; use httparse; From 95e2a0ef2e5617264176e67fe9b5769717702a56 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 12:17:01 +0600 Subject: [PATCH 1615/1635] migrate actix-framed --- Cargo.toml | 2 +- actix-framed/Cargo.toml | 21 +-- actix-framed/src/app.rs | 63 ++++----- actix-framed/src/helpers.rs | 48 ++++--- actix-framed/src/route.rs | 68 +++++----- actix-framed/src/service.rs | 67 +++++----- actix-framed/src/test.rs | 7 +- actix-framed/tests/test_server.rs | 204 +++++++++++++++++------------- 8 files changed, 263 insertions(+), 217 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 32918ee4..3efc058d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ members = [ "actix-http", "actix-cors", "actix-files", - #"actix-framed", + "actix-framed", #"actix-session", "actix-identity", #"actix-multipart", diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index 232c6ae6..4783daef 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -20,19 +20,20 @@ name = "actix_framed" path = "src/lib.rs" [dependencies] -actix-codec = "0.1.2" -actix-service = "0.4.2" +actix-codec = "0.2.0-alpha.1" +actix-service = "1.0.0-alpha.1" actix-router = "0.1.2" -actix-rt = "0.2.2" -actix-http = "0.2.11" -actix-server-config = "0.1.1" +actix-rt = "1.0.0-alpha.1" +actix-http = "0.3.0-alpha.1" +actix-server-config = "0.3.0-alpha.1" bytes = "0.4" -futures = "0.1.25" +futures = "0.3.1" +pin-project = "0.4.6" log = "0.4" [dev-dependencies] -actix-server = { version = "0.6.0", features=["openssl"] } -actix-connect = { version = "0.2.0", features=["openssl"] } -actix-http-test = { version = "0.1.0", features=["openssl"] } -actix-utils = "0.4.0" +actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } +actix-connect = { version = "0.3.0-alpha.1", features=["openssl"] } +actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } +actix-utils = "0.5.0-alpha.1" diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs index ad5b1ec2..f3e746e9 100644 --- a/actix-framed/src/app.rs +++ b/actix-framed/src/app.rs @@ -1,21 +1,24 @@ +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::h1::{Codec, SendResponse}; use actix_http::{Error, Request, Response}; use actix_router::{Path, Router, Url}; use actix_server_config::ServerConfig; -use actix_service::{IntoNewService, NewService, Service}; -use futures::{Async, Future, Poll}; +use actix_service::{IntoServiceFactory, Service, ServiceFactory}; +use futures::future::{ok, FutureExt, LocalBoxFuture}; use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService}; use crate::request::FramedRequest; use crate::state::State; -type BoxedResponse = Box>; +type BoxedResponse = LocalBoxFuture<'static, Result<(), Error>>; pub trait HttpServiceFactory { - type Factory: NewService; + type Factory: ServiceFactory; fn path(&self) -> &str; @@ -48,19 +51,19 @@ impl FramedApp { pub fn service(mut self, factory: U) -> Self where U: HttpServiceFactory, - U::Factory: NewService< + U::Factory: ServiceFactory< Config = (), Request = FramedRequest, Response = (), Error = Error, InitError = (), > + 'static, - ::Future: 'static, - ::Service: Service< + ::Future: 'static, + ::Service: Service< Request = FramedRequest, Response = (), Error = Error, - Future = Box>, + Future = LocalBoxFuture<'static, Result<(), Error>>, >, { let path = factory.path().to_string(); @@ -70,12 +73,12 @@ impl FramedApp { } } -impl IntoNewService> for FramedApp +impl IntoServiceFactory> for FramedApp where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: 'static, { - fn into_new_service(self) -> FramedAppFactory { + fn into_factory(self) -> FramedAppFactory { FramedAppFactory { state: self.state, services: Rc::new(self.services), @@ -89,9 +92,9 @@ pub struct FramedAppFactory { services: Rc>)>>, } -impl NewService for FramedAppFactory +impl ServiceFactory for FramedAppFactory where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, S: 'static, { type Config = ServerConfig; @@ -128,28 +131,30 @@ pub struct CreateService { enum CreateServiceItem { Future( Option, - Box>, Error = ()>>, + LocalBoxFuture<'static, Result>, ()>>, ), Service(String, BoxedHttpService>), } impl Future for CreateService where - T: AsyncRead + AsyncWrite, + T: AsyncRead + AsyncWrite + Unpin, { - type Item = FramedAppService; - type Error = (); + type Output = Result, ()>; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { let mut done = true; // poll http services for item in &mut self.fut { let res = match item { CreateServiceItem::Future(ref mut path, ref mut fut) => { - match fut.poll()? { - Async::Ready(service) => Some((path.take().unwrap(), service)), - Async::NotReady => { + match Pin::new(fut).poll(cx) { + Poll::Ready(Ok(service)) => { + Some((path.take().unwrap(), service)) + } + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Pending => { done = false; None } @@ -176,12 +181,12 @@ where } router }); - Ok(Async::Ready(FramedAppService { + Poll::Ready(Ok(FramedAppService { router: router.finish(), state: self.state.clone(), })) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -193,15 +198,15 @@ pub struct FramedAppService { impl Service for FramedAppService where - T: AsyncRead + AsyncWrite, + T: AsyncRead + AsyncWrite + Unpin, { type Request = (Request, Framed); type Response = (); type Error = Error; type Future = BoxedResponse; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { @@ -210,8 +215,8 @@ where if let Some((srv, _info)) = self.router.recognize_mut(&mut path) { return srv.call(FramedRequest::new(req, framed, path, self.state.clone())); } - Box::new( - SendResponse::new(framed, Response::NotFound().finish()).then(|_| Ok(())), - ) + SendResponse::new(framed, Response::NotFound().finish()) + .then(|_| ok(())) + .boxed_local() } } diff --git a/actix-framed/src/helpers.rs b/actix-framed/src/helpers.rs index b343301f..b654f9cd 100644 --- a/actix-framed/src/helpers.rs +++ b/actix-framed/src/helpers.rs @@ -1,36 +1,38 @@ +use std::task::{Context, Poll}; + use actix_http::Error; -use actix_service::{NewService, Service}; -use futures::{Future, Poll}; +use actix_service::{Service, ServiceFactory}; +use futures::future::{FutureExt, LocalBoxFuture}; pub(crate) type BoxedHttpService = Box< dyn Service< Request = Req, Response = (), Error = Error, - Future = Box>, + Future = LocalBoxFuture<'static, Result<(), Error>>, >, >; pub(crate) type BoxedHttpNewService = Box< - dyn NewService< + dyn ServiceFactory< Config = (), Request = Req, Response = (), Error = Error, InitError = (), Service = BoxedHttpService, - Future = Box, Error = ()>>, + Future = LocalBoxFuture<'static, Result, ()>>, >, >; -pub(crate) struct HttpNewService(T); +pub(crate) struct HttpNewService(T); impl HttpNewService where - T: NewService, + T: ServiceFactory, T::Response: 'static, T::Future: 'static, - T::Service: Service>> + 'static, + T::Service: Service>> + 'static, ::Future: 'static, { pub fn new(service: T) -> Self { @@ -38,12 +40,12 @@ where } } -impl NewService for HttpNewService +impl ServiceFactory for HttpNewService where - T: NewService, + T: ServiceFactory, T::Request: 'static, T::Future: 'static, - T::Service: Service>> + 'static, + T::Service: Service>> + 'static, ::Future: 'static, { type Config = (); @@ -52,13 +54,19 @@ where type Error = Error; type InitError = (); type Service = BoxedHttpService; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: &()) -> Self::Future { - Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { - let service: BoxedHttpService<_> = Box::new(HttpServiceWrapper { service }); - Ok(service) - })) + let fut = self.0.new_service(&()); + + async move { + fut.await.map_err(|_| ()).map(|service| { + let service: BoxedHttpService<_> = + Box::new(HttpServiceWrapper { service }); + service + }) + } + .boxed_local() } } @@ -70,7 +78,7 @@ impl Service for HttpServiceWrapper where T: Service< Response = (), - Future = Box>, + Future = LocalBoxFuture<'static, Result<(), Error>>, Error = Error, >, T::Request: 'static, @@ -78,10 +86,10 @@ where type Request = T::Request; type Response = (); type Error = Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result<(), Error>>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: Self::Request) -> Self::Future { diff --git a/actix-framed/src/route.rs b/actix-framed/src/route.rs index 5beb2416..78303968 100644 --- a/actix-framed/src/route.rs +++ b/actix-framed/src/route.rs @@ -1,11 +1,12 @@ use std::fmt; +use std::future::Future; use std::marker::PhantomData; +use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{http::Method, Error}; -use actix_service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Future, IntoFuture, Poll}; +use actix_service::{Service, ServiceFactory}; +use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; use log::error; use crate::app::HttpServiceFactory; @@ -15,11 +16,11 @@ use crate::request::FramedRequest; /// /// Route uses builder-like pattern for configuration. /// If handler is not explicitly set, default *404 Not Found* handler is used. -pub struct FramedRoute { +pub struct FramedRoute { handler: F, pattern: String, methods: Vec, - state: PhantomData<(Io, S, R)>, + state: PhantomData<(Io, S, R, E)>, } impl FramedRoute { @@ -53,12 +54,12 @@ impl FramedRoute { self } - pub fn to(self, handler: F) -> FramedRoute + pub fn to(self, handler: F) -> FramedRoute where F: FnMut(FramedRequest) -> R, - R: IntoFuture, - R::Future: 'static, - R::Error: fmt::Debug, + R: Future> + 'static, + + E: fmt::Debug, { FramedRoute { handler, @@ -69,15 +70,14 @@ impl FramedRoute { } } -impl HttpServiceFactory for FramedRoute +impl HttpServiceFactory for FramedRoute where Io: AsyncRead + AsyncWrite + 'static, F: FnMut(FramedRequest) -> R + Clone, - R: IntoFuture, - R::Future: 'static, - R::Error: fmt::Display, + R: Future> + 'static, + E: fmt::Display, { - type Factory = FramedRouteFactory; + type Factory = FramedRouteFactory; fn path(&self) -> &str { &self.pattern @@ -92,27 +92,26 @@ where } } -pub struct FramedRouteFactory { +pub struct FramedRouteFactory { handler: F, methods: Vec, - _t: PhantomData<(Io, S, R)>, + _t: PhantomData<(Io, S, R, E)>, } -impl NewService for FramedRouteFactory +impl ServiceFactory for FramedRouteFactory where Io: AsyncRead + AsyncWrite + 'static, F: FnMut(FramedRequest) -> R + Clone, - R: IntoFuture, - R::Future: 'static, - R::Error: fmt::Display, + R: Future> + 'static, + E: fmt::Display, { type Config = (); type Request = FramedRequest; type Response = (); type Error = Error; type InitError = (); - type Service = FramedRouteService; - type Future = FutureResult; + type Service = FramedRouteService; + type Future = Ready>; fn new_service(&self, _: &()) -> Self::Future { ok(FramedRouteService { @@ -123,35 +122,38 @@ where } } -pub struct FramedRouteService { +pub struct FramedRouteService { handler: F, methods: Vec, - _t: PhantomData<(Io, S, R)>, + _t: PhantomData<(Io, S, R, E)>, } -impl Service for FramedRouteService +impl Service for FramedRouteService where Io: AsyncRead + AsyncWrite + 'static, F: FnMut(FramedRequest) -> R + Clone, - R: IntoFuture, - R::Future: 'static, - R::Error: fmt::Display, + R: Future> + 'static, + E: fmt::Display, { type Request = FramedRequest; type Response = (); type Error = Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result<(), Error>>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, req: FramedRequest) -> Self::Future { - Box::new((self.handler)(req).into_future().then(|res| { + let fut = (self.handler)(req); + + async move { + let res = fut.await; if let Err(e) = res { error!("Error in request handler: {}", e); } Ok(()) - })) + } + .boxed_local() } } diff --git a/actix-framed/src/service.rs b/actix-framed/src/service.rs index fbbc9fbe..ed3a75ff 100644 --- a/actix-framed/src/service.rs +++ b/actix-framed/src/service.rs @@ -1,4 +1,6 @@ use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::body::BodySize; @@ -6,9 +8,9 @@ use actix_http::error::ResponseError; use actix_http::h1::{Codec, Message}; use actix_http::ws::{verify_handshake, HandshakeError}; use actix_http::{Request, Response}; -use actix_service::{NewService, Service}; -use futures::future::{ok, Either, FutureResult}; -use futures::{Async, Future, IntoFuture, Poll, Sink}; +use actix_service::{Service, ServiceFactory}; +use futures::future::{err, ok, Either, Ready}; +use futures::Future; /// Service that verifies incoming request if it is valid websocket /// upgrade request. In case of error returns `HandshakeError` @@ -22,14 +24,14 @@ impl Default for VerifyWebSockets { } } -impl NewService for VerifyWebSockets { +impl ServiceFactory for VerifyWebSockets { type Config = C; type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); type InitError = (); type Service = VerifyWebSockets; - type Future = FutureResult; + type Future = Ready>; fn new_service(&self, _: &C) -> Self::Future { ok(VerifyWebSockets { _t: PhantomData }) @@ -40,16 +42,16 @@ impl Service for VerifyWebSockets { type Request = (Request, Framed); type Response = (Request, Framed); type Error = (HandshakeError, Framed); - type Future = FutureResult; + type Future = Ready>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { match verify_handshake(req.head()) { - Err(e) => Err((e, framed)).into_future(), - Ok(_) => Ok((req, framed)).into_future(), + Err(e) => err((e, framed)), + Ok(_) => ok((req, framed)), } } } @@ -67,9 +69,9 @@ where } } -impl NewService for SendError +impl ServiceFactory for SendError where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, R: 'static, E: ResponseError + 'static, { @@ -79,7 +81,7 @@ where type Error = (E, Framed); type InitError = (); type Service = SendError; - type Future = FutureResult; + type Future = Ready>; fn new_service(&self, _: &C) -> Self::Future { ok(SendError(PhantomData)) @@ -88,25 +90,25 @@ where impl Service for SendError where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + 'static, R: 'static, E: ResponseError + 'static, { type Request = Result)>; type Response = R; type Error = (E, Framed); - type Future = Either)>, SendErrorFut>; + type Future = Either)>>, SendErrorFut>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _: &mut Context) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, req: Result)>) -> Self::Future { match req { - Ok(r) => Either::A(ok(r)), + Ok(r) => Either::Left(ok(r)), Err((e, framed)) => { let res = e.error_response().drop_body(); - Either::B(SendErrorFut { + Either::Right(SendErrorFut { framed: Some(framed), res: Some((res, BodySize::Empty).into()), err: Some(e), @@ -117,6 +119,7 @@ where } } +#[pin_project::pin_project] pub struct SendErrorFut { res: Option, BodySize)>>, framed: Option>, @@ -127,23 +130,27 @@ pub struct SendErrorFut { impl Future for SendErrorFut where E: ResponseError, - T: AsyncRead + AsyncWrite, + T: AsyncRead + AsyncWrite + Unpin, { - type Item = R; - type Error = (E, Framed); + type Output = Result)>; - fn poll(&mut self) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { if let Some(res) = self.res.take() { - if self.framed.as_mut().unwrap().force_send(res).is_err() { - return Err((self.err.take().unwrap(), self.framed.take().unwrap())); + if self.framed.as_mut().unwrap().write(res).is_err() { + return Poll::Ready(Err(( + self.err.take().unwrap(), + self.framed.take().unwrap(), + ))); } } - match self.framed.as_mut().unwrap().poll_complete() { - Ok(Async::Ready(_)) => { - Err((self.err.take().unwrap(), self.framed.take().unwrap())) + match self.framed.as_mut().unwrap().flush(cx) { + Poll::Ready(Ok(_)) => { + Poll::Ready(Err((self.err.take().unwrap(), self.framed.take().unwrap()))) } - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())), + Poll::Ready(Err(_)) => { + Poll::Ready(Err((self.err.take().unwrap(), self.framed.take().unwrap()))) + } + Poll::Pending => Poll::Pending, } } } diff --git a/actix-framed/src/test.rs b/actix-framed/src/test.rs index 3bc828df..b90a493d 100644 --- a/actix-framed/src/test.rs +++ b/actix-framed/src/test.rs @@ -1,4 +1,6 @@ //! Various helpers for Actix applications to use during testing. +use std::future::Future; + use actix_codec::Framed; use actix_http::h1::Codec; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; @@ -6,7 +8,6 @@ use actix_http::http::{HttpTryFrom, Method, Uri, Version}; use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest}; use actix_router::{Path, Url}; use actix_rt::Runtime; -use futures::IntoFuture; use crate::{FramedRequest, State}; @@ -121,10 +122,10 @@ impl TestRequest { pub fn run(self, f: F) -> Result where F: FnOnce(FramedRequest) -> R, - R: IntoFuture, + R: Future>, { let mut rt = Runtime::new().unwrap(); - rt.block_on(f(self.finish()).into_future()) + rt.block_on(f(self.finish())) } } diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs index 00f6a97d..6e4bb6ad 100644 --- a/actix-framed/tests/test_server.rs +++ b/actix-framed/tests/test_server.rs @@ -1,30 +1,31 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response}; -use actix_http_test::TestServer; -use actix_service::{IntoNewService, NewService}; +use actix_http_test::{block_on, TestServer}; +use actix_service::{pipeline_factory, IntoServiceFactory, ServiceFactory}; use actix_utils::framed::FramedTransport; use bytes::{Bytes, BytesMut}; -use futures::future::{self, ok}; -use futures::{Future, Sink, Stream}; +use futures::{future, SinkExt, StreamExt}; use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets}; -fn ws_service( +async fn ws_service( req: FramedRequest, -) -> impl Future { - let (req, framed, _) = req.into_parts(); +) -> Result<(), Error> { + let (req, mut framed, _) = req.into_parts(); let res = ws::handshake(req.head()).unwrap().message_body(()); framed .send((res, body::BodySize::None).into()) - .map_err(|_| panic!()) - .and_then(|framed| { - FramedTransport::new(framed.into_framed(ws::Codec::new()), service) - .map_err(|_| panic!()) - }) + .await + .unwrap(); + FramedTransport::new(framed.into_framed(ws::Codec::new()), service) + .await + .unwrap(); + + Ok(()) } -fn service(msg: ws::Frame) -> impl Future { +async fn service(msg: ws::Frame) -> Result { let msg = match msg { ws::Frame::Ping(msg) => ws::Message::Pong(msg), ws::Frame::Text(text) => { @@ -34,108 +35,129 @@ fn service(msg: ws::Frame) -> impl Future { ws::Frame::Close(reason) => ws::Message::Close(reason), _ => panic!(), }; - ok(msg) + Ok(msg) } #[test] fn test_simple() { - let mut srv = TestServer::new(|| { - HttpService::build() - .upgrade( - FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)), - ) - .finish(|_| future::ok::<_, Error>(Response::NotFound())) - }); + block_on(async { + let mut srv = TestServer::start(|| { + HttpService::build() + .upgrade( + FramedApp::new() + .service(FramedRoute::get("/index.html").to(ws_service)), + ) + .finish(|_| future::ok::<_, Error>(Response::NotFound())) + }); - assert!(srv.ws_at("/test").is_err()); + assert!(srv.ws_at("/test").await.is_err()); - // client service - let framed = srv.ws_at("/index.html").unwrap(); - let framed = srv - .block_on(framed.send(ws::Message::Text("text".to_string()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + // client service + let mut framed = srv.ws_at("/index.html").await.unwrap(); + framed + .send(ws::Message::Text("text".to_string())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Text(Some(BytesMut::from("text"))) + ); - let framed = srv - .block_on(framed.send(ws::Message::Binary("text".into()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!( - item, - Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) - ); + framed + .send(ws::Message::Binary("text".into())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) + ); - let framed = srv - .block_on(framed.send(ws::Message::Ping("text".into()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + framed.send(ws::Message::Ping("text".into())).await.unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Pong("text".to_string().into()) + ); - let framed = srv - .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) - .unwrap(); + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); - let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!( - item, - Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) - ); + let (item, _) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Close(Some(ws::CloseCode::Normal.into())) + ); + }) } #[test] fn test_service() { - let mut srv = TestServer::new(|| { - actix_http::h1::OneRequest::new().map_err(|_| ()).and_then( - VerifyWebSockets::default() - .then(SendError::default()) - .map_err(|_| ()) + block_on(async { + let mut srv = TestServer::start(|| { + pipeline_factory(actix_http::h1::OneRequest::new().map_err(|_| ())).and_then( + pipeline_factory( + pipeline_factory(VerifyWebSockets::default()) + .then(SendError::default()) + .map_err(|_| ()), + ) .and_then( FramedApp::new() .service(FramedRoute::get("/index.html").to(ws_service)) - .into_new_service() + .into_factory() .map_err(|_| ()), ), - ) - }); + ) + }); - // non ws request - let res = srv.block_on(srv.get("/index.html").send()).unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); + // non ws request + let res = srv.get("/index.html").send().await.unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); - // not found - assert!(srv.ws_at("/test").is_err()); + // not found + assert!(srv.ws_at("/test").await.is_err()); - // client service - let framed = srv.ws_at("/index.html").unwrap(); - let framed = srv - .block_on(framed.send(ws::Message::Text("text".to_string()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text"))))); + // client service + let mut framed = srv.ws_at("/index.html").await.unwrap(); + framed + .send(ws::Message::Text("text".to_string())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Text(Some(BytesMut::from("text"))) + ); - let framed = srv - .block_on(framed.send(ws::Message::Binary("text".into()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!( - item, - Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into()))) - ); + framed + .send(ws::Message::Binary("text".into())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) + ); - let framed = srv - .block_on(framed.send(ws::Message::Ping("text".into()))) - .unwrap(); - let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into()))); + framed.send(ws::Message::Ping("text".into())).await.unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Pong("text".to_string().into()) + ); - let framed = srv - .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into())))) - .unwrap(); + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); - let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap(); - assert_eq!( - item, - Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into()))) - ); + let (item, _) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Close(Some(ws::CloseCode::Normal.into())) + ); + }) } From 0de101bc4d05068948a042b35ee18e6f04a61a17 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 13:01:07 +0600 Subject: [PATCH 1616/1635] update actix-web-codegen tests --- Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 10 +- actix-web-codegen/src/lib.rs | 4 +- actix-web-codegen/tests/test_macro.rs | 138 ++++++++++++++------------ 4 files changed, 80 insertions(+), 74 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3efc058d..1af2836f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ members = [ #"actix-session", "actix-identity", #"actix-multipart", - #"actix-web-actors", + "actix-web-actors", "actix-web-codegen", "test-server", ] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 981e0032..f363cfba 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-codegen" -version = "0.1.3" +version = "0.2.0-alpha.1" description = "Actix web proc macros" readme = "README.md" authors = ["Nikolay Kim "] @@ -17,7 +17,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-web = { version = "1.0.0" } -actix-http = { version = "0.2.4", features=["ssl"] } -actix-http-test = { version = "0.2.0", features=["ssl"] } -futures = { version = "0.1" } +actix-web = { version = "2.0.0-alph.a" } +actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } +actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } +futures = { version = "0.3.1" } diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 88fa4dfd..0a727ed6 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -35,8 +35,8 @@ //! use futures::{future, Future}; //! //! #[get("/test")] -//! fn async_test() -> impl Future { -//! future::ok(HttpResponse::Ok().finish()) +//! async fn async_test() -> Result { +//! Ok(HttpResponse::Ok().finish()) //! } //! ``` diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 8ecc81dc..953de9cd 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,5 +1,5 @@ use actix_http::HttpService; -use actix_http_test::TestServer; +use actix_http_test::{block_on, TestServer}; use actix_web::{http, web::Path, App, HttpResponse, Responder}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, trace}; use futures::{future, Future}; @@ -45,12 +45,12 @@ fn trace_test() -> impl Responder { } #[get("/test")] -fn auto_async() -> impl Future { +fn auto_async() -> impl Future> { future::ok(HttpResponse::Ok().finish()) } #[get("/test")] -fn auto_sync() -> impl Future { +fn auto_sync() -> impl Future> { future::ok(HttpResponse::Ok().finish()) } @@ -71,87 +71,93 @@ fn get_param_test(_: Path) -> impl Responder { #[test] fn test_params() { - let mut srv = TestServer::new(|| { - HttpService::new( - App::new() - .service(get_param_test) - .service(put_param_test) - .service(delete_param_test), - ) - }); + block_on(async { + let srv = TestServer::start(|| { + HttpService::new( + App::new() + .service(get_param_test) + .service(put_param_test) + .service(delete_param_test), + ) + }); - let request = srv.request(http::Method::GET, srv.url("/test/it")); - let response = srv.block_on(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::OK); + let request = srv.request(http::Method::GET, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::OK); - let request = srv.request(http::Method::PUT, srv.url("/test/it")); - let response = srv.block_on(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::CREATED); + let request = srv.request(http::Method::PUT, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::CREATED); - let request = srv.request(http::Method::DELETE, srv.url("/test/it")); - let response = srv.block_on(request.send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::NO_CONTENT); + let request = srv.request(http::Method::DELETE, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); + }) } #[test] fn test_body() { - let mut srv = TestServer::new(|| { - HttpService::new( - App::new() - .service(post_test) - .service(put_test) - .service(head_test) - .service(connect_test) - .service(options_test) - .service(trace_test) - .service(patch_test) - .service(test), - ) - }); - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + block_on(async { + let srv = TestServer::start(|| { + HttpService::new( + App::new() + .service(post_test) + .service(put_test) + .service(head_test) + .service(connect_test) + .service(options_test) + .service(trace_test) + .service(patch_test) + .service(test), + ) + }); + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::HEAD, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::HEAD, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::CONNECT, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::CONNECT, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::OPTIONS, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::OPTIONS, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::TRACE, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::TRACE, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::PATCH, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::PATCH, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::PUT, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.status(), http::StatusCode::CREATED); + let request = srv.request(http::Method::PUT, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.status(), http::StatusCode::CREATED); - let request = srv.request(http::Method::POST, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.status(), http::StatusCode::NO_CONTENT); + let request = srv.request(http::Method::POST, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + }) } #[test] fn test_auto_async() { - let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_async))); + block_on(async { + let srv = TestServer::start(|| HttpService::new(App::new().service(auto_async))); - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = srv.block_on(request.send()).unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + }) } From 60ada97b3d6959ee040ffb6bf4ca16de7e9d02dc Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 13:08:22 +0600 Subject: [PATCH 1617/1635] migrate actix-session --- Cargo.toml | 2 +- actix-session/Cargo.toml | 8 +- actix-session/src/cookie.rs | 229 +++++++++++++++++++----------------- actix-session/src/lib.rs | 5 +- 4 files changed, 132 insertions(+), 112 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1af2836f..6c6face7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ members = [ "actix-cors", "actix-files", "actix-framed", - #"actix-session", + "actix-session", "actix-identity", #"actix-multipart", "actix-web-actors", diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index d2fd5ae5..3ce2a8b4 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -24,15 +24,15 @@ default = ["cookie-session"] cookie-session = ["actix-web/secure-cookies"] [dependencies] -actix-web = "1.0.9" -actix-service = "0.4.2" +actix-web = "2.0.0-alpha.1" +actix-service = "1.0.0-alpha.1" bytes = "0.4" derive_more = "0.15.0" -futures = "0.1.24" +futures = "0.3.1" hashbrown = "0.6.3" serde = "1.0" serde_json = "1.0" time = "0.1.42" [dev-dependencies] -actix-rt = "0.2.2" +actix-rt = "1.0.0-alpha.1" diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 19273778..9a486cce 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use std::rc::Rc; +use std::task::{Context, Poll}; use actix_service::{Service, Transform}; use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; @@ -24,8 +25,7 @@ use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::{header::SET_COOKIE, HeaderValue}; use actix_web::{Error, HttpMessage, ResponseError}; use derive_more::{Display, From}; -use futures::future::{ok, Future, FutureResult}; -use futures::Poll; +use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; use serde_json::error::Error as JsonError; use crate::{Session, SessionStatus}; @@ -284,7 +284,7 @@ where type Error = S::Error; type InitError = (); type Transform = CookieSessionMiddleware; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(CookieSessionMiddleware { @@ -309,10 +309,10 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } /// On first request, a new session cookie is returned in response, regardless @@ -325,29 +325,36 @@ where let (is_new, state) = self.inner.load(&req); Session::set_session(state.into_iter(), &mut req); - Box::new(self.service.call(req).map(move |mut res| { - match Session::get_changes(&mut res) { - (SessionStatus::Changed, Some(state)) - | (SessionStatus::Renewed, Some(state)) => { - res.checked_expr(|res| inner.set_cookie(res, state)) - } - (SessionStatus::Unchanged, _) => - // set a new session cookie upon first request (new client) - { - if is_new { - let state: HashMap = HashMap::new(); - res.checked_expr(|res| inner.set_cookie(res, state.into_iter())) - } else { + let fut = self.service.call(req); + + async move { + fut.await.map(|mut res| { + match Session::get_changes(&mut res) { + (SessionStatus::Changed, Some(state)) + | (SessionStatus::Renewed, Some(state)) => { + res.checked_expr(|res| inner.set_cookie(res, state)) + } + (SessionStatus::Unchanged, _) => + // set a new session cookie upon first request (new client) + { + if is_new { + let state: HashMap = HashMap::new(); + res.checked_expr(|res| { + inner.set_cookie(res, state.into_iter()) + }) + } else { + res + } + } + (SessionStatus::Purged, _) => { + let _ = inner.remove_cookie(&mut res); res } + _ => res, } - (SessionStatus::Purged, _) => { - let _ = inner.remove_cookie(&mut res); - res - } - _ => res, - } - })) + }) + } + .boxed_local() } } @@ -359,101 +366,113 @@ mod tests { #[test] fn cookie_session() { - let mut app = test::init_service( - App::new() - .wrap(CookieSession::signed(&[0; 32]).secure(false)) - .service(web::resource("/").to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - })), - ); + test::block_on(async { + let mut app = test::init_service( + App::new() + .wrap(CookieSession::signed(&[0; 32]).secure(false)) + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })), + ) + .await; - let request = test::TestRequest::get().to_request(); - let response = test::block_on(app.call(request)).unwrap(); - assert!(response - .response() - .cookies() - .find(|c| c.name() == "actix-session") - .is_some()); + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + assert!(response + .response() + .cookies() + .find(|c| c.name() == "actix-session") + .is_some()); + }) } #[test] fn private_cookie() { - let mut app = test::init_service( - App::new() - .wrap(CookieSession::private(&[0; 32]).secure(false)) - .service(web::resource("/").to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - })), - ); + test::block_on(async { + let mut app = test::init_service( + App::new() + .wrap(CookieSession::private(&[0; 32]).secure(false)) + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })), + ) + .await; - let request = test::TestRequest::get().to_request(); - let response = test::block_on(app.call(request)).unwrap(); - assert!(response - .response() - .cookies() - .find(|c| c.name() == "actix-session") - .is_some()); + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + assert!(response + .response() + .cookies() + .find(|c| c.name() == "actix-session") + .is_some()); + }) } #[test] fn cookie_session_extractor() { - let mut app = test::init_service( - App::new() - .wrap(CookieSession::signed(&[0; 32]).secure(false)) - .service(web::resource("/").to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - })), - ); + test::block_on(async { + let mut app = test::init_service( + App::new() + .wrap(CookieSession::signed(&[0; 32]).secure(false)) + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })), + ) + .await; - let request = test::TestRequest::get().to_request(); - let response = test::block_on(app.call(request)).unwrap(); - assert!(response - .response() - .cookies() - .find(|c| c.name() == "actix-session") - .is_some()); + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + assert!(response + .response() + .cookies() + .find(|c| c.name() == "actix-session") + .is_some()); + }) } #[test] fn basics() { - let mut app = test::init_service( - App::new() - .wrap( - CookieSession::signed(&[0; 32]) - .path("/test/") - .name("actix-test") - .domain("localhost") - .http_only(true) - .same_site(SameSite::Lax) - .max_age(100), - ) - .service(web::resource("/").to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - })) - .service(web::resource("/test/").to(|ses: Session| { - let val: usize = ses.get("counter").unwrap().unwrap(); - format!("counter: {}", val) - })), - ); + test::block_on(async { + let mut app = test::init_service( + App::new() + .wrap( + CookieSession::signed(&[0; 32]) + .path("/test/") + .name("actix-test") + .domain("localhost") + .http_only(true) + .same_site(SameSite::Lax) + .max_age(100), + ) + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })) + .service(web::resource("/test/").to(|ses: Session| { + let val: usize = ses.get("counter").unwrap().unwrap(); + format!("counter: {}", val) + })), + ) + .await; - let request = test::TestRequest::get().to_request(); - let response = test::block_on(app.call(request)).unwrap(); - let cookie = response - .response() - .cookies() - .find(|c| c.name() == "actix-test") - .unwrap() - .clone(); - assert_eq!(cookie.path().unwrap(), "/test/"); + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + let cookie = response + .response() + .cookies() + .find(|c| c.name() == "actix-test") + .unwrap() + .clone(); + assert_eq!(cookie.path().unwrap(), "/test/"); - let request = test::TestRequest::with_uri("/test/") - .cookie(cookie) - .to_request(); - let body = test::read_response(&mut app, request); - assert_eq!(body, Bytes::from_static(b"counter: 100")); + let request = test::TestRequest::with_uri("/test/") + .cookie(cookie) + .to_request(); + let body = test::read_response(&mut app, request).await; + assert_eq!(body, Bytes::from_static(b"counter: 100")); + }) } } diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 2e9e5171..def35a1e 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -47,6 +47,7 @@ use std::rc::Rc; use actix_web::dev::{Extensions, Payload, ServiceRequest, ServiceResponse}; use actix_web::{Error, FromRequest, HttpMessage, HttpRequest}; +use futures::future::{ok, Ready}; use hashbrown::HashMap; use serde::de::DeserializeOwned; use serde::Serialize; @@ -230,12 +231,12 @@ impl Session { /// ``` impl FromRequest for Session { type Error = Error; - type Future = Result; + type Future = Ready>; type Config = (); #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - Ok(Session::get_session(&mut *req.extensions_mut())) + ok(Session::get_session(&mut *req.extensions_mut())) } } From 471f82f0e0f7ad201708e1bcc3ee6a66b3bb42dd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 14:25:50 +0600 Subject: [PATCH 1618/1635] migrate actix-multipart --- Cargo.toml | 6 +- actix-multipart/Cargo.toml | 11 +- actix-multipart/src/extractor.rs | 29 ++- actix-multipart/src/server.rs | 299 ++++++++++++++++--------------- 4 files changed, 177 insertions(+), 168 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c6face7..6c0f0bc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ members = [ "actix-framed", "actix-session", "actix-identity", - #"actix-multipart", + "actix-multipart", "actix-web-actors", "actix-web-codegen", "test-server", @@ -125,9 +125,9 @@ actix-http = { path = "actix-http" } actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } # actix-web-actors = { path = "actix-web-actors" } -# actix-session = { path = "actix-session" } +actix-session = { path = "actix-session" } actix-files = { path = "actix-files" } -# actix-multipart = { path = "actix-multipart" } +actix-multipart = { path = "actix-multipart" } awc = { path = "awc" } actix-codec = { git = "https://github.com/actix/actix-net.git" } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index aa4e9be2..f5cdc8af 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -18,17 +18,18 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "1.0.9", default-features = false } -actix-service = "0.4.2" +actix-web = { version = "2.0.0-alpha.1", default-features = false } +actix-service = "1.0.0-alpha.1" +actix-utils = "0.5.0-alpha.1" bytes = "0.4" derive_more = "0.15.0" httparse = "1.3" -futures = "0.1.24" +futures = "0.3.1" log = "0.4" mime = "0.3" time = "0.1" twoway = "0.2" [dev-dependencies] -actix-rt = "0.2.2" -actix-http = "0.2.11" \ No newline at end of file +actix-rt = "1.0.0-alpha.1" +actix-http = "0.3.0-alpha.1" \ No newline at end of file diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs index 7274ed09..71c81522 100644 --- a/actix-multipart/src/extractor.rs +++ b/actix-multipart/src/extractor.rs @@ -1,5 +1,6 @@ //! Multipart payload support use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; +use futures::future::{ok, Ready}; use crate::server::Multipart; @@ -10,33 +11,31 @@ use crate::server::Multipart; /// ## Server example /// /// ```rust -/// # use futures::{Future, Stream}; -/// # use futures::future::{ok, result, Either}; +/// use futures::{Stream, StreamExt}; /// use actix_web::{web, HttpResponse, Error}; /// use actix_multipart as mp; /// -/// fn index(payload: mp::Multipart) -> impl Future { -/// payload.from_err() // <- get multipart stream for current request -/// .and_then(|field| { // <- iterate over multipart items +/// async fn index(mut payload: mp::Multipart) -> Result { +/// // iterate over multipart stream +/// while let Some(item) = payload.next().await { +/// let mut field = item?; +/// /// // Field in turn is stream of *Bytes* object -/// field.from_err() -/// .fold((), |_, chunk| { -/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk)); -/// Ok::<_, Error>(()) -/// }) -/// }) -/// .fold((), |_, _| Ok::<_, Error>(())) -/// .map(|_| HttpResponse::Ok().into()) +/// while let Some(chunk) = field.next().await { +/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?)); +/// } +/// } +/// Ok(HttpResponse::Ok().into()) /// } /// # fn main() {} /// ``` impl FromRequest for Multipart { type Error = Error; - type Future = Result; + type Future = Ready>; type Config = (); #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - Ok(Multipart::new(req.headers(), payload.take())) + ok(Multipart::new(req.headers(), payload.take())) } } diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index a7c787f4..dd7852c8 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1,15 +1,17 @@ //! Multipart payload support use std::cell::{Cell, RefCell, RefMut}; use std::marker::PhantomData; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll}; use std::{cmp, fmt}; use bytes::{Bytes, BytesMut}; -use futures::task::{current as current_task, Task}; -use futures::{Async, Poll, Stream}; +use futures::stream::{LocalBoxStream, Stream, StreamExt}; use httparse; use mime; +use actix_utils::task::LocalWaker; use actix_web::error::{ParseError, PayloadError}; use actix_web::http::header::{ self, ContentDisposition, HeaderMap, HeaderName, HeaderValue, @@ -60,7 +62,7 @@ impl Multipart { /// Create multipart instance for boundary. pub fn new(headers: &HeaderMap, stream: S) -> Multipart where - S: Stream + 'static, + S: Stream> + Unpin + 'static, { match Self::boundary(headers) { Ok(boundary) => Multipart { @@ -104,22 +106,25 @@ impl Multipart { } impl Stream for Multipart { - type Item = Field; - type Error = MultipartError; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context, + ) -> Poll> { if let Some(err) = self.error.take() { - Err(err) + Poll::Ready(Some(Err(err))) } else if self.safety.current() { - let mut inner = self.inner.as_mut().unwrap().borrow_mut(); - if let Some(mut payload) = inner.payload.get_mut(&self.safety) { - payload.poll_stream()?; + let this = self.get_mut(); + let mut inner = this.inner.as_mut().unwrap().borrow_mut(); + if let Some(mut payload) = inner.payload.get_mut(&this.safety) { + payload.poll_stream(cx)?; } - inner.poll(&self.safety) + inner.poll(&this.safety, cx) } else if !self.safety.is_clean() { - Err(MultipartError::NotConsumed) + Poll::Ready(Some(Err(MultipartError::NotConsumed))) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -238,9 +243,13 @@ impl InnerMultipart { Ok(Some(eof)) } - fn poll(&mut self, safety: &Safety) -> Poll, MultipartError> { + fn poll( + &mut self, + safety: &Safety, + cx: &mut Context, + ) -> Poll>> { if self.state == InnerState::Eof { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { // release field loop { @@ -249,10 +258,13 @@ impl InnerMultipart { if safety.current() { let stop = match self.item { InnerMultipartItem::Field(ref mut field) => { - match field.borrow_mut().poll(safety)? { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(_)) => continue, - Async::Ready(None) => true, + match field.borrow_mut().poll(safety) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Some(Ok(_))) => continue, + Poll::Ready(Some(Err(e))) => { + return Poll::Ready(Some(Err(e))) + } + Poll::Ready(None) => true, } } InnerMultipartItem::None => false, @@ -277,12 +289,12 @@ impl InnerMultipart { Some(eof) => { if eof { self.state = InnerState::Eof; - return Ok(Async::Ready(None)); + return Poll::Ready(None); } else { self.state = InnerState::Headers; } } - None => return Ok(Async::NotReady), + None => return Poll::Pending, } } // read boundary @@ -291,11 +303,11 @@ impl InnerMultipart { &mut *payload, &self.boundary, )? { - None => return Ok(Async::NotReady), + None => return Poll::Pending, Some(eof) => { if eof { self.state = InnerState::Eof; - return Ok(Async::Ready(None)); + return Poll::Ready(None); } else { self.state = InnerState::Headers; } @@ -311,14 +323,14 @@ impl InnerMultipart { self.state = InnerState::Boundary; headers } else { - return Ok(Async::NotReady); + return Poll::Pending; } } else { unreachable!() } } else { log::debug!("NotReady: field is in flight"); - return Ok(Async::NotReady); + return Poll::Pending; }; // content type @@ -335,7 +347,7 @@ impl InnerMultipart { // nested multipart stream if mt.type_() == mime::MULTIPART { - Err(MultipartError::Nested) + Poll::Ready(Some(Err(MultipartError::Nested))) } else { let field = Rc::new(RefCell::new(InnerField::new( self.payload.clone(), @@ -344,12 +356,7 @@ impl InnerMultipart { )?)); self.item = InnerMultipartItem::Field(Rc::clone(&field)); - Ok(Async::Ready(Some(Field::new( - safety.clone(), - headers, - mt, - field, - )))) + Poll::Ready(Some(Ok(Field::new(safety.clone(cx), headers, mt, field)))) } } } @@ -409,23 +416,21 @@ impl Field { } impl Stream for Field { - type Item = Bytes; - type Error = MultipartError; + type Item = Result; - fn poll(&mut self) -> Poll, Self::Error> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { if self.safety.current() { let mut inner = self.inner.borrow_mut(); if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) { - payload.poll_stream()?; + payload.poll_stream(cx)?; } - inner.poll(&self.safety) } else if !self.safety.is_clean() { - Err(MultipartError::NotConsumed) + Poll::Ready(Some(Err(MultipartError::NotConsumed))) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -482,9 +487,9 @@ impl InnerField { fn read_len( payload: &mut PayloadBuffer, size: &mut u64, - ) -> Poll, MultipartError> { + ) -> Poll>> { if *size == 0 { - Ok(Async::Ready(None)) + Poll::Ready(None) } else { match payload.read_max(*size)? { Some(mut chunk) => { @@ -494,13 +499,13 @@ impl InnerField { if !chunk.is_empty() { payload.unprocessed(chunk); } - Ok(Async::Ready(Some(ch))) + Poll::Ready(Some(Ok(ch))) } None => { if payload.eof && (*size != 0) { - Err(MultipartError::Incomplete) + Poll::Ready(Some(Err(MultipartError::Incomplete))) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -512,15 +517,15 @@ impl InnerField { fn read_stream( payload: &mut PayloadBuffer, boundary: &str, - ) -> Poll, MultipartError> { + ) -> Poll>> { let mut pos = 0; let len = payload.buf.len(); if len == 0 { return if payload.eof { - Err(MultipartError::Incomplete) + Poll::Ready(Some(Err(MultipartError::Incomplete))) } else { - Ok(Async::NotReady) + Poll::Pending }; } @@ -537,10 +542,10 @@ impl InnerField { if let Some(b_len) = b_len { let b_size = boundary.len() + b_len; if len < b_size { - return Ok(Async::NotReady); + return Poll::Pending; } else if &payload.buf[b_len..b_size] == boundary.as_bytes() { // found boundary - return Ok(Async::Ready(None)); + return Poll::Ready(None); } } } @@ -552,9 +557,9 @@ impl InnerField { // check if we have enough data for boundary detection if cur + 4 > len { if cur > 0 { - Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze()))) + Poll::Ready(Some(Ok(payload.buf.split_to(cur).freeze()))) } else { - Ok(Async::NotReady) + Poll::Pending } } else { // check boundary @@ -565,7 +570,7 @@ impl InnerField { { if cur != 0 { // return buffer - Ok(Async::Ready(Some(payload.buf.split_to(cur).freeze()))) + Poll::Ready(Some(Ok(payload.buf.split_to(cur).freeze()))) } else { pos = cur + 1; continue; @@ -577,49 +582,51 @@ impl InnerField { } } } else { - Ok(Async::Ready(Some(payload.buf.take().freeze()))) + Poll::Ready(Some(Ok(payload.buf.take().freeze()))) }; } } - fn poll(&mut self, s: &Safety) -> Poll, MultipartError> { + fn poll(&mut self, s: &Safety) -> Poll>> { if self.payload.is_none() { - return Ok(Async::Ready(None)); + return Poll::Ready(None); } let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) { if !self.eof { let res = if let Some(ref mut len) = self.length { - InnerField::read_len(&mut *payload, len)? + InnerField::read_len(&mut *payload, len) } else { - InnerField::read_stream(&mut *payload, &self.boundary)? + InnerField::read_stream(&mut *payload, &self.boundary) }; match res { - Async::NotReady => return Ok(Async::NotReady), - Async::Ready(Some(bytes)) => return Ok(Async::Ready(Some(bytes))), - Async::Ready(None) => self.eof = true, + Poll::Pending => return Poll::Pending, + Poll::Ready(Some(Ok(bytes))) => return Poll::Ready(Some(Ok(bytes))), + Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))), + Poll::Ready(None) => self.eof = true, } } - match payload.readline()? { - None => Async::Ready(None), - Some(line) => { + match payload.readline() { + Ok(None) => Poll::Ready(None), + Ok(Some(line)) => { if line.as_ref() != b"\r\n" { log::warn!("multipart field did not read all the data or it is malformed"); } - Async::Ready(None) + Poll::Ready(None) } + Err(e) => Poll::Ready(Some(Err(e))), } } else { - Async::NotReady + Poll::Pending }; - if Async::Ready(None) == result { + if let Poll::Ready(None) = result { self.payload.take(); } - Ok(result) + result } } @@ -659,7 +666,7 @@ impl Clone for PayloadRef { /// most task. #[derive(Debug)] struct Safety { - task: Option, + task: LocalWaker, level: usize, payload: Rc>, clean: Rc>, @@ -669,7 +676,7 @@ impl Safety { fn new() -> Safety { let payload = Rc::new(PhantomData); Safety { - task: None, + task: LocalWaker::new(), level: Rc::strong_count(&payload), clean: Rc::new(Cell::new(true)), payload, @@ -683,17 +690,17 @@ impl Safety { fn is_clean(&self) -> bool { self.clean.get() } -} -impl Clone for Safety { - fn clone(&self) -> Safety { + fn clone(&self, cx: &mut Context) -> Safety { let payload = Rc::clone(&self.payload); - Safety { - task: Some(current_task()), + let s = Safety { + task: LocalWaker::new(), level: Rc::strong_count(&payload), clean: self.clean.clone(), payload, - } + }; + s.task.register(cx.waker()); + s } } @@ -704,7 +711,7 @@ impl Drop for Safety { self.clean.set(true); } if let Some(task) = self.task.take() { - task.notify() + task.wake() } } } @@ -713,31 +720,32 @@ impl Drop for Safety { struct PayloadBuffer { eof: bool, buf: BytesMut, - stream: Box>, + stream: LocalBoxStream<'static, Result>, } impl PayloadBuffer { /// Create new `PayloadBuffer` instance fn new(stream: S) -> Self where - S: Stream + 'static, + S: Stream> + 'static, { PayloadBuffer { eof: false, buf: BytesMut::new(), - stream: Box::new(stream), + stream: stream.boxed_local(), } } - fn poll_stream(&mut self) -> Result<(), PayloadError> { + fn poll_stream(&mut self, cx: &mut Context) -> Result<(), PayloadError> { loop { - match self.stream.poll()? { - Async::Ready(Some(data)) => self.buf.extend_from_slice(&data), - Async::Ready(None) => { + match Pin::new(&mut self.stream).poll_next(cx) { + Poll::Ready(Some(Ok(data))) => self.buf.extend_from_slice(&data), + Poll::Ready(Some(Err(e))) => return Err(e), + Poll::Ready(None) => { self.eof = true; return Ok(()); } - Async::NotReady => return Ok(()), + Poll::Pending => return Ok(()), } } } @@ -800,13 +808,14 @@ impl PayloadBuffer { #[cfg(test)] mod tests { - use actix_http::h1::Payload; - use bytes::Bytes; - use futures::unsync::mpsc; - use super::*; + + use actix_http::h1::Payload; + use actix_utils::mpsc; use actix_web::http::header::{DispositionParam, DispositionType}; - use actix_web::test::run_on; + use actix_web::test::block_on; + use bytes::Bytes; + use futures::future::lazy; #[test] fn test_boundary() { @@ -852,12 +861,12 @@ mod tests { } fn create_stream() -> ( - mpsc::UnboundedSender>, - impl Stream, + mpsc::Sender>, + impl Stream>, ) { - let (tx, rx) = mpsc::unbounded(); + let (tx, rx) = mpsc::channel(); - (tx, rx.map_err(|_| panic!()).and_then(|res| res)) + (tx, rx.map(|res| res.map_err(|_| panic!()))) } fn create_simple_request_with_header() -> (Bytes, HeaderMap) { @@ -884,28 +893,28 @@ mod tests { #[test] fn test_multipart_no_end_crlf() { - run_on(|| { + block_on(async { let (sender, payload) = create_stream(); let (bytes, headers) = create_simple_request_with_header(); let bytes_stripped = bytes.slice_to(bytes.len()); // strip crlf - sender.unbounded_send(Ok(bytes_stripped)).unwrap(); + sender.send(Ok(bytes_stripped)).unwrap(); drop(sender); // eof let mut multipart = Multipart::new(&headers, payload); - match multipart.poll().unwrap() { - Async::Ready(Some(_)) => (), + match multipart.next().await.unwrap() { + Ok(_) => (), _ => unreachable!(), } - match multipart.poll().unwrap() { - Async::Ready(Some(_)) => (), + match multipart.next().await.unwrap() { + Ok(_) => (), _ => unreachable!(), } - match multipart.poll().unwrap() { - Async::Ready(None) => (), + match multipart.next().await { + None => (), _ => unreachable!(), } }) @@ -913,15 +922,15 @@ mod tests { #[test] fn test_multipart() { - run_on(|| { + block_on(async { let (sender, payload) = create_stream(); let (bytes, headers) = create_simple_request_with_header(); - sender.unbounded_send(Ok(bytes)).unwrap(); + sender.send(Ok(bytes)).unwrap(); let mut multipart = Multipart::new(&headers, payload); - match multipart.poll().unwrap() { - Async::Ready(Some(mut field)) => { + match multipart.next().await { + Some(Ok(mut field)) => { let cd = field.content_disposition().unwrap(); assert_eq!(cd.disposition, DispositionType::FormData); assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); @@ -929,37 +938,37 @@ mod tests { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); - match field.poll().unwrap() { - Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), + match field.next().await.unwrap() { + Ok(chunk) => assert_eq!(chunk, "test"), _ => unreachable!(), } - match field.poll().unwrap() { - Async::Ready(None) => (), + match field.next().await { + None => (), _ => unreachable!(), } } _ => unreachable!(), } - match multipart.poll().unwrap() { - Async::Ready(Some(mut field)) => { + match multipart.next().await.unwrap() { + Ok(mut field) => { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); - match field.poll() { - Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"), + match field.next().await { + Some(Ok(chunk)) => assert_eq!(chunk, "data"), _ => unreachable!(), } - match field.poll() { - Ok(Async::Ready(None)) => (), + match field.next().await { + None => (), _ => unreachable!(), } } _ => unreachable!(), } - match multipart.poll().unwrap() { - Async::Ready(None) => (), + match multipart.next().await { + None => (), _ => unreachable!(), } }); @@ -967,15 +976,15 @@ mod tests { #[test] fn test_stream() { - run_on(|| { + block_on(async { let (sender, payload) = create_stream(); let (bytes, headers) = create_simple_request_with_header(); - sender.unbounded_send(Ok(bytes)).unwrap(); + sender.send(Ok(bytes)).unwrap(); let mut multipart = Multipart::new(&headers, payload); - match multipart.poll().unwrap() { - Async::Ready(Some(mut field)) => { + match multipart.next().await.unwrap() { + Ok(mut field) => { let cd = field.content_disposition().unwrap(); assert_eq!(cd.disposition, DispositionType::FormData); assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); @@ -983,37 +992,37 @@ mod tests { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); - match field.poll().unwrap() { - Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"), + match field.next().await.unwrap() { + Ok(chunk) => assert_eq!(chunk, "test"), _ => unreachable!(), } - match field.poll().unwrap() { - Async::Ready(None) => (), + match field.next().await { + None => (), _ => unreachable!(), } } _ => unreachable!(), } - match multipart.poll().unwrap() { - Async::Ready(Some(mut field)) => { + match multipart.next().await { + Some(Ok(mut field)) => { assert_eq!(field.content_type().type_(), mime::TEXT); assert_eq!(field.content_type().subtype(), mime::PLAIN); - match field.poll() { - Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "data"), + match field.next().await { + Some(Ok(chunk)) => assert_eq!(chunk, "data"), _ => unreachable!(), } - match field.poll() { - Ok(Async::Ready(None)) => (), + match field.next().await { + None => (), _ => unreachable!(), } } _ => unreachable!(), } - match multipart.poll().unwrap() { - Async::Ready(None) => (), + match multipart.next().await { + None => (), _ => unreachable!(), } }); @@ -1021,26 +1030,26 @@ mod tests { #[test] fn test_basic() { - run_on(|| { + block_on(async { let (_, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(payload.buf.len(), 0); - payload.poll_stream().unwrap(); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); assert_eq!(None, payload.read_max(1).unwrap()); }) } #[test] fn test_eof() { - run_on(|| { + block_on(async { let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(None, payload.read_max(4).unwrap()); sender.feed_data(Bytes::from("data")); sender.feed_eof(); - payload.poll_stream().unwrap(); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap()); assert_eq!(payload.buf.len(), 0); @@ -1051,24 +1060,24 @@ mod tests { #[test] fn test_err() { - run_on(|| { + block_on(async { let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); assert_eq!(None, payload.read_max(1).unwrap()); sender.set_error(PayloadError::Incomplete(None)); - payload.poll_stream().err().unwrap(); + lazy(|cx| payload.poll_stream(cx)).await.err().unwrap(); }) } #[test] fn test_readmax() { - run_on(|| { + block_on(async { let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); - payload.poll_stream().unwrap(); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); assert_eq!(payload.buf.len(), 10); assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap()); @@ -1081,7 +1090,7 @@ mod tests { #[test] fn test_readexactly() { - run_on(|| { + block_on(async { let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); @@ -1089,7 +1098,7 @@ mod tests { sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); - payload.poll_stream().unwrap(); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2)); assert_eq!(payload.buf.len(), 8); @@ -1101,7 +1110,7 @@ mod tests { #[test] fn test_readuntil() { - run_on(|| { + block_on(async { let (mut sender, payload) = Payload::create(false); let mut payload = PayloadBuffer::new(payload); @@ -1109,7 +1118,7 @@ mod tests { sender.feed_data(Bytes::from("line1")); sender.feed_data(Bytes::from("line2")); - payload.poll_stream().unwrap(); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); assert_eq!( Some(Bytes::from("line")), From 55698f252425733a7052fdf50835d5e180bf7d97 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 14:52:33 +0600 Subject: [PATCH 1619/1635] migrade rest of middlewares --- src/middleware/condition.rs | 98 ++++++++++++++++++-------------- src/middleware/errhandlers.rs | 102 +++++++++++++++++++--------------- src/middleware/mod.rs | 10 ++-- src/middleware/normalize.rs | 94 +++++++++++++++++-------------- 4 files changed, 170 insertions(+), 134 deletions(-) diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index ddc5fdd4..6603fc00 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -1,7 +1,8 @@ //! `Middleware` for conditionally enables another middleware. +use std::task::{Context, Poll}; + use actix_service::{Service, Transform}; -use futures::future::{ok, Either, FutureResult, Map}; -use futures::{Future, Poll}; +use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; /// `Middleware` for conditionally enables another middleware. /// The controled middleware must not change the `Service` interfaces. @@ -13,11 +14,11 @@ use futures::{Future, Poll}; /// use actix_web::middleware::{Condition, NormalizePath}; /// use actix_web::App; /// -/// fn main() { -/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into()); -/// let app = App::new() -/// .wrap(Condition::new(enable_normalize, NormalizePath)); -/// } +/// # fn main() { +/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into()); +/// let app = App::new() +/// .wrap(Condition::new(enable_normalize, NormalizePath)); +/// # } /// ``` pub struct Condition { trans: T, @@ -32,29 +33,31 @@ impl Condition { impl Transform for Condition where - S: Service, + S: Service + 'static, T: Transform, + T::Future: 'static, + T::InitError: 'static, + T::Transform: 'static, { type Request = S::Request; type Response = S::Response; type Error = S::Error; type InitError = T::InitError; type Transform = ConditionMiddleware; - type Future = Either< - Map Self::Transform>, - FutureResult, - >; + type Future = LocalBoxFuture<'static, Result>; fn new_transform(&self, service: S) -> Self::Future { if self.enable { - let f = self - .trans - .new_transform(service) - .map(ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform); - Either::A(f) + let f = self.trans.new_transform(service).map(|res| { + res.map( + ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform, + ) + }); + Either::Left(f) } else { - Either::B(ok(ConditionMiddleware::Disable(service))) + Either::Right(ok(ConditionMiddleware::Disable(service))) } + .boxed_local() } } @@ -73,19 +76,19 @@ where type Error = E::Error; type Future = Either; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { use ConditionMiddleware::*; match self { - Enable(service) => service.poll_ready(), - Disable(service) => service.poll_ready(), + Enable(service) => service.poll_ready(cx), + Disable(service) => service.poll_ready(cx), } } fn call(&mut self, req: E::Request) -> Self::Future { use ConditionMiddleware::*; match self { - Enable(service) => Either::A(service.call(req)), - Disable(service) => Either::B(service.call(req)), + Enable(service) => Either::Left(service.call(req)), + Disable(service) => Either::Right(service.call(req)), } } } @@ -99,7 +102,7 @@ mod tests { use crate::error::Result; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; use crate::middleware::errhandlers::*; - use crate::test::{self, TestRequest}; + use crate::test::{self, block_on, TestRequest}; use crate::HttpResponse; fn render_500(mut res: ServiceResponse) -> Result> { @@ -111,33 +114,44 @@ mod tests { #[test] fn test_handler_enabled() { - let srv = |req: ServiceRequest| { - req.into_response(HttpResponse::InternalServerError().finish()) - }; + block_on(async { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::InternalServerError().finish())) + }; - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = - test::block_on(Condition::new(true, mw).new_transform(srv.into_service())) + let mut mw = Condition::new(true, mw) + .new_transform(srv.into_service()) + .await .unwrap(); - let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request()); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + let resp = + test::call_service(&mut mw, TestRequest::default().to_srv_request()) + .await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + }) } + #[test] fn test_handler_disabled() { - let srv = |req: ServiceRequest| { - req.into_response(HttpResponse::InternalServerError().finish()) - }; + block_on(async { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::InternalServerError().finish())) + }; - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = - test::block_on(Condition::new(false, mw).new_transform(srv.into_service())) + let mut mw = Condition::new(false, mw) + .new_transform(srv.into_service()) + .await .unwrap(); - let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request()); - assert_eq!(resp.headers().get(CONTENT_TYPE), None); + let resp = + test::call_service(&mut mw, TestRequest::default().to_srv_request()) + .await; + assert_eq!(resp.headers().get(CONTENT_TYPE), None); + }) } } diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 5f73d4d7..c8a70285 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -1,9 +1,9 @@ //! Custom handlers service for responses. use std::rc::Rc; +use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures::future::{err, ok, Either, Future, FutureResult}; -use futures::Poll; +use futures::future::{err, ok, Either, Future, FutureExt, LocalBoxFuture, Ready}; use hashbrown::HashMap; use crate::dev::{ServiceRequest, ServiceResponse}; @@ -15,7 +15,7 @@ pub enum ErrorHandlerResponse { /// New http response got generated Response(ServiceResponse), /// Result is a future that resolves to a new http response - Future(Box, Error = Error>>), + Future(LocalBoxFuture<'static, Result, Error>>), } type ErrorHandler = dyn Fn(ServiceResponse) -> Result>; @@ -39,17 +39,17 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result { handlers: Rc>>>, @@ -92,7 +92,7 @@ where type Error = Error; type InitError = (); type Transform = ErrorHandlersMiddleware; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(ErrorHandlersMiddleware { @@ -117,26 +117,30 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); + let fut = self.service.call(req); + + async move { + let res = fut.await?; - Box::new(self.service.call(req).and_then(move |res| { if let Some(handler) = handlers.get(&res.status()) { match handler(res) { - Ok(ErrorHandlerResponse::Response(res)) => Either::A(ok(res)), - Ok(ErrorHandlerResponse::Future(fut)) => Either::B(fut), - Err(e) => Either::A(err(e)), + Ok(ErrorHandlerResponse::Response(res)) => Ok(res), + Ok(ErrorHandlerResponse::Future(fut)) => fut.await, + Err(e) => Err(e), } } else { - Either::A(ok(res)) + Ok(res) } - })) + } + .boxed_local() } } @@ -147,7 +151,7 @@ mod tests { use super::*; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::test::{self, TestRequest}; + use crate::test::{self, block_on, TestRequest}; use crate::HttpResponse; fn render_500(mut res: ServiceResponse) -> Result> { @@ -159,19 +163,22 @@ mod tests { #[test] fn test_handler() { - let srv = |req: ServiceRequest| { - req.into_response(HttpResponse::InternalServerError().finish()) - }; + block_on(async { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::InternalServerError().finish())) + }; - let mut mw = test::block_on( - ErrorHandlers::new() + let mut mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) - .new_transform(srv.into_service()), - ) - .unwrap(); + .new_transform(srv.into_service()) + .await + .unwrap(); - let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request()); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + let resp = + test::call_service(&mut mw, TestRequest::default().to_srv_request()) + .await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + }) } fn render_500_async( @@ -180,23 +187,26 @@ mod tests { res.response_mut() .headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(ErrorHandlerResponse::Future(Box::new(ok(res)))) + Ok(ErrorHandlerResponse::Future(ok(res).boxed_local())) } #[test] fn test_handler_async() { - let srv = |req: ServiceRequest| { - req.into_response(HttpResponse::InternalServerError().finish()) - }; + block_on(async { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::InternalServerError().finish())) + }; - let mut mw = test::block_on( - ErrorHandlers::new() + let mut mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) - .new_transform(srv.into_service()), - ) - .unwrap(); + .new_transform(srv.into_service()) + .await + .unwrap(); - let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request()); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + let resp = + test::call_service(&mut mw, TestRequest::default().to_srv_request()) + .await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + }) } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 30acad15..84e0758b 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -2,13 +2,13 @@ mod compress; pub use self::compress::{BodyEncoding, Compress}; -//mod condition; +mod condition; mod defaultheaders; -//pub mod errhandlers; +pub mod errhandlers; mod logger; -//mod normalize; +mod normalize; -//pub use self::condition::Condition; +pub use self::condition::Condition; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; -//pub use self::normalize::NormalizePath; +pub use self::normalize::NormalizePath; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 9cfbefb3..b7eb1384 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,9 +1,10 @@ //! `Middleware` to normalize request's URI +use std::task::{Context, Poll}; use actix_http::http::{HttpTryFrom, PathAndQuery, Uri}; use actix_service::{Service, Transform}; use bytes::Bytes; -use futures::future::{self, FutureResult}; +use futures::future::{ok, Ready}; use regex::Regex; use crate::service::{ServiceRequest, ServiceResponse}; @@ -19,15 +20,15 @@ use crate::Error; /// ```rust /// use actix_web::{web, http, middleware, App, HttpResponse}; /// -/// fn main() { -/// let app = App::new() -/// .wrap(middleware::NormalizePath) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } +/// # fn main() { +/// let app = App::new() +/// .wrap(middleware::NormalizePath) +/// .service( +/// web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// # } /// ``` pub struct NormalizePath; @@ -42,10 +43,10 @@ where type Error = Error; type InitError = (); type Transform = NormalizePathNormalization; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - future::ok(NormalizePathNormalization { + ok(NormalizePathNormalization { service, merge_slash: Regex::new("//+").unwrap(), }) @@ -67,8 +68,8 @@ where type Error = Error; type Future = S::Future; - fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> { - self.service.poll_ready() + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.service.poll_ready(cx) } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { @@ -109,46 +110,57 @@ mod tests { #[test] fn test_wrap() { - let mut app = init_service( - App::new() - .wrap(NormalizePath::default()) - .service(web::resource("/v1/something/").to(|| HttpResponse::Ok())), - ); + block_on(async { + let mut app = init_service( + App::new() + .wrap(NormalizePath::default()) + .service(web::resource("/v1/something/").to(|| HttpResponse::Ok())), + ) + .await; - let req = TestRequest::with_uri("/v1//something////").to_request(); - let res = call_service(&mut app, req); - assert!(res.status().is_success()); + let req = TestRequest::with_uri("/v1//something////").to_request(); + let res = call_service(&mut app, req).await; + assert!(res.status().is_success()); + }) } #[test] fn test_in_place_normalization() { - let srv = |req: ServiceRequest| { - assert_eq!("/v1/something/", req.path()); - req.into_response(HttpResponse::Ok().finish()) - }; + block_on(async { + let srv = |req: ServiceRequest| { + assert_eq!("/v1/something/", req.path()); + ok(req.into_response(HttpResponse::Ok().finish())) + }; - let mut normalize = - block_on(NormalizePath.new_transform(srv.into_service())).unwrap(); + let mut normalize = NormalizePath + .new_transform(srv.into_service()) + .await + .unwrap(); - let req = TestRequest::with_uri("/v1//something////").to_srv_request(); - let res = block_on(normalize.call(req)).unwrap(); - assert!(res.status().is_success()); + let req = TestRequest::with_uri("/v1//something////").to_srv_request(); + let res = normalize.call(req).await.unwrap(); + assert!(res.status().is_success()); + }) } #[test] fn should_normalize_nothing() { - const URI: &str = "/v1/something/"; + block_on(async { + const URI: &str = "/v1/something/"; - let srv = |req: ServiceRequest| { - assert_eq!(URI, req.path()); - req.into_response(HttpResponse::Ok().finish()) - }; + let srv = |req: ServiceRequest| { + assert_eq!(URI, req.path()); + ok(req.into_response(HttpResponse::Ok().finish())) + }; - let mut normalize = - block_on(NormalizePath.new_transform(srv.into_service())).unwrap(); + let mut normalize = NormalizePath + .new_transform(srv.into_service()) + .await + .unwrap(); - let req = TestRequest::with_uri(URI).to_srv_request(); - let res = block_on(normalize.call(req)).unwrap(); - assert!(res.status().is_success()); + let req = TestRequest::with_uri(URI).to_srv_request(); + let res = normalize.call(req).await.unwrap(); + assert!(res.status().is_success()); + }) } } From 53c5151692978ecf21dda31b5c93cb1469e5c36f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 15:01:34 +0600 Subject: [PATCH 1620/1635] use response instead of result for asyn c handlers --- examples/basic.rs | 6 ++--- src/handler.rs | 63 +++++++++++++++++++---------------------------- src/resource.rs | 9 +++---- src/route.rs | 15 ++++++----- src/web.rs | 9 +++---- 5 files changed, 43 insertions(+), 59 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 76c97732..d25db789 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -8,9 +8,9 @@ fn index(req: HttpRequest, name: web::Path) -> String { format!("Hello: {}!\r\n", name) } -async fn index_async(req: HttpRequest) -> Result<&'static str, Error> { +async fn index_async(req: HttpRequest) -> &'static str { println!("REQ: {:?}", req); - Ok("Hello world!\r\n") + "Hello world!\r\n" } #[get("/")] @@ -26,7 +26,7 @@ fn main() -> std::io::Result<()> { App::new() .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .wrap(middleware::Compress::default()) - // .wrap(middleware::Logger::default()) + .wrap(middleware::Logger::default()) .service(index) .service(no_params) .service( diff --git a/src/handler.rs b/src/handler.rs index 7f5d5294..767f630d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -119,21 +119,19 @@ impl Future for HandlerServiceResponse { } /// Async handler converter factory -pub trait AsyncFactory: Clone + 'static +pub trait AsyncFactory: Clone + 'static where - R: Future>, + R: Future, O: Responder, - E: Into, { fn call(&self, param: T) -> R; } -impl AsyncFactory<(), R, O, E> for F +impl AsyncFactory<(), R, O> for F where F: Fn() -> R + Clone + 'static, - R: Future>, + R: Future, O: Responder, - E: Into, { fn call(&self, _: ()) -> R { (self)() @@ -141,23 +139,21 @@ where } #[doc(hidden)] -pub struct AsyncHandler +pub struct AsyncHandler where - F: AsyncFactory, - R: Future>, + F: AsyncFactory, + R: Future, O: Responder, - E: Into, { hnd: F, - _t: PhantomData<(T, R, O, E)>, + _t: PhantomData<(T, R, O)>, } -impl AsyncHandler +impl AsyncHandler where - F: AsyncFactory, - R: Future>, + F: AsyncFactory, + R: Future, O: Responder, - E: Into, { pub fn new(hnd: F) -> Self { AsyncHandler { @@ -167,12 +163,11 @@ where } } -impl Clone for AsyncHandler +impl Clone for AsyncHandler where - F: AsyncFactory, - R: Future>, + F: AsyncFactory, + R: Future, O: Responder, - E: Into, { fn clone(&self) -> Self { AsyncHandler { @@ -182,17 +177,16 @@ where } } -impl Service for AsyncHandler +impl Service for AsyncHandler where - F: AsyncFactory, - R: Future>, + F: AsyncFactory, + R: Future, O: Responder, - E: Into, { type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Infallible; - type Future = AsyncHandlerServiceResponse; + type Future = AsyncHandlerServiceResponse; fn poll_ready(&mut self, _: &mut Context) -> Poll> { Poll::Ready(Ok(())) @@ -209,11 +203,10 @@ where #[doc(hidden)] #[pin_project] -pub struct AsyncHandlerServiceResponse +pub struct AsyncHandlerServiceResponse where - T: Future>, + T: Future, R: Responder, - E: Into, { #[pin] fut: T, @@ -222,11 +215,10 @@ where req: Option, } -impl Future for AsyncHandlerServiceResponse +impl Future for AsyncHandlerServiceResponse where - T: Future>, + T: Future, R: Responder, - E: Into, { type Output = Result; @@ -247,16 +239,12 @@ where } match this.fut.poll(cx) { - Poll::Ready(Ok(res)) => { + Poll::Ready(res) => { let fut = res.respond_to(this.req.as_ref().unwrap()); self.as_mut().project().fut2.set(Some(fut)); self.poll(cx) } Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) - } } } } @@ -387,11 +375,10 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { } } - impl AsyncFactory<($($T,)+), Res, O, E1> for Func + impl AsyncFactory<($($T,)+), Res, O> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, - Res: Future>, + Res: Future, O: Responder, - E1: Into, { fn call(&self, param: ($($T,)+)) -> Res { (self)($(param.$n,)+) diff --git a/src/resource.rs b/src/resource.rs index 553d4156..904bc124 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -264,13 +264,12 @@ where /// App::new().service(web::resource("/").route(web::route().to_async(index))); /// ``` #[allow(clippy::wrong_self_convention)] - pub fn to_async(mut self, handler: F) -> Self + pub fn to_async(mut self, handler: F) -> Self where - F: AsyncFactory, + F: AsyncFactory, I: FromRequest + 'static, - R: Future> + 'static, - O: Responder + 'static, - E: Into + 'static, + R: Future + 'static, + U: Responder + 'static, { self.routes.push(Route::new().to_async(handler)); self diff --git a/src/route.rs b/src/route.rs index fb46dbfd..9b2c4390 100644 --- a/src/route.rs +++ b/src/route.rs @@ -261,13 +261,12 @@ impl Route { /// } /// ``` #[allow(clippy::wrong_self_convention)] - pub fn to_async(mut self, handler: F) -> Self + pub fn to_async(mut self, handler: F) -> Self where - F: AsyncFactory, + F: AsyncFactory, T: FromRequest + 'static, - R: Future> + 'static, - O: Responder + 'static, - E: Into + 'static, + R: Future + 'static, + U: Responder + 'static, { self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new( handler, @@ -410,7 +409,7 @@ mod tests { .route(web::post().to_async(|| { async { delay_for(Duration::from_millis(100)).await; - Ok::<_, Error>(HttpResponse::Created()) + HttpResponse::Created() } })) .route(web::delete().to_async(|| { @@ -423,9 +422,9 @@ mod tests { .service(web::resource("/json").route(web::get().to_async(|| { async { delay_for(Duration::from_millis(25)).await; - Ok::<_, Error>(web::Json(MyObject { + web::Json(MyObject { name: "test".to_string(), - })) + }) } }))), ) diff --git a/src/web.rs b/src/web.rs index 67cfd51a..3d716dc2 100644 --- a/src/web.rs +++ b/src/web.rs @@ -265,13 +265,12 @@ where /// web::to_async(index)) /// ); /// ``` -pub fn to_async(handler: F) -> Route +pub fn to_async(handler: F) -> Route where - F: AsyncFactory, + F: AsyncFactory, I: FromRequest + 'static, - R: Future> + 'static, - O: Responder + 'static, - E: Into + 'static, + R: Future + 'static, + U: Responder + 'static, { Route::new().to_async(handler) } From 1f0577f8d504e2a1c1f22c6b39acd3edf13d9f67 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 15:56:49 +0600 Subject: [PATCH 1621/1635] cleanup api doc examples --- awc/src/lib.rs | 1 - awc/src/request.rs | 2 -- src/app.rs | 2 -- src/lib.rs | 1 - src/resource.rs | 3 --- src/responder.rs | 1 - src/route.rs | 2 -- src/scope.rs | 1 - src/service.rs | 5 ++--- src/web.rs | 7 ++----- 10 files changed, 4 insertions(+), 21 deletions(-) diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 7bbe4219..d6cea6de 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -52,7 +52,6 @@ use self::connect::{Connect, ConnectorWrapper}; /// An HTTP Client /// /// ```rust -/// # use futures::future::{Future, lazy}; /// use actix_rt::System; /// use awc::Client; /// diff --git a/awc/src/request.rs b/awc/src/request.rs index 5181f190..c6b09e95 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -37,7 +37,6 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; /// builder-like pattern. /// /// ```rust -/// use futures::future::{Future, lazy}; /// use actix_rt::System; /// /// fn main() { @@ -310,7 +309,6 @@ impl ClientRequest { /// /// ```rust /// # use actix_rt::System; - /// # use futures::future::{lazy, Future}; /// fn main() { /// System::new("test").block_on(async { /// awc::Client::new().get("https://www.rust-lang.org") diff --git a/src/app.rs b/src/app.rs index 28825660..4c2b3462 100644 --- a/src/app.rs +++ b/src/app.rs @@ -343,7 +343,6 @@ where /// /// ```rust /// use actix_service::Service; - /// # use futures::Future; /// use actix_web::{middleware, web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// @@ -402,7 +401,6 @@ where /// /// ```rust /// use actix_service::Service; - /// # use futures::Future; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// diff --git a/src/lib.rs b/src/lib.rs index 1ae81505..3cd1f78d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,7 +171,6 @@ pub mod client { //! An HTTP Client //! //! ```rust - //! # use futures::future::{Future, lazy}; //! use actix_rt::System; //! use actix_web::client::Client; //! diff --git a/src/resource.rs b/src/resource.rs index 904bc124..a1c0d396 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -244,7 +244,6 @@ where /// /// ```rust /// use actix_web::*; - /// use futures::future::{ok, Future}; /// /// async fn index(req: HttpRequest) -> Result { /// Ok(HttpResponse::Ok().finish()) @@ -257,7 +256,6 @@ where /// /// ```rust /// # use actix_web::*; - /// # use futures::future::Future; /// # async fn index(req: HttpRequest) -> Result { /// # unimplemented!() /// # } @@ -326,7 +324,6 @@ where /// /// ```rust /// use actix_service::Service; - /// # use futures::Future; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// diff --git a/src/responder.rs b/src/responder.rs index 2bb422b2..3f147172 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -342,7 +342,6 @@ impl Future for CustomResponderFut { /// Combines two different responder types into a single type /// /// ```rust -/// # use futures::future::{ok, Future}; /// use actix_web::{Either, Error, HttpResponse}; /// /// type RegisterResult = Either>; diff --git a/src/route.rs b/src/route.rs index 9b2c4390..51305d84 100644 --- a/src/route.rs +++ b/src/route.rs @@ -238,9 +238,7 @@ impl Route { /// This method has to be used if your handler function returns `impl Future<>` /// /// ```rust - /// # use futures::future::ok; /// use actix_web::{web, App, Error}; - /// use futures::Future; /// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] diff --git a/src/scope.rs b/src/scope.rs index 2e59352d..f5ffe05f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -353,7 +353,6 @@ where /// /// ```rust /// use actix_service::Service; - /// # use futures::Future; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// diff --git a/src/service.rs b/src/service.rs index 9c4e6b4a..39540b06 100644 --- a/src/service.rs +++ b/src/service.rs @@ -449,11 +449,10 @@ impl WebService { /// Add match guard to a web service. /// /// ```rust - /// use futures::future::{ok, Ready}; /// use actix_web::{web, guard, dev, App, Error, HttpResponse}; /// - /// fn index(req: dev::ServiceRequest) -> Ready> { - /// ok(req.into_response(HttpResponse::Ok().finish())) + /// async fn index(req: dev::ServiceRequest) -> Result { + /// Ok(req.into_response(HttpResponse::Ok().finish())) /// } /// /// fn main() { diff --git a/src/web.rs b/src/web.rs index 3d716dc2..099e2662 100644 --- a/src/web.rs +++ b/src/web.rs @@ -254,7 +254,6 @@ where /// Create a new route and add async handler. /// /// ```rust -/// # use futures::future::{ok, Future}; /// use actix_web::{web, App, HttpResponse, Error}; /// /// async fn index() -> Result { @@ -278,12 +277,10 @@ where /// Create raw service for a specific path. /// /// ```rust -/// # extern crate actix_web; -/// use futures::future::{ok, Ready}; /// use actix_web::{dev, web, guard, App, Error, HttpResponse}; /// -/// fn my_service(req: dev::ServiceRequest) -> Ready> { -/// ok(req.into_response(HttpResponse::Ok().finish())) +/// async fn my_service(req: dev::ServiceRequest) -> Result { +/// Ok(req.into_response(HttpResponse::Ok().finish())) /// } /// /// fn main() { From 0b9e3d381b4e1a95a1e4d77732b1ae0d56903aa6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 17:36:18 +0600 Subject: [PATCH 1622/1635] add test with custom connector --- awc/Cargo.toml | 1 + awc/tests/test_client.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e085ea09..4d5fde54 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -63,6 +63,7 @@ rust-tls = { version = "0.16.0", package="rustls", optional = true, features = [ [dev-dependencies] actix-rt = "1.0.0-alpha.1" +actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index bcedaf64..95938030 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -125,9 +125,18 @@ fn test_timeout() { ))) }); + let connector = awc::Connector::new() + .connector(actix_connect::new_connector( + actix_connect::start_default_resolver(), + )) + .timeout(Duration::from_secs(15)) + .finish(); + let client = awc::Client::build() + .connector(connector) .timeout(Duration::from_millis(50)) .finish(); + let request = client.get(srv.url("/")).send(); match request.await { Err(SendRequestError::Timeout) => (), From 8683ba8bb03c27d2839a087f9c24e0791929269f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 21 Nov 2019 21:34:04 +0600 Subject: [PATCH 1623/1635] rename .to_async() to .to() --- Cargo.toml | 2 +- MIGRATION.md | 7 ++ README.md | 2 +- actix-http/src/response.rs | 40 +++++---- actix-web-codegen/src/route.rs | 2 +- examples/basic.rs | 12 ++- examples/uds.rs | 16 ++-- src/app.rs | 12 +-- src/data.rs | 2 +- src/extract.rs | 4 +- src/handler.rs | 146 ++++----------------------------- src/lib.rs | 4 +- src/request.rs | 2 +- src/resource.rs | 63 ++++---------- src/responder.rs | 5 +- src/route.rs | 65 ++++----------- src/scope.rs | 60 ++++++++------ src/test.rs | 76 +++++++++-------- src/types/form.rs | 2 +- src/types/json.rs | 6 +- src/types/path.rs | 10 +-- src/types/payload.rs | 8 +- src/types/query.rs | 6 +- src/web.rs | 36 ++------ tests/test_server.rs | 40 ++++----- 25 files changed, 232 insertions(+), 396 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c0f0bc8..6c9d0348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ actix-service = "1.0.0-alpha.1" actix-utils = "0.5.0-alpha.1" actix-router = "0.1.5" actix-rt = "1.0.0-alpha.1" -actix-web-codegen = "0.1.2" +actix-web-codegen = "0.2.0-alpha.1" actix-http = "0.3.0-alpha.1" actix-server = "0.8.0-alpha.1" actix-server-config = "0.3.0-alpha.1" diff --git a/MIGRATION.md b/MIGRATION.md index 2f0f369a..9709b4f0 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,10 @@ +## 2.0.0 + +* Sync handlers has been removed. `.to_async()` methtod has been renamed to `.to()` + + replace `fn` with `async fn` to convert sync handler to async + + ## 1.0.1 * Cors middleware has been moved to `actix-cors` crate diff --git a/README.md b/README.md index 99b7b176..cee8b73c 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. ```rust use actix_web::{web, App, HttpServer, Responder}; -fn index(info: web::Path<(u32, String)>) -> impl Responder { +async fn index(info: web::Path<(u32, String)>) -> impl Responder { format!("Hello {}! id:{}", info.1, info.0) } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index d05505d8..31876813 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,11 +1,14 @@ //! Http response use std::cell::{Ref, RefMut}; +use std::future::Future; use std::io::Write; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{ok, Ready}; -use futures::Stream; +use futures::stream::Stream; use serde::Serialize; use serde_json; @@ -280,15 +283,20 @@ impl fmt::Debug for Response { } } -// impl IntoFuture for Response { -// type Item = Response; -// type Error = Error; -// type Future = FutureResult; +impl Future for Response { + type Output = Result; -// fn into_future(self) -> Self::Future { -// ok(self) -// } -// } + fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll { + Poll::Ready(Ok(Response { + head: std::mem::replace( + &mut self.head, + BoxedResponseHead::new(StatusCode::OK), + ), + body: self.body.take_body(), + error: self.error.take(), + })) + } +} pub struct CookieIter<'a> { iter: header::GetAll<'a>, @@ -757,15 +765,13 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { } } -// impl IntoFuture for ResponseBuilder { -// type Item = Response; -// type Error = Error; -// type Future = FutureResult; +impl Future for ResponseBuilder { + type Output = Result; -// fn into_future(mut self) -> Self::Future { -// ok(self.finish()) -// } -// } + fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll { + Poll::Ready(Ok(self.finish())) + } +} impl fmt::Debug for ResponseBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index e792a7f0..f8e2496c 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -13,7 +13,7 @@ enum ResourceType { impl ToTokens for ResourceType { fn to_tokens(&self, stream: &mut TokenStream2) { let ident = match self { - ResourceType::Async => "to_async", + ResourceType::Async => "to", ResourceType::Sync => "to", }; let ident = Ident::new(ident, Span::call_site()); diff --git a/examples/basic.rs b/examples/basic.rs index d25db789..6d9a4dcd 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,9 +1,7 @@ -use actix_web::{ - get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, -}; +use actix_web::{get, middleware, web, App, HttpRequest, HttpResponse, HttpServer}; #[get("/resource1/{name}/index.html")] -fn index(req: HttpRequest, name: web::Path) -> String { +async fn index(req: HttpRequest, name: web::Path) -> String { println!("REQ: {:?}", req); format!("Hello: {}!\r\n", name) } @@ -14,7 +12,7 @@ async fn index_async(req: HttpRequest) -> &'static str { } #[get("/")] -fn no_params() -> &'static str { +async fn no_params() -> &'static str { "Hello world!\r\n" } @@ -37,9 +35,9 @@ fn main() -> std::io::Result<()> { .default_service( web::route().to(|| HttpResponse::MethodNotAllowed()), ) - .route(web::get().to_async(index_async)), + .route(web::get().to(index_async)), ) - .service(web::resource("/test1.html").to(|| "Test\r\n")) + .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) }) .bind("127.0.0.1:8080")? .workers(1) diff --git a/examples/uds.rs b/examples/uds.rs index 7da41a2c..fc6a58de 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -3,7 +3,7 @@ use actix_web::{ }; #[get("/resource1/{name}/index.html")] -fn index(req: HttpRequest, name: web::Path) -> String { +async fn index(req: HttpRequest, name: web::Path) -> String { println!("REQ: {:?}", req); format!("Hello: {}!\r\n", name) } @@ -14,11 +14,11 @@ async fn index_async(req: HttpRequest) -> Result<&'static str, Error> { } #[get("/")] -fn no_params() -> &'static str { +async fn no_params() -> &'static str { "Hello world!\r\n" } -#[cfg(feature = "uds")] +#[cfg(unix)] fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init(); @@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> { App::new() .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .wrap(middleware::Compress::default()) - // .wrap(middleware::Logger::default()) + .wrap(middleware::Logger::default()) .service(index) .service(no_params) .service( @@ -36,16 +36,16 @@ fn main() -> std::io::Result<()> { middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), ) .default_service( - web::route().to(|| ok(HttpResponse::MethodNotAllowed())), + web::route().to(|| HttpResponse::MethodNotAllowed()), ) - .route(web::get().to_async(index_async)), + .route(web::get().to(index_async)), ) - .service(web::resource("/test1.html").to(|| "Test\r\n")) + .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) }) .bind_uds("/Users/fafhrd91/uds-test")? .workers(1) .run() } -#[cfg(not(feature = "uds"))] +#[cfg(not(unix))] fn main() {} diff --git a/src/app.rs b/src/app.rs index 4c2b3462..d9ac8c09 100644 --- a/src/app.rs +++ b/src/app.rs @@ -90,7 +90,7 @@ where /// counter: Cell, /// } /// - /// fn index(data: web::Data) { + /// async fn index(data: web::Data) { /// data.counter.set(data.counter.get() + 1); /// } /// @@ -192,7 +192,7 @@ where /// ```rust /// use actix_web::{web, App, HttpResponse}; /// - /// fn index(data: web::Path<(String, String)>) -> &'static str { + /// async fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// @@ -247,7 +247,7 @@ where /// ```rust /// use actix_web::{web, App, HttpResponse}; /// - /// fn index() -> &'static str { + /// async fn index() -> &'static str { /// "Welcome!" /// } /// @@ -302,7 +302,7 @@ where /// ```rust /// use actix_web::{web, App, HttpRequest, HttpResponse, Result}; /// - /// fn index(req: HttpRequest) -> Result { + /// async fn index(req: HttpRequest) -> Result { /// let url = req.url_for("youtube", &["asdlkjqme"])?; /// assert_eq!(url.as_str(), "https://youtube.com/watch/asdlkjqme"); /// Ok(HttpResponse::Ok().into()) @@ -346,7 +346,7 @@ where /// use actix_web::{middleware, web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// - /// fn index() -> &'static str { + /// async fn index() -> &'static str { /// "Welcome!" /// } /// @@ -404,7 +404,7 @@ where /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// - /// fn index() -> &'static str { + /// async fn index() -> &'static str { /// "Welcome!" /// } /// diff --git a/src/data.rs b/src/data.rs index a11175c1..a026946a 100644 --- a/src/data.rs +++ b/src/data.rs @@ -45,7 +45,7 @@ pub(crate) trait DataFactory { /// } /// /// /// Use `Data` extractor to access data in handler. -/// fn index(data: web::Data>) { +/// async fn index(data: web::Data>) { /// let mut data = data.lock().unwrap(); /// data.counter += 1; /// } diff --git a/src/extract.rs b/src/extract.rs index 20a1180e..9c863336 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -75,7 +75,7 @@ pub trait FromRequest: Sized { /// } /// /// /// extract `Thing` from request -/// fn index(supplied_thing: Option) -> String { +/// async fn index(supplied_thing: Option) -> String { /// match supplied_thing { /// // Puns not intended /// Some(thing) => format!("Got something: {:?}", thing), @@ -146,7 +146,7 @@ where /// } /// /// /// extract `Thing` from request -/// fn index(supplied_thing: Result) -> String { +/// async fn index(supplied_thing: Result) -> String { /// match supplied_thing { /// Ok(thing) => format!("Got thing: {:?}", thing), /// Err(e) => format!("Error extracting thing: {}", e) diff --git a/src/handler.rs b/src/handler.rs index 767f630d..a7023422 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -15,111 +15,8 @@ use crate::request::HttpRequest; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; -/// Handler converter factory -pub trait Factory: Clone -where - R: Responder, -{ - fn call(&self, param: T) -> R; -} - -impl Factory<(), R> for F -where - F: Fn() -> R + Clone, - R: Responder, -{ - fn call(&self, _: ()) -> R { - (self)() - } -} - -#[doc(hidden)] -pub struct Handler -where - F: Factory, - R: Responder, -{ - hnd: F, - _t: PhantomData<(T, R)>, -} - -impl Handler -where - F: Factory, - R: Responder, -{ - pub fn new(hnd: F) -> Self { - Handler { - hnd, - _t: PhantomData, - } - } -} - -impl Clone for Handler -where - F: Factory, - R: Responder, -{ - fn clone(&self) -> Self { - Self { - hnd: self.hnd.clone(), - _t: PhantomData, - } - } -} - -impl Service for Handler -where - F: Factory, - R: Responder, -{ - type Request = (T, HttpRequest); - type Response = ServiceResponse; - type Error = Infallible; - type Future = HandlerServiceResponse; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - let fut = self.hnd.call(param).respond_to(&req); - HandlerServiceResponse { - fut, - req: Some(req), - } - } -} - -#[pin_project] -pub struct HandlerServiceResponse { - #[pin] - fut: T::Future, - req: Option, -} - -impl Future for HandlerServiceResponse { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.project(); - - match this.fut.poll(cx) { - Poll::Ready(Ok(res)) => { - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) - } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) - } - } - } -} - /// Async handler converter factory -pub trait AsyncFactory: Clone + 'static +pub trait Factory: Clone + 'static where R: Future, O: Responder, @@ -127,7 +24,7 @@ where fn call(&self, param: T) -> R; } -impl AsyncFactory<(), R, O> for F +impl Factory<(), R, O> for F where F: Fn() -> R + Clone + 'static, R: Future, @@ -139,9 +36,9 @@ where } #[doc(hidden)] -pub struct AsyncHandler +pub struct Handler where - F: AsyncFactory, + F: Factory, R: Future, O: Responder, { @@ -149,51 +46,51 @@ where _t: PhantomData<(T, R, O)>, } -impl AsyncHandler +impl Handler where - F: AsyncFactory, + F: Factory, R: Future, O: Responder, { pub fn new(hnd: F) -> Self { - AsyncHandler { + Handler { hnd, _t: PhantomData, } } } -impl Clone for AsyncHandler +impl Clone for Handler where - F: AsyncFactory, + F: Factory, R: Future, O: Responder, { fn clone(&self) -> Self { - AsyncHandler { + Handler { hnd: self.hnd.clone(), _t: PhantomData, } } } -impl Service for AsyncHandler +impl Service for Handler where - F: AsyncFactory, + F: Factory, R: Future, O: Responder, { type Request = (T, HttpRequest); type Response = ServiceResponse; type Error = Infallible; - type Future = AsyncHandlerServiceResponse; + type Future = HandlerServiceResponse; fn poll_ready(&mut self, _: &mut Context) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - AsyncHandlerServiceResponse { + HandlerServiceResponse { fut: self.hnd.call(param), fut2: None, req: Some(req), @@ -203,7 +100,7 @@ where #[doc(hidden)] #[pin_project] -pub struct AsyncHandlerServiceResponse +pub struct HandlerServiceResponse where T: Future, R: Responder, @@ -215,7 +112,7 @@ where req: Option, } -impl Future for AsyncHandlerServiceResponse +impl Future for HandlerServiceResponse where T: Future, R: Responder, @@ -366,16 +263,7 @@ where /// FromRequest trait impl for tuples macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { - impl Factory<($($T,)+), Res> for Func - where Func: Fn($($T,)+) -> Res + Clone, - Res: Responder, - { - fn call(&self, param: ($($T,)+)) -> Res { - (self)($(param.$n,)+) - } - } - - impl AsyncFactory<($($T,)+), Res, O> for Func + impl Factory<($($T,)+), Res, O> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, Res: Future, O: Responder, diff --git a/src/lib.rs b/src/lib.rs index 3cd1f78d..8063d0d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! use actix_web::{web, App, Responder, HttpServer}; //! # use std::thread; //! -//! fn index(info: web::Path<(String, u32)>) -> impl Responder { +//! async fn index(info: web::Path<(String, u32)>) -> impl Responder { //! format!("Hello {}! id:{}", info.0, info.1) //! } //! @@ -136,7 +136,7 @@ pub mod dev { pub use crate::config::{AppConfig, AppService}; #[doc(hidden)] - pub use crate::handler::{AsyncFactory, Factory}; + pub use crate::handler::Factory; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; pub use crate::service::{ diff --git a/src/request.rs b/src/request.rs index 84744af2..19072fcb 100644 --- a/src/request.rs +++ b/src/request.rs @@ -276,7 +276,7 @@ impl Drop for HttpRequest { /// use serde_derive::Deserialize; /// /// /// extract `Thing` from request -/// fn index(req: HttpRequest) -> String { +/// async fn index(req: HttpRequest) -> String { /// format!("Got thing: {:?}", req) /// } /// diff --git a/src/resource.rs b/src/resource.rs index a1c0d396..a06530d4 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -17,7 +17,7 @@ use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; use crate::guard::Guard; -use crate::handler::{AsyncFactory, Factory}; +use crate::handler::Factory; use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; @@ -98,7 +98,7 @@ where /// ```rust /// use actix_web::{web, guard, App, HttpResponse}; /// - /// fn index(data: web::Path<(String, String)>) -> &'static str { + /// async fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// @@ -156,9 +156,9 @@ where /// .route(web::delete().to(delete_handler)) /// ); /// } - /// # fn get_handler() {} - /// # fn post_handler() {} - /// # fn delete_handler() {} + /// # async fn get_handler() -> impl actix_web::Responder { HttpResponse::Ok() } + /// # async fn post_handler() -> impl actix_web::Responder { HttpResponse::Ok() } + /// # async fn delete_handler() -> impl actix_web::Responder { HttpResponse::Ok() } /// ``` pub fn route(mut self, route: Route) -> Self { self.routes.push(route); @@ -174,7 +174,7 @@ where /// use actix_web::{web, App, FromRequest}; /// /// /// extract text data from request - /// fn index(body: String) -> String { + /// async fn index(body: String) -> String { /// format!("Body {}!", body) /// } /// @@ -230,46 +230,14 @@ where /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Factory + 'static, - I: FromRequest + 'static, - R: Responder + 'static, - { - self.routes.push(Route::new().to(handler)); - self - } - - /// Register a new route and add async handler. - /// - /// ```rust - /// use actix_web::*; - /// - /// async fn index(req: HttpRequest) -> Result { - /// Ok(HttpResponse::Ok().finish()) - /// } - /// - /// App::new().service(web::resource("/").to_async(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # use actix_web::*; - /// # async fn index(req: HttpRequest) -> Result { - /// # unimplemented!() - /// # } - /// App::new().service(web::resource("/").route(web::route().to_async(index))); - /// ``` - #[allow(clippy::wrong_self_convention)] - pub fn to_async(mut self, handler: F) -> Self - where - F: AsyncFactory, + F: Factory, I: FromRequest + 'static, R: Future + 'static, U: Responder + 'static, { - self.routes.push(Route::new().to_async(handler)); + self.routes.push(Route::new().to(handler)); self } @@ -327,7 +295,7 @@ where /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// - /// fn index() -> &'static str { + /// async fn index() -> &'static str { /// "Welcome!" /// } /// @@ -705,17 +673,16 @@ mod tests { } #[test] - fn test_to_async() { + fn test_to() { block_on(async { - let mut srv = init_service(App::new().service( - web::resource("/test").to_async(|| { + let mut srv = + init_service(App::new().service(web::resource("/test").to(|| { async { delay_for(Duration::from_millis(100)).await; Ok::<_, Error>(HttpResponse::Ok()) } - }), - )) - .await; + }))) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); diff --git a/src/responder.rs b/src/responder.rs index 3f147172..b254567d 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -475,9 +475,10 @@ pub(crate) mod tests { let mut srv = init_service( App::new() .service( - web::resource("/none").to(|| -> Option<&'static str> { None }), + web::resource("/none") + .to(|| async { Option::<&'static str>::None }), ) - .service(web::resource("/some").to(|| Some("some"))), + .service(web::resource("/some").to(|| async { Some("some") })), ) .await; diff --git a/src/route.rs b/src/route.rs index 51305d84..3ebfc3f5 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,11 +5,11 @@ use std::task::{Context, Poll}; use actix_http::{http::Method, Error}; use actix_service::{Service, ServiceFactory}; -use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; +use futures::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready}; use crate::extract::FromRequest; use crate::guard::{self, Guard}; -use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; +use crate::handler::{Extract, Factory, Handler}; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -49,7 +49,7 @@ impl Route { pub fn new() -> Route { Route { service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| { - HttpResponse::NotFound() + ready(HttpResponse::NotFound()) })))), guards: Rc::new(Vec::new()), } @@ -187,7 +187,7 @@ impl Route { /// } /// /// /// extract path info using serde - /// fn index(info: web::Path) -> String { + /// async fn index(info: web::Path) -> String { /// format!("Welcome {}!", info.username) /// } /// @@ -212,7 +212,7 @@ impl Route { /// } /// /// /// extract path info using serde - /// fn index(path: web::Path, query: web::Query>, body: web::Json) -> String { + /// async fn index(path: web::Path, query: web::Query>, body: web::Json) -> String { /// format!("Welcome {}!", path.username) /// } /// @@ -223,52 +223,15 @@ impl Route { /// ); /// } /// ``` - pub fn to(mut self, handler: F) -> Route + pub fn to(mut self, handler: F) -> Self where - F: Factory + 'static, - T: FromRequest + 'static, - R: Responder + 'static, - { - self.service = - Box::new(RouteNewService::new(Extract::new(Handler::new(handler)))); - self - } - - /// Set async handler function, use request extractors for parameters. - /// This method has to be used if your handler function returns `impl Future<>` - /// - /// ```rust - /// use actix_web::{web, App, Error}; - /// use serde_derive::Deserialize; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// async fn index(info: web::Path) -> Result<&'static str, Error> { - /// Ok("Hello World!") - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/{username}/index.html") // <- define path parameters - /// .route(web::get().to_async(index)) // <- register async handler - /// ); - /// } - /// ``` - #[allow(clippy::wrong_self_convention)] - pub fn to_async(mut self, handler: F) -> Self - where - F: AsyncFactory, + F: Factory, T: FromRequest + 'static, R: Future + 'static, U: Responder + 'static, { - self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new( - handler, - )))); + self.service = + Box::new(RouteNewService::new(Extract::new(Handler::new(handler)))); self } } @@ -402,22 +365,24 @@ mod tests { web::resource("/test") .route(web::get().to(|| HttpResponse::Ok())) .route(web::put().to(|| { - Err::(error::ErrorBadRequest("err")) + async { + Err::(error::ErrorBadRequest("err")) + } })) - .route(web::post().to_async(|| { + .route(web::post().to(|| { async { delay_for(Duration::from_millis(100)).await; HttpResponse::Created() } })) - .route(web::delete().to_async(|| { + .route(web::delete().to(|| { async { delay_for(Duration::from_millis(100)).await; Err::(error::ErrorBadRequest("err")) } })), ) - .service(web::resource("/json").route(web::get().to_async(|| { + .service(web::resource("/json").route(web::get().to(|| { async { delay_for(Duration::from_millis(25)).await; web::Json(MyObject { diff --git a/src/scope.rs b/src/scope.rs index f5ffe05f..e5c04d71 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -46,7 +46,7 @@ type BoxedResponse = LocalBoxFuture<'static, Result>; /// fn main() { /// let app = App::new().service( /// web::scope("/{project_id}/") -/// .service(web::resource("/path1").to(|| HttpResponse::Ok())) +/// .service(web::resource("/path1").to(|| async { HttpResponse::Ok() })) /// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) /// .service(web::resource("/path3").route(web::head().to(|| HttpResponse::MethodNotAllowed()))) /// ); @@ -101,7 +101,7 @@ where /// ```rust /// use actix_web::{web, guard, App, HttpRequest, HttpResponse}; /// - /// fn index(data: web::Path<(String, String)>) -> &'static str { + /// async fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// @@ -132,7 +132,7 @@ where /// counter: Cell, /// } /// - /// fn index(data: web::Data) { + /// async fn index(data: web::Data) { /// data.counter.set(data.counter.get() + 1); /// } /// @@ -228,7 +228,7 @@ where /// /// struct AppState; /// - /// fn index(req: HttpRequest) -> &'static str { + /// async fn index(req: HttpRequest) -> &'static str { /// "Welcome!" /// } /// @@ -258,7 +258,7 @@ where /// ```rust /// use actix_web::{web, App, HttpResponse}; /// - /// fn index(data: web::Path<(String, String)>) -> &'static str { + /// async fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// @@ -356,7 +356,7 @@ where /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// - /// fn index() -> &'static str { + /// async fn index() -> &'static str { /// "Welcome!" /// } /// @@ -846,8 +846,10 @@ mod tests { let mut srv = init_service(App::new().service(web::scope("/ab-{project}").service( web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) + async move { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + } }), ))) .await; @@ -962,8 +964,12 @@ mod tests { let mut srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project_id}").service(web::resource("/path1").to( |r: HttpRequest| { - HttpResponse::Created() - .body(format!("project: {}", &r.match_info()["project_id"])) + async move { + HttpResponse::Created().body(format!( + "project: {}", + &r.match_info()["project_id"] + )) + } }, )), ))) @@ -989,11 +995,13 @@ mod tests { let mut srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project}").service(web::scope("/{id}").service( web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) + async move { + HttpResponse::Created().body(format!( + "project: {} - {}", + &r.match_info()["project"], + &r.match_info()["id"], + )) + } }), )), ))) @@ -1241,12 +1249,14 @@ mod tests { s.route( "/", web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["xxxxxx"]) - .unwrap() - .as_str() - )) + async move { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["xxxxxx"]) + .unwrap() + .as_str() + )) + } }), ); })); @@ -1267,8 +1277,12 @@ mod tests { let mut srv = init_service(App::new().service(web::scope("/a").service( web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( web::get().to(|req: HttpRequest| { - HttpResponse::Ok() - .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) + async move { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("c", &["12345"]).unwrap() + )) + } }), )), ))) diff --git a/src/test.rs b/src/test.rs index 8cee3bc6..0776b0f1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -55,7 +55,7 @@ pub fn default_service( /// fn test_init_service() { /// let mut app = test::init_service( /// App::new() -/// .service(web::resource("/test").to(|| HttpResponse::Ok())) +/// .service(web::resource("/test").to(|| async { HttpResponse::Ok() })) /// ); /// /// // Create request object @@ -94,14 +94,16 @@ where /// fn test_response() { /// let mut app = test::init_service( /// App::new() -/// .service(web::resource("/test").to(|| HttpResponse::Ok())) -/// ); +/// .service(web::resource("/test").to(|| async { +/// HttpResponse::Ok() +/// })) +/// ).await; /// /// // Create request object /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Call application -/// let resp = test::call_service(&mut app, req); +/// let resp = test::call_service(&mut app, req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` @@ -125,15 +127,17 @@ where /// let mut app = test::init_service( /// App::new().service( /// web::resource("/index.html") -/// .route(web::post().to( -/// || HttpResponse::Ok().body("welcome!"))))); +/// .route(web::post().to(|| async { +/// HttpResponse::Ok().body("welcome!") +/// }))) +/// ).await; /// /// let req = test::TestRequest::post() /// .uri("/index.html") /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let result = test::read_response(&mut app, req); +/// let result = test::read_response(&mut app, req).await; /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } /// ``` @@ -167,15 +171,17 @@ where /// let mut app = test::init_service( /// App::new().service( /// web::resource("/index.html") -/// .route(web::post().to( -/// || HttpResponse::Ok().body("welcome!"))))); +/// .route(web::post().to(|| async { +/// HttpResponse::Ok().body("welcome!") +/// }))) +/// ).await; /// /// let req = test::TestRequest::post() /// .uri("/index.html") /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let resp = test::call_service(&mut app, req); +/// let resp = test::call_service(&mut app, req).await; /// let result = test::read_body(resp); /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } @@ -221,10 +227,11 @@ where /// let mut app = test::init_service( /// App::new().service( /// web::resource("/people") -/// .route(web::post().to(|person: web::Json| { +/// .route(web::post().to(|person: web::Json| async { /// HttpResponse::Ok() /// .json(person.into_inner())}) -/// ))); +/// )) +/// ).await; /// /// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); /// @@ -234,7 +241,7 @@ where /// .set_payload(payload) /// .to_request(); /// -/// let result: Person = test::read_response_json(&mut app, req); +/// let result: Person = test::read_response_json(&mut app, req).await; /// } /// ``` pub async fn read_response_json(app: &mut S, req: Request) -> T @@ -262,7 +269,7 @@ where /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; /// -/// fn index(req: HttpRequest) -> HttpResponse { +/// async fn index(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { /// HttpResponse::Ok().into() /// } else { @@ -275,11 +282,11 @@ where /// let req = test::TestRequest::with_header("content-type", "text/plain") /// .to_http_request(); /// -/// let resp = test::block_on(index(req)).unwrap(); +/// let resp = index(req).await.unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); -/// let resp = test::block_on(index(req)).unwrap(); +/// let resp = index(req).await.unwrap(); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` @@ -535,9 +542,17 @@ mod tests { let mut app = init_service( App::new().service( web::resource("/index.html") - .route(web::put().to(|| HttpResponse::Ok().body("put!"))) - .route(web::patch().to(|| HttpResponse::Ok().body("patch!"))) - .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))), + .route( + web::put().to(|| async { HttpResponse::Ok().body("put!") }), + ) + .route( + web::patch() + .to(|| async { HttpResponse::Ok().body("patch!") }), + ) + .route( + web::delete() + .to(|| async { HttpResponse::Ok().body("delete!") }), + ), ), ) .await; @@ -567,13 +582,11 @@ mod tests { #[test] fn test_response() { block_on(async { - let mut app = init_service( - App::new().service( - web::resource("/index.html") - .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), - ), - ) - .await; + let mut app = + init_service(App::new().service(web::resource("/index.html").route( + web::post().to(|| async { HttpResponse::Ok().body("welcome!") }), + ))) + .await; let req = TestRequest::post() .uri("/index.html") @@ -597,7 +610,7 @@ mod tests { let mut app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Json| { - HttpResponse::Ok().json(person.into_inner()) + async { HttpResponse::Ok().json(person.into_inner()) } }), ))) .await; @@ -621,7 +634,7 @@ mod tests { let mut app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Form| { - HttpResponse::Ok().json(person.into_inner()) + async { HttpResponse::Ok().json(person.into_inner()) } }), ))) .await; @@ -650,7 +663,7 @@ mod tests { let mut app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Json| { - HttpResponse::Ok().json(person.into_inner()) + async { HttpResponse::Ok().json(person.into_inner()) } }), ))) .await; @@ -688,8 +701,7 @@ mod tests { } let mut app = init_service( - App::new() - .service(web::resource("/index.html").to_async(async_with_block)), + App::new().service(web::resource("/index.html").to(async_with_block)), ) .await; @@ -721,7 +733,7 @@ mod tests { // let addr = run_on(|| MyActor.start()); // let mut app = init_service(App::new().service( - // web::resource("/index.html").to_async(move || { + // web::resource("/index.html").to(move || { // addr.send(Num(1)).from_err().and_then(|res| { // if res == 1 { // HttpResponse::Ok() diff --git a/src/types/form.rs b/src/types/form.rs index 694fe6db..c20dc7a0 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -181,7 +181,7 @@ impl Responder for Form { /// /// /// Extract form data using serde. /// /// Custom configuration is used for this handler, max payload size is 4k -/// fn index(form: web::Form) -> Result { +/// async fn index(form: web::Form) -> Result { /// Ok(format!("Welcome {}!", form.username)) /// } /// diff --git a/src/types/json.rs b/src/types/json.rs index 19f8532b..206a4e42 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -46,7 +46,7 @@ use crate::responder::Responder; /// } /// /// /// deserialize `Info` from request's body -/// fn index(info: web::Json) -> String { +/// async fn index(info: web::Json) -> String { /// format!("Welcome {}!", info.username) /// } /// @@ -157,7 +157,7 @@ impl Responder for Json { /// } /// /// /// deserialize `Info` from request's body -/// fn index(info: web::Json) -> String { +/// async fn index(info: web::Json) -> String { /// format!("Welcome {}!", info.username) /// } /// @@ -217,7 +217,7 @@ where /// } /// /// /// deserialize `Info` from request's body, max payload size is 4kb -/// fn index(info: web::Json) -> String { +/// async fn index(info: web::Json) -> String { /// format!("Welcome {}!", info.username) /// } /// diff --git a/src/types/path.rs b/src/types/path.rs index 89b9392b..29a574fe 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -24,7 +24,7 @@ use crate::FromRequest; /// /// extract path info from "/{username}/{count}/index.html" url /// /// {username} - deserializes to a String /// /// {count} - - deserializes to a u32 -/// fn index(info: web::Path<(String, u32)>) -> String { +/// async fn index(info: web::Path<(String, u32)>) -> String { /// format!("Welcome {}! {}", info.0, info.1) /// } /// @@ -49,7 +49,7 @@ use crate::FromRequest; /// } /// /// /// extract `Info` from a path using serde -/// fn index(info: web::Path) -> Result { +/// async fn index(info: web::Path) -> Result { /// Ok(format!("Welcome {}!", info.username)) /// } /// @@ -119,7 +119,7 @@ impl fmt::Display for Path { /// /// extract path info from "/{username}/{count}/index.html" url /// /// {username} - deserializes to a String /// /// {count} - - deserializes to a u32 -/// fn index(info: web::Path<(String, u32)>) -> String { +/// async fn index(info: web::Path<(String, u32)>) -> String { /// format!("Welcome {}! {}", info.0, info.1) /// } /// @@ -144,7 +144,7 @@ impl fmt::Display for Path { /// } /// /// /// extract `Info` from a path using serde -/// fn index(info: web::Path) -> Result { +/// async fn index(info: web::Path) -> Result { /// Ok(format!("Welcome {}!", info.username)) /// } /// @@ -206,7 +206,7 @@ where /// } /// /// // deserialize `Info` from request's path -/// fn index(folder: web::Path) -> String { +/// async fn index(folder: web::Path) -> String { /// format!("Selected folder: {:?}!", folder) /// } /// diff --git a/src/types/payload.rs b/src/types/payload.rs index 61f7328b..ee7e1166 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -40,7 +40,7 @@ use crate::request::HttpRequest; /// fn main() { /// let app = App::new().service( /// web::resource("/index.html").route( -/// web::get().to_async(index)) +/// web::get().to(index)) /// ); /// } /// ``` @@ -88,7 +88,7 @@ impl Stream for Payload { /// fn main() { /// let app = App::new().service( /// web::resource("/index.html").route( -/// web::get().to_async(index)) +/// web::get().to(index)) /// ); /// } /// ``` @@ -117,7 +117,7 @@ impl FromRequest for Payload { /// use actix_web::{web, App}; /// /// /// extract binary data from request -/// fn index(body: Bytes) -> String { +/// async fn index(body: Bytes) -> String { /// format!("Body {:?}!", body) /// } /// @@ -169,7 +169,7 @@ impl FromRequest for Bytes { /// use actix_web::{web, App, FromRequest}; /// /// /// extract text data from request -/// fn index(text: String) -> String { +/// async fn index(text: String) -> String { /// format!("Body {}!", text) /// } /// diff --git a/src/types/query.rs b/src/types/query.rs index 8061d723..e442f1c3 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -40,7 +40,7 @@ use crate::request::HttpRequest; /// // Use `Query` extractor for query information (and destructure it within the signature). /// // This handler gets called only if the request's query string contains a `username` field. /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`. -/// fn index(web::Query(info): web::Query) -> String { +/// async fn index(web::Query(info): web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } /// @@ -118,7 +118,7 @@ impl fmt::Display for Query { /// // Use `Query` extractor for query information. /// // This handler get called only if request's query contains `username` field /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: web::Query) -> String { +/// async fn index(info: web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } /// @@ -179,7 +179,7 @@ where /// } /// /// /// deserialize `Info` from request's querystring -/// fn index(info: web::Query) -> String { +/// async fn index(info: web::Query) -> String { /// format!("Welcome {}!", info.username) /// } /// diff --git a/src/web.rs b/src/web.rs index 099e2662..22630ae8 100644 --- a/src/web.rs +++ b/src/web.rs @@ -8,7 +8,7 @@ pub use futures::channel::oneshot::Canceled; use crate::error::Error; use crate::extract::FromRequest; -use crate::handler::{AsyncFactory, Factory}; +use crate::handler::Factory; use crate::resource::Resource; use crate::responder::Responder; use crate::route::Route; @@ -231,10 +231,10 @@ pub fn method(method: Method) -> Route { /// Create a new route and add handler. /// /// ```rust -/// use actix_web::{web, App, HttpResponse}; +/// use actix_web::{web, App, HttpResponse, Responder}; /// -/// fn index() -> HttpResponse { -/// unimplemented!() +/// async fn index() -> impl Responder { +/// HttpResponse::Ok() /// } /// /// App::new().service( @@ -242,36 +242,14 @@ pub fn method(method: Method) -> Route { /// web::to(index)) /// ); /// ``` -pub fn to(handler: F) -> Route +pub fn to(handler: F) -> Route where - F: Factory + 'static, - I: FromRequest + 'static, - R: Responder + 'static, -{ - Route::new().to(handler) -} - -/// Create a new route and add async handler. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse, Error}; -/// -/// async fn index() -> Result { -/// Ok(HttpResponse::Ok().finish()) -/// } -/// -/// App::new().service(web::resource("/").route( -/// web::to_async(index)) -/// ); -/// ``` -pub fn to_async(handler: F) -> Route -where - F: AsyncFactory, + F: Factory, I: FromRequest + 'static, R: Future + 'static, U: Responder + 'static, { - Route::new().to_async(handler) + Route::new().to(handler) } /// Create raw service for a specific path. diff --git a/tests/test_server.rs b/tests/test_server.rs index eeaedec0..0114b21f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,13 +11,11 @@ use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; use flate2::Compression; -use futures::future::ok; -use futures::stream::once; +use futures::{future::ok, stream::once}; use rand::{distributions::Alphanumeric, Rng}; -use actix_connect::start_default_resolver; use actix_web::middleware::{BodyEncoding, Compress}; -use actix_web::{dev, http, test, web, App, HttpResponse, HttpServer}; +use actix_web::{dev, http, web, App, HttpResponse, HttpServer}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -817,18 +815,19 @@ fn test_brotli_encoding_large() { // } #[cfg(all( - feature = "rust-tls", - feature = "ssl", + feature = "rustls", + feature = "openssl", any(feature = "flate2-zlib", feature = "flate2-rust") ))] #[test] fn test_reading_deflate_encoding_large_random_ssl() { block_on(async { - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, pkcs8_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; + use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use rust_tls::internal::pemfile::{certs, pkcs8_private_keys}; + use rust_tls::{NoClientAuth, ServerConfig}; use std::fs::File; use std::io::BufReader; + use std::sync::mpsc; let addr = TestServer::unused_addr(); let (tx, rx) = mpsc::channel(); @@ -838,7 +837,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { .take(160_000) .collect::(); - thread::spawn(move || { + std::thread::spawn(move || { let sys = actix_rt::System::new("test"); // load ssl keys @@ -851,11 +850,13 @@ fn test_reading_deflate_encoding_large_random_ssl() { let srv = HttpServer::new(|| { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - Ok::<_, Error>( - HttpResponse::Ok() - .encoding(http::ContentEncoding::Identity) - .body(bytes), - ) + async move { + Ok::<_, Error>( + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .body(bytes), + ) + } }))) }) .bind_rustls(addr, config) @@ -866,8 +867,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { let _ = sys.run(); }); let (srv, _sys) = rx.recv().unwrap(); - test::block_on(futures::lazy(|| Ok::<_, ()>(start_default_resolver()))).unwrap(); - let client = test::run_on(|| { + let client = { let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); @@ -880,7 +880,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { .finish(), ) .finish() - }); + }; // encode data let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); @@ -893,11 +893,11 @@ fn test_reading_deflate_encoding_large_random_ssl() { .header(http::header::CONTENT_ENCODING, "deflate") .send_body(enc); - let mut response = test::block_on(req).unwrap(); + let mut response = req.await.unwrap(); assert!(response.status().is_success()); // read response - let bytes = test::block_on(response.body()).unwrap(); + let bytes = response.body().await.unwrap(); assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, Bytes::from(data)); From 512dd2be636ba796ab8e345319e330902449088d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Nov 2019 07:01:05 +0600 Subject: [PATCH 1624/1635] disable rustls support --- Cargo.toml | 2 +- README.md | 3 +-- actix-http/Cargo.toml | 4 ++-- awc/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c9d0348..7524bff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ fail = ["actix-http/fail"] openssl = ["open-ssl", "actix-server/openssl", "awc/openssl"] # rustls -rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"] +# rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"] [dependencies] actix-codec = "0.2.0-alpha.1" diff --git a/README.md b/README.md index cee8b73c..00bb3ec4 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,9 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [User Guide](https://actix.rs/docs/) * [API Documentation (1.0)](https://docs.rs/actix-web/) -* [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.36 or later +* Minimum supported Rust version: 1.39 or later ## Example diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 32af97ad..5bffa0ac 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -29,7 +29,7 @@ default = [] openssl = ["open-ssl", "actix-connect/openssl", "tokio-openssl"] # rustls support -rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] +# rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -107,7 +107,7 @@ webpki-roots = { version = "0.18", optional = true } [dev-dependencies] actix-rt = "1.0.0-alpha.1" -actix-server = { version = "0.8.0-alpha.1", features=["openssl", "rustls"] } +actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } env_logger = "0.6" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 4d5fde54..eed7eabe 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -30,7 +30,7 @@ default = ["brotli", "flate2-zlib"] openssl = ["open-ssl", "actix-http/openssl"] # rustls -rustls = ["rust-tls", "actix-http/rustls"] +# rustls = ["rust-tls", "actix-http/rustls"] # brotli encoding, requires c compiler brotli = ["actix-http/brotli"] From e668acc596334eeda3ebffdcef90467396087d7a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Nov 2019 10:13:32 +0600 Subject: [PATCH 1625/1635] update travis config --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 82db86a6..683f77cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ matrix: include: - rust: stable - rust: beta - - rust: nightly-2019-08-10 + - rust: nightly-2019-11-20 allow_failures: - - rust: nightly-2019-08-10 + - rust: nightly-2019-11-20 env: global: @@ -25,7 +25,7 @@ before_install: - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-08-10" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-20" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin fi @@ -37,8 +37,8 @@ script: - cargo update - cargo check --all --no-default-features - cargo test --all-features --all -- --nocapture - - cd actix-http; cargo test --no-default-features --features="rust-tls" -- --nocapture; cd .. - - cd awc; cargo test --no-default-features --features="rust-tls" -- --nocapture; cd .. + # - cd actix-http; cargo test --no-default-features --features="rustls" -- --nocapture; cd .. + # - cd awc; cargo test --no-default-features --features="rustls" -- --nocapture; cd .. # Upload docs after_success: @@ -51,7 +51,7 @@ after_success: echo "Uploaded documentation" fi - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-08-10" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-20" ]]; then taskset -c 0 cargo tarpaulin --out Xml --all --all-features bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" From 57981ca04add91258c3302bdfb4901772ac5f3e8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 22 Nov 2019 11:49:35 +0600 Subject: [PATCH 1626/1635] update tests to async handlers --- actix-cors/src/lib.rs | 2 +- actix-files/src/lib.rs | 16 +++-- actix-http/src/message.rs | 10 ++- actix-http/src/response.rs | 5 +- actix-identity/src/lib.rs | 16 ++--- actix-session/src/cookie.rs | 30 ++++++--- actix-web-codegen/tests/test_macro.rs | 22 +++---- awc/tests/test_client.rs | 87 +++++++++++++++------------ test-server/src/lib.rs | 2 +- 9 files changed, 111 insertions(+), 79 deletions(-) diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index 40f9fdf9..db7e4cc4 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -11,7 +11,7 @@ //! use actix_cors::Cors; //! use actix_web::{http, web, App, HttpRequest, HttpResponse, HttpServer}; //! -//! fn index(req: HttpRequest) -> &'static str { +//! async fn index(req: HttpRequest) -> &'static str { //! "Hello world" //! } //! diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 72db1695..2f8a5c49 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -1176,9 +1176,11 @@ mod tests { let mut srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| { - NamedFile::open("Cargo.toml") - .unwrap() - .set_content_encoding(header::ContentEncoding::Identity) + async { + NamedFile::open("Cargo.toml") + .unwrap() + .set_content_encoding(header::ContentEncoding::Identity) + } }), )) .await; @@ -1199,9 +1201,11 @@ mod tests { let mut srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| { - NamedFile::open("Cargo.toml") - .unwrap() - .set_content_encoding(header::ContentEncoding::Gzip) + async { + NamedFile::open("Cargo.toml") + .unwrap() + .set_content_encoding(header::ContentEncoding::Gzip) + } }), )) .await; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 316df261..5994ed39 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -388,6 +388,12 @@ impl BoxedResponseHead { pub fn new(status: StatusCode) -> Self { RESPONSE_POOL.with(|p| p.get_message(status)) } + + pub(crate) fn take(&mut self) -> Self { + BoxedResponseHead { + head: self.head.take(), + } + } } impl std::ops::Deref for BoxedResponseHead { @@ -406,7 +412,9 @@ impl std::ops::DerefMut for BoxedResponseHead { impl Drop for BoxedResponseHead { fn drop(&mut self) { - RESPONSE_POOL.with(|p| p.release(self.head.take().unwrap())) + if let Some(head) = self.head.take() { + RESPONSE_POOL.with(move |p| p.release(head)) + } } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 31876813..a5f18cc7 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -288,10 +288,7 @@ impl Future for Response { fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll { Poll::Ready(Ok(Response { - head: std::mem::replace( - &mut self.head, - BoxedResponseHead::new(StatusCode::OK), - ), + head: self.head.take(), body: self.body.take_body(), error: self.error.take(), })) diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 30761d87..2980a775 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -16,7 +16,7 @@ //! use actix_web::*; //! use actix_identity::{Identity, CookieIdentityPolicy, IdentityService}; //! -//! fn index(id: Identity) -> String { +//! async fn index(id: Identity) -> String { //! // access request identity //! if let Some(id) = id.identity() { //! format!("Welcome! {}", id) @@ -25,12 +25,12 @@ //! } //! } //! -//! fn login(id: Identity) -> HttpResponse { +//! async fn login(id: Identity) -> HttpResponse { //! id.remember("User1".to_owned()); // <- remember identity //! HttpResponse::Ok().finish() //! } //! -//! fn logout(id: Identity) -> HttpResponse { +//! async fn logout(id: Identity) -> HttpResponse { //! id.forget(); // <- remove identity //! HttpResponse::Ok().finish() //! } @@ -764,11 +764,13 @@ mod tests { .secure(false) .name(COOKIE_NAME)))) .service(web::resource("/").to(|id: Identity| { - let identity = id.identity(); - if identity.is_none() { - id.remember(COOKIE_LOGIN.to_string()) + async move { + let identity = id.identity(); + if identity.is_none() { + id.remember(COOKIE_LOGIN.to_string()) + } + web::Json(identity) } - web::Json(identity) })), ) .await diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 9a486cce..bb5fba97 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -371,8 +371,10 @@ mod tests { App::new() .wrap(CookieSession::signed(&[0; 32]).secure(false)) .service(web::resource("/").to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" + async move { + let _ = ses.set("counter", 100); + "test" + } })), ) .await; @@ -394,8 +396,10 @@ mod tests { App::new() .wrap(CookieSession::private(&[0; 32]).secure(false)) .service(web::resource("/").to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" + async move { + let _ = ses.set("counter", 100); + "test" + } })), ) .await; @@ -417,8 +421,10 @@ mod tests { App::new() .wrap(CookieSession::signed(&[0; 32]).secure(false)) .service(web::resource("/").to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" + async move { + let _ = ses.set("counter", 100); + "test" + } })), ) .await; @@ -448,12 +454,16 @@ mod tests { .max_age(100), ) .service(web::resource("/").to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" + async move { + let _ = ses.set("counter", 100); + "test" + } })) .service(web::resource("/test/").to(|ses: Session| { - let val: usize = ses.get("counter").unwrap().unwrap(); - format!("counter: {}", val) + async move { + let val: usize = ses.get("counter").unwrap().unwrap(); + format!("counter: {}", val) + } })), ) .await; diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 953de9cd..18c01f37 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -5,42 +5,42 @@ use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, t use futures::{future, Future}; #[get("/test")] -fn test() -> impl Responder { +async fn test() -> impl Responder { HttpResponse::Ok() } #[put("/test")] -fn put_test() -> impl Responder { +async fn put_test() -> impl Responder { HttpResponse::Created() } #[patch("/test")] -fn patch_test() -> impl Responder { +async fn patch_test() -> impl Responder { HttpResponse::Ok() } #[post("/test")] -fn post_test() -> impl Responder { +async fn post_test() -> impl Responder { HttpResponse::NoContent() } #[head("/test")] -fn head_test() -> impl Responder { +async fn head_test() -> impl Responder { HttpResponse::Ok() } #[connect("/test")] -fn connect_test() -> impl Responder { +async fn connect_test() -> impl Responder { HttpResponse::Ok() } #[options("/test")] -fn options_test() -> impl Responder { +async fn options_test() -> impl Responder { HttpResponse::Ok() } #[trace("/test")] -fn trace_test() -> impl Responder { +async fn trace_test() -> impl Responder { HttpResponse::Ok() } @@ -55,17 +55,17 @@ fn auto_sync() -> impl Future> { } #[put("/test/{param}")] -fn put_param_test(_: Path) -> impl Responder { +async fn put_param_test(_: Path) -> impl Responder { HttpResponse::Created() } #[delete("/test/{param}")] -fn delete_param_test(_: Path) -> impl Responder { +async fn delete_param_test(_: Path) -> impl Responder { HttpResponse::NoContent() } #[get("/test/{param}")] -fn get_param_test(_: Path) -> impl Responder { +async fn get_param_test(_: Path) -> impl Responder { HttpResponse::Ok() } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 95938030..9e1948f7 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -115,14 +115,14 @@ fn test_form() { fn test_timeout() { block_on(async { let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route( - web::to_async(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + || { async { tokio_timer::delay_for(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) } - }), - ))) + }, + )))) }); let connector = awc::Connector::new() @@ -149,14 +149,14 @@ fn test_timeout() { fn test_timeout_override() { block_on(async { let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route( - web::to_async(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + || { async { tokio_timer::delay_for(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) } - }), - ))) + }, + )))) }); let client = awc::Client::build() @@ -693,12 +693,9 @@ fn test_client_brotli_encoding() { #[test] fn test_client_cookie_handling() { + use std::io::{Error as IoError, ErrorKind}; + block_on(async { - fn err() -> Error { - use std::io::{Error as IoError, ErrorKind}; - // stub some generic error - Error::from(IoError::from(ErrorKind::NotFound)) - } let cookie1 = Cookie::build("cookie1", "value1").finish(); let cookie2 = Cookie::build("cookie2", "value2") .domain("www.example.org") @@ -717,31 +714,45 @@ fn test_client_cookie_handling() { HttpService::new(App::new().route( "/", web::to(move |req: HttpRequest| { - // Check cookies were sent correctly - req.cookie("cookie1") - .ok_or_else(err) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(err()) - } - }) - .and_then(|()| req.cookie("cookie2").ok_or_else(err)) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(err()) - } - }) - // Send some cookies back - .map(|_| { - HttpResponse::Ok() - .cookie(cookie1.clone()) - .cookie(cookie2.clone()) - .finish() - }) + let cookie1 = cookie1.clone(); + let cookie2 = cookie2.clone(); + + async move { + // Check cookies were sent correctly + let res: Result<(), Error> = req + .cookie("cookie1") + .ok_or(()) + .and_then(|c1| { + if c1.value() == "value1" { + Ok(()) + } else { + Err(()) + } + }) + .and_then(|()| req.cookie("cookie2").ok_or(())) + .and_then(|c2| { + if c2.value() == "value2" { + Ok(()) + } else { + Err(()) + } + }) + .map_err(|_| { + Error::from(IoError::from(ErrorKind::NotFound)) + }); + + if let Err(e) = res { + Err(e) + } else { + // Send some cookies back + Ok::<_, Error>( + HttpResponse::Ok() + .cookie(cookie1) + .cookie(cookie2) + .finish(), + ) + } + } }), )) }); diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 1ec69b10..1911c75d 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -35,7 +35,7 @@ pub use actix_testing::*; /// let mut srv = TestServer::start( /// || HttpService::new( /// App::new().service( -/// web::resource("/").to_async(my_handler)) +/// web::resource("/").to(my_handler)) /// ) /// ); /// From 525c22de157cc379c9944672897654b27c801b7a Mon Sep 17 00:00:00 2001 From: Martell Malone Date: Thu, 21 Nov 2019 23:13:19 -0800 Subject: [PATCH 1627/1635] fix typos from updating to futures 0.3 --- MIGRATION.md | 2 +- actix-web-codegen/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 9709b4f0..675dc61e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,6 @@ ## 2.0.0 -* Sync handlers has been removed. `.to_async()` methtod has been renamed to `.to()` +* Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` replace `fn` with `async fn` to convert sync handler to async diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index f363cfba..5336f60b 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -17,7 +17,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-web = { version = "2.0.0-alph.a" } +actix-web = { version = "2.0.0-alpha.1" } actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } futures = { version = "0.3.1" } From c5907747ad29d82c89b116a82a0aa3c214315fdf Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Tue, 19 Nov 2019 20:05:16 -0800 Subject: [PATCH 1628/1635] Remove implementation of Responder for (). Fixes #1108. Rationale: - In Rust, one can omit a semicolon after a function's final expression to make its value the function's return value. It's common for people to include a semicolon after the last expression by mistake - common enough that the Rust compiler suggests removing the semicolon when there's a type mismatch between the function's signature and body. By implementing Responder for (), Actix makes this common mistake a silent error in handler functions. - Functions returning an empty body should return HTTP status 204 ("No Content"), so the current Responder impl for (), which returns status 200 ("OK"), is not really what one wants anyway. - It's not much of a burden to ask handlers to explicitly return `HttpResponse::Ok()` if that is what they want; all the examples in the documentation do this already. --- CHANGES.md | 7 +++++++ src/app.rs | 5 +++-- src/data.rs | 5 +++-- src/resource.rs | 8 ++++---- src/responder.rs | 13 ------------- src/scope.rs | 5 +++-- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bb17a7ef..3d4b2d78 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [2.0.0-alpha.2] - 2019-xx-xx + +### Changed + +* Remove implementation of `Responder` for `()`. (#1167) + + ## [1.0.9] - 2019-11-14 ### Added diff --git a/src/app.rs b/src/app.rs index d9ac8c09..a9dc3f29 100644 --- a/src/app.rs +++ b/src/app.rs @@ -84,14 +84,15 @@ where /// /// ```rust /// use std::cell::Cell; - /// use actix_web::{web, App}; + /// use actix_web::{web, App, HttpResponse, Responder}; /// /// struct MyData { /// counter: Cell, /// } /// - /// async fn index(data: web::Data) { + /// async fn index(data: web::Data) -> impl Responder { /// data.counter.set(data.counter.get() + 1); + /// HttpResponse::Ok() /// } /// /// fn main() { diff --git a/src/data.rs b/src/data.rs index a026946a..5ace3a8f 100644 --- a/src/data.rs +++ b/src/data.rs @@ -38,16 +38,17 @@ pub(crate) trait DataFactory { /// /// ```rust /// use std::sync::Mutex; -/// use actix_web::{web, App}; +/// use actix_web::{web, App, HttpResponse, Responder}; /// /// struct MyData { /// counter: usize, /// } /// /// /// Use `Data` extractor to access data in handler. -/// async fn index(data: web::Data>) { +/// async fn index(data: web::Data>) -> impl Responder { /// let mut data = data.lock().unwrap(); /// data.counter += 1; +/// HttpResponse::Ok() /// } /// /// fn main() { diff --git a/src/resource.rs b/src/resource.rs index a06530d4..758e2f28 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -146,7 +146,7 @@ where /// match guards for route selection. /// /// ```rust - /// use actix_web::{web, guard, App, HttpResponse}; + /// use actix_web::{web, guard, App}; /// /// fn main() { /// let app = App::new().service( @@ -156,9 +156,9 @@ where /// .route(web::delete().to(delete_handler)) /// ); /// } - /// # async fn get_handler() -> impl actix_web::Responder { HttpResponse::Ok() } - /// # async fn post_handler() -> impl actix_web::Responder { HttpResponse::Ok() } - /// # async fn delete_handler() -> impl actix_web::Responder { HttpResponse::Ok() } + /// # async fn get_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } + /// # async fn post_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } + /// # async fn delete_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } /// ``` pub fn route(mut self, route: Route) -> Self { self.routes.push(route); diff --git a/src/responder.rs b/src/responder.rs index b254567d..fd86bb68 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -131,15 +131,6 @@ impl Responder for ResponseBuilder { } } -impl Responder for () { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK).finish()) - } -} - impl Responder for (T, StatusCode) where T: Responder, @@ -530,10 +521,6 @@ pub(crate) mod tests { block_on(async { let req = TestRequest::default().to_http_request(); - let resp: HttpResponse = ().respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(*resp.body().body(), Body::Empty); - let resp: HttpResponse = "test".respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); diff --git a/src/scope.rs b/src/scope.rs index e5c04d71..9bec0a1f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -126,14 +126,15 @@ where /// /// ```rust /// use std::cell::Cell; - /// use actix_web::{web, App}; + /// use actix_web::{web, App, HttpResponse, Responder}; /// /// struct MyData { /// counter: Cell, /// } /// - /// async fn index(data: web::Data) { + /// async fn index(data: web::Data) -> impl Responder { /// data.counter.set(data.counter.get() + 1); + /// HttpResponse::Ok() /// } /// /// fn main() { From c1c44a7dd69137d99fb05b6aca370eb26fb7ebe0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Nov 2019 17:59:14 +0600 Subject: [PATCH 1629/1635] upgrade derive_more --- CHANGES.md | 4 +++- Cargo.toml | 4 +++- actix-cors/Cargo.toml | 2 +- actix-files/Cargo.toml | 2 +- actix-http/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-session/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 6 +++--- awc/Cargo.toml | 2 +- 9 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3d4b2d78..a7569862 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,11 @@ # Changes -## [2.0.0-alpha.2] - 2019-xx-xx +## [2.0.0-alpha.1] - 2019-11-22 ### Changed +* Migrated to `std::future` + * Remove implementation of `Responder` for `()`. (#1167) diff --git a/Cargo.toml b/Cargo.toml index 7524bff7..dda01b48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ actix-threadpool = "0.2.0-alpha.1" awc = { version = "0.3.0-alpha.1", optional = true } bytes = "0.4" -derive_more = "0.15.0" +derive_more = "0.99.2" encoding_rs = "0.8" futures = "0.3.1" hashbrown = "0.6.3" @@ -125,6 +125,8 @@ actix-http = { path = "actix-http" } actix-http-test = { path = "test-server" } actix-web-codegen = { path = "actix-web-codegen" } # actix-web-actors = { path = "actix-web-actors" } +actix-cors = { path = "actix-cors" } +actix-identity = { path = "actix-identity" } actix-session = { path = "actix-session" } actix-files = { path = "actix-files" } actix-multipart = { path = "actix-multipart" } diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml index 57aa5833..11ad1033 100644 --- a/actix-cors/Cargo.toml +++ b/actix-cors/Cargo.toml @@ -19,5 +19,5 @@ path = "src/lib.rs" [dependencies] actix-web = "2.0.0-alpha.1" actix-service = "1.0.0-alpha.1" -derive_more = "0.15.0" +derive_more = "0.99.2" futures = "0.3.1" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6e33bb41..f5318b72 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -24,7 +24,7 @@ actix-service = "1.0.0-alpha.1" bitflags = "1" bytes = "0.4" futures = "0.3.1" -derive_more = "0.15.0" +derive_more = "0.99.2" log = "0.4" mime = "0.3" mime_guess = "2.0.1" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 5bffa0ac..cf390e79 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -59,7 +59,7 @@ bitflags = "1.0" bytes = "0.4" copyless = "0.1.4" chrono = "0.4.6" -derive_more = "0.15.0" +derive_more = "0.99.2" either = "1.5.2" encoding_rs = "0.8" futures = "0.3.1" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index f5cdc8af..52b33d58 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -22,7 +22,7 @@ actix-web = { version = "2.0.0-alpha.1", default-features = false } actix-service = "1.0.0-alpha.1" actix-utils = "0.5.0-alpha.1" bytes = "0.4" -derive_more = "0.15.0" +derive_more = "0.99.2" httparse = "1.3" futures = "0.3.1" log = "0.4" diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 3ce2a8b4..a4c53e56 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -27,7 +27,7 @@ cookie-session = ["actix-web/secure-cookies"] actix-web = "2.0.0-alpha.1" actix-service = "1.0.0-alpha.1" bytes = "0.4" -derive_more = "0.15.0" +derive_more = "0.99.2" futures = "0.3.1" hashbrown = "0.6.3" serde = "1.0" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 5336f60b..0aa81e47 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -12,9 +12,9 @@ workspace = ".." proc-macro = true [dependencies] -quote = "1" -syn = { version = "1", features = ["full", "parsing"] } -proc-macro2 = "1" +quote = "^1" +syn = { version = "^1", features = ["full", "parsing"] } +proc-macro2 = "^1" [dev-dependencies] actix-web = { version = "2.0.0-alpha.1" } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index eed7eabe..0f338b40 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -48,7 +48,7 @@ actix-http = "0.3.0-alpha.1" base64 = "0.10.1" bytes = "0.4" -derive_more = "0.15.0" +derive_more = "0.99.2" futures = "0.3.1" log =" 0.4" mime = "0.3" From 4dc31aac93977572f062a43e5a38c23160ea2d5d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Nov 2019 11:25:50 +0600 Subject: [PATCH 1630/1635] use actix_rt::test for test setup --- .travis.yml | 11 +- Cargo.toml | 15 +- actix-cors/Cargo.toml | 3 + actix-cors/src/lib.rs | 652 ++++++------ actix-files/Cargo.toml | 1 + actix-files/src/lib.rs | 1180 +++++++++++----------- actix-framed/src/test.rs | 6 +- actix-framed/tests/test_server.rs | 227 +++-- actix-http/Cargo.toml | 10 +- actix-http/src/body.rs | 65 +- actix-http/src/client/connector.rs | 8 +- actix-http/src/client/h1proto.rs | 5 +- actix-http/src/client/h2proto.rs | 5 +- actix-http/src/client/pool.rs | 25 +- actix-http/src/config.rs | 37 +- actix-http/src/cookie/secure/key.rs | 1 - actix-http/src/error.rs | 4 - actix-http/src/h1/decoder.rs | 4 +- actix-http/src/h1/dispatcher.rs | 17 +- actix-http/src/h1/expect.rs | 2 - actix-http/src/h1/payload.rs | 28 +- actix-http/src/h1/service.rs | 2 +- actix-http/src/h1/upgrade.rs | 2 - actix-http/src/h1/utils.rs | 1 - actix-http/src/h2/dispatcher.rs | 4 +- actix-http/src/lib.rs | 3 +- actix-http/src/payload.rs | 1 - actix-http/src/response.rs | 1 - actix-http/src/service.rs | 4 +- actix-http/src/test.rs | 2 +- actix-http/tests/test_client.rs | 95 +- actix-http/tests/test_openssl.rs | 739 +++++++------- actix-http/tests/test_rustls.rs | 641 ++++++------ actix-http/tests/test_server.rs | 1043 +++++++++---------- actix-http/tests/test_ws.rs | 88 +- actix-identity/src/lib.rs | 611 ++++++------ actix-multipart/src/server.rs | 375 ++++--- actix-session/src/cookie.rs | 210 ++-- actix-web-codegen/Cargo.toml | 1 + actix-web-codegen/tests/test_macro.rs | 146 ++- awc/Cargo.toml | 6 +- awc/src/lib.rs | 21 +- awc/src/request.rs | 57 +- awc/src/response.rs | 146 ++- awc/src/sender.rs | 2 +- awc/src/ws.rs | 74 +- awc/tests/test_client.rs | 1057 ++++++++++---------- awc/tests/test_rustls_client.rs | 93 +- awc/tests/test_ssl_client.rs | 91 +- awc/tests/test_ws.rs | 92 +- src/app.rs | 386 ++++--- src/app_service.rs | 36 +- src/config.rs | 128 ++- src/data.rs | 153 ++- src/extract.rs | 30 +- src/lib.rs | 25 +- src/middleware/condition.rs | 66 +- src/middleware/defaultheaders.rs | 83 +- src/middleware/errhandlers.rs | 62 +- src/middleware/logger.rs | 22 +- src/middleware/normalize.rs | 84 +- src/request.rs | 122 ++- src/resource.rs | 357 +++---- src/responder.rs | 308 +++--- src/route.rs | 141 ++- src/scope.rs | 1103 ++++++++++---------- src/service.rs | 54 +- src/test.rs | 332 +++---- src/types/form.rs | 186 ++-- src/types/json.rs | 402 ++++---- src/types/mod.rs | 1 + src/types/path.rs | 177 ++-- src/types/payload.rs | 30 +- src/types/query.rs | 74 +- src/types/readlines.rs | 38 +- src/web.rs | 1 - test-server/Cargo.toml | 2 - test-server/src/lib.rs | 25 +- tests/test_httpserver.rs | 69 +- tests/test_server.rs | 1328 ++++++++++++------------- 80 files changed, 6502 insertions(+), 7237 deletions(-) diff --git a/.travis.yml b/.travis.yml index 683f77cc..f10f82a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,9 +36,12 @@ before_script: script: - cargo update - cargo check --all --no-default-features - - cargo test --all-features --all -- --nocapture - # - cd actix-http; cargo test --no-default-features --features="rustls" -- --nocapture; cd .. - # - cd awc; cargo test --no-default-features --features="rustls" -- --nocapture; cd .. + - | + if [[ "$TRAVIS_RUST_VERSION" == "stable" || "$TRAVIS_RUST_VERSION" == "beta" ]]; then + cargo test --all-features --all -- --nocapture + cd actix-http; cargo test --no-default-features --features="rustls" -- --nocapture; cd .. + cd awc; cargo test --no-default-features --features="rustls" -- --nocapture; cd .. + fi # Upload docs after_success: @@ -51,7 +54,7 @@ after_success: echo "Uploaded documentation" fi - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-20" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-20" ]]; then taskset -c 0 cargo tarpaulin --out Xml --all --all-features bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" diff --git a/Cargo.toml b/Cargo.toml index dda01b48..a1875eb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ fail = ["actix-http/fail"] openssl = ["open-ssl", "actix-server/openssl", "awc/openssl"] # rustls -# rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"] +rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"] [dependencies] actix-codec = "0.2.0-alpha.1" @@ -110,7 +110,6 @@ actix-http-test = "0.3.0-alpha.1" rand = "0.7" env_logger = "0.6" serde_derive = "1.0" -tokio-timer = "0.3.0-alpha.6" brotli2 = "0.3.2" flate2 = "1.0.2" @@ -135,19 +134,9 @@ awc = { path = "awc" } actix-codec = { git = "https://github.com/actix/actix-net.git" } actix-connect = { git = "https://github.com/actix/actix-net.git" } actix-rt = { git = "https://github.com/actix/actix-net.git" } +actix-macros = { git = "https://github.com/actix/actix-net.git" } actix-server = { git = "https://github.com/actix/actix-net.git" } actix-server-config = { git = "https://github.com/actix/actix-net.git" } actix-service = { git = "https://github.com/actix/actix-net.git" } actix-testing = { git = "https://github.com/actix/actix-net.git" } -actix-threadpool = { git = "https://github.com/actix/actix-net.git" } actix-utils = { git = "https://github.com/actix/actix-net.git" } - -# actix-codec = { path = "../actix-net/actix-codec" } -# actix-connect = { path = "../actix-net/actix-connect" } -# actix-rt = { path = "../actix-net/actix-rt" } -# actix-server = { path = "../actix-net/actix-server" } -# actix-server-config = { path = "../actix-net/actix-server-config" } -# actix-service = { path = "../actix-net/actix-service" } -# actix-testing = { path = "../actix-net/actix-testing" } -# actix-threadpool = { path = "../actix-net/actix-threadpool" } -# actix-utils = { path = "../actix-net/actix-utils" } diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml index 11ad1033..ddb5f307 100644 --- a/actix-cors/Cargo.toml +++ b/actix-cors/Cargo.toml @@ -21,3 +21,6 @@ actix-web = "2.0.0-alpha.1" actix-service = "1.0.0-alpha.1" derive_more = "0.99.2" futures = "0.3.1" + +[dev-dependencies] +actix-rt = "1.0.0-alpha.1" diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index db7e4cc4..551e3bb4 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -814,142 +814,136 @@ where #[cfg(test)] mod tests { use actix_service::{service_fn2, Transform}; - use actix_web::test::{self, block_on, TestRequest}; + use actix_web::test::{self, TestRequest}; use super::*; - #[test] + #[actix_rt::test] #[should_panic(expected = "Credentials are allowed, but the Origin is set to")] - fn cors_validates_illegal_allow_credentials() { + async fn cors_validates_illegal_allow_credentials() { let _cors = Cors::new().supports_credentials().send_wildcard().finish(); } - #[test] - fn validate_origin_allows_all_origins() { - block_on(async { - let mut cors = Cors::new() - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .to_srv_request(); + #[actix_rt::test] + async fn validate_origin_allows_all_origins() { + let mut cors = Cors::new() + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn default() { - block_on(async { - let mut cors = Cors::default() - .new_transform(test::ok_service()) - .await - .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .to_srv_request(); + #[actix_rt::test] + async fn default() { + let mut cors = Cors::default() + .new_transform(test::ok_service()) + .await + .unwrap(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_preflight() { - block_on(async { - let mut cors = Cors::new() - .send_wildcard() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) - .allowed_header(header::CONTENT_TYPE) - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); + #[actix_rt::test] + async fn test_preflight() { + let mut cors = Cors::new() + .send_wildcard() + .max_age(3600) + .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) + .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) + .allowed_header(header::CONTENT_TYPE) + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed") - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed") + .to_srv_request(); - assert!(cors.inner.validate_allowed_method(req.head()).is_err()); - assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); - let resp = test::call_service(&mut cors, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + assert!(cors.inner.validate_allowed_method(req.head()).is_err()); + assert!(cors.inner.validate_allowed_headers(req.head()).is_err()); + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put") + .method(Method::OPTIONS) + .to_srv_request(); - assert!(cors.inner.validate_allowed_method(req.head()).is_err()); - assert!(cors.inner.validate_allowed_headers(req.head()).is_ok()); + assert!(cors.inner.validate_allowed_method(req.head()).is_err()); + assert!(cors.inner.validate_allowed_headers(req.head()).is_ok()); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ) - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") + .header( + header::ACCESS_CONTROL_REQUEST_HEADERS, + "AUTHORIZATION,ACCEPT", + ) + .method(Method::OPTIONS) + .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!( - &b"*"[..], - resp.headers() - .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"3600"[..], - resp.headers() - .get(&header::ACCESS_CONTROL_MAX_AGE) - .unwrap() - .as_bytes() - ); - let hdr = resp - .headers() - .get(&header::ACCESS_CONTROL_ALLOW_HEADERS) + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"*"[..], + resp.headers() + .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN) .unwrap() - .to_str() - .unwrap(); - assert!(hdr.contains("authorization")); - assert!(hdr.contains("accept")); - assert!(hdr.contains("content-type")); - - let methods = resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_METHODS) + .as_bytes() + ); + assert_eq!( + &b"3600"[..], + resp.headers() + .get(&header::ACCESS_CONTROL_MAX_AGE) .unwrap() - .to_str() - .unwrap(); - assert!(methods.contains("POST")); - assert!(methods.contains("GET")); - assert!(methods.contains("OPTIONS")); + .as_bytes() + ); + let hdr = resp + .headers() + .get(&header::ACCESS_CONTROL_ALLOW_HEADERS) + .unwrap() + .to_str() + .unwrap(); + assert!(hdr.contains("authorization")); + assert!(hdr.contains("accept")); + assert!(hdr.contains("content-type")); - Rc::get_mut(&mut cors.inner).unwrap().preflight = false; + let methods = resp + .headers() + .get(header::ACCESS_CONTROL_ALLOW_METHODS) + .unwrap() + .to_str() + .unwrap(); + assert!(methods.contains("POST")); + assert!(methods.contains("GET")); + assert!(methods.contains("OPTIONS")); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .header( - header::ACCESS_CONTROL_REQUEST_HEADERS, - "AUTHORIZATION,ACCEPT", - ) - .method(Method::OPTIONS) - .to_srv_request(); + Rc::get_mut(&mut cors.inner).unwrap().preflight = false; - let resp = test::call_service(&mut cors, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) + let req = TestRequest::with_header("Origin", "https://www.example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") + .header( + header::ACCESS_CONTROL_REQUEST_HEADERS, + "AUTHORIZATION,ACCEPT", + ) + .method(Method::OPTIONS) + .to_srv_request(); + + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::OK); } - // #[test] + // #[actix_rt::test] // #[should_panic(expected = "MissingOrigin")] - // fn test_validate_missing_origin() { + // async fn test_validate_missing_origin() { // let cors = Cors::build() // .allowed_origin("https://www.example.com") // .finish(); @@ -957,257 +951,245 @@ mod tests { // cors.start(&req).unwrap(); // } - #[test] + #[actix_rt::test] #[should_panic(expected = "OriginNotAllowed")] - fn test_validate_not_allowed_origin() { - block_on(async { - let cors = Cors::new() - .allowed_origin("https://www.example.com") - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); + async fn test_validate_not_allowed_origin() { + let cors = Cors::new() + .allowed_origin("https://www.example.com") + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.unknown.com") - .method(Method::GET) - .to_srv_request(); - cors.inner.validate_origin(req.head()).unwrap(); - cors.inner.validate_allowed_method(req.head()).unwrap(); - cors.inner.validate_allowed_headers(req.head()).unwrap(); - }) + let req = TestRequest::with_header("Origin", "https://www.unknown.com") + .method(Method::GET) + .to_srv_request(); + cors.inner.validate_origin(req.head()).unwrap(); + cors.inner.validate_allowed_method(req.head()).unwrap(); + cors.inner.validate_allowed_headers(req.head()).unwrap(); } - #[test] - fn test_validate_origin() { - block_on(async { - let mut cors = Cors::new() - .allowed_origin("https://www.example.com") - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); + #[actix_rt::test] + async fn test_validate_origin() { + let mut cors = Cors::new() + .allowed_origin("https://www.example.com") + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::GET) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::GET) + .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) + let resp = test::call_service(&mut cors, req).await; + assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_no_origin_response() { - block_on(async { - let mut cors = Cors::new() - .disable_preflight() - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); + #[actix_rt::test] + async fn test_no_origin_response() { + let mut cors = Cors::new() + .disable_preflight() + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::default().method(Method::GET).to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert!(resp - .headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_none()); + let req = TestRequest::default().method(Method::GET).to_srv_request(); + let resp = test::call_service(&mut cors, req).await; + assert!(resp + .headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .is_none()); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!( - &b"https://www.example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - }) - } - - #[test] - fn test_response() { - block_on(async { - let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let mut cors = Cors::new() - .send_wildcard() - .disable_preflight() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(exposed_headers.clone()) - .expose_headers(exposed_headers.clone()) - .allowed_header(header::CONTENT_TYPE) - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .to_srv_request(); - - let resp = test::call_service(&mut cors, req).await; - assert_eq!( - &b"*"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - assert_eq!( - &b"Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - { - let headers = resp - .headers() - .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) - .unwrap() - .to_str() - .unwrap() - .split(',') - .map(|s| s.trim()) - .collect::>(); - - for h in exposed_headers { - assert!(headers.contains(&h.as_str())); - } - } - - let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; - let mut cors = Cors::new() - .send_wildcard() - .disable_preflight() - .max_age(3600) - .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) - .allowed_headers(exposed_headers.clone()) - .expose_headers(exposed_headers.clone()) - .allowed_header(header::CONTENT_TYPE) - .finish() - .new_transform(service_fn2(|req: ServiceRequest| { - ok(req.into_response( - HttpResponse::Ok().header(header::VARY, "Accept").finish(), - )) - })) - .await - .unwrap(); - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!( - &b"Accept, Origin"[..], - resp.headers().get(header::VARY).unwrap().as_bytes() - ); - - let mut cors = Cors::new() - .disable_vary_header() - .allowed_origin("https://www.example.com") - .allowed_origin("https://www.google.com") - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); - - let req = TestRequest::with_header("Origin", "https://www.example.com") - .method(Method::OPTIONS) - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") - .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - - let origins_str = resp - .headers() + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .to_srv_request(); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://www.example.com"[..], + resp.headers() .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) .unwrap() + .as_bytes() + ); + } + + #[actix_rt::test] + async fn test_response() { + let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; + let mut cors = Cors::new() + .send_wildcard() + .disable_preflight() + .max_age(3600) + .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) + .allowed_headers(exposed_headers.clone()) + .expose_headers(exposed_headers.clone()) + .allowed_header(header::CONTENT_TYPE) + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); + + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .to_srv_request(); + + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"*"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); + assert_eq!( + &b"Origin"[..], + resp.headers().get(header::VARY).unwrap().as_bytes() + ); + + { + let headers = resp + .headers() + .get(header::ACCESS_CONTROL_EXPOSE_HEADERS) + .unwrap() .to_str() - .unwrap(); + .unwrap() + .split(',') + .map(|s| s.trim()) + .collect::>(); - assert_eq!("https://www.example.com", origins_str); - }) + for h in exposed_headers { + assert!(headers.contains(&h.as_str())); + } + } + + let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT]; + let mut cors = Cors::new() + .send_wildcard() + .disable_preflight() + .max_age(3600) + .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST]) + .allowed_headers(exposed_headers.clone()) + .expose_headers(exposed_headers.clone()) + .allowed_header(header::CONTENT_TYPE) + .finish() + .new_transform(service_fn2(|req: ServiceRequest| { + ok(req.into_response( + HttpResponse::Ok().header(header::VARY, "Accept").finish(), + )) + })) + .await + .unwrap(); + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .to_srv_request(); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"Accept, Origin"[..], + resp.headers().get(header::VARY).unwrap().as_bytes() + ); + + let mut cors = Cors::new() + .disable_vary_header() + .allowed_origin("https://www.example.com") + .allowed_origin("https://www.google.com") + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); + + let req = TestRequest::with_header("Origin", "https://www.example.com") + .method(Method::OPTIONS) + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST") + .to_srv_request(); + let resp = test::call_service(&mut cors, req).await; + + let origins_str = resp + .headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!("https://www.example.com", origins_str); } - #[test] - fn test_multiple_origins() { - block_on(async { - let mut cors = Cors::new() - .allowed_origin("https://example.com") - .allowed_origin("https://example.org") - .allowed_methods(vec![Method::GET]) - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); + #[actix_rt::test] + async fn test_multiple_origins() { + let mut cors = Cors::new() + .allowed_origin("https://example.com") + .allowed_origin("https://example.org") + .allowed_methods(vec![Method::GET]) + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://example.com") - .method(Method::GET) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://example.com") + .method(Method::GET) + .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!( - &b"https://example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://example.com"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); - let req = TestRequest::with_header("Origin", "https://example.org") - .method(Method::GET) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://example.org") + .method(Method::GET) + .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!( - &b"https://example.org"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - }) + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://example.org"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); } - #[test] - fn test_multiple_origins_preflight() { - block_on(async { - let mut cors = Cors::new() - .allowed_origin("https://example.com") - .allowed_origin("https://example.org") - .allowed_methods(vec![Method::GET]) - .finish() - .new_transform(test::ok_service()) - .await - .unwrap(); + #[actix_rt::test] + async fn test_multiple_origins_preflight() { + let mut cors = Cors::new() + .allowed_origin("https://example.com") + .allowed_origin("https://example.org") + .allowed_methods(vec![Method::GET]) + .finish() + .new_transform(test::ok_service()) + .await + .unwrap(); - let req = TestRequest::with_header("Origin", "https://example.com") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://example.com") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") + .method(Method::OPTIONS) + .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!( - &b"https://example.com"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://example.com"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); - let req = TestRequest::with_header("Origin", "https://example.org") - .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") - .method(Method::OPTIONS) - .to_srv_request(); + let req = TestRequest::with_header("Origin", "https://example.org") + .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET") + .method(Method::OPTIONS) + .to_srv_request(); - let resp = test::call_service(&mut cors, req).await; - assert_eq!( - &b"https://example.org"[..], - resp.headers() - .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) - .unwrap() - .as_bytes() - ); - }) + let resp = test::call_service(&mut cors, req).await; + assert_eq!( + &b"https://example.org"[..], + resp.headers() + .get(header::ACCESS_CONTROL_ALLOW_ORIGIN) + .unwrap() + .as_bytes() + ); } } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f5318b72..19366b90 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -32,4 +32,5 @@ percent-encoding = "2.1" v_htmlescape = "0.4" [dev-dependencies] +actix-rt = "1.0.0-alpha.1" actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 2f8a5c49..ed8b6c3b 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -12,7 +12,7 @@ use std::rc::Rc; use std::task::{Context, Poll}; use std::{cmp, io}; -use actix_service::boxed::{self, BoxedNewService, BoxedService}; +use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use actix_web::dev::{ AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest, @@ -39,8 +39,8 @@ use self::error::{FilesError, UriSegmentError}; pub use crate::named::NamedFile; pub use crate::range::HttpRange; -type HttpService = BoxedService; -type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; +type HttpService = BoxService; +type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Return the MIME type associated with a filename extension (case-insensitive). /// If `ext` is empty or no associated type for the extension was found, returns @@ -644,11 +644,11 @@ mod tests { }; use actix_web::http::{Method, StatusCode}; use actix_web::middleware::Compress; - use actix_web::test::{self, block_on, TestRequest}; + use actix_web::test::{self, TestRequest}; use actix_web::{App, Responder}; - #[test] - fn test_file_extension_to_mime() { + #[actix_rt::test] + async fn test_file_extension_to_mime() { let m = file_extension_to_mime("jpg"); assert_eq!(m, mime::IMAGE_JPEG); @@ -659,678 +659,622 @@ mod tests { assert_eq!(m, mime::APPLICATION_OCTET_STREAM); } - #[test] - fn test_if_modified_since_without_if_none_match() { - block_on(async { - let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + #[actix_rt::test] + async fn test_if_modified_since_without_if_none_match() { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = + header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); - let req = TestRequest::default() - .header(header::IF_MODIFIED_SINCE, since) - .to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); - }) + let req = TestRequest::default() + .header(header::IF_MODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); } - #[test] - fn test_if_modified_since_with_if_none_match() { - block_on(async { - let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + #[actix_rt::test] + async fn test_if_modified_since_with_if_none_match() { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = + header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); - let req = TestRequest::default() - .header(header::IF_NONE_MATCH, "miss_etag") - .header(header::IF_MODIFIED_SINCE, since) - .to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); - }) + let req = TestRequest::default() + .header(header::IF_NONE_MATCH, "miss_etag") + .header(header::IF_MODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); } - #[test] - fn test_named_file_text() { - block_on(async { - 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; - } + #[actix_rt::test] + async fn test_named_file_text() { + 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 req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - }) + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); } - #[test] - fn test_named_file_content_disposition() { - block_on(async { - 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; - } + #[actix_rt::test] + async fn test_named_file_content_disposition() { + 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 req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); - let file = NamedFile::open("Cargo.toml") - .unwrap() - .disable_content_disposition(); - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); - }) + let file = NamedFile::open("Cargo.toml") + .unwrap() + .disable_content_disposition(); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); } - #[test] - fn test_named_file_non_ascii_file_name() { - block_on(async { - let mut file = - NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml") - .unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + #[actix_rt::test] + async fn test_named_file_non_ascii_file_name() { + let mut file = + NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml") + .unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml" ); - }) } - #[test] - fn test_named_file_set_content_type() { - block_on(async { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_content_type(mime::TEXT_XML); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + #[actix_rt::test] + async fn test_named_file_set_content_type() { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_content_type(mime::TEXT_XML); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/xml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - }) + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/xml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); } - #[test] - fn test_named_file_image() { - block_on(async { - let mut file = NamedFile::open("tests/test.png").unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + #[actix_rt::test] + async fn test_named_file_image() { + let mut file = NamedFile::open("tests/test.png").unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - }) + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"test.png\"" + ); } - #[test] - fn test_named_file_image_attachment() { - block_on(async { - let cd = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename(String::from("test.png"))], - }; - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_content_disposition(cd); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + #[actix_rt::test] + async fn test_named_file_image_attachment() { + let cd = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![DispositionParam::Filename(String::from("test.png"))], + }; + let mut file = NamedFile::open("tests/test.png") + .unwrap() + .set_content_disposition(cd); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - }) + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "image/png" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.png\"" + ); } - #[test] - fn test_named_file_binary() { - block_on(async { - let mut file = NamedFile::open("tests/test.binary").unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + #[actix_rt::test] + async fn test_named_file_binary() { + let mut file = NamedFile::open("tests/test.binary").unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.binary\"" - ); - }) + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "application/octet-stream" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=\"test.binary\"" + ); } - #[test] - fn test_named_file_status_code_text() { - block_on(async { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_status_code(StatusCode::NOT_FOUND); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } + #[actix_rt::test] + async fn test_named_file_status_code_text() { + let mut file = NamedFile::open("Cargo.toml") + .unwrap() + .set_status_code(StatusCode::NOT_FOUND); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - }) + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/x-toml" + ); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); } - #[test] - fn test_mime_override() { - block_on(async { - fn all_attachment(_: &mime::Name) -> DispositionType { - DispositionType::Attachment - } + #[actix_rt::test] + async fn test_mime_override() { + fn all_attachment(_: &mime::Name) -> DispositionType { + DispositionType::Attachment + } - let mut srv = test::init_service( - App::new().service( - Files::new("/", ".") - .mime_override(all_attachment) - .index_file("Cargo.toml"), - ), - ) - .await; + let mut srv = test::init_service( + App::new().service( + Files::new("/", ".") + .mime_override(all_attachment) + .index_file("Cargo.toml"), + ), + ) + .await; - let request = TestRequest::get().uri("/").to_request(); - let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::OK); + let request = TestRequest::get().uri("/").to_request(); + let response = test::call_service(&mut srv, request).await; + assert_eq!(response.status(), StatusCode::OK); - let content_disposition = response - .headers() - .get(header::CONTENT_DISPOSITION) - .expect("To have CONTENT_DISPOSITION"); - let content_disposition = content_disposition - .to_str() - .expect("Convert CONTENT_DISPOSITION to str"); - assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); - }) + let content_disposition = response + .headers() + .get(header::CONTENT_DISPOSITION) + .expect("To have CONTENT_DISPOSITION"); + let content_disposition = content_disposition + .to_str() + .expect("Convert CONTENT_DISPOSITION to str"); + assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); } - #[test] - fn test_named_file_ranges_status_code() { - block_on(async { - let mut srv = test::init_service( - App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), - ) - .await; + #[actix_rt::test] + async fn test_named_file_ranges_status_code() { + let mut srv = test::init_service( + App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), + ) + .await; - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=10-20") - .to_request(); - let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/Cargo.toml") + .header(header::RANGE, "bytes=10-20") + .to_request(); + let response = test::call_service(&mut srv, request).await; + assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); - // Invalid range header - let request = TestRequest::get() - .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=1-0") - .to_request(); - let response = test::call_service(&mut srv, request).await; + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/Cargo.toml") + .header(header::RANGE, "bytes=1-0") + .to_request(); + let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); - }) + assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); } - #[test] - fn test_named_file_content_range_headers() { - block_on(async { - let mut srv = test::init_service( - App::new() - .service(Files::new("/test", ".").index_file("tests/test.binary")), - ) - .await; + #[actix_rt::test] + async fn test_named_file_content_range_headers() { + let mut srv = test::init_service( + App::new().service(Files::new("/test", ".").index_file("tests/test.binary")), + ) + .await; - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-20") - .to_request(); + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-20") + .to_request(); - let response = test::call_service(&mut srv, request).await; - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) + let response = test::call_service(&mut srv, request).await; + let contentrange = response + .headers() + .get(header::CONTENT_RANGE) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentrange, "bytes 10-20/100"); + + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-5") + .to_request(); + let response = test::call_service(&mut srv, request).await; + + let contentrange = response + .headers() + .get(header::CONTENT_RANGE) + .unwrap() + .to_str() + .unwrap(); + + assert_eq!(contentrange, "bytes */100"); + } + + #[actix_rt::test] + async fn test_named_file_content_length_headers() { + // use actix_web::body::{MessageBody, ResponseBody}; + + let mut srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("tests/test.binary")), + ) + .await; + + // Valid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-20") + .to_request(); + let _response = test::call_service(&mut srv, request).await; + + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "11"); + + // Invalid range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .header(header::RANGE, "bytes=10-8") + .to_request(); + let response = test::call_service(&mut srv, request).await; + assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); + + // Without range header + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + // .no_default_headers() + .to_request(); + let _response = test::call_service(&mut srv, request).await; + + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "100"); + + // chunked + let request = TestRequest::get() + .uri("/t%65st/tests/test.binary") + .to_request(); + let response = test::call_service(&mut srv, request).await; + + // with enabled compression + // { + // let te = response + // .headers() + // .get(header::TRANSFER_ENCODING) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(te, "chunked"); + // } + + let bytes = test::read_body(response).await; + let data = Bytes::from(fs::read("tests/test.binary").unwrap()); + assert_eq!(bytes, data); + } + + #[actix_rt::test] + async fn test_head_content_length_headers() { + let mut srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("tests/test.binary")), + ) + .await; + + // Valid range header + let request = TestRequest::default() + .method(Method::HEAD) + .uri("/t%65st/tests/test.binary") + .to_request(); + let _response = test::call_service(&mut srv, request).await; + + // TODO: fix check + // let contentlength = response + // .headers() + // .get(header::CONTENT_LENGTH) + // .unwrap() + // .to_str() + // .unwrap(); + // assert_eq!(contentlength, "100"); + } + + #[actix_rt::test] + async fn test_static_files_with_spaces() { + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").index_file("Cargo.toml")), + ) + .await; + let request = TestRequest::get() + .uri("/tests/test%20space.binary") + .to_request(); + let response = test::call_service(&mut srv, request).await; + assert_eq!(response.status(), StatusCode::OK); + + let bytes = test::read_body(response).await; + let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); + assert_eq!(bytes, data); + } + + #[actix_rt::test] + async fn test_files_not_allowed() { + let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + + let req = TestRequest::default() + .uri("/Cargo.toml") + .method(Method::POST) + .to_request(); + + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let req = TestRequest::default() + .method(Method::PUT) + .uri("/Cargo.toml") + .to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + } + + #[actix_rt::test] + async fn test_files_guards() { + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").use_guards(guard::Post())), + ) + .await; + + let req = TestRequest::default() + .uri("/Cargo.toml") + .method(Method::POST) + .to_request(); + + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_named_file_content_encoding() { + let mut srv = test::init_service(App::new().wrap(Compress::default()).service( + web::resource("/").to(|| { + async { + NamedFile::open("Cargo.toml") + .unwrap() + .set_content_encoding(header::ContentEncoding::Identity) + } + }), + )) + .await; + + let request = TestRequest::get() + .uri("/") + .header(header::ACCEPT_ENCODING, "gzip") + .to_request(); + let res = test::call_service(&mut srv, request).await; + assert_eq!(res.status(), StatusCode::OK); + assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); + } + + #[actix_rt::test] + async fn test_named_file_content_encoding_gzip() { + let mut srv = test::init_service(App::new().wrap(Compress::default()).service( + web::resource("/").to(|| { + async { + NamedFile::open("Cargo.toml") + .unwrap() + .set_content_encoding(header::ContentEncoding::Gzip) + } + }), + )) + .await; + + let request = TestRequest::get() + .uri("/") + .header(header::ACCEPT_ENCODING, "gzip") + .to_request(); + let res = test::call_service(&mut srv, request).await; + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers() + .get(header::CONTENT_ENCODING) .unwrap() .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes 10-20/100"); - - // Invalid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-5") - .to_request(); - let response = test::call_service(&mut srv, request).await; - - let contentrange = response - .headers() - .get(header::CONTENT_RANGE) - .unwrap() - .to_str() - .unwrap(); - - assert_eq!(contentrange, "bytes */100"); - }) + .unwrap(), + "gzip" + ); } - #[test] - fn test_named_file_content_length_headers() { - block_on(async { - // use actix_web::body::{MessageBody, ResponseBody}; - - let mut srv = test::init_service( - App::new() - .service(Files::new("test", ".").index_file("tests/test.binary")), - ) - .await; - - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-20") - .to_request(); - let _response = test::call_service(&mut srv, request).await; - - // let contentlength = response - // .headers() - // .get(header::CONTENT_LENGTH) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(contentlength, "11"); - - // Invalid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-8") - .to_request(); - let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); - - // Without range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - // .no_default_headers() - .to_request(); - let _response = test::call_service(&mut srv, request).await; - - // let contentlength = response - // .headers() - // .get(header::CONTENT_LENGTH) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(contentlength, "100"); - - // chunked - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .to_request(); - let response = test::call_service(&mut srv, request).await; - - // with enabled compression - // { - // let te = response - // .headers() - // .get(header::TRANSFER_ENCODING) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(te, "chunked"); - // } - - let bytes = test::read_body(response).await; - let data = Bytes::from(fs::read("tests/test.binary").unwrap()); - assert_eq!(bytes, data); - }) + #[actix_rt::test] + async fn test_named_file_allowed_method() { + let req = TestRequest::default().method(Method::GET).to_http_request(); + let file = NamedFile::open("Cargo.toml").unwrap(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_head_content_length_headers() { - block_on(async { - let mut srv = test::init_service( - App::new() - .service(Files::new("test", ".").index_file("tests/test.binary")), - ) - .await; + #[actix_rt::test] + async fn test_static_files() { + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").show_files_listing()), + ) + .await; + let req = TestRequest::with_uri("/missing").to_request(); - // Valid range header - let request = TestRequest::default() - .method(Method::HEAD) - .uri("/t%65st/tests/test.binary") - .to_request(); - let _response = test::call_service(&mut srv, request).await; + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - // TODO: fix check - // let contentlength = response - // .headers() - // .get(header::CONTENT_LENGTH) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(contentlength, "100"); - }) + let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + + let req = TestRequest::default().to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").show_files_listing()), + ) + .await; + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + "text/html; charset=utf-8" + ); + + let bytes = test::read_body(resp).await; + assert!(format!("{:?}", bytes).contains("/tests/test.png")); } - #[test] - fn test_static_files_with_spaces() { - block_on(async { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").index_file("Cargo.toml")), - ) - .await; - let request = TestRequest::get() - .uri("/tests/test%20space.binary") - .to_request(); - let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::OK); + #[actix_rt::test] + async fn test_redirect_to_slash_directory() { + // should not redirect if no index + let mut srv = test::init_service( + App::new().service(Files::new("/", ".").redirect_to_slash_directory()), + ) + .await; + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let bytes = test::read_body(response).await; - let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); - assert_eq!(bytes, data); - }) + // should redirect if index present + let mut srv = test::init_service( + App::new().service( + Files::new("/", ".") + .index_file("test.png") + .redirect_to_slash_directory(), + ), + ) + .await; + let req = TestRequest::with_uri("/tests").to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::FOUND); + + // should not redirect if the path is wrong + let req = TestRequest::with_uri("/not_existing").to_request(); + let resp = test::call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); } - #[test] - fn test_files_not_allowed() { - block_on(async { - let mut srv = - test::init_service(App::new().service(Files::new("/", "."))).await; - - let req = TestRequest::default() - .uri("/Cargo.toml") - .method(Method::POST) - .to_request(); - - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let mut srv = - test::init_service(App::new().service(Files::new("/", "."))).await; - let req = TestRequest::default() - .method(Method::PUT) - .uri("/Cargo.toml") - .to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - }) - } - - #[test] - fn test_files_guards() { - block_on(async { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").use_guards(guard::Post())), - ) - .await; - - let req = TestRequest::default() - .uri("/Cargo.toml") - .method(Method::POST) - .to_request(); - - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) - } - - #[test] - fn test_named_file_content_encoding() { - block_on(async { - let mut srv = - test::init_service(App::new().wrap(Compress::default()).service( - web::resource("/").to(|| { - async { - NamedFile::open("Cargo.toml") - .unwrap() - .set_content_encoding(header::ContentEncoding::Identity) - } - }), - )) - .await; - - let request = TestRequest::get() - .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") - .to_request(); - let res = test::call_service(&mut srv, request).await; - assert_eq!(res.status(), StatusCode::OK); - assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); - }) - } - - #[test] - fn test_named_file_content_encoding_gzip() { - block_on(async { - let mut srv = - test::init_service(App::new().wrap(Compress::default()).service( - web::resource("/").to(|| { - async { - NamedFile::open("Cargo.toml") - .unwrap() - .set_content_encoding(header::ContentEncoding::Gzip) - } - }), - )) - .await; - - let request = TestRequest::get() - .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") - .to_request(); - let res = test::call_service(&mut srv, request).await; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!( - res.headers() - .get(header::CONTENT_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "gzip" - ); - }) - } - - #[test] - fn test_named_file_allowed_method() { - block_on(async { - let req = TestRequest::default().method(Method::GET).to_http_request(); - let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) - } - - #[test] - fn test_static_files() { - block_on(async { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ) - .await; - let req = TestRequest::with_uri("/missing").to_request(); - - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let mut srv = - test::init_service(App::new().service(Files::new("/", "."))).await; - - let req = TestRequest::default().to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ) - .await; - let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/html; charset=utf-8" - ); - - let bytes = test::read_body(resp).await; - assert!(format!("{:?}", bytes).contains("/tests/test.png")); - }) - } - - #[test] - fn test_redirect_to_slash_directory() { - block_on(async { - // should not redirect if no index - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").redirect_to_slash_directory()), - ) - .await; - let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - // should redirect if index present - let mut srv = test::init_service( - App::new().service( - Files::new("/", ".") - .index_file("test.png") - .redirect_to_slash_directory(), - ), - ) - .await; - let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::FOUND); - - // should not redirect if the path is wrong - let req = TestRequest::with_uri("/not_existing").to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - }) - } - - #[test] - fn test_static_files_bad_directory() { + #[actix_rt::test] + async fn test_static_files_bad_directory() { let _st: Files = Files::new("/", "missing"); let _st: Files = Files::new("/", "Cargo.toml"); } - #[test] - fn test_default_handler_file_missing() { - block_on(async { - let mut st = Files::new("/", ".") - .default_handler(|req: ServiceRequest| { - ok(req.into_response(HttpResponse::Ok().body("default content"))) - }) - .new_service(&()) - .await - .unwrap(); - let req = TestRequest::with_uri("/missing").to_srv_request(); + #[actix_rt::test] + async fn test_default_handler_file_missing() { + let mut st = Files::new("/", ".") + .default_handler(|req: ServiceRequest| { + ok(req.into_response(HttpResponse::Ok().body("default content"))) + }) + .new_service(&()) + .await + .unwrap(); + let req = TestRequest::with_uri("/missing").to_srv_request(); - let resp = test::call_service(&mut st, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let bytes = test::read_body(resp).await; - assert_eq!(bytes, Bytes::from_static(b"default content")); - }) + let resp = test::call_service(&mut st, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let bytes = test::read_body(resp).await; + assert_eq!(bytes, Bytes::from_static(b"default content")); } - // #[test] - // fn test_serve_index() { + // #[actix_rt::test] + // async fn test_serve_index() { // let st = Files::new(".").index_file("test.binary"); // let req = TestRequest::default().uri("/tests").finish(); @@ -1375,8 +1319,8 @@ mod tests { // assert_eq!(resp.status(), StatusCode::NOT_FOUND); // } - // #[test] - // fn test_serve_index_nested() { + // #[actix_rt::test] + // async fn test_serve_index_nested() { // let st = Files::new(".").index_file("mod.rs"); // let req = TestRequest::default().uri("/src/client").finish(); // let resp = st.handle(&req).respond_to(&req).unwrap(); @@ -1392,7 +1336,7 @@ mod tests { // ); // } - // #[test] + // #[actix_rt::test] // fn integration_serve_index() { // let mut srv = test::TestServer::with_factory(|| { // App::new().handler( @@ -1425,7 +1369,7 @@ mod tests { // assert_eq!(response.status(), StatusCode::NOT_FOUND); // } - // #[test] + // #[actix_rt::test] // fn integration_percent_encoded() { // let mut srv = test::TestServer::with_factory(|| { // App::new().handler( @@ -1443,8 +1387,8 @@ mod tests { // assert_eq!(response.status(), StatusCode::OK); // } - #[test] - fn test_path_buf() { + #[actix_rt::test] + async fn test_path_buf() { assert_eq!( PathBufWrp::get_pathbuf("/test/.tt").map(|t| t.0), Err(UriSegmentError::BadStart('.')) diff --git a/actix-framed/src/test.rs b/actix-framed/src/test.rs index b90a493d..7969d51f 100644 --- a/actix-framed/src/test.rs +++ b/actix-framed/src/test.rs @@ -7,7 +7,6 @@ use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::{HttpTryFrom, Method, Uri, Version}; use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest}; use actix_router::{Path, Url}; -use actix_rt::Runtime; use crate::{FramedRequest, State}; @@ -119,13 +118,12 @@ impl TestRequest { } /// This method generates `FramedRequest` instance and executes async handler - pub fn run(self, f: F) -> Result + pub async fn run(self, f: F) -> Result where F: FnOnce(FramedRequest) -> R, R: Future>, { - let mut rt = Runtime::new().unwrap(); - rt.block_on(f(self.finish())) + f(self.finish()).await } } diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs index 6e4bb6ad..4d1028d3 100644 --- a/actix-framed/tests/test_server.rs +++ b/actix-framed/tests/test_server.rs @@ -1,6 +1,6 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response}; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use actix_service::{pipeline_factory, IntoServiceFactory, ServiceFactory}; use actix_utils::framed::FramedTransport; use bytes::{Bytes, BytesMut}; @@ -38,126 +38,121 @@ async fn service(msg: ws::Frame) -> Result { Ok(msg) } -#[test] -fn test_simple() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build() - .upgrade( - FramedApp::new() - .service(FramedRoute::get("/index.html").to(ws_service)), - ) - .finish(|_| future::ok::<_, Error>(Response::NotFound())) - }); - - assert!(srv.ws_at("/test").await.is_err()); - - // client service - let mut framed = srv.ws_at("/index.html").await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Text(Some(BytesMut::from("text"))) - ); - - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) - ); - - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Pong("text".to_string().into()) - ); - - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let (item, _) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Close(Some(ws::CloseCode::Normal.into())) - ); - }) -} - -#[test] -fn test_service() { - block_on(async { - let mut srv = TestServer::start(|| { - pipeline_factory(actix_http::h1::OneRequest::new().map_err(|_| ())).and_then( - pipeline_factory( - pipeline_factory(VerifyWebSockets::default()) - .then(SendError::default()) - .map_err(|_| ()), - ) - .and_then( - FramedApp::new() - .service(FramedRoute::get("/index.html").to(ws_service)) - .into_factory() - .map_err(|_| ()), - ), +#[actix_rt::test] +async fn test_simple() { + let mut srv = TestServer::start(|| { + HttpService::build() + .upgrade( + FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)), ) - }); + .finish(|_| future::ok::<_, Error>(Response::NotFound())) + }); - // non ws request - let res = srv.get("/index.html").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert!(srv.ws_at("/test").await.is_err()); - // not found - assert!(srv.ws_at("/test").await.is_err()); + // client service + let mut framed = srv.ws_at("/index.html").await.unwrap(); + framed + .send(ws::Message::Text("text".to_string())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Text(Some(BytesMut::from("text"))) + ); - // client service - let mut framed = srv.ws_at("/index.html").await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Text(Some(BytesMut::from("text"))) - ); + framed + .send(ws::Message::Binary("text".into())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) + ); - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) - ); + framed.send(ws::Message::Ping("text".into())).await.unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Pong("text".to_string().into()) + ); - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Pong("text".to_string().into()) - ); + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let (item, _) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Close(Some(ws::CloseCode::Normal.into())) - ); - }) + let (item, _) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Close(Some(ws::CloseCode::Normal.into())) + ); +} + +#[actix_rt::test] +async fn test_service() { + let mut srv = TestServer::start(|| { + pipeline_factory(actix_http::h1::OneRequest::new().map_err(|_| ())).and_then( + pipeline_factory( + pipeline_factory(VerifyWebSockets::default()) + .then(SendError::default()) + .map_err(|_| ()), + ) + .and_then( + FramedApp::new() + .service(FramedRoute::get("/index.html").to(ws_service)) + .into_factory() + .map_err(|_| ()), + ), + ) + }); + + // non ws request + let res = srv.get("/index.html").send().await.unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + + // not found + assert!(srv.ws_at("/test").await.is_err()); + + // client service + let mut framed = srv.ws_at("/index.html").await.unwrap(); + framed + .send(ws::Message::Text("text".to_string())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Text(Some(BytesMut::from("text"))) + ); + + framed + .send(ws::Message::Binary("text".into())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) + ); + + framed.send(ws::Message::Ping("text".into())).await.unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Pong("text".to_string().into()) + ); + + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); + + let (item, _) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Close(Some(ws::CloseCode::Normal.into())) + ); } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index cf390e79..cfed0bf1 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -29,7 +29,7 @@ default = [] openssl = ["open-ssl", "actix-connect/openssl", "tokio-openssl"] # rustls support -# rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] +rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -52,6 +52,7 @@ actix-codec = "0.2.0-alpha.1" actix-connect = "1.0.0-alpha.1" actix-utils = "0.5.0-alpha.1" actix-server-config = "0.3.0-alpha.1" +actix-rt = "1.0.0-alpha.1" actix-threadpool = "0.2.0-alpha.1" base64 = "0.10" @@ -83,11 +84,7 @@ slab = "0.4" serde_urlencoded = "0.6.1" time = "0.1.42" -tokio = "=0.2.0-alpha.6" -tokio-io = "=0.2.0-alpha.6" tokio-net = "=0.2.0-alpha.6" -tokio-timer = "0.3.0-alpha.6" -tokio-executor = "=0.2.0-alpha.6" trust-dns-resolver = { version="0.18.0-alpha.1", default-features = false } # for secure cookie @@ -106,8 +103,7 @@ rust-tls = { version = "0.16.0", package="rustls", optional = true } webpki-roots = { version = "0.18", optional = true } [dev-dependencies] -actix-rt = "1.0.0-alpha.1" -actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } +actix-server = { version = "0.8.0-alpha.1", features=["openssl", "rustls"] } actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } env_logger = "0.6" diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 1d3a43fe..b69c21ea 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -432,8 +432,7 @@ where #[cfg(test)] mod tests { use super::*; - use actix_http_test::block_on; - use futures::future::{lazy, poll_fn}; + use futures::future::poll_fn; impl Body { pub(crate) fn get_ref(&self) -> &[u8] { @@ -453,21 +452,21 @@ mod tests { } } - #[test] - fn test_static_str() { + #[actix_rt::test] + async fn test_static_str() { assert_eq!(Body::from("").size(), BodySize::Sized(0)); assert_eq!(Body::from("test").size(), BodySize::Sized(4)); assert_eq!(Body::from("test").get_ref(), b"test"); assert_eq!("test".size(), BodySize::Sized(4)); assert_eq!( - block_on(poll_fn(|cx| "test".poll_next(cx))).unwrap().ok(), + poll_fn(|cx| "test".poll_next(cx)).await.unwrap().ok(), Some(Bytes::from("test")) ); } - #[test] - fn test_static_bytes() { + #[actix_rt::test] + async fn test_static_bytes() { assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); assert_eq!( @@ -478,55 +477,57 @@ mod tests { assert_eq!((&b"test"[..]).size(), BodySize::Sized(4)); assert_eq!( - block_on(poll_fn(|cx| (&b"test"[..]).poll_next(cx))) + poll_fn(|cx| (&b"test"[..]).poll_next(cx)) + .await .unwrap() .ok(), Some(Bytes::from("test")) ); } - #[test] - fn test_vec() { + #[actix_rt::test] + async fn test_vec() { assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); assert_eq!(Vec::from("test").size(), BodySize::Sized(4)); assert_eq!( - block_on(poll_fn(|cx| Vec::from("test").poll_next(cx))) + poll_fn(|cx| Vec::from("test").poll_next(cx)) + .await .unwrap() .ok(), Some(Bytes::from("test")) ); } - #[test] - fn test_bytes() { + #[actix_rt::test] + async fn test_bytes() { let mut b = Bytes::from("test"); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!( - block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), + poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(), Some(Bytes::from("test")) ); } - #[test] - fn test_bytes_mut() { + #[actix_rt::test] + async fn test_bytes_mut() { let mut b = BytesMut::from("test"); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).get_ref(), b"test"); assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!( - block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), + poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(), Some(Bytes::from("test")) ); } - #[test] - fn test_string() { + #[actix_rt::test] + async fn test_string() { let mut b = "test".to_owned(); assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); assert_eq!(Body::from(b.clone()).get_ref(), b"test"); @@ -535,26 +536,26 @@ mod tests { assert_eq!(b.size(), BodySize::Sized(4)); assert_eq!( - block_on(poll_fn(|cx| b.poll_next(cx))).unwrap().ok(), + poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(), Some(Bytes::from("test")) ); } - #[test] - fn test_unit() { + #[actix_rt::test] + async fn test_unit() { assert_eq!(().size(), BodySize::Empty); - assert!(block_on(poll_fn(|cx| ().poll_next(cx))).is_none()); + assert!(poll_fn(|cx| ().poll_next(cx)).await.is_none()); } - #[test] - fn test_box() { + #[actix_rt::test] + async fn test_box() { let mut val = Box::new(()); assert_eq!(val.size(), BodySize::Empty); - assert!(block_on(poll_fn(|cx| val.poll_next(cx))).is_none()); + assert!(poll_fn(|cx| val.poll_next(cx)).await.is_none()); } - #[test] - fn test_body_eq() { + #[actix_rt::test] + async fn test_body_eq() { assert!(Body::None == Body::None); assert!(Body::None != Body::Empty); assert!(Body::Empty == Body::Empty); @@ -566,15 +567,15 @@ mod tests { assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); } - #[test] - fn test_body_debug() { + #[actix_rt::test] + async fn test_body_debug() { assert!(format!("{:?}", Body::None).contains("Body::None")); assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1")); } - #[test] - fn test_serde_json() { + #[actix_rt::test] + async fn test_serde_json() { use serde_json::json; assert_eq!( Body::from(serde_json::Value::String("test".into())).size(), diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 1895f530..eaa3d97e 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -1,8 +1,5 @@ use std::fmt; -use std::future::Future; use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; @@ -11,7 +8,6 @@ use actix_connect::{ }; use actix_service::{apply_fn, Service}; use actix_utils::timeout::{TimeoutError, TimeoutService}; -use futures::future::Ready; use http::Uri; use tokio_net::tcp::TcpStream; @@ -344,7 +340,6 @@ mod connect_impl { use std::task::{Context, Poll}; use futures::future::{err, Either, Ready}; - use futures::ready; use super::*; use crate::client::connection::IoConnection; @@ -402,7 +397,10 @@ mod connect_impl { #[cfg(any(feature = "openssl", feature = "rustls"))] mod connect_impl { + use std::future::Future; use std::marker::PhantomData; + use std::pin::Pin; + use std::task::{Context, Poll}; use futures::future::Either; use futures::ready; diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 041a3685..ddfc7a31 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -1,4 +1,3 @@ -use std::future::Future; use std::io::Write; use std::pin::Pin; use std::task::{Context, Poll}; @@ -6,8 +5,8 @@ use std::{io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use bytes::{BufMut, Bytes, BytesMut}; -use futures::future::{ok, poll_fn, Either}; -use futures::{Sink, SinkExt, Stream, StreamExt}; +use futures::future::poll_fn; +use futures::{SinkExt, Stream, StreamExt}; use crate::error::PayloadError; use crate::h1; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 1647abf8..a94562f2 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -1,11 +1,8 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; use std::time; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; -use futures::future::{err, poll_fn, Either}; +use futures::future::poll_fn; use h2::{client::SendRequest, SendStream}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; use http::{request::Request, HttpTryFrom, Method, Version}; diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 1952dca5..c6103986 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -1,23 +1,22 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::future::Future; -use std::io; use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; +use actix_rt::time::{delay_for, Delay}; use actix_service::Service; use actix_utils::{oneshot, task::LocalWaker}; use bytes::Bytes; -use futures::future::{err, ok, poll_fn, Either, FutureExt, LocalBoxFuture, Ready}; +use futures::future::{poll_fn, FutureExt, LocalBoxFuture}; use h2::client::{handshake, Connection, SendRequest}; use hashbrown::HashMap; use http::uri::Authority; use indexmap::IndexSet; use slab::Slab; -use tokio_timer::{delay_for, Delay}; use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; @@ -100,7 +99,7 @@ where fn call(&mut self, req: Connect) -> Self::Future { // start support future - tokio_executor::current_thread::spawn(ConnectorPoolSupport { + actix_rt::spawn(ConnectorPoolSupport { connector: self.0.clone(), inner: self.1.clone(), }); @@ -139,7 +138,7 @@ where )) } else { let (snd, connection) = handshake(io).await?; - tokio_executor::current_thread::spawn(connection.map(|_| ())); + actix_rt::spawn(connection.map(|_| ())); Ok(IoConnection::new( ConnectionType::H2(snd), Instant::now(), @@ -328,9 +327,7 @@ where { if let Some(timeout) = self.disconnect_timeout { if let ConnectionType::H1(io) = conn.io { - tokio_executor::current_thread::spawn(CloseConnection::new( - io, timeout, - )) + actix_rt::spawn(CloseConnection::new(io, timeout)) } } } else { @@ -342,9 +339,9 @@ where Poll::Ready(Ok(n)) if n > 0 => { if let Some(timeout) = self.disconnect_timeout { if let ConnectionType::H1(io) = io { - tokio_executor::current_thread::spawn( - CloseConnection::new(io, timeout), - ) + actix_rt::spawn(CloseConnection::new( + io, timeout, + )) } } continue; @@ -376,7 +373,7 @@ where self.acquired -= 1; if let Some(timeout) = self.disconnect_timeout { if let ConnectionType::H1(io) = io { - tokio_executor::current_thread::spawn(CloseConnection::new(io, timeout)) + actix_rt::spawn(CloseConnection::new(io, timeout)) } } self.check_availibility(); @@ -518,7 +515,7 @@ where inner: Rc>>, fut: F, ) { - tokio_executor::current_thread::spawn(OpenWaitingConnection { + actix_rt::spawn(OpenWaitingConnection { key, fut, h2: None, @@ -554,7 +551,7 @@ where if let Some(ref mut h2) = this.h2 { return match Pin::new(h2).poll(cx) { Poll::Ready(Ok((snd, connection))) => { - tokio_executor::current_thread::spawn(connection.map(|_| ())); + actix_rt::spawn(connection.map(|_| ())); let rx = this.rx.take().unwrap(); let _ = rx.send(Ok(IoConnection::new( ConnectionType::H2(snd), diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 488e4d98..bab3cdc6 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -4,10 +4,10 @@ use std::fmt::Write; use std::rc::Rc; use std::time::{Duration, Instant}; +use actix_rt::time::{delay, delay_for, Delay}; use bytes::BytesMut; -use futures::{future, Future, FutureExt}; +use futures::{future, FutureExt}; use time; -use tokio_timer::{delay, delay_for, Delay}; // "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; @@ -242,12 +242,10 @@ impl DateService { // periodic date update let s = self.clone(); - tokio_executor::current_thread::spawn( - delay_for(Duration::from_millis(500)).then(move |_| { - s.0.reset(); - future::ready(()) - }), - ); + actix_rt::spawn(delay_for(Duration::from_millis(500)).then(move |_| { + s.0.reset(); + future::ready(()) + })); } } @@ -265,26 +263,19 @@ impl DateService { #[cfg(test)] mod tests { use super::*; - use actix_rt::System; - use futures::future; #[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 rt = System::new("test"); - - let _ = rt.block_on(future::lazy(|_| { - let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); - let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1); - let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2); - assert_eq!(buf1, buf2); - future::ok::<_, ()>(()) - })); + #[actix_rt::test] + async fn test_date() { + let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); + let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf1); + let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf2); + assert_eq!(buf1, buf2); } } diff --git a/actix-http/src/cookie/secure/key.rs b/actix-http/src/cookie/secure/key.rs index 95058ed8..779c16b7 100644 --- a/actix-http/src/cookie/secure/key.rs +++ b/actix-http/src/cookie/secure/key.rs @@ -1,5 +1,4 @@ use ring::hkdf::{Algorithm, KeyType, Prk, HKDF_SHA256}; -use ring::hmac; use ring::rand::{SecureRandom, SystemRandom}; use super::private::KEY_LEN as PRIVATE_KEY_LEN; diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index a725789a..f1767cf1 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -16,7 +16,6 @@ use httparse; use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; -use tokio_timer::Error as TimerError; // re-export for convinience use crate::body::Body; @@ -178,9 +177,6 @@ impl ResponseError for JsonError {} /// `InternalServerError` for `FormError` impl ResponseError for FormError {} -/// `InternalServerError` for `TimerError` -impl ResponseError for TimerError {} - #[cfg(feature = "openssl")] /// `InternalServerError` for `openssl::ssl::Error` impl ResponseError for open_ssl::ssl::Error {} diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 272270ca..ffa00288 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -1,9 +1,7 @@ -use std::future::Future; use std::io; use std::marker::PhantomData; use std::mem::MaybeUninit; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::task::Poll; use actix_codec::Decoder; use bytes::{Bytes, BytesMut}; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 8c089602..154b3ed4 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -3,15 +3,15 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Instant; -use std::{fmt, io, io::Write, net}; +use std::{fmt, io, net}; -use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; +use actix_codec::{AsyncRead, Decoder, Encoder, Framed, FramedParts}; +use actix_rt::time::{delay, Delay}; use actix_server_config::IoStream; use actix_service::Service; use bitflags::bitflags; use bytes::{BufMut, BytesMut}; use log::{error, trace}; -use tokio_timer::{delay, Delay}; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::cloneable::CloneableService; @@ -893,10 +893,9 @@ mod tests { use crate::h1::{ExpectHandler, UpgradeHandler}; use crate::test::TestBuffer; - #[test] - fn test_req_parse_err() { - let mut sys = actix_rt::System::new("test"); - let _ = sys.block_on(lazy(|cx| { + #[actix_rt::test] + async fn test_req_parse_err() { + lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( @@ -918,7 +917,7 @@ mod tests { assert!(inner.flags.contains(Flags::READ_DISCONNECT)); assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n"); } - ok::<_, ()>(()) - })); + }) + .await; } } diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 79831eae..d6b4a9f1 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,5 +1,3 @@ -use std::future::Future; -use std::pin::Pin; use std::task::{Context, Poll}; use actix_server_config::ServerConfig; diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 2b52cfd8..46f2f972 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -1,7 +1,6 @@ //! Payload stream use std::cell::RefCell; use std::collections::VecDeque; -use std::future::Future; use std::pin::Pin; use std::rc::{Rc, Weak}; use std::task::{Context, Poll}; @@ -227,24 +226,19 @@ impl Inner { #[cfg(test)] mod tests { use super::*; - use actix_rt::Runtime; - use futures::future::{poll_fn, ready}; + use futures::future::poll_fn; - #[test] - fn test_unread_data() { - Runtime::new().unwrap().block_on(async { - let (_, mut payload) = Payload::create(false); + #[actix_rt::test] + async fn test_unread_data() { + let (_, mut payload) = Payload::create(false); - payload.unread_data(Bytes::from("data")); - assert!(!payload.is_empty()); - assert_eq!(payload.len(), 4); + payload.unread_data(Bytes::from("data")); + assert!(!payload.is_empty()); + assert_eq!(payload.len(), 4); - assert_eq!( - Bytes::from("data"), - poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap() - ); - - ready(()) - }); + assert_eq!( + Bytes::from("data"), + poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap() + ); } } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index ce8ff662..197c9288 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -9,7 +9,7 @@ use actix_codec::Framed; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use futures::future::{ok, Ready}; -use futures::{ready, Stream}; +use futures::ready; use crate::body::MessageBody; use crate::cloneable::CloneableService; diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 43ab53d0..ce46fbe9 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,6 +1,4 @@ -use std::future::Future; use std::marker::PhantomData; -use std::pin::Pin; use std::task::{Context, Poll}; use actix_codec::Framed; diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs index 7057bf1c..7af0b124 100644 --- a/actix-http/src/h1/utils.rs +++ b/actix-http/src/h1/utils.rs @@ -3,7 +3,6 @@ use std::pin::Pin; use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use futures::Sink; use crate::body::{BodySize, MessageBody, ResponseBody}; use crate::error::Error; diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 1a52a60f..18855380 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -7,6 +7,7 @@ use std::time::Instant; use std::{fmt, mem, net}; use actix_codec::{AsyncRead, AsyncWrite}; +use actix_rt::time::Delay; use actix_server_config::IoStream; use actix_service::Service; use bitflags::bitflags; @@ -19,7 +20,6 @@ use http::header::{ }; use http::HttpTryFrom; use log::{debug, error, trace}; -use tokio_timer::Delay; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::cloneable::CloneableService; @@ -139,7 +139,7 @@ where on_connect.set(&mut req.extensions_mut()); } - tokio_executor::current_thread::spawn(ServiceResponse::< + actix_rt::spawn(ServiceResponse::< S::Future, S::Response, S::Error, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 4d17347d..b57fdddc 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -4,8 +4,7 @@ clippy::too_many_arguments, clippy::new_without_default, clippy::borrow_interior_mutable_const, - clippy::write_with_newline, - unused_imports + clippy::write_with_newline )] #[macro_use] diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index f2cc6414..b3ec04d1 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -1,4 +1,3 @@ -use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index a5f18cc7..5eb0228d 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -7,7 +7,6 @@ use std::task::{Context, Poll}; use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; -use futures::future::{ok, Ready}; use futures::stream::Stream; use serde::Serialize; use serde_json; diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index e18b1013..7340c15f 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -8,7 +8,7 @@ use actix_server_config::{ Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig, }; use actix_service::{IntoServiceFactory, Service, ServiceFactory}; -use bytes::{Buf, BufMut, Bytes, BytesMut}; +use bytes::{BufMut, Bytes, BytesMut}; use futures::{ready, Future}; use h2::server::{self, Handshake}; use pin_project::{pin_project, project}; @@ -659,7 +659,7 @@ impl AsyncRead for Io { // } } -impl tokio_io::AsyncWrite for Io { +impl actix_codec::AsyncWrite for Io { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 26f2c223..744f057d 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -7,7 +7,7 @@ use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_server_config::IoStream; -use bytes::{Buf, Bytes, BytesMut}; +use bytes::{Bytes, BytesMut}; use http::header::{self, HeaderName, HeaderValue}; use http::{HttpTryFrom, Method, Uri, Version}; use percent_encoding::percent_encode; diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 05248966..cdcaea02 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -3,7 +3,7 @@ use bytes::Bytes; use futures::future::{self, ok}; use actix_http::{http, HttpService, Request, Response}; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -27,65 +27,58 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[test] -fn test_h1_v2() { - block_on(async { - let srv = TestServer::start(move || { - HttpService::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - }); +#[actix_rt::test] +async fn test_h1_v2() { + let srv = TestServer::start(move || { + HttpService::build().finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + }); - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.get("/").header("x-test", "111").send(); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + let request = srv.get("/").header("x-test", "111").send(); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let mut response = srv.post("/").send().await.unwrap(); - assert!(response.status().is_success()); + let mut response = srv.post("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_connection_close() { - block_on(async { - let srv = TestServer::start(move || { - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map(|_| ()) - }); +#[actix_rt::test] +async fn test_connection_close() { + let srv = TestServer::start(move || { + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map(|_| ()) + }); - let response = srv.get("/").force_close().send().await.unwrap(); - assert!(response.status().is_success()); - }) + let response = srv.get("/").force_close().send().await.unwrap(); + assert!(response.status().is_success()); } -#[test] -fn test_with_query_parameter() { - block_on(async { - let srv = TestServer::start(move || { - HttpService::build() - .finish(|req: Request| { - if req.uri().query().unwrap().contains("qp=") { - ok::<_, ()>(Response::Ok().finish()) - } else { - ok::<_, ()>(Response::BadRequest().finish()) - } - }) - .map(|_| ()) - }); +#[actix_rt::test] +async fn test_with_query_parameter() { + let srv = TestServer::start(move || { + HttpService::build() + .finish(|req: Request| { + if req.uri().query().unwrap().contains("qp=") { + ok::<_, ()>(Response::Ok().finish()) + } else { + ok::<_, ()>(Response::BadRequest().finish()) + } + }) + .map(|_| ()) + }); - let request = srv.request(http::Method::GET, srv.url("/?qp=5")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - }) + let request = srv.request(http::Method::GET, srv.url("/?qp=5")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 7eaa8e2a..0fdddaa1 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -2,7 +2,7 @@ use std::io; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use actix_server::ssl::OpensslAcceptor; use actix_server_config::ServerConfig; use actix_service::{factory_fn_cfg, pipeline_factory, service_fn2, ServiceFactory}; @@ -57,156 +57,147 @@ fn ssl_acceptor() -> io::Result io::Result<()> { - block_on(async { - let openssl = ssl_acceptor()?; - let srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|_| ok::<_, Error>(Response::Ok().finish())) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2() -> io::Result<()> { + let openssl = ssl_acceptor()?; + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) - }) + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + Ok(()) } -#[test] -fn test_h2_1() -> io::Result<()> { - block_on(async { - let openssl = ssl_acceptor()?; - let srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), Version::HTTP_2); - ok::<_, Error>(Response::Ok().finish()) - }) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_1() -> io::Result<()> { + let openssl = ssl_acceptor()?; + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), Version::HTTP_2); + ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) - }) + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + Ok(()) } -#[test] -fn test_h2_body() -> io::Result<()> { - block_on(async { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); - let openssl = ssl_acceptor()?; - let mut srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|mut req: Request<_>| { - async move { - let body = load_body(req.take_payload()).await?; - Ok::<_, Error>(Response::Ok().body(body)) - } - }) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_body() -> io::Result<()> { + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let openssl = ssl_acceptor()?; + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|mut req: Request<_>| { + async move { + let body = load_body(req.take_payload()).await?; + Ok::<_, Error>(Response::Ok().body(body)) + } + }) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send_body(data.clone()).await.unwrap(); - assert!(response.status().is_success()); + let response = srv.sget("/").send_body(data.clone()).await.unwrap(); + assert!(response.status().is_success()); - let body = srv.load_body(response).await.unwrap(); - assert_eq!(&body, data.as_bytes()); - Ok(()) - }) + let body = srv.load_body(response).await.unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) } -#[test] -fn test_h2_content_length() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); +#[actix_rt::test] +async fn test_h2_content_length() { + let openssl = ssl_acceptor().unwrap(); - let srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - ok::<_, ()>(Response::new(statuses[indx])) - }) - .map_err(|_| ()), - ) - }); + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); - { - for i in 0..4 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); + { + for i in 0..4 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), None); - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), None); } - }) + + for i in 4..6 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } } -#[test] -fn test_h2_headers() { - block_on(async { - let data = STR.repeat(10); - let data2 = data.clone(); - let openssl = ssl_acceptor().unwrap(); +#[actix_rt::test] +async fn test_h2_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - let data = data.clone(); - pipeline_factory(openssl + let mut srv = TestServer::start(move || { + let data = data.clone(); + pipeline_factory(openssl .clone() .map_err(|e| println!("Openssl error: {}", e))) .and_then( @@ -232,15 +223,14 @@ fn test_h2_headers() { } ok::<_, ()>(builder.body(data.clone())) }).map_err(|_| ())) - }); + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from(data2)); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from(data2)); } const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -265,281 +255,262 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[test] -fn test_h2_body2() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_body2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_h2_head_empty() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_head_empty() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); } -#[test] -fn test_h2_head_binary() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|_| { - ok::<_, ()>( - Response::Ok().content_length(STR.len() as u64).body(STR), - ) - }) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_head_binary() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); } -#[test] -fn test_h2_head_binary2() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - let srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_head_binary2() { + let openssl = ssl_acceptor().unwrap(); + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - }) + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } } -#[test] -fn test_h2_body_length() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); +#[actix_rt::test] +async fn test_h2_body_length() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_h2_body_chunked_explicit() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_h2_response_http_error_handling() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(factory_fn_cfg(|_: &ServerConfig| { + ok::<_, ()>(service_fn2(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .body(body::SizedStream::new(STR.len() as u64, body)), + .header(header::CONTENT_TYPE, broken_header) + .body(STR), ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) -} - -#[test] -fn test_h2_body_chunked_explicit() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|_| { - let body = - once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) -} - -#[test] -fn test_h2_response_http_error_handling() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(factory_fn_cfg(|_: &ServerConfig| { - ok::<_, ()>(service_fn2(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(header::CONTENT_TYPE, broken_header) - .body(STR), - ) - })) })) - .map_err(|_| ()), - ) - }); + })) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + let response = srv.sget("/").send().await.unwrap(); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); } -#[test] -fn test_h2_service_error() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); +#[actix_rt::test] +async fn test_h2_service_error() { + let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(|_| err::(ErrorBadRequest("error"))) - .map_err(|_| ()), - ) - }); + let mut srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(|_| err::(ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let response = srv.sget("/").send().await.unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"error")); } -#[test] -fn test_h2_on_connect() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); +#[actix_rt::test] +async fn test_h2_on_connect() { + let openssl = ssl_acceptor().unwrap(); - let srv = TestServer::start(move || { - pipeline_factory( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .on_connect(|_| 10usize) - .h2(|req: Request| { - assert!(req.extensions().contains::()); - ok::<_, ()>(Response::Ok().finish()) - }) - .map_err(|_| ()), - ) - }); + let srv = TestServer::start(move || { + pipeline_factory( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .on_connect(|_| 10usize) + .h2(|req: Request| { + assert!(req.extensions().contains::()); + ok::<_, ()>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - }) + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); } diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index c36d0579..4a649ca3 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -4,7 +4,7 @@ use actix_http::error::PayloadError; use actix_http::http::header::{self, HeaderName, HeaderValue}; use actix_http::http::{Method, StatusCode, Version}; use actix_http::{body, error, Error, HttpService, Request, Response}; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use actix_server::ssl::RustlsAcceptor; use actix_server_config::ServerConfig; use actix_service::{factory_fn_cfg, pipeline_factory, service_fn2, ServiceFactory}; @@ -45,140 +45,131 @@ fn ssl_acceptor() -> io::Result Ok(RustlsAcceptor::new(config)) } -#[test] -fn test_h2() -> io::Result<()> { - block_on(async { - let rustls = ssl_acceptor()?; - let srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2() -> io::Result<()> { + let rustls = ssl_acceptor()?; + let srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) - }) + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + Ok(()) } -#[test] -fn test_h2_1() -> io::Result<()> { - block_on(async { - let rustls = ssl_acceptor()?; - let srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), Version::HTTP_2); - future::ok::<_, Error>(Response::Ok().finish()) - }) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_1() -> io::Result<()> { + let rustls = ssl_acceptor()?; + let srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), Version::HTTP_2); + future::ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) - }) + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + Ok(()) } -#[test] -fn test_h2_body1() -> io::Result<()> { - block_on(async { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); - let rustls = ssl_acceptor()?; - let mut srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|mut req: Request<_>| { - async move { - let body = load_body(req.take_payload()).await?; - Ok::<_, Error>(Response::Ok().body(body)) - } - }) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_body1() -> io::Result<()> { + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let rustls = ssl_acceptor()?; + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|mut req: Request<_>| { + async move { + let body = load_body(req.take_payload()).await?; + Ok::<_, Error>(Response::Ok().body(body)) + } + }) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send_body(data.clone()).await.unwrap(); - assert!(response.status().is_success()); + let response = srv.sget("/").send_body(data.clone()).await.unwrap(); + assert!(response.status().is_success()); - let body = srv.load_body(response).await.unwrap(); - assert_eq!(&body, data.as_bytes()); - Ok(()) - }) + let body = srv.load_body(response).await.unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) } -#[test] -fn test_h2_content_length() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); +#[actix_rt::test] +async fn test_h2_content_length() { + let rustls = ssl_acceptor().unwrap(); - let srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }) - .map_err(|_| ()), - ) - }); + let srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); - { - for i in 0..4 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); + { + for i in 0..4 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), None); - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), None); } - }) + + for i in 4..6 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = req.await.unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } } -#[test] -fn test_h2_headers() { - block_on(async { - let data = STR.repeat(10); - let data2 = data.clone(); - let rustls = ssl_acceptor().unwrap(); +#[actix_rt::test] +async fn test_h2_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - let data = data.clone(); - pipeline_factory(rustls + let mut srv = TestServer::start(move || { + let data = data.clone(); + pipeline_factory(rustls .clone() .map_err(|e| println!("Rustls error: {}", e))) .and_then( @@ -204,15 +195,14 @@ fn test_h2_headers() { } future::ok::<_, ()>(config.body(data.clone())) }).map_err(|_| ())) - }); + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from(data2)); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from(data2)); } const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -237,238 +227,215 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[test] -fn test_h2_body2() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_body2() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_h2_head_empty() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); +#[actix_rt::test] +async fn test_h2_head_empty() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); } -#[test] -fn test_h2_head_binary() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|_| { +#[actix_rt::test] +async fn test_h2_head_binary() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); +} + +#[actix_rt::test] +async fn test_h2_head_binary2() { + let rustls = ssl_acceptor().unwrap(); + let srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.shead("/").send().await.unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[actix_rt::test] +async fn test_h2_body_length() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_h2_body_chunked_explicit() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.sget("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_h2_response_http_error_handling() { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(factory_fn_cfg(|_: &ServerConfig| { + ok::<_, ()>(service_fn2(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .content_length(STR.len() as u64) + .header(http::header::CONTENT_TYPE, broken_header) .body(STR), ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); - }) -} - -#[test] -fn test_h2_head_binary2() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); - let srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - }) -} - -#[test] -fn test_h2_body_length() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok().body(body::SizedStream::new( - STR.len() as u64, - body, - )), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) -} - -#[test] -fn test_h2_body_chunked_explicit() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|_| { - let body = - once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) -} - -#[test] -fn test_h2_response_http_error_handling() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); - - let mut srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(factory_fn_cfg(|_: &ServerConfig| { - ok::<_, ()>(service_fn2(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header( - http::header::CONTENT_TYPE, - broken_header, - ) - .body(STR), - ) - })) })) - .map_err(|_| ()), - ) - }); + })) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + let response = srv.sget("/").send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); } -#[test] -fn test_h2_service_error() { - block_on(async { - let rustls = ssl_acceptor().unwrap(); +#[actix_rt::test] +async fn test_h2_service_error() { + let rustls = ssl_acceptor().unwrap(); - let mut srv = TestServer::start(move || { - pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) - .and_then( - HttpService::build() - .h2(|_| err::(error::ErrorBadRequest("error"))) - .map_err(|_| ()), - ) - }); + let mut srv = TestServer::start(move || { + pipeline_factory(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(|_| err::(error::ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + let response = srv.sget("/").send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"error")); } diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index c37e8fad..a3ce3f9c 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -2,395 +2,361 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; +use actix_rt::time::delay_for; use actix_server_config::ServerConfig; use actix_service::{factory_fn_cfg, pipeline, service_fn, ServiceFactory}; use bytes::Bytes; use futures::future::{self, err, ok, ready, FutureExt}; use futures::stream::{once, StreamExt}; use regex::Regex; -use tokio_timer::delay_for; use actix_http::httpmessage::HttpMessage; use actix_http::{ body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, }; -#[test] -fn test_h1() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .h1(|req: Request| { - assert!(req.peer_addr().is_some()); - future::ok::<_, ()>(Response::Ok().finish()) - }) - }); +#[actix_rt::test] +async fn test_h1() { + let srv = TestServer::start(|| { + HttpService::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .h1(|req: Request| { + assert!(req.peer_addr().is_some()); + future::ok::<_, ()>(Response::Ok().finish()) + }) + }); - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - }) + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); } -#[test] -fn test_h1_2() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), http::Version::HTTP_11); - future::ok::<_, ()>(Response::Ok().finish()) - }) - .map(|_| ()) - }); +#[actix_rt::test] +async fn test_h1_2() { + let srv = TestServer::start(|| { + HttpService::build() + .keep_alive(KeepAlive::Disabled) + .client_timeout(1000) + .client_disconnect(1000) + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), http::Version::HTTP_11); + future::ok::<_, ()>(Response::Ok().finish()) + }) + .map(|_| ()) + }); - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - }) + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); } -#[test] -fn test_expect_continue() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build() - .expect(service_fn(|req: Request| { +#[actix_rt::test] +async fn test_expect_continue() { + let srv = TestServer::start(|| { + HttpService::build() + .expect(service_fn(|req: Request| { + if req.head().uri.query() == Some("yes=") { + ok(req) + } else { + err(error::ErrorPreconditionFailed("error")) + } + })) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); +} + +#[actix_rt::test] +async fn test_expect_continue_h1() { + let srv = TestServer::start(|| { + HttpService::build() + .expect(service_fn(|req: Request| { + delay_for(Duration::from_millis(20)).then(move |_| { if req.head().uri.query() == Some("yes=") { ok(req) } else { err(error::ErrorPreconditionFailed("error")) } - })) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = - stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); - }) -} - -#[test] -fn test_expect_continue_h1() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build() - .expect(service_fn(|req: Request| { - delay_for(Duration::from_millis(20)).then(move |_| { - if req.head().uri.query() == Some("yes=") { - ok(req) - } else { - err(error::ErrorPreconditionFailed("error")) - } - }) - })) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = - stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); - }) -} - -#[test] -fn test_chunked_payload() { - block_on(async { - let chunk_sizes = vec![32768, 32, 32768]; - let total_size: usize = chunk_sizes.iter().sum(); - - let srv = TestServer::start(|| { - HttpService::build().h1(service_fn(|mut request: Request| { - request - .take_payload() - .map(|res| match res { - Ok(pl) => pl, - Err(e) => panic!(format!("Error reading payload: {}", e)), - }) - .fold(0usize, |acc, chunk| ready(acc + chunk.len())) - .map(|req_size| { - Ok::<_, Error>(Response::Ok().body(format!("size={}", req_size))) - }) + }) })) - }); + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let returned_size = { - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream - .write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); - for chunk_size in chunk_sizes.iter() { - let mut bytes = Vec::new(); - let random_bytes: Vec = - (0..*chunk_size).map(|_| rand::random::()).collect(); - - bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes()); - bytes.extend(&random_bytes[..]); - bytes.extend(b"\r\n"); - let _ = stream.write_all(&bytes); - } - - let _ = stream.write_all(b"0\r\n\r\n"); - stream.shutdown(net::Shutdown::Write).unwrap(); - - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - - let re = Regex::new(r"size=(\d+)").unwrap(); - let size: usize = match re.captures(&data) { - Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), - None => { - panic!(format!("Failed to find size in HTTP Response: {}", data)) - } - }; - size - }; - - assert_eq!(returned_size, total_size); - }) + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); } -#[test] -fn test_slow_request() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build() - .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); +#[actix_rt::test] +async fn test_chunked_payload() { + let chunk_sizes = vec![32768, 32, 32768]; + let total_size: usize = chunk_sizes.iter().sum(); - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - }) -} - -#[test] -fn test_http1_malformed_request() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 400 Bad Request")); - }) -} - -#[test] -fn test_http1_keepalive() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - }) -} - -#[test] -fn test_http1_keepalive_timeout() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build() - .keep_alive(1) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - thread::sleep(Duration::from_millis(1100)); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); - }) -} - -#[test] -fn test_http1_keepalive_close() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); + let srv = TestServer::start(|| { + HttpService::build().h1(service_fn(|mut request: Request| { + request + .take_payload() + .map(|res| match res { + Ok(pl) => pl, + Err(e) => panic!(format!("Error reading payload: {}", e)), + }) + .fold(0usize, |acc, chunk| ready(acc + chunk.len())) + .map(|req_size| { + Ok::<_, Error>(Response::Ok().body(format!("size={}", req_size))) + }) + })) + }); + let returned_size = { let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let _ = stream - .write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + .write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); - }) -} + for chunk_size in chunk_sizes.iter() { + let mut bytes = Vec::new(); + let random_bytes: Vec = + (0..*chunk_size).map(|_| rand::random::()).collect(); -#[test] -fn test_http10_keepalive_default_close() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); - }) -} - -#[test] -fn test_http10_keepalive() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all( - b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n", - ); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); - }) -} - -#[test] -fn test_http1_keepalive_disabled() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); - }) -} - -#[test] -fn test_content_length() { - block_on(async { - use actix_http::http::{ - header::{HeaderName, HeaderValue}, - StatusCode, - }; - - let srv = TestServer::start(|| { - HttpService::build().h1(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }) - }); - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - - { - for i in 0..4 { - let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); - let response = req.send().await.unwrap(); - assert_eq!(response.headers().get(&header), None); - - let req = srv.request(http::Method::HEAD, srv.url(&format!("/{}", i))); - let response = req.send().await.unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); - let response = req.send().await.unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } + bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes()); + bytes.extend(&random_bytes[..]); + bytes.extend(b"\r\n"); + let _ = stream.write_all(&bytes); } - }) + + let _ = stream.write_all(b"0\r\n\r\n"); + stream.shutdown(net::Shutdown::Write).unwrap(); + + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + + let re = Regex::new(r"size=(\d+)").unwrap(); + let size: usize = match re.captures(&data) { + Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), + None => panic!(format!("Failed to find size in HTTP Response: {}", data)), + }; + size + }; + + assert_eq!(returned_size, total_size); } -#[test] -fn test_h1_headers() { - block_on(async { - let data = STR.repeat(10); - let data2 = data.clone(); +#[actix_rt::test] +async fn test_slow_request() { + let srv = TestServer::start(|| { + HttpService::build() + .client_timeout(100) + .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); - let mut srv = TestServer::start(move || { - let data = data.clone(); - HttpService::build().h1(move |_| { + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); +} + +#[actix_rt::test] +async fn test_http1_malformed_request() { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 400 Bad Request")); +} + +#[actix_rt::test] +async fn test_http1_keepalive() { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); +} + +#[actix_rt::test] +async fn test_http1_keepalive_timeout() { + let srv = TestServer::start(|| { + HttpService::build() + .keep_alive(1) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + thread::sleep(Duration::from_millis(1100)); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[actix_rt::test] +async fn test_http1_keepalive_close() { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = + stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[actix_rt::test] +async fn test_http10_keepalive_default_close() { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[actix_rt::test] +async fn test_http10_keepalive() { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream + .write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[actix_rt::test] +async fn test_http1_keepalive_disabled() { + let srv = TestServer::start(|| { + HttpService::build() + .keep_alive(KeepAlive::Disabled) + .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); + let mut data = vec![0; 1024]; + let _ = stream.read(&mut data); + assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); + + let mut data = vec![0; 1024]; + let res = stream.read(&mut data).unwrap(); + assert_eq!(res, 0); +} + +#[actix_rt::test] +async fn test_content_length() { + use actix_http::http::{ + header::{HeaderName, HeaderValue}, + StatusCode, + }; + + let srv = TestServer::start(|| { + HttpService::build().h1(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }) + }); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + { + for i in 0..4 { + let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); + let response = req.send().await.unwrap(); + assert_eq!(response.headers().get(&header), None); + + let req = srv.request(http::Method::HEAD, srv.url(&format!("/{}", i))); + let response = req.send().await.unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); + let response = req.send().await.unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} + +#[actix_rt::test] +async fn test_h1_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + + let mut srv = TestServer::start(move || { + let data = data.clone(); + HttpService::build().h1(move |_| { let mut builder = Response::Ok(); for idx in 0..90 { builder.header( @@ -412,15 +378,14 @@ fn test_h1_headers() { } future::ok::<_, ()>(builder.body(data.clone())) }) - }); + }); - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from(data2)); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from(data2)); } const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ @@ -445,230 +410,210 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[test] -fn test_h1_body() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - }); +#[actix_rt::test] +async fn test_h1_body() { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + }); - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_h1_head_empty() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - }); +#[actix_rt::test] +async fn test_h1_head_empty() { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + }); - let response = srv.head("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); } -#[test] -fn test_h1_head_binary() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build().h1(|_| { - ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) - }) - }); +#[actix_rt::test] +async fn test_h1_head_binary() { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| { + ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) + }) + }); - let response = srv.head("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert!(bytes.is_empty()); } -#[test] -fn test_h1_head_binary2() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - }); +#[actix_rt::test] +async fn test_h1_head_binary2() { + let srv = TestServer::start(|| { + HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR))) + }); - let response = srv.head("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - }) + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } } -#[test] -fn test_h1_body_length() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build().h1(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - }); +#[actix_rt::test] +async fn test_h1_body_length() { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| { + let body = once(ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + }); - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_h1_body_chunked_explicit() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build().h1(|_| { - let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); +#[actix_rt::test] +async fn test_h1_body_chunked_explicit() { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| { + let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + }); + + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!( + response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "chunked" + ); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_h1_body_chunked_implicit() { + let mut srv = TestServer::start(|| { + HttpService::build().h1(|_| { + let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>(Response::Ok().streaming(body)) + }) + }); + + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!( + response + .headers() + .get(header::TRANSFER_ENCODING) + .unwrap() + .to_str() + .unwrap(), + "chunked" + ); + + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_h1_response_http_error_handling() { + let mut srv = TestServer::start(|| { + HttpService::build().h1(factory_fn_cfg(|_: &ServerConfig| { + ok::<_, ()>(pipeline(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), ) - }) - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "chunked" - ); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) -} - -#[test] -fn test_h1_body_chunked_implicit() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build().h1(|_| { - let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) - }) - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "chunked" - ); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) -} - -#[test] -fn test_h1_response_http_error_handling() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build().h1(factory_fn_cfg(|_: &ServerConfig| { - ok::<_, ()>(pipeline(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) - })) })) - }); + })) + }); - let response = srv.get("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + let response = srv.get("/").send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); } -#[test] -fn test_h1_service_error() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build() - .h1(|_| future::err::(error::ErrorBadRequest("error"))) - }); +#[actix_rt::test] +async fn test_h1_service_error() { + let mut srv = TestServer::start(|| { + HttpService::build() + .h1(|_| future::err::(error::ErrorBadRequest("error"))) + }); - let response = srv.get("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + let response = srv.get("/").send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); - }) + // read response + let bytes = srv.load_body(response).await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"error")); } -#[test] -fn test_h1_on_connect() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::build() - .on_connect(|_| 10usize) - .h1(|req: Request| { - assert!(req.extensions().contains::()); - future::ok::<_, ()>(Response::Ok().finish()) - }) - }); +#[actix_rt::test] +async fn test_h1_on_connect() { + let srv = TestServer::start(|| { + HttpService::build() + .on_connect(|_| 10usize) + .h1(|req: Request| { + assert!(req.extensions().contains::()); + future::ok::<_, ()>(Response::Ok().finish()) + }) + }); - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - }) + let response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); } diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 74c1cb40..aa81bc41 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -1,6 +1,6 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use actix_utils::framed::FramedTransport; use bytes::{Bytes, BytesMut}; use futures::future; @@ -34,53 +34,51 @@ async fn service(msg: ws::Frame) -> Result { Ok(msg) } -#[test] -fn test_simple() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build() - .upgrade(actix_service::service_fn(ws_service)) - .finish(|_| future::ok::<_, ()>(Response::NotFound())) - }); +#[actix_rt::test] +async fn test_simple() { + let mut srv = TestServer::start(|| { + HttpService::build() + .upgrade(actix_service::service_fn(ws_service)) + .finish(|_| future::ok::<_, ()>(Response::NotFound())) + }); - // client service - let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Text(Some(BytesMut::from("text"))) - ); + // client service + let mut framed = srv.ws().await.unwrap(); + framed + .send(ws::Message::Text("text".to_string())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Text(Some(BytesMut::from("text"))) + ); - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) - ); + framed + .send(ws::Message::Binary("text".into())) + .await + .unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) + ); - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Pong("text".to_string().into()) - ); + framed.send(ws::Message::Ping("text".into())).await.unwrap(); + let (item, mut framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Pong("text".to_string().into()) + ); - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); - let (item, _framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Close(Some(ws::CloseCode::Normal.into())) - ); - }) + let (item, _framed) = framed.into_future().await; + assert_eq!( + item.unwrap().unwrap(), + ws::Frame::Close(Some(ws::CloseCode::Normal.into())) + ); } diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 2980a775..5dfd2ae6 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -607,144 +607,130 @@ mod tests { use super::*; use actix_web::http::StatusCode; - use actix_web::test::{self, block_on, TestRequest}; + use actix_web::test::{self, TestRequest}; use actix_web::{web, App, Error, HttpResponse}; const COOKIE_KEY_MASTER: [u8; 32] = [0; 32]; const COOKIE_NAME: &'static str = "actix_auth"; const COOKIE_LOGIN: &'static str = "test"; - #[test] - fn test_identity() { - block_on(async { - let mut srv = test::init_service( - App::new() - .wrap(IdentityService::new( - CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) - .domain("www.rust-lang.org") - .name(COOKIE_NAME) - .path("/") - .secure(true), - )) - .service(web::resource("/index").to(|id: Identity| { - if id.identity().is_some() { - HttpResponse::Created() - } else { - HttpResponse::Ok() - } - })) - .service(web::resource("/login").to(|id: Identity| { - id.remember(COOKIE_LOGIN.to_string()); + #[actix_rt::test] + async fn test_identity() { + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) + .domain("www.rust-lang.org") + .name(COOKIE_NAME) + .path("/") + .secure(true), + )) + .service(web::resource("/index").to(|id: Identity| { + if id.identity().is_some() { + HttpResponse::Created() + } else { HttpResponse::Ok() - })) - .service(web::resource("/logout").to(|id: Identity| { - if id.identity().is_some() { - id.forget(); - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - })), - ) - .await; - let resp = test::call_service( - &mut srv, - TestRequest::with_uri("/index").to_request(), - ) - .await; - assert_eq!(resp.status(), StatusCode::OK); + } + })) + .service(web::resource("/login").to(|id: Identity| { + id.remember(COOKIE_LOGIN.to_string()); + HttpResponse::Ok() + })) + .service(web::resource("/logout").to(|id: Identity| { + if id.identity().is_some() { + id.forget(); + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + })), + ) + .await; + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/index").to_request()) + .await; + assert_eq!(resp.status(), StatusCode::OK); - let resp = test::call_service( - &mut srv, - TestRequest::with_uri("/login").to_request(), - ) - .await; - assert_eq!(resp.status(), StatusCode::OK); - let c = resp.response().cookies().next().unwrap().to_owned(); + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()) + .await; + assert_eq!(resp.status(), StatusCode::OK); + let c = resp.response().cookies().next().unwrap().to_owned(); - let resp = test::call_service( - &mut srv, - TestRequest::with_uri("/index") - .cookie(c.clone()) - .to_request(), - ) - .await; - assert_eq!(resp.status(), StatusCode::CREATED); + let resp = test::call_service( + &mut srv, + TestRequest::with_uri("/index") + .cookie(c.clone()) + .to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::CREATED); - let resp = test::call_service( - &mut srv, - TestRequest::with_uri("/logout") - .cookie(c.clone()) - .to_request(), - ) - .await; - assert_eq!(resp.status(), StatusCode::OK); - assert!(resp.headers().contains_key(header::SET_COOKIE)) - }) + let resp = test::call_service( + &mut srv, + TestRequest::with_uri("/logout") + .cookie(c.clone()) + .to_request(), + ) + .await; + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)) } - #[test] - fn test_identity_max_age_time() { - block_on(async { - let duration = Duration::days(1); - let mut srv = test::init_service( - App::new() - .wrap(IdentityService::new( - CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) - .domain("www.rust-lang.org") - .name(COOKIE_NAME) - .path("/") - .max_age_time(duration) - .secure(true), - )) - .service(web::resource("/login").to(|id: Identity| { - id.remember("test".to_string()); - HttpResponse::Ok() - })), - ) - .await; - let resp = test::call_service( - &mut srv, - TestRequest::with_uri("/login").to_request(), - ) - .await; - assert_eq!(resp.status(), StatusCode::OK); - assert!(resp.headers().contains_key(header::SET_COOKIE)); - let c = resp.response().cookies().next().unwrap().to_owned(); - assert_eq!(duration, c.max_age().unwrap()); - }) + #[actix_rt::test] + async fn test_identity_max_age_time() { + let duration = Duration::days(1); + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) + .domain("www.rust-lang.org") + .name(COOKIE_NAME) + .path("/") + .max_age_time(duration) + .secure(true), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })), + ) + .await; + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()) + .await; + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + let c = resp.response().cookies().next().unwrap().to_owned(); + assert_eq!(duration, c.max_age().unwrap()); } - #[test] - fn test_identity_max_age() { - block_on(async { - let seconds = 60; - let mut srv = test::init_service( - App::new() - .wrap(IdentityService::new( - CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) - .domain("www.rust-lang.org") - .name(COOKIE_NAME) - .path("/") - .max_age(seconds) - .secure(true), - )) - .service(web::resource("/login").to(|id: Identity| { - id.remember("test".to_string()); - HttpResponse::Ok() - })), - ) - .await; - let resp = test::call_service( - &mut srv, - TestRequest::with_uri("/login").to_request(), - ) - .await; - assert_eq!(resp.status(), StatusCode::OK); - assert!(resp.headers().contains_key(header::SET_COOKIE)); - let c = resp.response().cookies().next().unwrap().to_owned(); - assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); - }) + #[actix_rt::test] + async fn test_identity_max_age() { + let seconds = 60; + let mut srv = test::init_service( + App::new() + .wrap(IdentityService::new( + CookieIdentityPolicy::new(&COOKIE_KEY_MASTER) + .domain("www.rust-lang.org") + .name(COOKIE_NAME) + .path("/") + .max_age(seconds) + .secure(true), + )) + .service(web::resource("/login").to(|id: Identity| { + id.remember("test".to_string()); + HttpResponse::Ok() + })), + ) + .await; + let resp = + test::call_service(&mut srv, TestRequest::with_uri("/login").to_request()) + .await; + assert_eq!(resp.status(), StatusCode::OK); + assert!(resp.headers().contains_key(header::SET_COOKIE)); + let c = resp.response().cookies().next().unwrap().to_owned(); + assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap()); } async fn create_identity_server< @@ -885,223 +871,202 @@ mod tests { assert!(cookies.get(COOKIE_NAME).is_none()); } - #[test] - fn test_identity_legacy_cookie_is_set() { - block_on(async { - let mut srv = create_identity_server(|c| c).await; - let mut resp = - test::call_service(&mut srv, TestRequest::with_uri("/").to_request()) - .await; - assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN); - assert_logged_in(resp, None).await; - }) + #[actix_rt::test] + async fn test_identity_legacy_cookie_is_set() { + let mut srv = create_identity_server(|c| c).await; + let mut resp = + test::call_service(&mut srv, TestRequest::with_uri("/").to_request()).await; + assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN); + assert_logged_in(resp, None).await; } - #[test] - fn test_identity_legacy_cookie_works() { - block_on(async { - let mut srv = create_identity_server(|c| c).await; - let cookie = legacy_login_cookie(COOKIE_LOGIN); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_no_login_cookie(&mut resp); - assert_logged_in(resp, Some(COOKIE_LOGIN)).await; - }) + #[actix_rt::test] + async fn test_identity_legacy_cookie_works() { + let mut srv = create_identity_server(|c| c).await; + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_no_login_cookie(&mut resp); + assert_logged_in(resp, Some(COOKIE_LOGIN)).await; } - #[test] - fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() { - block_on(async { - let mut srv = - create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; - let cookie = legacy_login_cookie(COOKIE_LOGIN); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NoTimestamp, - VisitTimeStampCheck::NewTimestamp, - ); - assert_logged_in(resp, None).await; - }) + #[actix_rt::test] + async fn test_identity_legacy_cookie_rejected_if_visit_timestamp_needed() { + let mut srv = + create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); + assert_logged_in(resp, None).await; } - #[test] - fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() { - block_on(async { - let mut srv = - create_identity_server(|c| c.login_deadline(Duration::days(90))).await; - let cookie = legacy_login_cookie(COOKIE_LOGIN); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NewTimestamp, - VisitTimeStampCheck::NoTimestamp, - ); - assert_logged_in(resp, None).await; - }) + #[actix_rt::test] + async fn test_identity_legacy_cookie_rejected_if_login_timestamp_needed() { + let mut srv = + create_identity_server(|c| c.login_deadline(Duration::days(90))).await; + let cookie = legacy_login_cookie(COOKIE_LOGIN); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); + assert_logged_in(resp, None).await; } - #[test] - fn test_identity_cookie_rejected_if_login_timestamp_needed() { - block_on(async { - let mut srv = - create_identity_server(|c| c.login_deadline(Duration::days(90))).await; - let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now())); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NewTimestamp, - VisitTimeStampCheck::NoTimestamp, - ); - assert_logged_in(resp, None).await; - }) + #[actix_rt::test] + async fn test_identity_cookie_rejected_if_login_timestamp_needed() { + let mut srv = + create_identity_server(|c| c.login_deadline(Duration::days(90))).await; + let cookie = login_cookie(COOKIE_LOGIN, None, Some(SystemTime::now())); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); + assert_logged_in(resp, None).await; } - #[test] - fn test_identity_cookie_rejected_if_visit_timestamp_needed() { - block_on(async { - let mut srv = - create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; - let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NoTimestamp, - VisitTimeStampCheck::NewTimestamp, - ); - assert_logged_in(resp, None).await; - }) + #[actix_rt::test] + async fn test_identity_cookie_rejected_if_visit_timestamp_needed() { + let mut srv = + create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; + let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); + assert_logged_in(resp, None).await; } - #[test] - fn test_identity_cookie_rejected_if_login_timestamp_too_old() { - block_on(async { - let mut srv = - create_identity_server(|c| c.login_deadline(Duration::days(90))).await; - let cookie = login_cookie( - COOKIE_LOGIN, - Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), - None, - ); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NewTimestamp, - VisitTimeStampCheck::NoTimestamp, - ); - assert_logged_in(resp, None).await; - }) + #[actix_rt::test] + async fn test_identity_cookie_rejected_if_login_timestamp_too_old() { + let mut srv = + create_identity_server(|c| c.login_deadline(Duration::days(90))).await; + let cookie = login_cookie( + COOKIE_LOGIN, + Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), + None, + ); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NewTimestamp, + VisitTimeStampCheck::NoTimestamp, + ); + assert_logged_in(resp, None).await; } - #[test] - fn test_identity_cookie_rejected_if_visit_timestamp_too_old() { - block_on(async { - let mut srv = - create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; - let cookie = login_cookie( - COOKIE_LOGIN, - None, - Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), - ); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::NoTimestamp, - VisitTimeStampCheck::NewTimestamp, - ); - assert_logged_in(resp, None).await; - }) + #[actix_rt::test] + async fn test_identity_cookie_rejected_if_visit_timestamp_too_old() { + let mut srv = + create_identity_server(|c| c.visit_deadline(Duration::days(90))).await; + let cookie = login_cookie( + COOKIE_LOGIN, + None, + Some(SystemTime::now() - Duration::days(180).to_std().unwrap()), + ); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::NoTimestamp, + VisitTimeStampCheck::NewTimestamp, + ); + assert_logged_in(resp, None).await; } - #[test] - fn test_identity_cookie_not_updated_on_login_deadline() { - block_on(async { - let mut srv = - create_identity_server(|c| c.login_deadline(Duration::days(90))).await; - let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_no_login_cookie(&mut resp); - assert_logged_in(resp, Some(COOKIE_LOGIN)).await; - }) + #[actix_rt::test] + async fn test_identity_cookie_not_updated_on_login_deadline() { + let mut srv = + create_identity_server(|c| c.login_deadline(Duration::days(90))).await; + let cookie = login_cookie(COOKIE_LOGIN, Some(SystemTime::now()), None); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_no_login_cookie(&mut resp); + assert_logged_in(resp, Some(COOKIE_LOGIN)).await; } - #[test] - fn test_identity_cookie_updated_on_visit_deadline() { - block_on(async { - let mut srv = create_identity_server(|c| { - c.visit_deadline(Duration::days(90)) - .login_deadline(Duration::days(90)) - }) - .await; - let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap(); - let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp)); - let mut resp = test::call_service( - &mut srv, - TestRequest::with_uri("/") - .cookie(cookie.clone()) - .to_request(), - ) - .await; - assert_login_cookie( - &mut resp, - COOKIE_LOGIN, - LoginTimestampCheck::OldTimestamp(timestamp), - VisitTimeStampCheck::NewTimestamp, - ); - assert_logged_in(resp, Some(COOKIE_LOGIN)).await; + #[actix_rt::test] + async fn test_identity_cookie_updated_on_visit_deadline() { + let mut srv = create_identity_server(|c| { + c.visit_deadline(Duration::days(90)) + .login_deadline(Duration::days(90)) }) + .await; + let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap(); + let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp)); + let mut resp = test::call_service( + &mut srv, + TestRequest::with_uri("/") + .cookie(cookie.clone()) + .to_request(), + ) + .await; + assert_login_cookie( + &mut resp, + COOKIE_LOGIN, + LoginTimestampCheck::OldTimestamp(timestamp), + VisitTimeStampCheck::NewTimestamp, + ); + assert_logged_in(resp, Some(COOKIE_LOGIN)).await; } } diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index dd7852c8..c4989676 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -813,12 +813,11 @@ mod tests { use actix_http::h1::Payload; use actix_utils::mpsc; use actix_web::http::header::{DispositionParam, DispositionType}; - use actix_web::test::block_on; use bytes::Bytes; use futures::future::lazy; - #[test] - fn test_boundary() { + #[actix_rt::test] + async fn test_boundary() { let headers = HeaderMap::new(); match Multipart::boundary(&headers) { Err(MultipartError::NoContentType) => (), @@ -891,246 +890,228 @@ mod tests { (bytes, headers) } - #[test] - fn test_multipart_no_end_crlf() { - block_on(async { - let (sender, payload) = create_stream(); - let (bytes, headers) = create_simple_request_with_header(); - let bytes_stripped = bytes.slice_to(bytes.len()); // strip crlf + #[actix_rt::test] + async fn test_multipart_no_end_crlf() { + let (sender, payload) = create_stream(); + let (bytes, headers) = create_simple_request_with_header(); + let bytes_stripped = bytes.slice_to(bytes.len()); // strip crlf - sender.send(Ok(bytes_stripped)).unwrap(); - drop(sender); // eof + sender.send(Ok(bytes_stripped)).unwrap(); + drop(sender); // eof - let mut multipart = Multipart::new(&headers, payload); + let mut multipart = Multipart::new(&headers, payload); - match multipart.next().await.unwrap() { - Ok(_) => (), - _ => unreachable!(), - } + match multipart.next().await.unwrap() { + Ok(_) => (), + _ => unreachable!(), + } - match multipart.next().await.unwrap() { - Ok(_) => (), - _ => unreachable!(), - } + match multipart.next().await.unwrap() { + Ok(_) => (), + _ => unreachable!(), + } - match multipart.next().await { - None => (), - _ => unreachable!(), - } - }) + match multipart.next().await { + None => (), + _ => unreachable!(), + } } - #[test] - fn test_multipart() { - block_on(async { - let (sender, payload) = create_stream(); - let (bytes, headers) = create_simple_request_with_header(); + #[actix_rt::test] + async fn test_multipart() { + let (sender, payload) = create_stream(); + let (bytes, headers) = create_simple_request_with_header(); - sender.send(Ok(bytes)).unwrap(); + sender.send(Ok(bytes)).unwrap(); - let mut multipart = Multipart::new(&headers, payload); - match multipart.next().await { - Some(Ok(mut field)) => { - let cd = field.content_disposition().unwrap(); - assert_eq!(cd.disposition, DispositionType::FormData); - assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); + let mut multipart = Multipart::new(&headers, payload); + match multipart.next().await { + Some(Ok(mut field)) => { + let cd = field.content_disposition().unwrap(); + assert_eq!(cd.disposition, DispositionType::FormData); + assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); - match field.next().await.unwrap() { - Ok(chunk) => assert_eq!(chunk, "test"), - _ => unreachable!(), - } - match field.next().await { - None => (), - _ => unreachable!(), - } + match field.next().await.unwrap() { + Ok(chunk) => assert_eq!(chunk, "test"), + _ => unreachable!(), } - _ => unreachable!(), - } - - match multipart.next().await.unwrap() { - Ok(mut field) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.next().await { - Some(Ok(chunk)) => assert_eq!(chunk, "data"), - _ => unreachable!(), - } - match field.next().await { - None => (), - _ => unreachable!(), - } + match field.next().await { + None => (), + _ => unreachable!(), } - _ => unreachable!(), } + _ => unreachable!(), + } - match multipart.next().await { - None => (), - _ => unreachable!(), - } - }); - } + match multipart.next().await.unwrap() { + Ok(mut field) => { + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); - #[test] - fn test_stream() { - block_on(async { - let (sender, payload) = create_stream(); - let (bytes, headers) = create_simple_request_with_header(); - - sender.send(Ok(bytes)).unwrap(); - - let mut multipart = Multipart::new(&headers, payload); - match multipart.next().await.unwrap() { - Ok(mut field) => { - let cd = field.content_disposition().unwrap(); - assert_eq!(cd.disposition, DispositionType::FormData); - assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); - - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.next().await.unwrap() { - Ok(chunk) => assert_eq!(chunk, "test"), - _ => unreachable!(), - } - match field.next().await { - None => (), - _ => unreachable!(), - } + match field.next().await { + Some(Ok(chunk)) => assert_eq!(chunk, "data"), + _ => unreachable!(), } - _ => unreachable!(), - } - - match multipart.next().await { - Some(Ok(mut field)) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.next().await { - Some(Ok(chunk)) => assert_eq!(chunk, "data"), - _ => unreachable!(), - } - match field.next().await { - None => (), - _ => unreachable!(), - } + match field.next().await { + None => (), + _ => unreachable!(), } - _ => unreachable!(), } + _ => unreachable!(), + } - match multipart.next().await { - None => (), - _ => unreachable!(), + match multipart.next().await { + None => (), + _ => unreachable!(), + } + } + + #[actix_rt::test] + async fn test_stream() { + let (sender, payload) = create_stream(); + let (bytes, headers) = create_simple_request_with_header(); + + sender.send(Ok(bytes)).unwrap(); + + let mut multipart = Multipart::new(&headers, payload); + match multipart.next().await.unwrap() { + Ok(mut field) => { + let cd = field.content_disposition().unwrap(); + assert_eq!(cd.disposition, DispositionType::FormData); + assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); + + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); + + match field.next().await.unwrap() { + Ok(chunk) => assert_eq!(chunk, "test"), + _ => unreachable!(), + } + match field.next().await { + None => (), + _ => unreachable!(), + } } - }); + _ => unreachable!(), + } + + match multipart.next().await { + Some(Ok(mut field)) => { + assert_eq!(field.content_type().type_(), mime::TEXT); + assert_eq!(field.content_type().subtype(), mime::PLAIN); + + match field.next().await { + Some(Ok(chunk)) => assert_eq!(chunk, "data"), + _ => unreachable!(), + } + match field.next().await { + None => (), + _ => unreachable!(), + } + } + _ => unreachable!(), + } + + match multipart.next().await { + None => (), + _ => unreachable!(), + } } - #[test] - fn test_basic() { - block_on(async { - let (_, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); + #[actix_rt::test] + async fn test_basic() { + let (_, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); - assert_eq!(payload.buf.len(), 0); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - assert_eq!(None, payload.read_max(1).unwrap()); - }) + assert_eq!(payload.buf.len(), 0); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); + assert_eq!(None, payload.read_max(1).unwrap()); } - #[test] - fn test_eof() { - block_on(async { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); + #[actix_rt::test] + async fn test_eof() { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); - assert_eq!(None, payload.read_max(4).unwrap()); - sender.feed_data(Bytes::from("data")); - sender.feed_eof(); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); + assert_eq!(None, payload.read_max(4).unwrap()); + sender.feed_data(Bytes::from("data")); + sender.feed_eof(); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap()); - assert_eq!(payload.buf.len(), 0); - assert!(payload.read_max(1).is_err()); - assert!(payload.eof); - }) + assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap()); + assert_eq!(payload.buf.len(), 0); + assert!(payload.read_max(1).is_err()); + assert!(payload.eof); } - #[test] - fn test_err() { - block_on(async { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - assert_eq!(None, payload.read_max(1).unwrap()); - sender.set_error(PayloadError::Incomplete(None)); - lazy(|cx| payload.poll_stream(cx)).await.err().unwrap(); - }) + #[actix_rt::test] + async fn test_err() { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); + assert_eq!(None, payload.read_max(1).unwrap()); + sender.set_error(PayloadError::Incomplete(None)); + lazy(|cx| payload.poll_stream(cx)).await.err().unwrap(); } - #[test] - fn test_readmax() { - block_on(async { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); + #[actix_rt::test] + async fn test_readmax() { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - assert_eq!(payload.buf.len(), 10); + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); + assert_eq!(payload.buf.len(), 10); - assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap()); - assert_eq!(payload.buf.len(), 5); + assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap()); + assert_eq!(payload.buf.len(), 5); - assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap()); - assert_eq!(payload.buf.len(), 0); - }) + assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap()); + assert_eq!(payload.buf.len(), 0); } - #[test] - fn test_readexactly() { - block_on(async { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); + #[actix_rt::test] + async fn test_readexactly() { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); - assert_eq!(None, payload.read_exact(2)); + assert_eq!(None, payload.read_exact(2)); - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2)); - assert_eq!(payload.buf.len(), 8); + assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2)); + assert_eq!(payload.buf.len(), 8); - assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4)); - assert_eq!(payload.buf.len(), 4); - }) + assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4)); + assert_eq!(payload.buf.len(), 4); } - #[test] - fn test_readuntil() { - block_on(async { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); + #[actix_rt::test] + async fn test_readuntil() { + let (mut sender, payload) = Payload::create(false); + let mut payload = PayloadBuffer::new(payload); - assert_eq!(None, payload.read_until(b"ne").unwrap()); + assert_eq!(None, payload.read_until(b"ne").unwrap()); - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); + sender.feed_data(Bytes::from("line1")); + sender.feed_data(Bytes::from("line2")); + lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - assert_eq!( - Some(Bytes::from("line")), - payload.read_until(b"ne").unwrap() - ); - assert_eq!(payload.buf.len(), 6); + assert_eq!( + Some(Bytes::from("line")), + payload.read_until(b"ne").unwrap() + ); + assert_eq!(payload.buf.len(), 6); - assert_eq!( - Some(Bytes::from("1line2")), - payload.read_until(b"2").unwrap() - ); - assert_eq!(payload.buf.len(), 0); - }) + assert_eq!( + Some(Bytes::from("1line2")), + payload.read_until(b"2").unwrap() + ); + assert_eq!(payload.buf.len(), 0); } } diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index bb5fba97..5d66d653 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -364,125 +364,117 @@ mod tests { use actix_web::{test, web, App}; use bytes::Bytes; - #[test] - fn cookie_session() { - test::block_on(async { - let mut app = test::init_service( - App::new() - .wrap(CookieSession::signed(&[0; 32]).secure(false)) - .service(web::resource("/").to(|ses: Session| { - async move { - let _ = ses.set("counter", 100); - "test" - } - })), - ) - .await; + #[actix_rt::test] + async fn cookie_session() { + let mut app = test::init_service( + App::new() + .wrap(CookieSession::signed(&[0; 32]).secure(false)) + .service(web::resource("/").to(|ses: Session| { + async move { + let _ = ses.set("counter", 100); + "test" + } + })), + ) + .await; - let request = test::TestRequest::get().to_request(); - let response = app.call(request).await.unwrap(); - assert!(response - .response() - .cookies() - .find(|c| c.name() == "actix-session") - .is_some()); - }) + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + assert!(response + .response() + .cookies() + .find(|c| c.name() == "actix-session") + .is_some()); } - #[test] - fn private_cookie() { - test::block_on(async { - let mut app = test::init_service( - App::new() - .wrap(CookieSession::private(&[0; 32]).secure(false)) - .service(web::resource("/").to(|ses: Session| { - async move { - let _ = ses.set("counter", 100); - "test" - } - })), - ) - .await; + #[actix_rt::test] + async fn private_cookie() { + let mut app = test::init_service( + App::new() + .wrap(CookieSession::private(&[0; 32]).secure(false)) + .service(web::resource("/").to(|ses: Session| { + async move { + let _ = ses.set("counter", 100); + "test" + } + })), + ) + .await; - let request = test::TestRequest::get().to_request(); - let response = app.call(request).await.unwrap(); - assert!(response - .response() - .cookies() - .find(|c| c.name() == "actix-session") - .is_some()); - }) + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + assert!(response + .response() + .cookies() + .find(|c| c.name() == "actix-session") + .is_some()); } - #[test] - fn cookie_session_extractor() { - test::block_on(async { - let mut app = test::init_service( - App::new() - .wrap(CookieSession::signed(&[0; 32]).secure(false)) - .service(web::resource("/").to(|ses: Session| { - async move { - let _ = ses.set("counter", 100); - "test" - } - })), - ) - .await; + #[actix_rt::test] + async fn cookie_session_extractor() { + let mut app = test::init_service( + App::new() + .wrap(CookieSession::signed(&[0; 32]).secure(false)) + .service(web::resource("/").to(|ses: Session| { + async move { + let _ = ses.set("counter", 100); + "test" + } + })), + ) + .await; - let request = test::TestRequest::get().to_request(); - let response = app.call(request).await.unwrap(); - assert!(response - .response() - .cookies() - .find(|c| c.name() == "actix-session") - .is_some()); - }) + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + assert!(response + .response() + .cookies() + .find(|c| c.name() == "actix-session") + .is_some()); } - #[test] - fn basics() { - test::block_on(async { - let mut app = test::init_service( - App::new() - .wrap( - CookieSession::signed(&[0; 32]) - .path("/test/") - .name("actix-test") - .domain("localhost") - .http_only(true) - .same_site(SameSite::Lax) - .max_age(100), - ) - .service(web::resource("/").to(|ses: Session| { - async move { - let _ = ses.set("counter", 100); - "test" - } - })) - .service(web::resource("/test/").to(|ses: Session| { - async move { - let val: usize = ses.get("counter").unwrap().unwrap(); - format!("counter: {}", val) - } - })), - ) - .await; + #[actix_rt::test] + async fn basics() { + let mut app = test::init_service( + App::new() + .wrap( + CookieSession::signed(&[0; 32]) + .path("/test/") + .name("actix-test") + .domain("localhost") + .http_only(true) + .same_site(SameSite::Lax) + .max_age(100), + ) + .service(web::resource("/").to(|ses: Session| { + async move { + let _ = ses.set("counter", 100); + "test" + } + })) + .service(web::resource("/test/").to(|ses: Session| { + async move { + let val: usize = ses.get("counter").unwrap().unwrap(); + format!("counter: {}", val) + } + })), + ) + .await; - let request = test::TestRequest::get().to_request(); - let response = app.call(request).await.unwrap(); - let cookie = response - .response() - .cookies() - .find(|c| c.name() == "actix-test") - .unwrap() - .clone(); - assert_eq!(cookie.path().unwrap(), "/test/"); + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + let cookie = response + .response() + .cookies() + .find(|c| c.name() == "actix-test") + .unwrap() + .clone(); + assert_eq!(cookie.path().unwrap(), "/test/"); - let request = test::TestRequest::with_uri("/test/") - .cookie(cookie) - .to_request(); - let body = test::read_response(&mut app, request).await; - assert_eq!(body, Bytes::from_static(b"counter: 100")); - }) + let request = test::TestRequest::with_uri("/test/") + .cookie(cookie) + .to_request(); + let body = test::read_response(&mut app, request).await; + assert_eq!(body, Bytes::from_static(b"counter: 100")); } } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 0aa81e47..95883363 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -17,6 +17,7 @@ syn = { version = "^1", features = ["full", "parsing"] } proc-macro2 = "^1" [dev-dependencies] +actix-rt = { version = "1.0.0-alpha.1" } actix-web = { version = "2.0.0-alpha.1" } actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 18c01f37..b6ac6dd1 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,5 +1,5 @@ use actix_http::HttpService; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use actix_web::{http, web::Path, App, HttpResponse, Responder}; use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, trace}; use futures::{future, Future}; @@ -69,95 +69,89 @@ async fn get_param_test(_: Path) -> impl Responder { HttpResponse::Ok() } -#[test] -fn test_params() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new( - App::new() - .service(get_param_test) - .service(put_param_test) - .service(delete_param_test), - ) - }); +#[actix_rt::test] +async fn test_params() { + let srv = TestServer::start(|| { + HttpService::new( + App::new() + .service(get_param_test) + .service(put_param_test) + .service(delete_param_test), + ) + }); - let request = srv.request(http::Method::GET, srv.url("/test/it")); - let response = request.send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::OK); + let request = srv.request(http::Method::GET, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::OK); - let request = srv.request(http::Method::PUT, srv.url("/test/it")); - let response = request.send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::CREATED); + let request = srv.request(http::Method::PUT, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::CREATED); - let request = srv.request(http::Method::DELETE, srv.url("/test/it")); - let response = request.send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::NO_CONTENT); - }) + let request = srv.request(http::Method::DELETE, srv.url("/test/it")); + let response = request.send().await.unwrap(); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); } -#[test] -fn test_body() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new( - App::new() - .service(post_test) - .service(put_test) - .service(head_test) - .service(connect_test) - .service(options_test) - .service(trace_test) - .service(patch_test) - .service(test), - ) - }); - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); +#[actix_rt::test] +async fn test_body() { + let srv = TestServer::start(|| { + HttpService::new( + App::new() + .service(post_test) + .service(put_test) + .service(head_test) + .service(connect_test) + .service(options_test) + .service(trace_test) + .service(patch_test) + .service(test), + ) + }); + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::HEAD, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::HEAD, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::CONNECT, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::CONNECT, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::OPTIONS, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::OPTIONS, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::TRACE, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::TRACE, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::PATCH, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); + let request = srv.request(http::Method::PATCH, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); - let request = srv.request(http::Method::PUT, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.status(), http::StatusCode::CREATED); + let request = srv.request(http::Method::PUT, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.status(), http::StatusCode::CREATED); - let request = srv.request(http::Method::POST, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.status(), http::StatusCode::NO_CONTENT); + let request = srv.request(http::Method::POST, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - }) + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); } -#[test] -fn test_auto_async() { - block_on(async { - let srv = TestServer::start(|| HttpService::new(App::new().service(auto_async))); +#[actix_rt::test] +async fn test_auto_async() { + let srv = TestServer::start(|| HttpService::new(App::new().service(auto_async))); - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - }) + let request = srv.request(http::Method::GET, srv.url("/test")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 0f338b40..1b35c279 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -30,7 +30,7 @@ default = ["brotli", "flate2-zlib"] openssl = ["open-ssl", "actix-http/openssl"] # rustls -# rustls = ["rust-tls", "actix-http/rustls"] +rustls = ["rust-tls", "actix-http/rustls"] # brotli encoding, requires c compiler brotli = ["actix-http/brotli"] @@ -45,6 +45,7 @@ flate2-rust = ["actix-http/flate2-rust"] actix-codec = "0.2.0-alpha.1" actix-service = "1.0.0-alpha.1" actix-http = "0.3.0-alpha.1" +actix-rt = "1.0.0-alpha.1" base64 = "0.10.1" bytes = "0.4" @@ -57,12 +58,10 @@ rand = "0.7" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.6.1" -tokio-timer = "0.3.0-alpha.6" open-ssl = { version="0.10", package="openssl", optional = true } rust-tls = { version = "0.16.0", package="rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-rt = "1.0.0-alpha.1" actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } actix-web = { version = "2.0.0-alpha.1", features=["openssl"] } actix-http = { version = "0.3.0-alpha.1", features=["openssl"] } @@ -73,5 +72,4 @@ brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" rand = "0.7" -tokio-tcp = "0.1" webpki = { version = "0.21" } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index d6cea6de..e995519e 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -6,19 +6,16 @@ //! use actix_rt::System; //! use awc::Client; //! -//! fn main() { -//! System::new("test").block_on(async { -//! let mut client = Client::default(); +//! #[actix_rt::main] +//! async fn main() { +//! let mut client = Client::default(); //! -//! client.get("http://www.rust-lang.org") // <- Create request builder -//! .header("User-Agent", "Actix-web") -//! .send() // <- Send http request -//! .await -//! .and_then(|response| { // <- server http response -//! println!("Response: {:?}", response); -//! Ok(()) -//! }) -//! }); +//! let response = client.get("http://www.rust-lang.org") // <- Create request builder +//! .header("User-Agent", "Actix-web") +//! .send() // <- Send http request +//! .await; +//! +//! println!("Response: {:?}", response); //! } //! ``` use std::cell::RefCell; diff --git a/awc/src/request.rs b/awc/src/request.rs index c6b09e95..3660f808 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -39,19 +39,18 @@ const HTTPS_ENCODING: &str = "gzip, deflate"; /// ```rust /// use actix_rt::System; /// -/// fn main() { -/// System::new("test").block_on(async { -/// let response = awc::Client::new() -/// .get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .send() // <- Send http request -/// .await; +/// #[actix_rt::main] +/// async fn main() { +/// let response = awc::Client::new() +/// .get("http://www.rust-lang.org") // <- Create request builder +/// .header("User-Agent", "Actix-web") +/// .send() // <- Send http request +/// .await; /// -/// response.and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// Ok(()) -/// }) -/// }); +/// response.and_then(|response| { // <- server http response +/// println!("Response: {:?}", response); +/// Ok(()) +/// }); /// } /// ``` pub struct ClientRequest { @@ -308,25 +307,21 @@ impl ClientRequest { /// Set a cookie /// /// ```rust - /// # use actix_rt::System; - /// fn main() { - /// System::new("test").block_on(async { - /// awc::Client::new().get("https://www.rust-lang.org") - /// .cookie( - /// awc::http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .send() - /// .await - /// .and_then(|response| { - /// println!("Response: {:?}", response); - /// Ok(()) - /// }) - /// }); + /// #[actix_rt::main] + /// async fn main() { + /// let resp = awc::Client::new().get("https://www.rust-lang.org") + /// .cookie( + /// awc::http::Cookie::build("name", "value") + /// .domain("www.rust-lang.org") + /// .path("/") + /// .secure(true) + /// .http_only(true) + /// .finish(), + /// ) + /// .send() + /// .await; + /// + /// println!("Response: {:?}", resp); /// } /// ``` pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { diff --git a/awc/src/response.rs b/awc/src/response.rs index 5ef8e18b..00ab4cee 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -358,41 +358,37 @@ where #[cfg(test)] mod tests { use super::*; - use actix_http_test::block_on; use serde::{Deserialize, Serialize}; use crate::{http::header, test::TestResponse}; - #[test] - fn test_body() { - block_on(async { - let mut req = - TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); - match req.body().await.err().unwrap() { - PayloadError::UnknownLength => (), - _ => unreachable!("error"), - } + #[actix_rt::test] + async fn test_body() { + let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); + match req.body().await.err().unwrap() { + PayloadError::UnknownLength => (), + _ => unreachable!("error"), + } - let mut req = - TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); - match req.body().await.err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } + let mut req = + TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + match req.body().await.err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } - let mut req = TestResponse::default() - .set_payload(Bytes::from_static(b"test")) - .finish(); - assert_eq!(req.body().await.ok().unwrap(), Bytes::from_static(b"test")); + let mut req = TestResponse::default() + .set_payload(Bytes::from_static(b"test")) + .finish(); + assert_eq!(req.body().await.ok().unwrap(), Bytes::from_static(b"test")); - let mut req = TestResponse::default() - .set_payload(Bytes::from_static(b"11111111111111")) - .finish(); - match req.body().limit(5).await.err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - }) + let mut req = TestResponse::default() + .set_payload(Bytes::from_static(b"11111111111111")) + .finish(); + match req.body().limit(5).await.err().unwrap() { + PayloadError::Overflow => (), + _ => unreachable!("error"), + } } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -414,58 +410,56 @@ mod tests { } } - #[test] - fn test_json_body() { - block_on(async { - let mut req = TestResponse::default().finish(); - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + #[actix_rt::test] + async fn test_json_body() { + let mut req = TestResponse::default().finish(); + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ) - .finish(); - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + ) + .finish(); + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ) - .finish(); + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ) + .finish(); - let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; - assert!(json_eq( - json.err().unwrap(), - JsonPayloadError::Payload(PayloadError::Overflow) - )); + let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; + assert!(json_eq( + json.err().unwrap(), + JsonPayloadError::Payload(PayloadError::Overflow) + )); - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); + let mut req = TestResponse::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .finish(); - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert_eq!( - json.ok().unwrap(), - MyObject { - name: "test".to_owned() - } - ); - }) + let json = JsonBody::<_, MyObject>::new(&mut req).await; + assert_eq!( + json.ok().unwrap(), + MyObject { + name: "test".to_owned() + } + ); } } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index f7461113..9cf158c0 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -4,12 +4,12 @@ use std::rc::Rc; use std::task::{Context, Poll}; use std::time::Duration; +use actix_rt::time::{delay_for, Delay}; use bytes::Bytes; use derive_more::From; use futures::{future::LocalBoxFuture, ready, Future, Stream}; use serde::Serialize; use serde_json; -use tokio_timer::{delay_for, Delay}; use actix_http::body::{Body, BodyStream}; use actix_http::encoding::Decoder; diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 8819b499..075c8356 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -7,8 +7,8 @@ use std::{fmt, str}; use actix_codec::Framed; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; +use actix_rt::time::Timeout; use percent_encoding::percent_encode; -use tokio_timer::Timeout; use actix_http::cookie::USERINFO; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; @@ -389,21 +389,19 @@ impl fmt::Debug for WebsocketsRequest { #[cfg(test)] mod tests { - use actix_web::test::block_on; - use super::*; use crate::Client; - #[test] - fn test_debug() { + #[actix_rt::test] + async fn test_debug() { let request = Client::new().ws("/").header("x-test", "111"); let repr = format!("{:?}", request); assert!(repr.contains("WebsocketsRequest")); assert!(repr.contains("x-test")); } - #[test] - fn test_header_override() { + #[actix_rt::test] + async fn test_header_override() { let req = Client::build() .header(header::CONTENT_TYPE, "111") .finish() @@ -421,8 +419,8 @@ mod tests { ); } - #[test] - fn basic_auth() { + #[actix_rt::test] + async fn basic_auth() { let req = Client::new() .ws("/") .basic_auth("username", Some("password")); @@ -448,8 +446,8 @@ mod tests { ); } - #[test] - fn bearer_auth() { + #[actix_rt::test] + async fn bearer_auth() { let req = Client::new().ws("/").bearer_auth("someS3cr3tAutht0k3n"); assert_eq!( req.head @@ -463,35 +461,33 @@ mod tests { let _ = req.connect(); } - #[test] - fn basics() { - block_on(async { - let req = Client::new() - .ws("http://localhost/") - .origin("test-origin") - .max_frame_size(100) - .server_mode() - .protocols(&["v1", "v2"]) - .set_header_if_none(header::CONTENT_TYPE, "json") - .set_header_if_none(header::CONTENT_TYPE, "text") - .cookie(Cookie::build("cookie1", "value1").finish()); - assert_eq!( - req.origin.as_ref().unwrap().to_str().unwrap(), - "test-origin" - ); - assert_eq!(req.max_size, 100); - assert_eq!(req.server_mode, true); - assert_eq!(req.protocols, Some("v1,v2".to_string())); - assert_eq!( - req.head.headers.get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("json") - ); + #[actix_rt::test] + async fn basics() { + let req = Client::new() + .ws("http://localhost/") + .origin("test-origin") + .max_frame_size(100) + .server_mode() + .protocols(&["v1", "v2"]) + .set_header_if_none(header::CONTENT_TYPE, "json") + .set_header_if_none(header::CONTENT_TYPE, "text") + .cookie(Cookie::build("cookie1", "value1").finish()); + assert_eq!( + req.origin.as_ref().unwrap().to_str().unwrap(), + "test-origin" + ); + assert_eq!(req.max_size, 100); + assert_eq!(req.server_mode, true); + assert_eq!(req.protocols, Some("v1,v2".to_string())); + assert_eq!( + req.head.headers.get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("json") + ); - let _ = req.connect().await; + let _ = req.connect().await; - assert!(Client::new().ws("/").connect().await.is_err()); - assert!(Client::new().ws("http:///test").connect().await.is_err()); - assert!(Client::new().ws("hmm://test.com/").connect().await.is_err()); - }) + assert!(Client::new().ws("/").connect().await.is_err()); + assert!(Client::new().ws("http:///test").connect().await.is_err()); + assert!(Client::new().ws("hmm://test.com/").connect().await.is_err()); } } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 9e1948f7..15e9a07a 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -13,7 +13,7 @@ use futures::future::ok; use rand::Rng; use actix_http::HttpService; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use actix_service::pipeline_factory; use actix_web::http::Cookie; use actix_web::middleware::{BodyEncoding, Compress}; @@ -42,514 +42,472 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[test] -fn test_simple() { - block_on(async { - let srv = TestServer::start(|| { +#[actix_rt::test] +async fn test_simple() { + let srv = + TestServer::start(|| { HttpService::new(App::new().service( web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), )) }); - let request = srv.get("/").header("x-test", "111").send(); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + let request = srv.get("/").header("x-test", "111").send(); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - let mut response = srv.post("/").send().await.unwrap(); - assert!(response.status().is_success()); + let mut response = srv.post("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // camel case - let response = srv.post("/").camel_case().send().await.unwrap(); - assert!(response.status().is_success()); - }) + // camel case + let response = srv.post("/").camel_case().send().await.unwrap(); + assert!(response.status().is_success()); } -#[test] -fn test_json() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(|_: web::Json| HttpResponse::Ok())), - ), - ) - }); +#[actix_rt::test] +async fn test_json() { + let srv = TestServer::start(|| { + HttpService::new(App::new().service( + web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), + )) + }); - let request = srv - .get("/") - .header("x-test", "111") - .send_json(&"TEST".to_string()); - let response = request.await.unwrap(); - assert!(response.status().is_success()); - }) + let request = srv + .get("/") + .header("x-test", "111") + .send_json(&"TEST".to_string()); + let response = request.await.unwrap(); + assert!(response.status().is_success()); } -#[test] -fn test_form() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |_: web::Form>| HttpResponse::Ok(), - )))) - }); +#[actix_rt::test] +async fn test_form() { + let srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |_: web::Form>| HttpResponse::Ok(), + )))) + }); - let mut data = HashMap::new(); - let _ = data.insert("key".to_string(), "TEST".to_string()); + let mut data = HashMap::new(); + let _ = data.insert("key".to_string(), "TEST".to_string()); - let request = srv.get("/").header("x-test", "111").send_form(&data); - let response = request.await.unwrap(); - assert!(response.status().is_success()); - }) + let request = srv.get("/").header("x-test", "111").send_form(&data); + let response = request.await.unwrap(); + assert!(response.status().is_success()); } -#[test] -fn test_timeout() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - || { - async { - tokio_timer::delay_for(Duration::from_millis(200)).await; - Ok::<_, Error>(HttpResponse::Ok().body(STR)) - } - }, - )))) - }); +#[actix_rt::test] +async fn test_timeout() { + let srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to(|| { + async { + actix_rt::time::delay_for(Duration::from_millis(200)).await; + Ok::<_, Error>(HttpResponse::Ok().body(STR)) + } + })))) + }); - let connector = awc::Connector::new() - .connector(actix_connect::new_connector( - actix_connect::start_default_resolver(), - )) - .timeout(Duration::from_secs(15)) - .finish(); + let connector = awc::Connector::new() + .connector(actix_connect::new_connector( + actix_connect::start_default_resolver(), + )) + .timeout(Duration::from_secs(15)) + .finish(); - let client = awc::Client::build() - .connector(connector) - .timeout(Duration::from_millis(50)) - .finish(); + let client = awc::Client::build() + .connector(connector) + .timeout(Duration::from_millis(50)) + .finish(); - let request = client.get(srv.url("/")).send(); - match request.await { - Err(SendRequestError::Timeout) => (), - _ => panic!(), - } - }) + let request = client.get(srv.url("/")).send(); + match request.await { + Err(SendRequestError::Timeout) => (), + _ => panic!(), + } } -#[test] -fn test_timeout_override() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - || { - async { - tokio_timer::delay_for(Duration::from_millis(200)).await; - Ok::<_, Error>(HttpResponse::Ok().body(STR)) - } - }, - )))) - }); +#[actix_rt::test] +async fn test_timeout_override() { + let srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to(|| { + async { + actix_rt::time::delay_for(Duration::from_millis(200)).await; + Ok::<_, Error>(HttpResponse::Ok().body(STR)) + } + })))) + }); - let client = awc::Client::build() - .timeout(Duration::from_millis(50000)) - .finish(); - let request = client - .get(srv.url("/")) - .timeout(Duration::from_millis(50)) - .send(); - match request.await { - Err(SendRequestError::Timeout) => (), - _ => panic!(), - } - }) + let client = awc::Client::build() + .timeout(Duration::from_millis(50000)) + .finish(); + let request = client + .get(srv.url("/")) + .timeout(Duration::from_millis(50)) + .send(); + match request.await { + Err(SendRequestError::Timeout) => (), + _ => panic!(), + } } -#[test] -fn test_connection_reuse() { - block_on(async { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); +#[actix_rt::test] +async fn test_connection_reuse() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let srv = TestServer::start(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then(HttpService::new(App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok())), - ))) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then(HttpService::new( + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), + )) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")); + let response = req.send().await.unwrap(); + assert!(response.status().is_success()); - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); - }) + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); } -#[test] -fn test_connection_force_close() { - block_on(async { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); +#[actix_rt::test] +async fn test_connection_force_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let srv = TestServer::start(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then(HttpService::new(App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok())), - ))) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then(HttpService::new( + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), + )) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).force_close().send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).force_close().send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")).force_close(); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")).force_close(); + let response = req.send().await.unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); - }) + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); } -#[test] -fn test_connection_server_close() { - block_on(async { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); +#[actix_rt::test] +async fn test_connection_server_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let srv = TestServer::start(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then(HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().finish())), - ), - )) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().finish())), + ), + )) + }); - let client = awc::Client::default(); + let client = awc::Client::default(); - // req 1 - let request = client.get(srv.url("/")).send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.url("/")); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); + // req 2 + let req = client.post(srv.url("/")); + let response = req.send().await.unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); - }) + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); } -#[test] -fn test_connection_wait_queue() { - block_on(async { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); +#[actix_rt::test] +async fn test_connection_wait_queue() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let srv = TestServer::start(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then(HttpService::new(App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), - ))) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then(HttpService::new(App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + ))) + }); - let client = awc::Client::build() - .connector(awc::Connector::new().limit(1).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); - // req 1 - let request = client.get(srv.url("/")).send(); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req2 = client.post(srv.url("/")); - let req2_fut = req2.send(); + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = req2.send(); - // read response 1 - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response 1 + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // req 2 - let response = req2_fut.await.unwrap(); - assert!(response.status().is_success()); + // req 2 + let response = req2_fut.await.unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 1); - }) + // two connection + assert_eq!(num.load(Ordering::Relaxed), 1); } -#[test] -fn test_connection_wait_queue_force_close() { - block_on(async { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); +#[actix_rt::test] +async fn test_connection_wait_queue_force_close() { + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let srv = TestServer::start(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then(HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), - ), - )) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then(HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), + ), + )) + }); - let client = awc::Client::build() - .connector(awc::Connector::new().limit(1).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().limit(1).finish()) + .finish(); - // req 1 - let request = client.get(srv.url("/")).send(); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.url("/")).send(); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req2 = client.post(srv.url("/")); - let req2_fut = req2.send(); + // req 2 + let req2 = client.post(srv.url("/")); + let req2_fut = req2.send(); - // read response 1 - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); + // read response 1 + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - // req 2 - let response = req2_fut.await.unwrap(); - assert!(response.status().is_success()); + // req 2 + let response = req2_fut.await.unwrap(); + assert!(response.status().is_success()); - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); - }) + // two connection + assert_eq!(num.load(Ordering::Relaxed), 2); } -#[test] -fn test_with_query_parameter() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").to( - |req: HttpRequest| { - if req.query_string().contains("qp") { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }, - ))) - }); - - let res = awc::Client::new() - .get(srv.url("/?qp=5")) - .send() - .await - .unwrap(); - assert!(res.status().is_success()); - }) -} - -#[test] -fn test_no_decompress() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().wrap(Compress::default()).service( - web::resource("/").route(web::to(|| { - let mut res = HttpResponse::Ok().body(STR); - res.encoding(header::ContentEncoding::Gzip); - res - })), - )) - }); - - let mut res = awc::Client::new() - .get(srv.url("/")) - .no_decompress() - .send() - .await - .unwrap(); - assert!(res.status().is_success()); - - // read response - let bytes = res.body().await.unwrap(); - - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - - // POST - let mut res = awc::Client::new() - .post(srv.url("/")) - .no_decompress() - .send() - .await - .unwrap(); - assert!(res.status().is_success()); - - let bytes = res.body().await.unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - }) -} - -#[test] -fn test_client_gzip_encoding() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - || { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let data = e.finish().unwrap(); - +#[actix_rt::test] +async fn test_with_query_parameter() { + let srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").to( + |req: HttpRequest| { + if req.query_string().contains("qp") { HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - }, - )))) - }); + } else { + HttpResponse::BadRequest() + } + }, + ))) + }); - // client request - let mut response = srv.post("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + let res = awc::Client::new() + .get(srv.url("/?qp=5")) + .send() + .await + .unwrap(); + assert!(res.status().is_success()); } -#[test] -fn test_client_gzip_encoding_large() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - || { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.repeat(10).as_ref()).unwrap(); - let data = e.finish().unwrap(); +#[actix_rt::test] +async fn test_no_decompress() { + let srv = TestServer::start(|| { + HttpService::new(App::new().wrap(Compress::default()).service( + web::resource("/").route(web::to(|| { + let mut res = HttpResponse::Ok().body(STR); + res.encoding(header::ContentEncoding::Gzip); + res + })), + )) + }); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - }, - )))) - }); + let mut res = awc::Client::new() + .get(srv.url("/")) + .no_decompress() + .send() + .await + .unwrap(); + assert!(res.status().is_success()); - // client request - let mut response = srv.post("/").send().await.unwrap(); - assert!(response.status().is_success()); + // read response + let bytes = res.body().await.unwrap(); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(STR.repeat(10))); - }) + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + + // POST + let mut res = awc::Client::new() + .post(srv.url("/")) + .no_decompress() + .send() + .await + .unwrap(); + assert!(res.status().is_success()); + + let bytes = res.body().await.unwrap(); + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -#[test] -fn test_client_gzip_encoding_large_random() { - block_on(async { - let data = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(100_000) - .collect::(); +#[actix_rt::test] +async fn test_client_gzip_encoding() { + let srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to(|| { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let data = e.finish().unwrap(); - let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |data: Bytes| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - }, - )))) - }); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + })))) + }); - // client request - let mut response = srv.post("/").send_body(data.clone()).await.unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv.post("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] -fn test_client_brotli_encoding() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().service(web::resource("/").route(web::to( - |data: Bytes| { - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "br") - .body(data) - }, - )))) - }); +#[actix_rt::test] +async fn test_client_gzip_encoding_large() { + let srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to(|| { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.repeat(10).as_ref()).unwrap(); + let data = e.finish().unwrap(); - // client request - let mut response = srv.post("/").send_body(STR).await.unwrap(); - assert!(response.status().is_success()); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + })))) + }); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // client request + let mut response = srv.post("/").send().await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(STR.repeat(10))); } -// #[test] -// fn test_client_brotli_encoding_large_random() { +#[actix_rt::test] +async fn test_client_gzip_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(100_000) + .collect::(); + + let srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |data: Bytes| { + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(&data).unwrap(); + let data = e.finish().unwrap(); + HttpResponse::Ok() + .header("content-encoding", "gzip") + .body(data) + }, + )))) + }); + + // client request + let mut response = srv.post("/").send_body(data.clone()).await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} + +#[actix_rt::test] +async fn test_client_brotli_encoding() { + let srv = TestServer::start(|| { + HttpService::new(App::new().service(web::resource("/").route(web::to( + |data: Bytes| { + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(&data).unwrap(); + let data = e.finish().unwrap(); + HttpResponse::Ok() + .header("content-encoding", "br") + .body(data) + }, + )))) + }); + + // client request + let mut response = srv.post("/").send_body(STR).await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +// #[actix_rt::test] +// async fn test_client_brotli_encoding_large_random() { // let data = rand::thread_rng() // .sample_iter(&rand::distributions::Alphanumeric) // .take(70_000) @@ -583,8 +541,8 @@ fn test_client_brotli_encoding() { // } // #[cfg(feature = "brotli")] -// #[test] -// fn test_client_deflate_encoding() { +// #[actix_rt::test] +// async fn test_client_deflate_encoding() { // let srv = test::TestServer::start(|app| { // app.handler(|req: &HttpRequest| { // req.body() @@ -611,8 +569,8 @@ fn test_client_brotli_encoding() { // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // } -// #[test] -// fn test_client_deflate_encoding_large_random() { +// #[actix_rt::test] +// async fn test_client_deflate_encoding_large_random() { // let data = rand::thread_rng() // .sample_iter(&rand::distributions::Alphanumeric) // .take(70_000) @@ -644,8 +602,8 @@ fn test_client_brotli_encoding() { // assert_eq!(bytes, Bytes::from(data)); // } -// #[test] -// fn test_client_streaming_explicit() { +// #[actix_rt::test] +// async fn test_client_streaming_explicit() { // let srv = test::TestServer::start(|app| { // app.handler(|req: &HttpRequest| { // req.body() @@ -671,8 +629,8 @@ fn test_client_brotli_encoding() { // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // } -// #[test] -// fn test_body_streaming_implicit() { +// #[actix_rt::test] +// async fn test_body_streaming_implicit() { // let srv = test::TestServer::start(|app| { // app.handler(|_| { // let body = once(Ok(Bytes::from_static(STR.as_ref()))); @@ -691,83 +649,76 @@ fn test_client_brotli_encoding() { // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // } -#[test] -fn test_client_cookie_handling() { +#[actix_rt::test] +async fn test_client_cookie_handling() { use std::io::{Error as IoError, ErrorKind}; - block_on(async { - let cookie1 = Cookie::build("cookie1", "value1").finish(); - let cookie2 = Cookie::build("cookie2", "value2") - .domain("www.example.org") - .path("/") - .secure(true) - .http_only(true) - .finish(); - // Q: are all these clones really necessary? A: Yes, possibly - let cookie1b = cookie1.clone(); - let cookie2b = cookie2.clone(); + let cookie1 = Cookie::build("cookie1", "value1").finish(); + let cookie2 = Cookie::build("cookie2", "value2") + .domain("www.example.org") + .path("/") + .secure(true) + .http_only(true) + .finish(); + // Q: are all these clones really necessary? A: Yes, possibly + let cookie1b = cookie1.clone(); + let cookie2b = cookie2.clone(); - let srv = TestServer::start(move || { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); + let srv = TestServer::start(move || { + let cookie1 = cookie1b.clone(); + let cookie2 = cookie2b.clone(); - HttpService::new(App::new().route( - "/", - web::to(move |req: HttpRequest| { - let cookie1 = cookie1.clone(); - let cookie2 = cookie2.clone(); + HttpService::new(App::new().route( + "/", + web::to(move |req: HttpRequest| { + let cookie1 = cookie1.clone(); + let cookie2 = cookie2.clone(); - async move { - // Check cookies were sent correctly - let res: Result<(), Error> = req - .cookie("cookie1") - .ok_or(()) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(()) - } - }) - .and_then(|()| req.cookie("cookie2").ok_or(())) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(()) - } - }) - .map_err(|_| { - Error::from(IoError::from(ErrorKind::NotFound)) - }); + async move { + // Check cookies were sent correctly + let res: Result<(), Error> = req + .cookie("cookie1") + .ok_or(()) + .and_then(|c1| { + if c1.value() == "value1" { + Ok(()) + } else { + Err(()) + } + }) + .and_then(|()| req.cookie("cookie2").ok_or(())) + .and_then(|c2| { + if c2.value() == "value2" { + Ok(()) + } else { + Err(()) + } + }) + .map_err(|_| Error::from(IoError::from(ErrorKind::NotFound))); - if let Err(e) = res { - Err(e) - } else { - // Send some cookies back - Ok::<_, Error>( - HttpResponse::Ok() - .cookie(cookie1) - .cookie(cookie2) - .finish(), - ) - } + if let Err(e) = res { + Err(e) + } else { + // Send some cookies back + Ok::<_, Error>( + HttpResponse::Ok().cookie(cookie1).cookie(cookie2).finish(), + ) } - }), - )) - }); + } + }), + )) + }); - let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, cookie1); - let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, cookie2); - }) + let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); + let c1 = response.cookie("cookie1").expect("Missing cookie1"); + assert_eq!(c1, cookie1); + let c2 = response.cookie("cookie2").expect("Missing cookie2"); + assert_eq!(c2, cookie2); } -// #[test] +// #[actix_rt::test] // fn client_read_until_eof() { // let addr = test::TestServer::unused_addr(); @@ -797,62 +748,58 @@ fn test_client_cookie_handling() { // assert_eq!(bytes, Bytes::from_static(b"welcome!")); // } -#[test] -fn client_basic_auth() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().route( - "/", - web::to(|req: HttpRequest| { - if req - .headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - }); +#[actix_rt::test] +async fn client_basic_auth() { + let srv = TestServer::start(|| { + HttpService::new(App::new().route( + "/", + web::to(|req: HttpRequest| { + if req + .headers() + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap() + == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + }); - // set authorization header to Basic - let request = srv.get("/").basic_auth("username", Some("password")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - }) + // set authorization header to Basic + let request = srv.get("/").basic_auth("username", Some("password")); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); } -#[test] -fn client_bearer_auth() { - block_on(async { - let srv = TestServer::start(|| { - HttpService::new(App::new().route( - "/", - web::to(|req: HttpRequest| { - if req - .headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - == "Bearer someS3cr3tAutht0k3n" - { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - }); +#[actix_rt::test] +async fn client_bearer_auth() { + let srv = TestServer::start(|| { + HttpService::new(App::new().route( + "/", + web::to(|req: HttpRequest| { + if req + .headers() + .get(header::AUTHORIZATION) + .unwrap() + .to_str() + .unwrap() + == "Bearer someS3cr3tAutht0k3n" + { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + }); - // set authorization header to Bearer - let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - }) + // set authorization header to Bearer + let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); + let response = request.send().await.unwrap(); + assert!(response.status().is_success()); } diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index bdfd2103..ac60d8e8 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use actix_server::ssl::OpensslAcceptor; use actix_service::{pipeline_factory, ServiceFactory}; use actix_web::http::Version; @@ -53,57 +53,54 @@ mod danger { } } -// #[test] -fn _test_connection_reuse_h2() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); +// #[actix_rt::test] +async fn _test_connection_reuse_h2() { + let openssl = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let srv = TestServer::start(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok())), - )) - .map_err(|_| ()), - ) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) + .map_err(|_| ()), + ) + }); - // disable ssl verification - let mut config = ClientConfig::new(); - let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - config.set_protocols(&protos); - config - .dangerous() - .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + // disable ssl verification + let mut config = ClientConfig::new(); + let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + config.set_protocols(&protos); + config + .dangerous() + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); - let client = awc::Client::build() - .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) + .finish(); - // req 1 - let request = client.get(srv.surl("/")).send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.surl("/")); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); + // req 2 + let req = client.post(srv.surl("/")); + let response = req.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); - }) + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); } diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index d37dba29..1abb071a 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use actix_server::ssl::OpensslAcceptor; use actix_service::{pipeline_factory, ServiceFactory}; use actix_web::http::Version; @@ -35,56 +35,53 @@ fn ssl_acceptor() -> Result> { Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) } -#[test] -fn test_connection_reuse_h2() { - block_on(async { - let openssl = ssl_acceptor().unwrap(); - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); +#[actix_rt::test] +async fn test_connection_reuse_h2() { + let openssl = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); - let srv = TestServer::start(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok())), - )) - .map_err(|_| ()), - ) - }); + let srv = TestServer::start(move || { + let num2 = num2.clone(); + pipeline_factory(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + ok(io) + }) + .and_then( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) + .map_err(|_| ()), + ) + }); - // disable ssl verification - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + // disable ssl verification + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - let client = awc::Client::build() - .connector(awc::Connector::new().ssl(builder.build()).finish()) - .finish(); + let client = awc::Client::build() + .connector(awc::Connector::new().ssl(builder.build()).finish()) + .finish(); - // req 1 - let request = client.get(srv.surl("/")).send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = request.await.unwrap(); + assert!(response.status().is_success()); - // req 2 - let req = client.post(srv.surl("/")); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); + // req 2 + let req = client.post(srv.surl("/")); + let response = req.send().await.unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); - }) + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); } diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 633e8db5..2e1d3981 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -2,7 +2,7 @@ use std::io; use actix_codec::Framed; use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use bytes::{Bytes, BytesMut}; use futures::future::ok; use futures::{SinkExt, StreamExt}; @@ -28,56 +28,54 @@ async fn ws_service(req: ws::Frame) -> Result { } } -#[test] -fn test_simple() { - block_on(async { - let mut srv = TestServer::start(|| { - HttpService::build() - .upgrade(|(req, mut framed): (Request, Framed<_, _>)| { - async move { - let res = ws::handshake_response(req.head()).finish(); - // send handshake response - framed - .send(h1::Message::Item((res.drop_body(), BodySize::None))) - .await?; +#[actix_rt::test] +async fn test_simple() { + let mut srv = TestServer::start(|| { + HttpService::build() + .upgrade(|(req, mut framed): (Request, Framed<_, _>)| { + async move { + let res = ws::handshake_response(req.head()).finish(); + // send handshake response + framed + .send(h1::Message::Item((res.drop_body(), BodySize::None))) + .await?; - // start websocket service - let framed = framed.into_framed(ws::Codec::new()); - ws::Transport::with(framed, ws_service).await - } - }) - .finish(|_| ok::<_, Error>(Response::NotFound())) - }); + // start websocket service + let framed = framed.into_framed(ws::Codec::new()); + ws::Transport::with(framed, ws_service).await + } + }) + .finish(|_| ok::<_, Error>(Response::NotFound())) + }); - // client service - let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Text(Some(BytesMut::from("text")))); + // client service + let mut framed = srv.ws().await.unwrap(); + framed + .send(ws::Message::Text("text".to_string())) + .await + .unwrap(); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, ws::Frame::Text(Some(BytesMut::from("text")))); - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!( - item, - ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) - ); + framed + .send(ws::Message::Binary("text".into())) + .await + .unwrap(); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!( + item, + ws::Frame::Binary(Some(Bytes::from_static(b"text").into())) + ); - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Pong("text".to_string().into())); + framed.send(ws::Message::Ping("text".into())).await.unwrap(); + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, ws::Frame::Pong("text".to_string().into())); - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); + framed + .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) + .await + .unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); - }) + let item = framed.next().await.unwrap().unwrap(); + assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); } diff --git a/src/app.rs b/src/app.rs index a9dc3f29..d67817d2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,12 +2,10 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; use std::marker::PhantomData; -use std::pin::Pin; use std::rc::Rc; -use std::task::{Context, Poll}; use actix_http::body::{Body, MessageBody}; -use actix_service::boxed::{self, BoxedNewService}; +use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, Transform, }; @@ -25,7 +23,7 @@ use crate::service::{ ServiceResponse, }; -type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; +type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; type FnDataFactory = Box LocalBoxFuture<'static, Result, ()>>>; @@ -485,231 +483,195 @@ where mod tests { use actix_service::Service; use bytes::Bytes; - use futures::future::{ok, Future}; + use futures::future::ok; use super::*; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; - use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; - use crate::{web, Error, HttpRequest, HttpResponse}; + use crate::service::ServiceRequest; + use crate::test::{call_service, init_service, read_body, TestRequest}; + use crate::{web, HttpRequest, HttpResponse}; - #[test] - fn test_default_resource() { - block_on(async { - let mut srv = init_service( - App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), - ) + #[actix_rt::test] + async fn test_default_resource() { + let mut srv = init_service( + App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/blah").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let mut srv = init_service( + App::new() + .service(web::resource("/test").to(|| HttpResponse::Ok())) + .service( + web::resource("/test2") + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::Created())) + }) + .route(web::get().to(|| HttpResponse::Ok())), + ) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::MethodNotAllowed())) + }), + ) + .await; + + let req = TestRequest::with_uri("/blah").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let req = TestRequest::with_uri("/test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/test2") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + } + + #[actix_rt::test] + async fn test_data_factory() { + let mut srv = + init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )) .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/blah").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let mut srv = init_service( - App::new() - .service(web::resource("/test").to(|| HttpResponse::Ok())) - .service( - web::resource("/test2") - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::Created())) - }) - .route(web::get().to(|| HttpResponse::Ok())), - ) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::MethodNotAllowed())) - }), - ) + let mut srv = + init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), + )) .await; - - let req = TestRequest::with_uri("/blah").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/test2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test2") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - }) + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[test] - fn test_data_factory() { - block_on(async { - let mut srv = - init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let mut srv = - init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - }) + #[actix_rt::test] + async fn test_wrap() { + let mut srv = init_service( + App::new() + .wrap( + DefaultHeaders::new() + .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), + ) + .route("/test", web::get().to(|| HttpResponse::Ok())), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); } - fn md( - req: ServiceRequest, - srv: &mut S, - ) -> impl Future, Error>> - where - S: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - >, - { - let fut = srv.call(req); - async move { - let mut res = fut.await?; - res.headers_mut() - .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(res) - } + #[actix_rt::test] + async fn test_router_wrap() { + let mut srv = init_service( + App::new() + .route("/test", web::get().to(|| HttpResponse::Ok())) + .wrap( + DefaultHeaders::new() + .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), + ), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); } - #[test] - fn test_wrap() { - block_on(async { - let mut srv = - init_service( - App::new() - .wrap(DefaultHeaders::new().header( + #[actix_rt::test] + async fn test_wrap_fn() { + let mut srv = init_service( + App::new() + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async move { + let mut res = fut.await?; + res.headers_mut().insert( header::CONTENT_TYPE, HeaderValue::from_static("0001"), + ); + Ok(res) + } + }) + .service(web::resource("/test").to(|| HttpResponse::Ok())), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[actix_rt::test] + async fn test_router_wrap_fn() { + let mut srv = init_service( + App::new() + .route("/test", web::get().to(|| HttpResponse::Ok())) + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { + let mut res = fut.await?; + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + Ok(res) + } + }), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[actix_rt::test] + async fn test_external_resource() { + let mut srv = init_service( + App::new() + .external_resource("youtube", "https://youtube.com/watch/{video_id}") + .route( + "/test", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["12345"]).unwrap() )) - .route("/test", web::get().to(|| HttpResponse::Ok())), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - }) - } - - #[test] - fn test_router_wrap() { - block_on(async { - let mut srv = - init_service( - App::new() - .route("/test", web::get().to(|| HttpResponse::Ok())) - .wrap(DefaultHeaders::new().header( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - )), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - }) - } - - #[test] - fn test_wrap_fn() { - block_on(async { - let mut srv = init_service( - App::new() - .wrap_fn(|req, srv| { - let fut = srv.call(req); - async move { - let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - Ok(res) - } - }) - .service(web::resource("/test").to(|| HttpResponse::Ok())), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - }) - } - - #[test] - fn test_router_wrap_fn() { - block_on(async { - let mut srv = init_service( - App::new() - .route("/test", web::get().to(|| HttpResponse::Ok())) - .wrap_fn(|req, srv| { - let fut = srv.call(req); - async { - let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - Ok(res) - } }), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - }) - } - - #[test] - fn test_external_resource() { - block_on(async { - let mut srv = init_service( - App::new() - .external_resource("youtube", "https://youtube.com/watch/{video_id}") - .route( - "/test", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["12345"]).unwrap() - )) - }), - ), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); - }) + ), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); } } diff --git a/src/app_service.rs b/src/app_service.rs index 7407ee2f..3fa5a6ee 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -8,9 +8,9 @@ use std::task::{Context, Poll}; use actix_http::{Extensions, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_server_config::ServerConfig; -use actix_service::boxed::{self, BoxedNewService, BoxedService}; +use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{service_fn, Service, ServiceFactory}; -use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; +use futures::future::{ok, FutureExt, LocalBoxFuture}; use crate::config::{AppConfig, AppService}; use crate::data::DataFactory; @@ -21,9 +21,9 @@ use crate::rmap::ResourceMap; use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse}; type Guards = Vec>; -type HttpService = BoxedService; -type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxedResponse = LocalBoxFuture<'static, Result>; +type HttpService = BoxService; +type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; +type BoxResponse = LocalBoxFuture<'static, Result>; type FnDataFactory = Box LocalBoxFuture<'static, Result, ()>>>; @@ -387,7 +387,7 @@ impl Service for AppRouting { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = BoxedResponse; + type Future = BoxResponse; fn poll_ready(&mut self, _: &mut Context) -> Poll> { if self.ready.is_none() { @@ -447,13 +447,12 @@ impl ServiceFactory for AppEntry { #[cfg(test)] mod tests { - use actix_service::Service; - use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; - use crate::{test, web, App, HttpResponse}; + use crate::test::{init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + use actix_service::Service; struct DropData(Arc); @@ -463,19 +462,20 @@ mod tests { } } - #[test] - fn drop_data() { + #[actix_rt::test] + async fn test_drop_data() { let data = Arc::new(AtomicBool::new(false)); - test::block_on(async { - let mut app = test::init_service( + + { + let mut app = init_service( App::new() .data(DropData(data.clone())) .service(web::resource("/test").to(|| HttpResponse::Ok())), ) .await; - let req = test::TestRequest::with_uri("/test").to_request(); + let req = TestRequest::with_uri("/test").to_request(); let _ = app.call(req).await.unwrap(); - }); + } assert!(data.load(Ordering::Relaxed)); } } diff --git a/src/config.rs b/src/config.rs index 3ce18f98..57ba1007 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,7 +18,7 @@ use crate::service::{ type Guards = Vec>; type HttpNewService = - boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; + boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application configuration pub struct AppService { @@ -246,28 +246,27 @@ mod tests { use super::*; use crate::http::{Method, StatusCode}; - use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; + use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{web, App, HttpRequest, HttpResponse}; - #[test] - fn test_data() { - block_on(async { - let cfg = |cfg: &mut ServiceConfig| { - cfg.data(10usize); - }; + #[actix_rt::test] + async fn test_data() { + let cfg = |cfg: &mut ServiceConfig| { + cfg.data(10usize); + }; - let mut srv = init_service(App::new().configure(cfg).service( + let mut srv = + init_service(App::new().configure(cfg).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); } - // #[test] - // fn test_data_factory() { + // #[actix_rt::test] + // async fn test_data_factory() { // let cfg = |cfg: &mut ServiceConfig| { // cfg.data_factory(|| { // sleep(std::time::Duration::from_millis(50)).then(|_| { @@ -282,7 +281,7 @@ mod tests { // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), // )); // let req = TestRequest::default().to_request(); - // let resp = block_on(srv.call(req)).unwrap(); + // let resp = srv.call(req).await.unwrap(); // assert_eq!(resp.status(), StatusCode::OK); // let cfg2 = |cfg: &mut ServiceConfig| { @@ -294,63 +293,58 @@ mod tests { // .configure(cfg2), // ); // let req = TestRequest::default().to_request(); - // let resp = block_on(srv.call(req)).unwrap(); + // let resp = srv.call(req).await.unwrap(); // assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); // } - #[test] - fn test_external_resource() { - block_on(async { - let mut srv = init_service( - App::new() - .configure(|cfg| { - cfg.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - }) - .route( - "/test", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["12345"]).unwrap() - )) - }), - ), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); - }) + #[actix_rt::test] + async fn test_external_resource() { + let mut srv = init_service( + App::new() + .configure(|cfg| { + cfg.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + }) + .route( + "/test", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["12345"]).unwrap() + )) + }), + ), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); } - #[test] - fn test_service() { - block_on(async { - let mut srv = init_service(App::new().configure(|cfg| { - cfg.service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Created())), - ) - .route("/index.html", web::get().to(|| HttpResponse::Ok())); - })) - .await; + #[actix_rt::test] + async fn test_service() { + let mut srv = init_service(App::new().configure(|cfg| { + cfg.service( + web::resource("/test").route(web::get().to(|| HttpResponse::Created())), + ) + .route("/index.html", web::get().to(|| HttpResponse::Ok())); + })) + .await; - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/index.html") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) + let req = TestRequest::with_uri("/index.html") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/data.rs b/src/data.rs index 5ace3a8f..e8928188 100644 --- a/src/data.rs +++ b/src/data.rs @@ -139,104 +139,97 @@ mod tests { use super::*; use crate::http::StatusCode; - use crate::test::{block_on, init_service, TestRequest}; + use crate::test::{init_service, TestRequest}; use crate::{web, App, HttpResponse}; - #[test] - fn test_data_extractor() { - block_on(async { - let mut srv = init_service(App::new().data(10usize).service( + #[actix_rt::test] + async fn test_data_extractor() { + let mut srv = + init_service(App::new().data(10usize).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let mut srv = init_service(App::new().data(10u32).service( + let mut srv = + init_service(App::new().data(10u32).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - }) + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } - #[test] - fn test_register_data_extractor() { - block_on(async { - let mut srv = - init_service(App::new().register_data(Data::new(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let mut srv = - init_service(App::new().register_data(Data::new(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - }) - } - - #[test] - fn test_route_data_extractor() { - block_on(async { - let mut srv = init_service(App::new().service( - web::resource("/").data(10usize).route(web::get().to( - |data: web::Data| { - let _ = data.clone(); - HttpResponse::Ok() - }, - )), + #[actix_rt::test] + async fn test_register_data_extractor() { + let mut srv = + init_service(App::new().register_data(Data::new(10usize)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - // different type - let mut srv = init_service( - App::new().service( - web::resource("/") - .data(10u32) - .route(web::get().to(|_: web::Data| HttpResponse::Ok())), - ), - ) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - }) - } - - #[test] - fn test_override_data() { - block_on(async { - let mut srv = init_service(App::new().data(1usize).service( - web::resource("/").data(10usize).route(web::get().to( - |data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }, - )), + let mut srv = + init_service(App::new().register_data(Data::new(10u32)).service( + web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) + #[actix_rt::test] + async fn test_route_data_extractor() { + let mut srv = + init_service(App::new().service(web::resource("/").data(10usize).route( + web::get().to(|data: web::Data| { + let _ = data.clone(); + HttpResponse::Ok() + }), + ))) + .await; + + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + // different type + let mut srv = init_service( + App::new().service( + web::resource("/") + .data(10u32) + .route(web::get().to(|_: web::Data| HttpResponse::Ok())), + ), + ) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } + + #[actix_rt::test] + async fn test_override_data() { + let mut srv = init_service(App::new().data(1usize).service( + web::resource("/").data(10usize).route(web::get().to( + |data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }, + )), + )) + .await; + + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/extract.rs b/src/extract.rs index 9c863336..d43402c7 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -270,7 +270,7 @@ mod tests { use serde_derive::Deserialize; use super::*; - use crate::test::{block_on, TestRequest}; + use crate::test::TestRequest; use crate::types::{Form, FormConfig}; #[derive(Deserialize, Debug, PartialEq)] @@ -278,8 +278,8 @@ mod tests { hello: String, } - #[test] - fn test_option() { + #[actix_rt::test] + async fn test_option() { let (req, mut pl) = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", @@ -287,7 +287,9 @@ mod tests { .data(FormConfig::default().limit(4096)) .to_http_parts(); - let r = block_on(Option::>::from_request(&req, &mut pl)).unwrap(); + let r = Option::>::from_request(&req, &mut pl) + .await + .unwrap(); assert_eq!(r, None); let (req, mut pl) = TestRequest::with_header( @@ -298,7 +300,9 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); - let r = block_on(Option::>::from_request(&req, &mut pl)).unwrap(); + let r = Option::>::from_request(&req, &mut pl) + .await + .unwrap(); assert_eq!( r, Some(Form(Info { @@ -314,12 +318,14 @@ mod tests { .set_payload(Bytes::from_static(b"bye=world")) .to_http_parts(); - let r = block_on(Option::>::from_request(&req, &mut pl)).unwrap(); + let r = Option::>::from_request(&req, &mut pl) + .await + .unwrap(); assert_eq!(r, None); } - #[test] - fn test_result() { + #[actix_rt::test] + async fn test_result() { let (req, mut pl) = TestRequest::with_header( header::CONTENT_TYPE, "application/x-www-form-urlencoded", @@ -328,7 +334,8 @@ mod tests { .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); - let r = block_on(Result::, Error>::from_request(&req, &mut pl)) + let r = Result::, Error>::from_request(&req, &mut pl) + .await .unwrap() .unwrap(); assert_eq!( @@ -346,8 +353,9 @@ mod tests { .set_payload(Bytes::from_static(b"bye=world")) .to_http_parts(); - let r = - block_on(Result::, Error>::from_request(&req, &mut pl)).unwrap(); + let r = Result::, Error>::from_request(&req, &mut pl) + .await + .unwrap(); assert!(r.is_err()); } } diff --git a/src/lib.rs b/src/lib.rs index 8063d0d3..4d1facd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(clippy::borrow_interior_mutable_const, unused_imports, dead_code)] +#![allow(clippy::borrow_interior_mutable_const)] //! Actix web is a small, pragmatic, and extremely fast web framework //! for Rust. //! @@ -143,9 +143,9 @@ pub mod dev { HttpServiceFactory, ServiceRequest, ServiceResponse, WebService, }; - //pub use crate::types::form::UrlEncoded; - //pub use crate::types::json::JsonBody; - //pub use crate::types::readlines::Readlines; + pub use crate::types::form::UrlEncoded; + pub use crate::types::json::JsonBody; + pub use crate::types::readlines::Readlines; pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; pub use actix_http::encoding::Decoder as Decompress; @@ -174,17 +174,16 @@ pub mod client { //! use actix_rt::System; //! use actix_web::client::Client; //! - //! fn main() { - //! System::new("test").block_on(async { - //! let mut client = Client::default(); + //! #[actix_rt::main] + //! async fn main() { + //! let mut client = Client::default(); //! - //! // Create request builder and send request - //! let response = client.get("http://www.rust-lang.org") - //! .header("User-Agent", "Actix-web") - //! .send().await; // <- Send http request + //! // Create request builder and send request + //! let response = client.get("http://www.rust-lang.org") + //! .header("User-Agent", "Actix-web") + //! .send().await; // <- Send http request //! - //! println!("Response: {:?}", response); - //! }); + //! println!("Response: {:?}", response); //! } //! ``` pub use awc::error::{ diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 6603fc00..2ede8178 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -2,7 +2,7 @@ use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; +use futures::future::{ok, Either, FutureExt, LocalBoxFuture}; /// `Middleware` for conditionally enables another middleware. /// The controled middleware must not change the `Service` interfaces. @@ -102,7 +102,7 @@ mod tests { use crate::error::Result; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; use crate::middleware::errhandlers::*; - use crate::test::{self, block_on, TestRequest}; + use crate::test::{self, TestRequest}; use crate::HttpResponse; fn render_500(mut res: ServiceResponse) -> Result> { @@ -112,46 +112,40 @@ mod tests { Ok(ErrorHandlerResponse::Response(res)) } - #[test] - fn test_handler_enabled() { - block_on(async { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; + #[actix_rt::test] + async fn test_handler_enabled() { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::InternalServerError().finish())) + }; - let mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = + ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = Condition::new(true, mw) - .new_transform(srv.into_service()) - .await - .unwrap(); - let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()) - .await; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - }) + let mut mw = Condition::new(true, mw) + .new_transform(srv.into_service()) + .await + .unwrap(); + let resp = + test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } - #[test] - fn test_handler_disabled() { - block_on(async { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; + #[actix_rt::test] + async fn test_handler_disabled() { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::InternalServerError().finish())) + }; - let mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = + ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = Condition::new(false, mw) - .new_transform(srv.into_service()) - .await - .unwrap(); + let mut mw = Condition::new(false, mw) + .new_transform(srv.into_service()) + .await + .unwrap(); - let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()) - .await; - assert_eq!(resp.headers().get(CONTENT_TYPE), None); - }) + let resp = + test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE), None); } } diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 5c995503..05a03106 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -1,6 +1,4 @@ //! Middleware for setting default response headers -use std::future::Future; -use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; @@ -161,55 +159,50 @@ mod tests { use super::*; use crate::dev::ServiceRequest; use crate::http::header::CONTENT_TYPE; - use crate::test::{block_on, ok_service, TestRequest}; + use crate::test::{ok_service, TestRequest}; use crate::HttpResponse; - #[test] - fn test_default_headers() { - block_on(async { - let mut mw = DefaultHeaders::new() - .header(CONTENT_TYPE, "0001") - .new_transform(ok_service()) - .await - .unwrap(); + #[actix_rt::test] + async fn test_default_headers() { + let mut mw = DefaultHeaders::new() + .header(CONTENT_TYPE, "0001") + .new_transform(ok_service()) + .await + .unwrap(); - let req = TestRequest::default().to_srv_request(); - let resp = mw.call(req).await.unwrap(); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + let req = TestRequest::default().to_srv_request(); + let resp = mw.call(req).await.unwrap(); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - let req = TestRequest::default().to_srv_request(); - let srv = |req: ServiceRequest| { - ok(req.into_response( - HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish(), - )) - }; - let mut mw = DefaultHeaders::new() - .header(CONTENT_TYPE, "0001") - .new_transform(srv.into_service()) - .await - .unwrap(); - let resp = mw.call(req).await.unwrap(); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); - }) + let req = TestRequest::default().to_srv_request(); + let srv = |req: ServiceRequest| { + ok(req + .into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())) + }; + let mut mw = DefaultHeaders::new() + .header(CONTENT_TYPE, "0001") + .new_transform(srv.into_service()) + .await + .unwrap(); + let resp = mw.call(req).await.unwrap(); + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); } - #[test] - fn test_content_type() { - block_on(async { - let srv = - |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); - let mut mw = DefaultHeaders::new() - .content_type() - .new_transform(srv.into_service()) - .await - .unwrap(); + #[actix_rt::test] + async fn test_content_type() { + let srv = + |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); + let mut mw = DefaultHeaders::new() + .content_type() + .new_transform(srv.into_service()) + .await + .unwrap(); - let req = TestRequest::default().to_srv_request(); - let resp = mw.call(req).await.unwrap(); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - }) + let req = TestRequest::default().to_srv_request(); + let resp = mw.call(req).await.unwrap(); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + "application/octet-stream" + ); } } diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index c8a70285..3dc1f082 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures::future::{err, ok, Either, Future, FutureExt, LocalBoxFuture, Ready}; +use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; use hashbrown::HashMap; use crate::dev::{ServiceRequest, ServiceResponse}; @@ -151,7 +151,7 @@ mod tests { use super::*; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::test::{self, block_on, TestRequest}; + use crate::test::{self, TestRequest}; use crate::HttpResponse; fn render_500(mut res: ServiceResponse) -> Result> { @@ -161,24 +161,21 @@ mod tests { Ok(ErrorHandlerResponse::Response(res)) } - #[test] - fn test_handler() { - block_on(async { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; + #[actix_rt::test] + async fn test_handler() { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::InternalServerError().finish())) + }; - let mut mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) - .new_transform(srv.into_service()) - .await - .unwrap(); + let mut mw = ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) + .new_transform(srv.into_service()) + .await + .unwrap(); - let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()) - .await; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - }) + let resp = + test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } fn render_500_async( @@ -190,23 +187,20 @@ mod tests { Ok(ErrorHandlerResponse::Future(ok(res).boxed_local())) } - #[test] - fn test_handler_async() { - block_on(async { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; + #[actix_rt::test] + async fn test_handler_async() { + let srv = |req: ServiceRequest| { + ok(req.into_response(HttpResponse::InternalServerError().finish())) + }; - let mut mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) - .new_transform(srv.into_service()) - .await - .unwrap(); + let mut mw = ErrorHandlers::new() + .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) + .new_transform(srv.into_service()) + .await + .unwrap(); - let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()) - .await; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - }) + let resp = + test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 45df4bf3..a57ea296 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -479,10 +479,10 @@ mod tests { use super::*; use crate::http::{header, StatusCode}; - use crate::test::{block_on, TestRequest}; + use crate::test::TestRequest; - #[test] - fn test_logger() { + #[actix_rt::test] + async fn test_logger() { let srv = |req: ServiceRequest| { ok(req.into_response( HttpResponse::build(StatusCode::OK) @@ -492,18 +492,18 @@ mod tests { }; let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - let mut srv = block_on(logger.new_transform(srv.into_service())).unwrap(); + let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); let req = TestRequest::with_header( header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"), ) .to_srv_request(); - let _res = block_on(srv.call(req)); + let _res = srv.call(req).await; } - #[test] - fn test_url_path() { + #[actix_rt::test] + async fn test_url_path() { let mut format = Format::new("%T %U"); let req = TestRequest::with_header( header::USER_AGENT, @@ -533,8 +533,8 @@ mod tests { assert!(s.contains("/test/route/yeah")); } - #[test] - fn test_default_format() { + #[actix_rt::test] + async fn test_default_format() { let mut format = Format::default(); let req = TestRequest::with_header( @@ -566,8 +566,8 @@ mod tests { assert!(s.contains("ACTIX-WEB")); } - #[test] - fn test_request_time_format() { + #[actix_rt::test] + async fn test_request_time_format() { let mut format = Format::new("%t"); let req = TestRequest::default().to_srv_request(); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index b7eb1384..2926eacc 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -105,62 +105,56 @@ mod tests { use super::*; use crate::dev::ServiceRequest; - use crate::test::{block_on, call_service, init_service, TestRequest}; + use crate::test::{call_service, init_service, TestRequest}; use crate::{web, App, HttpResponse}; - #[test] - fn test_wrap() { - block_on(async { - let mut app = init_service( - App::new() - .wrap(NormalizePath::default()) - .service(web::resource("/v1/something/").to(|| HttpResponse::Ok())), - ) - .await; + #[actix_rt::test] + async fn test_wrap() { + let mut app = init_service( + App::new() + .wrap(NormalizePath::default()) + .service(web::resource("/v1/something/").to(|| HttpResponse::Ok())), + ) + .await; - let req = TestRequest::with_uri("/v1//something////").to_request(); - let res = call_service(&mut app, req).await; - assert!(res.status().is_success()); - }) + let req = TestRequest::with_uri("/v1//something////").to_request(); + let res = call_service(&mut app, req).await; + assert!(res.status().is_success()); } - #[test] - fn test_in_place_normalization() { - block_on(async { - let srv = |req: ServiceRequest| { - assert_eq!("/v1/something/", req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) - }; + #[actix_rt::test] + async fn test_in_place_normalization() { + let srv = |req: ServiceRequest| { + assert_eq!("/v1/something/", req.path()); + ok(req.into_response(HttpResponse::Ok().finish())) + }; - let mut normalize = NormalizePath - .new_transform(srv.into_service()) - .await - .unwrap(); + let mut normalize = NormalizePath + .new_transform(srv.into_service()) + .await + .unwrap(); - let req = TestRequest::with_uri("/v1//something////").to_srv_request(); - let res = normalize.call(req).await.unwrap(); - assert!(res.status().is_success()); - }) + let req = TestRequest::with_uri("/v1//something////").to_srv_request(); + let res = normalize.call(req).await.unwrap(); + assert!(res.status().is_success()); } - #[test] - fn should_normalize_nothing() { - block_on(async { - const URI: &str = "/v1/something/"; + #[actix_rt::test] + async fn should_normalize_nothing() { + const URI: &str = "/v1/something/"; - let srv = |req: ServiceRequest| { - assert_eq!(URI, req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) - }; + let srv = |req: ServiceRequest| { + assert_eq!(URI, req.path()); + ok(req.into_response(HttpResponse::Ok().finish())) + }; - let mut normalize = NormalizePath - .new_transform(srv.into_service()) - .await - .unwrap(); + let mut normalize = NormalizePath + .new_transform(srv.into_service()) + .await + .unwrap(); - let req = TestRequest::with_uri(URI).to_srv_request(); - let res = normalize.call(req).await.unwrap(); - assert!(res.status().is_success()); - }) + let req = TestRequest::with_uri(URI).to_srv_request(); + let res = normalize.call(req).await.unwrap(); + assert!(res.status().is_success()); } } diff --git a/src/request.rs b/src/request.rs index 19072fcb..84f0503c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -350,7 +350,7 @@ mod tests { use super::*; use crate::dev::{ResourceDef, ResourceMap}; use crate::http::{header, StatusCode}; - use crate::test::{block_on, call_service, init_service, TestRequest}; + use crate::test::{call_service, init_service, TestRequest}; use crate::{web, App, HttpResponse}; #[test] @@ -466,16 +466,62 @@ mod tests { ); } - #[test] - fn test_app_data() { - block_on(async { - let mut srv = init_service(App::new().data(10usize).service( - web::resource("/").to(|req: HttpRequest| { - if req.app_data::().is_some() { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } + #[actix_rt::test] + async fn test_app_data() { + let mut srv = init_service(App::new().data(10usize).service( + web::resource("/").to(|req: HttpRequest| { + if req.app_data::().is_some() { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + .await; + + let req = TestRequest::default().to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + + let mut srv = init_service(App::new().data(10u32).service( + web::resource("/").to(|req: HttpRequest| { + if req.app_data::().is_some() { + HttpResponse::Ok() + } else { + HttpResponse::BadRequest() + } + }), + )) + .await; + + let req = TestRequest::default().to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[actix_rt::test] + async fn test_extensions_dropped() { + struct Tracker { + pub dropped: bool, + } + struct Foo { + tracker: Rc>, + } + impl Drop for Foo { + fn drop(&mut self) { + self.tracker.borrow_mut().dropped = true; + } + } + + let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); + { + let tracker2 = Rc::clone(&tracker); + let mut srv = init_service(App::new().data(10u32).service( + web::resource("/").to(move |req: HttpRequest| { + req.extensions_mut().insert(Foo { + tracker: Rc::clone(&tracker2), + }); + HttpResponse::Ok() }), )) .await; @@ -483,58 +529,8 @@ mod tests { let req = TestRequest::default().to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); + } - let mut srv = init_service(App::new().data(10u32).service( - web::resource("/").to(|req: HttpRequest| { - if req.app_data::().is_some() { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - }) - } - - #[test] - fn test_extensions_dropped() { - block_on(async { - struct Tracker { - pub dropped: bool, - } - struct Foo { - tracker: Rc>, - } - impl Drop for Foo { - fn drop(&mut self) { - self.tracker.borrow_mut().dropped = true; - } - } - - let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); - { - let tracker2 = Rc::clone(&tracker); - let mut srv = init_service(App::new().data(10u32).service( - web::resource("/").to(move |req: HttpRequest| { - req.extensions_mut().insert(Foo { - tracker: Rc::clone(&tracker2), - }); - HttpResponse::Ok() - }), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } - - assert!(tracker.borrow().dropped); - }) + assert!(tracker.borrow().dropped); } } diff --git a/src/resource.rs b/src/resource.rs index 758e2f28..866cbecf 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -6,12 +6,11 @@ use std::rc::Rc; use std::task::{Context, Poll}; use actix_http::{Error, Extensions, Response}; -use actix_service::boxed::{self, BoxedNewService, BoxedService}; +use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; use futures::future::{ok, Either, LocalBoxFuture, Ready}; -use pin_project::pin_project; use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; @@ -22,8 +21,8 @@ use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; -type HttpService = BoxedService; -type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; +type HttpService = BoxService; +type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// *Resource* is an entry in resources table which corresponds to requested URL. /// @@ -585,40 +584,20 @@ impl ServiceFactory for ResourceEndpoint { mod tests { use std::time::Duration; + use actix_rt::time::delay_for; use actix_service::Service; - use futures::future::{ok, Future}; - use tokio_timer::delay_for; + use futures::future::ok; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; - use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_service, init_service, TestRequest}; + use crate::service::ServiceRequest; + use crate::test::{call_service, init_service, TestRequest}; use crate::{guard, web, App, Error, HttpResponse}; - fn md( - req: ServiceRequest, - srv: &mut S, - ) -> impl Future, Error>> - where - S: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - >, - { - let fut = srv.call(req); - async move { - let mut res = fut.await?; - res.headers_mut() - .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(res) - } - } - - #[test] - fn test_middleware() { - block_on(async { - let mut srv = init_service( + #[actix_rt::test] + async fn test_middleware() { + let mut srv = + init_service( App::new().service( web::resource("/test") .name("test") @@ -630,185 +609,173 @@ mod tests { ), ) .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - }) + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); } - #[test] - fn test_middleware_fn() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::resource("/test") - .wrap_fn(|req, srv| { - let fut = srv.call(req); - async { - fut.await.map(|mut res| { - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - res - }) - } - }) - .route(web::get().to(|| HttpResponse::Ok())), - ), - ) + #[actix_rt::test] + async fn test_middleware_fn() { + let mut srv = init_service( + App::new().service( + web::resource("/test") + .wrap_fn(|req, srv| { + let fut = srv.call(req); + async { + fut.await.map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + } + }) + .route(web::get().to(|| HttpResponse::Ok())), + ), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[actix_rt::test] + async fn test_to() { + let mut srv = + init_service(App::new().service(web::resource("/test").to(|| { + async { + delay_for(Duration::from_millis(100)).await; + Ok::<_, Error>(HttpResponse::Ok()) + } + }))) .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - }) + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_to() { - block_on(async { - let mut srv = - init_service(App::new().service(web::resource("/test").to(|| { - async { - delay_for(Duration::from_millis(100)).await; - Ok::<_, Error>(HttpResponse::Ok()) - } - }))) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) - } + #[actix_rt::test] + async fn test_default_resource() { + let mut srv = init_service( + App::new() + .service( + web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), + ) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::BadRequest())) + }), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - #[test] - fn test_default_resource() { - block_on(async { - let mut srv = init_service( - App::new() - .service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())), - ) + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let mut srv = init_service( + App::new().service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) .default_service(|r: ServiceRequest| { ok(r.into_response(HttpResponse::BadRequest())) }), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); + ), + ) + .await; - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let mut srv = init_service( - App::new().service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::BadRequest())) - }), + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[actix_rt::test] + async fn test_resource_guards() { + let mut srv = init_service( + App::new() + .service( + web::resource("/test/{p}") + .guard(guard::Get()) + .to(|| HttpResponse::Ok()), + ) + .service( + web::resource("/test/{p}") + .guard(guard::Put()) + .to(|| HttpResponse::Created()), + ) + .service( + web::resource("/test/{p}") + .guard(guard::Delete()) + .to(|| HttpResponse::NoContent()), ), - ) - .await; + ) + .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/test/it") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - }) + let req = TestRequest::with_uri("/test/it") + .method(Method::PUT) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::CREATED); + + let req = TestRequest::with_uri("/test/it") + .method(Method::DELETE) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::NO_CONTENT); } - #[test] - fn test_resource_guards() { - block_on(async { - let mut srv = init_service( - App::new() - .service( - web::resource("/test/{p}") - .guard(guard::Get()) - .to(|| HttpResponse::Ok()), - ) - .service( - web::resource("/test/{p}") - .guard(guard::Put()) - .to(|| HttpResponse::Created()), - ) - .service( - web::resource("/test/{p}") - .guard(guard::Delete()) - .to(|| HttpResponse::NoContent()), - ), - ) - .await; + #[actix_rt::test] + async fn test_data() { + let mut srv = init_service( + App::new() + .data(1.0f64) + .data(1usize) + .register_data(web::Data::new('-')) + .service( + web::resource("/test") + .data(10usize) + .register_data(web::Data::new('*')) + .guard(guard::Get()) + .to( + |data1: web::Data, + data2: web::Data, + data3: web::Data| { + assert_eq!(*data1, 10); + assert_eq!(*data2, '*'); + assert_eq!(*data3, 1.0); + HttpResponse::Ok() + }, + ), + ), + ) + .await; - let req = TestRequest::with_uri("/test/it") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/it") - .method(Method::PUT) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::CREATED); - - let req = TestRequest::with_uri("/test/it") - .method(Method::DELETE) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NO_CONTENT); - }) - } - - #[test] - fn test_data() { - block_on(async { - let mut srv = init_service( - App::new() - .data(1.0f64) - .data(1usize) - .register_data(web::Data::new('-')) - .service( - web::resource("/test") - .data(10usize) - .register_data(web::Data::new('*')) - .guard(guard::Get()) - .to( - |data1: web::Data, - data2: web::Data, - data3: web::Data| { - assert_eq!(*data1, 10); - assert_eq!(*data2, '*'); - assert_eq!(*data3, 1.0); - HttpResponse::Ok() - }, - ), - ), - ) - .await; - - let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) + let req = TestRequest::get().uri("/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/responder.rs b/src/responder.rs index fd86bb68..7b30315f 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -10,7 +10,7 @@ use actix_http::http::{ }; use actix_http::{Error, Response, ResponseBuilder}; use bytes::{Bytes, BytesMut}; -use futures::future::{err, ok, Either as EitherFuture, LocalBoxFuture, Ready}; +use futures::future::{err, ok, Either as EitherFuture, Ready}; use futures::ready; use pin_project::{pin_project, project}; @@ -457,37 +457,34 @@ pub(crate) mod tests { use super::*; use crate::dev::{Body, ResponseBody}; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::test::{block_on, init_service, TestRequest}; + use crate::test::{init_service, TestRequest}; use crate::{error, web, App, HttpResponse}; - #[test] - fn test_option_responder() { - block_on(async { - let mut srv = init_service( - App::new() - .service( - web::resource("/none") - .to(|| async { Option::<&'static str>::None }), - ) - .service(web::resource("/some").to(|| async { Some("some") })), - ) - .await; + #[actix_rt::test] + async fn test_option_responder() { + let mut srv = init_service( + App::new() + .service( + web::resource("/none").to(|| async { Option::<&'static str>::None }), + ) + .service(web::resource("/some").to(|| async { Some("some") })), + ) + .await; - let req = TestRequest::with_uri("/none").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/none").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/some").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"some")); - } - _ => panic!(), + let req = TestRequest::with_uri("/some").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"some")); } - }) + _ => panic!(), + } } pub(crate) trait BodyTest { @@ -516,153 +513,142 @@ pub(crate) mod tests { } } - #[test] - fn test_responder() { - block_on(async { - let req = TestRequest::default().to_http_request(); + #[actix_rt::test] + async fn test_responder() { + let req = TestRequest::default().to_http_request(); - let resp: HttpResponse = "test".respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); + let resp: HttpResponse = "test".respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); - let resp: HttpResponse = b"test".respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); + let resp: HttpResponse = b"test".respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); - let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); + let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); - let resp: HttpResponse = - (&"test".to_string()).respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); + let resp: HttpResponse = (&"test".to_string()).respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); - let resp: HttpResponse = - Bytes::from_static(b"test").respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); + let resp: HttpResponse = + Bytes::from_static(b"test").respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); - let resp: HttpResponse = BytesMut::from(b"test".as_ref()) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - - // InternalError - let resp: HttpResponse = - error::InternalError::new("err", StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - }) - } - - #[test] - fn test_result_responder() { - block_on(async { - let req = TestRequest::default().to_http_request(); - - // Result - let resp: HttpResponse = Ok::<_, Error>("test".to_string()) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - - let res = Err::(error::InternalError::new( - "err", - StatusCode::BAD_REQUEST, - )) + let resp: HttpResponse = BytesMut::from(b"test".as_ref()) .respond_to(&req) - .await; - assert!(res.is_err()); - }) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/octet-stream") + ); + + // InternalError + let resp: HttpResponse = + error::InternalError::new("err", StatusCode::BAD_REQUEST) + .respond_to(&req) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } - #[test] - fn test_custom_responder() { - block_on(async { - let req = TestRequest::default().to_http_request(); - let res = "test" - .to_string() - .with_status(StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(res.body().bin_ref(), b"test"); + #[actix_rt::test] + async fn test_result_responder() { + let req = TestRequest::default().to_http_request(); - let res = "test" - .to_string() - .with_header("content-type", "json") - .respond_to(&req) - .await - .unwrap(); + // Result + let resp: HttpResponse = Ok::<_, Error>("test".to_string()) + .respond_to(&req) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!(resp.body().bin_ref(), b"test"); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("text/plain; charset=utf-8") + ); - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.body().bin_ref(), b"test"); - assert_eq!( - res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("json") - ); - }) + let res = + Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) + .respond_to(&req) + .await; + assert!(res.is_err()); } - #[test] - fn test_tuple_responder_with_status_code() { - block_on(async { - let req = TestRequest::default().to_http_request(); - let res = ("test".to_string(), StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(res.body().bin_ref(), b"test"); + #[actix_rt::test] + async fn test_custom_responder() { + let req = TestRequest::default().to_http_request(); + let res = "test" + .to_string() + .with_status(StatusCode::BAD_REQUEST) + .respond_to(&req) + .await + .unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!(res.body().bin_ref(), b"test"); - let req = TestRequest::default().to_http_request(); - let res = ("test".to_string(), StatusCode::OK) - .with_header("content-type", "json") - .respond_to(&req) - .await - .unwrap(); - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.body().bin_ref(), b"test"); - assert_eq!( - res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("json") - ); - }) + let res = "test" + .to_string() + .with_header("content-type", "json") + .respond_to(&req) + .await + .unwrap(); + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); + } + + #[actix_rt::test] + async fn test_tuple_responder_with_status_code() { + let req = TestRequest::default().to_http_request(); + let res = ("test".to_string(), StatusCode::BAD_REQUEST) + .respond_to(&req) + .await + .unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!(res.body().bin_ref(), b"test"); + + let req = TestRequest::default().to_http_request(); + let res = ("test".to_string(), StatusCode::OK) + .with_header("content-type", "json") + .respond_to(&req) + .await + .unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); } } diff --git a/src/route.rs b/src/route.rs index 3ebfc3f5..93f88bfe 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,7 +5,7 @@ use std::task::{Context, Poll}; use actix_http::{http::Method, Error}; use actix_service::{Service, ServiceFactory}; -use futures::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready}; +use futures::future::{ready, FutureExt, LocalBoxFuture}; use crate::extract::FromRequest; use crate::guard::{self, Guard}; @@ -342,93 +342,90 @@ where mod tests { use std::time::Duration; + use actix_rt::time::delay_for; use bytes::Bytes; - use futures::Future; use serde_derive::Serialize; - use tokio_timer::delay_for; use crate::http::{Method, StatusCode}; - use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; - use crate::{error, web, App, Error, HttpResponse}; + use crate::test::{call_service, init_service, read_body, TestRequest}; + use crate::{error, web, App, HttpResponse}; #[derive(Serialize, PartialEq, Debug)] struct MyObject { name: String, } - #[test] - fn test_route() { - block_on(async { - let mut srv = init_service( - App::new() - .service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())) - .route(web::put().to(|| { - async { - Err::(error::ErrorBadRequest("err")) - } - })) - .route(web::post().to(|| { - async { - delay_for(Duration::from_millis(100)).await; - HttpResponse::Created() - } - })) - .route(web::delete().to(|| { - async { - delay_for(Duration::from_millis(100)).await; - Err::(error::ErrorBadRequest("err")) - } - })), - ) - .service(web::resource("/json").route(web::get().to(|| { - async { - delay_for(Duration::from_millis(25)).await; - web::Json(MyObject { - name: "test".to_string(), - }) - } - }))), - ) - .await; + #[actix_rt::test] + async fn test_route() { + let mut srv = init_service( + App::new() + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::put().to(|| { + async { + Err::(error::ErrorBadRequest("err")) + } + })) + .route(web::post().to(|| { + async { + delay_for(Duration::from_millis(100)).await; + HttpResponse::Created() + } + })) + .route(web::delete().to(|| { + async { + delay_for(Duration::from_millis(100)).await; + Err::(error::ErrorBadRequest("err")) + } + })), + ) + .service(web::resource("/json").route(web::get().to(|| { + async { + delay_for(Duration::from_millis(25)).await; + web::Json(MyObject { + name: "test".to_string(), + }) + } + }))), + ) + .await; - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/test") + .method(Method::GET) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::CREATED); + let req = TestRequest::with_uri("/test") + .method(Method::POST) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::CREATED); - let req = TestRequest::with_uri("/test") - .method(Method::PUT) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/test") + .method(Method::PUT) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/test") - .method(Method::DELETE) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + let req = TestRequest::with_uri("/test") + .method(Method::DELETE) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + let req = TestRequest::with_uri("/test") + .method(Method::HEAD) + .to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let req = TestRequest::with_uri("/json").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/json").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); - }) + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); } } diff --git a/src/scope.rs b/src/scope.rs index 9bec0a1f..db6f5da5 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -6,7 +6,7 @@ use std::task::{Context, Poll}; use actix_http::{Extensions, Response}; use actix_router::{ResourceDef, ResourceInfo, Router}; -use actix_service::boxed::{self, BoxedNewService, BoxedService}; +use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; @@ -25,8 +25,8 @@ use crate::service::{ }; type Guards = Vec>; -type HttpService = BoxedService; -type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; +type HttpService = BoxService; +type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = LocalBoxFuture<'static, Result>; /// Resources scope. @@ -666,443 +666,389 @@ mod tests { use actix_service::Service; use bytes::Bytes; use futures::future::ok; - use futures::Future; use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; - use crate::service::{ServiceRequest, ServiceResponse}; - use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; - use crate::{guard, web, App, Error, HttpRequest, HttpResponse}; + use crate::service::ServiceRequest; + use crate::test::{call_service, init_service, read_body, TestRequest}; + use crate::{guard, web, App, HttpRequest, HttpResponse}; - #[test] - fn test_scope() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ) - .await; + #[actix_rt::test] + async fn test_scope() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ) + .await; - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_scope_root() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("/app") + #[actix_rt::test] + async fn test_scope_root() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), + ) + .await; + + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + } + + #[actix_rt::test] + async fn test_scope_root2() { + let mut srv = init_service(App::new().service( + web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), + )) + .await; + + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_scope_root3() { + let mut srv = init_service(App::new().service( + web::scope("/app/").service(web::resource("/").to(|| HttpResponse::Ok())), + )) + .await; + + let req = TestRequest::with_uri("/app").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_scope_route() { + let mut srv = init_service( + App::new().service( + web::scope("app") + .route("/path1", web::get().to(|| HttpResponse::Ok())) + .route("/path1", web::delete().to(|| HttpResponse::Ok())), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_scope_route_without_leading_slash() { + let mut srv = init_service( + App::new().service( + web::scope("app").service( + web::resource("path1") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::delete().to(|| HttpResponse::Ok())), + ), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::DELETE) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + } + + #[actix_rt::test] + async fn test_scope_guard() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .guard(guard::Get()) + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::with_uri("/app/path1") + .method(Method::GET) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_scope_variable_segment() { + let mut srv = + init_service(App::new().service(web::scope("/ab-{project}").service( + web::resource("/path1").to(|r: HttpRequest| { + async move { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + } + }), + ))) + .await; + + let req = TestRequest::with_uri("/ab-project1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project1")); + } + _ => panic!(), + } + + let req = TestRequest::with_uri("/aa-project1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_nested_scope() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::scope("/t1").service( + web::resource("/path1").to(|| HttpResponse::Created()), + )), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/t1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + } + + #[actix_rt::test] + async fn test_nested_scope_no_slash() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::scope("t1").service( + web::resource("/path1").to(|| HttpResponse::Created()), + )), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/t1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + } + + #[actix_rt::test] + async fn test_nested_scope_root() { + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") .service(web::resource("").to(|| HttpResponse::Ok())) .service(web::resource("/").to(|| HttpResponse::Created())), ), - ) - .await; + ), + ) + .await; - let req = TestRequest::with_uri("/app").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/t1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/app/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - }) + let req = TestRequest::with_uri("/app/t1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); } - #[test] - fn test_scope_root2() { - block_on(async { - let mut srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), - )) - .await; - - let req = TestRequest::with_uri("/app").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) - } - - #[test] - fn test_scope_root3() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("/app/") - .service(web::resource("/").to(|| HttpResponse::Ok())), - ), - ) - .await; - - let req = TestRequest::with_uri("/app").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - }) - } - - #[test] - fn test_scope_route() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("app") - .route("/path1", web::get().to(|| HttpResponse::Ok())) - .route("/path1", web::delete().to(|| HttpResponse::Ok())), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - }) - } - - #[test] - fn test_scope_route_without_leading_slash() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("app").service( - web::resource("path1") - .route(web::get().to(|| HttpResponse::Ok())) - .route(web::delete().to(|| HttpResponse::Ok())), - ), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - }) - } - - #[test] - fn test_scope_guard() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("/app") + #[actix_rt::test] + async fn test_nested_scope_filter() { + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") .guard(guard::Get()) .service(web::resource("/path1").to(|| HttpResponse::Ok())), ), - ) - .await; + ), + ) + .await; - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::POST) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let req = TestRequest::with_uri("/app/path1") - .method(Method::GET) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) + let req = TestRequest::with_uri("/app/t1/path1") + .method(Method::GET) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); } - #[test] - fn test_scope_variable_segment() { - block_on(async { - let mut srv = - init_service(App::new().service(web::scope("/ab-{project}").service( - web::resource("/path1").to(|r: HttpRequest| { - async move { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - } - }), - ))) - .await; + #[actix_rt::test] + async fn test_nested_scope_with_variable_segment() { + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project_id}").service(web::resource("/path1").to( + |r: HttpRequest| { + async move { + HttpResponse::Created() + .body(format!("project: {}", &r.match_info()["project_id"])) + } + }, + )), + ))) + .await; - let req = TestRequest::with_uri("/ab-project1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); + let req = TestRequest::with_uri("/app/project_1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project1")); - } - _ => panic!(), + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: project_1")); } - - let req = TestRequest::with_uri("/aa-project1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - }) - } - - #[test] - fn test_nested_scope() { - block_on(async { - let mut srv = - init_service(App::new().service( - web::scope("/app").service(web::scope("/t1").service( - web::resource("/path1").to(|| HttpResponse::Created()), - )), - )) - .await; - - let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - }) - } - - #[test] - fn test_nested_scope_no_slash() { - block_on(async { - let mut srv = - init_service(App::new().service( - web::scope("/app").service(web::scope("t1").service( - web::resource("/path1").to(|| HttpResponse::Created()), - )), - )) - .await; - - let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - }) - } - - #[test] - fn test_nested_scope_root() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("/app").service( - web::scope("/t1") - .service(web::resource("").to(|| HttpResponse::Ok())) - .service(web::resource("/").to(|| HttpResponse::Created())), - ), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/t1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - }) - } - - #[test] - fn test_nested_scope_filter() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("/app").service( - web::scope("/t1") - .guard(guard::Get()) - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) - } - - #[test] - fn test_nested_scope_with_variable_segment() { - block_on(async { - let mut srv = init_service(App::new().service(web::scope("/app").service( - web::scope("/{project_id}").service(web::resource("/path1").to( - |r: HttpRequest| { - async move { - HttpResponse::Created().body(format!( - "project: {}", - &r.match_info()["project_id"] - )) - } - }, - )), - ))) - .await; - - let req = TestRequest::with_uri("/app/project_1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); - } - _ => panic!(), - } - }) - } - - #[test] - fn test_nested2_scope_with_variable_segment() { - block_on(async { - let mut srv = init_service(App::new().service(web::scope("/app").service( - web::scope("/{project}").service(web::scope("/{id}").service( - web::resource("/path1").to(|r: HttpRequest| { - async move { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - } - }), - )), - ))) - .await; - - let req = TestRequest::with_uri("/app/test/1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/app/test/1/path2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - }) - } - - #[test] - fn test_default_resource() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("/path1").to(|| HttpResponse::Ok())) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::BadRequest())) - }), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/path2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/path2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - }) - } - - #[test] - fn test_default_resource_propagation() { - block_on(async { - let mut srv = init_service( - App::new() - .service(web::scope("/app1").default_service( - web::resource("").to(|| HttpResponse::BadRequest()), - )) - .service(web::scope("/app2")) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::MethodNotAllowed())) - }), - ) - .await; - - let req = TestRequest::with_uri("/non-exist").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/app1/non-exist").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/app2/non-exist").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - }) - } - - fn md( - req: ServiceRequest, - srv: &mut S, - ) -> impl Future, Error>> - where - S: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - >, - { - let fut = srv.call(req); - async move { - let mut res = fut.await?; - res.headers_mut() - .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(res) + _ => panic!(), } } - #[test] - fn test_middleware() { - block_on(async { - let mut srv = init_service( + #[actix_rt::test] + async fn test_nested2_scope_with_variable_segment() { + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project}").service(web::scope("/{id}").service( + web::resource("/path1").to(|r: HttpRequest| { + async move { + HttpResponse::Created().body(format!( + "project: {} - {}", + &r.match_info()["project"], + &r.match_info()["id"], + )) + } + }), + )), + ))) + .await; + + let req = TestRequest::with_uri("/app/test/1/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::CREATED); + + match resp.response().body() { + ResponseBody::Body(Body::Bytes(ref b)) => { + let bytes: Bytes = b.clone().into(); + assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); + } + _ => panic!(), + } + + let req = TestRequest::with_uri("/app/test/1/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_default_resource() { + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::BadRequest())) + }), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let req = TestRequest::with_uri("/path2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_default_resource_propagation() { + let mut srv = init_service( + App::new() + .service(web::scope("/app1").default_service( + web::resource("").to(|| HttpResponse::BadRequest()), + )) + .service(web::scope("/app2")) + .default_service(|r: ServiceRequest| { + ok(r.into_response(HttpResponse::MethodNotAllowed())) + }), + ) + .await; + + let req = TestRequest::with_uri("/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + + let req = TestRequest::with_uri("/app1/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let req = TestRequest::with_uri("/app2/non-exist").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); + } + + #[actix_rt::test] + async fn test_middleware() { + let mut srv = + init_service( App::new().service( web::scope("app") .wrap(DefaultHeaders::new().header( @@ -1117,186 +1063,169 @@ mod tests { ) .await; - let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - }) + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); } - #[test] - fn test_middleware_fn() { - block_on(async { - let mut srv = init_service( - App::new().service( - web::scope("app") - .wrap_fn(|req, srv| { - let fut = srv.call(req); - async move { - let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - Ok(res) - } - }) - .route("/test", web::get().to(|| HttpResponse::Ok())), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - }) - } - - #[test] - fn test_override_data() { - block_on(async { - let mut srv = init_service(App::new().data(1usize).service( - web::scope("app").data(10usize).route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), - ), - )) - .await; - - let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) - } - - #[test] - fn test_override_register_data() { - block_on(async { - let mut srv = init_service( - App::new().register_data(web::Data::new(1usize)).service( - web::scope("app") - .register_data(web::Data::new(10usize)) - .route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(*data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), - ), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - }) - } - - #[test] - fn test_scope_config() { - block_on(async { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.route("/path1", web::get().to(|| HttpResponse::Ok())); - }))) - .await; - - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) - } - - #[test] - fn test_scope_config_2() { - block_on(async { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.route("/", web::get().to(|| HttpResponse::Ok())); - })); - }))) - .await; - - let req = TestRequest::with_uri("/app/v1/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - }) - } - - #[test] - fn test_url_for_external() { - block_on(async { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - s.route( - "/", - web::get().to(|req: HttpRequest| { - async move { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["xxxxxx"]) - .unwrap() - .as_str() - )) - } - }), - ); - })); - }))) - .await; - - let req = TestRequest::with_uri("/app/v1/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); - }) - } - - #[test] - fn test_url_for_nested() { - block_on(async { - let mut srv = init_service(App::new().service(web::scope("/a").service( - web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( - web::get().to(|req: HttpRequest| { + #[actix_rt::test] + async fn test_middleware_fn() { + let mut srv = init_service( + App::new().service( + web::scope("app") + .wrap_fn(|req, srv| { + let fut = srv.call(req); async move { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("c", &["12345"]).unwrap() - )) + let mut res = fut.await?; + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + Ok(res) } - }), - )), - ))) + }) + .route("/test", web::get().to(|| HttpResponse::Ok())), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[actix_rt::test] + async fn test_override_data() { + let mut srv = init_service(App::new().data(1usize).service( + web::scope("app").data(10usize).route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + )) + .await; + + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_override_register_data() { + let mut srv = init_service( + App::new().register_data(web::Data::new(1usize)).service( + web::scope("app") + .register_data(web::Data::new(10usize)) + .route( + "/t", + web::get().to(|data: web::Data| { + assert_eq!(*data, 10); + let _ = data.clone(); + HttpResponse::Ok() + }), + ), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/t").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_scope_config() { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.route("/path1", web::get().to(|| HttpResponse::Ok())); + }))) .await; - let req = TestRequest::with_uri("/a/b/c/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!( - body, - Bytes::from_static(b"http://localhost:8080/a/b/c/12345") - ); - }) + let req = TestRequest::with_uri("/app/path1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_scope_config_2() { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.route("/", web::get().to(|| HttpResponse::Ok())); + })); + }))) + .await; + + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_url_for_external() { + let mut srv = + init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.external_resource( + "youtube", + "https://youtube.com/watch/{video_id}", + ); + s.route( + "/", + web::get().to(|req: HttpRequest| { + async move { + HttpResponse::Ok().body(format!( + "{}", + req.url_for("youtube", &["xxxxxx"]) + .unwrap() + .as_str() + )) + } + }), + ); + })); + }))) + .await; + + let req = TestRequest::with_uri("/app/v1/").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); + } + + #[actix_rt::test] + async fn test_url_for_nested() { + let mut srv = init_service(App::new().service(web::scope("/a").service( + web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( + web::get().to(|req: HttpRequest| { + async move { + HttpResponse::Ok() + .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) + } + }), + )), + ))) + .await; + + let req = TestRequest::with_uri("/a/b/c/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!( + body, + Bytes::from_static(b"http://localhost:8080/a/b/c/12345") + ); } } diff --git a/src/service.rs b/src/service.rs index 39540b06..b392e6e8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -10,7 +10,6 @@ use actix_http::{ }; use actix_router::{Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; -use futures::future::{ok, Ready}; use crate::config::{AppConfig, AppService}; use crate::data::Data; @@ -529,9 +528,10 @@ where #[cfg(test)] mod tests { use super::*; - use crate::test::{block_on, init_service, TestRequest}; + use crate::test::{init_service, TestRequest}; use crate::{guard, http, web, App, HttpResponse}; use actix_service::Service; + use futures::future::ok; #[test] fn test_service_request() { @@ -554,35 +554,29 @@ mod tests { assert!(ServiceRequest::from_request(r).is_err()); } - #[test] - fn test_service() { - block_on(async { - let mut srv = init_service( - App::new().service(web::service("/test").name("test").finish( - |req: ServiceRequest| { - ok(req.into_response(HttpResponse::Ok().finish())) - }, - )), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), http::StatusCode::OK); + #[actix_rt::test] + async fn test_service() { + let mut srv = init_service( + App::new().service(web::service("/test").name("test").finish( + |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), + )), + ) + .await; + let req = TestRequest::with_uri("/test").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); - let mut srv = init_service(App::new().service( - web::service("/test").guard(guard::Get()).finish( - |req: ServiceRequest| { - ok(req.into_response(HttpResponse::Ok().finish())) - }, - ), - )) - .await; - let req = TestRequest::with_uri("/test") - .method(http::Method::PUT) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); - }) + let mut srv = init_service( + App::new().service(web::service("/test").guard(guard::Get()).finish( + |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), + )), + ) + .await; + let req = TestRequest::with_uri("/test") + .method(http::Method::PUT) + .to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); } #[test] diff --git a/src/test.rs b/src/test.rs index 0776b0f1..e1939315 100644 --- a/src/test.rs +++ b/src/test.rs @@ -9,14 +9,13 @@ use actix_router::{Path, ResourceDef, Url}; use actix_server_config::ServerConfig; use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use bytes::{Bytes, BytesMut}; -use futures::future::{ok, Future, FutureExt}; +use futures::future::ok; use futures::stream::{Stream, StreamExt}; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; pub use actix_http::test::TestBuffer; -pub use actix_testing::{block_fn, block_on, run_on}; use crate::config::{AppConfig, AppConfigInner}; use crate::data::Data; @@ -51,8 +50,8 @@ pub fn default_service( /// use actix_service::Service; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// -/// #[test] -/// fn test_init_service() { +/// #[actix_rt::test] +/// async fn test_init_service() { /// let mut app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| async { HttpResponse::Ok() })) @@ -62,7 +61,7 @@ pub fn default_service( /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Execute application -/// let resp = test::block_on(app.call(req)).unwrap(); +/// let resp = app.call(req).await.unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` @@ -116,14 +115,13 @@ where } /// Helper function that returns a response body of a TestRequest -/// This function blocks the current thread until futures complete. /// /// ```rust /// use actix_web::{test, web, App, HttpResponse, http::header}; /// use bytes::Bytes; /// -/// #[test] -/// fn test_index() { +/// #[actix_rt::test] +/// async fn test_index() { /// let mut app = test::init_service( /// App::new().service( /// web::resource("/index.html") @@ -149,7 +147,7 @@ where let mut resp = app .call(req) .await - .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")); + .unwrap_or_else(|_| panic!("read_response failed at application call")); let mut body = resp.take_body(); let mut bytes = BytesMut::new(); @@ -160,14 +158,13 @@ where } /// Helper function that returns a response body of a ServiceResponse. -/// This function blocks the current thread until futures complete. /// /// ```rust /// use actix_web::{test, web, App, HttpResponse, http::header}; /// use bytes::Bytes; /// -/// #[test] -/// fn test_index() { +/// #[actix_rt::test] +/// async fn test_index() { /// let mut app = test::init_service( /// App::new().service( /// web::resource("/index.html") @@ -210,7 +207,6 @@ where } /// Helper function that returns a deserialized response body of a TestRequest -/// This function blocks the current thread until futures complete. /// /// ```rust /// use actix_web::{App, test, web, HttpResponse, http::header}; @@ -222,8 +218,8 @@ where /// name: String /// } /// -/// #[test] -/// fn test_add_person() { +/// #[actix_rt::test] +/// async fn test_add_person() { /// let mut app = test::init_service( /// App::new().service( /// web::resource("/people") @@ -512,90 +508,81 @@ mod tests { use super::*; use crate::{http::header, web, App, HttpResponse}; - #[test] - fn test_basics() { - block_on(async { - let req = TestRequest::with_hdr(header::ContentType::json()) - .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) - .param("test", "123") - .data(10u32) - .to_http_request(); - assert!(req.headers().contains_key(header::CONTENT_TYPE)); - assert!(req.headers().contains_key(header::DATE)); - assert_eq!(&req.match_info()["test"], "123"); - assert_eq!(req.version(), Version::HTTP_2); - let data = req.get_app_data::().unwrap(); - assert!(req.get_app_data::().is_none()); - assert_eq!(*data, 10); - assert_eq!(*data.get_ref(), 10); + #[actix_rt::test] + async fn test_basics() { + let req = TestRequest::with_hdr(header::ContentType::json()) + .version(Version::HTTP_2) + .set(header::Date(SystemTime::now().into())) + .param("test", "123") + .data(10u32) + .to_http_request(); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); + assert!(req.headers().contains_key(header::DATE)); + assert_eq!(&req.match_info()["test"], "123"); + assert_eq!(req.version(), Version::HTTP_2); + let data = req.get_app_data::().unwrap(); + assert!(req.get_app_data::().is_none()); + assert_eq!(*data, 10); + assert_eq!(*data.get_ref(), 10); - assert!(req.app_data::().is_none()); - let data = req.app_data::().unwrap(); - assert_eq!(*data, 10); - }) + assert!(req.app_data::().is_none()); + let data = req.app_data::().unwrap(); + assert_eq!(*data, 10); } - #[test] - fn test_request_methods() { - block_on(async { - let mut app = init_service( - App::new().service( - web::resource("/index.html") - .route( - web::put().to(|| async { HttpResponse::Ok().body("put!") }), - ) - .route( - web::patch() - .to(|| async { HttpResponse::Ok().body("patch!") }), - ) - .route( - web::delete() - .to(|| async { HttpResponse::Ok().body("delete!") }), - ), - ), - ) + #[actix_rt::test] + async fn test_request_methods() { + let mut app = init_service( + App::new().service( + web::resource("/index.html") + .route(web::put().to(|| async { HttpResponse::Ok().body("put!") })) + .route( + web::patch().to(|| async { HttpResponse::Ok().body("patch!") }), + ) + .route( + web::delete() + .to(|| async { HttpResponse::Ok().body("delete!") }), + ), + ), + ) + .await; + + let put_req = TestRequest::put() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); + + let result = read_response(&mut app, put_req).await; + assert_eq!(result, Bytes::from_static(b"put!")); + + let patch_req = TestRequest::patch() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); + + let result = read_response(&mut app, patch_req).await; + assert_eq!(result, Bytes::from_static(b"patch!")); + + let delete_req = TestRequest::delete().uri("/index.html").to_request(); + let result = read_response(&mut app, delete_req).await; + assert_eq!(result, Bytes::from_static(b"delete!")); + } + + #[actix_rt::test] + async fn test_response() { + let mut app = + init_service(App::new().service(web::resource("/index.html").route( + web::post().to(|| async { HttpResponse::Ok().body("welcome!") }), + ))) .await; - let put_req = TestRequest::put() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); + let req = TestRequest::post() + .uri("/index.html") + .header(header::CONTENT_TYPE, "application/json") + .to_request(); - let result = read_response(&mut app, put_req).await; - assert_eq!(result, Bytes::from_static(b"put!")); - - let patch_req = TestRequest::patch() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); - - let result = read_response(&mut app, patch_req).await; - assert_eq!(result, Bytes::from_static(b"patch!")); - - let delete_req = TestRequest::delete().uri("/index.html").to_request(); - let result = read_response(&mut app, delete_req).await; - assert_eq!(result, Bytes::from_static(b"delete!")); - }) - } - - #[test] - fn test_response() { - block_on(async { - let mut app = - init_service(App::new().service(web::resource("/index.html").route( - web::post().to(|| async { HttpResponse::Ok().body("welcome!") }), - ))) - .await; - - let req = TestRequest::post() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); - - let result = read_response(&mut app, req).await; - assert_eq!(result, Bytes::from_static(b"welcome!")); - }) + let result = read_response(&mut app, req).await; + assert_eq!(result, Bytes::from_static(b"welcome!")); } #[derive(Serialize, Deserialize)] @@ -604,114 +591,103 @@ mod tests { name: String, } - #[test] - fn test_response_json() { - block_on(async { - let mut app = - init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - async { HttpResponse::Ok().json(person.into_inner()) } - }), - ))) - .await; + #[actix_rt::test] + async fn test_response_json() { + let mut app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| { + async { HttpResponse::Ok().json(person.into_inner()) } + }), + ))) + .await; - let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); + let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); - let req = TestRequest::post() - .uri("/people") - .header(header::CONTENT_TYPE, "application/json") - .set_payload(payload) - .to_request(); + let req = TestRequest::post() + .uri("/people") + .header(header::CONTENT_TYPE, "application/json") + .set_payload(payload) + .to_request(); - let result: Person = read_response_json(&mut app, req).await; - assert_eq!(&result.id, "12345"); - }) + let result: Person = read_response_json(&mut app, req).await; + assert_eq!(&result.id, "12345"); } - #[test] - fn test_request_response_form() { - block_on(async { - let mut app = - init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Form| { - async { HttpResponse::Ok().json(person.into_inner()) } - }), - ))) - .await; + #[actix_rt::test] + async fn test_request_response_form() { + let mut app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Form| { + async { HttpResponse::Ok().json(person.into_inner()) } + }), + ))) + .await; - let payload = Person { - id: "12345".to_string(), - name: "User name".to_string(), - }; + let payload = Person { + id: "12345".to_string(), + name: "User name".to_string(), + }; - let req = TestRequest::post() - .uri("/people") - .set_form(&payload) - .to_request(); + let req = TestRequest::post() + .uri("/people") + .set_form(&payload) + .to_request(); - assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); + assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); - let result: Person = read_response_json(&mut app, req).await; - assert_eq!(&result.id, "12345"); - assert_eq!(&result.name, "User name"); - }) + let result: Person = read_response_json(&mut app, req).await; + assert_eq!(&result.id, "12345"); + assert_eq!(&result.name, "User name"); } - #[test] - fn test_request_response_json() { - block_on(async { - let mut app = - init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - async { HttpResponse::Ok().json(person.into_inner()) } - }), - ))) - .await; + #[actix_rt::test] + async fn test_request_response_json() { + let mut app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| { + async { HttpResponse::Ok().json(person.into_inner()) } + }), + ))) + .await; - let payload = Person { - id: "12345".to_string(), - name: "User name".to_string(), - }; + let payload = Person { + id: "12345".to_string(), + name: "User name".to_string(), + }; - let req = TestRequest::post() - .uri("/people") - .set_json(&payload) - .to_request(); + let req = TestRequest::post() + .uri("/people") + .set_json(&payload) + .to_request(); - assert_eq!(req.content_type(), "application/json"); + assert_eq!(req.content_type(), "application/json"); - let result: Person = read_response_json(&mut app, req).await; - assert_eq!(&result.id, "12345"); - assert_eq!(&result.name, "User name"); - }) + let result: Person = read_response_json(&mut app, req).await; + assert_eq!(&result.id, "12345"); + assert_eq!(&result.name, "User name"); } - #[test] - fn test_async_with_block() { - block_on(async { - async fn async_with_block() -> Result { - let res = web::block(move || Some(4usize).ok_or("wrong")).await; + #[actix_rt::test] + async fn test_async_with_block() { + async fn async_with_block() -> Result { + let res = web::block(move || Some(4usize).ok_or("wrong")).await; - match res? { - Ok(value) => Ok(HttpResponse::Ok() - .content_type("text/plain") - .body(format!("Async with block value: {}", value))), - Err(_) => panic!("Unexpected"), - } + match res? { + Ok(value) => Ok(HttpResponse::Ok() + .content_type("text/plain") + .body(format!("Async with block value: {}", value))), + Err(_) => panic!("Unexpected"), } + } - let mut app = init_service( - App::new().service(web::resource("/index.html").to(async_with_block)), - ) - .await; + let mut app = init_service( + App::new().service(web::resource("/index.html").to(async_with_block)), + ) + .await; - let req = TestRequest::post().uri("/index.html").to_request(); - let res = app.call(req).await.unwrap(); - assert!(res.status().is_success()); - }) + let req = TestRequest::post().uri("/index.html").to_request(); + let res = app.call(req).await.unwrap(); + assert!(res.status().is_success()); } - // #[test] + // #[actix_rt::test] // fn test_actor() { // use actix::Actor; diff --git a/src/types/form.rs b/src/types/form.rs index c20dc7a0..e1bd5237 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -10,7 +10,7 @@ use actix_http::{Error, HttpMessage, Payload, Response}; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; -use futures::{Stream, StreamExt}; +use futures::StreamExt; use serde::de::DeserializeOwned; use serde::Serialize; @@ -370,7 +370,7 @@ mod tests { use super::*; use crate::http::header::{HeaderValue, CONTENT_TYPE}; - use crate::test::{block_on, TestRequest}; + use crate::test::TestRequest; #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Info { @@ -378,26 +378,22 @@ mod tests { counter: i64, } - #[test] - fn test_form() { - block_on(async { - let (req, mut pl) = TestRequest::with_header( - CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + #[actix_rt::test] + async fn test_form() { + let (req, mut pl) = + TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); - let Form(s) = Form::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!( - s, - Info { - hello: "world".into(), - counter: 123 - } - ); - }) + let Form(s) = Form::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!( + s, + Info { + hello: "world".into(), + counter: 123 + } + ); } fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { @@ -418,95 +414,83 @@ mod tests { } } - #[test] - fn test_urlencoded_error() { - block_on(async { - let (req, mut pl) = TestRequest::with_header( - CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(CONTENT_LENGTH, "xxxx") - .to_http_parts(); - let info = UrlEncoded::::new(&req, &mut pl).await; - assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); - - let (req, mut pl) = TestRequest::with_header( - CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(CONTENT_LENGTH, "1000000") - .to_http_parts(); - let info = UrlEncoded::::new(&req, &mut pl).await; - assert!(eq( - info.err().unwrap(), - UrlencodedError::Overflow { size: 0, limit: 0 } - )); - - let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") - .header(CONTENT_LENGTH, "10") + #[actix_rt::test] + async fn test_urlencoded_error() { + let (req, mut pl) = + TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(CONTENT_LENGTH, "xxxx") .to_http_parts(); - let info = UrlEncoded::::new(&req, &mut pl).await; - assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); - }) + let info = UrlEncoded::::new(&req, &mut pl).await; + assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); + + let (req, mut pl) = + TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(CONTENT_LENGTH, "1000000") + .to_http_parts(); + let info = UrlEncoded::::new(&req, &mut pl).await; + assert!(eq( + info.err().unwrap(), + UrlencodedError::Overflow { size: 0, limit: 0 } + )); + + let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") + .header(CONTENT_LENGTH, "10") + .to_http_parts(); + let info = UrlEncoded::::new(&req, &mut pl).await; + assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); } - #[test] - fn test_urlencoded() { - block_on(async { - let (req, mut pl) = TestRequest::with_header( - CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + #[actix_rt::test] + async fn test_urlencoded() { + let (req, mut pl) = + TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); - let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); - assert_eq!( - info, - Info { - hello: "world".to_owned(), - counter: 123 - } - ); + let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); + assert_eq!( + info, + Info { + hello: "world".to_owned(), + counter: 123 + } + ); - let (req, mut pl) = TestRequest::with_header( - CONTENT_TYPE, - "application/x-www-form-urlencoded; charset=utf-8", - ) - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + let (req, mut pl) = TestRequest::with_header( + CONTENT_TYPE, + "application/x-www-form-urlencoded; charset=utf-8", + ) + .header(CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); - let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); - assert_eq!( - info, - Info { - hello: "world".to_owned(), - counter: 123 - } - ); - }) + let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); + assert_eq!( + info, + Info { + hello: "world".to_owned(), + counter: 123 + } + ); } - #[test] - fn test_responder() { - block_on(async { - let req = TestRequest::default().to_http_request(); + #[actix_rt::test] + async fn test_responder() { + let req = TestRequest::default().to_http_request(); - let form = Form(Info { - hello: "world".to_string(), - counter: 123, - }); - let resp = form.respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/x-www-form-urlencoded") - ); + let form = Form(Info { + hello: "world".to_string(), + counter: 123, + }); + let resp = form.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/x-www-form-urlencoded") + ); - use crate::responder::tests::BodyTest; - assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); - }) + use crate::responder::tests::BodyTest; + assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); } } diff --git a/src/types/json.rs b/src/types/json.rs index 206a4e42..028092d1 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -8,7 +8,7 @@ use std::{fmt, ops}; use bytes::BytesMut; use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; -use futures::{Stream, StreamExt}; +use futures::StreamExt; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; @@ -402,7 +402,7 @@ mod tests { use super::*; use crate::error::InternalError; use crate::http::header; - use crate::test::{block_on, load_stream, TestRequest}; + use crate::test::{load_stream, TestRequest}; use crate::HttpResponse; #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -424,236 +424,222 @@ mod tests { } } - #[test] - fn test_responder() { - block_on(async { - let req = TestRequest::default().to_http_request(); + #[actix_rt::test] + async fn test_responder() { + let req = TestRequest::default().to_http_request(); - let j = Json(MyObject { - name: "test".to_string(), - }); - let resp = j.respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("application/json") - ); + let j = Json(MyObject { + name: "test".to_string(), + }); + let resp = j.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + header::HeaderValue::from_static("application/json") + ); - use crate::responder::tests::BodyTest; - assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}"); - }) + use crate::responder::tests::BodyTest; + assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}"); } - #[test] - fn test_custom_error_responder() { - block_on(async { - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().limit(10).error_handler(|err, _| { - let msg = MyObject { - name: "invalid request".to_string(), - }; - let resp = HttpResponse::BadRequest() - .body(serde_json::to_string(&msg).unwrap()); - InternalError::from_response(err, resp).into() - })) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await; - let mut resp = Response::from_error(s.err().unwrap().into()); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let body = load_stream(resp.take_body()).await.unwrap(); - let msg: MyObject = serde_json::from_slice(&body).unwrap(); - assert_eq!(msg.name, "invalid request"); - }) - } - - #[test] - fn test_extract() { - block_on(async { - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s.name, "test"); - assert_eq!( - s.into_inner(), - MyObject { - name: "test".to_string() - } - ); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().limit(10)) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await; - assert!(format!("{}", s.err().unwrap()) - .contains("Json payload size is bigger than allowed")); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data( - JsonConfig::default() - .limit(10) - .error_handler(|_, _| JsonPayloadError::ContentType.into()), - ) - .to_http_parts(); - let s = Json::::from_request(&req, &mut pl).await; - assert!(format!("{}", s.err().unwrap()).contains("Content type error")); - }) - } - - #[test] - fn test_json_body() { - block_on(async { - let (req, mut pl) = TestRequest::default().to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ) - .to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ) - .to_http_parts(); - - let json = JsonBody::::new(&req, &mut pl, None) - .limit(100) - .await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .to_http_parts(); - - let json = JsonBody::::new(&req, &mut pl, None).await; - assert_eq!( - json.ok().unwrap(), - MyObject { - name: "test".to_owned() - } - ); - }) - } - - #[test] - fn test_with_json_and_bad_content_type() { - block_on(async { - let (req, mut pl) = TestRequest::with_header( + #[actix_rt::test] + async fn test_custom_error_responder() { + let (req, mut pl) = TestRequest::default() + .header( header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), + header::HeaderValue::from_static("application/json"), ) .header( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().limit(4096)) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await; - assert!(s.is_err()) - }) - } - - #[test] - fn test_with_json_and_good_custom_content_type() { - block_on(async { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + .data(JsonConfig::default().limit(10).error_handler(|err, _| { + let msg = MyObject { + name: "invalid request".to_string(), + }; + let resp = HttpResponse::BadRequest() + .body(serde_json::to_string(&msg).unwrap()); + InternalError::from_response(err, resp).into() })) .to_http_parts(); - let s = Json::::from_request(&req, &mut pl).await; - assert!(s.is_ok()) - }) + let s = Json::::from_request(&req, &mut pl).await; + let mut resp = Response::from_error(s.err().unwrap().into()); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + + let body = load_stream(resp.take_body()).await.unwrap(); + let msg: MyObject = serde_json::from_slice(&body).unwrap(); + assert_eq!(msg.name, "invalid request"); } - #[test] - fn test_with_json_and_bad_custom_content_type() { - block_on(async { - let (req, mut pl) = TestRequest::with_header( + #[actix_rt::test] + async fn test_extract() { + let (req, mut pl) = TestRequest::default() + .header( header::CONTENT_TYPE, - header::HeaderValue::from_static("text/html"), + header::HeaderValue::from_static("application/json"), ) .header( header::CONTENT_LENGTH, header::HeaderValue::from_static("16"), ) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) .to_http_parts(); - let s = Json::::from_request(&req, &mut pl).await; - assert!(s.is_err()) - }) + let s = Json::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(s.name, "test"); + assert_eq!( + s.into_inner(), + MyObject { + name: "test".to_string() + } + ); + + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().limit(10)) + .to_http_parts(); + + let s = Json::::from_request(&req, &mut pl).await; + assert!(format!("{}", s.err().unwrap()) + .contains("Json payload size is bigger than allowed")); + + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data( + JsonConfig::default() + .limit(10) + .error_handler(|_, _| JsonPayloadError::ContentType.into()), + ) + .to_http_parts(); + let s = Json::::from_request(&req, &mut pl).await; + assert!(format!("{}", s.err().unwrap()).contains("Content type error")); + } + + #[actix_rt::test] + async fn test_json_body() { + let (req, mut pl) = TestRequest::default().to_http_parts(); + let json = JsonBody::::new(&req, &mut pl, None).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/text"), + ) + .to_http_parts(); + let json = JsonBody::::new(&req, &mut pl, None).await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); + + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000"), + ) + .to_http_parts(); + + let json = JsonBody::::new(&req, &mut pl, None) + .limit(100) + .await; + assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); + + let (req, mut pl) = TestRequest::default() + .header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .to_http_parts(); + + let json = JsonBody::::new(&req, &mut pl, None).await; + assert_eq!( + json.ok().unwrap(), + MyObject { + name: "test".to_owned() + } + ); + } + + #[actix_rt::test] + async fn test_with_json_and_bad_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().limit(4096)) + .to_http_parts(); + + let s = Json::::from_request(&req, &mut pl).await; + assert!(s.is_err()) + } + + #[actix_rt::test] + async fn test_with_json_and_good_custom_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); + + let s = Json::::from_request(&req, &mut pl).await; + assert!(s.is_ok()) + } + + #[actix_rt::test] + async fn test_with_json_and_bad_custom_content_type() { + let (req, mut pl) = TestRequest::with_header( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html"), + ) + .header( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + ) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); + + let s = Json::::from_request(&req, &mut pl).await; + assert!(s.is_err()) } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 43a189e2..b32711e2 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -12,3 +12,4 @@ pub use self::json::{Json, JsonConfig}; pub use self::path::{Path, PathConfig}; pub use self::payload::{Payload, PayloadConfig}; pub use self::query::{Query, QueryConfig}; +pub use self::readlines::Readlines; diff --git a/src/types/path.rs b/src/types/path.rs index 29a574fe..40475930 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -1,5 +1,4 @@ //! Path extractor - use std::sync::Arc; use std::{fmt, ops}; @@ -253,7 +252,7 @@ mod tests { use serde_derive::Deserialize; use super::*; - use crate::test::{block_on, TestRequest}; + use crate::test::TestRequest; use crate::{error, http, HttpResponse}; #[derive(Deserialize, Debug, Display)] @@ -269,118 +268,110 @@ mod tests { value: u32, } - #[test] - fn test_extract_path_single() { - block_on(async { - let resource = ResourceDef::new("/{value}/"); + #[actix_rt::test] + async fn test_extract_path_single() { + let resource = ResourceDef::new("/{value}/"); - let mut req = TestRequest::with_uri("/32/").to_srv_request(); - resource.match_path(req.match_info_mut()); + let mut req = TestRequest::with_uri("/32/").to_srv_request(); + resource.match_path(req.match_info_mut()); - let (req, mut pl) = req.into_parts(); - assert_eq!(*Path::::from_request(&req, &mut pl).await.unwrap(), 32); - assert!(Path::::from_request(&req, &mut pl).await.is_err()); - }) + let (req, mut pl) = req.into_parts(); + assert_eq!(*Path::::from_request(&req, &mut pl).await.unwrap(), 32); + assert!(Path::::from_request(&req, &mut pl).await.is_err()); } - #[test] - fn test_tuple_extract() { - block_on(async { - let resource = ResourceDef::new("/{key}/{value}/"); + #[actix_rt::test] + async fn test_tuple_extract() { + let resource = ResourceDef::new("/{key}/{value}/"); - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - resource.match_path(req.match_info_mut()); + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + resource.match_path(req.match_info_mut()); - let (req, mut pl) = req.into_parts(); - let res = <(Path<(String, String)>,)>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - - let res = <(Path<(String, String)>, Path<(String, String)>)>::from_request( - &req, &mut pl, - ) + let (req, mut pl) = req.into_parts(); + let res = <(Path<(String, String)>,)>::from_request(&req, &mut pl) .await .unwrap(); - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - assert_eq!((res.1).0, "name"); - assert_eq!((res.1).1, "user1"); + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); - let () = <()>::from_request(&req, &mut pl).await.unwrap(); - }) + let res = <(Path<(String, String)>, Path<(String, String)>)>::from_request( + &req, &mut pl, + ) + .await + .unwrap(); + assert_eq!((res.0).0, "name"); + assert_eq!((res.0).1, "user1"); + assert_eq!((res.1).0, "name"); + assert_eq!((res.1).1, "user1"); + + let () = <()>::from_request(&req, &mut pl).await.unwrap(); } - #[test] - fn test_request_extract() { - block_on(async { - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + #[actix_rt::test] + async fn test_request_extract() { + let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); - let (req, mut pl) = req.into_parts(); - let mut s = Path::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - s.value = "user2".to_string(); - assert_eq!(s.value, "user2"); - assert_eq!( - format!("{}, {:?}", s, s), - "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" - ); - let s = s.into_inner(); - assert_eq!(s.value, "user2"); + let (req, mut pl) = req.into_parts(); + let mut s = Path::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); + s.value = "user2".to_string(); + assert_eq!(s.value, "user2"); + assert_eq!( + format!("{}, {:?}", s, s), + "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" + ); + let s = s.into_inner(); + assert_eq!(s.value, "user2"); - let s = Path::<(String, String)>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); + let s = Path::<(String, String)>::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); - let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); + let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); + let resource = ResourceDef::new("/{key}/{value}/"); + resource.match_path(req.match_info_mut()); - let (req, mut pl) = req.into_parts(); - let s = Path::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); + let (req, mut pl) = req.into_parts(); + let s = Path::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(s.as_ref().key, "name"); + assert_eq!(s.value, 32); - let s = Path::<(String, u8)>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); + let s = Path::<(String, u8)>::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); - let res = Path::>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); - }) + let res = Path::>::from_request(&req, &mut pl) + .await + .unwrap(); + assert_eq!(res[0], "name".to_owned()); + assert_eq!(res[1], "32".to_owned()); } - #[test] - fn test_custom_err_handler() { - block_on(async { - let (req, mut pl) = TestRequest::with_uri("/name/user1/") - .data(PathConfig::default().error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ) - .into() - })) - .to_http_parts(); + #[actix_rt::test] + async fn test_custom_err_handler() { + let (req, mut pl) = TestRequest::with_uri("/name/user1/") + .data(PathConfig::default().error_handler(|err, _| { + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish(), + ) + .into() + })) + .to_http_parts(); - let s = Path::<(usize,)>::from_request(&req, &mut pl) - .await - .unwrap_err(); - let res: HttpResponse = s.into(); + let s = Path::<(usize,)>::from_request(&req, &mut pl) + .await + .unwrap_err(); + let res: HttpResponse = s.into(); - assert_eq!(res.status(), http::StatusCode::CONFLICT); - }) + assert_eq!(res.status(), http::StatusCode::CONFLICT); } } diff --git a/src/types/payload.rs b/src/types/payload.rs index ee7e1166..2969e385 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -395,10 +395,10 @@ mod tests { use super::*; use crate::http::header; - use crate::test::{block_on, TestRequest}; + use crate::test::TestRequest; - #[test] - fn test_payload_config() { + #[actix_rt::test] + async fn test_payload_config() { let req = TestRequest::default().to_http_request(); let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); assert!(cfg.check_mimetype(&req).is_err()); @@ -415,32 +415,32 @@ mod tests { assert!(cfg.check_mimetype(&req).is_ok()); } - #[test] - fn test_bytes() { + #[actix_rt::test] + async fn test_bytes() { let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); - let s = block_on(Bytes::from_request(&req, &mut pl)).unwrap(); + let s = Bytes::from_request(&req, &mut pl).await.unwrap(); assert_eq!(s, Bytes::from_static(b"hello=world")); } - #[test] - fn test_string() { + #[actix_rt::test] + async fn test_string() { let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); - let s = block_on(String::from_request(&req, &mut pl)).unwrap(); + let s = String::from_request(&req, &mut pl).await.unwrap(); assert_eq!(s, "hello=world"); } - #[test] - fn test_message_body() { + #[actix_rt::test] + async fn test_message_body() { let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx") .to_srv_request() .into_parts(); - let res = block_on(HttpMessageBody::new(&req, &mut pl)); + let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { PayloadError::UnknownLength => (), _ => unreachable!("error"), @@ -449,7 +449,7 @@ mod tests { let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "1000000") .to_srv_request() .into_parts(); - let res = block_on(HttpMessageBody::new(&req, &mut pl)); + let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), @@ -458,13 +458,13 @@ mod tests { let (req, mut pl) = TestRequest::default() .set_payload(Bytes::from_static(b"test")) .to_http_parts(); - let res = block_on(HttpMessageBody::new(&req, &mut pl)); + let res = HttpMessageBody::new(&req, &mut pl).await; assert_eq!(res.ok().unwrap(), Bytes::from_static(b"test")); let (req, mut pl) = TestRequest::default() .set_payload(Bytes::from_static(b"11111111111111")) .to_http_parts(); - let res = block_on(HttpMessageBody::new(&req, &mut pl).limit(5)); + let res = HttpMessageBody::new(&req, &mut pl).limit(5).await; match res.err().unwrap() { PayloadError::Overflow => (), _ => unreachable!("error"), diff --git a/src/types/query.rs b/src/types/query.rs index e442f1c3..b1f4572f 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -228,7 +228,7 @@ mod tests { use super::*; use crate::error::InternalError; - use crate::test::{block_on, TestRequest}; + use crate::test::TestRequest; use crate::HttpResponse; #[derive(Deserialize, Debug, Display)] @@ -236,8 +236,8 @@ mod tests { id: String, } - #[test] - fn test_service_request_extract() { + #[actix_rt::test] + async fn test_service_request_extract() { let req = TestRequest::with_uri("/name/user1/").to_srv_request(); assert!(Query::::from_query(&req.query_string()).is_err()); @@ -252,48 +252,44 @@ mod tests { assert_eq!(s.id, "test1"); } - #[test] - fn test_request_extract() { - block_on(async { - let req = TestRequest::with_uri("/name/user1/").to_srv_request(); - let (req, mut pl) = req.into_parts(); - assert!(Query::::from_request(&req, &mut pl).await.is_err()); + #[actix_rt::test] + async fn test_request_extract() { + let req = TestRequest::with_uri("/name/user1/").to_srv_request(); + let (req, mut pl) = req.into_parts(); + assert!(Query::::from_request(&req, &mut pl).await.is_err()); - let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - let (req, mut pl) = req.into_parts(); + let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); + let (req, mut pl) = req.into_parts(); - let mut s = Query::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); + let mut s = Query::::from_request(&req, &mut pl).await.unwrap(); + assert_eq!(s.id, "test"); + assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); - s.id = "test1".to_string(); - let s = s.into_inner(); - assert_eq!(s.id, "test1"); - }) + s.id = "test1".to_string(); + let s = s.into_inner(); + assert_eq!(s.id, "test1"); } - #[test] - fn test_custom_error_responder() { - block_on(async { - let req = TestRequest::with_uri("/name/user1/") - .data(QueryConfig::default().error_handler(|e, _| { - let resp = HttpResponse::UnprocessableEntity().finish(); - InternalError::from_response(e, resp).into() - })) - .to_srv_request(); + #[actix_rt::test] + async fn test_custom_error_responder() { + let req = TestRequest::with_uri("/name/user1/") + .data(QueryConfig::default().error_handler(|e, _| { + let resp = HttpResponse::UnprocessableEntity().finish(); + InternalError::from_response(e, resp).into() + })) + .to_srv_request(); - let (req, mut pl) = req.into_parts(); - let query = Query::::from_request(&req, &mut pl).await; + let (req, mut pl) = req.into_parts(); + let query = Query::::from_request(&req, &mut pl).await; - assert!(query.is_err()); - assert_eq!( - query - .unwrap_err() - .as_response_error() - .error_response() - .status(), - StatusCode::UNPROCESSABLE_ENTITY - ); - }) + assert!(query.is_err()); + assert_eq!( + query + .unwrap_err() + .as_response_error() + .error_response() + .status(), + StatusCode::UNPROCESSABLE_ENTITY + ); } } diff --git a/src/types/readlines.rs b/src/types/readlines.rs index e2b3f9c1..123f8102 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::future::Future; use std::pin::Pin; use std::str; use std::task::{Context, Poll}; @@ -7,7 +6,6 @@ use std::task::{Context, Poll}; use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; use futures::Stream; -use pin_project::pin_project; use crate::dev::Payload; use crate::error::{PayloadError, ReadlinesError}; @@ -174,12 +172,11 @@ mod tests { use futures::stream::StreamExt; use super::*; - use crate::test::{block_on, TestRequest}; + use crate::test::TestRequest; - #[test] - fn test_readlines() { - block_on(async { - let mut req = TestRequest::default() + #[actix_rt::test] + async fn test_readlines() { + let mut req = TestRequest::default() .set_payload(Bytes::from_static( b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ industry. Lorem Ipsum has been the industry's standard dummy\n\ @@ -187,21 +184,20 @@ mod tests { )) .to_request(); - let mut stream = Readlines::new(&mut req); - assert_eq!( - stream.next().await.unwrap().unwrap(), - "Lorem Ipsum is simply dummy text of the printing and typesetting\n" - ); + let mut stream = Readlines::new(&mut req); + assert_eq!( + stream.next().await.unwrap().unwrap(), + "Lorem Ipsum is simply dummy text of the printing and typesetting\n" + ); - assert_eq!( - stream.next().await.unwrap().unwrap(), - "industry. Lorem Ipsum has been the industry's standard dummy\n" - ); + assert_eq!( + stream.next().await.unwrap().unwrap(), + "industry. Lorem Ipsum has been the industry's standard dummy\n" + ); - assert_eq!( - stream.next().await.unwrap().unwrap(), - "Contrary to popular belief, Lorem Ipsum is not simply random text." - ); - }) + assert_eq!( + stream.next().await.unwrap().unwrap(), + "Contrary to popular belief, Lorem Ipsum is not simply random text." + ); } } diff --git a/src/web.rs b/src/web.rs index 22630ae8..7f1e8d8f 100644 --- a/src/web.rs +++ b/src/web.rs @@ -6,7 +6,6 @@ pub use actix_http::Response as HttpResponse; pub use bytes::{Bytes, BytesMut}; pub use futures::channel::oneshot::Canceled; -use crate::error::Error; use crate::extract::FromRequest; use crate::handler::Factory; use crate::resource::Resource; diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index a2b03ffc..e59e439f 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -54,8 +54,6 @@ slab = "0.4" serde_urlencoded = "0.6.1" time = "0.1" tokio-net = "0.2.0-alpha.6" -tokio-timer = "0.3.0-alpha.6" - open-ssl = { version="0.10", package="openssl", optional = true } [dev-dependencies] diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 1911c75d..9ad06397 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -23,26 +23,25 @@ pub use actix_testing::*; /// /// ```rust /// use actix_http::HttpService; -/// use actix_http_test::{block_on, TestServer}; +/// use actix_http_test::TestServer; /// use actix_web::{web, App, HttpResponse, Error}; /// /// async fn my_handler() -> Result { /// Ok(HttpResponse::Ok().into()) /// } /// -/// fn main() { -/// block_on( async { -/// let mut srv = TestServer::start( -/// || HttpService::new( -/// App::new().service( -/// web::resource("/").to(my_handler)) -/// ) -/// ); +/// #[actix_rt::test] +/// async fn test_example() { +/// let mut srv = TestServer::start( +/// || HttpService::new( +/// App::new().service( +/// web::resource("/").to(my_handler)) +/// ) +/// ); /// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// }) +/// let req = srv.get("/"); +/// let response = req.send().await.unwrap(); +/// assert!(response.status().is_success()); /// } /// ``` pub struct TestServer; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 122f79ba..d19c46ee 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -6,7 +6,7 @@ use std::{net, thread, time::Duration}; use open_ssl::ssl::SslAcceptorBuilder; use actix_http::Response; -use actix_web::{test, web, App, HttpServer}; +use actix_web::{web, App, HttpServer}; fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); @@ -17,9 +17,9 @@ fn unused_addr() -> net::SocketAddr { tcp.local_addr().unwrap() } -#[test] #[cfg(unix)] -fn test_start() { +#[actix_rt::test] +async fn test_start() { let addr = unused_addr(); let (tx, rx) = mpsc::channel(); @@ -53,21 +53,18 @@ fn test_start() { #[cfg(feature = "client")] { use actix_http::client; - use actix_web::test; - test::block_on(async { - let client = awc::Client::build() - .connector( - client::Connector::new() - .timeout(Duration::from_millis(100)) - .finish(), - ) - .finish(); + let client = awc::Client::build() + .connector( + client::Connector::new() + .timeout(Duration::from_millis(100)) + .finish(), + ) + .finish(); - let host = format!("http://{}", addr); - let response = client.get(host.clone()).send().await.unwrap(); - assert!(response.status().is_success()); - }); + let host = format!("http://{}", addr); + let response = client.get(host.clone()).send().await.unwrap(); + assert!(response.status().is_success()); } // stop @@ -91,9 +88,9 @@ fn ssl_acceptor() -> std::io::Result { Ok(builder) } -#[test] +#[actix_rt::test] #[cfg(feature = "openssl")] -fn test_start_ssl() { +async fn test_start_ssl() { let addr = unused_addr(); let (tx, rx) = mpsc::channel(); @@ -119,27 +116,25 @@ fn test_start_ssl() { }); let (srv, sys) = rx.recv().unwrap(); - test::block_on(async move { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - let client = awc::Client::build() - .connector( - awc::Connector::new() - .ssl(builder.build()) - .timeout(Duration::from_millis(100)) - .finish(), - ) - .finish(); + let client = awc::Client::build() + .connector( + awc::Connector::new() + .ssl(builder.build()) + .timeout(Duration::from_millis(100)) + .finish(), + ) + .finish(); - let host = format!("https://{}", addr); - let response = client.get(host.clone()).send().await.unwrap(); - assert!(response.status().is_success()); - }); + let host = format!("https://{}", addr); + let response = client.get(host.clone()).send().await.unwrap(); + assert!(response.status().is_success()); // stop let _ = srv.stop(false); diff --git a/tests/test_server.rs b/tests/test_server.rs index 0114b21f..bfdf3f0e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -5,7 +5,7 @@ use actix_http::http::header::{ TRANSFER_ENCODING, }; use actix_http::{h1, Error, HttpService, Response}; -use actix_http_test::{block_on, TestServer}; +use actix_http_test::TestServer; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; use flate2::read::GzDecoder; @@ -39,728 +39,676 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[test] -fn test_body() { - block_on(async { - let srv = - TestServer::start(|| { - h1::H1Service::new(App::new().service( - web::resource("/").route(web::to(|| Response::Ok().body(STR))), - )) - }); +#[actix_rt::test] +async fn test_body() { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), + ) + }); - let mut response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); + let mut response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -#[test] -fn test_body_gzip() { - block_on(async { - let srv = TestServer::start(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/").route(web::to(|| Response::Ok().body(STR))), - ), - ) - }); +#[actix_rt::test] +async fn test_body_gzip() { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), + ) + }); - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - }) + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -#[test] -fn test_body_gzip2() { - block_on(async { - let srv = TestServer::start(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - Response::Ok().body(STR).into_body::() - }))), - ) - }); +#[actix_rt::test] +async fn test_body_gzip2() { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + Response::Ok().body(STR).into_body::() + }))), + ) + }); - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - }) + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -#[test] -fn test_body_encoding_override() { - block_on(async { - let srv = TestServer::start(|| { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - Response::Ok().encoding(ContentEncoding::Deflate).body(STR) - }))) - .service(web::resource("/raw").route(web::to(|| { - let body = actix_web::dev::Body::Bytes(STR.into()); - let mut response = - Response::with_body(actix_web::http::StatusCode::OK, body); +#[actix_rt::test] +async fn test_body_encoding_override() { + let srv = TestServer::start(|| { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + Response::Ok().encoding(ContentEncoding::Deflate).body(STR) + }))) + .service(web::resource("/raw").route(web::to(|| { + let body = actix_web::dev::Body::Bytes(STR.into()); + let mut response = + Response::with_body(actix_web::http::StatusCode::OK, body); - response.encoding(ContentEncoding::Deflate); + response.encoding(ContentEncoding::Deflate); - response - }))), - ) - }); + response + }))), + ) + }); - // Builder - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "deflate") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); + // Builder + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "deflate") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); + // decode + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - // Raw Response - let mut response = srv - .request(actix_web::http::Method::GET, srv.url("/raw")) - .no_decompress() - .header(ACCEPT_ENCODING, "deflate") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); + // Raw Response + let mut response = srv + .request(actix_web::http::Method::GET, srv.url("/raw")) + .no_decompress() + .header(ACCEPT_ENCODING, "deflate") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - }) + // decode + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -#[test] -fn test_body_gzip_large() { - block_on(async { - let data = STR.repeat(10); - let srv_data = data.clone(); +#[actix_rt::test] +async fn test_body_gzip_large() { + let data = STR.repeat(10); + let srv_data = data.clone(); - let srv = TestServer::start(move || { - let data = srv_data.clone(); - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(data.clone()))), - ), - ) - }); - - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from(data)); - }) -} - -#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -#[test] -fn test_body_gzip_large_random() { - block_on(async { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(70_000) - .collect::(); - let srv_data = data.clone(); - - let srv = TestServer::start(move || { - let data = srv_data.clone(); - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(data.clone()))), - ), - ) - }); - - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(dec.len(), data.len()); - assert_eq!(Bytes::from(dec), Bytes::from(data)); - }) -} - -#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -#[test] -fn test_body_chunked_implicit() { - block_on(async { - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::get().to(move || { - Response::Ok().streaming(once(ok::<_, Error>( - Bytes::from_static(STR.as_ref()), - ))) - }))), - ) - }); - - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response.headers().get(TRANSFER_ENCODING).unwrap(), - &b"chunked"[..] - ); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - }) -} - -#[test] -#[cfg(feature = "brotli")] -fn test_body_br_streaming() { - block_on(async { - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || { - Response::Ok().streaming(once(ok::<_, Error>( - Bytes::from_static(STR.as_ref()), - ))) - })), + let srv = TestServer::start(move || { + let data = srv_data.clone(); + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), ), - ) - }); + ) + }); - let mut response = srv - .get("/") - .header(ACCEPT_ENCODING, "br") - .no_decompress() - .send() - .await - .unwrap(); - assert!(response.status().is_success()); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode br - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - }) + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from(data)); } -#[test] -fn test_head_binary() { - block_on(async { - let srv = TestServer::start(move || { - h1::H1Service::new(App::new().service(web::resource("/").route( - web::head().to(move || Response::Ok().content_length(100).body(STR)), - ))) - }); - - let mut response = srv.head("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response.headers().get(CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = response.body().await.unwrap(); - assert!(bytes.is_empty()); - }) -} - -#[test] -fn test_no_chunking() { - block_on(async { - let srv = TestServer::start(move || { - h1::H1Service::new(App::new().service(web::resource("/").route(web::to( - move || { - Response::Ok() - .no_chunking() - .content_length(STR.len() as u64) - .streaming(once(ok::<_, Error>(Bytes::from_static( - STR.as_ref(), - )))) - }, - )))) - }); - - let mut response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(TRANSFER_ENCODING)); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) -} - -#[test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -fn test_body_deflate() { - block_on(async { - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new() - .wrap(Compress::new(ContentEncoding::Deflate)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(STR))), - ), - ) - }); +#[actix_rt::test] +async fn test_body_gzip_large_random() { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(70_000) + .collect::(); + let srv_data = data.clone(); - // client request - let mut response = srv - .get("/") - .header(ACCEPT_ENCODING, "deflate") - .no_decompress() - .send() - .await - .unwrap(); - assert!(response.status().is_success()); + let srv = TestServer::start(move || { + let data = srv_data.clone(); + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), + ) + }); - // read response - let bytes = response.body().await.unwrap(); + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = response.body().await.unwrap(); + + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(dec.len(), data.len()); + assert_eq!(Bytes::from(dec), Bytes::from(data)); } -#[test] -#[cfg(any(feature = "brotli"))] -fn test_body_brotli() { - block_on(async { - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new().wrap(Compress::new(ContentEncoding::Br)).service( +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] +#[actix_rt::test] +async fn test_body_chunked_implicit() { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::get().to(move || { + Response::Ok().streaming(once(ok::<_, Error>(Bytes::from_static( + STR.as_ref(), + )))) + }))), + ) + }); + + let mut response = srv + .get("/") + .no_decompress() + .header(ACCEPT_ENCODING, "gzip") + .send() + .await + .unwrap(); + assert!(response.status().is_success()); + assert_eq!( + response.headers().get(TRANSFER_ENCODING).unwrap(), + &b"chunked"[..] + ); + + // read response + let bytes = response.body().await.unwrap(); + + // decode + let mut e = GzDecoder::new(&bytes[..]); + let mut dec = Vec::new(); + e.read_to_end(&mut dec).unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +#[cfg(feature = "brotli")] +async fn test_body_br_streaming() { + let srv = TestServer::start(move || { + h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || { + Response::Ok() + .streaming(once(ok::<_, Error>(Bytes::from_static(STR.as_ref())))) + })), + )) + }); + + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "br") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + + // decode br + let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +async fn test_head_binary() { + let srv = TestServer::start(move || { + h1::H1Service::new(App::new().service(web::resource("/").route( + web::head().to(move || Response::Ok().content_length(100).body(STR)), + ))) + }); + + let mut response = srv.head("/").send().await.unwrap(); + assert!(response.status().is_success()); + + { + let len = response.headers().get(CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = response.body().await.unwrap(); + assert!(bytes.is_empty()); +} + +#[actix_rt::test] +async fn test_no_chunking() { + let srv = TestServer::start(move || { + h1::H1Service::new(App::new().service(web::resource("/").route(web::to( + move || { + Response::Ok() + .no_chunking() + .content_length(STR.len() as u64) + .streaming(once(ok::<_, Error>(Bytes::from_static(STR.as_ref())))) + }, + )))) + }); + + let mut response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(TRANSFER_ENCODING)); + + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] +#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] +async fn test_body_deflate() { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new() + .wrap(Compress::new(ContentEncoding::Deflate)) + .service( web::resource("/").route(web::to(move || Response::Ok().body(STR))), ), - ) - }); + ) + }); - // client request - let mut response = srv - .get("/") - .header(ACCEPT_ENCODING, "br") - .no_decompress() - .send() - .await - .unwrap(); - assert!(response.status().is_success()); + // client request + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "deflate") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); + // read response + let bytes = response.body().await.unwrap(); - // decode brotli - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - }) + let mut e = ZlibDecoder::new(Vec::new()); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); } -#[test] +#[actix_rt::test] +#[cfg(any(feature = "brotli"))] +async fn test_body_brotli() { + let srv = TestServer::start(move || { + h1::H1Service::new(App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || Response::Ok().body(STR))), + )) + }); + + // client request + let mut response = srv + .get("/") + .header(ACCEPT_ENCODING, "br") + .no_decompress() + .send() + .await + .unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + + // decode brotli + let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); + e.write_all(bytes.as_ref()).unwrap(); + let dec = e.finish().unwrap(); + assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); +} + +#[actix_rt::test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -fn test_encoding() { - block_on(async { - let srv = TestServer::start(move || { - HttpService::new( - App::new().wrap(Compress::default()).service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); +async fn test_encoding() { + let srv = TestServer::start(move || { + HttpService::new( + App::new().wrap(Compress::default()).service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] +#[actix_rt::test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -fn test_gzip_encoding() { - block_on(async { - let srv = TestServer::start(move || { - HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); +async fn test_gzip_encoding() { + let srv = TestServer::start(move || { + HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] +#[actix_rt::test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -fn test_gzip_encoding_large() { - block_on(async { - let data = STR.repeat(10); - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); +async fn test_gzip_encoding_large() { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); } -#[test] +#[actix_rt::test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -fn test_reading_gzip_encoding_large_random() { - block_on(async { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(60_000) - .collect::(); +async fn test_reading_gzip_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(60_000) + .collect::(); - let srv = TestServer::start(move || { - HttpService::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + let srv = TestServer::start(move || { + HttpService::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // client request + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + let request = srv + .post("/") + .header(CONTENT_ENCODING, "gzip") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); } -#[test] +#[actix_rt::test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -fn test_reading_deflate_encoding() { - block_on(async { - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); +async fn test_reading_deflate_encoding() { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[test] +#[actix_rt::test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -fn test_reading_deflate_encoding_large() { - block_on(async { - let data = STR.repeat(10); - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); +async fn test_reading_deflate_encoding_large() { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); } -#[test] +#[actix_rt::test] #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -fn test_reading_deflate_encoding_large_random() { - block_on(async { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); +async fn test_reading_deflate_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "deflate") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); } -#[test] +#[actix_rt::test] #[cfg(feature = "brotli")] -fn test_brotli_encoding() { - block_on(async { - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); +async fn test_brotli_encoding() { + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(STR.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "br") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "br") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } #[cfg(feature = "brotli")] -#[test] -fn test_brotli_encoding_large() { - block_on(async { - let data = STR.repeat(10); - let srv = TestServer::start(move || { - h1::H1Service::new( - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - ), - ) - }); +#[actix_rt::test] +async fn test_brotli_encoding_large() { + let data = STR.repeat(10); + let srv = TestServer::start(move || { + h1::H1Service::new( + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| Response::Ok().body(body))), + ), + ) + }); - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut e = BrotliEncoder::new(Vec::new(), 5); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "br") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); + // client request + let request = srv + .post("/") + .header(CONTENT_ENCODING, "br") + .send_body(enc.clone()); + let mut response = request.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); - }) + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); } // #[cfg(all(feature = "brotli", feature = "ssl"))] -// #[test] -// fn test_brotli_encoding_large_ssl() { +// #[actix_rt::test] +// async fn test_brotli_encoding_large_ssl() { // use actix::{Actor, System}; // use openssl::ssl::{ // SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, @@ -819,91 +767,89 @@ fn test_brotli_encoding_large() { feature = "openssl", any(feature = "flate2-zlib", feature = "flate2-rust") ))] -#[test] -fn test_reading_deflate_encoding_large_random_ssl() { - block_on(async { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rust_tls::internal::pemfile::{certs, pkcs8_private_keys}; - use rust_tls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - use std::sync::mpsc; +#[actix_rt::test] +async fn test_reading_deflate_encoding_large_random_ssl() { + use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use rust_tls::internal::pemfile::{certs, pkcs8_private_keys}; + use rust_tls::{NoClientAuth, ServerConfig}; + use std::fs::File; + use std::io::BufReader; + use std::sync::mpsc; - let addr = TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); + let addr = TestServer::unused_addr(); + let (tx, rx) = mpsc::channel(); - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .collect::(); - std::thread::spawn(move || { - let sys = actix_rt::System::new("test"); + std::thread::spawn(move || { + let sys = actix_rt::System::new("test"); - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + // load ssl keys + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - let srv = HttpServer::new(|| { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - async move { - Ok::<_, Error>( - HttpResponse::Ok() - .encoding(http::ContentEncoding::Identity) - .body(bytes), - ) - } - }))) - }) - .bind_rustls(addr, config) - .unwrap() - .start(); + let srv = HttpServer::new(|| { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + async move { + Ok::<_, Error>( + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .body(bytes), + ) + } + }))) + }) + .bind_rustls(addr, config) + .unwrap() + .start(); - let _ = tx.send((srv, actix_rt::System::current())); - let _ = sys.run(); - }); - let (srv, _sys) = rx.recv().unwrap(); - let client = { - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); + let _ = tx.send((srv, actix_rt::System::current())); + let _ = sys.run(); + }); + let (srv, _sys) = rx.recv().unwrap(); + let client = { + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); - awc::Client::build() - .connector( - awc::Connector::new() - .timeout(std::time::Duration::from_millis(500)) - .ssl(builder.build()) - .finish(), - ) - .finish() - }; + awc::Client::build() + .connector( + awc::Connector::new() + .timeout(std::time::Duration::from_millis(500)) + .ssl(builder.build()) + .finish(), + ) + .finish() + }; - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + // encode data + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); - // client request - let req = client - .post(format!("https://localhost:{}/", addr.port())) - .header(http::header::CONTENT_ENCODING, "deflate") - .send_body(enc); + // client request + let req = client + .post(format!("https://localhost:{}/", addr.port())) + .header(http::header::CONTENT_ENCODING, "deflate") + .send_body(enc); - let mut response = req.await.unwrap(); - assert!(response.status().is_success()); + let mut response = req.await.unwrap(); + assert!(response.status().is_success()); - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); - // stop - let _ = srv.stop(false); - }) + // stop + let _ = srv.stop(false); } // #[cfg(all(feature = "tls", feature = "ssl"))] From f73f97353b812bbe3d872932c8ca51d5de2a1f84 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Nov 2019 16:07:39 +0600 Subject: [PATCH 1631/1635] refactor ResponseError trait --- MIGRATION.md | 50 ++++++++++---------- README.md | 7 ++- actix-cors/src/lib.rs | 4 ++ actix-files/src/error.rs | 4 +- actix-http/src/client/error.rs | 12 ++--- actix-http/src/error.rs | 86 ++++++++++++++++++---------------- actix-http/src/response.rs | 2 +- actix-multipart/src/error.rs | 7 +-- awc/src/error.rs | 8 +--- src/error.rs | 34 +++++--------- src/lib.rs | 2 +- 11 files changed, 105 insertions(+), 111 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 675dc61e..dd3a1b04 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -4,6 +4,8 @@ replace `fn` with `async fn` to convert sync handler to async +* `TestServer::new()` renamed to `TestServer::start()` + ## 1.0.1 @@ -41,52 +43,52 @@ * Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration instead of - + ```rust - + #[derive(Default)] struct ExtractorConfig { config: String, } - + impl FromRequest for YourExtractor { type Config = ExtractorConfig; type Result = Result; - + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { println!("use the config: {:?}", cfg.config); ... } } - + App::new().resource("/route_with_config", |r| { r.post().with_config(handler_fn, |cfg| { cfg.0.config = "test".to_string(); }) }) - + ``` - + use the HttpRequest to get the configuration like any other `Data` with `req.app_data::()` and set it with the `data()` method on the `resource` - + ```rust #[derive(Default)] struct ExtractorConfig { config: String, } - + impl FromRequest for YourExtractor { type Error = Error; type Future = Result; type Config = ExtractorConfig; - + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let cfg = req.app_data::(); println!("config data?: {:?}", cfg.unwrap().role); ... } } - + App::new().service( resource("/route_with_config") .data(ExtractorConfig { @@ -95,7 +97,7 @@ .route(post().to(handler_fn)), ) ``` - + * Resource registration. 1.0 version uses generalized resource registration via `.service()` method. @@ -386,9 +388,9 @@ * `HttpRequest` does not implement `Stream` anymore. If you need to read request payload use `HttpMessage::payload()` method. - + instead of - + ```rust fn index(req: HttpRequest) -> impl Responder { req @@ -414,8 +416,8 @@ trait uses `&HttpRequest` instead of `&mut HttpRequest`. * Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. - - instead of + + instead of ```rust fn index(query: Query<..>, info: Json impl Responder {} @@ -431,7 +433,7 @@ * `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value -* Removed deprecated `HttpServer::threads()`, use +* Removed deprecated `HttpServer::threads()`, use [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. * Renamed `client::ClientConnectorError::Connector` to @@ -440,7 +442,7 @@ * `Route::with()` does not return `ExtractorConfig`, to configure extractor use `Route::with_config()` - instead of + instead of ```rust fn main() { @@ -451,11 +453,11 @@ }); } ``` - - use - + + use + ```rust - + fn main() { let app = App::new().resource("/index.html", |r| { r.method(http::Method::GET) @@ -485,12 +487,12 @@ * `HttpRequest::extensions()` returns read only reference to the request's Extension `HttpRequest::extensions_mut()` returns mutable reference. -* Instead of +* Instead of `use actix_web::middleware::{ CookieSessionBackend, CookieSessionError, RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` - + use `actix_web::middleware::session` `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, diff --git a/README.md b/README.md index 00bb3ec4..4c0553e3 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,15 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. ## Example ```rust -use actix_web::{web, App, HttpServer, Responder}; +use actix_web::{get, App, HttpServer, Responder}; +#[get("/{id}/{name}/index.html")] async fn index(info: web::Path<(u32, String)>) -> impl Responder { format!("Hello {}! id:{}", info.1, info.0) } fn main() -> std::io::Result<()> { - HttpServer::new( - || App::new().service( - web::resource("/{id}/{name}/index.html").to(index))) + HttpServer::new(|| App::new().service(index)) .bind("127.0.0.1:8080")? .run() } diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index 551e3bb4..d3607aa8 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -93,6 +93,10 @@ pub enum CorsError { } impl ResponseError for CorsError { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } + fn error_response(&self) -> HttpResponse { HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self).into()) } diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index ca99fa81..49a46e58 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -35,7 +35,7 @@ pub enum UriSegmentError { /// Return `BadRequest` for `UriSegmentError` impl ResponseError for UriSegmentError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 75f7935f..ee568e8b 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -7,8 +7,7 @@ use trust_dns_resolver::error::ResolveError; use open_ssl::ssl::{Error as SslError, HandshakeError}; use crate::error::{Error, ParseError, ResponseError}; -use crate::http::Error as HttpError; -use crate::response::Response; +use crate::http::{Error as HttpError, StatusCode}; /// A set of errors that can occur while connecting to an HTTP host #[derive(Debug, Display, From)] @@ -117,15 +116,14 @@ pub enum SendRequestError { /// Convert `SendRequestError` to a server `Response` impl ResponseError for SendRequestError { - fn error_response(&self) -> Response { + fn status_code(&self) -> StatusCode { match *self { SendRequestError::Connect(ConnectError::Timeout) => { - Response::GatewayTimeout() + StatusCode::GATEWAY_TIMEOUT } - SendRequestError::Connect(_) => Response::BadGateway(), - _ => Response::InternalServerError(), + SendRequestError::Connect(_) => StatusCode::BAD_REQUEST, + _ => StatusCode::INTERNAL_SERVER_ERROR, } - .into() } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index f1767cf1..587849bd 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -59,16 +59,18 @@ impl Error { /// Error that can be converted to `Response` pub trait ResponseError: fmt::Debug + fmt::Display { + /// Response's status code + /// + /// Internal server error is generated by default. + fn status_code(&self) -> StatusCode { + StatusCode::INTERNAL_SERVER_ERROR + } + /// Create response for error /// /// Internal server error is generated by default. fn error_response(&self) -> Response { - Response::new(StatusCode::INTERNAL_SERVER_ERROR) - } - - /// Constructs an error response - fn render_response(&self) -> Response { - let mut resp = self.error_response(); + let mut resp = Response::new(self.status_code()); let mut buf = BytesMut::new(); let _ = write!(Writer(&mut buf), "{}", self); resp.headers_mut().insert( @@ -156,10 +158,10 @@ impl From for Error { /// Return `GATEWAY_TIMEOUT` for `TimeoutError` impl ResponseError for TimeoutError { - fn error_response(&self) -> Response { + fn status_code(&self) -> StatusCode { match self { - TimeoutError::Service(e) => e.error_response(), - TimeoutError::Timeout => Response::new(StatusCode::GATEWAY_TIMEOUT), + TimeoutError::Service(e) => e.status_code(), + TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT, } } } @@ -187,8 +189,8 @@ impl ResponseError for open_ssl::ssl::HandshakeError {} /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } @@ -197,8 +199,8 @@ impl ResponseError for Canceled {} /// Return `BAD_REQUEST` for `Utf8Error` impl ResponseError for Utf8Error { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } @@ -208,26 +210,26 @@ impl ResponseError for HttpError {} /// Return `InternalServerError` for `io::Error` impl ResponseError for io::Error { - fn error_response(&self) -> Response { + fn status_code(&self) -> StatusCode { match self.kind() { - io::ErrorKind::NotFound => Response::new(StatusCode::NOT_FOUND), - io::ErrorKind::PermissionDenied => Response::new(StatusCode::FORBIDDEN), - _ => Response::new(StatusCode::INTERNAL_SERVER_ERROR), + io::ErrorKind::NotFound => StatusCode::NOT_FOUND, + io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN, + _ => StatusCode::INTERNAL_SERVER_ERROR, } } } /// `BadRequest` for `InvalidHeaderValue` impl ResponseError for header::InvalidHeaderValue { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } /// `BadRequest` for `InvalidHeaderValue` impl ResponseError for header::InvalidHeaderValueBytes { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } @@ -270,8 +272,8 @@ pub enum ParseError { /// Return `BadRequest` for `ParseError` impl ResponseError for ParseError { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } @@ -371,18 +373,18 @@ impl From for PayloadError { /// - `Overflow` returns `PayloadTooLarge` /// - Other errors returns `BadRequest` impl ResponseError for PayloadError { - fn error_response(&self) -> Response { + fn status_code(&self) -> StatusCode { match *self { - PayloadError::Overflow => Response::new(StatusCode::PAYLOAD_TOO_LARGE), - _ => Response::new(StatusCode::BAD_REQUEST), + PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, + _ => StatusCode::BAD_REQUEST, } } } /// Return `BadRequest` for `cookie::ParseError` impl ResponseError for crate::cookie::ParseError { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } @@ -446,8 +448,8 @@ pub enum ContentTypeError { /// Return `BadRequest` for `ContentTypeError` impl ResponseError for ContentTypeError { - fn error_response(&self) -> Response { - Response::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } @@ -517,6 +519,19 @@ impl ResponseError for InternalError where T: fmt::Debug + fmt::Display + 'static, { + fn status_code(&self) -> StatusCode { + match self.status { + InternalErrorType::Status(st) => st, + InternalErrorType::Response(ref resp) => { + if let Some(resp) = resp.borrow().as_ref() { + resp.head().status + } else { + StatusCode::INTERNAL_SERVER_ERROR + } + } + } + } + fn error_response(&self) -> Response { match self.status { InternalErrorType::Status(st) => { @@ -538,11 +553,6 @@ where } } } - - /// Constructs an error response - fn render_response(&self) -> Response { - self.error_response() - } } /// Convert Response to a Error @@ -947,11 +957,7 @@ mod failure_integration { use super::*; /// Compatibility for `failure::Error` - impl ResponseError for failure::Error { - fn error_response(&self) -> Response { - Response::new(StatusCode::INTERNAL_SERVER_ERROR) - } - } + impl ResponseError for failure::Error {} } #[cfg(test)] diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 5eb0228d..e9147aa4 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -53,7 +53,7 @@ impl Response { /// Constructs an error response #[inline] pub fn from_error(error: Error) -> Response { - let mut resp = error.as_response_error().render_response(); + let mut resp = error.as_response_error().error_response(); if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { error!("Internal Server Error: {:?}", error); } diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs index 32c740a1..6677f69c 100644 --- a/actix-multipart/src/error.rs +++ b/actix-multipart/src/error.rs @@ -1,7 +1,7 @@ //! Error and Result module use actix_web::error::{ParseError, PayloadError}; use actix_web::http::StatusCode; -use actix_web::{HttpResponse, ResponseError}; +use actix_web::ResponseError; use derive_more::{Display, From}; /// A set of errors that can occur during parsing multipart streams @@ -35,14 +35,15 @@ pub enum MultipartError { /// Return `BadRequest` for `MultipartError` impl ResponseError for MultipartError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } #[cfg(test)] mod tests { use super::*; + use actix_web::HttpResponse; #[test] fn test_multipart_error() { diff --git a/awc/src/error.rs b/awc/src/error.rs index eb8d03e2..8816c407 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -6,7 +6,7 @@ pub use actix_http::error::PayloadError; pub use actix_http::ws::HandshakeError as WsHandshakeError; pub use actix_http::ws::ProtocolError as WsProtocolError; -use actix_http::{Response, ResponseError}; +use actix_http::ResponseError; use serde_json::error::Error as JsonError; use actix_http::http::{header::HeaderValue, Error as HttpError, StatusCode}; @@ -68,8 +68,4 @@ pub enum JsonPayloadError { } /// Return `InternalServerError` for `JsonPayloadError` -impl ResponseError for JsonPayloadError { - fn error_response(&self) -> Response { - Response::new(StatusCode::INTERNAL_SERVER_ERROR) - } -} +impl ResponseError for JsonPayloadError {} diff --git a/src/error.rs b/src/error.rs index a60276a7..2eec7c51 100644 --- a/src/error.rs +++ b/src/error.rs @@ -54,15 +54,11 @@ pub enum UrlencodedError { /// Return `BadRequest` for `UrlencodedError` impl ResponseError for UrlencodedError { - fn error_response(&self) -> HttpResponse { + fn status_code(&self) -> StatusCode { match *self { - UrlencodedError::Overflow { .. } => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - UrlencodedError::UnknownLength => { - HttpResponse::new(StatusCode::LENGTH_REQUIRED) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + UrlencodedError::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE, + UrlencodedError::UnknownLength => StatusCode::LENGTH_REQUIRED, + _ => StatusCode::BAD_REQUEST, } } } @@ -106,10 +102,8 @@ pub enum PathError { /// Return `BadRequest` for `PathError` impl ResponseError for PathError { - fn error_response(&self) -> HttpResponse { - match *self { - PathError::Deserialize(_) => HttpResponse::new(StatusCode::BAD_REQUEST), - } + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } @@ -123,12 +117,8 @@ pub enum QueryPayloadError { /// Return `BadRequest` for `QueryPayloadError` impl ResponseError for QueryPayloadError { - fn error_response(&self) -> HttpResponse { - match *self { - QueryPayloadError::Deserialize(_) => { - HttpResponse::new(StatusCode::BAD_REQUEST) - } - } + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST } } @@ -152,12 +142,10 @@ pub enum ReadlinesError { /// Return `BadRequest` for `ReadlinesError` impl ResponseError for ReadlinesError { - fn error_response(&self) -> HttpResponse { + fn status_code(&self) -> StatusCode { match *self { - ReadlinesError::LimitOverflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), + ReadlinesError::LimitOverflow => StatusCode::PAYLOAD_TOO_LARGE, + _ => StatusCode::BAD_REQUEST, } } } diff --git a/src/lib.rs b/src/lib.rs index 4d1facd8..b7fd8d15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ //! * SSL support with OpenSSL or `native-tls` //! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`) //! * Supports [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.36 or later +//! * Supported Rust version: 1.39 or later //! //! ## Package feature //! From f2b3dc5625e09e0cefc33983e4d87339f9780999 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 26 Nov 2019 17:16:33 +0600 Subject: [PATCH 1632/1635] update examples --- README.md | 6 ++++-- examples/basic.rs | 6 ++++-- examples/client.rs | 32 +++++++++++++++----------------- examples/uds.rs | 6 ++++-- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4c0553e3..b7a1bf28 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,12 @@ async fn index(info: web::Path<(u32, String)>) -> impl Responder { format!("Hello {}! id:{}", info.1, info.0) } -fn main() -> std::io::Result<()> { +#[actix_rt::main] +async fn main() -> std::io::Result<()> { HttpServer::new(|| App::new().service(index)) .bind("127.0.0.1:8080")? - .run() + .start() + .await } ``` diff --git a/examples/basic.rs b/examples/basic.rs index 6d9a4dcd..b5b69fce 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -16,7 +16,8 @@ async fn no_params() -> &'static str { "Hello world!\r\n" } -fn main() -> std::io::Result<()> { +#[actix_rt::main] +async fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init(); @@ -41,5 +42,6 @@ fn main() -> std::io::Result<()> { }) .bind("127.0.0.1:8080")? .workers(1) - .run() + .start() + .await } diff --git a/examples/client.rs b/examples/client.rs index 90a362fe..874e08e1 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -1,27 +1,25 @@ use actix_http::Error; -use actix_rt::System; -fn main() -> Result<(), Error> { +#[actix_rt::main] +async fn main() -> Result<(), Error> { std::env::set_var("RUST_LOG", "actix_http=trace"); env_logger::init(); - System::new("test").block_on(async { - let client = awc::Client::new(); + let client = awc::Client::new(); - // Create request builder, configure request and send - let mut response = client - .get("https://www.rust-lang.org/") - .header("User-Agent", "Actix-web") - .send() - .await?; + // Create request builder, configure request and send + let mut response = client + .get("https://www.rust-lang.org/") + .header("User-Agent", "Actix-web") + .send() + .await?; - // server http response - println!("Response: {:?}", response); + // server http response + println!("Response: {:?}", response); - // read response body - let body = response.body().await?; - println!("Downloaded: {:?} bytes", body.len()); + // read response body + let body = response.body().await?; + println!("Downloaded: {:?} bytes", body.len()); - Ok(()) - }) + Ok(()) } diff --git a/examples/uds.rs b/examples/uds.rs index fc6a58de..8db4cf23 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -19,7 +19,8 @@ async fn no_params() -> &'static str { } #[cfg(unix)] -fn main() -> std::io::Result<()> { +#[actix_rt::main] +async fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init(); @@ -44,7 +45,8 @@ fn main() -> std::io::Result<()> { }) .bind_uds("/Users/fafhrd91/uds-test")? .workers(1) - .run() + .start() + .await } #[cfg(not(unix))] From f43a7063642bebe8b76f472beb5a9d6dfdb42ed6 Mon Sep 17 00:00:00 2001 From: Folyd Date: Tue, 26 Nov 2019 19:40:29 +0800 Subject: [PATCH 1633/1635] Set name for each generated resource --- actix-web-codegen/src/route.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index f8e2496c..16d3e815 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -184,6 +184,7 @@ impl Route { pub fn generate(&self) -> TokenStream { let name = &self.name; + let resource_name = name.to_string(); let guard = &self.guard; let ast = &self.ast; let path = &self.args.path; @@ -196,8 +197,8 @@ impl Route { impl actix_web::dev::HttpServiceFactory for #name { fn register(self, config: &mut actix_web::dev::AppService) { #ast - let resource = actix_web::Resource::new(#path) + .name(#resource_name) .guard(actix_web::guard::#guard()) #(.guard(actix_web::guard::fn_guard(#extra_guards)))* .#resource_type(#name); From 56b9f11c981f532556916b54205de2f5fc173fa7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 27 Nov 2019 21:07:49 +0600 Subject: [PATCH 1634/1635] disable rustls --- Cargo.toml | 4 ++-- actix-http/Cargo.toml | 9 +++++---- awc/Cargo.toml | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1875eb7..02e4ac0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ fail = ["actix-http/fail"] openssl = ["open-ssl", "actix-server/openssl", "awc/openssl"] # rustls -rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"] +# rustls = ["rust-tls", "actix-server/rustls", "awc/rustls"] [dependencies] actix-codec = "0.2.0-alpha.1" @@ -101,7 +101,7 @@ url = "2.1" # ssl support open-ssl = { version="0.10", package="openssl", optional = true } -rust-tls = { version = "0.16", package="rustls", optional = true } +# rust-tls = { version = "0.16", package="rustls", optional = true } [dev-dependencies] # actix = "0.8.3" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index cfed0bf1..9a14abef 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -29,7 +29,7 @@ default = [] openssl = ["open-ssl", "actix-connect/openssl", "tokio-openssl"] # rustls support -rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] +# rustls = ["rust-tls", "webpki-roots", "actix-connect/rustls"] # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -99,11 +99,12 @@ failure = { version = "0.1.5", optional = true } open-ssl = { version="0.10", package="openssl", optional = true } tokio-openssl = { version = "0.4.0-alpha.6", optional = true } -rust-tls = { version = "0.16.0", package="rustls", optional = true } -webpki-roots = { version = "0.18", optional = true } +# rust-tls = { version = "0.16.0", package="rustls", optional = true } +# webpki-roots = { version = "0.18", optional = true } [dev-dependencies] -actix-server = { version = "0.8.0-alpha.1", features=["openssl", "rustls"] } +#actix-server = { version = "0.8.0-alpha.1", features=["openssl", "rustls"] } +actix-server = { version = "0.8.0-alpha.1", features=["openssl"] } actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } actix-http-test = { version = "0.3.0-alpha.1", features=["openssl"] } env_logger = "0.6" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 1b35c279..e9268aac 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -30,7 +30,7 @@ default = ["brotli", "flate2-zlib"] openssl = ["open-ssl", "actix-http/openssl"] # rustls -rustls = ["rust-tls", "actix-http/rustls"] +# rustls = ["rust-tls", "actix-http/rustls"] # brotli encoding, requires c compiler brotli = ["actix-http/brotli"] @@ -59,7 +59,7 @@ serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.6.1" open-ssl = { version="0.10", package="openssl", optional = true } -rust-tls = { version = "0.16.0", package="rustls", optional = true, features = ["dangerous_configuration"] } +# rust-tls = { version = "0.16.0", package="rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] actix-connect = { version = "1.0.0-alpha.1", features=["openssl"] } From 33574403b50a61f7cec47103e3a666b002e4845b Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Thu, 28 Nov 2019 09:25:21 +0900 Subject: [PATCH 1635/1635] Remove `rustls` from `package.metadata.docs.rs` (#1182) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 02e4ac0a..689f7b14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" [package.metadata.docs.rs] -features = ["openssl", "rustls", "brotli", "flate2-zlib", "secure-cookies", "client"] +features = ["openssl", "brotli", "flate2-zlib", "secure-cookies", "client"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" }

  • {}/